2017-03-06 00:38:25 +04:00
|
|
|
//! Samurai Sudoku.
|
|
|
|
//!
|
|
|
|
//! https://en.wikipedia.org/wiki/Sudoku#Variants
|
|
|
|
//! http://www.samurai-sudoku.com/#ai
|
|
|
|
|
|
|
|
extern crate puzzle_solver;
|
|
|
|
|
2021-12-07 20:56:04 +04:00
|
|
|
use puzzle_solver::{Puzzle, Solution, Val, VarToken};
|
2017-03-06 00:38:25 +04:00
|
|
|
|
|
|
|
const SQRT_SIZE: usize = 3;
|
|
|
|
const SIZE: usize = 9;
|
2017-03-18 02:14:35 +04:00
|
|
|
const X: Val = -1;
|
|
|
|
type Board = [[Val; SIZE + SQRT_SIZE + SIZE]; SIZE + SQRT_SIZE + SIZE];
|
2017-03-06 00:38:25 +04:00
|
|
|
type SudokuVars = Vec<Vec<VarToken>>;
|
|
|
|
type SamuraiVars = (SudokuVars, SudokuVars, SudokuVars, SudokuVars, SudokuVars);
|
|
|
|
|
|
|
|
fn make_sudoku(sys: &mut Puzzle) -> SudokuVars {
|
2021-12-10 13:20:36 +04:00
|
|
|
let vars = sys.new_vars_2d(SIZE, SIZE, &[1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
2017-03-06 00:38:25 +04:00
|
|
|
|
|
|
|
for y in 0..SIZE {
|
|
|
|
sys.all_different(&vars[y]);
|
|
|
|
}
|
|
|
|
|
|
|
|
for x in 0..SIZE {
|
|
|
|
sys.all_different(vars.iter().map(|row| &row[x]));
|
|
|
|
}
|
|
|
|
|
|
|
|
for block in 0..SIZE {
|
|
|
|
let x0 = SQRT_SIZE * (block % SQRT_SIZE);
|
|
|
|
let y0 = SQRT_SIZE * (block / SQRT_SIZE);
|
2021-12-07 20:56:04 +04:00
|
|
|
sys.all_different((0..SIZE).map(|n| &vars[y0 + (n / SQRT_SIZE)][x0 + (n % SQRT_SIZE)]));
|
2017-03-06 00:38:25 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
vars
|
|
|
|
}
|
|
|
|
|
|
|
|
fn make_samurai_sudoku(board: &Board) -> (Puzzle, SamuraiVars) {
|
2021-12-07 20:56:04 +04:00
|
|
|
let set = |sys: &mut Puzzle, var, val| {
|
|
|
|
if val != 0 {
|
|
|
|
sys.set_value(var, val)
|
|
|
|
}
|
|
|
|
};
|
2017-03-06 00:38:25 +04:00
|
|
|
|
|
|
|
let mut sys = Puzzle::new();
|
|
|
|
let tl = make_sudoku(&mut sys);
|
|
|
|
let tr = make_sudoku(&mut sys);
|
|
|
|
let bl = make_sudoku(&mut sys);
|
|
|
|
let br = make_sudoku(&mut sys);
|
|
|
|
let mid = make_sudoku(&mut sys);
|
|
|
|
|
2021-12-08 11:17:32 +04:00
|
|
|
#[allow(clippy::erasing_op)]
|
2017-03-06 00:38:25 +04:00
|
|
|
for y in 0..SQRT_SIZE {
|
|
|
|
for x in 0..SQRT_SIZE {
|
2021-12-07 20:56:04 +04:00
|
|
|
sys.unify(
|
|
|
|
mid[0 * SQRT_SIZE + y][0 * SQRT_SIZE + x],
|
|
|
|
tl[2 * SQRT_SIZE + y][2 * SQRT_SIZE + x],
|
|
|
|
);
|
|
|
|
sys.unify(
|
|
|
|
mid[0 * SQRT_SIZE + y][2 * SQRT_SIZE + x],
|
|
|
|
tr[2 * SQRT_SIZE + y][0 * SQRT_SIZE + x],
|
|
|
|
);
|
|
|
|
sys.unify(
|
|
|
|
mid[2 * SQRT_SIZE + y][0 * SQRT_SIZE + x],
|
|
|
|
bl[0 * SQRT_SIZE + y][2 * SQRT_SIZE + x],
|
|
|
|
);
|
|
|
|
sys.unify(
|
|
|
|
mid[2 * SQRT_SIZE + y][2 * SQRT_SIZE + x],
|
|
|
|
br[0 * SQRT_SIZE + y][0 * SQRT_SIZE + x],
|
|
|
|
);
|
2017-03-06 00:38:25 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for y in 0..SIZE {
|
|
|
|
for x in 0..SIZE {
|
|
|
|
set(&mut sys, tl[y][x], board[y][x]);
|
|
|
|
set(&mut sys, tr[y][x], board[y][SIZE + SQRT_SIZE + x]);
|
|
|
|
set(&mut sys, bl[y][x], board[SIZE + SQRT_SIZE + y][x]);
|
2021-12-07 20:56:04 +04:00
|
|
|
set(
|
|
|
|
&mut sys,
|
|
|
|
br[y][x],
|
|
|
|
board[SIZE + SQRT_SIZE + y][SIZE + SQRT_SIZE + x],
|
|
|
|
);
|
|
|
|
set(
|
|
|
|
&mut sys,
|
|
|
|
mid[y][x],
|
|
|
|
board[2 * SQRT_SIZE + y][2 * SQRT_SIZE + x],
|
|
|
|
);
|
2017-03-06 00:38:25 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
(sys, (tl, tr, bl, br, mid))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn print_samurai_sudoku(dict: &Solution, vars: &SamuraiVars) {
|
|
|
|
let &(ref tl, ref tr, ref bl, ref br, ref mid) = vars;
|
2021-12-08 11:17:32 +04:00
|
|
|
let pr3 = |a: &[VarToken], j| print!(" {} {} {} ", dict[a[j]], dict[a[j + 1]], dict[a[j + 2]]);
|
2021-12-07 20:56:04 +04:00
|
|
|
let pr9 = |a| {
|
|
|
|
pr3(a, 0);
|
|
|
|
pr3(a, 3);
|
|
|
|
pr3(a, 6);
|
|
|
|
};
|
2021-12-08 11:17:32 +04:00
|
|
|
let gap = || print!(" ");
|
2017-03-06 00:38:25 +04:00
|
|
|
|
|
|
|
for i in 0..SIZE {
|
|
|
|
pr9(&tl[i]);
|
|
|
|
if 2 * SQRT_SIZE <= i {
|
|
|
|
pr3(&mid[i - 2 * SQRT_SIZE], 3);
|
|
|
|
} else {
|
|
|
|
gap();
|
|
|
|
}
|
|
|
|
pr9(&tr[i]);
|
|
|
|
println!();
|
|
|
|
}
|
|
|
|
|
|
|
|
for i in SQRT_SIZE..(2 * SQRT_SIZE) {
|
|
|
|
gap();
|
|
|
|
gap();
|
|
|
|
pr9(&mid[i]);
|
|
|
|
println!();
|
|
|
|
}
|
|
|
|
|
|
|
|
for i in 0..SIZE {
|
|
|
|
pr9(&bl[i]);
|
|
|
|
if i < SQRT_SIZE {
|
|
|
|
pr3(&mid[2 * SQRT_SIZE + i], 3);
|
|
|
|
} else {
|
|
|
|
gap();
|
|
|
|
}
|
|
|
|
pr9(&br[i]);
|
|
|
|
println!();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn verify_samurai_sudoku(dict: &Solution, vars: &SamuraiVars, expected: &Board) {
|
|
|
|
let &(ref tl, ref tr, ref bl, ref br, ref mid) = vars;
|
|
|
|
for i in 0..SIZE {
|
|
|
|
for j in 0..SIZE {
|
|
|
|
assert_eq!(dict[tl[i][j]], expected[i][j]);
|
|
|
|
assert_eq!(dict[tr[i][j]], expected[i][SIZE + SQRT_SIZE + j]);
|
|
|
|
assert_eq!(dict[bl[i][j]], expected[SIZE + SQRT_SIZE + i][j]);
|
2021-12-07 20:56:04 +04:00
|
|
|
assert_eq!(
|
|
|
|
dict[br[i][j]],
|
|
|
|
expected[SIZE + SQRT_SIZE + i][SIZE + SQRT_SIZE + j]
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
dict[mid[i][j]],
|
|
|
|
expected[2 * SQRT_SIZE + i][2 * SQRT_SIZE + j]
|
|
|
|
);
|
2017-03-06 00:38:25 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn samuraisudoku_easy() {
|
|
|
|
let puzzle = [
|
2021-12-07 20:56:04 +04:00
|
|
|
[
|
|
|
|
0, 0, 3, 0, 0, 0, 2, 0, 0, X, X, X, 0, 0, 6, 0, 0, 0, 2, 0, 0,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
0, 2, 0, 4, 0, 8, 0, 3, 0, X, X, X, 0, 3, 0, 4, 0, 2, 0, 8, 0,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
8, 0, 0, 0, 9, 0, 0, 0, 4, X, X, X, 8, 0, 0, 0, 1, 0, 0, 0, 4,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
0, 5, 0, 6, 0, 1, 0, 2, 0, X, X, X, 0, 2, 0, 1, 0, 7, 0, 9, 0,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
0, 0, 8, 0, 0, 0, 6, 0, 0, X, X, X, 0, 0, 9, 0, 0, 0, 8, 0, 0,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
0, 7, 0, 8, 0, 4, 0, 1, 0, X, X, X, 0, 8, 0, 5, 0, 9, 0, 4, 0,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
1, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 5,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
0, 4, 0, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 8, 0, 7, 0,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
X, X, X, X, X, X, 0, 0, 0, 5, 0, 1, 0, 0, 0, X, X, X, X, X, X,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
X, X, X, X, X, X, 0, 0, 9, 0, 0, 0, 6, 0, 0, X, X, X, X, X, X,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
X, X, X, X, X, X, 0, 0, 0, 3, 0, 6, 0, 0, 0, X, X, X, X, X, X,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
0, 4, 0, 5, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 5, 0, 7, 0,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
6, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 9,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
0, 9, 0, 1, 0, 3, 0, 7, 0, X, X, X, 0, 7, 0, 5, 0, 1, 0, 9, 0,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
0, 0, 5, 0, 0, 0, 1, 0, 0, X, X, X, 0, 0, 3, 0, 0, 0, 6, 0, 0,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
0, 1, 0, 6, 0, 8, 0, 9, 0, X, X, X, 0, 2, 0, 8, 0, 6, 0, 1, 0,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
5, 0, 0, 0, 7, 0, 0, 0, 6, X, X, X, 7, 0, 0, 0, 2, 0, 0, 0, 5,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
0, 2, 0, 3, 0, 5, 0, 1, 0, X, X, X, 0, 9, 0, 6, 0, 4, 0, 3, 0,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
0, 0, 6, 0, 0, 0, 2, 0, 0, X, X, X, 0, 0, 4, 0, 0, 0, 1, 0, 0,
|
|
|
|
],
|
|
|
|
];
|
2017-03-06 00:38:25 +04:00
|
|
|
|
|
|
|
let expected = [
|
2021-12-07 20:56:04 +04:00
|
|
|
[
|
|
|
|
4, 9, 3, 7, 1, 5, 2, 6, 8, X, X, X, 9, 4, 6, 8, 3, 5, 2, 1, 7,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
5, 2, 7, 4, 6, 8, 9, 3, 1, X, X, X, 1, 3, 7, 4, 9, 2, 5, 8, 6,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
8, 6, 1, 2, 9, 3, 5, 7, 4, X, X, X, 8, 5, 2, 7, 1, 6, 9, 3, 4,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
9, 5, 4, 6, 3, 1, 8, 2, 7, X, X, X, 5, 2, 4, 1, 8, 7, 6, 9, 3,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
3, 1, 8, 9, 2, 7, 6, 4, 5, X, X, X, 7, 1, 9, 6, 4, 3, 8, 5, 2,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
2, 7, 6, 8, 5, 4, 3, 1, 9, X, X, X, 6, 8, 3, 5, 2, 9, 7, 4, 1,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
1, 8, 2, 3, 7, 9, 4, 5, 6, 7, 1, 3, 2, 9, 8, 3, 7, 1, 4, 6, 5,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
6, 4, 5, 1, 8, 2, 7, 9, 3, 8, 5, 2, 4, 6, 1, 2, 5, 8, 3, 7, 9,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
7, 3, 9, 5, 4, 6, 1, 8, 2, 9, 6, 4, 3, 7, 5, 9, 6, 4, 1, 2, 8,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
X, X, X, X, X, X, 6, 4, 8, 5, 9, 1, 7, 2, 3, X, X, X, X, X, X,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
X, X, X, X, X, X, 3, 1, 9, 2, 8, 7, 6, 5, 4, X, X, X, X, X, X,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
X, X, X, X, X, X, 2, 7, 5, 3, 4, 6, 1, 8, 9, X, X, X, X, X, X,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
1, 7, 8, 9, 3, 6, 5, 2, 4, 1, 7, 8, 9, 3, 6, 2, 8, 7, 4, 5, 1,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
2, 4, 3, 5, 8, 1, 9, 6, 7, 4, 3, 5, 8, 1, 2, 9, 4, 5, 3, 7, 6,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
6, 5, 9, 4, 2, 7, 8, 3, 1, 6, 2, 9, 5, 4, 7, 1, 6, 3, 8, 2, 9,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
8, 9, 2, 1, 4, 3, 6, 7, 5, X, X, X, 6, 7, 8, 5, 3, 1, 2, 9, 4,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
4, 6, 5, 7, 9, 2, 1, 8, 3, X, X, X, 1, 5, 3, 4, 9, 2, 6, 8, 7,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
3, 1, 7, 6, 5, 8, 4, 9, 2, X, X, X, 4, 2, 9, 8, 7, 6, 5, 1, 3,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
5, 8, 1, 2, 7, 9, 3, 4, 6, X, X, X, 7, 6, 1, 3, 2, 8, 9, 4, 5,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
9, 2, 4, 3, 6, 5, 7, 1, 8, X, X, X, 2, 9, 5, 6, 1, 4, 7, 3, 8,
|
|
|
|
],
|
|
|
|
[
|
|
|
|
7, 3, 6, 8, 1, 4, 2, 5, 9, X, X, X, 3, 8, 4, 7, 5, 9, 1, 6, 2,
|
|
|
|
],
|
|
|
|
];
|
2017-03-06 00:38:25 +04:00
|
|
|
|
|
|
|
let (mut sys, vars) = make_samurai_sudoku(&puzzle);
|
|
|
|
let dict = sys.solve_any().expect("solution");
|
|
|
|
print_samurai_sudoku(&dict, &vars);
|
|
|
|
verify_samurai_sudoku(&dict, &vars, &expected);
|
|
|
|
println!("samuraisudoku_easy: {} guesses", sys.num_guesses());
|
|
|
|
}
|