1use std::collections::HashMap;
35
36use glam::Vec3;
37
38use crate::physics::{Body, BodyId, World};
39
40const WALL_HEIGHT: f32 = 3.0;
41
42#[derive(Debug, Clone, Default)]
44pub struct Level {
45 pub walls: Vec<BodyId>,
47 points: HashMap<char, Vec<Vec3>>,
48 width: f32,
49 depth: f32,
50}
51
52impl Level {
53 pub fn point(&self, marker: char) -> Option<Vec3> {
55 self.points.get(&marker).and_then(|p| p.first().copied())
56 }
57
58 pub fn points(&self, marker: char) -> Vec<Vec3> {
60 self.points.get(&marker).cloned().unwrap_or_default()
61 }
62
63 pub fn size(&self) -> (f32, f32) {
65 (self.width, self.depth)
66 }
67}
68
69impl World {
70 pub fn build_level(&mut self, map: &str, tile_size: f32) -> Level {
72 let lines: Vec<&str> = map.lines().collect();
73 let first = lines.iter().position(|l| !l.trim().is_empty()).unwrap_or(lines.len());
74 let last = lines.iter().rposition(|l| !l.trim().is_empty()).map_or(first, |i| i + 1);
75 let lines = &lines[first..last];
76 let indent = lines
77 .iter()
78 .filter(|l| !l.trim().is_empty())
79 .map(|l| l.len() - l.trim_start().len())
80 .min()
81 .unwrap_or(0);
82 let rows: Vec<Vec<char>> = lines.iter().map(|l| l.chars().skip(indent).collect()).collect();
83
84 let tile = tile_size.max(0.01);
85 let height = rows.len();
86 let mut level = Level {
87 width: rows.iter().map(Vec::len).max().unwrap_or(0) as f32 * tile,
88 depth: height as f32 * tile,
89 ..Level::default()
90 };
91 let center = |col: f32, row: usize| Vec3::new((col + 0.5) * tile, (height - 1 - row) as f32 * tile + tile * 0.5, 0.0);
92
93 for (r, row) in rows.iter().enumerate() {
94 let mut c = 0;
95 while c < row.len() {
96 match row[c] {
97 '#' => {
98 let start = c;
99 while c < row.len() && row[c] == '#' {
100 c += 1;
101 }
102 let run = (c - start) as f32;
103 let mut pos = center(start as f32 + (run - 1.0) / 2.0, r);
104 pos.z = WALL_HEIGHT / 2.0;
105 let wall = Body::fixed(pos).with_box(run * tile, tile, WALL_HEIGHT).with_tag("wall");
106 level.walls.push(self.add(wall));
107 continue;
108 }
109 '.' | ' ' => {}
110 marker => level.points.entry(marker).or_default().push(center(c as f32, r)),
111 }
112 c += 1;
113 }
114 }
115 level
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 #[test]
124 fn parses_walls_and_points() {
125 let mut w = World::new();
126 let level = w.build_level(
127 "
128 ####
129 #P.#
130 #.C#
131 ####
132 ",
133 2.0,
134 );
135 assert_eq!(level.walls.len(), 6);
137 assert_eq!(level.point('P'), Some(Vec3::new(3.0, 5.0, 0.0)));
138 assert_eq!(level.points('C'), vec![Vec3::new(5.0, 3.0, 0.0)]);
139 assert_eq!(level.size(), (8.0, 8.0));
140 assert!(w.at_point(Vec3::new(1.0, 7.0, 1.0)).len() == 1, "north west corner is a wall");
141
142 let player = w.add(Body::kinematic(level.point('P').unwrap()).with_sphere(0.4));
143 assert!(w.move_by(player, Vec3::new(0.0, 5.0, 0.0)).blocked);
144 }
145}