use thiserror::Error;
use crate::alive_zone::ZoneError;
use crate::{Color, PerColor, StoneId};
use super::{Game, GameStatus};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Error)]
pub enum BoardError {
#[error("the alive zone is corrupt: {0}")]
Zone(#[from] ZoneError),
#[error("stone {stone} is filed under id {id}")]
Misfiled {
id: StoneId,
stone: StoneId,
},
#[error("stone {stone} is in play with no dead zone carved for it")]
Uncarved {
stone: StoneId,
},
#[error("captured stone {stone} still has a dead zone carved for it")]
CapturedButCarved {
stone: StoneId,
},
#[error("stone {stone} is both in play and captured")]
InPlayAndCaptured {
stone: StoneId,
},
#[error("the turn is {turn} but history holds {committed} committed turns")]
TurnMismatch {
turn: u32,
committed: usize,
},
#[error("the status is {recorded:?} but the committed turns imply {derived:?}")]
StatusMismatch {
recorded: GameStatus,
derived: GameStatus,
},
#[error("{color:?} is credited with {recorded} captures but history holds {counted}")]
CaptureCountMismatch {
color: Color,
recorded: u32,
counted: u32,
},
}
impl Game {
pub fn validate(&self) -> Result<(), BoardError> {
self.zone.validate()?;
self.validate_bookkeeping()
}
fn validate_bookkeeping(&self) -> Result<(), BoardError> {
for (id, stone) in &self.stones {
if stone.id != *id {
return Err(BoardError::Misfiled {
id: *id,
stone: stone.id,
});
}
if !self.zone.has_circle(*id) {
return Err(BoardError::Uncarved { stone: *id });
}
if self.captured.contains_key(id) {
return Err(BoardError::InPlayAndCaptured { stone: *id });
}
}
for (id, stone) in &self.captured {
if stone.id != *id {
return Err(BoardError::Misfiled {
id: *id,
stone: stone.id,
});
}
if self.zone.has_circle(*id) {
return Err(BoardError::CapturedButCarved { stone: *id });
}
}
let committed = self.history.committed_count();
if self.turn as usize != committed {
return Err(BoardError::TurnMismatch {
turn: self.turn,
committed,
});
}
let derived = self.derived_status();
if self.status != derived {
return Err(BoardError::StatusMismatch {
recorded: self.status,
derived,
});
}
let mut counted: PerColor<u32> = PerColor::new(0, 0);
for entry in self.history.iter() {
if let Some(stone) = entry.delta.new_stone {
counted[stone.color] += entry.delta.captured_stone_ids.len() as u32;
}
}
for color in Color::ALL {
if counted[color] != self.captures[color] {
return Err(BoardError::CaptureCountMismatch {
color,
recorded: self.captures[color],
counted: counted[color],
});
}
}
Ok(())
}
pub(super) fn debug_validate(&self) {
if cfg!(debug_assertions) {
if let Err(error) = self.validate_bookkeeping() {
panic!("the board is inconsistent: {error}");
}
}
}
}