1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
// Copyright 2020 Zachary Stewart
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::{fmt::Debug, hash::Hash};

/// Trait for coordinates used in [`Dimensions`].
/// Requires [`Debug`] to enable certain common panic messages on misuse.
/// Coordinates are treated as disposable and cheaply cloneable. If you need a complex
/// coordinate type that isn't cheap to clone, you may want to wrap it in `Rc` or `Arc`.
pub trait Coordinate: Debug + Clone + Eq + Hash {}

/// Dimensions of a board.
/// Implements methods needed for the board to check bounds, linearize indexes, and compute
/// neighbor cells.
pub trait Dimensions: Debug {
    /// The type used to identify cells on the board.
    type Coordinate: Coordinate;

    /// Type used in the neighbor iterator.
    type NeighborIterState: NeighborIterState<Dimensions = Self>;

    /// Compute the total size of the dimensions. Used to allocate storage for the board.
    fn total_size(&self) -> usize;

    /// Convert a coordinate to a linear index within this dimension.
    /// Panics if the coordinate is out of range for the dimension.
    fn linearize(&self, coord: &Self::Coordinate) -> usize {
        match self.try_linearize(coord) {
            Some(v) => v,
            None => panic!("{:?} is out of bounds for {:?}", coord, self),
        }
    }

    /// Convert a coordinate to a linear index within this dimension.
    /// Returns `None` if the coordinate is out of bound for the dimension.
    fn try_linearize(&self, coord: &Self::Coordinate) -> Option<usize>;

    /// Iterate the neighbors of the given coordinate.
    fn neighbors(&self, coord: Self::Coordinate) -> NeighborIter<Self::NeighborIterState> {
        NeighborIter {
            dim: self,
            state: Self::NeighborIterState::start(self, coord),
        }
    }

    /// Return true if the given coordinates are neighbors. Defeault implemntation checks
    /// the neighbors iter. A board may wish to provide a more efficient implementation.
    fn is_neighbor(&self, c1: &Self::Coordinate, c2: &Self::Coordinate) -> bool {
        self.neighbors(c1.clone()).any(|n| &n == c2)
    }
}

/// Trait for [`Dimensions`] that support colinearity checks on their coordinates.
pub trait ColinearCheck: Dimensions {
    /// Returns true if the 3 coordinates are colinear.
    fn is_colinear(
        &self,
        c1: &Self::Coordinate,
        c2: &Self::Coordinate,
        c3: &Self::Coordinate,
    ) -> bool;
}

/// State type for the neighbor iterator.
pub trait NeighborIterState {
    type Dimensions: Dimensions + ?Sized;

    /// Construct an instance of this iter state given the arguments.
    fn start(dim: &Self::Dimensions, coord: <Self::Dimensions as Dimensions>::Coordinate) -> Self;

    /// Get the next item given a reference to the parent type.
    fn next(
        &mut self,
        dim: &Self::Dimensions,
    ) -> Option<<Self::Dimensions as Dimensions>::Coordinate>;
}

/// Iterator over the neighbors of a coordinate.
pub struct NeighborIter<'a, S: NeighborIterState> {
    dim: &'a S::Dimensions,
    state: S,
}

impl<'a, S: NeighborIterState> Iterator for NeighborIter<'a, S> {
    type Item = <S::Dimensions as Dimensions>::Coordinate;

    fn next(&mut self) -> Option<Self::Item> {
        self.state.next(self.dim)
    }
}