Skip to content

Commit

Permalink
feat: USI protocol helpers
Browse files Browse the repository at this point in the history
  • Loading branch information
Takashi Nozawa committed Feb 19, 2017
1 parent d89f90c commit 7d17799
Show file tree
Hide file tree
Showing 9 changed files with 793 additions and 10 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ readme = "README.md"
license = "MIT"

[dependencies]
itertools = "0.5.9"
12 changes: 8 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//! A library for implementing Shogi application.
//!
//!
//! `shogi` provides a various types and implementations for representing concepts and rules in Shogi.
//! Most types can be created programatically while they can also be deserialized from / serialized to SFEN format.
//! See http://www.geocities.jp/shogidokoro/usi.html for more detail about SFEN format.
Expand All @@ -8,16 +8,16 @@
//!
//! ```
//! use shogi::{Color, Move, Position, Square};
//!
//!
//! let mut pos = Position::new();
//!
//! // Position can be set from the SFEN formatted string.
//! pos.set_sfen("lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL b - 1").unwrap();
//!
//!
//! // You can programatically create a Move instance.
//! let m = Move::Normal{from: Square::new(2, 6), to: Square::new(2, 5), promote: false};
//! pos.make_move(&m).unwrap();
//!
//!
//! // Move can be created from the SFEN formatted string as well.
//! let m = Move::from_sfen("7c7d").unwrap();
//! pos.make_move(&m).unwrap();
Expand All @@ -26,6 +26,9 @@
//! assert_eq!("lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL b - 1 moves 7g7f 7c7d", pos.to_sfen());
//! ```

#[macro_use()]
extern crate itertools;

mod color;
mod error;
mod square;
Expand All @@ -34,6 +37,7 @@ mod piece;
mod moves;
mod hand;
mod position;
pub mod usi;

pub use self::color::*;
pub use self::error::*;
Expand Down
11 changes: 5 additions & 6 deletions src/position.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use std::collections::HashSet;
use std::fmt;
use itertools::Itertools;

use {Color, Hand, Move, Piece, PieceType, Square, MoveError, SfenError};

/// MoveRecord stores information necessary to undo the move.
Expand Down Expand Up @@ -53,10 +55,10 @@ impl PartialEq<Move> for MoveRecord {
///
/// let mut pos = Position::new();
/// pos.set_sfen("lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL b - 1").unwrap();
///
///
/// let m = Move::Normal{from: Square::new(2, 6), to: Square::new(2, 5), promote: false};
/// pos.make_move(&m).unwrap();
///
///
/// assert_eq!("lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL b - 1 moves 7g7f", pos.to_sfen());
/// ```
#[derive(Debug)]
Expand Down Expand Up @@ -163,7 +165,7 @@ impl Position {
fn log_position(&mut self) {
// TODO: SFEN string is used to represent a state of position, but any transformation which uniquely distinguish positions can be used here.
// Consider light-weight option if generating SFEN string for each move is time-consuming.
let sfen = self.generate_sfen().split(" ").take(3).collect::<Vec<_>>().join(" ");
let sfen = self.generate_sfen().split(" ").take(3).join(" ");
let in_check = self.in_check(self.side_to_move());

let continuous_check = if in_check {
Expand Down Expand Up @@ -729,7 +731,6 @@ impl Position {

s
})
.collect::<Vec<_>>()
.join("/");

let color = if self.side_to_move == Color::Black {
Expand Down Expand Up @@ -758,10 +759,8 @@ impl Position {
format!("{}{}", n, pc)
}
})
.collect::<Vec<_>>()
.join("")
})
.collect::<Vec<_>>()
.join("");

if hand.is_empty() {
Expand Down
182 changes: 182 additions & 0 deletions src/usi/engine/command.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
use std::time::Duration;

use Move;
use usi::Error;
use super::parser::EngineCommandParser;

/// Represents a kind of "option" command value.
#[derive(Debug)]
pub enum OptionKind {
Check { default: Option<bool> },
Spin {
default: Option<i32>,
min: Option<i32>,
max: Option<i32>,
},
Combo {
default: Option<String>,
vars: Vec<String>,
},
Button { default: Option<String> },
String { default: Option<String> },
Filename { default: Option<String> },
}

/// Represents parameters of "option" command.
#[derive(Debug)]
pub struct OptionParams {
pub name: String,
pub value: OptionKind,
}

/// Represents a kind of "score" parameter value in "info" command.
#[derive(Debug)]
pub enum ScoreKind {
CpExact,
CpLowerbound,
CpUpperbound,
MateExact,
MateSignOnly,
MateLowerbound,
MateUpperbound,
}

/// Represents parameters of "info" command.
#[derive(Debug)]
pub enum InfoParams {
CurrMove(String),
Depth(i32, Option<i32>),
HashFull(i32),
MultiPv(i32),
Nodes(i32),
Nps(i32),
Pv(Vec<String>),
Score(i32, ScoreKind),
Text(String),
Time(Duration),
}

/// Represents parameters of "checkmate" command.
#[derive(Debug)]
pub enum CheckmateParams {
Mate(Vec<Move>),
NoMate,
NotImplemented,
Timeout,
}

/// Represents parameters of "bestmove" command.
#[derive(Debug)]
pub enum BestMoveParams {
MakeMove(Move, Option<Move>),
Resign,
Win,
}

/// Represents parameters of "id" command.
#[derive(Debug)]
pub enum IdParams {
Name(String),
Author(String),
}

/// Represents a USI command sent from the engine.
///
/// # Examples
///
/// ```
/// use shogi::Move;
/// use shogi::usi::{EngineCommand, BestMoveParams};
///
/// let cmd = EngineCommand::parse("bestmove 7g7f ponder 8c8d").unwrap();
/// match cmd {
/// EngineCommand::BestMove(BestMoveParams::MakeMove(ref m, Some(ref pm))) => {
/// assert_eq!(Move::from_sfen("7g7f").unwrap(), *m);
/// assert_eq!(Move::from_sfen("8c8d").unwrap(), *pm);
/// },
/// _ => unreachable!(),
/// }
/// ```
#[derive(Debug)]
pub enum EngineCommand {
Id(IdParams),
BestMove(BestMoveParams),
Checkmate(CheckmateParams),
Info(Vec<InfoParams>),
Option(OptionParams),
ReadyOk,
UsiOk,
Unknown,
}

impl EngineCommand {
/// Parses a USI command string into a new instance of `EngineCommand`.
pub fn parse(cmd: &str) -> Result<EngineCommand, Error> {
let parser = EngineCommandParser::new(cmd);
parser.parse()
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn parse() {
let ok_cases =
["id name Lesserkai",
"id author Program Writer",
"bestmove 7g7f",
"bestmove 8h2b+ ponder 3a2b",
"bestmove resign",
"bestmove win",
"checkmate nomate",
"checkmate notimplemented",
"checkmate timeout",
"checkmate G*8f 9f9g 8f8g 9g9h 8g8h",
"info time 1141 depth 3 seldepth 5 nodes 135125 score cp -1521 pv 3a3b L*4h 4c4d",
"info nodes 120000 nps 116391 multipv 1 currmove 1 hashfull 104",
"info string 7g7f (70%)",
"info score cp 100 lowerbound",
"info score cp 100 upperbound",
"info score mate +",
"info score mate -",
"info score mate 5",
"info score mate -5",
"info score mate 5 lowerbound",
"info score mate 5 upperbound",
"option name UseBook type check default true",
"option name Selectivity type spin default 2 min 0 max 4",
"option name Style type combo default Normal var Solid var Normal var Risky",
"option name ResetLearning type button",
"option name BookFile type string default public.bin",
"option name LearningFile type filename default <empty>",
"readyok",
"usiok",
"unknown command"];

let ng_cases = ["",
"bestmove foo",
"bestmove foo ponder bar",
"bestmove 7g7f ponder foo",
"bestmove foo ponder 7g7f",
"checkmate foo",
"checkmate",
"id foo bar",
"info depth foo",
"info depth 1 seldepth foo",
"info multipv foo",
"info score foo 1",
"info foo bar",
"option foo bar baz",
"option name foo bar"];

for (i, c) in ok_cases.iter().enumerate() {
assert!(EngineCommand::parse(c).is_ok(), "failed at #{}", i);
}

for (i, c) in ng_cases.iter().enumerate() {
assert!(EngineCommand::parse(c).is_err(), "failed at #{}", i);
}
}
}
4 changes: 4 additions & 0 deletions src/usi/engine/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
mod command;
mod parser;

pub use self::command::*;
Loading

0 comments on commit 7d17799

Please sign in to comment.