From 941de10ea30a0640b183cc5bee95ca1d0c6b8ab5 Mon Sep 17 00:00:00 2001 From: Adrien Burgun Date: Thu, 7 Jul 2022 12:51:51 +0200 Subject: [PATCH] :sparkles: Rename Portal to Teleporter, new Sender tile [WIP] --- stackline/build.rs | 22 +++- stackline/src/lib.rs | 2 + stackline/src/pane.rs | 57 +++++++++- stackline/src/tile/full.rs | 3 + stackline/tiles/transmit.rs | 215 ++++++++++++++++++++++++++++++++---- 5 files changed, 272 insertions(+), 27 deletions(-) diff --git a/stackline/build.rs b/stackline/build.rs index 1b533b4..afa3fdc 100644 --- a/stackline/build.rs +++ b/stackline/build.rs @@ -91,10 +91,30 @@ fn main() { res += "#[enum_dispatch]\n"; res += "pub enum AnyTile {\n"; - for name in names { + for name in names.iter() { res += &format!(" {0}({0}),\n", name); } res += "}\n"; + // impl TryInto<&T> for &AnyTile + res += "\n"; + + for name in names { + res += &format!( + concat!( + "impl<'a> TryInto<&'a {0}> for &'a AnyTile {{\n", + " type Error = ();\n", + " fn try_into(self) -> Result<&'a {0}, Self::Error> {{\n", + " match self {{\n", + " AnyTile::{0}(tile) => Ok(tile),\n", + " _ => Err(()),\n", + " }}\n", + " }}\n", + "}}\n", + ), + name + ); + } + fs::write(dest_path.clone(), &res).expect(&format!("Couldn't write to {:?}", dest_path)); } diff --git a/stackline/src/lib.rs b/stackline/src/lib.rs index e6f099b..367eba5 100644 --- a/stackline/src/lib.rs +++ b/stackline/src/lib.rs @@ -7,6 +7,8 @@ This library is the rust implementation of the core logic of the language. */ +#![feature(drain_filter)] + #[macro_use] extern crate contracts; diff --git a/stackline/src/pane.rs b/stackline/src/pane.rs index a8963af..001b276 100644 --- a/stackline/src/pane.rs +++ b/stackline/src/pane.rs @@ -7,6 +7,8 @@ pub struct Pane { width: NonZeroUsize, height: NonZeroUsize, + position: (i32, i32), + pub(crate) signals: Vec<(usize, usize)>, } @@ -49,6 +51,8 @@ impl Pane { height: height.try_into().ok()?, tiles, + position: (0, 0), + signals: Vec::new(), }) } @@ -83,6 +87,19 @@ impl Pane { self.height } + /// Returns the position of the `Pane` in its [`World`]. + /// This property is used for drawing the `Pane` and calculating the distances between cross-pane tiles. + #[inline] + pub fn position(&self) -> (i32, i32) { + self.position + } + + /// Sets the position of the `Pane` in its [`World`]. + #[inline] + pub fn set_position(&mut self, position: (i32, i32)) { + self.position = position; + } + /// Given a `position = (x, y)` and an `offset = (Δx, Δy)`, /// returns `Some((x + Δx, y + Δy))` if `(x + Δx, y + Δy)` is inside the `Pane`. /// @@ -378,10 +395,12 @@ impl Pane { .filter_map(move |(i, v)| Some((i % self.width, i / self.width, v))) } + /// Draws the Pane at `(dx + self.position.0, dy + self.position.1)` on a [`TextSurface`]. + /// Empty tiles will leave the `TextSurface` untouched, but tiles are free to modify the characters around them. pub fn draw(&self, dx: isize, dy: isize, surface: &mut TextSurface) { for (x, y, tile) in self.tiles() { - let x = x as isize + dx; - let y = y as isize + dy; + let x = x as isize + dx + self.position.0 as isize; + let y = y as isize + dy + self.position.1 as isize; if x >= 0 && y >= 0 { tile.draw(x as usize, y as usize, surface); @@ -468,4 +487,38 @@ mod test { } } } + + #[test] + fn test_pane_draw_offset() { + use crate::tile::Wire; + use Orientation::*; + + let mut pane = test_tile_setup!( + 2, + 2, + [ + Wire::new(Horizontal), + Wire::new(Vertical), + Wire::new(Any), + () + ] + ); + + pane.set_position((2, 1)); + + let mut surface = TextSurface::new(9, 9); + + pane.draw(5, 3, &mut surface); + + assert_eq!(surface.get(5 + 2, 3 + 1).unwrap().ch, '-'); + assert_eq!(surface.get(5 + 3, 3 + 1).unwrap().ch, '|'); + assert_eq!(surface.get(5 + 2, 3 + 2).unwrap().ch, '+'); + for y in 0..9 { + for x in 0..9 { + if (x, y) != (5 + 2, 3 + 1) && (x, y) != (5 + 3, 3 + 1) && (x, y) != (5 + 2, 3 + 2) { + assert_eq!(surface.get(x, y), Some(TextChar::default())); + } + } + } + } } diff --git a/stackline/src/tile/full.rs b/stackline/src/tile/full.rs index af476d1..0308898 100644 --- a/stackline/src/tile/full.rs +++ b/stackline/src/tile/full.rs @@ -117,3 +117,6 @@ impl From<()> for FullTile { Self::new(None) } } + +// TODO: enum for > +// TODO: local trait for conversion to inner tiles diff --git a/stackline/tiles/transmit.rs b/stackline/tiles/transmit.rs index d372382..ac1d492 100644 --- a/stackline/tiles/transmit.rs +++ b/stackline/tiles/transmit.rs @@ -4,19 +4,19 @@ use crate::prelude::*; /// Instantly sends any incomming signals to `coordinates` #[derive(Clone, Debug)] -pub struct Portal { +pub struct Teleporter { pub coordinates: (String, usize, usize), } -impl Portal { +impl Teleporter { pub fn new(name: String, x: usize, y: usize) -> Self { Self { - coordinates: (name, x, y) + coordinates: (name, x, y), } } } -impl Tile for Portal { +impl Tile for Teleporter { fn update<'b>(&'b mut self, mut context: UpdateContext<'b>) { if let Some(signal) = context.take_signal() { context.send_outbound(self.coordinates.clone(), signal); @@ -32,12 +32,115 @@ impl Tile for Portal { } } +/// Sends a signal through a virtual wire towards `coordinates`. +#[derive(Clone, Debug)] +pub struct Sender { + pub coordinates: (String, usize, usize), + pub path: Vec<(i32, i32)>, // x, y + pub length: usize, + pub signals: Vec<(Signal, usize)>, +} + +impl Sender { + pub fn new(name: String, x: usize, y: usize) -> Self { + Self { + coordinates: (name, x, y), + path: Vec::new(), + length: 0, + signals: Vec::new(), + } + } + + // TODO: implement WorldMask, calculate_path and a method of Tile to call this method automatically + // pub fn calculate_path(&mut self, mask: &WorldMask) { + // + // } +} + +impl Tile for Sender { + fn update<'b>(&'b mut self, mut context: UpdateContext<'b>) { + let mut needs_sending = false; + + for (_signal, ref mut time) in self.signals.iter_mut() { + *time += 1; + + if *time >= self.length { + needs_sending = true; + } + } + + if let Some(signal) = context.take_signal() { + self.signals.push((signal, 0)); + if self.length == 0 { + needs_sending = true; + } + } + + if needs_sending { + for (signal, _time) in self.signals.drain_filter(|(_, time)| *time >= self.length) { + context.send_outbound(self.coordinates.clone(), signal); + } + } + + if context.state() == State::Active { + context.next_state(); + } else if context.state() == State::Dormant && self.signals.len() == 0 { + context.next_state(); + } + } + + // TODO: read self.signals to determine the state of each char + // TODO: automated test + fn draw(&self, x: usize, y: usize, _state: State, surface: &mut TextSurface) { + for (prev, next) in self.path.iter().zip(self.path.iter().skip(1)) { + if prev.0 != next.0 { + // Draw the diode of the corner + let ch = if next.0 > prev.0 { '>' } else { '<' }; + surface.set( + (x as i32 + prev.0) as usize, + (y as i32 + prev.1) as usize, + TextChar::from_state(ch, State::Idle), + ); + + // Draw the horizontal line + + for dx in (prev.0 + 1)..(next.0) { + surface.set( + (x as i32 + dx) as usize, + (y as i32 + prev.1) as usize, + TextChar::from_state('-', State::Idle), + ); + } + } else { + // Draw the diode of the corner + let ch = if next.1 > prev.1 { 'v' } else { '^' }; + surface.set( + (x as i32 + prev.0) as usize, + (y as i32 + prev.1) as usize, + TextChar::from_state(ch, State::Idle), + ); + + // Draw the vertical line + + for dy in (prev.1 + 1)..(next.1) { + surface.set( + (y as i32 + prev.0) as usize, + (y as i32 + dy) as usize, + TextChar::from_state('|', State::Idle), + ); + } + } + } + } +} + #[cfg(test)] mod test { use super::*; + use veccell::VecRef; #[test] - fn test_portal_transmit_same_pane() { + fn test_teleporter_transmit_same_pane() { use crate::{Diode, Wire}; use Direction::*; use Orientation::*; @@ -46,9 +149,15 @@ mod test { 3, 3, [ - Diode::new(Right), Portal::new(String::from("main"), 2, 2), (), - (), (), (), - (), (), Wire::new(Any) + Diode::new(Right), + Teleporter::new(String::from("main"), 2, 2), + (), + (), + (), + (), + (), + (), + Wire::new(Any) ] ); @@ -74,7 +183,7 @@ mod test { } #[test] - fn test_portal_transmit_other_pane() { + fn test_teleporter_transmit_other_pane() { use crate::{Diode, Wire}; use Direction::*; use Orientation::*; @@ -83,19 +192,14 @@ mod test { 2, 1, [ - Diode::new(Right), Portal::new(String::from("sub"), 0, 0), + Diode::new(Right), + Teleporter::new(String::from("sub"), 0, 0), ] ); test_set_signal!(main_pane, (0, 0), Right); - let sub_pane = test_tile_setup!( - 1, - 1, - [ - Wire::new(Any), - ] - ); + let sub_pane = test_tile_setup!(1, 1, [Wire::new(Any),]); let mut world = World::new(); world.set_pane(String::from("main"), main_pane); @@ -118,15 +222,33 @@ mod test { } #[test] - fn test_portal_transmit_self() { + fn test_teleporter_transmit_self() { use Direction::*; + let mut main_pane = test_tile_setup!(1, 1, [Teleporter::new(String::from("main"), 0, 0),]); + + test_set_signal!(main_pane, (0, 0), Right); + + let mut world = World::new(); + world.set_pane(String::from("main"), main_pane); + + for _ in 0..5 { + world.step(); + + assert_signal!(world.get_pane("main").unwrap(), (0, 0)); + } + } + + #[test] + fn test_sender_instantaneous() { + use crate::Wire; + use Direction::*; + use Orientation::*; + let mut main_pane = test_tile_setup!( 1, - 1, - [ - Portal::new(String::from("main"), 0, 0), - ] + 3, + [Sender::new(String::from("main"), 0, 2), (), Wire::new(Any)] ); test_set_signal!(main_pane, (0, 0), Right); @@ -134,10 +256,55 @@ mod test { let mut world = World::new(); world.set_pane(String::from("main"), main_pane); - for _ in 0..5 { + world.step(); + + assert_signal!(world.get_pane("main").unwrap(), (0, 2)); + } + + #[test] + fn test_sender_delay() { + use crate::Wire; + use Direction::*; + use Orientation::*; + + let mut sender = Sender::new(String::from("main"), 0, 2); + sender.length = 2; + + let mut main_pane = test_tile_setup!(1, 3, [sender, (), Wire::new(Any)]); + + test_set_signal!(main_pane, (0, 0), Right); + + let mut world = World::new(); + world.set_pane(String::from("main"), main_pane); + + for n in 0..2 { world.step(); - assert_signal!(world.get_pane("main").unwrap(), (0, 0)); + // TODO: pane.get_as::(coords) + let sender: VecRef<'_, Sender> = VecRef::map( + world.get_pane("main").unwrap().get((0, 0)).unwrap(), + |tile| tile.get().unwrap().try_into().unwrap(), + ); + + assert!(sender.signals.len() == 1); + assert!(sender.signals[0].1 == n); + + drop(sender); + + assert_no_signal!(world.get_pane("main").unwrap(), (0, 0)); + assert_no_signal!(world.get_pane("main").unwrap(), (0, 2)); } + + world.step(); + + let sender: VecRef<'_, Sender> = VecRef::map( + world.get_pane("main").unwrap().get((0, 0)).unwrap(), + |tile| tile.get().unwrap().try_into().unwrap(), + ); + + assert!(sender.signals.len() == 0); + + assert_no_signal!(world.get_pane("main").unwrap(), (0, 0)); + assert_signal!(world.get_pane("main").unwrap(), (0, 2)); } }