116 lines
3.2 KiB
Rust
116 lines
3.2 KiB
Rust
|
use std::collections::HashSet;
|
||
|
|
||
|
use crate::board::Board;
|
||
|
|
||
|
/**
|
||
|
Checks whether a board is valid according to the rules of the game
|
||
|
"Unruly" from Simon Tathams Portable Puzzle Collection.
|
||
|
|
||
|
Only works on boards which have an even number of tiles in width and height,
|
||
|
and which have only two options.
|
||
|
*/
|
||
|
pub fn valid_board_unruly(board: &Board) -> bool {
|
||
|
debug_assert!(board.get_num_options() == 2);
|
||
|
debug_assert!(board.get_width() % 2 == 0);
|
||
|
debug_assert!(board.get_height() % 2 == 0);
|
||
|
|
||
|
fn valid_window(win: &[Option<usize>]) -> bool {
|
||
|
win[0].zip(win[1]).map_or(true, |(x, y)| x != y)
|
||
|
|| win[1].zip(win[2]).map_or(true, |(x, y)| x != y)
|
||
|
}
|
||
|
|
||
|
// check each column and row:
|
||
|
// * may only contain n/2 number of one option
|
||
|
// * may not contain more than 2 consecutives
|
||
|
for x in 0..board.get_width() {
|
||
|
let col = board.get_column(x);
|
||
|
for opt in [0, 1] {
|
||
|
if col.iter().filter(|&&x| x == Some(opt)).count() > (board.get_width() / 2) {
|
||
|
log::warn!("col {x} invalid: too many of {opt}");
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if !col.windows(3).all(valid_window) {
|
||
|
log::warn!("col {x} invalid: invalid window");
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for y in 0..board.get_height() {
|
||
|
let row = board.get_row(y);
|
||
|
for opt in [0, 1] {
|
||
|
if row.iter().filter(|&&x| x == Some(opt)).count() > (board.get_width() / 2) {
|
||
|
log::warn!("row {y} invalid: too many of {opt}");
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if !row.windows(3).all(valid_window) {
|
||
|
log::warn!("row {y} invalid: invalid window");
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
true
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
Checks whether a board is valid according to the rules of ordinary 9x9 Sudoku:
|
||
|
Numbers must be unique in row, column and each 3x3 cell.
|
||
|
*/
|
||
|
pub fn valid_board_sudoku(board: &Board) -> bool {
|
||
|
debug_assert!(board.get_num_options() == 9);
|
||
|
debug_assert!(board.get_width() == 9);
|
||
|
debug_assert!(board.get_height() == 9);
|
||
|
|
||
|
fn get_cell(board: &Board, start_x: usize, start_y: usize) -> Vec<Option<usize>> {
|
||
|
(start_x..start_x + 3)
|
||
|
.flat_map(|x| (start_y..start_y + 3).map(|y| (x, y)).collect::<Vec<_>>())
|
||
|
.map(|(x, y)| board.get_tile(x, y))
|
||
|
.collect()
|
||
|
}
|
||
|
|
||
|
// check columns: must be unique
|
||
|
for x in 0..9 {
|
||
|
let mut set = HashSet::new();
|
||
|
if !board
|
||
|
.get_column(x)
|
||
|
.iter()
|
||
|
.filter_map(|&x| x)
|
||
|
.all(|x| set.insert(x))
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// check rows: must be unique
|
||
|
for y in 0..9 {
|
||
|
let mut set = HashSet::new();
|
||
|
if !board
|
||
|
.get_row(y)
|
||
|
.iter()
|
||
|
.filter_map(|&x| x)
|
||
|
.all(|x| set.insert(x))
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// check 3x3 cells: must be unique
|
||
|
for x in [0,3,6] {
|
||
|
for y in [0,3,6] {
|
||
|
let mut set = HashSet::new();
|
||
|
if !get_cell(board, x, y)
|
||
|
.iter()
|
||
|
.filter_map(|&x| x)
|
||
|
.all(|x| set.insert(x))
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
true
|
||
|
}
|