ca_rules/rules/
ntlife.rs

1//! Non-totalistic life-like rules.
2
3use super::{
4    life::{ParseLife, ParseLifeGen},
5    nthex::{ParseNtHex, ParseNtHexGen},
6    ntneumann::{ParseNtNeumann, ParseNtNeumannGen},
7    Gen,
8};
9use crate::ParseRuleError;
10
11rule_struct!(NtLife);
12
13impl NtLife {
14    parse_bs! {
15        '0' => {
16            'c' => [0x00],
17        },
18        '1' => {
19            'c' => [0x01, 0x04, 0x20, 0x80],
20            'e' => [0x02, 0x08, 0x10, 0x40],
21        },
22        '2' => {
23            'c' => [0x05, 0x21, 0x84, 0xa0],
24            'e' => [0x0a, 0x12, 0x48, 0x50],
25            'k' => [0x0c, 0x11, 0x22, 0x30, 0x41, 0x44, 0x82, 0x88],
26            'a' => [0x03, 0x06, 0x09, 0x14, 0x28, 0x60, 0x90, 0xc0],
27            'i' => [0x18, 0x42],
28            'n' => [0x24, 0x81],
29        },
30        '3' => {
31            'c' => [0x25, 0x85, 0xa1, 0xa4],
32            'e' => [0x1a, 0x4a, 0x52, 0x58],
33            'k' => [0x32, 0x4c, 0x51, 0x8a],
34            'a' => [0x0b, 0x16, 0x68, 0xd0],
35            'i' => [0x07, 0x29, 0x94, 0xe0],
36            'n' => [0x0d, 0x15, 0x23, 0x61, 0x86, 0xa8, 0xb0, 0xc4],
37            'y' => [0x31, 0x45, 0x8c, 0xa2],
38            'q' => [0x26, 0x2c, 0x34, 0x64, 0x83, 0x89, 0x91, 0xc1],
39            'j' => [0x0e, 0x13, 0x2a, 0x49, 0x54, 0x70, 0x92, 0xc8],
40            'r' => [0x19, 0x1c, 0x38, 0x43, 0x46, 0x62, 0x98, 0xc2],
41        },
42        '4' => {
43            'c' => [0xa5],
44            'e' => [0x5a],
45            'k' => [0x33, 0x4d, 0x55, 0x71, 0x8e, 0xaa, 0xb2, 0xcc],
46            'a' => [0x0f, 0x17, 0x2b, 0x69, 0x96, 0xd4, 0xe8, 0xf0],
47            'i' => [0x1d, 0x63, 0xb8, 0xc6],
48            'n' => [0x27, 0x2d, 0x87, 0x95, 0xa9, 0xb4, 0xe1, 0xe4],
49            'y' => [0x35, 0x65, 0x8d, 0xa3, 0xa6, 0xac, 0xb1, 0xc5],
50            'q' => [0x36, 0x6c, 0x8b, 0xd1],
51            'j' => [0x3a, 0x4e, 0x53, 0x59, 0x5c, 0x72, 0x9a, 0xca],
52            'r' => [0x1b, 0x1e, 0x4b, 0x56, 0x6a, 0x78, 0xd2, 0xd8],
53            't' => [0x39, 0x47, 0x9c, 0xe2],
54            'w' => [0x2e, 0x74, 0x93, 0xc9],
55            'z' => [0x3c, 0x66, 0x99, 0xc3],
56        },
57        '5' => {
58            'c' => [0x5b, 0x5e, 0x7a, 0xda],
59            'e' => [0xa7, 0xad, 0xb5, 0xe5],
60            'k' => [0x75, 0xae, 0xb3, 0xcd],
61            'a' => [0x2f, 0x97, 0xe9, 0xf4],
62            'i' => [0x1f, 0x6b, 0xd6, 0xf8],
63            'n' => [0x3b, 0x4f, 0x57, 0x79, 0x9e, 0xdc, 0xea, 0xf2],
64            'y' => [0x5d, 0x73, 0xba, 0xce],
65            'q' => [0x3e, 0x6e, 0x76, 0x7c, 0x9b, 0xcb, 0xd3, 0xd9],
66            'j' => [0x37, 0x6d, 0x8f, 0xab, 0xb6, 0xd5, 0xec, 0xf1],
67            'r' => [0x3d, 0x67, 0x9d, 0xb9, 0xbc, 0xc7, 0xe3, 0xe6],
68        },
69        '6' => {
70            'c' => [0x5f, 0x7b, 0xde, 0xfa],
71            'e' => [0xaf, 0xb7, 0xed, 0xf5],
72            'k' => [0x77, 0x7d, 0xbb, 0xbe, 0xcf, 0xdd, 0xee, 0xf3],
73            'a' => [0x3f, 0x6f, 0x9f, 0xd7, 0xeb, 0xf6, 0xf9, 0xfc],
74            'i' => [0xbd, 0xe7],
75            'n' => [0x7e, 0xdb],
76        },
77        '7' => {
78            'c' => [0x7f, 0xdf, 0xfb, 0xfe],
79            'e' => [0xbf, 0xef, 0xf7, 0xfd],
80        },
81        '8' => {
82            'c' => [0xff],
83        },
84    }
85    parse_rule!();
86    parse_rule_map!(8);
87}
88
89impl_parser!(
90    (ParseLife, ParseLifeGen) for NtLife,
91    |i: u8| i.count_ones() as u8,
92    0xff,
93);
94
95impl_parser!(
96    (ParseNtHex, ParseNtHexGen) for NtLife,
97    |i: u8| (i & 0xc0) >> 2 | (i & 0x18) >> 1 | (i & 0x03),
98    0xff,
99);
100
101impl_parser!(
102    (ParseNtNeumann, ParseNtNeumannGen) for NtLife,
103    |i: u8| (i & 0x40) >> 3 | (i & 0x18) >> 2 | (i & 0x02) >> 1,
104    0xff,
105);
106
107/// A trait for parsing [non-totalistic life-like rules](http://www.conwaylife.com/wiki/Non-totalistic_Life-like_cellular_automaton).
108/// Both [isotropic](http://www.conwaylife.com/wiki/Isotropic_non-totalistic_Life-like_cellular_automaton)
109/// and [non-isotropic](http://www.conwaylife.com/wiki/Non-isotropic_Life-like_cellular_automaton)
110/// rules are supported.
111///
112/// The `b` / `s` data of this type of rules consists of possible combinations of
113/// states of the 8 neighbors, represented by an 8-bit binary number,
114/// that cause a cell to be born / survive.
115///
116/// For example, the following neighborhood is represented by the number `42 = 0b00101010`:
117/// ```plaintext
118/// 0 0 1
119/// 0 _ 1
120/// 0 1 0
121/// ```
122///
123/// # Examples
124///
125/// ```
126/// use ca_rules::ParseNtLife;
127///
128/// #[derive(Debug, Eq, PartialEq)]
129/// struct Rule {
130///     b: Vec<u8>,
131///     s: Vec<u8>,
132/// }
133///
134/// impl ParseNtLife for Rule {
135///     fn from_bs(b: Vec<u8>, s: Vec<u8>) -> Self {
136///         Rule { b, s }
137///     }
138/// }
139///
140/// let life = Rule::parse_rule("B35y/S1e2-ci3-a5i").unwrap();
141///
142/// assert!(life.s.contains(&0x2a));
143/// ```
144pub trait ParseNtLife {
145    /// Construct the rule from `b` / `s` data.
146    fn from_bs(b: Vec<u8>, s: Vec<u8>) -> Self;
147
148    /// The parser.
149    fn parse_rule(input: &str) -> Result<Self, ParseRuleError>
150    where
151        Self: Sized,
152    {
153        let NtLife { b, s } = ParseLife::parse_rule(input)
154            .or_else(|_| NtLife::parse_rule(input))
155            .or_else(|e| ParseNtHex::parse_rule(input).map_err(|_| e))
156            .or_else(|e| ParseNtNeumann::parse_rule(input).map_err(|_| e))
157            .or_else(|e| {
158                NtLife::parse_rule_map(input).map_err(|e_map| {
159                    if e_map == ParseRuleError::NotMapRule {
160                        e
161                    } else {
162                        e_map
163                    }
164                })
165            })?;
166        Ok(Self::from_bs(b, s))
167    }
168}
169
170/// A trait for parsing [non-totalistic life-like](http://www.conwaylife.com/wiki/Non-totalistic_Life-like_cellular_automaton)
171/// [Generations](http://www.conwaylife.com/wiki/Generations) rules.
172/// Both [isotropic](http://www.conwaylife.com/wiki/Isotropic_non-totalistic_Life-like_cellular_automaton)
173/// and [non-isotropic](http://www.conwaylife.com/wiki/Non-isotropic_Life-like_cellular_automaton)
174/// rules are supported.
175///
176/// The `b` / `s` data of this type of rules consists of possible combinations of
177/// states of the 8 neighbors, represented by an 8-bit binary number,
178/// that cause a cell to be born / survive.
179///
180/// For example, the following neighborhood is represented by the number `42 = 0b00101010`:
181/// ```plaintext
182/// 0 0 1
183/// 0 _ 1
184/// 0 1 0
185/// ```
186///
187/// # Examples
188///
189/// ```
190/// use ca_rules::ParseNtLifeGen;
191///
192/// #[derive(Debug, Eq, PartialEq)]
193/// struct Rule {
194///     b: Vec<u8>,
195///     s: Vec<u8>,
196///     gen: usize,
197/// }
198///
199/// impl ParseNtLifeGen for Rule {
200///     fn from_bsg(b: Vec<u8>, s: Vec<u8>, gen: usize) -> Self {
201///         Rule { b, s, gen }
202///     }
203/// }
204///
205/// let life = Rule::parse_rule("g4b2c36k7s2ak34-a5-i").unwrap();
206///
207/// assert_eq!(life.gen, 4);
208/// ```
209pub trait ParseNtLifeGen {
210    /// Construct the rule from `b` / `s` data and the number of states.
211    fn from_bsg(b: Vec<u8>, s: Vec<u8>, gen: usize) -> Self;
212
213    /// The parser.
214    fn parse_rule(input: &str) -> Result<Self, ParseRuleError>
215    where
216        Self: Sized,
217    {
218        let Gen {
219            rule: NtLife { b, s },
220            gen,
221        } = ParseLifeGen::parse_rule(input)
222            .or_else(|_| NtLife::parse_rule_gen(input))
223            .or_else(|e| ParseNtHexGen::parse_rule(input).map_err(|_| e))
224            .or_else(|e| ParseNtNeumannGen::parse_rule(input).map_err(|_| e))
225            .or_else(|e| {
226                NtLife::parse_rule_gen_map(input).map_err(|e_map| {
227                    if e_map == ParseRuleError::NotMapRule {
228                        e
229                    } else {
230                        e_map
231                    }
232                })
233            })?;
234        Ok(Self::from_bsg(b, s, gen))
235    }
236}
237
238#[cfg(test)]
239mod tests {
240    use super::*;
241
242    struct Rule;
243
244    impl ParseNtLife for Rule {
245        fn from_bs(_b: Vec<u8>, _s: Vec<u8>) -> Self {
246            Rule
247        }
248    }
249
250    #[test]
251    fn valid_rules() -> Result<(), ParseRuleError> {
252        Rule::parse_rule("B3/S23")?;
253        Rule::parse_rule("B3/S23V")?;
254        Rule::parse_rule("B2e3-anq/S12-a3")?;
255        Rule::parse_rule("B35y/S1e2-ci3-a5i")?;
256        Rule::parse_rule("B2o3p4-o5/S2-p3p45H")?;
257        Rule::parse_rule("MAPFgFoF2gXgH5oF4B+gH4A6A")?;
258        Rule::parse_rule("B2i34cj6a7c8/S2-i3-a4ceit6in")?;
259        Rule::parse_rule("1e2cik3ejqry4anrwz5a6k/2c3aenq4aijryz5cikqr6ac8")?;
260        Rule::parse_rule("MAPARYXfhZofugWaH7oaIDogBZofuhogOiAaIDogIAAgAAWaH7oaIDogGiA6ICAAIAAaIDogIAAgACAAIAAAAAAAA")?;
261        Rule::parse_rule("MAPARYXfhZofugWaH7oaIDogBZofuhogOiAaIDogIAAgAAWaH7oaIDogGiA6ICAAIAAaIDogIAAgACAAIAAAAAAAA==")?;
262        Ok(())
263    }
264
265    #[test]
266    fn invalid_rules() {
267        assert_eq!(
268            Rule::parse_rule("12-a3/B2e3-anq").err(),
269            Some(ParseRuleError::ExtraJunk)
270        );
271        assert_eq!(
272            Rule::parse_rule("B35y/1e2-ci3-a5i").err(),
273            Some(ParseRuleError::Missing('S'))
274        );
275        assert_eq!(
276            Rule::parse_rule("B2i34cj6a7c82-i3-a4ceit6in").err(),
277            Some(ParseRuleError::Missing('S'))
278        );
279        assert_eq!(
280            Rule::parse_rule("B2c3aenq4aijryz5cikqrz6ac8/S1e2cik3ejqry4anrwz5a6k").err(),
281            Some(ParseRuleError::Missing('S'))
282        );
283        assert_eq!(
284            Rule::parse_rule("MAPARYXfhZofugWaH7oaIDogBZofuhogOiAaIDogIAAgAAWaH7oaIDogGiA6ICAAIAAaIDogIAAgACAAIA").err(),
285            Some(ParseRuleError::InvalidLength)
286        );
287        assert_eq!(
288            Rule::parse_rule("MAPARYXfhZofugWaH7oaIDogBZofuhogOiAaIDogIAAgAAWaH7oaIDogGiA6ICAAIAAaIDogIAAgACAAIAAAAAAAX").err(),
289            Some(ParseRuleError::Base64Error)
290        );
291    }
292
293    #[test]
294    fn parse_life_as_ntlife() -> Result<(), ParseRuleError> {
295        let rule: NtLife = ParseLife::parse_rule("B2/S23")?;
296        for b in 0..=0xff {
297            assert_eq!(rule.b.contains(&b), [2].contains(&b.count_ones()));
298        }
299
300        for s in 0..=0xff {
301            assert_eq!(rule.s.contains(&s), [2, 3].contains(&s.count_ones()));
302        }
303        Ok(())
304    }
305
306    #[test]
307    fn parse_hex_as_ntlife() -> Result<(), ParseRuleError> {
308        let rule: NtLife = ParseNtHex::parse_rule("B2/S34H")?;
309        for b in 0..=0xff {
310            assert_eq!(
311                rule.b.contains(&b),
312                [2].contains(&(b & 0b1101_1011).count_ones())
313            );
314        }
315
316        for s in 0..=0xff {
317            assert_eq!(
318                rule.s.contains(&s),
319                [3, 4].contains(&(s & 0b1101_1011).count_ones())
320            );
321        }
322        Ok(())
323    }
324
325    #[test]
326    fn parse_neumann_as_ntlife() -> Result<(), ParseRuleError> {
327        let rule: NtLife = ParseNtNeumann::parse_rule("B2/S013V")?;
328        for b in 0..=0xff {
329            assert_eq!(
330                rule.b.contains(&b),
331                [2].contains(&(b & 0b0101_1010).count_ones())
332            );
333        }
334
335        for s in 0..=0xff {
336            assert_eq!(
337                rule.s.contains(&s),
338                [0, 1, 3].contains(&(s & 0b0101_1010).count_ones())
339            );
340        }
341        Ok(())
342    }
343
344    #[test]
345    fn parse_map() -> Result<(), ParseRuleError> {
346        let rule1: NtLife = NtLife::parse_rule("B3/S23")?;
347        let rule2: NtLife = NtLife::parse_rule_map("MAPARYXfhZofugWaH7oaIDogBZofuhogOiAaIDogIAAgAAWaH7oaIDogGiA6ICAAIAAaIDogIAAgACAAIAAAAAAAA")?;
348        assert_eq!(rule1, rule2);
349        Ok(())
350    }
351
352    #[test]
353    fn parse_gen_map() -> Result<(), ParseRuleError> {
354        let rule1: Gen<NtLife> = NtLife::parse_rule_gen("3457/357/5")?;
355        let rule2: Gen<NtLife> = NtLife::parse_rule_gen_map("MAPARYBFxZpF38WaRd/aZZ//hZpF39pln/+aZZ//pZp/ukWaRd/aZZ//mmWf/6Waf7paZZ//pZp/umWaf7paZbplg/5")?;
356        assert_eq!(rule1, rule2);
357        Ok(())
358    }
359}