use crate::{ColorKind, PieceKind};
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub enum SquareColor {
Light,
Dark,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub enum CountOp {
Eq,
NotEq,
Le,
Lt,
Ge,
Gt,
}
impl CountOp {
#[must_use]
pub fn check<T: Ord>(self, lhs: T, rhs: T) -> bool {
match self {
Self::Eq => lhs == rhs,
Self::NotEq => lhs != rhs,
Self::Le => lhs <= rhs,
Self::Lt => lhs < rhs,
Self::Ge => lhs >= rhs,
Self::Gt => lhs > rhs,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "serde",
serde(bound(
serialize = "P: serde::Serialize, C: serde::Serialize",
deserialize = "P: serde::Deserialize<'de>, C: serde::Deserialize<'de>"
))
)]
#[non_exhaustive]
pub enum Constraint<P, C = SquareColor> {
Count {
piece: P,
op: CountOp,
value: usize,
},
CountOnColor {
piece: P,
color: C,
op: CountOp,
value: usize,
},
At {
piece: P,
square: usize,
},
NotAt {
piece: P,
square: usize,
},
Order(Vec<(P, usize)>),
Relative {
lhs: (P, usize),
rhs: (P, usize),
op: CountOp,
offset: i32,
},
And(Vec<Constraint<P, C>>),
Or(Vec<Constraint<P, C>>),
Not(Box<Constraint<P, C>>),
}
impl<P: PieceKind, C: ColorKind> Constraint<P, C> {
#[must_use]
pub fn evaluate(&self, arrangement: &[P], colors: &[C]) -> bool {
match self {
Self::Count { piece, op, value } => {
let n = arrangement.iter().filter(|p| *p == piece).count();
op.check(n, *value)
}
Self::CountOnColor {
piece,
color,
op,
value,
} => {
let n = arrangement
.iter()
.zip(colors.iter())
.filter(|(p, c)| *p == piece && *c == color)
.count();
op.check(n, *value)
}
Self::At { piece, square } => arrangement.get(*square) == Some(piece),
Self::NotAt { piece, square } => arrangement.get(*square) != Some(piece),
Self::Relative {
lhs,
rhs,
op,
offset,
} => {
let lhs_pos = nth_position(arrangement, &lhs.0, lhs.1);
let rhs_pos = nth_position(arrangement, &rhs.0, rhs.1);
match (lhs_pos, rhs_pos) {
(Some(l), Some(r)) => {
let diff = (l as i32) - (r as i32);
op.check(diff, *offset)
}
_ => false,
}
}
Self::Order(chain) => {
let mut positions: Vec<usize> = Vec::with_capacity(chain.len());
for (piece_kind, instance_idx) in chain {
let occurrence = nth_position(arrangement, piece_kind, *instance_idx);
match occurrence {
Some(pos) => positions.push(pos),
None => return false,
}
}
positions.windows(2).all(|w| w[0] < w[1])
}
Self::And(children) => children.iter().all(|c| c.evaluate(arrangement, colors)),
Self::Or(children) => children.iter().any(|c| c.evaluate(arrangement, colors)),
Self::Not(inner) => !inner.evaluate(arrangement, colors),
}
}
pub(crate) fn collect_eq_counts(&self) -> Vec<(P, usize)> {
let mut out = Vec::new();
self.collect_eq_counts_into(&mut out);
out
}
fn collect_eq_counts_into(&self, out: &mut Vec<(P, usize)>) {
match self {
Self::Count {
piece,
op: CountOp::Eq,
value,
} => out.push((*piece, *value)),
Self::And(children) => {
for c in children {
c.collect_eq_counts_into(out);
}
}
_ => {}
}
}
pub(crate) fn walk(&self, visitor: &mut impl FnMut(&Self)) {
visitor(self);
match self {
Self::And(children) | Self::Or(children) => {
for c in children {
c.walk(visitor);
}
}
Self::Not(inner) => inner.walk(visitor),
_ => {}
}
}
#[must_use]
pub fn simplify(&self) -> Self {
match self {
Self::And(children) => {
let mut acc: Vec<Self> = Vec::with_capacity(children.len());
for c in children {
let c = c.simplify();
if is_false(&c) {
return Self::Or(Vec::new());
}
if is_true(&c) {
continue;
}
acc.push(c);
}
if acc.len() == 1 {
acc.pop().expect("len == 1")
} else {
Self::And(acc)
}
}
Self::Or(children) => {
let mut acc: Vec<Self> = Vec::with_capacity(children.len());
for c in children {
let c = c.simplify();
if is_true(&c) {
return Self::And(Vec::new());
}
if is_false(&c) {
continue;
}
acc.push(c);
}
if acc.len() == 1 {
acc.pop().expect("len == 1")
} else {
Self::Or(acc)
}
}
Self::Not(inner) => {
let inner = inner.simplify();
if is_true(&inner) {
Self::Or(Vec::new())
} else if is_false(&inner) {
Self::And(Vec::new())
} else if let Self::Not(grandchild) = inner {
*grandchild
} else {
Self::Not(Box::new(inner))
}
}
leaf => leaf.clone(),
}
}
}
#[inline]
fn is_true<P, C>(c: &Constraint<P, C>) -> bool {
matches!(c, Constraint::And(v) if v.is_empty())
}
#[inline]
fn is_false<P, C>(c: &Constraint<P, C>) -> bool {
matches!(c, Constraint::Or(v) if v.is_empty())
}
fn nth_position<P: PieceKind>(arrangement: &[P], piece: &P, instance_idx: usize) -> Option<usize> {
arrangement
.iter()
.enumerate()
.filter_map(|(i, p)| (p == piece).then_some(i))
.nth(instance_idx)
}
#[cfg(test)]
mod simplify_tests {
use super::*;
use crate::chess::Piece;
fn leaf() -> Constraint<Piece> {
Constraint::Count {
piece: Piece::King,
op: CountOp::Eq,
value: 1,
}
}
fn leaf2() -> Constraint<Piece> {
Constraint::At {
piece: Piece::Queen,
square: 3,
}
}
const fn true_<P, C>() -> Constraint<P, C> {
Constraint::And(Vec::new())
}
const fn false_<P, C>() -> Constraint<P, C> {
Constraint::Or(Vec::new())
}
#[test]
fn leaves_simplify_to_themselves() {
assert_eq!(leaf().simplify(), leaf());
assert_eq!(leaf2().simplify(), leaf2());
}
#[test]
fn empty_and_is_identity() {
let c: Constraint<Piece> = true_();
assert_eq!(c.simplify(), true_());
}
#[test]
fn empty_or_is_identity() {
let c: Constraint<Piece> = false_();
assert_eq!(c.simplify(), false_());
}
#[test]
fn single_child_and_unwraps() {
let c = Constraint::And(vec![leaf()]);
assert_eq!(c.simplify(), leaf());
}
#[test]
fn single_child_or_unwraps() {
let c = Constraint::Or(vec![leaf()]);
assert_eq!(c.simplify(), leaf());
}
#[test]
fn double_negation_eliminates() {
let c = Constraint::Not(Box::new(Constraint::Not(Box::new(leaf()))));
assert_eq!(c.simplify(), leaf());
}
#[test]
fn triple_negation_collapses_to_single() {
let c = Constraint::Not(Box::new(Constraint::Not(Box::new(Constraint::Not(
Box::new(leaf()),
)))));
assert_eq!(c.simplify(), Constraint::Not(Box::new(leaf())));
}
#[test]
fn not_of_true_is_false() {
let c: Constraint<Piece> = Constraint::Not(Box::new(true_()));
assert_eq!(c.simplify(), false_());
}
#[test]
fn not_of_false_is_true() {
let c: Constraint<Piece> = Constraint::Not(Box::new(false_()));
assert_eq!(c.simplify(), true_());
}
#[test]
fn false_propagates_through_and() {
let c = Constraint::And(vec![leaf(), false_()]);
assert_eq!(c.simplify(), false_());
}
#[test]
fn true_propagates_through_or() {
let c = Constraint::Or(vec![leaf(), true_()]);
assert_eq!(c.simplify(), true_());
}
#[test]
fn neutral_true_drops_from_and() {
let c = Constraint::And(vec![leaf(), true_()]);
assert_eq!(c.simplify(), leaf());
}
#[test]
fn neutral_false_drops_from_or() {
let c = Constraint::Or(vec![leaf(), false_()]);
assert_eq!(c.simplify(), leaf());
}
#[test]
fn nested_editor_worst_case() {
let c = Constraint::And(vec![
leaf(),
Constraint::Or(vec![
Constraint::Not(Box::new(Constraint::Not(Box::new(leaf2())))),
false_(),
]),
]);
let expected = Constraint::And(vec![leaf(), leaf2()]);
assert_eq!(c.simplify(), expected);
}
#[test]
fn simplify_is_idempotent() {
let trees: [Constraint<Piece>; 6] = [
leaf(),
Constraint::And(vec![leaf(), Constraint::Not(Box::new(false_()))]),
Constraint::Or(vec![false_(), Constraint::And(vec![leaf2()])]),
Constraint::Not(Box::new(Constraint::Not(Box::new(leaf())))),
Constraint::And(vec![Constraint::Or(vec![leaf(), false_()])]),
true_(),
];
for t in trees {
let once = t.simplify();
let twice = once.simplify();
assert_eq!(once, twice, "not idempotent for {t:?}");
}
}
#[test]
fn deeply_nested_unsatisfiable_propagates_to_root() {
let c = Constraint::Or(vec![Constraint::And(vec![leaf(), false_()])]);
assert_eq!(c.simplify(), false_());
}
#[test]
fn nested_truth_propagates_to_root() {
let c = Constraint::And(vec![Constraint::Or(vec![
Constraint::Not(Box::new(false_())),
leaf(),
])]);
assert_eq!(c.simplify(), true_());
}
}