use std::io::{self, stdout, Write};
use backtracking::{Problem, Solutions};
fn main() -> io::Result<()> {
let sudoku = Sudoku::from_bytes([
6, 0, 3, 0, 0, 0, 1, 0, 0, 0, 0, 9, 0, 0, 0, 2, 0, 0, 0, 0, 7, 4, 0, 9, 0, 0, 0, 0, 0, 0,
0, 1, 0, 0, 0, 7, 4, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 5, 3, 0, 1, 0, 0, 0, 0,
0, 4, 0, 0, 0, 6, 3, 0, 7, 0, 9, 0, 0, 9, 0, 0, 0, 2, 0, 3, 0,
]);
for solution in Solutions::new(sudoku).take(1) {
solution.print_to(&mut stdout())?
}
Ok(())
}
#[derive(Clone)]
pub struct Sudoku {
fields: [u8; 9 * 9],
}
impl Sudoku {
pub fn new() -> Self {
let fields = [0u8; 9 * 9];
Self::from_bytes(fields)
}
pub fn from_bytes(bytes: [u8; 9 * 9]) -> Self {
if bytes.iter().any(|&n| n > 9) {
panic!("Only values from 0 to 9 are valid.")
}
Self { fields: bytes }
}
pub fn print_to(&self, to: &mut impl Write) -> io::Result<()> {
for index in 0..self.fields.len() {
if index % 9 == 0 && index != 0 {
writeln!(to)?;
}
match self.fields[index] {
0 => write!(to, "X")?,
n @ 1..=9 => write!(to, "{n}")?,
_ => unreachable!(),
};
}
writeln!(to)?;
Ok(())
}
pub fn possible_digits_at(&self, index: u8) -> impl Iterator<Item = u8> + '_ {
let row = index as usize / 9;
let col = index as usize % 9;
let group = col / 3 + (row / 3) * 3;
let group_off = group * 3 + (group / 3) * 18;
let is_in_row = move |digit| (0..9).any(|c| self.fields[c + row * 9] == digit);
let is_in_col = move |digit| (0..9).any(|r| self.fields[col + r * 9] == digit);
let is_in_group =
move |digit| (0..9).any(|i| self.fields[group_off + i % 3 + (i / 3) * 9] == digit);
(1..=9)
.filter(move |digit| !is_in_row(*digit))
.filter(move |digit| !is_in_col(*digit))
.filter(move |digit| !is_in_group(*digit))
}
}
impl Default for Sudoku {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Copy)]
pub struct WriteDigit {
index: u8,
digit: u8,
}
impl Problem for Sudoku {
type Posibility = WriteDigit;
type Solution = Sudoku;
fn extend_possibilities(&self, possible_moves: &mut Vec<WriteDigit>, _history: &[WriteDigit]) {
if let Some(index) = self.fields.iter().position(|value| *value == 0) {
let index = index as u8;
let mut all_possible_digits = self.possible_digits_at(index);
if let Some(digit) = all_possible_digits.next() {
possible_moves.push(WriteDigit { index, digit });
} else {
possible_moves.clear();
return;
}
possible_moves.extend(all_possible_digits.map(|digit| WriteDigit { index, digit }));
}
}
fn undo(&mut self, last: &WriteDigit, _history: &[WriteDigit]) {
self.fields[last.index as usize] = 0;
}
fn what_if(&mut self, move_: WriteDigit) {
self.fields[move_.index as usize] = move_.digit;
}
fn is_solution(&self, _history: &[WriteDigit]) -> Option<Self::Solution> {
if self.fields.iter().all(|digit| *digit != 0) {
Some(self.clone())
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use crate::lib::Game;
use super::{Sudoku, WriteDigit};
#[test]
fn print_empty_sudoku() {
let mut out = Vec::new();
let game = Sudoku::new();
game.print_to(&mut out).unwrap();
let expect = "XXXXXXXXX\n\
XXXXXXXXX\n\
XXXXXXXXX\n\
XXXXXXXXX\n\
XXXXXXXXX\n\
XXXXXXXXX\n\
XXXXXXXXX\n\
XXXXXXXXX\n\
XXXXXXXXX\n\
";
assert_eq!(expect, std::str::from_utf8(&out).unwrap());
}
#[test]
fn print_with_first_row_filled() {
let mut out = Vec::new();
let mut game = Sudoku::new();
game.play_move(WriteDigit { index: 0, digit: 1 });
game.play_move(WriteDigit { index: 1, digit: 2 });
game.play_move(WriteDigit { index: 2, digit: 3 });
game.play_move(WriteDigit { index: 3, digit: 4 });
game.play_move(WriteDigit { index: 4, digit: 5 });
game.play_move(WriteDigit { index: 5, digit: 6 });
game.play_move(WriteDigit { index: 6, digit: 7 });
game.play_move(WriteDigit { index: 7, digit: 8 });
game.play_move(WriteDigit { index: 8, digit: 9 });
game.print_to(&mut out).unwrap();
let expect = "123456789\n\
XXXXXXXXX\n\
XXXXXXXXX\n\
XXXXXXXXX\n\
XXXXXXXXX\n\
XXXXXXXXX\n\
XXXXXXXXX\n\
XXXXXXXXX\n\
XXXXXXXXX\n\
";
assert_eq!(expect, std::str::from_utf8(&out).unwrap());
}
#[test]
fn prevent_same_digit_twice_in_same_row() {
let mut game = Sudoku::new();
game.play_move(WriteDigit { index: 0, digit: 2 });
game.play_move(WriteDigit { index: 8, digit: 5 });
game.play_move(WriteDigit {
index: 7 * 9 + 6,
digit: 5,
});
let possibilities = game.possible_digits_at(1).collect::<Vec<u8>>();
assert_eq!(&[1u8, 3, 4, 6, 7, 8, 9][..], possibilities);
}
#[test]
fn prevent_same_digit_twice_in_same_col() {
let mut game = Sudoku::new();
game.play_move(WriteDigit { index: 3, digit: 2 });
game.play_move(WriteDigit {
index: 3 + 9 * 5,
digit: 5,
});
let possibilities = game.possible_digits_at(3 + 9 * 2).collect::<Vec<u8>>();
assert_eq!(&[1u8, 3, 4, 6, 7, 8, 9][..], possibilities);
}
#[test]
fn short_ciruct_if_one_field_has_no_more_possibile_digits() {
let mut game = Sudoku::new();
game.play_move(WriteDigit { index: 0, digit: 1 });
game.play_move(WriteDigit { index: 1, digit: 2 });
game.play_move(WriteDigit { index: 2, digit: 3 });
game.play_move(WriteDigit { index: 3, digit: 4 });
game.play_move(WriteDigit { index: 4, digit: 5 });
game.play_move(WriteDigit { index: 5, digit: 6 });
game.play_move(WriteDigit { index: 6, digit: 7 });
game.play_move(WriteDigit { index: 7, digit: 8 });
game.play_move(WriteDigit {
index: 9 + 8,
digit: 9,
});
let mut possible_moves = Vec::new();
game.fill_possible_moves(&mut possible_moves);
assert_eq!(0, game.possible_digits_at(8).count());
assert!(possible_moves.is_empty());
}
}