Skip to content

Commit

Permalink
Merge pull request #9 from JeromeSchmied/dev
Browse files Browse the repository at this point in the history
.cells feature
  • Loading branch information
JeromeSchmied authored Sep 29, 2024
2 parents 385f855 + 66020ce commit 823dc7e
Show file tree
Hide file tree
Showing 11 changed files with 566 additions and 642 deletions.
38 changes: 18 additions & 20 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 2 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "cgol-tui"
version = "0.4.0"
authors = ["Jeromos Kovacs <iITsnot.me214@proton.me>"]
version = "0.5.2"
authors = ["Jeromos Kovacs <iitsnotme214@proton.me>"]
edition = "2021"
description = "Conway's Game of Life implementation with a TUI"
license = "MIT OR Apache-2.0"
Expand All @@ -12,6 +12,4 @@ categories = ["games"]

[dependencies]
fastrand = "2.1.1"
fern = "0.6.2"
log = "0.4.22"
ratatui = "0.28.1"
22 changes: 16 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
# Conway's Game of Life tui in Rust
# Conway's Game of Life TUI in Rust

## Usage

either
installation methods

- `cargo install cgol-tui`
- `cargo install --locked --git "https://github.com/JeromeSchmied/cgol-tui-rs"`
- clone the repo and run `cargo r`
- clone the repo and run `cargo install --locked --path .`

after

`[curl "https://conwaylife.com/patterns/<pattern>.cells" | ] cgol-tui [[-],<pattern>.cells,...]`
eg.:

- `cgol-tui`
- `curl https://conwaylife.com/patterns/fx153.cells | cgol-tui -` the `-` stands for `stdin`
- `cgol-tui my_own_pattern.cells fx153.cells`

## Sample

Expand All @@ -19,14 +28,15 @@ either
- [x] error handling
- [x] publishing to crates.io
- [x] changing to `Canvas` for rendering viewer block
- [ ] the ability to parse `.cells` files, from [conwaylife.com](https://conwaylife.com/patterns)
- [x] the ability to parse `.cells` files, from [conwaylife.com](https://conwaylife.com/patterns)
- [ ] display the names of patterns

## Acknowledgements

- 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
- [ratatui](https://ratatui.rs): ui
- [crossterm](https://github.com/crossterm-rs/crossterm): ratatui backend

## License

Expand Down
148 changes: 77 additions & 71 deletions src/app.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
pub use area::Area;
pub use cell::Cell;
use ratatui::crossterm::event::{self, poll, Event, KeyEventKind};
use ratatui::crossterm::event::{self, Event, KeyEventKind};
use ratatui::{backend::Backend, Terminal};
pub use shapes::HandleError;
use std::io;
use std::time::Duration;
pub use std::str::FromStr;
use std::{io, time::Duration};
pub use universe::Universe;

/// Default poll duration
pub const DEF_DUR: Duration = Duration::from_millis(400);
const DEF_DUR: Duration = Duration::from_millis(400);

mod area;
mod cell;
/// Keymaps to handle input events
mod kmaps;
/// Starting shapes
mod shapes;
pub mod shapes;
/// ui
mod ui;
/// Conway's Game of Life universe
Expand All @@ -24,47 +24,10 @@ mod universe;
#[cfg(test)]
mod tests;

impl App {
pub fn run<B: Backend>(&mut self, terminal: &mut Terminal<B>) -> 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,
pub i: usize,
pub available_universes: Vec<Universe>,
universe: Universe,
i: usize,
pub poll_t: Duration,
pub paused: bool,
pub area: Area,
Expand All @@ -77,19 +40,46 @@ impl Default for App {
i: 0,
poll_t: DEF_DUR,
paused: false,
available_universes: shapes::all(),
}
}
}
impl App {
pub fn new(area: Area, universe: Universe, poll_t: Duration) -> Self {
pub fn with_universes(self, universes: Vec<Universe>) -> Self {
Self {
available_universes: [universes, shapes::all()].concat(),
..self
}
}
pub fn new(area: Area, available_universes: Vec<Universe>, poll_t: Duration) -> Self {
App {
area,
universe,
universe: available_universes[0].clone(),
i: 0,
poll_t,
paused: false,
available_universes,
}
}
pub fn len(&self) -> usize {
self.available_universes.len() + shapes::N
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn get(&self) -> Universe {
let true_len = self.available_universes.len();
if self.i < true_len {
self.available_universes
.get(self.i)
.expect("display area is too small to fit current shape")
.clone()
} else {
shapes::get_special(self.i - true_len, self.area)
}
}

// pub fn render_universe(&self) {
// println!("{}", self.universe);
// }
Expand All @@ -104,9 +94,8 @@ impl App {
self.paused = !self.paused;
}
pub fn restart(&mut self) {
self.universe = shapes::get(self.area, self.i)
.inspect_err(|e| log::error!("{e:?}"))
.expect("display area is too small to fit current shape");
let univ = self.get();
self.universe = Universe::from_figur(self.area, univ).unwrap();
}

pub fn tick(&mut self) {
Expand All @@ -133,37 +122,54 @@ impl App {
}

pub fn next(&mut self) {
if self.i + 1 == shapes::N as usize {
if self.i + 1 == self.len() {
self.i = 0;
} else {
self.i += 1;
}
if let Ok(shape) = shapes::get(self.area, self.i) {
self.universe = shape;
} else {
log::error!(
"couldn't switch to next shape: number of shapes: {}, idx: {}, universe: {:?}",
shapes::N,
self.i,
self.universe
);
}
self.restart();
}
pub fn prev(&mut self) {
if self.i > 0 {
self.i -= 1;
} else {
self.i = shapes::N as usize - 1;
self.i = self.len() - 1;
}
if let Ok(shape) = shapes::get(self.area, self.i) {
self.universe = shape;
} else {
log::error!(
"couldn't switch to previous shape: number of shapes: {}, idx: {}, universe: {:?}",
shapes::N,
self.i,
self.universe
);
self.restart();
}
pub fn run<B: Backend>(&mut self, terminal: &mut Terminal<B>) -> 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 event::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(())
}
}
15 changes: 10 additions & 5 deletions src/app/cell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,16 @@ impl From<bool> for Cell {
}
}
}
impl Cell {
fn toggle(&mut self) {
*self = match *self {
Cell::Dead => Cell::Alive,
Cell::Alive => Cell::Dead,
impl TryFrom<char> for Cell {
type Error = String;

fn try_from(ch: char) -> Result<Self, Self::Error> {
match ch {
'O' => Ok(Cell::Alive),
'.' => Ok(Cell::Dead),
_ => Err(format!(
"parse error: {ch:?} is an invalid character, should be either '.' or 'O'"
)),
}
}
}
Loading

0 comments on commit 823dc7e

Please sign in to comment.