/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package uk.co.iscoding.freecell.game;
import com.google.common.base.Objects;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import uk.co.iscoding.freecell.cards.DeckOfCards;
import uk.co.iscoding.freecell.cards.PlayingCard;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import static uk.co.iscoding.freecell.game.CardCollection.StartingSuit;
/**
*
* @author Stuart David James McHattie
* @version 1.0 2011-06-30
* @since 2011-06-30
*/
public class FreeCellLayout {
private enum Area {
FREECELLS,
FOUNDATIONS,
COLUMNS,
}
private final ArrayList<String> moveSequence;
private final ArrayList<FreeCell> cells;
private final ArrayList<Foundation> founds;
private final ArrayList<FreeCellColumn> cols;
public FreeCellLayout(DeckOfCards deck) {
this.moveSequence = Lists.newArrayList(); // No prior moves
// Create FreeCells
this.cells = Lists.newArrayList();
for (int i = 1; i <= 4; i++) {
FreeCell freeCell = new FreeCell();
freeCell.setName("FreeCell #" + i);
cells.add(freeCell);
}
// Create Foundations
this.founds = Lists.newArrayList();
for (int i = 1; i <= 4; i++) {
Foundation newFound = new Foundation(StartingSuit.values()[i]);
newFound.setName("Foundation #" + i);
founds.add(newFound);
}
// Create some columns and name them
this.cols = Lists.newArrayList();
for (int i = 0; i < 8; i++) {
FreeCellColumn newCol = new FreeCellColumn();
newCol.setName("Column #" + (i + 1));
cols.add(newCol);
}
// Add cards to the columns
for (int i = 0; i < 52; i++) {
cols.get(i % 8).addCard(deck.nextCard(), true);
}
}
private FreeCellLayout(ArrayList<String> moveSequence, ArrayList<FreeCell> cells,
ArrayList<Foundation> founds, ArrayList<FreeCellColumn> cols) {
this.moveSequence = moveSequence;
this.cells = cells;
this.founds = founds;
this.cols = cols;
}
private FreeCellLayout deepClone() {
// Strings are immutable so can just clone the move sequence
ArrayList<String> cloneMoveSequence = Lists.newArrayList(moveSequence);
ArrayList<FreeCell> cloneCells = Lists.newArrayList();
for (FreeCell cell : cells) {
cloneCells.add(cell.deepClone());
}
ArrayList<Foundation> cloneFounds = Lists.newArrayList();
for (Foundation found : founds) {
cloneFounds.add(found.deepClone());
}
ArrayList<FreeCellColumn> cloneCols = Lists.newArrayList();
for (FreeCellColumn col : cols) {
cloneCols.add(col.deepClone());
}
return new FreeCellLayout(cloneMoveSequence, cloneCells, cloneFounds, cloneCols);
}
public boolean isFinished() {
boolean finished = true;
for (Foundation found : founds) {
if (found.getNumberOfCards() != 13) finished = false;
}
return finished;
}
public void printMoves() {
for (int i = 0; i < moveSequence.size(); i++) {
System.out.println(String.format("%03d: %s", (i+1), moveSequence.get(i)));
}
}
public Set<FreeCellLayout> getPossibleMoves() {
Set<FreeCellLayout> possibleMoves = Sets.newHashSet();
int maxMove = 5;
for (FreeCell cell : cells) {
if (cell.isOccupied()) {
maxMove--;
}
}
int emptyColumns = 0;
for (FreeCellColumn col : cols) {
if (col.getNumberOfCards() == 0) emptyColumns++;
}
if (emptyColumns > 0) maxMove *= 2 * emptyColumns;
/**
* Move cards to foundations
*/
for (int foundIndex = 0; foundIndex < founds.size(); foundIndex++) {
Foundation found = founds.get(foundIndex);
for (int colIndex = 0; colIndex < cols.size(); colIndex++) {
FreeCellColumn col = cols.get(colIndex);
if (col.getNumberOfCards() > 0 && found.canAddCard(col.getTopCard())) {
possibleMoves.add(cloneAndMove(Area.COLUMNS, colIndex, Area.FOUNDATIONS, foundIndex, 1));
}
}
for (int cellIndex = 0; cellIndex < cells.size(); cellIndex++) {
FreeCell cell = cells.get(cellIndex);
if (cell.isOccupied() && found.canAddCard(cell.getTopCard())) {
possibleMoves.add(cloneAndMove(Area.FREECELLS, cellIndex, Area.FOUNDATIONS, foundIndex, 1));
}
}
}
/**
* Move holding cards to non-empty columns
*/
for (int cellIndex = 0; cellIndex < cells.size(); cellIndex++) {
FreeCell cell = cells.get(cellIndex);
for (int colIndex = 0; colIndex < cols.size(); colIndex++) {
FreeCellColumn col = cols.get(colIndex);
if (cell.isOccupied() && col.getNumberOfCards() > 0 && col.canAddCard(cell.getTopCard())) {
possibleMoves.add(cloneAndMove(Area.FREECELLS, cellIndex, Area.COLUMNS, colIndex, 1));
}
}
}
/**
* Move whole sequences to other columns
*/
for (int fromIndex = 0; fromIndex < cols.size(); fromIndex++) {
FreeCellColumn fromCol = cols.get(fromIndex);
CardCollection candidatesToMove = fromCol.getLongestSequenceNoLongerThan(maxMove);
if (candidatesToMove == null) {
continue;
}
for (int toIndex = 0; toIndex < cols.size(); toIndex++) {
FreeCellColumn toCol = cols.get(toIndex);
if (toCol.canAddCard(candidatesToMove.getBottomCard())) {
possibleMoves.add(cloneAndMove(Area.COLUMNS, fromIndex, Area.COLUMNS, toIndex,
candidatesToMove.getNumberOfCards()));
}
}
}
/**
* Move whole sequences to empty columns, unless:
* The sequence being moved is the whole column
*/
int emptyColIndex = -1;
for (int colIndex = 0; colIndex < cols.size(); colIndex++) {
FreeCellColumn col = cols.get(colIndex);
if (col.getNumberOfCards() == 0) {
emptyColIndex = colIndex;
break;
}
}
if (emptyColIndex >= 0) {
int reducedMaxMove = maxMove /= 2 * emptyColumns;
if (emptyColumns > 1) reducedMaxMove *= 2 * (emptyColumns - 1);
for (int fromIndex = 0; fromIndex < cols.size(); fromIndex++) {
FreeCellColumn fromCol = cols.get(fromIndex);
CardCollection candidatesToMove = fromCol.getLongestSequenceNoLongerThan(reducedMaxMove);
if (candidatesToMove == null || candidatesToMove.getNumberOfCards() == fromCol.getNumberOfCards()) {
continue;
}
possibleMoves.add(cloneAndMove(Area.COLUMNS, fromIndex, Area.COLUMNS, emptyColIndex,
candidatesToMove.getNumberOfCards()));
}
}
/**
* Move whole sequences to free cells, unless:
* The sequence being moved could move to a non-empty column
*/
List<Integer> emptyFreeCellIndices = Lists.newArrayList();
for (int cellIndex = 0; cellIndex < cells.size(); cellIndex++) {
if (!cells.get(cellIndex).isOccupied()) {
emptyFreeCellIndices.add(cellIndex);
}
}
for (int fromIndex = 0; fromIndex < cols.size(); fromIndex++) {
FreeCellColumn fromCol = cols.get(fromIndex);
CardCollection candidatesToMove = fromCol.getLongestSequenceNoLongerThan(emptyFreeCellIndices.size());
if (candidatesToMove == null) {
continue;
}
boolean cannotAddToNonEmptyColumn = true;
for (FreeCellColumn col : cols) {
if (col.getNumberOfCards() > 0 && col.canAddCard(candidatesToMove.getBottomCard())) {
cannotAddToNonEmptyColumn = false;
}
}
if (cannotAddToNonEmptyColumn) {
FreeCellLayout clone = deepClone();
FreeCellColumn cloneFromCol = clone.cols.get(fromIndex);
cloneFromCol.removeTopCards(candidatesToMove.getNumberOfCards());
for (int i = 0; i < candidatesToMove.getNumberOfCards(); i++) {
clone.cells.get(emptyFreeCellIndices.get(i)).addCard(candidatesToMove.getCard(i));
}
if (candidatesToMove.getNumberOfCards() == 2) {
clone.moveSequence.add(String.format("Move %s and 1 other card from %s to freecells",
candidatesToMove.getBottomCard(), fromCol));
} else {
clone.moveSequence.add(String.format("Move %s and %d other cards from %s to freecells",
candidatesToMove.getBottomCard(), candidatesToMove.getNumberOfCards() - 1, fromCol));
}
possibleMoves.add(clone);
}
}
/**
* Move whole column into free cells, unless:
* Doing so would reduce maxMove
*/
/**
* Move holding cards to an empty column, unless:
* Another holding card could be its parent
*/
return possibleMoves;
}
private FreeCellLayout cloneAndMove(Area fromArea, int fromIndex, Area toArea, int toIndex, int numCards) {
FreeCellLayout clone = deepClone();
CardCollection from = null;
CardCollection to = null;
switch(fromArea) {
case FREECELLS:
from = clone.cells.get(fromIndex);
break;
case FOUNDATIONS:
from = clone.founds.get(fromIndex);
break;
case COLUMNS:
from = clone.cols.get(fromIndex);
break;
}
switch(toArea) {
case FREECELLS:
to = clone.cells.get(toIndex);
break;
case FOUNDATIONS:
to = clone.founds.get(toIndex);
break;
case COLUMNS:
to = clone.cols.get(toIndex);
break;
}
if (numCards == 1) {
clone.moveSequence.add(String.format("Move %s from %s to %s", from.getTopCard(), from, to));
} else {
PlayingCard topOfMovedPile = from.getTopCards(numCards).getBottomCard();
if (numCards == 2) {
clone.moveSequence.add(String.format("Move %s and 1 other card from %s to %s", topOfMovedPile, from, to));
} else {
clone.moveSequence.add(String.format("Move %s and %d other cards from %s to %s", topOfMovedPile, numCards - 1, from, to));
}
}
CardCollection.moveCards(from, to, numCards);
return clone;
}
@Override
public boolean equals(Object other) {
if (!(other instanceof FreeCellLayout)) return false;
FreeCellLayout otherLayout = (FreeCellLayout)other;
// Check each set for equality
if (!Sets.newHashSet(cells).equals(Sets.newHashSet(otherLayout.cells))) return false;
if (!Sets.newHashSet(founds).equals(Sets.newHashSet(otherLayout.founds))) return false;
if (!Sets.newHashSet(cols).equals(Sets.newHashSet(otherLayout.cols))) return false;
return true;
}
@Override
public int hashCode() {
return Objects.hashCode(Sets.newHashSet(cells), Sets.newHashSet(cols), Sets.newHashSet(founds));
}
}