/* -*- Mode: C; tab-width: 4 -*- */
/* life1d --- Stephen Wolfram's 1d game of Life */

#if 0
static const char sccsid[] = "@(#)life1d.c	5.00 2000/11/01 xlockmore";

#endif

/*-
 * Copyright (c) 1995 by David Bagley.
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted,
 * provided that the above copyright notice appear in all copies and that
 * both that copyright notice and this permission notice appear in
 * supporting documentation.
 *
 * This file is provided AS IS with no warranties of any kind.  The author
 * shall have no liability with respect to the infringement of copyrights,
 * trade secrets or any patents by this file or any part thereof.  In no
 * event will the author be liable for any lost revenue or profits or
 * other special, indirect and consequential damages.
 *
 * Revision History:
 * 01-Nov-2000: Allocation checks
 * 27-Oct-1997: xpm and ras capability added... it does not make too much
 *              sense here but ...
 * 10-May-1997: Compatible with xscreensaver
 * 27-Jul-1995: written, used life.c as a basis, using totalistic rules
 *              (default).  Special thanks to Harold V. McIntosh
 *              <mcintosh@servidor.unam.mx> for providing me with the
 *              LCAU collection and references.
 *
 * References:
 * Dewdney, A.K., "The Armchair Universe, Computer Recreations from the
 *   Pages of Scientific American Magazine", W.H. Freedman and Company,
 *   New York, 1988 (May 1985).
 * Perry, Kenneth E., "Abstract Mathematical Art", BYTE, December, 1986
 *   pp. 181-192
 * Hayes, Brian, "Computer Recreations", Scientific American, March 1984,
 *   p. 12
 * Wolfram, Stephen, "Cellular automata as models of complexity", Nature,
 *   4 October 1984, pp. 419-424
 * Wolfram, Stephen, "Computer Software in Science and Mathematics",
 *   Scientific American, September 1984, pp. 188-203
 *
 */

#ifdef STANDALONE
# define MODE_life1d
# define DEFAULTS	"*delay: 10000 \n" \
			"*cycles: 10 \n" \
			"*size: 0 \n" \
			"*ncolors: 200 \n" \
			"*bitmap: \n" \
			"*verbose: \n" \

# define reshape_life1d 0
# define life1d_handle_event 0
#include "xlockmore.h"		/* in xscreensaver distribution */
#else /* STANDALONE */
# include "xlock.h"		/* in xlockmore distribution */
# include "color.h"
#define DO_STIPPLE
#include "iostuff.h"
#endif /* STANDALONE */
#include "automata.h"

#ifdef MODE_life1d

#define DEF_TOTALISTIC  "True"	/* False is LCAU */

static Bool totalistic;

static XrmOptionDescRec opts[] =
{
	{(char *) "-totalistic", (char *) ".life1d.totalistic", XrmoptionNoArg, (caddr_t) "on"},
	{(char *) "+totalistic", (char *) ".life1d.totalistic", XrmoptionNoArg, (caddr_t) "off"}
};
static argtype vars[] =
{
	{(void *) & totalistic, (char *) "totalistic", (char *) "Totalistic", (char *) DEF_TOTALISTIC, t_Bool}
};
static OptionStruct desc[] =
{
	{(char *) "-/+totalistic", (char *) "turn on/off totalistic rules (else LCAU rules)"}
};

ENTRYPOINT ModeSpecOpt life1d_opts =
{sizeof opts / sizeof opts[0], opts, sizeof vars / sizeof vars[0], vars, desc};

#ifdef USE_MODULES
ModStruct   life1d_description =
{"life1d", "init_life1d", "draw_life1d", "release_life1d",
 "refresh_life1d", "init_life1d", "free_life1d", &life1d_opts,
 10000, 1, 10, 0, 64, 1.0, "",
 "Shows Wolfram's game of 1D Life", 0, NULL};

#endif

#ifndef STANDALONE
#define LIFE1DBITS(n,w,h)\
  if ((lp->pixmaps[lp->init_bits]=\
  XCreatePixmapFromBitmapData(display,window,(char *)n,w,h,1,0,1))==None){\
  free_life1d_screen(display,lp); return;} else {lp->init_bits++;}

/* aliases for vars defined in the bitmap file */
#define CELL_WIDTH   image_width
#define CELL_HEIGHT    image_height
#define CELL_BITS    image_bits

#include "life1d.xbm"

#ifdef HAVE_XPM
static char *image_name[] =
{(char *) ""};
#endif

#define CELL_NAME image_name
#define DEFAULT_XPM 0
#endif

static int  maxstates;
static int  maxradius;
static int  maxsum_size;

#define MINGRIDSIZE 10
#define MINSIZE 2		/* 3 may be better here */
#define MAXSTATES 5

typedef struct {
	int         init_bits;
	int         pixelmode;
	int         xs, ys, xb, yb;	/* cell size, grid border */
	int         screen_generation, row;
	int         nrows, ncols, border;
	int         width, height;
	int         k, r;
	long        code;
	int         repeating;
	char       *nextstate;
	int         colors[MAXSTATES];
	Pixmap      pixmaps[MAXSTATES];
	GC          stippledGC;
	unsigned char *newcells;
	unsigned char *oldcells;
	unsigned char *buffer;
	unsigned char *previousBuffer;
	XImage     *logo;
	Colormap    cmap;
	unsigned long black;
	int         graphics_format;
	GC          backGC;
	int         busyLoop;
} life1dstruct;

static life1dstruct *life1ds = (life1dstruct *) NULL;

static int  totalistic_rules[][3] =
{

  /* Well behaved rules */
  /* Scientific American (Dewdney) */
	{2, 2, 20},
	{2, 2, 52},		/* A bit too prolific but I like it anyway */
	{2, 3, 88},		/* James K. Park's 1D Gun (1111111111011) */
	{2, 4, 368},

  /* Nature */
	{3, 1, 792},

  /* BYTE (Translated to Wolfram's notation) */
	{4, 1, 39744},
	{4, 1, 81036},
	{4, 1, 126092},
	{4, 1, 147824},
	{4, 1, 156272},
	{4, 1, 189468},
	{4, 1, 176412},
	{4, 1, 214840},
	{4, 1, 245028},
	{4, 1, 267320},
	{4, 1, 257808},
	{4, 1, 258596},
	{4, 1, 260224},
	{4, 1, 267408},
	{4, 1, 290960},
	{4, 1, 330056},
	{4, 1, 330436},
	{4, 1, 400192},
	{4, 1, 433296},
	{4, 1, 434492},
	{4, 1, 447368},
	{4, 1, 453768},
	{4, 1, 454416},
	{4, 1, 485488},
	{4, 1, 505904},
	{4, 1, 618960},
	{4, 1, 642948},
	{4, 1, 680528},
	{4, 1, 708484},
	{4, 1, 741004},
	{4, 1, 749708},
	{4, 1, 756420},
	{4, 1, 761356},
	{4, 1, 769088},
	{4, 1, 778568},
	{4, 1, 779792},
	{4, 1, 797456},
	{4, 1, 803728},
	{4, 1, 844092},
	{4, 1, 874524},
	{4, 1, 881440},
	{4, 1, 921476},
	{4, 1, 936832},
	{4, 1, 937792},
	{4, 1, 1004600},

  /* Nature */
	{5, 1, 580020},
	{5, 1, 5694390},
	{5, 1, 59123000},

#if 0				/* OK but annoying */

  /* BYTE */
	{4, 1, 10552},
	{4, 1, 14708},
	{4, 1, 25284},
	{4, 1, 42848},
	{4, 1, 44328},
	{4, 1, 51788},
	{4, 1, 107364},
	{4, 1, 111448},
	{4, 1, 155848},
	{4, 1, 173024},
	{4, 1, 224148},
	{4, 1, 238372},
	{4, 1, 241656},
	{4, 1, 243764},
	{4, 1, 255856},
	{4, 1, 259222},
	{4, 1, 310148},
	{4, 1, 324148},
	{4, 1, 346696},
	{4, 1, 364424},
	{4, 1, 403652},
	{4, 1, 436072},
	{4, 1, 456708},
	{4, 1, 461912},
	{4, 1, 534812},
	{4, 1, 546700},
	{4, 1, 552708},
	{4, 1, 569092},
	{4, 1, 616736},
	{4, 1, 658564},
	{4, 1, 717956},
	{4, 1, 748432},
	{4, 1, 800964},
	{4, 1, 800972},
	{4, 1, 801144},
	{4, 1, 821116},
	{4, 1, 840172},
	{4, 1, 858312},
	{4, 1, 865394},
	{4, 1, 914952},
	{4, 1, 919244},
	{4, 1, 984296},
	{4, 1, 997964},
	{4, 1, 1018488},
	{4, 1, 1018808},
	{4, 1, 1023864},
	{4, 1, 1024472},
	{4, 1, 1033776},
	{4, 1, 1033784},
	{4, 1, 1034552},

  /* Nature */
	{5, 1, 583330},
	{5, 1, 672900},
#endif

#if 0				/* rejects */
  /* Nature */
	{2, 1, 4},
	{2, 3, 18},
	{2, 3, 22},
	{2, 3, 90},
	{2, 3, 94},
	{2, 3, 126},
	{2, 3, 128},

  /* Scientific American (Dewdney) */
	{2, 3, 54},
	{3, 2, 66},		/* RIPPLE, Dewdney's personal 1d rule */
	{3, 1, 257},

  /* BYTE */
	{4, 1, 16},
	{4, 1, 56},
	{4, 1, 4408},
	{4, 1, 101988},
	{4, 1, 113688},
	{4, 1, 227892},
	{4, 1, 254636},
	{4, 1, 258598},
	{4, 1, 294146},
	{4, 1, 377576},
	{4, 1, 472095},
	{4, 1, 538992},
	{4, 1, 615028},
	{4, 1, 901544},
	{4, 1, 911876},

  /* Nature */
	{5, 1, 10175},
	{5, 1, 566780},
	{5, 1, 570090},
#endif
};

#define TOTALISTICRULES (sizeof totalistic_rules / sizeof totalistic_rules[0])

#if 0
static char lcau21_rules[][9] =
{
	"00010010",		/* 18 Hollow enlarging triangles */
	"00110110",		/* 54 Hollow triangles */
	"01101110",		/* 110 Hollow right triangles */
	"01111100",		/* 124 Hollow left triangles */
	"10010011",		/* 147 Solid triangles */
	"10001001",		/* 137 Solid right triangles */
	"11000001",		/* 193 Solid left triangles */
};

#define LCAU2RULES (sizeof lcau21_rules / sizeof lcau21_rules[0])
#endif

static char lcau31_rules[][28] =
{
	"000222111000111222222111000",	/* equal thirds */
	"002220210110110211110002200",	/* threads on triangles */
	"001121102222110110111002100",	/* interfaces of 2 vel */
	"020201010201010102010102020",	/* class iv, sparse */
	"020201011201011112011112120",	/* blue bground class iv */
	"021211110211110101110101020",	/* diagonal black gaps */
	"022221211221211012222111120",	/* macrocell w/ 012 membrane */
	"100002021002021210021210100",	/* totalistic rule 792 (iv) */
	"100002200112201200020121200",	/* binary counter */
	"101021220111100222112102020",	/* two patterns compete #1 */
	"110001000112221222112221000",	/* reversible rule */
	"111012101002110122121102120",	/* small blue triangles */
	"111021210012110202120212122",	/* 2 glider on 1 background */
	"111111101010202020202010101",	/* motes and triangles */
	"111220012222012120001221020",	/* black triangles */
	"112110201012001210101200010",	/* dendrites */
	"112122000000122112112122000",	/* reversible rule's reverse */
	"200211020111110002001101210",	/* crocodile skin */
	"202000200112001200120211100",	/* two slow gliders collide */
	"202211000201021001100200200",	/* Red Queen's binary counter */
	"210101012101012120012120200",	/* blue on red background */
	"212021022100200221201101020",	/* two patterns compete #2 */
	"220222010220211111000211010",	/* Fisch's cyclic eater */
	"222022001222211202022120012",	/* macrocell w/ 0122 membrane */
};

#define LCAU3RULES (sizeof lcau31_rules / sizeof lcau31_rules[0])

static char lcau41_rules[][65] =
{
	"0000000313131323232312121213232323231313131101010202020202010101",	/* skewed triangle */
	"0000020202000000020232220203300000000101020030000100322121003020",	/* slow glider - copies bar */
	"0000213323132331123303003213323113233123002033211332313233120001",	/* slightly chaotic symmetry */
	"0000332030033323010020122303001002120210000000100110020220000010",	/* shuttle squeeze */
	"0100030000030323200323120202001003000203000220001310220100200010",	/* cool gliders */
	"0121200113213320110223311213201013131010111011101120012132103210",	/* mixture of types */
	"0212301103023320313223121332310023203323333231301020023120303020",	/* slo gl w/ many f gl */
	"0320032100113332112321213211003321233210121032320000321000010200",	/* crosshatching */
	"0323323323313310323323313310310223313310310210213310310210210210",	/* Perry's 245028 */
	"0323330023210000121122232322122113310131032101002110122321102120",	/*  */
	"0332200131100112230221012111210202022013210332120210222311212100",	/* cycles on dgl bgrnd */
	"0332200131100112230221012111210202022013210332120210222311212300",	/* cycles on dgl bgrnd */
	"1230320301231030321132211021112010202330101010112312220310311000",	/* very complex glider */
	"1230320301321030321132211021112010212121232212112312220310311000",	/* nice cross hatching */
	"2031122112031101123123233000321210301112010303203232213000311000",	/* gliders among stills */
	"2202003300010200010011010011020003033000032302002203110033010200",	/* bin ctr */
	"2313032202112320111221023212120203132000233322021310311101010030",	/* diagonal growth on mesh */
	"2332102112210200233210120311023110322213112123233132331213211212",	/* gliderettes & latice */
	"3000213323132331123302003213323113233123001033211332313233120000",	/* symmetric rule */
	"3003003203213211003203213211211303213211211311323211211311321320",	/* Perry's blue background */
	"3010213223113300321221011010123112301033010221203333211323110100",	/* v. bars w/entanglement */
	"3111213323132331123313113213323113233123220133211332313233121110",	/* not purely symmetric */
	"3112001202313030102330122003321023102122030102001210223122203020",	/* slow & fast gliders */
	"3130123232333201012202313210121032302200211020313130002001103210",	/* crocodile skin */
	"3230120202031130033311232111223012311113322032103210123012303210",	/* y/o on b/g */
	"3330333312110010333032222222001033303222121111110000322212110010",	/* Fisch's rule */
	"3332032303133100221122122213220022000212021022100200221203103020",	/* slow glider */
	"3333101022220101323201012323101033331010222201013232010123231010",	/* reverse of rvble #22 */
	"3333111122000022333311112200002233111133002222003311113300222200",	/* reversible Rule 22 */
};

#define LCAU4RULES (sizeof lcau41_rules / sizeof lcau41_rules[0])

#ifdef LCAU2RULES
#define LCAURULES (LCAU2RULES + LCAU3RULES + LCAU4RULES)
#else
#define LCAURULES (LCAU3RULES + LCAU4RULES)
#endif

static void
drawcell(ModeInfo * mi, int col, int row, unsigned int state)
{
	Display    *display = MI_DISPLAY(mi);
	Window      window = MI_WINDOW(mi);
	life1dstruct *lp = &life1ds[MI_SCREEN(mi)];
	GC          gc = lp->backGC;

	if (!state) {
		XSetForeground(display, gc, lp->black);
		XFillRectangle(display, window, gc,
		lp->xb + lp->xs * col, lp->yb + lp->ys * row, lp->xs, lp->ys);
		return;
	}
	if (MI_NPIXELS(mi) > 2)
		XSetForeground(display, gc, MI_PIXEL(mi, lp->colors[state - 1]));
	if (lp->pixelmode || (MI_NPIXELS(mi) <= 2)) {
#ifndef STANDALONE
		if (MI_NPIXELS(mi) <= 2) {
			XGCValues   gcv;

			gcv.stipple = lp->pixmaps[state - 1];
			gcv.foreground = MI_WHITE_PIXEL(mi);
			gcv.background = lp->black;
			gcv.fill_style = FillOpaqueStippled;
			XChangeGC(display, lp->stippledGC,
				  GCStipple | GCFillStyle | GCForeground | GCBackground, &gcv);
			XFillRectangle(display, window, lp->stippledGC,
				       lp->xb + lp->xs * col, lp->yb + lp->ys * row, lp->xs, lp->ys);
		} else
#endif
			XFillRectangle(display, window, gc,
				       lp->xb + lp->xs * col, lp->yb + lp->ys * row, lp->xs, lp->ys);
	}
	 else {
		(void) XPutImage(display, window, gc,
		lp->logo, 0, 0, lp->xb + lp->xs * col, lp->yb + lp->ys * row,
				 lp->logo->width, lp->logo->height);
	}
}

static void
RandomSoup(life1dstruct * lp, int n, int v)
{
	int         col;

	v /= 2;
	if (v < 1)
		v = 1;
	for (col = lp->ncols / 2 - v; col < lp->ncols / 2 + v; ++col)
		if (NRAND(100) < n && col >= 0 && col < lp->ncols)
			lp->newcells[col + lp->border] = (unsigned char) NRAND(lp->k - 1) + 1;
}

static long
power(int x, int n)
{				/* raise x to the nth power n >= 0 */
	int         i;
	long        p = 1;

	for (i = 1; i <= n; ++i)
		p = p * x;
	return p;
}

static void
GetRule(life1dstruct * lp, int i)
{
	long        sum_size, j;

	if (totalistic) {
		long        code, pow_size;

		lp->k = totalistic_rules[i][0];
		lp->r = totalistic_rules[i][1];
		sum_size = (lp->k - 1) * (lp->r * 2 + 1) + 1;
		code = lp->code = totalistic_rules[i][2];

		pow_size = power(lp->k, (int) (sum_size - 1));	/* Should be < max long */
		for (j = 0; j < sum_size; j++) {
			lp->nextstate[sum_size - 1 - j] = (char) (code / pow_size);
			code -= ((long) lp->nextstate[sum_size - 1 - j]) * pow_size;
			pow_size /= (long) lp->k;
		}
	} else {
		lp->r = 1;
#ifdef LCAU2RULES
		if (i < (int) LCAU2RULES) {
			lp->k = 2;
			sum_size = power(lp->k, 2 * lp->r + 1);
			for (j = 0; j < sum_size; j++)
				lp->nextstate[sum_size - 1 - j] = lcau21_rules[i][j] - '0';
		} else if (i < LCAU2RULES + LCAU3RULES)
#else
		if (i < (int) LCAU3RULES)
#endif
		{
			lp->k = 3;
			sum_size = power(lp->k, 2 * lp->r + 1);
#ifdef LCAU2RULES
			i -= LCAU2RULES;
#endif
			for (j = 0; j < sum_size; j++)
				lp->nextstate[sum_size - 1 - j] = lcau31_rules[i][j] - '0';
		} else {
			lp->k = 4;
			sum_size = power(lp->k, 2 * lp->r + 1);
#ifdef LCAU2RULES
			i -= (LCAU2RULES + LCAU3RULES);
#else
			i -= LCAU3RULES;
#endif
			for (j = 0; j < sum_size; j++)
				lp->nextstate[sum_size - 1 - j] = lcau41_rules[i][j] - '0';
		}
	}
}

static int
compare(ModeInfo * mi)
{
	life1dstruct *lp = &life1ds[MI_SCREEN(mi)];
	int         row, col, tryagain = False;
	unsigned char *initl, *cmpr;

        /* The following sometimes does not detect when there
	   are multiple periodic life forms side by side. */
	initl = lp->buffer + lp->row * lp->ncols;
	for (row = lp->row - 1; row >= 0; row--) {
		cmpr = lp->buffer + row * lp->ncols;
		for (col = 0; col < lp->ncols; col++) {
			tryagain = False;
			if (*(initl + col) != *cmpr) {
				tryagain = True;
				break;
			}
			cmpr++;
		}
		if (!tryagain) {
			return True;
		}
	}
        /* This is not guaranteed but its twice as good as without. */
	if (lp->previousBuffer) {
		initl = lp->buffer + lp->row * lp->ncols;
		for (row = lp->nrows - 1; row >= 0; row--) {
			cmpr = lp->previousBuffer + row * lp->ncols;
			for (col = 0; col < lp->ncols; col++) {
				tryagain = False;
				if (*(initl + col) != *cmpr) {
					tryagain = True;
					break;
				}
				cmpr++;
			}
			if (!tryagain) {
				return True;
			}
		}
	}
	return False;

}

static Bool
init_stuff(ModeInfo * mi)
{
	life1dstruct *lp = &life1ds[MI_SCREEN(mi)];
#ifndef STANDALONE

	if (lp->logo == None) {
		getImage(mi, &lp->logo, CELL_WIDTH, CELL_HEIGHT, CELL_BITS,
#ifdef HAVE_XPM
			 DEFAULT_XPM, CELL_NAME,
#endif
			 &lp->graphics_format, &lp->cmap, &lp->black);
		if (lp->logo == None) {
			return False;
		}
	}
#endif /* STANDALONE */
	if (lp->cmap != None) {
		Display    *display = MI_DISPLAY(mi);
		Window      window = MI_WINDOW(mi);

#ifndef STANDALONE
		setColormap(display, window, lp->cmap, MI_IS_INWINDOW(mi));
#endif
		if (lp->backGC == None) {
			XGCValues   xgcv;

			xgcv.background = lp->black;
			if ((lp->backGC = XCreateGC(display, window, GCBackground,
					&xgcv)) == None) {
				return False;
			}
		}
	} else
	{
		lp->black = MI_BLACK_PIXEL(mi);
		lp->backGC = MI_GC(mi);
	}
	return True;
}

static void
free_stuff(Display * display, life1dstruct * lp)
{
	if (lp == NULL) {
		return;
	}
	if (lp->cmap != None) {
		XFreeColormap(display, lp->cmap);
		if (lp->backGC != None) {
			XFreeGC(display, lp->backGC);
			lp->backGC = None;
		}
		lp->cmap = None;
	} else
		lp->backGC = None;
	lp = NULL;
}

static void
free_life1d_screen(Display * display, life1dstruct * lp)
{
	int         shade;

	if (lp == NULL) {
		return;
	}
	if (lp->stippledGC != None) {
		XFreeGC(display, lp->stippledGC);
		lp->stippledGC = None;
	}
	if (lp->init_bits != 0) {
		for (shade = 0; shade < MAXSTATES; shade++)
			XFreePixmap(display, lp->pixmaps[shade]);
		lp->init_bits = 0;
	}
	if (lp->newcells != NULL) {
		free(lp->newcells);
		lp->newcells = (unsigned char *) NULL;
	}
	if (lp->oldcells != NULL) {
		free(lp->oldcells);
		lp->oldcells = (unsigned char *) NULL;
	}
	if (lp->buffer != NULL) {
		free(lp->buffer);
		lp->buffer = (unsigned char *) NULL;
	}
	if (lp->previousBuffer != NULL) {
		free(lp->previousBuffer);
		lp->previousBuffer = (unsigned char *) NULL;
	}
	if (lp->nextstate != NULL) {
		free(lp->nextstate);
		lp->nextstate = (char *) NULL;
	}
	free_stuff(display, lp);
#ifndef STANDALONE
	if (lp->logo != None) {
		destroyImage(&lp->logo, &lp->graphics_format);
		lp->logo = None;
	}
#endif
	lp = NULL;
}

ENTRYPOINT void
free_life1d(ModeInfo * mi)
{
	free_life1d_screen(MI_DISPLAY(mi), &life1ds[MI_SCREEN(mi)]);
}

ENTRYPOINT void
init_life1d(ModeInfo * mi)
{
	Display    *display = MI_DISPLAY(mi);
	int         size = MI_SIZE(mi);
	int         i;
	life1dstruct *lp;

	MI_INIT(mi, life1ds);
	lp = &life1ds[MI_SCREEN(mi)];

	if (!init_stuff(mi)) {
		free_life1d_screen(display, lp);
		return;
	}

	lp->screen_generation = 0;
	lp->row = 0;

	if (totalistic) {
		maxstates = MAXSTATES;
		maxradius = 4;
		maxsum_size = (maxstates - 1) * (maxradius * 2 + 1) + 1;
	} else {
		maxstates = MAXSTATES - 1;
		maxradius = 1;
		maxsum_size = (int) power(maxstates, (2 * maxradius + 1));
	}
	if (lp->nextstate == NULL) {
		if ((lp->nextstate = (char *) malloc(maxsum_size *
				sizeof (char))) == NULL) {
			free_life1d_screen(display, lp);
			return;
		}
	}
#ifndef STANDALONE
	if (lp->init_bits == 0) {
		Window      window = MI_WINDOW(mi);
		XGCValues   gcv;

		gcv.fill_style = FillOpaqueStippled;
		if ((lp->stippledGC = XCreateGC(display, window, GCFillStyle,
				&gcv)) == None) {
			free_life1d_screen(display, lp);
			return;
		}
		for (i = 0; i < MAXSTATES - 1; i++) {
			LIFE1DBITS(stipples[i + NUMSTIPPLES - MAXSTATES + 1],
				   STIPPLESIZE, STIPPLESIZE);
		}
		LIFE1DBITS(stipples[NUMSTIPPLES / 2],
			   STIPPLESIZE, STIPPLESIZE);	/* grey */
	}
#endif
	if (lp->newcells != NULL)
		free(lp->newcells);
	if (lp->oldcells != NULL)
		free(lp->oldcells);
	if (lp->buffer != NULL)
		free(lp->buffer);
	if (lp->previousBuffer != NULL)
		free(lp->previousBuffer);
	lp->previousBuffer = (unsigned char *) NULL;
	lp->width = MI_WIDTH(mi);
	lp->height = MI_HEIGHT(mi);
	if (lp->width < 2)
		lp->width = 2;
	if (lp->height < 2)
		lp->height = 2;
#ifdef STANDALONE
	if (size == 0)
#else
	if (size == 0 ||
	 MINGRIDSIZE * size > lp->width || MINGRIDSIZE * size > lp->height) {
		if (lp->width > MINGRIDSIZE * lp->logo->width &&
		    lp->height > MINGRIDSIZE * lp->logo->height) {
			lp->pixelmode = False;
			lp->xs = lp->logo->width;
			lp->ys = lp->logo->height;
		} else
#endif
		{
			int min = MIN(lp->width, lp->height) / (12 * MINGRIDSIZE);
			int max = MIN(lp->width, lp->height) / (4 * MINGRIDSIZE);


			lp->xs = lp->ys = MAX(MINSIZE, min + NRAND(max - min + 1));
			lp->pixelmode = True;
		}
#ifndef STANDALONE
	}
#endif
	else {
		lp->pixelmode = True;
		if (size < -MINSIZE) {
			lp->ys = NRAND(MIN(-size, MAX(MINSIZE, MIN(lp->width, lp->height) /
				      MINGRIDSIZE)) - MINSIZE + 1) + MINSIZE;
		} else if (size < MINSIZE) {
			lp->ys = MINSIZE;
		} else {
			lp->ys = MIN(size, MAX(MINSIZE, MIN(lp->width, lp->height) /
					       MINGRIDSIZE));
		}
		lp->xs = lp->ys;
	}
	lp->ncols = MAX(lp->width / lp->xs, 2);
	lp->nrows = MAX(lp->height / lp->ys, 2);
	lp->border = (lp->nrows / 2 + 1) * MI_CYCLES(mi);
	if ((lp->newcells = (unsigned char *) calloc(lp->ncols + 2 * lp->border,
			sizeof (unsigned char))) == NULL) {
		free_life1d_screen(display, lp);
		return;
	}

	if ((lp->oldcells = (unsigned char *) calloc(lp->ncols + 2 *
			(maxradius + lp->border),
			sizeof (unsigned char))) == NULL) {
		free_life1d_screen(display, lp);
		return;
	}

	if ((lp->buffer = (unsigned char *) calloc(lp->ncols * lp->nrows,
			sizeof (unsigned char))) == NULL) {
		free_life1d_screen(display, lp);
		return;
	}

	lp->xb = (lp->width - lp->xs * lp->ncols) / 2;
	lp->yb = (lp->height - lp->ys * lp->nrows) / 2;

	GetRule(lp, (int) NRAND((totalistic) ? TOTALISTICRULES : LCAURULES));
	if (MI_IS_VERBOSE(mi)) {
		(void) fprintf(stdout, "colors %d, radius %d, code %ld, ",
			       lp->k, lp->r, lp->code);
		if (totalistic) {
			(void) fprintf(stdout, "totalistic rule ");
			for (i = (lp->k - 1) * (lp->r * 2 + 1); i >= 0; i--)
				(void) fprintf(stdout, "%d", (int) lp->nextstate[i]);
		} else {
			(void) fprintf(stdout, "LCAU rule ");
			for (i = (int) power(lp->k, (lp->r * 2 + 1)); i >= 0; i--)
				(void) fprintf(stdout, "%d", (int) lp->nextstate[i]);
		}
		(void) fprintf(stdout, "\n");
	}
	if (MI_NPIXELS(mi) > 2) {
		int offset = NRAND(MI_NPIXELS(mi));

		for (i = 0; i < lp->k - 1; i++) {
			lp->colors[i] = ((offset +
				(i * MI_NPIXELS(mi) / (lp->k - 1))) %
				MI_NPIXELS(mi));
		}
	}
	RandomSoup(lp, 40, 25);
	(void) memcpy((char *) (lp->oldcells + maxradius + lp->border),
		      (char *) (lp->newcells + lp->border), lp->ncols);
	lp->busyLoop = 0;

	MI_CLEARWINDOWCOLORMAP(mi, lp->backGC, lp->black);
}

ENTRYPOINT void
draw_life1d(ModeInfo * mi)
{
	Display    *display = MI_DISPLAY(mi);
	int         col;
	life1dstruct *lp;

	if (life1ds == NULL)
		return;
	lp = &life1ds[MI_SCREEN(mi)];
	if (lp->buffer == NULL)
		return;

	MI_IS_DRAWN(mi) = True;
	if (lp->busyLoop) {
		if (lp->busyLoop >= 250)
			lp->busyLoop = 0;
		else
			lp->busyLoop++;
		return;
	}
	if (lp->row == 0) {
		lp->repeating = 0;
		if (lp->screen_generation > MI_CYCLES(mi))
			init_life1d(mi);
		if (!lp->previousBuffer && lp->screen_generation > 1) {
			lp->previousBuffer = (unsigned char *) calloc(lp->ncols *
				lp->nrows, sizeof (unsigned char));
		}
		if (lp->previousBuffer) {
			(void) memcpy((char *) (lp->previousBuffer),
				(char *) (lp->buffer), lp->nrows * lp->ncols);
		}

		for (col = 0; col < lp->ncols; col++)
			if (lp->buffer[col] != lp->newcells[col + lp->border])
				drawcell(mi, col, 0, lp->newcells[col + lp->border]);
		(void) memcpy((char *) lp->buffer, (char *) (lp->newcells + lp->border),
			      lp->ncols);
	} else {
		for (col = 0; col < lp->ncols + 2 * lp->border; col++) {
			int         sum = 0, m;

			if (totalistic) {
				for (m = col - lp->r; m <= col + lp->r; m++)
					sum += lp->oldcells[m + maxradius];
			} else {
				int         pow_size = 1;

				for (m = col + lp->r; m >= col - lp->r; m--) {
					sum += lp->oldcells[m + maxradius] * pow_size;
					pow_size *= lp->k;
				}
			}
			lp->newcells[col] = (unsigned char) lp->nextstate[sum];
		}
		(void) memcpy((char *) (lp->oldcells + maxradius),
			  (char *) lp->newcells, lp->ncols + 2 * lp->border);

		for (col = 0; col < lp->ncols; col++) {
			if (lp->buffer[col + lp->row * lp->ncols] !=
			    lp->newcells[col + lp->border])
				drawcell(mi, col, lp->row, lp->newcells[col + lp->border]);
		}
		(void) memcpy((char *) (lp->buffer + lp->row * lp->ncols),
			    (char *) (lp->newcells + lp->border), lp->ncols);
		{
			int         temp = compare(mi);

			if (temp)
				lp->repeating += temp;
			else
				lp->repeating = 0;
		}
		lp->repeating += (lp->row == lp->nrows - 1) ?
			(lp->nrows - 1) * compare(mi) : 0;
	}
#ifndef STANDALONE
	if (lp->repeating >= 1) {
		XGCValues   gcv;

		gcv.stipple = lp->pixmaps[MAXSTATES - 1];
		gcv.fill_style = FillStippled;
		gcv.foreground = lp->black;
		XChangeGC(MI_DISPLAY(mi), lp->stippledGC,
			  GCStipple | GCFillStyle | GCForeground, &gcv);
		XFillRectangle(display, MI_WINDOW(mi), lp->stippledGC,
			       0, lp->yb + lp->ys * lp->row,
			       lp->width, lp->ys);
	}
#endif
	lp->row++;
	if (lp->repeating >= lp->nrows - 1) {
		if (lp->row < lp->nrows) {
			XSetForeground(display, lp->backGC, lp->black);
			XFillRectangle(display, MI_WINDOW(mi), lp->backGC,
				       0, lp->yb + lp->ys * lp->row,
			  lp->width, lp->height - lp->ys * lp->row - lp->yb);
		}
		lp->screen_generation = MI_CYCLES(mi);
		lp->row = lp->nrows;
	}
	if (lp->row >= lp->nrows) {
		lp->screen_generation++;
		lp->busyLoop = 1;
		lp->row = 0;
	}
}

ENTRYPOINT void
release_life1d(ModeInfo * mi)
{
	if (life1ds != NULL) {
		int         screen;

		for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++)
			free_life1d_screen(MI_DISPLAY(mi), &life1ds[screen]);
		free(life1ds);
		life1ds = (life1dstruct *) NULL;
	}
}

#ifndef STANDALONE
ENTRYPOINT void
refresh_life1d(ModeInfo * mi)
{
	int         row, col, nrow;
	life1dstruct *lp;

	if (life1ds == NULL)
		return;
	lp = &life1ds[MI_SCREEN(mi)];
	if (lp->buffer == NULL)
		return;

#ifdef HAVE_XPM
        if (lp->graphics_format >= IS_XPM) {
		/* This is needed when another program changes the colormap. */
		free_life1d_screen(MI_DISPLAY(mi), lp);
		init_life1d(mi);
		return;
	}
#endif

	for (row = 0; row < lp->nrows; row++) {
		nrow = row * lp->ncols;
		for (col = 0; col < lp->ncols; col++)
			drawcell(mi, col, row, lp->buffer[col + nrow]);
	}
}
#endif

XSCREENSAVER_MODULE ("Life1d", life1d)

#endif /* MODE_life1d */
