From 14e59ac5fe3a86a2fde1466859865a255cd999ab Mon Sep 17 00:00:00 2001 From: Jon Fetter-Degges Date: Tue, 11 Oct 2022 21:37:05 -0400 Subject: [PATCH] Improve printing, make output closer to original Implemented Display for CellState, and tweaked outputs to match the BASIC implementation. Also fixed some more comments. --- 55_Life/rust/src/main.rs | 59 ++++++++++++++++++++++++++++++---------- 1 file changed, 45 insertions(+), 14 deletions(-) diff --git a/55_Life/rust/src/main.rs b/55_Life/rust/src/main.rs index 37e0f6c0..0cfe707f 100644 --- a/55_Life/rust/src/main.rs +++ b/55_Life/rust/src/main.rs @@ -1,12 +1,24 @@ -use std::{io, thread, time}; +// Rust implementation of David Ahl's implementation of Conway's Life +// +// Jon Fetter-Degges +// October 2022 + +// I am a Rust newbie. Corrections and suggestions are welcome. + +use std::{fmt, io, thread, time}; const HEIGHT: usize = 24; const WIDTH: usize = 70; // The BASIC implementation uses a 24x70 array of integers to represent the board state. -// 1 is alive, 2 is about to die, 3 is about to be born, all other values are dead. -// (I'm not actually sure whether there are other values besides zero.) -// Here, we'll use an enum instead. +// 1 is alive, 2 is about to die, 3 is about to be born, 0 is dead. Here, we'll use an +// enum instead. +// Deriving Copy (which requires Clone) allows us to use this enum value in assignments. +// Without that we would only be able to borrow it. That seems silly for a simple enum +// like this one - it is required because enums can have large amounts of associated +// data, so the programmer needs to decide whether to allow copying. Similarly, PartialEq +// allows use of the == comparison. Again, this seems silly for a simple enum, but if +// some enum cases have associated data, it may require some thought. #[derive(Clone, Copy, PartialEq)] enum CellState { Empty, @@ -15,6 +27,20 @@ enum CellState { AboutToBeBorn, } +// Support direct printing of the cell. In this program cells will only be Alive or Empty +// when they are printed. +impl fmt::Display for CellState { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let rep = match *self { + CellState::Empty => ' ', + CellState::Alive => '*', + CellState::AboutToDie => 'o', + CellState::AboutToBeBorn => '.', + }; + write!(f, "{}", rep) + } +} + // Following the BASIC implementation, we will bound the board at 24 rows x 70 columns. // Since that isn't too big (even in the 70's), we just store the whole board as an // array of CellState. I'm experimenting with using an array-of-arrays to make references @@ -39,7 +65,7 @@ impl Board { min_col: 0, max_col: 0, population: 0, - generation: 1, + generation: 0, invalid: false, } } @@ -47,6 +73,8 @@ impl Board { fn main() { println!(); println!(); println!(); + println!("{:33}{}", " ", "Life"); + println!("{:14}{}", " ", "Creative Computing Morristown, New Jersey"); println!("Enter your pattern: "); let mut board = parse_pattern(get_pattern()); loop { @@ -104,11 +132,14 @@ fn parse_pattern(rows: Vec>) -> Board { // at the beginning or end of every row. It wouldn't be hard to check for that, but // for now we'll preserve the original behavior. let nrows = rows.len(); - let ncols = rows.iter().map(|l| l.len()).max().unwrap_or(0); // handles the case where rows is empty + // If rows is empty, the call to max will return None. The unwrap_or then provides a + // default value + let ncols = rows.iter().map(|l| l.len()).max().unwrap_or(0); - // If nrows >= 24 or ncols >= 68, these assignments will wrap around to large values. - // The array accesses below will then be out of bounds. Rust will bounds-check them - // and panic rather than performing an invalid access. + // The min and max values here are unsigned. If nrows >= 24 or ncols >= 68, these + // assignments will panic - they do not wrap around unless we use a function with + // that specific behavior. Again, we expect bounds checking on the input before this + // function is called. board.min_row = 11 - nrows / 2; board.min_col = 33 - ncols / 2; board.max_row = board.min_row + nrows - 1; @@ -201,15 +232,15 @@ fn finish_cell_transitions(board: &mut Board) { fn print_board(board: &Board) { println!(); println!(); println!(); - println!("Generation: {}", board.generation); - println!("Population: {}", board.population); + print!("Generation: {} Population: {}", board.generation, board.population); if board.invalid { - println!("Invalid!"); + print!("Invalid!"); } + println!(); for row_index in 0..HEIGHT { for col_index in 0..WIDTH { - let rep = if board.cells[row_index][col_index] == CellState::Alive { "*" } else { " " }; - print!("{rep}"); + // This print will use the Display implementation for cell_state, above. + print!("{}", board.cells[row_index][col_index]); } println!(); }