From e2d174c281d553a8a7d8b40764fe1950ab58cdd6 Mon Sep 17 00:00:00 2001 From: Astesana Date: Fri, 22 Nov 2024 10:11:13 +0100 Subject: [PATCH 1/5] Adds comment about non compliance with uci specification --- README.md | 1 + src/main/java/com/fathzer/jchess/chesslib/uci/Main.java | 2 ++ 2 files changed, 3 insertions(+) diff --git a/README.md b/README.md index b697310..a85e207 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ To use this library, launch the engine with: ```java -DopeningsUrl=https://github.com/fathzer-games/jchess/raw/branch-first/src/main/resources/lichess/masters-shrink-full.json.gz -jar chesslib-uci-engine.jar``` ## Known bugs +- According to the UCI protocol, process startup should be as quick as possible. So, reading the openings table should be done on "isready" command and not during startup. - The chesslib library method ```Board.doMove(m,true)``` used to safely play moves from transposition table plays illegal moves as if they were legal. An [issue](https://github.com/bhlangonijr/chesslib/issues/114) has been posted to GitHub, as the probability of occurence of this bug is low, I'll wait for an answer... The test class ```com.fathzer.jchess.chesslib.ChessLibMoveGeneratorTest``` has commented assertions that currently fails. 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 8314f47..85c9fe3 100644 --- a/src/main/java/com/fathzer/jchess/chesslib/uci/Main.java +++ b/src/main/java/com/fathzer/jchess/chesslib/uci/Main.java @@ -30,6 +30,8 @@ public class Main extends ExtendedUCI { public static void main(String[] args) { final String pathProperty = System.getProperty("openingsUrl"); + //FIXME According to the UCI protocol, process startup should be as quick as possible + //So, reading the openings table should be done on "isready" command final MoveLibrary openings = pathProperty==null ? null : readOpenings(pathProperty); try (UCI uci = new Main(new ChessLibEngine(openings))) { uci.run(); From ff4fb91086ed203f341f3f356eb72692cab7403f Mon Sep 17 00:00:00 2001 From: Astesana Date: Wed, 27 Nov 2024 14:51:56 +0100 Subject: [PATCH 2/5] Bug fixes - ChesslibDeepeningPolicy.isEnoughtToDeepen sometimes returned true when max time was reached. - OwnBook was read at startup which does not comply with UCI specification --- README.md | 3 +- pom.xml | 12 ++- .../chesslib/ai/ChessLibDeepeningPolicy.java | 3 +- .../jchess/chesslib/uci/ChessLibEngine.java | 11 ++- .../jchess/chesslib/uci/DeferredReadBook.java | 67 +++++++++++++++++ .../com/fathzer/jchess/chesslib/uci/Main.java | 74 ++++++++----------- .../ai/ChessLibDeepeningPolicyTest.java | 35 +++++++++ 7 files changed, 152 insertions(+), 53 deletions(-) create mode 100644 src/main/java/com/fathzer/jchess/chesslib/uci/DeferredReadBook.java create mode 100644 src/test/java/com/fathzer/jchess/chesslib/ai/ChessLibDeepeningPolicyTest.java diff --git a/README.md b/README.md index a85e207..761df63 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,6 @@ To use this library, launch the engine with: ```java -DopeningsUrl=https://github.com/fathzer-games/jchess/raw/branch-first/src/main/resources/lichess/masters-shrink-full.json.gz -jar chesslib-uci-engine.jar``` ## Known bugs -- According to the UCI protocol, process startup should be as quick as possible. So, reading the openings table should be done on "isready" command and not during startup. - The chesslib library method ```Board.doMove(m,true)``` used to safely play moves from transposition table plays illegal moves as if they were legal. An [issue](https://github.com/bhlangonijr/chesslib/issues/114) has been posted to GitHub, as the probability of occurence of this bug is low, I'll wait for an answer... The test class ```com.fathzer.jchess.chesslib.ChessLibMoveGeneratorTest``` has commented assertions that currently fails. @@ -34,4 +33,4 @@ The duration and accuracy of this test greatly depends on its search depth. This depth is 1 by default (to limit Github's resources consumption - Every push trigger a mvn test action). In order to perform better test, you can set the **perftDepth** system property to a higher value. ### TODO -- The detection of invalid UNSAFE move does not work. \ No newline at end of file +- The detection of invalid UNSAFE move does not work (An [issue](https://github.com/bhlangonijr/chesslib/issues/114) is opened in Chesslib project about this problem). \ No newline at end of file diff --git a/pom.xml b/pom.xml index 0b7a9b2..084b396 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ 1.0.8 chesslib-uci-engine - 0.0.1 + 0.0.2 chesslib-uci-engine A basic uci engine plugin for jchess-uci. @@ -50,12 +50,12 @@ com.fathzer games-core - 0.0.11-SNAPSHOT + 0.0.12-SNAPSHOT com.fathzer jchess-uci - 2.0.3-SNAPSHOT + 2.0.4-SNAPSHOT org.json @@ -84,6 +84,12 @@ 5.10.2 test + + org.awaitility + awaitility + 4.2.2 + test + diff --git a/src/main/java/com/fathzer/jchess/chesslib/ai/ChessLibDeepeningPolicy.java b/src/main/java/com/fathzer/jchess/chesslib/ai/ChessLibDeepeningPolicy.java index 45f3b63..12de2a7 100644 --- a/src/main/java/com/fathzer/jchess/chesslib/ai/ChessLibDeepeningPolicy.java +++ b/src/main/java/com/fathzer/jchess/chesslib/ai/ChessLibDeepeningPolicy.java @@ -16,6 +16,7 @@ public int getNextDepth(int currentDepth) { @Override public boolean isEnoughTimeToDeepen(int depth) { - return depth<5 || getSpent() implements TestableMoveGeneratorBuilder, Displayable { private static final List> EVALUATORS = Arrays.asList(new EvaluatorConfiguration<>("simplified",SimplifiedEvaluator::new),new EvaluatorConfiguration<>("naive",NaiveEvaluator::new)); - private final MoveLibrary ownBook; + private final DeferredReadBook ownBook; public ChessLibEngine() { this (null); } - public ChessLibEngine(MoveLibrary ownBook) { + public ChessLibEngine(DeferredReadBook ownBook) { super (buildEngine(EVALUATORS.get(0).getBuilder(), 20), new BasicTimeManager<>(RemainingMoveOracle.INSTANCE)); setEvaluators(EVALUATORS); this.ownBook = ownBook; @@ -63,6 +62,10 @@ public String getId() { public String getAuthor() { return "Jean-Marc Astesana (Fathzer), Move generator is from Ben-Hur Carlos Vieira Langoni Junior"; } + + DeferredReadBook getOwnBook() { + return ownBook; + } @Override public boolean hasOwnBook() { @@ -111,7 +114,7 @@ public String getBoardAsString() { @Override public String getFEN() { - return board.getBoard().getFen(); + return board==null ? null : board.getBoard().getFen(); } @Override diff --git a/src/main/java/com/fathzer/jchess/chesslib/uci/DeferredReadBook.java b/src/main/java/com/fathzer/jchess/chesslib/uci/DeferredReadBook.java new file mode 100644 index 0000000..6daac7d --- /dev/null +++ b/src/main/java/com/fathzer/jchess/chesslib/uci/DeferredReadBook.java @@ -0,0 +1,67 @@ +package com.fathzer.jchess.chesslib.uci; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Optional; + +import com.fathzer.games.ai.evaluation.EvaluatedMove; +import com.fathzer.games.movelibrary.MoveLibrary; +import com.fathzer.jchess.chesslib.ChessLibMoveGenerator; +import com.fathzer.jchess.lichess.DefaultOpenings; +import com.github.bhlangonijr.chesslib.move.Move; + +class DeferredReadBook implements MoveLibrary { + private final String url; + private MoveLibrary internal; + + DeferredReadBook(String url) { + this.url = url; + } + + @Override + public Optional> apply(ChessLibMoveGenerator board) { + if (internal==null) { + return Optional.empty(); + } + return internal.apply(board); + } + + boolean isInitRequired() { + return internal==null; + } + + void init() throws IOException { + if (isInitRequired()) { + internal = readOpenings(toURL(this.url)); + } + } + + private static URL toURL(String path) throws IOException { + URL url; + try { + url = new URL(path); + } catch (MalformedURLException e) { + File file = new File(path); + if (!file.exists()) { + throw new FileNotFoundException(); + } + url = file.toURI().toURL(); + } + return url; + } + + private static MoveLibrary readOpenings(final URL location) throws IOException { + final boolean compressed = location.getFile().endsWith(".gz"); + try (InputStream stream = location.openStream()) { + return new DefaultOpenings(()->stream, compressed); + } + } + + String getUrl() { + return url; + } +} 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 85c9fe3..79aff12 100644 --- a/src/main/java/com/fathzer/jchess/chesslib/uci/Main.java +++ b/src/main/java/com/fathzer/jchess/chesslib/uci/Main.java @@ -1,12 +1,8 @@ package com.fathzer.jchess.chesslib.uci; -import java.io.File; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; -import java.net.MalformedURLException; -import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.Collection; import java.util.Deque; @@ -14,16 +10,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.fathzer.games.movelibrary.MoveLibrary; import com.fathzer.games.perft.PerfTParser; import com.fathzer.games.perft.PerfTTestData; -import com.fathzer.jchess.chesslib.ChessLibMoveGenerator; -import com.fathzer.jchess.lichess.DefaultOpenings; import com.fathzer.jchess.uci.Engine; import com.fathzer.jchess.uci.UCI; import com.fathzer.jchess.uci.extended.ExtendedUCI; import com.fathzer.jchess.uci.extended.SpeedTest; -import com.github.bhlangonijr.chesslib.move.Move; public class Main extends ExtendedUCI { private static final Logger LOGGER = LoggerFactory.getLogger(Main.class); @@ -32,49 +24,35 @@ public static void main(String[] args) { final String pathProperty = System.getProperty("openingsUrl"); //FIXME According to the UCI protocol, process startup should be as quick as possible //So, reading the openings table should be done on "isready" command - final MoveLibrary openings = pathProperty==null ? null : readOpenings(pathProperty); + final DeferredReadBook openings = pathProperty==null ? null : new DeferredReadBook(pathProperty); try (UCI uci = new Main(new ChessLibEngine(openings))) { uci.run(); } } - private static URL toURL(String path) throws IOException { - URL url; - try { - url = new URL(path); - } catch (MalformedURLException e) { - File file = new File(path); - if (!file.exists()) { - throw new FileNotFoundException(); - } - url = file.toURI().toURL(); - } - return url; - } - - private static MoveLibrary readOpenings(String url) { - try { - return readOpenings(url, toURL(url)); - } catch (IOException e) { - LOGGER.error("Unable to load opening library at "+url, e); - return null; - } - } - - private static MoveLibrary readOpenings(String url, final URL location) throws IOException { - final boolean compressed = location.getFile().endsWith(".gz"); - try (InputStream stream = location.openStream()) { - final DefaultOpenings result = new DefaultOpenings(()->stream, compressed); - LOGGER.info("Opening library read from {}", url); - return result; - } - } - public Main(Engine defaultEngine) { super(defaultEngine); addCommand(this::speedTest, "st"); } + @Override + protected void doIsReady(Deque tokens) { + if (engine instanceof ChessLibEngine cle) { + final DeferredReadBook book = cle.getOwnBook(); + if (book!=null && book.isInitRequired()) { + LOGGER.debug("Start reading opening library from {}", book.getUrl()); + try { + book.init(); + LOGGER.debug("Opening library read from {}", book.getUrl()); + } catch (IOException e) { + LOGGER.error("Unable to load opening library at {}", book.getUrl(), e); + debug("An error occurred while reading opening book"); + } + } + } + super.doIsReady(tokens); + } + @Override protected Collection readTestData() { try (InputStream stream = Main.class.getResourceAsStream("/Perft.txt")) { @@ -85,10 +63,20 @@ protected Collection readTestData() { } private void speedTest(Deque args) { - if (engine instanceof ChessLibEngine) { - out("completed in "+new SpeedTest<>((ChessLibEngine)engine).run()+"ms"); + if (engine instanceof ChessLibEngine chesslibEngine) { + out("completed in "+new SpeedTest<>(chesslibEngine, this::out).run()+"ms"); } else { debug("This engine does not support this command"); } } + + @Override + protected void err(String tag, Throwable e) { + LOGGER.error("An error occurred in {}", tag, e); + } + + @Override + protected void err(CharSequence message) { + LOGGER.error("{}", message); + } } diff --git a/src/test/java/com/fathzer/jchess/chesslib/ai/ChessLibDeepeningPolicyTest.java b/src/test/java/com/fathzer/jchess/chesslib/ai/ChessLibDeepeningPolicyTest.java new file mode 100644 index 0000000..3fb4b75 --- /dev/null +++ b/src/test/java/com/fathzer/jchess/chesslib/ai/ChessLibDeepeningPolicyTest.java @@ -0,0 +1,35 @@ +package com.fathzer.jchess.chesslib.ai; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.concurrent.TimeUnit; + +import static org.awaitility.Awaitility.*; + +import org.junit.jupiter.api.Test; + +import com.fathzer.games.ai.iterativedeepening.DeepeningPolicy; + +class ChessLibDeepeningPolicyTest { + @Test + void test() { + final DeepeningPolicy policy = new ChessLibDeepeningPolicy(10); + assertEquals(10, policy.getDepth()); + assertEquals(2, policy.getStartDepth()); + assertEquals(4, policy.getNextDepth(2)); + assertEquals(6, policy.getNextDepth(4)); + assertEquals(7, policy.getNextDepth(6)); + } + + @Test + void bug20241127() { + // isEnoughTimeToDeepen returned true when time was spent and depth was < 5 + final DeepeningPolicy policy = new ChessLibDeepeningPolicy(10); + policy.setMaxTime(1); + policy.start(); + await().atLeast(10, TimeUnit.MILLISECONDS); + assertFalse(policy.isEnoughTimeToDeepen(4)); + } + + +} From 68443021cd612f3b74a296e7ac940dcb6e61fa5d Mon Sep 17 00:00:00 2001 From: Astesana Date: Wed, 27 Nov 2024 19:58:35 +0100 Subject: [PATCH 3/5] Adds some tests --- .../jchess/chesslib/uci/DeferredReadBook.java | 4 +-- .../ai/ChessLibDeepeningPolicyTest.java | 5 +++ .../chesslib/uci/DeferredReadBookTest.java | 32 +++++++++++++++++++ src/test/resources/openings.json | 9 ++++++ 4 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 src/test/java/com/fathzer/jchess/chesslib/uci/DeferredReadBookTest.java create mode 100644 src/test/resources/openings.json diff --git a/src/main/java/com/fathzer/jchess/chesslib/uci/DeferredReadBook.java b/src/main/java/com/fathzer/jchess/chesslib/uci/DeferredReadBook.java index 6daac7d..264c809 100644 --- a/src/main/java/com/fathzer/jchess/chesslib/uci/DeferredReadBook.java +++ b/src/main/java/com/fathzer/jchess/chesslib/uci/DeferredReadBook.java @@ -40,7 +40,7 @@ void init() throws IOException { } } - private static URL toURL(String path) throws IOException { + static URL toURL(String path) throws IOException { URL url; try { url = new URL(path); @@ -54,7 +54,7 @@ private static URL toURL(String path) throws IOException { return url; } - private static MoveLibrary readOpenings(final URL location) throws IOException { + protected static MoveLibrary readOpenings(final URL location) throws IOException { final boolean compressed = location.getFile().endsWith(".gz"); try (InputStream stream = location.openStream()) { return new DefaultOpenings(()->stream, compressed); diff --git a/src/test/java/com/fathzer/jchess/chesslib/ai/ChessLibDeepeningPolicyTest.java b/src/test/java/com/fathzer/jchess/chesslib/ai/ChessLibDeepeningPolicyTest.java index 3fb4b75..02599d1 100644 --- a/src/test/java/com/fathzer/jchess/chesslib/ai/ChessLibDeepeningPolicyTest.java +++ b/src/test/java/com/fathzer/jchess/chesslib/ai/ChessLibDeepeningPolicyTest.java @@ -19,6 +19,7 @@ void test() { assertEquals(4, policy.getNextDepth(2)); assertEquals(6, policy.getNextDepth(4)); assertEquals(7, policy.getNextDepth(6)); + assertEquals(10, policy.getNextDepth(10)); } @Test @@ -26,9 +27,13 @@ void bug20241127() { // isEnoughTimeToDeepen returned true when time was spent and depth was < 5 final DeepeningPolicy policy = new ChessLibDeepeningPolicy(10); policy.setMaxTime(1); + assertThrows(IllegalStateException.class, () -> policy.getSpent()); policy.start(); await().atLeast(10, TimeUnit.MILLISECONDS); assertFalse(policy.isEnoughTimeToDeepen(4)); + + policy.setMaxTime(10000); + policy.start(); } diff --git a/src/test/java/com/fathzer/jchess/chesslib/uci/DeferredReadBookTest.java b/src/test/java/com/fathzer/jchess/chesslib/uci/DeferredReadBookTest.java new file mode 100644 index 0000000..fd49903 --- /dev/null +++ b/src/test/java/com/fathzer/jchess/chesslib/uci/DeferredReadBookTest.java @@ -0,0 +1,32 @@ +package com.fathzer.jchess.chesslib.uci; + +import static org.junit.jupiter.api.Assertions.*; + +import java.io.IOException; +import java.net.URI; + +import org.junit.jupiter.api.Test; + +import com.fathzer.chess.test.utils.FENUtils; + +class DeferredReadBookTest { + + + @Test + void test() throws Exception { + String httpsURL = "https://myApp.org/file.gz"; + assertEquals(URI.create(httpsURL).toURL(), DeferredReadBook.toURL(httpsURL)); + + assertThrows(IOException.class, () -> DeferredReadBook.toURL("httpx://myApp.org/file.gz")); + assertThrows(IOException.class, () -> DeferredReadBook.toURL("src/test/resources/unknownFile.json")); + + final String path = "src/test/resources/openings.json"; + final DeferredReadBook rb = new DeferredReadBook(path); + assertTrue(rb.isInitRequired()); + final String fen = "rn1qkb1r/pp3ppp/2p1pnb1/3p4/2PP3N/2N2PP1/PP2P2P/R1BQKB1R w KQkq -"; + assertTrue(rb.apply(FENUtils.from(fen)).isEmpty()); + rb.init(); + assertFalse(rb.isInitRequired()); + assertFalse(rb.apply(FENUtils.from(fen)).isEmpty()); + } +} diff --git a/src/test/resources/openings.json b/src/test/resources/openings.json new file mode 100644 index 0000000..cf0ceff --- /dev/null +++ b/src/test/resources/openings.json @@ -0,0 +1,9 @@ +{"rn1qkb1r/pp3ppp/2p1pnb1/3p4/2PP3N/2N2PP1/PP2P2P/R1BQKB1R w KQkq -":{"name":null,"count":83,"moves":[{"coord":"d1b3","count":75}]}, +"rnbqkb1r/ppp2ppp/4pn2/3p4/2PP4/4PN2/PP3PPP/RNBQKB1R b KQkq -":{"name":null,"count":3843,"moves":[{"coord":"f8e7","count":1136},{"coord":"c7c5","count":750},{"coord":"a7a6","count":602},{"coord":"b7b6","count":442},{"coord":"c7c6","count":411},{"coord":"b8d7","count":239},{"coord":"d5c4","count":128},{"coord":"f8b4","count":82},{"coord":"f8d6","count":36}]}, +"r1bqkb1r/pp2pppp/2n5/3np3/3P4/2N2N2/PP3PPP/R1BQKB1R w KQkq -":{"name":null,"count":168,"moves":[{"coord":"d4e5","count":158}]}, +"r1bqkb1r/pp2pp1p/2np1np1/8/Q3P3/2N2N2/PPP2PPP/R1B1KB1R w KQkq -":{"name":null,"count":382,"moves":[{"coord":"e4e5","count":378}]}, +"rnbqkb1r/pp3ppp/3pp3/3nP3/2BP4/5N2/PP3PPP/RNBQK2R b KQkq -":{"name":null,"count":2657,"moves":[{"coord":"b8c6","count":1206},{"coord":"d5b6","count":791},{"coord":"f8e7","count":540},{"coord":"d6e5","count":54},{"coord":"c8d7","count":45}]}, +"rnbqkbnr/pp3ppp/3pp3/1Bp5/4P3/2N2N2/PPPP1PPP/R1BQK2R b KQkq -":{"name":null,"count":47,"moves":[{"coord":"c8d7","count":33}]}, +"rnbqkb1r/ppp2ppp/4pn2/8/2BP4/4P3/PP3PPP/RNBQK1NR w KQkq -":{"name":null,"count":2232,"moves":[{"coord":"g1f3","count":2136},{"coord":"b1c3","count":65}]}, +"rnbqkbnr/p1pp1ppp/1p2p3/8/2P5/5N2/PP1PPPPP/RNBQKB1R w KQkq -":{"name":null,"count":453,"moves":[{"coord":"g2g3","count":347},{"coord":"b1c3","count":52}]}, +"rnbqkbnr/pp2pppp/2p5/3p4/2PP4/8/PP2PPPP/RNBQKBNR w KQkq -":{"name":"Slav Defense","count":90671,"moves":[{"coord":"g1f3","count":57569},{"coord":"b1c3","count":22650},{"coord":"c4d5","count":7033},{"coord":"e2e3","count":3227},{"coord":"d1c2","count":64},{"coord":"c1f4","count":57},{"coord":"g2g3","count":32}]}} \ No newline at end of file From 45e51bf0d8a13377a232580855828906d62abe64 Mon Sep 17 00:00:00 2001 From: Astesana Date: Thu, 28 Nov 2024 08:58:47 +0100 Subject: [PATCH 4/5] Fixes some Sonar complaints --- .../jchess/chesslib/uci/ChessLibEngine.java | 6 ++-- .../jchess/chesslib/uci/DeferredReadBook.java | 29 +++++++++---------- .../com/fathzer/jchess/chesslib/uci/Main.java | 18 +++++++++--- .../chesslib/uci/DeferredReadBookTest.java | 6 +++- 4 files changed, 35 insertions(+), 24 deletions(-) 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 7e76774..a20517c 100644 --- a/src/main/java/com/fathzer/jchess/chesslib/uci/ChessLibEngine.java +++ b/src/main/java/com/fathzer/jchess/chesslib/uci/ChessLibEngine.java @@ -41,13 +41,13 @@ public class ChessLibEngine extends AbstractEngine implements TestableMoveGeneratorBuilder, Displayable { private static final List> EVALUATORS = Arrays.asList(new EvaluatorConfiguration<>("simplified",SimplifiedEvaluator::new),new EvaluatorConfiguration<>("naive",NaiveEvaluator::new)); - private final DeferredReadBook ownBook; + private final DeferredReadBook ownBook; public ChessLibEngine() { this (null); } - public ChessLibEngine(DeferredReadBook ownBook) { + public ChessLibEngine(DeferredReadBook ownBook) { super (buildEngine(EVALUATORS.get(0).getBuilder(), 20), new BasicTimeManager<>(RemainingMoveOracle.INSTANCE)); setEvaluators(EVALUATORS); this.ownBook = ownBook; @@ -63,7 +63,7 @@ public String getAuthor() { return "Jean-Marc Astesana (Fathzer), Move generator is from Ben-Hur Carlos Vieira Langoni Junior"; } - DeferredReadBook getOwnBook() { + DeferredReadBook getOwnBook() { return ownBook; } diff --git a/src/main/java/com/fathzer/jchess/chesslib/uci/DeferredReadBook.java b/src/main/java/com/fathzer/jchess/chesslib/uci/DeferredReadBook.java index 264c809..ab13b4a 100644 --- a/src/main/java/com/fathzer/jchess/chesslib/uci/DeferredReadBook.java +++ b/src/main/java/com/fathzer/jchess/chesslib/uci/DeferredReadBook.java @@ -3,27 +3,31 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; -import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.util.Optional; +import com.fathzer.games.MoveGenerator; import com.fathzer.games.ai.evaluation.EvaluatedMove; import com.fathzer.games.movelibrary.MoveLibrary; -import com.fathzer.jchess.chesslib.ChessLibMoveGenerator; -import com.fathzer.jchess.lichess.DefaultOpenings; -import com.github.bhlangonijr.chesslib.move.Move; -class DeferredReadBook implements MoveLibrary { +class DeferredReadBook> implements MoveLibrary { + @FunctionalInterface + static interface IOReader { + T read(URL url) throws IOException; + } + private final String url; - private MoveLibrary internal; + private IOReader> reader; + private MoveLibrary internal; - DeferredReadBook(String url) { + DeferredReadBook(String url, IOReader> reader) { this.url = url; + this.reader = reader; } @Override - public Optional> apply(ChessLibMoveGenerator board) { + public Optional> apply(B board) { if (internal==null) { return Optional.empty(); } @@ -36,7 +40,7 @@ boolean isInitRequired() { void init() throws IOException { if (isInitRequired()) { - internal = readOpenings(toURL(this.url)); + internal = reader.read(toURL(this.url)); } } @@ -53,13 +57,6 @@ static URL toURL(String path) throws IOException { } return url; } - - protected static MoveLibrary readOpenings(final URL location) throws IOException { - final boolean compressed = location.getFile().endsWith(".gz"); - try (InputStream stream = location.openStream()) { - return new DefaultOpenings(()->stream, compressed); - } - } String getUrl() { return url; 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 79aff12..172aa52 100644 --- a/src/main/java/com/fathzer/jchess/chesslib/uci/Main.java +++ b/src/main/java/com/fathzer/jchess/chesslib/uci/Main.java @@ -3,6 +3,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; +import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.Collection; import java.util.Deque; @@ -10,21 +11,23 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.fathzer.games.movelibrary.MoveLibrary; import com.fathzer.games.perft.PerfTParser; import com.fathzer.games.perft.PerfTTestData; +import com.fathzer.jchess.chesslib.ChessLibMoveGenerator; +import com.fathzer.jchess.lichess.DefaultOpenings; import com.fathzer.jchess.uci.Engine; import com.fathzer.jchess.uci.UCI; import com.fathzer.jchess.uci.extended.ExtendedUCI; import com.fathzer.jchess.uci.extended.SpeedTest; +import com.github.bhlangonijr.chesslib.move.Move; public class Main extends ExtendedUCI { private static final Logger LOGGER = LoggerFactory.getLogger(Main.class); public static void main(String[] args) { final String pathProperty = System.getProperty("openingsUrl"); - //FIXME According to the UCI protocol, process startup should be as quick as possible - //So, reading the openings table should be done on "isready" command - final DeferredReadBook openings = pathProperty==null ? null : new DeferredReadBook(pathProperty); + final DeferredReadBook openings = pathProperty==null ? null : new DeferredReadBook<>(pathProperty, Main::readOpenings); try (UCI uci = new Main(new ChessLibEngine(openings))) { uci.run(); } @@ -38,7 +41,7 @@ public Main(Engine defaultEngine) { @Override protected void doIsReady(Deque tokens) { if (engine instanceof ChessLibEngine cle) { - final DeferredReadBook book = cle.getOwnBook(); + final DeferredReadBook book = cle.getOwnBook(); if (book!=null && book.isInitRequired()) { LOGGER.debug("Start reading opening library from {}", book.getUrl()); try { @@ -52,6 +55,13 @@ protected void doIsReady(Deque tokens) { } super.doIsReady(tokens); } + + static MoveLibrary readOpenings(final URL location) throws IOException { + final boolean compressed = location.getFile().endsWith(".gz"); + try (InputStream stream = location.openStream()) { + return new DefaultOpenings(()->stream, compressed); + } + } @Override protected Collection readTestData() { diff --git a/src/test/java/com/fathzer/jchess/chesslib/uci/DeferredReadBookTest.java b/src/test/java/com/fathzer/jchess/chesslib/uci/DeferredReadBookTest.java index fd49903..971945b 100644 --- a/src/test/java/com/fathzer/jchess/chesslib/uci/DeferredReadBookTest.java +++ b/src/test/java/com/fathzer/jchess/chesslib/uci/DeferredReadBookTest.java @@ -8,6 +8,8 @@ import org.junit.jupiter.api.Test; import com.fathzer.chess.test.utils.FENUtils; +import com.fathzer.jchess.chesslib.ChessLibMoveGenerator; +import com.github.bhlangonijr.chesslib.move.Move; class DeferredReadBookTest { @@ -21,12 +23,14 @@ void test() throws Exception { assertThrows(IOException.class, () -> DeferredReadBook.toURL("src/test/resources/unknownFile.json")); final String path = "src/test/resources/openings.json"; - final DeferredReadBook rb = new DeferredReadBook(path); + final DeferredReadBook rb = new DeferredReadBook<>(path, Main::readOpenings); assertTrue(rb.isInitRequired()); final String fen = "rn1qkb1r/pp3ppp/2p1pnb1/3p4/2PP3N/2N2PP1/PP2P2P/R1BQKB1R w KQkq -"; assertTrue(rb.apply(FENUtils.from(fen)).isEmpty()); rb.init(); assertFalse(rb.isInitRequired()); assertFalse(rb.apply(FENUtils.from(fen)).isEmpty()); + // A second init call should no throw any exception + rb.init(); } } From fe8a6be1173ca62fb67935b0263156086929f509 Mon Sep 17 00:00:00 2001 From: Astesana Date: Thu, 28 Nov 2024 18:21:52 +0100 Subject: [PATCH 5/5] Move DeferredReadBook in jchess-uci library --- .../jchess/chesslib/uci/ChessLibEngine.java | 7 +- .../jchess/chesslib/uci/DeferredReadBook.java | 64 ------------------- .../com/fathzer/jchess/chesslib/uci/Main.java | 5 +- .../chesslib/uci/DeferredReadBookTest.java | 36 ----------- src/test/resources/openings.json | 9 --- 5 files changed, 7 insertions(+), 114 deletions(-) delete mode 100644 src/main/java/com/fathzer/jchess/chesslib/uci/DeferredReadBook.java delete mode 100644 src/test/java/com/fathzer/jchess/chesslib/uci/DeferredReadBookTest.java delete mode 100644 src/test/resources/openings.json 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 a20517c..7af50ab 100644 --- a/src/main/java/com/fathzer/jchess/chesslib/uci/ChessLibEngine.java +++ b/src/main/java/com/fathzer/jchess/chesslib/uci/ChessLibEngine.java @@ -32,6 +32,7 @@ import com.fathzer.jchess.uci.UCIMove; import com.fathzer.jchess.uci.extended.Displayable; import com.fathzer.jchess.uci.helper.AbstractEngine; +import com.fathzer.jchess.uci.helper.DeferredReadMoveLibrary; import com.fathzer.jchess.uci.helper.EvaluatorConfiguration; import com.github.bhlangonijr.chesslib.Board; import com.github.bhlangonijr.chesslib.Piece; @@ -41,13 +42,13 @@ public class ChessLibEngine extends AbstractEngine implements TestableMoveGeneratorBuilder, Displayable { private static final List> EVALUATORS = Arrays.asList(new EvaluatorConfiguration<>("simplified",SimplifiedEvaluator::new),new EvaluatorConfiguration<>("naive",NaiveEvaluator::new)); - private final DeferredReadBook ownBook; + private final DeferredReadMoveLibrary ownBook; public ChessLibEngine() { this (null); } - public ChessLibEngine(DeferredReadBook ownBook) { + public ChessLibEngine(DeferredReadMoveLibrary ownBook) { super (buildEngine(EVALUATORS.get(0).getBuilder(), 20), new BasicTimeManager<>(RemainingMoveOracle.INSTANCE)); setEvaluators(EVALUATORS); this.ownBook = ownBook; @@ -63,7 +64,7 @@ public String getAuthor() { return "Jean-Marc Astesana (Fathzer), Move generator is from Ben-Hur Carlos Vieira Langoni Junior"; } - DeferredReadBook getOwnBook() { + DeferredReadMoveLibrary getOwnBook() { return ownBook; } diff --git a/src/main/java/com/fathzer/jchess/chesslib/uci/DeferredReadBook.java b/src/main/java/com/fathzer/jchess/chesslib/uci/DeferredReadBook.java deleted file mode 100644 index ab13b4a..0000000 --- a/src/main/java/com/fathzer/jchess/chesslib/uci/DeferredReadBook.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.fathzer.jchess.chesslib.uci; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.Optional; - -import com.fathzer.games.MoveGenerator; -import com.fathzer.games.ai.evaluation.EvaluatedMove; -import com.fathzer.games.movelibrary.MoveLibrary; - -class DeferredReadBook> implements MoveLibrary { - @FunctionalInterface - static interface IOReader { - T read(URL url) throws IOException; - } - - private final String url; - private IOReader> reader; - private MoveLibrary internal; - - DeferredReadBook(String url, IOReader> reader) { - this.url = url; - this.reader = reader; - } - - @Override - public Optional> apply(B board) { - if (internal==null) { - return Optional.empty(); - } - return internal.apply(board); - } - - boolean isInitRequired() { - return internal==null; - } - - void init() throws IOException { - if (isInitRequired()) { - internal = reader.read(toURL(this.url)); - } - } - - static URL toURL(String path) throws IOException { - URL url; - try { - url = new URL(path); - } catch (MalformedURLException e) { - File file = new File(path); - if (!file.exists()) { - throw new FileNotFoundException(); - } - url = file.toURI().toURL(); - } - return url; - } - - String getUrl() { - return url; - } -} 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 172aa52..194ddd1 100644 --- a/src/main/java/com/fathzer/jchess/chesslib/uci/Main.java +++ b/src/main/java/com/fathzer/jchess/chesslib/uci/Main.java @@ -20,6 +20,7 @@ import com.fathzer.jchess.uci.UCI; import com.fathzer.jchess.uci.extended.ExtendedUCI; import com.fathzer.jchess.uci.extended.SpeedTest; +import com.fathzer.jchess.uci.helper.DeferredReadMoveLibrary; import com.github.bhlangonijr.chesslib.move.Move; public class Main extends ExtendedUCI { @@ -27,7 +28,7 @@ public class Main extends ExtendedUCI { public static void main(String[] args) { final String pathProperty = System.getProperty("openingsUrl"); - final DeferredReadBook openings = pathProperty==null ? null : new DeferredReadBook<>(pathProperty, Main::readOpenings); + final DeferredReadMoveLibrary openings = pathProperty==null ? null : new DeferredReadMoveLibrary<>(pathProperty, Main::readOpenings); try (UCI uci = new Main(new ChessLibEngine(openings))) { uci.run(); } @@ -41,7 +42,7 @@ public Main(Engine defaultEngine) { @Override protected void doIsReady(Deque tokens) { if (engine instanceof ChessLibEngine cle) { - final DeferredReadBook book = cle.getOwnBook(); + final DeferredReadMoveLibrary book = cle.getOwnBook(); if (book!=null && book.isInitRequired()) { LOGGER.debug("Start reading opening library from {}", book.getUrl()); try { diff --git a/src/test/java/com/fathzer/jchess/chesslib/uci/DeferredReadBookTest.java b/src/test/java/com/fathzer/jchess/chesslib/uci/DeferredReadBookTest.java deleted file mode 100644 index 971945b..0000000 --- a/src/test/java/com/fathzer/jchess/chesslib/uci/DeferredReadBookTest.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.fathzer.jchess.chesslib.uci; - -import static org.junit.jupiter.api.Assertions.*; - -import java.io.IOException; -import java.net.URI; - -import org.junit.jupiter.api.Test; - -import com.fathzer.chess.test.utils.FENUtils; -import com.fathzer.jchess.chesslib.ChessLibMoveGenerator; -import com.github.bhlangonijr.chesslib.move.Move; - -class DeferredReadBookTest { - - - @Test - void test() throws Exception { - String httpsURL = "https://myApp.org/file.gz"; - assertEquals(URI.create(httpsURL).toURL(), DeferredReadBook.toURL(httpsURL)); - - assertThrows(IOException.class, () -> DeferredReadBook.toURL("httpx://myApp.org/file.gz")); - assertThrows(IOException.class, () -> DeferredReadBook.toURL("src/test/resources/unknownFile.json")); - - final String path = "src/test/resources/openings.json"; - final DeferredReadBook rb = new DeferredReadBook<>(path, Main::readOpenings); - assertTrue(rb.isInitRequired()); - final String fen = "rn1qkb1r/pp3ppp/2p1pnb1/3p4/2PP3N/2N2PP1/PP2P2P/R1BQKB1R w KQkq -"; - assertTrue(rb.apply(FENUtils.from(fen)).isEmpty()); - rb.init(); - assertFalse(rb.isInitRequired()); - assertFalse(rb.apply(FENUtils.from(fen)).isEmpty()); - // A second init call should no throw any exception - rb.init(); - } -} diff --git a/src/test/resources/openings.json b/src/test/resources/openings.json deleted file mode 100644 index cf0ceff..0000000 --- a/src/test/resources/openings.json +++ /dev/null @@ -1,9 +0,0 @@ -{"rn1qkb1r/pp3ppp/2p1pnb1/3p4/2PP3N/2N2PP1/PP2P2P/R1BQKB1R w KQkq -":{"name":null,"count":83,"moves":[{"coord":"d1b3","count":75}]}, -"rnbqkb1r/ppp2ppp/4pn2/3p4/2PP4/4PN2/PP3PPP/RNBQKB1R b KQkq -":{"name":null,"count":3843,"moves":[{"coord":"f8e7","count":1136},{"coord":"c7c5","count":750},{"coord":"a7a6","count":602},{"coord":"b7b6","count":442},{"coord":"c7c6","count":411},{"coord":"b8d7","count":239},{"coord":"d5c4","count":128},{"coord":"f8b4","count":82},{"coord":"f8d6","count":36}]}, -"r1bqkb1r/pp2pppp/2n5/3np3/3P4/2N2N2/PP3PPP/R1BQKB1R w KQkq -":{"name":null,"count":168,"moves":[{"coord":"d4e5","count":158}]}, -"r1bqkb1r/pp2pp1p/2np1np1/8/Q3P3/2N2N2/PPP2PPP/R1B1KB1R w KQkq -":{"name":null,"count":382,"moves":[{"coord":"e4e5","count":378}]}, -"rnbqkb1r/pp3ppp/3pp3/3nP3/2BP4/5N2/PP3PPP/RNBQK2R b KQkq -":{"name":null,"count":2657,"moves":[{"coord":"b8c6","count":1206},{"coord":"d5b6","count":791},{"coord":"f8e7","count":540},{"coord":"d6e5","count":54},{"coord":"c8d7","count":45}]}, -"rnbqkbnr/pp3ppp/3pp3/1Bp5/4P3/2N2N2/PPPP1PPP/R1BQK2R b KQkq -":{"name":null,"count":47,"moves":[{"coord":"c8d7","count":33}]}, -"rnbqkb1r/ppp2ppp/4pn2/8/2BP4/4P3/PP3PPP/RNBQK1NR w KQkq -":{"name":null,"count":2232,"moves":[{"coord":"g1f3","count":2136},{"coord":"b1c3","count":65}]}, -"rnbqkbnr/p1pp1ppp/1p2p3/8/2P5/5N2/PP1PPPPP/RNBQKB1R w KQkq -":{"name":null,"count":453,"moves":[{"coord":"g2g3","count":347},{"coord":"b1c3","count":52}]}, -"rnbqkbnr/pp2pppp/2p5/3p4/2PP4/8/PP2PPPP/RNBQKBNR w KQkq -":{"name":"Slav Defense","count":90671,"moves":[{"coord":"g1f3","count":57569},{"coord":"b1c3","count":22650},{"coord":"c4d5","count":7033},{"coord":"e2e3","count":3227},{"coord":"d1c2","count":64},{"coord":"c1f4","count":57},{"coord":"g2g3","count":32}]}} \ No newline at end of file