diff --git a/src/App.tsx b/src/App.tsx index 2fc1ddd..8160d25 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -38,13 +38,13 @@ function Controls({ noteMode ? { ToggleNote: { - pos: [selected % 9, Math.floor(selected / 9)], + pos: { x: selected % 9, y: Math.floor(selected / 9) }, value: value, }, } : { SetDefiniteAnswer: { - pos: [selected % 9, Math.floor(selected / 9)], + pos: { x: selected % 9, y: Math.floor(selected / 9) }, value: value, }, }, diff --git a/sudoku-rust/src/grid/reduce.rs b/sudoku-rust/src/grid/reduce.rs index 6131bf4..2cc0e17 100644 --- a/sudoku-rust/src/grid/reduce.rs +++ b/sudoku-rust/src/grid/reduce.rs @@ -66,12 +66,10 @@ pub fn reduce(original: PlayingGrid, action: Action) -> Result { // check the grid would be a valid state original.validate(pos, value)?; - if match original[pos] { - PlayingCell::Locked(_) => true, - _ => false, - } { + if let PlayingCell::Locked(_) = original[pos] { return Err(ReductionError::LockedCell); - }; + } + let arr = original.as_array(); let arr = arr .into_iter() diff --git a/sudoku-rust/src/grid/validate.rs b/sudoku-rust/src/grid/validate.rs index 485f984..7817811 100644 --- a/sudoku-rust/src/grid/validate.rs +++ b/sudoku-rust/src/grid/validate.rs @@ -1,7 +1,10 @@ -use crate::types::{ - Pos, - cell::{PlayingCell, SudokuCell}, - grid::{PlayingGrid, internal::Grid}, +use crate::{ + GRID_SIZE, + types::{ + Pos, + cell::{PlayingCell, SudokuCell}, + grid::{PlayingGrid, internal::Grid}, + }, }; use std::fmt::Debug; @@ -24,22 +27,22 @@ pub trait Validate { type Cell; fn cell_equal(current: &Self::Cell, target: &SudokuCell) -> bool; - fn get_row(&self, pos: Pos) -> [Self::Cell; 9]; - fn get_column(&self, pos: Pos) -> [Self::Cell; 9]; + fn get_row(&self, pos: Pos) -> [Self::Cell; GRID_SIZE]; + fn get_column(&self, pos: Pos) -> [Self::Cell; GRID_SIZE]; /// Returns the containing 3x3 box with cells in the same order as in `self` - fn get_box(&self, pos: Pos) -> [Self::Cell; 9]; + fn get_box(&self, pos: Pos) -> [Self::Cell; GRID_SIZE]; - fn validate_segment(arr: [Self::Cell; 9], skip: usize, value: &SudokuCell) -> bool { + fn validate_segment(arr: [Self::Cell; GRID_SIZE], skip: usize, value: &SudokuCell) -> bool { arr.iter().enumerate().fold(false, |acc, (idx, cur)| { acc || (idx != skip && Self::cell_equal(cur, value)) }) } - fn validate(&self, Pos(x, y): Pos, value: SudokuCell) -> Result<(), ValidationError> { - let row_invalid = Self::validate_segment(self.get_row(Pos(x, y)), x, &value); - let column_invalid = Self::validate_segment(self.get_column(Pos(x, y)), y, &value); + fn validate(&self, pos: Pos, value: SudokuCell) -> Result<(), ValidationError> { + let row_invalid = Self::validate_segment(self.get_row(pos), pos.x, &value); + let column_invalid = Self::validate_segment(self.get_column(pos), pos.y, &value); let box_invalid = - Self::validate_segment(self.get_box(Pos(x, y)), (y % 3) * 3 + (x % 3), &value); + Self::validate_segment(self.get_box(pos), (pos.y % 3) * 3 + (pos.x % 3), &value); if !row_invalid && !column_invalid && !box_invalid { Ok(()) @@ -64,27 +67,27 @@ impl Validate for PlayingGrid { } } - fn get_row(&self, Pos(_, y): Pos) -> [Self::Cell; 9] { - self.as_array_2d()[y] + fn get_row(&self, pos: Pos) -> [Self::Cell; GRID_SIZE] { + self.as_array_2d()[pos.y] } - fn get_column(&self, Pos(x, _): Pos) -> [Self::Cell; 9] { - self.as_array_2d().map(|row| row[x]) + fn get_column(&self, pos: Pos) -> [Self::Cell; GRID_SIZE] { + self.as_array_2d().map(|row| row[pos.x]) } - fn get_box(&self, Pos(x, y): Pos) -> [Self::Cell; 9] { - let Pos(box_pos_x, box_pos_y) = Pos((x / 3) * 3, (y / 3) * 3); + fn get_box(&self, pos: Pos) -> [Self::Cell; GRID_SIZE] { + let box_pos = pos.box_pos(); let box_pos = [ - Pos(box_pos_x, box_pos_y), - Pos(box_pos_x + 1, box_pos_y), - Pos(box_pos_x + 2, box_pos_y), - Pos(box_pos_x, box_pos_y + 1), - Pos(box_pos_x + 1, box_pos_y + 1), - Pos(box_pos_x + 2, box_pos_y + 1), - Pos(box_pos_x, box_pos_y + 2), - Pos(box_pos_x + 1, box_pos_y + 2), - Pos(box_pos_x + 2, box_pos_y + 2), + Pos::new(box_pos.x, box_pos.y), + Pos::new(box_pos.x + 1, box_pos.y), + Pos::new(box_pos.x + 2, box_pos.y), + Pos::new(box_pos.x, box_pos.y + 1), + Pos::new(box_pos.x + 1, box_pos.y + 1), + Pos::new(box_pos.x + 2, box_pos.y + 1), + Pos::new(box_pos.x, box_pos.y + 2), + Pos::new(box_pos.x + 1, box_pos.y + 2), + Pos::new(box_pos.x + 2, box_pos.y + 2), ]; box_pos.map(|pos| self[pos]) @@ -101,27 +104,27 @@ where current == target } - fn get_row(&self, Pos(_, y): Pos) -> [Self::Cell; 9] { - self.as_array_2d()[y] + fn get_row(&self, pos: Pos) -> [Self::Cell; GRID_SIZE] { + self.as_array_2d()[pos.y] } - fn get_column(&self, Pos(x, _): Pos) -> [Self::Cell; 9] { - self.as_array_2d().map(|row| row[x]) + fn get_column(&self, pos: Pos) -> [Self::Cell; GRID_SIZE] { + self.as_array_2d().map(|row| row[pos.x]) } - fn get_box(&self, Pos(x, y): Pos) -> [Self::Cell; 9] { - let Pos(box_pos_x, box_pos_y) = Pos((x / 3) * 3, (y / 3) * 3); + fn get_box(&self, pos: Pos) -> [Self::Cell; GRID_SIZE] { + let box_pos = Pos::new((pos.x / 3) * 3, (pos.y / 3) * 3); let box_pos = [ - Pos(box_pos_x, box_pos_y), - Pos(box_pos_x + 1, box_pos_y), - Pos(box_pos_x + 2, box_pos_y), - Pos(box_pos_x, box_pos_y + 1), - Pos(box_pos_x + 1, box_pos_y + 1), - Pos(box_pos_x + 2, box_pos_y + 1), - Pos(box_pos_x, box_pos_y + 2), - Pos(box_pos_x + 1, box_pos_y + 2), - Pos(box_pos_x + 2, box_pos_y + 2), + Pos::new(box_pos.x, box_pos.y), + Pos::new(box_pos.x + 1, box_pos.y), + Pos::new(box_pos.x + 2, box_pos.y), + Pos::new(box_pos.x, box_pos.y + 1), + Pos::new(box_pos.x + 1, box_pos.y + 1), + Pos::new(box_pos.x + 2, box_pos.y + 1), + Pos::new(box_pos.x, box_pos.y + 2), + Pos::new(box_pos.x + 1, box_pos.y + 2), + Pos::new(box_pos.x + 2, box_pos.y + 2), ]; box_pos.map(|pos| self[pos]) diff --git a/sudoku-rust/src/lib.rs b/sudoku-rust/src/lib.rs index 1544232..29b6f5a 100644 --- a/sudoku-rust/src/lib.rs +++ b/sudoku-rust/src/lib.rs @@ -3,6 +3,8 @@ pub mod types; use wasm_bindgen::prelude::*; +pub const GRID_SIZE: usize = 9; + #[wasm_bindgen(start, private)] fn main() { // When the `console_error_panic_hook` feature is enabled, we can call the diff --git a/sudoku-rust/src/types/grid.rs b/sudoku-rust/src/types/grid.rs index 3f65193..ab822c2 100644 --- a/sudoku-rust/src/types/grid.rs +++ b/sudoku-rust/src/types/grid.rs @@ -5,6 +5,7 @@ use std::{ }; use crate::{ + GRID_SIZE, grid::validate::{Validate, ValidationError}, types::{Pos, cell::*}, }; @@ -17,7 +18,9 @@ use wasm_bindgen::prelude::*; #[serde_as] #[derive(Clone, Debug, PartialEq, Eq, Tsify, Deserialize, Serialize)] #[tsify(from_wasm_abi, into_wasm_abi)] -pub struct PlayingGrid(#[serde_as(as = "[_; 81]")] [PlayingCell; 81]); +pub struct PlayingGrid( + #[serde_as(as = "[_; GRID_SIZE * GRID_SIZE]")] [PlayingCell; GRID_SIZE * GRID_SIZE], +); impl Default for PlayingGrid { fn default() -> Self { @@ -28,8 +31,8 @@ impl Default for PlayingGrid { impl Index for PlayingGrid { type Output = PlayingCell; - fn index(&self, Pos(x, y): Pos) -> &Self::Output { - &self.0[y * Self::SIZE + x] + fn index(&self, pos: Pos) -> &Self::Output { + &self.0[pos.y * GRID_SIZE + pos.x] } } @@ -53,18 +56,16 @@ impl Display for PlayingGrid { } impl PlayingGrid { - const SIZE: usize = 9; - - pub fn new(arr: [PlayingCell; 81]) -> Self { + pub fn new(arr: [PlayingCell; GRID_SIZE * GRID_SIZE]) -> Self { Self(arr) } - pub fn as_array(&self) -> [PlayingCell; 81] { + pub fn as_array(&self) -> [PlayingCell; GRID_SIZE * GRID_SIZE] { self.0 } - pub fn as_array_2d(&self) -> [[PlayingCell; 9]; 9] { - match self.0.as_chunks::<9>() { + pub fn as_array_2d(&self) -> [[PlayingCell; GRID_SIZE]; GRID_SIZE] { + match self.0.as_chunks::<{ GRID_SIZE }>() { (chunks, []) => Some(chunks), _ => None, } @@ -72,7 +73,7 @@ impl PlayingGrid { Ok(val) => Some(val), _ => None, }) - .expect("Grid must be 9x9") + .expect(&format!("Grid must be {0}x{0}", GRID_SIZE)) } pub fn as_vec(&self) -> Vec { @@ -108,10 +109,12 @@ pub fn playing_grid_validate( } pub mod internal { + use crate::GRID_SIZE; + use super::*; #[derive(Clone, Debug)] - pub struct Grid([T; 81]) + pub struct Grid([T; GRID_SIZE * GRID_SIZE]) where T: Clone + Copy + Debug; @@ -133,8 +136,8 @@ pub mod internal { { type Output = T; - fn index(&self, Pos(x, y): Pos) -> &Self::Output { - &self.0[y * Self::SIZE + x] + fn index(&self, pos: Pos) -> &Self::Output { + &self.0[pos.y * GRID_SIZE + pos.x] } } @@ -142,14 +145,7 @@ pub mod internal { where T: Clone + Copy + Debug, { - pub const SIZE: usize = 9; - } - - impl Grid - where - T: Clone + Copy + Debug, - { - pub fn as_array(&self) -> [T; 81] { + pub fn as_array(&self) -> [T; GRID_SIZE * GRID_SIZE] { self.0.clone() } @@ -157,8 +153,8 @@ pub mod internal { self.as_array().into_iter().collect() } - pub fn as_array_2d(&self) -> [[T; 9]; 9] { - match self.0.as_chunks::<9>() { + pub fn as_array_2d(&self) -> [[T; GRID_SIZE]; GRID_SIZE] { + match self.0.as_chunks::() { (chunks, []) => Some(chunks), _ => None, } @@ -166,7 +162,7 @@ pub mod internal { Ok(val) => Some(val), _ => None, }) - .expect("Grid must be 9x9") + .expect(&format!("Grid must be {0}x{0}", GRID_SIZE)) } pub fn as_vec_2d(&self) -> Vec> { diff --git a/sudoku-rust/src/types/mod.rs b/sudoku-rust/src/types/mod.rs index 8d5569a..7758f6d 100644 --- a/sudoku-rust/src/types/mod.rs +++ b/sudoku-rust/src/types/mod.rs @@ -1,16 +1,43 @@ +use std::fmt::Display; + use serde::{Deserialize, Serialize}; use tsify::Tsify; +use crate::GRID_SIZE; + pub mod cell; pub mod grid; #[derive(Clone, Copy, Debug, PartialEq, Eq, Tsify, Serialize, Deserialize)] #[tsify(into_wasm_abi, from_wasm_abi)] -pub struct Pos(pub usize, pub usize); +pub struct Pos { + pub x: usize, + pub y: usize, +} impl Pos { + pub fn new(x: usize, y: usize) -> Self { + Self { x, y } + } + + pub fn new_from_idx(idx: usize) -> Self { + Self { + x: idx % GRID_SIZE, + y: idx / GRID_SIZE, + } + } + pub fn as_idx(&self) -> usize { - let Pos(x, y) = self; - y * 9 + x + self.y * GRID_SIZE + self.x + } + + pub fn box_pos(&self) -> Self { + Pos::new((self.x / 3) * 3, (self.y / 3) * 3) + } +} + +impl Display for Pos { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "({}, {})", self.x, self.y) } }