use cbvault_chess::decode::{GameRef as Walked, MoveSink, start_as_played, walk_from};
use cbvault_chess::start::{Start, StartCache, start_board_cached};
use cbvault_format::cbh::bytes::NameBuf;
use cbvault_format::cbh::moves::GameMoves;
use cbvault_format::cbh::{Entity, GameAnnotations, GameHeaderRef};
use cbvault_format::error::Result;
use gigachess::Board;
use super::sink::GameRef;
use super::{Database, namebase::Entities};
#[derive(Clone, Debug, Default)]
pub struct MovesBuf {
moves: Vec<u16>,
keys: Vec<u64>,
}
impl MovesBuf {
pub fn with_capacity(moves: usize, keys: bool) -> MovesBuf {
MovesBuf {
moves: Vec::with_capacity(moves),
keys: if keys { Vec::with_capacity(moves + 1) } else { Vec::new() },
}
}
pub fn reserve(&mut self, moves: usize) {
self.moves.reserve(moves);
}
pub fn clear(&mut self) {
self.moves.clear();
self.keys.clear();
}
#[inline]
pub fn moves(&self) -> &[u16] {
&self.moves
}
#[inline]
pub fn keys(&self) -> &[u64] {
&self.keys
}
pub fn len(&self) -> usize {
self.moves.len()
}
pub fn is_empty(&self) -> bool {
self.moves.is_empty()
}
pub fn reserve_keys(&mut self, plies: usize) {
self.keys.reserve(plies + 1);
}
fn push_key(&mut self, key: u64) {
self.keys.push(key);
}
}
struct Line<'k> {
moves: &'k mut Vec<u16>,
keys: &'k mut Vec<u64>,
keep: bool,
main: bool,
}
impl MoveSink for Line<'_> {
#[inline]
fn play(&mut self, _board: &Board, mv: u16, main: bool) {
if main {
self.moves.push(mv);
}
self.main = main;
}
#[inline]
fn played(&mut self, board: &Board) {
if self.keep && self.main {
self.keys.push(board.zobrist());
}
}
fn branch(&mut self) {}
fn resume(&mut self) {}
fn wants_zobrist(&self) -> bool {
self.keep
}
}
#[derive(Debug, Default)]
pub struct GameBuf {
line: MovesBuf,
white: String,
black: String,
event: String,
site: String,
annotator: String,
source: String,
fen: String,
last: NameBuf,
first: NameBuf,
plain: NameBuf,
starts: StartCache,
annotation: Vec<u8>,
want_keys: bool,
want_annotations: bool,
}
pub const DEFAULT_PLY_ROOM: usize = 256;
const NAME_ROOM: usize = 80;
const FEN_ROOM: usize = 96;
impl GameBuf {
pub fn new() -> GameBuf {
GameBuf::default()
}
pub fn with_capacity(plies: usize) -> GameBuf {
let mut buf = GameBuf::default();
buf.reserve(plies);
buf
}
pub fn reserve(&mut self, plies: usize) {
self.line.reserve(plies);
for name in
[&mut self.white, &mut self.black, &mut self.event, &mut self.site, &mut self.annotator, &mut self.source]
{
name.reserve(NAME_ROOM);
}
self.fen.reserve(FEN_ROOM);
}
pub fn set_wants(&mut self, keys: bool, annotations: bool) {
self.want_keys = keys;
self.want_annotations = annotations;
if keys {
self.line.reserve_keys(DEFAULT_PLY_ROOM);
}
}
pub fn wants_annotations(&self) -> bool {
self.want_annotations
}
pub fn wants_keys(&self) -> bool {
self.want_keys
}
pub(crate) fn annotation_scratch(&mut self) -> &mut Vec<u8> {
&mut self.annotation
}
pub fn clear(&mut self) {
self.line.clear();
self.white.clear();
self.black.clear();
self.event.clear();
self.site.clear();
self.annotator.clear();
self.source.clear();
self.fen.clear();
}
pub fn moves(&self) -> &[u16] {
self.line.moves()
}
pub fn keys(&self) -> &[u64] {
self.line.keys()
}
pub fn white(&self) -> &str {
&self.white
}
pub fn black(&self) -> &str {
&self.black
}
pub fn event(&self) -> &str {
&self.event
}
pub fn site(&self) -> &str {
&self.site
}
pub fn annotator(&self) -> &str {
&self.annotator
}
pub fn source(&self) -> &str {
&self.source
}
pub fn start_fen(&self) -> Option<&str> {
(!self.fen.is_empty()).then_some(self.fen.as_str())
}
pub fn walk(&mut self, id: u32, at: u64, game: &GameMoves<'_>) -> Result<()> {
let what = Walked::at(id, at);
let start = start_as_played(what, game)?;
self.line.clear();
self.fen.clear();
if !matches!(start, Start::Standard) {
let board = start_board_cached(&start, &mut self.starts)?;
if self.want_keys {
self.line.push_key(board.zobrist());
}
self.fen.push_str(&board.to_fen());
} else if self.want_keys {
self.line.push_key(cbvault_chess::start::standard_board().zobrist());
}
let keep = self.want_keys;
let MovesBuf { moves, keys } = &mut self.line;
let mut line = Line { moves, keys, keep, main: true };
walk_from(what, game, &start, &mut line).map(|_| ())
}
pub fn view<'a>(
&'a mut self,
id: u32,
header: GameHeaderRef<'a>,
db: &'a Database,
want_annotations: bool,
) -> Result<GameRef<'a>> {
let annotations = if want_annotations && header.annotations_offset() != 0 {
Some(db.annotations()?.of_ref(&header, db.wide(), &mut self.annotation)?)
} else {
None
};
let GameBuf { white, black, event, site, annotator, source, fen, line, .. } = self;
let start_fen = (!fen.is_empty()).then_some(fen.as_str());
Ok(game_ref(
id,
header,
Names { white, black, event, site, annotator, source },
start_fen,
&line.moves,
&line.keys,
annotations,
))
}
}
pub fn game_ref<'a>(
id: u32,
header: GameHeaderRef<'a>,
names: Names<'a>,
start_fen: Option<&'a str>,
moves: &'a [u16],
keys: &'a [u64],
annotations: Option<GameAnnotations<'a>>,
) -> GameRef<'a> {
GameRef {
id,
header,
white: names.white,
black: names.black,
event: names.event,
site: names.site,
annotator: names.annotator,
source: names.source,
start_fen,
moves,
keys,
annotations,
}
}
#[derive(Clone, Copy, Debug)]
pub struct Names<'a> {
pub white: &'a str,
pub black: &'a str,
pub event: &'a str,
pub site: &'a str,
pub annotator: &'a str,
pub source: &'a str,
}
impl GameBuf {
pub fn names(&self) -> Names<'_> {
Names {
white: &self.white,
black: &self.black,
event: &self.event,
site: &self.site,
annotator: &self.annotator,
source: &self.source,
}
}
}
pub fn resolve_names(header: &GameHeaderRef<'_>, entities: &Entities, buf: &mut GameBuf) -> Result<()> {
player(entities, header.white(), &mut buf.white, &mut buf.last, &mut buf.first)?;
player(entities, header.black(), &mut buf.black, &mut buf.last, &mut buf.first)?;
let GameBuf { event, site, .. } = buf;
event.clear();
site.clear();
if let Some(name) = entities.name(Entity::Tournament, header.tournament())? {
last_buf_set(&mut buf.last, name.last(), event);
last_buf_set(&mut buf.first, name.first(), site);
}
let GameBuf { annotator, source, plain, .. } = buf;
single(entities, Entity::Annotator, header.annotator(), annotator, plain)?;
single(entities, Entity::Source, header.source(), source, plain)?;
Ok(())
}
fn last_buf_set(buf: &mut NameBuf, field: &[u8], out: &mut String) {
if field.is_empty() {
return;
}
buf.set(field);
out.push_str(buf.as_str());
}
fn player(entities: &Entities, id: u32, out: &mut String, last: &mut NameBuf, first: &mut NameBuf) -> Result<()> {
out.clear();
let Some(name) = entities.name(Entity::Player, id)? else { return Ok(()) };
last_buf_set(last, name.last(), out);
if !name.first().is_empty() {
if !out.is_empty() {
out.push_str(", ");
}
last_buf_set(first, name.first(), out);
}
Ok(())
}
fn single(entities: &Entities, entity: Entity, id: u32, out: &mut String, buf: &mut NameBuf) -> Result<()> {
out.clear();
let Some(name) = entities.name(entity, id)? else { return Ok(()) };
last_buf_set(buf, name.last(), out);
Ok(())
}