I've been learning Rust for a few weeks in my free time, and as a first project I decided to make a simple terminal Game of Life program (without UI, that might come later). It just starts with a glider and loops indefinitely (Torus topology). I decided to keep it simple so I can learn from early mistakes before I start to make it more complex. Here are the 3 files of the project (The only dependency I'm using is termion for colored terminal output):
grid.rs
use std::fmt;
pub struct Grid {
rows: usize,
cols: usize,
data: Vec<bool>,
aux_data: Vec<bool>,
}
impl Grid {
pub fn new(rows: usize, cols: usize) -> Self {
Grid {
rows: rows,
cols: cols,
data: vec![false; rows * cols],
aux_data: vec![false; rows * cols],
}
}
pub fn get_rows(&self) -> usize {
self.rows
}
pub fn get_cols(&self) -> usize {
self.cols
}
pub fn get_cell(&self, row: usize, col: usize) -> bool {
self.data[row*self.get_cols() + col]
}
pub fn set_cell(&mut self, value: bool, row: usize, col: usize) {
let ncols = self.get_cols();
self.data[row*ncols + col] = value;
}
fn count_alive_neighbours(&self, row: usize, col: usize) -> i32 {
let offsets: [(usize, usize); 8] = [
(self.get_rows()-1, self.get_cols()-1), (self.get_rows()-1, 0), (self.get_rows()-1, 1),
( 0, self.get_cols()-1), ( 0, 1),
( 1, self.get_cols()-1), ( 1, 0), ( 1, 1)
];
let mut count = 0;
for offset in offsets.iter() {
let is_alive = self.get_cell(
(row + offset.0)%self.get_rows(),
(col + offset.1)%self.get_cols()
);
if is_alive {
count += 1
}
}
count
}
pub fn update(&mut self) {
use std::mem::swap;
self.aux_data.resize(self.data.len(), false);
let ncols = self.get_cols();
for row in 0..self.get_rows() {
for col in 0..self.get_cols() {
let is_alive = self.get_cell(row, col);
let alive_neighbours = self.count_alive_neighbours(row, col);
self.aux_data[row*ncols + col] = match (is_alive, alive_neighbours) {
(true, n) if n < 2 || 3 < n => false,
(false, 3) => true,
(cell, _) => cell,
};
}
}
swap(&mut self.data, &mut self.aux_data);
}
}
impl fmt::Display for Grid {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use termion::color;
for index_row in 0..self.get_rows() {
for index_col in 0..self.get_cols() {
match self.get_cell(index_row, index_col) {
false => write!(f, "{}\u{25FC} ", color::Fg(color::White))?,
true => write!(f, "{}\u{25FC} ", color::Fg(color::Red))?,
}
}
write!(f, "\n")?
}
Ok(())
}
}
lib.rs
pub mod grid;
main.rs
extern crate game_of_life;
use game_of_life::grid::Grid;
use std::{thread, time};
fn main() {
let mut grid = Grid::new(5, 5);
grid.set_cell(true, 1, 2);
grid.set_cell(true, 2, 3);
grid.set_cell(true, 3, 1);
grid.set_cell(true, 3, 2);
grid.set_cell(true, 3, 3);
loop {
print!("{}\n", grid);
grid.update();
thread::sleep(time::Duration::from_secs(1));
}
}
I come from a very strong C++ background, so some C++ practices might leak in my Rust code, but I promise I want to learn about them and try to think in the Rust way.
Thank you everyone and have a nice day!