#ifndef lint
static char sccsid[] = "@(#)life.c	23.6 91/05/24 XLOCK";
#endif
/*-
 * life.c - Conway's game of Life for xlock, the X Window System lockscreen.
 *
 * Copyright (c) 1991 by Patrick J. Naughton.
 *
 * See xlock.c for copying information.
 *
 * Revision History:
 * 24-May-91: Added wraparound code from johnson@bugs.comm.mot.com.
 *	      Made old cells stay blue.
 *	      Made batchcount control the number of generations till restart.
 * 29-Jul-90: support for multiple screens.
 * 07-Feb-90: remove bogus semi-colon after #include line.
 * 15-Dec-89: Fix for proper skipping of {White,Black}Pixel() in colors.
 * 08-Oct-89: Moved seconds() to an extern.
 * 20-Sep-89: Written (life algorithm courtesy of Jim Graham, flar@sun.com).
 */

#include "xlock.h"
#include "lifeicon.bit"

static XImage logo = {
    0, 0,			/* width, height */
    0, XYBitmap, 0,		/* xoffset, format, data */
    LSBFirst, 8,		/* byte-order, bitmap-unit */
    LSBFirst, 8, 1		/* bitmap-bit-order, bitmap-pad, depth */
};
#define min(a, b) ((a)<(b)?(a):(b))
#define	MAXROWS	155
#define MAXCOLS	144
#define TIMEOUT 30

typedef struct {
    int         pixelmode;
    int         xs;
    int         ys;
    int         xb;
    int         yb;
    int         generation;
    long        shooterTime;
    int         nrows;
    int         ncols;
    int         width;
    int         height;
    unsigned char buffer[(MAXROWS + 2) * (MAXCOLS + 2) + 2];
    unsigned char tempbuf[MAXCOLS * 2];
    unsigned char lastbuf[MAXCOLS];
    unsigned char agebuf[(MAXROWS + 2) * (MAXCOLS + 2)];
}           lifestruct;

static lifestruct lifes[MAXSCREENS];
static int  icon_width, icon_height;

/* Buffer stores the data for each cell. Each cell is stored as
 * 8 bits representing the presence of a critter in each of it's
 * surrounding 8 cells. There is an empty row and column around
 * the whole array to allow stores without bounds checking as well
 * as an extra row at the end for the fetches into tempbuf.
 *
 * Tempbuf stores the data for the next two rows so that we know
 * the state of those critter before he was modified by the fate
 * of the critters that have already been processed.
 *
 * Agebuf stores the age of each critter.
 */

#define	UPLT	0x01
#define UP	0x02
#define UPRT	0x04
#define LT	0x08
#define RT	0x10
#define DNLT	0x20
#define DN	0x40
#define DNRT	0x80

/* Fates is a lookup table for the fate of a critter. The 256
 * entries represent the 256 possible combinations of the 8
 * neighbor cells. Each entry is one of BIRTH (create a cell
 * or leave one alive), SAME (leave the cell alive or dead),
 * or DEATH (kill anything in the cell).
 */
#define BIRTH	0
#define SAME	1
#define DEATH	2
static unsigned char fates[256];
static int  initialized = 0;

static int  patterns[][128] = {
    {				/* EIGHT */
	-3, -3, -2, -3, -1, -3,
	-3, -2, -2, -2, -1, -2,
	-3, -1, -2, -1, -1, -1,
	0, 0, 1, 0, 2, 0,
	0, 1, 1, 1, 2, 1,
	0, 2, 1, 2, 2, 2,
	99
    },
    {				/* PULSAR */
	1, 1, 2, 1, 3, 1, 4, 1, 5, 1,
	1, 2, 5, 2,
	99
    },
    {				/* BARBER */
	-7, -7, -6, -7,
	-7, -6, -5, -6,
	-5, -4, -3, -4,
	-3, -2, -1, -2,
	-1, 0, 1, 0,
	1, 2, 3, 2,
	3, 4, 5, 4,
	4, 5, 5, 5,
	99
    },
    {				/* HERTZ */
	-2, -6, -1, -6,
	-2, -5, -1, -5,
	-7, -3, -6, -3, -2, -3, -1, -3, 0, -3, 1, -3, 5, -3, 6, -3,
	-7, -2, -5, -2, -3, -2, 2, -2, 4, -2, 6, -2,
	-5, -1, -3, -1, -2, -1, 2, -1, 4, -1,
	-7, 0, -5, 0, -3, 0, 2, 0, 4, 0, 6, 0,
	-7, 1, -6, 1, -2, 1, -1, 1, 0, 1, 1, 1, 5, 1, 6, 1,
	-2, 3, -1, 3,
	-2, 4, -1, 4,
	99
    },
    {				/* TUMBLER */
	-6, -6, -5, -6, 6, -6, 7, -6,
	-6, -5, -5, -5, 6, -5, 7, -5,
	-5, 5, 6, 5,
	-7, 6, -5, 6, 6, 6, 8, 6,
	-7, 7, -5, 7, 6, 7, 8, 7,
	-7, 8, -6, 8, 7, 8, 8, 8,
	99
    },
    {				/* PERIOD4 */
	-5, -8, -4, -8,
	-7, -7, -5, -7,
	-8, -6, -2, -6,
	-7, -5, -3, -5, -2, -5,
	-5, -3, -3, -3,
	-4, -2,
	99
    },
    {				/* PERIOD5 */
	-5, -8, -4, -8,
	-6, -7, -3, -7,
	-7, -6, -2, -6,
	-8, -5, -1, -5,
	-8, -4, -1, -4,
	-7, -3, -2, -3,
	-6, -2, -3, -2,
	-5, -1, -4, -1,
	99
    },
    {				/* PERIOD6 */
	-4, -8, -3, -8,
	-8, -7, -7, -7, -5, -7,
	-8, -6, -7, -6, -4, -6, -1, -6,
	-3, -5, -1, -5,
	-2, -4,
	-3, -2, -2, -2,
	-3, -1, -2, -1,
	99
    },
    {				/* PINWHEEL */
	-4, -8, -3, -8,
	-4, -7, -3, -7,
	-4, -5, -3, -5, -2, -5, -1, -5,
	-5, -4, -3, -4, 0, -4, 2, -4, 3, -4,
	-5, -3, -1, -3, 0, -3, 2, -3, 3, -3,
	-8, -2, -7, -2, -5, -2, -2, -2, 0, -2,
	-8, -1, -7, -1, -5, -1, 0, -1,
	-4, 0, -3, 0, -2, 0, -1, 0,
	-2, 2, -1, 2,
	-2, 3, -1, 3,
	99
    },
    {				/* ] */
	-1, -1, 0, -1, 1, -1,
	0, 0, 1, 0,
	-1, 1, 0, 1, 1, 1,
	99
    },
    {				/* cc: */
	-3, -1, -2, -1, -1, -1, 1, -1, 2, -1, 3, -1,
	-3, 0, -2, 0, 1, 0, 2, 0,
	-3, 1, -2, 1, -1, 1, 1, 1, 2, 1, 3, 1,
	99
    },
    {				/* DOLBY */
	-3, -1, -2, -1, -1, -1, 1, -1, 2, -1, 3, -1,
	-3, 0, -2, 0, 2, 0, 3, 0,
	-3, 1, -2, 1, -1, 1, 1, 1, 2, 1, 3, 1,
	99
    },
    {				/* HORIZON */
	-15, 0, -14, 0, -13, 0, -12, 0, -11, 0,
	-10, 0, -9, 0, -8, 0, -7, 0, -6, 0,
	-5, 0, -4, 0, -3, 0, -2, 0, -1, 0,
	4, 0, 3, 0, 2, 0, 1, 0, 0, 0,
	9, 0, 8, 0, 7, 0, 6, 0, 5, 0,
	14, 0, 13, 0, 12, 0, 11, 0, 10, 0,
	99
    },
    {				/* SHEAR */
	-7, -2, -6, -2, -5, -2, -4, -2, -3, -2,
	-2, -2, -1, -2, 0, -2, 1, -2, 2, -2,
	-5, -1, -4, -1, -3, -1, -2, -1, -1, -1,
	0, -1, 1, -1, 2, -1, 3, -1, 4, -1,
	-3, 0, -2, 0, -1, 0, 0, 0, 1, 0,
	2, 0, 3, 0, 4, 0, 5, 0, 6, 0,
	-10, 1, -9, 1, -8, 1, -7, 1, -6, 1,
	-5, 1, -4, 1, -3, 1, -2, 1, -1, 1,
	-10, 2, -9, 2, -8, 2, -7, 2, -6, 2,
	-5, 2, -4, 2, -3, 2, -2, 2, -1, 2,
	99
    },
    {				/* VERTIGO */
	0, -7,
	0, -6,
	0, -5,
	0, -4,
	0, -3,
	0, -2,
	0, -1,
	0, 0,
	0, 7,
	0, 6,
	0, 5,
	0, 4,
	0, 3,
	0, 2,
	0, 1,
	99
    },
    {				/* CROSSBAR */
	-5, 0, -4, 0, -3, 0, -2, 0, -1, 0, 4, 0, 3, 0, 2, 0, 1, 0, 0, 0,
	99
    },
    {				/* GOALPOSTS */
	-8, -7, 8, -7,
	-8, -6, 8, -6,
	-8, -5, 8, -5,
	-8, -4, 8, -4,
	-8, -3, 8, -3,
	-8, -2, 8, -2,
	-8, -1, 8, -1,
	-8, 0, 8, 0,
	-8, 1, 8, 1,
	-8, 2, 8, 2,
	-8, 3, 8, 3,
	-8, 4, 8, 4,
	-8, 5, 8, 5,
	-8, 6, 8, 6,
	-8, 7, 8, 7,
	99
    },
    {				/* \ */
	-8, -8, -7, -8,
	-7, -7, -6, -7,
	-6, -6, -5, -6,
	-5, -5, -4, -5,
	-4, -4, -3, -4,
	-3, -3, -2, -3,
	-2, -2, -1, -2,
	-1, -1, 0, -1,
	0, 0, 1, 0,
	1, 1, 2, 1,
	2, 2, 3, 2,
	3, 3, 4, 3,
	4, 4, 5, 4,
	5, 5, 6, 5,
	6, 6, 7, 6,
	7, 7, 8, 7,
	99
    },
    {				/* LABYRINTH */
	-4, -4, -3, -4, -2, -4, -1, -4, 0, -4, 1, -4, 2, -4, 3, -4, 4, -4,
	-4, -3, 0, -3, 4, -3,
	-4, -2, -2, -2, -1, -2, 0, -2, 1, -2, 2, -2, 4, -2,
	-4, -1, -2, -1, 2, -1, 4, -1,
	-4, 0, -2, 0, -1, 0, 0, 0, 1, 0, 2, 0, 4, 0,
	-4, 1, -2, 1, 2, 1, 4, 1,
	-4, 2, -2, 2, -1, 2, 0, 2, 1, 2, 2, 2, 4, 2,
	-4, 3, 0, 3, 4, 3,
	-4, 4, -3, 4, -2, 4, -1, 4, 0, 4, 1, 4, 2, 4, 3, 4, 4, 4,
	99
    }
};

#define NPATS	(sizeof patterns / sizeof patterns[0])


static void
drawcell(win, row, col)
    Window      win;
    int         row, col;
{
    lifestruct *lp = &lifes[screen];

    XSetForeground(dsp, Scr[screen].gc, WhitePixel(dsp, screen));
    if (!mono && Scr[screen].npixels > 2) {
	unsigned char *loc = lp->buffer + ((row + 1) * (lp->ncols + 2)) + col + 1;
	unsigned char *ageptr = lp->agebuf + (loc - lp->buffer);
	unsigned char age = *ageptr;

	/* if we aren't up to blue yet, then keep aging the cell. */
	if (age < Scr[screen].npixels * 0.7)
	    ++age;

	XSetForeground(dsp, Scr[screen].gc, Scr[screen].pixels[age]);
	*ageptr = age;
    }
    if (lp->pixelmode)
	XFillRectangle(dsp, win, Scr[screen].gc,
	       lp->xb + lp->xs * col, lp->yb + lp->ys * row, lp->xs, lp->ys);
    else
	XPutImage(dsp, win, Scr[screen].gc, &logo,
		  0, 0, lp->xb + lp->xs * col, lp->yb + lp->ys * row,
		  icon_width, icon_height);
}


static void
erasecell(win, row, col)
    Window      win;
    int         row, col;
{
    lifestruct *lp = &lifes[screen];
    XSetForeground(dsp, Scr[screen].gc, BlackPixel(dsp, screen));
    XFillRectangle(dsp, win, Scr[screen].gc,
	       lp->xb + lp->xs * col, lp->yb + lp->ys * row, lp->xs, lp->ys);
}


static void
spawn(loc)
    unsigned char *loc;
{
    lifestruct *lp = &lifes[screen];
    unsigned char *ulloc, *ucloc, *urloc, *clloc, *crloc, *llloc, *lcloc, *lrloc,
               *arloc;
    int         off, row, col, lastrow;

    lastrow = (lp->nrows) * (lp->ncols + 2);
    off = loc - lp->buffer;
    col = off % (lp->ncols + 2);
    row = (off - col) / (lp->ncols + 2);
    ulloc = loc - lp->ncols - 3;
    ucloc = loc - lp->ncols - 2;
    urloc = loc - lp->ncols - 1;
    clloc = loc - 1;
    crloc = loc + 1;
    arloc = loc + 1;
    llloc = loc + lp->ncols + 1;
    lcloc = loc + lp->ncols + 2;
    lrloc = loc + lp->ncols + 3;
    if (row == 1) {
	ulloc += lastrow;
	ucloc += lastrow;
	urloc += lastrow;
    }
    if (row == lp->nrows) {
	llloc -= lastrow;
	lcloc -= lastrow;
	lrloc -= lastrow;
    }
    if (col == 1) {
	ulloc += lp->ncols;
	clloc += lp->ncols;
	llloc += lp->ncols;
    }
    if (col == lp->ncols) {
	urloc -= lp->ncols;
	crloc -= lp->ncols;
	lrloc -= lp->ncols;
    }
    *ulloc |= UPLT;
    *ucloc |= UP;
    *urloc |= UPRT;
    *clloc |= LT;
    *crloc |= RT;
    *arloc |= RT;
    *llloc |= DNLT;
    *lcloc |= DN;
    *lrloc |= DNRT;

    *(lp->agebuf + (loc - lp->buffer)) = 0;
}


static void
killme(loc)
    unsigned char *loc;
{
    lifestruct *lp = &lifes[screen];

    unsigned char *ulloc, *ucloc, *urloc, *clloc, *crloc, *llloc, *lcloc,
               *lrloc, *arloc;
    int         off, row, col, lastrow;

    lastrow = (lp->nrows) * (lp->ncols + 2);
    off = loc - lp->buffer;
    row = off / (lp->ncols + 2);
    col = off % (lp->ncols + 2);
    row = (off - col) / (lp->ncols + 2);
    ulloc = loc - lp->ncols - 3;
    ucloc = loc - lp->ncols - 2;
    urloc = loc - lp->ncols - 1;
    clloc = loc - 1;
    crloc = loc + 1;
    arloc = loc + 1;
    llloc = loc + lp->ncols + 1;
    lcloc = loc + lp->ncols + 2;
    lrloc = loc + lp->ncols + 3;
    if (row == 1) {
	ulloc += lastrow;
	ucloc += lastrow;
	urloc += lastrow;
    }
    if (row == lp->nrows) {
	llloc -= lastrow;
	lcloc -= lastrow;
	lrloc -= lastrow;
    }
    if (col == 1) {
	ulloc += lp->ncols;
	clloc += lp->ncols;
	llloc += lp->ncols;
    }
    if (col == lp->ncols) {
	urloc -= lp->ncols;
	crloc -= lp->ncols;
	lrloc -= lp->ncols;
    }
    *ulloc &= ~UPLT;
    *ucloc &= ~UP;
    *urloc &= ~UPRT;
    *clloc &= ~LT;
    *crloc &= ~RT;
    *arloc &= ~RT;
    *llloc &= ~DNLT;
    *lcloc &= ~DN;
    *lrloc &= ~DNRT;
}


static void
setcell(win, row, col)
    Window      win;
    int         row;
    int         col;
{
    lifestruct *lp = &lifes[screen];
    unsigned char *loc;

    loc = lp->buffer + ((row + 1) * (lp->ncols + 2)) + col + 1;
    spawn(loc);
    drawcell(win, row, col);
}


static void
init_fates()
{
    int         i, bits, neighbors;

    for (i = 0; i < 256; i++) {
	neighbors = 0;
	for (bits = i; bits; bits &= (bits - 1))
	    neighbors++;
	if (neighbors == 3)
	    fates[i] = BIRTH;
	else if (neighbors == 2)
	    fates[i] = SAME;
	else
	    fates[i] = DEATH;
    }
}


void
initlife(win)
    Window      win;
{
    int         row, col;
    int        *patptr;
    XWindowAttributes xgwa;
    lifestruct *lp = &lifes[screen];

    lp->generation = 0;
    lp->shooterTime = seconds();
    icon_width = lifeicon_width;
    icon_height = lifeicon_height;

    if (!initialized) {
	initialized = 1;
	init_fates();
	logo.data = (char *) lifeicon_bits;
	logo.width = icon_width;
	logo.height = icon_height;
	logo.bytes_per_line = (icon_width + 7) / 8;
    }
    XGetWindowAttributes(dsp, win, &xgwa);
    lp->width = xgwa.width;
    lp->height = xgwa.height;
    lp->pixelmode = (lp->width < 4 * icon_width);
    if (lp->pixelmode) {
	lp->ncols = 32;
	lp->nrows = 32;
    } else {
	lp->ncols = min(lp->width / icon_width, MAXCOLS);
	lp->nrows = min(lp->height / icon_height, MAXROWS);
    }
    lp->xs = lp->width / lp->ncols;
    lp->ys = lp->height / lp->nrows;
    lp->xb = (lp->width - lp->xs * lp->ncols) / 2;
    lp->yb = (lp->height - lp->ys * lp->nrows) / 2;

    XSetForeground(dsp, Scr[screen].gc, BlackPixel(dsp, screen));
    XFillRectangle(dsp, win, Scr[screen].gc, 0, 0, lp->width, lp->height);

    bzero(lp->buffer, sizeof(lp->buffer));
    patptr = &patterns[random() % NPATS][0];
    while ((col = *patptr++) != 99) {
	row = *patptr++;
	col += lp->ncols / 2;
	row += lp->nrows / 2;
	setcell(win, row, col);
    }
}


void
drawlife(win)
    Window      win;
{
    unsigned char *loc, *temploc, *lastloc;
    int         row, col;
    unsigned char fate;
    lifestruct *lp = &lifes[screen];

    loc = lp->buffer + lp->ncols + 2 + 1;
    temploc = lp->tempbuf;
    /* copy the first 2 rows to the tempbuf */
    bcopy(loc, temploc, lp->ncols);
    bcopy(loc + lp->ncols + 2, temploc + lp->ncols, lp->ncols);

    lastloc = lp->lastbuf;
    /* copy the last row to another buffer for wraparound */
    bcopy(loc + ((lp->nrows - 1) * (lp->ncols + 2)), lastloc, lp->ncols);

    for (row = 0; row < lp->nrows; ++row) {
	for (col = 0; col < lp->ncols; ++col) {
	    fate = fates[*temploc];
	    *temploc = (row == (lp->nrows - 3)) ?
		*(lastloc + col) :
		*(loc + (lp->ncols + 2) * 2);
	    switch (fate) {
	    case BIRTH:
		if (!(*(loc + 1) & RT)) {
		    spawn(loc);
		}
		/* NO BREAK */
	    case SAME:
		if (*(loc + 1) & RT) {
		    drawcell(win, row, col);
		}
		break;
	    case DEATH:
		if (*(loc + 1) & RT) {
		    killme(loc);
		    erasecell(win, row, col);
		}
		break;
	    }
	    loc++;
	    temploc++;
	}
	loc += 2;
	if (temploc >= lp->tempbuf + lp->ncols * 2)
	    temploc = lp->tempbuf;
    }

    if (++lp->generation > batchcount)
	initlife(win);

    /*
     * generate a randomized shooter aimed roughly toward the center of the
     * screen after timeout.
     */

    if (seconds() - lp->shooterTime > TIMEOUT) {
	int         hsp = random() % (lp->ncols - 5) + 3;
	int         vsp = random() % (lp->nrows - 5) + 3;
	int         hoff = 1;
	int         voff = 1;
	if (vsp > lp->nrows / 2)
	    voff = -1;
	if (hsp > lp->ncols / 2)
	    hoff = -1;
	setcell(win, vsp + 0 * voff, hsp + 2 * hoff);
	setcell(win, vsp + 1 * voff, hsp + 2 * hoff);
	setcell(win, vsp + 2 * voff, hsp + 2 * hoff);
	setcell(win, vsp + 2 * voff, hsp + 1 * hoff);
	setcell(win, vsp + 1 * voff, hsp + 0 * hoff);
	lp->shooterTime = seconds();
    }
}
