lazy move generation
This commit is contained in:
parent
a93bfe258c
commit
a127815cad
11 changed files with 132 additions and 75 deletions
|
|
@ -1,10 +1,8 @@
|
|||
use chess_engine::board::Board;
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
use chess_engine::eval::evaluate_board;
|
||||
use chess_engine::zobrist::init_zobrist;
|
||||
|
||||
fn run_eval_benchmark(c: &mut Criterion) {
|
||||
init_zobrist();
|
||||
let board = Board::from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");
|
||||
c.bench_function("standard_board_evaluation", |b| {
|
||||
b.iter(|| {
|
||||
|
|
|
|||
|
|
@ -1,19 +1,18 @@
|
|||
use chess_engine::board::Board;
|
||||
use chess_engine::movegen::generate_pseudo_legal_moves;
|
||||
use chess_engine::movegen::picker::MoveGenerator;
|
||||
use chess_engine::movegen::legal_check::is_other_king_attacked;
|
||||
use chess_engine::r#move::MoveList;
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
use chess_engine::zobrist::init_zobrist;
|
||||
|
||||
fn count_legal_moves_recursive(board: &mut Board, depth: u8) -> u64 {
|
||||
if depth == 0 {
|
||||
return 1_u64;
|
||||
}
|
||||
let mut list = MoveList::new();
|
||||
generate_pseudo_legal_moves(&board, &mut list);
|
||||
|
||||
let mut generator = MoveGenerator::new();
|
||||
let mut leaf_nodes = 0_u64;
|
||||
for mv in list.iter() {
|
||||
let undo_info = board.make_move(*mv);
|
||||
|
||||
while let Some(mv) = generator.next(board) {
|
||||
let undo_info = board.make_move(mv);
|
||||
if !is_other_king_attacked(board) {
|
||||
leaf_nodes += count_legal_moves_recursive(board, depth - 1);
|
||||
}
|
||||
|
|
@ -22,9 +21,8 @@ fn count_legal_moves_recursive(board: &mut Board, depth: u8) -> u64 {
|
|||
leaf_nodes
|
||||
}
|
||||
|
||||
|
||||
fn run_perft_benchmark(c: &mut Criterion) {
|
||||
init_zobrist();
|
||||
// init_zobrist() is no longer needed
|
||||
let mut board = Board::from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");
|
||||
|
||||
c.bench_function("standard_perft5", |b| {
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -2,7 +2,6 @@ use std::fs::File;
|
|||
use std::io::{self, BufRead};
|
||||
use chess_engine::engine::Engine;
|
||||
use std::time::{Instant, Duration};
|
||||
use chess_engine::zobrist::init_zobrist;
|
||||
|
||||
fn load_csv(path: &str) -> io::Result<Vec<Vec<String>>> {
|
||||
let file = File::open(path)?;
|
||||
|
|
@ -23,7 +22,6 @@ fn load_csv(path: &str) -> io::Result<Vec<Vec<String>>> {
|
|||
}
|
||||
|
||||
fn main() {
|
||||
init_zobrist();
|
||||
let time_limit_ms = 1000_u64;
|
||||
let time_limit = Duration::from_millis(time_limit_ms);
|
||||
|
||||
|
|
@ -40,7 +38,7 @@ fn main() {
|
|||
|
||||
let start_time = Instant::now();
|
||||
|
||||
let result = engine.search(time_limit_ms-1);
|
||||
let result = engine.search(time_limit_ms-5);
|
||||
|
||||
let duration = start_time.elapsed();
|
||||
if duration > time_limit {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
use chess_engine::engine::Engine;
|
||||
use chess_engine::uci::uci_mainloop;
|
||||
use chess_engine::zobrist::init_zobrist;
|
||||
|
||||
fn main() {
|
||||
init_zobrist();
|
||||
let mut engine = Engine::new("Yakari".to_string(), "EiSiMo".to_string());
|
||||
uci_mainloop(&mut engine);
|
||||
}
|
||||
|
|
|
|||
10
src/move.rs
10
src/move.rs
|
|
@ -87,6 +87,14 @@ impl MoveList {
|
|||
self.count += 1;
|
||||
}
|
||||
|
||||
pub fn pull(&mut self) -> Option<Move> {
|
||||
if self.count > 0 {
|
||||
self.count -= 1;
|
||||
return Some(self.moves[self.count]);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn swap(&mut self, a: usize, b: usize) {
|
||||
self.moves[..self.count].swap(a, b);
|
||||
}
|
||||
|
|
@ -106,6 +114,8 @@ impl MoveList {
|
|||
pub fn contains(&self, mv: &Move) -> bool {
|
||||
self.moves.contains(mv)
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) { self.count = 0 }
|
||||
}
|
||||
|
||||
impl Index<usize> for MoveList {
|
||||
|
|
|
|||
|
|
@ -1,20 +1,6 @@
|
|||
use crate::board::Board;
|
||||
use crate::movegen::non_sliders::{generate_king_moves, generate_knight_moves};
|
||||
use crate::movegen::pawns::generate_pawn_moves;
|
||||
use crate::movegen::sliders::{generate_bishop_moves, generate_queen_moves, generate_rook_moves};
|
||||
use crate::r#move::MoveList;
|
||||
|
||||
pub mod non_sliders;
|
||||
pub mod sliders;
|
||||
pub mod pawns;
|
||||
pub mod tables;
|
||||
pub mod legal_check;
|
||||
|
||||
pub fn generate_pseudo_legal_moves(board: &Board, list: &mut MoveList) {
|
||||
generate_pawn_moves(board, list);
|
||||
generate_knight_moves(board, list);
|
||||
generate_bishop_moves(board, list);
|
||||
generate_rook_moves(board, list);
|
||||
generate_queen_moves(board, list);
|
||||
generate_king_moves(board, list);
|
||||
}
|
||||
pub mod picker;
|
||||
70
src/movegen/picker.rs
Normal file
70
src/movegen/picker.rs
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
use crate::board::Board;
|
||||
use crate::movegen::non_sliders::{generate_king_moves, generate_knight_moves};
|
||||
use crate::movegen::pawns::generate_pawn_moves;
|
||||
use crate::movegen::sliders::*;
|
||||
use crate::r#move::{Move, MoveList};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[repr(u8)]
|
||||
enum GenStage {
|
||||
Pawns = 1,
|
||||
Knights = 2,
|
||||
Bishops = 3,
|
||||
Rooks = 4,
|
||||
Queens = 5,
|
||||
King = 6,
|
||||
Done = 7,
|
||||
}
|
||||
|
||||
impl GenStage {
|
||||
pub fn next(&self) -> Option<Self> {
|
||||
match self {
|
||||
Self::Pawns => Some(Self::Knights),
|
||||
Self::Knights => Some(Self::Bishops),
|
||||
Self::Bishops => Some(Self::Rooks),
|
||||
Self::Rooks => Some(Self::Queens),
|
||||
Self::Queens => Some(Self::King),
|
||||
Self::King => Some(Self::Done),
|
||||
Self::Done => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MoveGenerator {
|
||||
buffer: MoveList,
|
||||
stage: GenStage,
|
||||
}
|
||||
|
||||
impl MoveGenerator {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
buffer: MoveList::new(),
|
||||
stage: GenStage::Pawns,
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_next_batch(&mut self, board: &Board) {
|
||||
self.buffer.clear();
|
||||
|
||||
match self.stage {
|
||||
GenStage::Pawns => { generate_pawn_moves(board, &mut self.buffer) }
|
||||
GenStage::Knights => { generate_knight_moves(board, &mut self.buffer) }
|
||||
GenStage::Bishops => { generate_bishop_moves(board, &mut self.buffer) }
|
||||
GenStage::Rooks => { generate_rook_moves(board, &mut self.buffer) }
|
||||
GenStage::Queens => { generate_queen_moves(board, &mut self.buffer) }
|
||||
GenStage::King => { generate_king_moves(board, &mut self.buffer) }
|
||||
GenStage::Done => {}
|
||||
}
|
||||
if let Some(next_stage) = self.stage.next() {
|
||||
self.stage = next_stage;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next(&mut self, board: &Board) -> Option<Move> {
|
||||
loop {
|
||||
if let Some(mv) = self.buffer.pull() { return Some(mv) }
|
||||
if self.stage == GenStage::Done { return None }
|
||||
self.generate_next_batch(board);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,11 @@
|
|||
use crate::board::{Board, Color};
|
||||
use crate::eval::evaluate_board;
|
||||
use crate::movegen::generate_pseudo_legal_moves;
|
||||
use crate::movegen::legal_check::*;
|
||||
use crate::r#move::{Move, MoveList};
|
||||
use crate::tt::{TranspositionTable, NodeType, TTEntry}; // Import TT types
|
||||
use crate::r#move::Move;
|
||||
use crate::movegen::picker::MoveGenerator;
|
||||
use crate::tt::{TranspositionTable, NodeType};
|
||||
use std::time::{Instant, Duration};
|
||||
|
||||
// A score high enough to be > any material eval, but low enough to not overflow when adding ply
|
||||
const MATE_SCORE: i32 = 1_000_000;
|
||||
|
||||
fn evaluate_board_relative(board: &Board) -> i32 {
|
||||
|
|
@ -17,9 +16,6 @@ fn evaluate_board_relative(board: &Board) -> i32 {
|
|||
}
|
||||
}
|
||||
|
||||
// Helper to adjust mate scores for the TT.
|
||||
// TT stores "pure" scores, independent of ply.
|
||||
// Search uses "mated in X" relative to current ply.
|
||||
fn score_to_tt(score: i32, ply: u8) -> i32 {
|
||||
if score > MATE_SCORE - 1000 {
|
||||
score + (ply as i32)
|
||||
|
|
@ -49,7 +45,7 @@ pub fn alpha_beta(
|
|||
start_time: Instant,
|
||||
time_limit: Duration,
|
||||
nodes: &mut u64,
|
||||
tt: &mut TranspositionTable, // Added TT parameter
|
||||
tt: &mut TranspositionTable,
|
||||
) -> (Option<Move>, i32) {
|
||||
if (*nodes).is_multiple_of(4096)
|
||||
&& start_time.elapsed() > time_limit {
|
||||
|
|
@ -89,25 +85,35 @@ pub fn alpha_beta(
|
|||
return (None, evaluate_board_relative(board));
|
||||
}
|
||||
|
||||
let mut list = MoveList::new();
|
||||
generate_pseudo_legal_moves(board, &mut list);
|
||||
|
||||
if let Some(tm) = tt_move {
|
||||
for i in 0..list.len() {
|
||||
if list[i] == tm {
|
||||
list.swap(0, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut best_move: Option<Move> = None;
|
||||
let mut best_score: i32 = -i32::MAX;
|
||||
let mut legal_moves_found = false;
|
||||
let alpha_orig = alpha;
|
||||
|
||||
for i in 0..list.len() {
|
||||
let mv = list[i];
|
||||
let mut picker = MoveGenerator::new();
|
||||
let mut moves_tried = 0;
|
||||
|
||||
loop {
|
||||
// Move selection logic:
|
||||
// 1. Try TT move first (if exists)
|
||||
// 2. Then use the picker for the rest
|
||||
let mv = if moves_tried == 0 && tt_move.is_some() {
|
||||
tt_move.unwrap()
|
||||
} else {
|
||||
match picker.next(board) {
|
||||
Some(m) => {
|
||||
// Important: skip the TT move if we see it again in the generator
|
||||
if Some(m) == tt_move {
|
||||
continue;
|
||||
}
|
||||
m
|
||||
}
|
||||
None => break, // No more moves
|
||||
}
|
||||
};
|
||||
|
||||
moves_tried += 1;
|
||||
|
||||
let undo_mv = board.make_move(mv);
|
||||
|
||||
let is_illegal = is_other_king_attacked(board);
|
||||
|
|
|
|||
|
|
@ -30,11 +30,8 @@ pub struct ZobristKeys {
|
|||
|
||||
static KEYS: OnceLock<ZobristKeys> = OnceLock::new();
|
||||
|
||||
pub fn init_zobrist() {
|
||||
if KEYS.get().is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Helper function to generate keys, used by the lazy initializer
|
||||
fn generate_keys() -> ZobristKeys {
|
||||
let mut rng = Xorshift::new(1070372); // Fixed seed for reproducibility
|
||||
|
||||
let mut pieces = [[0; 64]; 12];
|
||||
|
|
@ -56,18 +53,17 @@ pub fn init_zobrist() {
|
|||
|
||||
let side_to_move = rng.next();
|
||||
|
||||
let keys = ZobristKeys {
|
||||
ZobristKeys {
|
||||
pieces,
|
||||
castling,
|
||||
en_passant,
|
||||
side_to_move,
|
||||
};
|
||||
|
||||
KEYS.set(keys).expect("Zobrist keys already initialized");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn zobrist_keys() -> &'static ZobristKeys {
|
||||
KEYS.get().expect("Zobrist keys not initialized! Call init_zobrist() in main.")
|
||||
KEYS.get_or_init(generate_keys)
|
||||
}
|
||||
|
||||
pub fn piece_index(pt: PieceType, c: Color) -> usize {
|
||||
|
|
|
|||
|
|
@ -1,20 +1,17 @@
|
|||
use chess_engine::board::Board;
|
||||
use chess_engine::movegen::generate_pseudo_legal_moves;
|
||||
use chess_engine::movegen::legal_check::is_other_king_attacked;
|
||||
use chess_engine::r#move::MoveList;
|
||||
use chess_engine::movegen::picker::MoveGenerator;
|
||||
|
||||
fn count_legal_moves_recursive(board: &mut Board, depth: u8) -> u64 {
|
||||
if depth == 0 {
|
||||
return 1_u64;
|
||||
}
|
||||
|
||||
let mut list = MoveList::new();
|
||||
generate_pseudo_legal_moves(&board, &mut list);
|
||||
|
||||
let mut generator = MoveGenerator::new();
|
||||
let mut leaf_nodes = 0_u64;
|
||||
for mv in list.iter() {
|
||||
let undo_info = board.make_move(*mv);
|
||||
|
||||
while let Some(mv) = generator.next(board) {
|
||||
let undo_info = board.make_move(mv);
|
||||
if !is_other_king_attacked(board) {
|
||||
leaf_nodes += count_legal_moves_recursive(board, depth - 1);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue