156 lines
3.9 KiB
Rust
156 lines
3.9 KiB
Rust
//! Game board logic.
|
|
|
|
use std::fs::read_to_string;
|
|
|
|
const SIZE: usize = 9;
|
|
|
|
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
|
pub struct Cell {
|
|
pub value: u8,
|
|
pub loaded: bool,
|
|
pub invalid: bool,
|
|
}
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
pub struct Gameboard {
|
|
pub cells: [[Cell; SIZE]; SIZE],
|
|
pub completed: bool,
|
|
}
|
|
|
|
impl Gameboard {
|
|
pub fn new() -> Gameboard {
|
|
Gameboard {
|
|
cells: [[Cell::default(); SIZE]; SIZE],
|
|
completed: false,
|
|
}
|
|
}
|
|
|
|
pub fn from_cells(cells: [[u8; SIZE]; SIZE]) -> Gameboard {
|
|
let mut ret = Gameboard::new();
|
|
for (i, row) in cells.iter().enumerate() {
|
|
for (j, &col) in row.iter().enumerate() {
|
|
ret.cells[i][j] = Cell {
|
|
value: col,
|
|
loaded: col != 0,
|
|
invalid: false,
|
|
};
|
|
}
|
|
}
|
|
ret
|
|
}
|
|
|
|
pub fn char(&self, ind: [usize; 2]) -> Option<char> {
|
|
Some(match self.cells[ind[1]][ind[0]].value {
|
|
1 => '1',
|
|
2 => '2',
|
|
3 => '3',
|
|
4 => '4',
|
|
5 => '5',
|
|
6 => '6',
|
|
7 => '7',
|
|
8 => '8',
|
|
9 => '9',
|
|
_ => return None,
|
|
})
|
|
}
|
|
|
|
pub fn set(&mut self, ind: [usize; 2], val: u8) {
|
|
if !self.cells[ind[1]][ind[0]].loaded {
|
|
self.validate(ind, val);
|
|
self.cells[ind[1]][ind[0]].value = val;
|
|
}
|
|
|
|
self.completed = self
|
|
.cells
|
|
.iter()
|
|
.flatten()
|
|
.all(|cell| !cell.invalid && cell.value != 0);
|
|
}
|
|
|
|
pub fn load_sdm(filename: &str) -> Self {
|
|
let data = read_to_string(filename).expect("failed to read SDM file");
|
|
let mut cells = [[Cell::default(); SIZE]; SIZE];
|
|
let mut row = 0;
|
|
let mut col = 0;
|
|
for c in data.chars() {
|
|
if col == SIZE {
|
|
col = 0;
|
|
row += 1;
|
|
}
|
|
if let Some(v) = c.to_digit(10) {
|
|
let value = v as u8;
|
|
cells[row][col] = Cell {
|
|
value,
|
|
loaded: value != 0,
|
|
invalid: false,
|
|
};
|
|
col += 1;
|
|
}
|
|
}
|
|
|
|
Self {
|
|
cells,
|
|
completed: false,
|
|
}
|
|
}
|
|
|
|
fn validate(&mut self, ind: [usize; 2], val: u8) {
|
|
let [b, a] = ind;
|
|
for i in 0..SIZE {
|
|
if i == a {
|
|
continue;
|
|
}
|
|
if self.cells[a][i].value == val {
|
|
self.cells[a][b].invalid = true;
|
|
return;
|
|
}
|
|
}
|
|
|
|
for i in 0..SIZE {
|
|
if i == b {
|
|
continue;
|
|
}
|
|
if self.cells[i][b].value == val {
|
|
self.cells[a][b].invalid = true;
|
|
return;
|
|
}
|
|
}
|
|
|
|
let (row, col) = (a / 3, b / 3);
|
|
for i in 3 * row..3 * row + 3 {
|
|
for j in 3 * col..3 * col + 3 {
|
|
if i == a && j == b {
|
|
continue;
|
|
}
|
|
if self.cells[i][j].value == val {
|
|
self.cells[a][b].invalid = true;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
self.cells[a][b].invalid = false;
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn load_sdm() {
|
|
let got = Gameboard::load_sdm("static/puzzle.sdm");
|
|
let want = Gameboard::from_cells([
|
|
[0, 1, 6, 4, 0, 0, 0, 0, 0],
|
|
[2, 0, 0, 0, 0, 9, 0, 0, 0],
|
|
[4, 0, 0, 0, 0, 0, 0, 6, 2],
|
|
[0, 7, 0, 2, 3, 0, 1, 0, 0],
|
|
[1, 0, 0, 0, 0, 0, 0, 0, 3],
|
|
[0, 0, 3, 0, 8, 7, 0, 4, 0],
|
|
[9, 6, 0, 0, 0, 0, 0, 0, 5],
|
|
[0, 0, 0, 8, 0, 0, 0, 0, 7],
|
|
[0, 0, 0, 0, 0, 6, 8, 2, 0],
|
|
]);
|
|
assert_eq!(got, want);
|
|
}
|
|
}
|