diff options
| -rw-r--r-- | Makefile.am | 4 | ||||
| -rw-r--r-- | README.md | 31 | ||||
| -rw-r--r-- | example.png | bin | 167019 -> 522305 bytes | |||
| -rw-r--r-- | src/main.c | 313 |
4 files changed, 209 insertions, 139 deletions
diff --git a/Makefile.am b/Makefile.am index 6e4c431..102e0fc 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,5 +1,5 @@ ACLOCAL_AMFLAGS = -I m4 -bin_PROGRAMS = matrix +bin_PROGRAMS = amx -matrix_SOURCES = src/main.c +amx_SOURCES = src/main.c @@ -2,25 +2,40 @@  -> All I see now is blonde, brunette, redhead. +> Do not try and bend the spoon, that's impossble. Instead, only try to realize the truth. There is no spoon. - -- _Cypher_ + -- _Spoon Boy_ -# Overview +## Overview -Pure C implementation of the famous [digital rain](https://en.wikipedia.org/wiki/Matrix_digital_rain) effect from _The Matrix_ series. While the implementation was inspired by [fakesteak](https://github.com/domsson/fakesteak), this implementation uses the default colours of the terminal to match the overall theme of the setup. Colour of the first drop can be changed by changing `ANSI_COL_DROP` which is set to white by default. +Pure C implementation of the famous [digital rain](https://en.wikipedia.org/wiki/Matrix_digital_rain) effect from _The Matrix_ series for Linux inspired by [fakesteak](https://github.com/domsson/fakesteak). While trying to keep the simplicity and lightweightness of fakesteak as much as possible, I have added the following characteristics. -Glitching effect is implemented using unicode characters, hence the terminal needs to support them or else change the glitch code characters to ascii before compiling. + - Simulation pattern is closer to the one seen in the background while Neo and Cypher were talking in the first Matrix movie. + - Ghosting effect of monochrome displays. + - Truecolor support and simple colour customisation options to match the theme of the setup for ricing. + - Unicode support. -# Building +## Requirements and Dependencies -All the code is in a single file (src/main.c) and has no dependencies. So, you can compile it any way you like. If you have autotools installed you can build the project with: + - Terminal emulator with support for 24-bit RGB colours and unicode characters. + +## Customisation + + - Character set: set `UNICODE_MIN` and `UNICODE_MAX` for the [unicode block](https://en.wikipedia.org/wiki/List_of_Unicode_characters) you like to use (e.g. 0x30A1 and 0x30F6 for Katakana). + - Colours: set the RGB values of `COLOR_BG_*`, `COLOR_HD_*` and `COLOR_TL_*` for background, head and the tail characters respectively. + - Rain attributes: set `RAIN_RATE` and `RAIN_DENSITY` to change the speed and the density of the rain. + +## Building and Running + +All the code is in a single file (src/main.c) and has no external dependencies. You can compile it any way you like. If you have autotools installed you can build and run using the following commands. ``` autoreconf -i cd build/ ../configure make +./amx ``` -# Contributing + +## Contributing Code is hosted on [sourcehut](https://git.sr.ht/~sadeep/matrix-digital-rain). Please submit your patches there or via email to sadeep@asciimx.com ([PGP key](http://www.asciimx.com/sadeep.asc)). diff --git a/example.png b/example.png Binary files differindex fbe8b0c..1dedacf 100644 --- a/example.png +++ b/example.png @@ -11,89 +11,159 @@ #include <unistd.h> #include <wchar.h> -#define ANSI_COL_DROP "\x1b[97m" +#define UNICODE_MIN 0x0021 +#define UNICODE_MAX 0x007E + +#define RAIN_RATE 60 +#define RAIN_DENSITY 0.6 + +#define COLOR_BG_RED 0 +#define COLOR_BG_GRN 0 +#define COLOR_BG_BLU 0 +#define COLOR_HD_RED 255 +#define COLOR_HD_GRN 255 +#define COLOR_HD_BLU 255 +#define COLOR_TL_RED 40 +#define COLOR_TL_GRN 254 +#define COLOR_TL_BLU 20 + #define ANSI_CUR_HIDE "\e[?25l" #define ANSI_CUR_SHOW "\e[?25h" #define ANSI_CUR_RESET "\x1b[H" -#define ANSI_SCRN_CLEAR "\x1b[2J" +#define ANSI_FONT_BOLD "\x1b[1m" #define ANSI_FONT_RESET "\x1b[0m" +#define ANSI_SCRN_CLEAR "\x1b[2J" + +enum { red, green, blue }; + +typedef union color_tag { + uint32_t rgb; + unsigned char color[4]; +} color; typedef struct matrix_tag { - int rows; - int cols; + size_t rows; + size_t cols; + size_t *col; + size_t *row; + char *shade; wchar_t *code; + color *rgb; } matrix; -static int mat_index(const matrix *mat, int row, int col) { +static size_t mat_idx(const matrix *mat, size_t row, size_t col) { return mat->cols * row + col; } -static int mat_init(matrix *mat, struct winsize *ws) { - int i, j; +static void mat_put_code(matrix *mat, size_t row, size_t col) { + mat->code[mat_idx(mat, row, col)] = + rand() % (UNICODE_MAX - UNICODE_MIN) + UNICODE_MIN; +} - static wchar_t code[] = { - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', - 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', - 'a', 'b', 'c', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', - 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'}; +static void shuffle(size_t *a, size_t n) { + size_t i, j; - static int codelen = sizeof(code) / sizeof(code[0]); + 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 int mat_init(matrix *mat, const struct winsize *ws) { + size_t i; - mat->rows = ws->ws_row + 1; mat->cols = ws->ws_col; + mat->rows = ws->ws_row + 1; + mat->code = realloc(mat->code, sizeof mat->code[0] * mat->rows * mat->cols); - if (mat->code) { - for (i = 0; i < mat->rows; i++) { - for (j = 0; j < mat->cols; j++) - mat->code[mat_index(mat, i, j)] = code[rand() % codelen]; - } - return 1; + if (!mat->code) + return 0; + + mat->rgb = realloc(mat->rgb, sizeof mat->rgb[0] * mat->rows * mat->cols); + if (!mat->rgb) { + free(mat->code); + return 0; } - return 0; -} -typedef struct table_tag { - int len; // number of columns - int *cols; // column indices - int *rows; // row indices ((rows[i], cols[i]) points to a cell in the matrix) - int *attr; // cell attributes -} table; + mat->shade = realloc(mat->shade, sizeof mat->shade[0] * mat->cols); + if (!mat->shade) { + free(mat->code); + free(mat->rgb); + return 0; + } -static void shuffle(int *a, int n) { - int i, j, tmp; + mat->col = realloc(mat->col, sizeof mat->col[0] * mat->cols); + if (!mat->col) { + free(mat->code); + free(mat->rgb); + free(mat->shade); + return 0; + } - for (i = n - 1; i > 0; i--) { - j = rand() % (i + 1); - tmp = a[j]; - a[j] = a[i]; - a[i] = tmp; + mat->row = realloc(mat->row, sizeof mat->row[0] * mat->cols); + if (!mat->row) { + free(mat->code); + free(mat->rgb); + free(mat->col); + free(mat->shade); + return 0; + } + + for (i = 0; i < mat->cols; i++) { + mat->row[i] = 0; + mat->col[i] = i; + mat->shade[i] = 0; } + + shuffle(mat->col, mat->cols); + return 1; } -static int tab_init(table *tab, const matrix *mat) { - size_t i; +static void mat_reset_head(matrix *mat, size_t row, size_t col) { + unsigned char *sc, *tc; - tab->len = mat->cols; - tab->cols = realloc(tab->cols, sizeof tab->cols[0] * tab->len); - tab->rows = realloc(tab->rows, sizeof tab->rows[0] * tab->len); - tab->attr = realloc(tab->attr, sizeof tab->attr[0] * tab->len); + sc = mat->rgb[mat_idx(mat, 0, col)].color; + tc = mat->rgb[mat_idx(mat, row, col)].color; + tc[red] = sc[red]; + tc[green] = sc[green]; + tc[blue] = sc[blue]; +} - if (tab->cols && tab->rows && tab->attr) { - for (i = 0; i < mat->cols; i++) { - tab->rows[i] = 0; - tab->cols[i] = i; - tab->attr[i] = 0; - } - shuffle(tab->cols, tab->len); - return 1; - } - return 0; +static void mat_set_tail(matrix *mat, size_t row, size_t col) { + unsigned char *color; + + color = mat->rgb[mat_idx(mat, row, col)].color; + color[red] = COLOR_TL_RED; + color[green] = COLOR_TL_GRN; + color[blue] = COLOR_TL_BLU; +} + +static void mat_set_head(matrix *mat, size_t row, size_t col) { + unsigned char *color; + + color = mat->rgb[mat_idx(mat, row, col)].color; + color[red] = COLOR_HD_RED; + color[green] = COLOR_HD_GRN; + color[blue] = COLOR_HD_BLU; } -static void tab_free(table *tab) { - free(tab->cols); - free(tab->rows); - free(tab->attr); +static void mat_shade(matrix *mat, size_t row, size_t col) { + unsigned char *color; + + color = mat->rgb[mat_idx(mat, row, col)].color; + color[red] = color[red] - (color[red] - COLOR_BG_RED) / 2; + color[green] = color[green] - (color[green] - COLOR_BG_GRN) / 2; + color[blue] = color[blue] - (color[blue] - COLOR_BG_BLU) / 2; +} + +static void mat_free(matrix *mat) { + free(mat->code); + free(mat->col); + free(mat->row); + free(mat->rgb); + free(mat->shade); } static int term_init() { @@ -102,6 +172,8 @@ static int term_init() { 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", COLOR_BG_RED, COLOR_BG_GRN, COLOR_BG_BLU); + wprintf(L"%s", ANSI_FONT_BOLD); wprintf(L"%s", ANSI_CUR_HIDE); wprintf(L"%s", ANSI_CUR_RESET); wprintf(L"%s", ANSI_SCRN_CLEAR); @@ -128,45 +200,17 @@ static void term_reset() { setvbuf(stdout, 0, _IOLBF, 0); } -static void term_size(struct winsize *ws) { +static void term_size(const struct winsize *ws) { ioctl(STDOUT_FILENO, TIOCGWINSZ, ws); } -static void term_clear_cell(int row, int col) { - wprintf(L"\x1b[%d;%dH ", row, col); -} - -static void term_reset_cell(const matrix *mat, int row, int col) { - wprintf(L"\x1b[%d;%dH\x1b[39m%lc", row, col, - mat->code[mat_index(mat, row, col)]); -} - -static void term_print(const matrix *mat, int row, int col) { - wprintf(L"\x1b[%d;%dH%s%lc", row, col, ANSI_COL_DROP, - mat->code[mat_index(mat, row, col)]); -} +static void term_print(const matrix *mat, size_t row, size_t col) { + size_t idx; -static void term_glitch(int row, int col) { - static wchar_t code[] = { - 0x0413, // Г - 0x0418, // И - 0x042F, // Я - 0x2200, // ∀ - 0x04D9, // ә - 0x0500, // Ԁ - 0x2203, // ∃ - 0x2132, // Ⅎ - 0x0245, // Ʌ - 0x037B, // ͻ - 0x03FD, // Ͻ - 0x041F, // П - 0x04D4, // Ӕ - 0x0424, // Ф - }; - - static int codelen = sizeof(code) / sizeof(code[0]); - - wprintf(L"\x1b[%d;%dH\x1b[39m%lc", row, col, code[rand() % codelen]); + idx = mat_idx(mat, row, col); + wprintf(L"\x1b[%d;%dH\x1b[38;2;%d;%d;%dm%lc", row, col, + mat->rgb[idx].color[red], mat->rgb[idx].color[green], + mat->rgb[idx].color[blue], mat->code[idx]); } static volatile int run; @@ -183,11 +227,10 @@ static void handle_signal(int sa) { int main(int argc, char *argv[]) { matrix mat; - table tab; struct winsize ws; struct timespec ts; struct sigaction sa; - int i, cols, blank, delay, tmp; + size_t i, j, len, maxlen; setlocale(LC_CTYPE, ""); @@ -208,54 +251,66 @@ int main(int argc, char *argv[]) { return 1; } - tab = (table){0}; - if (!tab_init(&tab, &mat)) { - term_reset(); - return 1; - } - - delay = 75; - ts.tv_sec = delay / 1000; - ts.tv_nsec = (delay % 1000) * 1000000; + ts.tv_sec = RAIN_RATE / 1000; + ts.tv_nsec = (RAIN_RATE % 1000) * 1000000; - run = cols = 1; - blank = tab.cols[tab.len - 1]; + run = 1, len = 1; + maxlen = mat.cols * RAIN_DENSITY; while (run) { - for (i = 0; run && i < cols; i++) { - if (tab.rows[i] == mat.rows) { - tab.rows[i] = 0; - term_reset_cell(&mat, mat.rows - 1, tab.cols[i]); + for (i = 0; run && i < len; i++) { + if (mat.row[i] == mat.rows) { + mat_reset_head(&mat, mat.row[i] - 1, mat.col[i]); + term_print(&mat, mat.rows - 1, mat.col[i]); + mat.row[i] = 0; } - if (tab.attr[i] == 0) { - // hard-coding to 5 (row[i] - rand() % rows[i] glitches on empty rows) - if (tab.rows[i] > 5 && rand() % 6 == 0) - term_glitch(tab.rows[i] - 5, tab.cols[i]); - if (tab.rows[i] > 0) - term_reset_cell(&mat, tab.rows[i] - 1, tab.cols[i]); - term_print(&mat, tab.rows[i], tab.cols[i]); - if (tab.rows[i] == mat.rows - 1) - tab.attr[i] = 1; - tab.rows[i]++; + if (mat.shade[i] == 0) { + if (mat.row[i] > 0) { + mat_set_tail(&mat, mat.row[i] - 1, mat.col[i]); + term_print(&mat, mat.row[i] - 1, mat.col[i]); + } + mat_set_head(&mat, mat.row[i], mat.col[i]); + mat_put_code(&mat, mat.row[i], mat.col[i]); + term_print(&mat, mat.row[i], mat.col[i]); + if (mat.row[i] > 0 && rand() % 6 == 0) { + j = rand() % mat.row[i]; + if (mat.code[mat_idx(&mat, j, mat.col[i])] != ' ') { + mat_put_code(&mat, j, mat.col[i]); + term_print(&mat, j, mat.col[i]); + } + } + if (mat.row[i] == mat.rows - 1) + mat.shade[i] = 1; + mat.row[i]++; + } else if (mat.shade[i] == 1 || mat.shade[i] == 2) { + mat_shade(&mat, mat.row[i], mat.col[i]); + term_print(&mat, mat.row[i], mat.col[i]); + if (mat.row[i] == mat.rows - 1) + mat.shade[i]++; + mat.row[i]++; } else { - term_clear_cell(tab.rows[i], tab.cols[i]); - if (tab.rows[i] == mat.rows - 1) { - tab.rows[i] = 0; - tab.attr[i] = 0; - tmp = blank; - blank = tab.cols[i]; - tab.cols[i] = tmp; + mat.code[mat_idx(&mat, mat.row[i], mat.col[i])] = ' '; + term_print(&mat, mat.row[i], mat.col[i]); + if (mat.row[i] == mat.rows - 1) { + mat.row[i] = 0; + mat.shade[i] = 0; + j = rand() % (mat.cols - maxlen) + maxlen; + 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]; } else - tab.rows[i]++; + mat.row[i]++; } } - if (cols < tab.len * 0.6) - tab.rows[cols++] = 0; + if (len < maxlen && + mat.row[len - 1] >= rand() % (int)(mat.rows * 0.25)) { + mat.row[len] = 0; + mat.shade[len++] = 0; + } fflush(stdout); nanosleep(&ts, &ts); } term_reset(); - tab_free(&tab); - free(mat.code); + mat_free(&mat); return 0; } |
