Add TT lookups in quiescence, move TT inside Game, remove redundant occupancy and time limit functions
This commit is contained in:
@@ -22,7 +22,7 @@ pub struct Board {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Board {
|
impl Board {
|
||||||
pub const fn new() -> Self {
|
pub const fn startpos() -> Self {
|
||||||
Self {
|
Self {
|
||||||
pieces: [
|
pieces: [
|
||||||
0xff00000000ff00,
|
0xff00000000ff00,
|
||||||
@@ -37,7 +37,7 @@ impl Board {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn empty_board() -> Self {
|
pub const fn new_empty() -> Self {
|
||||||
Self {
|
Self {
|
||||||
pieces: [0x0, 0x0, 0x0, 0x0, 0x0, 0x0],
|
pieces: [0x0, 0x0, 0x0, 0x0, 0x0, 0x0],
|
||||||
color: [0x0, 0x0],
|
color: [0x0, 0x0],
|
||||||
@@ -45,20 +45,8 @@ impl Board {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn white_occupancies(&self) -> Bitboard {
|
|
||||||
self.color[Color::White]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn black_occupancies(&self) -> Bitboard {
|
|
||||||
self.color[Color::Black]
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn all_occupancies(&self) -> Bitboard {
|
|
||||||
self.white_occupancies() | self.black_occupancies()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_attacked(&self, square: usize, opponent_color: Color) -> bool {
|
pub fn is_attacked(&self, square: usize, opponent_color: Color) -> bool {
|
||||||
let all_occupancies = self.all_occupancies();
|
let all_occupancies = self.color[Color::White] | self.color[Color::Black];
|
||||||
let own_color = opponent_color.opponent();
|
let own_color = opponent_color.opponent();
|
||||||
let opponent_color_bb = &self.color[opponent_color];
|
let opponent_color_bb = &self.color[opponent_color];
|
||||||
|
|
||||||
@@ -108,7 +96,7 @@ impl Board {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn pseudo_moves(&self, color: Color, piece_type: PieceType) -> Vec<Move> {
|
pub fn pseudo_moves(&self, color: Color, piece_type: PieceType) -> Vec<Move> {
|
||||||
let all_occupancies = self.all_occupancies();
|
let all_occupancies = self.color[Color::White] | self.color[Color::Black];
|
||||||
let pieces = self.pieces[piece_type] & self.color[color];
|
let pieces = self.pieces[piece_type] & self.color[color];
|
||||||
let own_occupancies = self.color[color];
|
let own_occupancies = self.color[color];
|
||||||
let opponent_occupancies = self.color[color.opponent()];
|
let opponent_occupancies = self.color[color.opponent()];
|
||||||
@@ -186,7 +174,7 @@ impl Board {
|
|||||||
|
|
||||||
impl Default for Board {
|
impl Default for Board {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new()
|
Self::new_empty()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,9 +265,12 @@ mod tests {
|
|||||||
fn test_occupancies() -> Result<(), String> {
|
fn test_occupancies() -> Result<(), String> {
|
||||||
let new_game = from_fen(FEN_EXAMPLE[0])?;
|
let new_game = from_fen(FEN_EXAMPLE[0])?;
|
||||||
|
|
||||||
assert_eq!(new_game.board.white_occupancies(), 0x40000002000000);
|
assert_eq!(new_game.board.color[Color::White], 0x40000002000000);
|
||||||
assert_eq!(new_game.board.black_occupancies(), 0x900204401002);
|
assert_eq!(new_game.board.color[Color::Black], 0x900204401002);
|
||||||
assert_eq!(new_game.board.all_occupancies(), 0x40900206401002);
|
assert_eq!(
|
||||||
|
new_game.board.color[Color::White] | new_game.board.color[Color::Black],
|
||||||
|
0x40900206401002
|
||||||
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
use crate::board::board::{Board, Color, PieceType};
|
use crate::board::board::{Board, Color, PieceType};
|
||||||
use crate::board::game::Game;
|
use crate::board::game::Game;
|
||||||
use crate::board::state::{Castle, State};
|
use crate::board::state::{Castle, State};
|
||||||
|
use crate::search::transposition_table::TranspositionTable;
|
||||||
|
use crate::search::MAX_TT_SIZE;
|
||||||
use String as FenError;
|
use String as FenError;
|
||||||
|
|
||||||
pub fn from_fen(fen: &str) -> Result<Game, FenError> {
|
pub fn from_fen(fen: &str) -> Result<Game, FenError> {
|
||||||
@@ -34,11 +36,12 @@ pub fn from_fen(fen: &str) -> Result<Game, FenError> {
|
|||||||
history: History::new(),
|
history: History::new(),
|
||||||
mailbox,
|
mailbox,
|
||||||
hash,
|
hash,
|
||||||
|
tt: TranspositionTable::new(MAX_TT_SIZE),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn piece_placement(pieces: &str) -> Result<Board, FenError> {
|
pub fn piece_placement(pieces: &str) -> Result<Board, FenError> {
|
||||||
let mut board = Board::empty_board();
|
let mut board = Board::new_empty();
|
||||||
let (mut file, mut rank): (u8, u8) = (0, 7);
|
let (mut file, mut rank): (u8, u8) = (0, 7);
|
||||||
|
|
||||||
for c in pieces.chars() {
|
for c in pieces.chars() {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
board::fen::from_fen,
|
board::fen::from_fen,
|
||||||
movegen::r#move::{Move, MoveType},
|
movegen::r#move::{Move, MoveType},
|
||||||
|
search::{transposition_table::TranspositionTable, MAX_TT_SIZE},
|
||||||
};
|
};
|
||||||
use String as FenError;
|
use String as FenError;
|
||||||
|
|
||||||
@@ -27,15 +28,17 @@ pub struct Game {
|
|||||||
pub history: History,
|
pub history: History,
|
||||||
pub mailbox: Mailbox,
|
pub mailbox: Mailbox,
|
||||||
pub hash: ZobristHash,
|
pub hash: ZobristHash,
|
||||||
|
pub tt: TranspositionTable,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Game {
|
impl Game {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
board: Board::new(),
|
board: Board::startpos(),
|
||||||
history: History::new(),
|
history: History::new(),
|
||||||
mailbox: Mailbox::from_board(&Board::new()),
|
mailbox: Mailbox::from_board(&Board::startpos()),
|
||||||
hash: zobrist_keys().calculate_hash(&Board::new()),
|
hash: zobrist_keys().calculate_hash(&Board::startpos()),
|
||||||
|
tt: TranspositionTable::new(MAX_TT_SIZE),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,10 @@ use std::{
|
|||||||
use anyhow::{anyhow, bail};
|
use anyhow::{anyhow, bail};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
board::{board::Color, game::Game},
|
board::{
|
||||||
|
board::{Board, Color},
|
||||||
|
game::Game,
|
||||||
|
},
|
||||||
movegen::r#move::Move,
|
movegen::r#move::Move,
|
||||||
search::{
|
search::{
|
||||||
iterative_deepening, transposition_table::TranspositionTable, MAX_DEPTH, MAX_TT_SIZE,
|
iterative_deepening, transposition_table::TranspositionTable, MAX_DEPTH, MAX_TT_SIZE,
|
||||||
@@ -137,11 +140,7 @@ pub fn uci_position(position: &mut SplitWhitespace) -> anyhow::Result<Game> {
|
|||||||
Ok(game)
|
Ok(game)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn uci_go(
|
pub fn uci_go(go_iter: &mut SplitWhitespace, game: &mut Game) -> anyhow::Result<Move> {
|
||||||
go_iter: &mut SplitWhitespace,
|
|
||||||
game: &mut Game,
|
|
||||||
tt: &mut TranspositionTable,
|
|
||||||
) -> anyhow::Result<Move> {
|
|
||||||
let mut params = UciParameters::new();
|
let mut params = UciParameters::new();
|
||||||
while let Some(subcommand) = go_iter.next() {
|
while let Some(subcommand) = go_iter.next() {
|
||||||
match subcommand {
|
match subcommand {
|
||||||
@@ -159,14 +158,12 @@ pub fn uci_go(
|
|||||||
Color::Black => params.btime.unwrap_or(REMAINING_TIME_DEFAULT),
|
Color::Black => params.btime.unwrap_or(REMAINING_TIME_DEFAULT),
|
||||||
};
|
};
|
||||||
|
|
||||||
iterative_deepening::iterative_deepening(game, MAX_DEPTH, remaining_time, tt)?.ok_or_else(
|
iterative_deepening::iterative_deepening(game, MAX_DEPTH, remaining_time)?.ok_or_else(|| {
|
||||||
|| {
|
anyhow!(
|
||||||
anyhow!(
|
"No stored best move found. Time: {}",
|
||||||
"No stored best move found. Time: {}",
|
remaining_time - time.elapsed().as_millis()
|
||||||
remaining_time - time.elapsed().as_millis()
|
)
|
||||||
)
|
})
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_next<T: std::str::FromStr>(go_iter: &mut SplitWhitespace, val: &str) -> anyhow::Result<T> {
|
fn parse_next<T: std::str::FromStr>(go_iter: &mut SplitWhitespace, val: &str) -> anyhow::Result<T> {
|
||||||
@@ -178,7 +175,6 @@ fn parse_next<T: std::str::FromStr>(go_iter: &mut SplitWhitespace, val: &str) ->
|
|||||||
|
|
||||||
pub fn uci_loop<R: BufRead, W: Write>(input: R, mut output: W) -> anyhow::Result<()> {
|
pub fn uci_loop<R: BufRead, W: Write>(input: R, mut output: W) -> anyhow::Result<()> {
|
||||||
let mut params = UciParameters::new();
|
let mut params = UciParameters::new();
|
||||||
let mut tt = TranspositionTable::new(MAX_TT_SIZE);
|
|
||||||
|
|
||||||
for line in input.lines() {
|
for line in input.lines() {
|
||||||
let line_str = line.unwrap_or_else(|_| "quit".to_string());
|
let line_str = line.unwrap_or_else(|_| "quit".to_string());
|
||||||
@@ -188,7 +184,14 @@ pub fn uci_loop<R: BufRead, W: Write>(input: R, mut output: W) -> anyhow::Result
|
|||||||
Command::Uci => Response::UciOk,
|
Command::Uci => Response::UciOk,
|
||||||
Command::IsReady => Response::ReadyOk,
|
Command::IsReady => Response::ReadyOk,
|
||||||
Command::UciNewGame => {
|
Command::UciNewGame => {
|
||||||
tt = TranspositionTable::new(MAX_TT_SIZE);
|
params.game.as_mut().map_or_else(
|
||||||
|
|| Response::Info("Failed to unwrap from UciParameter".to_string()),
|
||||||
|
|game| {
|
||||||
|
game.tt = TranspositionTable::new(MAX_TT_SIZE);
|
||||||
|
game.board = Board::startpos();
|
||||||
|
Response::Info("Initialized new game".to_string())
|
||||||
|
},
|
||||||
|
);
|
||||||
Response::Info("Clear cache".to_string())
|
Response::Info("Clear cache".to_string())
|
||||||
}
|
}
|
||||||
Command::Position => {
|
Command::Position => {
|
||||||
@@ -197,7 +200,7 @@ pub fn uci_loop<R: BufRead, W: Write>(input: R, mut output: W) -> anyhow::Result
|
|||||||
}
|
}
|
||||||
Command::Go => params.game.as_mut().map_or_else(
|
Command::Go => params.game.as_mut().map_or_else(
|
||||||
|| Response::Info("Failed to unwrap from UciParameter".to_string()),
|
|| Response::Info("Failed to unwrap from UciParameter".to_string()),
|
||||||
|game| match uci_go(&mut parts, game, &mut tt) {
|
|game| match uci_go(&mut parts, game) {
|
||||||
Ok(best_move) => Response::BestMove(best_move.parse_into_str()),
|
Ok(best_move) => Response::BestMove(best_move.parse_into_str()),
|
||||||
Err(e) => Response::Info(e.to_string()),
|
Err(e) => Response::Info(e.to_string()),
|
||||||
},
|
},
|
||||||
@@ -221,7 +224,6 @@ mod tests {
|
|||||||
board::{fen::from_fen, square::Square},
|
board::{fen::from_fen, square::Square},
|
||||||
interface::uci::{parse_command, Command},
|
interface::uci::{parse_command, Command},
|
||||||
movegen::{attack_generator::init_attacks, r#move::Move},
|
movegen::{attack_generator::init_attacks, r#move::Move},
|
||||||
search::{transposition_table::TranspositionTable, MAX_TT_SIZE},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::uci_go;
|
use super::uci_go;
|
||||||
@@ -257,11 +259,10 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_uci_go() -> anyhow::Result<()> {
|
fn test_uci_go() -> anyhow::Result<()> {
|
||||||
init_attacks();
|
init_attacks();
|
||||||
let mut tt = TranspositionTable::new(MAX_TT_SIZE);
|
|
||||||
let mut game = from_fen(FEN_MATE_IN_1).unwrap();
|
let mut game = from_fen(FEN_MATE_IN_1).unwrap();
|
||||||
let command_go = "go depth 2";
|
let command_go = "go depth 2";
|
||||||
let mut parts = command_go.split_whitespace();
|
let mut parts = command_go.split_whitespace();
|
||||||
let response = uci_go(&mut parts, &mut game, &mut tt)?;
|
let response = uci_go(&mut parts, &mut game)?;
|
||||||
|
|
||||||
assert_eq!(Move::new(Square::E3, Square::F2), response);
|
assert_eq!(Move::new(Square::E3, Square::F2), response);
|
||||||
|
|
||||||
|
|||||||
@@ -6,21 +6,19 @@ use crate::{
|
|||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
negamax,
|
negamax,
|
||||||
time::{time_limit_reached, TimeInfo},
|
time::{soft_limit, TimeInfo},
|
||||||
transposition_table::TranspositionTable,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn iterative_deepening(
|
pub fn iterative_deepening(
|
||||||
game: &mut Game,
|
game: &mut Game,
|
||||||
max_depth: u8,
|
max_depth: u8,
|
||||||
remaining_time: u128,
|
remaining_time: u128,
|
||||||
tt: &mut TranspositionTable,
|
|
||||||
) -> anyhow::Result<Option<Move>> {
|
) -> anyhow::Result<Option<Move>> {
|
||||||
let (mut best_move, mut best_score) = (None, MIN_SCORE);
|
let (mut best_move, mut best_score) = (None, MIN_SCORE);
|
||||||
let time = std::time::Instant::now();
|
let time = std::time::Instant::now();
|
||||||
|
|
||||||
for depth in 1..=max_depth {
|
for depth in 1..=max_depth {
|
||||||
if time_limit_reached(&time, remaining_time, best_score) {
|
if soft_limit(&time, remaining_time, best_score) {
|
||||||
return Ok(best_move);
|
return Ok(best_move);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,7 +29,6 @@ pub fn iterative_deepening(
|
|||||||
depth,
|
depth,
|
||||||
0,
|
0,
|
||||||
&TimeInfo::new(time, remaining_time),
|
&TimeInfo::new(time, remaining_time),
|
||||||
tt,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Ok(search_result) = search_result {
|
if let Ok(search_result) = search_result {
|
||||||
@@ -44,6 +41,12 @@ pub fn iterative_deepening(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tt.lookup(game.hash).map(|entry| {
|
||||||
|
// if entry.mv.is_some() {
|
||||||
|
// best_move = entry.mv;
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
Ok(best_move)
|
Ok(best_move)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,10 +55,7 @@ mod tests {
|
|||||||
use crate::{
|
use crate::{
|
||||||
board::fen::from_fen,
|
board::fen::from_fen,
|
||||||
movegen::attack_generator::init_attacks,
|
movegen::attack_generator::init_attacks,
|
||||||
search::{
|
search::{iterative_deepening, MAX_DEPTH, REMAINING_TIME_DEFAULT},
|
||||||
iterative_deepening, transposition_table::TranspositionTable, MAX_DEPTH, MAX_TT_SIZE,
|
|
||||||
REMAINING_TIME_DEFAULT,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const FEN: &str = "1r2k2r/2P1pq1p/2npb3/1p3ppP/p3P3/P2B1Q2/1P1PNPP1/R3K2R w KQk g6 0 1";
|
const FEN: &str = "1r2k2r/2P1pq1p/2npb3/1p3ppP/p3P3/P2B1Q2/1P1PNPP1/R3K2R w KQk g6 0 1";
|
||||||
@@ -64,15 +64,9 @@ mod tests {
|
|||||||
fn test_iterative_deepening() -> anyhow::Result<()> {
|
fn test_iterative_deepening() -> anyhow::Result<()> {
|
||||||
init_attacks();
|
init_attacks();
|
||||||
let mut game = from_fen(FEN).unwrap();
|
let mut game = from_fen(FEN).unwrap();
|
||||||
let mut tt = TranspositionTable::new(MAX_TT_SIZE);
|
|
||||||
let time_now = std::time::Instant::now();
|
let time_now = std::time::Instant::now();
|
||||||
|
|
||||||
iterative_deepening::iterative_deepening(
|
iterative_deepening::iterative_deepening(&mut game, MAX_DEPTH, REMAINING_TIME_DEFAULT)?;
|
||||||
&mut game,
|
|
||||||
MAX_DEPTH,
|
|
||||||
REMAINING_TIME_DEFAULT,
|
|
||||||
&mut tt,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
dbg!(time_now.elapsed());
|
dbg!(time_now.elapsed());
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use super::{
|
|||||||
move_ordering,
|
move_ordering,
|
||||||
quiescence::quiescence,
|
quiescence::quiescence,
|
||||||
time::{hard_limit, TimeInfo},
|
time::{hard_limit, TimeInfo},
|
||||||
transposition_table::{Bound, TTEntry, TranspositionTable},
|
transposition_table::TTEntry,
|
||||||
SearchResult,
|
SearchResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -20,7 +20,6 @@ pub fn negamax(
|
|||||||
depth: u8,
|
depth: u8,
|
||||||
plies: u8,
|
plies: u8,
|
||||||
time_info: &TimeInfo,
|
time_info: &TimeInfo,
|
||||||
tt: &mut TranspositionTable,
|
|
||||||
) -> Result<SearchResult> {
|
) -> Result<SearchResult> {
|
||||||
if hard_limit(&time_info.time, time_info.remaining_time_in_ms) {
|
if hard_limit(&time_info.time, time_info.remaining_time_in_ms) {
|
||||||
bail!("Time is up! In Negamax");
|
bail!("Time is up! In Negamax");
|
||||||
@@ -36,10 +35,9 @@ pub fn negamax(
|
|||||||
let mut best_score = MIN_SCORE;
|
let mut best_score = MIN_SCORE;
|
||||||
let mate_score = -MATE_SCORE + plies as i32;
|
let mate_score = -MATE_SCORE + plies as i32;
|
||||||
let mut legal_moves = 0;
|
let mut legal_moves = 0;
|
||||||
let mut bound = Bound::Alpha;
|
|
||||||
let all_moves = game.board.pseudo_moves_all();
|
let all_moves = game.board.pseudo_moves_all();
|
||||||
|
|
||||||
let tt_move = tt.lookup(game.hash).and_then(|entry| entry.mv);
|
let tt_move = game.tt.lookup(game.hash).and_then(|entry| entry.mv);
|
||||||
let moves = move_ordering::sort_moves(all_moves, &game.mailbox, tt_move);
|
let moves = move_ordering::sort_moves(all_moves, &game.mailbox, tt_move);
|
||||||
|
|
||||||
for mv in moves {
|
for mv in moves {
|
||||||
@@ -51,7 +49,7 @@ pub fn negamax(
|
|||||||
}
|
}
|
||||||
|
|
||||||
legal_moves += 1;
|
legal_moves += 1;
|
||||||
let score = -negamax(game, -beta, -alpha, depth - 1, plies + 1, time_info, tt)?.best_score;
|
let score = -negamax(game, -beta, -alpha, depth - 1, plies + 1, time_info)?.best_score;
|
||||||
game.unmake_move();
|
game.unmake_move();
|
||||||
|
|
||||||
if score > best_score {
|
if score > best_score {
|
||||||
@@ -60,14 +58,12 @@ pub fn negamax(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if score >= beta {
|
if score >= beta {
|
||||||
bound = Bound::Beta;
|
|
||||||
best_score = beta;
|
best_score = beta;
|
||||||
best_move = Some(mv);
|
best_move = Some(mv);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if score > alpha {
|
if score > alpha {
|
||||||
bound = Bound::Exact;
|
|
||||||
alpha = score;
|
alpha = score;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -79,7 +75,7 @@ pub fn negamax(
|
|||||||
return Ok(SearchResult::new(None, 0));
|
return Ok(SearchResult::new(None, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
tt.insert(TTEntry::new(game.hash, depth, best_score, best_move, bound));
|
game.tt.insert(TTEntry::new(game.hash, best_move));
|
||||||
Ok(SearchResult::new(best_move, best_score))
|
Ok(SearchResult::new(best_move, best_score))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,8 +87,6 @@ mod tests {
|
|||||||
use crate::movegen::r#move::Move;
|
use crate::movegen::r#move::Move;
|
||||||
use crate::search::negamax::negamax;
|
use crate::search::negamax::negamax;
|
||||||
use crate::search::time::TimeInfo;
|
use crate::search::time::TimeInfo;
|
||||||
use crate::search::transposition_table::TranspositionTable;
|
|
||||||
use crate::search::MAX_TT_SIZE;
|
|
||||||
|
|
||||||
const FEN_MATE_IN_1: [&str; 2] = [
|
const FEN_MATE_IN_1: [&str; 2] = [
|
||||||
"8/8/8/8/8/4q1k1/8/5K2 b - - 0 1",
|
"8/8/8/8/8/4q1k1/8/5K2 b - - 0 1",
|
||||||
@@ -104,10 +98,9 @@ mod tests {
|
|||||||
init_attacks();
|
init_attacks();
|
||||||
let mut game = from_fen(FEN_MATE_IN_1[0]).unwrap();
|
let mut game = from_fen(FEN_MATE_IN_1[0]).unwrap();
|
||||||
|
|
||||||
let mut tt = TranspositionTable::new(MAX_TT_SIZE);
|
|
||||||
let e3f2 = Move::new(Square::E3, Square::F2);
|
let e3f2 = Move::new(Square::E3, Square::F2);
|
||||||
let time_info = TimeInfo::new(std::time::Instant::now(), 1000);
|
let time_info = TimeInfo::new(std::time::Instant::now(), 1000);
|
||||||
let anointed_move = negamax(&mut game, MIN_SCORE, MAX_SCORE, 2, 0, &time_info, &mut tt)
|
let anointed_move = negamax(&mut game, MIN_SCORE, MAX_SCORE, 2, 0, &time_info)
|
||||||
.expect("Expected a search result")
|
.expect("Expected a search result")
|
||||||
.best_move
|
.best_move
|
||||||
.expect("Expected a move");
|
.expect("Expected a move");
|
||||||
@@ -117,7 +110,7 @@ mod tests {
|
|||||||
let mut game = from_fen(FEN_MATE_IN_1[1]).unwrap();
|
let mut game = from_fen(FEN_MATE_IN_1[1]).unwrap();
|
||||||
|
|
||||||
let e3f2 = Move::new(Square::E3, Square::F2);
|
let e3f2 = Move::new(Square::E3, Square::F2);
|
||||||
let anointed_move = negamax(&mut game, MIN_SCORE, MAX_SCORE, 2, 0, &time_info, &mut tt)
|
let anointed_move = negamax(&mut game, MIN_SCORE, MAX_SCORE, 2, 0, &time_info)
|
||||||
.expect("Expected a search result")
|
.expect("Expected a search result")
|
||||||
.best_move
|
.best_move
|
||||||
.expect("Expected a move");
|
.expect("Expected a move");
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ pub fn quiescence(game: &mut Game, mut alpha: i32, beta: i32, time_info: &TimeIn
|
|||||||
|
|
||||||
let color = game.current_player();
|
let color = game.current_player();
|
||||||
let all_moves = game.board.pseudo_moves_all_captures();
|
let all_moves = game.board.pseudo_moves_all_captures();
|
||||||
let moves = move_ordering::sort_moves(all_moves, &game.mailbox, None);
|
let tt_move = game.tt.lookup(game.hash).and_then(|entry| entry.mv);
|
||||||
|
let moves = move_ordering::sort_moves(all_moves, &game.mailbox, tt_move);
|
||||||
|
|
||||||
for mv in moves {
|
for mv in moves {
|
||||||
game.make_move(&mv);
|
game.make_move(&mv);
|
||||||
@@ -33,7 +34,7 @@ pub fn quiescence(game: &mut Game, mut alpha: i32, beta: i32, time_info: &TimeIn
|
|||||||
game.unmake_move();
|
game.unmake_move();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let score = -quiescence(game, -beta, -alpha, time_info)?;
|
let score = -quiescence(game, -beta, -alpha, time_info)?;
|
||||||
game.unmake_move();
|
game.unmake_move();
|
||||||
|
|
||||||
|
|||||||
@@ -16,10 +16,6 @@ impl TimeInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn time_limit_reached(time: &Instant, remaining_time: u128, eval: i32) -> bool {
|
|
||||||
hard_limit(time, remaining_time) || soft_limit(time, remaining_time, eval)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn hard_limit(time_now: &Instant, remaining_time: u128) -> bool {
|
pub fn hard_limit(time_now: &Instant, remaining_time: u128) -> bool {
|
||||||
time_now.elapsed().as_millis() >= remaining_time / HARD_LIMIT_DIVISION
|
time_now.elapsed().as_millis() >= remaining_time / HARD_LIMIT_DIVISION
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use crate::{board::zobrist::ZobristHash, movegen::r#move::Move};
|
use crate::{board::zobrist::ZobristHash, movegen::r#move::Move};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub struct TranspositionTable {
|
pub struct TranspositionTable {
|
||||||
positions: Vec<Option<TTEntry>>,
|
positions: Vec<Option<TTEntry>>,
|
||||||
size: u64,
|
size: u64,
|
||||||
@@ -24,60 +25,29 @@ impl TranspositionTable {
|
|||||||
|
|
||||||
pub fn insert(&mut self, tt_entry: TTEntry) {
|
pub fn insert(&mut self, tt_entry: TTEntry) {
|
||||||
let index = (tt_entry.hash.hash % self.size) as usize;
|
let index = (tt_entry.hash.hash % self.size) as usize;
|
||||||
|
self.positions[index] = Some(tt_entry);
|
||||||
if let Some(stored) = &self.positions[index] {
|
|
||||||
if tt_entry.depth > stored.depth || matches!(tt_entry.bound, Bound::Exact) {
|
|
||||||
self.positions[index] = Some(tt_entry);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.positions[index] = Some(tt_entry);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub struct TTEntry {
|
pub struct TTEntry {
|
||||||
pub hash: ZobristHash,
|
pub hash: ZobristHash,
|
||||||
pub depth: u8,
|
|
||||||
pub score: i32,
|
|
||||||
pub mv: Option<Move>,
|
pub mv: Option<Move>,
|
||||||
pub bound: Bound,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TTEntry {
|
impl TTEntry {
|
||||||
pub const fn new(
|
pub const fn new(hash: ZobristHash, mv: Option<Move>) -> Self {
|
||||||
hash: ZobristHash,
|
Self { hash, mv }
|
||||||
depth: u8,
|
|
||||||
score: i32,
|
|
||||||
mv: Option<Move>,
|
|
||||||
bound: Bound,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
hash,
|
|
||||||
depth,
|
|
||||||
score,
|
|
||||||
mv,
|
|
||||||
bound,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub enum Bound {
|
|
||||||
Exact,
|
|
||||||
Alpha,
|
|
||||||
Beta,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{
|
use crate::{
|
||||||
board::fen::from_fen,
|
board::fen::from_fen,
|
||||||
evaluation::{MAX_SCORE, MIN_SCORE},
|
evaluation::{MAX_SCORE, MIN_SCORE},
|
||||||
movegen::attack_generator::init_attacks,
|
movegen::attack_generator::init_attacks,
|
||||||
search::{
|
search::{negamax::negamax, time::TimeInfo},
|
||||||
negamax::negamax, time::TimeInfo, transposition_table::TranspositionTable, MAX_TT_SIZE,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const FEN: &str = "1r2k2r/2P1pq1p/2npb3/1p3ppP/p3P3/P2B1Q2/1P1PNPP1/R3K2R w KQk g6 0 1";
|
const FEN: &str = "1r2k2r/2P1pq1p/2npb3/1p3ppP/p3P3/P2B1Q2/1P1PNPP1/R3K2R w KQk g6 0 1";
|
||||||
@@ -88,10 +58,9 @@ mod tests {
|
|||||||
fn test_transposition_table() -> anyhow::Result<()> {
|
fn test_transposition_table() -> anyhow::Result<()> {
|
||||||
init_attacks();
|
init_attacks();
|
||||||
let mut game = from_fen(FEN).unwrap();
|
let mut game = from_fen(FEN).unwrap();
|
||||||
let mut tt = TranspositionTable::new(MAX_TT_SIZE);
|
|
||||||
let time_info = TimeInfo::new(std::time::Instant::now(), 30000);
|
let time_info = TimeInfo::new(std::time::Instant::now(), 30000);
|
||||||
|
|
||||||
negamax(&mut game, MIN_SCORE, MAX_SCORE, 2, 0, &time_info, &mut tt)
|
negamax(&mut game, MIN_SCORE, MAX_SCORE, 2, 0, &time_info)
|
||||||
.expect("Expected a search result")
|
.expect("Expected a search result")
|
||||||
.best_move
|
.best_move
|
||||||
.expect("Expected a move");
|
.expect("Expected a move");
|
||||||
@@ -99,12 +68,12 @@ mod tests {
|
|||||||
dbg!(time_info.time.elapsed());
|
dbg!(time_info.time.elapsed());
|
||||||
|
|
||||||
let will_be_hash = from_fen(FEN_POSSIBLE).unwrap().hash;
|
let will_be_hash = from_fen(FEN_POSSIBLE).unwrap().hash;
|
||||||
let tt_entry = tt.lookup(will_be_hash);
|
let tt_entry = game.tt.lookup(will_be_hash);
|
||||||
|
|
||||||
assert!(tt_entry.is_some());
|
assert!(tt_entry.is_some());
|
||||||
|
|
||||||
let wont_be_hash = from_fen(FEN_IMPOSSIBLE).unwrap().hash;
|
let wont_be_hash = from_fen(FEN_IMPOSSIBLE).unwrap().hash;
|
||||||
let tt_entry = tt.lookup(wont_be_hash);
|
let tt_entry = game.tt.lookup(wont_be_hash);
|
||||||
|
|
||||||
assert!(tt_entry.is_none());
|
assert!(tt_entry.is_none());
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user