--- /dev/null
+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);
+}
};
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::*;
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 }
}
}
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) {
}
}
- 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();
queue!(
stdout(),
- MoveTo(offset.x, offset.y),
+ MoveTo(cell.x + self.area.origin.x, cell.y + self.area.origin.y),
SetStyle(style),
Print(char)
)?;
&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()?;
+ }
+}
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 {
&& 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(())
}
}
+mod action;
mod cells;
mod graphics;
+mod timing;
+pub use action::*;
pub use cells::*;
pub use graphics::*;
+pub use timing::*;
#[cfg(test)]
mod tests;
+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(())
+ }
}
--- /dev/null
+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;
+ }
+}