bitsy_parser/
sprite.rs

1use crate::{optional_data_line, AnimationFrames, Image, Position};
2use crate::image::animation_frames_from_str;
3
4use std::fmt;
5
6#[derive(Clone, Debug, Eq, PartialEq)]
7pub struct Sprite {
8    pub id: String,
9    pub name: Option<String>,
10    pub animation_frames: Vec<Image>,
11    pub dialogue_id: Option<String>,
12    pub room_id: Option<String>,
13    pub position: Option<Position>,
14    pub colour_id: Option<u64>,
15    pub items: Vec<String>,
16}
17
18impl Sprite {
19    fn name_line(&self) -> String {
20        optional_data_line("NAME", self.name.as_ref())
21    }
22
23    fn dialogue_line(&self) -> String {
24        optional_data_line("DLG", self.dialogue_id.as_ref())
25    }
26
27    fn room_position_line(&self) -> String {
28        if self.room_id.is_some() && self.position.is_some() {
29            format!(
30                "\nPOS {} {}",
31                self.room_id.as_ref().unwrap(),
32                self.position.as_ref().unwrap().to_string()
33            )
34        } else {
35            "".to_string()
36        }
37    }
38
39    fn colour_line(&self) -> String {
40        optional_data_line("COL", self.colour_id.as_ref())
41    }
42
43    fn item_lines(&self) -> String {
44        if self.items.is_empty() {
45            "".to_string()
46        } else {
47            let lines: Vec<String> = self.items.iter().map(
48                |item| format!("ITM {}", item)
49            ).collect();
50
51            format!("\n{}", lines.join("\n"))
52        }
53    }
54    
55    pub fn from_str(str: &str) -> Result<Sprite, crate::Error> {
56        let mut lines: Vec<&str> = str.lines().collect();
57
58        if lines.is_empty() || !lines[0].starts_with("SPR ") {
59            return Err(crate::Error::Sprite);
60        }
61
62        let id = lines[0].replace("SPR ", "");
63        let mut name = None;
64        let mut dialogue_id: Option<String> = None;
65        let mut room_id: Option<String> = None;
66        let mut position: Option<Position> = None;
67        let mut colour_id: Option<u64> = None;
68        let mut items: Vec<String> = Vec::new();
69
70        loop {
71            let last_line = lines.pop().unwrap();
72
73            if last_line.starts_with("NAME") {
74                name = Some(last_line.replace("NAME ", "").to_string());
75            } else if last_line.starts_with("DLG") {
76                dialogue_id = Some(last_line.replace("DLG ", "").to_string());
77            } else if last_line.starts_with("POS") {
78                let last_line = last_line.replace("POS ", "");
79                let room_position: Vec<&str> = last_line.split(' ').collect();
80                room_id = Some(room_position[0].to_string());
81
82                if room_position.len() < 2 {
83                    return Err(crate::Error::Sprite);
84                }
85
86                if let Ok(pos) = Position::from_str(room_position[1]) {
87                    position = Some(pos);
88                } else {
89                    return Err(crate::Error::Sprite);
90                }
91            } else if last_line.starts_with("COL") {
92                colour_id = Some(last_line.replace("COL ", "").parse().unwrap());
93            } else if last_line.starts_with("ITM") {
94                items.push(last_line.replace("ITM ", ""));
95            } else {
96                lines.push(last_line);
97                break;
98            }
99        }
100
101        items.reverse();
102
103        let animation_frames = animation_frames_from_str(
104            &lines[1..].join("\n")
105        );
106
107        Ok(Sprite {
108            id,
109            name,
110            animation_frames,
111            dialogue_id,
112            room_id,
113            position,
114            colour_id,
115            items
116        })
117    }
118}
119
120impl fmt::Display for Sprite {
121    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122        write!(
123            f,
124            "SPR {}\n{}{}{}{}{}{}",
125            self.id,
126            self.animation_frames.to_string(),
127            self.name_line(),
128            self.dialogue_line(),
129            self.room_position_line(),
130            self.colour_line(),
131            self.item_lines(),
132        )
133    }
134}
135
136#[cfg(test)]
137mod test {
138    use crate::{mock, Sprite};
139
140    #[test]
141    fn sprite_from_string() {
142        let output = Sprite::from_str(include_str!("test-resources/sprite")).unwrap();
143        let expected = mock::sprite();
144
145        assert_eq!(output, expected);
146    }
147
148    #[test]
149    fn sprite_to_string() {
150        assert_eq!(mock::sprite().to_string(), include_str!("test-resources/sprite"));
151    }
152}