package com.christophdietze.jack.shared.board; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; /** * An immutable Position. * * <pre> * |-----------------------| * 8 |56|57|58|59|60|61|62|63| * |--+--+--+--+--+--+--+--| * 7 |48|49|50|51|52|53|54|55| * |--+--+--+--+--+--+--+--| * 6 |40|41|42|43|44|45|46|47| * |--+--+--+--+--+--+--+--| * 5 |32|33|34|35|36|37|38|39| * |--+--+--+--+--+--+--+--| * 4 |24|25|26|27|28|29|30|31| * |--+--+--+--+--+--+--+--| * 3 |16|17|18|19|20|21|22|23| * |--+--+--+--+--+--+--+--| * 2 | 8| 9|10|11|12|13|14|15| * |--+--+--+--+--+--+--+--| * 1 | 0| 1| 2| 3| 4| 5| 6| 7| * |-----------------------| * a b c d e f g h * </pre> * * 'a' to 'h' are called files and '1' to '8' are called ranks. */ public class Position implements Serializable { private static final long serialVersionUID = 1L; public static final Position STARTING_POSITION = buildStartingPosition(); public static final Position EMPTY_POSITION = new Builder().build(); private final ImmutableList<Piece> pieces; private final boolean whiteToMove; private final boolean canWhiteCastleKingside; private final boolean canWhiteCastleQueenside; private final boolean canBlackCastleKingside; private final boolean canBlackCastleQueenside; /** Starts with 1 and is incremented after every black move. */ private final int fullmoveNumber; /** Number of plys since the last pawn has moved. */ private final int halfmoveClock; /** After a double pawn move, contains the index of the skipped square. Is null otherwise. */ private final Integer enPassantPawnIndex; private final ImmutableList<Integer> phantomKingIndices; private Position(Builder b) { this.pieces = ImmutableList.copyOf(b.pieces); this.whiteToMove = b.whiteToMove; this.canWhiteCastleKingside = b.canWhiteCastleKingside; this.canWhiteCastleQueenside = b.canWhiteCastleQueenside; this.canBlackCastleKingside = b.canBlackCastleKingside; this.canBlackCastleQueenside = b.canBlackCastleQueenside; this.fullmoveNumber = b.fullmoveNumber; this.halfmoveClock = b.halfmoveClock; this.enPassantPawnIndex = b.enPassantPawnIndex; this.phantomKingIndices = ImmutableList.copyOf(b.phantomKingIndices); } public Piece getPiece(int index) { return pieces.get(index); } public Piece getPiece(String algebraic) { return pieces.get(ChessUtils.toIndexFromAlgebraic(algebraic)); } public boolean isWhiteToMove() { return whiteToMove; } public boolean canWhiteCastleKingside() { return canWhiteCastleKingside; } public boolean canWhiteCastleQueenside() { return canWhiteCastleQueenside; } public boolean canBlackCastleKingside() { return canBlackCastleKingside; } public boolean canBlackCastleQueenside() { return canBlackCastleQueenside; } public int getFullmoveNumber() { return fullmoveNumber; } public int getHalfmoveClock() { return halfmoveClock; } public ImmutableList<Integer> getPhantomKingIndices() { return phantomKingIndices; } /** * starts with 0 */ public int getPly() { return ChessUtils.toPlyFromFullmoveNumber(fullmoveNumber, whiteToMove); } public List<Integer> findPieces(final Piece piece) { List<Integer> result = new ArrayList<Integer>(); for (int index = 0; index < 64; ++index) { if (pieces.get(index).equals(piece)) { result.add(index); } } return result; } // public Position setWhiteToMove(boolean whiteToMove) { // return new Builder(this).whiteToMove(whiteToMove).build(); // } public static class Builder { private List<Piece> pieces; private boolean whiteToMove = true; private boolean canWhiteCastleKingside = true; private boolean canWhiteCastleQueenside = true; private boolean canBlackCastleKingside = true; private boolean canBlackCastleQueenside = true; private int fullmoveNumber = 1; private int halfmoveClock = 0; private Integer enPassantPawnIndex; private List<Integer> phantomKingIndices = ImmutableList.of(); public Builder() { pieces = Lists.newArrayList(); for (int i = 0; i < 64; ++i) { pieces.add(Piece.EMPTY); } } public Builder(Position position) { this.pieces = position.pieces; this.whiteToMove = position.whiteToMove; this.canWhiteCastleKingside = position.canWhiteCastleKingside; this.canWhiteCastleQueenside = position.canWhiteCastleQueenside; this.canBlackCastleKingside = position.canBlackCastleKingside; this.canBlackCastleQueenside = position.canBlackCastleQueenside; this.fullmoveNumber = position.fullmoveNumber; this.halfmoveClock = position.halfmoveClock; this.phantomKingIndices = position.phantomKingIndices; this.enPassantPawnIndex = position.enPassantPawnIndex; } public Piece getPiece(int index) { return pieces.get(index); } public boolean isWhiteToMove() { return whiteToMove; } public int getFullmoveNumber() { return fullmoveNumber; } public Builder piece(int index, Piece piece) { if (pieces instanceof ImmutableList<?>) { this.pieces = Lists.newArrayList(this.pieces); } this.pieces.set(index, piece); return this; } public Builder whiteToMove(boolean whiteToMove) { this.whiteToMove = whiteToMove; return this; } public Builder canWhiteCastleKingside(boolean canWhiteCastleKingside) { this.canWhiteCastleKingside = canWhiteCastleKingside; return this; } public Builder canWhiteCastleQueenside(boolean canWhiteCastleQueenside) { this.canWhiteCastleQueenside = canWhiteCastleQueenside; return this; } public Builder canBlackCastleKingside(boolean canBlackCastleKingside) { this.canBlackCastleKingside = canBlackCastleKingside; return this; } public Builder canBlackCastleQueenside(boolean canBlackCastleQueenside) { this.canBlackCastleQueenside = canBlackCastleQueenside; return this; } public Builder fullmoveNumber(int fullmoveNumber) { this.fullmoveNumber = fullmoveNumber; return this; } public Builder halfmoveClock(int halfmoveClock) { this.halfmoveClock = halfmoveClock; return this; } public Builder enPassantPawnIndex(int enPassantPawnIndex, boolean isWhiteEnPassantPawn) { clearEnPassantPawn(); this.enPassantPawnIndex = enPassantPawnIndex; piece(enPassantPawnIndex, isWhiteEnPassantPawn ? Piece.WHITE_EN_PASSANT_PAWN : Piece.BLACK_EN_PASSANT_PAWN); return this; } public Builder clearEnPassantPawn() { if (this.enPassantPawnIndex != null) { piece(enPassantPawnIndex, Piece.EMPTY); } this.enPassantPawnIndex = null; return this; } public Builder clearPhantomKings() { this.phantomKingIndices = ImmutableList.of(); return this; } public Builder setPhantomKings(int indexA, int indexB) { this.phantomKingIndices = ImmutableList.of(indexA, indexB); return this; } public Builder switchPlayerToMove() { this.whiteToMove = !whiteToMove; return this; } public Position build() { return new Position(this); } } private static Position buildStartingPosition() { Builder b = new Builder(); b.piece(0, Piece.WHITE_ROOK).piece(1, Piece.WHITE_KNIGHT).piece(2, Piece.WHITE_BISHOP) .piece(3, Piece.WHITE_QUEEN).piece(4, Piece.WHITE_KING).piece(5, Piece.WHITE_BISHOP) .piece(6, Piece.WHITE_KNIGHT).piece(7, Piece.WHITE_ROOK); for (int i = 8; i < 16; ++i) { b.piece(i, Piece.WHITE_PAWN); } b.piece(56, Piece.BLACK_ROOK).piece(57, Piece.BLACK_KNIGHT).piece(58, Piece.BLACK_BISHOP) .piece(59, Piece.BLACK_QUEEN).piece(60, Piece.BLACK_KING).piece(61, Piece.BLACK_BISHOP) .piece(62, Piece.BLACK_KNIGHT).piece(63, Piece.BLACK_ROOK); for (int i = 48; i < 56; ++i) { b.piece(i, Piece.BLACK_PAWN); } return b.build(); } }