croupier: Add Shortcut creation logic to Makefile

This adds a way to create shortcuts to the Makefile.
While this does belong in mojo.shared, this is here because of the
recent refactoring done there. The next CL will MultiPart the relevant
repositories.

mojo.shared's macro will need to take 5 variables:
* Target device ID Flag (optional)
* Shortcut Name (required)
* Shortcut Mojo Url (required)
* Shortcut Icon Url (optional)
* Shell command path (required)

https://github.com/vanadium/issues/issues/942

The Shortcut also requires some assets to be loaded onto a public and
secure location, like storage.googleapis.com. For now, I have manually
put a few of our binaries up there, though in the future we will also
want to version them.

https://github.com/vanadium/issues/issues/834

The related files are:
* Makefile
* shortcut_template
* .gitignore

=========================

The other files are actually part of a related change.

This other half modifies the Hearts and Croupier Game implementations.
A new signal called startGameSignal is called at the start of a game.
This allows Hearts to send a Ready command when the button is pressed.
This allows Deal to occur independently of the other players, since the
owner is known, and we can let them deal.
The tests were updated to correspond with this change.

Change-Id: I1acf494c45d7d3d9dda3acba815a3fe63a13e966
diff --git a/.gitignore b/.gitignore
index 21a40d5..2872723 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,3 +27,6 @@
 *.js_
 *.js.deps
 *.js.map
+
+# Don't commit the generated shortcut commands file.
+shortcut_commands
diff --git a/Makefile b/Makefile
index b9338ad..7ae15e9 100644
--- a/Makefile
+++ b/Makefile
@@ -42,7 +42,8 @@
 	# 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_FLAGS += --name=$(MOUNTTABLE)/$(NAME)
+	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 devservers
@@ -62,11 +63,11 @@
 export SYNCBASE_SERVER_URL := https://mojo.v.io/syncbase_server.mojo
 export DISCOVERY_SERVER_URL := https://mojo2.v.io/discovery.mojo
 MOJO_SHELL_FLAGS := --enable-multiprocess \
-	--map-origin="https://mojo2.v.io=$(DISCOVERY_MOJO_BIN_DIR)" --args-for="$(DISCOVERY_SERVER_URL) host$(ANDROID) mdns" \
+	--map-origin="https://mojo2.v.io=$(DISCOVERY_MOJO_BIN_DIR)" --args-for="$(DISCOVERY_SERVER_URL) host$(DEVICE_ID) mdns" \
 	--map-origin="https://mojo.v.io=$(SYNCBASE_MOJO_BIN_DIR)" --args-for="$(SYNCBASE_SERVER_URL) $(SYNCBASE_FLAGS)"
 
 ifdef ANDROID
-	MOJO_SHELL_FLAGS +=  --target-device $(DEVICE_ID)
+	TARGET_DEVICE_FLAG +=  --target-device $(DEVICE_ID)
 endif
 
 # Runs a sky app.
@@ -78,7 +79,8 @@
 	--mojo-path $(MOJO_DIR)/src \
 	--checked \
 	--mojo-debug \
-	-- $(MOJO_SHELL_FLAGS) \
+	-- $(TARGET_DEVICE_FLAG) \
+	$(MOJO_SHELL_FLAGS) \
 	$(REUSE_FLAG) \
 	--no-config-file
 endef
@@ -128,6 +130,38 @@
 endif
 	$(call RUN_SKY_APP,$<)
 
+CROUPIER_SHORTCUT_NAME := Croupier
+CROUPIER_URL := mojo://storage.googleapis.com/mojo_services/croupier/croupier.flx
+CROUPIER_URL_TO_ICON := https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png
+MOJO_SHELL_CMD_PATH := /data/local/tmp/org.chromium.mojo.shell.cmd
+
+# Creates a shortcut on the phone that runs the hosted version of croupier.flx
+# Does nothing if ANDROID is not defined.
+define GENERATE_SHORTCUT_FILE
+	sed -e "s;%DEVICE_ID%;$1;g" -e "s;%SYNCBASE_NAME_FLAG%;$2;g" \
+	shortcut_template > shortcut_commands
+endef
+
+shortcut: env-check
+ifdef ANDROID
+	# Create the shortcut file.
+	$(call GENERATE_SHORTCUT_FILE,$(DEVICE_ID),$(SYNCBASE_NAME_FLAG))
+
+	# TODO(alexfandrianto): Mojo Shell only allows a single default command. This may prove problematic.
+	adb -s $(DEVICE_ID) push -p shortcut_commands $(MOJO_SHELL_CMD_PATH)
+	adb -s $(DEVICE_ID) shell chmod 555 $(MOJO_SHELL_CMD_PATH)
+
+	# TODO(alexfandrianto): Put this in Mojo shared instead.
+	$(MOJO_DIR)/src/mojo/devtools/common/mojo_run --android $(TARGET_DEVICE_FLAG) "mojo:shortcut $(CROUPIER_SHORTCUT_NAME) $(CROUPIER_URL) $(CROUPIER_URL_TO_ICON)"
+endif
+
+# Removes the shortcut data from Mojo shell.
+# TODO(alexfandrianto): Can we remove the shortcut icon?
+shortcut-remove: env-check
+ifdef ANDROID
+	adb -s $(DEVICE_ID) shell rm -f $(MOJO_SHELL_CMD_PATH)
+endif
+
 .PHONY: mock
 mock:
 	mv lib/src/syncbase/log_writer.dart lib/src/syncbase/log_writer.dart.backup
diff --git a/lib/components/croupier.dart b/lib/components/croupier.dart
index 4ae72a6..183a180 100644
--- a/lib/components/croupier.dart
+++ b/lib/components/croupier.dart
@@ -149,10 +149,10 @@
         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)),
+              new FlatButton(child: new Text('Start Game'), onPressed: () {
+                makeSetStateCallback(logic_croupier.CroupierState.PlayGame)();
+                config.croupier.game.startGameSignal();
+              }),
               new Grid(profileWidgets, maxChildExtent: 150.0),
               new FlatButton(
                   child: new Text('Back'),
diff --git a/lib/components/hearts/hearts.part.dart b/lib/components/hearts/hearts.part.dart
index 1fcd74c..9fa4991 100644
--- a/lib/components/hearts/hearts.part.dart
+++ b/lib/components/hearts/hearts.part.dart
@@ -51,7 +51,9 @@
         width: config.width,
         height: config.height,
         child: heartsWidget));
-    if (game.phase != HeartsPhase.Deal && game.phase != HeartsPhase.Score) {
+    if (game.phase != HeartsPhase.StartGame &&
+        game.phase != HeartsPhase.Deal &&
+        game.phase != HeartsPhase.Score) {
       List<int> visibleCardCollections = new List<int>();
       int playerNum = game.playerNumber;
       if (game.viewType == HeartsType.Player) {
@@ -241,6 +243,7 @@
     }
 
     switch (game.phase) {
+      case HeartsPhase.StartGame:
       case HeartsPhase.Deal:
         return showDeal();
       case HeartsPhase.Pass:
@@ -261,6 +264,7 @@
     HeartsGame game = config.game as HeartsGame;
     List<Widget> kids = new List<Widget>();
     switch (game.phase) {
+      case HeartsPhase.StartGame:
       case HeartsPhase.Deal:
         kids.add(new Text("DEAL PHASE"));
         kids.add(_makeButton('Deal', game.dealCards));
@@ -384,7 +388,7 @@
         decoration: new BoxDecoration(backgroundColor: Colors.pink[500]),
         child: new Flex([
           new Text('Player ${game.playerNumber}'),
-          _makeButton('Deal', game.dealCards),
+          new Text('Waiting for Deal...'),
           _makeDebugButtons()
         ],
             direction: FlexDirection.vertical,
diff --git a/lib/logic/create_game.dart b/lib/logic/create_game.dart
index d92e848..94e7caa 100644
--- a/lib/logic/create_game.dart
+++ b/lib/logic/create_game.dart
@@ -7,14 +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, {int gameID}) {
+game_impl.Game createGame(game_impl.GameType gt, int pn,
+    {int gameID, bool isCreator}) {
   switch (gt) {
     case game_impl.GameType.Proto:
-      return new proto_impl.ProtoGame(pn, gameID: gameID);
+      return new proto_impl.ProtoGame(pn, gameID: gameID, isCreator: isCreator);
     case game_impl.GameType.Hearts:
-      return new hearts_impl.HeartsGame(pn, gameID: gameID);
+      return new hearts_impl.HeartsGame(pn,
+          gameID: gameID, isCreator: isCreator);
     case game_impl.GameType.Solitaire:
-      return new solitaire_impl.SolitaireGame(pn, gameID: gameID);
+      return new solitaire_impl.SolitaireGame(pn,
+          gameID: gameID, isCreator: isCreator);
     default:
       assert(false);
       return null;
diff --git a/lib/logic/croupier.dart b/lib/logic/croupier.dart
index eec18d6..559b367 100644
--- a/lib/logic/croupier.dart
+++ b/lib/logic/croupier.dart
@@ -112,7 +112,8 @@
 
         // data should be the game id here.
         GameType gt = data as GameType;
-        game = cg.createGame(gt, 0); // Start as player 0 of whatever game type.
+        game = cg.createGame(gt, 0,
+            isCreator: true); // Start as player 0 of whatever game type.
 
         _advertiseFuture = settings_manager
             .createGameSyncgroup(gameTypeToString(gt), game.gameID)
diff --git a/lib/logic/game/game_def.part.dart b/lib/logic/game/game_def.part.dart
index 886fb35..f969623 100644
--- a/lib/logic/game/game_def.part.dart
+++ b/lib/logic/game/game_def.part.dart
@@ -76,6 +76,7 @@
 abstract class Game {
   final GameType gameType;
   String get gameTypeName; // abstract
+  final bool isCreator;
 
   final List<List<Card>> cardCollections = new List<List<Card>>();
   final List<Card> deck = new List<Card>.from(Card.All);
@@ -97,8 +98,9 @@
   // A super constructor, don't call this unless you're a subclass.
   Game.create(
       this.gameType, this.gamelog, this._playerNumber, int numCollections,
-      {int gameID})
-      : gameID = gameID ?? new math.Random().nextInt(0x00FFFFFF) {
+      {int gameID, bool isCreator})
+      : gameID = gameID ?? new math.Random().nextInt(0x00FFFFFF),
+        isCreator = isCreator ?? false {
     print("The gameID is ${gameID}");
     gamelog.setGame(this);
     for (int i = 0; i < numCollections; i++) {
@@ -139,4 +141,5 @@
   // UNIMPLEMENTED
   void move(Card card, List<Card> dest);
   void triggerEvents();
+  void startGameSignal();
 }
diff --git a/lib/logic/hearts/hearts_command.part.dart b/lib/logic/hearts/hearts_command.part.dart
index 3037a93..350eb9a 100644
--- a/lib/logic/hearts/hearts_command.part.dart
+++ b/lib/logic/hearts/hearts_command.part.dart
@@ -16,7 +16,7 @@
   // The following constructors are used for the player generating the HeartsCommand.
   HeartsCommand.deal(int playerId, List<Card> cards)
       : super("Deal", computeDeal(playerId, cards),
-            simultaneity: SimulLevel.DEPENDENT);
+            simultaneity: SimulLevel.INDEPENDENT);
 
   HeartsCommand.pass(int senderId, List<Card> cards)
       : super("Pass", computePass(senderId, cards),
@@ -37,7 +37,7 @@
   static SimulLevel computeSimul(String phase) {
     switch (phase) {
       case "Deal":
-        return SimulLevel.DEPENDENT;
+        return SimulLevel.INDEPENDENT;
       case "Pass":
         return SimulLevel.INDEPENDENT;
       case "Take":
@@ -164,7 +164,8 @@
         if (game.hasGameEnded) {
           return false;
         }
-        if (game.phase != HeartsPhase.Score) {
+        if (game.phase != HeartsPhase.Score &&
+            game.phase != HeartsPhase.StartGame) {
           return false;
         }
         return true;
@@ -261,9 +262,10 @@
           throw new StateError(
               "Game has already ended. Start a new one to play again.");
         }
-        if (game.phase != HeartsPhase.Score) {
+        if (game.phase != HeartsPhase.Score &&
+            game.phase != HeartsPhase.StartGame) {
           throw new StateError(
-              "Cannot process ready commands when not in Score phase");
+              "Cannot process ready commands when not in Score or StartGame phase");
         }
         int playerId = int.parse(parts[0]);
         game.setReady(playerId);
diff --git a/lib/logic/hearts/hearts_game.part.dart b/lib/logic/hearts/hearts_game.part.dart
index 8b6535e..b2bb768 100644
--- a/lib/logic/hearts/hearts_game.part.dart
+++ b/lib/logic/hearts/hearts_game.part.dart
@@ -39,7 +39,7 @@
 
   HeartsType viewType = HeartsType.Player;
 
-  HeartsPhase _phase = HeartsPhase.Deal;
+  HeartsPhase _phase = HeartsPhase.StartGame;
   HeartsPhase get phase => _phase;
   void set phase(HeartsPhase other) {
     print('setting phase from ${_phase} to ${other}');
@@ -65,10 +65,11 @@
   List<int> scores = [0, 0, 0, 0];
   List<bool> ready;
 
-  HeartsGame(int playerNumber, {int gameID})
+  HeartsGame(int playerNumber, {int gameID, bool isCreator})
       : super.create(GameType.Hearts, new HeartsLog(), playerNumber, 16,
-            gameID: gameID) {
+            gameID: gameID, isCreator: isCreator) {
     resetGame();
+    unsetReady();
   }
 
   void resetGame() {
@@ -251,10 +252,15 @@
   // Note that this will be called by the UI.
   // It won't be possible to set the readiness for other players, except via the GameLog.
   void setReadyUI() {
-    assert(phase == HeartsPhase.Score);
+    assert(phase == HeartsPhase.Score || phase == HeartsPhase.StartGame);
     gamelog.add(new HeartsCommand.ready(playerNumber));
   }
 
+  @override
+  void startGameSignal() {
+    setReadyUI();
+  }
+
   // Note that this will be called by the UI.
   // TODO: Does this really need to be overridden? That seems like bad structure in GameComponent.
   // Overrides Game's move method with the "move" logic for Hearts. Used for drag-drop.
@@ -296,6 +302,18 @@
   @override
   void triggerEvents() {
     switch (this.phase) {
+      case HeartsPhase.StartGame:
+        if (this.allReady) {
+          phase = HeartsPhase.Deal;
+          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:
         if (this.allDealt) {
           if (this.passTarget != null) {
@@ -354,6 +372,11 @@
           this.roundNumber++;
           phase = HeartsPhase.Deal;
           this.resetGame();
+
+          // Only the creator should deal the cards once everyone is ready.
+          if (this.isCreator) {
+            this.dealCards();
+          }
         }
         return;
       default:
diff --git a/lib/logic/hearts/hearts_phase.part.dart b/lib/logic/hearts/hearts_phase.part.dart
index 55894f5..42b25f3 100644
--- a/lib/logic/hearts/hearts_phase.part.dart
+++ b/lib/logic/hearts/hearts_phase.part.dart
@@ -4,4 +4,4 @@
 
 part of hearts;
 
-enum HeartsPhase { Deal, Pass, Take, Play, Score }
+enum HeartsPhase { StartGame, Deal, Pass, Take, Play, Score }
diff --git a/lib/logic/proto/proto_game.part.dart b/lib/logic/proto/proto_game.part.dart
index 28ace55..623d062 100644
--- a/lib/logic/proto/proto_game.part.dart
+++ b/lib/logic/proto/proto_game.part.dart
@@ -8,9 +8,9 @@
   @override
   String get gameTypeName => "Proto";
 
-  ProtoGame(int playerNumber, {int gameID})
+  ProtoGame(int playerNumber, {int gameID, bool isCreator})
       : super.create(GameType.Proto, new ProtoLog(), playerNumber, 6,
-            gameID: gameID) {
+            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
 
@@ -50,4 +50,7 @@
 
   @override
   void triggerEvents() {}
+
+  @override
+  void startGameSignal() {}
 }
diff --git a/lib/logic/solitaire/solitaire_game.part.dart b/lib/logic/solitaire/solitaire_game.part.dart
index e84f04c..dd0e31a 100644
--- a/lib/logic/solitaire/solitaire_game.part.dart
+++ b/lib/logic/solitaire/solitaire_game.part.dart
@@ -26,10 +26,10 @@
     _phase = other;
   }
 
-  SolitaireGame(int playerNumber, {int gameID})
+  SolitaireGame(int playerNumber, {int gameID, bool isCreator})
       : super.create(
             GameType.Solitaire, new SolitaireLog(), playerNumber, NUM_PILES,
-            gameID: gameID) {
+            gameID: gameID, isCreator: isCreator) {
     resetGame();
   }
 
@@ -341,4 +341,8 @@
 
   // 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() {}
 }
diff --git a/pubspec.lock b/pubspec.lock
index 628c31c..226e558 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -214,11 +214,11 @@
   syncbase:
     description: syncbase
     source: hosted
-    version: "0.0.15"
+    version: "0.0.16"
   test:
     description: test
     source: hosted
-    version: "0.12.5+2"
+    version: "0.12.6"
   typed_data:
     description: typed_data
     source: hosted
diff --git a/shortcut_template b/shortcut_template
new file mode 100644
index 0000000..ea6b0b3
--- /dev/null
+++ b/shortcut_template
@@ -0,0 +1,10 @@
+--map-origin=http://flutter/=https://storage.googleapis.com/mojo/sky/shell/android-arm/fed101feb7db8790c2818798271d52957fbfd087/
+--url-mappings=mojo:flutter=http://flutter/flutter.mojo
+--debug
+--verbose
+--args-for=mojo:flutter --enable-checked-mode
+--enable-multiprocess
+--map-origin=https://mojo2.v.io=https://storage.googleapis.com/mojo_services/v23discovery/android/
+--args-for=https://mojo2.v.io/discovery.mojo host%DEVICE_ID% mdns
+--map-origin=https://mojo.v.io=https://storage.googleapis.com/mojo_services/syncbase/android/
+--args-for=https://mojo.v.io/syncbase_server.mojo --v=0 --logtostderr=true --root-dir=/data/data/org.chromium.mojo.shell/app_home/syncbase_data --v23.credentials=/sdcard/v23creds --v23.proxy=proxy %SYNCBASE_NAME_FLAG%
diff --git a/test/game_log_hearts_test.txt b/test/game_log_hearts_test.txt
index f2467fe..ec9205f 100644
--- a/test/game_log_hearts_test.txt
+++ b/test/game_log_hearts_test.txt
@@ -1,3 +1,9 @@
+# Start Game
+Ready|2:END
+Ready|0:END
+Ready|3:END
+Ready|1:END
+
 # Deal
 Deal|0:classic h1:classic h2:classic h3:classic h4:classic h5:classic d6:classic d7:classic d8:classic d9:classic d10:classic dj:classic dq:classic dk:END
 Deal|1:classic d1:classic d2:classic d3:classic d4:classic d5:classic s6:classic s7:classic s8:classic s9:classic s10:classic sj:classic sq:classic sk:END
@@ -505,4 +511,4 @@
 Play|2:classic hk:END
 Play|3:classic sk:END
 
-# Game is over!
\ No newline at end of file
+# Game is over!
diff --git a/test/hearts_test.dart b/test/hearts_test.dart
index ec501be..1b92fd4 100644
--- a/test/hearts_test.dart
+++ b/test/hearts_test.dart
@@ -11,6 +11,7 @@
 void main() {
   group("Initialization", () {
     HeartsGame game = new HeartsGame(0);
+    game.phase = HeartsPhase.Deal;
     test("Dealing", () {
       game.dealCards(); // What the dealer actually runs to get cards to everybody.
 
@@ -195,6 +196,16 @@
       }
     }
 
+    test("Start Game Phase", () {
+      expect(game.phase, equals(HeartsPhase.StartGame));
+
+      // Start Game consists of 4 ready commands.
+      runCommand();
+      runCommand();
+      runCommand();
+      runCommand();
+    });
+
     test("Deal Phase", () {
       expect(game.phase, equals(HeartsPhase.Deal));
 
@@ -444,29 +455,41 @@
   });
 
   group("Card Manipulation - Error Cases", () {
-    test("Dealing - wrong phase", () {
+    test("Dealing - too soon", () {
+      HeartsGame game = new HeartsGame(0);
+      expect(game.phase, HeartsPhase.StartGame);
       expect(() {
-        HeartsGame game = new HeartsGame(0);
-        game.phase = HeartsPhase.Score;
+        game.gamelog.add(new HeartsCommand.deal(
+            0, new List<Card>.from(Card.All.getRange(0, 13))));
+      }, throwsA(new isInstanceOf<StateError>()));
+    });
+    test("Dealing - wrong phase", () {
+      HeartsGame game = new HeartsGame(0);
+      game.phase = HeartsPhase.Score;
+      expect(() {
         game.gamelog.add(new HeartsCommand.deal(
             0, new List<Card>.from(Card.All.getRange(0, 13))));
       }, throwsA(new isInstanceOf<StateError>()));
     });
     test("Dealing - missing card", () {
+      HeartsGame game = new HeartsGame(0);
+      game.phase = HeartsPhase.Deal;
       expect(() {
-        HeartsGame game = new HeartsGame(0);
         game.gamelog.add(
             new HeartsCommand.deal(0, <Card>[new Card("fake", "not real")]));
       }, throwsA(new isInstanceOf<StateError>()));
     });
     test("Dealing - too many cards dealt", () {
+      HeartsGame game = new HeartsGame(0);
+      game.phase = HeartsPhase.Deal;
       expect(() {
-        HeartsGame game = new HeartsGame(0);
         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.phase = HeartsPhase.Deal;
       expect(() {
-        HeartsGame game = new HeartsGame(0);
         game.gamelog.add(new HeartsCommand.deal(
             0, new List<Card>.from(Card.All.getRange(0, 5))));
         game.gamelog.add(new HeartsCommand.deal(
@@ -474,147 +497,160 @@
       }, throwsA(new isInstanceOf<StateError>()));
     });
     test("Passing - wrong phase", () {
+      HeartsGame game = new HeartsGame(0);
+      game.phase = HeartsPhase.Deal;
+      game.gamelog.add(new HeartsCommand.deal(
+          0, new List<Card>.from(Card.All.getRange(0, 13))));
       expect(() {
-        HeartsGame game = new HeartsGame(0);
-        game.gamelog.add(new HeartsCommand.deal(
-            0, new List<Card>.from(Card.All.getRange(0, 13))));
         game.gamelog.add(new HeartsCommand.pass(
             0, new List<Card>.from(Card.All.getRange(0, 4))));
       }, throwsA(new isInstanceOf<StateError>()));
     });
     test("Passing - missing card", () {
+      HeartsGame game = new HeartsGame(0);
+      game.phase = HeartsPhase.Deal;
+      game.gamelog.add(new HeartsCommand.deal(
+          0, new List<Card>.from(Card.All.getRange(0, 13))));
+      game.phase = HeartsPhase.Pass;
       expect(() {
-        HeartsGame game = new HeartsGame(0);
-        game.gamelog.add(new HeartsCommand.deal(
-            0, new List<Card>.from(Card.All.getRange(0, 13))));
-        game.phase = HeartsPhase.Pass;
         game.gamelog.add(new HeartsCommand.pass(
             0, new List<Card>.from(Card.All.getRange(13, 16))));
       }, throwsA(new isInstanceOf<StateError>()));
     });
     test("Passing - wrong number of cards", () {
+      HeartsGame game = new HeartsGame(0);
+      game.phase = HeartsPhase.Deal;
+      game.gamelog.add(new HeartsCommand.deal(
+          0, new List<Card>.from(Card.All.getRange(0, 13))));
+      game.phase = HeartsPhase.Pass;
       expect(() {
-        HeartsGame game = new HeartsGame(0);
-        game.gamelog.add(new HeartsCommand.deal(
-            0, new List<Card>.from(Card.All.getRange(0, 13))));
-        game.phase = HeartsPhase.Pass;
         game.gamelog.add(new HeartsCommand.pass(
             0, new List<Card>.from(Card.All.getRange(0, 2))));
       }, throwsA(new isInstanceOf<StateError>()));
+
+      game = new HeartsGame(0);
+      game.phase = HeartsPhase.Deal;
+      game.gamelog.add(new HeartsCommand.deal(
+          0, new List<Card>.from(Card.All.getRange(0, 13))));
+      game.phase = HeartsPhase.Pass;
       expect(() {
-        HeartsGame game = new HeartsGame(0);
-        game.gamelog.add(new HeartsCommand.deal(
-            0, new List<Card>.from(Card.All.getRange(0, 13))));
-        game.phase = HeartsPhase.Pass;
         game.gamelog.add(new HeartsCommand.pass(
             0, new List<Card>.from(Card.All.getRange(0, 4))));
       }, throwsA(new isInstanceOf<StateError>()));
     });
     test("Taking - wrong phase", () {
+      HeartsGame game = new HeartsGame(0);
+      game.phase = HeartsPhase.Deal;
       expect(() {
-        HeartsGame game = new HeartsGame(0);
         game.gamelog.add(new HeartsCommand.take(3));
       }, throwsA(new isInstanceOf<StateError>()));
     });
     test("Playing - wrong phase", () {
+      HeartsGame game = new HeartsGame(0);
+      game.phase = HeartsPhase.Deal;
+      game.gamelog.add(new HeartsCommand.deal(
+          0, new List<Card>.from(Card.All.getRange(0, 13))));
       expect(() {
-        HeartsGame game = new HeartsGame(0);
-        game.gamelog.add(new HeartsCommand.deal(
-            0, new List<Card>.from(Card.All.getRange(0, 13))));
         game.gamelog.add(new HeartsCommand.play(0, Card.All[0]));
       }, throwsA(new isInstanceOf<StateError>()));
     });
     test("Playing - missing card", () {
+      HeartsGame game = new HeartsGame(0);
+      game.phase = HeartsPhase.Deal;
+      game.gamelog.add(new HeartsCommand.deal(
+          0, new List<Card>.from(Card.All.getRange(0, 13))));
+      game.phase = HeartsPhase.Play;
       expect(() {
-        HeartsGame game = new HeartsGame(0);
-        game.gamelog.add(new HeartsCommand.deal(
-            0, new List<Card>.from(Card.All.getRange(0, 13))));
-        game.phase = HeartsPhase.Play;
         game.gamelog.add(new HeartsCommand.play(0, Card.All[13]));
       }, throwsA(new isInstanceOf<StateError>()));
     });
     test("Playing - invalid card (not 2 of clubs as first card)", () {
+      HeartsGame game = new HeartsGame(0);
+      game.phase = HeartsPhase.Deal;
+      game.gamelog.add(new HeartsCommand.deal(
+          0, new List<Card>.from(Card.All.getRange(0, 13))));
+      game.phase = HeartsPhase.Play;
+      game.lastTrickTaker = 0;
       expect(() {
-        HeartsGame game = new HeartsGame(0);
-        game.gamelog.add(new HeartsCommand.deal(
-            0, new List<Card>.from(Card.All.getRange(0, 13))));
-        game.phase = HeartsPhase.Play;
-        game.lastTrickTaker = 0;
         game.gamelog.add(new HeartsCommand.play(0, Card.All[0]));
       }, throwsA(new isInstanceOf<StateError>()));
     });
     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);
+      game.phase = HeartsPhase.Deal;
+      game.gamelog.add(new HeartsCommand.deal(
+          0, new List<Card>.from(Card.All.getRange(0, 13))));
+      game.gamelog.add(new HeartsCommand.deal(
+          1, new List<Card>.from(Card.All.getRange(13, 26))));
+      game.gamelog.add(new HeartsCommand.deal(
+          2, new List<Card>.from(Card.All.getRange(26, 39))));
+      game.gamelog.add(new HeartsCommand.deal(
+          3, new List<Card>.from(Card.All.getRange(39, 52))));
+      game.phase = HeartsPhase.Play;
+      game.lastTrickTaker = 0;
+      game.gamelog.add(new HeartsCommand.play(0, Card.All[1]));
+      game.gamelog.add(new HeartsCommand.play(1, Card.All[13]));
       expect(() {
-        HeartsGame game = new HeartsGame(0);
-        game.gamelog.add(new HeartsCommand.deal(
-            0, new List<Card>.from(Card.All.getRange(0, 13))));
-        game.gamelog.add(new HeartsCommand.deal(
-            1, new List<Card>.from(Card.All.getRange(13, 26))));
-        game.gamelog.add(new HeartsCommand.deal(
-            2, new List<Card>.from(Card.All.getRange(26, 39))));
-        game.gamelog.add(new HeartsCommand.deal(
-            3, new List<Card>.from(Card.All.getRange(39, 52))));
-        game.phase = HeartsPhase.Play;
-        game.lastTrickTaker = 0;
-        game.gamelog.add(new HeartsCommand.play(0, Card.All[1]));
-        game.gamelog.add(new HeartsCommand.play(1, Card.All[13]));
         game.gamelog.add(new HeartsCommand.play(2, Card.All[26]));
       }, throwsA(new isInstanceOf<StateError>()));
     });
     test("Playing - wrong turn", () {
+      HeartsGame game = new HeartsGame(0);
+      game.phase = HeartsPhase.Deal;
+      game.gamelog.add(new HeartsCommand.deal(
+          0, new List<Card>.from(Card.All.getRange(0, 13))));
+      game.gamelog.add(new HeartsCommand.deal(
+          1, new List<Card>.from(Card.All.getRange(13, 26))));
+      game.gamelog.add(new HeartsCommand.deal(
+          2, new List<Card>.from(Card.All.getRange(26, 39))));
+      game.gamelog.add(new HeartsCommand.deal(
+          3, new List<Card>.from(Card.All.getRange(39, 52))));
+      game.phase = HeartsPhase.Play;
+      game.lastTrickTaker = 0;
       expect(() {
-        HeartsGame game = new HeartsGame(0);
-        game.gamelog.add(new HeartsCommand.deal(
-            0, new List<Card>.from(Card.All.getRange(0, 13))));
-        game.gamelog.add(new HeartsCommand.deal(
-            1, new List<Card>.from(Card.All.getRange(13, 26))));
-        game.gamelog.add(new HeartsCommand.deal(
-            2, new List<Card>.from(Card.All.getRange(26, 39))));
-        game.gamelog.add(new HeartsCommand.deal(
-            3, new List<Card>.from(Card.All.getRange(39, 52))));
-        game.phase = HeartsPhase.Play;
-        game.lastTrickTaker = 0;
         game.gamelog.add(new HeartsCommand.play(
             1, Card.All[13])); // player 0's turn, not player 1's.
       }, throwsA(new isInstanceOf<StateError>()));
     });
     test("Playing - invalid card (suit mismatch)", () {
+      HeartsGame game = new HeartsGame(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])));
+      game.gamelog.add(new HeartsCommand.deal(
+          1, new List<Card>.from(Card.All.getRange(12, 25))));
+      game.gamelog.add(new HeartsCommand.deal(
+          2, new List<Card>.from(Card.All.getRange(26, 39))));
+      game.gamelog.add(new HeartsCommand.deal(
+          3, new List<Card>.from(Card.All.getRange(39, 52))));
+      game.phase = HeartsPhase.Play;
+      game.lastTrickTaker = 0;
+      game.gamelog.add(new HeartsCommand.play(0, Card.All[1]));
       expect(() {
-        HeartsGame game = new HeartsGame(0);
-        game.gamelog.add(new HeartsCommand.deal(0,
-            new List<Card>.from(Card.All.getRange(0, 12))..add(Card.All[25])));
-        game.gamelog.add(new HeartsCommand.deal(
-            1, new List<Card>.from(Card.All.getRange(12, 25))));
-        game.gamelog.add(new HeartsCommand.deal(
-            2, new List<Card>.from(Card.All.getRange(26, 39))));
-        game.gamelog.add(new HeartsCommand.deal(
-            3, new List<Card>.from(Card.All.getRange(39, 52))));
-        game.phase = HeartsPhase.Play;
-        game.lastTrickTaker = 0;
-        game.gamelog.add(new HeartsCommand.play(0, Card.All[1]));
         game.gamelog
             .add(new HeartsCommand.play(0, Card.All[13])); // should play 12
       }, throwsA(new isInstanceOf<StateError>()));
     });
     test("Playing - invalid card (hearts not broken yet)", () {
+      HeartsGame game = new HeartsGame(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])));
+      game.gamelog.add(new HeartsCommand.deal(
+          1, new List<Card>.from(Card.All.getRange(13, 26))));
+      game.gamelog.add(new HeartsCommand.deal(2,
+          new List<Card>.from(Card.All.getRange(26, 38))..add(Card.All[12])));
+      game.gamelog.add(new HeartsCommand.deal(
+          3, new List<Card>.from(Card.All.getRange(39, 52))));
+      game.phase = HeartsPhase.Play;
+      game.lastTrickTaker = 0;
+      game.gamelog.add(new HeartsCommand.play(0, Card.All[1]));
+      game.gamelog.add(new HeartsCommand.play(1, Card.All[13]));
+      game.gamelog.add(new HeartsCommand.play(2, Card.All[12])); // 2 won!
+      game.gamelog.add(new HeartsCommand.play(3, Card.All[39]));
       expect(() {
-        HeartsGame game = new HeartsGame(0);
-        game.gamelog.add(new HeartsCommand.deal(0,
-            new List<Card>.from(Card.All.getRange(0, 12))..add(Card.All[38])));
-        game.gamelog.add(new HeartsCommand.deal(
-            1, new List<Card>.from(Card.All.getRange(13, 26))));
-        game.gamelog.add(new HeartsCommand.deal(2,
-            new List<Card>.from(Card.All.getRange(26, 38))..add(Card.All[12])));
-        game.gamelog.add(new HeartsCommand.deal(
-            3, new List<Card>.from(Card.All.getRange(39, 52))));
-        game.phase = HeartsPhase.Play;
-        game.lastTrickTaker = 0;
-        game.gamelog.add(new HeartsCommand.play(0, Card.All[1]));
-        game.gamelog.add(new HeartsCommand.play(1, Card.All[13]));
-        game.gamelog.add(new HeartsCommand.play(2, Card.All[12])); // 2 won!
-        game.gamelog.add(new HeartsCommand.play(3, Card.All[39]));
         game.gamelog.add(new HeartsCommand.play(
             2, Card.All[26])); // But 2 can't lead with a hearts.
       }, throwsA(new isInstanceOf<StateError>()));