How to resolve the algorithm Matrix digital rain step by step in the Rust programming language
Published on 12 May 2024 09:40 PM
How to resolve the algorithm Matrix digital rain step by step in the Rust programming language
Table of Contents
Problem Statement
Implement the Matrix Digital Rain visual effect from the movie "The Matrix" as described in Wikipedia. Provided is a reference implementation in Common Lisp to be run in a terminal.
Let's start with the solution:
Step by Step solution about How to resolve the algorithm Matrix digital rain step by step in the Rust programming language
Source code in the rust programming language
[package]
name = "matrix-digital-rain"
version = "0.1.0"
edition = "2021"
[dependencies]
termion = "1.5.6"
rand = "0.8.5"
#![warn(clippy::pedantic)] // make sure that clippy is even more annoying
use rand::prelude::{ThreadRng, SliceRandom};
use rand::{thread_rng, Rng};
use termion::{color::Rgb};
use termion::input::TermRead;
use termion::raw::IntoRawMode;
use std::sync::mpsc::{channel, TryRecvError};
use std::thread;
use std::{io::{Write, stdout, stdin}, iter::repeat, time::Duration};
/// Character pool to pick from
/// If your terminal is struggling with that choice, replace them by ordinary latin characters.
/// There's currently no nicer way to initialize constant array in Rust
const CHARS: [char; 322] = [
'M', 'Ї', 'Љ', 'Њ', 'Ћ', 'Ќ', 'Ѝ', 'Ў', 'Џ', 'Б', 'Г', 'Д', 'Ж', 'И', 'Й', 'Л', 'П', 'Ф', 'Ц', 'Ч', 'Ш', 'Щ', 'Ъ',
'Ы', 'Э', 'Ю', 'Я', 'в', 'д', 'ж', 'з', 'и', 'й', 'к', 'л', 'м', 'н', 'п', 'т', 'ф', 'ц', 'ч', 'ш', 'щ', 'ъ', 'ы',
'ь', 'э', 'ю', 'я', 'ѐ', 'ё', 'ђ', 'ѓ', 'є', 'ї', 'љ', 'њ', 'ћ', 'ќ', 'ѝ', 'ў', 'џ', 'Ѣ', 'ѣ', 'ѧ', 'Ѯ', 'ѱ', 'Ѳ',
'ѳ', 'ҋ', 'Ҍ', 'ҍ', 'Ҏ', 'ҏ', 'Ґ', 'ґ', 'Ғ', 'ғ', 'Ҕ', 'ҕ', 'Җ', 'җ', 'Ҙ', 'ҙ', 'Қ', 'қ', 'ҝ', 'ҟ', 'ҡ', 'Ң', 'ң',
'Ҥ', 'ҥ', 'ҩ', 'Ҫ', 'ҫ', 'Ҭ', 'ҭ', 'Ұ', 'ұ', 'Ҳ', 'ҳ', 'ҵ', 'ҷ', 'ҹ', 'Һ', 'ҿ', 'Ӂ', 'ӂ', 'Ӄ', 'ӄ', 'ӆ', 'Ӈ', 'ӈ',
'ӊ', 'Ӌ', 'ӌ', 'ӎ', 'Ӑ', 'ӑ', 'Ӓ', 'ӓ', 'Ӕ', 'ӕ', 'Ӗ', 'ӗ', 'Ә', 'ә', 'Ӛ', 'ӛ', 'Ӝ', 'ӝ', 'Ӟ', 'ӟ', 'ӡ', 'Ӣ', 'ӣ',
'Ӥ', 'ӥ', 'Ӧ', 'ӧ', 'Ө', 'ө', 'Ӫ', 'ӫ', 'Ӭ', 'ӭ', 'Ӯ', 'ӯ', 'Ӱ', 'ӱ', 'Ӳ', 'ӳ', 'Ӵ', 'ӵ', 'Ӷ', 'ӷ', 'Ӹ', 'ӹ', 'Ӻ',
'ӽ', 'ӿ', 'Ԁ', 'ԍ', 'ԏ', 'Ԑ', 'ԑ', 'ԓ', 'Ԛ', 'ԟ', 'Ԧ', 'ԧ', 'Ϥ', 'ϥ', 'ϫ', 'ϭ', 'ゥ', 'ェ', 'ォ', 'ャ', 'ュ', 'ョ', 'ッ',
'ー', 'ア', 'イ', 'ウ', 'エ', 'オ', 'カ', 'キ', 'ク', 'ケ', 'コ', 'サ', 'シ', 'ス', 'セ', 'ソ', 'タ', 'チ', 'ツ', 'テ', 'ト', 'ナ', 'ニ',
'ヌ', 'ネ', 'ノ', 'ハ', 'ヒ', 'フ', 'ヘ', 'ホ', 'マ', 'ミ', 'ム', 'メ', 'モ', 'ヤ', 'ユ', 'ヨ', 'ラ', 'リ', 'ル', 'レ', 'ロ', 'ワ', 'ン',
'ⲁ', 'Ⲃ', 'ⲃ', 'Ⲅ', 'Γ', 'Δ', 'Θ', 'Λ', 'Ξ', 'Π', 'Ѐ', 'Ё', 'Ђ', 'Ѓ', 'Є', 'ⲉ', 'Ⲋ', 'ⲋ', 'Ⲍ', 'ⲍ', 'ⲏ', 'ⲑ', 'ⲓ',
'ⲕ', 'ⲗ', 'ⲙ', 'ⲛ', 'Ⲝ', 'ⲝ', 'ⲡ', 'ⲧ', 'ⲩ', 'ⲫ', 'ⲭ', 'ⲯ', 'ⳁ', 'Ⳉ', 'ⳉ', 'ⳋ', 'ⳤ', '⳥', '⳦', '⳨', '⳩', '∀', '∁',
'∂', '∃', '∄', '∅', '∆', '∇', '∈', '∉', '∊', '∋', '∌', '∍', '∎', '∏', '∐', '∑', '∓', 'ℇ', 'ℏ', '℥', 'Ⅎ', 'ℷ', '⩫',
'⨀', '⨅', '⨆', '⨉', '⨍', '⨎', '⨏', '⨐', '⨑', '⨒', '⨓', '⨔', '⨕', '⨖', '⨗', '⨘', '⨙', '⨚', '⨛', '⨜', '⨝', '⨿', '⩪',
];
/// convert a brightness value to a green-ish gradient color
fn color(brightness: u8) -> Rgb {
let v = f32::from(brightness) / 255.0;
let r = v.powi(7);
let g = v.powi(1);
let b = v.powi(4);
// r, g, b will be in 0.0..=1.0 so there's no risk of exceeding the u8's range
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
Rgb ((r * 255.0).round() as u8, (g * 255.0).round() as u8, (b * 255.0).round() as u8)
}
/// A single character on the screen with its current brightness
#[derive(Clone, Copy)]
struct Symbol {
char: char,
brightness: u8,
}
/// Start with a black space by default
impl Default for Symbol {
fn default() -> Self {
Self { char: ' ', brightness: 0 }
}
}
impl Symbol {
/// output the colored symbol at the current cursor position
fn print<W: Write>(self, out: &mut W) {
write!(out, "{}{}", termion::color::Fg(color(self.brightness)), self.char).unwrap();
}
/// reduce the brightness of the symbol by a certain amount and make sure the value doesn't underrun
fn darken(&mut self) {
self.brightness = self.brightness.saturating_sub(10);
}
/// replace the character for this symbol and bring it to full brightness
fn set(&mut self, char: char) {
self.char = char;
self.brightness = 255;
}
}
/// a single column of symbols
#[derive(Clone)]
struct Column {
symbols: Vec<Symbol>,
}
impl Column {
/// create a new column with a given height
fn new(height: usize) -> Self {
Self {
symbols: vec![Symbol::default(); height],
}
}
/// print out a single colored symbol of this column
fn print_symbol<W: Write>(&self, out: &mut W, row: usize) {
self.symbols[row].print(out);
}
/// reduce the brightness of the entire column
fn darken(&mut self) {
self.symbols.iter_mut().for_each(Symbol::darken);
}
fn set(&mut self, row: usize, char: char) {
self.symbols[row].set(char);
}
}
/// Current position of a _falling symbol_
struct Droplet {
/// For the start of the animation we want to be able to place the symbol _above_ the screen,
/// that's we need negative row values as well.
row: isize,
col: usize,
}
impl Droplet {
/// create a new Droplet at a random location somewhere above the actual screen
fn new_random(rng: &mut ThreadRng, width: usize, height: usize) -> Self {
// the height of the terminal is expected lie within a sane range of this type
#[allow(clippy::cast_possible_wrap)]
Self {
row: -(rng.gen_range(0..height) as isize),
col: rng.gen_range(0..width),
}
}
/// move the droplet down by one row
/// if it hits the bottom row, move it back up to a random column
fn update(&mut self, width: usize, height: usize) {
self.row += 1;
// the height of the terminal is expected lie within a sane range of this type
#[allow(clippy::cast_possible_wrap)]
if self.row >= height as isize {
let mut rng = thread_rng();
self.col = rng.gen_range(0..width);
self.row = 0;
}
}
}
/// The entire screen filled with colored symbols
struct Screen {
width: usize,
height: usize,
columns: Vec<Column>,
droplets: Vec<Droplet>,
}
impl Screen {
/// create a new empty screen with the given dimensions
fn new(width: usize, height: usize) -> Self {
let mut rng = thread_rng();
Self {
width,
height,
columns: repeat(Column::new(height)).take(width).collect(),
droplets: (0..width).map(|_| Droplet::new_random(&mut rng, width, height)).collect(),
}
}
/// print the entire screen to the terminal
fn print<W: Write>(&self, out: &mut W) {
for row in 0..self.height {
for column in &self.columns {
column.print_symbol(out, row);
}
write!(out, "\r\n").unwrap();
}
}
// make all droplets fall down by one row
fn update_droplets(&mut self) {
let mut rng = thread_rng();
for droplet in &mut self.droplets {
droplet.update(self.width, self.height);
if let Ok(row) = droplet.row.try_into() {
let ch = CHARS.choose(&mut rng).copied().unwrap_or(' ');
self.columns[droplet.col].set(row, ch);
}
}
}
// reduce the brightness of all symbols in this screen
fn darken(&mut self) {
self.columns.iter_mut().for_each(Column::darken);
}
}
fn main() {
// create the screen with the terminal's dimensions (omit the last row to prevent auto-scrolling)
let (width, height) = termion::terminal_size().unwrap();
let mut screen = Screen::new(width as usize, height as usize - 1);
// create a channel which allows to send stuff between thread boundaries
let (tx, rx) = channel();
// spawn a new thread which will blockingly wait for a key to be pressed
thread::spawn(move || {
stdin().keys().next();
// send something down the channel to notify the main thread that a key has been pressed
tx.send(()).expect("Could not send signal on channel.");
});
// get write access to the terminal
let mut stdout = stdout().into_raw_mode().unwrap();
// clear the screen and hide the cursor
write!(stdout, "{}{}", termion::clear::All, termion::cursor::Hide).unwrap();
// continue while no key has been pressed (i.e. the notification channel is empty)
while rx.try_recv() == Err(TryRecvError::Empty) {
// move cursor to the top left and set background color to black
write!(stdout, "{}{}", termion::cursor::Goto(1, 1), termion::color::Bg(termion::color::Rgb(0, 0, 0))).unwrap();
// screen update
screen.print(&mut stdout);
screen.darken();
screen.update_droplets();
// make sure the terminal updates _now_
stdout.flush().unwrap();
// slow down animation
std::thread::sleep(Duration::from_millis(50));
}
// reset Terminal back to normal
write!(stdout, "{}", termion::style::Reset).unwrap();
write!(stdout, "{}", termion::clear::All).unwrap();
write!(stdout, "{}", termion::cursor::Goto(1, 1)).unwrap();
write!(stdout, "{}", termion::cursor::Show).unwrap();
}
You may also check:How to resolve the algorithm Copy a string step by step in the LC3 Assembly programming language
You may also check:How to resolve the algorithm Sorting algorithms/Shell sort step by step in the PARI/GP programming language
You may also check:How to resolve the algorithm Sort three variables step by step in the Ksh programming language
You may also check:How to resolve the algorithm Wieferich primes step by step in the Go programming language
You may also check:How to resolve the algorithm Loops/Break step by step in the AArch64 Assembly programming language