pub mod avatar; pub mod colour; pub mod dialogue; pub mod ending; pub mod exit; pub mod palette; pub mod image; pub mod item; pub mod mock; pub mod position; pub mod room; pub mod sprite; pub mod tile; pub mod variable; use avatar::Avatar; use colour::Colour; use dialogue::Dialogue; use ending::Ending; use exit::Exit; use palette::Palette; use image::Image; use item::Item; use position::Position; use room::Room; use sprite::Sprite; use tile::Tile; use variable::Variable; #[derive(Debug, Eq, PartialEq)] pub struct Instance { position: Position, id: String, // item / ending.rs id } #[derive(Debug, Eq, PartialEq)] pub struct ExitInstance { position: Position, exit: Exit, } #[derive(Debug, PartialEq)] pub struct Game { name: String, version: f64, room_format: u8, palettes: Vec, rooms: Vec, tiles: Vec, avatar: Avatar, sprites: Vec, items: Vec, dialogues: Vec, endings: Vec, variables: Vec, } fn example_game_default() -> Game { Game { name: "Write your game's title here".to_string(), version: 6.5, room_format: 1, palettes: vec![ Palette { id: "0".to_string(), name: None, colours: vec![ Colour {red: 0, green: 82, blue: 204 }, Colour {red: 128, green: 159, blue: 255 }, Colour {red: 255, green: 255, blue: 255 }, ] } ], rooms: vec![ Room { id: "0".to_string(), palette: "0".to_string(), name: None, 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(),"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(),"a".to_string(),"a".to_string(),"a".to_string(),"a".to_string(),"a".to_string(),"a".to_string(),"a".to_string(),"a".to_string(),"a".to_string(),"a".to_string(),"a".to_string(),"a".to_string(),"a".to_string(),"a".to_string(),"0".to_string(), "0".to_string(),"a".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(),"a".to_string(),"0".to_string(), "0".to_string(),"a".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(),"a".to_string(),"0".to_string(), "0".to_string(),"a".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(),"a".to_string(),"0".to_string(), "0".to_string(),"a".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(),"a".to_string(),"0".to_string(), "0".to_string(),"a".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(),"a".to_string(),"0".to_string(), "0".to_string(),"a".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(),"a".to_string(),"0".to_string(), "0".to_string(),"a".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(),"a".to_string(),"0".to_string(), "0".to_string(),"a".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(),"a".to_string(),"0".to_string(), "0".to_string(),"a".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(),"a".to_string(),"0".to_string(), "0".to_string(),"a".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(),"a".to_string(),"0".to_string(), "0".to_string(),"a".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(),"a".to_string(),"0".to_string(), "0".to_string(),"a".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(),"a".to_string(),"0".to_string(), "0".to_string(),"a".to_string(),"a".to_string(),"a".to_string(),"a".to_string(),"a".to_string(),"a".to_string(),"a".to_string(),"a".to_string(),"a".to_string(),"a".to_string(),"a".to_string(),"a".to_string(),"a".to_string(),"a".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: vec![], exits: vec![], endings: vec![] } ], tiles: vec![ Tile { id: "a".to_string(), name: None, wall: false, animation_frames: vec![ Image { pixels: vec![ 1,1,1,1,1,1,1,1, 1,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,1, 1,0,0,1,1,0,0,1, 1,0,0,1,1,0,0,1, 1,0,0,0,0,0,0,1, 1,0,0,0,0,0,0,1, 1,1,1,1,1,1,1,1, ] } ] } ], avatar: Avatar { animation_frames: vec![ Image { pixels: vec![ 0,0,0,1,1,0,0,0, 0,0,0,1,1,0,0,0, 0,0,0,1,1,0,0,0, 0,0,1,1,1,1,0,0, 0,1,1,1,1,1,1,0, 1,0,1,1,1,1,0,1, 0,0,1,0,0,1,0,0, 0,0,1,0,0,1,0,0, ] } ], room: "0".to_string(), position: Position { x: 4, y: 4 } }, sprites: vec![ Sprite { id: "a".to_string(), name: None, animation_frames: vec![ Image { pixels: vec![ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,1,0,1,0,0,0,1, 0,1,1,1,0,0,0,1, 0,1,1,1,0,0,1,0, 0,1,1,1,1,1,0,0, 0,0,1,1,1,1,0,0, 0,0,1,0,0,1,0,0, ] } ], dialogue: Some("SPR_0".to_string()), room: "0".to_string(), position: Position { x: 8, y: 12 } } ], items: vec![ Item { id: "0".to_string(), animation_frames: vec![ Image { pixels: vec![ 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,1,1,1,0,0, 0,1,1,0,0,1,0,0, 0,0,1,0,0,1,0,0, 0,0,0,1,1,0,0,0, 0,0,0,0,0,0,0,0, ] }, ], name: Some("tea".to_string()), dialogue: Some("ITM_0".to_string()) }, ], dialogues: vec![ Dialogue { id: "SPR_0".to_string(), contents: "I'm a cat".to_string(), }, Dialogue { id: "ITM_0".to_string(), contents: "You found a nice warm cup of tea".to_string(), }, ], endings: vec![], variables: vec![ Variable { id: "a".to_string(), initial_value: "42".to_string() } ], } } pub trait AnimationFrames { fn to_string(&self) -> String; } impl AnimationFrames for Vec { #[inline] fn to_string(&self) -> String { let mut string = String::new(); let last_frame = self.len() - 1; for (i, frame) in self.into_iter().enumerate() { string.push_str(&frame.to_string()); if i < last_frame { string.push_str(&"\n>\n".to_string()); } } string } } impl From for Game { fn from(string: String) -> Game { // dialogues and endings can have 2+ line breaks inside, so deal with these separately // otherwise, everything can be split on a double line break (\n\n) let mut dialogues: Vec = Vec::new(); let mut endings: Vec = Vec::new(); let mut variables: Vec = Vec::new(); let main_split: Vec<&str> = string.split("\n\nDLG").collect(); let main = main_split[0].to_string(); let mut dialogues_endings_variables: String = main_split[1..].join("\n\nDLG"); let variable_segments = dialogues_endings_variables.clone(); let variable_segments: Vec<&str> = variable_segments.split("\n\nVAR").collect(); if variable_segments.len() > 0 { dialogues_endings_variables = variable_segments[0].to_string(); let variable_segments = variable_segments[1..].to_owned(); for segment in variable_segments { let segment = format!("VAR{}", segment); variables.push(Variable::from(segment)); } } let ending_segments = dialogues_endings_variables.clone(); let ending_segments: Vec<&str> = ending_segments.split("\n\nEND").collect(); if ending_segments.len() > 0 { dialogues_endings_variables = ending_segments[0].to_string(); let ending_segments = ending_segments[1..].to_owned(); for segment in ending_segments { let segment = format!("END{}", segment); endings.push(Ending::from(segment)); } } let dialogue_segments = format!("\n\nDLG {}", dialogues_endings_variables.trim()); let dialogue_segments: Vec<&str> = dialogue_segments.split("\n\nDLG").collect(); for segment in dialogue_segments[1..].to_owned() { let segment = format!("DLG{}", segment); dialogues.push(Dialogue::from(segment)); } let segments: Vec<&str> = main.split("\n\n").collect(); let name = segments[0].to_string(); let mut version: f64 = 1.0; let mut room_format: u8 = 1; let mut palettes: Vec = Vec::new(); let mut rooms: Vec = Vec::new(); let mut tiles: Vec = Vec::new(); let mut avatar: Option = None; // unwrap this later let mut sprites: Vec = Vec::new(); let mut items: Vec = Vec::new(); for segment in segments[1..].to_owned() { let segment = segment.to_string(); if segment.starts_with("# BITSY VERSION") { version = segment.replace("# BITSY VERSION ", "").parse().unwrap(); } else if segment.starts_with("! ROOM_FORMAT") { room_format = segment.replace("! ROOM_FORMAT ", "").parse().unwrap(); } else if segment.starts_with("PAL") { palettes.push(Palette::from(segment)); } else if segment.starts_with("ROOM") { rooms.push(Room::from(segment)); } else if segment.starts_with("TIL") { tiles.push(Tile::from(segment)); } else if segment.starts_with("SPR A") { avatar = Some(Avatar::from(segment)); } else if segment.starts_with("SPR") { sprites.push(Sprite::from(segment)); } else if segment.starts_with("ITM") { items.push(Item::from(segment)); } } assert!(avatar.is_some()); let avatar = avatar.unwrap(); Game { name, version, room_format, palettes, rooms, tiles, avatar, sprites, items, dialogues, endings, variables, } } } #[test] fn test_game_from_string() { let output = Game::from( include_str!["../test/resources/default.bitsy"].to_string() ); let expected = example_game_default(); assert_eq!(output, expected); } impl ToString for Game { #[inline] fn to_string(&self) -> String { let mut segments: Vec = Vec::new(); // todo refactor for palette in &self.palettes { segments.push(palette.to_string()); } for room in &self.rooms { segments.push(room.to_string()); } for tile in &self.tiles { segments.push(tile.to_string()); } segments.push(self.avatar.to_string()); for sprite in &self.sprites { segments.push(sprite.to_string()); } for item in &self.items { segments.push(item.to_string()); } for dialogue in &self.dialogues { segments.push(dialogue.to_string()); } for ending in &self.endings { segments.push(ending.to_string()); } for variable in &self.variables { segments.push(variable.to_string()); } format!( "{}\n\n# BITSY VERSION {}\n\n! ROOM_FORMAT {}\n\n{}\n\n", &self.name, &self.version, &self.room_format, segments.join("\n\n"), ) } } #[test] fn test_game_to_string() { let output = example_game_default().to_string(); let expected = include_str!["../test/resources/default.bitsy"].to_string(); assert_eq!(output, expected); }