/* Hodge -- funky effect for (fast) ASCII character displays. * Idea from a "Computer Recreations" article in Scientific American years ago. * -- * Build with: cc -O2 -o hodge hodge.c -lm -ltermcap * -- * Original scary Commodore 64 assembler version: circa 1987 * Interpreted Pascal version: sometime in 1989 * Copylefted C version: August 1994 (and a svgalib version by Leon Garde) * Next major revision: October 1997 (optimisations, termcap, arguments) * -- * Chris Baird,, Belly-button Elf Productions. */ #include #include #include #include #include #include #include #include #include extern int tgetent(char *bp, char *name); /* termcap */ extern char* tgetstr(char *id, char **area); extern int tgetnum(char *id); extern char *optarg; /* getopt(3) stuff */ extern int optind; #define CHARBASE 32 #define RANGE 94 #define MAXMAP (5) /* different patterns available */ char *cell, *cell2; /* the CA workspaces */ int K1 = 1; /* CA parameters: influence of live cells */ int K2 = 2; /* influence of dead cells */ int G = 5; /* ~= speed of propagation */ int XMAX = 0, YMAX = 0; /* CA dimensions (default: screen size) */ int slowmo = 0; /* if wait for keypress */ int mutate = 0; /* set if steady-states aren't your thing */ int map = 0; /* which inital cell pattern to use */ int showfrate = 0; /* how often to calculate frame rate */ struct timeval then; char t_cl[16], t_cm[16], t_nl[16], home[16]; /* termcap things */ void swapcells () { register char *t; t = cell; cell = cell2; cell2 = t; } void draw (void) { register char *p, *q; q = cell + (XMAX * YMAX); for (p = cell; p < q; p += XMAX) { /* my kingdom for a faster portable blit to the screen */ fwrite (p, sizeof(char), XMAX, stdout); printf (t_nl); } } void calc (void) { register int a, b, s, c; register char *p; int x, y; char d, *p2; for (y = 1; y < YMAX - 1; y++) { p2 = cell2 + 1 + (y*XMAX); for (x = 1; x < XMAX - 1; x++) { #define test(p) c = *(p) - CHARBASE; \ if (c > 0) a++; \ if (c == RANGE) b++; \ s += c; a = b = s = 0; p = cell - 1 - XMAX + x + y*XMAX; test(p); /* [-1, -1] */ test(p+1); /* [-1, 0] */ test(p+2); /* [-1, +1] */ p+=XMAX*2;test(p); /* [+1, -1] */ test(p+1); /* [+1, 0] */ test(p+2); /* [+1, +1] */ p-=XMAX;test(p); /* [ 0, -1] */ test(p+2); /* [ 0, +1] */ test(p+1); /* [ 0, 0] */ if (c == 0) /* c was already set by the last test */ d = (int)(a / K1) + (int)(b / K2); else if (c < RANGE) { d = (int)(s / a) + G; if (d > RANGE) d = RANGE; } else d = 0; *p2++ = d + CHARBASE; } } } void set_initial_state (void) { register int x, y; register char *p, *p2; p = cell; p2 = cell2; for (y = 0; y < YMAX ; y++) for (x = 0; x < XMAX; x++) { switch (map) { case 1: *p = abs (RANGE * sin (x * y + x + y)); break; case 2: *p = abs (RANGE * sin ((1+x) * y)); break; case 3: *p = random () % 4; break; case 5: *p = random () % RANGE; if (x > 0 && x < (XMAX-1) && y > 0 && y < (YMAX-1)) *p = 0; break; case 4: *p = 0; /* set mutate>0 if trying this */ break; default: *p = random () % RANGE; } *p += CHARBASE; *p2++ = *p++; } } void init_term(void) { char *term; char termbuf[1024]; char *tp; if ((term= getenv("TERM")) == NULL || tgetent(termbuf, term) != 1) { fprintf(stderr, "Can't get terminal capabilities.\n"); exit(1); } if (tgetstr("cl", (tp = t_cl, &tp)) == NULL || tgetstr("cm", (tp = t_cm, &tp)) == NULL) { fprintf(stderr, "This terminal is too dumb.\n"); exit(1); } sprintf (home, t_cm, 0, 0, 0); if (tgetstr("nl", (tp = t_nl, &tp)) == NULL) strcpy (t_nl, "\n"); if (XMAX == 0) XMAX = tgetnum("co") - 1; if (YMAX == 0) YMAX = tgetnum("li") - 1; } void home_cursor(void) { printf (home); } void clear_screen(void) { printf (t_cl); } void get_frame_rate(void) { struct timeval now; static float bestfps = 0; float d, fps; gettimeofday(&now, NULL); d = now.tv_sec - then.tv_sec + ((float)(now.tv_usec - then.tv_usec) / 1000000); fps = showfrate / d; then.tv_sec = now.tv_sec; then.tv_usec = now.tv_usec; if (fps > bestfps) bestfps = fps; printf ("Frame rate =%6.1f fps Best =%6.1f", fps, bestfps); fflush(stdout); } void usage(void) { exit(1); } int main (int argc, char** argv) { int c, frame = 0; long p; while ((c = getopt(argc, argv, "g:l:d:wm:p:x:y:f")) != -1) switch (c) { #define ERROR { printf ("Unnacceptable value for '%c'.\n", c); exit (1); } case 'g': G = atoi (optarg); if (G < 1) ERROR; break; case 'l': K1 = atoi (optarg); if (K1 < 1) ERROR; break; case 'd': K2 = atoi (optarg); if (K2 < 1) ERROR; break; case 'w': slowmo++; break; case 'm': mutate = atoi (optarg); if (mutate < 1) ERROR; break; case 'p': map = atoi (optarg); if (map < 1 || map > MAXMAP) ERROR; break; case 'x': XMAX = atoi (optarg); if (XMAX < 3 ) ERROR; break; case 'y': YMAX = atoi (optarg); if (YMAX < 3 ) ERROR; break; case 'f': showfrate = 100; break; default: usage(); } init_term(); /* get/set screen related info */ cell = malloc(XMAX*YMAX); cell2 = malloc(XMAX*YMAX); srandom (time (NULL)); /* seed random number generator */ clear_screen (); set_initial_state (); gettimeofday(&then, NULL); while (1) { frame++; home_cursor (); draw (); if (slowmo != 0) if ((c=getchar ())=='q') exit(0); calc (); swapcells (); if (mutate != 0 && (frame % mutate) == 0) { p = random () % (YMAX*XMAX); *(cell2+p) = *(cell+p) = CHARBASE + (random () % RANGE); } if (showfrate != 0 && (frame % showfrate) == 0) get_frame_rate (); } }