use std::{
cmp::PartialEq,
collections::{BTreeMap, VecDeque},
};
use once_cell::sync::Lazy;
use shakmaty::{uci::Uci, Chess, Position, Role, Square};
use crate::{
abkbook::AbkBook,
hash::zobrist_hash,
polyglot::{from_uci, is_king_on_start_square, CompactBookEntry},
tr,
};
pub const ABK_INDEX_OFFSET: usize = 900;
pub const ABK_ENTRY_LENGTH: usize = std::mem::size_of::<SBookMoveEntry>();
pub const FIELDS: [&str; 64] = [
"a1", "b1", "c1", "d1", "e1", "f1", "g1", "h1", "a2", "b2", "c2", "d2", "e2", "f2", "g2", "h2",
"a3", "b3", "c3", "d3", "e3", "f3", "g3", "h3", "a4", "b4", "c4", "d4", "e4", "f4", "g4", "h4",
"a5", "b5", "c5", "d5", "e5", "f5", "g5", "h5", "a6", "b6", "c6", "d6", "e6", "f6", "g6", "h6",
"a7", "b7", "c7", "d7", "e7", "f7", "g7", "h7", "a8", "b8", "c8", "d8", "e8", "f8", "g8", "h8",
];
pub static ABK_EDIT_COMMENT: Lazy<String> = Lazy::new(|| {
tr!(
"#
# The file is in CSV (comma-separated-values) format.
# Lines starting with `#' are comments and ignored.
#
# Moves are given in UCI (universal chess interface) format
# which is a variation of a long algebraic format for chess
# moves commonly used by chess engines.
#
# Examples:
# e2e4, e7e5, e1g1 (white short castling), e7e8q (for promotion)
#
# Priority is a measure for the quality of the move.
# Ngames, Nwon and Nlost are game statistics about the move.
#
# All of priority, ngames, nwon and nlost are positive integers.
# Priority has a maximum of {}.
# Ngames, Nwon and Nlost has a maximum of {}.
#
# Edit the file as you like, the moves you deleted will be removed from the book.
# If you delete all the moves the position will be removed from the book.
# Exit without saving to abort the action.
",
u8::MAX,
i32::MAX
)
});
pub struct NagPriority {
pub good: u8,
pub mistake: u8,
pub hard: u8,
pub blunder: u8,
pub interesting: u8,
pub dubious: u8,
pub forced: u8,
pub only: u8,
}
pub struct ColorPriority {
pub green: u8,
pub blue: u8,
pub red: u8,
}
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct CompactSBookMoveEntry {
pub from: u8, pub to: u8, pub promotion: i8, pub priority: u8, pub ngames: u32, pub nwon: u32, pub nlost: u32, }
#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct SBookMoveEntry {
pub from: u8, pub to: u8, pub promotion: i8, pub priority: u8, pub ngames: u32, pub nwon: u32, pub nlost: u32, pub flags: u32,
pub next_move: u32, pub next_sibling: u32, }
impl From<SBookMoveEntry> for CompactSBookMoveEntry {
fn from(entry: SBookMoveEntry) -> Self {
CompactSBookMoveEntry {
from: entry.from,
to: entry.to,
promotion: entry.promotion,
priority: entry.priority,
ngames: entry.ngames,
nwon: entry.nwon,
nlost: entry.nlost,
}
}
}
impl PartialEq for SBookMoveEntry {
fn eq(&self, other: &Self) -> bool {
self.from == other.from && self.to == other.to && self.promotion == other.promotion
}
}
impl From<SBookMoveEntry> for Uci {
fn from(entry: SBookMoveEntry) -> Uci {
if entry.from >= 64 || entry.to >= 64 {
return Uci::Null;
}
let (from, to) = unsafe {
(
Square::new_unchecked(u32::from(entry.from)),
Square::new_unchecked(u32::from(entry.to)),
)
};
let promotion = match entry.promotion {
1 | -1 => Some(Role::Rook),
2 | -2 => Some(Role::Knight),
3 | -3 => Some(Role::Bishop),
4 | -4 => Some(Role::Queen),
_ => None,
};
Uci::Normal {
from,
to,
promotion,
}
}
}
impl From<CompactSBookMoveEntry> for Uci {
fn from(entry: CompactSBookMoveEntry) -> Uci {
if entry.from >= 64 || entry.to >= 64 {
return Uci::Null;
}
let (from, to) = unsafe {
(
Square::new_unchecked(u32::from(entry.from)),
Square::new_unchecked(u32::from(entry.to)),
)
};
let promotion = match entry.promotion {
1 | -1 => Some(Role::Rook),
2 | -2 => Some(Role::Knight),
3 | -3 => Some(Role::Bishop),
4 | -4 => Some(Role::Queen),
_ => None,
};
Uci::Normal {
from,
to,
promotion,
}
}
}
impl Default for SBookMoveEntry {
fn default() -> Self {
Self::new()
}
}
impl SBookMoveEntry {
pub fn new() -> Self {
SBookMoveEntry {
from: 255,
to: 255,
promotion: 0,
priority: 0,
ngames: 0,
nwon: 0,
nlost: 0,
flags: 0,
next_move: u32::MAX,
next_sibling: u32::MAX,
}
}
pub fn weight(&self) -> u16 {
if self.priority > 0 {
u16::from(self.priority).saturating_mul(2500)
} else if self.nwon > 0 {
self.nwon
.try_into()
.unwrap_or(u16::MAX)
.saturating_sub(self.nlost.try_into().unwrap_or(u16::MAX))
} else {
self.ngames.try_into().unwrap_or(u16::MAX)
}
}
pub fn merge(&mut self, other: SBookMoveEntry) {
if self.flags == 0x01000000 && other.flags != 0x01000000 {
*self = other;
return;
} else if other.flags == 0x01000000 {
return;
}
if other.ngames > self.ngames {
self.ngames = other.ngames;
self.nwon = other.nwon;
self.nlost = other.nlost;
}
if other.priority > self.priority {
self.priority = other.priority;
}
}
pub fn is_deleted(&self) -> bool {
self.flags == 1 || self.flags == 0x01000000
}
pub fn is_selected(&self, book: &AbkBook) -> bool {
if book.probability_games > 0 && self.ngames < book.probability_games {
return false;
}
if book.probability_win_percent > 0 {
let win_percent = if self.ngames > 0 {
(f64::from(self.nwon) / f64::from(self.ngames)) * 100.0
} else {
0.0
};
if win_percent < book.probability_win_percent.into() {
return false;
}
}
true
}
}
pub fn format_abk_entries(entries: Vec<SBookMoveEntry>) -> String {
let mut ret = String::new();
ret.push_str("*,uci,priority,ngames,nwon,nlost\n");
for (i, entry) in entries.iter().enumerate() {
let uci = Uci::from(*entry);
let ngames = entry.ngames;
let nwon = entry.nwon;
let nlost = entry.nlost;
ret.push_str(&format!(
"{},{},{},{},{},{}\n",
i + 1,
uci,
entry.priority,
ngames,
nwon,
nlost,
));
}
ret
}
pub fn traverse_tree(
tree: &BTreeMap<u64, Vec<SBookMoveEntry>>,
pos: Chess,
) -> BTreeMap<u64, Vec<CompactBookEntry>> {
let mut book = BTreeMap::new();
let mut queue = VecDeque::new();
let initial_zobrist = zobrist_hash(&pos);
queue.push_back((pos, initial_zobrist));
while let Some((pos, key)) = queue.pop_front() {
if let Some(entries) = tree.get(&key) {
let mut book_entries = Vec::with_capacity(entries.len());
for entry in entries {
let uci = Uci::from(*entry);
let chess_move = match uci.to_move(&pos) {
Ok(move_) => move_,
Err(_) => {
continue;
}
};
let mov = from_uci(uci, is_king_on_start_square(&pos));
let book_entry = CompactBookEntry {
mov,
weight: entry.weight(),
learn: u32::from(entry.priority),
};
match book_entries.binary_search_by_key(
&std::cmp::Reverse(book_entry.weight),
|entry: &CompactBookEntry| {
std::cmp::Reverse(entry.weight)
},
) {
Ok(pos) | Err(pos) => {
book_entries.insert(pos, book_entry);
}
};
let mut pos = pos.clone();
pos.play_unchecked(&chess_move);
let new_zobrist = zobrist_hash(&pos);
if !book.contains_key(&new_zobrist) {
queue.push_back((pos, new_zobrist));
}
}
book.insert(key, book_entries);
}
}
book
}