]> git.huck.website - cellseq.git/commitdiff
added: tokio loops
authorHuck Boles <huck@huck.website>
Tue, 13 Jun 2023 14:02:03 +0000 (09:02 -0500)
committerHuck Boles <huck@huck.website>
Tue, 13 Jun 2023 14:02:03 +0000 (09:02 -0500)
src/action.rs [new file with mode: 0644]
src/cells.rs
src/graphics.rs
src/lib.rs
src/main.rs
src/timing.rs [new file with mode: 0644]

diff --git a/src/action.rs b/src/action.rs
new file mode 100644 (file)
index 0000000..5d82789
--- /dev/null
@@ -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<Action>) -> 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<Action>) -> 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);
+}
index f2ee6b03f76f3fe5361739e797d109800a0bd616..efa4784e7d012294300e1df07d495b7eae637217 100644 (file)
@@ -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<usize>, 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()?;
+    }
+}
index d224b4bc2df231334dae3c3eb2568041fd8d0dea..30203752cce50032a56743fdb151bddc53ca7d0f 100644 (file)
@@ -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(())
     }
 }
 
index 20d18d0f65a349364626241d3b53536efd3a35d1..e6d573016fd3e7207fc0b2b0baed49f38b0e9dc0 100644 (file)
@@ -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;
index a3a92f49730dc6ab812919b967d246c79d535de8..eb7d8e4d410da0eb70dd7196f8b91abbf96a63b9 100644 (file)
@@ -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::<Action>(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 (file)
index 0000000..908da74
--- /dev/null
@@ -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<usize>,
+    pub tick: usize,
+}
+
+impl Metronome {
+    pub fn new(clock: Sender<usize>, 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;
+    }
+}