use std::{
fmt::{self, Display, Formatter},
io::{self, Read, Write},
};
use once_cell::sync::Lazy;
use serde::{
de::{self, Deserialize, Deserializer, SeqAccess, Visitor},
ser::{Serialize, SerializeTuple, Serializer},
};
use shakmaty::{uci::Uci, Chess, File, Move, Position, Rank, Role, Square};
use crate::tr;
pub const EXPERIENCE_ENTRY_SIZE: usize = std::mem::size_of::<ExperienceEntry>();
pub static BRAINLEARN_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)
#
# Depth is the engine depth reached in analyzing the move.
# Score is the score of the move.
# Performance is the performance of the move.
#
# All of depth, score and performance are integers in the range of {}..={}.
#
# Edit the file as you like, the moves you deleted will be removed from the experience file.
# If you delete all the moves the position will be removed from the experience file.
# Exit without saving to abort the action.
",
i32::MIN,
i32::MAX
)
});
#[derive(Copy, Clone, Default, Debug)]
#[repr(C, packed)]
pub struct ExperienceEntry {
pub key: u64,
pub depth: i32,
pub score: i32,
pub mov: i32,
pub perf: i32,
}
impl Display for ExperienceEntry {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(
f,
"{}",
serde_json::to_string(&self).map_err(|_| std::fmt::Error)?
)
}
}
impl Serialize for ExperienceEntry {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut tup = serializer.serialize_tuple(5)?;
let key = self.key;
tup.serialize_element(&key)?;
let depth = self.depth;
tup.serialize_element(&depth)?;
let score = self.score;
tup.serialize_element(&score)?;
let mov = self.mov;
tup.serialize_element(&mov)?;
let perf = self.perf;
tup.serialize_element(&perf)?;
tup.end()
}
}
struct ExperienceEntryVisitor;
impl<'de> Visitor<'de> for ExperienceEntryVisitor {
type Value = ExperienceEntry;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a five-element tuple")
}
fn visit_seq<A>(self, mut seq: A) -> Result<ExperienceEntry, A::Error>
where
A: SeqAccess<'de>,
{
let key = seq
.next_element()?
.ok_or_else(|| de::Error::invalid_length(0, &self))?;
let depth = seq
.next_element()?
.ok_or_else(|| de::Error::invalid_length(2, &self))?;
let score = seq
.next_element()?
.ok_or_else(|| de::Error::invalid_length(3, &self))?;
let mov = seq
.next_element()?
.ok_or_else(|| de::Error::invalid_length(1, &self))?;
let perf = seq
.next_element()?
.ok_or_else(|| de::Error::invalid_length(4, &self))?;
Ok(ExperienceEntry {
key,
mov,
depth,
score,
perf,
})
}
}
impl<'de> Deserialize<'de> for ExperienceEntry {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_tuple(5, ExperienceEntryVisitor)
}
}
pub fn format_exp_entries(position: &Chess, entries: Vec<ExperienceEntry>) -> String {
let mut ret = String::new();
ret.push_str("*,uci,depth,score,performance\n");
let mut i = 1;
for entry in &entries {
if let Some(mv) = to_move(position, entry.mov) {
let uci = Uci::from_standard(&mv);
let depth = entry.depth;
let score = entry.score;
let perf = entry.perf;
ret.push_str(&format!("{},{},{},{},{}\n", i, uci, depth, score, perf));
}
i += 1;
}
ret
}
pub fn exp_entry_to_file<W: Write>(mut f: W, entry: &ExperienceEntry) -> io::Result<()> {
f.write_all(&entry.key.to_le_bytes())?;
f.write_all(&entry.depth.to_le_bytes())?;
f.write_all(&entry.score.to_le_bytes())?;
f.write_all(&entry.mov.to_le_bytes())?;
f.write_all(&entry.perf.to_le_bytes())?;
Ok(())
}
pub fn exp_entry_from_file<R: Read>(mut f: R) -> io::Result<ExperienceEntry> {
let mut key_buf = [0; 8];
let mut depth_buf = [0; 4];
let mut score_buf = [0; 4];
let mut mov_buf = [0; 4];
let mut perf_buf = [0; 4];
f.read_exact(&mut key_buf)?;
f.read_exact(&mut depth_buf)?;
f.read_exact(&mut score_buf)?;
f.read_exact(&mut mov_buf)?;
f.read_exact(&mut perf_buf)?;
Ok(ExperienceEntry {
key: u64::from_le_bytes(key_buf),
depth: i32::from_le_bytes(depth_buf),
score: i32::from_le_bytes(score_buf),
mov: i32::from_le_bytes(mov_buf),
perf: i32::from_le_bytes(perf_buf),
})
}
pub fn from_move(mov: Move) -> i32 {
let from = match mov.from() {
Some(square) => square,
None => panic!("{}", tr!("Move doesn't have a from field.")),
};
let to = mov.to();
let move_type = match mov {
Move::Normal { promotion, .. } if promotion.is_some() => 1,
Move::Normal { .. } => 0,
Move::EnPassant { .. } => 2,
Move::Castle { .. } => 3,
Move::Put { .. } => panic!(
"{}",
tr!("Put move type isn't supported for compact book moves.")
),
};
let promotion = match mov.promotion() {
None => 0,
Some(role) => match role {
Role::Knight => 0,
Role::Bishop => 1,
Role::Rook => 2,
Role::Queen => 3,
_ => panic!(
"{}",
tr!(
"Invalid promotion role: {}, please report a bug!",
format!("{:?}", role)
)
),
},
};
(to.file() as i32 & 0x7)
| ((to.rank() as i32 & 0x7) << 3)
| ((from.file() as i32 & 0x7) << 6)
| ((from.rank() as i32 & 0x7) << 9)
| ((promotion & 0x3) << 12)
| ((move_type & 0x3) << 14)
}
pub fn to_move(position: &Chess, book_move: i32) -> Option<Move> {
let to = Square::from_coords(
File::new((book_move as u32) & 0x7),
Rank::new((book_move as u32 >> 3) & 0x7),
);
let from = Square::from_coords(
File::new((book_move as u32 >> 6) & 0x7),
Rank::new((book_move as u32 >> 9) & 0x7),
);
let movetype = (book_move >> 14) & 0x3;
let promotion = if movetype != 1 {
None
} else {
match (book_move >> 12) & 0x3 {
0 => Some(Role::Knight),
1 => Some(Role::Bishop),
2 => Some(Role::Rook),
3 => Some(Role::Queen),
n => {
panic!(
"{}",
tr!("Invalid promotion role: {}, please report a bug!", n)
);
}
}
};
let board = position.board();
let mov = match movetype {
0 | 1 =>
{
Move::Normal {
role: board.role_at(from)?,
from,
capture: board.role_at(to),
to,
promotion,
}
}
2 => Move::EnPassant { from, to },
3 => Move::Castle {
king: from,
rook: to,
},
_ => {
panic!(
"{}",
tr!("Invalid move type {}, please report a bug!", movetype)
);
}
};
Some(mov)
}