From 93339881c5183a8b170770093a9cf88bc00aeda9 Mon Sep 17 00:00:00 2001 From: Chris Mounce Date: Mon, 9 Mar 2026 20:34:07 -0700 Subject: [PATCH] Clean up element.rs A lot of this is deleting zztext-specific functionality, such as string mappings that go beyond official names for things. It's okay, zztext will cope. --- Cargo.lock | 77 ++++++++++++ Cargo.toml | 1 + src/elements.rs | 303 +++--------------------------------------------- src/lib.rs | 2 +- 4 files changed, 96 insertions(+), 287 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a2e307a..51ce614 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -205,6 +205,28 @@ dependencies = [ "memchr", ] +[[package]] +name = "num_enum" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -221,6 +243,15 @@ dependencies = [ "syn", ] +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -258,6 +289,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + [[package]] name = "ryu" version = "1.0.23" @@ -362,6 +399,36 @@ dependencies = [ "syn", ] +[[package]] +name = "toml_datetime" +version = "1.0.0+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.4+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7193cbd0ce53dc966037f54351dbbcf0d5a642c7f0038c382ef9e677ce8c13f2" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.9+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" +dependencies = [ + "winnow", +] + [[package]] name = "unicode-ident" version = "1.0.24" @@ -514,6 +581,15 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr", +] + [[package]] name = "wit-bindgen" version = "0.51.0" @@ -615,5 +691,6 @@ dependencies = [ "codepage-437", "insta", "nom", + "num_enum", "thiserror", ] diff --git a/Cargo.toml b/Cargo.toml index 3fd3211..f315004 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ license = "MIT" [dependencies] codepage-437 = "0.1" nom = "8.0" +num_enum = "0.7" thiserror = "2.0" [dev-dependencies] diff --git a/src/elements.rs b/src/elements.rs index a132db5..f7d28e9 100644 --- a/src/elements.rs +++ b/src/elements.rs @@ -1,5 +1,20 @@ -/// ZZT element types. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +use num_enum::{IntoPrimitive, TryFromPrimitive}; + +/// Officially supported ZZT element types. +/// +/// These are all the elements for which ZZT 3.2 has handler code. In general, +/// though, the file format allows any `u8` as a tile type. And sometimes +/// out-of-range values see actual use: +/// +/// - Some values don't crash ZZT right away, but instead trigger a different +/// bug where they appear as text colors besides the official seven. +/// - Some worlds intentionally crash ZZT for whatever reason. +/// - Forks of ZZT may take unused element IDs and repurpose them. +/// +/// In order to provide flexibility around these cases, the `Board` struct +/// represents tile types with raw `u8`s, but this enum is still provided for +/// convenience. +#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)] #[repr(u8)] pub enum Element { Empty = 0, @@ -57,287 +72,3 @@ pub enum Element { TextBrown = 52, TextBlack = 53, } - -impl Element { - /// Try to convert a u8 to an Element. - pub fn from_u8(id: u8) -> Option { - match id { - 0 => Some(Element::Empty), - 1 => Some(Element::BoardEdge), - 2 => Some(Element::Messenger), - 3 => Some(Element::Monitor), - 4 => Some(Element::Player), - 5 => Some(Element::Ammo), - 6 => Some(Element::Torch), - 7 => Some(Element::Gem), - 8 => Some(Element::Key), - 9 => Some(Element::Door), - 10 => Some(Element::Scroll), - 11 => Some(Element::Passage), - 12 => Some(Element::Duplicator), - 13 => Some(Element::Bomb), - 14 => Some(Element::Energizer), - 15 => Some(Element::Star), - 16 => Some(Element::Clockwise), - 17 => Some(Element::Counter), - 18 => Some(Element::Bullet), - 19 => Some(Element::Water), - 20 => Some(Element::Forest), - 21 => Some(Element::Solid), - 22 => Some(Element::Normal), - 23 => Some(Element::Breakable), - 24 => Some(Element::Boulder), - 25 => Some(Element::SliderNs), - 26 => Some(Element::SliderEw), - 27 => Some(Element::Fake), - 28 => Some(Element::Invisible), - 29 => Some(Element::BlinkWall), - 30 => Some(Element::Transporter), - 31 => Some(Element::Line), - 32 => Some(Element::Ricochet), - 33 => Some(Element::BlinkRayH), - 34 => Some(Element::Bear), - 35 => Some(Element::Ruffian), - 36 => Some(Element::Object), - 37 => Some(Element::Slime), - 38 => Some(Element::Shark), - 39 => Some(Element::SpinningGun), - 40 => Some(Element::Pusher), - 41 => Some(Element::Lion), - 42 => Some(Element::Tiger), - 43 => Some(Element::BlinkRayV), - 44 => Some(Element::Head), - 45 => Some(Element::Segment), - 47 => Some(Element::TextBlue), - 48 => Some(Element::TextGreen), - 49 => Some(Element::TextCyan), - 50 => Some(Element::TextRed), - 51 => Some(Element::TextPurple), - 52 => Some(Element::TextBrown), - 53 => Some(Element::TextBlack), - _ => None, - } - } - - /// Get the human-readable name for this element. - pub fn name(self) -> &'static str { - match self { - Element::Empty => "empty", - Element::BoardEdge => "board_edge", - Element::Messenger => "messenger", - Element::Monitor => "monitor", - Element::Player => "player", - Element::Ammo => "ammo", - Element::Torch => "torch", - Element::Gem => "gem", - Element::Key => "key", - Element::Door => "door", - Element::Scroll => "scroll", - Element::Passage => "passage", - Element::Duplicator => "duplicator", - Element::Bomb => "bomb", - Element::Energizer => "energizer", - Element::Star => "star", - Element::Clockwise => "clockwise", - Element::Counter => "counter", - Element::Bullet => "bullet", - Element::Water => "water", - Element::Forest => "forest", - Element::Solid => "solid", - Element::Normal => "normal", - Element::Breakable => "breakable", - Element::Boulder => "boulder", - Element::SliderNs => "sliderns", - Element::SliderEw => "sliderew", - Element::Fake => "fake", - Element::Invisible => "invisible", - Element::BlinkWall => "blinkwall", - Element::Transporter => "transporter", - Element::Line => "line", - Element::Ricochet => "ricochet", - Element::BlinkRayH => "blink_ray_h", - Element::Bear => "bear", - Element::Ruffian => "ruffian", - Element::Object => "object", - Element::Slime => "slime", - Element::Shark => "shark", - Element::SpinningGun => "spinninggun", - Element::Pusher => "pusher", - Element::Lion => "lion", - Element::Tiger => "tiger", - Element::BlinkRayV => "blink_ray_v", - Element::Head => "head", - Element::Segment => "segment", - Element::TextBlue => "text_blue", - Element::TextGreen => "text_green", - Element::TextCyan => "text_cyan", - Element::TextRed => "text_red", - Element::TextPurple => "text_purple", - Element::TextBrown => "text_brown", - Element::TextBlack => "text_black", - } - } - - /// Get the alias for parameter 1 based on element type. - pub fn p1_alias(self) -> Option<&'static str> { - match self { - Element::BlinkWall => Some("start_time"), - Element::Bear => Some("sensitivity"), - Element::Ruffian => Some("intelligence"), - Element::Object => Some("char"), - Element::Shark => Some("intelligence"), - Element::SpinningGun => Some("intelligence"), - Element::Lion => Some("intelligence"), - Element::Tiger => Some("intelligence"), - Element::Head => Some("intelligence"), - _ => None, - } - } - - /// Get the alias for parameter 2 based on element type. - pub fn p2_alias(self) -> Option<&'static str> { - match self { - Element::Duplicator => Some("rate"), - Element::BlinkWall => Some("period"), - Element::Ruffian => Some("resting_time"), - Element::Slime => Some("speed"), - Element::SpinningGun => Some("firing_rate"), - Element::Tiger => Some("firing_rate"), - Element::Head => Some("deviance"), - _ => None, - } - } - - /// Get the alias for parameter 3 based on element type. - pub fn p3_alias(self) -> Option<&'static str> { - match self { - Element::Passage => Some("destination"), - Element::SpinningGun => Some("firing_type"), - Element::Tiger => Some("firing_type"), - _ => None, - } - } - - /// Map alias name to parameter number (1, 2, or 3) based on element type. - pub fn alias_to_param(self, alias: &str) -> Option { - if self.p1_alias() == Some(alias) { - Some(1) - } else if self.p2_alias() == Some(alias) { - Some(2) - } else if self.p3_alias() == Some(alias) { - Some(3) - } else { - None - } - } -} - -/// Resolve alias name to parameter number, with optional element context. -/// Falls back to checking all known aliases if element is None. -pub fn resolve_alias(alias: &str, element: Option) -> Option { - // If we have an element, use its specific alias mapping - if let Some(elem) = element { - if let Some(param) = elem.alias_to_param(alias) { - return Some(param); - } - } - - // Fallback: check all known aliases - // p1 aliases - if matches!( - alias, - "char" | "sensitivity" | "intelligence" | "start_time" - ) { - return Some(1); - } - - // p2 aliases - if matches!( - alias, - "rate" | "period" | "resting_time" | "speed" | "firing_rate" | "deviance" - ) { - return Some(2); - } - - // p3 aliases - if matches!(alias, "destination" | "firing_type") { - return Some(3); - } - - None -} - -/// Get the human-readable name for an element ID, or describe unknown elements. -pub fn element_name(id: u8) -> String { - match Element::from_u8(id) { - Some(e) => e.name().to_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/lib.rs b/src/lib.rs index c918465..4df7c74 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,7 @@ mod encoding; mod error; mod parse; -pub use elements::{Element, element_id_from_name, element_name, resolve_alias}; +pub use elements::Element; pub use encoding::{decode_multiline, decode_oneline, encode_multiline, encode_oneline}; pub use error::ParseError; pub use parse::{Board, Program, Stat, Tile, World};