diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 diff --git a/Cargo.lock b/Cargo.lock index 4453d47..c4e1482 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,10 +3,59 @@ version = 3 [[package]] -name = "bitflags" -version = "2.5.0" +name = "anstream" +version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" + +[[package]] +name = "anstyle-parse" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "cfg-if" @@ -20,6 +69,46 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" +[[package]] +name = "clap" +version = "4.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35723e6a11662c2afb578bcf0b88bf6ea8e21282a953428f240574fcc3a2b5b3" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49eb96cbfa7cfa35017b7cd548c75b14c3118c98b423041d70562665e07fb0fa" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d029b67f89d30bbb547c89fd5161293c0aec155fc691d7924b64550662db93e" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" + [[package]] name = "clearscreen" version = "3.0.0" @@ -33,6 +122,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "colorchoice" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" + [[package]] name = "dirs" version = "4.0.0" @@ -55,9 +150,9 @@ dependencies = [ [[package]] name = "either" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "errno" @@ -77,8 +172,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "game-of-life" -version = "0.1.0" +version = "1.0.0" dependencies = [ + "clap", "clearscreen", "rand", ] @@ -94,6 +190,12 @@ dependencies = [ "wasi", ] +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "home" version = "0.5.9" @@ -103,6 +205,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "libc" version = "0.2.155" @@ -282,10 +390,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" [[package]] -name = "syn" -version = "2.0.68" +name = "strsim" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" dependencies = [ "proc-macro2", "quote", @@ -307,18 +421,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.61" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.61" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", @@ -331,6 +445,12 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -382,9 +502,9 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", @@ -398,51 +518,51 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winsafe" diff --git a/Cargo.toml b/Cargo.toml index 7af4b23..a35e3ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,17 @@ [package] name = "game-of-life" -version = "0.1.0" +version = "1.0.0" +authors = ["Michał Czyż "] edition = "2021" +description = "Rust implementation of the Game of Life" +homepage = "https://github.com/eRgo35/game-of-life" +repository = "https://github.com/eRgo35/game-of-life" +documentation = "https://github.com/eRgo35/game-of-life" +license = "MIT" +readme = "README.md" + [dependencies] +clap = { version = "4.5.10", features = ["derive"] } clearscreen = "3.0.0" rand = "0.8.5" diff --git a/README.md b/README.md old mode 100644 new mode 100755 diff --git a/examples/custom_game.sav b/examples/custom_game.sav new file mode 100644 index 0000000..0da31cb --- /dev/null +++ b/examples/custom_game.sav @@ -0,0 +1,5 @@ +X X X + X X +X X X + X X +X X X \ No newline at end of file diff --git a/examples/random_game.sav b/examples/random_game.sav new file mode 100644 index 0000000..ade80ef --- /dev/null +++ b/examples/random_game.sav @@ -0,0 +1,25 @@ + XX X X XX X +XX X XX XXX X X XX XXX +X XXXXXX XXXX XXXX XXX +XXX XXX XXX XX X + X XXX X XX X XXXX XX X +XXXXXX XX X X X X + XX XXXX XXX X X X +X XXX XX X XXX XXX +XX XXX XX X X XXXXXX + XX X XXX XX XXXX XXX X + XXXX XXXX XXX XX X XX + XX XXX X X X XX + XX X XXXXXXXX X X X + X X XXXXX X XXX X +XX XX X X X XX X +XX XXX X X XX X X +XXX XXXX X XXX X X + X XXX XX X X X +X X X XX XX X X X +XXX X XX XXX XXXXXXXX +XXXX XX X X X X XX X +X XX XX X XXX X X XXX +XX X X X X XX XXXXXXXX +XXX X X X X X X +XXX XXX X XXX X X diff --git a/src/args.rs b/src/args.rs new file mode 100644 index 0000000..82580d9 --- /dev/null +++ b/src/args.rs @@ -0,0 +1,41 @@ +use clap::Parser; + +#[derive(Parser)] +#[command( + name = "Game of Life", + author = "Michał Czyż", + version = "1.0.0", + about = "Rust implementation of Conway's Game of Life", + long_about = None +)] +pub struct Args { + #[arg(long, default_value_t = 25)] + pub width: usize, + + #[arg(long, default_value_t = 25)] + pub height: usize, + + #[arg(short, long, default_value_t = 0.5)] + pub probability: f64, + + #[arg(short, long, default_value_t = 0)] + pub seed: u64, + + #[arg(short, long, default_value_t = 15)] + pub tickrate: u64, + + #[arg(long, default_value_t = String::from(""))] + pub load: String, + + #[arg(long, default_value_t = String::from(""))] + pub save: String, + + #[arg(short, long, default_value_t = 3)] + pub repopulation: usize, + + #[arg(short, long, default_value_t = 2)] + pub overpopulation: usize, + + #[arg(short, long, default_value_t = 3)] + pub underpopulation: usize, +} \ No newline at end of file diff --git a/src/board.rs b/src/board.rs old mode 100644 new mode 100755 index e2abbc6..20b43ac --- a/src/board.rs +++ b/src/board.rs @@ -1,4 +1,6 @@ -use rand::Rng; +use std::{io::{Read, Write}, path::PathBuf}; + +use rand::{Rng, SeedableRng}; #[derive(Clone, PartialEq)] enum State { @@ -18,18 +20,16 @@ pub struct Board { } impl Board { - // fn dead_state() -> State { - // State::Dead - // } + fn random_state(probability: f64, seed: u64, row: usize, col: usize) -> State { + let mut rng: rand::rngs::StdRng; - // fn alive_state() -> State { - // State::Alive - // } + if seed == 0 { + rng = rand::rngs::StdRng::from_entropy(); + } else { + rng = rand::rngs::StdRng::seed_from_u64(seed * row as u64 + col as u64); + } - fn random_state() -> State { - let mut rng = rand::thread_rng(); - - match rng.gen_bool(0.5) { + match rng.gen_bool(probability) { true => State::Alive, false => State::Dead, } @@ -63,13 +63,13 @@ impl Board { alive_neightbors } - fn generate_cells(width: usize, height: usize, state: &dyn Fn() -> State) -> Vec> { + fn generate_cells(width: usize, height: usize, probability: f64, seed: u64) -> Vec> { let mut cells = Vec::new(); - for _ in 0..height { + for i in 0..height { let mut row = Vec::new(); - for _ in 0..width { - row.push(Cell { state: state() }); + for j in 0..width { + row.push(Cell { state: Self::random_state(probability, seed, i, j) }); } cells.push(row); } @@ -77,8 +77,8 @@ impl Board { cells } - pub fn init(width: usize, height: usize) -> Self { - let cells = Self::generate_cells(width, height, &Self::random_state); + pub fn init(width: usize, height: usize, probability: f64, seed: u64) -> Self { + let cells = Self::generate_cells(width, height, probability, seed); Self { cells, @@ -87,6 +87,65 @@ impl Board { } } + pub fn load(path: String) -> Self { + let path = PathBuf::from(path); + + let mut file = match std::fs::File::open(path) { + Ok(file) => file, + Err(err) => panic!("{}", err), + }; + + let mut contents = String::new(); + if let Err(err) = file.read_to_string(&mut contents) { + panic!("{}", err); + } + + let width = contents.lines().next().unwrap().len(); + let height = contents.lines().count(); + let mut cells = vec![vec![Cell { state: State::Dead }; width]; height]; + + println!("{}x{}", width, height); + + for (i, line) in contents.lines().enumerate() { + for (j, c) in line.chars().enumerate() { + match c { + 'X' => cells[i][j].state = State::Alive, + ' ' => cells[i][j].state = State::Dead, + _ => panic!("Invalid character in file: {}. Use X to mark alive cells and space to mark dead cells.", c), + } + } + } + + Self { + cells, + width, + height, + } + } + + pub fn save(&self, path: String) { + let path = PathBuf::from(path); + let mut file = match std::fs::File::create(path) { + Ok(file) => file, + Err(err) => panic!("{}", err), + }; + + let mut contents = String::new(); + for row in &self.cells { + for cell in row { + match cell.state { + State::Alive => contents.push('X'), + State::Dead => contents.push(' '), + } + } + contents.push('\n'); + } + + if let Err(err) = file.write(contents.as_bytes()) { + panic!("{}", err); + } + } + pub fn render(&self) { for row in &self.cells { for cell in row { @@ -99,7 +158,7 @@ impl Board { } } - pub fn next_generation(&mut self) { + pub fn next_generation(&mut self, overpopulation: usize, underpopulation: usize, repopulation: usize) { let width = self.width; let height = self.height; let cells = self.cells.clone(); @@ -111,18 +170,17 @@ impl Board { let current_cell = &cells[i][j]; let alive_neightbors = Self::count_alive_neightbors(&cells, i, j); - // Main Gmae Logic - // TODO: Optimize and remove magic numbers + // Main Game of Life Logic match current_cell.state { State::Alive => { // Any live cell with less than two or more than three live neighbours survives. - if !(2..=3).contains(&alive_neightbors) { + if !(underpopulation..=overpopulation).contains(&alive_neightbors) { new_cells[i][j].state = State::Dead; } }, State::Dead => { // Any dead cell with three live neighbours becomes a live cell. - if alive_neightbors == 3 { + if alive_neightbors == repopulation { new_cells[i][j].state = State::Alive; } }, @@ -134,3 +192,4 @@ impl Board { } } + diff --git a/src/main.rs b/src/main.rs old mode 100644 new mode 100755 index e60d65b..a1d7a83 --- a/src/main.rs +++ b/src/main.rs @@ -1,15 +1,30 @@ +mod args; mod board; + +use args::Args; use board::Board; +use clap::Parser; use std::{thread, time}; fn main() { - let one_second = time::Duration::from_millis(1000); - let mut board = Board::init(32, 32); + let args = Args::parse(); + let mut board: Board; + + if !args.load.is_empty() { + board = Board::load(args.load); + } else { + board = Board::init(args.width, args.height, args.probability, args.seed); + } + + if !args.save.is_empty() { + board.save(args.save); + } + loop { - board.render(); - thread::sleep(one_second/15); - board.next_generation(); clearscreen::clear().expect("Failed to clear screen"); + board.render(); + thread::sleep(time::Duration::from_millis(1000 / args.tickrate)); + board.next_generation(args.overpopulation, args.underpopulation, args.repopulation); } } \ No newline at end of file