package freecell.model;
import freecell.model.action.MoveAction;
import freecell.model.pile.Cell;
import freecell.model.pile.Foundation;
import freecell.model.pile.Pile;
import freecell.model.pile.Tableau;
import java.util.ArrayList;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
public class Game {
private List<Cell> cells = new ArrayList<>(4);
private List<Foundation> foundations = new ArrayList<>(4);
private List<Tableau> tableaux = new ArrayList<>(8);
private MoveTracker moveTracker = new MoveTracker();
public Game() {
for (int i = 0; i < 4; i++) {
cells.add(new Cell());
foundations.add(new Foundation());
}
for (int i = 0; i < 8; i++)
tableaux.add(new Tableau());
newGame();
}
public List<Cell> getCells() {
return cells;
}
public List<Foundation> getFoundations() {
return foundations;
}
public List<Tableau> getTableaux() {
return tableaux;
}
/**
* Moves cards from one pile to another, if the move is valid.
*/
public boolean move(Pile fromPile, Pile toPile) {
int cardsMoved = toPile.moveFrom(fromPile);
if (cardsMoved > 0) {
moveTracker.addMove(new MoveAction(fromPile, toPile, cardsMoved));
return true;
}
return false;
}
/**
* Redo a move that was previously undone.
*
* @return true if another redo can be performed
*/
public boolean redo() {
if (moveTracker.hasNextMove()) {
moveTracker.getNextMove().redo();
}
return moveTracker.hasNextMove();
}
/**
* Undo a previous move.
*
* @return true if another undo can be performed
*/
public boolean undo() {
if (moveTracker.hasLastMove()) {
moveTracker.getLastMove().undo();
}
return moveTracker.hasLastMove();
}
/**
* Returns true if the game cannot be lost.
*/
public boolean isWon() {
for (Pile pile : tableaux) {
if (!pile.isInOrder()) {
return false;
}
}
return true;
}
/**
* Returns true if the game cannot be won.
*/
public boolean isLost() {
// Are free cells full?
for (Pile pile : cells) {
if (pile.isEmpty()) {
return false;
}
}
// Can you not move to any tableau?
for (Pile pile : tableaux) {
for (Pile tableau : tableaux) {
if (pile.canMoveFrom(tableau)) {
return false;
}
}
for (Pile cell : cells) {
if (pile.canMoveFrom(cell)) {
return false;
}
}
}
// Can you not move to any home cell?
for (Pile pile : foundations) {
for (Pile tableau : tableaux) {
if (pile.canMoveFrom(tableau)) {
return false;
}
}
for (Pile cell : cells) {
if (pile.canMoveFrom(cell)) {
return false;
}
}
}
return true;
}
public void newGame() {
Deck deck = Deck.newDeck();
deck.shuffle();
moveTracker.clearMoves();
for (Pile pile : tableaux) {
pile.clear();
}
for (Pile pile : cells) {
pile.clear();
}
for (Pile pile : foundations) {
pile.clear();
}
// Deal 6 cards to each tableau.
for (int i = 0; i < 6; i++) {
for (int j = 0; j < 8; j++) {
Card card = deck.deal();
card.turn();
tableaux.get(j).addCard(card);
}
}
// Deal an additional card to first 4.
for (int i = 0; i < 4; i++) {
Card card = deck.deal();
card.turn();
tableaux.get(i).addCard(card);
}
}
private static class MoveTracker {
private final Deque<MoveAction> nextMoves = new LinkedList<>();
private final Deque<MoveAction> previousMoves = new LinkedList<>();
public void clearMoves() {
nextMoves.clear();
previousMoves.clear();
}
public boolean hasLastMove() {
return !previousMoves.isEmpty();
}
public MoveAction getLastMove() {
MoveAction lastMove = previousMoves.pop();
nextMoves.push(lastMove);
return lastMove;
}
public boolean hasNextMove() {
return !nextMoves.isEmpty();
}
public MoveAction getNextMove() {
MoveAction nextMove = nextMoves.pop();
previousMoves.push(nextMove);
return nextMove;
}
public void addMove(MoveAction move) {
MoveAction nextMove = nextMoves.peek();
/*
* if new move differs from saved next move,
* clear the remaining next moves
*/
if (move.equals(nextMove)) {
nextMoves.pop();
} else {
nextMoves.clear();
}
previousMoves.push(move);
}
}
}