├── .gitignore ├── src ├── lib.rs ├── color.rs ├── piece_type.rs ├── piece.rs ├── square.rs ├── main.rs ├── _move.rs ├── bitboard.rs ├── search.rs ├── zobrist.rs ├── constants.rs └── position.rs ├── Cargo.toml ├── README.md ├── LICENSE ├── benches └── find_move.rs └── ccr.csv /.gitignore: -------------------------------------------------------------------------------- 1 | # Swap files 2 | *.swp 3 | 4 | # Compiled files 5 | *.o 6 | *.so 7 | *.rlib 8 | *.dll 9 | 10 | # Executables 11 | *.exe 12 | 13 | # Generated by Cargo 14 | /target/ 15 | 16 | # Lock files 17 | *.lock 18 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(asm)] 2 | #![feature(box_syntax)] 3 | 4 | pub mod bitboard; 5 | pub mod color; 6 | pub mod constants; 7 | pub mod _move; 8 | pub mod piece; 9 | pub mod piece_type; 10 | pub mod position; 11 | pub mod search; 12 | pub mod square; 13 | pub mod zobrist; 14 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | 3 | name = "crust" 4 | version = "0.1.0" 5 | authors = [ "nicholasmeyer@gmail.com" ] 6 | 7 | [[bin]] 8 | 9 | name = "crust" 10 | path = "src/main.rs" 11 | 12 | [lib] 13 | 14 | name = "crust" 15 | path = "src/lib.rs" 16 | bench = true 17 | 18 | [dependencies] 19 | time = "*" 20 | -------------------------------------------------------------------------------- /src/color.rs: -------------------------------------------------------------------------------- 1 | #[derive(PartialEq, Eq, Show, Copy)] 2 | pub struct Color(pub u8); 3 | 4 | pub const WHITE : Color = Color(0); 5 | pub const BLACK : Color = Color(1); 6 | 7 | pub fn to_int (Color(color) : Color) -> u8 { 8 | return color; 9 | } 10 | 11 | pub fn to_idx (Color(color) : Color) -> usize { 12 | return color as usize; 13 | } 14 | 15 | pub fn flip (Color(color) : Color) -> Color { 16 | return Color(color ^ 1); 17 | } 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Crust 2 | 3 | ## Overview 4 | Crust is a simple Chess engine written in [Rust][rust-home]. 5 | 6 | [rust-home]: http://rust-lang.org 7 | 8 | ## Features 9 | * Bitboard Based Board Representation 10 | * Magic Bitboards for Sliding Piece Move Generation 11 | * Alpha-Beta Search 12 | * Iterative Deepening 13 | * Quiescence Search 14 | * Transposition Table 15 | * Aspiration Windows 16 | * Killer Move Heuristic 17 | * Material and Piece-Square Based Evaluation 18 | 19 | ## Planned 20 | * Static Exchange Evaluation 21 | * Mobility and Pawn Structure Evaluation Terms 22 | * Negascout/PVS 23 | * Null Move Pruning 24 | 25 | ## Build 26 | Simply run `cargo build --release && ./target/release/crust`. To run a simple benchmark, run `cargo bench` (this may take a little while). 27 | -------------------------------------------------------------------------------- /src/piece_type.rs: -------------------------------------------------------------------------------- 1 | #[derive(PartialEq, Eq, Show, Copy)] 2 | pub struct PieceType(pub u8); 3 | 4 | pub const NO_PIECE_TYPE : PieceType = PieceType(0); 5 | pub const PAWN : PieceType = PieceType(1); 6 | pub const KNIGHT : PieceType = PieceType(2); 7 | pub const BISHOP : PieceType = PieceType(3); 8 | pub const ROOK : PieceType = PieceType(4); 9 | pub const QUEEN : PieceType = PieceType(5); 10 | pub const KING : PieceType = PieceType(6); 11 | 12 | static PIECE_TYPE_CHARS : [char; 7] = [' ', 'p', 'n', 'b', 'r', 'q', 'k']; 13 | static CHAR_PIECE_TYPES : [Option; 26] = 14 | [None, Some(BISHOP), None, None, None, None, None, None, None, None, Some(KING), None, None, 15 | Some(KNIGHT), None, Some(PAWN), Some(QUEEN), Some(ROOK), None, None, None, None, None, None, None, None]; 16 | 17 | pub fn to_char (PieceType(piece_type): PieceType) -> char { 18 | PIECE_TYPE_CHARS[piece_type as usize] 19 | } 20 | 21 | // from a lowercase char 22 | pub fn from_char (c: char) -> Option { 23 | let idx = c as u8 - 97; 24 | if idx > 25 { return None } 25 | return CHAR_PIECE_TYPES[idx as usize]; 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Nicholas Meyer 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /benches/find_move.rs: -------------------------------------------------------------------------------- 1 | extern crate test; 2 | extern crate crust; 3 | 4 | use test::Bencher; 5 | 6 | #[bench] 7 | fn bench_find_move(b: &mut Bencher) { 8 | use std::io; 9 | use std::io::{BufferedReader, File}; 10 | 11 | use crust::position::Position; 12 | use crust::search::Searcher; 13 | 14 | use crust::constants; 15 | use crust::zobrist; 16 | 17 | // Initialize various constants, including magic bitboards 18 | constants::init(); 19 | // Initialize zobrist keys 20 | zobrist::init(); 21 | 22 | let board = Position::new(); 23 | let mut reader = io::stdin(); 24 | let mut searcher = Searcher::new(board); 25 | 26 | b.iter(|| { 27 | let path = Path::new("ccr.csv"); 28 | let mut file = BufferedReader::new(File::open(&path)); 29 | for line in file.lines() { 30 | match line { 31 | Ok(r) => { 32 | let pos = Position::from_fen(r.as_slice()); 33 | let mut search = Searcher::new(pos); 34 | search.search_depth(3, false); 35 | }, 36 | _ => () 37 | } 38 | } 39 | }); 40 | } 41 | -------------------------------------------------------------------------------- /src/piece.rs: -------------------------------------------------------------------------------- 1 | use std::char; 2 | 3 | use color::{Color, WHITE, BLACK}; 4 | use piece_type; 5 | use piece_type::PieceType; 6 | 7 | #[derive(PartialEq, Eq, Show, Copy)] 8 | pub struct Piece(pub u8); 9 | 10 | pub static NP : Piece = Piece(0); // No Piece 11 | 12 | pub static WP : Piece = Piece(1); 13 | pub static WN : Piece = Piece(2); 14 | pub static WB : Piece = Piece(3); 15 | pub static WR : Piece = Piece(4); 16 | pub static WQ : Piece = Piece(5); 17 | pub static WK : Piece = Piece(6); 18 | 19 | pub static BP : Piece = Piece(9); 20 | pub static BN : Piece = Piece(10); 21 | pub static BB : Piece = Piece(11); 22 | pub static BR : Piece = Piece(12); 23 | pub static BQ : Piece = Piece(13); 24 | pub static BK : Piece = Piece(14); 25 | 26 | impl Piece { 27 | pub fn new (PieceType(t): PieceType, Color(c): Color) -> Piece { 28 | return Piece(((c << 3) | t) as u8); 29 | } 30 | } 31 | 32 | pub fn to_type (Piece(piece): Piece) -> PieceType { 33 | PieceType(piece & 7) 34 | } 35 | 36 | pub fn to_color (Piece(piece): Piece) -> Color { 37 | Color((piece & 8) >> 3) 38 | } 39 | 40 | pub fn to_char (piece: Piece) -> char { 41 | let type_char = piece_type::to_char(to_type(piece)); 42 | match to_color(piece) { 43 | WHITE => type_char.to_uppercase(), 44 | BLACK => type_char, 45 | _ => ' ' 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /ccr.csv: -------------------------------------------------------------------------------- 1 | rnbqkb1r/ppp1pppp/5n2/8/3PP3/2N5/PP3PPP/R1BQKBNR b KQkq - 3 5 2 | rnbq1rk1/pppp1ppp/4pn2/8/1bPP4/P1N5/1PQ1PPPP/R1B1KBNR b KQ - 1 5 3 | r4rk1/3nppbp/bq1p1np1/2pP4/8/2N2NPP/PP2PPB1/R1BQR1K1 b - - 1 12 4 | rn1qkb1r/pb1p1ppp/1p2pn2/2p5/2PP4/5NP1/PP2PPBP/RNBQK2R w KQkq c6 1 6 5 | r1bq1rk1/1pp2pbp/p1np1np1/3Pp3/2P1P3/2N1BP2/PP4PP/R1NQKB1R b KQ - 1 9 6 | rnbqr1k1/1p3pbp/p2p1np1/2pP4/4P3/2N5/PP1NBPPP/R1BQ1RK1 w - - 1 11 7 | rnbqkb1r/pppp1ppp/5n2/4p3/4PP2/2N5/PPPP2PP/R1BQKBNR b KQkq f3 1 3 8 | r1bqk1nr/pppnbppp/3p4/8/2BNP3/8/PPP2PPP/RNBQK2R w KQkq - 2 6 9 | rnbq1b1r/ppp2kpp/3p1n2/8/3PP3/8/PPP2PPP/RNBQKB1R b KQ d3 1 5 10 | rnbqkb1r/pppp1ppp/3n4/8/2BQ4/5N2/PPP2PPP/RNB2RK1 b kq - 1 6 11 | r2q1rk1/2p1bppp/p2p1n2/1p2P3/4P1b1/1nP1BN2/PP3PPP/RN1QR1K1 w - - 1 12 12 | r1bqkb1r/2pp1ppp/p1n5/1p2p3/3Pn3/1B3N2/PPP2PPP/RNBQ1RK1 b kq - 2 7 13 | r2qkbnr/2p2pp1/p1pp4/4p2p/4P1b1/5N1P/PPPP1PP1/RNBQ1RK1 w kq - 1 8 14 | r1bqkb1r/pp3ppp/2np1n2/4p1B1/3NP3/2N5/PPP2PPP/R2QKB1R w KQkq e6 1 7 15 | rn1qk2r/1b2bppp/p2ppn2/1p6/3NP3/1BN5/PPP2PPP/R1BQR1K1 w kq - 5 10 16 | r1b1kb1r/1pqpnppp/p1n1p3/8/3NP3/2N1B3/PPP1BPPP/R2QK2R w KQkq - 3 8 17 | r1bqnr2/pp1ppkbp/4N1p1/n3P3/8/2N1B3/PPP2PPP/R2QK2R b KQ - 2 11 18 | r3kb1r/pp1n1ppp/1q2p3/n2p4/3P1Bb1/2PB1N2/PPQ2PPP/RN2K2R w KQkq - 3 11 19 | r1bq1rk1/pppnnppp/4p3/3pP3/1b1P4/2NB3N/PPP2PPP/R1BQK2R w KQ - 3 7 20 | r2qkbnr/ppp1pp1p/3p2p1/3Pn3/4P1b1/2N2N2/PPP2PPP/R1BQKB1R w KQkq - 2 6 21 | rn2kb1r/pp2pppp/1qP2n2/8/6b1/1Q6/PP1PPPBP/RNB1K1NR b KQkq - 1 6 22 | rn1qkb1r/pp2pppp/5n2/3p1b2/3P4/2N1P3/PP3PPP/R1BQKBNR w KQkq - 0 1 23 | rn1qkb1r/pp2pppp/5n2/3p1b2/3P4/1QN1P3/PP3PPP/R1B1KBNR b KQkq - 1 1 24 | r1bqk2r/ppp2ppp/2n5/4P3/2Bp2n1/5N1P/PP1N1PP1/R2Q1RK1 b kq - 1 10 25 | r1bqrnk1/pp2bp1p/2p2np1/3p2B1/3P4/2NBPN2/PPQ2PPP/1R3RK1 w - - 1 12 26 | -------------------------------------------------------------------------------- /src/square.rs: -------------------------------------------------------------------------------- 1 | use std::ops; 2 | use std::num::SignedInt; 3 | use std::fmt; 4 | use std::cmp::Ordering::{self, Less, Greater}; 5 | 6 | #[derive(PartialOrd, Eq, PartialEq, Copy)] 7 | pub struct Square(pub u8); 8 | impl Square { 9 | pub fn new (rank:u8, file:u8) -> Square { 10 | Square(file | (rank << 3)) 11 | } 12 | } 13 | 14 | impl ops::Add for Square { 15 | type Output = Square; 16 | 17 | fn add (self, rhs: u8) -> Square { 18 | let Square(lhs) = self; 19 | return Square(lhs + rhs); 20 | } 21 | } 22 | 23 | impl ops::Sub for Square { 24 | type Output = Square; 25 | 26 | fn sub (self, rhs: u8) -> Square { 27 | let Square(lhs) = self; 28 | return Square(lhs - rhs); 29 | } 30 | } 31 | 32 | impl ops::Rem for Square { 33 | type Output = Square; 34 | 35 | fn rem (self, rhs: u8) -> Square { 36 | let Square(lhs) = self; 37 | return Square(lhs % rhs); 38 | } 39 | } 40 | 41 | impl Ord for Square { 42 | fn cmp(&self, &other: &Square) -> Ordering { 43 | if file(*self) < file(other) { 44 | return Less; 45 | } else if file(*self) == file(other) { 46 | return rank(*self).cmp(&rank(other)); 47 | } else { 48 | return Greater; 49 | } 50 | } 51 | } 52 | 53 | impl fmt::Show for Square { 54 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 55 | if (*self) == NULL { 56 | return write!(f, "NULL"); 57 | } 58 | return write!(f, "{}{}", (file(*self) + 97) as u8 as char, rank(*self) + 1); 59 | } 60 | } 61 | 62 | pub fn from_str (s: &str) -> Option { 63 | if s.len() != 2 { return None } 64 | let chars: Vec = s.chars().collect(); 65 | let rank = chars[1] as u8 - 49; 66 | let file = chars[0] as u8 - 97; 67 | if rank > 7 || file > 7 { return None } 68 | return Some(Square::new(rank as u8, file as u8)); 69 | } 70 | 71 | pub fn abs_diff (Square(s1): Square, Square(s2): Square) -> u8 { 72 | return SignedInt::abs(s1 as i8 - s2 as i8) as u8; 73 | } 74 | 75 | pub fn to_int (Square(s): Square) -> u8 { 76 | return s; 77 | } 78 | 79 | pub fn file (Square(s): Square) -> u8 { 80 | return s & 0x7; 81 | } 82 | 83 | pub fn rank (Square(s): Square) -> u8 { 84 | return (s >> 3) & 0x7; 85 | } 86 | 87 | pub static NULL : Square = Square(64); 88 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![feature(asm)] 2 | #![feature(box_syntax)] 3 | 4 | mod bitboard; 5 | mod color; 6 | mod constants; 7 | mod _move; 8 | mod piece; 9 | mod piece_type; 10 | mod position; 11 | mod search; 12 | mod square; 13 | mod zobrist; 14 | 15 | fn main() { 16 | use std::collections::HashMap; 17 | use std::io; 18 | 19 | use constants; 20 | use _move; 21 | use position::{Position, perft}; 22 | use search::Searcher; 23 | use zobrist; 24 | 25 | // Initialize various constants, including magic bitboards 26 | constants::init(); 27 | // Initialize zobrist keys 28 | zobrist::init(); 29 | 30 | let board = Position::new(); 31 | let mut perftboard = Position::new(); 32 | println!("{}", perft(&mut perftboard, 6)); 33 | let mut reader = io::stdin(); 34 | let mut searcher = Searcher::new(board); 35 | 36 | loop { 37 | let mut user_move; 38 | let available_moves = searcher.pos.gen_moves(false); 39 | println!("Available Moves: {:?}", available_moves); 40 | let mut move_map = HashMap::new(); 41 | for _move in available_moves.iter() { 42 | move_map.insert(_move::to_int(*_move) & 0xfff, _move); 43 | } 44 | let pinned = searcher.pos.pinned_pieces(); 45 | let checkers = searcher.pos.checkers(); 46 | loop { 47 | print!("Enter move: "); 48 | let input = reader.read_line().ok().unwrap(); 49 | match _move::from_str(input.as_slice().trim()) { 50 | Some(m) => { 51 | match move_map.contains_key(&(_move::to_int(m) & 0xfff)) { 52 | true => { 53 | let actual_move = *move_map[(_move::to_int(m) & 0xfff)]; 54 | if searcher.pos.move_is_legal(actual_move, pinned, checkers) { 55 | user_move = actual_move; 56 | break; 57 | } else { 58 | println!("Illegal move") 59 | } 60 | } 61 | false => println!("Illegal move") 62 | } 63 | } 64 | None => println!("Invalid move format") 65 | } 66 | } 67 | searcher.pos.make_move(user_move); 68 | let _move = searcher.search(0.75, true); 69 | if _move == _move::NULL { 70 | print!("game over"); 71 | break; 72 | } else { 73 | searcher.pos.make_move(_move); 74 | println!("Computer move: {:?}", _move); 75 | println!("all nodes: {}\nquiesce nodes: {}", searcher.node_count, searcher.quiescent_node_count); 76 | print!("{:?}", searcher.pos); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/_move.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use square; 4 | use square::Square; 5 | use piece_type::PieceType; 6 | use std::cmp::Ordering::{self, Less, Equal, Greater}; 7 | 8 | pub const NULL : Move = Move(0); 9 | 10 | #[derive(Eq, Hash, PartialOrd, Copy, Clone)] 11 | pub struct Move(pub u64); 12 | 13 | impl Move { 14 | #[inline(always)] 15 | pub fn new (Square(from):Square, Square(to):Square, attack: u64) -> Move { 16 | Move(to as u64 | (from as u64) << 6 | (attack & 0x1) << 16) 17 | } 18 | #[inline(always)] 19 | pub fn new_castle(Square(from):Square, Square(to):Square) -> Move { 20 | Move(to as u64 | (from as u64) << 6 | (1 << 12)) 21 | } 22 | #[inline(always)] 23 | pub fn new_promotion(Square(from):Square, Square(to):Square, PieceType(p):PieceType, attack: u64) -> Move { 24 | Move(to as u64 | (from as u64) << 6 | ((p as u64 & 0x7) << 13) | ((attack & 0x1) << 16)) 25 | } 26 | 27 | pub fn set_score(&mut self, score: i16) -> () { 28 | let Move(m) = *self; 29 | *self = Move(m | ((score as i16 as u16 as u64) << 17)); 30 | } 31 | 32 | pub fn get_score(&self) -> i16 { 33 | let Move(m) = *self; 34 | return ((m >> 17) & 0xffff) as u16 as i16; 35 | } 36 | } 37 | 38 | impl PartialEq for Move { 39 | fn eq(&self, &Move(other):&Move) -> bool { 40 | let Move(m) = *self; 41 | return m & 0x1fff == other & 0x1fff; 42 | } 43 | fn ne(&self, &Move(other):&Move) -> bool { 44 | let Move(m) = *self; 45 | return m & 0x1fff != other & 0x1fff; 46 | } 47 | } 48 | 49 | impl Ord for Move { 50 | fn cmp(&self, other: &Move) -> Ordering { 51 | match get_from(*self).cmp(&get_from(*other)) { 52 | Less => Less, 53 | Equal => get_to(*self).cmp(&get_to(*other)), 54 | Greater => Greater 55 | } 56 | } 57 | } 58 | 59 | impl fmt::Show for Move { 60 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 61 | if (*self) == NULL { 62 | return write!(f, "NULL"); 63 | } 64 | return write!(f, "{:?}{:?}", get_from(*self), get_to(*self)); 65 | } 66 | } 67 | 68 | pub fn from_str (s: &str) -> Option { 69 | use piece_type; 70 | match s.len() { 71 | 4 => { 72 | let from: &str = s.slice(0,2); 73 | let to: &str = s.slice(2,4); 74 | match (square::from_str(from), square::from_str(to)) { 75 | // TODO: Do this better 76 | (Some(f), Some(t)) => Some(Move::new(f, t, 0)), 77 | _ => None 78 | } 79 | }, 80 | 5 => { 81 | let from: &str = s.slice(0,2); 82 | let to: &str = s.slice(2,4); 83 | let promotion: char = s.char_at(4).to_lowercase(); 84 | match (square::from_str(from), square::from_str(to), piece_type::from_char(promotion)) { 85 | (Some(f), Some(t), Some(p)) => { 86 | Some(Move::new_promotion(f, t, p, 0)) 87 | } 88 | _ => None 89 | } 90 | }, 91 | _ => None 92 | } 93 | } 94 | 95 | pub fn to_int (Move(m): Move) -> u64 { 96 | return m; 97 | } 98 | 99 | pub fn get_to (Move(m): Move) -> Square { 100 | Square((m & 0x3f) as u8) 101 | } 102 | 103 | pub fn get_from (Move(m): Move) -> Square { 104 | Square(((m >> 6) & 0x3f) as u8) 105 | } 106 | 107 | pub fn get_promotion (Move(m): Move) -> PieceType { 108 | PieceType(((m >> 13) & 7) as u8) 109 | } 110 | 111 | pub fn is_castle (Move(m): Move) -> bool { 112 | (m >> 12) & 1 > 0 113 | } 114 | 115 | pub fn is_attack (Move(m): Move) -> bool { 116 | (m >> 16) & 1 > 0 117 | } 118 | -------------------------------------------------------------------------------- /src/bitboard.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::iter::range_step; 3 | use std::ops; 4 | 5 | use constants; 6 | use piece::Piece; 7 | use square::Square; 8 | 9 | #[derive(PartialEq, Eq, Copy)] 10 | pub struct BitBoard(pub u64); 11 | 12 | mod test { 13 | use bitboard::{self, BitBoard}; 14 | use square::Square; 15 | 16 | #[test] 17 | fn bit_scan_forward() { 18 | assert!(bitboard::bit_scan_forward(BitBoard(0x01)) == Square(0)); 19 | assert!(bitboard::bit_scan_forward(BitBoard(0x10)) == Square(4)); 20 | assert!(bitboard::bit_scan_forward(BitBoard(0x1000)) == Square(12)); 21 | } 22 | 23 | #[test] 24 | fn popcnt() { 25 | assert!(bitboard::popcnt(BitBoard(0x01)) == 1); 26 | assert!(bitboard::popcnt(BitBoard(0xff0)) == 8); 27 | assert!(bitboard::popcnt(BitBoard(0x1f00)) == 5); 28 | } 29 | 30 | #[test] 31 | fn is_bit_set() { 32 | assert!(bitboard::is_bit_set(BitBoard(0x01), Square(0)) == true); 33 | assert!(bitboard::is_bit_set(BitBoard(0xff0), Square(5)) == true); 34 | assert!(bitboard::is_bit_set(BitBoard(0x1f00), Square(3)) == false); 35 | } 36 | 37 | #[test] 38 | fn clear_bit() { 39 | let mut b1 = &mut BitBoard(0x01); 40 | bitboard::clear_bit(b1, Square(0)); 41 | assert!(*b1 == BitBoard(0x0)); 42 | 43 | let mut b2 = &mut BitBoard(0xff0); 44 | bitboard::clear_bit(b2, Square(0)); 45 | assert!(*b2 == BitBoard(0xff0)); 46 | 47 | let mut b3 = &mut BitBoard(0x1f00); 48 | bitboard::clear_bit(b3, Square(8)); 49 | assert!(*b3 == BitBoard(0x1e00)); 50 | } 51 | 52 | #[test] 53 | fn set_bit() { 54 | let mut b1 = &mut BitBoard(0x01); 55 | bitboard::set_bit(b1, Square(0)); 56 | assert!(*b1 == BitBoard(0x1)); 57 | 58 | let mut b2 = &mut BitBoard(0xff0); 59 | bitboard::set_bit(b2, Square(0)); 60 | assert!(*b2 == BitBoard(0xff1)); 61 | 62 | let mut b3 = &mut BitBoard(0x1f00); 63 | bitboard::set_bit(b3, Square(8)); 64 | assert!(*b3 == BitBoard(0x1f00)); 65 | } 66 | } 67 | 68 | impl ops::BitAnd for BitBoard { 69 | type Output = BitBoard; 70 | 71 | fn bitand (self, BitBoard(rhs) : BitBoard) -> BitBoard { 72 | let BitBoard(lhs) = self; 73 | return BitBoard(lhs & rhs); 74 | } 75 | } 76 | 77 | impl ops::BitOr for BitBoard { 78 | type Output = BitBoard; 79 | 80 | fn bitor (self, BitBoard(rhs) : BitBoard) -> BitBoard { 81 | let BitBoard(lhs) = self; 82 | return BitBoard(lhs | rhs); 83 | } 84 | } 85 | 86 | impl ops::BitXor for BitBoard { 87 | type Output = BitBoard; 88 | 89 | fn bitxor (self, BitBoard(rhs) : BitBoard) -> BitBoard { 90 | let BitBoard(lhs) = self; 91 | return BitBoard(lhs ^ rhs); 92 | } 93 | } 94 | 95 | impl ops::Shr for BitBoard { 96 | type Output = BitBoard; 97 | 98 | fn shr (self, rhs : usize) -> BitBoard { 99 | let BitBoard(lhs) = self; 100 | return BitBoard(lhs >> rhs); 101 | } 102 | } 103 | 104 | impl ops::Shl for BitBoard { 105 | type Output = BitBoard; 106 | 107 | fn shl (self, rhs : usize) -> BitBoard { 108 | let BitBoard(lhs) = self; 109 | return BitBoard(lhs << rhs); 110 | } 111 | } 112 | 113 | impl ops::Sub for BitBoard { 114 | type Output = BitBoard; 115 | 116 | fn sub (self, BitBoard(rhs) : BitBoard) -> BitBoard { 117 | let BitBoard(lhs) = self; 118 | return BitBoard(lhs - rhs); 119 | } 120 | } 121 | 122 | impl ops::Mul for BitBoard { 123 | type Output = BitBoard; 124 | 125 | fn mul (self, rhs : u64) -> BitBoard { 126 | let BitBoard(lhs) = self; 127 | return BitBoard(lhs * rhs); 128 | } 129 | } 130 | 131 | impl ops::Not for BitBoard { 132 | type Output = BitBoard; 133 | 134 | fn not (self) -> BitBoard { 135 | let BitBoard(b) = self; 136 | return BitBoard(!b); 137 | } 138 | } 139 | 140 | #[inline(always)] 141 | pub fn clear_lsb(BitBoard(b) : BitBoard) -> BitBoard { 142 | return BitBoard(b & (b - 1)); 143 | } 144 | 145 | pub fn filter_pieces bool>(pieces : [Piece; 64], f:F) -> BitBoard { 146 | let mut b = BitBoard(0); 147 | for x in pieces.iter() { 148 | b = b >> 1; 149 | b = b | BitBoard(if f(*x) { 1 << 63 } else { 0 }); 150 | } 151 | return b; 152 | } 153 | 154 | pub const NOT_FILE_A : BitBoard = BitBoard(0xfefefefefefefefe); 155 | pub const NOT_FILE_H : BitBoard = BitBoard(0x7f7f7f7f7f7f7f7f); 156 | pub const RANK_1 : BitBoard = BitBoard(0x00000000000000FF); 157 | pub const RANK_3 : BitBoard = BitBoard(0x0000000000FF0000); 158 | pub const RANK_6 : BitBoard = BitBoard(0x0000FF0000000000); 159 | pub const RANK_8 : BitBoard = BitBoard(0xFF00000000000000); 160 | 161 | pub fn east_one(b : BitBoard) -> BitBoard { 162 | return (b << 1) & NOT_FILE_A; 163 | } 164 | pub fn west_one(b : BitBoard) -> BitBoard { 165 | return (b >> 1) & NOT_FILE_H; 166 | } 167 | pub fn north_one(b : BitBoard) -> BitBoard { 168 | return b << 8; 169 | } 170 | pub fn south_one(b : BitBoard) -> BitBoard { 171 | return b >> 8; 172 | } 173 | pub fn no_ea_one(b : BitBoard) -> BitBoard { 174 | return (b << 9) & NOT_FILE_A; 175 | } 176 | pub fn so_ea_one(b : BitBoard) -> BitBoard { 177 | return (b >> 7) & NOT_FILE_A; 178 | } 179 | pub fn no_we_one(b : BitBoard) -> BitBoard { 180 | return (b << 7) & NOT_FILE_H; 181 | } 182 | pub fn so_we_one(b : BitBoard) -> BitBoard { 183 | return (b >> 9) & NOT_FILE_H; 184 | } 185 | 186 | #[inline(always)] 187 | pub fn single_bit(Square(s): Square) -> BitBoard { 188 | return BitBoard (1 << s); 189 | } 190 | 191 | #[inline(always)] 192 | pub fn circular_left_shift(BitBoard(b): BitBoard, shift: usize) -> BitBoard { 193 | return BitBoard(b << shift | b >> (64 - shift)); 194 | } 195 | 196 | impl fmt::Show for BitBoard { 197 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 198 | for rank in range_step(7, -1, -1 as i8) { 199 | write!(f, "+---+---+---+---+---+---+---+---+\n"); 200 | for file in range(0, 8) { 201 | let square = Square::new(rank as u8, file); 202 | write!(f, "| {} ", if is_bit_set(*self, square) {'X'} else {' '}); 203 | } 204 | write!(f, "|\n"); 205 | } 206 | return write!(f, "+---+---+---+---+---+---+---+---+\n"); 207 | } 208 | } 209 | 210 | #[inline(always)] 211 | pub fn bit_scan_forward(BitBoard(mask) : BitBoard) -> Square { 212 | unsafe { 213 | let mut ret : usize; 214 | asm!( 215 | "bsfq $1, $0" 216 | :"=r"(ret) 217 | :"r"(mask) 218 | ); 219 | return Square(ret as u8); 220 | } 221 | } 222 | 223 | #[inline(always)] 224 | pub fn popcnt(BitBoard(mask) : BitBoard) -> u64 { 225 | unsafe { 226 | let mut ret; 227 | asm!( 228 | "popcnt $1, $0" 229 | :"=r"(ret) 230 | :"r"(mask) 231 | ); 232 | return ret; 233 | } 234 | } 235 | 236 | #[inline(always)] 237 | pub fn is_bit_set (BitBoard(board): BitBoard, Square(square): Square) -> bool { 238 | ((1u64 << square) & board) != 0 239 | } 240 | 241 | #[inline(always)] 242 | pub fn set_bit (bitboard: &mut BitBoard, Square(square): Square) -> () { 243 | let BitBoard(board) = *bitboard; 244 | *bitboard = BitBoard(board | (1 << square)); 245 | } 246 | 247 | #[inline(always)] 248 | pub fn clear_bit (bitboard: &mut BitBoard, Square(square): Square) -> () { 249 | let BitBoard(board) = *bitboard; 250 | *bitboard = BitBoard(board & !(1 << square)); 251 | } 252 | 253 | #[inline(always)] 254 | pub fn in_between(Square(s1): Square, Square(s2): Square) -> BitBoard { 255 | return constants::get_between_bb()[s1 as usize][s2 as usize]; 256 | } 257 | -------------------------------------------------------------------------------- /src/search.rs: -------------------------------------------------------------------------------- 1 | extern crate time; 2 | 3 | use _move; 4 | use _move::Move; 5 | use piece_type::PieceType; 6 | use position::Position; 7 | use zobrist::{Table, EXACT_BOUND, ALPHA_BOUND, BETA_BOUND}; 8 | use std::i16; 9 | 10 | // Aspiration window parameter 11 | static WINDOW : i16 = 50; 12 | 13 | pub struct Searcher { 14 | pub quiescent_node_count : u64, 15 | pub node_count : u64, 16 | pub pos: Box, 17 | pub table: Box, 18 | pub killers: [[Move; 2]; 32], 19 | pub ancient: u8 20 | } 21 | 22 | fn move_delta(_move: Move, pos: &Box) -> i16 { 23 | static WEIGHTS : [i16; 7] = [0, 100, 350, 350, 525, 1000, 20000]; 24 | let PieceType(from) = pos.type_of_piece_on(_move::get_from(_move)); 25 | let PieceType(to) = pos.type_of_piece_on(_move::get_to(_move)); 26 | if _move::get_to(_move) == pos.ep_square { 27 | return 0; 28 | } else { 29 | return WEIGHTS[to as usize] - WEIGHTS[from as usize]; 30 | } 31 | } 32 | 33 | impl Searcher { 34 | pub fn new(pos: Position) -> Searcher { 35 | Searcher { 36 | quiescent_node_count: 0, 37 | node_count: 0, 38 | pos: box pos, 39 | table: box Table::new(), 40 | killers: [[_move::NULL; 2]; 32], 41 | ancient: 0 42 | } 43 | } 44 | 45 | fn set_killer(&mut self, _move: Move, ply: u8) -> () { 46 | if self.killers[ply as usize][0] != _move::NULL { 47 | self.killers[ply as usize][1] = self.killers[ply as usize][0]; 48 | } 49 | self.killers[ply as usize][0] = _move; 50 | } 51 | 52 | pub fn alphabeta(&mut self, alpha: i16, beta: i16, depth: u8, ply: u8) -> i16 { 53 | self.node_count += 1; 54 | 55 | let (score, best_move): (Option, Move) = self.table.probe(self.pos.hash, depth, alpha, beta); 56 | match score { 57 | Some(s) => return s, 58 | None => () 59 | } 60 | if depth == 0 { 61 | let score = self.quiesce(alpha, beta); 62 | self.table.record(self.pos.hash, score, _move::NULL, depth, EXACT_BOUND, self.ancient); 63 | return score; 64 | } 65 | 66 | let pinned = self.pos.pinned_pieces(); 67 | let checkers = self.pos.checkers(); 68 | 69 | let mut alpha = alpha; 70 | let mut best = _move::NULL; 71 | 72 | let [killer_move_one, killer_move_two] = self.killers[ply as usize]; 73 | let mut moves = self.pos.gen_moves(false); 74 | 75 | for _move in moves.iter_mut() { 76 | if *_move == best_move { 77 | _move.set_score(10000); 78 | } else if *_move == killer_move_one { 79 | _move.set_score(9999); 80 | } else if *_move == killer_move_two { 81 | _move.set_score(9998); 82 | } else if _move::is_attack(*_move) { 83 | let sc = move_delta(*_move, &self.pos) + 5; 84 | _move.set_score(sc); 85 | } 86 | } 87 | 88 | moves.sort_by(|a, b| b.get_score().cmp(&a.get_score())); 89 | 90 | for _move in moves.iter() { 91 | if self.pos.move_is_legal(*_move, pinned, checkers) { 92 | self.pos.make_move(*_move); 93 | let score = -self.alphabeta(-beta, -alpha, depth - 1, ply + 1); 94 | self.pos.unmake_move(*_move); 95 | if score >= beta { 96 | self.set_killer(*_move, ply); 97 | self.table.record(self.pos.hash, beta, *_move, depth, BETA_BOUND, self.ancient); 98 | return beta; 99 | } 100 | if score > alpha { 101 | alpha = score; 102 | best = *_move; 103 | } 104 | } 105 | } 106 | self.table.record(self.pos.hash, alpha, best, depth, ALPHA_BOUND, self.ancient); 107 | return alpha; 108 | } 109 | 110 | pub fn quiesce(&mut self, alpha: i16, beta: i16) -> i16 { 111 | self.quiescent_node_count += 1; 112 | self.node_count += 1; 113 | 114 | let mut alpha = alpha; 115 | 116 | let stand_pat = self.pos.evaluation(); 117 | if stand_pat >= beta { 118 | return beta; 119 | } 120 | if alpha < stand_pat { 121 | alpha = stand_pat; 122 | } 123 | 124 | let mut moves = self.pos.gen_moves(true); 125 | moves.sort_by(|a, b| move_delta(*b, &self.pos).cmp(&move_delta(*a, &self.pos))); 126 | if moves.len() != 0 { 127 | let pinned = self.pos.pinned_pieces(); 128 | let checkers = self.pos.checkers(); 129 | for _move in moves.iter() { 130 | if self.pos.move_is_legal(*_move, pinned, checkers) { 131 | self.pos.make_move(*_move); 132 | let score = -self.quiesce(-beta, -alpha); 133 | self.pos.unmake_move(*_move); 134 | if score >= beta { 135 | return beta; 136 | } 137 | if score > alpha { 138 | alpha = score; 139 | } 140 | } 141 | } 142 | } 143 | return alpha; 144 | } 145 | 146 | pub fn root_alpha_beta(&mut self, alpha: i16, beta: i16, depth: u8) -> (Move, i16) { 147 | self.quiescent_node_count = 0; 148 | self.node_count = 0; 149 | 150 | let mut alpha: i16 = alpha; 151 | 152 | if depth == 0 { 153 | debug_assert!(false, "Root alpha beta with depth 0"); 154 | return (_move::NULL, 0); 155 | } 156 | 157 | let pinned = self.pos.pinned_pieces(); 158 | let checkers = self.pos.checkers(); 159 | 160 | let mut best = _move::NULL; 161 | 162 | let moves = self.pos.gen_moves(false); 163 | 164 | for _move in moves.iter() { 165 | if self.pos.move_is_legal(*_move, pinned, checkers) { 166 | self.pos.make_move(*_move); 167 | let score = -self.alphabeta(-beta, -alpha, depth - 1, 1); 168 | let eval = self.pos.evaluation(); 169 | self.pos.unmake_move(*_move); 170 | if score > alpha { 171 | best = *_move; 172 | alpha = score; 173 | } else { 174 | } 175 | } 176 | } 177 | 178 | return (best, alpha); 179 | } 180 | 181 | pub fn search(&mut self, secs: f64, print: bool) -> Move { 182 | self.ancient = self.ancient ^ 0x1; 183 | let time = time::precise_time_s(); 184 | let mut i = 1; 185 | let mut alpha: i16 = i16::MIN + 1; 186 | let mut beta: i16 = i16::MAX - 1; 187 | loop { 188 | let (_move, score) = self.root_alpha_beta(alpha, beta, i); 189 | let (_move, score) = 190 | if score <= alpha || score >= beta { 191 | alpha = i16::MIN + 1; 192 | beta = i16::MAX - 1; 193 | self.root_alpha_beta(alpha, beta, i) 194 | } else { 195 | (_move, score) 196 | }; 197 | alpha = score - WINDOW; 198 | beta = score + WINDOW; 199 | if print { 200 | print!("{}:\t({})\t{:?}\n", i, score as f64 / 100.0, self.extract_pv(_move)); 201 | } 202 | let elapsed = time::precise_time_s() - time; 203 | if elapsed > secs { 204 | if print { 205 | print!("Elapsed: {}\n", elapsed); 206 | } 207 | return _move; 208 | } 209 | i += 1; 210 | } 211 | } 212 | 213 | pub fn search_depth(&mut self, depth: u8, print: bool) -> Move { 214 | self.ancient = self.ancient ^ 0x1; 215 | let mut i = 1; 216 | let mut alpha: i16 = i16::MIN + 1; 217 | let mut beta: i16 = i16::MAX - 1; 218 | loop { 219 | let (_move, score) = self.root_alpha_beta(alpha, beta, i); 220 | let (_move, score) = 221 | if score <= alpha || score >= beta { 222 | alpha = i16::MIN + 1; 223 | beta = i16::MAX - 1; 224 | self.root_alpha_beta(alpha, beta, i) 225 | } else { 226 | (_move, score) 227 | }; 228 | alpha = score - WINDOW; 229 | beta = score + WINDOW; 230 | if print { 231 | print!("{}:\t({})\t{:?}\n", i, score as f64 / 100.0, self.extract_pv(_move)); 232 | } 233 | if i == depth { return _move; } 234 | i += 1; 235 | } 236 | } 237 | 238 | pub fn extract_pv(&mut self, best: Move) -> Vec { 239 | if best == _move::NULL { 240 | return vec![]; 241 | } else { 242 | let pv = &mut vec![]; 243 | self.rec_extract(pv, best); 244 | return pv.clone(); 245 | } 246 | } 247 | 248 | fn rec_extract(&mut self, pv: &mut Vec, best: Move) -> () { 249 | pv.push(best); 250 | self.pos.make_move(best); 251 | match self.table.best_move(self.pos.hash) { 252 | None => (), 253 | Some(m) => self.rec_extract(pv, m) 254 | }; 255 | self.pos.unmake_move(best); 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /src/zobrist.rs: -------------------------------------------------------------------------------- 1 | use std::ops; 2 | use std::fmt; 3 | 4 | use piece::Piece; 5 | use piece_type::PieceType; 6 | use color; 7 | use color::Color; 8 | use _move::Move; 9 | use square::Square; 10 | 11 | #[derive(Eq, PartialEq, Show, Copy)] 12 | pub struct ZobristHash(u64); 13 | 14 | static mut piece_keys : [ZobristHash; 768] = [ZobristHash(0); 768]; 15 | static mut castle_keys : [ZobristHash; 16] = [ZobristHash(0); 16]; 16 | static mut ep_keys : [ZobristHash; 8] = [ZobristHash(0); 8]; 17 | static mut black_key : ZobristHash = ZobristHash(0); 18 | 19 | pub fn init() -> () { 20 | use std::rand::{SeedableRng, StdRng, Rng}; 21 | let mut rng: StdRng = SeedableRng::from_seed([1us].as_slice()); 22 | for i in 0..768 { 23 | unsafe { 24 | piece_keys[i] = ZobristHash(rng.gen::()); 25 | } 26 | } 27 | for i in 0..16 { 28 | unsafe { 29 | castle_keys[i] = ZobristHash(rng.gen::()); 30 | } 31 | } 32 | for i in 0..8 { 33 | unsafe { 34 | ep_keys[i] = ZobristHash(rng.gen::()); 35 | } 36 | } 37 | unsafe { 38 | black_key = ZobristHash(rng.gen::()); 39 | } 40 | } 41 | 42 | impl ops::BitXor for ZobristHash { 43 | type Output = ZobristHash; 44 | 45 | fn bitxor (self, ZobristHash(rhs) : ZobristHash) -> ZobristHash { 46 | let ZobristHash(lhs) = self; 47 | return ZobristHash(lhs ^ rhs); 48 | } 49 | } 50 | 51 | impl ops::Rem for ZobristHash { 52 | type Output = usize; 53 | 54 | fn rem (self, rhs: usize) -> usize { 55 | let ZobristHash(lhs) = self; 56 | return (lhs as usize % rhs); 57 | } 58 | } 59 | 60 | //fn get_hash_for_piece_square(PieceType(p): PieceType, 61 | //Color(c): Color, 62 | //Square(s): Square) -> &ZobristHash { 63 | //return piece_keys[(384 * c) + ((p - 1) * 64) + s]; 64 | //} 65 | const TABLE_SIZE : usize = 1048583; 66 | 67 | pub struct Table { 68 | table: [Entry; TABLE_SIZE] 69 | } 70 | 71 | impl Table { 72 | pub fn new() -> Table { 73 | Table{ table: [NULL_ENTRY; TABLE_SIZE]} 74 | } 75 | 76 | pub fn probe(&self, hash: ZobristHash, depth: u8, alpha: i16, beta: i16) -> (Option, Move) { 77 | use _move; 78 | let entry: Entry = self.table[hash % TABLE_SIZE]; 79 | if entry != NULL_ENTRY && entry.get_hash() == hash { 80 | if entry.get_depth() >= depth { 81 | match entry.get_type() { 82 | ALPHA_BOUND => { 83 | if alpha >= entry.get_score() { 84 | return (Some(entry.get_score()), _move::NULL); 85 | } 86 | } 87 | BETA_BOUND => { 88 | if beta <= entry.get_score() { 89 | return (Some(entry.get_score()), _move::NULL); 90 | } 91 | } 92 | EXACT_BOUND => { 93 | return (Some(entry.get_score()), _move::NULL); 94 | } 95 | _ => { } 96 | } 97 | } 98 | let _move = entry.get_move(); 99 | return (None, _move); 100 | } 101 | return (None, _move::NULL); 102 | } 103 | 104 | pub fn best_move(&self, hash: ZobristHash) -> Option { 105 | use _move; 106 | let entry: Entry = self.table[hash % TABLE_SIZE]; 107 | if entry != NULL_ENTRY && entry.get_hash() == hash { 108 | let _move = entry.get_move(); 109 | match _move { 110 | _move::NULL => None, 111 | _ => Some(_move) 112 | } 113 | } else { 114 | return None; 115 | } 116 | } 117 | 118 | pub fn record(&mut self, hash: ZobristHash, score: i16, _move: Move, depth: u8, bound: Bound, ancient: u8) -> () { 119 | let entry: Entry = self.table[hash % TABLE_SIZE]; 120 | if depth >= entry.get_depth() || (entry.get_ancient() != ancient && entry.get_hash() != hash) { 121 | self.table[hash % TABLE_SIZE] = Entry::new(hash, score, _move, depth, bound, ancient); 122 | } 123 | } 124 | 125 | pub fn slots_filled(&self) -> u64 { 126 | let mut c = 0; 127 | for i in (0..TABLE_SIZE) { 128 | if self.table[i] != NULL_ENTRY { 129 | c += 1; 130 | } 131 | } 132 | return c; 133 | } 134 | 135 | pub fn print_nonempty(&self) -> () { 136 | for i in (0..TABLE_SIZE) { 137 | if self.table[i] != NULL_ENTRY { 138 | print!("{:?}\n", self.table[i]); 139 | } 140 | } 141 | } 142 | } 143 | 144 | #[derive(Eq, PartialEq, Copy)] 145 | pub struct Bound(u8); 146 | 147 | pub const ALPHA_BOUND : Bound = Bound(0); 148 | pub const BETA_BOUND : Bound = Bound(1); 149 | pub const EXACT_BOUND : Bound = Bound(2); 150 | 151 | impl fmt::Show for Bound { 152 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 153 | let s = match *self { 154 | ALPHA_BOUND => "ALPHA", 155 | BETA_BOUND => "BETA", 156 | EXACT_BOUND => "EXACT", 157 | _ => "NULL" 158 | }; 159 | return write!(f, "{:?}", s); 160 | } 161 | } 162 | 163 | // [64 bits: zobrist] 164 | // [18 bits: padding] [1: ancient] [21: score] [2: bound] [16: best move] [8: depth] 165 | #[derive(Eq, PartialEq, Copy)] 166 | struct Entry { 167 | zobrist: ZobristHash, 168 | data: u64 169 | } 170 | 171 | static NULL_ENTRY : Entry = Entry { 172 | zobrist: ZobristHash(0), 173 | data: 0 174 | }; 175 | 176 | impl fmt::Show for Entry { 177 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 178 | return write!(f, "Depth: {:?}, Move: {:?}, Score: {:?}, Bound: {:?}, Ancient: {:?}, Hash: {:?}", self.get_depth(), self.get_move(), self.get_score(), self.get_type(), self.get_ancient(), self.get_hash()); 179 | } 180 | } 181 | 182 | impl Entry { 183 | fn new(hash: ZobristHash, score: i16, Move(m): Move, depth: u8, Bound(b): Bound, ancient: u8) -> Entry { 184 | Entry { 185 | zobrist: hash, 186 | data: ((depth as u64 & 0xff) 187 | | ((m as u64 & 0xffff) << 8) 188 | | ((b as u64 & 0x3) << 24) 189 | | ((score as i16 as u16 as u64) << 26) 190 | | ((ancient as u64 & 0x1) << 42)) 191 | } 192 | } 193 | 194 | fn get_depth(&self) -> u8 { 195 | return (self.data & 0xff) as u8; 196 | } 197 | 198 | fn get_move(&self) -> Move { 199 | return Move(((self.data >> 8) & 0xffff) as u64); 200 | } 201 | 202 | fn get_type(&self) -> Bound { 203 | return Bound(((self.data >> 24) & 0x3) as u8); 204 | } 205 | 206 | fn get_score(&self) -> i16 { 207 | return ((self.data >> 26) & 0xffffffff) as u16 as i16 as i16; 208 | } 209 | 210 | fn get_ancient(&self) -> u8 { 211 | return ((self.data >> 42) & 0x1) as u8; 212 | } 213 | 214 | fn get_hash(&self) -> ZobristHash { 215 | return self.zobrist; 216 | } 217 | } 218 | 219 | 220 | impl ZobristHash { 221 | pub fn to_int(&self) -> u64 { 222 | let ZobristHash(z) = *self; 223 | z 224 | } 225 | 226 | pub fn new() -> ZobristHash { 227 | return ZobristHash(0); 228 | } 229 | 230 | pub fn init(pieces: [Piece; 64], 231 | to_move: Color, 232 | castling: u8, 233 | ep_square: Square) 234 | -> ZobristHash { 235 | use piece; 236 | use piece_type; 237 | 238 | let mut hash = ZobristHash::new(); 239 | let mut sq = Square(0); 240 | 241 | for piece in pieces.iter() { 242 | if piece::to_type(*piece) != piece_type::NO_PIECE_TYPE { 243 | let piece_type = piece::to_type(*piece); 244 | let color = piece::to_color(*piece); 245 | hash.set_piece(piece_type, color, sq); 246 | } 247 | sq = sq + 1; 248 | } 249 | 250 | if to_move == color::BLACK { 251 | hash.flip_color(); 252 | } 253 | 254 | hash.set_castling(castling); 255 | 256 | hash.set_ep(ep_square); 257 | 258 | return hash; 259 | } 260 | 261 | pub fn clear_piece(&mut self, 262 | PieceType(p): PieceType, 263 | Color(c): Color, 264 | Square(s): Square) 265 | -> () { 266 | debug_assert!(p <= 7); 267 | debug_assert!(p >= 1); 268 | debug_assert!(c == 0 || c == 1); 269 | debug_assert!(s <= 63); 270 | unsafe { 271 | *self = *self ^ piece_keys[((384 * c as usize) + ((p as usize - 1) * 64) + s as usize)]; 272 | } 273 | //*self = self ^ get_hash_for_piece_square(p, c, s); 274 | } 275 | 276 | pub fn set_piece(&mut self, 277 | PieceType(p): PieceType, 278 | Color(c): Color, 279 | Square(s): Square) 280 | -> () { 281 | unsafe { 282 | *self = *self ^ piece_keys[((384 * c as usize) + ((p as usize - 1) * 64) + s as usize)]; 283 | } 284 | //*self = self ^ get_hash_for_piece_square(p, c, s); 285 | } 286 | 287 | pub fn clear_ep(&mut self, s: Square) -> () { 288 | use square::file; 289 | unsafe { 290 | *self = *self ^ ep_keys[file(s) as usize]; 291 | } 292 | } 293 | 294 | pub fn set_ep(&mut self, s: Square) -> () { 295 | use square::file; 296 | unsafe { 297 | *self = *self ^ ep_keys[file(s) as usize]; 298 | } 299 | } 300 | 301 | pub fn flip_color(&mut self) -> () { 302 | unsafe { 303 | *self = *self ^ black_key; 304 | } 305 | } 306 | 307 | pub fn clear_castling(&mut self, castling: u8) -> () { 308 | unsafe { 309 | *self = *self ^ castle_keys[castling as usize]; 310 | } 311 | } 312 | 313 | pub fn set_castling(&mut self, castling: u8) -> () { 314 | unsafe { 315 | *self = *self ^ castle_keys[castling as usize]; 316 | } 317 | } 318 | } 319 | -------------------------------------------------------------------------------- /src/constants.rs: -------------------------------------------------------------------------------- 1 | use std::iter::range_step; 2 | 3 | use bitboard::{BitBoard, east_one, west_one, north_one, south_one, single_bit, popcnt}; 4 | use square::{Square, file, rank}; 5 | 6 | static mut king_moves : [BitBoard; 64] = [BitBoard(0); 64]; 7 | static mut knight_moves : [BitBoard; 64] = [BitBoard(0); 64]; 8 | static mut rook_moves : [BitBoard; 102400] = [BitBoard(0); 102400]; 9 | static mut rook_mob : [u8; 102400] = [0; 102400]; 10 | static mut bishop_moves : [BitBoard; 5248] = [BitBoard(0); 5248]; 11 | static mut bishop_mob : [u8; 5248] = [0; 5248]; 12 | static mut between_bb : [[BitBoard; 64]; 64] = [[BitBoard(0); 64]; 64]; 13 | pub static MAGIC_NUMBER_SHIFTS_ROOK : [u8; 64] = 14 | [52,53,53,53,53,53,53,52,53,54,54,54,54,54,54,53, 15 | 53,54,54,54,54,54,54,53,53,54,54,54,54,54,54,53, 16 | 53,54,54,54,54,54,54,53,53,54,54,54,54,54,54,53, 17 | 53,54,54,54,54,54,54,53,52,53,53,53,53,53,53,52]; 18 | pub static MAGIC_NUMBER_SHIFTS_BISHOP : [u8; 64] = 19 | [58,59,59,59,59,59,59,58,59,59,59,59,59,59,59, 20 | 59,59,59,57,57,57,57,59,59,59,59,57,55,55,57, 21 | 59,59,59,59,57,55,55,57,59,59,59,59,57,57,57, 22 | 57,59,59,59,59,59,59,59,59,59,59,58,59,59,59, 23 | 59,59,59,58]; 24 | pub static MAGIC_NUMBER_ROOK : [u64; 64] = 25 | [0xa180022080400230, 0x40100040022000, 0x80088020001002, 0x80080280841000, 26 | 0x4200042010460008, 0x4800a0003040080, 0x400110082041008, 0x8000a041000880, 27 | 0x10138001a080c010, 0x804008200480, 0x10011012000c0, 0x22004128102200, 28 | 0x200081201200c, 0x202a001048460004, 0x81000100420004, 0x4000800380004500, 29 | 0x208002904001, 0x90004040026008, 0x208808010002001, 0x2002020020704940, 30 | 0x8048010008110005, 0x6820808004002200, 0xa80040008023011, 0xb1460000811044, 31 | 0x4204400080008ea0, 0xb002400180200184, 0x2020200080100380, 0x10080080100080, 32 | 0x2204080080800400, 0xa40080360080, 0x2040604002810b1, 0x8c218600004104, 33 | 0x8180004000402000, 0x488c402000401001, 0x4018a00080801004, 0x1230002105001008, 34 | 0x8904800800800400, 0x42000c42003810, 0x8408110400b012, 0x18086182000401, 35 | 0x2240088020c28000, 0x1001201040c004, 0xa02008010420020, 0x10003009010060, 36 | 0x4008008008014, 0x80020004008080, 0x282020001008080, 0x50000181204a0004, 37 | 0x102042111804200, 0x40002010004001c0, 0x19220045508200, 0x20030010060a900, 38 | 0x8018028040080, 0x88240002008080, 0x10301802830400, 0x332a4081140200, 39 | 0x8080010a601241, 0x1008010400021, 0x4082001007241, 0x211009001200509, 40 | 0x8015001002441801, 0x801000804000603, 0xc0900220024a401, 0x1000200608243]; 41 | pub static MAGIC_NUMBER_BISHOP : [u64; 64] = 42 | [0x2910054208004104, 0x2100630a7020180, 0x5822022042000000, 0x2ca804a100200020, 43 | 0x204042200000900, 0x2002121024000002, 0x80404104202000e8, 0x812a020205010840, 44 | 0x8005181184080048, 0x1001c20208010101, 0x1001080204002100, 0x1810080489021800, 45 | 0x62040420010a00, 0x5028043004300020, 0xc0080a4402605002, 0x8a00a0104220200, 46 | 0x940000410821212, 0x1808024a280210, 0x40c0422080a0598, 0x4228020082004050, 47 | 0x200800400e00100, 0x20b001230021040, 0x90a0201900c00, 0x4940120a0a0108, 48 | 0x20208050a42180, 0x1004804b280200, 0x2048020024040010, 0x102c04004010200, 49 | 0x20408204c002010, 0x2411100020080c1, 0x102a008084042100, 0x941030000a09846, 50 | 0x244100800400200, 0x4000901010080696, 0x280404180020, 0x800042008240100, 51 | 0x220008400088020, 0x4020182000904c9, 0x23010400020600, 0x41040020110302, 52 | 0x412101004020818, 0x8022080a09404208, 0x1401210240484800, 0x22244208010080, 53 | 0x1105040104000210, 0x2040088800c40081, 0x8184810252000400, 0x4004610041002200, 54 | 0x40201a444400810, 0x4611010802020008, 0x80000b0401040402, 0x20004821880a00, 55 | 0x8200002022440100, 0x9431801010068, 0x1040c20806108040, 0x804901403022a40, 56 | 0x2400202602104000, 0x208520209440204, 0x40c000022013020, 0x2000104000420600, 57 | 0x400000260142410, 0x800633408100500, 0x2404080a1410, 0x138200122002900]; 58 | pub static OCCUPANCY_MASK_ROOK : [u64; 64] = 59 | [0x101010101017e, 0x202020202027c, 0x404040404047a, 0x8080808080876, 0x1010101010106e, 60 | 0x2020202020205e, 0x4040404040403e, 0x8080808080807e, 0x1010101017e00, 0x2020202027c00, 61 | 0x4040404047a00, 0x8080808087600, 0x10101010106e00, 0x20202020205e00, 0x40404040403e00, 62 | 0x80808080807e00, 0x10101017e0100, 0x20202027c0200, 0x40404047a0400, 0x8080808760800, 63 | 0x101010106e1000, 0x202020205e2000, 0x404040403e4000, 0x808080807e8000, 64 | 0x101017e010100, 0x202027c020200, 0x404047a040400, 0x8080876080800, 0x1010106e101000, 65 | 0x2020205e202000, 0x4040403e404000, 0x8080807e808000, 0x1017e01010100, 0x2027c02020200, 66 | 0x4047a04040400, 0x8087608080800, 0x10106e10101000, 0x20205e20202000, 0x40403e40404000, 67 | 0x80807e80808000, 0x17e0101010100, 0x27c0202020200, 0x47a0404040400, 0x8760808080800, 68 | 0x106e1010101000, 0x205e2020202000, 0x403e4040404000, 0x807e8080808000, 69 | 0x7e010101010100, 0x7c020202020200, 0x7a040404040400, 0x76080808080800, 70 | 0x6e101010101000, 0x5e202020202000, 0x3e404040404000, 0x7e808080808000, 71 | 0x7e01010101010100, 0x7c02020202020200, 0x7a04040404040400, 0x7608080808080800, 72 | 0x6e10101010101000, 0x5e20202020202000, 0x3e40404040404000, 0x7e80808080808000]; 73 | pub static OCCUPANCY_MASK_BISHOP : [u64; 64] = 74 | [0x40201008040200, 0x402010080400, 0x4020100a00, 0x40221400, 0x2442800, 0x204085000, 75 | 0x20408102000, 0x2040810204000, 0x20100804020000, 0x40201008040000, 0x4020100a0000, 76 | 0x4022140000, 0x244280000, 0x20408500000, 0x2040810200000, 0x4081020400000, 77 | 0x10080402000200, 0x20100804000400, 0x4020100a000a00, 0x402214001400, 0x24428002800, 78 | 0x2040850005000, 0x4081020002000, 0x8102040004000, 0x8040200020400, 0x10080400040800, 79 | 0x20100a000a1000, 0x40221400142200, 0x2442800284400, 0x4085000500800, 0x8102000201000, 80 | 0x10204000402000, 0x4020002040800, 0x8040004081000, 0x100a000a102000, 0x22140014224000, 81 | 0x44280028440200, 0x8500050080400, 0x10200020100800, 0x20400040201000, 0x2000204081000, 82 | 0x4000408102000, 0xa000a10204000, 0x14001422400000, 0x28002844020000, 0x50005008040200, 83 | 0x20002010080400, 0x40004020100800, 0x20408102000, 0x40810204000, 0xa1020400000, 84 | 0x142240000000, 0x284402000000, 0x500804020000, 0x201008040200, 0x402010080400, 85 | 0x2040810204000, 0x4081020400000, 0xa102040000000, 0x14224000000000, 0x28440200000000, 86 | 0x50080402000000, 0x20100804020000, 0x40201008040200]; 87 | pub static ROOK_INDEXES : [usize; 64] = 88 | [0, 4096, 6144, 8192, 10240, 12288, 14336, 16384, 20480, 22528, 23552, 24576, 25600, 26624, 89 | 27648, 28672, 30720, 32768, 33792, 34816, 35840, 36864, 37888, 38912, 40960, 43008, 44032, 90 | 45056, 46080, 47104, 48128, 49152, 51200, 53248, 54272, 55296, 56320, 57344, 58368, 59392, 91 | 61440, 63488, 64512, 65536, 66560, 67584, 68608, 69632, 71680, 73728, 74752, 75776, 76800, 92 | 77824, 78848, 79872, 81920, 86016, 88064, 90112, 92160, 94208, 96256, 98304]; 93 | pub static BISHOP_INDEXES : [usize; 64] = 94 | [0, 64, 96, 128, 160, 192, 224, 256, 320, 352, 384, 416, 448, 480, 512, 544, 576, 608, 640, 95 | 768, 896, 1024, 1152, 1184, 1216, 1248, 1280, 1408, 1920, 2432, 2560, 2592, 2624, 2656, 2688, 96 | 2816, 3328, 3840, 3968, 4000, 4032, 4064, 4096, 4224, 4352, 4480, 4608, 4640, 4672, 4704, 4736, 97 | 4768, 4800, 4832, 4864, 4896, 4928, 4992, 5024, 5056, 5088, 5120, 5152, 5184]; 98 | 99 | 100 | pub fn init() -> () { 101 | init_king_moves(); 102 | init_knight_moves(); 103 | init_rook_moves(); 104 | init_bishop_moves(); 105 | init_between_bb(); 106 | } 107 | 108 | fn init_king_moves() -> () { 109 | for i in 0..64 { 110 | let board = single_bit(Square(i)); 111 | let moves = north_one(board) | south_one(board); 112 | unsafe { 113 | king_moves[i as usize] = moves | west_one(moves | board) | east_one(moves | board); 114 | } 115 | } 116 | } 117 | 118 | fn init_knight_moves() -> () { 119 | for i in 0..64 { 120 | let board = single_bit(Square(i)); 121 | let east = east_one(board); 122 | let west = west_one(board); 123 | let mut moves = north_one(north_one(east | west)); 124 | moves = moves | south_one(south_one(east | west)); 125 | let east_east = east_one(east); 126 | let west_west = west_one(west); 127 | moves = moves | north_one(east_east|west_west); 128 | moves = moves | south_one(east_east|west_west); 129 | unsafe { 130 | knight_moves[i as usize] = moves; 131 | } 132 | } 133 | } 134 | 135 | #[allow(unsigned_negation)] 136 | fn init_between_bb() -> () { 137 | static M1 : u64 = -1u64; 138 | static A2A7 : u64 = 0x0001010101010100u64; 139 | static B2G7 : u64 = 0x0040201008040200u64; 140 | static H1B7 : u64 = 0x0002040810204080u64; /* Thanks Dustin, g2b7 did not work for c1-a3 */ 141 | for sq1 in 0..64 { 142 | for sq2 in 0..64 { 143 | let btwn = (M1 << sq1) ^ (M1 << sq2); 144 | let file = ((sq2 & 7) - (sq1 & 7)) as u64; 145 | let rank = (((sq2 | 7) - sq1) >> 3) as u64; 146 | let mut line = ((file & 7) - 1) & A2A7; /* a2a7 if same file */ 147 | line += 2 * (((rank & 7) - 1) >> 58); /* b1g1 if same rank */ 148 | line += (((rank - file) & 15) - 1) & B2G7; /* b2g7 if same diagonal */ 149 | line += (((rank + file) & 15) - 1) & H1B7; /* h1b7 if same antidiag */ 150 | line *= btwn & -btwn; /* mul acts like shift by smaller square */ 151 | unsafe { 152 | between_bb[sq1][sq2] = BitBoard(line & btwn); 153 | } 154 | } 155 | } 156 | } 157 | 158 | pub fn rook_moves_for_occ(sq: Square, b: BitBoard) -> BitBoard { 159 | let mut ret = BitBoard(0); 160 | for i in range_step(file(sq) as i8 - 1, -1, -1i8){ 161 | let add = single_bit(Square::new(rank(sq), i as u8)); 162 | ret = ret | add; 163 | if b & add != BitBoard(0) { 164 | break 165 | } 166 | } 167 | for i in range_step(file(sq) + 1, 8, 1){ 168 | let add = single_bit(Square::new(rank(sq), i)); 169 | ret = ret | add; 170 | if b & add != BitBoard(0) { 171 | break 172 | } 173 | } 174 | for i in range_step(rank(sq) + 1, 8, 1){ 175 | let add = single_bit(Square::new(i, file(sq))); 176 | ret = ret | add; 177 | if b & add != BitBoard(0) { 178 | break 179 | } 180 | } 181 | for i in range_step(rank(sq) as i8 - 1, -1, -1i8){ 182 | let add = single_bit(Square::new(i as u8, file(sq))); 183 | ret = ret | add; 184 | if b & add != BitBoard(0) { 185 | break 186 | } 187 | } 188 | return ret; 189 | } 190 | 191 | fn bishop_moves_for_occ(sq: Square, b: BitBoard) -> BitBoard { 192 | let mut ret = BitBoard(0); 193 | let mut f = file(sq); 194 | let mut r = rank(sq); 195 | loop { 196 | f = f + 1; 197 | r = r + 1; 198 | if f == 8 || r == 8 { break; } 199 | let add = single_bit(Square::new(r, f)); 200 | ret = ret | add; 201 | if b & add != BitBoard(0) { break; } 202 | } 203 | let mut f = file(sq); 204 | let mut r = rank(sq); 205 | loop { 206 | f = f + 1; 207 | r = r - 1; 208 | if f == 8 || r == -1 { break; } 209 | let add = single_bit(Square::new(r, f)); 210 | ret = ret | add; 211 | if b & add != BitBoard(0) { break; } 212 | } 213 | let mut f = file(sq); 214 | let mut r = rank(sq); 215 | loop { 216 | f = f - 1; 217 | r = r + 1; 218 | if f == -1 || r == 8 { break; } 219 | let add = single_bit(Square::new(r, f)); 220 | ret = ret | add; 221 | if b & add != BitBoard(0) { break; } 222 | } 223 | let mut f = file(sq); 224 | let mut r = rank(sq); 225 | loop { 226 | f = f - 1; 227 | r = r - 1; 228 | if f == -1 || r == -1 { break; } 229 | let add = single_bit(Square::new(r, f)); 230 | ret = ret | add; 231 | if b & add != BitBoard(0) { break; } 232 | } 233 | return ret; 234 | } 235 | 236 | fn init_rook_moves() -> () { 237 | for i in 0..64 { 238 | let d: BitBoard = BitBoard(OCCUPANCY_MASK_ROOK[i]); 239 | let mut subset: BitBoard = BitBoard(0); 240 | loop { 241 | let BitBoard(index) = (subset * MAGIC_NUMBER_ROOK[i]) >> MAGIC_NUMBER_SHIFTS_ROOK[i] as usize; 242 | unsafe { 243 | let moves = rook_moves_for_occ(Square(i as u8), subset); 244 | rook_moves[ROOK_INDEXES[i] + index as usize] = moves; 245 | rook_mob[ROOK_INDEXES[i] + index as usize] = popcnt(moves) as u8; 246 | } 247 | subset = (subset - d) & d; 248 | if subset == BitBoard(0) { break }; 249 | } 250 | } 251 | } 252 | 253 | fn init_bishop_moves() -> () { 254 | for i in 0..64 { 255 | let d: BitBoard = BitBoard(OCCUPANCY_MASK_BISHOP[i]); 256 | let mut subset: BitBoard = BitBoard(0); 257 | loop { 258 | let BitBoard(index) = (subset * MAGIC_NUMBER_BISHOP[i]) >> MAGIC_NUMBER_SHIFTS_BISHOP[i] as usize; 259 | unsafe { 260 | let moves = bishop_moves_for_occ(Square(i as u8), subset); 261 | bishop_moves[BISHOP_INDEXES[i] + index as usize] = moves; 262 | bishop_mob[BISHOP_INDEXES[i] + index as usize] = popcnt(moves) as u8; 263 | } 264 | subset = (subset - d) & d; 265 | if subset == BitBoard(0) { break }; 266 | } 267 | } 268 | } 269 | 270 | #[inline(always)] 271 | pub fn get_rook_moves() -> &'static[BitBoard; 102400] { 272 | unsafe { &rook_moves } 273 | } 274 | 275 | #[inline(always)] 276 | pub fn get_bishop_moves() -> &'static[BitBoard; 5248] { 277 | unsafe { &bishop_moves } 278 | } 279 | 280 | #[inline(always)] 281 | pub fn get_rook_mob() -> &'static[u8; 102400] { 282 | unsafe { &rook_mob } 283 | } 284 | 285 | #[inline(always)] 286 | pub fn get_bishop_mob() -> &'static[u8; 5248] { 287 | unsafe { &bishop_mob } 288 | } 289 | 290 | #[inline(always)] 291 | pub fn get_king_moves() -> &'static[BitBoard; 64] { 292 | unsafe { &king_moves } 293 | } 294 | 295 | #[inline(always)] 296 | pub fn get_knight_moves() -> &'static[BitBoard; 64] { 297 | unsafe { &knight_moves } 298 | } 299 | 300 | #[inline(always)] 301 | pub fn get_between_bb() -> &'static[[BitBoard; 64]; 64] { 302 | unsafe { &between_bb } 303 | } 304 | -------------------------------------------------------------------------------- /src/position.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::fmt; 3 | 4 | use bitboard; 5 | use bitboard::{BitBoard, clear_bit, set_bit, bit_scan_forward, circular_left_shift, clear_lsb, 6 | in_between, no_we_one, so_we_one, no_ea_one, so_ea_one, filter_pieces}; 7 | use color; 8 | use color::{Color, WHITE, BLACK}; 9 | use constants; 10 | use _move::{Move, get_from, get_to, get_promotion, is_castle}; 11 | use piece; 12 | use piece::{Piece, NP, 13 | WP, WN, WB, WR, WQ, WK, 14 | BP, BN, BB, BR, BQ, BK, to_color, to_type}; 15 | use piece_type::{PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING}; 16 | use piece_type::{PieceType, NO_PIECE_TYPE, from_char}; 17 | use square; 18 | use square::{Square}; 19 | use zobrist::ZobristHash; 20 | use std::str::FromStr; 21 | 22 | pub struct Position { 23 | pub occupied: BitBoard, 24 | pub to_move: Color, 25 | pub by_piece: [BitBoard; 7], 26 | pub by_color: [BitBoard; 2], 27 | pub castling_rights : u8, 28 | pub board: [Piece; 64], 29 | pub ep_square: Square, 30 | pub ep_square_hist: Vec, 31 | pub castling_hist: Vec, 32 | pub half_moves_hist: Vec, 33 | pub capture_hist: Vec, 34 | pub hash: ZobristHash, 35 | pub half_moves: u8 36 | } 37 | 38 | impl Position { 39 | pub fn from_fen(fen: &str) -> Position { 40 | let parts: Vec<&str> = fen.split(' ').collect(); 41 | assert!(parts.len() >= 5); 42 | assert!(parts[1] == "w" || parts[1] == "b"); 43 | let mut rank_idx: u8 = 7; 44 | let rank_strs: Vec<&str> = parts[0].split('/').collect(); 45 | assert!(rank_strs.len() == 8); 46 | let mut board: [Piece; 64] = [NP; 64]; 47 | for rank_str in rank_strs.iter() { 48 | let mut file_idx: u8 = 0; 49 | for c in rank_str.chars() { 50 | if c.is_digit(10) { 51 | file_idx += c.to_digit(10).expect("") as u8; 52 | } else if c.is_alphabetic() { 53 | let color = if c.is_lowercase() { BLACK } else { WHITE }; 54 | board[(rank_idx * 8 + file_idx) as usize] = Piece::new(from_char(c.to_lowercase()).expect("fen failed"), color); 55 | file_idx += 1; 56 | } 57 | } 58 | rank_idx -= 1; 59 | assert!(file_idx == 8); 60 | } 61 | 62 | let mut castling_rights = 0; 63 | let castling_vec: Vec = parts[2].chars().collect(); 64 | if castling_vec.contains(&'K') { castling_rights |= 1 } 65 | if castling_vec.contains(&'Q') { castling_rights |= 4 } 66 | if castling_vec.contains(&'k') { castling_rights |= 2 } 67 | if castling_vec.contains(&'q') { castling_rights |= 8 } 68 | 69 | let to_move = if parts[1] == "w" { WHITE } else { BLACK }; 70 | 71 | let ep_square = 72 | if parts[3].chars().all(|c| c.is_alphanumeric()) { 73 | square::from_str(parts[3]).expect("fen failed") 74 | } else { 75 | square::NULL 76 | }; 77 | 78 | let half_moves = FromStr::from_str(parts[4]).unwrap(); 79 | 80 | Position { 81 | occupied: filter_pieces(board, |piece| to_type(piece) != NO_PIECE_TYPE), 82 | by_piece: [ 83 | filter_pieces_by_type(board, NO_PIECE_TYPE), 84 | filter_pieces_by_type(board, PAWN), 85 | filter_pieces_by_type(board, KNIGHT), 86 | filter_pieces_by_type(board, BISHOP), 87 | filter_pieces_by_type(board, ROOK), 88 | filter_pieces_by_type(board, QUEEN), 89 | filter_pieces_by_type(board, KING), 90 | ], 91 | board: board, 92 | by_color: [filter_pieces_by_color(board, WHITE), filter_pieces_by_color(board, BLACK)], 93 | to_move: to_move, 94 | castling_rights: castling_rights, 95 | ep_square: ep_square, 96 | ep_square_hist: vec![], 97 | castling_hist: vec![], 98 | half_moves_hist: vec![], 99 | capture_hist: vec![], 100 | hash: ZobristHash::init(board, to_move, castling_rights, ep_square), 101 | half_moves: half_moves 102 | } 103 | } 104 | 105 | pub fn new () -> Position { 106 | let mut p = Position { 107 | occupied : BitBoard(0xffff00000000ffff_u64), 108 | by_piece: [ 109 | BitBoard(0x0000ffffffff0000_u64), 110 | BitBoard(0x00ff00000000ff00_u64), 111 | BitBoard(0x4200000000000042_u64), 112 | BitBoard(0x2400000000000024_u64), 113 | BitBoard(0x8100000000000081_u64), 114 | BitBoard(0x0800000000000008_u64), 115 | BitBoard(0x1000000000000010_u64), 116 | ], 117 | board: [ 118 | WR, WN, WB, WQ, WK, WB, WN, WR, 119 | WP, WP, WP, WP, WP, WP, WP, WP, 120 | NP, NP, NP, NP, NP, NP, NP, NP, 121 | NP, NP, NP, NP, NP, NP, NP, NP, 122 | NP, NP, NP, NP, NP, NP, NP, NP, 123 | NP, NP, NP, NP, NP, NP, NP, NP, 124 | BP, BP, BP, BP, BP, BP, BP, BP, 125 | BR, BN, BB, BQ, BK, BB, BN, BR, 126 | ], 127 | by_color: [ BitBoard(0xFFFF_u64), BitBoard(0xFFFF000000000000_u64) ], 128 | to_move: WHITE, 129 | castling_rights: 15, 130 | ep_square: square::NULL, 131 | ep_square_hist: vec![], 132 | castling_hist: vec![], 133 | half_moves_hist: vec![], 134 | capture_hist: vec![], 135 | hash: ZobristHash::new(), 136 | half_moves: 0 137 | }; 138 | p.hash = ZobristHash::init(p.board, p.to_move, p.castling_rights, p.ep_square); 139 | p 140 | } 141 | 142 | pub fn piece_on (&self, Square(square): Square) -> Piece { 143 | self.board[square as usize] 144 | } 145 | 146 | pub fn set_piece_on (&mut self, Square(square): Square, piece: Piece) -> () { 147 | self.board[square as usize] = piece; 148 | } 149 | 150 | pub fn type_of_piece_on (&self, square: Square) -> PieceType { 151 | piece::to_type(self.piece_on(square)) 152 | } 153 | 154 | pub fn color_of_piece_on (&self, square: Square) -> Color { 155 | piece::to_color(self.piece_on(square)) 156 | } 157 | 158 | pub fn pieces_of_type (&self, PieceType(piece_type): PieceType) -> BitBoard { 159 | self.by_piece[piece_type as usize] 160 | } 161 | 162 | pub fn mut_pieces_of_type (&mut self, PieceType(piece_type): PieceType) -> &mut BitBoard { 163 | &mut self.by_piece[piece_type as usize] 164 | } 165 | 166 | pub fn pieces_of_color (&self, Color(color): Color) -> BitBoard { 167 | self.by_color[color as usize] 168 | } 169 | 170 | pub fn mut_pieces_of_color (&mut self, Color(color): Color) -> &mut BitBoard { 171 | &mut self.by_color[color as usize] 172 | } 173 | 174 | pub fn pieces (&self, Color(color): Color, PieceType(piece_type): PieceType) -> BitBoard { 175 | self.by_piece[piece_type as usize] & self.by_color[color as usize] 176 | } 177 | 178 | pub fn move_is_legal(&mut self, _move: Move, pinned: BitBoard, checkers: BitBoard) -> bool { 179 | let from = get_from(_move); 180 | let to = get_to(_move); 181 | let PieceType(piece) = self.type_of_piece_on(from); 182 | 183 | if checkers != BitBoard(0) { 184 | if clear_lsb(checkers) != BitBoard(0) { 185 | // Double check, so make sure we move the king out 186 | if PieceType(piece) != KING { 187 | return false; 188 | } 189 | } else { 190 | // If there is only one checker 191 | if PieceType(piece) != KING { 192 | // Stop the check, either by blocking it or by capturing 193 | let checker_sq = bit_scan_forward(checkers); 194 | let checker_type = self.type_of_piece_on(checker_sq); 195 | match checker_type { 196 | KNIGHT | PAWN => { 197 | if to != checker_sq { 198 | return false; 199 | } 200 | }, 201 | _ => { 202 | // The move must block the check, otherwise return false 203 | let king_sq = bit_scan_forward(self.pieces(self.to_move, KING)); 204 | let blocks = (bitboard::in_between(king_sq, checker_sq) & bitboard::single_bit(to)) 205 | != BitBoard(0); 206 | let captures = to == checker_sq; 207 | if !blocks & !captures { 208 | return false; 209 | } 210 | } 211 | } 212 | } 213 | } 214 | if is_castle(_move) { 215 | return false; 216 | } 217 | } else { 218 | // Only check for castling being valid in the no checkers case, 219 | // since we already invalidated it otherwise 220 | if is_castle(_move) { 221 | static CASTLE_SQS : [[[Square; 2]; 2]; 2] = 222 | [[[Square(5), Square(6)], [Square(3), Square(2)]], 223 | [[Square(61), Square(62)], [Square(59), Square(58)]]]; 224 | let queenside_castle = square::file(to) == 2; 225 | let sqs = CASTLE_SQS[color::to_idx(self.to_move)][queenside_castle as usize]; 226 | let without_king = self.occupied ^ self.pieces(self.to_move, KING); 227 | if self.attacks_for_occ(sqs[0], without_king) != BitBoard(0) { 228 | return false; 229 | } 230 | static ROOK_SQS : [[Square; 2]; 2] = 231 | [[Square(7), Square(0)], [Square(63), Square(56)]]; 232 | let without_king_or_rook = 233 | self.occupied 234 | ^ bitboard::single_bit( 235 | ROOK_SQS[color::to_idx(self.to_move)][queenside_castle as usize] 236 | ); 237 | if self.attacks_for_occ(sqs[1], without_king_or_rook) != BitBoard(0) { 238 | return false; 239 | } 240 | } 241 | } 242 | // could switch this to checking that the direction between 243 | // squares is the same. 244 | if (bitboard::single_bit(from) & pinned) != BitBoard(0) { 245 | let king_sq = bit_scan_forward(self.pieces(self.to_move, KING)); 246 | if ((bitboard::in_between(king_sq, from) & bitboard::single_bit(to)) 247 | | (bitboard::in_between(king_sq, to) & bitboard::single_bit(from))) 248 | == BitBoard(0) { 249 | return false; 250 | } 251 | } 252 | if to == self.ep_square && PieceType(piece) == PAWN { 253 | if (bitboard::single_bit(to) & pinned) != BitBoard(0) { 254 | let king_sq = bit_scan_forward(self.pieces(self.to_move, KING)); 255 | static SINGLE_PUSH_DIFFS : [u8; 2] = [8, 64 - 8]; 256 | let target = ((from + 64) - SINGLE_PUSH_DIFFS[color::to_idx(self.to_move)]) % 64; 257 | if ((bitboard::in_between(king_sq, target) & bitboard::single_bit(to)) 258 | | (bitboard::in_between(king_sq, to) & bitboard::single_bit(target))) 259 | == BitBoard(0) { 260 | return false; 261 | } 262 | } 263 | } 264 | // No need to check here if it's a castle, we already did it above 265 | if PieceType(piece) == KING && !is_castle(_move) { 266 | if self.attacks_for_occ(to, self.occupied ^ self.pieces(self.to_move, KING)) != BitBoard(0) { 267 | return false; 268 | } 269 | } 270 | return true; 271 | } 272 | 273 | pub fn make_move(&mut self, _move: Move) -> () { 274 | self.ep_square_hist.push(self.ep_square); 275 | self.castling_hist.push(self.castling_rights); 276 | self.half_moves_hist.push(self.half_moves); 277 | 278 | self.half_moves += 1; 279 | 280 | let from = get_from(_move); 281 | let to = get_to(_move); 282 | 283 | let us = self.to_move; 284 | let us_int = color::to_idx(us); 285 | let them = color::flip(self.to_move); 286 | let them_int = color::to_idx(them); 287 | 288 | let piece = self.type_of_piece_on(from); 289 | let capture = self.type_of_piece_on(to); 290 | 291 | self.capture_hist.push(capture); 292 | 293 | if capture != NO_PIECE_TYPE { 294 | self.half_moves = 0; 295 | clear_bit(self.mut_pieces_of_type(capture), to); 296 | clear_bit(self.mut_pieces_of_color(them), to); 297 | self.hash.clear_piece(capture, them, to); 298 | } 299 | 300 | let on_from = self.piece_on(from); 301 | 302 | // Remove piece on old square 303 | clear_bit(self.mut_pieces_of_type(piece), from); 304 | clear_bit(self.mut_pieces_of_color(us), from); 305 | clear_bit(&mut self.occupied, from); 306 | set_bit(&mut self.by_piece[0], from); 307 | self.set_piece_on(from, NP); 308 | self.hash.clear_piece(piece, us, from); 309 | 310 | // Place piece on new square 311 | set_bit(self.mut_pieces_of_type(piece), to); 312 | set_bit(self.mut_pieces_of_color(us), to); 313 | set_bit(&mut self.occupied, to); 314 | clear_bit(&mut self.by_piece[0], to); 315 | self.set_piece_on(to, on_from); 316 | self.hash.set_piece(piece, us, to); 317 | 318 | self.hash.clear_ep(self.ep_square); 319 | let old_ep_square = self.ep_square; 320 | self.ep_square = square::NULL; 321 | match piece { 322 | PAWN => { 323 | static SINGLE_PUSH_DIFFS : [u8; 2] = [64 - 8, 8]; 324 | 325 | self.half_moves = 0; 326 | 327 | if square::abs_diff(to, from) == 16 { 328 | // Double push, set ep square 329 | self.ep_square = ((from + 64) 330 | - SINGLE_PUSH_DIFFS[us_int]) 331 | % 64; 332 | } else if to == old_ep_square { 333 | // En passant 334 | let target = Square::new(square::rank(from), square::file(to)); 335 | clear_bit(self.mut_pieces_of_type(PAWN), target); 336 | clear_bit(self.mut_pieces_of_color(them), target); 337 | clear_bit(&mut self.occupied, target); 338 | set_bit(&mut self.by_piece[0], target); 339 | self.set_piece_on(target, NP); 340 | self.hash.clear_piece(PAWN, them, target); 341 | } else if get_promotion(_move) != NO_PIECE_TYPE { 342 | // Promotion 343 | clear_bit(self.mut_pieces_of_type(piece), to); 344 | self.set_piece_on(to, Piece::new(get_promotion(_move), us)); 345 | set_bit(self.mut_pieces_of_type(get_promotion(_move)), to); 346 | self.hash.clear_piece(PAWN, us, to); 347 | self.hash.set_piece(get_promotion(_move), us, to); 348 | } 349 | } 350 | KING => { 351 | if is_castle(_move) { 352 | static ROOK_SQS : [[Square; 2]; 2] = 353 | [[Square(7), Square(0)], [Square(63), Square(56)]]; 354 | static ROOK_DESTS : [[Square; 2]; 2] = 355 | [[Square(5), Square(3)], [Square(61), Square(59)]]; 356 | let queenside_castle = square::file(to) == 2; 357 | let rook_from = ROOK_SQS[us_int][queenside_castle as usize]; 358 | let rook_to = ROOK_DESTS[us_int][queenside_castle as usize]; 359 | 360 | let on_from = self.piece_on(rook_from); 361 | 362 | clear_bit(self.mut_pieces_of_type(ROOK), rook_from); 363 | clear_bit(self.mut_pieces_of_color(us), rook_from); 364 | clear_bit(&mut self.occupied, rook_from); 365 | set_bit(&mut self.by_piece[0], rook_from); 366 | self.set_piece_on(rook_from, NP); 367 | self.hash.clear_piece(ROOK, us, rook_from); 368 | 369 | set_bit(self.mut_pieces_of_type(ROOK), rook_to); 370 | set_bit(self.mut_pieces_of_color(us), rook_to); 371 | set_bit(&mut self.occupied, rook_to); 372 | clear_bit(&mut self.by_piece[0], rook_to); 373 | self.set_piece_on(rook_to, on_from); 374 | self.hash.set_piece(ROOK, us, rook_to); 375 | // TODO should we just construct the rook piece here... 376 | } 377 | static CASTLING_RIGHTS_MASKS : [u8; 2] = [5, 10]; 378 | self.hash.clear_castling(self.castling_rights); 379 | self.castling_rights &= !CASTLING_RIGHTS_MASKS[us_int]; 380 | self.hash.set_castling(self.castling_rights); 381 | } 382 | ROOK => { 383 | static ROOK_SQS : [[Square; 2]; 2] = 384 | [[Square(7), Square(0)], [Square(63), Square(56)]]; 385 | // TODO: remove branch? 386 | let curr_rook_sqs = ROOK_SQS[us_int]; 387 | if from == curr_rook_sqs[0] || from == curr_rook_sqs[1] { 388 | let queenside_castle = from == curr_rook_sqs[1]; 389 | let idx = us_int | (if queenside_castle {2} else {0}); 390 | self.hash.clear_castling(self.castling_rights); 391 | self.castling_rights &= !(1 << idx); 392 | self.hash.set_castling(self.castling_rights); 393 | } 394 | } 395 | _ => { } 396 | } 397 | if capture == ROOK { 398 | static ROOK_SQS : [[Square; 2]; 2] = 399 | [[Square(7), Square(0)], [Square(63), Square(56)]]; 400 | let curr_rook_sqs = ROOK_SQS[them_int]; 401 | if to == curr_rook_sqs[0] || to == curr_rook_sqs[1] { 402 | let queenside_castle = to == curr_rook_sqs[1]; 403 | let idx = them_int | (if queenside_castle {2} else {0}); 404 | self.hash.clear_castling(self.castling_rights); 405 | self.castling_rights &= !(1 << idx); 406 | self.hash.set_castling(self.castling_rights); 407 | } 408 | } 409 | 410 | self.hash.set_ep(self.ep_square); 411 | 412 | self.to_move = color::flip(self.to_move); 413 | self.hash.flip_color(); 414 | 415 | } 416 | 417 | pub fn unmake_move(&mut self, _move: Move) -> () { 418 | self.hash.flip_color(); 419 | self.to_move = color::flip(self.to_move); 420 | 421 | self.half_moves = self.half_moves_hist.pop().unwrap(); 422 | 423 | self.hash.clear_ep(self.ep_square); 424 | self.ep_square = self.ep_square_hist.pop().unwrap(); 425 | self.hash.set_ep(self.ep_square); 426 | 427 | self.hash.clear_castling(self.castling_rights); 428 | self.castling_rights = self.castling_hist.pop().unwrap(); 429 | self.hash.set_castling(self.castling_rights); 430 | 431 | let from = get_from(_move); 432 | let to = get_to(_move); 433 | 434 | let us = self.to_move; 435 | let us_int = color::to_idx(us); 436 | let them = color::flip(self.to_move); 437 | let them_int = color::to_idx(them); 438 | 439 | let piece = self.type_of_piece_on(to); 440 | let capture = self.capture_hist.pop().unwrap(); 441 | 442 | // Note that piece is only reliable here if it is a pawn or king. 443 | // Anything else might have been a promotion. If any logic is added 444 | // here for other pieces, the promotion logic needs to be pulled 445 | // above. 446 | if piece == PAWN { 447 | if to == self.ep_square { 448 | let target = Square::new(square::rank(from), square::file(to)); 449 | set_bit(self.mut_pieces_of_color(them), target); 450 | set_bit(self.mut_pieces_of_type(PAWN), target); 451 | set_bit(&mut self.occupied, target); 452 | clear_bit(&mut self.by_piece[0], target); 453 | self.set_piece_on(target, Piece::new(PAWN, them)); 454 | self.hash.set_piece(PAWN, them, target); 455 | } 456 | } else if get_promotion(_move) != NO_PIECE_TYPE { 457 | // Set explicitly the pawn board. Piece is incorrect here... 458 | set_bit(self.mut_pieces_of_type(PAWN), to); 459 | clear_bit(self.mut_pieces_of_type(get_promotion(_move)), to); 460 | // Set the piece now so when we grab it again after this 461 | // if/else block it will be correctly a pawn 462 | self.set_piece_on(to, Piece::new(PAWN, us)); 463 | self.hash.clear_piece(get_promotion(_move), us, to); 464 | self.hash.set_piece(PAWN, us, to); 465 | } else if is_castle(_move) { 466 | static ROOK_SQS : [[Square; 2]; 2] = 467 | [[Square(7), Square(0)], [Square(63), Square(56)]]; 468 | static ROOK_DESTS : [[Square; 2]; 2] = 469 | [[Square(5), Square(3)], [Square(61), Square(59)]]; 470 | let queenside_castle = square::file(to) == 2; 471 | let rook_from = ROOK_SQS[us_int][queenside_castle as usize]; 472 | let rook_to = ROOK_DESTS[us_int][queenside_castle as usize]; 473 | 474 | let on_to = self.piece_on(rook_to); 475 | 476 | clear_bit(self.mut_pieces_of_type(ROOK), rook_to); 477 | clear_bit(self.mut_pieces_of_color(us), rook_to); 478 | clear_bit(&mut self.occupied, rook_to); 479 | set_bit(&mut self.by_piece[0], rook_to); 480 | self.set_piece_on(rook_to, NP); 481 | self.hash.clear_piece(ROOK, us, rook_to); 482 | 483 | set_bit(self.mut_pieces_of_type(ROOK), rook_from); 484 | set_bit(self.mut_pieces_of_color(us), rook_from); 485 | set_bit(&mut self.occupied, rook_from); 486 | clear_bit(&mut self.by_piece[0], rook_from); 487 | self.set_piece_on(rook_from, on_to); 488 | self.hash.set_piece(ROOK, us, rook_from); 489 | // TODO should we just construct the rook piece here... 490 | } 491 | 492 | // TODO: comment this line 493 | let piece = self.type_of_piece_on(to); 494 | 495 | let captured_piece = Piece::new(capture, them); 496 | let moved_piece = Piece::new(piece, us); 497 | self.set_piece_on(to, captured_piece); 498 | self.set_piece_on(from, moved_piece); 499 | 500 | set_bit(&mut self.occupied, from); 501 | set_bit(self.mut_pieces_of_color(us), from); 502 | set_bit(self.mut_pieces_of_type(piece), from); 503 | clear_bit(&mut self.by_piece[0], from); 504 | self.hash.set_piece(piece, us, from); 505 | 506 | clear_bit(&mut self.occupied, to); 507 | clear_bit(self.mut_pieces_of_color(us), to); 508 | clear_bit(self.mut_pieces_of_type(piece), to); 509 | set_bit(&mut self.by_piece[0], to); 510 | self.hash.clear_piece(piece, us, to); 511 | 512 | if capture != NO_PIECE_TYPE { 513 | set_bit(self.mut_pieces_of_type(capture), to); 514 | set_bit(self.mut_pieces_of_color(them), to); 515 | set_bit(&mut self.occupied, to); 516 | clear_bit(&mut self.by_piece[0], to); 517 | self.hash.set_piece(capture, them, to); 518 | } 519 | 520 | } 521 | 522 | // Return any attacks to square s by the side not to move 523 | fn attacks(&self, s: Square) -> BitBoard { 524 | return self.attacks_for_occ(s, self.occupied); 525 | } 526 | 527 | // Return attacks to square s by the side not to move with modified occupancy 528 | // This is used to compute squares the king can't move to 529 | fn attacks_for_occ(&self, s: Square, occ: BitBoard) -> BitBoard { 530 | let opp_color = color::flip(self.to_move); 531 | return (pawn_attacks(s, self.to_move) & self.pieces(opp_color, PAWN)) 532 | | (knight_attacks(s) & self.pieces(opp_color, KNIGHT)) 533 | | (bishop_attacks(occ, s) & (self.pieces(opp_color, BISHOP) | self.pieces(opp_color, QUEEN))) 534 | | (rook_attacks(occ, s) & (self.pieces(opp_color, ROOK) | self.pieces(opp_color, QUEEN))) 535 | | (king_attacks(s) & self.pieces(opp_color, KING)); 536 | } 537 | 538 | // Return checkers to the side to move 539 | pub fn checkers(&self) -> BitBoard { 540 | let king_sq = bit_scan_forward(self.pieces(self.to_move, KING)); 541 | return self.attacks(king_sq); 542 | } 543 | 544 | pub fn gen_moves(&self, attacks_only: bool) -> Vec { 545 | let mut moves: Vec = vec![]; 546 | if !attacks_only { 547 | self.gen_castle_moves(&mut moves); 548 | } 549 | self.gen_king_moves(&mut moves, attacks_only); 550 | self.gen_queen_moves(&mut moves, attacks_only); 551 | self.gen_rook_moves(&mut moves, attacks_only); 552 | self.gen_bishop_moves(&mut moves, attacks_only); 553 | self.gen_knight_moves(&mut moves, attacks_only); 554 | self.gen_pawn_moves(&mut moves, attacks_only); 555 | 556 | return moves; 557 | } 558 | 559 | pub fn gen_castle_moves(&self, moves: &mut Vec) -> () { 560 | static MASKS : [BitBoard; 4] = [BitBoard(0x60), BitBoard(0x6000000000000000), 561 | BitBoard(0xe), BitBoard(0xe00000000000000)]; 562 | static TARGETS : [Square; 4] = [Square(6), Square(62), Square(2), Square(58)]; 563 | let king = self.pieces(self.to_move, KING); 564 | let from = bit_scan_forward(king); 565 | // O-O 566 | let idx = color::to_idx(self.to_move) | 0; 567 | if self.castling_rights & (1 << idx) != 0 { 568 | if MASKS[idx] & self.occupied == BitBoard(0) { 569 | moves.push(Move::new_castle(from, TARGETS[idx])); 570 | } 571 | } 572 | // O-O-O 573 | let idx = color::to_idx(self.to_move) | 2; 574 | if self.castling_rights >> idx & 1 == 1 { 575 | if MASKS[idx] & self.occupied == BitBoard(0) { 576 | moves.push(Move::new_castle(from, TARGETS[idx])); 577 | } 578 | } 579 | } 580 | 581 | fn gen_king_moves(&self, moves: &mut Vec, attacks_only: bool) -> () { 582 | let king = self.pieces(self.to_move, KING); 583 | let king_sq = bit_scan_forward(king); 584 | let self_pieces = self.pieces_of_color(self.to_move); 585 | let targets = king_attacks(king_sq) & !self_pieces; 586 | let mut attack_moves = targets & !self.by_piece[0] ; 587 | while attack_moves != BitBoard(0) { 588 | let to = bit_scan_forward(attack_moves); 589 | moves.insert(0, Move::new(king_sq, to, 1)); 590 | attack_moves = clear_lsb(attack_moves); 591 | } 592 | if !attacks_only { 593 | let mut quiet_moves = targets & self.by_piece[0] ; 594 | while quiet_moves != BitBoard(0) { 595 | let to = bit_scan_forward(quiet_moves); 596 | moves.push(Move::new(king_sq, to, 0)); 597 | quiet_moves = clear_lsb(quiet_moves); 598 | } 599 | } 600 | } 601 | 602 | fn gen_knight_moves(&self, moves: &mut Vec, attacks_only: bool) -> () { 603 | let mut knights = self.pieces(self.to_move, KNIGHT); 604 | let self_pieces = self.pieces_of_color(self.to_move); 605 | while knights != BitBoard(0) { 606 | let from = bit_scan_forward(knights); 607 | let targets: BitBoard = knight_attacks(from) & !self_pieces; 608 | let mut attack_moves = targets & !self.by_piece[0]; 609 | while attack_moves != BitBoard(0) { 610 | let to = bit_scan_forward(attack_moves); 611 | moves.insert(0, Move::new(from, to, 1)); 612 | attack_moves = clear_lsb(attack_moves); 613 | } 614 | if !attacks_only { 615 | let mut quiet_moves = targets & self.by_piece[0]; 616 | while quiet_moves != BitBoard(0) { 617 | let to = bit_scan_forward(quiet_moves); 618 | moves.push(Move::new(from, to, 0)); 619 | quiet_moves = clear_lsb(quiet_moves); 620 | } 621 | } 622 | knights = clear_lsb(knights); 623 | } 624 | } 625 | 626 | fn gen_queen_moves(&self, moves: &mut Vec, attacks_only: bool) -> () { 627 | let mut queens = self.pieces(self.to_move, QUEEN); 628 | let self_pieces = self.pieces_of_color(self.to_move); 629 | while queens != BitBoard(0) { 630 | let from = bit_scan_forward(queens); 631 | let targets = (rook_attacks(self.occupied, from) 632 | | bishop_attacks(self.occupied, from)) 633 | & !self_pieces; 634 | let mut attack_moves = targets & !self.by_piece[0]; 635 | while attack_moves != BitBoard(0) { 636 | let to = bit_scan_forward(attack_moves); 637 | moves.insert(0, Move::new(from, to, 1)); 638 | attack_moves = clear_lsb(attack_moves); 639 | } 640 | if !attacks_only { 641 | let mut quiet_moves = targets & self.by_piece[0]; 642 | while quiet_moves != BitBoard(0) { 643 | let to = bit_scan_forward(quiet_moves); 644 | moves.push(Move::new(from, to, 0)); 645 | quiet_moves = clear_lsb(quiet_moves); 646 | } 647 | } 648 | queens = clear_lsb(queens); 649 | } 650 | } 651 | 652 | fn gen_rook_moves(&self, moves: &mut Vec, attacks_only: bool) -> () { 653 | let mut rooks = self.pieces(self.to_move, ROOK); 654 | let self_pieces = self.pieces_of_color(self.to_move); 655 | while rooks != BitBoard(0) { 656 | let from = bit_scan_forward(rooks); 657 | let targets = rook_attacks(self.occupied, from) & !self_pieces; 658 | let mut attack_moves = targets & !self.by_piece[0]; 659 | while attack_moves != BitBoard(0) { 660 | let to = bit_scan_forward(attack_moves); 661 | moves.insert(0, Move::new(from, to, 1)); 662 | attack_moves = clear_lsb(attack_moves); 663 | } 664 | if !attacks_only { 665 | let mut quiet_moves = targets & self.by_piece[0]; 666 | while quiet_moves != BitBoard(0) { 667 | let to = bit_scan_forward(quiet_moves); 668 | moves.push(Move::new(from, to, 0)); 669 | quiet_moves = clear_lsb(quiet_moves); 670 | } 671 | } 672 | rooks = clear_lsb(rooks); 673 | } 674 | } 675 | 676 | fn gen_bishop_moves(&self, moves: &mut Vec, attacks_only: bool) -> () { 677 | let mut bishops = self.pieces(self.to_move, BISHOP); 678 | let self_pieces = self.pieces_of_color(self.to_move); 679 | while bishops != BitBoard(0) { 680 | let from = bit_scan_forward(bishops); 681 | let targets = bishop_attacks(self.occupied, from) & !self_pieces; 682 | let mut attack_moves = targets & !self.by_piece[0]; 683 | while attack_moves != BitBoard(0) { 684 | let to = bit_scan_forward(attack_moves); 685 | moves.insert(0, Move::new(from, to, 1)); 686 | attack_moves = clear_lsb(attack_moves); 687 | } 688 | if !attacks_only { 689 | let mut quiet_moves = targets & self.by_piece[0]; 690 | while quiet_moves != BitBoard(0) { 691 | let to = bit_scan_forward(quiet_moves); 692 | moves.push(Move::new(from, to, 0)); 693 | quiet_moves = clear_lsb(quiet_moves); 694 | } 695 | } 696 | bishops = clear_lsb(bishops); 697 | } 698 | } 699 | 700 | fn gen_pawn_moves(&self, moves: &mut Vec, attacks_only: bool) -> () { 701 | let pawns = self.pieces(self.to_move, PAWN); 702 | static PROMOTION_MASK : [BitBoard; 2] = [bitboard::RANK_8, bitboard::RANK_1]; 703 | if !attacks_only { 704 | let empty = !self.occupied; 705 | 706 | static SINGLE_PUSH_DIFFS : [u8; 2] = [8, 64 - 8]; 707 | let diff = SINGLE_PUSH_DIFFS[color::to_idx(self.to_move)]; 708 | let single_pushes = 709 | circular_left_shift(pawns, diff as usize) 710 | & empty; 711 | let non_promotions = single_pushes & !PROMOTION_MASK[color::to_idx(self.to_move)]; 712 | self.add_pawn_moves(non_promotions, moves, diff); 713 | 714 | let promotions = single_pushes & PROMOTION_MASK[color::to_idx(self.to_move)]; 715 | self.add_pawn_promotions(promotions, moves, diff, 0); 716 | 717 | let double_push_mask = [bitboard::RANK_3, bitboard::RANK_6]; 718 | let double_pushes = 719 | circular_left_shift(single_pushes & double_push_mask[color::to_idx(self.to_move)], 720 | diff as usize) 721 | & empty; 722 | self.add_pawn_moves(double_pushes, moves, 2 * diff); 723 | } 724 | 725 | static ATTACK_DIFFS : [[u8; 2]; 2] = [[7, 64-9], [9, 64-7]]; 726 | static FILE_MASKS : [BitBoard; 2] = [bitboard::NOT_FILE_H, bitboard::NOT_FILE_A]; 727 | for i in 0..2 { 728 | let diff = ATTACK_DIFFS[i][color::to_idx(self.to_move)]; 729 | let targets = circular_left_shift(pawns, diff as usize) & FILE_MASKS[i]; 730 | let attack_moves = targets & self.pieces_of_color(color::flip(self.to_move)); 731 | let non_promotions = attack_moves & !PROMOTION_MASK[color::to_idx(self.to_move)]; 732 | self.add_pawn_attacks(non_promotions, moves, diff); 733 | 734 | if self.ep_square != square::NULL { 735 | let en_passant = targets & bitboard::single_bit(self.ep_square); 736 | self.add_pawn_attacks(en_passant, moves, diff); 737 | } 738 | 739 | let promotions = attack_moves & PROMOTION_MASK[color::to_idx(self.to_move)]; 740 | self.add_pawn_promotions(promotions, moves, diff, 1); 741 | } 742 | 743 | } 744 | 745 | // Attacks from a square by pawns of a given color 746 | 747 | pub fn add_pawn_moves(&self, mut targets: BitBoard, moves: &mut Vec, diff: u8) -> () { 748 | let t = &mut targets; 749 | while *t != BitBoard(0) { 750 | let to = bit_scan_forward(*t); 751 | // Add 64 because % is remainder, not modulo 752 | let from = ((to + 64) - diff) % 64; 753 | moves.push(Move::new(from, to, 0)); 754 | *t = clear_lsb(*t); 755 | } 756 | } 757 | 758 | pub fn add_pawn_attacks(&self, mut targets: BitBoard, moves: &mut Vec, diff: u8) -> () { 759 | let t = &mut targets; 760 | while *t != BitBoard(0) { 761 | let to = bit_scan_forward(*t); 762 | // Add 64 because % is remainder, not modulo 763 | let from = ((to + 64) - diff) % 64; 764 | moves.insert(0, Move::new(from, to, 1)); 765 | *t = clear_lsb(*t); 766 | } 767 | } 768 | 769 | pub fn add_pawn_promotions(&self, mut targets: BitBoard, moves: &mut Vec, diff: u8, attack: u64) -> () { 770 | let t = &mut targets; 771 | while *t != BitBoard(0) { 772 | let to = bit_scan_forward(*t); 773 | // Add 64 because % is remainder, not modulo 774 | let from = ((to + 64) - diff) % 64; 775 | moves.insert(0, Move::new_promotion(from, to, KNIGHT, attack)); 776 | moves.insert(0, Move::new_promotion(from, to, BISHOP, attack)); 777 | moves.insert(0, Move::new_promotion(from, to, ROOK, attack)); 778 | moves.insert(0, Move::new_promotion(from, to, QUEEN, attack)); 779 | *t = clear_lsb(*t); 780 | } 781 | } 782 | 783 | // Absolutely pinned pieces 784 | pub fn pinned_pieces(&self) -> BitBoard { 785 | let king = self.pieces(self.to_move, KING); 786 | let king_sq = bit_scan_forward(king); 787 | let mut pinned = BitBoard(0); 788 | let mut pinners = x_ray_rook_attacks(self.occupied, self.pieces_of_color(self.to_move), king_sq) 789 | & self.pieces_of_color(color::flip(self.to_move)) 790 | & (self.pieces_of_type(ROOK) | self.pieces_of_type(QUEEN)); 791 | while pinners != BitBoard(0){ 792 | let pinner_sq = bit_scan_forward(pinners); 793 | pinned = pinned | in_between(king_sq, pinner_sq) & self.pieces_of_color(self.to_move); 794 | pinners = clear_lsb(pinners); 795 | } 796 | let mut pinners = x_ray_bishop_attacks(self.occupied, self.pieces_of_color(self.to_move), king_sq) 797 | & self.pieces_of_color(color::flip(self.to_move)) 798 | & (self.pieces_of_type(BISHOP) | self.pieces_of_type(QUEEN)); 799 | while pinners != BitBoard(0){ 800 | let pinner_sq = bit_scan_forward(pinners); 801 | pinned = pinned | in_between(king_sq, pinner_sq) & self.pieces_of_color(self.to_move); 802 | pinners = clear_lsb(pinners); 803 | } 804 | return pinned; 805 | } 806 | 807 | fn rook_mob(occ: BitBoard, Square(i): Square) -> u8 { 808 | let sq_idx = i as usize; 809 | let occ = BitBoard(constants::OCCUPANCY_MASK_ROOK[sq_idx]) & occ; 810 | let BitBoard(index) = (occ * constants::MAGIC_NUMBER_ROOK[sq_idx]) >> constants::MAGIC_NUMBER_SHIFTS_ROOK[sq_idx] as usize; 811 | return constants::get_rook_mob()[constants::ROOK_INDEXES[sq_idx] + index as usize]; 812 | } 813 | 814 | fn bishop_mob(occ: BitBoard, Square(i): Square) -> u8 { 815 | let sq_idx = i as usize; 816 | let occ = BitBoard(constants::OCCUPANCY_MASK_BISHOP[sq_idx]) & occ; 817 | let BitBoard(index) = (occ * constants::MAGIC_NUMBER_BISHOP[sq_idx]) >> constants::MAGIC_NUMBER_SHIFTS_BISHOP[sq_idx] as usize; 818 | return constants::get_bishop_mob()[constants::BISHOP_INDEXES[sq_idx] + index as usize]; 819 | } 820 | 821 | pub fn evaluation(&self) -> i16 { 822 | use bitboard::popcnt; 823 | static PAWN_TABLE : [i8; 64] = [ 824 | 0, 0, 0, 0, 0, 0, 0, 0, 825 | 50, 50, 50, 50, 50, 50, 50, 50, 826 | 10, 10, 20, 30, 30, 20, 10, 10, 827 | 5, 5, 10, 27, 27, 10, 5, 5, 828 | 0, 0, 0, 25, 25, 0, 0, 0, 829 | 5, -5, -10, 0, 0, -10,-5, 5, 830 | 5, 10, 10, -25,-25, 10, 10, 5, 831 | 0, 0, 0, 0, 0, 0, 0, 0 832 | ]; 833 | static KNIGHT_TABLE : [i8; 64] = [ 834 | -50,-40,-30,-30,-30,-30,-40,-50, 835 | -40,-20, 0, 0, 0, 0,-20,-40, 836 | -30, 0, 10, 15, 15, 10, 0, -30, 837 | -30, 5, 15, 20, 20, 15, 5, -30, 838 | -30, 0, 15, 20, 20, 15, 0, -30, 839 | -30, 5, 10, 15, 15, 10, 5, -30, 840 | -40,-20, 0, 5, 5, 0,-20,-40, 841 | -50,-40,-20,-30,-30,-20,-40,-50, 842 | ]; 843 | static BISHOP_TABLE : [i8; 64] = [ 844 | -20,-10,-10,-10,-10,-10,-10,-20, 845 | -10, 0, 0, 0, 0, 0, 0,-10, 846 | -10, 0, 5, 10, 10, 5, 0, -10, 847 | -10, 5, 5, 10, 10, 5, 5, -10, 848 | -10, 0, 10, 10, 10, 10, 0, -10, 849 | -10, 10, 10, 10, 10, 10,10, -10, 850 | -10, 5, 0, 0, 0, 0, 5,-10, 851 | -20,-10,-40,-10,-10,-40,-10,-20, 852 | ]; 853 | static PAWN_WEIGHT : i16 = 100; 854 | static KNIGHT_WEIGHT : i16 = 350; 855 | static BISHOP_WEIGHT : i16 = 350; 856 | static ROOK_WEIGHT : i16 = 525; 857 | static QUEEN_WEIGHT : i16 = 1050; 858 | static KING_WEIGHT : i16 = 20000; 859 | 860 | if self.half_moves >= 100 { 861 | // Check if there are any legal moves, as checkmate takes precedent 862 | if self.gen_moves(false).len() != 0 { 863 | return 0; 864 | } 865 | } 866 | 867 | let white = self.by_color[0]; 868 | let black = self.by_color[1]; 869 | let pawns = self.pieces_of_type(PAWN); 870 | let knights = self.pieces_of_type(KNIGHT); 871 | let bishops = self.pieces_of_type(BISHOP); 872 | let rooks = self.pieces_of_type(ROOK); 873 | let queens = self.pieces_of_type(QUEEN); 874 | let kings = self.pieces_of_type(KING); 875 | 876 | let pawn_diff = popcnt(pawns & white) as i16 - popcnt(pawns & black) as i16; 877 | let knight_diff = popcnt(knights & white) as i16 - popcnt(knights & black) as i16; 878 | let bishop_diff = popcnt(bishops & white) as i16 - popcnt(bishops & black) as i16; 879 | let rook_diff = popcnt(rooks & white) as i16 - popcnt(rooks & black) as i16; 880 | let queen_diff = popcnt(queens & white) as i16 - popcnt(queens & black) as i16; 881 | let king_diff = popcnt(kings & white) as i16 - popcnt(kings & black) as i16; 882 | 883 | let mut targets = pawns & white; 884 | let mut pawn_sqs: i16 = 0; 885 | while targets != BitBoard(0) { 886 | let Square(pawn) = bit_scan_forward(targets); 887 | pawn_sqs += PAWN_TABLE[63 - pawn as usize] as i16; 888 | targets = clear_lsb(targets); 889 | } 890 | let mut targets = pawns & black; 891 | while targets != BitBoard(0) { 892 | let Square(pawn) = bit_scan_forward(targets); 893 | pawn_sqs -= PAWN_TABLE[pawn as usize] as i16; 894 | targets = clear_lsb(targets); 895 | } 896 | let mut targets = knights & white; 897 | let mut knight_sqs: i16 = 0; 898 | while targets != BitBoard(0) { 899 | let Square(night) = bit_scan_forward(targets); 900 | knight_sqs += KNIGHT_TABLE[63 - night as usize] as i16; 901 | targets = clear_lsb(targets); 902 | } 903 | let mut targets = knights & black; 904 | while targets != BitBoard(0) { 905 | let Square(night) = bit_scan_forward(targets); 906 | knight_sqs -= KNIGHT_TABLE[night as usize] as i16; 907 | targets = clear_lsb(targets); 908 | } 909 | let mut targets = bishops & white; 910 | let mut bishop_sqs: i16 = 0; 911 | while targets != BitBoard(0) { 912 | let Square(bishop) = bit_scan_forward(targets); 913 | bishop_sqs += BISHOP_TABLE[63 - bishop as usize] as i16; 914 | targets = clear_lsb(targets); 915 | } 916 | let mut targets = bishops & black; 917 | while targets != BitBoard(0) { 918 | let Square(bishop) = bit_scan_forward(targets); 919 | bishop_sqs -= BISHOP_TABLE[bishop as usize] as i16; 920 | targets = clear_lsb(targets); 921 | } 922 | 923 | let score = PAWN_WEIGHT * pawn_diff 924 | + KNIGHT_WEIGHT * knight_diff 925 | + BISHOP_WEIGHT * bishop_diff 926 | + ROOK_WEIGHT * rook_diff 927 | + QUEEN_WEIGHT * queen_diff 928 | + KING_WEIGHT * king_diff 929 | + pawn_sqs 930 | + knight_sqs 931 | + bishop_sqs; 932 | static TO_MOVE_ARR : [i16; 2] = [1, -1]; 933 | return score * TO_MOVE_ARR[color::to_idx(self.to_move)]; 934 | } 935 | } 936 | 937 | pub fn x_ray_rook_attacks(occ: BitBoard, blockers: BitBoard, sq: Square) -> BitBoard { 938 | let attacks = rook_attacks(occ, sq); 939 | return attacks ^ rook_attacks(occ ^ (blockers & attacks), sq); 940 | } 941 | 942 | pub fn x_ray_bishop_attacks(occ: BitBoard, blockers: BitBoard, sq: Square) -> BitBoard { 943 | let attacks = bishop_attacks(occ, sq); 944 | return attacks ^ bishop_attacks(occ ^ (blockers & attacks), sq); 945 | } 946 | 947 | fn rook_attacks(occ: BitBoard, Square(i): Square) -> BitBoard { 948 | let sq_idx = i as usize; 949 | let occ = BitBoard(constants::OCCUPANCY_MASK_ROOK[sq_idx]) & occ; 950 | let BitBoard(index) = (occ * constants::MAGIC_NUMBER_ROOK[sq_idx]) >> constants::MAGIC_NUMBER_SHIFTS_ROOK[sq_idx] as usize; 951 | return constants::get_rook_moves()[constants::ROOK_INDEXES[sq_idx] + index as usize] 952 | } 953 | 954 | fn bishop_attacks(occ: BitBoard, Square(i): Square) -> BitBoard { 955 | let sq_idx = i as usize; 956 | let occ = BitBoard(constants::OCCUPANCY_MASK_BISHOP[sq_idx]) & occ; 957 | let BitBoard(index) = (occ * constants::MAGIC_NUMBER_BISHOP[sq_idx]) >> constants::MAGIC_NUMBER_SHIFTS_BISHOP[sq_idx] as usize; 958 | return constants::get_bishop_moves()[constants::BISHOP_INDEXES[sq_idx] + index as usize]; 959 | } 960 | 961 | fn king_attacks(Square(s): Square) -> BitBoard { 962 | return constants::get_king_moves()[s as usize] 963 | } 964 | 965 | fn knight_attacks(Square(s): Square) -> BitBoard { 966 | return constants::get_knight_moves()[s as usize] 967 | } 968 | 969 | fn filter_pieces_by_type(pieces : [Piece; 64], piece_type: PieceType) -> BitBoard { 970 | filter_pieces(pieces, |piece| to_type(piece) == piece_type) 971 | } 972 | 973 | fn filter_pieces_by_color(pieces : [Piece; 64], color: Color) -> BitBoard { 974 | filter_pieces(pieces, |piece| to_color(piece) == color && to_type(piece) != NO_PIECE_TYPE) 975 | } 976 | 977 | 978 | fn pawn_attacks(s : Square, c: Color) -> BitBoard { 979 | let board = bitboard::single_bit(s); 980 | if c == WHITE { 981 | return no_we_one(board) | no_ea_one(board); 982 | } else { 983 | return so_we_one(board) | so_ea_one(board); 984 | } 985 | } 986 | 987 | pub fn perft(position: &mut Position, depth: u8) -> u64 { 988 | let moves = position.gen_moves(false); 989 | let mut count = 0; 990 | let pinned = position.pinned_pieces(); 991 | let checkers = position.checkers(); 992 | if depth == 1 { 993 | for _move in moves.iter() { 994 | if position.move_is_legal(*_move, pinned, checkers) { 995 | count += 1; 996 | } 997 | } 998 | return count; 999 | } 1000 | for _move in moves.iter() { 1001 | if position.move_is_legal(*_move, pinned, checkers) { 1002 | position.make_move(*_move); 1003 | count += perft(position, depth - 1); 1004 | position.unmake_move(*_move); 1005 | } 1006 | } 1007 | return count; 1008 | } 1009 | 1010 | pub fn divide(position: &mut Position, depth: u8) -> HashMap { 1011 | let pinned = position.pinned_pieces(); 1012 | let checkers = position.checkers(); 1013 | let moves = position.gen_moves(false); 1014 | let mut res = HashMap::new (); 1015 | if depth == 1 { 1016 | for _move in moves.iter() { 1017 | if position.move_is_legal(*_move, pinned, checkers) { 1018 | res.insert(*_move, 1); 1019 | } 1020 | } 1021 | return res; 1022 | } 1023 | for _move in moves.iter() { 1024 | if position.move_is_legal(*_move, pinned, checkers) { 1025 | position.make_move(*_move); 1026 | res.insert(*_move, perft(position, depth - 1)); 1027 | position.unmake_move(*_move); 1028 | } 1029 | } 1030 | return res; 1031 | } 1032 | 1033 | pub fn print_divide(position: &mut Position, depth: u8) -> () { 1034 | let res = divide(position, depth); 1035 | let mut keys: Vec<&Move> = res.keys().collect(); 1036 | keys.sort_by(|a, b| a.cmp(b)); 1037 | for _move in keys.iter() { 1038 | print!("{:?} {:?}\n", _move, res.get(*_move)); 1039 | } 1040 | } 1041 | 1042 | impl fmt::Show for Position { 1043 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 1044 | use std::iter::range_step; 1045 | for rank in range_step(7, -1, -1 as i8) { 1046 | write!(f, "+---+---+---+---+---+---+---+---+\n"); 1047 | for file in 0..8 { 1048 | let square = Square(file | ((rank as u8) << 3)); 1049 | write!(f, "| {} ", piece::to_char(self.piece_on(square))); 1050 | } 1051 | write!(f, "|\n"); 1052 | } 1053 | write!(f, "+---+---+---+---+---+---+---+---+\n") 1054 | } 1055 | } 1056 | --------------------------------------------------------------------------------