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)