use std::path::PathBuf;
use std::sync::Mutex;
use pyo3::prelude::*;
use crate::polyglot::{self, Book, Builder, Entry, Raw};
use super::board::{PyGame, PyMove, PyPosition, PyVariant};
#[pyclass(
frozen,
eq,
hash,
from_py_object,
module = "esca.polyglot",
name = "PolyglotRaw"
)]
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct PyRaw {
inner: Raw,
}
#[pymethods]
impl PyRaw {
#[new]
#[pyo3(signature = (key, bits, weight = 1, learn = 0))]
fn py_new(key: u64, bits: u16, weight: u16, learn: u32) -> PyRaw {
PyRaw {
inner: Raw {
key,
mv: bits,
weight,
learn,
},
}
}
#[getter]
fn key(&self) -> u64 {
self.inner.key
}
#[getter]
fn bits(&self) -> u16 {
self.inner.mv
}
#[getter]
fn uci(&self) -> Option<String> {
self.inner.uci()
}
#[getter]
fn weight(&self) -> u16 {
self.inner.weight
}
#[getter]
fn learn(&self) -> u32 {
self.inner.learn
}
#[pyo3(signature = (position, *, variant = None))]
fn decode(&self, position: &PyPosition, variant: Option<PyVariant>) -> Option<PyEntry> {
let variant = variant.unwrap_or_else(super::default_variant);
self.inner
.decode(variant.rules(), &position.inner)
.map(PyEntry::new)
}
fn __repr__(&self) -> String {
format!(
"<PolyglotRaw {:016x} {} weight {}>",
self.inner.key,
self.inner.uci().unwrap_or_else(|| "?".to_string()),
self.inner.weight
)
}
}
#[pyclass(
frozen,
eq,
hash,
from_py_object,
module = "esca.polyglot",
name = "PolyglotEntry"
)]
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct PyEntry {
inner: Entry,
}
impl PyEntry {
fn new(inner: Entry) -> PyEntry {
PyEntry { inner }
}
}
#[pymethods]
impl PyEntry {
#[new]
#[pyo3(signature = (key, mv, weight = 1, learn = 0))]
fn py_new(key: u64, mv: &PyMove, weight: u16, learn: u32) -> PyEntry {
PyEntry::new(Entry::new(key, mv.inner, weight, learn))
}
#[getter]
fn key(&self) -> u64 {
self.inner.key
}
#[getter]
#[pyo3(name = "move")]
fn get_move(&self) -> PyMove {
PyMove::new(self.inner.mv)
}
#[getter]
fn bits(&self) -> u16 {
Raw::from(self.inner).mv
}
#[getter]
fn weight(&self) -> u16 {
self.inner.weight
}
#[getter]
fn learn(&self) -> u32 {
self.inner.learn
}
fn __repr__(&self) -> String {
format!(
"<PolyglotEntry {:016x} {} weight {}>",
self.inner.key, self.inner.mv, self.inner.weight
)
}
}
#[pyclass(frozen, module = "esca.polyglot", name = "PolyglotBook")]
pub struct PyBook {
inner: Book,
}
#[pymethods]
impl PyBook {
#[new]
fn py_new(path: PathBuf) -> PyResult<PyBook> {
Ok(PyBook {
inner: Book::open(&path)?,
})
}
#[staticmethod]
fn from_bytes(data: Vec<u8>) -> PyResult<PyBook> {
Ok(PyBook {
inner: Book::from_bytes(data)?,
})
}
#[staticmethod]
fn write(path: PathBuf, entries: Vec<PyEntry>) -> PyResult<()> {
let entries: Vec<Entry> = entries.into_iter().map(|entry| entry.inner).collect();
Book::write(&path, &entries)?;
Ok(())
}
fn get(&self, index: usize) -> Option<PyRaw> {
self.inner.get(index).map(|inner| PyRaw { inner })
}
fn raw_entries(&self, key: u64) -> Vec<PyRaw> {
self.inner
.raw_entries(key)
.into_iter()
.map(|inner| PyRaw { inner })
.collect()
}
#[pyo3(signature = (position, *, variant = None))]
fn entries(&self, position: &PyPosition, variant: Option<PyVariant>) -> Vec<PyEntry> {
let variant = variant.unwrap_or_else(super::default_variant);
self.inner
.entries(variant.rules(), &position.inner)
.into_iter()
.map(PyEntry::new)
.collect()
}
#[pyo3(signature = (position, *, variant = None))]
fn best(&self, position: &PyPosition, variant: Option<PyVariant>) -> Option<PyEntry> {
let variant = variant.unwrap_or_else(super::default_variant);
self.inner
.best(variant.rules(), &position.inner)
.map(PyEntry::new)
}
#[pyo3(signature = (position, seed, *, variant = None))]
fn pick(
&self,
position: &PyPosition,
seed: u64,
variant: Option<PyVariant>,
) -> Option<PyEntry> {
let variant = variant.unwrap_or_else(super::default_variant);
self.inner
.pick(variant.rules(), &position.inner, seed)
.map(PyEntry::new)
}
fn __len__(&self) -> usize {
self.inner.len()
}
fn __iter__(&self) -> PyBookIter {
PyBookIter {
entries: Mutex::new(self.inner.iter().collect::<Vec<Raw>>().into_iter()),
}
}
fn __repr__(&self) -> String {
format!("<PolyglotBook {} entries>", self.inner.len())
}
}
#[pyclass(module = "esca.polyglot", name = "PolyglotBookIter")]
pub struct PyBookIter {
entries: Mutex<std::vec::IntoIter<Raw>>,
}
#[pymethods]
impl PyBookIter {
fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> {
slf
}
fn __next__(&self) -> Option<PyRaw> {
let mut entries = self.entries.lock().expect("the iterator is not shared");
entries.next().map(|inner| PyRaw { inner })
}
fn __repr__(&self) -> String {
"<PolyglotBookIter>".to_string()
}
}
#[pyclass(module = "esca.polyglot", name = "PolyglotBuilder")]
pub struct PyBuilder {
inner: Builder,
}
#[pymethods]
impl PyBuilder {
#[new]
#[pyo3(signature = (*, max_ply = None, min_count = 1))]
fn py_new(max_ply: Option<u32>, min_count: u32) -> PyBuilder {
let mut inner = Builder::new().min_count(min_count);
if let Some(plies) = max_ply {
inner = inner.max_ply(plies);
}
PyBuilder { inner }
}
fn add_game(&mut self, game: &PyGame) {
self.inner.add_game(game.played());
}
#[cfg(feature = "pgn")]
fn add_pgn(&mut self, path: PathBuf) -> PyResult<usize> {
let file = std::fs::File::open(&path)?;
Ok(self.inner.add_pgn(std::io::BufReader::new(file)))
}
#[cfg(feature = "pgn")]
fn add_pgn_string(&mut self, text: &str) -> usize {
self.inner.add_pgn(std::io::Cursor::new(text.as_bytes()))
}
fn entries(&self) -> Vec<PyRaw> {
self.inner
.entries()
.into_iter()
.map(|inner| PyRaw { inner })
.collect()
}
fn write(&self, path: PathBuf) -> PyResult<()> {
self.inner.write(&path)?;
Ok(())
}
fn __len__(&self) -> usize {
self.inner.len()
}
fn __repr__(&self) -> String {
format!("<PolyglotBuilder {} moves>", self.inner.len())
}
}
pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> {
module.add_class::<PyRaw>()?;
module.add_class::<PyEntry>()?;
module.add_class::<PyBook>()?;
module.add_class::<PyBookIter>()?;
module.add_class::<PyBuilder>()?;
module.add("POLYGLOT_ENTRY_SIZE", polyglot::ENTRY_SIZE)?;
Ok(())
}