Compare commits
43 Commits
Author | SHA1 | Date |
---|---|---|
|
4c12c857ff | 8 years ago |
|
4f8a5ef694 | 8 years ago |
|
fef0bc8e44 | 8 years ago |
|
0ce59d6169 | 8 years ago |
|
e049b59eb3 | 8 years ago |
|
8ca26fe648 | 8 years ago |
|
9b9e466cdb | 8 years ago |
|
a73dae7568 | 8 years ago |
|
d8577ee142 | 8 years ago |
|
428da563dd | 8 years ago |
|
a91331d10f | 8 years ago |
|
587fa6a4a6 | 8 years ago |
|
a1dae653fa | 8 years ago |
|
02638cd05a | 8 years ago |
|
5c9d13de42 | 8 years ago |
|
47438cf5ea | 8 years ago |
|
c176d390bd | 8 years ago |
|
219032db9d | 8 years ago |
|
bf87428887 | 8 years ago |
|
7c5c3338cd | 8 years ago |
|
54cab71bd7 | 8 years ago |
|
cb4b8bbced | 8 years ago |
|
be2ca9fc75 | 8 years ago |
|
72433e7c73 | 8 years ago |
|
039d1601ce | 8 years ago |
|
74822a9f2d | 8 years ago |
|
215af79b1e | 8 years ago |
|
9c24d8c7c3 | 8 years ago |
|
759e5bbe7e | 8 years ago |
|
64f4a7a89d | 8 years ago |
|
969aed2734 | 8 years ago |
|
3eca0caaa4 | 8 years ago |
|
bee54e617c | 8 years ago |
|
4ed87916a5 | 8 years ago |
|
3eb729653a | 8 years ago |
|
3e2fbfa216 | 8 years ago |
|
2fcacc815d | 8 years ago |
|
bae58bb10f | 8 years ago |
|
42f366b2aa | 8 years ago |
|
78e23831e0 | 8 years ago |
|
a1d7ca7e64 | 8 years ago |
|
1875853925 | 8 years ago |
|
d462ce8d11 | 8 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 |
# UCI |
||||||
An implementation of the Universal Chess Interface in Rust |
|
||||||
|
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