use std::iter::{FusedIterator, IntoIterator, Iterator};
use bitvec::prelude::*;
use crate::{Point2D, Size2D};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BoardArea {
board_size: Size2D,
data: BitVec,
}
impl BoardArea {
pub fn new(for_board_size: Size2D) -> Self {
Self {
board_size: for_board_size,
data: BitVec::repeat(false, for_board_size.size()),
}
}
pub fn from_points<I: IntoIterator<Item = Point2D>>(for_board_size: Size2D, points: I) -> Self {
let mut retv = Self::new(for_board_size);
retv.extend(points);
retv
}
pub fn board_size(&self) -> Size2D { self.board_size }
pub fn len(&self) -> usize { self.data.count_ones() }
pub fn is_empty(&self) -> bool { self.len() == 0 }
pub fn clear(&mut self) { self.data.fill(false); }
pub fn contains(&self, value: Point2D) -> bool {
match self.bit_idx(value) {
Some(idx) => self.data.get(idx).as_deref() == Some(&true),
None => false,
}
}
pub fn add_point(&mut self, value: Point2D) -> bool {
match self.bit_idx(value) {
Some(idx) => !self.data.replace(idx, true),
None => false,
}
}
pub fn remove_point(&mut self, value: Point2D) -> bool {
match self.bit_idx(value) {
Some(idx) => self.data.replace(idx, false),
None => false,
}
}
pub fn bounds(&self) -> Option<(Point2D, Point2D)> {
let mut ones = self.data.iter_ones();
let first = Point2D::new_from_idx(ones.next()?, &self.board_size);
let mut top_left = first;
let mut bottom_right = first;
for idx in ones {
let pos = Point2D::new_from_idx(idx, &self.board_size);
top_left.x = top_left.x.min(pos.x);
bottom_right.x = bottom_right.x.max(pos.x);
bottom_right.y = pos.y;
}
Some((top_left, bottom_right))
}
pub fn min_x(&self) -> Option<usize> { self.bounds().map(|(top_left, _)| top_left.x) }
pub fn min_y(&self) -> Option<usize> { self.bounds().map(|(top_left, _)| top_left.y) }
pub fn max_x(&self) -> Option<usize> { self.bounds().map(|(_, bottom_right)| bottom_right.x) }
pub fn max_y(&self) -> Option<usize> { self.bounds().map(|(_, bottom_right)| bottom_right.y) }
pub fn union(&self, other: &Self) -> Option<Self> {
let mut retv = self.cloned_for_op_with(other)?;
retv.data |= &other.data;
Some(retv)
}
pub fn intersection(&self, other: &Self) -> Option<Self> {
let mut retv = self.cloned_for_op_with(other)?;
retv.data &= &other.data;
Some(retv)
}
pub fn difference(&self, other: &Self) -> Option<Self> {
let mut retv = self.cloned_for_op_with(other)?;
retv.data &= !other.data.clone();
Some(retv)
}
pub fn symmetric_difference(&self, other: &Self) -> Option<Self> {
let mut retv = self.cloned_for_op_with(other)?;
retv.data ^= &other.data;
Some(retv)
}
pub fn is_subset(&self, other: &Self) -> bool {
self.board_size == other.board_size
&& self
.data
.iter_ones()
.all(|idx| other.data.get(idx).as_deref() == Some(&true))
}
pub fn is_superset(&self, other: &Self) -> bool { other.is_subset(self) }
pub fn is_disjoint(&self, other: &Self) -> bool {
self.board_size != other.board_size
|| !self
.data
.iter_ones()
.any(|idx| other.data.get(idx).as_deref() == Some(&true))
}
pub fn iter(&self) -> BoardAreaIter<'_> { BoardAreaIter { idx: 0, obj: self } }
fn bit_idx(&self, value: Point2D) -> Option<usize> {
value
.is_on_board(self.board_size)
.then(|| value.idx(self.board_size))
}
fn cloned_for_op_with(&self, other: &Self) -> Option<Self> {
(self.board_size == other.board_size).then(|| self.clone())
}
}
impl<'a> Extend<&'a Point2D> for BoardArea {
fn extend<T: IntoIterator<Item = &'a Point2D>>(&mut self, iter: T) {
for pos in iter {
self.add_point(*pos);
}
}
}
impl Extend<Point2D> for BoardArea {
fn extend<T: IntoIterator<Item = Point2D>>(&mut self, iter: T) {
for pos in iter {
self.add_point(pos);
}
}
}
impl<'a> IntoIterator for &'a BoardArea {
type Item = Point2D;
type IntoIter = BoardAreaIter<'a>;
fn into_iter(self) -> Self::IntoIter { self.iter() }
}
impl IntoIterator for BoardArea {
type Item = Point2D;
type IntoIter = BoardAreaIntoIter;
fn into_iter(self) -> Self::IntoIter { BoardAreaIntoIter { idx: 0, obj: self } }
}
#[derive(Debug, Clone)]
pub struct BoardAreaIter<'a> {
obj: &'a BoardArea,
idx: usize,
}
impl Iterator for BoardAreaIter<'_> {
type Item = Point2D;
fn next(&mut self) -> Option<Self::Item> {
let found = self.idx + self.obj.data.get(self.idx..)?.first_one()?;
self.idx = found + 1;
Some(Point2D::new_from_idx(found, &self.obj.board_size))
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = self
.obj
.data
.get(self.idx..)
.map_or(0, bitvec::slice::BitSlice::count_ones);
(remaining, Some(remaining))
}
}
impl ExactSizeIterator for BoardAreaIter<'_> {}
impl FusedIterator for BoardAreaIter<'_> {}
#[derive(Debug, Clone)]
pub struct BoardAreaIntoIter {
obj: BoardArea,
idx: usize,
}
impl Iterator for BoardAreaIntoIter {
type Item = Point2D;
fn next(&mut self) -> Option<Self::Item> {
let found = self.idx + self.obj.data.get(self.idx..)?.first_one()?;
self.idx = found + 1;
Some(Point2D::new_from_idx(found, &self.obj.board_size))
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = self
.obj
.data
.get(self.idx..)
.map_or(0, bitvec::slice::BitSlice::count_ones);
(remaining, Some(remaining))
}
}
impl ExactSizeIterator for BoardAreaIntoIter {}
impl FusedIterator for BoardAreaIntoIter {}
#[cfg(test)]
mod tests {
use super::*;
fn board_size() -> Size2D { Size2D::new(10, 10) }
#[test]
fn it_can_check_if_it_is_empty() {
let mut area = BoardArea::new(board_size());
assert!(area.is_empty());
assert_eq!(area.len(), 0);
area.add_point(Point2D::new(5, 5));
assert!(!area.is_empty());
assert_eq!(area.len(), 1);
}
#[test]
fn it_can_be_cleared_while_keeping_board_size() {
let mut area = BoardArea::from_points(board_size(), [Point2D::new(5, 5)]);
area.clear();
assert!(area.is_empty());
assert_eq!(area.board_size(), board_size());
}
#[test]
fn it_can_add_points() {
let mut area = BoardArea::new(board_size());
let pos = Point2D::new(5, 5);
assert!(area.add_point(pos));
assert!(area.contains(pos));
assert!(!area.add_point(pos));
assert_eq!(area.len(), 1);
}
#[test]
fn it_can_remove_points() {
let mut area = BoardArea::new(board_size());
let pos = Point2D::new(5, 5);
area.add_point(pos);
assert!(area.contains(pos));
assert!(area.remove_point(pos));
assert!(!area.contains(pos));
assert!(!area.remove_point(pos));
}
#[test]
fn it_can_be_extended_from_points_and_from_references() {
let mut area = BoardArea::new(board_size());
area.extend([Point2D::new(1, 1), Point2D::new(2, 2)]);
let more = [Point2D::new(3, 3)];
area.extend(more.iter());
assert_eq!(
Vec::from_iter(&area),
vec![Point2D::new(1, 1), Point2D::new(2, 2), Point2D::new(3, 3)]
);
}
#[test]
fn it_can_iterate_over_points() {
let mut area = BoardArea::new(board_size());
let pos1 = Point2D::new(5, 5);
let pos2 = Point2D::new(6, 6);
area.add_point(pos1);
area.add_point(pos2);
let mut data: Vec<Point2D> = vec![];
for pos in &area {
data.push(pos);
}
assert_eq!(data, vec![pos1, pos2]);
let mut data: Vec<Point2D> = vec![];
for pos in &area {
data.push(pos);
}
assert_eq!(data, vec![pos1, pos2]);
let data: Vec<Point2D> = Vec::from_iter(area);
assert_eq!(data, vec![pos1, pos2]);
}
#[test]
fn it_iterates_in_row_major_order() {
let area = BoardArea::from_points(
board_size(),
[Point2D::new(9, 0), Point2D::new(0, 9), Point2D::new(4, 4)],
);
assert_eq!(
Vec::from_iter(&area),
vec![Point2D::new(9, 0), Point2D::new(4, 4), Point2D::new(0, 9)]
);
}
#[test]
fn it_reports_count_of_remaining_points_while_iterating() {
let area = BoardArea::from_points(board_size(), [Point2D::new(5, 5), Point2D::new(6, 6)]);
let mut iter = area.iter();
assert_eq!(iter.len(), 2);
assert_eq!(iter.size_hint(), (2, Some(2)));
iter.next();
assert_eq!(iter.len(), 1);
assert_eq!(iter.size_hint(), (1, Some(1)));
iter.next();
assert_eq!(iter.len(), 0);
assert_eq!(iter.size_hint(), (0, Some(0)));
assert!(iter.next().is_none());
assert_eq!(iter.len(), 0);
}
#[test]
fn owning_iterator_reports_count_of_remaining_points_while_iterating() {
let area = BoardArea::from_points(board_size(), [Point2D::new(5, 5), Point2D::new(6, 6)]);
let mut iter = area.into_iter();
assert_eq!(iter.len(), 2);
assert_eq!(iter.size_hint(), (2, Some(2)));
iter.next();
assert_eq!(iter.len(), 1);
assert_eq!(iter.size_hint(), (1, Some(1)));
iter.next();
assert_eq!(iter.len(), 0);
assert_eq!(iter.size_hint(), (0, Some(0)));
assert!(iter.next().is_none());
assert_eq!(iter.len(), 0);
}
#[test]
fn when_point_is_off_board_it_is_not_a_member() {
let mut area = BoardArea::new(board_size());
let off_board_x = Point2D::new(15, 0);
assert!(!area.add_point(off_board_x));
assert!(!area.contains(off_board_x));
assert!(!area.contains(Point2D::new(5, 1)));
assert!(area.is_empty());
let off_board_y = Point2D::new(0, 15);
assert!(!area.add_point(off_board_y));
assert!(!area.contains(off_board_y));
assert!(area.is_empty());
assert!(!area.remove_point(off_board_x));
assert!(!area.remove_point(off_board_y));
}
#[test]
fn when_board_has_no_tiles_area_is_always_empty() {
for size in [Size2D::new(0, 0), Size2D::new(0, 5), Size2D::new(5, 0)] {
let mut area = BoardArea::new(size);
assert!(area.is_empty());
assert!(!area.add_point(Point2D::origin()));
assert!(!area.contains(Point2D::origin()));
assert_eq!(area.bounds(), None);
assert!(area.iter().next().is_none());
}
}
#[test]
fn when_area_is_empty_it_has_no_bounds() {
let area = BoardArea::new(board_size());
assert_eq!(area.bounds(), None);
assert_eq!(area.min_x(), None);
assert_eq!(area.min_y(), None);
assert_eq!(area.max_x(), None);
assert_eq!(area.max_y(), None);
}
#[test]
fn it_calculates_bounds_of_single_point_area() {
let area = BoardArea::from_points(board_size(), [Point2D::new(3, 7)]);
assert_eq!(area.bounds(), Some((Point2D::new(3, 7), Point2D::new(3, 7))));
assert_eq!(area.min_x(), Some(3));
assert_eq!(area.max_x(), Some(3));
assert_eq!(area.min_y(), Some(7));
assert_eq!(area.max_y(), Some(7));
}
#[test]
fn it_calculates_bounds_when_extremes_are_in_different_rows() {
let area = BoardArea::from_points(
board_size(),
[Point2D::new(7, 2), Point2D::new(3, 4), Point2D::new(5, 8)],
);
assert_eq!(area.bounds(), Some((Point2D::new(3, 2), Point2D::new(7, 8))));
assert_eq!(area.min_x(), Some(3));
assert_eq!(area.max_x(), Some(7));
assert_eq!(area.min_y(), Some(2));
assert_eq!(area.max_y(), Some(8));
}
#[test]
fn it_calculates_set_operations() {
let size = board_size();
let left = BoardArea::from_points(size, [Point2D::new(1, 1), Point2D::new(2, 2)]);
let right = BoardArea::from_points(size, [Point2D::new(2, 2), Point2D::new(3, 3)]);
assert_eq!(
left.union(&right),
Some(BoardArea::from_points(
size,
[Point2D::new(1, 1), Point2D::new(2, 2), Point2D::new(3, 3)]
))
);
assert_eq!(
left.intersection(&right),
Some(BoardArea::from_points(size, [Point2D::new(2, 2)]))
);
assert_eq!(
left.difference(&right),
Some(BoardArea::from_points(size, [Point2D::new(1, 1)]))
);
assert_eq!(
left.symmetric_difference(&right),
Some(BoardArea::from_points(size, [Point2D::new(1, 1), Point2D::new(3, 3)]))
);
}
#[test]
fn it_calculates_set_operations_on_disjoint_and_equal_areas() {
let size = board_size();
let left = BoardArea::from_points(size, [Point2D::new(1, 1)]);
let right = BoardArea::from_points(size, [Point2D::new(9, 9)]);
let empty = BoardArea::new(size);
assert_eq!(left.intersection(&right), Some(empty.clone()));
assert_eq!(left.difference(&right), Some(left.clone()));
assert_eq!(left.symmetric_difference(&right), left.union(&right));
assert_eq!(left.union(&left), Some(left.clone()));
assert_eq!(left.intersection(&left), Some(left.clone()));
assert_eq!(left.difference(&left), Some(empty.clone()));
assert_eq!(left.symmetric_difference(&left), Some(empty));
}
#[test]
fn it_checks_subset_superset_and_disjoint() {
let size = board_size();
let whole = BoardArea::from_points(
size,
[Point2D::new(1, 1), Point2D::new(2, 2), Point2D::new(3, 3)],
);
let part = BoardArea::from_points(size, [Point2D::new(2, 2)]);
let elsewhere = BoardArea::from_points(size, [Point2D::new(9, 9)]);
assert!(part.is_subset(&whole));
assert!(whole.is_superset(&part));
assert!(!whole.is_subset(&part));
assert!(!part.is_superset(&whole));
assert!(whole.is_subset(&whole));
assert!(whole.is_superset(&whole));
assert!(!part.is_disjoint(&whole));
assert!(elsewhere.is_disjoint(&whole));
assert!(whole.is_disjoint(&elsewhere));
}
#[test]
fn it_treats_empty_area_as_subset_of_every_area_of_same_board_size() {
let size = board_size();
let empty = BoardArea::new(size);
let area = BoardArea::from_points(size, [Point2D::new(1, 1)]);
assert!(empty.is_subset(&area));
assert!(empty.is_subset(&empty));
assert!(area.is_superset(&empty));
assert!(empty.is_disjoint(&area));
assert!(empty.is_disjoint(&empty));
}
#[test]
fn when_board_sizes_differ_set_operations_return_none() {
let left = BoardArea::from_points(board_size(), [Point2D::new(1, 1)]);
let right = BoardArea::from_points(Size2D::new(5, 5), [Point2D::new(1, 1)]);
assert_eq!(left.union(&right), None);
assert_eq!(left.intersection(&right), None);
assert_eq!(left.difference(&right), None);
assert_eq!(left.symmetric_difference(&right), None);
assert_eq!(right.union(&left), None);
assert_eq!(right.intersection(&left), None);
assert_eq!(right.difference(&left), None);
assert_eq!(right.symmetric_difference(&left), None);
}
#[test]
fn when_board_sizes_differ_areas_are_not_comparable() {
let left = BoardArea::from_points(board_size(), [Point2D::new(1, 1)]);
let right = BoardArea::from_points(Size2D::new(5, 5), [Point2D::new(1, 1)]);
assert!(!left.is_subset(&right));
assert!(!left.is_superset(&right));
assert!(left.is_disjoint(&right));
let empty = BoardArea::new(Size2D::new(5, 5));
assert!(!empty.is_subset(&left));
assert!(!left.is_superset(&empty));
let other_empty = BoardArea::new(board_size());
assert!(!empty.is_subset(&other_empty));
assert!(!empty.is_superset(&other_empty));
assert!(empty.is_disjoint(&other_empty));
}
}