1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
|
---
title: Recreating the Matrix rain with ANSI escape sequences
date: 2025-12-21
layout: post
project: true
thumbnail: thumb_sm.png
---
My 2022 implementation of the Matrix rain had too many loose ends. Unicode
support was inflexible: the character set had to be a single contiguous block
with no way to mix ASCII with something like Katakana; Phosphor decay level was
stored in a dedicated array--still don't understand why I did that when I had
already used bit-packing for the RGB channels; The algorithm was difficult to
decipher. The 2022 version worked, but that’s not the same thing as correct.
I began by placing the decay factor in the LSB of the 4-byte RGB value. Let's
call that RGB-PD. PD plays a somewhat analogous role to an alpha channel in
that both influence transparency. However, they work very differently. So, I
avoided labelling it A so as not to cause confusion:
```
enum {
R, /* Red */
G, /* Green */
B, /* Blue */
PD /* Phosphor decay level */
};
typedef union color_tag {
uint32_t value;
unsigned char color[4];
} color;
```
The decision to use union over more portable bit twiddling was made three years
ago, as I recall, for readability. Seeing as all my systems are little-endian,
this is unlikely to cause any trouble. Besides, if union is never to be used,
why is it in the language anyway?
The blend() function, which emulates the dim afterglow of Phosphor by eroding
the RGB channels towards the background, with minor refactoring, remains as
elegant as it did three years ago:
```
#define DECAY_MPLIER 2
static inline void blend(matrix *mat,
size_t row, size_t col)
{
unsigned char *color;
color = mat->rgb[index(mat, row, col)].color;
color[R] = color[R] - (color[R] - RGB_BG_RED) / DECAY_MPLIER;
color[G] = color[G] - (color[G] - RGB_BG_GRN) / DECAY_MPLIER;
color[B] = color[B] - (color[B] - RGB_BG_BLU) / DECAY_MPLIER;
}
```
While the memory inefficiency of Phosphor decay was a technical oversight I
hadn't noticed, the limitation around mixing nonadjacent Unicode blocks was a
nagging concern even three years ago. So, a fix was long overdue.
In the new version, I introduced an array that enables a user to add as
many Unicode blocks as they want. The insert_code() function picks a block
from it at random, and then picks a character from that block at random:
```
#define UNICODE(min, max) (((uint64_t)max << 32) | min)
static uint64_t glyphs[] = {
UNICODE(0x0021, 0x007E), /* ASCII */
UNICODE(0xFF65, 0xFF9F), /* Half-width Katakana */
};
static uint8_t glyphlen = (sizeof glyphs) / (sizeof glyphs[0]);
static inline void insert_code(matrix *mat,
size_t row, size_t col)
{
uint64_t block;
uint32_t unicode_min, unicode_max;
block = glyphs[(rand() % glyphlen)];
unicode_min = (uint32_t)block;
unicode_max = (uint32_t)(block >> 32);
mat->code[index(mat, row, col)] = rand()
% (unicode_max - unicode_min)
+ unicode_min;
}
```
The Unicode blocks are stored in 8-byte containers: the low four bytes form the
first codepoint and the high four bytes the last. Here, I chose bitwise
operations over unions because, first and foremost, the operations themselves
are trivial and idiomatic, and the UNICODE() macro simplifies the management of
charsets. The insert_code() function is now ready to take its rightful place
next to blend().
The init_term() function is the arbiter of this zero-dependency software. It
prepares the graphical environment so that I can interact with it via ANSI
escape codes instead of unnecessary layers of abstraction:
```
static inline int init_term(const struct winsize *ws)
{
struct termios ta;
if (tcgetattr(STDIN_FILENO, &ta) == 0) {
ta.c_lflag &= ~ECHO;
if (tcsetattr(STDIN_FILENO, TCSANOW, &ta) == 0) {
wprintf(L"\x1b[48;2;%d;%d;%dm",
RGB_BG_RED, RGB_BG_GRN, RGB_BG_BLU);
wprintf(L"%s", ANSI_FONT_BOLD);
wprintf(L"%s", ANSI_CRSR_HIDE);
wprintf(L"%s", ANSI_CRSR_RESET);
wprintf(L"%s", ANSI_SCRN_CLEAR);
setvbuf(stdout, 0, _IOFBF, 0);
ioctl(STDOUT_FILENO, TIOCGWINSZ, ws);
return 1;
}
}
return 0;
}
```
All credit for the terminal control function belongs to Domsson, whose <a
href="https://github.com/domsson/fakesteak" class="external" target="_blank"
rel="noopener noreferrer">Fakesteak</a> inspired my own three years ago.
insert_code() seeds the Matrix, blend() creates the old monochrome CRT display
nostalgia, and ANSI control sequences paint the screen. The result is a digital
rain that captures the original Matrix aesthetic with high visual fidelity:
```
$ cc -O3 main.c -o matrix
$ ./matrix
```
<video style="max-width:100%;" controls="" poster="poster.png">
<source src="matrix.mp4" type="video/mp4">
</video>
There was no cause to measure the program's performance characteristics
precisely; it's gentle on the CPU. On my ThinkPad T490 running OpenBSD, which
has a resolution of 1920x1080, it uses about 2-3% of the CPU, with occasional
jumps of up to about 8%; the cores remain silent, the fans don't whir, the rain
falls in quiet.
Files: [source.tar.gz](source.tar.gz)
|