/* zmatrix.c */ #include #include #include #include #include #include /*********************************/ /* #DEFINEs and pseudo-constants */ /*********************************/ #define TRUE 1 #define FALSE 0 /* Character parameters */ int MIN_CHAR = '!'; int MAX_CHAR = '}'; /* Stream parameters */ int NUM_STREAMS = 12; /* Number of streams in each column */ int MIN_HEIGHT = 6; /* Shortest height of each stream */ int MAX_HEIGHT = 20; /* Tallest height of each stream */ int MIN_GAP = 4; /* Smallest vertical gap between streams */ int MAX_GAP = 8; /* Largest vertical gap between streams */ int MIN_COLOR; /* Lowest color (set to red) */ int MAX_COLOR; /* Highest color (set to white) */ int USE_RAND_HEAD = TRUE; /* Use random white head characters? */ int USE_RAND_BOLD = TRUE; /* Use random bold characters? */ int USE_RAND_COLOR = FALSE; /* Use different colors for each stream? */ int FIXED_COLOR; /* Use if !USE_RAND_COLOR; green by default */ /* Screen parameters */ int SCR_RIGHT = 0; int SCR_BOTTOM = 24; int COL_GAP = 2; /* Space in between columns + 1 */ int NUM_COLS = 0; /* Speed params */ int TICK = 6250; int MIN_CYCLE = 4; int MAX_CYCLE = 16; /* Returns a random number between min and max */ #define rand_num(min, max) ((int)(min) + (int)((float)((max) - (min) + 1) * \ rand() / (float)(RAND_MAX))) /* Returns the lesser of the two parameters */ #define min(a, b) ((a) < (b) ? (a) : (b)) /********************/ /* Type definitions */ /********************/ /* An individual cascade of gibberish */ typedef struct stream_t { int top; int bottom; int color; int head_char; int bold_head; } stream_t; /* A vertical column in which streams live */ typedef struct col_t { int cycle; int time; stream_t* streams; } col_t; /* Global pointer to an array of columns */ col_t* cols = NULL; /**************/ /* Prototypes */ /**************/ int main(int argc, char** argv); /* Utility functions */ int rand_char(void); void curs_init(void); void curs_plot(int ch, int x, int y, int color, int bold); void change_params(int ch); void update_stream_params(stream_t* stream); void resize_screen(int); void finish(int); /* Primary functions */ void init_col(col_t* col); void free_col(col_t* col); void init_screen(void); void reset_stream(col_t* column, int stream_id); void move_streams(int); /*************/ /* Functions */ /*************/ /* Ye olde main function */ int main(int argc, char** argv) { int x; struct itimerval timer; struct timespec ts = {3600, 0} /* Set up timer delays */ timer.it_interval.tv_usec = timer.it_value.tv_usec = TICK; timer.it_interval.tv_sec = timer.it_value.tv_sec = 0; srand(time(NULL)); /* Initialize the randomizer */ curs_init(); /* Set up curses */ resize_screen(0); /* Initialize columns */ init_screen(); /* Display everything */ signal(SIGALRM, move_streams); /* Every tick, do a loop */ signal(SIGWINCH, resize_screen); /* Change screen params */ signal(SIGINT, finish); /* On interrupt, quit */ signal(SIGTERM, finish); /* On Ctrl+C, quit */ setitimer(ITIMER_REAL, &timer, NULL); /* Start the ball rolling */ while (TRUE) nanosleep(&ts); /* And wait forever */ /* This shouldn't happen, so... */ return EXIT_FAILURE; } /* Return a random character */ int rand_char(void) { return rand_num(MIN_CHAR, MAX_CHAR); } /* Set up curses */ void curs_init(void) { initscr(); /* Initialize the curses library */ keypad(stdscr, TRUE); /* Enable keyboard mapping */ nonl(); /* Tell curses not to do LF->CR+LF on output */ cbreak(); /* Don't wait for \n on input */ noecho(); /* Don't echo input */ nodelay(stdscr, TRUE); /* Don't wait for input before continuing */ /* Simple color assignment */ if (has_colors() ) { start_color(); init_pair(COLOR_BLACK, COLOR_BLACK, COLOR_BLACK); init_pair(COLOR_GREEN, COLOR_GREEN, COLOR_BLACK); init_pair(COLOR_RED, COLOR_RED, COLOR_BLACK); init_pair(COLOR_CYAN, COLOR_CYAN, COLOR_BLACK); init_pair(COLOR_WHITE, COLOR_WHITE, COLOR_BLACK); init_pair(COLOR_MAGENTA, COLOR_MAGENTA, COLOR_BLACK); init_pair(COLOR_BLUE, COLOR_BLUE, COLOR_BLACK); init_pair(COLOR_YELLOW, COLOR_YELLOW, COLOR_BLACK); } /* Set color defaults */ MIN_COLOR = COLOR_RED; MAX_COLOR = COLOR_WHITE; FIXED_COLOR = COLOR_GREEN; } /* Plot a character onscreen */ void curs_plot(int ch, int x, int y, int color, int bold) { if (x < 0 || x > SCR_RIGHT || y < 0 || y > SCR_BOTTOM) return; if (bold) attron(A_BOLD); attron(COLOR_PAIR(color)); mvaddch(y, x, ch); attrset(A_NORMAL); } /* Change program params during runtime */ void change_params(int ch) { int x, y; switch (ch) { case 'q': finish(0); break; case 'b': USE_RAND_BOLD = !USE_RAND_BOLD; break; case 'n': USE_RAND_BOLD = FALSE; USE_RAND_HEAD = FALSE; case 'B': USE_RAND_HEAD = !USE_RAND_HEAD; break; case 'C': USE_RAND_COLOR = !USE_RAND_COLOR; break; case '!': FIXED_COLOR = COLOR_RED; break; case '@': FIXED_COLOR = COLOR_GREEN; break; case '#': FIXED_COLOR = COLOR_YELLOW; break; case '$': FIXED_COLOR = COLOR_BLUE; break; case '%': FIXED_COLOR = COLOR_MAGENTA; break; case '^': FIXED_COLOR = COLOR_CYAN; break; case '&': FIXED_COLOR = COLOR_WHITE; break; } /* Update all the streams with the new settings right away */ for (x = 0; x < NUM_COLS; x++) for (y = 0; y < NUM_STREAMS; y++) update_stream_params(&cols[x].streams[y]); } /* Update a stream's parameters */ void update_stream_params(stream_t* stream) { if (USE_RAND_COLOR) stream->color = rand_num(MIN_COLOR, MAX_COLOR); else stream->color = FIXED_COLOR; if (USE_RAND_HEAD == FALSE) stream->bold_head = FALSE; } /* Resize the screen during runtime, after a SIGWINCH */ void resize_screen(int u) { int x; int old_num_cols = NUM_COLS; endwin(); initscr(); /* Grab the new dimensions from stdscr */ getmaxyx(stdscr, SCR_BOTTOM, SCR_RIGHT); /* Change the number of visible columns */ NUM_COLS = SCR_RIGHT / COL_GAP; if (old_num_cols < NUM_COLS) { /* Allocate every new column */ cols = (col_t*)realloc(cols, sizeof(col_t) * NUM_COLS); for (x = old_num_cols; x < NUM_COLS; x++) init_col(cols + x); } else if (old_num_cols > NUM_COLS) { /* Free every deleted column */ for (x = NUM_COLS; x < old_num_cols; x++) free_col(cols + x); cols = (col_t*)realloc(cols, sizeof(col_t) * NUM_COLS); } /* Immediately update display */ init_screen(); } /* Cleanup and exit */ void finish(int u) { int x; clear(); /* Clear the screen */ refresh(); /* Update screen */ endwin(); /* Shut down curses */ /* Free streams and columns */ for (x = 0; x < NUM_COLS; x++) free_col(cols + x); free(cols); exit(EXIT_SUCCESS); } /* Fill a given column structure with random starting data */ void init_col(col_t* col) { int bottom, height; stream_t* s; stream_t* s_end; /* TODO: adjust NUM_STREAMS to account for screen height */ s = (stream_t*)malloc(sizeof(stream_t) * NUM_STREAMS); col->streams = s; col->cycle = rand_num(MIN_CYCLE, MAX_CYCLE); col->time = rand_num(0, col->cycle - 1); /* Initialize the streams bottom-to-top */ bottom = -rand_num(MIN_GAP, MAX_HEIGHT); for (s_end = s + NUM_STREAMS; s < s_end; s++) { height = rand_num(MIN_HEIGHT, MAX_HEIGHT); s->bottom = bottom; s->top = bottom - height; if (USE_RAND_COLOR) s->color = rand_num(MIN_COLOR, MAX_COLOR); else s->color = FIXED_COLOR; s->bold_head = rand_num(FALSE, USE_RAND_HEAD); s->head_char = rand_char(); /* Go up above this stream for the next one */ bottom -= height + rand_num(MIN_GAP, MAX_GAP); } } /* Deallocate a column's internal data */ void free_col(col_t* col) { free(col->streams); } /* Show the streams in their initial positions onscreen */ void init_screen(void) { int x, y, z, bottom, top; stream_t* s; /* Clear the screen */ clear(); for (x = 0; x < NUM_COLS; x++) for (y = 0; y < NUM_STREAMS; y++) { s = &cols[x].streams[y]; /* Draw each character individually */ for (z = s->top; z <= s->bottom; z++) { if (z == s->bottom && s->bold_head) /* Draw white head character */ curs_plot(s->head_char, x * COL_GAP, z, COLOR_WHITE, TRUE); else /* Draw ordinary character and maybe make it bold */ curs_plot(rand_char(), x * COL_GAP, z, s->color, rand_num(FALSE, USE_RAND_BOLD)); } } /* Update screen */ refresh(); } /* Reset a stream at the top of its column */ void reset_stream(col_t* column, int stream_id) { int y, top, bottom; stream_t* s = &column->streams[stream_id]; /* Find top of all streams */ top = SCR_BOTTOM + 1; for (y = 0; y < NUM_STREAMS; y++) top = min(top, column->streams[y].top); /* Move stream up above previous top */ bottom = s->bottom = top - rand_num(MIN_GAP, MAX_GAP); s->top = bottom - rand_num(MIN_HEIGHT, MAX_HEIGHT); if (USE_RAND_COLOR) s->color = rand_num(MIN_COLOR, MAX_COLOR); else s->color = FIXED_COLOR; s->bold_head = rand_num(FALSE, USE_RAND_HEAD); s->head_char = rand_char(); } /* Move the streams one step down */ void move_streams(int u) { int x, y, keypress; stream_t* s; /* If the user pressed a key, process the keystroke */ if ((keypress = wgetch(stdscr)) != ERR) change_params(keypress); for (x = 0; x < NUM_COLS; x++) /* Increment timer and move streams if necessary */ if (++cols[x].time >= cols[x].cycle) { cols[x].time = 0; for (y = 0; y < NUM_STREAMS; y++) { s = &cols[x].streams[y]; /* Erase top character */ curs_plot(' ', x * COL_GAP, s->top, COLOR_BLACK, FALSE); /* If there's a white head, dim it */ if (s->bold_head) curs_plot(s->head_char, x * COL_GAP, s->bottom, s->color, rand_num(FALSE, USE_RAND_BOLD)); /* Move down */ s->top++; s->bottom++; s->head_char = rand_char(); /* Move to very top if necessary */ if (s->top > SCR_BOTTOM) reset_stream(&cols[x], y); /* Draw bottom character */ if (s->bold_head) /* Draw white head character */ curs_plot(s->head_char, x * COL_GAP, s->bottom, COLOR_WHITE, TRUE); else /* Draw ordinary character and maybe make it bold */ curs_plot(s->head_char, x * COL_GAP, s->bottom, s->color, rand_num(FALSE, USE_RAND_BOLD)); } } /* Update screen */ refresh(); } /* End of file */