Adding basic arrange players.
Discovery changes to align with Croupier Flutter.

Change-Id: Ia744cd9174a740532fcd6a0896aaf21423d9c13c
diff --git a/go/src/hearts/assets/SitSpot.png b/go/src/hearts/assets/SitSpot.png
new file mode 100644
index 0000000..3d66766
--- /dev/null
+++ b/go/src/hearts/assets/SitSpot.png
Binary files differ
diff --git a/go/src/hearts/assets/WatchSpot.png b/go/src/hearts/assets/WatchSpot.png
new file mode 100644
index 0000000..440011f
--- /dev/null
+++ b/go/src/hearts/assets/WatchSpot.png
Binary files differ
diff --git a/go/src/hearts/img/texture/texture.go b/go/src/hearts/img/texture/texture.go
index 3179eca..08c3fff 100644
--- a/go/src/hearts/img/texture/texture.go
+++ b/go/src/hearts/img/texture/texture.go
@@ -275,7 +275,7 @@
 		"R-Lower-Gray.png", "S-Lower-Gray.png", "T-Lower-Gray.png", "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", "Deal.png", "Period.png",
+		"VerticalPullTab.png", "NewGame.png", "NewRound.png", "JoinGame.png", "Deal.png", "Period.png", "SitSpot.png", "WatchSpot.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 d3c8aec..441b7b8 100644
--- a/go/src/hearts/img/uistate/uistate.go
+++ b/go/src/hearts/img/uistate/uistate.go
@@ -83,13 +83,15 @@
 	CurPlayerIndex int                      // the player number of this player
 	Ctx            *context.T
 	Service        syncbase.Service
-	Debug          bool        // true if debugging, adds extra functionality to switch between players
-	Shutdown       func()      // used to shut down a v23.Init()
-	GameID         int         // used to differentiate between concurrent games
-	IsOwner        bool        // true if this player is the game creator
-	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
+	Debug          bool                           // true if debugging, adds extra functionality to switch between players
+	Shutdown       func()                         // used to shut down a v23.Init()
+	GameID         int                            // used to differentiate between concurrent games
+	IsOwner        bool                           // true if this player is the game creator
+	UserData       map[int]map[string]interface{} // user data indexed by user ID
+	PlayerData     map[int]map[string]interface{} // user data indexed by player number
+	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
 }
 
 func MakeUIState() *UIState {
@@ -118,6 +120,8 @@
 		CurView:        None,
 		Done:           false,
 		Debug:          false,
+		UserData:       make(map[int]map[string]interface{}, 0),
+		PlayerData:     make(map[int]map[string]interface{}, 0),
 		AnimChans:      make([]chan bool, 0),
 	}
 }
diff --git a/go/src/hearts/img/view/view.go b/go/src/hearts/img/view/view.go
index b2cdf11..9718992 100644
--- a/go/src/hearts/img/view/view.go
+++ b/go/src/hearts/img/view/view.go
@@ -25,17 +25,64 @@
 	"golang.org/x/mobile/exp/sprite"
 )
 
-// TODO(emshack): Flesh out Arrange view to actually arrange players
 func LoadArrangeView(u *uistate.UIState) {
 	u.CurView = uistate.Arrange
 	<-time.After(1 * time.Second)
 	resetAnims(u)
 	resetImgs(u)
 	resetScene(u)
-	buttonPos := coords.MakeVec((u.WindowSize.X-2*u.CardDim.X)/2, (u.WindowSize.Y-u.CardDim.Y)/2)
-	buttonDim := coords.MakeVec(2*u.CardDim.X, u.CardDim.Y)
-	buttonImage := u.Texs["Deal.png"]
-	u.Buttons = append(u.Buttons, texture.MakeImgWithoutAlt(buttonImage, buttonPos, buttonDim, u.Eng, u.Scene))
+	addHeader(u)
+	sitImg := u.Texs["SitSpot.png"]
+	watchImg := u.Texs["WatchSpot.png"]
+	arrangeBlockLength := u.WindowSize.X - 4*u.Padding
+	if u.WindowSize.Y < u.WindowSize.X {
+		arrangeBlockLength = u.WindowSize.Y - 4*u.Padding
+	}
+	arrangeDim := coords.MakeVec(arrangeBlockLength/3-4*u.Padding, arrangeBlockLength/3-4*u.Padding)
+	nilDim := coords.MakeVec(0, 0)
+	// player 0 seat
+	sitPos := coords.MakeVec((u.WindowSize.X-arrangeDim.X)/2, u.WindowSize.Y-arrangeDim.Y-2*u.Padding)
+	if u.PlayerData[0] == nil {
+		u.Buttons = append(u.Buttons, texture.MakeImgWithoutAlt(sitImg, sitPos, arrangeDim, u.Eng, u.Scene))
+	} else {
+		u.Buttons = append(u.Buttons, texture.MakeImgWithoutAlt(sitImg, sitPos, nilDim, u.Eng, u.Scene))
+		avatarKey := u.PlayerData[0]["avatar"].(string)
+		avatar := u.Texs[avatarKey]
+		u.BackgroundImgs = append(u.BackgroundImgs, texture.MakeImgWithoutAlt(avatar, sitPos, arrangeDim, u.Eng, u.Scene))
+	}
+	// player 1 seat
+	sitPos = coords.MakeVec((u.WindowSize.X-arrangeDim.X)/2-arrangeDim.X-2*u.Padding, u.WindowSize.Y-2*arrangeDim.Y-4*u.Padding)
+	if u.PlayerData[1] == nil {
+		u.Buttons = append(u.Buttons, texture.MakeImgWithoutAlt(sitImg, sitPos, arrangeDim, u.Eng, u.Scene))
+	} else {
+		u.Buttons = append(u.Buttons, texture.MakeImgWithoutAlt(sitImg, sitPos, nilDim, u.Eng, u.Scene))
+		avatarKey := u.PlayerData[1]["avatar"].(string)
+		avatar := u.Texs[avatarKey]
+		u.BackgroundImgs = append(u.BackgroundImgs, texture.MakeImgWithoutAlt(avatar, sitPos, arrangeDim, u.Eng, u.Scene))
+	}
+	// player 2 seat
+	sitPos = coords.MakeVec((u.WindowSize.X-arrangeDim.X-2*u.Padding)/2, u.WindowSize.Y-3*arrangeDim.Y-6*u.Padding)
+	if u.PlayerData[2] == nil {
+		u.Buttons = append(u.Buttons, texture.MakeImgWithoutAlt(sitImg, sitPos, arrangeDim, u.Eng, u.Scene))
+	} else {
+		u.Buttons = append(u.Buttons, texture.MakeImgWithoutAlt(sitImg, sitPos, nilDim, u.Eng, u.Scene))
+		avatarKey := u.PlayerData[2]["avatar"].(string)
+		avatar := u.Texs[avatarKey]
+		u.BackgroundImgs = append(u.BackgroundImgs, texture.MakeImgWithoutAlt(avatar, sitPos, arrangeDim, u.Eng, u.Scene))
+	}
+	// player 3 seat
+	sitPos = coords.MakeVec((u.WindowSize.X-arrangeDim.X)/2+arrangeDim.X+2*u.Padding, u.WindowSize.Y-2*arrangeDim.Y-4*u.Padding)
+	if u.PlayerData[3] == nil {
+		u.Buttons = append(u.Buttons, texture.MakeImgWithoutAlt(sitImg, sitPos, arrangeDim, u.Eng, u.Scene))
+	} else {
+		u.Buttons = append(u.Buttons, texture.MakeImgWithoutAlt(sitImg, sitPos, nilDim, u.Eng, u.Scene))
+		avatarKey := u.PlayerData[3]["avatar"].(string)
+		avatar := u.Texs[avatarKey]
+		u.BackgroundImgs = append(u.BackgroundImgs, texture.MakeImgWithoutAlt(avatar, sitPos, arrangeDim, u.Eng, u.Scene))
+	}
+	// table
+	watchPos := coords.MakeVec((u.WindowSize.X-arrangeDim.X)/2, u.WindowSize.Y-2*arrangeDim.Y-4*u.Padding)
+	u.Buttons = append(u.Buttons, texture.MakeImgWithoutAlt(watchImg, watchPos, arrangeDim, u.Eng, u.Scene))
 }
 
 // Waiting view: Displays the word "Waiting". To be displayed when players are waiting for a new round to be dealt
@@ -388,6 +435,7 @@
 	}
 }
 
+// TODO(emshack): When go mobile implements sprite.engine.Unregister, use this instead
 func ChangePlayMessage(message string, u *uistate.UIState) {
 	// remove text and replace with message
 	var emptyTex sprite.SubTex
diff --git a/go/src/hearts/logic/player/player.go b/go/src/hearts/logic/player/player.go
index 4f61379..ce96d41 100644
--- a/go/src/hearts/logic/player/player.go
+++ b/go/src/hearts/logic/player/player.go
@@ -119,6 +119,18 @@
 	p.hand = cards
 }
 
+func (p *Player) SetName(name string) {
+	p.playerName = name
+}
+
+func (p *Player) SetIconImage(image sprite.SubTex) {
+	p.playerIconImage = image
+}
+
+func (p *Player) SetDeviceImage(image sprite.SubTex) {
+	p.playerDeviceImage = image
+}
+
 // Sets passedTo of p to cards
 func (p *Player) SetPassedTo(cards []*card.Card) {
 	p.passedTo = cards
diff --git a/go/src/hearts/main.go b/go/src/hearts/main.go
index e51485c..b82a34e 100644
--- a/go/src/hearts/main.go
+++ b/go/src/hearts/main.go
@@ -109,7 +109,8 @@
 	u.CurTable = table.InitializeGame(u.NumPlayers, u.Texs)
 	server.CreateTables(u)
 	// Create watch stream to update game state based on Syncbase updates
-	go watch.Update(u)
+	go watch.UpdateGame(u)
+	go watch.UpdateSettings(u)
 }
 
 func onStop(u *uistate.UIState) {
diff --git a/go/src/hearts/syncbase/client/main.go b/go/src/hearts/syncbase/client/main.go
index 8fccb35..46ddf10 100644
--- a/go/src/hearts/syncbase/client/main.go
+++ b/go/src/hearts/syncbase/client/main.go
@@ -7,11 +7,13 @@
 package client
 
 import (
-	"encoding/json"
 	"fmt"
+	"strconv"
+	"strings"
+
 	"hearts/img/uistate"
 	"hearts/syncbase/util"
-	"strings"
+
 	"v.io/v23/context"
 	"v.io/v23/discovery"
 	wire "v.io/v23/services/syncbase/nosql"
@@ -59,7 +61,7 @@
 		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 == util.CroupierInterface {
-			return found.Service.Addrs
+			return []string{found.Service.Attrs["settings_sgname"], found.Service.Addrs[0]}
 		}
 	case discovery.UpdateLost:
 		lost := u.Value
@@ -73,15 +75,14 @@
 	return nil
 }
 
-// Returns a watchstream of the gamelog data
-func WatchData(u *uistate.UIState) (nosql.WatchStream, error) {
+// Returns a watchstream of the data in the table
+func WatchData(tableName, prefix string, u *uistate.UIState) (nosql.WatchStream, error) {
 	db := u.Service.App(util.AppName).NoSQLDatabase(util.DbName, nil)
-	prefix := ""
 	resumeMarker, err := db.GetResumeMarker(u.Ctx)
 	if err != nil {
 		fmt.Println("RESUMEMARKER ERR: ", err)
 	}
-	return db.Watch(u.Ctx, util.LogName, prefix, resumeMarker)
+	return db.Watch(u.Ctx, tableName, prefix, resumeMarker)
 }
 
 // Joins a set of gamelog and game settings syncgroups
@@ -103,15 +104,8 @@
 		fmt.Println("Syncgroup joined")
 		// Set UIState GameID
 		tmp := strings.Split(logName, "-")
-		lasttmp := tmp[len(tmp)-1]
-		tmpMap := make(map[string]interface{})
-		err = json.Unmarshal([]byte(lasttmp), &tmpMap)
-		if err != nil {
-			fmt.Println("ERROR UNMARSHALLING")
-		}
-		u.GameID = int(tmpMap["gameID"].(float64))
-		u.CurPlayerIndex = NumInSG(logName, u) - 1
-		fmt.Println(u.CurPlayerIndex)
+		gameID, _ := strconv.Atoi(tmp[len(tmp)-1])
+		u.GameID = gameID
 		ch <- true
 	}
 }
diff --git a/go/src/hearts/syncbase/gamelog/logWriter.go b/go/src/hearts/syncbase/gamelog/logWriter.go
index d17d163..2cbeeb7 100644
--- a/go/src/hearts/syncbase/gamelog/logWriter.go
+++ b/go/src/hearts/syncbase/gamelog/logWriter.go
@@ -14,6 +14,7 @@
 	"hearts/img/uistate"
 	"hearts/logic/card"
 	"hearts/syncbase/server"
+	"hearts/syncbase/util"
 
 	"v.io/v23/context"
 	"v.io/v23/syncbase"
@@ -87,7 +88,13 @@
 	return logKeyValue(u.Service, u.Ctx, key, value)
 }
 
-// Note: The + is syntax used to replicate the way Croupier in Dart/Flutter writes keys.
+func LogPlayerNum(u *uistate.UIState) bool {
+	key := strconv.Itoa(u.GameID) + "/players/" + strconv.Itoa(util.UserID) + "/player_number"
+	value := strconv.Itoa(u.CurPlayerIndex)
+	return logKeyValue(u.Service, u.Ctx, key, value)
+}
+
+// Note: The syntax replicates the way Croupier in Dart/Flutter writes keys.
 func getKey(playerId int, u *uistate.UIState) string {
 	t := int(time.Now().UnixNano() / 1000000)
 	key := strconv.Itoa(u.GameID) + "/log/" + strconv.Itoa(t) + Dash + strconv.Itoa(playerId)
diff --git a/go/src/hearts/syncbase/server/main.go b/go/src/hearts/syncbase/server/main.go
index 1a711c2..4c3a0f0 100644
--- a/go/src/hearts/syncbase/server/main.go
+++ b/go/src/hearts/syncbase/server/main.go
@@ -9,9 +9,11 @@
 import (
 	"encoding/json"
 	"fmt"
+	"math/rand"
+
 	"hearts/img/uistate"
 	"hearts/syncbase/util"
-	"math/rand"
+
 	"v.io/v23/context"
 	"v.io/v23/discovery"
 	"v.io/v23/security"
@@ -25,7 +27,7 @@
 )
 
 // Advertises a set of game log and game settings syncgroups
-func Advertise(logAddress, settingsAddress string, quit chan bool, ctx *context.T) {
+func Advertise(logAddress, settingsAddress, gameStartData string, quit chan bool, ctx *context.T) {
 	ctx, stop := context.WithCancel(ctx)
 	mdns, err := mdns.New("")
 	if err != nil {
@@ -35,9 +37,9 @@
 	gameService := discovery.Service{
 		InstanceName:  "A sample game service",
 		InterfaceName: util.CroupierInterface,
-		Addrs:         []string{settingsAddress, logAddress},
+		Attrs:         map[string]string{"settings_sgname": settingsAddress, "game_start_data": gameStartData},
+		Addrs:         []string{logAddress},
 	}
-	fmt.Println(gameService)
 	if _, err := discoveryService.Advertise(ctx, &gameService, nil); err != nil {
 		ctx.Fatalf("Advertise failed: %v", err)
 	}
@@ -49,7 +51,7 @@
 	}
 }
 
-// Puts key and value into the syncbase table
+// Puts key and value into the syncbase gamelog table
 func AddKeyValue(service syncbase.Service, ctx *context.T, key, value string) bool {
 	app := service.App(util.AppName)
 	db := app.NoSQLDatabase(util.DbName, nil)
@@ -104,6 +106,7 @@
 	settingsMap["avatar"] = util.UserAvatar
 	settingsMap["name"] = util.UserName
 	settingsMap["color"] = util.UserColor
+	u.UserData[util.UserID] = settingsMap
 	value, err := json.Marshal(settingsMap)
 	if err != nil {
 		fmt.Println("WE HAVE A HUGE PROBLEM:", err)
@@ -115,7 +118,6 @@
 func CreateSyncgroups(ch chan string, u *uistate.UIState) {
 	fmt.Println("Creating Syncgroup")
 	u.IsOwner = true
-	u.CurPlayerIndex = 0
 	// Generate random gameID information to advertise this game
 	gameID := rand.Intn(1000000)
 	gameMap := make(map[string]interface{})
@@ -127,8 +129,9 @@
 	if err != nil {
 		fmt.Println("WE HAVE A HUGE PROBLEM:", err)
 	}
+	ch <- string(value)
 	// Create gamelog syncgroup
-	logSGName := util.MountPoint + "/croupier/" + util.SBName + "/%%sync/gaming-" + string(value)
+	logSGName := fmt.Sprintf("%s/croupier/%s/%%%%sync/gaming-%d", util.MountPoint, util.SBName, gameID)
 	allAccess := access.AccessList{In: []security.BlessingPattern{"..."}}
 	permissions := access.Permissions{
 		"Admin":   allAccess,
diff --git a/go/src/hearts/syncbase/watch/watch.go b/go/src/hearts/syncbase/watch/watch.go
index 9d48658..f749591 100644
--- a/go/src/hearts/syncbase/watch/watch.go
+++ b/go/src/hearts/syncbase/watch/watch.go
@@ -10,6 +10,7 @@
 package watch
 
 import (
+	"encoding/json"
 	"fmt"
 	"hearts/img/direction"
 	"hearts/img/reposition"
@@ -18,14 +19,15 @@
 	"hearts/logic/card"
 	"hearts/syncbase/client"
 	"hearts/syncbase/gamelog"
+	"hearts/syncbase/util"
 	"strconv"
 	"strings"
 	"time"
 	"v.io/v23/syncbase/nosql"
 )
 
-func Update(u *uistate.UIState) {
-	stream, err := client.WatchData(u)
+func UpdateSettings(u *uistate.UIState) {
+	stream, err := client.WatchData(util.SettingsName, "users", u)
 	if err != nil {
 		fmt.Println("WatchData error:", err)
 	}
@@ -37,26 +39,76 @@
 				if err := c.Value(&value); err != nil {
 					fmt.Println("Value error:", err)
 				}
+				var valueMap map[string]interface{}
+				err := json.Unmarshal(value, &valueMap)
+				if err != nil {
+					fmt.Println("Unmarshal error:", err)
+				}
+				key := c.Row
+				fmt.Println(key, string(value))
+				userID, _ := strconv.Atoi(strings.Split(key, "/")[1])
+				u.UserData[userID] = valueMap
+			} else {
+				fmt.Println("Unexpected ChangeType: ", c.ChangeType)
+			}
+		}
+	}
+}
+
+func UpdateGame(u *uistate.UIState) {
+	stream, err := client.WatchData(util.LogName, "", 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)
+				}
 				valueStr := string(value)
-				fmt.Println(valueStr)
-				updateType := strings.Split(valueStr, "|")[0]
-				switch updateType {
-				case gamelog.Deal:
-					go onDeal(valueStr, u)
-				case gamelog.Pass:
-					go onPass(valueStr, u)
-				case gamelog.Take:
-					go onTake(valueStr, u)
-				case gamelog.Play:
-					go onPlay(valueStr, u)
-				case gamelog.Ready:
-					go onReady(valueStr, u)
+				keyType := strings.Split(key, "/")[1]
+				switch keyType {
+				case "log":
+					updateType := strings.Split(valueStr, "|")[0]
+					switch updateType {
+					case gamelog.Deal:
+						onDeal(valueStr, u)
+					case gamelog.Pass:
+						onPass(valueStr, u)
+					case gamelog.Take:
+						onTake(valueStr, u)
+					case gamelog.Play:
+						onPlay(valueStr, u)
+					case gamelog.Ready:
+						onReady(valueStr, u)
+					}
+				case "players":
+					onPlayers(key, valueStr, u)
 				}
 			} else {
 				fmt.Println("Unexpected ChangeType: ", c.ChangeType)
 			}
 		}
-		fmt.Println(stream.Err())
+	}
+}
+
+func onPlayers(key, value string, u *uistate.UIState) {
+	userID, _ := strconv.Atoi(strings.Split(key, "/")[2])
+	playerNum, _ := strconv.Atoi(value)
+	u.PlayerData[playerNum] = u.UserData[userID]
+	user := u.UserData[userID]
+	if user != nil {
+		img := u.Texs[user["avatar"].(string)]
+		name := user["name"].(string)
+		u.CurTable.GetPlayers()[playerNum].SetIconImage(img)
+		u.CurTable.GetPlayers()[playerNum].SetName(name)
+	}
+	if u.CurView == uistate.Arrange {
+		view.LoadArrangeView(u)
 	}
 }
 
diff --git a/go/src/hearts/touchhandler/touchhandler.go b/go/src/hearts/touchhandler/touchhandler.go
index 78b389b..69c104a 100644
--- a/go/src/hearts/touchhandler/touchhandler.go
+++ b/go/src/hearts/touchhandler/touchhandler.go
@@ -8,8 +8,10 @@
 
 import (
 	"fmt"
+
 	"golang.org/x/mobile/event/touch"
 	"golang.org/x/mobile/exp/sprite"
+
 	"hearts/img/coords"
 	"hearts/img/reposition"
 	"hearts/img/staticimg"
@@ -106,13 +108,14 @@
 		if buttonList[0] == u.Buttons[0] {
 			ch := make(chan string)
 			go server.CreateSyncgroups(ch, u)
+			gameStartData := <-ch
 			logName := <-ch
 			settingsName := <-ch
 			if logName != "" && settingsName != "" {
 				u.ScanChan <- true
 				u.ScanChan = nil
 				u.SGChan = make(chan bool)
-				go server.Advertise(logName, settingsName, u.SGChan, u.Ctx)
+				go server.Advertise(logName, settingsName, gameStartData, u.SGChan, u.Ctx)
 				view.LoadArrangeView(u)
 			}
 		} else {
@@ -137,8 +140,16 @@
 
 func beginClickArrange(t touch.Event, u *uistate.UIState) {
 	buttonList := findClickedButton(t, u)
-	if len(buttonList) > 0 {
-		gamelog.LogReady(u)
+	if len(buttonList) > 0 && u.PlayerData[u.CurPlayerIndex] == nil {
+		for i, b := range u.Buttons {
+			if buttonList[0] == b {
+				u.CurPlayerIndex = i
+			}
+		}
+		if u.CurPlayerIndex < 4 {
+			gamelog.LogReady(u)
+			gamelog.LogPlayerNum(u)
+		}
 		view.LoadWaitingView(u)
 	}
 }