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();
}
}