use std::collections::HashMap; const IMAGE_DIMENSION_SD: usize = 8; const IMAGE_DIMENSION_HD: usize = 16; #[derive(Debug, Eq, PartialEq)] struct Colour { red: u8, green: u8, blue: u8, } #[derive(Debug, Eq, PartialEq)] struct Palette { id: String, // base36 string (why??) name: Option, colours: Vec, } #[derive(Debug, Eq, PartialEq)] struct Image { pixels: Vec, // 64 for SD, 256 for HD } #[derive(Debug, Eq, PartialEq)] struct Tile { id: String, // base36 string name: Option, wall: bool, animation_frames: Vec, } #[derive(Debug, Eq, PartialEq, Hash)] struct Position { x: u8, y: u8, } #[derive(Debug, Eq, PartialEq)] struct Dialogue { id: String, contents: String, } #[derive(Debug, Eq, PartialEq)] struct Sprite { id: String, // lowercase base36 name: Option, animation_frames: Vec, dialogue: Option, /// dialogue id room: String, /// room id position: Position, } /// avatar is a "sprite" in the game data but with a specific id #[derive(Debug, Eq, PartialEq)] struct Avatar { animation_frames: Vec, room: String, /// room id position: Position, } #[derive(Debug, Eq, PartialEq)] struct Item { id: String, animation_frames: Vec, name: Option, dialogue: Option, // dialogue id } #[derive(Debug, Eq, PartialEq)] struct Exit { /// destination room: String, /// id position: Position, } // same as a dialogue basically #[derive(Debug, Eq, PartialEq)] struct Ending { id: String, dialogue: String, } #[derive(Debug, Eq, PartialEq)] struct Room { id: String, palette: String, // id name: Option, tiles: Vec, // tile ids items: HashMap, // item id exits: HashMap, endings: HashMap, // ending id } #[derive(Debug, Eq, PartialEq)] struct Variable { id: String, initial_value: String, } #[derive(Debug, PartialEq)] struct Game { name: String, version: f64, room_format: u8, hd: bool, palettes: Vec, variables: Vec, } fn test_image_chequers_1() -> Image { Image { pixels: vec![ 1,0,1,0,1,0,1,0, 0,1,0,1,0,1,0,1, 1,0,1,0,1,0,1,0, 0,1,0,1,0,1,0,1, 1,0,1,0,1,0,1,0, 0,1,0,1,0,1,0,1, 1,0,1,0,1,0,1,0, 0,1,0,1,0,1,0,1, ] } } fn test_image_chequers_2() -> Image { Image { pixels: vec![ 0,1,0,1,0,1,0,1, 1,0,1,0,1,0,1,0, 0,1,0,1,0,1,0,1, 1,0,1,0,1,0,1,0, 0,1,0,1,0,1,0,1, 1,0,1,0,1,0,1,0, 0,1,0,1,0,1,0,1, 1,0,1,0,1,0,1,0, ] } } fn test_sprite() -> Sprite { Sprite { id: "a".to_string(), name: Some("hatch".to_string()), animation_frames: vec![ Image { pixels: vec![ 0,0,0,0,0,0,0,0, 0,1,1,1,1,0,0,0, 0,1,0,0,1,0,0,0, 0,0,1,1,1,1,0,0, 0,0,1,1,1,1,0,0, 0,1,0,1,1,1,1,0, 0,1,0,1,1,1,1,0, 0,1,1,0,1,1,1,1, ] } ], dialogue: Some("SPR_0".to_string()), room: "4".to_string(), position: Position { x: 9, y: 7 } } } fn test_item() -> Item { Item { id: "6".to_string(), animation_frames: vec![ Image { pixels: vec![ 0,1,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,1,0,0, 0,0,1,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,1,0, ] } ], name: Some("door".to_string()), dialogue: Some("ITM_2".to_string()) } } fn test_room() -> Room { // todo can I instantiate these inline? let mut items = HashMap::new(); let mut exits = HashMap::new(); let mut endings = HashMap::new(); items.insert(Position { x: 11, y: 5}, "d".to_string()); items.insert(Position { x: 8, y: 3}, "e".to_string()); items.insert(Position { x: 1, y: 0}, "5".to_string()); items.insert(Position { x: 2, y: 1}, "6".to_string()); items.insert(Position { x: 3, y: 2}, "6".to_string()); exits.insert( Position { x: 3, y: 3}, Exit { room: "3".to_string(), position: Position { x: 10, y: 6}} ); endings.insert(Position { x: 8, y: 7 }, "undefined".to_string()); Room { id: "a".to_string(), palette: "9".to_string(), name: Some("cellar 7".to_string()), tiles: vec![ "0".to_string(),"0".to_string(),"0".to_string(),"0".to_string(),"0".to_string(),"0".to_string(),"0".to_string(),"0".to_string(),"1l".to_string(),"0".to_string(),"0".to_string(),"0".to_string(),"0".to_string(),"0".to_string(),"0".to_string(),"0".to_string(), "y".to_string(),"x".to_string(),"0".to_string(),"0".to_string(),"1j".to_string(),"0".to_string(),"0".to_string(),"1j".to_string(),"1l".to_string(),"0".to_string(),"1j".to_string(),"0".to_string(),"0".to_string(),"1j".to_string(),"0".to_string(),"0".to_string(), "y".to_string(),"y".to_string(),"x".to_string(),"k".to_string(),"k".to_string(),"1c".to_string(),"1x".to_string(),"1y".to_string(),"1m".to_string(),"0".to_string(),"0".to_string(),"0".to_string(),"0".to_string(),"0".to_string(),"0".to_string(),"0".to_string(), "y".to_string(),"y".to_string(),"y".to_string(),"x".to_string(),"k".to_string(),"s".to_string(),"s".to_string(),"s".to_string(),"k".to_string(),"k".to_string(),"k".to_string(),"k".to_string(),"k".to_string(),"1g".to_string(),"1f".to_string(),"k".to_string(), "k".to_string(),"z".to_string(),"z".to_string(),"z".to_string(),"1i".to_string(),"1u".to_string(),"1u".to_string(),"1u".to_string(),"1v".to_string(),"11".to_string(),"19".to_string(),"1b".to_string(),"1a".to_string(),"1e".to_string(),"10".to_string(),"k".to_string(), "k".to_string(),"z".to_string(),"z".to_string(),"11".to_string(),"12".to_string(),"z".to_string(),"z".to_string(),"z".to_string(),"z".to_string(),"10".to_string(),"17".to_string(),"z".to_string(),"18".to_string(),"1e".to_string(),"12".to_string(),"k".to_string(), "k".to_string(),"z".to_string(),"z".to_string(),"z".to_string(),"z".to_string(),"z".to_string(),"z".to_string(),"z".to_string(),"z".to_string(),"1k".to_string(),"14".to_string(),"15".to_string(),"16".to_string(),"1h".to_string(),"z".to_string(),"k".to_string(), "k".to_string(),"z".to_string(),"z".to_string(),"z".to_string(),"z".to_string(),"z".to_string(),"z".to_string(),"10".to_string(),"1d".to_string(),"1v".to_string(),"1r".to_string(),"1s".to_string(),"1r".to_string(),"1q".to_string(),"1z".to_string(),"k".to_string(), "k".to_string(),"z".to_string(),"z".to_string(),"12".to_string(),"10".to_string(),"z".to_string(),"z".to_string(),"z".to_string(),"1i".to_string(),"1n".to_string(),"1o".to_string(),"1o".to_string(),"1o".to_string(),"1p".to_string(),"z".to_string(),"k".to_string(), "k".to_string(),"z".to_string(),"z".to_string(),"z".to_string(),"z".to_string(),"z".to_string(),"z".to_string(),"z".to_string(),"z".to_string(),"z".to_string(),"z".to_string(),"z".to_string(),"10".to_string(),"z".to_string(),"z".to_string(),"k".to_string(), "k".to_string(),"z".to_string(),"z".to_string(),"z".to_string(),"z".to_string(),"z".to_string(),"11".to_string(),"z".to_string(),"z".to_string(),"z".to_string(),"z".to_string(),"z".to_string(),"z".to_string(),"z".to_string(),"z".to_string(),"k".to_string(), "k".to_string(),"z".to_string(),"z".to_string(),"z".to_string(),"z".to_string(),"z".to_string(),"z".to_string(),"z".to_string(),"z".to_string(),"z".to_string(),"12".to_string(),"z".to_string(),"z".to_string(),"10".to_string(),"12".to_string(),"k".to_string(), "k".to_string(),"k".to_string(),"k".to_string(),"k".to_string(),"k".to_string(),"k".to_string(),"k".to_string(),"k".to_string(),"k".to_string(),"k".to_string(),"k".to_string(),"k".to_string(),"k".to_string(),"k".to_string(),"k".to_string(),"k".to_string(), "0".to_string(),"0".to_string(),"0".to_string(),"0".to_string(),"0".to_string(),"0".to_string(),"0".to_string(),"0".to_string(),"0".to_string(),"0".to_string(),"0".to_string(),"0".to_string(),"0".to_string(),"0".to_string(),"0".to_string(),"0".to_string(), "0".to_string(),"0".to_string(),"0".to_string(),"0".to_string(),"0".to_string(),"0".to_string(),"0".to_string(),"0".to_string(),"0".to_string(),"0".to_string(),"0".to_string(),"0".to_string(),"0".to_string(),"0".to_string(),"0".to_string(),"0".to_string(), "0".to_string(),"0".to_string(),"0".to_string(),"0".to_string(),"0".to_string(),"0".to_string(),"0".to_string(),"0".to_string(),"0".to_string(),"0".to_string(),"0".to_string(),"0".to_string(),"0".to_string(),"0".to_string(),"0".to_string(),"0".to_string() ], items, exits, endings } } fn test_room_string() -> String { "ROOM a\n0,0,0,0,0,0,0,0,1l,0,0,0,0,0,0,0\ny,x,0,0,1j,0,0,1j,1l,0,1j,0,0,1j,0,0\ny,y,x,k,k,1c,1x,1y,1m,0,0,0,0,0,0,0\ny,y,y,x,k,s,s,s,k,k,k,k,k,1g,1f,k\nk,z,z,z,1i,1u,1u,1u,1v,11,19,1b,1a,1e,10,k\nk,z,z,11,12,z,z,z,z,10,17,z,18,1e,12,k\nk,z,z,z,z,z,z,z,z,1k,14,15,16,1h,z,k\nk,z,z,z,z,z,z,10,1d,1v,1r,1s,1r,1q,1z,k\nk,z,z,12,10,z,z,z,1i,1n,1o,1o,1o,1p,z,k\nk,z,z,z,z,z,z,z,z,z,z,z,10,z,z,k\nk,z,z,z,z,z,11,z,z,z,z,z,z,z,z,k\nk,z,z,z,z,z,z,z,z,z,12,z,z,10,12,k\nk,k,k,k,k,k,k,k,k,k,k,k,k,k,k,k\n0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0\n0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0\n0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0\nNAME cellar 7\nITM d 11,5\nITM e 8,3\nITM 5 1,0\nITM 6 2,1\nITM 6 3,2\nEXT 3,3 3 10,6\nEND undefined 8,7\nPAL 9".to_string() } fn image_from_string(string: String) -> Image { let string = string.replace("\n", ""); let pixels: Vec<&str> = string.split("").collect(); // the above seems to add an extra "" at the start and end of the vec, so strip them below let pixels = &pixels[1..(pixels.len() - 1)]; let pixels: Vec = pixels.iter().map(|&pixel| { pixel.parse::().unwrap() }).collect(); Image { pixels } } #[test] fn test_image_from_string() { let output = image_from_string( "11111111\n11001111\n10111111\n11111111\n11111111\n11111111\n11111111\n11111111".to_string() ); let expected = Image { pixels: vec![ 1,1,1,1,1,1,1,1, 1,1,0,0,1,1,1,1, 1,0,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, ] }; assert_eq!(output, expected) } fn image_to_string(image: Image) -> String { image_to_string_opts(image, false) } fn image_to_string_opts(image: Image, hd: bool) -> String { let mut string = String::new(); let chunk_size = if hd {IMAGE_DIMENSION_HD} else {IMAGE_DIMENSION_SD}; for line in image.pixels.chunks(chunk_size) { for pixel in line { string.push_str(&format!("{}", *pixel)); } string.push('\n'); } string.pop(); // remove trailing newline string } #[test] fn test_image_to_string() { let output = image_to_string(test_image_chequers_1()); let expected = "10101010\n01010101\n10101010\n01010101\n10101010\n01010101\n10101010\n01010101".to_string(); assert_eq!(output, expected); } fn animation_frames_to_string(animation_frames: Vec) -> String { let mut string = String::new(); let last_frame = animation_frames.len() - 1; for (i, frame) in animation_frames.into_iter().enumerate() { string.push_str(&image_to_string(frame)); if i < last_frame { string.push_str(&"\n>\n".to_string()); } } string } fn tile_from_string(string: String) -> Tile { let mut lines: Vec<&str> = string.split("\n").collect(); let id = lines[0].replace("TIL ", ""); let last_line = lines.pop().unwrap(); let wall = match last_line == "WAL true" { true => true, false => { lines.push(last_line); false } }; let last_line = lines.pop().unwrap(); let name = match last_line.starts_with("NAME") { true => Some(last_line.replace("NAME ", "").to_string()), false => { lines.push(last_line); None } }; let animation_frames = lines[1..].join(""); let animation_frames: Vec<&str> = animation_frames.split("\n>\n").collect(); let animation_frames: Vec = animation_frames.iter().map(|&frame| { image_from_string(frame.to_string()) }).collect(); Tile {id, name, wall, animation_frames} } #[test] fn test_tile_from_string() { let output = tile_from_string("TIL z\n11111111\n11111111\n11111111\n11111111\n11111111\n11111111\n11111111\n11111111\nNAME concrete 1\nWAL true".to_string()); let expected = Tile { id: "z".to_string(), name: Some("concrete 1".to_string()), wall: true, animation_frames: vec![ Image { pixels: vec![1; 64] } ], }; assert_eq!(output, expected); } fn tile_to_string(tile: Tile) -> String { format!( "TIL {}\n{}{}{}", tile.id, animation_frames_to_string(tile.animation_frames), if tile.name.is_some() {format!("\nNAME {}", tile.name.unwrap())} else {"".to_string()}, if tile.wall {"\nWAL true"} else {""} ) } #[test] fn test_tile_to_string() { let output = tile_to_string(Tile { id: "7a".to_string(), name: Some("chequers".to_string()), wall: false, animation_frames: vec![ test_image_chequers_1(), test_image_chequers_2(), ] }); let expected = "TIL 7a\n10101010\n01010101\n10101010\n01010101\n10101010\n01010101\n10101010\n01010101\n>\n01010101\n10101010\n01010101\n10101010\n01010101\n10101010\n01010101\n10101010\nNAME chequers".to_string(); assert_eq!(output, expected); } fn colour_from_string(colour: String) -> Colour { let values: Vec<&str> = colour.split(',').collect(); let red: u8 = values[0].parse().unwrap_or(0); let green: u8 = values[1].parse().unwrap_or(0); let blue: u8 = values[2].parse().unwrap_or(0); Colour { red, green, blue } } #[test] fn test_colour_from_string() { assert_eq!( colour_from_string("0,255,0".to_string()), Colour { red: 0, green: 255, blue: 0 } ); } fn colour_to_string(colour: Colour) -> String { format!("{},{},{}", colour.red, colour.green, colour.blue) } #[test] fn test_colour_to_string() { assert_eq!( colour_to_string(Colour { red: 22, green: 33, blue: 44 }), "22,33,44".to_string() ); } fn palette_from_string(palette: String) -> Palette { let lines: Vec<&str> = palette.split('\n').collect(); let id = lines[0].replace("PAL ", ""); let name = match lines[1].starts_with("NAME") { true => Some(lines[1].replace("NAME ", "").to_string()), false => None, }; let colour_start_index = if name.is_some() {2} else {1}; let colours = lines[colour_start_index..].iter().map(|&line| { colour_from_string(line.to_string()) }).collect(); Palette { id, name, colours } } #[test] fn test_palette_from_string() { let output = palette_from_string( "PAL 1\nNAME lamplight\n45,45,59\n66,60,39\n140,94,1".to_string() ); let expected = Palette { id: "1".to_string(), name: Some("lamplight".to_string()), colours: vec![ Colour {red: 45, green: 45, blue: 59}, Colour {red: 66, green: 60, blue: 39}, Colour {red: 140, green: 94, blue: 1 }, ], }; assert_eq!(output, expected); } #[test] fn test_palette_from_string_no_name() { let output = palette_from_string( "PAL 9\n45,45,59\n66,60,39\n140,94,1".to_string() ); let expected = Palette { id: "9".to_string(), name: None, colours: vec![ Colour {red: 45, green: 45, blue: 59}, Colour {red: 66, green: 60, blue: 39}, Colour {red: 140, green: 94, blue: 1 }, ], }; assert_eq!(output, expected); } fn position_from_string(string: String) -> Position { // e.g. "2,5" let xy: Vec<&str> = string.split(',').collect(); let x = xy[0].parse().unwrap(); let y = xy[1].parse().unwrap(); Position {x, y} } #[test] fn test_position_from_string() { assert_eq!( position_from_string("4,12".to_string()), Position { x: 4, y: 12 } ) } fn position_to_string(position: Position) -> String { format!("{},{}", position.x, position.y) } #[test] fn test_position_to_string() { assert_eq!( position_to_string(Position { x: 4, y: 12 }), "4,12".to_string() ) } fn sprite_from_string(string: String) -> Sprite { let mut lines: Vec<&str> = string.split("\n").collect(); let id = lines[0].replace("SPR ", ""); let mut name = None; let mut dialogue = None; let mut room: Option = None; let mut position: Option = None; for _ in 0..3 { let last_line = lines.pop().unwrap(); if last_line.starts_with("NAME") { name = Some(last_line.replace("NAME ", "").to_string()); } else if last_line.starts_with("DLG") { dialogue = Some(last_line.replace("DLG ", "").to_string()); } else if last_line.starts_with("POS") { let last_line = last_line.replace("POS ", ""); let room_position: Vec<&str> = last_line.split(' ').collect(); room = Some(room_position[0].to_string()); position = Some(position_from_string(room_position[1].to_string())); } else { lines.push(last_line); break; } } let room = room.unwrap(); let position = position.unwrap(); // todo dedupe let animation_frames = lines[1..].join(""); let animation_frames: Vec<&str> = animation_frames.split("\n>\n").collect(); let animation_frames: Vec = animation_frames.iter().map(|&frame| { image_from_string(frame.to_string()) }).collect(); Sprite { id, name, animation_frames, dialogue, room, position } } #[test] fn test_sprite_from_string() { let output = sprite_from_string("SPR a\n00000000\n01111000\n01001000\n00111100\n00111100\n01011110\n01011110\n01101111\nNAME hatch\nDLG SPR_0\nPOS 4 9,7".to_string()); let expected = test_sprite(); assert_eq!(output, expected); } fn sprite_to_string(sprite: Sprite) -> String { format!( "SPR {}\n{}{}{}\nPOS {}", sprite.id, animation_frames_to_string(sprite.animation_frames), if sprite.name.is_some() {format!("\nNAME {}", sprite.name.unwrap())} else {"".to_string()}, if sprite.dialogue.is_some() {format!("\nDLG {}", sprite.dialogue.unwrap())} else {"".to_string()}, position_to_string(sprite.position), ) } #[test] fn test_sprite_to_string() { let output = sprite_to_string(test_sprite()); let expected = "SPR a\n00000000\n01111000\n01001000\n00111100\n00111100\n01011110\n01011110\n01101111\nNAME hatch\nDLG SPR_0\nPOS 4 9,7".to_string(); assert_eq!(output, expected); } fn item_from_string(string: String) -> Item { let mut lines: Vec<&str> = string.split("\n").collect(); let id = lines[0].replace("ITM ", ""); let mut name = None; let mut dialogue = None; for _ in 0..2 { let last_line = lines.pop().unwrap(); if last_line.starts_with("NAME") { name = Some(last_line.replace("NAME ", "").to_string()); } else if last_line.starts_with("DLG") { dialogue = Some(last_line.replace("DLG ", "").to_string()); } else { lines.push(last_line); break; } } // todo dedupe let animation_frames = lines[1..].join(""); let animation_frames: Vec<&str> = animation_frames.split("\n>\n").collect(); let animation_frames: Vec = animation_frames.iter().map(|&frame| { image_from_string(frame.to_string()) }).collect(); Item { id, name, animation_frames, dialogue } } #[test] fn test_item_from_string() { let output = item_from_string("ITM 6\n01000000\n00000000\n00000000\n00000100\n00100000\n00000000\n00000000\n00000010\nNAME door\nDLG ITM_2".to_string()); let expected = test_item(); assert_eq!(output, expected); } fn item_to_string(item: Item) -> String { format!( "ITM {}\n{}{}{}", item.id, animation_frames_to_string(item.animation_frames), if item.name.is_some() {format!("\nNAME {}", item.name.unwrap())} else {"".to_string()}, if item.dialogue.is_some() {format!("\nDLG {}", item.dialogue.unwrap())} else {"".to_string()}, ) } #[test] fn test_item_to_string() { let output = item_to_string(test_item()); let expected = "ITM 6\n01000000\n00000000\n00000000\n00000100\n00100000\n00000000\n00000000\n00000010\nNAME door\nDLG ITM_2".to_string(); assert_eq!(output, expected); } fn exit_from_string(string: String) -> Exit { // e.g. "4 3,3" let room_position: Vec<&str> = string.split(' ').collect(); let room = room_position[0].to_string(); let position = position_from_string(room_position[1].to_string()); Exit { room, position } } #[test] fn test_exit_from_string() { assert_eq!( exit_from_string("a 12,13".to_string()), Exit { room: "a".to_string(), position: Position { x: 12, y: 13}} ); } fn exit_to_string(exit: Exit) -> String { format!("{} {}", exit.room, position_to_string(exit.position)) } #[test] fn test_exit_to_string() { assert_eq!( exit_to_string(Exit { room: "8".to_string(), position: Position { x: 5, y: 6 }}), "8 5,6".to_string() ); } fn ending_from_string(string: String) -> Ending { let string = string.replace("END ", ""); let id_dialogue: Vec<&str> = string.split('\n').collect(); let id = id_dialogue[0].to_string(); let dialogue = id_dialogue[1].to_string(); Ending { id, dialogue } } #[test] fn test_ending_from_string() { assert_eq!( ending_from_string("END a\nThis is a long line of dialogue. Blah blah blah".to_string()), Ending { id: "a".to_string(), dialogue: "This is a long line of dialogue. Blah blah blah".to_string() } ); } fn ending_to_string(ending: Ending) -> String { format!("END {}\n{}", ending.id, ending.dialogue) } #[test] fn test_ending_to_string() { assert_eq!( ending_to_string( Ending { id: "7".to_string(), dialogue: "This is another long ending. So long, farewell, etc.".to_string() } ), "END 7\nThis is another long ending. So long, farewell, etc.".to_string() ); } fn dialogue_from_string(string: String) -> Dialogue { let lines: Vec<&str> = string.split("\n").collect(); let id = lines[0].replace("DLG ", "").to_string(); let contents = lines[1..].join("\n"); Dialogue { id, contents } } #[test] fn test_dialogue_from_string() { assert_eq!( dialogue_from_string("DLG h\nhello\ngoodbye".to_string()), Dialogue { id: "h".to_string(), contents: "hello\ngoodbye".to_string()} ) } fn dialogue_to_string(dialogue: Dialogue) -> String { format!("DLG {}\n{}", dialogue.id, dialogue.contents) } #[test] fn test_dialogue_to_string() { assert_eq!( dialogue_to_string( Dialogue { id: "y".to_string(), contents: "This is a bit of dialogue,\nblah blah\nblah blah".to_string() } ), "DLG y\nThis is a bit of dialogue,\nblah blah\nblah blah".to_string() ); } fn variable_from_string(string: String) -> Variable { let id_value: Vec<&str> = string.split('\n').collect(); let id = id_value[0].replace("VAR ", "").to_string(); let initial_value = id_value[1].to_string(); Variable { id, initial_value } } #[test] fn test_variable_from_string() { assert_eq!( variable_from_string("VAR a\n42".to_string()), Variable { id: "a".to_string(), initial_value: "42".to_string()} ); } fn variable_to_string(variable: Variable) -> String { format!("VAR {}\n{}", variable.id, variable.initial_value) } #[test] fn test_variable_to_string() { let output = variable_to_string( Variable { id: "c".to_string(), initial_value: "57".to_string() } ); let expected = "VAR c\n57".to_string(); assert_eq!(output, expected); } fn room_from_string(string: String) -> Room { // todo handle room_format? let mut lines: Vec<&str> = string.split("\n").collect(); let id = lines[0].replace("ROOM ", ""); let mut name = None; let mut palette = "0".to_string(); let mut items: HashMap = HashMap::new(); let mut exits: HashMap = HashMap::new(); let mut endings: HashMap = HashMap::new(); loop { let last_line = lines.pop().unwrap(); if last_line.starts_with("NAME") { name = Some(last_line.replace("NAME ", "").to_string()); } else if last_line.starts_with("PAL") { palette = last_line.replace("PAL ", "").to_string(); } else if last_line.starts_with("ITM") { let last_line = last_line.replace("ITM ", ""); let item_position: Vec<&str> = last_line.split(' ').collect(); let item_id = item_position[0]; let position = item_position[1]; let position = position_from_string(position.to_string()); items.insert(position, item_id.to_string()); } else if last_line.starts_with("EXT") { let last_line = last_line.replace("EXT ", ""); let parts: Vec<&str> = last_line.split(' ').collect(); let position = position_from_string(parts[0].to_string()); let exit = exit_from_string(format!("{} {}", parts[1], parts[2])); exits.insert(position, exit); } else if last_line.starts_with("END") { let last_line = last_line.replace("END ", ""); let ending_position: Vec<&str> = last_line.split(' ').collect(); let ending= ending_position[0].to_string(); let position = ending_position[1].to_string(); let position = position_from_string(position); endings.insert(position, ending); } else { lines.push(last_line); break; } } let lines= &lines[1..]; let mut tiles : Vec = Vec::new(); for line in lines.into_iter() { let line: Vec<&str> = line.split(",").collect(); for tile_id in line { tiles.push(tile_id.to_string()); } } Room { id, palette, name, tiles, items, exits, endings } } #[test] fn test_room_from_string() { let output = room_from_string(test_room_string()); let expected = test_room(); assert_eq!(output, expected); } fn room_to_string(room: Room) -> String { let mut tiles = String::new(); let mut items = String::new(); let mut exits = String::new(); let mut endings = String::new(); let sqrt = (room.tiles.len() as f64).sqrt() as usize; // 8 for SD, 16 for HD for line in room.tiles.chunks(sqrt) { for tile in line { tiles.push_str(&format!("{},", tile)); } tiles.pop(); // remove trailing comma tiles.push_str("\n"); } tiles.pop(); // remove trailing newline for (position, item) in room.items { items.push_str(&format!("\nITM {} {}", item, position_to_string(position))); } for (position, exit) in room.exits { exits.push_str( &format!("\nEXT {} {}", position_to_string(position), exit_to_string(exit)) ); } for (position, ending) in room.endings { endings.push_str(&format!("\nEND {} {}", ending, position_to_string(position))); } format!( "ROOM {}\n{}{}{}{}{}\nPAL {}", room.id, tiles, if room.name.is_some() {format!("\nNAME {}", room.name.unwrap())} else {"".to_string()}, items, exits, endings, room.palette ) } #[test] fn test_room_to_string() { assert_eq!( room_to_string(test_room()), test_room_string()); } // fn game_from_string(game: String ) -> Game { // // probably needs to split the game data into different segments starting from the end // // e.g. VAR... then END... then DLG... // // then split all these up into their individual items // // // no - let's try going from the beginning and popping off elements from the start? // } // fn game_to_string(game: Game) -> String { // // } fn main() { println!("why does a crate need a main function?") }