use crate::ordering::Ordering;
use crate::puzzles::class::{PuzzleClass, SimpleRng, generate_by_digging};
use crate::puzzles::futoshiki::Difficulty;
use crate::{Pruning, SolveConfig};
use super::csp::{CageOp, KenKenCage, create_kenken_csp};
fn gen_config(max_solutions: usize) -> SolveConfig {
SolveConfig {
pruning: Pruning::Ac3,
ordering: Ordering::FailFirst,
max_solutions,
..Default::default()
}
}
fn max_cage_len(difficulty: Difficulty) -> usize {
match difficulty {
Difficulty::Easy => 2,
Difficulty::Medium => 3,
Difficulty::Hard => 4,
}
}
fn seed_latin_square(n: u32, rng: &mut SimpleRng) -> Vec<u32> {
let total = (n * n) as usize;
let mut first_row: Vec<u32> = (1..=n).collect();
rng.shuffle(&mut first_row);
let mut board = vec![0u32; total];
board[..n as usize].copy_from_slice(&first_row);
let (mut csp, given) = create_kenken_csp(&board, n, &[]);
csp.solve_with_given(&gen_config(1), &given)
.into_iter()
.next()
.expect("an empty KenKen board with a fixed first row is always solvable")
}
fn ortho_neighbours(cell: usize, n: usize) -> Vec<usize> {
let (r, c) = (cell / n, cell % n);
let mut out = Vec::with_capacity(4);
if c + 1 < n {
out.push(cell + 1);
}
if c > 0 {
out.push(cell - 1);
}
if r + 1 < n {
out.push(cell + n);
}
if r > 0 {
out.push(cell - n);
}
out
}
fn assign_operator(cells: Vec<usize>, solution: &[u32], rng: &mut SimpleRng) -> KenKenCage {
let vals: Vec<u32> = cells.iter().map(|&c| solution[c]).collect();
if cells.len() == 1 {
return KenKenCage {
op: CageOp::Add,
target: vals[0],
cells,
};
}
if cells.len() == 2 {
let hi = vals[0].max(vals[1]);
let lo = vals[0].min(vals[1]);
let mut choices = vec![CageOp::Sub, CageOp::Mul, CageOp::Add];
if lo != 0 && hi.is_multiple_of(lo) {
choices.push(CageOp::Div);
}
let op = choices[rng.next_usize(choices.len())];
let target = match op {
CageOp::Sub => hi - lo,
CageOp::Div => hi / lo,
CageOp::Mul => hi * lo,
CageOp::Add => hi + lo,
};
return KenKenCage { op, target, cells };
}
let op = if rng.next_usize(2) == 0 {
CageOp::Add
} else {
CageOp::Mul
};
let target = match op {
CageOp::Add => vals.iter().sum(),
CageOp::Mul => vals.iter().product(),
_ => vals.iter().sum(),
};
KenKenCage { op, target, cells }
}
fn partition_into_cages(
solution: &[u32],
n: u32,
max_len: usize,
rng: &mut SimpleRng,
) -> Vec<KenKenCage> {
let nn = n as usize;
let total = solution.len();
const UNASSIGNED: usize = usize::MAX;
let mut cage_of = vec![UNASSIGNED; total];
let mut cages: Vec<Vec<usize>> = Vec::new();
let mut order: Vec<usize> = (0..total).collect();
rng.shuffle(&mut order);
for &start in &order {
if cage_of[start] != UNASSIGNED {
continue;
}
let id = cages.len();
let mut cells = vec![start];
cage_of[start] = id;
let target = 2 + rng.next_usize(max_len - 1);
while cells.len() < target {
let mut candidates: Vec<usize> = Vec::new();
for &c in &cells {
for nb in ortho_neighbours(c, nn) {
if cage_of[nb] == UNASSIGNED && !candidates.contains(&nb) {
candidates.push(nb);
}
}
}
if candidates.is_empty() {
break;
}
let next = candidates[rng.next_usize(candidates.len())];
cage_of[next] = id;
cells.push(next);
}
cages.push(cells);
}
cages
.into_iter()
.map(|mut cells| {
cells.sort_unstable(); assign_operator(cells, solution, rng)
})
.collect()
}
pub struct KenKenClass {
pub n: u32,
pub difficulty: Difficulty,
}
impl KenKenClass {
pub fn from_difficulty(n: u32, difficulty: Difficulty) -> Self {
Self { n, difficulty }
}
}
impl PuzzleClass for KenKenClass {
type Clue = KenKenCage;
type Puzzle = (Vec<u32>, Vec<KenKenCage>);
fn seed_solution(&self, rng: &mut SimpleRng) -> Vec<u32> {
seed_latin_square(self.n, rng)
}
fn place_clues(&self, solution: &[u32], rng: &mut SimpleRng) -> Vec<KenKenCage> {
partition_into_cages(solution, self.n, max_cage_len(self.difficulty), rng)
}
fn solve_candidate(
&self,
board: &[u32],
clues: &[KenKenCage],
max_solutions: usize,
) -> Vec<Vec<u32>> {
let (mut csp, given) = create_kenken_csp(board, self.n, clues);
csp.solve_with_given(&gen_config(max_solutions), &given)
}
fn target_holes(&self, board_len: usize) -> usize {
board_len
}
fn assemble(&self, board: Vec<u32>, clues: Vec<KenKenCage>) -> Self::Puzzle {
(board, clues)
}
}
pub fn generate_kenken(n: u32, difficulty: Difficulty) -> (Vec<u32>, Vec<KenKenCage>) {
generate_by_digging(
&KenKenClass::from_difficulty(n, difficulty),
&mut SimpleRng::from_time(),
)
}
pub fn generate_kenken_seeded(
n: u32,
difficulty: Difficulty,
seed: u64,
) -> (Vec<u32>, Vec<KenKenCage>) {
generate_by_digging(
&KenKenClass::from_difficulty(n, difficulty),
&mut SimpleRng::new(seed),
)
}