3 releases (1 stable)
| 1.1.0 | Feb 23, 2025 |
|---|---|
| 0.2.0 | Oct 17, 2024 |
| 0.1.0 | Oct 3, 2024 |
#1147 in Game dev
608 downloads per month
90KB
1.5K
SLoC
A Universal Chess Interface (UCI) Parser
This crate contains types and functions for communicating with chess engines and chess GUIs through the Universal Chess Interface (UCI) protocol.
Overview
The primary function of this crate is to provide well-typed representations for every message/command described in the UCI protocol, along with an easy-to-use API for converting between these well-typed representations and strings.
At present, parsing is only supported for the GUI-to-Engine types (UciCommand). Parsing support for Engine-to-GUI types (UciResponse) is not yet implemented.
Examples
The simplest use case is parsing commands like uci, which is as easy as it sounds:
use uci_parser::UciCommand;
let cmd = UciCommand::new("uci").unwrap();
assert_eq!(cmd, UciCommand::Uci);
Commands implement FromStr, so you can call .parse():
use uci_parser::UciCommand;
// Arbitrary whitespace is handled appropriately
let cmd = "setoption name \n Threads value \t 16".parse::<UciCommand>();
assert!(cmd.is_ok());
assert_eq!(
cmd.unwrap(),
UciCommand::SetOption {
name: "Threads".to_string(),
value: Some("16".to_string())
}
);
Commands that have many optional arguments, like go, implement Default so they can be parsed cleanly:
use std::time::Duration;
use uci_parser::{UciCommand, UciSearchOptions};
let cmd = "go movetime 42".parse::<UciCommand>();
assert!(cmd.is_ok());
assert_eq!(
cmd.unwrap(),
UciCommand::Go(UciSearchOptions {
// Times are provided as Durations
movetime: Some(Duration::from_millis(42)),
..Default::default()
})
);
Engine-to-GUI responses can also be created and printed easily:
use uci_parser::UciResponse;
let resp = UciResponse::BestMove {
bestmove: Some("e2e4"),
ponder: Some("c7c5")
};
assert_eq!(resp.to_string(), "bestmove e2e4 ponder c7c5");
Some responses, such as info and readyok, have helper functions:
use uci_parser::{UciResponse, UciInfo, UciScore};
let score = UciScore::cp(42);
let info = UciInfo::new().nodes(440).depth(2).score(score);
let resp = UciResponse::info(info);
// `info` params are displayed in the same order every time.
assert_eq!(resp.to_string(), "info depth 2 nodes 440 score cp 42");
Custom error types exist for when parsing fails:
use uci_parser::{UciCommand, UciParseError};
let unknown = "shutdown".parse::<UciCommand>();
assert!(matches!(
unknown.unwrap_err(),
UciParseError::UnrecognizedCommand { cmd: _ }
));
let invalid = "position default".parse::<UciCommand>();
assert!(matches!(
invalid.unwrap_err(),
UciParseError::InvalidArgument { cmd: _, arg: _ }
));
let insufficient = "setoption".parse::<UciCommand>();
assert!(matches!(
insufficient.unwrap_err(),
UciParseError::InsufficientArguments { cmd: _ }
));
Crate Features
How edge cases should be handled is a delicate subject and the correct answer depends on the needs of your engine. Rather than enforce my own opinion on handling those edge cases, I've marked them as crate features.
-
parse-go-perft: Adds support for parsingperft <depth>as an argument to thegocommand.- This is not part of the UCI protocol, but is common among engines.
-
parse-bench: Adds support for parsing the stringbenchintoUciCommand::Bench.- This is not part of the UCI protocol, but is common among engines and very useful for engine development.
- The arguments to
benchare the same as the arguments togo, since both commands involve running searches.
-
parse-position-kiwipete: Adds support to parsekiwipeteas a special argument toposition(similar tostartpos).- The "kiwipete" position is useful for debugging engines, as it is a messy position with many possible moves available.
- If enabled,
position kiwipetewill be equivalent to parsingposition fen r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 1, so thefenfield ofPositionwill beSome(<kiwipete fen>).
-
validate-promotion-moves: Restricts the grammar when parsing moves in UCI notation to ensure that promotions are valid:- By default, moves are parsed with the grammar
[a-h][1-8][a-h][1-8][pnbrqk], which will parsee2e4psuccessfully, even though it doesn't "make sense" in-game. With this feature enabled, the grammar restricts to:[a-h][1-8][a-h][1-8] | [a-h]7[a-h]8[qnrb] | [a-h]2[a-h]1[qnrb]. This means that only moves from the penultimate ranks to the final ranks will be parsed as promotions, and the only valid pieces for a promotion are a Queen, Knight, Rook, or Bishop.
- By default, moves are parsed with the grammar
-
clamp-negatives: Clamps negative numbers to0when parsing.- By default, all numbers are assumed to be positive, and the parser will fail on strings like
go wtime -80. This is normally not a problem, - All numeric values within the UCI protocol should be positive. That said, there have been instances where some GUIs send negative move times, which could mean any variety of things. If this feature is enabled, all numeric values are clamped to at least 0. That is, if a GUI sent
go movetime -42, this crate will parse that as aDurationof0milliseconds. It is up to your engine to determine how to respond to these situations.
- By default, all numbers are assumed to be positive, and the parser will fail on strings like
-
err-on-unused-input: Causes the parser to fail if the input text was not fully consumed during parsing.- As per the protocol, unknown tokens encountered before a command are ignored (
joho debug onparses todebug on). Unknown tokens encountered while parsing a specific command will generate errors (debug joho onfails). Unknown tokens after a command are, by default, ignored (debug on johoparses todebug on). If this feature is enabled, the parser will fail if all tokens were not consumed during parsing (debug on johowill fail).
- As per the protocol, unknown tokens encountered before a command are ignored (
-
types: Exposes several well-typed representations of UCI components, such as moves.- By default, commands like
position startpos moves e2e4will yield a list ofStrings for all parsedmoves, leaving you to have to re-parse them in your engine later. If this feature is enabled, anyStringthat is parsed as a move will be converted toUciMove, which contains types representing the files, ranks, squares, and pieces involved in each move. - See the
typesmodule for more information.
- By default, commands like
Dependencies
~1–1.6MB
~32K SLoC