const LEVELS: &[&str] = &[ "1-1.json", "1-2.json", "1-3.json", "1-4.json", "1-5.json", "1-6.json", "1-7.json", "1-8.json", "2-4.json", "2-2.json", "2-1.json", "2-3.json", ]; fn main() { let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR environment variable must be specified"); tiled_export::export_tilemap(&out_dir).expect("Failed to export tilemap"); for &level in LEVELS { tiled_export::export_level(&out_dir, level).expect("Failed to export level"); } } mod tiled_export { use serde::Deserialize; use std::collections::HashMap; use std::fs::File; use std::io::{BufReader, BufWriter, Write}; const COLLISION_TILE: i32 = 1; const KILL_TILE: i32 = 2; const WIN_TILE: i32 = 4; pub fn export_tilemap(out_dir: &str) -> std::io::Result<()> { let filename = "map/tilemap.json"; println!("cargo:rerun-if-changed={filename}"); let file = File::open(filename)?; let reader = BufReader::new(file); let tilemap: TiledTilemap = serde_json::from_reader(reader)?; let output_file = File::create(format!("{out_dir}/tilemap.rs"))?; let mut writer = BufWriter::new(output_file); let tile_data: HashMap<_, _> = tilemap .tiles .iter() .map(|tile| { ( tile.id, match tile.tile_type.as_str() { "Collision" => COLLISION_TILE, "Kill" => KILL_TILE, "Win" => WIN_TILE, _ => 0, }, ) }) .collect(); let tile_info = (0..tilemap.tilecount) .map(|id| *tile_data.get(&id).unwrap_or(&0)) .map(|tile_type| tile_type.to_string()) .collect::>() .join(", "); writeln!( &mut writer, "pub const COLLISION_TILE: i32 = {COLLISION_TILE};", )?; writeln!(&mut writer, "pub const KILL_TILE: i32 = {KILL_TILE};")?; writeln!(&mut writer, "pub const WIN_TILE: i32 = {WIN_TILE};")?; writeln!(&mut writer, "pub const TILE_DATA: &[u32] = &[{tile_info}];")?; Ok(()) } pub fn export_level(out_dir: &str, level_file: &str) -> std::io::Result<()> { let filename = format!("map/{level_file}"); println!("cargo:rerun-if-changed={filename}"); let file = File::open(filename)?; let reader = BufReader::new(file); let level: TiledLevel = serde_json::from_reader(reader)?; let output_file = File::create(format!("{out_dir}/{level_file}.rs"))?; let mut writer = BufWriter::new(output_file); let layer_1 = level.layers[0] .data .as_ref() .expect("Expected first layer to be a tile layer") .iter() .map(|id| get_map_id(*id).to_string()) .collect::>() .join(", "); let layer_2 = level.layers[1] .data .as_ref() .expect("Expected second layer to be a tile layer") .iter() .map(|id| get_map_id(*id).to_string()) .collect::>() .join(", "); writeln!(&mut writer, "const WIDTH: u32 = {};", level.width)?; writeln!(&mut writer, "const HEIGHT: u32 = {};", level.height)?; writeln!(&mut writer, "const TILEMAP: &[u16] = &[{layer_1}];")?; writeln!(&mut writer, "const BACKGROUND: &[u16] = &[{layer_2}];")?; let objects = level.layers[2] .objects .as_ref() .expect("Expected third layer to be an object layer") .iter() .map(|object| (&object.object_type, (object.x, object.y))); let mut snails = vec![]; let mut slimes = vec![]; let mut enemy_stops = vec![]; let mut player_start = None; for (object_type, (x, y)) in objects { match object_type.as_str() { "Snail Spawn" => snails.push((x, y)), "Slime Spawn" => slimes.push((x, y)), "Player Start" => player_start = Some((x, y)), "Enemy Stop" => enemy_stops.push((x, y)), _ => panic!("Unknown object type {object_type}"), } } let player_start = player_start.expect("Need a start place for the player"); let slimes_str = slimes .iter() .map(|slime| format!("({}, {})", slime.0, slime.1)) .collect::>() .join(", "); let snails_str = snails .iter() .map(|slime| format!("({}, {})", slime.0, slime.1)) .collect::>() .join(", "); let enemy_stop_str = enemy_stops .iter() .map(|enemy_stop| format!("({}, {})", enemy_stop.0, enemy_stop.1)) .collect::>() .join(", "); writeln!( &mut writer, "const SNAILS: &[(i32, i32)] = &[{snails_str}];", )?; writeln!( &mut writer, "const SLIMES: &[(i32, i32)] = &[{slimes_str}];", )?; writeln!( &mut writer, "const ENEMY_STOPS: &[(i32, i32)] = &[{enemy_stop_str}];", )?; writeln!( &mut writer, "const START_POS: (i32, i32) = ({}, {});", player_start.0, player_start.1 )?; writeln!( &mut writer, r#" use crate::Level; use agb::fixnum::Vector2D; pub const fn get_level() -> &'static Level {{ &Level {{ background: TILEMAP, foreground: BACKGROUND, dimensions: Vector2D {{x: WIDTH, y: HEIGHT}}, collision: crate::map_tiles::tilemap::TILE_DATA, enemy_stops: ENEMY_STOPS, slimes: SLIMES, snails: SNAILS, start_pos: START_POS, }} }} "# )?; Ok(()) } fn get_map_id(id: i32) -> i32 { match id { 0 => 10, i => i - 1, } } #[derive(Deserialize)] struct TiledLevel { layers: Vec, width: i32, height: i32, } #[derive(Deserialize)] struct TiledLayer { data: Option>, objects: Option>, } #[derive(Deserialize)] struct TiledObject { #[serde(rename = "type")] object_type: String, x: i32, y: i32, } #[derive(Deserialize)] struct TiledTilemap { tiles: Vec, tilecount: i32, } #[derive(Deserialize)] struct TiledTile { id: i32, #[serde(rename = "type")] tile_type: String, } }