summaryrefslogtreecommitdiffstats
path: root/main.c
diff options
context:
space:
mode:
authorSadeep Madurange <sadeep@asciimx.com>2025-12-21 09:14:53 +0800
committerSadeep Madurange <sadeep@asciimx.com>2025-12-26 13:32:43 +0800
commit69a888a5b0bc4ef4bce4f86c1556a06f0f131fda (patch)
tree3ebe8443d487f6c9ef52ca07bc7c7cd7abb252b0 /main.c
parentff887d9189c93f585ba9969a20d61acd4cc59176 (diff)
downloadmatrix-digital-rain-69a888a5b0bc4ef4bce4f86c1556a06f0f131fda.tar.gz
Support for multiple Unicode blocks.HEADmaster
Diffstat (limited to 'main.c')
-rw-r--r--main.c377
1 files changed, 377 insertions, 0 deletions
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..c817739
--- /dev/null
+++ b/main.c
@@ -0,0 +1,377 @@
+#include <locale.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <termios.h>
+#include <time.h>
+#include <unistd.h>
+#include <uchar.h>
+#include <wchar.h>
+#include <sys/ioctl.h>
+
+#define RHO 0.7 /* Rain density: (0, 1) */
+
+#define RGB_BG_RED 34 /* Background color */
+#define RGB_BG_GRN 34
+#define RGB_BG_BLU 34
+
+#define RGB_HD_RED 255 /* Color of the first raindrop */
+#define RGB_HD_GRN 255
+#define RGB_HD_BLU 255
+
+#define RGB_TL_RED 40 /* Color of the rain */
+#define RGB_TL_GRN 254
+#define RGB_TL_BLU 20
+
+#define DECAY_MPLIER 2 /* Phosphor decay multiplier */
+#define DELAY_US 60000 /* Delay between frames: increase to slow the rain */
+
+#define ANSI_CRSR_HIDE "\e[?25l"
+#define ANSI_CRSR_SHOW "\e[?25h"
+#define ANSI_CRSR_RESET "\x1b[H"
+#define ANSI_FONT_BOLD "\x1b[1m"
+#define ANSI_FONT_RESET "\x1b[0m"
+#define ANSI_SCRN_CLEAR "\x1b[2J"
+
+#define UNICODE(min, max) (((uint64_t)max << 32) | min)
+
+static uint64_t glyphs[] = {
+ UNICODE(0x0021, 0x007E), /* ASCII */
+#ifndef NOKANA
+ UNICODE(0xFF65, 0xFF9F), /* Half-width Katakana */
+#endif
+};
+
+static uint8_t glyphlen = (sizeof glyphs) / (sizeof glyphs[0]);
+
+enum {
+ R, /* Red */
+ G, /* Green */
+ B, /* Blue */
+ PD /* Phosphor decay multiplier */
+};
+
+typedef union color_tag {
+ uint32_t value;
+ unsigned char color[4];
+} color;
+
+typedef struct matrix_tag {
+ size_t rowlen; /* Row count: terminal screen height */
+ size_t collen; /* Column count: terminal screen width */
+ size_t *col; /* Column indices in random order (shuffle()) */
+ size_t *row; /* Current row index of columns: 1-1 map with col */
+ color *rgb; /* RGB color of the cell */
+ char32_t *code; /* Code point */
+} matrix;
+
+static inline size_t index(const matrix *mat,
+ size_t row, size_t col)
+{
+ return mat->collen * row + col;
+}
+
+static inline void print(const matrix *mat,
+ size_t row, size_t col)
+{
+ size_t idx;
+
+ idx = index(mat, row, col);
+ wprintf(L"\x1b[%d;%dH\x1b[38;2;%d;%d;%dm%lc", row, col,
+ mat->rgb[idx].color[R], mat->rgb[idx].color[G],
+ mat->rgb[idx].color[B], mat->code[idx]);
+}
+
+static inline void insert_code(matrix *mat,
+ size_t row, size_t col)
+{
+ uint64_t blk;
+ uint32_t min, max;
+
+ blk = glyphs[(rand() % glyphlen)];
+ min = (uint32_t)blk;
+ max = (uint32_t)(blk >> 32);
+ mat->code[index(mat, row, col)] = rand() % (max - min) + min;
+}
+
+static inline void delete_code(matrix *mat,
+ size_t row, size_t col)
+{
+ mat->code[index(mat, row, col)] = ' ';
+ print(mat, row, col);
+}
+
+static inline void recolor_head(matrix *mat,
+ size_t row, size_t col)
+{
+ unsigned char *sc, *tc;
+
+ sc = mat->rgb[index(mat, 0, col)].color;
+ tc = mat->rgb[index(mat, row, col)].color;
+ tc[R] = sc[R];
+ tc[G] = sc[G];
+ tc[B] = sc[B];
+ print(mat, row, col);
+}
+
+static inline void draw_tail(matrix *mat,
+ size_t row, size_t col)
+{
+ unsigned char *color;
+
+ color = mat->rgb[index(mat, row, col)].color;
+ color[R] = RGB_TL_RED;
+ color[G] = RGB_TL_GRN;
+ color[B] = RGB_TL_BLU;
+ print(mat, row, col);
+}
+
+static inline void draw_head(matrix *mat,
+ size_t row, size_t col)
+{
+ unsigned char *color;
+
+ color = mat->rgb[index(mat, row, col)].color;
+ color[R] = RGB_HD_RED;
+ color[G] = RGB_HD_GRN;
+ color[B] = RGB_HD_BLU;
+
+ insert_code(mat, row, col);
+ print(mat, row, col);
+}
+
+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;
+}
+
+static inline void swap_col(matrix *mat, size_t i, size_t max)
+{
+ size_t j;
+
+ mat->row[i] = 0;
+ mat->rgb[i].color[PD] = 0;
+
+ j = rand() % (mat->collen - max) + max;
+ mat->col[i] = mat->col[i] ^ mat->col[j];
+ mat->col[j] = mat->col[i] ^ mat->col[j];
+ mat->col[i] = mat->col[i] ^ mat->col[j];
+}
+
+static inline uint8_t is_tail(matrix *mat,
+ size_t row, size_t col)
+{
+ unsigned char *color;
+
+ color = mat->rgb[index(mat, row, col)].color;
+ return color[R] == RGB_TL_RED
+ && color[G] == RGB_TL_GRN
+ && color[B] == RGB_TL_BLU;
+}
+
+static inline void glitch(matrix *mat)
+{
+ size_t i, j;
+
+ i = rand() % (mat->rowlen - 1);
+ j = rand() % mat->collen;
+ if (mat->code[index(mat, i, j)] != ' '
+ && is_tail(mat, i, j)) {
+ insert_code(mat, i, j);
+ print(mat, i, j);
+ }
+}
+
+static inline void shuffle(size_t *a, size_t n)
+{
+ size_t i, j;
+
+ for (i = n - 1; i > 0; i--) {
+ j = rand() % (i + 1);
+ a[j] = a[i] ^ a[j];
+ a[i] = a[i] ^ a[j];
+ a[j] = a[i] ^ a[j];
+ }
+}
+
+static inline int init_matrix(matrix *mat,
+ const struct winsize *ws)
+{
+ size_t i;
+
+ mat->collen = ws->ws_col;
+ mat->rowlen = ws->ws_row + 1;
+
+ mat->code = realloc(mat->code,
+ sizeof mat->code[0] * mat->rowlen * mat->collen);
+ if (!mat->code)
+ return 0;
+
+ mat->rgb = realloc(mat->rgb,
+ sizeof mat->rgb[0] * mat->rowlen * mat->collen);
+ if (!mat->rgb)
+ return 0;
+
+ mat->col = realloc(mat->col,
+ sizeof mat->col[0] * mat->collen);
+ if (!mat->col)
+ return 0;
+
+ mat->row = realloc(mat->row,
+ sizeof mat->row[0] * mat->collen);
+ if (!mat->row)
+ return 0;
+
+ for (i = 0; i < mat->collen; i++) {
+ mat->row[i] = 0;
+ mat->col[i] = i;
+ }
+
+ shuffle(mat->col, mat->collen);
+ return 1;
+}
+
+static inline void destroy_matrix(matrix *mat)
+{
+ free(mat->code);
+ free(mat->col);
+ free(mat->row);
+ free(mat->rgb);
+}
+
+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;
+}
+
+static inline void reset_term()
+{
+ struct termios ta;
+
+ wprintf(L"%s", ANSI_FONT_RESET);
+ wprintf(L"%s", ANSI_CRSR_SHOW);
+ wprintf(L"%s", ANSI_SCRN_CLEAR);
+ wprintf(L"%s", ANSI_CRSR_RESET);
+
+ if (tcgetattr(STDIN_FILENO, &ta) == 0) {
+ ta.c_lflag |= ECHO;
+ if (tcsetattr(STDIN_FILENO, TCSANOW, &ta) != 0)
+ perror("reset_term()");
+ }
+ setvbuf(stdout, 0, _IOLBF, 0);
+}
+
+static volatile int run;
+
+static void handle_signal(int sa)
+{
+ switch (sa) {
+ case SIGINT:
+ case SIGQUIT:
+ case SIGTERM:
+ run = 0;
+ break;
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ matrix mat;
+ struct winsize ws;
+ struct sigaction sa;
+ size_t i, j, n, nmax;
+
+ setlocale(LC_CTYPE, "");
+
+ sa.sa_handler = handle_signal;
+ sigaction(SIGINT, &sa, 0);
+ sigaction(SIGQUIT, &sa, 0);
+ sigaction(SIGTERM, &sa, 0);
+
+ srand(time(0));
+
+ if (!init_term(&ws))
+ return 1;
+
+ mat = (matrix){0};
+ if (!init_matrix(&mat, &ws)) {
+ reset_term();
+ return 1;
+ }
+
+ run = 1;
+ n = 1; /* Used to ramp up the tracks from 1 to nmax to stagger the tracks. */
+ nmax = mat.collen * RHO; /* Number of rain tracks. */
+
+ while (run) {
+ for (i = 0; run && i < n; i++) {
+ if (mat.row[i] == mat.rowlen) {
+ recolor_head(&mat, mat.row[i] - 1, mat.col[i]);
+ mat.row[i] = 0;
+ }
+
+ if (mat.rgb[i].color[PD] == 0) {
+ if (mat.row[i] > 0)
+ draw_tail(&mat, mat.row[i] - 1, mat.col[i]);
+ draw_head(&mat, mat.row[i], mat.col[i]);
+ if (mat.row[i] == mat.rowlen - 1)
+ mat.rgb[i].color[PD] = 1;
+ mat.row[i]++;
+ } else if (mat.rgb[i].color[PD] > 0 &&
+ mat.rgb[i].color[PD] <= DECAY_MPLIER) {
+ blend(&mat, mat.row[i], mat.col[i]);
+ print(&mat, mat.row[i], mat.col[i]);
+ if (mat.row[i] == mat.rowlen - 1)
+ mat.rgb[i].color[PD]++;
+ mat.row[i]++;
+ } else {
+ delete_code(&mat, mat.row[i], mat.col[i]);
+ if (mat.row[i] == mat.rowlen - 1)
+ /* Track complete, start a new one. */
+ swap_col(&mat, i, nmax);
+ else
+ mat.row[i]++;
+ }
+ glitch(&mat);
+ }
+
+ if (n < nmax &&
+ /* Track ramp up: add a new one when the first track
+ * exceeds a random distance beyond 25% of the screen
+ * length. Do that until we reach the target density. */
+ mat.row[n - 1] >= rand() % (int)(mat.rowlen * 0.25)) {
+ mat.row[n++] = 0;
+ }
+
+ fflush(stdout);
+ usleep(DELAY_US);
+ }
+
+ reset_term();
+ destroy_matrix(&mat);
+ return 0;
+}
+