package suicideChess; import java.io.BufferedReader; import java.io.InputStreamReader; import java.util.ArrayList; import suicideChess.Board.NoPieceOnSquare; import suicideChess.Board.UnableToParseFENStringException; import suicideChess.Move.NotAValidMoveException; import suicideChess.OpeningBook.NoOpeningMovesLeft; import suicideChess.Square.NotAValidSquare; import suicideChess.SuicideProblems.NoSuchSuicideProblem; /** * Main file (the game in itself) * @author Jean-Baptiste Hétier * @version $LastChangedRevision$, $LastChangedDate$ */ public class SuicideChess { /* * Those flags are used to perform extra checks during the debugging of the * program. They may be safely all set to false once the program is stable. * It should improve performance a lot. */ /** * does BitBoard.class removePiece function checks if removing piece is legal ? */ public static final boolean BITBOARD_REMOVEPIECE_CHECK_REMOVE = false; /** * does Square.class checks if the strings are valid (is "z9" a valid square ?) */ public static final boolean SQUARE_CHECK_INVALID = false; /** * Use mobility in evaluation function (slows the program down a lot) */ public static final boolean USE_MOBILITY = false; /** * do move ordering in Alpha-Beta pruning ? */ public static final boolean MOVE_ORDERING = false; /** * Minimum ply to search to (when doing iterative deepening) */ public static final int MIN_PLY_DEPTH = 2; /** * Number of Plies the computes searches to */ private static int plyDepth = 4; /** * What is the current ply depth * @return integer */ public static int getPlyDepth() { return plyDepth; } /** * Quiescence search -> don't evaluate if captures are possible. */ public static final boolean QUIESCENCE_SEARCH = true; /** * Quiescence limit (ie. if more than that many possibilities of capturing, don't analyse further. */ public static final int QUIESCENCE_LIMIT = 4; /** * Maximum number of Plies the computer will ever go to */ public static final int MAX_QUIESCENCE_DEPTH = 8; /** * Adaptative depth -> ie: when only one possible move, don't search. When very few moves, add one to depth. */ public static final boolean ADAPTATIVE_DEPTH = false; /** * Adaptative branchin limit */ public static final int ADAPTATIVE_BRANCHING_LIMIT = 2; ///** // * Killer size (nb of killer moves remembered) // */ //public static final int KILLER_SIZE = 10; /** * Try the primary variation from the earliest iteration first */ public static final boolean PRINCIPAL_VARIATION_FIRST = true; /** * The name to be displayed */ public static final String NAME = "djib's SuShi v1.0.6"; /** * Displays informations in the console. */ private static boolean asciiGame = true; /** * Should the game be played in acsii * @return boolean */ public static boolean playInACSII() { return asciiGame; } /** * Test and display if the board is in a winning state. * @param bitboard A Board * @return True or False */ 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; } /** * If feature usermove has not been accepted by XBoard then consider all unknown commands * as moves */ private static boolean acceptedUsermove = false; /** * This array is used for undo purposes. Positions are sorted from last to first. */ private static ArrayList allPlayedPositions = new ArrayList(); /** * This function is used to add a new position to the list of all positions * @param position A {@link Board} * @see Board */ private static void addPlayedPosition (Board position) { allPlayedPositions.add(0,new Board(position)); } /** * This function is used to undo the last position played */ private static Board removePlayedPosition () { if(allPlayedPositions.size()>1) allPlayedPositions.remove(0); return allPlayedPositions.get(0); } /** * This variable says whether the computer should display a "thinking output" */ private static boolean postThinkingOutput = true; /** * Should computer display thinking output or not ? * @return boolean */ public static boolean postThinkingOutput() { return postThinkingOutput; } private static void displayPlayer(Board bitboard) { if(bitboard.getCurrentPlayer()==Piece.BLACK) { System.out.println("-> Black: "); } else { System.out.println("-> White: "); } //bitboard.debug(); } //this variable is used to decide whether or not the computer should use it's opening book //private static boolean playingWithOpeningBook = true; //this variable tells if the game is in the opening phase. private static boolean openingPhase = true; /** * The main function * @param args No parameters should be transmitted to this function. * @throws NotAValidSquare */ public static void main(String[] args) throws NotAValidSquare { System.out.println("Welcome to SuicideChess "+SuicideChess.NAME+"!\n"); System.out.println("Type 'help' for a quick help."); System.out.println("If you want a graphical interface, you can use XBoard, WinBoard or any compatible program."); System.out.println(); OpeningBook.load(); SuicideProblems.load(); ConfigFile.load(); System.out.println(); BufferedReader moveInput = new BufferedReader(new InputStreamReader(System.in)); Board bitboard = new Board(); addPlayedPosition(bitboard); bitboard.display(); System.out.println(); displayPlayer(bitboard); boolean computerPlaying = true; //the computer does not play in foce mode. boolean playing = true; while (playing) { try { String whatMove= moveInput.readLine(); boolean playedALegalMove = false; //System.out.println("Got message from xboard: "+whatMove); if (whatMove.startsWith("help")) { System.out.println("==================== Quick help ====================\nEnter moves in SAN notation : e2e3, e7e8r, ...\n"); System.out.println("new\t\t\tcreates a new game"); System.out.println("quit\t\t\tquits the game"); System.out.println("go\t\t\twill let the computer play the current player"); System.out.println("hint\t\t\treturns a legal move for current position"); System.out.println("undo\t\t\tundoes a half move (one side)"); System.out.println("remove\t\t\tundoes a full move"); System.out.println("force\t\t\tthe computer will check moves but not play"); System.out.println(); System.out.println("board\t\t\tdisplays the current status of the board"); System.out.println("eval\t\t\tevaluates the current board position"); System.out.println("setboard FEN\t\tsets the board according to the FEN position"); System.out.println("sd N\t\t\tsets the search depth to n"); System.out.println("bk\t\t\tdisplays available openbook moves for current position"); System.out.println("post\t\t\tdisplays thinking output"); System.out.println("nopost\t\t\tdo not display thinking output"); System.out.println(); System.out.println("problem\t\t\tdisplays the number of available problems"); System.out.println("problem auto\t\tlets the computer play automatically on each problem"); System.out.println("problem N\t\tloads problem number N"); System.out.println("problem load FILENAME\tloads the problem file FILENAME"); System.out.println("config\t\t\t displays current configuration"); System.out.println("config FILENAME\t\tloads a configuration from FILENAME"); System.out.println(); }else 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 { openingPhase=false; try { int problemNb = Integer.parseInt(whatMove.substring(8)); bitboard=new Board(SuicideProblems.getProblemNumber(problemNb)); if(asciiGame) { bitboard.display(); displayPlayer(bitboard); } addPlayedPosition(bitboard); } catch (NumberFormatException e) { System.out.println("Not a valid number: "+ whatMove.substring(8)); } } } else if (whatMove.startsWith("config")) { //special case for loading other configuration files if(whatMove.length()==6) { ConfigFile.display(); } else if ((whatMove.length()>6)) { ConfigFile.loadFile(whatMove.substring(7)); } } else if (whatMove.startsWith("asciiplay")) { asciiGame=true; bitboard.display(); displayPlayer(bitboard); } else if (whatMove.startsWith("asciiplay")) { asciiGame=false; } else if ((whatMove.startsWith("board"))) { bitboard.display(); } else if ((whatMove.startsWith("eval"))) { System.out.println(bitboard.getBoardValue()); }else { int xBoardCommand = XBoardProtocol.getCommand(whatMove); switch (xBoardCommand) { case XBoardProtocol.XBOARD: asciiGame=false; break; case XBoardProtocol.ACCEPTED: break; case XBoardProtocol.VARIANT_SUICIDE: 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: bitboard=new Board(); addPlayedPosition(bitboard); openingPhase = true; OpeningBook.reset(); computerPlaying = true; //the computer does not play in foce mode. if(playInACSII()) { bitboard.display(); displayPlayer(bitboard); } //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 (asciiGame) { bitboard.display(); displayPlayer(bitboard); } openingPhase = false; //due to the way I implemented the opening book break; case XBoardProtocol.POST: postThinkingOutput=true; break; case XBoardProtocol.NOPOST: postThinkingOutput=false; break; case XBoardProtocol.SETBOARD: bitboard=new Board(whatMove.substring(9)); addPlayedPosition(bitboard); openingPhase = false; if(asciiGame) bitboard.display(); displayPlayer(bitboard); break; case XBoardProtocol.SETPLY: try{ plyDepth = Integer.parseInt(whatMove.substring(3)); } catch (NumberFormatException e) { System.out.println("Not a valid depth: "+ whatMove.substring(3)); } break; case XBoardProtocol.BOOK: //display book moves boolean temp = postThinkingOutput; postThinkingOutput=true; try { OpeningBook.getMove(bitboard); } catch (NoOpeningMovesLeft e) { openingPhase = false; } postThinkingOutput=temp; 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 { theMove = new Move(whatMove, bitboard); } //theMove.display(); 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).isSimpleEqualTo(theMove)) { if(theMove.isPromotionMove()&& theMove.getPromotionPiece().getPieceNumber()!=allLegalMoves.get(moveIndex).getPromotionPiece().getPieceNumber()) { continue; } foundMoveIndex=moveIndex; break; } } if (foundMoveIndex == -1) { if (needToCapture) { if (asciiGame) System.out.println("Capturing is mandatory."); } //bitboard.debug(); //for (int moveIndex = 0; moveIndex < allLegalMoves.size(); moveIndex++) // System.out.println(allLegalMoves.get(moveIndex)); System.out.println("Illegal move: "+theMove.toString()); } else { bitboard.doMove(allLegalMoves.get(foundMoveIndex)); if(openingPhase) OpeningBook.played(allLegalMoves.get(foundMoveIndex)); addPlayedPosition(bitboard); if (asciiGame) { //allLegalMoves.get(foundMoveIndex).display(); //System.out.println("Board value: "+bitboard.getBoardValue()); bitboard.display(); displayPlayer(bitboard); } playedALegalMove=true; } } else { if (asciiGame) 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; if(openingPhase) { try { computerMove = OpeningBook.getMove(bitboard); OpeningBook.played(computerMove); } catch (NoOpeningMovesLeft e) { openingPhase = false; computerMove = ComputerPlayer.doAlphaBetaMove(bitboard); } } else { computerMove = ComputerPlayer.doAlphaBetaMove(bitboard); } bitboard.doMove(computerMove); addPlayedPosition(bitboard); XBoardProtocol.doMove(computerMove); if (asciiGame) { //computerMove.display(); //System.out.println("Board value: "+bitboard.getBoardValue()); bitboard.display(); displayPlayer(bitboard); } //computerMove.display(); //bitboard.debug(); } if (testAndDisplayIfWinningOrDrawPosition(bitboard)) { computerPlaying=false; } break; } } // if (whatMove.startsWith("hint")) { // Rules rules = new Rules(); // rules.legalMovesForPlayer(bitboard,currentPlayerColor); // ArrayList allLegalMoves = rules.getLegalMovesCapture(); // if (allLegalMoves.size()==0) { // allLegalMoves = rules.getLegalMovesNonCapture(); // } // for(int i = 0; i