life_backend/format/rle/core.rs
1use anyhow::Result;
2use std::fmt;
3use std::io::Read;
4use std::str::FromStr;
5
6use super::{RleHeader, RleParser, RleRunsTriple};
7use crate::{Format, Position, Rule};
8
9/// A representation for RLE file format.
10///
11/// The detail of this format is described in:
12///
13/// - [Run Length Encoded - LifeWiki](https://conwaylife.com/wiki/Run_Length_Encoded)
14/// - [Golly Help: File Formats > Extended RLE format](https://golly.sourceforge.net/Help/formats.html#rle)
15///
16/// # Examples
17///
18/// Parses the given RLE file, and checks live cells included in it:
19///
20/// ```
21/// use std::fs::File;
22/// use life_backend::format::Rle;
23/// use life_backend::Position;
24/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
25/// let file = File::open("patterns/rpentomino.rle")?;
26/// let parser = Rle::new(file)?;
27/// assert!(parser.live_cells().eq([Position(1, 0), Position(2, 0), Position(0, 1), Position(1, 1), Position(1, 2)]));
28/// # Ok(())
29/// # }
30/// ```
31///
32/// Parses the given string in RLE format:
33///
34/// ```
35/// use life_backend::format::Rle;
36/// use life_backend::Position;
37/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
38/// let pattern = "\
39/// #N R-pentomino\n\
40/// x = 3, y = 3\n\
41/// b2o$2o$bo!\n\
42/// ";
43/// let parser = pattern.parse::<Rle>()?;
44/// assert!(parser.live_cells().eq([Position(1, 0), Position(2, 0), Position(0, 1), Position(1, 1), Position(1, 2)]));
45/// # Ok(())
46/// # }
47/// ```
48///
49#[derive(Clone, Debug)]
50pub struct Rle {
51 pub(super) header: RleHeader,
52 pub(super) comments: Vec<String>,
53 pub(super) contents: Vec<RleRunsTriple>,
54}
55
56// Inherent methods
57
58impl Rle {
59 /// Creates from the specified implementor of [`Read`], such as [`File`] or `&[u8]`.
60 ///
61 /// [`Read`]: std::io::Read
62 /// [`File`]: std::fs::File
63 ///
64 /// # Examples
65 ///
66 /// ```
67 /// use life_backend::format::Rle;
68 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
69 /// let pattern = "\
70 /// #N T-tetromino\n\
71 /// x = 3, y = 2\n\
72 /// 3o$bo!\n\
73 /// ";
74 /// let parser = Rle::new(pattern.as_bytes())?;
75 /// # Ok(())
76 /// # }
77 /// ```
78 ///
79 #[inline]
80 pub fn new<R>(read: R) -> Result<Self>
81 where
82 R: Read,
83 {
84 RleParser::parse(read)
85 }
86
87 /// Returns the width written in the pattern.
88 ///
89 /// # Examples
90 ///
91 /// ```
92 /// use life_backend::format::Rle;
93 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
94 /// let pattern = "\
95 /// #N T-tetromino\n\
96 /// x = 3, y = 2\n\
97 /// 3o$bo!\n\
98 /// ";
99 /// let parser = Rle::new(pattern.as_bytes())?;
100 /// assert_eq!(parser.width(), 3);
101 /// # Ok(())
102 /// # }
103 /// ```
104 ///
105 #[inline]
106 pub const fn width(&self) -> usize {
107 self.header.width
108 }
109
110 /// Returns the height written in the pattern.
111 ///
112 /// # Examples
113 ///
114 /// ```
115 /// use life_backend::format::Rle;
116 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
117 /// let pattern = "\
118 /// #N T-tetromino\n\
119 /// x = 3, y = 2\n\
120 /// 3o$bo!\n\
121 /// ";
122 /// let parser = Rle::new(pattern.as_bytes())?;
123 /// assert_eq!(parser.height(), 2);
124 /// # Ok(())
125 /// # }
126 /// ```
127 ///
128 #[inline]
129 pub const fn height(&self) -> usize {
130 self.header.height
131 }
132
133 /// Returns the rule.
134 ///
135 /// # Examples
136 ///
137 /// ```
138 /// use life_backend::format::Rle;
139 /// use life_backend::Rule;
140 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
141 /// let pattern = "\
142 /// #N T-tetromino\n\
143 /// x = 3, y = 2, rule = B3/S23\n\
144 /// 3o$bo!\n\
145 /// ";
146 /// let parser = Rle::new(pattern.as_bytes())?;
147 /// assert_eq!(parser.rule(), &Rule::conways_life());
148 /// # Ok(())
149 /// # }
150 /// ```
151 ///
152 #[inline]
153 pub const fn rule(&self) -> &Rule {
154 &self.header.rule
155 }
156
157 /// Returns comments of the pattern.
158 ///
159 /// # Examples
160 ///
161 /// ```
162 /// use life_backend::format::Rle;
163 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
164 /// let pattern = "\
165 /// #N T-tetromino\n\
166 /// x = 3, y = 2\n\
167 /// 3o$bo!\n\
168 /// ";
169 /// let parser = Rle::new(pattern.as_bytes())?;
170 /// assert_eq!(parser.comments().len(), 1);
171 /// assert_eq!(parser.comments()[0], "#N T-tetromino");
172 /// # Ok(())
173 /// # }
174 /// ```
175 ///
176 #[inline]
177 pub const fn comments(&self) -> &Vec<String> {
178 &self.comments
179 }
180
181 /// Creates an owning iterator over the series of live cell positions in ascending order.
182 ///
183 /// # Examples
184 ///
185 /// ```
186 /// use life_backend::format::Rle;
187 /// use life_backend::Position;
188 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
189 /// let pattern = "\
190 /// #N T-tetromino\n\
191 /// x = 3, y = 2\n\
192 /// 3o$bo!\n\
193 /// ";
194 /// let parser = Rle::new(pattern.as_bytes())?;
195 /// assert!(parser.live_cells().eq([Position(0, 0), Position(1, 0), Position(2, 0), Position(1, 1)]));
196 /// # Ok(())
197 /// # }
198 /// ```
199 ///
200 pub fn live_cells(&self) -> impl Iterator<Item = Position<usize>> + '_ {
201 self.contents
202 .iter()
203 .scan((0, 0), |(state_x, state_y), item| {
204 if item.pad_lines > 0 {
205 *state_y += item.pad_lines;
206 *state_x = 0;
207 }
208 if item.pad_dead_cells > 0 {
209 *state_x += item.pad_dead_cells;
210 }
211 let output = (*state_y, *state_x, item.live_cells);
212 *state_x += item.live_cells;
213 Some(output)
214 })
215 .flat_map(|(y, x, num)| (x..(x + num)).map(move |x| Position(x, y)))
216 }
217}
218
219// Trait implementations
220
221impl Format for Rle {
222 fn rule(&self) -> Rule {
223 self.rule().clone()
224 }
225 fn live_cells(&self) -> Box<dyn Iterator<Item = Position<usize>> + '_> {
226 Box::new(self.live_cells())
227 }
228}
229
230impl fmt::Display for Rle {
231 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
232 const MAX_LINE_WIDTH: usize = 70;
233 fn convert_run_to_string(run_count: usize, tag_char: char) -> String {
234 if run_count > 1 {
235 let mut buf = run_count.to_string();
236 buf.push(tag_char);
237 buf
238 } else {
239 tag_char.to_string()
240 }
241 }
242 fn flush_buf(f: &mut fmt::Formatter, buf: &mut String) -> Result<(), fmt::Error> {
243 writeln!(f, "{buf}")?;
244 Ok(())
245 }
246 fn write_with_buf(f: &mut fmt::Formatter, buf: &mut String, s: &str) -> Result<(), fmt::Error> {
247 if buf.len() + s.len() > MAX_LINE_WIDTH {
248 flush_buf(f, buf)?;
249 buf.clear();
250 }
251 *buf += s;
252 Ok(())
253 }
254 for line in self.comments() {
255 writeln!(f, "{line}")?;
256 }
257 writeln!(f, "x = {}, y = {}, rule = {}", self.width(), self.height(), self.rule())?;
258 let mut buf = String::new();
259 for x in &self.contents {
260 for (run_count, tag_char) in [(x.pad_lines, '$'), (x.pad_dead_cells, 'b'), (x.live_cells, 'o')] {
261 if run_count > 0 {
262 let s = convert_run_to_string(run_count, tag_char);
263 write_with_buf(f, &mut buf, &s)?;
264 }
265 }
266 }
267 write_with_buf(f, &mut buf, "!")?;
268 flush_buf(f, &mut buf)?;
269 Ok(())
270 }
271}
272
273impl FromStr for Rle {
274 type Err = anyhow::Error;
275 #[inline]
276 fn from_str(s: &str) -> Result<Self, Self::Err> {
277 Self::new(s.as_bytes())
278 }
279}