From 67e03495dc8f7798fe289ba683c94edb1757cbee Mon Sep 17 00:00:00 2001 From: Peter Osterlund Date: Sat, 11 Apr 2020 01:58:26 +0200 Subject: [PATCH] Implement maxNPS UCI option for CuckooChess This is an alternative way to limit the engine strength. It also has the advantage of reducing heat and battery drain. --- .../src/main/java/uci/EngineControl.java | 6 +++- .../src/main/java/chess/Search.java | 29 +++++++++++++++++-- .../cuckoochess/DroidEngineControl.java | 10 +++++-- 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/CuckooChess/src/main/java/uci/EngineControl.java b/CuckooChess/src/main/java/uci/EngineControl.java index 6573497..57cc6ff 100644 --- a/CuckooChess/src/main/java/uci/EngineControl.java +++ b/CuckooChess/src/main/java/uci/EngineControl.java @@ -73,6 +73,7 @@ public class EngineControl { // Reduced strength variables private int strength = 1000; + private int maxNPS = 0; private long randomSeed = 0; /** @@ -226,7 +227,7 @@ public class EngineControl { sc = new Search(pos, posHashList, posHashListSize, tt, ht); sc.timeLimit(minTimeLimit, maxTimeLimit); sc.setListener(new SearchListener(os)); - sc.setStrength(strength, randomSeed); + sc.setStrength(strength, randomSeed, maxNPS); MoveGen.MoveList moves = moveGen.pseudoLegalMoves(pos); MoveGen.removeIllegal(pos, moves); if ((searchMoves != null) && (searchMoves.size() > 0)) @@ -374,6 +375,7 @@ public class EngineControl { os.printf("option name UCI_EngineAbout type string default %s by Peter Osterlund, see %s%n", ComputerPlayer.engineName, "http://hem.bredband.net/petero2b/javachess/index.html"); os.print("option name Strength type spin default 1000 min 0 max 1000\n"); + os.print("option name maxNPS type spin default 0 min 0 max 10000000\n"); for (String pName : Parameters.instance().getParamNames()) { ParamBase p = Parameters.instance().getParam(pName); @@ -424,6 +426,8 @@ public class EngineControl { analyseMode = Boolean.parseBoolean(optionValue); } else if (optionName.equals("strength")) { strength = Integer.parseInt(optionValue); + } else if (optionName.equals("maxnps")) { + maxNPS = Integer.parseInt(optionValue); } else { Parameters.instance().set(optionName, optionValue); } diff --git a/CuckooChessEngine/src/main/java/chess/Search.java b/CuckooChessEngine/src/main/java/chess/Search.java index 91e0290..adf1de8 100644 --- a/CuckooChessEngine/src/main/java/chess/Search.java +++ b/CuckooChessEngine/src/main/java/chess/Search.java @@ -66,6 +66,7 @@ public class Search { // Reduced strength variables private int strength = 1000; // Strength (0-1000) + private int maxNPS = 0; // If > 0, reduce strength by limiting NPS private boolean weak = false; // Set to strength < 1000 private long randomSeed = 0; @@ -170,12 +171,16 @@ public class Search { maxTimeMillis = maxTimeLimit; } - final public void setStrength(int strength, long randomSeed) { + final public void setStrength(int strength, long randomSeed, int maxNPS) { if (strength < 0) strength = 0; if (strength > 1000) strength = 1000; this.strength = strength; weak = strength < 1000; this.randomSeed = randomSeed; + this.maxNPS = maxNPS; + nodesBetweenTimeCheck = 5000; + if (maxNPS > 0) + nodesBetweenTimeCheck = Math.min(Math.max(maxNPS / 100, 1), nodesBetweenTimeCheck); } final public Move iterativeDeepening(MoveGen.MoveList scMovesIn, @@ -264,6 +269,9 @@ public class Search { mi, "-", alpha, beta, "-", "-"); System.out.printf("%-6s...\n", TextIO.moveToUCIString(m)); */ pos.makeMove(m, ui); + nodes++; + totalNodes++; + nodesToGo--; SearchTreeInfo sti = searchTreeInfo[0]; sti.currentMove = m; sti.lmr = lmrS; @@ -301,6 +309,9 @@ public class Search { nodes = qNodes = 0; posHashList[posHashListSize++] = pos.zobristHash(); pos.makeMove(m, ui); + nodes++; + totalNodes++; + nodesToGo--; int score2 = -negaScout(-beta, -score, 1, depthS - plyScale, -1, givesCheck); score = Math.max(score, score2); nodesThisMove += nodes + qNodes; @@ -320,6 +331,9 @@ public class Search { nodes = qNodes = 0; posHashList[posHashListSize++] = pos.zobristHash(); pos.makeMove(m, ui); + nodes++; + totalNodes++; + nodesToGo--; score = -negaScout(-score, -alpha, 1, depthS - plyScale, -1, givesCheck); nodesThisMove += nodes + qNodes; posHashListSize--; @@ -459,7 +473,7 @@ public class Search { long idx = log.logNodeStart(sti.nodeIdx, sti.currentMove, alpha, beta, ply, depth/plyScale); searchTreeInfo[ply].nodeIdx = idx; } - if (--nodesToGo <= 0) { + if (nodesToGo <= 0) { nodesToGo = nodesBetweenTimeCheck; long tNow = System.currentTimeMillis(); long timeLimit = searchNeedMoreTime ? maxTimeMillis : minTimeMillis; @@ -467,6 +481,15 @@ public class Search { ((maxNodes >= 0) && (totalNodes >= maxNodes))) { throw new StopSearch(); } + if (maxNPS > 0) { + long time = tNow - tStart; + if (totalNodes * 1000.0 > maxNPS * Math.max(1, time)) { + long wantedTime = totalNodes * 1000 / maxNPS; + long sleepTime = wantedTime - time; + if (sleepTime > 0) + try { Thread.sleep(sleepTime); } catch (InterruptedException ignore) {} + } + } if (tNow - tLastStats >= 1000) { notifyStats(); } @@ -797,6 +820,7 @@ public class Search { pos.makeMove(m, ui); nodes++; totalNodes++; + nodesToGo--; sti.currentMove = m; /* long nodes0 = nodes; long qNodes0 = qNodes; @@ -1021,6 +1045,7 @@ public class Search { pos.makeMove(m, ui); qNodes++; totalNodes++; + nodesToGo--; score = -quiesce(-beta, -alpha, ply + 1, depth - 1, nextInCheck); pos.unMakeMove(m, ui); if (score > bestScore) { diff --git a/DroidFishApp/src/main/java/org/petero/droidfish/engine/cuckoochess/DroidEngineControl.java b/DroidFishApp/src/main/java/org/petero/droidfish/engine/cuckoochess/DroidEngineControl.java index bab5279..1880bca 100644 --- a/DroidFishApp/src/main/java/org/petero/droidfish/engine/cuckoochess/DroidEngineControl.java +++ b/DroidFishApp/src/main/java/org/petero/droidfish/engine/cuckoochess/DroidEngineControl.java @@ -70,6 +70,7 @@ public class DroidEngineControl { // Reduced strength variables private int strength = 1000; + private int maxNPS = 0; private long randomSeed = 0; private Random rndGen = new Random(); @@ -224,8 +225,8 @@ public class DroidEngineControl { sc = new Search(pos, posHashList, posHashListSize, tt, ht); sc.timeLimit(minTimeLimit, maxTimeLimit); sc.setListener(new SearchListener(os)); - sc.setStrength(strength, randomSeed); - sc.nodesBetweenTimeCheck = 500; + sc.setStrength(strength, randomSeed, maxNPS); + sc.nodesBetweenTimeCheck = Math.min(500, sc.nodesBetweenTimeCheck); MoveGen.MoveList moves = moveGen.pseudoLegalMoves(pos); MoveGen.removeIllegal(pos, moves); if ((searchMoves != null) && (searchMoves.size() > 0)) @@ -367,9 +368,10 @@ public class DroidEngineControl { os.printLine("option name OwnBook type check default false"); os.printLine("option name Ponder type check default true"); os.printLine("option name UCI_AnalyseMode type check default false"); - os.printLine("option name UCI_EngineAbout type string default %s by Peter Osterlund, see http://web.comhem.se/petero2home/javachess/index.html", + os.printLine("option name UCI_EngineAbout type string default %s by Peter Osterlund, see http://hem.bredband.net/petero2b/javachess/index.html", ComputerPlayer.engineName); os.printLine("option name Strength type spin default 1000 min 0 max 1000"); + os.printLine("option name maxNPS type spin default 0 min 0 max 10000000"); } final void setOption(String optionName, String optionValue) { @@ -385,6 +387,8 @@ public class DroidEngineControl { analyseMode = Boolean.parseBoolean(optionValue); } else if (optionName.equals("strength")) { strength = Integer.parseInt(optionValue); + } else if (optionName.equals("maxnps")) { + maxNPS = Integer.parseInt(optionValue); } } catch (NumberFormatException ignore) { }