use crate::ordering::Ordering;
use crate::puzzles::class::{PuzzleClass, SimpleRng, generate_by_digging};
use crate::puzzles::sudoku::Difficulty;
use crate::puzzles::sudoku::csp::{sudoku_csp_skeleton, sudoku_given};
use crate::{Pruning, SolveConfig};
use super::csp::{KillerCage, create_killer_csp};
const MAX_CAGE_LEN: usize = 4;
fn gen_config(max_solutions: usize) -> SolveConfig {
SolveConfig {
pruning: Pruning::Ac3,
ordering: Ordering::FailFirst,
max_solutions,
..Default::default()
}
}
fn seed_sudoku_solution(n: u32, rng: &mut SimpleRng) -> Vec<u32> {
let m = n * n;
let total = (m * m) as usize;
let mut csp = sudoku_csp_skeleton(n);
let mut seed_board = vec![0u32; total];
let mut first_row: Vec<u32> = (1..=m).collect();
rng.shuffle(&mut first_row);
seed_board[..m as usize].copy_from_slice(&first_row);
csp.solve_with_given(&gen_config(1), &sudoku_given(&seed_board))
.into_iter()
.next()
.expect("a seeded Sudoku board with a fixed first row is always solvable")
}
fn ortho_neighbours(cell: usize, m: usize) -> Vec<usize> {
let (r, c) = (cell / m, cell % m);
let mut out = Vec::with_capacity(4);
if c + 1 < m {
out.push(cell + 1);
}
if c > 0 {
out.push(cell - 1);
}
if r + 1 < m {
out.push(cell + m);
}
if r > 0 {
out.push(cell - m);
}
out
}
fn partition_into_cages(solution: &[u32], n: u32, rng: &mut SimpleRng) -> Vec<KillerCage> {
let m = (n * 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_CAGE_LEN - 1);
while cells.len() < target {
let mut candidates: Vec<usize> = Vec::new();
for &c in &cells {
for nb in ortho_neighbours(c, m) {
if cage_of[nb] == UNASSIGNED
&& !cells.iter().any(|&cc| solution[cc] == solution[nb])
&& !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(); let sum = cells.iter().map(|&c| solution[c]).sum();
KillerCage { sum, cells }
})
.collect()
}
fn target_holes_for(difficulty: Difficulty, board_len: usize) -> usize {
match difficulty {
Difficulty::Easy => board_len / 4,
Difficulty::Medium => (board_len as f64 / 1.75) as usize,
Difficulty::Hard => (board_len as f64 / 1.25) as usize,
}
}
pub struct KillerClass {
pub n: u32,
pub difficulty: Difficulty,
}
impl KillerClass {
pub fn from_difficulty(n: u32, difficulty: Difficulty) -> Self {
Self { n, difficulty }
}
}
impl PuzzleClass for KillerClass {
type Clue = KillerCage;
type Puzzle = (Vec<u32>, Vec<KillerCage>);
fn seed_solution(&self, rng: &mut SimpleRng) -> Vec<u32> {
seed_sudoku_solution(self.n, rng)
}
fn place_clues(&self, solution: &[u32], rng: &mut SimpleRng) -> Vec<KillerCage> {
partition_into_cages(solution, self.n, rng)
}
fn solve_candidate(
&self,
board: &[u32],
clues: &[KillerCage],
max_solutions: usize,
) -> Vec<Vec<u32>> {
let (mut csp, given) = create_killer_csp(board, self.n, clues);
csp.solve_with_given(&gen_config(max_solutions), &given)
}
fn target_holes(&self, board_len: usize) -> usize {
target_holes_for(self.difficulty, board_len)
}
fn assemble(&self, board: Vec<u32>, clues: Vec<KillerCage>) -> Self::Puzzle {
(board, clues)
}
}
pub fn generate_killer(n: u32, difficulty: Difficulty) -> (Vec<u32>, Vec<KillerCage>) {
generate_by_digging(
&KillerClass::from_difficulty(n, difficulty),
&mut SimpleRng::from_time(),
)
}
pub fn generate_killer_seeded(
n: u32,
difficulty: Difficulty,
seed: u64,
) -> (Vec<u32>, Vec<KillerCage>) {
generate_by_digging(
&KillerClass::from_difficulty(n, difficulty),
&mut SimpleRng::new(seed),
)
}