use std::collections::HashMap; use std::path::Path; pub const SUBTILE_UNITS: i32 = 500; pub const SECTION_OBJECTS: &str = "Objects"; pub const SECTION_MAP: &str = "Map"; pub const OBJECT_KING_TOWER: &str = "KingTower"; pub const OBJECT_PRINCESS_TOWER: &str = "PrincessTower"; #[derive(Debug, Clone, Default, PartialEq, Eq)] pub struct LogicTilemap { pub objects: Vec<(String, Vec<(i32, i32)>)>, pub tiles: Vec>, } fn split_row(line: &str) -> Vec { let mut cells = Vec::new(); let mut current = String::new(); let mut quoted = false; for character in line.chars() { match character { '"' => quoted = !quoted, ',' if !quoted => cells.push(std::mem::take(&mut current)), '\r' => {} _ => current.push(character), } } cells.push(current); cells } fn cell(row: &[String], index: usize) -> &str { row.get(index).map(String::as_str).unwrap_or("").trim() } fn number(value: &str) -> Option { value.parse::().ok() } impl LogicTilemap { pub fn parse(source: &str) -> Self { let mut tilemap = Self::default(); let mut section = String::new(); let mut object: Option = None; for line in source.lines() { let row = split_row(line); let head = cell(&row, 0); if !head.is_empty() { section = head.to_owned(); object = None; continue; } match section.as_str() { SECTION_OBJECTS => { let name = cell(&row, 1); if !name.is_empty() && name != "string" && name != "int" { object = Some(name.to_owned()); tilemap.objects.push((name.to_owned(), Vec::new())); continue; } let (Some(x), Some(y)) = (number(cell(&row, 2)), number(cell(&row, 3))) else { continue; }; if object.is_some() { if let Some(entry) = tilemap.objects.last_mut() { entry.1.push((x, y)); } } } SECTION_MAP => { let values: Vec = row[1..] .iter() .map(|value| value.trim()) .take_while(|value| !value.is_empty()) .filter_map(number) .collect(); if !values.is_empty() { tilemap.tiles.push(values); } } _ => {} } } tilemap } pub fn load(root: &Path, file_name: &str) -> std::io::Result { let path = root.join(file_name); let source = std::fs::read_to_string(&path)?; Ok(Self::parse(&source)) } pub fn height(&self) -> i32 { self.tiles.len() as i32 } pub fn width(&self) -> i32 { self.tiles.iter().map(Vec::len).max().unwrap_or(0) as i32 } pub fn placements(&self, name: &str) -> &[(i32, i32)] { self.objects .iter() .find(|(object, _)| object == name) .map(|(_, positions)| positions.as_slice()) .unwrap_or(&[]) } pub fn king_towers(&self) -> &[(i32, i32)] { self.placements(OBJECT_KING_TOWER) } pub fn princess_towers(&self) -> &[(i32, i32)] { self.placements(OBJECT_PRINCESS_TOWER) } pub fn counts(&self) -> HashMap<&str, usize> { self.objects .iter() .map(|(name, positions)| (name.as_str(), positions.len())) .collect() } }