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"
|
edition = "2021"
|
||||||
license = "GPL-3.0-or-later"
|
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
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[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
|
-1, -1, 2, -1, 1, -1, 2, 1, // 4
|
||||||
2, 1, 1, 0, 0, 0, 0, 0, // 3
|
2, 1, 1, 0, 0, 0, 0, 0, // 3
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, // 2
|
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
|
// a b c d e f g h
|
||||||
], 500),
|
], 500),
|
||||||
|
|
||||||
@ -217,7 +217,7 @@ pub const PST_MIDGAME: Pst = Pst([
|
|||||||
-5, 1, 0, 0, 0, 0, 0, -5, // 6
|
-5, 1, 0, 0, 0, 0, 0, -5, // 6
|
||||||
-5, 2, 0, 10, 10, 0, 0, -5, // 5
|
-5, 2, 0, 10, 10, 0, 0, -5, // 5
|
||||||
-5, 0, 1, 10, 10, 0, 0, -5, // 4
|
-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, 1, 0, 0, 0, 0, 0, -5, // 2
|
||||||
-5, -5, -5, -5, -5, -5, -5, -5, // 1
|
-5, -5, -5, -5, -5, -5, -5, -5, // 1
|
||||||
// a b c d e f g h
|
// 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, // 5
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, // 4
|
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, // 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
|
0, 0, 10, 0, 0, 0, 20, 0, // 1
|
||||||
// a b c d e f g h
|
// a b c d e f g h
|
||||||
], 20_000),
|
], 20_000),
|
||||||
|
|
||||||
// queen
|
// queen
|
||||||
make_pst([
|
make_pst([
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, // 8
|
-20, -20, -20, -20, -20, -20, -20, -20, // 8
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, // 7
|
-20, -20, -20, -20, -20, -20, -20, -20, // 7
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, // 6
|
-20, -20, -20, -20, -20, -20, -20, -20, // 6
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, // 5
|
-20, -20, -20, -20, -20, -20, -20, -20, // 5
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, // 4
|
-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, // 3
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, // 2
|
0, 0, 0, 0, 0, 0, 0, 0, // 2
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, // 1
|
0, 0, 0, 0, 0, 0, 0, 0, // 1
|
||||||
@ -251,8 +251,8 @@ pub const PST_MIDGAME: Pst = Pst([
|
|||||||
|
|
||||||
// pawn
|
// pawn
|
||||||
make_pst([
|
make_pst([
|
||||||
10, 10, 10, 10, 10, 10, 10, 10, // 8
|
0, 0, 0, 0, 0, 0, 0, 0, // 8
|
||||||
9, 9, 9, 9, 9, 9, 9, 9, // 7
|
19, 19, 19, 19, 19, 19, 19, 19, // 7
|
||||||
8, 8, 8, 8, 8, 8, 8, 8, // 6
|
8, 8, 8, 8, 8, 8, 8, 8, // 6
|
||||||
7, 7, 7, 8, 8, 7, 7, 7, // 5
|
7, 7, 7, 8, 8, 7, 7, 7, // 5
|
||||||
6, 6, 6, 6, 6, 6, 6, 6, // 4
|
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::Index;
|
||||||
use std::ops::IndexMut;
|
use std::ops::IndexMut;
|
||||||
|
|
||||||
const PIECE_KEYS: [[[u64; N_SQUARES]; N_PIECES]; N_COLORS] =
|
const PIECE_KEYS: [[[u64; N_SQUARES]; N_PIECES]; N_COLORS] = [
|
||||||
[Pcg64Random::new(11).random_arr_2d_64(), Pcg64Random::new(22).random_arr_2d_64()];
|
Pcg64Random::new(11).random_arr_2d_64(),
|
||||||
|
Pcg64Random::new(22).random_arr_2d_64(),
|
||||||
|
];
|
||||||
|
|
||||||
// 4 bits in castle perms -> 16 keys
|
// 4 bits in castle perms -> 16 keys
|
||||||
const CASTLE_KEYS: [u64; 16] = Pcg64Random::new(33).random_arr_64();
|
const CASTLE_KEYS: [u64; 16] = Pcg64Random::new(33).random_arr_64();
|
||||||
@ -100,7 +102,10 @@ pub struct ZobristTable<T> {
|
|||||||
impl<T: Copy> ZobristTable<T> {
|
impl<T: Copy> ZobristTable<T> {
|
||||||
/// Create a table with 2^n entries.
|
/// Create a table with 2^n entries.
|
||||||
pub fn new(size: usize) -> Self {
|
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 {
|
ZobristTable {
|
||||||
data: vec![(Zobrist { hash: 0 }, None); 1 << size],
|
data: vec![(Zobrist { hash: 0 }, None); 1 << size],
|
||||||
size,
|
size,
|
||||||
@ -109,16 +114,15 @@ impl<T: Copy> ZobristTable<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<T> IndexMut<Zobrist> for 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 {
|
fn index_mut(&mut self, zobrist: Zobrist) -> &mut Self::Output {
|
||||||
let idx = zobrist.truncate_hash(self.size);
|
let idx = zobrist.truncate_hash(self.size);
|
||||||
if self.data[idx].0 == zobrist {
|
self.data[idx].0 = zobrist;
|
||||||
&mut self.data[idx].1
|
self.data[idx].1 = None;
|
||||||
} else {
|
&mut self.data[idx].1
|
||||||
// miss, overwrite
|
|
||||||
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.half_moves = pos_orig.half_moves;
|
||||||
pos.full_moves = pos_orig.full_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);
|
assert_eq!(pos.zobrist, pos_orig.zobrist);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -222,7 +229,9 @@ mod tests {
|
|||||||
let mut table = ZobristTable::<usize>::new(4);
|
let mut table = ZobristTable::<usize>::new(4);
|
||||||
|
|
||||||
macro_rules! z {
|
macro_rules! z {
|
||||||
($i: expr) => { Zobrist { hash: $i } }
|
($i: expr) => {
|
||||||
|
Zobrist { hash: $i }
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let big_number = 1 << 62;
|
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 random;
|
||||||
pub mod search;
|
pub mod search;
|
||||||
|
|
||||||
|
pub mod prelude;
|
||||||
|
|
||||||
use crate::fen::{FromFen, ToFen, START_POSITION};
|
use crate::fen::{FromFen, ToFen, START_POSITION};
|
||||||
use crate::hash::Zobrist;
|
use crate::hash::Zobrist;
|
||||||
use eval::eval_score::EvalScores;
|
use eval::eval_score::EvalScores;
|
||||||
|
|
||||||
const BOARD_WIDTH: usize = 8;
|
pub const BOARD_WIDTH: usize = 8;
|
||||||
const BOARD_HEIGHT: usize = 8;
|
pub const BOARD_HEIGHT: usize = 8;
|
||||||
const N_SQUARES: usize = BOARD_WIDTH * BOARD_HEIGHT;
|
pub const N_SQUARES: usize = BOARD_WIDTH * BOARD_HEIGHT;
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
|
||||||
pub enum Color {
|
pub enum Color {
|
||||||
@ -38,7 +40,7 @@ pub enum Color {
|
|||||||
White = 0,
|
White = 0,
|
||||||
Black = 1,
|
Black = 1,
|
||||||
}
|
}
|
||||||
const N_COLORS: usize = 2;
|
pub const N_COLORS: usize = 2;
|
||||||
|
|
||||||
impl Color {
|
impl Color {
|
||||||
/// Return opposite color (does not assign).
|
/// Return opposite color (does not assign).
|
||||||
@ -74,7 +76,7 @@ enum Piece {
|
|||||||
Queen,
|
Queen,
|
||||||
Pawn,
|
Pawn,
|
||||||
}
|
}
|
||||||
const N_PIECES: usize = 6;
|
pub const N_PIECES: usize = 6;
|
||||||
|
|
||||||
pub struct PieceErr;
|
pub struct PieceErr;
|
||||||
|
|
||||||
|
@ -12,11 +12,7 @@ Copyright © 2024 dogeystamp <dogeystamp@disroot.org>
|
|||||||
|
|
||||||
//! Main UCI engine binary.
|
//! Main UCI engine binary.
|
||||||
|
|
||||||
use chess_inator::eval::eval_metrics;
|
use chess_inator::prelude::*;
|
||||||
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 std::cmp::min;
|
use std::cmp::min;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::sync::mpsc::channel;
|
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 let Some((ans, cache_depth)) = cache[pos.zobrist] {
|
||||||
if depth == cache_depth {
|
if depth == cache_depth {
|
||||||
return ans;
|
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 {
|
fn default() -> Self {
|
||||||
SearchConfig {
|
SearchConfig {
|
||||||
alpha_beta_on: true,
|
alpha_beta_on: true,
|
||||||
|
// try to make this even to be more conservative and avoid horizon problem
|
||||||
depth: 10,
|
depth: 10,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -175,9 +176,15 @@ fn minmax(
|
|||||||
.map(|mv| (move_priority(board, &mv), mv))
|
.map(|mv| (move_priority(board, &mv), mv))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// remember the prior best move
|
// get transposition table entry
|
||||||
if let Some(cache) = cache {
|
if let Some(cache) = cache {
|
||||||
if let Some(entry) = &cache[board.zobrist] {
|
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));
|
mvs.push((EVAL_BEST, entry.best_move));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -224,7 +231,11 @@ fn minmax(
|
|||||||
if let Some(best_move) = best_move {
|
if let Some(best_move) = best_move {
|
||||||
best_continuation.push(best_move);
|
best_continuation.push(best_move);
|
||||||
if let Some(cache) = cache {
|
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)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct TranspositionEntry {
|
pub struct TranspositionEntry {
|
||||||
// best move found last time
|
/// best move found last time
|
||||||
best_move: Move,
|
best_move: Move,
|
||||||
|
/// last time's eval
|
||||||
|
eval: SearchEval,
|
||||||
|
/// depth of this entry
|
||||||
|
depth: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type TranspositionTable = ZobristTable<TranspositionEntry>;
|
pub type TranspositionTable = ZobristTable<TranspositionEntry>;
|
||||||
@ -254,14 +269,24 @@ fn iter_deep(
|
|||||||
interface: Option<InterfaceRx>,
|
interface: Option<InterfaceRx>,
|
||||||
cache: &mut TranspositionTableOpt,
|
cache: &mut TranspositionTableOpt,
|
||||||
) -> (Vec<Move>, SearchEval) {
|
) -> (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);
|
let (line, eval) = minmax(board, config, depth, None, None, cache);
|
||||||
|
|
||||||
if let Some(ref rx) = interface {
|
if let Some(ref rx) = interface {
|
||||||
// don't interrupt a depth 1 search so that there's at least a move to be played
|
// don't interrupt a depth 1 search so that there's at least a move to be played
|
||||||
if depth != 1 {
|
if depth != 1 {
|
||||||
match rx.try_recv() {
|
match rx.try_recv() {
|
||||||
Ok(msg) => match msg {
|
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 {
|
Err(e) => match e {
|
||||||
mpsc::TryRecvError::Empty => {}
|
mpsc::TryRecvError::Empty => {}
|
||||||
@ -272,8 +297,9 @@ fn iter_deep(
|
|||||||
} else if depth == config.depth - 1 {
|
} else if depth == config.depth - 1 {
|
||||||
return (line, eval);
|
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.
|
/// Find the best line (in reverse order) and its evaluation.
|
||||||
|
Loading…
Reference in New Issue
Block a user