1use crate::sat::clause_storage::LiteralStorage;
2use crate::sat::cnf::Cnf;
3use crate::sat::literal::Literal;
4use crate::sat::solver::Solutions;
5use itertools::Itertools;
6use std::fmt::Display;
7use std::num::NonZeroI32;
8use std::path::PathBuf;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct Board(Vec<Vec<usize>>);
15
16impl Display for Board {
17 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18 for row in &self.0 {
19 writeln!(
20 f,
21 "{}",
22 row.iter()
23 .map(std::string::ToString::to_string)
24 .collect_vec()
25 .join(" ")
26 )?;
27 }
28 Ok(())
29 }
30}
31
32impl Board {
33 #[must_use]
39 pub const fn new(board: Vec<Vec<usize>>) -> Self {
40 Self(board)
41 }
42}
43
44impl From<Vec<Vec<usize>>> for Board {
45 fn from(board: Vec<Vec<usize>>) -> Self {
47 Self::new(board)
48 }
49}
50
51impl From<Board> for Vec<Vec<usize>> {
52 fn from(board: Board) -> Self {
54 board.0
55 }
56}
57
58impl From<&Board> for Vec<Vec<usize>> {
59 fn from(board: &Board) -> Self {
61 board.0.clone()
62 }
63}
64
65impl<const N: usize> From<&[&[usize; N]; N]> for Board {
66 fn from(board: &[&[usize; N]; N]) -> Self {
71 Self::new(board.iter().map(|r| r.to_vec()).collect())
72 }
73}
74
75#[allow(dead_code)]
77pub const EXAMPLE_FOUR: [[usize; 4]; 4] = [[2, 3, 0, 0], [4, 0, 0, 1], [0, 1, 2, 0], [0, 0, 0, 0]];
78
79#[allow(dead_code)]
81pub const EXAMPLE_NINE: [[usize; 9]; 9] = [
82 [5, 3, 0, 0, 7, 0, 0, 0, 0],
83 [6, 0, 0, 1, 9, 5, 0, 0, 0],
84 [0, 9, 8, 0, 0, 0, 0, 6, 0],
85 [8, 0, 0, 0, 6, 0, 0, 0, 3],
86 [4, 0, 0, 8, 0, 3, 0, 0, 1],
87 [7, 0, 0, 0, 2, 0, 0, 0, 6],
88 [0, 6, 0, 0, 0, 0, 2, 8, 0],
89 [0, 0, 0, 4, 1, 9, 0, 0, 5],
90 [0, 0, 0, 0, 8, 0, 0, 7, 9],
91];
92
93#[allow(dead_code)]
95pub const EXAMPLE_SIXTEEN: [[usize; 16]; 16] = [
96 [0, 11, 0, 0, 0, 2, 3, 14, 0, 0, 9, 12, 0, 0, 0, 16],
97 [15, 12, 0, 0, 0, 11, 0, 1, 13, 10, 0, 0, 0, 0, 7, 2],
98 [0, 0, 10, 0, 0, 0, 0, 0, 16, 11, 0, 1, 6, 4, 12, 3],
99 [0, 16, 14, 1, 0, 4, 0, 6, 0, 3, 0, 15, 0, 8, 0, 0],
100 [1, 6, 5, 12, 0, 0, 11, 0, 0, 9, 8, 0, 0, 0, 0, 0],
101 [0, 0, 0, 7, 14, 1, 8, 0, 0, 15, 6, 0, 13, 5, 0, 4],
102 [4, 15, 8, 0, 9, 13, 0, 0, 0, 0, 7, 16, 3, 0, 0, 0],
103 [0, 9, 13, 0, 0, 0, 0, 15, 10, 0, 0, 0, 7, 6, 0, 11],
104 [14, 0, 6, 11, 0, 0, 0, 12, 7, 0, 0, 0, 0, 3, 13, 0],
105 [0, 0, 0, 5, 8, 14, 0, 0, 0, 0, 13, 11, 0, 1, 2, 6],
106 [13, 0, 16, 4, 0, 15, 5, 0, 0, 1, 12, 6, 8, 0, 0, 0],
107 [0, 0, 0, 0, 0, 16, 10, 0, 0, 8, 0, 0, 11, 9, 4, 5],
108 [0, 0, 11, 0, 1, 0, 14, 0, 5, 0, 3, 0, 15, 7, 16, 0],
109 [5, 13, 15, 3, 16, 0, 4, 7, 0, 0, 0, 0, 0, 2, 0, 0],
110 [16, 1, 0, 0, 0, 0, 12, 2, 14, 0, 15, 0, 0, 0, 3, 8],
111 [9, 0, 0, 0, 13, 5, 0, 0, 8, 6, 16, 0, 0, 0, 10, 0],
112];
113
114#[allow(dead_code)]
116pub const EXAMPLE_TWENTY_FIVE: [[usize; 25]; 25] = [
117 [
118 0, 2, 0, 0, 0, 3, 14, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 13, 4, 24, 0, 7, 1, 0, 0,
119 ],
120 [
121 0, 10, 17, 0, 0, 0, 6, 18, 0, 0, 22, 16, 0, 12, 0, 0, 0, 0, 1, 0, 0, 0, 13, 19, 0,
122 ],
123 [
124 0, 15, 24, 13, 7, 0, 0, 0, 4, 0, 10, 0, 0, 3, 14, 0, 18, 0, 0, 0, 0, 22, 2, 6, 0,
125 ],
126 [
127 0, 0, 1, 21, 0, 0, 15, 0, 22, 0, 0, 19, 13, 0, 0, 0, 8, 0, 0, 0, 0, 16, 18, 20, 0,
128 ],
129 [
130 0, 5, 0, 0, 20, 7, 25, 19, 0, 0, 0, 21, 17, 18, 2, 10, 12, 22, 9, 15, 11, 0, 0, 0, 0,
131 ],
132 [
133 11, 0, 0, 0, 22, 8, 0, 24, 7, 1, 5, 0, 0, 0, 13, 16, 17, 25, 23, 2, 4, 0, 6, 0, 19,
134 ],
135 [
136 16, 9, 12, 0, 17, 0, 19, 22, 0, 0, 0, 0, 18, 21, 0, 0, 20, 6, 13, 0, 7, 0, 0, 23, 11,
137 ],
138 [
139 0, 0, 6, 0, 21, 9, 16, 0, 3, 0, 0, 22, 20, 19, 0, 0, 0, 0, 15, 8, 25, 0, 0, 0, 0,
140 ],
141 [
142 0, 0, 23, 5, 0, 2, 0, 0, 11, 17, 8, 0, 0, 0, 16, 12, 9, 0, 0, 21, 0, 3, 10, 0, 0,
143 ],
144 [
145 0, 0, 0, 0, 0, 6, 0, 0, 12, 0, 9, 1, 25, 0, 3, 0, 11, 0, 0, 7, 0, 0, 21, 0, 0,
146 ],
147 [
148 0, 0, 9, 0, 0, 23, 0, 5, 17, 4, 16, 0, 11, 0, 22, 18, 2, 0, 21, 13, 0, 0, 7, 0, 0,
149 ],
150 [
151 4, 6, 0, 0, 5, 0, 0, 2, 0, 0, 0, 18, 21, 24, 0, 0, 19, 3, 0, 12, 23, 0, 0, 17, 0,
152 ],
153 [
154 0, 0, 0, 12, 11, 0, 7, 3, 0, 24, 17, 20, 15, 13, 19, 1, 0, 5, 8, 0, 6, 9, 0, 0, 0,
155 ],
156 [
157 0, 22, 0, 0, 14, 19, 0, 6, 16, 0, 0, 8, 9, 7, 0, 0, 0, 24, 0, 0, 3, 0, 0, 1, 18,
158 ],
159 [
160 0, 0, 21, 0, 0, 25, 13, 0, 20, 8, 12, 0, 14, 0, 10, 9, 16, 15, 0, 6, 0, 0, 4, 0, 0,
161 ],
162 [
163 0, 0, 25, 0, 0, 24, 0, 0, 18, 0, 4, 0, 3, 10, 5, 0, 1, 0, 0, 14, 0, 0, 0, 0, 0,
164 ],
165 [
166 0, 0, 5, 3, 0, 17, 0, 0, 23, 7, 13, 0, 0, 0, 18, 19, 21, 0, 0, 22, 0, 11, 12, 0, 0,
167 ],
168 [
169 0, 0, 0, 0, 18, 10, 8, 0, 0, 0, 0, 25, 23, 2, 0, 0, 5, 0, 16, 11, 9, 0, 3, 0, 0,
170 ],
171 [
172 17, 20, 0, 0, 2, 0, 22, 16, 6, 0, 0, 7, 12, 0, 0, 0, 0, 9, 3, 0, 18, 0, 23, 24, 25,
173 ],
174 [
175 6, 0, 4, 0, 16, 1, 11, 12, 25, 3, 19, 0, 0, 0, 21, 17, 23, 8, 0, 18, 2, 0, 0, 0, 14,
176 ],
177 [
178 0, 0, 0, 0, 4, 14, 24, 11, 19, 23, 21, 17, 16, 8, 0, 0, 0, 1, 2, 9, 13, 0, 0, 5, 0,
179 ],
180 [
181 0, 1, 14, 23, 0, 0, 0, 0, 9, 0, 0, 0, 19, 5, 0, 0, 24, 0, 12, 0, 0, 8, 17, 0, 0,
182 ],
183 [
184 0, 16, 11, 8, 0, 0, 0, 0, 1, 0, 6, 4, 0, 0, 23, 0, 15, 0, 0, 0, 14, 12, 9, 10, 0,
185 ],
186 [
187 0, 21, 3, 0, 0, 0, 17, 0, 0, 0, 0, 15, 0, 25, 20, 0, 0, 4, 10, 0, 0, 0, 16, 11, 0,
188 ],
189 [
190 0, 0, 20, 2, 0, 16, 5, 8, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 19, 25, 0, 0, 0, 3, 0,
191 ],
192];
193
194#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
196pub enum Size {
197 Four = 4,
199 Nine = 9,
201 Sixteen = 16,
203 TwentyFive = 25,
205}
206
207impl TryFrom<usize> for Size {
208 type Error = ();
209
210 fn try_from(value: usize) -> Result<Self, Self::Error> {
215 match value {
216 4 => Ok(Self::Four),
217 9 => Ok(Self::Nine),
218 16 => Ok(Self::Sixteen),
219 25 => Ok(Self::TwentyFive),
220 _ => Err(()),
221 }
222 }
223}
224
225impl From<Size> for usize {
226 fn from(size: Size) -> Self {
228 match size {
229 Size::Four => 4,
230 Size::Nine => 9,
231 Size::Sixteen => 16,
232 Size::TwentyFive => 25,
233 }
234 }
235}
236
237impl Display for Size {
238 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
240 let size: usize = (*self).into();
241 write!(f, "{size}x{size}")
242 }
243}
244
245#[derive(Debug, Clone, PartialEq, Eq)]
247pub struct Sudoku {
248 pub board: Board,
250 pub size: Size,
252}
253
254impl Display for Sudoku {
255 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
257 writeln!(f, "Sudoku {{ size: {} }}", self.size)?;
258 write!(f, "{}", self.board)
259 }
260}
261
262#[derive(Debug, Clone, Copy, PartialEq, Eq)]
267pub struct Variable {
268 pub row: usize,
270 pub col: usize,
272 pub num: usize,
274}
275
276impl Variable {
277 #[must_use]
285 pub const fn new(row: usize, col: usize, num: usize) -> Self {
286 Self { row, col, num }
287 }
288
289 #[must_use]
301 pub const fn encode(&self, size: Size) -> usize {
302 let board_size = size as usize;
303 let row = self.row;
304 let col = self.col;
305 let num = self.num;
306
307 ((row - 1) * board_size * board_size + (col - 1) * board_size + (num - 1)) + 1
308 }
309}
310
311impl Size {
312 #[must_use]
315 pub const fn block_size(self) -> usize {
316 match self {
317 Self::Four => 2,
318 Self::Nine => 3,
319 Self::Sixteen => 4,
320 Self::TwentyFive => 5,
321 }
322 }
323}
324
325fn generate_cell_clauses(size: usize) -> Vec<Vec<i32>> {
328 let mut clauses = vec![];
329 for row in 1..=size {
330 for col in 1..=size {
331 let clause = (1..=size)
332 .map(|num| {
333 i32::try_from(
334 Variable::new(row, col, num)
335 .encode(Size::try_from(size).expect("Invalid size")),
336 )
337 .expect("Invalid variable")
338 })
339 .collect();
340
341 clauses.push(clause);
342 }
343 }
344 clauses
345}
346
347fn generate_row_clauses(size: usize) -> Vec<Vec<i32>> {
350 let mut clauses = vec![];
351 for row in 1..=size {
352 for num in 1..=size {
353 for col1 in 1..=size {
354 for col2 in (col1 + 1)..=size {
355 if col1 > col2 {
356 continue;
357 }
358
359 let var1 = Variable::new(row, col1, num);
360 let var2 = Variable::new(row, col2, num);
361
362 clauses.push(vec![
363 -i32::try_from(var1.encode(Size::try_from(size).expect("Invalid size")))
364 .expect("Invalid variable"),
365 -i32::try_from(var2.encode(Size::try_from(size).expect("Invalid size")))
366 .expect("Invalid variable"),
367 ]);
368 }
369 }
370 }
371 }
372 clauses
373}
374
375fn generate_col_clauses(size: usize) -> Vec<Vec<i32>> {
378 let mut clauses = vec![];
379 for col in 1..=size {
380 for num in 1..=size {
381 for row1 in 1..=size {
382 for row2 in (row1 + 1)..=size {
383 let var1 = Variable::new(row1, col, num);
384 let var2 = Variable::new(row2, col, num);
385
386 clauses.push(vec![
387 -i32::try_from(var1.encode(Size::try_from(size).expect("Invalid size")))
388 .expect("Invalid variable"),
389 -i32::try_from(var2.encode(Size::try_from(size).expect("Invalid size")))
390 .expect("Invalid variable"),
391 ]);
392 }
393 }
394 }
395 }
396 clauses
397}
398
399fn generate_block_clauses(board_size: usize, block_size: usize) -> Vec<Vec<i32>> {
403 let mut clauses = Vec::new();
404 for n in 1..=board_size {
405 for br_start_idx in (0..board_size).step_by(block_size) {
406 for bc_start_idx in (0..board_size).step_by(block_size) {
407 let mut block_cells = Vec::new();
408 for r_offset in 0..block_size {
409 for c_offset in 0..block_size {
410 block_cells
411 .push((br_start_idx + r_offset + 1, bc_start_idx + c_offset + 1));
412 }
413 }
414
415 for i in 0..block_cells.len() {
416 for j in i + 1..block_cells.len() {
417 let (r1, c1) = block_cells[i];
418 let (r2, c2) = block_cells[j];
419 let var1 = Variable::new(r1, c1, n);
420 let var2 = Variable::new(r2, c2, n);
421
422 clauses.push(vec![
423 -i32::try_from(
424 var1.encode(Size::try_from(board_size).expect("Invalid size")),
425 )
426 .expect("Invalid variable"),
427 -i32::try_from(
428 var2.encode(Size::try_from(board_size).expect("Invalid size")),
429 )
430 .expect("Invalid variable"),
431 ]);
432 }
433 }
434 }
435 }
436 }
437 clauses
438}
439
440fn generate_pre_filled_clauses(size: usize, board: &Board) -> Vec<Vec<i32>> {
443 let mut clauses = vec![];
444 for (r_idx, row_vec) in board.0.iter().enumerate() {
445 for (c_idx, &n_val) in row_vec.iter().enumerate() {
446 if n_val != 0 {
447 let var = Variable::new(r_idx + 1, c_idx + 1, n_val);
448 clauses.push(vec![
449 i32::try_from(var.encode(Size::try_from(size).expect("Invalid size")))
450 .expect("Invalid variable"),
451 ]);
452 }
453 }
454 }
455 clauses
456}
457
458impl Sudoku {
459 pub fn new(board: Board) -> Result<Self, String> {
475 let size_val = board.0.len();
476 let size = Size::try_from(size_val).map_err(|()| {
477 format!("Invalid Sudoku size: {size_val}. Supported sizes are 4, 9, 16, or 25.",)
478 })?;
479 Ok(Self { board, size })
480 }
481
482 #[must_use]
499 pub fn decode(&self, solutions: &Solutions) -> Self {
500 let size_val: usize = self.size.into();
501 let mut board_vec = vec![vec![0; size_val]; size_val];
502 for row in 1..=size_val {
503 for col in 1..=size_val {
504 for num in 1..=size_val {
505 let var = Variable::new(row, col, num);
506 #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
507 let encoded_var = NonZeroI32::new(var.encode(self.size) as i32)
508 .expect("Encoded variable should not be zero");
509 if solutions.check(encoded_var) {
510 board_vec[row - 1][col - 1] = num;
511 }
512 }
513 }
514 }
515 Self {
516 board: Board::new(board_vec),
517 size: self.size,
518 }
519 }
520
521 #[must_use]
548 pub fn to_cnf<L: Literal, S: LiteralStorage<L>>(&self) -> Cnf<L, S> {
549 let size_val = self.size as usize;
550 let block_size_val = self.size.block_size();
551
552 let cell_clauses = generate_cell_clauses(size_val);
553 let row_clauses = generate_row_clauses(size_val);
554 let col_clauses = generate_col_clauses(size_val);
555 let block_clauses = generate_block_clauses(size_val, block_size_val);
556 let pre_filled_clauses = generate_pre_filled_clauses(size_val, &self.board);
557
558 let clauses: Vec<Vec<i32>> = cell_clauses
559 .into_iter()
560 .chain(row_clauses)
561 .chain(col_clauses)
562 .chain(block_clauses)
563 .chain(pre_filled_clauses)
564 .collect();
565
566 Cnf::new(clauses)
567 }
568
569 pub fn iter(&self) -> impl Iterator<Item = Vec<usize>> {
572 self.board.0.clone().into_iter()
573 }
574
575 pub fn from_string(sudoku_str: &str) -> Result<Self, String> {
587 parse_sudoku(sudoku_str)
588 }
589
590 pub fn from_file(file_path: &PathBuf) -> Result<Self, String> {
603 parse_sudoku_file(file_path)
604 }
605}
606
607impl TryFrom<Board> for Sudoku {
608 type Error = String;
609 fn try_from(board: Board) -> Result<Self, Self::Error> {
614 Self::new(board)
615 }
616}
617
618impl From<Sudoku> for Board {
619 fn from(sudoku: Sudoku) -> Self {
621 sudoku.board
622 }
623}
624
625pub fn parse_sudoku(sudoku: &str) -> Result<Sudoku, String> {
656 let lines = sudoku.lines().collect_vec();
657 let size = lines.len();
658 let mut board = vec![vec![0; size]; size];
659
660 for (i, line) in lines.iter().enumerate() {
661 if line.is_empty() {
662 continue;
663 }
664 for (j, c) in line.split_ascii_whitespace().enumerate() {
665 if j >= size {
666 return Err(format!(
667 "Invalid Sudoku: Too many numbers in line {}",
668 i + 1
669 ));
670 }
671
672 if let Ok(num) = c.parse::<usize>() {
673 if num > size {
674 return Err(format!("Invalid Sudoku: Number {num} exceeds size {size}"));
675 }
676 board[i][j] = num;
677 } else {
678 return Err(format!("Invalid Sudoku: Invalid character {c}"));
679 }
680 }
681 }
682
683 Sudoku::new(Board::new(board))
684}
685
686pub fn parse_sudoku_file(file_path: &PathBuf) -> Result<Sudoku, String> {
706 let content = std::fs::read_to_string(file_path).map_err(|e| e.to_string())?;
707 parse_sudoku(&content)
708}
709
710#[cfg(test)]
711mod tests {
712 use super::*;
713 use std::collections::HashSet;
714 #[test]
715 fn board_new_and_display() {
716 let data = vec![vec![1, 0], vec![0, 2]];
717 let board = Board::new(data.clone());
718 assert_eq!(board.0, data);
719 assert_eq!(format!("{board}"), "1 0\n0 2\n");
720 }
721
722 #[test]
723 fn board_from_vec_vec() {
724 let data = vec![vec![1], vec![2]];
725 let board: Board = data.clone().into();
726 assert_eq!(board.0, data);
727 }
728
729 #[test]
730 fn vec_vec_from_board() {
731 let data = vec![vec![1], vec![2]];
732 let board = Board::new(data.clone());
733 let converted_data: Vec<Vec<usize>> = board.into();
734 assert_eq!(converted_data, data);
735 }
736
737 #[test]
738 fn vec_vec_from_ref_board() {
739 let data = vec![vec![1], vec![2]];
740 let board = Board::new(data.clone());
741 let converted_data: Vec<Vec<usize>> = (&board).into();
742 assert_eq!(converted_data, data);
743 }
744
745 #[test]
746 fn board_from_array_of_refs() {
747 let r1: [usize; 2] = [1, 0];
748 let r2: [usize; 2] = [0, 2];
749 let board_data: [&[usize; 2]; 2] = [&r1, &r2];
750 let board = Board::from(&board_data);
751 assert_eq!(board, Board::new(vec![vec![1, 0], vec![0, 2]]));
752 }
753
754 #[test]
755 fn size_try_from_usize() {
756 assert_eq!(Size::try_from(4), Ok(Size::Four));
757 assert_eq!(Size::try_from(9), Ok(Size::Nine));
758 assert_eq!(Size::try_from(16), Ok(Size::Sixteen));
759 assert_eq!(Size::try_from(25), Ok(Size::TwentyFive));
760 assert_eq!(Size::try_from(1), Err(()));
761 assert_eq!(Size::try_from(0), Err(()));
762 }
763
764 #[test]
765 fn usize_from_size() {
766 assert_eq!(usize::from(Size::Four), 4);
767 assert_eq!(usize::from(Size::Nine), 9);
768 assert_eq!(usize::from(Size::Sixteen), 16);
769 assert_eq!(usize::from(Size::TwentyFive), 25);
770 }
771
772 #[test]
773 fn size_display() {
774 assert_eq!(format!("{}", Size::Four), "4x4");
775 assert_eq!(format!("{}", Size::Nine), "9x9");
776 }
777
778 #[test]
779 fn size_block_size() {
780 assert_eq!(Size::Four.block_size(), 2);
781 assert_eq!(Size::Nine.block_size(), 3);
782 assert_eq!(Size::Sixteen.block_size(), 4);
783 assert_eq!(Size::TwentyFive.block_size(), 5);
784 }
785
786 #[test]
787 fn variable_new_and_encode() {
788 let var = Variable::new(1, 1, 1);
789 assert_eq!(
790 var,
791 Variable {
792 row: 1,
793 col: 1,
794 num: 1
795 }
796 );
797
798 assert_eq!(Variable::new(1, 1, 1).encode(Size::Four), 1); assert_eq!(Variable::new(1, 1, 4).encode(Size::Four), 4); assert_eq!(Variable::new(1, 2, 1).encode(Size::Four), 5); assert_eq!(Variable::new(2, 1, 1).encode(Size::Four), 17); assert_eq!(Variable::new(4, 4, 4).encode(Size::Four), 64); assert_eq!(Variable::new(1, 1, 1).encode(Size::Nine), 1);
805 assert_eq!(Variable::new(9, 9, 9).encode(Size::Nine), 729); }
807
808 #[test]
810 fn sudoku_new() {
811 let board_data = vec![vec![0; 4]; 4];
812 let board = Board::new(board_data);
813 let sudoku = Sudoku::new(board.clone());
814 assert!(sudoku.is_ok());
815 let sudoku = sudoku.unwrap();
816 assert_eq!(sudoku.board, board);
817 assert_eq!(sudoku.size, Size::Four);
818 }
819
820 #[test]
821 #[should_panic(expected = "Invalid size for Sudoku board")]
822 fn sudoku_new_invalid_size() {
823 let board_data = vec![vec![0; 5]; 5];
824 let board = Board::new(board_data);
825 let _ = Sudoku::new(board);
826 }
827
828 #[test]
829 fn sudoku_display() {
830 let board_data = vec![
831 vec![1, 0, 0, 0],
832 vec![0, 2, 0, 0],
833 vec![0, 0, 3, 0],
834 vec![0, 0, 0, 4],
835 ];
836 let sudoku = Sudoku::new(Board::new(board_data));
837 assert!(sudoku.is_ok());
838 let sudoku = sudoku.unwrap();
839 let expected_display = "Sudoku { size: 4x4 }\n1 0 0 0\n0 2 0 0\n0 0 3 0\n0 0 0 4\n";
840 assert_eq!(format!("{sudoku}"), expected_display);
841 }
842
843 #[test]
844 fn sudoku_iter() {
845 let sudoku_board_data = vec![
846 vec![1, 2, 0, 0],
847 vec![3, 4, 0, 0],
848 vec![0, 0, 0, 0],
849 vec![0, 0, 0, 0],
850 ];
851 let sudoku = Sudoku::try_from(Board::from(sudoku_board_data));
852 assert!(sudoku.is_ok());
853 let sudoku = sudoku.unwrap();
854 let mut iter = sudoku.iter();
855 assert_eq!(iter.next(), Some(vec![1, 2, 0, 0]));
856 assert_eq!(iter.next(), Some(vec![3, 4, 0, 0]));
857 assert_eq!(iter.next(), Some(vec![0, 0, 0, 0]));
858 assert_eq!(iter.next(), Some(vec![0, 0, 0, 0]));
859 assert_eq!(iter.next(), None);
860 }
861
862 #[test]
864 fn parse_sudoku_valid_4x4() {
865 let s = "1 0 3 0\n0 2 0 0\n0 0 0 4\n0 0 1 0";
866 let sudoku = parse_sudoku(s).unwrap();
867 assert_eq!(sudoku.size, Size::Four);
868 let expected_board = Board::new(vec![
869 vec![1, 0, 3, 0],
870 vec![0, 2, 0, 0],
871 vec![0, 0, 0, 4],
872 vec![0, 0, 1, 0],
873 ]);
874 assert_eq!(sudoku.board, expected_board);
875 }
876
877 #[test]
878 #[should_panic(expected = "Invalid size for Sudoku board")]
879 fn parse_sudoku_invalid_size_panic() {
880 let s = "1 0\n0 1";
881 parse_sudoku(s).unwrap();
882 }
883
884 #[test]
885 fn parse_sudoku_invalid_char() {
886 let s = "1 0 X 0\n0 2 0 0\n0 0 0 4\n0 0 1 0";
887 assert!(parse_sudoku(s).is_err());
888 assert_eq!(
889 parse_sudoku(s).unwrap_err(),
890 "Invalid Sudoku: Invalid character X"
891 );
892 }
893
894 #[test]
895 fn parse_sudoku_number_too_large() {
896 let s = "1 0 5 0\n0 2 0 0\n0 0 0 4\n0 0 1 0"; let err = parse_sudoku(s).unwrap_err();
898 assert_eq!(err, "Invalid Sudoku: Number 5 exceeds size 4");
899 }
900
901 #[test]
902 fn parse_sudoku_too_many_numbers_in_line() {
903 let s = "1 0 0 0 5\n0 2 0 0\n0 0 0 4\n0 0 1 0";
904 match parse_sudoku(s) {
905 Ok(_) => panic!("Should have failed or panicked."),
906 Err(e) => {
907 assert_eq!(e, "Invalid Sudoku: Too many numbers in line 1");
908 }
909 }
910 }
911
912 #[test]
913 fn parse_sudoku_too_few_numbers_in_line() {
914 let s = "1 0\n0 2 0 0\n0 0 0 4\n0 0 1 0";
915 let sudoku = parse_sudoku(s).unwrap();
916 let expected_board = Board::new(vec![
917 vec![1, 0, 0, 0], vec![0, 2, 0, 0],
919 vec![0, 0, 0, 4],
920 vec![0, 0, 1, 0],
921 ]);
922 assert_eq!(sudoku.board, expected_board);
923 }
924
925 #[test]
926 fn parse_sudoku_empty_line_behavior() {
927 let s = "1 0 3 0\n0 2 0 0\n\n0 0 0 4\n0 0 1 0";
928 assert!(
929 std::panic::catch_unwind(|| parse_sudoku(s)).is_err(),
930 "Sudoku::new should panic for size 5"
931 );
932 }
933
934 #[test]
935 fn sudoku_decode_basic() {
936 let initial_board_data = vec![vec![0; 4]; 4];
937 let sudoku = Sudoku::new(Board::new(initial_board_data));
938 assert!(sudoku.is_ok());
939 let sudoku = sudoku.unwrap();
940
941 let mut solution_vars = Vec::new();
942 let solution_grid: [[usize; 4]; 4] =
943 [[1, 2, 3, 4], [3, 4, 1, 2], [2, 1, 4, 3], [4, 3, 2, 1]];
944
945 for (r, row) in solution_grid.iter().enumerate() {
946 for (c, &num) in row.iter().enumerate() {
947 #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
948 solution_vars.push(Variable::new(r + 1, c + 1, num).encode(Size::Four) as i32);
949 }
950 }
951
952 let solutions = Solutions::new(&solution_vars);
953 let decoded_sudoku = sudoku.decode(&solutions);
954
955 let expected_board_data: Vec<Vec<usize>> =
956 solution_grid.iter().map(|row| row.to_vec()).collect();
957 assert_eq!(decoded_sudoku.board.0, expected_board_data);
958 assert_eq!(decoded_sudoku.size, Size::Four);
959 }
960
961 #[test]
962 fn test_generate_cell_clauses_content() {
963 let cell_clauses = generate_cell_clauses(4);
964 let clause_for_cell_1_1 = vec![1, 2, 3, 4];
965 assert!(cell_clauses.contains(&clause_for_cell_1_1));
966 assert_eq!(cell_clauses.len(), 4 * 4);
967 }
968
969 #[test]
970 fn test_generate_row_clauses_content() {
971 let row_clauses = generate_row_clauses(4);
972 #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
973 let var111 = Variable::new(1, 1, 1).encode(Size::Four) as i32;
974 #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
975 let var121 = Variable::new(1, 2, 1).encode(Size::Four) as i32;
976 let clause_example = vec![-var111, -var121]; assert!(row_clauses.contains(&clause_example));
978 assert_eq!(row_clauses.len(), 96);
979 }
980
981 #[test]
982 fn test_generate_pre_filled_clauses() {
983 let board_data = vec![
984 vec![1, 0, 0, 0],
985 vec![0, 2, 0, 0],
986 vec![0, 0, 0, 0],
987 vec![0, 0, 0, 0],
988 ];
989 let board = Board::new(board_data);
990 let pre_filled = generate_pre_filled_clauses(4, &board);
991
992 #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
993 let var111 = Variable::new(1, 1, 1).encode(Size::Four) as i32;
994 #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
995 let var222 = Variable::new(2, 2, 2).encode(Size::Four) as i32;
996
997 let expected_clauses: HashSet<Vec<i32>> =
998 [vec![var111], vec![var222]].iter().cloned().collect();
999
1000 let actual_clauses: HashSet<Vec<i32>> = pre_filled.iter().cloned().collect();
1001 assert_eq!(actual_clauses, expected_clauses);
1002 assert_eq!(pre_filled.len(), 2);
1003 }
1004}