From 399883d8badf291be2d344a83e89c56c435e7940 Mon Sep 17 00:00:00 2001 From: Adrien Burgun Date: Sat, 13 Aug 2022 18:24:44 +0200 Subject: [PATCH] :sparkles: Refactor Tile::draw_simple, blink support in stackline-cli --- stackline-cli/src/main.rs | 21 +++++- stackline/src/context.rs | 1 - stackline/src/lib.rs | 1 + stackline/src/signal.rs | 9 +++ stackline/src/tile/mod.rs | 6 +- stackline/src/utils.rs | 12 ++- stackline/src/world.rs | 4 + stackline/tiles/stack.rs | 143 ++++++++++++++++++++++++++++++++++++ stackline/tiles/transmit.rs | 4 +- stackline/tiles/wire.rs | 12 +-- 10 files changed, 199 insertions(+), 14 deletions(-) create mode 100644 stackline/tiles/stack.rs diff --git a/stackline-cli/src/main.rs b/stackline-cli/src/main.rs index c3c74da..05546e4 100644 --- a/stackline-cli/src/main.rs +++ b/stackline-cli/src/main.rs @@ -14,6 +14,8 @@ struct Args { file: Option, } +const BLINK_DURATION: Duration = Duration::new(0, 250_000_000); + fn main() { let args = Args::parse(); @@ -28,6 +30,7 @@ fn main() { } else { World::new() }; + world.set_blink(BLINK_DURATION); loop { let mut line = String::new(); @@ -53,7 +56,22 @@ fn main() { } Some("print") => { - print!("{}", world); + if let Some(blinks) = tokens.next().and_then(|b| b.parse::().ok()) { + let mut first = true; + let mut stdout = std::io::stdout(); + for _ in 0..blinks { + if first { + first = false; + } else { + std::thread::sleep(BLINK_DURATION); + write!(stdout, "\x1b[{};A", world.get_bounds().3).unwrap(); + } + print!("{}", world); + stdout.flush().unwrap(); + } + } else { + print!("{}", world); + } } Some("panes") => { panes(&world); @@ -565,6 +583,7 @@ fn load(world: &mut World, path: impl AsRef) { Ok(string) => match serde_json::from_str(&string) { Ok(parsed) => { *world = parsed; + world.set_blink(BLINK_DURATION); } Err(err) => { eprintln!("Error while parsing file: {}", err); diff --git a/stackline/src/context.rs b/stackline/src/context.rs index 9dfe5d4..730dd4f 100644 --- a/stackline/src/context.rs +++ b/stackline/src/context.rs @@ -1,5 +1,4 @@ use super::*; -use std::time::Duration; use veccell::{VecRef, VecRefMut}; /** Provides an interface between a [`Tile`] and its parent [`Pane`] during [`Tile::update`]. diff --git a/stackline/src/lib.rs b/stackline/src/lib.rs index 2396c1e..01d5ea7 100644 --- a/stackline/src/lib.rs +++ b/stackline/src/lib.rs @@ -7,6 +7,7 @@ This library is the rust implementation of the core logic of the language. */ +#![feature(div_duration)] #![feature(drain_filter)] use std::num::NonZeroUsize; diff --git a/stackline/src/signal.rs b/stackline/src/signal.rs index cfe059f..53e350a 100644 --- a/stackline/src/signal.rs +++ b/stackline/src/signal.rs @@ -46,6 +46,15 @@ impl<'a> From<&'a str> for Value { } } +impl std::fmt::Display for Value { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Value::Number(num) => write!(f, "{:.2}", num), + Value::String(string) => write!(f, "\"{}\"", string), + } + } +} + /// The unit of information that [`Tile`]s transmit between each other. /// A `Signal` is made up of a [`stack`](Signal::stack), and tracks its [`position`](Signal::position) and [`direction`](Signal::direction). /// diff --git a/stackline/src/tile/mod.rs b/stackline/src/tile/mod.rs index ea25298..3f30835 100644 --- a/stackline/src/tile/mod.rs +++ b/stackline/src/tile/mod.rs @@ -169,19 +169,19 @@ pub trait Tile: std::clone::Clone + std::fmt::Debug + Serialize + for<'d> Deseri // TODO: Use a 2d slice type #[inline] #[allow(unused_variables)] - fn draw<'b>(&'b self, surface: &mut TextSurface, ctx: DrawContext<'b>) { + fn draw(&self, surface: &mut TextSurface, ctx: DrawContext<'_>) { if let (Ok(x), Ok(y)) = ( ctx.surface_coords.0.try_into(), ctx.surface_coords.1.try_into(), ) { - surface.set(x, y, self.draw_simple(ctx.state)); + surface.set(x, y, self.draw_simple(ctx)); } } /// Used by the default implementation of `draw`, #[inline] #[allow(unused_variables)] - fn draw_simple(&self, state: State) -> TextChar { + fn draw_simple(&self, ctx: DrawContext<'_>) -> TextChar { TextChar::default() } } diff --git a/stackline/src/utils.rs b/stackline/src/utils.rs index 59bbe44..acdbb2f 100644 --- a/stackline/src/utils.rs +++ b/stackline/src/utils.rs @@ -142,7 +142,17 @@ impl Blink { } } - // TODO: methods for blinking text + pub fn period(&self, amount: usize) -> usize { + if self.blink_speed.is_zero() { + 0 + } else { + self.elapsed.div_duration_f32(self.blink_speed) as usize % amount + } + } + + pub fn scroll(&self, text: &str) -> char { + text.chars().nth(self.period(text.len())).unwrap_or(' ') + } } #[cfg(test)] diff --git a/stackline/src/world.rs b/stackline/src/world.rs index aa92224..d434534 100644 --- a/stackline/src/world.rs +++ b/stackline/src/world.rs @@ -179,6 +179,10 @@ impl World { pub fn panes(&self) -> &HashMap { &self.panes } + + pub fn set_blink(&mut self, blink_speed: Duration) { + self.blink_speed = blink_speed; + } } impl std::fmt::Display for World { diff --git a/stackline/tiles/stack.rs b/stackline/tiles/stack.rs new file mode 100644 index 0000000..2b5814e --- /dev/null +++ b/stackline/tiles/stack.rs @@ -0,0 +1,143 @@ +//! Basic stack operations: push a value, pop a value, swap, etc. + +use crate::prelude::*; +use crate::tile::prelude::*; + +/// Pushes a value on the stack, then lets the signal through +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Push { + value: Value +} + +impl Push { + pub fn new(value: Value) -> Self { + Self { + value + } + } +} + +impl Default for Push { + fn default() -> Self { + Self::new(Value::Number(0.0)) + } +} + +impl Tile for Push { + fn update<'b>(&'b mut self, mut context: UpdateContext<'b>) { + if let Some(mut signal) = context.take_signal() { + signal.push(self.value.clone()); + + if let Some(coords) = context.offset(signal.direction().into_offset()) { + context.send(coords, signal.direction(), signal).unwrap_or_else(|_| unreachable!()); + } + } + + if context.state() != State::Idle { + context.next_state(); + } + } + + fn draw_simple(&self, ctx: DrawContext) -> TextChar { + TextChar::from_state('p', ctx.state) + } +} + +/// Pops `n` values from the top of the stack, then lets the signal through +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Pop { + amount: usize, +} + +impl Pop { + pub fn new(amount: usize) -> Self { + Self { + amount + } + } +} + +impl Default for Pop { + fn default() -> Self { + Self::new(1) + } +} + +impl Tile for Pop { + fn update<'b>(&'b mut self, mut context: UpdateContext<'b>) { + if let Some(mut signal) = context.take_signal() { + for _ in 0..self.amount { + let _ = signal.pop(); + } + + if let Some(coords) = context.offset(signal.direction().into_offset()) { + context.send(coords, signal.direction(), signal).unwrap_or_else(|_| unreachable!()); + } + } + + if context.state() != State::Idle { + context.next_state(); + } + } + + fn draw_simple(&self, ctx: DrawContext) -> TextChar { + if self.amount == 1 { + TextChar::from_state('P', ctx.state) + } else { + TextChar::from_state(ctx.blink.scroll(&format!("PPP{}", self.amount)), ctx.state) + } + } +} + +/// Reverses the order of the top `n` values from the stack, then lets the signal through +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Swap { + amount: usize, +} + +impl Swap { + pub fn new(amount: usize) -> Self { + Self { + amount + } + } +} + +impl Default for Swap { + fn default() -> Self { + Self::new(2) + } +} + +impl Tile for Swap { + fn update<'b>(&'b mut self, mut context: UpdateContext<'b>) { + if let Some(mut signal) = context.take_signal() { + let len = signal.stack().len(); + let last_elements = if self.amount > len { + &mut signal.stack_mut()[..] + } else { + &mut signal.stack_mut()[(len - self.amount)..len] + }; + + last_elements.reverse(); + + if let Some(coords) = context.offset(signal.direction().into_offset()) { + context.send(coords, signal.direction(), signal).unwrap_or_else(|_| unreachable!()); + } + } + + if context.state() != State::Idle { + context.next_state(); + } + } + + fn draw_simple(&self, ctx: DrawContext) -> TextChar { + if self.amount == 2 { + TextChar::from_state('s', ctx.state) + } else { + TextChar::from_state(ctx.blink.scroll(&format!("sss{}", self.amount)), ctx.state) + } + } +} + +// TODO: SwapN, RotateLeft, RotateRight, RotateLeftN, RotateRightN diff --git a/stackline/tiles/transmit.rs b/stackline/tiles/transmit.rs index 16d8b85..b79fe4b 100644 --- a/stackline/tiles/transmit.rs +++ b/stackline/tiles/transmit.rs @@ -28,8 +28,8 @@ impl Tile for Teleporter { } } - fn draw_simple(&self, state: State) -> TextChar { - TextChar::from_state('P', state) + fn draw_simple(&self, ctx: DrawContext) -> TextChar { + TextChar::from_state('P', ctx.state) } } diff --git a/stackline/tiles/wire.rs b/stackline/tiles/wire.rs index b6eef8a..7936cef 100644 --- a/stackline/tiles/wire.rs +++ b/stackline/tiles/wire.rs @@ -34,14 +34,14 @@ impl Tile for Wire { self.0.contains(direction) } - fn draw_simple(&self, state: State) -> TextChar { + fn draw_simple(&self, ctx: DrawContext) -> TextChar { let ch = match self.0 { Orientation::Horizontal => '-', Orientation::Vertical => '|', Orientation::Any => '+', }; - TextChar::from_state(ch, state) + TextChar::from_state(ch, ctx.state) } } @@ -76,7 +76,7 @@ impl Tile for Diode { direction.opposite() != self.0 } - fn draw_simple(&self, state: State) -> TextChar { + fn draw_simple(&self, ctx: DrawContext) -> TextChar { let ch = match self.0 { Direction::Up => '^', Direction::Down => 'v', @@ -84,7 +84,7 @@ impl Tile for Diode { Direction::Right => '>', }; - TextChar::from_state(ch, state) + TextChar::from_state(ch, ctx.state) } } @@ -119,7 +119,7 @@ impl Tile for Resistor { } } - fn draw_simple(&self, state: State) -> TextChar { + fn draw_simple(&self, ctx: DrawContext) -> TextChar { let ch = match self.direction { Direction::Up => '\u{2191}', // Upwards Arrow Direction::Down => '\u{2193}', // Downwards Arrow @@ -127,7 +127,7 @@ impl Tile for Resistor { Direction::Right => '\u{2192}', // Rightwards Arrow }; - TextChar::from_state(ch, state) + TextChar::from_state(ch, ctx.state) } }