croupier: Improve the UI for the Play Phase

Now shows only a single person's cards. Shows them sorted by suit.
And also sorted by value. The comparator can be changed based on the game.

Missing:
- width is set to 0 when the full flex is empty since you can't set min-width
- sky is set to 0.0.41 because later on drag and drop starts to break.
- sky_viewer is still not working. It seems that it's not allowed to draw.

Change-Id: I11eac0c3f36f5ddf25b1002ab36aa0bd0e56f1e8
diff --git a/lib/components/card_collection.dart b/lib/components/card_collection.dart
index 6456bb9..7d649f4 100644
--- a/lib/components/card_collection.dart
+++ b/lib/components/card_collection.dart
@@ -8,7 +8,7 @@
 import 'package:sky/widgets.dart';
 import 'package:sky/theme/colors.dart' as colors;
 
-enum Orientation { vert, horz, fan, show1 }
+enum Orientation { vert, horz, fan, show1, suit }
 enum DropType {
   none,
   card,
@@ -23,12 +23,13 @@
   Function parentCallback;
   bool dragChildren;
   DropType acceptType;
+  Function comparator;
 
   String status = 'bar';
 
   CardCollectionComponent(
       this.cards, this.faceUp, this.orientation, this.parentCallback,
-      {this.dragChildren: false, this.acceptType: DropType.none});
+      {this.dragChildren: false, this.acceptType: DropType.none, this.comparator: null});
 
   void syncConstructorArguments(CardCollectionComponent other) {
     cards = other.cards;
@@ -37,6 +38,7 @@
     parentCallback = other.parentCallback;
     dragChildren = other.dragChildren;
     acceptType = other.acceptType;
+    comparator = other.comparator;
   }
 
   bool _handleWillAccept(dynamic data) {
@@ -61,25 +63,100 @@
     });
   }
 
-  List<Widget> flexCards(List<Widget> cardWidgets) {
+  List<logic_card.Card> get _sortedCards {
+    assert(this.comparator != null);
+    List<logic_card.Card> cs = new List<logic_card.Card>();
+    cs.addAll(this.cards);
+    cs.sort(comparator);
+    return cs;
+  }
+
+  List<Widget> flexChildren(List<Widget> children) {
     List<Widget> flexWidgets = new List<Widget>();
-    cardWidgets.forEach(
-        (cardWidget) => flexWidgets.add(new Flexible(child: cardWidget)));
+    children.forEach(
+        (child) => flexWidgets.add(new Flexible(child: child)));
     return flexWidgets;
   }
 
+  // returns null if it's up to the container (like a Flex) to figure this out.
+  double get desiredHeight {
+    switch (this.orientation) {
+      case Orientation.vert:
+        return null;
+      case Orientation.horz:
+      case Orientation.fan:
+      case Orientation.show1:
+        return 60.0;
+      case Orientation.suit:
+        return 240.0;
+      default:
+        assert(false);
+        return null;
+    }
+  }
+
+  // returns null if it's up to the container (like a Flex) to figure this out.
+  double get desiredWidth {
+    switch (this.orientation) {
+      case Orientation.vert:
+      case Orientation.show1:
+        return 60.0;
+      case Orientation.horz:
+      case Orientation.fan:
+      case Orientation.suit:
+        return null;
+      default:
+        assert(false);
+        return null;
+    }
+  }
+
   Widget wrapCards(List<Widget> cardWidgets) {
     switch (this.orientation) {
       case Orientation.vert:
-        return new Flex(flexCards(cardWidgets),
+        return new Flex(flexChildren(cardWidgets),
             direction: FlexDirection.vertical);
       case Orientation.horz:
-        return new Flex(flexCards(cardWidgets));
+        return new Flex(flexChildren(cardWidgets));
       case Orientation.fan:
       // unimplemented, so we'll fall through to show1, for now.
       // Probably a Stack + Positioned
       case Orientation.show1:
         return new Stack(cardWidgets);
+      case Orientation.suit:
+        if (cards.length == 0) {
+          return new Stack(cardWidgets);
+        }
+        List<Widget> cs = new List<Widget>();
+        List<Widget> ds = new List<Widget>();
+        List<Widget> hs = new List<Widget>();
+        List<Widget> ss = new List<Widget>();
+
+        List<logic_card.Card> theCards =
+          this.comparator != null ? this._sortedCards : this.cards;
+        for (int i = 0; i < theCards.length; i++) {
+          // Group by suit. Then sort.
+          logic_card.Card c = theCards[i];
+          switch(c.identifier[0]) {
+            case 'c':
+              cs.add(cardWidgets[i]);
+              break;
+            case 'd':
+              ds.add(cardWidgets[i]);
+              break;
+            case 'h':
+              hs.add(cardWidgets[i]);
+              break;
+            case 's':
+              ss.add(cardWidgets[i]);
+              break;
+            default:
+              assert(false);
+          }
+        }
+        return new Flex(flexChildren(<Widget>[
+          new Flex(flexChildren(cs)), new Flex(flexChildren(ds)), new Flex(flexChildren(hs)), new Flex(flexChildren(ss))
+        ]), direction: FlexDirection.vertical);
       default:
         assert(false);
         return null;
@@ -104,8 +181,10 @@
       // https://github.com/domokit/sky_engine/blob/master/sky/packages/sky/lib/src/widgets/sizing.md
       cardComponents.add(new Text("")); // new Text(status)
     }
-    for (int i = 0; i < cards.length; i++) {
-      component_card.Card c = new component_card.Card(cards[i], faceUp);
+    List<logic_card.Card> cs = this.comparator != null ? this._sortedCards : this.cards;
+
+    for (int i = 0; i < cs.length; i++) {
+      component_card.Card c = new component_card.Card(cs[i], faceUp);
 
       if (dragChildren) {
         cardComponents.add(new Draggable<component_card.Card>(c));
@@ -123,7 +202,8 @@
             decoration: new BoxDecoration(
                 border: new Border.all(width: 3.0, color: colors.white),
                 backgroundColor: colors.Grey[500]),
-            height: 80.0,
+            height: this.desiredHeight,
+            width: this.desiredWidth,
             margin: new EdgeDims.all(10.0),
             child: wrapCards(cardComponents));
       case DropType.card:
@@ -137,7 +217,8 @@
                       color: data.isEmpty ? colors.white : colors.Blue[500]),
                   backgroundColor:
                       data.isEmpty ? colors.Grey[500] : colors.Green[500]),
-              height: 80.0,
+              height: this.desiredHeight,
+              width: this.desiredWidth,
               margin: new EdgeDims.all(10.0),
               child: wrapCards(cardComponents));
         });
@@ -152,7 +233,8 @@
                       color: data.isEmpty ? colors.white : colors.Blue[500]),
                   backgroundColor:
                       data.isEmpty ? colors.Grey[500] : colors.Green[500]),
-              height: 80.0,
+              height: this.desiredHeight,
+              width: this.desiredWidth,
               margin: new EdgeDims.all(10.0),
               child: wrapCards(cardComponents));
         });
diff --git a/lib/components/game.dart b/lib/components/game.dart
index cb634d4..a1c67c2 100644
--- a/lib/components/game.dart
+++ b/lib/components/game.dart
@@ -167,6 +167,15 @@
     });
   }
 
+  int _compareCards(logic_card.Card a, logic_card.Card b) {
+    if (a == b) return 0;
+    assert(a.deck == "classic" && b.deck == "classic");
+    HeartsGame game = this.game as HeartsGame;
+    int r = game.getCardSuit(a).compareTo(game.getCardSuit(b));
+    if (r != 0) return r;
+    return game.getCardValue(a) < game.getCardValue(b) ? -1 : 1;
+  }
+
   // This shouldn't always be here, but for now, we have little choice.
   void _switchPlayersCallback() {
     setState(() {
@@ -262,14 +271,14 @@
     List<Widget> cardCollections = new List<Widget>();
 
     cardCollections.add(new Text(game.debugString));
+    cardCollections.add(new Text("Player ${game.whoseTurn}'s turn"));
 
-    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: game.whoseTurn == i);
-      cardCollections.add(c); // flex
-    }
+    int i = game.playerNumber;
+    List<logic_card.Card> cards = game.cardCollections[i];
+    CardCollectionComponent c = new CardCollectionComponent(cards,
+        game.playerNumber == i, Orientation.suit, _makeGameMoveCallback,
+        dragChildren: game.whoseTurn == i, comparator: _compareCards);
+    cardCollections.add(c); // flex
 
     List<Widget> plays = new List<Widget>();
     for (int i = 0; i < 4; i++) {
@@ -365,7 +374,7 @@
               dragChildren: !hasPassed, acceptType: DropType.card)),
           new CardCollectionComponent(
               remainingCards, true, Orientation.horz, _uiPassCardCallback,
-              dragChildren: !hasPassed, acceptType: DropType.card),
+              dragChildren: !hasPassed, acceptType: DropType.card, comparator: _compareCards),
           _makeDebugButtons()
         ], direction: FlexDirection.vertical));
   }
@@ -392,7 +401,7 @@
           take,
           new CardCollectionComponent(
               playerCards, true, Orientation.horz, _makeGameTakeCallback,
-              dragChildren: true, acceptType: DropType.card_collection),
+              dragChildren: true, acceptType: DropType.card_collection, comparator: _compareCards),
           _makeDebugButtons()
         ], direction: FlexDirection.vertical));
   }
diff --git a/pubspec.yaml b/pubspec.yaml
index d23cca5..8a9341d 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,7 +1,9 @@
 name: your_app_name
 dependencies:
-  sky: any
+  sky: 0.0.41
   sky_tools: any
+  sky_engine: 0.0.18
+  sky_services: 0.0.18
   test: any
   ether: any
 dependency_overrides: