Chess-Titans

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

Tested with Stockfish 13.

Maven:

<dependency>
  <groupId>net.punith</groupId>
  <artifactId>chess-titans</artifactId>
  <version>1.0</version>
</dependency>

UCI Stockfish Integration 🎲

In the latest update of Chess-Titans, we’ve introduced an exciting new feature - the UCI Stockfish Integration! With this addition, Chess-Titans now harnesses the powerful AI capabilities of the renowned Stockfish chess engine to offer an even more challenging and engaging gameplay experience.

What’s New:

🔹 Stockfish-powered AI Opponent: The CPU opponent in the “VS CPU” mode has been upgraded to use Stockfish as its underlying engine. Stockfish is well-known for its exceptional strength and strategic prowess, making it a formidable adversary for players seeking a tough chess challenge.

🔹 Adjustable Difficulty Levels: With the UCI Stockfish Integration, players can now fine-tune the AI opponent’s difficulty level to match their skill and expertise. Whether you’re a casual player looking for a fun match or a seasoned chess enthusiast seeking a real test of your abilities, Chess-Titans has the perfect difficulty setting for you.

🔹 Enhanced Game Logic: The integration of Stockfish required us to rework and optimize various aspects of the game’s logic. From evaluating positions to calculating possible moves, the chess engine has been fine-tuned to deliver an unparalleled playing experience.

🔹 Seamless UCI Protocol Communication: The interaction between Chess-Titans and Stockfish is carried out flawlessly using the UCI (Universal Chess Interface) protocol. This ensures smooth communication, enabling the game to leverage Stockfish’s immense computational power while maintaining compatibility with the Arena GUI and other UCI-compatible interfaces.

What’s Next:

We’re thrilled with the inclusion of the UCI Stockfish Integration, but our journey doesn’t end here. We’re committed to continuously enhancing Chess-Titans and delivering the best possible chess experience to our players. In the upcoming updates, we plan to introduce additional features like online multiplayer functionality, new visual themes, and advanced analytics to further enrich the game.

Thank you for your continued support, and we hope you enjoy the enhanced chess gameplay experience with Chess-Titans! 🌟🏆🚀

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) chess-titans can be tested with other chess engines:

// chess-titans
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:

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:

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:

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

Let’s take the example the following position:

position01

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:

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:

position02

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

thank-you