croupier: Add Arrange Players
- A very rudimentary arrange players screen was added.
- Since this screen gets in the way of developing Hearts, a workaround
using Debug Mode was added.
- playerNumber was also removed since this is now set by arrange
players. the tests are adjusted to correpond with this.
Change-Id: I205ecc867ef1ba0ac7a83c97db65da54cc649107
diff --git a/lib/components/board.dart b/lib/components/board.dart
index 861efa4..1ee925c 100644
--- a/lib/components/board.dart
+++ b/lib/components/board.dart
@@ -199,14 +199,11 @@
}
Widget _getProfile(int playerNumber, bool isWide) {
- int userID = config.croupier.userIDFromPlayerNumber(playerNumber);
-
bool isMini = isWide && config.cardHeight * 2 > config.height * 0.25;
- CroupierSettings cs; // If cs is null, a placeholder is used instead.
- if (userID != null) {
- cs = config.croupier.settings_everyone[userID];
- }
+ // If cs is null, a placeholder is used instead.
+ CroupierSettings cs =
+ config.croupier.settingsFromPlayerNumber(playerNumber);
return new CroupierProfileComponent(
settings: cs, height: config.height * 0.15, isMini: isMini);
}
diff --git a/lib/components/croupier.dart b/lib/components/croupier.dart
index 415e8f7..b164dd1 100644
--- a/lib/components/croupier.dart
+++ b/lib/components/croupier.dart
@@ -106,27 +106,14 @@
padding: new EdgeDims.only(top: ui.window.padding.top),
child: new Column(gameAdWidgets));
case logic_croupier.CroupierState.ArrangePlayers:
- List<Widget> profileWidgets = new List<Widget>();
- config.croupier.players_found.forEach((int userID, _) {
- CroupierSettings cs = config.croupier.settings_everyone[userID];
- // cs could be null if this settings data hasn't synced yet.
- // If so, a placeholder is shown instead.
- profileWidgets.add(new CroupierProfileComponent(settings: cs));
- });
-
- // TODO(alexfandrianto): You can only start the game once there are enough players.
return new Container(
padding: new EdgeDims.only(top: ui.window.padding.top),
child: new Column([
- new FlatButton(child: new Text('Start Game'), onPressed: () {
- makeSetStateCallback(logic_croupier.CroupierState.PlayGame)();
- config.croupier.game.startGameSignal();
- }),
- new Grid(profileWidgets, maxChildExtent: 150.0),
+ _buildArrangePlayers(),
new FlatButton(
child: new Text('Back'),
onPressed: makeSetStateCallback(
- logic_croupier.CroupierState.ChooseGame))
+ logic_croupier.CroupierState.Welcome))
]));
case logic_croupier.CroupierState.PlayGame:
return new Container(
@@ -142,4 +129,58 @@
return null;
}
}
+
+ // Show the player profiles. If needs arrangement, then the profile is only
+ // shown if the person has not sat down yet.
+ Widget _buildPlayerProfiles(bool needsArrangement) {
+ List<Widget> profileWidgets = new List<Widget>();
+ config.croupier.players_found.forEach((int userID, int playerNumber) {
+ if (!needsArrangement || playerNumber == null) {
+ CroupierSettings cs = config.croupier.settings_everyone[userID];
+ // cs could be null if this settings data hasn't synced yet.
+ // If so, a placeholder is shown instead.
+ profileWidgets.add(new CroupierProfileComponent(settings: cs));
+ }
+ });
+
+ return new Grid(profileWidgets, maxChildExtent: 120.0);
+ }
+
+ Widget _buildArrangePlayers() {
+ List<Widget> allWidgets = new List<Widget>();
+
+ logic_game.GameArrangeData gad = config.croupier.game.gameArrangeData;
+ Iterable<int> playerNumbers = config.croupier.players_found.values;
+
+ // Allow games that can start with these players to begin.
+ // Debug Mode should also go through.
+ NoArgCb onPressed;
+ if (gad.canStart(playerNumbers) || config.croupier.debugMode) {
+ onPressed = () {
+ makeSetStateCallback(logic_croupier.CroupierState.PlayGame)();
+
+ // Since playerNumber starts out as null, we should set it to -1 if
+ // the person pressed Start Game without sitting.
+ if (config.croupier.game.playerNumber == null) {
+ config.croupier.game.playerNumber = -1;
+ }
+ config.croupier.game.startGameSignal();
+ };
+ }
+
+ // Always include the Start Game Button.
+ allWidgets.add(
+ new FlatButton(child: new Text('Start Game'), onPressed: onPressed));
+
+ // Games that need arrangement can show their game arrange component.
+ if (gad.needsArrangement) {
+ allWidgets.add(component_game.createGameArrangeComponent(config.croupier,
+ width: ui.window.size.width, height: ui.window.size.height / 2));
+ }
+
+ // Then show the profile widgets of those who have joined the game.
+ allWidgets.add(_buildPlayerProfiles(gad.needsArrangement));
+
+ return new Column(allWidgets);
+ }
}
diff --git a/lib/components/game.dart b/lib/components/game.dart
index 075d104..6049c6c 100644
--- a/lib/components/game.dart
+++ b/lib/components/game.dart
@@ -12,7 +12,6 @@
import '../logic/card.dart' as logic_card;
import '../logic/croupier.dart' show Croupier;
-import '../logic/croupier_settings.dart' show CroupierSettings;
import '../logic/game/game.dart' show Game, GameType;
import '../logic/hearts/hearts.dart' show HeartsGame, HeartsPhase, HeartsType;
import '../logic/solitaire/solitaire.dart' show SolitaireGame, SolitairePhase;
@@ -215,6 +214,25 @@
}
}
+abstract class GameArrangeComponent extends StatelessComponent {
+ final Croupier croupier;
+ final double width;
+ final double height;
+ GameArrangeComponent(this.croupier, {this.width, this.height});
+}
+
+GameArrangeComponent createGameArrangeComponent(Croupier croupier,
+ {double width, double height}) {
+ switch (croupier.game.gameType) {
+ case GameType.Hearts:
+ return new HeartsArrangeComponent(croupier, width: width, height: height);
+ default:
+ // We can't arrange this game.
+ throw new UnimplementedError(
+ "We cannot arrange the game type: ${croupier.game.gameType}");
+ }
+}
+
/// CardAnimationData contains the relevant information for a ZCard to be built.
/// It uses the comp_card's properties, the oldPoint, newPoint, and z-index to
/// determine how it needs to animate.
diff --git a/lib/components/hearts/hearts.part.dart b/lib/components/hearts/hearts.part.dart
index cc4e406..c3f03e5 100644
--- a/lib/components/hearts/hearts.part.dart
+++ b/lib/components/hearts/hearts.part.dart
@@ -103,6 +103,9 @@
game.viewType = HeartsType.Board;
} else {
game.viewType = HeartsType.Player;
+ if (!game.isPlayer) {
+ game.playerNumber = 0; // avoid accidental red screen
+ }
}
});
}
@@ -360,23 +363,13 @@
justifyContent: FlexJustifyContent.spaceBetween);
}
- Widget _getProfileComponent(int playerNumber) {
- int userID = config.croupier.userIDFromPlayerNumber(playerNumber);
-
- CroupierSettings cs; // If cs is null, a placeholder is used instead.
- if (userID != null) {
- cs = config.croupier.settings_everyone[userID];
- }
- return new CroupierProfileComponent(settings: cs);
- }
-
Widget showScore() {
HeartsGame game = config.game as HeartsGame;
Widget w;
if (game.hasGameEnded) {
w = new Text("Game Over!");
- } else if (game.ready[game.playerNumber]) {
+ } else if (!game.isPlayer || game.ready[game.playerNumber]) {
w = new Text("Waiting for other players...");
} else {
w = _makeButton('Ready?', game.setReadyUI);
@@ -415,7 +408,10 @@
scores.add(new Flexible(
child: new Flex([
- new Flexible(child: _getProfileComponent(i), flex: 1),
+ new Flexible(
+ child: new CroupierProfileComponent(
+ settings: config.croupier.settingsFromPlayerNumber(i)),
+ flex: 1),
new Flexible(
child: new Center(
child:
@@ -566,3 +562,77 @@
hasTaken ? null : _makeGameTakeCallback);
}
}
+
+class HeartsArrangeComponent extends GameArrangeComponent {
+ HeartsArrangeComponent(Croupier croupier, {double width, double height})
+ : super(croupier, width: width, height: height);
+
+ bool get hasSat => croupier.game.playerNumber != null;
+
+ Widget build(BuildContext context) {
+ int numAtTable = croupier.players_found.values
+ .where((int playerNumber) => playerNumber == 4)
+ .length;
+
+ return new Container(
+ decoration: style.Box.liveNow,
+ height: height,
+ width: width,
+ child: new Column([
+ new Flexible(
+ flex: 1,
+ child: new Row(
+ [_buildEmptySlot(), _buildSlot("Player", 2), _buildEmptySlot()],
+ justifyContent: FlexJustifyContent.spaceAround,
+ alignItems: FlexAlignItems.stretch)),
+ new Flexible(
+ flex: 1,
+ child: new Row([
+ _buildSlot("Player", 1),
+ _buildSlot("Table: ${numAtTable}", 4),
+ _buildSlot("Player", 3)
+ ],
+ justifyContent: FlexJustifyContent.spaceAround,
+ alignItems: FlexAlignItems.stretch)),
+ new Flexible(
+ flex: 1,
+ child: new Row(
+ [_buildEmptySlot(), _buildSlot("Player", 0), _buildEmptySlot()],
+ justifyContent: FlexJustifyContent.spaceAround,
+ alignItems: FlexAlignItems.stretch))
+ ],
+ justifyContent: FlexJustifyContent.spaceAround,
+ alignItems: FlexAlignItems.stretch));
+ }
+
+ Widget _buildEmptySlot() {
+ return new Flexible(flex: 1, child: new Text(""));
+ }
+
+ Widget _buildSlot(String name, int index) {
+ NoArgCb onTap = () {
+ croupier.settings_manager.setPlayerNumber(croupier.game.gameID, index);
+ };
+ Widget slotWidget = new Text(name, style: style.Text.hugeStyle);
+
+ bool seatTaken =
+ index >= 0 && index < 4 && croupier.players_found.containsValue(index);
+ if (seatTaken) {
+ onTap = null;
+ slotWidget = new CroupierProfileComponent(
+ settings: croupier.settingsFromPlayerNumber(index));
+ } else if (hasSat) {
+ onTap = null;
+ }
+
+ return new Flexible(
+ flex: 1,
+ child: new GestureDetector(
+ child: new Card(
+ color: croupier.game.playerNumber == index
+ ? style.theme.accentColor
+ : null,
+ child: new Center(child: slotWidget)),
+ onTap: onTap));
+ }
+}
diff --git a/lib/logic/create_game.dart b/lib/logic/create_game.dart
index ae08b66..1a2f95c 100644
--- a/lib/logic/create_game.dart
+++ b/lib/logic/create_game.dart
@@ -7,17 +7,17 @@
import 'proto/proto.dart' as proto_impl;
import 'solitaire/solitaire.dart' as solitaire_impl;
-game_impl.Game createGame(game_impl.GameType gt, int pn, bool debugMode,
+game_impl.Game createGame(game_impl.GameType gt, bool debugMode,
{int gameID, bool isCreator}) {
switch (gt) {
case game_impl.GameType.Proto:
- return new proto_impl.ProtoGame(pn, gameID: gameID, isCreator: isCreator)
+ return new proto_impl.ProtoGame(gameID: gameID, isCreator: isCreator)
..debugMode = debugMode;
case game_impl.GameType.Hearts:
- return new hearts_impl.HeartsGame(pn,
- gameID: gameID, isCreator: isCreator)..debugMode = debugMode;
+ return new hearts_impl.HeartsGame(gameID: gameID, isCreator: isCreator)
+ ..debugMode = debugMode;
case game_impl.GameType.Solitaire:
- return new solitaire_impl.SolitaireGame(pn,
+ return new solitaire_impl.SolitaireGame(
gameID: gameID, isCreator: isCreator)..debugMode = debugMode;
default:
assert(false);
diff --git a/lib/logic/croupier.dart b/lib/logic/croupier.dart
index 4f9789e..98dc9a6 100644
--- a/lib/logic/croupier.dart
+++ b/lib/logic/croupier.dart
@@ -78,10 +78,21 @@
orElse: () => null);
}
+ CroupierSettings settingsFromPlayerNumber(int playerNumber) {
+ int userID = userIDFromPlayerNumber(playerNumber);
+ if (userID != null) {
+ return settings_everyone[userID];
+ }
+ return null;
+ }
+
void _updatePlayerFoundCb(String playerID, String playerNum) {
int id = int.parse(playerID);
if (playerNum == null) {
- games_found.remove(id);
+ if (!players_found.containsKey(id)) {
+ // The player exists but has not sat down yet.
+ players_found[id] = null;
+ }
} else {
int playerNumber = int.parse(playerNum);
players_found[id] = playerNumber;
@@ -113,8 +124,7 @@
// data should be the game id here.
GameType gt = data as GameType;
- game = cg.createGame(gt, 0, this.debugMode,
- isCreator: true); // Start as player 0 of whatever game type.
+ game = cg.createGame(gt, this.debugMode, isCreator: true);
_advertiseFuture = settings_manager
.createGameSyncgroup(gameTypeToString(gt), game.gameID)
@@ -128,6 +138,7 @@
// Note that if we were in join game, we must have been scanning.
_scanFuture.then((_) {
settings_manager.stopScanSettings();
+ games_found.clear();
_scanFuture = null;
});
@@ -138,9 +149,8 @@
// data would probably be the game id again.
GameStartData gsd = data as GameStartData;
- game = cg.createGame(
- stringToGameType(gsd.type), gsd.playerNumber, this.debugMode,
- gameID: gsd.gameID); // Start as player 0 of whatever game type.
+ game = cg.createGame(stringToGameType(gsd.type), this.debugMode,
+ gameID: gsd.gameID);
String sgName;
games_found.forEach((String name, GameStartData g) {
if (g == gsd) {
@@ -150,6 +160,10 @@
assert(sgName != null);
settings_manager.joinGameSyncgroup(sgName, gsd.gameID);
+ players_found[gsd.ownerID] = null;
+ 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.
@@ -174,8 +188,8 @@
// 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 = new Map<String, GameStartData>();
- players_found = new Map<int, int>();
+ games_found.clear();
+ players_found.clear();
} else if (nextState == CroupierState.JoinGame) {
// Start scanning for games since that's what's next for you.
_scanFuture =
diff --git a/lib/logic/game/game_def.part.dart b/lib/logic/game/game_def.part.dart
index 70315df..5d53ab3 100644
--- a/lib/logic/game/game_def.part.dart
+++ b/lib/logic/game/game_def.part.dart
@@ -69,11 +69,26 @@
}
}
+// GameArrangeData details what a game needs before beginning.
+class GameArrangeData {
+ final bool needsArrangement;
+ final Set<int> requiredPlayerNumbers;
+ GameArrangeData(this.needsArrangement, this.requiredPlayerNumbers);
+ bool canStart(Iterable<int> actualPlayerNumbers) {
+ // None of the required player numbers can be missing from the actual ones.
+ return !needsArrangement ||
+ !requiredPlayerNumbers.any((int i) {
+ return !actualPlayerNumbers.contains(i);
+ });
+ }
+}
+
typedef void NoArgCb();
/// A game consists of multiple decks and tracks a single deck of cards.
/// It also handles events; when cards are dragged to and from decks.
abstract class Game {
+ GameArrangeData get gameArrangeData;
final GameType gameType;
String get gameTypeName; // abstract
final bool isCreator;
@@ -97,8 +112,7 @@
NoArgCb updateCallback; // Used to inform components of when a change has occurred. This is especially important when something non-UI related changes what should be drawn.
// A super constructor, don't call this unless you're a subclass.
- Game.create(
- this.gameType, this.gamelog, this._playerNumber, int numCollections,
+ Game.create(this.gameType, this.gamelog, int numCollections,
{int gameID, bool isCreator})
: gameID = gameID ?? new math.Random().nextInt(0x00FFFFFF),
isCreator = isCreator ?? false {
diff --git a/lib/logic/hearts/hearts.dart b/lib/logic/hearts/hearts.dart
index 14e7f5c..3f25c0a 100644
--- a/lib/logic/hearts/hearts.dart
+++ b/lib/logic/hearts/hearts.dart
@@ -7,7 +7,8 @@
import 'dart:math' as math;
import '../card.dart' show Card;
-import '../game/game.dart' show Game, GameType, GameCommand, GameLog;
+import '../game/game.dart'
+ show Game, GameArrangeData, GameType, GameCommand, GameLog;
import '../../src/syncbase/log_writer.dart' show LogWriter, SimulLevel;
part 'hearts_command.part.dart';
diff --git a/lib/logic/hearts/hearts_game.part.dart b/lib/logic/hearts/hearts_game.part.dart
index 63e5e1a..9461a46 100644
--- a/lib/logic/hearts/hearts_game.part.dart
+++ b/lib/logic/hearts/hearts_game.part.dart
@@ -66,8 +66,8 @@
List<int> deltaScores = [0, 0, 0, 0];
List<bool> ready;
- HeartsGame(int playerNumber, {int gameID, bool isCreator})
- : super.create(GameType.Hearts, new HeartsLog(), playerNumber, 16,
+ HeartsGame({int gameID, bool isCreator})
+ : super.create(GameType.Hearts, new HeartsLog(), 16,
gameID: gameID, isCreator: isCreator) {
resetGame();
unsetReady();
@@ -95,6 +95,8 @@
deal(PLAYER_D, forD);
}
+ bool get isPlayer => this.playerNumber >= 0 && this.playerNumber < 4;
+
int get passTarget {
switch (roundNumber % 4) {
// is a 4-cycle
@@ -254,14 +256,29 @@
// 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);
- if (playerNumber >= 0 && playerNumber < 4) {
+ if (this.debugMode) {
+ // Debug Mode should pretend this device is all players.
+ for (int i = 0; i < 4; i++) {
+ gamelog.add(new HeartsCommand.ready(i));
+ }
+ } else if (this.isPlayer) {
gamelog.add(new HeartsCommand.ready(playerNumber));
}
}
+ static final GameArrangeData _arrangeData =
+ new GameArrangeData(true, new Set.from([0, 1, 2, 3]));
+ GameArrangeData get gameArrangeData => _arrangeData;
+
@override
void startGameSignal() {
+ if (this.debugMode && this.playerNumber < 0) {
+ this.playerNumber = 0;
+ }
setReadyUI();
+ if (!this.isPlayer) {
+ this.viewType = HeartsType.Board;
+ }
}
// Note that this will be called by the UI.
diff --git a/lib/logic/proto/proto.dart b/lib/logic/proto/proto.dart
index f12361f..314046f 100644
--- a/lib/logic/proto/proto.dart
+++ b/lib/logic/proto/proto.dart
@@ -5,7 +5,8 @@
library proto;
import '../card.dart' show Card;
-import '../game/game.dart' show Game, GameType, GameCommand, GameLog;
+import '../game/game.dart'
+ show Game, GameArrangeData, GameType, GameCommand, GameLog;
part 'proto_command.part.dart';
part 'proto_game.part.dart';
diff --git a/lib/logic/proto/proto_game.part.dart b/lib/logic/proto/proto_game.part.dart
index 623d062..cb76c20 100644
--- a/lib/logic/proto/proto_game.part.dart
+++ b/lib/logic/proto/proto_game.part.dart
@@ -8,8 +8,12 @@
@override
String get gameTypeName => "Proto";
- ProtoGame(int playerNumber, {int gameID, bool isCreator})
- : super.create(GameType.Proto, new ProtoLog(), playerNumber, 6,
+ static final GameArrangeData _arrangeData =
+ new GameArrangeData(false, new Set());
+ GameArrangeData get gameArrangeData => _arrangeData;
+
+ ProtoGame({int gameID, bool isCreator})
+ : super.create(GameType.Proto, new ProtoLog(), 6,
gameID: gameID, isCreator: isCreator) {
// playerNumber would be used in a real game, but I have to ignore it for debugging.
// It would determine faceUp/faceDown status.faceDown
diff --git a/lib/logic/solitaire/solitaire.dart b/lib/logic/solitaire/solitaire.dart
index 6d38b61..aa4a63a 100644
--- a/lib/logic/solitaire/solitaire.dart
+++ b/lib/logic/solitaire/solitaire.dart
@@ -5,7 +5,8 @@
library solitaire;
import '../card.dart' show Card;
-import '../game/game.dart' show Game, GameType, GameCommand, GameLog;
+import '../game/game.dart'
+ show Game, GameArrangeData, GameType, GameCommand, GameLog;
import '../../src/syncbase/log_writer.dart' show LogWriter, SimulLevel;
part 'solitaire_command.part.dart';
diff --git a/lib/logic/solitaire/solitaire_game.part.dart b/lib/logic/solitaire/solitaire_game.part.dart
index dd0e31a..b90df65 100644
--- a/lib/logic/solitaire/solitaire_game.part.dart
+++ b/lib/logic/solitaire/solitaire_game.part.dart
@@ -19,6 +19,10 @@
static const OFFSET_DOWN = 6;
static const OFFSET_UP = 13;
+ static final GameArrangeData _arrangeData =
+ new GameArrangeData(false, new Set());
+ GameArrangeData get gameArrangeData => _arrangeData;
+
SolitairePhase _phase = SolitairePhase.Deal;
SolitairePhase get phase => _phase;
void set phase(SolitairePhase other) {
@@ -26,9 +30,8 @@
_phase = other;
}
- SolitaireGame(int playerNumber, {int gameID, bool isCreator})
- : super.create(
- GameType.Solitaire, new SolitaireLog(), playerNumber, NUM_PILES,
+ SolitaireGame({int gameID, bool isCreator})
+ : super.create(GameType.Solitaire, new SolitaireLog(), NUM_PILES,
gameID: gameID, isCreator: isCreator) {
resetGame();
}
@@ -342,7 +345,10 @@
// TODO(alexfandrianto): Maybe wanted for debug; if not, remove.
void jumpToScorePhaseDebug() {}
- // TODO(alexfandrianto): This needs to be a signal to trigger a deal.
@override
- void startGameSignal() {}
+ void startGameSignal() {
+ if (this.isCreator) {
+ this.dealCardsUI();
+ }
+ }
}
diff --git a/lib/src/mocks/settings_manager.dart b/lib/src/mocks/settings_manager.dart
index 9231d1a..f96ab8c 100644
--- a/lib/src/mocks/settings_manager.dart
+++ b/lib/src/mocks/settings_manager.dart
@@ -64,4 +64,8 @@
Future joinGameSyncgroup(String sgName, int gameID) {
return new Future(() => null);
}
+
+ Future setPlayerNumber(int gameID, int playerNumber) async {
+ return new Future(() => null);
+ }
}
diff --git a/lib/src/syncbase/settings_manager.dart b/lib/src/syncbase/settings_manager.dart
index 8a5b88b..64b419b 100644
--- a/lib/src/syncbase/settings_manager.dart
+++ b/lib/src/syncbase/settings_manager.dart
@@ -181,6 +181,9 @@
case "settings_sg":
// Join this player's settings syncgroup.
_cc.joinSyncgroup(value);
+
+ // Also, signal that this player has been found.
+ this.updatePlayerFoundCallback(playerID, null);
break;
default:
print("Unexpected key: ${key} with value ${value}");
@@ -208,10 +211,7 @@
int id = await _getUserID();
await gameTable.row("${gameID}/owner").put(UTF8.encode("${id}"));
await gameTable
- .row("${gameID}/players/${id}/player_number")
- .put(UTF8.encode("0"));
- await gameTable
- .row("${gameID}/players/{$id}/settings_sg")
+ .row("${gameID}/players/${id}/settings_sg")
.put(UTF8.encode(await _mySettingsSyncgroupName()));
logic_game.GameStartData gsd =
@@ -227,8 +227,8 @@
Future joinGameSyncgroup(String sgName, int gameID) async {
print("Now joining game syncgroup at ${sgName} and ${gameID}");
- sc.SyncbaseSyncgroup sg = await _cc.joinSyncgroup(sgName);
+ await _cc.joinSyncgroup(sgName);
sc.SyncbaseDatabase db = await _cc.createDatabase();
sc.SyncbaseTable gameTable = await _cc.createTable(db, util.tableNameGames);
@@ -237,18 +237,20 @@
util.syncgamePrefix(gameID) + "/players", UTF8.encode("now"));
_startWatchPlayers(watchStream); // Don't wait for this future.
- // Also write yourself to the table as player |NUM_PLAYERS - 1|
- Map<String, sc.SyncgroupMemberInfo> fellowPlayers = await sg.getMembers();
- print("I have found! ${fellowPlayers} ${fellowPlayers.length}");
+ int id = await _getUserID();
+ await gameTable
+ .row("${gameID}/players/${id}/settings_sg")
+ .put(UTF8.encode(await _mySettingsSyncgroupName()));
+ }
+
+ Future setPlayerNumber(int gameID, int playerNumber) async {
+ sc.SyncbaseDatabase db = await _cc.createDatabase();
+ sc.SyncbaseTable gameTable = await _cc.createTable(db, util.tableNameGames);
int id = await _getUserID();
- int playerNumber = fellowPlayers.length - 1;
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/styles/common.dart b/lib/styles/common.dart
index 5dffee2..b5473c1 100644
--- a/lib/styles/common.dart
+++ b/lib/styles/common.dart
@@ -40,6 +40,8 @@
class Box {
static final BoxDecoration liveNow = new BoxDecoration(
border: new Border.all(color: theme.accentColor), borderRadius: 2.0);
+ static final BoxDecoration border =
+ new BoxDecoration(border: new Border.all(), borderRadius: 2.0);
}
ThemeData theme = new ThemeData(
diff --git a/test/hearts_test.dart b/test/hearts_test.dart
index 09160db..0a3de71 100644
--- a/test/hearts_test.dart
+++ b/test/hearts_test.dart
@@ -10,7 +10,7 @@
void main() {
group("Initialization", () {
- HeartsGame game = new HeartsGame(0);
+ HeartsGame game = new HeartsGame()..playerNumber = 0;
game.phase = HeartsPhase.Deal;
test("Dealing", () {
game.dealCards(); // What the dealer actually runs to get cards to everybody.
@@ -101,7 +101,7 @@
// For this test, the cards may end up being duplicate or inconsistent.
group("Scoring", () {
- HeartsGame game = new HeartsGame(0);
+ HeartsGame game = new HeartsGame()..playerNumber = 0;
test("Compute/Prepare Score", () {
// In this situation, what's the score?
game.cardCollections[HeartsGame.PLAYER_A_TRICK] = <Card>[
@@ -163,7 +163,7 @@
});
group("Game Over", () {
- HeartsGame game = new HeartsGame(0);
+ HeartsGame game = new HeartsGame()..playerNumber = 0;
test("Has the game ended? Yes", () {
// Check if the game has ended. Should be yes.
@@ -181,7 +181,7 @@
// performing a single action or set of actions.
// Reads from a log, so we will go through logical game mechanics.
group("Card Manipulation", () {
- HeartsGame game = new HeartsGame(0);
+ HeartsGame game = new HeartsGame()..playerNumber = 0;
// Note: This could have been a non-file (in-memory), but it's fine to use a file too.
File file = new File("test/game_log_hearts_test.txt");
@@ -462,7 +462,7 @@
group("Card Manipulation - Error Cases", () {
test("Dealing - too soon", () {
- HeartsGame game = new HeartsGame(0);
+ HeartsGame game = new HeartsGame()..playerNumber = 0;
expect(game.phase, HeartsPhase.StartGame);
expect(() {
game.gamelog.add(new HeartsCommand.deal(
@@ -470,7 +470,7 @@
}, throwsA(new isInstanceOf<StateError>()));
});
test("Dealing - wrong phase", () {
- HeartsGame game = new HeartsGame(0);
+ HeartsGame game = new HeartsGame()..playerNumber = 0;
game.phase = HeartsPhase.Score;
expect(() {
game.gamelog.add(new HeartsCommand.deal(
@@ -478,7 +478,7 @@
}, throwsA(new isInstanceOf<StateError>()));
});
test("Dealing - missing card", () {
- HeartsGame game = new HeartsGame(0);
+ HeartsGame game = new HeartsGame()..playerNumber = 0;
game.phase = HeartsPhase.Deal;
expect(() {
game.gamelog.add(
@@ -486,14 +486,14 @@
}, throwsA(new isInstanceOf<StateError>()));
});
test("Dealing - too many cards dealt", () {
- HeartsGame game = new HeartsGame(0);
+ HeartsGame game = new HeartsGame()..playerNumber = 0;
game.phase = HeartsPhase.Deal;
expect(() {
game.gamelog.add(new HeartsCommand.deal(
0, new List<Card>.from(Card.All.getRange(0, 15))));
}, throwsA(new isInstanceOf<StateError>()));
- game = new HeartsGame(0);
+ game = new HeartsGame()..playerNumber = 0;
game.phase = HeartsPhase.Deal;
expect(() {
game.gamelog.add(new HeartsCommand.deal(
@@ -503,7 +503,7 @@
}, throwsA(new isInstanceOf<StateError>()));
});
test("Passing - wrong phase", () {
- HeartsGame game = new HeartsGame(0);
+ HeartsGame game = new HeartsGame()..playerNumber = 0;
game.phase = HeartsPhase.Deal;
game.gamelog.add(new HeartsCommand.deal(
0, new List<Card>.from(Card.All.getRange(0, 13))));
@@ -513,7 +513,7 @@
}, throwsA(new isInstanceOf<StateError>()));
});
test("Passing - missing card", () {
- HeartsGame game = new HeartsGame(0);
+ HeartsGame game = new HeartsGame()..playerNumber = 0;
game.phase = HeartsPhase.Deal;
game.gamelog.add(new HeartsCommand.deal(
0, new List<Card>.from(Card.All.getRange(0, 13))));
@@ -524,7 +524,7 @@
}, throwsA(new isInstanceOf<StateError>()));
});
test("Passing - wrong number of cards", () {
- HeartsGame game = new HeartsGame(0);
+ HeartsGame game = new HeartsGame()..playerNumber = 0;
game.phase = HeartsPhase.Deal;
game.gamelog.add(new HeartsCommand.deal(
0, new List<Card>.from(Card.All.getRange(0, 13))));
@@ -534,7 +534,7 @@
0, new List<Card>.from(Card.All.getRange(0, 2))));
}, throwsA(new isInstanceOf<StateError>()));
- game = new HeartsGame(0);
+ game = new HeartsGame()..playerNumber = 0;
game.phase = HeartsPhase.Deal;
game.gamelog.add(new HeartsCommand.deal(
0, new List<Card>.from(Card.All.getRange(0, 13))));
@@ -545,14 +545,14 @@
}, throwsA(new isInstanceOf<StateError>()));
});
test("Taking - wrong phase", () {
- HeartsGame game = new HeartsGame(0);
+ HeartsGame game = new HeartsGame()..playerNumber = 0;
game.phase = HeartsPhase.Deal;
expect(() {
game.gamelog.add(new HeartsCommand.take(3));
}, throwsA(new isInstanceOf<StateError>()));
});
test("Playing - wrong phase", () {
- HeartsGame game = new HeartsGame(0);
+ HeartsGame game = new HeartsGame()..playerNumber = 0;
game.phase = HeartsPhase.Deal;
game.gamelog.add(new HeartsCommand.deal(
0, new List<Card>.from(Card.All.getRange(0, 13))));
@@ -561,7 +561,7 @@
}, throwsA(new isInstanceOf<StateError>()));
});
test("Playing - missing card", () {
- HeartsGame game = new HeartsGame(0);
+ HeartsGame game = new HeartsGame()..playerNumber = 0;
game.phase = HeartsPhase.Deal;
game.gamelog.add(new HeartsCommand.deal(
0, new List<Card>.from(Card.All.getRange(0, 13))));
@@ -571,7 +571,7 @@
}, throwsA(new isInstanceOf<StateError>()));
});
test("Playing - invalid card (not 2 of clubs as first card)", () {
- HeartsGame game = new HeartsGame(0);
+ HeartsGame game = new HeartsGame()..playerNumber = 0;
game.phase = HeartsPhase.Deal;
game.gamelog.add(new HeartsCommand.deal(
0, new List<Card>.from(Card.All.getRange(0, 13))));
@@ -584,7 +584,7 @@
test("Playing - invalid card (no penalty on first round)", () {
// NOTE: It is actually possible to be forced to play a penalty card on round 1.
// But the odds are miniscule, so this rule will be enforced.
- HeartsGame game = new HeartsGame(0);
+ HeartsGame game = new HeartsGame()..playerNumber = 0;
game.phase = HeartsPhase.Deal;
game.gamelog.add(new HeartsCommand.deal(
0, new List<Card>.from(Card.All.getRange(0, 13))));
@@ -603,7 +603,7 @@
}, throwsA(new isInstanceOf<StateError>()));
});
test("Playing - wrong turn", () {
- HeartsGame game = new HeartsGame(0);
+ HeartsGame game = new HeartsGame()..playerNumber = 0;
game.phase = HeartsPhase.Deal;
game.gamelog.add(new HeartsCommand.deal(
0, new List<Card>.from(Card.All.getRange(0, 13))));
@@ -621,7 +621,7 @@
}, throwsA(new isInstanceOf<StateError>()));
});
test("Playing - invalid card (suit mismatch)", () {
- HeartsGame game = new HeartsGame(0);
+ HeartsGame game = new HeartsGame()..playerNumber = 0;
game.phase = HeartsPhase.Deal;
game.gamelog.add(new HeartsCommand.deal(
0, new List<Card>.from(Card.All.getRange(0, 12))..add(Card.All[25])));
@@ -640,7 +640,7 @@
}, throwsA(new isInstanceOf<StateError>()));
});
test("Playing - invalid card (hearts not broken yet)", () {
- HeartsGame game = new HeartsGame(0);
+ HeartsGame game = new HeartsGame()..playerNumber = 0;
game.phase = HeartsPhase.Deal;
game.gamelog.add(new HeartsCommand.deal(
0, new List<Card>.from(Card.All.getRange(0, 12))..add(Card.All[38])));
diff --git a/test/solitaire_test.dart b/test/solitaire_test.dart
index a5597f6..f41e5b9 100644
--- a/test/solitaire_test.dart
+++ b/test/solitaire_test.dart
@@ -37,7 +37,7 @@
void main() {
group("Initialization", () {
- SolitaireGame game = new SolitaireGame(0);
+ SolitaireGame game = new SolitaireGame();
test("Dealing", () {
game.dealCardsUI(); // What we run when starting the game.
@@ -71,7 +71,7 @@
// We have a debug cheat button that lets you advance in the game.
// After calling it once, it is forced to place cards up into the aces area.
group("Cheat Command", () {
- SolitaireGame game = new SolitaireGame(0);
+ SolitaireGame game = new SolitaireGame();
game.dealCardsUI(); // Get the cards out there.
test("Cheat Functionality", () {
@@ -91,7 +91,7 @@
});
group("Check Endgame", () {
- SolitaireGame game = new SolitaireGame(0);
+ SolitaireGame game = new SolitaireGame();
test("Has not won pre-deal", () {
expect(game.phase, equals(SolitairePhase.Deal));
@@ -135,7 +135,7 @@
// Run through a canonical game of Solitaire where the player doesn't win.
group("Solitaire - Loss", () {
- SolitaireGame game = new SolitaireGame(0);
+ SolitaireGame game = new SolitaireGame();
// Note: This could have been a non-file (in-memory), but it's fine to use a file too.
KeepGoingCb runCommand =
@@ -166,7 +166,7 @@
// Run through a canonical game of Solitaire where the player does win.
group("Solitaire - Win", () {
- SolitaireGame game = new SolitaireGame(0);
+ SolitaireGame game = new SolitaireGame();
// Note: This could have been a non-file (in-memory), but it's fine to use a file too.
KeepGoingCb runCommand =
@@ -198,7 +198,7 @@
group("Card Manipulation - Error Cases", () {
test("Dealing - wrong phase", () {
expect(() {
- SolitaireGame game = new SolitaireGame(0);
+ SolitaireGame game = new SolitaireGame();
game.phase = SolitairePhase.Score;
game.gamelog
.add(new SolitaireCommand.deal(new List<Card>.from(Card.All)));
@@ -206,7 +206,7 @@
});
test("Dealing - fake cards", () {
expect(() {
- SolitaireGame game = new SolitaireGame(0);
+ SolitaireGame game = new SolitaireGame();
game.gamelog.add(
new SolitaireCommand.deal(<Card>[new Card("fake", "not real")]));
}, throwsA(new isInstanceOf<StateError>()));
@@ -214,13 +214,13 @@
test("Dealing - wrong number of cards dealt", () {
// 2x as many cards
expect(() {
- SolitaireGame game = new SolitaireGame(0);
+ SolitaireGame game = new SolitaireGame();
game.gamelog.add(new SolitaireCommand.deal(
new List<Card>.from(Card.All)..addAll(Card.All)));
}, throwsA(new isInstanceOf<StateError>()));
// missing cards
expect(() {
- SolitaireGame game = new SolitaireGame(0);
+ SolitaireGame game = new SolitaireGame();
game.gamelog.add(new SolitaireCommand.deal(
new List<Card>.from(Card.All.getRange(0, 40))));
}, throwsA(new isInstanceOf<StateError>()));
@@ -245,7 +245,7 @@
Card s13 = Card.All[39 + 12];
SolitaireGame _makeArbitrarySolitaireGame() {
- SolitaireGame g = new SolitaireGame(0);
+ SolitaireGame g = new SolitaireGame();
// Top row
g.cardCollections[SolitaireGame.OFFSET_ACES].add(s2);
@@ -378,13 +378,13 @@
test("Playing - cannot flip", () {
// Wrong phase.
expect(() {
- SolitaireGame game = new SolitaireGame(0);
+ SolitaireGame game = new SolitaireGame();
game.gamelog.add(new SolitaireCommand.flip(3));
}, throwsA(new isInstanceOf<StateError>()));
// Bad index (low)
expect(() {
- SolitaireGame game = new SolitaireGame(0);
+ SolitaireGame game = new SolitaireGame();
// Deal first.
game.dealCardsUI();
@@ -398,7 +398,7 @@
// Bad index (high)
expect(() {
- SolitaireGame game = new SolitaireGame(0);
+ SolitaireGame game = new SolitaireGame();
// Deal first.
game.dealCardsUI();
@@ -412,7 +412,7 @@
// No down card to flip.
expect(() {
- SolitaireGame game = new SolitaireGame(0);
+ SolitaireGame game = new SolitaireGame();
// Deal first.
game.dealCardsUI();
@@ -427,7 +427,7 @@
// Up card is in the way.
expect(() {
- SolitaireGame game = new SolitaireGame(0);
+ SolitaireGame game = new SolitaireGame();
// Deal first.
game.dealCardsUI();
@@ -438,7 +438,7 @@
}, throwsA(new isInstanceOf<StateError>()));
// This scenario should work though.
- SolitaireGame game = new SolitaireGame(0);
+ SolitaireGame game = new SolitaireGame();
// Deal. Clear away pile 1. Flip pile 1.
game.dealCardsUI();
@@ -450,13 +450,13 @@
test("Playing - cannot draw", () {
// Wrong phase.
expect(() {
- SolitaireGame game = new SolitaireGame(0);
+ SolitaireGame game = new SolitaireGame();
game.gamelog.add(new SolitaireCommand.draw());
}, throwsA(new isInstanceOf<StateError>()));
// No draw cards remain.
expect(() {
- SolitaireGame game = new SolitaireGame(0);
+ SolitaireGame game = new SolitaireGame();
game.dealCardsUI();
// Remove all draw cards.
@@ -466,7 +466,7 @@
}, throwsA(new isInstanceOf<StateError>()));
// But it should be fine to draw just after dealing.
- SolitaireGame game = new SolitaireGame(0);
+ SolitaireGame game = new SolitaireGame();
// Deal first.
game.dealCardsUI();