I'm working on modernizing Rosetta Code's infrastructure. Starting with communications. Please accept this time-limited open invite to RC's Slack.. --Michael Mol (talk) 20:59, 30 May 2020 (UTC)

Matrix digital rain

From Rosetta Code
Task
Matrix digital rain
You are encouraged to solve this task according to the task description, using any language you may know.

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.

Ada[edit]

Should work with ANSI terminals.

with Ada.Text_IO;
with Ada.Numerics.Discrete_Random;
with Ada.Strings.Fixed;
 
procedure Digital_Rain is
type Column_Type is range 0 .. 150;
type Row_Type is range 0 .. 25;
type Character_Range is new Character range 'A' .. 'Z';
subtype Delay_Range is Integer range 2 .. 6;
 
package Column_Random is new Ada.Numerics.Discrete_Random (Column_Type);
package Row_Random is new Ada.Numerics.Discrete_Random (Row_Type);
package Character_Random is new Ada.Numerics.Discrete_Random (Character_Range);
package Delay_Random is new Ada.Numerics.Discrete_Random (Delay_Range);
 
Column_Generator  : Column_Random.Generator;
Row_Generator  : Row_Random.Generator;
Character_Generator : Character_Random.Generator;
Delay_Generator  : Delay_Random.Generator;
 
Esc  : constant Character := Character'Val (8#33#);
Running : Boolean  := True;
Char  : Character;
use Ada.Text_IO;
 
protected Text_Out is
procedure Put_At (Row : Row_Type; Col : Column_Type;
Color : Integer; Item : Character);
end Text_Out;
 
protected body Text_Out is
procedure Put_At (Row : Row_Type; Col : Column_Type;
Color : Integer; Item : Character)
is
use Ada.Strings;
begin
Put (Esc & "[" &
Fixed.Trim (Row'Image, Left) & ";" &
Fixed.Trim (Col'Image, Left) & "H"); -- Place Cursor
Put (Esc & "[" & Fixed.Trim (Color'Image, Left) & "m"); -- Foreground color
Put (Item);
end Put_At;
end Text_Out;
 
task type Rainer;
 
task body Rainer is
Row : Row_Type;
Col : Column_Type;
Del : Delay_Range;
begin
Outer_Loop:
loop
Col := Column_Random.Random (Column_Generator);
Row := Row_Random.Random (Row_Generator);
for Rain in reverse Boolean loop
Del := Delay_Random.Random (Delay_Generator);
for R in 0 .. Row loop
if Rain then
if R in 1 .. Row then
Text_Out.Put_At (R - 1, Col, Color => 32, Item => Char);
end if;
Char := Character (Character_Random.Random (Character_Generator));
if R in 0 .. Row - 1 then
Text_Out.Put_At (R, Col, Color => 97, Item => Char);
end if;
else
Text_Out.Put_At (R, Col, Color => 30, Item => ' ');
end if;
delay 0.020 * Del;
exit Outer_Loop when not Running;
end loop;
end loop;
end loop Outer_Loop;
end Rainer;
 
Dummy  : Character;
Rainers : array (1 .. 50) of Rainer;
begin
Put (ESC & "[?25l"); -- Hide the cursor
Put (ESC & "[40m"); -- Black background
Put (ESC & "[2J"); -- Clear Terminal
 
Get_Immediate (Dummy);
Running := False;
delay 0.200;
 
Put (ESC & "[?25h"); -- Restore the cursor
Put (ESC & "[0;0H"); -- Place cursor
Put (ESC & "[39m"); -- Default foreground
Put (ESC & "[49m"); -- Default backgound
Put (ESC & "[2J"); -- Clear Terminal
end Digital_Rain;

Batch File[edit]

Adding more %RANDOM% will increase the number of columns.

 
@echo off
cls
color a
:start
echo %RANDOM%%RANDOM%%RANDOM%%RANDOM%%RANDOM%%RANDOM%%RANDOM%%RANDOM%%RANDOM%%RANDOM%%RANDOM%%RANDOM%%RANDOM%%RANDOM%%RANDOM%%RANDOM%
goto start
 

C[edit]

NCURSES version[edit]

Adaptation of Dan Ruscoe's C code. Header files added and code rearranged to suppress implicit declaration warnings.

Requires ncurses, install on Ubuntu as follows :

$ sudo apt install libncurses5-dev

Compile with ncurses flag :

$ cc digiRain.c -lncurses

And here's the code :

 
/**
* Loosely emulates the "digital rain" effect from The Matrix.
*
* @author Dan Ruscoe <[email protected]>
*/

#include <unistd.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <ncurses.h>
 
/* Time between row updates in microseconds.
Controls the speed of the digital rain effect.
*/

#define ROW_DELAY 40000
 
/**
* Gets a random integer within a given range.
*
* @param int min The low-end of the range.
* @param int max The high-end of the range.
*
* @return int The random integer.
*/

int get_rand_in_range(int min, int max)
{
return (rand() % ((max + 1) - min) + min);
}
 
int main(void)
{
/* Basic seed for random numbers. */
srand(time(NULL));
 
/* Characters to randomly appear in the rain sequence. */
char chars[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
 
int total_chars = sizeof(chars);
 
/* Set up ncurses screen and colors. */
initscr();
noecho();
curs_set(FALSE);
 
start_color();
init_pair(1, COLOR_GREEN, COLOR_BLACK);
attron(COLOR_PAIR(1));
 
int max_x = 0, max_y = 0;
 
getmaxyx(stdscr, max_y, max_x);
 
/* Create arrays of columns based on screen width. */
 
/* Array containing the current row of each column. */
int columns_row[max_x];
 
/* Array containing the active status of each column.
A column draws characters on a row when active.
*/

int columns_active[max_x];
 
int i;
 
/* Set top row as current row for all columns. */
for (i = 0; i < max_x; i++)
{
columns_row[i] = -1;
columns_active[i] = 0;
}
 
while (1)
{
for (i = 0; i < max_x; i++)
{
if (columns_row[i] == -1)
{
/* If a column is at the top row, pick a
random starting row and active status.
*/

columns_row[i] = get_rand_in_range(0, max_y);
columns_active[i] = get_rand_in_range(0, 1);
}
}
 
/* Loop through columns and draw characters on rows. */
for (i = 0; i < max_x; i++)
{
if (columns_active[i] == 1)
{
/* Draw a random character at this column's current row. */
int char_index = get_rand_in_range(0, total_chars);
mvprintw(columns_row[i], i, "%c", chars[char_index]);
}
else
{
/* Draw an empty character if the column is inactive. */
mvprintw(columns_row[i], i, " ");
}
 
columns_row[i]++;
 
/* When a column reaches the bottom row, reset to top. */
if (columns_row[i] >= max_y)
{
columns_row[i] = -1;
}
 
/* Randomly alternate the column's active status. */
if (get_rand_in_range(0, 1000) == 0)
{
columns_active[i] = (columns_active[i] == 0) ? 1 : 0;
}
}
 
usleep(ROW_DELAY);
refresh();
}
 
endwin();
return 0;
}
 

Microsoft Windows console version[edit]

Single threaded[edit]

/*******************************************************************************
*
* Digital ASCII rain - the single thread variant.
* 2012 (C) by Author, 2020 GPL licensed for RossetaCode
*
*******************************************************************************/

 
#include <assert.h> /* assertions */
#include <conio.h> /* console operations */
#include <locale.h> /* l10n and i18n */
#include <string.h> /* operations on strings and memory blocks */
#include <process.h> /* Microsoft Windows API for threads etc. */
#include <stdio.h> /* standard i/o library */
#include <stdlib.h> /* standard library with rand() function */
#include <time.h> /* time(NULL) used as srand(time(NULL)) */
#include <windows.h> /* just the Microsoft Windows main header for C */
 
 
/*
* Global variables - you could use local variables instead...
* but then e.g. the memory for the buffer variable would be unnecessarily
* allocated and released manifold, which would lead to a worse performance.
*/

 
HANDLE hStdOut; /* the handle to console "out" */
CONSOLE_SCREEN_BUFFER_INFO csbi; /* numbers of rows, columns etc. */
 
PCHAR_INFO buffer = NULL; /* an enough big buffer */
COORD bufferSize, bufferCoord; /* the size of buffer etc. */
 
 
/*
* A structure that holds data that is distinct for each column.
*/

struct Data
{
DWORD armed; /* time in ms to synchronize */
int delay; /* armed = current_time + delay */
BOOL show; /* TRUE - draw, FALSE - erase */
COORD ncp; /* position for drawing new characters */
SMALL_RECT s, d; /* source/destination regions for copy */
};
 
/*
* A function to generate random numbers from the range (a, b).
* NOTE: POSIX rand () is not thread safe,
* but we use secure rand() from MS runtime libraries.
*/

 
int rnd(int a, int b)
{
return a + rand() % (b + 1 - a);
}
 
BOOL randomShow(void)
{
return rnd(0, 99) < 65;
}
 
int randomDelay(void)
{
return rnd(0, 150) + rnd(0, 150) + rnd(0, 150);
}
 
BOOL randomRegenerate(void)
{
return rnd(0, 99) < 2;
}
 
void column_init(struct Data* data, int k)
{
data->armed = 0;
data->show = randomShow();
data->delay = randomDelay();
 
data->s.Left = k;
data->s.Top = 0;
data->s.Bottom = bufferSize.Y - 2;
data->s.Right = k;
 
data->d.Left = data->s.Left;
data->d.Top = data->s.Top + 1;
data->d.Right = data->s.Right;
data->d.Bottom = data->s.Bottom + 1;
 
data->ncp.X = k;
data->ncp.Y = 0;
}
 
 
void column_run(struct Data* data)
{
char c;
WORD a;
DWORD result;
 
/*
* Shift down a column.
*/

ReadConsoleOutput (hStdOut, buffer, bufferSize, bufferCoord, &data->s);
WriteConsoleOutput(hStdOut, buffer, bufferSize, bufferCoord, &data->d);
 
/*
* If show == TRUE then generate a new character.
* If show == FALSE write the space to erase.
*/

if(data->show)
{
c = (rnd(1,100) <= 15) ? ' ' : rnd( 'a', 'z' );
a = FOREGROUND_GREEN | ((rnd(1,100) > 10) ? 0 : FOREGROUND_INTENSITY);
}
else
{
c = ' ';
a = FOREGROUND_GREEN;
}
 
WriteConsoleOutputCharacter(hStdOut, &c, 1, data->ncp, &result);
WriteConsoleOutputAttribute(hStdOut, &a, 1, data->ncp, &result);
 
/*
* Randomly regenerate the delay and the visibility state of the column.
*/

if(randomRegenerate()) data->show = randomShow();
if(randomRegenerate()) data->delay = randomDelay();
 
data->armed = GetTickCount() + data->delay;
}
 
 
int main(int argc, char* argv[])
{
int j;
struct Data* table;
 
srand((unsigned int)time(NULL));
 
hStdOut = GetStdHandle( STD_OUTPUT_HANDLE );
SetConsoleTitle("Digital ASCII rain");
 
/*
* An attempt to run in the full-screen mode.
*/

if(argc == 1)
{
COORD coord;
CONSOLE_CURSOR_INFO cci;
cci.bVisible = FALSE;
cci.dwSize = 0;
SetConsoleDisplayMode(hStdOut, CONSOLE_FULLSCREEN_MODE, &coord);
SetConsoleCursorInfo(hStdOut, &cci);
}
 
GetConsoleScreenBufferInfo(hStdOut, &csbi );
//SetConsoleTextAttribute(hStdOut, FOREGROUND_GREEN);
 
bufferSize.X = 1;
bufferSize.Y = csbi.dwSize.Y - 1;
bufferCoord.X = 0;
bufferCoord.Y = 0;
buffer = (PCHAR_INFO)calloc(bufferSize.Y, sizeof(CHAR_INFO));
assert(buffer != NULL);
 
table = (struct Data*)calloc(csbi.dwSize.X, sizeof(struct Data));
assert(table != NULL);
 
for(j = 0; j < csbi.dwSize.X; j++) column_init(&table[j], j);
 
/*
* Main loop. Sleep(1) significally decreases the CPU load.
*/

 
while(!_kbhit())
{
DWORD t = GetTickCount();
for(j = 0; j < csbi.dwSize.X; j++)
if(table[j].armed < t) column_run(&table[j]);
Sleep(1);
}
 
free(table);
free(buffer);
 
return 0;
}

Multiple threads[edit]

/*******************************************************************************
*
* Digital ASCII rain - multithreaded.
* 2012 (C) by Author, 2020 GPL licensed for RossetaCode
*
*******************************************************************************/

 
#include <assert.h> /* assertions */
#include <conio.h> /* console operations */
#include <locale.h> /* l10n and i18n */
#include <string.h> /* operations on strings and memory blocks */
#include <process.h> /* Microsoft Windows API for threads etc. */
#include <stdio.h> /* standard i/o library */
#include <stdlib.h> /* standard library with rand() function */
#include <time.h> /* time(NULL) used as srand(time(NULL)) */
#include <windows.h> /* just the Microsoft Windows main header for C */
 
 
//#define SYNCHRONIZED_INIT
 
 
/*
* Global variables, common for all threads.
* The handle eventThreadInitialized is to synchronize threads initializations.
*/

#ifdef SYNCHRONIZED_INIT
HANDLE eventThreadInitialized;
#endif
 
BOOL loop = TRUE; /* FALSE - przerwać działanie */
HANDLE hStdOut; /* uchwyt do konsoli "out" */
CONSOLE_SCREEN_BUFFER_INFO csbi; /* liczby wierszy i kolumn itd.*/
 
 
struct InitThreadData
{
int column;
unsigned seed;
};
 
 
/*
* A function to generate random numbers from the range (a, b).
* NOTE: POSIX rand () is not thread safe,
* but we use secure rand() from MS runtime libraries.
*/

 
int rnd(int a, int b)
{
return a + rand() % (b + 1 - a);
}
 
BOOL randomShow(void)
{
return rnd(0, 99) < 65;
}
 
int randomDelay(void)
{
return rnd(0,150) + rnd(0,150) + rnd(0,150);
}
 
BOOL randomRegenerate(void)
{
return rnd(0,99) < 2;
}
 
 
/*
* Update a single column.
*/

void one_column(void *arg)
{
BOOL show;
int k;
char c;
WORD a;
int delay;
PCHAR_INFO buffer;
COORD bufferSize, bufferCoord, newCharPosition;
SMALL_RECT s, d;
DWORD result;
 
/*
* Retrieve the column number, initialize the (pseudo)random numbers
* generator with a "private" seed, check assertions.
*/

k = ((struct InitThreadData*)arg)->column;
srand(((struct InitThreadData*)arg)->seed);
assert(csbi.dwSize.X != 0 && csbi.dwSize.Y != 0);
assert(0 <= k && k <= csbi.dwSize.X);
 
show = randomShow();
delay = randomDelay();
 
bufferSize.X = 1;
bufferSize.Y = csbi.dwSize.Y - 1;
bufferCoord.X = 0;
bufferCoord.Y = 0;
 
buffer = (PCHAR_INFO)malloc(bufferSize.Y * sizeof(CHAR_INFO));
assert( buffer != NULL );
 
s.Left = k;
s.Top = 0;
s.Bottom = bufferSize.Y - 2;
s.Right = k;
 
d.Left = s.Left;
d.Top = s.Top + 1;
d.Right = s.Right;
d.Bottom = s.Bottom + 1;
 
newCharPosition.X = k;
newCharPosition.Y = 0;
 
#ifdef SYNCHRONIZED_INIT
SetEvent(eventThreadInitialized);
#endif
 
/*
* Main loop inside this one thread.
*/

while(loop)
{
ReadConsoleOutput ( hStdOut, buffer, bufferSize, bufferCoord, &s);
WriteConsoleOutput( hStdOut, buffer, bufferSize, bufferCoord, &d);
 
if(show)
{
c = (rnd(1,100) <= 15) ? ' ' : rnd( 'a', 'z' );
a = FOREGROUND_GREEN | ((rnd(1,100) > 1) ? 0 : FOREGROUND_INTENSITY);
}
else
{
c = ' ';
a = FOREGROUND_GREEN;
}
 
WriteConsoleOutputCharacter( hStdOut, &c, 1, newCharPosition, &result );
WriteConsoleOutputAttribute( hStdOut, &a, 1, newCharPosition, &result );
 
if(randomRegenerate()) show = randomShow();
if(randomRegenerate()) delay = randomDelay();
 
Sleep( delay );
}
 
free(buffer);
}
 
 
int main(int argc, char* argv[])
{
int j;
struct InitThreadData *init = NULL;
 
srand((time_t)time(NULL));
 
hStdOut = GetStdHandle( STD_OUTPUT_HANDLE );
SetConsoleTitle("MATRIX");
 
if(argc == 1)
{
COORD coord;
CONSOLE_CURSOR_INFO cci;
 
cci.bVisible = FALSE;
cci.dwSize = 0;
SetConsoleDisplayMode(hStdOut,CONSOLE_FULLSCREEN_MODE,&coord);
SetConsoleCursorInfo(hStdOut,&cci);
}
 
GetConsoleScreenBufferInfo( hStdOut, &csbi );
//SetConsoleTextAttribute(hStdOut,FOREGROUND_GREEN);
 
#ifdef SYNCHRONIZED_INIT
eventThreadInitialized = CreateEvent(NULL,FALSE,FALSE,NULL);
assert( eventThreadInitialized != NULL );
#endif
 
init = (struct InitThreadData*)
malloc(csbi.dwSize.X * sizeof(struct InitThreadData));
assert( init != NULL );
 
for(j = 0; j < csbi.dwSize.X; j++)
{
init[j].column = j;
init[j].seed = rand();
_beginthread(one_column, 0, (void*)&init[j]);
#ifdef SYNCHRONIZED_INIT
WaitForSingleObject(eventThreadInitialized,INFINITE);
#endif
}
 
getchar();
free(init);
 
#ifdef SYNCHRONIZED_INIT
CloseHandle(eventThreadInitialized);
#endif SYNCHRONIZED_INIT
 
return 0;
}

Fibers[edit]

/*******************************************************************************
*
* Digital ASCII rain - multithreaded.
* 2012 (C) by Author, 2020 GPL licensed for RossetaCode
*
*******************************************************************************/

 
#include <assert.h> /* assertions */
#include <conio.h> /* console operations */
#include <locale.h> /* l10n and i18n */
#include <string.h> /* operations on strings and memory blocks */
#include <process.h> /* Microsoft Windows API for threads etc. */
#include <stdio.h> /* standard i/o library */
#include <stdlib.h> /* standard library with rand() function */
#include <time.h> /* time(NULL) used as srand(time(NULL)) */
#include <windows.h> /* just the Microsoft Windows main header for C */
 
 
/*
* Global variables, shared by fibers.
*/

 
BOOL loop = TRUE;
HANDLE hStdOut;
CONSOLE_SCREEN_BUFFER_INFO csbi;
 
LPVOID mainFiber = NULL;
 
struct WorkingFiber
{
int column;
DWORD callAfter;
unsigned seed;
LPVOID fiber;
} *workingFibersTable = NULL;
 
/*
* A function to generate random numbers from the range (a, b).
* NOTE: POSIX rand () is not thread safe,
* but we use secure rand() from MS runtime libraries.
*/

 
int rnd(int a, int b)
{
return a + rand() % (b + 1 - a);
}
 
BOOL randomShow(void)
{
return rnd(0, 99) < 65;
}
 
int randomDelay(void)
{
return rnd(0,150) + rnd(0,150) + rnd(0,150);
}
 
BOOL randomRegenerate(void)
{
return rnd(0,99) < 2;
}
 
 
VOID CALLBACK one_column(void *arg)
{
struct WorkingFiber *ptr;
BOOL show;
int k;
char c;
WORD a;
int delay;
PCHAR_INFO buffer;
COORD bufferSize, bufferCoord, newCharPosition;
SMALL_RECT s, d;
DWORD result;
 
ptr = (struct WorkingFiber*)arg;
k = ptr->column;
ptr->callAfter = 0;
srand(ptr->seed);
assert(csbi.dwSize.X != 0 && csbi.dwSize.Y != 0);
assert(0 <= k && k <= csbi.dwSize.X);
 
show = randomShow();
delay = randomDelay();
 
bufferSize.X = 1;
bufferSize.Y = csbi.dwSize.Y - 1;
bufferCoord.X = 0;
bufferCoord.Y = 0;
 
buffer = (PCHAR_INFO)malloc(bufferSize.Y * sizeof(CHAR_INFO));
assert( buffer != NULL );
 
s.Left = k;
s.Top = 0;
s.Bottom = bufferSize.Y - 2;
s.Right = k;
 
d.Left = s.Left;
d.Top = s.Top + 1;
d.Right = s.Right;
d.Bottom = s.Bottom + 1;
 
newCharPosition.X = k;
newCharPosition.Y = 0;
 
while(loop)
{
ReadConsoleOutput ( hStdOut, buffer, bufferSize, bufferCoord, &s);
WriteConsoleOutput( hStdOut, buffer, bufferSize, bufferCoord, &d);
 
if(show)
{
c = (rnd(1,100) <= 15) ? ' ' : rnd( 'a', 'z' );
a = FOREGROUND_GREEN | ((rnd(1,100) > 1) ? 0 : FOREGROUND_INTENSITY);
}
else
{
c = ' ';
a = FOREGROUND_GREEN;
}
 
WriteConsoleOutputCharacter( hStdOut, &c, 1, newCharPosition, &result );
WriteConsoleOutputAttribute( hStdOut, &a, 1, newCharPosition, &result );
 
if(randomRegenerate()) show = randomShow();
if(randomRegenerate()) delay = randomDelay();
 
ptr->callAfter = GetTickCount() + delay;
SwitchToFiber(mainFiber);
}
 
free(buffer);
}
 
 
int main(int argc, char* argv[])
{
int j;
 
srand((unsigned int)time(NULL));
 
hStdOut = GetStdHandle( STD_OUTPUT_HANDLE );
SetConsoleTitle("MATRIX - FIBERS");
 
if(argc == 1)
{
COORD coord;
CONSOLE_CURSOR_INFO cci;
 
cci.bVisible = FALSE;
cci.dwSize = 0;
SetConsoleDisplayMode(hStdOut,CONSOLE_FULLSCREEN_MODE,&coord);
SetConsoleCursorInfo(hStdOut,&cci);
}
 
GetConsoleScreenBufferInfo( hStdOut, &csbi );
//SetConsoleTextAttribute(hStdOut,FOREGROUND_GREEN);
 
mainFiber = ConvertThreadToFiber(NULL);
assert( mainFiber != NULL );
 
workingFibersTable = (struct WorkingFiber*)
calloc(csbi.dwSize.X, sizeof(struct WorkingFiber));
assert( workingFibersTable != NULL );
 
for(j = 0; j < csbi.dwSize.X; j++)
{
workingFibersTable[j].column = j;
workingFibersTable[j].callAfter = 0;
workingFibersTable[j].seed = rand();
workingFibersTable[j].fiber = CreateFiber( 0, one_column, &workingFibersTable[j] );
}
 
loop = TRUE;
while(!_kbhit())
{
DWORD t = GetTickCount();
for(j = 0; j < csbi.dwSize.X; j++)
if(workingFibersTable[j].callAfter < t)
SwitchToFiber( workingFibersTable[j].fiber );
Sleep(1);
}
 
loop = FALSE;
for(j = 0; j < csbi.dwSize.X; j++)
{
SwitchToFiber( workingFibersTable[j].fiber );
DeleteFiber(workingFibersTable[j].fiber);
}
 
free(workingFibersTable);
 
return 0;
}

Common Lisp[edit]

Works with: SBCL

Runs in the terminal (using the Ncurses C library and the croatoan Lisp wrapper).

 
(defun matrix-digital-rain ()
(with-screen (scr :input-echoing nil :input-blocking nil :cursor-visible 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))))
;; generate a random ascii char
(flet ((randch () (+ 64 (random 58))))
;; hit the q key to exit the main loop.
(bind scr #\q 'exit-event-loop)
(bind scr nil
(lambda (win event)
(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 (fgcolor win) :green)
(add win (randch) :y (mod pos height) :x col :fgcolor :white)
(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))))
 
Sample output:

https://i.imgur.com/17b36O3.png

Go[edit]

Library: goncurses

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.

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
}
}
}

Julia[edit]

The font used is based on Leonardo da Vinci's notebooks. The font is freely obtainable at https://www.wfonts.com/font/leonardos-mirrorwriting.

using Gtk, Colors, Cairo
import Base.iterate, Base.IteratorSize, Base.IteratorEltype
 
const caps = [c for c in "ABCDEFGHIJKLMNOPQRSTUVWXYZ"]
startfall() = rand() < 0.2
endfall() = rand() < 0.2
startblank() = rand() < 0.1
endblank() = rand() < 0.05
bechangingchar() = rand() < 0.03
 
struct RainChars chars::Vector{Char} end
Base.IteratorSize(s::RainChars) = Base.IsInfinite()
Base.IteratorEltype(s::RainChars) = Char
function Base.iterate(rain::RainChars, state = (true, false, 0))
c = '\0'
isfalling, isblank, blankcount = state
if isfalling # falling, so feed the column
if isblank
c = ' '
((blankcount += 1) > 12) && (isblank = endblank())
else
c = bechangingchar() ? '~' : rand(rain.chars)
isblank, blankcount = startblank(), 0
end
endfall() && (isfalling = false)
else
isfalling = startfall()
end
return c, (isfalling, isblank, blankcount)
end
 
function digitalrain()
mapwidth, mapheight, fontpointsize = 800, 450, 14
windowmaxx = div(mapwidth, Int(round(fontpointsize * 0.9)))
windowmaxy = div(mapheight, fontpointsize)
basebuffer = fill(' ', windowmaxy, windowmaxx)
bkcolor, rcolor, xcolor = colorant"black", colorant"green", colorant"limegreen"
 
columngenerators = [Iterators.Stateful(RainChars(caps)) for _ in 1:windowmaxx]
 
win = GtkWindow("Digital Rain Effect", mapwidth, mapheight) |>
(GtkFrame() |> (can = GtkCanvas()))
set_gtk_property!(can, :expand, true)
 
draw(can) do widget
ctx = Gtk.getgc(can)
select_font_face(ctx, "Leonardo\'s mirrorwriting", Cairo.FONT_SLANT_NORMAL,
Cairo.FONT_WEIGHT_BOLD)
set_font_size(ctx, fontpointsize)
set_source(ctx, bkcolor)
rectangle(ctx, 0, 0, mapwidth, mapheight)
fill(ctx)
set_source(ctx, rcolor)
for i in 1:size(basebuffer)[1], j in 1:size(basebuffer)[2]
move_to(ctx, j * fontpointsize * 0.9, i * fontpointsize)
c = basebuffer[i, j]
if c == '~'
set_source(ctx, xcolor)
show_text(ctx, String([rand(caps)]))
set_source(ctx, rcolor)
else
show_text(ctx, String([c]))
end
end
end
 
while true
for col in 1:windowmaxx
c = popfirst!(columngenerators[col])
if c != '\0'
basebuffer[2:end, col] .= basebuffer[1:end-1, col]
basebuffer[1, col] = c
end
end
draw(can)
Gtk.showall(win)
sleep(0.05)
end
end
 
digitalrain()
 

Locomotive Basic[edit]

10 mode 0:defint a-z:randomize time:ink 0,0:ink 1,26:ink 2,19:border 0
20 dim p(20):mm=5:dim act(mm):for i=1 to mm:act(i)=rnd*19+1:next
30 md=mm-2:dim del(md):for i=1 to md:del(i)=rnd*19+1:next
40 for i=1 to mm:x=act(i):locate x,p(x)+1:pen 1:print chr$(rnd*55+145);
50 if p(x)>0 then locate x,p(x):pen 2:print chr$(rnd*55+145);
60 p(x)=p(x)+1:if p(x)=25 then locate x,25:pen 2:print chr$(rnd*55+145);:p(x)=0:act(i)=rnd*19+1
70 next
80 for i=1 to md:x=del(i):locate x,p(x)+1:print " ";
90 p(x)=p(x)+1:if p(x)=25 then p(x)=0:del(i)=rnd*19+1
100 next
110 goto 40

The program above runs at an acceptable speed on a 4 MHz Z80, but can be made much faster by using the CPCBasic JavaScript emulator. CPCBasic also adds an 80x50 display mode ("mode 3") which real CPC hardware lacks. Changing screen size from 20x25 to 80x50 and adding a delay with "frame" in line 100 results in a far more impressive display in CPCBasic:

10 mode 3:defint a-z:randomize time:ink 0,0:ink 1,26:ink 2,19:border 0
20 dim p(80):mm=12:dim act(mm):for i=1 to mm:act(i)=rnd*79+1:next
30 md=mm-2:dim del(md):for i=1 to md:del(i)=rnd*79+1:next
40 for i=1 to mm:x=act(i):locate x,p(x)+1:pen 1:print chr$(rnd*55+145);
50 if p(x)>0 then locate x,p(x):pen 2:print chr$(rnd*55+145);
60 p(x)=p(x)+1:if p(x)=50 then locate x,50:pen 2:print chr$(rnd*55+145);:p(x)=0:act(i)=rnd*79+1
70 next
80 for i=1 to md:x=del(i):locate x,p(x)+1:print " ";
90 p(x)=p(x)+1:if p(x)=50 then p(x)=0:del(i)=rnd*79+1
100 next:frame
110 goto 40

Perl[edit]

Probably shouldn't, but until someone posts something better... here's something somewhat relevant I wrote back in 2006.

Follow the bouncing Neo!

#!/user/bin/perl
 
use strict;
use warnings;
use Tk;
 
my $delay = 50; # milliseconds
my $fade = 8; # number of characters to "fade"
my $base_color = '#004000'; # dark green
my $fontname = 'Times'; # Whatever
my $fontsize = 12; # point size
my $font = "{$fontname} $fontsize bold";
my @objects;
 
my ( $xv, $yv ) = ( 0, 0 );
 
my $top = MainWindow->new();
 
$top->geometry('800x600');
 
my $run = 1;
 
$top->protocol( 'WM_DELETE_WINDOW' => sub { $run = 0; } );
 
my @letters = ( 'A' .. 'Z', 'a' .. 'z', '0' .. '9' );
 
my $canvas = $top->Canvas(
-background => 'black'
)->pack(
-fill => 'both',
-expand => 'y'
);
 
my $testch = $canvas->createText(
100, 100,
-text => 'o',
-fill => 'black',
-font => $font
);
 
$top->update;
 
my @coords = $canvas->bbox($testch);
 
$canvas->delete($testch);
 
my $lwidth = $coords[2] - $coords[0];
my $lheight = ( $coords[3] - $coords[1] ) * .8;
my $cols = int $canvas->width / $lwidth;
my $rows = int $canvas->height / $lheight;
 
for my $y ( 0 .. $rows ) {
for my $x ( 0 .. $cols ) {
$objects[$x][$y] = $canvas->createText(
$x * $lwidth, $y * $lheight,
-text => $letters[ int rand @letters ],
-fill => $base_color,
-font => $font
);
}
}
 
my $neo_image = $top->Photo( -data => neo() );
 
my $neo = $canvas->createImage(
$canvas->width / 2,
$canvas->height / 2,
-image => $neo_image
);
 
while ($run) {
drop('Nothing Like The Matrix');
}
exit;
 
MainLoop;
 
sub drop {
my @phrase = split //, reverse shift;
my $x = int rand $cols;
my @orig;
for my $y ( 0 .. $rows ) {
$orig[$y] = $canvas->itemcget( $objects[$x][$y], '-text' );
}
for my $y ( 0 .. $rows + @phrase + $fade ) {
for my $letter ( 0 .. @phrase ) {
last if ( $y - $letter < 0 );
$canvas->itemconfigure(
$objects[$x][ $y - $letter ],
-text => $phrase[$letter],
-fill => "#00FF00"
);
}
if ( $y > @phrase ) {
$canvas->itemconfigure(
$objects[$x][ $y - @phrase ],
-text => $orig[ $y - @phrase ],
-fill => "#009000"
);
}
if ( $y > @phrase + 2 ) {
$canvas->itemconfigure( $objects[$x][ $y - @phrase - int ($fade / 2) ],
-fill => "#006000" );
$canvas->itemconfigure( $objects[$x][ $y - @phrase - $fade + 1 ],
-fill => $base_color );
}
last unless $run;
$top->after($delay);
neo_move();
$top->update;
}
}
 
sub neo_move {
$xv += ( ( rand 2 ) - 1 > 0 ) ? 1 : -1;
$yv += ( ( rand 2 ) - 1 > 0 ) ? 1 : -1;
my ( $x, $y ) = $canvas->coords($neo);
$xv = -$xv if ( ( $x < 0 ) or ( $x > $canvas->width ) );
$yv = -$yv if ( ( $y < 0 ) or ( $y > $canvas->height ) );
$canvas->move( $neo, $xv, $yv );
}
 
sub neo {
return '
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
8SLGjBo3cuzo8SPIkCJHkixp8iTKiQEBADs=
'
;
}
 

Another Perl Solution[edit]

#!/usr/bin/perl
 
use strict; # http://www.rosettacode.org/wiki/Matrix_Digital_Rain
use warnings;
use Tk;
use List::Util qw( shuffle );
 
my ($rows, $cols) = (20, 40);
my @text = shuffle 'a'..'z', 'A'..'Z';
my $n = 0;
 
my $mw = MainWindow->new;
$mw->geometry( '+850+300' );
my $c = $mw->Canvas( -width => $cols * 10, -height => $rows * 20,
-bg => 'black',
)->pack;
$c->Tk::bind('<ButtonRelease-1>' => sub { $mw->destroy } );
 
my @queue = [ 100, 0, 255, 'A' ]; # [ x, y, color, letter ]
$mw->after( 5, \&step );
MainLoop;
 
sub step
{
$c->delete('all');
my @new = [ 10 * int rand $cols, 0, 255, $text[++$n % @text] ];
for ( @queue )
{
my $color = $_->[2] == 255 ? '#ffffff' : sprintf '#00%02x00', $_->[2];
$c->createText($_->[0], $_->[1],
-font => '10x20', -text => $_->[3], -fill => $color );
$_->[2] == 255 and push @new, [ $_->[0], $_->[1] + 20, 255, $_->[3] ];
$_->[2] -= 13;
}
@queue = grep $_->[2] > 0 && $_->[1] < $rows * 20, @queue, @new;
$mw->after( 63, \&step );
}

Phix[edit]

Library: Phix/pGUI
-- demo\rosetta\Matrix_Digital_Rain.exw
sequence sushii = {}, -- w x h of unicode char strings
colours, -- """ of their fading colours
droplets -- w column droplets, or zeroes
 
include pGUI.e
 
Ihandle dlg, canvas, timer
cdCanvas cddbuffer, cdcanvas
 
procedure rain(integer w,h)
for x=1 to w do
integer y = droplets[x]
if y or rand(40)=1 then
if y<h then
droplets[x] = y+1
sushii[y+1][x] = utf32_to_utf8({0x30A0 + rand(96)})
colours[y+1][x] = CD_WHITE
end if
if y then
bool clear_droplet = true
if colours[y][x]=CD_WHITE then
colours[y][x] = #00F800 -- (CD_GREEN to nearest #800)
clear_droplet = false
y -= 1
end if
for y=y to 1 by -1 do
integer cy = colours[y][x]
if cy=0 then exit end if
clear_droplet = false
cy -= #000800
colours[y][x] = cy
end for
if clear_droplet then
droplets[x] = 0
end if
end if
end if
end for
end procedure
 
function redraw_cb(Ihandle /*ih*/, integer /*posx*/, integer /*posy*/)
integer {w,h} = IupGetIntInt(canvas, "DRAWSIZE"),
{dx,dy} = cdCanvasGetTextSize(cddbuffer, "W")
w = max(1,floor(w/dx))
h = max(1,floor(h/dy))
if length(sushii)!=h
or length(sushii[1])!=w then
sushii = repeat(repeat(" ",w),h)
colours = repeat(repeat(CD_BLACK,w),h)
droplets = repeat(0,w)
end if
cdCanvasActivate(cddbuffer)
cdCanvasClear(cddbuffer)
rain(w,h)
for x=1 to w do
for y=1 to h do
cdCanvasSetForeground(cddbuffer, colours[y][x])
cdCanvasText(cddbuffer,x*dx, (h-y)*dy, sushii[y][x])
end for
end for
cdCanvasFlush(cddbuffer)
return IUP_DEFAULT
end function
 
function timer_cb(Ihandle /*ih*/)
IupUpdate(canvas)
return IUP_IGNORE
end function
 
function map_cb(Ihandle ih)
cdcanvas = cdCreateCanvas(CD_IUP, ih)
cddbuffer = cdCreateCanvas(CD_DBUFFER, cdcanvas)
cdCanvasSetBackground(cddbuffer, CD_BLACK)
return IUP_DEFAULT
end function
 
procedure main()
IupOpen()
IupSetGlobal("UTF8MODE","YES")
 
canvas = IupCanvas(NULL)
IupSetAttribute(canvas, "RASTERSIZE", "640x480")
IupSetCallback(canvas, "MAP_CB", Icallback("map_cb"))
IupSetCallback(canvas, "ACTION", Icallback("redraw_cb"))
 
timer = IupTimer(Icallback("timer_cb"), 50)
 
dlg = IupDialog(canvas)
IupSetAttribute(dlg, "TITLE", "Wireworld")
IupCloseOnEscape(dlg)
 
IupShow(dlg)
IupSetAttribute(canvas, "RASTERSIZE", NULL)
IupMainLoop()
IupClose()
end procedure
main()

Python[edit]

 
import curses
import random
import time
 
"""
 
Based on C ncurses version
 
http://rosettacode.org/wiki/Matrix_Digital_Rain#NCURSES_version
 
"""

 
"""
Time between row updates in seconds
Controls the speed of the digital rain effect.
"""

 
ROW_DELAY=.0001
 
def get_rand_in_range(min, max):
return random.randrange(min,max+1)
 
try:
# Characters to randomly appear in the rain sequence.
chars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
 
total_chars = len(chars)
 
stdscr = curses.initscr()
curses.noecho()
curses.curs_set(False)
 
curses.start_color()
curses.init_pair(1, curses.COLOR_GREEN, curses.COLOR_BLACK)
stdscr.attron(curses.color_pair(1))
 
max_x = curses.COLS - 1
max_y = curses.LINES - 1
 
 
# Create arrays of columns based on screen width.
 
# Array containing the current row of each column.
 
columns_row = []
 
# Array containing the active status of each column.
# A column draws characters on a row when active.
 
columns_active = []
 
for i in range(max_x+1):
columns_row.append(-1)
columns_active.append(0)
 
while(True):
for i in range(max_x):
if columns_row[i] == -1:
# If a column is at the top row, pick a
# random starting row and active status.
columns_row[i] = get_rand_in_range(0, max_y)
columns_active[i] = get_rand_in_range(0, 1)
 
# Loop through columns and draw characters on rows
 
for i in range(max_x):
if columns_active[i] == 1:
# Draw a random character at this column's current row.
char_index = get_rand_in_range(0, total_chars-1)
#mvprintw(columns_row[i], i, "%c", chars[char_index])
stdscr.addstr(columns_row[i], i, chars[char_index])
else:
# Draw an empty character if the column is inactive.
#mvprintw(columns_row[i], i, " ");
stdscr.addstr(columns_row[i], i, " ");
 
 
columns_row[i]+=1
 
# When a column reaches the bottom row, reset to top.
if columns_row[i] >= max_y:
columns_row[i] = -1
 
# Randomly alternate the column's active status.
if get_rand_in_range(0, 1000) == 0:
if columns_active[i] == 0:
columns_active[i] = 1
else:
columns_active[i] = 0
 
time.sleep(ROW_DELAY)
stdscr.refresh()
 
except KeyboardInterrupt as err:
curses.endwin()
 

Racket[edit]

Translation of: Raku
#lang racket
 
(define codes '((Α Π) (Ѐ ѵ) (Ҋ ԯ) (Ϣ ϯ) (ヲ ン) (Ⲁ ⳩) (∀ ∗) (℀ ℺) (⨀ ⫿)))
 
(define (symbol->integer s) (char->integer (string-ref (symbol->string s) 0)))
(define (pick xs) (list-ref xs (random (length xs))))
 
(define glyphs
(map
integer->char
(append*
(for/list ([c (in-list codes)])
(range (symbol->integer (first c)) (add1 (symbol->integer (second c))))))))
 
(define palette (vector-append (vector "\e[38;2;255;255;255m")
(for/vector ([n (in-range 245 29 -10)])
(format "\e[38;2;0;~a;0m" n))
(make-vector 75 "\e[38;2;0;25;0m")))
 
(match-define (list (app (compose1 sub1 string->number) rows)
(app string->number cols))
(string-split (with-output-to-string (thunk (system "stty size")))))
 
(define screen (for/vector ([_ (in-range (* rows cols))]) (pick glyphs)))
(define offsets (for/vector ([col cols]) (random (vector-length palette))))
 
(display "\e[?25l\e[48;5;232m") ; hide the cursor, set the background color
 
(define (main)
(for ([iter (in-naturals)])
(sleep 0.1)
(display "\e[1;1H") ; reset cursor to top left
(for ([i (in-range 30)]) (vector-set! screen (random (* rows cols)) (pick glyphs)))
(for ([i (in-range rows)])
(for ([j (in-range cols)])
(display (vector-ref palette (modulo (+ (- i) iter (vector-ref offsets j))
(vector-length palette))))
(display (vector-ref screen (+ (* cols i) j))))
(display "\n"))))
 
(with-handlers ([exn:break? (thunk*
 ; reset ANSI codes, reshow cursor, clear screen
(display "\e[0m")
(display "\e[H\e[J\e[?25h"))])
(main))

Raku[edit]

(formerly Perl 6)

Works with: Rakudo version 2018.11

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.

# clean up on exit, reset ANSI codes, scroll, re-show the cursor & clear screen
signal(SIGINT).tap: { print "\e[0m", "\n" xx 50, "\e[H\e[J\e[?25h"; exit(0) }
 
# a list of glyphs to use
my @codes = flat 'Α' .. 'Π', 'Ѐ' .. 'ѵ', 'Ҋ' .. 'ԯ', 'Ϣ' .. 'ϯ', 'ヲ'.. 'ン',
'Ⲁ' .. '⳩', '∀' .. '∗', '℀' .. '℺', '⨀' .. '⫿';
 
# palette of gradient ANSI foreground colors
my @palette = flat "\e[38;2;255;255;255m", (255,24530).map({"\e[38;2;0;$_;0m"}),
"\e[38;2;0;25;0m" xx 75;
 
my @screen; # buffer to hold glyphs
my @rotate; # palette rotation position buffer
 
my ($rows, $cols) = qx/stty size/.words; # get the terminal size
init($rows, $cols); # set up the screen buffer and palette offsets
 
my $size-check;
 
print "\e[?25l\e[48;5;232m"; # hide the cursor, set the background color
 
loop {
if ++$size-check %% 20 { # periodically check for
my ($r, $c) = qx/stty size/.words; # resized terminal and
init($r, $c) if $r != $rows or $c != $cols; # re-initialize screen buffer
$size-check = 0
}
print "\e[1;1H"; # set cursor to top left
print join '', (^@screen).map: {
@rotate[$_] = (@rotate[$_] + 1) % +@palette; # rotate the palettes
flat @palette[@rotate[$_]], @screen[$_] # and print foreground, glyph
}
@screen[(^@screen).pick] = @codes.roll for ^30; # replace some random glyphs
}
 
sub init ($r, $c) {
@screen = @codes.roll($r * $c);
($rows, $cols) = $r, $c;
my @offset = (^@palette).pick xx $cols;
for ^$rows -> $row {
@rotate[$row * $cols ..^ $row * $cols + $cols] = @offset;
# for no "lightning" effect, add '1 + ' ↓ here: (1 + $_ % 3)
@offset = (^@offset).map: {(@offset[$_] - ($_ % 3)) % +@palette};
}
}
Sample output:

See matrix-digital-rain-perl6.png (offsite png image)

REXX[edit]

Digital rain note:   This REXX program favors the use of Latin letters   (both lower and uppercase)   letters over all others characters (glyphs) by a 25% factor.

/*REXX program creates/displays Matrix (the movie) digital rain; favors non-Latin chars.*/
signal on halt /*allow the user to halt/stop this pgm.*/
parse arg pc seed . /*obtain optional arguments from the CL*/
if pc=='' | pc=="," then pc= 20 /*Not specified? Then use the default.*/
if datatype(seed, 'W') then call random ,,seed /*Numeric? Use seed for repeatability.*/
parse value scrsize() with sd sw . /*obtain the dimensions of the screen. */
if sd==0 then sd= 54; sd= sd - 2 + 1 /*Not defined? Then use default; adjust*/
if sw==0 then sw= 80; sw= sw - 1 /* " " " " " " */
lowC= c2d(' ') + 1 /*don't use any characters ≤ a blank.*/
@.= ' ' /*PC is the % new Matric rain streams.*/
cloud= copies(@., sw) /*the cloud, where matrix rain is born.*/
cls= 'CLS' /*DOS command used to clear the screen.*/
do forever; call nimbus /*define bottom of cloud (the drops). */
call rain /*generate rain, display the raindrops.*/
end /*j*/
halt: exit /*stick a fork in it, we're all done. */
/*──────────────────────────────────────────────────────────────────────────────────────*/
rain: do a=sd by -1 for sd-1; _= a-1; @.a= @._; end; call fogger; return
show: cls; @.1= cloud; do r=1 for sd; say strip(@.r, 'T'); end /*r*/; return
/*──────────────────────────────────────────────────────────────────────────────────────*/
nimbus: if random(, 100)<pc then call mist /*should this be a new rain stream ? */
else call unmist /*should any of the rain streams cease?*/
return /*note: this subroutine may pass──►MIST*/
if random(, 100)<pc then return /*should this be a new rain stream ? */
 ?= random(1, sw) /*pick a random rain cloud position. */
if substr(cloud,?,1)\==' ' then return /*Is cloud position not a blank? Return*/
/*───── ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ────────────────────────────────────────────────────*/
mist:  ?= random(1, sw) /*obtain a random column in cloud. */
if substr(cloud,?,1)\==' ' then return /*if this stream is active, return. */
if random(, 100)<pc then return /*should this be a new rain stream ? */
cloud= overlay(drop(), cloud, ?) /*seed cloud with new matrix rain drop.*/
return
/*──────────────────────────────────────────────────────────────────────────────────────*/
unmist: ?= random(1, sw) /*obtain a random column in cloud. */
if substr(cloud,?,1) ==' ' then return /*if this stream is dry, return. */
if random(, 100)>pc then return /*should this be a new dry stream ? */
cloud= overlay(' ', cloud, ?); return /*seed cloud with new matrix rain drop.*/
/*──────────────────────────────────────────────────────────────────────────────────────*/
drop: Lat= random(1, 4) /*Now, chose a matrix rain stream char.*/
tChr= 254; if Lat==1 then tChr= 127 /*choose the type of rain stream char.*/
return d2c( random(lowC, tChr) ) /*Lat = 1? This favors Latin letters.*/
/*──────────────────────────────────────────────────────────────────────────────────────*/
fogger: do f=1 for sw /*display a screen full of rain streams*/
if substr(cloud, f, 1) \== ' ' then cloud= overlay( drop(), cloud, f)
end /*f*/; call show; return /* [↑] if raindrop, then change drop. */

Programming note:

The use of      random(, 100)     is
the same as   random(0, 100)     (inclusive).


This REXX program makes use of   SCRSIZE   REXX program (or BIF) which is used to determine the screen
width and depth of the terminal (console).   Some REXXes don't have this BIF.

The   SCRSIZE.REX   REXX program is included here   ───►   SCRSIZE.REX.


output   when using the default input:

(A screen snapshot shown at half size.)

              ░      O       3              k       ò         cº     Θ     &           F
              ~      Ö       ¡              )       Å         tπ     ÷     ç           ╛
              a      ±       █              q       L         P}     8     ╕           y
              Ü      d       |              Γ       1         P»     ≥     ╙           ù
              ╖      ¥       9              ╠       ▒         SÉ     k     ╟           }
              o      U       '              Æ       í         (N     E     C           0
              e      2       ╜              ─       ₧         bH     ï     Z           ╢
              o      å       -              S       2         ò┘     ║     P           ╖
              s      @       ê              ≈       ╝         ·v     F     4           û
              Æ      k       ┤              .ÿ      s         ╕@     b     U           ^
              9      ╩       «              ┐·      w         ╛-     e     U           =
              ≈      a       ┬              δu      ┬         ît     ╤     ╖           σ
              N      Ä       F              'N      @         ╒R     φ     O
              ô      @       Y              ÷x      X         ∩P     ¡     8
              9      ║       ╔              #■      ▌         d%     v     «
              ô      m       I              xm      :         Um     E     &
              ╚      »       r              >       ~         [email protected]     /     ┤
              ⌂      k       E              1       ╜         Wc     J     s
              8      g       E              m       ƒ         Ωε     q     U
              V      ë       &              |       ï         ■~     ≥     X
              à      Ö       .              î       3         ₧=     ⌂     ±
              π      ì       è              n       ┬         ·▌     Z     W
              ¢      6       n              S       £         [x     £     {
              '      &       ?              ╖       )         jÑ     ╞     α
              î      ╔       x              F       ╨         ☺▌     7     ┘
              á      ë       j              *       -         rⁿ     ú     )
              '      S       ╥              ∙       E         v≡     o     ú
              .      <       V              ²       °         ]G     7     H
              p      8       ╣              o       ╤         FÇ     S      
              ┴      t       ö              ^       ╣         n╟     ╕     >
              *      æ       ò              ╡       4         d_     ├     «
              ╔      â       -              Q       P         un     ≥     M
              6      ²       ╢              ╔       E         ≤A     °     *
              /      :       q              L       R         cë     %     ≡
              3      x       $              ⌡       ¥         .Ω     E     :
              E      ┤       y              i       U         Z      ú     O
              ⌂      Σ                      .       ¥         }      Ä     °
              V      φ                              ≤         ╙      ╫     j
              }      à                      ?       3         '      ≤     .
              í      ▐                      ╞       =         ~      Ä     ç
              B      î                      ö                 (      ¬     ╘
              ┬      [                      6                 ═      ╡     ▌
              r      %                      f                 ╔      [     C
         _    {      è                      %                 ╫      ▒     Z
         1    ≈      ]                      !                 -      ╨     o        ¢
              ?      ÿ                      9                 Å      e     ß        σ
         µ    ■      α                      Z                 h      4     ⌠        4
         ñ    ╙      2                      8                 ;      2     ╣        !
         J    Ñ      :                      ~                 █      ª     ░        I
         ╥    í      ┴                      c                 !      U     ;        [
         Ω    Ç      ╫                      N                 W      ⌂     $        ╨
         ì    +      ₧                      _                 v      k     ò        X
         c    s      ¿                      3                 %      ½     F        ╠
         x    Γ      p                      ?                 £      S     ç        -
         g           /                      ╡                 :      é     æ        ^
         √           ⌐                      0                 Ü      .     9        Γ
         ⌡           ╕                      w                 0      î     ╖        l
         ║           ñ                      ½                 █      D     ≥        Θ
         6           F                      ù                 ;      O     %        à
         m           -                      4                 n      '     ╬        {
         ├           ╚                      K                 &      G     R        ╗

Yabasic[edit]

open window 640,512,"swiss12"
backcolor 0,0,0
clear window
mx=50
my=42
dim scr(mx,my)
for y=0 to my
for x=0 to mx
scr(x,y)=int(ran(96)+33)
next x
next y
ms=50
dim sx(ms)
dim sy(ms)
for a=1 to ms
sx(a)=int(ran(mx))
sy(a)=int(ran(my))
next a
do
for s=1 to ms
x=sx(s)
y=sy(s)
 
letter(0,255,0)
y=y-1
 
letter(0,200,0)
y=y-1
 
letter(0,150,0)
y=y-1
 
color 0,0,0
fill rect x*12.8-1,y*12.8+4 to x*12.8+12,y*12.8-10
letter(0,70,0)
y=y-24
 
color 0,0,0
fill rect x*12.8-1,y*12.8+4 to x*12.8+12,y*12.8-10
next s
for s=1 to ms
if int(ran(5)+1)=1 sy(s)=sy(s)+1
if sy(s)>my+25 then
sy(s)=0
sx(s)=int(ran(mx))
end if
next s
loop
 
sub letter(r,g,b)
if y<0 or y>my return
c=scr(x,y)
color r,g,b
text x*12.8,y*12.8,chr$(c)
end sub

zkl[edit]

Translation of: Raku
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);
}

Offsite Image: Matrix rain dance

ZX Spectrum Basic[edit]

Appallingly slow (full speed on an emulator strongly recommended), but it works.

Authentic Matrix code includes reversed Latin characters and half-width Japanese. The Spectrum doesn't have anything like the space in the glyphs to support legible Japanese, but this program does use a machine code routine to generate a reversed Spectrum character set - and an upside down one and an upside down and reversed one, for good measure. (This could be duplicated in Basic but the program is slow enough as it is - ZX Spectrum Basic has no bitwise operations.) If you don't want to use the machine code (because this is a Basic demonstration, after all), or the alternate character sets, delete lines 30 and 4000; this will leave you with standard upright characters.

The left-right flip portion of the machine code routine is by John Metcalf, borrowed from his blog. (He has also posted a routine for the Matrix rain in Spectrum Z80 Assembly; his method, filling the screen with characters first and then applying the attributes, was one I had independently considered for this routine, but I didn't feel it resulted in enough character change.)

The PEEKs and POKEs throughout the routine are used for attribute handling. PEEK (22528+32*x+y) is the same thing as ATTR (x,y).

The next step for this routine would be a way to randomise the length of the character trails (currently fixed at 12).

10 CLEAR 61999
20 BORDER 0: POKE 23624,4: POKE 23693,0: CLS: REM easier than "bright 0: flash 0: ink 0: paper 0"
30 PRINT INK 4; FLASH 1;"Initialising": GO SUB 9000: LET m=USR 62000: CLS: REM set up and run machine code; USR is the call function
40 DIM s(32,2): REM current top and bottom of character sequence for each of the 32 spaces across the screen
 
50 FOR x=1 TO 32: REM main loop
60 IF s(x,1)=0 AND RND>.95 THEN LET s(x,1)=1: LET s(x,2)=2: GO SUB 4000: GO SUB 3000: GO TO 80: REM start a new column; decrease the .95 modifier for a busier - but slower - screen
70 IF s(x,2)>0 THEN GO SUB 1000
80 NEXT x
90 FOR l=1 TO 10: REM matrix code switches existing glyphs occasionally
100 LET x=INT (RND*22): LET y=INT (RND*32)
110 IF PEEK (22528+32*x+y)=0 THEN GO TO 140: REM no point updating a blank space
120 GO SUB 4000
130 PRINT AT x,y; INK 8; BRIGHT 8;CHR$ (33+RND*95): REM ink 8 and bright 8 tells it to keep the cell's existing ink and bright values
140 NEXT l
150 GO TO 50
 
999 REM continue an existing column
1000 LET s(x,2)=s(x,2)+1
1010 IF s(x,2)<21 THEN GO SUB 3000
1020 IF s(x,2)>12 THEN LET s(x,1)=s(x,1)+1
1030 LET k=2
1040 GO SUB 2000
1050 LET k=6
1060 GO SUB 2000
1070 LET k=12
1080 GO SUB 2000
1090 IF s(x,1)=22 THEN LET s(x,1)=0: LET s(x,2)=0
1100 RETURN
 
1999 REM update colour
2000 LET a=22527+x+32*(s(x,2)-k)
2010 LET c=PEEK a
2020 IF c=4 THEN POKE a,0
2030 IF c=68 THEN POKE a,4
2040 IF c=71 THEN POKE a,68: REM this poke could be done with 'print at s(x,2)-k-1,x-1; ink 4; bright 1; over 1; " " ' but poking is FAR easier, especially considering the above pokes would be similar
2050 RETURN
 
2999 REM new character at bottom of column
3000 PRINT AT s(x,2)-1,x-1; INK 7; BRIGHT 1;CHR$ (33+RND*95)
3010 RETURN
 
3999 REM select character set
4000 POKE 23607,242+3*INT (RND*4): REM the spectrum character set is pointed to by the two-byte system value CHARS at 23606 and 23607, so repoking this selects a new character set - the machine code below has created four copies of the character set at suitable locations
4010 RETURN
 
8999 REM machine code routine to create multiple character sets
9000 RESTORE 9800
9010 LET h$="0123456789ABCDEF"
9020 LET o=62000
9030 IF PEEK o=33 AND PEEK 62121=201 THEN RETURN: REM saves storing it all again if the machine code is already there
9040 READ a$
9050 IF a$="eof" THEN RETURN
9060 FOR x=1 TO 8
9070 LET n=0
9080 LET s=(x*2)-1
9090 LET t=s+1
9100 FOR m=1 TO 16
9110 IF h$(m)=a$(s) THEN LET n=n+16*(m-1)
9120 IF h$(m)=a$(t) THEN LET n=n+m-1
9130 NEXT m
9140 POKE o,n
9150 LET o=o+1
9160 NEXT x
9170 GO TO 9040
 
9800 DATA "21003D1100F30100"
9810 DATA "03EDB02100F31100"
9820 DATA "F6010009EDB01100"
9830 DATA "F901FF051A6F0707"
9840 DATA "ADE6AAAD6F070707"
9850 DATA "CB0DADE666AD1213"
9860 DATA "0B78B120E721FFF5"
9870 DATA "010000E5CD8BF2E1"
9880 DATA "E5CD8BF2E1E5CD8B"
9890 DATA "F2E1CD8BF2232323"
9900 DATA "23AF470C79FEC0C2"
9910 DATA "6BF2C9E5D1043E09"
9920 DATA "90835F8A93577885"
9930 DATA "6F8C95671AE521AA"
9940 DATA "F277E17E123AAAF2"
9950 DATA "77C9000000000000"
9960 DATA "eof"
 
9999 POKE 23606,0: POKE 23607,60: INK 4: REM reset to default character set and colour if you get lost

Offsite Image: Spectrum rain at imgur