de.schildbach.game.chess.ChessRules.java Source code

Java tutorial

Introduction

Here is the source code for de.schildbach.game.chess.ChessRules.java

Source

/*
 * Copyright 2001-2011 the original author or authors.
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package de.schildbach.game.chess;

import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang.ObjectUtils;

import de.schildbach.game.Board;
import de.schildbach.game.BoardGeometry;
import de.schildbach.game.Coordinate;
import de.schildbach.game.Game;
import de.schildbach.game.GameMove;
import de.schildbach.game.GamePosition;
import de.schildbach.game.MicroOperation;
import de.schildbach.game.Piece;
import de.schildbach.game.PieceSet;
import de.schildbach.game.PieceSet.ColorEntry;
import de.schildbach.game.chess.piece.AntiKing;
import de.schildbach.game.chess.piece.BerolinaPawn;
import de.schildbach.game.chess.piece.Bishop;
import de.schildbach.game.chess.piece.King;
import de.schildbach.game.chess.piece.Knight;
import de.schildbach.game.chess.piece.Pawn;
import de.schildbach.game.chess.piece.Queen;
import de.schildbach.game.chess.piece.Rook;
import de.schildbach.game.common.CapturePieceOperation;
import de.schildbach.game.common.CapturingGamePosition;
import de.schildbach.game.common.ChessLikeMove;
import de.schildbach.game.common.ChessLikeRules;
import de.schildbach.game.common.ExchangePieceOperation;
import de.schildbach.game.common.GenericPieceSet;
import de.schildbach.game.common.MovePieceOperation;
import de.schildbach.game.common.NextPlayerOperation;
import de.schildbach.game.common.OrthogonalBoardGeometry;
import de.schildbach.game.common.SwapOutPieceOperation;
import de.schildbach.game.common.GenericPieceSet.PieceEntry;
import de.schildbach.game.common.piece.ChessLikePiece;
import de.schildbach.game.exception.IllegalMoveException;
import de.schildbach.game.exception.ParseException;

/**
 * @author Andreas Schildbach
 */
public class ChessRules extends ChessLikeRules {
    public enum Variant {
        SUICIDE, ANTIKING
    }

    private static final String DEFAULT_INITIAL_BOARD = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR";

    private static final String ANTIKING_INITIAL_BOARD = "2prbkqA/2p1nnbr/2pppppp/8/8/PPPPPP2/RBNN1P2/aQKBRP2";

    private static final PieceEntry[] DEFAULT_PIECES = { new PieceEntry('P', 'B', Pawn.class), //
            new PieceEntry('R', 'T', Rook.class), //
            new PieceEntry('N', 'S', Knight.class), // 
            new PieceEntry('B', 'L', Bishop.class), //
            new PieceEntry('Q', 'D', Queen.class), //
            new PieceEntry('K', 'K', King.class) //
    };

    private static final PieceEntry[] ANTIKING_PIECES = { new PieceEntry('P', 'B', BerolinaPawn.class), //
            new PieceEntry('R', 'T', Rook.class), //
            new PieceEntry('N', 'S', Knight.class), // 
            new PieceEntry('B', 'L', Bishop.class), //
            new PieceEntry('Q', 'D', Queen.class), //
            new PieceEntry('K', 'K', King.class), //
            new PieceEntry('A', 'A', AntiKing.class) //
    };

    private static final ColorEntry[] COLORS = { new ColorEntry(0, "white", 'w'), //
            new ColorEntry(1, "black", 'b') //
    };

    @SuppressWarnings("unchecked")
    private static final Class<Piece>[] DEFAULT_PROMOTION_OPTIONS = new Class[] { Queen.class, Rook.class,
            Bishop.class, Knight.class };

    @SuppressWarnings("unchecked")
    private static final Class<Piece>[] SUICIDE_PROMOTION_OPTIONS = new Class[] { Queen.class, King.class,
            Rook.class, Bishop.class, Knight.class };

    private boolean mustCaptureRule = false;
    private boolean promotionRule = true;
    private Class<Piece>[] promotionOptions = DEFAULT_PROMOTION_OPTIONS;
    private boolean castlingRule = true;
    private boolean antiKingCastlingRule = false;
    private boolean advanceTwoSquaresRule = true;
    private boolean enPassantRule = true;

    public ChessRules(Variant variant) {
        super(ChessBoardGeometry.instance(),
                new GenericPieceSet(variant == Variant.ANTIKING ? ANTIKING_PIECES : DEFAULT_PIECES, COLORS));

        if (variant == null) {
            setCheckVulnerablePiece(King.class);
        } else if (variant == Variant.SUICIDE) {
            setCheckVulnerablePiece(null);
            mustCaptureRule = true;
            promotionOptions = SUICIDE_PROMOTION_OPTIONS;
            castlingRule = false;
            enPassantRule = false;
        } else if (variant == Variant.ANTIKING) {
            setCheckVulnerablePiece(King.class);
            setAntiCheckVulnerablePiece(AntiKing.class);
            castlingRule = false;
            antiKingCastlingRule = true;
            advanceTwoSquaresRule = false;
            enPassantRule = false;
        } else {
            throw new IllegalArgumentException("unknown variant: " + variant);
        }
    }

    @Override
    public GamePosition initialPositionFromBoard(Board initialBoard) {
        if (initialBoard == null) {
            initialBoard = getBoardGeometry().newBoard();
            if (antiKingCastlingRule)
                parseBoard(initialBoard, ANTIKING_INITIAL_BOARD);
            else
                parseBoard(initialBoard, DEFAULT_INITIAL_BOARD);
        }

        if (castlingRule) {
            ChessPosition position = new ChessPosition(initialBoard);
            position.setActivePlayerIndex(0);
            position.setFullmoveNumber(1);
            position.setHalfmoveClock(0);
            position.setCastlingAvailable(0, CastlingSide.QUEENSIDE, castlingRule);
            position.setCastlingAvailable(0, CastlingSide.KINGSIDE, castlingRule);
            position.setCastlingAvailable(1, CastlingSide.QUEENSIDE, castlingRule);
            position.setCastlingAvailable(1, CastlingSide.KINGSIDE, castlingRule);
            return position;
        } else if (antiKingCastlingRule) {
            AntiKingChessPosition position = new AntiKingChessPosition(initialBoard);
            position.setActivePlayerIndex(0);
            position.setFullmoveNumber(1);
            position.setHalfmoveClock(0);
            position.setCastlingAvailable(0, King.class, antiKingCastlingRule);
            position.setCastlingAvailable(0, AntiKing.class, antiKingCastlingRule);
            position.setCastlingAvailable(1, King.class, antiKingCastlingRule);
            position.setCastlingAvailable(1, AntiKing.class, antiKingCastlingRule);
            return position;
        } else {
            ChessLikePosition position = new ChessLikePosition(initialBoard);
            position.setActivePlayerIndex(0);
            position.setFullmoveNumber(1);
            return position;
        }
    }

    private static final Pattern SIMPLE_MOVE_PATTERN = Pattern.compile("^([a-z])(\\d)-([a-z]\\d)(?:=([A-Z]))?$");

    private static final Pattern ACN_MOVE_PATTERN = Pattern.compile(
            "^(?:([A-Z]?)([a-z])??(\\d)??(x?)([a-z]\\d)(?:=([A-Z]))?|(O-O(?:-O)?|0-0(?:-0)?))(\\+\\+?|#)?$");

    @Override
    public GameMove parseMove(String moveString, Locale locale, GamePosition position, Board initialBoard)
            throws ParseException {
        Collection<? extends ChessLikeMove> allowedMoves = allowedMoves(position, initialBoard);
        Matcher m = ACN_MOVE_PATTERN.matcher(moveString);
        if (m.matches()) {
            return parseMove(moveString, locale, position, m.group(1), m.group(2), m.group(3), m.group(4),
                    m.group(5), m.group(6), m.group(8), m.group(7), allowedMoves);
        }
        m = SIMPLE_MOVE_PATTERN.matcher(moveString);
        if (m.matches()) {
            return parseMove(moveString, locale, position, null, m.group(1), m.group(2), null, m.group(3),
                    m.group(4), null, null, allowedMoves);
        }
        throw new ParseException(moveString, "unknown format");
    }

    private ChessLikeMove parseMove(String moveString, Locale locale, GamePosition position, String pretendedPiece,
            String pretendedSourceFile, String pretendedSourceRank, String pretendedCapture, String pretendedTarget,
            String pretendedPromotionPiece, String flag, String castling,
            Collection<? extends ChessLikeMove> potentialMoves) throws ParseException {
        ChessBoardGeometry geometry = (ChessBoardGeometry) getBoardGeometry();
        PieceSet pieceSet = getPieceSet();
        Board board = position.getBoard();

        for (Iterator<? extends ChessLikeMove> i = potentialMoves.iterator(); i.hasNext();) {
            ChessMove move = (ChessMove) i.next();
            Coordinate source = move.getSource();
            if (castling != null) {
                if (!looksLikeCastlingMove(move, board))
                    i.remove();
                else if ((castling.equals("O-O") || castling.equals("0-0")) != geometry.isKingSide(source,
                        move.getTarget()))
                    i.remove();
            } else {
                Piece piece = board.getPiece(source);
                String sourceNotation = source.getNotation();
                char sourceFile = sourceNotation.charAt(0);
                char sourceRank = sourceNotation.charAt(1);
                boolean pawn = piece instanceof Pawn || piece instanceof BerolinaPawn;
                if (pretendedPiece != null && pretendedPiece.length() > 0
                        && pieceSet.getCharRepresentation(piece.getClass(), locale) != pretendedPiece.charAt(0))
                    i.remove();
                else if (pretendedPiece != null && pretendedPiece.length() == 0 && !pawn)
                    i.remove();
                else if (pretendedSourceFile != null && sourceFile != pretendedSourceFile.charAt(0))
                    i.remove();
                else if (pretendedSourceRank != null && sourceRank != pretendedSourceRank.charAt(0))
                    i.remove();
                else if ((pretendedCapture != null)
                        && (pretendedCapture.equals("x") != looksLikeCapture(move, board)))
                    i.remove();
                else if (!pretendedTarget.equals(move.getTarget().getNotation()))
                    i.remove();
                else if ((pretendedPromotionPiece != null)
                        && pieceSet.getCharRepresentation(move.getPromotionPiece(),
                                locale) != pretendedPromotionPiece.charAt(0))
                    i.remove();
            }
        }

        if (potentialMoves.size() == 1)
            return potentialMoves.iterator().next();
        else if (potentialMoves.size() > 1)
            throw new ParseException(moveString, "ambiguous move");
        else
            throw new ParseException(moveString, "no move");
    }

    private static final Pattern UNMARSHAL_MOVE_PATTERN = Pattern.compile("^([a-z]\\d)-([a-z]\\d)(?:=([A-Z]))?$");

    @Override
    protected GameMove unmarshalMove(String notation) throws ParseException {
        Matcher m = UNMARSHAL_MOVE_PATTERN.matcher(notation);
        if (m.matches()) {
            BoardGeometry geometry = getBoardGeometry();
            Coordinate source = geometry.locateCoordinate(m.group(1));
            Coordinate target = geometry.locateCoordinate(m.group(2));
            if (m.group(3) == null) {
                return new ChessMove(source, target);
            } else {
                Class<? extends Piece> promotionPiece = getPieceSet().getPiece(m.group(3).charAt(0), 0).getClass();
                return new ChessMove(source, target, promotionPiece);
            }
        } else {
            throw new ParseException(notation, "unknown format");
        }
    }

    @Override
    public String formatMove(GameMove move) {
        ChessMove chessMove = (ChessMove) move;
        Class<? extends Piece> promotionPiece = chessMove.getPromotionPiece();
        return chessMove.getSource().getNotation() + "-" + chessMove.getTarget().getNotation()
                + (promotionPiece != null ? "=" + getPieceSet().getCharRepresentation(promotionPiece) : "");
    }

    @Override
    public Set<String> clickables(GameMove move) {
        ChessMove chessMove = (ChessMove) move;
        Class<? extends Piece> promotionPiece = chessMove.getPromotionPiece();
        Set<String> clickables = new LinkedHashSet<String>();
        clickables.add(chessMove.getSource().getNotation());
        clickables.add(chessMove.getTarget().getNotation());
        if (promotionPiece != null)
            clickables.add("=" + getPieceSet().getCharRepresentation(promotionPiece));
        return clickables;
    }

    @Override
    protected String[] basicFormatGameArray(Game game, Locale locale) {
        int size = game.getSize();
        String[] array = new String[size];

        ChessBoardGeometry geometry = (ChessBoardGeometry) getBoardGeometry();
        ChessLikePosition position = (ChessLikePosition) game.getInitialPosition().clone();
        Board board = position.getBoard();
        Board initialBoard = game.getInitialPosition().getBoard();
        PieceSet pieceSet = getPieceSet();

        StringBuilder notation = new StringBuilder();
        for (int index = 0; index < size; index++) {
            ChessMove move = (ChessMove) game.getMove(index);
            Coordinate source = move.getSource();
            Coordinate target = move.getTarget();
            Piece piece = board.getPiece(source);
            String sourceNotation = source.getNotation();
            char sourceFile = sourceNotation.charAt(0);
            char sourceRank = sourceNotation.charAt(1);

            notation.setLength(0);

            if (looksLikeCastlingMove(move, board)) {
                if (geometry.isKingSide(source, target))
                    notation.append("O-O");
                else
                    notation.append("O-O-O");
            } else {
                Collection<? extends ChessLikeMove> potentialMoves = allowedMoves(position, initialBoard);

                // narrow by target
                for (Iterator<? extends ChessLikeMove> iAllowed = potentialMoves.iterator(); iAllowed.hasNext();) {
                    if (!iAllowed.next().getTarget().equals(target))
                        iAllowed.remove();
                }
                notation.append(target.getNotation());

                boolean pawn = piece instanceof Pawn || piece instanceof BerolinaPawn;

                // narrow by capture
                boolean capture = looksLikePlainCapture(move, board) || looksLikeEnPassantCapture(move, board);
                if (capture) {
                    for (Iterator<? extends ChessLikeMove> iAllowed = potentialMoves.iterator(); iAllowed
                            .hasNext();) {
                        if (!looksLikeCapture(iAllowed.next(), board))
                            iAllowed.remove();
                    }
                    notation.insert(0, 'x');

                    if (pawn) {
                        // narrow to source file
                        for (Iterator<? extends ChessLikeMove> iAllowed = potentialMoves.iterator(); iAllowed
                                .hasNext();) {
                            if (!geometry.sameFile(iAllowed.next().getSource(), source))
                                iAllowed.remove();
                        }
                        notation.insert(0, sourceFile);
                    }
                }

                // narrow by piece
                for (Iterator<? extends ChessLikeMove> iAllowed = potentialMoves.iterator(); iAllowed.hasNext();) {
                    if (!board.getPiece(iAllowed.next().getSource()).equals(piece))
                        iAllowed.remove();
                }

                // narrow by promotion
                for (Iterator<? extends ChessLikeMove> iAllowed = potentialMoves.iterator(); iAllowed.hasNext();) {
                    if (!ObjectUtils.equals(((ChessMove) iAllowed.next()).getPromotionPiece(),
                            move.getPromotionPiece()))
                        iAllowed.remove();
                }
                if (move.getPromotionPiece() != null)
                    notation.append("=" + pieceSet.getCharRepresentation(move.getPromotionPiece(), locale));

                if (potentialMoves.size() > 1) {
                    boolean sameRank = false;
                    boolean sameFile = false;
                    for (ChessLikeMove potentialMove : potentialMoves) {
                        Coordinate potentialSource = potentialMove.getSource();
                        if (!potentialSource.equals(source)) {
                            if (geometry.sameFile(potentialSource, source))
                                sameFile = true;
                            if (geometry.sameRank(potentialSource, source))
                                sameRank = true;
                        }
                    }
                    if (sameRank && sameFile) {
                        // narrow to source
                        for (Iterator<? extends ChessLikeMove> iAllowed = potentialMoves.iterator(); iAllowed
                                .hasNext();) {
                            if (!iAllowed.next().getSource().equals(source))
                                iAllowed.remove();
                        }
                        notation.insert(0, sourceNotation);
                    } else if (!sameFile) {
                        // narrow to source file
                        for (Iterator<? extends ChessLikeMove> iAllowed = potentialMoves.iterator(); iAllowed
                                .hasNext();) {
                            if (!geometry.sameFile(iAllowed.next().getSource(), source))
                                iAllowed.remove();
                        }
                        notation.insert(0, sourceFile);
                    } else {
                        // narrow to source rank
                        for (Iterator<? extends ChessLikeMove> iAllowed = potentialMoves.iterator(); iAllowed
                                .hasNext();) {
                            if (!geometry.sameRank(iAllowed.next().getSource(), source))
                                iAllowed.remove();
                        }
                        notation.insert(0, sourceRank);
                    }
                }

                // piece character
                if (!pawn)
                    notation.insert(0, pieceSet.getCharRepresentation(piece.getClass(), locale));

                // sanity check
                if (potentialMoves.size() != 1)
                    throw new IllegalStateException(potentialMoves.toString());
            }

            // execute move
            List<MicroOperation> ops = disassembleMove(move, position, game.getInitialPosition().getBoard());
            doOperations(ops, position);

            // check
            CheckState checkState = checkState(position, game.getInitialPosition().getBoard());
            if (checkState == CheckState.CHECKMATE) {
                notation.append("#");
            } else if (checkState == CheckState.CHECK) {
                notation.append("+");
            }

            array[index] = notation.toString();
        }

        return array;
    }

    @Override
    protected final Set<ChessLikeMove> potentialMovesForSource(Coordinate source, CapturingGamePosition position,
            final Board initialBoard) {
        Board board = position.getBoard();
        ChessBoardGeometry geometry = (ChessBoardGeometry) getBoardGeometry();
        ChessLikePiece piece = (ChessLikePiece) board.getPiece(source);
        int pieceColor = piece.getColor();

        // calculate potential moves from pieces
        Set<ChessLikeMove> potentialMoves = new HashSet<ChessLikeMove>();
        for (Coordinate target : piece.getPotentialTargets(geometry, board, source))
            potentialMoves.add(new ChessMove(source, target));

        // add castling moves
        if (castlingRule && piece instanceof King) {
            if (((ChessPosition) position).getCastlingAvailable(pieceColor, CastlingSide.QUEENSIDE)) {
                Coordinate rookCoordinate = castle(board, source, CastlingSide.QUEENSIDE, initialBoard);
                if (rookCoordinate != null)
                    potentialMoves.add(new ChessMove(source, rookCoordinate));
            }

            if (((ChessPosition) position).getCastlingAvailable(pieceColor, CastlingSide.KINGSIDE)) {
                Coordinate rookCoordinate = castle(board, source, CastlingSide.KINGSIDE, initialBoard);
                if (rookCoordinate != null)
                    potentialMoves.add(new ChessMove(source, rookCoordinate));
            }
        }

        // add antiking castling moves
        if (antiKingCastlingRule && (piece instanceof King || piece instanceof AntiKing)) {
            if (((AntiKingChessPosition) position).getCastlingAvailable(pieceColor, piece.getClass())) {
                for (int[] vector : geometry.knightVectors2D()) {
                    Coordinate target = geometry.vectorAdd(source, vector, 1);
                    if (target != null && board.getPiece(target) == null) {
                        potentialMoves.add(new ChessMove(source, target));
                    }
                }
            }
        }

        // add en passant capture
        if (enPassantRule) {
            Coordinate enPassantTargetSquare = ((ChessPosition) position).getEnPassantTargetSquare();
            if (enPassantTargetSquare != null && piece instanceof Pawn
                    && ((Pawn) piece).isThreateningSquare(geometry, board, source, enPassantTargetSquare)) {
                potentialMoves.add(new ChessMove(source, enPassantTargetSquare));
            }
        }

        // advance two squares rule
        if (advanceTwoSquaresRule && piece instanceof Pawn && isInHomeArea(initialBoard, source, piece)) {
            int[] forward = geometry.forwardDir(piece.getColor());
            Coordinate target = geometry.vectorAdd(source, forward, 1);
            if (target != null && board.getPiece(target) == null) {
                target = geometry.vectorAdd(source, forward, 2);
                if (target != null && board.getPiece(target) == null)
                    potentialMoves.add(new ChessMove(source, target));
            }
        }

        // replace promotion moves
        if (promotionRule) {
            List<ChessMove> promotionMoves = new LinkedList<ChessMove>();
            for (Iterator<ChessLikeMove> iMoves = potentialMoves.iterator(); iMoves.hasNext();) {
                ChessMove move = (ChessMove) iMoves.next();

                if (looksLikePromotionMove(move, board)) {
                    iMoves.remove();
                    for (Class<Piece> option : promotionOptions)
                        promotionMoves.add(new ChessMove(move.getSource(), move.getTarget(), option));
                }
            }
            potentialMoves.addAll(promotionMoves);
        }

        return potentialMoves;
    }

    private Coordinate castle(final Board board, final Coordinate kingCoordinate, CastlingSide side,
            final Board initialBoard) {
        ChessBoardGeometry geometry = (ChessBoardGeometry) getBoardGeometry();
        final int pieceColor = board.getPiece(kingCoordinate).getColor();
        char rank = kingCoordinate.getNotation().charAt(1);

        // search for castling rook (on starting position for unambiguousness)
        final Coordinate rookCoordinate = lookForCastlingRook(kingCoordinate, side, initialBoard, pieceColor);

        // sanity check
        if (!board.getPiece(rookCoordinate).equals(getPieceSet().getPiece(Rook.class, pieceColor)))
            throw new IllegalStateException();

        // check for empty and unthreatened fields along path of king
        final boolean checkRule = getCheckVulnerablePiece() != null;
        Coordinate kingTargetCoordinate = geometry
                .locateCoordinate((side == CastlingSide.QUEENSIDE ? "c" : "g") + rank);
        if (geometry.walk(kingCoordinate, kingTargetCoordinate,
                geometry.isKingSide(kingCoordinate, kingTargetCoordinate) ? geometry.kingSideDir()
                        : geometry.queenSideDir(),
                new OrthogonalBoardGeometry.CoordinateHook() {
                    public boolean doNode(Coordinate coordinate) {
                        if (!coordinate.equals(rookCoordinate) && !coordinate.equals(kingCoordinate)
                                && board.getPiece(coordinate) != null)
                            return false;
                        if (checkRule && isCoordinateThreatened(board, coordinate, pieceColor))
                            return false;
                        return true;
                    }
                }) != null)
            return null;

        // check for empty fields along path of rook
        Coordinate rookTargetCoordinate = geometry
                .locateCoordinate((side == CastlingSide.QUEENSIDE ? "d" : "f") + rank);
        if (geometry.walk(rookCoordinate, rookTargetCoordinate,
                geometry.isKingSide(rookCoordinate, rookTargetCoordinate) ? geometry.kingSideDir()
                        : geometry.queenSideDir(),
                new OrthogonalBoardGeometry.CoordinateHook() {
                    public boolean doNode(Coordinate coordinate) {
                        if (!coordinate.equals(rookCoordinate) && !coordinate.equals(kingCoordinate)
                                && board.getPiece(coordinate) != null)
                            return false;
                        return true;
                    }
                }) != null)
            return null;

        return rookCoordinate;
    }

    private Coordinate lookForCastlingRook(Coordinate kingCoordinate, CastlingSide side, final Board board,
            int color) {
        ChessBoardGeometry geometry = (ChessBoardGeometry) getBoardGeometry();
        final Piece rook = getPieceSet().getPiece(Rook.class, color);
        char rank = kingCoordinate.getNotation().charAt(1);

        // shortcut for standard chess position
        Coordinate shortcutCoordinate = geometry
                .locateCoordinate((side == CastlingSide.QUEENSIDE ? "a" : "h") + rank);
        if (rook.equals(board.getPiece(shortcutCoordinate)))
            return shortcutCoordinate;

        // look
        Coordinate rookCoordinate = geometry.walk(kingCoordinate,
                side == CastlingSide.QUEENSIDE ? geometry.queenSideDir() : geometry.kingSideDir(),
                new OrthogonalBoardGeometry.CoordinateHook() {
                    public boolean doNode(Coordinate coordinate) {
                        Piece piece = board.getPiece(coordinate);
                        if (rook.equals(piece))
                            return false;
                        return true;
                    }
                });

        // sanity checks
        if (rookCoordinate == null)
            throw new IllegalStateException();

        return rookCoordinate;
    }

    @Override
    protected void vetoPotentialMoves(Set<ChessLikeMove> potentialMoves, CapturingGamePosition position,
            Board initialBoard) {
        Board board = position.getBoard();

        // enforce check rule
        removeKingInCheckPositions(potentialMoves, position, initialBoard);

        // enforce must capture rule
        if (mustCaptureRule) {
            boolean capturing = false;
            for (ChessLikeMove move : potentialMoves) {
                if (looksLikeCapture(move, board)) {
                    capturing = true;
                    break;
                }
            }

            if (capturing) {
                for (Iterator<ChessLikeMove> iMoves = potentialMoves.iterator(); iMoves.hasNext();) {
                    ChessLikeMove move = iMoves.next();
                    if (!looksLikeCapture(move, board))
                        iMoves.remove();
                }
            }
        }
    }

    @Override
    public String formatPosition(GamePosition position) {
        return ChessPositionFormat.format((OrthogonalBoardGeometry) getBoardGeometry(), getPieceSet(),
                (ChessLikePosition) position);
    }

    @Override
    public GamePosition parsePosition(String notation) {
        ChessLikePosition position = (ChessLikePosition) initialPositionFromBoard(null);
        ChessPositionFormat.parse(position, (OrthogonalBoardGeometry) getBoardGeometry(), getPieceSet(), notation);
        return position;
    }

    /**
     * determines if the current active player can claim remis
     */
    @Override
    public boolean canDrawBeClaimed(Game game) {
        return ((ChessLikePosition) game.getActualPosition()).getHalfmoveClock() >= 50
                || super.canDrawBeClaimed(game);
    }

    @Override
    public List<MicroOperation> disassembleMove(GameMove move, GamePosition position, Board initialBoard)
            throws IllegalMoveException {
        ChessMove chessMove = (ChessMove) move;
        Coordinate source = chessMove.getSource();
        Coordinate target = chessMove.getTarget();
        Piece piece = position.getBoard().getPiece(source);
        PieceSet pieceSet = getPieceSet();

        final ChessBoardGeometry geometry = (ChessBoardGeometry) getBoardGeometry();
        final Board board = position.getBoard();
        final int activeColor = position.getActivePlayerIndex();

        // is there a piece on source square?
        assert board.getPiece(source) != null : "source square cannot be vacant: " + formatMove(move);

        assert board.getPiece(source).equals(piece) : "source square does not contain piece: " + formatMove(move);

        // is it of the right color?
        assert ((ChessLikePiece) board.getPiece(source))
                .getColor() == activeColor : "can only move your own pieces: " + formatMove(move);

        List<MicroOperation> ops = new LinkedList<MicroOperation>();

        boolean looksLikePlainCapture = looksLikePlainCapture(chessMove, board);
        boolean looksLikeEnPassantCapture = looksLikeEnPassantCapture(chessMove, board);

        // capture
        if (looksLikePlainCapture || looksLikeEnPassantCapture) {
            Coordinate capturedCoordinate;

            if (looksLikePlainCapture) {
                capturedCoordinate = target;
            } else
            // if (looksLikeEnPassantCapture)
            {
                // is en passant option available?
                assert target.equals(
                        ((ChessPosition) position).getEnPassantTargetSquare()) : "no en passant option available: "
                                + formatMove(chessMove);

                // valid pawn capture move?
                assert ((ChessLikePiece) piece).isThreateningSquare(geometry, board, source,
                        target) : "not a valid pawn capture move: " + formatMove(chessMove);

                // promotion attribute null?
                assert chessMove.getPromotionPiece() == null : "no promotion available: " + formatMove(chessMove);

                // captured piece
                capturedCoordinate = geometry.vectorAdd(target, geometry.backwardDir(piece.getColor()), 1);

                // asserts
                ChessLikePiece capturedPiece = (ChessLikePiece) board.getPiece(capturedCoordinate);
                assert capturedPiece != null;
                assert capturedPiece instanceof Pawn;
                assert !capturedPiece.sameColor(piece);
            }

            ChessLikePiece capturedPiece = (ChessLikePiece) board.getPiece(capturedCoordinate);
            int capturedPieceColor = capturedPiece.getColor();

            // capture
            ops.add(new CapturePieceOperation(capturedCoordinate));

            if (castlingRule) {
                // forfeit opponent castling if king is captured (for games with check rule)
                if (capturedPiece instanceof King && isInHomeArea(initialBoard, target, capturedPiece)) {
                    if (((ChessPosition) position).getCastlingAvailable(capturedPieceColor,
                            CastlingSide.KINGSIDE) == true)
                        ops.add(new ForfeitCastlingOperation(capturedPieceColor, CastlingSide.KINGSIDE));
                    if (((ChessPosition) position).getCastlingAvailable(capturedPieceColor,
                            CastlingSide.QUEENSIDE) == true)
                        ops.add(new ForfeitCastlingOperation(capturedPieceColor, CastlingSide.QUEENSIDE));
                }

                // forfeit opponent castling if rook is captured
                if (capturedPiece instanceof Rook && isInHomeArea(initialBoard, target, capturedPiece)) {
                    Coordinate kingCoords = initialBoard
                            .locatePiece(pieceSet.getPiece(King.class, capturedPieceColor));
                    if (geometry.isKingSide(kingCoords, capturedCoordinate)) {
                        if (((ChessPosition) position).getCastlingAvailable(capturedPieceColor,
                                CastlingSide.KINGSIDE) == true)
                            ops.add(new ForfeitCastlingOperation(capturedPieceColor, CastlingSide.KINGSIDE));
                    } else {
                        if (((ChessPosition) position).getCastlingAvailable(capturedPieceColor,
                                CastlingSide.QUEENSIDE) == true)
                            ops.add(new ForfeitCastlingOperation(capturedPieceColor, CastlingSide.QUEENSIDE));
                    }
                }
            }
        }

        // move piece(s)
        if (!looksLikeCastlingMove(chessMove, board)) {
            // normal move
            ops.add(new MovePieceOperation(source, target));
        } else {
            // castling move
            char rank = source.getNotation().charAt(1);
            Coordinate kingTarget, rookTarget;
            if (geometry.isKingSide(source, target)) {
                kingTarget = geometry.locateCoordinate("g" + rank);
                rookTarget = geometry.locateCoordinate("f" + rank);
            } else {
                kingTarget = geometry.locateCoordinate("c" + rank);
                rookTarget = geometry.locateCoordinate("d" + rank);
            }

            // move king and rook
            if (source.equals(rookTarget) && target.equals(kingTarget)) {
                // exchange
                ops.add(new ExchangePieceOperation(source, target));
            } else if (source.equals(kingTarget)) {
                // move rook only
                ops.add(new MovePieceOperation(target, rookTarget));
            } else if (target.equals(rookTarget)) {
                // move king only
                ops.add(new MovePieceOperation(source, kingTarget));
            } else if (kingTarget.equals(target)) {
                // move rook first
                ops.add(new MovePieceOperation(target, rookTarget));
                ops.add(new MovePieceOperation(source, kingTarget));
            } else {
                // move king first
                ops.add(new MovePieceOperation(source, kingTarget));
                ops.add(new MovePieceOperation(target, rookTarget));
            }
        }

        // promotion
        if (chessMove.getPromotionPiece() != null) // is promotion move
        {
            assert promotionRule : "there is no promotion in this rule";
            assert looksLikePromotionMove(chessMove, board) : "is not eligable for promotion: "
                    + formatMove(chessMove);

            // promote pawn
            ops.add(new SwapOutPieceOperation(target,
                    pieceSet.getPiece(chessMove.getPromotionPiece(), activeColor)));
        } else {
            assert !promotionRule || !looksLikePromotionMove(chessMove,
                    board) : "is eligable for promotion, but promotion piece missing: " + formatMove(chessMove);
        }

        // adjust half move clock
        if (looksLikePlainCapture || looksLikeEnPassantCapture || piece instanceof Pawn)
            ops.add(new ResetHalfmoveClockOperation());
        else
            ops.add(IncrementHalfmoveClockOperation.instance());

        if (castlingRule) {
            // forfeit all castling if the king is moving
            if (piece instanceof King) {
                if (((ChessPosition) position).getCastlingAvailable(activeColor, CastlingSide.QUEENSIDE) == true)
                    ops.add(new ForfeitCastlingOperation(activeColor, CastlingSide.QUEENSIDE));
                if (((ChessPosition) position).getCastlingAvailable(activeColor, CastlingSide.KINGSIDE) == true)
                    ops.add(new ForfeitCastlingOperation(activeColor, CastlingSide.KINGSIDE));
            }

            // forfeit castling if a rook is moving from its initial position
            if (piece instanceof Rook && isInHomeArea(initialBoard, source, piece)) {
                Coordinate kingCoords = initialBoard.locatePiece(pieceSet.getPiece(King.class, activeColor));
                if (geometry.isKingSide(kingCoords, source)) {
                    if (((ChessPosition) position).getCastlingAvailable(activeColor, CastlingSide.KINGSIDE) == true)
                        ops.add(new ForfeitCastlingOperation(activeColor, CastlingSide.KINGSIDE));
                } else {
                    if (((ChessPosition) position).getCastlingAvailable(activeColor,
                            CastlingSide.QUEENSIDE) == true)
                        ops.add(new ForfeitCastlingOperation(activeColor, CastlingSide.QUEENSIDE));
                }
            }
        }

        if (antiKingCastlingRule) {
            // forfeit castling if king or antiking is moving
            if (piece instanceof King || piece instanceof AntiKing) {
                if (((AntiKingChessPosition) position).getCastlingAvailable(activeColor, piece.getClass()) == true)
                    ops.add(new ForfeitAntiKingCastlingOperation(activeColor, piece.getClass()));
            }
        }

        // adjust en passant option
        if (enPassantRule) {
            if (looksLikeEnPassantMove(chessMove, board))
                ops.add(new SetEnPassantTargetSquareOperation(
                        geometry.vectorAdd(source, geometry.forwardDir(piece.getColor()), 1)));
            else if (((ChessPosition) position).getEnPassantTargetSquare() != null)
                ops.add(new SetEnPassantTargetSquareOperation(null));
        }

        // next player
        ops.add(NextPlayerOperation.instance());

        return ops;
    }

    @Override
    protected final boolean looksLikePlainCapture(ChessLikeMove move, Board board) {
        if (looksLikeCastlingMove(move, board))
            return false;

        return super.looksLikePlainCapture(move, board);
    }

    @Override
    protected final boolean looksLikeCapture(ChessLikeMove move, Board board) {
        return looksLikePlainCapture(move, board) || (enPassantRule && looksLikeEnPassantCapture(move, board));
    }

    protected boolean looksLikeEnPassantCapture(ChessLikeMove move, Board board) {
        ChessBoardGeometry geometry = (ChessBoardGeometry) getBoardGeometry();
        Coordinate source = move.getSource();
        Coordinate target = move.getTarget();
        Piece piece = board.getPiece(source);

        // piece is a pawn?
        if (!(piece instanceof Pawn))
            return false;

        // pawn changing file?
        if (geometry.sameFile(source, target))
            return false;

        // does not look like plain capture?
        if (looksLikePlainCapture(move, board))
            return false;

        return true;
    }

    /**
     * Determines if this move is a casteling move by the nature of the chessMove.getPiece() and target squares. It does
     * not check if any of the casteling conditions are fulfilled.
     */
    protected boolean looksLikeCastlingMove(ChessLikeMove move, Board board) {
        ChessBoardGeometry geometry = (ChessBoardGeometry) getBoardGeometry();

        // source is a king?
        Coordinate source = move.getSource();
        Piece sourcePiece = board.getPiece(source);
        if (!(sourcePiece instanceof King))
            return false;

        // target on same rank?
        Coordinate target = move.getTarget();
        if (!geometry.sameRank(source, target))
            return false;

        // target is a rook of same color
        Piece targetPiece = board.getPiece(target);
        if (!(targetPiece instanceof Rook))
            return false;
        if (!targetPiece.sameColor(sourcePiece))
            return false;

        return true;
    }

    /**
     * Determines if the move is opening the option of en passant
     */
    protected boolean looksLikeEnPassantMove(ChessMove move, Board board) {
        ChessBoardGeometry geometry = (ChessBoardGeometry) getBoardGeometry();
        Coordinate source = move.getSource();
        Coordinate target = move.getTarget();
        Piece piece = board.getPiece(source);

        // piece is pawn?
        if (!(piece instanceof Pawn))
            return false;

        // same file?
        if (!geometry.sameFile(source, target))
            return false;

        // distance 2 squares?
        if (geometry.distance(source, target) != 2)
            return false;

        return true;
    }

    /**
     * Determines if the move on the given board leads to a pawn promotion.
     */
    protected boolean looksLikePromotionMove(ChessMove move, Board board) {
        ChessBoardGeometry geometry = (ChessBoardGeometry) getBoardGeometry();
        Coordinate source = move.getSource();
        Coordinate target = move.getTarget();
        Piece piece = board.getPiece(source);

        // piece is pawn?
        if (!(piece instanceof Pawn))
            return false;

        // is arriving at target rank?
        if (!geometry.isInTargetArea(target, piece.getColor()))
            return false;

        return true;
    }

    /**
     * Determines if the piece is in its home area.
     */
    private boolean isInHomeArea(Board initialBoard, Coordinate coordinate, Piece piece) {
        return piece.equals(initialBoard.getPiece(coordinate));
    }
}