Browse Source

Plan 9 from Bell Labs 2009-08-28

David du Colombier 14 years ago
parent
commit
61a4cafe41

+ 37 - 13
sys/man/1/games

@@ -1,6 +1,6 @@
 .TH GAMES 1
 .SH NAME
-4s, 5s, juggle, mahjongg, memo, sokoban, sudoku \- time wasters
+4s, 5s, juggle, life, mahjongg, memo, sokoban, sudoku \- time wasters
 .SH SYNOPSIS
 .B games/4s
 .br
@@ -18,6 +18,9 @@
 ]
 .I pattern
 .br
+.B games/life
+.I startfile
+.br
 .B games/mahjongg
 [
 .B -c
@@ -48,7 +51,9 @@
 .SH DESCRIPTION
 There are a few games in
 .BR /bin/games :
-.TP \w'\fLfireworksXX'u
+.TF mahjongg
+.PD
+.TP
 .BR 4s , " 5s"
 Try to fill complete rows using 4-square or 5-square tiles.
 Move tiles left or right by moving the mouse.
@@ -96,7 +101,14 @@ can be used to speed up or slow down the action (default is 20).
 Try the pattern 333333441333333 or 333353505151512333333
 or YWUSQOMKIGECA
 (see
-.IR http://seehuhn.de/jong/theory.html ).
+.BR http://seehuhn.de/jong/theory.html ).
+.TP
+.B life
+Play the game of Life, given an initial position.
+There is a library of interesting initial positions;
+the library is consulted if
+.I startfile
+cannot be found.
 .TP
 .B mahjongg
 Remove all tiles
@@ -208,33 +220,38 @@ clear the board to its starting (or last loaded) state
 .TP
 .B Save
 save the current board to
-.I /tmp/sudoku-save
+.B /tmp/sudoku-save
 .TP
 .B Load
 load the last saved board from
-.I /tmp/sudoku-save
+.B /tmp/sudoku-save
 .TP
 .B Print
 print the current board and solution in a format
-suitable for addition in the sudoku library to
-.I /tmp/sudoku-board
+suitable for addition in the
+.I sudoku
+library to
+.B /tmp/sudoku-board
 .TP
 .B Offline
 pretty-print the board for off-line solving to
-.I /tmp/sudoku-print
+.B /tmp/sudoku-print
 .TP
 .B Exit
 quit the game
 .RE
 .IP
-Button 2 presents a list of sudoku boards from
-.I /sys/games/lib/sudoku/boards
-with varying degree of difficulty.
+Button 2 presents a list of
+.I sudoku
+boards of varying degrees of difficulty from
+.BR /sys/games/lib/sudoku/boards .
 .IP
 Pressing the
 .B Q
-key quits sudoku
+key quits
+.IR sudoku .
 .SH FILES
+.TF /sys/games/lib/mahjongg/*
 .TP
 .B /sys/games/lib/[45]scores
 score files of
@@ -242,6 +259,9 @@ score files of
 and
 .I 5s
 .TP
+.B /sys/games/lib/life/*
+interesting starting positions
+.TP
 .B /sys/games/lib/mahjongg/*
 image sprites, levels and backgrounds used by
 .I mahjongg
@@ -260,7 +280,11 @@ images and boards used by
 .SH SOURCE
 .B /sys/src/games
 .SH BUGS
-In 4s and 5s mouse warping (when the game is resumed,
+In
+.I 4s
+and
+.IR 5s ,
+mouse warping (when the game is resumed,
 and when a new tile appears) does not happen when
 the mouse cursor is outside the game window.
 Those who prefer to use the keyboard without the mouse

+ 7 - 4
sys/man/4/exportfs

@@ -44,14 +44,17 @@ gives the appearance of exporting a name space from a remote machine
 into a local file tree.
 .PP
 The options are:
-.TP
+.TF "-A \fIaddress"
 .PD
+.TP
 .B -A \fIaddress
-Use the address
+Use the network
 .I address
-for
+to
+.IR announce (2)
 .IR aan (8)
-connections.
+connections,
+if requested by the initial protocol.
 .TP
 .B -a
 Authenticate the user with the

+ 23 - 13
sys/man/8/aan

@@ -3,24 +3,30 @@
 aan \- always available network
 .SH SYNOPSIS
 .B aan
+.B -c
 [
 .B -d
 ]
 [
-.B -c
+.B -m maxto
+]
+.I dialstring
+.br
+.B aan
+[
+.B -d
 ]
 [
 .B -m maxto
 ]
-.I dialstring
-|
-.I dir
+.I netdir
 .SH DESCRIPTION
 .I Aan
 tunnels traffic between a client and a server through a persistent
 network connection.  If the connection breaks (voluntarily or
-due to networking problems), the aan client re-establishes the
-connection by redialing the server.
+due to networking problems), the
+.I aan
+client re-establishes the connection by redialing the server.
 .PP
 .I Aan 
 uses a unique protocol to make sure no data is ever lost even
@@ -31,7 +37,7 @@ retransmits all unacknowledged data between client and server.
 .PP
 A connection can be broken voluntarily (e.g. by roaming over IP networks), 
 or a connection can break when the IP service is unreliable.
-In either case
+In either case,
 .I aan
 re-establishes the client's connection automatically.
 .PP 
@@ -39,17 +45,17 @@ When the server part has not heard from the client in
 .I maxto
 seconds, the server part of
 .I aan
-exits.  The default
+exits.
+The default
 .I maxto
 is one day.
 The client side (option
 .BR -c )
 calls the server by its
 .IR dialstring ,
-while
-the server side listens for connections in the 
+while the server side listens for connections in the 
 already-announced network directory
-.IR devdir .
+.IR netdir .
 .PP
 .I Aan
 is usually run automatically through the
@@ -57,8 +63,11 @@ is usually run automatically through the
 option of
 .IR import (4).
 .SH EXAMPLE
-Assume the server part of aan is encapsulated in exportfs on the
-machine
+Assume the server part of
+.I aan
+is encapsulated in
+.I exportfs
+on the machine
 .B sob
 and started through
 .B aux/listen
@@ -79,6 +88,7 @@ using this command:
 import -p astro6 / /mnt/term
 .EE
 .SH FILES
+.TF /sys/log/aan
 .TP
 .B /sys/log/aan
 Log file

+ 11 - 1
sys/man/8/auth

@@ -1,6 +1,6 @@
 .TH AUTH 8
 .SH NAME
-changeuser, convkeys, convkeys2, printnetkey, status, enable, disable, authsrv, guard.srv, wrkey, login, newns, none, as \- maintain or query authentication databases
+changeuser, convkeys, convkeys2, printnetkey, status, enable, disable, authsrv, guard.srv, debug, wrkey, login, newns, none, as \- maintain or query authentication databases
 .SH SYNOPSIS
 .B auth/changeuser
 .RB [ -np ]
@@ -30,6 +30,8 @@ changeuser, convkeys, convkeys2, printnetkey, status, enable, disable, authsrv,
 .PP
 .B auth/guard.srv
 .PP
+.B auth/debug
+.PP
 .B auth/wrkey
 .PP
 .B auth/login
@@ -185,9 +187,17 @@ described in
 .I Guard.srv
 is similar.  It is called whenever a foreign (e.g. Unix) system wants
 to do a SecureNet challenge/response authentication.
+.SS Anywhere commands
 .PP
 The remaining commands need not be run on an authentication server.
 .PP
+.I Debug
+attempts to authenticate using each
+.B p9sk1
+key found in
+.I factotum
+and prints progress reports.
+.PP
 .I Wrkey
 prompts for a machine key, host owner, and host domain and stores them in
 local non-volatile RAM.

+ 2 - 4
sys/src/cmd/aan.c

@@ -78,7 +78,7 @@ static void		freeendpoints(Endpoints *);
 static void
 usage(void)
 {
-	fprint(2, "Usage: %s [-c] [-d] [-m maxto] dialstring|handle\n", progname);
+	fprint(2, "Usage: %s [-cd] [-m maxto] dialstring|netdir\n", progname);
 	exits("usage");
 }
 
@@ -112,11 +112,9 @@ threadmain(int argc, char **argv)
 	case 'm':
 		maxto = (int)strtol(EARGF(usage()), (char **)nil, 0);
 		break;
-	case '?':
 	default:
 		usage();
-	}
-	ARGEND;
+	} ARGEND;
 
 	if (argc != 1)
 		usage();

+ 0 - 1
sys/src/cmd/unix/drawterm/Make.config

@@ -1,3 +1,2 @@
 AUDIO=none
-CONF=osx
 include $(ROOT)/Make.$(CONF)

+ 1 - 2
sys/src/cmd/unix/drawterm/Make.win32

@@ -10,8 +10,7 @@ CC=$(MING)gcc
 AS=$(MING)as
 RANLIB=$(MING)ranlib
 WINDRES=$(MING)windres
-CFLAGS=-Wall -Wno-missing-braces -I$(ROOT)/include -I$(ROOT) -I$(ROOT)/kern\
- -c -D_X86_ -DIS_32 -DWINDOWS -DUNICODE -O2 # -Duintptr=unsigned
+CFLAGS=-Wall -Wno-missing-braces -I$(ROOT)/include -I$(ROOT) -I$(ROOT)/kern -c -D_X86_ -DIS_32 -DWINDOWS -DUNICODE -O2
 O=o
 FS=fs-win32
 IP=win32

+ 5 - 4
sys/src/cmd/unix/drawterm/README

@@ -24,14 +24,15 @@ See http://swtch.com/drawterm/
 
 SOURCE
 ------
-Use CVS: cvs -d :pserver:anoncvs@cvs.pdos.csail.mit.edu:/cvs co drawterm
-On the web at http://cvs.pdos.csail.mit.edu/cvs/drawterm
-In the Plan 9 distribution: /sys/src/cmd/unix/drawterm
+Use Mercurial: hg clone http://code.swtch.com/drawterm
+On the web at http://code.swtch.com/drawterm
+Tar file at http://swtch.com/drawterm/
+In the Plan 9 distribution: /sys/src/cmd/unix/drawterm/ (sometimes out of date)
 
 
 HELP
 ----
-Email Russ Cox <rsc@swtch.com> with bug reports or problems.
+Issue tracker: http://code.swtch.com/drawterm/issues
 
 
 TO DO:

+ 34 - 18
sys/src/cmd/unix/drawterm/cpu-bl.c

@@ -29,6 +29,7 @@ static AuthInfo *p9any(int);
 static char	*system;
 static int	cflag;
 extern int	dbg;
+extern char*	base;	// fs base for devroot
 
 static char	*srvname = "ncpu";
 static char	*ealgs = "rc4_256 sha1";
@@ -111,9 +112,7 @@ cpumain(int argc, char **argv)
 		close(fd);
 	}
 
-        user = getenv("USER");
-        if(user == nil)
-        	user = readcons("user", nil, 0);
+	user = getenv("USER");
 	secstoreserver = nil;
 	authserver = getenv("auth");
 	if(authserver == nil)
@@ -128,6 +127,14 @@ cpumain(int argc, char **argv)
 	case 'c':
 		system = EARGF(usage());
 		break;
+	case 'd':
+		dbg++;
+		break;
+	case 'e':
+		ealgs = EARGF(usage());
+		if(*ealgs == 0 || strcmp(ealgs, "clear") == 0)
+			ealgs = nil;
+		break;
 	case 'C':
 		cflag++;
 		cmd[0] = '!';
@@ -137,20 +144,11 @@ cpumain(int argc, char **argv)
 			strcat(cmd, p);
 		}
 		break;
-	case 'd':
-		dbg++;
-		break;
-	case 'e':
-		ealgs = EARGF(usage());
-		if(*ealgs == 0 || strcmp(ealgs, "clear") == 0)
-			ealgs = nil;
-		break;
 	case 'k':
 		keyspec = EARGF(usage());
 		break;
-	case 'o':
-		authserver = "plan9.bell-labs.com";
-		system = "p9auth.cs.bell-labs.com";
+	case 'r':
+		base = EARGF(usage());
 		break;
 	case 's':
 		secstoreserver = EARGF(usage());
@@ -165,6 +163,9 @@ cpumain(int argc, char **argv)
 	if(argc != 0)
 		usage();
 
+	if(user == nil)
+		user = readcons("user", nil, 0);
+
 	if(mountfactotum() < 0){
 		if(secstoreserver == nil)
 			secstoreserver = authserver;
@@ -552,7 +553,7 @@ p9any(int fd)
 	char tbuf[TICKETLEN+TICKETLEN+AUTHENTLEN], trbuf[TICKREQLEN];
 	char authkey[DESKEYLEN];
 	Authenticator auth;
-	int afd, i, v2;
+	int afd, i, n, v2;
 	Ticketreq tr;
 	Ticket t;
 	AuthInfo *ai;
@@ -630,8 +631,23 @@ p9any(int fd)
 	if(write(fd, tbuf, TICKETLEN+AUTHENTLEN) != TICKETLEN+AUTHENTLEN)
 		fatal(1, "cannot send ticket and authenticator back in p9sk1");
 
-	if(readn(fd, tbuf, AUTHENTLEN) != AUTHENTLEN)
-		fatal(1, "cannot read authenticator in p9sk1");
+	if((n=readn(fd, tbuf, AUTHENTLEN)) != AUTHENTLEN ||
+			memcmp(tbuf, "cpu:", 4) == 0){
+		if(n <= 4)
+			fatal(1, "cannot read authenticator in p9sk1");
+
+		/*
+		 * didn't send back authenticator:
+		 * sent back fatal error message.
+		 */
+		memmove(buf, tbuf, n);
+		i = readn(fd, buf+n, sizeof buf-n-1);
+		if(i > 0)
+			n += i;
+		buf[n] = 0;
+		werrstr("");
+		fatal(0, "server says: %s", buf);
+	}
 	
 	convM2A(tbuf, &auth, t.key);
 	if(auth.num != AuthAs
@@ -639,7 +655,7 @@ p9any(int fd)
 	|| auth.id != 0){
 		print("?you and auth server agree about password.\n");
 		print("?server is confused.\n");
-		fatal(1, "server lies got %llux.%d want %llux.%d", *(vlong*)auth.chal, auth.id, *(vlong*)cchal, 0);
+		fatal(0, "server lies got %llux.%d want %llux.%d", *(vlong*)auth.chal, auth.id, *(vlong*)cchal, 0);
 	}
 	//print("i am %s there.\n", t.suid);
 	ai = mallocz(sizeof(AuthInfo), 1);

+ 35 - 15
sys/src/cmd/unix/drawterm/cpu.c

@@ -29,6 +29,7 @@ static AuthInfo *p9any(int);
 static char	*system;
 static int	cflag;
 extern int	dbg;
+extern char*	base;	// fs base for devroot
 
 static char	*srvname = "ncpu";
 static char	*ealgs = "rc4_256 sha1";
@@ -111,9 +112,7 @@ cpumain(int argc, char **argv)
 		close(fd);
 	}
 
-        user = getenv("USER");
-        if(user == nil)
-        	user = readcons("user", nil, 0);
+	user = getenv("USER");
 	secstoreserver = nil;
 	authserver = getenv("auth");
 	if(authserver == nil)
@@ -128,6 +127,14 @@ cpumain(int argc, char **argv)
 	case 'c':
 		system = EARGF(usage());
 		break;
+	case 'd':
+		dbg++;
+		break;
+	case 'e':
+		ealgs = EARGF(usage());
+		if(*ealgs == 0 || strcmp(ealgs, "clear") == 0)
+			ealgs = nil;
+		break;
 	case 'C':
 		cflag++;
 		cmd[0] = '!';
@@ -137,17 +144,12 @@ cpumain(int argc, char **argv)
 			strcat(cmd, p);
 		}
 		break;
-	case 'd':
-		dbg++;
-		break;
-	case 'e':
-		ealgs = EARGF(usage());
-		if(*ealgs == 0 || strcmp(ealgs, "clear") == 0)
-			ealgs = nil;
-		break;
 	case 'k':
 		keyspec = EARGF(usage());
 		break;
+	case 'r':
+		base = EARGF(usage());
+		break;
 	case 's':
 		secstoreserver = EARGF(usage());
 		break;
@@ -161,6 +163,9 @@ cpumain(int argc, char **argv)
 	if(argc != 0)
 		usage();
 
+	if(user == nil)
+		user = readcons("user", nil, 0);
+
 	if(mountfactotum() < 0){
 		if(secstoreserver == nil)
 			secstoreserver = authserver;
@@ -548,7 +553,7 @@ p9any(int fd)
 	char tbuf[TICKETLEN+TICKETLEN+AUTHENTLEN], trbuf[TICKREQLEN];
 	char authkey[DESKEYLEN];
 	Authenticator auth;
-	int afd, i, v2;
+	int afd, i, n, v2;
 	Ticketreq tr;
 	Ticket t;
 	AuthInfo *ai;
@@ -626,8 +631,23 @@ p9any(int fd)
 	if(write(fd, tbuf, TICKETLEN+AUTHENTLEN) != TICKETLEN+AUTHENTLEN)
 		fatal(1, "cannot send ticket and authenticator back in p9sk1");
 
-	if(readn(fd, tbuf, AUTHENTLEN) != AUTHENTLEN)
-		fatal(1, "cannot read authenticator in p9sk1");
+	if((n=readn(fd, tbuf, AUTHENTLEN)) != AUTHENTLEN ||
+			memcmp(tbuf, "cpu:", 4) == 0){
+		if(n <= 4)
+			fatal(1, "cannot read authenticator in p9sk1");
+
+		/*
+		 * didn't send back authenticator:
+		 * sent back fatal error message.
+		 */
+		memmove(buf, tbuf, n);
+		i = readn(fd, buf+n, sizeof buf-n-1);
+		if(i > 0)
+			n += i;
+		buf[n] = 0;
+		werrstr("");
+		fatal(0, "server says: %s", buf);
+	}
 	
 	convM2A(tbuf, &auth, t.key);
 	if(auth.num != AuthAs
@@ -635,7 +655,7 @@ p9any(int fd)
 	|| auth.id != 0){
 		print("?you and auth server agree about password.\n");
 		print("?server is confused.\n");
-		fatal(1, "server lies got %llux.%d want %llux.%d", *(vlong*)auth.chal, auth.id, *(vlong*)cchal, 0);
+		fatal(0, "server lies got %llux.%d want %llux.%d", *(vlong*)auth.chal, auth.id, *(vlong*)cchal, 0);
 	}
 	//print("i am %s there.\n", t.suid);
 	ai = mallocz(sizeof(AuthInfo), 1);

+ 432 - 0
sys/src/games/life.c

@@ -0,0 +1,432 @@
+/*
+ * life - john conways's game of life (and variations),
+ * sci. am. 223, october 1970, pp. 120—123, or
+ * http://en.wikipedia.org/wiki/Conway's_Game_of_Life
+ */
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <draw.h>
+#include <event.h>
+
+enum {
+	NLIFE	= 256,		/* life array size */
+	PX	= 4,		/* cell spacing */
+	BX	= PX - 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	centerlife(void);
+void	death(int, int);
+int	generate(void);
+int	interest(int [NLIFE], int);
+void	main(int, char *[]);
+int	min(int, int);
+void	readlife(char *);
+void	redraw(void);
+void	setrules(char *);
+void	window(void);
+
+static void	reshape(void);
+
+static void
+setbox(int i, int j)
+{
+	Point loc;
+
+	loc = Pt(cen.x + j*PX, cen.y + i*PX);
+	draw(screen, Rpt(loc, addpt(loc, Pt(BX, BX))), box, nil, ZP);
+}
+
+static void
+clrbox(int i, int j)
+{
+	Point loc;
+
+	loc = Pt(cen.x + j*PX, cen.y + i*PX);
+	draw(screen, Rpt(loc, addpt(loc, Pt(BX, BX))), display->white, nil, ZP);
+}
+
+void
+setrules(char *r)
+{
+	char *a;
+
+	for (a = action; a != &action[nelem(action)]; *a++ = *r++)
+		;
+}
+
+static void
+g9err(Display *, char *err)
+{
+	static int entered = 0;
+
+	fprint(2, "%s: %s (%r)\n", argv0, err);
+	exits(err);
+}
+
+void
+usage(void)
+{
+	fprint(2, "Usage: %s [-3o] [-r rules] file\n", argv0);
+	exits("usage");
+}
+
+void
+idle(void)
+{
+	int c;
+
+	while (ecanmouse())
+		emouse();			/* throw away mouse events */
+	while (ecankbd())
+		if ((c = ekbd()) == 'q' || c == 0177) /* watch keyboard ones */
+			exits(nil);
+	if (needresize)
+		reshape();
+}
+
+void
+main(int argc, char *argv[])
+{
+	int delay = 1000;
+
+	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;
+	default:
+		usage();
+	} ARGEND
+	if (argc != 1)
+		usage();
+
+	initdraw(g9err, 0, argv0);
+	einit(Emouse|Ekeyboard);	/* implies rawon() */
+
+	cen = divpt(subpt(addpt(screen->r.min, screen->r.max),
+		Pt(NLIFE * PX, NLIFE * PX)), 2);
+	box  = allocimage(display, Rect(0, 0, BX, BX), RGB24, 1, DBlack);
+	assert(box != nil);
+
+	redraw();
+	readlife(argv[0]);
+	do {
+		flushimage(display, 1);
+		idle();
+		sleep(delay);
+		idle();
+	} 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];
+					setbox(i, j);
+					*addp++ = lp;
+					break;
+				case 'd':
+					--*lp;
+					--drow[i];
+					--dcol[j];
+					clrbox(i, 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];
+	}
+	if (row[1] || row[NLIFE-2] || col[1] || col[NLIFE-2])
+		centerlife();
+	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(+);
+	setbox(i, j);
+}
+
+/*
+ * 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(-);
+	clrbox(i, j);
+}
+
+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);
+	}
+	draw(screen, screen->r, display->white, nil, ZP);
+	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);
+	centerlife();
+}
+
+int
+min(int a, int b)
+{
+	return(a < b ? a : b);
+}
+
+void
+centerlife(void)
+{
+	int i, j, di, dj, iinc, jinc, t;
+
+	window();
+	di = NLIFE / 2 - (i0 + i1) / 2;
+	if (i0 + di < 1)
+		di = 1 - i0;
+	else if (i1 + di >= NLIFE - 1)
+		di = NLIFE - 2 - i1;
+	dj = NLIFE / 2 - (j0 + j1) / 2;
+	if (j0 + dj < 1)
+		dj = 1 - j0;
+	else if (j1 + dj >= NLIFE - 1)
+		dj = NLIFE - 2 - j1;
+	if (di != 0 || dj != 0) {
+		if (di > 0) {
+			iinc = -1;
+			t = i0;
+			i0 = i1;
+			i1 = t;
+		} else
+			iinc = 1;
+		if (dj > 0) {
+			jinc = -1;
+			t = j0;
+			j0 = j1;
+			j1 = t;
+		} else
+			jinc = 1;
+		for (i = i0; i * iinc <= i1 * iinc; i += iinc)
+			for (j = j0; j * jinc <= j1 * jinc; j += jinc)
+				if (life[i][j] & 1) {
+					birth(i + di, j + dj);
+					death(i, j);
+				}
+	}
+}
+
+void
+redraw(void)
+{
+	int i, j;
+
+	window();
+	draw(screen, screen->r, display->white, nil, ZP);
+	for (i = i0; i <= i1; i++)
+		for (j = j0; j <= j1; j++)
+			if (life[i][j] & 1)
+				setbox(i, j);
+}
+
+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)
+		;
+}
+
+static void
+reshape(void)
+{
+//	int dy12;
+
+//	if (needresize) {
+//		sqwid = Dx(screen->r) / (1 + bdp->cols + 1);
+//		dy12  = Dy(screen->r) / (1 + bdp->rows + 1 + 2);
+//		if (sqwid > dy12)
+//			sqwid = dy12;
+//		recompute(bdp, sqwid);
+//	}
+	sleep(1000);
+	needresize = 0;
+	cen = divpt(subpt(addpt(screen->r.min, screen->r.max),
+		Pt(NLIFE * PX, NLIFE * PX)), 2);
+	redraw();
+	flushimage(display, 1);
+}
+
+/* called from graphics library */
+void
+eresized(int callgetwin)
+{
+	needresize = callgetwin + 1;
+
+	/* new window? */
+	/* was using Refmesg */
+	if (needresize > 1 && getwindow(display, Refnone) < 0)
+		sysfatal("can't reattach to window: %r");
+
+	/* destroyed window? */
+	if (Dx(screen->r) == 0 || Dy(screen->r) == 0)
+		exits("window gone");
+
+	reshape();
+}