Merge "Can now line up a card to play before your turn"
diff --git a/go/src/hearts/assets/UnplayedBorder1.png b/go/src/hearts/assets/UnplayedBorder1.png
new file mode 100644
index 0000000..0068dd5
--- /dev/null
+++ b/go/src/hearts/assets/UnplayedBorder1.png
Binary files differ
diff --git a/go/src/hearts/assets/UnplayedBorder2.png b/go/src/hearts/assets/UnplayedBorder2.png
new file mode 100644
index 0000000..48f6832
--- /dev/null
+++ b/go/src/hearts/assets/UnplayedBorder2.png
Binary files differ
diff --git a/go/src/hearts/img/reposition/reposition.go b/go/src/hearts/img/reposition/reposition.go
index 6d30a40..78d8fd1 100644
--- a/go/src/hearts/img/reposition/reposition.go
+++ b/go/src/hearts/img/reposition/reposition.go
@@ -7,6 +7,8 @@
 package reposition
 
 import (
+	"time"
+
 	"hearts/img/coords"
 	"hearts/img/direction"
 	"hearts/img/staticimg"
@@ -287,7 +289,7 @@
 	bannerImgs = append(bannerImgs, u.Buttons["takeTrick"])
 	bannerImgs = append(bannerImgs, u.Buttons["toggleSplit"])
 	tableImgs = append(tableImgs, u.DropTargets...)
-	tableImgs = append(tableImgs, u.BackgroundImgs[:u.NumPlayers]...)
+	tableImgs = append(tableImgs, u.BackgroundImgs[:u.NumPlayers+1]...)
 	for _, img := range tableImgs {
 		from := img.GetCurrent()
 		to := coords.MakeVec(from.X, from.Y+topOfBanner)
@@ -326,7 +328,7 @@
 	bannerImgs = append(bannerImgs, u.Buttons["takeTrick"])
 	bannerImgs = append(bannerImgs, u.Buttons["toggleSplit"])
 	tableImgs = append(tableImgs, u.DropTargets...)
-	tableImgs = append(tableImgs, u.BackgroundImgs[:u.NumPlayers]...)
+	tableImgs = append(tableImgs, u.BackgroundImgs[:u.NumPlayers+1]...)
 	for _, img := range tableImgs {
 		from := img.GetCurrent()
 		to := coords.MakeVec(from.X, from.Y-topOfBanner)
@@ -400,7 +402,7 @@
 	}
 	for i, c := range cards {
 		destination := c.GetDimensions().Times(-1)
-		if i < len(cards) - 1 {
+		if i < len(cards)-1 {
 			animateCardNoChannel(c, destination, c.GetDimensions(), u)
 		} else {
 			animateCardMovement(ch, c, destination, c.GetDimensions(), u)
@@ -568,6 +570,19 @@
 	c.Move(pos, u.CardDim, u.Eng)
 }
 
+func AlternateImgs(s *staticimg.StaticImg, u *uistate.UIState) {
+	<-time.After(100 * time.Millisecond)
+	node := s.GetNode()
+	node.Arranger = arrangerFunc(func(eng sprite.Engine, node *sprite.Node, t clock.Time) {
+		t0 := uint32(t) % 10
+		if t0 < 5 {
+			u.Eng.SetSubTex(s.GetNode(), s.GetImage())
+		} else {
+			u.Eng.SetSubTex(s.GetNode(), s.GetAlt())
+		}
+	})
+}
+
 func RemoveAnimChan(ch chan bool, u *uistate.UIState) {
 	for i, c := range u.AnimChans {
 		if ch == c {
diff --git a/go/src/hearts/img/texture/texture.go b/go/src/hearts/img/texture/texture.go
index f59d539..9e3fe92 100644
--- a/go/src/hearts/img/texture/texture.go
+++ b/go/src/hearts/img/texture/texture.go
@@ -278,6 +278,7 @@
 		"PassPressed.png", "PassUnpressed.png", "RightArrowBlue.png", "LeftArrowBlue.png", "AcrossArrowBlue.png", "RightArrowGray.png",
 		"LeftArrowGray.png", "AcrossArrowGray.png", "TakeTrickTableUnpressed.png", "TakeTrickTablePressed.png", "TakeTrickHandPressed.png",
 		"TakeTrickHandUnpressed.png", "android.png", "cat.png", "man.png", "woman.png", "TakeUnpressed.png", "TakePressed.png",
+		"UnplayedBorder1.png", "UnplayedBorder2.png",
 	}
 	for _, f := range boundedImgs {
 		a, err := asset.Open(f)
diff --git a/go/src/hearts/img/uistate/uistate.go b/go/src/hearts/img/uistate/uistate.go
index 4effbe3..74ccb18 100644
--- a/go/src/hearts/img/uistate/uistate.go
+++ b/go/src/hearts/img/uistate/uistate.go
@@ -68,6 +68,9 @@
 	Buttons        map[string]*staticimg.StaticImg
 	Other          []*staticimg.StaticImg
 	ModText        []*staticimg.StaticImg
+	RoundScores    []int                // the scores of the most recently finished round
+	Winners        []int                // the list of players, if any, who have won
+	CardToPlay     *card.Card           // the card, if any, curPlayer has decided to play before their turn
 	CurCard        *card.Card           // the card that is currently clicked on
 	CurImg         *staticimg.StaticImg // the image that is currently clicked on
 	// lastMouseXY is in Px: divide by pixelsPerPt to get Pt
@@ -121,6 +124,7 @@
 		Buttons:          make(map[string]*staticimg.StaticImg),
 		Other:            make([]*staticimg.StaticImg, 0),
 		ModText:          make([]*staticimg.StaticImg, 0),
+		RoundScores:      make([]int, numPlayers),
 		LastMouseXY:      coords.MakeVec(-1, -1),
 		NumPlayers:       numPlayers,
 		NumSuits:         numSuits,
diff --git a/go/src/hearts/img/view/view.go b/go/src/hearts/img/view/view.go
index d88f3da..9169a6b 100644
--- a/go/src/hearts/img/view/view.go
+++ b/go/src/hearts/img/view/view.go
@@ -38,7 +38,7 @@
 	case uistate.Take:
 		LoadTakeView(u)
 	case uistate.Play:
-		LoadPlayView(u)
+		LoadPlayView(true, u)
 	case uistate.Split:
 		LoadSplitView(true, u)
 	}
@@ -377,8 +377,14 @@
 // Decides which view of the player's hand to load based on what steps of the round they have completed
 func LoadPassOrTakeOrPlay(u *uistate.UIState) {
 	p := u.CurTable.GetPlayers()[u.CurPlayerIndex]
-	if p.GetDoneTaking() || u.CurTable.GetDir() == direction.None {
-		LoadPlayView(u)
+	if u.CurTable.RoundOver() {
+		LoadScoreView(u)
+	} else if p.GetDoneTaking() || u.CurTable.GetDir() == direction.None {
+		if u.CurView == uistate.Play {
+			LoadPlayView(true, u)
+		} else {
+			LoadPlayView(false, u)
+		}
 	} else if p.GetDonePassing() {
 		LoadTakeView(u)
 	} else {
@@ -387,7 +393,7 @@
 }
 
 // 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) {
+func LoadScoreView(u *uistate.UIState) {
 	u.M.Lock()
 	defer u.M.Unlock()
 	reposition.ResetAnims(u)
@@ -396,8 +402,8 @@
 	u.CurView = uistate.Score
 	addHeader(u)
 	addScoreViewHeaderText(u)
-	addPlayerScores(roundScores, u)
-	addScoreButton(len(winners) > 0, u)
+	addPlayerScores(u.RoundScores, u)
+	addScoreButton(len(u.Winners) > 0, u)
 }
 
 // Pass View: Shows player's hand and allows them to pass cards
@@ -438,14 +444,15 @@
 }
 
 // Play View: Shows player's hand and allows them to play cards
-func LoadPlayView(u *uistate.UIState) {
+func LoadPlayView(reloading bool, u *uistate.UIState) {
 	u.M.Lock()
 	defer u.M.Unlock()
 	reposition.ResetAnims(u)
 	resetImgs(u)
 	resetScene(u)
 	u.CurView = uistate.Play
-	addPlaySlot(u)
+	display := u.CurTable.GetTrick()[u.CurPlayerIndex] == nil && !u.CurTable.TrickNew() && reloading
+	addPlaySlot(display, u)
 	addHand(u)
 	addPlayHeader(getTurnText(u), false, u)
 	SetNumTricksHand(u)
@@ -453,15 +460,7 @@
 		addDebugBar(u)
 	}
 	// animate in play slot if relevant
-	if u.CurTable.WhoseTurn() == u.CurPlayerIndex {
-		if u.SequentialPhases {
-			if u.CurTable.AllDoneTaking() {
-				reposition.AnimateInPlay(u)
-			}
-		} else if u.CurTable.AllDonePassing() {
-			reposition.AnimateInPlay(u)
-		}
-	} else if u.CurTable.GetTrickRecipient() == u.CurPlayerIndex {
+	if u.CurTable.TrickNew() || u.CurTable.GetTrickRecipient() == u.CurPlayerIndex || (!reloading && u.CurTable.GetTrick()[u.CurPlayerIndex] == nil) {
 		reposition.AnimateInPlay(u)
 	}
 }
@@ -488,9 +487,18 @@
 		u.SwitchingViews = true
 		reposition.AnimateInSplit(ch, u)
 		go func() {
-			onDone := func() { u.SwitchingViews = false }
+			onDone := func() {
+				u.SwitchingViews = false
+				if u.CardToPlay != nil {
+					reposition.AlternateImgs(u.BackgroundImgs[0], u)
+				}
+			}
 			reposition.SwitchOnChan(ch, quit, onDone, u)
 		}()
+	} else {
+		if u.CardToPlay != nil {
+			reposition.AlternateImgs(u.BackgroundImgs[0], u)
+		}
 	}
 }
 
@@ -569,6 +577,17 @@
 	dropTargetPos := coords.MakeVec(dropTargetX, dropTargetY)
 	d := texture.MakeImgWithAlt(dropTargetImage, dropTargetAlt, dropTargetPos, dropTargetDimensions, true, u)
 	u.DropTargets = append(u.DropTargets, d)
+	// 'unplayed' border
+	borderImage := u.Texs["UnplayedBorder1.png"]
+	borderAlt := u.Texs["UnplayedBorder2.png"]
+	borderDim := dropTargetDimensions.Plus(2)
+	borderPos := dropTargetPos.Minus(1)
+	b := texture.MakeImgWithAlt(borderImage, borderAlt, borderPos, borderDim, true, u)
+	u.BackgroundImgs = append(u.BackgroundImgs, b)
+	if u.CardToPlay == nil {
+		var emptyTex sprite.SubTex
+		u.Eng.SetSubTex(b.GetNode(), emptyTex)
+	}
 	// first player icon
 	playerIconImage := uistate.GetAvatar(u.CurPlayerIndex, u)
 	u.BackgroundImgs = append(u.BackgroundImgs,
@@ -728,12 +747,15 @@
 	}
 }
 
-func addPlaySlot(u *uistate.UIState) {
+func addPlaySlot(display bool, u *uistate.UIState) {
 	topOfHand := u.WindowSize.Y - 5*(u.CardDim.Y+u.Padding) - (2 * u.Padding / 5) - u.BottomPadding
 	// adding blue rectangle
 	blueRectImg := u.Texs["RoundedRectangle-LBlue.png"]
 	blueRectDim := coords.MakeVec(u.WindowSize.X-4*u.BottomPadding, topOfHand+u.CardDim.Y)
 	blueRectPos := coords.MakeVec(2*u.BottomPadding, -blueRectDim.Y)
+	if display {
+		blueRectPos = coords.MakeVec(blueRectPos.X, blueRectPos.Y+u.WindowSize.Y/3+u.TopPadding)
+	}
 	u.BackgroundImgs = append(u.BackgroundImgs,
 		texture.MakeImgWithoutAlt(blueRectImg, blueRectPos, blueRectDim, u))
 	// adding drop target
@@ -753,6 +775,9 @@
 	} else {
 		dropTargetImg := u.Texs["trickDrop.png"]
 		dropTargetPos := coords.MakeVec(u.WindowSize.X/2-u.CardDim.X/2, -u.CardDim.Y-3*u.Padding)
+		if display {
+			dropTargetPos = coords.MakeVec(dropTargetPos.X, dropTargetPos.Y+u.WindowSize.Y/3+u.TopPadding)
+		}
 		u.DropTargets = append(u.DropTargets,
 			texture.MakeImgWithoutAlt(dropTargetImg, dropTargetPos, u.CardDim, u))
 	}
@@ -1137,6 +1162,11 @@
 		texture.PopulateCardImage(u.Cards[i], u)
 		reposition.SetCardPositionHand(u.Cards[i], numInSuit, suitCounts, u)
 	}
+	if u.CardToPlay != nil && u.DropTargets[0].GetCardHere() == nil {
+		u.CardToPlay.Move(u.DropTargets[0].GetCurrent(), u.DropTargets[0].GetDimensions(), u.Eng)
+		u.DropTargets[0].SetCardHere(u.CardToPlay)
+		reposition.RealignSuit(u.CardToPlay.GetSuit(), u.CardToPlay.GetInitial().Y, u)
+	}
 }
 
 func addScoreViewHeaderText(u *uistate.UIState) {
diff --git a/go/src/hearts/logic/table/table.go b/go/src/hearts/logic/table/table.go
index f20e905..4eece8a 100644
--- a/go/src/hearts/logic/table/table.go
+++ b/go/src/hearts/logic/table/table.go
@@ -37,7 +37,7 @@
 		heartsBroken: false,
 		firstTrick:   true,
 		winCondition: 100,
-		dir:          direction.Right,
+		dir:          direction.None,
 	}
 }
 
@@ -232,6 +232,16 @@
 	return true
 }
 
+// Returns true if no players have played a card in the current trick
+func (t *Table) TrickNew() bool {
+	for _, c := range t.trick {
+		if c != nil {
+			return false
+		}
+	}
+	return true
+}
+
 // Returns true if all players are out of cards, indicating the end of a round
 func (t *Table) RoundOver() bool {
 	for _, p := range t.players {
diff --git a/go/src/hearts/sync/util.go b/go/src/hearts/sync/util.go
index 2f8d8fc..7155506 100644
--- a/go/src/hearts/sync/util.go
+++ b/go/src/hearts/sync/util.go
@@ -8,13 +8,13 @@
 
 const (
 	// switch back to my mountpoint with the following code:
-	//MountPoint = "users/emshack@google.com"
-	MountPoint        = "/192.168.86.254:8101"
-	UserID            = 2222
+	MountPoint = "users/emshack@google.com"
+	//MountPoint        = "/192.168.86.254:8101"
+	UserID            = 111
 	UserColor         = 16777215
-	UserAvatar        = "man.png"
+	UserAvatar        = "woman.png"
 	UserName          = "Bob"
-	SBName            = "syncbase1"
+	SBName            = "syncbase"
 	AppName           = "app"
 	DbName            = "db"
 	LogName           = "games"
diff --git a/go/src/hearts/sync/watch.go b/go/src/hearts/sync/watch.go
index f2f2195..a52b82d 100644
--- a/go/src/hearts/sync/watch.go
+++ b/go/src/hearts/sync/watch.go
@@ -18,10 +18,10 @@
 	"hearts/img/uistate"
 	"hearts/img/view"
 	"hearts/logic/card"
+	"os"
 	"sort"
 	"strconv"
 	"strings"
-	"os"
 	"time"
 	"v.io/v23/syncbase/nosql"
 )
@@ -72,20 +72,7 @@
 	u.UserData[userID] = valueMap
 	for _, v := range u.PlayerData {
 		if v == userID {
-			switch u.CurView {
-			case uistate.Arrange:
-				view.LoadArrangeView(u)
-			case uistate.Table:
-				view.LoadTableView(u)
-			case uistate.Pass:
-				view.LoadPassView(u)
-			case uistate.Take:
-				view.LoadTakeView(u)
-			case uistate.Play:
-				view.LoadPlayView(u)
-			case uistate.Split:
-				view.LoadSplitView(true, u)
-			}
+			view.ReloadView(u)
 		}
 	}
 	if u.CurView == uistate.Discovery {
@@ -135,7 +122,7 @@
 			tmp := strings.Split(key, "/")
 			if len(tmp) == 3 {
 				keyTime, _ := strconv.ParseInt(strings.Split(tmp[2], "-")[0], 10, 64)
-				fmt.Fprintf(file, fmt.Sprintf("diff: %d milliseconds\n\n", curTime - keyTime))
+				fmt.Fprintf(file, fmt.Sprintf("diff: %d milliseconds\n\n", curTime-keyTime))
 			} else {
 				fmt.Fprintf(file, "\n")
 			}
@@ -247,7 +234,7 @@
 			view.LoadTakeView(u)
 		}
 	} else if u.CurView == uistate.Play && u.CurTable.AllDonePassing() {
-		view.LoadPlayView(u)
+		view.LoadPlayView(true, u)
 	}
 }
 
@@ -269,14 +256,14 @@
 			}
 			// UI
 			if u.CurView == uistate.Play {
-				view.LoadPlayView(u)
+				view.LoadPlayView(true, u)
 			}
 		}
 	} else if p.HasTwoOfClubs() {
 		u.CurTable.SetFirstPlayer(p.GetPlayerIndex())
 		// UI
 		if u.CurView == uistate.Play && u.CurPlayerIndex != playerInt {
-			view.LoadPlayView(u)
+			view.LoadPlayView(true, u)
 		}
 	}
 	// UI
@@ -325,9 +312,42 @@
 				b := u.Buttons["takeTrick"]
 				u.Eng.SetSubTex(b.GetNode(), b.GetImage())
 			}
+		} else if u.CardToPlay != nil && u.CurTable.WhoseTurn() == u.CurPlayerIndex {
+			ch := make(chan bool)
+			if err := PlayCard(ch, u.CurPlayerIndex, u); err != "" {
+				view.ChangePlayMessage(err, u)
+				RemoveCardFromTarget(u.CardToPlay, u)
+				// add card back to hand
+				reposition.ResetCardPosition(u.CardToPlay, u.Eng)
+			}
+			u.CardToPlay = nil
+			u.BackgroundImgs[0].GetNode().Arranger = nil
+			var emptyTex sprite.SubTex
+			u.Eng.SetSubTex(u.BackgroundImgs[0].GetNode(), emptyTex)
 		}
 	} else if u.CurView == uistate.Play && u.CurPlayerIndex != playerInt {
-		view.LoadPlayView(u)
+		view.LoadPlayView(true, u)
+		if u.CardToPlay != nil && u.CurTable.WhoseTurn() == u.CurPlayerIndex {
+			ch := make(chan bool)
+			if err := PlayCard(ch, u.CurPlayerIndex, u); err != "" {
+				view.ChangePlayMessage(err, u)
+				RemoveCardFromTarget(u.CardToPlay, u)
+				// add card back to hand
+				reposition.ResetCardPosition(u.CardToPlay, u.Eng)
+				reposition.RealignSuit(u.CardToPlay.GetSuit(), u.CardToPlay.GetInitial().Y, u)
+			}
+			u.CardToPlay = nil
+			quit := make(chan bool)
+			u.AnimChans = append(u.AnimChans, quit)
+			go func() {
+				onDone := func() {
+					if u.CurView == uistate.Play {
+						view.LoadPlayView(true, u)
+					}
+				}
+				reposition.SwitchOnChan(ch, quit, onDone, u)
+			}()
+		}
 	}
 }
 
@@ -335,10 +355,8 @@
 	trickCards := u.CurTable.GetTrick()
 	recipient := u.CurTable.GetTrickRecipient()
 	roundOver := u.CurTable.SendTrick(recipient)
-	var roundScores []int
-	var winners []int
 	if roundOver {
-		roundScores, winners = u.CurTable.EndRound()
+		u.RoundScores, u.Winners = u.CurTable.EndRound()
 	}
 	// UI
 	if u.CurView == uistate.Table {
@@ -364,7 +382,7 @@
 		var emptyTex sprite.SubTex
 		u.Eng.SetSubTex(u.Buttons["takeTrick"].GetNode(), emptyTex)
 		if roundOver {
-			view.LoadScoreView(roundScores, winners, u)
+			view.LoadScoreView(u)
 		} else {
 			var trickDir direction.Direction
 			switch recipient {
@@ -384,13 +402,13 @@
 		}
 	} else if u.CurView == uistate.Play {
 		if roundOver {
-			view.LoadScoreView(roundScores, winners, u)
+			view.LoadScoreView(u)
 		} else {
-			view.LoadPlayView(u)
+			view.LoadPlayView(true, u)
 		}
 	}
 	// logic
-	if len(winners) > 0 {
+	if len(u.Winners) > 0 {
 		u.CurTable.NewGame()
 	}
 }
@@ -455,3 +473,46 @@
 	jKey := us[j].Row
 	return iKey < jKey
 }
+
+func PlayCard(ch chan bool, playerId int, u *uistate.UIState) string {
+	c := u.DropTargets[0].GetCardHere()
+	if c == nil {
+		return "No card has been played"
+	}
+	// checks to make sure that:
+	// -player has not already played a card this round
+	// -all players have passed cards
+	// -the play is in the right order
+	// -the play is valid given game logic
+	if u.CurTable.GetPlayers()[playerId].GetDonePlaying() {
+		return "You have already played a card in this trick"
+	}
+	if !u.CurTable.AllDonePassing() {
+		return "Not all players have passed their cards"
+	}
+	if !u.CurTable.ValidPlayOrder(playerId) {
+		return "It is not your turn"
+	}
+	if err := u.CurTable.ValidPlayLogic(c, playerId); err != "" {
+		return err
+	}
+	success := LogPlay(u, c)
+	for !success {
+		success = LogPlay(u, c)
+	}
+	// no animation when in split view
+	if u.CurView == uistate.Play {
+		reposition.AnimateHandCardPlay(ch, c, u)
+	}
+	return ""
+}
+
+func RemoveCardFromTarget(c *card.Card, u *uistate.UIState) bool {
+	for _, d := range u.DropTargets {
+		if d.GetCardHere() == c {
+			d.SetCardHere(nil)
+			return true
+		}
+	}
+	return false
+}
diff --git a/go/src/hearts/touchhandler/touchhandler.go b/go/src/hearts/touchhandler/touchhandler.go
index bafcec1..32db10b 100644
--- a/go/src/hearts/touchhandler/touchhandler.go
+++ b/go/src/hearts/touchhandler/touchhandler.go
@@ -311,7 +311,7 @@
 	if u.CurCard != nil {
 		if !dropCardOnTarget(u.CurCard, t, u) {
 			// check to see if card was removed from a drop target
-			removeCardFromTarget(u.CurCard, u)
+			sync.RemoveCardFromTarget(u.CurCard, u)
 			// add card back to hand
 			reposition.ResetCardPosition(u.CurCard, u.Eng)
 			reposition.RealignSuit(u.CurCard.GetSuit(), u.CurCard.GetInitial().Y, u)
@@ -402,7 +402,7 @@
 				}
 			}
 			for _, c := range cards {
-				removeCardFromTarget(c, u)
+				sync.RemoveCardFromTarget(c, u)
 				// add card back to hand
 				reposition.ResetCardPosition(c, u.Eng)
 				reposition.RealignSuit(c.GetSuit(), c.GetInitial().Y, u)
@@ -417,7 +417,7 @@
 						fmt.Println("Invalid take")
 					} else {
 						if u.CurView == uistate.Take {
-							view.LoadPlayView(u)
+							view.LoadPlayView(false, u)
 						}
 					}
 				}
@@ -459,28 +459,40 @@
 
 func endClickPlay(t touch.Event, u *uistate.UIState) {
 	if u.CurCard != nil {
-		if dropCardOnTarget(u.CurCard, t, u) {
-			ch := make(chan bool)
-			if err := playCard(ch, u.CurPlayerIndex, u); err != "" {
-				view.ChangePlayMessage(err, u)
-				removeCardFromTarget(u.CurCard, u)
+		if u.CurTable.GetTrick()[u.CurPlayerIndex] == nil {
+			if dropCardOnTarget(u.CurCard, t, u) {
+				if u.CurTable.WhoseTurn() == u.CurPlayerIndex {
+					ch := make(chan bool)
+					if err := sync.PlayCard(ch, u.CurPlayerIndex, u); err != "" {
+						view.ChangePlayMessage(err, u)
+						sync.RemoveCardFromTarget(u.CurCard, u)
+						u.CardToPlay = nil
+						// add card back to hand
+						reposition.ResetCardPosition(u.CurCard, u.Eng)
+						reposition.RealignSuit(u.CurCard.GetSuit(), u.CurCard.GetInitial().Y, u)
+					}
+					quit := make(chan bool)
+					u.AnimChans = append(u.AnimChans, quit)
+					go func() {
+						onDone := func() {
+							if u.CurView == uistate.Play {
+								view.LoadPlayView(true, u)
+							}
+						}
+						reposition.SwitchOnChan(ch, quit, onDone, u)
+					}()
+				} else {
+					u.CardToPlay = u.CurCard
+				}
+			} else {
 				// add card back to hand
+				if sync.RemoveCardFromTarget(u.CurCard, u) {
+					u.CardToPlay = nil
+				}
 				reposition.ResetCardPosition(u.CurCard, u.Eng)
 				reposition.RealignSuit(u.CurCard.GetSuit(), u.CurCard.GetInitial().Y, u)
 			}
-			quit := make(chan bool)
-			u.AnimChans = append(u.AnimChans, quit)
-			go func() {
-				onDone := func() {
-					if u.CurView == uistate.Play {
-						view.LoadPlayView(u)
-					}
-				}
-				reposition.SwitchOnChan(ch, quit, onDone, u)
-			}()
 		} else {
-			// check to see if card was removed from a drop target
-			removeCardFromTarget(u.CurCard, u)
 			// add card back to hand
 			reposition.ResetCardPosition(u.CurCard, u.Eng)
 			reposition.RealignSuit(u.CurCard.GetSuit(), u.CurCard.GetInitial().Y, u)
@@ -493,7 +505,7 @@
 			u.Eng.SetSubTex(b.GetNode(), emptyTex)
 			u.Buttons["takeTrick"] = nil
 			for _, takenCard := range u.TableCards {
-				removeCardFromTarget(takenCard, u)
+				sync.RemoveCardFromTarget(takenCard, u)
 				reposition.BringNodeToFront(takenCard.GetNode(), u)
 			}
 			ch := make(chan bool)
@@ -527,7 +539,7 @@
 				onDone := func() {
 					u.SwitchingViews = false
 					if u.CurView == uistate.Split {
-						view.LoadPlayView(u)
+						view.LoadPlayView(false, u)
 					}
 				}
 				reposition.SwitchOnChan(ch, quit, onDone, u)
@@ -555,17 +567,40 @@
 
 func endClickSplit(t touch.Event, u *uistate.UIState) {
 	if u.CurCard != nil {
-		if dropCardHere(u.CurCard, u.DropTargets[0], t, u) {
-			ch := make(chan bool)
-			if err := playCard(ch, u.CurPlayerIndex, u); err != "" {
-				view.ChangePlayMessage(err, u)
-				removeCardFromTarget(u.CurCard, u)
+		if u.CurTable.GetTrick()[u.CurPlayerIndex] == nil {
+			if dropCardHere(u.CurCard, u.DropTargets[0], t, u) {
+				if u.CurTable.WhoseTurn() == u.CurPlayerIndex {
+					ch := make(chan bool)
+					if err := sync.PlayCard(ch, u.CurPlayerIndex, u); err != "" {
+						view.ChangePlayMessage(err, u)
+						if sync.RemoveCardFromTarget(u.CurCard, u) {
+							u.CardToPlay = nil
+							u.BackgroundImgs[0].GetNode().Arranger = nil
+							var emptyTex sprite.SubTex
+							u.Eng.SetSubTex(u.BackgroundImgs[0].GetNode(), emptyTex)
+						}
+						// add card back to hand
+						reposition.ResetCardPosition(u.CurCard, u.Eng)
+					}
+				} else {
+					u.CardToPlay = u.CurCard
+					reposition.AlternateImgs(u.BackgroundImgs[0], u)
+				}
+			} else {
 				// add card back to hand
+				if sync.RemoveCardFromTarget(u.CurCard, u) {
+					u.CardToPlay = nil
+					u.BackgroundImgs[0].GetNode().Arranger = nil
+					var emptyTex sprite.SubTex
+					u.Eng.SetSubTex(u.BackgroundImgs[0].GetNode(), emptyTex)
+				}
 				reposition.ResetCardPosition(u.CurCard, u.Eng)
+				reposition.RealignSuit(u.CurCard.GetSuit(), u.CurCard.GetInitial().Y, u)
 			}
 		} else {
 			// add card back to hand
 			reposition.ResetCardPosition(u.CurCard, u.Eng)
+			reposition.RealignSuit(u.CurCard.GetSuit(), u.CurCard.GetInitial().Y, u)
 		}
 	}
 	pressed := unpressButtons(u)
@@ -677,39 +712,6 @@
 	return true
 }
 
-func playCard(ch chan bool, playerId int, u *uistate.UIState) string {
-	c := u.DropTargets[0].GetCardHere()
-	if c == nil {
-		return "No card has been played"
-	}
-	// checks to make sure that:
-	// -player has not already played a card this round
-	// -all players have passed cards
-	// -the play is in the right order
-	// -the play is valid given game logic
-	if u.CurTable.GetPlayers()[playerId].GetDonePlaying() {
-		return "You have already played a card in this trick"
-	}
-	if !u.CurTable.AllDonePassing() {
-		return "Not all players have passed their cards"
-	}
-	if !u.CurTable.ValidPlayOrder(playerId) {
-		return "It is not your turn"
-	}
-	if err := u.CurTable.ValidPlayLogic(c, playerId); err != "" {
-		return err
-	}
-	success := sync.LogPlay(u, c)
-	for !success {
-		success = sync.LogPlay(u, c)
-	}
-	// no animation when in split view
-	if u.CurView == uistate.Play {
-		reposition.AnimateHandCardPlay(ch, c, u)
-	}
-	return ""
-}
-
 func pressButton(b *staticimg.StaticImg, u *uistate.UIState) {
 	u.Eng.SetSubTex(b.GetNode(), b.GetAlt())
 	b.SetDisplayingImage(false)
@@ -780,16 +782,6 @@
 	return true
 }
 
-func removeCardFromTarget(c *card.Card, u *uistate.UIState) bool {
-	for _, d := range u.DropTargets {
-		if d.GetCardHere() == c {
-			d.SetCardHere(nil)
-			return true
-		}
-	}
-	return false
-}
-
 func touchingCard(t touch.Event, c *card.Card, u *uistate.UIState) bool {
 	withinXBounds := t.X/u.PixelsPerPt >= c.GetCurrent().X && t.X/u.PixelsPerPt <= c.GetDimensions().X+c.GetCurrent().X
 	withinYBounds := t.Y/u.PixelsPerPt >= c.GetCurrent().Y && t.Y/u.PixelsPerPt <= c.GetDimensions().Y+c.GetCurrent().Y