//! Transmission tiles: allow for inter-Pane communication use crate::prelude::*; use crate::tile::prelude::*; /// Instantly sends any incomming signals to `coordinates` #[derive(Clone, Debug, Serialize, Deserialize, Default)] pub struct Teleporter { pub coordinates: (String, usize, usize), } impl Teleporter { pub fn new(name: String, x: usize, y: usize) -> Self { Self { coordinates: (name, x, y), } } } 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); } if context.state() != State::Idle { context.next_state(); } } fn draw_simple(&self, ctx: DrawContext) -> TextChar { TextChar::from_state('T', ctx.state) } } /// Sends a signal through a virtual wire towards `coordinates`. #[derive(Clone, Debug, Serialize, Deserialize, Default)] 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, origin: (i32, i32), world: &World) { use pathfinding::directed::astar::astar; // A* search #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] struct Pos(i32, i32); impl Pos { fn neighbors(&self, world: &World) -> [(Pos, i32); 4] { let x = self.0; let y = self.1; [ Pos(x + 1, y).with_weight(world), Pos(x - 1, y).with_weight(world), Pos(x, y + 1).with_weight(world), Pos(x, y - 1).with_weight(world), ] } fn with_weight(self, world: &World) -> (Self, i32) { if world.in_pane(self.0, self.1) { (self, 100) } else { (self, 1) } } fn heuristic(&self, target: Pos) -> i32 { (self.0 - target.0).abs() + (self.1 - target.1).abs() } } impl From for (i32, i32) { fn from(pos: Pos) -> (i32, i32) { (pos.0, pos.1) } } impl From<&Pos> for (i32, i32) { fn from(pos: &Pos) -> (i32, i32) { (pos.0, pos.1) } } if let Some(pane) = world.get_pane(&self.coordinates.0) { let target = Pos( pane.position().0 + self.coordinates.1 as i32, pane.position().1 + self.coordinates.2 as i32, ); if let Some((best_path, _)) = astar( &Pos(origin.0, origin.1), |node| node.neighbors(world), |node| node.heuristic(target), |&node| node == target, ) { self.path = Vec::new(); self.path.push((best_path[0].0, best_path[0].1)); for (prev, current) in best_path.iter().zip(best_path.iter().skip(1)).skip(1) { // If self.path.last(), prev, current aren't aligned, push prev to self.path let prev_x_aligned = self.path[self.path.len() - 1].0 == prev.0; let curr_x_aligned = prev.0 == current.0; if prev_x_aligned != curr_x_aligned { self.path.push(prev.into()); } } if self.path[self.path.len() - 1] != best_path[best_path.len() - 1].into() { self.path.push(best_path[best_path.len() - 1].into()); } self.length = best_path.len() - 1; } } } } impl Tile for Sender { fn init<'b>(&'b mut self, context: InitContext<'b>) { self.calculate_path(context.world_coords, context.world) } 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.state() == State::Dormant && self.signals.is_empty() { context.next_state(); } } // TODO: read self.signals to determine the state of each char // TODO: automated test fn draw(&self, surface: &mut TextSurface, ctx: DrawContext) { let (x, y) = if self.path.len() > 0 { (ctx.surface_coords.0 - self.path[0].0, ctx.surface_coords.1 - self.path[0].1) } else { if let (Ok(x), Ok(y)) = (ctx.surface_coords.0.try_into(), ctx.surface_coords.1.try_into()) { surface.set(x, y, TextChar::from_state('S', ctx.state)); } return // (ctx.surface_coords.0, ctx.surface_coords.1) }; let mut count = 0; let mut get_state = || { let mut found_exact = false; let mut found_next = false; for (_sig, n) in self.signals.iter() { if *n == count { found_exact = true; } else if *n == count + 1 { found_next = true; } } count += 1; if found_exact { State::Active } else if found_next { State::Dormant } else { State::Idle } }; for (prev, next) in self.path.iter().zip(self.path.iter().skip(1)) { let (line_char, dx, dy) = if prev.0 != next.0 { // Draw the diode of the corner let ch = if next.0 > prev.0 { '>' } else { '<' }; let x2 = x + prev.0; let y2 = y + prev.1; if x2 >= 0 && y2 >= 0 { surface.set( x2 as usize, y2 as usize, TextChar::from_state(ch, get_state()), ); } // Draw an horizontal line if prev.0 < next.0 { ('-', 1, 0) } else { ('-', -1, 0) } } else { // Draw the diode of the corner let ch = if next.1 > prev.1 { 'v' } else { '^' }; let x2 = x + prev.0; let y2 = y + prev.1; if x2 >= 0 && y2 >= 0 { surface.set( x2 as usize, y2 as usize, TextChar::from_state(ch, get_state()), ); } // Draw a vertical line if prev.1 < next.1 { ('|', 0, 1) } else { ('|', 0, -1) } }; // Draw the line for (x2, y2) in std::iter::successors( Some(*prev), |&curr| { let res = (curr.0.checked_add(dx)?, curr.1.checked_add(dy)?); if res == *next { None } else { Some(res) } } ).skip(1) { let x2 = x + x2; let y2 = y + y2; if x2 >= 0 && y2 >= 0 { surface.set( x2 as usize, y2 as usize, TextChar::from_state(line_char, get_state()), ); } } } } } #[cfg(test)] mod test { use super::*; use veccell::{VecRef}; #[test] fn test_teleporter_transmit_same_pane() { use crate::{Diode, Wire}; use Direction::*; use Orientation::*; let mut main_pane = test_tile_setup!( 3, 3, [ Diode::new(Right), Teleporter::new(String::from("main"), 2, 2), (), (), (), (), (), (), Wire::new(Any) ] ); test_set_signal!(main_pane, (0, 0), Right); let mut world = World::new(); world.set_pane(String::from("main"), main_pane); world.init(); world.step(); assert_signal!(world.get_pane("main").unwrap(), (1, 0)); world.step(); assert_no_signal!(world.get_pane("main").unwrap(), (1, 0)); assert_signal!(world.get_pane("main").unwrap(), (2, 2)); world.step(); assert_no_signal!(world.get_pane("main").unwrap(), (0, 0)); assert_no_signal!(world.get_pane("main").unwrap(), (1, 0)); assert_no_signal!(world.get_pane("main").unwrap(), (2, 2)); } #[test] fn test_teleporter_transmit_other_pane() { use crate::{Diode, Wire}; use Direction::*; use Orientation::*; let mut main_pane = test_tile_setup!( 2, 1, [ 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 mut world = World::new(); world.set_pane(String::from("main"), main_pane); world.set_pane(String::from("sub"), sub_pane); world.init(); world.step(); assert_signal!(world.get_pane("main").unwrap(), (1, 0)); world.step(); assert_no_signal!(world.get_pane("main").unwrap(), (1, 0)); assert_signal!(world.get_pane("sub").unwrap(), (0, 0)); world.step(); assert_no_signal!(world.get_pane("main").unwrap(), (0, 0)); assert_no_signal!(world.get_pane("main").unwrap(), (1, 0)); assert_no_signal!(world.get_pane("sub").unwrap(), (0, 0)); } #[test] 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); world.init(); for _ in 0..5 { world.step(); assert_signal!(world.get_pane("main").unwrap(), (0, 0)); } } #[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); // REGRESSION: world.init() computes a longer path than length = 2, causing errors // world.init(); for n in 0..2 { world.step(); let sender: VecRef<'_, Sender> = world .get_pane("main") .unwrap() .get_as::((0, 0)) .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> = world .get_pane("main") .unwrap() .get_as::((0, 0)) .unwrap(); assert!(sender.signals.is_empty()); assert_no_signal!(world.get_pane("main").unwrap(), (0, 0)); assert_signal!(world.get_pane("main").unwrap(), (0, 2)); } #[test] fn test_sender_pathfinding() { use crate::Wire; let mut main_pane = test_tile_setup!(1, 1, [Sender::new(String::from("second"), 0, 0),]); main_pane.set_position((0, 0)); let mut second_pane = test_tile_setup!(1, 1, [Wire::new(Orientation::Any)]); second_pane.set_position((2, 0)); let mut world = World::new(); world.set_pane(String::from("main"), main_pane); world.set_pane(String::from("second"), second_pane); let mut tile = world .get_pane("main") .unwrap() .borrow_mut_as::((0, 0)) .unwrap(); tile.calculate_path((0, 0), &world); assert_eq!(tile.path, [(0, 0), (2, 0)]); assert_eq!(tile.length, 2); drop(tile); world.get_pane_mut("second").unwrap().set_position((2, 2)); let mut tile = world .get_pane("main") .unwrap() .borrow_mut_as::((0, 0)) .unwrap(); tile.calculate_path((0, 0), &world); assert!(tile.path == [(0, 0), (2, 0), (2, 2)] || tile.path == [(0, 0), (0, 2), (2, 2)]); assert_eq!(tile.length, 4); } #[test] fn test_sender_pathfinding_penalty() { use crate::Wire; let mut main_pane = test_tile_setup!(2, 4, [ Sender::new(String::from("second"), 0, 0), (), (), (), (), (), (), (), ]); main_pane.set_position((0, 0)); let mut obstacle_pane = test_tile_setup!(4, 1, [(), (), (), ()]); obstacle_pane.set_position((2, 1)); let mut second_pane = test_tile_setup!(1, 1, [Wire::new(Orientation::Any)]); second_pane.set_position((2, 2)); let mut world = World::new(); world.set_pane(String::from("main"), main_pane); world.set_pane(String::from("obstacle"), obstacle_pane); world.set_pane(String::from("second"), second_pane); let mut tile = world .get_pane("main") .unwrap() .borrow_mut_as::((0, 0)) .unwrap(); tile.calculate_path((0, 0), &world); assert_eq!(tile.path, [ (0, 0), (-1, 0), (-1, 4), (2, 4), (2, 2) ]); } }