diff --git a/.project b/.project index e8849b3..85fd998 100644 --- a/.project +++ b/.project @@ -20,4 +20,15 @@ org.eclipse.jdt.core.javanature org.eclipse.m2e.core.maven2Nature + + + 1737039058705 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + diff --git a/README.md b/README.md index e4a9f61..43ed93e 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,12 @@ # chesslib-uci-engine A basic [UCI](https://en.wikipedia.org/wiki/Universal_Chess_Interface) engine based on the [bhlangonijr/chesslib](https://github.com/bhlangonijr/chesslib) move generator, and the [games-core](https://github.com/fathzer-games/games-core) alpha beta search algorithm implementation. It uses the evaluation functions, the remaining move oracle and some other things from the [chess-utils](https://github.com/fathzer-games/chess-utils) library. -You can play against it on [Lichess](https://lichess.org/@/fathzer-jchess). It runs on a Minisforum U300 with [3867U processor](https://www.cpubenchmark.net/cpu.php?cpu=Intel+Celeron+3867U+%40+1.80GHz&id=3442). +You can play against it on [Lichess](https://lichess.org/@/fathzer-jchess). It runs on a Nipogi e2 with N97 processor](https://www.passmark.com/baselines/V11/display.php?id=284967660181). ## How to run the engine It requires a Java 17+ virtual machine. -Download the jar [here](https://fathzer-games.github.io/chesslib-uci-engine/chesslib-uci-engine.jar), then Launch the engine with the following command: ```java -jar chesslib-uci-engine.jar``` +Download the jar [here](https://fathzer-games.github.io/chesslib-uci-engine/chesslib-uci-engine.jar), then launch the engine with the following command: ```java -jar chesslib-uci-engine.jar``` ### Openings library You can use an opening library located at a URL using the ```openingsUrl``` system property. diff --git a/pom.xml b/pom.xml index 670b2cb..e079b06 100644 --- a/pom.xml +++ b/pom.xml @@ -7,13 +7,13 @@ com.fathzer parent-pom - 1.0.8 + 1.0.9 chesslib-uci-engine - 0.0.3 + 0.0.4 chesslib-uci-engine - A basic uci engine plugin for jchess-uci. + A basic uci engine based on chesslib move generator. https://github.com/fathzer-games/chesslib-uci-engine @@ -27,6 +27,7 @@ 17 fathzer-games chesslib-uci-engine + yyyyMMdd HH-mm-ss @@ -40,60 +41,85 @@ com.github.bhlangonijr chesslib - 1.3.3 + 1.3.4 com.fathzer chess-utils - 0.0.5-SNAPSHOT + 0.0.1 com.fathzer games-core - 0.0.12-SNAPSHOT + 0.0.1 com.fathzer jchess-uci - 2.0.4-SNAPSHOT + 2.0.0 org.json json - 20240205 + 20250107 com.fathzer jchess-perft-dataset - 1.0.0 + 2.0.0 org.slf4j slf4j-api - 2.0.7 + 2.0.16 org.slf4j slf4j-simple - 2.0.7 + 2.0.16 runtime - - org.junit.jupiter - junit-jupiter - 5.10.2 - test - org.awaitility awaitility 4.2.2 test + + com.fathzer + chess-test-utils + 0.0.1 + test + + + org.apache.maven.plugins + maven-antrun-plugin + 3.1.0 + + + generate-build-info + generate-resources + + run + + + + + + + + + + + + + + + maven-shade-plugin 3.4.1 diff --git a/runConfigs/ChessLib-uci-engine (PerfTTest).launch b/runConfigs/ChessLib-uci-engine (PerfTTest).launch index 435ad64..5e9c2ba 100644 --- a/runConfigs/ChessLib-uci-engine (PerfTTest).launch +++ b/runConfigs/ChessLib-uci-engine (PerfTTest).launch @@ -8,6 +8,7 @@ + @@ -19,5 +20,5 @@ - + diff --git a/src/main/java/com/fathzer/jchess/chesslib/ai/DefaultLogger.java b/src/main/java/com/fathzer/jchess/chesslib/ai/DefaultLogger.java index bd0a695..996e9ee 100644 --- a/src/main/java/com/fathzer/jchess/chesslib/ai/DefaultLogger.java +++ b/src/main/java/com/fathzer/jchess/chesslib/ai/DefaultLogger.java @@ -51,18 +51,15 @@ public void logEndedByPolicy(int depth) { @Override public void logSearchEnd(ChessLibMoveGenerator board, SearchHistory result) { - log.info("--- End of iterative evaluation returns: {}", result.getBestMoves()); - } - - @Override - public void logMoveChosen(ChessLibMoveGenerator board, EvaluatedMove evaluatedMove) { - if (evaluatedMove==null) { + log.info("--- End of iterative evaluation returns: {}", result.getAccurateMoves()); + if (result.isEmpty()) { log.info("No valid move found"); } else { - Move move = evaluatedMove.getContent(); + //TODO Not the right place, the engine could return another move, especially when there is tie moves + EvaluatedMove evaluatedMove = result.getAccurateMoves().get(0); + Move move = evaluatedMove.getMove(); log.info("Move chosen :{}", move); - final List pv = evaluatedMove.getPrincipalVariation(); - log.info("pv: {}", pv); + log.info("pv: {}", engine.getTranspositionTable().collectPV(board, move, result.getLastDepth())); } } } diff --git a/src/main/java/com/fathzer/jchess/chesslib/ai/TT.java b/src/main/java/com/fathzer/jchess/chesslib/ai/TT.java index 935cee0..ff93453 100644 --- a/src/main/java/com/fathzer/jchess/chesslib/ai/TT.java +++ b/src/main/java/com/fathzer/jchess/chesslib/ai/TT.java @@ -2,11 +2,12 @@ import com.fathzer.games.ai.transposition.OneLongEntryTranspositionTable; import com.fathzer.games.ai.transposition.SizeUnit; +import com.fathzer.jchess.chesslib.ChessLibMoveGenerator; import com.github.bhlangonijr.chesslib.Piece; import com.github.bhlangonijr.chesslib.Square; import com.github.bhlangonijr.chesslib.move.Move; -public class TT extends OneLongEntryTranspositionTable { +public class TT extends OneLongEntryTranspositionTable { // Move is encoded as an int: // 12 bits for source, 12 bits for destination // 8 bits for promotion diff --git a/src/main/java/com/fathzer/jchess/chesslib/uci/ChessLibEngine.java b/src/main/java/com/fathzer/jchess/chesslib/uci/ChessLibEngine.java index 39ca7e8..460c28c 100644 --- a/src/main/java/com/fathzer/jchess/chesslib/uci/ChessLibEngine.java +++ b/src/main/java/com/fathzer/jchess/chesslib/uci/ChessLibEngine.java @@ -17,7 +17,7 @@ import com.fathzer.games.ai.time.BasicTimeManager; import com.fathzer.games.ai.transposition.SizeUnit; import com.fathzer.games.ai.transposition.TranspositionTable; -import com.fathzer.games.perft.TestableMoveGeneratorBuilder; +import com.fathzer.games.perft.FromPositionMoveGeneratorBuilder; import com.fathzer.games.util.PhysicalCores; import com.fathzer.games.util.exec.ExecutionContext; import com.fathzer.jchess.chesslib.ChessLibMoveGenerator; @@ -40,7 +40,7 @@ import com.github.bhlangonijr.chesslib.Square; import com.github.bhlangonijr.chesslib.move.Move; -public class ChessLibEngine extends AbstractEngine implements TestableMoveGeneratorBuilder, Displayable { +public class ChessLibEngine extends AbstractEngine implements FromPositionMoveGeneratorBuilder, Displayable { private static final List> EVALUATORS = Arrays.asList( new EvaluatorConfiguration<>("pesto",PestoEvaluator::new), new EvaluatorConfiguration<>("simplified",SimplifiedEvaluator::new), @@ -54,7 +54,7 @@ public ChessLibEngine() { } public ChessLibEngine(DeferredReadMoveLibrary ownBook) { - super (buildEngine(EVALUATORS.get(0).getBuilder(), 20), new BasicTimeManager<>(RemainingMoveOracle.INSTANCE)); + super (buildEngine(EVALUATORS.get(0).evaluatorBuilder(), 20), new BasicTimeManager<>(RemainingMoveOracle.INSTANCE)); setEvaluators(EVALUATORS); this.ownBook = ownBook; } @@ -85,7 +85,7 @@ public void setOwnBook(boolean activate) { @Override public void setStartPosition(String fen) { - board = fromFEN(fen); + board = fromPosition(fen); board.setMoveComparatorBuilder(BasicMoveComparator::new); } @@ -124,7 +124,7 @@ public String getFEN() { } @Override - public ChessLibMoveGenerator fromFEN(String fen) { + public ChessLibMoveGenerator fromPosition(String fen) { final Board internalBoard = new Board(); internalBoard.loadFromFen(fen); return new ChessLibMoveGenerator(internalBoard); @@ -133,8 +133,8 @@ public ChessLibMoveGenerator fromFEN(String fen) { public static IterativeDeepeningEngine buildEngine(Supplier> evaluatorBuilder, int maxDepth) { final IterativeDeepeningEngine engine = new IterativeDeepeningEngine<>(new ChessLibDeepeningPolicy(maxDepth), new TT(16, SizeUnit.MB), evaluatorBuilder) { @Override - protected Negamax buildAi(ExecutionContext> context) { - final Negamax negaMax = (Negamax) super.buildAi(context); + protected Negamax buildAI(ExecutionContext> context) { + final Negamax negaMax = (Negamax) super.buildAI(context); negaMax.setQuiesceEvaluator(new BasicQuiesceSearch()); return negaMax; } @@ -156,7 +156,7 @@ protected EvaluatedMove getSelected(ChessLibMoveGenerator b, SearchHistory } @Override - protected TranspositionTable buildTranspositionTable(int sizeInMB) { + protected TranspositionTable buildTranspositionTable(int sizeInMB) { return new TT(sizeInMB, SizeUnit.MB); } } diff --git a/src/main/java/com/fathzer/jchess/chesslib/uci/Main.java b/src/main/java/com/fathzer/jchess/chesslib/uci/Main.java index 194ddd1..7ea8e77 100644 --- a/src/main/java/com/fathzer/jchess/chesslib/uci/Main.java +++ b/src/main/java/com/fathzer/jchess/chesslib/uci/Main.java @@ -66,7 +66,7 @@ static MoveLibrary readOpenings(final URL location) @Override protected Collection readTestData() { - try (InputStream stream = Main.class.getResourceAsStream("/Perft.txt")) { + try (InputStream stream = Main.class.getResourceAsStream("/com/fathzer/jchess/perft/Perft.txt")) { return new PerfTParser().withStartPositionPrefix("position fen").withStartPositionCustomizer(s -> s+" 0 1").read(stream, StandardCharsets.UTF_8); } catch (IOException e) { throw new UncheckedIOException(e); diff --git a/src/main/java/com/fathzer/jchess/lichess/AbstractDefaultOpenings.java b/src/main/java/com/fathzer/jchess/lichess/AbstractDefaultOpenings.java index a083ea3..b63a9c7 100644 --- a/src/main/java/com/fathzer/jchess/lichess/AbstractDefaultOpenings.java +++ b/src/main/java/com/fathzer/jchess/lichess/AbstractDefaultOpenings.java @@ -2,9 +2,9 @@ import java.io.IOException; import java.io.InputStream; +import java.util.Collections; import java.util.LinkedList; import java.util.List; -import java.util.Optional; import java.util.function.Supplier; import java.util.zip.GZIPInputStream; @@ -38,18 +38,18 @@ private String toReducedXFen(B board) { } @Override - protected Optional> getRecord(B board) { + protected List getRecords(B board) { final String fen = toReducedXFen(board); final JSONObject theRecord = db.optJSONObject(fen); final JSONArray moves = theRecord==null ? null : theRecord.optJSONArray("moves"); if (moves==null) { - return Optional.empty(); + return Collections.emptyList(); } final List result = new LinkedList<>(); for (int i=0;i { + + @Override + public ChessLibBoard fenToBoard(String fen, Variant variant) { + final ChessLibMoveGenerator from = FENUtils.from(fen); + return new ChessLibBoard(from); + } +} \ No newline at end of file diff --git a/src/test/java/com/fathzer/chess/test/utils/ChessLibBoard.java b/src/test/java/com/fathzer/chess/test/utils/ChessLibBoard.java new file mode 100644 index 0000000..6a4a23f --- /dev/null +++ b/src/test/java/com/fathzer/chess/test/utils/ChessLibBoard.java @@ -0,0 +1,35 @@ +package com.fathzer.chess.test.utils; + +import java.util.List; + +import com.fathzer.jchess.chesslib.ChessLibMoveGenerator; +import com.fathzer.chess.utils.model.IBoard; +import com.fathzer.games.MoveGenerator.MoveConfidence; +import com.github.bhlangonijr.chesslib.move.Move; + +public class ChessLibBoard implements IBoard{ + private final ChessLibMoveGenerator board; + + public ChessLibBoard(ChessLibMoveGenerator board) { + this.board = board; + } + + @Override + public List getMoves() { + return board.getMoves(); + } + + @Override + public boolean makeMove(Move mv) { + return board.makeMove(mv, MoveConfidence.LEGAL); + } + + @Override + public void unmakeMove() { + board.unmakeMove(); + } + + public ChessLibMoveGenerator getBoard() { + return board; + } +} \ No newline at end of file diff --git a/src/test/java/com/fathzer/chess/test/utils/SuiteTest.java b/src/test/java/com/fathzer/chess/test/utils/SuiteTest.java new file mode 100644 index 0000000..a2bae1b --- /dev/null +++ b/src/test/java/com/fathzer/chess/test/utils/SuiteTest.java @@ -0,0 +1,13 @@ +package com.fathzer.chess.test.utils; + +import org.junit.platform.suite.api.SelectClasses; +import org.junit.platform.suite.api.Suite; +import org.junit.platform.suite.api.SuiteDisplayName; + +import com.fathzer.chess.utils.test.PerftTest; + +@Suite +@SuiteDisplayName("Tests from chess-test-utils") +@SelectClasses({PerftTest.class}) +public class SuiteTest { +} \ No newline at end of file diff --git a/src/test/java/com/fathzer/jchess/chesslib/ChessLibMoveGeneratorTest.java b/src/test/java/com/fathzer/jchess/chesslib/ChessLibMoveGeneratorTest.java index b72a685..85a63aa 100644 --- a/src/test/java/com/fathzer/jchess/chesslib/ChessLibMoveGeneratorTest.java +++ b/src/test/java/com/fathzer/jchess/chesslib/ChessLibMoveGeneratorTest.java @@ -36,7 +36,7 @@ void testInvalidMovesWithUnsafe() { assertFalse(mvg.makeMove(move, UNSAFE)); // Move a piece through another piece - move = new Move(D6, B6); + move = new Move(D6, B8); assertFalse(mvg.makeMove(move, UNSAFE)); // Piece takes own piece @@ -83,8 +83,8 @@ void testInvalidMovesWithUnsafe() { // assertFalse(mvg.makeMove(move, UNSAFE)); //FIXME // Promotion of a piece that is not a pawn - move = new Move(B7, B8); -// assertFalse(mvg.makeMove(move, UNSAFE)); + move = new Move(B7, B8, Piece.WHITE_QUEEN); + assertFalse(mvg.makeMove(move, UNSAFE)); // imaginary en-passant move = new Move(H5, G6); diff --git a/src/test/java/com/fathzer/jchess/chesslib/PerfTTest.java b/src/test/java/com/fathzer/jchess/chesslib/PerfTTest.java deleted file mode 100644 index 9db9b50..0000000 --- a/src/test/java/com/fathzer/jchess/chesslib/PerfTTest.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.fathzer.jchess.chesslib; - -import static org.junit.jupiter.api.Assertions.*; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.util.Iterator; -import java.util.List; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.DisabledIfSystemProperty; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.fathzer.games.MoveGenerator; -import com.fathzer.games.perft.PerfT; -import com.fathzer.games.perft.PerfTParser; -import com.fathzer.games.perft.PerfTResult; -import com.fathzer.games.perft.PerfTTestData; -import com.fathzer.games.util.PhysicalCores; -import com.fathzer.games.util.exec.ContextualizedExecutor; -import com.github.bhlangonijr.chesslib.Board; -import com.github.bhlangonijr.chesslib.move.Move; - -class PerfTTest { - private static final Logger log = LoggerFactory.getLogger(PerfTTest.class); - - @DisabledIfSystemProperty(named="perftDepth",matches = "0") - @Test - void test() throws IOException { - final int depth = Integer.getInteger("perftDepth", 1); - if (depth!=1) { - log.info("PerfT test depth is set to {}",depth); - } - final Iterator iterator = readTests().iterator(); - try (ContextualizedExecutor> exec =new ContextualizedExecutor<>(PhysicalCores.count())) { - while (iterator.hasNext()) { - final PerfTTestData test = iterator.next(); - try { - doTest(exec, test, depth); - } catch (Exception e) { - fail("Exception on "+test.getStartPosition(),e); - } - } - } - } - -// @Test - void showDivide() { - try (ContextualizedExecutor> exec =new ContextualizedExecutor<>(PhysicalCores.count())) { - final Board board = new Board(); - board.loadFromFen("8/8/6b1/k3p2N/8/b1PB4/K6p/8 b - - 0 1"); - final PerfT perfT = new PerfT<>(exec); - final PerfTResult divide = perfT.divide(2, new ChessLibMoveGenerator(board)); - System.out.println("Leaves: "+ divide.getNbLeaves()); - System.out.println("Divide is "+divide.getDivides()); - } - } - - private void doTest(ContextualizedExecutor> exec, PerfTTestData test, int depth) { - final Board board = new Board(); - board.loadFromFen(test.getStartPosition()+" 0 1"); - final PerfT perfT = new PerfT<>(exec); - if (test.getSize()>=depth) { -// try { - final PerfTResult divide = perfT.divide(depth, new ChessLibMoveGenerator(board)); - assertEquals(test.getCount(depth), divide.getNbLeaves(), "Error for "+test.getStartPosition()+". Divide is "+divide.getDivides()); -// if (count != test.getCount(depth)) { -// System.out.println("Error for "+test.getFen()+" expected "+test.getCount(depth)+" got "+count); -// } else { -// System.out.println("Ok for "+test.getFen()); -// } -// } catch (RuntimeException e) { -// System.out.println("Exception for "+test.getFen()); -// throw e; -// } - } - } - - private List readTests() throws IOException { - try (InputStream stream = getClass().getResourceAsStream("/Perft.txt")) { - return new PerfTParser().withStartPositionPrefix("position fen").read(stream, StandardCharsets.UTF_8); - } - } -} diff --git a/src/test/java/com/fathzer/jchess/chesslib/ai/MinimaxEngineTest.java b/src/test/java/com/fathzer/jchess/chesslib/ai/MinimaxEngineTest.java index fe28d3e..77bf5ae 100644 --- a/src/test/java/com/fathzer/jchess/chesslib/ai/MinimaxEngineTest.java +++ b/src/test/java/com/fathzer/jchess/chesslib/ai/MinimaxEngineTest.java @@ -15,7 +15,7 @@ import com.fathzer.games.MoveGenerator; import com.fathzer.games.ai.Negamax; import com.fathzer.games.ai.SearchContext; -import com.fathzer.games.ai.SearchParameters; +import com.fathzer.games.ai.DepthFirstSearchParameters; import com.fathzer.games.ai.evaluation.EvaluatedMove; import com.fathzer.games.ai.evaluation.Evaluation; import com.fathzer.games.ai.evaluation.Evaluation.Type; @@ -24,7 +24,6 @@ import com.fathzer.games.ai.evaluation.Evaluator; import com.fathzer.games.util.SelectiveComparator; import com.fathzer.games.util.exec.ExecutionContext; -import com.fathzer.games.util.exec.SingleThreadContext; import com.fathzer.jchess.chesslib.ChessLibMoveGenerator; import com.fathzer.jchess.chesslib.ai.eval.NaiveEvaluator; import com.fathzer.jchess.chesslib.uci.ChessLibEngine; @@ -50,8 +49,8 @@ void blackPlayingTest() { final List> moves = getBests(mme4, fromFEN("7k/5p1Q/5P1N/5PPK/6PP/8/8/8 b - - 6 5", StrictMoveEvaluator::new)); //show(moves); assertEquals(1, moves.size()); - assertEquals(H8, moves.get(0).getContent().getFrom()); - assertEquals(H7, moves.get(0).getContent().getTo()); + assertEquals(H8, moves.get(0).getMove().getFrom()); + assertEquals(H7, moves.get(0).getMove().getTo()); assertEquals(-800, moves.get(0).getScore()); } @@ -61,7 +60,7 @@ private void show(Collection> moves) { private > List> getBests(IterativeDeepeningEngine engine, B moveGenerator) { final SearchHistory bestMoves = engine.getBestMoves(moveGenerator); - return bestMoves.getBestMoves(); + return bestMoves.getAccurateMoves(); } @Test @@ -92,7 +91,7 @@ void test() { max = moves.get(0).getEvaluation(); assertEquals(Type.WIN, max.getType()); assertEquals(1, max.getCountToEnd()); - mv = moves.get(0).getContent(); + mv = moves.get(0).getMove(); assertEquals(C3, mv.getFrom()); assertEquals(C2, mv.getTo()); // Warning, due to transposition table effects, the second best move (M+3) can be detected even if we search at depth 4! @@ -106,7 +105,7 @@ void test() { assertEquals(Type.WIN, max.getType()); assertEquals(2, max.getCountToEnd()); assertTrue(moves.get(1).getScore() basicEvaluator = new NaiveEvaluator(); basicEvaluator.init(board); SearchContext context = SearchContext.get(board, () -> basicEvaluator); - try (ExecutionContext> exec = new SingleThreadContext<>(context)) { + try (ExecutionContext> exec = ExecutionContext.get(1, context)) { Negamax ai = new Negamax<>(exec); List l = new ArrayList<>(); l.add(new Move(H1, G1)); l.add(new Move(F2, F3)); l.add(new Move(F2, F4)); - final SearchParameters params = new SearchParameters(4, Integer.MAX_VALUE, 0); + final DepthFirstSearchParameters params = new DepthFirstSearchParameters(4, Integer.MAX_VALUE, 0); final List> eval = ai.getBestMoves(l, params).getCut(); assertEquals(3, eval.size()); for (EvaluatedMove e : eval) { @@ -207,7 +206,7 @@ void bug20230911() { engine.getDeepeningPolicy().setSize(1); engine.getDeepeningPolicy().setAccuracy(0); final SearchHistory history = engine.getBestMoves(board); - List> bestMoves = history.getBestMoves(); + List> bestMoves = history.getAccurateMoves(); System.out.println(bestMoves); assertEquals(2, bestMoves.size()); } @@ -218,6 +217,6 @@ void bug20230911() { void bug20230821() { // Not a bug, just a problem with evaluation function IterativeDeepeningEngine engine = ChessLibEngine.buildEngine(NaiveEvaluator::new, 7); - System.out.println(engine.getBestMoves(fromFEN("8/6k1/6p1/1N6/6K1/R7/4B3/8 w - - 21 76")).getBest()); + System.out.println(engine.getBestMoves(fromFEN("8/6k1/6p1/1N6/6K1/R7/4B3/8 w - - 21 76")).getAccurateMoves().get(0)); } } diff --git a/src/test/resources/META-INF/services/com.fathzer.chess.utils.model.TestAdapter b/src/test/resources/META-INF/services/com.fathzer.chess.utils.model.TestAdapter new file mode 100644 index 0000000..5109cb4 --- /dev/null +++ b/src/test/resources/META-INF/services/com.fathzer.chess.utils.model.TestAdapter @@ -0,0 +1 @@ +com.fathzer.chess.test.utils.ChessLibAdapter \ No newline at end of file