1use std::io::{self, stdout, Write};
2
3use backtracking::{Problem, Solutions};
4
5fn main() -> io::Result<()> {
6 let sudoku = Sudoku::from_bytes([
8 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,
9 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,
10 0, 4, 0, 0, 0, 6, 3, 0, 7, 0, 9, 0, 0, 9, 0, 0, 0, 2, 0, 3, 0,
11 ]);
12 for solution in Solutions::new(sudoku).take(1) {
13 solution.print_to(&mut stdout())?
14 }
15 Ok(())
16}
17
18#[derive(Clone)]
19pub struct Sudoku {
20 fields: [u8; 9 * 9],
23}
24
25impl Sudoku {
26 pub fn new() -> Self {
27 let fields = [0u8; 9 * 9];
28 Self::from_bytes(fields)
29 }
30
31 pub fn from_bytes(bytes: [u8; 9 * 9]) -> Self {
32 if bytes.iter().any(|&n| n > 9) {
33 panic!("Only values from 0 to 9 are valid.")
34 }
35 Self { fields: bytes }
36 }
37
38 pub fn print_to(&self, to: &mut impl Write) -> io::Result<()> {
39 for index in 0..self.fields.len() {
40 if index % 9 == 0 && index != 0 {
42 writeln!(to)?;
43 }
44 match self.fields[index] {
45 0 => write!(to, "X")?,
46 n @ 1..=9 => write!(to, "{n}")?,
47 _ => unreachable!(),
48 };
49 }
50 writeln!(to)?;
51 Ok(())
52 }
53
54 pub fn possible_digits_at(&self, index: u8) -> impl Iterator<Item = u8> + '_ {
55 let row = index as usize / 9;
56 let col = index as usize % 9;
57 let group = col / 3 + (row / 3) * 3;
58 let group_off = group * 3 + (group / 3) * 18;
60 let is_in_row = move |digit| (0..9).any(|c| self.fields[c + row * 9] == digit);
61 let is_in_col = move |digit| (0..9).any(|r| self.fields[col + r * 9] == digit);
62 let is_in_group =
63 move |digit| (0..9).any(|i| self.fields[group_off + i % 3 + (i / 3) * 9] == digit);
64 (1..=9)
65 .filter(move |digit| !is_in_row(*digit))
66 .filter(move |digit| !is_in_col(*digit))
67 .filter(move |digit| !is_in_group(*digit))
68 }
69}
70
71impl Default for Sudoku {
72 fn default() -> Self {
73 Self::new()
74 }
75}
76
77#[derive(Clone, Copy)]
78pub struct WriteDigit {
79 index: u8,
80 digit: u8,
81}
82
83impl Problem for Sudoku {
84 type Posibility = WriteDigit;
85 type Solution = Sudoku;
86
87 fn extend_possibilities(&self, possible_moves: &mut Vec<WriteDigit>, _history: &[WriteDigit]) {
89 if let Some(index) = self.fields.iter().position(|value| *value == 0) {
90 let index = index as u8;
91 let mut all_possible_digits = self.possible_digits_at(index);
92 if let Some(digit) = all_possible_digits.next() {
95 possible_moves.push(WriteDigit { index, digit });
96 } else {
97 possible_moves.clear();
101 return;
102 }
103 possible_moves.extend(all_possible_digits.map(|digit| WriteDigit { index, digit }));
105 }
106 }
107
108 fn undo(&mut self, last: &WriteDigit, _history: &[WriteDigit]) {
109 self.fields[last.index as usize] = 0;
110 }
111
112 fn what_if(&mut self, move_: WriteDigit) {
113 self.fields[move_.index as usize] = move_.digit;
114 }
115
116 fn is_solution(&self, _history: &[WriteDigit]) -> Option<Self::Solution> {
117 if self.fields.iter().all(|digit| *digit != 0) {
118 Some(self.clone())
119 } else {
120 None
121 }
122 }
123}
124
125#[cfg(test)]
126mod tests {
127 use crate::lib::Game;
128
129 use super::{Sudoku, WriteDigit};
130
131 #[test]
132 fn print_empty_sudoku() {
133 let mut out = Vec::new();
134 let game = Sudoku::new();
135
136 game.print_to(&mut out).unwrap();
137
138 let expect = "XXXXXXXXX\n\
139 XXXXXXXXX\n\
140 XXXXXXXXX\n\
141 XXXXXXXXX\n\
142 XXXXXXXXX\n\
143 XXXXXXXXX\n\
144 XXXXXXXXX\n\
145 XXXXXXXXX\n\
146 XXXXXXXXX\n\
147 ";
148 assert_eq!(expect, std::str::from_utf8(&out).unwrap());
149 }
150
151 #[test]
152 fn print_with_first_row_filled() {
153 let mut out = Vec::new();
154 let mut game = Sudoku::new();
155 game.play_move(WriteDigit { index: 0, digit: 1 });
156 game.play_move(WriteDigit { index: 1, digit: 2 });
157 game.play_move(WriteDigit { index: 2, digit: 3 });
158 game.play_move(WriteDigit { index: 3, digit: 4 });
159 game.play_move(WriteDigit { index: 4, digit: 5 });
160 game.play_move(WriteDigit { index: 5, digit: 6 });
161 game.play_move(WriteDigit { index: 6, digit: 7 });
162 game.play_move(WriteDigit { index: 7, digit: 8 });
163 game.play_move(WriteDigit { index: 8, digit: 9 });
164
165 game.print_to(&mut out).unwrap();
166
167 let expect = "123456789\n\
168 XXXXXXXXX\n\
169 XXXXXXXXX\n\
170 XXXXXXXXX\n\
171 XXXXXXXXX\n\
172 XXXXXXXXX\n\
173 XXXXXXXXX\n\
174 XXXXXXXXX\n\
175 XXXXXXXXX\n\
176 ";
177 assert_eq!(expect, std::str::from_utf8(&out).unwrap());
178 }
179
180 #[test]
181 fn prevent_same_digit_twice_in_same_row() {
182 let mut game = Sudoku::new();
183 game.play_move(WriteDigit { index: 0, digit: 2 });
184 game.play_move(WriteDigit { index: 8, digit: 5 });
185 game.play_move(WriteDigit {
187 index: 7 * 9 + 6,
188 digit: 5,
189 });
190
191 let possibilities = game.possible_digits_at(1).collect::<Vec<u8>>();
192
193 assert_eq!(&[1u8, 3, 4, 6, 7, 8, 9][..], possibilities);
194 }
195
196 #[test]
197 fn prevent_same_digit_twice_in_same_col() {
198 let mut game = Sudoku::new();
199 game.play_move(WriteDigit { index: 3, digit: 2 });
200 game.play_move(WriteDigit {
201 index: 3 + 9 * 5,
202 digit: 5,
203 });
204
205 let possibilities = game.possible_digits_at(3 + 9 * 2).collect::<Vec<u8>>();
206
207 assert_eq!(&[1u8, 3, 4, 6, 7, 8, 9][..], possibilities);
208 }
209
210 #[test]
211 fn short_ciruct_if_one_field_has_no_more_possibile_digits() {
212 let mut game = Sudoku::new();
213 game.play_move(WriteDigit { index: 0, digit: 1 });
214 game.play_move(WriteDigit { index: 1, digit: 2 });
215 game.play_move(WriteDigit { index: 2, digit: 3 });
216 game.play_move(WriteDigit { index: 3, digit: 4 });
217 game.play_move(WriteDigit { index: 4, digit: 5 });
218 game.play_move(WriteDigit { index: 5, digit: 6 });
219 game.play_move(WriteDigit { index: 6, digit: 7 });
220 game.play_move(WriteDigit { index: 7, digit: 8 });
221 game.play_move(WriteDigit {
222 index: 9 + 8,
223 digit: 9,
224 });
225
226 let mut possible_moves = Vec::new();
227 game.fill_possible_moves(&mut possible_moves);
228
229 assert_eq!(0, game.possible_digits_at(8).count());
230 assert!(possible_moves.is_empty());
231 }
232}