Last active
August 29, 2015 14:16
-
-
Save nirenjan/432f662e31df08e6a162 to your computer and use it in GitHub Desktop.
Matrix Digital Rain
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/********************************************************************** | |
* 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