TBR Update Croupier

* Fixes a bug where Sky changed StatefulComponent's location.
* Forces checked mode.
* Includes pass and take phases to the UI.
* Uses the syncbase game log idea (in a simplistic way).
* For use in make test and make start, a mock implementation of the store is used.
  This allows testing with pub, but also requires use of `make mock` and `make unmock`.
  This is very hacky.

Bug found in sky with drag and drop, so I have disabled Transform for now.

Change-Id: I3f4c7dac3b66ba85f3e4c61c9334a75d66e6e252
diff --git a/Makefile b/Makefile
index cbd61ea..27f166e 100644
--- a/Makefile
+++ b/Makefile
@@ -49,16 +49,25 @@
 
 .PHONY: lint
 lint:
-	dartanalyzer lib/main.dart
-	dartanalyzer $(DART_TEST_FILES)
+	dartanalyzer lib/main.dart | grep -v "\[warning\] The imported libraries"
+	dartanalyzer $(DART_TEST_FILES) | grep -v "\[warning\] The imported libraries"
 
 .PHONY: start
 start:
-	./packages/sky/sky_tool start
+	./packages/sky/sky_tool start --checked
+
+.PHONY: mock
+mock:
+	mv lib/src/syncbase/log_writer.dart lib/src/syncbase/log_writer.dart.backup
+	cp lib/src/mocks/log_writer.dart lib/src/syncbase/
+
+.PHONY: unmock
+unmock:
+	mv lib/src/syncbase/log_writer.dart.backup lib/src/syncbase/log_writer.dart
 
 .PHONY: install
 install: packages
-	./packages/sky/sky_tool start --install
+	./packages/sky/sky_tool start --install --checked
 
 .PHONY: env-check
 env-check:
@@ -81,12 +90,15 @@
 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)
+	# Protect src/syncbase/log_writer.dart
+	mv lib/src/syncbase/log_writer.dart lib/src/syncbase/log_writer.dart.backup
+	cp lib/src/mocks/log_writer.dart lib/src/syncbase/
+	pub run test -r expanded $(DART_TEST_FILES) || (mv lib/src/syncbase/log_writer.dart.backup lib/src/syncbase/log_writer.dart && exit 1)
+	mv lib/src/syncbase/log_writer.dart.backup lib/src/syncbase/log_writer.dart
 
 .PHONY: clean
 clean:
diff --git a/lib/components/card_collection.dart b/lib/components/card_collection.dart
index 3ad62f8..f1c5d21 100644
--- a/lib/components/card_collection.dart
+++ b/lib/components/card_collection.dart
@@ -1,37 +1,51 @@
 import '../logic/card.dart' as logic_card;
-import 'card.dart' show Card;
+import 'card.dart' as component_card;
 import 'draggable.dart' show Draggable;
-import 'package:sky/widgets/basic.dart';
-import 'package:sky/widgets.dart' show DragTarget;
+import 'package:sky/widgets.dart';
 import 'package:sky/theme/colors.dart' as colors;
 
 enum Orientation { vert, horz, fan, show1 }
+enum DropType { none, card, card_collection } // I can see that both would be nice, but I'm not sure how to do that yet.
 
 class CardCollectionComponent extends StatefulComponent {
   List<logic_card.Card> cards;
   Orientation orientation;
   bool faceUp;
   Function parentCallback;
+  bool dragChildren;
+  DropType acceptType;
 
   String status = 'bar';
 
   CardCollectionComponent(
-      this.cards, this.faceUp, this.orientation, this.parentCallback);
+      this.cards, this.faceUp, this.orientation, this.parentCallback,
+      {this.dragChildren: false, this.acceptType: DropType.none});
 
   void syncConstructorArguments(CardCollectionComponent other) {
     cards = other.cards;
     orientation = other.orientation;
     faceUp = other.faceUp;
     parentCallback = other.parentCallback;
+    dragChildren = other.dragChildren;
+    acceptType = other.acceptType;
   }
 
-  void _handleAccept(Card data) {
+  void _handleAccept(component_card.Card data) {
+    print('accept');
     setState(() {
       status = 'ACCEPT ${data.card.toString()}';
       parentCallback(data.card, this.cards);
     });
   }
 
+  void _handleAcceptMultiple(CardCollectionComponent data) {
+    print('acceptMulti');
+    setState(() {
+      status = 'ACCEPT multi: ${data.cards.toString()}';
+      parentCallback(data.cards, this.cards);
+    });
+  }
+
   List<Widget> flexCards(List<Widget> cardWidgets) {
     List<Widget> flexWidgets = new List<Widget>();
     cardWidgets.forEach(
@@ -58,31 +72,77 @@
   }
 
   Widget build() {
+    Widget w = new Container(
+      decoration: new BoxDecoration(
+        backgroundColor: colors.Green[500], borderRadius: 5.0),
+      child:_buildHearts()
+    );
+    return w;
+  }
+
+  Widget _buildHearts() {
     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
+      component_card.Card c = new component_card.Card(cards[i], faceUp);
+
+      if (dragChildren) {
+        cardComponents.add(new Draggable<component_card.Card>(c));
+      } else {
+        cardComponents.add(c);
+      }
     }
 
     // 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(
-          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]),
-          height: 80.0,
-          margin: new EdgeDims.all(10.0),
-          child: wrapCards(cardComponents));
-    });
+    switch(this.acceptType) {
+      case DropType.none:
+        return new Container(
+            decoration: new BoxDecoration(
+                border: new Border.all(
+                    width: 3.0,
+                    color: colors.white),
+                backgroundColor: colors.Grey[500]),
+            height: 80.0,
+            margin: new EdgeDims.all(10.0),
+            child: wrapCards(cardComponents)
+        );
+      case DropType.card:
+        return new DragTarget<component_card.Card>(
+            onAccept: _handleAccept, builder: (List<component_card.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]),
+              height: 80.0,
+              margin: new EdgeDims.all(10.0),
+              child: wrapCards(cardComponents));
+        });
+      case DropType.card_collection:
+        return new DragTarget<CardCollectionComponent>(
+            onAccept: _handleAcceptMultiple, builder: (List<CardCollectionComponent> data, _) {
+          print('CC ${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]),
+              height: 80.0,
+              margin: new EdgeDims.all(10.0),
+              child: wrapCards(cardComponents));
+          });
+    }
+
   }
 }
diff --git a/lib/components/croupier.dart b/lib/components/croupier.dart
index cf46d45..02c4f6d 100644
--- a/lib/components/croupier.dart
+++ b/lib/components/croupier.dart
@@ -1,9 +1,8 @@
 import '../logic/croupier.dart' as logic_croupier;
 import '../logic/game.dart' as logic_game;
-import 'game.dart' show GameComponent;
+import 'game.dart' show createGameComponent;
 
-import 'package:sky/widgets.dart' show FlatButton;
-import 'package:sky/widgets/basic.dart';
+import 'package:sky/widgets.dart';
 
 import 'dart:sky' as sky;
 
@@ -69,8 +68,7 @@
       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.
+            child: createGameComponent(croupier.game) // Asks the game UI to draw itself.
             );
       default:
         assert(false);
diff --git a/lib/components/draggable.dart b/lib/components/draggable.dart
index 55406fb..a703620 100644
--- a/lib/components/draggable.dart
+++ b/lib/components/draggable.dart
@@ -22,11 +22,12 @@
         onPointerUp: _drop,
         child: new widgets.Transform(
             transform: new vector_math.Matrix4.identity().translate(
-                displacement.dx, displacement.dy),
+                0.0,0.0),//displacement.dx, displacement.dy),
             child: child));
   }
 
   widgets.EventDisposition _startDrag(sky.PointerEvent event) {
+    print("Drag Start");
     setState(() {
       dragController = new widgets.DragController(this.child);
       dragController.update(new widgets.Point(event.x, event.y));
@@ -44,6 +45,7 @@
   }
 
   widgets.EventDisposition _cancelDrag(sky.PointerEvent event) {
+    print("Drag Cancel");
     setState(() {
       dragController.cancel();
       dragController = null;
@@ -52,6 +54,7 @@
   }
 
   widgets.EventDisposition _drop(sky.PointerEvent event) {
+    print("Drag Drop");
     setState(() {
       dragController.update(new widgets.Point(event.x, event.y));
       dragController.drop();
diff --git a/lib/components/game.dart b/lib/components/game.dart
index 94c5bca..4a36501 100644
--- a/lib/components/game.dart
+++ b/lib/components/game.dart
@@ -1,17 +1,16 @@
-import '../logic/card.dart' show Card;
+import '../logic/card.dart' as logic_card;
 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 '../src/syncbase/syncbase_echo_impl.dart' show SyncbaseEchoImpl;
+//import 'board.dart' show Board;
+import 'card_collection.dart' show CardCollectionComponent, DropType, Orientation;
+import 'draggable.dart' show Draggable;
 
-import 'package:sky/widgets/basic.dart';
-import 'package:sky/widgets.dart' show FlatButton, RaisedButton;
+import 'package:sky/widgets.dart';
 import 'package:sky/theme/colors.dart' as colors;
 
-class GameComponent extends StatefulComponent {
+abstract class GameComponent extends StatefulComponent {
   Game game;
-  SyncbaseEchoImpl s;
 
   GameComponent(this.game) {
     game.updateCallback = update;
@@ -25,33 +24,58 @@
     this.game = other.game;
   }
 
+  Widget _makeButton(String text, Function callback) {
+    return new FlatButton(child: new Text(text), onPressed: callback);
+  }
+
+  Widget build();
+}
+
+GameComponent createGameComponent(Game game) {
+  switch(game.gameType) {
+    case GameType.Proto:
+      return new ProtoGameComponent(game);
+    case GameType.Hearts:
+      return new HeartsGameComponent(game);
+    case GameType.SyncbaseEcho:
+      return new SyncbaseEchoGameComponent(game);
+    default:
+      // We're probably not ready to serve the other games yet.
+      assert(false);
+      return null;
+  }
+}
+
+
+class ProtoGameComponent extends GameComponent {
+  ProtoGameComponent(Game game) : super(game);
+
   Widget build() {
-    switch (game.gameType) {
-      case GameType.Proto:
-        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]);
-      default:
-        return null; // unsupported
+    List<Widget> cardCollections = new List<Widget>();
+
+    cardCollections.add(new Text(game.debugString));
+
+    for (int i = 0; i < 4; i++) {
+      List<logic_card.Card> cards = game.cardCollections[i];
+      CardCollectionComponent c = new CardCollectionComponent(
+          cards, game.playerNumber == i, Orientation.horz, _makeGameMoveCallback, dragChildren: true, acceptType: DropType.card);
+      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, _makeGameMoveCallback, dragChildren: true, acceptType: DropType.card)));
+
+    cardCollections.add(_makeSwitchViewButton());
+
+    return new Container(
+        decoration: new BoxDecoration(backgroundColor: colors.Pink[500]),
+        child: new Flex(cardCollections, direction: FlexDirection.vertical));
   }
 
-  _switchPlayersCallback() {
-    setState(() {
-      game.playerNumber = (game.playerNumber + 1) % 4;
-    });
-  }
-
-  _updateGameCallback(Card card, List<Card> dest) {
+  void _makeGameMoveCallback(logic_card.Card card, List<logic_card.Card> dest) {
     setState(() {
       try {
         game.move(card, dest);
@@ -62,30 +86,109 @@
     });
   }
 
-  Widget buildProto() {
-    List<Widget> cardCollections = new List<Widget>();
+  Widget _makeSwitchViewButton() =>
+      _makeButton('Switch View', _switchPlayersCallback);
 
-    cardCollections.add(new Text(game.debugString));
+  void _switchPlayersCallback() {
+    setState(() {
+      game.playerNumber = (game.playerNumber + 1) % 4;
+    });
+  }
+}
 
-    for (int i = 0; i < 4; i++) {
-      List<Card> cards = game.cardCollections[i];
-      CardCollectionComponent c = new CardCollectionComponent(
-          cards, game.playerNumber == i, Orientation.horz, _updateGameCallback);
-      cardCollections.add(c); // flex
+class SyncbaseEchoGameComponent extends GameComponent {
+  SyncbaseEchoImpl s;
+
+  SyncbaseEchoGameComponent(Game game) : super(game);
+
+  Widget build() {
+    if (s == null) {
+      s = new SyncbaseEchoImpl(game);
     }
+    return buildSyncbaseEcho();
+  }
 
-    cardCollections.add(new Container(
-        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));
-
+  Widget buildSyncbaseEcho() {
     return new Container(
-        decoration: new BoxDecoration(backgroundColor: colors.Pink[500]),
-        child: new Flex(cardCollections, direction: FlexDirection.vertical));
+        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));
+  }
+}
+
+class HeartsGameComponent extends GameComponent {
+  List<logic_card.Card> passingCards = new List<logic_card.Card>();
+
+  HeartsGameComponent(Game game) : super(game);
+  Widget build() {
+    return buildHearts();
+    // 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]);
+    // For GameType.Board
+  }
+
+  // Passing between the temporary pass list and the player's hand.
+  // Does not actually move anything in game logic terms.
+  void _uiPassCardCallback(logic_card.Card card, List<logic_card.Card> dest) {
+    setState(() {
+      if (dest == passingCards && !passingCards.contains(card) && passingCards.length < 3) {
+        passingCards.add(card);
+      } else if (dest != passingCards && passingCards.contains(card)) {
+        passingCards.remove(card);
+      }
+    });
+  }
+
+  // This shouldn't always be here, but for now, we have little choice.
+  void _switchPlayersCallback() {
+    setState(() {
+      game.playerNumber = (game.playerNumber + 1) % 4;
+      passingCards.clear(); // Just for sanity.
+    });
+  }
+
+  void _makeGamePassCallback(List<logic_card.Card> cards, List<logic_card.Card> dest) {
+    setState(() {
+      try {
+        HeartsGame game = this.game as HeartsGame;
+        game.passCards(cards);
+        passingCards.clear();
+      } catch (e) {
+        print("You can't do that! ${e.toString()}");
+        game.debugString = e.toString();
+      }
+    });
+  }
+
+  void _makeGameTakeCallback(List<logic_card.Card> cards, List<logic_card.Card> dest) {
+    setState(() {
+      try {
+        HeartsGame game = this.game as HeartsGame;
+        game.takeCards();
+      } catch (e) {
+        print("You can't do that! ${e.toString()}");
+        game.debugString = e.toString();
+      }
+    });
+  }
+
+  void _makeGameMoveCallback(logic_card.Card card, List<logic_card.Card> dest) {
+    setState(() {
+      try {
+        game.move(card, dest);
+      } catch (e) {
+        print("You can't do that! ${e.toString()}");
+        game.debugString = e.toString();
+      }
+    });
   }
 
   Widget _makeSwitchViewButton() =>
@@ -108,7 +211,9 @@
           _makeSwitchViewButton()
         ], direction: FlexDirection.vertical));
       case HeartsPhase.Pass:
+        return showPass();
       case HeartsPhase.Take:
+        return showTake();
       case HeartsPhase.Play:
       case HeartsPhase.Score:
         return showBoard();
@@ -118,20 +223,6 @@
     }
   }
 
-  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;
 
@@ -140,9 +231,9 @@
     cardCollections.add(new Text(game.debugString));
 
     for (int i = 0; i < 4; i++) {
-      List<Card> cards = game.cardCollections[i];
+      List<logic_card.Card> cards = game.cardCollections[i];
       CardCollectionComponent c = new CardCollectionComponent(
-          cards, game.playerNumber == i, Orientation.horz, _updateGameCallback);
+          cards, game.playerNumber == i, Orientation.horz, _makeGameMoveCallback);
       cardCollections.add(c); // flex
     }
 
@@ -150,7 +241,7 @@
         decoration: new BoxDecoration(
             backgroundColor: colors.Green[500], borderRadius: 5.0),
         child: new CardCollectionComponent(game.cardCollections[4], true,
-            Orientation.show1, _updateGameCallback)));
+            Orientation.show1, _makeGameMoveCallback)));
 
     cardCollections.add(new FlatButton(
         child: new Text('Switch View'), onPressed: _switchPlayersCallback));
@@ -159,4 +250,64 @@
         decoration: new BoxDecoration(backgroundColor: colors.Pink[500]),
         child: new Flex(cardCollections, direction: FlexDirection.vertical));
   }
+
+  Widget showPass() {
+    HeartsGame game = this.game as HeartsGame;
+
+    List<logic_card.Card> passCards = game.cardCollections[game.playerNumber + HeartsGame.OFFSET_PASS];
+
+    List<logic_card.Card> playerCards = game.cardCollections[game.playerNumber];
+    List<logic_card.Card> remainingCards = new List<logic_card.Card>();
+    playerCards.forEach((logic_card.Card c) {
+      if (!passingCards.contains(c)){
+        remainingCards.add(c);
+      }
+    });
+
+    bool hasPassed = passCards.length != 0;
+    // TODO(alexfandrianto): You can pass as many times as you want... which is silly.
+    // Luckily, later passes shouldn't do anything.
+
+    return new Container(
+        decoration: new BoxDecoration(backgroundColor: colors.Pink[500]),
+        child: new Flex(<Widget>[
+          new Text(game.debugString),
+          new CardCollectionComponent(passCards, true,
+                Orientation.horz, _makeGamePassCallback, acceptType: DropType.card_collection),
+          new Draggable<CardCollectionComponent>(new CardCollectionComponent(passingCards, true,
+                Orientation.horz, _uiPassCardCallback, dragChildren: !hasPassed, acceptType: DropType.card)),
+          new CardCollectionComponent(remainingCards, true,
+                Orientation.horz, _uiPassCardCallback, dragChildren: !hasPassed, acceptType: DropType.card),
+          new FlatButton(
+            child: new Text('Switch View'),
+            onPressed: _switchPlayersCallback)
+        ], direction: FlexDirection.vertical));
+  }
+
+  Widget showTake() {
+    HeartsGame game = this.game as HeartsGame;
+
+    List<logic_card.Card> playerCards = game.cardCollections[game.playerNumber];
+    List<logic_card.Card> takeCards = game.cardCollections[game.takeTarget + HeartsGame.OFFSET_PASS];
+
+    bool hasTaken = takeCards.length == 0;
+
+    Widget take = new CardCollectionComponent(takeCards, true,
+                Orientation.horz, _makeGameTakeCallback);
+    if (!hasTaken) {
+      take = new Draggable<CardCollectionComponent>(take);
+    }
+
+    return new Container(
+        decoration: new BoxDecoration(backgroundColor: colors.Pink[500]),
+        child: new Flex(<Widget>[
+          new Text(game.debugString),
+          take,
+          new CardCollectionComponent(playerCards, true,
+                Orientation.horz, _makeGameTakeCallback, dragChildren: true, acceptType: DropType.card_collection),
+          new FlatButton(
+            child: new Text('Switch View'),
+            onPressed: _switchPlayersCallback)
+        ], direction: FlexDirection.vertical));
+  }
 }
diff --git a/lib/logic/game.dart b/lib/logic/game.dart
index 142ec30..de9c5f3 100644
--- a/lib/logic/game.dart
+++ b/lib/logic/game.dart
@@ -1,6 +1,7 @@
 import 'card.dart' show Card;
 import 'dart:math' as math;
 import 'syncbase_echo.dart' show SyncbaseEcho;
+import '../src/syncbase/log_writer.dart' show LogWriter;
 
 // Note: Proto and Board are "fake" games intended to demonstrate what we can do.
 // Proto is just a drag cards around "game".
@@ -15,7 +16,7 @@
   final List<Card> deck = new List<Card>.from(Card.All);
 
   final math.Random random = new math.Random();
-  final GameLog gamelog = new GameLog();
+  final GameLog gamelog;
   int playerNumber;
   String debugString = 'hello?';
 
@@ -39,19 +40,19 @@
   // 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) {}
+  Game.dummy(this.gameType, this.gamelog) {}
 
   // A super constructor, don't call this unless you're a subclass.
-  Game._create(this.gameType, this.playerNumber, int numCollections) {
+  Game._create(this.gameType, this.gamelog, this.playerNumber, int numCollections) {
     gamelog.setGame(this);
     for (int i = 0; i < numCollections; i++) {
       cardCollections.add(new List<Card>());
     }
   }
 
-  List<Card> deckPeek(int numCards) {
+  List<Card> deckPeek(int numCards, [int start = 0]) {
     assert(deck.length >= numCards);
-    List<Card> cards = new List<Card>.from(deck.take(numCards));
+    List<Card> cards = new List<Card>.from(deck.getRange(start, start + numCards));
     return cards;
   }
 
@@ -81,7 +82,7 @@
 }
 
 class ProtoGame extends Game {
-  ProtoGame(int playerNumber) : super._create(GameType.Proto, playerNumber, 6) {
+  ProtoGame(int playerNumber) : super._create(GameType.Proto, new ProtoGameLog(), playerNumber, 6) {
     // playerNumber would be used in a real game, but I have to ignore it for debugging.
     // It would determine faceUp/faceDown status.faceDown
 
@@ -167,7 +168,7 @@
   List<bool> ready;
 
   HeartsGame(int playerNumber)
-      : super._create(GameType.Hearts, playerNumber, 16) {
+      : super._create(GameType.Hearts, new HeartsGameLog(), playerNumber, 16) {
     resetGame();
   }
 
@@ -180,10 +181,12 @@
 
   void dealCards() {
     deck.shuffle();
-    deal(PLAYER_A, 13);
-    deal(PLAYER_B, 13);
-    deal(PLAYER_C, 13);
-    deal(PLAYER_D, 13);
+
+    // These things happen asynchronously, so we have to specify all cards now.
+    deal(PLAYER_A, this.deckPeek(13, 0));
+    deal(PLAYER_B, this.deckPeek(13, 13));
+    deal(PLAYER_C, this.deckPeek(13, 26));
+    deal(PLAYER_D, this.deckPeek(13, 39));
   }
 
   int get passTarget {
@@ -308,8 +311,8 @@
     ready = <bool>[false, false, false, false];
   }
 
-  void deal(int playerId, int numCards) {
-    gamelog.add(new HeartsCommand.deal(playerId, this.deckPeek(numCards)));
+  void deal(int playerId, List<Card> cards) {
+    gamelog.add(new HeartsCommand.deal(playerId, cards));
   }
 
   // Note that this will be called by the UI.
@@ -539,32 +542,139 @@
   }
 }
 
-class GameLog {
+abstract class GameLog {
   Game game;
   List<GameCommand> log = new List<GameCommand>();
-  int position = 0;
+  List<GameCommand> pendingCommands = new List<GameCommand>(); // This list is normally empty, but may grow if multiple commands arrive.
+  bool hasFired = false;
+  //int position = 0;
 
   void setGame(Game g) {
     this.game = g;
   }
 
-  // This adds and executes the GameCommand.
   void add(GameCommand gc) {
-    log.add(gc);
+    pendingCommands.add(gc);
+    _tryPendingCommand();
+  }
 
-    while (position < log.length) {
-      log[position].execute(game);
-      game.triggerEvents();
+  void _tryPendingCommand() {
+    if (pendingCommands.length > 0 && !hasFired) {
+      GameCommand gc = pendingCommands[0];
+      if (gc.canExecute(game)) {
+        hasFired = true;
+        addToLogCb(log, gc);
+      } else {
+        // What can we do if the first command isn't allowed to fire?
+        throw new StateError("Cannot run ${gc.data}");
+      }
+    }
+  }
+
+  void update(List<GameCommand> otherLog) {
+    int numMatches = 0;
+    while (numMatches < log.length && numMatches < otherLog.length && log[numMatches] == otherLog[numMatches]) {
+      numMatches++;
+    }
+
+    // At this point, i is at the farthest point of common-ness.
+    // If i matches the log length, then take the rest of the other log.
+    if (numMatches == log.length) {
+      for (int j = numMatches; j < otherLog.length; j++) {
+        log.add(otherLog[j]);
+        if (pendingCommands[0] == otherLog[j]) {
+          pendingCommands.removeAt(0);
+          hasFired = false;
+        }
+        log[j].execute(game);
+        game.triggerEvents();
+      }
       if (game.updateCallback != null) {
         game.updateCallback();
       }
-      position++;
+    } else if (numMatches == otherLog.length) {
+      // We seem to have done more valid moves, so we can just ignore the other side.
+      // TODO(alexfandrianto): If we play a game with actual 'undo' moves,
+      // do we want to record them or erase history?
+      print('Ignoring shorter log');
+    } else {
+      // This case is weird, we have some amount of common log and some mismatch.
+      // Ask the game itself what to do.
+      print('Oh no! A conflict!');
+      log = updateLogCb(log, otherLog, numMatches);
+      assert(false); // What we need to do here is to undo the moves that didn't match and then replay the new ones.
+      // TODO(alexfandrianto): At worst, we can also just reset the game and play through all of it. (No UI updates till the end).
     }
+
+    // Now that we got an update, let's try our other pending commands.
+    _tryPendingCommand();
+  }
+
+  // UNIMPLEMENTED: Let subclasses override this.
+  void addToLogCb(List<GameCommand> log, GameCommand newCommand);
+  List<GameCommand> updateLogCb(List<GameCommand> current, List<GameCommand> other, int mismatchIndex);
+}
+
+class HeartsGameLog extends GameLog {
+  LogWriter logWriter;
+
+  HeartsGameLog() {
+    logWriter = new LogWriter(handleSyncUpdate);
+  }
+
+  Map<String, String> _toLogData(List<GameCommand> log, GameCommand newCommand) {
+    Map<String, String> data = new Map<String, String>();
+    for (int i = 0; i < log.length; i++) {
+      data["${i}"] = log[i].data;
+    }
+    data["${log.length}"] = newCommand.data;
+    return data;
+  }
+  List<HeartsCommand> _logFromData(Map<String, String> data) {
+    List<HeartsCommand> otherlog = new List<HeartsCommand>();
+    otherlog.length = data.length;
+    data.forEach((String k, String v) {
+      otherlog[int.parse(k)] = new HeartsCommand(v);
+    });
+    return otherlog;
+  }
+
+  void handleSyncUpdate(Map<String, String> data) {
+    this.update(_logFromData(data));
+  }
+
+  void addToLogCb(List<GameCommand> log, GameCommand newCommand) {
+    logWriter.write(_toLogData(log, newCommand));
+  }
+  List<GameCommand> updateLogCb(List<GameCommand> current, List<GameCommand> other, int mismatchIndex) {
+    assert(false); // TODO(alexfandrianto): How do you handle conflicts with Hearts?
+    return current;
+  }
+}
+
+class ProtoGameLog extends GameLog {
+  void addToLogCb(List<GameCommand> log, GameCommand newCommand) {
+    update(new List<GameCommand>.from(log)..add(newCommand));
+  }
+  List<GameCommand> updateLogCb(List<GameCommand> current, List<GameCommand> other, int mismatchIndex) {
+    assert(false); // This game can't have conflicts.
+    return current;
   }
 }
 
 abstract class GameCommand {
+  bool canExecute(Game game);
   void execute(Game game);
+  String get data;
+  bool operator ==(Object other) {
+    if (other is GameCommand) {
+      return this.data == other.data;
+    }
+    return false;
+  }
+  String toString() {
+    return data;
+  }
 }
 
 class HeartsCommand extends GameCommand {
@@ -611,6 +721,10 @@
     return "Ready:${playerId}:END";
   }
 
+  bool canExecute(Game g) {
+    return true; // TODO(alexfandrianto): not really. Should do validation too.
+  }
+
   void execute(Game g) {
     HeartsGame game = g as HeartsGame;
 
@@ -754,6 +868,10 @@
     return "Play:${playerId}:${c.toString()}:END";
   }
 
+  bool canExecute(Game game) {
+    return true;
+  }
+
   void execute(Game game) {
     print("ProtoCommand is executing: ${data}");
     List<String> parts = data.split(":");
diff --git a/lib/logic/syncbase_echo.dart b/lib/logic/syncbase_echo.dart
index 2befa0b..5776b23 100644
--- a/lib/logic/syncbase_echo.dart
+++ b/lib/logic/syncbase_echo.dart
@@ -1,5 +1,15 @@
-import 'game.dart' show Game, GameType;
+import 'game.dart' show Game, GameType, GameLog, GameCommand;
 
 class SyncbaseEcho extends Game {
-  SyncbaseEcho() : super.dummy(GameType.SyncbaseEcho);
+  SyncbaseEcho() : super.dummy(GameType.SyncbaseEcho, new SyncbaseEchoLog());
+}
+
+class SyncbaseEchoLog extends GameLog {
+  void addToLogCb(List<GameCommand> log, GameCommand newCommand) {
+    update(new List<GameCommand>.from(log)..add(newCommand));
+  }
+  List<GameCommand> updateLogCb(List<GameCommand> current, List<GameCommand> other, int mismatchIndex) {
+    assert(false); // This game can't have conflicts.
+    return current;
+  }
 }
\ No newline at end of file
diff --git a/lib/src/mocks/log_writer.dart b/lib/src/mocks/log_writer.dart
new file mode 100644
index 0000000..f76ba5b
--- /dev/null
+++ b/lib/src/mocks/log_writer.dart
@@ -0,0 +1,10 @@
+class LogWriter {
+  final Function updateCallback; // Takes in Map<String, String> data
+  LogWriter(this.updateCallback);
+
+  Map<String, String> _data;
+  void write(Map<String, String> data) {
+    _data = data;
+    updateCallback(_data);
+  }
+}
\ No newline at end of file
diff --git a/lib/src/syncbase/log_writer.dart b/lib/src/syncbase/log_writer.dart
new file mode 100644
index 0000000..71c9d64
--- /dev/null
+++ b/lib/src/syncbase/log_writer.dart
@@ -0,0 +1,97 @@
+/// The goal of log writer is to generically manage game logs.
+/// Syncbase will produce values that combine to form a List<GameCommand> while
+/// the in-memory GameLog will also hold such a list.
+///
+/// Updating the GameLog from the Store/Syncbase:
+/// GameLog will update to whatever Store data says.
+/// If it merges, the game log, then it will write that information off.
+/// Case A: Store is farther along than current state.
+/// Continue.
+/// Case B: Store is somehow behind the current state.
+/// Update with the current state of the GameLog (if not sent yet).
+/// Case C: Store's log branches off from the curernt GameLog.
+/// Depending on phase, resolve the conflict differently and write the resolution.
+///
+/// Updating the Store:
+/// When a new GameCommand is received (that doesn't contradict the existing log),
+/// it is added to a list of pending changes and written to the local store.
+
+/// Since this file includes Sky/Mojo, it will need to be mocked out for unit tests.
+/// Unfortunately, imports can't be replaced, so the best thing to do is to swap out the whole file.
+
+import 'dart:async';
+import 'dart:convert' show UTF8, JSON;
+
+import 'package:sky/mojo/embedder.dart' show embedder;
+
+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 LogWriter {
+  final Function updateCallback; // Takes in Map<String, String> data
+  final SyncbaseClient _syncbaseClient;
+
+  LogWriter(this.updateCallback) :
+    _syncbaseClient = new SyncbaseClient(embedder.connectToService,
+        'https://mojo.v.io/syncbase_server.mojo');
+
+  int seq = 0;
+  SyncbaseTable tb;
+  String sendMsg, recvMsg, putStr, getStr;
+
+  Future _doSyncbaseInit() async {
+    log('LogWriter.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');
+
+    // TODO(alexfandrianto): I'm not sure how we setup 'watch', but we would do so here.
+  }
+
+  Future write(Map<String, String> data) async {
+    log('LogWriter.write start');
+    await _doSyncbaseInit();
+
+    var row = tb.row('key');
+    await row.put(UTF8.encode(JSON.encode(data)));
+
+    // TODO(alexfandrianto): Normally, we would watch, but since I don't know how, I will just poll here.
+    await _poll();
+    log('LogWriter.start done');
+  }
+
+  Future _poll() async {
+    log('LogWriter.poll start');
+    await _doSyncbaseInit();
+
+    // Realistically, we wouldn't write it all to a single row, but I don't think it matters right now.
+    var row = tb.row('key');
+    var getBytes = await row.get();
+
+    Map<String, String> data = JSON.decode(UTF8.decode(getBytes));
+    this.updateCallback(data);
+    log('LogWriter.poll done');
+  }
+}
diff --git a/lib/logic/syncbase_echo_impl.dart b/lib/src/syncbase/syncbase_echo_impl.dart
similarity index 97%
rename from lib/logic/syncbase_echo_impl.dart
rename to lib/src/syncbase/syncbase_echo_impl.dart
index 4343d57..2eedd71 100644
--- a/lib/logic/syncbase_echo_impl.dart
+++ b/lib/src/syncbase/syncbase_echo_impl.dart
@@ -1,7 +1,7 @@
 import 'dart:async';
 import 'dart:convert' show UTF8;
 
-import 'game.dart' show Game;
+import '../../logic/game.dart' show Game;
 
 import 'package:sky/mojo/embedder.dart' show embedder;