Matrix digital rain
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.
Common Lisp
Runs in the terminal (using the Ncurses C library and the croatoan Lisp wrapper).
<lang lisp> (defun matrix-digital-rain ()
(with-screen (scr :input-echoing nil :input-blocking nil :cursor-visibility nil) (let* ((width (.width scr)) (height (.height scr)) ;; start at a random height in each column. (positions (loop repeat width collect (random height))) ;; run each column at a random speed. (speeds (loop repeat width collect (random 4)))) ;; hit the q key to exit the main loop. (add-event-handler (scr #\q) 'exit-event-loop) (add-event-handler (scr nil) (lambda (win event) ;; generate a random ascii char (flet ((randch () (+ 64 (random 58)))) (loop for col from 0 to (1- width) do (loop repeat (nth col speeds) do ;; position of the first point in the current column (let ((pos (nth col positions))) (setf (.attributes win) '(:bold)) (setf (.color-pair win) '(:white :black)) (add win (randch) :y (mod pos height) :x col) (setf (.color-pair win) '(:green :black)) (add win (randch) :y (mod (- pos 1) height) :x col) (add win (randch) :y (mod (- pos 2) height) :x col) (setf (.attributes win) '()) (add win (randch) :y (mod (- pos 3) height) :x col) ;; overwrite the last char half the height from the first char. (add win #\space :y (mod (- pos (floor height 2)) height) :x col) (refresh win) ;; advance the current column (setf (nth col positions) (mod (+ pos 1) height)))))))) (setf (.frame-rate scr) 20) (run-event-loop scr))))
</lang>
- Sample output:
https://i.imgur.com/17b36O3.png
Go
This is a translation of the C code here which uses the ncurses library.
Rather than pressing Ctrl+C to stop the program, I've added code so that it stops automatically after 1 minute and restores the terminal to its original state. <lang go>package main
import (
gc "github.com/rthornton128/goncurses" "log" "math/rand" "time"
)
// Time between row updates in microseconds. // Controls the speed of the digital rain effect. const rowDelay = 40000
func main() {
start := time.Now() rand.Seed(time.Now().UnixNano())
// Characters to randomly appear in the rain sequence. chars := []byte("0123456789") totalChars := len(chars)
// Set up ncurses screen and colors. stdscr, err := gc.Init() if err != nil { log.Fatal("init", err) } defer gc.End()
gc.Echo(false) gc.Cursor(0)
if !gc.HasColors() { log.Fatal("Program requires a colour capable terminal") }
if err := gc.StartColor(); err != nil { log.Fatal(err) }
if err := gc.InitPair(1, gc.C_GREEN, gc.C_BLACK); err != nil { log.Fatal("InitPair failed: ", err) } stdscr.ColorOn(1) maxY, maxX := stdscr.MaxYX()
/* Create slices of columns based on screen width. */
// Slice containing the current row of each column. columnsRow := make([]int, maxX)
// Slice containing the active status of each column. // A column draws characters on a row when active. columnsActive := make([]int, maxX)
// Set top row as current row for all columns. for i := 0; i < maxX; i++ { columnsRow[i] = -1 columnsActive[i] = 0 }
for { for i := 0; i < maxX; i++ { if columnsRow[i] == -1 { // If a column is at the top row, pick a // random starting row and active status. columnsRow[i] = rand.Intn(maxY + 1) columnsActive[i] = rand.Intn(2) } }
// Loop through columns and draw characters on rows. for i := 0; i < maxX; i++ { if columnsActive[i] == 1 { // Draw a random character at this column's current row. charIndex := rand.Intn(totalChars) stdscr.MovePrintf(columnsRow[i], i, "%c", chars[charIndex]) } else { // Draw an empty character if the column is inactive. stdscr.MovePrintf(columnsRow[i], i, "%c", ' ') }
columnsRow[i]++
// When a column reaches the bottom row, reset to top. if columnsRow[i] >= maxY { columnsRow[i] = -1 }
// Randomly alternate the column's active status. if rand.Intn(1001) == 0 { if columnsActive[i] == 0 { columnsActive[i] = 1 } else { columnsActive[i] = 0 } } } time.Sleep(rowDelay * time.Microsecond) stdscr.Refresh() elapsed := time.Since(start) // Stop after 1 minute. if elapsed.Minutes() >= 1 { break } }
}</lang>
Perl 6
Kind-of cheap and cheesy, but what the heck... Probably will only work in a POSIX compatible terminal. Runs until you hit ^C to exit.
The "lightning" effect is actually a bug, but I liked it so I kept it.
<lang perl6>signal(SIGINT).tap: { print "\e[H\e[J\e[?25h"; exit(0) }
my @codes = flat 'Α' .. 'Π', 'Ѐ' .. 'ѵ', 'Ҋ' .. 'ԯ', 'Ϣ' .. 'ϯ', 'ヲ'.. 'ン',
'Ⲁ' .. '⳩', '∀' .. '∗', '℀' .. '℺', '⨀' .. '⫿';
my ($rows,$cols) = qx/stty size/.words;
my @c = flat "\e[38;2;255;255;255m", (255,245 … 30).map({"\e[38;2;0;$_;0m"}),
"\e[38;2;0;25;0m" xx 75;
my $sz = +@c; my (@o, @s, @a); print "\e[?25l\e[48;5;232m"; init($rows, $cols);
my $delay; my ($r, $c);
loop {
if ++$delay %% 20 { ($r, $c) = qx/stty size/.words; init($r, $c) if $r != $rows or $c != $cols; $delay = 0 } print "\e[1;1H"; print join , (^@s).map: { @a[$_] = (@a[$_] + 1) % $sz; flat @c[@a[$_]], @s[$_] } @s[(^@s).pick] = @codes.roll for ^30;
} sub init ($r, $c) {
@s = @codes.roll($r * $c); ($rows, $cols) = $r, $c; my @o = (^@c).pick xx $cols; for ^$rows -> $row { @a[$row * $cols ..^ $row * $cols + $cols] = @o; @o = (^@o).map: {(@o[$_] - ($_ % 3)) % $sz}; }
}</lang>
- Sample output:
See matrix-digital-rain-perl6.png (offsite png image)
zkl
<lang zkl>var [const] codes=Walker.chain( // a bunch of UTF non ascii chars
[0x0391..0x03a0], [0x03a3..0x0475], [0x0400..0x0475], [0x048a..0x052f], [0x03e2..0x03ef], [0x2c80..0x2ce9], [0x2200..0x2217], [0x2100..0x213a], [0x2a00..0x2aff])
.apply(fcn(utf){ utf.toString(-8) }), // jeez this is lame
codeSz=codes.len(), // 970 c=L("\e[38;2;255;255;255m",[255..30,-15].apply("\e[38;2;0;%d;0m".fmt), (250).pump(List,T(Void,"\e[38;2;0;25;0m"))).flatten(), csz=c.len(); // 267, c is ANSI escape code fg colors: 38;2;<r;g;b>m
// query the ANSI terminal rows,cols := System.popen("stty size","r").readln().split().apply("toInt");
o,s,fg := buildScreen(rows,cols); ssz:=s.len();
print("\e[?25l\e[48;5;232m"); // hide the cursor, set background color to dark while(1){ // ignore screen resizes
print("\e[1;1H"); // move cursor to 1,1 foreach n in (ssz){ // print a screen full print( c[fg[n]], s[n] ); // forground color, character fg[n]=(fg[n] + 1)%csz; // fade to black } do(100){ s[(0).random(ssz)]=codes[(0).random(codeSz)] } // some new chars Atomic.sleep(0.1); // frame rate for my system, up to 200x41 terminal
}
fcn buildScreen(rows,cols){ // build a row major array as list
// s --> screen full of characters s:=(rows*cols).pump(List(), fcn{ codes[(0).random(codeSz)]}); // array fb-->( fg color, fg ..) where fg is an ANSI term 48;5;<n>m color fg:=List.createLong(s.len(),0); o:=csz.pump(List()).shuffle()[0,cols]; // cols random #s foreach row in (rows){ // set fg indices foreach col in (cols){ fg[row*cols + col] = o[col] } o=o.apply(fcn(n){ n-=1; if(n<0) n=csz-1; n%csz }); // fade out } return(o,s,fg);
}</lang> Offsite Image: Matrix rain dance