Merge "Fixing bug with unset card.Initial(). Allowing for restart without restarting syncbase"
diff --git a/lib/components/card.dart b/lib/components/card.dart
index a0bf42c..c222020 100644
--- a/lib/components/card.dart
+++ b/lib/components/card.dart
@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-import 'dart:async';
-
import 'package:flutter/animation.dart';
import 'package:flutter/material.dart' as widgets;
import 'package:flutter/rendering.dart';
diff --git a/lib/logic/croupier.dart b/lib/logic/croupier.dart
index 70747ac..e3de247 100644
--- a/lib/logic/croupier.dart
+++ b/lib/logic/croupier.dart
@@ -6,6 +6,7 @@
import '../settings/client.dart' show AppSettings;
import '../src/syncbase/settings_manager.dart' show SettingsManager;
+import '../src/syncbase/util.dart' as sync_util;
import 'create_game.dart' as cg;
import 'croupier_settings.dart' show CroupierSettings;
import 'game/game.dart'
@@ -90,7 +91,12 @@
return null;
}
- void _updatePlayerFoundCb(String playerID, String playerNum) {
+ void _updatePlayerFoundCb(String playerKey, String playerNum) {
+ String gameIDStr = sync_util.gameIDFromGameKey(playerKey);
+ if (game == null || game.gameID != int.parse(gameIDStr)) {
+ return; // ignore
+ }
+ String playerID = sync_util.playerIDFromPlayerKey(playerKey);
int id = int.parse(playerID);
if (playerNum == null) {
if (!players_found.containsKey(id)) {
@@ -112,11 +118,15 @@
}
void _updateGameStatusCb(String statusKey, String newStatus) {
+ String gameIDStr = sync_util.gameIDFromGameKey(statusKey);
+ if (game == null || game.gameID != int.parse(gameIDStr)) {
+ return; // ignore
+ }
switch (newStatus) {
case "RUNNING":
if (state == CroupierState.ArrangePlayers) {
game.startGameSignal();
- state = CroupierState.PlayGame;
+ setState(CroupierState.PlayGame, null);
}
break;
default:
@@ -194,12 +204,12 @@
});
}
+ // The signal to start or quit is not anything special.
// data should be empty.
- // All rearrangements affect the Game's player number without changing app state.
+ assert(data == null);
break;
case CroupierState.PlayGame:
// data should be empty.
- // The signal to start really isn't anything special.
break;
default:
assert(false);
diff --git a/lib/logic/hearts/hearts_game.part.dart b/lib/logic/hearts/hearts_game.part.dart
index b33606e..477de00 100644
--- a/lib/logic/hearts/hearts_game.part.dart
+++ b/lib/logic/hearts/hearts_game.part.dart
@@ -278,6 +278,10 @@
if (!this.isPlayer) {
this.viewType = HeartsType.Board;
}
+ // Only the creator should deal the cards once everyone is ready.
+ if (this.isCreator) {
+ this.dealCards();
+ }
}
// Note that this will be called by the UI.
@@ -327,10 +331,6 @@
this.resetGame();
print('we are all ready. ${isCreator}');
- // Only the creator should deal the cards once everyone is ready.
- if (this.isCreator) {
- this.dealCards();
- }
}
return;
case HeartsPhase.Deal:
diff --git a/lib/src/mocks/util.dart b/lib/src/mocks/util.dart
index e466b8d..0413ea1 100644
--- a/lib/src/mocks/util.dart
+++ b/lib/src/mocks/util.dart
@@ -3,3 +3,11 @@
// license that can be found in the LICENSE file.
typedef void keyValueCallback(String key, String value);
+
+String gameIDFromGameKey(String gameKey) {
+ return null;
+}
+
+String playerIDFromPlayerKey(String playerKey) {
+ return null;
+}
diff --git a/lib/src/syncbase/log_writer.dart b/lib/src/syncbase/log_writer.dart
index 3e58094..bfb278e 100644
--- a/lib/src/syncbase/log_writer.dart
+++ b/lib/src/syncbase/log_writer.dart
@@ -176,17 +176,6 @@
await _writeData(propKey, proposalData);
proposalsKnown[propKey] = proposalData;
- // TODO(alexfandrianto): Remove when we have 4 players going at once.
- // For quick development purposes, we may wish to keep this block.
- // FAKE: Do some bonus work. Where "everyone else" accepts the proposal.
- // Normally, one would rely on watch and the syncgroup peers to do this.
- /*for (int i = 0; i < users.length; i++) {
- if (users[i] != associatedUser) {
- // DO NOT AWAIT HERE. It must be done "asynchronously".
- _writeData(_proposalKey(users[i]), proposalData);
- }
- }*/
-
return;
}
await _writeData(key, value);
@@ -194,26 +183,16 @@
// Helper that writes data to the "store" and calls the update callback.
Future _writeData(String key, String value) async {
- var row = tb.row("${this.logPrefix}/${key}");
+ var row = tb.row(_rowKey(key));
await row.put(UTF8.encode(value));
}
- /*
- // _readData could be helpful eventually, but it's not needed yet.
- Future<String> _readData(String key) async {
- var row = tb.row("${this.logPrefix}/${key}");
- if (!(await row.exists())) {
- print("${key} did not exist");
- return null;
- }
- var getBytes = await row.get();
-
- return UTF8.decode(getBytes);
+ String _rowKey(String key) {
+ return "${this.logPrefix}/${key}";
}
- */
Future _deleteData(String key) async {
- var row = tb.row(key);
+ var row = tb.row(_rowKey(key));
await row.delete();
}
diff --git a/lib/src/syncbase/settings_manager.dart b/lib/src/syncbase/settings_manager.dart
index 790b43c..32f63a0 100644
--- a/lib/src/syncbase/settings_manager.dart
+++ b/lib/src/syncbase/settings_manager.dart
@@ -35,9 +35,7 @@
final CroupierClient _cc;
sc.SyncbaseTable tb;
- static const String _discoverySettingsKey = "settings";
- static const String _personalKey = "personal";
- static const String _settingsWatchSyncPrefix = "users";
+ static const String _discoveryGameAdKey = "discovery-game-ad";
SettingsManager(
settings_client.AppSettings appSettings,
@@ -47,15 +45,6 @@
this.updateGameStatusCallback)
: _cc = new CroupierClient(appSettings);
- String _settingsDataKey(int userID) {
- return "${_settingsWatchSyncPrefix}/${userID}/settings";
- }
-
- String _settingsDataKeyUserID(String dataKey) {
- List<String> parts = dataKey.split("/");
- return parts[parts.length - 2];
- }
-
Future _prepareSettingsTable() async {
if (tb != null) {
return; // Then we're already prepared.
@@ -65,8 +54,8 @@
tb = await _cc.createTable(db, util.tableNameSettings);
// Start to watch the stream for the shared settings table.
- Stream<sc.WatchChange> watchStream = db.watch(
- util.tableNameSettings, _settingsWatchSyncPrefix, UTF8.encode("now"));
+ Stream<sc.WatchChange> watchStream = db.watch(util.tableNameSettings,
+ util.settingsWatchSyncPrefix, UTF8.encode("now"));
_startWatchSettings(watchStream); // Don't wait for this future.
_loadSettings(tb); // Don't wait for this future.
}
@@ -84,7 +73,7 @@
await this.save(settings.userID, jsonStr);
return jsonStr;
} else {
- return await _tryReadData(tb, this._settingsDataKey(userID));
+ return await _tryReadData(tb, util.settingsDataKeyFromUserID(userID));
}
}
@@ -104,8 +93,10 @@
util.log('SettingsManager.save');
await _prepareSettingsTable();
- await tb.row(_personalKey).put(UTF8.encode("${userID}"));
- await tb.row(this._settingsDataKey(userID)).put(UTF8.encode(jsonString));
+ await tb.row(util.settingsPersonalKey).put(UTF8.encode("${userID}"));
+ await tb
+ .row(util.settingsDataKeyFromUserID(userID))
+ .put(UTF8.encode(jsonString));
}
// This watch method ensures that any changes are propagated to the caller.
@@ -132,7 +123,7 @@
}
if (this.updateSettingsCallback != null) {
- this.updateSettingsCallback(_settingsDataKeyUserID(key), value);
+ this.updateSettingsCallback(util.userIDFromSettingsDataKey(key), value);
}
}
}
@@ -143,7 +134,7 @@
_cc.createSyncgroup(
await _mySettingsSyncgroupName(), util.tableNameSettings,
- prefix: this._settingsDataKey(id));
+ prefix: util.settingsDataKeyFromUserID(id));
}
Future<String> _mySettingsSyncgroupName() async {
@@ -175,19 +166,18 @@
if (key.indexOf("/players") != -1) {
if (this.updatePlayerFoundCallback != null) {
- String playerID = _getPartFromBack(key, "/", 1);
- String type = _getPartFromBack(key, "/", 0);
+ String type = util.playerUpdateTypeFromPlayerKey(key);
switch (type) {
case "player_number":
// Update the player number for this player.
- this.updatePlayerFoundCallback(playerID, value);
+ this.updatePlayerFoundCallback(key, value);
break;
case "settings_sg":
// Join this player's settings syncgroup.
_cc.joinSyncgroup(value);
// Also, signal that this player has been found.
- this.updatePlayerFoundCallback(playerID, null);
+ this.updatePlayerFoundCallback(key, null);
break;
default:
print("Unexpected key: ${key} with value ${value}");
@@ -215,12 +205,12 @@
print("Now writing to some rows of ${gameID}");
// Start up the table and write yourself as player 0.
- await gameTable.row("${gameID}/type").put(UTF8.encode("${type}"));
+ await gameTable.row(util.gameTypeKey(gameID)).put(UTF8.encode("${type}"));
int id = await _getUserID();
- await gameTable.row("${gameID}/owner").put(UTF8.encode("${id}"));
+ await gameTable.row(util.gameOwnerKey(gameID)).put(UTF8.encode("${id}"));
await gameTable
- .row("${gameID}/players/${id}/settings_sg")
+ .row(util.playerSettingsKeyFromData(gameID, id))
.put(UTF8.encode(await _mySettingsSyncgroupName()));
logic_game.GameStartData gsd =
@@ -249,7 +239,7 @@
int id = await _getUserID();
await gameTable
- .row("${gameID}/players/${id}/settings_sg")
+ .row(util.playerSettingsKeyFromData(gameID, id))
.put(UTF8.encode(await _mySettingsSyncgroupName()));
}
@@ -259,7 +249,7 @@
int id = await _getUserID();
await gameTable
- .row("${gameID}/players/${id}/player_number")
+ .row(util.playerNumberKeyFromData(gameID, id))
.put(UTF8.encode("${playerNumber}"));
}
@@ -267,19 +257,19 @@
sc.SyncbaseDatabase db = await _cc.createDatabase();
sc.SyncbaseTable gameTable = await _cc.createTable(db, util.tableNameGames);
- await gameTable.row("${gameID}/status").put(UTF8.encode(status));
+ await gameTable.row(util.gameStatusKey(gameID)).put(UTF8.encode(status));
}
// When starting the settings manager, there may be settings already in the
// store. Make sure to load those.
Future _loadSettings(sc.SyncbaseTable tb) async {
tb
- .scan(new sc.RowRange.prefix(_settingsWatchSyncPrefix))
+ .scan(new sc.RowRange.prefix(util.settingsWatchSyncPrefix))
.forEach((sc.KeyValue kv) {
- if (kv.key.endsWith("/settings")) {
+ if (util.isSettingsKey(kv.key)) {
// Then we can process the value as if it were settings data.
this.updateSettingsCallback(
- _settingsDataKeyUserID(kv.key), UTF8.decode(kv.value));
+ util.userIDFromSettingsDataKey(kv.key), UTF8.decode(kv.value));
}
});
}
@@ -292,12 +282,12 @@
Future scanSettings() async {
SettingsScanHandler ssh =
new SettingsScanHandler(_cc, this.updateGamesCallback);
- return _cc.discoveryClient.scan(_discoverySettingsKey,
+ return _cc.discoveryClient.scan(_discoveryGameAdKey,
'v.InterfaceName="${util.discoveryInterfaceName}"', ssh);
}
Future stopScanSettings() {
- return _cc.discoveryClient.stopScan(_discoverySettingsKey);
+ return _cc.discoveryClient.stopScan(_discoveryGameAdKey);
}
// Someone who wants to join a game should advertise their presence.
@@ -305,7 +295,7 @@
String settingsSuffix = await _syncSettingsSuffix();
String gameSuffix = util.syncgameSuffix("${gsd.gameID}");
return _cc.discoveryClient.advertise(
- _discoverySettingsKey,
+ _discoveryGameAdKey,
DiscoveryClient.serviceMaker(
interfaceName: util.discoveryInterfaceName,
attrs: <String, String>{
@@ -316,11 +306,11 @@
}
Future stopAdvertiseSettings() {
- return _cc.discoveryClient.stopAdvertise(_discoverySettingsKey);
+ return _cc.discoveryClient.stopAdvertise(_discoveryGameAdKey);
}
Future<int> _getUserID() async {
- String result = await _tryReadData(tb, _personalKey);
+ String result = await _tryReadData(tb, util.settingsPersonalKey);
if (result == null) {
return null;
}
@@ -337,11 +327,6 @@
}
}
-String _getPartFromBack(String input, String separator, int indexFromLast) {
- List<String> parts = input.split(separator);
- return parts[parts.length - 1 - indexFromLast];
-}
-
// Implementation of the ScanHandler for Settings information.
// Upon finding a settings advertiser, you want to join the syncgroup that
// they're advertising.
diff --git a/lib/src/syncbase/util.dart b/lib/src/syncbase/util.dart
index 749e0cd..9523947 100644
--- a/lib/src/syncbase/util.dart
+++ b/lib/src/syncbase/util.dart
@@ -21,6 +21,9 @@
const String discoveryInterfaceName = 'CroupierSettingsAndGame';
+const String settingsPersonalKey = "personal";
+const String settingsWatchSyncPrefix = "users";
+
typedef void NoArgCb();
typedef void keyValueCallback(String key, String value);
@@ -44,3 +47,56 @@
const String syncgameSettingsAttr = "settings_sgname";
const String syncgameGameStartDataAttr = "game_start_data";
+
+const String separator = "/";
+
+String gameIDFromGameKey(String gameKey) {
+ List<String> parts = gameKey.split(separator);
+ return parts[0];
+}
+
+String playerUpdateTypeFromPlayerKey(String playerKey) {
+ return _getPartFromBack(playerKey, 0);
+}
+
+String playerIDFromPlayerKey(String playerKey) {
+ return _getPartFromBack(playerKey, 1);
+}
+
+String gameOwnerKey(int gameID) {
+ return "${gameID}/owner";
+}
+
+String gameTypeKey(int gameID) {
+ return "${gameID}/type";
+}
+
+String gameStatusKey(int gameID) {
+ return "${gameID}/status";
+}
+
+String playerSettingsKeyFromData(int gameID, int userID) {
+ return "${gameID}/players/${userID}/settings_sg";
+}
+
+String playerNumberKeyFromData(int gameID, int userID) {
+ return "${gameID}/players/${userID}/player_number";
+}
+
+bool isSettingsKey(String key) {
+ return key.indexOf(settingsWatchSyncPrefix) == 0 && key.endsWith("/settings");
+}
+
+String settingsDataKeyFromUserID(int userID) {
+ return "${settingsWatchSyncPrefix}/${userID}/settings";
+}
+
+String userIDFromSettingsDataKey(String dataKey) {
+ List<String> parts = dataKey.split("/");
+ return parts[parts.length - 2];
+}
+
+String _getPartFromBack(String input, int indexFromLast) {
+ List<String> parts = input.split(separator);
+ return parts[parts.length - 1 - indexFromLast];
+}
diff --git a/pubspec.lock b/pubspec.lock
index 5610040..93350e9 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -154,7 +154,7 @@
path:
description: path
source: hosted
- version: "1.3.8"
+ version: "1.3.9"
petitparser:
description: petitparser
source: hosted
@@ -214,7 +214,7 @@
syncbase:
description: syncbase
source: hosted
- version: "0.0.18"
+ version: "0.0.20"
test:
description: test
source: hosted