From 4f2bc0025976276b63d50df978b2fffef394e43d Mon Sep 17 00:00:00 2001 From: Peter Osterlund Date: Tue, 27 Dec 2011 14:57:36 +0000 Subject: [PATCH] DroidFish: Some code re-organization. --- .../src/org/petero/droidfish/DroidFish.java | 6 +- .../org/petero/droidfish/GUIInterface.java | 2 +- .../org/petero/droidfish/engine/CtgBook.java | 4 +- .../petero/droidfish/engine/DroidBook.java | 3 +- .../droidfish/engine/DroidComputerPlayer.java | 466 ++++---- .../gamelogic/DroidChessController.java | 1026 +++++++++-------- .../org/petero/droidfish/gamelogic/Game.java | 17 +- .../petero/droidfish/gamelogic/GameTree.java | 10 +- .../droidfish/gamelogic/GameTreeTest.java | 3 +- 9 files changed, 819 insertions(+), 718 deletions(-) diff --git a/DroidFish/src/org/petero/droidfish/DroidFish.java b/DroidFish/src/org/petero/droidfish/DroidFish.java index 3a989ba..da566a2 100644 --- a/DroidFish/src/org/petero/droidfish/DroidFish.java +++ b/DroidFish/src/org/petero/droidfish/DroidFish.java @@ -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 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) { diff --git a/DroidFish/src/org/petero/droidfish/GUIInterface.java b/DroidFish/src/org/petero/droidfish/GUIInterface.java index 5cd68b9..cd8d9b8 100644 --- a/DroidFish/src/org/petero/droidfish/GUIInterface.java +++ b/DroidFish/src/org/petero/droidfish/GUIInterface.java @@ -31,7 +31,7 @@ public interface GUIInterface { /** Update the displayed board position. */ public void setPosition(Position pos, String variantInfo, List 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 { diff --git a/DroidFish/src/org/petero/droidfish/engine/CtgBook.java b/DroidFish/src/org/petero/droidfish/engine/CtgBook.java index 58fac85..3590d14 100644 --- a/DroidFish/src/org/petero/droidfish/engine/CtgBook.java +++ b/DroidFish/src/org/petero/droidfish/engine/CtgBook.java @@ -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++) { diff --git a/DroidFish/src/org/petero/droidfish/engine/DroidBook.java b/DroidFish/src/org/petero/droidfish/engine/DroidBook.java index 334f542..7eb0c9a 100644 --- a/DroidFish/src/org/petero/droidfish/engine/DroidBook.java +++ b/DroidFish/src/org/petero/droidfish/engine/DroidBook.java @@ -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> getAllBookMoves(Position pos) { StringBuilder ret = new StringBuilder(); ArrayList bookMoveList = new ArrayList(); diff --git a/DroidFish/src/org/petero/droidfish/engine/DroidComputerPlayer.java b/DroidFish/src/org/petero/droidfish/engine/DroidComputerPlayer.java index 799828b..7a8e546 100644 --- a/DroidFish/src/org/petero/droidfish/engine/DroidComputerPlayer.java +++ b/DroidFish/src/org/petero/droidfish/engine/DroidComputerPlayer.java @@ -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> 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 doSearch(Position prevPos, ArrayList 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(TextIO.moveToString(currPos, bookMove, false), null); + } + } + + // If only one legal move, play it without searching + ArrayList moves = new MoveGen().pseudoLegalMoves(currPos); + moves = MoveGen.removeIllegal(currPos, moves); + if (moves.size() == 0) { + return new Pair("", 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(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 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(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 mList, Position currPos, + boolean drawOffer, int engineThreads) { + if (shouldStop) + return; + if (listener != null) { + Pair> bi = getBookHints(currPos); + listener.notifyBookInfo(bi.first, bi.second); + } + + // If no legal moves, there is nothing to analyze + ArrayList 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 doSearch(Position prevPos, ArrayList 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(TextIO.moveToString(currPos, bookMove, false), null); - } - } - - // If only one legal move, play it without searching - ArrayList moves = new MoveGen().pseudoLegalMoves(currPos); - moves = MoveGen.removeIllegal(currPos, moves); - if (moves.size() == 0) { - return new Pair("", 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(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 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(bestMove, nextPonderMove); - } - /** Wait for engine to respond with bestMove and ponderMove. * While waiting, monitor and report search info. */ private final Pair monitorEngine(Position pos, Move ponderMove) { @@ -355,49 +425,6 @@ public class DroidComputerPlayer { } } - public final Pair> getBookHints(Position pos) { - Pair> bi = book.getAllBookMoves(pos); - return new Pair>(bi.first, bi.second); - } - - public boolean shouldStop = false; - - public final void analyze(Position prevPos, ArrayList mList, Position currPos, - boolean drawOffer, int engineThreads) { - if (shouldStop) - return; - if (listener != null) { - Pair> bi = getBookHints(currPos); - listener.notifyBookInfo(bi.first, bi.second); - } - - // If no legal moves, there is nothing to analyze - ArrayList 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; - } } diff --git a/DroidFish/src/org/petero/droidfish/gamelogic/DroidChessController.java b/DroidFish/src/org/petero/droidfish/gamelogic/DroidChessController.java index ae8ed55..713f725 100644 --- a/DroidFish/src/org/petero/droidfish/gamelogic/DroidChessController.java +++ b/DroidFish/src/org/petero/droidfish/gamelogic/DroidChessController.java @@ -54,7 +54,542 @@ public class DroidChessController { private int movesPerSession; private int timeIncrement; - class SearchListener implements org.petero.droidfish.gamelogic.SearchListener { + private SearchListener listener; + private boolean guiPaused = false; + + /** Partial move that needs promotion choice to be completed. */ + private Move promoteMove; + + private Object shutdownEngineLock = new Object(); + + + /** Constructor. */ + public DroidChessController(GUIInterface gui, PgnToken.PgnTokenReceiver gameTextListener, PGNOptions options) { + this.gui = gui; + this.gameTextListener = gameTextListener; + pgnOptions = options; + listener = new SearchListener(); + } + + /** Start a new game. */ + public final void newGame(GameMode gameMode) { + ss.searchResultWanted = false; + boolean updateGui = stopComputerThinking(); + updateGui |= stopAnalysis(); + if (updateGui) + updateGUI(); + this.gameMode = gameMode; + ponderMove = null; + if (computerPlayer == null) { + computerPlayer = new DroidComputerPlayer(engine); + computerPlayer.setListener(listener); + computerPlayer.setBookOptions(bookOptions); + } + game = new Game(computerPlayer, gameTextListener, timeControl, movesPerSession, timeIncrement); + setPlayerNames(game); + updateGameMode(); + } + + /** Start playing a new game. Should be called after newGame(). */ + public final void startGame() { + updateComputeThreads(true); + setSelection(); + updateGUI(); + updateGameMode(); + } + + /** Set time control parameters. */ + public final synchronized void setTimeLimit(int time, int moves, int inc) { + timeControl = time; + movesPerSession = moves; + timeIncrement = inc; + if (game != null) + game.timeController.setTimeControl(timeControl, movesPerSession, timeIncrement); + } + + /** The chess clocks are stopped when the GUI is paused. */ + public final void setGuiPaused(boolean paused) { + guiPaused = paused; + updateGameMode(); + } + + /** Set game mode. */ + public final void setGameMode(GameMode newMode) { + if (!gameMode.equals(newMode)) { + if (newMode.humansTurn(game.currPos().whiteMove)) + ss.searchResultWanted = false; + gameMode = newMode; + if (!gameMode.playerWhite() || !gameMode.playerBlack()) + setPlayerNames(game); // If computer player involved, set player names + updateGameMode(); + ponderMove = null; + ss.searchResultWanted = false; + stopComputerThinking(); + updateComputeThreads(true); + updateGUI(); + } + } + + /** Set engine book options. */ + public final synchronized void setBookOptions(BookOptions options) { + if (!bookOptions.equals(options)) { + bookOptions = options; + if (computerPlayer != null) { + computerPlayer.setBookOptions(bookOptions); + if (analysisThread != null) { + boolean updateGui = stopAnalysis(); + updateGui |= startAnalysis(); + if (updateGui) + updateGUI(); + } + updateBookHints(); + } + } + } + + /** Set engine and engine strength. Restart computer thinking if appropriate. + * @param engine Name of engine. + * @param strength Engine strength, 0 - 1000. */ + public final synchronized void setEngineStrength(String engine, int strength) { + boolean newEngine = !engine.equals(this.engine); + if (newEngine) + numPV = 1; + if (newEngine || (strength != this.strength)) { + this.engine = engine; + this.strength = strength; + if (newEngine && ((analysisThread != null) || (computerThread != null))) { + stopAnalysis(); + updateComputeThreads(true); + updateGUI(); + } + } + } + + /** Notify controller that preferences has changed. */ + public final void prefsChanged() { + updateBookHints(); + updateMoveList(); + listener.prefsChanged(); + } + + /** De-serialize from byte array. */ + public final void fromByteArray(byte[] data) { + game.fromByteArray(data); + } + + /** Serialize to byte array. */ + public final byte[] toByteArray() { + return game.tree.toByteArray(); + } + + /** Return FEN string corresponding to a current position. */ + public final String getFEN() { + return TextIO.toFEN(game.tree.currentPos); + } + + /** Convert current game to PGN format. */ + public final String getPGN() { + return game.tree.toPGN(pgnOptions); + } + + /** Parse a string as FEN or PGN data. */ + public final void setFENOrPGN(String fenPgn) throws ChessParseError { + Game newGame = new Game(null, gameTextListener, timeControl, movesPerSession, timeIncrement); + try { + Position pos = TextIO.readFEN(fenPgn); + newGame.setPos(pos); + setPlayerNames(newGame); + } catch (ChessParseError e) { + // Try read as PGN instead + if (!newGame.readPGN(fenPgn, pgnOptions)) { + // FIXME!! Should detect when PGN game contains only one invalid move. + throw e; + } + } + ss.searchResultWanted = false; + game = newGame; + game.setComputerPlayer(computerPlayer); + gameTextListener.clear(); + updateGameMode(); + stopAnalysis(); + stopComputerThinking(); + computerPlayer.clearTT(); + ponderMove = null; + updateComputeThreads(true); + gui.setSelection(-1); + updateGUI(); + } + + /** True if human's turn to make a move. (True in analysis mode.) */ + public final boolean humansTurn() { + return gameMode.humansTurn(game.currPos().whiteMove); + } + + /** Return true if computer player is using CPU power. */ + public final boolean computerBusy() { + return (computerThread != null) || (analysisThread != null); + } + + /** Make a move for a human player. */ + public final void makeHumanMove(Move m) { + if (humansTurn()) { + Position oldPos = new Position(game.currPos()); + if (doMove(m)) { + if (m.equals(ponderMove) && !gameMode.analysisMode() && + (analysisThread == null) && (computerThread != null)) { + computerPlayer.ponderHit(oldPos, ponderMove); + } else { + ss.searchResultWanted = false; + stopAnalysis(); + stopComputerThinking(); + updateComputeThreads(true); + } + setAnimMove(oldPos, m, true); + updateGUI(); + } else { + gui.setSelection(-1); + } + } + } + + /** Report promotion choice for incomplete move. + * @param choice 0=queen, 1=rook, 2=bishop, 3=knight. */ + public final void reportPromotePiece(int choice) { + if (promoteMove == null) + return; + final boolean white = game.currPos().whiteMove; + int promoteTo; + switch (choice) { + case 1: + promoteTo = white ? Piece.WROOK : Piece.BROOK; + break; + case 2: + promoteTo = white ? Piece.WBISHOP : Piece.BBISHOP; + break; + case 3: + promoteTo = white ? Piece.WKNIGHT : Piece.BKNIGHT; + break; + default: + promoteTo = white ? Piece.WQUEEN : Piece.BQUEEN; + break; + } + promoteMove.promoteTo = promoteTo; + Move m = promoteMove; + promoteMove = null; + makeHumanMove(m); + } + + /** Add a null-move to the game tree. */ + public final void makeHumanNullMove() { + if (humansTurn()) { + int varNo = game.tree.addMove("--", "", 0, "", ""); + game.tree.goForward(varNo); + ss.searchResultWanted = false; + stopAnalysis(); + stopComputerThinking(); + ponderMove = null; + updateComputeThreads(true); + updateGUI(); + gui.setSelection(-1); + } + } + + /** Help human to claim a draw by trying to find and execute a valid draw claim. */ + public final boolean claimDrawIfPossible() { + if (!findValidDrawClaim()) + return false; + updateGUI(); + return true; + } + + /** Resign game for current player. */ + public final void resignGame() { + if (game.getGameState() == GameState.ALIVE) { + game.processString("resign"); + updateGUI(); + } + } + + /** Undo last move. Does not truncate game tree. */ + public final void undoMove() { + if (game.getLastMove() != null) { + ss.searchResultWanted = false; + stopAnalysis(); + stopComputerThinking(); + boolean didUndo = undoMoveNoUpdate(); + updateComputeThreads(true); + setSelection(); + if (didUndo) + setAnimMove(game.currPos(), game.getNextMove(), false); + updateGUI(); + } + } + + /** Redo last move. Follows default variation. */ + public final void redoMove() { + if (game.canRedoMove()) { + ss.searchResultWanted = false; + stopAnalysis(); + stopComputerThinking(); + redoMoveNoUpdate(); + updateComputeThreads(true); + setSelection(); + setAnimMove(game.prevPos(), game.getLastMove(), true); + updateGUI(); + } + } + + /** Go back/forward to a given move number. + * Follows default variations when going forward. */ + public final void gotoMove(int moveNr) { + boolean needUpdate = false; + while (game.currPos().fullMoveCounter > moveNr) { // Go backward + int before = game.currPos().fullMoveCounter * 2 + (game.currPos().whiteMove ? 0 : 1); + undoMoveNoUpdate(); + int after = game.currPos().fullMoveCounter * 2 + (game.currPos().whiteMove ? 0 : 1); + if (after >= before) + break; + needUpdate = true; + } + while (game.currPos().fullMoveCounter < moveNr) { // Go forward + int before = game.currPos().fullMoveCounter * 2 + (game.currPos().whiteMove ? 0 : 1); + redoMoveNoUpdate(); + int after = game.currPos().fullMoveCounter * 2 + (game.currPos().whiteMove ? 0 : 1); + if (after <= before) + break; + needUpdate = true; + } + if (needUpdate) { + ss.searchResultWanted = false; + stopAnalysis(); + stopComputerThinking(); + ponderMove = null; + updateComputeThreads(true); + setSelection(); + updateGUI(); + } + } + + /** Go to start of the current variation. */ + public final void gotoStartOfVariation() { + boolean needUpdate = false; + while (true) { + if (!undoMoveNoUpdate()) + break; + needUpdate = true; + if (game.numVariations() > 1) + break; + } + if (needUpdate) { + ss.searchResultWanted = false; + stopAnalysis(); + stopComputerThinking(); + ponderMove = null; + updateComputeThreads(true); + setSelection(); + updateGUI(); + } + } + + /** Go to given node in game tree. */ + public final void goNode(Node node) { + if (node == null) + return; + if (!game.goNode(node)) + return; + ponderMove = null; + if (!humansTurn()) { + if (game.getLastMove() != null) { + game.undoMove(); + if (!humansTurn()) + game.redoMove(); + } + } + ss.searchResultWanted = false; + stopAnalysis(); + stopComputerThinking(); + ponderMove = null; + updateComputeThreads(true); + setSelection(); + updateGUI(); + } + + /** Get number of variations in current game position. */ + public final int numVariations() { + return game.numVariations(); + } + + /** Get current variation in current position. */ + public final int currVariation() { + return game.currVariation(); + } + + /** Go to a new variation in the game tree. */ + public final void changeVariation(int delta) { + if (game.numVariations() > 1) { + ss.searchResultWanted = false; + stopAnalysis(); + stopComputerThinking(); + game.changeVariation(delta); + ponderMove = null; + updateComputeThreads(true); + setSelection(); + updateGUI(); + } + } + + /** Delete whole game sub-tree rooted at current position. */ + public final void removeSubTree() { + ss.searchResultWanted = false; + stopAnalysis(); + stopComputerThinking(); + game.removeSubTree(); + ponderMove = null; + updateComputeThreads(true); + setSelection(); + updateGUI(); + } + + /** Move current variation up/down in the game tree. */ + public final void moveVariation(int delta) { + if (game.numVariations() > 1) { + game.moveVariation(delta); + updateGUI(); + } + } + + /** Add a variation to the game tree. + * @param preComment Comment to add before first move. + * @param pvMoves List of moves in variation. + * @param updateDefault If true, make this the default variation. */ + public final void addVariation(String preComment, List pvMoves, boolean updateDefault) { + for (int i = 0; i < pvMoves.size(); i++) { + Move m = pvMoves.get(i); + String moveStr = TextIO.moveToUCIString(m); + String pre = (i == 0) ? preComment : ""; + int varNo = game.tree.addMove(moveStr, "", 0, pre, ""); + game.tree.goForward(varNo, updateDefault); + } + for (int i = 0; i < pvMoves.size(); i++) + game.tree.goBack(); + gameTextListener.clear(); + updateGUI(); + } + + /** Update remaining time and trigger GUI update of clocks. */ + public final void updateRemainingTime() { + long now = System.currentTimeMillis(); + long wTime = game.timeController.getRemainingTime(true, now); + long bTime = game.timeController.getRemainingTime(false, now); + long nextUpdate = 0; + if (game.timeController.clockRunning()) { + long t = game.currPos().whiteMove ? wTime : bTime; + nextUpdate = (t % 1000); + if (nextUpdate < 0) nextUpdate += 1000; + nextUpdate += 1; + } + gui.setRemainingTime(wTime, bTime, nextUpdate); + } + + /** Return maximum number of PVs supported by engine. */ + public final synchronized int maxPV() { + if (computerPlayer == null) + return 1; + return computerPlayer.getMaxPV(); + } + + /** Get multi-PV mode setting. */ + public final int getNumPV() { + return this.numPV; + } + + /** Set multi-PV mode. */ + public final synchronized void setMultiPVMode(int numPV) { + if (numPV < 1) numPV = 1; + if (numPV > maxPV()) numPV = maxPV(); + if (numPV != this.numPV) { + this.numPV = numPV; + if (analysisThread != null) { + stopAnalysis(); + ponderMove = null; + updateComputeThreads(true); + updateGUI(); + } + } + } + + /** Request computer player to make a move immediately. */ + public final synchronized void stopSearch() { + if (computerThread != null) { + computerPlayer.stopSearch(); + } + } + + /** Stop ponder search. */ + public final synchronized void stopPonder() { + if ((computerThread != null) && humansTurn()) { + boolean updateGui = stopComputerThinking(); + if (updateGui) + updateGUI(); + } + } + + /** Shut down chess engine process. */ + public final synchronized void shutdownEngine() { + synchronized (shutdownEngineLock) { + gameMode = new GameMode(GameMode.TWO_PLAYERS); + ss.searchResultWanted = false; + boolean updateGui = stopComputerThinking(); + updateGui |= stopAnalysis(); + if (updateGui) + updateGUI(); + ponderMove = null; + computerPlayer.shutdownEngine(); + } + } + + /** Get PGN header tags and values. */ + public final void getHeaders(Map headers) { + game.tree.getHeaders(headers); + } + + /** Set PGN header tags and values. */ + public final void setHeaders(Map headers) { + game.tree.setHeaders(headers); + gameTextListener.clear(); + updateGUI(); + } + + /** Comments associated with a move. */ + public static final class CommentInfo { + public String move; + public String preComment, postComment; + public int nag; + } + + /** Get comments associated with current position. */ + public final CommentInfo getComments() { + Node cur = game.tree.currentNode; + CommentInfo ret = new CommentInfo(); + ret.move = cur.moveStr; + ret.preComment = cur.preComment; + ret.postComment = cur.postComment; + ret.nag = cur.nag; + return ret; + } + + /** Set comments associated with current position. */ + public final void setComments(CommentInfo commInfo) { + Node cur = game.tree.currentNode; + cur.preComment = commInfo.preComment; + cur.postComment = commInfo.postComment; + cur.nag = commInfo.nag; + gameTextListener.clear(); + updateGUI(); + } + + /** Engine search information receiver. */ + private final class SearchListener implements org.petero.droidfish.gamelogic.SearchListener { private int currDepth = 0; private int currMoveNr = 0; private String currMove = ""; @@ -180,31 +715,6 @@ public class DroidChessController { setSearchInfo(); } } - SearchListener listener; - - - public DroidChessController(GUIInterface gui, PgnToken.PgnTokenReceiver gameTextListener, PGNOptions options) { - this.gui = gui; - this.gameTextListener = gameTextListener; - pgnOptions = options; - listener = new SearchListener(); - } - - public final synchronized void setBookOptions(BookOptions options) { - if (!bookOptions.equals(options)) { - bookOptions = options; - if (computerPlayer != null) { - computerPlayer.setBookOptions(bookOptions); - if (analysisThread != null) { - boolean updateGui = stopAnalysis(); - updateGui |= startAnalysis(); - if (updateGui) - updateGUI(); - } - updateBookHints(); - } - } - } private final void updateBookHints() { if (gameMode != null) { @@ -220,38 +730,7 @@ public class DroidChessController { private final static class SearchStatus { boolean searchResultWanted = true; } - SearchStatus ss = new SearchStatus(); - - public final void newGame(GameMode gameMode) { - ss.searchResultWanted = false; - boolean updateGui = stopComputerThinking(); - updateGui |= stopAnalysis(); - if (updateGui) - updateGUI(); - this.gameMode = gameMode; - ponderMove = null; - if (computerPlayer == null) { - computerPlayer = new DroidComputerPlayer(engine); - computerPlayer.setListener(listener); - computerPlayer.setBookOptions(bookOptions); - } - game = new Game(computerPlayer, gameTextListener, timeControl, movesPerSession, timeIncrement); - setPlayerNames(game); - updateGameMode(); - } - - public final void startGame() { - updateComputeThreads(true); - setSelection(); - updateGUI(); - updateGameMode(); - } - - private boolean guiPaused = false; - public final void setGuiPaused(boolean paused) { - guiPaused = paused; - updateGameMode(); - } + private SearchStatus ss = new SearchStatus(); private final void updateGameMode() { if (game != null) { @@ -263,6 +742,7 @@ public class DroidChessController { } } + /** Start/stop computer thinking/analysis as appropriate. */ private final void updateComputeThreads(boolean clearPV) { boolean analysis = gameMode.analysisMode(); boolean computersTurn = !humansTurn(); @@ -281,29 +761,6 @@ public class DroidChessController { startComputerThinking(ponder); } - /** Set game mode. */ - public final void setGameMode(GameMode newMode) { - if (!gameMode.equals(newMode)) { - if (newMode.humansTurn(game.currPos().whiteMove)) - ss.searchResultWanted = false; - gameMode = newMode; - if (!gameMode.playerWhite() || !gameMode.playerBlack()) - setPlayerNames(game); // If computer player involved, set player names - updateGameMode(); - ponderMove = null; - ss.searchResultWanted = false; - stopComputerThinking(); - updateComputeThreads(true); - updateGUI(); - } - } - - public final void prefsChanged() { - updateBookHints(); - updateMoveList(); - listener.prefsChanged(); - } - private final void setPlayerNames(Game game) { if (game != null) { String engine = (computerPlayer != null) ? computerPlayer.getEngineName() : @@ -314,59 +771,6 @@ public class DroidChessController { } } - public final void fromByteArray(byte[] data) { - game.fromByteArray(data); - } - - public final byte[] toByteArray() { - return game.tree.toByteArray(); - } - - public final String getFEN() { - return TextIO.toFEN(game.tree.currentPos); - } - - /** Convert current game to PGN format. */ - public final String getPGN() { - return game.tree.toPGN(pgnOptions); - } - - public final void setFENOrPGN(String fenPgn) throws ChessParseError { - Game newGame = new Game(null, gameTextListener, timeControl, movesPerSession, timeIncrement); - try { - Position pos = TextIO.readFEN(fenPgn); - newGame.setPos(pos); - setPlayerNames(newGame); - } catch (ChessParseError e) { - // Try read as PGN instead - if (!newGame.readPGN(fenPgn, pgnOptions)) { - throw e; - } - } - ss.searchResultWanted = false; - game = newGame; - game.setComputerPlayer(computerPlayer); - gameTextListener.clear(); - updateGameMode(); - stopAnalysis(); - stopComputerThinking(); - computerPlayer.clearTT(); - ponderMove = null; - updateComputeThreads(true); - gui.setSelection(-1); - updateGUI(); - } - - /** True if human's turn to make a move. (True in analysis mode.) */ - public final boolean humansTurn() { - return gameMode.humansTurn(game.currPos().whiteMove); - } - - /** Return true if computer player is using CPU power. */ - public final boolean computerBusy() { - return (computerThread != null) || (analysisThread != null); - } - private final boolean undoMoveNoUpdate() { if (game.getLastMove() == null) return false; @@ -392,20 +796,6 @@ public class DroidChessController { return true; } - public final void undoMove() { - if (game.getLastMove() != null) { - ss.searchResultWanted = false; - stopAnalysis(); - stopComputerThinking(); - boolean didUndo = undoMoveNoUpdate(); - updateComputeThreads(true); - setSelection(); - if (didUndo) - setAnimMove(game.currPos(), game.getNextMove(), false); - updateGUI(); - } - } - private final void redoMoveNoUpdate() { if (game.canRedoMove()) { ss.searchResultWanted = false; @@ -419,203 +809,11 @@ public class DroidChessController { } } - public final void redoMove() { - if (game.canRedoMove()) { - ss.searchResultWanted = false; - stopAnalysis(); - stopComputerThinking(); - redoMoveNoUpdate(); - updateComputeThreads(true); - setSelection(); - setAnimMove(game.prevPos(), game.getLastMove(), true); - updateGUI(); - } - } - - public final void goNode(Node node) { - if (node == null) - return; - ss.searchResultWanted = false; - stopAnalysis(); - stopComputerThinking(); - game.goNode(node); - ponderMove = null; - if (!humansTurn()) { - if (game.getLastMove() != null) { - game.undoMove(); - if (!humansTurn()) - game.redoMove(); - } - } - updateComputeThreads(true); - setSelection(); - updateGUI(); - } - - public final int numVariations() { - return game.numVariations(); - } - - public final int currVariation() { - return game.currVariation(); - } - - public final void changeVariation(int delta) { - if (game.numVariations() > 1) { - ss.searchResultWanted = false; - stopAnalysis(); - stopComputerThinking(); - game.changeVariation(delta); - ponderMove = null; - updateComputeThreads(true); - setSelection(); - updateGUI(); - } - } - - public final void removeSubTree() { - ss.searchResultWanted = false; - stopAnalysis(); - stopComputerThinking(); - game.removeSubTree(); - ponderMove = null; - updateComputeThreads(true); - setSelection(); - updateGUI(); - } - - public final void moveVariation(int delta) { - if (game.numVariations() > 1) { - game.moveVariation(delta); - updateGUI(); - } - } - - public final void addVariation(String preComment, List pvMoves, boolean updateDefault) { - for (int i = 0; i < pvMoves.size(); i++) { - Move m = pvMoves.get(i); - String moveStr = TextIO.moveToUCIString(m); - String pre = (i == 0) ? preComment : ""; - int varNo = game.tree.addMove(moveStr, "", 0, pre, ""); - game.tree.goForward(varNo, updateDefault); - } - for (int i = 0; i < pvMoves.size(); i++) - game.tree.goBack(); - gameTextListener.clear(); - updateGUI(); - } - - public final void gotoMove(int moveNr) { - boolean needUpdate = false; - while (game.currPos().fullMoveCounter > moveNr) { // Go backward - int before = game.currPos().fullMoveCounter * 2 + (game.currPos().whiteMove ? 0 : 1); - undoMoveNoUpdate(); - int after = game.currPos().fullMoveCounter * 2 + (game.currPos().whiteMove ? 0 : 1); - if (after >= before) - break; - needUpdate = true; - } - while (game.currPos().fullMoveCounter < moveNr) { // Go forward - int before = game.currPos().fullMoveCounter * 2 + (game.currPos().whiteMove ? 0 : 1); - redoMoveNoUpdate(); - int after = game.currPos().fullMoveCounter * 2 + (game.currPos().whiteMove ? 0 : 1); - if (after <= before) - break; - needUpdate = true; - } - if (needUpdate) { - ss.searchResultWanted = false; - stopAnalysis(); - stopComputerThinking(); - ponderMove = null; - updateComputeThreads(true); - setSelection(); - updateGUI(); - } - } - - public final void gotoStartOfVariation() { - ss.searchResultWanted = false; - stopAnalysis(); - stopComputerThinking(); - while (true) { - if (!undoMoveNoUpdate()) - break; - if (game.numVariations() > 1) - break; - } - ponderMove = null; - updateComputeThreads(true); - setSelection(); - updateGUI(); - } - - public final void makeHumanMove(Move m) { - if (humansTurn()) { - Position oldPos = new Position(game.currPos()); - if (doMove(m)) { - if (m.equals(ponderMove) && !gameMode.analysisMode() && - (analysisThread == null) && (computerThread != null)) { - computerPlayer.ponderHit(oldPos, ponderMove); - } else { - ss.searchResultWanted = false; - stopAnalysis(); - stopComputerThinking(); - updateComputeThreads(true); - } - setAnimMove(oldPos, m, true); - updateGUI(); - } else { - gui.setSelection(-1); - } - } - } - - public final void makeHumanNullMove() { - if (humansTurn()) { - int varNo = game.tree.addMove("--", "", 0, "", ""); - game.tree.goForward(varNo); - ss.searchResultWanted = false; - stopAnalysis(); - stopComputerThinking(); - ponderMove = null; - updateComputeThreads(true); - updateGUI(); - gui.setSelection(-1); - } - } - - private Move promoteMove; - public final void reportPromotePiece(int choice) { - if (promoteMove == null) - return; - final boolean white = game.currPos().whiteMove; - int promoteTo; - switch (choice) { - case 1: - promoteTo = white ? Piece.WROOK : Piece.BROOK; - break; - case 2: - promoteTo = white ? Piece.WBISHOP : Piece.BBISHOP; - break; - case 3: - promoteTo = white ? Piece.WKNIGHT : Piece.BKNIGHT; - break; - default: - promoteTo = white ? Piece.WQUEEN : Piece.BQUEEN; - break; - } - promoteMove.promoteTo = promoteTo; - Move m = promoteMove; - promoteMove = null; - makeHumanMove(m); - } - /** * Move a piece from one square to another. * @return True if the move was legal, false otherwise. */ - final private boolean doMove(Move move) { + private final boolean doMove(Move move) { Position pos = game.currPos(); ArrayList moves = new MoveGen().pseudoLegalMoves(pos); moves = MoveGen.removeIllegal(pos, moves); @@ -638,7 +836,7 @@ public class DroidChessController { return false; } - final private void updateGUI() { + private final void updateGUI() { GUIInterface.GameStatus s = new GUIInterface.GameStatus(); s.state = game.getGameState(); if (s.state == Game.GameState.ALIVE) { @@ -678,7 +876,7 @@ public class DroidChessController { updateRemainingTime(); } - final private void updateMoveList() { + private final void updateMoveList() { if (game == null) return; if (!gameTextListener.isUpToDate()) { @@ -696,22 +894,8 @@ public class DroidChessController { gui.moveListUpdated(); } - final public void updateRemainingTime() { - // Update remaining time - long now = System.currentTimeMillis(); - long wTime = game.timeController.getRemainingTime(true, now); - long bTime = game.timeController.getRemainingTime(false, now); - long nextUpdate = 0; - if (game.timeController.clockRunning()) { - long t = game.currPos().whiteMove ? wTime : bTime; - nextUpdate = (t % 1000); - if (nextUpdate < 0) nextUpdate += 1000; - nextUpdate += 1; - } - gui.setRemainingTime(wTime, bTime, nextUpdate); - } - - final private void setSelection() { + /** Mark last played move in the GUI. */ + private final void setSelection() { Move m = game.getLastMove(); int sq = ((m != null) && (m.from != m.to)) ? m.to : -1; gui.setSelection(sq); @@ -839,89 +1023,6 @@ public class DroidChessController { return false; } - public final synchronized void setEngineStrength(String engine, int strength) { - boolean newEngine = !engine.equals(this.engine); - if (newEngine) - numPV = 1; - if (newEngine || (strength != this.strength)) { - this.engine = engine; - this.strength = strength; - if (newEngine && ((analysisThread != null) || (computerThread != null))) { - stopAnalysis(); - updateComputeThreads(true); - updateGUI(); - } - } - } - - public final synchronized int maxPV() { - if (computerPlayer == null) - return 1; - return computerPlayer.getMaxPV(); - } - - public final int getNumPV() { - return this.numPV; - } - - public final synchronized void setMultiPVMode(int numPV) { - if (numPV < 1) numPV = 1; - if (numPV > maxPV()) numPV = maxPV(); - if (numPV != this.numPV) { - this.numPV = numPV; - if (analysisThread != null) { - stopAnalysis(); - ponderMove = null; - updateComputeThreads(true); - updateGUI(); - } - } - } - - public final synchronized void setTimeLimit(int time, int moves, int inc) { - timeControl = time; - movesPerSession = moves; - timeIncrement = inc; - if (game != null) - game.timeController.setTimeControl(timeControl, movesPerSession, timeIncrement); - } - - public final synchronized void stopSearch() { - if (computerThread != null) { - computerPlayer.stopSearch(); - } - } - - public final synchronized void stopPonder() { - if ((computerThread != null) && humansTurn()) { - boolean updateGui = stopComputerThinking(); - if (updateGui) - updateGUI(); - } - } - - private Object shutdownEngineLock = new Object(); - public final synchronized void shutdownEngine() { - synchronized (shutdownEngineLock) { - gameMode = new GameMode(GameMode.TWO_PLAYERS); - ss.searchResultWanted = false; - boolean updateGui = stopComputerThinking(); - updateGui |= stopAnalysis(); - if (updateGui) - updateGUI(); - ponderMove = null; - computerPlayer.shutdownEngine(); - } - } - - /** Help human to claim a draw by trying to find and execute a valid draw claim. */ - public final boolean claimDrawIfPossible() { - if (!findValidDrawClaim()) - return false; - updateGUI(); - return true; - } - private final boolean findValidDrawClaim() { if (game.getGameState() != GameState.ALIVE) return true; game.processString("draw accept"); @@ -932,45 +1033,4 @@ public class DroidChessController { if (game.getGameState() != GameState.ALIVE) return true; return false; } - - public final void resignGame() { - if (game.getGameState() == GameState.ALIVE) { - game.processString("resign"); - updateGUI(); - } - } - - public final void setHeaders(Map headers) { - game.tree.setHeaders(headers); - gameTextListener.clear(); - updateGUI(); - } - - public final void getHeaders(Map headers) { - game.tree.getHeaders(headers); - } - - public static final class CommentInfo { - public String move; - public String preComment, postComment; - public int nag; - } - public final CommentInfo getComments() { - Node cur = game.tree.currentNode; - CommentInfo ret = new CommentInfo(); - ret.move = cur.moveStr; - ret.preComment = cur.preComment; - ret.postComment = cur.postComment; - ret.nag = cur.nag; - return ret; - } - - public final void setComments(CommentInfo commInfo) { - Node cur = game.tree.currentNode; - cur.preComment = commInfo.preComment; - cur.postComment = commInfo.postComment; - cur.nag = commInfo.nag; - gameTextListener.clear(); - updateGUI(); - } } diff --git a/DroidFish/src/org/petero/droidfish/gamelogic/Game.java b/DroidFish/src/org/petero/droidfish/gamelogic/Game.java index 8cb4ced..0316aaa 100644 --- a/DroidFish/src/org/petero/droidfish/gamelogic/Game.java +++ b/DroidFish/src/org/petero/droidfish/gamelogic/Game.java @@ -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() { diff --git a/DroidFish/src/org/petero/droidfish/gamelogic/GameTree.java b/DroidFish/src/org/petero/droidfish/gamelogic/GameTree.java index a23cebd..d8d6620 100644 --- a/DroidFish/src/org/petero/droidfish/gamelogic/GameTree.java +++ b/DroidFish/src/org/petero/droidfish/gamelogic/GameTree.java @@ -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 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 headers) { for (Entry entry : headers.entrySet()) { String tag = entry.getKey(); @@ -1417,6 +1422,7 @@ public class GameTree { } } + /** Get PGN header tags and values. */ void getHeaders(Map headers) { headers.put("Event", event); headers.put("Site", site); diff --git a/DroidFishTest/src/org/petero/droidfish/gamelogic/GameTreeTest.java b/DroidFishTest/src/org/petero/droidfish/gamelogic/GameTreeTest.java index 4255166..3965222 100644 --- a/DroidFishTest/src/org/petero/droidfish/gamelogic/GameTreeTest.java +++ b/DroidFishTest/src/org/petero/droidfish/gamelogic/GameTreeTest.java @@ -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));