Plan 9 from Bell Labs’s /usr/web/sources/contrib/anothy/src/cmd/curslife.c

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


/*
 * Conway's Game of Life - in your cursor.
 * Adapted from /sys/src/games/life.c
 */
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <draw.h>
#include <cursor.h>
#include <mouse.h>

enum {
	NLIFE	= 16,		/* life array size */
	PX	= 1,		/* cell spacing */
	BX	= 1,	/* box size */
	NADJUST	= NLIFE * NLIFE,
};

/*
 * life[i][j] stores L+2*N, where L is 0 if the cell is dead and 1
 * if alive, and N is the number of the cell's 8-connected neighbours
 * which live.
 * row[i] indicates how many cells are alive in life[i][*].
 * col[j] indicates how many cells are alive in life[*][j].
 * Adjust contains pointers to cells that need to have their neighbour
 * counts adjusted in the second pass of the generation procedure.
 */
char	life[NLIFE][NLIFE];
int	row[NLIFE];
int	col[NLIFE];
char	action[18];		/* index by cell contents to find action */
char	*adjust[NADJUST];

Point	cen;
//Image	*box;
int	i0, i1, j0, j1;
int	needresize;

void	birth(int, int);
void	death(int, int);
int	generate(void);
int	interest(int [NLIFE], int);
void	main(int, char *[]);
int	min(int, int);
void	readlife(char *);
void	setrules(char *);
void	window(void);

void
setrules(char *r)
{
	char *a;

	for (a = action; a != &action[nelem(action)]; *a++ = *r++)
		;
}

void
usage(void)
{
	fprint(2, "Usage: %s [-d msec] [-3o] [-r rules] file\n", argv0);
	exits("usage");
}

void
printcurs()
{
	int i, j, k, m;

	print("	{-7, -7},\n");
	/* Background is clear for now. */
	print("	{\n");
	for (i = 0 ; i < 4 ; i++) {
		print("		");
		for (j = 0 ; j < 8 ; j++)
			print("0x00, ");
		print("\n");
	}
	print("	},\n");
	/* Paint live cells black. */
	print("	{\n");
	for (i = 0 ; i < NLIFE ; i++) {
		print("		");
		for (j = 0, k = 0 ; j < NLIFE ;) {
			for (m = 0 ; m < 8 ; m++, j++) {
				k += (life[i][j] & 1) << 7-m;
			}
			print("%#x, ", k);
			k = 0;
		}
		print("\n");
	}
	print("	}\n");
}

void
curslife()
{
	Cursor c = {
		{-1, -1},
		{0, 0, 0, 0, 0, 0, 0, 0,
		0, 0, 0, 0, 0, 0, 0, 0,
		0, 0, 0, 0, 0, 0, 0, 0,
		0, 0, 0, 0, 0, 0, 0, 0,},
		{0, 0, 0, 0, 0, 0, 0, 0,
		0, 0, 0, 0, 0, 0, 0, 0,
		0, 0, 0, 0, 0, 0, 0, 0,
		0, 0, 0, 0, 0, 0, 0, 0,},
	};

	int i, j, k, m, p, fd;

	fd = open("#m/cursor", OWRITE);

	char curs[2*4+2*2*16];

	BPLONG(curs+0*4, c.offset.x);
	BPLONG(curs+1*4, c.offset.y);
	memmove(curs+2*4, c.clr, 2*16);
	memmove(curs+2*4+2*16, c.set, 2*16);

	for (i = 0, p = 2*4 ; i < NLIFE ; i++) {
		for (j = 0, k = 0 ; j < NLIFE ;) {
			for (m = 0 ; m < 8 ; m++, j++) {
				k += (life[i][j] & 1) << 7-m;
			}
			curs[p] = k;
			p++;
			k = 0;
		}
	}

	write(fd, curs, 2*4+2*2*16);
	close(fd);

}

void
main(int argc, char *argv[])
{
	int delay = 1000;
	int verbose = 0;

	setrules(".d.d..b..d.d.d.d.d");			/* regular rules */
	ARGBEGIN {
	case '3':
		setrules(".d.d.db.b..d.d.d.d");
		break;					/* 34-life */
	case 'o':
		setrules(".d.d.db.b.b..d.d.d");
		break;					/* lineosc? */
	case 'r':					/* rules from cmdline */
		setrules(EARGF(usage()));
		break;
	case 'd':
		delay = atoi(EARGF(usage()));
		break;
	case 'v':
		verbose++;
		break;
	default:
		usage();
	} ARGEND
	if (argc != 1)
		usage();

	readlife(argv[0]);
	do {
		sleep(delay);
//		printcurs();
		curslife();
	} while (generate());
	exits(nil);
}

/*
 * We can only have interest in a given row (or column) if there
 * is something alive in it or in the neighbouring rows (or columns.)
 */
int
interest(int rc[NLIFE], int i)
{
	return(rc[i-1] != 0 || rc[i] != 0 || rc[i+1] != 0);
}

/*
 * A life generation proceeds in two passes.  The first pass identifies
 * cells that have births or deaths.  The `alive bit' is updated, as are
 * the screen and the row/col count deltas.  Also, a record is made
 * of the cell's address.  In the second pass, the neighbours of all changed
 * cells get their neighbour counts updated, and the row/col deltas get
 * merged into the row/col count arrays.
 *
 * The border cells (i==0 || i==NLIFE-1 || j==0 || j==NLIFE-1) are not
 * processed, purely for speed reasons.  With a little effort, a wrap-around
 * universe could be implemented.
 *
 * Generate returns 0 if there was no change from the last generation,
 * and 1 if there were changes.
 */
#define	neighbour(di, dj, op) lp[(di)*NLIFE+(dj)] op= 2
#define	neighbours(op)\
	neighbour(-1, -1, op);\
	neighbour(-1,  0, op);\
	neighbour(-1,  1, op);\
	neighbour( 0, -1, op);\
	neighbour( 0,  1, op);\
	neighbour( 1, -1, op);\
	neighbour( 1,  0, op);\
	neighbour( 1,  1, op)

int
generate(void)
{
	char *lp;
	char **p, **addp, **delp;
	int i, j, j0 = NLIFE, j1 = -1;
	int drow[NLIFE], dcol[NLIFE];

	for (j = 1; j != NLIFE - 1; j++) {
		drow[j] = dcol[j] = 0;
		if (interest(col, j)) {
			if (j < j0)
				j0 = j;
			if (j1 < j)
				j1 = j;
		}
	}
	addp = adjust;
	delp = &adjust[NADJUST];
	for (i = 1; i != NLIFE - 1; i++)
		if (interest(row, i)) {
			for (j = j0, lp = &life[i][j0]; j <= j1; j++, lp++)
				switch (action[*lp]) {
				case 'b':
					++*lp;
					++drow[i];
					++dcol[j];
					*addp++ = lp;
					break;
				case 'd':
					--*lp;
					--drow[i];
					--dcol[j];
					*--delp = lp;
					break;
				}
		}
	if (addp == adjust && delp == &adjust[NADJUST])
		return 0;
	if (delp < addp)
		sysfatal("Out of space (delp < addp)");
	p = adjust;
	while (p != addp) {
		lp = *p++;
		neighbours(+);
	}
	p = delp;
	while (p != &adjust[NADJUST]) {
		lp = *p++;
		neighbours(-);
	}
	for (i = 1; i != NLIFE - 1; i++) {
		row[i] += drow[i];
		col[i] += dcol[i];
	}
	return 1;
}

/*
 * Record a birth at (i,j).
 */
void
birth(int i, int j)
{
	char *lp;

	if (i < 1 || NLIFE - 1 <= i || j < 1 || NLIFE - 1 <= j ||
	    life[i][j] & 1)
		return;
	lp = &life[i][j];
	++*lp;
	++row[i];
	++col[j];
	neighbours(+);
}

/*
 * Record a death at (i,j)
 */
void
death(int i, int j)
{
	char *lp;

	if (i < 1 || NLIFE - 1 <= i || j < 1 || NLIFE - 1 <= j ||
	    !(life[i][j] & 1))
		return;
	lp = &life[i][j];
	--*lp;
	--row[i];
	--col[j];
	neighbours(-);
}

void
readlife(char *filename)
{
	int c, i, j;
	char name[256];
	Biobuf *bp;

	if ((bp = Bopen(filename, OREAD)) == nil) {
		snprint(name, sizeof name, "/sys/games/lib/life/%s", filename);
		if ((bp = Bopen(name, OREAD)) == nil)
			sysfatal("can't read %s: %r", name);
	}
	for (i = 0; i != NLIFE; i++) {
		row[i] = col[i] = 0;
		for (j = 0; j != NLIFE; j++)
			life[i][j] = 0;
	}
	c = 0;
	for (i = 1; i != NLIFE - 1 && c >= 0; i++) {
		j = 1;
		while ((c = Bgetc(bp)) >= 0 && c != '\n')
			if (j != NLIFE - 1)
				switch (c) {
				case '.':
					j++;
					break;
				case 'x':
					birth(i, j);
					j++;
					break;
				}
	}
	Bterm(bp);
}

int
min(int a, int b)
{
	return(a < b ? a : b);
}

void
window(void)
{
	for (i0 = 1; i0 != NLIFE - 2 && row[i0] == 0; i0++)
		;
	for (i1 = NLIFE - 2; i1 != i0 && row[i1] == 0; --i1)
		;
	for (j0 = 1; j0 != NLIFE - 2 && col[j0] == 0; j0++)
		;
	for (j1 = NLIFE - 2; j1 != j0 && col[j1] == 0; --j1)
		;
}

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.