nomemory / neat-chess

Java UCI Protocol implementation (Universal Chess Engine)

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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

Tested with Stockfish 13.

Maven:

<dependency>
  <groupId>net.andreinc</groupId>
  <artifactId>neatchess</artifactId>
  <version>1.0</version>
</dependency>

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, ...]}

About

Java UCI Protocol implementation (Universal Chess Engine)


Languages

Language:Java 99.9%Language:Kotlin 0.1%