use std::fmt;
use std::fmt::Display;
use std::num::TryFromIntError;
use crate::utilities::{index_x, index_y, saturating_index_1d};
use crate::{Direction, DirectionsSet, DirectionsSetIter, Size2D, Tessellation, TileShape};
#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Point2D {
pub x: usize,
pub y: usize,
}
impl Point2D {
pub fn new(x: usize, y: usize) -> Self { Self { x, y } }
pub fn new_from_idx(idx: usize, size: &Size2D) -> Self {
Self {
x: index_x(idx, size.width),
y: index_y(idx, size.width),
}
}
pub fn origin() -> Self { Self { x: 0, y: 0 } }
pub fn is_on_board(&self, size: Size2D) -> bool { self.x < size.width && self.y < size.height }
pub fn idx(&self, size: Size2D) -> usize {
saturating_index_1d(self.x, self.y, size.width)
}
pub fn neighbor(
&self, tessellation: Tessellation, to: Direction, size: Size2D,
) -> Option<Self> {
tessellation
.neighbor(self.x, self.y, to, size.width, size.height)
.map(|(new_x, new_y)| Self { x: new_x, y: new_y })
}
pub fn neighbors(&self, tessellation: Tessellation, size: Size2D) -> Option<IterNeighbors> {
IterNeighbors::new(tessellation, *self, size)
}
pub fn neighbor_directions(
&self, tessellation: Tessellation, size: Size2D,
) -> Option<DirectionsSet> {
tessellation.neighbor_directions(self.x, self.y, size.width, size.height)
}
pub fn tile_shape(&self, tessellation: Tessellation) -> TileShape {
tessellation.tile_shape_from_pos(self.x, self.y)
}
}
impl Display for Point2D {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}
macro_rules! impl_point2d_from_2D {
(usize) => {
impl From<(usize, usize)> for Point2D {
fn from(value: (usize, usize)) -> Self {
Self {
x: value.0,
y: value.1,
}
}
}
impl From<[usize; 2]> for Point2D {
fn from(value: [usize; 2]) -> Self { Self::from((value[0], value[1])) }
}
};
(u8) => {
impl_point2d_from_2D!(@smaller u8);
};
(u16) => {
impl_point2d_from_2D!(@smaller u16);
};
(@smaller $t:ty) => {
impl From<($t, $t)> for Point2D {
fn from(value: ($t, $t)) -> Self {
Self {
x: value.0.into(),
y: value.1.into(),
}
}
}
impl From<[$t; 2]> for Point2D {
fn from(value: [$t; 2]) -> Self { Self::from((value[0], value[1])) }
}
};
(u32) => {
impl_point2d_from_2D!(@others u32);
};
(u64) => {
impl_point2d_from_2D!(@others u64);
};
(isize) => {
impl_point2d_from_2D!(@others isize);
};
(i64) => {
impl_point2d_from_2D!(@others i64);
};
(i32) => {
impl_point2d_from_2D!(@others i32);
};
(i16) => {
impl_point2d_from_2D!(@others i16);
};
(i8) => {
impl_point2d_from_2D!(@others i8);
};
(@others $t:ty) => {
impl TryFrom<($t, $t)> for Point2D {
type Error = TryFromIntError;
fn try_from(value: ($t, $t)) -> Result<Self, Self::Error> {
Ok(Point2D {
x: value.0.try_into()?,
y: value.1.try_into()?,
})
}
}
impl TryFrom<[$t; 2]> for Point2D {
type Error = TryFromIntError;
fn try_from(value: [$t; 2]) -> Result<Self, Self::Error> {
Self::try_from((value[0], value[1]))
}
}
};
}
impl_point2d_from_2D!(usize);
impl_point2d_from_2D!(u64);
impl_point2d_from_2D!(u32);
impl_point2d_from_2D!(u16);
impl_point2d_from_2D!(u8);
impl_point2d_from_2D!(isize);
impl_point2d_from_2D!(i64);
impl_point2d_from_2D!(i32);
impl_point2d_from_2D!(i16);
impl_point2d_from_2D!(i8);
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct IterNeighbors {
tessellation: Tessellation,
src: Point2D,
size: Size2D,
only_neighbor_directions: DirectionsSetIter,
}
impl IterNeighbors {
pub(self) fn new(tessellation: Tessellation, at: Point2D, size: Size2D) -> Option<Self> {
tessellation
.neighbor_directions(at.x, at.y, size.width, size.height)
.map(|ndd| {
Self {
tessellation,
src: at,
size,
only_neighbor_directions: ndd.iter(),
}
})
}
}
impl Iterator for IterNeighbors {
type Item = (Direction, Point2D);
fn next(&mut self) -> Option<(Direction, Point2D)> {
self.only_neighbor_directions.next().map(|d| {
self.src
.neighbor(self.tessellation, d, self.size)
.map(|p| (d, p))
})?
}
}
#[cfg(test)]
mod tests {
use super::*;
const DATA: [(usize, usize, usize); 45] = [
(0, 0, 0),
(1, 0, 1),
(2, 0, 2),
(3, 0, 3),
(4, 0, 4),
(0, 1, 5),
(1, 1, 6),
(2, 1, 7),
(3, 1, 8),
(4, 1, 9),
(0, 2, 10),
(1, 2, 11),
(2, 2, 12),
(3, 2, 13),
(4, 2, 14),
(0, 3, 15),
(1, 3, 16),
(2, 3, 17),
(3, 3, 18),
(4, 3, 19),
(0, 4, 20),
(1, 4, 21),
(2, 4, 22),
(3, 4, 23),
(4, 4, 24),
(0, 5, 25),
(1, 5, 26),
(2, 5, 27),
(3, 5, 28),
(4, 5, 29),
(0, 6, 30),
(1, 6, 31),
(2, 6, 32),
(3, 6, 33),
(4, 6, 34),
(0, 7, 35),
(1, 7, 36),
(2, 7, 37),
(3, 7, 38),
(4, 7, 39),
(0, 8, 40),
(1, 8, 41),
(2, 8, 42),
(3, 8, 43),
(4, 8, 44),
];
#[test]
fn it_can_be_constructed_from_2d_coordinates() {
let size: Size2D = (5_usize, 9_usize).into();
for (x, y, idx) in DATA {
let pos = Point2D::new(x, y);
assert_eq!(pos.x, x);
assert_eq!(pos.y, y);
assert_eq!(pos.idx(size), idx);
}
}
#[test]
fn it_can_be_constructed_from_1d_index() {
let size: Size2D = (5_usize, 9_usize).into();
for (x, y, idx) in DATA {
let pos = Point2D::new_from_idx(idx, &size);
assert_eq!(pos.x, x);
assert_eq!(pos.y, y);
assert_eq!(pos.idx(size), idx);
}
}
}