1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub struct Bounds {
6 pub left: i32,
7 pub top: i32,
8 pub right: i32,
9 pub bottom: i32,
10}
11
12impl Bounds {
13 pub fn new(left: i32, top: i32, right: i32, bottom: i32) -> Self {
14 Self {
15 left,
16 top,
17 right,
18 bottom,
19 }
20 }
21
22 pub fn from_str(s: &str) -> Option<Self> {
24 let parts: Vec<i32> = s.split(',').filter_map(|p| p.trim().parse().ok()).collect();
25 if parts.len() == 4 {
26 Some(Self::new(parts[0], parts[1], parts[2], parts[3]))
27 } else {
28 None
29 }
30 }
31
32 pub fn center(&self) -> (i32, i32) {
34 ((self.left + self.right) / 2, (self.top + self.bottom) / 2)
35 }
36
37 pub fn width(&self) -> i32 {
39 self.right - self.left
40 }
41
42 pub fn height(&self) -> i32 {
44 self.bottom - self.top
45 }
46
47 pub fn area(&self) -> i32 {
49 self.width() * self.height()
50 }
51
52 pub fn overlaps(&self, other: &Bounds) -> bool {
54 !(self.right <= other.left
55 || other.right <= self.left
56 || self.bottom <= other.top
57 || other.bottom <= self.top)
58 }
59
60 pub fn contains_point(&self, x: i32, y: i32) -> bool {
62 x >= self.left && x < self.right && y >= self.top && y < self.bottom
63 }
64
65 pub fn to_string(&self) -> String {
67 format!("{},{},{},{}", self.left, self.top, self.right, self.bottom)
68 }
69}
70
71pub fn find_clear_point(bounds: &Bounds, blockers: &[Bounds]) -> Option<(i32, i32)> {
75 find_clear_point_recursive(bounds, blockers, 0)
76}
77
78fn find_clear_point_recursive(bounds: &Bounds, blockers: &[Bounds], depth: u32) -> Option<(i32, i32)> {
79 let (cx, cy) = bounds.center();
80
81 let blocked = blockers.iter().any(|b| b.contains_point(cx, cy));
83
84 if !blocked {
85 return Some((cx, cy));
86 }
87
88 if depth > 4 || bounds.area() < 100 {
90 return None;
91 }
92
93 let quadrants = [
95 Bounds::new(bounds.left, bounds.top, cx, cy),
96 Bounds::new(cx, bounds.top, bounds.right, cy),
97 Bounds::new(bounds.left, cy, cx, bounds.bottom),
98 Bounds::new(cx, cy, bounds.right, bounds.bottom),
99 ];
100
101 let mut best_point = None;
102 let mut best_area = 0;
103
104 for q in &quadrants {
105 let area = q.area();
106 if area <= 0 {
107 continue;
108 }
109 if let Some(point) = find_clear_point_recursive(q, blockers, depth + 1) {
110 if area > best_area {
111 best_point = Some(point);
112 best_area = area;
113 }
114 }
115 }
116
117 best_point
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123
124 #[test]
125 fn test_bounds_center() {
126 let b = Bounds::new(0, 0, 100, 200);
127 assert_eq!(b.center(), (50, 100));
128 }
129
130 #[test]
131 fn test_bounds_from_str() {
132 let b = Bounds::from_str("10,20,30,40").unwrap();
133 assert_eq!(b, Bounds::new(10, 20, 30, 40));
134 }
135
136 #[test]
137 fn test_bounds_from_str_invalid() {
138 assert!(Bounds::from_str("10,20").is_none());
139 assert!(Bounds::from_str("abc").is_none());
140 }
141
142 #[test]
143 fn test_overlaps() {
144 let a = Bounds::new(0, 0, 100, 100);
145 let b = Bounds::new(50, 50, 150, 150);
146 assert!(a.overlaps(&b));
147 assert!(b.overlaps(&a));
148 }
149
150 #[test]
151 fn test_no_overlap() {
152 let a = Bounds::new(0, 0, 100, 100);
153 let b = Bounds::new(100, 100, 200, 200);
154 assert!(!a.overlaps(&b));
155 }
156
157 #[test]
158 fn test_contains_point() {
159 let b = Bounds::new(0, 0, 100, 100);
160 assert!(b.contains_point(50, 50));
161 assert!(!b.contains_point(100, 100));
162 assert!(!b.contains_point(-1, 50));
163 }
164
165 #[test]
166 fn test_find_clear_point_no_blockers() {
167 let bounds = Bounds::new(0, 0, 200, 200);
168 let point = find_clear_point(&bounds, &[]).unwrap();
169 assert_eq!(point, (100, 100));
170 }
171
172 #[test]
173 fn test_find_clear_point_blocked_center() {
174 let bounds = Bounds::new(0, 0, 200, 200);
175 let blocker = Bounds::new(90, 90, 110, 110); let point = find_clear_point(&bounds, &[blocker]).unwrap();
177 assert!(!blocker.contains_point(point.0, point.1));
179 }
180
181 #[test]
182 fn test_find_clear_point_fully_blocked() {
183 let bounds = Bounds::new(0, 0, 10, 10);
184 let blocker = Bounds::new(0, 0, 10, 10); let point = find_clear_point(&bounds, &[blocker]);
186 assert!(point.is_none());
187 }
188
189 #[test]
190 fn test_bounds_dimensions() {
191 let b = Bounds::new(10, 20, 110, 220);
192 assert_eq!(b.width(), 100);
193 assert_eq!(b.height(), 200);
194 assert_eq!(b.area(), 20000);
195 }
196}