use std::fmt; use std::collections::HashMap; const IMAGE_DIMENSION_SD: usize = 8; const IMAGE_DIMENSION_HD: usize = 16; #[derive(Eq, PartialEq)] struct Colour { red: u8, green: u8, blue: u8, } #[derive(Eq, PartialEq)] struct Palette { id: String, // base36 string (why??) name: Option, colours: Vec, } #[derive(Eq, PartialEq)] struct Image { pixels: Vec, // 64 for SD, 256 for HD } #[derive(Eq, PartialEq)] struct Tile { id: String, // base36 string name: Option, wall: bool, animation_frames: Vec, } #[derive(Debug, Eq, PartialEq, Hash)] struct Position { room: String, // id. is room id int or base36 string? x: u8, y: u8, } #[derive(Eq, PartialEq)] struct Dialogue { id: String, contents: String, } #[derive(Eq, PartialEq)] struct Sprite { id: String, // lowercase base36 name: Option, animation_frames: Vec, dialogue: Option, position: Position, } /// avatar is a "sprite" in the game data but with a specific ID #[derive(Eq, PartialEq)] struct Avatar { animation_frames: Vec, position: Position, } #[derive(Eq, PartialEq)] struct Item { id: String, animation_frames: Vec, name: Option, dialogue: Option, } #[derive(Eq, PartialEq)] struct Exit { /// destination room: String, /// id position: Position, } // same as a dialogue basically #[derive(Eq, PartialEq)] struct Ending { contents: String, } #[derive(Eq, PartialEq)] struct Room { id: String, palette: String, /// id name: Option, tiles: Vec, /// tile ids items: HashMap, exits: HashMap, endings: HashMap, } #[derive(PartialEq)] struct Game { name: String, version: f64, room_format: bool, palettes: Vec, } impl fmt::Debug for Colour { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Colour") .field("red", &self.red) .field("green", &self.green) .field("blue", &self.blue) .finish() } } impl fmt::Debug for Palette { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Palette") .field("id", &self.id) .field("name", &self.name) .field("colours", &self.colours) .finish() } } impl fmt::Debug for Image { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Image") .field("pixels", &self.pixels) .finish() } } impl fmt::Debug for Tile { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Tile") .field("id", &self.id) .field("name", &self.name) .field("wall", &self.wall) .field("animation_frames", &self.animation_frames) .finish() } } 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, true) } 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(*pixel as char); } string.push('\n'); } string.pop(); // remove trailing newline string } #[test] fn test_image_to_string() { let output = image_to_string(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, ] }); let expected = "10101010\n01010101\n10101010\n01010101\n10101010\n01010101\n10101010\n01010101".to_string(); assert_eq!(output, expected); } 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 { let mut animation_frames = String::new(); let last_frame = tile.animation_frames.len() - 1; for (i, frame) in tile.animation_frames.into_iter().enumerate() { animation_frames.push_str(&image_to_string(frame)); if i < last_frame { animation_frames.push_str(&"\n>\n".to_string()); } } format!( "TIL {}\n{}{}{}", tile.id, 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![ 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, ]}, 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, ]}, ] }); 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); } // todo support Bitsy HD? // todo impl (Game::from_string etc.) 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. "0 2,5" let room_xy: Vec<&str> = string.split(' ').collect(); let xy: Vec<&str> = room_xy[1].split(',').collect(); let room = room_xy[0].to_string(); let x = xy[0].parse().unwrap(); let y = xy[1].parse().unwrap(); Position {room, x, y} } #[test] fn test_position_from_string() { assert_eq!( position_from_string("5 4,12".to_string()), Position { room: "5".to_string(), x: 4, y: 12 } ) } fn position_to_string(position: Position) -> String { format!("{} {},{}", position.room, position.x, position.y) } #[test] fn test_position_to_string() { assert_eq!( position_to_string(Position { room: "5".to_string(), x: 4, y: 12 }), "5 4,12".to_string() ) } // fn get_avatar(game: Game) -> Sprite { // // get the sprite with an id of A // } // 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 // } // // fn game_to_string(game: Game) -> String { // // } fn main() { println!("why am I here?") }