croupier: Improve Score View
Now shows the deltaScore and looks much more Material than before.
Both portrait and landscape mode fit pretty well on a thin phone.
Change-Id: If897873f5fef446450d20a830e4f9f8b36b79d0d
diff --git a/lib/components/croupier.dart b/lib/components/croupier.dart
index 5ccdf6e..132a09d 100644
--- a/lib/components/croupier.dart
+++ b/lib/components/croupier.dart
@@ -149,7 +149,7 @@
case logic_croupier.CroupierState.PlayGame:
return new Container(
padding: new EdgeDims.only(top: ui.window.padding.top),
- child: component_game.createGameComponent(config.croupier.game, () {
+ child: component_game.createGameComponent(config.croupier, () {
config.croupier.game.quit();
makeSetStateCallback(logic_croupier.CroupierState.Welcome)();
},
diff --git a/lib/components/game.dart b/lib/components/game.dart
index 66d13da..258a09a 100644
--- a/lib/components/game.dart
+++ b/lib/components/game.dart
@@ -4,7 +4,11 @@
library game_component;
+import 'dart:math' as math;
+
import '../logic/card.dart' as logic_card;
+import '../logic/croupier.dart' show Croupier;
+import '../logic/croupier_settings.dart' show CroupierSettings;
import '../logic/game/game.dart' show Game, GameType;
import '../logic/hearts/hearts.dart' show HeartsGame, HeartsPhase, HeartsType;
import '../logic/solitaire/solitaire.dart' show SolitaireGame, SolitairePhase;
@@ -12,6 +16,7 @@
import 'card.dart' as component_card;
import 'card_collection.dart'
show CardCollectionComponent, DropType, CardCollectionOrientation, AcceptCb;
+import 'croupier_profile.dart' show CroupierProfileComponent;
import '../styles/common.dart' as style;
import 'package:flutter/animation.dart';
@@ -25,12 +30,13 @@
typedef void NoArgCb();
abstract class GameComponent extends StatefulComponent {
- final Game game;
+ final Croupier croupier;
+ Game get game => croupier.game;
final NoArgCb gameEndCallback;
final double width;
final double height;
- GameComponent(this.game, this.gameEndCallback, {this.width, this.height});
+ GameComponent(this.croupier, this.gameEndCallback, {this.width, this.height});
}
abstract class GameComponentState<T extends GameComponent> extends State<T> {
@@ -69,11 +75,11 @@
@override
Widget build(BuildContext context); // still UNIMPLEMENTED
- void _cardLevelMapProcessAllVisible(List<int> visibleCardCollections) {
+ void _cardLevelMapProcessAllVisible(List<int> visibleCardCollectionIndexes) {
Game game = config.game;
- for (int i = 0; i < visibleCardCollections.length; i++) {
- int index = visibleCardCollections[i];
+ for (int i = 0; i < visibleCardCollectionIndexes.length; i++) {
+ int index = visibleCardCollectionIndexes[i];
for (int j = 0; j < game.cardCollections[index].length; j++) {
_cardLevelMapProcess(game.cardCollections[index][j]);
}
@@ -129,11 +135,11 @@
// Helper to build the card animation layer.
// Note: This isn't a component because of its dependence on Widgets.
- Widget buildCardAnimationLayer(List<int> visibleCardCollections) {
+ Widget buildCardAnimationLayer(List<int> visibleCardCollectionIndexes) {
// It's possible that some cards need to be moved after this build.
// If so, we can catch it in the next frame.
scheduler.requestPostFrameCallback((Duration d) {
- _cardLevelMapProcessAllVisible(visibleCardCollections);
+ _cardLevelMapProcessAllVisible(visibleCardCollectionIndexes);
});
List<Widget> positionedCards = new List<Widget>();
@@ -158,7 +164,7 @@
orderedKeys.forEach((logic_card.Card c) {
// Don't show a card if it isn't part of a visible collection.
- if (!visibleCardCollections.contains(config.game.findCard(c))) {
+ if (!visibleCardCollectionIndexes.contains(config.game.findCard(c))) {
cardLevelMap.remove(c); // It is an old card, which we can clean up.
return;
}
@@ -188,17 +194,17 @@
}
}
-GameComponent createGameComponent(Game game, NoArgCb gameEndCallback,
+GameComponent createGameComponent(Croupier croupier, NoArgCb gameEndCallback,
{double width, double height}) {
- switch (game.gameType) {
+ switch (croupier.game.gameType) {
case GameType.Proto:
- return new ProtoGameComponent(game, gameEndCallback,
+ return new ProtoGameComponent(croupier, gameEndCallback,
width: width, height: height);
case GameType.Hearts:
- return new HeartsGameComponent(game, gameEndCallback,
+ return new HeartsGameComponent(croupier, gameEndCallback,
width: width, height: height);
case GameType.Solitaire:
- return new SolitaireGameComponent(game, gameEndCallback,
+ return new SolitaireGameComponent(croupier, gameEndCallback,
width: width, height: height);
default:
// We're probably not ready to serve the other games yet.
diff --git a/lib/components/hearts/hearts.part.dart b/lib/components/hearts/hearts.part.dart
index a310ee5..0c5aa4f 100644
--- a/lib/components/hearts/hearts.part.dart
+++ b/lib/components/hearts/hearts.part.dart
@@ -5,8 +5,9 @@
part of game_component;
class HeartsGameComponent extends GameComponent {
- HeartsGameComponent(Game game, NoArgCb cb, {double width, double height})
- : super(game, cb, width: width, height: height);
+ HeartsGameComponent(Croupier croupier, NoArgCb cb,
+ {double width, double height})
+ : super(croupier, cb, width: width, height: height);
HeartsGameComponentState createState() => new HeartsGameComponentState();
}
@@ -51,25 +52,30 @@
width: config.width,
height: config.height,
child: heartsWidget));
+ List<int> visibleCardCollectionIndexes = new List<int>();
if (game.phase != HeartsPhase.StartGame &&
game.phase != HeartsPhase.Deal &&
game.phase != HeartsPhase.Score) {
- List<int> visibleCardCollections = new List<int>();
int playerNum = game.playerNumber;
if (game.viewType == HeartsType.Player) {
switch (game.phase) {
case HeartsPhase.Pass:
- visibleCardCollections.add(HeartsGame.OFFSET_PASS + playerNum);
- visibleCardCollections.add(HeartsGame.OFFSET_HAND + playerNum);
+ visibleCardCollectionIndexes
+ .add(HeartsGame.OFFSET_PASS + playerNum);
+ visibleCardCollectionIndexes
+ .add(HeartsGame.OFFSET_HAND + playerNum);
break;
case HeartsPhase.Take:
- visibleCardCollections
+ visibleCardCollectionIndexes
.add(HeartsGame.OFFSET_PASS + game.takeTarget);
- visibleCardCollections.add(HeartsGame.OFFSET_HAND + playerNum);
+ visibleCardCollectionIndexes
+ .add(HeartsGame.OFFSET_HAND + playerNum);
break;
case HeartsPhase.Play:
- visibleCardCollections.add(HeartsGame.OFFSET_HAND + playerNum);
- visibleCardCollections.add(HeartsGame.OFFSET_PLAY + playerNum);
+ visibleCardCollectionIndexes
+ .add(HeartsGame.OFFSET_HAND + playerNum);
+ visibleCardCollectionIndexes
+ .add(HeartsGame.OFFSET_PLAY + playerNum);
break;
default:
break;
@@ -77,13 +83,13 @@
} else {
// A board will need to see these things.
for (int i = 0; i < 4; i++) {
- visibleCardCollections.add(HeartsGame.OFFSET_PLAY + i);
- visibleCardCollections.add(HeartsGame.OFFSET_PASS + i);
- visibleCardCollections.add(HeartsGame.OFFSET_HAND + i);
+ visibleCardCollectionIndexes.add(HeartsGame.OFFSET_PLAY + i);
+ visibleCardCollectionIndexes.add(HeartsGame.OFFSET_PASS + i);
+ visibleCardCollectionIndexes.add(HeartsGame.OFFSET_HAND + i);
}
}
- children.add(this.buildCardAnimationLayer(visibleCardCollections));
}
+ children.add(this.buildCardAnimationLayer(visibleCardCollectionIndexes));
return new Container(
width: config.width, height: config.height, child: new Stack(children));
@@ -363,6 +369,16 @@
justifyContent: FlexJustifyContent.spaceBetween);
}
+ Widget _getProfileComponent(int playerNumber) {
+ int userID = config.croupier.userIDFromPlayerNumber(playerNumber);
+
+ CroupierSettings cs; // If cs is null, a placeholder is used instead.
+ if (userID != null) {
+ cs = config.croupier.settings_everyone[userID];
+ }
+ return new CroupierProfileComponent(cs);
+ }
+
Widget showScore() {
HeartsGame game = config.game as HeartsGame;
@@ -375,16 +391,60 @@
w = _makeButton('Ready?', game.setReadyUI);
}
- return new Container(
- decoration: new BoxDecoration(backgroundColor: Colors.pink[500]),
+ bool isTall = MediaQuery.of(context).orientation == Orientation.portrait;
+ FlexDirection crossDirection =
+ isTall ? FlexDirection.horizontal : FlexDirection.vertical;
+ FlexDirection mainDirection =
+ isTall ? FlexDirection.vertical : FlexDirection.horizontal;
+ TextStyle bigStyle = isTall ? style.Text.hugeStyle : style.Text.largeStyle;
+ TextStyle bigRedStyle =
+ isTall ? style.Text.hugeRedStyle : style.Text.largeRedStyle;
+
+ List<Widget> scores = new List<Widget>();
+ scores.add(new Flexible(
child: new Flex([
- new Text('Player ${game.playerNumber}'),
- // TODO(alexfandrianto): we want to show round by round, deltas too, don't we?
- new Text('${game.scores}'),
- w,
- _makeButton("Return to Lobby", _quitGameCallback),
- _makeDebugButtons()
- ], direction: FlexDirection.vertical));
+ new Flexible(
+ child: new Center(child: new Text("Score:", style: bigStyle)),
+ flex: 1),
+ new Flexible(
+ child: new Center(child: new Text("Round", style: bigStyle)),
+ flex: 1),
+ new Flexible(
+ child: new Center(child: new Text("Total", style: bigStyle)),
+ flex: 1)
+ ], direction: crossDirection),
+ flex: 1));
+ for (int i = 0; i < 4; i++) {
+ bool isMaxForRound =
+ game.deltaScores.reduce(math.max) == game.deltaScores[i];
+ bool isMaxOverall = game.scores.reduce(math.max) == game.scores[i];
+
+ TextStyle deltaStyle = isMaxForRound ? bigRedStyle : bigStyle;
+ TextStyle scoreStyle = isMaxOverall ? bigRedStyle : bigStyle;
+
+ scores.add(new Flexible(
+ child: new Flex([
+ new Flexible(child: _getProfileComponent(i), flex: 1),
+ new Flexible(
+ child: new Center(
+ child:
+ new Text("${game.deltaScores[i]}", style: deltaStyle)),
+ flex: 1),
+ new Flexible(
+ child: new Center(
+ child: new Text("${game.scores[i]}", style: scoreStyle)),
+ flex: 1)
+ ], direction: crossDirection),
+ flex: 2));
+ }
+ return new Column([
+ new Flexible(child: new Flex(scores, direction: mainDirection), flex: 5),
+ new Flexible(
+ child: new Row([w, _makeButton("Return to Lobby", _quitGameCallback)],
+ justifyContent: FlexJustifyContent.spaceAround),
+ flex: 1),
+ new Flexible(child: new Row([_makeDebugButtons()]), flex: 1)
+ ]);
}
Widget showDeal() {
diff --git a/lib/components/proto/proto.part.dart b/lib/components/proto/proto.part.dart
index 4a11900..a09f6f8 100644
--- a/lib/components/proto/proto.part.dart
+++ b/lib/components/proto/proto.part.dart
@@ -5,8 +5,9 @@
part of game_component;
class ProtoGameComponent extends GameComponent {
- ProtoGameComponent(Game game, NoArgCb cb, {double width, double height})
- : super(game, cb, width: width, height: height);
+ ProtoGameComponent(Croupier croupier, NoArgCb cb,
+ {double width, double height})
+ : super(croupier, cb, width: width, height: height);
ProtoGameComponentState createState() => new ProtoGameComponentState();
}
diff --git a/lib/components/solitaire/solitaire.part.dart b/lib/components/solitaire/solitaire.part.dart
index fdcec8f..933922f 100644
--- a/lib/components/solitaire/solitaire.part.dart
+++ b/lib/components/solitaire/solitaire.part.dart
@@ -5,8 +5,9 @@
part of game_component;
class SolitaireGameComponent extends GameComponent {
- SolitaireGameComponent(Game game, NoArgCb cb, {double width, double height})
- : super(game, cb, width: width, height: height);
+ SolitaireGameComponent(Croupier croupier, NoArgCb cb,
+ {double width, double height})
+ : super(croupier, cb, width: width, height: height);
SolitaireGameComponentState createState() =>
new SolitaireGameComponentState();
@@ -31,10 +32,10 @@
child: solitaireWidget));
if (game.phase == SolitairePhase.Play) {
// All cards are visible.
- List<int> visibleCardCollections =
+ List<int> visibleCardCollectionIndexes =
game.cardCollections.asMap().keys.toList();
- children.add(this.buildCardAnimationLayer(visibleCardCollections));
+ children.add(this.buildCardAnimationLayer(visibleCardCollectionIndexes));
}
return new Container(
diff --git a/lib/logic/croupier.dart b/lib/logic/croupier.dart
index ee119ac..8d85e76 100644
--- a/lib/logic/croupier.dart
+++ b/lib/logic/croupier.dart
@@ -70,6 +70,12 @@
}
}
+ int userIDFromPlayerNumber(int playerNumber) {
+ return players_found.keys.firstWhere(
+ (int user) => players_found[user] == playerNumber,
+ orElse: () => null);
+ }
+
void _updatePlayerFoundCb(String playerID, String playerNum) {
int id = int.parse(playerID);
if (playerNum == null) {
diff --git a/lib/logic/hearts/hearts_game.part.dart b/lib/logic/hearts/hearts_game.part.dart
index b2bb768..fde65f7 100644
--- a/lib/logic/hearts/hearts_game.part.dart
+++ b/lib/logic/hearts/hearts_game.part.dart
@@ -63,6 +63,7 @@
// Used by the score screen to track scores and see which players are ready to continue to the next round.
List<int> scores = [0, 0, 0, 0];
+ List<int> deltaScores = [0, 0, 0, 0];
List<bool> ready;
HeartsGame(int playerNumber, {int gameID, bool isCreator})
@@ -443,11 +444,14 @@
}
void updateScore() {
+ // Clear out delta scores.
+ deltaScores = [0, 0, 0, 0];
+
// Count up points and check if someone shot the moon.
int shotMoon = null;
for (int i = 0; i < 4; i++) {
int delta = computeScore(i);
- this.scores[i] += delta;
+ this.deltaScores[i] = delta;
if (delta == 26) {
// Shot the moon!
shotMoon = i;
@@ -458,12 +462,17 @@
if (shotMoon != null) {
for (int i = 0; i < 4; i++) {
if (shotMoon == i) {
- this.scores[i] -= 26;
+ this.deltaScores[i] -= 26;
} else {
- this.scores[i] += 26;
+ this.deltaScores[i] += 26;
}
}
}
+
+ // Finally, apply deltaScores to scores. Preserve deltaScores for the UI.
+ for (int i = 0; i < 4; i++) {
+ this.scores[i] += this.deltaScores[i];
+ }
}
int computeScore(int player) {
diff --git a/lib/styles/common.dart b/lib/styles/common.dart
index ccbef24..5dffee2 100644
--- a/lib/styles/common.dart
+++ b/lib/styles/common.dart
@@ -13,6 +13,12 @@
static final TextStyle liveNow =
new TextStyle(fontSize: 12.0, color: theme.accentColor);
static final TextStyle error = new TextStyle(color: errorTextColor);
+ static final TextStyle hugeStyle = new TextStyle(fontSize: 32.0);
+ static final TextStyle hugeRedStyle =
+ new TextStyle(fontSize: 32.0, color: errorTextColor);
+ static final TextStyle largeStyle = new TextStyle(fontSize: 24.0);
+ static final TextStyle largeRedStyle =
+ new TextStyle(fontSize: 24.0, color: errorTextColor);
}
class Size {
diff --git a/pubspec.lock b/pubspec.lock
index 226e558..be2270f 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -120,23 +120,23 @@
mojo:
description: mojo
source: hosted
- version: "0.4.3"
+ version: "0.4.5"
mojo_apptest:
description: mojo_apptest
source: hosted
- version: "0.2.8"
+ version: "0.2.9"
mojo_sdk:
description: mojo_sdk
source: hosted
- version: "0.2.2"
+ version: "0.2.4"
mojo_services:
description: mojo_services
source: hosted
- version: "0.4.5"
+ version: "0.4.7"
mojom:
description: mojom
source: hosted
- version: "0.2.8"
+ version: "0.2.9"
mustache4dart:
description: mustache4dart
source: hosted
@@ -186,11 +186,11 @@
sky_engine:
description: sky_engine
source: hosted
- version: "0.0.57"
+ version: "0.0.65"
sky_services:
description: sky_services
source: hosted
- version: "0.0.57"
+ version: "0.0.65"
source_map_stack_trace:
description: source_map_stack_trace
source: hosted
@@ -218,7 +218,7 @@
test:
description: test
source: hosted
- version: "0.12.6"
+ version: "0.12.5+2"
typed_data:
description: typed_data
source: hosted
diff --git a/test/hearts_test.dart b/test/hearts_test.dart
index 1b92fd4..09160db 100644
--- a/test/hearts_test.dart
+++ b/test/hearts_test.dart
@@ -144,10 +144,12 @@
// Now, update the score, modifying game.scores.
game.updateScore();
expect(game.scores, equals([4, 8, 14, 0]));
+ expect(game.deltaScores, equals([4, 8, 14, 0]));
// Do it again.
game.updateScore();
expect(game.scores, equals([8, 16, 28, 0]));
+ expect(game.deltaScores, equals([4, 8, 14, 0]));
// Shoot the moon!
game.cardCollections[HeartsGame.PLAYER_A_TRICK] = <Card>[];
@@ -156,6 +158,7 @@
game.cardCollections[HeartsGame.PLAYER_D_TRICK] = Card.All;
game.updateScore();
expect(game.scores, equals([34, 42, 54, 0]));
+ expect(game.deltaScores, equals([26, 26, 26, 0]));
});
});
@@ -391,6 +394,7 @@
// Check score to ensure it matches the expectation.
expect(game.scores, equals([21, 3, 2, 0]));
+ expect(game.deltaScores, equals([21, 3, 2, 0]));
// Score consists of 4 ready commands.
runCommand();
@@ -435,6 +439,7 @@
2 + 26 + 26 + 26,
0 + 26 + 26 + 26
]));
+ 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.
@@ -449,6 +454,7 @@
2 + 26 + 26 + 26 + 26,
0 + 26 + 26 + 26 + 26
]));
+ expect(game.deltaScores, equals([0, 26, 26, 26]));
expect(game.hasGameEnded,
isTrue); // assumes game ends after about 100 points.
});