use pyo3::prelude::*;
use crate::explain;
use crate::types::{Square, SquareSet};
use super::board::PySquareSet;
use super::convert::square_name;
fn pairs(items: &[(Square, SquareSet)]) -> Vec<(String, PySquareSet)> {
items
.iter()
.map(|&(square, set)| (square_name(square), PySquareSet::new(set)))
.collect()
}
#[pyclass(
frozen,
skip_from_py_object,
module = "esca.explain",
name = "Castling"
)]
#[derive(Clone)]
pub struct PyCastling {
#[pyo3(get)]
right: bool,
#[pyo3(get)]
rook_present: bool,
#[pyo3(get)]
king_in_check_by: PySquareSet,
#[pyo3(get)]
path_attacked: Vec<(String, PySquareSet)>,
#[pyo3(get)]
path_blocked: PySquareSet,
#[pyo3(get)]
allowed: bool,
}
impl PyCastling {
pub(crate) fn of(castling: &explain::Castling) -> PyCastling {
PyCastling {
right: castling.right,
rook_present: castling.rook_present,
king_in_check_by: PySquareSet::new(castling.king_in_check_by),
path_attacked: pairs(&castling.path_attacked),
path_blocked: PySquareSet::new(castling.path_blocked),
allowed: castling.allowed,
}
}
}
#[pymethods]
impl PyCastling {
fn __repr__(&self) -> String {
format!("<Castling allowed={}>", self.allowed)
}
}
#[pyclass(
frozen,
skip_from_py_object,
module = "esca.explain",
name = "EnPassant"
)]
#[derive(Clone)]
pub struct PyEnPassant {
#[pyo3(get)]
target: Option<String>,
#[pyo3(get)]
captures: Vec<PyEpCapture>,
}
impl PyEnPassant {
pub(crate) fn of(status: &explain::EnPassant) -> PyEnPassant {
PyEnPassant {
target: status.target().map(square_name),
captures: status.captures().iter().map(PyEpCapture::of).collect(),
}
}
}
#[pymethods]
impl PyEnPassant {
fn __repr__(&self) -> String {
match &self.target {
Some(target) => format!("<EnPassant {target}>"),
None => "<EnPassant none>".to_string(),
}
}
}
#[pyclass(
frozen,
skip_from_py_object,
module = "esca.explain",
name = "EpCapture"
)]
#[derive(Clone)]
pub struct PyEpCapture {
#[pyo3(get)]
origin: String,
#[pyo3(get)]
legal: bool,
#[pyo3(get)]
forbidden_by: Option<PyEpObstacle>,
}
impl PyEpCapture {
fn of(capture: &explain::EpCapture) -> PyEpCapture {
PyEpCapture {
origin: square_name(capture.from),
legal: capture.legal,
forbidden_by: capture.forbidden_by.as_ref().map(PyEpObstacle::of),
}
}
}
#[pymethods]
impl PyEpCapture {
fn __repr__(&self) -> String {
format!("<EpCapture {} legal={}>", self.origin, self.legal)
}
}
#[pyclass(
frozen,
skip_from_py_object,
module = "esca.explain",
name = "EpObstacle"
)]
#[derive(Clone)]
pub struct PyEpObstacle {
#[pyo3(get)]
kind: String,
#[pyo3(get)]
ray: PySquareSet,
#[pyo3(get)]
pinner: Option<String>,
#[pyo3(get)]
attacker: Option<String>,
#[pyo3(get)]
by: PySquareSet,
}
impl PyEpObstacle {
fn of(obstacle: &explain::EpObstacle) -> PyEpObstacle {
let mut out = PyEpObstacle {
kind: String::new(),
ray: PySquareSet::new(SquareSet::EMPTY),
pinner: None,
attacker: None,
by: PySquareSet::new(SquareSet::EMPTY),
};
match *obstacle {
explain::EpObstacle::Pinned { ray, pinner } => {
out.kind = "pinned".to_string();
out.ray = PySquareSet::new(ray);
out.pinner = Some(square_name(pinner));
}
explain::EpObstacle::ExposesKing { attacker } => {
out.kind = "exposes_king".to_string();
out.attacker = Some(square_name(attacker));
}
explain::EpObstacle::InCheck { by } => {
out.kind = "in_check".to_string();
out.by = PySquareSet::new(by);
}
}
out
}
}
#[pymethods]
impl PyEpObstacle {
fn __repr__(&self) -> String {
format!("<EpObstacle {}>", self.kind)
}
}
#[pyclass(frozen, skip_from_py_object, module = "esca.explain", name = "Pin")]
#[derive(Clone)]
pub struct PyPin {
#[pyo3(get)]
pinned: String,
#[pyo3(get)]
pinner: String,
#[pyo3(get)]
king: String,
#[pyo3(get)]
ray: PySquareSet,
}
impl PyPin {
pub(crate) fn of(pin: &explain::Pin) -> PyPin {
PyPin {
pinned: square_name(pin.pinned),
pinner: square_name(pin.pinner),
king: square_name(pin.king),
ray: PySquareSet::new(pin.ray),
}
}
}
#[pymethods]
impl PyPin {
fn __repr__(&self) -> String {
format!("<Pin {} by {}>", self.pinned, self.pinner)
}
}
#[pyclass(frozen, skip_from_py_object, module = "esca.explain", name = "Skewer")]
#[derive(Clone)]
pub struct PySkewer {
#[pyo3(get)]
attacker: String,
#[pyo3(get)]
front: String,
#[pyo3(get)]
behind: String,
#[pyo3(get)]
ray: PySquareSet,
}
impl PySkewer {
pub(crate) fn of(skewer: &explain::Skewer) -> PySkewer {
PySkewer {
attacker: square_name(skewer.attacker),
front: square_name(skewer.front),
behind: square_name(skewer.behind),
ray: PySquareSet::new(skewer.ray),
}
}
}
#[pymethods]
impl PySkewer {
fn __repr__(&self) -> String {
format!("<Skewer {} then {}>", self.front, self.behind)
}
}
#[pyclass(
frozen,
skip_from_py_object,
module = "esca.explain",
name = "Repetition"
)]
#[derive(Clone)]
pub struct PyRepetition {
#[pyo3(get)]
count: u32,
#[pyo3(get)]
plies: Vec<u32>,
#[pyo3(get)]
near_misses: Vec<PyNearMiss>,
}
impl PyRepetition {
pub(crate) fn of(repetition: &explain::Repetition) -> PyRepetition {
PyRepetition {
count: repetition.count,
plies: repetition.plies.clone(),
near_misses: repetition.near_misses.iter().map(PyNearMiss::of).collect(),
}
}
}
#[pymethods]
impl PyRepetition {
fn __repr__(&self) -> String {
format!("<Repetition {}>", self.count)
}
}
#[pyclass(
frozen,
skip_from_py_object,
module = "esca.explain",
name = "NearMiss"
)]
#[derive(Clone)]
pub struct PyNearMiss {
#[pyo3(get)]
ply: u32,
#[pyo3(get)]
differs: Vec<String>,
}
impl PyNearMiss {
fn of(miss: &explain::NearMiss) -> PyNearMiss {
PyNearMiss {
ply: miss.ply,
differs: miss
.differs
.iter()
.map(|difference| {
match difference {
explain::Difference::CastlingRights => "castling_rights",
explain::Difference::EnPassant => "en_passant",
explain::Difference::SideToMove => "side_to_move",
}
.to_string()
})
.collect(),
}
}
}
#[pymethods]
impl PyNearMiss {
fn __repr__(&self) -> String {
format!("<NearMiss ply {}>", self.ply)
}
}
#[pyclass(
frozen,
skip_from_py_object,
module = "esca.explain",
name = "FiftyMove"
)]
#[derive(Clone)]
pub struct PyFiftyMove {
#[pyo3(get)]
clock: u32,
#[pyo3(get)]
plies_to_claim: u32,
#[pyo3(get)]
plies_to_automatic: u32,
#[pyo3(get)]
last_reset: Option<PyReset>,
}
impl PyFiftyMove {
pub(crate) fn of(fifty: &explain::FiftyMove) -> PyFiftyMove {
PyFiftyMove {
clock: fifty.clock,
plies_to_claim: fifty.plies_to_claim,
plies_to_automatic: fifty.plies_to_automatic,
last_reset: fifty.last_reset.map(|reset| PyReset::of(&reset)),
}
}
}
#[pymethods]
impl PyFiftyMove {
fn __repr__(&self) -> String {
format!("<FiftyMove clock {}>", self.clock)
}
}
#[pyclass(frozen, skip_from_py_object, module = "esca.explain", name = "Reset")]
#[derive(Clone)]
pub struct PyReset {
#[pyo3(get)]
ply: u32,
#[pyo3(get)]
kind: String,
}
impl PyReset {
fn of(reset: &explain::Reset) -> PyReset {
PyReset {
ply: reset.ply,
kind: match reset.kind {
explain::ResetKind::Capture => "capture",
explain::ResetKind::PawnMove => "pawn_move",
}
.to_string(),
}
}
}
#[pymethods]
impl PyReset {
fn __repr__(&self) -> String {
format!("<Reset ply {} {}>", self.ply, self.kind)
}
}
#[pyclass(
frozen,
skip_from_py_object,
module = "esca.explain",
name = "DrawStatus"
)]
#[derive(Clone)]
pub struct PyDrawStatus {
#[pyo3(get)]
automatic: Vec<PyAutomaticDraw>,
#[pyo3(get)]
claimable: Vec<PyClaimableDraw>,
}
impl PyDrawStatus {
pub(crate) fn of(status: &explain::DrawStatus) -> PyDrawStatus {
PyDrawStatus {
automatic: status.automatic.iter().map(PyAutomaticDraw::of).collect(),
claimable: claims(&status.claimable),
}
}
}
#[pymethods]
impl PyDrawStatus {
fn __repr__(&self) -> String {
format!(
"<DrawStatus automatic {} claimable {}>",
self.automatic.len(),
self.claimable.len()
)
}
}
pub(crate) fn claims(claims: &[explain::ClaimableDraw]) -> Vec<PyClaimableDraw> {
claims.iter().map(PyClaimableDraw::of).collect()
}
#[pyclass(
frozen,
skip_from_py_object,
module = "esca.explain",
name = "AutomaticDraw"
)]
#[derive(Clone)]
pub struct PyAutomaticDraw {
#[pyo3(get)]
kind: String,
#[pyo3(get)]
stalemate: Option<PyStalemateDetail>,
#[pyo3(get)]
material: Option<String>,
#[pyo3(get)]
repetition: Option<PyRepetition>,
#[pyo3(get)]
fifty_move: Option<PyFiftyMove>,
}
impl PyAutomaticDraw {
fn of(draw: &explain::AutomaticDraw) -> PyAutomaticDraw {
let mut out = PyAutomaticDraw {
kind: String::new(),
stalemate: None,
material: None,
repetition: None,
fifty_move: None,
};
match draw {
explain::AutomaticDraw::Stalemate(detail) => {
out.kind = "stalemate".to_string();
out.stalemate = Some(PyStalemateDetail::of(detail));
}
explain::AutomaticDraw::InsufficientMaterial(config) => {
out.kind = "insufficient_material".to_string();
out.material = Some(material_name(*config).to_string());
}
explain::AutomaticDraw::Fivefold(repetition) => {
out.kind = "fivefold".to_string();
out.repetition = Some(PyRepetition::of(repetition));
}
explain::AutomaticDraw::SeventyFiveMoves(fifty) => {
out.kind = "seventy_five_moves".to_string();
out.fifty_move = Some(PyFiftyMove::of(fifty));
}
}
out
}
}
#[pymethods]
impl PyAutomaticDraw {
fn __repr__(&self) -> String {
format!("<AutomaticDraw {}>", self.kind)
}
}
fn material_name(config: explain::MaterialConfig) -> &'static str {
match config {
explain::MaterialConfig::KvK => "k_v_k",
explain::MaterialConfig::KNvK => "kn_v_k",
explain::MaterialConfig::KBvK => "kb_v_k",
explain::MaterialConfig::KBvKBSameColour => "kb_v_kb_same_colour",
}
}
#[pyclass(
frozen,
skip_from_py_object,
module = "esca.explain",
name = "ClaimableDraw"
)]
#[derive(Clone)]
pub struct PyClaimableDraw {
#[pyo3(get)]
kind: String,
#[pyo3(get)]
repetition: Option<PyRepetition>,
#[pyo3(get)]
fifty_move: Option<PyFiftyMove>,
}
impl PyClaimableDraw {
fn of(claim: &explain::ClaimableDraw) -> PyClaimableDraw {
match claim {
explain::ClaimableDraw::Threefold(repetition) => PyClaimableDraw {
kind: "threefold".to_string(),
repetition: Some(PyRepetition::of(repetition)),
fifty_move: None,
},
explain::ClaimableDraw::FiftyMoves(fifty) => PyClaimableDraw {
kind: "fifty_moves".to_string(),
repetition: None,
fifty_move: Some(PyFiftyMove::of(fifty)),
},
}
}
}
#[pymethods]
impl PyClaimableDraw {
fn __repr__(&self) -> String {
format!("<ClaimableDraw {}>", self.kind)
}
}
#[pyclass(
frozen,
skip_from_py_object,
module = "esca.explain",
name = "StalemateDetail"
)]
#[derive(Clone)]
pub struct PyStalemateDetail {
#[pyo3(get)]
king: String,
#[pyo3(get)]
escape_squares: Vec<(String, PySquareSet)>,
#[pyo3(get)]
stuck_units: Vec<(String, PyStuck)>,
}
impl PyStalemateDetail {
fn of(detail: &explain::StalemateDetail) -> PyStalemateDetail {
PyStalemateDetail {
king: square_name(detail.king),
escape_squares: pairs(&detail.escape_squares),
stuck_units: detail
.stuck_units
.iter()
.map(|&(square, stuck)| (square_name(square), PyStuck::of(stuck)))
.collect(),
}
}
}
#[pymethods]
impl PyStalemateDetail {
fn __repr__(&self) -> String {
format!("<StalemateDetail {}>", self.king)
}
}
#[pyclass(frozen, skip_from_py_object, module = "esca.explain", name = "Stuck")]
#[derive(Clone)]
pub struct PyStuck {
#[pyo3(get)]
kind: String,
#[pyo3(get)]
ray: PySquareSet,
#[pyo3(get)]
pinner: Option<String>,
}
impl PyStuck {
fn of(stuck: explain::Stuck) -> PyStuck {
match stuck {
explain::Stuck::Pinned { ray, pinner } => PyStuck {
kind: "pinned".to_string(),
ray: PySquareSet::new(ray),
pinner: Some(square_name(pinner)),
},
explain::Stuck::Blocked => PyStuck {
kind: "blocked".to_string(),
ray: PySquareSet::new(SquareSet::EMPTY),
pinner: None,
},
explain::Stuck::NoMoves => PyStuck {
kind: "no_moves".to_string(),
ray: PySquareSet::new(SquareSet::EMPTY),
pinner: None,
},
}
}
}
#[pymethods]
impl PyStuck {
fn __repr__(&self) -> String {
format!("<Stuck {}>", self.kind)
}
}