rustoku_lib/core/
board.rs

1use crate::error::RustokuError;
2
3/// Raw 9x9 board with some useful helpers.
4///
5/// There are multiple ways to create a `Board`:
6/// - Using a 2D array of `u8` with dimensions 9x9
7/// - Using a 1D array of `u8` with length 81
8/// - Using a string representation with length 81
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
10pub struct Board {
11    /// Each cell can contain a number from 1 to 9, or be empty (is 0).
12    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    /// Gets a value from the board at the specified row and column.
23    pub fn get(&self, r: usize, c: usize) -> u8 {
24        self.cells[r][c]
25    }
26
27    /// Sets a value in the board at the specified row and column.
28    pub(super) fn set(&mut self, r: usize, c: usize, value: u8) {
29        self.cells[r][c] = value;
30    }
31
32    /// Checks if a cell at the specified row and column is empty (contains 0).
33    pub fn is_empty(&self, r: usize, c: usize) -> bool {
34        self.cells[r][c] == 0
35    }
36
37    /// Iterates over all cells in the board, yielding their row and column indices.
38    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    /// Iterates over empty cells in the board, yielding their row and column indices.
43    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            // Validate that numbers are within 0-9 if you want strictness here,
63            // though Rustoku::new will validate initial state safety.
64            if bytes[i] > 9 {
65                return Err(RustokuError::InvalidInputCharacter); // Or a more specific error
66            }
67            board[i / 9][i % 9] = bytes[i];
68        }
69        Ok(Board::new(board)) // Construct the board
70    }
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, // Treat '.' and '_' as empty cells
85                _ => return Err(RustokuError::InvalidInputCharacter),
86            }
87        }
88        // Now use the TryFrom<[u8; 81]> for board
89        bytes.try_into()
90    }
91}