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, Color, File, Move, Piece, Position, Rank, Role, Square};
use crate::{
file::{int_from_file, int_to_file},
tr,
};
pub const BOOK_ENTRY_SIZE: usize = std::mem::size_of::<BookEntry>();
pub static POLYGLOT_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)
#
# Weight is a measure for the quality of the move.
# Learn is used by some programs to save learning information,
# however this field is commonly unused.
#
# Both weight and learn are positive integers.
# Weight has a maximum of {} whereas Learn 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.
",
u16::MAX,
u32::MAX
)
});
#[derive(Copy, Clone, Default, Debug)]
#[repr(C, packed)]
pub struct BookEntry {
pub key: u64,
pub mov: u16,
pub weight: u16,
pub learn: u32,
}
impl Display for BookEntry {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(
f,
"{}",
serde_json::to_string(&self).map_err(|_| std::fmt::Error)?
)
}
}
impl Serialize for BookEntry {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut tup = serializer.serialize_tuple(4)?;
let key = self.key;
tup.serialize_element(&key)?;
let mov = self.mov;
tup.serialize_element(&mov)?;
let weight = self.weight;
tup.serialize_element(&weight)?;
let learn = self.learn;
tup.serialize_element(&learn)?;
tup.end()
}
}
struct BookEntryVisitor;
impl<'de> Visitor<'de> for BookEntryVisitor {
type Value = BookEntry;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a four-element tuple")
}
fn visit_seq<A>(self, mut seq: A) -> Result<BookEntry, A::Error>
where
A: SeqAccess<'de>,
{
let key = seq
.next_element()?
.ok_or_else(|| de::Error::invalid_length(0, &self))?;
let mov = seq
.next_element()?
.ok_or_else(|| de::Error::invalid_length(1, &self))?;
let weight = seq
.next_element()?
.ok_or_else(|| de::Error::invalid_length(2, &self))?;
let learn = seq
.next_element()?
.ok_or_else(|| de::Error::invalid_length(3, &self))?;
Ok(BookEntry {
key,
mov,
weight,
learn,
})
}
}
impl<'de> Deserialize<'de> for BookEntry {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_tuple(4, BookEntryVisitor)
}
}
#[derive(Copy, Clone, Default, Debug)]
#[repr(C, packed)]
pub struct CompactBookEntry {
pub mov: u16,
pub weight: u16,
pub learn: u32,
}
pub struct NagWeight {
pub good: u16,
pub mistake: u16,
pub hard: u16,
pub blunder: u16,
pub interesting: u16,
pub dubious: u16,
pub forced: u16,
pub only: u16,
}
pub struct ColorWeight {
pub green: u16,
pub blue: u16,
pub red: u16,
}
pub fn format_bin_entries(position: &Chess, entries: Vec<BookEntry>) -> String {
let mut ret = String::new();
ret.push_str("*,uci,weight,learn\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 weight = entry.weight;
let learn = entry.learn;
ret.push_str(&format!("{},{},{},{}\n", i, uci, weight, learn));
}
i += 1;
}
ret
}
pub fn bin_entry_to_file<W: Write>(mut f: W, entry: &BookEntry) -> io::Result<()> {
int_to_file(&mut f, 8, entry.key)?;
int_to_file(&mut f, 2, u64::from(entry.mov))?;
int_to_file(&mut f, 2, u64::from(entry.weight))?;
int_to_file(&mut f, 4, u64::from(entry.learn))?;
Ok(())
}
pub fn bin_entry_from_file<R: Read>(mut f: R) -> io::Result<BookEntry> {
let key = int_from_file(&mut f, 8)?;
let mov = int_from_file(&mut f, 2)? as u16;
let weight = int_from_file(&mut f, 2)? as u16;
let learn = int_from_file(&mut f, 4)? as u32;
Ok(BookEntry {
key,
mov,
weight,
learn,
})
}
pub fn from_uci(uci: Uci, king_on_start_square: bool) -> u16 {
match uci {
Uci::Null => 0,
Uci::Normal {
from,
to,
promotion,
} => {
let ff = from.file() as u16;
let fr = from.rank() as u16;
let (tf, tr) =
if king_on_start_square && from == Square::E1 && (to == Square::G1 || to == Square::C1) {
if to == Square::G1 { (File::H as u16, Rank::First as u16) } else { (File::A as u16, Rank::First as u16) }
} else if king_on_start_square && from == Square::E8 && (to == Square::G8 || to == Square::C8) {
if to == Square::G8 { (File::H as u16, Rank::Eighth as u16) } else { (File::A as u16, Rank::Eighth as u16) }
} else {
(to.file() as u16, to.rank() as u16)
};
let p = match promotion {
Some(Role::Knight) => 1,
Some(Role::Bishop) => 2,
Some(Role::Rook) => 3,
Some(Role::Queen) => 4,
_ => 0,
};
(p << 12) + (fr << 9) + (ff << 6) + (tr << 3) + tf
}
Uci::Put { .. } => unreachable!(),
}
}
pub fn from_move(mov: &Move) -> u16 {
match mov {
Move::Normal {
from,
to,
promotion,
..
} => {
let ff = from.file() as u16;
let fr = from.rank() as u16;
let tf = to.file() as u16;
let tr = to.rank() as u16;
let p = match promotion {
Some(Role::Knight) => 1,
Some(Role::Bishop) => 2,
Some(Role::Rook) => 3,
Some(Role::Queen) => 4,
_ => 0,
};
(p << 12) + (fr << 9) + (ff << 6) + (tr << 3) + tf
}
Move::EnPassant { from, to } => {
let ff = from.file() as u16;
let fr = from.rank() as u16;
let tf = to.file() as u16;
let tr = to.rank() as u16;
(fr << 9) + (ff << 6) + (tr << 3) + tf
}
Move::Castle { king, rook } => {
let kf = king.file() as u16;
let kr = king.rank() as u16;
let tf = rook.file() as u16;
let tr = rook.rank() as u16;
(kr << 9) + (kf << 6) + (tr << 3) + tf
}
Move::Put { .. } => unreachable!(),
}
}
pub fn to_move(position: &dyn Position, book_move: u16) -> Option<Move> {
let book_move = u32::from(book_move);
let to_ = Square::from_coords(
File::new(book_move & 0x7),
Rank::new((book_move >> 3) & 0x07),
);
let from_ = Square::from_coords(
File::new((book_move >> 6) & 0x7),
Rank::new((book_move >> 9) & 0x07),
);
let promotion_role = match book_move >> 12 {
0 => None,
1 => Some(Role::Knight),
2 => Some(Role::Bishop),
3 => Some(Role::Rook),
4 => Some(Role::Queen),
_ => {
return None;
}
};
let moves = position.legal_moves();
for chess_move in moves {
match chess_move {
Move::Normal {
from,
to,
promotion,
..
} => {
if from == from_ && to == to_ && promotion == promotion_role {
return Some(chess_move);
}
}
Move::Castle { king, rook } => {
if king == from_ && rook == to_ {
return Some(chess_move);
}
}
Move::EnPassant { from, to } => {
if from == from_ && to == to_ {
return Some(chess_move);
}
}
_ => {
unreachable!(
"What's going on with this move? `{:?}' (from:{} to:{}), please report a bug.",
chess_move, from_, to_
);
}
};
}
None
}
pub fn piece_index(piece: Piece) -> u8 {
match piece {
Piece {
color: Color::Black,
role: Role::Pawn,
} => 0,
Piece {
color: Color::White,
role: Role::Pawn,
} => 1,
Piece {
color: Color::Black,
role: Role::Knight,
} => 2,
Piece {
color: Color::White,
role: Role::Knight,
} => 3,
Piece {
color: Color::Black,
role: Role::Bishop,
} => 4,
Piece {
color: Color::White,
role: Role::Bishop,
} => 5,
Piece {
color: Color::Black,
role: Role::Rook,
} => 6,
Piece {
color: Color::White,
role: Role::Rook,
} => 7,
Piece {
color: Color::Black,
role: Role::Queen,
} => 8,
Piece {
color: Color::White,
role: Role::Queen,
} => 9,
Piece {
color: Color::Black,
role: Role::King,
} => 10,
Piece {
color: Color::White,
role: Role::King,
} => 11,
}
}
pub fn is_king_on_start_square(position: &Chess) -> bool {
let turn = position.turn();
position
.board()
.king_of(turn)
.map(|square| square == turn.fold_wb(Square::E1, Square::E8))
.unwrap_or(false)
}