package com.editev.chess.piece; import com.editev.chess.Game; import com.editev.chess.Move; import com.editev.chess.Board; import com.editev.chess.Square; import com.editev.chess.Chess; /** The Pawn has four special cases; first move, en passant, capturing and promotion. * This is the most complicated of all the Pieces, and there are two derived classes, * Pawn.Black and Pawn.White. * * @see See the source here. */ abstract public class Pawn extends Piece { /** Is this Pawn move legal? It may only move two squares on the first move. * It moves forward to empty squares, and captures diagonally. * En passant occurs if the opponent's last move was the two square first move * and the player could have captured the pawn if it only moved one square. * Promotion isn't involved here. * * @return true if this Pawn move is illegal */ public boolean isIllegal( Move move, Game game ) { byte rows = getRowOffset( move ); // moving how many rows byte columns = getColumnOffset( move ); // number of columns to move.... boolean isWhite = rows < 0; // is it a white piece? boolean occupied = game.getPieceIndex( move.target ) != NO_PIECE; // is there a piece on the target square? byte home = (byte) (isWhite ? 6 : 1); // home space for an unmoved pawn. if (2 == Math.abs( rows )) { // moving 2 rows? return occupied // illegal to move forward onto another piece. || move.source.row != home; // can't have moved this pawn before. } if (columns == 0) return occupied; // illegal to move forward on other piece. if (game.getPieceIndex( move.target ) == NO_PIECE) { // can only be en passant // because we are moving diagonally, but no piece! if (game.getEP() != move.target.column) return true; // not ep'ing the correct column. byte epRow = (byte) (isWhite ? 3 : 4); // can only ep from one possible row. return move.source.row != epRow; // fail if wrong row. } return false; } /** Increment the move to the next one. This is overridden in order to * count through the promotions. */ public boolean incrementMoveIndex( Move move ) { if (!moreMoves( move )) return false; if (isPromotion( move ) ) { if (++move.promotion <= Chess.Black.KNIGHT) return true; // there's another piece in the promotion cycle move.promotion = Chess.Black.QUEEN; // start a new promotion cycle next time. if (!super.moreMoves( move )) return false; // no more moves. } return super.incrementMoveIndex( move ); // otherwise, continue with a new possible move. } /** Any more moves? This is also overridden to take care of the promotions. */ public boolean moreMoves( Move move ) { return super.moreMoves( move ) || ( move.index == (moves.length-1) && isPromotion( move ) && move.promotion < Chess.Black.KNIGHT ); } /** Apply the move to the squares of the board. * In the case of a promotion, we need to replace the Pawn with another Piece. * In the case of an e.p. capture, we need to remove the captured enemy Pawn. */ public byte applyMoveToBoard( Move move, Board board ) { boolean isEP = isepCapture( move, board ); // have to do this first before changing the board! byte piece = super.applyMoveToBoard( move, board ); // check for promotion. // if (isPromotion( move, WHITE )) board.setPieceIndex( move.target, (byte) (move.promotion+WHITE) ); else if (isPromotion( move, BLACK )) board.setPieceIndex( move.target, (byte) (move.promotion+BLACK) ); else if (isEP) { // an en passant capture! Square sq = new Square( move.target.column, move.source.row ); board.setPieceIndex( sq, NO_PIECE ); // erase the actual captured pawn. piece = EP_CAPTURE; // special flag to unmove squares tells of e.p. pawn capture. } return piece; // return the name of the captured piece, if any.... } /** Unapply the move to the squares of the board. * We have the same special cases as applyMoveToBoard, namely promotion and e.p. */ public void undoMoveToBoard( Move move, Board board, byte piece ) { if (piece == EP_CAPTURE ) { super.undoMoveToBoard( move, board, NO_PIECE ); // move the pawn back, leave an empty square. board.setPieceIndex( new Square( move.target.column, move.source.row ), (move.source.row == 3) ? Chess.Black.PAWN : Chess.White.PAWN ); // undo the e.p. capture. return; } super.undoMoveToBoard( move, board, piece ); // move the pawn back, replacing any captured piece. if (isPromotion( move, WHITE )) { board.setPieceIndex( move.source, Chess.White.PAWN ); // undo the white promotion } else if (isPromotion( move, BLACK )) { board.setPieceIndex( move.source, Chess.Black.PAWN ); // undo the black promotion } } /** Change the GameState component of the game for this pawn move -- we need to override this method * because we need to store the e.p. game to validate next pawn moves for a possible e.p. capture. */ public void applyMoveToState( Move move, Game game ) { super.applyMoveToState( move, game ); // superclass. // if we move two spaces then there is a possibility for e.p. capture next move. // if (isTwoSquare( move ) ) game.setEP( move.source.column ); } /** Pawn moves are never reversible. * @return false. */ public boolean isIrreversible( Move move, Board board ) { return true; } /** We have to override this method because Pawns only capture diagonally. */ public boolean isCapture( Move move, Board board ) { return move.source.column != move.target.column; } /** Is this move a promotion for the given color? */ public static boolean isPromotion( Move move, byte color ) { return !isTwoSquare( move ) && move.target.row == notColor( color ); } /** Is this move a promotion for either color? */ public static boolean isPromotion( Move move ) { return isPromotion( move, WHITE ) || isPromotion( move, BLACK ); } /** Is this a two square move for the pawn? */ public static boolean isTwoSquare( Move move ) { return Math.abs( move.source.row - move.target.row ) > 1; } /** Special piece indicates an en passant (e.p.) capture. */ public static byte EP_CAPTURE = (byte) (NO_PIECE - 1); /** Is this legal move an e.p. capture? */ public boolean isepCapture( Move move, Board board ) { return isCapture( move, board ) && !super.isCapture( move, board ); } // ep captures are pawn captures where the "target" square is empty. protected Pawn( byte[][] moves ) { super( moves ); } /** This derivation of Pawn represents a Black pawn, and only differs from the White pawn by its list of moves. */ public static class Black extends Pawn { /** An array of all the possible Pawn.Black moves as byte offsets. */ public static final byte[][] MOVES = { {-1, 1}, {0, 1}, {1, 1}, {0, 2}, }; /** This class is a singleton, so the constructor is private. */ private Black() { super( MOVES ); } /** The unique/singleton instantiation of Pawn.Black. */ public static final Black PIECE = new Black(); } /** This derivation of Pawn represents a White pawn, and only differs from the Black pawn by its list of moves. */ public static class White extends Pawn { /** An array of all the possible Pawn.White moves as byte offsets. */ public static final byte[][] MOVES = { {0, -2}, {-1, -1}, {0, -1}, {1, -1}, }; /** This class is a singleton, so the constructor is private. */ private White() { super( MOVES ); } /** The unique/singleton instantiation of Pawn.White. */ public static final White PIECE = new White(); } }