Added ability to leave game when arranging.
Added player name and icon in discovery screen.
Now removing join game buttons of games no longer advertised

Change-Id: I07f361462df1dd21525ec48499092b9e11e50898
diff --git a/go/Makefile b/go/Makefile
index 2144da3..8e58388 100644
--- a/go/Makefile
+++ b/go/Makefile
@@ -35,7 +35,6 @@
 		--alsologtostderr=false \
 		--root-dir=tmp/$(name) \
 		--name=users/$(email)/croupier/$(name) \
-		--v23.proxy=proxy \
 		--v23.credentials=credentials \
 		--v23.permissions.literal='{"Admin":{"In":["..."]},"Write":{"In":["..."]},"Read":{"In":["..."]},"Resolve":{"In":["..."]},"Debug":{"In":["..."]}}'
 
@@ -51,4 +50,5 @@
 all: fmt 
 
 # Switch mountpoints by replacing the --name flag in syncbase with the following line:
-# --name=/192.168.86.254:8101/croupier/$(name) \
\ No newline at end of file
+# 
+#--name=/192.168.86.254:8101/croupier/$(name) \
\ No newline at end of file
diff --git a/go/src/hearts/Makefile b/go/src/hearts/Makefile
index 9b44e5c..0185275 100644
--- a/go/src/hearts/Makefile
+++ b/go/src/hearts/Makefile
@@ -13,7 +13,6 @@
 hearts: fmt credentials
 	jiri go build hearts
 	./hearts \
-    --v23.proxy=proxy \
     --v23.tcp.address=:$(syncbase_port) \
 	--v23.credentials=credentials
 
diff --git a/go/src/hearts/assets/Apostrophe.png b/go/src/hearts/assets/Apostrophe.png
new file mode 100644
index 0000000..e09a9c4
--- /dev/null
+++ b/go/src/hearts/assets/Apostrophe.png
Binary files differ
diff --git a/go/src/hearts/assets/Quit.png b/go/src/hearts/assets/Quit.png
new file mode 100644
index 0000000..d266994
--- /dev/null
+++ b/go/src/hearts/assets/Quit.png
Binary files differ
diff --git a/go/src/hearts/img/reposition/reposition.go b/go/src/hearts/img/reposition/reposition.go
index f037498..d4503e0 100644
--- a/go/src/hearts/img/reposition/reposition.go
+++ b/go/src/hearts/img/reposition/reposition.go
@@ -283,7 +283,7 @@
 	bannerImgs := make([]*staticimg.StaticImg, 0)
 	cards := make([]*card.Card, 0)
 	bannerImgs = append(bannerImgs, u.Other...)
-	bannerImgs = append(bannerImgs, u.Buttons[0])
+	bannerImgs = append(bannerImgs, u.Buttons["toggleSplit"])
 	tableImgs = append(tableImgs, u.DropTargets...)
 	tableImgs = append(tableImgs, u.BackgroundImgs[:u.NumPlayers]...)
 	cards = append(cards, u.TableCards...)
@@ -327,7 +327,7 @@
 	bannerImgs := make([]*staticimg.StaticImg, 0)
 	cards := make([]*card.Card, 0)
 	bannerImgs = append(bannerImgs, u.Other...)
-	bannerImgs = append(bannerImgs, u.Buttons[0])
+	bannerImgs = append(bannerImgs, u.Buttons["toggleSplit"])
 	tableImgs = append(tableImgs, u.DropTargets...)
 	tableImgs = append(tableImgs, u.BackgroundImgs[:u.NumPlayers]...)
 	cards = append(cards, u.TableCards...)
@@ -555,14 +555,19 @@
 }
 
 func BringNodeToFront(n *sprite.Node, u *uistate.UIState) {
-	u.Scene.RemoveChild(n)
-	u.Scene.AppendChild(n)
+	if n.Parent == u.Scene {
+		u.Scene.RemoveChild(n)
+	}
+	if n.Parent != u.Scene {
+		u.Scene.AppendChild(n)
+	}
 }
 
 func SwitchOnChan(animChan, quitChan chan bool, f func(), u *uistate.UIState) {
 	select {
 	case <-quitChan:
 		RemoveAnimChan(quitChan, u)
+		f()
 		return
 	case <-animChan:
 		RemoveAnimChan(quitChan, u)
diff --git a/go/src/hearts/img/resize/resize.go b/go/src/hearts/img/resize/resize.go
index 8fdbd28..fde54d3 100644
--- a/go/src/hearts/img/resize/resize.go
+++ b/go/src/hearts/img/resize/resize.go
@@ -38,9 +38,14 @@
 	adjustCardArray(u.TableCards, oldWindowSize, u.WindowSize, u.Eng)
 	adjustImgArray(u.DropTargets, oldWindowSize, u.WindowSize, u.Eng)
 	adjustImgArray(u.BackgroundImgs, oldWindowSize, u.WindowSize, u.Eng)
-	adjustImgArray(u.Buttons, oldWindowSize, u.WindowSize, u.Eng)
 	adjustImgArray(u.EmptySuitImgs, oldWindowSize, u.WindowSize, u.Eng)
 	adjustImgArray(u.Other, oldWindowSize, u.WindowSize, u.Eng)
+	adjustImgArray(u.ModText, oldWindowSize, u.WindowSize, u.Eng)
+	buttons := make([]*staticimg.StaticImg, 0)
+	for _, b := range u.Buttons {
+		buttons = append(buttons, b)
+	}
+	adjustImgArray(buttons, oldWindowSize, u.WindowSize, u.Eng)
 }
 
 // Returns coordinates for images with same width and height but in new positions proportional to the screen
diff --git a/go/src/hearts/img/staticimg/staticimg.go b/go/src/hearts/img/staticimg/staticimg.go
index 97900a1..871b6f8 100644
--- a/go/src/hearts/img/staticimg/staticimg.go
+++ b/go/src/hearts/img/staticimg/staticimg.go
@@ -38,7 +38,7 @@
 	// cardHere is used if the StaticImg instance is a drop target
 	cardHere *card.Card
 	// info contains any additional information contained in the image (eg. game syncgroup address, in a join game button)
-	info []string
+	info string
 }
 
 // Returns the node of s
@@ -81,7 +81,7 @@
 }
 
 // Returns the additional info associated with s
-func (s *StaticImg) GetInfo() []string {
+func (s *StaticImg) GetInfo() string {
 	return s.info
 }
 
@@ -124,6 +124,6 @@
 }
 
 // Sets the additional info of s
-func (s *StaticImg) SetInfo(i []string) {
+func (s *StaticImg) SetInfo(i string) {
 	s.info = i
 }
diff --git a/go/src/hearts/img/texture/texture.go b/go/src/hearts/img/texture/texture.go
index 2bec090..187f3ab 100644
--- a/go/src/hearts/img/texture/texture.go
+++ b/go/src/hearts/img/texture/texture.go
@@ -240,10 +240,10 @@
 		"E-Lower.png", "F-Lower.png", "G-Lower.png", "H-Lower.png", "I-Lower.png", "J-Lower.png", "K-Lower.png", "L-Lower.png",
 		"M-Lower.png", "N-Lower.png", "O-Lower.png", "P-Lower.png", "Q-Lower.png", "R-Lower.png", "S-Lower.png", "T-Lower.png",
 		"U-Lower.png", "V-Lower.png", "W-Lower.png", "X-Lower.png", "Y-Lower.png", "Z-Lower.png", "Space.png", "Colon.png", "Bang.png",
-		"1.png", "2.png", "3.png", "4.png", "5.png", "6.png", "7.png", "8.png", "9.png", "0.png", "1-Red.png", "2-Red.png", "3-Red.png",
-		"4-Red.png", "5-Red.png", "6-Red.png", "7-Red.png", "8-Red.png", "9-Red.png", "0-Red.png", "1-DBlue.png", "2-DBlue.png",
-		"3-DBlue.png", "4-DBlue.png", "5-DBlue.png", "6-DBlue.png", "7-DBlue.png", "8-DBlue.png", "9-DBlue.png", "0-DBlue.png",
-		"A-Upper-DBlue.png", "B-Upper-DBlue.png",
+		"Apostrophe.png", "1.png", "2.png", "3.png", "4.png", "5.png", "6.png", "7.png", "8.png", "9.png", "0.png", "1-Red.png",
+		"2-Red.png", "3-Red.png", "4-Red.png", "5-Red.png", "6-Red.png", "7-Red.png", "8-Red.png", "9-Red.png", "0-Red.png",
+		"1-DBlue.png", "2-DBlue.png", "3-DBlue.png", "4-DBlue.png", "5-DBlue.png", "6-DBlue.png", "7-DBlue.png", "8-DBlue.png",
+		"9-DBlue.png", "0-DBlue.png", "A-Upper-DBlue.png", "B-Upper-DBlue.png",
 		"C-Upper-DBlue.png", "D-Upper-DBlue.png", "E-Upper-DBlue.png", "F-Upper-DBlue.png", "G-Upper-DBlue.png", "H-Upper-DBlue.png",
 		"I-Upper-DBlue.png", "J-Upper-DBlue.png", "K-Upper-DBlue.png", "L-Upper-DBlue.png", "M-Upper-DBlue.png", "N-Upper-DBlue.png",
 		"O-Upper-DBlue.png", "P-Upper-DBlue.png", "Q-Upper-DBlue.png", "R-Upper-DBlue.png", "S-Upper-DBlue.png", "T-Upper-DBlue.png",
@@ -272,7 +272,7 @@
 		"U-Lower-Gray.png", "V-Lower-Gray.png", "W-Lower-Gray.png", "X-Lower-Gray.png", "Y-Lower-Gray.png", "Z-Lower-Gray.png",
 		"Space-Gray.png", "RoundedRectangle-DBlue.png", "RoundedRectangle-LBlue.png", "RoundedRectangle-Gray.png", "Rectangle-LBlue.png",
 		"Rectangle-DBlue.png", "HorizontalPullTab.png", "VerticalPullTab.png", "NewGame.png", "NewRound.png", "JoinGame.png", "Period.png",
-		"SitSpot.png", "WatchSpot.png", "StartBlue.png", "StartGray.png", "Restart.png", "Visibility.png", "VisibilityOff.png",
+		"SitSpot.png", "WatchSpot.png", "StartBlue.png", "StartGray.png", "Restart.png", "Visibility.png", "VisibilityOff.png", "Quit.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 9cfae3d..bb5c898 100644
--- a/go/src/hearts/img/uistate/uistate.go
+++ b/go/src/hearts/img/uistate/uistate.go
@@ -8,6 +8,8 @@
 package uistate
 
 import (
+	"encoding/json"
+	"fmt"
 	"time"
 
 	"hearts/img/coords"
@@ -62,7 +64,7 @@
 	BackgroundImgs []*staticimg.StaticImg
 	EmptySuitImgs  []*staticimg.StaticImg
 	DropTargets    []*staticimg.StaticImg
-	Buttons        []*staticimg.StaticImg
+	Buttons        map[string]*staticimg.StaticImg
 	Other          []*staticimg.StaticImg
 	ModText        []*staticimg.StaticImg
 	CurCard        *card.Card           // the card that is currently clicked on
@@ -91,6 +93,7 @@
 	CurPlayerIndex   int                      // the player number of this player
 	Ctx              *context.T
 	Service          syncbase.Service
+	LogSG            string                         // name of the game log syncgroup the user is currently in
 	Debug            bool                           // true if debugging, adds extra functionality to switch between players
 	SequentialPhases bool                           // true if trying to match Croupier Flutter Pass -> Take -> Play phase system
 	SwitchingViews   bool                           // true if currently animating between play and split views
@@ -102,6 +105,7 @@
 	AnimChans        []chan bool                    // keeps track of all 'quit' channels in animations so their goroutines can be stopped
 	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
 }
 
 func MakeUIState() *UIState {
@@ -112,7 +116,7 @@
 		BackgroundImgs:   make([]*staticimg.StaticImg, 0),
 		EmptySuitImgs:    make([]*staticimg.StaticImg, 0),
 		DropTargets:      make([]*staticimg.StaticImg, 0),
-		Buttons:          make([]*staticimg.StaticImg, 0),
+		Buttons:          make(map[string]*staticimg.StaticImg),
 		Other:            make([]*staticimg.StaticImg, 0),
 		ModText:          make([]*staticimg.StaticImg, 0),
 		LastMouseXY:      coords.MakeVec(-1, -1),
@@ -136,6 +140,7 @@
 		UserData:         make(map[int]map[string]interface{}),
 		PlayerData:       make(map[int]int),
 		AnimChans:        make([]chan bool, 0),
+		DiscGroups:       make(map[string]*DiscStruct),
 		CurPlayerIndex:   -1,
 	}
 }
@@ -166,3 +171,23 @@
 	}
 	return u.Texs[u.UserData[userID][Device].(string)]
 }
+
+type DiscStruct struct {
+	SettingsAddr  string
+	LogAddr       string
+	GameStartData map[string]interface{}
+}
+
+func MakeDiscStruct(s, l, g string) *DiscStruct {
+	gameStartData := []byte(g)
+	var dataMap map[string]interface{}
+	err := json.Unmarshal(gameStartData, &dataMap)
+	if err != nil {
+		fmt.Println("Unmarshal error:", err)
+	}
+	return &DiscStruct{
+		SettingsAddr:  s,
+		LogAddr:       l,
+		GameStartData: dataMap,
+	}
+}
diff --git a/go/src/hearts/img/view/view.go b/go/src/hearts/img/view/view.go
index 99f9cba..41dd84c 100644
--- a/go/src/hearts/img/view/view.go
+++ b/go/src/hearts/img/view/view.go
@@ -9,6 +9,7 @@
 package view
 
 import (
+	"fmt"
 	"sort"
 	"strconv"
 	"time"
@@ -49,14 +50,18 @@
 	addArrangePlayer(3, arrangeDim, arrangeBlockLength, u)
 	// table
 	watchPos := coords.MakeVec((u.WindowSize.X-arrangeDim.X)/2, (u.WindowSize.Y+arrangeBlockLength)/2-2*arrangeDim.Y-4*u.Padding)
-	u.Buttons = append(u.Buttons, texture.MakeImgWithoutAlt(watchImg, watchPos, arrangeDim, u))
+	u.Buttons["joinTable"] = texture.MakeImgWithoutAlt(watchImg, watchPos, arrangeDim, u)
+	quitImg := u.Texs["Quit.png"]
+	quitDim := u.CardDim
+	quitPos := coords.MakeVec(u.Padding, u.TopPadding+10)
+	u.Buttons["exit"] = texture.MakeImgWithoutAlt(quitImg, quitPos, quitDim, u)
 	if u.IsOwner {
 		startImg := u.Texs["StartBlue.png"]
 		startAlt := u.Texs["StartGray.png"]
 		startDim := coords.MakeVec(2*u.CardDim.X, u.CardDim.Y)
 		startPos := u.WindowSize.MinusVec(startDim).Minus(u.BottomPadding)
 		display := u.CurTable.AllReadyForNewRound()
-		u.Buttons = append(u.Buttons, texture.MakeImgWithAlt(startImg, startAlt, startPos, startDim, display, u))
+		u.Buttons["start"] = texture.MakeImgWithAlt(startImg, startAlt, startPos, startDim, display, u)
 	}
 }
 
@@ -76,7 +81,7 @@
 }
 
 // Discovery view: Displays a menu of possible games to join
-func LoadDiscoveryView(discChan chan []string, u *uistate.UIState) {
+func LoadDiscoveryView(u *uistate.UIState) {
 	u.CurView = uistate.Discovery
 	reposition.ResetAnims(u)
 	resetImgs(u)
@@ -84,18 +89,37 @@
 	newGameImg := u.Texs["NewGame.png"]
 	newGameDim := coords.MakeVec(2*u.CardDim.X, u.CardDim.Y)
 	newGamePos := coords.MakeVec((u.WindowSize.X-newGameDim.X)/2, u.TopPadding)
-	u.Buttons = append(u.Buttons, texture.MakeImgWithoutAlt(newGameImg, newGamePos, newGameDim, u))
+	u.Buttons["newGame"] = texture.MakeImgWithoutAlt(newGameImg, newGamePos, newGameDim, u)
 	buttonNum := 1
-	go func() {
-		for u.CurView == uistate.Discovery {
-			sgSet := <-discChan
-			joinGameImg := u.Texs["JoinGame.png"]
-			joinGamePos := coords.MakeVec(newGamePos.X, newGamePos.Y+float32(buttonNum)*(newGameDim.Y+u.Padding))
-			u.Buttons = append(u.Buttons, texture.MakeImgWithoutAlt(joinGameImg, joinGamePos, newGameDim, u))
-			u.Buttons[buttonNum].SetInfo(sgSet)
-			buttonNum++
+	for _, d := range u.DiscGroups {
+		if d != nil {
+			dataMap := d.GameStartData
+			creatorID := int(dataMap["ownerID"].(float64))
+			if u.UserData[creatorID] != nil {
+				bgBannerDim := coords.MakeVec(u.WindowSize.X, u.CardDim.Y+(4*u.Padding/5))
+				bgBannerPos := coords.MakeVec(0, newGamePos.Y+float32(buttonNum)*(bgBannerDim.Y+u.Padding))
+				playerIconImg := u.Texs[u.UserData[creatorID]["avatar"].(string)]
+				playerIconDim := u.CardDim
+				playerIconPos := coords.MakeVec(u.BottomPadding, bgBannerPos.Y+2*u.Padding/5)
+				u.BackgroundImgs = append(u.BackgroundImgs,
+					texture.MakeImgWithoutAlt(playerIconImg, playerIconPos, playerIconDim, u))
+				creatorName := u.UserData[creatorID]["name"].(string)
+				gameText := creatorName + "'s game"
+				scaler := float32(6)
+				maxWidth := u.WindowSize.X - 3*u.CardDim.X - 4*u.Padding
+				left := coords.MakeVec(playerIconPos.X+playerIconDim.X+u.Padding, playerIconPos.Y+playerIconDim.Y/2-10)
+				textImgs := texture.MakeStringImgLeftAlign(gameText, "", "", true, left, scaler, maxWidth, u)
+				for _, img := range textImgs {
+					u.BackgroundImgs = append(u.BackgroundImgs, img)
+				}
+				joinGameImg := u.Texs["JoinGame.png"]
+				joinGamePos := coords.MakeVec(u.WindowSize.X-u.BottomPadding-newGameDim.X, newGamePos.Y+float32(buttonNum)*(bgBannerDim.Y+u.Padding))
+				u.Buttons[fmt.Sprintf("joinGame-%d", buttonNum)] = texture.MakeImgWithoutAlt(joinGameImg, joinGamePos, newGameDim, u)
+				u.Buttons[fmt.Sprintf("joinGame-%d", buttonNum)].SetInfo(d.LogAddr)
+				buttonNum++
+			}
 		}
-	}()
+	}
 }
 
 // Table View: Displays the table. Intended for public devices
@@ -190,8 +214,7 @@
 	playerIconY := u.WindowSize.Y - u.TableCardDim.Y - u.BottomPadding - u.Padding - u.PlayerIconDim.Y
 	playerIconPos := coords.MakeVec(playerIconX, playerIconY)
 	if u.Debug {
-		u.Buttons = append(u.Buttons,
-			texture.MakeImgWithoutAlt(playerIconImage, playerIconPos, u.PlayerIconDim, u))
+		u.Buttons["player0"] = texture.MakeImgWithoutAlt(playerIconImage, playerIconPos, u.PlayerIconDim, u)
 	} else {
 		u.BackgroundImgs = append(u.BackgroundImgs,
 			texture.MakeImgWithoutAlt(playerIconImage, playerIconPos, u.PlayerIconDim, u))
@@ -218,8 +241,7 @@
 		u.PlayerIconDim.Y - u.Padding
 	playerIconPos = coords.MakeVec(playerIconX, playerIconY)
 	if u.Debug {
-		u.Buttons = append(u.Buttons,
-			texture.MakeImgWithoutAlt(playerIconImage, playerIconPos, u.PlayerIconDim, u))
+		u.Buttons["player1"] = texture.MakeImgWithoutAlt(playerIconImage, playerIconPos, u.PlayerIconDim, u)
 	} else {
 		u.BackgroundImgs = append(u.BackgroundImgs,
 			texture.MakeImgWithoutAlt(playerIconImage, playerIconPos, u.PlayerIconDim, u))
@@ -242,8 +264,7 @@
 	playerIconY = u.TopPadding + u.TableCardDim.Y + u.Padding
 	playerIconPos = coords.MakeVec(playerIconX, playerIconY)
 	if u.Debug {
-		u.Buttons = append(u.Buttons,
-			texture.MakeImgWithoutAlt(playerIconImage, playerIconPos, u.PlayerIconDim, u))
+		u.Buttons["player2"] = texture.MakeImgWithoutAlt(playerIconImage, playerIconPos, u.PlayerIconDim, u)
 	} else {
 		u.BackgroundImgs = append(u.BackgroundImgs,
 			texture.MakeImgWithoutAlt(playerIconImage, playerIconPos, u.PlayerIconDim, u))
@@ -269,8 +290,7 @@
 		u.PlayerIconDim.Y - u.Padding
 	playerIconPos = coords.MakeVec(playerIconX, playerIconY)
 	if u.Debug {
-		u.Buttons = append(u.Buttons,
-			texture.MakeImgWithoutAlt(playerIconImage, playerIconPos, u.PlayerIconDim, u))
+		u.Buttons["player3"] = texture.MakeImgWithoutAlt(playerIconImage, playerIconPos, u.PlayerIconDim, u)
 	} else {
 		u.BackgroundImgs = append(u.BackgroundImgs,
 			texture.MakeImgWithoutAlt(playerIconImage, playerIconPos, u.PlayerIconDim, u))
@@ -440,11 +460,15 @@
 func ChangePlayMessage(message string, u *uistate.UIState) {
 	// remove text and replace with message
 	for _, img := range u.Other {
-		u.Scene.RemoveChild(img.GetNode())
+		if img.GetNode().Parent == u.Scene {
+			u.Scene.RemoveChild(img.GetNode())
+		}
 	}
-	u.Scene.RemoveChild(u.Buttons[0].GetNode())
+	if u.Buttons["toggleSplit"].GetNode().Parent == u.Scene {
+		u.Scene.RemoveChild(u.Buttons["toggleSplit"].GetNode())
+	}
 	u.Other = make([]*staticimg.StaticImg, 0)
-	u.Buttons = make([]*staticimg.StaticImg, 0)
+	u.Buttons = make(map[string]*staticimg.StaticImg)
 	addPlayHeader(message, false, u)
 	for _, img := range u.Other {
 		reposition.BringNodeToFront(img.GetNode(), u)
@@ -471,9 +495,8 @@
 		sitPos = coords.MakeVec((u.WindowSize.X-arrangeDim.X)/2+arrangeDim.X+2*u.Padding, (u.WindowSize.Y+arrangeBlockLength)/2-2*arrangeDim.Y-4*u.Padding)
 	}
 	if u.PlayerData[player] == 0 {
-		u.Buttons = append(u.Buttons, texture.MakeImgWithoutAlt(sitImg, sitPos, arrangeDim, u))
+		u.Buttons[fmt.Sprintf("joinPlayer-%d", player)] = texture.MakeImgWithoutAlt(sitImg, sitPos, arrangeDim, u)
 	} else {
-		u.Buttons = append(u.Buttons, texture.MakeImgWithoutAlt(sitImg, sitPos, coords.MakeVec(0, 0), u))
 		avatar := uistate.GetAvatar(player, u)
 		u.BackgroundImgs = append(u.BackgroundImgs, texture.MakeImgWithoutAlt(avatar, sitPos, arrangeDim, u))
 		name := uistate.GetName(player, u)
@@ -629,8 +652,7 @@
 	pullTabAlt := u.Texs["VisibilityOff.png"]
 	pullTabDim := u.CardDim.DividedBy(2)
 	pullTabPos := headerPos.PlusVec(headerDimensions).MinusVec(pullTabDim).Minus(u.Padding)
-	u.Buttons = append(u.Buttons,
-		texture.MakeImgWithAlt(pullTabImage, pullTabAlt, pullTabPos, pullTabDim, !beforeSplitAnimation, u))
+	u.Buttons["toggleSplit"] = texture.MakeImgWithAlt(pullTabImage, pullTabAlt, pullTabPos, pullTabDim, !beforeSplitAnimation, u)
 	// adding text
 	color := "DBlue"
 	scaler := float32(4)
@@ -764,7 +786,9 @@
 func SetNumTricksTable(u *uistate.UIState) {
 	// remove old num tricks
 	for _, img := range u.Other {
-		u.Scene.RemoveChild(img.GetNode())
+		if img.GetNode().Parent == u.Scene {
+			u.Scene.RemoveChild(img.GetNode())
+		}
 	}
 	u.Other = make([]*staticimg.StaticImg, 0)
 	// set new num tricks
@@ -804,11 +828,13 @@
 func SetNumTricksHand(u *uistate.UIState) {
 	// remove old num tricks
 	for _, img := range u.ModText {
-		u.Scene.RemoveChild(img.GetNode())
+		if img.GetNode().Parent == u.Scene {
+			u.Scene.RemoveChild(img.GetNode())
+		}
 	}
 	u.ModText = make([]*staticimg.StaticImg, 0)
 	// set new num tricks
-	b := u.Buttons[0]
+	b := u.Buttons["toggleSplit"]
 	center := coords.MakeVec(u.Padding+b.GetDimensions().X/2, b.GetCurrent().Y-3*u.Padding)
 	scaler := float32(4)
 	max := b.GetDimensions().Y
@@ -886,8 +912,7 @@
 	pullTabImage := u.Texs["VerticalPullTab.png"]
 	pullTabSpotPos := coords.MakeVec(2*u.BottomPadding, passBannerPos.Y)
 	pullTabSpotDim := coords.MakeVec(4*u.BottomPadding, u.CardDim.Y+2*u.Padding)
-	u.Buttons = append(u.Buttons,
-		texture.MakeImgWithAlt(pullTabImage, pullTabSpotImage, pullTabSpotPos, pullTabSpotDim, false, u))
+	u.Buttons["dragPass"] = texture.MakeImgWithAlt(pullTabImage, pullTabSpotImage, pullTabSpotPos, pullTabSpotDim, false, u)
 	// adding text
 	textLeft := coords.MakeVec(pullTabSpotPos.X+pullTabSpotDim.X/2, passBannerPos.Y-20)
 	scaler := float32(5)
@@ -1075,8 +1100,7 @@
 	}
 	buttonDim := coords.MakeVec(2*u.CardDim.X, 3*u.CardDim.Y/4)
 	buttonPos := coords.MakeVec((u.WindowSize.X-buttonDim.X)/2, u.WindowSize.Y-buttonDim.Y-u.BottomPadding)
-	u.Buttons = append(u.Buttons,
-		texture.MakeImgWithoutAlt(buttonImg, buttonPos, buttonDim, u))
+	u.Buttons["ready"] = texture.MakeImgWithoutAlt(buttonImg, buttonPos, buttonDim, u)
 }
 
 func resetImgs(u *uistate.UIState) {
@@ -1085,7 +1109,7 @@
 	u.BackgroundImgs = make([]*staticimg.StaticImg, 0)
 	u.EmptySuitImgs = make([]*staticimg.StaticImg, 0)
 	u.DropTargets = make([]*staticimg.StaticImg, 0)
-	u.Buttons = make([]*staticimg.StaticImg, 0)
+	u.Buttons = make(map[string]*staticimg.StaticImg)
 	u.Other = make([]*staticimg.StaticImg, 0)
 	u.ModText = make([]*staticimg.StaticImg, 0)
 }
@@ -1103,12 +1127,10 @@
 	buttonDim := u.CardDim
 	debugTableImage := u.Texs["BakuSquare.png"]
 	debugTablePos := u.WindowSize.MinusVec(buttonDim)
-	u.Buttons = append(u.Buttons,
-		texture.MakeImgWithoutAlt(debugTableImage, debugTablePos, buttonDim, u))
+	u.Buttons["table"] = texture.MakeImgWithoutAlt(debugTableImage, debugTablePos, buttonDim, u)
 	debugPassImage := u.Texs["Clubs-2.png"]
 	debugPassPos := coords.MakeVec(u.WindowSize.X-2*buttonDim.X, u.WindowSize.Y-buttonDim.Y)
-	u.Buttons = append(u.Buttons,
-		texture.MakeImgWithoutAlt(debugPassImage, debugPassPos, buttonDim, u))
+	u.Buttons["hand"] = texture.MakeImgWithoutAlt(debugPassImage, debugPassPos, buttonDim, u)
 }
 
 // Helper function that returns the largest int in a non-negative int array (not index of largest int)
diff --git a/go/src/hearts/main.go b/go/src/hearts/main.go
index 8526f23..de2ab74 100644
--- a/go/src/hearts/main.go
+++ b/go/src/hearts/main.go
@@ -123,10 +123,9 @@
 
 func onPaint(glctx gl.Context, sz size.Event, u *uistate.UIState) {
 	if u.CurView == uistate.None {
-		discChan := make(chan []string)
 		u.ScanChan = make(chan bool)
-		go sync.ScanForSG(discChan, u.Ctx, u.ScanChan)
-		view.LoadDiscoveryView(discChan, u)
+		go sync.ScanForSG(u.Ctx, u.ScanChan, u)
+		view.LoadDiscoveryView(u)
 	}
 	glctx.ClearColor(1, 1, 1, 1)
 	glctx.Clear(gl.COLOR_BUFFER_BIT)
diff --git a/go/src/hearts/sync/client.go b/go/src/hearts/sync/client.go
index c126c7c..27b721d 100644
--- a/go/src/hearts/sync/client.go
+++ b/go/src/hearts/sync/client.go
@@ -8,10 +8,9 @@
 
 import (
 	"fmt"
-	"strconv"
-	"strings"
 
 	"hearts/img/uistate"
+	"hearts/img/view"
 
 	"v.io/v23/context"
 	"v.io/v23/discovery"
@@ -24,7 +23,7 @@
 )
 
 // Searches for new syncgroups being advertised, sends found syncgroups to sgChan
-func ScanForSG(sgChan chan []string, ctx *context.T, quit chan bool) {
+func ScanForSG(ctx *context.T, quit chan bool, u *uistate.UIState) {
 	mdns, err := mdns.New("")
 	if err != nil {
 		ctx.Fatalf("Plugin failed: %v", err)
@@ -40,10 +39,13 @@
 	for {
 		select {
 		case update := <-ch:
-			sgNames := GetSG(instances, update)
-			if sgNames != nil {
-				sgChan <- sgNames
+			key, discStruct := GetSG(instances, update, u)
+			if discStruct != nil {
+				settingsAddr := discStruct.SettingsAddr
+				JoinSettingsSyncgroup(settingsAddr, u)
+				u.DiscGroups[key] = discStruct
 			}
+			view.LoadDiscoveryView(u)
 		case <-signals.ShutdownOnSignals(ctx):
 			break loop
 		case <-quit:
@@ -53,25 +55,28 @@
 }
 
 // Returns the addresses of any discovered syncgroups that contain croupier game information
-func GetSG(instances map[string]string, update discovery.Update) []string {
-	switch u := update.(type) {
+func GetSG(instances map[string]string, update discovery.Update, u *uistate.UIState) (string, *uistate.DiscStruct) {
+	switch uType := update.(type) {
 	case discovery.UpdateFound:
-		found := u.Value
+		found := uType.Value
 		instances[string(found.Service.InstanceId)] = found.Service.InstanceName
 		fmt.Printf("Discovered %q: Instance=%x, Interface=%q, Addrs=%v\n", found.Service.InstanceName, found.Service.InstanceId, found.Service.InterfaceName, found.Service.Addrs)
 		if found.Service.InterfaceName == CroupierInterface {
-			return []string{found.Service.Attrs["settings_sgname"], found.Service.Addrs[0]}
+			key := found.Service.InstanceId
+			ds := uistate.MakeDiscStruct(found.Service.Attrs["settings_sgname"], found.Service.Addrs[0], found.Service.Attrs["game_start_data"])
+			return key, ds
 		}
 	case discovery.UpdateLost:
-		lost := u.Value
+		lost := uType.Value
 		name, ok := instances[string(lost.InstanceId)]
 		if !ok {
 			name = "unknown"
 		}
 		delete(instances, string(lost.InstanceId))
+		u.DiscGroups[lost.InstanceId] = nil
 		fmt.Printf("Lost %q: Instance=%x\n", name, lost.InstanceId)
 	}
-	return nil
+	return "", nil
 }
 
 // Returns a watchstream of the data in the table
@@ -107,17 +112,15 @@
 		ch <- false
 	} else {
 		fmt.Println("Syncgroup joined")
-		// Set UIState GameID
-		tmp := strings.Split(logName, "-")
-		gameID, _ := strconv.Atoi(tmp[len(tmp)-1])
-		u.GameID = gameID
-		go UpdateGame(u)
+		if u.LogSG != logName {
+			resetGame(logName, false, u)
+		}
 		ch <- true
 	}
 }
 
 // Joins player settings syncgroup
-func JoinSettingsSyncgroup(ch chan bool, settingsName string, u *uistate.UIState) {
+func JoinSettingsSyncgroup(settingsName string, u *uistate.UIState) {
 	fmt.Println("Joining user settings syncgroup")
 	app := u.Service.App(AppName)
 	db := app.NoSQLDatabase(DbName, nil)
@@ -126,10 +129,8 @@
 	_, err := settingsSg.Join(u.Ctx, myInfoJoiner)
 	if err != nil {
 		fmt.Println("SYNCGROUP JOIN ERROR: ", err)
-		ch <- false
 	} else {
 		fmt.Println("Syncgroup joined")
-		ch <- true
 	}
 }
 
diff --git a/go/src/hearts/sync/server.go b/go/src/hearts/sync/server.go
index 155830d..03887be 100644
--- a/go/src/hearts/sync/server.go
+++ b/go/src/hearts/sync/server.go
@@ -10,6 +10,8 @@
 	"encoding/json"
 	"fmt"
 	"math/rand"
+	"strconv"
+	"strings"
 
 	"hearts/img/uistate"
 
@@ -167,7 +169,9 @@
 		}
 	} else {
 		fmt.Println("Syncgroup created")
-		go UpdateGame(u)
+		if logSGName != u.LogSG {
+			resetGame(logSGName, true, u)
+		}
 		ch <- logSGName
 	}
 }
@@ -214,3 +218,15 @@
 		ch <- settingsSGName
 	}
 }
+
+func resetGame(logName string, creator bool, u *uistate.UIState) {
+	u.PlayerData = make(map[int]int)
+	u.CurPlayerIndex = -1
+	u.LogSG = logName
+	if !creator {
+		tmp := strings.Split(logName, "-")
+		gameID, _ := strconv.Atoi(tmp[len(tmp)-1])
+		u.GameID = gameID
+	}
+	go UpdateGame(u)
+}
diff --git a/go/src/hearts/sync/util.go b/go/src/hearts/sync/util.go
index cf72edd..f5a77b1 100644
--- a/go/src/hearts/sync/util.go
+++ b/go/src/hearts/sync/util.go
@@ -13,11 +13,11 @@
 	UserID            = 4444
 	UserColor         = 16777215
 	UserAvatar        = "player3.jpeg"
-	UserName          = "Dan"
+	UserName          = "D"
 	SBName            = "syncbase3"
 	AppName           = "app"
 	DbName            = "db"
 	LogName           = "games"
 	SettingsName      = "table_settings"
-	CroupierInterface = "CroupierSettingsAndGameEmily"
+	CroupierInterface = "CroupierSettingsAndGame"
 )
diff --git a/go/src/hearts/sync/watch.go b/go/src/hearts/sync/watch.go
index 0184ec4..3cf301b 100644
--- a/go/src/hearts/sync/watch.go
+++ b/go/src/hearts/sync/watch.go
@@ -40,19 +40,20 @@
 	stream, err := WatchData(SettingsName, "users", u)
 	if err != nil {
 		fmt.Println("WatchData error:", err)
-	}
-	for {
-		if updateExists := stream.Advance(); updateExists {
-			c := stream.Change()
-			if c.ChangeType == nosql.PutChange {
-				key := c.Row
-				var value []byte
-				if err := c.Value(&value); err != nil {
-					fmt.Println("Value error:", err)
+	} else {
+		for {
+			if updateExists := stream.Advance(); updateExists {
+				c := stream.Change()
+				if c.ChangeType == nosql.PutChange {
+					key := c.Row
+					var value []byte
+					if err := c.Value(&value); err != nil {
+						fmt.Println("Value error:", err)
+					}
+					handleSettingsUpdate(key, value, u)
+				} else {
+					fmt.Println("Unexpected ChangeType: ", c.ChangeType)
 				}
-				handleSettingsUpdate(key, value, u)
-			} else {
-				fmt.Println("Unexpected ChangeType: ", c.ChangeType)
 			}
 		}
 	}
@@ -84,6 +85,9 @@
 			}
 		}
 	}
+	if u.CurView == uistate.Discovery {
+		view.LoadDiscoveryView(u)
+	}
 }
 
 func UpdateGame(u *uistate.UIState) {
@@ -118,15 +122,15 @@
 		updateType := strings.Split(valueStr, "|")[0]
 		switch updateType {
 		case Deal:
-			go onDeal(valueStr, u)
+			onDeal(valueStr, u)
 		case Pass:
-			go onPass(valueStr, u)
+			onPass(valueStr, u)
 		case Take:
-			go onTake(valueStr, u)
+			onTake(valueStr, u)
 		case Play:
-			go onPlay(valueStr, u)
+			onPlay(valueStr, u)
 		case Ready:
-			go onReady(valueStr, u)
+			onReady(valueStr, u)
 		}
 	case "players":
 		switch strings.Split(key, "/")[3] {
@@ -149,9 +153,7 @@
 }
 
 func onSettings(key, value string, u *uistate.UIState) {
-	joinDone := make(chan bool)
-	go JoinSettingsSyncgroup(joinDone, value, u)
-	<-joinDone
+	JoinSettingsSyncgroup(value, u)
 }
 
 func onDeal(value string, u *uistate.UIState) {
@@ -347,7 +349,7 @@
 	// UI
 	if u.CurTable.AllReadyForNewRound() && u.IsOwner {
 		if u.CurView == uistate.Arrange {
-			b := u.Buttons[5]
+			b := u.Buttons["start"]
 			if !b.GetDisplayingImage() {
 				u.Eng.SetSubTex(b.GetNode(), b.GetImage())
 				b.SetDisplayingImage(true)
diff --git a/go/src/hearts/touchhandler/touchhandler.go b/go/src/hearts/touchhandler/touchhandler.go
index 12dce44..65918e7 100644
--- a/go/src/hearts/touchhandler/touchhandler.go
+++ b/go/src/hearts/touchhandler/touchhandler.go
@@ -8,6 +8,8 @@
 
 import (
 	"fmt"
+	"strconv"
+	"strings"
 
 	"golang.org/x/mobile/event/touch"
 	"golang.org/x/mobile/exp/sprite"
@@ -107,8 +109,8 @@
 
 func beginClickDiscovery(t touch.Event, u *uistate.UIState) {
 	buttonList := findClickedButton(t, u)
-	if len(buttonList) > 0 {
-		if buttonList[0] == u.Buttons[0] {
+	for _, button := range buttonList {
+		if button == u.Buttons["newGame"] {
 			logCh := make(chan string)
 			settingsCh := make(chan string)
 			go sync.CreateLogSyncgroup(logCh, u)
@@ -126,14 +128,10 @@
 			}
 		} else {
 			for _, b := range u.Buttons {
-				if buttonList[0] == b {
+				if button == b {
 					joinLogDone := make(chan bool)
-					joinSettingsDone := make(chan bool)
-					settingsAddr := b.GetInfo()[0]
-					logAddr := b.GetInfo()[1]
+					logAddr := b.GetInfo()
 					go sync.JoinLogSyncgroup(joinLogDone, logAddr, u)
-					go sync.JoinSettingsSyncgroup(joinSettingsDone, settingsAddr, u)
-					<-joinSettingsDone
 					if success := <-joinLogDone; success {
 						settingsCh := make(chan string)
 						go sync.CreateSettingsSyncgroup(settingsCh, u)
@@ -155,27 +153,43 @@
 
 func beginClickArrange(t touch.Event, u *uistate.UIState) {
 	buttonList := findClickedButton(t, u)
-	if len(buttonList) > 0 {
-		for i, b := range u.Buttons {
-			if buttonList[0] == b {
-				if i == 5 {
-					if b.GetDisplayingImage() {
-						successStart := sync.LogGameStart(u)
-						for !successStart {
-							successStart = sync.LogGameStart(u)
-						}
-						newHands := u.CurTable.Deal()
-						successDeal := sync.LogDeal(u, u.CurPlayerIndex, newHands)
-						for !successDeal {
-							successDeal = sync.LogDeal(u, u.CurPlayerIndex, newHands)
+	for _, b := range buttonList {
+		if b == u.Buttons["exit"] {
+			if u.SGChan != nil {
+				u.SGChan <- true
+				u.SGChan = nil
+			}
+			u.IsOwner = false
+			u.DiscGroups = make(map[string]*uistate.DiscStruct)
+			u.ScanChan = make(chan bool)
+			go sync.ScanForSG(u.Ctx, u.ScanChan, u)
+			view.LoadDiscoveryView(u)
+		} else if b == u.Buttons["start"] {
+			if b.GetDisplayingImage() {
+				successStart := sync.LogGameStart(u)
+				for !successStart {
+					successStart = sync.LogGameStart(u)
+				}
+				newHands := u.CurTable.Deal()
+				successDeal := sync.LogDeal(u, u.CurPlayerIndex, newHands)
+				for !successDeal {
+					successDeal = sync.LogDeal(u, u.CurPlayerIndex, newHands)
+				}
+			}
+		} else {
+			for key, button := range u.Buttons {
+				if b == button {
+					if key == "joinTable" {
+						u.CurPlayerIndex = 4
+						sync.LogPlayerNum(u)
+					} else {
+						playerNum := strings.Split(key, "-")[1]
+						if u.CurPlayerIndex < 0 {
+							u.CurPlayerIndex, _ = strconv.Atoi(playerNum)
+							sync.LogReady(u)
+							sync.LogPlayerNum(u)
 						}
 					}
-				} else if u.CurPlayerIndex < 0 {
-					u.CurPlayerIndex = i
-					if u.CurPlayerIndex >= 0 && u.CurPlayerIndex < u.NumPlayers {
-						sync.LogReady(u)
-					}
-					sync.LogPlayerNum(u)
 				}
 			}
 		}
@@ -195,51 +209,27 @@
 		reposition.BringNodeToFront(u.CurCard.GetNode(), u)
 	}
 	buttonList := findClickedButton(t, u)
-	if len(buttonList) > 0 {
-		if u.Debug {
-			if u.Buttons[0] == buttonList[0] {
-				pullTab := u.Buttons[0]
-				if pullTab.GetDisplayingImage() {
-					u.CurImg = u.Buttons[0]
-					for _, img := range u.Other {
-						u.Eng.SetSubTex(img.GetNode(), img.GetAlt())
-						img.SetDisplayingImage(false)
-					}
-					blueBanner := u.Other[0]
-					reposition.BringNodeToFront(u.BackgroundImgs[1].GetNode(), u)
-					reposition.BringNodeToFront(pullTab.GetNode(), u)
-					for _, d := range u.DropTargets {
-						reposition.BringNodeToFront(d.GetCardHere().GetNode(), u)
-					}
-					if blueBanner.GetNode().Arranger == nil {
-						finalX := blueBanner.GetInitial().X
-						finalY := pullTab.GetInitial().Y + pullTab.GetDimensions().Y - blueBanner.GetDimensions().Y
-						finalPos := coords.MakeVec(finalX, finalY)
-						reposition.AnimateImageNoChannel(blueBanner, finalPos, blueBanner.GetDimensions(), u)
-					}
-				}
-			} else if u.Buttons[1] == buttonList[0] {
-				view.LoadTableView(u)
-			} else if u.Buttons[2] == buttonList[0] {
-				view.LoadPassOrTakeOrPlay(u)
-			}
-		} else {
-			pullTab := u.Buttons[0]
-			if pullTab.GetDisplayingImage() {
-				u.CurImg = u.Buttons[0]
+	for _, b := range buttonList {
+		if b == u.Buttons["table"] {
+			view.LoadTableView(u)
+		} else if b == u.Buttons["hand"] {
+			view.LoadPassOrTakeOrPlay(u)
+		} else if b == u.Buttons["dragPass"] {
+			if b.GetDisplayingImage() {
+				u.CurImg = b
 				for _, img := range u.Other {
 					u.Eng.SetSubTex(img.GetNode(), img.GetAlt())
 					img.SetDisplayingImage(false)
 				}
 				blueBanner := u.Other[0]
 				reposition.BringNodeToFront(u.BackgroundImgs[1].GetNode(), u)
-				reposition.BringNodeToFront(pullTab.GetNode(), u)
+				reposition.BringNodeToFront(b.GetNode(), u)
 				for _, d := range u.DropTargets {
 					reposition.BringNodeToFront(d.GetCardHere().GetNode(), u)
 				}
 				if blueBanner.GetNode().Arranger == nil {
 					finalX := blueBanner.GetInitial().X
-					finalY := pullTab.GetInitial().Y + pullTab.GetDimensions().Y - blueBanner.GetDimensions().Y
+					finalY := b.GetInitial().Y + b.GetDimensions().Y - blueBanner.GetDimensions().Y
 					finalPos := coords.MakeVec(finalX, finalY)
 					reposition.AnimateImageNoChannel(blueBanner, finalPos, blueBanner.GetDimensions(), u)
 				}
@@ -252,7 +242,7 @@
 	if u.CurImg != nil {
 		imgs := make([]*staticimg.StaticImg, 0)
 		cards := make([]*card.Card, 0)
-		pullTab := u.Buttons[0]
+		pullTab := u.Buttons["dragPass"]
 		blueBanner := u.BackgroundImgs[1]
 		imgs = append(imgs, pullTab)
 		imgs = append(imgs, blueBanner)
@@ -287,7 +277,7 @@
 				readyToPass = false
 			}
 		}
-		pullTab := u.Buttons[0]
+		pullTab := u.Buttons["dragPass"]
 		if readyToPass {
 			u.Eng.SetSubTex(pullTab.GetNode(), pullTab.GetImage())
 			pullTab.SetDisplayingImage(true)
@@ -322,13 +312,11 @@
 		u.CurCard.GetNode().Arranger = nil
 	}
 	buttonList := findClickedButton(t, u)
-	if len(buttonList) > 0 {
-		if u.Debug {
-			if u.Buttons[0] == buttonList[0] {
-				view.LoadTableView(u)
-			} else if u.Buttons[1] == buttonList[0] {
-				view.LoadPassOrTakeOrPlay(u)
-			}
+	for _, b := range buttonList {
+		if b == u.Buttons["table"] {
+			view.LoadTableView(u)
+		} else if b == u.Buttons["hand"] {
+			view.LoadPassOrTakeOrPlay(u)
 		}
 	}
 }
@@ -373,17 +361,13 @@
 		reposition.BringNodeToFront(u.CurCard.GetNode(), u)
 	}
 	buttonList := findClickedButton(t, u)
-	if len(buttonList) > 0 {
-		if u.Debug {
-			if u.Buttons[0] == buttonList[0] && !u.SwitchingViews {
-				view.LoadSplitView(false, u)
-			} else if u.Buttons[1] == buttonList[0] {
-				view.LoadTableView(u)
-			} else if u.Buttons[2] == buttonList[0] {
-				view.LoadPassOrTakeOrPlay(u)
-			}
-		} else if !u.SwitchingViews {
+	for _, b := range buttonList {
+		if b == u.Buttons["toggleSplit"] && !u.SwitchingViews {
 			view.LoadSplitView(false, u)
+		} else if b == u.Buttons["table"] {
+			view.LoadTableView(u)
+		} else if b == u.Buttons["hand"] {
+			view.LoadPassOrTakeOrPlay(u)
 		}
 	}
 }
@@ -423,27 +407,8 @@
 		reposition.BringNodeToFront(u.CurCard.GetNode(), u)
 	}
 	buttonList := findClickedButton(t, u)
-	if len(buttonList) > 0 {
-		if u.Debug {
-			if u.Buttons[0] == buttonList[0] && !u.SwitchingViews {
-				ch := make(chan bool)
-				u.SwitchingViews = true
-				reposition.AnimateOutSplit(ch, u)
-				quit := make(chan bool)
-				u.AnimChans = append(u.AnimChans, quit)
-				go func() {
-					onDone := func() {
-						u.SwitchingViews = false
-						view.LoadPlayView(u)
-					}
-					reposition.SwitchOnChan(ch, quit, onDone, u)
-				}()
-			} else if u.Buttons[1] == buttonList[0] {
-				view.LoadTableView(u)
-			} else if u.Buttons[2] == buttonList[0] {
-				view.LoadPassOrTakeOrPlay(u)
-			}
-		} else if !u.SwitchingViews {
+	for _, b := range buttonList {
+		if b == u.Buttons["toggleSplit"] && !u.SwitchingViews {
 			ch := make(chan bool)
 			u.SwitchingViews = true
 			reposition.AnimateOutSplit(ch, u)
@@ -456,6 +421,10 @@
 				}
 				reposition.SwitchOnChan(ch, quit, onDone, u)
 			}()
+		} else if b == u.Buttons["table"] {
+			view.LoadTableView(u)
+		} else if b == u.Buttons["hand"] {
+			view.LoadPassOrTakeOrPlay(u)
 		}
 	}
 }
@@ -533,7 +502,7 @@
 		success = sync.LogPass(u, cardsPassed)
 	}
 	// UI component
-	pullTab := u.Buttons[0]
+	pullTab := u.Buttons["dragPass"]
 	blueBanner := u.BackgroundImgs[1]
 	imgs := []*staticimg.StaticImg{pullTab, blueBanner}
 	for _, d := range dropsToReset {
@@ -672,21 +641,21 @@
 }
 
 func updateViewFromTable(b *staticimg.StaticImg, u *uistate.UIState) {
-	if u.Buttons[0] == b {
+	if b == u.Buttons["player0"] {
 		u.CurPlayerIndex = 0
 		view.LoadPassOrTakeOrPlay(u)
-	} else if u.Buttons[1] == b {
+	} else if b == u.Buttons["player1"] {
 		u.CurPlayerIndex = 1
 		view.LoadPassOrTakeOrPlay(u)
-	} else if u.Buttons[2] == b {
+	} else if b == u.Buttons["player2"] {
 		u.CurPlayerIndex = 2
 		view.LoadPassOrTakeOrPlay(u)
-	} else if u.Buttons[3] == b {
+	} else if b == u.Buttons["player3"] {
 		u.CurPlayerIndex = 3
 		view.LoadPassOrTakeOrPlay(u)
-	} else if u.Buttons[4] == b {
+	} else if b == u.Buttons["table"] {
 		view.LoadTableView(u)
-	} else if u.Buttons[5] == b {
+	} else if b == u.Buttons["hand"] {
 		view.LoadPassOrTakeOrPlay(u)
 	}
 }