Compare commits
43 Commits
Author | SHA1 | Date |
---|---|---|
Taylor Bockman | 4c12c857ff | 6 years ago |
Taylor Bockman | 4f8a5ef694 | 6 years ago |
Taylor Bockman | fef0bc8e44 | 6 years ago |
Taylor Bockman | 0ce59d6169 | 6 years ago |
Taylor Bockman | e049b59eb3 | 6 years ago |
Taylor Bockman | 8ca26fe648 | 6 years ago |
Taylor Bockman | 9b9e466cdb | 6 years ago |
Taylor Bockman | a73dae7568 | 6 years ago |
Taylor Bockman | d8577ee142 | 6 years ago |
Taylor Bockman | 428da563dd | 6 years ago |
Taylor Bockman | a91331d10f | 6 years ago |
Taylor Bockman | 587fa6a4a6 | 6 years ago |
Taylor Bockman | a1dae653fa | 6 years ago |
Taylor Bockman | 02638cd05a | 6 years ago |
Taylor Bockman | 5c9d13de42 | 6 years ago |
Taylor Bockman | 47438cf5ea | 6 years ago |
Taylor Bockman | c176d390bd | 6 years ago |
Taylor Bockman | 219032db9d | 6 years ago |
Taylor Bockman | bf87428887 | 6 years ago |
Taylor Bockman | 7c5c3338cd | 6 years ago |
Taylor Bockman | 54cab71bd7 | 6 years ago |
Taylor Bockman | cb4b8bbced | 6 years ago |
Taylor Bockman | be2ca9fc75 | 6 years ago |
Taylor Bockman | 72433e7c73 | 6 years ago |
Taylor Bockman | 039d1601ce | 6 years ago |
Taylor Bockman | 74822a9f2d | 6 years ago |
Taylor Bockman | 215af79b1e | 6 years ago |
Taylor Bockman | 9c24d8c7c3 | 6 years ago |
Taylor Bockman | 759e5bbe7e | 6 years ago |
Taylor Bockman | 64f4a7a89d | 6 years ago |
Taylor Bockman | 969aed2734 | 6 years ago |
Taylor Bockman | 3eca0caaa4 | 6 years ago |
Taylor Bockman | bee54e617c | 6 years ago |
Taylor Bockman | 4ed87916a5 | 6 years ago |
Taylor Bockman | 3eb729653a | 6 years ago |
Taylor Bockman | 3e2fbfa216 | 6 years ago |
Taylor Bockman | 2fcacc815d | 6 years ago |
Taylor Bockman | bae58bb10f | 6 years ago |
Taylor Bockman | 42f366b2aa | 6 years ago |
Taylor Bockman | 78e23831e0 | 6 years ago |
Taylor Bockman | a1d7ca7e64 | 6 years ago |
Taylor Bockman | 1875853925 | 6 years ago |
Taylor Bockman | d462ce8d11 | 6 years ago |
11 changed files with 759 additions and 2 deletions
@ -0,0 +1,7 @@
|
||||
language: rust |
||||
rust: |
||||
- stable |
||||
- nightly |
||||
matrix: |
||||
allow_failures: |
||||
- rust: stable |
@ -0,0 +1,19 @@
|
||||
# Contributing to UCI |
||||
|
||||
TODO |
||||
|
||||
|
||||
# Styleguide |
||||
|
||||
We try to make use of the styleguide found [here](https://github.com/rust-lang-nursery/fmt-rfcs/blob/master/guide/guide.md). |
||||
|
||||
## Character Limit |
||||
|
||||
We try to make each line less than or equal to 120 charcters. |
||||
|
||||
## Testing |
||||
|
||||
Prefer seperating out your tests into discrete modules where possible. In general: |
||||
|
||||
1. Engine "global" tests go into `tests/lib.rs` |
||||
2. Everything else gets it's own module inside `tests/` |
@ -0,0 +1,13 @@
|
||||
[package] |
||||
name = "uci" |
||||
version = "0.1.0" |
||||
authors = ["Taylor Bockman <angrygoats@protonmail.com>"] |
||||
description = "An implementation of the UCI specification in Rust" |
||||
repository = "https://github.com/angrygoats/uci" |
||||
readme = "README.md" |
||||
keywords = ["uci", "universal chess interface", "chess", "engine", "chess engine"] |
||||
categories = ["games", "game-engines", "api-bindings", "config", "development-tools"] |
||||
license = "GPL-3.0" |
||||
|
||||
[dependencies] |
||||
"either" = "1.4.0" |
@ -1,2 +1,137 @@
|
||||
# uci |
||||
An implementation of the Universal Chess Interface in Rust |
||||
# UCI |
||||
|
||||
UCI is an acronym for **U**niversal **C**hess **I**nterface. It is a standard for communication that competes with |
||||
XBoard/Winboard. [http://wbec-ridderkerk.nl/html/UCIProtocol.html](UCI) makes communication a little easier, but |
||||
it seems there's a nearly religious debate on which is better. |
||||
|
||||
Here are some benefits to using UCI: |
||||
|
||||
* It works with Chessbase 14 |
||||
* Simple to use |
||||
* Fewer bugs in code |
||||
* Built from scratch rather than ad-hoc |
||||
* Flexible time controls |
||||
* Additional search information can be displayed |
||||
|
||||
and to be fair, some downsides: |
||||
|
||||
* Stateless |
||||
* Delegates some important decisions to the GUI |
||||
* Difficult to add new features on top of it |
||||
* Sends the whole move list each turn |
||||
|
||||
Overall, UCI seems to be fairly popular and is worth considering for your next engine. |
||||
|
||||
## Why |
||||
|
||||
People shouldn't waste their time implementing protocols that really should be libraries. With this, you can |
||||
include it in your project, build your engine on top of it, and be able to focus on what matters - beating Stockfish. |
||||
|
||||
|
||||
### Yeah, but why Rust? |
||||
|
||||
Originally I had written part of this library in C++. However, considering how prone people are to errors in C++, I |
||||
decided to choose a language that provides a little more safety to the user. Currently |
||||
[Rust is beating Go](http://benchmarksgame.alioth.debian.org/u64q/compare.php?lang=rust&lang2=go) in the Computer |
||||
Language Benchmark Game, so it was the winner. People who write chess engines care about performance. If you're not |
||||
writing C/C++, Rust seems like the next best thing. |
||||
|
||||
|
||||
## Will You Implement Winboard? |
||||
|
||||
Yeah, probably. |
||||
|
||||
## Why GPL 3.0? |
||||
|
||||
I have noticed that there are no good _free_ solutions so that anyone can build a chess engine. Since UCI is |
||||
a common format, and something all engines (should) implement, making this chunk of it free makes _total_ sense. |
||||
|
||||
## Usage |
||||
|
||||
UCI is designed to complement your chess engine rather than be a one-stop shop for engine development. |
||||
|
||||
As such, you will be responsible for maintaining the game loop. What UCI provides is a series of functions |
||||
that will help you send data to, and receive data from the GUI. |
||||
|
||||
### First Steps |
||||
|
||||
Your engine will first need to wait on STDIN for a command `uci` from the GUI. This indicates that your engine should |
||||
switch to UCI mode. You can use `commands::UCI` to make sure the command text you are waiting for is correct. |
||||
|
||||
|
||||
Next, you'll need to create a copy of `Engine` by calling `Engine::new`. Once this is setup, you can call |
||||
`Engine::identify` to send identification information to the GUI. |
||||
|
||||
Once identification is done, you need to send your configuration options. This is dependent on your engine. Refer to |
||||
the UCI standard for the available options. Here is an example of an option configuration: |
||||
|
||||
```rust |
||||
let o1 = EngineOption { |
||||
name: constants::HASH, |
||||
option_type: EngineOptionType::Spin, |
||||
option_data: [(EngineOptionDataType::DefaultVal, EngineOptionData::Int(1)), |
||||
(EngineOptionDataType::Min, EngineOptionData::Int(1)), |
||||
(EngineOptionDataType::Max, EngineOptionData::Int(128)) |
||||
].iter().cloned().collect(); |
||||
}; |
||||
``` |
||||
|
||||
As nice as it would be to have fully typechecked options you will need to be careful a little here. The available |
||||
options your engine uses must be passed to `Engine::new` as an array of `Options::EngineOption<T>` |
||||
from `options.rs`. Every `Options::EngineOption<T>` has a `name`, which you can use any of the |
||||
`Options::*` constants to represent, a `type`, which can be an `Options::EngineOptionType`, and a hashmap |
||||
of `Options::EngineOptionDataType` to `Options::EngineOptionDataValue<T>`. Refer to the standard for more |
||||
information on option configurations. The `T` type parameters represents the type of that particular |
||||
`Options::EngineOption<T>` and allows us to at least fix the type in the HashMap to a single type. You can still |
||||
hurt yourself, but doing this makes it much harder. Keep this list of options close, the GUI may send back |
||||
information after call `Engine::send_available_options` to reconfigure default settings. |
||||
|
||||
You must configure this before calling `Engine::new` so the rest of this guide assumes you've done that already. |
||||
|
||||
To send your configuration options simply call `Engine::send_available_options`. Once this finishes `uciok` will |
||||
also be sent, indicating to the GUI your engine is ready to roll. At this point you need to set up two threads, one |
||||
to calculate with your engine, and one to read STDIN. |
||||
|
||||
``` |
||||
EXAMPLE HERE WITH LOOPS IN THREADS AND BLOCKING AND WHATEVER |
||||
``` |
||||
|
||||
Notice how in our STDIN thread we are calling `Engine::parse` and the handling the output using a match statement |
||||
depending on what kind of token it was. You are responsible for obeying the commands from this thread, the UCI library |
||||
just makes it convenient to work with. Here is an example of handling a parsed command: |
||||
|
||||
``` |
||||
EXAMPLE OF HANDLING A PARSED COMMAND AND SENDING BACK SOMETHING |
||||
``` |
||||
|
||||
At this point the engine will send a series of `setoption` commands to you in your STDIN thread. We can use the |
||||
`Engine::parse` function to get these: |
||||
|
||||
``` |
||||
EXAMPLE HERE |
||||
``` |
||||
|
||||
Once the options are extracted you will be responsible for configuring your engine with the values. The GUI will send |
||||
an `isready` command which you will be responsible for replying to by using `Engine::ready`. |
||||
|
||||
**TODO: MORE STUFF WITH EXAMPLES** |
||||
**THINGS LIKE SENDINB BEST MOVE AFTER CALCULATING, ETC** |
||||
**RECEIVING SETOPTION COMMANDS AND PROCESSING THEM** |
||||
|
||||
|
||||
### Other Options |
||||
|
||||
**TODO: Talk about the additional helpers available in the UCI library and what-not**. |
||||
|
||||
* Copy protection checking |
||||
* Registration checking |
||||
|
||||
** TODO: Put an example engine under `/examples` that does nothing but talks to the GUI and receives commands. |
||||
** Document it here and mention it can be used for guidance. |
||||
|
||||
|
||||
### Other Libraries in the works |
||||
|
||||
In the future more libraries will be available to help chess engine developers get started. I will be writing |
||||
a library to give some standard implementations of [Zobrist Hashing](https://en.wikipedia.org/wiki/Zobrist_hashing), |
||||
among other useful tools that are commonly re-implemented a thousand times for every engine. |
||||
|
@ -0,0 +1,57 @@
|
||||
//! Commands contains the command constants expected during the UCI lifecycle
|
||||
//! It is useful to note many of these commands do not end with '\n'. This is because they expect arguments.
|
||||
|
||||
|
||||
///////// Engine to GUI /////////
|
||||
|
||||
|
||||
/// ID is used to signal the engine name and author to the GUI
|
||||
pub const ID: &'static str = "id"; |
||||
|
||||
/// NAME is used to signal the engine name
|
||||
pub const NAME: &'static str = "name"; |
||||
|
||||
/// AUTHOR is used to signal the engine author
|
||||
pub const AUTHOR: &'static str = "author"; |
||||
|
||||
/// UCIOK is sent after the ID and optional options to tell the GUI that the engine has sent all infos and is ready
|
||||
/// in UCI mode
|
||||
pub const UCIOK: &'static str = "uciok\n"; |
||||
|
||||
/// READYOK is sent when the engine has received an "isready" command and has processed all input and is ready to
|
||||
/// accept new commands now
|
||||
pub const READYOK: &'static str = "readyok\n"; |
||||
|
||||
/// BESTMOVE is sent when the engine has stopped searching and found the move best in this position
|
||||
pub const BESTMOVE: &'static str = "bestmove"; |
||||
|
||||
/// COPYPROTECTIONCHECKING tells the GUI that the engine is checking the copy protection
|
||||
pub const COPYPROTECTIONCHECKING: &'static str = "copyprotection checking\n"; |
||||
|
||||
/// COPYPROTECTIONOK tells the GUI that the engine has verified the copy protection
|
||||
pub const COPYPROTECTIONOK: &'static str = "copyprotection ok\n"; |
||||
|
||||
/// COPYPROTECTIONERROR tells the GUI that the engine has rejected the copy protection
|
||||
pub const COPYPROTECTIONERROR: &'static str = "copyprotection error\n"; |
||||
|
||||
/// REGISTRATIONCHECKING tells the GUI that the engine is checking the registration
|
||||
pub const REGISTRATIONCHECKING: &'static str = "registration checking\n"; |
||||
|
||||
/// REGISTRATIONOK tells the GUI that the engine the registration has been verified
|
||||
pub const REGISTRATIONOK: &'static str = "registration ok\n"; |
||||
|
||||
/// REGISTRATIONERROR tells the GUI that the engine has rejected the registration
|
||||
pub const REGISTRATIONERROR: &'static str = "registration error\n"; |
||||
|
||||
/// INFO tells the GUI the engine wants to sent infos to the GUI. This should be done whenever the info has changed
|
||||
pub const INFO: &'static str = "info"; |
||||
|
||||
/// OPTION tells the GUI which parameters can be changed in the engine
|
||||
pub const OPTION: &'static str = "option"; |
||||
|
||||
/// OPTIONNAME tells the GUI which option name is being sent
|
||||
pub const OPTIONNAME: &'static str = "name"; |
||||
|
||||
/// TYPE tells the GUI the type of the option
|
||||
pub const TYPE: &'static str = "type"; |
||||
|
@ -0,0 +1,87 @@
|
||||
//! UCI is a simple library to allow people to ignore the lower-level protocol needed to create chess engines.
|
||||
//! The engine is fully generic. By specifying a valid reader and writing you can send the messages to STDIN
|
||||
//! and STDOUT, per the standard - or to memory for testing.
|
||||
|
||||
|
||||
// BIG TODO: Change pub struct members to private and use getters/setters. DO THIS FOR EVERYTHING AFTER YOU
|
||||
// IMPLEMENT OPTION SENDING.
|
||||
// ALSO CHANGE COMMANDS TO HAVE A CONSTANTS SUBMODULE LIKE OPTIONS
|
||||
|
||||
extern crate either; |
||||
|
||||
pub mod commands; |
||||
pub mod options; |
||||
pub mod parser; |
||||
|
||||
use std::io::{BufRead, Write}; |
||||
use std::vec; |
||||
|
||||
#[derive(Debug)] |
||||
pub struct Engine<'a, R, W> { |
||||
pub name: &'a str, |
||||
pub author: &'a str, |
||||
pub reader: R, |
||||
pub writer: W, |
||||
pub engine_options: Vec<options::EngineOption>, |
||||
} |
||||
|
||||
/// Notes to delete later:
|
||||
///
|
||||
///
|
||||
/// A way to make this comfortable for users to use is this provides easy access to commands as well as a
|
||||
/// loop and stuff they can use. This way they don't have to override anything - they just include this
|
||||
/// in their code for their engine to make the communication portion easy.
|
||||
///
|
||||
/// Think of this in the manner of a game engine. They don't provide the loop and everything, but they do
|
||||
/// provide convenience functions and stuff to make _building_ that loop easier.
|
||||
///
|
||||
/// In other words, the engine writer still needs to know how the standard works. They just don't need to
|
||||
/// implement all the nasty details.
|
||||
/// For example, a start up function calls the correct "boot up" code for the engine so they only have to
|
||||
/// worry about calling "boot up" prior to their main engine loop.
|
||||
///
|
||||
///
|
||||
/// TODO: THE NEXT THING - THE PARSER! (after putting in getters but _no_ setters and taking away pub fields
|
||||
/// for all structs in the app if that is idiomatic)
|
||||
///
|
||||
/// Additionally this code should have a parser. You call it with the reader supplied and it waits for a command
|
||||
/// and parses it into a tuple of <token, value>. For example it would parse `setoption` into an option constant
|
||||
/// and the value to set it to. This should be super generic and parsing results should just be of a type
|
||||
/// ParseResult. The tuple implements ParseResult which represents a tuple of a token and value.
|
||||
|
||||
impl<'a, R, W> Engine<'a, R, W> |
||||
where |
||||
R: BufRead, |
||||
W: Write, |
||||
{ |
||||
pub fn new(name: &'a str, author: &'a str, |
||||
reader: R, writer: W, engine_options: Vec<options::EngineOption>) -> Engine<'a, R, W> { |
||||
Engine { name, author, reader, writer, engine_options, |
||||
} |
||||
} |
||||
|
||||
/// Sends identification messages to the writer for the GUI to pick up
|
||||
pub fn identify(&mut self) { |
||||
let name_id: String = format!("{} {} {}\n", commands::ID, commands::NAME, self.name); |
||||
let author_id: String = format!("{} {} {}\n", commands::ID, commands::AUTHOR, self.author); |
||||
|
||||
// For these two writes we can panic - there's no possibility of recovery if the engine fails at this stage
|
||||
write!(&mut self.writer, "{}", name_id).expect("failed to send name identification to writer"); |
||||
write!(&mut self.writer, "{}", author_id).expect("failed to send author identification to writer"); |
||||
} |
||||
|
||||
/// Sends all available options in the options configuration and a final UCIOK meaning we are reading to go.
|
||||
pub fn send_available_options(&mut self) { |
||||
for eo in &self.engine_options { |
||||
write!(&mut self.writer, "{}", eo.to_string()).expect(&format!("failed to send `{}`", eo.to_string())); |
||||
} |
||||
|
||||
// Again this command must complete before we can say the engine is connected, so panicking at this stage is ok
|
||||
write!(&mut self.writer, "{}", commands::UCIOK).expect("failed to send `uciok` command"); |
||||
} |
||||
|
||||
/// Sends `readyok` to the GUI
|
||||
pub fn ready(&mut self) { |
||||
write!(&mut self.writer, "{}", commands::READYOK).expect("failed to send `readyok` command"); |
||||
} |
||||
} |
@ -0,0 +1,143 @@
|
||||
//! Options contains everything related to engine options. The idea behind this is to take as much
|
||||
//! advantage of the typechecker as possible. As a result, engine option names are constant static strings,
|
||||
//! and EngineOption tries to be flexible so it can be reused for each option, which also maintaining some
|
||||
//! of the nicer parts of typechecking.
|
||||
|
||||
use std::collections::HashMap; |
||||
use commands; |
||||
|
||||
/// These constants can be used for naming options easily. Option name is fairly flexible.
|
||||
pub mod constants { |
||||
|
||||
/// Represents the hash option
|
||||
pub const HASH: &'static str = "Hash"; |
||||
|
||||
/// Represents the Nalimov Path option
|
||||
pub const NALIMOVPATH: &'static str = "NalimovPath"; |
||||
|
||||
/// Represents the Nalimov Cache option
|
||||
pub const NALIMOVCACHE: &'static str = "NalimovCache"; |
||||
|
||||
/// Represents the ponder option
|
||||
pub const PONDER: &'static str = "Ponder"; |
||||
|
||||
/// Represents the OwnBook option
|
||||
pub const OWNBOOK: &'static str = "OwnBook"; |
||||
|
||||
/// Represents the MultiPV option
|
||||
pub const MULTIPV: &'static str = "MultiPV"; |
||||
|
||||
/// Represents the UCI_ShowCurrLine option
|
||||
pub const UCISHOWCURRLINE: &'static str = "UCI_ShowCurrLine"; |
||||
|
||||
/// Represents the UCI_Refutations option
|
||||
pub const UCISHOWREFUTATIONS: &'static str = "UCI_ShowRefutations"; |
||||
|
||||
/// Represents the UCI_LimitStrength option
|
||||
pub const UCILIMITSTRENGTH: &'static str = "UCI_LimitStrength"; |
||||
|
||||
/// Represents the UCI_Elo option
|
||||
pub const UCIELO: &'static str = "UCI_Elo"; |
||||
|
||||
/// Represents the UCI_AnalysisMode option
|
||||
pub const UCIANALYSISMODE: &'static str = "UCI_AnalysisMode"; |
||||
|
||||
/// Represents the UCI_Opponent option
|
||||
pub const UCIOPPONENT: &'static str = "UCI_Opponent"; |
||||
|
||||
} |
||||
|
||||
/// The `EngineOptionType` type used to indicate what type of option the GUI should display
|
||||
#[derive(Copy, Clone, Debug, PartialEq)] |
||||
pub enum EngineOptionType { |
||||
Check, |
||||
Spin, |
||||
Combo, |
||||
Button, |
||||
TypeString, // `String` is a reserved word so `TypeString` is substituted
|
||||
} |
||||
|
||||
#[derive(Clone, Debug, PartialEq, PartialOrd)] |
||||
/// The `EngineOptionData` makes the data type generic so one `EngineOption` can represent everything
|
||||
/// This would be set to the type of the engine option (ex. i32)
|
||||
pub enum EngineOptionData { |
||||
Int(i32), |
||||
Float(f64), |
||||
Text(String), |
||||
} |
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] |
||||
/// The `EngineOptionDataType` type used to indicate the type of the `EngineOption` setting
|
||||
pub enum EngineOptionDataType { |
||||
DefaultVal, // `Default` is reserved so `DefaultVal` is used
|
||||
Min, |
||||
Max, |
||||
Var, |
||||
} |
||||
|
||||
#[derive(Debug, PartialEq)] |
||||
/// The `EngineOption` type is the overarching type representing a single configurable engine option
|
||||
pub struct EngineOption { |
||||
pub name: &'static str, |
||||
pub option_type: EngineOptionType, |
||||
pub option_data: HashMap<EngineOptionDataType, EngineOptionData>, |
||||
} |
||||
|
||||
impl EngineOption { |
||||
|
||||
/// Constructs a new EngineOption of type T
|
||||
pub fn new(name: &'static str, option_type: EngineOptionType, |
||||
option_data: HashMap<EngineOptionDataType, EngineOptionData>) -> EngineOption { |
||||
EngineOption { name, option_type, option_data, } |
||||
} |
||||
|
||||
fn engine_option_data_string(d: &EngineOptionData) -> String { |
||||
match d { |
||||
&EngineOptionData::Int(v) => v.to_string(), |
||||
&EngineOptionData::Float(v) => v.to_string(), |
||||
&EngineOptionData::Text(ref v) => v.clone(), |
||||
} |
||||
} |
||||
|
||||
/// Turns the EngineOption into a string that can be sent to the GUI
|
||||
pub fn to_string(&self) -> String { |
||||
let mut option_data_string: String = String::new(); |
||||
|
||||
// NOTE: The user is left to understand what option takes which of these data type values. There's some
|
||||
// work that can be done later switching based on the option type, but for now it's reasonable to
|
||||
// expect the user understands the standard well enough to know which options take what kind
|
||||
// of settings.
|
||||
if self.option_data.contains_key(&EngineOptionDataType::DefaultVal) { |
||||
let data = self.option_data.get(&EngineOptionDataType::DefaultVal); |
||||
option_data_string.push_str(&format!(" {} {}", "default", |
||||
EngineOption::engine_option_data_string(&data.unwrap()))); |
||||
} |
||||
|
||||
if self.option_data.contains_key(&EngineOptionDataType::Min) { |
||||
let data = self.option_data.get(&EngineOptionDataType::Min); |
||||
option_data_string.push_str(&format!(" {} {}", "min", EngineOption::engine_option_data_string(&data.unwrap()))); |
||||
} |
||||
|
||||
if self.option_data.contains_key(&EngineOptionDataType::Max) { |
||||
let data = self.option_data.get(&EngineOptionDataType::Max); |
||||
option_data_string.push_str(&format!(" {} {}", "max", EngineOption::engine_option_data_string(&data.unwrap()))); |
||||
} |
||||
|
||||
if self.option_data.contains_key(&EngineOptionDataType::Var) { |
||||
let data = self.option_data.get(&EngineOptionDataType::Var); |
||||
option_data_string.push_str(&format!(" {} {}", "var", EngineOption::engine_option_data_string(&data.unwrap()))); |
||||
} |
||||
|
||||
|
||||
let ots = match self.option_type { |
||||
EngineOptionType::Check => "check", |
||||
EngineOptionType::Spin => "spin", |
||||
EngineOptionType::Combo => "combo", |
||||
EngineOptionType::Button => "button", |
||||
EngineOptionType::TypeString => "string", |
||||
}; |
||||
|
||||
format!("{} {} {} {} {}{}\n", commands::OPTION, commands::OPTIONNAME, self.name, |
||||
commands::TYPE, ots, option_data_string) |
||||
} |
||||
} |
@ -0,0 +1,92 @@
|
||||
//! Parser contains the command parser for handling receiving commands from the GUI
|
||||
|
||||
|
||||
use std::collections::HashMap; |
||||
use commands; |
||||
|
||||
use either::Either; |
||||
pub use either::Left; |
||||
pub use either::Right; |
||||
|
||||
/// Token represents a parsable token in the string sent via STDIN from the GUI
|
||||
#[derive(Debug, Eq, PartialEq)] |
||||
pub enum Token { |
||||
UCI, |
||||
DEBUG, |
||||
ISREADY, |
||||
SETOPTION, |
||||
REGISTER, |
||||
UCINEWGAME, |
||||
POSITION, |
||||
GO, |
||||
STOP, |
||||
PONDERHIT, |
||||
QUIT, |
||||
} |
||||
|
||||
#[derive(Clone, Debug, PartialEq, PartialOrd)] |
||||
pub enum CommandValue { |
||||
Int(i32), |
||||
Float(f64), |
||||
Text(String), |
||||
Boolean(bool), |
||||
} |
||||
|
||||
pub struct Parser {} |
||||
|
||||
/// TokenResult is a convenient way to package an entire command string. It is identified by it's associated
|
||||
/// Token, and the arguments.
|
||||
///
|
||||
/// Example:
|
||||
///
|
||||
/// setoption name Hash value 32
|
||||
/// --------- ---- ---- ----- --
|
||||
/// Token arg val arg val
|
||||
///
|
||||
/// debug on
|
||||
/// ----- --
|
||||
/// Token val
|
||||
///
|
||||
///
|
||||
/// The user is still responsible for knowing what to look for in the hashmap but this structure makes it far
|
||||
/// easier to work with commands. Some commands don't have arguments, in that case the args will be None.
|
||||
#[derive(Debug, PartialEq)] |
||||
pub struct TokenResult { |
||||
token: Token, |
||||
args: Either<Option<HashMap<&'static str, CommandValue>>, &'static str>, |
||||
} |
||||
|
||||
impl TokenResult { |
||||
|
||||
/// Intantiates a new TokenResult
|
||||
pub fn new(token: Token, args: Either<Option<HashMap<&'static str, CommandValue>>, &'static str>>) -> TokenResult { |
||||
TokenResult { token, args } |
||||
} |
||||
} |
||||
|
||||
impl Parser { |
||||
|
||||
/// Instantiates a new Parser
|
||||
pub fn new() -> Parser { |
||||
Parser{} |
||||
} |
||||
|
||||
/// Parses a single string from the GUI and turns it into a neatly packaged TokenResult for processing
|
||||
/// by the engine.
|
||||
pub fn parse(&self, s: &str) -> Result<TokenResult, &'static str> { |
||||
|
||||
// TODO: Remove the /n from the end
|
||||
|
||||
let tokens: Vec<&str> = s.split_whitespace().collect::<Vec<&str>>(); |
||||
|
||||
// TODO: Parse things based on the token, probably need a special function for each token based on a switch
|
||||
// of tokens[0].
|
||||
|
||||
|
||||
// remove this when it's working
|
||||
Ok(TokenResult::new(Token::SETOPTION, Left(Some(HashMap::new())))) |
||||
} |
||||
} |
||||
|
||||
|
||||
|
@ -0,0 +1,82 @@
|
||||
extern crate uci; |
||||
|
||||
use uci::Engine; |
||||
use uci::options::constants; |
||||
use uci::options::{ EngineOption, EngineOptionType, EngineOptionDataType, EngineOptionData }; |
||||
use std::str; |
||||
|
||||
#[test] |
||||
fn instantiate_new_engine() { |
||||
let input = b"UNUSED"; |
||||
let mut output = Vec::new(); |
||||
|
||||
let e = Engine::new("test_name", "test", &input[..], &mut output, vec!()); |
||||
assert_eq!(e.name, "test_name"); |
||||
assert_eq!(e.author, "test"); |
||||
} |
||||
|
||||
#[test] |
||||
fn send_identification_data() { |
||||
let input = b"UNUSED"; |
||||
let mut output = Vec::new(); |
||||
|
||||
// We need to scope this so that the mutable borrow ends and we can test it safely
|
||||
{ |
||||
let mut e = Engine::new("test_name", "test", &input[..], &mut output, vec!()); |
||||
e.identify(); |
||||
} |
||||
assert_eq!(str::from_utf8(&output).unwrap_or("Unwrapping output failed in send_identification_data"), |
||||
"id name test_name\nid author test\n"); |
||||
} |
||||
|
||||
#[test] |
||||
fn send_readyok() { |
||||
let input = b"UNUSED"; |
||||
let mut output = Vec::new(); |
||||
{ |
||||
let mut e = Engine::new("test_name", "test", &input[..], &mut output, vec!()); |
||||
e.ready(); |
||||
} |
||||
|
||||
assert_eq!(str::from_utf8(&output).unwrap_or("Unwrapping output failed in send_readyok"), |
||||
"readyok\n"); |
||||
} |
||||
|
||||
#[test] |
||||
fn send_available_engine_options() { |
||||
let input = b"UNUSED"; |
||||
let mut output = Vec::new(); |
||||
|
||||
let o1 = EngineOption { |
||||
name: constants::HASH, |
||||
option_type: EngineOptionType::Spin, |
||||
option_data: [(EngineOptionDataType::DefaultVal, EngineOptionData::Int(1)), |
||||
(EngineOptionDataType::Min, EngineOptionData::Int(1)), |
||||
(EngineOptionDataType::Max, EngineOptionData::Int(128)) |
||||
].iter().cloned().collect(), |
||||
}; |
||||
|
||||
let o2 = EngineOption { |
||||
name: constants::NALIMOVPATH, |
||||
option_type: EngineOptionType::TypeString, |
||||
option_data: [(EngineOptionDataType::DefaultVal, EngineOptionData::Text(String::from(r"c:\"))), |
||||
].iter().cloned().collect(), |
||||
}; |
||||
|
||||
let o3 = EngineOption { |
||||
name: "Clear Hash", |
||||
option_type: EngineOptionType::Button, |
||||
option_data: [].iter().cloned().collect(), |
||||
}; |
||||
|
||||
{ |
||||
let mut e = Engine::new("test_name", "test", &input[..], &mut output, vec!(o1, o2, o3)); |
||||
e.send_available_options(); |
||||
} |
||||
assert_eq!(str::from_utf8(&output).unwrap_or("Unwrapping output failed in send_identification_data"), |
||||
"option name Hash type spin default 1 min 1 max 128\n\ |
||||
option name NalimovPath type string default c:\\\n\ |
||||
option name Clear Hash type button\n\ |
||||
uciok\n"); |
||||
} |
||||
|
@ -0,0 +1,51 @@
|
||||
extern crate uci; |
||||
|
||||
use uci::options::constants; |
||||
use uci::options::{ EngineOption, EngineOptionType, EngineOptionDataType, EngineOptionData }; |
||||
|
||||
#[test] |
||||
fn engine_option_equality() { |
||||
let name = constants::HASH; |
||||
let option_type = EngineOptionType::Spin; |
||||
let option_data1 = |
||||
[(EngineOptionDataType::DefaultVal, EngineOptionData::Int(1)), |
||||
(EngineOptionDataType::Min, EngineOptionData::Int(1)), |
||||
(EngineOptionDataType::Max, EngineOptionData::Int(128)) |
||||
].iter().cloned().collect(); |
||||
let option_data2 = |
||||
[(EngineOptionDataType::DefaultVal, EngineOptionData::Int(1)), |
||||
(EngineOptionDataType::Min, EngineOptionData::Int(1)), |
||||
(EngineOptionDataType::Max, EngineOptionData::Int(128)) |
||||
].iter().cloned().collect(); |
||||
|
||||
let o1 = EngineOption { |
||||
name: name, |
||||
option_type: option_type, |
||||
option_data: option_data1, |
||||
}; |
||||
|
||||
let o2 = EngineOption { |
||||
name: name, |
||||
option_type: option_type, |
||||
option_data: option_data2, |
||||
}; |
||||
|
||||
assert_eq!(o1, o2); |
||||
} |
||||
|
||||
#[test] |
||||
fn engine_option_string() { |
||||
let name = constants::HASH; |
||||
let option_type = EngineOptionType::Spin; |
||||
let option_data = |
||||
[(EngineOptionDataType::DefaultVal, EngineOptionData::Int(1)), |
||||
(EngineOptionDataType::Min, EngineOptionData::Int(1)), |
||||
(EngineOptionDataType::Max, EngineOptionData::Int(128)) |
||||
].iter().cloned().collect(); |
||||
|
||||
let o = EngineOption { name, option_type, option_data, }; |
||||
|
||||
let expected = "option name Hash type spin default 1 min 1 max 128\n"; |
||||
assert_eq!(o.to_string(), expected); |
||||
} |
||||
|
@ -0,0 +1,71 @@
|
||||
// TODO: Write parser tests before implementing the parser.
|
||||
// Start with setoption and branch out from there. The commands are finite so you should be able
|
||||
// to write a test for each one. Take the time to do this right so that the rest of this project is
|
||||
// a cakewalk
|
||||
//
|
||||
// // Also remember to fix the constant thing noted in the lib.rs for uci.... lol
|
||||
//
|
||||
|
||||
extern crate uci; |
||||
|
||||
use uci::parser; |
||||
|
||||
#[test] |
||||
fn parse_setoption() { |
||||
let s = "setoption name Hash value 32\n"; |
||||
let expected = parser::TokenResult::new( |
||||
parser::Token::SETOPTION, |
||||
parser::Left(Some( |
||||
[ |
||||
("name", parser::CommandValue::Text(String::from("Hash"))), |
||||
("value", parser::CommandValue::Int(32)), |
||||
].iter().cloned().collect()) |
||||
)); |
||||
|
||||
let p = parser::Parser::new(); |
||||
|
||||
match p.parse(s) { |
||||
Ok(r) => { |
||||
assert_eq!(r, expected); |
||||
} |
||||
Err(_) => panic!("failed to parse setoption as expected") |
||||
} |
||||
} |
||||
|
||||
#[test] |
||||
fn parse_uci() { |
||||
let s = "uci\n"; |
||||
let expected = parser::TokenResult::new( |
||||
parser::Token::UCI, parser::Left(None); |
||||
|
||||
let p = parser::Parser::new(); |
||||
|
||||
match p.parse(s) { |
||||
Ok(r) => { |
||||
assert_eq!(r, expected); |
||||
} |
||||
Err(_) => panic!("failed to parse uci as expected") |
||||
} |
||||
} |
||||
|
||||
#[test] |
||||
fn parse_debug() { |
||||
let s = "debug on\n"; |
||||
let expected = parser::TokenResult::new( |
||||
parser::Token::DEBUG, parser::Right("on") |
||||
); |
||||
|
||||
let p = parser::Parser::new(); |
||||
|
||||
match p.parse(s) { |
||||
Ok(r) => { |
||||
assert_eq!(r, expected); |
||||
} |
||||
Err(_) => panic!("failed to parse uci as expected") |
||||
} |
||||
} |
||||
|
||||
#[test] |
||||
fn bad_parse() { |
||||
assert_eq!(true, false); |
||||
} |
Loading…
Reference in new issue