diff --git a/src/zzt/elements.rs b/src/zzt/elements.rs index 3fc8016..a132db5 100644 --- a/src/zzt/elements.rs +++ b/src/zzt/elements.rs @@ -274,3 +274,70 @@ pub fn element_name(id: u8) -> String { None => format!("unknown ({})", id), } } + +/// Convert an element name back to its ID. +/// Handles "unknown_N" format for unknown element IDs. +pub fn element_id_from_name(name: &str) -> Option { + // Handle "unknown_N" format first + if let Some(suffix) = name.strip_prefix("unknown_") { + return suffix.parse().ok(); + } + // Match all known element names to their IDs + match name { + "empty" => Some(0), + "board_edge" => Some(1), + "messenger" => Some(2), + "monitor" => Some(3), + "player" => Some(4), + "ammo" => Some(5), + "torch" => Some(6), + "gem" => Some(7), + "key" => Some(8), + "door" => Some(9), + "scroll" => Some(10), + "passage" => Some(11), + "duplicator" => Some(12), + "bomb" => Some(13), + "energizer" => Some(14), + "star" => Some(15), + "clockwise" => Some(16), + "counter" => Some(17), + "bullet" => Some(18), + "water" => Some(19), + "forest" => Some(20), + "solid" => Some(21), + "normal" => Some(22), + "breakable" => Some(23), + "boulder" => Some(24), + "sliderns" => Some(25), + "sliderew" => Some(26), + "fake" => Some(27), + "invisible" => Some(28), + "blinkwall" => Some(29), + "transporter" => Some(30), + "line" => Some(31), + "ricochet" => Some(32), + "blink_ray_h" => Some(33), + "bear" => Some(34), + "ruffian" => Some(35), + "object" => Some(36), + "slime" => Some(37), + "shark" => Some(38), + "spinninggun" => Some(39), + "pusher" => Some(40), + "lion" => Some(41), + "tiger" => Some(42), + "blink_ray_v" => Some(43), + "head" => Some(44), + "segment" => Some(45), + // 46 is unused + "text_blue" => Some(47), + "text_green" => Some(48), + "text_cyan" => Some(49), + "text_red" => Some(50), + "text_purple" => Some(51), + "text_brown" => Some(52), + "text_black" => Some(53), + _ => None, + } +} diff --git a/src/zzt/text.rs b/src/zzt/text.rs index e9207a2..8ab98ba 100644 --- a/src/zzt/text.rs +++ b/src/zzt/text.rs @@ -11,7 +11,7 @@ use nom::{ sequence::pair, }; -use super::elements::{Element, element_name, resolve_alias}; +use super::elements::{Element, element_id_from_name, element_name, resolve_alias}; use super::error::ParseError; use super::parse::{Board, Program, Stat, Tile, World}; @@ -206,10 +206,14 @@ fn write_stat(output: &mut String, index: usize, stat: &Stat, element: Option e.name().to_string(), + None => format!("unknown_{}", stat.under.element), + }; writeln!( output, - "under = ({}, {})", - stat.under.element, stat.under.color + "under = ({}, 0x{:02x})", + elem_name, stat.under.color ) .unwrap(); } @@ -374,18 +378,44 @@ fn boolean(input: &str) -> IResult<&str, bool> { alt((value(true, tag("true")), value(false, tag("false")))).parse(input) } -/// Parse a tuple like (element, color). +/// Parse a hex number like 0x0f or 0x1F. +fn hex_u8(input: &str) -> IResult<&str, u8> { + let (input, _) = tag("0x").parse(input)?; + let (input, hex_str) = take_while1(|c: char| c.is_ascii_hexdigit()).parse(input)?; + match u8::from_str_radix(hex_str, 16) { + Ok(n) => Ok((input, n)), + Err(_) => Err(nom::Err::Error(nom::error::Error::new( + input, + nom::error::ErrorKind::HexDigit, + ))), + } +} + +/// Parse a tuple like (element_name, 0xNN) or legacy (element_id, color). +/// Supports both new format: (empty, 0x0f) and legacy: (0, 15). fn tuple2(input: &str) -> IResult<&str, (u8, u8)> { let (input, _) = char('(').parse(input)?; let (input, _) = multispace0.parse(input)?; - let (input, a) = map_res(digit1, |s: &str| s.parse::()).parse(input)?; + + // First value: element name (identifier) or decimal number + let (input, element_id) = alt(( + // Element name (identifier) -> convert to ID + map(identifier, |name| element_id_from_name(name).unwrap_or(0)), + // Legacy: decimal element ID + map_res(digit1, |s: &str| s.parse::()), + )) + .parse(input)?; + let (input, _) = multispace0.parse(input)?; let (input, _) = char(',').parse(input)?; let (input, _) = multispace0.parse(input)?; - let (input, b) = map_res(digit1, |s: &str| s.parse::()).parse(input)?; + + // Second value: hex color (0xNN) or decimal color + let (input, color) = alt((hex_u8, map_res(digit1, |s: &str| s.parse::()))).parse(input)?; + let (input, _) = multispace0.parse(input)?; let (input, _) = char(')').parse(input)?; - Ok((input, (a, b))) + Ok((input, (element_id, color))) } /// Parse a string array like ["foo", "bar", ""].