1use crate::error::RustokuError;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
10pub struct Board {
11 pub(crate) cells: [[u8; 9]; 9],
13}
14
15impl Board {
16 pub fn new(initial_board: [[u8; 9]; 9]) -> Self {
17 Board {
18 cells: initial_board,
19 }
20 }
21
22 pub fn get(&self, r: usize, c: usize) -> u8 {
24 self.cells[r][c]
25 }
26
27 pub(super) fn set(&mut self, r: usize, c: usize, value: u8) {
29 self.cells[r][c] = value;
30 }
31
32 pub fn is_empty(&self, r: usize, c: usize) -> bool {
34 self.cells[r][c] == 0
35 }
36
37 pub fn iter_cells(&self) -> impl Iterator<Item = (usize, usize)> + '_ {
39 (0..9).flat_map(move |r| (0..9).map(move |c| (r, c)))
40 }
41
42 pub fn iter_empty_cells(&self) -> impl Iterator<Item = (usize, usize)> + '_ {
44 (0..9).flat_map(move |r| {
45 (0..9).filter_map(move |c| {
46 if self.is_empty(r, c) {
47 Some((r, c))
48 } else {
49 None
50 }
51 })
52 })
53 }
54}
55
56impl TryFrom<[u8; 81]> for Board {
57 type Error = RustokuError;
58
59 fn try_from(bytes: [u8; 81]) -> Result<Self, Self::Error> {
60 let mut board = [[0u8; 9]; 9];
61 for i in 0..81 {
62 if bytes[i] > 9 {
65 return Err(RustokuError::InvalidInputCharacter); }
67 board[i / 9][i % 9] = bytes[i];
68 }
69 Ok(Board::new(board)) }
71}
72
73impl TryFrom<&str> for Board {
74 type Error = RustokuError;
75
76 fn try_from(s: &str) -> Result<Self, Self::Error> {
77 if s.len() != 81 {
78 return Err(RustokuError::InvalidInputLength);
79 }
80 let mut bytes = [0u8; 81];
81 for (i, ch) in s.bytes().enumerate() {
82 match ch {
83 b'0'..=b'9' => bytes[i] = ch - b'0',
84 b'.' | b'_' => bytes[i] = 0, _ => return Err(RustokuError::InvalidInputCharacter),
86 }
87 }
88 bytes.try_into()
90 }
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96 use crate::error::RustokuError;
97
98 const UNIQUE_PUZZLE: &str =
99 "530070000600195000098000060800060003400803001700020006060000280000419005000080079";
100
101 #[test]
102 fn test_new_with_bytes_and_str() {
103 let board = [
104 [5, 3, 0, 0, 7, 0, 0, 0, 0],
105 [6, 0, 0, 1, 9, 5, 0, 0, 0],
106 [0, 9, 8, 0, 0, 0, 0, 6, 0],
107 [8, 0, 0, 0, 6, 0, 0, 0, 3],
108 [4, 0, 0, 8, 0, 3, 0, 0, 1],
109 [7, 0, 0, 0, 2, 0, 0, 0, 6],
110 [0, 6, 0, 0, 0, 0, 2, 8, 0],
111 [0, 0, 0, 4, 1, 9, 0, 0, 5],
112 [0, 0, 0, 0, 8, 0, 0, 7, 9],
113 ];
114
115 let flat_bytes: [u8; 81] = board
116 .concat()
117 .try_into()
118 .expect("Concat board to bytes failed");
119 let board_str: String = flat_bytes.iter().map(|&b| (b + b'0') as char).collect();
120
121 let board_from_new = Board::new(board);
122 let board_from_bytes = Board::try_from(flat_bytes).expect("Board from flat bytes failed");
123 let board_from_str = Board::try_from(board_str.as_str()).expect("Board from string failed");
124
125 assert_eq!(board_from_new, board_from_bytes);
126 assert_eq!(board_from_new, board_from_str);
127 assert_eq!(board_from_bytes, board_from_str);
128 }
129
130 #[test]
131 fn test_try_from_with_valid_input() {
132 let rustoku = Board::try_from(UNIQUE_PUZZLE);
133 assert!(rustoku.is_ok());
134 }
135
136 #[test]
137 fn test_try_from_with_invalid_length() {
138 let s = "530070000"; let rustoku = Board::try_from(s);
140 assert!(matches!(rustoku, Err(RustokuError::InvalidInputLength)));
141 }
142
143 #[test]
144 fn test_try_from_with_invalid_character() {
145 let s = "53007000060019500009800006080006000340080300170002000606000028000041900500008007X"; let rustoku = Board::try_from(s);
147 assert!(matches!(rustoku, Err(RustokuError::InvalidInputCharacter)));
148 }
149}