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