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