1use rand::Rng;
2use std::fmt::{Debug, Display, Result};
3
4pub type Vector = (u32, u32);
5
6#[derive(PartialEq)]
7pub struct Rectangle {
8 pos: Vector,
9 size: Vector,
10}
11
12impl Debug for Rectangle {
13 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result {
14 write!(
15 f,
16 "Rectangle[{}, {}] x [{}, {}]",
17 self.pos.0, self.pos.1, self.size.0, self.size.1
18 )
19 }
20}
21
22impl Display for Rectangle {
23 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result {
24 Debug::fmt(&self, f)
25 }
26}
27
28impl Clone for Rectangle {
29 fn clone(&self) -> Self {
30 Self {
31 pos: self.pos,
32 size: self.size,
33 }
34 }
35}
36
37impl Rectangle {
38 pub fn new(x: u32, y: u32, width: u32, height: u32) -> Rectangle {
39 Rectangle {
40 pos: (x, y),
41 size: (width, height),
42 }
43 }
44
45 pub fn random(minmax_width: Vector, minmax_height: Vector) -> Option<Rectangle> {
46 if (minmax_width.0 >= minmax_width.1) || (minmax_height.0 >= minmax_height.1) {
47 return None;
48 }
49
50 let mut generator = rand::thread_rng();
51
52 Some(Rectangle {
53 pos: (0, 0),
54 size: (
55 generator.gen_range(minmax_width.0..=minmax_width.1),
56 generator.gen_range(minmax_height.0..=minmax_height.1),
57 ),
58 })
59 }
60
61 pub fn from_points(x1: u32, y1: u32, x2: u32, y2: u32) -> Option<Rectangle> {
62 if (x2 <= x1) || (y2 <= y1) {
63 return None;
64 }
65
66 Some(Rectangle {
67 pos: (x1, y1),
68 size: (x2 - x1, y2 - y1),
69 })
70 }
71
72 pub fn from_center(cx: u32, cy: u32, width: u32, height: u32) -> Option<Rectangle> {
73 let half_size = (width / 2, height / 2);
74
75 if ((cx as isize - half_size.0 as isize) < 0) || ((cy as isize - half_size.1 as isize) < 0)
76 {
77 return None;
78 }
79
80 let pos = (cx - half_size.0, cy - half_size.1);
81
82 Some(Rectangle {
83 pos,
84 size: (width, height),
85 })
86 }
87
88 pub fn pos(&self) -> Vector {
89 self.pos
90 }
91
92 pub fn size(&self) -> Vector {
93 self.size
94 }
95
96 pub fn top_left(&self) -> Vector {
97 self.pos
98 }
99
100 pub fn bottom_right(&self) -> Vector {
101 (self.pos.0 + self.size.0, self.pos.1 + self.size.1)
102 }
103
104 pub fn x1(&self) -> u32 {
105 self.pos.0
106 }
107
108 pub fn y1(&self) -> u32 {
109 self.pos.1
110 }
111
112 pub fn x2(&self) -> u32 {
113 self.bottom_right().0
114 }
115
116 pub fn y2(&self) -> u32 {
117 self.bottom_right().1
118 }
119
120 pub fn center(&self) -> Vector {
121 (
122 self.pos.0 + (self.size.0 / 2),
123 self.pos.1 + (self.size.1 / 2),
124 )
125 }
126
127 pub fn contains(&self, other: &Rectangle) -> bool {
128 (self.x1() <= other.x1())
129 && (self.x2() >= other.x1())
130 && (self.x1() <= other.x2())
131 && (self.x2() >= other.x2())
132 && (self.y1() <= other.y1())
133 && (self.y2() >= other.y1())
134 && (self.y1() <= other.y2())
135 && (self.y2() >= other.y2())
136 }
137
138 pub fn collides(&self, other: &Rectangle) -> bool {
139 (self.x1() <= other.x2())
140 && (other.x1() <= self.x2())
141 && (self.y1() <= other.y2())
142 && (other.y1() <= self.y2())
143 }
144
145 pub fn collides_with_point(&self, other: Vector) -> bool {
146 (self.x1() <= other.0)
147 && (other.0 <= self.x2())
148 && (self.y1() <= other.1)
149 && (other.1 <= self.y2())
150 }
151
152 pub fn grow(&self, size: isize) -> Option<Rectangle> {
153 let size = (self.size.0 as isize + size, self.size.1 as isize + size);
154
155 if (size.0 <= 0) || (size.1 <= 0) {
156 return None;
157 }
158
159 Some(Rectangle {
160 pos: self.pos,
161 size: (size.0 as u32, size.1 as u32),
162 })
163 }
164
165 pub fn displace(&self, x_offset: isize, y_offset: isize) -> Option<Rectangle> {
166 let pos = (
167 self.pos.0 as isize + x_offset,
168 self.pos.1 as isize + y_offset,
169 );
170
171 if (pos.0 < 0) || (pos.1 < 0) {
172 return None;
173 }
174
175 Some(Rectangle {
176 pos: (pos.0 as u32, pos.1 as u32),
177 size: (self.size.0, self.size.1),
178 })
179 }
180}
181
182#[cfg(test)]
183mod test {
184 use super::*;
185
186 #[test]
187 fn new() {
188 let rect = Rectangle::new(10, 20, 30, 40);
189
190 assert_eq!((10, 20), rect.pos());
191 assert_eq!((30, 40), rect.size());
192 assert_eq!((10, 20), rect.top_left());
193 assert_eq!((40, 60), rect.bottom_right());
194 assert_eq!((25, 40), rect.center());
195
196 assert_eq!(10, rect.x1());
197 assert_eq!(20, rect.y1());
198 assert_eq!(40, rect.x2());
199 assert_eq!(60, rect.y2());
200 }
201
202 #[test]
203 fn random() {
204 let r = Rectangle::random((10, 20), (2, 5)).unwrap();
205
206 assert_eq!(0, r.pos().0);
207 assert_eq!(0, r.pos().1);
208 assert!((r.size().0 >= 10) && (r.size().0 <= 20));
209 assert!((r.size().1 >= 2) && (r.size().1 <= 5));
210 }
211
212 #[test]
213 fn from_points() {
214 let rect = Rectangle::from_points(10, 20, 30, 40).unwrap();
215
216 assert_eq!((10, 20), rect.pos());
217 assert_eq!((20, 20), rect.size());
218 assert_eq!((10, 20), rect.top_left());
219 assert_eq!((30, 40), rect.bottom_right());
220 assert_eq!((20, 30), rect.center());
221
222 assert_eq!(10, rect.x1());
223 assert_eq!(20, rect.y1());
224 assert_eq!(30, rect.x2());
225 assert_eq!(40, rect.y2());
226 }
227
228 #[test]
229 fn from_center() {
230 let rect = Rectangle::from_center(5, 5, 10, 10).unwrap();
231
232 assert_eq!(0, rect.x1());
233 assert_eq!(0, rect.y1());
234 assert_eq!(10, rect.size().0);
235 assert_eq!(10, rect.size().1);
236
237 let rect = Rectangle::from_center(0, 0, 5, 5);
238 assert_eq!(None, rect);
239 }
240
241 #[test]
242 fn from_point_with_negative_size() {
243 let r = Rectangle::from_points(10, 10, 0, 0);
244 assert_eq!(None, r);
245 }
246
247 #[test]
248 fn from_point_with_empty_rectangle() {
249 let r = Rectangle::from_points(10, 10, 10, 10);
250 assert_eq!(None, r);
251 }
252
253 #[test]
254 fn collides_with_no_collision() {
255 let r1 = Rectangle::from_points(0, 0, 10, 12).unwrap();
256 let r2 = Rectangle::from_points(25, 25, 30, 35).unwrap();
257
258 assert!(!r1.collides(&r2));
259 assert!(!r2.collides(&r1));
260
261 let r1 = r1.grow(5).unwrap();
262
263 assert!(!r1.collides(&r2));
264 assert!(!r2.collides(&r1));
265 }
266
267 #[test]
268 fn collides_with_collisions() {
269 let r1 = Rectangle::from_points(0, 0, 10, 10).unwrap();
270 let r2 = Rectangle::from_points(7, 9, 30, 35).unwrap();
271
272 assert!(r1.collides(&r2));
273 assert!(r2.collides(&r1));
274
275 let r1 = r1.grow(5).unwrap();
276
277 assert!(r1.collides(&r2));
278 assert!(r2.collides(&r1));
279 }
280
281 #[test]
282 fn collides_with_touching() {
283 let r1 = Rectangle::from_points(0, 0, 10, 10).unwrap();
284 let r2 = Rectangle::from_points(10, 10, 20, 20).unwrap();
285
286 assert!(r1.collides(&r2));
287 assert!(r2.collides(&r1));
288 }
289
290 #[test]
291 fn grow() {
292 let r = Rectangle::new(10, 15, 20, 10);
293
294 let new_rect = r.grow(5).unwrap();
295
296 assert_eq!(25, new_rect.size().0);
297 assert_eq!(15, new_rect.size().1);
298
299 let new_rect = new_rect.grow(-10).unwrap();
300
301 assert_eq!(15, new_rect.size().0);
302 assert_eq!(5, new_rect.size().1);
303 }
304
305 #[test]
306 fn grow_negative() {
307 let r = Rectangle::new(10, 15, 20, 10);
308
309 let new_rect = r.grow(-10);
310
311 assert_eq!(None, new_rect);
312 }
313
314 #[test]
315 fn displace() {
316 let r = Rectangle::new(10, 15, 20, 10);
317
318 let new_rect = r.displace(5, 15).unwrap();
319
320 assert_eq!(15, new_rect.pos().0);
321 assert_eq!(30, new_rect.pos().1);
322 }
323
324 #[test]
325 fn displace_outside() {
326 let r = Rectangle::new(10, 15, 20, 10);
327
328 let new_rect = r.displace(-15, -15);
329
330 assert_eq!(None, new_rect);
331 }
332}