dashn9/conway-game-of-life: The Game of Life That Changes as It Reads Itself
A tiny Rust terminal simulator that updates cells in place, turning grid order into the hidden mechanic.
- The repo’s real lesson is that mutating a simulation in place turns traversal order into part of the rules.
- Its tiny Rust surface area makes the order-of-operations problem easy to see, not easy to miss.
- The plain ANSI terminal loop keeps attention on the grid logic instead of on UI machinery.
- This is a compact lesson in correctness, because the board only behaves properly when reading and writing stay separate.
The rule that leaks through the grid
On paper, this is Conway’s Game of Life. In practice, it is a different machine. The surprise is simple: should_i_live writes the new state back into the same grid it is still reading, so the top-left of the board can affect the bottom-right before the generation is finished.
That is the whole thesis. A cellular automaton is supposed to be blind to the order you inspect it in. This repo makes order matter, which means the code is no longer just simulating Life. It is simulating a sweep through Life.
Inside the Rust loop
The shape of the program is spare. main.rs calls start_game(8, 8), and the engine in game.rs seeds a Vec<Vec<bool>>, clears the terminal, renders the board, sleeps for two seconds, then walks every row and column again.
fn start_game(rows: u16, columns: u16) {
let mut world = seed_world(rows, columns);
loop {
print!("\x1B[2J\x1B[1;1H");
render_world(&world);
std::thread::sleep(std::time::Duration::from_secs(2));
for row in 0..rows {
for column in 0..columns {
should_i_live(&mut world, row, column, rows, columns);
}
}
}
}
The neighbor lookup is explicit, not clever. fetch_neighbours_positions checks each edge and corner by hand, returning only the cells that actually exist. That is readable, but it also means a tiny mismatch between rows and columns can slip through, which is exactly the kind of bug that shows up in small grid code.
The terminal rendering is equally bare bones. ANSI clear-screen escape codes do the work, so the whole experience stays focused on the board instead of on a framework. There is nowhere for the logic to hide.
Why this is not canonical Game of Life
Canonical Life reads from one board and writes to another. That separation is not an implementation detail, it is the rule that keeps the math honest. This repo skips the second board, so the current cell can change what later cells think the world looks like.
| Dimension | Canonical Game of Life | dashn9/conway-game-of-life |
|---|---|---|
| Update model | Read from one grid, write to a separate next grid | Read and write the same grid in place |
| Traversal order | Irrelevant to the result | Changes the result as the sweep advances |
| Boundary handling | Usually wrapped, buffered, or hidden behind helpers | Explicit edge checks in neighbor lookup |
| Determinism | Stable for a given seed and rule set | Stable only if you keep the sweep order fixed |
| Teaching value | Reference implementation of the rules | A live demonstration of why buffering matters |
If you want the canonical behavior, you need a double buffer. If you want a teaching example of what goes wrong when you skip one, this repo is perfect. It turns a familiar automaton into a visible argument about state.
A beginner Rust project wearing a classic skin
The repo is small enough to read in one sitting, which is part of the value. There is one dependency, rand, a terminal renderer, and a single engine file carrying nearly all the logic. That makes it a clean Rust exercise in indexing, mutation, and loop structure.
That also makes it a useful reminder that small simulation code can still be subtle. A grid of booleans looks harmless until you realize that every choice about traversal, bounds, and mutation changes the meaning of the program. This is not a polished Game of Life app. It is a compact lesson in why correctness depends on separating observation from update.





