1use image::{Rgb, RgbImage};
4use rand::{rng, seq::SliceRandom};
5use serde_json::json;
6use std::{
7 cmp::Reverse,
8 collections::{BinaryHeap, HashMap},
9};
10
11pub struct Maze {
13 pub width: usize,
14 pub height: usize,
15 pub vert_walls: Vec<Vec<bool>>,
16 pub hor_walls: Vec<Vec<bool>>,
17}
18
19impl Maze {
20 pub fn new(width: usize, height: usize) -> Self {
21 let vert_walls = vec![vec![true; width + 1]; height];
22 let hor_walls = vec![vec![true; width]; height + 1];
23 Maze { width, height, vert_walls, hor_walls }
24 }
25
26 pub fn generate(&mut self) {
27 let mut visited = vec![vec![false; self.width]; self.height];
28 let mut stack = Vec::new();
29 visited[0][0] = true;
30 stack.push((0, 0, 0));
31
32 while let Some((x, y, dir_idx)) = stack.pop() {
33 let mut dirs = vec![
34 (1isize, 0isize, 'R'),
35 (-1, 0, 'L'),
36 (0, 1, 'D'),
37 (0, -1, 'U'),
38 ];
39 dirs.shuffle(&mut rng());
40
41 for i in dir_idx..dirs.len() {
42 let (dx, dy, dir) = dirs[i];
43 let nx = x as isize + dx;
44 let ny = y as isize + dy;
45 if nx >= 0 && nx < self.width as isize && ny >= 0 && ny < self.height as isize {
46 let (nx, ny) = (nx as usize, ny as usize);
47 if !visited[ny][nx] {
48 match dir {
49 'R' => self.vert_walls[y][x + 1] = false,
50 'L' => self.vert_walls[y][x] = false,
51 'D' => self.hor_walls[y + 1][x] = false,
52 'U' => self.hor_walls[y][x] = false,
53 _ => {}
54 }
55 stack.push((x, y, i + 1));
56 visited[ny][nx] = true;
57 stack.push((nx, ny, 0));
58 break;
59 }
60 }
61 }
62 }
63 }
64
65 pub fn solve(&self) -> Vec<(usize, usize)> {
67 let total = self.width * self.height;
68 let start = 0;
69 let goal = total - 1;
70
71 let mut g_score = vec![usize::MAX; total];
72 let mut came_from = HashMap::new();
73 let mut open = BinaryHeap::new();
74
75 let h = |idx: usize| {
77 let x = (idx % self.width) as isize;
78 let y = (idx / self.width) as isize;
79 let gx = (self.width - 1) as isize;
80 let gy = (self.height - 1) as isize;
81 ((gx - x).abs() + (gy - y).abs()) as usize
82 };
83
84 g_score[start] = 0;
85 open.push((Reverse(h(start)), start));
86
87 while let Some((_, current)) = open.pop() {
88 if current == goal { break }
89 let cx = current % self.width;
90 let cy = current / self.width;
91
92 let neighbors = [
93 (cx.wrapping_sub(1), cy, cx > 0 && !self.vert_walls[cy][cx]),
94 (cx + 1, cy, cx + 1 < self.width && !self.vert_walls[cy][cx + 1]),
95 (cx, cy.wrapping_sub(1), cy > 0 && !self.hor_walls[cy][cx]),
96 (cx, cy + 1, cy + 1 < self.height && !self.hor_walls[cy + 1][cx]),
97 ];
98
99 for &(nx, ny, ok) in &neighbors {
100 if !ok || nx >= self.width || ny >= self.height { continue }
101 let neighbor = ny * self.width + nx;
102 let tentative = g_score[current] + 1;
103 if tentative < g_score[neighbor] {
104 g_score[neighbor] = tentative;
105 came_from.insert(neighbor, current);
106 open.push((Reverse(tentative + h(neighbor)), neighbor));
107 }
108 }
109 }
110
111 let mut path = Vec::new();
113 let mut cur = goal;
114 while let Some(&p) = came_from.get(&cur) {
115 path.push((cur % self.width, cur / self.width));
116 cur = p;
117 }
118 path.push((0, 0));
119 path.reverse();
120 path
121 }
122
123 pub fn draw(&self, cell_size: usize, wall_thick: usize) -> RgbImage {
125 let img_w = (self.width * cell_size + wall_thick) as u32;
126 let img_h = (self.height * cell_size + wall_thick) as u32;
127 let mut img = RgbImage::new(img_w, img_h);
128
129 let white = Rgb([255, 255, 255]);
131 let black = Rgb([0, 0, 0 ]);
132 let red = Rgb([255, 0, 0]);
133
134 for x in 0..img_w {
136 for y in 0..img_h {
137 img.put_pixel(x, y, white);
138 }
139 }
140
141 for y in 0..self.height {
143 let y0 = (y * cell_size) as u32;
144 for x in 0..=self.width {
145 if self.vert_walls[y][x] {
146 let x0 = (x * cell_size) as u32;
147 for dx in 0..wall_thick as u32 {
148 for dy in 0..cell_size as u32 {
149 img.put_pixel(x0 + dx, y0 + dy, black);
150 }
151 }
152 }
153 }
154 }
155 for y in 0..=self.height {
156 let y0 = (y * cell_size) as u32;
157 for x in 0..self.width {
158 if self.hor_walls[y][x] {
159 let x0 = (x * cell_size) as u32;
160 for dx in 0..cell_size as u32 {
161 for dy in 0..wall_thick as u32 {
162 img.put_pixel(x0 + dx, y0 + dy, black);
163 }
164 }
165 }
166 }
167 }
168
169 let thickness = (cell_size as u32) / 2;
171 for window in self.solve().windows(2) {
172 let (x1, y1) = window[0];
173 let (x2, y2) = window[1];
174 let cx1 = x1 as u32 * cell_size as u32 + cell_size as u32 / 2;
175 let cy1 = y1 as u32 * cell_size as u32 + cell_size as u32 / 2;
176 let cx2 = x2 as u32 * cell_size as u32 + cell_size as u32 / 2;
177 let cy2 = y2 as u32 * cell_size as u32 + cell_size as u32 / 2;
178
179 if cx1 == cx2 {
180 let x0 = cx1.saturating_sub(thickness / 2);
181 let h = (cy2 as i32 - cy1 as i32).abs() as u32;
182 let y_min = cy1.min(cy2);
183 for dx in 0..thickness {
184 for dy in 0..=h {
185 img.put_pixel(x0 + dx, y_min + dy, red);
186 }
187 }
188 } else {
189 let y0 = cy1.saturating_sub(thickness / 2);
190 let w = (cx2 as i32 - cx1 as i32).abs() as u32;
191 let x_min = cx1.min(cx2);
192 for dy in 0..thickness {
193 for dx in 0..=w {
194 img.put_pixel(x_min + dx, y0 + dy, red);
195 }
196 }
197 }
198 }
199
200 img
201 }
202
203 pub fn to_map_json(&self, cell_size: usize, wall_thick: usize) -> serde_json::Value {
205 let mut segments = Vec::new();
206 for x in 0..=self.width {
208 let mut y = 0;
209 while y < self.height {
210 if self.vert_walls[y][x] {
211 let y1 = y;
212 let mut y2 = y + 1;
213 while y2 < self.height && self.vert_walls[y2][x] {
214 y2 += 1;
215 }
216 segments.push(json!({"type":"vertical","x":x,"y1":y1,"y2":y2}));
217 y = y2;
218 } else {
219 y += 1;
220 }
221 }
222 }
223 for y in 0..=self.height {
225 let mut x = 0;
226 while x < self.width {
227 if self.hor_walls[y][x] {
228 let x1 = x;
229 let mut x2 = x + 1;
230 while x2 < self.width && self.hor_walls[y][x2] {
231 x2 += 1;
232 }
233 segments.push(json!({"type":"horizontal","y":y,"x1":x1,"x2":x2}));
234 x = x2;
235 } else {
236 x += 1;
237 }
238 }
239 }
240 let fw = (self.width * cell_size) as i32;
241 let fd = (self.height * cell_size) as i32;
242 let mut sizes = vec![fw, 1, fd];
243 let mut objects = vec![json!({ "p":[fw/2, -1, fd/2], "si":0 })];
244
245 for (i, seg) in segments.iter().enumerate() {
246 let si = i + 1;
247 if seg["type"] == "vertical" {
248 let x = seg["x"].as_i64().unwrap() as i32 * cell_size as i32;
249 let y1 = seg["y1"].as_i64().unwrap() as i32 * cell_size as i32;
250 let y2 = seg["y2"].as_i64().unwrap() as i32 * cell_size as i32;
251 let length = y2 - y1;
252 sizes.extend([wall_thick as i32, 20, length]);
253 objects.push(json!({"p":[x,0,(y1+y2)/2],"si":si}));
254 } else {
255 let y = seg["y"].as_i64().unwrap() as i32 * cell_size as i32;
256 let x1 = seg["x1"].as_i64().unwrap() as i32 * cell_size as i32;
257 let x2 = seg["x2"].as_i64().unwrap() as i32 * cell_size as i32;
258 let length = x2 - x1;
259 sizes.extend([length,20, wall_thick as i32]);
260 objects.push(json!({"p":[(x1+x2)/2,0,y],"si":si}));
261 }
262 }
263
264 let half = (cell_size as i32) / 2;
265 let start_spawn = json!([half,0,half,0,0,0]);
266 let end_spawn = json!([(fw-half),0,(fd-half),0,0,0]);
267
268 json!({
269 "name": "GeneratedMaze",
270 "ambient": "#97a0a8",
271 "light": "#f2f8fc",
272 "sky": "#dce8ed",
273 "fog": "#8d9aa0",
274 "fogD": 2000,
275 "xyz": sizes,
276 "objects": objects,
277 "spawns": [start_spawn, end_spawn],
278 })
279 }
280}