Java UCI Protocol implementation (Universal Chess Engine)

Related tags

Testing neat-chess
Overview

A simple UCI (Universal Chess Interface) Client written in Java.

Tested with Stockfish 13.

Documentation

Starting / Closing the client

By using the startStockfish() method, the client assumes that Stockfish is already installed on the system, and it's accesible in $PATH as "stockfish".

var uci = new UCI();
uci.startStockfish();

Using start(String cmd) neat-chess can be tested with other chess engines:

// leela chess 
var uci = new UCI();
uci.start("lc0");

By default each command you are sending to the engine, has a timeout of 60s (during which the thread is blocked).

You can configure the global default timeout to a different value:

var uci = new UCI(5000l); // default timeout 5 seconds
uci.startStockfish();

If the client commands exceed the timeout interval an unchecked UCITimeoutException is thrown. But more on that in the next sections.

To retrieve the defaultTimeout simply call: var timeout = uci.getDefaultTimeout().

To close the client simply call: uci.close();.

Commands

Most commands respond with an UCIResponse<T> object. This class wraps a possible exception, and the actual result value.

The most common idiom(s) is/are:

var response = uci.<some_command>(...);
var result = response.getResultOrThrow();
// do something with the result

or

var response = uci.<some_command>(...);
if (response.succes()) {
  var result = response.getResult();
  // do something with the result
}

An UCIResponse<T> can only throw a UCIRuntimeException. This class has the following sub-types:

  • UCIExecutionException - when the Thread executing the command fails;
  • UCIInterruptedException- when the Thread executing the command fails;
  • UCITimeoutException - when Thread exeucuting the command timeouts;
  • UCIUncheckedIOException - when the communication with the engine process fails;
  • UCIUnknownCommandException - when the server doesn't understand the command the client has sent;
  • UCIParsingException - when the engine sends output that the client doesn't understand;

All commands support custom timeout. Usually this is the last parametere of the command function:

var response = uci.<some_command>(args, long timeout);

Retrieving the engine information

The method for obtaining the engine information (the current engine name and supported engine options) is UCIResponse<EngineInfo> = uci.getEngineInfo().

Example:

var uci = new UCI(5000l); // default timeout 5 seconds
uci.startStockfish();
UCIResponse<EngineInfo> response = uci.getEngineInfo();
if (response.success()) {

    // Engine name
    EngineInfo engineInfo = response.getResult();
    System.out.println("Engine name:" + engineInfo.getName());
    
    // Supported engine options
    System.out.println("Supported engine options:");
    Map<String, EngineOption> engineOptions = engineInfo.getOptions();
    engineOptions.forEach((key, value) -> {
        System.out.println("\t" + key);
        System.out.println("\t\t" + value);
    });
}
uci.close();

Output:

Engine name:Stockfish 13
Supported engine options:
	Hash
		SpinEngineOption{name='Hash', defaultValue=16, min=1, max=33554432}
	Move Overhead
		SpinEngineOption{name='Move Overhead', defaultValue=10, min=0, max=5000}
	UCI_AnalyseMode
		CheckEngineOption{name='UCI_AnalyseMode', defaultValue=false}
	UCI_LimitStrength
		CheckEngineOption{name='UCI_LimitStrength', defaultValue=false}
	Threads
		SpinEngineOption{name='Threads', defaultValue=1, min=1, max=512}
	MultiPV
		SpinEngineOption{name='MultiPV', defaultValue=1, min=1, max=500}
/// and so on

Setting an option

For changing an option of the engine you can use the following methods:

  • UCIResponse<List<String>> setOption(String optionName, String value, long timeout)
  • UCIResponse<List<String>> setOption(String optionName, String value)

For example, modifying the MultiPV option to 10 is as simple as:

var uci = new UCI(5000l); // default timeout 5 seconds
uci.startStockfish();
uci.setOption("MultiPV", "10", 3000l).getResultOrThrow(); // custome timeout 3 seconds
uci.close();

Getting the best move for a position

If you plan using the engine to analyse various positions that are not part of the same game, it's recommended to call the uciNewGame() first.

An UCI engine understands FEN notations, so the method to make the engine aware of the position he needs to analyse is:

  • UCIResponse<List<String>> positionFen(String fen, long timeout)
  • UCIResponse<List<String>> positionFen(String fen)

After the position has been set on the board, to retrieve the best move:

  • UCIResponse<BestMove> bestMove(int depth, long timeout) - to analyse for a given depth (e.g.: 18 moves deep);
  • UCIResponse<BestMove> bestMove(int depth);
  • UCIResponse<BestMove> bestMove(long moveTime, long timeout) - to analyse for a fixed amount of time (e.g.: 10000l - 10 seconds);
  • UCIResponse<BestMove> bestMove(long moveTime);

Let's take the example the following position:

The corresponding FEN for the position is:

rnbqk3/pp6/5b2/2pp1p1p/1P3P1P/5N2/P1PPP1P1/RNBQKB2 b Qq - 0 14

The code that determines what the best move is:

var uci = new UCI(5000l); // default timeout 5 seconds
uci.startStockfish();
uci.uciNewGame();

uci.positionFen("rnbqk3/pp6/5b2/2pp1p1p/1P3P1P/5N2/P1PPP1P1/RNBQKB2 b Qq - 0 14");

var result10depth = uci.bestMove(10).getResultOrThrow();
System.out.println("Best move after analysing 10 moves deep: " + result10depth);

var result10seconds = uci.bestMove(10_000l).getResultOrThrow();
System.out.println("Best move after analysing for 10 seconds: " + result10seconds);

uci.close();

Analysing a position

Analysing the best N lines for a given FEN position is very similar to the code for finding what is best move.

The methods for finding out what are the best lines are:

  • UCIResponse<Analysis> analysis(long moveTime, long timeout) - to analyse for a given depth (e.g.: 18 moves deep);
  • UCIResponse<Analysis> analysis(long moveTime)
  • UCIResponse<Analysis> analysis(int depth, long timeout) - to analyse for a fixed amount of time (e.g.: 10000l - 10 seconds);
  • UCIResponse<Analysis> analysis(int depth)

By default Stockfish analyses only one line, so if you want to analyse multiple lines in parallel, you need to set MultiPV: uci.setOption("MultiPV", "10", 3000l)

Let's take for example the following position:

The corresponding FEN for the position is:

r1bqkb1r/2pp1ppp/p1n2n2/1p2p3/4P3/1B3N2/PPPP1PPP/RNBQK2R w KQkq - 2 6

And in order to get the 10 best continuations, the code is:

var uci = new UCI();
uci.startStockfish();
uci.setOption("MultiPV", "10");

uci.uciNewGame();
uci.positionFen("r1bqkb1r/2pp1ppp/p1n2n2/1p2p3/4P3/1B3N2/PPPP1PPP/RNBQK2R w KQkq - 2 6");
UCIResponse<Analysis> response = uci.analysis(18);
var analysis = response.getResultOrThrow();

// Best move
System.out.println("Best move: " + analysis.getBestMove());
System.out.println("Is Draw: " + analysis.isDraw());
System.out.println("Is Mate: " + analysis.isMate());

// Possible best moves
var moves = analysis.getAllMoves();
moves.forEach((idx, move) -> {
    System.out.println("\t" + move);
});

uci.close();

The output:

Best move: 
	Move{lan='e1g1', strength=0.6, pv=1, depth=18, continuation=[c8b7, d2d3, f8c5, b1c3, ...]}
Best moves:
	Move{lan='e1g1', strength=0.6, pv=1, depth=18, continuation=[c8b7, d2d3, f8c5, b1c3, ...]}
	Move{lan='d2d4', strength=0.55, pv=2, depth=18, continuation=[d7d6, c2c3, f8e7, e1g1, ...]}
	Move{lan='a2a4', strength=0.52, pv=3, depth=18, continuation=[c8b7, d2d3, b5b4, b1d2, ...]}
	Move{lan='b1c3', strength=0.36, pv=4, depth=18, continuation=[f8e7, d2d3, d7d6, a2a4, ...]}
	Move{lan='d2d3', strength=0.26, pv=5, depth=18, continuation=[f8c5, b1c3, d7d6, c1g5, ...]}
	Move{lan='d1e2', strength=-0.02, pv=6, depth=18, continuation=[f8c5, a2a4, a8b8, a4b5, ...]}
	Move{lan='c2c3', strength=-0.50, pv=7, depth=18, continuation=[f6e4, e1g1, d7d5, f1e1, ...]}
	Move{lan='b3d5', strength=-0.57, pv=8, depth=18, continuation=[f6d5, e4d5, c6d4, f3d4, ...]}
	Move{lan='h2h3', strength=-0.63, pv=9, depth=18, continuation=[f6e4, e1g1, d7d5, b1c3, ...]}
	Move{lan='f3g5', strength=-0.7, pv=10, depth=18, continuation=[d7d5, d2d3, c6d4, e4d5, ...]}
Comments
  • fix bug in bestmove method by changing best_move_regex

    fix bug in bestmove method by changing best_move_regex

    Fix for https://github.com/nomemory/neat-chess/issues/2

    build.gradle needs to be fixed in order to publish artifacts, as MavenDeployment is no longer supported

    cc. @nomemory

    opened by Michal299 3
  • Method net.andreinc.neatchess.client.processor.BestMoveProcessor#process throws UCIRuntimeException when no ponder is returned

    Method net.andreinc.neatchess.client.processor.BestMoveProcessor#process throws UCIRuntimeException when no ponder is returned

    Description

    Method net.andreinc.neatchess.client.processor.BestMoveProcessor#process throws

    net.andreinc.neatchess.client.exception.UCIRuntimeException: Cannot find best movement in engine output!
    

    exception when stockfish return no ponder.

    Steps to reproduce

    var uci = new UCI();
    uci.startStockfish();
    uci.uciNewGame();
    uci.positionFen("Q1b3kr/p1p1P1p1/3p2Bp/8/P4N2/8/1PP2PPP/R1B1R1K1 w - - 3 25");
    System.out.println(uci.bestMove(18).getResultOrThrow());
    

    Actual result

    Exception in thread "main" net.andreinc.neatchess.client.exception.UCIRuntimeException: net.andreinc.neatchess.client.exception.UCIRuntimeException: Cannot find best movement in engine output!
    
    	at net.andreinc.neatchess.client.UCI.lambda$command$1(UCI.java:127)
    	at java.base/java.util.concurrent.CompletableFuture.uniHandle(CompletableFuture.java:934)
    	at java.base/java.util.concurrent.CompletableFuture$UniHandle.tryFire(CompletableFuture.java:911)
    	at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510)
    	at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1773)
    	at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1760)
    	at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373)
    	at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1182)
    	at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1655)
    	at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1622)
    	at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165)
    Caused by: net.andreinc.neatchess.client.exception.UCIRuntimeException: Cannot find best movement in engine output!
    
    	at net.andreinc.neatchess.client.processor.BestMoveProcessor.lambda$process$0(BestMoveProcessor.java:19)
    	at java.base/java.util.Optional.orElseThrow(Optional.java:403)
    	at net.andreinc.neatchess.client.processor.BestMoveProcessor.process(BestMoveProcessor.java:19)
    	at net.andreinc.neatchess.client.UCI.lambda$command$1(UCI.java:124)
    	... 10 more
    

    Expected result

    Valid move should be returned, for example a8c8, which is mate.

    Returned value from stockfish

    go bestmove depth 18
    info string NNUE evaluation using nn-6877cd24400e.nnue enabled
    info depth 1 seldepth 1 multipv 1 score mate 1 nodes 196 nps 196000 tbhits 0 time 1 pv a8c8
    info depth 2 seldepth 2 multipv 1 score mate 1 nodes 390 nps 390000 tbhits 0 time 1 pv a8c8
    info depth 3 seldepth 2 multipv 1 score mate 1 nodes 582 nps 582000 tbhits 0 time 1 pv a8c8
    info depth 4 seldepth 2 multipv 1 score mate 1 nodes 774 nps 774000 tbhits 0 time 1 pv a8c8
    info depth 5 seldepth 2 multipv 1 score mate 1 nodes 966 nps 483000 tbhits 0 time 2 pv a8c8
    info depth 6 seldepth 2 multipv 1 score mate 1 nodes 1158 nps 579000 tbhits 0 time 2 pv a8c8
    info depth 7 seldepth 2 multipv 1 score mate 1 nodes 1350 nps 675000 tbhits 0 time 2 pv a8c8
    info depth 8 seldepth 2 multipv 1 score mate 1 nodes 1542 nps 771000 tbhits 0 time 2 pv a8c8
    info depth 9 seldepth 2 multipv 1 score mate 1 nodes 1734 nps 867000 tbhits 0 time 2 pv a8c8
    info depth 10 seldepth 2 multipv 1 score mate 1 nodes 1926 nps 963000 tbhits 0 time 2 pv a8c8
    info depth 11 seldepth 2 multipv 1 score mate 1 nodes 2118 nps 1059000 tbhits 0 time 2 pv a8c8
    info depth 12 seldepth 2 multipv 1 score mate 1 nodes 2310 nps 1155000 tbhits 0 time 2 pv a8c8
    info depth 13 seldepth 2 multipv 1 score mate 1 nodes 2502 nps 834000 tbhits 0 time 3 pv a8c8
    info depth 14 seldepth 2 multipv 1 score mate 1 nodes 2694 nps 898000 tbhits 0 time 3 pv a8c8
    info depth 15 seldepth 2 multipv 1 score mate 1 nodes 2886 nps 962000 tbhits 0 time 3 pv a8c8
    info depth 16 seldepth 2 multipv 1 score mate 1 nodes 3078 nps 1026000 tbhits 0 time 3 pv a8c8
    info depth 17 seldepth 2 multipv 1 score mate 1 nodes 3270 nps 1090000 tbhits 0 time 3 pv a8c8
    info depth 18 seldepth 2 multipv 1 score mate 1 nodes 3464 nps 1154666 tbhits 0 time 3 pv a8c8
    bestmove a8c8
    
    opened by Michal299 2
  • Downgrade Language Level for Java 8

    Downgrade Language Level for Java 8

    Remove usage of var to be compatible with java 8 so that I can use it properly

    I am planning to both use this on a project that doesn't use maven, and also is at the java 8 language level. I plan to directly clone this repo to use as a dep.

    I use java 8 because java 8 was the last java version to ship with javafx, and the only thing this repo has stopping is the usage of var so I went through and resolved all of them to an explicit type.

    NOTE I did add a .gitignore as it was missing.

    opened by jojo2357 1
  • UCIRuntimeException (Cannot find best move)

    UCIRuntimeException (Cannot find best move)

    Using Stockfish 15, I am getting this exception:

    Exception in thread "AWT-EventQueue-0" net.andreinc.neatchess.client.exception.UCIRuntimeException: net.andreinc.neatchess.client.exception.UCIRuntimeException: Cannot find best movement in engine output!
    
    	at net.andreinc.neatchess.client.UCI.lambda$command$1(UCI.java:127)
    	at java.base/java.util.concurrent.CompletableFuture.uniHandle(CompletableFuture.java:934)
    	at java.base/java.util.concurrent.CompletableFuture$UniHandle.tryFire(CompletableFuture.java:911)
    	at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510)
    	at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1773)
    	at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1760)
    	at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373)
    	at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1182)
    	at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1655)
    	at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1622)
    	at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165)
    Caused by: net.andreinc.neatchess.client.exception.UCIRuntimeException: Cannot find best movement in engine output!
    
    	at net.andreinc.neatchess.client.processor.BestMoveProcessor.lambda$process$0(BestMoveProcessor.java:19)
    	at java.base/java.util.Optional.orElseThrow(Optional.java:403)
    	at net.andreinc.neatchess.client.processor.BestMoveProcessor.process(BestMoveProcessor.java:19)
    	at net.andreinc.neatchess.client.UCI.lambda$command$1(UCI.java:124)
    	... 10 more
    Caused by: net.andreinc.neatchess.client.exception.UCIRuntimeException: Cannot find best movement in engine output!
    

    This is happening here in my code:

        private void autoPlayEngine() {
    
            this.uci.uciNewGame();
    
            final String stringedFen = Fen.fromBoard(this.board).toString();
            System.out.println("Sending FEN to engine: " + stringedFen);
            this.uci.positionFen(stringedFen);
    
            // Exception thrown on either of these lines (i can't tell):
            final UCIResponse<BestMove> uciResponse = this.uci.bestMove(10, 3000);
            System.out.println("Best move: " + uciResponse.getResultOrThrow()); 
            
            // rest of my method
        }
    

    I have ensured that the FEN i'm sending is correct.

    This issue happens often in the late-endgame for whatever reason. And it always happens if stockfish has mate-in-one with either pieces. It appears to be unable to deliver the mate.

    //

    Example position with thrown exception I was letting stockfish play a game against itself automatically. Auto play was enabled, which for my application means that there is a clock thread which prompts the configured chess engine to make a move every second. But on this final move, stockfish timed out. Thus, i disabled auto play and send the command to make a move manually (which runs the same code, just not on a clock) - and the exception is thrown.

    //update 1: I was able to determine that the exception is thrown by trying to get the best move without checking UCIResponse#success(). However, no matter how many times tried, it is still incapable of performing mate-in-one.

    //update 2: I think this happens because of pondering. Did the following:

    • Started a stockfish instance in windows CMD
    • input the FEN
    • input "go ponder"
    • input "go bestmove"
    • no response!

    However, if i leave out "go ponder", it will return a move properly. This is confusing, because i did not set stockfish to ponder. And when launching stockfish via...

    this.uci = new UCI();
    this.uci.start("C:\\Users\\Admin\\Desktop\\stockfish_15.1_win_x64_popcnt\\stockfish.exe");
    

    ... it prints that pondering is turned off: Ponder: CheckEngineOption{name='Ponder', defaultValue=false}

    opened by jaylawl 0
  • Improved instructions on how to start an engine

    Improved instructions on how to start an engine

    I've tried for a good half hour to start stockfish using the provided startStockfish() method.

    I have manually added the $PATH on my windows environment.

    No matter what variation I tried, the code was unable to start stockfish - despite using the PATH in CMD working fine.

    The thrown exceptions unfortunately were not helpful at all. It was the same general message every time.

    My suggestion in a nut shell:

    • throw more detailed exceptions as to what went wrong when failing to start a uci instance

    • provide more Idiot -proof instructions in the readme.

    Eventually though, I got it work by just pasting the raw path of stockfish.exe. whatever works

    opened by jaylawl 2
  • Add a license

    Add a license

    Hello!

    I'm messing about with this API for a chess project. Its been great so far, mostly without issues! So thanks for providing this, first off.

    As for usage of this code, I am unaware as to what Is allowed and what isn't.

    Due to that, I am asking that you please add a clarifying license to the project.

    Thanks!

    opened by jaylawl 0
Owner
Andrei Ciobanu
Andrei Ciobanu
cdp4j - Chrome DevTools Protocol for Java

cdp4j - Browser automation libray for Java cdp4j is Java library with a clear and concise API to automate Chrome/Chromium based browser. It use Google

WebFolder 148 Jun 16, 2022
A collection of bite size examples for using chrome DevTools protocol commands with Selenium Webdriver v4.

selenium-devtools-guide A collection of bite size examples for using chrome DevTools protocol commands with Selenium Webdriver v4. Chrome Devtools Pro

Sudharsan Selvaraj 4 Aug 12, 2021
A Camunda Process Engine Plugin to execute Clojure Functions from Activities

camunda-clojure-plugin A Camunda Process Engine Plugin to execute Clojure Functions as Delegates Why do we need this? While Camunda is tightly integra

lambdaschmiede GmbH 11 Oct 11, 2022
The engine for the classification of texts into negative, neutral or positive sentiment (sentiment analysis)

Umigon-core The classification engine for sentiment analysis. The basic operations are: decompose the text into n-grams create a version of the n-gram

Clement Levallois 3 Jun 23, 2022
A Java architecture test library, to specify and assert architecture rules in plain Java

ArchUnit is a free, simple and extensible library for checking the architecture of your Java code. That is, ArchUnit can check dependencies between pa

TNG Technology Consulting GmbH 2.5k Jan 2, 2023
HATEOAS with HAL for Java. Create hypermedia APIs by easily serializing your Java models into HAL JSON.

hate HATEOAS with HAL for Java. Create hypermedia APIs by easily serializing your Java models into HAL JSON. More info in the wiki. Install with Maven

null 20 Oct 5, 2022
Never debug a test again: Detailed failure reports and hassle free assertions for Java tests - Power Asserts for Java

Scott Test Reporter for Maven and Gradle Get extremely detailed failure messages for your tests without assertion libraries, additional configuration

Dávid Csákvári 133 Nov 17, 2022
TCP Chat Application - Java networking, java swing

TCP-Chat-Application-in-Java TCP Chat Application - Java networking, java swing Java – Multithread Chat System Java Project on core Java, Java swing &

Muhammad Asad 5 Feb 4, 2022
A sample repo to help you handle basic auth for automation test in Java-selenium on LambdaTest. Run your Java Selenium tests on LambdaTest platform.

How to handle basic auth for automation test in Java-selenium on LambdaTest Prerequisites Install and set environment variable for java. Windows - htt

null 12 Jul 13, 2022
A sample repo to help you clear browser cache with Selenium 4 Java on LambdaTest cloud. Run your Java Selenium tests on LambdaTest platform.

How to clear browser cache with Selenium 4 Java on LambdaTest cloud Prerequisites Install and set environment variable for java. Windows - https://www

null 12 Jul 13, 2022
A sample repo to help you run automation test in incognito mode in Java-selenium on LambdaTest. Run your Java Selenium tests on LambdaTest platform.

How to run automation test in incognito mode in Java-selenium on LambdaTest Prerequisites Install and set environment variable for java. Windows - htt

null 12 Jul 13, 2022
A sample repo to help you handle cookies for automation test in Java-selenium on LambdaTest. Run your Java Selenium tests on LambdaTest platform.

How to handle cookies for automation test in Java-selenium on LambdaTest Prerequisites Install and set environment variable for java. Windows - https:

null 13 Jul 13, 2022
A sample repo to help you set geolocation for automation test in Java-selenium on LambdaTest. Run your Java Selenium tests on LambdaTest platform.

How to set geolocation for automation test in Java-selenium on LambdaTest Prerequisites Install and set environment variable for java. Windows - https

null 12 Jul 13, 2022
A sample repo to help you capture JavaScript exception for automation test in Java-selenium on LambdaTest. Run your Java Selenium tests on LambdaTest platform.

How to capture JavaScript exception for automation test in Java-selenium on LambdaTest Prerequisites Install and set environment variable for java. Wi

null 12 Jul 13, 2022
A sample repo to help you find an element by text for automation test in Java-selenium on LambdaTest. Run your Java Selenium tests on LambdaTest platform.

How to find an element by text for automation test in Java-selenium on LambdaTest Prerequisites Install and set environment variable for java. Windows

null 12 Jul 13, 2022
A sample repo to help you emulate network conditions in Java-selenium automation test on LambdaTest. Run your Java Selenium tests on LambdaTest platform.

How to emulate network conditions in Java-selenium automation test on LambdaTest Prerequisites Install and set environment variable for java. Windows

null 12 Jul 13, 2022
Awaitility is a small Java DSL for synchronizing asynchronous operations

Testing asynchronous systems is hard. Not only does it require handling threads, timeouts and concurrency issues, but the intent of the test code can

Awaitility 3.3k Dec 31, 2022