Life game for the UNIX PC
Karl F. Fox
karl at zip.UUCP
Thu Oct 26 01:10:53 AEST 1989
Here's another graphics game for the UNIX PC. Please send me bug
reports, bug fixes, interesting new patterns, and suggestions for
improvements.
Note: This code is designed to be fast, not small. Look:
% size /usr/games/life
7432(.text) + 1148(.data) + 518884(.bss) + 0(.lib) = 527464
My apologies if you only have a .5M machine.
#!/bin/sh
#
# This is a SHAR archive. To extract, remove everything
# above the #!/bin/sh line and type "sh filename"
#
if `test ! -s ./README`
then
echo "Writing ./README"
sed 's/^X//' > ./README << '\End\of\File\'
XThis is John Horton Conway's game of life for the AT&T UNIX PC.
X
X
XHow to make it:
X
XType "make".
X
X
XHow to use it:
X
XJust run it. There are no arguments. Stop it by giving it a SIGINT or
XSIGTERM or by typing any key (except a digit) to the display window.
XTyping digit n will set the time to sleep between generations to n/10 of
Xa second.
X
XPressing the middle mouse button freezes or resumes animation, and
Xpressing the right mouse button kills all the cells. When the left
Xmouse button is pressed *down* the cursor changes from a tiny dot
X(which, by the way, will disappear completely if places in the far upper
Xright corner of the screen) to a life cell, which can be placed on the
Xscreen by releasing the button. Depositing the new cell on an old cell
Xkills it.
X
XWhen the arena gets into a stable configuration (all dead, completely
Xstatic or a pattern repeating every 63 generations or less), a glider or
Xspaceship will be injected to stir up the action. After a few tries at
Xthis, the screen will be cleared and a random starting pattern will be
Xselected from the file /usr/local/lib/life-patterns. Note that the cell
Xarena extends a ways beyond the boundaries of the visible screen, so an
Xapparently static configuration may be active off-screen.
X
XPlease send me any interesting new startup patterns that you discover
X(karl at MorningStar.Com or karl at zip.UUCP).
X
XHave fun!
X
XP.S. Send me email if you want my xlock driver.
X
X@(#)README 1.1
\End\of\File\
else
echo "Not overwriting ./README"
fi
if `test ! -s ./Makefile`
then
echo "Writing ./Makefile"
sed 's/^X//' > ./Makefile << '\End\of\File\'
X# @(#)Makefile 1.1
X
X#
X# Makefile for the game of life
X#
X# karl at MorningStar.Com or karl at zip.UUCP
X#
X
XLDFLAGS = /lib/crt0s.o /lib/shlib.ifile
X
Xlife: life.o
X $(LD) -o $@ life.o $(LDFLAGS)
X
Xlife.o: life.c lifegame.c
X $(CC) $(CFLAGS) -c life.c
X
Xclean:
X rm -f life *.o core
Xlint:
X lint -p life.c
X
XREADME: s.README
X $(GET) $(GFLAGS) -p s.README > $@
X
Xlife.shar: README Makefile life.c lifegame.c
X -shar README Makefile life.c lifegame.c > life.shar
\End\of\File\
else
echo "Not overwriting ./Makefile"
fi
if `test ! -s ./life.c`
then
echo "Writing ./life.c"
sed 's/^X//' > ./life.c << '\End\of\File\'
X/*
X * Copyright (c) 1989 Karl F. Fox, all rights reserved
X *
X * You may use this software for any purpose as long as you include
X * this copyright notice.
X */
X
X# ifndef lint
Xstatic char life_sccs_id[] = "@(#)life.c 1.1";
X# endif /* lint */
X
X/*
X * life.c
X *
X * John Horton Conway's game of life for the AT&T UNIX PC.
X *
X *
X * How to use it:
X *
X *
X * Just run it. There are no arguments. Stop it by giving it a
X * SIGINT or SIGTERM or by typing any key (except a digit) to the
X * display window. Typing digit n will set the time to sleep
X * between generations to n/10 of a second.
X *
X * Pressing the middle mouse button freezes or resumes animation,
X * and pressing the right mouse button kills all the cells. When
X * the left mouse button is pressed *down* the cursor changes from
X * a tiny dot (which, by the way, will disappear completely if
X * places in the far upper right corner of the screen) to a life
X * cell, which can be placed on the screen by releasing the button.
X * Depositing the new cell on an old cell kills it.
X *
X * When the arena gets into a stable configuration (all dead,
X * completely static or a pattern repeating every 63 generations or
X * less), a glider or spaceship will be injected to stir up the
X * action. After a few tries at this, the screen will be cleared
X * and a random starting pattern will be selected from the file
X * /usr/local/lib/life-patterns. Note that the cell arena extends
X * a ways beyond the boundaries of the visible screen, so an
X * apparently static configuration may be active off-screen.
X *
X * Please send me any interesting new startup patterns that you
X * discover (karl at MorningStar.Com or karl at zip.UUCP).
X *
X * Have fun!
X */
X
X# include <stdio.h>
X# include <termio.h>
X# include <fcntl.h>
X# include <sys/window.h>
X# include <sys/font.h>
X# include <signal.h>
X# include <string.h>
X# include <memory.h>
X
X# define K_OK 0
X# define K_OTHER 1
X# define K_EXIT 2
X
X# define ESC '\033'
X
X/*
X * Mouse event string character classes
X */
X# define ESCAPE 0
X# define SEMI 1
X# define M 2
X# define O 3
X# define DIGIT 4
X# define DEFAULT 5
X
Xint mouse_state = 0,
X mouse_x = 0,
X mouse_y = 0,
X mouse_buttons = 0,
X mouse_reason = 0;
X
Xstruct termio orig_tm;
X
X# define PATTERN_FILE "/usr/local/lib/life-patterns"
X
X# define SYSV
X
X# ifdef SYSV
X# define srandom(seed) srand((unsigned)(seed))
X# define random() rand()
X# define bcmp(a,b,c) memcmp((char *)(b), (char *)(a), (int)(c))
X# define bzero(a,c) memset((char *)(a), 0, (int)(c))
X# endif /* SYSV */
X
Xstatic int width, height, xs, ys, xb, yb, xp, yp;
X
X# define NROWS 128
X# define NCOLS 256
X
X# define XS1 4
X# define YS1 3
X# define NR1 64
X# define NC1 64
X# define XS2 1
X# define YS2 1
X
Xextern int open(), close(), read(), write(), ioctl(), rand(), memcmp(),
X fprintf(), fcntl();
Xextern void srand(), exit(), perror();
Xextern unsigned sleep();
Xextern long time();
X
Xint wd;
X
Xvoid main()
X {
X extern void process();
X
X if ((wd = open("/dev/window", O_RDWR)) < 0)
X {
X (void)perror("Can't open '/dev/window'\7");
X (void)exit(1);
X }
X
X process();
X (void)close(wd);
X (void)exit(0);
X /*NOTREACHED*/
X }
X
Xvoid terminate()
X {
X extern void cursor_visible(), mouse_off(), term_reset();
X
X static int in_terminate = 0;
X
X if ( ! in_terminate)
X {
X ++in_terminate;
X cursor_visible();
X mouse_off();
X term_reset();
X (void)close(wd);
X (void)exit(1);
X }
X }
X
Xvoid cursor_invisible()
X {
X if (write(wd, "\033[=1C", 5) != 5)
X {
X (void)perror("Can't set cursor invisibility\7");
X terminate();
X }
X }
X
Xvoid cursor_visible()
X {
X if (write(wd, "\033[=0C", 5) != 5)
X {
X (void)perror("Can't restore cursor visibility\7");
X terminate();
X }
X }
X
X# define WIDTH 720
X# define HEIGHT 300
X
Xint running = 1, delay = 0;
X
Xvoid process()
X {
X extern void mouse_on(), term_cbreak();
X extern int set_window_parameters();
X
X if (set_window_parameters(0, 0, WIDTH, HEIGHT, NBORDER) < 0)
X return;
X
X term_cbreak(0, 0);
X mouse_on(1, 1, WIDTH, HEIGHT);
X (void)signal(SIGTERM, (int (*)())terminate);
X (void)signal(SIGINT, (int (*)())terminate);
X
X cursor_invisible();
X
X for (;;)
X {
X extern void initlife(), draw_life_game();
X extern int life_game_done();
X
X initlife(WIDTH, HEIGHT);
X
X do
X {
X register int i, n;
X char buffer[64];
X
X do
X {
X extern int do_key();
X
X if ((n = read(wd, buffer, sizeof buffer)) > 0)
X for (i = 0; i < n; ++i)
X switch (do_key(buffer[i]))
X {
X case K_OK:
X break;
X
X case K_OTHER:
X case K_EXIT:
X terminate();
X }
X }
X while ( ! running);
X
X
X draw_life_game();
X }
X while ( ! life_game_done());
X }
X
X /*NOTREACHED*/
X }
X
Xint set_window_parameters(x, y, w, h, uflags)
Xregister int x, y, w, h, uflags;
X {
X struct uwdata uwdata;
X struct utdata utdata;
X
X if (ioctl(wd, WIOCGETD, &uwdata) < 0)
X {
X (void)perror("Can't perform WIOCGETD\7");
X return (-1);
X }
X
X uwdata.uw_x = x;
X uwdata.uw_y = y;
X uwdata.uw_width = w;
X uwdata.uw_height = h;
X uwdata.uw_uflags = uflags;
X
X if (ioctl(wd, WIOCSETD, &uwdata) < 0)
X {
X (void)perror("Can't perform WIOCSETD\7");
X return (-1);
X }
X
X (void)strncpy(utdata.ut_text, "life", WTXTLEN);
X utdata.ut_num = WTXTUSER;
X
X if (ioctl(wd, WIOCSETTEXT, &utdata) < 0)
X {
X (void)perror("Can't perform WIOCSETTEXT\7");
X return (-1);
X }
X
X return (0);
X }
X
Xint window_rastop(srcbase, srcwidth, dstbase, dstwidth, srcx, srcy, dstx,
X dsty, bltwidth, bltheight, srcop, dstop, pattern)
Xunsigned short *srcbase, srcwidth, *dstbase, dstwidth, srcx, srcy, dstx,
X dsty, bltwidth, bltheight, *pattern;
Xchar srcop, dstop;
X {
X struct urdata urdata;
X
X urdata.ur_srcbase = srcbase;
X urdata.ur_srcwidth = srcwidth;
X urdata.ur_dstbase = dstbase;
X urdata.ur_dstwidth = dstwidth;
X urdata.ur_srcx = srcx;
X urdata.ur_srcy = srcy;
X urdata.ur_dstx = dstx;
X urdata.ur_dsty = dsty;
X urdata.ur_width = bltwidth;
X urdata.ur_height = bltheight;
X urdata.ur_srcop = srcop;
X urdata.ur_dstop = dstop;
X urdata.ur_pattern = pattern;
X
X if (ioctl(wd, WIOCRASTOP, &urdata) < 0)
X {
X (void)perror("Can't perform WIOCRASTOP\7");
X return (-1);
X }
X
X return (0);
X }
X
X# define DISPLAY_WIDTH ((WIDTH + 7) / 8)
X
Xunsigned short display[DISPLAY_WIDTH/2 * HEIGHT];
X
Xunsigned short white_pattern[] =
X {
X 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
X 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF
X };
X
Xunsigned short black_pattern[] =
X { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
X
Xvoid draw_rectangle(x, y, xsize, ysize)
Xint x, y, xsize, ysize;
X {
X register unsigned short *s = &display[(y * DISPLAY_WIDTH/2) + (x >> 4)];
X
X if (xsize == 3 && ysize == 2)
X switch (x & 15)
X {
X case 0: *s |= 0x0007; s += DISPLAY_WIDTH/2;
X *s |= 0x0007;
X return;
X case 4: *s |= 0x0070; s += DISPLAY_WIDTH/2;
X *s |= 0x0070;
X return;
X case 8: *s |= 0x0700; s += DISPLAY_WIDTH/2;
X *s |= 0x0700;
X return;
X case 12: *s |= 0x7000; s += DISPLAY_WIDTH/2;
X *s |= 0x7000;
X return;
X }
X
X if (window_rastop(0, 0, display, DISPLAY_WIDTH, 0, 0, (unsigned short)x,
X (unsigned short)y, (unsigned short)xsize, (unsigned short)ysize,
X SRCPAT, DSTSRC, white_pattern) < 0)
X (void)fprintf(stderr,
X "window_rastop() failed in draw_rectangle(%d, %d, %d, %d)\n",
X x, y, xsize, ysize);
X }
X
Xvoid erase_rectangle(x, y, xsize, ysize)
Xint x, y, xsize, ysize;
X {
X register unsigned short *s = &display[(y * DISPLAY_WIDTH/2) + (x >> 4)];
X
X if (xsize == 4 && ysize == 3)
X switch (x & 15)
X {
X case 0: *s &= ~0x000F; s += DISPLAY_WIDTH/2;
X *s &= ~0x000F; s += DISPLAY_WIDTH/2;
X *s &= ~0x000F;
X return;
X case 4: *s &= ~0x00F0; s += DISPLAY_WIDTH/2;
X *s &= ~0x00F0; s += DISPLAY_WIDTH/2;
X *s &= ~0x00F0;
X return;
X case 8: *s &= ~0x0F00; s += DISPLAY_WIDTH/2;
X *s &= ~0x0F00; s += DISPLAY_WIDTH/2;
X *s &= ~0x0F00;
X return;
X case 12: *s &= ~0xF000; s += DISPLAY_WIDTH/2;
X *s &= ~0xF000; s += DISPLAY_WIDTH/2;
X *s &= ~0xF000;
X return;
X }
X
X if (window_rastop(0, 0, display, DISPLAY_WIDTH, 0, 0, (unsigned short)x,
X (unsigned short)y, (unsigned short)xsize, (unsigned short)ysize,
X SRCPAT, DSTSRC, black_pattern) < 0)
X (void)fprintf(stderr,
X "window_rastop() failed in erase_rectangle(%d, %d, %d, %d)\n",
X x, y, xsize, ysize);
X }
X
Xvoid update_display()
X {
X if (window_rastop(display, DISPLAY_WIDTH, 0, 0, 0, 0, 0, 0,
X (unsigned short)width, (unsigned short)height,
X SRCSRC, DSTSRC, 0) < 0)
X (void)fprintf(stderr,
X "window_rastop() failed in update_display()\n");
X }
X
X/*ARGSUSED*/
Xstatic int new_color(color)
Xint color;
X {
X return (0);
X }
X
X/*ARGSUSED*/
Xstatic void draw(row, col, age)
Xint row, col, age;
X {
X draw_rectangle(xb + xs * col, yb + ys * row, xp, yp);
X }
X
Xstatic void erase(row, col)
Xint row, col;
X {
X erase_rectangle(xb + xs * col, yb + ys * row, xs, ys);
X }
X
Xvoid initlife(w, h)
Xint w, h;
X {
X extern void init_life_game();
X
X int rows, cols;
X
X width = w;
X height = h;
X
X erase_rectangle(0, 0, width, height);
X
X xs = XS1;
X ys = YS1;
X
X if (width / xs < NC1 || height / ys < NR1)
X {
X xs = XS2;
X ys = YS2;
X }
X
X cols = width / xs;
X rows = height / ys;
X
X if (xs <= 2 || ys <= 2)
X xp = xs, yp = ys;
X else if (xs <= 4 || ys <= 4)
X xp = xs - 1, yp = ys - 1;
X else
X xp = xs - 2, yp = ys - 2;
X
X xb = (width - xs * cols) / 2;
X yb = (height - ys * rows) / 2;
X
X init_life_game(rows, cols, 0);
X }
X
Xvoid term_cbreak(vmin, vtime)
Xint vmin, vtime;
X {
X static int initialized = 0;
X struct termio tm;
X
X if ( ! initialized)
X {
X if (ioctl(wd, TCGETA, &orig_tm) < 0)
X {
X perror("Can't get terminal characteristics\7");
X terminate();
X }
X
X ++initialized;
X }
X
X tm = orig_tm;
X
X tm.c_cc[VMIN] = vmin;
X tm.c_cc[VTIME] = vtime;
X tm.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHOK | ECHONL);
X
X if (ioctl(wd, TCSETA, &tm) < 0)
X {
X perror("Can't set terminal characteristics\7");
X terminate();
X }
X }
X
Xvoid term_reset()
X {
X if (ioctl(wd, TCSETA, &orig_tm) < 0)
X {
X perror("Can't reset terminal characteristics\7");
X terminate();
X }
X }
X
X/*
X * Life cell cursor:
X *
X * ***
X * ***
X */
Xstruct icon cell_icon =
X {
X 0, /* ic_flags (ignored) */
X
X { /* struct fcdef ic_fc */
X 3, /* fc_hs */
X 2, /* fc_vs */
X -2, /* fc_ha */
X -1, /* fc_va */
X 0, /* fc_hi */
X 0, /* fc_vi */
X 0 /* fc_mr */
X },
X
X { /* unsigned short ic_raster[64] */
X 7,
X 7
X }
X };
X
Xstruct icon tiny_icon =
X {
X 0, /* ic_flags (ignored) */
X
X { /* struct fcdef ic_fc */
X 1, /* fc_hs */
X 1, /* fc_vs */
X -1, /* fc_ha */
X -1, /* fc_va */
X 0, /* fc_hi */
X 0, /* fc_vi */
X 0 /* fc_mr */
X },
X
X { /* unsigned short ic_raster[64] */
X 1
X }
X };
X
Xstruct umdata umdata;
X
Xvoid mouse_cursor(cursor)
Xstruct icon *cursor;
X {
X umdata.um_icon = cursor;
X
X if (ioctl(wd, WIOCSETMOUSE, &umdata) < 0)
X {
X perror("Can't set mouse characteristics\7");
X terminate();
X }
X }
X
Xvoid mouse_on(x, y, w, h)
Xregister int x, y, w, h;
X {
X umdata.um_flags = MSUP | MSDOWN;
X umdata.um_x = x;
X umdata.um_y = y;
X umdata.um_w = w;
X umdata.um_h = h;
X mouse_cursor(&tiny_icon);
X }
X
Xvoid mouse_off()
X {
X umdata.um_flags = 0;
X mouse_cursor((struct icon *)0);
X }
X
X/*ARGSUSED*/
Xint m_null(c)
Xchar c;
X {
X return (K_OK);
X }
X
X/*ARGSUSED*/
Xint m_err(c)
Xchar c;
X {
X mouse_state = 0;
X return (K_OTHER);
X }
X
X/*ARGSUSED*/
Xint m_init(c)
Xchar c;
X {
X mouse_x = mouse_y = mouse_buttons = mouse_reason = 0;
X mouse_state = 1;
X return (K_OK);
X }
X
Xint m_digit(c)
Xchar c;
X {
X term_cbreak( ! running, delay = c - '0');
X mouse_state = 0;
X return (K_OK);
X }
X
Xint m_x(c)
Xchar c;
X {
X mouse_x = (mouse_x * 10) + c - '0';
X return (K_OK);
X }
X
Xint m_y(c)
Xchar c;
X {
X mouse_y = (mouse_y * 10) + c - '0';
X return (K_OK);
X }
X
Xint m_buttons(c)
Xchar c;
X {
X mouse_buttons = (mouse_buttons * 10) + c - '0';
X return (K_OK);
X }
X
Xint m_reason(c)
Xchar c;
X {
X mouse_reason = (mouse_reason * 10) + c - '0';
X return (K_OK);
X }
X
Xint m_key(c)
Xchar c;
X {
X switch (c)
X {
X case 'w': case 'W': /* Exit key or close box selection */
X return (K_EXIT);
X
X default:
X return (K_OTHER);
X }
X }
X
X/*ARGSUSED*/
Xint m_2(c)
Xchar c;
X {
X mouse_state = 2;
X return (K_OK);
X }
X
X/*ARGSUSED*/
Xint m_3(c)
Xchar c;
X {
X mouse_state = 3;
X return (K_OK);
X }
X
X/*ARGSUSED*/
Xint m_4(c)
Xchar c;
X {
X mouse_state = 4;
X return (K_OK);
X }
X
X/*ARGSUSED*/
Xint m_5(c)
Xchar c;
X {
X mouse_state = 5;
X return (K_OK);
X }
X
X# define LEFT_BUTTON 4
X# define MIDDLE_BUTTON 2
X# define RIGHT_BUTTON 1
X
X/*ARGSUSED*/
Xint m_push(c)
Xchar c;
X {
X extern void reversecell(), clear_the_board();
X
X static int old_mouse_buttons = 0;
X
X if (mouse_reason & MSDOWN)
X {
X if (mouse_buttons & LEFT_BUTTON)
X mouse_cursor(&cell_icon);
X
X if (mouse_buttons & MIDDLE_BUTTON)
X {
X running = ! running;
X term_cbreak( ! running, delay);
X }
X
X if (mouse_buttons & RIGHT_BUTTON)
X clear_the_board();
X }
X
X if (mouse_reason & MSUP)
X if (old_mouse_buttons & LEFT_BUTTON)
X {
X reversecell((mouse_y - yb) / ys, (mouse_x - xb) / xs);
X mouse_cursor(&tiny_icon);
X }
X
X old_mouse_buttons = mouse_buttons;
X mouse_state = 0;
X return (K_OK);
X }
X
Xint (*(mouse_action[][6]))() =
X {
X/* State ESCAPE SEMI M O DIGIT DEFAULT */
X/* 0 */ { m_init, m_err, m_err, m_err, m_digit, m_err },
X/* 1 */ { m_err, m_2, m_err, m_5, m_x, m_null },
X/* 2 */ { m_err, m_3, m_err, m_err, m_y, m_err },
X/* 3 */ { m_err, m_4, m_err, m_err, m_buttons, m_err },
X/* 4 */ { m_err, m_err, m_push, m_err, m_reason, m_err },
X/* 5 */ { m_err, m_err, m_err, m_err, m_err, m_key },
X };
X
Xint do_key(c)
Xregister char c;
X {
X register int class;
X register int (*action)();
X
X/* # define ECHO_INPUT */
X# ifdef ECHO_INPUT
X fprintf(stderr, c == ESC ? "<ESC>" : "%c", c);
X# endif /* ECHO_INPUT */
X
X switch (c)
X {
X case ESC: class = ESCAPE; break;
X case ';': class = SEMI; break;
X case 'M': class = M; break;
X case 'O': class = O; break;
X case '0': case '1': case '2': case '3': case '4':
X case '5': case '6': case '7': case '8': case '9':
X class = DIGIT; break;
X default: class = DEFAULT;
X }
X
X action = mouse_action[mouse_state][class];
X return ((*action)(c));
X }
X
X# include "lifegame.c"
X
Xvoid reversecell(row, col)
Xint row, col;
X {
X register int index;
X
X row += firstrow;
X col += firstcol;
X index = row * NCOLS + col;
X
X /*
X * Draw/erase a life cell
X */
X if (alive[index])
X death(row, col);
X else
X birth(row, col);
X
X update_display();
X }
X
Xvoid clear_the_board()
X {
X (void)bzero(neighbors, sizeof(neighbors));
X (void)bzero(alive, sizeof(alive));
X (void)bzero(ages, sizeof(ages));
X (void)bzero(links, sizeof(links));
X head = links;
X head -> l_next = 0;
X erase_rectangle(0, 0, width, height);
X births = deaths = cells = 0;
X update_display();
X }
\End\of\File\
else
echo "Not overwriting ./life.c"
fi
if `test ! -s ./lifegame.c`
then
echo "Writing ./lifegame.c"
sed 's/^X//' > ./lifegame.c << '\End\of\File\'
X/*
X * Copyright (c) 1989 Karl F. Fox, all rights reserved
X *
X * You may use this software for any purpose as long as you include
X * this copyright notice.
X */
X
X# ifndef lint
Xstatic char lifegame_sccs_id[] = "@(#)lifegame.c 1.1";
X# endif /* lint */
X
X/*
X * lifegame.c
X *
X * Machine-independent (more or less) implementation of John Horton
X * Conway's game of life.
X *
X *
X * How to use it:
X *
X * Include this file (# include "lifegame.c") from a driver program.
X * The driver should call init_life_game(rows, cols, color) once and
X * then call draw_life_game() followed by life_game_done() until
X * the latter returns non-zero. The including file should #define
X * NROWS and NCOLS (the maximum size of the cell grid), both of
X * which should be powers of two unless you don't mind glacial-speed
X * animation. The driver code should supply the following routines:
X *
X * draw(row, col, color) Display a cell. Color
X * is ignored if init_life_game()
X * was passed a zero color flag.
X * erase(row, col) Erase a cell from the display.
X * new_color(color) When using color, return a new
X * color if aging cells should
X * gradually color-shift.
X * update_display() Flush changes to the display.
X *
X * Also, # define the following if you're not on a BSD system:
X *
X * # define srandom(seed) srand(seed)
X * # define random() rand()
X * # define bcmp(a,b,c) memcmp((b),(a),(c))
X * # define bzero(a,c) memset((a),0,(c))
X */
X
X# include <stdio.h>
X
Xstatic int n_patterns, births = 0, deaths = 0, cells = 0, shot_time = 0,
X shots = 0, use_color, nrows, ncols, firstrow, firstcol, lastrow,
X lastcol;
X
Xtypedef struct link
X {
X struct link *l_next;
X } link_t;
X
Xstatic link_t links[NROWS * NCOLS], *head;
Xstatic unsigned char neighbors[NROWS * NCOLS], alive[NROWS * NCOLS],
X ages[NROWS * NCOLS];
X
Xtypedef struct
X {
X enum
X {
X c_age,
X c_birth,
X c_death
X } c_op_code;
X
X union
X {
X int cu_index;
X unsigned short cu_coord[2];
X } c_un;
X# define c_index c_un.cu_index
X# define c_row c_un.cu_coord[0]
X# define c_col c_un.cu_coord[1]
X } change_t;
X
Xstatic change_t changevec[NROWS * NCOLS];
X
X# define HISTORY_SIZE 128
X
Xstatic short history[HISTORY_SIZE],
X *historyp = history,
X *history_end = &history[HISTORY_SIZE];
X
X# define DRAW(row,col,age) if (row >= firstrow && row <= lastrow \
X && col >= firstcol && col <= lastcol) \
X draw(row - firstrow, col - firstcol, age)
X
X# define ERASE(row,col) if (row >= firstrow && row <= lastrow \
X && col >= firstcol && col <= lastcol) \
X erase(row - firstrow, col - firstcol)
X
X# define ENQUEUE(lp) if ((lp) -> l_next == 0) \
X ((lp) -> l_next = head, \
X head = (lp))
X
X# define NB_ALIVE(np, lp) if (++*(np) == 3 || *(np) == 4) \
X ENQUEUE(lp)
X
X# define NB_DEAD(np, lp) if (--*(np) == 3 || *(np) == 1) \
X ENQUEUE(lp)
X
Xstatic void birth(row, col)
Xint row, col;
X {
X register int index = row * NCOLS + col;
X register unsigned char *np;
X register link_t *lp;
X
X alive[index] = 1;
X ++cells;
X ++births;
X ages[index] = 0;
X DRAW(row, col, 0);
X
X np = neighbors + index - NCOLS - 1; lp = links + index - NCOLS - 1;
X NB_ALIVE(np, lp);
X
X ++np; ++lp;
X NB_ALIVE(np, lp);
X
X ++np; ++lp;
X NB_ALIVE(np, lp);
X
X np += NCOLS - 2; lp += NCOLS - 2;
X NB_ALIVE(np, lp);
X
X ENQUEUE(lp + 1); /* This line is only needed for patterns or */
X /* shooting gliders or spaceships */
X
X np += 2; lp += 2;
X NB_ALIVE(np, lp);
X
X np += NCOLS - 2; lp += NCOLS - 2;
X NB_ALIVE(np, lp);
X
X ++np; ++lp;
X NB_ALIVE(np, lp);
X
X ++np; ++lp;
X NB_ALIVE(np, lp);
X }
X
Xstatic void death(row, col)
Xint row, col;
X {
X register int index = row * NCOLS + col;
X register unsigned char *np;
X register link_t *lp;
X
X alive[index] = 0;
X --cells;
X ++deaths;
X ERASE(row, col);
X
X np = neighbors + index - NCOLS - 1; lp = links + index - NCOLS - 1;
X NB_DEAD(np, lp);
X
X ++np; ++lp;
X NB_DEAD(np, lp);
X
X ++np; ++lp;
X NB_DEAD(np, lp);
X
X np += NCOLS - 2; lp += NCOLS - 2;
X NB_DEAD(np, lp);
X
X ENQUEUE(lp + 1); /* This line is only needed for patterns or */
X /* shooting gliders or spaceships */
X
X np += 2; lp += 2;
X NB_DEAD(np, lp);
X
X np += NCOLS - 2; lp += NCOLS - 2;
X NB_DEAD(np, lp);
X
X ++np; ++lp;
X NB_DEAD(np, lp);
X
X ++np; ++lp;
X NB_DEAD(np, lp);
X }
X
Xvoid draw_life_game()
X {
X register link_t *lp = head;
X register change_t *cp = changevec, *endp;
X
X births = deaths = 0;
X
X while (lp)
X {
X register int index = lp - links;
X register int row = index / NCOLS, col = index % NCOLS;
X
X link_t *ltmp;
X
X ltmp = lp;
X lp = lp -> l_next;
X ltmp -> l_next = 0;
X
X if (row > 0 && row < NROWS - 1 && col > 0 && col < NCOLS - 1)
X switch (neighbors[index])
X {
X case 2: /* 2 neighbors -- survives if alive */
X if (alive[index])
X {
X register unsigned char *ageptr = ages + index, age;
X
X if (use_color
X && (age = new_color((int)*ageptr)) != *ageptr)
X {
X *ageptr = age;
X DRAW(row, col, (int)age);
X cp -> c_op_code = c_age;
X cp -> c_index = index;
X ++cp;
X }
X }
X
X break;
X
X case 3: /* 3 neighbors -- birth if none yet */
X if (alive[index])
X {
X register unsigned char *ageptr = ages + index, age;
X
X if (use_color
X && (age = new_color((int)*ageptr)) != *ageptr)
X {
X *ageptr = age;
X DRAW(row, col, (int)age);
X cp -> c_op_code = c_age;
X cp -> c_index = index;
X ++cp;
X }
X }
X else
X {
X cp -> c_op_code = c_birth;
X cp -> c_row = row;
X cp -> c_col = col;
X ++cp;
X }
X
X break;
X
X default: /* anything else -- cell dies */
X if (alive[index])
X {
X cp -> c_op_code = c_death;
X cp -> c_row = row;
X cp -> c_col = col;
X ++cp;
X }
X }
X }
X
X head = links;
X head -> l_next = 0;
X
X for (endp = cp, cp = changevec; cp < endp; ++cp)
X switch (cp -> c_op_code)
X {
X case c_birth: birth((int)cp -> c_row, (int)cp -> c_col); break;
X case c_death: death((int)cp -> c_row, (int)cp -> c_col); break;
X case c_age: ENQUEUE(links + cp -> c_index);
X }
X
X update_display();
X }
X
Xstatic void read_pattern(patternp)
Xint *patternp;
X {
X extern int fclose();
X
X register FILE *fp;
X register int c;
X register change_t *cp = changevec;
X int pattern = 0, in_comment = 0, in_pattern = 0;
X unsigned short row, col;
X
X if ((fp = fopen(PATTERN_FILE, "r")) == NULL)
X {
X *patternp = 0;
X return;
X }
X
X while ((c = getc(fp)) != EOF)
X if (in_comment)
X {
X if (c == '\n')
X in_comment = 0;
X }
X else
X {
X if ( ! in_pattern)
X switch (c)
X {
X case '#': ++in_comment; continue;
X case '\n': continue;
X default: ++in_pattern; row = col = 1;
X }
X
X switch (c)
X {
X case ' ':
X ++col;
X break;
X
X case '\t':
X col = ((col + 7) & ~7) + 1;
X break;
X
X case '\n':
X col = 1;
X ++row;
X break;
X
X case '#':
X if (*patternp == pattern)
X {
X cp -> c_op_code = c_death;
X (void)fclose(fp);
X return;
X }
X
X in_pattern = 0;
X ++in_comment;
X ++pattern;
X break;
X
X default:
X if (*patternp == pattern)
X {
X cp -> c_op_code = c_birth;
X cp -> c_row = row;
X cp -> c_col = col;
X ++cp;
X }
X
X ++col;
X }
X }
X
X if (in_pattern)
X if (*patternp == pattern)
X {
X cp -> c_op_code = c_death;
X (void)fclose(fp);
X return;
X }
X else
X ++pattern;
X
X *patternp = pattern;
X (void)fclose(fp);
X }
X
Xstatic void count_patterns()
X {
X n_patterns = 10000;
X read_pattern(&n_patterns);
X }
X
Xvoid init_life_game(rows, cols, color)
Xint rows, cols, color;
X {
X static int first_time = 1;
X register change_t *cp;
X int row, col, pattern, maxrow, maxcol, minrow, mincol,
X row_offset, col_offset;
X
X nrows = rows;
X ncols = cols;
X use_color = color;
X
X if (first_time)
X {
X srandom(time((long *)0));
X count_patterns();
X first_time = 0;
X }
X
X births = deaths = cells = shot_time = shots = 0;
X historyp = history;
X
X firstrow = (NROWS - nrows) / 2;
X firstcol = (NCOLS - ncols) / 2;
X lastrow = firstrow + nrows - 1;
X lastcol = firstcol + ncols - 1;
X
X (void)bzero(neighbors, sizeof(neighbors));
X (void)bzero(alive, sizeof(alive));
X (void)bzero(ages, sizeof(ages));
X (void)bzero(links, sizeof(links));
X
X head = links;
X head -> l_next = 0;
X
X pattern = random() % n_patterns;
X read_pattern(&pattern);
X
X maxrow = maxcol = 0;
X minrow = mincol = 10000;
X
X for (cp = changevec; cp -> c_op_code == c_birth; ++cp)
X {
X if (cp -> c_row > maxrow) maxrow = cp -> c_row;
X if (cp -> c_col > maxcol) maxcol = cp -> c_col;
X if (cp -> c_row < minrow) minrow = cp -> c_row;
X if (cp -> c_col < mincol) mincol = cp -> c_col;
X }
X
X row_offset = firstrow + nrows / 2 - minrow - (maxrow - minrow + 1) / 2;
X col_offset = firstcol + ncols / 2 - mincol - (maxcol - mincol + 1) / 2;
X
X for (cp = changevec; cp -> c_op_code == c_birth; ++cp)
X {
X row = cp -> c_row + row_offset;
X col = cp -> c_col + col_offset;
X
X if (row > 0 && row < NROWS - 1 && col > 0 && col < NCOLS - 1)
X birth(row, col);
X }
X
X update_display();
X (void)sleep(1);
X }
X
Xstatic void find_target(rowp, colp)
Xint *rowp, *colp;
X {
X register int row, col;
X
X for (row = firstrow; row <= lastrow; ++row)
X for (col = firstcol; col <= lastcol; ++col)
X if (alive[row * NCOLS + col])
X {
X *rowp = row;
X *colp = col;
X
X /*
X * Don't always just pick the topmost cell.
X * Look around a little bit.
X */
X if ((random() & 15) == 0)
X return;
X }
X }
X
Xint life_game_done()
X {
X int cycle = 0;
X
X if (cells == 0)
X return (1);
X
X /*
X * Quick and dirty way to detect cycles. Sometimes a bit hasty.
X */
X *historyp++ = (births << 8) | (deaths & 0xFF);
X
X /*
X * Check for cycles every HISTORY_SIZE generations
X */
X if (historyp >= history_end)
X {
X register unsigned short cycle_size;
X
X for (cycle_size = 1;
X ! cycle && cycle_size < HISTORY_SIZE / 2;
X ++cycle_size)
X {
X register int n_cycles = HISTORY_SIZE / cycle_size, i, no_cycle = 0;
X
X if (n_cycles > 10)
X n_cycles = 10;
X
X for (i = 2; ! no_cycle && i <= n_cycles; ++i)
X if (bcmp(historyp - cycle_size,
X historyp - (cycle_size * i),
X cycle_size * sizeof *historyp) != 0)
X no_cycle = 1;
X
X if ( ! no_cycle)
X cycle = cycle_size;
X }
X
X historyp = history;
X }
X
X if (births == 0 && deaths == 0)
X cycle = 1;
X
X if (shot_time)
X --shot_time;
X
X if (shot_time == 0 && cycle)
X {
X int row, col;
X
X if (shots && (random() & 1) == 0)
X return (1);
X
X ++shots;
X find_target(&row, &col);
X
X if (row < 5
X || row > nrows - 5
X || (random() & 15) < 5)
X {
X
X /*
X * Shoot a (light, middleweight or heavyweight) spaceship
X */
X int ship_size = (random() & 63) / 22; /* ~1/3 */
X
X shot_time = 2 * col + 20;
X
X col = (random() & 3) + firstcol / 2;
X row += (random() & 3);
X
X if (col > NCOLS - 6 - 1)
X col = NCOLS - 6 - 1;
X
X if (row > NROWS - 3 - 1)
X row = NROWS - 3 - 1;
X
X birth(1 + row, 0 + col + ship_size);
X
X if (ship_size < 2)
X birth(0 + row, 1 + col + ship_size);
X
X if (ship_size < 1)
X birth(0 + row, 2 + col + ship_size);
X
X birth(0 + row, 3 + col);
X birth(0 + row, 4 + col);
X birth(0 + row, 5 + col);
X birth(0 + row, 6 + col);
X birth(1 + row, 6 + col);
X birth(2 + row, 6 + col);
X birth(3 + row, 5 + col);
X }
X else
X {
X /*
X * Shoot a glider
X */
X shot_time = 4 * (col > row ? row : col) + 20;
X
X if (col > row)
X col -= row, row = 0;
X else
X row -= col, col = 0;
X
X col += (random() & 3);
X row += (random() & 3);
X
X if (firstrow < firstcol)
X col += firstrow / 2, row += firstrow / 2;
X else
X col += firstcol / 2, row += firstcol / 2;
X
X if (col > NCOLS - 2 - 1)
X col = NCOLS - 2 - 1;
X
X if (row > NROWS - 2 - 1)
X row = NROWS - 2 - 1;
X
X birth(0 + row, 2 + col);
X birth(1 + row, 2 + col);
X birth(2 + row, 2 + col);
X birth(2 + row, 1 + col);
X birth(1 + row, 0 + col);
X }
X }
X
X return (0);
X }
\End\of\File\
else
echo "Not overwriting ./lifegame.c"
fi
echo "Finished archive 1 of 1"
exit
--
Karl F. Fox, Morning Star Technologies, Inc. karl at MorningStar.COM
More information about the Unix-pc.sources
mailing list