use pyo3::prelude::*;
use crate::facts;
use crate::game::Game;
use crate::position::Position;
use crate::types::{File, FileSet, Square, SquareSet};
use super::board::{PyMove, PySquareSet, PyVariant};
use super::convert::{files_text, role_name, side_from, square_name};
fn pair<T: Copy>(values: [T; 2]) -> (T, T) {
(values[0], values[1])
}
fn list_pair<T: Copy, const N: usize>(values: [[T; N]; 2]) -> (Vec<T>, Vec<T>) {
(values[0].to_vec(), values[1].to_vec())
}
fn count_pair<const N: usize>(values: [[u8; N]; 2]) -> (Vec<u16>, Vec<u16>) {
let list = |counts: [u8; N]| counts.iter().copied().map(u16::from).collect();
(list(values[0]), list(values[1]))
}
fn files_pair(values: [FileSet; 2]) -> (String, String) {
(files_text(values[0]), files_text(values[1]))
}
fn square_pair(values: [Square; 2]) -> (String, String) {
(square_name(values[0]), square_name(values[1]))
}
fn file_run_pair(values: [[File; 3]; 2]) -> (String, String) {
let text = |files: [File; 3]| files.iter().map(|f| f.to_char()).collect::<String>();
(text(values[0]), text(values[1]))
}
fn set_list_pair(values: [[SquareSet; 6]; 2]) -> (Vec<PySquareSet>, Vec<PySquareSet>) {
let list = |sets: [SquareSet; 6]| sets.into_iter().map(PySquareSet::new).collect();
(list(values[0]), list(values[1]))
}
type GroupReduce<'py> = PyResult<(Bound<'py, PyAny>, (Py<PyFacts>, &'static str))>;
type SideGroupReduce<'py> = PyResult<(Bound<'py, PyAny>, (Py<PyFacts>, &'static str, isize))>;
type KingDistances = ((Option<u8>, Option<u8>), (Option<u8>, Option<u8>));
fn group_reconstructor(py: Python<'_>) -> PyResult<Bound<'_, PyAny>> {
py.import("esca._esca")?.getattr("_facts_group")
}
#[pyfunction]
#[pyo3(name = "_facts_group")]
#[pyo3(signature = (facts, name, index = None))]
pub(crate) fn facts_group<'py>(
facts: &Bound<'py, PyFacts>,
name: &str,
index: Option<isize>,
) -> PyResult<Bound<'py, PyAny>> {
let group = facts.getattr(name)?;
match index {
Some(index) => group.get_item(index),
None => Ok(group),
}
}
#[pyclass(frozen, module = "esca", name = "PlacementFacts")]
pub struct PyPlacementFacts {
parent: Py<PyFacts>,
#[pyo3(get)]
by_role: (Vec<PySquareSet>, Vec<PySquareSet>),
}
impl PyPlacementFacts {
fn of(facts: &facts::PlacementFacts, parent: Py<PyFacts>) -> PyPlacementFacts {
PyPlacementFacts {
parent,
by_role: set_list_pair(facts.by_role),
}
}
}
#[pymethods]
impl PyPlacementFacts {
fn __repr__(&self) -> String {
"<PlacementFacts>".to_string()
}
fn __reduce__<'py>(slf: &Bound<'py, Self>) -> GroupReduce<'py> {
let py = slf.py();
Ok((
group_reconstructor(py)?,
(slf.get().parent.clone_ref(py), "placement"),
))
}
}
#[pyclass(frozen, module = "esca", name = "StateFacts")]
pub struct PyStateFacts {
parent: Py<PyFacts>,
#[pyo3(get)]
in_check: bool,
#[pyo3(get)]
double_check: bool,
#[pyo3(get)]
castle_short: (bool, bool),
#[pyo3(get)]
castle_long: (bool, bool),
#[pyo3(get)]
en_passant: Option<String>,
#[pyo3(get)]
ep_capture_legal: bool,
}
impl PyStateFacts {
fn of(facts: &facts::StateFacts, parent: Py<PyFacts>) -> PyStateFacts {
PyStateFacts {
parent,
in_check: facts.in_check,
double_check: facts.double_check,
castle_short: pair(facts.castle_short),
castle_long: pair(facts.castle_long),
en_passant: facts.en_passant.map(|file| file.to_char().to_string()),
ep_capture_legal: facts.ep_capture_legal,
}
}
}
#[pymethods]
impl PyStateFacts {
fn __repr__(&self) -> String {
format!("<StateFacts in_check={}>", self.in_check)
}
fn __reduce__<'py>(slf: &Bound<'py, Self>) -> GroupReduce<'py> {
let py = slf.py();
Ok((
group_reconstructor(py)?,
(slf.get().parent.clone_ref(py), "state"),
))
}
}
#[pyclass(frozen, module = "esca", name = "HistoryFacts")]
pub struct PyHistoryFacts {
parent: Py<PyFacts>,
#[pyo3(get)]
known: bool,
#[pyo3(get)]
halfmove_clock: u32,
#[pyo3(get)]
halfmove_known: bool,
#[pyo3(get)]
repetition_seen: bool,
#[pyo3(get)]
repetition_available: bool,
#[pyo3(get)]
captures_in_last_8: u8,
#[pyo3(get)]
checks_in_last_8: u8,
#[pyo3(get)]
quiet_plies: u32,
#[pyo3(get)]
material_trend: i32,
#[pyo3(get)]
last_move_victim: Option<String>,
#[pyo3(get)]
last_move_mover: Option<String>,
}
impl PyHistoryFacts {
fn of(facts: &facts::HistoryFacts, parent: Py<PyFacts>) -> PyHistoryFacts {
PyHistoryFacts {
parent,
known: facts.known,
halfmove_clock: facts.halfmove_clock,
halfmove_known: facts.halfmove_known,
repetition_seen: facts.repetition_seen,
repetition_available: facts.repetition_available,
captures_in_last_8: facts.captures_in_last_8,
checks_in_last_8: facts.checks_in_last_8,
quiet_plies: facts.quiet_plies,
material_trend: facts.material_trend,
last_move_victim: facts.last_move_victim.map(role_name),
last_move_mover: facts.last_move_mover.map(role_name),
}
}
}
#[pymethods]
impl PyHistoryFacts {
fn __repr__(&self) -> String {
format!("<HistoryFacts known={}>", self.known)
}
fn __reduce__<'py>(slf: &Bound<'py, Self>) -> GroupReduce<'py> {
let py = slf.py();
Ok((
group_reconstructor(py)?,
(slf.get().parent.clone_ref(py), "history"),
))
}
}
#[pyclass(frozen, module = "esca", name = "MaterialFacts")]
pub struct PyMaterialFacts {
parent: Py<PyFacts>,
#[pyo3(get)]
count: (Vec<u16>, Vec<u16>),
#[pyo3(get)]
non_pawn_value: (i32, i32),
#[pyo3(get)]
value: (i32, i32),
#[pyo3(get)]
phase: f32,
#[pyo3(get)]
both_queens: bool,
#[pyo3(get)]
pawns_only: bool,
#[pyo3(get)]
insufficient: (bool, bool),
#[pyo3(get)]
bishop_pair_imbalance: i32,
}
impl PyMaterialFacts {
fn of(facts: &facts::MaterialFacts, parent: Py<PyFacts>) -> PyMaterialFacts {
PyMaterialFacts {
parent,
count: count_pair(facts.count),
non_pawn_value: pair(facts.non_pawn_value),
value: pair(facts.value),
phase: facts.phase,
both_queens: facts.both_queens,
pawns_only: facts.pawns_only,
insufficient: pair(facts.insufficient),
bishop_pair_imbalance: facts.bishop_pair_imbalance,
}
}
}
#[pymethods]
impl PyMaterialFacts {
fn __repr__(&self) -> String {
format!(
"<MaterialFacts value={:?} phase={:.2}>",
self.value, self.phase
)
}
fn __reduce__<'py>(slf: &Bound<'py, Self>) -> GroupReduce<'py> {
let py = slf.py();
Ok((
group_reconstructor(py)?,
(slf.get().parent.clone_ref(py), "material"),
))
}
}
#[pyclass(frozen, module = "esca", name = "PawnFacts")]
pub struct PyPawnFacts {
parent: Py<PyFacts>,
#[pyo3(get)]
pawns: (PySquareSet, PySquareSet),
#[pyo3(get)]
passed: (PySquareSet, PySquareSet),
#[pyo3(get)]
candidates: (PySquareSet, PySquareSet),
#[pyo3(get)]
doubled: (PySquareSet, PySquareSet),
#[pyo3(get)]
isolated: (PySquareSet, PySquareSet),
#[pyo3(get)]
backward: (PySquareSet, PySquareSet),
#[pyo3(get)]
defended: (PySquareSet, PySquareSet),
#[pyo3(get)]
count_by_file: (Vec<u16>, Vec<u16>),
#[pyo3(get)]
count_by_rank: (Vec<u16>, Vec<u16>),
#[pyo3(get)]
open_files: String,
#[pyo3(get)]
semi_open_files: (String, String),
#[pyo3(get)]
islands: (u8, u8),
#[pyo3(get)]
levers: (u8, u8),
#[pyo3(get)]
rams: u8,
#[pyo3(get)]
passer_lead_rank: (Option<u8>, Option<u8>),
#[pyo3(get)]
passer_protected: (u8, u8),
#[pyo3(get)]
passers_connected: (bool, bool),
#[pyo3(get)]
passer_unstoppable: (bool, bool),
#[pyo3(get)]
chain_max_length: (u8, u8),
#[pyo3(get)]
chain_base_attacked: (bool, bool),
#[pyo3(get)]
majority_by_wing: ((bool, bool), (bool, bool)),
#[pyo3(get)]
holes: (PySquareSet, PySquareSet),
#[pyo3(get)]
holes_occupied: (u8, u8),
#[pyo3(get)]
fixed_pawns: (u8, u8),
#[pyo3(get)]
blocked_passers: (u8, u8),
#[pyo3(get)]
passer_distance: (Option<u8>, Option<u8>),
#[pyo3(get)]
passer_king_distance: KingDistances,
#[pyo3(get)]
passer_in_square: (bool, bool),
#[pyo3(get)]
passer_free_path: (u8, u8),
#[pyo3(get)]
half_open_at_enemy_king: (u8, u8),
#[pyo3(get)]
backward_on_semi_open: (u8, u8),
}
impl PyPawnFacts {
fn of(facts: &facts::PawnFacts, parent: Py<PyFacts>) -> PyPawnFacts {
PyPawnFacts {
parent,
pawns: PySquareSet::pair(facts.pawns),
passed: PySquareSet::pair(facts.passed),
candidates: PySquareSet::pair(facts.candidates),
doubled: PySquareSet::pair(facts.doubled),
isolated: PySquareSet::pair(facts.isolated),
backward: PySquareSet::pair(facts.backward),
defended: PySquareSet::pair(facts.defended),
count_by_file: count_pair(facts.count_by_file),
count_by_rank: count_pair(facts.count_by_rank),
open_files: files_text(facts.open_files),
semi_open_files: files_pair(facts.semi_open_files),
islands: pair(facts.islands),
levers: pair(facts.levers),
rams: facts.rams,
passer_lead_rank: pair(facts.passer_lead_rank),
passer_protected: pair(facts.passer_protected),
passers_connected: pair(facts.passers_connected),
passer_unstoppable: pair(facts.passer_unstoppable),
chain_max_length: pair(facts.chain_max_length),
chain_base_attacked: pair(facts.chain_base_attacked),
majority_by_wing: pair(facts.majority_by_wing.map(pair)),
holes: PySquareSet::pair(facts.holes),
holes_occupied: pair(facts.holes_occupied),
fixed_pawns: pair(facts.fixed_pawns),
blocked_passers: pair(facts.blocked_passers),
passer_distance: pair(facts.passer_distance),
passer_king_distance: pair(facts.passer_king_distance.map(pair)),
passer_in_square: pair(facts.passer_in_square),
passer_free_path: pair(facts.passer_free_path),
half_open_at_enemy_king: pair(facts.half_open_at_enemy_king),
backward_on_semi_open: pair(facts.backward_on_semi_open),
}
}
}
#[pymethods]
impl PyPawnFacts {
fn __repr__(&self) -> String {
format!("<PawnFacts islands={:?} rams={}>", self.islands, self.rams)
}
fn __reduce__<'py>(slf: &Bound<'py, Self>) -> GroupReduce<'py> {
let py = slf.py();
Ok((
group_reconstructor(py)?,
(slf.get().parent.clone_ref(py), "pawns"),
))
}
}
#[pyclass(frozen, module = "esca", name = "PieceFacts")]
pub struct PyPieceFacts {
parent: Py<PyFacts>,
#[pyo3(get)]
bishop_pair: (bool, bool),
#[pyo3(get)]
bishops_light: (u8, u8),
#[pyo3(get)]
bishops_dark: (u8, u8),
#[pyo3(get)]
opposite_coloured_bishops: bool,
#[pyo3(get)]
pawns_on_bishop_colour: (u8, u8),
#[pyo3(get)]
rooks_connected_rank: (bool, bool),
#[pyo3(get)]
rooks_connected_file: (bool, bool),
#[pyo3(get)]
rooks_on_open_file: (u8, u8),
#[pyo3(get)]
rooks_on_semi_open_file: (u8, u8),
#[pyo3(get)]
rooks_on_relative_7th: (u8, u8),
#[pyo3(get)]
rook_behind_own_passer: (u8, u8),
#[pyo3(get)]
rook_behind_enemy_passer: (u8, u8),
#[pyo3(get)]
trapped_rook: (bool, bool),
#[pyo3(get)]
outposts: (PySquareSet, PySquareSet),
#[pyo3(get)]
minors_on_outpost: (u8, u8),
#[pyo3(get)]
outpost_squares_free: (u8, u8),
#[pyo3(get)]
knights_on_rim: (u8, u8),
#[pyo3(get)]
minors_undeveloped: (u8, u8),
#[pyo3(get)]
queen_developed: (bool, bool),
#[pyo3(get)]
fixed_pawns_on_bishop_colour: (u8, u8),
#[pyo3(get)]
bishop_pair_vs_knight_pair: i8,
#[pyo3(get)]
rook_on_7th_with_king_on_8th: (bool, bool),
#[pyo3(get)]
trapped_pieces: (u8, u8),
#[pyo3(get)]
trapped_value: (u8, u8),
}
impl PyPieceFacts {
fn of(facts: &facts::PieceFacts, parent: Py<PyFacts>) -> PyPieceFacts {
PyPieceFacts {
parent,
bishop_pair: pair(facts.bishop_pair),
bishops_light: pair(facts.bishops_light),
bishops_dark: pair(facts.bishops_dark),
opposite_coloured_bishops: facts.opposite_coloured_bishops,
pawns_on_bishop_colour: pair(facts.pawns_on_bishop_colour),
rooks_connected_rank: pair(facts.rooks_connected_rank),
rooks_connected_file: pair(facts.rooks_connected_file),
rooks_on_open_file: pair(facts.rooks_on_open_file),
rooks_on_semi_open_file: pair(facts.rooks_on_semi_open_file),
rooks_on_relative_7th: pair(facts.rooks_on_relative_7th),
rook_behind_own_passer: pair(facts.rook_behind_own_passer),
rook_behind_enemy_passer: pair(facts.rook_behind_enemy_passer),
trapped_rook: pair(facts.trapped_rook),
outposts: PySquareSet::pair(facts.outposts),
minors_on_outpost: pair(facts.minors_on_outpost),
outpost_squares_free: pair(facts.outpost_squares_free),
knights_on_rim: pair(facts.knights_on_rim),
minors_undeveloped: pair(facts.minors_undeveloped),
queen_developed: pair(facts.queen_developed),
fixed_pawns_on_bishop_colour: pair(facts.fixed_pawns_on_bishop_colour),
bishop_pair_vs_knight_pair: facts.bishop_pair_vs_knight_pair,
rook_on_7th_with_king_on_8th: pair(facts.rook_on_7th_with_king_on_8th),
trapped_pieces: pair(facts.trapped_pieces),
trapped_value: pair(facts.trapped_value),
}
}
}
#[pymethods]
impl PyPieceFacts {
fn __repr__(&self) -> String {
format!("<PieceFacts bishop_pair={:?}>", self.bishop_pair)
}
fn __reduce__<'py>(slf: &Bound<'py, Self>) -> GroupReduce<'py> {
let py = slf.py();
Ok((
group_reconstructor(py)?,
(slf.get().parent.clone_ref(py), "pieces"),
))
}
}
#[pyclass(frozen, module = "esca", name = "KingFacts")]
pub struct PyKingFacts {
parent: Py<PyFacts>,
#[pyo3(get)]
square: (String, String),
#[pyo3(get)]
on_home_square: (bool, bool),
#[pyo3(get)]
castled_queenside: (bool, bool),
#[pyo3(get)]
castled_kingside: (bool, bool),
#[pyo3(get)]
shield_files: (String, String),
#[pyo3(get)]
shield: (Vec<Option<u8>>, Vec<Option<u8>>),
#[pyo3(get)]
file_open: (Vec<bool>, Vec<bool>),
#[pyo3(get)]
file_semi_open_for_enemy: (Vec<bool>, Vec<bool>),
#[pyo3(get)]
storm: (Vec<Option<u8>>, Vec<Option<u8>>),
#[pyo3(get)]
ring: (PySquareSet, PySquareSet),
#[pyo3(get)]
ring_attackers: (u8, u8),
#[pyo3(get)]
ring_attack_weight: (u8, u8),
#[pyo3(get)]
ring_defended: (u8, u8),
#[pyo3(get)]
ring_holes: (u8, u8),
#[pyo3(get)]
escape_squares: (u8, u8),
#[pyo3(get)]
back_rank_risk: (bool, bool),
#[pyo3(get)]
distance: u8,
#[pyo3(get)]
tropism: (f32, f32),
#[pyo3(get)]
virtual_mobility: (u8, u8),
#[pyo3(get)]
ring_defenders: (u8, u8),
#[pyo3(get)]
ring_defence_weight: (u8, u8),
#[pyo3(get)]
open_rays: (u8, u8),
#[pyo3(get)]
luft: (bool, bool),
#[pyo3(get)]
castled_side: (Option<String>, Option<String>),
#[pyo3(get)]
opposite_side_castling: bool,
}
impl PyKingFacts {
fn of(facts: &facts::KingFacts, parent: Py<PyFacts>) -> PyKingFacts {
PyKingFacts {
parent,
square: square_pair(facts.square),
on_home_square: pair(facts.on_home_square),
castled_queenside: pair(facts.castled_queenside),
castled_kingside: pair(facts.castled_kingside),
shield_files: file_run_pair(facts.shield_files),
shield: list_pair(facts.shield),
file_open: list_pair(facts.file_open),
file_semi_open_for_enemy: list_pair(facts.file_semi_open_for_enemy),
storm: list_pair(facts.storm),
ring: PySquareSet::pair(facts.ring),
ring_attackers: pair(facts.ring_attackers),
ring_attack_weight: pair(facts.ring_attack_weight),
ring_defended: pair(facts.ring_defended),
ring_holes: pair(facts.ring_holes),
escape_squares: pair(facts.escape_squares),
back_rank_risk: pair(facts.back_rank_risk),
distance: facts.distance,
tropism: pair(facts.tropism),
virtual_mobility: pair(facts.virtual_mobility),
ring_defenders: pair(facts.ring_defenders),
ring_defence_weight: pair(facts.ring_defence_weight),
open_rays: pair(facts.open_rays),
luft: pair(facts.luft),
castled_side: (
facts.castled_side[0].map(castled_side_name),
facts.castled_side[1].map(castled_side_name),
),
opposite_side_castling: facts.opposite_side_castling,
}
}
}
fn castled_side_name(side: facts::CastledSide) -> String {
match side {
facts::CastledSide::Short => "short",
facts::CastledSide::Long => "long",
}
.to_string()
}
#[pymethods]
impl PyKingFacts {
#[getter]
fn ring_attacker_surplus(&self) -> (i32, i32) {
(
i32::from(self.ring_attack_weight.0) - i32::from(self.ring_defence_weight.0),
i32::from(self.ring_attack_weight.1) - i32::from(self.ring_defence_weight.1),
)
}
fn __repr__(&self) -> String {
format!("<KingFacts square={:?}>", self.square)
}
fn __reduce__<'py>(slf: &Bound<'py, Self>) -> GroupReduce<'py> {
let py = slf.py();
Ok((
group_reconstructor(py)?,
(slf.get().parent.clone_ref(py), "king"),
))
}
}
#[pyclass(frozen, module = "esca", name = "MobilityFacts")]
pub struct PyMobilityFacts {
parent: Py<PyFacts>,
#[pyo3(get)]
by_role: (Vec<u16>, Vec<u16>),
#[pyo3(get)]
safe_by_role: (Vec<u16>, Vec<u16>),
#[pyo3(get)]
total: (u16, u16),
#[pyo3(get)]
space: (u16, u16),
#[pyo3(get)]
controlled: (u16, u16),
#[pyo3(get)]
centre_control: (u8, u8),
#[pyo3(get)]
extended_centre_control: (u8, u8),
#[pyo3(get)]
immobile_pieces: (u8, u8),
}
impl PyMobilityFacts {
fn of(facts: &facts::MobilityFacts, parent: Py<PyFacts>) -> PyMobilityFacts {
PyMobilityFacts {
parent,
by_role: list_pair(facts.by_role),
safe_by_role: list_pair(facts.safe_by_role),
total: pair(facts.total),
space: pair(facts.space),
controlled: pair(facts.controlled),
centre_control: pair(facts.centre_control),
extended_centre_control: pair(facts.extended_centre_control),
immobile_pieces: pair(facts.immobile_pieces),
}
}
}
#[pymethods]
impl PyMobilityFacts {
fn __repr__(&self) -> String {
format!("<MobilityFacts total={:?}>", self.total)
}
fn __reduce__<'py>(slf: &Bound<'py, Self>) -> GroupReduce<'py> {
let py = slf.py();
Ok((
group_reconstructor(py)?,
(slf.get().parent.clone_ref(py), "mobility"),
))
}
}
#[pyclass(frozen, module = "esca", name = "AttackFacts")]
pub struct PyAttackFacts {
parent: Py<PyFacts>,
inner: facts::AttackFacts,
#[pyo3(get)]
by: (PySquareSet, PySquareSet),
#[pyo3(get)]
by_pawns: (PySquareSet, PySquareSet),
#[pyo3(get)]
by_role: (Vec<PySquareSet>, Vec<PySquareSet>),
#[pyo3(get)]
attacked: (PySquareSet, PySquareSet),
#[pyo3(get)]
hanging: (PySquareSet, PySquareSet),
#[pyo3(get)]
en_prise: (PySquareSet, PySquareSet),
#[pyo3(get)]
pinned: (PySquareSet, PySquareSet),
#[pyo3(get)]
defended: (PySquareSet, PySquareSet),
#[pyo3(get)]
attacked_value: (i32, i32),
#[pyo3(get)]
hanging_value: (i32, i32),
#[pyo3(get)]
en_prise_value: (i32, i32),
#[pyo3(get)]
en_prise_max_value: (i32, i32),
#[pyo3(get)]
pinned_value: (i32, i32),
#[pyo3(get)]
skewer_candidates: (u8, u8),
}
impl PyAttackFacts {
fn of(facts: &facts::AttackFacts, parent: Py<PyFacts>) -> PyAttackFacts {
PyAttackFacts {
parent,
inner: *facts,
by: PySquareSet::pair(facts.by),
by_pawns: PySquareSet::pair(facts.by_pawns),
by_role: set_list_pair(facts.by_role),
attacked: PySquareSet::pair(facts.attacked),
hanging: PySquareSet::pair(facts.hanging),
en_prise: PySquareSet::pair(facts.en_prise),
pinned: PySquareSet::pair(facts.pinned),
defended: PySquareSet::pair(facts.defended),
attacked_value: pair(facts.attacked_value),
hanging_value: pair(facts.hanging_value),
en_prise_value: pair(facts.en_prise_value),
en_prise_max_value: pair(facts.en_prise_max_value),
pinned_value: pair(facts.pinned_value),
skewer_candidates: pair(facts.skewer_candidates),
}
}
}
#[pymethods]
impl PyAttackFacts {
fn attackers_of(&self, square: &str, side: isize) -> PyResult<PySquareSet> {
let square = super::convert::square_from(square)?;
Ok(PySquareSet::new(
self.inner.attackers_of(square, side_from(side)?),
))
}
fn is_hanging(&self, square: &str) -> PyResult<bool> {
Ok(self.inner.is_hanging(super::convert::square_from(square)?))
}
fn units(&self, side: isize) -> PyResult<PySquareSet> {
Ok(PySquareSet::new(self.inner.units(side_from(side)?)))
}
fn __repr__(&self) -> String {
format!("<AttackFacts hanging_value={:?}>", self.hanging_value)
}
fn __reduce__<'py>(slf: &Bound<'py, Self>) -> GroupReduce<'py> {
let py = slf.py();
Ok((
group_reconstructor(py)?,
(slf.get().parent.clone_ref(py), "attacks"),
))
}
}
#[pyclass(frozen, module = "esca", name = "ExchangeFacts")]
pub struct PyExchangeFacts {
parent: Py<PyFacts>,
side: isize,
#[pyo3(get)]
see_best_capture: i32,
#[pyo3(get)]
see_positive_capture_count: u16,
#[pyo3(get)]
see_equal_capture_count: u16,
#[pyo3(get)]
see_positive_total: i32,
}
impl PyExchangeFacts {
fn of(facts: &facts::ExchangeFacts, parent: Py<PyFacts>, side: isize) -> PyExchangeFacts {
PyExchangeFacts {
parent,
side,
see_best_capture: facts.see_best_capture,
see_positive_capture_count: facts.see_positive_capture_count,
see_equal_capture_count: facts.see_equal_capture_count,
see_positive_total: facts.see_positive_total,
}
}
}
#[pymethods]
impl PyExchangeFacts {
fn __repr__(&self) -> String {
format!("<ExchangeFacts see_best_capture={}>", self.see_best_capture)
}
fn __reduce__<'py>(slf: &Bound<'py, Self>) -> SideGroupReduce<'py> {
let py = slf.py();
Ok((
group_reconstructor(py)?,
(slf.get().parent.clone_ref(py), "exchange", slf.get().side),
))
}
}
#[pyclass(frozen, module = "esca", name = "ThreatFacts")]
pub struct PyThreatFacts {
parent: Py<PyFacts>,
#[pyo3(get)]
threatened: (PySquareSet, PySquareSet),
#[pyo3(get)]
threatened_value: (i32, i32),
#[pyo3(get)]
threat_max_gain: (i32, i32),
#[pyo3(get)]
attacked_by_lesser: (PySquareSet, PySquareSet),
#[pyo3(get)]
queen_attacked_by_lesser: (bool, bool),
#[pyo3(get)]
overloaded_defenders: (PySquareSet, PySquareSet),
#[pyo3(get)]
removable_defenders: (PySquareSet, PySquareSet),
#[pyo3(get)]
loose: (PySquareSet, PySquareSet),
#[pyo3(get)]
attacker_surplus: (PySquareSet, PySquareSet),
#[pyo3(get)]
xray_through_enemy: (u8, u8),
#[pyo3(get)]
battery_count: (u8, u8),
#[pyo3(get)]
battery_at_king: (bool, bool),
}
impl PyThreatFacts {
fn of(facts: &facts::ThreatFacts, parent: Py<PyFacts>) -> PyThreatFacts {
PyThreatFacts {
parent,
threatened: PySquareSet::pair(facts.threatened),
threatened_value: pair(facts.threatened_value),
threat_max_gain: pair(facts.threat_max_gain),
attacked_by_lesser: PySquareSet::pair(facts.attacked_by_lesser),
queen_attacked_by_lesser: pair(facts.queen_attacked_by_lesser),
overloaded_defenders: PySquareSet::pair(facts.overloaded_defenders),
removable_defenders: PySquareSet::pair(facts.removable_defenders),
loose: PySquareSet::pair(facts.loose),
attacker_surplus: PySquareSet::pair(facts.attacker_surplus),
xray_through_enemy: pair(facts.xray_through_enemy),
battery_count: pair(facts.battery_count),
battery_at_king: pair(facts.battery_at_king),
}
}
}
#[pymethods]
impl PyThreatFacts {
fn __repr__(&self) -> String {
format!("<ThreatFacts threatened_value={:?}>", self.threatened_value)
}
fn __reduce__<'py>(slf: &Bound<'py, Self>) -> GroupReduce<'py> {
let py = slf.py();
Ok((
group_reconstructor(py)?,
(slf.get().parent.clone_ref(py), "threats"),
))
}
}
#[pyclass(frozen, module = "esca", name = "TacticsFacts")]
pub struct PyTacticsFacts {
parent: Py<PyFacts>,
side: isize,
#[pyo3(get)]
available: bool,
#[pyo3(get)]
check_count: u16,
#[pyo3(get)]
check_by_role: Vec<bool>,
#[pyo3(get)]
safe_check_count: u16,
#[pyo3(get)]
safe_check_by_role: Vec<bool>,
#[pyo3(get)]
double_check_available: bool,
#[pyo3(get)]
discovered_check_available: bool,
#[pyo3(get)]
mate_in_1: bool,
#[pyo3(get)]
stalemate_in_1: bool,
#[pyo3(get)]
promotion_files: String,
#[pyo3(get)]
promotion_roles: Vec<bool>,
#[pyo3(get)]
safe_promotion_files: String,
#[pyo3(get)]
capture_count: u16,
#[pyo3(get)]
winning_capture_available: bool,
#[pyo3(get)]
winning_capture_max_gain: i32,
#[pyo3(get)]
captures_hanging: bool,
#[pyo3(get)]
hanging_victim_max_value: i32,
#[pyo3(get)]
equal_capture_count: u16,
#[pyo3(get)]
losing_capture_count: u16,
#[pyo3(get)]
fork_count: u16,
#[pyo3(get)]
fork_max_value: i32,
#[pyo3(get)]
knight_fork_available: bool,
#[pyo3(get)]
royal_fork_available: bool,
#[pyo3(get)]
pin_creation_count: u16,
#[pyo3(get)]
skewer_creation_available: bool,
#[pyo3(get)]
discovered_attack_available: bool,
#[pyo3(get)]
legal_move_count: u16,
#[pyo3(get)]
safe_check_capturing: bool,
#[pyo3(get)]
discovered_attack_on_queen: bool,
#[pyo3(get)]
back_rank_mate_threat: bool,
#[pyo3(get)]
quiet_threat_available: bool,
#[pyo3(get)]
no_safe_moves: bool,
#[pyo3(get)]
promotion_see_positive: bool,
}
impl PyTacticsFacts {
fn of(facts: &facts::TacticsFacts, parent: Py<PyFacts>, side: isize) -> PyTacticsFacts {
PyTacticsFacts {
parent,
side,
available: facts.available,
check_count: facts.check_count,
check_by_role: facts.check_by_role.to_vec(),
safe_check_count: facts.safe_check_count,
safe_check_by_role: facts.safe_check_by_role.to_vec(),
double_check_available: facts.double_check_available,
discovered_check_available: facts.discovered_check_available,
mate_in_1: facts.mate_in_1,
stalemate_in_1: facts.stalemate_in_1,
promotion_files: files_text(facts.promotion_files),
promotion_roles: facts.promotion_roles.to_vec(),
safe_promotion_files: files_text(facts.safe_promotion_files),
capture_count: facts.capture_count,
winning_capture_available: facts.winning_capture_available,
winning_capture_max_gain: facts.winning_capture_max_gain,
captures_hanging: facts.captures_hanging,
hanging_victim_max_value: facts.hanging_victim_max_value,
equal_capture_count: facts.equal_capture_count,
losing_capture_count: facts.losing_capture_count,
fork_count: facts.fork_count,
fork_max_value: facts.fork_max_value,
knight_fork_available: facts.knight_fork_available,
royal_fork_available: facts.royal_fork_available,
pin_creation_count: facts.pin_creation_count,
skewer_creation_available: facts.skewer_creation_available,
discovered_attack_available: facts.discovered_attack_available,
legal_move_count: facts.legal_move_count,
safe_check_capturing: facts.safe_check_capturing,
discovered_attack_on_queen: facts.discovered_attack_on_queen,
back_rank_mate_threat: facts.back_rank_mate_threat,
quiet_threat_available: facts.quiet_threat_available,
no_safe_moves: facts.no_safe_moves,
promotion_see_positive: facts.promotion_see_positive,
}
}
}
#[pymethods]
impl PyTacticsFacts {
#[getter]
fn check_available(&self) -> bool {
self.check_count > 0
}
#[getter]
fn safe_check_available(&self) -> bool {
self.safe_check_count > 0
}
#[getter]
fn promotion_available(&self) -> bool {
!self.promotion_files.is_empty()
}
#[getter]
fn safe_promotion_available(&self) -> bool {
!self.safe_promotion_files.is_empty()
}
#[getter]
fn capture_available(&self) -> bool {
self.capture_count > 0
}
#[getter]
fn fork_available(&self) -> bool {
self.fork_count > 0
}
#[getter]
fn pin_creation_available(&self) -> bool {
self.pin_creation_count > 0
}
#[getter]
fn only_moves(&self) -> bool {
self.available && self.legal_move_count <= 2
}
fn __repr__(&self) -> String {
format!("<TacticsFacts legal_move_count={}>", self.legal_move_count)
}
fn __reduce__<'py>(slf: &Bound<'py, Self>) -> SideGroupReduce<'py> {
let py = slf.py();
Ok((
group_reconstructor(py)?,
(slf.get().parent.clone_ref(py), "tactics", slf.get().side),
))
}
}
#[pyclass(frozen, module = "esca", name = "EndgameFacts")]
pub struct PyEndgameFacts {
parent: Py<PyFacts>,
#[pyo3(get)]
king_centralisation: (u8, u8),
#[pyo3(get)]
race_plies: (u8, u8),
#[pyo3(get)]
opposition: Option<String>,
#[pyo3(get)]
key_square_occupied: (bool, bool),
#[pyo3(get)]
wrong_colour_bishop: (bool, bool),
#[pyo3(get)]
drawish_material: Option<String>,
}
impl PyEndgameFacts {
fn of(facts: &facts::EndgameFacts, parent: Py<PyFacts>) -> PyEndgameFacts {
PyEndgameFacts {
parent,
king_centralisation: pair(facts.king_centralisation),
race_plies: pair(facts.race_plies),
opposition: facts.opposition.map(opposition_name),
key_square_occupied: pair(facts.key_square_occupied),
wrong_colour_bishop: pair(facts.wrong_colour_bishop),
drawish_material: facts.drawish_material.map(drawish_material_name),
}
}
}
#[pymethods]
impl PyEndgameFacts {
#[getter]
fn race_plies_diff(&self) -> i32 {
i32::from(self.race_plies.0) - i32::from(self.race_plies.1)
}
fn __repr__(&self) -> String {
format!("<EndgameFacts race_plies={:?}>", self.race_plies)
}
fn __reduce__<'py>(slf: &Bound<'py, Self>) -> GroupReduce<'py> {
let py = slf.py();
Ok((
group_reconstructor(py)?,
(slf.get().parent.clone_ref(py), "endgame"),
))
}
}
fn opposition_name(kind: facts::Opposition) -> String {
match kind {
facts::Opposition::Direct => "direct",
facts::Opposition::Distant => "distant",
}
.to_string()
}
fn drawish_material_name(kind: facts::DrawishMaterial) -> String {
match kind {
facts::DrawishMaterial::TwoKnights => "two_knights",
facts::DrawishMaterial::WrongBishop => "wrong_bishop",
facts::DrawishMaterial::OppositeBishops => "opposite_bishops",
}
.to_string()
}
#[pyclass(frozen, module = "esca", name = "PlaneFacts")]
pub struct PyPlaneFacts {
parent: Py<PyFacts>,
#[pyo3(get)]
attacked: (PySquareSet, PySquareSet),
#[pyo3(get)]
attacked_by_pawns: (PySquareSet, PySquareSet),
#[pyo3(get)]
hanging: (PySquareSet, PySquareSet),
#[pyo3(get)]
pinned: (PySquareSet, PySquareSet),
#[pyo3(get)]
threatened: (PySquareSet, PySquareSet),
}
impl PyPlaneFacts {
fn of(facts: &facts::PlaneFacts, parent: Py<PyFacts>) -> PyPlaneFacts {
PyPlaneFacts {
parent,
attacked: PySquareSet::pair(facts.attacked),
attacked_by_pawns: PySquareSet::pair(facts.attacked_by_pawns),
hanging: PySquareSet::pair(facts.hanging),
pinned: PySquareSet::pair(facts.pinned),
threatened: PySquareSet::pair(facts.threatened),
}
}
}
#[pymethods]
impl PyPlaneFacts {
fn __repr__(&self) -> String {
"<PlaneFacts>".to_string()
}
fn __reduce__<'py>(slf: &Bound<'py, Self>) -> GroupReduce<'py> {
let py = slf.py();
Ok((
group_reconstructor(py)?,
(slf.get().parent.clone_ref(py), "planes"),
))
}
}
#[pyclass(frozen, skip_from_py_object, module = "esca", name = "MoveFacts")]
#[derive(Clone)]
pub struct PyMoveFacts {
#[pyo3(get)]
victim: Option<String>,
#[pyo3(get)]
mover: String,
#[pyo3(get)]
promotion: Option<String>,
#[pyo3(get)]
gives_check: bool,
#[pyo3(get)]
gives_safe_check: bool,
#[pyo3(get)]
is_safe: bool,
#[pyo3(get)]
captures_hanging: bool,
#[pyo3(get)]
escapes_attack: bool,
#[pyo3(get)]
to_attacked_by_pawn: bool,
#[pyo3(get)]
is_castling: bool,
#[pyo3(get)]
is_en_passant: bool,
#[pyo3(get)]
see: i32,
#[pyo3(get)]
threat_created_max: i32,
#[pyo3(get)]
moves_attacked_unit: bool,
#[pyo3(get)]
blocks_check: bool,
#[pyo3(get)]
advances_passer: bool,
#[pyo3(get)]
creates_passer: bool,
#[pyo3(get)]
creates_isolated: bool,
#[pyo3(get)]
creates_doubled: bool,
#[pyo3(get)]
creates_backward: bool,
#[pyo3(get)]
opens_file_at_enemy_king: bool,
#[pyo3(get)]
our_ring_attackers_delta: i32,
#[pyo3(get)]
their_ring_attackers_delta: i32,
#[pyo3(get)]
own_hanging_delta: i32,
#[pyo3(get)]
their_hanging_delta: i32,
#[pyo3(get)]
leaves_unit_hanging: bool,
#[pyo3(get)]
gives_discovered_attack: bool,
}
impl PyMoveFacts {
fn of(facts: &facts::MoveFacts) -> PyMoveFacts {
PyMoveFacts {
victim: facts.victim.map(role_name),
mover: role_name(facts.mover),
promotion: facts.promotion.map(role_name),
gives_check: facts.gives_check,
gives_safe_check: facts.gives_safe_check,
is_safe: facts.is_safe,
captures_hanging: facts.captures_hanging,
escapes_attack: facts.escapes_attack,
to_attacked_by_pawn: facts.to_attacked_by_pawn,
is_castling: facts.is_castling,
is_en_passant: facts.is_en_passant,
see: facts.see,
threat_created_max: facts.threat_created_max,
moves_attacked_unit: facts.moves_attacked_unit,
blocks_check: facts.blocks_check,
advances_passer: facts.advances_passer,
creates_passer: facts.creates_passer,
creates_isolated: facts.creates_isolated,
creates_doubled: facts.creates_doubled,
creates_backward: facts.creates_backward,
opens_file_at_enemy_king: facts.opens_file_at_enemy_king,
our_ring_attackers_delta: facts.our_ring_attackers_delta,
their_ring_attackers_delta: facts.their_ring_attackers_delta,
own_hanging_delta: facts.own_hanging_delta,
their_hanging_delta: facts.their_hanging_delta,
leaves_unit_hanging: facts.leaves_unit_hanging,
gives_discovered_attack: facts.gives_discovered_attack,
}
}
}
#[pymethods]
impl PyMoveFacts {
fn __repr__(&self) -> String {
format!("<MoveFacts mover={}>", self.mover)
}
}
#[pyclass(frozen, module = "esca", name = "AnnotatedMove")]
pub struct PyAnnotatedMove {
inner: facts::AnnotatedMove,
}
impl PyAnnotatedMove {
pub(crate) fn new(inner: facts::AnnotatedMove) -> PyAnnotatedMove {
PyAnnotatedMove { inner }
}
}
#[pymethods]
impl PyAnnotatedMove {
#[getter]
#[pyo3(name = "move")]
fn get_move(&self) -> PyMove {
PyMove::new(self.inner.mv)
}
#[getter]
fn facts(&self) -> PyMoveFacts {
PyMoveFacts::of(&self.inner.facts)
}
fn __repr__(&self) -> String {
format!("<AnnotatedMove {}>", self.inner.mv)
}
}
#[pyclass(frozen, module = "esca", name = "Facts")]
pub struct PyFacts {
pub(crate) inner: facts::Facts,
text: String,
variant: PyVariant,
}
impl PyFacts {
pub(crate) fn of_position(position: &Position, variant: PyVariant) -> PyFacts {
let inner = position.facts(variant.rules());
PyFacts {
inner,
text: position.fen(),
variant,
}
}
pub(crate) fn of_game(game: &Game, variant: PyVariant) -> PyFacts {
PyFacts {
inner: game.facts(),
text: game.position().fen(),
variant,
}
}
}
#[pymethods]
impl PyFacts {
#[new]
#[pyo3(signature = (fen, *, variant = None))]
fn py_new(fen: &str, variant: Option<PyVariant>) -> PyResult<PyFacts> {
let variant = variant.unwrap_or_else(super::default_variant);
let position = Position::from_fen(fen).map_err(super::convert::value_error)?;
Ok(PyFacts::of_position(&position, variant))
}
#[getter]
fn variant(&self) -> PyVariant {
self.variant.clone()
}
#[getter]
fn side_to_move(&self) -> String {
super::convert::colour_name(self.inner.side_to_move())
}
fn side(&self, colour: &str) -> PyResult<usize> {
Ok(self
.inner
.side(super::convert::colour_from(colour)?)
.index())
}
#[getter]
fn position(&self) -> PyResult<super::board::PyPosition> {
Position::from_fen(&self.text)
.map(super::board::PyPosition::new)
.map_err(super::convert::value_error)
}
#[getter]
fn placement(slf: &Bound<'_, Self>) -> PyPlacementFacts {
PyPlacementFacts::of(&slf.get().inner.placement, slf.clone().unbind())
}
#[getter]
fn state(slf: &Bound<'_, Self>) -> PyStateFacts {
PyStateFacts::of(&slf.get().inner.state, slf.clone().unbind())
}
#[getter]
fn history(slf: &Bound<'_, Self>) -> PyHistoryFacts {
PyHistoryFacts::of(&slf.get().inner.history, slf.clone().unbind())
}
#[getter]
fn material(slf: &Bound<'_, Self>) -> PyMaterialFacts {
PyMaterialFacts::of(&slf.get().inner.material, slf.clone().unbind())
}
#[getter]
fn pawns(slf: &Bound<'_, Self>) -> PyPawnFacts {
PyPawnFacts::of(&slf.get().inner.pawns, slf.clone().unbind())
}
#[getter]
fn pieces(slf: &Bound<'_, Self>) -> PyPieceFacts {
PyPieceFacts::of(&slf.get().inner.pieces, slf.clone().unbind())
}
#[getter]
fn king(slf: &Bound<'_, Self>) -> PyKingFacts {
PyKingFacts::of(&slf.get().inner.king, slf.clone().unbind())
}
#[getter]
fn mobility(slf: &Bound<'_, Self>) -> PyMobilityFacts {
PyMobilityFacts::of(&slf.get().inner.mobility, slf.clone().unbind())
}
#[getter]
fn attacks(slf: &Bound<'_, Self>) -> PyAttackFacts {
PyAttackFacts::of(&slf.get().inner.attacks, slf.clone().unbind())
}
#[getter]
fn exchange(slf: &Bound<'_, Self>) -> (PyExchangeFacts, PyExchangeFacts) {
let facts = &slf.get().inner.exchange;
(
PyExchangeFacts::of(&facts[0], slf.clone().unbind(), 0),
PyExchangeFacts::of(&facts[1], slf.clone().unbind(), 1),
)
}
#[getter]
fn threats(slf: &Bound<'_, Self>) -> PyThreatFacts {
PyThreatFacts::of(&slf.get().inner.threats, slf.clone().unbind())
}
#[getter]
fn tactics(slf: &Bound<'_, Self>) -> (PyTacticsFacts, PyTacticsFacts) {
let facts = &slf.get().inner.tactics;
(
PyTacticsFacts::of(&facts[0], slf.clone().unbind(), 0),
PyTacticsFacts::of(&facts[1], slf.clone().unbind(), 1),
)
}
#[getter]
fn endgame(slf: &Bound<'_, Self>) -> PyEndgameFacts {
PyEndgameFacts::of(&slf.get().inner.endgame, slf.clone().unbind())
}
#[getter]
fn planes(slf: &Bound<'_, Self>) -> PyPlaneFacts {
PyPlaneFacts::of(&slf.get().inner.planes, slf.clone().unbind())
}
#[getter]
fn moves(&self) -> Vec<PyAnnotatedMove> {
self.inner
.moves
.iter()
.copied()
.map(PyAnnotatedMove::new)
.collect()
}
fn summary(&self) -> String {
self.inner.summary()
}
fn __repr__(&self) -> String {
format!("<Facts {} {}>", self.variant.rules().name(), self.text)
}
fn __getnewargs_ex__(&self) -> ((String,), std::collections::HashMap<String, PyVariant>) {
let mut kwargs = std::collections::HashMap::new();
kwargs.insert("variant".to_string(), self.variant.clone());
((self.text.clone(),), kwargs)
}
}