voronoi-go 1.0.1

Core rules and engine for Voronoi Go.
Documentation
//! Stones and their identity.

use core::fmt;

use crate::{Color, Point};

/// Identifies a stone within one game.
///
/// Ids are dense and never reused: at most one stone is placed per turn, so the
/// next id is the turn number.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct StoneId(pub u32);

impl StoneId {
    /// The id numbered `value`.
    #[must_use]
    pub const fn new(value: u32) -> Self {
        Self(value)
    }

    /// The underlying number.
    #[must_use]
    pub const fn get(self) -> u32 {
        self.0
    }
}

impl fmt::Display for StoneId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

/// A stone on the board: a disc of radius
/// [`STONE_RADIUS`](crate::STONE_RADIUS) centred on `position`.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Stone {
    /// Which stone this is.
    pub id: StoneId,
    /// Whose stone it is.
    pub color: Color,
    /// Where its centre sits.
    #[cfg_attr(feature = "serde", serde(rename = "pos"))]
    pub position: Point,
}

impl Stone {
    /// A stone of `color` centred at `position`.
    #[must_use]
    pub const fn new(id: StoneId, color: Color, position: Point) -> Self {
        Self {
            id,
            color,
            position,
        }
    }
}

#[cfg(test)]
mod tests {
    #![allow(clippy::unwrap_used, clippy::expect_used)]

    use super::{Stone, StoneId};
    use crate::{Color, Point};

    #[test]
    fn ids_order_and_display_as_numbers() {
        assert!(StoneId::new(2) < StoneId::new(10));
        assert_eq!(StoneId::new(7).get(), 7);
        assert_eq!(StoneId::new(7).to_string(), "7");
    }

    #[test]
    fn stone_equality_is_exact_in_its_position() {
        let a = Stone::new(StoneId::new(0), Color::Black, Point::new(4.0, 4.0));
        let b = Stone::new(StoneId::new(0), Color::Black, Point::new(4.0, 4.0));
        // One ulp away at this magnitude — the smallest difference there is.
        let nudged = f64::from_bits(4.0_f64.to_bits() + 1);
        let c = Stone::new(StoneId::new(0), Color::Black, Point::new(4.0, nudged));
        assert_eq!(a, b);
        assert_ne!(a, c);
    }
}