Compare commits
8 Commits
5ef46b3e7a
...
b9819a52e6
Author | SHA1 | Date | |
---|---|---|---|
b9819a52e6 | |||
f415a9148c | |||
c494230706 | |||
e8cb125df9 | |||
3dcd27013d | |||
e79f19942e | |||
8895770da6 | |||
af18600d15 |
@ -4,8 +4,6 @@ version = "0.1.0"
|
||||
edition = "2021"
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
default-run = "engine"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
|
@ -1,15 +0,0 @@
|
||||
//! Generates moves from the FEN in the argv.
|
||||
|
||||
use chess_inator::fen::FromFen;
|
||||
use chess_inator::movegen::MoveGen;
|
||||
use chess_inator::Board;
|
||||
use std::env;
|
||||
|
||||
fn main() {
|
||||
let fen = env::args().nth(1).unwrap();
|
||||
let mut board = Board::from_fen(&fen).unwrap();
|
||||
let mvs = board.gen_moves();
|
||||
for mv in mvs.into_iter() {
|
||||
println!("{mv:?}")
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
//! Runs perft at depth for a given FEN.
|
||||
|
||||
use chess_inator::fen::FromFen;
|
||||
use chess_inator::movegen::perft;
|
||||
use chess_inator::Board;
|
||||
use std::env;
|
||||
|
||||
fn main() {
|
||||
let depth = env::args().nth(1).unwrap().parse::<usize>().unwrap();
|
||||
let fen = env::args().nth(2).unwrap();
|
||||
let mut board = Board::from_fen(&fen).unwrap();
|
||||
let res = perft(depth, &mut board);
|
||||
println!("{res}")
|
||||
}
|
20
src/eval.rs
20
src/eval.rs
@ -193,7 +193,7 @@ pub const PST_MIDGAME: Pst = Pst([
|
||||
-1, -1, 2, -1, 1, -1, 2, 1, // 4
|
||||
2, 1, 1, 0, 0, 0, 0, 0, // 3
|
||||
0, 0, 0, 0, 0, 0, 0, 0, // 2
|
||||
0, 0, 0, 10, 10, 5, 0, 0, // 1
|
||||
-5, 0, 0, 10, 10, 5, 0, -5, // 1
|
||||
// a b c d e f g h
|
||||
], 500),
|
||||
|
||||
@ -217,7 +217,7 @@ pub const PST_MIDGAME: Pst = Pst([
|
||||
-5, 1, 0, 0, 0, 0, 0, -5, // 6
|
||||
-5, 2, 0, 10, 10, 0, 0, -5, // 5
|
||||
-5, 0, 1, 10, 10, 0, 0, -5, // 4
|
||||
-5, 2, 10, 0, 0, 10, 0, -5, // 3
|
||||
-5, 2, 20, 0, 0, 20, 0, -5, // 3
|
||||
-5, 1, 0, 0, 0, 0, 0, -5, // 2
|
||||
-5, -5, -5, -5, -5, -5, -5, -5, // 1
|
||||
// a b c d e f g h
|
||||
@ -231,18 +231,18 @@ pub const PST_MIDGAME: Pst = Pst([
|
||||
0, 0, 0, 0, 0, 0, 0, 0, // 5
|
||||
0, 0, 0, 0, 0, 0, 0, 0, // 4
|
||||
0, 0, 0, 0, 0, 0, 0, 0, // 3
|
||||
0, 0, 0, 0, 0, 0, 0, 0, // 2
|
||||
0, 0, 0, -5, -5, -5, 0, 0, // 2
|
||||
0, 0, 10, 0, 0, 0, 20, 0, // 1
|
||||
// a b c d e f g h
|
||||
], 20_000),
|
||||
|
||||
// queen
|
||||
make_pst([
|
||||
0, 0, 0, 0, 0, 0, 0, 0, // 8
|
||||
0, 0, 0, 0, 0, 0, 0, 0, // 7
|
||||
0, 0, 0, 0, 0, 0, 0, 0, // 6
|
||||
0, 0, 0, 0, 0, 0, 0, 0, // 5
|
||||
0, 0, 0, 0, 0, 0, 0, 0, // 4
|
||||
-20, -20, -20, -20, -20, -20, -20, -20, // 8
|
||||
-20, -20, -20, -20, -20, -20, -20, -20, // 7
|
||||
-20, -20, -20, -20, -20, -20, -20, -20, // 6
|
||||
-20, -20, -20, -20, -20, -20, -20, -20, // 5
|
||||
-20, -20, -20, -20, -20, -20, -20, -20, // 4
|
||||
0, 0, 0, 0, 0, 0, 0, 0, // 3
|
||||
0, 0, 0, 0, 0, 0, 0, 0, // 2
|
||||
0, 0, 0, 0, 0, 0, 0, 0, // 1
|
||||
@ -251,8 +251,8 @@ pub const PST_MIDGAME: Pst = Pst([
|
||||
|
||||
// pawn
|
||||
make_pst([
|
||||
10, 10, 10, 10, 10, 10, 10, 10, // 8
|
||||
9, 9, 9, 9, 9, 9, 9, 9, // 7
|
||||
0, 0, 0, 0, 0, 0, 0, 0, // 8
|
||||
19, 19, 19, 19, 19, 19, 19, 19, // 7
|
||||
8, 8, 8, 8, 8, 8, 8, 8, // 6
|
||||
7, 7, 7, 8, 8, 7, 7, 7, // 5
|
||||
6, 6, 6, 6, 6, 6, 6, 6, // 4
|
||||
|
35
src/hash.rs
35
src/hash.rs
@ -20,8 +20,10 @@ use crate::{
|
||||
use std::ops::Index;
|
||||
use std::ops::IndexMut;
|
||||
|
||||
const PIECE_KEYS: [[[u64; N_SQUARES]; N_PIECES]; N_COLORS] =
|
||||
[Pcg64Random::new(11).random_arr_2d_64(), Pcg64Random::new(22).random_arr_2d_64()];
|
||||
const PIECE_KEYS: [[[u64; N_SQUARES]; N_PIECES]; N_COLORS] = [
|
||||
Pcg64Random::new(11).random_arr_2d_64(),
|
||||
Pcg64Random::new(22).random_arr_2d_64(),
|
||||
];
|
||||
|
||||
// 4 bits in castle perms -> 16 keys
|
||||
const CASTLE_KEYS: [u64; 16] = Pcg64Random::new(33).random_arr_64();
|
||||
@ -100,7 +102,10 @@ pub struct ZobristTable<T> {
|
||||
impl<T: Copy> ZobristTable<T> {
|
||||
/// Create a table with 2^n entries.
|
||||
pub fn new(size: usize) -> Self {
|
||||
assert!(size <= 27, "Attempted to make 2^{size} entry table; aborting to avoid excessive memory usage.");
|
||||
assert!(
|
||||
size <= 27,
|
||||
"Attempted to make 2^{size} entry table; aborting to avoid excessive memory usage."
|
||||
);
|
||||
ZobristTable {
|
||||
data: vec![(Zobrist { hash: 0 }, None); 1 << size],
|
||||
size,
|
||||
@ -109,16 +114,15 @@ impl<T: Copy> ZobristTable<T> {
|
||||
}
|
||||
|
||||
impl<T> IndexMut<Zobrist> for ZobristTable<T> {
|
||||
/// Overwrite a table entry.
|
||||
///
|
||||
/// If you `mut`ably index, it will automatically wipe an existing entry,
|
||||
/// regardless of it was a cache hit or miss.
|
||||
fn index_mut(&mut self, zobrist: Zobrist) -> &mut Self::Output {
|
||||
let idx = zobrist.truncate_hash(self.size);
|
||||
if self.data[idx].0 == zobrist {
|
||||
&mut self.data[idx].1
|
||||
} else {
|
||||
// miss, overwrite
|
||||
self.data[idx].0 = zobrist;
|
||||
self.data[idx].1 = None;
|
||||
&mut self.data[idx].1
|
||||
}
|
||||
self.data[idx].0 = zobrist;
|
||||
self.data[idx].1 = None;
|
||||
&mut self.data[idx].1
|
||||
}
|
||||
}
|
||||
|
||||
@ -212,7 +216,10 @@ mod tests {
|
||||
}
|
||||
pos.half_moves = pos_orig.half_moves;
|
||||
pos.full_moves = pos_orig.full_moves;
|
||||
assert_eq!(pos, pos_orig, "test case is incorrect, position should loop back to the original");
|
||||
assert_eq!(
|
||||
pos, pos_orig,
|
||||
"test case is incorrect, position should loop back to the original"
|
||||
);
|
||||
assert_eq!(pos.zobrist, pos_orig.zobrist);
|
||||
}
|
||||
}
|
||||
@ -222,7 +229,9 @@ mod tests {
|
||||
let mut table = ZobristTable::<usize>::new(4);
|
||||
|
||||
macro_rules! z {
|
||||
($i: expr) => { Zobrist { hash: $i } }
|
||||
($i: expr) => {
|
||||
Zobrist { hash: $i }
|
||||
};
|
||||
}
|
||||
|
||||
let big_number = 1 << 62;
|
||||
|
12
src/lib.rs
12
src/lib.rs
@ -24,13 +24,15 @@ pub mod movegen;
|
||||
pub mod random;
|
||||
pub mod search;
|
||||
|
||||
pub mod prelude;
|
||||
|
||||
use crate::fen::{FromFen, ToFen, START_POSITION};
|
||||
use crate::hash::Zobrist;
|
||||
use eval::eval_score::EvalScores;
|
||||
|
||||
const BOARD_WIDTH: usize = 8;
|
||||
const BOARD_HEIGHT: usize = 8;
|
||||
const N_SQUARES: usize = BOARD_WIDTH * BOARD_HEIGHT;
|
||||
pub const BOARD_WIDTH: usize = 8;
|
||||
pub const BOARD_HEIGHT: usize = 8;
|
||||
pub const N_SQUARES: usize = BOARD_WIDTH * BOARD_HEIGHT;
|
||||
|
||||
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
|
||||
pub enum Color {
|
||||
@ -38,7 +40,7 @@ pub enum Color {
|
||||
White = 0,
|
||||
Black = 1,
|
||||
}
|
||||
const N_COLORS: usize = 2;
|
||||
pub const N_COLORS: usize = 2;
|
||||
|
||||
impl Color {
|
||||
/// Return opposite color (does not assign).
|
||||
@ -74,7 +76,7 @@ enum Piece {
|
||||
Queen,
|
||||
Pawn,
|
||||
}
|
||||
const N_PIECES: usize = 6;
|
||||
pub const N_PIECES: usize = 6;
|
||||
|
||||
pub struct PieceErr;
|
||||
|
||||
|
@ -12,11 +12,7 @@ Copyright © 2024 dogeystamp <dogeystamp@disroot.org>
|
||||
|
||||
//! Main UCI engine binary.
|
||||
|
||||
use chess_inator::eval::eval_metrics;
|
||||
use chess_inator::fen::FromFen;
|
||||
use chess_inator::movegen::{FromUCIAlgebraic, Move, ToUCIAlgebraic};
|
||||
use chess_inator::search::{best_line, InterfaceMsg, SearchEval, TranspositionTable};
|
||||
use chess_inator::{Board, Color};
|
||||
use chess_inator::prelude::*;
|
||||
use std::cmp::min;
|
||||
use std::io;
|
||||
use std::sync::mpsc::channel;
|
@ -770,7 +770,11 @@ impl MoveGenInternal for Board {
|
||||
}
|
||||
}
|
||||
|
||||
fn perft_internal(depth: usize, pos: &mut Board, cache: &mut ZobristTable<(usize, usize)>) -> usize {
|
||||
fn perft_internal(
|
||||
depth: usize,
|
||||
pos: &mut Board,
|
||||
cache: &mut ZobristTable<(usize, usize)>,
|
||||
) -> usize {
|
||||
if let Some((ans, cache_depth)) = cache[pos.zobrist] {
|
||||
if depth == cache_depth {
|
||||
return ans;
|
||||
|
20
src/prelude.rs
Normal file
20
src/prelude.rs
Normal file
@ -0,0 +1,20 @@
|
||||
/*
|
||||
|
||||
This file is part of chess_inator.
|
||||
|
||||
chess_inator is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
|
||||
|
||||
chess_inator is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along with chess_inator. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
Copyright © 2024 dogeystamp <dogeystamp@disroot.org>
|
||||
*/
|
||||
|
||||
//! Prelude that you can import entirely to use the library conveniently.
|
||||
|
||||
pub use crate::eval::{eval_metrics, EvalMetrics};
|
||||
pub use crate::fen::{FromFen, ToFen};
|
||||
pub use crate::movegen::{FromUCIAlgebraic, Move, MoveGen, ToUCIAlgebraic};
|
||||
pub use crate::search::{best_line, best_move, InterfaceMsg, SearchEval, TranspositionTable};
|
||||
pub use crate::{Board, Color, BOARD_HEIGHT, BOARD_WIDTH, N_COLORS, N_PIECES, N_SQUARES};
|
@ -105,6 +105,7 @@ impl Default for SearchConfig {
|
||||
fn default() -> Self {
|
||||
SearchConfig {
|
||||
alpha_beta_on: true,
|
||||
// try to make this even to be more conservative and avoid horizon problem
|
||||
depth: 10,
|
||||
}
|
||||
}
|
||||
@ -175,9 +176,15 @@ fn minmax(
|
||||
.map(|mv| (move_priority(board, &mv), mv))
|
||||
.collect();
|
||||
|
||||
// remember the prior best move
|
||||
// get transposition table entry
|
||||
if let Some(cache) = cache {
|
||||
if let Some(entry) = &cache[board.zobrist] {
|
||||
// the entry has a deeper knowledge than we do, so follow its best move exactly instead of
|
||||
// just prioritizing what it thinks is best
|
||||
if entry.depth >= depth {
|
||||
// we don't save PV line in transposition table, so no information on that
|
||||
return (vec![entry.best_move], entry.eval)
|
||||
}
|
||||
mvs.push((EVAL_BEST, entry.best_move));
|
||||
}
|
||||
}
|
||||
@ -224,7 +231,11 @@ fn minmax(
|
||||
if let Some(best_move) = best_move {
|
||||
best_continuation.push(best_move);
|
||||
if let Some(cache) = cache {
|
||||
cache[board.zobrist] = Some(TranspositionEntry { best_move });
|
||||
cache[board.zobrist] = Some(TranspositionEntry {
|
||||
best_move,
|
||||
eval: abs_best,
|
||||
depth,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -240,8 +251,12 @@ type InterfaceRx = mpsc::Receiver<InterfaceMsg>;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct TranspositionEntry {
|
||||
// best move found last time
|
||||
/// best move found last time
|
||||
best_move: Move,
|
||||
/// last time's eval
|
||||
eval: SearchEval,
|
||||
/// depth of this entry
|
||||
depth: usize,
|
||||
}
|
||||
|
||||
pub type TranspositionTable = ZobristTable<TranspositionEntry>;
|
||||
@ -254,14 +269,24 @@ fn iter_deep(
|
||||
interface: Option<InterfaceRx>,
|
||||
cache: &mut TranspositionTableOpt,
|
||||
) -> (Vec<Move>, SearchEval) {
|
||||
for depth in 1..=config.depth {
|
||||
let (mut prev_line, mut prev_eval) = minmax(board, config, 1, None, None, cache);
|
||||
for depth in 2..=config.depth {
|
||||
let (line, eval) = minmax(board, config, depth, None, None, cache);
|
||||
|
||||
if let Some(ref rx) = interface {
|
||||
// don't interrupt a depth 1 search so that there's at least a move to be played
|
||||
if depth != 1 {
|
||||
match rx.try_recv() {
|
||||
Ok(msg) => match msg {
|
||||
InterfaceMsg::Stop => return (line, eval),
|
||||
InterfaceMsg::Stop => {
|
||||
if depth & 1 == 1 && (EvalInt::from(eval) - EvalInt::from(prev_eval) > 300) {
|
||||
// be skeptical if we move last and we suddenly earn a lot of
|
||||
// centipawns. this may be a sign of horizon problem
|
||||
return (prev_line, prev_eval)
|
||||
} else {
|
||||
return (line, eval)
|
||||
}
|
||||
},
|
||||
},
|
||||
Err(e) => match e {
|
||||
mpsc::TryRecvError::Empty => {}
|
||||
@ -272,8 +297,9 @@ fn iter_deep(
|
||||
} else if depth == config.depth - 1 {
|
||||
return (line, eval);
|
||||
}
|
||||
(prev_line, prev_eval) = (line, eval);
|
||||
}
|
||||
panic!("iterative deepening did not search at all")
|
||||
(prev_line, prev_eval)
|
||||
}
|
||||
|
||||
/// Find the best line (in reverse order) and its evaluation.
|
||||
|
Loading…
Reference in New Issue
Block a user