Skip to content

Instantly share code, notes, and snippets.

@nirenjan
Last active August 29, 2015 14:16
Show Gist options
  • Save nirenjan/432f662e31df08e6a162 to your computer and use it in GitHub Desktop.
Save nirenjan/432f662e31df08e6a162 to your computer and use it in GitHub Desktop.
Matrix Digital Rain
/**********************************************************************
* Matrix Digital Rain
*
* Copyright (C) 2015 Nirenjan Krishnan
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use, copy,
* modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
**********************************************************************
*/
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <sys/ioctl.h> /* Needed for window size */
#include <string.h>
#include <unistd.h>
#include <time.h>
/* Stream types */
typedef enum {
STREAM_TYPE_INACTIVE,
STREAM_TYPE_DROP,
STREAM_TYPE_SLEEP,
STREAM_TYPE_SCROLL,
STREAM_TYPE_CLEAR,
STREAM_TYPE_TEXT,
STREAM_TYPE_DISABLED,
STREAM_TYPE_MAX
} t_stream_type;
/* Stream speeds */
typedef enum {
STREAM_SPEED_FAST = 1,
STREAM_SPEED_MEDIUM,
STREAM_SPEED_SLOW,
STREAM_SPEED_MAX
} t_stream_speed;
/* Minimum characters in a stream */
const uint8_t STREAM_LENGTH_MIN = 5;
/* Maximum characters in a stream */
const uint8_t STREAM_LENGTH_MAX = 25;
/* Stream definition */
typedef struct {
uint8_t type; /* Type of stream, see t_stream_type */
uint8_t speed; /* Speed of stream, see t_stream_speed */
uint8_t timer; /* Timer used to adjust for speed */
uint8_t row; /* Starting row from which to display the stream */
uint8_t length; /* Length of the stream */
uint8_t index; /* Current index of the stream to display */
uint8_t column; /* Column of the stream */
/* Display characters in stream */
uint8_t disp[STREAM_LENGTH_MAX];
} t_stream;
uint32_t win_rows;
uint32_t win_cols;
/*
* Pointer to an array of streams.
* This is initialized at runtime - each stream corresponds to a column
*/
t_stream *streams;
/* Number of active streams - this is limited to 25% of win_cols */
uint32_t active;
/* Reset the screen and streams */
uint8_t reset_streams;
#define TEXT_LEN 80
/* Saved text from random seek */
char text_saved[TEXT_LEN];
/* Length of display text */
uint8_t text_length;
/* Starting column from which to display the text */
uint8_t text_offset;
/* Row in which to display the text */
uint8_t text_row;
/* Cycle delay after which to display the text */
int32_t text_delay = -1;
/* Characters remaining to be displayed */
uint8_t text_chars = -1;
/* Exit delay */
/* This will eventually exit, but if we are displaying text, then we can
* control the exit time
*/
int32_t exit_delay = -1;
/*
* Generate a random number in the range [min, max]
*/
static uint8_t
rand_range(uint8_t min, uint8_t max)
{
return ((rand() % (max - min + 1)) + min);
}
/**********************************************************************
* Terminal control
*********************************************************************/
/* The control sequence initiator */
#define CSI(x) "\033[" x
/*
* Clear the screen
*/
static void
clear_screen(void)
{
printf(CSI("2J"));
}
/* Default Colors */
#define COLOR_BLACK "0"
#define COLOR_RED "1"
#define COLOR_GREEN "2"
#define COLOR_YELLOW "3"
#define COLOR_BLUE "4"
#define COLOR_MAGENTA "5"
#define COLOR_CYAN "6"
#define COLOR_WHITE "7"
static void
set_background(char *color)
{
printf(CSI("4%sm"), color);
}
static void
set_foreground(char *color)
{
printf(CSI("3%sm"), color);
}
static void
reset_colors(void)
{
printf(CSI("0m"));
}
/* Cursor control */
static void
hide_cursor(void)
{
printf(CSI("?25l"));
}
static void
show_cursor(void)
{
printf(CSI("?25h"));
}
static void
mvprintch(uint8_t row, uint8_t col, uint8_t ch, char *fgcolor)
{
/* Move to row & column */
printf(CSI("%u;%uH"), row, col);
/* Set color */
set_foreground(fgcolor);
/* Print character */
printf("%c", ch);
}
/*
* Get screen size
*
* This uses the ioctl approach
*/
static void
get_win_size(void)
{
struct winsize w;
ioctl(0, TIOCGWINSZ, &w);
win_rows = w.ws_row;
win_cols = w.ws_col;
}
/* Triggered on exit to cleanup the window */
__attribute__((destructor)) void
cleanup(void)
{
reset_colors();
clear_screen();
show_cursor();
system("reset");
}
static void
sighandler(int sig)
{
cleanup();
exit(0);
}
/*
* Compute the delay timer for a given speed
*/
static uint8_t
stream_countdown_init(uint8_t speed)
{
return (speed * 3);
}
/* Return 0 if timer has not yet expired, 1 if it has */
static uint8_t
stream_countdown(t_stream *stream)
{
stream->timer--;
if (stream->timer) {
return 0;
} else {
stream->timer = stream_countdown_init(stream->speed);
return 1;
}
}
/* Display a particular stream */
static void
display_stream(t_stream *stream)
{
uint8_t i;
switch (stream->type) {
case STREAM_TYPE_INACTIVE:
/* Current stream is inactive, maybe activate it? */
if (active <= (win_cols >> 1)) {
if (rand_range(0, 100) <= 5) {
/*
* Start off with a drop stream at a random row and of
* random length
*/
stream->type = STREAM_TYPE_DROP;
stream->length = rand_range(STREAM_LENGTH_MIN, STREAM_LENGTH_MAX);
stream->row = rand_range(1, win_rows - stream->length);
stream->speed = rand_range(STREAM_SPEED_FAST, STREAM_SPEED_SLOW);
stream->timer = stream_countdown_init(stream->speed);
stream->index = 0;
/* Set up the characters in the stream */
for (i = 0; i < stream->length; i++) {
/* Each character is printable ASCII */
stream->disp[i] = rand_range(33, 126);
}
active++;
/*
* If text delay timer has expired, then either this is
* a text display column, or the column should be disabled
*/
if (text_delay == 0) {
if (stream->column >= text_offset &&
stream->column < text_offset + text_length) {
stream->type = STREAM_TYPE_TEXT;
stream->disp[stream->length - 1] =
text_saved[stream->column - text_offset];
} else {
active--;
stream->type = STREAM_TYPE_DISABLED;
}
}
}
}
break;
case STREAM_TYPE_DROP:
if (stream_countdown(stream)) {
if (stream->index < stream->length) {
uint8_t idx = stream->index;
uint8_t row = stream->row + idx;
uint8_t col = stream->column;
if (stream->index) {
mvprintch(row, col, stream->disp[idx], COLOR_GREEN);
}
idx++;
row++;
if (idx < stream->length) {
mvprintch(row, col, stream->disp[idx], COLOR_WHITE);
}
stream->index = idx;
} else {
mvprintch(stream->row + stream->index, stream->column,
stream->disp[stream->index - 1], COLOR_GREEN);
/* Reset type */
stream->type = STREAM_TYPE_SLEEP;
/* Sleep for anywhere between 1 & 10 cycles */
stream->timer = rand_range(1, 10);
stream->index = 0;
}
}
break;
case STREAM_TYPE_SLEEP:
if (stream_countdown(stream)) {
/* Reset to either scroll or clear type */
stream->type = rand_range(STREAM_TYPE_SCROLL, STREAM_TYPE_CLEAR);
stream->speed = rand_range(STREAM_SPEED_FAST, STREAM_SPEED_SLOW);
stream->timer = stream_countdown_init(stream->speed);
}
break;
case STREAM_TYPE_SCROLL: /* TODO: implement scrolling */
if (stream_countdown(stream)) {
if (stream->row <= win_rows) {
uint8_t idx = 0;
uint8_t row = stream->row++;
mvprintch(row, stream->column, ' ', COLOR_BLACK);
row++;
for (; idx < stream->length && row <= win_rows; idx++, row++) {
mvprintch(row, stream->column, stream->disp[idx], COLOR_GREEN);
}
} else {
stream->type = STREAM_TYPE_INACTIVE;
active--;
}
}
break;
case STREAM_TYPE_CLEAR:
if (stream_countdown(stream)) {
if (stream->index <= stream->length) {
uint8_t row = stream->row + stream->index;
mvprintch(row, stream->column, ' ', COLOR_BLACK);
stream->index++;
} else {
stream->type = STREAM_TYPE_INACTIVE;
active--;
}
}
break;
case STREAM_TYPE_TEXT:
if (stream_countdown(stream)) {
char *color = COLOR_GREEN;
uint8_t ch = stream->disp[stream->index];
if (stream->index == stream->length - 1) {
color = COLOR_WHITE;
stream->type = STREAM_TYPE_DISABLED;
text_chars--;
}
mvprintch(text_row, stream->column, ch, color);
stream->index++;
}
break;
default:
break;
}
/* Add a mutation with 1% probability */
if (rand_range(0, 100) <= 1) {
uint8_t mutate = 0;
uint8_t row = stream->row;
switch (stream->type) {
case STREAM_TYPE_DROP:
row += rand_range(0, stream->index);
mutate = 1;
break;
case STREAM_TYPE_SCROLL:
case STREAM_TYPE_SLEEP:
if (row < win_rows) {
row += rand_range(0, stream->length);
mutate = 1;
}
break;
default:
break;
}
if (mutate) {
mvprintch(row, stream->column, rand_range(33, 126), COLOR_WHITE);
}
}
}
static void
get_random_text_from_file(int argc, char **argv)
{
uint32_t i;
FILE *fd;
char line[TEXT_LEN];
for (i = 0; i < TEXT_LEN; i++) {
text_saved[i] = ' ';
}
/* Pick a random file */
i = rand_range(1, argc - 1);
fd = fopen(argv[i], "r");
if (!fd) {
/* Unable to open file for reading */
return;
}
/*
* The following code assumes that lines in the file are
* less than 80 characters long.
*/
i = 1;
while (fgets(line, TEXT_LEN, fd)) {
if (!(rand() % i)) {
strncpy(&text_saved[1], line, TEXT_LEN - 1);
}
i++;
}
fclose(fd);
/* Compute the rest of the parameters */
text_saved[TEXT_LEN - 1] = '\0';
text_length = strlen(text_saved);
text_saved[text_length - 1] = ' ';
text_chars = text_length;
text_delay = rand_range(15, 20) * 100;
}
static void
compute_text_location(void)
{
text_offset = (win_cols - text_length) >> 1;
text_row = win_rows >> 1;
}
int main(int argc, char *argv[])
{
uint8_t i;
reset_streams = 1;
srand(time(NULL));
signal(SIGINT, sighandler);
if (argc >= 2) {
get_random_text_from_file(argc, argv);
}
/* Disable buffering on stdout */
setbuf(stdout, NULL);
for(;;) {
if (reset_streams) {
reset_streams = 0;
set_background(COLOR_BLACK);
clear_screen();
get_win_size();
compute_text_location();
if (streams) {
free(streams);
streams = NULL;
}
streams = malloc(win_cols * sizeof(t_stream));
memset(streams, 0, win_cols * sizeof(t_stream));
for (i = 0; i < win_cols; i++) {
streams[i].column = i + 1;
}
}
for (i = 0; i < win_cols; i++) {
display_stream(&streams[i]);
}
usleep(10000); /* Sleep for 1 cycle */
/* Decrement the text display delay */
if (text_delay > 0) {
text_delay--;
}
if (!text_chars) {
exit_delay = 1000;
text_chars = -1;
}
if (!exit_delay--) {
break;
}
}
cleanup();
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment