4 Commits

  1. 80
      src/grid.rs
  2. 488
      src/main.rs

80
src/grid.rs

@ -11,6 +11,7 @@ pub const TILESIZE: usize = 1 << TILEBITS;
pub const TILEMASK: usize = TILESIZE - 1;
pub const TILEMASKI: isize = TILEMASK as isize;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Grid
{
tiles: HashMap<(isize, isize), RefCell<Tile>>,
@ -18,13 +19,14 @@ pub struct Grid
wrapy: Option<NonZeroUsize>,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Tile
{
which: bool,
cells: [[Cell; TILESIZE]; TILESIZE],
}
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct Cell
{
// Switch between which of the states we use.
@ -32,11 +34,11 @@ pub struct Cell
second: State,
}
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum State
{
Dead(),
Alive(),
Dead,
Alive,
}
pub fn extend(extents: &mut ((isize, isize), (isize, isize)), coords: (isize, isize))
@ -84,7 +86,7 @@ impl Grid
{
for y in 0 .. TILESIZE
{
tile.cells[x][y].set(if d.sample(&mut rng) { State::Alive() } else { State::Dead() }, false);
tile.cells[x][y].set(if d.sample(&mut rng) { State::Alive } else { State::Dead }, false);
}
}
map.insert((xtile, ytile), RefCell::new(tile));
@ -153,7 +155,7 @@ impl Grid
let (xtile, ytile) = self.to_tile_coords(x, y);
match self.tiles.get(&(xtile, ytile))
{
None => State::Dead(),
None => State::Dead,
Some(tile) =>
{
let tile = tile.borrow();
@ -170,7 +172,7 @@ impl Grid
let (xminor, yminor) = ((x & (TILESIZE - 1) as isize) as usize, (y & (TILESIZE - 1) as isize) as usize);
match self.tiles.get(&(xtile, ytile))
{
None => if let State::Alive() = state
None => if let State::Alive = state
{
let mut tile = Tile::new_blank(false);
let which = tile.which ^ other;
@ -213,22 +215,22 @@ impl Grid
if highidx
{
fn idx_c_f(cells: &mut[[Cell; TILESIZE]; TILESIZE], i: usize) -> &mut Cell
{ &mut cells[TILESIZE - 1][i] };
{ &mut cells[TILESIZE - 1][i] }
fn idx_n_f(cells: &[[Cell; TILESIZE]; TILESIZE], i: usize) -> &Cell
{ &cells[TILESIZE - 2][i] };
{ &cells[TILESIZE - 2][i] }
fn idx_f_f(cells: &[[Cell; TILESIZE]; TILESIZE], i: usize) -> &Cell
{ &cells[0][i] };
{ &cells[0][i] }
idx_c = idx_c_f as fn(&mut[[Cell; TILESIZE]; TILESIZE], usize) -> &mut Cell;
idx_n = idx_n_f as fn(&[[Cell; TILESIZE]; TILESIZE], usize) -> &Cell;
idx_f = idx_f_f as fn(&[[Cell; TILESIZE]; TILESIZE], usize) -> &Cell;
} else
{
fn idx_c_f(cells: &mut[[Cell; TILESIZE]; TILESIZE], i: usize) -> &mut Cell
{ &mut cells[0][i] };
{ &mut cells[0][i] }
fn idx_n_f(cells: &[[Cell; TILESIZE]; TILESIZE], i: usize) -> &Cell
{ &cells[1][i] };
{ &cells[1][i] }
fn idx_f_f(cells: &[[Cell; TILESIZE]; TILESIZE], i: usize) -> &Cell
{ &cells[TILESIZE - 1][i] };
{ &cells[TILESIZE - 1][i] }
idx_c = idx_c_f as fn(&mut[[Cell; TILESIZE]; TILESIZE], usize) -> &mut Cell;
idx_n = idx_n_f as fn(&[[Cell; TILESIZE]; TILESIZE], usize) -> &Cell;
idx_f = idx_f_f as fn(&[[Cell; TILESIZE]; TILESIZE], usize) -> &Cell;
@ -238,22 +240,22 @@ impl Grid
if highidx
{
fn idx_c_f(cells: &mut[[Cell; TILESIZE]; TILESIZE], i: usize) -> &mut Cell
{ &mut cells[i][TILESIZE - 1] };
{ &mut cells[i][TILESIZE - 1] }
fn idx_n_f(cells: &[[Cell; TILESIZE]; TILESIZE], i: usize) -> &Cell
{ &cells[i][TILESIZE - 2] };
{ &cells[i][TILESIZE - 2] }
fn idx_f_f(cells: &[[Cell; TILESIZE]; TILESIZE], i: usize) -> &Cell
{ &cells[i][0] };
{ &cells[i][0] }
idx_c = idx_c_f as fn(&mut[[Cell; TILESIZE]; TILESIZE], usize) -> &mut Cell;
idx_n = idx_n_f as fn(&[[Cell; TILESIZE]; TILESIZE], usize) -> &Cell;
idx_f = idx_f_f as fn(&[[Cell; TILESIZE]; TILESIZE], usize) -> &Cell;
} else
{
fn idx_c_f(cells: &mut[[Cell; TILESIZE]; TILESIZE], i: usize) -> &mut Cell
{ &mut cells[i][0] };
{ &mut cells[i][0] }
fn idx_n_f(cells: &[[Cell; TILESIZE]; TILESIZE], i: usize) -> &Cell
{ &cells[i][1] };
{ &cells[i][1] }
fn idx_f_f(cells: &[[Cell; TILESIZE]; TILESIZE], i: usize) -> &Cell
{ &cells[i][TILESIZE - 1] };
{ &cells[i][TILESIZE - 1] }
idx_c = idx_c_f as fn(&mut[[Cell; TILESIZE]; TILESIZE], usize) -> &mut Cell;
idx_n = idx_n_f as fn(&[[Cell; TILESIZE]; TILESIZE], usize) -> &Cell;
idx_f = idx_f_f as fn(&[[Cell; TILESIZE]; TILESIZE], usize) -> &Cell;
@ -266,21 +268,21 @@ impl Grid
.map(|i|
{
let mut c = 0;
if let State::Alive() = idx_n(&tile.cells, i).get(which)
if let State::Alive = idx_n(&tile.cells, i).get(which)
{ c += 1; }
if let Some(lt) = adjacent
{
if let State::Alive() = idx_f(&lt.cells, i).get(lt.which)
if let State::Alive = idx_f(&lt.cells, i).get(lt.which)
{ c += 1; }
}
c
}).fold(0, |a, c| a + c) +
if let State::Alive() = idx_c(&mut tile.cells, i - 1).get(which) { 1 } else { 0 } +
if let State::Alive() = idx_c(&mut tile.cells, i + 1).get(which) { 1 } else { 0 };
if let State::Alive = idx_c(&mut tile.cells, i - 1).get(which) { 1 } else { 0 } +
if let State::Alive = idx_c(&mut tile.cells, i + 1).get(which) { 1 } else { 0 };
if off[neighbors]
{ idx_c(&mut tile.cells, i).set_other(State::Dead(), which); }
{ idx_c(&mut tile.cells, i).set_other(State::Dead, which); }
else if on[neighbors]
{ if !pretend { idx_c(&mut tile.cells, i).set_other(State::Alive(), which); }; rv = true; }
{ if !pretend { idx_c(&mut tile.cells, i).set_other(State::Alive, which); }; rv = true; }
else
{
let state = *idx_c(&mut tile.cells, i).get(which);
@ -297,18 +299,18 @@ impl Grid
.map(|i|
{
let mut c = 0;
if let State::Alive() = self.get(i, coords.1 + 1)
if let State::Alive = self.get(i, coords.1 + 1)
{ c += 1; }
if let State::Alive() = self.get(i, coords.1 - 1)
if let State::Alive = self.get(i, coords.1 - 1)
{ c += 1; }
c
}).fold(0, |a, c| a + c) +
if let State::Alive() = self.get(coords.0 - 1, coords.1) { 1 } else { 0 } +
if let State::Alive() = self.get(coords.0 + 1, coords.1) { 1 } else { 0 };
if let State::Alive = self.get(coords.0 - 1, coords.1) { 1 } else { 0 } +
if let State::Alive = self.get(coords.0 + 1, coords.1) { 1 } else { 0 };
if off[neighbors]
{ self.set_noc(coords.0, coords.1, State::Dead(), true); }
{ self.set_noc(coords.0, coords.1, State::Dead, true); }
else if on[neighbors]
{ if !pretend { self.set_noc(coords.0, coords.1, State::Alive(), true); }; rv = true; }
{ if !pretend { self.set_noc(coords.0, coords.1, State::Alive, true); }; rv = true; }
else
{
let state = self.get(coords.0, coords.1);
@ -432,14 +434,14 @@ impl Tile
Tile
{
which,
cells: [[Cell {first: State::Dead(), second: State::Dead()}; TILESIZE]; TILESIZE],
cells: [[Cell {first: State::Dead, second: State::Dead}; TILESIZE]; TILESIZE],
}
}
fn is_blank(&self, which: bool) -> bool
{
self.cells.iter().fold(true, |a, l| a && l.iter()
.fold(true, |a, c| if let State::Dead() = c.get(which) { a } else { false }))
.fold(true, |a, c| if let State::Dead = c.get(which) { a } else { false }))
}
fn do_step_internal(&mut self, on: &[bool; 9], off: &[bool; 9])
@ -451,18 +453,18 @@ impl Tile
let neighbors = (y - 1 ..= y + 1).map(|i|
{
let mut c = 0;
if let State::Alive() = self.cells[x - 1][i].get(self.which)
if let State::Alive = self.cells[x - 1][i].get(self.which)
{ c += 1; }
if let State::Alive() = self.cells[x + 1][i].get(self.which)
if let State::Alive = self.cells[x + 1][i].get(self.which)
{ c += 1; }
c
}).fold(0, |a, c| a + c) +
if let State::Alive() = self.cells[x][y - 1].get(self.which) { 1 } else { 0 } +
if let State::Alive() = self.cells[x][y + 1].get(self.which) { 1 } else { 0 };
if let State::Alive = self.cells[x][y - 1].get(self.which) { 1 } else { 0 } +
if let State::Alive = self.cells[x][y + 1].get(self.which) { 1 } else { 0 };
if off[neighbors]
{ self.cells[x][y].set_other(State::Dead(), self.which); }
{ self.cells[x][y].set_other(State::Dead, self.which); }
else if on[neighbors]
{ self.cells[x][y].set_other(State::Alive(), self.which); }
{ self.cells[x][y].set_other(State::Alive, self.which); }
else
{ self.cells[x][y].set_other(self.cells[x][y].get(self.which).clone(), self.which); }
}

488
src/main.rs

@ -1,28 +1,26 @@
#![feature(const_in_array_repeat_expressions)]
mod grid;
use crate::grid::{Grid, State, TILESIZE, TILEMASKI, TILEBITS};
use std::thread::sleep;
use std::time::Duration;
use std::num::NonZeroUsize;
use rand::Rng;
struct Disp<'a>(&'a Grid, (isize, isize), (isize, isize));
impl<'a> std::fmt::Display for Disp<'a>
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
{
// write!(f, "\x1b[2J\x1b[H")?;
for y in ((self.2).0 << TILEBITS .. (self.2).1 << TILEBITS).rev()
{
for x in (self.1).0 << TILEBITS .. (self.1).1 << TILEBITS
{
match self.0.get(x, y)
{
State::Dead() if x & TILEMASKI == 0 || y & TILEMASKI == 0 => { write!(f, "▒")?; },
State::Dead() if self.0.backed_at(x, y) => { write!(f, "░")?; },
State::Dead() => { write!(f, " ")?; },
State::Alive() => { write!(f, "▓")?; },
State::Dead if x & TILEMASKI == 0 || y & TILEMASKI == 0 => { write!(f, "▒")?; },
State::Dead if self.0.backed_at(x, y) => { write!(f, "░")?; },
State::Dead => { write!(f, " ")?; },
State::Alive => { write!(f, "▓")?; },
}
}
writeln!(f, "")?;
@ -31,108 +29,386 @@ impl<'a> std::fmt::Display for Disp<'a>
}
}
#[derive(Clone, Debug, PartialEq)]
enum Op {
FromFile {
filename: String,
init_grid: Option<Grid>,
wrapx: usize,
wrapy: usize,
maxsteps: usize,
rules: ([bool; 9], [bool; 9]),
delay_ms: u64,
},
Random {
prob: f64,
wrapx: usize,
wrapy: usize,
maxsteps: usize,
rules: ([bool; 9], [bool; 9]),
delay_ms: u64,
},
RandomHist {
samples: usize,
threads: usize,
wrapx: usize,
wrapy: usize,
maxsteps: usize,
rules: ([bool; 9], [bool; 9]),
}
}
const USAGE_STRING: &str =
"
This tool has three subcommands, which can be passed in as arguments.
The first is the `file` subcommand, which reads a pattern from a file and steps the automaton for a number of steps:
file <filename> <x size> <y size> <max steps> <ruleset> <frame delay>
`filename` indicates the file to load the initial pattern from. The file is parsed as if it were ASCII art.
The second is the `random` subcommand, which creates a random initial configuration and steps the automaton for a number of steps:
random <probability> <x size> <y size> <max steps> <ruleset> <frame delay>
The `probability` argument specifies the probability of a cell being alive in the initial state.
The third command in the `randhist` subcommand, which runs many simulations starting in a random state and outputs statistics about them:
randhist <number of samples> <number of threads> <x size> <y size> <max steps> <ruleset>
Some subcommands have have the following arguments:
`x size` and `y size` specify the size of the world, after which it wraps around. (BUG: this cannot be 1.) If it is zero, then the world will be unbounded, although this is disallowed in random trials.
`max steps` is the largest number of steps allowed for cycle detection or simulation.
`ruleset` describes the rules of the ceullular automaton, given in conditions required for birth or survival. For example, Conway's gave of life is specified as B3/S23 -- a cell becomes alive with 3 living neighbors, and stays alive with 2 or 3 living neighbors.
`frame delay` is the number of milliseconds between the printing of frames. If this is zero, then no display will happen at all.
";
fn main()
{
for _ii in 0 .. 1
{
//let mut grid = Grid::new_blank_wrapped(2, 2);
let mut grid = Grid::new_random(NonZeroUsize::new(2).unwrap(), NonZeroUsize::new(2).unwrap(), 0.5);
//grid.set(15, 50, State::Alive());
//grid.set(50, 50, State::Alive());
//grid.set(50, 15, State::Alive());
// NE Glider
//grid.set(15, 10, State::Alive());
//grid.set(15, 9, State::Alive());
//grid.set(15, 8, State::Alive());
//grid.set(14, 10, State::Alive());
//grid.set(13, 9, State::Alive());
// NE Glider
//grid.set(5, 15, State::Alive());
//grid.set(5, 14, State::Alive());
//grid.set(5, 13, State::Alive());
//grid.set(4, 15, State::Alive());
//grid.set(3, 14, State::Alive());
// NE Glider
//grid.set(15, 15, State::Alive());
//grid.set(15, 14, State::Alive());
//grid.set(15, 13, State::Alive());
//grid.set(14, 15, State::Alive());
//grid.set(13, 14, State::Alive());
//grid.set(15, 17, State::Alive());
//grid.set(15, 16, State::Alive());
//grid.set(15, 15, State::Alive());
//grid.set(14, 16, State::Alive());
//grid.set(14, 15, State::Alive());
//grid.set(14, 14, State::Alive());
//grid.set(14, 6, State::Alive());
//grid.set(14, 7, State::Alive());
//grid.set(15, 6, State::Alive());
//grid.set(15, 7, State::Alive());
//grid.set(25, 25, State::Alive());
//grid.set(25, 26, State::Alive());
//grid.set(26, 25, State::Alive());
//grid.set(26, 26, State::Alive());
// Raster semicircle
//grid.set(0, 0, State::Alive());
//grid.set(1, 0, State::Alive());
//grid.set(2, 1, State::Alive());
//grid.set(3, 2, State::Alive());
//grid.set(3, 3, State::Alive());
//grid.set(3, 4, State::Alive());
//grid.set(2, 5, State::Alive());
//grid.set(1, 6, State::Alive());
//grid.set(0, 6, State::Alive());
// Line
//for i in 0..10
//{
// grid.set(i, 0, State::Alive());
//}
// Acorn
//grid.set(0, 0, State::Alive());
//grid.set(1, 0, State::Alive());
//grid.set(1, 2, State::Alive());
//grid.set(3, 1, State::Alive());
//grid.set(4, 0, State::Alive());
//grid.set(5, 0, State::Alive());
//grid.set(6, 0, State::Alive());
let mut disp = format!("{}", Disp(&grid, (0, 1), (0, 1)));
for steps in 0..3000
{
println!("{}", steps);
print!("{}", disp);
let extents = grid.step(&[false, false, false, true, false, false, false, false, false], &[true, true, false, false, true, true, true, true, true], steps % TILESIZE == 0);
disp = format!("{}", Disp(&grid, extents.0, extents.1));
sleep(Duration::from_millis(1000 / 60));
use std::sync::{mpsc, atomic::AtomicUsize, Arc};
let mut args = std::env::args().skip(1);
let mut ops = vec![];
loop {
match args.next().as_deref() {
Some("file") => {
ops.push(Op::FromFile {
filename: args.next().expect("incorrect usage"),
init_grid: None,
wrapx: args.next().expect("incorrect usage").parse::<usize>().expect("not an integer"),
wrapy: args.next().expect("incorrect usage").parse::<usize>().expect("not an integer"),
maxsteps: args.next().expect("incorrect usage").parse::<usize>().expect("not an integer"),
rules: parse_rules(&args.next().expect("incorrect usage")).expect("could not parse rules"),
delay_ms: args.next().expect("incorrect usage").parse::<u64>().expect("not an integer"),
});
}
Some("random") => {
ops.push(Op::Random {
prob: args.next().expect("incorrect usage").parse::<f64>().expect("not a probability"),
wrapx: args.next().expect("incorrect usage").parse::<usize>().expect("not an integer"),
wrapy: args.next().expect("incorrect usage").parse::<usize>().expect("not an integer"),
maxsteps: args.next().expect("incorrect usage").parse::<usize>().expect("not an integer"),
rules: parse_rules(&args.next().expect("incorrect usage")).expect("could not parse rules"),
delay_ms: args.next().expect("incorrect usage").parse::<u64>().expect("not an integer"),
});
}
Some("randhist") => {
ops.push(Op::RandomHist {
samples: args.next().expect("incorrect usage").parse::<usize>().expect("not an integer"),
threads: args.next().expect("incorrect usage").parse::<usize>().expect("not an integer"),
wrapx: args.next().expect("incorrect usage").parse::<usize>().expect("not an integer"),
wrapy: args.next().expect("incorrect usage").parse::<usize>().expect("not an integer"),
maxsteps: args.next().expect("incorrect usage").parse::<usize>().expect("not an integer"),
rules: parse_rules(&args.next().expect("incorrect usage")).expect("could not parse rules"),
});
}
Some("--help") | Some("-h") | Some("-help") | Some("/?") => {
println!("{USAGE_STRING}");
return
}
Some(_) => {
eprintln!("{USAGE_STRING}");
return
}
None => break
}
let mut count_off = 0;
let mut count_on = 0;
grid.per_tile(|_, t|
{
for x in 0 .. TILESIZE
{
for y in 0 .. TILESIZE
{
match t.get_at(x, y)
}
for op in ops.iter_mut() {
match op {
Op::FromFile { filename, init_grid, wrapx, wrapy, .. } => {
let fstr = format!("could not open file {}", &filename);
let data = std::fs::read_to_string(filename).expect(fstr.as_str());
let mut grid = Grid::new_blank_wrapped(*wrapx, *wrapy);
for (y, line) in data.lines().enumerate() {
for (x, c) in line.chars().enumerate() {
if !c.is_whitespace() {
grid.set(x as isize, y as isize, State::Alive);
}
}
}
*init_grid = Some(grid);
}
Op::Random { prob, wrapx, wrapy, .. } => {
if prob.is_nan() || *prob > 1.0 || *prob < 0.0 {
eprintln!("probability must be between 0 and 1");
return;
}
if *wrapx == 0 || *wrapy == 0 {
eprintln!("random configurations must have a nonzero wrap size");
return;
}
}
Op::RandomHist { wrapx, wrapy, .. } => {
if *wrapx == 0 || *wrapy == 0 {
eprintln!("random configurations must have a nonzero wrap size");
return;
}
}
}
}
for op in ops {
match op {
Op::FromFile {
filename: _,
init_grid: Some(mut init_grid),
wrapx: _,
wrapy: _,
maxsteps,
rules: (on, off),
delay_ms,
} => {
let mut grid = init_grid.clone();
let fo = find_first_oscillation(&mut init_grid, &on, &off, maxsteps);
let num_steps = if let Some((stability_time, period)) = fo {
let mut count_off = 0;
let mut count_on = 0;
init_grid.per_tile(|_, t|
{
State::Dead() => { count_off += 1; },
State::Alive() => { count_on += 1; },
}
}
}
});
println!("cells off : {}", count_off);
println!("cells on : {}", count_on);
println!("ratio on : {}", (count_on as f64) / (count_off + count_on) as f64);
for x in 0 .. TILESIZE
{
for y in 0 .. TILESIZE
{
match t.get_at(x, y)
{
State::Dead => { count_off += 1; },
State::Alive => { count_on += 1; },
}
}
}
});
println!("took {stability_time} steps to converge to a period of {period}");
println!("cells off : {count_off}");
println!("cells on : {count_on}");
if count_off + count_on != 0 {
println!("ratio on : {}", (count_on as f64) / (count_off + count_on) as f64);
}
stability_time + 3 * period
} else {
println!("could not find oscillation within maximum steps");
maxsteps
};
if delay_ms > 0 {
let mut disp = format!("{}", Disp(&grid, (0, 1), (0, 1)));
for i in 0..num_steps {
println!("step {i}");
print!("{disp}");
let extents = grid.step(&on, &off, i % TILESIZE == 0);
disp = format!("{}", Disp(&grid, extents.0, extents.1));
sleep(Duration::from_millis(delay_ms));
}
}
}
Op::FromFile { init_grid: None, .. } => unreachable!(),
Op::Random {
prob,
wrapx,
wrapy,
maxsteps,
rules: (on, off),
delay_ms,
} => {
let mut init_grid = Grid::new_random(NonZeroUsize::new(wrapx).unwrap(), NonZeroUsize::new(wrapy).unwrap(), prob);
let mut grid = init_grid.clone();
let fo = find_first_oscillation(&mut init_grid, &on, &off, maxsteps);
let num_steps = if let Some((stability_time, period)) = fo {
let mut count_off = 0;
let mut count_on = 0;
init_grid.per_tile(|_, t|
{
for x in 0 .. TILESIZE
{
for y in 0 .. TILESIZE
{
match t.get_at(x, y)
{
State::Dead => { count_off += 1; },
State::Alive => { count_on += 1; },
}
}
}
});
println!("took {stability_time} steps to converge to a period of {period}");
println!("cells off : {count_off}");
println!("cells on : {count_on}");
if count_off + count_on != 0 {
println!("ratio on : {}", (count_on as f64) / (count_off + count_on) as f64);
}
stability_time + 3 * period
} else {
println!("could not find oscillation within maximum steps");
maxsteps
};
if delay_ms > 0 {
let mut disp = format!("{}", Disp(&grid, (0, 1), (0, 1)));
for i in 0..num_steps {
println!("step {i}");
print!("{disp}");
let extents = grid.step(&on, &off, i % TILESIZE == 0);
disp = format!("{}", Disp(&grid, extents.0, extents.1));
sleep(Duration::from_millis(delay_ms));
}
}
},
Op::RandomHist {
samples,
threads,
wrapx,
wrapy,
maxsteps,
rules: (on, off),
} => {
let wrapx = NonZeroUsize::new(wrapx).unwrap();
let wrapy = NonZeroUsize::new(wrapy).unwrap();
let (tx, rx) = mpsc::channel();
let i = Arc::new(AtomicUsize::new(0));
for _ in 0..threads {
let tx = tx.clone();
let i = i.clone();
let f = move || {
let mut rng = rand::thread_rng();
while i.fetch_add(1, std::sync::atomic::Ordering::Relaxed) < samples {
let prob = rng.gen::<f64>();
let mut init_grid = Grid::new_random(wrapx, wrapy, prob);
let fo = find_first_oscillation(&mut init_grid, &on, &off, maxsteps);
if let Some((stability_time, period)) = fo {
let mut count_off = 0;
let mut count_on = 0;
init_grid.per_tile(|_, t|
{
for x in 0 .. TILESIZE
{
for y in 0 .. TILESIZE
{
match t.get_at(x, y)
{
State::Dead => { count_off += 1; },
State::Alive => { count_on += 1; },
}
}
}
});
let ratio = if count_off + count_on != 0 {
(count_on as f64) / (count_off + count_on) as f64
} else {
0.0
};
tx.send((prob, Some((stability_time, period, count_off, count_on, ratio)))).expect("main thread ended before child");
} else {
tx.send((prob, None)).expect("main thread ended before child");
};
}
};
std::thread::spawn(f);
}
std::mem::drop(tx);
while let Ok((prob, stats)) = rx.recv() {
if let Some((stability_time, period, count_off, count_on, ratio)) = stats {
println!("{wrapx} {wrapy} {prob} {stability_time} {period} {count_off} {count_on} {ratio}");
} else {
println!("{wrapx} {wrapy} {prob} Inf NaN NaN NaN NaN");
}
}
}
}
}
}
fn parse_rules(s: &String) -> Option<([bool; 9], [bool; 9])> {
let mut on = [false; 9];
let mut off = [true; 9];
let mut state = None;
for c in s.chars() {
match (state, c) {
(_, '/') => (),
(Some(true), '0'..='9') => {
off[c.to_digit(10).unwrap() as usize] = false;
}
(Some(false), '0'..='9') => {
on[c.to_digit(10).unwrap() as usize] = true;
}
(_, 'B') | (_, 'b') => {
state = Some(false);
}
(_, 'S') | (_, 's') => {
state = Some(true);
}
_ => return None
}
}
Some((on, off))
}
fn find_first_oscillation(g: &mut Grid, on: &[bool; 9], off: &[bool; 9], max_steps: usize) -> Option<(usize, usize)> {
let mut tortoise = g.clone();
let mut hare = g.clone();
let mut i = 0;
tortoise.step(on, off, i % TILESIZE == 0);
hare.step(on, off, false);
hare.step(on, off, i % TILESIZE == 0);
while hare != tortoise {
if i > max_steps {
return None;
}
tortoise.step(on, off, i % TILESIZE == 0);
hare.step(on, off, false);
hare.step(on, off, i % TILESIZE == 0);
i += 1;
}
tortoise.step(on, off, true);
let mut next_occurrence = tortoise.clone();
let mut period = 1;
next_occurrence.step(on, off, true);
while tortoise != next_occurrence {
next_occurrence.step(on, off, true);
period += 1;
}
let mut leading = g.clone();
for _ in 0..period {
leading.step(on, off, true);
}
let mut first_index = 0;
while *g != leading {
g.step(on, off, true);
leading.step(on, off, true);
first_index += 1;
}
Some((first_index, period))
}
Loading…
Cancel
Save