game-life: Implement Conway's Game of Life
This commit is contained in:
commit
5f1f0e1f7b
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/target
|
7
Cargo.lock
generated
Normal file
7
Cargo.lock
generated
Normal file
@ -0,0 +1,7 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "game-life"
|
||||
version = "0.1.0"
|
6
Cargo.toml
Normal file
6
Cargo.toml
Normal file
@ -0,0 +1,6 @@
|
||||
[package]
|
||||
name = "game-life"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
98
src/lib.rs
Normal file
98
src/lib.rs
Normal file
@ -0,0 +1,98 @@
|
||||
//! Conway's Game of Life on a torus
|
||||
|
||||
pub fn rem(lhs: isize, rhs: usize) -> usize {
|
||||
lhs.rem_euclid(rhs as _) as usize
|
||||
}
|
||||
|
||||
pub struct Board<const X: usize, const Y: usize> {
|
||||
pub front: Box<[[u8; X]; Y]>, // The display buffer
|
||||
pub back: Box<[[u8; X]; Y]>, // These are heap allocated to make buffer swaps a pointer operation
|
||||
}
|
||||
|
||||
impl<const X: usize, const Y: usize> Default for Board<X, Y> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
back: Box::new([[0; X]; Y]),
|
||||
front: Box::new([[0; X]; Y]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<const X: usize, const Y: usize> Board<X, Y> {
|
||||
/// Copies a template into the top left corner of the front buffer
|
||||
pub fn copy_from<const AX: usize, const AY: usize>(&mut self, other: [[u8; AY]; AX]) {
|
||||
for (y, other) in other.iter().enumerate().take(Y) {
|
||||
for (x, other) in other.iter().enumerate().take(X) {
|
||||
self.set(x, y, *other);
|
||||
}
|
||||
}
|
||||
self.swap()
|
||||
}
|
||||
/// Sets `(x, y)` to `value` in the back buffer
|
||||
pub fn set(&mut self, x: usize, y: usize, value: u8) {
|
||||
self.back[y][x] = value;
|
||||
}
|
||||
/// Gets the value of `(x, y)` in the front buffer
|
||||
pub fn get(&self, x: usize, y: usize) -> u8 {
|
||||
self.front[y][x]
|
||||
}
|
||||
/// Swaps the front and back buffers
|
||||
pub fn swap(&mut self) {
|
||||
let Self { back, front, .. } = self;
|
||||
std::mem::swap(front, back)
|
||||
}
|
||||
/// sums the immediate neighbors of (cx, dx)
|
||||
pub fn sum_neighbors(&self, cx: usize, cy: usize) -> u8 {
|
||||
let (cx, cy, mut neighbors) = (cx as isize, cy as isize, 0);
|
||||
for dx in -1..=1 {
|
||||
for dy in -1..=1 {
|
||||
if (dx, dy) == (0, 0) {
|
||||
continue;
|
||||
}
|
||||
let (x, y) = (rem(cx + dx, X), rem(cy + dy, Y));
|
||||
neighbors += self.get(x, y)
|
||||
}
|
||||
}
|
||||
neighbors
|
||||
}
|
||||
/// Evaluates the Game of Life ruleset
|
||||
pub fn life(&mut self, x: usize, y: usize) {
|
||||
let state = match (self.get(x, y), self.sum_neighbors(x, y)) {
|
||||
(0, 2) => 0, // A dead cell with 2 neighbors dies
|
||||
(_, 2..=3) => 1, // A cell with 2 or 3 neigbors comes alive
|
||||
_ => 0, // Any other cell dies
|
||||
};
|
||||
self.set(x, y, state)
|
||||
}
|
||||
/// Produces a new generation
|
||||
pub fn update(&mut self) {
|
||||
for y in 0..Y {
|
||||
for x in 0..X {
|
||||
self.life(x, y);
|
||||
}
|
||||
}
|
||||
self.swap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const X: usize, const Y: usize> std::fmt::Display for Board<X, Y> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
writeln!(f, "┌{:─<1$}┐", "", X * 2)?;
|
||||
for y in self.front.iter() {
|
||||
"│".fmt(f)?;
|
||||
for x in y {
|
||||
f.write_str(to_str(*x))?;
|
||||
}
|
||||
"│\n".fmt(f)?;
|
||||
}
|
||||
write!(f, "└{:─<1$}┘", "", X * 2)
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets a little guy if the cell is alive
|
||||
fn to_str(cell: u8) -> &'static str {
|
||||
match cell {
|
||||
0 => " ",
|
||||
_ => "🮲🮳",
|
||||
}
|
||||
}
|
26
src/main.rs
Normal file
26
src/main.rs
Normal file
@ -0,0 +1,26 @@
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use game_life::Board;
|
||||
|
||||
const W: usize = 22;
|
||||
const H: usize = 12;
|
||||
// Framerate
|
||||
const RATE: f32 = 60.0;
|
||||
|
||||
#[allow(unused_mut)]
|
||||
fn main() {
|
||||
let mut board: Board<W, H> = Default::default();
|
||||
// Stick a glider in the top left corner
|
||||
board.copy_from([
|
||||
[0, 1, 0],
|
||||
[0, 0, 1],
|
||||
[1, 1, 1],
|
||||
]);
|
||||
println!("\x1b[H\x1b[J");
|
||||
loop {
|
||||
let time = Instant::now();
|
||||
println!("\x1b[H{board}");
|
||||
board.update();
|
||||
std::thread::sleep(Duration::from_secs_f32(RATE.recip()).saturating_sub(time.elapsed()));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user