extern crate loe; use std::io::Cursor; use radix_fmt::radix_36; use loe::{process, Config, TransformMode}; pub mod colour; pub mod dialogue; pub mod ending; pub mod exit; pub mod game; pub mod image; pub mod item; pub mod mock; pub mod palette; pub mod position; pub mod room; pub mod sprite; pub mod text; pub mod tile; pub mod variable; // pub mod test_omnibus; use colour::Colour; use dialogue::Dialogue; use ending::Ending; use exit::{Exit, Transition}; use game::{Game, Version}; use image::Image; use item::Item; use palette::Palette; use position::Position; use room::Room; use sprite::Sprite; use std::fmt::Display; use text::{Font, TextDirection}; use tile::Tile; use variable::Variable; use std::num::ParseIntError; #[derive(Clone, Debug, Eq, PartialEq)] pub struct Instance { position: Position, id: String, // item / ending id } #[derive(Clone, Debug, Eq, PartialEq)] pub struct ExitInstance { position: Position, exit: Exit, transition: Option, dialogue_id: Option, } 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 } } #[inline] fn from_base36(str: &str) -> Result { u64::from_str_radix(str, 36) } /// this doesn't work inside ToBase36 for some reason #[inline] fn to_base36(int: u64) -> String { format!("{}", radix_36(int)) } pub trait ToBase36 { fn to_base36(&self) -> String; } impl ToBase36 for u64 { #[inline] fn to_base36(&self) -> String { to_base36(*self) } } /// e.g. `\nNAME DLG_0` #[inline] fn optional_data_line(label: &str, item: Option) -> String { if item.is_some() { format!("\n{} {}", label, item.unwrap()) } else { "".to_string() } } #[inline] 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() } #[inline] fn segments_from_string(string: String) -> Vec { // this is pretty weird but a dialogue can just have an empty line followed by a name // however, on entering two empty lines, dialogue will be wrapped in triple quotation marks // so, handle this here let string = string.replace("\n\nNAME", "\n\"\"\"\n\"\"\"\nNAME"); let mut output:Vec = Vec::new(); // are we inside `"""\n...\n"""`? if so, ignore empty lines let mut inside_escaped_block = false; let mut current_segment : Vec = Vec::new(); for line in string.lines() { if line == "\"\"\"" { inside_escaped_block = ! inside_escaped_block; } if line == "" && !inside_escaped_block { output.push(current_segment.join("\n")); current_segment = Vec::new(); } else { current_segment.push(line.to_string()); } } output.push(current_segment.join("\n")); output } /// tries to use an existing ID - if it is already in use, generate a new one /// then return the ID (either original or new) /// todo refactor (unnecessary clones etc.) fn try_id(ids: Vec, id: String) -> String { let id = id.clone(); let ids = ids.clone(); if is_id_available(&ids, &id) { id } else { new_unique_id(ids) } } fn is_id_available(ids: &Vec, id: &String) -> bool { ! ids.contains(id) } /// e.g. pass all tile IDs into this to get a new non-conflicting tile ID #[inline] fn new_unique_id(mut ids: Vec) -> String { ids.sort(); ids.dedup(); let mut new_id: u64 = 0; while ids.contains(&new_id.to_base36()) { new_id += 1; } return to_base36(new_id); } pub trait Quote { fn quote(&self) -> String; } impl Quote for String { #[inline] fn quote(&self) -> String { format!("\"\"\"\n{}\n\"\"\"", self).to_string() } } pub trait Unquote { fn unquote(&self) -> String; } impl Unquote for String { #[inline] fn unquote(&self) -> String { self.trim_matches('\"').trim_matches('\n').to_string() } } #[cfg(test)] mod test { use crate::{from_base36, ToBase36, optional_data_line, mock, segments_from_string, Quote, Unquote, new_unique_id, try_id}; #[test] fn test_from_base36() { assert_eq!(from_base36("0").unwrap(), 0); assert_eq!(from_base36("0z").unwrap(), 35); assert_eq!(from_base36("11").unwrap(), 37); } #[test] fn test_to_base36() { assert_eq!((37 as u64).to_base36(), "11"); } #[test] fn test_optional_data_line() { let output = optional_data_line("NAME", mock::item().name); assert_eq!(output, "\nNAME door".to_string()); } #[test] fn test_string_to_segments() { let output = segments_from_string( include_str!("./test-resources/segments").to_string() ); let expected = vec![ "\"\"\"\nthe first segment is a long bit of text\n\n\nit contains empty lines\n\n\"\"\"".to_string(), "this is a new segment\nthis is still the second segment\nblah\nblah".to_string(), "DLG SEGMENT_3\n\"\"\"\nthis is a short \"long\" bit of text\n\"\"\"".to_string(), "this is the last segment".to_string(), ]; assert_eq!(output, expected); } #[test] fn test_quote() { let output = "this is a string.\nIt has 2 lines".to_string().quote(); let expected = "\"\"\"\nthis is a string.\nIt has 2 lines\n\"\"\"".to_string(); assert_eq!(output, expected); } #[test] fn test_unquote() { let output = "\"\"\"\nwho the fuck is scraeming \"LOG OFF\" at my house.\nshow yourself, coward.\ni will never log off\n\"\"\"".to_string().unquote(); let expected = "who the fuck is scraeming \"LOG OFF\" at my house.\nshow yourself, coward.\ni will never log off".to_string(); assert_eq!(output, expected); } #[test] fn test_try_id() { // does a conflict generate a new ID? assert_eq!( try_id(vec!["0".to_string(), "1".to_string()], "1".to_string()), "2".to_string() ); // with no conflict, does the ID remain the same? assert_eq!( try_id(vec!["0".to_string(), "1".to_string()], "3".to_string()), "3".to_string() ); } #[test] fn test_new_unique_id() { // start assert_eq!(new_unique_id(vec!["1".to_string(), "z".to_string()]), "0".to_string()); // middle assert_eq!(new_unique_id(vec!["0".to_string(), "2".to_string()]), "1".to_string()); // end assert_eq!(new_unique_id(vec!["0".to_string(), "1".to_string()]), "2".to_string()); // check sorting assert_eq!(new_unique_id(vec!["1".to_string(), "0".to_string()]), "2".to_string()); // check deduplication assert_eq!( new_unique_id(vec!["0".to_string(), "0".to_string(), "1".to_string()]), "2".to_string() ); } }