use std::collections::BTreeSet;
use csp_solver::ordering::Ordering;
use csp_solver::puzzles::futoshiki::{FutoshikiPuzzle, create_futoshiki_csp};
use csp_solver::{Pruning, SolveConfig};
const N: u32 = 4;
const NN: usize = 16;
const SOLUTION: [u32; NN] = [1, 2, 3, 4, 2, 1, 4, 3, 3, 4, 1, 2, 4, 3, 2, 1];
const FREE: [usize; 4] = [5, 6, 9, 10];
const INEQUALITIES: [(usize, usize); 6] = [
(1, 0), (3, 2), (4, 0), (12, 8), (4, 5), (6, 7), ];
fn givens() -> Vec<(usize, u32)> {
(0..NN)
.filter(|i| !FREE.contains(i))
.map(|i| (i, SOLUTION[i]))
.collect()
}
fn puzzle() -> FutoshikiPuzzle {
FutoshikiPuzzle::from_parts(N, givens(), INEQUALITIES.to_vec())
.expect("hand-built board must pass from_parts validation")
}
fn solve_all(pruning: Pruning, ordering: Ordering) -> BTreeSet<Vec<u32>> {
let mut csp = create_futoshiki_csp(&puzzle());
let config = SolveConfig {
pruning,
ordering,
max_solutions: usize::MAX,
..Default::default()
};
csp.solve(&config).into_iter().collect()
}
fn brute_force() -> BTreeSet<Vec<u32>> {
let fixed = givens();
let mut out = BTreeSet::new();
for combo in 0..4u32.pow(FREE.len() as u32) {
let mut grid = [0u32; NN];
for &(i, v) in &fixed {
grid[i] = v;
}
for (k, &cell) in FREE.iter().enumerate() {
grid[cell] = 1 + (combo / 4u32.pow(k as u32)) % 4;
}
if is_valid(&grid) {
out.insert(grid.to_vec());
}
}
out
}
fn is_valid(grid: &[u32; NN]) -> bool {
let n = N as usize;
for i in 0..n {
let mut row = BTreeSet::new();
let mut col = BTreeSet::new();
for j in 0..n {
if !row.insert(grid[i * n + j]) || !col.insert(grid[j * n + i]) {
return false;
}
}
}
INEQUALITIES.iter().all(|&(a, b)| grid[a] > grid[b])
}
#[test]
fn solver_matches_bruteforce_oracle() {
let oracle = brute_force();
assert!(
oracle.contains(SOLUTION.as_slice()),
"the known Latin square must itself be a valid oracle solution — test data is wrong"
);
let solver = solve_all(Pruning::Ac3, Ordering::Mrv);
assert_eq!(
solver, oracle,
"Futoshiki solve engine diverged from the brute-force oracle (invented or dropped a board)"
);
}
#[test]
fn solution_set_is_config_invariant() {
let production = solve_all(Pruning::Ac3, Ordering::Mrv);
let alternate = solve_all(Pruning::ForwardChecking, Ordering::FailFirst);
assert_eq!(
production, alternate,
"Futoshiki solution set changed with the search strategy — an unsound prune"
);
assert!(
!production.is_empty(),
"the board must have at least one solution"
);
}