croupier: Add TakeTrick command, Remove initial Ready commands

From user feedback, it would be nice if players had a greater
opportunity to see the trick cards. We have added a button that
will manually allow users to take the trick. Animation only
begins after that button is pressed.

The tests were updated to reflect this new command.

The StartGame Ready commands were removed, since they were never
really needed.

Change-Id: I318c81277b18a7e8d4f080047708ce25a4af6c7e
diff --git a/lib/components/board.dart b/lib/components/board.dart
index bb611ac..382eb85 100644
--- a/lib/components/board.dart
+++ b/lib/components/board.dart
@@ -50,8 +50,6 @@
   final Croupier croupier;
   final bool isMini;
   final AcceptCb gameAcceptCallback;
-  final bool trickTaking;
-  final List<List<logic_card.Card>> playedCards;
 
   HeartsBoard(Croupier croupier,
       {double height,
@@ -59,9 +57,7 @@
       double cardHeight,
       double cardWidth,
       this.isMini: false,
-      this.gameAcceptCallback,
-      this.trickTaking,
-      this.playedCards})
+      this.gameAcceptCallback})
       : super(croupier.game,
             height: height,
             width: width,
@@ -148,15 +144,11 @@
 
     List<Widget> items = new List<Widget>();
     bool isMe = playerNumber == p;
-    bool isPlayerTurn = playerNumber == game.whoseTurn && !config.trickTaking;
+    bool isPlayerTurn = playerNumber == game.whoseTurn && !game.allPlayed;
 
     List<logic_card.Card> showCard =
         game.cardCollections[playerNumber + HeartsGame.OFFSET_PLAY];
 
-    if (config.trickTaking) {
-      showCard = config.playedCards[playerNumber];
-    }
-
     items.add(new Positioned(
         top: 0.0,
         left: 0.0,
@@ -307,9 +299,6 @@
     HeartsGame game = config.game;
     List<logic_card.Card> cards =
         game.cardCollections[playerNumber + HeartsGame.OFFSET_PLAY];
-    if (config.trickTaking) {
-      cards = config.playedCards[playerNumber];
-    }
 
     return new Container(
         decoration: game.whoseTurn == playerNumber ? style.Box.liveNow : null,
@@ -323,14 +312,8 @@
   Widget _buildOffScreenCards(int playerNumber) {
     HeartsGame game = config.game;
 
-    List<logic_card.Card> cards =
-        game.cardCollections[playerNumber + HeartsGame.OFFSET_TRICK];
-    // If took trick, exclude the last 4 cards for the trick taking animation.
-    if (config.trickTaking && playerNumber == game.lastTrickTaker) {
-      cards = new List.from(cards.sublist(0, cards.length - 4));
-    } else {
-      cards = new List.from(cards);
-    }
+    List<logic_card.Card> cards = new List.from(
+        game.cardCollections[playerNumber + HeartsGame.OFFSET_TRICK]);
 
     double sizeFactor = 2.0;
     if (config.isMini) {
diff --git a/lib/components/game.dart b/lib/components/game.dart
index 7a858ad..c798c71 100644
--- a/lib/components/game.dart
+++ b/lib/components/game.dart
@@ -5,7 +5,6 @@
 library game_component;
 
 import 'dart:math' as math;
-import 'dart:async';
 
 import 'package:flutter/scheduler.dart';
 import 'package:flutter/material.dart';
diff --git a/lib/components/hearts/hearts.part.dart b/lib/components/hearts/hearts.part.dart
index d74946b..174da16 100644
--- a/lib/components/hearts/hearts.part.dart
+++ b/lib/components/hearts/hearts.part.dart
@@ -19,10 +19,6 @@
 
   HeartsType _lastViewType;
   bool _showSplitView = false;
-  bool trickTaking = false;
-  List<List<logic_card.Card>> playedCards = new List<List<logic_card.Card>>(4);
-
-  static const int SHOW_TRICK_DURATION = 2000; // ms
 
   @override
   void initState() {
@@ -34,46 +30,6 @@
       _showSplitView = true;
     }
     _reset();
-
-    _fillPlayedCards();
-  }
-
-  // Make copies of the played cards.
-  void _fillPlayedCards() {
-    for (int i = 0; i < 4; i++) {
-      playedCards[i] = new List<logic_card.Card>.from(
-          config.game.cardCollections[i + HeartsGame.OFFSET_PLAY]);
-    }
-  }
-
-  // If there were 3 played cards before and now there are 0...
-  bool _detectTrick() {
-    HeartsGame game = config.game;
-    int lastNumPlayed = playedCards.where((List<logic_card.Card> list) {
-      return list.length > 0;
-    }).length;
-    return lastNumPlayed == 3 && game.numPlayed == 0;
-  }
-
-  // Make a copy of the missing played card.
-  void _fillMissingPlayedCard() {
-    HeartsGame game = config.game;
-    List<logic_card.Card> trickPile =
-        game.cardCollections[game.lastTrickTaker + HeartsGame.OFFSET_TRICK];
-
-    // Find the index of the missing play card.
-    int missing;
-    for (int j = 0; j < 4; j++) {
-      if (playedCards[j].length == 0) {
-        missing = j;
-        break;
-      }
-    }
-
-    // Use the trickPile to get this card.
-    playedCards[missing] = <logic_card.Card>[
-      trickPile[trickPile.length - 4 + missing]
-    ];
   }
 
   @override
@@ -92,24 +48,6 @@
       _reset();
     }
 
-    // Set the trickTaking flag on each build.
-    if (!trickTaking) {
-      if (_detectTrick()) {
-        trickTaking = true;
-        _fillMissingPlayedCard();
-        // Unfortunately, ZCards are drawn on the game layer,
-        // so instead of setState, we must use trueSetState.
-        new Future.delayed(const Duration(milliseconds: SHOW_TRICK_DURATION),
-            () {
-          setState(() {
-            trickTaking = false;
-          });
-        });
-      } else {
-        _fillPlayedCards();
-      }
-    }
-
     // Hearts Widget
     Widget heartsWidget = new Container(
         decoration: new BoxDecoration(backgroundColor: Colors.grey[300]),
@@ -122,9 +60,7 @@
         height: config.height,
         child: heartsWidget));
     List<int> visibleCardCollectionIndexes = new List<int>();
-    if (game.phase != HeartsPhase.StartGame &&
-        game.phase != HeartsPhase.Deal &&
-        game.phase != HeartsPhase.Score) {
+    if (game.phase != HeartsPhase.Deal && game.phase != HeartsPhase.Score) {
       int playerNum = game.playerNumber;
       if (game.viewType == HeartsType.Player) {
         switch (game.phase) {
@@ -343,7 +279,6 @@
     }
 
     switch (game.phase) {
-      case HeartsPhase.StartGame:
       case HeartsPhase.Deal:
         return showDeal();
       case HeartsPhase.Pass:
@@ -364,7 +299,6 @@
     HeartsGame game = config.game as HeartsGame;
     List<Widget> kids = new List<Widget>();
     switch (game.phase) {
-      case HeartsPhase.StartGame:
       case HeartsPhase.Deal:
         kids.add(new Text("Waiting for Deal..."));
         break;
@@ -385,10 +319,7 @@
 
   Widget showBoard() {
     return new HeartsBoard(config.croupier,
-        width: config.width,
-        height: 0.80 * config.height,
-        trickTaking: trickTaking,
-        playedCards: playedCards);
+        width: config.width, height: 0.80 * config.height);
   }
 
   String _getName(int playerNumber) {
@@ -408,10 +339,10 @@
             : "${name}'s turn";
 
         // Override if someone is taking a trick.
-        if (this.trickTaking) {
-          String trickTaker =
-              _getName(game.lastTrickTaker) ?? "Player ${game.lastTrickTaker}";
-          status = game.lastTrickTaker == game.playerNumber
+        if (game.allPlayed) {
+          int winner = game.determineTrickWinner();
+          String trickTaker = _getName(winner) ?? "Player ${winner}";
+          status = winner == game.playerNumber
               ? "Your trick"
               : "${trickTaker}'s trick";
         }
@@ -449,12 +380,28 @@
   Widget _buildStatusBar() {
     HeartsGame game = config.game;
 
-    List<Widget> statusWidgets = new List<Widget>();
-    statusWidgets.add(new Text(_getStatus(), style: style.Text.largeStyle));
+    List<Widget> statusBarWidgets = new List<Widget>();
+    statusBarWidgets.add(new Flexible(
+        flex: 1, child: new Text(_getStatus(), style: style.Text.largeStyle)));
 
     switch (game.phase) {
       case HeartsPhase.Play:
-        statusWidgets
+        if (game.allPlayed &&
+            game.determineTrickWinner() == game.playerNumber) {
+          statusBarWidgets.add(new Flexible(
+              flex: 0,
+              child: new GestureDetector(onTap: () {
+                setState(() {
+                  game.takeTrickUI();
+                });
+              },
+                  child: new Container(
+                      decoration: style.Box.liveBackground,
+                      padding: style.Spacing.smallPadding,
+                      child: new Text("Take Cards",
+                          style: style.Text.largeStyle)))));
+        }
+        statusBarWidgets
             .add(new IconButton(icon: "action/swap_vert", onPressed: () {
           setState(() {
             _showSplitView = !_showSplitView;
@@ -478,7 +425,7 @@
         if (game.phase == HeartsPhase.Take) {
           rotationAngle = rotationAngle + math.PI; // opposite
         }
-        statusWidgets.add(new Transform(
+        statusBarWidgets.add(new Transform(
             transform:
                 new vector_math.Matrix4.identity().rotateZ(rotationAngle),
             alignment: new FractionalOffset(0.5, 0.5),
@@ -492,7 +439,7 @@
         padding: new EdgeDims.all(10.0),
         decoration:
             new BoxDecoration(backgroundColor: style.theme.primaryColor),
-        child: new Row(statusWidgets,
+        child: new Row(statusBarWidgets,
             justifyContent: FlexJustifyContent.spaceBetween));
   }
 
@@ -506,9 +453,7 @@
             cardWidth: config.height * 0.1,
             cardHeight: config.height * 0.1,
             isMini: true,
-            gameAcceptCallback: _makeGameMoveCallback,
-            trickTaking: trickTaking,
-            playedCards: playedCards));
+            gameAcceptCallback: _makeGameMoveCallback));
   }
 
   Widget showPlay() {
@@ -808,9 +753,6 @@
   Widget _buildSlot(String name, int index) {
     NoArgCb onTap = () {
       croupier.settings_manager.setPlayerNumber(croupier.game.gameID, index);
-      HeartsGame game = croupier.game;
-      game.playerNumber = index;
-      game.setReadyUI();
     };
     Widget slotWidget = new Text(name, style: style.Text.hugeStyle);
 
diff --git a/lib/logic/croupier.dart b/lib/logic/croupier.dart
index 53d1824..b6096b5 100644
--- a/lib/logic/croupier.dart
+++ b/lib/logic/croupier.dart
@@ -156,6 +156,8 @@
         if (state == CroupierState.ArrangePlayers) {
           game.startGameSignal();
           setState(CroupierState.PlayGame, null);
+        } else if (state == CroupierState.ResumeGame) {
+          game.startGameSignal();
         }
         break;
       default:
diff --git a/lib/logic/hearts/hearts_command.part.dart b/lib/logic/hearts/hearts_command.part.dart
index bfd62da..fe6826b 100644
--- a/lib/logic/hearts/hearts_command.part.dart
+++ b/lib/logic/hearts/hearts_command.part.dart
@@ -30,6 +30,10 @@
       : super("Play", computePlay(playerId, c),
             simultaneity: SimulLevel.TURN_BASED);
 
+  HeartsCommand.takeTrick()
+      : super("TakeTrick", computeTakeTrick(),
+            simultaneity: SimulLevel.TURN_BASED);
+
   HeartsCommand.ready(int playerId)
       : super("Ready", computeReady(playerId),
             simultaneity: SimulLevel.INDEPENDENT);
@@ -44,6 +48,8 @@
         return SimulLevel.INDEPENDENT;
       case "Play":
         return SimulLevel.TURN_BASED;
+      case "TakeTrick":
+        return SimulLevel.TURN_BASED;
       case "Ready":
         return SimulLevel.INDEPENDENT;
       default:
@@ -77,6 +83,10 @@
     return "${playerId}:${c.toString()}:END";
   }
 
+  static String computeTakeTrick() {
+    return "END";
+  }
+
   static String computeReady(int playerId) {
     return "${playerId}:END";
   }
@@ -160,12 +170,18 @@
         }
         bool canTransfer = this.transferCheck(hand, discard, c);
         return canTransfer;
+      case "TakeTrick":
+        if (game.phase != HeartsPhase.Play) {
+          return false;
+        }
+
+        // There must be 4 cards played.
+        return game.allPlayed;
       case "Ready":
         if (game.hasGameEnded) {
           return false;
         }
-        if (game.phase != HeartsPhase.Score &&
-            game.phase != HeartsPhase.StartGame) {
+        if (game.phase != HeartsPhase.Score) {
           return false;
         }
         return true;
@@ -257,15 +273,45 @@
         }
         this.transfer(hand, discard, c);
         return;
+      case "TakeTrick":
+        if (game.phase != HeartsPhase.Play) {
+          throw new StateError(
+              "Cannot process take trick commands when not in Play phase");
+        }
+        if (!game.allPlayed) {
+          throw new StateError(
+              "Cannot take trick when some players have not played");
+        }
+
+        // Determine who won this trick.
+        int winner = game.determineTrickWinner();
+
+        // Move the cards to their trick list. Also check if hearts was broken.
+        // Note: While some variants of Hearts allow the QUEEN_OF_SPADES to
+        // break hearts, this version does NOT implement that rule.
+        for (int i = 0; i < 4; i++) {
+          List<Card> play = game.cardCollections[i + HeartsGame.OFFSET_PLAY];
+          if (!game.heartsBroken && game.isHeartsCard(play[0])) {
+            game.heartsBroken = true;
+          }
+          game.cardCollections[winner + HeartsGame.OFFSET_TRICK]
+              .addAll(play); // or add(play[0])
+          play.clear();
+        }
+
+        // Set them as the next person to go.
+        game.lastTrickTaker = winner;
+        game.trickNumber++;
+
+        return;
       case "Ready":
         if (game.hasGameEnded) {
           throw new StateError(
               "Game has already ended. Start a new one to play again.");
         }
-        if (game.phase != HeartsPhase.Score &&
-            game.phase != HeartsPhase.StartGame) {
+        if (game.phase != HeartsPhase.Score) {
           throw new StateError(
-              "Cannot process ready commands when not in Score or StartGame phase");
+              "Cannot process ready commands when not in Score phase");
         }
         int playerId = int.parse(parts[0]);
         game.setReady(playerId);
diff --git a/lib/logic/hearts/hearts_game.part.dart b/lib/logic/hearts/hearts_game.part.dart
index 8a25172..53043a4 100644
--- a/lib/logic/hearts/hearts_game.part.dart
+++ b/lib/logic/hearts/hearts_game.part.dart
@@ -39,7 +39,7 @@
 
   HeartsType viewType = HeartsType.Player;
 
-  HeartsPhase _phase = HeartsPhase.StartGame;
+  HeartsPhase _phase = HeartsPhase.Deal;
   HeartsPhase get phase => _phase;
   void set phase(HeartsPhase other) {
     print('setting phase from ${_phase} to ${other}');
@@ -259,7 +259,7 @@
   // Note that this will be called by the UI.
   // It won't be possible to set the readiness for other players, except via the GameLog.
   void setReadyUI() {
-    assert(phase == HeartsPhase.Score || phase == HeartsPhase.StartGame);
+    assert(phase == HeartsPhase.Score);
     if (this.debugMode) {
       // Debug Mode should pretend this device is all players.
       for (int i = 0; i < 4; i++) {
@@ -270,6 +270,13 @@
     }
   }
 
+  // Note that this will be called by the UI.
+  void takeTrickUI() {
+    assert(phase == HeartsPhase.Play);
+    assert(this.allPlayed);
+    gamelog.add(new HeartsCommand.takeTrick());
+  }
+
   static final GameArrangeData _arrangeData =
       new GameArrangeData(true, new Set.from([0, 1, 2, 3]));
   GameArrangeData get gameArrangeData => _arrangeData;
@@ -329,14 +336,6 @@
   @override
   void triggerEvents() {
     switch (this.phase) {
-      case HeartsPhase.StartGame:
-        if (this.allReady) {
-          phase = HeartsPhase.Deal;
-          this.resetGame();
-
-          print('we are all ready. ${isCreator}');
-        }
-        return;
       case HeartsPhase.Deal:
         if (this.allDealt) {
           if (this.passTarget != null) {
@@ -363,31 +362,10 @@
         }
         return;
       case HeartsPhase.Play:
-        if (this.allPlayed) {
-          // Determine who won this trick.
-          int winner = this.determineTrickWinner();
-
-          // Move the cards to their trick list. Also check if hearts was broken.
-          // Note: Some variants of Hearts allows the QUEEN_OF_SPADES to break hearts too.
-          for (int i = 0; i < 4; i++) {
-            List<Card> play = this.cardCollections[i + OFFSET_PLAY];
-            if (!heartsBroken && isHeartsCard(play[0])) {
-              heartsBroken = true;
-            }
-            this.cardCollections[winner + OFFSET_TRICK]
-                .addAll(play); // or add(play[0])
-            play.clear();
-          }
-
-          // Set them as the next person to go.
-          this.lastTrickTaker = winner;
-          this.trickNumber++;
-
-          // Additionally, if that was the last trick, move onto the score phase.
-          if (this.trickNumber == 13) {
-            phase = HeartsPhase.Score;
-            this.prepareScore();
-          }
+        // If that was the last trick, move onto the score phase.
+        if (this.trickNumber == 13) {
+          phase = HeartsPhase.Score;
+          this.prepareScore();
         }
         return;
       case HeartsPhase.Score:
@@ -415,6 +393,9 @@
     if (!cardCollections[player].contains(c)) {
       return "Player ${player} does not have the card (${c.toString()})";
     }
+    if (this.allPlayed) {
+      return "Trick not taken yet.";
+    }
     if (this.whoseTurn != player) {
       return "It is not Player ${player}'s turn.";
     }
diff --git a/lib/logic/hearts/hearts_phase.part.dart b/lib/logic/hearts/hearts_phase.part.dart
index 42b25f3..55894f5 100644
--- a/lib/logic/hearts/hearts_phase.part.dart
+++ b/lib/logic/hearts/hearts_phase.part.dart
@@ -4,4 +4,4 @@
 
 part of hearts;
 
-enum HeartsPhase { StartGame, Deal, Pass, Take, Play, Score }
+enum HeartsPhase { Deal, Pass, Take, Play, Score }
diff --git a/lib/styles/common.dart b/lib/styles/common.dart
index 894dcc5..ac4fd52 100644
--- a/lib/styles/common.dart
+++ b/lib/styles/common.dart
@@ -29,12 +29,15 @@
 }
 
 class Spacing {
+  static final EdgeDims smallPadding = new EdgeDims.all(5.0);
   static final EdgeDims normalPadding = new EdgeDims.all(10.0);
 }
 
 class Box {
   static final BoxDecoration liveNow = new BoxDecoration(
       border: new Border.all(color: theme.accentColor), borderRadius: 2.0);
+  static final BoxDecoration liveBackground =
+      new BoxDecoration(backgroundColor: theme.accentColor);
   static final BoxDecoration border = new BoxDecoration(
       border: new Border.all(color: theme.primaryColor), borderRadius: 2.0);
   static final BoxDecoration borderInactive = new BoxDecoration(
diff --git a/test/game_log_hearts_test.txt b/test/game_log_hearts_test.txt
index ec9205f..7a3a9db 100644
--- a/test/game_log_hearts_test.txt
+++ b/test/game_log_hearts_test.txt
@@ -1,9 +1,3 @@
-# Start Game
-Ready|2:END
-Ready|0:END
-Ready|3:END
-Ready|1:END
-
 # Deal
 Deal|0:classic h1:classic h2:classic h3:classic h4:classic h5:classic d6:classic d7:classic d8:classic d9:classic d10:classic dj:classic dq:classic dk:END
 Deal|1:classic d1:classic d2:classic d3:classic d4:classic d5:classic s6:classic s7:classic s8:classic s9:classic s10:classic sj:classic sq:classic sk:END
@@ -32,78 +26,91 @@
 Play|3:classic c4:END
 Play|0:classic d1:END
 Play|1:classic s1:END
+TakeTrick|END
 
 # Trick 2 (3 won last round with 4 of clubs)
 Play|3:classic c5:END
 Play|0:classic d2:END
 Play|1:classic s2:END
 Play|2:classic c1:END
+TakeTrick|END
 
 # Trick 3 (2 won with ace of clubs)
 Play|2:classic s4:END
 Play|3:classic h1:END
 Play|0:classic h5:END
 Play|1:classic s3:END
+TakeTrick|END
 
 # Trick 4 (2 won with s4)
 Play|2:classic s5:END
 Play|3:classic hk:END
 Play|0:classic h4:END
 Play|1:classic sk:END
+TakeTrick|END
 
 # Trick 5 (1 won with sk)
 Play|1:classic d5:END
 Play|2:classic ck:END
 Play|3:classic hq:END
 Play|0:classic d3:END
+TakeTrick|END
 
 # Trick 6 (1 won with d5)
 Play|1:classic d4:END
 Play|2:classic cq:END
 Play|3:classic hj:END
 Play|0:classic dk:END
+TakeTrick|END
 
 # Trick 7 (0 won with dk)
 Play|0:classic dq:END
 Play|1:classic sq:END
 Play|2:classic c3:END
 Play|3:classic h2:END
+TakeTrick|END
 
 # Trick 8 (0 won with dq)
 Play|0:classic dj:END
 Play|1:classic sj:END
 Play|2:classic cj:END
 Play|3:classic h3:END
+TakeTrick|END
 
 # Trick 9 (0 won with dj)
 Play|0:classic d10:END
 Play|1:classic s10:END
 Play|2:classic c10:END
 Play|3:classic h10:END
+TakeTrick|END
 
 # Trick 10 (0 won with d10)
 Play|0:classic d9:END
 Play|1:classic s9:END
 Play|2:classic c9:END
 Play|3:classic h9:END
+TakeTrick|END
 
 # Trick 11 (0 won with d9)
 Play|0:classic d8:END
 Play|1:classic s8:END
 Play|2:classic c8:END
 Play|3:classic h8:END
+TakeTrick|END
 
 # Trick 12 (0 won with d8)
 Play|0:classic d7:END
 Play|1:classic s7:END
 Play|2:classic c7:END
 Play|3:classic h7:END
+TakeTrick|END
 
 # Trick 13 (0 won with d7)
 Play|0:classic d6:END
 Play|1:classic s6:END
 Play|2:classic c6:END
 Play|3:classic h6:END
+TakeTrick|END
 
 # Score Phase (ready)
 Ready|2:END
@@ -138,78 +145,91 @@
 Play|1:classic d2:END
 Play|2:classic d4:END
 Play|3:classic s2:END
+TakeTrick|END
 
 # Trick 2
 Play|0:classic c3:END
 Play|1:classic d3:END
 Play|2:classic h3:END
 Play|3:classic s3:END
+TakeTrick|END
 
 # Trick 3
 Play|0:classic c4:END
 Play|1:classic s4:END
 Play|2:classic h2:END
 Play|3:classic h4:END
+TakeTrick|END
 
 # Trick 4
 Play|0:classic c5:END
 Play|1:classic d5:END
 Play|2:classic h5:END
 Play|3:classic s5:END
+TakeTrick|END
 
 # Trick 5
 Play|0:classic c6:END
 Play|1:classic d6:END
 Play|2:classic h6:END
 Play|3:classic s6:END
+TakeTrick|END
 
 # Trick 6
 Play|0:classic c7:END
 Play|1:classic d7:END
 Play|2:classic h7:END
 Play|3:classic s7:END
+TakeTrick|END
 
 # Trick 7
 Play|0:classic c8:END
 Play|1:classic d8:END
 Play|2:classic h8:END
 Play|3:classic s8:END
+TakeTrick|END
 
 # Trick 8
 Play|0:classic c9:END
 Play|1:classic d9:END
 Play|2:classic h9:END
 Play|3:classic s9:END
+TakeTrick|END
 
 # Trick 9
 Play|0:classic c1:END
 Play|1:classic d1:END
 Play|2:classic h1:END
 Play|3:classic s1:END
+TakeTrick|END
 
 # Trick 10
 Play|0:classic c10:END
 Play|1:classic d10:END
 Play|2:classic h10:END
 Play|3:classic s10:END
+TakeTrick|END
 
 # Trick 11
 Play|0:classic cj:END
 Play|1:classic dj:END
 Play|2:classic hj:END
 Play|3:classic sj:END
+TakeTrick|END
 
 # Trick 12
 Play|0:classic cq:END
 Play|1:classic dq:END
 Play|2:classic hq:END
 Play|3:classic sq:END
+TakeTrick|END
 
 # Trick 13
 Play|0:classic ck:END
 Play|1:classic dk:END
 Play|2:classic hk:END
 Play|3:classic sk:END
+TakeTrick|END
 
 # Score Phase (ready)
 Ready|2:END
@@ -242,78 +262,91 @@
 Play|1:classic d2:END
 Play|2:classic d4:END
 Play|3:classic s2:END
+TakeTrick|END
 
 # Trick 2
 Play|0:classic c3:END
 Play|1:classic d3:END
 Play|2:classic h3:END
 Play|3:classic s3:END
+TakeTrick|END
 
 # Trick 3
 Play|0:classic c4:END
 Play|1:classic s4:END
 Play|2:classic h2:END
 Play|3:classic h4:END
+TakeTrick|END
 
 # Trick 4
 Play|0:classic c5:END
 Play|1:classic d5:END
 Play|2:classic h5:END
 Play|3:classic s5:END
+TakeTrick|END
 
 # Trick 5
 Play|0:classic c6:END
 Play|1:classic d6:END
 Play|2:classic h6:END
 Play|3:classic s6:END
+TakeTrick|END
 
 # Trick 6
 Play|0:classic c7:END
 Play|1:classic d7:END
 Play|2:classic h7:END
 Play|3:classic s7:END
+TakeTrick|END
 
 # Trick 7
 Play|0:classic c8:END
 Play|1:classic d8:END
 Play|2:classic h8:END
 Play|3:classic s8:END
+TakeTrick|END
 
 # Trick 8
 Play|0:classic c9:END
 Play|1:classic d9:END
 Play|2:classic h9:END
 Play|3:classic s9:END
+TakeTrick|END
 
 # Trick 9
 Play|0:classic c1:END
 Play|1:classic d1:END
 Play|2:classic h1:END
 Play|3:classic s1:END
+TakeTrick|END
 
 # Trick 10
 Play|0:classic c10:END
 Play|1:classic d10:END
 Play|2:classic h10:END
 Play|3:classic s10:END
+TakeTrick|END
 
 # Trick 11
 Play|0:classic cj:END
 Play|1:classic dj:END
 Play|2:classic hj:END
 Play|3:classic sj:END
+TakeTrick|END
 
 # Trick 12
 Play|0:classic cq:END
 Play|1:classic dq:END
 Play|2:classic hq:END
 Play|3:classic sq:END
+TakeTrick|END
 
 # Trick 13
 Play|0:classic ck:END
 Play|1:classic dk:END
 Play|2:classic hk:END
 Play|3:classic sk:END
+TakeTrick|END
 
 # Score Phase (ready)
 Ready|2:END
@@ -334,78 +367,91 @@
 Play|1:classic d2:END
 Play|2:classic d4:END
 Play|3:classic s2:END
+TakeTrick|END
 
 # Trick 2
 Play|0:classic c3:END
 Play|1:classic d3:END
 Play|2:classic h3:END
 Play|3:classic s3:END
+TakeTrick|END
 
 # Trick 3
 Play|0:classic c4:END
 Play|1:classic s4:END
 Play|2:classic h2:END
 Play|3:classic h4:END
+TakeTrick|END
 
 # Trick 4
 Play|0:classic c5:END
 Play|1:classic d5:END
 Play|2:classic h5:END
 Play|3:classic s5:END
+TakeTrick|END
 
 # Trick 5
 Play|0:classic c6:END
 Play|1:classic d6:END
 Play|2:classic h6:END
 Play|3:classic s6:END
+TakeTrick|END
 
 # Trick 6
 Play|0:classic c7:END
 Play|1:classic d7:END
 Play|2:classic h7:END
 Play|3:classic s7:END
+TakeTrick|END
 
 # Trick 7
 Play|0:classic c8:END
 Play|1:classic d8:END
 Play|2:classic h8:END
 Play|3:classic s8:END
+TakeTrick|END
 
 # Trick 8
 Play|0:classic c9:END
 Play|1:classic d9:END
 Play|2:classic h9:END
 Play|3:classic s9:END
+TakeTrick|END
 
 # Trick 9
 Play|0:classic c1:END
 Play|1:classic d1:END
 Play|2:classic h1:END
 Play|3:classic s1:END
+TakeTrick|END
 
 # Trick 10
 Play|0:classic c10:END
 Play|1:classic d10:END
 Play|2:classic h10:END
 Play|3:classic s10:END
+TakeTrick|END
 
 # Trick 11
 Play|0:classic cj:END
 Play|1:classic dj:END
 Play|2:classic hj:END
 Play|3:classic sj:END
+TakeTrick|END
 
 # Trick 12
 Play|0:classic cq:END
 Play|1:classic dq:END
 Play|2:classic hq:END
 Play|3:classic sq:END
+TakeTrick|END
 
 # Trick 13
 Play|0:classic ck:END
 Play|1:classic dk:END
 Play|2:classic hk:END
 Play|3:classic sk:END
+TakeTrick|END
 
 # Score Phase (ready)
 Ready|2:END
@@ -438,77 +484,90 @@
 Play|1:classic d2:END
 Play|2:classic d4:END
 Play|3:classic s2:END
+TakeTrick|END
 
 # Trick 2
 Play|0:classic c3:END
 Play|1:classic d3:END
 Play|2:classic h3:END
 Play|3:classic s3:END
+TakeTrick|END
 
 # Trick 3
 Play|0:classic c4:END
 Play|1:classic s4:END
 Play|2:classic h2:END
 Play|3:classic h4:END
+TakeTrick|END
 
 # Trick 4
 Play|0:classic c5:END
 Play|1:classic d5:END
 Play|2:classic h5:END
 Play|3:classic s5:END
+TakeTrick|END
 
 # Trick 5
 Play|0:classic c6:END
 Play|1:classic d6:END
 Play|2:classic h6:END
 Play|3:classic s6:END
+TakeTrick|END
 
 # Trick 6
 Play|0:classic c7:END
 Play|1:classic d7:END
 Play|2:classic h7:END
 Play|3:classic s7:END
+TakeTrick|END
 
 # Trick 7
 Play|0:classic c8:END
 Play|1:classic d8:END
 Play|2:classic h8:END
 Play|3:classic s8:END
+TakeTrick|END
 
 # Trick 8
 Play|0:classic c9:END
 Play|1:classic d9:END
 Play|2:classic h9:END
 Play|3:classic s9:END
+TakeTrick|END
 
 # Trick 9
 Play|0:classic c1:END
 Play|1:classic d1:END
 Play|2:classic h1:END
 Play|3:classic s1:END
+TakeTrick|END
 
 # Trick 10
 Play|0:classic c10:END
 Play|1:classic d10:END
 Play|2:classic h10:END
 Play|3:classic s10:END
+TakeTrick|END
 
 # Trick 11
 Play|0:classic cj:END
 Play|1:classic dj:END
 Play|2:classic hj:END
 Play|3:classic sj:END
+TakeTrick|END
 
 # Trick 12
 Play|0:classic cq:END
 Play|1:classic dq:END
 Play|2:classic hq:END
 Play|3:classic sq:END
+TakeTrick|END
 
 # Trick 13
 Play|0:classic ck:END
 Play|1:classic dk:END
 Play|2:classic hk:END
 Play|3:classic sk:END
+TakeTrick|END
 
 # Game is over!
diff --git a/test/hearts_test.dart b/test/hearts_test.dart
index 0a3de71..d5300e1 100644
--- a/test/hearts_test.dart
+++ b/test/hearts_test.dart
@@ -199,16 +199,6 @@
       }
     }
 
-    test("Start Game Phase", () {
-      expect(game.phase, equals(HeartsPhase.StartGame));
-
-      // Start Game consists of 4 ready commands.
-      runCommand();
-      runCommand();
-      runCommand();
-      runCommand();
-    });
-
     test("Deal Phase", () {
       expect(game.phase, equals(HeartsPhase.Deal));
 
@@ -314,12 +304,16 @@
     test("Play Phase - Trick 1", () {
       expect(game.phase, equals(HeartsPhase.Play));
 
-      // Play Trick 1 consists of 4 play commands.
+      // Play Trick 1 consists of 4 play commands + 1 take trick command.
       runCommand();
       runCommand();
       runCommand();
       runCommand();
 
+      // Confirm that everyone has played before taking the trick
+      expect(game.allPlayed, isTrue);
+      runCommand();
+
       // Confirm the winner of the round.
       expect(game.lastTrickTaker, equals(3),
           reason: "Player 3 played 4 of Clubs");
@@ -329,12 +323,16 @@
     test("Play Phase - Trick 2", () {
       expect(game.phase, equals(HeartsPhase.Play));
 
-      // Play Trick 2 consists of 4 play commands.
+      // Play Trick 2 consists of 4 play commands + 1 take trick command.
       runCommand();
       runCommand();
       runCommand();
       runCommand();
 
+      // Confirm that everyone has played before taking the trick
+      expect(game.allPlayed, isTrue);
+      runCommand();
+
       // Confirm the winner of the round.
       expect(game.lastTrickTaker, equals(2),
           reason: "Player 2 played Ace of Clubs");
@@ -346,9 +344,9 @@
     test("Play Phase - Trick 13", () {
       expect(game.phase, equals(HeartsPhase.Play));
 
-      // Play Trick 13 consists of 44 play commands.
+      // Play Trick 13 consists of 44 play commands  + 11 take trick command.
       // Read line by line until the game is "over".
-      for (int i = 8; i < 52; i++) {
+      for (int i = 10; i < 65; i++) {
         runCommand();
       }
 
@@ -411,24 +409,24 @@
     test("Score Phase - end of game", () {
       expect(game.hasGameEnded, isFalse);
 
-      // 2nd Round: 4 deal, 4 pass, 4 take, 52 play, 4 ready
+      // 2nd Round: 4 deal, 4 pass, 4 take, 52 play, 13 take trick, 4 ready
       // Player A will shoot the moon for all remaining games (for simplicity).
-      for (int i = 0; i < 68; i++) {
+      for (int i = 0; i < 81; i++) {
         runCommand();
       }
       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++) {
+      // 3rd Round: 4 deal, 4 pass, 4 take, 52 play, 13 take trick, 4 ready
+      for (int i = 0; i < 81; i++) {
         runCommand();
       }
       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++) {
+      // 4th Round: 4 deal, 52 play, 13 take trick, 4 ready
+      for (int i = 0; i < 73; i++) {
         runCommand();
       }
       expect(
@@ -442,8 +440,8 @@
       expect(game.deltaScores, equals([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++) {
+      // 5th round: 4 deal, 4 pass, 4 take, 52 play, 13 take trick. Game is over, so no ready phase.
+      for (int i = 0; i < 77; i++) {
         runCommand();
       }
       expect(
@@ -461,14 +459,6 @@
   });
 
   group("Card Manipulation - Error Cases", () {
-    test("Dealing - too soon", () {
-      HeartsGame game = new HeartsGame()..playerNumber = 0;
-      expect(game.phase, HeartsPhase.StartGame);
-      expect(() {
-        game.gamelog.add(new HeartsCommand.deal(
-            0, new List<Card>.from(Card.All.getRange(0, 13))));
-      }, throwsA(new isInstanceOf<StateError>()));
-    });
     test("Dealing - wrong phase", () {
       HeartsGame game = new HeartsGame()..playerNumber = 0;
       game.phase = HeartsPhase.Score;
@@ -656,10 +646,68 @@
       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.takeTrick());
       expect(() {
         game.gamelog.add(new HeartsCommand.play(
             2, Card.All[26])); // But 2 can't lead with a hearts.
       }, throwsA(new isInstanceOf<StateError>()));
     });
+    test("Playing - trick not taken yet", () {
+      HeartsGame game = new HeartsGame()..playerNumber = 0;
+      game.phase = HeartsPhase.Deal;
+      game.gamelog.add(new HeartsCommand.deal(
+          0,
+          new List<Card>.from(Card.All.getRange(0, 11))
+            ..add(Card.All[38])
+            ..add(Card.All[37])));
+      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, 37))
+            ..add(Card.All[12])
+            ..add(Card.All[11])));
+      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]));
+
+      // Do not take trick. Play a card instead.
+      expect(() {
+        game.gamelog.add(new HeartsCommand.play(
+            2, Card.All[11])); // But 2 can't play until trick is taken
+      }, throwsA(new isInstanceOf<StateError>()));
+    });
+    test("Playing - take trick wrong phase", () {
+      HeartsGame game = new HeartsGame()..playerNumber = 0;
+      game.phase = HeartsPhase.Deal;
+      expect(() {
+        game.gamelog.add(new HeartsCommand.takeTrick()); // bad phase
+      }, throwsA(new isInstanceOf<StateError>()));
+    });
+    test("Playing - take trick too soon", () {
+      HeartsGame game = new HeartsGame()..playerNumber = 0;
+      game.phase = HeartsPhase.Deal;
+      game.gamelog.add(new HeartsCommand.deal(
+          0, new List<Card>.from(Card.All.getRange(0, 12))..add(Card.All[38])));
+      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]));
+      expect(() {
+        game.gamelog.add(new HeartsCommand.takeTrick()); // too soon
+      }, throwsA(new isInstanceOf<StateError>()));
+    });
   });
 }