diff --git a/Cargo.toml b/Cargo.toml index b35a506..8897b6e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,3 +13,4 @@ keywords = ["gamedev"] [dependencies] radix_fmt = "1.0.0" +loe = "0.2.0" diff --git a/src/game.rs b/src/game.rs index c61e03d..8576f21 100644 --- a/src/game.rs +++ b/src/game.rs @@ -1,8 +1,51 @@ -use crate::{ - optional_data_line, Avatar, Dialogue, Ending, Font, Item, Palette, Room, Sprite, TextDirection, - Tile, ToBase36, Variable, -}; +use crate::{Avatar, Dialogue, Ending, Font, Item, Palette, Room, Sprite, TextDirection, Tile, ToBase36, Variable, transform_line_endings}; use std::error::Error; +use loe::TransformMode; + +/// in very early versions of Bitsy, room tiles were defined as single characters +/// so, only 36 tiles total. later versions are comma-separated +#[derive(Debug, Eq, PartialEq, Copy, Clone)] +pub enum RoomFormat {Contiguous, CommaSeparated} + +impl RoomFormat { + fn from(str: &str) -> Result { + match str { + "0" => Ok(RoomFormat::Contiguous), + "1" => Ok(RoomFormat::CommaSeparated), + _ => panic!(format!("Invalid room format: {}", str)), + } + } + + fn to_string(&self) -> String { + match &self { + RoomFormat::Contiguous => "0", + RoomFormat::CommaSeparated => "1", + }.to_string() + } +} + +/// in very early versions of Bitsy, a room was called a "set" +#[derive(Debug, Eq, PartialEq, Copy, Clone)] +pub enum RoomType {Room, Set} + +impl From<&str> for RoomType { + fn from(string: &str) -> RoomType { + match string { + "ROOM" => RoomType::Room, + "SET" => RoomType::Set, + _ => panic!("Unrecognised room type"), + } + } +} + +impl ToString for RoomType { + fn to_string(&self) -> String { + match &self { + RoomType::Set => "SET", + RoomType::Room => "ROOM", + }.to_string() + } +} #[derive(Debug, Eq, PartialEq, Copy, Clone)] pub struct Version { @@ -25,7 +68,8 @@ impl Version { pub struct Game { pub name: String, pub version: Option, - pub room_format: u8, // this is "0 = non-comma separated, 1 = comma separated" apparently + pub room_format: Option, + pub(crate) room_type: RoomType, pub font: Font, pub custom_font: Option, // used if font is Font::Custom pub text_direction: TextDirection, @@ -44,6 +88,12 @@ pub struct Game { impl Game { pub fn from(string: String) -> Result { + let line_endings_crlf = string.contains("\r\n"); + let mut string = string; + if line_endings_crlf { + string = transform_line_endings(string, TransformMode::LF) + } + let mut string = format!("{}\n\n", string.trim_matches('\n')); if string.starts_with("# BITSY VERSION") { @@ -105,7 +155,8 @@ impl Game { let name = segments[0].to_string(); let mut version = None; - let mut room_format: u8 = 1; + let mut room_format = None; + let mut room_type = RoomType::Room; let mut font = Font::AsciiSmall; let mut custom_font = None; let mut text_direction = TextDirection::LeftToRight; @@ -123,7 +174,8 @@ impl Game { let segment = segment.replace("# BITSY VERSION ", ""); version = Some(Version::from(&segment)); } else if segment.starts_with("! ROOM_FORMAT") { - room_format = segment.replace("! ROOM_FORMAT ", "").parse().unwrap(); + let segment = segment.replace("! ROOM_FORMAT ", ""); + room_format = Some(RoomFormat::from(&segment).unwrap()); } else if segment.starts_with("DEFAULT_FONT") { let segment = segment.replace("DEFAULT_FONT ", ""); @@ -137,6 +189,9 @@ impl Game { } else if segment.starts_with("PAL") { palettes.push(Palette::from(segment)); } else if segment.starts_with("ROOM") || segment.starts_with("SET") { + if segment.starts_with("SET") { + room_type = RoomType::Set; + } rooms.push(Room::from(segment)); } else if segment.starts_with("TIL") { tiles.push(Tile::from(segment)); @@ -156,6 +211,7 @@ impl Game { name, version, room_format, + room_type, font, custom_font, text_direction, @@ -169,7 +225,7 @@ impl Game { endings, variables, font_data, - line_endings_crlf: false + line_endings_crlf }) } } diff --git a/src/lib.rs b/src/lib.rs index 639afe9..d365a88 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,8 @@ +extern crate loe; + +use std::io::Cursor; use radix_fmt::radix_36; +use loe::{process, Config, TransformMode}; pub mod avatar; pub mod colour; @@ -96,6 +100,14 @@ fn optional_data_line(label: &str, item: Option) -> String { } } +fn transform_line_endings(input: String, mode: TransformMode) -> String { + let mut input = Cursor::new(input); + let mut output = Cursor::new(Vec::new()); + + process(&mut input, &mut output, Config::default().transform(mode)).unwrap(); + String::from_utf8(output.into_inner()).unwrap() +} + #[cfg(test)] mod test { use crate::{from_base36, ToBase36, optional_data_line, mock}; diff --git a/src/mock.rs b/src/mock.rs index 16afc16..c284d7b 100644 --- a/src/mock.rs +++ b/src/mock.rs @@ -1,4 +1,5 @@ use crate::*; +use crate::game::{RoomType, RoomFormat}; pub mod image { use crate::Image; @@ -404,7 +405,8 @@ pub fn game_default() -> Game { Game { name: "Write your game's title here".to_string(), version: Some(Version { major: 6, minor: 5 }), - room_format: 1, + room_format: Some(RoomFormat::CommaSeparated), + room_type: RoomType::Room, font: Font::AsciiSmall, custom_font: None, text_direction: TextDirection::LeftToRight, diff --git a/src/room.rs b/src/room.rs index dd72a70..00d163f 100644 --- a/src/room.rs +++ b/src/room.rs @@ -1,4 +1,5 @@ use crate::{from_base36, optional_data_line, Exit, ExitInstance, Instance, Position, ToBase36}; +use crate::game::{RoomType, RoomFormat}; #[derive(Debug, Eq, PartialEq)] pub struct Room { @@ -125,8 +126,8 @@ impl From for Room { } } -impl ToString for Room { - fn to_string(&self) -> String { +impl Room { + pub fn to_string(&self, room_format: RoomFormat, room_type: RoomType) -> String { let mut tiles = String::new(); let mut items = String::new(); let mut exits = String::new(); @@ -167,7 +168,8 @@ impl ToString for Room { } format!( - "ROOM {}\n{}{}{}{}{}{}{}", + "{} {}\n{}{}{}{}{}{}{}", + room_type.to_string(), self.id.to_base36(), tiles, self.name_line(), @@ -183,6 +185,7 @@ impl ToString for Room { #[cfg(test)] mod test { use crate::room::Room; + use crate::game::{RoomType, RoomFormat}; #[test] fn test_room_from_string() { @@ -195,7 +198,7 @@ mod test { #[test] fn test_room_to_string() { assert_eq!( - crate::mock::room().to_string(), + crate::mock::room().to_string(RoomFormat::CommaSeparated, RoomType::Room), include_str!("test-resources/room").to_string() ); }