use super::*; #[derive(Debug)] pub struct Pane { tiles: Vec, width: NonZeroUsize, height: NonZeroUsize, pub(crate) signals: Vec<(usize, usize)>, } impl Pane { pub fn empty(width: usize, height: usize) -> Option { // TODO: check that width * height is a valid usize let length = width.checked_mul(height)?; Some(Self { width: width.try_into().ok()?, height: height.try_into().ok()?, tiles: vec![FullTile::default(); length], signals: Vec::new(), }) } /// Returns `Some((x + Δx, y + Δy))` iff `(x + Δx, y + Δy)` is inside the world #[inline] pub fn offset(&self, position: (usize, usize), offset: (i8, i8)) -> Option<(usize, usize)> { if offset.0 < 0 && (-offset.0) as usize > position.0 || offset.1 < 0 && (-offset.1) as usize > position.1 { return None; } // TODO: check that position and position + offset are valid isize values let new_pos = ( (position.0 as isize + offset.0 as isize) as usize, (position.1 as isize + offset.1 as isize) as usize, ); if new_pos.0 < self.width.get() && new_pos.1 < self.height.get() { Some(new_pos) } else { None } } // TODO: Have a Result instead of an Option #[inline] pub fn get<'b>(&'b self, position: (usize, usize)) -> Option<&'b FullTile> { if !self.in_bounds(position) { return None; } self.tiles.get(position.1 * self.width.get() + position.0) } /// Returns a mutable reference to the [`Tile`] at `position`. /// /// ## Example /// /// ``` /// use stackline::prelude::*; /// use stackline::tile::Wire; /// /// let mut pane = Pane::empty(4, 4).unwrap(); /// /// pane.set_tile((0, 0), Wire::new(Orientation::Horizontal)); /// /// let mut tile = pane.get_mut((0, 0)).unwrap(); /// tile.set_state(State::Active); /// ``` #[inline] pub fn get_mut<'b>(&'b mut self, position: (usize, usize)) -> Option<&'b mut FullTile> { if !self.in_bounds(position) { return None; } self.tiles .get_mut(position.1 * self.width.get() + position.0) } /// Sets the tile at `position` to `tile`. `T` must either implement [`Tile`] or be `()`. #[inline] pub fn set_tile(&mut self, position: (usize, usize), tile: T) -> Option<()> where FullTile: From, { let full_tile = self.get_mut(position)?; *full_tile = FullTile::from(tile); Some(()) } /// Returns the [`State`] of the tile at `position`, if it exists. #[inline] pub fn get_state(&self, position: (usize, usize)) -> Option { self.get(position).map(|x| x.state().clone()) } /// Sets the signal for the tile at `position` to `signal`. /// Returns `Some` iff: /// - the tile exists /// - the tile accepts a signal (ie. it isn't empty) #[inline] pub fn set_signal(&mut self, position: (usize, usize), mut signal: Signal) -> Option<()> { signal.set_position(position); if let Some(tile) = self.get_mut(position) { tile.set_signal(Some(signal))?; tile.set_state(State::Active); self.signals.push(position); Some(()) } else { None } } #[inline] pub fn in_bounds(&self, position: (usize, usize)) -> bool { position.0 < self.width.get() && position.1 < self.height.get() } #[inline] fn update(&mut self, position: (usize, usize), commit: &mut UpdateCommit) -> Option<()> { let (ctx, tile) = UpdateContext::new(self, position, commit)?; tile.update(ctx); Some(()) } // TODO: document pub fn step(&mut self) { let mut commit = UpdateCommit::new(); for position in std::mem::replace(&mut self.signals, Vec::new()) { let _ = self.update(position, &mut commit); } for y in 0..self.height.get() { for x in 0..self.width.get() { if self.get_state((x, y)).unwrap() != State::Idle { let _ = self.update((x, y), &mut commit); } } } commit.apply(self); } /// Returns an iterator over the tiles and their coordinates #[inline] pub fn tiles<'b>(&'b self) -> impl Iterator + 'b { self.tiles .iter() .enumerate() .filter_map(move |(i, v)| Some((i % self.width, i / self.width, v))) } 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; if x >= 0 && y >= 0 { tile.draw(x as usize, y as usize, surface); } } } } #[cfg(test)] mod test { use super::*; #[test] fn test_pane_draw() { use crate::tile::Wire; use Orientation::*; let mut surface = TextSurface::new(3, 3); let mut pane = test_tile_setup!(2, 2, [Wire::new(Horizontal), Wire::new(Vertical), Wire::new(Any), ()]); test_set_signal!(pane, (0, 0), Direction::Right); pane.draw(0, 0, &mut surface); assert_eq!(surface.get(0, 0).unwrap().ch, '-'); assert_eq!(surface.get(1, 0).unwrap().ch, '|'); assert_eq!(surface.get(0, 1).unwrap().ch, '+'); assert_eq!(surface.get(1, 1), Some(TextChar::default())); for n in 0..3 { assert_eq!(surface.get(2, n), Some(TextChar::default())); assert_eq!(surface.get(n, 2), Some(TextChar::default())); } // With offset (1, 0) let mut surface = TextSurface::new(3, 3); pane.draw(1, 0, &mut surface); assert_eq!(surface.get(1, 0).unwrap().ch, '-'); assert_eq!(surface.get(2, 0).unwrap().ch, '|'); assert_eq!(surface.get(1, 1).unwrap().ch, '+'); assert_eq!(surface.get(2, 1), Some(TextChar::default())); for n in 0..3 { assert_eq!(surface.get(0, n), Some(TextChar::default())); assert_eq!(surface.get(n, 2), Some(TextChar::default())); } // With offset (0, 1) let mut surface = TextSurface::new(3, 3); pane.draw(0, 1, &mut surface); assert_eq!(surface.get(0, 1).unwrap().ch, '-'); assert_eq!(surface.get(1, 1).unwrap().ch, '|'); assert_eq!(surface.get(0, 2).unwrap().ch, '+'); assert_eq!(surface.get(1, 2), Some(TextChar::default())); for n in 0..3 { assert_eq!(surface.get(2, n), Some(TextChar::default())); assert_eq!(surface.get(n, 0), Some(TextChar::default())); } // Draw outside of bounds with offset (2, 2) let mut surface = TextSurface::new(3, 3); pane.draw(2, 2, &mut surface); assert_eq!(surface.get(2, 2).unwrap().ch, '-'); for y in 0..3 { for x in 0..3 { if (x, y) != (2, 2) { assert_eq!(surface.get(x, y), Some(TextChar::default())); } } } } }