Merge "croupier: Add Resume Game button"
diff --git a/lib/components/croupier.dart b/lib/components/croupier.dart
index bb7506a..770c7b7 100644
--- a/lib/components/croupier.dart
+++ b/lib/components/croupier.dart
@@ -50,6 +50,13 @@
child: new Text('Join Game', style: style.Text.titleStyle),
onPressed: makeSetStateCallback(
logic_croupier.CroupierState.JoinGame)),
+ new FlatButton(
+ child: new Text('Resume Game', style: style.Text.titleStyle),
+ onPressed: config.croupier.mostRecentGameID != null
+ ? makeSetStateCallback(
+ logic_croupier.CroupierState.ResumeGame,
+ config.croupier.mostRecentGameID)
+ : null),
new CroupierProfileComponent(
settings: config.croupier.settings,
width: style.Size.settingsSize,
@@ -118,13 +125,18 @@
case logic_croupier.CroupierState.PlayGame:
return new Container(
padding: new EdgeDims.only(top: ui.window.padding.top),
- child: component_game.createGameComponent(config.croupier, () {
- config.croupier.game.quit();
- makeSetStateCallback(logic_croupier.CroupierState.Welcome)();
- },
+ child: component_game.createGameComponent(config.croupier,
+ makeSetStateCallback(logic_croupier.CroupierState.Welcome),
width: ui.window.size.width,
height: ui.window.size.height - ui.window.padding.top,
key: _gameKey));
+
+ case logic_croupier.CroupierState.ResumeGame:
+ return new Container(
+ padding: new EdgeDims.only(top: ui.window.padding.top),
+ child: new Text("Resuming Game...", style: style.Text.titleStyle),
+ width: ui.window.size.width,
+ height: ui.window.size.height - ui.window.padding.top);
default:
assert(false);
return null;
diff --git a/lib/components/hearts/hearts.part.dart b/lib/components/hearts/hearts.part.dart
index 01f223b..d74946b 100644
--- a/lib/components/hearts/hearts.part.dart
+++ b/lib/components/hearts/hearts.part.dart
@@ -301,7 +301,9 @@
Widget _makeDebugButtons() {
if (config.game.debugMode == false) {
- return new Flex([]);
+ return new Flex([
+ new Flexible(flex: 4, child: _makeButton('Quit', _quitGameCallback))
+ ]);
}
return new Container(
width: config.width,
diff --git a/lib/components/proto/proto.part.dart b/lib/components/proto/proto.part.dart
index 79e1e7f..2e1bd88 100644
--- a/lib/components/proto/proto.part.dart
+++ b/lib/components/proto/proto.part.dart
@@ -17,7 +17,7 @@
Widget build(BuildContext context) {
List<Widget> cardCollections = new List<Widget>();
- cardCollections.add(new Text(config.game.debugString));
+ cardCollections.add(new Text(config.game.debugString ?? ""));
for (int i = 0; i < 4; i++) {
List<logic_card.Card> cards = config.game.cardCollections[i];
@@ -60,7 +60,9 @@
Widget _makeDebugButtons() {
if (config.game.debugMode == false) {
- return new Flex([]);
+ return new Flex([
+ new Flexible(flex: 4, child: _makeButton('Quit', _quitGameCallback))
+ ]);
}
return new Flex([
new Text('P${config.game.playerNumber}'),
diff --git a/lib/components/solitaire/solitaire.part.dart b/lib/components/solitaire/solitaire.part.dart
index a9833db..28c071b 100644
--- a/lib/components/solitaire/solitaire.part.dart
+++ b/lib/components/solitaire/solitaire.part.dart
@@ -58,7 +58,9 @@
Widget _makeDebugButtons() {
if (config.game.debugMode == false) {
- return new Flex([]);
+ return new Flex([
+ new Flexible(flex: 4, child: _makeButton('Quit', _quitGameCallback))
+ ]);
}
return new Container(
width: config.width,
diff --git a/lib/logic/create_game.dart b/lib/logic/create_game.dart
index 1a2f95c..fbad529 100644
--- a/lib/logic/create_game.dart
+++ b/lib/logic/create_game.dart
@@ -8,17 +8,21 @@
import 'solitaire/solitaire.dart' as solitaire_impl;
game_impl.Game createGame(game_impl.GameType gt, bool debugMode,
- {int gameID, bool isCreator}) {
+ {int gameID, bool isCreator, int playerNumber}) {
switch (gt) {
case game_impl.GameType.Proto:
return new proto_impl.ProtoGame(gameID: gameID, isCreator: isCreator)
- ..debugMode = debugMode;
+ ..debugMode = debugMode
+ ..playerNumber = playerNumber;
case game_impl.GameType.Hearts:
return new hearts_impl.HeartsGame(gameID: gameID, isCreator: isCreator)
- ..debugMode = debugMode;
+ ..debugMode = debugMode
+ ..playerNumber = playerNumber;
case game_impl.GameType.Solitaire:
return new solitaire_impl.SolitaireGame(
- gameID: gameID, isCreator: isCreator)..debugMode = debugMode;
+ gameID: gameID, isCreator: isCreator)
+ ..debugMode = debugMode
+ ..playerNumber = playerNumber;
default:
assert(false);
return null;
diff --git a/lib/logic/croupier.dart b/lib/logic/croupier.dart
index e3de247..53d1824 100644
--- a/lib/logic/croupier.dart
+++ b/lib/logic/croupier.dart
@@ -12,7 +12,14 @@
import 'game/game.dart'
show Game, GameType, GameStartData, stringToGameType, gameTypeToString;
-enum CroupierState { Welcome, ChooseGame, JoinGame, ArrangePlayers, PlayGame }
+enum CroupierState {
+ Welcome,
+ ChooseGame,
+ JoinGame,
+ ArrangePlayers,
+ PlayGame,
+ ResumeGame
+}
typedef void NoArgCb();
@@ -26,6 +33,7 @@
Map<String, GameStartData> games_found; // empty, but loads asynchronously
Map<int, int> players_found; // empty, but loads asynchronously
Game game; // null until chosen
+ int mostRecentGameID; // null until a game was started.
NoArgCb informUICb;
// Futures to use in order to cancel scans and advertisements.
@@ -83,6 +91,27 @@
orElse: () => null);
}
+ void _setCurrentGame(Game g) {
+ game = g;
+ mostRecentGameID = game.gameID;
+ }
+
+ Game _createNewGame(GameType gt) {
+ return cg.createGame(gt, this.debugMode, isCreator: true);
+ }
+
+ Game _createExistingGame(GameStartData gsd) {
+ return cg.createGame(stringToGameType(gsd.type), this.debugMode,
+ gameID: gsd.gameID, playerNumber: gsd.playerNumber);
+ }
+
+ void _quitGame() {
+ if (game != null) {
+ game.quit();
+ game = null;
+ }
+ }
+
CroupierSettings settingsFromPlayerNumber(int playerNumber) {
int userID = userIDFromPlayerNumber(playerNumber);
if (userID != null) {
@@ -142,8 +171,10 @@
void setState(CroupierState nextState, var data) {
switch (state) {
case CroupierState.Welcome:
- // data should be empty.
- assert(data == null);
+ // data should be empty unless nextState is ResumeGame.
+ if (nextState != CroupierState.ResumeGame) {
+ assert(data == null);
+ }
break;
case CroupierState.ChooseGame:
if (data == null) {
@@ -154,11 +185,14 @@
// data should be the game id here.
GameType gt = data as GameType;
- game = cg.createGame(gt, this.debugMode, isCreator: true);
+ _setCurrentGame(_createNewGame(gt));
_advertiseFuture = settings_manager
.createGameSyncgroup(gameTypeToString(gt), game.gameID)
.then((GameStartData gsd) {
+ if (!game.gameArrangeData.needsArrangement) {
+ settings_manager.setPlayerNumber(gsd.gameID, 0);
+ }
// Only the game chooser should be advertising the game.
return settings_manager.advertiseSettings(gsd);
}); // don't wait for this future.
@@ -179,8 +213,8 @@
// data would probably be the game id again.
GameStartData gsd = data as GameStartData;
- game = cg.createGame(stringToGameType(gsd.type), this.debugMode,
- gameID: gsd.gameID);
+ gsd.playerNumber = null; // At first, there is no player number.
+ _setCurrentGame(_createExistingGame(gsd));
String sgName;
games_found.forEach((String name, GameStartData g) {
if (g == gsd) {
@@ -189,11 +223,13 @@
});
assert(sgName != null);
- settings_manager.joinGameSyncgroup(sgName, gsd.gameID);
players_found[gsd.ownerID] = null;
- if (!game.gameArrangeData.needsArrangement) {
- settings_manager.setPlayerNumber(gsd.gameID, 0);
- }
+ settings_manager.joinGameSyncgroup(sgName, gsd.gameID).then((_) {
+ if (!game.gameArrangeData.needsArrangement) {
+ settings_manager.setPlayerNumber(gsd.gameID, 0);
+ }
+ });
+
break;
case CroupierState.ArrangePlayers:
// Note that if we were arranging players, we might have been advertising.
@@ -209,7 +245,13 @@
assert(data == null);
break;
case CroupierState.PlayGame:
- // data should be empty.
+ break;
+ case CroupierState.ResumeGame:
+ // Data might be GameStartData. If so, then we must advertise it.
+ GameStartData gsd = data;
+ if (gsd != null) {
+ _advertiseFuture = settings_manager.advertiseSettings(gsd);
+ }
break;
default:
assert(false);
@@ -217,16 +259,62 @@
// A simplified way of clearing out the games and players found.
// They will need to be re-discovered in the future.
- if (nextState == CroupierState.Welcome) {
- games_found.clear();
- players_found.clear();
- game = null;
- } else if (nextState == CroupierState.JoinGame) {
- // Start scanning for games since that's what's next for you.
- _scanFuture =
- settings_manager.scanSettings(); // don't wait for this future.
+ switch (nextState) {
+ case CroupierState.Welcome:
+ games_found.clear();
+ players_found.clear();
+ _quitGame();
+ break;
+ case CroupierState.JoinGame:
+ // Start scanning for games since that's what's next for you.
+ _scanFuture =
+ settings_manager.scanSettings(); // don't wait for this future.
+ break;
+ case CroupierState.ResumeGame:
+ // We need to create the game again.
+ int gameIDData = data;
+ _resumeGameAsynchronously(gameIDData);
+ break;
+ default:
+ break;
}
state = nextState;
}
+
+ // Resumes the game from the given gameID.
+ Future _resumeGameAsynchronously(int gameIDData) async {
+ GameStartData gsd = await settings_manager.getGameStartData(gameIDData);
+ bool wasOwner = (gsd.ownerID == settings?.userID);
+ print(
+ "The game was ${gsd.toJSONString()}, and was I the owner? ${wasOwner}");
+ _setCurrentGame(_createExistingGame(gsd));
+
+ String sgName = await settings_manager.getGameSyncgroup(gameIDData);
+ print("The sg name was ${sgName}");
+ await settings_manager.joinGameSyncgroup(sgName, gameIDData);
+
+ // Since initial scan processing is done, we can now set isCreator
+ game.isCreator = wasOwner;
+ String gameStatus = await settings_manager.getGameStatus(gameIDData);
+
+ print("The game's status was ${gameStatus}");
+ // Depending on the game state, we should go to a different screen.
+ switch (gameStatus) {
+ case "RUNNING":
+ // The game is running, so let's play it!
+ setState(CroupierState.PlayGame, null);
+ break;
+ default:
+ // We are still arranging players, so we need to advertise our game
+ // start data.
+ setState(CroupierState.ArrangePlayers, gsd);
+ break;
+ }
+
+ // And we can ask the UI to redraw
+ if (this.informUICb != null) {
+ this.informUICb();
+ }
+ }
}
diff --git a/lib/logic/game/game_def.part.dart b/lib/logic/game/game_def.part.dart
index c7544e5..43deaa6 100644
--- a/lib/logic/game/game_def.part.dart
+++ b/lib/logic/game/game_def.part.dart
@@ -91,7 +91,7 @@
GameArrangeData get gameArrangeData;
final GameType gameType;
String get gameTypeName; // abstract
- final bool isCreator;
+ bool isCreator; // True if this user created the game. Behavior can vary based on this flag, so it can make sense to defer setting it.
final List<List<Card>> cardCollections = new List<List<Card>>();
final List<Card> deck = new List<Card>.from(Card.All);
diff --git a/lib/src/mocks/settings_manager.dart b/lib/src/mocks/settings_manager.dart
index 533fead..e0b1daa 100644
--- a/lib/src/mocks/settings_manager.dart
+++ b/lib/src/mocks/settings_manager.dart
@@ -77,4 +77,16 @@
Future setGameStatus(int gameID, String status) async {
return new Future(() => null);
}
+
+ Future<String> getGameStatus(int gameID) async {
+ return new Future(() => null);
+ }
+
+ Future<logic_game.GameStartData> getGameStartData(int gameID) async {
+ return new Future(() => null);
+ }
+
+ Future<String> getGameSyncgroup(int gameID) async {
+ return new Future(() => null);
+ }
}
diff --git a/lib/src/syncbase/settings_manager.dart b/lib/src/syncbase/settings_manager.dart
index 55a71b8..ef40e49 100644
--- a/lib/src/syncbase/settings_manager.dart
+++ b/lib/src/syncbase/settings_manager.dart
@@ -169,9 +169,11 @@
logic_game.GameStartData gsd =
new logic_game.GameStartData(type, 0, gameID, id);
- await _cc.createSyncgroup(
- _cc.makeSyncgroupName(util.syncgameSuffix("${gsd.gameID}")),
- util.tableNameGames,
+ String sgName = _cc.makeSyncgroupName(util.syncgameSuffix("${gsd.gameID}"));
+
+ await gameTable.row(util.gameSyncgroupKey(gameID)).put(UTF8.encode(sgName));
+
+ await _cc.createSyncgroup(sgName, util.tableNameGames,
prefix: util.syncgamePrefix(gameID));
return gsd;
@@ -212,6 +214,34 @@
await gameTable.row(util.gameStatusKey(gameID)).put(UTF8.encode(status));
}
+ Future<String> getGameStatus(int gameID) async {
+ sc.SyncbaseDatabase db = await _cc.createDatabase();
+ sc.SyncbaseTable gameTable = await _cc.createTable(db, util.tableNameGames);
+
+ return _tryReadData(gameTable, util.gameStatusKey(gameID));
+ }
+
+ Future<String> getGameSyncgroup(int gameID) async {
+ sc.SyncbaseDatabase db = await _cc.createDatabase();
+ sc.SyncbaseTable gameTable = await _cc.createTable(db, util.tableNameGames);
+ return _tryReadData(gameTable, util.gameSyncgroupKey(gameID));
+ }
+
+ Future<logic_game.GameStartData> getGameStartData(int gameID) async {
+ sc.SyncbaseDatabase db = await _cc.createDatabase();
+ sc.SyncbaseTable gameTable = await _cc.createTable(db, util.tableNameGames);
+
+ String owner = await _tryReadData(gameTable, util.gameOwnerKey(gameID));
+ String type = await _tryReadData(gameTable, util.gameTypeKey(gameID));
+
+ int id = await _getUserID();
+ String playerNumber =
+ await _tryReadData(gameTable, util.playerNumberKeyFromData(gameID, id));
+ int pn = playerNumber != null ? int.parse(playerNumber) : null;
+
+ return new logic_game.GameStartData(type, pn, gameID, int.parse(owner));
+ }
+
// TODO(alexfandrianto): It is possible that the more efficient way of
// scanning is to do it for only short bursts. In that case, we should call
// stopScanSettings a few seconds after starting it.
diff --git a/lib/src/syncbase/util.dart b/lib/src/syncbase/util.dart
index af882ba..c6c8259 100644
--- a/lib/src/syncbase/util.dart
+++ b/lib/src/syncbase/util.dart
@@ -77,6 +77,10 @@
return "${gameID}/status";
}
+String gameSyncgroupKey(int gameID) {
+ return "${gameID}/game_sg";
+}
+
String playerSettingsKeyFromData(int gameID, int userID) {
return "${gameID}/players/${userID}/settings_sg";
}
diff --git a/schema.md b/schema.md
index 8c01264..718055e 100644
--- a/schema.md
+++ b/schema.md
@@ -11,6 +11,7 @@
```
For advertising and setting up the game:
+<game_id>/game_sg = <game_syncgroup_name>
<game_id>/type = <game_type>
<game_id>/owner = <user_id>
<game_id>/status = [null|RUNNING]