use rand::Rng;
use std::fmt::{Debug, Display, Result};
pub type Vector = (u32, u32);
#[derive(PartialEq)]
pub struct Rectangle {
pos: Vector,
size: Vector,
}
impl Debug for Rectangle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result {
write!(
f,
"Rectangle[{}, {}] x [{}, {}]",
self.pos.0, self.pos.1, self.size.0, self.size.1
)
}
}
impl Display for Rectangle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result {
Debug::fmt(&self, f)
}
}
impl Clone for Rectangle {
fn clone(&self) -> Self {
Self {
pos: self.pos,
size: self.size,
}
}
}
impl Rectangle {
pub fn new(x: u32, y: u32, width: u32, height: u32) -> Rectangle {
Rectangle {
pos: (x, y),
size: (width, height),
}
}
pub fn random(minmax_width: Vector, minmax_height: Vector) -> Option<Rectangle> {
if (minmax_width.0 >= minmax_width.1) || (minmax_height.0 >= minmax_height.1) {
return None;
}
let mut generator = rand::thread_rng();
Some(Rectangle {
pos: (0, 0),
size: (
generator.gen_range(minmax_width.0..=minmax_width.1),
generator.gen_range(minmax_height.0..=minmax_height.1),
),
})
}
pub fn from_points(x1: u32, y1: u32, x2: u32, y2: u32) -> Option<Rectangle> {
if (x2 <= x1) || (y2 <= y1) {
return None;
}
Some(Rectangle {
pos: (x1, y1),
size: (x2 - x1, y2 - y1),
})
}
pub fn from_center(cx: u32, cy: u32, width: u32, height: u32) -> Option<Rectangle> {
let half_size = (width / 2, height / 2);
if ((cx as isize - half_size.0 as isize) < 0) || ((cy as isize - half_size.1 as isize) < 0)
{
return None;
}
let pos = (cx - half_size.0, cy - half_size.1);
Some(Rectangle {
pos,
size: (width, height),
})
}
pub fn pos(&self) -> Vector {
self.pos
}
pub fn size(&self) -> Vector {
self.size
}
pub fn top_left(&self) -> Vector {
self.pos
}
pub fn bottom_right(&self) -> Vector {
(self.pos.0 + self.size.0, self.pos.1 + self.size.1)
}
pub fn x1(&self) -> u32 {
self.pos.0
}
pub fn y1(&self) -> u32 {
self.pos.1
}
pub fn x2(&self) -> u32 {
self.bottom_right().0
}
pub fn y2(&self) -> u32 {
self.bottom_right().1
}
pub fn center(&self) -> Vector {
(
self.pos.0 + (self.size.0 / 2),
self.pos.1 + (self.size.1 / 2),
)
}
pub fn contains(&self, other: &Rectangle) -> bool {
(self.x1() <= other.x1())
&& (self.x2() >= other.x1())
&& (self.x1() <= other.x2())
&& (self.x2() >= other.x2())
&& (self.y1() <= other.y1())
&& (self.y2() >= other.y1())
&& (self.y1() <= other.y2())
&& (self.y2() >= other.y2())
}
pub fn collides(&self, other: &Rectangle) -> bool {
(self.x1() <= other.x2())
&& (other.x1() <= self.x2())
&& (self.y1() <= other.y2())
&& (other.y1() <= self.y2())
}
pub fn collides_with_point(&self, other: Vector) -> bool {
(self.x1() <= other.0)
&& (other.0 <= self.x2())
&& (self.y1() <= other.1)
&& (other.1 <= self.y2())
}
pub fn grow(&self, size: isize) -> Option<Rectangle> {
let size = (self.size.0 as isize + size, self.size.1 as isize + size);
if (size.0 <= 0) || (size.1 <= 0) {
return None;
}
Some(Rectangle {
pos: self.pos,
size: (size.0 as u32, size.1 as u32),
})
}
pub fn displace(&self, x_offset: isize, y_offset: isize) -> Option<Rectangle> {
let pos = (
self.pos.0 as isize + x_offset,
self.pos.1 as isize + y_offset,
);
if (pos.0 < 0) || (pos.1 < 0) {
return None;
}
Some(Rectangle {
pos: (pos.0 as u32, pos.1 as u32),
size: (self.size.0, self.size.1),
})
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn new() {
let rect = Rectangle::new(10, 20, 30, 40);
assert_eq!((10, 20), rect.pos());
assert_eq!((30, 40), rect.size());
assert_eq!((10, 20), rect.top_left());
assert_eq!((40, 60), rect.bottom_right());
assert_eq!((25, 40), rect.center());
assert_eq!(10, rect.x1());
assert_eq!(20, rect.y1());
assert_eq!(40, rect.x2());
assert_eq!(60, rect.y2());
}
#[test]
fn random() {
let r = Rectangle::random((10, 20), (2, 5)).unwrap();
assert_eq!(0, r.pos().0);
assert_eq!(0, r.pos().1);
assert!((r.size().0 >= 10) && (r.size().0 <= 20));
assert!((r.size().1 >= 2) && (r.size().1 <= 5));
}
#[test]
fn from_points() {
let rect = Rectangle::from_points(10, 20, 30, 40).unwrap();
assert_eq!((10, 20), rect.pos());
assert_eq!((20, 20), rect.size());
assert_eq!((10, 20), rect.top_left());
assert_eq!((30, 40), rect.bottom_right());
assert_eq!((20, 30), rect.center());
assert_eq!(10, rect.x1());
assert_eq!(20, rect.y1());
assert_eq!(30, rect.x2());
assert_eq!(40, rect.y2());
}
#[test]
fn from_center() {
let rect = Rectangle::from_center(5, 5, 10, 10).unwrap();
assert_eq!(0, rect.x1());
assert_eq!(0, rect.y1());
assert_eq!(10, rect.size().0);
assert_eq!(10, rect.size().1);
let rect = Rectangle::from_center(0, 0, 5, 5);
assert_eq!(None, rect);
}
#[test]
fn from_point_with_negative_size() {
let r = Rectangle::from_points(10, 10, 0, 0);
assert_eq!(None, r);
}
#[test]
fn from_point_with_empty_rectangle() {
let r = Rectangle::from_points(10, 10, 10, 10);
assert_eq!(None, r);
}
#[test]
fn collides_with_no_collision() {
let r1 = Rectangle::from_points(0, 0, 10, 12).unwrap();
let r2 = Rectangle::from_points(25, 25, 30, 35).unwrap();
assert!(!r1.collides(&r2));
assert!(!r2.collides(&r1));
let r1 = r1.grow(5).unwrap();
assert!(!r1.collides(&r2));
assert!(!r2.collides(&r1));
}
#[test]
fn collides_with_collisions() {
let r1 = Rectangle::from_points(0, 0, 10, 10).unwrap();
let r2 = Rectangle::from_points(7, 9, 30, 35).unwrap();
assert!(r1.collides(&r2));
assert!(r2.collides(&r1));
let r1 = r1.grow(5).unwrap();
assert!(r1.collides(&r2));
assert!(r2.collides(&r1));
}
#[test]
fn collides_with_touching() {
let r1 = Rectangle::from_points(0, 0, 10, 10).unwrap();
let r2 = Rectangle::from_points(10, 10, 20, 20).unwrap();
assert!(r1.collides(&r2));
assert!(r2.collides(&r1));
}
#[test]
fn grow() {
let r = Rectangle::new(10, 15, 20, 10);
let new_rect = r.grow(5).unwrap();
assert_eq!(25, new_rect.size().0);
assert_eq!(15, new_rect.size().1);
let new_rect = new_rect.grow(-10).unwrap();
assert_eq!(15, new_rect.size().0);
assert_eq!(5, new_rect.size().1);
}
#[test]
fn grow_negative() {
let r = Rectangle::new(10, 15, 20, 10);
let new_rect = r.grow(-10);
assert_eq!(None, new_rect);
}
#[test]
fn displace() {
let r = Rectangle::new(10, 15, 20, 10);
let new_rect = r.displace(5, 15).unwrap();
assert_eq!(15, new_rect.pos().0);
assert_eq!(30, new_rect.pos().1);
}
#[test]
fn displace_outside() {
let r = Rectangle::new(10, 15, 20, 10);
let new_rect = r.displace(-15, -15);
assert_eq!(None, new_rect);
}
}