From 1b500b44218f94fd4db9e79bdf8d48d2c48aa0e9 Mon Sep 17 00:00:00 2001 From: djib Date: Sat, 1 Jul 2006 15:06:24 +0000 Subject: [PATCH] Version 6.9 =========== Now detects draw (50 moves only) Displays time to find move Can read a library of problems from a file Each problem can be selected independently or all can be played in succession and then the result is displayed. --- src/suicideChess/Board.java | 70 +++++- src/suicideChess/ComputerPlayer.java | 23 +- src/suicideChess/Rules.java | 5 + src/suicideChess/SuicideChess.java | 343 +++++++++++++++----------- src/suicideChess/SuicideProblems.java | 89 +++++++ 5 files changed, 369 insertions(+), 161 deletions(-) create mode 100644 src/suicideChess/SuicideProblems.java diff --git a/src/suicideChess/Board.java b/src/suicideChess/Board.java index 46b2191..728225a 100644 --- a/src/suicideChess/Board.java +++ b/src/suicideChess/Board.java @@ -1,5 +1,6 @@ package suicideChess; + import suicideChess.Square.NotAValidSquare; /** @@ -32,8 +33,11 @@ public class Board { private static final int NB_OF_BITBOARDS = 14; //with less than that many pawns on one side, the computer will enter endgame mode - public static final int ENDGAME = 3; + public static final int ENDGAME_PAWNS = 3; + //with less than that many pieces on one side, the computer will enter endgame mode + public static final int ENDGAME_PIECES = 6; + @SuppressWarnings("serial") public class NoPieceOnSquare extends Exception { NoPieceOnSquare(String s) { super(s); }; @@ -61,9 +65,12 @@ public class Board { * Value representing the minimum value possible for the evaluation function */ public static final int MIN_VALUE = BLACK_WINS-1; - - + /** + * Value representing the value of a draw position + */ + public static final int DRAW_BOARD = 0; + /*======* * DATA * *======*/ @@ -94,6 +101,11 @@ public class Board { private int boardValue = 0; //evaluation of the board value private int currentPlayer; //color of the current player. + + //starts at 1 and increment after each of black's move + private int fullmoveNumber; + //this variable holds the number of Moves since last capture or last pawn move + private int halfmoveClock; /*=============* * CONSTRUCTOR * @@ -108,6 +120,9 @@ public class Board { //the following line makes sure that enPassantSquare is defined at some point. enPassantSquare = new Square("a1"); + fullmoveNumber = 1; + halfmoveClock = 0; + numberOfPieces = new int[Piece.MAX_PIECE_NUMBER+1]; for (int i=0; i<=Piece.MAX_PIECE_NUMBER; i++) { numberOfPieces[i]=0; @@ -163,6 +178,9 @@ public class Board { this.numberOfPieces[i] = bitboard.numberOfPieces[i]; } + this.fullmoveNumber = bitboard.fullmoveNumber; + this.halfmoveClock = bitboard.halfmoveClock; + this.boardValue = bitboard.boardValue; this.bitBoards = new long[NB_OF_BITBOARDS]; for (int i=0; i=Rules.NUMBER_OF_MOVES_BEFORE_DRAW) { + return true; + } + return false; + } + /** + * Used to get the halfmoveClock. + * @return the number of halfmoves since the last capture or pawn movement + */ + public int getHalfmoveClock() { + return halfmoveClock; + } + + /** * This function returns the current player color * @return Integer @@ -543,13 +592,14 @@ public class Board { //this is a very very basic evaluation function that will be changed. //boardValue = numberOfBlackPieces - numberOfWhitePieces; boardValue = 0; - if((numberOfPieces[Piece.BLACK_PAWN] <= ENDGAME) || (numberOfPieces[Piece.WHITE_PAWN] <= ENDGAME)) { - System.out.println("Playing endgame"); + if((numberOfPieces[Piece.BLACK_PAWN] <= ENDGAME_PAWNS) || (numberOfPieces[Piece.WHITE_PAWN] <= ENDGAME_PAWNS) + || (numberOfPieces[Piece.BLACK_PIECES] <= ENDGAME_PIECES) || (numberOfPieces[Piece.WHITE_PIECES] <= ENDGAME_PIECES) ) { + //System.out.println("Playing endgame"); for (int i = Piece.OFFSET; i<=Piece.MAX_PIECE_NUMBER; i++) { boardValue += numberOfPieces[i]*Piece.PIECE_VALUE_ENDGAME[i]; } } else { - System.out.println("Playing midgame"); + //System.out.println("Playing midgame"); for (int i = Piece.OFFSET; i<=Piece.MAX_PIECE_NUMBER; i++) { boardValue += numberOfPieces[i]*Piece.PIECE_VALUE_MIDDLEGAME[i]; } diff --git a/src/suicideChess/ComputerPlayer.java b/src/suicideChess/ComputerPlayer.java index daf1dfb..0df511c 100644 --- a/src/suicideChess/ComputerPlayer.java +++ b/src/suicideChess/ComputerPlayer.java @@ -1,6 +1,7 @@ package suicideChess; import java.util.ArrayList; +import java.util.Date; import java.util.Random; import suicideChess.Board.NoPieceOnSquare; @@ -138,9 +139,11 @@ public class ComputerPlayer { public static Move doAlphaBetaMove(Board bitboard) throws NotAValidSquare, NoPieceOnSquare { bestMove = null; nodesSearched = 0; - - ReturnWrapper bestScore = AlphaBeta(bitboard, 0, Board.BLACK_WINS-1, Board.WHITE_WINS+1); + Date thinkingBeginingTime = new Date(); + ReturnWrapper bestScore = AlphaBeta(bitboard, 0, Board.BLACK_WINS-1, Board.WHITE_WINS+1); + Date thinkingEndTime = new Date(); + //select one of the best moves randomly Random generator = new Random(); //if (currentDepth == 0) { System.out.println(bestMoveIndex.size()); } @@ -149,7 +152,9 @@ public class ComputerPlayer { System.out.println("Found "+bestMoves.size()+" good moves."); if (SuicideChess.postThinkingOutput()) { - System.out.println(SuicideChess.PLY_DEPTH+" "+bestScore.getAlphaBeta()*100+" 0 "+nodesSearched+" "+bestMove); + System.out.println(SuicideChess.PLY_DEPTH+" "+bestScore.getAlphaBeta()*100+ + " "+((int)(thinkingEndTime.getTime()-thinkingBeginingTime.getTime())/10)+ //search time in centiseconds + " "+nodesSearched+" "+bestMove); } return bestMove; @@ -172,6 +177,10 @@ public class ComputerPlayer { }; private static ReturnWrapper AlphaBeta(Board bitboard, int currentDepth, int alpha, int beta) throws NotAValidSquare, NoPieceOnSquare { nodesSearched++; + + if(bitboard.isADraw()) { + return new ReturnWrapper(Board.DRAW_BOARD,Board.DRAW_BOARD); + } if (currentDepth >= SuicideChess.PLY_DEPTH) { //System.out.println("'-> Evaluate: "+bitboard.getBoardValue()); return new ReturnWrapper(bitboard.getBoardValue(),bitboard.getBoardValue()); @@ -202,7 +211,7 @@ public class ComputerPlayer { //System.out.println("Analysing "+currentDepth+":"+allLegalMoves.get(i)); - ReturnWrapper returnValue = AlphaBeta(boardCopy,currentDepth+1,alpha,beta); + ReturnWrapper returnValue = AlphaBeta(boardCopy,currentDepth+1,Board.MIN_VALUE,beta); currentScore = returnValue.getBranchValue(); currentAlphaBeta = returnValue.getAlphaBeta(); @@ -229,7 +238,7 @@ public class ComputerPlayer { } if(betabeta) { - //System.out.println("Pruning "+Integer.toString(allLegalMoves.size()-i)+" alternatives at depth "+ currentDepth); + //if(currentDepth!=SuicideChess.PLY_DEPTH-1) System.out.println("Pruning "+Integer.toString(allLegalMoves.size()-i)+" alternatives at depth "+ currentDepth); return new ReturnWrapper(beta,bestScoreSoFar); //pruning } } diff --git a/src/suicideChess/Rules.java b/src/suicideChess/Rules.java index 3afa290..4cd56dc 100644 --- a/src/suicideChess/Rules.java +++ b/src/suicideChess/Rules.java @@ -12,6 +12,11 @@ import suicideChess.Square.NotAValidSquare; */ public class Rules { + + /** + * Number of moves without a piece being capture or a pawn being moved before the game becomes a draw + */ + public static int NUMBER_OF_MOVES_BEFORE_DRAW=50; private static ArrayList legalMovesNonCapture; private static ArrayList legalMovesCapture; diff --git a/src/suicideChess/SuicideChess.java b/src/suicideChess/SuicideChess.java index 5f89682..515f86f 100644 --- a/src/suicideChess/SuicideChess.java +++ b/src/suicideChess/SuicideChess.java @@ -5,8 +5,11 @@ import java.io.InputStreamReader; import java.util.ArrayList; +import suicideChess.Board.NoPieceOnSquare; +import suicideChess.Board.UnableToParseFENStringException; import suicideChess.Move.NotAValidMoveException; import suicideChess.Square.NotAValidSquare; +import suicideChess.SuicideProblems.NoSuchSuicideProblem; /** * Main file (the game in itself) @@ -33,7 +36,7 @@ public class SuicideChess { /** * The name to be displayed */ - public static final String NAME = "djib's SuShi v0.5.5"; + public static final String NAME = "djib's SuShi v0.6.9"; /** * Displays informations in the console. @@ -51,13 +54,16 @@ public class SuicideChess { * @return True or False */ - private static boolean testWinningPosition (Board bitboard) { + private static boolean testAndDisplayIfWinningOrDrawPosition (Board bitboard) { if (bitboard.getBoardValue()==Board.BLACK_WINS) { System.out.println("0-1 {Black mates}"); return true; } else if (bitboard.getBoardValue()==Board.WHITE_WINS) { System.out.println("1-0 {White mates}"); return true; + } else if (bitboard.isADraw()) { + System.out.println("1/2-1/2 {Draw}"); + return true; } return false; } @@ -134,164 +140,184 @@ public class SuicideChess { try { String whatMove= moveInput.readLine(); boolean playedALegalMove = false; - - int xBoardCommand = XBoardProtocol.getCommand(whatMove); - switch (xBoardCommand) { - case XBoardProtocol.XBOARD: - break; - case XBoardProtocol.PROTOVER: - XBoardProtocol.initialise(); - break; - case XBoardProtocol.NOT_ACCEPTED_SUICIDE: - System.out.println("tellusererror \"This game only plays suicide chess.\""); - playing=false; - break; - case XBoardProtocol.ACCEPTED_USERMOVE: - acceptedUsermove=true; - break; - case XBoardProtocol.NOPROTOVER: - System.out.println("tellusererror \"You must use an engine with XBoard protocol 2 or higher.\""); - playing=false; - break; - case XBoardProtocol.QUIT: - System.out.println("Goodbye!"); - playing=false; - break; - case XBoardProtocol.NEW: - //System.out.println("variant suicide"); - break; - case XBoardProtocol.HINT: - System.out.println("Hint: "+ComputerPlayer.doRandomMove(bitboard)); - break; - case XBoardProtocol.FORCE: - computerPlaying = false; - break; - case XBoardProtocol.PING: - System.out.println("pong "+whatMove.substring(5)); - break; - case XBoardProtocol.REMOVE: - removePlayedPosition(); - //no break here - case XBoardProtocol.UNDO: - bitboard=new Board(removePlayedPosition()); - if (ASCII_GAME) { - bitboard.display(); - } - break; - case XBoardProtocol.POST: - post=true; - break; - case XBoardProtocol.NOPOST: - post=false; - break; - case XBoardProtocol.SETBOARD: - bitboard=new Board(whatMove.substring(9)); - if(ASCII_GAME) - bitboard.display(); - break; - case XBoardProtocol.UNKNOWN: - if (acceptedUsermove) { - System.out.println("Error (unknown command): "+whatMove); - break; - } - //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); + if (whatMove.startsWith("problem")) { //special case for problems + if(whatMove.length()==7) { + System.out.println("There are "+SuicideProblems.numberOfProblems()+" problems."); + } else if (whatMove.substring(8).equals("auto")) { + autoProblem(); + } else if ((whatMove.length()>=12)&&(whatMove.substring(8,12).equals("load"))) { + SuicideProblems.suicideProblemsLoad(whatMove.substring(13)); } else { - theMove = new Move(whatMove, bitboard); + try { + int problemNb = Integer.parseInt(whatMove.substring(8)); + bitboard=new Board(SuicideProblems.getProblemNumber(problemNb)); + if(ASCII_GAME) + bitboard.display(); + } catch (NumberFormatException e) { + System.out.println("Not a valid number: "+ whatMove.substring(8)); + } } + } else { + int xBoardCommand = XBoardProtocol.getCommand(whatMove); - if (testWinningPosition(bitboard)) { - //if board was set in an illegal position - System.out.println("Illegal move: "+theMove.toString()); + switch (xBoardCommand) { + case XBoardProtocol.XBOARD: break; - } - - - boolean needToCapture = false; - int foundMoveIndex = -1; - if(theMove.getMovingPiece().getColor() == bitboard.getCurrentPlayer()) { - Rules.legalMovesForPlayer(bitboard); - ArrayList allLegalMoves = Rules.getLegalMovesCapture(); - if (allLegalMoves.size()==0) { - allLegalMoves = Rules.getLegalMovesNonCapture(); + case XBoardProtocol.PROTOVER: + XBoardProtocol.initialise(); + break; + case XBoardProtocol.NOT_ACCEPTED_SUICIDE: + System.out.println("tellusererror \"This game only plays suicide chess.\""); + playing=false; + break; + case XBoardProtocol.ACCEPTED_USERMOVE: + acceptedUsermove=true; + break; + case XBoardProtocol.NOPROTOVER: + System.out.println("tellusererror \"You must use an engine with XBoard protocol 2 or higher.\""); + playing=false; + break; + case XBoardProtocol.QUIT: + System.out.println("Goodbye!"); + playing=false; + break; + case XBoardProtocol.NEW: + //System.out.println("variant suicide"); + break; + case XBoardProtocol.HINT: + System.out.println("Hint: "+ComputerPlayer.doRandomMove(bitboard)); + break; + case XBoardProtocol.FORCE: + computerPlaying = false; + break; + case XBoardProtocol.PING: + System.out.println("pong "+whatMove.substring(5)); + break; + case XBoardProtocol.REMOVE: + removePlayedPosition(); + //no break here + case XBoardProtocol.UNDO: + bitboard=new Board(removePlayedPosition()); + if (ASCII_GAME) { + bitboard.display(); + } + break; + case XBoardProtocol.POST: + post=true; + break; + case XBoardProtocol.NOPOST: + post=false; + break; + case XBoardProtocol.SETBOARD: + bitboard=new Board(whatMove.substring(9)); + if(ASCII_GAME) + bitboard.display(); + break; + case XBoardProtocol.UNKNOWN: + if (acceptedUsermove) { + System.out.println("Error (unknown command): "+whatMove); + break; + } + //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 { - needToCapture = true; + theMove = new Move(whatMove, bitboard); } - 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."); - } + + if (testAndDisplayIfWinningOrDrawPosition(bitboard)) { + //if board was set in an illegal position System.out.println("Illegal move: "+theMove.toString()); + break; + } + + + boolean needToCapture = false; + int foundMoveIndex = -1; + if(theMove.getMovingPiece().getColor() == bitboard.getCurrentPlayer()) { + Rules.legalMovesForPlayer(bitboard); + ArrayList allLegalMoves = Rules.getLegalMovesCapture(); + if (allLegalMoves.size()==0) { + 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)); + addPlayedPosition(bitboard); + if (ASCII_GAME) { + allLegalMoves.get(foundMoveIndex).display(); + System.out.println("Board value: "+bitboard.getBoardValue()); + bitboard.display(); + } + playedALegalMove=true; + } } else { - bitboard.doMove(allLegalMoves.get(foundMoveIndex)); - addPlayedPosition(bitboard); + if (ASCII_GAME) + System.out.println("Please play a piece of the right color."); + System.out.println("Illegal move: "+theMove.toString()); + } + + if (testAndDisplayIfWinningOrDrawPosition(bitboard)) { + computerPlaying=false; + } + + if (!playedALegalMove) { + break; + } + //No break statement here on purpose. + case XBoardProtocol.GO: + //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 (testAndDisplayIfWinningOrDrawPosition(bitboard)) { + //in case an illegal position have been sent + computerPlaying=false; + break; + } + + if (computerPlaying) { + Move computerMove = ComputerPlayer.doAlphaBetaMove(bitboard); + bitboard.doMove(computerMove); + addPlayedPosition(bitboard); + XBoardProtocol.doMove(computerMove); if (ASCII_GAME) { - allLegalMoves.get(foundMoveIndex).display(); + computerMove.display(); System.out.println("Board value: "+bitboard.getBoardValue()); bitboard.display(); } - playedALegalMove=true; + } + + if (testAndDisplayIfWinningOrDrawPosition(bitboard)) { + computerPlaying=false; } - } else { - if (ASCII_GAME) - System.out.println("Please play a piece of the right color."); - System.out.println("Illegal move: "+theMove.toString()); - } - - if (testWinningPosition(bitboard)) { - computerPlaying=false; - } - - if (!playedALegalMove) { + break; } - //No break statement here on purpose. - case XBoardProtocol.GO: - //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 (testWinningPosition(bitboard)) { - //in case an illegal position have been sent - computerPlaying=false; - break; - } - - if (computerPlaying) { - Move computerMove = ComputerPlayer.doAlphaBetaMove(bitboard); - bitboard.doMove(computerMove); - addPlayedPosition(bitboard); - XBoardProtocol.doMove(computerMove); - if (ASCII_GAME) { - computerMove.display(); - System.out.println("Board value: "+bitboard.getBoardValue()); - bitboard.display(); - } - } - if (testWinningPosition(bitboard)) { - computerPlaying=false; - } - - break; } // if (whatMove.startsWith("hint")) { @@ -330,10 +356,39 @@ public class SuicideChess { } catch (NotAValidSquare err) { System.out.println(err); continue; + } catch (NoSuchSuicideProblem err) { + System.out.println(err); + continue; } catch (Exception e) { e.printStackTrace(); break; } } - } + } + + //lets the computer try every problem and stop on error. + //in the end it says what games where lost by white. + private static void autoProblem() throws NotAValidSquare, UnableToParseFENStringException, NoPieceOnSquare, NoSuchSuicideProblem { + Board bitboard; + boolean[] result=new boolean[SuicideProblems.numberOfProblems()]; + for(int i=1; i<=SuicideProblems.numberOfProblems(); i++) { + System.out.println("\n\nProblem "+i); + bitboard=new Board(SuicideProblems.getProblemNumber(i)); + while(!testAndDisplayIfWinningOrDrawPosition(bitboard)) { + Move computerMove = ComputerPlayer.doAlphaBetaMove(bitboard); + bitboard.doMove(computerMove); + } + if (bitboard.getCurrentPlayer()==Piece.BLACK) { + result[i-1]=false; + } else { + result[i-1]=true; + } + } + System.out.println("\n\nLost following games:\n========begin========"); + for(int i=1; i<=SuicideProblems.numberOfProblems(); i++) { + if (!result[i-1]) + System.out.println("Problem "+i+" lost !"); + } + System.out.println("=========end========="); + } } \ No newline at end of file diff --git a/src/suicideChess/SuicideProblems.java b/src/suicideChess/SuicideProblems.java new file mode 100644 index 0000000..6bb1746 --- /dev/null +++ b/src/suicideChess/SuicideProblems.java @@ -0,0 +1,89 @@ +package suicideChess; + +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; + +public class SuicideProblems { + /** + * This class is used to load problems from a file + * + * @author Jean-Baptiste Hétier + * @version $LastChangedRevision$, $LastChangedDate$ + * + */ + + @SuppressWarnings("serial") + public static class NoSuchSuicideProblem extends Exception { + NoSuchSuicideProblem(String s) { super(s); }; + } + + + //This array can hold setup for different problems. + private static String[] suicideProblems; + static { suicideProblemsLoad("problems"); } //initialise with file 'problems' if found in current directory + + /** + * How many problems are loaded + */ + public static int numberOfProblems() { + if (suicideProblems[0].equals("")) { + return 0; + } else { + return suicideProblems.length; + } + } + + /** + * Return FEN notation string for given problem number + * @param problemNb + * @throws NoSuchSuicideProblem + */ + public static String getProblemNumber(int problemNb) throws NoSuchSuicideProblem { + if ((problemNb > numberOfProblems())||(problemNb<1)) { + throw new NoSuchSuicideProblem("*** Invalid Entry: "+problemNb+" ***"); + } + return suicideProblems[problemNb-1]; + } + + /** + * This function is used to load other problem files + * @param file + */ + public static void suicideProblemsLoad(String file) { + StringBuffer problemBuffer = new StringBuffer(); + //declared here only to make visible to finally clause + BufferedReader problemReader = null; + try { + problemReader = new BufferedReader(new FileReader(file)); + String line = null; //not declared within while loop + while ((line = problemReader.readLine()) != null) { + if (!line.startsWith("#")) { //ignore lines starting with # (comments) + problemBuffer.append(line); + problemBuffer.append("\n"); //I will then use this symbol to break the buffer in an array + } + } + } + catch (FileNotFoundException e) { + System.out.println("File '"+file+"' not found. Problems won't be available."); + } + catch (IOException e){ + System.out.println("Error reading file '"+file+"'."); + } + finally { + try { + if (problemReader!= null) { + problemReader.close(); + } + } + catch (IOException ex) { + ex.printStackTrace(); + } + } + suicideProblems = problemBuffer.toString().split("\n"); + System.out.println(numberOfProblems()+" loaded"); + } + + +}