diff --git a/src/encoding.rs b/src/encoding.rs index 5f34066..24b55ac 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -30,16 +30,34 @@ pub fn decode_multiline(input: &[u8]) -> String { .collect() } +pub fn encode_oneline(input: &str) -> Result> { + input + .chars() + .map(|c| { + CP437_WINGDINGS + .encode(c) + .ok_or(anyhow!("Couldn't encode char: {}", c)) + }) + .collect() +} + +pub fn decode_oneline(input: &[u8]) -> String { + input.iter().map(|&x| CP437_WINGDINGS.decode(x)).collect() +} + #[cfg(test)] mod tests { + use super::*; use insta::assert_debug_snapshot; - use crate::encoding::{decode_multiline, encode_multiline}; - fn serialize_code(input: &str) -> Vec { encode_multiline(input).expect("Error in test") } + fn serialize_title(input: &str) -> Vec { + encode_oneline(input).expect("Error in test") + } + #[test] fn roundtrip_multiline() { let bytes: Vec = (0..=255).collect(); @@ -47,7 +65,13 @@ mod tests { } #[test] - fn newlines() { + fn roundtrip_oneline() { + let bytes: Vec = (0..=255).collect(); + assert_eq!(bytes, serialize_title(&decode_oneline(&bytes))) + } + + #[test] + fn newlines_to_cr() { assert_debug_snapshot!(serialize_code("ABC\nDEF"), @r###" [ 65, @@ -61,6 +85,16 @@ mod tests { "###) } + #[test] + fn byte_13_to_wingding() { + // Code is allowed to have newlines + let bytes = serialize_code("ABC\nDEF"); + assert!(bytes.contains(&13)); + + // But in a board title, that same byte is a wingding + assert_debug_snapshot!(decode_oneline(&bytes), @r#""ABC♪DEF""#); + } + #[test] fn wingdings() { assert_debug_snapshot!(decode_multiline(&(0..32).collect::>()), @r###""\0☺☻♥♦♣♠•◘○◙♂♀\n♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼""###); diff --git a/src/main.rs b/src/main.rs index 382e12e..88acbf5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,15 +4,10 @@ mod preprocess; mod world; use anyhow::anyhow; -use encoding::{decode_multiline, encode_multiline}; use preprocess::eval::Context; use std::{env, error::Error, fs, path::PathBuf, process::exit}; use world::World; -fn to_latin1(bytes: &[u8]) -> String { - bytes.iter().map(|&x| x as char).collect() -} - fn main() -> Result<(), Box> { let args: Vec = env::args().collect(); if args.len() != 2 { @@ -33,7 +28,7 @@ fn main() -> Result<(), Box> { println!("num boards: {}", &world.boards.len()); for board in &mut world.boards { - println!("board: {}", to_latin1(&board.name)); + println!("board: {}", board.name); for stat in &mut board.stats { // Print some of the data parsed. let (x, y) = (stat.x as usize, stat.y as usize); @@ -43,19 +38,12 @@ fn main() -> Result<(), Box> { let terrain_index = (x - 1) + (y - 1) * 60; Some(board.terrain[terrain_index]) }; - println!( - " stat at ({}, {}): {:?}, {:?}", - x, - y, - terrain, - to_latin1(&stat.code) - ); + println!(" stat at ({}, {}): {:?}, {:?}", x, y, terrain, stat.code); // Evaluate macros - let old_code = decode_multiline(&stat.code); - let new_code = eval_context.eval_program(&old_code)?; - stat.code = encode_multiline(&new_code)?; + stat.code = eval_context.eval_program(&stat.code)?; } + board.name.push_str(" (♪)"); } // Try to write a modified world file diff --git a/src/world.rs b/src/world.rs index 46bf6b8..dbb7276 100644 --- a/src/world.rs +++ b/src/world.rs @@ -10,6 +10,8 @@ use nom::{ sequence::tuple, }; +use crate::encoding::{decode_multiline, decode_oneline, encode_multiline, encode_oneline}; + #[derive(Debug)] pub struct LoadError { message: String, @@ -87,7 +89,7 @@ pub struct World { } pub struct Board { - pub name: Vec, + pub name: String, pub terrain: Vec<[u8; 2]>, pub max_shots: u8, pub is_dark: bool, @@ -118,7 +120,7 @@ pub struct Stat { pub under_color: u8, pub instruction_pointer: i16, pub bind_index: i16, - pub code: Vec, + pub code: String, } impl World { @@ -201,7 +203,8 @@ impl Board { let (input, _) = le_u16.parse(bytes)?; // Read board name - let (input, name) = pstring(50)(input)?; + let (input, name_bytes) = pstring(50)(input)?; + let name = decode_oneline(&name_bytes); // Read terrain const NUM_TILES: usize = 60 * 25; @@ -256,7 +259,8 @@ impl Board { pub fn to_bytes(&self) -> Result, &'static str> { let mut result = vec![]; result.push_padding(2); // reserve space for board size - result.push_string(50, &self.name)?; + let name_bytes = encode_oneline(&self.name).unwrap(); + result.push_string(50, &name_bytes)?; // Encode terrain if self.terrain.len() != 1500 { @@ -315,7 +319,8 @@ impl Stat { let (input, _) = take(4usize)(input)?; let (input, (instruction_pointer, length)) = tuple((le_i16, le_i16))(input)?; let (input, _) = take(8usize)(input)?; - let (input, code) = take(0.max(length) as usize)(input)?; + let (input, code_bytes) = take(0.max(length) as usize)(input)?; + let code = decode_multiline(&code_bytes); Ok(( input, Stat { @@ -333,7 +338,7 @@ impl Stat { under_color, instruction_pointer, bind_index: 0.min(length), - code: Vec::from(code), + code, }, )) } @@ -363,7 +368,8 @@ impl Stat { result.push_padding(8); if self.bind_index >= 0 { // TODO: more safety around bind-index XOR code - result.extend_from_slice(&self.code); + let code_bytes = encode_multiline(&self.code).unwrap(); + result.extend_from_slice(&code_bytes); } result }