croupier: Improve Game Affordances for Pass and Play (split)
Pass Affordances added
- dragging animations restored
- now has a status bar to show who to pass to/take from
- has arrows indicating direction
Play affordances added to split view
- better animations in for new cards
Cards can no longer be dropped on the card collection they
originate from.
Change-Id: I02724eef628b9545f6f11d27d3fe9377ae34c8bd
diff --git a/lib/components/board.dart b/lib/components/board.dart
index 41b072c..bb611ac 100644
--- a/lib/components/board.dart
+++ b/lib/components/board.dart
@@ -76,6 +76,8 @@
class HeartsBoardState extends State<HeartsBoard> {
Widget build(BuildContext context) {
+ double offscreenDelta = config.isMini ? 5.0 : 2.0;
+
return new Container(
height: config.height,
width: config.width,
@@ -87,24 +89,24 @@
? _buildMiniBoardLayout()
: _buildBoardLayout()),
new Positioned(
- top: config.height * 5.5,
+ top: config.height * (offscreenDelta + 0.5),
left: (config.width - config.cardWidth) / 2,
- child: _buildTrick(
+ child: _buildOffScreenCards(
config.isMini ? rotateByGamePlayerNumber(0) : 0)), // bottom
new Positioned(
top: (config.height - config.cardHeight) / 2,
- left: config.width * -4.5,
- child: _buildTrick(
+ left: config.width * (-offscreenDelta + 0.5),
+ child: _buildOffScreenCards(
config.isMini ? rotateByGamePlayerNumber(1) : 1)), // left
new Positioned(
- top: config.height * -4.5,
+ top: config.height * (-offscreenDelta + 0.5),
left: (config.width - config.cardWidth) / 2,
- child: _buildTrick(
+ child: _buildOffScreenCards(
config.isMini ? rotateByGamePlayerNumber(2) : 2)), // top
new Positioned(
top: (config.height - config.cardHeight) / 2,
- left: config.width * 5.5,
- child: _buildTrick(
+ left: config.width * (offscreenDelta + 0.5),
+ child: _buildOffScreenCards(
config.isMini ? rotateByGamePlayerNumber(3) : 3)) // right
]));
}
@@ -161,7 +163,6 @@
child: new CardCollectionComponent(
showCard, true, CardCollectionOrientation.show1,
useKeys: true,
- animationType: component_card.CardAnimationType.NONE,
acceptCallback: config.gameAcceptCallback,
acceptType: isMe && isPlayerTurn ? DropType.card : DropType.none,
widthCard: config.cardWidth - 6.0,
@@ -319,7 +320,7 @@
useKeys: true));
}
- Widget _buildTrick(int playerNumber) {
+ Widget _buildOffScreenCards(int playerNumber) {
HeartsGame game = config.game;
List<logic_card.Card> cards =
@@ -327,9 +328,18 @@
// 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);
}
- double sizeFactor = config.isMini ? 1.0 : 2.0;
+ double sizeFactor = 2.0;
+ if (config.isMini) {
+ sizeFactor = 1.0;
+ if (playerNumber != game.playerNumber) {
+ cards.addAll(
+ game.cardCollections[playerNumber + HeartsGame.OFFSET_HAND]);
+ }
+ }
return new CardCollectionComponent(
cards, true, CardCollectionOrientation.show1,
diff --git a/lib/components/card_collection.dart b/lib/components/card_collection.dart
index 1a2f93f..6ede30d 100644
--- a/lib/components/card_collection.dart
+++ b/lib/components/card_collection.dart
@@ -75,10 +75,12 @@
class CardCollectionComponentState extends State<CardCollectionComponent> {
String status = 'bar';
- bool _handleWillAccept(dynamic data) {
- print('will accept?');
- print(data);
- return true;
+ bool _handleWillAccept(component_card.Card data) {
+ return !config.cards.contains(data.card);
+ }
+
+ bool _handleWillAcceptMultiple(CardCollectionComponent data) {
+ return data != config; // don't accept your own self.
}
void _handleAccept(component_card.Card data) {
@@ -344,7 +346,7 @@
});
case DropType.card_collection:
return new DragTarget<CardCollectionComponent>(
- onWillAccept: _handleWillAccept,
+ onWillAccept: _handleWillAcceptMultiple,
onAccept: _handleAcceptMultiple, builder:
(BuildContext context, List<CardCollectionComponent> data, _) {
return new Container(
diff --git a/lib/components/game.dart b/lib/components/game.dart
index c726f92..7a858ad 100644
--- a/lib/components/game.dart
+++ b/lib/components/game.dart
@@ -10,6 +10,7 @@
import 'package:flutter/scheduler.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
+import 'package:vector_math/vector_math_64.dart' as vector_math;
import '../logic/card.dart' as logic_card;
import '../logic/croupier.dart' show Croupier;
diff --git a/lib/components/hearts/hearts.part.dart b/lib/components/hearts/hearts.part.dart
index 60cef30..01f223b 100644
--- a/lib/components/hearts/hearts.part.dart
+++ b/lib/components/hearts/hearts.part.dart
@@ -141,15 +141,17 @@
.add(HeartsGame.OFFSET_HAND + playerNum);
break;
case HeartsPhase.Play:
- for (int i = 0; i < 4; i++) {
- if (_showSplitView || i == playerNum) {
+ if (_showSplitView) {
+ for (int i = 0; i < 4; i++) {
+ visibleCardCollectionIndexes.add(HeartsGame.OFFSET_HAND + i);
+ visibleCardCollectionIndexes.add(HeartsGame.OFFSET_TRICK + i);
visibleCardCollectionIndexes.add(HeartsGame.OFFSET_PLAY + i);
}
- if (_showSplitView) {
- visibleCardCollectionIndexes
- .add(HeartsGame.OFFSET_HAND + playerNum);
- visibleCardCollectionIndexes.add(HeartsGame.OFFSET_TRICK + i);
- }
+ } else {
+ visibleCardCollectionIndexes
+ .add(HeartsGame.OFFSET_PLAY + playerNum);
+ visibleCardCollectionIndexes
+ .add(HeartsGame.OFFSET_HAND + playerNum);
}
break;
@@ -252,7 +254,7 @@
game.debugString = null;
} catch (e) {
print("You can't do that! ${e.toString()}");
- config.game.debugString = e.toString();
+ config.game.debugString = "You must pass 3 cards";
}
});
}
@@ -394,16 +396,44 @@
String _getStatus() {
HeartsGame game = config.game;
- // Who's turn is it?
- String name = _getName(game.whoseTurn) ?? "Player ${game.whoseTurn}";
- String status =
- game.whoseTurn == game.playerNumber ? "Your turn" : "${name}'s turn";
+ String status;
+ switch (game.phase) {
+ case HeartsPhase.Play:
+ // Who's turn is it?
+ String name = _getName(game.whoseTurn) ?? "Player ${game.whoseTurn}";
+ status = game.whoseTurn == game.playerNumber
+ ? "Your turn"
+ : "${name}'s turn";
- // Override if someone is taking a trick.
- if (this.trickTaking) {
- String trickTaker =
- _getName(game.lastTrickTaker) ?? "Player ${game.lastTrickTaker}";
- status = "${trickTaker}'s trick";
+ // Override if someone is taking a trick.
+ if (this.trickTaking) {
+ String trickTaker =
+ _getName(game.lastTrickTaker) ?? "Player ${game.lastTrickTaker}";
+ status = game.lastTrickTaker == game.playerNumber
+ ? "Your trick"
+ : "${trickTaker}'s trick";
+ }
+ break;
+ case HeartsPhase.Pass:
+ if (game.hasPassed(game.playerNumber)) {
+ status = "Waiting for cards...";
+ } else {
+ String name =
+ _getName(game.passTarget) ?? "Player ${game.passTarget}";
+ status = "Pass to ${name}";
+ }
+ break;
+ case HeartsPhase.Take:
+ if (game.hasTaken(game.playerNumber)) {
+ status = "Waiting for other players...";
+ } else {
+ String name =
+ _getName(game.takeTarget) ?? "Player ${game.takeTarget}";
+ status = "Take from ${name}";
+ }
+ break;
+ default:
+ break;
}
// Override if there is a debug string.
@@ -415,18 +445,53 @@
}
Widget _buildStatusBar() {
+ HeartsGame game = config.game;
+
+ List<Widget> statusWidgets = new List<Widget>();
+ statusWidgets.add(new Text(_getStatus(), style: style.Text.largeStyle));
+
+ switch (game.phase) {
+ case HeartsPhase.Play:
+ statusWidgets
+ .add(new IconButton(icon: "action/swap_vert", onPressed: () {
+ setState(() {
+ _showSplitView = !_showSplitView;
+ });
+ }));
+ break;
+ case HeartsPhase.Pass:
+ case HeartsPhase.Take:
+ // TODO(alexfandrianto): Icons for arrow_upward and arrow_downward were
+ // just added to the material icon list. However, they are not available
+ // through Flutter yet.
+ double rotationAngle = 0.0; // right
+ switch (game.roundNumber % 4) {
+ case 1:
+ rotationAngle = math.PI; // left
+ break;
+ case 2:
+ rotationAngle = -math.PI / 2; // up
+ break;
+ }
+ if (game.phase == HeartsPhase.Take) {
+ rotationAngle = rotationAngle + math.PI; // opposite
+ }
+ statusWidgets.add(new Transform(
+ transform:
+ new vector_math.Matrix4.identity().rotateZ(rotationAngle),
+ alignment: new FractionalOffset(0.5, 0.5),
+ child: new Icon(icon: "navigation/arrow_forward")));
+ break;
+ default:
+ break;
+ }
+
return new Container(
padding: new EdgeDims.all(10.0),
decoration:
new BoxDecoration(backgroundColor: style.theme.primaryColor),
- child: new Row([
- new Text(_getStatus(), style: style.Text.largeStyle),
- new IconButton(icon: "action/swap_vert", onPressed: () {
- setState(() {
- _showSplitView = !_showSplitView;
- });
- })
- ], justifyContent: FlexJustifyContent.spaceBetween));
+ child: new Row(statusWidgets,
+ justifyContent: FlexJustifyContent.spaceBetween));
}
Widget _buildFullMiniBoard() {
@@ -465,7 +530,6 @@
true,
CardCollectionOrientation.show1,
useKeys: true,
- animationType: component_card.CardAnimationType.NONE,
acceptCallback: _makeGameMoveCallback,
acceptType:
p == game.whoseTurn ? DropType.card : DropType.none,
@@ -478,7 +542,7 @@
cardCollections.add(new Container(
decoration:
new BoxDecoration(backgroundColor: style.theme.primaryColor),
- child: new Column([_buildStatusBar(), playArea])));
+ child: new BlockBody([_buildStatusBar(), playArea])));
}
List<logic_card.Card> cards = game.cardCollections[p];
@@ -487,10 +551,8 @@
dragChildren: true, // Can drag, but may not have anywhere to drop
comparator: _compareCards,
width: config.width,
- useKeys: _showSplitView);
- cardCollections.add(c); // flex
-
- cardCollections.add(_makeDebugButtons());
+ useKeys: true);
+ cardCollections.add(new BlockBody([c, _makeDebugButtons()]));
return new Column(cardCollections,
justifyContent: FlexJustifyContent.spaceBetween);
@@ -600,32 +662,39 @@
Color bgColor = completed ? Colors.teal[600] : Colors.teal[500];
+ Widget statusBar = _buildStatusBar();
+
Widget topArea = new Container(
decoration: new BoxDecoration(backgroundColor: bgColor),
padding: new EdgeDims.all(10.0),
width: config.width,
child: new Flex(topCardWidgets,
justifyContent: FlexJustifyContent.spaceBetween));
+ Widget combinedTopArea = new BlockBody([statusBar, topArea]);
Widget handArea = new CardCollectionComponent(
hand, true, CardCollectionOrientation.suit,
dragChildren: draggable,
comparator: _compareCards,
width: config.width,
+ acceptCallback: cb,
+ acceptType: cb != null ? DropType.card : null,
backgroundColor: Colors.grey[500],
altColor: Colors.grey[700],
useKeys: true);
- return new Column(<Widget>[topArea, handArea, _makeDebugButtons()],
+ Widget combinedBottomArea = new BlockBody([handArea, _makeDebugButtons()]);
+
+ return new Column(<Widget>[combinedTopArea, combinedBottomArea],
justifyContent: FlexJustifyContent.spaceBetween);
}
Widget _topCardWidget(List<logic_card.Card> cards, AcceptCb cb) {
Widget ccc = new CardCollectionComponent(
cards, true, CardCollectionOrientation.show1,
+ dragChildren: cb != null,
acceptCallback: cb,
acceptType: cb != null ? DropType.card : null,
- animationType: component_card.CardAnimationType.NONE,
backgroundColor: Colors.white,
altColor: Colors.grey[200],
useKeys: true);
diff --git a/lib/logic/hearts/hearts_game.part.dart b/lib/logic/hearts/hearts_game.part.dart
index a635060..8a25172 100644
--- a/lib/logic/hearts/hearts_game.part.dart
+++ b/lib/logic/hearts/hearts_game.part.dart
@@ -207,10 +207,14 @@
cardCollections[PLAYER_C].length == 13 &&
cardCollections[PLAYER_D].length == 13;
+ bool hasPassed(int player) =>
+ cardCollections[player + OFFSET_PASS].length == 3;
bool get allPassed => cardCollections[PLAYER_A_PASS].length == 3 &&
cardCollections[PLAYER_B_PASS].length == 3 &&
cardCollections[PLAYER_C_PASS].length == 3 &&
cardCollections[PLAYER_D_PASS].length == 3;
+ bool hasTaken(int player) =>
+ cardCollections[getTakeTarget(player) + OFFSET_PASS].length == 0;
bool get allTaken => cardCollections[PLAYER_A_PASS].length == 0 &&
cardCollections[PLAYER_B_PASS].length == 0 &&
cardCollections[PLAYER_C_PASS].length == 0 &&
diff --git a/manifest.yaml b/manifest.yaml
index f9e8c92..6fbe3fb 100644
--- a/manifest.yaml
+++ b/manifest.yaml
@@ -6,6 +6,7 @@
- name: av/play_arrow
- name: action/swap_vert
- name: navigation/arrow_back
+ - name: navigation/arrow_forward
- name: navigation/menu
assets:
- images/default/classic/down/c10.png