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
use std::ops::Range;

use rand::{Rng, seq::IteratorRandom, thread_rng};

use crate::{Pos, Delta, LIGHTHOUSE_COLS};

/// A rectangle on the integer grid.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct Rect {
    pub origin: Pos,
    pub size: Delta,
}

impl Rect {
    /// Creates a new rectangle.
    pub const fn new(origin: Pos, size: Delta) -> Self {
        Self { origin, size }
    }

    /// The range of x values.
    pub const fn x_range(self) -> Range<i32> {
        self.origin.x..(self.origin.x + self.size.dx)
    }

    /// The range of y values.
    pub const fn y_range(self) -> Range<i32> {
        self.origin.y..(self.origin.y + self.size.dy)
    }

    /// Checks whether the rectangle contains the given position.
    pub const fn contains(self, pos: Pos) -> bool {
        pos.x >= self.origin.x && pos.x < self.origin.x + self.width()
        && pos.y >= self.origin.y && pos.y < self.origin.y + self.height()
    }

    /// Converts a position to an index.
    pub fn index_of(self, pos: Pos) -> usize {
        debug_assert!(self.contains(pos));
        let relative = pos - self.origin;
        relative.dy as usize * LIGHTHOUSE_COLS + relative.dx as usize
    }

    /// Whether this rectangle is empty.
    pub const fn is_empty(self) -> bool {
        self.size.dx == 0 && self.size.dy == 0
    }

    /// Samples a random position within the rectangle with the given rng.
    pub fn sample_random_with(self, rng: &mut impl Rng) -> Option<Pos> {
        let x = self.x_range().choose(rng)?;
        let y = self.y_range().choose(rng)?;
        Some(Pos::new(x, y))
    }

    /// Samples a random position within the rectangle.
    pub fn sample_random(self) -> Option<Pos> {
        self.sample_random_with(&mut thread_rng())
    }

    /// The rectangle's width.
    pub const fn width(self) -> i32 {
        self.size.dx
    }

    /// The rectangle's height.
    pub const fn height(self) -> i32 {
        self.size.dy
    }

    /// The rectangle's area.
    pub const fn area(self) -> i32 {
        self.width() * self.height()
    }

    /// Wraps a value to the rectangle's bounds.
    pub const fn wrap(self, pos: Pos) -> Pos {
        Pos::new(
            pos.x.rem_euclid(self.width()),
            pos.y.rem_euclid(self.height()),
        )
    }
}