use crate::{CellData, Coordinates, Input};
use lazy_static::lazy_static;
use regex::Regex;
use std::io::{BufReader, Error as IoError, Read};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum Error {
#[error("Invalid state: {0}.")]
InvalidState(String),
#[error("Invalid \"#CXRLE\" line: {0}.")]
InvalidCXRLELine(String),
#[error("Invalid header line: {0}.")]
InvalidHeaderLine(String),
#[error("Error when reading from input: {0}.")]
IoError(IoError),
}
#[derive(Clone, Debug, Eq, PartialEq, Default, Hash)]
pub struct CxrleData {
pub pos: Option<Coordinates>,
pub gen: Option<u64>,
}
fn parse_cxrle(line: &str) -> Option<CxrleData> {
lazy_static! {
static ref RE: Regex =
Regex::new(r"(?:Pos\s*=\s*(?P<x>-?\d+),\s*(?P<y>-?\d+))|(?:Gen\s*=\s*(?P<gen>\d+))")
.unwrap();
}
let mut data = CxrleData::default();
for cap in RE.captures_iter(line) {
if let Some(gen) = cap.name("gen") {
data.gen = Some(gen.as_str().parse().ok())?;
} else {
let x = cap["x"].parse().ok()?;
let y = cap["y"].parse().ok()?;
data.pos = Some((x, y));
}
}
Some(data)
}
#[derive(Clone, Debug, Eq, PartialEq, Default, Hash)]
pub struct HeaderData {
pub x: u64,
pub y: u64,
pub rule: Option<String>,
}
fn parse_header(line: &str) -> Option<HeaderData> {
let re =
Regex::new(r"^x\s*=\s*(?P<x>\d+),\s*y\s*=\s*(?P<y>\d+)(?:,\s*rule\s*=\s*(?P<rule>\S+))?")
.unwrap();
let mut data = HeaderData::default();
let cap = re.captures(line)?;
data.x = cap["x"].parse().ok()?;
data.y = cap["y"].parse().ok()?;
if let Some(rule) = cap.name("rule") {
data.rule = Some(rule.as_str().to_owned());
}
Some(data)
}
#[derive(Clone, Debug)]
pub struct Rle<I: Input> {
cxrle_data: Option<CxrleData>,
header_data: Option<HeaderData>,
lines: I::Lines,
current_line: Option<I::Bytes>,
position: Coordinates,
x_start: i64,
run_count: i64,
alive_count: i64,
state: u8,
state_prefix: Option<u8>,
}
impl<I: Input> Rle<I> {
pub fn new(input: I) -> Result<Self, Error> {
let mut lines = input.lines();
let mut cxrle_data = None;
let mut header_data = None;
let mut current_line = None;
let mut position = (0, 0);
let mut x_start = 0;
while let Some(item) = lines.next() {
let line = I::line(item).map_err(Error::IoError)?;
if line.as_ref().starts_with("#CXRLE") {
cxrle_data.replace(
parse_cxrle(line.as_ref())
.ok_or(Error::InvalidCXRLELine(line.as_ref().to_string()))?,
);
} else if line.as_ref().starts_with("x ") || line.as_ref().starts_with("x=") {
header_data.replace(
parse_header(line.as_ref())
.ok_or(Error::InvalidHeaderLine(line.as_ref().to_string()))?,
);
} else if !line.as_ref().starts_with('#') {
current_line = Some(I::bytes(line));
break;
}
}
if let Some(CxrleData { pos: Some(pos), .. }) = cxrle_data {
position = pos;
x_start = pos.0;
}
Ok(Rle {
cxrle_data,
header_data,
lines,
current_line,
position,
x_start,
run_count: 0,
alive_count: 0,
state: 1,
state_prefix: None,
})
}
pub fn cxrle_data(&self) -> Option<&CxrleData> {
self.cxrle_data.as_ref()
}
pub fn header_data(&self) -> Option<&HeaderData> {
self.header_data.as_ref()
}
}
impl<R: Read> Rle<BufReader<R>> {
pub fn new_from_file(file: R) -> Result<Self, Error> {
Self::new(BufReader::new(file))
}
}
impl<I: Input> Iterator for Rle<I> {
type Item = Result<CellData, Error>;
fn next(&mut self) -> Option<Self::Item> {
if self.alive_count > 0 {
self.alive_count -= 1;
let cell = CellData {
position: self.position,
state: self.state,
};
self.position.0 += 1;
return Some(Ok(cell));
}
loop {
if let Some(c) = self.current_line.as_mut().and_then(|i| i.next()) {
if c.is_ascii_digit() {
self.run_count = 10 * self.run_count + (c - b'0') as i64
} else if !c.is_ascii_whitespace() {
if self.run_count == 0 {
self.run_count = 1;
}
if self.state_prefix.is_some() && (c < b'A' || c > b'X') {
let mut state_string = char::from(self.state_prefix.unwrap()).to_string();
state_string.push(char::from(c));
return Some(Err(Error::InvalidState(state_string)));
}
match c {
b'b' | b'.' => {
self.position.0 += self.run_count;
self.run_count = 0;
}
b'o' | b'A'..=b'X' => {
if c == b'o' {
self.state = 1;
} else {
self.state = 24 * (self.state_prefix.take().unwrap_or(b'o') - b'o');
self.state += c + 1 - b'A';
}
self.alive_count = self.run_count - 1;
self.run_count = 0;
let cell = CellData {
position: self.position,
state: self.state,
};
self.position.0 += 1;
return Some(Ok(cell));
}
b'p'..=b'y' => {
self.state_prefix = Some(c);
}
b'$' => {
self.position.0 = self.x_start;
self.position.1 += self.run_count;
self.run_count = 0;
}
b'!' => return None,
_ => return Some(Err(Error::InvalidState(char::from(c).to_string()))),
}
}
} else if let Some(item) = self.lines.next() {
match I::line(item) {
Ok(line) => {
if line.as_ref().starts_with('#')
| line.as_ref().starts_with("x ")
| line.as_ref().starts_with("x=")
{
continue;
} else {
self.current_line = Some(I::bytes(line));
}
}
Err(e) => {
return Some(Err(Error::IoError(e)));
}
}
} else {
return None;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rle_parse_cxrle() {
assert_eq!(
parse_cxrle("#CXRLE"),
Some(CxrleData {
pos: None,
gen: None
})
);
assert_eq!(
parse_cxrle("#CXRLE Pos=0,-1377 Gen=3480106827776"),
Some(CxrleData {
pos: Some((0, -1377)),
gen: Some(3480106827776)
})
);
assert_eq!(
parse_cxrle("#CXRLE Gen = 3480106827776 Pos = 0, -1377"),
Some(CxrleData {
pos: Some((0, -1377)),
gen: Some(3480106827776)
})
);
assert_eq!(
parse_cxrle("#CXRLE211Pos=0,-9dcdcs2,[a ccGen=348sss1068cscPos= -333,-1a6"),
Some(CxrleData {
pos: Some((-333, -1)),
gen: Some(348)
})
);
}
#[test]
fn rle_parse_header() {
assert_eq!(parse_header("xxx"), None);
assert_eq!(
parse_header("x = 3, y = 3, rule = B3/S23"),
Some(HeaderData {
x: 3,
y: 3,
rule: Some(String::from("B3/S23"))
})
);
assert_eq!(
parse_header("x = 3, y = 3"),
Some(HeaderData {
x: 3,
y: 3,
rule: None
})
);
assert_eq!(parse_header("x = 3, y = -3"), None);
assert_eq!(
parse_header("x=3,y=3,rule=B3/S23 ignored"),
Some(HeaderData {
x: 3,
y: 3,
rule: Some(String::from("B3/S23"))
})
);
}
#[test]
fn rle_glider() -> Result<(), Error> {
const GLIDER: &str = r"#N Glider
#O Richard K. Guy
#C The smallest, most common, and first discovered spaceship. Diagonal, has period 4 and speed c/4.
#C www.conwaylife.com/wiki/index.php?title=Glider
x = 3, y = 3, rule = B3/S23
bob$2bo$3o!";
let glider = Rle::new(GLIDER)?;
assert_eq!(glider.cxrle_data, None);
assert_eq!(
glider.header_data,
Some(HeaderData {
x: 3,
y: 3,
rule: Some(String::from("B3/S23"))
})
);
let cells = glider
.map(|res| res.map(|c| c.position))
.collect::<Result<Vec<_>, _>>()?;
assert_eq!(cells, vec![(1, 0), (2, 1), (0, 2), (1, 2), (2, 2)]);
Ok(())
}
#[test]
fn rle_glider_cxrle() -> Result<(), Error> {
const GLIDER: &str = r"#CXRLE Pos=-1,-1
x = 3, y = 3, rule = B3/S23
bo$2bo$3o!";
let glider = Rle::new(GLIDER)?;
assert_eq!(
glider.cxrle_data,
Some(CxrleData {
pos: Some((-1, -1)),
gen: None
})
);
assert_eq!(
glider.header_data,
Some(HeaderData {
x: 3,
y: 3,
rule: Some(String::from("B3/S23"))
})
);
let cells = glider
.map(|res| res.map(|c| c.position))
.collect::<Result<Vec<_>, _>>()?;
assert_eq!(cells, vec![(0, -1), (1, 0), (-1, 1), (0, 1), (1, 1)]);
Ok(())
}
#[test]
fn rle_generations() -> Result<(), Error> {
const OSCILLATOR: &str = r"x = 3, y = 3, rule = 3457/357/5
3A$B2A$.CD!";
let oscillator = Rle::new(OSCILLATOR)?;
assert_eq!(oscillator.cxrle_data, None);
assert_eq!(
oscillator.header_data,
Some(HeaderData {
x: 3,
y: 3,
rule: Some(String::from("3457/357/5"))
})
);
let cells = oscillator.collect::<Result<Vec<_>, _>>()?;
assert_eq!(
cells,
vec![
CellData {
position: (0, 0),
state: 1
},
CellData {
position: (1, 0),
state: 1
},
CellData {
position: (2, 0),
state: 1
},
CellData {
position: (0, 1),
state: 2
},
CellData {
position: (1, 1),
state: 1
},
CellData {
position: (2, 1),
state: 1
},
CellData {
position: (1, 2),
state: 3
},
CellData {
position: (2, 2),
state: 4
},
]
);
Ok(())
}
#[test]
fn rle_generations_256() -> Result<(), Error> {
const OSCILLATOR: &str = r"x = 3, y = 3, rule = 23/3/256
.AwH$vIxNrQ$2pU!";
let oscillator = Rle::new(OSCILLATOR)?;
assert_eq!(oscillator.cxrle_data, None);
assert_eq!(
oscillator.header_data,
Some(HeaderData {
x: 3,
y: 3,
rule: Some(String::from("23/3/256"))
})
);
let cells = oscillator.collect::<Result<Vec<_>, _>>()?;
assert_eq!(
cells,
vec![
CellData {
position: (1, 0),
state: 1
},
CellData {
position: (2, 0),
state: 200
},
CellData {
position: (0, 1),
state: 177
},
CellData {
position: (1, 1),
state: 230
},
CellData {
position: (2, 1),
state: 89
},
CellData {
position: (0, 2),
state: 45
},
CellData {
position: (1, 2),
state: 45
},
]
);
Ok(())
}
}