croupier: Use settings_sg to share settings between players

Players of the same game shouldn't need to advertise to each other,
but they also shouldn't assume the settings syncgroup has a fixed
name. This CL makes sure that players write that name into the
games table and read from it too.

Change-Id: I754be737859762b9adfc537b6bf65b5bdb4a8154
diff --git a/lib/src/syncbase/settings_manager.dart b/lib/src/syncbase/settings_manager.dart
index 682a2b7..e86b250 100644
--- a/lib/src/syncbase/settings_manager.dart
+++ b/lib/src/syncbase/settings_manager.dart
@@ -136,10 +136,14 @@
     int id = await _getUserID();
 
     _cc.createSyncgroup(
-        _cc.makeSyncgroupName(await _syncSuffix()), util.tableNameSettings,
+        await _mySettingsSyncgroupName(), util.tableNameSettings,
         prefix: this._settingsDataKey(id));
   }
 
+  Future<String> _mySettingsSyncgroupName() async {
+    return _cc.makeSyncgroupName(await _syncSettingsSuffix());
+  }
+
   // This watch method ensures that any changes are propagated to the caller.
   // In this case, we're forwarding any player changes to the Croupier logic.
   Future _startWatchPlayers(Stream<sc.WatchChange> watchStream) async {
@@ -164,11 +168,20 @@
 
       if (this.updatePlayerFoundCallback != null) {
         String playerID = _getPartFromBack(key, "/", 1);
-        this.updatePlayerFoundCallback(playerID, value);
-
-        // Also, you should be sure to join this person's syncgroup.
-        _cc.joinSyncgroup(
-            _cc.makeSyncgroupName(await _syncSuffix(int.parse(playerID))));
+        String type = _getPartFromBack(key, "/", 0);
+        switch (type) {
+          case "player_number":
+            // Update the player number for this player.
+            this.updatePlayerFoundCallback(playerID, value);
+            break;
+          case "settings_sg":
+            // Join this player's settings syncgroup.
+            _cc.joinSyncgroup(_cc.makeSyncgroupName(value));
+            break;
+          default:
+            print("Unexpected key: ${key} with value ${value}");
+            assert(false);
+        }
       }
     }
   }
@@ -193,6 +206,9 @@
     await gameTable
         .row("${gameID}/players/${id}/player_number")
         .put(UTF8.encode("0"));
+    await gameTable
+        .row("${gameID}/players/{$id}/settings_sg")
+        .put(UTF8.encode(await _mySettingsSyncgroupName()));
 
     logic_game.GameStartData gsd =
         new logic_game.GameStartData(type, 0, gameID, id);
@@ -260,14 +276,14 @@
 
   // Someone who wants to join a game should advertise their presence.
   Future advertiseSettings(logic_game.GameStartData gsd) async {
-    String suffix = await _syncSuffix();
+    String settingsSuffix = await _syncSettingsSuffix();
     String gameSuffix = util.syncgameSuffix("${gsd.gameID}");
     return _cc.discoveryClient.advertise(
         _discoverySettingsKey,
         DiscoveryClient.serviceMaker(
             interfaceName: util.discoveryInterfaceName,
             attrs: <String, String>{
-              util.syncgameSettingsAttr: _cc.makeSyncgroupName(suffix),
+              util.syncgameSettingsAttr: _cc.makeSyncgroupName(settingsSuffix),
               util.syncgameGameStartDataAttr: gsd.toJSONString()
             },
             addrs: <String>[_cc.makeSyncgroupName(gameSuffix)]));
@@ -285,7 +301,7 @@
     return int.parse(result);
   }
 
-  Future<String> _syncSuffix([int userID]) async {
+  Future<String> _syncSettingsSuffix([int userID]) async {
     int id = userID;
     if (id == null) {
       id = await _getUserID();
diff --git a/schema.md b/schema.md
index c1e4bda..d5aafcd 100644
--- a/schema.md
+++ b/schema.md
@@ -14,6 +14,7 @@
 <game_id>/type = <game_type>
 <game_id>/owner = <user_id>
 <game_id>/players/<user_id>/player_number = <player_number>
+<game_id>/players/<user_id>/settings_sg = <settings_syncgroup_name>
 
 For the game log writer:
 <game_id>/log/<timestamp>-<player_id> = <command_string>