use crate::types::{Color, Move, Piece, PieceType, Square};
use super::tt_history::TTMoveHistory;
pub const PAWN_HISTORY_SIZE: usize = 512;
pub const CORRECTION_HISTORY_SIZE: usize = 32768;
pub const CORRECTION_HISTORY_LIMIT: i32 = 1024;
pub const LOW_PLY_HISTORY_SIZE: usize = 5;
pub const FROM_TO_SIZE: usize = (Square::NUM + 7) * Square::NUM;
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, 1108), (2, 652), (3, 273), (4, 572), (5, 126), (6, 449)];
pub const LOW_PLY_HISTORY_MULTIPLIER: i32 = 771;
pub const LOW_PLY_HISTORY_OFFSET: i32 = 40;
pub const CONTINUATION_HISTORY_POS_MULTIPLIER: i32 = 979;
pub const CONTINUATION_HISTORY_NEG_MULTIPLIER: i32 = 842;
pub const PAWN_HISTORY_POS_MULTIPLIER: i32 = 704;
pub const PAWN_HISTORY_NEG_MULTIPLIER: i32 = 439;
pub const PAWN_HISTORY_OFFSET: i32 = 70;
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(0);
}
}
}
}
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 {
Self {
table: [[StatsEntry::default(); FROM_TO_SIZE]; LOW_PLY_HISTORY_SIZE],
}
}
#[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(0);
}
}
}
}
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(0);
}
}
}
}
}
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) {
for pc_table in &mut self.table {
for entry in pc_table.iter_mut() {
entry.set(0);
}
}
}
}
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(0);
}
}
}
}
}
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(5);
}
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.correction_history.fill_initial_values();
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();
}
}
#[inline]
pub fn stat_bonus(depth: i32, is_tt_move: bool) -> i32 {
let base = (170 * depth - 87).min(1598);
if is_tt_move {
base + 332
} else {
base
}
}
#[inline]
pub fn quiet_malus(depth: i32, quiets_count: usize) -> i32 {
let base = (743 * depth - 180).min(2287);
base - 33 * quiets_count as i32
}
#[inline]
pub fn capture_malus(depth: i32, captures_count: usize) -> i32 {
let base = (708 * depth - 148).min(2287);
base - 29 * captures_count as i32
}
#[deprecated(note = "Use stat_bonus(depth, is_tt_move) instead")]
#[inline]
pub fn stat_bonus_old(depth: i32) -> i32 {
(130 * depth - 103).min(1652)
}
#[deprecated(note = "Use quiet_malus(depth, quiets_count) instead")]
#[inline]
pub fn stat_malus(depth: i32) -> i32 {
(303 * depth - 273).min(1352)
}
#[inline]
pub fn low_ply_history_bonus(bonus: i32) -> i32 {
bonus * LOW_PLY_HISTORY_MULTIPLIER / 1024 + LOW_PLY_HISTORY_OFFSET
}
#[inline]
pub fn continuation_history_bonus(bonus: i32) -> i32 {
if bonus > 0 {
bonus * CONTINUATION_HISTORY_POS_MULTIPLIER / 1024
} else {
bonus * CONTINUATION_HISTORY_NEG_MULTIPLIER / 1024
}
}
#[inline]
pub fn continuation_history_bonus_with_offset(bonus: i32, ply_back: usize) -> i32 {
let base = continuation_history_bonus(bonus);
if ply_back <= 2 {
base + CONTINUATION_HISTORY_NEAR_PLY_OFFSET
} else {
base
}
}
#[inline]
pub fn pawn_history_bonus(bonus: i32) -> i32 {
if bonus > 0 {
bonus * PAWN_HISTORY_POS_MULTIPLIER / 1024 + PAWN_HISTORY_OFFSET
} else {
bonus * PAWN_HISTORY_NEG_MULTIPLIER / 1024 + PAWN_HISTORY_OFFSET
}
}
#[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();
history.update(0, mv, 100);
assert!(history.get(0, mv) > 0);
assert_eq!(history.get(1, mv), 0);
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() {
assert_eq!(stat_bonus(1, false), 83);
assert_eq!(stat_bonus(1, true), 415);
assert_eq!(stat_bonus(20, false), 1598);
assert_eq!(stat_bonus(20, true), 1598 + 332);
}
#[test]
fn test_quiet_malus() {
assert_eq!(quiet_malus(1, 0), 563);
assert_eq!(quiet_malus(1, 10), 233);
assert_eq!(quiet_malus(10, 0), 2287);
}
#[test]
fn test_capture_malus() {
assert_eq!(capture_malus(1, 0), 560);
assert_eq!(capture_malus(1, 5), 415);
assert_eq!(capture_malus(10, 0), 2287);
}
#[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);
}
}