use std::io::{self, Write};
use lineprior::{BuildConfig, Observation, Outcome};
use sekirei_core::board::Board;
use sekirei_core::color::Color;
use sekirei_core::sfen::{board_to_sfen, move_to_usi};
use crate::csa::{CsaGame, GameResult};
pub fn build_book(
games: &[CsaGame],
max_ply: usize,
min_count: u64,
out: &mut impl Write,
) -> io::Result<()> {
let mut observations = Vec::new();
for (game_idx, game) in games.iter().enumerate() {
let mut board = Board::startpos();
for (ply, &mv) in game.moves.iter().enumerate() {
if ply >= max_ply {
break;
}
let mover = if ply % 2 == 0 {
Color::Black
} else {
Color::White
};
let (outcome, score) = match (mover, game.result) {
(Color::Black, GameResult::BlackWin) | (Color::White, GameResult::WhiteWin) => {
(Outcome::Success, Some(1.0))
}
(Color::Black, GameResult::WhiteWin) | (Color::White, GameResult::BlackWin) => {
(Outcome::Failure, Some(0.0))
}
(_, GameResult::Draw) => (Outcome::Draw, Some(0.5)),
(_, GameResult::Unknown) => (Outcome::Unknown, None),
};
observations.push(Observation {
sequence_id: format!("game{game_idx:06}"),
step: ply as u32,
state: board_to_sfen(&board),
action: move_to_usi(mv),
outcome,
score,
weight: 1.0,
tags: vec!["opening".to_string()],
observed_at_unix_seconds: None,
source: None,
});
board.do_move(mv);
}
}
let seen = observations.len();
let config = BuildConfig {
min_count,
max_actions_per_state: Some(6),
smoothing_alpha: 5.0,
..Default::default()
};
let book = match lineprior::build_prior_book(&observations, &config) {
Ok(b) => b,
Err(e) => {
eprintln!("book: lineprior build failed: {e}");
return Err(io::Error::other(e.to_string()));
}
};
let kept = book.entries.len();
if let Err(e) = lineprior::save_prior_book(&book, &mut *out) {
eprintln!("book: failed to write prior book: {e}");
return Err(io::Error::other(e.to_string()));
}
eprintln!(
"book: {kept} positions kept (of {seen} observations, min_count={min_count}, max_ply={max_ply})"
);
Ok(())
}