333 lines
15 KiB
Java
333 lines
15 KiB
Java
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<Move> 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<Move> 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<Integer> bestMoveIndex=new ArrayList<Integer>();
|
||
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<allLegalMoves.size(); i++) {
|
||
Board boardCopy = new Board(bitboard);
|
||
boardCopy.doMove(allLegalMoves.get(i));
|
||
currentScore=MinMax(boardCopy,currentDepth+1);
|
||
if (currentScore <= bestScoreSoFar) { //black tries to minimise his score
|
||
if (currentScore<bestScoreSoFar) {
|
||
bestScoreSoFar = currentScore;
|
||
bestMoveIndex.clear();
|
||
}
|
||
bestMoveIndex.add(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,currentDepth+1);
|
||
if (currentScore >= 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<Move> bestMoves=new ArrayList<Move>();
|
||
//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<Move> 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<allLegalMoves.size(); i++) {
|
||
Board boardCopy = new Board(bitboard);
|
||
boardCopy.doMove(allLegalMoves.get(i));
|
||
|
||
//System.out.println("Analysing "+currentDepth+":"+allLegalMoves.get(i));
|
||
|
||
ReturnWrapper returnValue = AlphaBeta(boardCopy,currentDepth+1,Board.MIN_VALUE,beta);
|
||
currentScore = returnValue.getBranchValue();
|
||
currentAlphaBeta = returnValue.getAlphaBeta();
|
||
|
||
//System.out.println("| CurrentScore, BestScore:" + currentScore + ", " + bestScoreSoFar);
|
||
//System.out.println("| CurrentBeta, beta:" + currentAlphaBeta + ", " + beta);
|
||
|
||
//calculating new value of beta
|
||
if (currentAlphaBeta<=beta) {
|
||
beta = currentAlphaBeta;
|
||
}
|
||
//calculating branch value
|
||
if (currentScore <= bestScoreSoFar) {
|
||
if (currentScore < bestScoreSoFar) {
|
||
bestScoreSoFar=currentScore;
|
||
bestVariationSoFar = allLegalMoves.get(i).toString()+" "+returnValue.getPrincipalVariation();
|
||
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);
|
||
}
|
||
//System.out.println("*** Clear ");
|
||
if(bestScoreSoFar==Board.BLACK_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(beta,bestScoreSoFar,bestVariationSoFar);
|
||
}
|
||
}
|
||
}
|
||
if(currentDepth==0) {
|
||
bestMoves.add(allLegalMoves.get(i));
|
||
//System.out.println("*** Adding "+allLegalMoves.get(i));
|
||
}
|
||
}
|
||
|
||
if(beta<alpha) {
|
||
//if(currentDepth!=SuicideChess.getPlyDepth()-1) System.out.println("Pruning "+Integer.toString(allLegalMoves.size()-i)+" alternatives at depth "+ currentDepth);
|
||
return new ReturnWrapper(alpha,bestScoreSoFar,bestVariationSoFar); //pruning
|
||
}
|
||
}
|
||
return new ReturnWrapper(beta,bestScoreSoFar,bestVariationSoFar);
|
||
} else { //white piece
|
||
bestScoreSoFar=Board.MIN_VALUE; //white tries to maximise
|
||
for (int i=0; i<allLegalMoves.size(); i++) {
|
||
Board boardCopy = new Board(bitboard);
|
||
boardCopy.doMove(allLegalMoves.get(i));
|
||
|
||
//System.out.println("Analysing "+currentDepth+":"+allLegalMoves.get(i));
|
||
|
||
ReturnWrapper returnValue = AlphaBeta(boardCopy,currentDepth+1,alpha,Board.MAX_VALUE);
|
||
currentScore = returnValue.getBranchValue();
|
||
currentAlphaBeta = returnValue.getAlphaBeta();
|
||
|
||
//System.out.println("| CurrentScore, BestScore:" + currentScore + ", " + bestScoreSoFar);
|
||
//System.out.println("| CurrentAlpha, alpha:" + currentAlphaBeta + ", " + alpha);
|
||
|
||
//calculating new value of alpha
|
||
if (currentAlphaBeta>=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);
|
||
}
|
||
}
|
||
}
|
||
|
||
} |