Skip to content
20 changes: 18 additions & 2 deletions src/matrix/entity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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::<u16>(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;
}

Expand All @@ -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(());
}

Expand All @@ -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
}
}
7 changes: 5 additions & 2 deletions src/matrix/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))?;
Expand Down
30 changes: 18 additions & 12 deletions src/matrix/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -118,22 +119,27 @@ 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,
// and if it is, we regenerate the stream and place it back at the top.
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,
};

Expand Down
8 changes: 8 additions & 0 deletions src/symbols.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading