ca_formats/
apgcode.rs

1//! Parsers for [apgcode](https://www.conwaylife.com/wiki/Apgcode) format
2//! and [Extended Wechsler format](https://www.conwaylife.com/wiki/Apgcode#Extended_Wechsler_Format).
3
4use crate::Coordinates;
5use displaydoc::Display;
6use std::str::Bytes;
7use thiserror::Error;
8
9/// Errors that can be returned when parsing a apgcode string.
10#[derive(Clone, Debug, Eq, Error, Display, PartialEq)]
11pub enum Error {
12    /// Unexpected character: {0}.
13    UnexpectedChar(char),
14    /// Pattern not encoded in extended Wechsler format
15    Unencodable,
16}
17
18/// A parser for [Extended Wechsler format](https://www.conwaylife.com/wiki/Apgcode#Extended_Wechsler_Format).
19///
20/// Extended Wechsler format is the part of apgcode that encodes the cells in the pattern,
21/// e.g. `153` in `xq4_153`.
22///
23/// As an iterator, it iterates over the living cells.
24///
25/// Reading from files is not supported, since the format is usually short and not stored in a file.
26///
27/// # Example
28///
29/// ```rust
30/// use ca_formats::apgcode::Wechsler;
31///
32/// const GLIDER: &str = "153";
33///
34/// let glider = Wechsler::new(GLIDER);
35///
36/// let cells = glider.map(|cell| cell.unwrap()).collect::<Vec<_>>();
37/// assert_eq!(cells, vec![(0, 0), (1, 0), (1, 2), (2, 0), (2, 1)]);
38/// ```
39#[must_use]
40#[derive(Clone, Debug)]
41pub struct Wechsler<'a> {
42    /// An iterator over bytes of the string.
43    bytes: Bytes<'a>,
44
45    /// Coordinates of the current cell.
46    position: Coordinates,
47
48    /// The current strip of 5 cells, represented by 1 character in Extended Wechsler format.
49    current_strip: u8,
50
51    /// Index of the current cell in the current strip.
52    index: u8,
53}
54
55impl<'a> Wechsler<'a> {
56    /// Creates a new parser instance from a string.
57    pub fn new(string: &'a str) -> Self {
58        Wechsler {
59            bytes: string.bytes(),
60            position: (0, 0),
61            current_strip: 0,
62            index: 5,
63        }
64    }
65}
66
67/// An iterator over living cells in a string in Extended Wechsler format.
68impl<'a> Iterator for Wechsler<'a> {
69    type Item = Result<Coordinates, Error>;
70
71    fn next(&mut self) -> Option<Self::Item> {
72        loop {
73            if self.index < 5 {
74                loop {
75                    if self.current_strip & 1 << self.index == 0 {
76                        self.index += 1;
77                        if self.index == 5 {
78                            self.position.0 += 1;
79                            break;
80                        }
81                    } else {
82                        let cell = (self.position.0, self.position.1 + self.index as i64);
83                        self.index += 1;
84                        if self.index == 5 {
85                            self.position.0 += 1;
86                        }
87                        return Some(Ok(cell));
88                    }
89                }
90            } else if let Some(c) = self.bytes.next() {
91                match c {
92                    b'0' => self.position.0 += 1,
93                    b'1'..=b'9' => {
94                        self.current_strip = c - b'0';
95                        self.index = 0;
96                    }
97                    b'a'..=b'v' => {
98                        self.current_strip = c - b'a' + 10;
99                        self.index = 0;
100                    }
101                    b'w' => self.position.0 += 2,
102                    b'x' => self.position.0 += 3,
103                    b'y' => {
104                        if let Some(c) = self.bytes.next() {
105                            let n = match c {
106                                b'0'..=b'9' => c - b'0',
107                                b'a'..=b'z' => c - b'a' + 10,
108                                _ => return Some(Err(Error::UnexpectedChar(char::from(c)))),
109                            };
110                            self.position.0 += 4 + n as i64
111                        } else {
112                            return Some(Err(Error::UnexpectedChar('y')));
113                        }
114                    }
115                    b'z' => {
116                        self.position.0 = 0;
117                        self.position.1 += 5;
118                    }
119                    _ => return Some(Err(Error::UnexpectedChar(char::from(c)))),
120                }
121            } else {
122                return None;
123            }
124        }
125    }
126}
127
128/// Type of a pattern.
129#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
130pub enum PatternType {
131    /// [Still life](https://conwaylife.com/wiki/Still_life).
132    StillLife,
133    /// [Oscillator](https://conwaylife.com/wiki/Oscillator).
134    Oscillator,
135    /// [Spaceship](https://conwaylife.com/wiki/Spaceship).
136    Spaceship,
137}
138
139/// A parser for [apgcode](https://www.conwaylife.com/wiki/Apgcode) format.
140///
141/// Only supports patterns that are encoded in Extended Wechsler format,
142/// i.e., still lifes, oscillators, spaceships.
143/// Rules with more than 2 states are not yet supported.
144///
145/// As an iterator, it iterates over the living cells.
146///
147/// Reading from files is not supported, since apgcode is usually short and not stored in a file.
148///
149/// # Example
150///
151/// ```rust
152/// use ca_formats::apgcode::{ApgCode, PatternType};
153///
154/// const GLIDER: &str = "xq4_153";
155///
156/// let glider = ApgCode::new(GLIDER).unwrap();
157/// assert_eq!(glider.pattern_type(), PatternType::Spaceship);
158/// assert_eq!(glider.period(), 4);
159///
160/// let cells = glider.map(|cell| cell.unwrap()).collect::<Vec<_>>();
161/// assert_eq!(cells, vec![(0, 0), (1, 0), (1, 2), (2, 0), (2, 1)]);
162/// ```
163#[must_use]
164#[derive(Clone, Debug)]
165pub struct ApgCode<'a> {
166    pattern_type: PatternType,
167    period: u64,
168    wechsler: Wechsler<'a>,
169}
170
171impl<'a> ApgCode<'a> {
172    /// Creates a new parser instance from a string.
173    pub fn new(string: &'a str) -> Result<Self, Error> {
174        let mut split = string.split('_');
175        let prefix = split.next().ok_or(Error::Unencodable)?;
176        if prefix[2..].bytes().any(|c| !c.is_ascii_digit()) {
177            return Err(Error::Unencodable);
178        }
179        let pattern_type = match &prefix[..2] {
180            "xs" => PatternType::StillLife,
181            "xp" => PatternType::Oscillator,
182            "xq" => PatternType::Spaceship,
183            _ => return Err(Error::Unencodable),
184        };
185        let period = if pattern_type == PatternType::StillLife {
186            1
187        } else {
188            prefix[2..].parse().map_err(|_| Error::Unencodable)?
189        };
190        let wechsler_string = split.next().ok_or(Error::Unencodable)?;
191        let wechsler = Wechsler::new(wechsler_string);
192        Ok(ApgCode {
193            pattern_type,
194            period,
195            wechsler,
196        })
197    }
198
199    /// Period of the pattern.
200    pub const fn period(&self) -> u64 {
201        self.period
202    }
203
204    /// Type of the pattern.
205    pub const fn pattern_type(&self) -> PatternType {
206        self.pattern_type
207    }
208}
209
210/// An iterator over living cells in an apgcode string.
211impl<'a> Iterator for ApgCode<'a> {
212    type Item = Result<Coordinates, Error>;
213
214    fn next(&mut self) -> Option<Self::Item> {
215        self.wechsler.next()
216    }
217}
218
219#[cfg(test)]
220mod tests {
221    use super::*;
222
223    #[test]
224    fn wechsler_glider() -> Result<(), Error> {
225        const GLIDER: &str = "153";
226        let glider = Wechsler::new(GLIDER);
227
228        let _ = glider.clone();
229
230        let cells = glider.collect::<Result<Vec<_>, _>>()?;
231        assert_eq!(cells, vec![(0, 0), (1, 0), (1, 2), (2, 0), (2, 1)]);
232        Ok(())
233    }
234
235    #[test]
236    fn wechsler_twin_bees_shuttle() -> Result<(), Error> {
237        const TWIN_BEE_SHUTTLE: &str = "033y133zzzckgsxsgkczz0cc";
238        let twin_bees_shuttle = Wechsler::new(TWIN_BEE_SHUTTLE);
239
240        let cells = twin_bees_shuttle.collect::<Result<Vec<_>, _>>()?;
241        assert_eq!(
242            cells,
243            vec![
244                (1, 0),
245                (1, 1),
246                (2, 0),
247                (2, 1),
248                (8, 0),
249                (8, 1),
250                (9, 0),
251                (9, 1),
252                (0, 17),
253                (0, 18),
254                (1, 17),
255                (1, 19),
256                (2, 19),
257                (3, 17),
258                (3, 18),
259                (3, 19),
260                (7, 17),
261                (7, 18),
262                (7, 19),
263                (8, 19),
264                (9, 17),
265                (9, 19),
266                (10, 17),
267                (10, 18),
268                (1, 27),
269                (1, 28),
270                (2, 27),
271                (2, 28)
272            ]
273        );
274        Ok(())
275    }
276
277    #[test]
278    fn apgcode_glider() -> Result<(), Error> {
279        const GLIDER: &str = "xq4_153";
280        let glider = ApgCode::new(GLIDER)?;
281
282        let _ = glider.clone();
283
284        assert_eq!(glider.pattern_type(), PatternType::Spaceship);
285        assert_eq!(glider.period(), 4);
286
287        let cells = glider.collect::<Result<Vec<_>, _>>()?;
288        assert_eq!(cells, vec![(0, 0), (1, 0), (1, 2), (2, 0), (2, 1)]);
289        Ok(())
290    }
291}