595 lines
26 KiB
Java
595 lines
26 KiB
Java
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<Board> allPlayedPositions = new ArrayList<Board>();
|
|
|
|
/**
|
|
* 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<Move> 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<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 (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, NotAValidMoveException {
|
|
Board bitboard;
|
|
int[] result=new int[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]=Piece.BLACK;
|
|
} else if(bitboard.isADraw()){
|
|
result[i-1]=Piece.NONE;
|
|
} else {
|
|
result[i-1]=Piece.WHITE;
|
|
}
|
|
|
|
}
|
|
System.out.println("\n\nLost following games:\n========begin========");
|
|
int stats=SuicideProblems.numberOfProblems();
|
|
for(int i=1; i<=SuicideProblems.numberOfProblems(); i++) {
|
|
if (result[i-1]==Piece.BLACK) {
|
|
System.out.println("Problem "+i+" lost !");
|
|
stats--;
|
|
} else if (result[i-1]==Piece.NONE) {
|
|
System.out.println("Problem "+i+" drawn !");
|
|
stats--;
|
|
}
|
|
}
|
|
System.out.println("---------------------\n"+stats+" problems solved out of "+SuicideProblems.numberOfProblems());
|
|
System.out.println("=========end=========");
|
|
}
|
|
} |