/*
CuckooChess - A java chess program.
Copyright (C) 2011 Peter Ă–sterlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package chess;
import java.security.SecureRandom;
import java.util.List;
import java.util.Random;
/** A computer algorithm player. */
public class ComputerPlayer implements Player {
public static final String engineName;
static {
String name = "CuckooChess 1.13a9";
try {
String m = System.getProperty("sun.arch.data.model");
if ("32".equals(m))
name += " 32-bit";
else if ("64".equals(m))
name += " 64-bit";
} catch (SecurityException ex) {
// getProperty not allowed in applets
}
engineName = name;
}
int minTimeMillis;
int maxTimeMillis;
int maxDepth;
int maxNodes;
public boolean verbose;
TranspositionTable tt;
Book book;
boolean bookEnabled;
boolean randomMode;
Search currentSearch;
public ComputerPlayer() {
minTimeMillis = 10000;
maxTimeMillis = 10000;
maxDepth = 100;
maxNodes = -1;
verbose = true;
setTTLogSize(15);
book = new Book(verbose);
bookEnabled = true;
randomMode = false;
}
public void setTTLogSize(int logSize) {
tt = new TranspositionTable(logSize);
}
Search.Listener listener;
public void setListener(Search.Listener listener) {
this.listener = listener;
}
@Override
public String getCommand(Position pos, boolean drawOffer, List<Position> history) {
// Create a search object
long[] posHashList = new long[200 + history.size()];
int posHashListSize = 0;
for (Position p : history) {
posHashList[posHashListSize++] = p.zobristHash();
}
tt.nextGeneration();
History ht = new History();
Search sc = new Search(pos, posHashList, posHashListSize, tt, ht);
// Determine all legal moves
MoveGen.MoveList moves = new MoveGen().pseudoLegalMoves(pos);
MoveGen.removeIllegal(pos, moves);
sc.scoreMoveList(moves, 0);
// Test for "game over"
if (moves.size == 0) {
// Switch sides so that the human can decide what to do next.
return "swap";
}
if (bookEnabled) {
Move bookMove = book.getBookMove(pos);
if (bookMove != null) {
System.out.printf("Book moves: %s\n", book.getAllBookMoves(pos));
return TextIO.moveToString(pos, bookMove, false);
}
}
// Find best move using iterative deepening
currentSearch = sc;
sc.setListener(listener);
Move bestM;
if ((moves.size == 1) && (canClaimDraw(pos, posHashList, posHashListSize, moves.m[0]) == "")) {
bestM = moves.m[0];
bestM.score = 0;
} else if (randomMode) {
bestM = findSemiRandomMove(sc, moves);
} else {
sc.timeLimit(minTimeMillis, maxTimeMillis);
bestM = sc.iterativeDeepening(moves, maxDepth, maxNodes, verbose);
}
currentSearch = null;
// tt.printStats();
String strMove = TextIO.moveToString(pos, bestM, false);
// Claim draw if appropriate
if (bestM.score <= 0) {
String drawClaim = canClaimDraw(pos, posHashList, posHashListSize, bestM);
if (drawClaim != "")
strMove = drawClaim;
}
return strMove;
}
/** Check if a draw claim is allowed, possibly after playing "move".
* @param move The move that may have to be made before claiming draw.
* @return The draw string that claims the draw, or empty string if draw claim not valid.
*/
private String canClaimDraw(Position pos, long[] posHashList, int posHashListSize, Move move) {
String drawStr = "";
if (Search.canClaimDraw50(pos)) {
drawStr = "draw 50";
} else if (Search.canClaimDrawRep(pos, posHashList, posHashListSize, posHashListSize)) {
drawStr = "draw rep";
} else {
String strMove = TextIO.moveToString(pos, move, false);
posHashList[posHashListSize++] = pos.zobristHash();
UndoInfo ui = new UndoInfo();
pos.makeMove(move, ui);
if (Search.canClaimDraw50(pos)) {
drawStr = "draw 50 " + strMove;
} else if (Search.canClaimDrawRep(pos, posHashList, posHashListSize, posHashListSize)) {
drawStr = "draw rep " + strMove;
}
pos.unMakeMove(move, ui);
}
return drawStr;
}
@Override
public boolean isHumanPlayer() {
return false;
}
@Override
public void useBook(boolean bookOn) {
bookEnabled = bookOn;
}
@Override
public void timeLimit(int minTimeLimit, int maxTimeLimit, boolean randomMode) {
if (randomMode) {
minTimeLimit = 0;
maxTimeLimit = 0;
}
minTimeMillis = minTimeLimit;
maxTimeMillis = maxTimeLimit;
this.randomMode = randomMode;
if (currentSearch != null) {
currentSearch.timeLimit(minTimeLimit, maxTimeLimit);
}
}
@Override
public void clearTT() {
tt.clear();
}
/** Search a position and return the best move and score. Used for test suite processing. */
public TwoReturnValues<Move, String> searchPosition(Position pos, int maxTimeMillis) {
// Create a search object
long[] posHashList = new long[200];
tt.nextGeneration();
History ht = new History();
Search sc = new Search(pos, posHashList, 0, tt, ht);
// Determine all legal moves
MoveGen.MoveList moves = new MoveGen().pseudoLegalMoves(pos);
MoveGen.removeIllegal(pos, moves);
sc.scoreMoveList(moves, 0);
// Find best move using iterative deepening
sc.timeLimit(maxTimeMillis, maxTimeMillis);
Move bestM = sc.iterativeDeepening(moves, -1, -1, false);
// Extract PV
String PV = TextIO.moveToString(pos, bestM, false) + " ";
UndoInfo ui = new UndoInfo();
pos.makeMove(bestM, ui);
PV += tt.extractPV(pos);
pos.unMakeMove(bestM, ui);
// tt.printStats();
// Return best move and PV
return new TwoReturnValues<Move, String>(bestM, PV);
}
private Move findSemiRandomMove(Search sc, MoveGen.MoveList moves) {
sc.timeLimit(minTimeMillis, maxTimeMillis);
Move bestM = sc.iterativeDeepening(moves, 1, maxNodes, verbose);
int bestScore = bestM.score;
Random rndGen = new SecureRandom();
rndGen.setSeed(System.currentTimeMillis());
int sum = 0;
for (int mi = 0; mi < moves.size; mi++) {
sum += moveProbWeight(moves.m[mi].score, bestScore);
}
int rnd = rndGen.nextInt(sum);
for (int mi = 0; mi < moves.size; mi++) {
int weight = moveProbWeight(moves.m[mi].score, bestScore);
if (rnd < weight) {
return moves.m[mi];
}
rnd -= weight;
}
assert(false);
return null;
}
private final static int moveProbWeight(int moveScore, int bestScore) {
double d = (bestScore - moveScore) / 100.0;
double w = 100*Math.exp(-d*d/2);
return (int)Math.ceil(w);
}
}