blob: 0d18a26853fd241dc4971aa11171280ec7c51352 [file] [log] [blame]
import 'card.dart' show Card;
import 'dart:math' show Random;
enum GameType {
Hearts
}
/// A game consists of multiple decks and tracks a single deck of cards.
/// It also handles events; when cards are dragged to and from decks.
class Game {
final GameType gameType;
final List<List<Card>> cardCollections = new List<List<Card>>();
final List<Card> deck = <Card>[
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(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(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> deckPeek(int numCards) {
assert(deck.length >= numCards);
List<Card> cards = new List<Card>.from(deck.take(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.
debugString = 'Moving... ${card.toString()}';
int i = findCard(card);
if (i == -1) {
debugString = 'NO... ${card.toString()}';
return;
}
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);
}
// Which card collection has the card?
int findCard(Card card) {
for (int i = 0; i < cardCollections.length; i++) {
if (cardCollections[i].contains(card)) {
return i;
}
}
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);
}
}