Version 0.4

Now plays with a grandpa minmax.
It is much better, though not excellent ;)
This commit is contained in:
2006-01-26 18:05:16 +00:00
parent 6cf9f27892
commit 5266b582bb
5 changed files with 337 additions and 160 deletions

View File

@ -10,6 +10,9 @@ import suicideChess.Square.NotAValidSquare;
* <li>a2 is square 8</li> * <li>a2 is square 8</li>
* <li>... and so on</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 * @author Jean-Baptiste H&eacute;tier
* @version $LastChangedRevision$, $LastChangedDate$ * @version $LastChangedRevision$, $LastChangedDate$
* *
@ -37,6 +40,16 @@ public class Board {
NoPieceOnSquare(String s) { super(s); }; NoPieceOnSquare(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 * * DATA *
@ -60,6 +73,9 @@ public class Board {
private boolean enPassant=false; //is there an 'en passant pawn' on the board private boolean enPassant=false; //is there an 'en passant pawn' on the board
private Square enPassantSquare; 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
/*=============* /*=============*
@ -111,12 +127,29 @@ public class Board {
} }
/**
* 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 = bitboard.enPassantSquare;
}
/*================* /*================*
* PUBLIC METHODS * * PUBLIC METHODS *
*================*/ *================*/
/**
/**
* This methods takes a {@link Move} and applies it (updating the bitboard) * This methods takes a {@link Move} and applies it (updating the bitboard)
* @param move The move that is to be done * @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 NoPieceOnSquare If a piece is trying to be moved from a square that does not exist.
@ -130,6 +163,8 @@ public class Board {
} else { } else {
removePiece(move.toSquare(), move.getCapturedPiece()); removePiece(move.toSquare(), move.getCapturedPiece());
} }
//capture moves change the value of the board
evaluateNewBoardValue(move);
} }
removePiece(move.fromSquare(), move.getMovingPiece()); removePiece(move.fromSquare(), move.getMovingPiece());
if (move.isPromotionMove()) { if (move.isPromotionMove()) {
@ -238,6 +273,17 @@ public class Board {
return enPassantSquare; return enPassantSquare;
} }
/**
* This function returns an integer representing the result of the static evaluation function
* for the current board
* @return
*/
public int getBoardValue() {
return boardValue;
}
/** /**
* This function can be used to display the board * This function can be used to display the board
* Black pieces are displayed in uppercase letters. * Black pieces are displayed in uppercase letters.
@ -359,4 +405,22 @@ public class Board {
} }
} }
private void evaluateNewBoardValue (Move move) {
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;
}
}
}
} }

View File

@ -3,6 +3,7 @@ package suicideChess;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Random; import java.util.Random;
import suicideChess.Board.NoPieceOnSquare;
import suicideChess.Square.NotAValidSquare; import suicideChess.Square.NotAValidSquare;
/** /**
@ -13,6 +14,8 @@ import suicideChess.Square.NotAValidSquare;
public class ComputerPlayer { public class ComputerPlayer {
/** /**
* This constructor creates a computer. * This constructor creates a computer.
*/ */
@ -23,12 +26,13 @@ public class ComputerPlayer {
/** /**
* This asks the computer to compute a move * This asks the computer to compute a move
* @param bitboard The current status of the {@link Board} * @param bitboard The current status of the {@link Board}
* @param color The color to play
* @return move A {@link Move} * @return move A {@link Move}
* @throws NotAValidSquare * @throws NotAValidSquare
* @see Board * @see Board
* @see Move * @see Move
*/ */
public Move doMove(Board bitboard, int color) throws NotAValidSquare { public Move doRandomMove(Board bitboard, int color) throws NotAValidSquare {
Random generator = new Random(); Random generator = new Random();
Rules.legalMovesForPlayer(bitboard,color); Rules.legalMovesForPlayer(bitboard,color);
ArrayList<Move> allLegalMoves = Rules.getLegalMovesCapture(); ArrayList<Move> allLegalMoves = Rules.getLegalMovesCapture();
@ -39,8 +43,73 @@ public class ComputerPlayer {
return allLegalMoves.get(generator.nextInt(allLegalMoves.size())); return allLegalMoves.get(generator.nextInt(allLegalMoves.size()));
} }
throw new RuntimeException(); throw new RuntimeException("**Error** in doRandomMove");
} }
/**
* Basic MinMax
* @param bitboard The bitboard
* @param color The color to play
* @return The best Move found
* @throws NotAValidSquare
* @throws NoPieceOnSquare
* @see Board
* @see Move
*/
public Move doMinMaxMove(Board bitboard, int color) throws NotAValidSquare, NoPieceOnSquare {
bestMove = null;
MinMax(bitboard, color, 0);
return bestMove;
}
private int MinMax(Board bitboard, int color, int currentDepth) throws NotAValidSquare, NoPieceOnSquare {
if (currentDepth >= SuicideChess.PLY_DEPTH) {
return bitboard.getBoardValue();
}
Rules.legalMovesForPlayer(bitboard,color);
ArrayList<Move> allLegalMoves = Rules.getLegalMovesCapture();
if (allLegalMoves.size()==0) {
allLegalMoves = Rules.getLegalMovesNonCapture();
}
if (allLegalMoves.size()==0) {
if (color==Piece.BLACK) {
return Board.BLACK_WINS;
} else {
return Board.WHITE_WINS;
}
} else {
int bestMoveIndex=0;
int currentScore;
int bestScoreSoFar;
if (color==Piece.BLACK) {
bestScoreSoFar=Board.WHITE_WINS+1; //any move even a WHITE_WINS will be better than that
for (int i=0; i<allLegalMoves.size(); i++) {
Board boardCopy = new Board(bitboard);
boardCopy.doMove(allLegalMoves.get(i));
currentScore=MinMax(boardCopy,Piece.WHITE,currentDepth+1);
if (currentScore < bestScoreSoFar) { //black tries to minimise his score
bestScoreSoFar = currentScore;
bestMoveIndex = i;
}
}
} else { //white piece
bestScoreSoFar=Board.BLACK_WINS-1; //any move even a BLACK_WINS will be better than that
for (int i=0; i<allLegalMoves.size(); i++) {
Board boardCopy = new Board(bitboard);
boardCopy.doMove(allLegalMoves.get(i));
currentScore=MinMax(boardCopy,Piece.BLACK,currentDepth+1);
if (currentScore > bestScoreSoFar) { //white tries to maximise his score
bestScoreSoFar = currentScore;
bestMoveIndex = i;
}
}
}
bestMove = allLegalMoves.get(bestMoveIndex);
return bestScoreSoFar;
}
}
private static Move bestMove = null;
} }

View File

@ -33,13 +33,18 @@ public class SuicideChess {
/** /**
* The name to be displayed * The name to be displayed
*/ */
public static final String NAME = "djib's SuicideChess v0.3 beta 2"; public static final String NAME = "djib's SuicideChess v0.4.1";
/** /**
* Displays informations in the console. * Displays informations in the console.
*/ */
public static final boolean ASCII_GAME = false; public static final boolean ASCII_GAME = false;
/**
* Number of Plies the computes searches to
*/
public static final int PLY_DEPTH = 5;
/** /**
* The color of the current player * The color of the current player
*/ */
@ -60,167 +65,200 @@ public class SuicideChess {
} }
} }
/**
* If feature usermove has not been accepted by XBoard then consider all unknown commands
* as moves
*/
private static boolean acceptedUsermove = false;
/** /**
* The main function * The main function
* @param args No parameters should be transmitted to this function. * @param args No parameters should be transmitted to this function.
* @throws NotAValidSquare
*/ */
public static void main(String[] args) { public static void main(String[] args) throws NotAValidSquare {
try {
BufferedReader moveInput = new BufferedReader(new InputStreamReader(System.in)); System.out.println("Welcome to SuicideChess "+SuicideChess.NAME+"!");
Board bitboard = new Board(); if (!SuicideChess.ASCII_GAME) {
System.out.println("This game was not designed to be played in console. Please use it with XBoard, WinBoard or any compatible program.");
}
System.out.println();
if (ASCII_GAME) {
bitboard.display();
System.out.println("White: ");
}
ComputerPlayer computer = new ComputerPlayer(); BufferedReader moveInput = new BufferedReader(new InputStreamReader(System.in));
Board bitboard = new Board();
boolean playing = true; if (ASCII_GAME) {
bitboard.display();
System.out.println("White: ");
}
while (playing) { ComputerPlayer computer = new ComputerPlayer();
try { boolean computerPlaying = true; //the computer does not play in foce mode.
String whatMove= moveInput.readLine();
boolean playedALegalMove = false;
int xBoardCommand = XBoardProtocol.getCommand(whatMove); boolean playing = true;
switch (xBoardCommand) { while (playing) {
case XBoardProtocol.XBOARD: try {
break; String whatMove= moveInput.readLine();
case XBoardProtocol.PROTOVER: boolean playedALegalMove = false;
XBoardProtocol.initialise();
break; int xBoardCommand = XBoardProtocol.getCommand(whatMove);
case XBoardProtocol.NOT_ACCEPTED_SUICIDE:
System.out.println("tellusererror \"This game only plays suicide chess.\""); switch (xBoardCommand) {
playing=false; case XBoardProtocol.XBOARD:
break; break;
case XBoardProtocol.NOT_ACCEPTED_USERMOVE: case XBoardProtocol.PROTOVER:
System.out.println("tellusererror \"XBoard must send moves starting with 'usermove'\""); XBoardProtocol.initialise();
playing=false; break;
break; case XBoardProtocol.NOT_ACCEPTED_SUICIDE:
case XBoardProtocol.NOPROTOVER: System.out.println("tellusererror \"This game only plays suicide chess.\"");
System.out.println("tellusererror \"You must use an engine with XBoard protocol 2 or higher.\""); playing=false;
playing=false; break;
break; case XBoardProtocol.ACCEPTED_USERMOVE:
case XBoardProtocol.QUIT: acceptedUsermove=true;
System.out.println("Goodbye!"); break;
playing=false; case XBoardProtocol.NOPROTOVER:
break; System.out.println("tellusererror \"You must use an engine with XBoard protocol 2 or higher.\"");
case XBoardProtocol.NEW: playing=false;
System.out.println("variant suicide"); break;
break; case XBoardProtocol.QUIT:
case XBoardProtocol.UNKNOWN: System.out.println("Goodbye!");
playing=false;
break;
case XBoardProtocol.NEW:
System.out.println("variant suicide");
break;
case XBoardProtocol.HINT:
System.out.println("Hint: "+computer.doRandomMove(bitboard,currentPlayerColor));
break;
case XBoardProtocol.FORCE:
computerPlaying = false;
break;
case XBoardProtocol.UNKNOWN:
if (acceptedUsermove) {
System.out.println("Error (unknown command): "+whatMove); System.out.println("Error (unknown command): "+whatMove);
break; break;
case XBoardProtocol.MOVE: }
Move theMove = new Move(whatMove.substring(9), bitboard); //if XBoard did not accept usermove command we try and interpret every unknown command
//as a move.
case XBoardProtocol.MOVE:
Move theMove;
if (acceptedUsermove) {
theMove = new Move(whatMove.substring(9), bitboard);
} else {
theMove = new Move(whatMove, bitboard);
}
boolean needToCapture = false; boolean needToCapture = false;
int foundMoveIndex = -1; int foundMoveIndex = -1;
if(theMove.getMovingPiece().getColor() == currentPlayerColor) { if(theMove.getMovingPiece().getColor() == currentPlayerColor) {
Rules.legalMovesForPlayer(bitboard,currentPlayerColor); Rules.legalMovesForPlayer(bitboard,currentPlayerColor);
ArrayList<Move> allLegalMoves = Rules.getLegalMovesCapture(); ArrayList<Move> allLegalMoves = Rules.getLegalMovesCapture();
if (allLegalMoves.size()==0) { if (allLegalMoves.size()==0) {
allLegalMoves = Rules.getLegalMovesNonCapture(); allLegalMoves = Rules.getLegalMovesNonCapture();
} else {
needToCapture = true;
}
for (int moveIndex = 0; moveIndex < allLegalMoves.size(); moveIndex++) {
if (allLegalMoves.get(moveIndex).isSimpleEqual(theMove)) {
if(theMove.isPromotionMove()&&
theMove.getPromotionPiece().getPieceNumber()!=allLegalMoves.get(moveIndex).getPromotionPiece().getPieceNumber()) {
continue;
}
foundMoveIndex=moveIndex;
break;
}
}
if (foundMoveIndex == -1) {
if (needToCapture) {
if (ASCII_GAME)
System.out.println("Capturing is mandatory.");
}
System.out.println("Illegal move: "+theMove.toString());
} else {
bitboard.doMove(allLegalMoves.get(foundMoveIndex));
if (ASCII_GAME) {
allLegalMoves.get(foundMoveIndex).display();
bitboard.display();
}
playedALegalMove=true;
}
} else { } else {
if (ASCII_GAME) needToCapture = true;
System.out.println("Please play a piece of the right color."); }
for (int moveIndex = 0; moveIndex < allLegalMoves.size(); moveIndex++) {
if (allLegalMoves.get(moveIndex).isSimpleEqual(theMove)) {
if(theMove.isPromotionMove()&&
theMove.getPromotionPiece().getPieceNumber()!=allLegalMoves.get(moveIndex).getPromotionPiece().getPieceNumber()) {
continue;
}
foundMoveIndex=moveIndex;
break;
}
}
if (foundMoveIndex == -1) {
if (needToCapture) {
if (ASCII_GAME)
System.out.println("Capturing is mandatory.");
}
System.out.println("Illegal move: "+theMove.toString()); System.out.println("Illegal move: "+theMove.toString());
} else {
bitboard.doMove(allLegalMoves.get(foundMoveIndex));
if (ASCII_GAME) {
allLegalMoves.get(foundMoveIndex).display();
System.out.println("Board value: "+bitboard.getBoardValue());
bitboard.display();
}
playedALegalMove=true;
} }
} else {
if (ASCII_GAME)
System.out.println("Please play a piece of the right color.");
System.out.println("Illegal move: "+theMove.toString());
}
if (!playedALegalMove) { if (!playedALegalMove) {
break; break;
} }
changeCurrentPlayerColor(); changeCurrentPlayerColor();
//No break statement here on purpose. //No break statement here on purpose.
case XBoardProtocol.GO: case XBoardProtocol.GO:
Move computerMove = computer.doMove(bitboard,currentPlayerColor); //this is not really nice but it avoids having to write twice the code
//I check if I did really receive a XBoardProtocol.GO or it I just got there
//because there is no break statement.
if (xBoardCommand==XBoardProtocol.GO)
computerPlaying = true;
if (computerPlaying) {
Move computerMove = computer.doMinMaxMove(bitboard,currentPlayerColor);
bitboard.doMove(computerMove); bitboard.doMove(computerMove);
XBoardProtocol.doMove(computerMove); XBoardProtocol.doMove(computerMove);
if (ASCII_GAME) { if (ASCII_GAME) {
computerMove.display(); computerMove.display();
System.out.println("Board value: "+bitboard.getBoardValue());
bitboard.display(); bitboard.display();
} }
changeCurrentPlayerColor(); changeCurrentPlayerColor();
break;
} }
// if (whatMove.startsWith("hint")) {
// Rules rules = new Rules();
// rules.legalMovesForPlayer(bitboard,currentPlayerColor);
// ArrayList<Move> allLegalMoves = rules.getLegalMovesCapture();
// if (allLegalMoves.size()==0) {
// allLegalMoves = rules.getLegalMovesNonCapture();
// }
// for(int i = 0; i<allLegalMoves.size(); i++) {
// if(allLegalMoves.get(i).isPromotionMove()) {
// System.out.println(allLegalMoves.get(i).fromSquare().toString() +
// allLegalMoves.get(i).toSquare().toString() +
// allLegalMoves.get(i).getPromotionPiece().toString());
// } else {
// System.out.println(allLegalMoves.get(i).fromSquare().toString() +
// allLegalMoves.get(i).toSquare().toString());
// }
//
// }
// continue;
// }
// if (whatMove.startsWith("force")) {
// Move theMove = new Move(whatMove.substring(6,10), bitboard);
// theMove.display();
// bitboard.doMove(theMove);
// bitboard.display();
// continue;
// }
} catch (NotAValidMoveException err) {
System.out.println(err);
continue;
} catch (NotAValidSquare err) {
System.out.println(err);
continue;
} catch (Exception err) {
System.out.println(err);
break; break;
} }
// if (whatMove.startsWith("hint")) {
// Rules rules = new Rules();
// rules.legalMovesForPlayer(bitboard,currentPlayerColor);
// ArrayList<Move> allLegalMoves = rules.getLegalMovesCapture();
// if (allLegalMoves.size()==0) {
// allLegalMoves = rules.getLegalMovesNonCapture();
// }
// for(int i = 0; i<allLegalMoves.size(); i++) {
// if(allLegalMoves.get(i).isPromotionMove()) {
// System.out.println(allLegalMoves.get(i).fromSquare().toString() +
// allLegalMoves.get(i).toSquare().toString() +
// allLegalMoves.get(i).getPromotionPiece().toString());
// } else {
// System.out.println(allLegalMoves.get(i).fromSquare().toString() +
// allLegalMoves.get(i).toSquare().toString());
// }
//
// }
// continue;
// }
// if (whatMove.startsWith("force")) {
// Move theMove = new Move(whatMove.substring(6,10), bitboard);
// theMove.display();
// bitboard.doMove(theMove);
// bitboard.display();
// continue;
// }
} catch (NotAValidMoveException err) {
System.out.println(err);
continue;
} catch (NotAValidSquare err) {
System.out.println(err);
continue;
} catch (Exception err) {
System.out.println(err);
break;
} }
} catch (NotAValidSquare e) {
e.printStackTrace();
} }
} }

View File

@ -46,14 +46,22 @@ public class XBoardProtocol {
* XBoard tells the computer to play the current color * XBoard tells the computer to play the current color
*/ */
public static final int GO = 7; public static final int GO = 7;
/**
* XBoard user asks for a Hint
*/
public static final int HINT = 8;
/**
* XBoard asks for computer to be put in force mode.
*/
public static final int FORCE = 9;
/** /**
* XBoard did not accept sending moves with usermove * XBoard did not accept sending moves with usermove
*/ */
public static final int NOT_ACCEPTED_USERMOVE = 9; public static final int ACCEPTED_USERMOVE = 10;
/** /**
* XBoard did not accept variant suicide chess * XBoard did not accept variant suicide chess
*/ */
public static final int NOT_ACCEPTED_SUICIDE = 10; public static final int NOT_ACCEPTED_SUICIDE = 11;
/** /**
* Unknown command * Unknown command
*/ */
@ -66,12 +74,6 @@ public class XBoardProtocol {
*/ */
public static void initialise() throws IOException { public static void initialise() throws IOException {
//done=1 is here to tell that the program has finish requesting features //done=1 is here to tell that the program has finish requesting features
if (SuicideChess.ASCII_GAME) {
System.out.println("Welcome to SuicideChess "+SuicideChess.NAME+"!");
System.out.println("This game was not designed to be played in console. Please use it with XBoard, WinBoard or any compatible program.");
System.out.println();
}
System.out.println("");
System.out.println("feature myname=\"djib's Suicide Chess\""); System.out.println("feature myname=\"djib's Suicide Chess\"");
System.out.println("feature sigint=0 sigterm=0"); System.out.println("feature sigint=0 sigterm=0");
System.out.println("feature usermove=1"); //sends moves like 'usermove e2e4' System.out.println("feature usermove=1"); //sends moves like 'usermove e2e4'
@ -113,10 +115,14 @@ public class XBoardProtocol {
return MOVE; return MOVE;
} else if (command.equals("rejected variants")) { } else if (command.equals("rejected variants")) {
return NOT_ACCEPTED_SUICIDE; return NOT_ACCEPTED_SUICIDE;
} else if (command.equals("rejected usermove")) { } else if (command.equals("accepted usermove")) {
return NOT_ACCEPTED_USERMOVE; return ACCEPTED_USERMOVE;
} else if (command.equals("go")) { } else if (command.equals("go")) {
return GO; return GO;
} else if (command.equals("hint")) {
return HINT;
} else if (command.equals("force")) {
return FORCE;
} }
return UNKNOWN; return UNKNOWN;