package suicideChess; import java.util.ArrayList; import java.util.Date; import java.util.Random; import suicideChess.Board.NoPieceOnSquare; import suicideChess.Square.NotAValidSquare; /** * This class will contain all the AI. * @author Jean-Baptiste Hétier * @version $LastChangedRevision$, $LastChangedDate$ */ public class ComputerPlayer { //best move found private static Move bestMove = null; //to tell the number of nodes searched private static int nodesSearched; /** * This asks the computer to compute a move * @param bitboard The current status of the {@link Board} * @return move A {@link Move} * @throws NotAValidSquare * @see Board * @see Move */ public static Move doRandomMove(Board bitboard) throws NotAValidSquare { Random generator = new Random(); Rules.legalMovesForPlayer(bitboard); ArrayList allLegalMoves = Rules.getLegalMovesCapture(); if (allLegalMoves.size()==0) { allLegalMoves = Rules.getLegalMovesNonCapture(); } if (allLegalMoves.size()!=0) { return allLegalMoves.get(generator.nextInt(allLegalMoves.size())); } throw new RuntimeException("**Error** in doRandomMove"); } /** * Basic MinMax * @param bitboard The bitboard * @return The best Move found * @throws NotAValidSquare * @throws NoPieceOnSquare * @see Board * @see Move */ public static Move doMinMaxMove(Board bitboard) throws NotAValidSquare, NoPieceOnSquare { bestMove = null; nodesSearched = 0; int bestScore = MinMax(bitboard, 0); if (SuicideChess.postThinkingOutput()) { System.out.println(SuicideChess.getPlyDepth()+" "+bestScore*100+" 0 "+nodesSearched+" "+bestMove); } return bestMove; } private static int MinMax(Board bitboard, int currentDepth) throws NotAValidSquare, NoPieceOnSquare { nodesSearched++; if (currentDepth >= SuicideChess.getPlyDepth()) { return bitboard.getBoardValue(); } Rules.legalMovesForPlayer(bitboard); ArrayList allLegalMoves = Rules.getLegalMovesCapture(); if (allLegalMoves.size()==0) { allLegalMoves = Rules.getLegalMovesNonCapture(); } if (allLegalMoves.size()==0) { if (bitboard.getCurrentPlayer()==Piece.BLACK) { return Board.BLACK_WINS; } else { return Board.WHITE_WINS; } } else { ArrayList bestMoveIndex=new ArrayList(); int currentScore; int bestScoreSoFar; if (bitboard.getCurrentPlayer()==Piece.BLACK) { bestScoreSoFar=Board.WHITE_WINS+1; //any move even a WHITE_WINS will be better than that for (int i=0; i= bestScoreSoFar) { //white tries to maximise his score if (currentScore>bestScoreSoFar) { bestScoreSoFar = currentScore; bestMoveIndex.clear(); } bestMoveIndex.add(i); } } } //select one of the best moves randomly Random generator = new Random(); if (currentDepth == 0) { System.out.println(bestMoveIndex.size()); } bestMove = allLegalMoves.get(bestMoveIndex.get(generator.nextInt(bestMoveIndex.size()))); return bestScoreSoFar; } } /** * Alpha-Beta * @param bitboard The bitboard * @return The best Move found * @throws NotAValidSquare * @throws NoPieceOnSquare * @see Board * @see Move */ public static Move doAlphaBetaMove(Board bitboard) throws NotAValidSquare, NoPieceOnSquare { bestMove = null; nodesSearched = 0; thinkingBeginingTime = new Date(); //iterative deepening for(maxDepth=2; maxDepth<=SuicideChess.getPlyDepth(); maxDepth++) { ReturnWrapper bestScore = AlphaBeta(bitboard, 0, Board.MIN_VALUE, Board.MAX_VALUE); Date thinkingEndTime = new Date(); //select one of the best moves randomly Random generator = new Random(); //if (currentDepth == 0) { System.out.println(bestMoveIndex.size()); } bestMove = bestMoves.get(generator.nextInt(bestMoves.size())); if (SuicideChess.postThinkingOutput()) { System.out.println(maxDepth+"\t"+bestScore.getBranchValue()+ "\t"+((int)(thinkingEndTime.getTime()-thinkingBeginingTime.getTime())/10)+ //search time in centiseconds "\t"+nodesSearched+"\t"+bestScore.getPrincipalVariation()); } if((bitboard.getCurrentPlayer()==Piece.BLACK && bestScore.getBranchValue()==Board.BLACK_WINS) || (bitboard.getCurrentPlayer()==Piece.WHITE && bestScore.getBranchValue()==Board.WHITE_WINS)) { break; //no need to continue iterative deepening. } } if(SuicideChess.playInACSII()) { System.out.println("Found "+bestMoves.size()+" good moves."); } System.out.println(((bitboard.mobility(Piece.WHITE)))); System.out.println(((-bitboard.mobility(Piece.BLACK)))); return bestMove; } private static Date thinkingBeginingTime; private static int maxDepth; private static ArrayList bestMoves=new ArrayList(); //this class is used to return two arguments in the next function, the two arguments being //an integer representing the value of alpha or beta //an integer representing the real value of the branch (alpha-beta only give boundaries) private static class ReturnWrapper { private int alphaBeta; private int branchValue; private String principalVariation; public ReturnWrapper(int alphaBeta, int branchValue, String principalVariation) { this.alphaBeta = alphaBeta; this.branchValue = branchValue; this.principalVariation = principalVariation; } public int getAlphaBeta() {return this.alphaBeta;} public int getBranchValue() {return this.branchValue;} public String getPrincipalVariation() {return this.principalVariation;} }; 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 >= maxDepth) { //System.out.println("'-> Evaluate: "+bitboard.getBoardValue()); return new ReturnWrapper(bitboard.getBoardValue(),bitboard.getBoardValue(),""); } Rules.legalMovesForPlayer(bitboard); ArrayList allLegalMoves = Rules.getLegalMovesCapture(); if (allLegalMoves.size()==0) { allLegalMoves = Rules.getLegalMovesNonCapture(); } if (allLegalMoves.size()==0) { if (bitboard.getCurrentPlayer()==Piece.BLACK) { //System.out.println("'-> Evaluate *BLACK WINS*: "+Board.BLACK_WINS); return new ReturnWrapper(Board.BLACK_WINS,Board.BLACK_WINS,""); } else { //System.out.println("'-> Evaluate *WHITE WINS* : "+Board.WHITE_WINS); return new ReturnWrapper(Board.WHITE_WINS,Board.WHITE_WINS,""); } } else { int currentScore; int bestScoreSoFar; int currentAlphaBeta; String bestVariationSoFar=""; if (bitboard.getCurrentPlayer()==Piece.BLACK) { bestScoreSoFar=Board.MAX_VALUE; //black tries to minimise for (int i=0; i=alpha) { alpha = currentAlphaBeta; } //calculating branch value if (currentScore >= bestScoreSoFar) { if (currentScore > bestScoreSoFar) { bestVariationSoFar = allLegalMoves.get(i).toString()+" "+returnValue.getPrincipalVariation(); bestScoreSoFar=currentScore; if (currentDepth==0) { bestMoves.clear(); if (SuicideChess.postThinkingOutput()) { System.out.println(maxDepth+"\t"+returnValue.getBranchValue()+ "\t"+((int)((new Date()).getTime()-thinkingBeginingTime.getTime())/10)+ //search time in centiseconds "\t"+nodesSearched+"\t"+bestVariationSoFar); } if(bestScoreSoFar==Board.WHITE_WINS) { //found a win, no need to go further if(SuicideChess.playInACSII()) System.out.println("Found a win !"); bestMoves.add(allLegalMoves.get(i)); return new ReturnWrapper(alpha,bestScoreSoFar,bestVariationSoFar); } //System.out.println("*** Clear "); } } if(currentDepth==0) { bestMoves.add(allLegalMoves.get(i)); //System.out.println("*** Adding "+allLegalMoves.get(i)); } } if(alpha>beta) { //if(currentDepth!=SuicideChess.getPlyDepth()-1) System.out.println("Pruning "+Integer.toString(allLegalMoves.size()-i)+" alternatives at depth "+ currentDepth); return new ReturnWrapper(beta,bestScoreSoFar,bestVariationSoFar); //pruning } } return new ReturnWrapper(alpha,bestScoreSoFar,bestVariationSoFar); } } } }