use rand::seq::IteratorRandom;
use std::collections::HashMap;
use std::convert::{From, Into};
use std::fmt;
use std::iter::Filter;
use std::ops::{Deref, DerefMut};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Cell {
global_width: u8,
global_depth: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Grid {
start: Cell,
end: Cell,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Cells {
grid: Grid,
current: Cell,
consumed: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Rows {
grid: Grid,
current: Grid,
consumed: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Columns {
grid: Grid,
current: Grid,
consumed: bool,
}
#[derive(Debug, Clone)]
pub struct GridMap<V> {
grid: Grid,
hashmap: HashMap<Cell, V>,
}
impl Cell {
pub fn new(global_width: u8, global_depth: u8) -> Self {
Self {
global_width,
global_depth,
}
}
pub fn at(self, other: Cell) -> bool {
self == other
}
pub fn aligns(self, other: Cell) -> bool {
self.global_width == other.global_width || self.global_depth == other.global_depth
}
pub fn aligns_panic(self, other: Cell) {
if !self.aligns(other) {
panic!("cells have no common lines! cell:{self}, other:{other}")
}
}
pub fn within(self, grid: Grid) -> bool {
(grid.start.global_width..=grid.end.global_width).contains(&self.global_width)
&& (grid.start.global_depth..=grid.end.global_depth).contains(&self.global_depth)
}
pub fn within_panic(self, grid: Grid) {
if !self.within(grid) {
panic!("cell is not within given grid! cell:{self}, grid:{grid}")
}
}
pub fn global_width(self) -> u8 {
self.global_width
}
pub fn global_depth(self) -> u8 {
self.global_depth
}
pub fn width(self, grid: Grid) -> u8 {
self.within_panic(grid);
self.global_width - grid.start.global_width
}
pub fn width_gap(self, grid: Grid) -> u8 {
self.within_panic(grid);
grid.end.global_width - self.global_width
}
pub fn depth(self, grid: Grid) -> u8 {
self.within_panic(grid);
self.global_depth - grid.start.global_depth
}
pub fn depth_gap(self, grid: Grid) -> u8 {
self.within_panic(grid);
grid.end.global_depth - self.global_depth
}
pub fn will_underflow_depth(self, grid: Grid, step: u8) -> bool {
self.within_panic(grid);
self.global_depth < step || self.global_depth - step < grid.start.global_depth
}
pub fn will_overflow_depth(self, grid: Grid, step: u8) -> bool {
self.within_panic(grid);
self.global_depth > u8::MAX - step || self.global_depth + step > grid.end.global_depth
}
pub fn will_underflow_width(self, grid: Grid, step: u8) -> bool {
self.within_panic(grid);
self.global_width < step || self.global_width - step < grid.start.global_width
}
pub fn will_overflow_width(self, grid: Grid, step: u8) -> bool {
self.within_panic(grid);
self.global_width > u8::MAX - step || self.global_width + step > grid.end.global_width
}
pub fn strict_up(self, grid: Grid, step: u8) -> Cell {
if self.will_underflow_depth(grid, step) {
panic!(
"this operation will violate grid upper bounds! cell:{self}, grid:{grid}, step:{step}"
);
}
Cell {
global_width: self.global_width,
global_depth: self.global_depth - step,
}
}
pub fn strict_down(self, grid: Grid, step: u8) -> Cell {
if self.will_overflow_depth(grid, step) {
panic!(
"this operation will violate grid lower bounds! cell:{self}, grid:{grid}, step:{step}"
);
}
Cell {
global_width: self.global_width,
global_depth: self.global_depth + step,
}
}
pub fn strict_left(self, grid: Grid, step: u8) -> Cell {
if self.will_underflow_width(grid, step) {
panic!(
"this operation will violate grid left bounds! cell:{self}, grid:{grid}, step:{step}"
);
}
Cell {
global_width: self.global_width - step,
global_depth: self.global_depth,
}
}
pub fn strict_right(self, grid: Grid, step: u8) -> Cell {
if self.will_overflow_width(grid, step) {
panic!(
"this operation will violate grid right bounds! cell:{self}, grid:{grid}, step:{step}"
);
}
Cell {
global_width: self.global_width + step,
global_depth: self.global_depth,
}
}
pub fn saturating_up(self, grid: Grid, step: u8) -> Cell {
let next_depth = if self.will_underflow_depth(grid, step) {
grid.start.global_depth
} else {
self.global_depth - step
};
Cell {
global_width: self.global_width,
global_depth: next_depth,
}
}
pub fn saturating_down(self, grid: Grid, step: u8) -> Cell {
let next_depth = if self.will_overflow_depth(grid, step) {
grid.end.global_depth
} else {
self.global_depth + step
};
Cell {
global_width: self.global_width,
global_depth: next_depth,
}
}
pub fn saturating_left(self, grid: Grid, step: u8) -> Cell {
let next_width = if self.will_underflow_width(grid, step) {
grid.start.global_width
} else {
self.global_width - step
};
Cell {
global_width: next_width,
global_depth: self.global_depth,
}
}
pub fn saturating_right(self, grid: Grid, step: u8) -> Cell {
let next_width = if self.will_overflow_width(grid, step) {
grid.end.global_width
} else {
self.global_width + step
};
Cell {
global_width: next_width,
global_depth: self.global_depth,
}
}
pub fn overflowing_up(self, grid: Grid, step: u8) -> (Cell, bool) {
let underflowed = self.will_underflow_depth(grid, step);
let next_depth = if underflowed {
grid.end.global_depth - ((step - self.depth(grid) - 1) % grid.depth())
} else {
self.global_depth - step
};
(
Cell {
global_width: self.global_width,
global_depth: next_depth,
},
underflowed,
)
}
pub fn overflowing_down(self, grid: Grid, step: u8) -> (Cell, bool) {
let overflowed = self.will_overflow_depth(grid, step);
let next_depth = if overflowed {
grid.start.global_depth + ((step - self.depth_gap(grid) - 1) % grid.depth())
} else {
self.global_depth + step
};
(
Cell {
global_width: self.global_width,
global_depth: next_depth,
},
overflowed,
)
}
pub fn overflowing_left(self, grid: Grid, step: u8) -> (Cell, bool) {
let underflowed = self.will_underflow_width(grid, step);
let next_width = if underflowed {
grid.end.global_width - ((step - self.width(grid) - 1) % grid.width())
} else {
self.global_width - step
};
(
Cell {
global_width: next_width,
global_depth: self.global_depth,
},
underflowed,
)
}
pub fn overflowing_right(self, grid: Grid, step: u8) -> (Cell, bool) {
let overflowed = self.will_overflow_width(grid, step);
let next_width = if overflowed {
grid.start.global_width + ((step - self.width_gap(grid) - 1) % grid.width())
} else {
self.global_width + step
};
(
Cell {
global_width: next_width,
global_depth: self.global_depth,
},
overflowed,
)
}
pub fn wrapping_up(self, grid: Grid, step: u8) -> Cell {
self.overflowing_up(grid, step).0
}
pub fn wrapping_down(self, grid: Grid, step: u8) -> Cell {
self.overflowing_down(grid, step).0
}
pub fn wrapping_left(self, grid: Grid, step: u8) -> Cell {
self.overflowing_left(grid, step).0
}
pub fn wrapping_right(self, grid: Grid, step: u8) -> Cell {
self.overflowing_right(grid, step).0
}
pub fn project_up(self, grid: Grid) -> Cell {
self.saturating_up(grid, u8::MAX)
}
pub fn project_down(self, grid: Grid) -> Cell {
self.saturating_down(grid, u8::MAX)
}
pub fn project_left(self, grid: Grid) -> Cell {
self.saturating_left(grid, u8::MAX)
}
pub fn project_right(self, grid: Grid) -> Cell {
self.saturating_right(grid, u8::MAX)
}
pub fn strict_towards(self, grid: Grid, target: Cell, step: u8) -> Cell {
self.within_panic(grid);
target.within_panic(grid);
self.aligns_panic(target);
match ((self.into()), (target.into())) {
((_, d1), (_, d2)) if d1 > d2 => self.strict_up(grid, step),
((_, d1), (_, d2)) if d1 < d2 => self.strict_down(grid, step),
((w1, _), (w2, _)) if w1 > w2 => self.strict_left(grid, step),
((w1, _), (w2, _)) if w1 < w2 => self.strict_right(grid, step),
_ => target,
}
}
pub fn saturating_towards(self, grid: Grid, target: Cell, step: u8) -> Cell {
self.within_panic(grid);
target.within_panic(grid);
self.aligns_panic(target);
match ((self.into()), (target.into())) {
((_, d1), (_, d2)) if d1 > d2 => self.saturating_up(grid, step),
((_, d1), (_, d2)) if d1 < d2 => self.saturating_down(grid, step),
((w1, _), (w2, _)) if w1 > w2 => self.saturating_left(grid, step),
((w1, _), (w2, _)) if w1 < w2 => self.saturating_right(grid, step),
_ => target,
}
}
pub fn overflowing_towards(self, grid: Grid, target: Cell, step: u8) -> (Cell, bool) {
self.within_panic(grid);
target.within_panic(grid);
self.aligns_panic(target);
match ((self.into()), (target.into())) {
((_, d1), (_, d2)) if d1 > d2 => self.overflowing_up(grid, step),
((_, d1), (_, d2)) if d1 < d2 => self.overflowing_down(grid, step),
((w1, _), (w2, _)) if w1 > w2 => self.overflowing_left(grid, step),
((w1, _), (w2, _)) if w1 < w2 => self.overflowing_right(grid, step),
_ => (target, false),
}
}
pub fn wrapping_towards(self, grid: Grid, target: Cell, step: u8) -> Cell {
self.overflowing_towards(grid, target, step).0
}
pub fn project_towards(self, grid: Grid, target: Cell) -> Cell {
self.saturating_towards(grid, target, u8::MAX)
}
pub fn on_the_edge(self, grid: Grid) -> bool {
self.global_width == grid.start.global_width
|| self.global_width == grid.end.global_width
|| self.global_depth == grid.start.global_depth
|| self.global_depth == grid.end.global_depth
}
}
impl fmt::Display for Cell {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"({w}, {d})",
w = self.global_width,
d = self.global_depth
)
}
}
impl From<(u8, u8)> for Cell {
fn from(value: (u8, u8)) -> Self {
Self {
global_width: value.0,
global_depth: value.1,
}
}
}
#[allow(clippy::from_over_into)]
impl Into<(u8, u8)> for Cell {
fn into(self) -> (u8, u8) {
(self.global_width, self.global_depth)
}
}
impl Grid {
pub fn new(width: u8, depth: u8) -> Self {
if width < 1 || depth < 1 {
panic!("can't create grid with width < 0 or depth < 0!")
}
Self {
start: Cell {
global_width: 0,
global_depth: 0,
},
end: Cell {
global_width: width - 1,
global_depth: depth - 1,
},
}
}
pub fn indented(width: u8, depth: u8, indent: (u8, u8)) -> Self {
if width < 1 || depth < 1 {
panic!("can't create grid with width < 0 or depth < 0!")
}
Self {
start: Cell {
global_width: indent.0,
global_depth: indent.1,
},
end: Cell {
global_width: indent.0 + width - 1,
global_depth: indent.1 + depth - 1,
},
}
}
pub fn within(self, grid: Grid) -> bool {
self.start.within(grid) && self.end.within(grid)
}
pub fn within_panic(self, grid: Grid) {
if !self.within(grid) {
panic!("subgrid is not within given grid! subgrid:{self}, grid:{grid}")
}
}
pub fn member(self, width: u8, depth: u8) -> Cell {
self.start
.strict_right(self, width)
.strict_down(self, depth)
}
pub fn area(self, width: u8, depth: u8) -> Grid {
if width < 1 || depth < 1 {
panic!("can't create grid with width < 0 or depth < 0!")
}
Grid {
start: self.start,
end: self
.start
.strict_right(self, width - 1)
.strict_down(self, depth - 1),
}
}
pub fn slice(self, width: u8, depth: u8, indent: (u8, u8)) -> Grid {
if width < 1 || depth < 1 {
panic!("can't create grid with width < 0 or depth < 0!")
}
Grid {
start: self
.start
.strict_right(self, indent.0)
.strict_down(self, indent.1),
end: self
.start
.strict_right(self, indent.0 + width - 1)
.strict_down(self, indent.1 + depth - 1),
}
}
pub fn start(self) -> Cell {
self.start
}
pub fn end(self) -> Cell {
self.end
}
pub fn width(self) -> u8 {
self.end.global_width - self.start.global_width + 1
}
pub fn depth(self) -> u8 {
self.end.global_depth - self.start.global_depth + 1
}
pub fn size(self) -> u16 {
self.width() as u16 * self.depth() as u16
}
pub fn cells(self) -> Cells {
Cells::from(self)
}
pub fn rows(self) -> Rows {
Rows::from(self)
}
pub fn columns(self) -> Columns {
Columns::from(self)
}
}
impl From<(Cell, Cell)> for Grid {
fn from(value: (Cell, Cell)) -> Self {
let (start, end) = value;
if start.global_width > end.global_width || start.global_depth > end.global_depth {
panic!("start cell overflows end cell! start:{start}, end:{end}")
}
Self { start, end }
}
}
#[allow(clippy::from_over_into)]
impl Into<(Cell, Cell)> for Grid {
fn into(self) -> (Cell, Cell) {
(self.start, self.end)
}
}
impl From<((u8, u8), (u8, u8))> for Grid {
fn from(value: ((u8, u8), (u8, u8))) -> Self {
let (start, end): (Cell, Cell) = (value.0.into(), value.1.into());
if start.global_width > end.global_width || start.global_depth > end.global_depth {
panic!("start cell overflows end cell! start:{start}, end:{end}")
}
Self { start, end }
}
}
#[allow(clippy::from_over_into)]
impl Into<((u8, u8), (u8, u8))> for Grid {
fn into(self) -> ((u8, u8), (u8, u8)) {
(self.start.into(), self.end.into())
}
}
impl fmt::Display for Grid {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[{start}:{end}]", start = self.start, end = self.end)
}
}
impl From<Grid> for Cells {
fn from(grid: Grid) -> Self {
Self {
grid,
current: grid.start,
consumed: false,
}
}
}
impl From<Grid> for Columns {
fn from(grid: Grid) -> Self {
Self {
grid,
current: Grid {
start: grid.start,
end: grid.start.project_down(grid),
},
consumed: false,
}
}
}
impl From<Grid> for Rows {
fn from(grid: Grid) -> Self {
Self {
grid,
current: Grid {
start: grid.start,
end: grid.start.project_right(grid),
},
consumed: false,
}
}
}
impl Iterator for Cells {
type Item = Cell;
fn next(&mut self) -> Option<Self::Item> {
if self.consumed {
return None;
}
if self.current == self.grid.end {
self.consumed = true;
return Some(self.current);
}
let previous = self.current;
match self.current.overflowing_right(self.grid, 1) {
(next, true) => self.current = next.wrapping_down(self.grid, 1),
(next, false) => self.current = next,
}
Some(previous)
}
}
impl Iterator for Columns {
type Item = Grid;
fn next(&mut self) -> Option<Self::Item> {
if self.consumed {
return None;
}
if self.current.end == self.grid.end {
self.consumed = true;
return Some(self.current);
}
let previous = self.current;
self.current = Grid {
start: self.current.start.saturating_right(self.grid, 1),
end: self.current.end.saturating_right(self.grid, 1),
};
Some(previous)
}
}
impl Iterator for Rows {
type Item = Grid;
fn next(&mut self) -> Option<Self::Item> {
if self.consumed {
return None;
}
if self.current.end == self.grid.end {
self.consumed = true;
return Some(self.current);
}
let previous = self.current;
self.current = Grid {
start: self.current.start.saturating_down(self.grid, 1),
end: self.current.end.saturating_down(self.grid, 1),
};
Some(previous)
}
}
impl<V> From<Grid> for GridMap<V> {
fn from(grid: Grid) -> Self {
Self {
grid,
hashmap: HashMap::new(),
}
}
}
impl<V> From<(Grid, HashMap<Cell, V>)> for GridMap<V> {
fn from(data: (Grid, HashMap<Cell, V>)) -> Self {
data.1.keys().for_each(|cell| cell.within_panic(data.0));
Self {
grid: data.0,
hashmap: data.1,
}
}
}
impl<V> GridMap<V> {
pub fn new(width: u8, depth: u8) -> Self {
Self {
grid: Grid::new(width, depth),
hashmap: HashMap::new(),
}
}
pub fn insert(&mut self, cell: Cell, value: V) -> Option<V> {
cell.within_panic(self.grid);
self.hashmap.insert(cell, value)
}
pub fn vacant_insert(&mut self, cell: Cell, value: V) -> bool {
cell.within_panic(self.grid);
if self.vacant(cell) {
self.hashmap.insert(cell, value);
true
} else {
false
}
}
pub fn grid(&self) -> Grid {
self.grid
}
pub fn occupied(&self, cell: Cell) -> bool {
cell.within_panic(self.grid);
self.contains_key(&cell)
}
pub fn vacant(&self, cell: Cell) -> bool {
cell.within_panic(self.grid);
!self.contains_key(&cell)
}
pub fn occupied_count(&self) -> u16 {
self.len() as u16
}
pub fn vacant_count(&self) -> u16 {
self.grid.size() - self.occupied_count()
}
pub fn all_occupied(&self) -> Filter<Cells, impl FnMut(&Cell) -> bool> {
self.grid.cells().filter(|&cell| self.occupied(cell))
}
pub fn all_vacant(&self) -> Filter<Cells, impl FnMut(&Cell) -> bool> {
self.grid.cells().filter(|&cell| self.vacant(cell))
}
pub fn first_occupied(&self) -> Option<Cell> {
self.all_occupied().next()
}
pub fn first_vacant(&self) -> Option<Cell> {
self.all_vacant().next()
}
pub fn random_occupied(&self) -> Option<Cell> {
self.all_occupied().choose(&mut rand::rng())
}
pub fn random_vacant(&self) -> Option<Cell> {
self.all_vacant().choose(&mut rand::rng())
}
}
impl<V> Deref for GridMap<V> {
type Target = HashMap<Cell, V>;
fn deref(&self) -> &Self::Target {
&self.hashmap
}
}
impl<V> DerefMut for GridMap<V> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.hashmap
}
}