multistockfish 0.6.0
multistockfish: ^0.6.0 copied to clipboard
Multiple flavors of Stockfish Engine
multistockfish #
Multiple flavors of Stockfish Engine.
This plugin provides the following Stockfish engines:
- Stockfish 19, with a small embedded NNUE (~1MB)
- Stockfish 19, without embedded NNUE
- Fairy-Stockfish, for chess variants
Usage #
Start an engine #
An engine is a handle you create and dispose. Stockfish.create() starts one
and completes when it is ready for commands.
Note
When using the StockfishFlavor.latestNoNNUE flavor, you need to download the .nnue file before
starting an evaluation, since it is not embedded in the binary. Stockfish 19
evaluates with a single net, replacing the big/small pair Stockfish 18 used.
StockfishFlavor.light embeds a ~1MB net instead, so it needs no download.
import 'package:multistockfish/multistockfish.dart';
// defaults to StockfishFlavor.light
final stockfish = await Stockfish.create();
// state is a ValueListenable<StockfishState>
print(stockfish.state.value); // StockfishState.ready
// for latestNoNNUE, the NNUE file path is required
final latest = await Stockfish.create(
flavor: StockfishFlavor.latestNoNNUE,
nnuePath: '/path/to/nn-1a298aa575a0.nnue',
);
One engine per flavor #
The handle is the flavor. At most one engine per StockfishFlavor can be
live at a time: create() throws a StateError while another engine of the
same flavor holds the slot, and dispose() frees it. Engines of different
flavors are independent and can run side by side, each with its own stdin,
stdout, state and diagnostics.
// An NNUE engine for analysis and a Fairy-Stockfish opponent, at the same time.
final analysis = await Stockfish.create(flavor: StockfishFlavor.light);
final opponent = await Stockfish.create(
flavor: StockfishFlavor.variant,
variant: 'crazyhouse',
);
// Refused: light is taken until `analysis` is disposed.
await Stockfish.create(flavor: StockfishFlavor.light); // throws StateError
An engine that ends releases its flavor's slot without waiting to be disposed,
so a replacement can be created straight away. dispose() is still what you
call to stop one that is still running.
UCI command #
stockfish.stdin = 'isready';
stockfish.stdin = 'go movetime 3000';
stockfish.stdin = 'go infinite';
stockfish.stdin = 'stop';
Engine output is directed to a Stream<String>, add a listener to process results.
stockfish.stdout.listen((line) {
// do something useful
print(line);
});
create() only completes once the engine is ready, so a listener attached to
stdout afterwards has already missed the banner and the UCI handshake. Pass
onStdout to see those too — it is attached before the engine is spawned and
receives every line for the engine's whole life.
final stockfish = await Stockfish.create(onStdout: console.add);
Quit / Hot reload #
There are two active isolates per running Stockfish engine. That interferes with Flutter's hot reload feature so you need to dispose your engines before attempting to reload.
dispose() sends the UCI quit command, waits for the engine to exit and frees
its flavor's slot. An engine that will not exit is given up on rather than
waited for forever.
await stockfish.dispose();
// the stdout stream is closed, and the handle stays dead
print(stockfish.state.value); // StockfishState.disposed
Sending quit over stdin works too: the engine exits cleanly and the handle
ends as disposed just the same. Only an engine that died badly ends as
StockfishState.error.