DroidFish: Some code re-organization.

This commit is contained in:
Peter Osterlund
2011-12-27 14:57:36 +00:00
parent d44cb003f1
commit 4f2bc00259
9 changed files with 819 additions and 718 deletions

View File

@@ -107,6 +107,7 @@ public class DroidFish extends Activity implements GUIInterface {
// FIXME!!! PGN view option: game continuation (for training)
// FIXME!!! Remove invalid playerActions in PGN import (should be done in verifyChildren)
// FIXME!!! Implement bookmark mechanism for positions in pgn files
// FIXME!!! Display chess notation in local language
// FIXME!!! Computer clock should stop if phone turned off (computer stops thinking if unplugged)
// FIXME!!! Add support for all time controls defined by the PGN standard
@@ -511,9 +512,8 @@ public class DroidFish extends Activity implements GUIInterface {
@Override
protected void onDestroy() {
if (ctrl != null) {
if (ctrl != null)
ctrl.shutdownEngine();
}
setNotification(false);
super.onDestroy();
}
@@ -1552,7 +1552,7 @@ public class DroidFish extends Activity implements GUIInterface {
for (int i = 0; i < pvMovesTmp.size(); i++) {
ArrayList<Move> pv = pvMovesTmp.get(i);
StringBuilder preComment = new StringBuilder();
if (pvStrs.length > i) {
if (i < pvStrs.length) {
String[] tmp = pvStrs[i].split(" ");
for (int j = 0; j < 2; j++) {
if (j < tmp.length) {

View File

@@ -31,7 +31,7 @@ public interface GUIInterface {
/** Update the displayed board position. */
public void setPosition(Position pos, String variantInfo, List<Move> variantMoves);
/** Mark square i as selected. Set to -1 to clear selection. */
/** Mark square sq as selected. Set to -1 to clear selection. */
public void setSelection(int sq);
final static class GameStatus {

View File

@@ -271,7 +271,7 @@ public class CtgBook implements IOpeningBook {
return page;
}
final private static int tbl[] = {
private final static int tbl[] = {
0x3100d2bf, 0x3118e3de, 0x34ab1372, 0x2807a847,
0x1633f566, 0x2143b359, 0x26d56488, 0x3b9e6f59,
0x37755656, 0x3089ca7b, 0x18e92d85, 0x0cd0e9d8,
@@ -290,7 +290,7 @@ public class CtgBook implements IOpeningBook {
0x274c7e7c, 0x1e8be65c, 0x2fa0b0bb, 0x1eb6c371
};
final private static int getHashValue(byte[] encodedPos) {
private final static int getHashValue(byte[] encodedPos) {
int hash = 0;
int tmp = 0;
for (int i = 0; i < encodedPos.length; i++) {

View File

@@ -74,6 +74,7 @@ public final class DroidBook {
rndGen.setSeed(System.currentTimeMillis());
}
/** Set opening book options. */
public final void setOptions(BookOptions options) {
this.options = options;
if (CtgBook.canHandle(options))
@@ -136,7 +137,7 @@ public final class DroidBook {
}
}
/** Return a string describing all book moves. */
/** Return all book moves, both as a formatted string and as a list of moves. */
public final Pair<String,ArrayList<Move>> getAllBookMoves(Position pos) {
StringBuilder ret = new StringBuilder();
ArrayList<Move> bookMoveList = new ArrayList<Move>();

View File

@@ -46,11 +46,13 @@ public class DroidComputerPlayer {
private DroidBook book;
private boolean newGame = false;
private String engine = "";
private int maxPV = 1; // >1 if multiPV mode is supported
/** >1 if multiPV mode is supported. */
private int maxPV = 1;
private int numCPUs = 1;
private boolean havePonderHit = false;
/** Constructor. Starts engine process if not already started. */
public DroidComputerPlayer(String engine) {
this.engine = engine;
startEngine();
@@ -58,6 +60,245 @@ public class DroidComputerPlayer {
book = DroidBook.getInstance();
}
/** Set engine and engine strength.
* @param engine Name of engine.
* @param strength Engine strength, 0 - 1000. */
public final synchronized void setEngineStrength(String engine, int strength) {
if (!engine.equals(this.engine)) {
shutdownEngine();
this.engine = engine;
startEngine();
}
if (uciEngine != null)
uciEngine.setStrength(strength);
}
/** Set search listener. */
public final void setListener(SearchListener listener) {
this.listener = listener;
}
/** Return maximum number of PVs supported by engine. */
public final synchronized int getMaxPV() {
return maxPV;
}
/** Set engine multi-PV mode. */
public final synchronized void setNumPV(int numPV) {
if ((uciEngine != null) && (maxPV > 1)) {
int num = Math.min(maxPV, numPV);
uciEngine.setOption("MultiPV", num);
}
}
/** Set opening book options. */
public final void setBookOptions(BookOptions options) {
book.setOptions(options);
}
/** Return all book moves, both as a formatted string and as a list of moves. */
public final Pair<String, ArrayList<Move>> getBookHints(Position pos) {
return book.getAllBookMoves(pos);
}
/** Get engine reported name, including strength setting. */
public synchronized String getEngineName() {
if (uciEngine != null)
return engineName + uciEngine.addStrengthToName();
else
return engineName;
}
/** Clear transposition table. */
public final void clearTT() {
newGame = true;
}
/** Sends "ucinewgame" to engine if clearTT() has previously been called. */
public final void maybeNewGame() {
if (newGame) {
newGame = false;
if (uciEngine != null) {
uciEngine.writeLineToEngine("ucinewgame");
syncReady();
}
}
}
/** Sends "ponderhit" command to engine. */
public final synchronized void ponderHit(Position pos, Move ponderMove) {
havePonderHit = true;
uciEngine.writeLineToEngine("ponderhit");
pvModified = true;
notifyGUI(pos, ponderMove);
}
/** Stop the engine process. */
public final synchronized void shutdownEngine() {
if (uciEngine != null) {
uciEngine.shutDown();
uciEngine = null;
}
}
/**
* Do a search and return a command from the computer player.
* The command can be a valid move string, in which case the move is played
* and the turn goes over to the other player. The command can also be a special
* command, such as "draw" and "resign".
* @param pos An earlier position from the game
* @param mList List of moves to go from the earlier position to the current position.
* This list makes it possible for the computer to correctly handle draw
* by repetition/50 moves.
* @param ponderEnabled True if pondering is enabled in the GUI. Can affect time management.
* @param ponderMove Move to ponder, or null for non-ponder search.
* @param engineThreads Number of engine threads to use, if supported by engine.
* @return The computer player command, and the next ponder move.
*/
public final Pair<String,Move> doSearch(Position prevPos, ArrayList<Move> mList,
Position currPos, boolean drawOffer,
int wTime, int bTime, int inc, int movesToGo,
boolean ponderEnabled, Move ponderMove,
int engineThreads) {
if (listener != null)
listener.notifyBookInfo("", null);
if (ponderMove != null)
mList.add(ponderMove);
havePonderHit = false;
// Set up for draw detection
long[] posHashList = new long[mList.size()+1];
int posHashListSize = 0;
Position p = new Position(prevPos);
UndoInfo ui = new UndoInfo();
for (int i = 0; i < mList.size(); i++) {
posHashList[posHashListSize++] = p.zobristHash();
p.makeMove(mList.get(i), ui);
}
if (ponderMove == null) {
// If we have a book move, play it.
Move bookMove = book.getBookMove(currPos);
if (bookMove != null) {
if (canClaimDraw(currPos, posHashList, posHashListSize, bookMove) == "") {
return new Pair<String,Move>(TextIO.moveToString(currPos, bookMove, false), null);
}
}
// If only one legal move, play it without searching
ArrayList<Move> moves = new MoveGen().pseudoLegalMoves(currPos);
moves = MoveGen.removeIllegal(currPos, moves);
if (moves.size() == 0) {
return new Pair<String,Move>("", null); // User set up a position where computer has no valid moves.
}
if (moves.size() == 1) {
Move bestMove = moves.get(0);
if (canClaimDraw(currPos, posHashList, posHashListSize, bestMove) == "") {
return new Pair<String,Move>(TextIO.moveToUCIString(bestMove), null);
}
}
}
StringBuilder posStr = new StringBuilder();
posStr.append("position fen ");
posStr.append(TextIO.toFEN(prevPos));
int nMoves = mList.size();
if (nMoves > 0) {
posStr.append(" moves");
for (int i = 0; i < nMoves; i++) {
posStr.append(" ");
posStr.append(TextIO.moveToUCIString(mList.get(i)));
}
}
maybeNewGame();
uciEngine.setOption("Ponder", ponderEnabled);
uciEngine.setOption("UCI_AnalyseMode", false);
uciEngine.setOption("Threads", engineThreads > 0 ? engineThreads : numCPUs);
uciEngine.writeLineToEngine(posStr.toString());
if (wTime < 1) wTime = 1;
if (bTime < 1) bTime = 1;
StringBuilder goStr = new StringBuilder(96);
goStr.append(String.format("go wtime %d btime %d", wTime, bTime));
if (inc > 0)
goStr.append(String.format(" winc %d binc %d", inc, inc));
if (movesToGo > 0)
goStr.append(String.format(" movestogo %d", movesToGo));
if (ponderMove != null)
goStr.append(" ponder");
uciEngine.writeLineToEngine(goStr.toString());
Pair<String,String> pair = monitorEngine(currPos, ponderMove);
String bestMove = pair.first;
Move nextPonderMove = TextIO.UCIstringToMove(pair.second);
// Claim draw if appropriate
if (statScore <= 0) {
String drawClaim = canClaimDraw(currPos, posHashList, posHashListSize, TextIO.UCIstringToMove(bestMove));
if (drawClaim != "")
bestMove = drawClaim;
}
// Accept draw offer if engine is losing
if (drawOffer && !statIsMate && (statScore <= -300)) {
bestMove = "draw accept";
}
return new Pair<String,Move>(bestMove, nextPonderMove);
}
public boolean shouldStop = false;
/** Tell engine to stop searching. */
public final synchronized void stopSearch() {
shouldStop = true;
if (uciEngine != null)
uciEngine.writeLineToEngine("stop");
havePonderHit = false;
}
/** Start analyzing a position.
* @param prevPos Position corresponding to last irreversible move.
* @param mList List of moves from prevPos to currPos.
* @param currPos Position to analyze.
* @param drawOffer True if other side have offered draw.
* @param engineThreads Number of threads to use, or 0 for default value.
*/
public final void analyze(Position prevPos, ArrayList<Move> mList, Position currPos,
boolean drawOffer, int engineThreads) {
if (shouldStop)
return;
if (listener != null) {
Pair<String, ArrayList<Move>> bi = getBookHints(currPos);
listener.notifyBookInfo(bi.first, bi.second);
}
// If no legal moves, there is nothing to analyze
ArrayList<Move> moves = new MoveGen().pseudoLegalMoves(currPos);
moves = MoveGen.removeIllegal(currPos, moves);
if (moves.size() == 0)
return;
StringBuilder posStr = new StringBuilder();
posStr.append("position fen ");
posStr.append(TextIO.toFEN(prevPos));
int nMoves = mList.size();
if (nMoves > 0) {
posStr.append(" moves");
for (int i = 0; i < nMoves; i++) {
posStr.append(" ");
posStr.append(TextIO.moveToUCIString(mList.get(i)));
}
}
maybeNewGame();
uciEngine.writeLineToEngine(posStr.toString());
uciEngine.setOption("UCI_AnalyseMode", true);
uciEngine.setOption("Threads", engineThreads > 0 ? engineThreads : numCPUs);
String goStr = String.format("go infinite");
uciEngine.writeLineToEngine(goStr);
monitorEngine(currPos, null);
}
private final synchronized void startEngine() {
boolean useCuckoo = engine.equals("cuckoochess");
if (uciEngine == null) {
@@ -80,27 +321,6 @@ public class DroidComputerPlayer {
}
}
public final synchronized void setEngineStrength(String engine, int strength) {
if (!engine.equals(this.engine)) {
shutdownEngine();
this.engine = engine;
startEngine();
}
if (uciEngine != null)
uciEngine.setStrength(strength);
}
public final synchronized int getMaxPV() {
return maxPV;
}
public final synchronized void setNumPV(int numPV) {
if ((uciEngine != null) && (maxPV > 1)) {
int num = Math.min(maxPV, numPV);
uciEngine.setOption("MultiPV", num);
}
}
private static int getNumCPUs() {
int nCPUsFromProc = 1;
try {
@@ -121,15 +341,7 @@ public class DroidComputerPlayer {
return Math.max(nCPUsFromProc, nCPUsFromOS);
}
public final void setListener(SearchListener listener) {
this.listener = listener;
}
public final void setBookOptions(BookOptions options) {
book.setOptions(options);
}
private void readUCIOptions() {
private final void readUCIOptions() {
int timeout = 1000;
maxPV = 1;
while (true) {
@@ -159,13 +371,6 @@ public class DroidComputerPlayer {
}
}
public synchronized String getEngineName() {
if (uciEngine != null)
return engineName + uciEngine.addStrengthToName();
else
return engineName;
}
/** Convert a string to tokens by splitting at whitespace characters. */
private final String[] tokenize(String cmdLine) {
cmdLine = cmdLine.trim();
@@ -181,141 +386,6 @@ public class DroidComputerPlayer {
}
}
/** Clear transposition table. */
public final void clearTT() {
newGame = true;
}
public final void maybeNewGame() {
if (newGame) {
newGame = false;
if (uciEngine != null) {
uciEngine.writeLineToEngine("ucinewgame");
syncReady();
}
}
}
/** Stop the engine process. */
public final synchronized void shutdownEngine() {
if (uciEngine != null) {
uciEngine.shutDown();
uciEngine = null;
}
}
public final synchronized void ponderHit(Position pos, Move ponderMove) {
havePonderHit = true;
uciEngine.writeLineToEngine("ponderhit");
pvModified = true;
notifyGUI(pos, ponderMove);
}
/**
* Do a search and return a command from the computer player.
* The command can be a valid move string, in which case the move is played
* and the turn goes over to the other player. The command can also be a special
* command, such as "draw" and "resign".
* @param pos An earlier position from the game
* @param mList List of moves to go from the earlier position to the current position.
* This list makes it possible for the computer to correctly handle draw
* by repetition/50 moves.
* @param ponderEnabled True if pondering is enabled in the GUI. Can affect time management.
* @param ponderMove Move to ponder, or null for non-ponder search.
* @param engineThreads Number of engine threads to use, if supported by engine.
* @return The computer player command, and the next ponder move.
*/
public final Pair<String,Move> doSearch(Position prevPos, ArrayList<Move> mList,
Position currPos, boolean drawOffer,
int wTime, int bTime, int inc, int movesToGo,
boolean ponderEnabled, Move ponderMove,
int engineThreads) {
if (listener != null)
listener.notifyBookInfo("", null);
if (ponderMove != null)
mList.add(ponderMove);
havePonderHit = false;
// Set up for draw detection
long[] posHashList = new long[mList.size()+1];
int posHashListSize = 0;
Position p = new Position(prevPos);
UndoInfo ui = new UndoInfo();
for (int i = 0; i < mList.size(); i++) {
posHashList[posHashListSize++] = p.zobristHash();
p.makeMove(mList.get(i), ui);
}
if (ponderMove == null) {
// If we have a book move, play it.
Move bookMove = book.getBookMove(currPos);
if (bookMove != null) {
if (canClaimDraw(currPos, posHashList, posHashListSize, bookMove) == "") {
return new Pair<String,Move>(TextIO.moveToString(currPos, bookMove, false), null);
}
}
// If only one legal move, play it without searching
ArrayList<Move> moves = new MoveGen().pseudoLegalMoves(currPos);
moves = MoveGen.removeIllegal(currPos, moves);
if (moves.size() == 0) {
return new Pair<String,Move>("", null); // User set up a position where computer has no valid moves.
}
if (moves.size() == 1) {
Move bestMove = moves.get(0);
if (canClaimDraw(currPos, posHashList, posHashListSize, bestMove) == "") {
return new Pair<String,Move>(TextIO.moveToUCIString(bestMove), null);
}
}
}
StringBuilder posStr = new StringBuilder();
posStr.append("position fen ");
posStr.append(TextIO.toFEN(prevPos));
int nMoves = mList.size();
if (nMoves > 0) {
posStr.append(" moves");
for (int i = 0; i < nMoves; i++) {
posStr.append(" ");
posStr.append(TextIO.moveToUCIString(mList.get(i)));
}
}
maybeNewGame();
uciEngine.setOption("Ponder", ponderEnabled);
uciEngine.setOption("UCI_AnalyseMode", false);
uciEngine.setOption("Threads", engineThreads > 0 ? engineThreads : numCPUs);
uciEngine.writeLineToEngine(posStr.toString());
if (wTime < 1) wTime = 1;
if (bTime < 1) bTime = 1;
StringBuilder goStr = new StringBuilder(96);
goStr.append(String.format("go wtime %d btime %d", wTime, bTime));
if (inc > 0)
goStr.append(String.format(" winc %d binc %d", inc, inc));
if (movesToGo > 0)
goStr.append(String.format(" movestogo %d", movesToGo));
if (ponderMove != null)
goStr.append(" ponder");
uciEngine.writeLineToEngine(goStr.toString());
Pair<String,String> pair = monitorEngine(currPos, ponderMove);
String bestMove = pair.first;
Move nextPonderMove = TextIO.UCIstringToMove(pair.second);
// Claim draw if appropriate
if (statScore <= 0) {
String drawClaim = canClaimDraw(currPos, posHashList, posHashListSize, TextIO.UCIstringToMove(bestMove));
if (drawClaim != "")
bestMove = drawClaim;
}
// Accept draw offer if engine is losing
if (drawOffer && !statIsMate && (statScore <= -300)) {
bestMove = "draw accept";
}
return new Pair<String,Move>(bestMove, nextPonderMove);
}
/** Wait for engine to respond with bestMove and ponderMove.
* While waiting, monitor and report search info. */
private final Pair<String,String> monitorEngine(Position pos, Move ponderMove) {
@@ -355,49 +425,6 @@ public class DroidComputerPlayer {
}
}
public final Pair<String, ArrayList<Move>> getBookHints(Position pos) {
Pair<String, ArrayList<Move>> bi = book.getAllBookMoves(pos);
return new Pair<String, ArrayList<Move>>(bi.first, bi.second);
}
public boolean shouldStop = false;
public final void analyze(Position prevPos, ArrayList<Move> mList, Position currPos,
boolean drawOffer, int engineThreads) {
if (shouldStop)
return;
if (listener != null) {
Pair<String, ArrayList<Move>> bi = getBookHints(currPos);
listener.notifyBookInfo(bi.first, bi.second);
}
// If no legal moves, there is nothing to analyze
ArrayList<Move> moves = new MoveGen().pseudoLegalMoves(currPos);
moves = MoveGen.removeIllegal(currPos, moves);
if (moves.size() == 0)
return;
StringBuilder posStr = new StringBuilder();
posStr.append("position fen ");
posStr.append(TextIO.toFEN(prevPos));
int nMoves = mList.size();
if (nMoves > 0) {
posStr.append(" moves");
for (int i = 0; i < nMoves; i++) {
posStr.append(" ");
posStr.append(TextIO.moveToUCIString(mList.get(i)));
}
}
maybeNewGame();
uciEngine.writeLineToEngine(posStr.toString());
uciEngine.setOption("UCI_AnalyseMode", true);
uciEngine.setOption("Threads", engineThreads > 0 ? engineThreads : numCPUs);
String goStr = String.format("go infinite");
uciEngine.writeLineToEngine(goStr);
monitorEngine(currPos, null);
}
/** 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.
@@ -583,11 +610,4 @@ public class DroidComputerPlayer {
statsModified = false;
}
}
public final synchronized void stopSearch() {
shouldStop = true;
if (uciEngine != null)
uciEngine.writeLineToEngine("stop");
havePonderHit = false;
}
}

View File

@@ -35,6 +35,7 @@ public class Game {
private DroidComputerPlayer computerPlayer;
TimeControl timeController;
private boolean gamePaused;
/** If true, add new moves as mainline moves. */
private boolean addFirst;
PgnToken.PgnTokenReceiver gameTextListener;
@@ -50,6 +51,7 @@ public class Game {
newGame();
}
/** De-serialize from byte array. */
final void fromByteArray(byte[] data) {
tree.fromByteArray(data);
updateTimeControl(true);
@@ -66,10 +68,12 @@ public class Game {
}
}
/** Set whether new moves are entered as mainline moves or variations. */
public final void setAddFirst(boolean addFirst) {
this.addFirst = addFirst;
}
/** Sets start position and discards the whole game tree. */
final void setPos(Position pos) {
tree.setStartPos(new Position(pos));
updateTimeControl(false);
@@ -217,6 +221,7 @@ public class Game {
return nVar > 0;
}
/** Get number of variations in current game position. */
public final int numVariations() {
if (tree.currentNode == tree.rootNode)
return 1;
@@ -226,6 +231,7 @@ public class Game {
return nChildren;
}
/** Get current variation in current position. */
public final int currVariation() {
if (tree.currentNode == tree.rootNode)
return 0;
@@ -235,6 +241,7 @@ public class Game {
return defChild;
}
/** Go to a new variation in the game tree. */
public final void changeVariation(int delta) {
if (tree.currentNode == tree.rootNode)
return;
@@ -249,6 +256,7 @@ public class Game {
updateTimeControl(true);
}
/** Move current variation up/down in the game tree. */
public final void moveVariation(int delta) {
if (tree.currentNode == tree.rootNode)
return;
@@ -264,6 +272,7 @@ public class Game {
updateTimeControl(true);
}
/** Delete whole game sub-tree rooted at current position. */
public final void removeSubTree() {
if (getLastMove() != null) {
tree.goBack();
@@ -323,10 +332,14 @@ public class Game {
}
}
public final void goNode(Node node) {
tree.goNode(node);
/** Go to given node in game tree.
* @return True if current node changed, false otherwise. */
public final boolean goNode(Node node) {
if (!tree.goNode(node))
return false;
pendingDrawOffer = false;
updateTimeControl(true);
return true;
}
public final void newGame() {

View File

@@ -657,13 +657,17 @@ public class GameTree {
}
}
/** Go to given node in game tree. */
public final void goNode(Node node) {
/** Go to given node in game tree.
* @return True if current node changed, false otherwise. */
public final boolean goNode(Node node) {
if (node == currentNode)
return false;
ArrayList<Integer> path = node.getPathFromRoot();
while (currentNode != rootNode)
goBack();
for (Integer c : path)
goForward(c);
return true;
}
/** List of possible continuation moves. */
@@ -1388,6 +1392,7 @@ public class GameTree {
}
}
/** Set PGN header tags and values. */
void setHeaders(Map<String,String> headers) {
for (Entry<String, String> entry : headers.entrySet()) {
String tag = entry.getKey();
@@ -1417,6 +1422,7 @@ public class GameTree {
}
}
/** Get PGN header tags and values. */
void getHeaders(Map<String,String> headers) {
headers.put("Event", event);
headers.put("Site", site);

View File

@@ -667,7 +667,8 @@ public class GameTreeTest extends TestCase {
for (int i = 0; i < 5; i++) gt.goForward(-1);
assertEquals("e4 e5 Nf3 Nc6 Bb5 a6*", getMoveListAsString(gt));
Node na6 = gt.currentNode;
gt.goNode(gt.rootNode);
assertTrue(gt.goNode(gt.rootNode));
assertFalse(gt.goNode(gt.rootNode));
assertEquals("*e4 e5 Nf3 Nc6 Bb5 a6", getMoveListAsString(gt));
gt.goNode(na6);
assertEquals("e4 e5 Nf3 Nc6 Bb5 a6*", getMoveListAsString(gt));