/* CuckooChess - A java chess program. Copyright (C) 2011 Peter Ă–sterlund, peterosterlund2@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package chess; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; /** * Stores the state of a chess position. * All required state is stored, except for all previous positions * since the last capture or pawn move. That state is only needed * for three-fold repetition draw detection, and is better stored * in a separate hash table. * @author petero */ public class Position { public int[] squares; // Bitboards public long[] pieceTypeBB; public long whiteBB, blackBB; // Piece square table scores public short[] psScore1, psScore2; public boolean whiteMove; /** Bit definitions for the castleMask bit mask. */ public static final int A1_CASTLE = 0; /** White long castle. */ public static final int H1_CASTLE = 1; /** White short castle. */ public static final int A8_CASTLE = 2; /** Black long castle. */ public static final int H8_CASTLE = 3; /** Black short castle. */ private int castleMask; private int epSquare; /** Number of half-moves since last 50-move reset. */ int halfMoveClock; /** Game move number, starting from 1. */ public int fullMoveCounter; private long hashKey; // Cached Zobrist hash key private long pHashKey; public int wKingSq, bKingSq; // Cached king positions public int wMtrl; // Total value of all white pieces and pawns public int bMtrl; // Total value of all black pieces and pawns public int wMtrlPawns; // Total value of all white pawns public int bMtrlPawns; // Total value of all black pawns /** Initialize board to empty position. */ public Position() { squares = new int[64]; for (int i = 0; i < 64; i++) squares[i] = Piece.EMPTY; pieceTypeBB = new long[Piece.nPieceTypes]; psScore1 = new short[Piece.nPieceTypes]; psScore2 = new short[Piece.nPieceTypes]; for (int i = 0; i < Piece.nPieceTypes; i++) { pieceTypeBB[i] = 0L; psScore1[i] = 0; psScore2[i] = 0; } whiteBB = blackBB = 0L; whiteMove = true; castleMask = 0; epSquare = -1; halfMoveClock = 0; fullMoveCounter = 1; hashKey = computeZobristHash(); wKingSq = bKingSq = -1; wMtrl = bMtrl = -Evaluate.kV; wMtrlPawns = bMtrlPawns = 0; } public Position(Position other) { squares = new int[64]; for (int i = 0; i < 64; i++) squares[i] = other.squares[i]; pieceTypeBB = new long[Piece.nPieceTypes]; psScore1 = new short[Piece.nPieceTypes]; psScore2 = new short[Piece.nPieceTypes]; for (int i = 0; i < Piece.nPieceTypes; i++) { pieceTypeBB[i] = other.pieceTypeBB[i]; psScore1[i] = other.psScore1[i]; psScore2[i] = other.psScore2[i]; } whiteBB = other.whiteBB; blackBB = other.blackBB; whiteMove = other.whiteMove; castleMask = other.castleMask; epSquare = other.epSquare; halfMoveClock = other.halfMoveClock; fullMoveCounter = other.fullMoveCounter; hashKey = other.hashKey; pHashKey = other.pHashKey; wKingSq = other.wKingSq; bKingSq = other.bKingSq; wMtrl = other.wMtrl; bMtrl = other.bMtrl; wMtrlPawns = other.wMtrlPawns; bMtrlPawns = other.bMtrlPawns; } @Override public boolean equals(Object o) { if ((o == null) || (o.getClass() != this.getClass())) return false; Position other = (Position)o; if (!drawRuleEquals(other)) return false; if (halfMoveClock != other.halfMoveClock) return false; if (fullMoveCounter != other.fullMoveCounter) return false; if (hashKey != other.hashKey) return false; if (pHashKey != other.pHashKey) return false; return true; } @Override public int hashCode() { return (int)hashKey; } /** * Return Zobrist hash value for the current position. * Everything except the move counters are included in the hash value. */ public final long zobristHash() { return hashKey; } public final long pawnZobristHash() { return pHashKey; } public final long kingZobristHash() { return psHashKeys[Piece.WKING][wKingSq] ^ psHashKeys[Piece.BKING][bKingSq]; } public final long historyHash() { long ret = hashKey; if (halfMoveClock >= 80) { ret ^= moveCntKeys[Math.min(halfMoveClock, 100)]; } return ret; } /** * Decide if two positions are equal in the sense of the draw by repetition rule. * @return True if positions are equal, false otherwise. */ final public boolean drawRuleEquals(Position other) { for (int i = 0; i < 64; i++) { if (squares[i] != other.squares[i]) return false; } if (whiteMove != other.whiteMove) return false; if (castleMask != other.castleMask) return false; if (epSquare != other.epSquare) return false; return true; } public final void setWhiteMove(boolean whiteMove) { if (whiteMove != this.whiteMove) { hashKey ^= whiteHashKey; this.whiteMove = whiteMove; } } /** Return index in squares[] vector corresponding to (x,y). */ public final static int getSquare(int x, int y) { return y * 8 + x; } /** Return x position (file) corresponding to a square. */ public final static int getX(int square) { return square & 7; } /** Return y position (rank) corresponding to a square. */ public final static int getY(int square) { return square >> 3; } /** Return true if (x,y) is a dark square. */ public final static boolean darkSquare(int x, int y) { return (x & 1) == (y & 1); } /** Return piece occupying a square. */ public final int getPiece(int square) { return squares[square]; } /** Move a non-pawn piece to an empty square. */ private final void movePieceNotPawn(int from, int to) { final int piece = squares[from]; hashKey ^= psHashKeys[piece][from]; hashKey ^= psHashKeys[piece][to]; hashKey ^= psHashKeys[Piece.EMPTY][from]; hashKey ^= psHashKeys[Piece.EMPTY][to]; squares[from] = Piece.EMPTY; squares[to] = piece; final long sqMaskF = 1L << from; final long sqMaskT = 1L << to; pieceTypeBB[piece] &= ~sqMaskF; pieceTypeBB[piece] |= sqMaskT; if (Piece.isWhite(piece)) { whiteBB &= ~sqMaskF; whiteBB |= sqMaskT; if (piece == Piece.WKING) wKingSq = to; } else { blackBB &= ~sqMaskF; blackBB |= sqMaskT; if (piece == Piece.BKING) bKingSq = to; } psScore1[piece] += Evaluate.psTab1[piece][to] - Evaluate.psTab1[piece][from]; psScore2[piece] += Evaluate.psTab2[piece][to] - Evaluate.psTab2[piece][from]; } /** Set a square to a piece value. */ public final void setPiece(int square, int piece) { int removedPiece = squares[square]; squares[square] = piece; // Update hash key hashKey ^= psHashKeys[removedPiece][square]; hashKey ^= psHashKeys[piece][square]; // Update bitboards final long sqMask = 1L << square; pieceTypeBB[removedPiece] &= ~sqMask; pieceTypeBB[piece] |= sqMask; if (removedPiece != Piece.EMPTY) { int pVal = Evaluate.pieceValue[removedPiece]; if (Piece.isWhite(removedPiece)) { wMtrl -= pVal; whiteBB &= ~sqMask; if (removedPiece == Piece.WPAWN) { wMtrlPawns -= pVal; pHashKey ^= psHashKeys[Piece.WPAWN][square]; } } else { bMtrl -= pVal; blackBB &= ~sqMask; if (removedPiece == Piece.BPAWN) { bMtrlPawns -= pVal; pHashKey ^= psHashKeys[Piece.BPAWN][square]; } } } if (piece != Piece.EMPTY) { int pVal = Evaluate.pieceValue[piece]; if (Piece.isWhite(piece)) { wMtrl += pVal; whiteBB |= sqMask; if (piece == Piece.WPAWN) { wMtrlPawns += pVal; pHashKey ^= psHashKeys[Piece.WPAWN][square]; } if (piece == Piece.WKING) wKingSq = square; } else { bMtrl += pVal; blackBB |= sqMask; if (piece == Piece.BPAWN) { bMtrlPawns += pVal; pHashKey ^= psHashKeys[Piece.BPAWN][square]; } if (piece == Piece.BKING) bKingSq = square; } } // Update piece/square table scores psScore1[removedPiece] -= Evaluate.psTab1[removedPiece][square]; psScore2[removedPiece] -= Evaluate.psTab2[removedPiece][square]; psScore1[piece] += Evaluate.psTab1[piece][square]; psScore2[piece] += Evaluate.psTab2[piece][square]; } /** * Set a square to a piece value. * Special version that only updates enough of the state for the SEE function to be happy. */ public final void setSEEPiece(int square, int piece) { int removedPiece = squares[square]; // Update board squares[square] = piece; // Update bitboards long sqMask = 1L << square; pieceTypeBB[removedPiece] &= ~sqMask; pieceTypeBB[piece] |= sqMask; if (removedPiece != Piece.EMPTY) { if (Piece.isWhite(removedPiece)) whiteBB &= ~sqMask; else blackBB &= ~sqMask; } if (piece != Piece.EMPTY) { if (Piece.isWhite(piece)) whiteBB |= sqMask; else blackBB |= sqMask; } } /** Return true if white long castling right has not been lost. */ public final boolean a1Castle() { return (castleMask & (1 << A1_CASTLE)) != 0; } /** Return true if white short castling right has not been lost. */ public final boolean h1Castle() { return (castleMask & (1 << H1_CASTLE)) != 0; } /** Return true if black long castling right has not been lost. */ public final boolean a8Castle() { return (castleMask & (1 << A8_CASTLE)) != 0; } /** Return true if black short castling right has not been lost. */ public final boolean h8Castle() { return (castleMask & (1 << H8_CASTLE)) != 0; } /** Bitmask describing castling rights. */ public final int getCastleMask() { return castleMask; } public final void setCastleMask(int castleMask) { hashKey ^= castleHashKeys[this.castleMask]; hashKey ^= castleHashKeys[castleMask]; this.castleMask = castleMask; } /** En passant square, or -1 if no ep possible. */ public final int getEpSquare() { return epSquare; } public final void setEpSquare(int epSquare) { if (this.epSquare != epSquare) { hashKey ^= epHashKeys[(this.epSquare >= 0) ? getX(this.epSquare) + 1 : 0]; hashKey ^= epHashKeys[(epSquare >= 0) ? getX(epSquare) + 1 : 0]; this.epSquare = epSquare; } } public final int getKingSq(boolean white) { return white ? wKingSq : bKingSq; } /** Apply a move to the current position. */ public final void makeMove(Move move, UndoInfo ui) { ui.capturedPiece = squares[move.to]; ui.castleMask = castleMask; ui.epSquare = epSquare; ui.halfMoveClock = halfMoveClock; boolean wtm = whiteMove; final int p = squares[move.from]; int capP = squares[move.to]; long fromMask = 1L << move.from; int prevEpSquare = epSquare; setEpSquare(-1); if ((capP != Piece.EMPTY) || (((pieceTypeBB[Piece.WPAWN] | pieceTypeBB[Piece.BPAWN]) & fromMask) != 0)) { halfMoveClock = 0; // Handle en passant and epSquare if (p == Piece.WPAWN) { if (move.to - move.from == 2 * 8) { int x = Position.getX(move.to); if ( ((x > 0) && (squares[move.to - 1] == Piece.BPAWN)) || ((x < 7) && (squares[move.to + 1] == Piece.BPAWN))) { setEpSquare(move.from + 8); } } else if (move.to == prevEpSquare) { setPiece(move.to - 8, Piece.EMPTY); } } else if (p == Piece.BPAWN) { if (move.to - move.from == -2 * 8) { int x = Position.getX(move.to); if ( ((x > 0) && (squares[move.to - 1] == Piece.WPAWN)) || ((x < 7) && (squares[move.to + 1] == Piece.WPAWN))) { setEpSquare(move.from - 8); } } else if (move.to == prevEpSquare) { setPiece(move.to + 8, Piece.EMPTY); } } if (((pieceTypeBB[Piece.WKING] | pieceTypeBB[Piece.BKING]) & fromMask) != 0) { if (wtm) { setCastleMask(castleMask & ~(1 << Position.A1_CASTLE)); setCastleMask(castleMask & ~(1 << Position.H1_CASTLE)); } else { setCastleMask(castleMask & ~(1 << Position.A8_CASTLE)); setCastleMask(castleMask & ~(1 << Position.H8_CASTLE)); } } // Perform move setPiece(move.from, Piece.EMPTY); // Handle promotion if (move.promoteTo != Piece.EMPTY) { setPiece(move.to, move.promoteTo); } else { setPiece(move.to, p); } } else { halfMoveClock++; // Handle castling if (((pieceTypeBB[Piece.WKING] | pieceTypeBB[Piece.BKING]) & fromMask) != 0) { int k0 = move.from; if (move.to == k0 + 2) { // O-O movePieceNotPawn(k0 + 3, k0 + 1); } else if (move.to == k0 - 2) { // O-O-O movePieceNotPawn(k0 - 4, k0 - 1); } if (wtm) { setCastleMask(castleMask & ~(1 << Position.A1_CASTLE)); setCastleMask(castleMask & ~(1 << Position.H1_CASTLE)); } else { setCastleMask(castleMask & ~(1 << Position.A8_CASTLE)); setCastleMask(castleMask & ~(1 << Position.H8_CASTLE)); } } // Perform move movePieceNotPawn(move.from, move.to); } if (wtm) { // Update castling rights when rook moves if ((BitBoard.maskCorners & fromMask) != 0) { if (p == Piece.WROOK) removeCastleRights(move.from); } if ((BitBoard.maskCorners & (1L << move.to)) != 0) { if (capP == Piece.BROOK) removeCastleRights(move.to); } } else { fullMoveCounter++; // Update castling rights when rook moves if ((BitBoard.maskCorners & fromMask) != 0) { if (p == Piece.BROOK) removeCastleRights(move.from); } if ((BitBoard.maskCorners & (1L << move.to)) != 0) { if (capP == Piece.WROOK) removeCastleRights(move.to); } } hashKey ^= whiteHashKey; whiteMove = !wtm; } public final void unMakeMove(Move move, UndoInfo ui) { hashKey ^= whiteHashKey; whiteMove = !whiteMove; int p = squares[move.to]; setPiece(move.from, p); setPiece(move.to, ui.capturedPiece); setCastleMask(ui.castleMask); setEpSquare(ui.epSquare); halfMoveClock = ui.halfMoveClock; boolean wtm = whiteMove; if (move.promoteTo != Piece.EMPTY) { p = wtm ? Piece.WPAWN : Piece.BPAWN; setPiece(move.from, p); } if (!wtm) { fullMoveCounter--; } // Handle castling int king = wtm ? Piece.WKING : Piece.BKING; if (p == king) { int k0 = move.from; if (move.to == k0 + 2) { // O-O movePieceNotPawn(k0 + 1, k0 + 3); } else if (move.to == k0 - 2) { // O-O-O movePieceNotPawn(k0 - 1, k0 - 4); } } // Handle en passant if (move.to == epSquare) { if (p == Piece.WPAWN) { setPiece(move.to - 8, Piece.BPAWN); } else if (p == Piece.BPAWN) { setPiece(move.to + 8, Piece.WPAWN); } } } /** * Apply a move to the current position. * Special version that only updates enough of the state for the SEE function to be happy. */ public final void makeSEEMove(Move move, UndoInfo ui) { ui.capturedPiece = squares[move.to]; int p = squares[move.from]; // Handle en passant if (move.to == epSquare) { if (p == Piece.WPAWN) { setSEEPiece(move.to - 8, Piece.EMPTY); } else if (p == Piece.BPAWN) { setSEEPiece(move.to + 8, Piece.EMPTY); } } // Perform move setSEEPiece(move.from, Piece.EMPTY); setSEEPiece(move.to, p); whiteMove = !whiteMove; } public final void unMakeSEEMove(Move move, UndoInfo ui) { whiteMove = !whiteMove; int p = squares[move.to]; setSEEPiece(move.from, p); setSEEPiece(move.to, ui.capturedPiece); // Handle en passant if (move.to == epSquare) { if (p == Piece.WPAWN) { setSEEPiece(move.to - 8, Piece.BPAWN); } else if (p == Piece.BPAWN) { setSEEPiece(move.to + 8, Piece.WPAWN); } } } private final void removeCastleRights(int square) { if (square == Position.getSquare(0, 0)) { setCastleMask(castleMask & ~(1 << Position.A1_CASTLE)); } else if (square == Position.getSquare(7, 0)) { setCastleMask(castleMask & ~(1 << Position.H1_CASTLE)); } else if (square == Position.getSquare(0, 7)) { setCastleMask(castleMask & ~(1 << Position.A8_CASTLE)); } else if (square == Position.getSquare(7, 7)) { setCastleMask(castleMask & ~(1 << Position.H8_CASTLE)); } } /* ------------- Hashing code ------------------ */ static final long[][] psHashKeys; // [piece][square] private static final long whiteHashKey; private static final long[] castleHashKeys; // [castleMask] private static final long[] epHashKeys; // [epFile + 1] (epFile==-1 for no ep) private static final long[] moveCntKeys; // [min(halfMoveClock, 100)] static { psHashKeys = new long[Piece.nPieceTypes][64]; castleHashKeys = new long[16]; epHashKeys = new long[9]; moveCntKeys = new long[101]; int rndNo = 0; for (int p = 0; p < Piece.nPieceTypes; p++) { for (int sq = 0; sq < 64; sq++) { psHashKeys[p][sq] = getRandomHashVal(rndNo++); } } whiteHashKey = getRandomHashVal(rndNo++); for (int cm = 0; cm < castleHashKeys.length; cm++) castleHashKeys[cm] = getRandomHashVal(rndNo++); for (int f = 0; f < epHashKeys.length; f++) epHashKeys[f] = getRandomHashVal(rndNo++); for (int mc = 0; mc < moveCntKeys.length; mc++) moveCntKeys[mc] = getRandomHashVal(rndNo++); } /** * Compute the Zobrist hash value non-incrementally. Only useful for test programs. */ final long computeZobristHash() { long hash = 0; for (int sq = 0; sq < 64; sq++) { int p = squares[sq]; hash ^= psHashKeys[p][sq]; if ((p == Piece.WPAWN) || (p == Piece.BPAWN)) pHashKey ^= psHashKeys[p][sq]; } if (whiteMove) hash ^= whiteHashKey; hash ^= castleHashKeys[castleMask]; hash ^= epHashKeys[(epSquare >= 0) ? getX(epSquare) + 1 : 0]; return hash; } private final static long getRandomHashVal(int rndNo) { try { MessageDigest md = MessageDigest.getInstance("SHA-1"); byte[] input = new byte[4]; for (int i = 0; i < 4; i++) input[i] = (byte)((rndNo >> (i * 8)) & 0xff); byte[] digest = md.digest(input); long ret = 0; for (int i = 0; i < 8; i++) { ret ^= ((long)digest[i]) << (i * 8); } return ret; } catch (NoSuchAlgorithmException ex) { throw new UnsupportedOperationException("SHA-1 not available"); } } /** Useful for debugging. */ public final String toString() { return TextIO.asciiBoard(this) + (whiteMove ? "white\n" : "black\n") + Long.toHexString(zobristHash()) + "\n"; } }