├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE.md ├── README.md ├── game ├── Cargo.toml └── src │ ├── castle.rs │ ├── color.rs │ ├── lib.rs │ ├── moves.rs │ ├── piece.rs │ ├── pos │ ├── bitboard.rs │ ├── board.rs │ ├── eval.rs │ ├── fen.rs │ ├── hash.rs │ ├── legal.rs │ ├── make_move.rs │ ├── mate.rs │ ├── mod.rs │ └── psudo_legal.rs │ └── square.rs ├── search ├── Cargo.toml └── src │ ├── depth_limited_search.rs │ ├── iterated_deepening.rs │ ├── lib.rs │ ├── negamax.rs │ ├── start.rs │ ├── transposition_table.rs │ └── types.rs ├── src └── main.rs ├── timer ├── Cargo.toml └── src │ ├── control.rs │ └── lib.rs └── uci ├── Cargo.toml └── src ├── input.rs ├── lib.rs ├── output.rs ├── parse.rs ├── process ├── go_param.rs ├── mod.rs ├── pos.rs └── time_start.rs ├── start.rs ├── state ├── mod.rs └── mode.rs └── types ├── mod.rs ├── options.rs └── param.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | */target 3 | */Cargo.lock 4 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | [root] 2 | name = "binary_turk" 3 | version = "0.0.1" 4 | dependencies = [ 5 | "env_logger 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 6 | "uci 0.0.1", 7 | ] 8 | 9 | [[package]] 10 | name = "aho-corasick" 11 | version = "0.5.3" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | dependencies = [ 14 | "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", 15 | ] 16 | 17 | [[package]] 18 | name = "env_logger" 19 | version = "0.3.5" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | dependencies = [ 22 | "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 23 | "regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)", 24 | ] 25 | 26 | [[package]] 27 | name = "game" 28 | version = "0.0.1" 29 | dependencies = [ 30 | "lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 31 | "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 32 | "rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", 33 | ] 34 | 35 | [[package]] 36 | name = "kernel32-sys" 37 | version = "0.2.2" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | dependencies = [ 40 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 41 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 42 | ] 43 | 44 | [[package]] 45 | name = "lazy_static" 46 | version = "0.2.2" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | 49 | [[package]] 50 | name = "libc" 51 | version = "0.2.18" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | 54 | [[package]] 55 | name = "log" 56 | version = "0.3.6" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | 59 | [[package]] 60 | name = "memchr" 61 | version = "0.1.11" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | dependencies = [ 64 | "libc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", 65 | ] 66 | 67 | [[package]] 68 | name = "rand" 69 | version = "0.3.15" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | dependencies = [ 72 | "libc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", 73 | ] 74 | 75 | [[package]] 76 | name = "regex" 77 | version = "0.1.80" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | dependencies = [ 80 | "aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", 81 | "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", 82 | "regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", 83 | "thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", 84 | "utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 85 | ] 86 | 87 | [[package]] 88 | name = "regex-syntax" 89 | version = "0.3.9" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | 92 | [[package]] 93 | name = "search" 94 | version = "0.0.1" 95 | dependencies = [ 96 | "game 0.0.1", 97 | "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 98 | ] 99 | 100 | [[package]] 101 | name = "thread-id" 102 | version = "2.0.0" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | dependencies = [ 105 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 106 | "libc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", 107 | ] 108 | 109 | [[package]] 110 | name = "thread_local" 111 | version = "0.2.7" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | dependencies = [ 114 | "thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 115 | ] 116 | 117 | [[package]] 118 | name = "time" 119 | version = "0.1.35" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | dependencies = [ 122 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 123 | "libc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", 124 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 125 | ] 126 | 127 | [[package]] 128 | name = "timer" 129 | version = "0.0.1" 130 | dependencies = [ 131 | "game 0.0.1", 132 | ] 133 | 134 | [[package]] 135 | name = "uci" 136 | version = "0.0.1" 137 | dependencies = [ 138 | "game 0.0.1", 139 | "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 140 | "search 0.0.1", 141 | "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", 142 | "timer 0.0.1", 143 | ] 144 | 145 | [[package]] 146 | name = "utf8-ranges" 147 | version = "0.1.3" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | 150 | [[package]] 151 | name = "winapi" 152 | version = "0.2.8" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | 155 | [[package]] 156 | name = "winapi-build" 157 | version = "0.1.1" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | 160 | [metadata] 161 | "checksum aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ca972c2ea5f742bfce5687b9aef75506a764f61d37f8f649047846a9686ddb66" 162 | "checksum env_logger 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "15abd780e45b3ea4f76b4e9a26ff4843258dd8a3eed2775a0e7368c2e7936c2f" 163 | "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 164 | "checksum lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6abe0ee2e758cd6bc8a2cd56726359007748fbf4128da998b65d0b70f881e19b" 165 | "checksum libc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "a51822fc847e7a8101514d1d44e354ba2ffa7d4c194dcab48870740e327cac70" 166 | "checksum log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ab83497bf8bf4ed2a74259c1c802351fcd67a65baa86394b6ba73c36f4838054" 167 | "checksum memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20" 168 | "checksum rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "022e0636ec2519ddae48154b028864bdce4eaf7d35226ab8e65c611be97b189d" 169 | "checksum regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)" = "4fd4ace6a8cf7860714a2c2280d6c1f7e6a413486c13298bbc86fd3da019402f" 170 | "checksum regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "f9ec002c35e86791825ed294b50008eea9ddfc8def4420124fbc6b08db834957" 171 | "checksum thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03" 172 | "checksum thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "8576dbbfcaef9641452d5cf0df9b0e7eeab7694956dd33bb61515fb8f18cfdd5" 173 | "checksum time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "3c7ec6d62a20df54e07ab3b78b9a3932972f4b7981de295563686849eb3989af" 174 | "checksum utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f" 175 | "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 176 | "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 177 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | 3 | name = "binary_turk" 4 | version = "0.0.1" 5 | authors = ["Theemathas Chirananthavat "] 6 | 7 | [dependencies] 8 | env_logger = "*" 9 | 10 | [dependencies.uci] 11 | path = "uci" 12 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Theemathas Chirananthavat 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Binary Turk 2 | 3 | A UCI (Universal Chess Interface) chess engine written in the Rust programming 4 | language 5 | 6 | It is not perfect, but it works and makes pretty reasonable moves. 7 | 8 | Like all UCI engines, this program needs a GUI, such as Xboard, Winboard, 9 | Knights, Eboard, or Fritz. However, this program is currently tested only on 10 | xboard (which is an old program), since I cannot get the other programs to work 11 | on my computer. 12 | 13 | This program is licensed under the MIT license. 14 | -------------------------------------------------------------------------------- /game/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | 3 | name = "game" 4 | version = "0.0.1" 5 | authors = ["Theemathas Chirananthavat "] 6 | 7 | [dependencies] 8 | log = "*" 9 | lazy_static = "*" 10 | rand = "*" 11 | -------------------------------------------------------------------------------- /game/src/castle.rs: -------------------------------------------------------------------------------- 1 | use color::{Color, White, Black}; 2 | use square::{Square, File, Rank}; 3 | 4 | pub use self::Side::{Kingside, Queenside}; 5 | 6 | #[derive(PartialEq, Eq, Copy, Clone, Debug)] 7 | pub enum Side { 8 | Kingside, 9 | Queenside, 10 | } 11 | impl Side { 12 | pub fn require_empty_squares(self, c: Color) -> Vec { 13 | match (c, self) { 14 | (White, Kingside) => vec![Square::new(File(5), Rank(0)), 15 | Square::new(File(6), Rank(0))], 16 | (White, Queenside) => vec![Square::new(File(3), Rank(0)), 17 | Square::new(File(2), Rank(0)), 18 | Square::new(File(1), Rank(0))], 19 | (Black, Kingside) => vec![Square::new(File(5), Rank(7)), 20 | Square::new(File(6), Rank(7))], 21 | (Black, Queenside) => vec![Square::new(File(3), Rank(7)), 22 | Square::new(File(2), Rank(7)), 23 | Square::new(File(1), Rank(7))], 24 | } 25 | } 26 | 27 | pub fn require_no_attack(self, c: Color) -> Vec { 28 | match (c, self) { 29 | (White, Kingside) => vec![Square::new(File(4), Rank(0)), 30 | Square::new(File(5), Rank(0)), 31 | Square::new(File(6), Rank(0))], 32 | (White, Queenside) => vec![Square::new(File(4), Rank(0)), 33 | Square::new(File(3), Rank(0)), 34 | Square::new(File(2), Rank(0))], 35 | (Black, Kingside) => vec![Square::new(File(4), Rank(7)), 36 | Square::new(File(5), Rank(7)), 37 | Square::new(File(6), Rank(7))], 38 | (Black, Queenside) => vec![Square::new(File(4), Rank(7)), 39 | Square::new(File(3), Rank(7)), 40 | Square::new(File(2), Rank(7))], 41 | } 42 | } 43 | } 44 | 45 | #[derive(PartialEq, Eq, Copy, Clone, Debug)] 46 | pub struct CastlingData { 47 | w_kingside: bool, 48 | w_queenside: bool, 49 | b_kingside: bool, 50 | b_queenside: bool, 51 | } 52 | impl CastlingData { 53 | pub fn new() -> CastlingData { 54 | CastlingData { 55 | w_kingside: false, 56 | w_queenside: false, 57 | b_kingside: false, 58 | b_queenside: false, 59 | } 60 | } 61 | pub fn get(&self, side: Side, c: Color) -> bool { 62 | match (c, side) { 63 | (White, Kingside) => self.w_kingside, 64 | (White, Queenside) => self.w_queenside, 65 | (Black, Kingside) => self.b_kingside, 66 | (Black, Queenside) => self.b_queenside, 67 | } 68 | } 69 | pub fn set(&mut self, side: Side, c: Color, val: bool) { 70 | match (c, side) { 71 | (White, Kingside) => self.w_kingside = val, 72 | (White, Queenside) => self.w_queenside = val, 73 | (Black, Kingside) => self.b_kingside = val, 74 | (Black, Queenside) => self.b_queenside = val, 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /game/src/color.rs: -------------------------------------------------------------------------------- 1 | pub use self::Color::{White, Black}; 2 | 3 | #[derive(PartialEq, Eq, Copy, Clone, Debug)] 4 | pub enum Color { 5 | White, 6 | Black, 7 | } 8 | 9 | impl Color { 10 | pub fn invert(self) -> Color { 11 | match self { 12 | White => Black, 13 | Black => White, 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /game/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This module is for everything about the rules of chess. 2 | 3 | #[macro_use] 4 | extern crate lazy_static; 5 | 6 | #[macro_use] 7 | extern crate log; 8 | 9 | extern crate rand; 10 | 11 | pub use color::{Color, White, Black}; 12 | pub use piece::Piece; 13 | pub use piece::Type as PieceType; 14 | pub use piece::Type::*; 15 | pub use moves::{Move, FromTo}; 16 | pub use moves::{NumPlies, NumMoves}; 17 | 18 | pub use pos::Position; 19 | pub use pos::ExtraData as PosExtraData; 20 | pub use pos::{ScoreUnit, Score}; 21 | pub use pos::ZobristHash; 22 | 23 | mod color; 24 | mod piece; 25 | mod square; 26 | mod moves; 27 | mod castle; 28 | mod pos; 29 | -------------------------------------------------------------------------------- /game/src/moves.rs: -------------------------------------------------------------------------------- 1 | //! The types for moves and plies. 2 | 3 | use std::str::FromStr; 4 | use std::fmt; 5 | 6 | use piece::{self, Piece, Queen, Bishop, Knight, Rook, King, Pawn}; 7 | use square::{Square, File, ParseSquareError}; 8 | use castle::{Side, Kingside, Queenside}; 9 | use pos::{Position, at_in_pos, is_empty_at_in_pos}; 10 | 11 | #[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Debug)] 12 | pub struct NumPlies(pub u32); 13 | 14 | #[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Debug)] 15 | pub struct NumMoves(pub u32); 16 | 17 | #[derive(PartialEq, Eq, Clone, Debug)] 18 | pub struct Move { 19 | from: Square, 20 | to: Square, 21 | capture_normal: Option, 22 | castle: Option, 23 | is_en_passant: bool, 24 | promote: Option, 25 | is_pawn_double_move: bool, 26 | } 27 | impl Move { 28 | pub fn new(from: Square, to: Square) -> Move { 29 | Move { 30 | from: from, 31 | to: to, 32 | capture_normal: None, 33 | castle: None, 34 | is_en_passant: false, 35 | promote: None, 36 | is_pawn_double_move: false, 37 | } 38 | } 39 | 40 | pub fn from(&self) -> Square { self.from } 41 | pub fn to(&self) -> Square { self.to } 42 | 43 | pub fn capture_normal(&self) -> Option { self.capture_normal } 44 | pub fn set_capture_normal(&mut self, val: Option) { 45 | self.capture_normal = val; 46 | } 47 | 48 | pub fn castle(&self) -> Option { self.castle } 49 | pub fn set_castle(&mut self, val: Option) { 50 | self.castle = val; 51 | } 52 | 53 | pub fn is_en_passant(&self) -> bool { self.is_en_passant } 54 | pub fn set_en_passant(&mut self, val: bool) { 55 | self.is_en_passant = val; 56 | } 57 | 58 | pub fn promote(&self) -> Option { self.promote } 59 | pub fn set_promote(&mut self, val: Option) { 60 | self.promote = val; 61 | } 62 | 63 | pub fn is_pawn_double_move(&self) -> bool { self.is_pawn_double_move } 64 | pub fn set_pawn_double_move(&mut self, val: bool) { 65 | self.is_pawn_double_move = val; 66 | } 67 | 68 | pub fn is_noisy(&self) -> bool { 69 | self.capture_normal().is_some() || self.is_en_passant() || self.promote().is_some() 70 | } 71 | pub fn is_quiet(&self) -> bool { 72 | !self.is_noisy() 73 | } 74 | } 75 | impl fmt::Display for Move { 76 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 77 | try!(write!(f, "{}{}", self.from, self.to)); 78 | if let Some(val) = self.promote() { 79 | try!(write!(f, "{}", match val { 80 | Queen => 'q', 81 | Bishop => 'b', 82 | Knight => 'n', 83 | Rook => 'r', 84 | _ => return Err(fmt::Error), 85 | })) 86 | } 87 | Ok(()) 88 | } 89 | } 90 | 91 | pub struct ParseFromToError(()); 92 | impl From for ParseFromToError { 93 | fn from(_: ParseSquareError) -> Self { ParseFromToError(()) } 94 | } 95 | 96 | #[derive(PartialEq, Eq, Copy, Clone, Debug)] 97 | pub struct FromTo { 98 | from: Square, 99 | to: Square, 100 | promote: Option, 101 | } 102 | impl FromTo { 103 | pub fn new(from: Square, to: Square) -> FromTo { 104 | FromTo { from: from, to: to, promote: None } 105 | } 106 | pub fn to_move_with_pos(&self, pos: &Position) -> Move { 107 | let mut ans = Move::new(self.from, self.to); 108 | ans.set_promote(self.promote); 109 | if !is_empty_at_in_pos(pos, self.to) { 110 | ans.set_capture_normal(at_in_pos(pos, self.to)); 111 | } 112 | match at_in_pos(pos, self.from).map(|x| x.piece_type()) { 113 | Some(King) => { 114 | match (self.from.file(), self.to.file()) { 115 | (File(4), File(6)) => ans.set_castle(Some(Kingside)), 116 | (File(4), File(2)) => ans.set_castle(Some(Queenside)), 117 | _ => {}, 118 | } 119 | }, 120 | Some(Pawn) => { 121 | if self.from.file() != self.to.file() && ans.capture_normal().is_none() { 122 | ans.set_en_passant(true); 123 | } else if ((self.from.rank().0) - (self.to.rank().0)).abs() != 1 { 124 | ans.set_pawn_double_move(true); 125 | } 126 | }, 127 | _ => {}, 128 | } 129 | debug!("Converted {:?} into {:?}", *self, ans); 130 | ans 131 | } 132 | } 133 | impl FromStr for FromTo { 134 | type Err = ParseFromToError; 135 | fn from_str(s: &str) -> Result { 136 | if s.len() != 4 && s.len() != 5 { return Err(ParseFromToError(())); } 137 | let from: Square = try!(FromStr::from_str(&s[0..2])); 138 | let to : Square = try!(FromStr::from_str(&s[2..4])); 139 | let mut ans = FromTo::new(from, to); 140 | if s.len() == 5 { 141 | ans.promote = Some( match s.as_bytes()[4] { 142 | b'q' => Queen, 143 | b'b' => Bishop, 144 | b'n' => Knight, 145 | b'r' => Rook, 146 | _ => return Err(ParseFromToError(())), 147 | }); 148 | } 149 | Ok(ans) 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /game/src/piece.rs: -------------------------------------------------------------------------------- 1 | //! The types and some utility functions related to pieces. 2 | 3 | use color::{Color, White, Black}; 4 | 5 | pub use self::Type::{Pawn, King, Queen, Bishop, Knight, Rook}; 6 | 7 | use self::Piece::*; 8 | 9 | #[derive(PartialEq, Eq, Copy, Clone, Debug)] 10 | pub enum Piece { 11 | WP, 12 | WK, 13 | WQ, 14 | WB, 15 | WN, 16 | WR, 17 | 18 | BP, 19 | BK, 20 | BQ, 21 | BB, 22 | BN, 23 | BR, 24 | } 25 | 26 | pub static ALL: [Piece; 12] = [WP, WK, WQ, WB, WN, WR, 27 | BP, BK, BQ, BB, BN, BR]; 28 | 29 | #[derive(PartialEq, Eq, Copy, Clone, Debug)] 30 | pub enum Type { 31 | Pawn, 32 | King, 33 | Queen, 34 | Bishop, 35 | Knight, 36 | Rook, 37 | } 38 | 39 | impl Piece { 40 | pub fn piece_type(&self) -> Type { 41 | match *self { 42 | WP | BP => Pawn, 43 | WK | BK => King, 44 | WQ | BQ => Queen, 45 | WB | BB => Bishop, 46 | WN | BN => Knight, 47 | WR | BR => Rook, 48 | } 49 | } 50 | pub fn color(&self) -> Color { 51 | match *self { 52 | WP | WK | WQ | WB | WN | WR => White, 53 | BP | BK | BQ | BB | BN | BR => Black, 54 | } 55 | } 56 | pub fn new(c : Color, t : Type) -> Piece { 57 | match (c, t) { 58 | (White, Pawn) => WP, 59 | (White, King) => WK, 60 | (White, Queen) => WQ, 61 | (White, Bishop) => WB, 62 | (White, Knight) => WN, 63 | (White, Rook) => WR, 64 | (Black, Pawn) => BP, 65 | (Black, King) => BK, 66 | (Black, Queen) => BQ, 67 | (Black, Bishop) => BB, 68 | (Black, Knight) => BN, 69 | (Black, Rook) => BR, 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /game/src/pos/bitboard.rs: -------------------------------------------------------------------------------- 1 | //! Implements a bitboard for a single piece. 2 | 3 | use std::ops::Not; 4 | 5 | use square::Square; 6 | 7 | #[derive(PartialEq, Eq, Copy, Clone, Debug)] 8 | pub struct BitBoard(u64); 9 | impl BitBoard { 10 | pub fn new() -> BitBoard { BitBoard(0_u64) } 11 | pub fn new_full() -> BitBoard { BitBoard(!0_u64) } 12 | 13 | pub fn at(self, s: Square) -> bool { 14 | self.0 & (1_u64 << s.to_id()) != 0 15 | } 16 | pub fn set_at(&mut self, s: Square) { 17 | debug_assert!(!self.at(s)); 18 | self.0 |= 1_u64 << s.to_id(); 19 | } 20 | pub fn remove_at(&mut self, s: Square) { 21 | debug_assert!(self.at(s)); 22 | self.0 ^= 1_u64 << s.to_id(); 23 | } 24 | 25 | pub fn iter(self) -> Iter { 26 | Iter(self.0) 27 | } 28 | 29 | pub fn intersect(self, other: BitBoard) -> BitBoard { 30 | BitBoard(self.0 & other.0) 31 | } 32 | } 33 | impl Not for BitBoard { 34 | type Output = BitBoard; 35 | fn not(self) -> BitBoard { BitBoard(!(self.0)) } 36 | } 37 | 38 | #[derive(Copy, Clone, Debug)] 39 | pub struct Iter(u64); 40 | impl Iterator for Iter { 41 | type Item = Square; 42 | fn next(&mut self) -> Option { 43 | if self.0 == 0 { 44 | None 45 | } else { 46 | let res = self.0.trailing_zeros(); 47 | self.0 &= !(1 << res); 48 | Some(Square::from_id(res as i32)) 49 | } 50 | } 51 | fn size_hint(&self) -> (usize, Option) { 52 | let res = self.0.count_ones() as usize; 53 | (res, Some(res)) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /game/src/pos/board.rs: -------------------------------------------------------------------------------- 1 | //! implements the board representation 2 | 3 | use color::{Color, White, Black}; 4 | use piece::{self, Piece, King}; 5 | use square::Square; 6 | 7 | use super::bitboard::BitBoard; 8 | 9 | #[derive(PartialEq, Eq, Clone, Debug)] 10 | pub struct Board { 11 | data: [BitBoard; 12], 12 | white_data: BitBoard, 13 | black_data: BitBoard, 14 | empty_data: BitBoard, 15 | } 16 | 17 | impl Board { 18 | pub fn new() -> Board { 19 | Board { 20 | data: [BitBoard::new(); 12], 21 | white_data: BitBoard::new(), 22 | black_data: BitBoard::new(), 23 | empty_data: BitBoard::new_full(), 24 | } 25 | } 26 | 27 | fn piece_data(&self, p: Piece) -> BitBoard { 28 | self.data[p as usize] 29 | } 30 | fn piece_data_mut(&mut self, p: Piece) -> &mut BitBoard { 31 | &mut self.data[p as usize] 32 | } 33 | 34 | pub fn empty_data(&self) -> BitBoard { 35 | self.empty_data 36 | } 37 | pub fn color_data(&self, c: Color) -> BitBoard { 38 | match c { 39 | White => self.white_data, 40 | Black => self.black_data, 41 | } 42 | } 43 | fn color_data_mut(&mut self, c: Color) -> &mut BitBoard { 44 | match c { 45 | White => &mut self.white_data, 46 | Black => &mut self.black_data, 47 | } 48 | } 49 | 50 | pub fn at(&self, s: Square) -> Option { 51 | for x in piece::ALL.iter() { 52 | if self.is_piece_at(*x, s) { 53 | return Some(*x); 54 | } 55 | } 56 | None 57 | } 58 | pub fn is_piece_at(&self, p: Piece, s: Square) -> bool { self.piece_data(p).at(s) } 59 | pub fn is_empty_at(&self, s: Square) -> bool { self.empty_data.at(s) } 60 | pub fn is_color_at(&self, s: Square, c: Color) -> bool { self.color_data(c).at(s) } 61 | 62 | pub fn set_at(&mut self, s: Square, p: Piece) { 63 | debug_assert!(self.is_empty_at(s), "set_at(), s = {:?}, p = {:?}", s, p); 64 | self.piece_data_mut(p).set_at(s); 65 | self.color_data_mut(p.color()).set_at(s); 66 | self.empty_data.remove_at(s); 67 | } 68 | pub fn remove_at(&mut self, s: Square, p: Piece) { 69 | debug_assert!(self.is_piece_at(p, s), "remove_at(), s = {:?}, p = {:?}", s, p); 70 | self.piece_data_mut(p).remove_at(s); 71 | self.color_data_mut(p.color()).remove_at(s); 72 | self.empty_data.set_at(s); 73 | } 74 | 75 | pub fn king_square(&self, c: Color) -> Square { 76 | let curr_king = Piece::new(c, King); 77 | self.piece_data(curr_king).iter().next().unwrap() 78 | } 79 | 80 | pub fn iter<'a>(&'a self) -> Iter<'a> { 81 | Iter(Box::new(piece::ALL.iter().cloned().flat_map(move |p| { 82 | self.piece_data(p).iter().map(move |s: Square| (p, s)) 83 | }))) 84 | } 85 | } 86 | 87 | pub struct Iter<'a>(Box + 'a>); 88 | impl<'a> Iterator for Iter<'a> { 89 | type Item = (Piece, Square); 90 | fn next(&mut self) -> Option<(Piece, Square)> { self.0.next() } 91 | fn size_hint(&self) -> (usize, Option) { self.0.size_hint() } 92 | } 93 | -------------------------------------------------------------------------------- /game/src/pos/eval.rs: -------------------------------------------------------------------------------- 1 | //! This module is for statically evaluating a position. 2 | 3 | use std::fmt; 4 | use std::ops::{Add, Sub, Neg, Mul}; 5 | use std::cmp::Ordering; 6 | 7 | use moves::NumMoves; 8 | use {Color, Piece, PieceType}; 9 | use {Pawn, King, Queen, Bishop, Knight, Rook}; 10 | use super::Position; 11 | 12 | #[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Debug)] 13 | pub struct ScoreUnit(pub i32); 14 | impl Add for ScoreUnit { 15 | type Output = Self; 16 | fn add(self, rhs: Self) -> Self { ScoreUnit(self.0 + rhs.0) } 17 | } 18 | impl Sub for ScoreUnit { 19 | type Output = Self; 20 | fn sub(self, rhs: Self) -> Self { ScoreUnit(self.0 - rhs.0) } 21 | } 22 | impl Neg for ScoreUnit { 23 | type Output = Self; 24 | fn neg(self) -> Self { ScoreUnit(-self.0) } 25 | } 26 | impl Mul for ScoreUnit { 27 | type Output = Self; 28 | fn mul(self, rhs: i32) -> Self { ScoreUnit(self.0 * rhs) } 29 | } 30 | 31 | /// An assessment of the position. 32 | #[derive(PartialEq, Eq, Copy, Clone, Debug)] 33 | pub enum Score { 34 | // Positive: advantage for side to move. 35 | // Negative: disadvantage for side to move. 36 | Value(ScoreUnit), 37 | // Side to move can checkmate in x moves. 38 | // WinIn(NumMoves(1)): can checkmate now. 39 | // WinIn(NumMoves(2)): can checkmate next move. 40 | WinIn(NumMoves), 41 | // Side to move will be checkmated in x moves. 42 | // WinIn(NumMoves(0)): already checkmated. 43 | // WinIn(NumMoves(1)): Will be immediately checkmated after any move. 44 | LoseIn(NumMoves), 45 | } 46 | impl Score { 47 | pub fn increment(self) -> Score { 48 | match self { 49 | Score::Value(val) => Score::Value(-val), 50 | Score::WinIn(val) => Score::LoseIn(val), 51 | Score::LoseIn(val) => Score::WinIn(NumMoves(val.0+1)), 52 | } 53 | } 54 | pub fn decrement(self) -> Score { 55 | match self { 56 | Score::Value(val) => Score::Value(-val), 57 | Score::WinIn(val) => Score::LoseIn(NumMoves( 58 | if val.0 == 0 { 0 } else { val.0 - 1 } )), 59 | Score::LoseIn(val) => Score::WinIn(val), 60 | } 61 | } 62 | } 63 | impl fmt::Display for Score { 64 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 65 | match *self { 66 | Score::Value(val) => write!(f, "cp {}", val.0), 67 | Score::WinIn(val) => write!(f, "mate {}", val.0 as i32), 68 | Score::LoseIn(val) => write!(f, "mate {}", (val.0 as i32) * -1), 69 | } 70 | } 71 | } 72 | impl Ord for Score { 73 | fn cmp(&self, other: &Score) -> Ordering { 74 | match *self { 75 | Score::WinIn(val1) => match *other { 76 | Score::WinIn(val2) => val2.cmp(&val1), 77 | _ => Ordering::Greater, 78 | }, 79 | Score::LoseIn(val1) => match *other { 80 | Score::LoseIn(val2) => val1.cmp(&val2), 81 | _ => Ordering::Less, 82 | }, 83 | Score::Value(val1) => match *other { 84 | Score::WinIn(_) => Ordering::Less, 85 | Score::LoseIn(_) => Ordering::Greater, 86 | Score::Value(val2) => val1.cmp(&val2), 87 | }, 88 | } 89 | } 90 | } 91 | impl PartialOrd for Score { 92 | fn partial_cmp(&self, other: &Self) -> Option { 93 | Some(self.cmp(other)) 94 | } 95 | } 96 | 97 | /// Evaluates the position without searching. 98 | pub fn eval(p: &mut Position) -> Score { 99 | if p.is_checkmated() { 100 | Score::LoseIn(NumMoves(0)) 101 | } else if p.is_stalemated() { 102 | Score::Value(ScoreUnit(0)) 103 | } else { 104 | let c = p.side_to_move(); 105 | // TODO change fold() to sum() when possible 106 | let piece_eval = p.piece_iter() 107 | .map( |(piece, _pos)| val_for_color(piece, c) ) 108 | .fold(ScoreUnit(0), |x, y| x+y); 109 | let our_mobility = p.psudo_legal_iter().count(); 110 | p.swap_side_to_move(); 111 | let his_mobility = p.psudo_legal_iter().count(); 112 | p.swap_side_to_move(); 113 | let mobility_diff = our_mobility as i32- his_mobility as i32; 114 | Score::Value(piece_eval + VALUE_PER_MOBILITY * (mobility_diff)) 115 | } 116 | } 117 | 118 | fn val_for_color(piece: Piece, c: Color) -> ScoreUnit { 119 | let val = val_of_piece_type(piece.piece_type()); 120 | if piece.color() == c { 121 | val 122 | } else { 123 | -val 124 | } 125 | } 126 | 127 | const VALUE_PER_MOBILITY: ScoreUnit = ScoreUnit(5); 128 | 129 | fn val_of_piece_type(piece_type: PieceType) -> ScoreUnit { 130 | ScoreUnit(match piece_type { 131 | King => 100000, 132 | Pawn => 100, 133 | Queen => 900, 134 | Bishop => 300, 135 | Knight => 300, 136 | Rook => 500, 137 | }) 138 | } 139 | -------------------------------------------------------------------------------- /game/src/pos/fen.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use moves::NumPlies; 4 | use square::{File, Rank, Square}; 5 | use castle::{Kingside, Queenside}; 6 | use piece::Piece::*; 7 | use color::Color::{White, Black}; 8 | 9 | use super::Position; 10 | 11 | #[derive(Debug)] 12 | pub struct ParsePosError(&'static str); 13 | 14 | pub fn start_pos() -> Position { 15 | fen_to_position("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1").unwrap() 16 | } 17 | 18 | pub fn fen_to_position(fen: &str) -> Result { 19 | let fields: Vec<&str> = fen.split(' ').collect(); 20 | if fields.len() < 6 { 21 | return Err(ParsePosError("Not enough fields in FEN input.")); 22 | }; 23 | let mut pos_str = "".to_string(); 24 | for ch in fields[0].chars() { 25 | match ch { 26 | '0' ... '9' => { 27 | let val = { 28 | match ch.to_digit(10) { 29 | Some(val) => val, 30 | None => return Err(ParsePosError("invalid character in fen")), 31 | } 32 | }; 33 | for _ in 0..val { 34 | pos_str.push('1') 35 | } 36 | } 37 | _ => pos_str.push(ch) 38 | } 39 | } 40 | let mut pos = Position::new(); 41 | let (mut rank, mut file) = (7, 0); 42 | let mut decode = HashMap::new(); 43 | decode.insert('P', WP); 44 | decode.insert('K', WK); 45 | decode.insert('Q', WQ); 46 | decode.insert('B', WB); 47 | decode.insert('N', WN); 48 | decode.insert('R', WR); 49 | decode.insert('p', BP); 50 | decode.insert('k', BK); 51 | decode.insert('q', BQ); 52 | decode.insert('b', BB); 53 | decode.insert('n', BN); 54 | decode.insert('r', BR); 55 | for ch in pos_str.chars() { 56 | if ch == '/' { 57 | rank = rank - 1; 58 | file = 0; 59 | } else if ch == '1' { 60 | file = file + 1; 61 | } else { 62 | match decode.get(&ch) { 63 | None => return Err(ParsePosError("Unexpected charactor found.")), 64 | Some(val) => { 65 | pos.set_at(Square::new(File(file), Rank(rank)), *val); 66 | file = file + 1; 67 | } 68 | } 69 | } 70 | } 71 | let side_to_move = fields[1].chars().next().unwrap(); 72 | match side_to_move { 73 | 'w' => pos.set_side_to_move(White), 74 | 'b' => pos.set_side_to_move(Black), 75 | _ => return Err(ParsePosError("Invalid side to move.")), 76 | }; 77 | let castle = fields[2]; 78 | for ch in castle.chars() { 79 | match ch { 80 | 'K' => pos.set_castle(Kingside , White, true), 81 | 'Q' => pos.set_castle(Queenside, White, true), 82 | 'k' => pos.set_castle(Kingside , Black, true), 83 | 'q' => pos.set_castle(Queenside, Black, true), 84 | _ => {} 85 | } 86 | } 87 | let en_passant_char = fields[3].chars().next().unwrap(); 88 | if en_passant_char != '-' { 89 | pos.set_en_passant(Some(File((en_passant_char as u8 - b'a') as i32))); 90 | } 91 | match fields[4].parse::() { 92 | Ok(val) => pos.set_ply_count(NumPlies(val)), 93 | Err(_) => return Err(ParsePosError("Invalid number of plies.")), 94 | } 95 | Ok(pos) 96 | } 97 | -------------------------------------------------------------------------------- /game/src/pos/hash.rs: -------------------------------------------------------------------------------- 1 | use std::ops::BitXor; 2 | 3 | use rand::{ChaChaRng, Rng, SeedableRng}; 4 | 5 | use piece::Piece; 6 | use square::{Square, File}; 7 | use color::Color; 8 | use castle::Side; 9 | 10 | #[derive(Copy, Clone, PartialEq, Eq, Debug)] 11 | pub struct ZobristHash(pub u64); 12 | impl BitXor for ZobristHash { 13 | type Output = Self; 14 | fn bitxor(self, other: Self) -> Self { 15 | ZobristHash(self.0 ^ other.0) 16 | } 17 | } 18 | 19 | lazy_static! { 20 | static ref RANDOM_VALUES: [ZobristHash; 781] = { 21 | let mut rng = ChaChaRng::from_seed(&[123, 456, 789]); 22 | let mut ans = [ZobristHash(0); 781]; 23 | for x in ans.iter_mut() { *x = ZobristHash(rng.gen()); } 24 | ans 25 | }; 26 | static ref PIECE: [[ZobristHash; 64]; 12] = { 27 | let mut ans = [[ZobristHash(0); 64]; 12]; 28 | for i in 0..12 { 29 | for j in 0..64 { 30 | ans[i][j] = RANDOM_VALUES[i * 64 + j]; 31 | } 32 | } 33 | ans 34 | }; 35 | static ref BLACK_MOVE: ZobristHash = RANDOM_VALUES[768]; 36 | static ref CASTLING: [ZobristHash; 4] = { 37 | let mut ans = [ZobristHash(0); 4]; 38 | for i in 0..4 { 39 | ans[i] = RANDOM_VALUES[i + 769]; 40 | } 41 | ans 42 | }; 43 | static ref EN_PASSANT: [ZobristHash; 8] = { 44 | let mut ans = [ZobristHash(0); 8]; 45 | for i in 0..8 { 46 | ans[i] = RANDOM_VALUES[i + 773]; 47 | } 48 | ans 49 | }; 50 | } 51 | 52 | pub fn piece_square(piece: Piece, square: Square) -> ZobristHash { 53 | PIECE[piece as usize][square.to_id() as usize] 54 | } 55 | pub fn side_to_move() -> ZobristHash { *BLACK_MOVE } 56 | pub fn castling(side: Side, color: Color) -> ZobristHash { 57 | CASTLING[color as usize * 2 + side as usize] 58 | } 59 | pub fn en_passant(file: File) -> ZobristHash { 60 | EN_PASSANT[file.0 as usize] 61 | } 62 | -------------------------------------------------------------------------------- /game/src/pos/legal.rs: -------------------------------------------------------------------------------- 1 | use std::iter; 2 | 3 | use square::Square; 4 | use moves::Move; 5 | 6 | use super::Position; 7 | 8 | pub struct Iter<'a>(iter::Chain, QuietIter<'a>>); 9 | impl<'a> Iterator for Iter<'a> { 10 | type Item = Move; 11 | fn next(&mut self) -> Option { self.0.next() } 12 | fn size_hint(&self) -> (usize, Option) { self.0.size_hint() } 13 | } 14 | 15 | pub struct NoisyIter<'a>(Box + 'a>); 16 | impl<'a> Iterator for NoisyIter<'a> { 17 | type Item = Move; 18 | fn next(&mut self) -> Option { self.0.next() } 19 | fn size_hint(&self) -> (usize, Option) { self.0.size_hint() } 20 | } 21 | 22 | pub struct QuietIter<'a>(Box + 'a>); 23 | impl<'a> Iterator for QuietIter<'a> { 24 | type Item = Move; 25 | fn next(&mut self) -> Option { self.0.next() } 26 | fn size_hint(&self) -> (usize, Option) { self.0.size_hint() } 27 | } 28 | 29 | pub fn iter<'a>(p: &'a Position) -> Iter<'a> { 30 | Iter(p.legal_noisy_iter().chain(p.legal_quiet_iter())) 31 | } 32 | 33 | pub fn noisy_iter<'a>(p: &'a Position) -> NoisyIter<'a> { 34 | let mut temp = p.clone(); 35 | NoisyIter(Box::new(p.psudo_legal_noisy_iter().filter(move |x| is_legal(&mut temp, x)))) 36 | } 37 | 38 | pub fn quiet_iter<'a>(p: &'a Position) -> QuietIter<'a> { 39 | let mut temp = p.clone(); 40 | QuietIter(Box::new(p.psudo_legal_quiet_iter().filter(move |x| is_legal(&mut temp, x)))) 41 | } 42 | 43 | fn is_legal(p: &mut Position, curr_move: &Move) -> bool { 44 | let c = p.side_to_move(); 45 | p.with_move(curr_move, |new_pos| { 46 | match curr_move.castle() { 47 | None => { 48 | !new_pos.can_take_king() 49 | }, 50 | Some(side) => { 51 | // Check for castling out of check, through check, and into check. 52 | let check_squares: Vec = side.require_no_attack(c); 53 | check_squares.iter().all( |val| !new_pos.can_move_to(*val) ) 54 | } 55 | } 56 | }) 57 | } 58 | -------------------------------------------------------------------------------- /game/src/pos/make_move.rs: -------------------------------------------------------------------------------- 1 | use color::{White, Black}; 2 | use piece::Piece::{self, WK, WR, BK, BR}; 3 | use piece::Pawn; 4 | use square::{Square, File, Rank}; 5 | use moves::{Move, NumPlies}; 6 | use castle::{Kingside, Queenside}; 7 | 8 | use super::{Position, ExtraData}; 9 | 10 | pub fn make_move(p: &mut Position, m: &Move) { 11 | 12 | debug!("make_move(): {:?}", *m); 13 | 14 | debug!("before: {:?}", *p); 15 | 16 | let from = m.from(); 17 | let to = m.to(); 18 | let curr_piece = p.at(from).unwrap(); 19 | let curr_color = p.side_to_move(); 20 | 21 | if m.capture_normal().is_some() || m.is_en_passant() || curr_piece.piece_type() == Pawn { 22 | p.set_ply_count(NumPlies(0)); 23 | } else { 24 | let NumPlies(temp) = p.ply_count(); 25 | p.set_ply_count(NumPlies(temp+1)); 26 | } 27 | 28 | if let Some(castle_side) = m.castle() { 29 | 30 | //no en passant 31 | p.set_en_passant(None); 32 | 33 | let (rook_from, rook_to) = match (curr_color, castle_side) { 34 | (White, Kingside ) => (Square::new(File(7), Rank(0)), 35 | Square::new(File(5), Rank(0))), 36 | (White, Queenside) => (Square::new(File(0), Rank(0)), 37 | Square::new(File(3), Rank(0))), 38 | (Black, Kingside ) => (Square::new(File(7), Rank(7)), 39 | Square::new(File(5), Rank(7))), 40 | (Black, Queenside) => (Square::new(File(0), Rank(7)), 41 | Square::new(File(3), Rank(7))), 42 | }; 43 | 44 | p.remove_at(from, curr_piece); 45 | p.set_at(to, curr_piece); 46 | let curr_rook = p.at(rook_from).unwrap(); 47 | p.remove_at(rook_from, curr_rook); 48 | p.set_at(rook_to, curr_rook); 49 | 50 | } else if m.is_en_passant() { 51 | 52 | //no en passant after this 53 | p.set_en_passant(None); 54 | 55 | let captured = Square::new(to.file(), from.rank()); 56 | let captured_piece = Piece::new(p.side_to_move().invert(), Pawn); 57 | 58 | p.remove_at(captured, captured_piece); 59 | p.remove_at(from, curr_piece); 60 | p.set_at(to, curr_piece); 61 | 62 | } else if let Some(promote_piece) = m.promote() { 63 | //no en passant 64 | p.set_en_passant(None); 65 | 66 | //change the board and promote 67 | if let Some(captured_piece) = m.capture_normal() { 68 | p.remove_at(to, captured_piece); 69 | } 70 | p.remove_at(from, curr_piece); 71 | p.set_at(to, Piece::new(curr_color, promote_piece)); 72 | } else { 73 | 74 | if m.is_pawn_double_move() { 75 | p.set_en_passant(Some(to.file())); 76 | } else { 77 | p.set_en_passant(None); 78 | } 79 | 80 | //change the board 81 | if let Some(captured_piece) = m.capture_normal() { 82 | p.remove_at(to, captured_piece); 83 | } 84 | p.remove_at(from, curr_piece); 85 | p.set_at(to, curr_piece); 86 | } 87 | 88 | //castling 89 | if p.at(Square::new(File(4), Rank(0))) != Some(WK) { 90 | p.set_castle(Queenside, White, false); 91 | p.set_castle(Kingside , White, false); 92 | } 93 | if p.at(Square::new(File(0), Rank(0))) != Some(WR) { 94 | p.set_castle(Queenside, White, false); 95 | } 96 | if p.at(Square::new(File(7), Rank(0))) != Some(WR) { 97 | p.set_castle(Kingside , White, false); 98 | } 99 | if p.at(Square::new(File(4), Rank(7))) != Some(BK) { 100 | p.set_castle(Queenside, Black, false); 101 | p.set_castle(Kingside , Black, false); 102 | } 103 | if p.at(Square::new(File(0), Rank(7))) != Some(BR) { 104 | p.set_castle(Queenside, Black, false); 105 | } 106 | if p.at(Square::new(File(7), Rank(7))) != Some(BR) { 107 | p.set_castle(Kingside , Black, false); 108 | } 109 | 110 | p.swap_side_to_move(); 111 | 112 | debug!("after : {:?}", *p); 113 | } 114 | 115 | pub fn unmake_move(p: &mut Position, m: &Move, extra_data: ExtraData) { 116 | 117 | debug!("unmake_move(): {:?}", *m); 118 | debug!("{:?}", extra_data); 119 | 120 | debug!("before: {:?}", *p); 121 | 122 | let from = m.from(); 123 | let to = m.to(); 124 | let curr_piece = p.at(to).unwrap(); 125 | let curr_color = p.side_to_move().invert(); 126 | 127 | if let Some(castle_side) = m.castle() { 128 | 129 | let (rook_from, rook_to) = match (curr_color, castle_side) { 130 | (White, Kingside ) => (Square::new(File(7), Rank(0)), 131 | Square::new(File(5), Rank(0))), 132 | (White, Queenside) => (Square::new(File(0), Rank(0)), 133 | Square::new(File(3), Rank(0))), 134 | (Black, Kingside ) => (Square::new(File(7), Rank(7)), 135 | Square::new(File(5), Rank(7))), 136 | (Black, Queenside) => (Square::new(File(0), Rank(7)), 137 | Square::new(File(3), Rank(7))), 138 | }; 139 | 140 | p.remove_at(to, curr_piece); 141 | p.set_at(from, curr_piece); 142 | let curr_rook = p.at(rook_to).unwrap(); 143 | p.remove_at(rook_to, curr_rook); 144 | p.set_at(rook_from, curr_rook); 145 | 146 | } else if m.is_en_passant() { 147 | 148 | let captured = Square::new(to.file(), from.rank()); 149 | let captured_piece = Piece::new(curr_color.invert(), Pawn); 150 | 151 | p.set_at(captured, captured_piece); 152 | p.remove_at(to, curr_piece); 153 | p.set_at(from, curr_piece); 154 | 155 | } else if let Some(promote_piece) = m.promote() { 156 | 157 | p.remove_at(to, Piece::new(curr_color, promote_piece)); 158 | p.set_at(from, Piece::new(curr_color, Pawn)); 159 | if let Some(captured_piece) = m.capture_normal() { 160 | p.set_at(to, captured_piece); 161 | } 162 | 163 | } else { 164 | 165 | p.remove_at(to, curr_piece); 166 | p.set_at(from, curr_piece); 167 | if let Some(captured_piece) = m.capture_normal() { 168 | p.set_at(to, captured_piece); 169 | } 170 | 171 | } 172 | 173 | p.set_extra_data(extra_data); 174 | p.swap_side_to_move(); 175 | 176 | debug!("after : {:?}", *p); 177 | 178 | } 179 | -------------------------------------------------------------------------------- /game/src/pos/mate.rs: -------------------------------------------------------------------------------- 1 | use super::Position; 2 | 3 | pub fn is_checkmated(p: &mut Position) -> bool { 4 | if p.has_legal_moves() { 5 | false 6 | } else { 7 | p.swap_side_to_move(); 8 | let ans = p.can_take_king(); 9 | p.swap_side_to_move(); 10 | ans 11 | } 12 | } 13 | 14 | pub fn is_stalemated(p: &mut Position) -> bool { 15 | if p.has_legal_moves() { 16 | false 17 | } else { 18 | p.swap_side_to_move(); 19 | let ans = !p.can_take_king(); 20 | p.swap_side_to_move(); 21 | ans 22 | } 23 | } 24 | 25 | pub fn has_legal_moves(p: &Position) -> bool { 26 | p.legal_iter().next().is_some() 27 | } 28 | -------------------------------------------------------------------------------- /game/src/pos/mod.rs: -------------------------------------------------------------------------------- 1 | //! Implements the game representation 2 | 3 | use std::str::FromStr; 4 | 5 | pub use self::eval::{Score, ScoreUnit}; 6 | pub use self::hash::ZobristHash; 7 | 8 | use super::piece::Piece; 9 | use super::color::Color; 10 | use super::square::{File, Square}; 11 | use super::castle::{CastlingData, Side}; 12 | use super::moves::{Move, NumPlies}; 13 | 14 | use self::board::Board; 15 | use self::fen::{fen_to_position, ParsePosError}; 16 | 17 | mod board; 18 | mod bitboard; 19 | mod legal; 20 | mod psudo_legal; 21 | mod make_move; 22 | mod mate; 23 | mod fen; 24 | mod eval; 25 | mod hash; 26 | 27 | /// Data required to unmake moves 28 | #[derive(PartialEq, Eq, Clone, Debug)] 29 | pub struct ExtraData { 30 | castling: CastlingData, 31 | en_passant: Option, 32 | ply_count: NumPlies, 33 | } 34 | 35 | /// A complete representation of a chess position. 36 | #[derive(PartialEq, Eq, Clone, Debug)] 37 | pub struct Position { 38 | data: Board, 39 | side_to_move: Color, 40 | extra_data: ExtraData, 41 | hash: ZobristHash, 42 | } 43 | impl Position { 44 | fn new() -> Position { 45 | Position { 46 | data: Board::new(), 47 | side_to_move: Color::White, 48 | extra_data: ExtraData { 49 | castling: CastlingData::new(), 50 | en_passant: None, 51 | ply_count: NumPlies(0), 52 | }, 53 | hash: ZobristHash(0), 54 | } 55 | } 56 | pub fn start() -> Self { 57 | fen::start_pos() 58 | } 59 | 60 | fn at(&self, s: Square) -> Option { 61 | self.data.at(s) 62 | } 63 | fn is_piece_at(&self, p: Piece, s: Square) -> bool { 64 | self.data.is_piece_at(p, s) 65 | } 66 | fn is_empty_at(&self, s: Square) -> bool { 67 | self.data.is_empty_at(s) 68 | } 69 | fn is_color_at(&self, s: Square, c: Color) -> bool { 70 | self.data.is_color_at(s, c) 71 | } 72 | 73 | fn set_at(&mut self, s: Square, p: Piece) { 74 | self.data.set_at(s, p); 75 | self.hash = self.hash ^ hash::piece_square(p, s); 76 | } 77 | fn remove_at(&mut self, s: Square, p: Piece) { 78 | self.data.remove_at(s, p); 79 | self.hash = self.hash ^ hash::piece_square(p, s); 80 | } 81 | 82 | fn king_square(&self, c: Color) -> Square { 83 | self.data.king_square(c) 84 | } 85 | fn piece_iter(&self) -> board::Iter { 86 | self.data.iter() 87 | } 88 | 89 | pub fn side_to_move(&self) -> Color { 90 | self.side_to_move 91 | } 92 | fn set_side_to_move(&mut self, c: Color) { 93 | if self.side_to_move != c { 94 | self.side_to_move = c; 95 | self.hash = self.hash ^ hash::side_to_move(); 96 | } 97 | } 98 | fn swap_side_to_move(&mut self) { 99 | let c = self.side_to_move.invert(); 100 | self.set_side_to_move(c); 101 | } 102 | 103 | fn can_castle(&self, side: Side, c: Color) -> bool { 104 | self.extra_data.castling.get(side, c) 105 | } 106 | // Does not check for castling out of check, through check, or into check. 107 | fn can_castle_now(&self, side: Side, c: Color) -> bool { 108 | self.can_castle(side, c) && 109 | side.require_empty_squares(c).iter().all( |x| self.is_empty_at(*x) ) 110 | } 111 | fn set_castle(&mut self, side:Side, c:Color, val: bool) { 112 | let old_val = self.extra_data.castling.get(side, c); 113 | if val != old_val { 114 | self.extra_data.castling.set(side, c, val); 115 | self.hash = self.hash ^ hash::castling(side, c); 116 | } 117 | } 118 | 119 | fn en_passant(&self) -> Option { 120 | self.extra_data.en_passant 121 | } 122 | fn set_en_passant(&mut self, val: Option) { 123 | if let Some(old_file) = self.extra_data.en_passant { 124 | self.hash = self.hash ^ hash::en_passant(old_file); 125 | } 126 | self.extra_data.en_passant = val; 127 | if let Some(file) = val { 128 | self.hash = self.hash ^ hash::en_passant(file); 129 | } 130 | } 131 | 132 | fn ply_count(&self) -> NumPlies { 133 | self.extra_data.ply_count 134 | } 135 | fn set_ply_count(&mut self, val: NumPlies) { 136 | self.extra_data.ply_count = val; 137 | } 138 | 139 | fn extra_data(&self) -> &ExtraData { 140 | &self.extra_data 141 | } 142 | fn set_extra_data(&mut self, val: ExtraData) { 143 | for &side in &[Side::Kingside, Side::Queenside] { 144 | for &color in &[Color::White, Color::Black] { 145 | self.set_castle(side, color, val.castling.get(side, color)); 146 | } 147 | } 148 | self.set_en_passant(val.en_passant); 149 | self.set_ply_count(val.ply_count); 150 | } 151 | 152 | pub fn hash(&self) -> ZobristHash { self.hash } 153 | 154 | fn psudo_legal_iter<'a>(&'a self) -> psudo_legal::Iter<'a> { 155 | psudo_legal::iter(self) 156 | } 157 | fn psudo_legal_noisy_iter<'a>(&'a self) -> psudo_legal::NoisyIter { 158 | psudo_legal::noisy_iter(self) 159 | } 160 | fn psudo_legal_quiet_iter<'a>(&'a self) -> psudo_legal::QuietIter { 161 | psudo_legal::quiet_iter(self) 162 | } 163 | 164 | pub fn legal_iter<'a>(&'a self) -> legal::Iter<'a> { 165 | legal::iter(self) 166 | } 167 | pub fn legal_noisy_iter<'a>(&'a self) -> legal::NoisyIter<'a> { 168 | legal::noisy_iter(self) 169 | } 170 | pub fn legal_quiet_iter<'a>(&'a self) -> legal::QuietIter<'a> { 171 | legal::quiet_iter(self) 172 | } 173 | 174 | fn can_move_to(&self, to: Square) -> bool { 175 | self.psudo_legal_iter().any( |m| m.to() == to ) 176 | } 177 | fn can_take_king(&self) -> bool { 178 | let king_square = self.king_square(self.side_to_move().invert()); 179 | self.can_move_to(king_square) 180 | } 181 | 182 | pub fn make_move(&mut self, m: &Move) { 183 | make_move::make_move(self, m); 184 | } 185 | pub fn unmake_move(&mut self, m: &Move, extra_data: ExtraData) { 186 | make_move::unmake_move(self, m, extra_data); 187 | } 188 | 189 | pub fn is_checkmated(&mut self) -> bool { 190 | mate::is_checkmated(self) 191 | } 192 | pub fn is_stalemated(&mut self) -> bool { 193 | mate::is_stalemated(self) 194 | } 195 | fn has_legal_moves(&mut self) -> bool { 196 | mate::has_legal_moves(self) 197 | } 198 | 199 | pub fn eval(&mut self) -> Score { 200 | eval::eval(self) 201 | } 202 | 203 | pub fn with_move T>(&mut self, curr_move: &Move,f: F) -> T { 204 | let extra_data = self.extra_data().clone(); 205 | self.make_move(curr_move); 206 | let ans = f(self); 207 | self.unmake_move(curr_move, extra_data); 208 | ans 209 | } 210 | } 211 | 212 | impl FromStr for Position { 213 | type Err = ParsePosError; 214 | fn from_str(s: &str) -> Result { 215 | fen_to_position(s) 216 | } 217 | } 218 | 219 | pub fn at_in_pos(pos: &Position, s: Square) -> Option { 220 | pos.at(s) 221 | } 222 | 223 | pub fn is_empty_at_in_pos(pos: &Position, s: Square) -> bool { 224 | pos.is_empty_at(s) 225 | } 226 | -------------------------------------------------------------------------------- /game/src/pos/psudo_legal.rs: -------------------------------------------------------------------------------- 1 | use std::vec; 2 | use std::iter; 3 | 4 | use square::{Square, Rank, File}; 5 | use moves::Move; 6 | use color::{White, Black}; 7 | use piece::Piece; 8 | use piece::Type::{Pawn, King, Queen, Bishop, Knight, Rook}; 9 | use castle::{Kingside, Queenside}; 10 | 11 | use super::Position; 12 | use super::bitboard::BitBoard; 13 | 14 | pub struct Iter<'a>(iter::Chain, QuietIter<'a>>); 15 | impl<'a> Iterator for Iter<'a> { 16 | type Item = Move; 17 | fn next(&mut self) -> Option { self.0.next() } 18 | fn size_hint(&self) -> (usize, Option) { self.0.size_hint() } 19 | } 20 | 21 | pub struct NoisyIter<'a>(Box + 'a>); 22 | impl<'a> Iterator for NoisyIter<'a> { 23 | type Item = Move; 24 | fn next(&mut self) -> Option { self.0.next() } 25 | fn size_hint(&self) -> (usize, Option) { self.0.size_hint() } 26 | } 27 | 28 | pub struct QuietIter<'a>(Box + 'a>); 29 | impl<'a> Iterator for QuietIter<'a> { 30 | type Item = Move; 31 | fn next(&mut self) -> Option { self.0.next() } 32 | fn size_hint(&self) -> (usize, Option) { self.0.size_hint() } 33 | } 34 | 35 | pub fn iter<'a>(p: &'a Position) -> Iter<'a> { 36 | Iter(noisy_iter(p).chain(quiet_iter(p))) 37 | } 38 | 39 | pub fn noisy_iter<'a>(p: &'a Position) -> NoisyIter<'a> { 40 | NoisyIter(Box::new(en_passant_iter(p).chain( 41 | p.piece_iter().flat_map(move |(piece_id, from)| { 42 | noisy_move_from_iter(p, piece_id, from) 43 | }) 44 | ))) 45 | } 46 | 47 | pub fn quiet_iter<'a>(p: &'a Position) -> QuietIter<'a> { 48 | QuietIter(Box::new(castle_iter(p).chain( 49 | p.piece_iter().flat_map(move |(piece_id, from)| { 50 | quiet_move_from_iter(p, piece_id, from) 51 | }) 52 | ))) 53 | } 54 | 55 | fn quiet_move_from_iter(p: &Position, 56 | piece_id: Piece, 57 | from: Square) -> Box> { 58 | if piece_id.color() != p.side_to_move() { 59 | Box::new(None.into_iter()) 60 | } else { 61 | match piece_id.piece_type() { 62 | Pawn => quiet_pawn_from_iter(p, piece_id, from), 63 | Queen|Bishop|Rook => quiet_slider_from_iter(p, piece_id, from), 64 | King|Knight => quiet_fixed_from_iter(p, piece_id, from), 65 | } 66 | } 67 | } 68 | 69 | fn noisy_move_from_iter<'a>(p: &'a Position, 70 | piece_id: Piece, 71 | from: Square) -> Box + 'a> { 72 | if piece_id.color() != p.side_to_move() { 73 | Box::new(None.into_iter()) 74 | } else { 75 | match piece_id.piece_type() { 76 | Pawn => noisy_pawn_from_iter(p, piece_id, from), 77 | Queen|Bishop|Rook => noisy_slider_from_iter(p, piece_id, from), 78 | King|Knight => noisy_fixed_from_iter(p, piece_id, from), 79 | } 80 | } 81 | } 82 | 83 | // (file, rank) 84 | type Diff = (i32, i32); 85 | 86 | fn shift(s: Square, dir: Diff) -> Option { 87 | let (dx, dy) = dir; 88 | let (File(file), Rank(rank)) = s.to_tuple(); 89 | Square::from_i32(file + dx, rank + dy) 90 | } 91 | 92 | static ROOK_SLIDE: [Diff; 4] = [(1, 0), (0, 1), (-1, 0), (0, -1)]; 93 | static BISHOP_SLIDE: [Diff; 4] = [(1, 1), (1, -1), (-1, -1), (-1, 1)]; 94 | static QUEEN_SLIDE: [Diff; 8] = [(1, 0), (0, 1), (-1, 0), (0, -1), 95 | (1, 1), (1, -1), (-1, -1), (-1, 1)]; 96 | 97 | lazy_static! { 98 | static ref ROOK_SLIDE_TABLE: [BitBoard; 64] = slider_table_gen(&ROOK_SLIDE); 99 | static ref BISHOP_SLIDE_TABLE: [BitBoard; 64] = slider_table_gen(&BISHOP_SLIDE); 100 | static ref QUEEN_SLIDE_TABLE: [BitBoard; 64] = slider_table_gen(&QUEEN_SLIDE); 101 | static ref BEHIND_TABLE: [[BitBoard; 64]; 64] = behind_table_gen(); 102 | } 103 | 104 | fn behind_table_gen() -> [[BitBoard; 64]; 64] { 105 | let mut ans = [[BitBoard::new(); 64]; 64]; 106 | for i in 0..64 { 107 | ans[i as usize] = behind_from_square_gen(Square::from_id(i)); 108 | } 109 | ans 110 | } 111 | 112 | fn behind_from_square_gen(from: Square) -> [BitBoard; 64] { 113 | let mut ans = [BitBoard::new(); 64]; 114 | 115 | for &dir in &QUEEN_SLIDE { 116 | 117 | let mut to = from; 118 | while let Some(temp_to) = shift(to, dir) { 119 | to = temp_to; 120 | 121 | let curr_ans: &mut BitBoard = &mut ans[to.to_id() as usize]; 122 | 123 | let mut blocked = to; 124 | while let Some(temp_blocked) = shift(blocked, dir) { 125 | blocked = temp_blocked; 126 | 127 | curr_ans.set_at(blocked); 128 | } 129 | } 130 | } 131 | ans 132 | } 133 | 134 | fn slider_table_gen(diffs: &[Diff]) -> [BitBoard; 64] { 135 | let mut ans = [BitBoard::new(); 64]; 136 | for i in 0..64 { 137 | ans[i as usize] = slider_from_square_gen(Square::from_id(i), diffs); 138 | } 139 | ans 140 | } 141 | 142 | fn slider_from_square_gen(from: Square, diffs: &[Diff]) -> BitBoard { 143 | let mut ans = BitBoard::new(); 144 | for &dir in diffs { 145 | let mut to = from; 146 | while let Some(temp) = shift(to, dir) { 147 | to = temp; 148 | ans.set_at(to); 149 | } 150 | } 151 | ans 152 | } 153 | 154 | fn reachable_from_bitboard(p: &Position, piece_id: Piece, from: Square) -> BitBoard { 155 | let piece_type = piece_id.piece_type(); 156 | let table: &[BitBoard; 64] = &match piece_type { 157 | Rook => *ROOK_SLIDE_TABLE, 158 | Bishop => *BISHOP_SLIDE_TABLE, 159 | Queen => *QUEEN_SLIDE_TABLE, 160 | _ => panic!(), 161 | }; 162 | let mut ans: BitBoard = table[from.to_id() as usize]; 163 | let potential_blocker: BitBoard = ans.intersect(!p.data.empty_data()); 164 | 165 | for blocker_square in potential_blocker.iter() { 166 | ans = ans.intersect( 167 | !BEHIND_TABLE[from.to_id() as usize][blocker_square.to_id() as usize]); 168 | } 169 | 170 | ans 171 | } 172 | 173 | fn quiet_slider_from_iter(p: &Position, 174 | piece_id: Piece, 175 | from: Square) -> Box> { 176 | let to_bitboard = reachable_from_bitboard(p, piece_id, from); 177 | 178 | let ans = to_bitboard.intersect(p.data.empty_data()); 179 | 180 | Box::new(ans.iter().map(move |to: Square| Move::new(from, to))) 181 | } 182 | 183 | fn noisy_slider_from_iter<'a>(p: &'a Position, 184 | piece_id: Piece, 185 | from: Square) -> Box + 'a> { 186 | let to_bitboard = reachable_from_bitboard(p, piece_id, from); 187 | 188 | let ans = to_bitboard.intersect(p.data.color_data(p.side_to_move().invert())); 189 | 190 | Box::new(ans.iter().map(move |to: Square| { 191 | let mut ans = Move::new(from, to); 192 | ans.set_capture_normal(p.at(to)); 193 | ans 194 | })) 195 | } 196 | 197 | static KING_FIXED: [Diff; 8] = [(1, 0), (0, 1), (-1, 0), (0, -1), 198 | (1, 1), (1, -1), (-1, -1), (-1, 1)]; 199 | static KNIGHT_FIXED: [Diff; 8] = [(2, 1), (2, -1), (-2, -1), (-2, 1), 200 | (1, 2), (1, -2), (-1, -2), (-1, 2)]; 201 | 202 | lazy_static! { 203 | static ref KING_FIXED_TABLE: [BitBoard; 64] = fixed_table_gen(&KING_FIXED); 204 | static ref KNIGHT_FIXED_TABLE: [BitBoard; 64] = fixed_table_gen(&KNIGHT_FIXED); 205 | } 206 | 207 | fn fixed_table_gen(diffs: &[Diff]) -> [BitBoard; 64] { 208 | let mut ans = [BitBoard::new(); 64]; 209 | for i in 0..64 { 210 | ans[i as usize] = fixed_from_square_gen(Square::from_id(i), diffs); 211 | } 212 | ans 213 | } 214 | 215 | fn fixed_from_square_gen(from: Square, diffs: &[Diff]) -> BitBoard { 216 | let mut ans = BitBoard::new(); 217 | for dir in diffs { 218 | if let Some(to) = shift(from, *dir) { 219 | ans.set_at(to); 220 | } 221 | } 222 | ans 223 | } 224 | 225 | fn quiet_fixed_from_iter(p: &Position, 226 | piece_id: Piece, 227 | from: Square) -> Box> { 228 | 229 | let table: &[BitBoard; 64] = &match piece_id.piece_type() { 230 | King => *KING_FIXED_TABLE, 231 | Knight => *KNIGHT_FIXED_TABLE, 232 | _ => panic!(), 233 | }; 234 | let to_bits = table[from.to_id() as usize].intersect(p.data.empty_data()); 235 | 236 | Box::new(to_bits.iter().map(move |to: Square| Move::new(from, to))) 237 | } 238 | 239 | fn noisy_fixed_from_iter<'a>(p: &'a Position, 240 | piece_id: Piece, 241 | from: Square) -> Box + 'a> { 242 | let table: &[BitBoard; 64] = &match piece_id.piece_type() { 243 | King => *KING_FIXED_TABLE, 244 | Knight => *KNIGHT_FIXED_TABLE, 245 | _ => panic!(), 246 | }; 247 | let other_color = piece_id.color().invert(); 248 | let to_bits = table[from.to_id() as usize].intersect(p.data.color_data(other_color)); 249 | 250 | Box::new(to_bits.iter().map(move |to: Square| { 251 | let mut curr_move = Move::new(from, to); 252 | curr_move.set_capture_normal(p.at(to)); 253 | curr_move 254 | })) 255 | } 256 | 257 | fn quiet_pawn_from_iter(p: &Position, 258 | piece_id: Piece, 259 | from: Square) -> Box> { 260 | let mut ans = Vec::new(); 261 | 262 | let piece_color = piece_id.color(); 263 | let from_rank = from.rank().0; 264 | //rank_up is the 1-based rank from the piece-owner's side. 265 | let (dy, rank_up): (i32, i32) = match piece_color { 266 | White => ( 1, 1 + from_rank ), 267 | Black => (-1, 8 - from_rank ), 268 | }; 269 | let move_dir: Diff = (0, dy); 270 | let to: Square = shift(from, move_dir).unwrap(); 271 | // if destination is empty 272 | if p.is_empty_at(to) { 273 | match rank_up { 274 | 7 => {}, 275 | 2 => { 276 | let curr_move = Move::new(from, to); 277 | ans.push(curr_move); 278 | let to2: Square = shift(to, move_dir).unwrap(); 279 | if p.is_empty_at(to2) { 280 | let mut curr_move2 = Move::new(from, to2); 281 | curr_move2.set_pawn_double_move(true); 282 | ans.push(curr_move2); 283 | } 284 | }, 285 | _ => { 286 | let curr_move = Move::new(from, to); 287 | ans.push(curr_move); 288 | }, 289 | } 290 | } 291 | 292 | Box::new(ans.into_iter()) 293 | } 294 | 295 | fn noisy_pawn_from_iter(p: &Position, 296 | piece_id: Piece, 297 | from: Square) -> Box> { 298 | let mut ans = Vec::new(); 299 | 300 | let piece_color = piece_id.color(); 301 | let from_rank = from.rank().0; 302 | //rank_up is the 1-based rank from the piece-owner's side. 303 | let (dy, rank_up): (i32, i32) = match piece_color { 304 | White => ( 1, 1 + from_rank ), 305 | Black => (-1, 8 - from_rank ), 306 | }; 307 | let move_dir: Diff = (0, dy); 308 | let to: Square = shift(from, move_dir).unwrap(); 309 | // if destination is empty 310 | if p.is_empty_at(to) { 311 | match rank_up { 312 | 7 => { 313 | for new_piece in [Queen, Knight, Rook, Bishop].iter() { 314 | let mut curr_move = Move::new(from, to); 315 | curr_move.set_promote(Some(*new_piece)); 316 | ans.push(curr_move); 317 | } 318 | }, 319 | _ => {}, 320 | } 321 | } 322 | 323 | for dx in [1, -1].iter() { 324 | let capture_dir: Diff = (*dx, dy); 325 | let capture_new_pos: Option = shift(from, capture_dir); 326 | let capture_to: Square = match capture_new_pos { 327 | Some(val) => val, 328 | None => continue, 329 | }; 330 | if p.is_color_at(capture_to, piece_color.invert()) { 331 | if rank_up == 7 { 332 | for new_piece in [Queen, Knight, Rook, Bishop].iter() { 333 | let mut curr_move = Move::new(from, capture_to); 334 | curr_move.set_capture_normal(p.at(capture_to)); 335 | curr_move.set_promote(Some(*new_piece)); 336 | ans.push(curr_move); 337 | } 338 | } else { 339 | let mut curr_move = Move::new(from, capture_to); 340 | curr_move.set_capture_normal(p.at(capture_to)); 341 | ans.push(curr_move); 342 | } 343 | } 344 | } 345 | 346 | Box::new(ans.into_iter()) 347 | } 348 | 349 | fn en_passant_iter(p: &Position) -> vec::IntoIter { 350 | let mut ans = Vec::new(); 351 | 352 | let to_file = match p.en_passant() { 353 | Some(f) => f, 354 | None => return ans.into_iter(), 355 | }; 356 | let (from_rank, to_rank) = match p.side_to_move() { 357 | White => (Rank(4), Rank(5)), 358 | Black => (Rank(3), Rank(2)), 359 | }; 360 | let (x, y, z); 361 | let from_file_all: &[File] = match to_file { 362 | File(0) => { x = [File(1)]; &x }, 363 | File(7) => { y = [File(6)]; &y }, 364 | File(f) => { z = [File(f-1), File(f+1)]; &z }, 365 | }; 366 | 367 | let expect_piece = Piece::new(p.side_to_move(), Pawn); 368 | let to = Square::new(to_file, to_rank); 369 | 370 | for &from_file in from_file_all.iter() { 371 | let from = Square::new(from_file, from_rank); 372 | if p.is_piece_at(expect_piece, from) { 373 | let mut curr_move = Move::new(from, to); 374 | curr_move.set_en_passant(true); 375 | ans.push(curr_move); 376 | } 377 | } 378 | 379 | ans.into_iter() 380 | } 381 | 382 | fn castle_iter(p: &Position) -> vec::IntoIter { 383 | let mut ans = Vec::new(); 384 | match p.side_to_move() { 385 | White => { 386 | if p.can_castle_now(Kingside, White) { 387 | let from = Square::new(File(4), Rank(0)); 388 | let to = Square::new(File(6), Rank(0)); 389 | let mut curr_move = Move::new(from, to); 390 | curr_move.set_castle(Some(Kingside)); 391 | ans.push(curr_move); 392 | } 393 | if p.can_castle_now(Queenside, White) { 394 | let from = Square::new(File(4), Rank(0)); 395 | let to = Square::new(File(2), Rank(0)); 396 | let mut curr_move = Move::new(from, to); 397 | curr_move.set_castle(Some(Queenside)); 398 | ans.push(curr_move); 399 | } 400 | } 401 | Black => { 402 | if p.can_castle_now(Kingside, Black) { 403 | let from = Square::new(File(4), Rank(7)); 404 | let to = Square::new(File(6), Rank(7)); 405 | let mut curr_move = Move::new(from, to); 406 | curr_move.set_castle(Some(Kingside)); 407 | ans.push(curr_move); 408 | } 409 | if p.can_castle_now(Queenside, Black) { 410 | let from = Square::new(File(4), Rank(7)); 411 | let to = Square::new(File(2), Rank(7)); 412 | let mut curr_move = Move::new(from, to); 413 | curr_move.set_castle(Some(Queenside)); 414 | ans.push(curr_move); 415 | } 416 | } 417 | } 418 | 419 | ans.into_iter() 420 | } 421 | -------------------------------------------------------------------------------- /game/src/square.rs: -------------------------------------------------------------------------------- 1 | //! The types for files, ranks, and squares 2 | 3 | use std::str::FromStr; 4 | use std::fmt; 5 | 6 | pub struct ParseFileError(()); 7 | pub struct ParseRankError(()); 8 | pub struct ParseSquareError(()); 9 | impl From for ParseSquareError { 10 | fn from(_: ParseFileError) -> Self { ParseSquareError(()) } 11 | } 12 | impl From for ParseSquareError { 13 | fn from(_: ParseRankError) -> Self { ParseSquareError(()) } 14 | } 15 | 16 | // File and Rank are 0-based. 17 | #[derive(PartialEq, Eq, Copy, Clone, Debug)] 18 | pub struct File(pub i32); 19 | impl FromStr for File { 20 | type Err = ParseFileError; 21 | fn from_str(s: &str) -> Result { 22 | if s.len() != 1 { return Err(ParseFileError(())); } 23 | match s.as_bytes()[0] { 24 | ch @ b'a' ... b'h' => Ok(File((ch - b'a') as i32)), 25 | _ => Err(ParseFileError(())), 26 | } 27 | } 28 | } 29 | impl fmt::Display for File { 30 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 31 | debug_assert!(self.0 >= 0 && self.0 < 8); 32 | write!(f, "{}", (self.0 as u8 + b'a') as char) 33 | } 34 | } 35 | 36 | #[derive(PartialEq, Eq, Copy, Clone, Debug)] 37 | pub struct Rank(pub i32); 38 | impl FromStr for Rank { 39 | type Err = ParseRankError; 40 | fn from_str(s: &str) -> Result { 41 | if s.len() != 1 { return Err(ParseRankError(())); } 42 | match s.as_bytes()[0] { 43 | ch @ b'1' ... b'8' => Ok(Rank((ch - b'1') as i32)), 44 | _ => Err(ParseRankError(())), 45 | } 46 | } 47 | } 48 | impl fmt::Display for Rank { 49 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 50 | debug_assert!(self.0 >= 0 && self.0 < 8); 51 | write!(f, "{}", (self.0 as u8 + b'1') as char) 52 | } 53 | } 54 | 55 | #[derive(PartialEq, Eq, Copy, Clone, Debug)] 56 | pub struct Square(File, Rank); 57 | impl Square { 58 | pub fn new(f: File, r: Rank) -> Square { 59 | Square(f, r) 60 | } 61 | 62 | pub fn file(self) -> File { 63 | self.0 64 | } 65 | pub fn rank(self) -> Rank { 66 | self.1 67 | } 68 | 69 | pub fn to_tuple(self) -> (File, Rank) { 70 | let Square(f, r) = self; 71 | (f, r) 72 | } 73 | pub fn from_i32(file: i32, rank: i32) -> Option { 74 | if file>=0 && file<8 && rank>=0 && rank<8 { 75 | Some(Square(File(file), Rank(rank))) 76 | } else { 77 | None 78 | } 79 | } 80 | 81 | pub fn to_id(&self) -> i32 { 82 | let (File(f), Rank(r)) = self.to_tuple(); 83 | f*8 + r 84 | } 85 | pub fn from_id(val: i32) -> Square { 86 | let (f, r) = (val/8, val%8); 87 | Square::new(File(f), Rank(r)) 88 | } 89 | } 90 | impl FromStr for Square { 91 | type Err = ParseSquareError; 92 | fn from_str(s: &str) -> Result { 93 | if s.len() != 2 { return Err(ParseSquareError(())); } 94 | let f: File = try!(FromStr::from_str(&s[0..1])); 95 | let r: Rank = try!(FromStr::from_str(&s[1..2])); 96 | Ok(Square::new(f, r)) 97 | } 98 | } 99 | impl fmt::Display for Square { 100 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 101 | write!(f, "{}{}", self.file(), self.rank()) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /search/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | 3 | name = "search" 4 | version = "0.0.1" 5 | authors = ["Theemathas Chirananthavat "] 6 | 7 | [dependencies] 8 | log = "*" 9 | 10 | [dependencies.game] 11 | path = "../game" 12 | -------------------------------------------------------------------------------- /search/src/depth_limited_search.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::{AtomicBool, Ordering}; 2 | 3 | use game::{Move, Position, Score, ScoreUnit, NumPlies}; 4 | use types::InnerData; 5 | use negamax::{negamax_root, Bound}; 6 | use transposition_table::TranspositionTable; 7 | 8 | pub fn depth_limited_search(pos: &mut Position, 9 | search_moves: &[Move], 10 | depth: NumPlies, 11 | table: &mut TranspositionTable, 12 | is_killed: &AtomicBool) -> Option<(Score, Move, InnerData)> { 13 | assert!(!search_moves.is_empty()); 14 | assert!(depth.0 >= 1); 15 | 16 | let guess_score = table.get(pos).map_or(ScoreUnit(0), |x| { 17 | if let Bound::Exact(Score::Value(val)) = x.bound { 18 | val 19 | } else { 20 | ScoreUnit(0) 21 | } 22 | }); 23 | 24 | let aspiration_width = [ScoreUnit(25), ScoreUnit(100), ScoreUnit(500)]; 25 | 26 | let mut alpha_window = 0; 27 | let mut beta_window = 0; 28 | let mut best_score_move_opt = None; 29 | let mut data = InnerData::one_node(); 30 | 31 | while best_score_move_opt.is_none() { 32 | let curr_alpha = aspiration_width.get(alpha_window) 33 | .map(|&diff| Score::Value(guess_score - diff)); 34 | let curr_beta = aspiration_width.get(beta_window) 35 | .map(|&diff| Score::Value(guess_score + diff)); 36 | let curr_ans = negamax_root(pos, curr_alpha, curr_beta, 37 | depth, table, is_killed, search_moves); 38 | if is_killed.load(Ordering::Relaxed) { 39 | // Thread killed. Bail out 40 | return None; 41 | } 42 | let (curr_bound, curr_best_move_opt, curr_data) = curr_ans; 43 | data = data.combine(curr_data); 44 | match curr_bound { 45 | Bound::Lower(_) => beta_window += 1, 46 | Bound::Upper(_) => alpha_window += 1, 47 | Bound::Exact(x) => best_score_move_opt = Some((x, curr_best_move_opt.unwrap())), 48 | } 49 | } 50 | 51 | let (best_score, best_move) = best_score_move_opt.unwrap(); 52 | 53 | Some((best_score, best_move, data)) 54 | } 55 | -------------------------------------------------------------------------------- /search/src/iterated_deepening.rs: -------------------------------------------------------------------------------- 1 | use std::sync::mpsc::Sender; 2 | use std::sync::atomic::{AtomicBool, Ordering}; 3 | use std::sync::Arc; 4 | 5 | use game::{Position, Move, NumPlies}; 6 | 7 | use types::{InnerData, Data, Report}; 8 | use transposition_table::TranspositionTable; 9 | use depth_limited_search::depth_limited_search; 10 | 11 | pub fn iterated_deepening(pos: Position, 12 | search_moves: &[Move], 13 | mut table: TranspositionTable, 14 | tx: Sender, 15 | is_killed: Arc) { 16 | let mut best_score; 17 | let mut best_move; 18 | let mut total_search_data = InnerData::one_node(); 19 | let mut curr_depth = NumPlies(1); 20 | 21 | while !is_killed.load(Ordering::SeqCst) { 22 | debug!("Starting depth limited search with depth = {} plies", curr_depth.0); 23 | let mut temp_pos = pos.clone(); 24 | let result_opt = depth_limited_search(&mut temp_pos, search_moves, 25 | curr_depth, &mut table, &is_killed); 26 | if let None = result_opt { break; } 27 | let result = result_opt.unwrap(); 28 | 29 | let (temp_best_score, temp_best_move, curr_search_data) = result; 30 | best_score = temp_best_score; 31 | best_move = temp_best_move; 32 | total_search_data = total_search_data.combine(curr_search_data); 33 | 34 | let _ = tx.send(Report { data: Data { nodes: total_search_data.nodes, depth: curr_depth }, 35 | score: best_score, 36 | pv: vec![best_move] }); 37 | 38 | curr_depth.0 += 1; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /search/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(mpsc_select)] 2 | 3 | #[macro_use] 4 | extern crate log; 5 | 6 | extern crate game; 7 | 8 | pub use types::{NumNodes, State, Param, Cmd, Report, BestMove}; 9 | pub use start::start; 10 | 11 | mod types; 12 | 13 | mod start; 14 | 15 | mod iterated_deepening; 16 | mod depth_limited_search; 17 | mod negamax; 18 | 19 | mod transposition_table; 20 | -------------------------------------------------------------------------------- /search/src/negamax.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::{AtomicBool, Ordering}; 2 | use std::cmp::max; 3 | 4 | use game::{Position, Move, Score, ScoreUnit, NumPlies}; 5 | use types::InnerData; 6 | 7 | use transposition_table::TranspositionTable; 8 | 9 | #[derive(Copy, Clone, Debug)] 10 | pub enum Bound { 11 | Exact(Score), 12 | Lower(Score), 13 | Upper(Score), 14 | } 15 | impl Bound { 16 | pub fn as_score(self) -> Score { 17 | match self { 18 | Bound::Exact(x) => x, 19 | Bound::Lower(x) => x, 20 | Bound::Upper(x) => x, 21 | } 22 | } 23 | pub fn is_exact(self) -> bool { 24 | if let Bound::Exact(_) = self { true } else { false } 25 | } 26 | pub fn is_lower(self) -> bool { 27 | if let Bound::Lower(_) = self { true } else { false } 28 | } 29 | pub fn is_upper(self) -> bool { 30 | if let Bound::Upper(_) = self { true } else { false } 31 | } 32 | } 33 | 34 | // TODO put more parameters here 35 | #[derive(Clone)] 36 | pub struct Param { 37 | pub eval_depth: Option, 38 | pub table_depth: NumPlies, 39 | } 40 | 41 | pub fn negamax_root(pos: &mut Position, 42 | alpha: Option, 43 | beta: Option, 44 | depth: NumPlies, 45 | table: &mut TranspositionTable, 46 | is_killed: &AtomicBool, 47 | search_moves: &[Move]) -> (Bound, Option, InnerData) { 48 | let next_depth = NumPlies(depth.0 - 1); 49 | let param = Param { eval_depth: Some(NumPlies(1)), 50 | table_depth: depth }; 51 | negamax_generic(pos, alpha, beta, param, table, is_killed, 52 | &mut |_| Box::new(search_moves.to_vec().into_iter()), 53 | &mut |inner_pos, inner_alpha, inner_beta, inner_table| { 54 | let inner_param = Param { 55 | eval_depth: Some(next_depth), 56 | table_depth: next_depth, 57 | }; 58 | let (bound, _, data) = 59 | negamax_inner(inner_pos, inner_alpha, inner_beta, 60 | inner_param, inner_table, is_killed); 61 | (bound, data) 62 | }, 63 | &mut |_| None) 64 | } 65 | 66 | fn negamax_inner(pos: &mut Position, 67 | alpha: Option, 68 | beta: Option, 69 | param: Param, 70 | table: &mut TranspositionTable, 71 | is_killed: &AtomicBool) -> (Bound, Option, InnerData) { 72 | negamax_generic(pos, alpha, beta, param, table, is_killed, 73 | &mut |x| Box::new(x.legal_iter()), 74 | &mut |x, inner_alpha, inner_beta, table| { 75 | let quiescence_param = Param { 76 | eval_depth: None, 77 | table_depth: NumPlies(0), 78 | }; 79 | let (bound, _, data) = quiescence(x, inner_alpha, inner_beta, 80 | quiescence_param, table, is_killed); 81 | (bound, data) 82 | }, 83 | &mut |_| None) 84 | } 85 | 86 | fn quiescence(pos: &mut Position, 87 | alpha: Option, 88 | beta: Option, 89 | param: Param, 90 | table: &mut TranspositionTable, 91 | is_killed: &AtomicBool) -> (Bound, Option, InnerData) { 92 | negamax_generic(pos, alpha, beta, param, table, is_killed, 93 | &mut |x| Box::new(x.legal_noisy_iter()), 94 | &mut |x, _, _, _| 95 | (Bound::Exact(x.eval()), InnerData::one_node()), 96 | &mut |x| Some(x.eval())) 97 | } 98 | 99 | // TODO somehow eliminate the Box 100 | fn negamax_generic(pos: &mut Position, 101 | alpha: Option, 102 | beta: Option, 103 | param: Param, 104 | table: &mut TranspositionTable, 105 | is_killed: &AtomicBool, 106 | move_gen_fn: &mut F, 107 | eval_fn: &mut G, 108 | stand_pat_fn: &mut H) -> (Bound, Option, InnerData) where 109 | for<'a> F: FnMut(&'a Position) -> Box + 'a>, 110 | for<'b> G: FnMut(&'b mut Position, Option, Option, 111 | &mut TranspositionTable) -> (Bound, InnerData), 112 | for<'c> H: FnMut(&'c mut Position) -> Option { 113 | if is_killed.load(Ordering::Relaxed) { 114 | return (Bound::Exact(Score::Value(ScoreUnit(0))), None, InnerData::one_node()); 115 | } 116 | 117 | let mut table_best_move_opt = None; 118 | if let Some(data_ref) = table.get(pos) { 119 | table_best_move_opt = data_ref.best_move_opt.clone(); 120 | if data_ref.depth >= param.table_depth { 121 | let table_bound = data_ref.bound; 122 | let lower_than_alpha = alpha.is_some() && 123 | !table_bound.is_lower() && 124 | table_bound.as_score() <= alpha.unwrap(); 125 | if lower_than_alpha { 126 | return (Bound::Upper(alpha.unwrap()), table_best_move_opt, InnerData::one_node()); 127 | } 128 | let higher_than_beta = beta.is_some() && 129 | !table_bound.is_upper() && 130 | table_bound.as_score() >= beta.unwrap(); 131 | if higher_than_beta { 132 | return (Bound::Lower(beta.unwrap()), table_best_move_opt, InnerData::one_node()); 133 | } 134 | if table_bound.is_exact() { 135 | return (table_bound, table_best_move_opt, InnerData::one_node()); 136 | } 137 | } 138 | } 139 | let table_best_move_opt = table_best_move_opt; 140 | 141 | if param.eval_depth == Some(NumPlies(0)) { 142 | let (bound, data) = eval_fn(pos, alpha, beta, table); 143 | table.set(pos, NumPlies(0), None, bound); 144 | return (bound, None, data); 145 | } 146 | 147 | let (has_legal, score_opt, best_move_opt, data): 148 | (bool, Option, Option, InnerData) = (|| { 149 | let temp = pos.clone(); 150 | let move_iter: Box> = { 151 | let normal_iter = move_gen_fn(&temp); 152 | if let Some(ref table_move) = table_best_move_opt { 153 | let is_table_move_valid = move_gen_fn(&temp).any(|y| y == *table_move); 154 | if is_table_move_valid { 155 | Box::new(Some( 156 | table_move.clone()).into_iter() 157 | .chain(normal_iter.filter(move |x| *x != *table_move))) 158 | } else { 159 | normal_iter 160 | } 161 | } else { 162 | normal_iter 163 | } 164 | }; 165 | 166 | let mut has_legal = false; 167 | let mut prev_score_opt: Option = alpha; 168 | let mut prev_best_move_opt: Option = None; 169 | 170 | if let Some(stand_pat_score) = stand_pat_fn(pos) { 171 | let new_score = match prev_score_opt { 172 | None => stand_pat_score, 173 | Some(prev_score) => max(prev_score, stand_pat_score), 174 | }; 175 | 176 | has_legal = true; 177 | prev_score_opt = Some(new_score); 178 | 179 | if let Some(beta_val) = beta { 180 | if new_score >= beta_val { 181 | return (true, beta, None, InnerData::one_node()); 182 | } 183 | } 184 | } 185 | 186 | let mut prev_data = InnerData::one_node(); 187 | 188 | for curr_move in move_iter { 189 | 190 | let new_alpha = beta.map(|x| x.decrement()); 191 | let new_beta = prev_score_opt.map(|x| x.decrement()); 192 | let new_param = Param { 193 | eval_depth: param.eval_depth.map(|x| NumPlies(x.0 - 1)), 194 | table_depth: NumPlies(param.table_depth.0.saturating_sub(1)), 195 | }; 196 | let (temp_bound, _, temp_data) = pos.with_move(&curr_move, |new_pos| 197 | negamax_generic(new_pos, 198 | new_alpha, 199 | new_beta, 200 | new_param, 201 | table, 202 | is_killed, 203 | move_gen_fn, 204 | eval_fn, 205 | stand_pat_fn)); 206 | let curr_score = temp_bound.as_score().increment(); 207 | let curr_data = temp_data.increment(); 208 | 209 | let (new_score, new_best_move_opt) = match prev_score_opt { 210 | None => (curr_score, Some(curr_move)), 211 | Some(prev_score) => { 212 | if curr_score <= prev_score { 213 | (prev_score, prev_best_move_opt) 214 | } else { 215 | (curr_score, Some(curr_move)) 216 | } 217 | } 218 | }; 219 | let new_data = prev_data.combine(curr_data); 220 | 221 | has_legal = true; 222 | prev_score_opt = Some(new_score); 223 | prev_best_move_opt = new_best_move_opt; 224 | prev_data = new_data; 225 | 226 | if let Some(beta_val) = beta { 227 | if new_score >= beta_val { 228 | prev_score_opt = beta; 229 | break; 230 | } 231 | } 232 | } 233 | 234 | (has_legal, prev_score_opt, prev_best_move_opt, prev_data) 235 | })(); 236 | 237 | if has_legal { 238 | let score = score_opt.unwrap(); 239 | let bound = { 240 | if alpha.is_some() && score <= alpha.unwrap() { 241 | Bound::Upper(alpha.unwrap()) 242 | } else if beta.is_some() && score >= beta.unwrap() { 243 | Bound::Lower(beta.unwrap()) 244 | } else { 245 | Bound::Exact(score) 246 | } 247 | }; 248 | table.set(pos, param.table_depth, best_move_opt.clone(), bound); 249 | (bound, best_move_opt, data) 250 | } else { 251 | let (bound, data) = eval_fn(pos, alpha, beta, table); 252 | table.set(pos, param.table_depth, None, bound); 253 | (bound, None, data) 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /search/src/start.rs: -------------------------------------------------------------------------------- 1 | use std::sync::mpsc::{Sender, Receiver, channel}; 2 | use std::sync::atomic::{AtomicBool, Ordering}; 3 | use std::sync::Arc; 4 | use std::thread; 5 | use std::mem::size_of; 6 | 7 | use game::{Move, Score, ScoreUnit, NumPlies}; 8 | 9 | use types::{State, Cmd, Data, Report, BestMove, NumNodes}; 10 | use iterated_deepening::iterated_deepening; 11 | use transposition_table::{self, TranspositionTable}; 12 | 13 | pub fn start(mut state: State, rx: Receiver, 14 | tx: Sender) -> BestMove { 15 | if state.param.ponder { 16 | debug!("pondering, waiting for next command"); 17 | // Actually should ponder, but now just waits for our move. 18 | for cmd in rx.iter() { 19 | match cmd { 20 | Cmd::SetDebug(val) => { 21 | // TODO set debug 22 | debug!("debug is now {:?}", val); 23 | }, 24 | Cmd::PonderHit => { 25 | debug!("ponder hit when pondering"); 26 | state.param.ponder = false; 27 | break; 28 | }, 29 | Cmd::Stop => { 30 | debug!("stop from pondering"); 31 | // TODO report stuff.about pondering and terminate. 32 | unimplemented!(); 33 | }, 34 | } 35 | } 36 | if state.param.ponder { 37 | panic!("Sender hung up while pondering"); 38 | } 39 | debug!("pondering finished"); 40 | } 41 | 42 | let search_moves: Vec<(Move)> = { 43 | //let legal_moves_chan = receive_legal(state.pos.clone()); 44 | let legal_moves = state.pos.legal_iter(); 45 | match state.param.search_moves { 46 | None => legal_moves.collect(), 47 | Some(ref val) => legal_moves.filter(|x| val.contains(x)).collect(), 48 | } 49 | }; 50 | if search_moves.is_empty() { 51 | panic!("No legal moves searched in searched position"); 52 | } 53 | 54 | // this is just a placeholder report 55 | let mut last_report = Report { data: Data { nodes: NumNodes(0), depth: NumPlies(0) }, 56 | score: Score::Value(ScoreUnit(0)), 57 | pv: vec![search_moves[0].clone()] }; 58 | let table_capacity = state.param.hash_size / 59 | size_of::>(); 60 | let table = TranspositionTable::with_capacity(table_capacity); 61 | 62 | let (search_tx, search_rx) = channel::(); 63 | let is_killed = Arc::new(AtomicBool::new(false)); 64 | 65 | let temp_is_killed = is_killed.clone(); 66 | 67 | debug!("Starting iterated deepening search"); 68 | thread::spawn(move || 69 | iterated_deepening(state.pos, &search_moves, table, search_tx, temp_is_killed)); 70 | 71 | loop { 72 | select! { 73 | val = rx.recv() => { 74 | let cmd = val.ok().expect("Sender hung up while calculating"); 75 | debug!("received command {:?}", cmd); 76 | match cmd { 77 | Cmd::SetDebug(val) => { 78 | // TODO set debug 79 | debug!("debug is now {:?}", val); 80 | }, 81 | Cmd::PonderHit => { 82 | debug!("ponder hit when not pondering (ignored)"); 83 | // Ignore this cmd 84 | }, 85 | Cmd::Stop => { 86 | 87 | debug!("killing search"); 88 | is_killed.store(true, Ordering::SeqCst); 89 | 90 | debug!("reporting result"); 91 | tx.send(last_report.clone()).unwrap(); 92 | 93 | debug!("search stopping"); 94 | return BestMove(last_report.pv[0].clone(), None); 95 | } 96 | } 97 | }, 98 | search_res = search_rx.recv() => { 99 | debug!("receiving result from iterated_deepening"); 100 | 101 | last_report = search_res.ok() 102 | .expect("iterated_deepening unexpectedly dropped Sender"); 103 | 104 | tx.send(last_report.clone()).unwrap(); 105 | } 106 | } 107 | } 108 | } 109 | 110 | -------------------------------------------------------------------------------- /search/src/transposition_table.rs: -------------------------------------------------------------------------------- 1 | use game::{Position, Move, ZobristHash, NumPlies}; 2 | 3 | use negamax::Bound; 4 | 5 | #[derive(Clone, Debug)] 6 | pub struct Data { 7 | pub hash: ZobristHash, 8 | pub depth: NumPlies, 9 | pub bound: Bound, 10 | pub best_move_opt: Option, 11 | } 12 | 13 | pub struct TranspositionTable(Vec>); 14 | impl TranspositionTable { 15 | pub fn with_capacity(capacity: usize) -> Self { 16 | TranspositionTable(vec![None; capacity]) 17 | } 18 | pub fn get<'a>(&'a self, pos: &Position) -> Option<&'a Data> { 19 | let hash = pos.hash(); 20 | let idx = (hash.0 % (self.0.len() as u64)) as usize; 21 | self.0[idx].as_ref().and_then(|x| { 22 | let is_correct_pos = x.hash == hash && 23 | x.best_move_opt.as_ref() 24 | .map_or(true, |y| pos.legal_iter().any(|z| *y == z)); 25 | if is_correct_pos { Some(x) } else { None } 26 | }) 27 | } 28 | // TODO implement a better replacement scheme 29 | pub fn set(&mut self, 30 | pos: &Position, 31 | depth: NumPlies, 32 | best_move_opt: Option, 33 | bound: Bound) { 34 | let hash = pos.hash(); 35 | let idx = (hash.0 % (self.0.len() as u64)) as usize; 36 | self.0[idx] = Some(Data { 37 | hash: hash, 38 | depth: depth, 39 | best_move_opt: best_move_opt, 40 | bound: bound 41 | }); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /search/src/types.rs: -------------------------------------------------------------------------------- 1 | use game::{Position, Move, Score, NumPlies, NumMoves}; 2 | 3 | #[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Debug)] 4 | pub struct NumNodes(pub u64); 5 | 6 | 7 | #[derive(Clone, Debug)] 8 | pub struct State { 9 | pub pos: Position, 10 | pub prev_pos: Option, 11 | pub prev_move: Option, 12 | pub param: Param, 13 | } 14 | 15 | #[derive(Clone, Debug)] 16 | pub struct Param { 17 | pub ponder: bool, 18 | pub search_moves: Option>, 19 | pub depth: Option, 20 | pub nodes: Option, 21 | pub mate: Option, 22 | pub hash_size: usize, 23 | } 24 | impl Param { 25 | pub fn new(hash_size: usize) -> Self { 26 | Param { 27 | ponder: false, 28 | search_moves: None, 29 | depth: None, 30 | nodes: None, 31 | mate: None, 32 | hash_size: hash_size, 33 | } 34 | } 35 | } 36 | 37 | #[derive(PartialEq, Eq, Copy, Clone, Debug)] 38 | pub enum Cmd { 39 | SetDebug(bool), 40 | PonderHit, 41 | Stop, 42 | } 43 | 44 | #[derive(PartialEq, Eq, Clone, Debug)] 45 | pub struct BestMove(pub Move, pub Option); 46 | 47 | #[derive(Clone, Debug)] 48 | pub struct Report { 49 | pub data: Data, 50 | pub score: Score, 51 | pub pv: Vec, 52 | } 53 | 54 | #[derive(Clone, Debug)] 55 | pub struct Data { 56 | pub nodes: NumNodes, 57 | pub depth: NumPlies, 58 | } 59 | 60 | // TODO put actual data here 61 | #[derive(Clone, Debug)] 62 | pub struct InnerData { 63 | pub nodes: NumNodes, 64 | } 65 | impl InnerData { 66 | pub fn one_node() -> InnerData { InnerData { nodes: NumNodes(1) } } 67 | pub fn combine(self, other: InnerData) -> InnerData { 68 | InnerData { nodes: NumNodes(self.nodes.0 + other.nodes.0) } 69 | } 70 | pub fn increment(self) -> InnerData { 71 | InnerData { nodes: NumNodes(self.nodes.0 + 1) } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate env_logger; 2 | 3 | extern crate uci; 4 | 5 | use std::io::{stdin, stdout}; 6 | 7 | fn main() { 8 | env_logger::init().unwrap(); 9 | //uci::start(stdin().lock(), stdout().lock()); 10 | uci::start(stdin(), stdout()); 11 | } 12 | -------------------------------------------------------------------------------- /timer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | 3 | name = "timer" 4 | version = "0.0.1" 5 | authors = ["Theemathas Chirananthavat "] 6 | 7 | [dependencies.game] 8 | path = "../game" 9 | -------------------------------------------------------------------------------- /timer/src/control.rs: -------------------------------------------------------------------------------- 1 | use std::sync::mpsc::SyncSender; 2 | use std::time::Duration; 3 | use std::cmp; 4 | use std::thread; 5 | 6 | use game::Color; 7 | 8 | use {Timer, TimeOut}; 9 | 10 | pub fn start(data: Timer, c: Color, tx: SyncSender) { 11 | match data { 12 | Timer::Infinite => return, 13 | Timer::Exact(val) => { 14 | send_after(val, tx); 15 | }, 16 | Timer::Remain(val) => { 17 | // TODO what is the right default value for base time? 18 | let base = val.time(c).unwrap_or(Duration::new(0, 0)); 19 | let inc = val.inc(c); 20 | let val = calc_time(base, inc); 21 | send_after(val, tx); 22 | }, 23 | } 24 | } 25 | 26 | fn send_after(delay: Duration, tx: SyncSender) { 27 | thread::sleep(delay); 28 | let _ = tx.send(TimeOut(())); 29 | } 30 | 31 | fn calc_time(base: Duration, inc: Duration) -> Duration { 32 | cmp::min( base / 40 + inc, base ) 33 | } 34 | -------------------------------------------------------------------------------- /timer/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate game; 2 | 3 | use std::time::Duration; 4 | use std::sync::mpsc::SyncSender; 5 | use std::thread; 6 | 7 | use game::{Color, NumMoves}; 8 | 9 | mod control; 10 | 11 | #[derive(Copy, Clone, Debug)] 12 | pub struct TimeOut(()); 13 | 14 | #[derive(Clone, Debug)] 15 | pub enum Timer { 16 | Remain(RemainData), 17 | Exact(Duration), 18 | Infinite, 19 | } 20 | impl Timer { 21 | pub fn new() -> Timer { 22 | // TODO what is the right default value for Timer? 23 | Timer::Infinite 24 | } 25 | pub fn start(self, c: Color, tx: SyncSender) { 26 | thread::spawn(move || control::start(self, c, tx)); 27 | } 28 | 29 | pub fn time(&mut self, c: Color, val: Duration) -> &mut Timer { 30 | self.force_time_left(); 31 | match *self { 32 | Timer::Remain(ref mut x) => x.set_time(c, val), 33 | _ => unreachable!(), 34 | } 35 | self 36 | } 37 | pub fn inc(&mut self, c: Color, val: Duration) -> &mut Timer { 38 | self.force_time_left(); 39 | match *self { 40 | Timer::Remain(ref mut x) => x.set_inc(c, val), 41 | _ => unreachable!(), 42 | } 43 | self 44 | } 45 | pub fn moves_to_go(&mut self, val: NumMoves) -> &mut Timer { 46 | self.force_time_left(); 47 | match *self { 48 | Timer::Remain(ref mut x) => x.moves_to_go = Some(val), 49 | _ => unreachable!(), 50 | } 51 | self 52 | } 53 | pub fn exact(&mut self, val: Duration) -> &mut Timer { 54 | *self = Timer::Exact(val); 55 | self 56 | } 57 | pub fn infinite(&mut self) -> &mut Timer { 58 | *self = Timer::Infinite; 59 | self 60 | } 61 | 62 | fn force_time_left(&mut self) { 63 | match *self { 64 | Timer::Remain(_) => {}, 65 | ref mut x => *x = Timer::Remain(RemainData::new()), 66 | } 67 | } 68 | } 69 | 70 | #[derive(Clone, Debug)] 71 | pub struct RemainData { 72 | w_time: Option, 73 | b_time: Option, 74 | w_inc: Duration, 75 | b_inc: Duration, 76 | moves_to_go: Option, 77 | } 78 | impl RemainData { 79 | fn new() -> RemainData { 80 | RemainData { 81 | w_time: None, 82 | b_time: None, 83 | w_inc: Duration::new(0, 0), 84 | b_inc: Duration::new(0, 0), 85 | moves_to_go: None, 86 | } 87 | } 88 | fn time(&self, c: Color) -> Option { 89 | match c { 90 | Color::White => self.w_time, 91 | Color::Black => self.b_time, 92 | } 93 | } 94 | fn set_time(&mut self, c: Color, val: Duration) { 95 | match c { 96 | Color::White => self.w_time = Some(val), 97 | Color::Black => self.b_time = Some(val), 98 | } 99 | } 100 | fn inc(&self, c: Color) -> Duration { 101 | match c { 102 | Color::White => self.w_inc, 103 | Color::Black => self.b_inc, 104 | } 105 | } 106 | fn set_inc(&mut self, c: Color, val: Duration) { 107 | match c { 108 | Color::White => self.w_inc = val, 109 | Color::Black => self.b_inc = val, 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /uci/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | 3 | name = "uci" 4 | version = "0.0.1" 5 | authors = ["Theemathas Chirananthavat "] 6 | 7 | [dependencies] 8 | time = "*" 9 | log = "*" 10 | 11 | [dependencies.game] 12 | path = "../game" 13 | 14 | [dependencies.search] 15 | path = "../search" 16 | 17 | [dependencies.timer] 18 | path = "../timer" 19 | -------------------------------------------------------------------------------- /uci/src/input.rs: -------------------------------------------------------------------------------- 1 | use std::io::BufRead; 2 | use std::sync::mpsc::SyncSender; 3 | 4 | use types::Cmd; 5 | use parse::parse; 6 | 7 | pub fn parse_input(input: R, tx: SyncSender) { 8 | for x in input.lines() { 9 | let s = x.ok().expect("cannot read input"); 10 | if let Some(cmd) = parse(&*s) { 11 | tx.send(cmd).ok().expect("parse_input tx is closed"); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /uci/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate time; 2 | #[macro_use] 3 | extern crate log; 4 | 5 | extern crate game; 6 | extern crate search; 7 | extern crate timer; 8 | 9 | pub use self::start::start; 10 | pub use self::types::{Cmd, Response, InfoParam}; 11 | 12 | mod types; 13 | mod state; 14 | 15 | mod start; 16 | mod input; 17 | mod output; 18 | mod parse; 19 | mod process; 20 | -------------------------------------------------------------------------------- /uci/src/output.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Write, LineWriter}; 2 | use std::sync::mpsc::{Receiver, SyncSender}; 3 | 4 | use search; 5 | 6 | use types::Response::{self, Info}; 7 | use InfoParam::{self, Depth, NodesSearched, PrincipalVariation}; 8 | 9 | pub fn format_output(output: W, rx: Receiver) { 10 | let mut output = LineWriter::new(output); 11 | for x in rx.iter() { 12 | writeln!(&mut output, "{}", x).ok().expect("cannot write to output"); 13 | } 14 | } 15 | 16 | pub fn engine_response_output(rx: Receiver, tx: SyncSender) { 17 | for search::Report { data, score, pv } in rx.iter() { 18 | tx.send(Info(vec![Depth(data.depth), NodesSearched(data.nodes)])).unwrap(); 19 | tx.send(Info(vec![InfoParam::Score(None, score), 20 | PrincipalVariation(pv)] 21 | )).unwrap(); 22 | 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /uci/src/parse.rs: -------------------------------------------------------------------------------- 1 | use std::iter::Peekable; 2 | use std::str::FromStr; 3 | use std::time::Duration; 4 | 5 | use game::{Position, FromTo, White, Black, NumPlies, NumMoves}; 6 | use search::NumNodes; 7 | use types::{Cmd, RegisterParam, GoParam}; 8 | use types::options; 9 | 10 | pub fn parse(s: &str) -> Option { 11 | let mut words = s.split(' '); 12 | 13 | let mut cmd_val: Option = None; 14 | 15 | while cmd_val.is_none() { 16 | let next_word_opt = words.next(); 17 | let next_word = match next_word_opt { 18 | Some(val) => val, 19 | None => break, 20 | }; 21 | 22 | cmd_val = match next_word { 23 | "uci" => Some(Cmd::Uci), 24 | "debug" => parse_on_off(&mut words).map(|val| Cmd::Debug(val)), 25 | "isready" => Some(Cmd::IsReady), 26 | "setoption" => parse_option_val(&mut words) 27 | .map(|x| Cmd::SetOption(x)), 28 | "register" => parse_register_vec(&mut words).map(|val| Cmd::Register(val)), 29 | "ucinewgame" => Some(Cmd::UciNewGame), 30 | "position" => parse_setup_position(&mut words) 31 | .map(|(pos, moves)| Cmd::SetupPosition(pos, moves)), 32 | "go" => parse_go_param_vec(&mut words).map(|val| Cmd::Go(val)), 33 | "stop" => Some(Cmd::Stop), 34 | "ponderhit" => Some(Cmd::PonderHit), 35 | "quit" => Some(Cmd::Quit), 36 | _ => None, 37 | } 38 | } 39 | 40 | if let Some(Cmd::Debug(val)) = cmd_val { 41 | // TODO set debug 42 | debug!("debug is now {:?}", val); 43 | } 44 | 45 | debug!("parse() returning {:?}", cmd_val); 46 | 47 | cmd_val 48 | } 49 | 50 | fn parse_on_off<'a, I>(mut words: I) -> Option 51 | where I: Iterator{ 52 | let mut ans = None; 53 | while ans.is_none() { 54 | let next_word_opt = words.next(); 55 | let next_word = match next_word_opt { 56 | Some(val) => val, 57 | None => break, 58 | }; 59 | ans = match next_word { 60 | "on" => Some(true), 61 | "off" => Some(false), 62 | _ => None, 63 | } 64 | } 65 | 66 | debug!("parse_on_off() returning {:?}", ans); 67 | 68 | ans 69 | } 70 | 71 | fn parse_option_val<'a, I>(words: I) -> Option 72 | where I: Iterator { 73 | debug!("Parsing option"); 74 | let ans = words.collect::>().join(" ").trim().parse().ok(); 75 | debug!("parse_option_val() returning {:?}", ans); 76 | ans 77 | } 78 | 79 | fn parse_register_vec<'a, I>(words: I) -> Option> 80 | where I: Iterator { 81 | let mut words = words.peekable(); 82 | let mut res = Vec::::new(); 83 | while let Some(next_word) = words.next() { 84 | let register_val = match next_word { 85 | "later" => Some(RegisterParam::Later), 86 | "name" => { 87 | let mut name_vec = Vec::<&str>::new(); 88 | loop { 89 | let curr_name = words.peek().and_then( |s| { 90 | if ["later", "name", "code"].contains(s) { None } 91 | else { Some(*s) } 92 | }); 93 | match curr_name { 94 | Some(val) => { 95 | name_vec.push(val); 96 | words.next(); 97 | }, 98 | None => break, 99 | } 100 | } 101 | Some(RegisterParam::Name(name_vec.join(" "))) 102 | }, 103 | "code" => words.next().map( |x| RegisterParam::Code(x.to_string())), 104 | _ => None, 105 | }; 106 | if let Some(val) = register_val { 107 | res.push(val); 108 | } 109 | } 110 | let ans = if res.is_empty() { None } else { Some(res) }; 111 | 112 | debug!("parse_register_vec() returning {:?}", ans); 113 | 114 | ans 115 | } 116 | 117 | fn parse_setup_position<'a, I>(mut words: I) -> Option<(Position, Vec)> 118 | where I: Iterator { 119 | let temp = parse_position(words.by_ref()); 120 | temp.map(|pos| { 121 | // consume everything up to and including "moves" 122 | let mut words = words.skip_while(|&s| s != "moves"); 123 | words.next(); 124 | 125 | // Attempt to parse the moves 126 | let moves = match parse_from_to_vec(&mut words.peekable()) { 127 | Some(val) => val, 128 | None => Vec::new(), 129 | }; 130 | 131 | (pos, moves) 132 | }) 133 | } 134 | 135 | fn parse_position<'a, I>(words: I) -> Option 136 | where I: Iterator { 137 | let mut words = words.peekable(); 138 | let ans = if words.peek() == Some(&"startpos") { 139 | debug!("parse_position() consumed \"startpos\""); 140 | words.next(); 141 | Some(Position::start()) 142 | } else if words.peek() == Some(&"fen") { 143 | words.next(); 144 | let six_words: Vec<_> = words.by_ref().take(6).collect(); 145 | debug!("parse_position(): six_words = {:?}", six_words); 146 | six_words.join(" ").parse::().ok() 147 | } else { 148 | None 149 | }; 150 | 151 | debug!("parse_position() returning {:?}", ans); 152 | 153 | ans 154 | } 155 | 156 | fn parse_from_to_vec<'a, I>(words: &mut Peekable) -> Option> 157 | where I: Iterator { 158 | let mut res = Vec::::new(); 159 | debug!("parse_from_to_vec() peeked at {:?}", words.peek()); 160 | while let Some(val) = words.peek().and_then(|val| FromStr::from_str(*val).ok()) { 161 | debug!("parse_from_to_vec(): val = {:?}", val); 162 | res.push(val); 163 | words.next(); 164 | debug!("parse_from_to_vec() peeked at {:?}", words.peek()); 165 | } 166 | let ans = if res.is_empty() { None } else { Some(res) }; 167 | 168 | debug!("parse_from_to_vec() returning {:?}", ans); 169 | 170 | ans 171 | } 172 | 173 | fn parse_go_param_vec<'a, I>(words: I) -> Option> 174 | where I: Iterator { 175 | let mut words = words.peekable(); 176 | let mut res = Vec::::new(); 177 | while let Some(next_word) = words.next() { 178 | match next_word { 179 | "searchmoves" => parse_from_to_vec(&mut words).map(|x| GoParam::SearchMoves(x)), 180 | "ponder" => Some(GoParam::Ponder), 181 | "wtime" => words.next().and_then(|s| s.parse::().ok()) 182 | .map(|x| GoParam::Time(White, 183 | Duration::from_millis(x))), 184 | "btime" => words.next().and_then(|s| s.parse::().ok()) 185 | .map(|x| GoParam::Time(Black, 186 | Duration::from_millis(x))), 187 | "winc" => words.next().and_then(|s| s.parse::().ok()) 188 | .map(|x| GoParam::IncTime(White, 189 | Duration::from_millis(x))), 190 | "binc" => words.next().and_then(|s| s.parse::().ok()) 191 | .map(|x| GoParam::IncTime(Black, 192 | Duration::from_millis(x))), 193 | "movestogo" => words.next().and_then(|s| s.parse::().ok()) 194 | .map(|x| GoParam::MovesToGo(NumMoves(x))), 195 | "depth" => words.next().and_then(|s| s.parse::().ok()) 196 | .map(|x| GoParam::Depth(NumPlies(x))), 197 | "nodes" => words.next().and_then(|s| s.parse::().ok()) 198 | .map(|x| GoParam::Nodes(NumNodes(x))), 199 | "mate" => words.next().and_then(|s| s.parse::().ok()) 200 | .map(|x| GoParam::Mate(NumMoves(x))), 201 | "movetime" => words.next().and_then(|s| s.parse::().ok()) 202 | .map(|x| GoParam::MoveTime(Duration::from_millis(x))), 203 | "infinite" => Some(GoParam::Infinite), 204 | _ => None, 205 | }.map(|val| res.push(val)); 206 | } 207 | let ans = if res.is_empty() { None } else { Some(res) }; 208 | 209 | debug!("parse_go_param_vec() returning {:?}", ans); 210 | 211 | ans 212 | } 213 | -------------------------------------------------------------------------------- /uci/src/process/go_param.rs: -------------------------------------------------------------------------------- 1 | use time::precise_time_ns; 2 | 3 | use game::Move; 4 | use types::GoParam; 5 | use state::State; 6 | 7 | pub fn setup(state: &mut State, mut data: Vec) { 8 | let ref mut search_state = state.search_state.as_mut() 9 | .expect("invalid search state"); 10 | let ref mut timer = state.timer; 11 | let ref mut param = search_state.param; 12 | let ref pos = search_state.pos; 13 | for go_param in data.drain(..) { 14 | match go_param { 15 | GoParam::SearchMoves(mut from_to_vec) => { 16 | let move_vec: Vec = from_to_vec.drain(..) 17 | .map(|x| x.to_move_with_pos(pos)) 18 | .collect(); 19 | param.search_moves = Some(move_vec); 20 | }, 21 | GoParam::Ponder => param.ponder = true, 22 | GoParam::Time(c, val) => { let _ = timer.time(c, val); }, 23 | GoParam::IncTime(c, val) => { let _ = timer.inc(c, val); }, 24 | GoParam::MovesToGo(val) => { let _ = timer.moves_to_go(val); }, 25 | GoParam::Depth(val) => param.depth = Some(val), 26 | GoParam::Nodes(val) => param.nodes = Some(val), 27 | GoParam::Mate(val) => param.mate = Some(val), 28 | GoParam::MoveTime(val) => { let _ = timer.exact(val); }, 29 | GoParam::Infinite => { let _ = timer.infinite(); }, 30 | } 31 | } 32 | state.start_search_time = Some(precise_time_ns()); 33 | if !param.ponder { 34 | state.start_move_time = state.start_search_time; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /uci/src/process/mod.rs: -------------------------------------------------------------------------------- 1 | use time::precise_time_ns; 2 | 3 | use std::sync::mpsc::{sync_channel, channel, SyncSender}; 4 | use std::thread; 5 | 6 | use search; 7 | use timer::Timer; 8 | use types::{Cmd, Response, ID_DATA}; 9 | use state::{State, Mode}; 10 | use output::engine_response_output; 11 | use types::options; 12 | 13 | use self::time_start::time_start; 14 | 15 | mod go_param; 16 | mod time_start; 17 | mod pos; 18 | 19 | pub fn process(state: &mut State, 20 | cmd: Cmd, 21 | output: &SyncSender, 22 | cmd_tx: &SyncSender) { 23 | match cmd { 24 | Cmd::Debug(val) => { 25 | // TODO set debug 26 | debug!("debug is now {:?}", val); 27 | state.search_tx.as_ref().map(|tx| { 28 | tx.send(search::Cmd::SetDebug(val)) 29 | .ok().expect("state.search_tx closed") } ); 30 | }, 31 | Cmd::IsReady => { 32 | output.send(Response::ReadyOk) 33 | .ok().expect("output channel closed"); 34 | }, 35 | Cmd::Register(..) => { 36 | // TODO register 37 | unimplemented!(); 38 | }, 39 | cmd => { 40 | match state.mode { 41 | Mode::Init => { 42 | if cmd == Cmd::Uci { 43 | for x in ID_DATA.iter() { 44 | output.send(Response::Id(x.clone())) 45 | .ok().expect("output channel closed"); 46 | } 47 | for x in &options::INFO { 48 | output.send(Response::ShowOption(x.clone())) 49 | .ok().expect("output channel closed"); 50 | } 51 | // TODO print option list 52 | output.send(Response::UciOk) 53 | .ok().expect("output channel closed"); 54 | state.mode = Mode::Wait; 55 | } 56 | }, 57 | Mode::Wait => { 58 | match cmd { 59 | Cmd::SetOption(val) => { 60 | // TODO maybe initialize 61 | state.options.set_value(val); 62 | }, 63 | Cmd::UciNewGame => { 64 | state.reset_new_game() 65 | }, 66 | Cmd::SetupPosition(pos, from_to_vec) => { 67 | if state.ucinewgame_support { 68 | pos::setup_same(state, pos, from_to_vec); 69 | state.mode = Mode::Ready; 70 | } else { 71 | process(state, Cmd::UciNewGame, output, cmd_tx); 72 | state.ucinewgame_support = false; 73 | process(state, Cmd::SetupPosition(pos, from_to_vec), 74 | output, cmd_tx); 75 | } 76 | }, 77 | _ => {}, 78 | } 79 | }, 80 | Mode::NewGame => { 81 | if let Cmd::SetupPosition(pos, from_to_vec) = cmd { 82 | pos::setup_new(state, pos, from_to_vec); 83 | state.mode = Mode::Ready; 84 | } 85 | }, 86 | Mode::Ready => { 87 | assert!(state.search_state.is_some()); 88 | if let Cmd::Go(param) = cmd { 89 | go_param::setup(state, param); 90 | let (search_tx, search_rx) = sync_channel::(0); 91 | let (response_tx, response_rx) = channel::(); 92 | let search_state = state.search_state.as_ref().unwrap().clone(); 93 | let output = output.clone(); 94 | let temp = thread::spawn(move || 95 | search::start(search_state, search_rx, response_tx)); 96 | thread::spawn(move || engine_response_output(response_rx, output)); 97 | 98 | state.search_tx = Some(search_tx); 99 | state.search_guard = Some(temp); 100 | 101 | if !state.search_state.as_ref().unwrap().param.ponder { 102 | time_start(state, cmd_tx.clone()); 103 | } 104 | 105 | state.mode = Mode::Search; 106 | } 107 | }, 108 | Mode::Search => { 109 | assert!(state.search_state.is_some()); 110 | assert!(state.search_guard.is_some()); 111 | assert!(state.search_tx.is_some()); 112 | match cmd { 113 | Cmd::PonderHit => { 114 | if !state.search_state.as_ref().unwrap().param.ponder { 115 | return; 116 | } 117 | state.search_tx.as_ref().unwrap().send(search::Cmd::PonderHit) 118 | .ok().expect("state.search_tx was closed"); 119 | state.search_state.as_mut().unwrap().param.ponder = false; 120 | state.start_move_time = Some(precise_time_ns()); 121 | time_start(state, cmd_tx.clone()); 122 | }, 123 | Cmd::Stop => { 124 | state.search_tx.as_ref().unwrap().send(search::Cmd::Stop) 125 | .ok().expect("state.search_tx was closed"); 126 | state.search_tx = None; 127 | state.start_search_time = None; 128 | state.start_move_time = None; 129 | state.timer = Timer::new(); 130 | state.mode = Mode::Wait; 131 | let search::BestMove(best_move, ponder_move) = 132 | state.search_guard.take().unwrap().join().unwrap(); 133 | output.send(Response::BestMove(best_move, ponder_move)).unwrap(); 134 | }, 135 | _ => {}, 136 | } 137 | }, 138 | } 139 | }, 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /uci/src/process/pos.rs: -------------------------------------------------------------------------------- 1 | use game::{Position, FromTo}; 2 | use search; 3 | use state::State; 4 | 5 | pub fn setup_new(state: &mut State, 6 | mut pos: Position, 7 | mut from_to_vec: Vec) { 8 | let mut prev_pos = None; 9 | let mut prev_move = None; 10 | for x in from_to_vec.drain(..) { 11 | let temp_move = x.to_move_with_pos(&pos); 12 | prev_pos = Some(pos.clone()); 13 | pos.make_move(&temp_move); 14 | prev_move = Some(temp_move); 15 | } 16 | state.search_state = Some(search::State { 17 | pos: pos, 18 | prev_pos: prev_pos, 19 | prev_move: prev_move, 20 | param: search::Param::new((state.options.hash_size*(1<<20)) as usize), 21 | }); 22 | } 23 | 24 | pub fn setup_same(state: &mut State, 25 | pos: Position, 26 | from_to_vec: Vec) { 27 | // TODO setup same game (currently just does new game) 28 | setup_new(state, pos, from_to_vec); 29 | } 30 | -------------------------------------------------------------------------------- /uci/src/process/time_start.rs: -------------------------------------------------------------------------------- 1 | use std::sync::mpsc::{sync_channel, SyncSender}; 2 | use std::thread; 3 | 4 | use timer::TimeOut; 5 | use state::State; 6 | use types::Cmd; 7 | 8 | pub fn time_start(state: &mut State, cmd_tx: SyncSender) { 9 | let c = state.search_state.as_ref() 10 | .expect("invalid search_state") 11 | .pos.side_to_move(); 12 | let (time_out_tx, time_out_rx) = sync_channel::(0); 13 | 14 | state.timer.clone().start(c, time_out_tx); 15 | thread::spawn(move || { 16 | let recv_res = time_out_rx.recv(); 17 | if recv_res.is_ok() { 18 | cmd_tx.send(Cmd::Stop).unwrap(); 19 | } 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /uci/src/start.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Read, BufReader, Write}; 2 | use std::thread; 3 | use std::sync::mpsc::sync_channel; 4 | 5 | use types::{Cmd, Response}; 6 | use process::process; 7 | use state::State; 8 | use input::parse_input; 9 | use output::format_output; 10 | 11 | pub fn start(input: R, output: W) where 12 | R: Read + Send + 'static, W: Write + Send + 'static { 13 | let mut state = State::new(); 14 | let (cmd_tx, cmd_rx) = sync_channel::(0); 15 | let temp = cmd_tx.clone(); 16 | let _input_guard = thread::spawn(move || parse_input(BufReader::new(input), temp)); 17 | let (resp_tx, resp_rx) = sync_channel::(0); 18 | let _output_guard = thread::spawn(move || format_output(output, resp_rx)); 19 | for cmd in cmd_rx.iter() { 20 | debug!("cmd = {:?}", cmd); 21 | if cmd == Cmd::Quit { 22 | return; 23 | } else { 24 | process(&mut state, cmd, &resp_tx, &cmd_tx); 25 | } 26 | debug!("state.mode = {:?}", state.mode); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /uci/src/state/mod.rs: -------------------------------------------------------------------------------- 1 | use std::thread::JoinHandle; 2 | use std::sync::mpsc::SyncSender; 3 | 4 | use search; 5 | use timer::Timer; 6 | use types::options; 7 | 8 | pub use self::mode::Mode; 9 | 10 | mod mode; 11 | 12 | pub struct State { 13 | pub search_state: Option, 14 | pub search_guard: Option>, 15 | pub search_tx: Option>, 16 | pub mode: Mode, 17 | pub start_search_time: Option, 18 | pub start_move_time: Option, 19 | pub timer: Timer, 20 | pub ucinewgame_support: bool, 21 | pub options: options::Data, 22 | } 23 | impl State { 24 | pub fn new() -> State { 25 | State { 26 | search_state: None, 27 | search_guard: None, 28 | search_tx: None, 29 | mode: Mode::new(), 30 | start_search_time: None, 31 | start_move_time: None, 32 | timer: Timer::new(), 33 | ucinewgame_support: false, 34 | options: options::Data::new(), 35 | } 36 | } 37 | pub fn reset_new_game(&mut self) { 38 | *self = State { 39 | mode: Mode::NewGame, 40 | ucinewgame_support: true, 41 | options: self.options.clone(), 42 | ..State::new() 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /uci/src/state/mode.rs: -------------------------------------------------------------------------------- 1 | // When starting program, start at state "Init" 2 | // Always allow commands "debug" and "isready" 3 | #[derive(PartialEq, Eq, Copy, Clone, Debug)] 4 | pub enum Mode { 5 | // * `uci` 6 | // send all "id" and "option" messages 7 | // send one "uciok" message 8 | // go to mode "Wait" 9 | Init, 10 | // * `setoption` 11 | // maybe initialize stuff 12 | // set the option 13 | // stay in mode Wait 14 | // * `ucinewgame` 15 | // reset status 16 | // take note that GUI supports the ucinewgame command 17 | // go to NewGame mode 18 | // * `position` 19 | // if GUI does not support ucinewgame, then: 20 | // simulate `ucinewgame` command 21 | // reprocess `position` command 22 | // set up position for same game 23 | // go to Ready mode 24 | Wait, 25 | // * `position` 26 | // set up position for new game 27 | // go to Ready mode 28 | NewGame, 29 | // * `go` 30 | // process the arguments 31 | // start searching 32 | // go to mode Search 33 | Ready, 34 | // * `ponderhit` 35 | // Execute the ponder move 36 | // stay in mode Search 37 | // * `stop` 38 | // * OR 39 | // * engine decides on best move 40 | // stop searching 41 | // send all "info" data/messages 42 | // send one "bestmove" message 43 | // stop search 44 | // go to mode Wait 45 | // * engine runs for a while 46 | // send all "info" data/messages 47 | Search, 48 | // For any mode: 49 | // 50 | // * `debug` 51 | // set debug as on or off 52 | // * `isready` 53 | // if mode is not Search, then: wait for everything to finish 54 | // send one "readyok" message 55 | } 56 | impl Mode { 57 | pub fn new() -> Mode { 58 | Mode::Init 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /uci/src/types/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use std::fmt; 4 | 5 | use game::{Position, Move, FromTo}; 6 | 7 | pub use self::param::{RegisterParam, GoParam, IdParam, InfoParam, ScoreType}; 8 | pub use self::param::ID_DATA; 9 | 10 | pub mod options; 11 | mod param; 12 | 13 | #[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Debug)] 14 | pub struct NumVariations(pub u32); 15 | 16 | #[derive(PartialEq, Eq, Copy, Clone, Debug)] 17 | pub struct PerMill(pub u32); 18 | 19 | #[derive(PartialEq, Eq, Copy, Clone, Debug)] 20 | pub struct NumCpu(pub u32); 21 | 22 | #[derive(PartialEq, Eq, Clone, Debug)] 23 | pub enum Cmd { 24 | Uci, 25 | Debug(bool), 26 | IsReady, 27 | SetOption(options::Value), 28 | Register(Vec), 29 | UciNewGame, 30 | SetupPosition(Position, Vec), 31 | Go(Vec), 32 | Stop, 33 | PonderHit, 34 | Quit, 35 | } 36 | 37 | #[derive(PartialEq, Eq, Clone, Debug)] 38 | pub enum Response { 39 | Id(IdParam), 40 | UciOk, 41 | ReadyOk, 42 | BestMove(Move, Option), 43 | CopyProtection(VertifyingState), 44 | Registration(VertifyingState), 45 | Info(Vec), 46 | ShowOption(options::Info), 47 | } 48 | impl fmt::Display for Response { 49 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 50 | match *self { 51 | Response::Id(ref val) => write!(f, "id {}", val), 52 | Response::UciOk => write!(f, "uciok"), 53 | Response::ReadyOk => write!(f, "readyok"), 54 | Response::BestMove(ref best, ref ponder) => { 55 | try!(write!(f, "bestmove {}", best)); 56 | if let Some(ref val) = ponder.as_ref() { try!(write!(f, " ponder {}", val)) } 57 | Ok(()) 58 | }, 59 | Response::CopyProtection(val) => write!(f, "copyprotection {}", val), 60 | Response::Registration(val) => write!(f, "registration {}", val), 61 | Response::Info(ref params) => { 62 | try!(write!(f, "info")); 63 | for x in params.iter() { try!(write!(f, " {}", x)) } 64 | Ok(()) 65 | }, 66 | Response::ShowOption(ref info) => write!(f, "option {}", *info) 67 | } 68 | } 69 | } 70 | 71 | #[derive(PartialEq, Eq, Copy, Clone, Debug)] 72 | pub enum VertifyingState { 73 | Checking, 74 | Ok, 75 | Error, 76 | } 77 | impl fmt::Display for VertifyingState { 78 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 79 | write!(f, "{}", match *self { 80 | VertifyingState::Checking => "checking", 81 | VertifyingState::Ok => "ok", 82 | VertifyingState::Error => "error", 83 | }) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /uci/src/types/options.rs: -------------------------------------------------------------------------------- 1 | // TODO enumerate options. 2 | 3 | use std::str::{FromStr, ParseBoolError}; 4 | use std::num::ParseIntError; 5 | use std::fmt::{self, Display, Formatter}; 6 | use std::string; 7 | 8 | #[derive(Debug)] 9 | pub struct ParseValueError(()); 10 | 11 | #[derive(Debug)] 12 | pub struct ParseNameError(()); 13 | 14 | impl From for ParseValueError { 15 | fn from(_: ParseNameError) -> Self { ParseValueError(()) } 16 | } 17 | impl From for ParseValueError { 18 | fn from(_: ParseIntError) -> Self { ParseValueError(()) } 19 | } 20 | impl From for ParseValueError { 21 | fn from(_: ParseBoolError) -> Self { ParseValueError(()) } 22 | } 23 | // Required since String implements FromStr with Error = () 24 | impl From for ParseValueError { 25 | fn from(_: string::ParseError) -> Self { ParseValueError(()) } 26 | } 27 | 28 | macro_rules! declare_type { 29 | ($name:ident, Check) => { pub type $name = bool; }; 30 | ($name:ident, Spin) => { pub type $name = i64; }; 31 | ($name:ident, Combo) => { pub type $name = u32; }; 32 | ($name:ident, Button) => { pub type $name = (); }; 33 | ($name:ident, String) => { pub type $name = String; }; 34 | } 35 | 36 | macro_rules! to_owned { 37 | ($value:expr, String) => { $value.to_string() }; 38 | ($value:expr, $kind:ident) => { $value }; 39 | } 40 | 41 | macro_rules! info_impl { 42 | ($name:ident : Check() = $default:expr) => { Info::Check(Name::$name, $default) }; 43 | ($name:ident : Spin($min:expr, $max:expr) = $default:expr) => { Info::Spin(Name::$name, $default, $min, $max) }; 44 | ($name:ident : Combo($($val:expr),+) = $default:expr) => { Info::Combo(Name::$name, $default, &[$($val,)+]) }; 45 | ($name:ident : Button() = $default:expr) => { Info::Button(Name::$name, $default) }; 46 | ($name:ident : String() = $default:expr) => { Info::String(Name::$name, $default) }; 47 | } 48 | 49 | macro_rules! parse_value { 50 | ($name:expr, Combo, $value_string:expr) => {{ 51 | let temp = $name as usize; // work around an ICE 52 | let combo_list: &[&'static str] = match INFO[temp] { 53 | Info::Combo(_, _, x) => &x, 54 | _ => unreachable!(), 55 | }; 56 | combo_list.iter().position(|x| *x == $value_string) 57 | .map_or(Err(ParseValueError(())), |x| Ok(x as u32)) 58 | }}; 59 | ($name:expr, Button, $value_string:expr) => { Ok::<(), ParseValueError>(()) }; 60 | ($name:expr, Spin, $value_string:expr) => {{ 61 | let val: i64 = try!($value_string.parse()); 62 | let temp = $name as usize; // work around an ICE 63 | let (min_val, max_val): (i64, i64) = match INFO[temp] { 64 | Info::Spin(_, _, x, y) => (x, y), 65 | _ => unreachable!(), 66 | }; 67 | if val >= min_val && val <= max_val { 68 | Ok(val) 69 | } else { 70 | Err(ParseValueError(())) 71 | } 72 | }}; 73 | ($name:expr, $kind:ident, $value_string:expr) => { 74 | $value_string.parse() 75 | }; 76 | } 77 | 78 | macro_rules! options_impl { 79 | (($num_opt:expr) options 80 | $( 81 | $name:ident ($field_name:ident, $str:expr) : $kind:ident ($($info:tt),*) = $default:expr, 82 | )+) => { 83 | 84 | mod type_of { 85 | $(declare_type!($name, $kind);)+ 86 | } 87 | 88 | #[derive(PartialEq, Eq, Clone, Debug)] 89 | pub enum Value { 90 | $($name(type_of::$name),)+ 91 | } 92 | 93 | #[derive(PartialEq, Eq, Copy, Clone, Debug)] 94 | pub enum Name { 95 | $($name,)+ 96 | } 97 | 98 | pub const INFO: [Info; $num_opt] = [$( 99 | info_impl!($name: $kind($($info),*) = $default), 100 | )+]; 101 | 102 | #[derive(Clone, Debug)] 103 | pub struct Data { 104 | $( 105 | pub $field_name: type_of::$name, 106 | )+ 107 | } 108 | impl Data { 109 | pub fn new() -> Data { 110 | Data { 111 | $( 112 | $field_name: to_owned!($default, $kind), 113 | )+ 114 | } 115 | } 116 | pub fn set_value(&mut self, val: Value) { 117 | match val { 118 | $( 119 | Value::$name(x) => self.$field_name = x, 120 | )+ 121 | } 122 | } 123 | } 124 | 125 | impl FromStr for Value { 126 | type Err = ParseValueError; 127 | fn from_str(s: &str) -> Result { 128 | // consume everything up to and including "name" 129 | let mut words = s.split(' ').skip_while(|&s| s != "name"); 130 | words.next(); 131 | 132 | let mut name_vec = Vec::<&str>::new(); 133 | let mut value_vec = Vec::<&str>::new(); 134 | 135 | let mut found_value = false; 136 | for curr_word in words { 137 | if found_value { 138 | value_vec.push(curr_word); 139 | } else { 140 | if curr_word == "value" { 141 | found_value = true; 142 | } else { 143 | name_vec.push(curr_word); 144 | } 145 | } 146 | } 147 | 148 | let name_string = name_vec.join(" ").trim().to_string(); 149 | let value_string = value_vec.join(" ").trim().to_string(); 150 | 151 | let name: Name = try!(name_string.parse()); 152 | Ok(match name { 153 | $( 154 | Name::$name => Value::$name(try!( 155 | parse_value!(Name::$name, $kind, value_string))), 156 | )+ 157 | }) 158 | } 159 | } 160 | 161 | impl FromStr for Name { 162 | type Err = ParseNameError; 163 | fn from_str(s: &str) -> Result { 164 | match s { 165 | $( 166 | $str => Ok(Name::$name), 167 | )+ 168 | _ => Err(ParseNameError(())), 169 | } 170 | } 171 | } 172 | 173 | impl Display for Name { 174 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { 175 | match *self { 176 | $( 177 | Name::$name => write!(f, $str), 178 | )+ 179 | } 180 | } 181 | } 182 | } 183 | } 184 | 185 | options_impl!{ 186 | (5) options 187 | Hash(hash_size, "hash"): Spin (1, 1024) = 1, 188 | TestCheck(test_check, "testcheck"): Check () = false, 189 | TestCombo(test_combo, "testcombo"): Combo ("foo", "bar", "baz") = 0, 190 | TestButton(test_button, "testbutton"): Button () = (), 191 | TestString(test_string, "teststring"): String () = "something", 192 | } 193 | 194 | #[derive(PartialEq, Eq, Copy, Clone, Debug)] 195 | pub enum Type { 196 | Check, 197 | Spin, 198 | Combo, 199 | Button, 200 | String, 201 | } 202 | 203 | // The second fields of each variant are the default values 204 | #[derive(PartialEq, Eq, Clone, Debug)] 205 | pub enum Info { 206 | Check(Name, bool), 207 | Spin(Name, i64, i64, i64), 208 | Combo(Name, u32, &'static[&'static str]), 209 | Button(Name, ()), 210 | String(Name, &'static str), 211 | } 212 | 213 | impl Display for Info { 214 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { 215 | match *self { 216 | Info::Check(name, default) => 217 | write!(f, "name {} type check default {}", name, default), 218 | Info::Spin(name, default, min, max) => 219 | write!(f, "name {} type spin default {} min {} max {}", name, default, min, max), 220 | Info::Combo(name, default, choices) => { 221 | try!(write!(f, "name {} type combo default {}", name, choices[default as usize])); 222 | for s in choices { 223 | try!(write!(f, " var {}", s)); 224 | } 225 | Ok(()) 226 | }, 227 | Info::Button(name, _) => 228 | write!(f, "name {} type button", name), 229 | Info::String(name, default) => 230 | write!(f, "name {} type string default {}", name, default), 231 | } 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /uci/src/types/param.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | use std::fmt; 3 | 4 | use game::{Move, FromTo, Color, Score, NumPlies, NumMoves}; 5 | use search::NumNodes; 6 | use types::{NumVariations, PerMill, NumCpu}; 7 | 8 | #[derive(PartialEq, Eq, Clone, Debug)] 9 | pub enum RegisterParam { 10 | Later, 11 | Name(String), 12 | Code(String), 13 | } 14 | 15 | #[derive(PartialEq, Eq, Clone, Debug)] 16 | pub enum GoParam { 17 | SearchMoves(Vec), 18 | Ponder, 19 | Time(Color, Duration), 20 | IncTime(Color, Duration), 21 | MovesToGo(NumMoves), 22 | Depth(NumPlies), 23 | Nodes(NumNodes), 24 | Mate(NumMoves), 25 | MoveTime(Duration), 26 | Infinite, 27 | } 28 | 29 | #[derive(PartialEq, Eq, Clone, Debug)] 30 | pub enum IdParam { 31 | Name(&'static str), 32 | Author(&'static str), 33 | } 34 | impl fmt::Display for IdParam { 35 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 36 | match *self { 37 | IdParam::Name(ref val) => write!(f, "name {}", val), 38 | IdParam::Author(ref val) => write!(f, "author {}", val), 39 | } 40 | } 41 | } 42 | 43 | pub const ID_DATA: [IdParam; 2] = [IdParam::Name("Binary Turk"), 44 | IdParam::Author("Theemathas Chirananthavat")]; 45 | 46 | #[derive(PartialEq, Eq, Clone, Debug)] 47 | pub enum InfoParam { 48 | Depth(NumPlies), 49 | SelDepth(NumPlies), 50 | TimeSearched(Duration), 51 | NodesSearched(NumNodes), 52 | PrincipalVariation(Vec), 53 | MultiPv(NumVariations), 54 | Score(Option, Score), 55 | CurrMove(Move), 56 | CurrMoveNumber(NumMoves), 57 | HashFull(PerMill), 58 | NodesPerSec(u64), 59 | TablebaseHits(u64), 60 | ShredderTablebaseHits(u64), 61 | CpuLoad(PerMill), 62 | ShowString(String), 63 | Refutation(Vec), 64 | CurrLine(Option, Vec), 65 | } 66 | impl fmt::Display for InfoParam { 67 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 68 | match *self { 69 | InfoParam::Depth(val) => write!(f, "depth {}", val.0), 70 | InfoParam::SelDepth(val) => write!(f, "seldepth {}", val.0), 71 | InfoParam::TimeSearched(val) => write!(f, "time {}", val.as_secs() as u32 * 1000 + 72 | val.subsec_nanos() / 1000000), 73 | InfoParam::NodesSearched(val) => write!(f, "nodes {}", val.0), 74 | InfoParam::PrincipalVariation(ref moves) => { 75 | try!(write!(f, "pv")); 76 | for x in moves.iter() { try!(write!(f, " {}", x)) } 77 | Ok(()) 78 | }, 79 | InfoParam::MultiPv(val) => write!(f, "multipv {}", val.0), 80 | InfoParam::Score(score_type, val) => { 81 | try!(write!(f, "score {}", val)); 82 | if let Some(x) = score_type { try!(write!(f, " {}", x)) } 83 | Ok(()) 84 | }, 85 | InfoParam::CurrMove(ref val) => write!(f, "currmove {}", val), 86 | InfoParam::CurrMoveNumber(val) => write!(f, "currmovenumber {}", val.0), 87 | InfoParam::HashFull(val) => write!(f, "hashfull {}", val.0), 88 | InfoParam::NodesPerSec(val) => write!(f, "nps {}", val), 89 | InfoParam::TablebaseHits(val) => write!(f, "tbhits {}", val), 90 | InfoParam::ShredderTablebaseHits(val) => write!(f, "sbhits {}", val), 91 | InfoParam::CpuLoad(val) => write!(f, "cpuload {}", val.0), 92 | InfoParam::ShowString(ref val) => write!(f, "string {}", val), 93 | InfoParam::Refutation(ref moves) => { 94 | try!(write!(f, "refutation")); 95 | for x in moves.iter() { try!(write!(f, " {}", x)) } 96 | Ok(()) 97 | }, 98 | InfoParam::CurrLine(num_cpu, ref moves) => { 99 | try!(write!(f, "currline")); 100 | if let Some(x) = num_cpu { try!(write!(f, " {}", x.0)) } 101 | for x in moves.iter() { try!(write!(f, " {}", x)) } 102 | Ok(()) 103 | }, 104 | } 105 | } 106 | } 107 | 108 | #[derive(PartialEq, Eq, Copy, Clone, Debug)] 109 | pub enum ScoreType { 110 | LowerBound, 111 | UpperBound, 112 | } 113 | impl fmt::Display for ScoreType { 114 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 115 | write!(f, "{}", match *self { 116 | ScoreType::LowerBound => "lowerbound", 117 | ScoreType::UpperBound => "upperbound", 118 | }) 119 | } 120 | } 121 | --------------------------------------------------------------------------------