diff --git a/pom.xml b/pom.xml index bd4d01f..4ccf9ca 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ 1.0.8 jchess-uci - 2.0.2-SNAPSHOT + 2.0.3-SNAPSHOT UTF-8 diff --git a/src/main/java/com/fathzer/jchess/uci/ClassicalOptions.java b/src/main/java/com/fathzer/jchess/uci/ClassicalOptions.java index 724c9f9..9067e69 100644 --- a/src/main/java/com/fathzer/jchess/uci/ClassicalOptions.java +++ b/src/main/java/com/fathzer/jchess/uci/ClassicalOptions.java @@ -42,8 +42,8 @@ public static CheckOption limitStrength(Consumer trigger) { return new CheckOption(LIMIT_STRENGTH_NAME, trigger, false); } - public static SpinOption multiPV(Consumer trigger, int maxValue) { - return new IntegerSpinOption(MULTI_PV_NAME, trigger, 1, 1, maxValue); + public static SpinOption multiPV(Consumer trigger) { + return new IntegerSpinOption(MULTI_PV_NAME, trigger, 1, 1, 256); } public static CheckOption ownBook(Consumer trigger, boolean defaultValue) { diff --git a/src/main/java/com/fathzer/jchess/uci/GoReply.java b/src/main/java/com/fathzer/jchess/uci/GoReply.java index f1fb659..0a72467 100644 --- a/src/main/java/com/fathzer/jchess/uci/GoReply.java +++ b/src/main/java/com/fathzer/jchess/uci/GoReply.java @@ -154,14 +154,20 @@ public String toString() { * @return The line or an empty optional if no information is available */ public Optional getMainInfoString() { - if (info==null) { - return Optional.empty(); - } + return getInfoString(0); + } + + /** Gets the uci info line to return just before sending the reply. + * @param index The move index (0 for the best move or the index or the extra moves passed to {@code Info#setExtraMoves(List)} +1 + * @return The line or an empty optional if no information is available + */ + public Optional getInfoString(int index) { final StringBuilder builder = new StringBuilder(); if (info.depth>0) { builder.append("depth ").append(info.depth); } - final Optional score = info.scoreBuilder.apply(bestMove); + final UCIMove move = index==0 ? bestMove : info.extraMoves.get(index-1); + final Optional score = info.scoreBuilder.apply(move); if (score.isPresent()) { if (!builder.isEmpty()) { builder.append(' '); @@ -174,13 +180,13 @@ public Optional getMainInfoString() { } builder.append("hashfull ").append(info.hashFull); } - final Optional> pv = info.pvBuilder.apply(bestMove); + final Optional> pv = info.pvBuilder.apply(move); if (pv.isPresent()) { if (!builder.isEmpty()) { builder.append(' '); } final String moves = String.join(" ", pv.get().stream().map(UCIMove::toString).toList()); - builder.append("multipv 1 pv ").append(moves); + builder.append("multipv ").append(index+1).append(" pv ").append(moves); } return builder.isEmpty() ? Optional.empty() : Optional.of("info "+builder); } diff --git a/src/main/java/com/fathzer/jchess/uci/UCI.java b/src/main/java/com/fathzer/jchess/uci/UCI.java index 8d03c46..30ccde5 100644 --- a/src/main/java/com/fathzer/jchess/uci/UCI.java +++ b/src/main/java/com/fathzer/jchess/uci/UCI.java @@ -210,7 +210,12 @@ protected void doGo(Deque tokens) { final boolean started = doBackground(() -> { final GoReply goReply = task.get(); final Optional mainInfo = goReply.getMainInfoString(); - mainInfo.ifPresent(this::out); + if (mainInfo.isPresent()) { + this.out(mainInfo.get()); + for (int i = 1; i <= goReply.getInfo().get().getExtraMoves().size(); i++) { + this.out(goReply.getInfoString(i).get()); + } + } out(goReply.toString()); }, task::stop, e -> err(GO_CMD, e)); if (!started) { diff --git a/src/main/java/com/fathzer/jchess/uci/extended/SpeedTest.java b/src/main/java/com/fathzer/jchess/uci/extended/SpeedTest.java index 9c66e60..9a0786e 100644 --- a/src/main/java/com/fathzer/jchess/uci/extended/SpeedTest.java +++ b/src/main/java/com/fathzer/jchess/uci/extended/SpeedTest.java @@ -59,7 +59,7 @@ public SpeedTest(AbstractEngine engine) { private Result fill(String fen) { uciEngine.newGame(); uciEngine.setStartPosition(fen); - return new Result<>(fen, uciEngine.getEngine().getBestMoves(uciEngine.get())); + return new Result<>(fen, uciEngine.getEngine().getBestMoves(uciEngine.get()).getBestMoves()); } /** Launches the test. diff --git a/src/main/java/com/fathzer/jchess/uci/helper/AbstractEngine.java b/src/main/java/com/fathzer/jchess/uci/helper/AbstractEngine.java index e9e1ec3..e65f838 100644 --- a/src/main/java/com/fathzer/jchess/uci/helper/AbstractEngine.java +++ b/src/main/java/com/fathzer/jchess/uci/helper/AbstractEngine.java @@ -6,6 +6,7 @@ import java.util.Map; import java.util.Optional; import java.util.function.Supplier; +import java.util.stream.Collectors; import com.fathzer.games.MoveGenerator; import com.fathzer.games.MoveGenerator.MoveConfidence; @@ -14,7 +15,7 @@ import com.fathzer.games.ai.evaluation.Evaluation.Type; import com.fathzer.games.ai.evaluation.Evaluator; import com.fathzer.games.ai.iterativedeepening.IterativeDeepeningEngine; -import com.fathzer.games.ai.iterativedeepening.IterativeDeepeningEngine.BestMove; +import com.fathzer.games.ai.iterativedeepening.SearchHistory; import com.fathzer.games.ai.time.TimeManager; import com.fathzer.games.ai.transposition.TranspositionTable; import com.fathzer.jchess.uci.GoReply; @@ -98,6 +99,7 @@ public List> getOptions() { options.add(new ComboOption("evaluation", this::setEvaluator, defaultEvaluator, evaluatorBuilders.keySet())); } options.add(ClassicalOptions.threads(this.engine::setParallelism, defaultThreads)); + options.add(ClassicalOptions.multiPV(this.engine.getDeepeningPolicy()::setSize)); options.add(new IntegerSpinOption("depth", this.engine.getDeepeningPolicy()::setDepth, defaultDepth, 1, 128)); options.add(new LongSpinOption("maxtime", this.engine.getDeepeningPolicy()::setMaxTime, defaultMaxTime, 1, Long.MAX_VALUE)); return options; @@ -130,24 +132,27 @@ public GoReply get() { final UCIEngineSearchConfiguration c = new UCIEngineSearchConfiguration<>(timeManager); final UCIEngineSearchConfiguration.EngineConfiguration previous = c.configure(engine, options, board); final List candidates = options.getMoveToSearch().stream().map(AbstractEngine.this::toMove).toList(); - final Optional> best = engine.getBestMove(board, candidates.isEmpty() ? null : candidates); + final SearchHistory search = engine.getBestMoves(board, candidates.isEmpty() ? null : candidates); c.set(engine, previous); - if (best.isEmpty()) { + if (search.isEmpty()) { return new GoReply(null); } - final EvaluatedMove move = best.get().move(); + final EvaluatedMove move = getChoice(search); final GoReply goReply = new GoReply(toUCI(move.getContent())); - final Info info = new Info(best.get().depth()); + final Info info = new Info(search.getDepth()); final TranspositionTable tt = engine.getTranspositionTable(); final int entryCount = tt.getEntryCount(); if (entryCount>0) { info.setHashFull((int)(1000L*entryCount/tt.getSize())); } - info.setScoreBuilder(m -> toScore(toMove(m), move)); + final List> bestMoves = search.getBestMoves(); + final Map> scores = bestMoves.stream().collect(Collectors.toMap(em -> toUCI(em.getContent()).toString(), em -> toScore(em.getEvaluation()))); + info.setScoreBuilder(m -> scores.get(m.toString())); info.setPvBuilder(m -> { final List list = tt.collectPV(board, toMove(m), info.getDepth()).stream().map(x -> toUCI(x)).toList(); return list.isEmpty() ? Optional.empty() : Optional.of(list); }); + info.setExtraMoves(bestMoves.stream().filter(em -> !move.getContent().equals(em.getContent())).map(em->toUCI(em.getContent())).toList()); goReply.setInfo(info); return goReply; } @@ -160,10 +165,13 @@ public void stop() { }; } - private Optional toScore(M m, EvaluatedMove known) { - final Evaluation evaluation = known.getEvaluation(); + private EvaluatedMove getChoice(SearchHistory history) { + return history.getBestMoves().get(0); + } + + private Optional toScore(Evaluation evaluation) { final Type type = evaluation.getType(); - if (!m.equals(known.getContent()) || type==Type.UNKNOWN) { + if (type==Type.UNKNOWN) { return Optional.empty(); } final Score score; diff --git a/src/main/java/com/fathzer/jchess/uci/helper/UCIEngineSearchConfiguration.java b/src/main/java/com/fathzer/jchess/uci/helper/UCIEngineSearchConfiguration.java index b6da415..7b2d3c0 100644 --- a/src/main/java/com/fathzer/jchess/uci/helper/UCIEngineSearchConfiguration.java +++ b/src/main/java/com/fathzer/jchess/uci/helper/UCIEngineSearchConfiguration.java @@ -14,10 +14,12 @@ public class UCIEngineSearchConfiguration> { public static class EngineConfiguration { private long maxTime; private int depth; + private boolean deepenOnForced; private EngineConfiguration(IterativeDeepeningEngine engine) { maxTime = engine.getDeepeningPolicy().getMaxTime(); depth = engine.getDeepeningPolicy().getDepth(); + deepenOnForced = engine.getDeepeningPolicy().isDeepenOnForced(); } } @@ -35,6 +37,7 @@ public EngineConfiguration configure(IterativeDeepeningEngine engine, GoPa } if (options.getDepth()>0) { engine.getDeepeningPolicy().setDepth(options.getDepth()); + engine.getDeepeningPolicy().setDeepenOnForced(true); } if (timeOptions.getMoveTimeMs()>0) { engine.getDeepeningPolicy().setMaxTime(timeOptions.getMoveTimeMs()); @@ -50,6 +53,7 @@ public EngineConfiguration configure(IterativeDeepeningEngine engine, GoPa public void set(IterativeDeepeningEngine engine, EngineConfiguration c) { engine.getDeepeningPolicy().setMaxTime(c.maxTime); engine.getDeepeningPolicy().setDepth(c.depth); + engine.getDeepeningPolicy().setDeepenOnForced(c.deepenOnForced); } public long getMaxTime(B board, long remainingMs, long incrementMs, int movesToGo) {