use core::fmt::{self, Display, Formatter};
use core::num::NonZero;
use core::ops::Add;
use core::str::FromStr;
use crate::CoordinateParseError;
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct Coordinate {
x: usize,
y: usize,
}
const NEIGHBOR_OFFSETS: [(isize, isize); 8] = [
(-1, -1),
(0, -1),
(1, -1),
(-1, 0),
(1, 0),
(-1, 1),
(0, 1),
(1, 1),
];
const SUPPORTED_SEPARATORS: [char; 3] = ['x', ',', ' '];
impl Coordinate {
#[must_use]
pub const fn new(x: usize, y: usize) -> Self {
Self { x, y }
}
#[must_use]
pub fn from_width_and_index(width: NonZero<usize>, index: usize) -> Self {
let x = index % width;
Self::new(x, (index - x) / width)
}
#[must_use]
pub const fn x(&self) -> usize {
self.x
}
#[must_use]
pub const fn y(&self) -> usize {
self.y
}
#[must_use]
pub fn as_index(&self, width: NonZero<usize>) -> Option<usize> {
if self.x >= width.get() {
return None;
}
self.y
.checked_mul(width.into())
.and_then(|row| row.checked_add(self.x))
}
pub fn neighbors(&self) -> impl Iterator<Item = Self> + '_ {
NEIGHBOR_OFFSETS
.iter()
.filter_map(move |offset| self + offset)
}
}
impl Add<&(isize, isize)> for &Coordinate {
type Output = Option<Coordinate>;
fn add(self, (dx, dy): &(isize, isize)) -> Self::Output {
self.x.checked_add_signed(*dx).and_then(|x| {
self.y
.checked_add_signed(*dy)
.map(|y| Coordinate::new(x, y))
})
}
}
impl Display for Coordinate {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}x{}", self.x, self.y)
}
}
impl From<&Self> for Coordinate {
fn from(coordinate: &Self) -> Self {
*coordinate
}
}
impl From<(usize, usize)> for Coordinate {
fn from((x, y): (usize, usize)) -> Self {
Self::new(x, y)
}
}
impl From<&(usize, usize)> for Coordinate {
fn from((x, y): &(usize, usize)) -> Self {
Self::new(*x, *y)
}
}
impl From<[usize; 2]> for Coordinate {
fn from([x, y]: [usize; 2]) -> Self {
Self::new(x, y)
}
}
impl From<&[usize; 2]> for Coordinate {
fn from([x, y]: &[usize; 2]) -> Self {
Self::new(*x, *y)
}
}
impl From<Coordinate> for (usize, usize) {
fn from(coordinate: Coordinate) -> Self {
(coordinate.x, coordinate.y)
}
}
impl From<&Coordinate> for (usize, usize) {
fn from(coordinate: &Coordinate) -> Self {
(coordinate.x, coordinate.y)
}
}
impl From<Coordinate> for [usize; 2] {
fn from(coordinate: Coordinate) -> Self {
[coordinate.x, coordinate.y]
}
}
impl From<&Coordinate> for [usize; 2] {
fn from(coordinate: &Coordinate) -> Self {
[coordinate.x, coordinate.y]
}
}
impl FromStr for Coordinate {
type Err = CoordinateParseError;
fn from_str(string: &str) -> Result<Self, Self::Err> {
match SUPPORTED_SEPARATORS
.into_iter()
.find_map(|char| string.split_once(char))
{
Some((x, y)) => Self::try_from((x.trim(), y.trim())),
None => Err(CoordinateParseError::NotTwoNumbers),
}
}
}
impl TryFrom<(&str, &str)> for Coordinate {
type Error = CoordinateParseError;
fn try_from((x, y): (&str, &str)) -> Result<Self, Self::Error> {
x.parse::<usize>()
.map_err(CoordinateParseError::InvalidXValue)
.and_then(|x| {
y.parse::<usize>()
.map_err(CoordinateParseError::InvalidYValue)
.map(|y| Self::new(x, y))
})
}
}