diff --git a/pom.xml b/pom.xml index bd4eaa6..22a906f 100644 --- a/pom.xml +++ b/pom.xml @@ -37,7 +37,7 @@ 25 UTF-8 - 1.10.4 + 1.12.0 1.0.0 1.20.0 6.1.2 diff --git a/src/main/java/dicechess/bot/OnnxStrategy.java b/src/main/java/dicechess/bot/OnnxStrategy.java index 16e26cd..ef2b9fe 100644 --- a/src/main/java/dicechess/bot/OnnxStrategy.java +++ b/src/main/java/dicechess/bot/OnnxStrategy.java @@ -1,43 +1,26 @@ package dicechess.bot; -import dicechess.engine.domain.FenParser; import dicechess.engine.domain.GameState; -import dicechess.engine.search.TurnGenerator; +import dicechess.engine.jvmapi.JvmApi; import lv.id.jc.dicechess.runtime.TurnContext; -import scala.collection.immutable.List; - -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; import java.lang.System.Logger; import java.lang.System.Logger.Level; -import java.util.ArrayList; import java.util.Collections; +import java.util.List; /** - * Strategy implementation using an ONNX value model (or fallback engine evaluation) - * over all legal full-turn paths generated by TurnGenerator. + * Strategy implementation using an ONNX value model (or fallback engine evaluation) over every legal full turn + * available from the current position, via the engine's {@link JvmApi} facade. * - *

TurnGenerator returns {@code List[List[Move]]} where {@code Move} is an opaque - * Int with layout: bits 0-5 = destination (to), bits 6-11 = source (from), - * bits 12-15 = flags.

+ *

{@link TurnContext#legalMoves()} is not consulted here: scoring a candidate turn needs the resulting board + * position, not just its UCI tokens, so the engine's own enumeration ({@link JvmApi#legalTurns(GameState)}) has to + * run regardless of whether the platform's inline tree was present.

*/ public class OnnxStrategy implements Strategy { private static final Logger logger = System.getLogger(OnnxStrategy.class.getName()); - private static final MethodHandle MAKE_MOVE_MH; - - static { - try { - var clazz = Class.forName("dicechess.engine.domain.Position$package"); - var methodType = MethodType.methodType(GameState.class, GameState.class, int.class); - MAKE_MOVE_MH = MethodHandles.lookup().findStatic(clazz, "makeMove_Move", methodType); - } catch (Exception e) { - throw new ExceptionInInitializerError(e); - } - } private final OnnxEvaluator evaluator; @@ -51,120 +34,40 @@ public OnnxStrategy(OnnxEvaluator evaluator) { } @Override - public java.util.List chooseMoves(TurnContext context) { + public List chooseMoves(TurnContext context) { if (context == null || context.dfen() == null || context.dfen().isBlank()) { logger.log(Level.WARNING, "Received empty or null DFEN context"); return Collections.emptyList(); } - var parseResult = FenParser.parse(context.dfen()); - if (parseResult.isLeft()) { - var errorMsg = parseResult.left().toOption().isDefined() - ? parseResult.left().toOption().get() - : "Unknown FEN error"; - logger.log(Level.ERROR, "Failed to parse DFEN ''{0}'': {1}", context.dfen(), errorMsg); + GameState initialState; + try { + initialState = JvmApi.parseDfen(context.dfen()); + } catch (IllegalArgumentException e) { + logger.log(Level.ERROR, "Failed to parse DFEN ''{0}'': {1}", context.dfen(), e.getMessage()); return Collections.emptyList(); } - var initialState = parseResult.toOption().get(); - var activeColor = initialState.flags() & 1; // Bit 0 of flags is activeColor (0 = White, 1 = Black) - - // TurnGenerator returns List[List[Move]] - @SuppressWarnings("unchecked") - var legalTurnPaths = - (List>) (Object) TurnGenerator.generateAllLegalTurnPaths(initialState); + var activeColor = JvmApi.activeColor(initialState); + var turns = JvmApi.legalTurns(initialState); - if (legalTurnPaths.isEmpty()) { + if (turns.isEmpty()) { logger.log(Level.INFO, "No legal turn paths available for DFEN: {0}", context.dfen()); return Collections.emptyList(); } - java.util.List bestPath = null; + JvmApi.Turn bestTurn = null; float bestScore = -Float.MAX_VALUE; - for (var pathScala : scala.jdk.javaapi.CollectionConverters.asJava(legalTurnPaths)) { - var pathJava = toJavaIntegerList(pathScala); - - // Apply sequence of Move values using makeMove_Move to get final turn state - var currentState = initialState; - for (var moveInt : pathJava) { - currentState = applyMove(currentState, moveInt); - } - - // Score final state from initial mover's perspective - var score = evaluator.evaluate(currentState, activeColor); - - if (score > bestScore || bestPath == null) { + for (var turn : turns) { + var score = evaluator.evaluate(turn.finalState(), activeColor); + if (bestTurn == null || score > bestScore) { bestScore = score; - bestPath = pathJava; + bestTurn = turn; } } - if (bestPath == null || bestPath.isEmpty()) { - return Collections.emptyList(); - } - - var moveNotations = new ArrayList(); - for (var moveInt : bestPath) { - moveNotations.add(moveToNotation(moveInt)); - } - - logger.log(Level.DEBUG, "Chosen turn path: {0} with score {1}", moveNotations, bestScore); - return moveNotations; - } - - private static GameState applyMove(GameState state, int moveInt) { - try { - return (GameState) MAKE_MOVE_MH.invokeExact(state, moveInt); - } catch (Throwable e) { - throw new IllegalStateException("Failed to apply move via engine: " + moveInt, e); - } - } - - /** - * Converts a packed Move int to long algebraic notation (e.g. "e2e4", "e7e8q"). - * - *

Move layout (from Move.scala): - *

    - *
  • Bits 0-5: destination square (to)
  • - *
  • Bits 6-11: source square (from)
  • - *
  • Bits 12-15: move flags
  • - *
- */ - static String moveToNotation(int move) { - var to = move & 0x3f; - var from = (move >>> 6) & 0x3f; - var flags = (move >>> 12) & 0x0f; - - var fromFile = (char) ('a' + (from % 8)); - var fromRank = (from / 8) + 1; - var toFile = (char) ('a' + (to % 8)); - var toRank = (to / 8) + 1; - - // Promotion: flags bit 3 set (flags >= 8) - var promStr = ""; - if ((flags & 8) != 0) { - promStr = switch (flags & 3) { - case 0 -> "n"; // KnightPromotion(8) or KnightPromoCapture(12) - case 1 -> "b"; // BishopPromotion(9) or BishopPromoCapture(13) - case 2 -> "r"; // RookPromotion(10) or RookPromoCapture(14) - case 3 -> "q"; // QueenPromotion(11) or QueenPromoCapture(15) - default -> ""; - }; - } - - return "" + fromFile + fromRank + toFile + toRank + promStr; - } - - private static java.util.List toJavaIntegerList(List scalaList) { - var result = new ArrayList(); - for (var item : scala.jdk.javaapi.CollectionConverters.asJava(scalaList)) { - if (item instanceof Integer intValue) { - result.add(intValue); - } else if (item instanceof Number number) { - result.add(number.intValue()); - } - } - return result; + logger.log(Level.DEBUG, "Chosen turn path: {0} with score {1}", bestTurn.uci(), bestScore); + return bestTurn.uci(); } }