use ratatui::Frame;
use ratatui::style::{Color, Modifier, Style};
const GLYPHS: &[u8] = b"abcdefghijklmnopqrstuvwxyz0123456789=+*<>[]{}/\\|-_$#@%&";
struct Drop {
head: f32,
speed: f32,
len: usize,
}
pub struct Matrix {
pub on: bool,
drops: Vec<Drop>,
rng: u64,
frame: u64,
}
impl Default for Matrix {
fn default() -> Self {
Matrix {
on: false,
drops: Vec::new(),
rng: 0x9E37_79B9_7F4A_7C15,
frame: 0,
}
}
}
impl Matrix {
fn next_random(&mut self) -> u64 {
self.rng ^= self.rng << 13;
self.rng ^= self.rng >> 7;
self.rng ^= self.rng << 17;
self.rng
}
fn spawn(&mut self, height: usize) -> Drop {
let a = self.next_random();
let b = self.next_random();
Drop {
head: -((a % (height as u64 * 2).max(1)) as f32),
speed: 0.3 + (b % 100) as f32 / 100.0,
len: 4 + (a % 14) as usize,
}
}
fn step(&mut self, width: usize, height: usize, level: f32) {
while self.drops.len() < width {
let drop = self.spawn(height);
self.drops.push(drop);
}
self.drops.truncate(width);
let push = level * 6.0;
if push <= f32::EPSILON {
return;
}
self.frame = self.frame.wrapping_add(1);
for i in 0..self.drops.len() {
self.drops[i].head += self.drops[i].speed * push;
if self.drops[i].head - self.drops[i].len as f32 > height as f32 {
self.drops[i] = self.spawn(height);
}
}
}
fn glyph(&self, col: usize, row: usize) -> char {
let era = self.frame / 6;
let mut h = (col as u64)
.wrapping_mul(0x9E37_79B9)
.wrapping_add((row as u64).wrapping_mul(0x85EB_CA6B))
.wrapping_add(era.wrapping_mul(0xC2B2_AE35));
h ^= h >> 15;
GLYPHS[(h % GLYPHS.len() as u64) as usize] as char
}
}
pub fn overlay(frame: &mut Frame, matrix: &mut Matrix, level: f32) {
let area = frame.area();
let (width, height) = (area.width as usize, area.height as usize);
if width == 0 || height == 0 {
return;
}
matrix.step(width, height, level);
let mut cells: Vec<(u16, u16, char, Style)> = Vec::new();
for col in 0..width {
let drop = &matrix.drops[col];
for back in 0..drop.len {
let row = drop.head - back as f32;
if row < 0.0 || row >= height as f32 {
continue;
}
let row = row as usize;
let style = match back {
0 => Style::default()
.fg(Color::Indexed(120))
.add_modifier(Modifier::BOLD),
1..=2 => Style::default().fg(Color::Indexed(35)),
3..=6 => Style::default().fg(Color::Indexed(28)),
_ => Style::default().fg(Color::Indexed(22)),
};
cells.push((col as u16, row as u16, matrix.glyph(col, row), style));
}
}
let buffer = frame.buffer_mut();
for (x, y, glyph, style) in cells {
let cell = &mut buffer[(x, y)];
cell.set_char(glyph);
cell.set_style(style);
}
}