Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

display pattern names #11

Merged
merged 9 commits into from
Oct 4, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "cgol-tui"
version = "0.5.3"
version = "0.6.1"
authors = ["Jeromos Kovacs <iitsnotme214@proton.me>"]
edition = "2021"
description = "Conway's Game of Life implementation with a TUI"
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -17,6 +17,19 @@ eg.:
- `curl https://conwaylife.com/patterns/fx153.cells | cgol-tui -` the `-` stands for `stdin`
- `cgol-tui my_own_pattern.cells fx153.cells`

### Script

there is a fish script provided under [scripts](./scripts/pattern.fish) for viewing patterns from [conwaylife.com](https://conwaylife.com/patterns)</br>
usage: `pattern.fish <PATTERN_NAME> [OPTIONS]`</br>
PATTERN_NAME: either a name of a pattern, or nothing to list all patterns</br>
OPTIONS: -d, --download download to /tmp

#### needed tools

- [fish](https://fishshell.com/): shell
- [curl](https://curl.se/): download
- [ripgrep](https://github.com/BurntSushi/ripgrep): only for listing all patterns

## Sample

![Sample][1]
@@ -29,7 +42,7 @@ eg.:
- [x] publishing to crates.io
- [x] changing to `Canvas` for rendering viewer block
- [x] the ability to parse `.cells` files, from [conwaylife.com](https://conwaylife.com/patterns)
- [ ] display the names of patterns
- [x] display the names of patterns

## Acknowledgements

24 changes: 24 additions & 0 deletions scripts/pattern.fish
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/usr/bin/env fish

set cmd $argv[1]
if test "$cmd" = all; or test "$cmd" = any; or test "$cmd" = ""
for line in "$(curl https://conwaylife.com/patterns/ | rg .cells)"
echo $line | string split '"' -f 2
end
return 0
end
set pattern "$(string replace '.cells' '' $cmd)"
echo "pattern: '$pattern'"
set url "https://conwaylife.com/patterns/$pattern.cells"
echo "url: '$url'"
if test "$argv[2]" = --download; or test "$argv[2]" = -d
set p "/tmp/$pattern.cells"
if test -e $p
echo "already saved to '$p'"
else
echo "saving to '$p'"
curl $url -o $p
end
else
curl $url | cgol-tui -
end
22 changes: 10 additions & 12 deletions src/app.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
pub use area::Area;
pub use cell::Cell;
use ratatui::crossterm::event::{self, Event, KeyEventKind};
use ratatui::crossterm::event::{self, Event, KeyCode, KeyEventKind};
use ratatui::{backend::Backend, Terminal};
pub use shapes::HandleError;
pub use std::str::FromStr;
@@ -12,8 +12,6 @@ const DEF_DUR: Duration = Duration::from_millis(400);

mod area;
mod cell;
/// Keymaps to handle input events
mod kmaps;
/// Starting shapes
pub mod shapes;
/// ui
@@ -95,7 +93,7 @@ impl App {
}
pub fn restart(&mut self) {
let univ = self.get();
self.universe = Universe::from_figur(self.area, univ).unwrap();
self.universe = Universe::from_figur(self.area, &univ).unwrap();
}

pub fn tick(&mut self) {
@@ -150,14 +148,14 @@ impl App {
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(),
KeyCode::Char('q') | KeyCode::Esc => break,
KeyCode::Char('j') | KeyCode::Down => self.slower(false),
KeyCode::Char('k') | KeyCode::Up => self.faster(false),
KeyCode::Char(' ') | KeyCode::Enter => self.play_pause(&mut prev_poll_t),
KeyCode::Char('r') => self.restart(),
KeyCode::Char('n') | KeyCode::Char('l') | KeyCode::Right => self.next(),
KeyCode::Char('p') | KeyCode::Char('h') | KeyCode::Left => self.prev(),
KeyCode::Char('R') | KeyCode::Backspace => *self = Self::default(),
_ => {}
}
} else {
20 changes: 0 additions & 20 deletions src/app/kmaps.rs

This file was deleted.

19 changes: 12 additions & 7 deletions src/app/shapes.rs
Original file line number Diff line number Diff line change
@@ -14,7 +14,7 @@ pub enum HandleError {

pub fn all() -> Vec<Universe> {
vec![
Universe::from_str(FEATHERWEIGTH_SPACESHIP).unwrap(),
Universe::from_str(GLIDER).unwrap(),
Universe::from_str(GOSPER_GLIDER_GUN).unwrap(),
Universe::from_str(COPPERHEAD).unwrap(),
Universe::from_str(RABBITS).unwrap(),
@@ -35,7 +35,7 @@ pub fn get_special(i: usize, area: Area) -> Universe {

pub fn rand(area: Area) -> Universe {
let cells = (0..area.len()).map(|_i| fastrand::bool().into()).collect();
Universe { area, cells }
Universe::new(area, cells, "random")
}

pub fn stripes(area: Area) -> Universe {
@@ -48,17 +48,17 @@ pub fn stripes(area: Area) -> Universe {
}
})
.collect();
Universe { area, cells }
Universe::new(area, cells, "stripes")
}

pub fn empty(area: Area) -> Universe {
let cells = vec![Cell::Dead; area.len()];
Universe::new(area, cells)
Universe::new(area, cells, "empty")
}

pub fn full(area: Area) -> Universe {
let cells = vec![Cell::Alive; area.len()];
Universe { area, cells }
Universe::new(area, cells, "full")
}

/// height: 5
@@ -71,7 +71,7 @@ pub fn full(area: Area) -> Universe {
/// 4.....4
/// 01234
pub fn frame(area: Area) -> Universe {
let mut univ = empty(area);
let mut univ = empty(area).with_name("frame");
if area.height < 3 || area.width < 3 {
return univ;
}
@@ -92,6 +92,7 @@ pub fn frame(area: Area) -> Universe {
}

pub const COPPERHEAD: &str = "\
!Name: Copperhead
.....O.OO...
....O......O
...OO...O..O
@@ -118,20 +119,23 @@ OO........O...O.OO....O.O
";

/// 3x3
pub const FEATHERWEIGTH_SPACESHIP: &str = "\
pub const GLIDER: &str = "\
!Name: Glider
..O
O.O
.OO";

/// 8x4
pub const RABBITS: &str = "\
!Name: Rabbits
O.....O.
..O...O.
..O..O.O
.O.O....";

/// 3×5
pub const BONK_TIE: &str = "\
!Name: Bonk tie
OO
OO
..O
@@ -140,6 +144,7 @@ OO

/// 7×3
pub const ACORN: &str = "\
!Name: Acorn
.O
...O
OO..OOO";
32 changes: 20 additions & 12 deletions src/app/shapes/tests.rs
Original file line number Diff line number Diff line change
@@ -3,18 +3,20 @@ use super::*;
#[test]
fn frame_test00() {
let area = Area::new(3, 2);
let univ = Universe::from_str("...\n...");
let univ = Universe::from_str("...\n...").unwrap().with_name("frame");
let frame = frame(area);
print!("{frame}");
assert_eq!(univ, Ok(frame));
assert_eq!(univ, frame);
}
#[test]
fn frame_test0() {
let area = Area::new(3, 3);
let univ = Universe::from_str("...\n.O.\n...");
let univ = Universe::from_str("...\n.O.\n...")
.unwrap()
.with_name("frame");
let frame = frame(area);
print!("{frame}");
assert_eq!(univ, Ok(frame));
assert_eq!(univ, frame);
}
#[test]
fn frame_test1() {
@@ -25,10 +27,12 @@ fn frame_test1() {
.OO.
.OO.
....",
);
)
.unwrap()
.with_name("frame");
let frame = frame(area);
print!("{frame}");
assert_eq!(univ, Ok(frame));
assert_eq!(univ, frame);
}
#[test]
fn frame_test2() {
@@ -40,10 +44,12 @@ fn frame_test2() {
.O.O.
.OOO.
.....",
);
)
.unwrap()
.with_name("frame");
let frame = frame(area);
print!("{frame}");
assert_eq!(univ, Ok(frame));
assert_eq!(univ, frame);
}
#[test]
fn frame_test3() {
@@ -56,16 +62,18 @@ fn frame_test3() {
.O..O.
.OOOO.
......",
);
)
.unwrap()
.with_name("frame");
let frame = frame(area);
print!("{frame}");
assert_eq!(univ, Ok(frame));
assert_eq!(univ, frame);
}

#[test]
fn featherweight_spaceship_test() {
fn glider_test() {
let area = Area::new(3, 3);
let m = Universe::from_str(FEATHERWEIGTH_SPACESHIP).unwrap();
let m = Universe::from_str(GLIDER).unwrap();
assert_eq!(m.area, area);
dbg!(&m);
let alive = [(0u8, 2u8), (1u8, 0u8), (1u8, 2u8), (2u8, 1u8), (2u8, 2u8)];
15 changes: 6 additions & 9 deletions src/app/tests.rs
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@ use super::*;

fn gen_uni(area: Area, cells: &[bool]) -> Universe {
let cells = cells.iter().map(|c| (*c).into()).collect::<Vec<Cell>>();
Universe { area, cells }
Universe::new(area, cells, "test")
}

#[test]
@@ -13,7 +13,7 @@ OO..O

...O
..O";
let univ = Universe::from_str(figur).unwrap();
let univ = Universe::from_str(figur).unwrap().with_name("test");
let cells = [
/* 1st row */ false, false, true, false, false, /* 2nd row */ true, true, false,
false, true, /* 3rd row */ false, false, false, false, false,
@@ -30,7 +30,7 @@ fn full() {
let full = shapes::full(area);
let cells = vec![true; area.len()];
let uni = gen_uni(area, &cells);
assert_eq!(full, uni);
assert_eq!(full, uni.with_name("full"));
}
#[test]
fn halp() {
@@ -62,11 +62,8 @@ fn halp() {
#[test]
fn bigass_tickler() {
let area = Area::new(8, 8);
let mut univ = Universe::from_figur(
area,
Universe::from_str(shapes::FEATHERWEIGTH_SPACESHIP).unwrap(),
)
.unwrap();
let mut univ =
Universe::from_figur(area, &Universe::from_str(shapes::GLIDER).unwrap()).unwrap();

let exp_unis = [
"\
@@ -369,7 +366,7 @@ O.O.....
];

for exp_uni in exp_unis.map(Universe::from_str) {
let exp_uni = exp_uni.unwrap();
let exp_uni = exp_uni.unwrap().with_name("Glider");
println!("exp univ:\n{exp_uni}");
println!("univ:\n{univ}");
assert_eq!(univ, exp_uni);
5 changes: 2 additions & 3 deletions src/app/ui.rs
Original file line number Diff line number Diff line change
@@ -28,7 +28,7 @@ pub fn ui(f: &mut Frame, app: &mut App) {
let cgol = Block::default()
.borders(Borders::ALL)
.border_type(BorderType::Rounded)
.title("Conway's Game of Life");
.title(format!("Conway's Game of Life - {} ", app.universe.name));
// 2 blocks less: border
let new_area = Area::new(
(chunks[0].width - 2) * BRAILLE.width,
@@ -52,8 +52,7 @@ pub fn ui(f: &mut Frame, app: &mut App) {
.constraints([Constraint::Percentage(100)])
.split(chunks[1]);

let current_keys_hint =
"[q]uit, [r]estart, [R]eset, [n]ext, [p]rev, play[ ]pause, speed: 'k' ↑, 'j' ↓".yellow();
let current_keys_hint = "[q]uit, [r]estart, pause: [ ], nav: vim/arrows".yellow();

let poll_t = {
if let std::time::Duration::MAX = app.poll_t {
51 changes: 40 additions & 11 deletions src/app/universe.rs
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@ use super::shapes;
pub struct Universe {
pub area: Area,
pub cells: Vec<Cell>,
pub name: String,
}
impl<U1: Into<usize>, U2: Into<usize>> std::ops::Index<(U1, U2)> for Universe {
type Output = Cell;
@@ -33,9 +34,23 @@ impl<U1: Into<usize>, U2: Into<usize>> std::ops::IndexMut<(U1, U2)> for Universe
}

impl Universe {
pub fn new(area: Area, cells: Vec<Cell>) -> Self {
Self { area, cells }
pub fn new(area: Area, cells: Vec<Cell>, name: impl AsRef<str>) -> Self {
Self {
area,
cells,
name: name.as_ref().into(),
}
}
pub fn with_name(self, name: impl AsRef<str>) -> Self {
Self {
name: name.as_ref().into(),
..self
}
}
pub fn name(&self) -> String {
self.name.clone()
}

fn get_idx(&self, row: impl Into<usize>, col: impl Into<usize>) -> usize {
let row = row.into();
let col = col.into();
@@ -79,15 +94,29 @@ impl Universe {

/// Convert properly formatted Vec of Strings to Universe
fn from_vec_str(s: &[String]) -> Result<Self, String> {
let width = s.iter().map(|ln| ln.chars().count()).max().unwrap_or(0) as u16;
let height = s.len() as u16;
let s = s.iter();
let metadata = s.clone().filter(|l| l.starts_with('!')).collect::<Vec<_>>();
let pattern = s.filter(|l| !l.starts_with('!')).collect::<Vec<_>>();

let width = pattern
.iter()
.map(|ln| ln.chars().count())
.max()
.unwrap_or(0) as u16;
let height = pattern.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; // ignore comment
}
if let Some(name) = metadata.first() {
let name = name
.replace(".cells", "")
.replace('!', "")
.replace("Name:", "");
let name = name.trim();
univ.name = name.to_string();
};

for (i, line) in pattern.iter().enumerate() {
for (j, ch) in line.chars().enumerate() {
univ[(i, j)] = ch.try_into()?;
}
@@ -101,21 +130,21 @@ impl Universe {
/// # Errors
///
/// if shape can't fit universe
pub fn from_figur(area: Area, figur: Universe) -> Result<Universe, HandleError> {
pub fn from_figur(area: Area, figur: &Universe) -> Result<Universe, HandleError> {
let count_alive = |univ: &Universe| -> usize {
univ.cells
.iter()
.filter(|cell| *cell == &Cell::Alive)
.count()
};

let figur_alive = count_alive(&figur);
let figur_alive = count_alive(figur);

if area < figur.area {
return Err(HandleError::TooBig);
}

let mut univ = shapes::empty(area);
let mut univ = shapes::empty(area).with_name(figur.name());

let (start_row, start_col) = (
(area.height - figur.height()) / 2,
4 changes: 2 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -30,8 +30,8 @@ where <pattern> is either a .cells file, or - for stdin"
let universes = args
.iter()
.flat_map(std::fs::read_to_string)
.map(|s| Universe::from_str(&s))
.collect::<Result<Vec<_>, _>>()?;
.flat_map(|s| Universe::from_str(&s))
.collect::<Vec<_>>();

let mut app = App::default().with_universes([universes, piped_universe].concat());