This adds a the notion of a game log to Croupier.
That is why when the app is opened, a few things will play out automatically.
(That will soon be moved to its own file for a test.)
You can also switch the player view.
diff --git a/lib/components/card_collection.dart b/lib/components/card_collection.dart
index d041951..32aa14c 100644
--- a/lib/components/card_collection.dart
+++ b/lib/components/card_collection.dart
@@ -109,7 +109,7 @@
             ),
             backgroundColor: data.isEmpty ? colors.Grey[500] : colors.Green[500]
           ),
-          height: 100.0,
+          height: 80.0,
           margin: new EdgeDims.all(10.0),
           child: wrapCards(cardComponents)//new Stack(cardComponents)
         );
diff --git a/lib/components/game.dart b/lib/components/game.dart
index 246393b..7d96544 100644
--- a/lib/components/game.dart
+++ b/lib/components/game.dart
@@ -1,7 +1,8 @@
 import '../logic/card.dart' show Card;
-import '../logic/game.dart' show Game, GameType;
+import '../logic/game.dart' show Game, GameType, Viewer;
 import 'card_collection.dart' show CardCollectionComponent, Orientation;
 import 'package:sky/widgets/basic.dart';
+import 'package:sky/widgets.dart' show FlatButton;
 import 'package:sky/theme/colors.dart' as colors;
 import 'card_constants.dart' as card_constants;
 import 'package:vector_math/vector_math.dart' as vector_math;
@@ -9,7 +10,13 @@
 class GameComponent extends StatefulComponent {
   Game game;
 
-  GameComponent(this.game);
+  GameComponent(this.game) {
+    game.updateCallback = update;
+  }
+
+  void update() {
+    setState(() {});
+  }
 
   void syncFields(GameComponent other) {
     this.game = other.game;
@@ -24,6 +31,12 @@
     }
   }
 
+  _switchPlayersCallback() {
+    setState(() {
+      game.playerNumber = (game.playerNumber + 1) % 4;
+    });
+  }
+
   _updateGameCallback(Card card, List<Card> dest) {
     setState(() {
       game.move(card, dest);
@@ -38,7 +51,7 @@
 
     for (int i = 0; i < 4; i++) {
       List<Card> cards = game.cardCollections[i];
-      CardCollectionComponent c = new CardCollectionComponent(cards, true, Orientation.horz, _updateGameCallback);
+      CardCollectionComponent c = new CardCollectionComponent(cards, game.playerNumber == i, Orientation.horz, _updateGameCallback);
 
       /*cardCollections.add(new Positioned(
         top: i * (card_constants.CARD_HEIGHT + 20.0),
@@ -76,6 +89,10 @@
 
     // game.cardCollections[5] is just not shown
 
+    cardCollections.add(new FlatButton(
+      child: new Text('Switch View'),
+      onPressed: _switchPlayersCallback
+    ));
 
     return new Container(
       decoration: new BoxDecoration(backgroundColor: colors.Pink[500]),
diff --git a/lib/logic/card.dart b/lib/logic/card.dart
index 021495f..157e1b9 100644
--- a/lib/logic/card.dart
+++ b/lib/logic/card.dart
@@ -2,7 +2,15 @@
   final String deck;
   final String identifier;
 
-  const Card(this.deck, this.identifier);
+  Card(this.deck, this.identifier);
+  Card.fromString(String cardData) : deck = cardData.split(" ")[0], identifier = cardData.split(" ")[1];
+
+  bool operator ==(Object other) {
+    if (other is! Card) return false;
+    Card o = other as Card;
+    return deck == o.deck && identifier == o.identifier;
+  }
+  int get hashCode => 37 * (deck.hashCode + 41 * identifier.hashCode);
 
   toString() => "${deck} ${identifier}";
 
diff --git a/lib/logic/game.dart b/lib/logic/game.dart
index 0aeaf9d..0d18a26 100644
--- a/lib/logic/game.dart
+++ b/lib/logic/game.dart
@@ -11,83 +11,96 @@
   final GameType gameType;
   final List<List<Card>> cardCollections = new List<List<Card>>();
   final List<Card> deck = <Card>[
-    const Card("classic", "c1"),
-    const Card("classic", "c2"),
-    const Card("classic", "c3"),
-    const Card("classic", "c4"),
-    const Card("classic", "c5"),
-    const Card("classic", "c6"),
-    const Card("classic", "c7"),
-    const Card("classic", "c8"),
-    const Card("classic", "c9"),
-    const Card("classic", "c10"),
-    const Card("classic", "cj"),
-    const Card("classic", "cq"),
-    const Card("classic", "ck"),
-    const Card("classic", "d1"),
-    const Card("classic", "d2"),
-    const Card("classic", "d3"),
-    const Card("classic", "d4"),
-    const Card("classic", "d5"),
-    const Card("classic", "d6"),
-    const Card("classic", "d7"),
-    const Card("classic", "d8"),
-    const Card("classic", "d9"),
-    const Card("classic", "d10"),
-    const Card("classic", "dj"),
-    const Card("classic", "dq"),
-    const Card("classic", "dk"),
-    const Card("classic", "h1"),
-    const Card("classic", "h2"),
-    const Card("classic", "h3"),
-    const Card("classic", "h4"),
-    const Card("classic", "h5"),
-    const Card("classic", "h6"),
-    const Card("classic", "h7"),
-    const Card("classic", "h8"),
-    const Card("classic", "h9"),
-    const Card("classic", "h10"),
-    const Card("classic", "hj"),
-    const Card("classic", "hq"),
-    const Card("classic", "hk"),
-    const Card("classic", "s1"),
-    const Card("classic", "s2"),
-    const Card("classic", "s3"),
-    const Card("classic", "s4"),
-    const Card("classic", "s5"),
-    const Card("classic", "s6"),
-    const Card("classic", "s7"),
-    const Card("classic", "s8"),
-    const Card("classic", "s9"),
-    const Card("classic", "s10"),
-    const Card("classic", "sj"),
-    const Card("classic", "sq"),
-    const Card("classic", "sk"),
+    new Card("classic", "c1"),
+    new Card("classic", "c2"),
+    new Card("classic", "c3"),
+    new Card("classic", "c4"),
+    new Card("classic", "c5"),
+    new Card("classic", "c6"),
+    new Card("classic", "c7"),
+    new Card("classic", "c8"),
+    new Card("classic", "c9"),
+    new Card("classic", "c10"),
+    new Card("classic", "cj"),
+    new Card("classic", "cq"),
+    new Card("classic", "ck"),
+    new Card("classic", "d1"),
+    new Card("classic", "d2"),
+    new Card("classic", "d3"),
+    new Card("classic", "d4"),
+    new Card("classic", "d5"),
+    new Card("classic", "d6"),
+    new Card("classic", "d7"),
+    new Card("classic", "d8"),
+    new Card("classic", "d9"),
+    new Card("classic", "d10"),
+    new Card("classic", "dj"),
+    new Card("classic", "dq"),
+    new Card("classic", "dk"),
+    new Card("classic", "h1"),
+    new Card("classic", "h2"),
+    new Card("classic", "h3"),
+    new Card("classic", "h4"),
+    new Card("classic", "h5"),
+    new Card("classic", "h6"),
+    new Card("classic", "h7"),
+    new Card("classic", "h8"),
+    new Card("classic", "h9"),
+    new Card("classic", "h10"),
+    new Card("classic", "hj"),
+    new Card("classic", "hq"),
+    new Card("classic", "hk"),
+    new Card("classic", "s1"),
+    new Card("classic", "s2"),
+    new Card("classic", "s3"),
+    new Card("classic", "s4"),
+    new Card("classic", "s5"),
+    new Card("classic", "s6"),
+    new Card("classic", "s7"),
+    new Card("classic", "s8"),
+    new Card("classic", "s9"),
+    new Card("classic", "s10"),
+    new Card("classic", "sj"),
+    new Card("classic", "sq"),
+    new Card("classic", "sk"),
   ];
 
   final Random random = new Random();
+  final GameLog gamelog = new GameLog();
+  int playerNumber;
+  Function updateCallback;
   String debugString = 'hello?';
 
-  Game.hearts(int playerNumber) : gameType = GameType.Hearts {
+  Game.hearts(this.playerNumber) : gameType = GameType.Hearts {
+    gamelog.setGame(this);
+
     // playerNumber would be used in a real game, but I have to ignore it for debugging.
     // It would determine faceUp/faceDown status.
 
     deck.shuffle();
-    cardCollections.add(this.deal(10));
-    cardCollections.add(this.deal(5));
-    cardCollections.add(this.deal(1));
-    cardCollections.add(this.deal(1));
-    cardCollections.add(new List<Card>()); // an empty pile TODO(alexfandrianto): Why can't I just have an empty stack?
+    cardCollections.add(new List<Card>()); // Player A
+    cardCollections.add(new List<Card>()); // Player B
+    cardCollections.add(new List<Card>()); // Player C
+    cardCollections.add(new List<Card>()); // Player D
+    cardCollections.add(new List<Card>()); // an empty pile
     cardCollections.add(new List<Card>()); // a hidden pile!
+
+    /*deal(0, 8);
+    deal(1, 5);
+    deal(2, 4);
+    deal(3, 1);*/
   }
 
-  List<Card> deal(int numCards) {
+  List<Card> deckPeek(int numCards) {
     assert(deck.length >= numCards);
     List<Card> cards = new List<Card>.from(deck.take(numCards));
-    deck.removeRange(0, numCards);
     return cards;
   }
 
+  void deal(int playerId, int numCards) {
+    gamelog.add(new HeartsCommand.deal(playerId, this.deckPeek(numCards)));
+  }
+
   void move(Card card, List<Card> dest) {
     // The first step is to find the card. Where is it?
     // then we can remove it and add to the dest.
@@ -97,8 +110,12 @@
       debugString = 'NO... ${card.toString()}';
       return;
     }
-    cardCollections[i].remove(card);
-    dest.add(card);
+    int destId = cardCollections.indexOf(dest);
+
+    gamelog.add(new HeartsCommand.pass(i, destId, <Card>[card]));
+
+    /*cardCollections[i].remove(card);
+    dest.add(card);*/
     debugString = 'Move ${i} ${card.toString()}';
     print(debugString);
   }
@@ -112,4 +129,117 @@
     }
     return -1;
   }
+}
+
+class GameLog {
+  Game game;
+  List<GameCommand> log = new List<GameCommand>();
+  int position = 0;
+
+  void setGame(Game g) {
+    this.game = g;
+  }
+
+  // This adds and executes the GameCommand.
+  void add(GameCommand gc) {
+    log.add(gc);
+
+    while (position < log.length) {
+      log[position].execute(game);
+      if (game.updateCallback != null) {
+        game.updateCallback();
+      }
+      position++;
+    }
+  }
+}
+
+abstract class GameCommand {
+  void execute(Game game);
+}
+
+
+class HeartsCommand extends GameCommand {
+  final String data; // This will be parsed.
+
+  // Usually this constructor is used when reading from a log/syncbase.
+  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);
+
+  // TODO: receiverId is actually implied by the game round. So it may end up being removable.
+  HeartsCommand.pass(int senderId, int receiverId, List<Card> cards) :
+    this.data = computePass(senderId, receiverId, cards);
+
+  HeartsCommand.play(int playerId, Card c) :
+    this.data = computePlay(playerId, c);
+
+  static computeDeal(int playerId, List<Card> cards) {
+    StringBuffer buff = new StringBuffer();
+    buff.write("Deal:${playerId}:");
+    cards.forEach((card) => buff.write("${card.toString()}:"));
+    buff.write("END");
+    return buff.toString();
+  }
+  static computePass(int senderId, int receiverId, List<Card> cards) {
+    StringBuffer buff = new StringBuffer();
+    buff.write("Pass:${senderId}:${receiverId}:");
+    cards.forEach((card) => buff.write("${card.toString()}:"));
+    buff.write("END");
+    return buff.toString();
+  }
+  static computePlay(int playerId, Card c) {
+    return "Play:${playerId}:${c.toString()}:END";
+  }
+
+  void execute(Game game) {
+    print("HeartsCommand is executing: ${data}");
+    List<String> parts = data.split(":");
+    switch (parts[0]) {
+      case "Deal":
+        // Deal appends cards to playerId's hand.
+        int playerId = int.parse(parts[1]);
+        List<Card> hand = game.cardCollections[playerId];
+
+        // The last part is 'END', but the rest are cards.
+        for (int i = 2; i < parts.length - 1; i++) {
+          Card c = new Card.fromString(parts[i]);
+          this.transfer(game.deck, hand, c);
+        }
+        return;
+      case "Pass":
+        // Pass moves a set of cards from senderId to receiverId.
+        int senderId = int.parse(parts[1]);
+        int receiverId = int.parse(parts[2]);
+        List<Card> handS = game.cardCollections[senderId];
+        List<Card> handR = game.cardCollections[receiverId];
+
+        // The last part is 'END', but the rest are cards.
+        for (int i = 3; i < parts.length - 1; i++) {
+          Card c = new Card.fromString(parts[i]);
+          this.transfer(handS, handR, c);
+        }
+        return;
+      case "Play":
+        // In this case, move it to the designated discard pile.
+        // For now, the discard pile is pile #4. This may change.
+        int playerId = int.parse(parts[1]);
+        List<Card> hand = game.cardCollections[playerId];
+
+        Card c = new Card.fromString(parts[2]);
+        this.transfer(hand, game.cardCollections[4], c);
+        return;
+      default:
+        print(data);
+        assert(false); // How could this have happened?
+    }
+  }
+
+  void transfer(List<Card> sender, List<Card> receiver, Card c) {
+    assert(sender.contains(c));
+    sender.remove(c);
+    receiver.add(c);
+  }
 }
\ No newline at end of file
diff --git a/lib/main.dart b/lib/main.dart
index 4a6f279..7a6ea14 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -7,9 +7,13 @@
 //import 'package:sky/theme/colors.dart' as colors;
 import 'package:sky/widgets.dart';
 
-import 'logic/game.dart' show Game;
+import 'logic/game.dart' show Game, HeartsCommand;
 import 'components/game.dart' show GameComponent;
 
+import 'dart:io';
+import 'dart:convert';
+import 'dart:async';
+
 class CroupierApp extends App {
   Game game;
 
@@ -23,5 +27,32 @@
 }
 
 void main() {
-  runApp(new CroupierApp());
+  CroupierApp app = new CroupierApp();
+
+  // Had difficulty reading from a file, so I can use this to simulate it.
+  // Seems like only NetworkImage exists, but why not also have NetworkFile?
+  List<String> commands = <String>[
+    "Deal:0:classic h1:classic h2:classic h3:classic h4:END",
+    "Deal:1:classic d1:classic d2:classic d3:classic d4:END",
+    "Deal:2:classic s1:classic s2:classic s3:classic s4:END",
+    "Deal:3:classic c1:classic c2:classic c3:classic c4:END",
+    "Pass:0:1:classic h2:classic h3:END",
+    "Pass:1:2:classic d1:classic d4:END",
+    "Play:0:classic h1:END",
+    "Play:1:classic d3:END",
+    "Play:2:classic d4:END",
+    "Play:3:classic c2:END"
+  ];
+  new Future.delayed(new Duration(seconds: 2)).then((_) {
+    for (var i = 0; i < commands.length; i++) {
+      new Future.delayed(new Duration(seconds: 1*i)).then((_) {
+        app.game.gamelog.add(new HeartsCommand(commands[i]));
+      });
+    }
+  });
+
+
+  runApp(app);
+
+
 }
\ No newline at end of file