use crate::cross::initial_cross;
use crate::model::{Direction, Record};
use std::collections::HashSet;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ValidationError {
BadLinePosition {
index: usize,
pos: u8,
},
PointOccupied {
index: usize,
point: (i16, i16),
},
MissingPoint {
index: usize,
point: (i16, i16),
},
TouchRule {
index: usize,
},
}
impl std::fmt::Display for ValidationError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ValidationError::BadLinePosition { index, pos } => {
write!(f, "move {}: line position {pos} out of range", index + 1)
}
ValidationError::PointOccupied { index, point } => {
write!(f, "move {}: point {point:?} already occupied", index + 1)
}
ValidationError::MissingPoint { index, point } => {
write!(f, "move {}: line point {point:?} is not present", index + 1)
}
ValidationError::TouchRule { index } => {
write!(f, "move {}: violates the touch rule", index + 1)
}
}
}
}
impl std::error::Error for ValidationError {}
fn track_position(origin: (i16, i16), dir: Direction) -> (i16, i16) {
let (x, y) = origin;
match dir {
Direction::H => (y, x),
Direction::V => (x, y),
Direction::DP => (x + y, x), Direction::DN => (x - y, x), }
}
pub fn validate(record: &Record) -> Result<(), ValidationError> {
let n = record.variant.line_len();
let max_overlap: i16 = if record.variant.disjoint() { 0 } else { 1 };
let forbid = n as i16 - 1 - max_overlap;
let mut points: HashSet<(i16, i16)> = initial_cross(record.variant).into_iter().collect();
let mut lines: Vec<((i16, i16), Direction)> = Vec::with_capacity(record.moves.len());
for (index, m) in record.moves.iter().enumerate() {
if m.pos >= n {
return Err(ValidationError::BadLinePosition { index, pos: m.pos });
}
let new_point = (m.x, m.y);
if points.contains(&new_point) {
return Err(ValidationError::PointOccupied {
index,
point: new_point,
});
}
for p in m.line_points(n) {
if p != new_point && !points.contains(&p) {
return Err(ValidationError::MissingPoint { index, point: p });
}
}
let origin = m.origin();
let (track, position) = track_position(origin, m.dir);
for (lo, ld) in &lines {
if *ld == m.dir {
let (t2, p2) = track_position(*lo, *ld);
if t2 == track && (position - p2).abs() <= forbid {
return Err(ValidationError::TouchRule { index });
}
}
}
points.insert(new_point);
lines.push((origin, m.dir));
}
Ok(())
}