package com.christophdietze.jack.shared.board;
import java.util.List;
public class MoveChecker {
/**
* A bitboard with an additional 2 square sized border.<br>
* Contains the index of the corresponding normal chess board square or -1 if out of bounds.<br>
*/
private static final int boundaryBoardIndices[] = new int[12 * 12];
/**
* The offsets and directions are defined in the boundary board dimensions. Thus on a 12x12 board.
*/
private static final int kingMoveOffsets[] = { -13, -12, -11, -1, 1, 11, 12, 13 };
private static final int knightMoveOffsets[] = { -25, -23, -14, -10, 10, 14, 23, 25 };
private static final int bishopDirections[] = { -13, -11, 11, 13 };
private static final int rookDirections[] = { -12, -1, 1, 12 };
private static final int queenDirections[] = { -13, -12, -11, -1, 1, 11, 12, 13 };
private static final int whitePawnOffsets[] = { 11, 12, 13, 24 };
private static final int blackPawnOffsets[] = { -11, -12, -13, -24 };
static {
init();
}
private static void init() {
initBoundaryBoard();
}
private static void initBoundaryBoard() {
for (int x = 0; x < 12; ++x) {
for (int y = 0; y < 12; ++y) {
int file = x - 2;
int rank = y - 2;
if (file >= 0 && file <= 7 && rank >= 0 && rank <= 7) {
boundaryBoardIndices[x + y * 12] = file + rank * 8;
} else {
boundaryBoardIndices[x + y * 12] = -1;
}
}
}
}
public static MoveLegality isPseudoLegalMove(Position position, Move move) {
Piece toSquare = position.getPiece(move.getTo());
if (toSquare.isPiece() && toSquare.isWhite() == position.isWhiteToMove()) {
// cannot capture or move over our own pieces
return MoveLegality.CANNOT_CAPTURE_OWN_PIECES;
}
Piece fromSquare = position.getPiece(move.getFrom());
PieceType piece = fromSquare.getPieceType();
if (piece == null) {
return MoveLegality.BASIC_ILLEGAL_MOVE;
}
if (position.isWhiteToMove() && !fromSquare.isWhite()) {
return MoveLegality.IT_IS_WHITES_TURN;
}
if (!position.isWhiteToMove() && fromSquare.isWhite()) {
return MoveLegality.IT_IS_BLACKS_TURN;
}
switch (piece) {
case PAWN:
return MoveLegality.valueOf(isPseudoLegalPawnMove(position, move));
case KING:
if (isPseudoLegalCastleMove(position, move)) {
return MoveLegality.LEGAL_MOVE;
} else {
return MoveLegality.valueOf(isPseudoLegalMoveByOffset(position, kingMoveOffsets, move));
}
case KNIGHT:
return MoveLegality.valueOf(isPseudoLegalMoveByOffset(position, knightMoveOffsets, move));
case BISHOP:
return MoveLegality.valueOf(isPseudoLegalMoveByDirection(position, bishopDirections, move));
case ROOK:
return MoveLegality.valueOf(isPseudoLegalMoveByDirection(position, rookDirections, move));
case QUEEN:
return MoveLegality.valueOf(isPseudoLegalMoveByDirection(position, queenDirections, move));
default:
throw new AssertionError();
}
}
private static boolean isPseudoLegalMoveByOffset(Position position, int[] offsets, Move move) {
int boundaryIndexFrom = toBoundaryIndex(move.getFrom());
int boundaryIndexTo = toBoundaryIndex(move.getTo());
int desiredOffset = boundaryIndexTo - boundaryIndexFrom;
for (int possibleOffset : offsets) {
if (desiredOffset == possibleOffset) {
return true;
}
}
return false;
}
private static boolean isPseudoLegalMoveByDirection(Position position, int[] directions, Move move) {
int boundaryIndexFrom = toBoundaryIndex(move.getFrom());
for (int dir : directions) {
for (int curBoundaryIndex = boundaryIndexFrom + dir; boundaryBoardIndices[curBoundaryIndex] >= 0; curBoundaryIndex += dir) {
int realIndex = toRealIndex(curBoundaryIndex);
Piece toSquare = position.getPiece(realIndex);
if (toSquare.isPiece() && toSquare.isWhite() == position.isWhiteToMove()) {
// cannot capture or move over our own pieces
break;
}
if (realIndex == move.getTo()) {
return true;
}
if (toSquare.isPiece() && toSquare.isWhite() != position.isWhiteToMove()) {
// we can capture the opponent's pieces, but not move over
// them
break;
}
}
}
return false;
}
private static boolean isPseudoLegalPawnMove(Position position, Move move) {
final int moveOffsetsWhite[] = { 8, 16 };
final int moveOffsetsBlack[] = { -8, -16 };
final int captureOffsetsWhite[] = { 7, 9 };
final int captureOffsetsBlack[] = { -9, -7 };
int moveOffsets[] = position.isWhiteToMove() ? moveOffsetsWhite : moveOffsetsBlack;
int captureOffsets[] = position.isWhiteToMove() ? captureOffsetsWhite : captureOffsetsBlack;
int offset = move.getTo() - move.getFrom();
Piece toSquare = position.getPiece(move.getTo());
// cheap prefilter
if (offset != moveOffsets[0] && offset != moveOffsets[1] && offset != captureOffsets[0]
&& offset != captureOffsets[1]) {
return false;
}
int file = move.getFrom() % 8;
int rank = move.getFrom() / 8;
if (offset == moveOffsets[0]) {
return toSquare.isEmpty();
}
if (offset == moveOffsets[1]) {
if ((position.isWhiteToMove() && rank != 1) || (!position.isWhiteToMove() && rank != 6)) {
return false;
}
return toSquare.isEmpty() && position.getPiece(move.getFrom() + moveOffsets[0]).isEmpty();
}
if (offset == captureOffsets[0]) {
return file != 0
&& (toSquare.isPiece() || toSquare == Piece.WHITE_EN_PASSANT_PAWN || toSquare == Piece.BLACK_EN_PASSANT_PAWN)
&& (toSquare.isWhite() != position.isWhiteToMove());
}
if (offset == captureOffsets[1]) {
return file != 7
&& (toSquare.isPiece() || toSquare == Piece.WHITE_EN_PASSANT_PAWN || toSquare == Piece.BLACK_EN_PASSANT_PAWN)
&& (toSquare.isWhite() != position.isWhiteToMove());
}
// all other cases must have been prefiltered
assert false;
return false;
}
public static boolean isPseudoLegalCastleMove(Position position, Move move) {
if (position.isWhiteToMove() && move.getFrom() == 4 && move.getTo() == 6) {
return (position.canWhiteCastleKingside() && position.getPiece(4) == Piece.WHITE_KING
&& position.getPiece(5) == Piece.EMPTY && position.getPiece(6) == Piece.EMPTY && position.getPiece(7) == Piece.WHITE_ROOK);
}
if (position.isWhiteToMove() && move.getFrom() == 4 && move.getTo() == 2) {
return (position.canWhiteCastleQueenside() && position.getPiece(0) == Piece.WHITE_ROOK
&& position.getPiece(1) == Piece.EMPTY && position.getPiece(2) == Piece.EMPTY
&& position.getPiece(3) == Piece.EMPTY && position.getPiece(4) == Piece.WHITE_KING);
}
if (!position.isWhiteToMove() && move.getFrom() == 60 && move.getTo() == 62) {
return (position.canBlackCastleKingside() && position.getPiece(60) == Piece.BLACK_KING
&& position.getPiece(61) == Piece.EMPTY && position.getPiece(62) == Piece.EMPTY && position.getPiece(63) == Piece.BLACK_ROOK);
}
if (!position.isWhiteToMove() && move.getFrom() == 60 && move.getTo() == 58) {
return (position.canBlackCastleQueenside() && position.getPiece(56) == Piece.BLACK_ROOK
&& position.getPiece(57) == Piece.EMPTY && position.getPiece(58) == Piece.EMPTY
&& position.getPiece(59) == Piece.EMPTY && position.getPiece(60) == Piece.BLACK_KING);
}
return false;
}
private static int toBoundaryIndex(int realIndex) {
return realIndex % 8 + 2 + (realIndex / 8 + 2) * 12;
}
private static int toRealIndex(int boundaryIndex) {
return boundaryIndex % 12 - 2 + (boundaryIndex / 12 - 2) * 8;
}
public static boolean canCaptureKing(Position position) {
boolean isWhiteKing = !position.isWhiteToMove();
Piece kingSquare = isWhiteKing ? Piece.WHITE_KING : Piece.BLACK_KING;
List<Integer> kingIndices = position.findPieces(kingSquare);
assert kingIndices.size() == 1;
kingIndices.addAll(position.getPhantomKingIndices());
for (int index = 0; index < 64; ++index) {
Piece srcSquare = position.getPiece(index);
if (srcSquare.isPiece() && srcSquare.isWhite() != isWhiteKing) {
for (int kingIndex : kingIndices) {
Move move = new Move(index, kingIndex);
if (isPseudoLegalMove(position, move).isLegal()) {
return true;
}
}
}
}
return false;
}
public static boolean isLegalMove(Position position, Move move) {
if (!isPseudoLegalMove(position, move).isLegal()) {
return false;
}
Position trialPosition = PositionUtils.makeMove(position, move);
if (canCaptureKing(trialPosition)) {
return false;
}
return true;
}
/**
* @return true exactly when the current player has at least one legal move.
*/
public static boolean hasLegalMove(Position position) {
for (int index = 0; index < 64; ++index) {
Piece piece = position.getPiece(index);
if (piece.isEmpty() || piece.isWhite() != position.isWhiteToMove()) {
continue;
}
switch (piece.getPieceType()) {
case PAWN:
if (hasLegalPawnMoves(position, index)) {
return true;
}
break;
case KING:
// A king always has a normal legal move when he has a legal castle move, thus we can skip the castle moves
if (hasLegalMoveByOffset(position, index, kingMoveOffsets)) {
return true;
}
break;
case KNIGHT:
if (hasLegalMoveByOffset(position, index, knightMoveOffsets)) {
return true;
}
break;
case BISHOP:
if (hasLegalMoveByDirection(position, index, bishopDirections)) {
return true;
}
break;
case ROOK:
if (hasLegalMoveByDirection(position, index, rookDirections)) {
return true;
}
break;
case QUEEN:
if (hasLegalMoveByDirection(position, index, queenDirections)) {
return true;
}
break;
default:
throw new AssertionError();
}
}
return false;
}
private static boolean hasLegalMoveByOffset(Position position, int index, int[] offsets) {
int boundaryIndex = toBoundaryIndex(index);
for (int offset : offsets) {
int toBoundaryIndex = boundaryIndex + offset;
if (boundaryBoardIndices[toBoundaryIndex] < 0) {
continue;
}
if (isLegalMove(position, new Move(index, toRealIndex(toBoundaryIndex)))) {
return true;
}
}
return false;
}
private static boolean hasLegalMoveByDirection(Position position, int index, int[] directions) {
int boundaryIndex = toBoundaryIndex(index);
for (int direction : directions) {
for (int curBoundaryIndex = boundaryIndex + direction; boundaryBoardIndices[curBoundaryIndex] >= 0; curBoundaryIndex += direction) {
Move move = new Move(index, toRealIndex(curBoundaryIndex));
if (!isPseudoLegalMove(position, move).isLegal()) {
break;
}
if (isLegalMove(position, move)) {
return true;
}
}
}
return false;
}
private static boolean hasLegalPawnMoves(Position position, int index) {
int boundaryIndex = toBoundaryIndex(index);
int[] offsets = position.isWhiteToMove() ? whitePawnOffsets : blackPawnOffsets;
for (int offset : offsets) {
int toIndex = boundaryBoardIndices[boundaryIndex + offset];
if (toIndex < 0) {
continue;
}
int toRank = ChessUtils.toRank(toIndex);
PieceType promotionPiece = position.isWhiteToMove() ? (toRank != 7 ? null : PieceType.QUEEN)
: (toRank != 0 ? null : PieceType.QUEEN);
Move move = new Move(index, toIndex, promotionPiece);
if (isLegalMove(position, move)) {
return true;
}
}
return false;
}
}