croupier: Use distinct settings

And also amend the syncgroup that is joined.

Change-Id: Iafb3844530a8499d0c3775f8e7e6a973dfae8ec0
diff --git a/Makefile b/Makefile
index 33b040d..6190ee5 100644
--- a/Makefile
+++ b/Makefile
@@ -26,7 +26,8 @@
 	# Location of mounttable on syncslides-alpha network.
 	MOUNTTABLE := /192.168.86.254:8101
 	# Name to mount under.
-	NAME := croupierAlex
+	SYNCBASE_NAME_FLAG := --name=$(MOUNTTABLE)/croupier-$(DEVICE_ID)
+	SYNCBASE_FLAGS += $(SYNCBASE_NAME_FLAG)
 
 	APP_HOME_DIR = /data/data/org.chromium.mojo.shell/app_home
 	ANDROID_CREDS_DIR := /sdcard/v23creds
@@ -36,16 +37,6 @@
 		--v23.credentials=$(ANDROID_CREDS_DIR) \
 		--v23.proxy=proxy
 
-	#--v23.namespace.root=$(MOUNTTABLE) \
-
-ifeq ($(ANDROID), 1)
-	# If ANDROID is set to 1 exactly, then treat it like the first device.
-	# TODO(alexfandrianto): If we can do a better job of this, we won't have to
-	# special-case the first device.
-	SYNCBASE_NAME_FLAG := --name=$(MOUNTTABLE)/$(NAME)
-	SYNCBASE_FLAGS += $(SYNCBASE_NAME_FLAG)
-endif
-
 # If this is not the first mojo shell, then you must reuse the dev servers
 # to avoid a "port in use" error.
 ifneq ($(shell fuser 31841/tcp),)
@@ -119,10 +110,18 @@
 croupier.flx: packages $(DART_LIB_FILES_ALL)
 	pub run flutter_tools -v build --manifest manifest.yaml --output-file $@
 
+SETTINGS_FILE := /sdcard/croupier_settings.json
+SETTINGS_JSON := {\"deviceID\": \"$(DEVICE_ID)\", \"mounttable\": \"$(MOUNTTABLE)\"}
+.PHONY: push-settings
+push-settings:
+ifdef ANDROID
+	adb -s $(DEVICE_ID) shell 'echo $(SETTINGS_JSON) > $(SETTINGS_FILE)'
+endif
+
 # Starts the app on the specified ANDROID device.
 # Don't forget to make creds first if they are not present.
 .PHONY: start
-start: croupier.flx env-check packages
+start: croupier.flx env-check packages push-settings
 ifdef ANDROID
 	# Make creds dir if it does not exist.
 	mkdir -p creds
@@ -164,7 +163,7 @@
 	shortcut_template > shortcut_commands
 endef
 
-shortcut: env-check
+shortcut: env-check push-settings
 ifdef ANDROID
 	# Create the shortcut file.
 	$(call GENERATE_SHORTCUT_FILE,$(DEVICE_ID),$(SYNCBASE_NAME_FLAG))
@@ -217,8 +216,8 @@
 .PHONY: clean
 clean:
 ifdef ANDROID
-	# Clean syncbase data dir.
 	adb -s $(DEVICE_ID) shell run-as org.chromium.mojo.shell rm -rf $(APP_HOME_DIR)/syncbase_data
+	adb -s $(DEVICE_ID) shell rm $(SETTINGS_FILE)
 endif
 	rm -f croupier.flx snapshot_blob.bin
 	rm -rf bin tmp
diff --git a/images/avatars/player0.jpeg b/images/avatars/player0.jpeg
index 53b2470..4371774 100644
--- a/images/avatars/player0.jpeg
+++ b/images/avatars/player0.jpeg
Binary files differ
diff --git a/images/avatars/player1.jpeg b/images/avatars/player1.jpeg
index f4df56a..34d57b0 100644
--- a/images/avatars/player1.jpeg
+++ b/images/avatars/player1.jpeg
Binary files differ
diff --git a/images/avatars/player2.jpeg b/images/avatars/player2.jpeg
index 02b35af..f961d3a 100644
--- a/images/avatars/player2.jpeg
+++ b/images/avatars/player2.jpeg
Binary files differ
diff --git a/images/avatars/player3.jpeg b/images/avatars/player3.jpeg
index 7409c03..4d0ce50 100644
--- a/images/avatars/player3.jpeg
+++ b/images/avatars/player3.jpeg
Binary files differ
diff --git a/lib/components/board.dart b/lib/components/board.dart
index 27e4520..861efa4 100644
--- a/lib/components/board.dart
+++ b/lib/components/board.dart
@@ -69,7 +69,7 @@
   bool trickTaking = false;
   List<List<logic_card.Card>> playedCards = new List<List<logic_card.Card>>(4);
 
-  static const int SHOW_TRICK_DURATION = 750; // ms
+  static const int SHOW_TRICK_DURATION = 2000; // ms
 
   @override
   void initState() {
diff --git a/lib/components/card.dart b/lib/components/card.dart
index 52a85de..7d7dce2 100644
--- a/lib/components/card.dart
+++ b/lib/components/card.dart
@@ -194,8 +194,9 @@
       case CardAnimationType.LONG:
         return const Duration(milliseconds: 750);
       default:
-        print(config.animationType);
+        print("Unexpected animation type: ${config.animationType}");
         assert(false);
+        return null;
     }
   }
 
diff --git a/lib/components/hearts/hearts.part.dart b/lib/components/hearts/hearts.part.dart
index f9c3808..cc4e406 100644
--- a/lib/components/hearts/hearts.part.dart
+++ b/lib/components/hearts/hearts.part.dart
@@ -287,8 +287,7 @@
         kids.add(showBoard());
         break;
       case HeartsPhase.Score:
-        kids.add(new Text("SCORE PHASE"));
-        break;
+        return showScore();
       default:
         assert(false);
         return null;
diff --git a/lib/logic/croupier.dart b/lib/logic/croupier.dart
index 8d85e76..4f9789e 100644
--- a/lib/logic/croupier.dart
+++ b/lib/logic/croupier.dart
@@ -4,6 +4,7 @@
 
 import 'dart:async';
 
+import '../settings/client.dart' show AppSettings;
 import '../src/syncbase/settings_manager.dart' show SettingsManager;
 import 'create_game.dart' as cg;
 import 'croupier_settings.dart' show CroupierSettings;
@@ -15,6 +16,7 @@
 typedef void NoArgCb();
 
 class Croupier {
+  AppSettings appSettings;
   CroupierState state;
   SettingsManager settings_manager;
   CroupierSettings settings; // null, but loaded asynchronously.
@@ -31,12 +33,12 @@
 
   bool debugMode = false; // whether to show debug buttons or not
 
-  Croupier() {
+  Croupier(this.appSettings) {
     state = CroupierState.Welcome;
     settings_everyone = new Map<int, CroupierSettings>();
     games_found = new Map<String, GameStartData>();
     players_found = new Map<int, int>();
-    settings_manager = new SettingsManager(
+    settings_manager = new SettingsManager(appSettings,
         _updateSettingsEveryoneCb, _updateGamesFoundCb, _updatePlayerFoundCb);
 
     settings_manager.load().then((String csString) {
diff --git a/lib/logic/hearts/hearts_game.part.dart b/lib/logic/hearts/hearts_game.part.dart
index 1a7ccef..63e5e1a 100644
--- a/lib/logic/hearts/hearts_game.part.dart
+++ b/lib/logic/hearts/hearts_game.part.dart
@@ -254,7 +254,9 @@
   // It won't be possible to set the readiness for other players, except via the GameLog.
   void setReadyUI() {
     assert(phase == HeartsPhase.Score || phase == HeartsPhase.StartGame);
-    gamelog.add(new HeartsCommand.ready(playerNumber));
+    if (playerNumber >= 0 && playerNumber < 4) {
+      gamelog.add(new HeartsCommand.ready(playerNumber));
+    }
   }
 
   @override
diff --git a/lib/main.dart b/lib/main.dart
index 265fdbd..367c822 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -5,6 +5,7 @@
 import 'package:flutter/material.dart';
 import 'dart:async';
 
+import 'settings/client.dart' as settings_client;
 import 'logic/croupier.dart' show Croupier;
 import 'components/settings_route.dart' show SettingsRoute;
 import 'components/debug_route.dart' show DebugRoute;
@@ -12,7 +13,8 @@
 import 'styles/common.dart' as style;
 
 class CroupierApp extends StatefulComponent {
-  CroupierApp();
+  settings_client.AppSettings appSettings;
+  CroupierApp(this.appSettings);
 
   CroupierAppState createState() => new CroupierAppState();
 }
@@ -22,7 +24,7 @@
 
   void initState() {
     super.initState();
-    this.croupier = new Croupier();
+    this.croupier = new Croupier(config.appSettings);
   }
 
   Widget build(BuildContext context) {
@@ -41,7 +43,7 @@
   // TODO(alexfandrianto): Perhaps my app will run better if I initialize more
   // things here instead of in Croupier. I added this 500 ms delay because the
   // tablet was sometimes not rendering without it (repainting too early?).
-  new Future.delayed(const Duration(milliseconds: 500), () {
-    runApp(new CroupierApp());
+  new Future.delayed(const Duration(milliseconds: 500), () async {
+    runApp(new CroupierApp(await settings_client.getSettings()));
   });
 }
diff --git a/lib/settings/client.dart b/lib/settings/client.dart
new file mode 100644
index 0000000..e78524f
--- /dev/null
+++ b/lib/settings/client.dart
@@ -0,0 +1,25 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:io';
+import 'dart:convert';
+
+class AppSettings {
+  String deviceID;
+  String mounttable;
+
+  AppSettings.fromJson(String json) {
+    Map map = JSON.decode(json);
+    deviceID = map['deviceID'];
+    mounttable = map['mounttable'];
+  }
+}
+
+const String settingsFilePath = '/sdcard/croupier_settings.json';
+
+Future<AppSettings> getSettings() async {
+  String settingsJson = await new File(settingsFilePath).readAsString();
+  return new AppSettings.fromJson(settingsJson);
+}
diff --git a/lib/src/mocks/settings_manager.dart b/lib/src/mocks/settings_manager.dart
index 71d497a..9231d1a 100644
--- a/lib/src/mocks/settings_manager.dart
+++ b/lib/src/mocks/settings_manager.dart
@@ -4,6 +4,7 @@
 
 import 'dart:async';
 
+import '../../settings/client.dart' as settings_client;
 import 'util.dart' as util;
 import '../../logic/game/game.dart' as logic_game;
 import '../../logic/croupier_settings.dart' show CroupierSettings;
@@ -13,8 +14,8 @@
   final util.keyValueCallback updateGamesCallback;
   final util.keyValueCallback updatePlayerFoundCallback;
 
-  SettingsManager(this.updateCallback, this.updateGamesCallback,
-      this.updatePlayerFoundCallback);
+  SettingsManager(settings_client.AppSettings _, this.updateCallback,
+      this.updateGamesCallback, this.updatePlayerFoundCallback);
 
   Map<String, String> _data = new Map<String, String>();
 
diff --git a/lib/src/syncbase/croupier_client.dart b/lib/src/syncbase/croupier_client.dart
index e4fe3ca..b3714cf 100644
--- a/lib/src/syncbase/croupier_client.dart
+++ b/lib/src/syncbase/croupier_client.dart
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+import '../../settings/client.dart' as settings_client;
 import 'discovery_client.dart' show DiscoveryClient;
 import 'util.dart' as util;
 
@@ -16,6 +17,7 @@
 class CroupierClient {
   final sc.SyncbaseClient _syncbaseClient;
   final DiscoveryClient _discoveryClient;
+  final settings_client.AppSettings appSettings;
   static final String syncbaseServerUrl = Platform.environment[
           'SYNCBASE_SERVER_URL'] ??
       'https://mojo.v.io/syncbase_server.mojo';
@@ -24,12 +26,20 @@
 
   // We want CroupierClient to be a singleton for simplicity purposes.
   // This prevents duplicate table/database creation.
-  static final CroupierClient _singleton = new CroupierClient._internal();
-  factory CroupierClient() {
+  // TODO(alexfandrianto): This is not a singleton if I give arguments.
+  // That means later runs will ignore the settings.
+  // https://github.com/vanadium/issues/issues/964
+  static CroupierClient _singleton;
+  factory CroupierClient(settings_client.AppSettings appSettings) {
+    _singleton ??= new CroupierClient._internal(appSettings);
+    return _singleton;
+  }
+  factory CroupierClient.singleton() {
+    assert(_singleton != null);
     return _singleton;
   }
 
-  CroupierClient._internal()
+  CroupierClient._internal(this.appSettings)
       : _syncbaseClient =
             new sc.SyncbaseClient(shell.connectToService, syncbaseServerUrl),
         _discoveryClient = new DiscoveryClient() {
@@ -102,7 +112,7 @@
     }
 
     var myInfo = sc.SyncbaseClient.syncgroupMemberInfo(syncPriority: 3);
-    String mtName = util.mtAddr;
+    String mtName = this.appSettings.mounttable;
 
     sc.SyncbaseSyncgroup sg = await _getSyncgroup(sgName);
     var sgSpec = sc.SyncbaseClient.syncgroupSpec(
@@ -137,8 +147,8 @@
 
   // Helper that converts a suffix to a syncgroup name.
   String makeSyncgroupName(String suffix) {
-    String mtName = util.mtAddr;
-    String sgPrefix = naming.join(mtName, util.sgPrefix);
+    String sgPrefix = util.makeSgPrefix(
+        this.appSettings.mounttable, this.appSettings.deviceID);
     String sgName = naming.join(sgPrefix, suffix);
     return sgName;
   }
diff --git a/lib/src/syncbase/log_writer.dart b/lib/src/syncbase/log_writer.dart
index d896cc3..3e58094 100644
--- a/lib/src/syncbase/log_writer.dart
+++ b/lib/src/syncbase/log_writer.dart
@@ -67,7 +67,8 @@
 
   // The LogWriter takes a callback for watch updates, the list of users, and
   // the logPrefix to write at on table.
-  LogWriter(this.updateCallback, this.users) : _cc = new CroupierClient() {
+  LogWriter(this.updateCallback, this.users)
+      : _cc = new CroupierClient.singleton() {
     _prepareLog();
   }
 
diff --git a/lib/src/syncbase/settings_manager.dart b/lib/src/syncbase/settings_manager.dart
index e86b250..297a363 100644
--- a/lib/src/syncbase/settings_manager.dart
+++ b/lib/src/syncbase/settings_manager.dart
@@ -14,6 +14,7 @@
 /// In the background, these values will be synced.
 /// When setting up a syncgroup, the userIDs are very important.
 
+import '../../settings/client.dart' as settings_client;
 import '../../logic/game/game.dart' as logic_game;
 import '../../logic/croupier_settings.dart' show CroupierSettings;
 import 'croupier_client.dart' show CroupierClient;
@@ -37,9 +38,12 @@
   static const String _personalKey = "personal";
   static const String _settingsWatchSyncPrefix = "users";
 
-  SettingsManager(this.updateSettingsCallback, this.updateGamesCallback,
+  SettingsManager(
+      settings_client.AppSettings appSettings,
+      this.updateSettingsCallback,
+      this.updateGamesCallback,
       this.updatePlayerFoundCallback)
-      : _cc = new CroupierClient();
+      : _cc = new CroupierClient(appSettings);
 
   String _settingsDataKey(int userID) {
     return "${_settingsWatchSyncPrefix}/${userID}/settings";
@@ -176,7 +180,7 @@
             break;
           case "settings_sg":
             // Join this player's settings syncgroup.
-            _cc.joinSyncgroup(_cc.makeSyncgroupName(value));
+            _cc.joinSyncgroup(value);
             break;
           default:
             print("Unexpected key: ${key} with value ${value}");
@@ -239,9 +243,12 @@
 
     int id = await _getUserID();
     int playerNumber = fellowPlayers.length - 1;
-    gameTable
+    await gameTable
         .row("${gameID}/players/${id}/player_number")
         .put(UTF8.encode("${playerNumber}"));
+    await gameTable
+        .row("${gameID}/players/{$id}/settings_sg")
+        .put(UTF8.encode(await _mySettingsSyncgroupName()));
   }
 
   // When starting the settings manager, there may be settings already in the
diff --git a/lib/src/syncbase/util.dart b/lib/src/syncbase/util.dart
index 4bc8bb2..749e0cd 100644
--- a/lib/src/syncbase/util.dart
+++ b/lib/src/syncbase/util.dart
@@ -12,12 +12,14 @@
 // TODO(alexfandrianto): This may need to be the global mount table with a
 // proxy. Otherwise, it will be difficult for other users to run.
 // https://github.com/vanadium/issues/issues/782
-const String mtAddr = '/192.168.86.254:8101';
-const String sgPrefix = 'croupierAlex/%%sync';
+String makeSgPrefix(String mounttable, String deviceID) {
+  return "${mounttable}/croupier-${deviceID}/%%sync";
+}
+
 const String sgSuffix = 'discovery';
 const String sgSuffixGame = 'gaming';
 
-const String discoveryInterfaceName = 'CroupierSettingsAndGame2';
+const String discoveryInterfaceName = 'CroupierSettingsAndGame';
 
 typedef void NoArgCb();
 typedef void keyValueCallback(String key, String value);
diff --git a/pubspec.lock b/pubspec.lock
index be2270f..5610040 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -154,7 +154,7 @@
   path:
     description: path
     source: hosted
-    version: "1.3.7"
+    version: "1.3.8"
   petitparser:
     description: petitparser
     source: hosted
@@ -214,7 +214,7 @@
   syncbase:
     description: syncbase
     source: hosted
-    version: "0.0.16"
+    version: "0.0.18"
   test:
     description: test
     source: hosted