Matrix digital rain: Difference between revisions
m →{{header|J}}: be slightly more robust in the face of some typical development contexts |
m →{{header|J}}: using ptimer now, rather than "sys_timer" |
||
Line 1,329: | Line 1,329: | ||
Notes: |
Notes: |
||
<tt> |
<tt>ptimer 100</tt> to roughly match the update rate used in the matrix movie. |
||
In the movie, the display was somewhat pixelated, and had some other artifacts which were characteristic of cathode display tubes. The font support we use here does not emulate all of that. |
In the movie, the display was somewhat pixelated, and had some other artifacts which were characteristic of cathode display tubes. The font support we use here does not emulate all of that. |
Revision as of 19:17, 12 January 2024
![Task](http://static.miraheze.org/rosettacodewiki/thumb/b/ba/Rcode-button-task-crushed.png/64px-Rcode-button-task-crushed.png)
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
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;
Amazing Hopper
VERSION 1:
#include <basico.h>
#proto desplegarmatrixrain(_X_,_Y_,_Z_,_W_)
algoritmo
permitir interrupción
dimensionar (80) matriz aleatoria entera ( -50, t )
y=0
color(232,232)
borrar pantalla
enfatizado
iterar
iterar para(i=1, #(i<=80), ++i )
cuando ( #(t[i]>50) ){ #(t[i]=0) }
#basic{
t[i] += 1
y = t[i]
desplegar matrix rain(232,232,i,(y-10))
desplegar matrix rain(22,232,i,(y-9))
desplegar matrix rain(22,232,i,(y-8))
desplegar matrix rain(22,232,i,(y-7))
desplegar matrix rain(28,232,i,(y-6))
desplegar matrix rain(28,232,i,(y-5))
desplegar matrix rain(34,232,i,(y-4))
desplegar matrix rain(34,232,i,(y-3))
desplegar matrix rain(40,232,i,(y-2))
desplegar matrix rain(46,232,i,(y-1))
desplegar matrix rain(232,40,i,y)
}
siguiente
microsegundos(5000)
hasta que una tecla sea presionada
color(7,0)
borrar pantalla
terminar
subrutinas
desplegar matrix rain(p,s,x,y)
si ( y, está entre-excluyendo-(0,41) )
color (p,s)
imprimir en ( y,x, #(utf8(chr(33+((x*y) % 200)) )) )
fin si
retornar
- Output:
semejante a la imagen de la VERSION 2, pero con menos "cola"
VERSION 2:
#include <basico.h>
#proto desplegarmatrixrain(_X_,_Y_)
algoritmo
dimensionar con (80) matriz aleatoria entera ( -50, t )
matrices (ctexto, cfondo)
'232,22,22,22,22,28,28,28,34,34,40,40,46,232' enlistar en 'ctexto'
'232,232,232,232,232,232,232,232,232,232,232,232,232,40' enlistar en 'cfondo'
color(232,232)
borrar pantalla
enfatizado
iterar
iterar para(i=1, #(i<=80), ++i )
cuando ( #(t[i]>53) ){ #(t[i]=rand(-50)) }
#basic{
t[i] += 1
desplegar matrix rain(i,t[i])
}
siguiente
microsegundos(50000)
hasta que una tecla sea presionada
color(7,0)
borrar pantalla
terminar
subrutinas
desplegar matrix rain(x,y)
i=13, j=1
iterar
si ( #(y-i), está entre-excluyendo-(0,41) )
color ( #(ctexto[j]), #(cfondo[j]) )
imprimir en ( #(y-i),x, #(utf8(chr(33+((x*(y-i)) % 200)) )) )
fin si
--i, ++j
mientras ' no es negativo(i)'
retornar
- Output:
File:Matrix rain terminal linux 2.png
BASIC
FreeBASIC
Sub d(p As Ulong, s As Ulong, x As Long, y As Long)
Color p, s
If y > 0 And y < 24 Then Locate y, x: Print Chr(33 + (x * y) Mod 200);
End Sub
Dim As Long t(80)
For i As Integer = 1 To 80
t(i) = Int(-50 * Rnd)
Next
Dim As Double s = Timer
Cls
Do
For i As Integer = 1 To 80
If t(i) > 28 Then t(i) = 0
t(i) += 1
Dim As Long y = t(i)
d( 0, 0, i, y - 6)
d( 2, 0, i, y - 5)
d( 2, 0, i, y - 4)
d(10, 0, i, y - 2)
d(11, 0, i, y - 1)
d( 0, 2, i, y)
Next i
Dim As Double l = Timer
While l = Timer: Wend
Loop
QBasic
Original code programmed by nitro2k01
DECLARE SUB d (p!, s!, x!, y!)
DIM t(80)
FOR i = 1 TO 80
t(i) = INT(-50 * RND)
NEXT
s = TIMER
CLS
WHILE 1
FOR i = 1 TO 80
IF t(i) > 28 THEN t(i) = 0
t(i) = t(i) + 1
y = t(i)
d 0, 0, i, y - 6
d 2, 0, i, y - 5
d 2, 0, i, y - 4
d 10, 0, i, y - 3
d 10, 0, i, y - 2
d 11, 0, i, y - 1
d 0, 2, i, y
NEXT
l = TIMER
WHILE l = TIMER
WEND
WEND
SUB d (p, s, x, y)
COLOR p, s
IF y > 0 AND y < 24 THEN LOCATE y, x: PRINT CHR$(33 + (x * y) MOD 200);
END SUB
True BASIC
SUB d (p,s,x,y)
SET COLOR p
SET BACKGROUND COLOR s
IF y > 0 AND y < 24 THEN
SET CURSOR y, x
PRINT CHR$(33+REMAINDER((x*y),200));
END IF
END SUB
DIM t(80)
FOR i = 1 TO 80
LET t(i) = INT(-50*RND)
NEXT i
LET s = TIME
CLEAR
DO
FOR i = 1 TO 80
IF t(i) > 28 THEN LET t(i) = 0
LET t(i) = t(i)+1
LET y = t(i)
CALL d (0, 0, i, y-6)
CALL d (2, 0, i, y-5)
CALL d (2, 0, i, y-4)
CALL d (10, 0, i, y-2)
CALL d (11, 0, i, y-1)
CALL d (0, 2, i, y)
NEXT i
LET l = TIME
DO WHILE l = TIME
LOOP
LOOP
END
Batch File
This code uses Windows 10 VT100 escape sequences.
:: Matrix Digital Rain Task from RosettaCode
:: Batch File Implementation
@echo off
setlocal enabledelayedexpansion
rem escape character (for Windows 10 VT100 escape sequences)
rem info: https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences
for /f %%e in ('echo prompt $e^| cmd') do @set "esc=%%e"
rem set window size
set "col=120" %== please don't make this too large ==%
set "row=30" %== please don't make this too large ==%
mode con cols=%col% lines=%row%
rem set up the variables for display
set "rain_length=12"
for /l %%y in (1,1,%col%) do set "disp_col[%%y]= " %== what to display ==%
for /l %%y in (1,1,%col%) do set "ctr_col[%%y]=0" %== counter for rain length ==%
rem hide the cursor, and clear the screen
<nul set /p "=%esc%[?25l"
cls
:matrix_loop
for /l %%y in (1,1,%col%) do (
if !ctr_col[%%y]! equ 0 (
set "disp_col[%%y]= "
) else (
set /a "rnd_digit=!random! %% 10"
if !ctr_col[%%y]! equ 1 (
set "disp_col[%%y]=%esc%[97m!rnd_digit!%esc%[32m"
) else if !ctr_col[%%y]! equ 2 (
set "disp_col[%%y]=%esc%[92m!rnd_digit!%esc%[32m"
) else (
set "disp_col[%%y]=!rnd_digit!"
)
set /a "ctr_col[%%y]=(!ctr_col[%%y]! + 1) %% (%rain_length% + 1)"
)
rem drop rain randomly
set /a "rnd_drop=!random! %% 20"
if !rnd_drop! equ 0 set "ctr_col[%%y]=1"
)
set "disp_line=%esc%[32m"
for /l %%y in (1,1,%col%) do set "disp_line=!disp_line!!disp_col[%%y]!"
<nul set /p "=%esc%[1T%esc%[1;1H" %== scroll down and set cursor position to home ==%
echo(%disp_line%
goto matrix_loop
C
NCURSES version
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 <dan@ruscoe.org>
*/
#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
Single threaded
/*******************************************************************************
*
* 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
/*******************************************************************************
*
* 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
/*******************************************************************************
*
* 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
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
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
}
}
}
J
This implementation was written for jqt under j903, after studying https://youtu.be/MvEXkd3O2ow
Some key issues are the (deliberate) relatively low resolution of the screen in the movie, a somewhat slow update rate and some variation over time in the update rate of the screen. This implementation is, of course, only an approximation...
require'gl2'
coinsert'jgl2'
junk=: 7 u:;48 65 16b30a1(+i.)&.>10 26 90
sz=:40 25
len=: <.1.4*{:sz
heat=: (224 255 255),~(<.0.5+255*(%>./)(-<./)^>:(% >./)i.len)*/0 1 0
cols=: i.0
rows=: i.0
scale=: 24
live=: (#heat)#<i.0 3
rain_timer=: {{
try.
wd 'psel rain'
glsel 'green'
glfill 0 0 0 255
glfont font=.'courier ',":0.8*scale
upd=. 0>._3++/?2 2 2 2 4
cols=: cols,upd{.(?~{.sz)-.(-<.0.3*{:sz){.cols
rows=: (#cols){.rows
live=: }.live,<(scale*cols,.rows),.?(#cols)##junk
for_p. live do.
gltextcolor glrgb p_index{heat
if.p_index=<:#live do.
glfont font,' bold'
end.
for_xyj.;p do.
gltextxy 2{.xyj
gltext 8 u:junk{~{:xyj
end.
end.
glpaint''
keep=: rows<{:sz-1
cols=: keep#cols
rows=: keep#rows+1
EMPTY
catch.
wd'ptimer 0'
end.
}}
wd rplc&('DIMS';":scale*sz) {{)n
pc rain closeok;
setp wh DIMS;
cc green isidraw flush;
pshow;
ptimer 100
}}
Notes:
ptimer 100 to roughly match the update rate used in the matrix movie.
In the movie, the display was somewhat pixelated, and had some other artifacts which were characteristic of cathode display tubes. The font support we use here does not emulate all of that.
Conceptually, we are emulating an emulation of a cathode ray tube with a long phosphor persistence time. Thus, there's an initial "burst" of light when the phosphor is being painted followed by a lingering glow which gradually fades out. Here, we use a light cyan to represent the initial paint event and fading shades of green to represent the fading phosphors. To better approximate the phosphor persistence mechanism, we have intensity fall off exponentially (and then we adjust the numeric range of the result so it still fades from our brightest green to black).
JavaScript
El código es de Christian Behler (christian@pingpoli.de)
var tileSize = 20;
// a higher fade factor will make the characters fade quicker
var fadeFactor = 0.05;
var canvas;
var ctx;
var columns = [];
var maxStackHeight;
function init() {
canvas = document.getElementById('canvas');
ctx = canvas.getContext('2d');
// https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver
const resizeObserver = new ResizeObserver(entries =>
{
for (let entry of entries)
{
if (entry.contentBoxSize)
{
// Firefox implements `contentBoxSize` as a single content rect, rather than an array
const contentBoxSize = Array.isArray(entry.contentBoxSize) ? entry.contentBoxSize[0] : entry.contentBoxSize;
canvas.width = contentBoxSize.inlineSize;
canvas.height = window.innerHeight;
initMatrix();
}
}
});
// observe the size of the document
resizeObserver.observe(document.documentElement);
// start the main loop
tick();
}
function initMatrix() {
columns = [];
maxStackHeight = Math.ceil(canvas.height/tileSize);
// divide the canvas into columns
for (let i = 0 ; i < canvas.width/tileSize ; ++i) {
var column = {};
// save the x position of the column
column.x = i*tileSize;
// create a random stack height for the column
column.stackHeight = 10+Math.random()*maxStackHeight;
// add a counter to count the stack height
column.stackCounter = 0;
// add the column to the list
columns.push(column);
}
}
function draw() {
// draw a semi transparent black rectangle on top of the scene to slowly fade older characters
ctx.fillStyle = "rgba(0 , 0 , 0 , "+fadeFactor+")";
ctx.fillRect(0 , 0 , canvas.width , canvas.height);
ctx.font = (tileSize-2)+"px monospace";
ctx.fillStyle = "rgb(0 , 255 , 0)";
for (let i = 0 ; i < columns.length ; ++i) {
// pick a random ascii character (change the 94 to a higher number to include more characters)
var randomCharacter = String.fromCharCode(33+Math.floor(Math.random()*94));
ctx.fillText(randomCharacter , columns[i].x , columns[i].stackCounter*tileSize+tileSize);
// if the stack is at its height limit, pick a new random height and reset the counter
if (++columns[i].stackCounter >= columns[i].stackHeight)
{
columns[i].stackHeight = 10+Math.random()*maxStackHeight;
columns[i].stackCounter = 0;
}
}
}
// MAIN LOOP
function tick() {
draw();
setTimeout(tick , 50);
}
var b_isFullscreen = false;
function fullscreen() {
var elem = document.documentElement;
if (elem.requestFullscreen) {
elem.requestFullscreen();
}
else if (elem.webkitRequestFullscreen) {
elem.webkitRequestFullscreen(); // Safari
}
else if (elem.msRequestFullscreen) {
elem.msRequestFullscreen(); // IE11
}
}
function exitFullscreen() {
if (document.exitFullscreen) {
document.exitFullscreen();
}
else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen(); // Safari
}
else if (document.msExitFullscreen) {
document.msExitFullscreen(); // IE11
}
}
function toggleFullscreen() {
if (!b_isFullscreen) {
fullscreen();
b_isFullscreen = true;
}
else {
exitFullscreen();
b_isFullscreen = false;
}
}
function updateTileSize() {
tileSize = Math.min(Math.max(document.getElementById("tileSize").value , 10) , 100);
initMatrix();
}
function updateFadeFactor() {
fadeFactor = Math.min(Math.max(document.getElementById("fadeFactor").value , 0.0) , 1.0);
initMatrix();
}
Html to test:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <script src="The_Matrix_rain.js"></script> <title>The Matrix rain</title> <style> body {padding: 50px;width: 50%;box-shadow: 0 0 15px 0 rgba(0, 0, 0, 0.25);margin: 15px auto;font-family: "Gill Sans", "Gill Sans MT", Calibri, "Trebuchet MS", sans-serif;letter-spacing: 1px;} a {color: #00aadd;text-decoration: none;} input {width: 50px;text-align: center;} ul {list-style: none;padding: 0;margin: 0;width: 25%;margin: auto;border: 1px solid #aaa;} li {text-align: center;background-color: #eaeaea;} li:nth-child(even) {background: #fff;} </style> </head> <body onload="init();" style="margin: 0; padding: 0; background-color:#000000;"> <canvas id="canvas" style="display:block;margin:0 auto;" width="1920" height="955"></canvas> <div id="options" style="position: absolute; right: 10px; bottom: 10px; padding: 5px; background-color: #aaaaaa; font-family: monospace;"> <button onclick="toggleFullscreen()" style="font-family: monospace;">Toggle Fullscreen</button> <table> <tbody><tr> <td>Tile Size</td> <td><input type="number" id="tileSize" value="20" onchange="updateTileSize()" onkeyup="updateTileSize()" style="width: 80px; font-family: monospace; outline: none;" min="10" max="100" step="1"></td> </tr> <tr> <td>Fade Factor</td> <td><input type="number" id="fadeFactor" value="0.05" onchange="updateFadeFactor()" onkeyup="updateFadeFactor()" style="width: 80px; font-family: monospace; outline: none;" min="0" max="1" step="0.01"></td> </tr> </tbody></table> </div> </body> </html>
Julia
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
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
Mathematica /Wolfram Language
SeedRandom[1234];
ClearAll[ColorFunc]
chars = RandomSample[Flatten[CharacterRange @@@ Partition[Characters["\[CapitalAlpha]\[CapitalPi]ЀѵҊԯ\:03e2\:03efヲンⲀ⳩\[ForAll]∗℀℺⨀⫿"], 2]]];
charlen = Length[chars];
fadelength = 6;
ColorFunc[fade_] := ColorFunc[fade] = Blend[{Black, Green}, fade/fadelength]
rows = 30;
cols = 50;
n = 10;
trailpos = {RandomInteger[{1, cols}, n], RandomInteger[{1, rows}, n]} // Transpose;
trailchars = RandomInteger[{1, Length[chars]}, n];
charmap = ConstantArray[".", {rows, cols}];
fade = ConstantArray[5, {rows, cols}];
indices = MapIndexed[#2 &, fade, {2}];
Dynamic[Graphics[{txts}, PlotRange -> {{1, cols}, {1, rows}}, PlotRangePadding -> 1, Background -> Black]]
Do[
trailpos[[All, 2]] += 1;
fade = Ramp[fade - 1];
trailchars = Mod[trailchars + 1, charlen, 1];
Do[
If[trailpos[[i, 2]] >= rows,
trailpos[[i, 2]] = 1;
trailpos[[i, 1]] = RandomInteger[{1, cols}];
];
charmap[[trailpos[[i, 2]], trailpos[[i, 1]]]] =
chars[[trailchars[[i]]]];
fade[[trailpos[[i, 2]], trailpos[[i, 1]]]] = fadelength
,
{i, n}
];
txts = MapThread[If[#2 > 0, Text[Style[#1, ColorFunc[#2]], {#1, rows - #2 + 1} & @@ Reverse[#3]], {}] &, {charmap, fade, indices}, 2];
Pause[0.1]
,
{1000}
]
- Output:
Outputs a animating graphic of the matrix digital rain.
Nim
import os, random, sequtils
import ncurses
const RowDelay = 40 # In milliseconds.
proc exit() {.noconv.} =
endwin()
quit QuitSuccess
proc run() =
const
Chars = "0123456789" # Characters to randomly appear in the rain sequence.
let stdscr = initscr()
noEcho()
cursSet(0)
startColor()
initPair(1, COLOR_GREEN, COLOR_BLACK)
attron(COLOR_PAIR(1).cint)
var width, height: cint
stdscr.getmaxyx(height, width)
let maxX = width - 1
let maxY = height - 1
# Create arrays of columns based on screen width.
# Array containing the current row of each column.
# Set top row as current row for all columns.
var columnsRow = repeat(cint -1, width)
# Array containing the active status of each column.
# A column draws characters on a row when active.
var columnsActive = newSeq[bool](width)
setControlCHook(exit)
while true:
for i in 0..maxX:
if columnsRow[i] == -1:
# If a column is at the top row, pick a random starting row and active status.
columnsRow[i] = cint(rand(maxY))
columnsActive[i] = bool(rand(1))
# Loop through columns and draw characters on rows.
for i in 0..maxX:
if columnsActive[i]:
# Draw a random character at this column's current row.
let charIndex = rand(Chars.high)
mvprintw(columnsRow[i], i, "%c", Chars[charIndex])
else:
# Draw an empty character if the column is inactive.
mvprintw(columnsRow[i], i, " ")
inc 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(999) == 0: columnsActive[i] = not columnsActive[i]
sleep(RowDelay)
refresh()
run()
Perl
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
#!/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
You can run this online here.
-- -- demo\rosetta\Matrix_Digital_Rain.exw -- with javascript_semantics 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 while y>=1 do integer cy = colours[y][x] if cy=0 then exit end if clear_droplet = false cy -= #000800 colours[y][x] = cy y -= 1 end while if clear_droplet then droplets[x] = 0 end if end if end if end for end procedure function redraw_cb(Ihandle /*ih*/, integer /*posx*/, /*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 if colours[y][x] then cdCanvasSetForeground(cddbuffer, colours[y][x]) cdCanvasText(cddbuffer,x*dx, (h-y)*dy, sushii[y][x]) end if 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("RASTERSIZE=640x480") IupSetCallbacks(canvas, {"MAP_CB", Icallback("map_cb"), "ACTION", Icallback("redraw_cb")}) timer = IupTimer(Icallback("timer_cb"), 50) dlg = IupDialog(canvas,`TITLE="Matrix Digital Rain"`) IupShow(dlg) IupSetAttribute(canvas, "RASTERSIZE", NULL) if platform()!=JS then IupMainLoop() IupClose() end if end procedure main()
Python
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
#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
(formerly 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.
# 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,245 … 30).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
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 > ~ H@ / ┤ ⌂ 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 ╗
Rust
Cargo.toml
[package]
name = "matrix-digital-rain"
version = "0.1.0"
edition = "2021"
[dependencies]
termion = "1.5.6"
rand = "0.8.5"
main.rs
#![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();
}
Wren
The memory.ttf file is included with the DOME 'fonts' example and can be downloaded from here.
import "dome" for Window
import "graphics" for Canvas, Color, Font
import "random" for Random
var Rand = Random.new()
class MatrixDigitalRain {
construct new(width, height) {
Window.resize(width, height)
Canvas.resize(width, height)
Window.title = "Matrix digital rain"
Font.load("Mem12", "memory.ttf", 24)
Canvas.font = "Mem12"
}
letter(x, y, r, g, b) {
if (y < 0 || y >= _my) return
var col = Color.rgb(r, g, b)
var c = String.fromByte(_scr[x][y])
Canvas.print(c, x * 12.8 , y * 12.8 , col)
}
init() {
_mx = 50
_my = 42
_scr = List.filled(_mx, null)
for (x in 0..._mx) {
_scr[x] = List.filled(_my, 0)
for (y in 0..._my) _scr[x][y] = Rand.int(33, 128)
}
_ms = 50
_sx = List.filled(_ms, 0)
_sy = List.filled(_ms, 0)
}
update() {
for (a in 0..._ms) {
_sx[a] = Rand.int(_mx)
_sy[a] = Rand.int(_my)
}
}
draw(alpha) {
for (s in 0..._ms) {
var x = _sx[s]
var y = _sy[s]
letter(x, y, 0, 255, 0)
y = y - 1
letter(x, y, 0, 200, 0)
y = y - 1
letter(x, y, 0, 150, 0)
y = y - 1
if (y*12.8 + 3 < 0) y = 0
var c = Color.rgb(0, 0, 0)
Canvas.rectfill(x*12.8, y*12.8 + 3, 13, 14, c)
letter(x, y, 0, 70, 0)
y = y - 24
if (y*12.8 + 3 < 0) y = 0
Canvas.rectfill(x*12.8, y*12.8 + 3, 13, 14, c)
}
for (s in 0..._ms) {
if (Rand.int(1, 6) == 1) _sy[s] = _sy[s] + 1
if (_sy[s] > _my + 25) {
_sy[s] = 0
_sx[s] = Rand.int(_mx)
}
}
}
}
var Game = MatrixDigitalRain.new(640, 550)
Yabasic
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
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
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
- Programming Tasks
- Solutions by Programming Task
- Ada
- Amazing Hopper
- Pages with broken file links
- BASIC
- FreeBASIC
- QBasic
- True BASIC
- Batch File
- C
- Common Lisp
- Go
- Goncurses
- J
- JavaScript
- Julia
- Locomotive Basic
- Mathematica
- Wolfram Language
- Nim
- Nim-ncurses
- Perl
- Phix
- Phix/pGUI
- Phix/online
- Python
- Racket
- Raku
- REXX
- Rust
- Wren
- DOME
- Yabasic
- Zkl
- ZX Spectrum Basic
- Stata/Omit
- Ncurses
- Curses