1use crate::{Dialogue, Ending, Font, Image, Item, Palette, Room, Sprite, TextDirection, Tile, Variable, transform_line_endings, new_unique_id, try_id, Instance, Error};
2
3use loe::TransformMode;
4
5use std::collections::HashMap;
6use std::borrow::BorrowMut;
7use std::fmt;
8use crate::error::NotFound;
9
10#[derive(Debug, Eq, PartialEq, Copy, Clone)]
14pub enum RoomFormat {Contiguous, CommaSeparated}
15
16#[derive(Debug)]
17pub struct InvalidRoomFormat;
18
19impl RoomFormat {
20 fn from(str: &str) -> Result<RoomFormat, InvalidRoomFormat> {
21 match str {
22 "0" => Ok(RoomFormat::Contiguous),
23 "1" => Ok(RoomFormat::CommaSeparated),
24 _ => Err(InvalidRoomFormat),
25 }
26 }
27}
28
29impl fmt::Display for RoomFormat {
30 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31 write!(f, "{}", match &self {
32 RoomFormat::Contiguous => 0,
33 RoomFormat::CommaSeparated => 1,
34 })
35 }
36}
37
38#[derive(Debug, Eq, PartialEq, Copy, Clone)]
40pub enum RoomType {Room, Set}
41
42impl ToString for RoomType {
43 fn to_string(&self) -> String {
44 match &self {
45 RoomType::Set => "SET",
46 RoomType::Room => "ROOM",
47 }.to_string()
48 }
49}
50
51#[derive(Debug, Eq, PartialEq, Copy, Clone)]
52pub struct Version {
53 pub major: u8,
54 pub minor: u8,
55}
56
57#[derive(Debug)]
58pub enum VersionError {
59 MissingParts,
60 ExtraneousParts,
61 MalformedInteger,
62}
63
64impl fmt::Display for VersionError {
65 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66 write!(f, "{}", match self {
67 VersionError::MissingParts => "Not enough parts supplied for version",
68 VersionError::ExtraneousParts => "Too many parts supplied for version",
69 VersionError::MalformedInteger => "Version did not contain valid integers",
70 })
71 }
72}
73
74impl std::error::Error for VersionError {}
75
76impl Version {
77 fn from(str: &str) -> Result<Version, VersionError> {
78 let parts: Vec<&str> = str.split('.').collect();
79
80 if parts.len() < 2 {
81 Err(VersionError::MissingParts)
82 } else if parts.len() > 2 {
83 Err(VersionError::ExtraneousParts)
84 } else if let (Ok(major), Ok(minor)) = (parts[0].parse(), parts[1].parse()) {
85 Ok(Version { major, minor })
86 } else {
87 Err(VersionError::MalformedInteger)
88 }
89 }
90}
91
92#[derive(Clone, Debug, PartialEq)]
93pub struct Game {
94 pub name: String,
95 pub version: Option<Version>,
96 pub room_format: Option<RoomFormat>,
97 pub(crate) room_type: RoomType,
98 pub font: Font,
99 pub custom_font: Option<String>, pub text_direction: TextDirection,
101 pub palettes: Vec<Palette>,
102 pub rooms: Vec<Room>,
103 pub tiles: Vec<Tile>,
104 pub sprites: Vec<Sprite>,
105 pub items: Vec<Item>,
106 pub dialogues: Vec<Dialogue>,
107 pub endings: Vec<Ending>,
108 pub variables: Vec<Variable>,
109 pub font_data: Option<String>, pub(crate) line_endings_crlf: bool, }
112
113impl Game {
114 pub fn from(string: String) -> Result<(Game, Vec<crate::Error>), crate::error::NotFound> {
115 if string.trim() == "" {
116 return Err(crate::error::NotFound::Anything);
117 }
118
119 let mut warnings = Vec::new();
120
121 let line_endings_crlf = string.contains("\r\n");
122 let mut string = string;
123 if line_endings_crlf {
124 string = transform_line_endings(string, TransformMode::LF)
125 }
126
127 let string = string.trim_start_matches('\n').to_string();
128 let mut segments = crate::segments_from_str(&string);
129
130 let mut name = "".to_string();
131
132 if
137 segments[0].starts_with("\"\"\"") ||
139 (
140 ! segments[0].starts_with("# BITSY VERSION ")
141 &&
142 ! segments[0].starts_with("! ROOM_FORMAT ")
143 &&
144 ! segments[0].starts_with("PAL ")
145 &&
146 ! segments[0].starts_with("DEFAULT_FONT ")
147 &&
148 ! segments[0].starts_with("TEXT_DIRECTION ")
149 )
150 {
151 name = segments[0].to_string();
152 segments = segments[1..].to_owned();
153 }
154
155 let segments = segments;
156
157 let name = name;
158 let mut dialogues: Vec<Dialogue> = Vec::new();
159 let mut endings: Vec<Ending> = Vec::new();
160 let mut variables: Vec<Variable> = Vec::new();
161 let mut font_data: Option<String> = None;
162
163 let mut version = None;
164 let mut room_format = None;
165 let mut room_type = RoomType::Room;
166 let mut font = Font::AsciiSmall;
167 let mut custom_font = None;
168 let mut text_direction = TextDirection::LeftToRight;
169 let mut palettes: Vec<Palette> = Vec::new();
170 let mut rooms: Vec<Room> = Vec::new();
171 let mut tiles: Vec<Tile> = Vec::new();
172 let mut sprites: Vec<Sprite> = Vec::new();
173 let mut items: Vec<Item> = Vec::new();
174 let mut avatar_exists = false;
175
176 for segment in segments {
177 if segment.starts_with("# BITSY VERSION") {
178 let segment = segment.replace("# BITSY VERSION ", "");
179 let result = Version::from(&segment);
180
181 if let Ok(v) = result {
182 version = Some(v);
183 } else {
184 warnings.push(Error::Version);
185 }
186 } else if segment.starts_with("! ROOM_FORMAT") {
187 let segment = segment.replace("! ROOM_FORMAT ", "");
188 room_format = Some(
189 RoomFormat::from(&segment).unwrap_or(RoomFormat::CommaSeparated)
190 );
191 } else if segment.starts_with("DEFAULT_FONT") {
192 let segment = segment.replace("DEFAULT_FONT ", "");
193
194 font = Font::from(&segment);
195
196 if font == Font::Custom {
197 custom_font = Some(segment.to_string());
198 }
199 } else if segment.trim() == "TEXT_DIRECTION RTL" {
200 text_direction = TextDirection::RightToLeft;
201 } else if segment.starts_with("PAL ") {
202 let result = Palette::from_str(&segment);
203 if let Ok((palette, mut errors)) = result {
204 palettes.push(palette);
205 warnings.append(&mut errors);
206 } else {
207 warnings.push(result.unwrap_err());
208 }
209 } else if segment.starts_with("ROOM ") || segment.starts_with("SET ") {
210 if segment.starts_with("SET ") {
211 room_type = RoomType::Set;
212 }
213 rooms.push(Room::from(segment));
214 } else if segment.starts_with("TIL ") {
215 tiles.push(Tile::from(segment));
216 } else if segment.starts_with("SPR ") {
217 let result = Sprite::from_str(&segment);
218
219 if let Ok(sprite) = result {
220 avatar_exists |= sprite.id == "A";
221
222 sprites.push(sprite);
223 } else {
224 warnings.push(result.unwrap_err());
225 }
226 } else if segment.starts_with("ITM ") {
227 let result = Item::from_str(&segment);
228
229 if let Ok(item) = result {
230 items.push(item);
231 } else {
232 warnings.push(result.unwrap_err());
233 }
234 } else if segment.starts_with("DLG ") {
235 let result = Dialogue::from_str(&segment);
236
237 if let Ok(dialogue) = result {
238 dialogues.push(dialogue);
239 } else {
240 warnings.push(result.unwrap_err());
241 }
242 } else if segment.starts_with("END ") {
243 let result = Ending::from_str(&segment);
244
245 if let Ok(ending) = result {
246 endings.push(ending);
247 } else {
248 warnings.push(result.unwrap_err());
249 }
250 } else if segment.starts_with("VAR ") {
251 variables.push(Variable::from(segment));
252 } else if segment.starts_with("FONT ") {
253 font_data = Some(segment);
254 }
255 }
256
257 if ! avatar_exists {
258 warnings.push(crate::Error::Game { missing: NotFound::Avatar });
259 }
260
261 Ok(
262 (
263 Game {
264 name,
265 version,
266 room_format,
267 room_type,
268 font,
269 custom_font,
270 text_direction,
271 palettes,
272 rooms,
273 tiles,
274 sprites,
275 items,
276 dialogues,
277 endings,
278 variables,
279 font_data,
280 line_endings_crlf,
281 },
282 warnings
283 )
284 )
285 }
286
287 pub fn get_sprite_by_id(&self, id: String) -> Result<&Sprite, crate::error::NotFound> {
289 let index = self.sprites.iter().position(
290 |sprite| sprite.id == id
291 );
292
293 match index {
294 Some(index) => Ok(&self.sprites[index]),
295 None => Err(crate::error::NotFound::Sprite),
296 }
297 }
298
299 pub fn get_tile_by_id(&self, id: String) -> Result<&Tile, crate::error::NotFound> {
300 let index = self.tiles.iter().position(
301 |tile| tile.id == id
302 );
303
304 match index {
305 Some(index) => Ok(&self.tiles[index]),
306 None => Err(crate::error::NotFound::Tile),
307 }
308 }
309
310 pub fn get_room_by_id(&self, id: String) -> Result<&Room, crate::error::NotFound> {
311 let index = self.rooms.iter().position(
312 |room| room.id == id
313 );
314
315 match index {
316 Some(index) => Ok(&self.rooms[index]),
317 None => Err(crate::error::NotFound::Room),
318 }
319 }
320
321 pub fn get_avatar(&self) -> Result<&Sprite, crate::error::NotFound> {
322 self.get_sprite_by_id("A".to_string())
323 }
324
325 pub fn get_tiles_by_ids(&self, ids: Vec<String>) -> Vec<&Tile> {
327 let mut tiles: Vec<&Tile> = Vec::new();
328
329 for id in ids {
330 if let Ok(tile) = self.get_tile_by_id(id) {
331 tiles.push(tile);
332 }
333 }
334
335 tiles
336 }
337
338 pub fn get_tiles_for_room(&self, id: String) -> Result<Vec<&Tile>, crate::error::NotFound> {
339 let room = self.get_room_by_id(id)?;
340 let mut tile_ids = room.tiles.clone();
341 tile_ids.sort();
342 tile_ids.dedup();
343 let zero_index = tile_ids.iter()
345 .position(|i| i == "0");
346 if let Some(zero_index) = zero_index {
347 tile_ids.remove(zero_index);
348 }
349 Ok(self.get_tiles_by_ids(tile_ids))
351 }
352
353 pub fn merge(&mut self, game: &Game) {
355 let mut palette_id_changes: HashMap<String, String> = HashMap::new();
358 let mut tile_id_changes: HashMap<String, String> = HashMap::new();
359 let mut dialogue_id_changes: HashMap<String, String> = HashMap::new();
360 let mut ending_id_changes: HashMap<String, String> = HashMap::new();
361 let mut item_id_changes: HashMap<String, String> = HashMap::new();
362 let mut room_id_changes: HashMap<String, String> = HashMap::new();
363 let mut sprite_id_changes: HashMap<String, String> = HashMap::new();
364
365 fn insert_if_different(map: &mut HashMap<String, String>, old: String, new: String) {
366 if old != new && ! map.contains_key(&old) {
367 map.insert(old, new);
368 }
369 }
370
371 for palette in &game.palettes {
377 insert_if_different(
378 palette_id_changes.borrow_mut(),
379 palette.id.clone(),
380 self.add_palette(palette.clone())
381 );
382 }
383
384 for tile in &game.tiles {
386 insert_if_different(
387 tile_id_changes.borrow_mut(),
388 tile.id.clone(),
389 self.add_tile(tile.clone())
390 );
391 }
392
393 for variable in &game.variables {
394 if ! self.variable_ids().contains(&variable.id) {
396 self.add_variable(variable.clone());
397 }
398 }
399
400 for item in &game.items {
401 let old_id = item.id.clone();
402 let new_id = try_id(&self.item_ids(), &item.id);
403 insert_if_different(item_id_changes.borrow_mut(), old_id, new_id)
404 }
405
406 for dialogue in &game.dialogues {
409 let mut dialogue = dialogue.clone();
410
411 for (old, new) in &item_id_changes {
412 dialogue.contents = dialogue.contents.replace(
414 &format!("item \"{}\"", old),
415 &format!("item \"{}\"", new)
416 );
417 }
418
419 let old_id = dialogue.id.clone();
420 let new_id = self.add_dialogue(dialogue);
421 insert_if_different(dialogue_id_changes.borrow_mut(), old_id, new_id);
422 }
423
424 for ending in &game.endings {
426 insert_if_different(
427 ending_id_changes.borrow_mut(),
428 ending.id.clone(),
429 self.add_ending(ending.clone())
430 );
431 }
432
433 for item in &game.items {
436 let mut item = item.clone();
437
438 if item_id_changes.contains_key(&item.id) {
439 item.id = item_id_changes[&item.id].clone();
440 }
441
442 if let Some(key) = item.dialogue_id.clone() {
443 if let Some(change) = dialogue_id_changes.get(&key) {
444 item.dialogue_id = Some(change.clone());
445 }
446 }
447
448 self.add_item(item);
449 }
450
451 let mut all_room_ids = self.room_ids();
456
457 for room in &game.rooms {
458 let old = room.id.clone();
459 let new = try_id(&all_room_ids, &room.id);
460 insert_if_different(room_id_changes.borrow_mut(), old, new.clone());
461 all_room_ids.push(new);
462 }
463
464 for room in &game.rooms {
467 let mut room = room.clone();
468
469 if let Some(room_id_change) = room_id_changes.get(&room.id) {
470 room.id = room_id_change.clone();
471 }
472
473 if let Some(key) = room.palette_id.clone() {
474 if let Some(change) = palette_id_changes.get(&key) {
475 room.palette_id = Some(change.clone());
476 }
477 }
478
479 room.change_tile_ids(&tile_id_changes);
480
481 room.items = room.items.iter().map(|instance|
482 if item_id_changes.contains_key(&instance.id) {
483 Instance {
484 position: instance.position.clone(),
485 id: item_id_changes[&instance.id].clone()
486 }
487 } else {
488 instance.clone()
489 }
490 ).collect();
491
492 room.exits = room.exits.iter().map(|exit| {
493 let mut exit = exit.clone();
494
495 let key = exit.exit.room_id.clone();
496
497 if let Some(change) = room_id_changes.get(&key) {
498 exit.exit.room_id = change.clone();
499 }
500
501 if let Some(key) = exit.dialogue_id.clone() {
502 if let Some(dialogue_change) = dialogue_id_changes.get(&key) {
503 exit.dialogue_id = Some(dialogue_change.clone());
504 }
505 }
506
507 exit
508 }).collect();
509
510 room.endings = room.endings.iter().map(|ending| {
511 let mut ending = ending.clone();
512 let key = ending.id.clone();
513
514 if let Some(change) = ending_id_changes.get(&key) {
515 ending.id = change.clone();
516 }
517
518 ending
519 }).collect();
520
521 self.add_room(room);
522 }
523
524 for sprite in &game.sprites {
527 let mut sprite = sprite.clone();
528 if sprite.id == "A" {
530 sprite.id = "0".to_string(); }
532
533 if let Some(key) = sprite.dialogue_id.clone() {
534 if dialogue_id_changes.contains_key(&key) {
535 sprite.dialogue_id = Some(dialogue_id_changes[&key].clone());
536 }
537 }
538
539 if let Some(key) = sprite.room_id.clone() {
540 if let Some(change) = room_id_changes.get(&key) {
541 sprite.room_id = Some(change.clone());
542 }
543 }
544
545 let old_id = sprite.id.clone();
546 let new_id = self.add_sprite(sprite);
547 insert_if_different(sprite_id_changes.borrow_mut(), old_id, new_id);
548 }
549 }
550}
551
552impl ToString for Game {
553 fn to_string(&self) -> String {
554 let mut segments: Vec<String> = Vec::new();
555
556 for palette in &self.palettes {
559 segments.push(palette.to_string());
560 }
561
562 for room in &self.rooms {
563 segments.push(room.to_string(self.room_format(), self.room_type));
564 }
565
566 for tile in &self.tiles {
567 segments.push(tile.to_string());
568 }
569
570 for sprite in &self.sprites {
571 segments.push(sprite.to_string());
572 }
573
574 for item in &self.items {
575 segments.push(item.to_string());
576 }
577
578 for dialogue in &self.dialogues {
579 segments.push(dialogue.to_string().replace("\"\"\"\n\"\"\"", ""));
581 }
582
583 for ending in &self.endings {
584 segments.push(ending.to_string());
585 }
586
587 for variable in &self.variables {
588 segments.push(variable.to_string());
589 }
590
591 if self.font_data.is_some() {
592 segments.push(self.font_data.to_owned().unwrap())
593 }
594
595 transform_line_endings(
596 format!(
597 "{}{}{}{}{}\n\n{}\n\n",
598 &self.name,
599 &self.version_line(),
600 &self.room_format_line(),
601 &self.font_line(),
602 &self.text_direction_line(),
603 segments.join("\n\n"),
604 ),
605 if self.line_endings_crlf {TransformMode::CRLF} else {TransformMode::LF}
606 )
607 }
608}
609
610impl Game {
611 pub fn palette_ids(&self) -> Vec<String> {
613 self.palettes.iter().map(|palette| palette.id.clone()).collect()
614 }
615
616 pub fn tile_ids(&self) -> Vec<String> {
617 self.tiles.iter().map(|tile| tile.id.clone()).collect()
618 }
619
620 pub fn sprite_ids(&self) -> Vec<String> {
621 self.sprites.iter().map(|sprite| sprite.id.clone()).collect()
622 }
623 pub fn room_ids(&self) -> Vec<String> {
624 self.rooms.iter().map(|room| room.id.clone()).collect()
625 }
626
627 pub fn item_ids(&self) -> Vec<String> {
628 self.items.iter().map(|item| item.id.clone()).collect()
629 }
630
631 pub fn dialogue_ids(&self) -> Vec<String> {
632 self.dialogues.iter().map(|dialogue| dialogue.id.clone()).collect()
633 }
634
635 pub fn ending_ids(&self) -> Vec<String> {
636 self.endings.iter().map(|ending| ending.id.clone()).collect()
637 }
638
639 pub fn variable_ids(&self) -> Vec<String> {
640 self.variables.iter().map(|variable| variable.id.clone()).collect()
641 }
642
643 pub fn new_palette_id(&self) -> String {
646 new_unique_id(self.palette_ids())
647 }
648
649 pub fn new_tile_id(&self) -> String {
653 let mut ids = self.tile_ids();
654 ids.push("0".to_string());
656 new_unique_id(ids)
657 }
658
659 pub fn new_sprite_id(&self) -> String {
660 new_unique_id(self.sprite_ids())
661 }
662
663 pub fn new_room_id(&self) -> String {
664 new_unique_id(self.room_ids())
665 }
666
667 pub fn new_item_id(&self) -> String {
668 new_unique_id(self.item_ids())
669 }
670
671 pub fn new_dialogue_id(&self) -> String {
672 new_unique_id(self.dialogue_ids())
673 }
674
675 pub fn new_ending_id(&self) -> String {
676 new_unique_id(self.ending_ids())
677 }
678
679 pub fn new_variable_id(&self) -> String {
680 new_unique_id(self.variable_ids())
681 }
682
683 pub fn get_palette(&self, id: &str) -> Option<&Palette> {
684 self.palettes.iter().find(|palette| palette.id == id)
685 }
686
687 pub fn get_tile_id(&self, matching_tile: &Tile) -> Option<String> {
689 for tile in &self.tiles {
690 if tile == matching_tile {
691 return Some(tile.id.clone());
692 }
693 }
694
695 None
696 }
697
698 pub fn find_tile_with_animation(&self, animation: &[Image]) -> Option<&Tile> {
699 self.tiles.iter().find(|&tile| tile.animation_frames.as_slice() == animation)
700 }
701
702 pub fn add_palette(&mut self, mut palette: Palette) -> String {
704 let new_id = try_id(&self.palette_ids(), &palette.id);
705 if new_id != palette.id {
706 palette.id = new_id.clone();
707 }
708 self.palettes.push(palette);
709 new_id
710 }
711
712 pub fn add_tile(&mut self, mut tile: Tile) -> String {
714 if tile.id == "0" || self.tile_ids().contains(&tile.id) {
715 let new_id = self.new_tile_id();
716 if new_id != tile.id {
717 tile.id = new_id;
718 }
719 }
720
721 let id = tile.id.clone();
722 self.tiles.push(tile);
723 id
724 }
725
726 pub fn add_sprite(&mut self, mut sprite: Sprite) -> String {
728 let new_id = try_id(&self.sprite_ids(), &sprite.id);
729 if new_id != sprite.id {
730 sprite.id = new_id.clone();
731 }
732 self.sprites.push(sprite);
733 new_id
734 }
735
736 pub fn add_item(&mut self, mut item: Item) -> String {
738 let new_id = try_id(&self.item_ids(), &item.id);
739 if new_id != item.id {
740 item.id = new_id.clone();
741 }
742 self.items.push(item);
743 new_id
744 }
745
746 pub fn add_dialogue(&mut self, mut dialogue: Dialogue) -> String {
748 let new_id = try_id(&self.dialogue_ids(), &dialogue.id);
749 if new_id != dialogue.id {
750 dialogue.id = new_id.clone();
751 }
752 self.dialogues.push(dialogue);
753 new_id
754 }
755
756 pub fn add_ending(&mut self, mut ending: Ending) -> String {
758 let new_id = try_id(&self.ending_ids(), &ending.id);
759 if new_id != ending.id {
760 ending.id = new_id.clone();
761 }
762 self.endings.push(ending);
763 new_id
764 }
765
766 pub fn add_room(&mut self, mut room: Room) -> String {
770 let new_id = try_id(&self.room_ids(), &room.id);
771 if new_id != room.id {
772 room.id = new_id.clone();
773 }
774 self.rooms.push(room);
775 new_id
776 }
777
778 pub fn add_variable(&mut self, mut variable: Variable) -> String {
779 let new_id = try_id(&self.variable_ids(), &variable.id);
780 if new_id != variable.id {
781 variable.id = new_id.clone();
782 }
783 new_id
784 }
785
786 pub fn dedupe_tiles(&mut self) {
790 let mut tiles_temp = self.tiles.clone();
791 let mut unique_tiles: Vec<Tile> = Vec::new();
792 let mut tile_id_changes: HashMap<String, String> = HashMap::new();
793
794 while !tiles_temp.is_empty() {
795 let tile = tiles_temp.pop().unwrap();
796
797 if tile == crate::mock::tile_background() {
798 tile_id_changes.insert(tile.id, "0".to_string());
799 } else if tiles_temp.contains(&tile) {
800 tile_id_changes.insert(
801 tile.id.clone(),
802 self.get_tile_id(&tile).unwrap()
803 );
804 } else {
805 unique_tiles.push(tile);
806 }
807 }
808
809 for room in &mut self.rooms {
810 room.change_tile_ids(&tile_id_changes);
811 }
812
813 unique_tiles.reverse();
814
815 self.tiles = unique_tiles;
816 }
817
818 fn version_line(&self) -> String {
819 if self.version.is_some() {
820 format!(
821 "\n\n# BITSY VERSION {}.{}",
822 self.version.as_ref().unwrap().major, self.version.as_ref().unwrap().minor
823 )
824 } else {
825 "".to_string()
826 }
827 }
828
829 fn room_format_line(&self) -> String {
830 if self.room_format.is_some() {
831 format!("\n\n! ROOM_FORMAT {}", self.room_format.unwrap().to_string())
832 } else {
833 "".to_string()
834 }
835 }
836
837 fn font_line(&self) -> String {
838 match self.font {
839 Font::AsciiSmall => "".to_string(),
840 Font::Custom => format!("\n\nDEFAULT_FONT {}", self.custom_font.as_ref().unwrap()),
841 _ => format!("\n\nDEFAULT_FONT {}", self.font.to_string().unwrap()),
842 }
843 }
844
845 fn text_direction_line(&self) -> &str {
846 match self.text_direction {
847 TextDirection::RightToLeft => "\n\nTEXT_DIRECTION RTL",
848 _ => "",
849 }
850 }
851
852 pub fn version(&self) -> Version {
854 self.version.unwrap_or(Version { major: 1, minor: 0 })
855 }
856
857 pub fn room_format(&self) -> RoomFormat {
859 self.room_format.unwrap_or(RoomFormat::Contiguous)
860 }
861}
862
863#[cfg(test)]
864mod test {
865 use crate::{TextDirection, Font, Version, Game, Tile, Image, Palette, Colour};
866
867 #[test]
868 fn game_from_string() {
869 let (output, _) = Game::from(include_str!["test-resources/default.bitsy"].to_string()).unwrap();
870 let expected = crate::mock::game_default();
871
872 assert_eq!(output, expected);
873 }
874
875 #[test]
876 fn game_to_string() {
877 let output = crate::mock::game_default().to_string();
878 let expected = include_str!["test-resources/default.bitsy"].to_string();
879 assert_eq!(output, expected);
880 }
881
882 #[test]
883 fn tile_ids() {
884 assert_eq!(crate::mock::game_default().tile_ids(), vec!["a".to_string()]);
885 }
886
887 #[test]
888 fn new_tile_id() {
889 assert_eq!(crate::mock::game_default().new_tile_id(), "1".to_string());
891
892 let mut game = crate::mock::game_default();
895 let mut tiles: Vec<Tile> = Vec::new();
896
897 for n in 1..10 {
899 if n != 4 {
900 let mut new_tile = crate::mock::tile_default();
901 new_tile.id = format!("{}", n).to_string();
902 tiles.push(new_tile);
903 }
904 }
905
906 game.tiles = tiles;
907
908 assert_eq!(game.new_tile_id(), "4".to_string());
909
910 let mut new_tile = crate::mock::tile_default();
913 new_tile.id = "4".to_string();
914 game.tiles.push(new_tile);
915
916 assert_eq!(game.new_tile_id(), "a".to_string());
917 }
918
919 #[test]
920 fn add_tile() {
921 let mut game = crate::mock::game_default();
922 let new_id = game.add_tile(crate::mock::tile_default());
923 assert_eq!(new_id, "1".to_string());
924 assert_eq!(game.tiles.len(), 2);
925 let new_id = game.add_tile(crate::mock::tile_default());
926 assert_eq!(new_id, "2".to_string());
927 assert_eq!(game.tiles.len(), 3);
928 }
929
930 #[test]
931 fn arabic() {
932 let (game, _) = Game::from(include_str!("test-resources/arabic.bitsy").to_string()).unwrap();
933
934 assert_eq!(game.font, Font::Arabic);
935 assert_eq!(game.text_direction, TextDirection::RightToLeft);
936 }
937
938 #[test]
939 fn version_formatting() {
940 let mut game = crate::mock::game_default();
941 game.version = Some(Version { major: 5, minor: 0 });
942 assert!(game.to_string().contains("# BITSY VERSION 5.0"))
943 }
944
945 #[test]
946 fn get_tiles_for_room() {
947 assert_eq!(
948 crate::mock::game_default().get_tiles_for_room("0".to_string()).unwrap(),
949 vec![&crate::mock::tile_default()]
950 )
951 }
952
953 #[test]
954 fn add_item() {
955 let mut game = crate::mock::game_default();
956 game.add_item(crate::mock::item());
957 game.add_item(crate::mock::item());
958
959 let expected = vec![
960 "0".to_string(), "1".to_string(), "6".to_string(), "2".to_string()
961 ];
962
963 assert_eq!(game.item_ids(), expected);
964 }
965
966 #[test]
967 fn merge() {
968 let mut game = crate::mock::game_default();
970 game.merge(&crate::mock::game_default());
971
972 assert_eq!(game.room_ids(), vec!["0".to_string(), "1".to_string()]);
973 assert_eq!(game.tile_ids(), vec!["a".to_string(), "1".to_string()]); assert_eq!(
976 game.sprite_ids(),
977 vec!["A".to_string(), "a".to_string(), "0".to_string(), "1".to_string()]
978 );
979 assert_eq!(
980 game.item_ids(),
981 vec!["0".to_string(), "1".to_string(), "2".to_string(), "3".to_string()]
982 );
983 assert_eq!(
984 game.dialogue_ids(),
985 vec![
986 "0".to_string(),
987 "1".to_string(),
988 "2".to_string(),
989 "3".to_string(),
990 "4".to_string(),
991 "5".to_string()
992 ]
993 );
994 assert_eq!(game.palette_ids(), vec!["0".to_string(), "1".to_string()]);
995 assert_eq!(
996 game.get_room_by_id("1".to_string()).unwrap().palette_id,
997 Some("1".to_string())
998 );
999
1000 let mut game_a = crate::mock::game_default();
1002 let mut game_b = crate::mock::game_default();
1003 let mut room = crate::mock::room();
1004 let mut sprite = crate::mock::sprite();
1005 let room_id = "2".to_string();
1006 room.id = room_id.clone();
1007 sprite.room_id = Some(room_id.clone());
1008 game_b.add_sprite(sprite);
1009 game_a.merge(&game_b);
1010 assert_eq!(game_a.get_sprite_by_id("2".to_string()).unwrap().room_id, Some(room_id));
1011 }
1012
1013 #[test]
1014 fn dedupe_tiles() {
1015 let mut game = crate::mock::game_default();
1016 game.add_tile(crate::mock::tile_default());
1017 game.add_tile(crate::mock::tile_default());
1018 game.add_tile(crate::mock::tile_background());
1019 game.dedupe_tiles();
1020 assert_eq!(game.tiles, vec![crate::mock::tile_default()]);
1021
1022 let tile_a = Tile {
1023 id: "0".to_string(),
1024 name: Some("apple".to_string()),
1025 wall: Some(true),
1026 animation_frames: vec![Image {
1027 pixels: vec![
1028 0,1,1,0,1,1,0,1,
1029 0,1,1,0,1,1,0,1,
1030 1,0,1,0,1,0,0,1,
1031 1,0,1,0,1,0,0,1,
1032 0,0,0,0,1,1,1,1,
1033 0,0,0,0,1,1,1,1,
1034 1,1,0,1,1,0,1,1,
1035 1,1,0,1,1,0,1,1,
1036 ]
1037 }],
1038 colour_id: Some(1)
1039 };
1040
1041 let tile_b = Tile {
1042 id: "1".to_string(),
1043 name: Some("frogspawn".to_string()),
1044 wall: Some(false),
1045 animation_frames: vec![Image {
1046 pixels: vec![
1047 1,0,1,0,1,0,0,1,
1048 0,1,1,0,1,1,0,1,
1049 0,1,1,0,1,1,0,1,
1050 1,1,0,1,1,0,1,1,
1051 1,0,1,0,1,0,0,1,
1052 0,0,0,0,1,1,1,1,
1053 0,0,0,0,1,1,1,1,
1054 1,1,0,1,1,0,1,1,
1055 ]
1056 }],
1057 colour_id: None
1058 };
1059
1060 game.add_tile(tile_a.clone());
1061 game.add_tile(tile_b.clone());
1062 game.add_tile(tile_a.clone());
1063 game.add_tile(tile_b.clone());
1064
1065 game.dedupe_tiles();
1066
1067 assert_eq!(game.tiles, vec![crate::mock::tile_default(), tile_a, tile_b]);
1068 }
1069
1070 #[test]
1071 fn find_tile_with_animation() {
1072 let game = crate::mock::game_default();
1073 let animation = vec![Image { pixels: vec![
1074 1, 1, 1, 1, 1, 1, 1, 1,
1075 1, 0, 0, 0, 0, 0, 0, 1,
1076 1, 0, 0, 0, 0, 0, 0, 1,
1077 1, 0, 0, 1, 1, 0, 0, 1,
1078 1, 0, 0, 1, 1, 0, 0, 1,
1079 1, 0, 0, 0, 0, 0, 0, 1,
1080 1, 0, 0, 0, 0, 0, 0, 1,
1081 1, 1, 1, 1, 1, 1, 1, 1,
1082 ]}];
1083 let output = game.find_tile_with_animation(&animation);
1084 let expected = Some(&game.tiles[0]);
1085 assert_eq!(output, expected);
1086 }
1087
1088 #[test]
1089 fn empty_game_data_throws_error() {
1090 assert_eq!(Game::from("".to_string() ).unwrap_err(), crate::error::NotFound::Anything);
1091 assert_eq!(Game::from(" \n \r\n".to_string()).unwrap_err(), crate::error::NotFound::Anything);
1092 }
1093
1094 #[test]
1095 fn get_palette() {
1096 let mut game = crate::mock::game_default();
1097 let new_palette = Palette {
1098 id: "1".to_string(),
1099 name: Some("sadness".to_string()),
1100 colours: vec![
1101 Colour { red: 133, green: 131, blue: 111 },
1102 Colour { red: 105, green: 93, blue: 104 },
1103 Colour { red: 62, green: 74, blue: 76 },
1104 ]
1105 };
1106 game.add_palette(new_palette.clone());
1107 assert_eq!(game.get_palette("0").unwrap(), &crate::mock::game_default().palettes[0]);
1108 assert_eq!(game.get_palette("1").unwrap(), &new_palette);
1109 assert_eq!(game.get_palette("2"), None);
1110 }
1111}