1use crate::{CellData, Coordinates, Input};
7use lazy_regex::regex;
8use std::io::{BufReader, Error as IoError, Read};
9use thiserror::Error;
10
11#[derive(Debug, Error)]
13pub enum Error {
14 #[error("Invalid state: {0}.")]
15 InvalidState(String),
16 #[error("Invalid \"#CXRLE\" line: {0}.")]
17 InvalidCxrleLine(String),
18 #[error("Invalid header line: {0}.")]
19 InvalidHeaderLine(String),
20 #[error("Error when reading from input: {0}.")]
21 IoError(#[from] IoError),
22}
23
24#[derive(Clone, Debug, Eq, PartialEq, Default, Hash)]
26pub struct CxrleData {
27 pub pos: Option<Coordinates>,
29 pub gen: Option<u64>,
31}
32
33fn parse_cxrle(line: &str) -> Option<CxrleData> {
35 let re = regex!(r"(?:Pos\s*=\s*(?P<x>-?\d+),\s*(?P<y>-?\d+))|(?:Gen\s*=\s*(?P<gen>\d+))");
36 let mut data = CxrleData::default();
37 for cap in re.captures_iter(line) {
38 if let Some(gen) = cap.name("gen") {
39 data.gen = Some(gen.as_str().parse().ok())?;
40 } else {
41 let x = cap["x"].parse().ok()?;
42 let y = cap["y"].parse().ok()?;
43 data.pos = Some((x, y));
44 }
45 }
46 Some(data)
47}
48
49#[derive(Clone, Debug, Eq, PartialEq, Default, Hash)]
51pub struct HeaderData {
52 pub x: u64,
54 pub y: u64,
56 pub rule: Option<String>,
58}
59
60fn parse_header(line: &str) -> Option<HeaderData> {
62 let re =
63 regex!(r"^x\s*=\s*(?P<x>\d+),\s*y\s*=\s*(?P<y>\d+)(?:,\s*rule\s*=\s*(?P<rule>.*\S)\s*)?$");
64 let mut data = HeaderData::default();
65 let cap = re.captures(line)?;
66 data.x = cap["x"].parse().ok()?;
67 data.y = cap["y"].parse().ok()?;
68 if let Some(rule) = cap.name("rule") {
69 data.rule = Some(rule.as_str().to_owned());
70 }
71 Some(data)
72}
73
74#[must_use]
116#[derive(Debug)]
117pub struct Rle<I: Input> {
118 cxrle_data: Option<CxrleData>,
120
121 header_data: Option<HeaderData>,
123
124 lines: I::Lines,
126
127 current_line: Option<I::Bytes>,
129
130 position: Coordinates,
132
133 x_start: i64,
135
136 run_count: i64,
138
139 alive_count: i64,
141
142 state: u8,
144
145 state_prefix: Option<u8>,
147
148 #[cfg(feature = "unknown")]
150 unknown: bool,
151}
152
153impl<I: Input> Rle<I> {
154 pub fn new(input: I) -> Result<Self, Error> {
158 let mut lines = input.lines();
159 let mut cxrle_data = None;
160 let mut header_data = None;
161 let mut current_line = None;
162 let mut position = (0, 0);
163 let mut x_start = 0;
164 for item in &mut lines {
165 let line = I::line(item)?;
166 if line.as_ref().starts_with("#CXRLE") {
167 cxrle_data.replace(
168 parse_cxrle(line.as_ref())
169 .ok_or_else(|| Error::InvalidCxrleLine(line.as_ref().to_string()))?,
170 );
171 } else if line.as_ref().starts_with("x ") || line.as_ref().starts_with("x=") {
172 header_data.replace(
173 parse_header(line.as_ref())
174 .ok_or_else(|| Error::InvalidHeaderLine(line.as_ref().to_string()))?,
175 );
176 } else if !line.as_ref().starts_with('#') {
177 current_line = Some(I::bytes(line));
178 break;
179 }
180 }
181 if let Some(CxrleData { pos: Some(pos), .. }) = cxrle_data {
182 position = pos;
183 x_start = pos.0;
184 }
185 Ok(Self {
186 cxrle_data,
187 header_data,
188 lines,
189 current_line,
190 position,
191 x_start,
192 run_count: 0,
193 alive_count: 0,
194 state: 1,
195 state_prefix: None,
196 #[cfg(feature = "unknown")]
197 unknown: false,
198 })
199 }
200
201 pub const fn cxrle_data(&self) -> Option<&CxrleData> {
203 self.cxrle_data.as_ref()
204 }
205
206 pub const fn header_data(&self) -> Option<&HeaderData> {
208 self.header_data.as_ref()
209 }
210
211 #[cfg(feature = "unknown")]
218 #[cfg_attr(docs_rs, doc(cfg(feature = "unknown")))]
219 pub fn with_unknown(mut self) -> Self {
220 self.unknown = true;
221 self
222 }
223}
224
225impl<I, L> Rle<I>
226where
227 I: Input<Lines = L>,
228 L: Input,
229{
230 pub fn remains(self) -> Result<Rle<L>, Error> {
232 Rle::new(self.lines)
233 }
234
235 pub fn try_remains(self) -> Result<Option<Rle<L>>, Error> {
240 let rle = Rle::new(self.lines)?;
241 Ok(if rle.current_line.is_some() {
242 Some(rle)
243 } else {
244 None
245 })
246 }
247}
248
249impl<R: Read> Rle<BufReader<R>> {
250 pub fn new_from_file(file: R) -> Result<Self, Error> {
253 Self::new(BufReader::new(file))
254 }
255}
256
257impl<I: Input> Clone for Rle<I>
258where
259 I::Lines: Clone,
260 I::Bytes: Clone,
261{
262 fn clone(&self) -> Self {
263 Self {
264 cxrle_data: self.cxrle_data.clone(),
265 header_data: self.header_data.clone(),
266 lines: self.lines.clone(),
267 current_line: self.current_line.clone(),
268 position: self.position,
269 x_start: self.x_start,
270 run_count: self.run_count,
271 alive_count: self.alive_count,
272 state: self.state,
273 state_prefix: self.state_prefix,
274 #[cfg(feature = "unknown")]
275 unknown: self.unknown,
276 }
277 }
278}
279
280impl<I: Input> Iterator for Rle<I> {
282 type Item = Result<CellData, Error>;
283
284 fn next(&mut self) -> Option<Self::Item> {
285 if self.alive_count > 0 {
286 self.alive_count -= 1;
287 let cell = CellData {
288 position: self.position,
289 state: self.state,
290 };
291 self.position.0 += 1;
292 return Some(Ok(cell));
293 }
294 loop {
295 if let Some(c) = self.current_line.as_mut().and_then(Iterator::next) {
296 if c.is_ascii_digit() {
297 self.run_count = 10 * self.run_count + (c - b'0') as i64
298 } else if !c.is_ascii_whitespace() {
299 if self.run_count == 0 {
300 self.run_count = 1;
301 }
302 if self.state_prefix.is_some() && !(b'A'..=b'X').contains(&c) {
303 let mut state_string = char::from(self.state_prefix.unwrap()).to_string();
304 state_string.push(char::from(c));
305 return Some(Err(Error::InvalidState(state_string)));
306 }
307 match c {
308 #[cfg(feature = "unknown")]
309 b'?' if self.unknown => {
310 self.position.0 += self.run_count;
311 self.run_count = 0;
312 }
313 #[cfg(feature = "unknown")]
314 b'b' | b'.' | b'o' | b'A'..=b'X' if self.unknown => {
315 match c {
316 b'b' | b'.' => self.state = 0,
317 b'o' => self.state = 1,
318 _ => {
319 self.state =
320 24 * (self.state_prefix.take().unwrap_or(b'o') - b'o');
321 self.state += c + 1 - b'A';
322 }
323 }
324 self.alive_count = self.run_count - 1;
325 self.run_count = 0;
326 let cell = CellData {
327 position: self.position,
328 state: self.state,
329 };
330 self.position.0 += 1;
331 return Some(Ok(cell));
332 }
333 b'b' | b'.' => {
334 self.position.0 += self.run_count;
335 self.run_count = 0;
336 }
337 b'o' | b'A'..=b'X' => {
338 if c == b'o' {
339 self.state = 1;
340 } else {
341 self.state = 24 * (self.state_prefix.take().unwrap_or(b'o') - b'o');
342 self.state += c + 1 - b'A';
343 }
344 self.alive_count = self.run_count - 1;
345 self.run_count = 0;
346 let cell = CellData {
347 position: self.position,
348 state: self.state,
349 };
350 self.position.0 += 1;
351 return Some(Ok(cell));
352 }
353 b'p'..=b'y' => {
354 self.state_prefix = Some(c);
355 }
356 b'$' => {
357 self.position.0 = self.x_start;
358 self.position.1 += self.run_count;
359 self.run_count = 0;
360 }
361 b'!' => {
362 self.current_line = None;
363 return None;
364 }
365 _ => return Some(Err(Error::InvalidState(char::from(c).to_string()))),
366 }
367 }
368 } else if let Some(item) = self.lines.next() {
369 match I::line(item) {
370 Ok(line) => {
371 if line.as_ref().starts_with('#')
372 | line.as_ref().starts_with("x ")
373 | line.as_ref().starts_with("x=")
374 {
375 continue;
376 } else {
377 self.current_line = Some(I::bytes(line));
378 }
379 }
380 Err(e) => {
381 return Some(Err(Error::IoError(e)));
382 }
383 }
384 } else {
385 return None;
386 }
387 }
388 }
389}
390
391#[cfg(test)]
392mod tests {
393 use super::*;
394
395 #[test]
396 fn rle_parse_cxrle() {
397 assert_eq!(
398 parse_cxrle("#CXRLE"),
399 Some(CxrleData {
400 pos: None,
401 gen: None
402 })
403 );
404 assert_eq!(
405 parse_cxrle("#CXRLE Pos=0,-1377 Gen=3480106827776"),
406 Some(CxrleData {
407 pos: Some((0, -1377)),
408 gen: Some(3480106827776)
409 })
410 );
411 assert_eq!(
412 parse_cxrle("#CXRLE Gen = 3480106827776 Pos = 0, -1377"),
413 Some(CxrleData {
414 pos: Some((0, -1377)),
415 gen: Some(3480106827776)
416 })
417 );
418 assert_eq!(
419 parse_cxrle("#CXRLE211Pos=0,-9dcdcs2,[a ccGen=348sss1068cscPos= -333,-1a6"),
420 Some(CxrleData {
421 pos: Some((-333, -1)),
422 gen: Some(348)
423 })
424 );
425 }
426
427 #[test]
428 fn rle_parse_header() {
429 assert_eq!(parse_header("xxx"), None);
430 assert_eq!(
431 parse_header("x = 3, y = 3, rule = B3/S23"),
432 Some(HeaderData {
433 x: 3,
434 y: 3,
435 rule: Some(String::from("B3/S23"))
436 })
437 );
438 assert_eq!(
439 parse_header("x = 3, y = 3"),
440 Some(HeaderData {
441 x: 3,
442 y: 3,
443 rule: None
444 })
445 );
446 assert_eq!(parse_header("x = 3, y = -3"), None);
447 assert_eq!(
448 parse_header("x = 3, y = 3, rule = Conway's Game of Life "),
449 Some(HeaderData {
450 x: 3,
451 y: 3,
452 rule: Some(String::from("Conway's Game of Life"))
453 })
454 );
455 }
456
457 #[test]
458 fn rle_glider() -> Result<(), Error> {
459 const GLIDER: &str = r"#N Glider
460#O Richard K. Guy
461#C The smallest, most common, and first discovered spaceship. Diagonal, has period 4 and speed c/4.
462#C www.conwaylife.com/wiki/index.php?title=Glider
463x = 3, y = 3, rule = B3/S23
464bob$2bo$3o!";
465
466 let glider = Rle::new(GLIDER)?;
467
468 let _ = glider.clone();
469
470 assert_eq!(glider.cxrle_data, None);
471 assert_eq!(
472 glider.header_data,
473 Some(HeaderData {
474 x: 3,
475 y: 3,
476 rule: Some(String::from("B3/S23"))
477 })
478 );
479
480 let cells = glider
481 .map(|res| res.map(|c| c.position))
482 .collect::<Result<Vec<_>, _>>()?;
483 assert_eq!(cells, vec![(1, 0), (2, 1), (0, 2), (1, 2), (2, 2)]);
484 Ok(())
485 }
486
487 #[test]
488 fn rle_glider_cxrle() -> Result<(), Error> {
489 const GLIDER: &str = r"#CXRLE Pos=-1,-1
490x = 3, y = 3, rule = B3/S23
491bo$2bo$3o!";
492
493 let glider = Rle::new(GLIDER)?;
494
495 assert_eq!(
496 glider.cxrle_data,
497 Some(CxrleData {
498 pos: Some((-1, -1)),
499 gen: None
500 })
501 );
502 assert_eq!(
503 glider.header_data,
504 Some(HeaderData {
505 x: 3,
506 y: 3,
507 rule: Some(String::from("B3/S23"))
508 })
509 );
510
511 let cells = glider
512 .map(|res| res.map(|c| c.position))
513 .collect::<Result<Vec<_>, _>>()?;
514 assert_eq!(cells, vec![(0, -1), (1, 0), (-1, 1), (0, 1), (1, 1)]);
515 Ok(())
516 }
517
518 #[test]
519 fn rle_generations() -> Result<(), Error> {
520 const OSCILLATOR: &str = r"x = 3, y = 3, rule = 3457/357/5
5213A$B2A$.CD!";
522
523 let oscillator = Rle::new(OSCILLATOR)?;
524
525 assert_eq!(oscillator.cxrle_data, None);
526 assert_eq!(
527 oscillator.header_data,
528 Some(HeaderData {
529 x: 3,
530 y: 3,
531 rule: Some(String::from("3457/357/5"))
532 })
533 );
534
535 let cells = oscillator.collect::<Result<Vec<_>, _>>()?;
536 assert_eq!(
537 cells,
538 vec![
539 CellData {
540 position: (0, 0),
541 state: 1,
542 },
543 CellData {
544 position: (1, 0),
545 state: 1,
546 },
547 CellData {
548 position: (2, 0),
549 state: 1,
550 },
551 CellData {
552 position: (0, 1),
553 state: 2,
554 },
555 CellData {
556 position: (1, 1),
557 state: 1,
558 },
559 CellData {
560 position: (2, 1),
561 state: 1,
562 },
563 CellData {
564 position: (1, 2),
565 state: 3,
566 },
567 CellData {
568 position: (2, 2),
569 state: 4,
570 },
571 ]
572 );
573 Ok(())
574 }
575
576 #[test]
577 fn rle_generations_256() -> Result<(), Error> {
578 const OSCILLATOR: &str = r"x = 3, y = 3, rule = 23/3/256
579.AwH$vIxNrQ$2pU!";
580
581 let oscillator = Rle::new(OSCILLATOR)?;
582
583 assert_eq!(oscillator.cxrle_data, None);
584 assert_eq!(
585 oscillator.header_data,
586 Some(HeaderData {
587 x: 3,
588 y: 3,
589 rule: Some(String::from("23/3/256"))
590 })
591 );
592
593 let cells = oscillator.collect::<Result<Vec<_>, _>>()?;
594 assert_eq!(
595 cells,
596 vec![
597 CellData {
598 position: (1, 0),
599 state: 1,
600 },
601 CellData {
602 position: (2, 0),
603 state: 200,
604 },
605 CellData {
606 position: (0, 1),
607 state: 177,
608 },
609 CellData {
610 position: (1, 1),
611 state: 230,
612 },
613 CellData {
614 position: (2, 1),
615 state: 89,
616 },
617 CellData {
618 position: (0, 2),
619 state: 45,
620 },
621 CellData {
622 position: (1, 2),
623 state: 45,
624 },
625 ]
626 );
627 Ok(())
628 }
629
630 #[test]
631 fn rle_two_rles() -> Result<(), Error> {
632 const GLIDER: &str = r"#N Glider
633#O Richard K. Guy
634#C The smallest, most common, and first discovered spaceship. Diagonal, has period 4 and speed c/4.
635#C www.conwaylife.com/wiki/index.php?title=Glider
636x = 3, y = 3, rule = B3/S23
637bob$2bo$3o!
638#N Glider
639#O Richard K. Guy
640#C The smallest, most common, and first discovered spaceship. Diagonal, has period 4 and speed c/4.
641#C www.conwaylife.com/wiki/index.php?title=Glider
642x = 3, y = 3, rule = B3/S23
643bob$2bo$3o!";
644
645 let mut first_rle = Rle::new(GLIDER)?;
646
647 assert_eq!(first_rle.cxrle_data, None);
648 assert_eq!(
649 first_rle.header_data,
650 Some(HeaderData {
651 x: 3,
652 y: 3,
653 rule: Some(String::from("B3/S23"))
654 })
655 );
656
657 let mut cells = Vec::new();
658 for c in &mut first_rle {
659 cells.push(c?.position);
660 }
661 assert_eq!(cells, vec![(1, 0), (2, 1), (0, 2), (1, 2), (2, 2)]);
662
663 let mut second_rle = first_rle.remains()?;
664 cells.clear();
665
666 for c in &mut second_rle {
667 cells.push(c?.position);
668 }
669
670 let third_rle = second_rle.try_remains()?;
671 assert!(third_rle.is_none());
672 Ok(())
673 }
674
675 #[test]
676 #[cfg(feature = "unknown")]
677 fn rle_glider_with_unknown() -> Result<(), Error> {
678 const GLIDER: &str = r"#CXRLE Pos=-1,-1
679x = 3, y = 3, rule = B3/S23
6805?$?bob?$?2bo?$?3o?$5?!";
681
682 let glider = Rle::new(GLIDER)?.with_unknown();
683
684 assert_eq!(
685 glider.cxrle_data(),
686 Some(&CxrleData {
687 pos: Some((-1, -1)),
688 gen: None
689 })
690 );
691 assert_eq!(
692 glider.header_data(),
693 Some(&HeaderData {
694 x: 3,
695 y: 3,
696 rule: Some(String::from("B3/S23"))
697 })
698 );
699
700 let cells = glider.collect::<Result<Vec<_>, _>>()?;
701 assert_eq!(
702 cells,
703 vec![
704 CellData {
705 position: (0, 0),
706 state: 0,
707 },
708 CellData {
709 position: (1, 0),
710 state: 1,
711 },
712 CellData {
713 position: (2, 0),
714 state: 0,
715 },
716 CellData {
717 position: (0, 1),
718 state: 0,
719 },
720 CellData {
721 position: (1, 1),
722 state: 0,
723 },
724 CellData {
725 position: (2, 1),
726 state: 1,
727 },
728 CellData {
729 position: (0, 2),
730 state: 1,
731 },
732 CellData {
733 position: (1, 2),
734 state: 1,
735 },
736 CellData {
737 position: (2, 2),
738 state: 1,
739 },
740 ]
741 );
742 Ok(())
743 }
744}