use crate::constraint::VarId;
use crate::domain::bitset::BitsetDomain;
use crate::error::CspError;
use crate::ordering::Ordering;
use crate::{Csp, Pruning, SolveConfig};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FutoshikiPuzzle {
pub n: u32,
pub fixed_cells: Vec<(usize, u32)>,
pub inequalities: Vec<(usize, usize)>,
}
impl FutoshikiPuzzle {
pub fn parse(input: &str) -> Self {
let mut lines = input.lines();
let n: u32 = lines.next().unwrap().trim().parse().unwrap();
let ls: Vec<usize> = lines
.next()
.unwrap()
.split_whitespace()
.map(|s| s.parse().unwrap())
.collect();
let vs: Vec<u32> = lines
.next()
.unwrap()
.split_whitespace()
.map(|s| s.parse().unwrap())
.collect();
let fixed_cells: Vec<(usize, u32)> = ls.into_iter().zip(vs).collect();
let a_s: Vec<usize> = lines
.next()
.unwrap()
.split_whitespace()
.map(|s| s.parse().unwrap())
.collect();
let b_s: Vec<usize> = lines
.next()
.unwrap()
.split_whitespace()
.map(|s| s.parse().unwrap())
.collect();
let inequalities: Vec<(usize, usize)> = a_s.into_iter().zip(b_s).collect();
Self {
n,
fixed_cells,
inequalities,
}
}
pub fn from_parts(
n: u32,
fixed_cells: Vec<(usize, u32)>,
inequalities: Vec<(usize, usize)>,
) -> Result<Self, CspError> {
let nn = n as usize;
let total = nn * nn;
for &(cell, value) in &fixed_cells {
if cell >= total {
return Err(CspError::invalid_input(format!(
"fixed-cell index {cell} out of range for a {n}×{n} board (must be < {total})"
)));
}
if value == 0 || value as usize > nn {
return Err(CspError::invalid_input(format!(
"fixed-cell value {value} out of range for a {n}×{n} board (must be 1..={n})"
)));
}
}
for &(a, b) in &inequalities {
if a >= total || b >= total {
return Err(CspError::invalid_input(format!(
"inequality pair ({a}, {b}) out of range for a {n}×{n} board \
(both indices must be < {total})"
)));
}
let (ra, ca) = (a / nn, a % nn);
let (rb, cb) = (b / nn, b % nn);
let manhattan = ra.abs_diff(rb) + ca.abs_diff(cb);
if manhattan != 1 {
return Err(CspError::invalid_input(format!(
"inequality pair ({a}, {b}) is not orthogonally adjacent: cells \
({ra},{ca}) and ({rb},{cb}) are Manhattan distance {manhattan} apart, \
but a caret can only sit on a shared cell edge (distance 1)"
)));
}
}
Ok(Self {
n,
fixed_cells,
inequalities,
})
}
}
pub fn create_futoshiki_csp(puzzle: &FutoshikiPuzzle) -> Csp<BitsetDomain> {
let n = puzzle.n;
let total = (n * n) as usize;
let mut csp = Csp::new();
let domain = BitsetDomain::new(1..=n);
for _ in 0..total {
csp.add_variable(domain.clone());
}
for &(cell, value) in &puzzle.fixed_cells {
csp.add_equals(cell as VarId, value);
}
for &(a, b) in &puzzle.inequalities {
csp.add_greater_than(a as VarId, b as VarId);
}
for i in 0..n {
let row: Vec<VarId> = (0..n).map(|j| (i * n + j) as VarId).collect();
csp.add_all_different(row);
}
for j in 0..n {
let col: Vec<VarId> = (0..n).map(|i| (i * n + j) as VarId).collect();
csp.add_all_different(col);
}
csp.finalize();
csp
}
pub fn solve_futoshiki(puzzle: &FutoshikiPuzzle) -> Vec<Vec<u32>> {
let mut csp = create_futoshiki_csp(puzzle);
let config = SolveConfig {
pruning: Pruning::Ac3,
ordering: Ordering::Mrv,
max_solutions: usize::MAX,
..Default::default()
};
csp.solve(&config)
}