package com.christophdietze.jack.shared.board; public class PositionUtils { public static Position makeMoveVerified(Position position, Move move) throws IllegalMoveException { MoveLegality legality = MoveChecker.isPseudoLegalMove(position, move); if (!legality.isLegal()) { throw new IllegalMoveException(legality.getMessage()); } Position trialPosition = makeMove(position, move); if (MoveChecker.canCaptureKing(trialPosition)) { throw new IllegalMoveException(); } return trialPosition; } public static Position makeMoveVerified(Position position, String algebraicMove) throws IllegalMoveException { return makeMoveVerified(position, ChessUtils.toMoveFromAlgebraic(algebraicMove)); } public static Position makeMove(Position position, Move move) { if (isPseudoLegalCastleMove(position, move)) { // castle move Position.Builder builder = new Position.Builder(position); builder.clearEnPassantPawn().clearPhantomKings().switchPlayerToMove(); if (builder.isWhiteToMove()) { builder.fullmoveNumber(builder.getFullmoveNumber() + 1); } makeCastleMove(builder, move); return builder.build(); } else { // normal move Position.Builder builder = new Position.Builder(position); builder.clearEnPassantPawn().clearPhantomKings(); Piece fromPiece = position.getPiece(move.getFrom()); Piece toPiece = position.getPiece(move.getTo()); builder.clearPhantomKings(); // adjust castling availability if (fromPiece == Piece.WHITE_KING) { builder.canWhiteCastleKingside(false).canWhiteCastleQueenside(false); } else if (fromPiece == Piece.BLACK_KING) { builder.canBlackCastleKingside(false).canBlackCastleQueenside(false); } if (move.getFrom() == 0) { builder.canWhiteCastleQueenside(false); } else if (move.getFrom() == 7) { builder.canWhiteCastleKingside(false); } else if (move.getFrom() == 56) { builder.canBlackCastleQueenside(false); } else if (move.getFrom() == 63) { builder.canBlackCastleKingside(false); } // if we're capturing an en passant pawn, remove the real pawn, too if (toPiece == Piece.WHITE_EN_PASSANT_PAWN) { builder.piece(move.getTo() + 8, Piece.EMPTY); } else if (toPiece == Piece.BLACK_EN_PASSANT_PAWN) { builder.piece(move.getTo() - 8, Piece.EMPTY); } builder.clearEnPassantPawn(); // create en passant pawns if necessary if (fromPiece == Piece.WHITE_PAWN && move.getFrom() / 8 == 1 && move.getTo() / 8 == 3) { int enPassantPawnIndex = move.getFrom() + 8; builder.enPassantPawnIndex(enPassantPawnIndex, true); } else if (fromPiece == Piece.BLACK_PAWN && move.getFrom() / 8 == 6 && move.getTo() / 8 == 4) { int enPassantPawnIndex = move.getFrom() - 8; builder.enPassantPawnIndex(enPassantPawnIndex, false); } boolean isPromotionMove = isPseudoPromotionMove(position, move); // actually move the selected piece builder.piece(move.getTo(), fromPiece); builder.piece(move.getFrom(), Piece.EMPTY); // if necessary, perform a promotion if (isPromotionMove) { if (move.getPromotionPiece() == null) { throw new RuntimeException("Made a promotion move, but no promotion piece selected"); } Piece promoPiece = Piece.getFromColorAndPieceType(builder.isWhiteToMove(), move.getPromotionPiece()); builder.piece(move.getTo(), promoPiece); } builder.switchPlayerToMove(); if (builder.isWhiteToMove()) { builder.fullmoveNumber(builder.getFullmoveNumber() + 1); } return builder.build(); } } public static Position makeMove(Position position, String algebraicMove) { return makeMove(position, ChessUtils.toMoveFromAlgebraic(algebraicMove)); } /** * TODO fix redundancy to MoveChecker.isPseudoLegalCastleMove */ private 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; } public static boolean isPseudoPromotionMove(Position position, Move move) { if (position.getPiece(move.getFrom()) == Piece.WHITE_PAWN && move.getFrom() / 8 == 6) { return true; } if (position.getPiece(move.getFrom()) == Piece.BLACK_PAWN && move.getFrom() / 8 == 1) { return true; } return false; } public static enum GameState { // TODO: detect draw by 50 move rule // TODO: detect draw by threefold repetition ACTIVE, MATE, STALEMATE; } public static GameState getGameState(Position position) { if (MoveChecker.hasLegalMove(position)) { return GameState.ACTIVE; } Position trialPosition = new Position.Builder(position).whiteToMove(!position.isWhiteToMove()).build(); if (MoveChecker.canCaptureKing(trialPosition)) { return GameState.MATE; } else { return GameState.STALEMATE; } } /** * The specified move must already be checked for pseudo legality, this function does not check. */ private static Position.Builder makeCastleMove(Position.Builder builder, Move move) { assert (builder.getPiece(move.getFrom()) == Piece.WHITE_KING) || (builder.getPiece(move.getFrom()) == Piece.BLACK_KING); if (move.getFrom() == 4 && move.getTo() == 6) { // white O-O builder.piece(4, Piece.EMPTY); builder.piece(5, Piece.WHITE_ROOK); builder.piece(6, Piece.WHITE_KING); builder.piece(7, Piece.EMPTY); builder.setPhantomKings(4, 5); } else if (move.getFrom() == 4 && move.getTo() == 2) { // white O-O-O builder.piece(0, Piece.EMPTY); builder.piece(1, Piece.EMPTY); builder.piece(2, Piece.WHITE_KING); builder.piece(3, Piece.WHITE_ROOK); builder.piece(4, Piece.EMPTY); builder.setPhantomKings(3, 4); } else if (move.getFrom() == 60 && move.getTo() == 62) { // black O-O builder.piece(60, Piece.EMPTY); builder.piece(61, Piece.BLACK_ROOK); builder.piece(62, Piece.BLACK_KING); builder.piece(63, Piece.EMPTY); builder.setPhantomKings(60, 61); } else if (move.getFrom() == 60 && move.getTo() == 58) { // black O-O-O builder.piece(56, Piece.EMPTY); builder.piece(57, Piece.EMPTY); builder.piece(58, Piece.BLACK_KING); builder.piece(59, Piece.BLACK_ROOK); builder.piece(60, Piece.EMPTY); builder.setPhantomKings(59, 60); } else { throw new AssertionError(); } return builder; } public static String toDiagramString(Position pos) { StringBuilder sb = new StringBuilder(); for (int y = 7; y >= 0; --y) { if (y == 7) { sb.append("\n"); } else { sb.append("|\n"); } sb.append("--------------------------------\n"); for (int x = 0; x < 8; ++x) { int index = y * 8 + x; sb.append("| "); sb.append(pos.getPiece(index).getSymbol()); sb.append(" "); } } sb.append("|\n--------------------------------\n"); return sb.toString(); } }