From 13a2529a06fd722e8542ceb10c19497eb4b000cc Mon Sep 17 00:00:00 2001 From: Jeromos Kovacs Date: Mon, 15 Jul 2024 19:57:41 +0200 Subject: [PATCH 01/12] chore(Cargo.toml): added 2 categories: tui, games --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 58d7249..26f8941 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ license = "MIT OR Apache-2.0" documentation = "https://docs.rs/cgol-tui" repository = "https://github.com/JeromeSchmied/cgol-tui-rs" keywords = ["conway", "game-of-life", "tui"] +categories = ["games", "tui"] [dependencies] crossterm = "0.27.0" From c868cf14c60b48910c51b5bd31b1ac2e0054a90a Mon Sep 17 00:00:00 2001 From: Jeromos Kovacs Date: Mon, 15 Jul 2024 20:23:35 +0200 Subject: [PATCH 02/12] fix: use std::ops::Index(Mut) --- src/lib.rs | 57 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 4117b1f..071a83a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,6 +24,7 @@ pub enum Cell { Alive = 1, } impl Cell { + #[allow(unused)] fn toggle(&mut self) { *self = match *self { Cell::Dead => Cell::Alive, @@ -33,17 +34,41 @@ impl Cell { } /// the `Universe` in which game plays. Represented as a `Vec` of `Cell`s. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Universe { width: u16, height: u16, cells: Vec, } +impl, U2: Into> std::ops::Index<(U1, U2)> for Universe { + type Output = Cell; + + fn index(&self, idx: (U1, U2)) -> &Self::Output { + let row = idx.0.into(); + let col = idx.1.into(); + // Convert (x;y) to index + let idx = self.get_idx(row, col); + + &self.cells[idx] + } +} +impl, U2: Into> std::ops::IndexMut<(U1, U2)> for Universe { + fn index_mut(&mut self, idx: (U1, U2)) -> &mut Self::Output { + let row = idx.0.into(); + let col = idx.1.into(); + // Convert (x;y) to index + let idx = self.get_idx(row, col); + + &mut self.cells[idx] + } +} impl Universe { - /// Convert (x;y) to index - fn get_index(&self, row: u16, col: u16) -> usize { - (row as u32 * self.width as u32 + col as u32) as usize + fn get_idx, U2: Into>(&self, row: U1, col: U2) -> usize { + let row = row.into(); + let col = col.into(); + // Convert (x;y) to index + (row * self.width as usize) + col } fn live_neighbour_count(&self, row: u16, col: u16) -> u8 { @@ -57,8 +82,8 @@ impl Universe { let neighbour_row = (row + delta_row) % self.height; let neighbour_col = (col + delta_col) % self.width; - let idx = self.get_index(neighbour_row, neighbour_col); - sum += self.cells[idx] as u8; + + sum += self[(neighbour_row, neighbour_col)] as u8; } } sum @@ -115,9 +140,8 @@ impl Universe { let mut j = 0; for row in start_row as usize..start_row as usize + figur.height() as usize { - let idx = univ.get_index(row as u16, start_col); for i in 0..figur.width() as usize { - univ.cells[idx + i] = figur.cells[j]; + univ[(row, start_col as usize + i)] = figur.cells[j]; j += 1; } } @@ -136,12 +160,12 @@ impl Universe { /// update life: `Universe` pub fn tick(&mut self) { - let mut next = self.cells.clone(); + let mut next = self.clone(); for row in 0..self.width { for col in 0..self.height { - let idx = self.get_index(row, col); - let cell = self.cells[idx]; + let idx = (row, col); + let cell = self[idx]; let live_neighbours = self.live_neighbour_count(row, col); let next_cell = match (cell, live_neighbours) { @@ -165,7 +189,7 @@ impl Universe { } } - self.cells = next; + *self = next; } pub fn width(&self) -> u16 { @@ -175,20 +199,13 @@ impl Universe { pub fn height(&self) -> u16 { self.height } - - // unused - /// toggles cell at (`row`;`col`) - pub fn toggle_cell(&mut self, row: u16, col: u16) { - let idx = self.get_index(row, col); - self.cells[idx].toggle(); - } } impl Shape for Universe { fn draw(&self, painter: &mut ratatui::widgets::canvas::Painter) { for y in 0..self.height { for x in 0..self.width { - match self.cells.get(self.get_index(x, y)).unwrap() { + match self[(x, y)] { Cell::Alive => painter.paint(y.into(), x.into(), Color::White), Cell::Dead => continue, } From af5cf561dcdc9eb0014a17881312bf0fb7b141c5 Mon Sep 17 00:00:00 2001 From: Jeromos Kovacs Date: Mon, 15 Jul 2024 21:20:47 +0200 Subject: [PATCH 03/12] fix: tests and convenience --- src/lib.rs | 49 +++++++++++++++++++++++++++++++++++++++++++++---- src/shapes.rs | 5 +++++ 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 071a83a..6542835 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,11 +18,21 @@ pub mod shapes; pub mod ui; /// information about one `Cell`: either `Dead` or `Alive` -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] pub enum Cell { + #[default] Dead = 0, Alive = 1, } +impl From for Cell { + fn from(alive: bool) -> Self { + if alive { + Self::Alive + } else { + Self::Dead + } + } +} impl Cell { #[allow(unused)] fn toggle(&mut self) { @@ -34,7 +44,7 @@ impl Cell { } /// the `Universe` in which game plays. Represented as a `Vec` of `Cell`s. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Universe { width: u16, height: u16, @@ -117,7 +127,7 @@ impl Universe { /// # Errors /// /// if shape can't fit universe - pub fn from_figur(wh: u16, figur: &[String]) -> Result { + fn from_figur(wh: u16, figur: &[String]) -> Result { let figur = Universe::from_vec_str(figur); let figur_alive = figur .cells @@ -129,7 +139,7 @@ impl Universe { return Err(HandleError::TooBig); } - let cells = (0..wh as u32 * wh as u32).map(|_i| Cell::Dead).collect(); + let cells = vec![Cell::default(); wh.pow(2).into()]; let mut univ = Universe { cells, width: wh, @@ -229,3 +239,34 @@ impl Shape for Universe { // Ok(()) // } // } +#[cfg(test)] +mod tests { + use super::*; + + const WH: u16 = 20; + + fn gen_uni(w: u16, h: u16, cells: &[bool]) -> Universe { + let cells = cells.iter().map(|c| (*c).into()).collect::>(); + Universe { + width: w, + height: h, + cells, + } + } + + #[test] + fn rabbit_hole() { + let rabbit = shapes::featherweigth_spaceship(); + let rabbit_uni = Universe::from_vec_str(&rabbit); + let cells = [false, false, true, true, false, true, false, true, true]; + let uni = gen_uni(3, 3, &cells); + assert_eq!(rabbit_uni, uni); + } + #[test] + fn full() { + let full = shapes::full(WH); + let cells = [true; WH.pow(2) as usize]; + let uni = gen_uni(WH, WH, &cells); + assert_eq!(full, uni); + } +} diff --git a/src/shapes.rs b/src/shapes.rs index 1d00d3d..6e6daaf 100644 --- a/src/shapes.rs +++ b/src/shapes.rs @@ -105,10 +105,12 @@ pub fn gosper_glider_gun() -> Vec { .to_vec() } +/// 3x3 pub fn featherweigth_spaceship() -> Vec { ["__#".into(), "#_#".into(), "_##".into()].to_vec() } +/// 8x4 pub fn rabbits() -> Vec { [ "#_____#_".into(), @@ -136,6 +138,7 @@ pub fn acorn() -> Vec { ["_#_____".into(), "___#___".into(), "##__###".into()].to_vec() } +/// `wh`x`wh` pub fn rand(wh: u16) -> Universe { let cells = (0..wh * wh) .map(|_i| { @@ -153,6 +156,7 @@ pub fn rand(wh: u16) -> Universe { } } +/// `wh`x`wh` pub fn stripes(wh: u16) -> Universe { let cells = (0..wh * wh) .map(|i| { @@ -170,6 +174,7 @@ pub fn stripes(wh: u16) -> Universe { } } +/// `wh`x`wh` pub fn full(wh: u16) -> Universe { let cells = vec![Cell::Alive; wh as usize * wh as usize]; Universe { From 0e49d66b559576158387ab12644a891ba1dd2cd3 Mon Sep 17 00:00:00 2001 From: Jeromos Kovacs Date: Tue, 16 Jul 2024 16:00:10 +0200 Subject: [PATCH 04/12] chore: version bump --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cd74e93..62a031e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -55,7 +55,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "cgol-tui" -version = "0.3.0" +version = "0.3.1" dependencies = [ "crossterm", "fastrand", diff --git a/Cargo.toml b/Cargo.toml index 26f8941..67ded09 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cgol-tui" -version = "0.3.0" +version = "0.3.1" authors = ["Jeromos Kovacs "] edition = "2018" description = "Conway's Game of Life implementation with a TUI" From b7e7523b5b2a31182810ec34b818830571033bc1 Mon Sep 17 00:00:00 2001 From: Jeromos Kovacs Date: Tue, 16 Jul 2024 16:01:36 +0200 Subject: [PATCH 05/12] fix(Cargo.toml): tui category doesn't exist --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 67ded09..45d9467 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" documentation = "https://docs.rs/cgol-tui" repository = "https://github.com/JeromeSchmied/cgol-tui-rs" keywords = ["conway", "game-of-life", "tui"] -categories = ["games", "tui"] +categories = ["games"] [dependencies] crossterm = "0.27.0" From 70878f5ea3cbab7629596cbdffae7aa49936e0a6 Mon Sep 17 00:00:00 2001 From: Jeromos Kovacs Date: Fri, 27 Sep 2024 08:17:17 +0200 Subject: [PATCH 06/12] fix(App): use new --- src/main.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 69fc153..8fa0f1a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -31,7 +31,8 @@ fn main() -> Result<(), Box> { // create app and run it with width and height from terminal size let wh = size()?; - let mut app = App::new((wh.1 + 10) * 3); + let wh = ((wh.1 - 3) * 4).min((wh.0 / 2 - 2) * 2); + let mut app = App::new(wh); let res = run_app(&mut terminal, &mut app); From 0bf3c91d6debbcf86f7d18f007f3335162f4c6ec Mon Sep 17 00:00:00 2001 From: Jeromos Kovacs Date: Fri, 27 Sep 2024 08:36:51 +0200 Subject: [PATCH 07/12] deps: upgrade, ... --- Cargo.lock | 197 ++++++++++++++++++++++---------------------------- Cargo.toml | 5 +- src/app.rs | 14 ++-- src/kmaps.rs | 2 +- src/lib.rs | 18 ++--- src/main.rs | 66 ++++------------- src/shapes.rs | 2 +- src/ui.rs | 2 +- 8 files changed, 122 insertions(+), 184 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 62a031e..a2b23d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -57,35 +57,35 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" name = "cgol-tui" version = "0.3.1" dependencies = [ - "crossterm", "fastrand", "ratatui", ] [[package]] name = "compact_str" -version = "0.7.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f" +checksum = "6050c3a16ddab2e412160b31f2c871015704239bca62f72f6e5f0be631d3f644" dependencies = [ "castaway", "cfg-if", "itoa", + "rustversion", "ryu", "static_assertions", ] [[package]] name = "crossterm" -version = "0.27.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" +checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ "bitflags", "crossterm_winapi", - "libc", "mio", "parking_lot", + "rustix", "signal-hook", "signal-hook-mio", "winapi", @@ -106,11 +106,21 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys", +] + [[package]] name = "fastrand" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "hashbrown" @@ -128,6 +138,22 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "instability" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b23a0c8dfe501baac4adf6ebbfa6eddf8f0c07f56b058cc1288017e32397846c" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "itertools" version = "0.13.0" @@ -145,9 +171,15 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "libc" -version = "0.2.155" +version = "0.2.159" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "lock_api" @@ -167,19 +199,20 @@ checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "lru" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3262e75e648fce39813cb56ac41f3c3e3f65217ebf3844d818d1f9398cfb0dc" +checksum = "37ee39891760e7d94734f6f63fedc29a2e4a152f836120753a72503f09fcf904" dependencies = [ "hashbrown", ] [[package]] name = "mio" -version = "0.8.11" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ + "hermit-abi", "libc", "log", "wasi", @@ -212,7 +245,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -232,27 +265,27 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] [[package]] name = "ratatui" -version = "0.27.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16546c5b5962abf8ce6e2881e722b4e0ae3b6f1a08a26ae3573c55853ca68d3" +checksum = "fdef7f9be5c0122f890d58bdf4d964349ba6a6161f705907526d891efabba57d" dependencies = [ "bitflags", "cassowary", "compact_str", "crossterm", + "instability", "itertools", "lru", "paste", - "stability", "strum", "strum_macros", "unicode-segmentation", @@ -269,6 +302,19 @@ dependencies = [ "bitflags", ] +[[package]] +name = "rustix" +version = "0.38.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + [[package]] name = "rustversion" version = "1.0.17" @@ -299,9 +345,9 @@ dependencies = [ [[package]] name = "signal-hook-mio" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" dependencies = [ "libc", "mio", @@ -323,16 +369,6 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" -[[package]] -name = "stability" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d904e7009df136af5297832a3ace3370cd14ff1546a232f4f185036c2736fcac" -dependencies = [ - "quote", - "syn", -] - [[package]] name = "static_assertions" version = "1.1.0" @@ -363,9 +399,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.71" +version = "2.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b146dcf730474b4bcd16c311627b31ede9ab149045db4d6088b3becaea046462" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" dependencies = [ "proc-macro2", "quote", @@ -374,15 +410,15 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-segmentation" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-truncate" @@ -397,15 +433,15 @@ dependencies = [ [[package]] name = "unicode-width" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wasi" @@ -437,26 +473,11 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "windows-targets", ] [[package]] @@ -465,46 +486,28 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -517,48 +520,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" diff --git a/Cargo.toml b/Cargo.toml index 45d9467..b0f1c25 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,5 @@ keywords = ["conway", "game-of-life", "tui"] categories = ["games"] [dependencies] -crossterm = "0.27.0" -fastrand = "2.0.1" -ratatui = "0.27.0" +fastrand = "2.1.1" +ratatui = "0.28.1" diff --git a/src/app.rs b/src/app.rs index 6255437..42b0ba6 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,6 +1,5 @@ -use crossterm::terminal::size; - use crate::{shapes, Universe, DEF_DUR}; +use ratatui::crossterm::terminal; use std::time::Duration; pub struct App { @@ -8,13 +7,12 @@ pub struct App { pub i: usize, pub poll_t: Duration, pub paused: bool, - pub wh: u16, + pub wh: (u16, u16), } impl Default for App { fn default() -> Self { let i = 0; - let wh = size().expect("couldn't get terminal size"); - let wh = (wh.1 + 10) * 3; + let wh = terminal::size().expect("couldn't get terminal size"); App { wh, universe: shapes::get(wh, i).unwrap(), @@ -81,10 +79,10 @@ impl App { } pub fn next(&mut self) { - if self.i + 1 != shapes::N as usize { - self.i += 1; - } else { + if self.i + 1 == shapes::N as usize { self.i = 0; + } else { + self.i += 1; } if let Ok(shape) = shapes::get(self.wh, self.i) { self.universe = shape; diff --git a/src/kmaps.rs b/src/kmaps.rs index f61510c..7d450f6 100644 --- a/src/kmaps.rs +++ b/src/kmaps.rs @@ -1,4 +1,4 @@ -use crossterm::event::KeyCode; +use ratatui::crossterm::event::KeyCode; pub const PLAY_PAUSE: KeyCode = KeyCode::Char(' '); diff --git a/src/lib.rs b/src/lib.rs index 6542835..f511d5a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,5 @@ -use ratatui::{style::Color, widgets::canvas::Shape}; - use crate::shapes::HandleError; +use ratatui::{style::Color, widgets::canvas::Shape}; use std::time::Duration; /// Default poll duration @@ -34,7 +33,6 @@ impl From for Cell { } } impl Cell { - #[allow(unused)] fn toggle(&mut self) { *self = match *self { Cell::Dead => Cell::Alive, @@ -74,7 +72,7 @@ impl, U2: Into> std::ops::IndexMut<(U1, U2)> for Universe } impl Universe { - fn get_idx, U2: Into>(&self, row: U1, col: U2) -> usize { + fn get_idx(&self, row: impl Into, col: impl Into) -> usize { let row = row.into(); let col = col.into(); // Convert (x;y) to index @@ -127,7 +125,7 @@ impl Universe { /// # Errors /// /// if shape can't fit universe - fn from_figur(wh: u16, figur: &[String]) -> Result { + fn from_figur(wh: (u16, u16), figur: &[String]) -> Result { let figur = Universe::from_vec_str(figur); let figur_alive = figur .cells @@ -135,18 +133,18 @@ impl Universe { .filter(|cell| cell == &&Cell::Alive) .count(); - if wh < figur.height() || wh < figur.width() { + if wh.0 < figur.width() || wh.1 < figur.height() { return Err(HandleError::TooBig); } - let cells = vec![Cell::default(); wh.pow(2).into()]; + let cells = vec![Cell::default(); (wh.0 * wh.1).into()]; let mut univ = Universe { cells, - width: wh, - height: wh, + width: wh.0, + height: wh.1, }; - let (start_row, start_col) = ((wh - figur.height()) / 2, (wh - figur.width()) / 2); + let (start_row, start_col) = ((wh.1 - figur.height()) / 2, (wh.0 - figur.width()) / 2); let mut j = 0; for row in start_row as usize..start_row as usize + figur.height() as usize { diff --git a/src/main.rs b/src/main.rs index 8fa0f1a..5f0743c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,35 +1,17 @@ use cgol_tui::{app::App, *}; -use crossterm::{ +use ratatui::crossterm::{ event::{self, poll, Event, KeyEventKind}, - execute, - terminal::{ - disable_raw_mode, enable_raw_mode, size, EnterAlternateScreen, LeaveAlternateScreen, - }, + terminal::size, }; -use ratatui::{ - backend::{Backend, CrosstermBackend}, - Terminal, -}; -use std::{io, panic}; +use ratatui::{backend::Backend, Terminal}; +use std::io; fn main() -> Result<(), Box> { - // Define a custom panic hook to reset the terminal properties. - // This way, you won't have your terminal messed up if an unexpected error happens. - let panic_hook = panic::take_hook(); - panic::set_hook(Box::new(move |panic| { - disable_raw_mode().expect("couldn't disable raw_mode"); - execute!(io::stdout(), LeaveAlternateScreen).expect("couldn't leave alternate screen"); - panic_hook(panic); - })); - // init terminal - enable_raw_mode()?; - execute!(io::stdout(), EnterAlternateScreen)?; - - let backend = CrosstermBackend::new(io::stdout()); - let mut terminal = Terminal::new(backend)?; + let mut terminal = ratatui::try_init()?; // create app and run it with width and height from terminal size + // FIXME: use render_are.area() for size determination let wh = size()?; let wh = ((wh.1 - 3) * 4).min((wh.0 / 2 - 2) * 2); let mut app = App::new(wh); @@ -37,9 +19,7 @@ fn main() -> Result<(), Box> { let res = run_app(&mut terminal, &mut app); // reset terminal - disable_raw_mode()?; - execute!(terminal.backend_mut(), LeaveAlternateScreen)?; - terminal.show_cursor()?; + ratatui::try_restore()?; if let Err(err) = res { println!("Error: {err:?}"); @@ -63,30 +43,14 @@ fn run_app(terminal: &mut Terminal, app: &mut App) -> io::Result< return Ok(()); } match key.code { - kmaps::QUIT => { - break; - } - kmaps::SLOWER => { - app.slower(false); - } - kmaps::FASTER => { - app.faster(false); - } - kmaps::PLAY_PAUSE => { - app.play_pause(&mut prev_poll_t); - } - kmaps::RESTART => { - app.restart(); - } - kmaps::NEXT => { - app.next(); - } - kmaps::PREV => { - app.prev(); - } - kmaps::RESET => { - *app = app::App::default(); - } + kmaps::QUIT => break, + kmaps::SLOWER => app.slower(false), + kmaps::FASTER => app.faster(false), + kmaps::PLAY_PAUSE => app.play_pause(&mut prev_poll_t), + kmaps::RESTART => app.restart(), + kmaps::NEXT => app.next(), + kmaps::PREV => app.prev(), + kmaps::RESET => *app = App::default(), _ => {} } } else { diff --git a/src/shapes.rs b/src/shapes.rs index 6e6daaf..1eafe59 100644 --- a/src/shapes.rs +++ b/src/shapes.rs @@ -16,7 +16,7 @@ pub enum HandleError { /// /// `from_figur()` /// `IndexOutOfRange` -pub fn get(wh: u16, i: usize) -> Result { +pub fn get(wh: (u16, u16), i: usize) -> Result { if i > shapes::N as usize { return Err(HandleError::OutOfRange); } diff --git a/src/ui.rs b/src/ui.rs index 2e9769e..e0c02ff 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -17,7 +17,7 @@ pub fn ui(f: &mut Frame, app: &App) { let chunks = Layout::default() .direction(Direction::Vertical) .constraints([Constraint::Min(0), Constraint::Length(1)]) - .split(f.size()); + .split(f.area()); let main_chunks = Layout::default() .direction(Direction::Horizontal) From 4215cea3bd80ec97325b3ef0ba43d4d704ba9f7c Mon Sep 17 00:00:00 2001 From: Jeromos Kovacs Date: Fri, 27 Sep 2024 17:06:24 +0200 Subject: [PATCH 08/12] misc(tests): serious amount of new ones --- src/shapes.rs | 181 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 140 insertions(+), 41 deletions(-) diff --git a/src/shapes.rs b/src/shapes.rs index 1eafe59..8e98a1d 100644 --- a/src/shapes.rs +++ b/src/shapes.rs @@ -16,33 +16,41 @@ pub enum HandleError { /// /// `from_figur()` /// `IndexOutOfRange` -pub fn get(wh: (u16, u16), i: usize) -> Result { +pub fn get(area: Area, i: usize) -> Result { if i > shapes::N as usize { return Err(HandleError::OutOfRange); } match i { - 0 => Universe::from_figur(wh, &shapes::featherweigth_spaceship()), + 0 => Universe::from_figur(area, &shapes::featherweigth_spaceship()), - 1 => Universe::from_figur(wh, &shapes::copperhead()), + 1 => Universe::from_figur(area, &shapes::copperhead()), - 2 => Universe::from_figur(wh, &shapes::gosper_glider_gun()), + 2 => Universe::from_figur(area, &shapes::gosper_glider_gun()), - 3 => Ok(shapes::stripes(wh)), + 3 => Ok(shapes::stripes(area)), - 4 => Ok(shapes::rand(wh)), + 4 => Ok(shapes::rand(area)), - 5 => Universe::from_figur(wh, &shapes::rabbits()), + 5 => Universe::from_figur(area, &shapes::rabbits()), - 6 => Universe::from_figur(wh, &shapes::bonk_tie()), + 6 => Universe::from_figur(area, &shapes::bonk_tie()), - 7 => Universe::from_figur(wh, &shapes::acorn()), + 7 => Universe::from_figur(area, &shapes::acorn()), - 8 => Ok(shapes::full(wh)), + 8 => Ok(shapes::full(area)), _ => Err(HandleError::OutOfRange), } } +#[test] +fn get_test() { + let area = Area::new(40u8, 40u8); + for i in 0..N { + assert!(get(area, i.into()).is_ok()); + } + assert!(get(area, N.into()).is_err()); +} pub fn copperhead() -> Vec { // ["_".repeat(5), "#_##".into(), "_".repeat(7), "#".into(), "_".repeat(6), "#".into(), "___##___#__###_"] @@ -109,6 +117,21 @@ pub fn gosper_glider_gun() -> Vec { pub fn featherweigth_spaceship() -> Vec { ["__#".into(), "#_#".into(), "_##".into()].to_vec() } +#[test] +fn featherweight_spaceship_test() { + let area = Area::new(3u8, 3u8); + let m = Universe::from_vec_str(&featherweigth_spaceship()); + assert_eq!(m.area, area); + dbg!(&m); + let alive = [(0u8, 2u8), (1u8, 0u8), (1u8, 2u8), (2u8, 1u8), (2u8, 2u8)]; + for alive_cell in alive { + dbg!(alive_cell); + assert_eq!(m.get(alive_cell), Some(&Cell::Alive)); + } + assert!(m.get((3u8, 3u8)).is_none()); + assert!(m.get((3u8, 4u8)).is_none()); + assert!(m.get((4u8, 3u8)).is_none()); +} /// 8x4 pub fn rabbits() -> Vec { @@ -120,6 +143,30 @@ pub fn rabbits() -> Vec { ] .to_vec() } +#[test] +fn rabbits_test() { + let area = Area::new(8u8, 4u8); + let m = Universe::from_vec_str(&rabbits()); + assert_eq!(m.area, area); + dbg!(&m); + let alive = [ + (0u8, 0u8), + (0u8, 6u8), + (1u8, 2u8), + (1u8, 6u8), + (2u8, 2u8), + (2u8, 5u8), + (2u8, 7u8), + (3u8, 1u8), + (3u8, 3u8), + ]; + for alive_cell in alive { + dbg!(alive_cell); + assert_eq!(m.get(alive_cell), Some(&Cell::Alive)); + } + assert!(m.get((4u8, 8u8)).is_none()); + assert!(m.get((8u8, 4u8)).is_none()); +} /// 3×5 pub fn bonk_tie() -> Vec { @@ -132,33 +179,65 @@ pub fn bonk_tie() -> Vec { ] .to_vec() } +#[test] +fn bonk_tie_test() { + let area = Area::new(3u8, 5u8); + let m = Universe::from_vec_str(&bonk_tie()); + assert_eq!(m.area, area); + dbg!(&m); + let alive = [ + (0u8, 0u8), + (0u8, 1u8), + (1u8, 0u8), + (1u8, 1u8), + (2u8, 2u8), + (3u8, 2u8), + (4u8, 2u8), + ]; + for alive_cell in alive { + dbg!(alive_cell); + assert_eq!(m.get(alive_cell), Some(&Cell::Alive)); + } + assert!(m.get((4u8, 3u8)).is_none()); + assert!(m.get((3u8, 4u8)).is_none()); +} /// 7×3 pub fn acorn() -> Vec { ["_#_____".into(), "___#___".into(), "##__###".into()].to_vec() } - -/// `wh`x`wh` -pub fn rand(wh: u16) -> Universe { - let cells = (0..wh * wh) - .map(|_i| { - if fastrand::bool() { - Cell::Alive - } else { - Cell::Dead - } - }) - .collect(); - Universe { - width: wh, - height: wh, - cells, +#[test] +fn acorn_test() { + let area = Area::new(7u8, 3u8); + let m = Universe::from_vec_str(&acorn()); + assert_eq!(m.area, area); + dbg!(&m); + let alive = [ + (0u8, 1u8), + (1u8, 3u8), + (2u8, 0u8), + (2u8, 1u8), + (2u8, 4u8), + (2u8, 5u8), + (2u8, 6u8), + ]; + for alive_cell in alive { + dbg!(alive_cell); + assert_eq!(m.get(alive_cell), Some(&Cell::Alive)); } + assert!(m.get((4u8, 3u8)).is_none()); + assert!(m.get((3u8, 4u8)).is_none()); } -/// `wh`x`wh` -pub fn stripes(wh: u16) -> Universe { - let cells = (0..wh * wh) +/// `area.len()` +pub fn rand(area: Area) -> Universe { + let cells = (0..area.len()).map(|_i| fastrand::bool().into()).collect(); + Universe { area, cells } +} + +/// `area.len()` +pub fn stripes(area: Area) -> Universe { + let cells = (0..area.len()) .map(|i| { if i % 2 == 0 || i % 7 == 0 { Cell::Alive @@ -167,21 +246,41 @@ pub fn stripes(wh: u16) -> Universe { } }) .collect(); - Universe { - width: wh, - height: wh, - cells, - } + Universe { area, cells } +} +#[test] +fn stripes_test() { + let area = Area::new(0u8, 0u8); + let m = stripes(area); + assert!(m.cells.is_empty()); + assert_eq!(m.area, area); + dbg!(&m); + assert!(m.get((4u8, 3u8)).is_none()); + assert!(m.get((3u8, 4u8)).is_none()); + assert!(m.get((0u8, 1u8)).is_none()); + assert!(m.get((1u8, 0u8)).is_none()); } -/// `wh`x`wh` -pub fn full(wh: u16) -> Universe { - let cells = vec![Cell::Alive; wh as usize * wh as usize]; - Universe { - width: wh, - height: wh, - cells, +/// `area.len()` +pub fn full(area: Area) -> Universe { + let cells = vec![Cell::Alive; area.len()]; + Universe { area, cells } +} +#[test] +fn full_test() { + let area = Area::new(4u8, 3u8); + let m = full(area); + assert_eq!(m.area, area); + assert!(m.cells.iter().all(|j| *j == Cell::Alive)); + dbg!(&m); + for i in 0..m.height() - 1 { + for j in 0..m.width() - 1 { + dbg!((i, j)); + assert_eq!(m.get((i, j)), Some(&Cell::Alive)); + } } + assert!(m.get((4u8, 3u8)).is_none()); + assert!(m.get((3u8, 4u8)).is_none()); } pub fn two_engine_cordership() -> String { From 5d20f0d3ffbaa26ff1e060f567289c99899ce4d9 Mon Sep 17 00:00:00 2001 From: Jeromos Kovacs Date: Sat, 28 Sep 2024 15:20:46 +0200 Subject: [PATCH 09/12] fix: various --- .gitignore | 1 + Cargo.lock | 11 ++ Cargo.toml | 2 + src/app.rs | 32 ++-- src/lib.rs | 222 ++++++++++++++++++---------- src/main.rs | 36 ++--- src/shapes.rs | 117 ++++++++++++--- src/tests.rs | 398 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/ui.rs | 70 +++++---- 9 files changed, 721 insertions(+), 168 deletions(-) create mode 100644 src/tests.rs diff --git a/.gitignore b/.gitignore index ea8c4bf..909e903 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +.cgoltui.log diff --git a/Cargo.lock b/Cargo.lock index a2b23d2..536f61e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -58,6 +58,8 @@ name = "cgol-tui" version = "0.3.1" dependencies = [ "fastrand", + "fern", + "log", "ratatui", ] @@ -122,6 +124,15 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" +[[package]] +name = "fern" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f0c14694cbd524c8720dd69b0e3179344f04ebb5f90f2e4a440c6ea3b2f1ee" +dependencies = [ + "log", +] + [[package]] name = "hashbrown" version = "0.14.5" diff --git a/Cargo.toml b/Cargo.toml index b0f1c25..607e8a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,4 +12,6 @@ categories = ["games"] [dependencies] fastrand = "2.1.1" +fern = "0.6.2" +log = "0.4.22" ratatui = "0.28.1" diff --git a/src/app.rs b/src/app.rs index 42b0ba6..5e660e3 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,5 +1,4 @@ -use crate::{shapes, Universe, DEF_DUR}; -use ratatui::crossterm::terminal; +use crate::{shapes, Area, Universe, DEF_DUR}; use std::time::Duration; pub struct App { @@ -7,27 +6,25 @@ pub struct App { pub i: usize, pub poll_t: Duration, pub paused: bool, - pub wh: (u16, u16), + pub area: Area, } impl Default for App { fn default() -> Self { - let i = 0; - let wh = terminal::size().expect("couldn't get terminal size"); App { - wh, - universe: shapes::get(wh, i).unwrap(), - i, + area: Area::default(), + universe: Universe::default(), + i: 0, poll_t: DEF_DUR, paused: false, } } } impl App { - pub fn new(wh: u16) -> Self { + pub fn new(area: Area) -> Self { let i = 0; App { - wh, - universe: shapes::get(wh, i).unwrap(), + area, + universe: shapes::get(area, i).unwrap(), i, poll_t: DEF_DUR, paused: false, @@ -36,10 +33,6 @@ impl App { // pub fn render_universe(&self) { // println!("{}", self.universe); // } - pub fn set_wh(&mut self) { - let wh = size().expect("couldn't get terminal size"); - self.wh = (wh.1 + 10) * 3; - } pub fn play_pause(&mut self, prev_poll_t: &mut Duration) { if self.paused { @@ -51,8 +44,9 @@ impl App { self.paused = !self.paused; } pub fn restart(&mut self) { - self.universe = - shapes::get(self.wh, self.i).expect("display area is too small to fit current shape"); + self.universe = shapes::get(self.area, self.i) + .inspect_err(|e| log::error!("{e:?}")) + .expect("display area is too small to fit current shape"); } pub fn tick(&mut self) { @@ -84,7 +78,7 @@ impl App { } else { self.i += 1; } - if let Ok(shape) = shapes::get(self.wh, self.i) { + if let Ok(shape) = shapes::get(self.area, self.i) { self.universe = shape; } else { eprintln!("couldn't switch to next shape"); @@ -96,7 +90,7 @@ impl App { } else { self.i = shapes::N as usize - 1; } - if let Ok(shape) = shapes::get(self.wh, self.i) { + if let Ok(shape) = shapes::get(self.area, self.i) { self.universe = shape; } else { eprintln!("couldn't switch to previous shape"); diff --git a/src/lib.rs b/src/lib.rs index f511d5a..af93457 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,8 +4,6 @@ use std::time::Duration; /// Default poll duration pub const DEF_DUR: Duration = Duration::from_millis(400); -/// Default Width and Height -// pub const DEF_WH: u16 = 32; /// App pub mod app; @@ -16,6 +14,48 @@ pub mod shapes; /// ui pub mod ui; +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)] +pub struct Area { + pub width: u16, + pub height: u16, +} +impl Area { + pub fn new(width: impl Into, height: impl Into) -> Self { + Area { + width: width.into(), + height: height.into(), + } + } + pub fn with_width(self, width: impl Into) -> Self { + Self::new(width, self.height) + } + pub fn with_height(self, height: impl Into) -> Self { + Self::new(self.width, height) + } + pub fn add_to_width(self, width: impl Into) -> Self { + self.with_width((self.width as i32 + width.into()) as u16) + } + pub fn add_to_height(self, height: impl Into) -> Self { + self.with_height((self.height as i32 + height.into()) as u16) + } + pub const fn len(&self) -> usize { + self.width as usize * self.height as usize + } + + #[must_use] + pub const fn is_empty(&self) -> bool { + self.len() == 0 + } +} +impl, U2: Into> From<(U1, U2)> for Area { + fn from(val: (U1, U2)) -> Self { + Self { + width: val.0.into(), + height: val.1.into(), + } + } +} + /// information about one `Cell`: either `Dead` or `Alive` #[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] pub enum Cell { @@ -42,10 +82,9 @@ impl Cell { } /// the `Universe` in which game plays. Represented as a `Vec` of `Cell`s. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Default)] pub struct Universe { - width: u16, - height: u16, + area: Area, cells: Vec, } impl, U2: Into> std::ops::Index<(U1, U2)> for Universe { @@ -75,23 +114,70 @@ impl Universe { fn get_idx(&self, row: impl Into, col: impl Into) -> usize { let row = row.into(); let col = col.into(); + assert!( + (0..self.area.height).contains(&(row as u16)), + "index out of range: len is {}, but index is {row}", + self.area.height, + ); + assert!( + (0..self.area.width).contains(&(col as u16)), + "index out of range: len is {}, but index is {col}", + self.area.width, + ); + // Convert (x;y) to index + let idx = (row * self.area.width as usize) + col; + assert!( + idx < self.cells.len(), + "index out of range: len is {}, but index is {idx}", + self.cells.len() + ); + idx + } + fn get_idx_res(&self, row: impl Into, col: impl Into) -> Option { + let row = row.into(); + let col = col.into(); + if !(0..self.area.height).contains(&(row as u16)) { + log::debug!("row is {row}, but len is {}", self.area.height); + return None; + } + if !(0..self.area.width).contains(&(col as u16)) { + log::debug!("col is {col}, but len is {}", self.area.width); + return None; + } // Convert (x;y) to index - (row * self.width as usize) + col + let idx = (row * self.area.width as usize) + col; + if idx >= self.cells.len() { + log::debug!("idx: {idx}, len: {}", self.cells.len()); + return None; + } + // log::debug!("idx: {idx}"); + Some(idx) + } + pub fn get(&self, idx: (impl Into, impl Into)) -> Option<&Cell> { + // log::debug!("get()"); + let idx = self.get_idx_res(idx.0, idx.1)?; + self.cells.get(idx) } + // pub fn get_mut(&mut self, idx: (impl Into, impl Into)) -> Option<&mut Cell> { + // // log::debug!("get_mut()"); + // let idx = self.get_idx(idx.0, idx.1); + // self.cells.get_mut(idx) + // } fn live_neighbour_count(&self, row: u16, col: u16) -> u8 { let mut sum = 0; - for delta_row in [self.height - 1, 0, 1] { - for delta_col in [self.width - 1, 0, 1] { + for delta_row in [self.area.height - 1, 0, 1] { + for delta_col in [self.area.width - 1, 0, 1] { if delta_row == 0 && delta_col == 0 { continue; } - let neighbour_row = (row + delta_row) % self.height; - let neighbour_col = (col + delta_col) % self.width; + let neighbour_row = (row + delta_row) % self.area.height; + let neighbour_col = (col + delta_col) % self.area.width; sum += self[(neighbour_row, neighbour_col)] as u8; + // sum += *self.get((neighbour_row, neighbour_col)).unwrap() as u8; } } sum @@ -102,10 +188,13 @@ impl Universe { let mut cells = Vec::new(); for line in s { + if line.starts_with('!') { + continue; + } for ch in line.chars() { - if ch == '#' || ch == '1' { + if ch == '#' || ch == '1' || ch == 'O' { cells.push(Cell::Alive); - } else if ch == '_' || ch == ' ' || ch == '0' { + } else if ch == '_' || ch == ' ' || ch == '0' || ch == '.' { cells.push(Cell::Dead); } else { eprintln!("can't do nothing with this character: {ch}"); @@ -113,11 +202,15 @@ impl Universe { } } - Universe { + let area = Area { width: s[0].len() as u16, height: s.len() as u16, - cells, - } + }; + Universe { area, cells } + } + fn from_str(s: &str) -> Self { + let v = s.trim().lines().map(|l| l.into()).collect::>(); + Self::from_vec_str(&v) } /// Create universe with width, height: inserting starting shape into the middle @@ -125,31 +218,31 @@ impl Universe { /// # Errors /// /// if shape can't fit universe - fn from_figur(wh: (u16, u16), figur: &[String]) -> Result { + fn from_figur(area: Area, figur: &[String]) -> Result { let figur = Universe::from_vec_str(figur); let figur_alive = figur .cells .iter() - .filter(|cell| cell == &&Cell::Alive) + .filter(|cell| *cell == &Cell::Alive) .count(); - if wh.0 < figur.width() || wh.1 < figur.height() { + if area < figur.area { return Err(HandleError::TooBig); } - let cells = vec![Cell::default(); (wh.0 * wh.1).into()]; - let mut univ = Universe { - cells, - width: wh.0, - height: wh.1, - }; + let cells = vec![Cell::default(); area.len()]; + let mut univ = Universe { cells, area }; - let (start_row, start_col) = ((wh.1 - figur.height()) / 2, (wh.0 - figur.width()) / 2); + let (start_row, start_col) = ( + (area.height - figur.height()) / 2, + (area.width - figur.width()) / 2, + ); let mut j = 0; for row in start_row as usize..start_row as usize + figur.height() as usize { for i in 0..figur.width() as usize { univ[(row, start_col as usize + i)] = figur.cells[j]; + // *univ.get_mut((row, start_col as usize + i)).unwrap() = figur.cells[j]; j += 1; } } @@ -157,7 +250,7 @@ impl Universe { let univ_alive = univ .cells .iter() - .filter(|cell| cell == &&Cell::Alive) + .filter(|cell| *cell == &Cell::Alive) .count(); if figur_alive == univ_alive { Ok(univ) @@ -170,9 +263,10 @@ impl Universe { pub fn tick(&mut self) { let mut next = self.clone(); - for row in 0..self.width { - for col in 0..self.height { + for row in 0..self.height() { + for col in 0..self.width() { let idx = (row, col); + // let cell = self.get(idx).unwrap(); let cell = self[idx]; let live_neighbours = self.live_neighbour_count(row, col); @@ -194,6 +288,7 @@ impl Universe { }; next[idx] = next_cell; + // *next.get_mut(idx).unwrap() = next_cell; } } @@ -201,70 +296,41 @@ impl Universe { } pub fn width(&self) -> u16 { - self.width + self.area.width } pub fn height(&self) -> u16 { - self.height + self.area.height } } impl Shape for Universe { fn draw(&self, painter: &mut ratatui::widgets::canvas::Painter) { - for y in 0..self.height { - for x in 0..self.width { - match self[(x, y)] { - Cell::Alive => painter.paint(y.into(), x.into(), Color::White), - Cell::Dead => continue, + for y in 0..self.height() { + for x in 0..self.width() { + match self.get((y, x)) { + Some(Cell::Alive) => painter.paint(x.into(), y.into(), Color::White), + Some(Cell::Dead) => continue, + None => unreachable!("got None"), } } } } } -// impl fmt::Display for Universe { -// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -// writeln!(f, "╭{}╮\r", "─".repeat(self.width as usize * 2))?; -// for line in self.cells.as_slice().chunks(self.width as usize) { -// write!(f, "│")?; -// for &cell in line { -// let symbol = if cell == Cell::Dead { ' ' } else { '◼' }; // ◻ -// write!(f, "{symbol} ")?; -// } -// writeln!(f, "│\r")?; -// } -// writeln!(f, "╰{}╯\r", "─".repeat(self.width as usize * 2))?; -// Ok(()) -// } -// } -#[cfg(test)] -mod tests { - use super::*; - - const WH: u16 = 20; - - fn gen_uni(w: u16, h: u16, cells: &[bool]) -> Universe { - let cells = cells.iter().map(|c| (*c).into()).collect::>(); - Universe { - width: w, - height: h, - cells, +impl std::fmt::Display for Universe { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "╭{}╮\r", "─".repeat(self.area.width as usize * 2))?; + for line in self.cells.as_slice().chunks(self.area.width as usize) { + write!(f, "│")?; + for &cell in line { + let symbol = if cell == Cell::Dead { '◻' } else { '◼' }; // ◻ + write!(f, "{symbol} ")?; + } + writeln!(f, "│\r")?; } - } - - #[test] - fn rabbit_hole() { - let rabbit = shapes::featherweigth_spaceship(); - let rabbit_uni = Universe::from_vec_str(&rabbit); - let cells = [false, false, true, true, false, true, false, true, true]; - let uni = gen_uni(3, 3, &cells); - assert_eq!(rabbit_uni, uni); - } - #[test] - fn full() { - let full = shapes::full(WH); - let cells = [true; WH.pow(2) as usize]; - let uni = gen_uni(WH, WH, &cells); - assert_eq!(full, uni); + writeln!(f, "╰{}╯\r", "─".repeat(self.area.width as usize * 2)) } } +#[cfg(test)] +mod tests; diff --git a/src/main.rs b/src/main.rs index 5f0743c..2ce1548 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,29 +1,34 @@ use cgol_tui::{app::App, *}; -use ratatui::crossterm::{ - event::{self, poll, Event, KeyEventKind}, - terminal::size, -}; +use ratatui::crossterm::event::{self, poll, Event, KeyEventKind}; use ratatui::{backend::Backend, Terminal}; use std::io; fn main() -> Result<(), Box> { + // set up logger + fern::Dispatch::new() + // Add blanket level filter - + .level(log::LevelFilter::Debug) + // Output to stdout, files, and other Dispatch configurations + .chain( + std::fs::OpenOptions::new() + .create(true) + .append(true) + .open(".cgoltui.log")?, + ) + // Apply globally + .apply()?; + // init terminal let mut terminal = ratatui::try_init()?; - // create app and run it with width and height from terminal size - // FIXME: use render_are.area() for size determination - let wh = size()?; - let wh = ((wh.1 - 3) * 4).min((wh.0 / 2 - 2) * 2); - let mut app = App::new(wh); - + let mut app = App::default(); let res = run_app(&mut terminal, &mut app); // reset terminal ratatui::try_restore()?; - if let Err(err) = res { - println!("Error: {err:?}"); - } + // if any error has occured while executing, print it in cooked mode + res.inspect_err(|e| println!("error: {e:?}"))?; Ok(()) } @@ -32,15 +37,13 @@ fn run_app(terminal: &mut Terminal, app: &mut App) -> io::Result< let mut prev_poll_t = app.poll_t; loop { - app.set_wh(); - terminal.draw(|f| ui::ui(f, app))?; // Wait up to `poll_t` for another event if poll(app.poll_t)? { if let Event::Key(key) = event::read()? { if key.kind != KeyEventKind::Press { - return Ok(()); + continue; } match key.code { kmaps::QUIT => break, @@ -55,7 +58,6 @@ fn run_app(terminal: &mut Terminal, app: &mut App) -> io::Result< } } else { // resize and restart - app.set_wh(); app.restart(); } } else { diff --git a/src/shapes.rs b/src/shapes.rs index 8e98a1d..c9608b7 100644 --- a/src/shapes.rs +++ b/src/shapes.rs @@ -1,7 +1,7 @@ use super::*; /// Number of currently supported shapes -pub const N: u8 = 9; +pub const N: u8 = 10; #[derive(Debug)] pub enum HandleError { @@ -23,23 +23,15 @@ pub fn get(area: Area, i: usize) -> Result { match i { 0 => Universe::from_figur(area, &shapes::featherweigth_spaceship()), - 1 => Universe::from_figur(area, &shapes::copperhead()), - 2 => Universe::from_figur(area, &shapes::gosper_glider_gun()), - 3 => Ok(shapes::stripes(area)), - 4 => Ok(shapes::rand(area)), - 5 => Universe::from_figur(area, &shapes::rabbits()), - 6 => Universe::from_figur(area, &shapes::bonk_tie()), - 7 => Universe::from_figur(area, &shapes::acorn()), - 8 => Ok(shapes::full(area)), - + 9 => Ok(shapes::frame(area)), _ => Err(HandleError::OutOfRange), } } @@ -52,17 +44,106 @@ fn get_test() { assert!(get(area, N.into()).is_err()); } +// height: 5 +// width: 5 +// 01234 +// 0.....0 +// 1.---.1 +// 2.|.|.2 +// 3.---.3 +// 4.....4 +// 01234 +pub fn frame(area: Area) -> Universe { + let cells = vec![Cell::Dead; area.len()]; + let mut univ = Universe { area, cells }; + if area.height < 3 || area.width < 3 { + return univ; + } + // horizontal + for i in [1, area.height - 2] { + for j in 1..area.width - 1 { + univ[(i, j)] = Cell::Alive; + } + } + + // vertical + for j in [1, area.width - 2] { + for i in 2..area.height - 2 { + univ[(i, j)] = Cell::Alive; + } + } + univ +} +#[test] +fn frame_test00() { + let area = Area::new(3u8, 2u8); + let univ = Universe::from_vec_str(&["___".to_owned(), "___".to_owned()]); + let frame = frame(area); + print!("{frame}"); + assert_eq!(univ, frame); +} +#[test] +fn frame_test0() { + let area = Area::new(3u8, 3u8); + let univ = Universe::from_vec_str(&["___".to_owned(), "_#_".to_owned(), "___".to_owned()]); + let frame = frame(area); + print!("{frame}"); + assert_eq!(univ, frame); +} +#[test] +fn frame_test1() { + let area = Area::new(4u8, 4u8); + let univ = Universe::from_vec_str(&[ + "____".to_owned(), + "_##_".to_owned(), + "_##_".to_owned(), + "____".to_owned(), + ]); + let frame = frame(area); + print!("{frame}"); + assert_eq!(univ, frame); +} +#[test] +fn frame_test2() { + let area = Area::new(5u8, 5u8); + let univ = Universe::from_vec_str(&[ + "_____".to_owned(), + "_###_".to_owned(), + "_#_#_".to_owned(), + "_###_".to_owned(), + "_____".to_owned(), + ]); + let frame = frame(area); + print!("{frame}"); + assert_eq!(univ, frame); +} +#[test] +fn frame_test3() { + let area = Area::new(6u8, 6u8); + let univ = Universe::from_vec_str(&[ + "______".to_owned(), + "_####_".to_owned(), + "_#__#_".to_owned(), + "_#__#_".to_owned(), + "_####_".to_owned(), + "______".to_owned(), + ]); + let frame = frame(area); + print!("{frame}"); + assert_eq!(univ, frame); +} + pub fn copperhead() -> Vec { // ["_".repeat(5), "#_##".into(), "_".repeat(7), "#".into(), "_".repeat(6), "#".into(), "___##___#__###_"] [ - "_____#_##___".to_owned(), - "____#______#".to_owned(), - "___##___#__#".to_owned(), - "##_#_____##_".to_owned(), - "##_#_____##_".to_owned(), - "___##___#__#".to_owned(), - "____#______#".to_owned(), - "_____#_##___".to_owned(), + "_____#_##___".into(), + "____#______#".into(), + "___##___#__#".into(), + "##_#_____##_".into(), + "##_#_____##_".into(), + "___##___#__#".into(), + "____#______#".into(), + "_____#_##___".into(), ] .to_vec() } diff --git a/src/tests.rs b/src/tests.rs new file mode 100644 index 0000000..db823cf --- /dev/null +++ b/src/tests.rs @@ -0,0 +1,398 @@ +use super::*; + +fn gen_uni(area: Area, cells: &[bool]) -> Universe { + let cells = cells.iter().map(|c| (*c).into()).collect::>(); + Universe { area, cells } +} + +#[test] +fn rabbit_hole() { + let rabbit = shapes::featherweigth_spaceship(); + let rabbit_uni = Universe::from_vec_str(&rabbit); + let cells = [false, false, true, true, false, true, false, true, true]; + let uni = gen_uni((3u16, 3u16).into(), &cells); + assert_eq!(rabbit_uni, uni); +} +#[test] +fn full() { + let area = Area::new(20u16, 20u16); + let full = shapes::full(area); + let cells = vec![true; area.len()]; + let uni = gen_uni(area, &cells); + assert_eq!(full, uni); +} +#[test] +fn halp() { + let area = Area::new(2u8, 2u8); + let cells = vec![false, true, true, false]; + let univ = gen_uni(area, &cells); + + let idx: (u8, u8) = (0, 0); + let x = univ[idx]; + assert_eq!(x, false.into()); + assert_eq!(univ.get(idx), Some(&x)); + + let idx: (u8, u8) = (0, 1); + let x = univ[idx]; + assert_eq!(x, true.into()); + assert_eq!(univ.get(idx), Some(&x)); + + let idx: (u8, u8) = (1, 0); + let x = univ[idx]; + assert_eq!(x, true.into()); + assert_eq!(univ.get(idx), Some(&x)); + + let idx: (u8, u8) = (1, 1); + let x = univ[idx]; + assert_eq!(x, false.into()); + assert_eq!(univ.get(idx), Some(&x)); +} + +#[test] +fn bigass_tickler() { + let area = Area::new(8u8, 8u8); + let mut univ = Universe::from_figur(area, &shapes::featherweigth_spaceship()).unwrap(); + + let exp_unis = [ + "\ +........ +........ +....#... +..#.#... +...##... +........ +........ +........", + "\ +........ +........ +...#.... +....##.. +...##... +........ +........ +........", + "\ +........ +........ +....#... +.....#.. +...###.. +........ +........ +........", + "\ +........ +........ +........ +...#.#.. +....##.. +....#... +........ +........", + "\ +........ +........ +........ +.....#.. +...#.#.. +....##.. +........ +........", + "\ +........ +........ +........ +....#... +.....##. +....##.. +........ +........", + "\ +........ +........ +........ +.....#.. +......#. +....###. +........ +........", + "\ +........ +........ +........ +........ +....#.#. +.....##. +.....#.. +........", + "\ +........ +........ +........ +........ +......#. +....#.#. +.....##. +........", + "\ +........ +........ +........ +........ +.....#.. +......## +.....##. +........", + "\ +........ +........ +........ +........ +......#. +.......# +.....### +........", + "\ +........ +........ +........ +........ +........ +.....#.# +......## +......#.", + "\ +........ +........ +........ +........ +........ +.......# +.....#.# +......##", + "\ +........ +........ +........ +........ +........ +......#. +#......# +......##", + "\ +........ +........ +........ +........ +........ +.......# +#....... +#.....##", + "\ +.......# +........ +........ +........ +........ +........ +#.....#. +#......#", + "\ +#......# +........ +........ +........ +........ +........ +#....... +#.....#.", + "\ +#......# +........ +........ +........ +........ +........ +.......# +##......", + "\ +##.....# +........ +........ +........ +........ +........ +#....... +.#......", + "\ +##...... +#....... +........ +........ +........ +........ +........ +.#.....#", + "\ +.#.....# +##...... +........ +........ +........ +........ +........ +.#......", + "\ +.##..... +##...... +........ +........ +........ +........ +........ +#.......", + "\ +..#..... +###..... +........ +........ +........ +........ +........ +.#......", + "\ +#.#..... +.##..... +.#...... +........ +........ +........ +........ +........", + "\ +..#..... +#.#..... +.##..... +........ +........ +........ +........ +........", + "\ +.#...... +..##.... +.##..... +........ +........ +........ +........ +........", + "\ +..#..... +...#.... +.###.... +........ +........ +........ +........ +........", + "\ +........ +.#.#.... +..##.... +..#..... +........ +........ +........ +........", + "\ +........ +...#.... +.#.#.... +..##.... +........ +........ +........ +........", + "\ +........ +..#..... +...##... +..##.... +........ +........ +........ +........", + "\ +........ +...#.... +....#... +..###... +........ +........ +........ +........", + "\ +........ +........ +..#.#... +...##... +...#.... +........ +........ +........", + "\ +........ +........ +....#... +..#.#... +...##... +........ +........ +........", + ]; + + for exp_uni in exp_unis.map(Universe::from_str) { + println!("exp univ:\n{exp_uni}"); + println!("univ:\n{univ}"); + assert_eq!(univ, exp_uni); + univ.tick(); + } +} + +#[test] +fn neighbours() { + let univ = Universe::from_str( + "\ +.#.# +.##. +..#. +..#.", + ); + let nghbrs = |coord: (u16, u16)| -> u8 { univ.live_neighbour_count(coord.0, coord.1) }; + + // 1. row + assert_eq!(nghbrs((0, 0)), 3); + assert_eq!(nghbrs((0, 1)), 3); + assert_eq!(nghbrs((0, 2)), 5); + assert_eq!(nghbrs((0, 3)), 2); + + // 2. row + assert_eq!(nghbrs((1, 0)), 3); + assert_eq!(nghbrs((1, 1)), 3); + assert_eq!(nghbrs((1, 2)), 4); + assert_eq!(nghbrs((1, 3)), 3); + + // 3. row + assert_eq!(nghbrs((2, 0)), 1); + assert_eq!(nghbrs((2, 1)), 4); + assert_eq!(nghbrs((2, 2)), 3); + assert_eq!(nghbrs((2, 3)), 3); + + // 4. row + assert_eq!(nghbrs((3, 0)), 2); + assert_eq!(nghbrs((3, 1)), 3); + assert_eq!(nghbrs((3, 2)), 3); + assert_eq!(nghbrs((3, 3)), 3); +} diff --git a/src/ui.rs b/src/ui.rs index e0c02ff..7742c8f 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,4 +1,4 @@ -use crate::app::App; +use crate::{app::App, Area}; use ratatui::{ layout::{Constraint, Direction, Layout}, style::Stylize, @@ -7,46 +7,46 @@ use ratatui::{ Frame, }; -pub fn ui(f: &mut Frame, app: &App) { - // ____________________ - // | | | - // | | | - // | | | - // |__________|_________| +/// area of a braille character +const BRAILLE: Area = Area { + width: 2, + height: 4, +}; + +pub fn ui(f: &mut Frame, app: &mut App) { + // _cgol_______________ + // | | + // | | + // | | + // |____________________| // |____________________| let chunks = Layout::default() .direction(Direction::Vertical) - .constraints([Constraint::Min(0), Constraint::Length(1)]) + .constraints([Constraint::Fill(1), Constraint::Length(1)]) .split(f.area()); - let main_chunks = Layout::default() - .direction(Direction::Horizontal) - .constraints([Constraint::Percentage(50)]) - .split(chunks[0]); - let cgol = Block::default() .borders(Borders::ALL) .border_type(BorderType::Rounded) .title("Conway's Game of Life"); - // let universe = Paragraph::new(app.universe.to_string()).block(cgol); - // let universe = Canvas::new().block(cgol); + // 2 blocks less: border + let new_area: crate::Area = ( + chunks[0].width * BRAILLE.width - 2 * BRAILLE.width, + chunks[0].height * BRAILLE.height - 2 * BRAILLE.height, + ) + .into(); + // apply the area change + if app.area != new_area { + app.area = new_area; + app.restart(); + } let universe = Canvas::default() - // .x_bounds([0., main_chunks[0].height as f64 * 2. - 4.]) - // .y_bounds([0., main_chunks[0].height as f64 * 2. - 4.]) + // .x_bounds([0., chunks[0].height as f64 * 2. - 4.]) + // .y_bounds([0., chunks[0].height as f64 * 2. - 4.]) .paint(|ctx| ctx.draw(&app.universe)) .block(cgol); - f.render_widget(universe, main_chunks[0]); - - // f.render_widget( - // universe, - // Rect::new( - // 0, - // 0, - // main_chunks[0].height * 2 - 4, - // main_chunks[0].height - 1, - // ), - // ); + f.render_widget(universe, chunks[0]); let footer = Layout::default() .direction(Direction::Horizontal) @@ -54,21 +54,19 @@ pub fn ui(f: &mut Frame, app: &App) { .split(chunks[1]); let current_keys_hint = - "[q]uit, [r]estart, [R]eset, [n]ext, [p]rev, play[ ]pause, 'k': faster, 'j': slower" - .yellow(); + "[q]uit, [r]estart, [R]eset, [n]ext, [p]rev, play[ ]pause, speed: 'k' ↑, 'j' ↓".yellow(); - let poll_t = format!( - "Poll time: {}", + let poll_t = { if let std::time::Duration::MAX = app.poll_t { - "max".into() + "paused".into() } else { - format!("{:.0?}", app.poll_t) + format!("Poll time: {:.0?}", app.poll_t) } - ) + } .light_blue(); let div = " | ".white(); - let current_stats = vec![current_keys_hint, div.clone(), poll_t]; + let current_stats = vec![current_keys_hint, div, poll_t]; let footer_data = Line::from(current_stats); f.render_widget(footer_data, footer[0]); From 3dd40926b1fc1044c3ee4652ae51db16ca1090e1 Mon Sep 17 00:00:00 2001 From: Jeromos Kovacs Date: Sat, 28 Sep 2024 15:46:42 +0200 Subject: [PATCH 10/12] docs(README): updated; fix: cosmetics --- README.md | 10 +++++----- src/lib.rs | 9 +++++++-- src/ui.rs | 9 ++++----- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 0c77e36..151c086 100644 --- a/README.md +++ b/README.md @@ -15,14 +15,14 @@ clone the repo and run `cargo r` - [x] error handling - [x] publishing to crates.io - [x] changing to `Canvas` for rendering viewer block - -## Warning! - -If your terminal is messed up by accident fix: on Mac, Linux: `reset`, on Windows: exit the app. +- [ ] *maybe* the ability to parse `.cells` files ## Acknowledgements -The core of this app is adapted from the great [Rust-Wasm tutorial](https://rustwasm.github.io/docs/book/). +- The core of this app is adapted from the [Rust-Wasm tutorial](https://rustwasm.github.io/docs/book/). +- main dependencies: + - ratatui: ui + - crossterm: ratatui backend ## License diff --git a/src/lib.rs b/src/lib.rs index af93457..e08879a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -208,8 +208,13 @@ impl Universe { }; Universe { area, cells } } + fn from_str(s: &str) -> Self { - let v = s.trim().lines().map(|l| l.into()).collect::>(); + let v = s + .trim() + .lines() + .map(std::convert::Into::into) + .collect::>(); Self::from_vec_str(&v) } @@ -231,7 +236,7 @@ impl Universe { } let cells = vec![Cell::default(); area.len()]; - let mut univ = Universe { cells, area }; + let mut univ = Universe { area, cells }; let (start_row, start_col) = ( (area.height - figur.height()) / 2, diff --git a/src/ui.rs b/src/ui.rs index 7742c8f..1dd8777 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -30,11 +30,10 @@ pub fn ui(f: &mut Frame, app: &mut App) { .border_type(BorderType::Rounded) .title("Conway's Game of Life"); // 2 blocks less: border - let new_area: crate::Area = ( - chunks[0].width * BRAILLE.width - 2 * BRAILLE.width, - chunks[0].height * BRAILLE.height - 2 * BRAILLE.height, - ) - .into(); + let new_area = Area::new( + (chunks[0].width - 2) * BRAILLE.width, + (chunks[0].height - 2) * BRAILLE.height, + ); // apply the area change if app.area != new_area { app.area = new_area; From eb5c5e02e6421e77b9935eb57b2774f72d36d294 Mon Sep 17 00:00:00 2001 From: Jeromos Kovacs Date: Sat, 28 Sep 2024 16:54:13 +0200 Subject: [PATCH 11/12] getting ready for new release --- Cargo.lock | 2 +- Cargo.toml | 4 ++-- README.md | 12 ++++++++---- assets/0.4.0.png | Bin 0 -> 334482 bytes 4 files changed, 11 insertions(+), 7 deletions(-) create mode 100644 assets/0.4.0.png diff --git a/Cargo.lock b/Cargo.lock index 536f61e..e8fbc60 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -55,7 +55,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "cgol-tui" -version = "0.3.1" +version = "0.4.0" dependencies = [ "fastrand", "fern", diff --git a/Cargo.toml b/Cargo.toml index 607e8a4..aa7765e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "cgol-tui" -version = "0.3.1" +version = "0.4.0" authors = ["Jeromos Kovacs "] -edition = "2018" +edition = "2021" description = "Conway's Game of Life implementation with a TUI" license = "MIT OR Apache-2.0" documentation = "https://docs.rs/cgol-tui" diff --git a/README.md b/README.md index 151c086..b86365f 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,12 @@ -# Conway's Game of Life cli in Rust +# Conway's Game of Life tui in Rust ## Usage -clone the repo and run `cargo r` +either + +- `cargo install cgol-tui` +- `cargo install --locked --git "https://github.com/JeromeSchmied/cgol-tui-rs"` +- clone the repo and run `cargo r` ## Sample @@ -15,7 +19,7 @@ clone the repo and run `cargo r` - [x] error handling - [x] publishing to crates.io - [x] changing to `Canvas` for rendering viewer block -- [ ] *maybe* the ability to parse `.cells` files +- [ ] the ability to parse `.cells` files, from [conwaylife.com](https://conwaylife.com/patterns) ## Acknowledgements @@ -40,4 +44,4 @@ submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. -[1]: assets/0.3.0_stripes.png "Image of using cgol-cli-rs in Alacritty on Arch Linux" +[1]: assets/0.4.0.png "Image of using cgol-tui in Alacritty on Arch Linux btw" diff --git a/assets/0.4.0.png b/assets/0.4.0.png new file mode 100644 index 0000000000000000000000000000000000000000..616c1b3d5618d9df46be322ea7c3f3a55240e248 GIT binary patch literal 334482 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

FJj)?k9ILnk!R=W$ihB|(^O7N-s; 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

^+z9lq!cQ5U%Qj`qkQWUFRDAqL84zw zyK&BA_OWoMMabsg1lnuA|0+IN>XrIm)ea3R6xM9nzM*JL(-$;n?2qh6sx1T+JcI;! zHcqzod9tN)*Tx}hzW?qW9^PpWv|=*Z6VzGN6w0vBT>c*yN09!3fxN zCxyRy{EC3AboD!VZ-Vv@1>P0ktQ>;7)2ClOohCNx)Ntc=Ww<3_XP$8gVkkQVGRnhMwUP_^PI^7N#+ct*LC0T`@HXme_Bjt&YU?jnK|e8-5gfe zvSi|MEDho5%-wN{|3TJ{==YNRVL595#K|4i6MIgY|1aFA6P6176zi{89(!Ne-^BwhP_{J`ksrM zwPH*DzmaDsyoaPNro@{UxWw}0FQ4XS+}t_YTi!S$5ICqw#vev@vm`^K+hk&VSR15r zf3OKsDL>MSFyXWR>D@igms#&fuS~fTJ;#)g%U67&E`^N8NOswZC~awNKnd^%YO0Jw`t0=dVYf2dg4QXj>A zn^fv`tmwNujyKYNB(VC$c1(|SNylW6_z%Edy~%%&s69Y@*Jct=1km3#@Bnf0{+jZe zXa8%z1o_)-YA*)JqXM@j>lJa^gqN@^aLfa?6y=-1mYouhgWiXQWh&%I^~g|xA zUI>|sC@wh)a=uX4?}=;N*3-dZ$sme`WpK@^dcKfCUe={>TJ`H~Lxz0(=M0;!(){Cy zXHO@3L9Od|%X?2VeAjL*DlgvmCgq;7JV;#~6E@Gb**5x9#oK$sK%N-$=k*ith!U^> z7XC3zyr7*q>->b`D^C*Y&u;tH|5nl77$v2A%c_G1OHD>=xyI@~FNk(^qVvOsa99h4 zTXL~^LNGF9ov^8}c`AC*sRuNdgfP?I{2gD9jGSLoTFma+ky)Ou-$5SK%* z8!wDpHw1xa*mrmLxL7l)&8B-Obrw;lEtTnD?_p81`J*$pe0d~T(mSnK(WEhgp;w~9 z60FqGHz1(Ka*}AVE;_d%=O>-PGhHywo#8Xz>`G-bf$4RuR&ntkDqp5M#;YvTye{+a z8w~rTbNT$7&bm0HIjZZFTKD!{HWazquGV*DEv29xw)&98Dy+of^#-dIxN!n=p zA!4Psq0RjR=mxEZf*Yw}E4MAjt{tY|$T(GB9m8Q>r%M0D!qf0-L5L9#2 zqIT<%>WOv7=%2KXxt%pVvD2dVp8ejU#I$s;mFx>2^J!nQlhva*3+Q*0={l~h=U12} zPl)levuOLx@@9}*+Det(rL|Ql(_A(*T2rd#sOBzAO7p5X7#gEQ-^gLz?{HD=an{Ug zNO~RiDPwltZpy4&Y#|3DPOW?V67I-s*{Hfz$tv z{ig)J9^#u0D(FNEmzb0$Y9`WsNH1!dAwW$dxp|@8W5#C;CnBSw2wFsev?ha{BPs2` zuwqW!ulPcsBr$oJg29r=hhkhA#27IkrJYvgn`LU^6<+VN6+CaO2v(7Q)S#cJCm`K*Govl9(3A35z3H#`NXi{WFZrTRcU|(+(BIpz zpOg(j2C%bZk;@^6toJ$CZ&Il&#!|U1rLq|=N*U6b!j{sFA>mTkb1-Hp9i}H8oVQpe zju;y&)fG`dj5Icbt&*v~Vauf;WSKT);*4ec16EX0)jgTYU8MWof%l(-1WBC_2bWHB>Dku^?pgq?wnu*rM;Q5jY2t=OO)fmoqxE zeift~U*DwTy75N##ZPq99Up-#%3r6adk-8QprD++QOuB|!-MWaGrO|Kho=iKGNt3JILq}h;|K-v zNca1&a`kUy<9kUQKb5DsYV9mLG-`oN@832m9ZJ0sPW}96>FB$;!vsiS(Ur*_$&G6lcPEh)9Q(Nq45rrOy zB0rn*a;xpFb=9$Dz2CTH7oM1pf734bMvgrqCV{Ly_Jg|p7E2k}w9)o%HNLGg)w*^h zl}G5`aCY?16i@&a_Pd-VI|Ix}yjM#g*vsGobg!g2QJoLycf{vC?z~lDX_|*;MM>L^ zsLr?M@1nXGnPqv-8zYBK$c#+z>EykLt9@wEd@td?Wve!w=~b=ZITkWR%^-x7h)A?I zA3&3PM?zwZnk%}z)Ka~VSD3ZBM|B*0ouaEM;9r`Lm=|ApSu=z9%}JeOxto*fccSR`jIICIRz$S<)JF=azGZu>#}VlY`hdJkwc}relHY+L&+h^5#Zyc4=lmj4MlJ@%h`LbBF!J zrM?y-GZs3+{5CFKly;kcK#00<6@C-_VHzU@b0Sa;(O+U%2X&D7AS73e?+lw-z3Gek zyP`c(n2r>^4Re**M*os9L5?C$i#z+hFk6=wD(X}(6a*@D;(m&|hy5CBq#T|#oMS6W zn8SV&{h{<)*eeb)xxbl|@)uj)aq<)=su177SO2s!-3RF^(Qv`PGoSJl;N9+1=}Cu#Psv)qE4+ct z+v=gpd*$8sN;%H$qhiSGZOx_k?r{r0v6U(H(F%3vtg4)l&TpGmAscU4Up_N@ixmi! zoI7e1c<(@tpR~H{xR;@RM`fwwCQ4It$jjYz3PzkoS1ti02{XX|H}(i;oND_%96$vi zQ%9D>xoCELV^5ijRyu^{;Jdl(d>`UXsozU!yKfiL5wF3ys^@y>P~@{&hw(SrQdQbJ z;hXIBt;F?Al96yZt3Ld0u1kQR->_1$o%+lfb|DM_`EKH+vJ8R~^VF`eAO9hMQr&8V ziOB5vu1#;>Cd~jt-1die>nlbq?PGOF&bB5n#Fx*Ue8OdIoHP_L5$|+Rx-Ki!Ue>m6 zeWF3R7G9oUY|J^mp4gMXAMouTVD312r||95x)l2HdGsVF-*ElPKPm0_1x5Vuj;Z!I z+Uv|!?Q}J%jO2LV-Ra*0)g-Amo}Vn;P;`E&0>wiaLI@SP}blcVgCb6DH0+y#xks zmS{=x>{x(ah$qf1-D5tzRRL*trEIBK!9M$sO(1@RcB}djQq8N&cdAa0wmiMpwur(A zC~(d~Js+8y6tN%3JN5ya7b9k)l~JgFCioRm%tW*1$g&#$a2A}eyO4m<4xGM-lZ5d_ zSl=11-<>Z*E{=S4sNypnnH6WSrpRa9Yl&#?VLAb|ZzlH4nVNAfS2AGIVqRY)Hw2kw z8h*B>e4454k{jz*AK*||dT%oO!p)H(XqSd_gW{Q6MM*zNUOHk6AQ&1##hfg;ll1lG z(rf!JLq5AN{E}O^fPYbTEqPf2z-A~d2tO50MSV)Uhwp{0kkG;~6{=@<4DIBtTbnKF zo>~N&IV5^BdwRn6F|cA#X;F5IQ*bjYuiR`AV$;16!OrOI^c!_Dmw6%A1u!5SD#{Eh z>}cNFG+N~6LLOhSY5&T(n&11_sC67P_~cc7PMemSISEYHUcSr!e$Giy0s^6#s6%QA zvo+gkGn3sM>ZlR1j7vYjKr|V3vtmtHFA`6@C}|t-(AEZKJlr0Q(yYUq1cA-)ujFje z?Y1Gl!iI-9Wa}kmWrH`kxM3CuR(*cTwdZ79?ey}RS};}ObebduSjADUD0JVB182pF zPD{}ZY55wnDT|}w1mqIA@+bZ-X_*_v{k5t>#l)?9=l$Um1SeXK*}3&-d+PR`sOb<; zUeX4t0!%B3ugp{_{Az=uqg)?PQ*dl}DLSv#(O*OL!i|yvzW+X2|w3_+;l6PaX6o*vC^AEmT!Rp~mc<66GhIrYbD{Y6JB>5W6EGaGq1^b5I653-ux)Pblf>@f| z0!;pDWOY&ATr=@THt-A=NCmof6;71qIE<7bM<4-jkE)au@3(PDiEbS6^gPH$ z{f8D=caKcZJf>Cr(cID2W6k0<+M$35;q`EFbh2*v#*;Kuh6pi4Het3{nmJ){U8)1a z`|mCk7n9+4Og-6Oi)^ke>tli3n_O!!277FbZ5{IFPue5*Z+gf^;Nd$$b{p3T{hug-w1Q2Q<(O+4lB4VIHaASJTk^%FxzV*Hk=YqXsV)aa zEAQYB3-fjt)s>etSGXe04z!Hqp1WMrr4e51@^+V2$7)cov3kNXU7GRN%c10|(z7R; zlKJC3J3G-p>(WG%8`#SG?H62>IubfN^t#GeJ!Tt_C&>V!^E z1U!AcAZ4cPr!MJFES#EN8g!O>$}@XBknH76Wg5BL^H;z+FqAk7`u-gXIsi{B3E}PX_v(h0wEt&2J~?)K^?`QR<9u%rdF%L_f8bdK@~Nu#b42PEU{*3BDes9W~)? z9>`a>CQz(PnsMN$<15)u&5xelIbFrMsY*3YpZh1TTund3L-XNM(@l^on6&zxdwRl{ zo>GsV>Aa?9{^I<2S9%zK7~$$6V`5h6)Rd;{+k3Qa<0ezxDV-L(_M&0XAuT}0LAQ<@ z_>F;#fVKcV9*4?;ELU&K>wkK~9s-^9GS`T(W3|O11UOCvsMbvo>Q8wNio!oYhPxOg zrcaUiPV6zll!1>yo%zByv)0F|196hjS7e;5O$qF|!hl2f*UI7kP z$huf{8rq+7gA)YWPyrGS-z8>O%-c`zH@;G*xjd_8L!jt$%mPR|s3x)M0EaVY&#<8)R<<+>Bui1_TZ*M@lIS1t$Nn$Ey0J0p2RORQ zF)!N=W*t7bG3B^b-d`6|z2;aMTTGQyfneWBaBcM=uMl`~F`klrJTK#~N0eQUr%jXOjDdvf+&bHw(b-{hfEPx=7!8TNxY_|yUqT9Z zQFHp)UaGMHc0sb&TgI+K0XtZM4P{-(iRg4U^Fl#xZ+xKHF%hWzb-U@+M_IgfFwH-q8_PLw zU7DQDt9hDKpqDh!q5B5?z_TMEiFshHJpxW=!!-UM$t`{M>MHwz(t0QZkH;W(L9jjK z_q9O-9)27y}))#{?Q(Sz?Zf1t`19=KGisFU8Tz^K-|1Phbmr z1aY%$XoTRP?btB*K#-mEe|<#EjYzBnLS00kD5Y`;Ny%8OP<7Oz`d&QEa8Yy8HKzMZ z{fk~SPZBB~uUUhHI;XjSgluTacU2s4*3k7jLpYr;2lL$5khU_Z+vfQS&Cc7?kDcT2 zDp?dSKQq;|hNKK*CyxKG<+O!J`x%d^^27eCO)Y_^vW{Z`N5kixHuZcV%`d2mJv};! zvjeRk)wy&$+tP%Lj7WT4L|FVn;#7^clM2}COyFYxqcn2HYT45!L~*8RrswMIqt!j+ z6(~0CRrwd2LPu6G1}!`vBIlFjfH~W-GO`oeFo$F$5wb)psJf3fQaO$SxgLn_0<3|^ zON2OA!o2KwEEv;rz^uw8hUYw**il&1=SBJFnMhmNUktdno-)y1T;;o_1O^jL43DAA zM$s(S7<=|;0&Np=0639U^ZGI%E0neu=QvnF)Qf$SqrSgjC(MmoMx=m(dBTAKkx2hz z&uwfR(K%fk;@J)x894D3n+7p9{4Wl}rS|7FgMF4}jLwjvg<4FN^0~nRM3PWV8SbYH z&2+1jPg4Ol_UA_lF}bY|{gZt?zNn3xaMnuWSkagCbWV`Yv}u|b?>tJ5Nx$P|95nvd zaemlBkWl|LEANNeJupvS|INsud>8&tY~3F zoGTtJ5HEJVJ==I|u}}_s_#n^BNXi`&yxFDc!Jbk!k!Pf+!~K1Sjg~Tb+-I>=n)-YG zWwfH6ZAVoV*H~qoUj%A>>2&&sxgzOo(Cm?-b(ijXpcubCqh3;js0UMLyHDJe*p2bNtU`U?bt{ve&KE;Z%lv5uuL9 z4UOzruG%_@&Yo;hwZ`#DesS6C`hC8##L}b*eZ1a)0?#}v%ceQBTc)&c%iPiud=2l? zW{P>B!}kC+uPlw!^gUshC!55a;mGdLuz%Z!xa&?+b#%;^ZOZzsw`2KP7gJxPR9@Ko zp$?m&jZyq)SLrr^pAgxw4H(}7v;PuVR8bKmDM_d_lb zwyn2g1M34XKaNOi0T^MSB7xQ0*vINs=HSju&&1A27@x>)#r#+n@CKRa9Bm( zf#dIxwow1;RK#^%zQ_s*u&iehdA-lcNxBa(4jC&J@^qO@O0ES`45kUeKqJ3@7LX7 zwBGhEd-=4aYQD+wk=A&Lm~XNj%G8ynw1S$u_n(FfazKnzcBFdc@h0o%R3_g8 zNmtlvxOALN z2zoc%c_H9%{PGMZxl~f>*+P=b-4e~Wco;AC1B4#f=Pd;l1l*S3Zmy0is)^QY1&o?0 zJiaCMvR>ZwD?q1=n$}NYP&($|Y?jkZtU<#$GXYS80wuhu=G}YrG$zZ(DkVvC;~wy< z`Kj-yv>6*7MJJ^ARF~YeJaT6JN1lzOpB9Af2>3-tP{!nM5?)(B&bMu}b*}n#Y4ey# zMLdUoP@)(gG2gExFskQgobT5l;QPttxF|vhe>mffM<-=QuGYe|#Dq5p>#zWG6EpC% zl4={Ce~UBly8nrRhf5^sV!9uw#JOwc;&eZnknRUXtM_P`k`V{-J7JZ$kFumZNj{|v zGctEO786`ISIo^zHGAG<49EmkQECF#UR*!qJnA_zj-~|5bcjP%LvnrCyk3?mwG4uh z07~Br5zTGwcLMI+9yC*A!yu-{fjex63?~kMl-r25hduex0^0zsEditO`?>S6jH^R8 zOU$fO6aPW=7jpQL97SwAWGJaFjc1}A8CZI>Zm0c;ws&1+)Z;3ymO3v-8l5>q+fscv zYqx7i?FS92AB&%w>C}04wk;~ZEP}7!6klx+(xqThqk!?U%qc3s79!^`Lf31Gr)!|8 ziriEx^1YIRJSasd`$f!gEu#gRh4NN-b}Fc4mMQr^S~F?p-jH|kyPOMeStqn-nN+)C zEl#B;D!&147u}YJ%ois#+8eFxszdAbOtNnZ2s;HF9R31merPc8Tg`qxc_{gQuVd69rlz|kve;`CoI<{{e zkzQ1+K64b%T&NjMnxx~$rEB06MIdhuR#%6TdceEz$|FU=_zM2|&uuJL2q>EP&t5NA1$g|$c}BEgS4bO|RcqOMTc>ad^u)u{Yp z$_wHT4x17?WOdauZ~YqzXa!*IST>~Nzfb|O2-xsp?}~Zhj}z*#Z%X85IF;c%9$A{r zmAXCnm@r>z$!#?61Pm7Y6+Tw_Abf={s$1~BDL@@Fz%vn9+EdrW4)^?ON_*{fOkfb4=>uZsk%B zpNP*4<>jeytS3EOX^VHnuf0+gY12b;LX89%@B>WygSQ7StC_b9nTtz*d#|#{Wd`ru z&ii}Qsk~y#wl1bn3jF}n{Kt>D)C;e%y#fP06cPAjv}4!>Uu!Bi&xiCO|kVP;l7f>cd-$q0uS)Cb)FQHe)`r!-ThXFg#(Yx@hRSy zlJ`O7@v@b(NBHKBFjLmIJA5}cW@FBjF~?%rm;ClOBrNs_r3v^#Cg)A)rmCoE?43kWxK3mVR#U3XiXNcaKwgwBzK8 z!JBCC*E9IoC_k@#BG+*F?p67_tra#7J})y%2A`LMFWtcx#o!xqaDF*Bu^wDe{I5<8 za1vTOb`fsrTaP3V1?=*mYK=J&S?MCGxu88?qE)VT{+{YU`PNlYtv{D0%?@-ee{HE> zshW{;a&XhZ5f~hS!4Vi7fx!_N9D%_R7#xAY5f~hS!4Vi7fx!_N_y_=c+jdhvGK)l= z`v80Qhj}}4Q}<)y3l`eAO;;9Hz%c~x`haqWf! zkxL1BROkKib@pg?b4Xs>#jYL=R_?Rlg@fH194!ewHbgExcyFb%c&T9 zmxxim0;kE3lozlZoXGBcC6Q;O;T0*L5L`Uc&00ia_~;;QZX#vL(lhv--|Lj}A>P>& z2ggNZ(=&Ka+#0+m27eE|wpD|_hr!>&;QOJ^#%=KT0RKH;`Lj-cb9u3m@=2k?Hf_An zt~(c}RWXC4<`>vOp?MoH-#-PH>jup8uKEdNpy$WsudgA8t#x0Jwq^?ZWtao?bCMvxP0L`xi!8 zT(d>0yl=`I%gWElt0@&8>x<(h%F=;ceihLyjM--vHVRMlZTxV&&V)Zg-fHN+p0Djm zxq8nhMt%L;idQyHKMu)sa6E?82%4tN6X*BJm~%y-W?O*^`EF@XEpX2qE#O%9mtmU~ zzsu!N^LeiY9SWu^fR6*j^%X@9_T#+e2??pq50&=vDnRt`OR!vCsi`vad0jX+ra>!# ze<;rBkZ!A{F#i;+Jv4<`t1g!$0#Os{S+Ch&9-36@F&j1FkC{LS&y#P z=ANBsmB=sa$ckvBVOv+8L(izlvMDCCs^!0KW}i}@IO%g5`+GqWD(u{VMcR5nnCTK> zyln6Q8y@ZASgGpC>+uX^#tRc@mn_zZ?Cz8dbES#P+P?nQ^?S zg|@!o2Hayi0>D+9ek}qmk+3&npIrzEuLL<}^~@h(9!-zmwo_`hD%5S&cHlh8)DPBR zc}~pLa{FRT0cma{5UkLo#^1&JE*Q&=QbtWu4pX;(F8WwplNP=!e0YS>4e-a zU`THdY;Q2;$_(Y3I?r>t8w|hsMKU!rX#Q!x+;Lk}SZv;%+3Fy_3}FoLBxQSU6lZz& z9&Ti_02bq_^hckumMNdUnlx_2lwbIWHjy+u5?q=yBpyi6#7S04t336b0{2B+Rl7yw z8$7b8X@2Lj``{X_7H4mkUn%q`FvQbyWU@DV6E<_J#krC%%-YIQ6XH0Dj}7Gaj|1o7@xG?YdMD z85x}-jJNCyq}-0(6FXU9XZun4BKk(_zsZRKY1uiu@UA*1-Vm^(@sxoBK7i}8v;Tzn$gPY z_@0*&4Lv+co$7Xme2v#=l}9@I4%0-*erV!3joIT(uWlka4BqdaNSD&?_hukgrc1u* znd2E?j)uEhmAT%k{t#Xc0(?@aj)iAu`*(B@ysONYk+^~3520VLoS!iD1Hy;^$n^vFU^q#ZAZK6)h(M>`{43D7qtmO_{iT*)OrBOyoeJTYg!UG1VX zuj_L@dkE2SDzdu}W_8tEQ?!^~<3;VV13EYNW#GXrOv_{X+R0stZ^8rD2;TvIi=GcL zjZS2DrSu9n;DScX;SbiTkGRJdA)U|VQLal2Z3&omj!P=(VUBaaGBh(+@^3Jam$&}P z82&=I8jzjWV!z`tZcp))x36nJ%3BiK%14yChueYG9pIRgrP^)BrjQ?O#N%;tlM3~} zygA+?5Bk!*OY;ZtmJ-C|fnN`z0cO%8%NvK42fDfi1l8T}%wdDvJu}Z02v`I%e-+>&sqK}hG7%mrvUnc~Vfu}svRNCB&>Hmqn+`rNG z(bGAx+I8QK0v4ubGGS>Phpo^2t@IeSc1v_(lW$LPf*C;g=0FX^#4c#s7&Z?c16IIf z!LrSmr=js+{1RA}7sp{aBnS=WYzPd`rosBbDBMQXWE%~ zE?{;V((B5N7vdw^LyTU72he$_G^#lg!q50N|I*0Nl;0q-*OwESwyP}>qBUS;K;0zD z4|A9(%-aIb3Q>p_(^&Ww$q*Tc8f%^;ju&ed1{dOh&@-aevyuQ2I8xD+r`U^tWD z*EC<0v&GF1SZsuM(gFl>;Za!$_0%6hU>5yy@P23b&A$&%(E0&vz8zZwT0wV%94;{C znc3oSFFcrQJLdBTbfP6wd}v38T!X%PK%XQZZ38!C`$c9?=s&xe;E^IKeaQ4k>Ytd> zPv8l?UNQ~~n6Ghf`t96Hd$J#x^h>8`0lD7UZ*#QSIS+IwzkVNE^T0abG&kes&dJ{L z#u3IEy6S__SqGS?bt_|k9C;H`xE z3xxcq-Acg%Luh&E9yF4uu51!tAP&i7G{UIrYc3>jX$UczDJmq-S;cKD$qx9$xPc_2 z$$SJ!{ zJ$JX~8}}1UbDmqc^;`d2MSEkEl=3aB4j$xL_P%~un!>j_YBpagY$wuPYlBn@3rM?h zv1U}8P4`ghETT@EF}g(0Y|*OtBcD%#=X~yb{>A;@GlxBrG?SX2Dwd8X>{oTXM~mzV{kz6m-z;OCZ=u$LABd0>ZhDb zHa4gHYK1!d0cqeUWJo~(Dr~+AO4oH;9YMf*N@8-!z*ASYW1Gn$QIIhs3JtzYOe#rC zUWQ{M4}0O2(LcctqzD$+QFV6Zt$uFzyj824?LQ?nN?<$0TR-@;jar8Qz`2eCI3HHB zxZ9AFl6;<+p!Pi#(`hT9z*rKayYx!G;KTq&`C$xsgp_33=!@ zJE&v4uFjW)Cvp1^vzgmuTE%Yl8;NoNpaY}yKdqr_DLB6KD{M&!<)XZfX8vSq%R1(d zHXyyqYTnQx!0>!6NPm)vG2u1e`DS>LPj1ZmKh0e^ev~d8`T1y9N&`l6!kv&1VX_D? z$G^PARs`u#e}Or_3veOoZU8cV+8gKC80+?0F*KzZU)Xgn0007o!OvCaAZ);0%##ki#em zn*wWnXV^8s{XGyL`{*ny%c_Z`Zi~8ln4Ys-Z|bS%j=3ud5^*iV%xv3=g4~8{tByg@ zsL)r_>&6?o?2PqE40TfOEFHPU>NaEp4zlCF0I|3V@6N3>LEdgln7ol8zW|1lq09gL zHujj7uG6CHog+rsf}z&>i`E?w^7`TGkCdMzdIz0+Jge7zx;OV-=+|HDN4#a9dc3)m zf3YOKPRkMGbq$k2rKsDWXCjfQ!poHHVq2rq8(Xi;=V`O!TYqioMEL8QbX+&y$c9Iu zL_j|N_=x@4#C<{B&_kTnki?h8x!9Bod#qf%6f@&=nV-J#c|uzQX=T@q2=v`n(p-S; z*cP%3k^CSr(j}W9f{iBIM>2Qh`Q)h1!}R+qH>;gY66nivF(b0QdVwO4Wm|6S0>Vff zR1x`1HQrQ%nqjO9g&t20w~65~HuVP0UL3v>n^e${yaEqjVdLQ|SFjc0L&#!LrurJ3 z)5Ro3gfYKaEM-K5c#E@H#yN1GX{O`ZES`8aOAXaeJQA|!G_07XF#!@U!}O|F@Eohw zcFG~WA^Bt2d~q1cFw&kg(xPqbtMsWYcC?5>k3*54O?kQ1_SU-Ua_j^cLWcujsB7=B zD;HaK-iyC)*;+`)qirY%*?}TZK1oelh26gz()VS$+gG!u1 z`wq`$1@u(n5|zE2p?HoB>!`n3sQW@@r#$vzH^{N!T3QidLP1sy=O&H|$^dpn=Iv?_ zQ)3*wwcKYuy;T8eccpBpSivqP;5IMfS5>K@_04WKUmVK8mPp`OYjyh&F6=u(7Rw%~ zcAclWbytVJdT27?*gmiCcdAa0wmiMpwul#$b*SPq9hntpum;Rg#!MyvY;SoH7b9k) zl~JgFCLS~cpFe==S!cFnOUzxw>B|UZQ$t>=C4V|y5d_z0*6e?3{+Ow@s8BKXJBcr7 zu{TGCpj{fy4T@)O75zkrQR7lyW*tu0k5GS&)Uf5O)!n%`=u5MFcKaDSR9o_7#@!1A zv23L|D@&^WGg_5HH=x5_dgFiu~*T26k7p#W_v!bEuktd46xVIJDmCI{Z zsu_d##V8YM_EnzR3<%Uynss=SoUO$SJf;S$Msnp({9V#AH;VgfRfUR)TldcUHO#Aa z=x;*kNj_E-bm6}$M5TnsAd9xK+|RMx1l9(pTs^S3%0^aKEqK^+ zI!)pO3a-l)C9Nh;*TLk4(KXGORuW&CsZ#jW21Ri!FQ5(&d4x5{bFfmDQ;+lj!&0H( zsa*c`sl`QSM}G|pf6=x!6A)M+z&OMKYTLmC&IR%8qA5-x?Pi zo7Ta;Ico?Txg*^jWcnLv7z)*#w>vZX*5+6(C6yA3pM2f+N*@b#Rg`J+0k)%`kQ5~H zP;yo2*%M94{PCWhooJwSDL&m!&#AAtFSsamBy@J@b(OJt z%tD-+4d7*AL8`fkK++h4mg}>(v?RyKIVhp`vP5FNB00?BlVLJo)mc2$3sH3^8F-M9>uSqX^v6 z6r)h$14f`@O5u5pAVG00fRC{G07%*pwAcuBcnzQ<*Q`KwOmF|(VA99$?)KyRjjv$S z#h??mIoN2f=3@{lgl!W*PF?`4A(rJKL_1rq!oYC1~b0U6kjhZupMmwHYLPv`OJ!k?wL*9o+TvQr7k55mhaFvWl)? z%heBXbd_UXwjIm@;oHb;sSYxylg9O#w};w50VE88!$g=O6d2*$ful#JyXVH{n`fFO zrz*GJkK!J@Z(Ntq0G9kq7U@%b&SX_d$78)Q(<yGCkGfDhf5bD21kw%k1NdLlMlg-acC0LtcSm(_WRa=N~E-w$w*)-zJrM9V_}SkK>K>5B{{=xsvvop_bN=lws_| z@&C1)wh(DQ<1tm9!wmh`0|#D&0Q+$`;NP*NB@XHN#osTyi-E-Zr5>%u6s?XmCoq_3 zVt5Q?wn%P@jdXe9dWy*}33s-o2nFep%Gk93S?GsQXB09c+nKiremp_5Q}targ-GX- zKnB3_W9g{6@n)%Bk z?cqq~wx(z6Z)A&FvQP(D&h;SLG7H|COhH~D(s{LKYD{#ThmU@wyXQ^66bvWt2C~>4>jKCV;s^yXB7r%%Ouc>m4=i1xI9| z2KW9oCGcAcfR7|n6GR`PXQ|fKNu;(hy^0J@=sR%y9nu!+f1QfBuFJP$^`n--*5W)G z=C4v1!WAhP1M`rEHLx8rIEXCAx^Hp`ib_%l2schDLH-7pbyo_^kd?x8YyMCR*r~&W zrS>s<$1f>Or6a8THl;#4=bmjLDfhyB_-3ImT|G-o#0bLS?=hRAo5}6-kJrWVhZoxW zhErO<`)e-8!HT$d?eLSrNOZo&6)DLJ$rbSxcH}n!MgV3p8wG%XOUH-smRqb2Mf1eP zA#G{ak8b2J{dD6U6{zS5YPO{)+&w5z5sakQn9i1JR%Z+^9fu;WT04F|xDO-lzJB62 z!kpXAJ75ZjZGje<$J~*OL;pjv;*J*nB$dz2m?tUbczv8${fGaiC&RmmBPZ>6%(pUX9Bp_@V)6m{>?*LRDyr|n1 z=M0}uRcj-JYe#7ig1q~woj<@1Lh8b;u8zDEdgVHUmWOW1rXDM5D^%!$hZ@;ZAKUb= zTIr_4hM~8>FQb29TbhW)A?M2quUH054h$0FgO#HtYHEs-h(nJm4fxF8R-bFN|JG`h&4Kv8$SZUra=pD5I|KT-dX zSFXkz=DJ(wC40m)Vx`vUNgP4UA+CYLa=O`;c+)cZ=T!$CK$bZrJ3zHl9j(X@!K1-B zwm|)G-5O}*u45i}xgD#7lz0Ww51Fbu=?!N)Kv4!S6Fg!?N6nw*b7^Tl9+57i@_?#S zl(vv2tz#?6lq)jbx0oPIO3!b*o2l2+WI)^Toj*z6ADr56F|X1lbO_jej^RqWc$VrYzOM1EZnu zGiClG7L&UK!I}>>iNjrr09TT&HgL|(Z4LA3}Ilq`vfE-tshfX z?PCs4EqX-z{bp9%r}O+tW35uzJ0T|xet1FsAx-&4@Idt8LPbb9W(R5kp1P5r3weCS zru{4D=7)j_)Uf6b)FHKm*_!RNnaOSrb<@bSr>PT#7T8hS5MSZ;Lmaa8lCrV^LJGEb z)QDKd4JsNb=L1+S2Ms=Xm7mk5i z*~BXNP?DOuq8tMWPCJSVRtV=9;$??+3e)DLuZT#aWAMq&EuirDA}4XBoo57>*$jbn z^o-t4KR{fEl~w^Iy<5r0-k`cDZ={*oJ`a~Im*s~2B(3OpDJA)sBO@)rl%&4iz-y3E znn*Vnc$<;06C0EAA+V_Y<~H>gJWH9Qg&ND$_(?CMjOYbf(tiP8R%Rk6Q_es759q_1 ze?TjSNW%qy+^#f)eE!vr-Gb^(V5*R$)$iQX6UOwEdh|@^H8t}W=f}I!!}!DUc9&Mi zYEXe-F)Y)i`QBtUb`AzHAR}T{>C}{_>)U&@Z3AjTcS@%PU<6$!)a9WX5lkD_C7#Cj z5nk&E9e_S%!7Y}r1(C~eLSMl^08E((T&NoM$P?r*lZm4ex&j*wRok$Z5<8&tky;@; zD&Q?Su(9d^Y{mr;vVvTH#InetB8;MLg2?~OSlVfWOPw?8{Dk5wPq=Y1Tgr(FB-nfr zhJl+d3yDF^wjGQ6uuf`F}b+B;JE@Ewu<^>_7 zBJQsF(aBIlH(Y3AntUEW6P%#n{cW>=K!!FRU>?|g09hbGwnlBd-{uBWyulyHHUNkq zf7%+67UoTHd5z{Z5n3W>hl_tCfK9Z;?cB)@RL{4>wK=AIoB!XSA%GYnDY~(kCy6~s zArIM-=m&OL@?TO|Nc97yEdzl=lq8`Rr-CsW5>N(hiexvi-=#p0(79wd%1cQs2TZ|O zKI9DQXZ2@;3DCsR%ky)`d_PcW3bu7$PtH*yl0iKWM+)Ey9@FF*@Mg#!Z6-wROv9{g zHzF)1z@#he7^QpT$`y$ytmU*Y~sMd5CIZn-p(Ue=Rl_c=yC`qA_O|}L)Evicrz^O zR$5$OxTdw`nQg>FPB7DmtSqqP+@mGPH$GZ^vrYl4|GX&k6gHJa7t?|S5D^fv zBsmI|L@R{<08W9&BtMZTR4H>yA*w?lF2~pc2zWxh009xs5-^QIzX*$^klo4d>wFk` z^FS$QK&qIA3`7M6>9`W{5{O7=%s!|Vh^g2g5ny*ac;4)@3Fx)78YTkn7ybOG=;MCt zFy}|EFDjkS%##$9bTy0d{@=fqgexdN3BmqhBAh|`15i7@DWPN#DIh^{-{vi31G1q?ZxT7e%%G z9Jln#?kb>u;i$bjki5&q?a6_|>{ExSbQK0Ri#Z6%QD7CwLH+wj!1gH zc&tZIyxXIUqzS#^7KLAWzN5SO7;;8yw$9M`SobA;_FG<*7DRc2FaVPBB|BL?inD-z zN13kU+Il`P%l#?4=!JyFHf2+SgiP z*ka$@F_+R6A0KPRxkYVPQn{D~9YP^|##%=J{6r!n3epdThk60)w+EP`v|wn=0V&%z zUgt7txlqItr<%9rGv&SYgE|5?EtZn~a2yIUE*pDztEhCCe>D4kC5I_33nG4RBn9CC zcLW$LCnGN6Funhqqdwhl{xIQ*YucL0J>Cm%@aLjZ9aNQ>qXBtpE*pN9|q zlExMab0Cyi>i_}9hy5ht7eB{!KgX0^L-f_3eW&1Fv{;1SId*_5udUEIE?6jMnk3{B zSBOmWfLRZCbnYPVd1+ckXV$NZru{CeR30yz8%J%_YN7x_5auLwpbG?YAp7T+fQM=f-2N%zCa_kh%l5h#t8*f@`91zDu`Iv zk|TG!S3`!~X3$=VfY6>|U;qM8yN_fP22;L<6sT#wLk2~>TcY{4Pnm=rgt^VZL_Yth z=0UDX=!sU(Kl||3<}E5?*eBKuWf0ms-m_svNkz0~>o@k4DLlR<^|D^x^egPg5-sqW z3;VhW7f@MTlH5N3-C`{o!Ao#aNm^Ncyq|~tu<&Jn!#03c{jpQBE`DX6mnGF%pWRj4 z{_y7f!HA=MtPaTuuxD;G=1`54KT!BaCbc_e*r9Wv11llZ;xH}yHdp=Qh`s{teJbsJ zZV9E)$%IV;3rkxO-tQBDQFr~5#A`Kl1IT#>vVrMI|JpbgDNDv#c3|N`ol0KF1UKvj zu>wg(i$!XVCzpc^9idHWOD7ruPU}?-)jqUnzLx-0L)rq>klgh;7*dM^)?E4lm$Uz- z4`dF4v>6P{CC5b(BAUX5OFUy+)cR+~)Y+C7iLQxY--X|nw!BiR9w1iT&D&De;ljJy| zy(W;d5cMhBNU|>UdN7)m*SA~Tu<4$RaNNxRZm!2L2lopZ~ z#g!jZKsq~3(zO&;S^P&ftpDY0R6(w`+k0Pq;HTsn+Cmj+}R%(IT_Xf7{%B2^F#vb8}Q*AHuc1c~<8qf3??Rtxe;w&qR;rR%an?PYEI)+ZX2YvJVy#>Sk`KN$`!e`Ffm zRQC7D5w8Zx#r^3+Ss$qG2`n!;pUqamLmb}q)dvB$dH!d@+8f}_idKA61 z0y}_rA!qWH37536fDMhbmf32rXFHTu9WrrSV3O<-6v#%V8GW;nff#ACDz?&3=xD1b z(t8ra9r4pjT3vSB%TT|gvea=CrKvgOT&$uJ$ z?q5<9DzCehvh>d;=BZs_kEmxWQ|hA?>dskJIU$|jHmyQ{`WAK#W{(_r`g3aSS-&kVMS^TO$3Hb#8w!dqTIT_3v_u!O4*Rj#}8UP-j^?`lbs$|UJiSVIzm?dNC zh@6#H&qbh(QgFJgYh?twSQcKV&y;R+>}qKfOHgvuxZMwrVuGa;!xqlZ(rXg%6+)*-w2_SBq;jkV|N}=x`sdo}K=Kp@PNWSH42}oB4 zKyH&lbwf5txh^V5YU--bUXf7m&6LEG_e#Rm@Fxyc%D!=+P3bcvO(}Ou0@X=BNh0f! z?v>JgXh+8Ox5mZhLRD)C^&=)i?He@#!Odm5L;HA#G{$pwSn!ftb&-Jdm_PzL?_Neg zxp5F#Y5zdQc1qy&lHecb?yX(5*^={SQ&65uAt=#7wPfws3)_aNQ9bOa%o673?C4vD zlxiGv+o|F~oeA8zFo!c6 zuKOEF*3<&I{|UUDdl@dYBYABcB}1io5a^pk4oG^c>;;$Lof-CTj@m!q4h+}3sb_HH zJ9HmRLkEMDX=?^*fOwP&!I?F$6X0)%q%Ua(RA2~sHDPN1Ic7g%8Bq-w`RwHYVe~Nr zN8thdV)eI|>equDIamS_;3MKHI!VCzlt>{cigy~fS}dHuF9njj?UlmDfs{|nHCFd| zK{T)&;D-%yDF7ajux6*sFDfl&ckRe5PuK6Dk39b}T2aroqpFH)tTN6|{(|teE!8y1 zn*+XLz$j|4vB}(IYYu=F@ogQLp#oZGtf*ZrQl3da0vh{J!gxP8PEXkh%F*8JxUn z;?d}IpdRMr%sd9M#-~Noigc2Sa0U&>Pt?prKwU z%Qr}$y=!hm!Hv|gmD`r%FQ+$TFQ*0)sCN`tYs(lgqPLjoRVnm}vhqfmcr7fTx0MV5 zoEyo_3+)~=KBLAG_iBL}Lz=e_B}wao`c)#n>0W9?6HUmrVs}Z%!xGHr`n)Zr!^wsa z!Sm;R$i1}FCS+Tt2xuR3n~XF7k(r(lMwel8iO2qr63|e37+#mI3zm|IcG~n4Plhd7 z9{B;_!d15~@KYz}vRMzLG@tATN*PijQ5l;|X)&twk#3OCf325GA?;0tl32P7YR&(s z<9w+3l2>Yov4`>f1Cj1~rR_732pLp6TWW{E{`!t*6i`q0XHjg*cbM&SX`_Y~;IGJ- zZ4xan*S&A2e4454k{jz*AK*||igVs00afSkRQt-0cCA-647_51_l*eWR$B05ZMS?` z&br8wnvQUaS4n)ao?~3Bfk8>Dxk&>(woJxYU&3^Eu(D zzJmL!T=SU4A>B14x~_-LCd?A5n^JH|?mK&dx{2(v%qM{M13JkIM97?tOcnslqR!XN z5DX$9W8(Px?|`WD^^ut#(K@M0?kKy zHtf2gw0@$kt-WJamsXw9^qE^hUA<$DYKmXxX(ra7;hdQuz!LTa{Il0332cq{NkYRC z(CNuOd{)|^6N*pd|Jj%Erf}7+s>!-ELqG1+yQzDA<$Tr9zh-vo3GKEd;@$kS47QIe zgo{VgPxu%*R!3JfW8FTbPED6q1YMKSEPw9seX>Gx<3Ei#Tr+%MLQj2ZBLj<&z_f>r zwFyD*hC463q@{3Ia=Uo|W+F(OLFVM7ehk?jVEVlxV+l`5vM4#qrCiQZOkrMpwb z@8bhWniHU<++HZR*Mm}~h8&;84JS;48ZAnpZ&x zV~98S52`c?0yUHMkJO*Du2+;S0VMY(*2#eKxqxyf)N7Oaf68FNSFi@WyS>JyeP6Tt zQ)eRqkfI{6_1%tT%7~KnFA4MNC=8Lv`0V#m$#WJuZ~&LI;AVN?;_~Q#P2wE9fwr5o zU15*Zj`)*Jw?Bd_R)$N6|3S=43)IkWGUBC#>I+Oh&jJ$G^}0De-<+aNF*@~Gc2hI=`M&P!e!pL@=S#@Tfkm-N z65QZc=|_U;NsEO0M2!ZEf>RlKFsn3>VJ#mpc>AU<5J8vgW8Q2K{mcLrO5mVzqk!95 zg+jLva9roA}}@fweG)X|3TDNr?4NZSJ#yq^(@h1&xX3OE2bj(bkUYOUeD z1q>B!<&tA1O(_JE74F@{M4-EUlkZ;d-2TZE!8SMv)Nn!wk}P`$Z%fS^W5vd0Yggld zG>NyYR`29TYZJk-d)4WtaP*T{4xW$>DTSC`MCeASSe6X?v5%n8>>E5*R6@nL80j ze*%FuHFQtb0QPxO+A`F&rKP6rLPL)C|bEY zO}U28GG8_R1LG0Jx{W6%vQRmg7W%G>PbJvvz_N&+ zl|XE{<7_UNBXXK;-8oqU9sdEJV-5K~&#R@~-D?_oL-pu=6vGtA}BDljrqo;?B3$7`q9uE32aCw-LS^xO`e!=sQg&Fo(qd{{% z(y{B3Lkl|_;o;MwAihI>`Vi( zxEw$Qz5VaJDry<&D&A^d^=ro+d(jy5T~pJZHS|~ZQ``htZsOx?CtPpTQO^@k@D*yt z6u7TJQ)&Owu4QvGzIH}M=L4^-Da|(Q4l~gU9*+~w2Tm;bhT+%IEWGf6P^cnOF|?jT2c{tXbcb zdu63SZKlHbojE(+zCDmVYoZ#CP$jugh4xwWE?(S}8w;}wgZ3c3CwC*`5$VrK-pi8` zGN+||$Zwg-IH-|XF%fqaL*H}7RzGwt2=Pe?t2t;_yZ)Ez;dO^X-WeaF7?yA81bwN2 zN#4SF*i^^?P_W^;{zO{!Ooik=Vf+TtU*Q|pFVfjbaDmZPm6kOjuJT>gOgug?7B#s= zY0<6}uwTMnO@!=~-PY2lxV1D@9!O`~$_}C!Qe~NR02TQ)w$q+ey@7bD-p4mb>a4{5 z)WP!yXO2NFudMSl23jReNf37a6@69s?VL)@#-ne|#-c;Id$UBVEv&;Ujo8znYmy3i z)OK#0vYfx}t}V&N3^KCd5rfb(Dz_Lj{hwp2h&JAhnArnGqb?_H7QxrLTDi+Ney?J0 z^s=ys=mIA8CV`Kfy^Y|KCAL4|_b>NvCH>8f*e2rskQ$!oL1MoXn?~qw?m>xrPqHZ= zCpLnu#ZCVxZp(ZD@{JN-52h;t>OzbGvK}U)EwRQ%pOAQ6ZCjg$QH{81izy~lh_RxJ znhGd$5~J1uJB|d}GldN_Vl>3yv45I}5+zjHIRq0d9H_PvO38OH^NO z0@|?z>vLRZ(o_{XsC`~a9kw0-I{JsUF14+TrAosZA?{rc0H>4v$<#tGV>2}o=@z1? zc&>gZD77TbvFE2ah# zDaX*#Bi8Gij2wP{qhMDL-xtGsUwrzeaFS7QKf&X%$2c3Y@0{V?Rh8|sJ-uI%>lZD^ zSUS90ie2bg47h=z%OR7am7UQ`UpV3-SIY&yqR);3HL7e{9A~LTf^l@FRtbh-HMij_GctU(+)FJ z^lk2^aR!utMVL%U?5V6dOomy5$5IIB*nYL~b>LB<{YpC4^D!sXGJ&Pfx6cLF#f>ku zWyyh(U_;O4A6OXQ8!64|m$}3!#CaeZ8oM zvuuo&K;{N+irvcS88?sJWTF9Ooqd6k)mp6cTp<{7i1WcLrsy@V*%TO3;nSL%&uBl7 zP76}VVl=l|*T)JGX{WRf#BH?8R!9S;-~GF`(W_G$Fpd4C(Axy(A)eBao)s6|;m7VY z(>DS>R5b;-HF8H0Z>>|b&iU;-F?a_C zq4&O>TjSG3?G?W2>@II4e{N&B5eFF$b0pI6b{~CUQTsnWFTajD42*YJ9<*Vl9q~G2 zT;0q@x6;s<=S|`l)hX>S2jPcdKdco7PcXFF1@m2EN2qfyO=bF5tUrn8E7Xx08v#*Q zW@6xciS>(@ypKuigM9W7qqQ=MtW2OzLu^4-TSUEAtf$w6iqG>6IKR9%_q<{;mIKD?kgqZC2}7v7UI`8t zT%z#n2Tct4IJc;o5+8`saCrL-gBmWu?9?0{k;sH61quyZVUZUi01Tw*4gIvT>N7{P z>Y!cq^<^+gB3SM_7D+bbauxAok>++U8=DLais}&qymBBAG&P`g_ zHnTASnYSHnmGzOf5Y2T;hGF(`0;E>%utOhwbIQT!P;o(oGliULec%15GGW0^bLptRs+^{sUPBVMwcyEPD|9_$NL@ z(J)QH1o&#VH>wf`s+P+Mp*Vkp=n+#4@*}r=7$A!zzRtO#Imqj}pMG_d%e$11X1*s9 zyCyK^^aHxQ;c2C*>BJ@u4FDA{*kc=L(V;7DbZ;*a01JS_U(s8l@3X|Bk=+)!Al4^0 zH|K3~3@YZn0o5cnC%_wdC;pt>~5{+E->|4kc9PL_Nu9)H>SQ*oFVz^!SZXnf4GF?B;% z39ug_J(<;lUuIaV@+U~?i4C!8u zf?*EdUFdRf`&-{9qNaP$YHG;6wf~x~ofNK9C<~6nRC8QmLuJTJjok#JRJX&*o+Di# z?dx7#XH222(-6Oh&_s~CwbBmA%KV5Qp`{>jqDjBR^2x1>_@zpq+Q>DR#}s-auwt#m zM`8%95u1l&D%H=xv6mzsr+FZb0PF){N_uQj2_q@p5Fi2r%u2uv_NrMcWd=`?Fe}Fb zdM98LH$RJfC}9)-)L1sLvtP*J1YioC-K>dp?fL>rvJ8hBj(P{dPtv|?0r@-x`8gv9 zXJdMV;*`hVXfUDRv2xb3;=c5;;x5I6l8%=Y^qF2fA-OpmRIYvINFb%yR2&KvL<}^#Rz?XV>&|Cb<5~-xuON$124m?41q;yybHxSibxBB5e8(|^&W&65o2y;Kt`Qpul!MIr;# z^-3c2iDVYJ-!u>FR@BTlSgjXTJ^hoMJEo$h7Qnv+?N-U5 zufx@fK`B~MfvnICZDpVRlD`ag?Bwkp!p_jeoYq;vU37WaP^}^h;8Odf4q8K<41U31g73qUH$c7hm9v zcJTgC)-NI_ZBiOqBzKsSVS>Gw$SWmzz5I1<5H^J5fMcO#=qPUqae3j`DCN7JHjBtd z#n_~HQvpT~4iwqXh44WI7}Ox9vTY3X!Tcg9;ho?e+TKCM_7ybf^DEY`>}HHs(fsBE zAm3CdD>T-$9~AV8XuukZ=(TWIZ!a6xlGK10|G0d?f%J= zL*=VSivpynp4b6dH&A9111NT>+f9JUY;B^LU*K4?7Hk9XcVYWM%Wp-P%)^Dv!&`^@ zK|p8PGvE)y;i{|`yJgf>C9iQOD6ygVBGLBemkcqM;@^bG44Vrg_ILj!pjAYBO1Nu5 z#6~x-yStq75()k=?j(iYAF&fCY*OqC3K;}eD}j^Rj9gZ1L()(1!8_SvLjN#AvAKEb z{BdDU)Rqx`>Z@}uiNek+KwU{DG=vaWij^pu(1@X=?1A(h%3N(|gD;E&4aj7MKtZcQ z&G8Spwz-s)Of=FT-fh%^vPfA=;5|j;HI$%A{~!Za(lGdjGQWqCThr9wC{=bAbj`p) z2R}T|;U5>k4?{kDcVl)yTcsv6eL`AzXv_*46f4q~E<1d6&eER@`^kmd{cBk#0m(@ICT;6c7IjlHx^C(Hqo0~ zE^6gjT&exGx!vk<c1SleHEx>n65*a2NXkX32`^G>?K z8r_a_R6KyZzDk0?vB75uFjPvOsK=B+x$7G4mIMaYG2G_O)1NGx9~AxgP4JcC9f>+R zT>GlMdqv)I`1)4j0I}NRq1zG_Tk$zWRt5EByVWUYBKVbK&34}g5hNx`UjL_IK*rR;GosR z8O?j`Dktjqwd{&Y_ga1#+W?E~gz!s2>#8Kjm?6B!C|qq`H9B}^qdUmz6efY57>TgD zQV>4}0yxFylU832g~fJ7xQ#M3wVl0Y;1l5Yw)T1*R=d&e!^qR=t7(9)l0Z;m7U@U* z7tRfz2E}*YOMAY${OMiKfI#*A1tT8BH2a5n&5Qq}rFs0CPoW)1ETws0$6w^dNif4A z1+WSYbQQHnI5)yJKu{d!y~?g`qy7pLeYeJ{;WI*8f96!axcpayI@t%AYWYw0NXqe! zCv$IUt*DTgRM5ht*)Ga~L2g0LcFI*s^6DQ#s|0BI-i)TB4e5fnN!^Cnyg_k3#S^@e zJiQ(CkJ#GU6v?cYy}!OQk3lG_h?KHQ+s(&>)MkDcZ9b_&D(sg<3>`xX8$(3xfImbM zEQrnMoRJ{>2A&D4bOLvg#!d-P{}k`4&}O>6Xh6ls@(LmWSLphCx86a;U4!PX*10Bl zeS)0MmKdseRF>ZgTs*d2cVTg`kic2FzxONLw=#VQwy%3NL`-pC$Q2OGd)t6ML;6g` zW)MA?T|~pFsE#OaAbCawB=LmEhV*rkECbs}aCj04wyX^0o8tNNSLiYn>Xf7cW-E`Y z44g}Jy<$E}w@JAl*P0+Cq4;mnJA{L=hir%a%2V-$i4V214s()X8{nTKlWV0W)l*bY zs4N>c4NtmdzT2bkj_1(Uep}{yC6QjAEVdC5Hl_?a5*! z2;^C4MbC9qgh30f+@NJNWfCoBU{8M)-M>;2ZU_V@o$o9^y^Hkpdd@UIZ$zX0`Q32b zSZ7M;ti(~uS_0HQe_Jw-Tk_H}e?lt*%!O?r`2~GI{}oVu!gK6w$KgqYi^*b{N8<5> zPOyzr3t2hiu>~reF<7OgWg3*SRIB$(eu^|7Fr6$#qG2-MQ%wY=s)lIhv~66Fqg?Nxe?B@=s%n66w584SxN|3u-WP_vfrK#21P%7_l1t-=p~)Bxv@t&Vi9}S@ z8@3(MEAT6W1we^JVVeAE5UzAtI8sz@Z*fc#EWJgV(>NNJ3~<-tUlnq{HjM*Y!|#_U zWH|QZ?hbJkK4N6g7oTCXXeBvFKo1m|Y|iarGLu4WTU>Yatc9HJ%e)LM)7-T*VyoWU z?PjTvqDz)&8F2WlaA2B7)V@YJHa2*hhjx?jm}|aGdc|swwd^WaeMCLiKs0JmO&?Ms zaF4`EL7F6IdH;`K+VuC_8puKFW?qvF(LG|fmQC^PFNtC|T9KM6`ns<CXPUqI2x*jEj73)vz2EFBMA&zR$WN+4%Mc~We~2YT zI>#MWBStuvoOPRY=)yjyyHYuuqtu_CkRcVJMRO#=u$=xNO39uZp)r z1!*#IThI`hE={IGGnNHVZ`9mH_}U4fb)s~0Fgw{d4DzKLBnO!blZYD{cX;@3>K5-_ z@O!yDY976)eJ(GCho-Zu)P3Js@4M5CZBt1)CNv44YSd<>YI9ol<*S(#`FHyw1FSAkn3nh zTrd5CB5r*Vh`25*m|t4;m(}Ad9w4CyOI1J_lqe7x7;7kcI=7y6Vsj|xV7uaBW4=)& z)N7b1;t*WJUOjnbfGv0NIeu4L_umixNJlKY+6@GgTc;18SK!^6hLZ)*%woQMrZE=b z)aPi{K~3*@2BHkt%*e-Y0@usA@!A9od=|9QX?}UX6^t?AZ6pNIy7&N_XDk|67MN3!=bQt;;J$ySK;M@6m3E0jw zzC!>I^H;uijDQAFr-eEsnP1TC{0)#7>;wr}GF*caJUaj~n48dR6X^=6m^uyf9&9l< z%wVZN8~8C(pQG;ccj>1|@xskjvH{Q@UcTvZCF2C2IL&mHtMTfVz zq<_>$8=GRzILCEjg|~+eT>yeJOQS^_e49e_x#{?5IQoC2go8((CsNTDP%^s4P8i5y zgo;}PUpG8%ou(L$Y3w<8;2AmDTx1DV{Xy23`vUDnfqAGb_=A!D(fBzkN6YJ|nA9$! zNqV13y-f6#SuV zw2@W^z+S0>~uDGS(B~wA0XdbAr1wr8Hh$tC}0WrrU!H2)F;=`B< ze-j^$<=kxxx!W8yS9+Ad6?5^IoySxk&Inq{)kPp;7!IWr*em0r}3#zz;r z>vc4Zqas;2bBmK}m!uW$Py5EC$-nkWQj4`!vY#738dgz| zn|~}zn{dgq;<>#=rG8F^$WwJWB+vXrkd1y;eOk_HH$m{`3f45F^`!SGbpdi7t{ogj zPmAef21|~zV?m>Zr=SRF|K*Zn7LO@(dBd^Y0TI_>Gbw7fnU<%$8~O2AT!m zemgSdscJ$w*jH&tKOi}dDyh@^wlO7=ji%mAG@pQ?d7g~WQdlMdkIP20T$6=GSW*T9tluleZ`f(Qao5W)KyWUcS1aIZeL4-B5xKi0cc(rzO}= zRIv>v729mYmUIhrzRaqpO(Ty*uR#d;Qn4BQT79&JHekSD?N`8QU~;Qw-KcVdln+e* zZiyCAc7-33Wq*Q~Gi){)oMNd=Swu?8-iR$AYGr6HmJ*^`Cl>V}U}A^_c@kkJA{L!M zLdt^-<-tmvM%ESxA zv&G_V+!OllbBmk;Z!QuZD%`epM_gySRC9ypNRz}9@@2!OFW7KtE;VJX0w<{w)(2s<&P7FjFb1jGi@F>u69$}{|1N2 zKv34r=OE!o)Noum&8A&ezn8L9&ORY^)oDn#_1)`HXtv`V5H)@Z48~Gk=)Mx%wh2A! zApuJG#{f9D2ZmmI$FAu*uwFO!BxRFf=mG=UZJxTAW^bSx0erKqE#hIWWq^)k%y8+qWB2dO`W$p!f&Lhz1JB;k{`N+>aLq3=~AcR+P3F1``Jn93Y9v2fB9I7ORj1Eldhs ze)5Uho(JsQF`i|)+E`_Ps4|8r3DmwdjW#8^eOp~}>t9JN-pD7-vV)ZaB4eXN=q@$| z>V-mAj(_V*WC$cuvEQf|sVp{9q>+O>ilNH8SEqCdNzD|x_c@voVFeXpZe61g&P;5O zLb7IJ(K`feN2gVFcZ>Alw+b62ObS$r<*>FmM|T(zZCVmm3Yf9r(sThOBDt5e=SAR3 zNciI$xq0*?1YS1&H|PSv_;4PS;oG$!ZJNei^CzhpV^fW}E5LO0m?yO_18rHfvQN@< z1o5Qe`ScA#FzDU`+jEfys?qDN(35^Mtu0IfK)Taj+N5Fv*8+z}q{Qz#&ueQv|9;n0Msj+LL+vy(02{6Y|s)wqMcR&;3?JxS`ng~d28mRe6W{C z+ggje{)<`zuM|P!=aiM(W7a4_1g0yt4M3uZ04rH zC(LBC`s*f{`xmi-!m9XV64C^l&<_LKXAPyT`GbP1(S@w{kp|SFI4w@VfX+1!Ez5NB zmV7OJfX+&fKkdGvP|yF?sjOwJUEO@UGm)NIErmA1SfjE`Q;-b;=-G}U<)ah%V z9U!~hts$m*{9gF$Y)N{Z3)VqiF zgg9ON@Lc>)Yxwxpdf?p>&BOOu@G1>c7MKB!8_CYTe22GV;0pDynPcvb9^D>FjMW7f z*nXLLT;uv=rzWR_cW+-_@3U!HwJ(u#zF69*?(U`o0K11{WVOv0Z=LpH=4g1cNbE|4 zqkH`(d#K4NJOnI!atxtbKbv9O{p7 zUDjiomN`1)XJ*}OhG1t}$BE$PEHwGAblyQcABTXz8IKI(rO%1YEO2= zGKFT*?nM({+HL}AxU2}BY6(~#1z>qk30Pj^N9gaRb$(V!-V1b*Wmnot`a>~Q4t*yc z5^!^1!ab2@X${jJnYx-xu}gP(iuyaYTS~@Rl=qW-WuyRF?E4Mq5Lm&y_bei;yqZW&XM`(5ut&gm*+glo ztl%l30?!dy5?s8O!TkUhJLn0)k}EV()*?)iC3JN>TI+)RD^2K0)HAHrN7b4Y_l?xu@kn=;jp3#y(7D%re3#2X(W+S!xjmmxJGv$igp29nz zOx%zZ%q4Fm*!PkW{W5CeDGQ!7{-;RyOL%ih#~lzl<8Y^eX~-j1STq;QfU8P?E0$btLlm|qZ(m737X;9=8{Q`zB$fi9=gCeR#Xow zMoW+t@jpVU{XJ_yCLBj5llDWS4k|%)-MQbP*e{uF?D7(2m9? z(YJDU$j{${0m6P+kHZsePE2+~Osbzj7aCLN>QYv<&&q3RYW_eq++~6NeBm4(0t?%5 zYjIJi#G5h+H>1i|)v?dTbdr9%r`3-66Y*uQBCP7_oUXdY%3S0vh@g@2k{jKtm;)|i zV+*^5`Cv~N_vmlEM~6bqqJ8DJo|u^CM>RuZqbU_WJVOa`%UX-WrcjcMMMcV~xA;EZ zq_oiR#vK_!i|^KG1@i(jjJEnF)LBJWE4zh6(3H05AhP%ymkJtrgHI3BfGXWwqLG)# z+o6McTqk23oD_Qj!@(s$f~}r5qv_Km2DdwsukRC3B?U=Iw(X#xm|-9?PJ6#UeH5^% zS$TDCxgk@L6ZP46;SU^IhE%&4yqzcrK$DMF*0ec z8Uxpd2hSGy2#Tsd@;=uV7fy(FD_U`59=46jl5Cp8WC6?jP}i);$NXy%Gq2ReC93}c z5vAe_keoQI6)r;+6ql6L9JH(7oznWyyCap2Ont~wn~MER16Y(!h3wbD3qXS*j77m< zV;9!2c)@5!HpgrytL~2IwRYnUy#hp?2D?bS&g9odD>IU+Ae9CHwnHSHG?gMH>Q6kf z?B@%nK7tcoSBku*xKs?!H*>9xk3OJTy&b*yrzjQ@bSJ65b~bHSNh=8{L}A+?6(^dj zBZm8*>qub|RLOxw)FDZKJY}wlx=93eBba20yOs}JdiB9+(3a)O>OyiGgvu2d0)J6~ zW{6r4If~M^OLEm4J1?yf^_S@%>Mtw`ls;0Bxl=YfIp0fMzo6Lc?STbLRnTHa zFOyf4FxQ@XnKz*d<>gCTG?J+d!StP(1+4_#!frzAB4HpY%)ySI?S<#@1>kvnFQ$aQ#5t4ULdmKa0P!1&)@p+yV+>t>F82+wTWpe)BQDO zNeeIpv!BTI_yqLO9@{%9_dT4k6UH1iV~A*(t?d*K+2^G42P7 zJ@&cg{R2cs2oYxorev6!k`g&SeS1iKr%hC3h>83Nw6Bd=21G<6i<_ZTxCBk(B{_JE z4HV5LAcju*zP3}nykJ=zF61;nQOeaHR(_U@(;^Rsl6gONf~;8eUi=Htsou3oK4fEF z8^C?raD4=yZAGm_os#YiZ-oTkMITh7h-R~GHCQuzz+LUgI;nmXt5jsCMNgFG_9%k9 z@Qdjenn*wJ0?AygP@no!H6|3A`HRXZrnt@!nv&LF*h0{?WP*}3+Rsp-a)zZVicfoA zeEOzvl2LF!!Q-*V;B^a3b25FFps_;XE==W3`u8sXz188VNxcDIOraJBq%m7zf>8?n zOCnTdeh-nK?$I@Gn~1|Vj|oPiRax*<59?s%nJ zd(Z2J;GoMUCs&?l?h#iIigWQr5^`!iw)|e$C(<|H57k@Y^#AF6)$Gf^Z4wT&ccO&gaPx#Y=Ui@@zUVvQnRbt=K8zg4v*w zo3n3DhNx7^71|DBd5KwfCxn{B06lz2_fjGy#h4fThlM+y2VzQWD@0})+}4&{9o*$r z1-O7rk-*5K`M?w0K`pjuOs-xO-Jxk!qlt;~Y|Zs%S)Yxa=vmIXWD_Hr=i9DnkXx=3 zd>0TPfwn(>H%tv`XS(X-N!B`;Y!xjUWxcxbkzjh#BH=z!qrsx!RED10{UnG;6E;J` zG$A@ty^kN=a>ukcXCYqWk&ZemepTgBkd=A6tv$KAFwy+In^l!2+925#?xcVL$AmgT zkR1X+zUK_b_w7GR|os%7oV0_9p%g;dY<;CReN1S%o>wJjL=MNVI~ui zD0q=EtCkNaNW{gtU@id@)hSa&i7~C~?&TblmY&eGW&gCAzZ+-b!FlNQA=I_541dT4 z8yfB~G7giH5t}89yE?;xtBM1l`ps#Q&oOMNPsTHnN2<0YDL2|(*E6czhy(KV&Yiqc zY%3|I(_GU5itA66dMoA@xu|!i^U8pjONw0Fb6>fFQ=|R|3!o47G;7MYdRbUv%}fzRng(u&tV+` zpc^^JY{-Y2*9DiitX$M_f_K`)=Z?-bc{-bID^>uheBy$(x*rv5*_VEcC=-c4rwCo?N-^wnn(3N z|7IDfp8P&%e@7Yj2;$q-j-D~-_}%{cgPe^RCM!9sNp+aO-XJS;$D)3+LWM%XoBEgp zk4`L6R&$Y8T7W)QmVuS>VF3|V;qe+gt!-at1kn#Zuz)RqEN5OyV&VesK`q1wxD38;9v*!<>cq~yoeBnQxY~1z^>-iCX zhw1!(kcmb|DnJm3#cP7165Fh(&s0=XT8p_870S@?+7)=W);y<>1m*S`+fKJY%t2B7 z*`Ya6p*~JhYjRJ~&&E5k-IPKz9lT&HyabN?QSxTCT|37ohP*$=N@Z3*rJ-X~O+ zs)9DMK7J!xs8b}45a@vr8ji%>rIN5y%-sd4#lzk;@GGqpTGB_Rs z8)3OKr0Rc^dyb_3#Qq`_*NLD8BuOTtBY%?>V1%FA^<%MRQM0qI&%F85)@)G&RY{9?0y1g-u z`H~txXf5gZ6U*1CDiHDW=>a4dloS_9nHqMs!S5nyD#rLzD>{(okC1ZmV_7+QBgVlo z0f^b$xHq={npORFMw~ftwn&Z#ilk@uWYHv%u4GBo{)e`*a!6jCI zDfWNx=#gM^JQGuz-WUnS@LC*f6K6zs)|75p%OB)%piD}wlPOu?ZtmG%b8vfg-7NX*I(LCf3o`3PtxckHKr@G9g&+$oG6w%ai$)NhO zbMrq&yuzR7Xc|fizMzW2zAV)>21<&h#*VH&gLpz`X2yfkswq}S`p4$k?if0=EHlK1 zYb9_ja(3@}69y$Vbi)Pdz@Bi83Wjq_b=_u~u)`zpY$o!-&K_8spTWoF3H_y>ZN z2OE!Fg!=S%M<#cDo8-oIemk!}>;urNNX+~}sj^RfDk&d8!o>%ZK;#GL2@ zVEq)Qk52AYn`bp?XchnGHAv&M+WN=e6n&nC@@Z?3(F{7TIVgA9Thf%^a1%HkL7e7d zSqQ<&Czoy+I`Am8Xo?pCFMv(pS;}jcWKg}RyAlb(v>#2XX|-!zkFz-?3U+`s2Mc37 zfCXgX=f;#s3O^-da^o97qH)vgo`!S*1sc*y9k7<)!XC)MyvvzOp__-j<( zv&_xFO5|tM;)kHSX6g?O2kAB^pFn4>t?XY7mp(PW2{WHF{-zqw2SkE|6p1$+s186y z5<37XMmpi1@49&^R?|SqwjEZo{S4Gr|C&EpYhssK=gaRBl(O5cMNc%vQ}4_N=a&s4 zDZ1-p+>zvpTA!C=aiORvml_QOjzi!LiilG1R9b19vuY)dUse^ejOtKToFID$-!za9 zy7K8dCp(s`nJ@(F;?s#GcG{H;R!qtr6@94@)c_PO14O8H4t0^JQ`>=Jsbm+ zzmMU?AP-nYOy+!UreX8q!}gqjdVSzFrqqi>fllZ~C)&h%_`QV;q*1onCexwT5NHLB>O+nvXuL*a4%yB0=F;dw%u+7+LoxT!Q zu|lN)QZ)D zIb|Luh}hH0k@cNO9NM&}yg&mIFAjP$p;RX^JjNRzbCk9)ti`T33C`bRM46^sU@r%1 zn+FnYx}XI)ksu=-EPFB}uFT*U8s{c0Y@6AbfXrJ=2(gw2VP7Mx8h+mz5QfN z*`{J}G$FUyrQP4fff7yK=$NzP?b`#{vnGOb(EYO-&g87zXn)C?^_R`4wwZ1qG@BBm zTt4B$&cLuEB|3Ez_1%V8>UgGj-OaIG4^)^wDPcfDUc3I6>fv=W^rS+>eJr%}n@2B>pQ@oKPb%UEDI55HWtF@d(4Bn9-4e>dfPw z-bp6>jNI%82RWYTKlTS^O`?8Q(T7Z(e9!4uw5VZtT4`$fK+Q!n<37Hj&%E}b3sk1h z#|9Ox-4N|*&w_}qZ%F(xrbK2dW_|~zf@lmuno81<604G{$(6>y9A&anV!$v-0t&04#CK$@JOjKv{ZU1 zM0f*U&F4{#oz#F@RTkgpB^XDk^Z$dd6IJnRkNa>Kf@qaEJr%6lV9vt0AS*H0C*^&# zO2tbI5d92A%scF$h34UB99eOBf#<$c_<=5kn*x-}jj@9gAB#X084L!fB7g=k9`QO7kl=4Ke;R=!1c{A?)gHQkhdNU# zT96AEtZFZQ)R1`z2U`|aRHj#4w)V*yh;}CV2D+k^%w4|K6TH}(hc)yM{U~}KBGzMy zrXgJSInc6tx7YzYytBUMU|Obk`uzA#e{E5#t9T?&l!-hO&|>9n%hG*YeYFWc4RX}CH5^H>|rGr?pGcNVG6G>`CP-T`-|}l<`>DX z9qUoJK&P8hNha#ht)9nJ z!%OhX+*IjgdY!7p0m+J7;F=dI6O2ys(mhxxTzrRzt6DOk$xXqRg|I2b5BhERU%`yw zqjP6F&09W%Cq|dY0Uu-7%T-swET(=m_GY4qbOna!l1IgAVL^ua0pmlt3d`ud6Iv= zGCEaQ&-PxJKewtW=7>rC9?4zth1O3f;kBHQb%9%3Q3ry}kp$bKd|csbm)!v?CJ^58 z2~ilNm<(LX)M~JV5DG$pbS2Sv^uaZ1P)HJbfK9;m($c6PV-fq3JRoDYgKN8%w>d+m z$iPQ;W;pSW__JX&{%lb4JdplXbrIt*Tby(!&j!Q{AS7W zv2eni^eYUlRlg1}l&lfmX1IR*Ij@oU7r_#>L1J_fq>=u@=|t}1tO?1@n?JQ9;^mE| zo-3vO3FLuGLY~U{0f-x61xy71Dzp%6{tT&=hP_Ee&rvH|Ly7vB8DM&dvo@_x{jowE zJZ+#pErfcv+PdNfxx5Squ)Xt4d31}BRNm2m9OQ8AqpdhaIcPJ{$^7egO z3hC98Bh`WC_7Yr|h%F?hf=dP-c91Fv_rT|pbnpIxrE-@Iep#byiIL#3s16YKVw)vz zCVSerj zKvTRs5}Gg;%Fme*-FEWkz0@c*{Pl%p45pv@z71VSG&{vD@TNuOUkx*jBsYZJDE$4$ zajpbn^cbrXM(QSc{i_}Qyz#|K^N*{Jw0nx?ndoVUbfw%=3-d z1S)fk7)e2CT%0E`En4#M}-B&mS>3u;d=r-P#V};FP6_K74WuCh)hsCHr<9oMK)e!_>oz%n0vQtAo8I(!y zNYY;t9{KpWL<~u6ZRv1L!5Ae?CbY1VKOlxvftI|wrsC$J2!oCc`>G`X5-AEba+^01 zGv0%hm5crfL8n@={vL{_L2Yk807PP7+4fw>o76hV#X-KtS`D7%rCa&TVSYNn&N=~t znS@4GReSL&Cm0A`N0oI^-{PR}Y3qQ#SPMLGEVmjoHXE@8xuS^W`-em#iTgH#Nba^U zbCj|Cp`o*Mxig6GUSMJdd6+QYbdui5z?d0%TeH0*lqYmR*Wlm@cFDZk9e&KN)%utY zurC+`4(#&UTUY{4={YN>^dL#{pU7)LwRf)udMdE52ph0^3?|?)mskltN0f}B%Kp=e z^a35YFHZwd2CmstxTDEuK(?;0a7Jj1Q(?V8sCN8q%9=ZF_iYmr!q-btc3;{rgt^Me zsX%SB{v9=e?SSa~>_Bgkm}wrQgCzL5S7iahTl&fN z#$RrzXyVAg1!kqXE2Y;c?363~4zqi(Y2B5DIWOF^E70K&Su^``C(acHvJa2XuRUY! zo2_%RNwvxmc!dcviQ;G5H~cPoA7c?5sMF@PXh3w}5YGzMC8jtrxA{!`r%^6x&4VxC zGv6;BS+*t5+D^RDUh60i%Vh5!WfZM>yv|{ikuRo+jVw)jv;XnnqNoMytf#4Ttcn7L zMMi4qw?0#E9>l;lqcvRp+$)~Z*~VP;i*=dCNjx_|-cDra+OTW)w}e|-gfm6e>}r4_ zoH95wONFayBbH*A%)cC()*1WQFogB_gx80knre!WYVN3<^Ey)83**V{Ca;UuzHSx5 zF}$O5Z-yiy2UZ%xhhowykvSPh-n?qP$P`^;SNkCiMJaXB8pZs6zqKsA?RTEp0e)N6 zxQNjf?)O8gYKEQ_gX-d0S+)U$rXy%Fmy}-ZO z--FWcG-oGtt>N%0R`DdjDmk%YKy%v596oQk*0^Xt=a(SPXV|zb!S}k6i1win z&yd*pNLR9Viic;GsD5QtL93BhiJyU^UA8z1#2?wdb&Y5|(^=uI@$k(VO<8ZEo%Eb&UXvbUrJh-s;mmO=e-* znXUz)sZGgGc;-GK2j^nj437l~W0q(Yhhx5hoPLZht)c^auC*-A{F%dB)M(dOHfG7v z-py%FcY|2QtD;iSzKpI<)djK1F>_P8f;B{(SM8fk1ng6WcV}bbk&zm9Z4=`v3%;v8 zcKY>Ywoh4lS&G?C^q`>|zf(J{Av0ude3fruO{3Y&PbOA8hw=hMXVrEGv}?v!9|h2I z+Qw`CTRrCOX>AW<{>8Wfj_%wmI>P#!_B$;iu$k~(nT;*HVCo?rXxCPm8)5CnbaZbY zqtg+2vnjy^)W6UNoh>$x{%SB^(xqJwJttg zX5fA~x9omiW#b~GxkJ!!Clv6uOv-+_5O(<8(9DeCNNY!86!T{hp2@o{fB)lu9ro5W z*0f`yzw`htUYt8QaE!~5ys&Eqjn!?r7ROo-Q=YPz_MhfYoEFtxv_nVgd)#syHG1Dn z8+|>u=0=UOgZr}h_Lfyy5o7GcwXXb*HENTOKR)AV{(JR%PlE$(cbg5CO@CY{`+rY3ZUF=KKxZS@T}nJ~TJ2 zsw&q>u7ByzVFzag%r#5v%;yy}N2vYK*JlLRYiaJQrbTZXc2u)7DT~T}bKK7^DX${C zPvfJzS0k`ZiNYxB!$KRcg1@47>;=p_)`6_-RK|g#imZTFW@$B<>+V`;jL#C^NJvQa z&bs6I(XFZRazaGD;ku}j%GdVjo?vd@qvPMjdgW*@jP`Bs<*2EP=}vz*(){zIwQV!? zL5SE0jwtCer!MVfKJTf+y4JzVCUAD=pO`SS!sl4(%%Ea$6GoXPX2!Bb9KD! zZa!=Di@CV5PocgsYWBajsJO!~rW>2E%8QPSVjSY=IllaHIzQu1NW6J%q0gJ2s@eJf z;aSAdsaqe^dj*6g2UyOyROZcHWVML79n~1P&d&R65%NG?+po(utxiiPZ1qcXzv`Qw zUSjLiQ$~dVd0^u=M|)dN*oRgRFg~=dxzn|6t5bPl5@0NZ@`-X!^bt)1U~lfrwRxgVn} zya!UIELg7%`cFpbUh47_X4?IhRVKA=B+C`?DB+f&xt5&J)0={9DgUmdR)Mr>e95vX?EN+#&Umbq*iXB zUY&Err*UQNHKoGec_}Bv8KR`~rib{Zu7cB=m5+f_NK;%mra_a>4xMd0yEZB2F7ox1)`;N6dQL9XN68VcMh_(Key`dB@_V$gZTtk|FcF>-L)+-T6EiySd> z)HG7N;a2s=qmME6b74)KV^r-;#F>*eXqD3#9{-?y^I^WvxTUtOS9JmX+w0|)!t%>e z>34VC(x|9;;PQLy5`S#d*KQS<9U_5Q#j|3p_e|_yI3|uB-T&(1(N58ptNnj04!1tz z^`hkV>C?w1pX(oq%>p+;L16h4-E`5xtd?`U&TEdJOt_}o_ww1NI6v+%G<<;z5uIbSKR{K$Lv`@6CQz73u(j-D4qezQ%74F8-MtW|Xj zGd;93@KM4lYYp-QR2;LUR=AmA4XDUX)$euG)n*mvh)Nrp^jpHhf)}e*eGbx&?|51C zqc9{144g&2^?9E+p3TfU$M1KcxcJ?I$FF}{c2pR%J>9ms-6}2QroEoplkK_!*F?7W zGkd#*y?gn;GFg{awc37lEjuDB=R0omo#@fM3y^66K|#^gHWu^yhGPTY|SbvA11tw1`l7qkkZPvc5Q>(7gaApYgVNW9=Odq1?_Z5`)9(sPdV|I%RJEl^H$N( zW8(5j;-??=vnAJEreb(cB_-ot~k=0$hI1rywk^71%Fd#UGw$$>n*%BI~}gob!@U4(u+}w9`uV_lvdn85ELQ z@4pAl|8{e{?wyF2qw8n0*6-SH-aOFLz4TRMFC)v?(=5CGZ|c9>f8&16;Wem3z}9I+ zQ5ApGoqK!+I{*q1b+hpf8lgUbdHiY4?0?g3v_Q1#YSXmH!0(6O-Za5-*}a5!?1qv*$g4mvzutw~jEc(HSKx8Jxa?iYWu3me zCV8v(iJcvwAGjqq zP4j#IHHmg1dU2y?a1vKKxqNowX!M>FnttNg>+t0RjdjKd7L6*>2ID(UK1rB8Evn-E zlyRL~aw9@@3r_c4w&z|Fd(Zp`Y^3C1sht+DRyS|_Hq7fzm}z$7DIfH+YvWF2o|;K7rc0B{)6D+}ER(JJrxU7C_TW!at+<9jk#;pBAXjMeJI^@+=GY>BrVY8`Zm@4No^l{?>Fta+L7q`08Z#W?pPu@iCw zR{KrZ7af+d5BHO;l%Aq~ANGvNHJ%!K?O5jT>%ZUOam`^<_F)Tfu=eiL|MQ>iv+Kj# zmkd0=?@j&r%#GKsvwkZ{b8uY01suINT|Rh@xD=)6MJ7{Acf{q;w1i-d1X zu(JXe!q&@mBdOQV@SVo>#o;5qe=^T_=q2syU&HGR10Kga&78XN?$)6DZ%>78p1$y5 z-3X_lLyM;SWr=dXJ)ILi>2%Jc2xQ2q;(wzO-epx*K6&wINA#;%^K$0Rp7#bssGQ4s zzduB9I^KP5>8s()uE?a9>!NDz*o(ibcss)NZJBG?a|>^6KD$Ex4Rg%^PEhn6){?3P zrkoe|&hN83w`1?weLMHwjucvr8-{uRS~4l{PQU6({mjG5A2>ctS#c-h{=%I;zbwDO z`t47P@|{bH+L!k&%t>Cjb93CYHPLJT$(|$#T2NiNsQ&e3$AG6!^`o|}y5#MB|C0H+ zt3NnubY@5WIOx#J)AKW5jC=Kum&=)@S6u&Q@R$1=-yRhl9G(+U_xo>!BMXd;2Sm6X zTzQ&OqQMNOV*M|e9^}ln`?%ZnZMI9v4t?7G3hL)e$ExV$RxE!5q-ZI;yc~iXm z7~PF(1Bz4c=30rLhk8Br*P8?YVBQs-SDrL!5oEi+{oK)%- zW673f$S}m1Q)nz>n_(tl#u(c$%kSmf-|z2n-=A~t`#zuFio8QrlL5>DGF+#%vQq0$F|Xmc0$OpB##Xld{(!hG#=06fh(s&_XC5(%`A332Y!6<5?F2e~rv z*;kF8J}>UkuzT@qxCT_fGM)LDYTcIUZNzc??N!1GZ-5JxjY6c$Z}4XORPKJhUHd`b z3MB6WmF~Nq)vU_s#lvDj7al&fgE8|BiaBNq;a5+7!7hCoexDo#kV|lsN`ZA?V&7Xf z#dn<2L}E*dEYBRgdkvy4*h*f|k$y|j{w$_@6<5F2Uz6dmE+o8|LloMJ<1eNJ%|AF@ zCaV0poDyd}XH1&OFoJ*0QsC@T1nB-~o1chHwrP)&*M`wQ(<33vk1y9wE(tF<+%)=Oot`mCpnskXz4;yZbR~UTD z?Qv&6sr}YekwLR?47}n97h4SR-(U7wn=#(FgFF-9#3J#`4$x$W(yB%q8MBI{2#wU0 zf_&jX>M)lRmp$6!_HAFVwYfK63?WOXm4eDjO+#Io?4!WBRt2$KnqNJgfSN7Cg{#&Stp8;a>o)$QW=jCvE8Rwtmk-ih z)DB-Ol5}6{*pHeO&4FJ+y~@H^qYObbBjXo;%LDTO`H z^rI(HxgRubqS~4O|DGgGV}!8<#G2r`I8906fElh^?!V5-oEp+ic3ZQ#$*A$ zY{3GX`*@3|;%*W=Q!iJ|(0Q(m7-v1U6T0&HGBb>Ja3Jyijnc}>F{t!gM6^tx3K&7; zB0l=7t&?y|s(DjPQJ;s~s0Xf1g?Q(gjdZ1qi8AyOy1&YuhtFx{qx2}a?T829lnE7Gj<36?T5~fFlY!;+0eAaadw0s#?{AHdyGwtqmD&?u zyn8adBFUd2$co!DI5rPFuR9&3>cl48HsYMo37r~KLf^ zL1i0BjeTVCk3?~mWff1?{h>UWl2}r0!XDw}3%YHVj`7g_)9;?iohCOXLxoxJFnYeOl8a36|J6jhm-3rIh>=+f?KpjZpI3GVxJ4&YXH1*%_%cRhkugLGEK9A&xAy z4C~5FATPhsWKTum4dtG9RGJCn@gp2n+-65toJLu)`MW3MyeQ^^9=YwZbYZi@d4NsgKqB z9M}_=f#mp&lMVIkjZiTK1{KD$-$FG64{kt9vZ?~FD@_&dtejFO1f{^Sp4Xrf}CUfs%ue6n@sMT@`tba~ddziZ% zW7+4bEBA60cY2>$8QG&|K14{wUvcTx0CmW8;7mNBLL}7Vmj?UpQ*(-1Hiz{;V(V34 zGVRp;q9)??n2`xRl$%|V9*CaRJ(^-?J!Nc~Gi7GB&m4XNd_Qe9qmL=oM<@Q|G?toM zO7$^Fe_%MdqZ43$&5*FPCI#VHQiPEo?0z6;u4Hf2j-WpDw=R0grz`3=@R#kB$#orYe{@ZY;Xdb>>>99lLBW}*F=(eyQoZ_~W>U}&0i z)FD07EjIJ=sM4AX;G}GUGW50)V*c-@VYQ~J(88)>OJ&{O{=ShNra?lk)6mVqZ71J6 zrPlUdtep>FQUsr?B*b;RYr0g&z?m%4^54KWAo`mU&j5`uDa&aMljSaUsQ(o7-X~uOi$1@(M)gWh(eW3RR)nvlp<6Ren#^ElU~$>c82V zS6^Cs1A_hFk7kV=;NoHJL_gr(nqC-lY0XcfZ$9 z#gq`xlZ>ha_RG)}D|u$wg_NA$5fk_MUqSpAcWW}+ctCb*<0FdqFLJ(gF*?Y?qTrb?uw^KN)c z=Rv`#tr?L;aEwq_PgaLyf}H4ACGs zHAttlUB1CY(+*Y+s`ZLfy-)7Qwk|&dUs{j(W=vXoosP~5ISik@#6aFED3xt>Vx3Tg6#K!B8+G53)%mn$dwqkcl+&9lB4B9B#R0J>9d*rg-QWJ& zK3w%%_^*pK>95S?UtG{~KVQcHtc`Mm+;UMJnmoWaA0hr2HbvP;68&9i9DMQer6Z0J z&YK%rI(H*xt}C&7G%-m%jgN#Y+6)(5nrSs}kL1g?WNo_hdTJ^ngl$4N?dXCFbKI2# z%$Wf=FXe1rEUB{|@i^!9Ls@f@GrEs}5yfe+rwSKCxT0PD#KhIq*3_vgb$Wh{yzMOi zA5e6=zLi&;Wp1P@ZsFZJHy+6BSmG$L)P1lmrU!}PUzmcn#~dP znAC;kyD?fA2n@So&ojrJQ(b#<^)&BQUy^b?&JTj$Q&XJq!Ah^2W{9DV>{Fn3jL2Ol zvZAPZhn4u#Qir`@1dldO(s%K7S@9xud|AluNtJ9`9K`NW`xuxQ0FfxG+tf8Ld*sfa z`kp(GjhWLA`Swcf)7(1vbVB75|83hfZZMeVYDPESlBl&L=s!0cHg7JO)K5I4w07Gk zL`au@?HcI(ds5?bQ#(~?NcgDw5vwvb14)Ys9^IRYJlVpQt_Ep@yZamVZ? z*KD8SA4|QRM`RZsjd0|1ST8k8n;zD53cARWZNXM#--_ehm+q{yZnE*p`1A3&nlH-h zwrCOYa+I5$V|$%I8e1zlni*TfzBv3-dO+}-c<$3KN_sBZ)CqebWH5%r{na$648_?zX4mtjuawxa1jpHLj171myd7aPu~zk2a%+Np!HbJ+|7|SXUlLF*STDuP-isbO zLDG3j=Nk>Tm0iIjdjg^bjNFgz)={}VKCslFSq@_6klR!V18fL5)qS{HN8tvw0^NhJ zPSN-xo<+cO$dSv|?Z;1fm3+-CD9P^F<;Cq*BuTcIh9sGgmnNOYp}F<+TPFxq7)EG{ z-U(gkodg?$$R_b!(*DAnwQW3H#aRY(w6{vwhMHALwWR+>!*nsQ>r-o`dl=I{@T@Wn zW!L`~H-&NeH55(h^tIr~U&x=^4Sz^oPg`ZZo|3v!ZG(zxURU+)voaTyb7CQ#Z7H}Pb<%dy$(GYuyOyb(X4c}@L(oV8V?%tyFTwvPz$y`gY$3J zlzqCqG?~wTGML1OPL31aQnG4H94w`E(G|A&OUwV#`h%q~Lx&B{{10gWI@Y@oqZ3>u+9`Rt3n#X78YSh{+eH|;t(ZVM3#{hWLnGQ@PBx~7 zFZKdn!Hg29PgAt*CeIpm7tLm<)+AJ7$|o@sak(Ri+!Febhun4hZHEA1W9@c&>FvNo z=cvrdUQZfS*3u;^$mf@-tLW@bBv$WhZ*$R#aGaj$Cxxt|UKJ*ir9Vh#Csy7bEW-q* zo8n^Z1YW6iDP#=GT)Nz;>o4sJ`j4A+(f%*Jg3eTS|7f}U=*E;1klah&h{wAYuU+&N z_8vq?H>R?hAy^HZkb)G?=h%P#>+|Wg!PMsKPN?Luh5_?-O0IO;FJw|=BBYr^CCa9= zk!+MvahyfJ%@`wkspbT%-V}P1|uzeW*ol(6~&P2;}R!*umyh6SF6H#>SUSe zqQHQ3TYD8X2pJF!R2K(|o&V)F%&;spb9lJ1D>qz~m8lbq{;E*RSF_r*c{}vCPf{)N z!hqASkB>5vanOYJKsmNC%{ME}fp|t45JWJ6FSdfz58aQZ6kkvGzd&xh4Ha6{&P~>DF}31+VhY|_UM*DH zQ)&{Jw}}{sHt>oNpM@-6AW^dI#Z7b>ywYa;O3VEE9pP#N;rsTqjGagjWya0CcE;Z3 z)VB^xm`=!*^xQ_XgJmkFv&smO0dwYiQildkw#bwwt(<(SqJ%vk98HNatCjGV2=2?e zojG-fw-oG`hM?pAz*>G~Z@M#2p1WK%Jf2p-*kAeE^&z4H)2{p&CrZ?s|Bv)By z6{lg`b*m~6?fU%x5AZy#p1 z3)L4TnluXyJUhH$o5H}9>-AEyUgsRac%2AIi0%a!pl;;6F&|;bd`=y)G#Y8(_pYEUg6^g&WVRr(pWY#>^a*Xhn2?( z##{{)HaCQfKF()i8tjKd($pMjB7!Ae&L=DUgkyq2rmuRsr-PL7xQf$p&}pSBUuciJ zW6DiE_*|`Oen>YJZtXX;LAh64aELN*s?L3;qT#eMV7Zj2#}D&ACl|Xl4sa*dxh==0 zGed554l}FQ|B|dNo2jZ15#%-5}jqNPy%d3_gl>6ET6r*IgXrgASUVJ;-43@`V8++C_j4`<4a^01Bkr7m8 zrIez$Pp%y5f(%<1n@~g7IQ~+kY43HVd009>E#?g8aDO@%zY8b)d221x7QyLAFn4Wu z{M*01_{O{c*c6Yyg7(htr*&Iv#7$kOY6MCrD6n_%hSojW%`Kr41#0#9X=;~rK~c(T zt8v+u&NQFxO{%tFz;M{Z*fiKcWVh; zo#O>8glst?TFCE28tYIVkue`Pxbc29MRF+F1|Fwfa804X=AQ)b$aOJf^sE6scD+Yc zNb6-DeB>p5aVi1pU_9-?uRO#Uuv)UGL z49-?L+sC{3@z^Yr^<-E7_KN+5)$wdj`HpK46Bql|Gp|pKVh~ufuQrELQS<{Mw2s z$|pCf$i)0>N-9UV)$GkqW2aJU6ACZqzd7^H*u1E>jy$bsq&C+pCe(9*Z7T9?$2dj*-5uKlMH*0`ZcrG4L?%jCu@9H(89og`@=4u<+ zv0$t?{aVpkxmQS3*l{yg=&o>nv%fNpByo&2x-bX5V5gSF!htO1qXQ_45f}Fd#DYK=w(S zqoQRb-e{5iI)AC_i;*#bxzMq@IY|{aXX3yqjUPFc&+R_2Z18x!f678C`rBY!?T2J4 zI?oxq-Ki=n72A2Nx{A#4FfW0qafVNM)qkxX?LSszWt~s@;O~2b5V+8ID(>u@VS96>_OM<+;oNZRp)`T!`d0Qw-CfBB08YE}ntPY!D?sV3w z+&<23){Ig}uReMJ{o90jd3^m=ol{wP5^ZASq82HHeSGL^4}_u;Jj^B{?G1P&^Q(?; zsj=1HZE=L;8u3rNcAy1z?oB=hiJKtdk&efn^VgpD5aRCBs8K10Att&eSU*}Vh)buRn*>ER7&4I4*BKCdCmR8fKv)96FLm*Tql9|CVT zK&2Bo%sNvqDWZgCg>oCA=iYger*(Wkp=q4b{Y$~a?K0nufd&KQDmq`*V+DwV`)SC> z_tUA|4$DibMsDIy48zR!1NY@~h?f(;)|^y!EW=9gViyhuSNSHUCw_d{b5|EuX<9Xk zroP;YuUh$ha&<*lb33;J7y}w+th3PvS(Tl!FT8v^YxwNH8U4(_2s&Swv~?Xcng#>? zEpu#@j)G8>H0juR6#@xkG=ur@?&A?RVeH~6BQLSi@ z)z3yQAIbKuC~x!Ce0yLM(Yc+%d01xNZ^L?n(gZj*gq`d*eF{FhV8t`99Ag3ScAOF! zn4$llRuoM3Ch)&5D)T>ovQ7Wt!VmuaRDSn*qve2=c@i@!qAg=%18j1h^67EyNf@>! zZpz(a?KVJu$?yJXs~M_6lHF2bg>MtH5LU@Hm{|irXK*%0(a%ILFczzYovsV=Wp&pw z79aM#-G0B$US9kSVe^ohJ5|jOVd2&Zhi#QjnRj9}V~U=(n`PyXaB<@aOqBl!PDc-0X_ev!=Ow(2+brjILS5tY=b!tvHp}e{SF=We?G)f4(Yr)B*JT)Z0u(A|2T&M2V z?`>w`qkfjCdxW6pbg5?lZoR5eT{oh`x?jG64)fs)Z@TO4S7piF7LT+*X<3E zCkj*dd|CHL9+vI6`*~&8&Dm{{4#b#~07_#2`~veV{ulmQ%8zs(g1xFzH5V*SoJ~T} z?hETdl6IeAb~}3?E4qBQfg7Clp)o#4Znc@b3`WTw8htFaYGMy5ihcOOHPTt$`xRxJ zKxuL2MNP_{F7b`PK*Wp%X?eTb#HeNK`Gb2@%EP=MOl`OQ11|5;tyZfsd05vhA}kS) zP1pdqkFXglKjmaQ{m>58ciN)+kJ`|;T{J+D<)&11- zUBbw+n&ZsA{FKYKOwyMR&53p{W_wB$8a|Dt`x9n1HFd!zR6a=}Ry^|-81 zl#3UtZwY!68B)yoY0yx~r^rxt1<-zKI}5BII7coihNW$8IbE2YA-@>UEw;EW7#mnX zzj1~)=LPqThN4JUbpthr$Fd4Pi2SCIypRuKCEtI#)Z#$z(8z7Kc`eXuMLkY;Fg$o` zoC2@1gKPR`ry0|gcg(roMJob~FllDuKKdhedyO zwM+7U{BLK7L;f&367MdX&JUSrCXMef)nJz+;13Nadsex>Jn6)+3g)~BZpnQy6M=9?(w z4j^3E$!d|$89fI1!5mrZ%^!;5Rq^9NXcwEsB-)SAlfJV_0|p^{d)Ib*_=1I0`lbdx@qMX_lS6xg4j{}GjlR^ZZH{uPj>#Xa ze32>3%znEVvG>Y4dL!`HfA}@kwLK3U5`&q^)4b0NIarYp+}PsP0->k+cg3gsUHahy zLkrTHgk79{;4a<#3@Y5>A{>dEX_dFx?fEKiT?8L}q*5}U8^)hr{O$VMQxlQTe*i~q z9fE|?wBt9)Z9hG2Zh?ZvVdZOxon&QwzL8|fG#2Ti70>S}+ z_Y+-~@THDyk7l4Wcu;i|=tKr??gNa_ipN9nP zHga6hD^JjM7GH^$9bQ@WT$<8+>(4pi7sji%7x(iaGjWM2(}jB(+5ID@Of|SKHKW!k z)kiccNk62czlqk!4LCHlx{E;*ReE`$1fy8dXLGfy1DJh(2Z(+!*+qwd7S1)DO9A_( zQGrGZt3X!e?21z#6&N-7$~-O_DeVW$Ih7k4eac##s9zt*2{nPN1@P(!GHV$96j&-d z3>^>BaPbb_!nJ+A`_2Lw6EVJ+TbfD(T3l%(XpsHj<=1SI1f5%O)>P2%~#-$jm%NQy3x}prKP4b)P*le0ggd4 zzYtP{pHu7a$-CE#$m2%O11_xG-9Mcs%((-Un?Q!d=QymgI^0(V*GWS@mDHKAvo5Q_ zzrJGE&gCIS8W{#a&5Krcva<1jG;qw%&ADo=ZE1T^-2v+|oK?j|>yu?aM}Zd!0{D=z zt=ylj&b?cpy@0(~cj=@GG>LK(CQGJtM$nBQI4Djs_MxDb;_oKWrHAnO!_Cb%+2lt- z9g+tJUMrWc*D@$;%;4JnpGhSL8o2gym1G>ZV9c}{W*%Q3iz-oF?g5>@UD(bNJyLc-`6ecN z5trwXt3r-f#+U9lA3k({{qO2EH=m00zx=$PLU#BaU<7@{4*S{ed?Gsmr2(pa^MSW1 zUzibGUB&)2w*%5q0P9A-RJCd9q8C*a2WH@60FEes&{j1h6xDX?1pUCK&4E0|?G1fN zT@>CB<)uUyHH1u{T1_9YoyNr2X|Oq$jffGRcmMcTL_(1AA>B;^XnL=Cd6T~NHyvRb zeEtm=&2o)17TVnZu3K4L<5P+=Gu-?BZeor7m2}MRQviiJDXHa-yDj*-eQVi^6IP;1 zF|_k;voC{LU$Bazy!@%-#zN+Q9%EPc*Sgyk*1g;vY*yBGv}33->#zt_axqKQ;7mt!8S5TD(RK8&2~j;{S9exBtmqFxK1dQiQv7t9Tu| z)tce+6I}GioD~QE=j#(M|84!|{XmSn+GAkz;bT3T;rH?A-(ae_6=RL2ypf(q`9sz8 z#~j@IB51dAeC)mNx0+`6`Rw-Pd(eK~{_n3y`;&DD{~KeMkCCHF4ORuefM;Upx{$?p ziQCG#3Xt1L1ScB0|KXYweKzVD+gux(2X#RVP28_jjDaYxwzit>a(vv<@~!99ooXi^ z`$}Ek7wsp6X7vsds@{>uI2~2#W_=0K_xfS5I1Lla4m{d|hk6GXMndYwNY{DclWed3JiD zFI$zeOY)XmlKMU+j+nK>E`X?cUfr_3@wOi4f!ZPy)zI9uS?ErOIpIE9iRTc5A5UEi znVJJU6J^uT#cFK;-53;Sci`mDpWDBN_3%$uoa~CDic{ogD{*s73NU=FBI4^-;`0G8 za#g8z$$qFI&k-f=$sKC3VPZ}j$zl>d(+`!)DaiF~pcd5;_a%=@b+6z5mgLPCKJ9+6 z(^|x@=2r}~8XwUZG&z6BP>vN{QIgyxcdl`Yk_?}F>i*Q5YC1BrmVWTUu(oM`m2Y*8 z7iI!pgMug5x6IA+{VUX0$Al@;T}L!irgl--Tqz4^>t6*<2&_UWI4 zmYcU}ZTndSV~{EOAdu>kpQ-2KhPWMqx~*GhDUwxe*)zU`Nfc@WGolK884 z(&f)WgrA4{!Gnov2(wPW_3*pq3v{7wbX(L&3Q|A#YKHu`_uo0MKRI#VKk!AQZ`nt$ zEB^j1hyDGD&Aw*Wk^VhZobH2_E@kH}$+4SYyEO`cj12c zYiqQlk;6$MkOo-wmYo7nh`*khv2&$1rGOqATbZkL_Lc9>MnU=j6CI|Qx$W|-dSGmx z3I0kl_>_hLLg+hTK)i?drxFXG4fJff8}!3{w&m6cSZcNsF!kRv1*%!2!s1Y) zXe{<5UTjzuxWGxP8V03vpJI$Rs0kroc-ah0stXJUnenxcnccOby1*?3(*%& zrl9erXhYJ0rVn!lGH9+-r>!bz427xgaIy-9|6z&D^3H@vOYwiJa5_ zbamlhrd#rBDN%8Z*{^o4?TN!>EPp@zHrna+1cFg;eQ@Fm&x~qhu&vi6q8QscY!j7g zifi)*J1_-_7H`YP3b9TW)Q5R3LhX@NX>=0S9~u ziF}xEc7Nva*OzKe_ElAxdNja}GzxMySQ6|U)-&8tNepyffO-8Pm#o{_y_sc|)?Oz2 z?#_RDM2;3;bNhBmX>^x6XQM&blQ{Rv5pmrj*bM=n#i{>oOx6@9UkJJ5*VxI~q0(5+ z&|sUmEnH85Zo&I%=tXd*3RAB-0ZB=Eb24aVXqirx9W??%YCwdr453!D#h-)FCiBntm|wd@TRK8VaGx2ajy!c+wuqi(8;5{ly1d%|hFo%x?>Wl5WP`aFh0!M|HT=F0Q4T zVs@XQn?M&nW?NsY;=gek4dLGn+Y+q}=e}3PZ3wOs z!nr(iy0OU*N6HFSva1AV7Z8#g2H6AJR1R!e6g<=#?SAka|H%pn8Z zMzt8TLyYWSv|0di!gk_~EZf<{PX=$%F-R<8>$(9R(6?t7zuUio;Yx1v`Xu-}NxU7h z;H5Iz)6j*mqONumdwh5(z&9GUzKu7Hnz&rcCfGb%iD10my}KCzZr1l~EG<58`gTqQ z0%7Y~b}LVs)y5%zKYau$`Q8)=CV~wKi!Nn7pBD8n1&o5D)Ug?l=3y%-gotMBpou5@ zvTGI(kR{HGv%VzsG5*}{)baYQ_KIK5Gk$bb&%K;)Vk6nATAi#91B&-~=jwZm$cInr znx3pG*HWfC%qtGT=QpEf-<1Uf5}O)_v~Vlku0wM#odhWe=~doE(R%6B z*G-iz8Yq65DUr3yO`w%sE!uYN2o<&koGzA-pOYh;nRF5;BDRagB%1`BnW^A26r%!- zW^V+&K9d_7QMk0q$hwbV4vI=w#Di@9D5wQKbs)`pEyobK@7oFWbBBinG|l#?nt&g+ zCVE9nOmiD@H8ebhfmx z4Q7qVvpb35!O0@$VsjM#O!NwgH%@aCTpyY^25FB1c*NWEqJnHQ>wL$6=w(%dFg54E zz@V9Lq_B0Q`7PSmIPFz2q5423GVl`mn+k8t-;dEN$6?l~il)i$@ut}308D-*`skwT z)v#j6_eEi>rEH5rpt`PV4Z4e|0DNfa!ke1f3Hw}@#;~}ASy^+oy!a4@ffH^P_kG#{ zqrdX-EASgq-D4NA$Z&wk#yfzk^v<0LI28hN(QT|!_RVc*Y8PodXNXn8M0vI~T1>qE zn7~GUd&kMgjUK3613*(+KAP=Z{eNt$W(1|9`A@vz)7)bIxB@&tAp4etK0 zv#xpbf*=hl^l)2#xo>i#o>O4R9Rn#3!2_w!(epz1vp9R;fuN-nN!bh*5v1wm%@giF z{El4BX%01e1~5Yxt+DLGZlk+2KV%Z#5ewH|ltyFu$1rT;#pMo3rG`pIN%@oW|NYdy_8;j>O)PB^UAF zFcZA{wX}`pe|3XAA=d>o11|_-bTE(&ja?sF$x<<*_2jStB@l`4Rq3ro8Yo zwCAd+8OAGyjTE48NF}FQzw`SV{!-{sC|+&n%?{vhLlFy)7_*KD!Bt*O@t8+Wxa&AY zV1E0$u!|h>WI)bI#R>E68V^0U8PwXElw_s)XRm(A6FB2=O8D#JB~)N?lXmrJq~laa z{89DcsY5Jswn7V&o(WhnW#azapqU6#s5WBmTV-+Oog+^5s53^gavA zy3#{PIqdbm)jc=rfNJNx{9TJy0z~yvS}Nz2bEHa&4Qto=+dIE7rFdLNX;6sdyw*RQ zS^udmEv5f5OY$LL`EWX2ve1Po#8*F4OPN=n)|jyalYm1~(@bk8HcIPK78FH=Hg`78 z??Q~Vct1t@K?PLYZP1?Krs4a|8)IO*jggt_Z&+@Hgb9zo-!7NdH>lZbUedgDT-uAoUfRJWPH-L4DjrF>`zdNh83UIbXn z_Fh=On7E-kN&CF6D>}pzg3UIbB%F<{TSttbr+w)lH@|}b*20~QD|kiBs$7ZFrN#`0 z15M{v?gCnz&SM|uetEGb4bUbb(d9;|^_aLWYNX>)i&9;DCtt{@bRBV$Nv(O78HRNK zfB#@=Q@>Mu-Nj8G^=+1Q@*RW2icvu1Gw&*%P=SU`=LF1-lU{C9v9n$KpPzSK^5i`m zQTh!}tC=(<^OiU1YGd71lbu_cHKv|I_2=tB-aNNK5vm@#(znZBYQNXa;wqBcXZx&u zvvvbe?$4iJoUtF!AW3`E&C*LUaf$v`z?NRLZ2_t=TbgRoOv?v;B51>871hpZ!IIt} z^>Sz7`E@!@(JU+REk8(wb4iGrhFqy63W&Zui!#2f!-?>857}p} z-^F8hB4Y?C^1!fTlnF`2eU5^UGqa58p8tM~{$%n#HhlYuHt3hyYn6RYcobZV>WZ)O zD`}*V2mi{n{o4KEqN%#~=o1@JB4GUuC}w;vhAaumwK*z;r>ZraJ*m~0#1+Mey;;@6 zRgk50o9bO()UVVZj$cFARi0gIO#N`HX>0 zNbjD1WV1Y3Qw?l{Fw*}y)waEyAU|r-WRJ-*0#ZEWK1c)Lx9A$M z+>mMxUtk1}Jz*fiXzGee&zMNhwu})6qM#eQ)NJ$icLC$Kv4iqhJ8_TuaC5G;-~eN& z`k=e?`bWdicaDL?o42dRDx+m@)Tf49C+oyum%EK=1`m4w#KBn%FtJojH2Zv9EycSC z$Wf23I-xA$kSW6}1vS-GfpTW)e(-rnv_MH8;5+GHl-nAIfTySaHn)GpoWMb+#0KC7{fr-f}6=wQqz?h^=u^0~@4sd_KEAW;60H?Qo3Z zrTZ$D$mO+hswq{HsYk{#gNd;=r`kC8>5e?a(CAzVQ6(`Yu-a- z>?rH|Z^Itl6*&Tx&F%$6gt42Nw;vIlfJ8yyu}=@wA#8!u`_%mG8|j?VzqAp7YVrTX zSQ@?_#8{NNtHl^p#xnr?VEI~~zA+t*WbXKvDn4@!ifNiJ1g-5St5QRC9<*9AWNQF% z1cp(dStaH3Jg}EAJ$AgAuvBZBRSl}7=7MxG!#~Rm723vWJeQ?<LkDuDkV>f}y|;S$Ky;6@PONt9 z{CZdu&lN7HjLtk!w-M;XLeTUsbbqec8fC>kYN`SF6{aMuAh~R9KbbTYKE~3AC z+QcgdB4}nq>rcKizn+prlY1A++U!)7IDoKhf4&viZJ>?6-@TQX+y5hs`*8c+-oB~V zAUDAmhE8_z#^w3dt+kIlf%QYyPqAdNFkkqBtfZ(U|GB|Tz{M>a6PHjM61a2_n(f!{0lRRv16O;f5p?g^7XgJd zZ@jg)!#D|u0c;-PmL~60geQ*T*V$&R^c0nvLjjc5>1(H;I`neYlCD;SD#urWaQyWS9 z1^0EI-+s}KYX_U}+fRU~KT#-_3=f}orx-h`AGK>~%a+AiR!P2KH|t^LwP`a{oOq{n zl(bcu-d^mYxXqs!M|%nGG^s@Vm@B>clC;ME3h=)ee}Hi;CuN%-&o`@HDY9pl0e%+y z%x2J#R!6Me-=%)cuEn>wI)sj?m*p?~dQp8_n`G7HXExu78;Zlpe{6XfvtwO(L15=r zExD4izDu_8s)2Pb!Yb+@?Q(A9vt-P#v8~2UAj)O!AyjwxB2c-*l00p2u8Mw{^&zc5 zqm<;l9*UtZjPCjG{t9TK@DYn3#X%pzE#sfoZ+vmB&`uGr!ZGiu4+D%$))=qGAe257 zFG^L_?H$Srt=)Xr0r^gSFQJjcbmcA$<@Re+pWHM8hiFl9B@U8a{Fhjwqz^xbn1%!9X)9H3whkfVVEB6C_Dn1m>_ z-Gs^4$ctx5Hcy6gHIQt(#}R>6Y%p%U%4xG`qCvQ z#5i9jbzD9G9JoBpPdIYKHq6H9${~9*;0kk`?n`Kr>{3-c_>)R1jupj>4sECnxpW^8 z3aA9RR%5&NC#%WPziia4rWaL!vt%87bb{NLu_3q(xTlyLgwaa@;f9Kf)CW9!)Mr(I zLdmy32|dA;jVcC{=iNTE4K>EEscb29s=;g4at{cP#LiP1Z#~~Dou%h?rc15O;Oghr zW&Hb%758tqWA*=BRH)Lvfk7@x_BGySrTybur3;%@{fd4vIqr}^6K1?h$hVqzBhJhT z13T7Mj)j<)?o%OffmIcT$>woDpSQiG_HVX6S)LhY*Dof~;J-yyb3#*V2%w*KR89Du z!V~{kPZqcs3`vGL!bdlwLRXABPw-=aX^?ud!)6?iih>b8VrF*Ln|RqVnJj9*f$3M_ z?KiZu13^aiJxrBX-Jm4ySOc+pMq?swzhf!MzPF#3@!P!Bjt`EYNoHp8{Gtj>$>UX?JyT+eYpB+f=nmd-OlauK_+lSBp7tee zWjuspgWKCMw z1;e9Ccrk}>l}sJ1`M@a+Y?Df}qKHAA!ZgZ7rTSP?O!H(fZ~gt?FG%0A5UmGjX{qkY ziv9w|v7o2nR&!aOx<;Y71#7qkpz1YE6tYjrac*q7QGevfW+!rk)Iy|mv)FS5m>fu2 zQhm*J_W*b#TDVATVe?!)33*K>c5$`mbV9pgNWdmdU{PUt?-#;+cHdE#P2j81;Zv=C z)c`ZPt*g5?cOUQ?oZQpm>4nNbd%g^i0}^huc{j+8sd18BbH~G`yHBc~gRGO+0FJ%|QN{ zy<2!}XhyHbL8)$Hvbkw?_NOoFoY2b~78q@~Loo^idMNayX|!?s;l^UP%PfJsXbeH| zx2W}ZR~6TcVS3*_TUVe;))ud&r8!*%%vj@s(Vdmi5 zTD!l?tOV=NsxE(1f_)=6d1(u^0$Tx#46YvENfK^hd|FsoD${fEFwa!|4+{@i{Ql|K zKSUmkYd49DtNq)*+=clss}Om@;j%aG)|BS#f&V*IZGU*~5C3=a$d8Q7U5d=%W9~m^ zf^4N-7n=5ul_pZXX%A>YQ^M?jOn1ZEE9<~2$K}IJK#v|14TfYsa*S+W(NZn6t39+^ z4H;}^o>wRj0^rsuQo8JWk+tss`f|humHa zspi%{kvVW|Qjx$uDq(&N^nriVqdd=MWP2#>Q?m)37UWZoy0?q&5AXMbv&{R(%!(oM zUuwT*t2B*Tga0z+$|pTTXRg5W1tF<|$Nz+y{7&naB7U8fYcdR30ZNpvpwix{Yaz{l zHzEj6-o-Q~`5!yJUvTu&eeVII6nf0c^mJiWh+!HBx8Z!YSsl$};Hl7$F02A(IbCAA zeX;ij_(R;R;Phr+`hEQ?IeppkYdgqpKacb-B(a4f(O9fLV9LIz{x*D4SQ@6kC4FThkAC!naqr0qNvZ(Zvtlp7tA>Y$9s$;+ztkxW z*vt;Nl)0MY7T~Eh(|3>4a4$UfR7rSIMH8@VfeS~gym~TenEegn$upZ%g6L+}O1#R- zO28EcvW;Cjp{zL=Bha}s*NV;#wgjCgP5(U`gUQ(3fcF=76RfrtUA^s#glsbOAiP^+ zC@VeGSTenr%59V^EL;IR+4Dm3ePCoe#L1feC@SM(m_hPM{!;DUgF$i~IC65~#i7}Y znr+@iIb|6?#x?=QD(Jhs0r(B(-d}yeRcGvt4Px2;1eN^CY9J$#PGKOZfi>P)GR2DX zYO99@{>iUy0k<)d;0RFiyCkJQp#)xO^`vP^1RC&(A%McrZ*{eTeD;0Ju46RZ`hng* zSRxikQ=iYu6@XAT8{jmu=y#7&X0}QqG=_E?JHW?5zHxn5%B%Gauu7Y0w|aK{)}Kd! z>kQb)-N4SYk@GfWf~DmoUBRv*1ESP&3zu1a^1}LP-0pMm#4f;_JTD~q5(aW3pu?fV z8!pTQ#`cQ&3`9@ev1`dTTXl%d{$Pgz1iy?1 zP6NgBtN3em(Xb|C$0>-3)am;#EUe#`}87!|Fiy~-t1iluJExo91esoTH~Vi={`wSyB0b9hweIYyUXFXhm2YKn9sz2Bp@O&vj)se|U6- zf4O7P`z<>IJGbBsAhTm+G9DNvXUM!@_)a%grBv()K?)Ut+n*0O4`7GWop0t+sXyKf z_-WUGbA2pDPx#Wk6<0%f9wWo_ZK^yDPtS@tQ7`oQ$GKGh96FY5Hd=i>l?s#H^A)Bf z8<@fO0mCdCTcB4ruefAp;ZjBp%qWl!ufpCbK|8@GcM7To&WA5q)faoKJp`ZfmG;=` zZ{#G3*C!-sX@pn#;`$6_RtN4E(iF>Eq+!5OEJ``?480`z!>7?K$;~D)HGWeC%(8pm zN_&E_tWL)_>Q|^&R@!{V3z}^Aj>a#(ao4?Z=1({>Yv2*U?L9mCHH0QD^^{^yufIXi zxRop!Kb=(|B?EWBe|jRaUExE~+t1GTXokkmZ1Y}9y2by2Ksn^ gNQ!=x;b&5tG`;s~b>~OQfq&=EIG!&0&g1_71EOD-ZU6uP literal 0 HcmV?d00001 From 575206d7c1c1afd9aad69cff1c0cc734397a53c4 Mon Sep 17 00:00:00 2001 From: Jeromos Kovacs Date: Sat, 28 Sep 2024 18:20:11 +0200 Subject: [PATCH 12/12] restructured app, parser can now parse .cells --- src/app.rs | 86 +++++++++- src/app/area.rs | 38 +++++ src/app/cell.rs | 24 +++ src/{ => app}/kmaps.rs | 0 src/{ => app}/shapes.rs | 55 +++---- src/{ => app}/tests.rs | 27 +++- src/{ => app}/ui.rs | 2 +- src/app/universe.rs | 260 ++++++++++++++++++++++++++++++ src/lib.rs | 340 ---------------------------------------- src/main.rs | 50 +----- 10 files changed, 452 insertions(+), 430 deletions(-) create mode 100644 src/app/area.rs create mode 100644 src/app/cell.rs rename src/{ => app}/kmaps.rs (100%) rename src/{ => app}/shapes.rs (91%) rename src/{ => app}/tests.rs (89%) rename src/{ => app}/ui.rs (98%) create mode 100644 src/app/universe.rs diff --git a/src/app.rs b/src/app.rs index 5e660e3..92118a4 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,5 +1,66 @@ -use crate::{shapes, Area, Universe, DEF_DUR}; +pub use area::Area; +pub use cell::Cell; +use ratatui::crossterm::event::{self, poll, Event, KeyEventKind}; +use ratatui::{backend::Backend, Terminal}; +pub use shapes::HandleError; +use std::io; use std::time::Duration; +pub use universe::Universe; + +/// Default poll duration +pub const DEF_DUR: Duration = Duration::from_millis(400); + +mod area; +mod cell; +/// Keymaps to handle input events +mod kmaps; +/// Starting shapes +mod shapes; +/// ui +mod ui; +/// Conway's Game of Life universe +mod universe; + +#[cfg(test)] +mod tests; + +impl App { + pub fn run(&mut self, terminal: &mut Terminal) -> io::Result<()> { + let mut prev_poll_t = self.poll_t; + + loop { + terminal.draw(|f| ui::ui(f, self))?; + + // Wait up to `poll_t` for another event + if poll(self.poll_t)? { + if let Event::Key(key) = event::read()? { + if key.kind != KeyEventKind::Press { + continue; + } + match key.code { + kmaps::QUIT => break, + kmaps::SLOWER => self.slower(false), + kmaps::FASTER => self.faster(false), + kmaps::PLAY_PAUSE => self.play_pause(&mut prev_poll_t), + kmaps::RESTART => self.restart(), + kmaps::NEXT => self.next(), + kmaps::PREV => self.prev(), + kmaps::RESET => *self = Self::default(), + _ => {} + } + } else { + // resize and restart + self.restart(); + } + } else { + // Timeout expired, updating life state + self.tick(); + } + } + + Ok(()) + } +} pub struct App { pub universe: Universe, @@ -20,13 +81,12 @@ impl Default for App { } } impl App { - pub fn new(area: Area) -> Self { - let i = 0; + pub fn new(area: Area, universe: Universe, poll_t: Duration) -> Self { App { area, - universe: shapes::get(area, i).unwrap(), - i, - poll_t: DEF_DUR, + universe, + i: 0, + poll_t, paused: false, } } @@ -81,7 +141,12 @@ impl App { if let Ok(shape) = shapes::get(self.area, self.i) { self.universe = shape; } else { - eprintln!("couldn't switch to next shape"); + log::error!( + "couldn't switch to next shape: number of shapes: {}, idx: {}, universe: {:?}", + shapes::N, + self.i, + self.universe + ); } } pub fn prev(&mut self) { @@ -93,7 +158,12 @@ impl App { if let Ok(shape) = shapes::get(self.area, self.i) { self.universe = shape; } else { - eprintln!("couldn't switch to previous shape"); + log::error!( + "couldn't switch to previous shape: number of shapes: {}, idx: {}, universe: {:?}", + shapes::N, + self.i, + self.universe + ); } } } diff --git a/src/app/area.rs b/src/app/area.rs new file mode 100644 index 0000000..f9780ba --- /dev/null +++ b/src/app/area.rs @@ -0,0 +1,38 @@ +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)] +pub struct Area { + pub width: u16, + pub height: u16, +} +impl Area { + pub fn new(width: u16, height: u16) -> Self { + Area { width, height } + } + pub fn with_width(self, width: impl Into) -> Self { + Self::new(width.into(), self.height) + } + pub fn with_height(self, height: impl Into) -> Self { + Self::new(self.width, height.into()) + } + pub fn add_to_width(self, width: impl Into) -> Self { + self.with_width((self.width as i32 + width.into()) as u16) + } + pub fn add_to_height(self, height: impl Into) -> Self { + self.with_height((self.height as i32 + height.into()) as u16) + } + pub const fn len(&self) -> usize { + self.width as usize * self.height as usize + } + + #[must_use] + pub const fn is_empty(&self) -> bool { + self.len() == 0 + } +} +impl, U2: Into> From<(U1, U2)> for Area { + fn from(val: (U1, U2)) -> Self { + Self { + width: val.0.into(), + height: val.1.into(), + } + } +} diff --git a/src/app/cell.rs b/src/app/cell.rs new file mode 100644 index 0000000..53bfd0d --- /dev/null +++ b/src/app/cell.rs @@ -0,0 +1,24 @@ +/// information about one `Cell`: either `Dead` or `Alive` +#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] +pub enum Cell { + #[default] + Dead = 0, + Alive = 1, +} +impl From for Cell { + fn from(alive: bool) -> Self { + if alive { + Self::Alive + } else { + Self::Dead + } + } +} +impl Cell { + fn toggle(&mut self) { + *self = match *self { + Cell::Dead => Cell::Alive, + Cell::Alive => Cell::Dead, + } + } +} diff --git a/src/kmaps.rs b/src/app/kmaps.rs similarity index 100% rename from src/kmaps.rs rename to src/app/kmaps.rs diff --git a/src/shapes.rs b/src/app/shapes.rs similarity index 91% rename from src/shapes.rs rename to src/app/shapes.rs index c9608b7..c151e76 100644 --- a/src/shapes.rs +++ b/src/app/shapes.rs @@ -1,3 +1,7 @@ +use area::Area; +use cell::Cell; +use universe::Universe; + use super::*; /// Number of currently supported shapes @@ -37,7 +41,7 @@ pub fn get(area: Area, i: usize) -> Result { } #[test] fn get_test() { - let area = Area::new(40u8, 40u8); + let area = Area::new(40, 40); for i in 0..N { assert!(get(area, i.into()).is_ok()); } @@ -54,8 +58,7 @@ fn get_test() { // 4.....4 // 01234 pub fn frame(area: Area) -> Universe { - let cells = vec![Cell::Dead; area.len()]; - let mut univ = Universe { area, cells }; + let mut univ = empty(area); if area.height < 3 || area.width < 3 { return univ; } @@ -76,7 +79,7 @@ pub fn frame(area: Area) -> Universe { } #[test] fn frame_test00() { - let area = Area::new(3u8, 2u8); + let area = Area::new(3, 2); let univ = Universe::from_vec_str(&["___".to_owned(), "___".to_owned()]); let frame = frame(area); print!("{frame}"); @@ -84,7 +87,7 @@ fn frame_test00() { } #[test] fn frame_test0() { - let area = Area::new(3u8, 3u8); + let area = Area::new(3, 3); let univ = Universe::from_vec_str(&["___".to_owned(), "_#_".to_owned(), "___".to_owned()]); let frame = frame(area); print!("{frame}"); @@ -92,7 +95,7 @@ fn frame_test0() { } #[test] fn frame_test1() { - let area = Area::new(4u8, 4u8); + let area = Area::new(4, 4); let univ = Universe::from_vec_str(&[ "____".to_owned(), "_##_".to_owned(), @@ -105,7 +108,7 @@ fn frame_test1() { } #[test] fn frame_test2() { - let area = Area::new(5u8, 5u8); + let area = Area::new(5, 5); let univ = Universe::from_vec_str(&[ "_____".to_owned(), "_###_".to_owned(), @@ -119,7 +122,7 @@ fn frame_test2() { } #[test] fn frame_test3() { - let area = Area::new(6u8, 6u8); + let area = Area::new(6, 6); let univ = Universe::from_vec_str(&[ "______".to_owned(), "_####_".to_owned(), @@ -200,7 +203,7 @@ pub fn featherweigth_spaceship() -> Vec { } #[test] fn featherweight_spaceship_test() { - let area = Area::new(3u8, 3u8); + let area = Area::new(3, 3); let m = Universe::from_vec_str(&featherweigth_spaceship()); assert_eq!(m.area, area); dbg!(&m); @@ -226,7 +229,7 @@ pub fn rabbits() -> Vec { } #[test] fn rabbits_test() { - let area = Area::new(8u8, 4u8); + let area = Area::new(8, 4); let m = Universe::from_vec_str(&rabbits()); assert_eq!(m.area, area); dbg!(&m); @@ -262,7 +265,7 @@ pub fn bonk_tie() -> Vec { } #[test] fn bonk_tie_test() { - let area = Area::new(3u8, 5u8); + let area = Area::new(3, 5); let m = Universe::from_vec_str(&bonk_tie()); assert_eq!(m.area, area); dbg!(&m); @@ -289,7 +292,7 @@ pub fn acorn() -> Vec { } #[test] fn acorn_test() { - let area = Area::new(7u8, 3u8); + let area = Area::new(7, 3); let m = Universe::from_vec_str(&acorn()); assert_eq!(m.area, area); dbg!(&m); @@ -331,7 +334,7 @@ pub fn stripes(area: Area) -> Universe { } #[test] fn stripes_test() { - let area = Area::new(0u8, 0u8); + let area = Area::new(0, 0); let m = stripes(area); assert!(m.cells.is_empty()); assert_eq!(m.area, area); @@ -342,6 +345,11 @@ fn stripes_test() { assert!(m.get((1u8, 0u8)).is_none()); } +pub fn empty(area: Area) -> Universe { + let cells = vec![Cell::Dead; area.len()]; + Universe::new(area, cells) +} + /// `area.len()` pub fn full(area: Area) -> Universe { let cells = vec![Cell::Alive; area.len()]; @@ -349,7 +357,7 @@ pub fn full(area: Area) -> Universe { } #[test] fn full_test() { - let area = Area::new(4u8, 3u8); + let area = Area::new(4, 3); let m = full(area); assert_eq!(m.area, area); assert!(m.cells.iter().all(|j| *j == Cell::Alive)); @@ -363,22 +371,3 @@ fn full_test() { assert!(m.get((4u8, 3u8)).is_none()); assert!(m.get((3u8, 4u8)).is_none()); } - -pub fn two_engine_cordership() -> String { - todo!(); - // [ - // "_".repeat(19), - // "##".into(), - // "_".repeat(19), - // "\n".into(), - // "_".repeat(19), - // "####".into(), - // "_".repeat(17), - // "\n".into(), - // ] - // .concat() -} - -pub fn snark_loop() -> String { - todo!() -} diff --git a/src/tests.rs b/src/app/tests.rs similarity index 89% rename from src/tests.rs rename to src/app/tests.rs index db823cf..57c784c 100644 --- a/src/tests.rs +++ b/src/app/tests.rs @@ -5,17 +5,36 @@ fn gen_uni(area: Area, cells: &[bool]) -> Universe { Universe { area, cells } } +#[test] +fn parse0() { + let figur = "\ +..O +OO..O + +...O +..O"; + let univ = Universe::from_str(figur); + let cells = [ + /* 1st row */ false, false, true, false, false, /* 2nd row */ true, true, false, + false, true, /* 3rd row */ false, false, false, false, false, + /* 4th row */ false, false, false, true, false, /* 5th row */ false, false, true, + false, false, + ]; + let area = Area::new(5, 5); + assert_eq!(gen_uni(area, &cells), univ); +} + #[test] fn rabbit_hole() { let rabbit = shapes::featherweigth_spaceship(); let rabbit_uni = Universe::from_vec_str(&rabbit); let cells = [false, false, true, true, false, true, false, true, true]; - let uni = gen_uni((3u16, 3u16).into(), &cells); + let uni = gen_uni((3u8, 3u8).into(), &cells); assert_eq!(rabbit_uni, uni); } #[test] fn full() { - let area = Area::new(20u16, 20u16); + let area = Area::new(20, 20); let full = shapes::full(area); let cells = vec![true; area.len()]; let uni = gen_uni(area, &cells); @@ -23,7 +42,7 @@ fn full() { } #[test] fn halp() { - let area = Area::new(2u8, 2u8); + let area = Area::new(2, 2); let cells = vec![false, true, true, false]; let univ = gen_uni(area, &cells); @@ -50,7 +69,7 @@ fn halp() { #[test] fn bigass_tickler() { - let area = Area::new(8u8, 8u8); + let area = Area::new(8, 8); let mut univ = Universe::from_figur(area, &shapes::featherweigth_spaceship()).unwrap(); let exp_unis = [ diff --git a/src/ui.rs b/src/app/ui.rs similarity index 98% rename from src/ui.rs rename to src/app/ui.rs index 1dd8777..98c7d78 100644 --- a/src/ui.rs +++ b/src/app/ui.rs @@ -1,4 +1,4 @@ -use crate::{app::App, Area}; +use crate::{app::App, app::Area}; use ratatui::{ layout::{Constraint, Direction, Layout}, style::Stylize, diff --git a/src/app/universe.rs b/src/app/universe.rs new file mode 100644 index 0000000..dfedd08 --- /dev/null +++ b/src/app/universe.rs @@ -0,0 +1,260 @@ +use crate::{app::Area, app::Cell, app::HandleError}; +use ratatui::{style::Color, widgets::canvas::Shape}; + +use super::shapes; + +/// the `Universe` in which game plays. Represented as a `Vec` of `Cell`s. +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct Universe { + pub area: Area, + pub cells: Vec, +} +impl, U2: Into> std::ops::Index<(U1, U2)> for Universe { + type Output = Cell; + + fn index(&self, idx: (U1, U2)) -> &Self::Output { + let row = idx.0.into(); + let col = idx.1.into(); + // Convert (x;y) to index + let idx = self.get_idx(row, col); + + &self.cells[idx] + } +} +impl, U2: Into> std::ops::IndexMut<(U1, U2)> for Universe { + fn index_mut(&mut self, idx: (U1, U2)) -> &mut Self::Output { + let row = idx.0.into(); + let col = idx.1.into(); + // Convert (x;y) to index + let idx = self.get_idx(row, col); + + &mut self.cells[idx] + } +} + +impl Universe { + pub fn new(area: Area, cells: Vec) -> Self { + Self { area, cells } + } + fn get_idx(&self, row: impl Into, col: impl Into) -> usize { + let row = row.into(); + let col = col.into(); + assert!( + (0..self.area.height).contains(&(row as u16)), + "index out of range: len is {}, but index is {row}", + self.area.height, + ); + assert!( + (0..self.area.width).contains(&(col as u16)), + "index out of range: len is {}, but index is {col}", + self.area.width, + ); + // Convert (x;y) to index + let idx = (row * self.area.width as usize) + col; + assert!( + idx < self.cells.len(), + "index out of range: len is {}, but index is {idx}", + self.cells.len() + ); + idx + } + fn get_idx_res(&self, row: impl Into, col: impl Into) -> Option { + let row = row.into(); + let col = col.into(); + if !(0..self.area.height).contains(&(row as u16)) { + log::debug!("row is {row}, but len is {}", self.area.height); + return None; + } + if !(0..self.area.width).contains(&(col as u16)) { + log::debug!("col is {col}, but len is {}", self.area.width); + return None; + } + // Convert (x;y) to index + let idx = (row * self.area.width as usize) + col; + if idx >= self.cells.len() { + log::debug!("idx: {idx}, len: {}", self.cells.len()); + return None; + } + // log::debug!("idx: {idx}"); + Some(idx) + } + pub fn get(&self, idx: (impl Into, impl Into)) -> Option<&Cell> { + // log::debug!("get()"); + let idx = self.get_idx_res(idx.0, idx.1)?; + self.cells.get(idx) + } + // pub fn get_mut(&mut self, idx: (impl Into, impl Into)) -> Option<&mut Cell> { + // // log::debug!("get_mut()"); + // let idx = self.get_idx(idx.0, idx.1); + // self.cells.get_mut(idx) + // } + + pub fn live_neighbour_count(&self, row: u16, col: u16) -> u8 { + let mut sum = 0; + + for delta_row in [self.area.height - 1, 0, 1] { + for delta_col in [self.area.width - 1, 0, 1] { + if delta_row == 0 && delta_col == 0 { + continue; + } + + let neighbour_row = (row + delta_row) % self.area.height; + let neighbour_col = (col + delta_col) % self.area.width; + + sum += self[(neighbour_row, neighbour_col)] as u8; + // sum += *self.get((neighbour_row, neighbour_col)).unwrap() as u8; + } + } + sum + } + + /// Convert properly formatted Vec of Strings to Universe + pub fn from_vec_str(s: &[String]) -> Self { + let width = s.iter().map(|ln| ln.chars().count()).max().unwrap_or(0) as u16; + let height = s.len() as u16; + let area = Area::new(width, height); + let mut univ = shapes::empty(area); + + for (i, line) in s.iter().enumerate() { + if line.starts_with('!') { + continue; + } + for (j, ch) in line.chars().enumerate() { + if ch == 'O' || ch == '#' { + univ[(i, j)] = Cell::Alive; + } + } + } + + univ + } + + pub fn from_str(s: &str) -> Self { + let v = s + .trim() + .lines() + .map(str::trim) + .map(std::convert::Into::into) + .collect::>(); + Self::from_vec_str(&v) + } + + /// Create universe with width, height: inserting starting shape into the middle + /// + /// # Errors + /// + /// if shape can't fit universe + pub fn from_figur(area: Area, figur: &[String]) -> Result { + let figur = Universe::from_vec_str(figur); + let figur_alive = figur + .cells + .iter() + .filter(|cell| *cell == &Cell::Alive) + .count(); + + if area < figur.area { + return Err(HandleError::TooBig); + } + + let cells = vec![Cell::default(); area.len()]; + let mut univ = Universe { area, cells }; + + let (start_row, start_col) = ( + (area.height - figur.height()) / 2, + (area.width - figur.width()) / 2, + ); + + let mut j = 0; + for row in start_row as usize..start_row as usize + figur.height() as usize { + for i in 0..figur.width() as usize { + univ[(row, start_col as usize + i)] = figur.cells[j]; + // *univ.get_mut((row, start_col as usize + i)).unwrap() = figur.cells[j]; + j += 1; + } + } + + let univ_alive = univ + .cells + .iter() + .filter(|cell| *cell == &Cell::Alive) + .count(); + if figur_alive == univ_alive { + Ok(univ) + } else { + Err(HandleError::Other) + } + } + + /// update life: `Universe` + pub fn tick(&mut self) { + let mut next = self.clone(); + + for row in 0..self.height() { + for col in 0..self.width() { + let idx = (row, col); + // let cell = self.get(idx).unwrap(); + let cell = self[idx]; + let live_neighbours = self.live_neighbour_count(row, col); + + let next_cell = match (cell, live_neighbours) { + // Rule 1: Any live cell with fewer than two live neighbours + // dies, as if caused by underpopulation. + (Cell::Alive, n) if n < 2 => Cell::Dead, + // Rule 2: Any live cell with two or three live neighbours + // lives on to the next generation. + (Cell::Alive, 2 | 3) => Cell::Alive, + // Rule 3: Any live cell with more than three live + // neighbours dies, as if by overpopulation. + (Cell::Alive, n) if n > 3 => Cell::Dead, + // Rule 4: Any dead cell with exactly three live neighbours + // becomes a live cell, as if by reproduction. + (Cell::Dead, 3) => Cell::Alive, + // All other cells remain in the same state. + (otherwise, _) => otherwise, + }; + + next[idx] = next_cell; + // *next.get_mut(idx).unwrap() = next_cell; + } + } + + *self = next; + } + + pub fn width(&self) -> u16 { + self.area.width + } + + pub fn height(&self) -> u16 { + self.area.height + } +} + +impl Shape for Universe { + fn draw(&self, painter: &mut ratatui::widgets::canvas::Painter) { + for y in 0..self.height() { + for x in 0..self.width() { + match self.get((y, x)) { + Some(Cell::Alive) => painter.paint(x.into(), y.into(), Color::White), + Some(Cell::Dead) => continue, + None => unreachable!("got None"), + } + } + } + } +} + +impl std::fmt::Display for Universe { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "╭{}╮\r", "─".repeat(self.area.width as usize * 2))?; + for line in self.cells.as_slice().chunks(self.area.width as usize) { + write!(f, "│")?; + for &cell in line { + let symbol = if cell == Cell::Dead { '◻' } else { '◼' }; // ◻ + write!(f, "{symbol} ")?; + } + writeln!(f, "│\r")?; + } + writeln!(f, "╰{}╯\r", "─".repeat(self.area.width as usize * 2)) + } +} diff --git a/src/lib.rs b/src/lib.rs index e08879a..8b13789 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,341 +1 @@ -use crate::shapes::HandleError; -use ratatui::{style::Color, widgets::canvas::Shape}; -use std::time::Duration; -/// Default poll duration -pub const DEF_DUR: Duration = Duration::from_millis(400); - -/// App -pub mod app; -/// Keymaps to handle input events -pub mod kmaps; -/// Starting shapes -pub mod shapes; -/// ui -pub mod ui; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)] -pub struct Area { - pub width: u16, - pub height: u16, -} -impl Area { - pub fn new(width: impl Into, height: impl Into) -> Self { - Area { - width: width.into(), - height: height.into(), - } - } - pub fn with_width(self, width: impl Into) -> Self { - Self::new(width, self.height) - } - pub fn with_height(self, height: impl Into) -> Self { - Self::new(self.width, height) - } - pub fn add_to_width(self, width: impl Into) -> Self { - self.with_width((self.width as i32 + width.into()) as u16) - } - pub fn add_to_height(self, height: impl Into) -> Self { - self.with_height((self.height as i32 + height.into()) as u16) - } - pub const fn len(&self) -> usize { - self.width as usize * self.height as usize - } - - #[must_use] - pub const fn is_empty(&self) -> bool { - self.len() == 0 - } -} -impl, U2: Into> From<(U1, U2)> for Area { - fn from(val: (U1, U2)) -> Self { - Self { - width: val.0.into(), - height: val.1.into(), - } - } -} - -/// information about one `Cell`: either `Dead` or `Alive` -#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] -pub enum Cell { - #[default] - Dead = 0, - Alive = 1, -} -impl From for Cell { - fn from(alive: bool) -> Self { - if alive { - Self::Alive - } else { - Self::Dead - } - } -} -impl Cell { - fn toggle(&mut self) { - *self = match *self { - Cell::Dead => Cell::Alive, - Cell::Alive => Cell::Dead, - } - } -} - -/// the `Universe` in which game plays. Represented as a `Vec` of `Cell`s. -#[derive(Debug, Clone, PartialEq, Eq, Default)] -pub struct Universe { - area: Area, - cells: Vec, -} -impl, U2: Into> std::ops::Index<(U1, U2)> for Universe { - type Output = Cell; - - fn index(&self, idx: (U1, U2)) -> &Self::Output { - let row = idx.0.into(); - let col = idx.1.into(); - // Convert (x;y) to index - let idx = self.get_idx(row, col); - - &self.cells[idx] - } -} -impl, U2: Into> std::ops::IndexMut<(U1, U2)> for Universe { - fn index_mut(&mut self, idx: (U1, U2)) -> &mut Self::Output { - let row = idx.0.into(); - let col = idx.1.into(); - // Convert (x;y) to index - let idx = self.get_idx(row, col); - - &mut self.cells[idx] - } -} - -impl Universe { - fn get_idx(&self, row: impl Into, col: impl Into) -> usize { - let row = row.into(); - let col = col.into(); - assert!( - (0..self.area.height).contains(&(row as u16)), - "index out of range: len is {}, but index is {row}", - self.area.height, - ); - assert!( - (0..self.area.width).contains(&(col as u16)), - "index out of range: len is {}, but index is {col}", - self.area.width, - ); - // Convert (x;y) to index - let idx = (row * self.area.width as usize) + col; - assert!( - idx < self.cells.len(), - "index out of range: len is {}, but index is {idx}", - self.cells.len() - ); - idx - } - fn get_idx_res(&self, row: impl Into, col: impl Into) -> Option { - let row = row.into(); - let col = col.into(); - if !(0..self.area.height).contains(&(row as u16)) { - log::debug!("row is {row}, but len is {}", self.area.height); - return None; - } - if !(0..self.area.width).contains(&(col as u16)) { - log::debug!("col is {col}, but len is {}", self.area.width); - return None; - } - // Convert (x;y) to index - let idx = (row * self.area.width as usize) + col; - if idx >= self.cells.len() { - log::debug!("idx: {idx}, len: {}", self.cells.len()); - return None; - } - // log::debug!("idx: {idx}"); - Some(idx) - } - pub fn get(&self, idx: (impl Into, impl Into)) -> Option<&Cell> { - // log::debug!("get()"); - let idx = self.get_idx_res(idx.0, idx.1)?; - self.cells.get(idx) - } - // pub fn get_mut(&mut self, idx: (impl Into, impl Into)) -> Option<&mut Cell> { - // // log::debug!("get_mut()"); - // let idx = self.get_idx(idx.0, idx.1); - // self.cells.get_mut(idx) - // } - - fn live_neighbour_count(&self, row: u16, col: u16) -> u8 { - let mut sum = 0; - - for delta_row in [self.area.height - 1, 0, 1] { - for delta_col in [self.area.width - 1, 0, 1] { - if delta_row == 0 && delta_col == 0 { - continue; - } - - let neighbour_row = (row + delta_row) % self.area.height; - let neighbour_col = (col + delta_col) % self.area.width; - - sum += self[(neighbour_row, neighbour_col)] as u8; - // sum += *self.get((neighbour_row, neighbour_col)).unwrap() as u8; - } - } - sum - } - - /// Convert properly formatted Vec of Strings to Universe - fn from_vec_str(s: &[String]) -> Self { - let mut cells = Vec::new(); - - for line in s { - if line.starts_with('!') { - continue; - } - for ch in line.chars() { - if ch == '#' || ch == '1' || ch == 'O' { - cells.push(Cell::Alive); - } else if ch == '_' || ch == ' ' || ch == '0' || ch == '.' { - cells.push(Cell::Dead); - } else { - eprintln!("can't do nothing with this character: {ch}"); - } - } - } - - let area = Area { - width: s[0].len() as u16, - height: s.len() as u16, - }; - Universe { area, cells } - } - - fn from_str(s: &str) -> Self { - let v = s - .trim() - .lines() - .map(std::convert::Into::into) - .collect::>(); - Self::from_vec_str(&v) - } - - /// Create universe with width, height: inserting starting shape into the middle - /// - /// # Errors - /// - /// if shape can't fit universe - fn from_figur(area: Area, figur: &[String]) -> Result { - let figur = Universe::from_vec_str(figur); - let figur_alive = figur - .cells - .iter() - .filter(|cell| *cell == &Cell::Alive) - .count(); - - if area < figur.area { - return Err(HandleError::TooBig); - } - - let cells = vec![Cell::default(); area.len()]; - let mut univ = Universe { area, cells }; - - let (start_row, start_col) = ( - (area.height - figur.height()) / 2, - (area.width - figur.width()) / 2, - ); - - let mut j = 0; - for row in start_row as usize..start_row as usize + figur.height() as usize { - for i in 0..figur.width() as usize { - univ[(row, start_col as usize + i)] = figur.cells[j]; - // *univ.get_mut((row, start_col as usize + i)).unwrap() = figur.cells[j]; - j += 1; - } - } - - let univ_alive = univ - .cells - .iter() - .filter(|cell| *cell == &Cell::Alive) - .count(); - if figur_alive == univ_alive { - Ok(univ) - } else { - Err(HandleError::Other) - } - } - - /// update life: `Universe` - pub fn tick(&mut self) { - let mut next = self.clone(); - - for row in 0..self.height() { - for col in 0..self.width() { - let idx = (row, col); - // let cell = self.get(idx).unwrap(); - let cell = self[idx]; - let live_neighbours = self.live_neighbour_count(row, col); - - let next_cell = match (cell, live_neighbours) { - // Rule 1: Any live cell with fewer than two live neighbours - // dies, as if caused by underpopulation. - (Cell::Alive, n) if n < 2 => Cell::Dead, - // Rule 2: Any live cell with two or three live neighbours - // lives on to the next generation. - (Cell::Alive, 2 | 3) => Cell::Alive, - // Rule 3: Any live cell with more than three live - // neighbours dies, as if by overpopulation. - (Cell::Alive, n) if n > 3 => Cell::Dead, - // Rule 4: Any dead cell with exactly three live neighbours - // becomes a live cell, as if by reproduction. - (Cell::Dead, 3) => Cell::Alive, - // All other cells remain in the same state. - (otherwise, _) => otherwise, - }; - - next[idx] = next_cell; - // *next.get_mut(idx).unwrap() = next_cell; - } - } - - *self = next; - } - - pub fn width(&self) -> u16 { - self.area.width - } - - pub fn height(&self) -> u16 { - self.area.height - } -} - -impl Shape for Universe { - fn draw(&self, painter: &mut ratatui::widgets::canvas::Painter) { - for y in 0..self.height() { - for x in 0..self.width() { - match self.get((y, x)) { - Some(Cell::Alive) => painter.paint(x.into(), y.into(), Color::White), - Some(Cell::Dead) => continue, - None => unreachable!("got None"), - } - } - } - } -} - -impl std::fmt::Display for Universe { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - writeln!(f, "╭{}╮\r", "─".repeat(self.area.width as usize * 2))?; - for line in self.cells.as_slice().chunks(self.area.width as usize) { - write!(f, "│")?; - for &cell in line { - let symbol = if cell == Cell::Dead { '◻' } else { '◼' }; // ◻ - write!(f, "{symbol} ")?; - } - writeln!(f, "│\r")?; - } - writeln!(f, "╰{}╯\r", "─".repeat(self.area.width as usize * 2)) - } -} -#[cfg(test)] -mod tests; diff --git a/src/main.rs b/src/main.rs index 2ce1548..a6ab67a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,12 @@ -use cgol_tui::{app::App, *}; -use ratatui::crossterm::event::{self, poll, Event, KeyEventKind}; -use ratatui::{backend::Backend, Terminal}; -use std::io; +use app::App; + +pub mod app; fn main() -> Result<(), Box> { // set up logger fern::Dispatch::new() - // Add blanket level filter - + // Add blanket level filter + // TODO: cli -v^n, 0 < n < 5 .level(log::LevelFilter::Debug) // Output to stdout, files, and other Dispatch configurations .chain( @@ -18,13 +18,11 @@ fn main() -> Result<(), Box> { // Apply globally .apply()?; - // init terminal let mut terminal = ratatui::try_init()?; let mut app = App::default(); - let res = run_app(&mut terminal, &mut app); + let res = app.run(&mut terminal); - // reset terminal ratatui::try_restore()?; // if any error has occured while executing, print it in cooked mode @@ -32,39 +30,3 @@ fn main() -> Result<(), Box> { Ok(()) } - -fn run_app(terminal: &mut Terminal, app: &mut App) -> io::Result<()> { - let mut prev_poll_t = app.poll_t; - - loop { - terminal.draw(|f| ui::ui(f, app))?; - - // Wait up to `poll_t` for another event - if poll(app.poll_t)? { - if let Event::Key(key) = event::read()? { - if key.kind != KeyEventKind::Press { - continue; - } - match key.code { - kmaps::QUIT => break, - kmaps::SLOWER => app.slower(false), - kmaps::FASTER => app.faster(false), - kmaps::PLAY_PAUSE => app.play_pause(&mut prev_poll_t), - kmaps::RESTART => app.restart(), - kmaps::NEXT => app.next(), - kmaps::PREV => app.prev(), - kmaps::RESET => *app = App::default(), - _ => {} - } - } else { - // resize and restart - app.restart(); - } - } else { - // Timeout expired, updating life state - app.tick(); - } - } - - Ok(()) -}