diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..dcf443f --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,27 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'rustdoku'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=rustdoku" + ], + "filter": { + "name": "rustdoku", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index fd14e88..a60cade 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "assertables" +version = "8.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "857057651cdf1fe4bc1e8308493c752db559df0330f23b45f532f6b24c2b443d" + [[package]] name = "rustdoku" version = "0.1.0" +dependencies = [ + "assertables", +] diff --git a/Cargo.toml b/Cargo.toml index 7092887..61ee043 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,3 +4,4 @@ version = "0.1.0" edition = "2021" [dependencies] +assertables = "8.18.0" diff --git a/src/board.rs b/src/board.rs new file mode 100644 index 0000000..9f9b542 --- /dev/null +++ b/src/board.rs @@ -0,0 +1,132 @@ +use crate::cell::{Cell, CellValue}; +use std::collections::HashSet; + +pub struct Board { + cells: Vec>, + regions: Vec, +} + +impl Board { + pub fn new() -> Board { + let mut rows = Vec::with_capacity(9); + for _ in 0..9 { + let mut col = Vec::with_capacity(9); + for _ in 0..9 { + col.push(Cell::default()); + } + rows.push(col); + } + let mut regions = Vec::with_capacity(9); + for x in 0..3 { + for y in 0..3 { + regions.push(Region::new(&Point(x, y))); + } + } + let board = Board { + cells: rows, + regions + }; + board + } + + pub fn value(&self, point: &Point) -> Option { + self.cells[point.0][point.1].value() + } + + pub fn set_value(&mut self, point: &Point, value: CellValue) { + self.cells[point.0][point.1].set_value(value); + self.update_row(point.0, value); + self.update_column(point.1, value); + self.update_region(point, value); + } + + pub fn update_row(&mut self, x: usize, value: CellValue) { + for i in self.cells[x].iter_mut() { + i.remove_candidate(value); + } + } + + pub fn update_column(&mut self, y: usize, value: CellValue) { + for i in self.cells.iter_mut() { + (*i)[y].remove_candidate(value); + } + } + + pub fn update_region(&mut self, point: &Point, value: CellValue) { + for i in self.regions.iter_mut() { + if i.includes(point) { + for pt in i.iter_mut() { + self.cells[pt.0][pt.1].remove_candidate(value); + } + } + } + } + + pub fn candidates(&self, point: &Point) -> HashSet { + self.cells[point.0][point.1].candidates() + } +} + +#[derive(PartialEq, Debug)] +pub struct Point(usize, usize); + +pub struct Region { + points: Vec, +} + +impl Region { + pub fn new(start: &Point)-> Region { + let mut points = Vec::with_capacity(9); + for x in start.0..(start.0 + 3) { + for y in start.1..(start.1 + 3) { + points.push(Point(x, y)); + } + } + Region { + points, + } + } + + pub fn iter(&self) -> std::slice::Iter { + self.points.iter() + } + + pub fn includes(&self, point: &Point) -> bool { + self.points.contains(point) + } + + pub fn iter_mut(&mut self) -> std::slice::IterMut { + self.points.iter_mut() + } +} + +#[cfg(test)] +mod test { + use assertables::{assert_contains, assert_not_contains}; + + use super::*; + + #[test] + fn has_none_values() { + let board = Board::new(); + assert!(board.value(&Point(0, 0)).is_none()); + } + + #[test] + fn set_value_updates_cell() { + let mut board = Board::new(); + board.set_value(&Point(0, 0), CellValue::One); + let val = board.value(&Point(0, 0)).unwrap(); + assert_eq!(val, CellValue::One); + + // Same column + assert_not_contains!(board.candidates(&Point(0, 1)), &CellValue::One); + // Same row + assert_not_contains!(board.candidates(&Point(1, 0)), &CellValue::One); + // Same sub-structure + assert_not_contains!(board.candidates(&Point(1, 1)), &CellValue::One); + + // Different row + assert_contains!(board.candidates(&Point(8, 8)), &CellValue::One); + } +} diff --git a/src/cell.rs b/src/cell.rs new file mode 100644 index 0000000..884e7dd --- /dev/null +++ b/src/cell.rs @@ -0,0 +1,97 @@ +use std::collections::HashSet; + +#[derive(Clone, Debug, Copy, Hash, Eq, PartialEq)] +pub enum CellValue { + One, + Two, + Three, + Four, + Five, + Six, + Seven, + Eight, + Nine, +} + +pub struct Cell { + value: Option, + candidates: HashSet, +} + +impl Cell { + pub fn value(&self) -> Option { + self.value + } + + pub fn set_value(&mut self, value: CellValue) { + self.value = Some(value); + } + + pub fn candidates(&self) -> HashSet { + self.candidates.clone() + } + + pub fn remove_candidate(&mut self, value: CellValue) { + self.candidates.remove(&value); + } +} + +impl Default for Cell { + fn default() -> Cell { + let mut candidates = HashSet::new(); + candidates.insert(CellValue::One); + candidates.insert(CellValue::Two); + candidates.insert(CellValue::Three); + candidates.insert(CellValue::Four); + candidates.insert(CellValue::Five); + candidates.insert(CellValue::Six); + candidates.insert(CellValue::Seven); + candidates.insert(CellValue::Eight); + candidates.insert(CellValue::Nine); + Cell { + value: None, + candidates, + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use assertables::{assert_contains, assert_not_contains}; + + #[test] + fn inits_empty() { + let cell = Cell::default(); + assert!(cell.value().is_none()); + } + + #[test] + fn has_all_candidates() { + let cell = Cell::default(); + assert_contains!(cell.candidates(), &CellValue::One); + assert_contains!(cell.candidates(), &CellValue::Two); + assert_contains!(cell.candidates(), &CellValue::Three); + assert_contains!(cell.candidates(), &CellValue::Four); + assert_contains!(cell.candidates(), &CellValue::Five); + assert_contains!(cell.candidates(), &CellValue::Six); + assert_contains!(cell.candidates(), &CellValue::Seven); + assert_contains!(cell.candidates(), &CellValue::Eight); + assert_contains!(cell.candidates(), &CellValue::Nine); + } + + #[test] + fn removed_candidate_is_gone() { + let mut cell = Cell::default(); + cell.remove_candidate(CellValue::One); + assert_not_contains!(cell.candidates(), &CellValue::One); + assert_contains!(cell.candidates(), &CellValue::Two); + assert_contains!(cell.candidates(), &CellValue::Three); + assert_contains!(cell.candidates(), &CellValue::Four); + assert_contains!(cell.candidates(), &CellValue::Five); + assert_contains!(cell.candidates(), &CellValue::Six); + assert_contains!(cell.candidates(), &CellValue::Seven); + assert_contains!(cell.candidates(), &CellValue::Eight); + assert_contains!(cell.candidates(), &CellValue::Nine); + } +} diff --git a/src/lib.rs b/src/lib.rs index c8b86da..50b0b5f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,62 +1,2 @@ -#[derive(Clone, Debug, Copy)] -pub enum CellValue { - One, - Two, - Three, - Four, - Five, - Six, - Seven, - Eight, - Nine, -} - -pub struct Cell { - value: Option, - candidates: Vec, -} - -impl Default for Cell { - fn default() -> Cell { - Cell { - value: None, - candidates: vec![ - CellValue::One, - CellValue::Two, - CellValue::Three, - CellValue::Four, - CellValue::Five, - CellValue::Six, - CellValue::Seven, - CellValue::Eight, - CellValue::Nine, - ], - } - } -} - -#[derive(Default)] -pub struct Board { - cells: [[Cell; 9]; 9], -} - -impl Board { - fn value(self, x: usize, y: usize) -> Option { - self.cells[x][y].value - } -} - -pub fn add(left: u64, right: u64) -> u64 { - left + right -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } -} +pub mod cell; +pub mod board;