Merge "Adding mutexes to avoid crashes Adding shelf for play view take trick"
diff --git a/go/src/hearts/img/reposition/reposition.go b/go/src/hearts/img/reposition/reposition.go
index 74d92df..1d83c72 100644
--- a/go/src/hearts/img/reposition/reposition.go
+++ b/go/src/hearts/img/reposition/reposition.go
@@ -7,8 +7,6 @@
package reposition
import (
- "time"
-
"hearts/img/coords"
"hearts/img/direction"
"hearts/img/staticimg"
@@ -128,7 +126,7 @@
dropTargetDim := u.DropTargets[playerIndex].GetDimensions()
targetCenter := dropTargetXY.PlusVec(dropTargetDim.DividedBy(2))
xPlayerBlockSize := 2*u.PlayerIconDim.X + u.Padding
- yPlayerBlockSize := u.TopPadding + 2*u.TableCardDim.Y + 3*u.Padding + u.PlayerIconDim.Y
+ yPlayerBlockSize := u.TopPadding + u.TableCardDim.Y + 3*u.Padding + u.PlayerIconDim.Y
blockEdge := targetCenter.MinusVec(cardDim.Times(1.5).Plus(u.Padding))
var destination *coords.Vec
switch playerIndex {
@@ -143,7 +141,7 @@
case 2:
destination = coords.MakeVec(
blockEdge.X+float32(cardNum)*(u.Padding+cardDim.X),
- yPlayerBlockSize)
+ yPlayerBlockSize+u.Padding)
case 3:
destination = coords.MakeVec(
u.WindowSize.X-xPlayerBlockSize-u.TableCardDim.X,
@@ -250,7 +248,7 @@
// Animation to bring in the play slot when app is in the hand view and it is the player's turn
func AnimateInPlay(u *uistate.UIState) {
- imgs := []*staticimg.StaticImg{u.BackgroundImgs[0], u.DropTargets[0]}
+ imgs := append(u.DropTargets, u.BackgroundImgs[0])
for _, i := range imgs {
dims := i.GetDimensions()
to := coords.MakeVec(i.GetCurrent().X, i.GetCurrent().Y+u.WindowSize.Y/3+u.TopPadding)
@@ -390,6 +388,11 @@
}
}
+func AnimateHandCardTakeTrick(ch chan bool, c *card.Card, u *uistate.UIState) {
+ destination := c.GetDimensions().Times(-1)
+ animateCardMovement(ch, c, destination, c.GetDimensions(), u)
+}
+
func animateImageMovement(c chan bool, animImage *staticimg.StaticImg, endPos, endDim *coords.Vec, u *uistate.UIState) {
node := animImage.GetNode()
startPos := animImage.GetCurrent()
@@ -573,7 +576,6 @@
case <-quitChan:
RemoveAnimChan(quitChan, u)
f()
- <-time.After(time.Second)
return
case <-animChan:
RemoveAnimChan(quitChan, u)
diff --git a/go/src/hearts/img/uistate/uistate.go b/go/src/hearts/img/uistate/uistate.go
index bb5c898..4effbe3 100644
--- a/go/src/hearts/img/uistate/uistate.go
+++ b/go/src/hearts/img/uistate/uistate.go
@@ -10,6 +10,7 @@
import (
"encoding/json"
"fmt"
+ "sync"
"time"
"hearts/img/coords"
@@ -106,6 +107,7 @@
SGChan chan bool // pass in a bool to stop advertising the syncgroup
ScanChan chan bool // pass in a bool to stop scanning for syncgroups
DiscGroups map[string]*DiscStruct // contains a set of addresses and game start data for each advertised game found
+ M sync.Mutex
}
func MakeUIState() *UIState {
diff --git a/go/src/hearts/img/view/view.go b/go/src/hearts/img/view/view.go
index b7397d2..6b8a78f 100644
--- a/go/src/hearts/img/view/view.go
+++ b/go/src/hearts/img/view/view.go
@@ -25,8 +25,29 @@
"golang.org/x/mobile/exp/sprite"
)
+func ReloadView(u *uistate.UIState) {
+ switch u.CurView {
+ case uistate.Discovery:
+ LoadDiscoveryView(u)
+ case uistate.Arrange:
+ LoadArrangeView(u)
+ case uistate.Table:
+ LoadTableView(u)
+ case uistate.Pass:
+ LoadPassView(u)
+ case uistate.Take:
+ LoadTakeView(u)
+ case uistate.Play:
+ LoadPlayView(u)
+ case uistate.Split:
+ LoadSplitView(true, u)
+ }
+}
+
// Arrange view: For seating players
func LoadArrangeView(u *uistate.UIState) {
+ u.M.Lock()
+ fmt.Println("ARRANGE LOCKED")
reposition.ResetAnims(u)
resetImgs(u)
resetScene(u)
@@ -67,11 +88,15 @@
u.Eng.SetSubTex(u.Buttons["start"].GetNode(), emptyTex)
}
}
+ u.M.Unlock()
+ fmt.Println("UNLOCKED")
}
// Waiting view: Displays the word "Waiting". To be displayed when players are waiting for a new round to be dealt
// TODO(emshack): Integrate this with Arrange view and Score view so that a separate screen is not necessary
func LoadWaitingView(u *uistate.UIState) {
+ u.M.Lock()
+ fmt.Println("WAITING LOCKED")
reposition.ResetAnims(u)
resetImgs(u)
resetScene(u)
@@ -82,10 +107,14 @@
for _, img := range textImgs {
u.BackgroundImgs = append(u.BackgroundImgs, img)
}
+ u.M.Unlock()
+ fmt.Println("UNLOCKED")
}
// Discovery view: Displays a menu of possible games to join
func LoadDiscoveryView(u *uistate.UIState) {
+ u.M.Lock()
+ fmt.Println("DISCOVERY LOCKED")
reposition.ResetAnims(u)
resetImgs(u)
resetScene(u)
@@ -126,10 +155,14 @@
}
}
}
+ u.M.Unlock()
+ fmt.Println("UNLOCKED")
}
// Table View: Displays the table. Intended for public devices
func LoadTableView(u *uistate.UIState) {
+ u.M.Lock()
+ fmt.Println("TABLE LOCKED")
reposition.ResetAnims(u)
resetImgs(u)
resetScene(u)
@@ -336,7 +369,6 @@
texture.PopulateCardImage(c, u)
c.SetBackDisplay(u.Eng)
pos := reposition.DetermineTablePassPosition(c, i, p.GetPlayerIndex(), u)
- c.SetInitial(pos)
c.Move(pos, u.TableCardDim, u.Eng)
u.TableCards = append(u.TableCards, c)
}
@@ -346,6 +378,8 @@
addDebugBar(u)
}
reposition.SetTableDropColors(u)
+ u.M.Unlock()
+ fmt.Println("UNLOCKED")
}
// Decides which view of the player's hand to load based on what steps of the round they have completed
@@ -362,6 +396,8 @@
// Score View: Shows current player standings at the end of every round, including the end of the game
func LoadScoreView(roundScores, winners []int, u *uistate.UIState) {
+ u.M.Lock()
+ fmt.Println("SCORE LOCKED")
reposition.ResetAnims(u)
resetImgs(u)
resetScene(u)
@@ -370,10 +406,14 @@
addScoreViewHeaderText(u)
addPlayerScores(roundScores, u)
addScoreButton(len(winners) > 0, u)
+ u.M.Unlock()
+ fmt.Println("UNLOCKED")
}
// Pass View: Shows player's hand and allows them to pass cards
func LoadPassView(u *uistate.UIState) {
+ u.M.Lock()
+ fmt.Println("PASS LOCKED")
reposition.ResetAnims(u)
resetImgs(u)
resetScene(u)
@@ -386,10 +426,14 @@
addDebugBar(u)
}
reposition.AnimateInPass(u)
+ u.M.Unlock()
+ fmt.Println("UNLOCKED")
}
// Take View: Shows player's hand and allows them to take the cards that have been passed to them
func LoadTakeView(u *uistate.UIState) {
+ u.M.Lock()
+ fmt.Println("TAKE LOCKED")
reposition.ResetAnims(u)
resetImgs(u)
resetScene(u)
@@ -403,10 +447,14 @@
}
// animate in take bar
reposition.AnimateInTake(u)
+ u.M.Unlock()
+ fmt.Println("UNLOCKED")
}
// Play View: Shows player's hand and allows them to play cards
func LoadPlayView(u *uistate.UIState) {
+ u.M.Lock()
+ fmt.Println("PLAY LOCKED")
reposition.ResetAnims(u)
resetImgs(u)
resetScene(u)
@@ -427,10 +475,16 @@
} else if u.CurTable.AllDonePassing() {
reposition.AnimateInPlay(u)
}
+ } else if u.CurTable.GetTrickRecipient() == u.CurPlayerIndex {
+ reposition.AnimateInPlay(u)
}
+ u.M.Unlock()
+ fmt.Println("UNLOCKED")
}
func LoadSplitView(reloading bool, u *uistate.UIState) {
+ u.M.Lock()
+ fmt.Println("SPLIT LOCKED")
reposition.ResetAnims(u)
resetImgs(u)
resetScene(u)
@@ -454,6 +508,8 @@
reposition.SwitchOnChan(ch, quit, onDone, u)
}()
}
+ u.M.Unlock()
+ fmt.Println("UNLOCKED")
}
func ChangePlayMessage(message string, u *uistate.UIState) {
@@ -694,10 +750,25 @@
u.BackgroundImgs = append(u.BackgroundImgs,
texture.MakeImgWithoutAlt(blueRectImg, blueRectPos, blueRectDim, u))
// adding drop target
- dropTargetImg := u.Texs["trickDrop.png"]
- dropTargetPos := coords.MakeVec(u.WindowSize.X/2-u.CardDim.X/2, -u.CardDim.Y-3*u.Padding)
- u.DropTargets = append(u.DropTargets,
- texture.MakeImgWithoutAlt(dropTargetImg, dropTargetPos, u.CardDim, u))
+ if u.CurTable.GetTrickRecipient() == u.CurPlayerIndex {
+ var emptyTex sprite.SubTex
+ dropTargetImg := emptyTex
+ blockStartX := (u.WindowSize.X - float32(u.NumPlayers)*(u.CardDim.X+u.Padding) + u.Padding) / 2
+ for i, c := range u.CurTable.GetTrick() {
+ dropTargetPos := coords.MakeVec(blockStartX+float32(i)*(u.CardDim.X+u.Padding), -u.CardDim.Y-3*u.Padding)
+ d := texture.MakeImgWithoutAlt(dropTargetImg, dropTargetPos, u.CardDim, u)
+ texture.PopulateCardImage(c, u)
+ c.Move(dropTargetPos, u.CardDim, u.Eng)
+ d.SetCardHere(c)
+ u.TableCards = append(u.TableCards, c)
+ u.DropTargets = append(u.DropTargets, d)
+ }
+ } else {
+ dropTargetImg := u.Texs["trickDrop.png"]
+ dropTargetPos := coords.MakeVec(u.WindowSize.X/2-u.CardDim.X/2, -u.CardDim.Y-3*u.Padding)
+ u.DropTargets = append(u.DropTargets,
+ texture.MakeImgWithoutAlt(dropTargetImg, dropTargetPos, u.CardDim, u))
+ }
}
func addGrayPassBar(u *uistate.UIState) {
diff --git a/go/src/hearts/logic/table/table.go b/go/src/hearts/logic/table/table.go
index 25ea103..f20e905 100644
--- a/go/src/hearts/logic/table/table.go
+++ b/go/src/hearts/logic/table/table.go
@@ -243,12 +243,19 @@
}
// Calculates who should take the cards in the current trick
+// Returns -1 if trick is incomplete
func (t *Table) GetTrickRecipient() int {
+ if t.firstPlayer < 0 || t.firstPlayer >= len(t.players) || t.trick[t.firstPlayer] == nil {
+ return -1
+ }
trickSuit := t.trick[t.firstPlayer].GetSuit()
highestCardFace := card.Two
highestIndex := -1
for i := 0; i < len(t.trick); i++ {
curCard := t.trick[i]
+ if curCard == nil {
+ return -1
+ }
if curCard.GetSuit() == trickSuit && curCard.GetFace() >= highestCardFace {
highestCardFace = curCard.GetFace()
highestIndex = i
diff --git a/go/src/hearts/sync/watch.go b/go/src/hearts/sync/watch.go
index 4057809..3def64c 100644
--- a/go/src/hearts/sync/watch.go
+++ b/go/src/hearts/sync/watch.go
@@ -157,8 +157,10 @@
func onPlayerNum(key, value string, u *uistate.UIState) {
userID, _ := strconv.Atoi(strings.Split(key, "/")[2])
playerNum, _ := strconv.Atoi(value)
- u.PlayerData[playerNum] = userID
- u.CurTable.GetPlayers()[playerNum].SetDoneScoring(true)
+ if playerNum >= 0 && playerNum < 4 {
+ u.PlayerData[playerNum] = userID
+ u.CurTable.GetPlayers()[playerNum].SetDoneScoring(true)
+ }
if playerNum == u.CurPlayerIndex && userID != UserID {
u.CurPlayerIndex = -1
}
@@ -216,7 +218,7 @@
quit := make(chan bool)
u.AnimChans = append(u.AnimChans, quit)
reposition.AnimateTableCardPass(curCards, receivingPlayer, quit, u)
- reposition.SetTableDropColors(u)
+ view.LoadTableView(u)
} else if u.CurView == uistate.Take {
if u.SequentialPhases {
if u.CurTable.AllDonePassing() {
@@ -263,7 +265,7 @@
quit := make(chan bool)
u.AnimChans = append(u.AnimChans, quit)
reposition.AnimateTableCardTake(passed, u.CurTable.GetPlayers()[playerInt], quit, u)
- reposition.SetTableDropColors(u)
+ view.LoadTableView(u)
}
}
diff --git a/go/src/hearts/touchhandler/touchhandler.go b/go/src/hearts/touchhandler/touchhandler.go
index f69a541..eee5b3c 100644
--- a/go/src/hearts/touchhandler/touchhandler.go
+++ b/go/src/hearts/touchhandler/touchhandler.go
@@ -45,6 +45,7 @@
if numTaps == 5 {
fmt.Println("TOGGLING DEBUG")
u.Debug = !u.Debug
+ view.ReloadView(u)
numTaps = 0
}
} else {
@@ -366,7 +367,7 @@
onDone := func() {
if !success {
fmt.Println("Invalid pass")
- } else {
+ } else if u.CurView == uistate.Pass {
view.LoadTakeView(u)
}
}
@@ -419,7 +420,9 @@
if !success {
fmt.Println("Invalid take")
} else {
- view.LoadPlayView(u)
+ if u.CurView == uistate.Take {
+ view.LoadPlayView(u)
+ }
}
}
reposition.SwitchOnChan(ch, quit, onDone, u)
@@ -432,6 +435,29 @@
if u.CurCard != nil {
reposition.BringNodeToFront(u.CurCard.GetNode(), u)
}
+ takenCard := findClickedTableCard(t, u)
+ if takenCard != nil {
+ removeCardFromTarget(takenCard, u)
+ reposition.BringNodeToFront(takenCard.GetNode(), u)
+ ch := make(chan bool)
+ reposition.AnimateHandCardTakeTrick(ch, takenCard, u)
+ quit := make(chan bool)
+ u.AnimChans = append(u.AnimChans, quit)
+ go func() {
+ onDone := func() {
+ doneTaking := len(u.DropTargets) == 4
+ for _, d := range u.DropTargets {
+ if d.GetCardHere() != nil {
+ doneTaking = false
+ }
+ }
+ if doneTaking {
+ sync.LogTakeTrick(u)
+ }
+ }
+ reposition.SwitchOnChan(ch, quit, onDone, u)
+ }()
+ }
buttonList := findClickedButton(t, u)
for _, b := range buttonList {
if b == u.Buttons["toggleSplit"] && !u.SwitchingViews {
@@ -461,7 +487,11 @@
quit := make(chan bool)
u.AnimChans = append(u.AnimChans, quit)
go func() {
- onDone := func() { view.LoadPlayView(u) }
+ onDone := func() {
+ if u.CurView == uistate.Play {
+ view.LoadPlayView(u)
+ }
+ }
reposition.SwitchOnChan(ch, quit, onDone, u)
}()
} else {
@@ -489,7 +519,9 @@
go func() {
onDone := func() {
u.SwitchingViews = false
- view.LoadPlayView(u)
+ if u.CurView == uistate.Split {
+ view.LoadPlayView(u)
+ }
}
reposition.SwitchOnChan(ch, quit, onDone, u)
}()
@@ -575,6 +607,18 @@
return nil
}
+// returns a card object if a card was clicked, or nil if no card was clicked
+func findClickedTableCard(t touch.Event, u *uistate.UIState) *card.Card {
+ // i goes from the end backwards so that it checks cards displayed on top of other cards first
+ for i := len(u.TableCards) - 1; i >= 0; i-- {
+ c := u.TableCards[i]
+ if touchingCard(t, c, u) {
+ return c
+ }
+ }
+ return nil
+}
+
// returns a button object if a button was clicked, or nil if no button was clicked
func findClickedButton(t touch.Event, u *uistate.UIState) []*staticimg.StaticImg {
pressed := make([]*staticimg.StaticImg, 0)