Files
Sushi/src/suicideChess/Board.java
djib ed438e593a Version 1.4.2
Now possible to undo, save games, restore positions, and view statistics when the computer plays.
2006-01-28 16:35:21 +00:00

520 lines
16 KiB
Java

package suicideChess;
import suicideChess.Square.NotAValidSquare;
/**
* This class contains the board representation.
* The board is represented using bitboards.
* <ul><li>a1 is square 0</li>
* <li>h8 is square 7</li>
* <li>a2 is square 8</li>
* <li>... and so on</li>
*
* Note that the evaluation of the board balance is in this class since I considered that it is
* very closely related to the board position.
*
* @author Jean-Baptiste H&eacute;tier
* @version $LastChangedRevision$, $LastChangedDate$
*
*/
public class Board {
/*===========*
* CONSTANTS *
*===========*/
//Some constants to make code more readable
public static final int NB_OF_RANKS = 8;
public static final int NB_OF_FILES = 8;
public static final int NB_OF_SQUARES = NB_OF_RANKS*NB_OF_FILES;
private static final int NB_OF_BITBOARDS = 14;
@SuppressWarnings("serial")
public class NoPieceOnSquare extends Exception {
NoPieceOnSquare(String s) { super(s); };
}
@SuppressWarnings("serial")
public class UnableToParseFENStringException extends Exception {
UnableToParseFENStringException(String s) { super(s); };
}
/**
* Value returned by the evaluation function when White wins
*/
public static final int WHITE_WINS = 99999;
/**
* Value returned by the evaluation function when Black wins
*/
public static final int BLACK_WINS = -99999;
/*======*
* DATA *
*======*/
//The following table is used to map squares to bits
protected static long mapSquaresToBits[];
//static function used to initialise data
static {
mapSquaresToBits = new long[NB_OF_SQUARES];
for(int i=0; i<NB_OF_SQUARES; i++) {
mapSquaresToBits[i] = (1L << i);
}
}
//The following table contains all the bit boards
protected long bitBoards[];
private boolean enPassant=false; //is there an 'en passant pawn' on the board
private Square enPassantSquare;
private int numberOfBlackPieces = NB_OF_FILES*2;
private int numberOfWhitePieces = NB_OF_FILES*2;
private int boardValue = 0; //evaluation of the board value
private int currentPlayer; //color of the current player.
/*=============*
* CONSTRUCTOR *
*=============*/
/**
* Constructor of the class Board
* @throws NotAValidSquare
*/
public Board() throws NotAValidSquare {
//the following line makes sure that enPassantSquare is defined at some point.
enPassantSquare = new Square("a1");
bitBoards = new long[NB_OF_BITBOARDS];
addPiece(new Square("a1"),new Piece(Piece.WHITE_ROOK));
addPiece(new Square("b1"),new Piece(Piece.WHITE_KNIGHT));
addPiece(new Square("c1"),new Piece(Piece.WHITE_BISHOP));
addPiece(new Square("d1"),new Piece(Piece.WHITE_QUEEN));
addPiece(new Square("e1"),new Piece(Piece.WHITE_KING));
addPiece(new Square("f1"),new Piece(Piece.WHITE_BISHOP));
addPiece(new Square("g1"),new Piece(Piece.WHITE_KNIGHT));
addPiece(new Square("h1"),new Piece(Piece.WHITE_ROOK));
addPiece(new Square("a2"),new Piece(Piece.WHITE_PAWN));
addPiece(new Square("b2"),new Piece(Piece.WHITE_PAWN));
addPiece(new Square("c2"),new Piece(Piece.WHITE_PAWN));
addPiece(new Square("d2"),new Piece(Piece.WHITE_PAWN));
addPiece(new Square("e2"),new Piece(Piece.WHITE_PAWN));
addPiece(new Square("f2"),new Piece(Piece.WHITE_PAWN));
addPiece(new Square("g2"),new Piece(Piece.WHITE_PAWN));
addPiece(new Square("h2"),new Piece(Piece.WHITE_PAWN));
addPiece(new Square("a8"),new Piece(Piece.BLACK_ROOK));
addPiece(new Square("b8"),new Piece(Piece.BLACK_KNIGHT));
addPiece(new Square("c8"),new Piece(Piece.BLACK_BISHOP));
addPiece(new Square("d8"),new Piece(Piece.BLACK_QUEEN));
addPiece(new Square("e8"),new Piece(Piece.BLACK_KING));
addPiece(new Square("f8"),new Piece(Piece.BLACK_BISHOP));
addPiece(new Square("g8"),new Piece(Piece.BLACK_KNIGHT));
addPiece(new Square("h8"),new Piece(Piece.BLACK_ROOK));
addPiece(new Square("a7"),new Piece(Piece.BLACK_PAWN));
addPiece(new Square("b7"),new Piece(Piece.BLACK_PAWN));
addPiece(new Square("c7"),new Piece(Piece.BLACK_PAWN));
addPiece(new Square("d7"),new Piece(Piece.BLACK_PAWN));
addPiece(new Square("e7"),new Piece(Piece.BLACK_PAWN));
addPiece(new Square("f7"),new Piece(Piece.BLACK_PAWN));
addPiece(new Square("g7"),new Piece(Piece.BLACK_PAWN));
addPiece(new Square("h7"),new Piece(Piece.BLACK_PAWN));
}
/**
* A constructor that simply copies a bitboard
* @param bitboard The bitboard to be copied
*/
public Board(Board bitboard) {
this.numberOfBlackPieces = bitboard.numberOfBlackPieces;
this.numberOfWhitePieces = bitboard.numberOfWhitePieces;
this.boardValue = bitboard.boardValue;
this.bitBoards = new long[NB_OF_BITBOARDS];
for (int i=0; i<NB_OF_BITBOARDS; i++) {
this.bitBoards[i] = bitboard.bitBoards[i];
}
this.enPassant = bitboard.enPassant;
this.enPassantSquare = new Square(bitboard.enPassantSquare);
this.currentPlayer = bitboard.currentPlayer;
}
/**
* This constructor is used to create a board with a string in FEN notation
* @param command A string representing the command
* @return A {@link Board} corresponding to the FEN string
* @throws NotAValidSquare
* @throws UnableToParseFENStringException
* @see Board
*/
public Board(String command) throws NotAValidSquare, UnableToParseFENStringException {
String[] result = command.split("/|\\s");
/*
* After splitting
* 0->rank 8
* 1->rank 7
* ...
* 7->rank 1
* 8->color on move
* 9->castling (I don't care about this)
* 10->enpassant Square (if any), otherwise '-'
* 11->count of plies since the last pawn advance or capturing move
* 12->fullmove number
*/
bitBoards = new long[NB_OF_BITBOARDS];
if (result.length!=13) {
throw new UnableToParseFENStringException(command);
}
if (result[8].equals("b")) {
currentPlayer=Piece.BLACK;
} else {
currentPlayer=Piece.WHITE;
}
if (!result[10].equals("-")) {
enPassant = true;
enPassantSquare = new Square(result[10]);
} else {
enPassant = false;
enPassantSquare = new Square("a1"); //otherwise it is null
}
numberOfBlackPieces = 0;
numberOfWhitePieces = 0;
for(int split=0; split < 8; split++) {
int offset=0;
char[] rowToParse = result[split].toCharArray();
for (int character=0; character<rowToParse.length; character++) {
if (Character.isDigit(rowToParse[character])) {
offset+=((int)rowToParse[character]-(int)'1');
} else {
Piece pieceToAdd = new Piece(rowToParse[character]);
addPiece(new Square(character+offset+1,NB_OF_RANKS-split),pieceToAdd);
if (pieceToAdd.getColor()==Piece.BLACK) {
numberOfBlackPieces++;
} else {
numberOfWhitePieces++;
}
}
}
}
boardValue = getBoardValue();
}
/*================*
* PUBLIC METHODS *
*================*/
/**
* This methods takes a {@link Move} and applies it (updating the bitboard)
* @param move The move that is to be done
* @throws NoPieceOnSquare If a piece is trying to be moved from a square that does not exist.
* @throws NotAValidSquare
* @see Move
*/
public void doMove(Move move) throws NoPieceOnSquare, NotAValidSquare {
if (move.isCaptureMove()) {
if (move.isEnPassant()) {
removePiece(move.getEnPassantSquare(), move.getCapturedPiece());
} else {
removePiece(move.toSquare(), move.getCapturedPiece());
}
}
removePiece(move.fromSquare(), move.getMovingPiece());
if (move.isPromotionMove()) {
addPiece(move.toSquare(), move.getPromotionPiece());
} else {
addPiece(move.toSquare(), move.getMovingPiece());
}
this.enPassant=false;
if (move.enablesEnPassant()) {
enPassant=true;
enPassantSquare=move.getEnPassantSquare();
}
if(currentPlayer==Piece.WHITE) {
currentPlayer=Piece.BLACK;
if (SuicideChess.ASCII_GAME)
System.out.println("Black: ");
} else {
currentPlayer=Piece.WHITE;
if (SuicideChess.ASCII_GAME)
System.out.println("White: ");
}
evaluateNewBoardValue(move);
}
/**
* This function searchs for what piece is on a square.
* @param onSquare The Square on which we will look for a piece.
* @return a {@link Piece}
* @see Square
* @see Piece
*/
public Piece getPiece(Square onSquare) {
//if there is a corresponding white piece.
if (!isEmpty(onSquare, new Piece(Piece.WHITE_PIECES))) {
//for every white piece bitboard, look for the piece
for (int piece = Piece.WHITE_PAWN; piece <= Piece.WHITE_ROOK; piece += 2) {
if(!isEmpty(onSquare, new Piece(piece))) {
return new Piece(piece);
}
}
} else if (!isEmpty(onSquare, new Piece(Piece.BLACK_PIECES))) {
//for every white piece bitboard, look for the piece
for (int piece = Piece.BLACK_PAWN; piece <= Piece.BLACK_ROOK; piece += 2) {
if(!isEmpty(onSquare, new Piece(piece))) {
return new Piece(piece);
}
}
}
//if no piece found
return new Piece(Piece.NONE);
}
/**
* This function returns a boolean telling if a {@link Piece} is on a {@link Square}.
* @param square The Square
* @param piece A Piece constant
* @return boolean
* @see Piece
* @see Square
*/
public boolean isEmpty(Square square, Piece piece) {
long mask = mapSquaresToBits[squareToBitBoardSquare(square)];
if ((bitBoards[piece.getPieceNumber()] & mask) == 0) {
return true;
} else {
return false;
}
}
/**
* This function converts a {@link Square} to a number representing a bitboard square
* @param square The Square to be converted
* @return int
* @see Square
*/
public static int squareToBitBoardSquare(Square square) {
//converts a square ("e2") to a BitboardSquare (
return square.getFileNb() -1 + (square.getRank()-1)*NB_OF_FILES;
}
// /**
// * This function is used to set the table to an state in which a {@link Square} is an 'en passant' Square
// * @param Square The 'en passant' Square
// * @see Square
// */
// public void setEnPassant(Square square) {
// enPassant=true;
// enPassantSquare=square;
// }
//
// /**
// * This function is used to set the table back to a state with no 'en passant' Squares.
// */
// public void clearEnPassant () {
// enPassant=false;
// }
/**
* This function returns a boolean saying if the board is in an 'en passant' state.
* @return boolean
*/
public boolean isEnPassant() {
return enPassant;
}
/**
* This function is used to return the 'en passant' Square. Don't use it unless table is in an 'en passant' state.
* @return Square
* @see Square
*/
public Square getEnPassantSquare() {
return enPassantSquare;
}
/**
* This function returns an integer representing the result of the static evaluation function
* for the current board
* @return Integer
*/
public int getBoardValue() {
return boardValue;
}
/**
* This function returns the current player color
* @return Integer
*/
public int getCurrentPlayer() {
return currentPlayer;
}
/**
* This function can be used to display the board
* Black pieces are displayed in uppercase letters.
*/
public void display(){
for (int file = NB_OF_FILES; file >= 1; file--) {
System.out.println(" +---+---+---+---+---+---+---+---+");
String display = file+" |";
for (int rank=1; rank <= NB_OF_RANKS; rank++) {
boolean displayedSomething = false;
long mask = mapSquaresToBits[rank-1+(file-1)*NB_OF_RANKS];
if (displayPiece(Piece.BLACK_PAWN,mask)) {
displayedSomething=true;
display+="'"+Piece.BLACK_PAWN_CHAR+"'|";
}
if (displayPiece(Piece.BLACK_QUEEN,mask)) {
displayedSomething=true;
display+="'"+Piece.BLACK_QUEEN_CHAR+"'|";
}
if (displayPiece(Piece.BLACK_KING,mask)) {
displayedSomething=true;
display+="'"+Piece.BLACK_KING_CHAR+"'|";
}
if (displayPiece(Piece.BLACK_KNIGHT,mask)) {
displayedSomething=true;
display+="'"+Piece.BLACK_KNIGHT_CHAR+"'|";
}
if (displayPiece(Piece.BLACK_ROOK,mask)) {
displayedSomething=true;
display+="'"+Piece.BLACK_ROOK_CHAR+"'|";
}
if (displayPiece(Piece.BLACK_BISHOP,mask)) {
displayedSomething=true;
display+="'"+Piece.BLACK_BISHOP_CHAR+"'|";
}
if (displayPiece(Piece.WHITE_PAWN,mask)) {
displayedSomething=true;
display+=" "+Piece.WHITE_PAWN_CHAR+" |";
}
if (displayPiece(Piece.WHITE_QUEEN,mask)) {
displayedSomething=true;
display+=" "+Piece.WHITE_QUEEN_CHAR+" |";
}
if (displayPiece(Piece.WHITE_KING,mask)) {
displayedSomething=true;
display+=" "+Piece.WHITE_KING_CHAR+" |";
}
if (displayPiece(Piece.WHITE_KNIGHT,mask)) {
displayedSomething=true;
display+=" "+Piece.WHITE_KNIGHT_CHAR+" |";
}
if (displayPiece(Piece.WHITE_ROOK,mask)) {
displayedSomething=true;
display+=" "+Piece.WHITE_ROOK_CHAR+" |";
}
if (displayPiece(Piece.WHITE_BISHOP,mask)) {
displayedSomething=true;
display+=" "+Piece.WHITE_BISHOP_CHAR+" |";
}
if (!displayedSomething)
display+=" |";
}
System.out.println(display);
}
System.out.println(" +---+---+---+---+---+---+---+---+");
System.out.println(" a b c d e f g h");
System.out.println();
}
/*=================*
* PRIVATE METHODS *
*=================*/
/* private long getBitBoard(int bitboard_number) {
return bitBoards[bitboard_number];
}
*/
protected void addPiece(Square square, Piece piece) {
//OR :
// 0 OR a = a
// 1 OR a = 1
//add Piece to corresponding bitboard.
bitBoards[piece.getPieceNumber()] |= mapSquaresToBits[squareToBitBoardSquare(square)];
//update the bitboard of all pieces of that color
bitBoards[piece.getColor()] |= mapSquaresToBits[squareToBitBoardSquare(square)];
}
protected void removePiece(Square square, Piece piece) throws NoPieceOnSquare {
//XOR :
// 0 XOR a = a
// 1 XOR 0 = 1 !!! Don't remove a piece that don't exist !!!
// 1 XOR 1 = 0
//remove Piece to corresponding bitboard.
if (SuicideChess.BITBOARD_REMOVEPIECE_CHECK_REMOVE) {
if (!isEmpty(square, piece)) {
bitBoards[piece.getPieceNumber()] ^= mapSquaresToBits[squareToBitBoardSquare(square)];
} else {
throw new NoPieceOnSquare("Square: "+square + "; Piece: " + piece.getPieceNumber());
}
} else {
bitBoards[piece.getPieceNumber()] ^= mapSquaresToBits[squareToBitBoardSquare(square)];
}
//update the bitboard of all pieces of that color
bitBoards[piece.getColor()] ^= mapSquaresToBits[squareToBitBoardSquare(square)];;
}
//used by function display()
private boolean displayPiece(int whatToDisplay, long mask) {
if ((bitBoards[whatToDisplay] & mask)==0) {
return false;
} else {
return true;
}
}
private void evaluateNewBoardValue (Move move) throws NotAValidSquare {
if (move.isCaptureMove()) {
if (move.getCapturedPiece().getColor()==Piece.BLACK) {
numberOfBlackPieces--;
} else {
numberOfWhitePieces--;
}
if (numberOfBlackPieces == 0) {
boardValue = BLACK_WINS;
} else if (numberOfWhitePieces == 0){
boardValue = WHITE_WINS;
} else {
//this is a very very basic evaluation function that will be changed.
boardValue = numberOfBlackPieces - numberOfWhitePieces;
}
}
if (Rules.isThereALegalMovesForPlayer(this)) {
if (currentPlayer==Piece.WHITE) {
boardValue = WHITE_WINS;
} else {
boardValue = BLACK_WINS;
}
}
}
}