use std::cell::UnsafeCell;
use std::marker::PhantomData;
use std::rc::Rc;
use crate::types::{Color, Move, Piece, PieceType, Square};
use super::tt_history::TTMoveHistory;
use super::tune_params::SearchTuneParams;
pub const PAWN_HISTORY_SIZE: usize = 8192;
pub const CORRECTION_HISTORY_SIZE: usize = 65536;
pub const CORRECTION_HISTORY_LIMIT: i32 = 1024;
pub const LOW_PLY_HISTORY_SIZE: usize = 5;
pub const FROM_TO_SIZE: usize = 1 << 16;
const MAIN_HISTORY_INIT: i16 = 68;
const CAPTURE_HISTORY_INIT: i16 = -689;
pub const CONTINUATION_HISTORY_INIT: i16 = -529;
const PAWN_HISTORY_INIT: i16 = -1238;
const PIECE_TYPE_NUM: usize = PieceType::NUM + 1;
const PIECE_NUM: usize = Piece::NUM;
pub const TT_MOVE_HISTORY_BONUS: i32 = 811;
pub const TT_MOVE_HISTORY_MALUS: i32 = -848;
pub const CONTINUATION_HISTORY_WEIGHTS: [(usize, i32); 6] =
[(1, 1157), (2, 648), (3, 288), (4, 576), (5, 140), (6, 441)];
pub const LOW_PLY_HISTORY_MULTIPLIER: i32 = 761;
pub const LOW_PLY_HISTORY_OFFSET: i32 = 0;
pub const CONTINUATION_HISTORY_MULTIPLIER: i32 = 955;
pub const PAWN_HISTORY_POS_MULTIPLIER: i32 = 850;
pub const PAWN_HISTORY_NEG_MULTIPLIER: i32 = 550;
pub const CONTINUATION_HISTORY_NEAR_PLY_OFFSET: i32 = 88;
pub const PRIOR_CAPTURE_COUNTERMOVE_BONUS: i32 = 964;
#[derive(Clone, Copy)]
pub struct StatsEntry<const D: i32> {
value: i16,
}
impl<const D: i32> Default for StatsEntry<D> {
fn default() -> Self {
Self { value: 0 }
}
}
impl<const D: i32> StatsEntry<D> {
#[inline]
pub fn get(&self) -> i16 {
self.value
}
#[inline]
pub fn set(&mut self, v: i16) {
self.value = v;
}
#[inline]
pub fn update(&mut self, bonus: i32) {
let clamped = bonus.clamp(-D, D);
let delta = clamped - (self.value as i32) * clamped.abs() / D;
self.value = (self.value as i32 + delta) as i16;
debug_assert!(
self.value.abs() <= D as i16,
"StatsEntry out of range: {} (D={})",
self.value,
D
);
}
}
pub struct ButterflyHistory {
table: [[StatsEntry<7183>; FROM_TO_SIZE]; Color::NUM],
}
impl ButterflyHistory {
pub fn new() -> Self {
Self {
table: [[StatsEntry::default(); FROM_TO_SIZE]; Color::NUM],
}
}
#[inline]
pub fn get(&self, color: Color, mv: Move) -> i16 {
self.table[color.index()][mv.history_index()].get()
}
#[inline]
pub fn update(&mut self, color: Color, mv: Move, bonus: i32) {
self.table[color.index()][mv.history_index()].update(bonus);
}
pub fn clear(&mut self) {
for color_table in &mut self.table {
for entry in color_table.iter_mut() {
entry.set(MAIN_HISTORY_INIT);
}
}
}
}
impl Default for ButterflyHistory {
fn default() -> Self {
Self::new()
}
}
pub struct LowPlyHistory {
table: [[StatsEntry<7183>; FROM_TO_SIZE]; LOW_PLY_HISTORY_SIZE],
}
impl LowPlyHistory {
pub fn new() -> Self {
let mut lph = Self {
table: [[StatsEntry::default(); FROM_TO_SIZE]; LOW_PLY_HISTORY_SIZE],
};
lph.clear();
lph
}
#[inline]
pub fn get(&self, ply: usize, mv: Move) -> i16 {
if ply < LOW_PLY_HISTORY_SIZE {
self.table[ply][mv.history_index()].get()
} else {
0
}
}
#[inline]
pub fn update(&mut self, ply: usize, mv: Move, bonus: i32) {
if ply < LOW_PLY_HISTORY_SIZE {
self.table[ply][mv.history_index()].update(bonus);
}
}
pub fn clear(&mut self) {
for ply_table in &mut self.table {
for entry in ply_table.iter_mut() {
entry.set(97);
}
}
}
}
impl Default for LowPlyHistory {
fn default() -> Self {
Self::new()
}
}
pub struct CapturePieceToHistory {
table: [[[StatsEntry<10692>; PIECE_TYPE_NUM]; Square::NUM]; PIECE_NUM],
}
impl CapturePieceToHistory {
pub fn new() -> Self {
Self {
table: [[[StatsEntry::default(); PIECE_TYPE_NUM]; Square::NUM]; PIECE_NUM],
}
}
pub fn new_boxed() -> Box<Self> {
unsafe { Box::<Self>::new_zeroed().assume_init() }
}
#[inline]
pub fn get_with_captured_piece(&self, pc: Piece, to: Square, captured: Piece) -> i16 {
let captured_idx = (captured.raw() & 0x0F) as usize;
debug_assert!(
captured_idx < PIECE_TYPE_NUM,
"captured_idx {} out of bounds (PIECE_TYPE_NUM = {})",
captured_idx,
PIECE_TYPE_NUM
);
self.table[pc.index()][to.index()][captured_idx].get()
}
#[inline]
pub fn get(&self, pc: Piece, to: Square, captured_pt: PieceType) -> i16 {
self.table[pc.index()][to.index()][captured_pt as usize].get()
}
#[inline]
pub fn update(&mut self, pc: Piece, to: Square, captured_pt: PieceType, bonus: i32) {
self.table[pc.index()][to.index()][captured_pt as usize].update(bonus);
}
pub fn clear(&mut self) {
for pc_table in self.table.iter_mut() {
for sq_table in pc_table.iter_mut() {
for entry in sq_table.iter_mut() {
entry.set(CAPTURE_HISTORY_INIT);
}
}
}
}
}
impl Default for CapturePieceToHistory {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone)]
pub struct PieceToHistory {
table: [[StatsEntry<30000>; Square::NUM]; PIECE_NUM],
}
impl PieceToHistory {
pub fn new() -> Self {
Self {
table: [[StatsEntry::default(); Square::NUM]; PIECE_NUM],
}
}
#[inline]
pub fn get(&self, pc: Piece, to: Square) -> i16 {
self.table[pc.index()][to.index()].get()
}
#[inline]
pub fn update(&mut self, pc: Piece, to: Square, bonus: i32) {
self.table[pc.index()][to.index()].update(bonus);
}
pub fn clear(&mut self) {
self.fill(CONTINUATION_HISTORY_INIT);
}
pub fn fill(&mut self, value: i16) {
for pc_table in &mut self.table {
for entry in pc_table.iter_mut() {
entry.set(value);
}
}
}
}
impl Default for PieceToHistory {
fn default() -> Self {
Self::new()
}
}
pub struct ContinuationHistory {
table: [[PieceToHistory; Square::NUM]; PIECE_NUM],
}
impl ContinuationHistory {
pub fn new() -> Self {
let table = std::array::from_fn(|_| std::array::from_fn(|_| PieceToHistory::new()));
Self { table }
}
#[inline]
pub fn get_table(&self, prev_pc: Piece, prev_to: Square) -> &PieceToHistory {
&self.table[prev_pc.index()][prev_to.index()]
}
#[inline]
pub fn get_table_mut(&mut self, prev_pc: Piece, prev_to: Square) -> &mut PieceToHistory {
&mut self.table[prev_pc.index()][prev_to.index()]
}
pub fn new_boxed() -> Box<Self> {
unsafe { Box::<Self>::new_zeroed().assume_init() }
}
pub fn update_from_prev(
&mut self,
prev_pc: Piece,
prev_to: Square,
pc: Piece,
to: Square,
bonus: i32,
) {
self.get_table_mut(prev_pc, prev_to).update(pc, to, bonus);
}
#[inline]
pub fn get(&self, prev_pc: Piece, prev_to: Square, pc: Piece, to: Square) -> i16 {
self.get_table(prev_pc, prev_to).get(pc, to)
}
#[inline]
pub fn update(&mut self, prev_pc: Piece, prev_to: Square, pc: Piece, to: Square, bonus: i32) {
self.get_table_mut(prev_pc, prev_to).update(pc, to, bonus);
}
pub fn clear(&mut self) {
for row in self.table.iter_mut() {
for entry in row.iter_mut() {
entry.clear();
}
}
}
}
impl Default for ContinuationHistory {
fn default() -> Self {
Self::new()
}
}
pub struct PawnHistory {
table: [[[StatsEntry<8192>; Square::NUM]; PIECE_NUM]; PAWN_HISTORY_SIZE],
}
impl PawnHistory {
pub fn new() -> Self {
let row = [[StatsEntry::default(); Square::NUM]; PIECE_NUM];
Self {
table: [row; PAWN_HISTORY_SIZE],
}
}
pub fn new_boxed() -> Box<Self> {
unsafe { Box::<Self>::new_zeroed().assume_init() }
}
#[inline]
pub fn get(&self, pawn_key_index: usize, pc: Piece, to: Square) -> i16 {
self.table[pawn_key_index][pc.index()][to.index()].get()
}
#[inline]
pub fn update(&mut self, pawn_key_index: usize, pc: Piece, to: Square, bonus: i32) {
self.table[pawn_key_index][pc.index()][to.index()].update(bonus);
}
pub fn clear(&mut self) {
for pawn_table in self.table.iter_mut() {
for pc_table in pawn_table.iter_mut() {
for entry in pc_table.iter_mut() {
entry.set(PAWN_HISTORY_INIT);
}
}
}
}
}
impl Default for PawnHistory {
fn default() -> Self {
Self::new()
}
}
pub struct CounterMoveHistory {
table: [[Move; Square::NUM]; PIECE_NUM],
}
impl CounterMoveHistory {
pub fn new() -> Self {
Self {
table: [[Move::NONE; Square::NUM]; PIECE_NUM],
}
}
#[inline]
pub fn get(&self, pc: Piece, sq: Square) -> Move {
self.table[pc.index()][sq.index()]
}
#[inline]
pub fn set(&mut self, pc: Piece, sq: Square, mv: Move) {
self.table[pc.index()][sq.index()] = mv;
}
pub fn clear(&mut self) {
for pc_table in &mut self.table {
for entry in pc_table.iter_mut() {
*entry = Move::NONE;
}
}
}
}
impl Default for CounterMoveHistory {
fn default() -> Self {
Self::new()
}
}
pub struct CorrectionHistory {
pawn: [[StatsEntry<CORRECTION_HISTORY_LIMIT>; Color::NUM]; CORRECTION_HISTORY_SIZE],
minor: [[StatsEntry<CORRECTION_HISTORY_LIMIT>; Color::NUM]; CORRECTION_HISTORY_SIZE],
non_pawn:
[[[StatsEntry<CORRECTION_HISTORY_LIMIT>; Color::NUM]; Color::NUM]; CORRECTION_HISTORY_SIZE],
continuation: [[[[StatsEntry<CORRECTION_HISTORY_LIMIT>; Square::NUM]; Piece::NUM]; Square::NUM];
Piece::NUM],
}
impl CorrectionHistory {
pub fn new() -> Self {
let mut history = Self {
pawn: [[StatsEntry::default(); Color::NUM]; CORRECTION_HISTORY_SIZE],
minor: [[StatsEntry::default(); Color::NUM]; CORRECTION_HISTORY_SIZE],
non_pawn: [[[StatsEntry::default(); Color::NUM]; Color::NUM]; CORRECTION_HISTORY_SIZE],
continuation: [[[[StatsEntry::default(); Square::NUM]; Piece::NUM]; Square::NUM];
Piece::NUM],
};
history.fill_initial_values();
history
}
pub fn new_boxed() -> Box<Self> {
let mut history = unsafe { Box::<Self>::new_zeroed().assume_init() };
history.fill_initial_values();
history
}
pub fn clear(&mut self) {
self.fill_initial_values();
}
fn fill_initial_values(&mut self) {
for entry in self.pawn.iter_mut().flatten() {
entry.set(0);
}
for entry in self.minor.iter_mut().flatten() {
entry.set(0);
}
for entry in self.non_pawn.iter_mut().flatten().flatten() {
entry.set(0);
}
for prev_pc in self.continuation.iter_mut() {
for prev_to in prev_pc.iter_mut() {
for entry in prev_to.iter_mut().flatten() {
entry.set(8);
}
}
}
}
#[inline]
pub fn pawn_value(&self, idx: usize, color: Color) -> i16 {
self.pawn[idx % CORRECTION_HISTORY_SIZE][color.index()].get()
}
#[inline]
pub fn update_pawn(&mut self, idx: usize, color: Color, bonus: i32) {
self.pawn[idx % CORRECTION_HISTORY_SIZE][color.index()].update(bonus);
}
#[inline]
pub fn minor_value(&self, idx: usize, color: Color) -> i16 {
self.minor[idx % CORRECTION_HISTORY_SIZE][color.index()].get()
}
#[inline]
pub fn update_minor(&mut self, idx: usize, color: Color, bonus: i32) {
self.minor[idx % CORRECTION_HISTORY_SIZE][color.index()].update(bonus);
}
#[inline]
pub fn non_pawn_value(&self, idx: usize, board_color: Color, stm: Color) -> i16 {
self.non_pawn[idx % CORRECTION_HISTORY_SIZE][board_color.index()][stm.index()].get()
}
#[inline]
pub fn update_non_pawn(&mut self, idx: usize, board_color: Color, stm: Color, bonus: i32) {
self.non_pawn[idx % CORRECTION_HISTORY_SIZE][board_color.index()][stm.index()]
.update(bonus);
}
#[inline]
pub fn continuation_value(
&self,
prev_pc: Piece,
prev_to: Square,
pc: Piece,
to: Square,
) -> i16 {
self.continuation[prev_pc.index()][prev_to.index()][pc.index()][to.index()].get()
}
#[inline]
pub fn update_continuation(
&mut self,
prev_pc: Piece,
prev_to: Square,
pc: Piece,
to: Square,
bonus: i32,
) {
self.continuation[prev_pc.index()][prev_to.index()][pc.index()][to.index()].update(bonus);
}
}
impl Default for CorrectionHistory {
fn default() -> Self {
Self::new()
}
}
pub struct HistoryTables {
pub main_history: ButterflyHistory,
pub low_ply_history: LowPlyHistory,
pub capture_history: CapturePieceToHistory,
pub continuation_history: [[ContinuationHistory; 2]; 2],
pub pawn_history: PawnHistory,
pub correction_history: CorrectionHistory,
pub tt_move_history: TTMoveHistory,
}
impl HistoryTables {
pub fn new_boxed() -> Box<Self> {
let mut history = unsafe { Box::<Self>::new_zeroed().assume_init() };
history.clear();
history
}
pub fn clear(&mut self) {
self.main_history.clear();
self.low_ply_history.clear();
self.capture_history.clear();
for row in &mut self.continuation_history {
for ch in row {
ch.clear();
}
}
self.pawn_history.clear();
self.correction_history.clear();
self.tt_move_history.clear();
}
}
pub struct HistoryCell {
inner: UnsafeCell<HistoryTables>,
_marker: PhantomData<Rc<()>>,
}
impl HistoryCell {
pub fn new(history: HistoryTables) -> Self {
Self {
inner: UnsafeCell::new(history),
_marker: PhantomData,
}
}
pub fn new_boxed() -> Box<Self> {
let cell = unsafe { Box::<Self>::new_zeroed().assume_init() };
unsafe { cell.as_mut_unchecked() }.clear();
cell
}
#[inline]
pub unsafe fn as_ref_unchecked(&self) -> &HistoryTables {
unsafe { &*self.inner.get() }
}
#[inline]
#[allow(clippy::mut_from_ref)]
pub unsafe fn as_mut_unchecked(&self) -> &mut HistoryTables {
unsafe { &mut *self.inner.get() }
}
#[inline]
pub fn get_mut(&mut self) -> &mut HistoryTables {
self.inner.get_mut()
}
pub fn clear(&mut self) {
self.inner.get_mut().clear();
}
}
#[inline]
pub fn stat_bonus(depth: i32, is_tt_move: bool, tune_params: &SearchTuneParams) -> i32 {
let base = (tune_params.stat_bonus_depth_mult * depth + tune_params.stat_bonus_offset)
.min(tune_params.stat_bonus_max);
if is_tt_move {
base + tune_params.stat_bonus_tt_bonus
} else {
base
}
}
#[inline]
pub fn stat_malus(depth: i32, move_count: i32, tune_params: &SearchTuneParams) -> i32 {
(tune_params.stat_malus_depth_mult * depth + tune_params.stat_malus_offset)
.min(tune_params.stat_malus_max)
- tune_params.stat_malus_move_count_mult * move_count
}
#[inline]
pub fn low_ply_history_bonus(bonus: i32, tune_params: &SearchTuneParams) -> i32 {
bonus * tune_params.low_ply_history_multiplier / 1024 + tune_params.low_ply_history_offset
}
#[inline]
pub fn continuation_history_weight(tune_params: &SearchTuneParams, ply_back: usize) -> i32 {
match ply_back {
1 => tune_params.continuation_history_weight_1,
2 => tune_params.continuation_history_weight_2,
3 => tune_params.continuation_history_weight_3,
4 => tune_params.continuation_history_weight_4,
5 => tune_params.continuation_history_weight_5,
6 => tune_params.continuation_history_weight_6,
_ => 0,
}
}
#[inline]
pub fn continuation_history_bonus_with_offset(
bonus: i32,
ply_back: usize,
tune_params: &SearchTuneParams,
) -> i32 {
if ply_back < 2 {
bonus + tune_params.continuation_history_near_ply_offset
} else {
bonus
}
}
#[inline]
pub fn pawn_history_bonus(bonus: i32, tune_params: &SearchTuneParams) -> i32 {
if bonus > 0 {
bonus * tune_params.pawn_history_pos_multiplier / 1024
} else {
bonus * tune_params.pawn_history_neg_multiplier / 1024
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_stats_entry_default() {
let entry = StatsEntry::<1000>::default();
assert_eq!(entry.get(), 0);
}
#[test]
fn test_stats_entry_update_positive() {
let mut entry = StatsEntry::<1000>::default();
entry.update(100);
assert!(entry.get() > 0);
assert!(entry.get() <= 1000);
}
#[test]
fn test_stats_entry_update_convergence() {
let mut entry = StatsEntry::<1000>::default();
for _ in 0..100 {
entry.update(1000);
}
assert!(entry.get() <= 1000);
assert!(entry.get() > 900); }
#[test]
fn test_stats_entry_update_negative() {
let mut entry = StatsEntry::<1000>::default();
for _ in 0..100 {
entry.update(-1000);
}
assert!(entry.get() >= -1000);
assert!(entry.get() < -900); }
#[test]
fn test_stats_entry_decay() {
let mut entry = StatsEntry::<1000>::default();
for _ in 0..50 {
entry.update(1000);
}
let high_value = entry.get();
assert!(high_value > 0, "値が上がっているべき");
for _ in 0..5 {
entry.update(-100);
}
let decayed_value = entry.get();
assert!(decayed_value < high_value, "減衰しているべき");
}
#[test]
fn test_butterfly_history() {
let mut history = ButterflyHistory::new();
let mv = Move::from_usi("7g7f").unwrap();
assert_eq!(history.get(Color::Black, mv), 0);
history.update(Color::Black, mv, 100);
assert!(history.get(Color::Black, mv) > 0);
assert_eq!(history.get(Color::White, mv), 0); }
#[test]
fn test_low_ply_history() {
let mut history = LowPlyHistory::new();
let mv = Move::from_usi("7g7f").unwrap();
assert_eq!(history.get(0, mv), 97);
history.update(0, mv, 100);
assert!(history.get(0, mv) > 97);
assert_eq!(history.get(1, mv), 97);
assert_eq!(history.get(LOW_PLY_HISTORY_SIZE, mv), 0);
}
#[test]
fn test_counter_move_history() {
let mut history = CounterMoveHistory::new();
let mv = Move::from_usi("7g7f").unwrap();
let pc = Piece::B_PAWN;
let sq = Square::SQ_55;
assert!(history.get(pc, sq).is_none());
history.set(pc, sq, mv);
assert_eq!(history.get(pc, sq), mv);
}
#[test]
fn test_stat_bonus() {
let tune = SearchTuneParams::default();
assert_eq!(stat_bonus(1, false, &tune), 44);
assert_eq!(stat_bonus(1, true, &tune), 419);
assert_eq!(stat_bonus(20, false, &tune), 1633);
assert_eq!(stat_bonus(20, true, &tune), 1633 + 375);
}
#[test]
fn test_stat_malus() {
let tune = SearchTuneParams::default();
assert_eq!(stat_malus(1, 0, &tune), 629);
assert_eq!(stat_malus(1, 10, &tune), 469);
assert_eq!(stat_malus(10, 0, &tune), 2159);
}
#[test]
fn test_capture_piece_to_history_with_captured_piece() {
let mut history = CapturePieceToHistory::new_boxed();
let pc = Piece::B_GOLD;
let to = Square::SQ_55;
let captured = Piece::W_SILVER;
assert_eq!(history.get_with_captured_piece(pc, to, captured), 0);
history.update(pc, to, captured.piece_type(), 100);
assert_eq!(
history.get_with_captured_piece(pc, to, captured),
history.get(pc, to, captured.piece_type())
);
let captured_none = Piece::NONE;
assert_eq!(history.get_with_captured_piece(pc, to, captured_none), 0);
}
#[test]
fn test_history_cell_read() {
let cell = HistoryCell::new_boxed();
let value = unsafe { cell.as_ref_unchecked() }
.main_history
.get(Color::Black, Move::from_usi("7g7f").unwrap());
assert_eq!(value, MAIN_HISTORY_INIT);
}
#[test]
fn test_history_cell_write() {
let cell = HistoryCell::new_boxed();
let mv = Move::from_usi("7g7f").unwrap();
unsafe { cell.as_mut_unchecked() }.main_history.update(Color::Black, mv, 100);
let value = unsafe { cell.as_ref_unchecked() }.main_history.get(Color::Black, mv);
assert!(value > 0);
}
#[test]
fn test_history_cell_clear() {
let mut cell = HistoryCell::new_boxed();
let mv = Move::from_usi("7g7f").unwrap();
unsafe { cell.as_mut_unchecked() }.main_history.update(Color::Black, mv, 100);
cell.clear();
let value = unsafe { cell.as_ref_unchecked() }.main_history.get(Color::Black, mv);
assert_eq!(value, MAIN_HISTORY_INIT);
}
}