use crate::SudokuGrid;
use crate::util::USizeSet;
use std::iter::Cloned;
use std::slice::Iter;
pub type Group = Vec<(usize, usize)>;
pub trait Constraint {
fn check(&self, grid: &SudokuGrid) -> bool {
let size = grid.size();
for row in 0..size {
for column in 0..size {
if !self.check_cell(grid, column, row) {
return false;
}
}
}
true
}
fn check_cell(&self, grid: &SudokuGrid, column: usize, row: usize)
-> bool {
if let Some(number) = grid.get_cell(column, row).unwrap() {
self.check_number(grid, column, row, number)
}
else {
true
}
}
fn check_number(&self, grid: &SudokuGrid, column: usize, row: usize,
number: usize) -> bool;
fn get_groups(&self, grid: &SudokuGrid) -> Vec<Group>;
}
#[derive(Clone)]
pub struct RowConstraint;
impl Constraint for RowConstraint {
fn check(&self, grid: &SudokuGrid) -> bool {
let size = grid.size();
let mut set = USizeSet::new(1, size).unwrap();
for row in 0..size {
set.clear();
for column in 0..size {
if let Some(number) = grid.get_cell(column, row).unwrap() {
if !set.insert(number).unwrap() {
return false;
}
}
}
}
true
}
fn check_number(&self, grid: &SudokuGrid, column: usize, row: usize,
number: usize) -> bool {
let size = grid.size();
for other_column in 0..size {
if other_column != column &&
grid.has_number(other_column, row, number).unwrap() {
return false;
}
}
true
}
fn get_groups(&self, grid: &SudokuGrid) -> Vec<Group> {
let size = grid.size();
let mut groups = Vec::new();
for row in 0..size {
let mut group = Group::new();
for column in 0..size {
group.push((column, row));
}
groups.push(group);
}
groups
}
}
#[derive(Clone)]
pub struct ColumnConstraint;
impl Constraint for ColumnConstraint {
fn check(&self, grid: &SudokuGrid) -> bool {
let size = grid.size();
let mut set = USizeSet::new(1, size).unwrap();
for column in 0..size {
set.clear();
for row in 0..size {
if let Some(number) = grid.get_cell(column, row).unwrap() {
if !set.insert(number).unwrap() {
return false;
}
}
}
}
true
}
fn check_number(&self, grid: &SudokuGrid, column: usize, row: usize,
number: usize) -> bool {
let size = grid.size();
for other_row in 0..size {
if other_row != row &&
grid.has_number(column, other_row, number).unwrap() {
return false;
}
}
true
}
fn get_groups(&self, grid: &SudokuGrid) -> Vec<Group> {
let size = grid.size();
let mut groups = Vec::new();
for column in 0..size {
let mut group = Group::new();
for row in 0..size {
group.push((column, row));
}
groups.push(group);
}
groups
}
}
fn check_number_block(grid: &SudokuGrid, column: usize, row: usize,
number: usize, bop: impl Fn(bool, bool) -> bool) -> bool {
let block_width = grid.block_width();
let block_height = grid.block_height();
let block_column = (column / block_width) * block_width;
let block_row = (row / block_height) * block_height;
for other_row in block_row..(block_row + block_height) {
for other_column in block_column..(block_column + block_width) {
if bop(other_row != row, other_column != column) &&
grid.has_number(other_column, other_row, number).unwrap() {
return false;
}
}
}
true
}
fn get_groups_block(grid: &SudokuGrid) -> Vec<Group> {
let block_width = grid.block_width();
let block_height = grid.block_height();
let mut groups = Vec::new();
for block_row in 0..block_width {
let base_row = block_row * block_height;
for block_column in 0..block_height {
let base_column = block_column * block_width;
let mut group = Group::new();
for sub_row in 0..block_height {
let row = base_row + sub_row;
for sub_column in 0..block_width {
let column = base_column + sub_column;
group.push((column, row));
}
}
groups.push(group);
}
}
groups
}
#[derive(Clone)]
pub struct BlockConstraint;
impl Constraint for BlockConstraint {
fn check(&self, grid: &SudokuGrid) -> bool {
let block_width = grid.block_width();
let block_height = grid.block_height();
let size = grid.size();
let mut set = USizeSet::new(1, size).unwrap();
for block_row in 0..block_width {
for block_column in 0..block_height {
set.clear();
let start_column = block_column * block_width;
let start_row = block_row * block_height;
for row in start_row..(start_row + block_height) {
for column in start_column..(start_column + block_width) {
if let Some(number) =
grid.get_cell(column, row).unwrap() {
if !set.insert(number).unwrap() {
return false;
}
}
}
}
}
}
true
}
fn check_number(&self, grid: &SudokuGrid, column: usize, row: usize,
number: usize) -> bool {
check_number_block(grid, column, row, number, |a, b| a || b)
}
fn get_groups(&self, grid: &SudokuGrid) -> Vec<Group> {
get_groups_block(grid)
}
}
#[derive(Clone)]
struct BlockConstraintNoLineColumn;
impl Constraint for BlockConstraintNoLineColumn {
fn check(&self, grid: &SudokuGrid) -> bool {
BlockConstraint.check(grid)
}
fn check_number(&self, grid: &SudokuGrid, column: usize, row: usize,
number: usize) -> bool {
check_number_block(grid, column, row, number, |a, b| a && b)
}
fn get_groups(&self, grid: &SudokuGrid) -> Vec<Group> {
get_groups_block(grid)
}
}
#[derive(Clone)]
pub struct DefaultConstraint;
impl Constraint for DefaultConstraint {
fn check(&self, grid: &SudokuGrid) -> bool {
RowConstraint.check(grid) &&
ColumnConstraint.check(grid) &&
BlockConstraintNoLineColumn.check(grid)
}
fn check_cell(&self, grid: &SudokuGrid, column: usize, row: usize)
-> bool {
RowConstraint.check_cell(grid, column, row) &&
ColumnConstraint.check_cell(grid, column, row) &&
BlockConstraintNoLineColumn.check_cell(grid, column, row)
}
fn check_number(&self, grid: &SudokuGrid, column: usize, row: usize,
number: usize) -> bool {
RowConstraint.check_number(grid, column, row, number) &&
ColumnConstraint.check_number(grid, column, row, number) &&
BlockConstraintNoLineColumn.check_number(grid, column, row, number)
}
fn get_groups(&self, grid: &SudokuGrid) -> Vec<Group> {
let mut groups = RowConstraint.get_groups(grid);
groups.append(&mut ColumnConstraint.get_groups(grid));
groups.append(&mut BlockConstraintNoLineColumn.get_groups(grid));
groups
}
}
#[derive(Clone)]
pub struct DiagonalsConstraint;
impl Constraint for DiagonalsConstraint {
fn check_number(&self, grid: &SudokuGrid, column: usize, row: usize,
number: usize) -> bool {
let size = grid.size();
if column == row {
for i in 0..size {
if i != column &&
grid.has_number(i, i, number).unwrap() {
return false;
}
}
}
if column + row == size - 1 {
for i in 0..size {
if i != column &&
grid.has_number(i, size - i - 1, number).unwrap() {
return false;
}
}
}
true
}
fn get_groups(&self, grid: &SudokuGrid) -> Vec<Group> {
let size = grid.size();
let mut main_diagonal = Group::new();
let mut anti_diagonal = Group::new();
for i in 0..size {
main_diagonal.push((i, i));
anti_diagonal.push((i, size - i - 1));
}
vec![
main_diagonal,
anti_diagonal
]
}
}
struct RelativeCellIter<'a, I>
where
I : Iterator<Item = (isize, isize)>
{
coords: I,
grid: &'a SudokuGrid,
center_column: usize,
center_row: usize
}
impl<'a, I> Iterator for RelativeCellIter<'a, I>
where
I : Iterator<Item = (isize, isize)>
{
type Item = Option<usize>;
fn next(&mut self) -> Option<Self::Item> {
while let Some((delta_column, delta_row)) = self.coords.next() {
let column = self.center_column as isize + delta_column;
let row = self.center_row as isize + delta_row;
let size = self.grid.size() as isize;
if column >= 0 && column < size && row >= 0 && row < size {
return Some(
self.grid.get_cell(column as usize, row as usize)
.unwrap());
}
}
None
}
}
type ISizePairIterator<'a> = Cloned<Iter<'a, (isize, isize)>>;
impl<'a> RelativeCellIter<'a, ISizePairIterator<'a>> {
fn new(coords: &'a [(isize, isize)], grid: &'a SudokuGrid,
column: usize, row: usize)
-> RelativeCellIter<'a, ISizePairIterator<'a>> {
RelativeCellIter {
coords: coords.iter().cloned(),
grid,
center_column: column,
center_row: row
}
}
}
pub trait RelativeCellConstraint {
const RELATIVE_COORDINATES: &'static [(isize, isize)];
fn is_forbidden(&self, reference_cell: usize, other_cell: usize) -> bool {
reference_cell == other_cell
}
}
impl<C: RelativeCellConstraint> Constraint for C {
fn check_number(&self, grid: &SudokuGrid, column: usize, row: usize,
number: usize) -> bool {
let iter =
RelativeCellIter::new(&C::RELATIVE_COORDINATES, grid, column, row);
for other_cell in iter {
if let Some(other_number) = other_cell {
if self.is_forbidden(number, other_number) {
return false;
}
}
}
true
}
fn get_groups(&self, _: &SudokuGrid) -> Vec<Group> {
Vec::new()
}
}
#[derive(Clone)]
pub struct KnightsMoveConstraint;
const KNIGHTS_MOVES: [(isize, isize); 8] = [
(-1, -2),
(1, -2),
(-2, -1),
(2, -1),
(-2, 1),
(2, 1),
(-1, 2),
(1, 2)
];
impl RelativeCellConstraint for KnightsMoveConstraint {
const RELATIVE_COORDINATES: &'static [(isize, isize)] = &KNIGHTS_MOVES;
}
#[derive(Clone)]
pub struct KingsMoveConstraint;
const KINGS_MOVES: [(isize, isize); 8] = [
(-1, -1),
(0, -1),
(1, -1),
(-1, 0),
(1, 0),
(-1, 1),
(0, 1),
(1, 1)
];
impl RelativeCellConstraint for KingsMoveConstraint {
const RELATIVE_COORDINATES: &'static [(isize, isize)] = &KINGS_MOVES;
}
#[derive(Clone)]
pub struct DiagonallyAdjacentConstraint;
const DIAGONALLY_ADJACENT: [(isize, isize); 4] = [
(-1, -1),
(1, -1),
(-1, 1),
(1, 1)
];
impl RelativeCellConstraint for DiagonallyAdjacentConstraint {
const RELATIVE_COORDINATES: &'static [(isize, isize)] =
&DIAGONALLY_ADJACENT;
}
#[derive(Clone)]
pub struct AdjacentConsecutiveConstraint;
const ORTHOGONALLY_ADJACENT: [(isize, isize); 4] = [
(-1, 0),
(1, 0),
(0, -1),
(0, 1)
];
impl RelativeCellConstraint for AdjacentConsecutiveConstraint {
const RELATIVE_COORDINATES: &'static [(isize, isize)] =
&ORTHOGONALLY_ADJACENT;
fn is_forbidden(&self, reference_cell: usize, other_cell: usize) -> bool {
reference_cell + 1 == other_cell || reference_cell == other_cell + 1
}
}
#[derive(Clone)]
pub struct CompositeConstraint<C1, C2>
where
C1: Constraint + Clone + 'static,
C2: Constraint + Clone + 'static
{
c1: C1,
c2: C2
}
impl<C1, C2> CompositeConstraint<C1, C2>
where
C1: Constraint + Clone + 'static,
C2: Constraint + Clone + 'static
{
pub fn new(c1: C1, c2: C2) -> CompositeConstraint<C1, C2> {
CompositeConstraint {
c1,
c2
}
}
}
impl<C1, C2> Constraint for CompositeConstraint<C1, C2>
where
C1: Constraint + Clone + 'static,
C2: Constraint + Clone + 'static
{
fn check(&self, grid: &SudokuGrid) -> bool {
self.c1.check(grid) && self.c2.check(grid)
}
fn check_cell(&self, grid: &SudokuGrid, column: usize, row: usize)
-> bool {
self.c1.check_cell(grid, column, row) &&
self.c2.check_cell(grid, column, row)
}
fn check_number(&self, grid: &SudokuGrid, column: usize, row: usize,
number: usize) -> bool {
self.c1.check_number(grid, column, row, number) &&
self.c2.check_number(grid, column, row, number)
}
fn get_groups(&self, grid: &SudokuGrid) -> Vec<Group> {
let mut groups = self.c1.get_groups(grid);
groups.append(&mut self.c2.get_groups(grid));
groups
}
}
pub trait CloneConstraint : Constraint {
fn clone_box(&self) -> Box<dyn CloneConstraint>;
}
impl<C: Constraint + Clone + 'static> CloneConstraint for C {
fn clone_box(&self) -> Box<dyn CloneConstraint> {
Box::new(self.clone())
}
}
pub struct DynamicConstraint {
constraints: Vec<Box<dyn CloneConstraint>>
}
impl DynamicConstraint {
pub fn with_children(constraints: Vec<Box<dyn CloneConstraint>>)
-> DynamicConstraint {
DynamicConstraint {
constraints
}
}
pub fn new() -> DynamicConstraint {
DynamicConstraint {
constraints: Vec::new()
}
}
pub fn add(&mut self, constraint: impl CloneConstraint + 'static) {
self.constraints.push(Box::new(constraint))
}
}
impl Constraint for DynamicConstraint {
fn check(&self, grid: &SudokuGrid) -> bool {
self.constraints.iter().all(|c| c.check(grid))
}
fn check_cell(&self, grid: &SudokuGrid, column: usize, row: usize) -> bool {
self.constraints.iter().all(|c| c.check_cell(grid, column, row))
}
fn check_number(&self, grid: &SudokuGrid, column: usize, row: usize,
number: usize) -> bool {
self.constraints.iter().all(
|c| c.check_number(grid, column, row, number))
}
fn get_groups(&self, grid: &SudokuGrid) -> Vec<Group> {
self.constraints.iter()
.map(|c| c.get_groups(grid))
.flat_map(|g| g.into_iter())
.collect()
}
}
impl Clone for DynamicConstraint {
fn clone(&self) -> Self {
let constraints = self.constraints.iter()
.map(|c| c.clone_box())
.collect();
DynamicConstraint {
constraints
}
}
}
impl Default for DynamicConstraint {
fn default() -> DynamicConstraint {
DynamicConstraint::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Sudoku;
#[test]
fn row_satisfied() {
let code = "2x2;\
1, , ,2,\
,2, ,3,\
,4,1, ,\
3, , ,2";
let sudoku = Sudoku::parse(code, RowConstraint).unwrap();
assert!(sudoku.is_valid());
assert!(sudoku.is_valid_cell(3, 2).unwrap());
assert!(sudoku.is_valid_cell(3, 3).unwrap());
assert!(sudoku.is_valid_number(2, 2, 3).unwrap());
}
#[test]
fn row_violated() {
let code = "2x2;\
1, , ,2,\
,2, ,3,\
, ,1, ,\
4, , ,4";
let sudoku = Sudoku::parse(code, RowConstraint).unwrap();
assert!(!sudoku.is_valid());
assert!(!sudoku.is_valid_cell(0, 3).unwrap());
assert!(!sudoku.is_valid_cell(3, 3).unwrap());
assert!(sudoku.is_valid_cell(2, 2).unwrap());
assert!(!sudoku.is_valid_number(2, 0, 1).unwrap());
assert!(!sudoku.is_valid_number(2, 1, 3).unwrap());
assert!(sudoku.is_valid_number(3, 3, 1).unwrap());
}
#[test]
fn column_satisfied() {
let code = "2x2;\
1, ,3, ,\
,2, ,2,\
3, , ,1,\
,4, , ";
let sudoku = Sudoku::parse(code, ColumnConstraint).unwrap();
assert!(sudoku.is_valid());
assert!(sudoku.is_valid_cell(1, 1).unwrap());
assert!(sudoku.is_valid_cell(1, 2).unwrap());
assert!(sudoku.is_valid_number(3, 0, 3).unwrap());
}
#[test]
fn column_violated() {
let code = "2x2;\
1, ,3, ,\
,2, ,4,\
1, , ,3,\
,4, , ";
let sudoku = Sudoku::parse(code, ColumnConstraint).unwrap();
assert!(!sudoku.is_valid());
assert!(!sudoku.is_valid_cell(0, 0).unwrap());
assert!(!sudoku.is_valid_cell(0, 2).unwrap());
assert!(sudoku.is_valid_cell(1, 1).unwrap());
assert!(!sudoku.is_valid_number(2, 1, 3).unwrap());
assert!(!sudoku.is_valid_number(1, 0, 4).unwrap());
assert!(sudoku.is_valid_number(3, 3, 1).unwrap());
}
#[test]
fn block_satisfied() {
let code = "2x2;\
1,2, , ,\
,3, ,3,\
,2,4, ,\
3, , ,1";
let sudoku = Sudoku::parse(code, BlockConstraint).unwrap();
assert!(sudoku.is_valid());
assert!(sudoku.is_valid_cell(1, 1).unwrap());
assert!(sudoku.is_valid_cell(3, 2).unwrap());
assert!(sudoku.is_valid_number(3, 2, 2).unwrap());
}
#[test]
fn block_violated() {
let code = "2x2;\
1, , ,3,\
,3, , ,\
,2,4, ,\
2, , ,1";
let sudoku = Sudoku::parse(code, BlockConstraint).unwrap();
assert!(!sudoku.is_valid());
assert!(!sudoku.is_valid_cell(0, 3).unwrap());
assert!(!sudoku.is_valid_cell(1, 2).unwrap());
assert!(sudoku.is_valid_cell(1, 1).unwrap());
assert!(!sudoku.is_valid_number(2, 0, 3).unwrap());
assert!(!sudoku.is_valid_number(3, 3, 4).unwrap());
assert!(sudoku.is_valid_number(2, 1, 4).unwrap());
}
#[test]
fn diagonals_satisfied() {
let code = "2x2;\
2,3,4,1,\
, , , ,\
, ,4, ,\
3, , ,3";
let sudoku = Sudoku::parse(code, DiagonalsConstraint).unwrap();
assert!(sudoku.is_valid());
assert!(sudoku.is_valid_cell(2, 2).unwrap());
assert!(sudoku.is_valid_cell(3, 0).unwrap());
assert!(sudoku.is_valid_cell(2, 0).unwrap());
assert!(sudoku.is_valid_number(1, 1, 1).unwrap());
}
#[test]
fn diagonals_violated() {
let code = "2x2;\
2,3,4,1,\
, , , ,\
, ,2, ,\
3, , ,4";
let sudoku = Sudoku::parse(code, DiagonalsConstraint).unwrap();
assert!(!sudoku.is_valid());
assert!(!sudoku.is_valid_cell(0, 0).unwrap());
assert!(!sudoku.is_valid_cell(2, 2).unwrap());
assert!(sudoku.is_valid_cell(0, 3).unwrap());
assert!(sudoku.is_valid_cell(2, 0).unwrap());
assert!(!sudoku.is_valid_number(1, 2, 1).unwrap());
assert!(!sudoku.is_valid_number(1, 1, 4).unwrap());
assert!(sudoku.is_valid_number(2, 2, 3).unwrap());
assert!(sudoku.is_valid_number(1, 1, 3).unwrap());
}
#[test]
fn knights_move_satisfied() {
let code = "2x2;\
1, ,3, ,\
,2, ,3,\
,4, ,1,\
1,4,1,2";
let sudoku = Sudoku::parse(code, KnightsMoveConstraint).unwrap();
assert!(sudoku.is_valid());
assert!(sudoku.is_valid_cell(3, 2).unwrap());
assert!(sudoku.is_valid_cell(0, 2).unwrap());
assert!(sudoku.is_valid_number(2, 1, 3).unwrap());
}
#[test]
fn knights_move_violated() {
let code = "2x2;\
1, ,3, ,\
,2, ,4,\
,4, ,1,\
3, ,2, ";
let sudoku = Sudoku::parse(code, KnightsMoveConstraint).unwrap();
assert!(!sudoku.is_valid());
assert!(!sudoku.is_valid_cell(1, 1).unwrap());
assert!(!sudoku.is_valid_cell(2, 3).unwrap());
assert!(!sudoku.is_valid_cell(3, 1).unwrap());
assert!(!sudoku.is_valid_cell(1, 2).unwrap());
assert!(sudoku.is_valid_cell(2, 0).unwrap());
assert!(sudoku.is_valid_cell(2, 1).unwrap());
assert!(!sudoku.is_valid_number(2, 2, 3).unwrap());
assert!(sudoku.is_valid_number(1, 1, 4).unwrap());
}
#[test]
fn kings_move_satisfied() {
let code = "2x2;\
2,3, , ,\
,4,2,1,\
,1, ,4,\
,2, ,2";
let sudoku = Sudoku::parse(code, KingsMoveConstraint).unwrap();
assert!(sudoku.is_valid());
assert!(sudoku.is_valid_cell(1, 1).unwrap());
assert!(sudoku.is_valid_cell(1, 3).unwrap());
assert!(sudoku.is_valid_number(2, 2, 3).unwrap());
}
#[test]
fn kings_move_violated() {
let code = "2x2;\
1,3, , ,\
,2,2,1,\
,1, ,4,\
, ,1,3";
let sudoku = Sudoku::parse(code, KingsMoveConstraint).unwrap();
assert!(!sudoku.is_valid());
assert!(!sudoku.is_valid_cell(1, 1).unwrap());
assert!(!sudoku.is_valid_cell(2, 1).unwrap());
assert!(!sudoku.is_valid_cell(1, 2).unwrap());
assert!(!sudoku.is_valid_cell(2, 3).unwrap());
assert!(sudoku.is_valid_cell(3, 2).unwrap());
assert!(sudoku.is_valid_cell(2, 2).unwrap());
assert!(!sudoku.is_valid_number(2, 2, 3).unwrap());
assert!(!sudoku.is_valid_number(2, 2, 4).unwrap());
assert!(sudoku.is_valid_number(2, 0, 4).unwrap());
}
#[test]
fn diagonally_adjacent_satisfied() {
let code = "2x2;\
1,3, , ,\
,3,2, ,\
2,1,1, ,\
4, , , ";
let sudoku =
Sudoku::parse(code, DiagonallyAdjacentConstraint).unwrap();
assert!(sudoku.is_valid());
assert!(sudoku.is_valid_cell(1, 1).unwrap());
assert!(sudoku.is_valid_cell(2, 2).unwrap());
assert!(sudoku.is_valid_number(1, 2, 3).unwrap());
}
#[test]
fn diagonally_adjacent_violated() {
let code = "2x2;\
1,2, , ,\
,3,2, ,\
,2, ,4,\
4, ,1, ";
let sudoku =
Sudoku::parse(code, DiagonallyAdjacentConstraint).unwrap();
assert!(!sudoku.is_valid());
assert!(!sudoku.is_valid_cell(1, 0).unwrap());
assert!(!sudoku.is_valid_cell(2, 1).unwrap());
assert!(sudoku.is_valid_cell(1, 1).unwrap());
assert!(!sudoku.is_valid_number(2, 1, 4).unwrap());
assert!(!sudoku.is_valid_number(3, 0, 2).unwrap());
assert!(sudoku.is_valid_number(1, 2, 3).unwrap());
}
#[test]
fn adjacent_consecutive_satisfied() {
let code = "2x2;\
4,2,4,1,\
,4, ,3,\
,1,3, ,\
2, ,1, ";
let sudoku =
Sudoku::parse(code, AdjacentConsecutiveConstraint).unwrap();
assert!(sudoku.is_valid());
assert!(sudoku.is_valid_cell(1, 1).unwrap());
assert!(sudoku.is_valid_cell(2, 2).unwrap());
assert!(sudoku.is_valid_number(2, 1, 1).unwrap());
}
#[test]
fn adjacent_consecutive_violated() {
let code = "2x2;\
4,2, ,1,\
,3, ,4,\
,1,3,2,\
2, , , ";
let sudoku =
Sudoku::parse(code, AdjacentConsecutiveConstraint).unwrap();
assert!(!sudoku.is_valid());
assert!(!sudoku.is_valid_cell(1, 0).unwrap());
assert!(!sudoku.is_valid_cell(1, 1).unwrap());
assert!(!sudoku.is_valid_cell(2, 2).unwrap());
assert!(!sudoku.is_valid_cell(3, 2).unwrap());
assert!(sudoku.is_valid_cell(1, 2).unwrap());
assert!(!sudoku.is_valid_number(2, 1, 2).unwrap());
assert!(sudoku.is_valid_number(1, 0, 1).unwrap());
}
fn test_column_row_satisfied(constraint: impl Constraint + Clone) {
let code = "2x2;\
2,4, ,1,\
1,3,2, ,\
,1, ,3,\
4, ,3, ";
let sudoku = Sudoku::parse(code, constraint).unwrap();
assert!(sudoku.is_valid());
assert!(sudoku.is_valid_cell(1, 1).unwrap());
assert!(sudoku.is_valid_number(2, 2, 4).unwrap());
}
fn test_column_row_violated(constraint: impl Constraint + Clone) {
let code1 = "2x2;\
2,4, ,4,\
1,3,2, ,\
,1, ,3,\
4, ,3, ";
let sudoku = Sudoku::parse(code1, constraint).unwrap();
assert!(!sudoku.is_valid());
assert!(!sudoku.is_valid_cell(1, 0).unwrap());
assert!(!sudoku.is_valid_cell(3, 0).unwrap());
assert!(sudoku.is_valid_cell(1, 1).unwrap());
assert!(!sudoku.is_valid_number(2, 2, 1).unwrap());
assert!(sudoku.is_valid_number(2, 0, 1).unwrap());
}
#[test]
fn composite_satisfied() {
test_column_row_satisfied(CompositeConstraint::new(
RowConstraint, ColumnConstraint));
}
#[test]
fn composite_violated() {
test_column_row_violated(CompositeConstraint::new(
RowConstraint, ColumnConstraint));
}
#[test]
fn dynamic_satisfied() {
test_column_row_satisfied(DynamicConstraint::with_children(vec![
Box::new(RowConstraint),
Box::new(ColumnConstraint)
]));
}
#[test]
fn dynamic_violated() {
test_column_row_violated(DynamicConstraint::with_children(vec![
Box::new(RowConstraint),
Box::new(ColumnConstraint)
]));
}
}