blob: f3c26ebcd10362caa4241d891b07b7c0dfda0913 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
part of solitaire;
class SolitaireCommand extends GameCommand {
// Usually this constructor is used when reading from a log/syncbase.
SolitaireCommand(String phase, String data)
: super(phase, data, simultaneity: SimulLevel.TURN_BASED);
SolitaireCommand.fromCommand(String cmd)
: super(cmd.split("|")[0], cmd.split("|")[1],
simultaneity: SimulLevel.TURN_BASED);
// The following constructors are used for the player generating the SolitaireCommand.
SolitaireCommand.deal(List<Card> allCards)
: super("Deal", computeDeal(allCards),
simultaneity: SimulLevel.TURN_BASED);
SolitaireCommand.move(Card target, int targetPile)
: super("Move", computeMove(target, targetPile),
simultaneity: SimulLevel.TURN_BASED);
SolitaireCommand.draw()
: super("Draw", computeDraw(), simultaneity: SimulLevel.TURN_BASED);
SolitaireCommand.flip(int targetPile)
: super("Flip", computeFlip(targetPile),
simultaneity: SimulLevel.TURN_BASED);
static String computeDeal(List<Card> allCards) {
StringBuffer buff = new StringBuffer();
allCards.forEach((card) => buff.write("${card.toString()}:"));
buff.write("END");
return buff.toString();
}
// Note: Depending on the target's position w.r.t. the targetPile, this may
// actually move a group of cards instead.
static String computeMove(Card target, int targetPile) {
return "${target.toString()}:${targetPile}:END";
}
// Note: If there are no cards to draw, this will reset the draw pile.
static String computeDraw() {
return "END";
}
static String computeFlip(int targetPile) {
return "${targetPile}:END";
}
@override
bool canExecute(Game g) {
// TODO(alexfandrianto): This is very similar to execute, but without the
// mutations. It's possible to use a shared function to simplify/combine the
// logic.
SolitaireGame game = g as SolitaireGame;
print("SolitaireCommand is checking: ${command}");
List<String> parts = data.split(":");
switch (phase) {
case "Deal":
return game.phase == SolitairePhase.Deal && parts.length - 1 == 52;
case "Move":
if (game.phase != SolitairePhase.Play) {
return false;
}
// Move the card to the pile.
Card c = new Card.fromString(parts[0]);
int targetId = int.parse(parts[1]);
int sourceId = game.findCard(c);
if (sourceId == -1) {
return false;
}
if (targetId < 0 || targetId >= game.cardCollections.length) {
return false;
}
List<Card> source = game.cardCollections[sourceId];
List<Card> dest = game.cardCollections[targetId];
// If the card isn't valid, then we have an error.
String reason = game.canPlay(c, dest);
if (reason != null) {
return false;
}
bool canTransfer = this.transferCheck(source, dest, c);
return canTransfer;
case "Draw":
if (game.phase != SolitairePhase.Play) {
return false;
}
List<Card> drawPile = game.cardCollections[SolitaireGame.OFFSET_DRAW];
List<Card> discardPile =
game.cardCollections[SolitaireGame.OFFSET_DISCARD];
return drawPile.length > 0 || discardPile.length > 0;
case "Flip":
if (game.phase != SolitairePhase.Play) {
return false;
}
int flipId = int.parse(parts[0]);
if (flipId < 0 || flipId >= 7) {
return false;
}
List<Card> flipSource =
game.cardCollections[SolitaireGame.OFFSET_DOWN + flipId];
List<Card> flipDest =
game.cardCollections[SolitaireGame.OFFSET_UP + flipId];
return flipDest.length == 0 && flipSource.length > 0;
default:
print(data);
assert(false); // How could this have happened?
return false;
}
}
@override
void execute(Game g) {
SolitaireGame game = g as SolitaireGame;
print("SolitaireCommand is executing: ${command}");
List<String> parts = data.split(":");
switch (phase) {
case "Deal":
if (game.phase != SolitairePhase.Deal) {
throw new StateError(
"Cannot process deal commands when not in Deal phase");
}
if (parts.length - 1 != 52) {
throw new StateError(
"Not enough cards dealt. Need 52, got ${parts.length - 1}");
}
// Deal fills out each of the down cards with one of each up card.
int index = 0;
for (int i = 0; i < 7; i++) {
for (int j = 0; j < i; j++) {
this.transfer(
game.deck,
game.cardCollections[SolitaireGame.OFFSET_DOWN + i],
new Card.fromString(parts[index]));
index++;
}
this.transfer(
game.deck,
game.cardCollections[SolitaireGame.OFFSET_UP + i],
new Card.fromString(parts[index]));
index++;
}
// The remaining cards are for the draw pile.
for (; index < 52; index++) {
this.transfer(
game.deck,
game.cardCollections[SolitaireGame.OFFSET_DRAW],
new Card.fromString(parts[index]));
}
return;
case "Move":
if (game.phase != SolitairePhase.Play) {
throw new StateError(
"Cannot process move commands when not in Play phase");
}
// Move the card to the pile.
Card c = new Card.fromString(parts[0]);
int targetId = int.parse(parts[1]);
int sourceId = game.findCard(c);
if (sourceId == -1) {
throw new StateError("Cannot move unknown card ${c.toString()}");
}
if (targetId < 0 || targetId >= game.cardCollections.length) {
throw new StateError("Cannot move to unknown pile ${targetId}");
}
List<Card> source = game.cardCollections[sourceId];
List<Card> dest = game.cardCollections[targetId];
// If the card isn't valid, then we have an error.
String reason = game.canPlay(c, dest);
if (reason != null) {
throw new StateError(
"Cannot move ${c.toString()} to Pile ${targetId} because ${reason}");
}
this.transferGroup(source, dest, c);
return;
case "Draw":
if (game.phase != SolitairePhase.Play) {
throw new StateError(
"Cannot process draw commands when not in Play phase");
}
List<Card> drawPile = game.cardCollections[SolitaireGame.OFFSET_DRAW];
List<Card> discardPile =
game.cardCollections[SolitaireGame.OFFSET_DISCARD];
if (drawPile.length != 0) {
this.transfer(drawPile, discardPile, drawPile[0]);
} else if (discardPile.length != 0) {
this.transferGroup(discardPile, drawPile, discardPile[0]);
} else {
throw new StateError("No cards left to draw");
}
return;
case "Flip":
if (game.phase != SolitairePhase.Play) {
throw new StateError(
"Cannot process flip commands when not in Play phase");
}
int flipId = int.parse(parts[0]);
if (flipId < 0 || flipId >= 7) {
throw new StateError(
"Cannot process flip command for index ${flipId}");
}
List<Card> flipSource =
game.cardCollections[SolitaireGame.OFFSET_DOWN + flipId];
List<Card> flipDest =
game.cardCollections[SolitaireGame.OFFSET_UP + flipId];
if (flipDest.length != 0) {
throw new StateError(
"Cannot flip ${flipId} because destination has cards");
}
if (flipSource.length == 0) {
throw new StateError(
"Cannot flip ${flipId} because source has no cards");
}
this.transfer(flipSource, flipDest, flipSource[flipSource.length - 1]);
return;
default:
print(data);
assert(false); // How could this have happened?
}
}
void transfer(List<Card> sender, List<Card> receiver, Card c) {
if (!sender.contains(c)) {
throw new StateError(
"Sender ${sender.toString()} lacks Card ${c.toString()}");
}
sender.remove(c);
receiver.add(c);
}
// Transfers every card from a certain cutoff card onwards.
void transferGroup(List<Card> sender, List<Card> receiver, Card c) {
int index = sender.indexOf(c);
if (index == -1) {
throw new StateError(
"Sender ${sender.toString()} lacks Card ${c.toString()}");
}
List<Card> lost =
new List<Card>.from(sender.getRange(index, sender.length));
sender.removeRange(index, sender.length);
receiver.addAll(lost);
}
bool transferCheck(List<Card> sender, List<Card> receiver, Card c) {
return sender.contains(c);
}
}