diff --git a/src/matrix/entity.rs b/src/matrix/entity.rs index 5a0ccca..0c95700 100644 --- a/src/matrix/entity.rs +++ b/src/matrix/entity.rs @@ -25,6 +25,8 @@ pub struct Entity { /// The symbol the entity represents symbol: char, + /// Width of the symbol that is visible in the terminal + width: usize, /// The color of the symbol color: colors::RGBColor, /// The character set to use for the symbols @@ -56,15 +58,21 @@ impl Entity { speed_y, color, symbol: ' ', + width: config.mode.width(), mode: config.mode.clone(), frame_count: 0, switch_interval: utils::random_between::(1, config.switch_interval * config.fps), } } + /// Returns the width of the entity's symbol in terminal columns + pub fn width(&self) -> usize { + self.width + } + /// Rain. Updates the position of the [Entity] using the rain speed. pub fn rain(&mut self) { - self.x += self.speed_x; + self.x += self.speed_x * self.width as f32; self.y += self.speed_y; } @@ -90,7 +98,7 @@ impl Entity { stdout: &mut std::io::Stdout, ) -> std::io::Result<()> { // Don't render if the entity is off-screen - if self.x < 0.0 || self.x >= columns as f32 || self.y < 0.0 || self.y >= rows as f32 { + if self.is_offscreen(columns, rows) { return Ok(()); } @@ -106,4 +114,12 @@ impl Entity { Ok(()) } + + /// Returns true if the [Entity] is offscreen + fn is_offscreen(&self, columns: i32, rows: i32) -> bool { + self.x < 0.0 + || self.x + self.width as f32 >= columns as f32 + || self.y < 0.0 + || self.y >= rows as f32 + } } diff --git a/src/matrix/mod.rs b/src/matrix/mod.rs index ca82ec8..4a4e838 100644 --- a/src/matrix/mod.rs +++ b/src/matrix/mod.rs @@ -152,8 +152,11 @@ impl Matrix { )))?; // Iterate over each row and pre-render random faint symbols for _ in 0..self.rows { - let random_symbol_str = - String::from_iter((0..self.columns).map(|_| config.mode.get_random())); + let random_symbol_str = String::from_iter( + (0..self.columns) + .step_by(config.mode.width()) + .map(|_| config.mode.get_random()), + ); stdout .queue(Print(random_symbol_str))? .queue(MoveToNextLine(1))?; diff --git a/src/matrix/stream.rs b/src/matrix/stream.rs index 766fc8a..7dadd50 100644 --- a/src/matrix/stream.rs +++ b/src/matrix/stream.rs @@ -87,15 +87,16 @@ impl Stream { let color = gradient.interpolate(i as f32 / self.count as f32); // Determine the entity starting x and y positions based on the direction of flow + let width_adjusted_i = i as f32 * config.mode.width() as f32; let (x, y) = match config.direction { Direction::Down => (self.x, self.y - i as f32), Direction::Up => (self.x, self.y + i as f32), - Direction::Right => (self.x - i as f32, self.y), - Direction::Left => (self.x + i as f32, self.y), - Direction::DiagonalRight => (self.x - i as f32, self.y - i as f32), - Direction::DiagonalRightReverse => (self.x + i as f32, self.y + i as f32), - Direction::DiagonalLeft => (self.x + i as f32, self.y - i as f32), - Direction::DiagonalLeftReverse => (self.x - i as f32, self.y + i as f32), + Direction::Right => (self.x - width_adjusted_i, self.y), + Direction::Left => (self.x + width_adjusted_i, self.y), + Direction::DiagonalRight => (self.x - width_adjusted_i, self.y - i as f32), + Direction::DiagonalRightReverse => (self.x + width_adjusted_i, self.y + i as f32), + Direction::DiagonalLeft => (self.x + width_adjusted_i, self.y - i as f32), + Direction::DiagonalLeftReverse => (self.x - width_adjusted_i, self.y + i as f32), }; // Create the entity and add it to the entities vector @@ -118,10 +119,11 @@ impl Stream { // Clean up the last entity. As the stream moves down, all entities will be overwritten // by the next frame, except for the trailing entity. So we manually overwrite it so that // the stream doesn't leave a trail. - if !config.leave_trail { + if !config.leave_trail && e.x >= 0.0 { + let space = " ".repeat(config.mode.width()); stdout .queue(cursor::MoveTo(e.x as u16, e.y as u16))? - .queue(Print(" "))?; + .queue(Print(space))?; } // This is also a good time to check if the last entity is off the screen, @@ -129,11 +131,15 @@ impl Stream { let should_regenerate = match config.direction { Direction::Down => e.y >= rows as f32, Direction::Up => e.y < 0.0, - Direction::Right => e.x >= columns as f32, - Direction::Left => e.x < 0.0, + Direction::Right => e.x + e.width() as f32 >= columns as f32, + Direction::Left => e.x + (e.width() as f32) < 0.0, Direction::DiagonalLeft => e.x < 0.0 && e.y >= rows as f32, - Direction::DiagonalLeftReverse => e.x >= columns as f32 && e.y < 0.0, - Direction::DiagonalRight => e.x >= columns as f32 && e.y >= rows as f32, + Direction::DiagonalLeftReverse => { + e.x + e.width() as f32 >= columns as f32 && e.y < 0.0 + } + Direction::DiagonalRight => { + e.x + e.width() as f32 >= columns as f32 && e.y >= rows as f32 + } Direction::DiagonalRightReverse => e.x < 0.0 && e.y < 0.0, }; diff --git a/src/symbols.rs b/src/symbols.rs index 1a913ef..50432fd 100644 --- a/src/symbols.rs +++ b/src/symbols.rs @@ -45,6 +45,14 @@ impl FromStr for Symbols { } impl Symbols { + /// Get the width of the character in the symbol set + pub fn width(&self) -> usize { + match self { + Self::Original | Self::Cursed => 2, // Katakana and Emojis + _ => 1, // Default to width of 1 for most characters + } + } + /// Get a random character from the symbol set pub fn get_random(&self) -> char { match self {