use crate::constraint::{LambdaConstraint, VarId};
use crate::domain::bitset::BitsetDomain;
use crate::puzzles::sudoku::csp::sudoku_given;
use crate::{Csp, SolveConfig};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CageOp {
Add,
Sub,
Mul,
Div,
}
impl CageOp {
pub fn ordinal(self) -> u32 {
match self {
CageOp::Add => 0,
CageOp::Sub => 1,
CageOp::Mul => 2,
CageOp::Div => 3,
}
}
pub fn from_ordinal(o: u32) -> Option<CageOp> {
match o {
0 => Some(CageOp::Add),
1 => Some(CageOp::Sub),
2 => Some(CageOp::Mul),
3 => Some(CageOp::Div),
_ => None,
}
}
pub fn is_binary(self) -> bool {
matches!(self, CageOp::Sub | CageOp::Div)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct KenKenCage {
pub op: CageOp,
pub target: u32,
pub cells: Vec<usize>,
}
pub fn create_kenken_csp(
board: &[u32],
n: u32,
cages: &[KenKenCage],
) -> (Csp<BitsetDomain>, Vec<(VarId, u32)>) {
let total = (n * n) as usize;
assert_eq!(board.len(), total, "board must have n*n = {total} elements");
let mut csp = Csp::new();
let domain = BitsetDomain::new(1..=n);
for _ in 0..total {
csp.add_variable(domain.clone());
}
let mut group: Vec<VarId> = Vec::with_capacity(n as usize);
for r in 0..n {
group.clear();
for c in 0..n {
group.push((r * n + c) as VarId);
}
csp.add_all_different(group.clone());
}
for c in 0..n {
group.clear();
for r in 0..n {
group.push((r * n + c) as VarId);
}
csp.add_all_different(group.clone());
}
for cage in cages {
let scope: Vec<VarId> = cage.cells.iter().map(|&c| c as VarId).collect();
match cage.op {
CageOp::Add => csp.add_cage_sum(scope, cage.target),
CageOp::Mul => csp.add_cage_product(scope, cage.target),
CageOp::Sub => {
if let [a, b] = scope[..] {
let target = cage.target;
csp.add_constraint(LambdaConstraint::new(
vec![a, b],
move |assignment: &[Option<u32>]| match (
&assignment[a as usize],
&assignment[b as usize],
) {
(Some(x), Some(y)) => x.abs_diff(*y) == target,
_ => true,
},
format!("cage_sub({a},{b})=|·|={target}"),
));
}
}
CageOp::Div => {
if let [a, b] = scope[..] {
let target = cage.target;
csp.add_constraint(LambdaConstraint::new(
vec![a, b],
move |assignment: &[Option<u32>]| match (
&assignment[a as usize],
&assignment[b as usize],
) {
(Some(x), Some(y)) => {
let (hi, lo) = (*x.max(y), *x.min(y));
lo != 0 && hi.is_multiple_of(lo) && hi / lo == target
}
_ => true,
},
format!("cage_div({a},{b})=max/min={target}"),
));
}
}
}
}
csp.finalize();
let given = sudoku_given(board);
(csp, given)
}
pub fn solve_kenken(
board: &[u32],
n: u32,
cages: &[KenKenCage],
config: &SolveConfig,
) -> Option<Vec<u32>> {
let (mut csp, given) = create_kenken_csp(board, n, cages);
csp.solve_with_given(config, &given).into_iter().next()
}