use std::collections::HashMap;
use glam::Vec3;
use crate::physics::{Body, BodyId, World};
const WALL_HEIGHT: f32 = 3.0;
#[derive(Debug, Clone, Default)]
pub struct Level {
pub walls: Vec<BodyId>,
points: HashMap<char, Vec<Vec3>>,
width: f32,
depth: f32,
}
impl Level {
pub fn point(&self, marker: char) -> Option<Vec3> {
self.points.get(&marker).and_then(|p| p.first().copied())
}
pub fn points(&self, marker: char) -> Vec<Vec3> {
self.points.get(&marker).cloned().unwrap_or_default()
}
pub fn size(&self) -> (f32, f32) {
(self.width, self.depth)
}
}
impl World {
pub fn build_level(&mut self, map: &str, tile_size: f32) -> Level {
let lines: Vec<&str> = map.lines().collect();
let first = lines.iter().position(|l| !l.trim().is_empty()).unwrap_or(lines.len());
let last = lines.iter().rposition(|l| !l.trim().is_empty()).map_or(first, |i| i + 1);
let lines = &lines[first..last];
let indent = lines
.iter()
.filter(|l| !l.trim().is_empty())
.map(|l| l.len() - l.trim_start().len())
.min()
.unwrap_or(0);
let rows: Vec<Vec<char>> = lines.iter().map(|l| l.chars().skip(indent).collect()).collect();
let tile = tile_size.max(0.01);
let height = rows.len();
let mut level = Level {
width: rows.iter().map(Vec::len).max().unwrap_or(0) as f32 * tile,
depth: height as f32 * tile,
..Level::default()
};
let center = |col: f32, row: usize| Vec3::new((col + 0.5) * tile, (height - 1 - row) as f32 * tile + tile * 0.5, 0.0);
for (r, row) in rows.iter().enumerate() {
let mut c = 0;
while c < row.len() {
match row[c] {
'#' => {
let start = c;
while c < row.len() && row[c] == '#' {
c += 1;
}
let run = (c - start) as f32;
let mut pos = center(start as f32 + (run - 1.0) / 2.0, r);
pos.z = WALL_HEIGHT / 2.0;
let wall = Body::fixed(pos).with_box(run * tile, tile, WALL_HEIGHT).with_tag("wall");
level.walls.push(self.add(wall));
continue;
}
'.' | ' ' => {}
marker => level.points.entry(marker).or_default().push(center(c as f32, r)),
}
c += 1;
}
}
level
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_walls_and_points() {
let mut w = World::new();
let level = w.build_level(
"
####
#P.#
#.C#
####
",
2.0,
);
assert_eq!(level.walls.len(), 6);
assert_eq!(level.point('P'), Some(Vec3::new(3.0, 5.0, 0.0)));
assert_eq!(level.points('C'), vec![Vec3::new(5.0, 3.0, 0.0)]);
assert_eq!(level.size(), (8.0, 8.0));
assert!(w.at_point(Vec3::new(1.0, 7.0, 1.0)).len() == 1, "north west corner is a wall");
let player = w.add(Body::kinematic(level.point('P').unwrap()).with_sphere(0.4));
assert!(w.move_by(player, Vec3::new(0.0, 5.0, 0.0)).blocked);
}
}