pub mod point;
pub use point::Point;
pub mod pointdelta;
pub use pointdelta::PointDelta;
use std::ops::{Index, IndexMut};
use crate::GameResult;
use crate::GridError;
use crate::ensure;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct GridSize {
width: usize,
height: usize,
}
impl GridSize {
pub fn new(width: usize, height: usize) -> GameResult<Self> {
const MAX_DIM: usize = i32::MAX as usize;
ensure!(
width > 0 && width < MAX_DIM && height > 0 && height < MAX_DIM,
GridError::InvalidSize(width, height)
);
ensure!(width.checked_mul(height).is_some(), GridError::AreaOverflow);
Ok(Self { width, height })
}
#[must_use]
pub fn width(&self) -> usize {
self.width
}
#[must_use]
pub fn height(&self) -> usize {
self.height
}
pub fn area(&self) -> GameResult<usize> {
self.width
.checked_mul(self.height)
.ok_or(GridError::AreaOverflow.into())
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Grid<T> {
cells: Vec<T>,
size: GridSize,
}
impl<T: Clone> Grid<T> {
pub fn new(size: GridSize, filler: T) -> GameResult<Self> {
Ok(Self {
cells: vec![filler; size.area()?],
size,
})
}
}
impl<T> Grid<T> {
pub fn from_vec(size: GridSize, cells: Vec<T>) -> GameResult<Self> {
let area = size.area()?;
ensure!(
cells.len() == area,
GridError::CellCountMismatch {
actual: cells.len(),
expected: area
}
);
Ok(Self { cells, size })
}
pub fn new_with_fn<F>(size: GridSize, mut filler: F) -> GameResult<Self>
where
F: FnMut(Point) -> T,
{
let mut cells: Vec<T> = Vec::with_capacity(size.area()?);
for row in 0..size.height {
for col in 0..size.width {
cells.push(filler(Point {
col: i32::try_from(col).map_err(|_| GridError::DimensionTruncated("width"))?,
row: i32::try_from(row).map_err(|_| GridError::DimensionTruncated("height"))?,
}));
}
}
Ok(Self { cells, size })
}
#[must_use]
pub fn size(&self) -> GridSize {
self.size
}
#[must_use]
pub fn get(&self, cell: Point) -> Option<&T> {
self.point_to_index(cell).map(|index| &self.cells[index])
}
pub fn get_mut(&mut self, cell: Point) -> Option<&mut T> {
self.point_to_index(cell)
.map(|index| &mut self.cells[index])
}
pub fn points(&self) -> impl Iterator<Item = Point> {
let width = self.size.width;
(0..self.cells.len()).map(move |index| index_to_point(index, width))
}
pub fn iter(&self) -> impl Iterator<Item = (Point, &T)> {
let width = self.size.width;
self.cells
.iter()
.enumerate()
.map(move |(idx, cell)| (index_to_point(idx, width), cell))
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = (Point, &mut T)> {
let width = self.size.width;
self.cells
.iter_mut()
.enumerate()
.map(move |(idx, cell)| (index_to_point(idx, width), cell))
}
#[must_use]
pub fn is_in_bounds(&self, point: Point) -> bool {
self.point_to_index(point).is_some()
}
#[must_use]
pub fn contains_point(&self, point: Point) -> bool {
self.is_in_bounds(point)
}
fn point_to_index(&self, point: Point) -> Option<usize> {
let row = usize::try_from(point.row).ok()?;
let col = usize::try_from(point.col).ok()?;
if row >= self.size.height || col >= self.size.width {
return None;
}
let index = self.size.width * row + col;
if index >= self.cells.len() {
return None;
}
Some(index)
}
fn neighbors_inner(
&self,
center: Point,
deltas: &[PointDelta],
) -> impl Iterator<Item = (Point, &T)> {
deltas.iter().filter_map(move |delta| {
let point = center + *delta;
self.get(point).map(|value| (point, value))
})
}
fn neighbors_inner_mut(
&mut self,
center: Point,
deltas: &[PointDelta],
) -> impl Iterator<Item = (Point, &mut T)> {
let neighbors: Vec<(Point, usize)> = deltas
.iter()
.filter_map(|delta| {
let point = center + *delta;
self.point_to_index(point).map(|index| (point, index))
})
.collect();
self.cells
.iter_mut()
.enumerate()
.filter_map(move |(index, value)| {
neighbors
.iter()
.find_map(|(point, neighbor_index)| {
(*neighbor_index == index).then_some(*point)
})
.map(|point| (point, value))
})
}
pub fn cardinal_neighbors(&self, center: Point) -> impl Iterator<Item = (Point, &T)> {
self.neighbors_inner(center, &PointDelta::CARDINALS)
}
pub fn cardinal_neighbors_mut(
&mut self,
center: Point,
) -> impl Iterator<Item = (Point, &mut T)> {
self.neighbors_inner_mut(center, &PointDelta::CARDINALS)
}
pub fn diagonal_neighbors(&self, center: Point) -> impl Iterator<Item = (Point, &T)> {
self.neighbors_inner(center, &PointDelta::DIAGONALS)
}
pub fn diagonal_neighbors_mut(
&mut self,
center: Point,
) -> impl Iterator<Item = (Point, &mut T)> {
self.neighbors_inner_mut(center, &PointDelta::DIAGONALS)
}
pub fn all_neighbors(&self, center: Point) -> impl Iterator<Item = (Point, &T)> {
self.neighbors_inner(center, &PointDelta::ALL_DIRECTIONS)
}
pub fn all_neighbors_mut(&mut self, center: Point) -> impl Iterator<Item = (Point, &mut T)> {
self.neighbors_inner_mut(center, &PointDelta::ALL_DIRECTIONS)
}
pub fn row_at(&self, cell: Point) -> impl Iterator<Item = (Point, &T)> {
let desired = cell.row;
let width = self.size.width;
self.cells
.iter()
.enumerate()
.filter_map(move |(idx, cell)| {
let point = index_to_point(idx, width);
if point.row == desired {
Some((point, cell))
} else {
None
}
})
}
pub fn row_at_mut(&mut self, cell: Point) -> impl Iterator<Item = (Point, &mut T)> {
let desired = cell.row;
let width = self.size.width;
self.cells
.iter_mut()
.enumerate()
.filter_map(move |(idx, cell)| {
let point = index_to_point(idx, width);
if point.row == desired {
Some((point, cell))
} else {
None
}
})
}
pub fn col_at(&self, cell: Point) -> impl Iterator<Item = (Point, &T)> {
let desired = cell.col;
let width = self.size.width;
self.cells
.iter()
.enumerate()
.filter_map(move |(idx, cell)| {
let point = index_to_point(idx, width);
if point.col == desired {
Some((point, cell))
} else {
None
}
})
}
pub fn col_at_mut(&mut self, cell: Point) -> impl Iterator<Item = (Point, &mut T)> {
let desired = cell.col;
let width = self.size.width;
self.cells
.iter_mut()
.enumerate()
.filter_map(move |(idx, cell)| {
let point = index_to_point(idx, width);
if point.col == desired {
Some((point, cell))
} else {
None
}
})
}
}
impl<T> Index<Point> for Grid<T> {
type Output = T;
fn index(&self, point: Point) -> &Self::Output {
&self.cells[self.point_to_index(point).unwrap()]
}
}
impl<T> IndexMut<Point> for Grid<T> {
fn index_mut(&mut self, point: Point) -> &mut Self::Output {
let idx = self
.point_to_index(point)
.expect("point index out of bounds");
&mut self.cells[idx]
}
}
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::cast_possible_wrap)]
fn index_to_point(index: usize, width: usize) -> Point {
Point {
col: (index % width) as i32,
row: (index / width) as i32,
}
}
#[cfg(test)]
mod tests {
use super::{Grid, GridSize, Point};
use crate::{GameError, GridError};
use std::collections::BTreeMap;
use std::ops::IndexMut;
fn size(width: usize, height: usize) -> GridSize {
GridSize::new(width, height).expect("valid grid size")
}
fn sample_grid() -> Grid<i32> {
Grid::from_vec(size(3, 3), (0..9).collect()).expect("valid grid")
}
fn collect_neighbor_values<'a>(
neighbors: impl Iterator<Item = (Point, &'a i32)>,
) -> Vec<(Point, i32)> {
neighbors.map(|(point, value)| (point, *value)).collect()
}
#[test]
fn grid_size_new_accepts_positive_dimensions() {
let size = GridSize::new(4, 7).expect("positive dimensions are valid");
assert_eq!(size.width(), 4);
assert_eq!(size.height(), 7);
assert_eq!(size.area().expect("area fits"), 28);
}
#[test]
fn grid_size_new_rejects_zero_dimensions() {
assert_eq!(
GridSize::new(0, 2),
Err(GameError::GridError(GridError::InvalidSize(0, 2)))
);
assert_eq!(
GridSize::new(2, 0),
Err(GameError::GridError(GridError::InvalidSize(2, 0)))
);
}
#[test]
fn grid_size_new_rejects_dimensions_that_reach_i32_max() {
let max = i32::MAX as usize;
assert_eq!(
GridSize::new(max, 2),
Err(GameError::GridError(GridError::InvalidSize(max, 2)))
);
assert_eq!(
GridSize::new(2, max),
Err(GameError::GridError(GridError::InvalidSize(2, max)))
);
}
#[test]
#[cfg(target_pointer_width = "32")]
fn grid_size_new_rejects_overflowing_area_on_32_bit_targets() {
assert_eq!(
GridSize::new(65_536, 65_536),
Err(GameError::GridError(GridError::AreaOverflow))
);
}
#[test]
#[cfg(target_pointer_width = "64")]
fn grid_size_new_accepts_largest_allowed_dimensions_on_64_bit_targets() {
let max_allowed = (i32::MAX - 1) as usize;
let size = GridSize::new(max_allowed, max_allowed).expect("area fits in 64-bit usize");
assert_eq!(size.width(), max_allowed);
assert_eq!(size.height(), max_allowed);
assert_eq!(size.area().expect("area fits"), max_allowed * max_allowed);
}
#[test]
fn new_fills_every_cell() {
let grid = Grid::new(size(3, 2), "x").expect("valid grid");
assert_eq!(grid.size(), size(3, 2));
assert!(grid.iter().all(|(_, value)| *value == "x"));
assert_eq!(grid.iter().count(), 6);
}
#[test]
fn from_vec_preserves_row_major_cells() {
let grid = Grid::from_vec(size(3, 2), vec![10, 11, 12, 20, 21, 22]).expect("valid grid");
assert_eq!(grid[Point::new(0, 0)], 10);
assert_eq!(grid[Point::new(2, 0)], 12);
assert_eq!(grid[Point::new(0, 1)], 20);
assert_eq!(grid[Point::new(2, 1)], 22);
}
#[test]
fn from_vec_rejects_cell_count_mismatch() {
assert_eq!(
Grid::from_vec(size(2, 3), vec![1, 2, 3]),
Err(GameError::GridError(GridError::CellCountMismatch {
actual: 3,
expected: 6
}))
);
}
#[test]
fn new_with_fn_receives_points_in_row_major_order() {
let mut visited = Vec::new();
let grid = Grid::new_with_fn(size(3, 2), |point| {
visited.push(point);
point.row * 10 + point.col
})
.expect("valid grid");
assert_eq!(
visited,
vec![
Point::new(0, 0),
Point::new(1, 0),
Point::new(2, 0),
Point::new(0, 1),
Point::new(1, 1),
Point::new(2, 1),
]
);
assert_eq!(grid[Point::new(2, 1)], 12);
}
#[test]
fn get_and_bounds_check_reject_negative_and_oversized_points() {
let grid = sample_grid();
assert_eq!(grid.get(Point::new(1, 1)), Some(&4));
assert!(grid.is_in_bounds(Point::new(2, 2)));
assert!(grid.contains_point(Point::new(2, 2)));
assert!(!grid.is_in_bounds(Point::new(-1, 0)));
assert!(!grid.contains_point(Point::new(-1, 0)));
assert!(!grid.is_in_bounds(Point::new(0, -1)));
assert!(!grid.is_in_bounds(Point::new(3, 0)));
assert!(!grid.is_in_bounds(Point::new(0, 3)));
assert_eq!(grid.get(Point::new(4, 0)), None);
}
#[test]
fn get_mut_updates_in_bounds_cells_only() {
let mut grid = sample_grid();
*grid.get_mut(Point::new(1, 1)).expect("cell exists") = 99;
assert_eq!(grid.get(Point::new(1, 1)), Some(&99));
assert_eq!(grid.get_mut(Point::new(3, 1)), None);
}
#[test]
fn point_to_index_rejects_points_beyond_backing_storage() {
let grid = Grid {
cells: vec![1],
size: size(2, 2),
};
assert_eq!(grid.point_to_index(Point::new(0, 0)), Some(0));
assert_eq!(grid.point_to_index(Point::new(1, 0)), None);
}
#[test]
fn points_iterates_in_row_major_order() {
let grid = Grid::new(size(2, 2), ()).expect("valid grid");
assert_eq!(
grid.points().collect::<Vec<_>>(),
vec![
Point::new(0, 0),
Point::new(1, 0),
Point::new(0, 1),
Point::new(1, 1),
]
);
}
#[test]
fn iter_pairs_points_with_values() {
let grid = Grid::from_vec(size(2, 2), vec!['a', 'b', 'c', 'd']).expect("valid grid");
assert_eq!(
grid.iter()
.map(|(point, value)| (point, *value))
.collect::<Vec<_>>(),
vec![
(Point::new(0, 0), 'a'),
(Point::new(1, 0), 'b'),
(Point::new(0, 1), 'c'),
(Point::new(1, 1), 'd'),
]
);
}
#[test]
fn iter_mut_pairs_points_with_mutable_values() {
let mut grid =
Grid::new_with_fn(size(2, 2), |point| point.row * 10 + point.col).expect("valid grid");
for (point, value) in grid.iter_mut() {
*value += point.col + point.row;
}
assert_eq!(grid[Point::new(0, 0)], 0);
assert_eq!(grid[Point::new(1, 0)], 2);
assert_eq!(grid[Point::new(0, 1)], 11);
assert_eq!(grid[Point::new(1, 1)], 13);
}
#[test]
fn index_mut_updates_cell() {
let mut grid = sample_grid();
grid[Point::new(2, 2)] = 42;
*grid.index_mut(Point::new(0, 0)) = 24;
assert_eq!(grid[Point::new(2, 2)], 42);
assert_eq!(grid[Point::new(0, 0)], 24);
}
#[test]
fn cardinal_neighbors_filters_to_in_bounds_cells() {
let grid = sample_grid();
assert_eq!(
collect_neighbor_values(grid.cardinal_neighbors(Point::new(1, 1))),
vec![
(Point::new(1, 0), 1),
(Point::new(0, 1), 3),
(Point::new(2, 1), 5),
(Point::new(1, 2), 7),
]
);
assert_eq!(
collect_neighbor_values(grid.cardinal_neighbors(Point::new(0, 0))),
vec![(Point::new(1, 0), 1), (Point::new(0, 1), 3),]
);
}
#[test]
fn diagonal_neighbors_filters_to_in_bounds_cells() {
let grid = sample_grid();
assert_eq!(
collect_neighbor_values(grid.diagonal_neighbors(Point::new(1, 1))),
vec![
(Point::new(0, 0), 0),
(Point::new(2, 0), 2),
(Point::new(0, 2), 6),
(Point::new(2, 2), 8),
]
);
assert_eq!(
collect_neighbor_values(grid.diagonal_neighbors(Point::new(0, 0))),
vec![(Point::new(1, 1), 4)]
);
}
#[test]
fn all_neighbors_filters_to_in_bounds_cells() {
let grid = sample_grid();
assert_eq!(grid.all_neighbors(Point::new(1, 1)).count(), 8);
assert_eq!(
collect_neighbor_values(grid.all_neighbors(Point::new(0, 0))),
vec![
(Point::new(1, 0), 1),
(Point::new(0, 1), 3),
(Point::new(1, 1), 4),
]
);
}
#[test]
fn mutable_neighbor_iterators_update_only_neighbors() {
let mut grid = Grid::new(size(3, 3), 0).expect("valid grid");
for (_, value) in grid.cardinal_neighbors_mut(Point::new(1, 1)) {
*value += 1;
}
for (_, value) in grid.diagonal_neighbors_mut(Point::new(1, 1)) {
*value += 10;
}
for (_, value) in grid.all_neighbors_mut(Point::new(0, 0)) {
*value += 100;
}
let values = grid
.iter()
.map(|(point, value)| (point, *value))
.collect::<BTreeMap<_, _>>();
assert_eq!(values[&Point::new(0, 0)], 10);
assert_eq!(values[&Point::new(1, 0)], 101);
assert_eq!(values[&Point::new(2, 0)], 10);
assert_eq!(values[&Point::new(0, 1)], 101);
assert_eq!(values[&Point::new(1, 1)], 100);
assert_eq!(values[&Point::new(2, 1)], 1);
assert_eq!(values[&Point::new(0, 2)], 10);
assert_eq!(values[&Point::new(1, 2)], 1);
assert_eq!(values[&Point::new(2, 2)], 10);
}
#[test]
fn row_at_returns_matching_row_cells() {
let grid = sample_grid();
assert_eq!(
collect_neighbor_values(grid.row_at(Point::new(1, 1))),
vec![
(Point::new(0, 1), 3),
(Point::new(1, 1), 4),
(Point::new(2, 1), 5),
]
);
assert!(grid.row_at(Point::new(1, -1)).next().is_none());
assert!(grid.row_at(Point::new(1, 3)).next().is_none());
}
#[test]
fn row_at_mut_updates_matching_row_cells() {
let mut grid = sample_grid();
for (point, value) in grid.row_at_mut(Point::new(99, 1)) {
*value += point.col;
}
assert_eq!(grid[Point::new(0, 0)], 0);
assert_eq!(grid[Point::new(0, 1)], 3);
assert_eq!(grid[Point::new(1, 1)], 5);
assert_eq!(grid[Point::new(2, 1)], 7);
assert_eq!(grid[Point::new(2, 2)], 8);
assert!(grid.row_at_mut(Point::new(0, -1)).next().is_none());
assert!(grid.row_at_mut(Point::new(0, 3)).next().is_none());
}
#[test]
fn col_at_returns_matching_column_cells() {
let grid = sample_grid();
assert_eq!(
collect_neighbor_values(grid.col_at(Point::new(1, 99))),
vec![
(Point::new(1, 0), 1),
(Point::new(1, 1), 4),
(Point::new(1, 2), 7),
]
);
assert!(grid.col_at(Point::new(-1, 1)).next().is_none());
assert!(grid.col_at(Point::new(3, 1)).next().is_none());
}
#[test]
fn col_at_mut_updates_matching_column_cells() {
let mut grid = sample_grid();
for (point, value) in grid.col_at_mut(Point::new(1, 99)) {
*value += point.row;
}
assert_eq!(grid[Point::new(0, 0)], 0);
assert_eq!(grid[Point::new(1, 0)], 1);
assert_eq!(grid[Point::new(1, 1)], 5);
assert_eq!(grid[Point::new(1, 2)], 9);
assert_eq!(grid[Point::new(2, 2)], 8);
assert!(grid.col_at_mut(Point::new(-1, 0)).next().is_none());
assert!(grid.col_at_mut(Point::new(3, 0)).next().is_none());
}
}