use std::collections::BTreeSet;
use std::convert::TryInto;
use std::error;
use std::fmt;
use std::str::FromStr;
use error::Error;
use fmt::Display;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum BoardSize {
FourByFour,
NineByNine,
SixteenBySixteen,
}
impl BoardSize {
pub fn get_base_size(&self) -> usize {
match self {
Self::FourByFour => 2,
Self::NineByNine => 3,
Self::SixteenBySixteen => 4,
}
}
}
#[derive(Debug)]
pub struct BoardSizeOutOfRangeError(usize);
impl Display for BoardSizeOutOfRangeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_fmt(format_args!(
"Board size is out of range, {} is not an accepted base size for a board",
self.0,
))
}
}
impl Error for BoardSizeOutOfRangeError {}
impl TryInto<BoardSize> for usize {
type Error = BoardSizeOutOfRangeError;
fn try_into(self) -> Result<BoardSize, Self::Error> {
match self {
2 => Ok(BoardSize::FourByFour),
3 => Ok(BoardSize::NineByNine),
4 => Ok(BoardSize::SixteenBySixteen),
_ => Err(BoardSizeOutOfRangeError(self)),
}
}
}
#[derive(Debug, Clone)]
pub struct Board {
base_size: usize,
cells: Vec<Option<u8>>,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct CellLoc {
base_size: usize,
idx: usize,
}
impl fmt::Display for CellLoc {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({}, {})", self.line(), self.col())
}
}
impl fmt::Debug for CellLoc {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[{}, {}]", self.line(), self.col())
}
}
impl CellLoc {
pub fn at(l: usize, c: usize, board_size: BoardSize) -> Self {
let base_size = board_size.get_base_size();
CellLoc {
idx: l * base_size.pow(2) + c,
base_size,
}
}
pub fn new(idx: usize, board_size: BoardSize) -> Self {
let base_size = board_size.get_base_size();
CellLoc { idx, base_size }
}
pub fn get_index(&self) -> usize {
self.idx
}
pub fn get_possible_values(&self, board: &Board) -> Option<BTreeSet<u8>> {
if board.cells[self.idx].is_some() {
return None;
}
Some(self.calculate_possible_values(board))
}
fn calculate_possible_values(&self, board: &Board) -> BTreeSet<u8> {
let mut possible_values: BTreeSet<u8> = (1..=board.base_size.pow(2) as u8).collect();
let values_iter = self
.iter_line()
.chain(self.iter_col())
.chain(self.iter_square())
.filter_map(|cell_loc| board.cells[cell_loc.idx]);
for value in values_iter {
possible_values.remove(&value);
}
possible_values
}
pub fn line(&self) -> usize {
self.idx / self.base_size.pow(2)
}
pub fn col(&self) -> usize {
self.idx % self.base_size.pow(2)
}
pub fn square(&self) -> usize {
let line_no = self.line();
let col_no = self.col();
(line_no / self.base_size) * self.base_size + (col_no / self.base_size)
}
pub fn iter_line(&self) -> impl Iterator<Item = CellLoc> {
let base_size = self.base_size;
let line_start = self.line() * self.base_size.pow(2);
let line_end = line_start + self.base_size.pow(2);
(line_start..line_end).map(move |idx| CellLoc { idx, base_size })
}
pub fn iter_col(&self) -> impl Iterator<Item = CellLoc> {
let base_size = self.base_size;
let col_no = self.col();
(0..base_size.pow(2)).map(move |line_no| CellLoc {
idx: line_no * base_size.pow(2) + col_no,
base_size,
})
}
pub fn iter_square(&self) -> impl Iterator<Item = CellLoc> {
let base_size = self.base_size;
let line_no = self.idx / self.base_size.pow(2);
let col_no = self.idx % self.base_size.pow(2);
let sq_line = (line_no / base_size) * base_size;
let sq_col = (col_no / base_size) * base_size;
(sq_line..(sq_line + base_size)).flat_map(move |line| {
(sq_col..(sq_col + base_size)).map(move |col| CellLoc {
idx: line * base_size.pow(2) + col,
base_size,
})
})
}
}
impl Board {
#[must_use]
pub fn new(board_size: BoardSize) -> Self {
let base_size = board_size.get_base_size();
Board {
base_size,
cells: vec![None; base_size.pow(4)],
}
}
pub fn board_size(&self) -> BoardSize {
self.base_size.try_into().unwrap()
}
pub fn set(&mut self, loc: &CellLoc, value: u8) -> Option<u8> {
self.cells[loc.get_index()].replace(value)
}
pub fn set_at(&mut self, l: usize, c: usize, value: u8) -> Option<u8> {
let board_size = self.board_size();
self.cells[CellLoc::at(l, c, board_size).get_index()].replace(value)
}
pub fn unset(&mut self, loc: &CellLoc) -> Option<u8> {
self.cells[loc.get_index()].take()
}
#[must_use]
pub fn get(&self, cell: &CellLoc) -> Option<u8> {
self.cells[cell.idx]
}
pub fn get_at(&self, l: usize, c: usize) -> Option<u8> {
self.get(&CellLoc::at(l, c, self.board_size()))
}
pub fn iter_cells(&self) -> impl Iterator<Item = CellLoc> {
let base_size = self.base_size;
(0..self.base_size.pow(4)).map(move |idx| CellLoc { idx, base_size })
}
#[must_use]
pub fn cell_at(&self, l: usize, c: usize) -> CellLoc {
CellLoc::at(l, c, self.board_size())
}
pub fn rotated(&self) -> Self {
let mut board = Board::new(self.board_size());
let width = self.base_size.pow(2);
for cell in self.iter_cells() {
let l = cell.col();
let c = width - 1 - cell.line();
if let Some(value) = self.get(&cell) {
board.set_at(l, c, value);
}
}
board
}
}
impl PartialEq for Board {
fn eq(&self, other: &Self) -> bool {
if self.base_size != other.base_size {
return false;
}
for idx in 0..self.base_size.pow(4) {
if self.cells[idx] != other.cells[idx] {
return false;
}
}
true
}
}
impl fmt::Display for Board {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for l in 0..self.base_size.pow(2) {
for c in 0..self.base_size.pow(2) {
if let Some(value) = self.cells[l * self.base_size.pow(2) + c] {
write!(f, "{} ", value)?;
} else {
write!(f, ". ")?;
}
}
writeln!(f)?;
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct MalformedBoardError;
impl fmt::Display for MalformedBoardError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "This board is not correctly formed")
}
}
impl error::Error for MalformedBoardError {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
None
}
}
impl FromStr for Board {
type Err = MalformedBoardError;
fn from_str(board_as_string: &str) -> Result<Self, Self::Err> {
let board_as_string = board_as_string.replace(" ", "");
let board_as_string = board_as_string.replace("\n", "");
let board_as_string = board_as_string.replace("_", "");
let board_as_string = board_as_string.replace("-", "");
let board_as_string = board_as_string.replace("|", "");
let base_size = (board_as_string.len() as f64).sqrt().sqrt();
if base_size.fract() != 0.0 {
return Err(MalformedBoardError);
}
let board_size: BoardSize = (base_size as usize)
.try_into()
.map_err(|_| MalformedBoardError)?;
let mut table = Board::new(board_size);
for (idx, c) in board_as_string.char_indices() {
match c {
'1'..='9' => {
table.set(
&CellLoc::new(idx, board_size),
c.to_digit(10).unwrap().try_into().unwrap(),
);
}
'.' => continue,
_ => return Err(MalformedBoardError), }
}
Ok(table)
}
}
#[cfg(test)]
mod test {
use super::CellLoc;
use super::{Board, BoardSize};
use std::collections::BTreeSet;
#[test]
fn basics() {
let table = Board::new(BoardSize::FourByFour);
assert!(table.iter_cells().all(|cell| table.get(&cell).is_none()));
}
#[test]
fn set_value() {
let mut table = Board::new(BoardSize::NineByNine);
assert_eq!(table.get_at(0, 0), None);
table.set(&CellLoc::new(0, BoardSize::NineByNine), 3);
assert_eq!(table.get_at(0, 0), Some(3));
}
#[test]
fn square() {
assert_eq!(CellLoc::at(0, 0, BoardSize::NineByNine).square(), 0);
assert_eq!(CellLoc::at(0, 3, BoardSize::NineByNine).square(), 1);
assert_eq!(CellLoc::at(3, 0, BoardSize::NineByNine).square(), 3);
}
#[test]
fn iter_cells() {
let table = Board::new(BoardSize::NineByNine);
assert_eq!(
table
.iter_cells()
.map(|cell| cell.idx)
.collect::<Vec<usize>>(),
(0..81).collect::<Vec<usize>>()
)
}
#[test]
fn iter_square() {
let cell0 = CellLoc {
idx: 0,
base_size: 3,
};
assert_eq!(
cell0
.iter_square()
.map(|cell| cell.idx)
.collect::<Vec<usize>>(),
&[0, 1, 2, 9, 10, 11, 18, 19, 20]
)
}
#[test]
fn possible_values_is_zero() {
let mut table = Board::new(BoardSize::NineByNine);
table.set_at(0, 0, 1);
let mut iter = table.iter_cells();
let cell = iter.next().expect("table should have 81 cells");
assert_eq!(cell.idx, 0);
assert!(cell.get_possible_values(&table).is_none())
}
#[test]
fn possible_values() {
let mut table = Board::new(BoardSize::NineByNine);
table.set_at(0, 1, 2);
table.set_at(0, 2, 3);
table.set_at(1, 0, 4);
table.set_at(2, 2, 5);
let mut iter = table.iter_cells();
let cell = iter.next().expect("table should have 81 cells");
assert_eq!(
cell.get_possible_values(&table),
Some(
vec![1u8, 6, 7, 8, 9]
.iter()
.map(|value| value.to_owned())
.collect::<BTreeSet<u8>>()
)
)
}
#[test]
fn from() {
let table: Board = "................".parse().unwrap();
print!("{}", table);
assert_eq!(table, Board::new(BoardSize::FourByFour));
}
}