2024-10-15 22:45:44 -05:00
|
|
|
use crate::cell::{Cell, CellValue};
|
|
|
|
|
use std::collections::HashSet;
|
|
|
|
|
|
2024-10-15 23:32:56 -05:00
|
|
|
const REGION_SIZE: usize = 9;
|
|
|
|
|
const SUB_REGION_SIZE: usize = 3;
|
|
|
|
|
|
2024-10-15 22:45:44 -05:00
|
|
|
pub struct Board {
|
|
|
|
|
cells: Vec<Vec<Cell>>,
|
2024-10-15 23:32:56 -05:00
|
|
|
squares: Vec<Region>,
|
|
|
|
|
rows: Vec<Region>,
|
|
|
|
|
cols: Vec<Region>,
|
2024-10-15 22:45:44 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Board {
|
|
|
|
|
pub fn new() -> Board {
|
2024-10-15 23:32:56 -05:00
|
|
|
let mut cells = Vec::with_capacity(REGION_SIZE);
|
|
|
|
|
for _ in 0..REGION_SIZE {
|
|
|
|
|
let mut col = Vec::with_capacity(REGION_SIZE);
|
|
|
|
|
for _ in 0..REGION_SIZE {
|
2024-10-15 22:45:44 -05:00
|
|
|
col.push(Cell::default());
|
|
|
|
|
}
|
2024-10-15 23:32:56 -05:00
|
|
|
cells.push(col);
|
2024-10-15 22:45:44 -05:00
|
|
|
}
|
2024-10-15 23:32:56 -05:00
|
|
|
let mut squares = Vec::with_capacity(REGION_SIZE);
|
|
|
|
|
for x in 0..SUB_REGION_SIZE {
|
|
|
|
|
for y in 0..SUB_REGION_SIZE {
|
|
|
|
|
squares.push(Region::new_square(&Point(x, y)));
|
2024-10-15 22:45:44 -05:00
|
|
|
}
|
|
|
|
|
}
|
2024-10-15 23:32:56 -05:00
|
|
|
let mut rows = Vec::with_capacity(REGION_SIZE);
|
|
|
|
|
for x in 0..REGION_SIZE {
|
|
|
|
|
rows.push(Region::new_row(&Point(x, 0)));
|
|
|
|
|
}
|
|
|
|
|
let mut cols = Vec::with_capacity(REGION_SIZE);
|
|
|
|
|
for y in 0..REGION_SIZE {
|
|
|
|
|
cols.push(Region::new_col(&Point(0, y)));
|
|
|
|
|
}
|
2024-10-15 22:45:44 -05:00
|
|
|
let board = Board {
|
2024-10-15 23:32:56 -05:00
|
|
|
cells,
|
|
|
|
|
squares,
|
|
|
|
|
rows,
|
|
|
|
|
cols
|
2024-10-15 22:45:44 -05:00
|
|
|
};
|
|
|
|
|
board
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn value(&self, point: &Point) -> Option<CellValue> {
|
|
|
|
|
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);
|
2024-10-15 23:32:56 -05:00
|
|
|
self.update_regions(point, value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn update_regions(&mut self, point: &Point, value: CellValue) {
|
|
|
|
|
self.update_columns(point, value);
|
|
|
|
|
self.update_rows(point, value);
|
|
|
|
|
self.update_squares(point, value);
|
2024-10-15 22:45:44 -05:00
|
|
|
}
|
|
|
|
|
|
2024-10-15 23:32:56 -05:00
|
|
|
pub fn update_rows(&mut self, point: &Point, value: CellValue) {
|
|
|
|
|
for row in self.rows.iter_mut() {
|
|
|
|
|
if row.includes(point) {
|
|
|
|
|
for pt in row.iter_mut() {
|
|
|
|
|
self.cells[pt.0][pt.1].remove_candidate(value);
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-10-15 22:45:44 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-15 23:32:56 -05:00
|
|
|
pub fn update_columns(&mut self, point: &Point, value: CellValue) {
|
|
|
|
|
for col in self.cols.iter_mut() {
|
|
|
|
|
if col.includes(point) {
|
|
|
|
|
for pt in col.iter_mut() {
|
|
|
|
|
self.cells[pt.0][pt.1].remove_candidate(value);
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-10-15 22:45:44 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-15 23:32:56 -05:00
|
|
|
pub fn update_squares(&mut self, point: &Point, value: CellValue) {
|
|
|
|
|
for i in self.squares.iter_mut() {
|
2024-10-15 22:45:44 -05:00
|
|
|
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<CellValue> {
|
|
|
|
|
self.cells[point.0][point.1].candidates()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(PartialEq, Debug)]
|
|
|
|
|
pub struct Point(usize, usize);
|
|
|
|
|
|
|
|
|
|
pub struct Region {
|
|
|
|
|
points: Vec<Point>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Region {
|
2024-10-15 23:32:56 -05:00
|
|
|
pub fn new_square(start: &Point)-> Region {
|
|
|
|
|
let mut points = Vec::with_capacity(REGION_SIZE);
|
|
|
|
|
for x in start.0..(start.0 + SUB_REGION_SIZE) {
|
|
|
|
|
for y in start.1..(start.1 + SUB_REGION_SIZE) {
|
2024-10-15 22:45:44 -05:00
|
|
|
points.push(Point(x, y));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Region {
|
2024-10-15 23:32:56 -05:00
|
|
|
points,
|
2024-10-15 22:45:44 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-15 23:32:56 -05:00
|
|
|
pub fn new_row(start: &Point) -> Region {
|
|
|
|
|
let mut points = Vec::with_capacity(REGION_SIZE);
|
|
|
|
|
for y in 0..REGION_SIZE {
|
|
|
|
|
points.push(Point(start.0, y));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Region { points }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn new_col(start: &Point) -> Region {
|
|
|
|
|
let mut points = Vec::with_capacity(REGION_SIZE);
|
|
|
|
|
for x in 0..REGION_SIZE {
|
|
|
|
|
points.push(Point(x, start.1));
|
|
|
|
|
}
|
|
|
|
|
Region { points }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Region {
|
|
|
|
|
fn iter(&self) -> std::slice::Iter<Point> {
|
2024-10-15 22:45:44 -05:00
|
|
|
self.points.iter()
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-15 23:32:56 -05:00
|
|
|
fn includes(&self, point: &Point) -> bool {
|
2024-10-15 22:45:44 -05:00
|
|
|
self.points.contains(point)
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-15 23:32:56 -05:00
|
|
|
fn iter_mut(&mut self) -> std::slice::IterMut<Point> {
|
2024-10-15 22:45:44 -05:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|