croupier: Add SyncbaseEcho

Modified the Makefile and Croupier organization slightly to add a new
'game' called SyncbaseEcho.

SyncbaseEcho and SyncbaseEchoImpl essentially do what the original Sky
example in roadmap/mojo/syncbase does.
Note: These had to be split up, or else make test wouldn't work anymore.
Some of the embedder code won't run in a stand-alone VM.

To run with mojo_shell use `make start-with-mojo` instead of the usual
`make start` or `make install`. It also doesn't run on Android at the moment.

Improved the Makefile too, by making things PHONY-er.

Change-Id: I7e7a3d921e02b587b1d7047633fbcc84b1094892
diff --git a/Makefile b/Makefile
index 6bcebad..cbd61ea 100644
--- a/Makefile
+++ b/Makefile
@@ -1,29 +1,93 @@
+# This beginning section is used to setup the environment for running with mojo_shell.
+ETHER_DIR := $(V23_ROOT)/roadmap/mojo/syncbase
+CROUPIER_DIR := $(shell pwd)
+SHELL := /bin/bash -euo pipefail
+
+ifdef ANDROID
+	MOJO_ANDROID_FLAGS := --android
+
+	MOJO_BUILD_DIR := $(MOJO_DIR)/src/out/android_Debug
+	SKY_BUILD_DIR := $(SKY_DIR)/src/out/android_Debug
+	ETHER_BUILD_DIR := $(ETHER_DIR)/gen/mojo/android
+
+	SYNCBASE_DATA_DIR := /data/data/org.chromium.mojo.shell/app_home/syncbase_data
+else
+	MOJO_BUILD_DIR := $(MOJO_DIR)/src/out/Debug
+	SKY_BUILD_DIR := $(SKY_DIR)/src/out/Debug
+	ETHER_BUILD_DIR := $(ETHER_DIR)/gen/mojo/linux_amd64
+
+	SYNCBASE_DATA_DIR := /tmp/syncbase_data
+endif
+
+# NOTE(nlacasse): Running Go Mojo services requires passing the
+# --enable-multiprocess flag to mojo_shell.  This is because the Go runtime is
+# very large, and can interfere with C++ memory if they are in the same
+# process.
+MOJO_SHELL_FLAGS := -v --enable-multiprocess \
+	--config-alias MOJO_BUILD_DIR=$(MOJO_BUILD_DIR) \
+	--config-alias SKY_DIR=$(SKY_DIR) \
+	--config-alias SKY_BUILD_DIR=$(SKY_BUILD_DIR) \
+	--config-alias ETHER_DIR=$(ETHER_DIR) \
+	--config-alias ETHER_BUILD_DIR=$(ETHER_BUILD_DIR) \
+	--config-alias CROUPIER_DIR=$(CROUPIER_DIR)
+
+
+.DELETE_ON_ERROR:
+
 # Get the packages used by the dart project, according to pubspec.yaml
 # Can also use `pub get`, but Sublime occasionally reverts me to an ealier version.
 # Only `pub upgrade` can escape such a thing.
-get-packages: pubspec.yaml
+packages: pubspec.yaml
 	pub upgrade
 
-TEST_FILES := $(shell find test -name *.dart ! -name *.part.dart)
+DART_LIB_FILES := $(shell find lib -name *.dart ! -name *.part.dart)
+DART_TEST_FILES := $(shell find test -name *.dart ! -name *.part.dart)
 
-check-fmt:
-	dartfmt -n lib/main.dart $(TEST_FILES)
+.PHONY: dartfmt
+dartfmt:
+	dartfmt -w $(DART_LIB_FILES) $(DART_TEST_FILES)
 
+.PHONY: lint
 lint:
 	dartanalyzer lib/main.dart
-	dartanalyzer $(TEST_FILES)
+	dartanalyzer $(DART_TEST_FILES)
 
+.PHONY: start
 start:
 	./packages/sky/sky_tool start
 
-install: get-packages
+.PHONY: install
+install: packages
 	./packages/sky/sky_tool start --install
 
-# Could use `pub run test` too, but I like seeing every assertion print out.
-test:
-	dart --checked $(TEST_FILES)
+.PHONY: env-check
+env-check:
+ifndef MOJO_DIR
+	$(error MOJO_DIR is not set)
+endif
+ifndef SKY_DIR
+	$(error SKY_DIR is not set)
+endif
+ifndef V23_ROOT
+	$(error V23_ROOT is not set)
+endif
+ifeq ($(wildcard $(MOJO_BUILD_DIR)),)
+	$(error ERROR: $(MOJO_BUILD_DIR) does not exist.  Please see README.md for instructions on compiling Mojo resources.)
+endif
 
+# Run the Sky program with mojo shell. This allows use of Syncbase and Mojo.
+# If syncbase doesn't load, it could be that port 4002 is still in use; try fuser 4002/tcp.
+.PHONY: start-with-mojo
+start-with-mojo: env-check packages
+	$(MOJO_DIR)/src/mojo/devtools/common/mojo_run --config-file $(PWD)/mojoconfig $(MOJO_SHELL_FLAGS) $(MOJO_ANDROID_FLAGS) 'mojo:window_manager https://croupier.v.io/lib/main.dart'
+
+# Could use `pub run test` too, but I like seeing every assertion print out.
+# TODO(alexfandrianto): I split off the syncbase logic from game.dart because it
+# would not run in a stand-alone VM. We will need to add mojo_test eventually.
+.PHONY: test
+test: packages
+	dart --checked $(DART_TEST_FILES)
+
+.PHONY: clean
 clean:
 	rm -rf packages
-
-.PHONY: check-fmt lint start install test clean
\ No newline at end of file
diff --git a/lib/components/board.dart b/lib/components/board.dart
index 8a5aaeb..2e1ea9a 100644
--- a/lib/components/board.dart
+++ b/lib/components/board.dart
@@ -22,27 +22,30 @@
       switch (posMod) {
         case 0:
           widgetsList.add(new widgets.Transform(
-              transform: new vector_math.Matrix4.identity().rotateZ(math.PI).translate(0.0, -cardHeight / 2),
-              child: new Card(logic_card.Card.All[cards[i]], true)
-          ));
+              transform: new vector_math.Matrix4.identity()
+                  .rotateZ(math.PI)
+                  .translate(0.0, -cardHeight / 2),
+              child: new Card(logic_card.Card.All[cards[i]], true)));
           break;
         case 1:
           widgetsList.add(new widgets.Transform(
-              transform: new vector_math.Matrix4.identity().rotateZ(math.PI/2.0).translate(0.0, cardWidth/2),
-              child: new Card(logic_card.Card.All[cards[i]], true)
-          ));
+              transform: new vector_math.Matrix4.identity()
+                  .rotateZ(math.PI / 2.0)
+                  .translate(0.0, cardWidth / 2),
+              child: new Card(logic_card.Card.All[cards[i]], true)));
           break;
         case 2:
           widgetsList.add(new widgets.Transform(
-              transform: new vector_math.Matrix4.identity().translate(-cardWidth, cardWidth / 2),
-              child: new Card(logic_card.Card.All[cards[i]], true)
-          ));
+              transform: new vector_math.Matrix4.identity().translate(
+                  -cardWidth, cardWidth / 2),
+              child: new Card(logic_card.Card.All[cards[i]], true)));
           break;
         case 3:
           widgetsList.add(new widgets.Transform(
-              transform: new vector_math.Matrix4.identity().rotateZ(math.PI/2.0).translate(0.0, -cardHeight/2),
-              child: new Card(logic_card.Card.All[cards[i]], true)
-          ));
+              transform: new vector_math.Matrix4.identity()
+                  .rotateZ(math.PI / 2.0)
+                  .translate(0.0, -cardHeight / 2),
+              child: new Card(logic_card.Card.All[cards[i]], true)));
           break;
       }
     }
@@ -57,9 +60,10 @@
   widgets.Widget build() {
     List<widgets.Positioned> cards = [];
     for (int i = 0; i < count; i++) {
-      cards.add(new widgets.Positioned(child: new Card(logic_card.Card.All[0], false),
-        top: 0.0,
-        left: cardWidth*i/2.0));
+      cards.add(new widgets.Positioned(
+          child: new Card(logic_card.Card.All[0], false),
+          top: 0.0,
+          left: cardWidth * i / 2.0));
     }
     return new widgets.Stack(cards);
   }
@@ -69,46 +73,42 @@
   CardCluster centerCluster;
   List<PlayerHand> hands; // counts of cards in players hands, in clockwise order
 
-  Board(int firstCardPlayedPosition, List<int> cards, List<int> playerHandCount) :
-    centerCluster = new CardCluster(firstCardPlayedPosition, cards) {
-      assert(playerHandCount.length == 4);
-      hands = new List<PlayerHand>();
-      for (int count in playerHandCount) {
-        hands.add(new PlayerHand(count));
-      }
+  Board(int firstCardPlayedPosition, List<int> cards, List<int> playerHandCount)
+      : centerCluster = new CardCluster(firstCardPlayedPosition, cards) {
+    assert(playerHandCount.length == 4);
+    hands = new List<PlayerHand>();
+    for (int count in playerHandCount) {
+      hands.add(new PlayerHand(count));
+    }
   }
 
   widgets.Widget build() {
     return new widgets.Container(
-      decoration: new widgets.BoxDecoration(backgroundColor: colors.Pink[500]),
-      child: new widgets.Stack(
-        [
-          new widgets.Positioned(child: hands[0],
-            top: 0.0,
-            left: 250.0),
-          new widgets.Positioned(child: new widgets.Transform(
-              transform: new vector_math.Matrix4.identity().rotateZ(math.PI/2.0),
-              child: hands[1]
-              ),
-            left: 100.0,
-            top: 400.0),
-          new widgets.Positioned(child: new widgets.Transform(
+        decoration: new widgets.BoxDecoration(
+            backgroundColor: colors.Pink[500]),
+        child: new widgets.Stack([
+      new widgets.Positioned(child: hands[0], top: 0.0, left: 250.0),
+      new widgets.Positioned(
+          child: new widgets.Transform(
+              transform: new vector_math.Matrix4.identity()
+                  .rotateZ(math.PI / 2.0),
+              child: hands[1]),
+          left: 100.0,
+          top: 400.0),
+      new widgets.Positioned(
+          child: new widgets.Transform(
               transform: new vector_math.Matrix4.identity().rotateZ(math.PI),
-              child: hands[2]
-            ),
-            top: 820.0,
-            left: 350.0),
-          new widgets.Positioned(child: new widgets.Transform(
-              transform: new vector_math.Matrix4.identity().rotateZ(math.PI/2.0),
-              child: hands[3]
-            ),
-            left: 500.0,
-            top: 400.0),
-          new widgets.Positioned(child: centerCluster,
-            top: 400.0,
-            left: 300.0),
-        ]
-      )
-    );
+              child: hands[2]),
+          top: 820.0,
+          left: 350.0),
+      new widgets.Positioned(
+          child: new widgets.Transform(
+              transform: new vector_math.Matrix4.identity()
+                  .rotateZ(math.PI / 2.0),
+              child: hands[3]),
+          left: 500.0,
+          top: 400.0),
+      new widgets.Positioned(child: centerCluster, top: 400.0, left: 300.0),
+    ]));
   }
 }
diff --git a/lib/components/card.dart b/lib/components/card.dart
index 9554583..5f7ee4b 100644
--- a/lib/components/card.dart
+++ b/lib/components/card.dart
@@ -8,16 +8,15 @@
   Card(this.card, this.faceUp);
 
   widgets.Widget build() {
-    return new widgets.Listener(
-      child: imageFromCard(card, faceUp)
-    );
+    return new widgets.Listener(child: imageFromCard(card, faceUp));
   }
 
   static widgets.Widget imageFromCard(logic_card.Card c, bool faceUp) {
     // TODO(alexfandrianto): If we allow an optional prefix in front of this,
     // we would be able to have multiple skins of the same deck.
     // TODO(alexfandrianto): Better card organization?
-    String imageName = "${c.deck}/${faceUp ? 'up' : 'down'}/${c.identifier}.png";
+    String imageName =
+        "${c.deck}/${faceUp ? 'up' : 'down'}/${c.identifier}.png";
     return new widgets.NetworkImage(src: imageName);
   }
 }
diff --git a/lib/components/card_collection.dart b/lib/components/card_collection.dart
index 0058cce..3ad62f8 100644
--- a/lib/components/card_collection.dart
+++ b/lib/components/card_collection.dart
@@ -5,9 +5,7 @@
 import 'package:sky/widgets.dart' show DragTarget;
 import 'package:sky/theme/colors.dart' as colors;
 
-enum Orientation {
-  vert, horz, fan, show1
-}
+enum Orientation { vert, horz, fan, show1 }
 
 class CardCollectionComponent extends StatefulComponent {
   List<logic_card.Card> cards;
@@ -17,7 +15,8 @@
 
   String status = 'bar';
 
-  CardCollectionComponent(this.cards, this.faceUp, this.orientation, this.parentCallback);
+  CardCollectionComponent(
+      this.cards, this.faceUp, this.orientation, this.parentCallback);
 
   void syncConstructorArguments(CardCollectionComponent other) {
     cards = other.cards;
@@ -35,19 +34,21 @@
 
   List<Widget> flexCards(List<Widget> cardWidgets) {
     List<Widget> flexWidgets = new List<Widget>();
-    cardWidgets.forEach((cardWidget) => flexWidgets.add(new Flexible(child: cardWidget)));
+    cardWidgets.forEach(
+        (cardWidget) => flexWidgets.add(new Flexible(child: cardWidget)));
     return flexWidgets;
   }
 
   Widget wrapCards(List<Widget> cardWidgets) {
     switch (this.orientation) {
       case Orientation.vert:
-        return new Flex(flexCards(cardWidgets), direction: FlexDirection.vertical);
+        return new Flex(flexCards(cardWidgets),
+            direction: FlexDirection.vertical);
       case Orientation.horz:
         return new Flex(flexCards(cardWidgets));
       case Orientation.fan:
-        // unimplemented, so we'll fall through to show1, for now.
-        // Probably a Stack + Positioned
+      // unimplemented, so we'll fall through to show1, for now.
+      // Probably a Stack + Positioned
       case Orientation.show1:
         return new Stack(cardWidgets);
       default:
@@ -60,30 +61,28 @@
     List<Widget> cardComponents = new List<Widget>();
     cardComponents.add(new Text(status));
     for (int i = 0; i < cards.length; i++) {
-      cardComponents.add(new Draggable<Card>(new Card(cards[i], faceUp))); // flex
+      cardComponents
+          .add(new Draggable<Card>(new Card(cards[i], faceUp))); // flex
     }
 
     // Let's draw a stack of cards with DragTargets.
     // TODO(alexfandrianto): In many cases, card collections shouldn't have draggable cards.
     // Additionally, it may be worthwhile to restrict it to 1 at a time.
     return new DragTarget<Card>(
-      onAccept: _handleAccept,
-      builder: (List<Card> data, _) {
-        print(this.cards.length);
-        print(data);
-        return new Container(
+        onAccept: _handleAccept, builder: (List<Card> data, _) {
+      print(this.cards.length);
+      print(data);
+      return new Container(
           decoration: new BoxDecoration(
-            border: new Border.all(
-              width: 3.0,
-              color: data.isEmpty ? colors.white : colors.Blue[500]
-            ),
-            backgroundColor: data.isEmpty ? colors.Grey[500] : colors.Green[500]
-          ),
+              border: new Border.all(
+                  width: 3.0,
+                  color: data.isEmpty ? colors.white : colors.Blue[500]),
+              backgroundColor: data.isEmpty
+                  ? colors.Grey[500]
+                  : colors.Green[500]),
           height: 80.0,
           margin: new EdgeDims.all(10.0),
-          child: wrapCards(cardComponents)
-        );
-      }
-    );
+          child: wrapCards(cardComponents));
+    });
   }
 }
diff --git a/lib/components/croupier.dart b/lib/components/croupier.dart
index 7313003..cf46d45 100644
--- a/lib/components/croupier.dart
+++ b/lib/components/croupier.dart
@@ -16,7 +16,8 @@
     croupier = other.croupier;
   }
 
-  Function setStateCallbackFactory(logic_croupier.CroupierState s, [var data = null]) {
+  Function setStateCallbackFactory(logic_croupier.CroupierState s,
+      [var data = null]) {
     return () => setState(() {
       croupier.setState(s, data);
     });
@@ -27,57 +28,53 @@
       case logic_croupier.CroupierState.Welcome:
         // in which we show them a UI to start a new game, join a game, or change some settings.
         return new Container(
-          padding: new EdgeDims.only(top: sky.view.paddingTop),
-          child: new Flex([
-            new FlatButton(
+            padding: new EdgeDims.only(top: sky.view.paddingTop),
+            child: new Flex([
+          new FlatButton(
               child: new Text('Create Game'),
-              onPressed: setStateCallbackFactory(logic_croupier.CroupierState.ChooseGame)
-            ),
-            new FlatButton(
-              child: new Text('Join Game')
-            ),
-            new FlatButton(
-              child: new Text('Settings')
-            )
-          ], direction: FlexDirection.vertical
-        )
-      );
+              onPressed: setStateCallbackFactory(
+                  logic_croupier.CroupierState.ChooseGame)),
+          new FlatButton(child: new Text('Join Game')),
+          new FlatButton(child: new Text('Settings'))
+        ], direction: FlexDirection.vertical));
       case logic_croupier.CroupierState.Settings:
         return null; // in which we let them pick an avatar, name, and color. And return to the previous screen after (NOT IMPLEMENTED YET)
       case logic_croupier.CroupierState.ChooseGame:
         // in which we let them pick a game out of the many possible games... There aren't that many.
         return new Container(
-          padding: new EdgeDims.only(top: sky.view.paddingTop),
-          child: new Flex([
-            new FlatButton(
+            padding: new EdgeDims.only(top: sky.view.paddingTop),
+            child: new Flex([
+          new FlatButton(
               child: new Text('Proto'),
-              onPressed: setStateCallbackFactory(logic_croupier.CroupierState.PlayGame, logic_game.GameType.Proto)
-            ),
-            new FlatButton(
+              onPressed: setStateCallbackFactory(
+                  logic_croupier.CroupierState.PlayGame,
+                  logic_game.GameType.Proto)),
+          new FlatButton(
               child: new Text('Hearts'),
-              onPressed: setStateCallbackFactory(logic_croupier.CroupierState.PlayGame, logic_game.GameType.Hearts)
-            ),
-            new FlatButton(
-              child: new Text('Poker')
-            ),
-            new FlatButton(
-              child: new Text('Solitaire')
-            )
-          ], direction: FlexDirection.vertical
-        )
-      );
+              onPressed: setStateCallbackFactory(
+                  logic_croupier.CroupierState.PlayGame,
+                  logic_game.GameType.Hearts)),
+          new FlatButton(child: new Text('Poker')),
+          new FlatButton(child: new Text('Solitaire')),
+          new FlatButton(
+              child: new Text('Syncbase Echo'),
+              onPressed: setStateCallbackFactory(
+                  logic_croupier.CroupierState.PlayGame,
+                  logic_game.GameType.SyncbaseEcho))
+        ], direction: FlexDirection.vertical));
       case logic_croupier.CroupierState.AwaitGame:
         return null; // in which players wait for game invitations to arrive.
       case logic_croupier.CroupierState.ArrangePlayers:
         return null; // If needed, lists the players around and what devices they'd like to use.
       case logic_croupier.CroupierState.PlayGame:
         return new Container(
-          padding: new EdgeDims.only(top: sky.view.paddingTop),
-          child: new GameComponent(croupier.game) // Asks the game UI to draw itself.
-        );
+            padding: new EdgeDims.only(top: sky.view.paddingTop),
+            child: new GameComponent(
+                croupier.game) // Asks the game UI to draw itself.
+            );
       default:
         assert(false);
         return null;
     }
   }
-}
\ No newline at end of file
+}
diff --git a/lib/components/draggable.dart b/lib/components/draggable.dart
index ad4cbec..55406fb 100644
--- a/lib/components/draggable.dart
+++ b/lib/components/draggable.dart
@@ -16,14 +16,14 @@
 
   widgets.Widget build() {
     return new widgets.Listener(
-      onPointerDown: _startDrag,
-      onPointerMove: _updateDrag,
-      onPointerCancel: _cancelDrag,
-      onPointerUp: _drop,
-      child:     new widgets.Transform(
-                  transform: new vector_math.Matrix4.identity().translate(displacement.dx, displacement.dy),
-                  child: child)
-    );
+        onPointerDown: _startDrag,
+        onPointerMove: _updateDrag,
+        onPointerCancel: _cancelDrag,
+        onPointerUp: _drop,
+        child: new widgets.Transform(
+            transform: new vector_math.Matrix4.identity().translate(
+                displacement.dx, displacement.dy),
+            child: child));
   }
 
   widgets.EventDisposition _startDrag(sky.PointerEvent event) {
diff --git a/lib/components/game.dart b/lib/components/game.dart
index 5eb4c5b..94c5bca 100644
--- a/lib/components/game.dart
+++ b/lib/components/game.dart
@@ -1,15 +1,17 @@
 import '../logic/card.dart' show Card;
-import '../logic/game.dart' show Game, GameType, Viewer, HeartsGame, HeartsPhase;
+import '../logic/game.dart'
+    show Game, GameType, Viewer, HeartsGame, HeartsPhase;
+import '../logic/syncbase_echo_impl.dart' show SyncbaseEchoImpl;
 import 'board.dart' show Board;
 import 'card_collection.dart' show CardCollectionComponent, Orientation;
 
 import 'package:sky/widgets/basic.dart';
-import 'package:sky/widgets.dart' show FlatButton;
+import 'package:sky/widgets.dart' show FlatButton, RaisedButton;
 import 'package:sky/theme/colors.dart' as colors;
 
-
 class GameComponent extends StatefulComponent {
   Game game;
+  SyncbaseEchoImpl s;
 
   GameComponent(this.game) {
     game.updateCallback = update;
@@ -29,10 +31,15 @@
         return buildProto();
       case GameType.Hearts:
         return buildHearts();
+      case GameType.SyncbaseEcho:
+        if (s == null) {
+          s = new SyncbaseEchoImpl(game);
+        }
+        return buildSyncbaseEcho();
       case GameType.Board:
         // Does NOT work in checked mode since it has a Stack of Positioned Stack with Positioned Widgets.
         // Issue and possible workaround? https://github.com/domokit/sky_engine/issues/732
-        return new Board(1, [2,3,4], [1, 2, 3, 4]);
+        return new Board(1, [2, 3, 4], [1, 2, 3, 4]);
       default:
         return null; // unsupported
     }
@@ -48,7 +55,7 @@
     setState(() {
       try {
         game.move(card, dest);
-      } catch(e) {
+      } catch (e) {
         print("You can't do that! ${e.toString()}");
         game.debugString = e.toString();
       }
@@ -62,33 +69,30 @@
 
     for (int i = 0; i < 4; i++) {
       List<Card> cards = game.cardCollections[i];
-      CardCollectionComponent c = new CardCollectionComponent(cards, game.playerNumber == i, Orientation.horz, _updateGameCallback);
+      CardCollectionComponent c = new CardCollectionComponent(
+          cards, game.playerNumber == i, Orientation.horz, _updateGameCallback);
       cardCollections.add(c); // flex
     }
 
     cardCollections.add(new Container(
-      decoration: new BoxDecoration(backgroundColor: colors.Green[500], borderRadius: 5.0),
-      child: new CardCollectionComponent(game.cardCollections[4], true, Orientation.show1, _updateGameCallback)
-    ));
+        decoration: new BoxDecoration(
+            backgroundColor: colors.Green[500], borderRadius: 5.0),
+        child: new CardCollectionComponent(game.cardCollections[4], true,
+            Orientation.show1, _updateGameCallback)));
 
     cardCollections.add(new FlatButton(
-      child: new Text('Switch View'),
-      onPressed: _switchPlayersCallback
-    ));
+        child: new Text('Switch View'), onPressed: _switchPlayersCallback));
 
     return new Container(
-      decoration: new BoxDecoration(backgroundColor: colors.Pink[500]),
-      child: new Flex(cardCollections, direction: FlexDirection.vertical)
-    );
+        decoration: new BoxDecoration(backgroundColor: colors.Pink[500]),
+        child: new Flex(cardCollections, direction: FlexDirection.vertical));
   }
 
-  Widget _makeSwitchViewButton() =>_makeButton('Switch View', _switchPlayersCallback);
+  Widget _makeSwitchViewButton() =>
+      _makeButton('Switch View', _switchPlayersCallback);
 
   Widget _makeButton(String text, Function callback) {
-    return new FlatButton(
-      child: new Text(text),
-      onPressed: callback
-    );
+    return new FlatButton(child: new Text(text), onPressed: callback);
   }
 
   Widget buildHearts() {
@@ -97,13 +101,12 @@
     switch (game.phase) {
       case HeartsPhase.Deal:
         return new Container(
-          decoration: new BoxDecoration(backgroundColor: colors.Pink[500]),
-          child: new Flex([
-            new Text('Player ${game.playerNumber}'),
-            _makeButton('Deal', game.dealCards),
-            _makeSwitchViewButton()
-          ], direction: FlexDirection.vertical)
-        );
+            decoration: new BoxDecoration(backgroundColor: colors.Pink[500]),
+            child: new Flex([
+          new Text('Player ${game.playerNumber}'),
+          _makeButton('Deal', game.dealCards),
+          _makeSwitchViewButton()
+        ], direction: FlexDirection.vertical));
       case HeartsPhase.Pass:
       case HeartsPhase.Take:
       case HeartsPhase.Play:
@@ -115,6 +118,20 @@
     }
   }
 
+  Widget buildSyncbaseEcho() {
+    return new Container(
+        decoration: const BoxDecoration(
+            backgroundColor: const Color(0xFF00ACC1)),
+        child: new Flex([
+      new RaisedButton(child: new Text('doEcho'), onPressed: s.doEcho),
+      new Text('sendMsg: ${s.sendMsg}'),
+      new Text('recvMsg: ${s.recvMsg}'),
+      new RaisedButton(child: new Text('doPutGet'), onPressed: s.doPutGet),
+      new Text('putStr: ${s.putStr}'),
+      new Text('getStr: ${s.getStr}')
+    ], direction: FlexDirection.vertical));
+  }
+
   Widget showBoard() {
     HeartsGame game = this.game as HeartsGame;
 
@@ -124,23 +141,22 @@
 
     for (int i = 0; i < 4; i++) {
       List<Card> cards = game.cardCollections[i];
-      CardCollectionComponent c = new CardCollectionComponent(cards, game.playerNumber == i, Orientation.horz, _updateGameCallback);
+      CardCollectionComponent c = new CardCollectionComponent(
+          cards, game.playerNumber == i, Orientation.horz, _updateGameCallback);
       cardCollections.add(c); // flex
     }
 
     cardCollections.add(new Container(
-      decoration: new BoxDecoration(backgroundColor: colors.Green[500], borderRadius: 5.0),
-      child: new CardCollectionComponent(game.cardCollections[4], true, Orientation.show1, _updateGameCallback)
-    ));
+        decoration: new BoxDecoration(
+            backgroundColor: colors.Green[500], borderRadius: 5.0),
+        child: new CardCollectionComponent(game.cardCollections[4], true,
+            Orientation.show1, _updateGameCallback)));
 
     cardCollections.add(new FlatButton(
-      child: new Text('Switch View'),
-      onPressed: _switchPlayersCallback
-    ));
+        child: new Text('Switch View'), onPressed: _switchPlayersCallback));
 
     return new Container(
-      decoration: new BoxDecoration(backgroundColor: colors.Pink[500]),
-      child: new Flex(cardCollections, direction: FlexDirection.vertical)
-    );
+        decoration: new BoxDecoration(backgroundColor: colors.Pink[500]),
+        child: new Flex(cardCollections, direction: FlexDirection.vertical));
   }
 }
diff --git a/lib/logic/card.dart b/lib/logic/card.dart
index 3f53685..4dbba8a 100644
--- a/lib/logic/card.dart
+++ b/lib/logic/card.dart
@@ -5,7 +5,9 @@
   final String identifier;
 
   Card(this.deck, this.identifier);
-  Card.fromString(String cardData) : deck = cardData.split(" ")[0], identifier = cardData.split(" ")[1];
+  Card.fromString(String cardData)
+      : deck = cardData.split(" ")[0],
+        identifier = cardData.split(" ")[1];
 
   bool operator ==(Object other) {
     if (other is! Card) return false;
diff --git a/lib/logic/croupier.dart b/lib/logic/croupier.dart
index 9a548cc..494e29e 100644
--- a/lib/logic/croupier.dart
+++ b/lib/logic/croupier.dart
@@ -1,7 +1,12 @@
 import 'game.dart' show Game, GameType;
 
 enum CroupierState {
-  Welcome, Settings, ChooseGame, AwaitGame, ArrangePlayers, PlayGame
+  Welcome,
+  Settings,
+  ChooseGame,
+  AwaitGame,
+  ArrangePlayers,
+  PlayGame
 }
 
 class Croupier {
@@ -62,4 +67,4 @@
 
   // Settings.load(String data) {}
   // String save() { return null; }
-}
\ No newline at end of file
+}
diff --git a/lib/logic/game.dart b/lib/logic/game.dart
index b5f7d36..142ec30 100644
--- a/lib/logic/game.dart
+++ b/lib/logic/game.dart
@@ -1,12 +1,11 @@
 import 'card.dart' show Card;
 import 'dart:math' as math;
+import 'syncbase_echo.dart' show SyncbaseEcho;
 
 // Note: Proto and Board are "fake" games intended to demonstrate what we can do.
 // Proto is just a drag cards around "game".
 // Board is meant to show how one _could_ layout a game of Hearts. This one is not hooked up very well yet.
-enum GameType {
-  Proto, Hearts, Poker, Solitaire, Board
-}
+enum GameType { Proto, Hearts, Poker, Solitaire, Board, SyncbaseEcho }
 
 /// 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.
@@ -28,12 +27,20 @@
         return new ProtoGame(pn);
       case GameType.Hearts:
         return new HeartsGame(pn);
+      case GameType.SyncbaseEcho:
+        return new SyncbaseEcho();
       default:
         assert(false);
         return null;
     }
   }
 
+  // A public super constructor that doesn't really do anything.
+  // TODO(alexfandrianto): The proper way to handle this would be to use 'parts'.
+  // That way, I can have all the game logic split up across multiple files and
+  // still access private constructors.
+  Game.dummy(this.gameType) {}
+
   // A super constructor, don't call this unless you're a subclass.
   Game._create(this.gameType, this.playerNumber, int numCollections) {
     gamelog.setGame(this);
@@ -112,9 +119,7 @@
   }
 }
 
-enum HeartsPhase {
-  Deal, Pass, Take, Play, Score
-}
+enum HeartsPhase { Deal, Pass, Take, Play, Score }
 
 class HeartsGame extends Game {
   static const PLAYER_A = 0;
@@ -161,7 +166,8 @@
   List<int> scores = [0, 0, 0, 0];
   List<bool> ready;
 
-  HeartsGame(int playerNumber) : super._create(GameType.Hearts, playerNumber, 16) {
+  HeartsGame(int playerNumber)
+      : super._create(GameType.Hearts, playerNumber, 16) {
     resetGame();
   }
 
@@ -181,7 +187,8 @@
   }
 
   int get passTarget {
-    switch (roundNumber % 4) { // is a 4-cycle
+    switch (roundNumber % 4) {
+      // is a 4-cycle
       case 0:
         return (playerNumber - 1) % 4; // passLeft
       case 1:
@@ -197,7 +204,8 @@
   }
   int get takeTarget => _getTakeTarget(playerNumber);
   int _getTakeTarget(takerId) {
-    switch (roundNumber % 4) { // is a 4-cycle
+    switch (roundNumber % 4) {
+      // is a 4-cycle
       case 0:
         return (takerId + 1) % 4; // takeRight
       case 1:
@@ -255,14 +263,12 @@
 
   bool hasSuit(int player, String suit) {
     Card matchesSuit = this.cardCollections[player + OFFSET_HAND].firstWhere(
-      (Card element) => (getCardSuit(element) == suit),
-      orElse: () => null
-    );
+        (Card element) => (getCardSuit(element) == suit), orElse: () => null);
     return matchesSuit != null;
   }
 
   Card get leadingCard {
-    if(this.numPlayed >= 1) {
+    if (this.numPlayed >= 1) {
       return cardCollections[this.lastTrickTaker + OFFSET_PLAY][0];
     }
     return null;
@@ -280,18 +286,18 @@
   bool get hasGameEnded => this.scores.reduce(math.max) >= HeartsGame.MAX_SCORE;
 
   bool get allDealt => cardCollections[PLAYER_A].length == 13 &&
-    cardCollections[PLAYER_B].length == 13 &&
-    cardCollections[PLAYER_C].length == 13 &&
-    cardCollections[PLAYER_D].length == 13;
+      cardCollections[PLAYER_B].length == 13 &&
+      cardCollections[PLAYER_C].length == 13 &&
+      cardCollections[PLAYER_D].length == 13;
 
   bool get allPassed => cardCollections[PLAYER_A_PASS].length == 3 &&
-    cardCollections[PLAYER_B_PASS].length == 3 &&
-    cardCollections[PLAYER_C_PASS].length == 3 &&
-    cardCollections[PLAYER_D_PASS].length == 3;
+      cardCollections[PLAYER_B_PASS].length == 3 &&
+      cardCollections[PLAYER_C_PASS].length == 3 &&
+      cardCollections[PLAYER_D_PASS].length == 3;
   bool get allTaken => cardCollections[PLAYER_A_PASS].length == 0 &&
-    cardCollections[PLAYER_B_PASS].length == 0 &&
-    cardCollections[PLAYER_C_PASS].length == 0 &&
-    cardCollections[PLAYER_D_PASS].length == 0;
+      cardCollections[PLAYER_B_PASS].length == 0 &&
+      cardCollections[PLAYER_C_PASS].length == 0 &&
+      cardCollections[PLAYER_D_PASS].length == 0;
   bool get allPlayed => this.numPlayed == 4;
 
   bool get allReady => ready[0] && ready[1] && ready[2] && ready[3];
@@ -344,14 +350,17 @@
 
     int i = findCard(card);
     if (i == -1) {
-      throw new StateError('card does not exist or was not dealt: ${card.toString()}');
+      throw new StateError(
+          'card does not exist or was not dealt: ${card.toString()}');
     }
     int destId = cardCollections.indexOf(dest);
     if (destId == -1) {
-      throw new StateError('destination list does not exist: ${dest.toString()}');
+      throw new StateError(
+          'destination list does not exist: ${dest.toString()}');
     }
     if (destId != playerNumber + OFFSET_PLAY) {
-      throw new StateError('player ${playerNumber} is not playing to the correct list: ${destId}');
+      throw new StateError(
+          'player ${playerNumber} is not playing to the correct list: ${destId}');
     }
 
     gamelog.add(new HeartsCommand.play(playerNumber, card));
@@ -405,7 +414,8 @@
             if (!heartsBroken && isHeartsCard(play[0])) {
               heartsBroken = true;
             }
-            this.cardCollections[winner + OFFSET_TRICK].addAll(play); // or add(play[0])
+            this.cardCollections[winner + OFFSET_TRICK]
+                .addAll(play); // or add(play[0])
             play.clear();
           }
 
@@ -435,7 +445,7 @@
   // Returns null or the reason that the player cannot play the card.
   String canPlay(int player, Card c) {
     if (phase != HeartsPhase.Play) {
-     return "It is not the Play phase of Hearts.";
+      return "It is not the Play phase of Hearts.";
     }
     if (!cardCollections[player].contains(c)) {
       return "Player ${player} does not have the card (${c.toString()})";
@@ -455,7 +465,9 @@
     if (this.leadingCard != null) {
       String leadingSuit = getCardSuit(this.leadingCard);
       String otherSuit = getCardSuit(c);
-      if (this.numPlayed >= 1 && leadingSuit != otherSuit && hasSuit(player, leadingSuit)) {
+      if (this.numPlayed >= 1 &&
+          leadingSuit != otherSuit &&
+          hasSuit(player, leadingSuit)) {
         return "Must follow with a ${leadingSuit}.";
       }
     }
@@ -470,7 +482,8 @@
       Card c = cardCollections[i + OFFSET_PLAY][0];
       int value = this.getCardValue(c);
       String suit = this.getCardSuit(c);
-      if (suit == leadingSuit && (highestIndex == null || highestValue < value)) {
+      if (suit == leadingSuit &&
+          (highestIndex == null || highestValue < value)) {
         highestIndex = i;
         highestValue = value;
       }
@@ -492,7 +505,8 @@
     for (int i = 0; i < 4; i++) {
       int delta = computeScore(i);
       this.scores[i] += delta;
-      if (delta == 26) { // Shot the moon!
+      if (delta == 26) {
+        // Shot the moon!
         shotMoon = i;
       }
     }
@@ -525,7 +539,6 @@
   }
 }
 
-
 class GameLog {
   Game game;
   List<GameCommand> log = new List<GameCommand>();
@@ -561,20 +574,18 @@
   HeartsCommand(this.data);
 
   // The following constructors are used for the player generating the HeartsCommand.
-  HeartsCommand.deal(int playerId, List<Card> cards) :
-    this.data = computeDeal(playerId, cards);
+  HeartsCommand.deal(int playerId, List<Card> cards)
+      : this.data = computeDeal(playerId, cards);
 
-  HeartsCommand.pass(int senderId, List<Card> cards) :
-    this.data = computePass(senderId, cards);
+  HeartsCommand.pass(int senderId, List<Card> cards)
+      : this.data = computePass(senderId, cards);
 
-  HeartsCommand.take(int takerId) :
-    this.data = computeTake(takerId);
+  HeartsCommand.take(int takerId) : this.data = computeTake(takerId);
 
-  HeartsCommand.play(int playerId, Card c) :
-    this.data = computePlay(playerId, c);
+  HeartsCommand.play(int playerId, Card c)
+      : this.data = computePlay(playerId, c);
 
-  HeartsCommand.ready(int playerId) :
-    this.data = computeReady(playerId);
+  HeartsCommand.ready(int playerId) : this.data = computeReady(playerId);
 
   static computeDeal(int playerId, List<Card> cards) {
     StringBuffer buff = new StringBuffer();
@@ -608,7 +619,8 @@
     switch (parts[0]) {
       case "Deal":
         if (game.phase != HeartsPhase.Deal) {
-          throw new StateError("Cannot process deal commands when not in Deal phase");
+          throw new StateError(
+              "Cannot process deal commands when not in Deal phase");
         }
         // Deal appends cards to playerId's hand.
         int playerId = int.parse(parts[1]);
@@ -625,7 +637,8 @@
         return;
       case "Pass":
         if (game.phase != HeartsPhase.Pass) {
-          throw new StateError("Cannot process pass commands when not in Pass phase");
+          throw new StateError(
+              "Cannot process pass commands when not in Pass phase");
         }
         // Pass moves a set of cards from senderId to receiverId.
         int senderId = int.parse(parts[1]);
@@ -646,7 +659,8 @@
         return;
       case "Take":
         if (game.phase != HeartsPhase.Take) {
-          throw new StateError("Cannot process take commands when not in Take phase");
+          throw new StateError(
+              "Cannot process take commands when not in Take phase");
         }
         int takerId = int.parse(parts[1]);
         int senderPile = game._getTakeTarget(takerId) + HeartsGame.OFFSET_PASS;
@@ -657,7 +671,8 @@
         return;
       case "Play":
         if (game.phase != HeartsPhase.Play) {
-          throw new StateError("Cannot process play commands when not in Play phase");
+          throw new StateError(
+              "Cannot process play commands when not in Play phase");
         }
 
         // Play the card from the player's hand to their play pile.
@@ -671,16 +686,19 @@
         // If the card isn't valid, then we have an error.
         String reason = game.canPlay(playerId, c);
         if (reason != null) {
-          throw new StateError("Player ${playerId} cannot play ${c.toString()} because ${reason}");
+          throw new StateError(
+              "Player ${playerId} cannot play ${c.toString()} because ${reason}");
         }
         this.transfer(hand, discard, c);
         return;
       case "Ready":
         if (game.hasGameEnded) {
-          throw new StateError("Game has already ended. Start a new one to play again.");
+          throw new StateError(
+              "Game has already ended. Start a new one to play again.");
         }
         if (game.phase != HeartsPhase.Score) {
-          throw new StateError("Cannot process ready commands when not in Score phase");
+          throw new StateError(
+              "Cannot process ready commands when not in Score phase");
         }
         int playerId = int.parse(parts[1]);
         game.setReady(playerId);
@@ -693,7 +711,8 @@
 
   void transfer(List<Card> sender, List<Card> receiver, Card c) {
     if (!sender.contains(c)) {
-      throw new StateError("Sender ${sender.toString()} lacks Card ${c.toString()}");
+      throw new StateError(
+          "Sender ${sender.toString()} lacks Card ${c.toString()}");
     }
     sender.remove(c);
     receiver.add(c);
@@ -707,15 +726,15 @@
   ProtoCommand(this.data);
 
   // The following constructors are used for the player generating the ProtoCommand.
-  ProtoCommand.deal(int playerId, List<Card> cards) :
-    this.data = computeDeal(playerId, cards);
+  ProtoCommand.deal(int playerId, List<Card> cards)
+      : this.data = computeDeal(playerId, cards);
 
   // TODO: receiverId is actually implied by the game round. So it may end up being removable.
-  ProtoCommand.pass(int senderId, int receiverId, List<Card> cards) :
-    this.data = computePass(senderId, receiverId, cards);
+  ProtoCommand.pass(int senderId, int receiverId, List<Card> cards)
+      : this.data = computePass(senderId, receiverId, cards);
 
-  ProtoCommand.play(int playerId, Card c) :
-    this.data = computePlay(playerId, c);
+  ProtoCommand.play(int playerId, Card c)
+      : this.data = computePlay(playerId, c);
 
   static computeDeal(int playerId, List<Card> cards) {
     StringBuffer buff = new StringBuffer();
diff --git a/lib/logic/syncbase_echo.dart b/lib/logic/syncbase_echo.dart
new file mode 100644
index 0000000..2befa0b
--- /dev/null
+++ b/lib/logic/syncbase_echo.dart
@@ -0,0 +1,5 @@
+import 'game.dart' show Game, GameType;
+
+class SyncbaseEcho extends Game {
+  SyncbaseEcho() : super.dummy(GameType.SyncbaseEcho);
+}
\ No newline at end of file
diff --git a/lib/logic/syncbase_echo_impl.dart b/lib/logic/syncbase_echo_impl.dart
new file mode 100644
index 0000000..4343d57
--- /dev/null
+++ b/lib/logic/syncbase_echo_impl.dart
@@ -0,0 +1,91 @@
+import 'dart:async';
+import 'dart:convert' show UTF8;
+
+import 'game.dart' show Game;
+
+import 'package:sky/mojo/embedder.dart' show embedder;
+
+import 'package:ether/echo_client.dart' show EchoClient;
+import 'package:ether/syncbase_client.dart'
+    show Perms, SyncbaseClient, SyncbaseTable;
+
+log(String msg) {
+  DateTime now = new DateTime.now();
+  print('$now $msg');
+}
+
+Perms emptyPerms() => new Perms()..json = '{}';
+
+class SyncbaseEchoImpl {
+  final EchoClient _echoClient;
+  final SyncbaseClient _syncbaseClient;
+  final Game game;
+
+  SyncbaseEchoImpl(this.game)
+      : _echoClient = new EchoClient(
+            embedder.connectToService, 'https://mojo.v.io/echo_server.mojo'),
+        _syncbaseClient = new SyncbaseClient(embedder.connectToService,
+            'https://mojo.v.io/syncbase_server.mojo');
+
+  int seq = 0;
+  SyncbaseTable tb;
+  String sendMsg, recvMsg, putStr, getStr;
+
+  Future doEcho() async {
+    log('DemoApp.doEcho');
+
+    sendMsg = seq.toString();
+    recvMsg = '';
+    seq++;
+    log('setState sendMsg done');
+
+    String recvMsgAsync = await _echoClient.echo(sendMsg);
+
+    recvMsg = recvMsgAsync;
+    log('setState recvMsg done');
+
+    game.updateCallback(); // tell the UI to set/update state.
+  }
+
+  Future doSyncbaseInit() async {
+    log('DemoApp.doSyncbaseInit');
+    if (tb != null) {
+      log('syncbase already initialized');
+      return;
+    }
+    var app = _syncbaseClient.app('app');
+    if (!(await app.exists())) {
+      await app.create(emptyPerms());
+    }
+    var db = app.noSqlDatabase('db');
+    if (!(await db.exists())) {
+      await db.create(emptyPerms());
+    }
+    var table = db.table('table');
+    if (!(await table.exists())) {
+      await table.create(emptyPerms());
+    }
+    tb = table;
+    log('syncbase is now initialized');
+  }
+
+  Future doPutGet() async {
+    log('DemoApp.doPutGet');
+    await doSyncbaseInit();
+
+    putStr = seq.toString();
+    getStr = '';
+    seq++;
+    log('setState putStr done');
+
+    // TODO(sadovsky): Switch to tb.put/get once they exist.
+    var row = tb.row('key');
+    await row.put(UTF8.encode(putStr));
+    var getBytes = await row.get();
+
+    getStr = UTF8.decode(getBytes);
+    log('setState getStr done');
+
+    game.updateCallback(); // tell the UI to set/update state.
+  }
+}
diff --git a/lib/main.dart b/lib/main.dart
index 718f454..ae59599 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -12,15 +12,12 @@
 
   Widget build() {
     return new Container(
-      decoration: new BoxDecoration(
-        backgroundColor: const Color(0xFF6666FF),
-        borderRadius: 5.0
-      ),
-      child: new CroupierComponent(this.croupier)
-    );
+        decoration: new BoxDecoration(
+            backgroundColor: const Color(0xFF6666FF), borderRadius: 5.0),
+        child: new CroupierComponent(this.croupier));
   }
 }
 
 void main() {
   runApp(new CroupierApp());
-}
\ No newline at end of file
+}
diff --git a/mojoconfig b/mojoconfig
new file mode 100644
index 0000000..23a16b5
--- /dev/null
+++ b/mojoconfig
@@ -0,0 +1,41 @@
+# This describes how we access our mojo services, including the sky_viewer.
+
+{
+  'dev_servers': [
+    {
+      'host': 'https://croupier.v.io/',
+      'mappings': [
+        ('packages/', [
+          # For croupier packages.
+          '@{CROUPIER_DIR}/packages',
+        ]),
+        ('', [
+          # For croupier/lib/main.dart.
+          '@{CROUPIER_DIR}',
+        ]),
+      ]
+    },
+    {
+      'host': 'https://mojo.v.io/',
+      'mappings': [
+        ('', [
+          # For echo_server.mojo and syncbase_server.mojo.
+          '@{ETHER_BUILD_DIR}',
+        ]),
+      ],
+    },
+    {
+      'host': 'https://sky/',
+      'mappings': [
+        ('', [
+          # For sky_viewer.mojo.
+          '@{SKY_BUILD_DIR}'
+        ]),
+      ],
+    }
+  ],
+
+  'content_handlers': {
+    'application/dart': 'https://sky/sky_viewer.mojo',
+  }
+}
diff --git a/pubspec.yaml b/pubspec.yaml
index 298989f..d23cca5 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -2,4 +2,8 @@
 dependencies:
   sky: any
   sky_tools: any
-  test: any
\ No newline at end of file
+  test: any
+  ether: any
+dependency_overrides:
+  ether:
+    path: ../../mojo/syncbase/dart
\ No newline at end of file
diff --git a/test/hearts_test.dart b/test/hearts_test.dart
index 85fc692..744245e 100644
--- a/test/hearts_test.dart
+++ b/test/hearts_test.dart
@@ -11,22 +11,54 @@
       game.dealCards(); // What the dealer actually runs to get cards to everybody.
 
       // By virtue of creating the game, HeartsGame should have 4 collections with 13 cards and 8 collections with 0 cards each.
-      expect(game.cardCollections[HeartsGame.PLAYER_A + HeartsGame.OFFSET_HAND].length, equals(13), reason: "Dealt 13 cards to A");
-      expect(game.cardCollections[HeartsGame.PLAYER_B + HeartsGame.OFFSET_HAND].length, equals(13), reason: "Dealt 13 cards to B");
-      expect(game.cardCollections[HeartsGame.PLAYER_C + HeartsGame.OFFSET_HAND].length, equals(13), reason: "Dealt 13 cards to C");
-      expect(game.cardCollections[HeartsGame.PLAYER_D + HeartsGame.OFFSET_HAND].length, equals(13), reason: "Dealt 13 cards to D");
-      expect(game.cardCollections[HeartsGame.PLAYER_A + HeartsGame.OFFSET_PLAY].length, equals(0), reason: "Not playing yet");
-      expect(game.cardCollections[HeartsGame.PLAYER_B + HeartsGame.OFFSET_PLAY].length, equals(0), reason: "Not playing yet");
-      expect(game.cardCollections[HeartsGame.PLAYER_C + HeartsGame.OFFSET_PLAY].length, equals(0), reason: "Not playing yet");
-      expect(game.cardCollections[HeartsGame.PLAYER_D + HeartsGame.OFFSET_PLAY].length, equals(0), reason: "Not playing yet");
-      expect(game.cardCollections[HeartsGame.PLAYER_A + HeartsGame.OFFSET_PASS].length, equals(0), reason: "Not passing yet");
-      expect(game.cardCollections[HeartsGame.PLAYER_B + HeartsGame.OFFSET_PASS].length, equals(0), reason: "Not passing yet");
-      expect(game.cardCollections[HeartsGame.PLAYER_C + HeartsGame.OFFSET_PASS].length, equals(0), reason: "Not passing yet");
-      expect(game.cardCollections[HeartsGame.PLAYER_D + HeartsGame.OFFSET_PASS].length, equals(0), reason: "Not passing yet");
-      expect(game.cardCollections[HeartsGame.PLAYER_A + HeartsGame.OFFSET_TRICK].length, equals(0), reason: "No tricks yet");
-      expect(game.cardCollections[HeartsGame.PLAYER_B + HeartsGame.OFFSET_TRICK].length, equals(0), reason: "No tricks yet");
-      expect(game.cardCollections[HeartsGame.PLAYER_C + HeartsGame.OFFSET_TRICK].length, equals(0), reason: "No tricks yet");
-      expect(game.cardCollections[HeartsGame.PLAYER_D + HeartsGame.OFFSET_TRICK].length, equals(0), reason: "No tricks yet");
+      expect(game.cardCollections[
+          HeartsGame.PLAYER_A + HeartsGame.OFFSET_HAND].length, equals(13),
+          reason: "Dealt 13 cards to A");
+      expect(game.cardCollections[
+          HeartsGame.PLAYER_B + HeartsGame.OFFSET_HAND].length, equals(13),
+          reason: "Dealt 13 cards to B");
+      expect(game.cardCollections[
+          HeartsGame.PLAYER_C + HeartsGame.OFFSET_HAND].length, equals(13),
+          reason: "Dealt 13 cards to C");
+      expect(game.cardCollections[
+          HeartsGame.PLAYER_D + HeartsGame.OFFSET_HAND].length, equals(13),
+          reason: "Dealt 13 cards to D");
+      expect(game.cardCollections[
+          HeartsGame.PLAYER_A + HeartsGame.OFFSET_PLAY].length, equals(0),
+          reason: "Not playing yet");
+      expect(game.cardCollections[
+          HeartsGame.PLAYER_B + HeartsGame.OFFSET_PLAY].length, equals(0),
+          reason: "Not playing yet");
+      expect(game.cardCollections[
+          HeartsGame.PLAYER_C + HeartsGame.OFFSET_PLAY].length, equals(0),
+          reason: "Not playing yet");
+      expect(game.cardCollections[
+          HeartsGame.PLAYER_D + HeartsGame.OFFSET_PLAY].length, equals(0),
+          reason: "Not playing yet");
+      expect(game.cardCollections[
+          HeartsGame.PLAYER_A + HeartsGame.OFFSET_PASS].length, equals(0),
+          reason: "Not passing yet");
+      expect(game.cardCollections[
+          HeartsGame.PLAYER_B + HeartsGame.OFFSET_PASS].length, equals(0),
+          reason: "Not passing yet");
+      expect(game.cardCollections[
+          HeartsGame.PLAYER_C + HeartsGame.OFFSET_PASS].length, equals(0),
+          reason: "Not passing yet");
+      expect(game.cardCollections[
+          HeartsGame.PLAYER_D + HeartsGame.OFFSET_PASS].length, equals(0),
+          reason: "Not passing yet");
+      expect(game.cardCollections[
+          HeartsGame.PLAYER_A + HeartsGame.OFFSET_TRICK].length, equals(0),
+          reason: "No tricks yet");
+      expect(game.cardCollections[
+          HeartsGame.PLAYER_B + HeartsGame.OFFSET_TRICK].length, equals(0),
+          reason: "No tricks yet");
+      expect(game.cardCollections[
+          HeartsGame.PLAYER_C + HeartsGame.OFFSET_TRICK].length, equals(0),
+          reason: "No tricks yet");
+      expect(game.cardCollections[
+          HeartsGame.PLAYER_D + HeartsGame.OFFSET_TRICK].length, equals(0),
+          reason: "No tricks yet");
     });
   });
 
@@ -44,7 +76,8 @@
         new Card("classic", "h4")
       ];
 
-      expect(game.computeScore(HeartsGame.PLAYER_A), equals(4), reason: "Player A has 4 hearts");
+      expect(game.computeScore(HeartsGame.PLAYER_A), equals(4),
+          reason: "Player A has 4 hearts");
 
       // In this alternative situation, what's the score?
       game.cardCollections[HeartsGame.PLAYER_B_TRICK] = <Card>[
@@ -60,14 +93,16 @@
         new Card("classic", "s2")
       ];
 
-      expect(game.computeScore(HeartsGame.PLAYER_B), equals(8), reason: "Player B has 8 hearts.");
+      expect(game.computeScore(HeartsGame.PLAYER_B), equals(8),
+          reason: "Player B has 8 hearts.");
 
       // Should prepare C as well.
       game.cardCollections[HeartsGame.PLAYER_C_TRICK] = <Card>[
         new Card("classic", "h5"),
         new Card("classic", "sq")
       ];
-      expect(game.computeScore(HeartsGame.PLAYER_C), equals(14), reason: "Player C has 1 heart and the queen of spades.");
+      expect(game.computeScore(HeartsGame.PLAYER_C), equals(14),
+          reason: "Player C has 1 heart and the queen of spades.");
 
       // Now, update the score, modifying game.scores.
       game.updateScore();
@@ -116,7 +151,8 @@
     void runCommand() {
       String c = commands[commandIndex];
       commandIndex++;
-      if (c == "" || c[0] == "#") { // Essentially, this case allows empty lines and comments.
+      if (c == "" || c[0] == "#") {
+        // Essentially, this case allows empty lines and comments.
         runCommand();
       } else {
         game.gamelog.add(new HeartsCommand(c));
@@ -133,10 +169,18 @@
       runCommand();
 
       // Confirm cards in hands.
-      List<Card> expectedAHand = new List<Card>.from(Card.All.getRange(26, 26+5))..addAll(Card.All.getRange(13+5, 26));
-      List<Card> expectedBHand = new List<Card>.from(Card.All.getRange(13, 13+5))..addAll(Card.All.getRange(39+5, 52));
-      List<Card> expectedCHand = new List<Card>.from(Card.All.getRange(39, 39+5))..addAll(Card.All.getRange(0+5, 13));
-      List<Card> expectedDHand = new List<Card>.from(Card.All.getRange(0, 0+5))..addAll(Card.All.getRange(26+5, 39));
+      List<Card> expectedAHand =
+          new List<Card>.from(Card.All.getRange(26, 26 + 5))
+        ..addAll(Card.All.getRange(13 + 5, 26));
+      List<Card> expectedBHand =
+          new List<Card>.from(Card.All.getRange(13, 13 + 5))
+        ..addAll(Card.All.getRange(39 + 5, 52));
+      List<Card> expectedCHand =
+          new List<Card>.from(Card.All.getRange(39, 39 + 5))
+        ..addAll(Card.All.getRange(0 + 5, 13));
+      List<Card> expectedDHand =
+          new List<Card>.from(Card.All.getRange(0, 0 + 5))
+        ..addAll(Card.All.getRange(26 + 5, 39));
       expect(game.cardCollections[HeartsGame.PLAYER_A], equals(expectedAHand));
       expect(game.cardCollections[HeartsGame.PLAYER_B], equals(expectedBHand));
       expect(game.cardCollections[HeartsGame.PLAYER_C], equals(expectedCHand));
@@ -152,22 +196,38 @@
       runCommand();
 
       // Confirm cards in hands and passes.
-      List<Card> expectedAHand = new List<Card>.from(Card.All.getRange(26+3, 26+5))..addAll(Card.All.getRange(13+5, 26));
-      List<Card> expectedBHand = new List<Card>.from(Card.All.getRange(13+3, 13+5))..addAll(Card.All.getRange(39+5, 52));
-      List<Card> expectedCHand = new List<Card>.from(Card.All.getRange(39+3, 39+5))..addAll(Card.All.getRange(0+5, 13));
-      List<Card> expectedDHand = new List<Card>.from(Card.All.getRange(0+3, 0+5))..addAll(Card.All.getRange(26+5, 39));
-      List<Card> expectedAPass = new List<Card>.from(Card.All.getRange(26, 26+3));
-      List<Card> expectedBPass = new List<Card>.from(Card.All.getRange(13, 13+3));
-      List<Card> expectedCPass = new List<Card>.from(Card.All.getRange(39, 39+3));
-      List<Card> expectedDPass = new List<Card>.from(Card.All.getRange(0, 0+3));
+      List<Card> expectedAHand =
+          new List<Card>.from(Card.All.getRange(26 + 3, 26 + 5))
+        ..addAll(Card.All.getRange(13 + 5, 26));
+      List<Card> expectedBHand =
+          new List<Card>.from(Card.All.getRange(13 + 3, 13 + 5))
+        ..addAll(Card.All.getRange(39 + 5, 52));
+      List<Card> expectedCHand =
+          new List<Card>.from(Card.All.getRange(39 + 3, 39 + 5))
+        ..addAll(Card.All.getRange(0 + 5, 13));
+      List<Card> expectedDHand =
+          new List<Card>.from(Card.All.getRange(0 + 3, 0 + 5))
+        ..addAll(Card.All.getRange(26 + 5, 39));
+      List<Card> expectedAPass =
+          new List<Card>.from(Card.All.getRange(26, 26 + 3));
+      List<Card> expectedBPass =
+          new List<Card>.from(Card.All.getRange(13, 13 + 3));
+      List<Card> expectedCPass =
+          new List<Card>.from(Card.All.getRange(39, 39 + 3));
+      List<Card> expectedDPass =
+          new List<Card>.from(Card.All.getRange(0, 0 + 3));
       expect(game.cardCollections[HeartsGame.PLAYER_A], equals(expectedAHand));
       expect(game.cardCollections[HeartsGame.PLAYER_B], equals(expectedBHand));
       expect(game.cardCollections[HeartsGame.PLAYER_C], equals(expectedCHand));
       expect(game.cardCollections[HeartsGame.PLAYER_D], equals(expectedDHand));
-      expect(game.cardCollections[HeartsGame.PLAYER_A_PASS], equals(expectedAPass));
-      expect(game.cardCollections[HeartsGame.PLAYER_B_PASS], equals(expectedBPass));
-      expect(game.cardCollections[HeartsGame.PLAYER_C_PASS], equals(expectedCPass));
-      expect(game.cardCollections[HeartsGame.PLAYER_D_PASS], equals(expectedDPass));
+      expect(game.cardCollections[HeartsGame.PLAYER_A_PASS],
+          equals(expectedAPass));
+      expect(game.cardCollections[HeartsGame.PLAYER_B_PASS],
+          equals(expectedBPass));
+      expect(game.cardCollections[HeartsGame.PLAYER_C_PASS],
+          equals(expectedCPass));
+      expect(game.cardCollections[HeartsGame.PLAYER_D_PASS],
+          equals(expectedDPass));
     });
     test("Take Phase", () {
       expect(game.phase, equals(HeartsPhase.Take));
@@ -180,23 +240,26 @@
 
       // Confirm cards in hands again.
       // Note: I will eventually want to do a sorted comparison or set comparison instead.
-      List<Card> expectedAHand = new List<Card>.from(Card.All.getRange(26+3, 26+5))
-        ..addAll(Card.All.getRange(13+5, 26))
-        ..addAll(Card.All.getRange(13, 13+3));
-      List<Card> expectedBHand = new List<Card>.from(Card.All.getRange(13+3, 13+5))
-        ..addAll(Card.All.getRange(39+5, 52))
-        ..addAll(Card.All.getRange(39, 39+3));
-      List<Card> expectedCHand = new List<Card>.from(Card.All.getRange(39+3, 39+5))
-        ..addAll(Card.All.getRange(0+5, 13))
-        ..addAll(Card.All.getRange(0, 0+3));
-      List<Card> expectedDHand = new List<Card>.from(Card.All.getRange(0+3, 0+5))
-        ..addAll(Card.All.getRange(26+5, 39))
-        ..addAll(Card.All.getRange(26, 26+3));
+      List<Card> expectedAHand =
+          new List<Card>.from(Card.All.getRange(26 + 3, 26 + 5))
+        ..addAll(Card.All.getRange(13 + 5, 26))
+        ..addAll(Card.All.getRange(13, 13 + 3));
+      List<Card> expectedBHand =
+          new List<Card>.from(Card.All.getRange(13 + 3, 13 + 5))
+        ..addAll(Card.All.getRange(39 + 5, 52))
+        ..addAll(Card.All.getRange(39, 39 + 3));
+      List<Card> expectedCHand =
+          new List<Card>.from(Card.All.getRange(39 + 3, 39 + 5))
+        ..addAll(Card.All.getRange(0 + 5, 13))
+        ..addAll(Card.All.getRange(0, 0 + 3));
+      List<Card> expectedDHand =
+          new List<Card>.from(Card.All.getRange(0 + 3, 0 + 5))
+        ..addAll(Card.All.getRange(26 + 5, 39))
+        ..addAll(Card.All.getRange(26, 26 + 3));
       expect(game.cardCollections[HeartsGame.PLAYER_A], equals(expectedAHand));
       expect(game.cardCollections[HeartsGame.PLAYER_B], equals(expectedBHand));
       expect(game.cardCollections[HeartsGame.PLAYER_C], equals(expectedCHand));
       expect(game.cardCollections[HeartsGame.PLAYER_D], equals(expectedDHand));
-
     });
     test("Play Phase - Trick 1", () {
       expect(game.phase, equals(HeartsPhase.Play));
@@ -208,8 +271,10 @@
       runCommand();
 
       // Confirm the winner of the round.
-      expect(game.lastTrickTaker, equals(3), reason: "Player 3 played 4 of Clubs");
-      expect(game.cardCollections[HeartsGame.PLAYER_D_TRICK].length, equals(4), reason: "Player 3 won 1 trick.");
+      expect(game.lastTrickTaker, equals(3),
+          reason: "Player 3 played 4 of Clubs");
+      expect(game.cardCollections[HeartsGame.PLAYER_D_TRICK].length, equals(4),
+          reason: "Player 3 won 1 trick.");
     });
     test("Play Phase - Trick 2", () {
       expect(game.phase, equals(HeartsPhase.Play));
@@ -221,10 +286,12 @@
       runCommand();
 
       // Confirm the winner of the round.
-      expect(game.lastTrickTaker, equals(2), reason: "Player 2 played Ace of Clubs");
-      expect(game.cardCollections[HeartsGame.PLAYER_C_TRICK].length, equals(4), reason: "Player 2 won 1 trick.");
-      expect(game.cardCollections[HeartsGame.PLAYER_D_TRICK].length, equals(4), reason: "Player 3 won 1 trick.");
-
+      expect(game.lastTrickTaker, equals(2),
+          reason: "Player 2 played Ace of Clubs");
+      expect(game.cardCollections[HeartsGame.PLAYER_C_TRICK].length, equals(4),
+          reason: "Player 2 won 1 trick.");
+      expect(game.cardCollections[HeartsGame.PLAYER_D_TRICK].length, equals(4),
+          reason: "Player 3 won 1 trick.");
     });
     test("Play Phase - Trick 13", () {
       expect(game.phase, equals(HeartsPhase.Play));
@@ -236,17 +303,33 @@
       }
 
       // Assert that hands/plays/passes are empty.
-      expect(game.cardCollections[HeartsGame.PLAYER_A + HeartsGame.OFFSET_HAND].length, equals(0), reason: "Played all cards");
-      expect(game.cardCollections[HeartsGame.PLAYER_B + HeartsGame.OFFSET_HAND].length, equals(0), reason: "Played all cards");
-      expect(game.cardCollections[HeartsGame.PLAYER_C + HeartsGame.OFFSET_HAND].length, equals(0), reason: "Played all cards");
-      expect(game.cardCollections[HeartsGame.PLAYER_D + HeartsGame.OFFSET_HAND].length, equals(0), reason: "Played all cards");
+      expect(game.cardCollections[
+          HeartsGame.PLAYER_A + HeartsGame.OFFSET_HAND].length, equals(0),
+          reason: "Played all cards");
+      expect(game.cardCollections[
+          HeartsGame.PLAYER_B + HeartsGame.OFFSET_HAND].length, equals(0),
+          reason: "Played all cards");
+      expect(game.cardCollections[
+          HeartsGame.PLAYER_C + HeartsGame.OFFSET_HAND].length, equals(0),
+          reason: "Played all cards");
+      expect(game.cardCollections[
+          HeartsGame.PLAYER_D + HeartsGame.OFFSET_HAND].length, equals(0),
+          reason: "Played all cards");
 
       // Check that all 52 cards are in tricks.
-      expect(game.lastTrickTaker, equals(0), reason: "Player 0 won the last trick.");
-      expect(game.cardCollections[HeartsGame.PLAYER_A_TRICK].length, equals(4*8), reason: "Player 0 won 8 tricks.");
-      expect(game.cardCollections[HeartsGame.PLAYER_B_TRICK].length, equals(4*2), reason: "Player 1 won 2 tricks.");
-      expect(game.cardCollections[HeartsGame.PLAYER_C_TRICK].length, equals(4*2), reason: "Player 2 won 2 tricks.");
-      expect(game.cardCollections[HeartsGame.PLAYER_D_TRICK].length, equals(4), reason: "Player 3 won 1 trick.");
+      expect(game.lastTrickTaker, equals(0),
+          reason: "Player 0 won the last trick.");
+      expect(
+          game.cardCollections[HeartsGame.PLAYER_A_TRICK].length, equals(4 * 8),
+          reason: "Player 0 won 8 tricks.");
+      expect(
+          game.cardCollections[HeartsGame.PLAYER_B_TRICK].length, equals(4 * 2),
+          reason: "Player 1 won 2 tricks.");
+      expect(
+          game.cardCollections[HeartsGame.PLAYER_C_TRICK].length, equals(4 * 2),
+          reason: "Player 2 won 2 tricks.");
+      expect(game.cardCollections[HeartsGame.PLAYER_D_TRICK].length, equals(4),
+          reason: "Player 3 won 1 trick.");
     });
     test("Score Phase", () {
       expect(game.phase, equals(HeartsPhase.Score));
@@ -274,29 +357,41 @@
       for (int i = 0; i < 68; i++) {
         runCommand();
       }
-      expect(game.scores, equals([21+0, 3+26, 2+26, 0+26]));
+      expect(game.scores, equals([21 + 0, 3 + 26, 2 + 26, 0 + 26]));
       expect(game.hasGameEnded, isFalse);
 
       // 3rd Round: 4 deal, 4 pass, 4 take, 52 play, 4 ready
       for (int i = 0; i < 68; i++) {
         runCommand();
       }
-      expect(game.scores, equals([21+0+0, 3+26+26, 2+26+26, 0+26+26]));
+      expect(game.scores,
+          equals([21 + 0 + 0, 3 + 26 + 26, 2 + 26 + 26, 0 + 26 + 26]));
       expect(game.hasGameEnded, isFalse);
 
       // 4th Round: 4 deal, 52 play, 4 ready
       for (int i = 0; i < 60; i++) {
         runCommand();
       }
-      expect(game.scores, equals([21+0+0+0, 3+26+26+26, 2+26+26+26, 0+26+26+26]));
+      expect(game.scores, equals([
+        21 + 0 + 0 + 0,
+        3 + 26 + 26 + 26,
+        2 + 26 + 26 + 26,
+        0 + 26 + 26 + 26
+      ]));
       expect(game.hasGameEnded, isFalse);
 
       // 5th round: 4 deal, 4 pass, 4 take, 52 play. Game is over, so no ready phase.
       for (int i = 0; i < 64; i++) {
         runCommand();
       }
-      expect(game.scores, equals([21+0+0+0+0, 3+26+26+26+26, 2+26+26+26+26, 0+26+26+26+26]));
-      expect(game.hasGameEnded, isTrue); // assumes game ends after about 100 points.
+      expect(game.scores, equals([
+        21 + 0 + 0 + 0 + 0,
+        3 + 26 + 26 + 26 + 26,
+        2 + 26 + 26 + 26 + 26,
+        0 + 26 + 26 + 26 + 26
+      ]));
+      expect(game.hasGameEnded,
+          isTrue); // assumes game ends after about 100 points.
     });
   });
 
@@ -305,53 +400,66 @@
       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))));
+        game.gamelog.add(new HeartsCommand.deal(
+            0, new List<Card>.from(Card.All.getRange(0, 13))));
       }, throwsA(new isInstanceOf<StateError>()));
     });
     test("Dealing - missing card", () {
       expect(() {
         HeartsGame game = new HeartsGame(0);
-        game.gamelog.add(new HeartsCommand.deal(0, <Card>[new Card("fake", "not real")]));
+        game.gamelog.add(
+            new HeartsCommand.deal(0, <Card>[new Card("fake", "not real")]));
       }, throwsA(new isInstanceOf<StateError>()));
     });
     test("Dealing - too many cards dealt", () {
       expect(() {
         HeartsGame game = new HeartsGame(0);
-        game.gamelog.add(new HeartsCommand.deal(0, new List<Card>.from(Card.All.getRange(0, 15))));
+        game.gamelog.add(new HeartsCommand.deal(
+            0, new List<Card>.from(Card.All.getRange(0, 15))));
       }, throwsA(new isInstanceOf<StateError>()));
       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(0, new List<Card>.from(Card.All.getRange(5, 15))));
+        game.gamelog.add(new HeartsCommand.deal(
+            0, new List<Card>.from(Card.All.getRange(0, 5))));
+        game.gamelog.add(new HeartsCommand.deal(
+            0, new List<Card>.from(Card.All.getRange(5, 15))));
       }, throwsA(new isInstanceOf<StateError>()));
     });
     test("Passing - wrong phase", () {
       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))));
+        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", () {
       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(
+            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))));
+        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", () {
       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(
+            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))));
+        game.gamelog.add(new HeartsCommand.pass(
+            0, new List<Card>.from(Card.All.getRange(0, 2))));
       }, throwsA(new isInstanceOf<StateError>()));
       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(
+            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))));
+        game.gamelog.add(new HeartsCommand.pass(
+            0, new List<Card>.from(Card.All.getRange(0, 4))));
       }, throwsA(new isInstanceOf<StateError>()));
     });
     test("Taking - wrong phase", () {
@@ -363,14 +471,16 @@
     test("Playing - wrong phase", () {
       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(
+            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", () {
       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(
+            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>()));
@@ -378,7 +488,8 @@
     test("Playing - invalid card (not 2 of clubs as first card)", () {
       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(
+            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]));
@@ -389,10 +500,14 @@
       // But the odds are miniscule, so this rule will be enforced.
       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.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]));
@@ -403,43 +518,58 @@
     test("Playing - wrong turn", () {
       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.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.
+        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)", () {
       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.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
+        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)", () {
       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.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.
+        game.gamelog.add(new HeartsCommand.play(
+            2, Card.All[26])); // But 2 can't lead with a hearts.
       }, throwsA(new isInstanceOf<StateError>()));
     });
   });
-}
\ No newline at end of file
+}