From ef0d32d27dd7756bb63e5456d41c5174482cc0d5 Mon Sep 17 00:00:00 2001 From: Huck Boles Date: Tue, 13 Jun 2023 09:02:03 -0500 Subject: [PATCH] added: tokio loops --- src/action.rs | 83 +++++++++++++++++++++++++++++++++++++++++++++++++ src/cells.rs | 74 ++++++++++++++++++++++++++++++++++--------- src/graphics.rs | 74 ++++++++++++++++++++++++++++++++++++++++--- src/lib.rs | 4 +++ src/main.rs | 38 +++++++++++++++++++++- src/timing.rs | 37 ++++++++++++++++++++++ 6 files changed, 289 insertions(+), 21 deletions(-) create mode 100644 src/action.rs create mode 100644 src/timing.rs diff --git a/src/action.rs b/src/action.rs new file mode 100644 index 0000000..5d82789 --- /dev/null +++ b/src/action.rs @@ -0,0 +1,83 @@ +use std::{io::stdout, time::Duration}; + +use super::*; + +use crossterm::{ + cursor::Show, + event::{poll, read, Event, KeyCode}, + execute, + terminal::{disable_raw_mode, Clear, ClearType::All}, +}; +use tokio::sync::mpsc::{self, Receiver, Sender}; + +#[derive(Debug, Clone, Copy)] +pub enum Action { + Exit, + Help, + MoveCursor(Direction), + SwapSide, + Remove, + Select, +} + +#[derive(Debug, Clone, Copy)] +pub enum Direction { + Up, + Down, + Left, + Right, +} + +pub async fn run_keys(snd: Sender) -> Result<()> { + loop { + if poll(Duration::from_millis(100)).unwrap() { + match read().unwrap() { + Event::Resize(_x, _y) => todo!(), + Event::Key(k) => match k.code { + KeyCode::Backspace => todo!(), + KeyCode::Enter => todo!(), + KeyCode::Tab => snd.send(Action::SwapSide).await?, + KeyCode::Esc => snd.send(Action::Exit).await?, + KeyCode::Up => snd.send(Action::MoveCursor(Direction::Up)).await?, + KeyCode::Down => snd.send(Action::MoveCursor(Direction::Down)).await?, + KeyCode::Left => snd.send(Action::MoveCursor(Direction::Left)).await?, + KeyCode::Right => snd.send(Action::MoveCursor(Direction::Right)).await?, + KeyCode::Char(c) => match c { + ' ' => snd.send(Action::Select).await?, + 'q' => snd.send(Action::Exit).await?, + '?' => snd.send(Action::Help).await?, + 'h' => snd.send(Action::MoveCursor(Direction::Left)).await?, + 'j' => snd.send(Action::MoveCursor(Direction::Down)).await?, + 'k' => snd.send(Action::MoveCursor(Direction::Up)).await?, + 'l' => snd.send(Action::MoveCursor(Direction::Right)).await?, + _ => continue, + }, + _ => continue, + }, + _ => continue, + } + } + } +} + +pub async fn run_action(rcv: &mut Receiver) -> Result<()> { + while let Some(action) = rcv.recv().await { + match action { + Action::Exit => exit(), + Action::Help => todo!(), + Action::MoveCursor(_) => todo!(), + Action::SwapSide => todo!(), + Action::Remove => todo!(), + Action::Select => todo!(), + } + } + + Ok(()) +} + +fn exit() { + disable_raw_mode().unwrap(); + execute!(stdout(), Clear(All), Show).unwrap(); + + std::process::exit(0); +} diff --git a/src/cells.rs b/src/cells.rs index f2ee6b0..efa4784 100644 --- a/src/cells.rs +++ b/src/cells.rs @@ -9,7 +9,12 @@ use crossterm::{ }; use ndarray::Array2; use rand::{thread_rng, Rng}; -use std::{cell::Cell, io::stdout, ops::Deref}; +use std::{ + cell::Cell, + io::{stdout, Write}, + ops::Deref, +}; +use tokio::sync::watch::Receiver; use super::*; @@ -27,7 +32,10 @@ pub struct World { impl World { pub fn new(area: Area) -> Self { - let map = Array2::from_elem((area.width(), area.height()), Cell::new(State::Dead)); + let width = area.width().try_into().unwrap_or(0); + let height = area.height().try_into().unwrap_or(0); + + let map = Array2::from_elem((height + 1, width + 1), Cell::new(State::Dead)); Self { map, area } } @@ -43,18 +51,37 @@ impl World { } fn wrap_walls(&mut self) { - macro_rules! wrap { - ($portal:expr,$wall:expr) => { - for (portal, wall) in $portal.iter().zip($wall) { - portal.set(wall.get()); - } - }; + for (hidden, shown) in self + .column(0) + .iter() + .zip(self.column((self.area.width() - 2).try_into().unwrap_or(0))) + { + hidden.set(shown.get()); + } + + for (hidden, shown) in self + .column((self.area.width() - 1).try_into().unwrap_or(0)) + .iter() + .zip(self.column(1)) + { + hidden.set(shown.get()); + } + + for (hidden, shown) in self + .row(0) + .iter() + .zip(self.row((self.area.height() - 2).try_into().unwrap_or(0))) + { + hidden.set(shown.get()); } - wrap!(self.column(0), self.column(self.area.width() - 2)); - wrap!(self.column(self.area.width() - 1), self.column(1)); - wrap!(self.row(0), self.row(self.area.height() - 2)); - wrap!(self.row(self.area.height() - 1), self.row(1)); + for (hidden, shown) in self + .row((self.area.height() - 1).try_into().unwrap_or(0)) + .iter() + .zip(self.row(1)) + { + hidden.set(shown.get()); + } } pub fn update(&mut self) { @@ -84,14 +111,15 @@ impl World { } } - fn draw_point(&self, point: Point, offset: Point) -> Result<()> { + pub fn draw_point(&self, cell: Point) -> Result<()> { let mut style = ContentStyle { foreground_color: None, background_color: Some(Black), underline_color: None, attributes: Reset.into(), }; - let char = if let Some(cell) = self.get::<(usize, usize)>(point.into()) { + let (col, row) = cell.into(); + let char = if let Some(cell) = self.get((row, col)) { if cell.get() == State::Alive { style.foreground_color = Some(Green); style.attributes = Bold.into(); @@ -106,7 +134,7 @@ impl World { queue!( stdout(), - MoveTo(offset.x, offset.y), + MoveTo(cell.x + self.area.origin.x, cell.y + self.area.origin.y), SetStyle(style), Print(char) )?; @@ -120,3 +148,19 @@ impl Deref for World { &self.map } } + +pub async fn run_world(clock: &mut Receiver, world: &mut World) -> Result<()> { + loop { + world.update(); + + for x in 1..(world.area.width() - 1).try_into()? { + for y in 1..(world.area.height()).try_into()? { + world.draw_point(Point::new(x, y))?; + } + } + + clock.changed().await?; + + stdout().flush()?; + } +} diff --git a/src/graphics.rs b/src/graphics.rs index d224b4b..3020375 100644 --- a/src/graphics.rs +++ b/src/graphics.rs @@ -1,6 +1,18 @@ use super::*; -use std::ops::{Add, Sub}; +use crossterm::{ + cursor::MoveTo, + queue, + style::{ + Attribute::Bold, + Color::{Black, White}, + ContentStyle, Print, SetStyle, + }, +}; +use std::{ + io::{stdout, Write}, + ops::{Add, Sub}, +}; #[derive(Clone, Debug, Copy, PartialEq, Eq)] pub struct Point { @@ -70,12 +82,64 @@ impl Area { && point.y <= self.max.y } - pub fn height(&self) -> usize { - (self.max.y - self.origin.y).try_into().unwrap_or(0) + pub fn height(&self) -> u16 { + self.max.y - self.origin.y } - pub fn width(&self) -> usize { - (self.max.x - self.origin.x).try_into().unwrap_or(0) + pub fn width(&self) -> u16 { + self.max.x - self.origin.x + } + + pub fn draw_outline(&self) -> Result<()> { + let style = ContentStyle { + foreground_color: Some(White), + background_color: Some(Black), + underline_color: None, + attributes: Bold.into(), + }; + + for x in 0..self.width() { + queue!( + stdout(), + MoveTo(x + self.origin.x, self.origin.y), + SetStyle(style), + Print('━'), + MoveTo(x + self.origin.x, self.max.y), + SetStyle(style), + Print('━') + )?; + } + + for y in 0..self.height() { + queue!( + stdout(), + MoveTo(self.origin.x, y + self.origin.y), + SetStyle(style), + Print('┃'), + MoveTo(self.max.x, y + self.origin.y), + SetStyle(style), + Print('┃') + )?; + } + + queue!( + stdout(), + MoveTo(self.origin.x, self.origin.y), + SetStyle(style), + Print('┏'), + MoveTo(self.origin.x, self.max.y), + SetStyle(style), + Print('┗'), + MoveTo(self.max.x, self.origin.y), + SetStyle(style), + Print('┓'), + MoveTo(self.max.x, self.max.y), + SetStyle(style), + Print('┛'), + )?; + + stdout().flush()?; + Ok(()) } } diff --git a/src/lib.rs b/src/lib.rs index 20d18d0..e6d5730 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,12 @@ +mod action; mod cells; mod graphics; +mod timing; +pub use action::*; pub use cells::*; pub use graphics::*; +pub use timing::*; #[cfg(test)] mod tests; diff --git a/src/main.rs b/src/main.rs index a3a92f4..eb7d8e4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,42 @@ +use crossterm::{ + cursor::{Hide, Show}, + execute, + terminal::{enable_raw_mode, Clear, ClearType::All}, +}; use eyre::Result; +use std::io::stdout; +use tokio::{ + sync::{mpsc, watch}, + try_join, +}; + +use cellseq::*; #[tokio::main] async fn main() -> Result<()> { - todo!() + enable_raw_mode()?; + execute!(stdout(), Clear(All), Hide)?; + let (clock_snd, clock_rcv) = watch::channel(0); + let (action_snd, mut action_rcv) = mpsc::channel::(16); + + let mut metro = Metronome::new(clock_snd, 120); + let clock = run_clock(&mut metro); + + let cell_area = Area::new(Point::from((1, 1)), Point::from((60, 25))); + cell_area.draw_outline()?; + let mut map = World::new(cell_area); + map.randomize(0.5); + let mut world_clock = clock_rcv.clone(); + let world = run_world(&mut world_clock, &mut map); + + let input = run_keys(action_snd); + let action = run_action(&mut action_rcv); + + if let Err(e) = try_join!(clock, world, input, action) { + execute!(stdout(), Clear(All), Show)?; + eprintln!("{e}"); + return Err(e); + } else { + Ok(()) + } } diff --git a/src/timing.rs b/src/timing.rs new file mode 100644 index 0000000..908da74 --- /dev/null +++ b/src/timing.rs @@ -0,0 +1,37 @@ +use super::*; + +use std::time::Duration; +use tokio::{ + sync::watch::Sender, + time::{interval, Interval}, +}; + +pub struct Metronome { + pub interval: Interval, + pub tick_len: Duration, + pub bpm: usize, + pub clock: Sender, + pub tick: usize, +} + +impl Metronome { + pub fn new(clock: Sender, bpm: usize) -> Self { + let tick_len = Duration::from_millis((60000 / bpm).try_into().unwrap_or(0)); + let interval = interval(tick_len); + Self { + interval, + tick_len, + bpm, + clock, + tick: 0, + } + } +} + +pub async fn run_clock(metro: &mut Metronome) -> Result<()> { + loop { + metro.interval.tick().await; + metro.clock.send(metro.tick)?; + metro.tick += 1; + } +} -- 2.44.2