ca_rules/rules/
ntneumann.rs

1//! Non-totalistic rules with von Neumann neighborhood.
2
3use super::{
4    neumann::{ParseNeumann, ParseNeumannGen},
5    Gen,
6};
7use crate::ParseRuleError;
8
9rule_struct!(NtNeumann);
10
11impl NtNeumann {
12    parse_rule_map!(4);
13}
14
15impl_parser!(
16    (ParseNeumann, ParseNeumannGen) for NtNeumann,
17    |i: u8| i.count_ones() as u8,
18    0x0f,
19);
20
21/// A trait for parsing non-totalistic rules with
22/// [von Neumann neighborhood](http://www.conwaylife.com/wiki/Von_Neumann_neighbourhood).
23/// Both [isotropic](http://www.conwaylife.com/wiki/Isotropic_non-totalistic_Life-like_cellular_automaton)
24/// and [non-isotropic](http://www.conwaylife.com/wiki/Non-isotropic_Life-like_cellular_automaton)
25/// rules are supported.
26///
27/// The `b` / `s` data of this type of rules consists of possible combinations of
28/// states of the 4 neighbors, represented by an 8-bit binary number,
29/// that cause a cell to be born / survive.
30///
31/// For example, the following neighborhood is represented by the number `10 = 0b1010`:
32/// ```plaintext
33///   1
34/// 0 _ 1
35///   0
36/// ```
37///
38/// # Examples
39///
40/// ```
41/// use ca_rules::ParseNtNeumann;
42///
43/// #[derive(Debug, Eq, PartialEq)]
44/// struct Rule {
45///     b: Vec<u8>,
46///     s: Vec<u8>,
47/// }
48///
49/// impl ParseNtNeumann for Rule {
50///     fn from_bs(b: Vec<u8>, s: Vec<u8>) -> Self {
51///         Rule { b, s }
52///     }
53/// }
54///
55/// let life = Rule::parse_rule("MAPHmlphg").unwrap();
56///
57/// assert!(life.s.contains(&0x00));
58/// ```
59pub trait ParseNtNeumann {
60    /// Construct the rule from `b` / `s` data.
61    fn from_bs(b: Vec<u8>, s: Vec<u8>) -> Self;
62
63    /// The parser.
64    fn parse_rule(input: &str) -> Result<Self, ParseRuleError>
65    where
66        Self: Sized,
67    {
68        let NtNeumann { b, s } = ParseNeumann::parse_rule(input).or_else(|e| {
69            NtNeumann::parse_rule_map(input).map_err(|e_map| {
70                if e_map == ParseRuleError::NotMapRule {
71                    e
72                } else {
73                    e_map
74                }
75            })
76        })?;
77        Ok(Self::from_bs(b, s))
78    }
79}
80
81/// A trait for parsing non-totalistic [Generations](http://www.conwaylife.com/wiki/Generations)
82/// rules with [von Neumann neighborhood](http://www.conwaylife.com/wiki/Von_Neumann_neighbourhood).
83/// Both [isotropic](http://www.conwaylife.com/wiki/Isotropic_non-totalistic_Life-like_cellular_automaton)
84/// and [non-isotropic](http://www.conwaylife.com/wiki/Non-isotropic_Life-like_cellular_automaton)
85/// rules are supported.
86///
87/// The `b` / `s` data of this type of rules consists of possible combinations of
88/// states of the 4 neighbors, represented by an 8-bit binary number,
89/// that cause a cell to be born / survive.
90///
91/// For example, the following neighborhood is represented by the number `10 = 0b1010`:
92/// ```plaintext
93///   1
94/// 0 _ 1
95///   0
96/// ```
97///
98/// # Examples
99///
100/// ```
101/// use ca_rules::ParseNtNeumannGen;
102///
103/// #[derive(Debug, Eq, PartialEq)]
104/// struct Rule {
105///     b: Vec<u8>,
106///     s: Vec<u8>,
107///     gen: usize,
108/// }
109///
110/// impl ParseNtNeumannGen for Rule {
111///     fn from_bsg(b: Vec<u8>, s: Vec<u8>, gen: usize) -> Self {
112///         Rule { b, s, gen }
113///     }
114/// }
115///
116/// let life = Rule::parse_rule("MAPHmlphg/3").unwrap();
117///
118/// assert_eq!(life.gen, 3);
119/// ```
120pub trait ParseNtNeumannGen {
121    /// Construct the rule from `b` / `s` data and the number of states.
122    fn from_bsg(b: Vec<u8>, s: Vec<u8>, gen: usize) -> Self;
123
124    /// The parser.
125    fn parse_rule(input: &str) -> Result<Self, ParseRuleError>
126    where
127        Self: Sized,
128    {
129        let Gen {
130            rule: NtNeumann { b, s },
131            gen,
132        } = ParseNeumannGen::parse_rule(input).or_else(|e| {
133            NtNeumann::parse_rule_gen_map(input).map_err(|e_map| {
134                if e_map == ParseRuleError::NotMapRule {
135                    e
136                } else {
137                    e_map
138                }
139            })
140        })?;
141        Ok(Self::from_bsg(b, s, gen))
142    }
143}
144
145#[cfg(test)]
146mod tests {
147    use super::*;
148
149    struct Rule;
150
151    impl ParseNtNeumann for Rule {
152        fn from_bs(_b: Vec<u8>, _s: Vec<u8>) -> Self {
153            Rule
154        }
155    }
156
157    #[test]
158    fn valid_rules() -> Result<(), ParseRuleError> {
159        Rule::parse_rule("B3/S23V")?;
160        Rule::parse_rule("B3S23V")?;
161        Rule::parse_rule("b3s23v")?;
162        Rule::parse_rule("23/3V")?;
163        Rule::parse_rule("23/v")?;
164        Rule::parse_rule("MAPHmlphg")?;
165        Ok(())
166    }
167
168    #[test]
169    fn invalid_rules() {
170        assert_eq!(
171            Rule::parse_rule("B3/S23va").err(),
172            Some(ParseRuleError::ExtraJunk)
173        );
174        assert_eq!(
175            Rule::parse_rule("B3V/S23").err(),
176            Some(ParseRuleError::Missing('S'))
177        );
178        assert_eq!(
179            Rule::parse_rule("B3/S23").err(),
180            Some(ParseRuleError::Missing('V'))
181        );
182        assert_eq!(
183            Rule::parse_rule("B3/S25V").err(),
184            Some(ParseRuleError::Missing('V'))
185        );
186        assert_eq!(
187            Rule::parse_rule("233v").err(),
188            Some(ParseRuleError::Missing('/'))
189        );
190        assert_eq!(
191            Rule::parse_rule("MAPFgFoF2gXgH5oF4B+gH4A6A").err(),
192            Some(ParseRuleError::InvalidLength)
193        );
194    }
195
196    #[test]
197    fn parse_neumann_as_ntneumann() -> Result<(), ParseRuleError> {
198        let rule: NtNeumann = ParseNeumann::parse_rule("B2/S013V")?;
199        for b in 0..=0x0f {
200            assert_eq!(rule.b.contains(&b), [2].contains(&b.count_ones()));
201        }
202
203        for s in 0..=0x0f {
204            assert_eq!(rule.s.contains(&s), [0, 1, 3].contains(&s.count_ones()));
205        }
206        Ok(())
207    }
208
209    #[test]
210    fn parse_map() -> Result<(), ParseRuleError> {
211        let rule1: NtNeumann = NtNeumann::parse_rule("B2/S013V")?;
212        let rule2: NtNeumann = NtNeumann::parse_rule_map("MAPHmlphg")?;
213        assert_eq!(rule1, rule2);
214        Ok(())
215    }
216}