package com.charego.freecellfx.model; import com.charego.freecellfx.model.pile.Cell; import com.charego.freecellfx.model.pile.Pile; import com.charego.freecellfx.model.action.MoveAction; import com.charego.freecellfx.model.pile.Foundation; import com.charego.freecellfx.model.pile.Tableau; import java.util.ArrayList; import java.util.Deque; import java.util.LinkedList; import java.util.List; public class Game { private List cells = new ArrayList<>(4); private List foundations = new ArrayList<>(4); private List 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 getCells() { return cells; } public List getFoundations() { return foundations; } public List getTableaux() { return tableaux; } /** * Moves cards from one pile to another, if the move is valid. */ public boolean tryMove(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(); tableaux.forEach(Pile::clear); cells.forEach(Pile::clear); foundations.forEach(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 nextMoves = new LinkedList<>(); private final Deque 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); } } }