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}