├── typos.toml ├── pleco_engine ├── src │ ├── uci │ │ ├── mod.rs │ │ ├── parse.rs │ │ └── options.rs │ ├── time │ │ ├── mod.rs │ │ ├── uci_timer.rs │ │ └── time_management.rs │ ├── main.rs │ ├── lib.rs │ ├── tables │ │ ├── counter_move.rs │ │ ├── butterfly.rs │ │ ├── capture_piece_history.rs │ │ ├── continuation.rs │ │ ├── mod.rs │ │ └── material.rs │ ├── sync │ │ └── mod.rs │ ├── root_moves │ │ ├── mod.rs │ │ └── root_moves_list.rs │ ├── consts.rs │ ├── movepick │ │ └── pick.rs │ └── engine.rs ├── tests │ └── test_get_move.rs ├── benches │ ├── bench_engine_main.rs │ ├── startpos_benches.rs │ └── multimove_benches.rs ├── Cargo.toml └── README.md ├── pleco ├── tests │ ├── test.rs │ ├── basic_bots.rs │ ├── board_move_apply.rs │ ├── board_properties.rs │ ├── fen_building.rs │ ├── pseudo_legal_checks.rs │ ├── board_build.rs │ └── board_hash.rs ├── benches │ ├── bench_main.rs │ ├── bot_benches.rs │ ├── bit_benches.rs │ ├── perft_benches.rs │ ├── lookup_benches.rs │ └── eval_benches.rs ├── src │ ├── bots │ │ ├── minimax.rs │ │ ├── parallel_minimax.rs │ │ ├── alphabeta.rs │ │ ├── jamboree.rs │ │ └── mod.rs │ ├── tools │ │ ├── prng.rs │ │ ├── mod.rs │ │ ├── pleco_arc.rs │ │ └── eval.rs │ ├── helper │ │ ├── zobrist.rs │ │ └── prelude.rs │ ├── lib.rs │ ├── core │ │ ├── score.rs │ │ └── macros.rs │ └── board │ │ ├── castle_rights.rs │ │ ├── piece_locations.rs │ │ └── perft.rs ├── LICENSE ├── Cargo.toml └── README.md ├── .gitignore ├── .github ├── CONTRIBUTING.md └── workflows │ └── test.yml ├── Cargo.toml ├── TODO.md └── README.md /typos.toml: -------------------------------------------------------------------------------- 1 | [default.extend-words] 2 | ba = "ba" 3 | fo = "fo" 4 | nd = "nd" 5 | -------------------------------------------------------------------------------- /pleco_engine/src/uci/mod.rs: -------------------------------------------------------------------------------- 1 | /// uci protocol functions 2 | pub mod options; 3 | pub mod parse; 4 | -------------------------------------------------------------------------------- /pleco_engine/src/time/mod.rs: -------------------------------------------------------------------------------- 1 | //! Objects for managing time. 2 | 3 | pub mod time_management; 4 | pub mod uci_timer; 5 | -------------------------------------------------------------------------------- /pleco_engine/src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate pleco_engine; 2 | use pleco_engine::engine::PlecoSearcher; 3 | 4 | fn main() { 5 | let mut s = PlecoSearcher::init(true); 6 | s.uci(); 7 | } 8 | -------------------------------------------------------------------------------- /pleco/tests/test.rs: -------------------------------------------------------------------------------- 1 | extern crate pleco; 2 | 3 | mod basic_bots; 4 | mod board_build; 5 | mod board_hash; 6 | mod board_move_apply; 7 | mod fen_building; 8 | mod move_generating; 9 | mod pseudo_legal_checks; 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | **/*.rs.bk 3 | .idea/ 4 | .vscode/launch.json 5 | .vscode/ 6 | rusty_chess.iml 7 | Pleco.iml 8 | pleco.iml 9 | pleco/Cargo.lock 10 | pleco_engine/Cargo.lock 11 | Cargo.lock 12 | notes.txt 13 | .DS_Store 14 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | All and any contributions are welcome! 2 | 3 | To contribute, open up a Pull Request on the main branch. This will be subject to passing all of tests before it can be merged. 4 | 5 | The basic benchmarks will be run as well, and there can't be any serious regressions for the PR to be merged. 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["pleco", "pleco_engine"] 3 | default-members = ["pleco", "pleco_engine"] 4 | 5 | [profile.release] 6 | opt-level = 3 7 | debug = false 8 | rpath = false 9 | debug-assertions = false 10 | codegen-units = 1 11 | lto = true 12 | panic = "abort" 13 | 14 | [profile.bench] 15 | opt-level = 3 16 | debug = false 17 | rpath = false 18 | lto = true 19 | debug-assertions = false 20 | codegen-units = 1 21 | 22 | [profile.test] 23 | opt-level = 3 24 | debug = true 25 | rpath = false 26 | debug-assertions = true 27 | codegen-units = 4 28 | -------------------------------------------------------------------------------- /pleco/benches/bench_main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate criterion; 3 | #[macro_use] 4 | extern crate lazy_static; 5 | 6 | extern crate pleco; 7 | 8 | mod bit_benches; 9 | mod board_benches; 10 | mod bot_benches; 11 | mod eval_benches; 12 | mod lookup_benches; 13 | mod move_gen_benches; 14 | mod perft_benches; 15 | 16 | criterion_main! { 17 | move_gen_benches::movegen_benches, 18 | bit_benches::bit_benches, 19 | board_benches::board_benches, 20 | bot_benches::bot_benches, 21 | eval_benches::eval_benches, 22 | lookup_benches::lookup_benches, 23 | perft_benches::perft_benches 24 | } 25 | -------------------------------------------------------------------------------- /pleco/tests/basic_bots.rs: -------------------------------------------------------------------------------- 1 | extern crate pleco; 2 | 3 | use pleco::board::{Board, RandBoard}; 4 | use pleco::bot_prelude::*; 5 | use pleco::tools::Searcher; 6 | 7 | #[test] 8 | fn test_all_bot() { 9 | for _x in 0..3 { 10 | let board: Board = RandBoard::default().one(); 11 | RandomBot::best_move(board.shallow_clone(), 4); 12 | MiniMaxSearcher::best_move(board.shallow_clone(), 4); 13 | AlphaBetaSearcher::best_move(board.shallow_clone(), 4); 14 | ParallelMiniMaxSearcher::best_move(board.shallow_clone(), 4); 15 | JamboreeSearcher::best_move(board.shallow_clone(), 4); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /pleco_engine/tests/test_get_move.rs: -------------------------------------------------------------------------------- 1 | extern crate pleco; 2 | extern crate pleco_engine; 3 | 4 | use pleco::Board; 5 | use pleco_engine::engine::PlecoSearcher; 6 | use pleco_engine::time::uci_timer::PreLimits; 7 | 8 | pub fn get_move(fen: String, depth: u16) -> String { 9 | let mut limit = PreLimits::blank(); 10 | limit.depth = Some(depth); 11 | let board = Board::from_fen(fen.as_str()).unwrap(); 12 | let mut s = PlecoSearcher::init(false); 13 | 14 | s.search(&board, &limit); 15 | let bit_move = s.await_move(); 16 | 17 | bit_move.to_string() 18 | } 19 | 20 | #[cfg(test)] 21 | mod tests { 22 | use super::*; 23 | 24 | #[test] 25 | fn it_works() { 26 | let result = get_move( 27 | "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1".to_string(), 28 | 10, 29 | ); 30 | assert_eq!(result, "e2e4"); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /pleco/src/bots/minimax.rs: -------------------------------------------------------------------------------- 1 | //! The minimax algorithm. 2 | use super::*; 3 | use board::*; 4 | 5 | pub fn minimax(board: &mut Board, depth: u16) -> ScoringMove { 6 | if depth == 0 { 7 | return eval_board(board); 8 | } 9 | 10 | board 11 | .generate_scoring_moves() 12 | .into_iter() 13 | .map(|mut m: ScoringMove| { 14 | board.apply_move(m.bit_move); 15 | m.score = -minimax(board, depth - 1).score; 16 | board.undo_move(); 17 | m 18 | }) 19 | .max() 20 | .unwrap_or_else(|| match board.in_check() { 21 | true => ScoringMove::blank(-MATE_V), 22 | false => ScoringMove::blank(DRAW_V), 23 | }) 24 | } 25 | 26 | pub fn minimax_eval_bitmove(board: &mut Board, bm: BitMove, depth: u16) -> i16 { 27 | board.apply_move(bm); 28 | let out = -minimax(board, depth).score; 29 | board.undo_move(); 30 | out 31 | } 32 | -------------------------------------------------------------------------------- /pleco_engine/benches/bench_engine_main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate criterion; 3 | 4 | extern crate pleco; 5 | extern crate pleco_engine; 6 | 7 | mod eval_benches; 8 | mod multimove_benches; 9 | mod startpos_benches; 10 | 11 | trait DepthLimit { 12 | fn depth() -> u16; 13 | } 14 | 15 | struct Depth5 {} 16 | struct Depth6 {} 17 | struct Depth7 {} 18 | struct Depth8 {} 19 | struct Depth9 {} 20 | 21 | impl DepthLimit for Depth5 { 22 | fn depth() -> u16 { 23 | 5 24 | } 25 | } 26 | impl DepthLimit for Depth6 { 27 | fn depth() -> u16 { 28 | 6 29 | } 30 | } 31 | impl DepthLimit for Depth7 { 32 | fn depth() -> u16 { 33 | 7 34 | } 35 | } 36 | impl DepthLimit for Depth8 { 37 | fn depth() -> u16 { 38 | 8 39 | } 40 | } 41 | impl DepthLimit for Depth9 { 42 | fn depth() -> u16 { 43 | 9 44 | } 45 | } 46 | 47 | criterion_main! { 48 | eval_benches::eval_benches, 49 | multimove_benches::search_multimove, 50 | startpos_benches::search_singular 51 | } 52 | -------------------------------------------------------------------------------- /pleco/src/bots/parallel_minimax.rs: -------------------------------------------------------------------------------- 1 | //! The parallel minimax algorithm. 2 | use mucow::MuCow; 3 | use rayon::prelude::*; 4 | 5 | use super::*; 6 | use board::*; 7 | use bots::minimax::minimax; 8 | use core::piece_move::*; 9 | 10 | pub fn parallel_minimax(board: &mut Board, depth: u16) -> ScoringMove { 11 | if depth <= 2 { 12 | return minimax(board, depth); 13 | } 14 | 15 | let mut moves = board.generate_scoring_moves(); 16 | if moves.is_empty() { 17 | if board.in_check() { 18 | return ScoringMove::blank(-MATE_V); 19 | } else { 20 | return ScoringMove::blank(DRAW_V); 21 | } 22 | } 23 | let board_wr: MuCow = MuCow::Borrowed(board); 24 | *moves 25 | .as_mut_slice() 26 | .par_iter_mut() 27 | .map_with(board_wr, |b: &mut MuCow, m: &mut ScoringMove| { 28 | b.apply_move(m.bit_move); 29 | m.score = -parallel_minimax(&mut *b, depth - 1).score; 30 | b.undo_move(); 31 | m 32 | }) 33 | .max() 34 | .unwrap() 35 | } 36 | -------------------------------------------------------------------------------- /pleco/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Pleco 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /pleco_engine/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A Rust re-write of the Stockfish chess engine. 2 | //! 3 | //! This crate is not intended to be used by other crates as a dependency, as it's a mostly useful as a direct 4 | //! executable. 5 | //! 6 | //! If you are interested in using the direct chess library functions (The Boards, move generation, etc), please 7 | //! checkout the core library, `pleco`, available on [on crates.io](https://crates.io/crates/pleco). 8 | //! 9 | #![cfg_attr(test, allow(dead_code))] 10 | #![allow(dead_code)] 11 | #![allow(clippy::cast_lossless)] 12 | #![allow(clippy::unreadable_literal)] 13 | #![allow(clippy::cast_ptr_alignment)] 14 | #![allow(clippy::mut_from_ref)] 15 | #![allow(clippy::cognitive_complexity)] 16 | #![allow(clippy::uninit_assumed_init)] 17 | 18 | //#![crate_type = "staticlib"] 19 | 20 | extern crate chrono; 21 | extern crate num_cpus; 22 | extern crate pleco; 23 | extern crate rand; 24 | 25 | pub mod consts; 26 | pub mod engine; 27 | pub mod movepick; 28 | pub mod root_moves; 29 | pub mod search; 30 | pub mod sync; 31 | pub mod tables; 32 | pub mod threadpool; 33 | pub mod time; 34 | pub mod uci; 35 | 36 | pub use consts::*; 37 | -------------------------------------------------------------------------------- /pleco_engine/src/tables/counter_move.rs: -------------------------------------------------------------------------------- 1 | use pleco::core::masks::*; 2 | use pleco::{BitMove, Piece, SQ}; 3 | use std::ops::{Index, IndexMut}; 4 | 5 | use super::StatBoard; 6 | 7 | /// CounterMoveHistory stores counter moves indexed by [player][piece][to] of the previous 8 | /// move 9 | pub struct CounterMoveHistory { 10 | a: [[BitMove; SQ_CNT]; PIECE_CNT], 11 | } 12 | 13 | // [Us][Piece][To SQ] 14 | #[allow(non_camel_case_types)] 15 | type CM_idx = (Piece, SQ); 16 | 17 | impl Index for CounterMoveHistory { 18 | type Output = BitMove; 19 | 20 | #[inline(always)] 21 | fn index(&self, idx: CM_idx) -> &Self::Output { 22 | unsafe { 23 | self.a 24 | .get_unchecked(idx.0 as usize) // [Piece Moved] 25 | .get_unchecked((idx.1).0 as usize) // [To SQ] 26 | } 27 | } 28 | } 29 | 30 | impl IndexMut for CounterMoveHistory { 31 | #[inline(always)] 32 | fn index_mut(&mut self, idx: CM_idx) -> &mut Self::Output { 33 | unsafe { 34 | self.a 35 | .get_unchecked_mut(idx.0 as usize) // [Piece Moved] 36 | .get_unchecked_mut((idx.1).0 as usize) // [To SQ] 37 | } 38 | } 39 | } 40 | 41 | impl StatBoard for CounterMoveHistory { 42 | const FILL: BitMove = BitMove::null(); 43 | } 44 | -------------------------------------------------------------------------------- /pleco/tests/board_move_apply.rs: -------------------------------------------------------------------------------- 1 | extern crate pleco; 2 | extern crate rand; 3 | 4 | use pleco::board::Board; 5 | use pleco::core::piece_move::BitMove; 6 | use std::*; 7 | 8 | #[test] 9 | fn random_moves() { 10 | let mut chess_board = Board::start_pos(); 11 | let mut moves = chess_board.generate_moves(); 12 | let mut i = 0; 13 | while i < 50 && !moves.is_empty() { 14 | chess_board.apply_move(moves[rand::random::() % moves.len()]); 15 | moves = chess_board.generate_moves(); 16 | i += 1; 17 | } 18 | } 19 | 20 | #[test] 21 | fn apply_null_moves() { 22 | let null_move = BitMove::null(); 23 | let mut trials = 0; 24 | 25 | while trials < 5 { 26 | let mut chess_board = Board::default(); 27 | let mut moves = chess_board.generate_moves(); 28 | let mut i = 0; 29 | while i < 70 && !moves.is_empty() { 30 | chess_board.apply_move(moves[rand::random::() % moves.len()]); 31 | moves = chess_board.generate_moves(); 32 | assert!(!chess_board.legal_move(null_move)); 33 | unsafe { 34 | if !chess_board.in_check() { 35 | chess_board.apply_null_move(); 36 | chess_board.undo_null_move(); 37 | } 38 | } 39 | 40 | i += 1; 41 | } 42 | trials += 1; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /pleco/src/bots/alphabeta.rs: -------------------------------------------------------------------------------- 1 | //! The alpha-beta algorithm. 2 | use super::*; 3 | use board::*; 4 | 5 | use super::{eval_board, ScoringMove}; 6 | 7 | const MAX_PLY: u16 = 5; 8 | 9 | pub fn alpha_beta_search(board: &mut Board, mut alpha: i16, beta: i16, depth: u16) -> ScoringMove { 10 | if depth == 0 { 11 | return eval_board(board); 12 | } 13 | 14 | let mut moves = board.generate_scoring_moves(); 15 | 16 | if moves.is_empty() { 17 | if board.in_check() { 18 | return ScoringMove::blank(-MATE_V); 19 | } else { 20 | return ScoringMove::blank(DRAW_V); 21 | } 22 | } 23 | 24 | let mut best_move = ScoringMove::blank(alpha); 25 | for mov in moves.iter_mut() { 26 | board.apply_move(mov.bit_move); 27 | mov.score = -alpha_beta_search(board, -beta, -alpha, depth - 1).score; 28 | board.undo_move(); 29 | if mov.score > alpha { 30 | alpha = mov.score; 31 | if alpha >= beta { 32 | return *mov; 33 | } 34 | best_move = *mov; 35 | } 36 | } 37 | 38 | best_move 39 | } 40 | 41 | pub fn alpha_beta_eval_bitmove( 42 | board: &mut Board, 43 | bm: BitMove, 44 | alpha: i16, 45 | beta: i16, 46 | depth: u16, 47 | ) -> i16 { 48 | board.apply_move(bm); 49 | let out = -alpha_beta_search(board, alpha, beta, depth).score; 50 | board.undo_move(); 51 | out 52 | } 53 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | 8 | jobs: 9 | lint: 10 | name: Lint 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - name: Lint 15 | run: cargo clippy 16 | - name: cargo fmt 17 | run: cargo fmt --all -- --check 18 | - name: check typos 19 | uses: crate-ci/typos@master 20 | with: 21 | config: typos.toml 22 | test-stable: 23 | name: Test stable 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: actions/checkout@v3 27 | - name: Build 28 | run: cargo +stable build --verbose 29 | - name: Run tests 30 | run: cargo +stable test --verbose 31 | test-nightly: 32 | name: Test nightly 33 | runs-on: ubuntu-latest 34 | steps: 35 | - uses: actions/checkout@v3 36 | - name: Install dependencies 37 | run: rustup install nightly 38 | - name: Build 39 | run: cargo +nightly build --verbose 40 | - name: Run tests 41 | run: cargo +nightly test --verbose 42 | test-beta: 43 | name: Test beta 44 | runs-on: ubuntu-latest 45 | steps: 46 | - uses: actions/checkout@v3 47 | - name: Install dependencies 48 | run: rustup install beta 49 | - name: Build 50 | run: cargo +beta build --verbose 51 | - name: Run tests 52 | run: cargo +beta test --verbose 53 | -------------------------------------------------------------------------------- /pleco/benches/bot_benches.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use criterion::{black_box, Bencher, Criterion, Fun}; 4 | 5 | use lazy_static; 6 | use pleco::bot_prelude::*; 7 | use pleco::tools::Searcher; 8 | use pleco::Board; 9 | 10 | lazy_static! { 11 | pub static ref RAND_BOARDS: Vec = { 12 | let mut vec = Vec::new(); 13 | vec.push(Board::start_pos()); 14 | vec 15 | }; 16 | } 17 | 18 | fn bench_searcher(b: &mut Bencher, data: &(&Vec, u16)) { 19 | b.iter(|| { 20 | for board in data.0.iter() { 21 | black_box(S::best_move(board.shallow_clone(), data.1)); 22 | } 23 | }) 24 | } 25 | 26 | fn bench_all_searchers_4_ply(c: &mut Criterion) { 27 | lazy_static::initialize(&RAND_BOARDS); 28 | let minimax = Fun::new("MiniMax", bench_searcher::); 29 | let parallel_minimax = Fun::new("ParallelMiniMax", bench_searcher::); 30 | let alpha_beta = Fun::new("AlphaBeta", bench_searcher::); 31 | let jamboree = Fun::new("Jamboree", bench_searcher::); 32 | 33 | let funs = vec![minimax, parallel_minimax, alpha_beta, jamboree]; 34 | 35 | c.bench_functions("Searcher Benches 4 ply", funs, (&RAND_BOARDS, 4)); 36 | } 37 | 38 | criterion_group!(name = bot_benches; 39 | config = Criterion::default().sample_size(11).warm_up_time(Duration::from_millis(100)); 40 | targets = bench_all_searchers_4_ply 41 | ); 42 | -------------------------------------------------------------------------------- /pleco/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pleco" 3 | version = "0.5.0" 4 | authors = ["Stephen Fleischman "] 5 | description = "A blazingly-fast chess library." 6 | homepage = "https://github.com/sfleischman105/Pleco" 7 | documentation = "https://docs.rs/pleco/" 8 | readme = "README.md" 9 | keywords = ["chess","ai","engine","game","gaming"] 10 | license = "MIT" 11 | categories = ["games","game-engines"] 12 | repository = "https://github.com/sfleischman105/Pleco" 13 | autobenches = false 14 | 15 | include = [ 16 | "src/*", 17 | "tests/*.rs", 18 | "Cargo.toml", 19 | ".gitignore", 20 | "README.md", 21 | ] 22 | 23 | [lib] 24 | name = "pleco" 25 | path = "src/lib.rs" 26 | doctest = true 27 | 28 | [profile.dev] 29 | opt-level = 3 30 | 31 | [profile.release] 32 | opt-level = 3 33 | debug = false 34 | debug-assertions = false 35 | panic = "unwind" 36 | codegen-units = 1 37 | lto = true 38 | 39 | [profile.bench] 40 | opt-level = 3 41 | debug = false 42 | lto = true 43 | debug-assertions = false 44 | codegen-units = 1 45 | 46 | [profile.test] 47 | opt-level = 3 48 | debug = true 49 | debug-assertions = true 50 | codegen-units = 4 51 | 52 | 53 | [dependencies] 54 | bitflags = "1.3.2" 55 | rand = "0.8.5" 56 | rayon = "1.5.3" 57 | num_cpus = "1.13.1" 58 | mucow = "0.1.0" 59 | lazy_static = "1.4.0" 60 | 61 | [features] 62 | default = [] 63 | nightly = [] 64 | 65 | [dev-dependencies] 66 | criterion = { version = '0.2.10', default-features = false} 67 | 68 | [[bench]] 69 | name = "bench_main" 70 | harness = false 71 | -------------------------------------------------------------------------------- /pleco/benches/bit_benches.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use criterion::{black_box, Bencher, Criterion, Fun}; 4 | use pleco::core::bit_twiddles::*; 5 | use pleco::core::bitboard::{BitBoard, RandBitBoard}; 6 | 7 | fn popcount_rust(b: &mut Bencher, data: &Vec) { 8 | b.iter(|| { 9 | { 10 | for bits in data.iter() { 11 | black_box({ 12 | black_box(black_box(bits.0)).count_ones(); 13 | }) 14 | } 15 | }; 16 | black_box(()) 17 | }); 18 | } 19 | 20 | fn popcount_old_8(b: &mut Bencher, data: &Vec) { 21 | b.iter(|| { 22 | { 23 | for bits in data.iter() { 24 | black_box({ 25 | popcount_table(black_box(bits.0)); 26 | }) 27 | } 28 | }; 29 | black_box(()) 30 | }); 31 | } 32 | 33 | fn popcount(c: &mut Criterion) { 34 | let bit_set_dense_100: Vec = RandBitBoard::default() 35 | .pseudo_random(2661634) 36 | .avg(6) 37 | .max(11) 38 | .many(1000); 39 | 40 | let popcnt_rust = Fun::new("Popcount Rust", popcount_rust); 41 | let popcnt_old = Fun::new("Popcount Old", popcount_old_8); 42 | let funs = vec![popcnt_rust, popcnt_old]; 43 | 44 | c.bench_functions("PopCount", funs, bit_set_dense_100); 45 | } 46 | 47 | criterion_group!(name = bit_benches; 48 | config = Criterion::default() 49 | .sample_size(100) 50 | .warm_up_time(Duration::from_millis(1)); 51 | targets = popcount 52 | ); 53 | -------------------------------------------------------------------------------- /pleco_engine/benches/startpos_benches.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, BatchSize, Bencher, Criterion}; 2 | use std::time::Duration; 3 | 4 | use pleco::Board; 5 | 6 | use pleco_engine::engine::PlecoSearcher; 7 | use pleco_engine::threadpool::*; 8 | use pleco_engine::time::uci_timer::PreLimits; 9 | 10 | use super::*; 11 | 12 | fn search_singular_engine(b: &mut Bencher) { 13 | let mut pre_limit = PreLimits::blank(); 14 | pre_limit.depth = Some(D::depth()); 15 | let mut searcher = PlecoSearcher::init(false); 16 | let limit = pre_limit.create(); 17 | b.iter_batched( 18 | || { 19 | threadpool().clear_all(); 20 | searcher.clear_tt(); 21 | Board::start_pos() 22 | }, 23 | |board| { 24 | black_box(threadpool().search(&board, &limit)); 25 | }, 26 | BatchSize::PerIteration, 27 | ) 28 | } 29 | 30 | fn bench_engine_evaluations(c: &mut Criterion) { 31 | c.bench_function("Search Singular Depth 5", search_singular_engine::); 32 | c.bench_function("Search Singular Depth 6", search_singular_engine::); 33 | c.bench_function("Search Singular Depth 7", search_singular_engine::); 34 | c.bench_function("Search Singular Depth 8", search_singular_engine::); 35 | c.bench_function("Search Singular Depth 9", search_singular_engine::); 36 | } 37 | 38 | criterion_group!(name = search_singular; 39 | config = Criterion::default() 40 | .sample_size(35) 41 | .warm_up_time(Duration::from_millis(150)); 42 | targets = bench_engine_evaluations 43 | ); 44 | -------------------------------------------------------------------------------- /pleco_engine/src/tables/butterfly.rs: -------------------------------------------------------------------------------- 1 | use pleco::core::masks::*; 2 | use pleco::{BitMove, Player}; 3 | use std::ops::{Index, IndexMut}; 4 | 5 | use super::{NumStatCube, StatBoard}; 6 | 7 | /// ButterflyBoards are 2 tables (one for each color) indexed by the move's from 8 | /// and to squares, see chessprogramming.wikispaces.com/Butterfly+Boards 9 | pub struct ButterflyHistory { 10 | a: [[i16; (SQ_CNT * SQ_CNT)]; PLAYER_CNT], 11 | } 12 | 13 | // [Us][Move], Or rather [Us][To SQ][From SQ] 14 | #[allow(non_camel_case_types)] 15 | type BF_idx = (Player, BitMove); 16 | 17 | impl Index for ButterflyHistory { 18 | type Output = i16; 19 | 20 | #[inline(always)] 21 | fn index(&self, idx: BF_idx) -> &Self::Output { 22 | unsafe { 23 | let from_to = idx.1.from_to() as usize; 24 | self.a 25 | .get_unchecked(idx.0 as usize) // [player] 26 | .get_unchecked(from_to) // [From SQ][to SQ] 27 | } 28 | } 29 | } 30 | 31 | impl IndexMut for ButterflyHistory { 32 | #[inline(always)] 33 | fn index_mut(&mut self, idx: BF_idx) -> &mut Self::Output { 34 | unsafe { 35 | let from_to = idx.1.from_to() as usize; 36 | self.a 37 | .get_unchecked_mut(idx.0 as usize) // [player] 38 | .get_unchecked_mut(from_to) // [From SQ][to SQ] 39 | } 40 | } 41 | } 42 | 43 | impl StatBoard for ButterflyHistory { 44 | const FILL: i16 = 0; 45 | } 46 | 47 | impl NumStatCube for ButterflyHistory { 48 | const D: i32 = 324; 49 | const W: i32 = 32; 50 | } 51 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | ## Branding 2 | 3 | Create logo and branding 4 | Setup Github org 5 | Setup docs and move content to there: https://github.com/pleco-rs/Pleco/issues/51 6 | Revise all readmes 7 | Create website 8 | Revise what files are included in the crates 9 | 10 | ## Publishing 11 | 12 | Publish new version of crates 13 | Raise PR for other protocols to use pleco again 14 | 15 | ## Development 16 | 17 | Test engine in Arena: https://github.com/pleco-rs/Pleco/issues/132 18 | Port over Stockfish end of game table: https://github.com/pleco-rs/Pleco/issues/113 19 | Review unstable features and which ones we can add back: https://github.com/pleco-rs/Pleco/issues/77 20 | Do some code profiling to see where the bottlenecks are 21 | Suggestions from here (Fix nightly warnings): https://github.com/sfleischman105/Pleco/issues/131 (then remove `uninit_assumed_init` and `missing_safety_doc`) 22 | 23 | ## Integrations 24 | 25 | Give speed comparison vs Stockfish: https://github.com/pleco-rs/Pleco/issues/128 26 | Create comparison with other projects: https://github.com/pleco-rs/Pleco/issues/126 27 | Update Chess engine competitive list 28 | Setup Lichess playable bot 29 | 30 | ## New features 31 | 32 | Implement PGN Parser: https://github.com/pleco-rs/Pleco/issues/71 33 | Consider splitting up repos 34 | Look at all changes stockfish has made since Pleco was created, port over meaningful ones that will benefit Pleco 35 | 36 | ================================================ 37 | 38 | Update more packages 39 | Use the Chess.dom analyser to see weaknesses 40 | Consider moving the board ranking to the pleco_engine package 41 | Add cool runtime output 42 | -------------------------------------------------------------------------------- /pleco/benches/perft_benches.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use criterion::{black_box, Bencher, Criterion, Fun}; 4 | 5 | use pleco::board::perft::*; 6 | use pleco::board::Board; 7 | 8 | fn perft_3(b: &mut Bencher, boards: &Vec) { 9 | b.iter(|| { 10 | for board in boards.iter() { 11 | black_box(perft(board, 3)); 12 | } 13 | }) 14 | } 15 | 16 | fn perft_4(b: &mut Bencher, boards: &Vec) { 17 | b.iter(|| { 18 | for board in boards.iter() { 19 | black_box(perft(board, 4)); 20 | } 21 | }) 22 | } 23 | 24 | fn perft_all(c: &mut Criterion) { 25 | let rand_boards: Vec = RAND_BOARDS_ALL 26 | .iter() 27 | .map(|b| Board::from_fen(b).unwrap()) 28 | .collect(); 29 | 30 | let perft_3_f = Fun::new("Perft 3", perft_3); 31 | let perft_4_f = Fun::new("Perft 4", perft_4); 32 | 33 | let funs = vec![perft_3_f, perft_4_f]; 34 | 35 | c.bench_functions("Perft All", funs, rand_boards); 36 | } 37 | 38 | criterion_group!(name = perft_benches; 39 | config = Criterion::default() 40 | .sample_size(12) 41 | .warm_up_time(Duration::from_millis(20)); 42 | targets = perft_all 43 | ); 44 | 45 | static RAND_BOARDS_ALL: [&str; 6] = [ 46 | "rn2k3/pp1qPppr/5n2/1b2B3/8/4NP2/3NP1PP/R2K1B1R b q - 0 23", 47 | "r1bqkbnr/ppp2ppp/2np4/4p3/4PQ2/2NP4/PPP1NPPP/R1B1KB1R w KQkq e6 0 8", 48 | "r1bqkb1r/pp2pp2/2p2n2/6Q1/7p/2N4P/PP1B1PP1/R3KBNR w KQkq - 0 14", 49 | "3k4/6b1/1p5p/4p3/5rP1/6K1/8/ w - - 0 40", 50 | "1k6/1p1n4/p6p/4P3/2P5/1R6/5K1P/4R b - - 2 33", 51 | "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", 52 | ]; 53 | -------------------------------------------------------------------------------- /pleco_engine/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pleco_engine" 3 | version = "0.1.6" 4 | authors = ["Stephen Fleischman "] 5 | description = "A blazingly-fast Chess AI." 6 | homepage = "https://github.com/sfleischman105/Pleco" 7 | documentation = "https://docs.rs/pleco_engine/" 8 | readme = "README.md" 9 | keywords = ["chess","ai","engine","game","uci"] 10 | license = "GPL-3.0+" 11 | categories = ["games","game-engines","command-line-utilities"] 12 | repository = "https://github.com/sfleischman105/Pleco" 13 | autobenches = false 14 | 15 | include = [ 16 | "src/*", 17 | "tests/*.rs", 18 | "Cargo.toml", 19 | ".gitignore", 20 | "README.md" 21 | ] 22 | 23 | [profile.release] 24 | opt-level = 3 25 | debug = false 26 | debug-assertions = false 27 | panic = "abort" 28 | codegen-units = 1 29 | lto = true 30 | 31 | [profile.bench] 32 | opt-level = 3 33 | debug = false 34 | rpath = false 35 | lto = true 36 | debug-assertions = false 37 | codegen-units = 1 38 | 39 | [profile.test] 40 | opt-level = 3 41 | debug = true 42 | debug-assertions = true 43 | codegen-units = 1 44 | 45 | [lib] 46 | name = "pleco_engine" 47 | bench = true 48 | path = "src/lib.rs" 49 | doctest = true 50 | 51 | [dependencies] 52 | pleco = { path = "../pleco", version = "0.5.0" } 53 | chrono = "0.4.19" 54 | rand = "0.8.5" 55 | num_cpus = "1.13.1" 56 | 57 | [features] 58 | default = [] 59 | 60 | [[bin]] 61 | name = "pleco" 62 | path = "src/main.rs" 63 | test = false 64 | doc = false 65 | 66 | [dev-dependencies] 67 | criterion = { version = '0.2.10', default-features = false } 68 | lazy_static = {version = "1.3.0"} 69 | 70 | [[bench]] 71 | name = "bench_engine_main" 72 | harness = false 73 | -------------------------------------------------------------------------------- /pleco_engine/src/tables/capture_piece_history.rs: -------------------------------------------------------------------------------- 1 | use pleco::core::masks::*; 2 | use pleco::{Piece, PieceType, SQ}; 3 | use std::ops::{Index, IndexMut}; 4 | 5 | use super::{NumStatCube, StatBoard}; 6 | 7 | /// CapturePieceToBoards are addressed by a move's 8 | /// [player][moved piecetype][to][captured piecetype] information. 9 | pub struct CapturePieceToHistory { 10 | a: [[[i16; PIECE_TYPE_CNT]; SQ_CNT]; PIECE_CNT], 11 | } 12 | 13 | // [player][moved piecetype][to][captured piecetype] 14 | #[allow(non_camel_case_types)] 15 | type CP_idx = (Piece, SQ, PieceType); 16 | 17 | impl Index for CapturePieceToHistory { 18 | type Output = i16; 19 | 20 | #[inline(always)] 21 | fn index(&self, idx: CP_idx) -> &Self::Output { 22 | unsafe { 23 | self.a 24 | .get_unchecked(idx.0 as usize) // [Moved Piece] 25 | .get_unchecked((idx.1).0 as usize) // [to square] 26 | .get_unchecked(idx.2 as usize) // [Captured piece type] 27 | } 28 | } 29 | } 30 | 31 | impl IndexMut for CapturePieceToHistory { 32 | #[inline(always)] 33 | fn index_mut(&mut self, idx: CP_idx) -> &mut Self::Output { 34 | unsafe { 35 | self.a 36 | .get_unchecked_mut(idx.0 as usize) // [Moved Piece] 37 | .get_unchecked_mut((idx.1).0 as usize) // [to square] 38 | .get_unchecked_mut(idx.2 as usize) // [Captured piece type] 39 | } 40 | } 41 | } 42 | 43 | impl StatBoard for CapturePieceToHistory { 44 | const FILL: i16 = 0; 45 | } 46 | 47 | impl NumStatCube for CapturePieceToHistory { 48 | const D: i32 = 324; 49 | const W: i32 = 2; 50 | } 51 | -------------------------------------------------------------------------------- /pleco/tests/board_properties.rs: -------------------------------------------------------------------------------- 1 | extern crate pleco; 2 | extern crate rand; 3 | 4 | use pleco::board::Board; 5 | use std::*; 6 | 7 | #[test] 8 | fn threefold_repetition() { 9 | let mut chess_board = Board::start_pos(); 10 | assert!(!chess_board.threefold_repetition()); 11 | assert!(!chess_board.stalemate()); 12 | chess_board.apply_uci_move("e2e4"); 13 | assert!(!chess_board.threefold_repetition()); 14 | assert!(!chess_board.stalemate()); 15 | chess_board.apply_uci_move("e7e5"); 16 | assert!(!chess_board.threefold_repetition()); 17 | assert!(!chess_board.stalemate()); 18 | chess_board.apply_uci_move("f1c4"); 19 | assert!(!chess_board.threefold_repetition()); 20 | assert!(!chess_board.stalemate()); 21 | chess_board.apply_uci_move("f8c5"); 22 | assert!(!chess_board.threefold_repetition()); 23 | assert!(!chess_board.stalemate()); 24 | chess_board.apply_uci_move("c4f1"); 25 | assert!(!chess_board.threefold_repetition()); 26 | assert!(!chess_board.stalemate()); 27 | chess_board.apply_uci_move("c5f8"); 28 | assert!(!chess_board.threefold_repetition()); 29 | assert!(!chess_board.stalemate()); 30 | chess_board.apply_uci_move("f1c4"); 31 | assert!(!chess_board.threefold_repetition()); 32 | assert!(!chess_board.stalemate()); 33 | chess_board.apply_uci_move("f8c5"); 34 | assert!(!chess_board.threefold_repetition()); 35 | assert!(!chess_board.stalemate()); 36 | chess_board.apply_uci_move("c4f1"); 37 | assert!(!chess_board.threefold_repetition()); 38 | assert!(!chess_board.stalemate()); 39 | chess_board.apply_uci_move("c5f8"); 40 | assert!(chess_board.threefold_repetition()); 41 | assert!(chess_board.stalemate()); 42 | } 43 | -------------------------------------------------------------------------------- /pleco_engine/src/sync/mod.rs: -------------------------------------------------------------------------------- 1 | //! Useful synchronization primitives. 2 | 3 | use std::sync::{Condvar, Mutex}; 4 | 5 | /// A `LockLatch` starts as false and eventually becomes true. You can block 6 | /// until it becomes true. 7 | pub struct LockLatch { 8 | m: Mutex, 9 | v: Condvar, 10 | } 11 | 12 | impl LockLatch { 13 | #[inline] 14 | pub fn new() -> LockLatch { 15 | LockLatch { 16 | m: Mutex::new(false), 17 | v: Condvar::new(), 18 | } 19 | } 20 | 21 | /// Block until latch is set. 22 | #[inline] 23 | pub fn wait(&self) { 24 | let mut guard = self.m.lock().unwrap(); 25 | while !*guard { 26 | guard = self.v.wait(guard).unwrap(); 27 | } 28 | } 29 | 30 | // Sets the lock to true and notifies any threads waiting on it. 31 | #[inline] 32 | pub fn set(&self) { 33 | let mut guard = self.m.lock().unwrap(); 34 | *guard = true; 35 | self.v.notify_all(); 36 | } 37 | 38 | // Locks the latch, causing threads to await its unlocking. 39 | #[inline] 40 | pub fn lock(&self) { 41 | let mut guard = self.m.lock().unwrap(); 42 | *guard = false; 43 | } 44 | 45 | #[inline] 46 | fn new_value(value: bool) -> LockLatch { 47 | LockLatch { 48 | m: Mutex::new(value), 49 | v: Condvar::new(), 50 | } 51 | } 52 | 53 | #[inline] 54 | fn set_value(&self, value: bool) { 55 | let mut guard = self.m.lock().unwrap(); 56 | *guard = value; 57 | self.v.notify_all(); 58 | } 59 | 60 | #[inline] 61 | fn await_value(&self, value: bool) { 62 | let mut guard = self.m.lock().unwrap(); 63 | while *guard != value { 64 | guard = self.v.wait(guard).unwrap(); 65 | } 66 | } 67 | } 68 | 69 | /// A `GuardedBool` allows for waiting on a specific bool value. 70 | pub struct GuardedBool { 71 | a: LockLatch, 72 | } 73 | 74 | impl GuardedBool { 75 | #[inline] 76 | pub fn new(value: bool) -> GuardedBool { 77 | GuardedBool { 78 | a: LockLatch::new_value(value), 79 | } 80 | } 81 | 82 | /// Sets the value. 83 | #[inline] 84 | pub fn set(&self, value: bool) { 85 | self.a.set_value(value); 86 | } 87 | 88 | /// Awaits a value. 89 | #[inline] 90 | pub fn wait(&self, value: bool) { 91 | self.a.await_value(value); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /pleco/tests/fen_building.rs: -------------------------------------------------------------------------------- 1 | extern crate pleco; 2 | 3 | use pleco::board::Board; 4 | use pleco::core::{PieceType, Player}; 5 | 6 | // https://chessprogramming.wikispaces.com/Forsyth-Edwards+Notation 7 | // "r1bqkbr1/1ppppp1N/p1n3pp/8/1P2PP2/3P4/P1P2nPP/RNBQKBR1 b KQkq -", 8 | 9 | #[test] 10 | fn basic_fen() { 11 | // Test if positions int he right place 12 | let board = Board::from_fen("k6r/1p2b3/8/8/8/8/P4KPP/1B5R w KQkq - 0 3").unwrap(); 13 | assert_eq!(board.count_piece(Player::White, PieceType::P), 3); 14 | assert_eq!(board.count_piece(Player::White, PieceType::N), 0); 15 | assert_eq!(board.count_piece(Player::White, PieceType::B), 1); 16 | assert_eq!(board.count_piece(Player::White, PieceType::R), 1); 17 | assert_eq!(board.count_piece(Player::White, PieceType::Q), 0); 18 | assert_eq!(board.count_piece(Player::White, PieceType::K), 1); 19 | assert_eq!(board.count_piece(Player::Black, PieceType::P), 1); 20 | assert_eq!(board.count_piece(Player::Black, PieceType::N), 0); 21 | assert_eq!(board.count_piece(Player::Black, PieceType::B), 1); 22 | assert_eq!(board.count_piece(Player::Black, PieceType::R), 1); 23 | assert_eq!(board.count_piece(Player::Black, PieceType::Q), 0); 24 | assert_eq!(board.count_piece(Player::Black, PieceType::K), 1); 25 | 26 | let board = Board::from_fen("8/2Q1pk2/nbpppppp/8/8/2K4N/PPPPPPPP/BBB2BBB w - - 0 10").unwrap(); 27 | assert_eq!(board.count_piece(Player::White, PieceType::P), 8); 28 | assert_eq!(board.count_piece(Player::White, PieceType::N), 1); 29 | assert_eq!(board.count_piece(Player::White, PieceType::B), 6); 30 | assert_eq!(board.count_piece(Player::White, PieceType::R), 0); 31 | assert_eq!(board.count_piece(Player::White, PieceType::Q), 1); 32 | assert_eq!(board.count_piece(Player::White, PieceType::K), 1); 33 | assert_eq!(board.count_piece(Player::Black, PieceType::P), 7); 34 | assert_eq!(board.count_piece(Player::Black, PieceType::N), 1); 35 | assert_eq!(board.count_piece(Player::Black, PieceType::B), 1); 36 | assert_eq!(board.count_piece(Player::Black, PieceType::R), 0); 37 | assert_eq!(board.count_piece(Player::Black, PieceType::Q), 0); 38 | assert_eq!(board.count_piece(Player::Black, PieceType::K), 1); 39 | } 40 | 41 | #[test] 42 | fn all_fens() { 43 | for fen in pleco::board::fen::ALL_FENS.iter() { 44 | let board = Board::from_fen(fen).unwrap(); 45 | assert_eq!(*fen, board.fen()); 46 | } 47 | } 48 | 49 | #[test] 50 | fn rank8_zero_fen() { 51 | let fen = "8/2Q1pk2/nbpppppp/8/8/2K4N/PPPPPPPP/BBB2BBB w - - 0 10"; 52 | let board = Board::from_fen(fen).unwrap(); 53 | assert_eq!(fen, board.fen()); 54 | } 55 | -------------------------------------------------------------------------------- /pleco/src/tools/prng.rs: -------------------------------------------------------------------------------- 1 | //! Contains the Pseudo-random number generator. Used for generating random `Board`s and 2 | //! `BitBoard`s. 3 | 4 | use std::mem::transmute; 5 | 6 | /// Object for generating pseudo-random numbers. 7 | pub struct PRNG { 8 | seed: u64, 9 | } 10 | 11 | impl PRNG { 12 | /// Creates PRNG from a seed. 13 | /// 14 | /// # Panics 15 | /// 16 | /// Undefined behavior if the seed is zero 17 | #[inline(always)] 18 | pub fn init(s: u64) -> PRNG { 19 | PRNG { seed: s } 20 | } 21 | 22 | /// Returns a pseudo-random number. 23 | #[allow(dead_code)] 24 | pub fn rand(&mut self) -> u64 { 25 | self.rand_change() 26 | } 27 | 28 | /// Returns a pseudo-random number with on average 8 bits being set. 29 | pub fn sparse_rand(&mut self) -> u64 { 30 | let mut s = self.rand_change(); 31 | s &= self.rand_change(); 32 | s &= self.rand_change(); 33 | s 34 | } 35 | 36 | /// Returns a u64 with exactly one bit set in a random location. 37 | pub fn singular_bit(&mut self) -> u64 { 38 | let arr: [u8; 8] = unsafe { transmute(self.rand() ^ self.rand()) }; 39 | let byte: u8 = arr.iter().fold(0, |acc, &x| acc ^ x); 40 | (1u64).wrapping_shl(((byte) >> 2) as u32) 41 | } 42 | 43 | /// Randomizes the current seed and returns a random value. 44 | fn rand_change(&mut self) -> u64 { 45 | self.seed ^= self.seed >> 12; 46 | self.seed ^= self.seed << 25; 47 | self.seed ^= self.seed >> 27; 48 | self.seed.wrapping_mul(2685_8216_5773_6338_717) 49 | } 50 | } 51 | 52 | #[cfg(test)] 53 | mod test { 54 | use super::PRNG; 55 | 56 | const ROUNDS: u32 = 4; 57 | const MUTS: u32 = 32; 58 | 59 | #[test] 60 | fn check_bit_displacement() { 61 | let mut seeder = PRNG::init(10300014); 62 | let mut acc = [0u32; 64]; 63 | for _ in 0..ROUNDS { 64 | let mut prng = PRNG::init(seeder.rand()); 65 | for _ in 0..MUTS { 66 | add_to_bit_counts(prng.singular_bit(), &mut acc); 67 | } 68 | } 69 | 70 | let max = *acc.iter().max().unwrap(); 71 | for (_i, m) in acc.iter_mut().enumerate() { 72 | *m *= 100; 73 | *m /= max; 74 | //println!("{} : {}",_i, m); 75 | } 76 | 77 | let _sum: u32 = acc.iter().sum(); 78 | // println!("avg: {}", _sum / 64); 79 | } 80 | 81 | fn add_to_bit_counts(mut num: u64, acc: &mut [u32; 64]) { 82 | while num != 0 { 83 | let i = num.trailing_zeros(); 84 | acc[i as usize] += 1; 85 | num &= !((1u64) << i); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /pleco_engine/src/root_moves/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module contains the basic RootMove structures, allowing for storage of the moves from a specific position 2 | //! alongside information about each of the moves. 3 | 4 | pub mod root_moves_list; 5 | 6 | use std::cmp::Ordering as CmpOrder; 7 | 8 | use pleco::core::score::*; 9 | use pleco::BitMove; 10 | 11 | // 250 as this fits into 64 byte cache lines easily. 12 | const MAX_MOVES: usize = 250; 13 | 14 | /// Keeps track of information of a move for the position to be searched. 15 | #[derive(Copy, Clone, Eq)] 16 | pub struct RootMove { 17 | pub score: i32, 18 | pub prev_score: i32, 19 | pub bit_move: BitMove, 20 | pub depth_reached: i16, 21 | } 22 | 23 | impl RootMove { 24 | /// Creates a new `RootMove`. 25 | #[inline] 26 | pub fn new(bit_move: BitMove) -> Self { 27 | RootMove { 28 | bit_move, 29 | score: NEG_INFINITE as i32, 30 | prev_score: NEG_INFINITE as i32, 31 | depth_reached: 0, 32 | } 33 | } 34 | 35 | /// Places the current score into the previous_score field, and then updates 36 | /// the score and depth. 37 | #[inline] 38 | pub fn rollback_insert(&mut self, score: i32, depth: i16) { 39 | self.prev_score = self.score; 40 | self.score = score; 41 | self.depth_reached = depth; 42 | } 43 | 44 | /// Inserts a score and depth. 45 | #[inline] 46 | pub fn insert(&mut self, score: i32, depth: i16) { 47 | self.score = score; 48 | self.depth_reached = depth; 49 | } 50 | 51 | /// Places the current score in the previous score. 52 | #[inline] 53 | pub fn rollback(&mut self) { 54 | self.prev_score = self.score; 55 | } 56 | } 57 | 58 | // Moves with higher score for a higher depth are less 59 | impl Ord for RootMove { 60 | #[inline] 61 | fn cmp(&self, other: &RootMove) -> CmpOrder { 62 | let value_diff = self.score - other.score; 63 | if value_diff == 0 { 64 | let prev_value_diff = self.prev_score - other.prev_score; 65 | if prev_value_diff == 0 { 66 | return CmpOrder::Equal; 67 | } else if prev_value_diff > 0 { 68 | return CmpOrder::Less; 69 | } 70 | } else if value_diff > 0 { 71 | return CmpOrder::Less; 72 | } 73 | CmpOrder::Greater 74 | } 75 | } 76 | 77 | impl PartialOrd for RootMove { 78 | fn partial_cmp(&self, other: &RootMove) -> Option { 79 | Some(self.cmp(other)) 80 | } 81 | } 82 | 83 | impl PartialEq for RootMove { 84 | fn eq(&self, other: &RootMove) -> bool { 85 | self.score == other.score && self.prev_score == other.prev_score 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /pleco/tests/pseudo_legal_checks.rs: -------------------------------------------------------------------------------- 1 | extern crate pleco; 2 | 3 | use std::u16::MAX; 4 | 5 | use pleco::board::fen::ALL_FENS; 6 | use pleco::{BitMove, Board}; 7 | 8 | #[test] 9 | fn pseudolegal_all_fens() { 10 | for fen in ALL_FENS.iter() { 11 | let board = Board::from_fen(fen).unwrap(); 12 | pseudolegal_correctness(&board); 13 | } 14 | } 15 | 16 | #[test] 17 | fn pseudolegal_rand() { 18 | for _x in 0..9 { 19 | let board = Board::random().one(); 20 | pseudolegal_correctness(&board); 21 | } 22 | } 23 | 24 | #[test] 25 | fn pseudolegal_incheck() { 26 | let board = 27 | Board::from_fen("r1b1kb1r/pp2nppp/2pp4/4p3/7P/2Pn2P1/PPq1NPB1/RNB1K1R1 w Qkq - 4 17") 28 | .unwrap(); 29 | pseudolegal_correctness(&board); 30 | let board = Board::from_fen("k1r/pp3ppp/n7/3R4/1P5q/1P6/3Kb3/3r4 w - - 1 30").unwrap(); 31 | pseudolegal_correctness(&board); 32 | } 33 | 34 | fn pseudolegal_correctness(board: &Board) { 35 | let pseudo_moves = board.generate_pseudolegal_moves(); 36 | for x in 0..MAX { 37 | let bit_move = BitMove::new(x); 38 | if board.pseudo_legal_move(bit_move) { 39 | if !pseudo_moves.contains(&bit_move) { 40 | panic!( 41 | "\nNot a Pseudo-legal move!\ 42 | \n fen: {}\ 43 | \n move: {} bits: {:b}\n", 44 | board.fen(), 45 | bit_move, 46 | bit_move.get_raw() 47 | ); 48 | } 49 | } else if pseudo_moves.contains(&bit_move) && board.legal_move(bit_move) { 50 | panic!( 51 | "\nBoard::pseudolegal move returned false, when it should be true!\ 52 | \n fen: {}\ 53 | \n move: {} bits: {:b}\n", 54 | board.fen(), 55 | bit_move, 56 | bit_move.get_raw() 57 | ); 58 | } 59 | } 60 | } 61 | 62 | #[test] 63 | fn legal_all_fens() { 64 | for fen in ALL_FENS.iter() { 65 | let board = Board::from_fen(fen).unwrap(); 66 | legal_correctness(&board); 67 | } 68 | } 69 | 70 | #[test] 71 | fn legal_rand() { 72 | for _x in 0..10 { 73 | let board = Board::random().one(); 74 | legal_correctness(&board); 75 | } 76 | } 77 | 78 | fn legal_correctness(board: &Board) { 79 | let moves = board.generate_moves(); 80 | for m in moves.iter() { 81 | if !board.pseudo_legal_move(*m) { 82 | panic!( 83 | "\nLegal move was not pseudo legal!\ 84 | \n fen: {}\ 85 | \n move: {} bits: {:b}\n", 86 | board.fen(), 87 | m, 88 | m.get_raw() 89 | ); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /pleco/src/bots/jamboree.rs: -------------------------------------------------------------------------------- 1 | //! The jamboree algorithm. 2 | use super::alphabeta::alpha_beta_search; 3 | use super::ScoringMove; 4 | use super::*; 5 | use board::*; 6 | use rayon; 7 | 8 | const DIVIDE_CUTOFF: usize = 5; 9 | const DIVISOR_SEQ: usize = 4; 10 | 11 | pub fn jamboree( 12 | board: &mut Board, 13 | mut alpha: i16, 14 | beta: i16, 15 | depth: u16, 16 | plys_seq: u16, 17 | ) -> ScoringMove { 18 | assert!(alpha <= beta); 19 | if depth <= 2 { 20 | return alpha_beta_search(board, alpha, beta, depth); 21 | } 22 | 23 | let mut moves = board.generate_scoring_moves(); 24 | 25 | if moves.is_empty() { 26 | if board.in_check() { 27 | return ScoringMove::blank(-MATE_V); 28 | } else { 29 | return ScoringMove::blank(DRAW_V); 30 | } 31 | } 32 | 33 | let amount_seq: usize = 1 + (moves.len() / DIVISOR_SEQ).min(2) as usize; 34 | let (seq, non_seq) = moves.split_at_mut(amount_seq); 35 | 36 | let mut best_move: ScoringMove = ScoringMove::blank(alpha); 37 | 38 | for mov in seq { 39 | board.apply_move(mov.bit_move); 40 | mov.score = -jamboree(board, -beta, -alpha, depth - 1, plys_seq).score; 41 | board.undo_move(); 42 | 43 | if mov.score > alpha { 44 | alpha = mov.score; 45 | if alpha >= beta { 46 | return *mov; 47 | } 48 | best_move = *mov; 49 | } 50 | } 51 | 52 | parallel_task(non_seq, board, alpha, beta, depth, plys_seq).max(best_move) 53 | } 54 | 55 | fn parallel_task( 56 | slice: &mut [ScoringMove], 57 | board: &mut Board, 58 | mut alpha: i16, 59 | beta: i16, 60 | depth: u16, 61 | plys_seq: u16, 62 | ) -> ScoringMove { 63 | if slice.len() <= DIVIDE_CUTOFF { 64 | let mut best_move: ScoringMove = ScoringMove::blank(alpha); 65 | for mov in slice { 66 | board.apply_move(mov.bit_move); 67 | mov.score = -jamboree(board, -beta, -alpha, depth - 1, plys_seq).score; 68 | board.undo_move(); 69 | if mov.score > alpha { 70 | alpha = mov.score; 71 | if alpha >= beta { 72 | return *mov; 73 | } 74 | best_move = *mov; 75 | } 76 | } 77 | best_move 78 | } else { 79 | let mid_point = slice.len() / 2; 80 | let (left, right) = slice.split_at_mut(mid_point); 81 | let mut left_clone = board.parallel_clone(); 82 | 83 | let (left_move, right_move): (ScoringMove, ScoringMove) = rayon::join( 84 | || parallel_task(left, &mut left_clone, alpha, beta, depth, plys_seq), 85 | || parallel_task(right, board, alpha, beta, depth, plys_seq), 86 | ); 87 | 88 | left_move.max(right_move) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /pleco/src/helper/zobrist.rs: -------------------------------------------------------------------------------- 1 | use core::masks::*; 2 | use tools::prng::PRNG; 3 | use {BitBoard, Piece, SQ}; 4 | 5 | /// Seed for the Zobrist's pseudo-random number generator. 6 | const ZOBRIST_SEED: u64 = 23_081; 7 | 8 | /// Zobrist key for each piece on each square. 9 | static mut ZOBRIST_PIECE_SQUARE: [[u64; PIECE_CNT]; SQ_CNT] = [[0; PIECE_CNT]; SQ_CNT]; 10 | 11 | /// Zobrist key for each possible en-passant capturable file. 12 | static mut ZOBRIST_ENPASSANT: [u64; FILE_CNT] = [0; FILE_CNT]; 13 | 14 | /// Zobrist key for each possible castling rights. 15 | static mut ZOBRIST_CASTLE: [u64; ALL_CASTLING_RIGHTS] = [0; ALL_CASTLING_RIGHTS]; 16 | 17 | /// Zobrist key for the side to move. 18 | static mut ZOBRIST_SIDE: u64 = 0; 19 | 20 | /// Zobrist key for having no pawns; 21 | static mut ZOBRIST_NO_PAWNS: u64 = 0; 22 | 23 | /// initialize the zobrist hash 24 | #[cold] 25 | pub fn init_zobrist() { 26 | let mut rng = PRNG::init(ZOBRIST_SEED); 27 | 28 | unsafe { 29 | for i in 0..SQ_CNT { 30 | for j in (Piece::WhitePawn as usize)..(Piece::BlackKing as usize) { 31 | ZOBRIST_PIECE_SQUARE[i][j] = rng.rand(); 32 | ZOBRIST_PIECE_SQUARE[i][j] = rng.rand(); 33 | } 34 | } 35 | 36 | for i in 0..FILE_CNT { 37 | ZOBRIST_ENPASSANT[i] = rng.rand() 38 | } 39 | 40 | for cr in 0..ALL_CASTLING_RIGHTS { 41 | ZOBRIST_CASTLE[cr] = 0; 42 | 43 | // We do this as having all castling rights is similar to having all individual 44 | // castling rights. So, ALL_CASTLE = CASLTE_Q_W ^ CASLTE_Q_B ^ CASLTE_K_W ^ CASLTE_K_B 45 | let mut b = BitBoard(cr as u64); 46 | while let Some(s) = b.pop_some_lsb() { 47 | let mut k: u64 = ZOBRIST_CASTLE[1 << s.0 as usize]; 48 | if k == 0 { 49 | k = rng.rand(); 50 | } 51 | ZOBRIST_CASTLE[cr] ^= k; 52 | } 53 | } 54 | ZOBRIST_SIDE = rng.rand(); 55 | ZOBRIST_NO_PAWNS = rng.rand(); 56 | } 57 | } 58 | 59 | #[inline(always)] 60 | pub fn z_square(sq: SQ, piece: Piece) -> u64 { 61 | debug_assert!(sq.is_okay()); 62 | unsafe { *(*ZOBRIST_PIECE_SQUARE.get_unchecked(sq.0 as usize)).get_unchecked(piece as usize) } 63 | } 64 | 65 | #[inline(always)] 66 | pub fn z_ep(sq: SQ) -> u64 { 67 | debug_assert!(sq.is_okay()); 68 | unsafe { *ZOBRIST_ENPASSANT.get_unchecked(sq.file() as usize) } 69 | } 70 | 71 | #[inline(always)] 72 | pub fn z_castle(castle: u8) -> u64 { 73 | debug_assert!((castle as usize) < ALL_CASTLING_RIGHTS); 74 | unsafe { *ZOBRIST_CASTLE.get_unchecked(castle as usize) } 75 | } 76 | 77 | #[inline(always)] 78 | pub fn z_side() -> u64 { 79 | unsafe { ZOBRIST_SIDE } 80 | } 81 | 82 | #[inline(always)] 83 | pub fn z_no_pawns() -> u64 { 84 | unsafe { ZOBRIST_NO_PAWNS } 85 | } 86 | -------------------------------------------------------------------------------- /pleco/benches/lookup_benches.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use criterion::{black_box, Bencher, Criterion}; 4 | 5 | use pleco::helper::prelude::*; 6 | use pleco::{BitBoard, SQ}; 7 | 8 | fn lookup_tables(c: &mut Criterion) { 9 | init_statics(); 10 | 11 | c.bench_function("king_lookup", king_lookup); 12 | c.bench_function("knight_lookup", knight_lookup); 13 | c.bench_function("bishop_lookup", bishop_lookup); 14 | c.bench_function("rook_lookup", rook_lookup); 15 | c.bench_function("queen_lookup", queen_lookup); 16 | c.bench_function("multi_lookup_sequential", multi_lookup_sequential); 17 | c.bench_function("multi_lookup_stutter", multi_lookup_stutter); 18 | } 19 | 20 | fn king_lookup(b: &mut Bencher) { 21 | b.iter(|| { 22 | (0..64).fold(0, |a: u64, c| { 23 | let x: u64 = black_box(knight_moves(SQ(c)).0); 24 | a ^ (x) 25 | }) 26 | }) 27 | } 28 | 29 | fn knight_lookup(b: &mut Bencher) { 30 | b.iter(|| { 31 | (0..64).fold(0, |a: u64, c| { 32 | let x: u64 = black_box(king_moves(SQ(c)).0); 33 | a ^ (x) 34 | }) 35 | }) 36 | } 37 | 38 | fn rook_lookup(b: &mut Bencher) { 39 | b.iter(|| { 40 | (0..64).fold(0, |a: u64, c| { 41 | let x: u64 = black_box(rook_moves(BitBoard(a), SQ(c)).0); 42 | a ^ (x) 43 | }) 44 | }) 45 | } 46 | 47 | fn bishop_lookup(b: &mut Bencher) { 48 | b.iter(|| { 49 | (0..64).fold(0, |a: u64, c| { 50 | let x: u64 = black_box(bishop_moves(BitBoard(a), SQ(c)).0); 51 | a ^ (x) 52 | }) 53 | }) 54 | } 55 | 56 | fn queen_lookup(b: &mut Bencher) { 57 | b.iter(|| { 58 | (0..64).fold(0, |a: u64, c| { 59 | let x: u64 = black_box(queen_moves(BitBoard(a), SQ(c)).0); 60 | a ^ (x) 61 | }) 62 | }) 63 | } 64 | 65 | // Benefits from locality 66 | fn multi_lookup_sequential(b: &mut Bencher) { 67 | b.iter(|| { 68 | (0..64).fold(0, |a: u64, c| { 69 | let mut x: u64 = black_box(knight_moves(SQ(c)).0); 70 | x ^= king_moves(SQ(c)).0; 71 | x ^= bishop_moves(BitBoard(x), SQ(c)).0; 72 | x ^= rook_moves(BitBoard(x), SQ(c)).0; 73 | x ^= black_box(queen_moves(BitBoard(x), SQ(c)).0); 74 | a ^ (x) 75 | }) 76 | }) 77 | } 78 | 79 | // Stutters so Cache must be refreshed more often 80 | fn multi_lookup_stutter(b: &mut Bencher) { 81 | b.iter(|| { 82 | (0..64).fold(0, |a: u64, c| { 83 | let mut x: u64 = queen_moves(BitBoard(a), SQ(c)).0; 84 | x ^= king_moves(SQ(c)).0; 85 | x ^= bishop_moves(BitBoard(a), SQ(c)).0; 86 | x ^= knight_moves(SQ(c)).0; 87 | x ^= black_box(rook_moves(BitBoard(x), SQ(c)).0); 88 | a ^ (x) 89 | }) 90 | }) 91 | } 92 | 93 | criterion_group!(name = lookup_benches; 94 | config = Criterion::default() 95 | .sample_size(250) 96 | .warm_up_time(Duration::from_millis(3)); 97 | targets = lookup_tables 98 | ); 99 | -------------------------------------------------------------------------------- /pleco_engine/src/tables/continuation.rs: -------------------------------------------------------------------------------- 1 | use std::mem; 2 | use std::ops::{Index, IndexMut}; 3 | 4 | use super::{NumStatCube, StatBoard}; 5 | use pleco::core::masks::*; 6 | use pleco::{Piece, SQ}; 7 | 8 | /// PieceToBoards are addressed by a move's [piece]][to] information 9 | pub struct PieceToHistory { 10 | a: [[i16; SQ_CNT]; PIECE_CNT], 11 | } 12 | 13 | // [Us][Our Piece][To SQ] 14 | #[allow(non_camel_case_types)] 15 | type PTH_idx = (Piece, SQ); 16 | 17 | impl Index for PieceToHistory { 18 | type Output = i16; 19 | 20 | #[inline(always)] 21 | fn index(&self, idx: PTH_idx) -> &Self::Output { 22 | unsafe { 23 | self.a 24 | .get_unchecked(idx.0 as usize) // [Piece moved] 25 | .get_unchecked((idx.1).0 as usize) // [To SQ] 26 | } 27 | } 28 | } 29 | 30 | impl IndexMut for PieceToHistory { 31 | #[inline(always)] 32 | fn index_mut(&mut self, idx: PTH_idx) -> &mut Self::Output { 33 | unsafe { 34 | self.a 35 | .get_unchecked_mut(idx.0 as usize) // [Piece moved] 36 | .get_unchecked_mut((idx.1).0 as usize) // [To SQ] 37 | } 38 | } 39 | } 40 | 41 | impl StatBoard for PieceToHistory { 42 | const FILL: i16 = 0; 43 | } 44 | 45 | impl NumStatCube for PieceToHistory { 46 | const D: i32 = 936; 47 | const W: i32 = 32; 48 | } 49 | 50 | /// ContinuationHistory is the history of a given pair of moves, usually the 51 | /// current one given a previous one. History table is based on PieceToBoards 52 | /// instead of ButterflyBoards. 53 | pub struct ContinuationHistory { 54 | a: [[PieceToHistory; SQ_CNT]; PIECE_CNT], 55 | } 56 | 57 | impl ContinuationHistory { 58 | pub fn new() -> Self { 59 | unsafe { mem::zeroed() } 60 | } 61 | 62 | pub fn clear(&mut self) { 63 | *self = unsafe { mem::zeroed() }; 64 | } 65 | } 66 | 67 | // [player][Our Moved Piece][To SQ] 68 | #[allow(non_camel_case_types)] 69 | type CH_idx = (Piece, SQ); 70 | 71 | impl Index for ContinuationHistory { 72 | type Output = PieceToHistory; 73 | 74 | #[inline(always)] 75 | fn index(&self, idx: CH_idx) -> &Self::Output { 76 | unsafe { 77 | self.a 78 | .get_unchecked(idx.0 as usize) // [moved piece] 79 | .get_unchecked((idx.1).0 as usize) // [To SQ] 80 | } 81 | } 82 | } 83 | 84 | impl IndexMut for ContinuationHistory { 85 | #[inline(always)] 86 | fn index_mut(&mut self, idx: CH_idx) -> &mut Self::Output { 87 | unsafe { 88 | self.a 89 | .get_unchecked_mut(idx.0 as usize) // [moved Piece] 90 | .get_unchecked_mut((idx.1).0 as usize) // [To SQ] 91 | } 92 | } 93 | } 94 | 95 | #[cfg(test)] 96 | mod tests { 97 | use super::*; 98 | use std::mem; 99 | 100 | #[test] 101 | fn size() { 102 | println!("{}", mem::size_of::()); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /pleco_engine/src/consts.rs: -------------------------------------------------------------------------------- 1 | //! Constant values and static structures. 2 | use std::mem; 3 | use std::ptr; 4 | use std::sync::atomic::compiler_fence; 5 | use std::sync::atomic::AtomicBool; 6 | use std::sync::atomic::Ordering; 7 | use std::sync::Once; 8 | 9 | use pleco::helper::prelude; 10 | use pleco::tools::tt::TranspositionTable; 11 | 12 | use search; 13 | use tables::pawn_table; 14 | use threadpool; 15 | use time::time_management::TimeManager; 16 | 17 | pub const MAX_PLY: u16 = 126; 18 | pub const THREAD_STACK_SIZE: usize = MAX_PLY as usize + 7; 19 | pub const MAX_THREADS: usize = 256; 20 | 21 | pub const DEFAULT_TT_SIZE: usize = 256; 22 | pub const PAWN_TABLE_SIZE: usize = 16384; 23 | pub const MATERIAL_TABLE_SIZE: usize = 8192; 24 | 25 | const TT_ALLOC_SIZE: usize = mem::size_of::(); 26 | const TIMER_ALLOC_SIZE: usize = mem::size_of::(); 27 | 28 | // A object that is the same size as a transposition table 29 | type DummyTranspositionTable = [u8; TT_ALLOC_SIZE]; 30 | type DummyTimeManager = [u8; TIMER_ALLOC_SIZE]; 31 | 32 | pub static USE_STDOUT: AtomicBool = AtomicBool::new(true); 33 | 34 | static INITIALIZED: Once = Once::new(); 35 | 36 | /// Global Transposition Table 37 | static mut TT_TABLE: DummyTranspositionTable = [0; TT_ALLOC_SIZE]; 38 | 39 | // Global Timer 40 | static mut TIMER: DummyTimeManager = [0; TIMER_ALLOC_SIZE]; 41 | 42 | #[cold] 43 | pub fn init_globals() { 44 | INITIALIZED.call_once(|| { 45 | prelude::init_statics(); // Initialize static tables 46 | compiler_fence(Ordering::SeqCst); 47 | init_tt(); // Transposition Table 48 | init_timer(); // Global timer manager 49 | pawn_table::init(); 50 | threadpool::init_threadpool(); // Make Threadpool 51 | search::init(); 52 | }); 53 | } 54 | 55 | // Initializes the transposition table 56 | #[cold] 57 | fn init_tt() { 58 | unsafe { 59 | let tt = &mut TT_TABLE as *mut DummyTranspositionTable as *mut TranspositionTable; 60 | ptr::write(tt, TranspositionTable::new(DEFAULT_TT_SIZE)); 61 | } 62 | } 63 | 64 | // Initializes the global Timer 65 | #[cold] 66 | fn init_timer() { 67 | unsafe { 68 | let timer: *mut TimeManager = &mut TIMER as *mut DummyTimeManager as *mut TimeManager; 69 | ptr::write(timer, TimeManager::uninitialized()); 70 | } 71 | } 72 | 73 | // Returns access to the global timer 74 | pub fn timer() -> &'static TimeManager { 75 | unsafe { &*(&TIMER as *const DummyTimeManager as *const TimeManager) } 76 | } 77 | 78 | /// Returns access to the global transposition table 79 | #[inline(always)] 80 | pub fn tt() -> &'static TranspositionTable { 81 | unsafe { &*(&TT_TABLE as *const DummyTranspositionTable as *const TranspositionTable) } 82 | } 83 | 84 | pub trait PVNode { 85 | fn is_pv() -> bool; 86 | } 87 | 88 | pub struct PV {} 89 | pub struct NonPV {} 90 | 91 | impl PVNode for PV { 92 | fn is_pv() -> bool { 93 | true 94 | } 95 | } 96 | 97 | impl PVNode for NonPV { 98 | fn is_pv() -> bool { 99 | false 100 | } 101 | } 102 | 103 | #[cfg(test)] 104 | mod tests { 105 | use super::*; 106 | #[test] 107 | fn initializing_threadpool() { 108 | threadpool::init_threadpool(); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /pleco_engine/README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | [![Pleco][pleco-engine-badge]][pleco-engine-link] 4 | 5 | [![Build][build-badge]][build-link] 6 | [![License][license-badge]][license-link] 7 | [![Commits][commits-badge]][commits-link] 8 | 9 |
10 | 11 | # Overview 12 | 13 | Pleco Engine is a Rust re-write of the [Stockfish](https://stockfishchess.org/) chess engine. 14 | 15 | This project is split into two crates, `pleco_engine` (the current folder), which contains the 16 | UCI (Universal Chess Interface) compatible Engine & AI, and `pleco`, which contains the library functionality. 17 | 18 | The overall goal of pleco is to recreate the Stockfish engine in rust, for comparison and 19 | educational purposes. As such, the majority of the algorithms used here are a direct port of stockfish's, and the 20 | credit for all of the advanced algorithms used for searching, evaluation, and many others, go directly to the 21 | maintainers and authors of Stockfish. 22 | 23 | - [Documentation](https://docs.rs/pleco_engine) 24 | - [crates.io](https://crates.io/crates/pleco_engine) 25 | 26 | ## Standalone Installation and Use 27 | 28 | Currently, Pleco's use as a standalone program is limited in functionality. A UCI client is needed to properly interact with the program. 29 | As a recommendation, check out [Arena](http://www.playwitharena.com/). 30 | 31 | The easiest way to use the engine would be to check out the "releases" tab, 32 | [here](https://github.com/sfleischman105/Pleco/releases). 33 | 34 | If you would rather build it yourself (for a specific architecture, or otherwise), clone the repo 35 | and navigate into the created folder with the following commands: 36 | 37 | ``` 38 | $ git clone https://github.com/sfleischman105/Pleco --branch main 39 | $ cd Pleco/ 40 | ``` 41 | 42 | Once inside the pleco directory, build the binaries using `cargo`: 43 | 44 | ``` 45 | $ cargo build --release 46 | ``` 47 | 48 | The compiled program will appear in `./target/release/`. 49 | 50 | Pleco can now be run with a `./Pleco` on Linux or a `./Pleco.exe` on Windows. 51 | 52 | ## Rust Toolchain Version 53 | 54 | Currently, `pleco_engine` requires **nightly** rust to use. 55 | 56 | ## Contributing 57 | 58 | Any and all contributions are welcome! Open up a PR to contribute some improvements. Look at the Issues tab to see what needs some help. 59 | 60 | ## License 61 | 62 | Pleco Engine is distributed under the GNU General Public License version 3 (or any later version at your option). See [LICENSE](LICENSE) for full details. Opening a pull requests is assumed to signal agreement with these licensing terms. 63 | 64 | [build-link]: https://github.com/pleco-rs/Pleco/blob/main/.github/workflows/test.yml 65 | [build-badge]: https://img.shields.io/github/actions/workflow/status/pleco-rs/Pleco/test.yml?branch=main&style=for-the-badge&label=tanton&logo=github 66 | [license-badge]: https://img.shields.io/github/license/pleco-rs/Pleco?style=for-the-badge&label=license&color=success 67 | [license-link]: https://github.com/pleco-rs/Pleco/blob/main/LICENSE 68 | [commits-badge]: https://img.shields.io/github/commit-activity/m/pleco-rs/Pleco?style=for-the-badge 69 | [commits-link]: https://github.com/pleco-rs/Pleco/commits/main 70 | [pleco-badge]: https://img.shields.io/crates/v/pleco.svg?style=for-the-badge 71 | [pleco-link]: https://crates.io/crates/pleco 72 | [pleco-engine-badge]: https://img.shields.io/crates/v/pleco_engine.svg?style=for-the-badge 73 | [pleco-engine-link]: https://crates.io/crates/pleco_engine 74 | -------------------------------------------------------------------------------- /pleco/src/tools/mod.rs: -------------------------------------------------------------------------------- 1 | //! Miscellaneous tools for used for Searching. Most notably this module 2 | //! contains the `TranspositionTable`, a fast lookup table able to be accessed by 3 | //! multiple threads. Other useful objects are the `UciLimit` enum and `Searcher` trait 4 | //! for building bots. 5 | 6 | pub mod eval; 7 | pub mod pleco_arc; 8 | pub mod prng; 9 | pub mod tt; 10 | 11 | use board::Board; 12 | use core::piece_move::BitMove; 13 | 14 | /// Defines an object that can play chess. 15 | pub trait Searcher { 16 | /// Returns the name of the searcher. 17 | fn name() -> &'static str 18 | where 19 | Self: Sized; 20 | 21 | /// Returns the BestMove of a position from a search of depth. 22 | fn best_move(board: Board, depth: u16) -> BitMove 23 | where 24 | Self: Sized; 25 | } 26 | 27 | // https://doc.rust-lang.org/core/arch/x86_64/fn._mm_prefetch.html 28 | /// Allows an object to have it's entries pre-fetchable. 29 | pub trait PreFetchable { 30 | /// Pre-fetches a particular key. This means bringing it into the cache for faster access. 31 | fn prefetch(&self, key: u64); 32 | 33 | /// Pre-fetches a particular key, alongside the next key. 34 | fn prefetch2(&self, key: u64) { 35 | self.prefetch(key); 36 | self.prefetch(key + 1); 37 | } 38 | } 39 | 40 | /// Prefetch's `ptr` to all levels of the cache. 41 | /// 42 | /// For some platforms this may compile down to nothing, and be optimized away. 43 | /// To prevent compiling down into nothing, compilation must be done for a 44 | /// `x86` or `x86_64` platform with SSE instructions available. An easy way to 45 | /// do this is to add the environmental variable `RUSTFLAGS=-C target-cpu=native`. 46 | #[inline(always)] 47 | pub fn prefetch_write(ptr: *const T) { 48 | __prefetch_write::(ptr); 49 | } 50 | 51 | #[cfg(all( 52 | any(target_arch = "x86", target_arch = "x86_64"), 53 | target_feature = "sse" 54 | ))] 55 | #[inline(always)] 56 | fn __prefetch_write(ptr: *const T) { 57 | #[cfg(target_arch = "x86")] 58 | use std::arch::x86::_mm_prefetch; 59 | #[cfg(target_arch = "x86_64")] 60 | use std::arch::x86_64::_mm_prefetch; 61 | unsafe { 62 | _mm_prefetch(ptr as *const i8, 3); 63 | } 64 | } 65 | 66 | #[cfg(all(any( 67 | all( 68 | any(target_arch = "x86", target_arch = "x86_64"), 69 | not(target_feature = "sse") 70 | ), 71 | not(any(target_arch = "x86", target_arch = "x86_64")) 72 | )))] 73 | #[inline(always)] 74 | fn __prefetch_write(ptr: *const T) { 75 | // Do nothing 76 | } 77 | 78 | /// Hints to the compiler for optimizations. 79 | /// 80 | /// These functions normally compile down to no-operations without the `nightly` flag. 81 | pub mod hint { 82 | 83 | /// Hints to the compiler that branch condition is likely to be false. 84 | /// Returns the value passed to it. 85 | /// 86 | /// Any use other than with `if` statements will probably not have an effect. 87 | #[inline(always)] 88 | pub fn unlikely(cond: bool) -> bool { 89 | cond 90 | } 91 | 92 | /// Hints to the compiler that branch condition is likely to be true. 93 | /// Returns the value passed to it. 94 | /// 95 | /// Any use other than with `if` statements will probably not have an effect. 96 | #[inline(always)] 97 | pub fn likely(cond: bool) -> bool { 98 | cond 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /pleco_engine/benches/multimove_benches.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, BatchSize, Bencher, Criterion}; 2 | use std::time::Duration; 3 | 4 | use pleco::Board; 5 | 6 | use pleco_engine::engine::PlecoSearcher; 7 | use pleco_engine::threadpool::*; 8 | use pleco_engine::time::uci_timer::PreLimits; 9 | 10 | use super::*; 11 | 12 | const KIWIPETE: &str = "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq -"; 13 | 14 | fn search_kiwipete_3moves_engine(b: &mut Bencher) { 15 | let mut pre_limit = PreLimits::blank(); 16 | pre_limit.depth = Some(D::depth()); 17 | let mut searcher = PlecoSearcher::init(false); 18 | let limit = pre_limit.create(); 19 | let board_kwi: Board = Board::from_fen(KIWIPETE).unwrap(); 20 | b.iter_batched( 21 | || { 22 | threadpool().clear_all(); 23 | searcher.clear_tt(); 24 | board_kwi.shallow_clone() 25 | }, 26 | |mut board| { 27 | let mov = black_box(threadpool().search(&board, &limit)); 28 | board.apply_move(mov); 29 | let mov = black_box(threadpool().search(&board, &limit)); 30 | board.apply_move(mov); 31 | black_box(threadpool().search(&board, &limit)); 32 | }, 33 | BatchSize::PerIteration, 34 | ) 35 | } 36 | 37 | fn search_startpos_3moves_engine(b: &mut Bencher) { 38 | let mut pre_limit = PreLimits::blank(); 39 | pre_limit.depth = Some(D::depth()); 40 | let mut searcher = PlecoSearcher::init(false); 41 | let limit = pre_limit.create(); 42 | b.iter_batched( 43 | || { 44 | threadpool().clear_all(); 45 | searcher.clear_tt(); 46 | Board::start_pos() 47 | }, 48 | |mut board| { 49 | let mov = black_box(threadpool().search(&board, &limit)); 50 | board.apply_move(mov); 51 | let mov = black_box(threadpool().search(&board, &limit)); 52 | board.apply_move(mov); 53 | black_box(threadpool().search(&board, &limit)); 54 | }, 55 | BatchSize::PerIteration, 56 | ) 57 | } 58 | 59 | fn bench_engine_evaluations(c: &mut Criterion) { 60 | c.bench_function( 61 | "Search MultiMove Depth 5", 62 | search_startpos_3moves_engine::, 63 | ); 64 | c.bench_function( 65 | "Search MultiMove Depth 6", 66 | search_startpos_3moves_engine::, 67 | ); 68 | c.bench_function( 69 | "Search MultiMove Depth 7", 70 | search_startpos_3moves_engine::, 71 | ); 72 | c.bench_function( 73 | "Search MultiMove Depth 8", 74 | search_startpos_3moves_engine::, 75 | ); 76 | c.bench_function( 77 | "Search KiwiPete MultiMove Depth 5", 78 | search_kiwipete_3moves_engine::, 79 | ); 80 | c.bench_function( 81 | "Search KiwiPete MultiMove Depth 6", 82 | search_kiwipete_3moves_engine::, 83 | ); 84 | c.bench_function( 85 | "Search KiwiPete MultiMove Depth 7", 86 | search_kiwipete_3moves_engine::, 87 | ); 88 | c.bench_function( 89 | "Search KiwiPete MultiMove Depth 8", 90 | search_kiwipete_3moves_engine::, 91 | ); 92 | } 93 | 94 | criterion_group!(name = search_multimove; 95 | config = Criterion::default() 96 | .sample_size(35) 97 | .warm_up_time(Duration::from_millis(150)); 98 | targets = bench_engine_evaluations 99 | ); 100 | -------------------------------------------------------------------------------- /pleco/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A blazingly fast chess library. 2 | //! 3 | //! This package is separated into two parts. Firstly, the board representation & associated functions 4 | //! (the current crate, `pleco`), and secondly, the AI implementations using these chess foundations, 5 | //! [pleco_engine](https://crates.io/crates/pleco_engine). 6 | //! 7 | //! The general formatting and structure of the library take heavy influence from the basic building 8 | //! blocks of the [Stockfish](https://stockfishchess.org/) chess engine. 9 | //! 10 | //! # Usage 11 | //! 12 | //! This crate is [on crates.io](https://crates.io/crates/pleco) and can be 13 | //! used by adding `pleco` to the dependencies in your project's `Cargo.toml`. 14 | //! 15 | //! # Platforms 16 | //! 17 | //! `pleco` is currently tested and created for use with the `x86_64` instruction set in mind. 18 | //! Currently, there are no guarantees of correct behavior if compiled for a different 19 | //! instruction set. 20 | //! 21 | //! # Safety 22 | //! 23 | //! While generally a safe library, pleco was built with a focus of speed in mind. Usage of methods 24 | //! must be followed carefully, as there are many possible ways to `panic` unexpectedly. Methods 25 | //! with the ability to panic will be documented as such. 26 | //! 27 | //! # Examples 28 | //! 29 | //! You can create a [`Board`] with the starting position like so: 30 | //! 31 | //! ```ignore 32 | //! use pleco::Board; 33 | //! let board = Board::start_pos(); 34 | //! ``` 35 | //! 36 | //! Generating a list of moves (Contained inside a [`MoveList`]) can be done with: 37 | //! 38 | //! ```ignore 39 | //! let list = board.generate_moves(); 40 | //! ``` 41 | //! 42 | //! Applying and undoing moves is simple: 43 | //! 44 | //! ```ignore 45 | //! let mut board = Board::start_pos(); 46 | //! let list = board.generate_moves(); 47 | //! 48 | //! for mov in list.iter() { 49 | //! board.apply_move(*mov); 50 | //! println!("{}",board.get_fen()); 51 | //! board.undo_move(); 52 | //! } 53 | //! ``` 54 | //! 55 | //! Using fen strings is also supported: 56 | //! 57 | //! ```ignore 58 | //! let start_position = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; 59 | //! let board = Board::from_fen(start_position).unwrap(); 60 | //! ``` 61 | //! 62 | //! [`MoveList`]: core/move_list/struct.MoveList.html 63 | //! [`Board`]: board/struct.Board.html 64 | 65 | #![cfg_attr(test, allow(dead_code))] 66 | #![allow(clippy::cast_lossless)] 67 | #![allow(clippy::unreadable_literal)] 68 | #![allow(clippy::unusual_byte_groupings)] 69 | #![allow(clippy::uninit_assumed_init)] 70 | #![allow(clippy::missing_safety_doc)] 71 | #![allow(dead_code)] 72 | 73 | #[macro_use] 74 | extern crate bitflags; 75 | #[macro_use] 76 | extern crate lazy_static; 77 | extern crate mucow; 78 | extern crate num_cpus; 79 | extern crate rand; 80 | extern crate rayon; 81 | 82 | pub mod board; 83 | pub mod bots; 84 | pub mod core; 85 | pub mod helper; 86 | pub mod tools; 87 | 88 | pub use board::Board; 89 | pub use core::bitboard::BitBoard; 90 | pub use core::move_list::{MoveList, ScoringMoveList}; 91 | pub use core::piece_move::{BitMove, ScoringMove}; 92 | pub use core::sq::SQ; 93 | pub use core::{File, Piece, PieceType, Player, Rank}; 94 | pub use helper::Helper; 95 | 96 | pub mod bot_prelude { 97 | //! Easy importing of all available bots. 98 | pub use bots::AlphaBetaSearcher; 99 | pub use bots::IterativeSearcher; 100 | pub use bots::JamboreeSearcher; 101 | pub use bots::MiniMaxSearcher; 102 | pub use bots::ParallelMiniMaxSearcher; 103 | pub use bots::RandomBot; 104 | 105 | pub use tools::Searcher; 106 | } 107 | -------------------------------------------------------------------------------- /pleco_engine/src/movepick/pick.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::mem; 3 | 4 | #[repr(u8)] 5 | #[derive(Copy, Clone)] 6 | pub enum Pick { 7 | // Main Searching Algo 8 | MainSearch = 0, 9 | CapturesInit = 1, 10 | GoodCaptures = 2, 11 | KillerOne = 3, 12 | KillerTwo = 4, 13 | CounterMove = 5, 14 | QuietInit = 6, 15 | QuietMoves = 7, 16 | BadCaptures = 8, 17 | // Evasions, Main and Qsearch 18 | EvasionSearch = 9, 19 | EvasionsInit = 10, 20 | AllEvasions = 11, 21 | // ProbCut Searching 22 | ProbCutSearch = 12, 23 | ProbCutCapturesInit = 13, 24 | ProbCutCaptures = 14, 25 | // QSearch - Non Re-Captures 26 | QSearch = 15, 27 | QSearchInit = 16, 28 | QCaptures = 17, 29 | QChecks = 18, 30 | // Q Search ReCaptures 31 | QSearchRecaptures = 19, 32 | QRecaptures = 20, 33 | } 34 | 35 | impl Pick { 36 | pub fn incr(&mut self) { 37 | unsafe { 38 | *self = mem::transmute(*self as u8 + 1); 39 | } 40 | } 41 | } 42 | 43 | impl fmt::Display for Pick { 44 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 45 | f.pad(&match self { 46 | Pick::MainSearch => "MainSearch", 47 | Pick::CapturesInit => "CapturesInit", 48 | Pick::GoodCaptures => "GoodCaptures", 49 | Pick::KillerOne => "KillerOne", 50 | Pick::KillerTwo => "KillerTwo", 51 | Pick::CounterMove => "CounterMove", 52 | Pick::QuietInit => "QuietInit", 53 | Pick::QuietMoves => "QuietMoves", 54 | Pick::BadCaptures => "BadCaptures", 55 | Pick::EvasionSearch => "EvasionSearch", 56 | Pick::EvasionsInit => "EvasionsInit", 57 | Pick::AllEvasions => "AllEvasions", 58 | Pick::ProbCutSearch => "ProbCutSearch", 59 | Pick::ProbCutCapturesInit => "ProbCutCapturesInit", 60 | Pick::ProbCutCaptures => "ProbCutCaptures", 61 | Pick::QSearch => "QSearch", 62 | Pick::QSearchInit => "QSearchInit", 63 | Pick::QCaptures => "QCaptures", 64 | Pick::QChecks => "QChecks", 65 | Pick::QSearchRecaptures => "QSearchRecaptures", 66 | Pick::QRecaptures => "QRecaptures", 67 | }) 68 | } 69 | } 70 | 71 | // types 72 | 73 | // Root 74 | // MainSearch 75 | // Evasions 76 | // ProbCut 77 | // Qsearch 78 | 79 | // Strategy 80 | 81 | // RootMoves ------- 82 | // Get the next rootmoves. 83 | 84 | // MainSearch ------ 85 | // TT Move 86 | // Increment. 87 | // Return TT move 88 | // Captures_init 89 | // Generate Captures 90 | // Sort 91 | // Increment 92 | // Go to next_move(); 93 | // Good_Captures 94 | // Loop through each capture, once done increment stage 95 | // Killer0 96 | // Do KillerMove1, increment 97 | // Killer1 98 | // Do KillerMove2, increment 99 | // CounterMove 100 | // Do CounterMove, increment 101 | // Quiet_Init 102 | // Generate Quiets 103 | // Sort 104 | // Partial Insertion sort? 105 | // Increment 106 | // Go to next_move(); 107 | // Quiet 108 | // 109 | // Bad Captures 110 | // 111 | 112 | // Evasions ------- 113 | // TT Move 114 | // Return TT move, Increment. 115 | // Evasions_init 116 | // All_evasions 117 | 118 | // ProbCut 119 | // TT Move 120 | // Return TT move, Increment. 121 | // Probcut_Captures_Init 122 | // Probvut Captures 123 | 124 | // Qsearch ----------- 125 | // TT Move 126 | // Return TT move, Increment. 127 | // QCaptures_Init 128 | // QCaptures 129 | // QChecks 130 | // QSearch_Recaptures 131 | // QRecaptures 132 | -------------------------------------------------------------------------------- /pleco/tests/board_build.rs: -------------------------------------------------------------------------------- 1 | extern crate pleco; 2 | 3 | use self::board::Board; 4 | use pleco::board; 5 | use pleco::core::piece_move; 6 | use pleco::core::piece_move::*; 7 | use pleco::core::*; 8 | use pleco::*; 9 | 10 | #[test] 11 | fn test_init_counts() { 12 | let board = Board::start_pos(); 13 | assert_eq!(board.count_piece(Player::White, PieceType::P), 8); 14 | assert_eq!(board.count_piece(Player::White, PieceType::N), 2); 15 | assert_eq!(board.count_piece(Player::White, PieceType::B), 2); 16 | assert_eq!(board.count_piece(Player::White, PieceType::R), 2); 17 | assert_eq!(board.count_piece(Player::White, PieceType::K), 1); 18 | assert_eq!(board.count_piece(Player::White, PieceType::Q), 1); 19 | assert_eq!(board.count_piece(Player::Black, PieceType::P), 8); 20 | assert_eq!(board.count_piece(Player::Black, PieceType::N), 2); 21 | assert_eq!(board.count_piece(Player::Black, PieceType::B), 2); 22 | assert_eq!(board.count_piece(Player::Black, PieceType::R), 2); 23 | assert_eq!(board.count_piece(Player::Black, PieceType::K), 1); 24 | assert_eq!(board.count_piece(Player::Black, PieceType::Q), 1); 25 | assert_eq!(board.diagonal_piece_bb(Player::White).0, 0b101100); 26 | assert_eq!(board.sliding_piece_bb(Player::White).0, 0b10001001); 27 | assert_eq!( 28 | board.count_pieces_player(Player::White), 29 | board.count_pieces_player(Player::Black) 30 | ); 31 | assert_eq!(board.occupied().0, 0xFFFF00000000FFFF); 32 | assert_eq!(board.count_all_pieces(), 32); 33 | } 34 | 35 | #[test] 36 | fn basic_move_apply() { 37 | let mut b = Board::start_pos(); 38 | let p1 = PreMoveInfo { 39 | src: SQ(12), 40 | dst: SQ(28), 41 | flags: MoveFlag::DoublePawnPush, 42 | }; 43 | let m = BitMove::init(p1); 44 | b.apply_move(m); 45 | let p2 = PreMoveInfo { 46 | src: SQ(51), 47 | dst: SQ(35), 48 | flags: MoveFlag::DoublePawnPush, 49 | }; 50 | let m = BitMove::init(p2); 51 | b.apply_move(m); 52 | let p3 = PreMoveInfo { 53 | src: SQ(28), 54 | dst: SQ(35), 55 | flags: MoveFlag::Capture { ep_capture: false }, 56 | }; 57 | let m = BitMove::init(p3); 58 | b.apply_move(m); 59 | assert_eq!(b.count_piece(Player::Black, PieceType::P), 7); 60 | b.undo_move(); 61 | assert_eq!(b.count_piece(Player::Black, PieceType::P), 8); 62 | assert!(!b.in_check()); 63 | } 64 | 65 | #[test] 66 | fn move_seq_1() { 67 | let mut b = board::Board::start_pos(); 68 | let p = PreMoveInfo { 69 | src: SQ(12), 70 | dst: SQ(28), 71 | flags: MoveFlag::DoublePawnPush, 72 | }; 73 | let m = BitMove::init(p); 74 | b.apply_move(m); 75 | let p = PreMoveInfo { 76 | src: SQ(51), 77 | dst: SQ(35), 78 | flags: MoveFlag::DoublePawnPush, 79 | }; 80 | let m = BitMove::init(p); 81 | b.apply_move(m); 82 | let p = PreMoveInfo { 83 | src: SQ(28), 84 | dst: SQ(35), 85 | flags: MoveFlag::Capture { ep_capture: false }, 86 | }; 87 | let m = BitMove::init(p); 88 | b.apply_move(m); 89 | 90 | let p = PreMoveInfo { 91 | src: SQ(59), 92 | dst: SQ(35), 93 | flags: MoveFlag::Capture { ep_capture: false }, 94 | }; 95 | let m = BitMove::init(p); 96 | b.apply_move(m); 97 | let p = PreMoveInfo { 98 | src: SQ(5), 99 | dst: SQ(12), 100 | flags: MoveFlag::QuietMove, 101 | }; 102 | let m = BitMove::init(p); 103 | b.apply_move(m); 104 | let p = PreMoveInfo { 105 | src: SQ(35), 106 | dst: SQ(8), 107 | flags: MoveFlag::Capture { ep_capture: false }, 108 | }; 109 | let m = BitMove::init(p); 110 | b.apply_move(m); 111 | let p = PreMoveInfo { 112 | src: SQ(6), 113 | dst: SQ(21), 114 | flags: MoveFlag::QuietMove, 115 | }; 116 | let m = BitMove::init(p); 117 | b.apply_move(m); 118 | 119 | let p = piece_move::PreMoveInfo { 120 | src: SQ(60), 121 | dst: SQ(59), 122 | flags: piece_move::MoveFlag::QuietMove, 123 | }; 124 | let m = piece_move::BitMove::init(p); 125 | b.apply_move(m); 126 | let p = PreMoveInfo { 127 | src: SQ(4), 128 | dst: SQ(7), 129 | flags: MoveFlag::Castle { king_side: true }, 130 | }; 131 | let m = BitMove::init(p); 132 | b.apply_move(m); 133 | } 134 | -------------------------------------------------------------------------------- /pleco/src/core/score.rs: -------------------------------------------------------------------------------- 1 | //! Primitives for determining the value / score of a specific location. 2 | //! 3 | //! A `Value` stores a single `i32` to represent a score. `Score` stores two `i32`s inside of it, 4 | //! the first to determine the mid-game score, and the second to determine the end-game score. 5 | 6 | use std::fmt; 7 | use std::ops::*; 8 | 9 | // TODO: Why is Value an i32 now? Need some notes on why that changed. 10 | 11 | /// Type for `i32` to determine the `Value` of an evaluation. 12 | pub type Value = i32; 13 | 14 | pub const ZERO: Value = 0; 15 | pub const DRAW: Value = 0; 16 | pub const LIKELY_WIN: Value = -10000; 17 | pub const MATE: Value = 31000; 18 | pub const INFINITE: Value = 32001; 19 | pub const NEG_INFINITE: Value = -32001; 20 | pub const NONE: Value = 32002; 21 | 22 | pub const PAWN: Value = 100; 23 | pub const KNIGHT: Value = 350; 24 | pub const BISHOP: Value = 351; 25 | pub const ROOK: Value = 500; 26 | pub const QUEEN: Value = 900; 27 | 28 | pub const PAWN_MG: Value = 171; 29 | pub const KNIGHT_MG: Value = 764; 30 | pub const BISHOP_MG: Value = 826; 31 | pub const ROOK_MG: Value = 1282; 32 | pub const QUEEN_MG: Value = 2526; 33 | 34 | pub const PAWN_EG: Value = 240; 35 | pub const KNIGHT_EG: Value = 848; 36 | pub const BISHOP_EG: Value = 891; 37 | pub const ROOK_EG: Value = 1373; 38 | pub const QUEEN_EG: Value = 2646; 39 | 40 | pub const MID_GAME_LIMIT: Value = 15258; 41 | pub const END_GAME_LIMIT: Value = 3915; 42 | 43 | pub const MATE_IN_MAX_PLY: Value = MATE - 2 * 128; 44 | pub const MATED_IN_MAX_PLY: Value = -MATE + 2 * 128; 45 | 46 | /// Struct to define the value of a mid-game / end-game evaluation. 47 | #[derive(Copy, Clone, PartialEq, Eq, Debug)] 48 | pub struct Score(pub Value, pub Value); 49 | 50 | impl Score { 51 | /// The Score of zero 52 | pub const ZERO: Score = Score(0, 0); 53 | 54 | /// Creates a new `Score`. 55 | pub fn make(mg: Value, eg: Value) -> Self { 56 | Score(mg, eg) 57 | } 58 | 59 | /// Creates a new `Score`. 60 | pub fn new(mg: Value, eg: Value) -> Self { 61 | Score(mg, eg) 62 | } 63 | 64 | /// Returns the mid-game score. 65 | pub fn mg(self) -> Value { 66 | self.0 67 | } 68 | 69 | /// Returns the end-game score. 70 | pub fn eg(self) -> Value { 71 | self.1 72 | } 73 | 74 | /// Gives the value of the score in centi-pawns 75 | pub fn centipawns(self) -> (f64, f64) { 76 | let mg: f64 = self.mg() as f64 / PAWN_MG as f64; 77 | let eg: f64 = self.eg() as f64 / PAWN_EG as f64; 78 | (mg, eg) 79 | } 80 | } 81 | 82 | impl fmt::Display for Score { 83 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 84 | let (mg, eg) = self.centipawns(); 85 | write!(f, "{:5.2} {:5.2}", mg, eg) 86 | } 87 | } 88 | 89 | impl Add for Score { 90 | type Output = Score; 91 | 92 | fn add(self, other: Score) -> Score { 93 | Score(self.0 + other.0, self.1 + other.1) 94 | } 95 | } 96 | 97 | impl AddAssign for Score { 98 | fn add_assign(&mut self, other: Score) { 99 | *self = Score(self.0 + other.0, self.1 + other.1); 100 | } 101 | } 102 | 103 | impl Sub for Score { 104 | type Output = Score; 105 | 106 | fn sub(self, other: Score) -> Score { 107 | Score(self.0 - other.0, self.1 - other.1) 108 | } 109 | } 110 | 111 | impl SubAssign for Score { 112 | fn sub_assign(&mut self, other: Score) { 113 | *self = Score(self.0 - other.0, self.1 - other.1); 114 | } 115 | } 116 | 117 | impl Neg for Score { 118 | type Output = Score; 119 | 120 | fn neg(self) -> Score { 121 | Score(-self.0, -self.1) 122 | } 123 | } 124 | 125 | impl Mul for Score { 126 | type Output = Score; 127 | 128 | fn mul(self, rhs: u8) -> Score { 129 | Score(self.0 * rhs as i32, self.1 * rhs as i32) 130 | } 131 | } 132 | 133 | impl Mul for Score { 134 | type Output = Score; 135 | 136 | fn mul(self, rhs: u16) -> Score { 137 | Score(self.0 * rhs as i32, self.1 * rhs as i32) 138 | } 139 | } 140 | 141 | impl Mul for Score { 142 | type Output = Score; 143 | 144 | fn mul(self, rhs: i16) -> Score { 145 | Score(self.0 * rhs as i32, self.1 * rhs as i32) 146 | } 147 | } 148 | 149 | impl Mul for Score { 150 | type Output = Score; 151 | 152 | fn mul(self, rhs: i32) -> Score { 153 | Score(self.0 * rhs, self.1 * rhs) 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /pleco/src/bots/mod.rs: -------------------------------------------------------------------------------- 1 | //! Contains all of the currently completed standard bots/searchers/AIs. 2 | //! 3 | //! These are mostly for example purposes, to see how one can create a chess AI. 4 | 5 | extern crate rand; 6 | 7 | pub mod alphabeta; 8 | pub mod iterative_parallel_mvv_lva; 9 | pub mod jamboree; 10 | pub mod minimax; 11 | pub mod parallel_minimax; 12 | 13 | use board::Board; 14 | use core::piece_move::*; 15 | use core::score::*; 16 | use tools::eval::*; 17 | use tools::Searcher; 18 | 19 | const MAX_PLY: u16 = 4; 20 | const MATE_V: i16 = MATE as i16; 21 | const DRAW_V: i16 = DRAW as i16; 22 | const NEG_INF_V: i16 = NEG_INFINITE as i16; 23 | const INF_V: i16 = INFINITE as i16; 24 | 25 | struct BoardWrapper<'a> { 26 | b: &'a mut Board, 27 | } 28 | 29 | /// Searcher that randomly chooses a move. The fastest, yet dumbest, searcher we have to offer. 30 | pub struct RandomBot {} 31 | 32 | /// Searcher that uses a MiniMax algorithm to search for a best move. 33 | pub struct MiniMaxSearcher {} 34 | 35 | /// Searcher that uses a MiniMax algorithm to search for a best move, but does so in parallel. 36 | pub struct ParallelMiniMaxSearcher {} 37 | 38 | /// Searcher that uses an alpha-beta algorithm to search for a best move. 39 | pub struct AlphaBetaSearcher {} 40 | 41 | /// Searcher that uses a modified alpha-beta algorithm to search for a best move, but does so in parallel. 42 | /// The specific name of this algorithm is called "jamboree". 43 | pub struct JamboreeSearcher {} 44 | 45 | /// Modified `JamboreeSearcher` that uses the parallel alpha-beta algorithm. Improves upon `JamboreeSearcher` by 46 | /// adding iterative deepening with an aspiration window, MVV-LVA move ordering, as well as a qscience search. 47 | pub struct IterativeSearcher {} 48 | 49 | impl Searcher for RandomBot { 50 | fn name() -> &'static str { 51 | "Random Searcher" 52 | } 53 | 54 | fn best_move(board: Board, _depth: u16) -> BitMove { 55 | let moves = board.generate_moves(); 56 | moves[rand::random::() % moves.len()] 57 | } 58 | } 59 | 60 | impl Searcher for AlphaBetaSearcher { 61 | fn name() -> &'static str { 62 | "AlphaBeta Searcher" 63 | } 64 | 65 | fn best_move(board: Board, depth: u16) -> BitMove { 66 | let alpha = NEG_INF_V; 67 | let beta = INF_V; 68 | alphabeta::alpha_beta_search(&mut board.shallow_clone(), alpha, beta, depth).bit_move 69 | } 70 | } 71 | 72 | impl Searcher for IterativeSearcher { 73 | fn name() -> &'static str { 74 | "Advanced Searcher" 75 | } 76 | 77 | fn best_move(board: Board, depth: u16) -> BitMove { 78 | iterative_parallel_mvv_lva::iterative_deepening(&mut board.shallow_clone(), depth) 79 | } 80 | } 81 | 82 | impl Searcher for JamboreeSearcher { 83 | fn name() -> &'static str { 84 | "Jamboree Searcher" 85 | } 86 | 87 | fn best_move(board: Board, depth: u16) -> BitMove { 88 | let alpha = NEG_INF_V; 89 | let beta = INF_V; 90 | jamboree::jamboree(&mut board.shallow_clone(), alpha, beta, depth, 2).bit_move 91 | } 92 | } 93 | 94 | impl Searcher for MiniMaxSearcher { 95 | fn name() -> &'static str { 96 | "Simple Searcher" 97 | } 98 | 99 | fn best_move(board: Board, depth: u16) -> BitMove { 100 | minimax::minimax(&mut board.shallow_clone(), depth).bit_move 101 | } 102 | } 103 | 104 | impl Searcher for ParallelMiniMaxSearcher { 105 | fn name() -> &'static str { 106 | "Parallel Searcher" 107 | } 108 | 109 | fn best_move(board: Board, depth: u16) -> BitMove { 110 | parallel_minimax::parallel_minimax(&mut board.shallow_clone(), depth).bit_move 111 | } 112 | } 113 | 114 | #[doc(hidden)] 115 | pub fn eval_board(board: &Board) -> ScoringMove { 116 | ScoringMove::blank(Eval::eval_low(board) as i16) 117 | } 118 | 119 | #[cfg(test)] 120 | mod tests { 121 | use super::*; 122 | 123 | // We test these, as both algorithms should give the same result no matter if paralleized 124 | // or not. 125 | 126 | #[test] 127 | fn minimax_equality() { 128 | let b = Board::start_pos(); 129 | let b2 = b.shallow_clone(); 130 | assert_eq!( 131 | MiniMaxSearcher::best_move(b, 5), 132 | ParallelMiniMaxSearcher::best_move(b2, 5) 133 | ); 134 | } 135 | 136 | #[test] 137 | fn alpha_equality() { 138 | let b = Board::start_pos(); 139 | let b2 = b.shallow_clone(); 140 | assert_eq!( 141 | AlphaBetaSearcher::best_move(b, 5), 142 | JamboreeSearcher::best_move(b2, 5) 143 | ); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /pleco_engine/src/tables/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod butterfly; 2 | pub mod capture_piece_history; 3 | pub mod continuation; 4 | pub mod counter_move; 5 | pub mod material; 6 | pub mod pawn_table; 7 | 8 | use std::alloc::{alloc_zeroed, dealloc, Layout}; 9 | use std::mem; 10 | use std::ops::*; 11 | use std::ptr; 12 | use std::ptr::NonNull; 13 | 14 | pub mod prelude { 15 | // easier exporting :) 16 | pub use super::butterfly::ButterflyHistory; 17 | pub use super::capture_piece_history::CapturePieceToHistory; 18 | pub use super::continuation::{ContinuationHistory, PieceToHistory}; 19 | pub use super::counter_move::CounterMoveHistory; 20 | pub use super::{NumStatBoard, NumStatCube, StatBoard}; 21 | } 22 | 23 | // TODO: Create StatBoards using const generics: https://github.com/rust-lang/rust/issues/44580 24 | // TODO: Create 3DBoard using const generics: https://github.com/rust-lang/rust/issues/44580 25 | 26 | pub trait StatBoard: Sized + IndexMut 27 | where 28 | T: Copy + Clone + Sized, 29 | { 30 | const FILL: T; 31 | 32 | fn new() -> Self { 33 | unsafe { mem::zeroed() } 34 | } 35 | 36 | fn clear(&mut self) { 37 | self.fill(Self::FILL); 38 | } 39 | 40 | fn fill(&mut self, val: T) { 41 | let num: usize = mem::size_of::() / mem::size_of::(); 42 | 43 | unsafe { 44 | let ptr: *mut T = self as *mut Self as *mut T; 45 | for i in 0..num { 46 | ptr::write(ptr.add(i), val); 47 | } 48 | } 49 | } 50 | } 51 | 52 | pub trait NumStatBoard: StatBoard { 53 | const D: i16; 54 | fn update(&mut self, idx: IDX, bonus: i16) { 55 | assert!(bonus.abs() <= Self::D); // Ensure range is [-32 * D, 32 * D] 56 | let entry = self.index_mut(idx); 57 | *entry += bonus * 32 - (*entry) * bonus.abs() / Self::D; 58 | } 59 | } 60 | 61 | pub trait NumStatCube: StatBoard { 62 | const D: i32; 63 | const W: i32; 64 | 65 | fn update(&mut self, idx: IDX, bonus: i32) { 66 | assert!(bonus.abs() <= Self::D); 67 | let entry = self.index_mut(idx); 68 | *entry += (bonus * Self::W - (*entry) as i32 * bonus.abs() / Self::D) as i16; 69 | assert!(((*entry) as i32).abs() <= Self::D * Self::W); 70 | } 71 | } 72 | 73 | // TODO: Performance increase awaiting with const generics: https://github.com/rust-lang/rust/issues/44580 74 | 75 | /// Generic Heap-stored array of entries. Used for building more specific abstractions. 76 | /// 77 | /// Objects placed inside must not implement `Drop`, or else undefined behavior follows. Indexing is done 78 | /// with `u64`s, and returns a value using a mask of the lower log2(table size) bits. Collisions 79 | /// are possible using this structure, although very rare. 80 | pub struct TableBase { 81 | table: NonNull, 82 | } 83 | 84 | pub trait TableBaseConst { 85 | const ENTRY_COUNT: usize; 86 | } 87 | 88 | impl TableBase { 89 | /// Constructs a new `TableBase`. The size must be a power of 2, or else `None` is 90 | /// returned. 91 | /// 92 | /// # Safety 93 | /// 94 | /// Size must be a power of 2/ 95 | pub fn new() -> Option> { 96 | if T::ENTRY_COUNT.count_ones() != 1 { 97 | None 98 | } else { 99 | unsafe { 100 | let table = TableBase { 101 | table: TableBase::alloc(), 102 | }; 103 | Some(table) 104 | } 105 | } 106 | } 107 | 108 | /// Gets a mutable reference to an entry with a certain key. 109 | #[inline(always)] 110 | pub fn get_mut(&mut self, key: u64) -> &mut T { 111 | unsafe { &mut *self.get_ptr(key) } 112 | } 113 | 114 | /// Gets a mutable pointer to an entry with a certain key. 115 | /// 116 | /// # Safety 117 | /// 118 | /// Unsafe due to returning a raw pointer that may dangle if the `TableBase` is 119 | /// dropped prematurely. 120 | #[inline(always)] 121 | pub unsafe fn get_ptr(&self, key: u64) -> *mut T { 122 | let index: usize = (key & (T::ENTRY_COUNT as u64 - 1)) as usize; 123 | self.table.as_ptr().offset(index as isize) 124 | } 125 | 126 | pub fn clear(&mut self) { 127 | unsafe { 128 | let t_ptr = self.get_ptr(0); 129 | ptr::write_bytes(t_ptr, 0, T::ENTRY_COUNT); 130 | } 131 | } 132 | 133 | // allocates space. 134 | unsafe fn alloc() -> NonNull { 135 | let layout = Layout::array::(T::ENTRY_COUNT).unwrap(); 136 | let ptr = alloc_zeroed(layout); 137 | let new_ptr = ptr.cast(); 138 | NonNull::new(new_ptr as *mut T).unwrap() 139 | } 140 | 141 | /// de-allocates the current table. 142 | unsafe fn de_alloc(&mut self) { 143 | let ptr: NonNull = mem::transmute(self.table); 144 | dealloc(ptr.as_ptr(), Layout::array::(T::ENTRY_COUNT).unwrap()); 145 | } 146 | } 147 | 148 | impl Drop for TableBase { 149 | fn drop(&mut self) { 150 | unsafe { 151 | self.de_alloc(); 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /pleco/tests/board_hash.rs: -------------------------------------------------------------------------------- 1 | extern crate pleco; 2 | extern crate rand; 3 | 4 | use pleco::board::RandBoard; 5 | use pleco::{BitMove, Board}; 6 | 7 | trait HashCorrect { 8 | fn get_hash(board: &Board) -> u64; 9 | 10 | fn check_hash(board: &Board) { 11 | if board.depth() > 0 { 12 | let pre_hash: u64 = Self::get_hash(board); 13 | let fen = board.fen(); 14 | let fen_board = Board::from_fen(&fen).unwrap(); 15 | let post_hash: u64 = Self::get_hash(&fen_board); 16 | 17 | if pre_hash != post_hash { 18 | Self::print_hash(board, &fen); 19 | } 20 | } 21 | } 22 | 23 | fn print_hash(board: &Board, fen: &str) { 24 | let last_move_played = board.last_move().unwrap_or(BitMove::null()); 25 | let mut prev_board: Board = board.parallel_clone(); 26 | prev_board.undo_move(); 27 | let prev_fen = prev_board.fen(); 28 | panic!( 29 | "\nBoard did not have correct zobrist before and after! ply: {} \n\ 30 | current fen: {}\n\ 31 | last move played: {}, flags: {:b} \n\ 32 | previous fen: {}\n\ 33 | pretty: \n\ 34 | {} \n 35 | previous pretty: \n\ 36 | {} \n", 37 | board.depth(), 38 | fen, 39 | last_move_played, 40 | last_move_played.get_raw() >> 12, 41 | prev_fen, 42 | board.pretty_string(), 43 | prev_board.pretty_string() 44 | ); 45 | } 46 | } 47 | 48 | struct ZobHashCorrect {} 49 | struct MaterialHashCorrect {} 50 | struct PawnHashCorrect {} 51 | 52 | impl HashCorrect for ZobHashCorrect { 53 | fn get_hash(board: &Board) -> u64 { 54 | board.zobrist() 55 | } 56 | } 57 | impl HashCorrect for MaterialHashCorrect { 58 | fn get_hash(board: &Board) -> u64 { 59 | board.material_key() 60 | } 61 | } 62 | 63 | impl HashCorrect for PawnHashCorrect { 64 | fn get_hash(board: &Board) -> u64 { 65 | board.pawn_key() 66 | } 67 | } 68 | 69 | // Testing that applying / undoing a move leads to the same zobriust hash 70 | #[test] 71 | fn zobrist_correctness() { 72 | for _x in 0..15 { 73 | let mut board = RandBoard::default().one().shallow_clone(); 74 | randomize::(&mut board); 75 | } 76 | } 77 | 78 | #[test] 79 | fn pawn_key_correctness() { 80 | for _x in 0..15 { 81 | let mut board = RandBoard::default().one().shallow_clone(); 82 | randomize::(&mut board); 83 | } 84 | } 85 | 86 | #[test] 87 | fn material_key_correctness() { 88 | for _x in 0..15 { 89 | let mut board = RandBoard::default().one().shallow_clone(); 90 | randomize::(&mut board); 91 | } 92 | } 93 | 94 | fn randomize(board: &mut Board) { 95 | let list = board.generate_moves(); 96 | let num_iterations = ((rand::random::() % 6) + 3).min(list.len()); 97 | 98 | let mut moves = Vec::with_capacity(num_iterations); 99 | for _x in 0..num_iterations { 100 | moves.push(list[rand::random::() % list.len()]); 101 | } 102 | 103 | while let Some(mov) = moves.pop() { 104 | let depth: usize = (rand::random::() % 9) + 6; 105 | board.apply_move(mov); 106 | randomize_inner::(board, depth); 107 | board.undo_move(); 108 | } 109 | } 110 | 111 | fn randomize_inner(board: &mut Board, depth: usize) { 112 | H::check_hash(board); 113 | if depth != 0 { 114 | let moves = board.generate_moves(); 115 | if moves.is_empty() { 116 | return; 117 | } 118 | 119 | let rn = rand::random::() % moves.len(); 120 | board.apply_move(moves[rn % moves.len()]); 121 | randomize_inner::(board, depth - 1); 122 | board.undo_move(); 123 | 124 | if rn > 3 && rn % 4 == 0 && depth > 4 { 125 | board.apply_move(moves[rn - 1]); 126 | randomize_inner::(board, depth - 2); 127 | board.undo_move(); 128 | } 129 | } 130 | } 131 | 132 | // 133 | //fn check_zob(board: &Board) { 134 | // let zobrist = board.zobrist(); 135 | // let fen = board.fen(); 136 | // let fen_board = Board::from_fen(&fen).unwrap(); 137 | // let post_zob = fen_board.zobrist(); 138 | // 139 | // if board.depth() > 0 && zobrist != post_zob { 140 | // let last_move_played = board.last_move().unwrap_or(BitMove::null()); 141 | // let mut prev_board: Board = board.parallel_clone(); 142 | // prev_board.undo_move(); 143 | // let prev_fen = prev_board.fen(); 144 | // panic!("\nBoard did not have correct zobrist before and after! ply: {} \n\ 145 | // current fen: {}\n\ 146 | // last move played: {}, flags: {:b} \n\ 147 | // previous fen: {}\n\ 148 | // pretty: \n\ 149 | // {} \n 150 | // previous pretty: \n\ 151 | // {} \n", 152 | // board.depth(), fen, last_move_played, last_move_played.get_raw() >> 12, 153 | // prev_fen, board.pretty_string(), prev_board.pretty_string()); 154 | // } 155 | // 156 | //} 157 | -------------------------------------------------------------------------------- /pleco_engine/src/time/uci_timer.rs: -------------------------------------------------------------------------------- 1 | use chrono; 2 | use pleco::core::masks::PLAYER_CNT; 3 | use std::time; 4 | 5 | #[derive(Clone)] 6 | pub enum LimitsType { 7 | Time(UCITimer), // use time limits 8 | MoveTime(u64), // search for exactly x msec 9 | Mate(u16), // Search for a mate in x moves 10 | Depth(u16), // Search only x plys 11 | Nodes(u64), // Search only x nodes 12 | Infinite, // infinite 13 | Ponder, // ponder mode 14 | } 15 | 16 | impl LimitsType { 17 | pub fn is_depth(&self) -> bool { 18 | match *self { 19 | LimitsType::Depth(_x) => true, 20 | _ => false, 21 | } 22 | } 23 | } 24 | 25 | #[derive(Clone)] 26 | pub struct UCITimer { 27 | pub time_msec: [i64; PLAYER_CNT], // time each player has remaining 28 | pub inc_msec: [i64; PLAYER_CNT], // increments for each palyer after each turn 29 | pub moves_to_go: u32, // Moves to go until next time control sent 30 | } 31 | 32 | impl UCITimer { 33 | pub fn blank() -> Self { 34 | UCITimer { 35 | time_msec: [0; PLAYER_CNT], 36 | inc_msec: [0; PLAYER_CNT], 37 | moves_to_go: 0, 38 | } 39 | } 40 | 41 | pub fn is_blank(&self) -> bool { 42 | self.time_msec[0] == 0 43 | && self.time_msec[1] == 0 44 | && self.inc_msec[0] == 0 45 | && self.inc_msec[1] == 0 46 | } 47 | 48 | pub fn display(&self) { 49 | println!( 50 | "time: [{}, {}], inc: [{}, {}], moves to go: {}", 51 | self.time_msec[0], 52 | self.time_msec[1], 53 | self.inc_msec[0], 54 | self.inc_msec[1], 55 | self.moves_to_go 56 | ); 57 | } 58 | } 59 | 60 | #[derive(Clone)] 61 | pub struct PreLimits { 62 | pub time: Option, 63 | pub move_time: Option, 64 | pub nodes: Option, 65 | pub depth: Option, 66 | pub mate: Option, 67 | pub infinite: bool, 68 | pub ponder: bool, 69 | pub search_moves: Vec, 70 | } 71 | 72 | impl PreLimits { 73 | pub fn print(&self) { 74 | if let Some(ref time) = self.time { 75 | println!( 76 | "time_msec: W = {}, B = {}", 77 | time.time_msec[0], time.time_msec[1] 78 | ); 79 | println!( 80 | "inc_msec: W = {}, B = {}", 81 | time.inc_msec[0], time.inc_msec[1] 82 | ); 83 | println!("movestogo: {}", time.moves_to_go); 84 | } 85 | if let Some(move_time) = self.move_time { 86 | println!("move_time: {}", move_time) 87 | } 88 | if let Some(nodes) = self.nodes { 89 | println!("nodes: {}", nodes) 90 | } 91 | if let Some(depth) = self.depth { 92 | println!("depth: {}", depth) 93 | } 94 | if let Some(mate) = self.mate { 95 | println!("move_time: {}", mate) 96 | } 97 | println!("infinite: {}", self.infinite); 98 | println!("ponder: {}", self.ponder); 99 | if self.search_moves.len() > 1 { 100 | print!("search_moves:"); 101 | self.search_moves.iter().for_each(|p| print!(" {}", p)); 102 | println!(); 103 | } 104 | } 105 | pub fn blank() -> Self { 106 | PreLimits { 107 | time: None, 108 | move_time: None, 109 | nodes: None, 110 | depth: None, 111 | mate: None, 112 | infinite: false, 113 | ponder: false, 114 | search_moves: Vec::new(), 115 | } 116 | } 117 | 118 | pub fn create(self) -> Limits { 119 | let mut limits = Limits { 120 | search_moves: self.search_moves.clone(), 121 | limits_type: LimitsType::Infinite, 122 | start: time::Instant::now(), 123 | }; 124 | 125 | limits.limits_type = if self.ponder { 126 | LimitsType::Ponder 127 | } else if let Some(m_time) = self.move_time { 128 | LimitsType::MoveTime(m_time) 129 | } else if let Some(mate) = self.mate { 130 | LimitsType::Mate(mate) 131 | } else if let Some(depth) = self.depth { 132 | LimitsType::Depth(depth) 133 | } else if let Some(nodes) = self.nodes { 134 | LimitsType::Nodes(nodes) 135 | } else if self.infinite { 136 | LimitsType::Infinite 137 | } else if let Some(timer) = self.time { 138 | LimitsType::Time(timer) 139 | } else { 140 | LimitsType::Infinite 141 | }; 142 | limits 143 | } 144 | } 145 | 146 | #[derive(Clone)] 147 | pub struct Limits { 148 | pub search_moves: Vec, 149 | pub limits_type: LimitsType, 150 | pub start: time::Instant, 151 | } 152 | 153 | impl Limits { 154 | pub fn use_time_management(&self) -> Option { 155 | match self.limits_type { 156 | LimitsType::Time(ref timer) => Some(timer.clone()), 157 | _ => None, 158 | } 159 | } 160 | 161 | pub fn blank() -> Self { 162 | Limits { 163 | search_moves: Vec::new(), 164 | limits_type: LimitsType::Infinite, 165 | start: time::Instant::now(), 166 | } 167 | } 168 | 169 | pub fn elapsed(&self) -> i64 { 170 | chrono::Duration::from_std(self.start.elapsed()) 171 | .unwrap() 172 | .num_milliseconds() 173 | } 174 | 175 | pub fn use_movetime(&self) -> Option { 176 | match self.limits_type { 177 | LimitsType::MoveTime(time) => Some(time), 178 | _ => None, 179 | } 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /pleco/README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | [![Pleco][pleco-badge]][pleco-link] 4 | 5 | [![Build][build-badge]][build-link] 6 | [![License][license-badge]][license-link] 7 | [![Commits][commits-badge]][commits-link] 8 | 9 |
10 | 11 | # Overview 12 | 13 | Pleco is a Chess Library, containing the building blocks of the chess engine [Stockfish](https://stockfishchess.org/), 14 | re-written entirely in Rust. 15 | 16 | This project is split into two crates, `pleco` (the library you are currently in), which contains the library functionality, 17 | and `pleco_engine`, which contains the UCI (Universal Chess Interface) compatible Engine & AI. 18 | 19 | The overall goal of pleco is to recreate the Stockfish engine in rust, for comparison and 20 | educational purposes. As such, the majority of the algorithms used here are a direct port of Stockfish's, and the 21 | credit for the majority of the code go directly to the maintainers and authors of Stockfish. 22 | 23 | - [Documentation](https://docs.rs/pleco) 24 | - [crates.io](https://crates.io/crates/pleco) 25 | 26 | For the chess engine implemented using this library provided by `pleco`, 27 | see [pleco_engine](https://github.com/sfleischman105/Pleco/tree/main/pleco_engine). 28 | 29 | ## Features 30 | 31 | Some of the features `pleco` implements: 32 | 33 | - Bitboard Representation of Piece Locations: 34 | - Ability for concurrent Board State access, for use by parallel searchers 35 | - Full Move-generation Capabilities, including generation of pseudo-legal moves 36 | - Statically computed lookup-tables (including Magic Bitboards) 37 | - Zobrist Hashing 38 | - A Transposition Table: lightning fast lookup table storing information for a board 39 | - Pre-implemented searchers, some of which using [rayon.rs](https://crates.io/crates/rayon) for easy parallelism 40 | 41 | ## Use 42 | 43 | To use Pleco inside your own Rust projects, 44 | [Pleco.rs is available as a library on crates.io](https://crates.io/crates/pleco). 45 | Pleco runs on all three distributions (`nightly`, `beta`, `stable`) of rust. 46 | 47 | ### Basic Usage 48 | 49 | Setting up a board position is extremely simple. 50 | 51 | ```rust 52 | use pleco::{Board,Player,PieceType}; 53 | 54 | let board = Board::start_pos(); 55 | assert_eq!(board.count_piece(Player::White,PieceType::P), 8); 56 | assert_eq!(&board.fen(),"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"); 57 | ``` 58 | 59 | #### Creating a board from a Position 60 | 61 | A `Board` can be created with any valid chess position using a valid FEN (Forsyth-Edwards Notation) String. 62 | Check out the [Wikipedia article](https://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation) for more information on FEN Strings 63 | and their format. 64 | 65 | ```rust 66 | let board: Board = Board::from_fen("rnbqkbnr/pp1ppppp/8/2p5/4P3/8/PPPP1PPP/RNBQKBNR w KQkq c6 0 2").unwrap(); 67 | ``` 68 | 69 | #### Applying and Generating Moves 70 | 71 | Moves are represented with a `BitMove` structure. They must be generated by a `Board` object directly, to be 72 | considered a valid move. Using `Board::generate_moves()` will generate all legal `BitMove`s of the current 73 | position for the current player. 74 | 75 | ```rust 76 | use pleco::{Board,BitMove}; 77 | 78 | let mut board = Board::start_pos(); // create a board of the starting position 79 | let moves = board.generate_moves(); // generate all possible legal moves 80 | board.apply_move(moves[0]); 81 | assert_eq!(board.moves_played(), 1); 82 | ``` 83 | 84 | We can ask the Board to apply a move to itself from a string. This string must follow the format of a standard 85 | UCI Move, in the format [src_sq][dst_sq][promo]. E.g., moving a piece from A1 to B3 would have a uci string of "a1b3", 86 | while promoting a pawn would look something like "e7e81". If the board is supplied a UCI move that is either 87 | incorrectly formatted or illegal, false shall be returned. 88 | 89 | ```rust 90 | let mut board = Board::start_pos(); // create a board of the starting position 91 | let success = board.apply_uci_move("e7e8q"); // apply a move where piece on e7 -> eq, promotes to queen 92 | assert!(!success); // Wrong, not a valid move for the starting position 93 | ``` 94 | 95 | #### Undoing Moves 96 | 97 | We can revert to the previous chessboard state with a simple `Board::undo_move()`: 98 | 99 | ```rust 100 | let mut board = Board::start_pos(); 101 | board.apply_uci_move("e2e4"); // A very good starting move, might I say 102 | assert_eq!(board.moves_played(),1); 103 | board.undo_move(); 104 | assert_eq!(board.moves_played(),0); 105 | ``` 106 | 107 | ## Contributing 108 | 109 | Any and all contributions are welcome! Open up a PR to contribute some improvements. Look at the Issues tab to see what needs some help. 110 | 111 | ## License 112 | 113 | Pleco is distributed under the terms of the MIT license. See [LICENSE](LICENSE) for details. Opening a pull requests is assumed to signal agreement with these licensing terms. 114 | 115 | [build-link]: https://github.com/pleco-rs/Pleco/blob/main/.github/workflows/test.yml 116 | [build-badge]: https://img.shields.io/github/actions/workflow/status/pleco-rs/Pleco/test.yml?branch=main&style=for-the-badge&label=tanton&logo=github 117 | [license-badge]: https://img.shields.io/github/license/pleco-rs/Pleco?style=for-the-badge&label=license&color=success 118 | [license-link]: https://github.com/pleco-rs/Pleco/blob/main/LICENSE 119 | [commits-badge]: https://img.shields.io/github/commit-activity/m/pleco-rs/Pleco?style=for-the-badge 120 | [commits-link]: https://github.com/pleco-rs/Pleco/commits/main 121 | [pleco-badge]: https://img.shields.io/crates/v/pleco.svg?style=for-the-badge 122 | [pleco-link]: https://crates.io/crates/pleco 123 | [pleco-engine-badge]: https://img.shields.io/crates/v/pleco_engine.svg?style=for-the-badge 124 | [pleco-engine-link]: https://crates.io/crates/pleco_engine 125 | -------------------------------------------------------------------------------- /pleco_engine/src/root_moves/root_moves_list.rs: -------------------------------------------------------------------------------- 1 | use std::iter::{ExactSizeIterator, FusedIterator, IntoIterator, Iterator}; 2 | use std::mem; 3 | use std::ops::{Deref, DerefMut, Index, IndexMut}; 4 | use std::ptr; 5 | use std::slice; 6 | use std::sync::atomic::{AtomicUsize, Ordering}; 7 | 8 | use super::{RootMove, MAX_MOVES}; 9 | use pleco::{BitMove, MoveList}; 10 | 11 | pub struct RootMoveList { 12 | len: AtomicUsize, 13 | moves: [RootMove; MAX_MOVES], 14 | } 15 | 16 | impl Clone for RootMoveList { 17 | fn clone(&self) -> Self { 18 | RootMoveList { 19 | len: AtomicUsize::new(self.len.load(Ordering::SeqCst)), 20 | moves: self.moves, 21 | } 22 | } 23 | } 24 | 25 | unsafe impl Send for RootMoveList {} 26 | unsafe impl Sync for RootMoveList {} 27 | 28 | impl RootMoveList { 29 | /// Creates an empty `RootMoveList`. 30 | #[inline] 31 | pub fn new() -> Self { 32 | unsafe { 33 | RootMoveList { 34 | len: AtomicUsize::new(0), 35 | moves: [mem::MaybeUninit::uninit().assume_init(); MAX_MOVES], 36 | } 37 | } 38 | } 39 | 40 | /// Returns the length of the list. 41 | #[inline(always)] 42 | pub fn len(&self) -> usize { 43 | self.len.load(Ordering::SeqCst) 44 | } 45 | 46 | /// Replaces the current `RootMoveList` with another `RootMoveList`. 47 | pub fn clone_from_other(&mut self, other: &RootMoveList) { 48 | self.len.store(other.len(), Ordering::SeqCst); 49 | unsafe { 50 | let self_moves: *mut [RootMove; MAX_MOVES] = 51 | self.moves.as_mut_ptr() as *mut [RootMove; MAX_MOVES]; 52 | let other_moves: *const [RootMove; MAX_MOVES] = 53 | other.moves.as_ptr() as *const [RootMove; MAX_MOVES]; 54 | ptr::copy_nonoverlapping(other_moves, self_moves, 1); 55 | } 56 | } 57 | 58 | /// Replaces the current `RootMoveList` with the moves inside a `MoveList`. 59 | pub fn replace(&mut self, moves: &MoveList) { 60 | self.len.store(moves.len(), Ordering::SeqCst); 61 | for (i, mov) in moves.iter().enumerate() { 62 | self[i] = RootMove::new(*mov); 63 | } 64 | } 65 | 66 | /// Applies `RootMove::rollback()` to each `RootMove` inside. 67 | #[inline] 68 | pub fn rollback(&mut self) { 69 | self.iter_mut().for_each(|b| b.prev_score = b.score); 70 | } 71 | 72 | /// Returns the first `RootMove` in the list. 73 | /// 74 | /// # Safety 75 | /// 76 | /// May return a nonsense `RootMove` if the list hasn't been initialized since the start. 77 | #[inline] 78 | pub fn first(&mut self) -> &mut RootMove { 79 | unsafe { self.get_unchecked_mut(0) } 80 | } 81 | 82 | /// Converts to a `MoveList`. 83 | pub fn to_list(&self) -> MoveList { 84 | let vec = self.iter().map(|m| m.bit_move).collect::>(); 85 | MoveList::from(vec) 86 | } 87 | 88 | /// Returns the previous best score. 89 | #[inline] 90 | pub fn prev_best_score(&self) -> i32 { 91 | unsafe { self.get_unchecked(0).prev_score } 92 | } 93 | 94 | #[inline] 95 | pub fn insert_score_depth(&mut self, index: usize, score: i32, depth: i16) { 96 | unsafe { 97 | let rm: &mut RootMove = self.get_unchecked_mut(index); 98 | rm.score = score; 99 | rm.depth_reached = depth; 100 | } 101 | } 102 | 103 | #[inline] 104 | pub fn insert_score(&mut self, index: usize, score: i32) { 105 | unsafe { 106 | let rm: &mut RootMove = self.get_unchecked_mut(index); 107 | rm.score = score; 108 | } 109 | } 110 | 111 | pub fn find(&mut self, mov: BitMove) -> Option<&mut RootMove> { 112 | self.iter_mut().find(|m| m.bit_move == mov) 113 | } 114 | } 115 | 116 | impl Deref for RootMoveList { 117 | type Target = [RootMove]; 118 | 119 | #[inline] 120 | fn deref(&self) -> &[RootMove] { 121 | unsafe { 122 | let p = self.moves.as_ptr(); 123 | slice::from_raw_parts(p, self.len()) 124 | } 125 | } 126 | } 127 | 128 | impl DerefMut for RootMoveList { 129 | #[inline] 130 | fn deref_mut(&mut self) -> &mut [RootMove] { 131 | unsafe { 132 | let p = self.moves.as_mut_ptr(); 133 | slice::from_raw_parts_mut(p, self.len()) 134 | } 135 | } 136 | } 137 | 138 | impl Index for RootMoveList { 139 | type Output = RootMove; 140 | 141 | #[inline] 142 | fn index(&self, index: usize) -> &RootMove { 143 | &(**self)[index] 144 | } 145 | } 146 | 147 | impl IndexMut for RootMoveList { 148 | #[inline] 149 | fn index_mut(&mut self, index: usize) -> &mut RootMove { 150 | &mut (**self)[index] 151 | } 152 | } 153 | 154 | pub struct MoveIter<'a> { 155 | movelist: &'a RootMoveList, 156 | idx: usize, 157 | len: usize, 158 | } 159 | 160 | impl<'a> Iterator for MoveIter<'a> { 161 | type Item = RootMove; 162 | 163 | #[inline] 164 | fn next(&mut self) -> Option { 165 | if self.idx >= self.len { 166 | None 167 | } else { 168 | unsafe { 169 | let m = *self.movelist.get_unchecked(self.idx); 170 | self.idx += 1; 171 | Some(m) 172 | } 173 | } 174 | } 175 | 176 | #[inline] 177 | fn size_hint(&self) -> (usize, Option) { 178 | (self.len - self.idx, Some(self.len - self.idx)) 179 | } 180 | } 181 | 182 | impl<'a> IntoIterator for &'a RootMoveList { 183 | type Item = RootMove; 184 | type IntoIter = MoveIter<'a>; 185 | 186 | #[inline] 187 | fn into_iter(self) -> Self::IntoIter { 188 | MoveIter { 189 | movelist: &self, 190 | idx: 0, 191 | len: self.len(), 192 | } 193 | } 194 | } 195 | 196 | impl<'a> ExactSizeIterator for MoveIter<'a> {} 197 | 198 | impl<'a> FusedIterator for MoveIter<'a> {} 199 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | [![Pleco][pleco-badge]][pleco-link] 4 | [![Pleco][pleco-engine-badge]][pleco-engine-link] 5 | 6 | [![Build][build-badge]][build-link] 7 | [![License][license-badge]][license-link] 8 | [![Commits][commits-badge]][commits-link] 9 | 10 |
11 | 12 | # Overview 13 | 14 | Pleco is a chess Engine & Library derived from Stockfish, written entirely in Rust. 15 | 16 | This project is split into two crates, `pleco`, which contains the library functionality, and `pleco_engine`, which contains the 17 | UCI (Universal Chess Interface) compatible engine. 18 | 19 | The overall goal for this project is to utilize the efficiency of Rust to create a Chess AI matching the speed of modern chess engines. 20 | For the engine, the majority of the code is a direct port of Stockfish's C++ code. See [their website](https://stockfishchess.org/) for 21 | more information about the engine. As such, the credit for all of the advanced algorithms used for searching, evaluation, 22 | and many others, go directly to the maintainers and authors of Stockfish. This project is for speed comparisons 23 | between the two languages, as well as for educational purposes. 24 | 25 | - [Documentation](https://docs.rs/pleco), [crates.io](https://crates.io/crates/pleco) for library functionality 26 | - [Documentation](https://docs.rs/pleco_engine), [crates.io](https://crates.io/crates/pleco_engine) for the Engine. 27 | 28 | ## Standalone Installation and Use 29 | 30 | To use pleco as an executable, please [navigate to here](https://github.com/sfleischman105/Pleco/tree/main/pleco_engine) and read the `README.md`. 31 | 32 | ## Using Pleco as a Library 33 | 34 | To use pleco inside your own Rust projects, [Pleco.rs is available as a library on crates.io.](https://crates.io/crates/pleco) 35 | Simply include the current version in your `Cargo.toml`: 36 | 37 | ``` 38 | [dependencies] 39 | pleco = "x.x.x" 40 | ``` 41 | 42 | And add the following to a `main.rs` or `lib.rs`: 43 | 44 | ```rust 45 | extern crate pleco; 46 | ``` 47 | 48 | As of version `0.5.0`, the pleco library is available on all three Rust channels (stable, beta, nightly). 49 | 50 | ### Basic Usage 51 | 52 | Setting up a board position is extremely simple. 53 | 54 | ```rust 55 | use pleco::{Board,Player,PieceType}; 56 | 57 | let board = Board::start_pos(); 58 | assert_eq!(board.count_piece(Player::White,PieceType::P), 8); 59 | assert_eq!(&board.fen(),"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"); 60 | ``` 61 | 62 | #### Creating a board from a Position 63 | 64 | A `Board` can be created with any valid chess position using a valid FEN (Forsyth-Edwards Notation) String. 65 | Check out the [Wikipedia article](https://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation) for more information on FEN Strings 66 | and their format. 67 | 68 | ```rust 69 | let board = Board::from_fen("rnbqkbnr/pp1ppppp/8/2p5/4P3/8/PPPP1PPP/RNBQKBNR w KQkq c6 0 2").unwrap(); 70 | ``` 71 | 72 | #### Applying and Generating Moves 73 | 74 | Moves are represented with a `BitMove` structure. They must be generated by a `Board` object directly, to be 75 | considered a valid move. Using `Board::generate_moves()` will generate all legal `BitMove`s of the current 76 | position for the current player. 77 | 78 | ```rust 79 | use pleco::{Board,BitMove}; 80 | 81 | let mut board = Board::start_pos(); // create a board of the starting position 82 | let moves = board.generate_moves(); // generate all possible legal moves 83 | board.apply_move(moves[0]); 84 | assert_eq!(board.moves_played(), 1); 85 | ``` 86 | 87 | We can ask the Board to apply a move to itself from a string. This string must follow the format of a standard 88 | UCI Move, in the format [src_sq][dst_sq][promo]. E.g., moving a piece from A1 to B3 would have a uci string of "a1b3", 89 | while promoting a pawn would look something like "e7e81". If the board is supplied a UCI move that is either 90 | incorrectly formatted or illegal, false shall be returned. 91 | 92 | ```rust 93 | let mut board = Board::start_pos(); // create a board of the starting position 94 | let success = board.apply_uci_move("e7e8q"); // apply a move where piece on e7 -> eq, promotes to queen 95 | assert!(!success); // Wrong, not a valid move for the starting position 96 | ``` 97 | 98 | #### Undoing Moves 99 | 100 | We can revert to the previous chessboard state with a simple Board::undo_move() 101 | 102 | ```rust 103 | let mut board = Board::start_pos(); 104 | board.apply_uci_move("e2e4"); // A very good starting move, might I say 105 | assert_eq!(board.moves_played(),1); 106 | board.undo_move(); 107 | assert_eq!(board.moves_played(),0); 108 | ``` 109 | 110 | For more information about `pleco` as a library, see the [pleco README.md](https://github.com/sfleischman105/Pleco/tree/main/pleco). 111 | 112 | ## Contributing 113 | 114 | Any and all contributions are welcome! Open up a PR to contribute some improvements. Look at the Issues tab to see what needs some help. 115 | 116 | ## License 117 | 118 | The Pleco Engine is distributed under the GNU General Public License version 3 (or any later version at your option). See [LICENSE](LICENSE) for full details. 119 | 120 | The Pleco crate (the library functionality) is distributed under the terms of the MIT license. See [pleco/LICENSE](pleco/LICENSE) for details. 121 | 122 | Opening a pull requests is assumed to signal agreement with these licensing terms. 123 | 124 | [build-link]: https://github.com/pleco-rs/Pleco/blob/main/.github/workflows/test.yml 125 | [build-badge]: https://img.shields.io/github/actions/workflow/status/pleco-rs/Pleco/test.yml?branch=main&style=for-the-badge&label=tanton&logo=github 126 | [license-badge]: https://img.shields.io/github/license/pleco-rs/Pleco?style=for-the-badge&label=license&color=success 127 | [license-link]: https://github.com/pleco-rs/Pleco/blob/main/LICENSE 128 | [commits-badge]: https://img.shields.io/github/commit-activity/m/pleco-rs/Pleco?style=for-the-badge 129 | [commits-link]: https://github.com/pleco-rs/Pleco/commits/main 130 | [pleco-badge]: https://img.shields.io/crates/v/pleco.svg?style=for-the-badge 131 | [pleco-link]: https://crates.io/crates/pleco 132 | [pleco-engine-badge]: https://img.shields.io/crates/v/pleco_engine.svg?style=for-the-badge 133 | [pleco-engine-link]: https://crates.io/crates/pleco_engine 134 | -------------------------------------------------------------------------------- /pleco/src/tools/pleco_arc.rs: -------------------------------------------------------------------------------- 1 | //! A faster version of `std::sync::Arc`. 2 | //! 3 | //! This is mostly copied from [servo_arc](https://doc.servo.org/servo_arc/index.html), so see 4 | //! that documentation for more information. 5 | 6 | use std::ops::{Deref, DerefMut}; 7 | use std::ptr::NonNull; 8 | #[allow(unused_imports)] 9 | use std::sync::atomic; 10 | use std::sync::atomic::Ordering::{Acquire, Relaxed, Release}; 11 | 12 | /// The Inner structure of an `Arc`. 13 | pub struct ArcInner { 14 | count: atomic::AtomicUsize, 15 | data: T, 16 | } 17 | 18 | /// An `Arc` that ensures a single reference to it. Allows for modification to the 19 | /// state inside, and also transformation into an `Arc`. 20 | pub struct UniqueArc(Arc); 21 | 22 | unsafe impl Send for ArcInner {} 23 | unsafe impl Sync for ArcInner {} 24 | 25 | impl UniqueArc { 26 | #[inline] 27 | /// Construct a new UniqueArc 28 | pub fn new(data: T) -> Self { 29 | UniqueArc(Arc::new(data)) 30 | } 31 | 32 | #[inline] 33 | /// Convert to a shareable Arc once we're done using it 34 | pub fn shareable(self) -> Arc { 35 | self.0 36 | } 37 | } 38 | 39 | impl Deref for UniqueArc { 40 | type Target = T; 41 | fn deref(&self) -> &T { 42 | &self.0 43 | } 44 | } 45 | 46 | impl DerefMut for UniqueArc { 47 | fn deref_mut(&mut self) -> &mut T { 48 | // We know this to be uniquely owned 49 | unsafe { &mut (*self.0.ptr()).data } 50 | } 51 | } 52 | 53 | /// Reference counting pointer, shareable between threads. 54 | pub struct Arc { 55 | p: NonNull>, 56 | } 57 | 58 | unsafe impl Send for Arc {} 59 | unsafe impl Sync for Arc {} 60 | 61 | impl Arc { 62 | /// Creates a new `Arc`. 63 | #[inline] 64 | pub fn new(data: T) -> Self { 65 | let x = Box::new(ArcInner { 66 | count: atomic::AtomicUsize::new(1), 67 | data, 68 | }); 69 | unsafe { 70 | Arc { 71 | p: NonNull::new_unchecked(Box::into_raw(x)), 72 | } 73 | } 74 | } 75 | } 76 | 77 | impl Arc { 78 | /// Returns a pointer to the inner Arc. 79 | #[inline] 80 | fn ptr(&self) -> *mut ArcInner { 81 | self.p.as_ptr() 82 | } 83 | 84 | #[inline] 85 | fn inner(&self) -> &ArcInner { 86 | // This unsafety is ok because while this arc is alive we're guaranteed 87 | // that the inner pointer is valid. Furthermore, we know that the 88 | // `ArcInner` structure itself is `Sync` because the inner data is 89 | // `Sync` as well, so we're ok loaning out an immutable pointer to these 90 | // contents. 91 | unsafe { &*self.ptr() } 92 | } 93 | 94 | /// Gets a `& mut T` of the inner value if there is only one reference. 95 | #[inline] 96 | pub fn get_mut(this: &mut Self) -> Option<&mut T> { 97 | if this.is_unique() { 98 | unsafe { 99 | // See make_mut() for documentation of the threadsafety here. 100 | Some(&mut (*this.ptr()).data) 101 | } 102 | } else { 103 | None 104 | } 105 | } 106 | 107 | /// Allows for determining if the reference count is zero. 108 | #[inline] 109 | pub fn is_unique(&self) -> bool { 110 | // We can use Relaxed here, but the justification is a bit subtle. 111 | // 112 | // The reason to use Acquire would be to synchronize with other threads 113 | // that are modifying the refcount with Release, i.e. to ensure that 114 | // their writes to memory guarded by this refcount are flushed. However, 115 | // we know that threads only modify the contents of the Arc when they 116 | // observe the refcount to be 1, and no other thread could observe that 117 | // because we're holding one strong reference here. 118 | self.inner().count.load(Relaxed) == 1 119 | } 120 | 121 | // Non-inlined part of `drop`. Just invokes the destructor. 122 | #[inline(never)] 123 | unsafe fn drop_slow(&mut self) { 124 | let _ = Box::from_raw(self.ptr()); 125 | } 126 | } 127 | 128 | impl Deref for Arc { 129 | type Target = T; 130 | 131 | #[inline] 132 | fn deref(&self) -> &T { 133 | &self.inner().data 134 | } 135 | } 136 | 137 | impl Clone for Arc { 138 | #[inline] 139 | fn clone(&self) -> Self { 140 | self.inner().count.fetch_add(1, Relaxed); 141 | unsafe { 142 | Arc { 143 | p: NonNull::new_unchecked(self.ptr()), 144 | } 145 | } 146 | } 147 | } 148 | 149 | impl Drop for Arc { 150 | #[inline] 151 | fn drop(&mut self) { 152 | // Because `fetch_sub` is already atomic, we do not need to synchronize 153 | // with other threads unless we are going to delete the object. 154 | if self.inner().count.fetch_sub(1, Release) != 1 { 155 | return; 156 | } 157 | 158 | // This load is needed to prevent reordering of use of the data and 159 | // deletion of the data. Because it is marked `Release`, the decreasing 160 | // of the reference count synchronizes with this `Acquire` load. This 161 | // means that use of the data happens before decreasing the reference 162 | // count, which happens before this load, which happens before the 163 | // deletion of the data. 164 | // 165 | // As explained in the [Boost documentation][1], 166 | // 167 | // > It is important to enforce any possible access to the object in one 168 | // > thread (through an existing reference) to *happen before* deleting 169 | // > the object in a different thread. This is achieved by a "release" 170 | // > operation after dropping a reference (any access to the object 171 | // > through this reference must obviously happened before), and an 172 | // > "acquire" operation before deleting the object. 173 | // 174 | // [1]: (www.boost.org/doc/libs/1_55_0/doc/html/atomic/usage_examples.html) 175 | // [2]: https://github.com/rust-lang/rust/pull/41714 176 | self.inner().count.load(Acquire); 177 | 178 | unsafe { 179 | self.drop_slow(); 180 | } 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /pleco_engine/src/time/time_management.rs: -------------------------------------------------------------------------------- 1 | //! Time Management calculations for the searcher. 2 | 3 | use chrono; 4 | 5 | use super::uci_timer::UCITimer; 6 | use pleco::Player; 7 | 8 | use std::cell::UnsafeCell; 9 | use std::f64; 10 | use std::time::Instant; 11 | 12 | const MOVE_HORIZON: i64 = 50; 13 | const MAX_RATIO: f64 = 6.32; 14 | const STEAL_RATIO: f64 = 0.34; 15 | 16 | // TODO: These should be made into UCIOptions 17 | const MIN_THINKING_TIME: i64 = 20; 18 | const MOVE_OVERHEAD: i64 = 100; 19 | 20 | // Lower values means places less importance on the current move 21 | const SLOW_MOVER: i64 = 22; 22 | 23 | #[derive(PartialEq)] 24 | enum TimeCalc { 25 | Ideal, 26 | Max, 27 | } 28 | 29 | impl TimeCalc { 30 | #[inline(always)] 31 | pub fn t_max_ratio(&self) -> f64 { 32 | match *self { 33 | TimeCalc::Ideal => 1.0, 34 | TimeCalc::Max => MAX_RATIO, 35 | } 36 | } 37 | 38 | #[inline(always)] 39 | pub fn t_steal_ratio(&self) -> f64 { 40 | match *self { 41 | TimeCalc::Ideal => 0.0, 42 | TimeCalc::Max => STEAL_RATIO, 43 | } 44 | } 45 | } 46 | 47 | pub struct TimeManager { 48 | ideal_time: UnsafeCell, 49 | maximum_time: UnsafeCell, 50 | start: UnsafeCell, 51 | } 52 | 53 | unsafe impl Sync for TimeManager {} 54 | 55 | impl TimeManager { 56 | pub fn uninitialized() -> TimeManager { 57 | TimeManager { 58 | ideal_time: UnsafeCell::new(0), 59 | maximum_time: UnsafeCell::new(0), 60 | start: UnsafeCell::new(Instant::now()), 61 | } 62 | } 63 | 64 | pub fn start_timer(&self, start: Instant) { 65 | unsafe { 66 | let self_start = self.start.get(); 67 | *self_start = start; 68 | } 69 | } 70 | 71 | pub fn init(&self, start: Instant, timer: &UCITimer, turn: Player, ply: u16) { 72 | let moves_to_go: i64 = timer.moves_to_go as i64; 73 | let my_time: i64 = (timer.time_msec[turn as usize]) as i64; 74 | let my_inc: i64 = (timer.inc_msec[turn as usize]) as i64; 75 | 76 | let mut ideal_time = (timer.time_msec[turn as usize]).max(MIN_THINKING_TIME); 77 | let mut max_time = ideal_time; 78 | 79 | let max_mtg: i64 = if moves_to_go == 0 { 80 | MOVE_HORIZON 81 | } else { 82 | moves_to_go.min(MOVE_HORIZON) 83 | }; 84 | 85 | // We calculate optimum time usage for different hypothetical "moves to go"-values 86 | // and choose the minimum of calculated search time values. Usually the greatest 87 | // hypMTG gives the minimum values. 88 | for hyp_mtg in 1..=max_mtg { 89 | let mut hyp_my_time: i64 = 90 | my_time + my_inc * (hyp_mtg - 1) - MOVE_OVERHEAD * (2 + hyp_mtg.min(40)); 91 | hyp_my_time = hyp_my_time.max(0); 92 | 93 | let t1: i64 = MIN_THINKING_TIME 94 | + TimeManager::remaining( 95 | hyp_my_time, 96 | hyp_mtg, 97 | ply as i64, 98 | SLOW_MOVER, 99 | TimeCalc::Ideal, 100 | ); 101 | let t2: i64 = MIN_THINKING_TIME 102 | + TimeManager::remaining( 103 | hyp_my_time, 104 | hyp_mtg, 105 | ply as i64, 106 | SLOW_MOVER - 5, 107 | TimeCalc::Max, 108 | ); 109 | 110 | ideal_time = t1.min(ideal_time); 111 | max_time = t2.min(max_time); 112 | } 113 | 114 | unsafe { 115 | let self_start = self.start.get(); 116 | let self_ideal = self.ideal_time.get(); 117 | let self_max = self.maximum_time.get(); 118 | *self_start = start; 119 | *self_ideal = ideal_time; 120 | *self_max = max_time; 121 | } 122 | } 123 | 124 | pub fn start(&self) -> Instant { 125 | unsafe { *self.start.get() } 126 | } 127 | 128 | pub fn elapsed(&self) -> i64 { 129 | let start = self.start(); 130 | chrono::Duration::from_std(start.elapsed()) 131 | .unwrap() 132 | .num_milliseconds() 133 | } 134 | 135 | fn move_importance(ply: i64) -> f64 { 136 | const X_SCALE: f64 = 6.85; 137 | const X_SHIFT: f64 = 64.5; 138 | const SKEW: f64 = 0.171; 139 | 140 | let exp: f64 = ((ply as f64 - X_SHIFT) / X_SCALE).exp(); 141 | let base: f64 = 1.0 + exp; 142 | base.powf(-SKEW) + f64::MIN_POSITIVE 143 | } 144 | 145 | fn remaining( 146 | my_time: i64, 147 | movestogo: i64, 148 | move_num: i64, 149 | slow_mover: i64, 150 | time_type: TimeCalc, 151 | ) -> i64 { 152 | let slow_move_f: f64 = slow_mover as f64; 153 | let t_max_ratio: f64 = time_type.t_max_ratio(); 154 | let t_steal_ratio: f64 = time_type.t_steal_ratio(); 155 | 156 | let move_importance: f64 = (TimeManager::move_importance(move_num) * slow_move_f) / 100.0; 157 | let mut other_moves_importance: f64 = 0.0; 158 | 159 | for i in 1..movestogo { 160 | other_moves_importance += TimeManager::move_importance(move_num + 2 * i); 161 | } 162 | 163 | let ratio1: f64 = (t_max_ratio * move_importance) 164 | / (t_max_ratio * move_importance + other_moves_importance); 165 | let ratio2: f64 = (move_importance + t_steal_ratio * other_moves_importance) 166 | / (move_importance + other_moves_importance); 167 | 168 | (my_time as f64 * ratio1.min(ratio2)) as i64 169 | } 170 | 171 | #[inline(always)] 172 | pub fn maximum_time(&self) -> i64 { 173 | unsafe { *self.maximum_time.get() } 174 | } 175 | 176 | #[inline(always)] 177 | pub fn ideal_time(&self) -> i64 { 178 | unsafe { *self.ideal_time.get() } 179 | } 180 | } 181 | 182 | #[cfg(test)] 183 | mod tests { 184 | use super::*; 185 | 186 | #[test] 187 | fn time_man() { 188 | let timer = UCITimer { 189 | time_msec: [120000, 0], 190 | inc_msec: [6000, 0], 191 | moves_to_go: 20, 192 | }; 193 | let ply: u16 = 0; 194 | let time_man = TimeManager::uninitialized(); 195 | time_man.init(Instant::now(), &timer, Player::White, ply); 196 | let max = time_man.maximum_time(); 197 | let ideal = time_man.ideal_time(); 198 | println!("ideal: {} max: {}", ideal, max); 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /pleco/src/board/castle_rights.rs: -------------------------------------------------------------------------------- 1 | //! Module for the `Castling` structure, which helps provide an easy way for the 2 | //! `Board` to keep track of the various castling rights available for each player. 3 | //! 4 | //! Alongside keeping track of castling rights, it also keeps track of if a player has castled. 5 | //! 6 | //! At it's core, a [`Castling`] is a simple u8 which sets bits for each possible castling right. 7 | //! This is necessary to keep track of for a chess match due to determining future castlings. 8 | //! 9 | //! [`Castling`]: struct.Castling.html 10 | 11 | use core::masks::*; 12 | use core::*; 13 | use std::fmt; 14 | 15 | use core::sq::SQ; 16 | 17 | const ALL_CASTLING: u8 = 0b0000_1111; 18 | 19 | bitflags! { 20 | /// Structure to help with recognizing the various possibilities of castling. 21 | /// 22 | /// For internal use by the [`Board`] only. 23 | /// 24 | /// Keeps track of what sides are possible to castle from for each player. 25 | /// 26 | /// Does not guarantee that the player containing a castling bit can castle at that 27 | /// time. Rather marks that castling is a possibility, e.g. a Castling struct 28 | /// containing a bit marking WHITE_Q means that neither the White King or Queen-side 29 | /// rook has moved since the game started. 30 | /// 31 | /// [`Board`]: ../struct.Board.html 32 | pub struct Castling: u8 { 33 | const WHITE_K = C_WHITE_K_MASK; // White has King-side Castling ability 34 | const WHITE_Q = C_WHITE_Q_MASK; // White has Queen-side Castling ability 35 | const BLACK_K = C_BLACK_K_MASK; // Black has King-side Castling ability 36 | const BLACK_Q = C_BLACK_Q_MASK; // White has Queen-side Castling ability 37 | const WHITE_ALL = Self::WHITE_K.bits // White can castle for both sides 38 | | Self::WHITE_Q.bits; 39 | const BLACK_ALL = Self::BLACK_K.bits // Black can castle for both sides 40 | | Self::BLACK_Q.bits; 41 | } 42 | } 43 | 44 | impl Castling { 45 | /// Removes all castling possibility for a single player 46 | #[inline] 47 | pub fn remove_player_castling(&mut self, player: Player) { 48 | match player { 49 | Player::White => self.bits &= Self::BLACK_ALL.bits, 50 | Player::Black => self.bits &= Self::WHITE_ALL.bits, 51 | } 52 | } 53 | 54 | #[doc(hidden)] 55 | #[inline] 56 | pub const fn all_castling() -> Self { 57 | Castling { bits: ALL_CASTLING } 58 | } 59 | 60 | #[doc(hidden)] 61 | #[inline] 62 | pub const fn empty_set() -> Self { 63 | Castling { bits: 0 } 64 | } 65 | 66 | /// Removes King-Side castling possibility for a single player 67 | #[inline] 68 | pub fn remove_king_side_castling(&mut self, player: Player) { 69 | match player { 70 | Player::White => self.bits &= !Self::WHITE_K.bits, 71 | Player::Black => self.bits &= !Self::BLACK_K.bits, 72 | } 73 | } 74 | 75 | /// Removes Queen-Side castling possibility for a single player 76 | #[inline] 77 | pub fn remove_queen_side_castling(&mut self, player: Player) { 78 | match player { 79 | Player::White => self.bits &= !Self::WHITE_Q.bits, 80 | Player::Black => self.bits &= !Self::BLACK_Q.bits, 81 | } 82 | } 83 | 84 | /// Returns if a player can castle for a given side 85 | #[inline] 86 | pub fn castle_rights(self, player: Player, side: CastleType) -> bool { 87 | match player { 88 | Player::White => match side { 89 | CastleType::KingSide => self.contains(Self::WHITE_K), 90 | CastleType::QueenSide => self.contains(Self::WHITE_Q), 91 | }, 92 | Player::Black => match side { 93 | CastleType::KingSide => self.contains(Self::BLACK_K), 94 | CastleType::QueenSide => self.contains(Self::BLACK_Q), 95 | }, 96 | } 97 | } 98 | 99 | #[inline] 100 | pub fn player_can_castle(self, player: Player) -> Castling { 101 | Castling { 102 | bits: self.bits & (Castling::WHITE_ALL.bits >> (2 * player as u16)), 103 | } 104 | } 105 | 106 | /// Returns if both players have lost their ability to castle 107 | #[inline] 108 | pub fn no_castling(self) -> bool { 109 | !self.contains(Castling::WHITE_K) 110 | && !self.contains(Castling::WHITE_Q) 111 | && !self.contains(Castling::BLACK_K) 112 | && !self.contains(Castling::BLACK_Q) 113 | } 114 | 115 | #[inline] 116 | pub fn update_castling(&mut self, to: SQ, from: SQ) -> u8 { 117 | let mask_change: u8 = to.castle_rights_mask() | from.castle_rights_mask(); 118 | let to_return: u8 = self.bits & mask_change; 119 | self.bits &= !mask_change; 120 | to_return 121 | } 122 | 123 | /// Adds the Right to castle based on an `char`. 124 | /// 125 | /// ```md 126 | /// `K` -> Add White King-side Castling bit. 127 | /// `Q` -> Add White Queen-side Castling bit. 128 | /// `k` -> Add Black King-side Castling bit. 129 | /// `q` -> Add Black Queen-side Castling bit. 130 | /// `-` -> Do nothing. 131 | /// ``` 132 | /// 133 | /// # Panics 134 | /// 135 | /// Panics of the char is not `K`, `Q`, `k`, `q`, or `-`. 136 | pub fn add_castling_char(&mut self, c: char) { 137 | self.bits |= match c { 138 | 'K' => Castling::WHITE_K.bits, 139 | 'Q' => Castling::WHITE_Q.bits, 140 | 'k' => Castling::BLACK_K.bits, 141 | 'q' => Castling::BLACK_Q.bits, 142 | '-' => 0, 143 | _ => panic!(), 144 | }; 145 | } 146 | 147 | /// Returns a pretty String representing the castling state 148 | /// 149 | /// Used for FEN Strings, with (`K` | `Q`) representing white castling abilities, 150 | /// and (`k` | `q`) representing black castling abilities. If there are no bits set, 151 | /// returns a String containing "-". 152 | pub fn pretty_string(self) -> String { 153 | if self.no_castling() { 154 | "-".to_owned() 155 | } else { 156 | let mut s = String::default(); 157 | if self.contains(Castling::WHITE_K) { 158 | s.push('K'); 159 | } 160 | if self.contains(Castling::WHITE_Q) { 161 | s.push('Q'); 162 | } 163 | 164 | if self.contains(Castling::BLACK_K) { 165 | s.push('k'); 166 | } 167 | 168 | if self.contains(Castling::BLACK_Q) { 169 | s.push('q'); 170 | } 171 | assert!(!s.is_empty()); 172 | s 173 | } 174 | } 175 | } 176 | 177 | impl fmt::Display for Castling { 178 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 179 | write!(f, "{}", self.pretty_string()) 180 | } 181 | } 182 | 183 | #[cfg(test)] 184 | mod tests { 185 | use super::*; 186 | #[test] 187 | pub fn const_test() { 188 | let c = Castling::all(); 189 | let c_const = Castling::all_castling(); 190 | assert_eq!(c, c_const); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /pleco_engine/src/tables/material.rs: -------------------------------------------------------------------------------- 1 | //! Table to map from position -> material value; 2 | 3 | use pleco::core::masks::{PIECE_TYPE_CNT, PLAYER_CNT}; 4 | use pleco::core::mono_traits::*; 5 | use pleco::core::score::*; 6 | use pleco::tools::{prefetch_write, PreFetchable}; 7 | use pleco::{Board, PieceType, Player}; 8 | 9 | use super::{TableBase, TableBaseConst}; 10 | 11 | pub const PHASE_END_GAME: u16 = 0; 12 | pub const PHASE_MID_GAME: u16 = 128; 13 | 14 | pub const SCALE_FACTOR_DRAW: u8 = 0; 15 | pub const SCALE_FACTOR_ONEPAWN: u8 = 48; 16 | pub const SCALE_FACTOR_NORMAL: u8 = 64; 17 | pub const SCALE_FACTOR_MAX: u8 = 128; 18 | pub const SCALE_FACTOR_NONE: u8 = 255; 19 | 20 | // Polynomial material imbalance parameters 21 | const QUADRATIC_OURS: [[i32; PIECE_TYPE_CNT - 2]; PIECE_TYPE_CNT - 2] = [ 22 | [1667, 0, 0, 0, 0, 0], // Bishop pair 23 | [40, 0, 0, 0, 0, 0], // Pawn 24 | [32, 255, -3, 0, 0, 0], // Knight OUR PIECES 25 | [0, 104, 4, 0, 0, 0], // Bishop 26 | [-26, -2, 47, 105, -149, 0], // Rook 27 | [-189, 24, 117, 133, -134, -10], // Queen 28 | ]; // pair pawn knight bishop rook queen 29 | // OUR PIECES 30 | 31 | const QUADRATIC_THEIRS: [[i32; PIECE_TYPE_CNT - 2]; PIECE_TYPE_CNT - 2] = [ 32 | [0, 0, 0, 0, 0, 0], // Bishop pair 33 | [36, 0, 0, 0, 0, 0], // Pawn 34 | [9, 63, 0, 0, 0, 0], // Knight OUR PIECES 35 | [59, 65, 42, 0, 0, 0], // Bishop 36 | [46, 39, 24, -24, 0, 0], // Rook 37 | [97, 100, -42, 137, 268, 0], // Queen 38 | ]; // pair pawn knight bishop rook queen 39 | // THEIR PIECES 40 | 41 | pub struct MaterialEntry { 42 | key: u64, 43 | pub value: Value, 44 | pub factor: [u8; PLAYER_CNT], 45 | pub phase: u16, 46 | } 47 | 48 | impl MaterialEntry { 49 | #[inline(always)] 50 | pub fn score(&self) -> Score { 51 | Score(self.value, self.value) 52 | } 53 | 54 | #[inline(always)] 55 | pub fn scale_factor(&self, player: Player) -> u8 { 56 | self.factor[player as usize] 57 | } 58 | } 59 | 60 | // TODO: Use const-generics once it becomes available 61 | impl TableBaseConst for MaterialEntry { 62 | const ENTRY_COUNT: usize = 8192; 63 | } 64 | 65 | //pawns: PawnTable::new(16384), 66 | //material: Material::new(8192), 67 | pub struct Material { 68 | table: TableBase, 69 | } 70 | 71 | impl PreFetchable for Material { 72 | /// Pre-fetches a particular key. This means bringing it into the cache for faster eventual 73 | /// access. 74 | #[inline(always)] 75 | fn prefetch(&self, key: u64) { 76 | unsafe { 77 | let ptr = self.table.get_ptr(key); 78 | prefetch_write(ptr); 79 | } 80 | } 81 | } 82 | 83 | unsafe impl Send for Material {} 84 | 85 | impl Material { 86 | /// Creates a new `Material` of `size` entries. 87 | /// 88 | /// # Panics 89 | /// 90 | /// Panics if size is not a power of 2. 91 | pub fn new() -> Self { 92 | Material { 93 | table: TableBase::new().unwrap(), 94 | } 95 | } 96 | 97 | pub fn clear(&mut self) { 98 | self.table.clear(); 99 | } 100 | 101 | pub fn probe(&mut self, board: &Board) -> &mut MaterialEntry { 102 | let key: u64 = board.material_key(); 103 | let entry: &mut MaterialEntry = self.table.get_mut(key); 104 | if entry.key == key { 105 | return entry; 106 | } 107 | 108 | entry.key = key; 109 | entry.factor = [SCALE_FACTOR_NORMAL; PLAYER_CNT]; 110 | 111 | let npm_w: Value = board.non_pawn_material(Player::White); 112 | let npm_b: Value = board.non_pawn_material(Player::Black); 113 | let npm: Value = END_GAME_LIMIT.max(MID_GAME_LIMIT.min(npm_w + npm_b)); 114 | 115 | entry.phase = (((npm - END_GAME_LIMIT) * PHASE_MID_GAME as i32) 116 | / (MID_GAME_LIMIT - END_GAME_LIMIT)) as u16; 117 | 118 | let w_pawn_count: u8 = board.count_piece(Player::White, PieceType::P); 119 | let w_knight_count: u8 = board.count_piece(Player::White, PieceType::N); 120 | let w_bishop_count: u8 = board.count_piece(Player::White, PieceType::B); 121 | let w_rook_count: u8 = board.count_piece(Player::White, PieceType::R); 122 | let w_queen_count: u8 = board.count_piece(Player::White, PieceType::Q); 123 | 124 | let b_pawn_count: u8 = board.count_piece(Player::Black, PieceType::P); 125 | let b_knight_count: u8 = board.count_piece(Player::Black, PieceType::N); 126 | let b_bishop_count: u8 = board.count_piece(Player::Black, PieceType::B); 127 | let b_rook_count: u8 = board.count_piece(Player::Black, PieceType::R); 128 | let b_queen_count: u8 = board.count_piece(Player::Black, PieceType::Q); 129 | 130 | if w_pawn_count == 0 && npm_w - npm_b <= BISHOP_MG { 131 | entry.factor[Player::White as usize] = if npm_w < ROOK_MG { 132 | SCALE_FACTOR_DRAW 133 | } else if npm_b <= BISHOP_MG { 134 | 4 135 | } else { 136 | 14 137 | }; 138 | } 139 | 140 | if b_pawn_count == 0 && npm_b - npm_w <= BISHOP_MG { 141 | entry.factor[Player::Black as usize] = if npm_b < ROOK_MG { 142 | SCALE_FACTOR_DRAW 143 | } else if npm_w <= BISHOP_MG { 144 | 4 145 | } else { 146 | 14 147 | }; 148 | } 149 | 150 | if w_pawn_count == 1 && npm_w - npm_b <= BISHOP_MG { 151 | entry.factor[Player::White as usize] = SCALE_FACTOR_ONEPAWN; 152 | } 153 | 154 | if b_pawn_count == 1 && npm_b - npm_w <= BISHOP_MG { 155 | entry.factor[Player::Black as usize] = SCALE_FACTOR_ONEPAWN; 156 | } 157 | 158 | let w_pair_bish: u8 = (w_bishop_count > 1) as u8; 159 | let b_pair_bish: u8 = (b_bishop_count > 1) as u8; 160 | 161 | let piece_counts: [[u8; PIECE_TYPE_CNT - 2]; PLAYER_CNT] = [ 162 | [ 163 | w_pair_bish, 164 | w_pawn_count, 165 | w_knight_count, 166 | w_bishop_count, 167 | w_rook_count, 168 | w_queen_count, 169 | ], 170 | [ 171 | b_pair_bish, 172 | b_pawn_count, 173 | b_knight_count, 174 | b_bishop_count, 175 | b_rook_count, 176 | b_queen_count, 177 | ], 178 | ]; 179 | 180 | entry.value = 181 | (imbalance::(&piece_counts) - imbalance::(&piece_counts)) / 16; 182 | 183 | entry 184 | } 185 | } 186 | 187 | fn imbalance(piece_counts: &[[u8; PIECE_TYPE_CNT - 2]; PLAYER_CNT]) -> i32 { 188 | let mut bonus: i32 = 0; 189 | 190 | for pt1 in 0..6 { 191 | if piece_counts[P::player() as usize][pt1] == 0 { 192 | continue; 193 | } 194 | 195 | let mut v: i32 = 0; 196 | 197 | for pt2 in 0..6 { 198 | v += QUADRATIC_OURS[pt1][pt2] * piece_counts[P::player() as usize][pt2] as i32 199 | + QUADRATIC_THEIRS[pt1][pt2] * piece_counts[P::opp_player() as usize][pt2] as i32; 200 | } 201 | 202 | bonus += piece_counts[P::player() as usize][pt1] as i32 * v; 203 | } 204 | bonus 205 | } 206 | -------------------------------------------------------------------------------- /pleco/src/tools/eval.rs: -------------------------------------------------------------------------------- 1 | //! Module for simply evaluating the strength of a current position. 2 | //! 3 | //! This is a VERY basic evaluation, and while decent, it certainly isn't anything exceptional. 4 | 5 | use core::bitboard::BitBoard; 6 | use core::masks::*; 7 | use core::mono_traits::*; 8 | use core::score::Value; 9 | use core::*; 10 | use std::i32; 11 | use Board; 12 | 13 | lazy_static! { 14 | pub static ref PAWN_POS: [[i32; SQ_CNT]; PLAYER_CNT] = 15 | [flatten(flip(PAWN_POS_ARRAY)), flatten(PAWN_POS_ARRAY)]; 16 | } 17 | 18 | const PAWN_POS_ARRAY: [[i32; FILE_CNT]; RANK_CNT] = [ 19 | [0, 0, 0, 0, 0, 0, 0, 0], // RANK_8 20 | [5, 10, 15, 20, 20, 15, 10, 5], 21 | [4, 8, 12, 16, 16, 12, 8, 4], 22 | [0, 6, 9, 10, 10, 9, 6, 0], 23 | [0, 4, 6, 10, 10, 6, 4, 0], 24 | [0, 2, 3, 4, 4, 3, 2, 0], 25 | [0, 0, 0, -5, -5, 0, 0, 0], 26 | [0, 0, 0, 0, 0, 0, 0, 0], // RANK_1 27 | ]; 28 | 29 | // Flips the board, so rank_1 becomes rank_8, rank_8 becomes rank_1, rank_2 becomes rank_7, etc 30 | fn flip(arr: [[i32; FILE_CNT]; RANK_CNT]) -> [[i32; FILE_CNT]; RANK_CNT] { 31 | let mut new_arr: [[i32; FILE_CNT]; RANK_CNT] = [[0; FILE_CNT]; RANK_CNT]; 32 | for i in 0..RANK_CNT { 33 | new_arr[i] = arr[7 - i]; 34 | } 35 | new_arr 36 | } 37 | 38 | // Flattens 2D array to a singular 1D array 39 | fn flatten(arr: [[i32; FILE_CNT]; RANK_CNT]) -> [i32; SQ_CNT] { 40 | let mut new_arr: [i32; SQ_CNT] = [0; SQ_CNT]; 41 | for i in 0..SQ_CNT { 42 | new_arr[i] = arr[i / 8][i % 8]; 43 | } 44 | new_arr 45 | } 46 | 47 | /// A simple evaluation structure. This is included as an example, and shouldn't 48 | /// necessarily be used inside serious chess engines. 49 | /// 50 | /// ``` 51 | /// use pleco::tools::eval::Eval; 52 | /// use pleco::Board; 53 | /// 54 | /// let board = Board::start_pos(); 55 | /// let score = Eval::eval_low(&board); 56 | /// println!("Score: {}", score); 57 | /// ``` 58 | pub struct Eval; 59 | 60 | trait EvalRuns { 61 | fn eval_castling(&self) -> i32; 62 | fn eval_king_pos(&self) -> i32; 63 | fn eval_bishop_pos(&self) -> i32; 64 | fn eval_threats(&self) -> i32; 65 | fn eval_piece_counts(&self) -> i32; 66 | } 67 | 68 | const INFINITY: i32 = 30_001; 69 | const NEG_INFINITY: i32 = -30_001; 70 | const STALEMATE: i32 = 0; 71 | const PAWN_VALUE: i32 = 100; 72 | const KNIGHT_VALUE: i32 = 300; 73 | const BISHOP_VALUE: i32 = 300; 74 | const ROOK_VALUE: i32 = 500; 75 | const QUEEN_VALUE: i32 = 800; 76 | const KING_VALUE: i32 = 350; 77 | const CASTLE_ABILITY: i32 = 7; 78 | const CASTLE_BONUS: i32 = 20; 79 | const KING_BOTTOM: i32 = 8; 80 | const MATE: i32 = -25_000; 81 | const CHECK: i32 = 14; 82 | 83 | // Pawn, Knight, Bishop, Rook, Queen, King 84 | pub const PIECE_VALS: [i32; PIECE_TYPE_CNT] = [ 85 | 0, 86 | PAWN_VALUE, 87 | KNIGHT_VALUE, 88 | BISHOP_VALUE, 89 | ROOK_VALUE, 90 | QUEEN_VALUE, 91 | KING_VALUE, 92 | 0, 93 | ]; 94 | 95 | impl Eval { 96 | /// Evaluates the score of a `Board` for the current side to move. 97 | pub fn eval_low(board: &Board) -> Value { 98 | match board.turn() { 99 | Player::White => { 100 | eval_all::(board) - eval_all::(board) 101 | + board.non_pawn_material(Player::White) 102 | - board.non_pawn_material(Player::Black) 103 | } 104 | Player::Black => { 105 | eval_all::(board) - eval_all::(board) 106 | + board.non_pawn_material(Player::Black) 107 | - board.non_pawn_material(Player::White) 108 | } 109 | } 110 | } 111 | } 112 | 113 | fn eval_all(board: &Board) -> Value { 114 | if board.rule_50() >= 50 { 115 | return MATE; 116 | } 117 | eval_piece_counts::

(board) 118 | + eval_castling::

(board) 119 | + eval_king_pos::

(board) 120 | + eval_bishop_pos::

(board) 121 | + eval_king_blockers_pinners::

(board) 122 | + eval_pawns::

(board) 123 | } 124 | 125 | fn eval_piece_counts(board: &Board) -> i32 { 126 | board.count_piece(P::player(), PieceType::P) as i32 * PAWN_VALUE 127 | } 128 | 129 | fn eval_castling(board: &Board) -> i32 { 130 | let mut score: i32 = 0; 131 | 132 | if board.can_castle(P::player(), CastleType::KingSide) { 133 | score += CASTLE_ABILITY 134 | } 135 | if board.can_castle(P::player(), CastleType::QueenSide) { 136 | score += CASTLE_ABILITY 137 | } 138 | score 139 | } 140 | 141 | fn eval_king_pos(board: &Board) -> i32 { 142 | let mut score: i32 = 0; 143 | let us_ksq = board.king_sq(P::player()); 144 | 145 | if board.in_check() && P::player() == board.turn() { 146 | score -= CHECK 147 | } 148 | 149 | let bb_around_us: BitBoard = 150 | board.magic_helper.king_moves(us_ksq) & board.get_occupied_player(P::player()); 151 | score += bb_around_us.count_bits() as i32 * 9; 152 | 153 | score 154 | } 155 | 156 | fn eval_bishop_pos(board: &Board) -> i32 { 157 | let mut score: i32 = 0; 158 | 159 | if board.count_piece(P::player(), PieceType::B) > 1 { 160 | score += 19 161 | } 162 | 163 | score 164 | } 165 | 166 | fn eval_king_blockers_pinners(board: &Board) -> i32 { 167 | let mut score: i32 = 0; 168 | 169 | let blockers: BitBoard = board.all_pinned_pieces(P::player()); 170 | 171 | let them_blockers: BitBoard = blockers & board.get_occupied_player(P::opp_player()); 172 | 173 | // Our pieces blocking a check on their king 174 | let us_blockers: BitBoard = blockers & board.get_occupied_player(P::player()); 175 | 176 | score += 18 * us_blockers.count_bits() as i32; 177 | 178 | score += 6 * them_blockers.count_bits() as i32; 179 | 180 | score 181 | } 182 | 183 | fn eval_pawns(board: &Board) -> i32 { 184 | let mut score: i32 = 0; 185 | 186 | let pawns_bb: BitBoard = board.piece_bb(P::player(), PieceType::P); 187 | let mut bb = pawns_bb; 188 | let mut file_counts: [u8; FILE_CNT] = [0; FILE_CNT]; 189 | 190 | let mut sqs_defended: BitBoard = BitBoard(0); 191 | 192 | while bb.is_not_empty() { 193 | let lsb = bb.lsb(); 194 | let sq = lsb.to_sq(); 195 | sqs_defended |= board.magic_helper.pawn_attacks_from(sq, P::player()); 196 | file_counts[(sq.0 % 8) as usize] += 1; 197 | score += PAWN_POS[P::player() as usize][sq.0 as usize]; 198 | bb &= !lsb; 199 | } 200 | 201 | // Add score for squares attacked by pawns 202 | score += sqs_defended.count_bits() as i32; 203 | 204 | // Add score for pawns defending other pawns 205 | sqs_defended &= pawns_bb; 206 | score += 3 * sqs_defended.count_bits() as i32; 207 | 208 | for i in 0..FILE_CNT { 209 | if file_counts[i] > 1 { 210 | score -= (file_counts[i] * 3) as i32; 211 | } 212 | if i > 0 && i < 7 && file_counts[i] > 0 { 213 | if file_counts[i - 1] != 0 { 214 | if file_counts[i + 1] != 0 { 215 | score += 7; 216 | } else { 217 | score += 3; 218 | } 219 | } else if file_counts[i + 1] != 0 { 220 | score += 3; 221 | } else { 222 | score -= 4; 223 | } 224 | } 225 | } 226 | 227 | score 228 | } 229 | -------------------------------------------------------------------------------- /pleco/benches/eval_benches.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use criterion::{black_box, Criterion}; 4 | 5 | use pleco::tools::eval::Eval; 6 | use pleco::Board; 7 | 8 | fn bench_100_evaluations(c: &mut Criterion) { 9 | c.bench_function("bench_100_evaluations", |b| { 10 | let rand_boards: Vec = RAND_BOARD_NON_CHECKS_100 11 | .iter() 12 | .map(|b| Board::from_fen(b).unwrap()) 13 | .collect(); 14 | 15 | b.iter(|| { 16 | { 17 | for board in rand_boards.iter() { 18 | black_box(Eval::eval_low(board)); 19 | } 20 | }; 21 | black_box(()) 22 | }) 23 | }); 24 | } 25 | 26 | criterion_group!(name = eval_benches; 27 | config = Criterion::default().sample_size(20).warm_up_time(Duration::from_millis(5)); 28 | targets = bench_100_evaluations 29 | ); 30 | 31 | static RAND_BOARD_NON_CHECKS_100: [&str; 100] = [ 32 | "3qkb1r/3ppp2/3r1np1/2Q4p/5P2/1P3B2/P1P1PP1P/R2NK2R b k - 0 22", 33 | "r3kb1r/1p1bpp2/1p3n1p/q2p2p1/8/PQ6/1P1NPPPP/R3KBNR w KQkq - 2 14", 34 | "r2qkbnr/pp2p1pp/2p1b3/3pNpB1/3P4/8/PP1NPPPP/R2QKB1R w KQkq - 2 8", 35 | "r1bqk2r/pppp3p/5b2/1P6/5p2/P5P1/1QP1P2P/RN2KB1R b KQkq - 2 16", 36 | "3rr3/2pkb3/2p1p3/p1Pn1p2/P1QP1P2/1P1KPP1p/7P/1R w - - 12 39", 37 | "3k3r/1r5p/6p1/1B6/1P2K3/P7/5RPP/ b - - 0 28", 38 | "r1bqkbnr/ppppppp1/n7/3P2p1/Q4P2/2P5/PP2P1PP/RN2KBNR b KQkq - 2 6", 39 | "3rk2r/pppb3p/2n1p3/1B6/3bP3/P4P2/3N2PP/4K2R b Kk - 0 22", 40 | "rn2kb1r/1ppqpbpp/5n2/p3Q3/8/PP1P4/1BPP1PPP/R2NKB1R b KQkq - 3 13", 41 | "r2qkbnr/ppp1Bppp/2n5/3p1b2/3P4/2N5/PPP1PPPP/R2QKBNR b KQkq - 0 4", 42 | "r3k1nr/pp1n1pbp/1qp1p1p1/6B1/P2PP1P1/1Pp2N2/2P2P2/R2QKB1R b KQkq - 0 13", 43 | "2r1r3/3k4/1qpn1p2/8/RP1pP3/3R1PPp/1p5P/1N4K w - - 2 39", 44 | "r1bqkb1r/ppp1pppp/2n5/3p2B1/P2Pn3/1P6/2P1PPPP/RN1QKBNR w KQkq - 2 5", 45 | "r2nk2r/1p2bppp/p3p3/8/P4nB1/1P1P2N1/2QN1PbP/R1B1K1R b Qkq - 7 21", 46 | "2r1k2r/pp1n2p1/5p1p/2P5/4PP2/8/PPb3PP/4KBNR b Kk - 0 19", 47 | "rkb4r/pp1pnppp/2npp3/8/P5P1/1P1N1N1P/3PPP2/2RQKB1R w K - 4 20", 48 | "7r/3b3p/Q2b1k2/2pq2p1/5p2/2P5/PP1NBPPP/3R1KR w - - 4 22", 49 | "r2qk1nr/1pp2pBp/8/3p4/pb1P2b1/2N5/PPP1PPPP/R2QKB1R b KQkq - 0 9", 50 | "8/5k1p/2p3p1/1p1p4/p4b2/5B1P/8/5K b - - 4 38", 51 | "2kr4/2pnr3/3p4/1p1P1B2/P3P2P/2K4P/2R5/R w - - 0 42", 52 | "8/pp5p/3r1bp1/1Pp1kbP1/P1B1p2P/4P3/2P2P2/3NKR w - - 5 25", 53 | "5rk1/3rbp1p/4p3/1N5p/5P2/1PNP2P1/1BK4P/4R b - - 3 35", 54 | "r1bq1b1r/p2pkppp/2p2n2/1n2N3/4p3/PPP1P3/3P1PPP/R1BQKB1R b KQ - 0 10", 55 | "3qkb1r/p3pppp/1r3n2/2pBn3/8/2N2PP1/PPPP1P1P/1RBQKR w k - 9 12", 56 | "1n1bk2r/2p3pp/p3bp2/4p3/K7/P1q2NPP/4PPB1/3R b k - 1 29", 57 | "r2qk2r/pppb1pp1/2n1p2p/8/1B1Pn2P/5NP1/PPP2P2/R2QKB1R b KQkq - 0 11", 58 | "r2qkr2/ppp1n3/6b1/7p/2P4Q/1P6/P3PPPP/R3KB1R w KQq - 3 19", 59 | "2N2knr/1p3ppp/2qPp3/8/p7/2PQ4/1P1P1PPP/R1B1KBNR b KQ - 0 17", 60 | "r1bqkbnr/pppppppp/8/6B1/1n6/8/PPP1PPPP/RN1QKBNR w KQkq - 2 5", 61 | "r2k1b1r/pp2ppp1/2p2n1p/3p1b2/1P1P4/q1N1PN2/2nBKPPP/2RQ1B1R w - - 0 12", 62 | "r4rk1/np2bppp/p3p3/2p5/2PP1q2/P3R3/1P2PPBP/R1BQK w Q - 1 17", 63 | "r3kb2/pppqpppr/5n1p/3p4/3P4/2N5/PPPBPPPP/R2QKB1R w KQq - 0 9", 64 | "r1bqkbnr/pppppppp/2n5/6B1/3P4/8/PPP1PPPP/RN1QKBNR b KQkq - 2 2", 65 | "r2qkr2/1bpppp2/p7/1p4pp/1P1Q4/PP2BP1P/1N2P1P1/1N2KnR w q - 0 18", 66 | "r1bqkr2/ppppn1pp/5p2/4p3/3P3B/1PQ2N2/P1P1PPPP/R3KB1R w KQq - 1 12", 67 | "r1b1kb1r/ppqp1p2/2n1p3/1B4pn/4P3/2P5/PP1N1PPP/R2QK2R b KQkq - 1 11", 68 | "r3kbnr/pppqpppp/6b1/1N2P3/3p2P1/8/PPPP1P1P/R1BQKB1R b KQkq - 0 8", 69 | "2Q5/4k1b1/6p1/5p1p/pP1P1P2/2P5/5RPP/5RK w - - 5 45", 70 | "r3k1nr/p1p1pp2/2N3qp/8/5pb1/bP6/3NPPPP/1R1QKB1R w Kkq - 1 16", 71 | "r3k1r1/4np1p/4p3/4n3/6P1/P4N1P/2NPPP2/4KB1R w K - 0 21", 72 | "5R2/2k5/pppr4/P3Q3/P2P2P1/2P2N2/3NP1P1/R3KB w Q - 0 33", 73 | "4k2r/pp2pppp/7n/3b4/8/2P4P/P3KP2/1R b - - 1 20", 74 | "4kr2/6R1/1pn4p/p1p1p3/2P5/P4P2/1P2BP1P/4K w - - 2 25", 75 | "3rkb1r/p2nnp1p/5qp1/4p3/8/1P1PN1P1/PB1PPPBP/R2QK1R w Qk - 0 16", 76 | "r7/pbkp1Np1/4rp1p/1Q2P3/8/P7/2P1P1PP/4KB1R b K - 0 20", 77 | "r1b1k2r/1p1pbp2/7p/p7/2p1P3/P5B1/1q1N1PP1/R2QKB1R w KQkq - 1 18", 78 | "3qkb1r/p1pnpp2/7p/6p1/3P4/PrP1PQ2/3B1PPP/R4RK w k - 0 17", 79 | "r3kb1r/p2ppp1p/5np1/8/1p2b3/1N2Q3/Pq3PPP/3RKBNR b Kkq - 1 14", 80 | "r7/pk6/2b1p3/5p2/P6P/4P2N/2P3r1/2K3n w - - 0 36", 81 | "r3kb1r/p3pppp/2p2n2/Rp6/3P1B2/1PP2b2/1P2PPPP/3QKB1R w Kkq - 0 14", 82 | "4k2r/2Pb1ppp/4p3/3p4/1P1P1B2/r3P1PB/1R1K1P1P/ w - - 1 25", 83 | "4kb2/p1p1pp1r/6p1/8/PP1qn3/1p3Q2/6PP/R4R1K w - - 0 24", 84 | "4r1k1/p4p1p/2p3p1/1p6/1P2B3/P3BP2/1P2KP1P/5R b - - 1 25", 85 | "5b1r/1N2nppp/5k2/2q5/p3Q3/P1P5/1P3PPP/R4RK b - - 4 22", 86 | "8/ppQ5/k7/5p2/7P/P3P1P1/3NP3/R3KB b Q - 3 36", 87 | "3k1b2/4p1p1/2B5/5p2/Pr6/2N5/R1P2PPP/4K2R b K - 4 26", 88 | "r2qkb2/1ppbpp2/p6r/3p4/6P1/1PP1P1QP/P2N1P2/RN2KB1R b KQq - 4 20", 89 | "4kb1B/4pp2/7p/8/1p6/2N1q3/2K1N1PP/7R w - - 0 26", 90 | "8/4p1bk/2P1Q3/5P1p/1q2P3/8/2P1K2P/6r w - - 3 36", 91 | "r2qk2r/Q2ppp1p/1p4p1/2P5/8/P2BPN2/1RP3PP/1N2K2R w Kk - 1 22", 92 | "5q1r/p1p1k2p/2p1bb1Q/3pp3/P1P5/1r6/3N2PP/5RK w - - 2 25", 93 | "3qkr2/2p2pb1/2p1ppN1/3p4/3P2P1/4r2P/2pK1P2/1R1Q1R b - - 1 21", 94 | "3k4/2pqpp2/6p1/pp2n3/8/8/Pb2QPB1/5K b - - 3 31", 95 | "3r4/1p3kbp/1p2p1p1/5q2/2Pp4/PP2PP2/4Q1PP/R3K2R w KQ - 0 20", 96 | "r5kr/pp2b1p1/2p5/2P4p/5B2/P5P1/1P2qPB1/1RR3K w - - 1 21", 97 | "1B2kb1r/p2p2p1/b1q1p3/5n1p/P1p1N3/2P2PP1/7P/R2QK1NR b k - 0 20", 98 | "3r1k1r/Q4ppp/3b4/8/6N1/PP1P2P1/K2N1P1P/4R2R w - - 3 28", 99 | "3rkb1r/p2nnp2/6pp/8/7P/PPP1P3/2qB1P2/R3K1R w Qk - 2 22", 100 | "r2qk2r/p1pb1ppp/p4n2/4p3/3bP3/Pn1P3P/1P3PP1/2B2KNR w kq - 1 14", 101 | "2kr4/1pp5/2b1p1pr/p2pPpRp/P4P1P/1PP1P3/3K4/2RQ b - - 0 31", 102 | "r7/pp1n3p/2b2p2/2b4Q/5PP1/6kP/PB2P2R/R3KB b Q - 2 22", 103 | "R7/p2kn2R/2p1p3/2b5/1P2p3/8/1PP2P2/1K1N1B b - - 0 34", 104 | "r2qkr1b/2pppp2/1pQ3p1/p5Bp/8/2P2N2/PP2PPPP/1N1RKB1R b Kq - 0 14", 105 | "r3k1n1/pp6/2p5/q2pb2r/4p1K1/1P2P1B1/P4PPP/1Q3R b q - 3 22", 106 | "8/k7/ppp5/2b1n1p1/8/2P3PP/2B1K3/2R w - - 0 44", 107 | "1r1kr3/p2p2pp/4Bn2/5P2/NP6/8/5PPP/3RK2R b K - 0 22", 108 | "3r2kr/5p1p/8/3Pn1PB/2p5/7q/7P/3RK1R b - - 1 35", 109 | "r3kb1Q/1p1bppp1/8/4q3/p3N3/n4PP1/P1p1B1KP/3R w q - 0 29", 110 | "r3kr2/pppn1pQ1/8/4p3/3pNq2/1P1B4/P2R1PPP/4KR b q - 1 24", 111 | "8/8/5k2/5p2/P7/3P1PP1/3QPP2/4KB w - - 6 39", 112 | "6nr/pQbk4/2N1ppB1/1N5p/3P4/P7/1P1P1PPP/R1B1K2R b KQ - 4 21", 113 | "r3kr2/p3pp2/1qbp2p1/1p5p/1P3B2/P1P2PP1/1N5P/R2Q1K b q - 0 24", 114 | "2r5/3k1p2/2p2P1p/p2r4/3P4/P1p4P/4R3/3BK w - - 1 39", 115 | "5r2/b4kp1/p1p4p/1p3P2/1P2R3/2N1PK1P/5P2/2N w - - 1 28", 116 | "2n1qk2/p4pb1/6p1/1p2P3/5P2/4P1P1/PPP2K1R/R4Q b - - 0 22", 117 | "r2qkb2/p5pQ/5p1p/4p3/4p3/P3PN2/1P1B2PP/3NKB1R w Kq - 0 27", 118 | "3k4/3rn3/1p6/p6p/1r2PP2/2R3P1/PP5P/1K2Q1R b - - 1 31", 119 | "3qkb2/2p1pp2/p4n1p/3P4/5r2/4NP2/P3PKPP/2R2B1R w - - 5 22", 120 | "r1b1k2r/p3b2p/8/1P6/2p1NP2/4Pp2/PP3P1P/2R1KBR w kq - 0 25", 121 | "5k2/5p2/p2p3p/8/2pP4/5N2/7P/1K w - - 0 43", 122 | "r7/p1p5/2k2p2/3p2rp/4PNP1/P1P2P2/7P/3R2K b - - 0 33", 123 | "7r/6kp/2n2p2/p4r2/1P2NP2/P5KP/5RP1/3R w - - 2 45", 124 | "8/7p/5pk1/6p1/8/3R4/p1K5/6r w - - 0 43", 125 | "4k2r/R4p2/4p2p/2Pq2p1/3P1bP1/7P/5BB1/6KR b - - 2 34", 126 | "5r2/prk5/2pn3p/5pp1/6R1/1P4P1/PKP4P/7R w - f6 0 35", 127 | "5k2/1pp2p2/6p1/p4n2/5R2/2N2P2/6PP/4K b - - 2 34", 128 | "r3r3/2Q2pp1/p6k/3p4/2p1pP1P/4P1P1/2RKB3/1q w - - 0 41", 129 | "r6k/pp5p/6p1/2p2b2/2n5/8/7P/K w - - 0 39", 130 | "5r2/1b1rkp2/3pp3/2p3R1/p3P3/5PP1/q3BKP1/4Q1N b - - 1 38", 131 | "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", 132 | ]; 133 | -------------------------------------------------------------------------------- /pleco_engine/src/engine.rs: -------------------------------------------------------------------------------- 1 | //! The main searching structure. 2 | 3 | use std::io; 4 | use std::sync::atomic::Ordering; 5 | 6 | use pleco::BitMove; 7 | use pleco::Board; 8 | 9 | use consts::*; 10 | use threadpool::threadpool; 11 | use time::uci_timer::PreLimits; 12 | use uci::options::{OptionWork, OptionsMap}; 13 | use uci::parse; 14 | 15 | use search::eval::Evaluation; 16 | 17 | // --------- STATIC VARIABLES 18 | 19 | pub static ID_NAME: &str = "Pleco"; 20 | pub static ID_AUTHORS: &str = "Stephen Fleischman"; 21 | pub static VERSION: &str = env!("CARGO_PKG_VERSION"); 22 | 23 | #[derive(PartialEq)] 24 | enum SearchType { 25 | None, 26 | Search, 27 | Ponder, 28 | } 29 | 30 | pub struct PlecoSearcher { 31 | options: OptionsMap, 32 | search_mode: SearchType, 33 | board: Board, 34 | } 35 | 36 | impl PlecoSearcher { 37 | pub fn init(use_stdout: bool) -> Self { 38 | init_globals(); 39 | USE_STDOUT.store(use_stdout, Ordering::Relaxed); 40 | PlecoSearcher { 41 | options: OptionsMap::new(), 42 | search_mode: SearchType::None, 43 | board: Board::start_pos(), 44 | } 45 | } 46 | 47 | pub fn uci(&mut self) { 48 | self.uci_startup(); 49 | let mut full_command = String::new(); 50 | loop { 51 | full_command.clear(); 52 | io::stdin().read_line(&mut full_command).ok().unwrap(); 53 | let args: Vec<&str> = full_command.split_whitespace().collect(); 54 | let command: &str = args.first().unwrap_or(&""); 55 | match command { 56 | "" => continue, 57 | "uci" => self.uci_startup(), 58 | "setoption" => self.apply_option(&full_command), 59 | "options" | "alloptions" => self.options.display_all(), 60 | "ucinewgame" => self.clear_search(), 61 | "isready" => println!("readyok"), 62 | "position" => { 63 | if let Some(b) = parse::position_parse_board(&args[1..]) { 64 | self.board = b; 65 | } else { 66 | println!("unable to parse board"); 67 | } 68 | } 69 | "setboard" => { 70 | if let Some(b) = parse::setboard_parse_board(&args[1..]) { 71 | self.board = b; 72 | } else { 73 | println!("unable to parse board"); 74 | } 75 | } 76 | "go" => self.uci_go(&args[1..]), 77 | "quit" => { 78 | self.halt(); 79 | break; 80 | } 81 | "stop" => self.halt(), 82 | "eval" => Evaluation::trace(&self.board), 83 | _ => print!("Unknown Command: {}", full_command), 84 | } 85 | self.apply_all_options(); 86 | } 87 | } 88 | 89 | pub fn clear_search(&mut self) { 90 | self.clear_tt(); 91 | threadpool().clear_all(); 92 | } 93 | 94 | fn uci_go(&mut self, args: &[&str]) { 95 | let limit = parse::parse_time(&args); 96 | threadpool().uci_search(&self.board, &limit.create()) 97 | } 98 | 99 | fn apply_option(&mut self, full_command: &str) { 100 | let mut args = full_command.split_whitespace(); 101 | args.next().unwrap(); // setoption 102 | if let Some(non_name) = args.next() { 103 | if non_name != "name" { 104 | println!("setoption [name]"); 105 | return; 106 | } 107 | } else { 108 | println!("setoption name [name] "); 109 | return; 110 | } 111 | let mut name = String::new(); 112 | let mut value = String::new(); 113 | 114 | if let Some(third_arg) = args.next() { 115 | //[should be name of the option] 116 | name += third_arg; 117 | } else { 118 | println!("setoption name [name]"); 119 | return; 120 | } 121 | 122 | 'nv: while let Some(ref partial_name) = args.next() { 123 | if *partial_name == "value" { 124 | value = args 125 | .map(|s| s.to_string() + " ") 126 | .collect::() 127 | .trim() 128 | .to_string(); 129 | if &value == "" { 130 | println!("forgot a value!"); 131 | return; 132 | } 133 | break 'nv; 134 | } else { 135 | name += " "; 136 | name += partial_name; 137 | } 138 | } 139 | 140 | if !self.options.apply_option(&name, &value) { 141 | println!("unable to apply option: '{}'", full_command); 142 | } else { 143 | self.apply_all_options(); 144 | } 145 | } 146 | 147 | fn apply_all_options(&mut self) { 148 | while let Some(work) = self.options.work() { 149 | if self.is_searching() && !work.usable_while_searching() { 150 | println!("unable to apply work"); 151 | } else { 152 | match work { 153 | OptionWork::ClearTT => self.clear_tt(), 154 | OptionWork::ResizeTT(mb) => self.resize_tt(mb), 155 | OptionWork::Threads(num) => threadpool().set_thread_count(num), 156 | } 157 | } 158 | } 159 | } 160 | 161 | fn uci_startup(&self) { 162 | println!("id name {}", ID_NAME); 163 | println!("id authors {}", ID_AUTHORS); 164 | self.options.display_all(); 165 | println!("uciok"); 166 | } 167 | 168 | pub fn search(&mut self, board: &Board, limit: &PreLimits) { 169 | self.search_mode = SearchType::Search; 170 | threadpool().uci_search(board, &(limit.clone().create())); 171 | } 172 | 173 | pub fn halt(&mut self) { 174 | self.search_mode = SearchType::None; 175 | threadpool().set_stop(true); 176 | } 177 | 178 | pub fn stop_search_get_move(&mut self) -> BitMove { 179 | self.search_mode = SearchType::None; 180 | if self.is_searching() { 181 | threadpool().set_stop(true); 182 | threadpool().wait_for_finish(); 183 | threadpool().best_move() 184 | } else { 185 | BitMove::null() 186 | } 187 | } 188 | 189 | pub fn await_move(&mut self) -> BitMove { 190 | if self.is_searching() { 191 | threadpool().wait_for_finish(); 192 | threadpool().best_move() 193 | } else { 194 | BitMove::null() 195 | } 196 | } 197 | 198 | pub fn is_searching(&self) -> bool { 199 | if self.search_mode == SearchType::None { 200 | return false; 201 | } 202 | true 203 | } 204 | 205 | pub fn hash_percent(&self) -> f64 { 206 | tt().hash_percent() 207 | } 208 | 209 | pub fn clear_tt(&mut self) { 210 | unsafe { tt().clear() }; 211 | } 212 | 213 | pub fn resize_tt(&mut self, mb: usize) { 214 | unsafe { tt().resize_to_megabytes(mb) }; 215 | } 216 | 217 | pub fn use_stdout(&mut self, stdout: bool) { 218 | threadpool().stdout(stdout); 219 | } 220 | } 221 | 222 | #[cfg(test)] 223 | mod tests { 224 | use super::*; 225 | 226 | fn ply_3() { 227 | let mut limit = PreLimits::blank(); 228 | limit.depth = Some(3); 229 | let board = Board::start_pos(); 230 | let mut s = PlecoSearcher::init(false); 231 | s.search(&board, &limit); 232 | s.await_move(); 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /pleco/src/board/piece_locations.rs: -------------------------------------------------------------------------------- 1 | //! Contains the `PieceLocations` structure that maps from squares of a board to a player / piece at that square. 2 | //! 3 | //! This is useful mainly for the [`Board`] to use internally for fast square lookups. 4 | //! 5 | //! [`Board`]: ../struct.Board.html 6 | //! [`PieceLocations`]: struct.PieceLocations.html 7 | 8 | use std::mem; 9 | 10 | use super::FenBuildError; 11 | use core::masks::*; 12 | use core::sq::SQ; 13 | use core::*; 14 | 15 | /// Struct to allow fast lookups for any square. Given a square, allows for determining if there 16 | /// is a piece currently there, and if so, allows for determining it's color and type of piece. 17 | /// 18 | /// Piece Locations is a BLIND structure, Providing a function of |sq| -> |Piece AND/OR Player| 19 | /// The reverse cannot be done Looking up squares from a piece / player. 20 | pub struct PieceLocations { 21 | // Pieces are represented by the following bit_patterns: 22 | // x000 -> Pawn (P) 23 | // x001 -> Knight(N) 24 | // x010 -> Bishop (B) 25 | // x011 -> Rook(R) 26 | // x100 -> Queen(Q) 27 | // x101 -> King (K) 28 | // x110 -> ??? Undefined ?? 29 | // x111 -> None 30 | // 0xxx -> White Piece 31 | // 1xxx -> Black Piece 32 | 33 | // array of u8's, with standard ordering mapping index to square 34 | data: [Piece; SQ_CNT], 35 | } 36 | 37 | impl PieceLocations { 38 | /// Constructs a new `PieceLocations` with a default of no pieces on the board. 39 | pub const fn blank() -> PieceLocations { 40 | PieceLocations { 41 | data: [Piece::None; 64], 42 | } 43 | } 44 | 45 | /// Places a given piece for a given player at a certain square. 46 | /// 47 | /// # Panics 48 | /// 49 | /// Panics if Square is of index higher than 63 or the piece is `PieceType::{None || All}` 50 | #[inline] 51 | pub fn place(&mut self, square: SQ, player: Player, piece: PieceType) { 52 | debug_assert!(square.is_okay()); 53 | debug_assert!(piece.is_real()); 54 | self.data[square.0 as usize] = Piece::make_lossy(player, piece); 55 | } 56 | 57 | /// Removes a Square. 58 | /// 59 | /// # Panics 60 | /// 61 | /// Panics if Square is of index higher than 63. 62 | #[inline] 63 | pub fn remove(&mut self, square: SQ) { 64 | debug_assert!(square.is_okay()); 65 | self.data[square.0 as usize] = Piece::None; 66 | } 67 | 68 | /// Returns the Piece at a `SQ`, Or None if the square is empty. 69 | /// 70 | /// # Panics 71 | /// 72 | /// Panics if square is of index higher than 63. 73 | #[inline] 74 | pub fn piece_at(&self, square: SQ) -> Piece { 75 | debug_assert!(square.is_okay()); 76 | self.data[square.0 as usize] 77 | } 78 | 79 | /// Returns if a square is occupied. 80 | #[inline] 81 | pub fn at_square(&self, square: SQ) -> bool { 82 | assert!(square.is_okay()); 83 | self.data[square.0 as usize] != Piece::None 84 | } 85 | 86 | /// Returns the first square (if any) that a piece / player is at. 87 | #[inline] 88 | pub fn first_square(&self, piece: PieceType, player: Player) -> Option { 89 | let target = Piece::make_lossy(player, piece); 90 | for x in 0..64 { 91 | if target == self.data[x as usize] { 92 | return Some(SQ(x)); 93 | } 94 | } 95 | None 96 | } 97 | 98 | /// Returns if the Board contains a particular piece / player. 99 | #[inline] 100 | pub fn contains(&self, piece: PieceType, player: Player) -> bool { 101 | self.first_square(piece, player).is_some() 102 | } 103 | 104 | /// Generates a `PieceLocations` from a partial fen. A partial fen is defined as the first part of a 105 | /// fen, where the piece positions are available. 106 | pub(crate) fn from_partial_fen( 107 | ranks: &[&str], 108 | ) -> Result, FenBuildError> { 109 | let mut loc = Vec::with_capacity(64); 110 | for (i, rank) in ranks.iter().enumerate() { 111 | let min_sq = (7 - i) * 8; 112 | let max_sq = min_sq + 7; 113 | let mut idx = min_sq; 114 | for ch in rank.chars() { 115 | if idx < min_sq { 116 | return Err(FenBuildError::SquareSmallerRank { 117 | rank: i, 118 | square: SQ(idx as u8).to_string(), 119 | }); 120 | } else if idx > max_sq { 121 | return Err(FenBuildError::SquareLargerRank { 122 | rank: i, 123 | square: SQ(idx as u8).to_string(), 124 | }); 125 | } 126 | 127 | let dig = ch.to_digit(10); 128 | if let Some(digit) = dig { 129 | idx += digit as usize; 130 | } else { 131 | // if no space, then there is a piece here 132 | let piece = match ch { 133 | 'p' | 'P' => PieceType::P, 134 | 'n' | 'N' => PieceType::N, 135 | 'b' | 'B' => PieceType::B, 136 | 'r' | 'R' => PieceType::R, 137 | 'q' | 'Q' => PieceType::Q, 138 | 'k' | 'K' => PieceType::K, 139 | _ => return Err(FenBuildError::UnrecognizedPiece { piece: ch }), 140 | }; 141 | let player = if ch.is_lowercase() { 142 | Player::Black 143 | } else { 144 | Player::White 145 | }; 146 | loc.push((SQ(idx as u8), player, piece)); 147 | idx += 1; 148 | } 149 | } 150 | } 151 | Ok(loc) 152 | } 153 | } 154 | 155 | impl Clone for PieceLocations { 156 | // Need to use transmute copy as [_;64] does not automatically implement Clone. 157 | fn clone(&self) -> PieceLocations { 158 | unsafe { mem::transmute_copy(&self.data) } 159 | } 160 | } 161 | 162 | impl PartialEq for PieceLocations { 163 | fn eq(&self, other: &PieceLocations) -> bool { 164 | for sq in 0..64 { 165 | if self.data[sq] != other.data[sq] { 166 | return false; 167 | } 168 | } 169 | true 170 | } 171 | } 172 | 173 | pub struct PieceLocationsIter { 174 | locations: PieceLocations, 175 | sq: SQ, 176 | } 177 | 178 | impl Iterator for PieceLocationsIter { 179 | type Item = (SQ, Piece); 180 | 181 | fn next(&mut self) -> Option { 182 | loop { 183 | let cur_sq = self.sq; 184 | if cur_sq >= SQ::NONE { 185 | return None; 186 | } 187 | let piece = self.locations.data[cur_sq.0 as usize]; 188 | self.sq += SQ(1); 189 | if piece != Piece::None { 190 | return Some((cur_sq, piece)); 191 | } 192 | } 193 | } 194 | } 195 | 196 | impl IntoIterator for PieceLocations { 197 | type Item = (SQ, Piece); 198 | type IntoIter = PieceLocationsIter; 199 | 200 | #[inline(always)] 201 | fn into_iter(self) -> Self::IntoIter { 202 | PieceLocationsIter { 203 | locations: self, 204 | sq: SQ(0), 205 | } 206 | } 207 | } 208 | 209 | #[cfg(test)] 210 | mod test { 211 | use Board; 212 | 213 | #[test] 214 | fn stack_overflow_test() { 215 | let board = Board::start_pos(); 216 | let piece_locations = board.get_piece_locations(); 217 | let mut v = Vec::new(); 218 | for (sq, _) in piece_locations { 219 | v.push(sq); 220 | } 221 | assert_eq!(v.len(), 32); 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /pleco/src/core/macros.rs: -------------------------------------------------------------------------------- 1 | //! Macros for easily implementing bit operations, shifting operations, math operations, 2 | //! and the `From` trait to a struct. 3 | 4 | /// Allows for shifting operations to be applied to a struct consisting of a singular tuple 5 | /// containing a type that implements that bit operation. 6 | macro_rules! impl_indv_shift_ops { 7 | ($t:ty, $tname:ident, $fname:ident, $w:ident, $ta_name:ident, $fa_name:ident) => { 8 | impl $tname for $t { 9 | type Output = $t; 10 | 11 | #[inline] 12 | fn $fname(self, rhs: usize) -> $t { 13 | Self::from((self.0).$w(rhs as u32)) 14 | } 15 | } 16 | 17 | impl $ta_name for $t { 18 | #[inline] 19 | fn $fa_name(&mut self, rhs: usize) { 20 | *self = Self::from((self.0).$w(rhs as u32)); 21 | } 22 | } 23 | }; 24 | } 25 | 26 | /// Allows for bit operations to be applied to a struct consisting of a singular tuple 27 | /// containing a type that implements that bit operation. 28 | macro_rules! impl_indv_bit_ops { 29 | ($t:ty, $b:ty, $tname:ident, $fname:ident, $w:ident, $ta_name:ident, $fa_name:ident) => { 30 | impl $tname for $t { 31 | type Output = $t; 32 | 33 | #[inline] 34 | fn $fname(self, rhs: $t) -> $t { 35 | Self::from((self.0).$w(rhs.0)) 36 | } 37 | } 38 | 39 | impl $ta_name for $t { 40 | #[inline] 41 | fn $fa_name(&mut self, rhs: $t) { 42 | *self = Self::from((self.0).$w(rhs.0)); 43 | } 44 | } 45 | 46 | impl $tname<$b> for $t { 47 | type Output = $t; 48 | 49 | #[inline] 50 | fn $fname(self, rhs: $b) -> $t { 51 | Self::from((self.0).$w(rhs)) 52 | } 53 | } 54 | 55 | impl $ta_name<$b> for $t { 56 | #[inline] 57 | fn $fa_name(&mut self, rhs: $b) { 58 | *self = Self::from((self.0).$w(rhs)); 59 | } 60 | } 61 | }; 62 | } 63 | 64 | /// Implies bit operations `&, |, ^, !`, shifting operations `<< >>`, 65 | /// math operations `+, -, *, /, %` and `From` trait to a struct consisting of a 66 | /// singular tuple. This tuple must contain a type that implements these bit operations. 67 | macro_rules! impl_bit_ops { 68 | ($t:tt, $b:tt) => { 69 | impl From<$b> for $t { 70 | fn from(bit_type: $b) -> Self { 71 | $t(bit_type) 72 | } 73 | } 74 | 75 | impl From<$t> for $b { 76 | fn from(it: $t) -> Self { 77 | it.0 78 | } 79 | } 80 | 81 | impl_indv_bit_ops!($t, $b, Rem, rem, rem, RemAssign, rem_assign); 82 | impl_indv_bit_ops!($t, $b, BitOr, bitor, bitor, BitOrAssign, bitor_assign); 83 | impl_indv_bit_ops!($t, $b, BitAnd, bitand, bitand, BitAndAssign, bitand_assign); 84 | impl_indv_bit_ops!($t, $b, BitXor, bitxor, bitxor, BitXorAssign, bitxor_assign); 85 | 86 | impl_indv_bit_ops!($t, $b, Add, add, wrapping_add, AddAssign, add_assign); 87 | impl_indv_bit_ops!($t, $b, Div, div, wrapping_div, DivAssign, div_assign); 88 | impl_indv_bit_ops!($t, $b, Mul, mul, wrapping_mul, MulAssign, mul_assign); 89 | impl_indv_bit_ops!($t, $b, Sub, sub, wrapping_sub, SubAssign, sub_assign); 90 | 91 | impl_indv_shift_ops!($t, Shl, shl, wrapping_shl, ShlAssign, shl_assign); 92 | impl_indv_shift_ops!($t, Shr, shr, wrapping_shr, ShrAssign, shr_assign); 93 | 94 | impl Not for $t { 95 | type Output = $t; 96 | 97 | #[inline] 98 | fn not(self) -> $t { 99 | $t(!self.0) 100 | } 101 | } 102 | }; 103 | } 104 | 105 | #[cfg(test)] 106 | mod tests { 107 | 108 | use std::ops::*; 109 | 110 | macro_rules! test_bit_ops_impls { 111 | ($t:tt, $int_t:ty, $fi:expr, $si:expr, $opp:tt) => ({ 112 | let c_a = $fi $opp $si; 113 | let i_fo = $t::from($fi); 114 | let i_so = $t::from($si); 115 | let c = i_fo $opp i_so; 116 | assert_eq!(c.0, c_a); 117 | }); 118 | } 119 | 120 | macro_rules! test_math_impls { 121 | ($t:tt, $int_t:ty, $fi:expr, $si:expr, $opp:tt, $w_opp:tt) => ({ 122 | let c_a = $fi.$w_opp($si); 123 | let i_fo = $t::from($fi); 124 | let i_so = $t::from($si); 125 | let c = i_fo $opp i_so; 126 | assert_eq!(c.0, c_a); 127 | }); 128 | } 129 | 130 | macro_rules! test_bit_shift_impls { 131 | ($t:tt, $int_t:ty, $fi:expr, $si:expr, $opp:tt, $w_opp:tt) => ({ 132 | let c_a = $fi.$w_opp($si as u32); 133 | let i_fo = $t::from($fi); 134 | let c = i_fo $opp $si as usize; 135 | assert_eq!(c.0, c_a); 136 | }); 137 | } 138 | 139 | #[derive(Copy, Clone, Default, Hash, PartialEq, Eq)] 140 | struct DummyBB(pub u64); 141 | 142 | #[derive(Copy, Clone, Default, Hash, PartialEq, Eq)] 143 | struct DummySQ(pub u8); 144 | 145 | impl_bit_ops!(DummySQ, u8); 146 | impl_bit_ops!(DummyBB, u64); 147 | 148 | const SQ_CONSTS: [u8; 18] = [ 149 | 0xFE, 0xC1, 0x21, 0x9F, 0x44, 0xA0, 0xF7, 0xFF, 0x11, 0x7A, 0x01, 0x02, 0x03, 0x04, 0x05, 150 | 0x06, 0x07, 0x08, 151 | ]; 152 | 153 | const BIT_CONSTS: [u64; 18] = [ 154 | 0xFE00C4D0, 155 | 0x12F450012, 156 | 0xFFFFFFFF, 157 | 0x00000001, 158 | 0xA0E34001, 159 | 0x9ABBC0AA, 160 | 0x412CBFFF, 161 | 0x90000C10, 162 | 0xC200C4D0, 163 | 0xFE00C4D0, 164 | 0xFE00C4D0, 165 | 0x44FF2221, 166 | 0x772C0F64, 167 | 0x09F3C833, 168 | 0x04444A09, 169 | 0x3333FFEE, 170 | 0x670FA111, 171 | 0x7BBBB005, 172 | ]; 173 | 174 | #[test] 175 | pub fn macro_imlps_sq() { 176 | for bits in SQ_CONSTS.iter() { 177 | assert_eq!((!DummySQ::from(*bits)).0, !(*bits)); 178 | for bits_2 in SQ_CONSTS.iter() { 179 | test_bit_ops_impls!(DummySQ, u8, *bits, *bits_2, % ); 180 | test_bit_ops_impls!(DummySQ, u8, *bits, *bits_2, ^ ); 181 | test_bit_ops_impls!(DummySQ, u8, *bits, *bits_2, | ); 182 | test_bit_ops_impls!(DummySQ, u8, *bits, *bits_2, & ); 183 | test_math_impls!(DummySQ, u8, *bits, *bits_2, + , wrapping_add); 184 | test_math_impls!(DummySQ, u8, *bits, *bits_2, * , wrapping_mul); 185 | test_math_impls!(DummySQ, u8, *bits, *bits_2, - , wrapping_sub); 186 | test_math_impls!(DummySQ, u8, *bits, *bits_2, / , wrapping_div); 187 | test_bit_shift_impls!(DummySQ, u8, *bits, *bits_2, << , wrapping_shl); 188 | test_bit_shift_impls!(DummySQ, u8, *bits, *bits_2, >> , wrapping_shr); 189 | } 190 | } 191 | } 192 | 193 | #[test] 194 | pub fn macro_imlps_bb() { 195 | for bits in BIT_CONSTS.iter() { 196 | assert_eq!((!DummyBB::from(*bits)).0, !(*bits)); 197 | for bits_2 in BIT_CONSTS.iter() { 198 | test_bit_ops_impls!(DummyBB, u8, *bits, *bits_2, % ); 199 | test_bit_ops_impls!(DummyBB, u8, *bits, *bits_2, ^ ); 200 | test_bit_ops_impls!(DummyBB, u8, *bits, *bits_2, | ); 201 | test_bit_ops_impls!(DummyBB, u8, *bits, *bits_2, & ); 202 | test_math_impls!(DummyBB, u8, *bits, *bits_2, + , wrapping_add); 203 | test_math_impls!(DummyBB, u8, *bits, *bits_2, * , wrapping_mul); 204 | test_math_impls!(DummyBB, u8, *bits, *bits_2, - , wrapping_sub); 205 | test_math_impls!(DummyBB, u8, *bits, *bits_2, / , wrapping_div); 206 | } 207 | 208 | for x in 0..67usize { 209 | test_bit_shift_impls!(DummyBB, u8, *bits, x, << , wrapping_shl); 210 | test_bit_shift_impls!(DummyBB, u8, *bits, x, >> , wrapping_shr); 211 | } 212 | } 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /pleco_engine/src/uci/parse.rs: -------------------------------------------------------------------------------- 1 | //! Functions for parsing UCI input, including both time data & the position of the board to be searched. 2 | 3 | use pleco::Board; 4 | 5 | use time::uci_timer::{PreLimits, UCITimer}; 6 | 7 | fn is_keyword(arg: &str) -> bool { 8 | match arg { 9 | "searchmoves" | "ponder" | "wtime" | "btime" | "winc" | "binc" | "movestogo" | "depth" 10 | | "nodes" | "mate" | "movetime" | "infinite" => true, 11 | _ => false, 12 | } 13 | } 14 | 15 | // when "go" is passed into stdin, followed by several time control parameters 16 | // "searchmoves" "move"+ 17 | // "ponder" 18 | // "wtime" "[msec]" 19 | // "btime" "[msec]" 20 | // "winc" "[msec]" 21 | // "binc" "[msec]" 22 | // "movestogo" "[u32]" 23 | // "depth" "[u16]" 24 | // "nodes" "[u64]" 25 | // "mate" "[moves]" 26 | // movetime "msec" 27 | // "infinite" 28 | pub fn parse_time(args: &[&str]) -> PreLimits { 29 | let mut token_idx: usize = 0; 30 | let mut limit = PreLimits::blank(); 31 | let mut timer = UCITimer::blank(); 32 | while let Some(token) = args.get(token_idx) { 33 | match *token { 34 | "infinite" => { 35 | limit.infinite = true; 36 | } 37 | "ponder" => { 38 | limit.ponder = true; 39 | } 40 | "wtime" => { 41 | if let Some(wtime_s) = args.get(token_idx + 1) { 42 | if let Ok(wtime) = wtime_s.parse::() { 43 | timer.time_msec[0] = wtime; 44 | } 45 | token_idx += 1; 46 | } 47 | } 48 | "btime" => { 49 | if let Some(btime_s) = args.get(token_idx + 1) { 50 | if let Ok(btime) = btime_s.parse::() { 51 | timer.time_msec[1] = btime; 52 | } 53 | token_idx += 1; 54 | } 55 | } 56 | "winc" => { 57 | if let Some(winc_s) = args.get(token_idx + 1) { 58 | if let Ok(winc) = winc_s.parse::() { 59 | timer.inc_msec[0] = winc; 60 | } 61 | token_idx += 1; 62 | } 63 | } 64 | "binc" => { 65 | if let Some(binc_s) = args.get(token_idx + 1) { 66 | if let Ok(binc) = binc_s.parse::() { 67 | timer.inc_msec[1] = binc; 68 | } 69 | token_idx += 1; 70 | } 71 | } 72 | "movestogo" => { 73 | if let Some(movestogo_s) = args.get(token_idx + 1) { 74 | if let Ok(movestogo) = movestogo_s.parse::() { 75 | timer.moves_to_go = movestogo; 76 | } 77 | token_idx += 1; 78 | } 79 | } 80 | "depth" => { 81 | if let Some(depth_s) = args.get(token_idx + 1) { 82 | if let Ok(depth) = depth_s.parse::() { 83 | limit.depth = Some(depth); 84 | } 85 | token_idx += 1; 86 | } 87 | } 88 | "nodes" => { 89 | if let Some(nodes_s) = args.get(token_idx + 1) { 90 | if let Ok(nodes) = nodes_s.parse::() { 91 | limit.nodes = Some(nodes); 92 | } 93 | token_idx += 1; 94 | } 95 | } 96 | "mate" => { 97 | if let Some(mate_s) = args.get(token_idx + 1) { 98 | if let Ok(mate) = mate_s.parse::() { 99 | limit.mate = Some(mate); 100 | } 101 | token_idx += 1; 102 | } 103 | } 104 | "movetime" => { 105 | if let Some(movetime_s) = args.get(token_idx + 1) { 106 | if let Ok(movetime) = movetime_s.parse::() { 107 | limit.move_time = Some(movetime); 108 | } 109 | token_idx += 1; 110 | } 111 | } 112 | "searchmoves" => 'searchmoves: loop { 113 | if let Some(mov) = args.get(token_idx + 1) { 114 | if !is_keyword(mov) { 115 | limit.search_moves.push((*mov).to_string()); 116 | token_idx += 1; 117 | } else { 118 | break 'searchmoves; 119 | } 120 | } else { 121 | break 'searchmoves; 122 | } 123 | }, 124 | _ => {} 125 | } 126 | token_idx += 1; 127 | } 128 | if !timer.is_blank() { 129 | limit.time = Some(timer); 130 | } 131 | limit 132 | } 133 | 134 | fn valid_move(board: &mut Board, mov: &str) -> bool { 135 | let all_moves = board 136 | .generate_moves() 137 | .iter() 138 | .map(|m| m.stringify()) 139 | .collect::>(); 140 | 141 | if all_moves.contains(&mov.to_string()) { 142 | // println!("Yes: {}", mov); 143 | return board.apply_uci_move(mov); 144 | } 145 | // println!("Nope: :{}:", mov); 146 | // for mov3 in all_moves.iter() { 147 | // println!("mov: :{}:", &*mov3); 148 | // } 149 | false 150 | } 151 | 152 | pub fn setboard_parse_board(args: &[&str]) -> Option { 153 | let fen_string: String = args 154 | .iter() 155 | .take_while(|p: &&&str| **p != "moves") 156 | .map(|p| (*p).to_string()) 157 | .collect::>() 158 | .join(" "); 159 | Board::from_fen(&fen_string).ok() 160 | } 161 | 162 | pub fn position_parse_board(args: &[&str]) -> Option { 163 | let start: &str = args[0]; 164 | let mut board = if start == "startpos" { 165 | Some(Board::start_pos()) 166 | } else if start == "fen" { 167 | let fen_string: String = args[1..] 168 | .iter() 169 | .take_while(|p: &&&str| **p != "moves") 170 | .map(|p| (*p).to_string()) 171 | .collect::>() 172 | .join(" "); 173 | Board::from_fen(&fen_string).ok() 174 | } else { 175 | None 176 | }; 177 | 178 | let mut moves_start: Option = None; 179 | for (i, mov) in args.iter().enumerate() { 180 | if *mov == "moves" { 181 | moves_start = Some(i); 182 | } 183 | } 184 | 185 | if let Some(start) = moves_start { 186 | if let Some(ref mut op_board) = board { 187 | let mut index = start + 1; 188 | while index < args.len() { 189 | if !(valid_move(op_board, args[index])) { 190 | break; 191 | } 192 | index += 1; 193 | } 194 | } 195 | } 196 | board 197 | } 198 | 199 | #[cfg(test)] 200 | mod tests { 201 | use super::*; 202 | use pleco::Player; 203 | 204 | // TODO: More testing 205 | 206 | #[test] 207 | fn board_parse() { 208 | let b_str = "position startpos moves e2e4 e7e5"; 209 | let args: Vec<&str> = b_str.split_whitespace().collect(); 210 | let board = position_parse_board(&args[1..]).unwrap(); 211 | assert_eq!(board.moves_played(), 2); 212 | assert_eq!(board.turn(), Player::White); 213 | 214 | let b_str = "position startpos"; 215 | let args: Vec<&str> = b_str.split_whitespace().collect(); 216 | let board = position_parse_board(&args[1..]).unwrap(); 217 | assert_eq!(board.moves_played(), 0); 218 | } 219 | 220 | #[test] 221 | fn time_parse() { 222 | let t_str = "go infinite searchmoves e2e4 d2d4"; 223 | let args: Vec<&str> = t_str.split_whitespace().collect(); 224 | let time = parse_time(&args[1..]); 225 | assert_eq!(time.search_moves.len(), 2); 226 | } 227 | 228 | #[test] 229 | fn tempboard() { 230 | // should be e1g1 231 | let old_str = "position startpos moves e2e4 d7d5 e4d5 d8d5 g1f3 d5e4 f1e2 c7c6 e1g1"; 232 | // e8c8 233 | let args: Vec<&str> = old_str.split_whitespace().collect(); 234 | position_parse_board(&args[1..]).unwrap(); 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /pleco/src/helper/prelude.rs: -------------------------------------------------------------------------------- 1 | //! Default functions for accessing the statically computed tables. 2 | //! 3 | //! # Safety 4 | //! 5 | //! Using any of these methods is inherently unsafe, as it is not guaranteed that they'll be 6 | //! initiated correctly. Consider using the [`Helper`] structure to guarantee initialization. 7 | //! 8 | //! # Documentation 9 | //! 10 | //! These functions are documented in [`Helper`] , rather than here. 11 | //! 12 | //! [`Helper`]: ../struct.Helper.html 13 | 14 | use super::boards; 15 | use super::magic; 16 | use super::psqt; 17 | use super::zobrist; 18 | 19 | use core::score::{Score, Value}; 20 | use {BitBoard, File, Piece, PieceType, Player, Rank, SQ}; 21 | 22 | use std::mem; 23 | use std::sync::atomic::{compiler_fence, fence, AtomicBool, Ordering}; 24 | use std::sync::Once; 25 | 26 | static INITIALIZED: AtomicBool = AtomicBool::new(false); 27 | 28 | static INIT: Once = Once::new(); 29 | 30 | /// Initializes the static structures. Guarantees to only allow being called once. 31 | #[cold] 32 | pub fn init_statics() { 33 | INIT.call_once(|| { 34 | compiler_fence(Ordering::SeqCst); 35 | fence(Ordering::SeqCst); 36 | zobrist::init_zobrist(); 37 | psqt::init_psqt(); 38 | magic::init_magics(); 39 | compiler_fence(Ordering::SeqCst); 40 | boards::init_boards(); 41 | fence(Ordering::SeqCst); 42 | compiler_fence(Ordering::SeqCst); 43 | }); 44 | } 45 | 46 | // MAGIC FUNCTIONS 47 | 48 | /// Generate Bishop Moves `BitBoard` from a bishop square and all occupied squares on the board. 49 | /// This function will return captures to pieces on both sides. The resulting `BitBoard` must be 50 | /// AND'd with the inverse of the intending moving player's pieces. 51 | #[inline(always)] 52 | pub fn bishop_moves(occupied: BitBoard, sq: SQ) -> BitBoard { 53 | debug_assert!(sq.is_okay()); 54 | BitBoard(magic::bishop_attacks(occupied.0, sq.0)) 55 | } 56 | 57 | /// Generate Rook Moves `BitBoard` from a bishop square and all occupied squares on the board. 58 | /// This function will return captures to pieces on both sides. The resulting `BitBoard` must be 59 | /// AND'd with the inverse of the intending moving player's pieces. 60 | #[inline(always)] 61 | pub fn rook_moves(occupied: BitBoard, sq: SQ) -> BitBoard { 62 | debug_assert!(sq.is_okay()); 63 | BitBoard(magic::rook_attacks(occupied.0, sq.0)) 64 | } 65 | 66 | /// Generate Queen Moves `BitBoard` from a bishop square and all occupied squares on the board. 67 | /// This function will return captures to pieces on both sides. The resulting `BitBoard` must be 68 | /// AND'd with the inverse of the intending moving player's pieces. 69 | #[inline(always)] 70 | pub fn queen_moves(occupied: BitBoard, sq: SQ) -> BitBoard { 71 | debug_assert!(sq.is_okay()); 72 | BitBoard(magic::rook_attacks(occupied.0, sq.0) | magic::bishop_attacks(occupied.0, sq.0)) 73 | } 74 | 75 | // BOARD FUNCTIONS 76 | 77 | /// Generate Knight Moves `BitBoard` from a source square. 78 | #[inline(always)] 79 | pub fn knight_moves(sq: SQ) -> BitBoard { 80 | BitBoard(boards::knight_moves(sq)) 81 | } 82 | 83 | /// Generate King Moves `BitBoard` from a source square. 84 | #[inline(always)] 85 | pub fn king_moves(sq: SQ) -> BitBoard { 86 | BitBoard(boards::king_moves(sq)) 87 | } 88 | 89 | /// Get the distance of two squares. 90 | #[inline(always)] 91 | pub fn distance_of_sqs(sq_one: SQ, sq_two: SQ) -> u8 { 92 | boards::distance_of_sqs(sq_one, sq_two) 93 | } 94 | 95 | /// Get the line (diagonal / file / rank) `BitBoard` that two squares both exist on, if it exists. 96 | #[inline(always)] 97 | pub fn line_bb(sq_one: SQ, sq_two: SQ) -> BitBoard { 98 | BitBoard(boards::line_bb(sq_one, sq_two)) 99 | } 100 | 101 | /// Get the line (diagonal / file / rank) `BitBoard` between two squares, not including the squares, if it exists. 102 | #[inline(always)] 103 | pub fn between_bb(sq_one: SQ, sq_two: SQ) -> BitBoard { 104 | BitBoard(boards::between_bb(sq_one, sq_two)) 105 | } 106 | 107 | /// Gets the adjacent files `BitBoard` of the square 108 | #[inline(always)] 109 | pub fn adjacent_sq_file(sq: SQ) -> BitBoard { 110 | BitBoard(boards::adjacent_sq_file(sq)) 111 | } 112 | 113 | /// Gets the adjacent files `BitBoard` of the file 114 | #[inline(always)] 115 | pub fn adjacent_file(f: File) -> BitBoard { 116 | BitBoard(boards::adjacent_file(f)) 117 | } 118 | 119 | /// Pawn attacks `BitBoard` from a given square, per player. 120 | /// Basically, given square x, returns the BitBoard of squares a pawn on x attacks. 121 | #[inline(always)] 122 | pub fn pawn_attacks_from(sq: SQ, player: Player) -> BitBoard { 123 | BitBoard(boards::pawn_attacks_from(sq, player)) 124 | } 125 | 126 | /// Returns if three Squares are in the same diagonal, file, or rank. 127 | #[inline(always)] 128 | pub fn aligned(s1: SQ, s2: SQ, s3: SQ) -> bool { 129 | boards::aligned(s1, s2, s3) 130 | } 131 | 132 | /// Returns the ring of bits surrounding the square sq at a specified distance. 133 | /// 134 | /// # Safety 135 | /// 136 | /// distance must be less than 8, or else a panic will occur. 137 | #[inline(always)] 138 | pub fn ring_distance(sq: SQ, distance: u8) -> BitBoard { 139 | BitBoard(boards::ring_distance(sq, distance)) 140 | } 141 | 142 | /// Returns the BitBoard of all squares in the rank in front of the given one. 143 | #[inline(always)] 144 | pub fn forward_rank_bb(player: Player, rank: Rank) -> BitBoard { 145 | BitBoard(boards::forward_rank_bb(player, rank)) 146 | } 147 | 148 | /// Returns the `BitBoard` of all squares that can be attacked by a pawn 149 | /// of the same color when it moves along its file, starting from the 150 | /// given square. Basically, if the pawn progresses along the same file 151 | /// for the entire game, this bitboard would contain all possible forward squares 152 | /// it could attack 153 | /// 154 | /// # Safety 155 | /// 156 | /// The Square must be within normal bounds, or else a panic or undefined behaviour may occur. 157 | #[inline(always)] 158 | pub fn pawn_attacks_span(player: Player, sq: SQ) -> BitBoard { 159 | BitBoard(boards::pawn_attacks_span(player, sq)) 160 | } 161 | 162 | /// Returns the BitBoard of all squares in the file in front of the given one. 163 | /// 164 | /// # Safety 165 | /// 166 | /// The Square must be within normal bounds, or else a panic or undefined behaviour may occur. 167 | #[inline(always)] 168 | pub fn forward_file_bb(player: Player, sq: SQ) -> BitBoard { 169 | BitBoard(boards::forward_file_bb(player, sq)) 170 | } 171 | 172 | /// Returns a `BitBoard` allowing for testing of the a pawn being a 173 | /// "passed pawn". 174 | /// # Safety 175 | /// 176 | /// The Square must be within normal bounds, or else a panic or undefined behaviour may occur. 177 | #[inline(always)] 178 | pub fn passed_pawn_mask(player: Player, sq: SQ) -> BitBoard { 179 | BitBoard(boards::passed_pawn_mask(player, sq)) 180 | } 181 | 182 | // ZOBRIST FUNCTIONS 183 | 184 | /// Returns the Zobrist hash for a given square, and player / piece at that square. 185 | #[inline(always)] 186 | pub fn z_square(sq: SQ, piece: Piece) -> u64 { 187 | zobrist::z_square(sq, piece) 188 | } 189 | 190 | /// Returns the zobrist hash for a given zobrist square. 191 | #[inline(always)] 192 | pub fn z_ep(sq: SQ) -> u64 { 193 | zobrist::z_ep(sq) 194 | } 195 | 196 | /// Returns the Zobrish hash for a castling right. 197 | /// 198 | /// Undefined behavior will occur if the bits are greater than 15. 199 | #[inline(always)] 200 | pub fn z_castle(castle: u8) -> u64 { 201 | zobrist::z_castle(castle) 202 | } 203 | 204 | /// Returns the Zobrist Hash for having a Black Player's turn. 205 | #[inline(always)] 206 | pub fn z_side() -> u64 { 207 | zobrist::z_side() 208 | } 209 | 210 | /// Returns the Zobrist Hash for having no pawns. 211 | #[inline(always)] 212 | pub fn z_no_pawns() -> u64 { 213 | zobrist::z_no_pawns() 214 | } 215 | 216 | // PSQT FUNCTIONS 217 | 218 | /// Returns the score for a player's piece being at a particular square. 219 | #[inline(always)] 220 | pub fn psq(piece: Piece, sq: SQ) -> Score { 221 | psqt::psq(piece, sq) 222 | } 223 | 224 | /// Returns the value of a `Piece`. If `eg` is true, it returns the end game value. Otherwise, 225 | /// it'll return the midgame value. 226 | #[inline(always)] 227 | pub fn piece_value(piece: Piece, eg: bool) -> Value { 228 | psqt::piece_value(piece, eg) 229 | } 230 | 231 | /// Returns the value of a `PieceType`. If `eg` is true, it returns the end game value. Otherwise, 232 | /// it'll return the midgame value. 233 | #[inline(always)] 234 | pub fn piecetype_value(piece_type: PieceType, eg: bool) -> Value { 235 | let piece: Piece = unsafe { mem::transmute(piece_type) }; 236 | psqt::piece_value(piece, eg) 237 | } 238 | -------------------------------------------------------------------------------- /pleco/src/board/perft.rs: -------------------------------------------------------------------------------- 1 | //! perft, or Performance Test, Move Path Enumeration, tests the correctness of move-generation. 2 | //! 3 | //! Use these functions on a [`Board`] to test that the correct amount of leaf nodes are created. 4 | //! 5 | //! [`Board`]: ../struct.Board.html 6 | 7 | use super::{Board, MoveList}; 8 | 9 | /// Holds all information about the number of nodes counted. 10 | pub struct PerftNodes { 11 | /// Total number of nodes counted. 12 | pub nodes: u64, 13 | /// Number of capturing moves, including en-passant moves. 14 | pub captures: u64, 15 | /// Number of En-Passant moves. 16 | pub en_passant: u64, 17 | /// Number of Castles. 18 | pub castles: u64, 19 | /// The number of promotions 20 | pub promotions: u64, 21 | /// The number of checking moves. 22 | pub checks: u64, 23 | /// The number of moves resulting in a checkmate. 24 | pub checkmates: u64, 25 | } 26 | 27 | impl Default for PerftNodes { 28 | fn default() -> Self { 29 | PerftNodes { 30 | nodes: 0, 31 | captures: 0, 32 | en_passant: 0, 33 | castles: 0, 34 | promotions: 0, 35 | checks: 0, 36 | checkmates: 0, 37 | } 38 | } 39 | } 40 | 41 | impl PerftNodes { 42 | /// Checks for the correct number of nodes in each category. If the results don't 43 | /// match, panics with an error-message containing the failed checks. 44 | pub fn check( 45 | &self, 46 | nodes: u64, 47 | captures: u64, 48 | en_passant: u64, 49 | castles: u64, 50 | promotions: u64, 51 | checks: u64, 52 | checkmates: u64, 53 | ) { 54 | if self.captures != captures 55 | || self.en_passant != en_passant 56 | || self.promotions != promotions 57 | || self.checks != checks 58 | || self.checkmates != checkmates 59 | || self.castles != castles 60 | || self.nodes != nodes 61 | { 62 | panic!( 63 | "\n Perft did not return the correct results!\ 64 | \n total nodes {}, expected: {}, difference: {}\ 65 | \n captures {}, expected: {}, difference: {}\ 66 | \n en_passant {}, expected: {}, difference: {}\ 67 | \n promotions {}, expected: {}, difference: {}\ 68 | \n checks {}, expected: {}, difference: {}\ 69 | \n checkmates {}, expected: {}, difference: {}\ 70 | \n castles {}, expected: {}, difference: {}\n", 71 | self.nodes, 72 | nodes, 73 | nodes - self.nodes, 74 | self.captures, 75 | captures, 76 | captures as i64 - self.captures as i64, 77 | self.en_passant, 78 | en_passant, 79 | en_passant as i64 - self.en_passant as i64, 80 | self.promotions, 81 | promotions, 82 | promotions as i64 - self.promotions as i64, 83 | self.checks, 84 | checks, 85 | checks as i64 - self.checks as i64, 86 | self.checkmates, 87 | checkmates as i64, 88 | checkmates as i64 - self.checkmates as i64, 89 | self.castles, 90 | castles, 91 | castles as i64 - self.castles as i64 92 | ); 93 | } 94 | } 95 | } 96 | 97 | /// Returns the number of leaf nodes from generating moves to a certain depth. 98 | pub fn perft(board: &Board, depth: u16) -> u64 { 99 | if depth == 0 { 100 | 1 101 | } else { 102 | let mut pos = board.shallow_clone(); 103 | inner_perft(&mut pos, depth) 104 | } 105 | } 106 | 107 | /// Returns the count of all move types for the leaf nodes up to a certain depth. 108 | pub fn perft_all(board: &Board, depth: u16) -> PerftNodes { 109 | let mut b = board.shallow_clone(); 110 | let mut perft = PerftNodes::default(); 111 | inner_perft_all(&mut b, depth, &mut perft); 112 | perft 113 | } 114 | 115 | fn inner_perft(board: &mut Board, depth: u16) -> u64 { 116 | let moves: MoveList = board.generate_moves(); 117 | 118 | if depth == 1 { 119 | return moves.len() as u64; 120 | } 121 | 122 | let mut count: u64 = 0; 123 | 124 | for mov in moves { 125 | board.apply_move(mov); 126 | count += inner_perft(board, depth - 1); 127 | board.undo_move(); 128 | } 129 | 130 | count 131 | } 132 | 133 | fn inner_perft_all(board: &mut Board, depth: u16, perft: &mut PerftNodes) { 134 | let moves: MoveList = board.generate_moves(); 135 | 136 | if depth == 0 { 137 | perft.nodes += 1; 138 | if board.in_check() { 139 | perft.checks += 1; 140 | if moves.is_empty() { 141 | perft.checkmates += 1; 142 | } 143 | } 144 | } else { 145 | for mov in moves { 146 | if depth == 1 { 147 | if mov.is_capture() { 148 | perft.captures += 1 149 | } 150 | if mov.is_en_passant() { 151 | perft.en_passant += 1 152 | } 153 | if mov.is_castle() { 154 | perft.castles += 1 155 | } 156 | if mov.is_promo() { 157 | perft.promotions += 1 158 | } 159 | } 160 | board.apply_move(mov); 161 | inner_perft_all(board, depth - 1, perft); 162 | board.undo_move(); 163 | } 164 | } 165 | } 166 | 167 | #[cfg(test)] 168 | mod tests { 169 | use super::*; 170 | 171 | #[test] 172 | fn start_pos_perft() { 173 | let b: Board = Board::start_pos(); 174 | assert_eq!(1, perft(&b, 0)); 175 | assert_eq!(20, perft(&b, 1)); 176 | assert_eq!(400, perft(&b, 2)); 177 | assert_eq!(8902, perft(&b, 3)); 178 | assert_eq!(197_281, perft(&b, 4)); 179 | assert_eq!(4_865_609, perft(&b, 5)); 180 | } 181 | 182 | #[test] 183 | fn start_pos_perft_all() { 184 | let b: Board = Board::start_pos(); 185 | perft_all(&b, 3).check(8902, 34, 0, 0, 0, 12, 0); 186 | perft_all(&b, 4).check(197_281, 1576, 0, 0, 0, 469, 8); 187 | perft_all(&b, 5).check(4_865_609, 82_719, 258, 0, 0, 27351, 347); 188 | } 189 | 190 | #[test] 191 | fn perft_kiwipete() { 192 | let b: Board = 193 | Board::from_fen("r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq -") 194 | .unwrap(); 195 | assert_eq!(48, perft(&b, 1)); 196 | assert_eq!(2039, perft(&b, 2)); 197 | assert_eq!(97862, perft(&b, 3)); 198 | assert_eq!(4085603, perft(&b, 4)); 199 | assert_eq!(193690690, perft(&b, 5)); 200 | } 201 | 202 | // This passes, but we're gonna ignore it as it takes a long time to use. 203 | #[ignore] 204 | #[test] 205 | fn perft_kiwipete_all() { 206 | let b: Board = 207 | Board::from_fen("r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq -") 208 | .unwrap(); 209 | perft_all(&b, 3).check(97862, 17102, 45, 3162, 0, 993, 1); 210 | perft_all(&b, 4).check(4085603, 757163, 1929, 128013, 15172, 25523, 43); 211 | perft_all(&b, 5).check(193690690, 35043416, 73365, 4993637, 8392, 3309887, 30171); 212 | } 213 | 214 | #[test] 215 | fn perft_board_3() { 216 | let b: Board = Board::from_fen("8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - -").unwrap(); 217 | assert_eq!(14, perft(&b, 1)); 218 | assert_eq!(191, perft(&b, 2)); 219 | assert_eq!(2812, perft(&b, 3)); 220 | assert_eq!(43238, perft(&b, 4)); 221 | assert_eq!(674624, perft(&b, 5)); 222 | } 223 | 224 | #[test] 225 | fn perft_board_5() { 226 | let b: Board = 227 | Board::from_fen("rnbq1k1r/pp1Pbppp/2p5/8/2B5/8/PPP1NnPP/RNBQK2R w KQ - 1 8 ").unwrap(); 228 | assert_eq!(44, perft(&b, 1)); 229 | assert_eq!(1_486, perft(&b, 2)); 230 | assert_eq!(62_379, perft(&b, 3)); 231 | assert_eq!(2_103_487, perft(&b, 4)); 232 | assert_eq!(89_941_194, perft(&b, 5)); 233 | } 234 | 235 | #[test] 236 | fn perft_board_6() { 237 | let b: Board = Board::from_fen( 238 | "r4rk1/1pp1qppp/p1np1n2/2b1p1B1/2B1P1b1/P1NP1N2/1PP1QPPP/R4RK1 w - - 0 10", 239 | ) 240 | .unwrap(); 241 | assert_eq!(46, perft(&b, 1)); 242 | assert_eq!(2_079, perft(&b, 2)); 243 | assert_eq!(89_890, perft(&b, 3)); 244 | assert_eq!(3_894_594, perft(&b, 4)); 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /pleco_engine/src/uci/options.rs: -------------------------------------------------------------------------------- 1 | //! Houses any UCI compatible options, as well as the framework for parsing and applying them. 2 | 3 | use consts::{DEFAULT_TT_SIZE, MAX_THREADS}; 4 | 5 | use std::collections::VecDeque; 6 | use std::option::Option; 7 | 8 | use num_cpus; 9 | 10 | /// A List of work for the Searcher to do following the application of options 11 | pub enum OptionWork { 12 | ClearTT, 13 | ResizeTT(usize), 14 | Threads(usize), 15 | } 16 | 17 | impl OptionWork { 18 | pub fn usable_while_searching(&self) -> bool { 19 | match *self { 20 | OptionWork::ClearTT => false, 21 | OptionWork::ResizeTT(_) => false, 22 | OptionWork::Threads(_) => false, 23 | } 24 | } 25 | } 26 | 27 | /// A sorted map of options available 28 | pub struct OptionsMap { 29 | pub map: Vec>, 30 | pub work: VecDeque, 31 | } 32 | 33 | impl OptionsMap { 34 | /// Creates a new `OptionsMap`. 35 | pub fn new() -> Self { 36 | let mut map = Vec::new(); 37 | let work = VecDeque::new(); 38 | map.push(OptionsMap::clear_hash()); 39 | map.push(OptionsMap::resize_hash()); 40 | map.push(OptionsMap::threads()); 41 | map.sort_by(|a, b| a.option_name().cmp(b.option_name())); 42 | 43 | OptionsMap { map, work } 44 | } 45 | 46 | /// Applies an option and returns its success. 47 | pub fn apply_option(&mut self, name: &str, value: &str) -> bool { 48 | for op in self.map.iter() { 49 | if op.option_name() == name { 50 | if let Some(work) = op.mutate(value) { 51 | self.work.push_back(work); 52 | return true; 53 | } 54 | return false; 55 | } 56 | } 57 | false 58 | } 59 | 60 | /// Displays all available options in alphabetical order 61 | pub fn display_all(&self) { 62 | for op in self.map.iter() { 63 | println!("{}", op.display()); 64 | } 65 | } 66 | 67 | /// Returns if there is any work available from the `OptionsMap`. 68 | pub fn work(&mut self) -> Option { 69 | self.work.pop_front() 70 | } 71 | 72 | fn clear_hash() -> Box { 73 | let mutator: fn() -> Option = || Some(OptionWork::ClearTT); 74 | Box::new(UCIButton { 75 | option_name: "Clear Hash", 76 | mutator, 77 | }) 78 | } 79 | 80 | fn resize_hash() -> Box { 81 | let mutator: fn(i32) -> Option = 82 | |x: i32| Some(OptionWork::ResizeTT(x as usize)); 83 | Box::new(UCISpin { 84 | option_name: "Hash", 85 | default: DEFAULT_TT_SIZE as i32, 86 | min: 1, 87 | max: 8000, 88 | mutator, 89 | }) 90 | } 91 | 92 | fn threads() -> Box { 93 | let mutator: fn(i32) -> Option = |x: i32| Some(OptionWork::Threads(x as usize)); 94 | Box::new(UCISpin { 95 | option_name: "Threads", 96 | default: num_cpus::get() as i32, 97 | min: 1, 98 | max: MAX_THREADS as i32, 99 | mutator, 100 | }) 101 | } 102 | } 103 | 104 | // "option name Nullmove type check default true\n" 105 | // "option name Style type combo default Normal var Solid var Normal var Risky\n" 106 | // "option name Clear Hash type button\n" 107 | 108 | /// UCI compliant options for a searcher. 109 | pub trait UCIOption { 110 | // Returns the type of option. This can be one of the following: button, check, spin, text, or combo. 111 | fn option_type(&self) -> &'static str; 112 | 113 | // Returns the exact name of the option. 114 | fn option_name(&self) -> &'static str; 115 | 116 | // Returns the remaining display text of the `UCIOption`. 117 | fn partial_display(&self) -> Option; 118 | 119 | /// Displays the options 120 | fn display(&self) -> String { 121 | let mut display = 122 | String::from("option name ") + self.option_name() + " type " + self.option_type(); 123 | 124 | if let Some(part_dis) = self.partial_display() { 125 | display += " "; 126 | display += &part_dis; 127 | } 128 | display 129 | } 130 | 131 | /// Possibly mutates a value with an option. If additional work needs to be done 132 | /// by the searcher, `Some(OptionWork)` is returned back. 133 | fn mutate(&self, val: &str) -> Option; 134 | } 135 | 136 | pub struct UCIButton { 137 | option_name: &'static str, 138 | mutator: fn() -> Option, 139 | } 140 | 141 | pub struct UCICheck { 142 | option_name: &'static str, 143 | default: bool, 144 | mutator: fn(bool) -> Option, 145 | } 146 | 147 | pub struct UCISpin { 148 | option_name: &'static str, 149 | default: i32, 150 | max: i32, 151 | min: i32, 152 | mutator: fn(i32) -> Option, 153 | } 154 | 155 | pub struct UCICombo { 156 | option_name: &'static str, 157 | default: &'static str, 158 | values: &'static [&'static str], 159 | mutator: fn(&str) -> Option, 160 | } 161 | 162 | pub struct UCIText { 163 | option_name: &'static str, 164 | default: &'static str, 165 | mutator: fn(&str) -> Option, 166 | } 167 | 168 | impl UCIOption for UCIButton { 169 | fn option_type(&self) -> &'static str { 170 | "button" 171 | } 172 | 173 | fn option_name(&self) -> &'static str { 174 | self.option_name 175 | } 176 | 177 | fn partial_display(&self) -> Option { 178 | None 179 | } 180 | 181 | fn mutate(&self, _val: &str) -> Option { 182 | (self.mutator)() 183 | } 184 | } 185 | 186 | impl UCIOption for UCICheck { 187 | fn option_type(&self) -> &'static str { 188 | "check" 189 | } 190 | 191 | fn option_name(&self) -> &'static str { 192 | self.option_name 193 | } 194 | 195 | fn partial_display(&self) -> Option { 196 | Some(String::from("default ") + &self.default.to_string()) 197 | } 198 | 199 | fn mutate(&self, val: &str) -> Option { 200 | if val.contains("true") { 201 | return (self.mutator)(true); 202 | } else if val.contains("false") { 203 | return (self.mutator)(false); 204 | } 205 | None 206 | } 207 | } 208 | 209 | impl UCIOption for UCISpin { 210 | fn option_type(&self) -> &'static str { 211 | "spin" 212 | } 213 | 214 | fn option_name(&self) -> &'static str { 215 | self.option_name 216 | } 217 | 218 | fn partial_display(&self) -> Option { 219 | Some( 220 | String::from("default ") 221 | + &self.default.to_string() 222 | + " min " 223 | + &self.min.to_string() 224 | + " max " 225 | + &self.max.to_string(), 226 | ) 227 | } 228 | 229 | fn mutate(&self, val: &str) -> Option { 230 | if let Ok(integer) = val.parse::() { 231 | if integer >= self.min && integer <= self.max { 232 | return (self.mutator)(integer); 233 | } 234 | } 235 | None 236 | } 237 | } 238 | 239 | impl UCIOption for UCICombo { 240 | fn option_type(&self) -> &'static str { 241 | "combo" 242 | } 243 | 244 | fn option_name(&self) -> &'static str { 245 | self.option_name 246 | } 247 | 248 | fn partial_display(&self) -> Option { 249 | let mut disp = String::from("default ") + self.default; 250 | self.values.iter().for_each(|s| { 251 | disp += " var "; 252 | disp += *s; 253 | }); 254 | Some(disp) 255 | } 256 | 257 | fn mutate(&self, val: &str) -> Option { 258 | if self.values.contains(&val) { 259 | let f = self.mutator; 260 | let ret = f(val); 261 | return ret; 262 | } 263 | return None; 264 | } 265 | } 266 | 267 | impl UCIOption for UCIText { 268 | fn option_type(&self) -> &'static str { 269 | "text" 270 | } 271 | 272 | fn option_name(&self) -> &'static str { 273 | self.option_name 274 | } 275 | 276 | fn partial_display(&self) -> Option { 277 | Some(String::from("default ") + self.default) 278 | } 279 | 280 | fn mutate(&self, val: &str) -> Option { 281 | (self.mutator)(val) 282 | } 283 | } 284 | 285 | #[cfg(test)] 286 | mod tests { 287 | use super::*; 288 | 289 | // #[test] 290 | fn test_print() { 291 | let all = OptionsMap::new(); 292 | all.display_all(); 293 | } 294 | } 295 | --------------------------------------------------------------------------------