I decided to implement Conways Game of Life for my first project using Rust. I also haven't had much code review ever, and have been looking for a good critique of my code so I can improve my skills. Here is the code:
extern crate glutin_window;
extern crate graphics;
extern crate piston;
extern crate piston_window;
use glutin_window::GlutinWindow as Window;
use graphics::*;
use opengl_graphics::{GlGraphics, OpenGL};
use piston::{
event_loop::*, RenderArgs, RenderEvent, UpdateArgs, UpdateEvent,
};
use piston::window::WindowSettings;
use rand::prelude::*; //Generate random life and death
const WINDOW_WIDTH: f64 = 400.0;
const WINDOW_HEIGHT: f64 = 400.0;
const BACKGROUND_COLOR: [f32; 4] = [0.5, 0.5, 0.5, 1.0];
const CELL_ALIVE: [f32; 4] = [0.0, 1.0, 0.0, 1.0];
const CELL_DEAD: [f32; 4] = [0.25, 0.25, 0.25, 1.0];
const CELL_SIZE: i32 = 1;
#[derive(Clone)]
struct Cell {
alive: bool,
}
impl Cell {
fn set(&mut self, alive: bool) {
self.alive = alive;
}
}
struct CellWorld {
rows: i32,
columns: i32,
map: Box<Vec<Vec<Cell>>>,
}
impl CellWorld {
fn new(window_height: f64, window_width: f64) -> CellWorld {
let n_rows = (window_height as i32) / CELL_SIZE;
let n_cols = (window_width as i32) / CELL_SIZE;
CellWorld {
rows: n_rows,
columns: n_cols,
map: Box::new(Vec::new()),
}
}
}
struct PistonApp {
gl_context: GlGraphics,
world: CellWorld,
}
impl PistonApp {
fn init(&mut self) {
let mut rand_life = thread_rng();
for row in 0..self.world.rows {
let rand_num: i32 = rand_life.gen();
let mut cell_row = vec![Cell { alive: row / rand_num == rand_life.gen() }; self.world.columns as usize];
for cell in 0..self.world.columns {
cell_row[cell as usize].set(cell % 2 == 0);
}
self.world.map.push(cell_row);
}
}
fn render(&mut self, render_args: &RenderArgs) {
let cell_map = &self.world.map;
let n_rows = self.world.rows;
let n_columns = self.world.columns;
self.gl_context.draw(render_args.viewport(), |c, gl| {
clear(BACKGROUND_COLOR, gl);
for row in 0..n_rows {
let cell_row = &cell_map[row as usize];
let row_offset = row * CELL_SIZE;
for col in 0..n_columns {
let cell = &cell_row[col as usize];
let col_offset = col * CELL_SIZE;
let cell_repr = rectangle::square(0.0, 0.0, CELL_SIZE as f64);
let trnsfrm = c.transform.trans(col_offset as f64, row_offset as f64);
let cell_color = match cell.alive {
true => CELL_ALIVE,
false => CELL_DEAD
};
rectangle(cell_color, cell_repr, trnsfrm, gl);
}
}
});
}
fn update(&mut self, _update_args: &UpdateArgs) {
self.simulate();
}
fn simulate(&mut self) {
let n_rows = self.world.rows;
let n_cols = self.world.columns;
for row in 0..n_rows {
for col in 0..n_cols {
self.update_cell(row as usize, col as usize);
}
}
}
fn update_cell(&mut self, row: usize, col: usize) {
let world = &mut self.world;
let world_map = &mut world.map;
let mut alive_neighbors = 0;
let has_left_neighbors = if col == 0 { false } else { col - 1 > 0 };
let left_col = if has_left_neighbors { col - 1 } else { 0 };
let has_right_neighbors = col + 1 < world.columns as usize;
let right_col = if has_right_neighbors { col + 1 } else { 0 };
let has_top_neighbors = if row == 0 { false } else { row - 1 > 0 };
let top_row = if has_top_neighbors { row - 1 } else { 0 };
let has_btm_neighbors = row + 1 < world.rows as usize;
let btm_row = if has_btm_neighbors { row + 1 } else { 0 };
if has_left_neighbors {
if world_map[row][left_col].alive {
alive_neighbors += 1;
}
if has_top_neighbors && world_map[top_row][left_col].alive {
alive_neighbors += 1;
}
if has_btm_neighbors && world_map[btm_row][left_col].alive {
alive_neighbors += 1;
}
}
if has_right_neighbors {
if world_map[row][right_col].alive {
alive_neighbors += 1;
}
if has_top_neighbors && world_map[top_row][right_col].alive {
alive_neighbors += 1;
}
if has_btm_neighbors && world_map[btm_row][right_col].alive {
alive_neighbors += 1;
}
}
if has_top_neighbors {
if world_map[top_row][col].alive {
alive_neighbors += 1;
}
}
if has_btm_neighbors {
if world_map[btm_row][col].alive {
alive_neighbors += 1;
}
}
if alive_neighbors < 2 || alive_neighbors > 3 {
world_map[row][col].set(false);
}
if alive_neighbors == 2 || alive_neighbors == 3 || world_map[row][col].alive == false && alive_neighbors == 3 {
world_map[row][col].set(true);
}
}
}
fn main() {
let opengl_version = OpenGL::V3_2;
let mut window: Window = WindowSettings::new(
"Cellular Automata!",
piston_window::Size::from([WINDOW_WIDTH, WINDOW_HEIGHT]),
).graphics_api(opengl_version).exit_on_esc(true)
.resizable(false).fullscreen(true).build().unwrap();
let mut cellular_automata_app = PistonApp {
gl_context: GlGraphics::new(opengl_version),
world: CellWorld::new(WINDOW_HEIGHT, WINDOW_WIDTH),
};
cellular_automata_app.init();
let mut window_events = Events::new(EventSettings::new());
while let Some(event) = window_events.next(&mut window) {
if let Some(render_args) = event.render_args() {
cellular_automata_app.render(&render_args);
}
if let Some(update_args) = event.update_args() {
cellular_automata_app.update(&update_args);
}
std::thread::sleep(std::time::Duration::from_millis(10));
}
}
Let me know what you think about my code!
update_cell
function a lot, and making use of the overlap between the neighborhoods of adjacent cells would let you reduce your array accesses roughly by a factor of three. \$\endgroup\$ – Ilmari Karonen Jul 5 '19 at 19:16