// External includes.
use rand::distributions::Distribution;
use rand::{thread_rng, Rng};
// Standard includes.
// Internal includes.
use super::{Coord, IsPosition, Position, ProvidesPosition};
/// Provides a range of [`Position`](struct.Position.html)s, from a start position to an end position.
///
/// Both of these methods provide a random position between the start position in the range, and the end position in the range. The x- and y-components of the returned `Position` are bounded together, such that the returned random position is somewhere along a tiled line from the start to the end position.
/// ```
/// # use dungen_minion_geometry::*;
/// use std::sync::Arc;
/// use rayon::prelude::*;
///
/// use rand::{thread_rng, Rng};
/// // The end position is an inclusive bound.
/// // The divergent min and max for x and y guarantee that the samples are separate.
/// let position_range = Arc::new(PositionRange::new(Position::new(4, 14), Position::new(13, 23)));
/// // Random generators are hard to guarantee. But this should be viable.
/// [0..5_000].par_iter().for_each(|_i| {
/// let rand_position = position_range.provide_position();
/// assert!(rand_position.x() >= 4 && rand_position.x() <= 13);
/// assert!(rand_position.y() >= 14 && rand_position.y() <= 23);
/// });
///
/// [0..5_000].par_iter().for_each(|_i| {
/// let rand_position = thread_rng().sample(*position_range);
/// assert!(rand_position.x() >= 4 && rand_position.x() <= 13);
/// assert!(rand_position.y() >= 14 && rand_position.y() <= 23);
/// });
/// ```
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
pub struct PositionRange {
start: Position,
end: Position,
}
impl PositionRange {
/// Creates a new `PositionRange` from a start `Position` and an end `Position`.
#[must_use]
pub fn new(start: Position, end: Position) -> Self {
Self { start, end }
}
}
impl Distribution<Position> for PositionRange {
fn sample<R>(&self, rng: &mut R) -> Position
where
R: Rng + ?Sized,
{
// rng.gen<f64>() generates the range [0.0, 1.0), and thus cannot generate 1.0.
// This is sub-optimal for this use case.
#[allow(clippy::cast_precision_loss)]
let i = rng.gen::<u64>() as f64 / 18_446_744_073_709_551_615.0_f64;
#[allow(clippy::cast_possible_truncation)]
Position::new(
(f64::from(self.start.x()) * (1.0 - i)).round() as Coord
+ (f64::from(self.end.x()) * i).round() as Coord,
(f64::from(self.start.y()) * (1.0 - i)).round() as Coord
+ (f64::from(self.end.y()) * i).round() as Coord,
)
}
}
impl From<Position> for PositionRange {
fn from(position: Position) -> Self {
Self::new(position, position)
}
}
impl ProvidesPosition for PositionRange {
fn provide_position(&self) -> Position {
self.sample(&mut thread_rng())
}
}