[net.sources] LIFE - fancy new version

dbell@daisy.UUCP (David I. Bell) (02/03/85)

This article (and the next 2) contain sources to my version of a program
which plays the mathematical game of life.  Its features include:
	* An 'infinite' sized board
	* Display routines capable of showing large objects
	* Editing of multiple life objects at once
	* Provides macros, loops, and variables
	* Allows rule changing to create alternate life universes
	* Supports libraries of life objects
	* Friendly storage format for objects

This program requires my 'dpy' module for screen handling.  That module
was posted very recently by me.  If necessary, I can mail copies to you.

Part 4 of this posting contains a library of life objects which I have
seen or created, along with comments for each one.  I am very interested
in receiving other interesting life objects to add to the library.  Thanks!!

			- nsc!daisy!dbell -

#---Cut here and place in it's own directory, then feed to Bourne shell---
# This is a shell archive.  Remove anything before this line, then
# unpack it by saving it in a file and typing "sh file".  (Files
# unpacked will be owned by you and have default permissions.)
# This archive contains:
#   README (1117 chars)
#   life.doc (21688 chars)
#   makefile (357 chars)
#   life.h (7899 chars)
#
echo x - README
sed -e 's/^X//' > "README" << '//E*O*F README//'
XThis is a version of life which was written to run under 4.2 BSD UNIX
Xrunning on a VAX 750.  If you are not running this configuration, you
Xwill probably have to make modifications.  The only Berkeley specific
Xfeature used (that I can recall) is the use of the FIONREAD ioctl.  But
Xthere are many int/long mismatches (some of us on VAXes are rather lax).
X
XThis program requires the use of my "dpy" library module for its screen
Xupdating.  This module was recently posted to net.sources.  If you did
Xnot receive that module, I can send it to you privately if necessary.
XChanging the life program to use curses would be possible, but painful.
X
XA program like this can have almost an infinite number of features.
XThere are still things I would like to include in it, but enough major
Xthings are implemented to make it very useful.  Suggestions for new
Xfeatures or changes are welcome, as are the (to be expected) bug fixes.
X
XOnce again, sorry about the the documentation.  Life.doc should be formatted,
Xbut the life program would probably have waited forever until I did that.
XEnjoy!
X			David I. Bell
X			nsc!daisy!dbell
//E*O*F README//
echo x - life.doc
sed -e 's/^X//' > "life.doc" << '//E*O*F life.doc//'
X			The Game of LIFE
X			program by David I. Bell
X
X
XIf you do not know what the game of life is, see the appendix at the end.
XExcept in that appendix, the remainder of this document assumes that you
Xknow the rules and terms of the game.
X
XThis program plays the game of life on a practically infinite board by using
Xsparse matrices.  Therefore there is no hard limit on the size of a life
Xobject except for its memory requirements.  The memory requirements depend
Xon the number of live cells of the object, and not on their locality.  The
Xtime needed to compute one generation also depends on the number of live cells.
XThe speed is such that on a VAX 750, an object with 2000 live cells takes
Xabout 1 second of run time per generation.  Typical objects run much faster.
X
XLife programs are limited by the size of their display.  On special kinds of
Xterminals, very large objects can be seen directly.  However, this program
Xis designed to run on the vast majority of terminals, most of which have
Xdisplays which are limited to showing 24 by 80 characters.  The program gets
Xaround this limitation by doing two things.  Firstly, the standard display
Xcan show any portion of an object.  As the cursor is moved around the object,
Xthe view scrolls as necessary in any direction so that the location of the
Xcursor remains visible.  Thus you can move to any point in the object and
Xsee what is nearby.  Secondly, the display can be scaled by any amount up
Xto 1000.  This means that each position on the screen can represent an area
Xwhich is up to 1000 by 1000 cells of the object.  Obviously, information must
Xbe lost when this is done.  So the program displays the number of live cells
Xin each region, from 1 to 9 (or * for 10 or more).  In practice this display
Xis perfectly adequate for seeing what is happening.  Once you have moved the
Xcursor to an interesting location, you can reduce the scale to see what is
Xactually there in detail.  The 's' command sets the viewing scale as desired,
Xand 'S' sets auto-scale mode, which keeps an object visible as it grows.
XBoth commands recenter the view around the current cursor position.  When
Xyou are trying to count the number of cells between two parts of an object,
Xthe ';grid' command is useful.  It changes the display of dead cells from
Xblanks to another character (typically periods).
X
XThis program can simulaneously handle more than one object.  Each object is
Xdistinct and has its own name, and you can move between objects to select the
Xone currently being edited.  The ';create name' command creates a new object,
Xand the ';edit name' switches between objects (defaulting to the previously
Xedited object if no name is given).  Cells can be copied or moved between
Xobjects.  This allows you to make backups of an object, compare two objects,
Xor split up and edit the parts of a single object.  The ';copy' command
Xcopies the current object to another object.  The ';insert' command inserts
Xanother object into the current object.  This is done in such as way that the
Xtwo object's cursor locations match.  The ';list' command will display data
Xabout all objects whose names do not begin with a period (adding '-a' to the
Xcommand will show even those).  Objects with names beginning with '..' are
Xreserved, and have special uses like temporary object manipulation, the saving
Xof deleted objects, and for implementing undo.
X
XMost commands (and the viewpoint), are based on the current cursor location.
XTo change the viewpoint, or to point at a location in the object that you
Xwish to modify, you must move the cursor around in the object.  To do this,
Xyou can use some single character commands.  The directions for movement
Xare like in rogue, so that for example, 'h' moves left, 'j' moves down, 'y'
Xmoves to the upper left, and so on.  The commands are multiplied by the scale
Xfactor, so that you can move about in large objects easily.  In addition,
Xthey accept an optional preceeding numeric argument, which multiplies their
Xmovement by the given amount.  Also, space (or period) moves to the right,
Xwhile return moves down to the next line and back to the column of the pointer.
XThe pointer is simply a location that is remembered in an object, and only a
Xfew commands use it.  The '@' command sets the pointer to the cursor location.
XThe movement commands when capitalized perform a shift of both the cursor
Xand the viewing region together by 1/4 of the screen size.  This is useful
Xto shift the view slightly to see an object on the edge better.
X
XThe simplest commands for changing an object are 'o', 'x', 't', and 'i'.
XThe 'o' command creates a live cell and moves the cursor to the right.
XThe 'x' command creates a dead cell and moves the cursor to the right.
XBoth of these commands can accept a numeric argument which replicates their
Xaction by the given amount (as an example, the command string '25o' creates
Xa row of 25 live cells).  The 't' command toggles the current cell from
Xdead to alive, or from alive to dead, without moving the cursor.  The 'i'
Xcommand sets a mode in which some of the movement commands described in
Xthe previous paragraph automatically insert or delete cells.  This makes it
Xeasy to create lines of cells in any direction.
X
XA very useful feature is the use of 'selections'.  You can select any group
Xof cells of an object, and handle them specially, leaving the rest of the
Xcells unchanged.  For example, you can save the selected cells as a different
Xobject, or can move them around, or can delete them.  Selected cells appear
Xdifferently in the display (as hash marks), so that you can recognize which
Xcells are selected.  Selections are set based on where the cursor is.  You
Xcan select all the cells in any quadrant or half-plane around the cursor.
XYou can also select all the cells which are king-wise connected to your cursor
Xlocation.  Each selection command adds to the set of selected cells.  The 'M'
Xcommand removes all current selections so you can start a new set.  There are
Xseveral commands which act on a selection in interesting ways.  For example,
Xif the cursor is on a glider, the command 'dd' will delete just the glider.
XThe commands which flip or rotate selections do so with respect to the current
Xcursor location.
X
XTo compute generations, you use the 'g' or 'G' commands.   The 'g' command
Xcomputes a single generation of the current object, or as many generations
Xas you specified in its preceeding argument.  The 'G' command simply means
Xrun for an infinite number of generations.  If the object dies or becomes
Xstable, computations automatically stop.  However, loops of period 2 or
Xgreater are not detected.  While generations are being computed, you can use
Xthe movement or scaling commands to wander around the object.  But attempts
Xto change the object while it is running will be rejected.  If you wish to
Xwait until the indicated computations are complete before the next command is
Xexecuted, use the ';wait' command (this is often used inside of a command
Xmacro or loop).  To stop the computations for an object, use the 'g' command
Xagain to reset the count to zero.  Normally each generation will be displayed.
XTo speed up the computations when you don't want to see every generation, use
Xthe ';frequency n' command to set how often the screen is updated.
X
XThis program can read commands from a file.  It can also save objects out to
Xa file.  When writing an object to a file, what is actually written is a
Xsequence of commands which regenerates the object.  The choice of commands is
Xsuch that for small objects, the commands look like a picture of the object.
XThus, the object is written using the commands '.' (to move right), 'o' (to
Xinsert cells), and '\n' (to move to the next line).  (Periods are used instead
Xof spaces so that you can count the spaces between live cells easily.  However,
Xspaces or tabs could also be used and will act reasonably.)  Thus for small
Xobjects, you can create a picture of an object in an editor, and then the
Xobject can be read in by the life program.  Lines beginning with '!' or '#'
Xare ignored, so you can comment your objects.  When writing out an object, if
Xit is too large to be typed to your screen or edited easily, the output will
Xcontain numeric arguments as necessary in order to compress the resulting file.
XThe command ';write filename' is used to save an object.
X
XWhen reading an input file, the program looks for six different file names in
Xsequence.  It starts by looking for the file name as given.  Then it looks for
Xthe name with an '.l' extension appended.  Then, if the environment variable
XLIFELIB is defined, it looks in that directory for the file, and then for the
Xfile with the '.l' extension.  Finally, it looks in /usr/games/lib/life for
Xthe file, and then for the file with the '.l' extension.  Thus there is a
Xstandard library of interesting life objects, and you can create your own
Xlife library.  The ';read filename' command reads back a command file.  In
Xaddition, when the life program is started, you can specify a filename on the
Xcommand line which will be read before any commands are read from the terminal.
XThis is useful when you want to immediately read a life object from a library.
X
XCommand loops and macros can be defined using the '<' and '>' commands.  If
Xa numeric argument is specified, a loop is being defined.  If no argument is
Xgiven, a macro is being defined.  While defining a macro or loop, the commands
Xyou type are executed normally, so that you can see how the definition will
Xexecute.  This is useful when you define a macro to create an object, since
Xyou can easily see how to create the object.  Loops and macros can contain
Xboth line mode and character mode style commands.  While you are defining a
Xloop or macro, the status line will indicate this and give the current depth
Xof loop or macro nesting.
X
XWhen defining a macro, the next letter after the '<' is the macro name being
Xdefined (the name must be a lower case letter).  All of the commands between
Xthe '<' and the '>' (except for the macro name character) are saved.  Once a
Xmacro has been defined, it is used by typing the ESCAPE character followed by
Xthe macro letter.  As an example, the command string '<gththtuktntj>' defines
Xa macro named 'g' which causes a glider to be inserted at the current cursor
Xposition every time you type '<ESC>g'.  The ';dumpmacros file' command writes
Xout the definitions of all macros so that you can read them back in later.
X
XIf a loop is being defined, the loop is executed the number of times specifed
Xby the argument to '<'.  If two arguments are specifed, the loop runs from
Xthe first to the second argument (backwards if the second argument is less
Xthan the first argument).  The value of the loop counter is available in the
Xloop itself by using '%' as you would a number.  As an example of a loop,
Xthe command string '3,7<%o >' inserts five strings of cells spaced apart by
Xone cell, where the first string contains three cells, and the last string
Xcontains seven cells.  When nesting loops, supplying an argument to '%' can
Xobtain the loop counter values for the outside loops while in the inner loop.
XThus, '2%' obtains the loop value for the next-to-innermost loop level.
X
XWhile defining a loop or macro, you can request user intervention by using
Xthe ';ttyinput' command.  This will suspend the execution of the loop or macro
Xand notify the user that input is desired by displaying 'tty-input' in the
Xstatus line.  The user can then execute any commands of his choice.  When he
Xis ready to proceed, he uses the ';endinput' (or a control-D) command to 
Xcontinue execution where it had paused.  Different commands can be given by
Xthe user each time the loop or macro asks for terminal input.  If the command
X';ttyinput -c' is used, terminal commands will only be asked for if there
Xis any terminal input available to be read.
X
XWhile a macro or loop is executing, there is no screen updating.  Thus a
Xcomplicated command sequence can be executed without letting the user see
Xwhat is occuring until it is finished.  However, if you wish to update the
Xscreen in the middle of execution, the ';updateview' command will do this.
XCommands from the terminal automatically do an update whenever no more
Xinput is available.
X
XThere is a set of variables which you can use to influence your commands.
XThey come in two kinds.  The first kind is the single-character variable.
XThere are 52 of these variables (the lower and upper case letters).  These
Xvariables are used with a leading dollar sign.  Any command which accepts an
Xargument can accept '$c' to mean the value of variable 'c'.  These variables
Xcan be set using the ';set' command, which takes a variable name followed by
Xan expression.  (As a shortcut, the '+c' command increments variable c.)
XThe initial values of all variables are zero.  These variables are useful
Xinside a macro in order to execute a command string over and over again with
Xonly small differences each time (such as to vary the placement of two objects
Xwith respect to each other).  As an example of the use of a single-character
Xvariable, the command string '$al' moves the cursor to the right by the number
Xof cells given by the value of variable 'a'.
X
XThe other kind of variables are the multi-character variables.  These are
Xa fixed set of names, and their values are specific things, and cannot be
Xchanged.  For example, the 'cx' variable returns the x coordinate of the
Xcurrent cursor position.  You can see the complete set of variables using
Xthe ';variables' command.  Since the parser must be able to distinguish
Xvariable names from command letters, you cannot use them without surrounding
Xthem with a pair of parenthesis.  The parenthesis can actually contain any
Xexpression, including single-character variables.  When inside parenthesis
Xor as part of a line mode command, variables do not need the leading '$'.
XAs an example of multi-character variables used with an expression, the
Xcommand string '((vmaxx-vminx)/2)l' shifts the cursor to the right by an
Xamount equal to half of the screen width.
X
XThe rules of life can be changed to some degree.  This allows you to explore
Xalternative life universes.  You can specify how many live cells are required
Xfor a cell to be born, or to stay alive.  However, you cannot change the
Xneighborhood used for counting live cells (the program always counts all of
Xthe eight neighboring cells).  To change the cells needed for birth or death,
Xuse the ';rules <born> <live>' line mode command.  The <born> string lists
Xthose counts of neighbors which are required for a new cell to be born.  The
X<live> string lists those counts of neighbors which are required for an
Xexisting cell to stay alive.  The standard rules are thus described by the
Xcommand ';rules 3 23'.
X
XThere are two kinds of commands to life.  Line mode commands begin with a
Xcolon or semi-colon character, and are terminated by an end of line character.
XMany of these have already been described above.  Use the ';help' command to
Xlist all of the line mode commands.  They are reasonably self-documenting.
XHere are some of the remaining useful line mode commands.  The ';quit' command
Xexits the program.  The ';zero' command clears all cells of an object and
Xresets the scaling factor and cursor position.  The ';copyselection name' and
X';moveselection name' commands copy or move selected cells to another object.
XFinally, the ';undo' command will undo the most recent change to the current
Xobject.  This can undo deletions, insertions, and running of generations,
Xamong other things.  But you get only one level of backup  (A related backup
Xfeature is the 'p' command, which will reinsert the last deleted object to
Xthe current cursor location.)
X
XCharacter mode commands are executed immediately when complete.  Until it is
Xcomplete, it can be edited using the normal erase and kill characters.  The
Xcharacter mode commands can be preceeded by zero, one, or two arguments.
XThese arguments are generally used as a count of how many times the command
Xis to be executed.  Missing arguments are defaulted to 1 (with the exceptions
Xnoted in the tables below).  If two arguments are specified, they are separated
Xby a comma.  So a character mode command can take one of the following forms:
X
X                <cmd>		Both arguments defaulted.
X         <arg1> <cmd>		First argument given, second is defaulted.
X<arg1> , <arg2> <cmd>		Both arguments given.
X
XThe following table specifies the character mode commands, arranged in
Xuseful catagories.  Many of these have been described in detail above.
X
X
X	--- MOVEMENT COMMANDS ---
X
XSPACE	move right <arg1> cells
X.	move right <arg1> cells
Xh	move left <arg1> cells
Xl	move right <arg1> cells
Xk	move up <arg1> cells
Xj	move down <arg1> cells
Xy	move upper left <arg1> cells
Xu	move upper right <arg1> cells
Xb	move lower left <arg1> cells
Xn	move lower right <arg1> cells
XLF	move to next "line"
XTAB	move to next "tab stop"
X@	remember current location as the pointer
Xc	return to location of the pointer
X/	move cursor to next object
X
X
X	--- SCREEN COMMANDS ---
X
Xs	set viewing scale to <arg1> and center view (default current scale)
XS	turn on auto-scaling and center view
XH	shift view left by width/4
XL	shift view right by width/4
XK	shift view up by height/4
XJ	shift view down by height/4
XY	shift view left and up
XU	shift view right and up
XB	shift view left and down
XN	shift view right and down
X^L	refresh the screen
X
X
X	--- SINGLE CELL COMMANDS --
X
Xo	insert <arg1> cells and move cursor right
XO	insert <arg1> cells and move cursor right
Xx	kill <arg1> cells and move cursor right
Xt	toggle cell at current location
Xi	toggle movement mode to move, insert, or delete
X
X
X	--- MULTIPLE CELL COMMANDS ---
X
Xd<sel>	delete cells described by <sel>
Xp	place last deleted object at current location
Xf<sel>	flip cells left-to-right as described by <sel>
Xr<sel>	rotate cells 90 degrees clockwise as described by <sel>
Xm<sel>	mark (select) cells as described by <sel>
XM	remove all marks (selections)
X
X
X	--- LOOP, VARIABLE, AND MACRO COMMANDS ---
X
X<	begin loop which executes from <arg1> to <arg2> times (if <arg1> given)
X<<ch>	begin definition of macro named <ch> (if <arg1> not given)
X>	end loop or macro definition
XESC<ch>	execute a macro command named <ch>
X+<ch>	increment the value of the single-character variable <ch> by arg1
X
X
X	--- MISCELLANEOUS COMMANDS ---
X
Xg	compute <arg1> generations (or stop if running)
XG	compute infinite generations
Xz	set generation number to <arg1> (default zero)
X!	ignore characters until end of line
X#	ignore character until end of line
X:<line>	execute a line mode command
X;<line>	execute a line mode command
X
X
XArguments specified above as <arg1> and <arg2> can be any of the following:
X$<var>	value of variable <var>, where <var> is a lower or upper case letter
X(expr)	an arithmetic expression containing constants, variables, or operators
X%	current loop counter value
X<num>	explicit numeric value
X
X
XThose commands above referencing <sel> take one of the following letters.
XThe command affects only those cells selected.  As a special case, repeating
Xthe command letter is equivilant to selecting 'o' (the connected object).
XExample: 'dd' deletes the connected object the cursor is on.
Xa	all cells
Xc	cell at current location
Xo	cells in the king-wise connected object at current location
Xh	cells to left of cursor
Xl	cells to right of cursor
Xk	cells above cursor
Xj	cells below cursor
Xb	cells below and left of cursor
Xn	cells below and right of cursor
Xu	cells above and right of cursor
Xy	cells above and left of cursor
Xp	cells in rectangle determined by pointer and cursor
Xm	cells which are marked
Xv	cells visible in window
X
X
X				APPENDIX
X
XThis is a short introduction to life.  Life is played on an infinitely large
Xboard divided into squares.  Each square is called a cell.  Each cell can be
Xeither dead or alive.  Dead cells are seen as blanks, whereas live cells are
Xseen as non-blanks.  You begin to play by choosing some arbitrary set of
Xlive and dead cells.  This configuration is called generation 0.  There is
Xa set of rules which transforms this set of cells into another set of cells,
Xcalled generation 1.  These same rules are then reapplied to generation 1 to
Xproduce generation 2.  This process continues indefinitely.  The 'purpose' of
Xthe game is to find starting patterns such that 'interesting things' result.
X
XThe rules which are applied are as follows.  Take any cell of a generation,
Xand call it the current cell.  Consider the eight cells immediately adjacent
Xto the current cell.  Count the number of these eight cells which are alive.
XIf the current cell is dead and the count is 3, then the current cell changes
Xto a live cell in the next generation.  If the current cell is alive and the
Xcount is NOT 2 or 3, then the current cell changes to a dead cell in the next
Xgeneration.  Otherwise the cell remains unchanged in the next generation.
XThis rule is applied to every cell of a generation SIMULTANEOUSLY.  Thus to
Xrephrase, 3 live neighbors causes a new cell to be born, whereas 2 or 3 live
Xneighbors keeps a cell alive.
X
XTo see how these rules work in practice, run the program and start with some
Xnumber of live cells in some arrangement, and watch the generations change.
XIf you can predict what the changes will be, then you understand the rules.
XThe following are some objects to try, along with some descriptive names.
XWarning: the last example gets complicated!!
X
X  O         O          O                  O             O          OO
X   O        O         OO       OO         OO             O          OO
X  O         O         O        O          O            OOO          O
X
Xdoomed   blinker   beehive   block   traffic lights   glider   r-pentomino
//E*O*F life.doc//
echo x - makefile
sed -e 's/^X//' > "makefile" << '//E*O*F makefile//'
X# @(#)makefile	1.3	1/31/85
X# @(#)
X
XCFLAGS = -O
XCC = cc
X
XCFILES = alloc.c cell.c cmd.c cmdl.c debug.c gen.c io.c main.c mark.c\
X	object.c scan.c vars.c view.c
X
XOFILES = alloc.o cell.o cmd.o cmdl.o debug.o gen.o io.o main.o mark.o\
X	object.o scan.o vars.o view.o
X
Xlife:	${OFILES} makefile
X	${CC} ${CFLAGS} -o life ${OFILES} -ldpy -ltermlib
X
X${OFILES}: life.h
//E*O*F makefile//
echo x - life.h
sed -e 's/^X//' > "life.h" << '//E*O*F life.h//'
X/*
X * @(#)life.h	1.9	1/31/85
X * @(#)Copyright (C) 1985 by D Bell
X */
X
X#include <stdio.h>
X#include <signal.h>
X#include <setjmp.h>
X
X#define	LIFELIB	"/usr/games/lib/life"	/* directory for general life library */
X#define	LIFEVAR	"LIFELIB"		/* environment name for user's lib */
X#define	ALLOCOBJ 10			/* how many new objects to allocate */
X#define	ALLOCROW 50			/* how many new rows to allocate */
X#define	ALLOCCELL 500			/* how many new cells to allocate */
X#define	FILESIZE (1024*4)		/* file buffer size */
X#define	LOOPSIZE 5000			/* characters in command loops */
X#define	SCAN_SIZE 100			/* maximum command length */
X#define	MAXINPUT 20			/* maximum nesting of command inputs */
X#define	MAXNAME	32			/* maximum object name size */
X#define	MAXSCALE 1000			/* maximum scale factor */
X#define	WRITEROWS 100			/* default maximum rows for writing */
X#define	WRITECOLS 79			/* default maximum cols for writing */
X#define	ESC	'\033'			/* escape character */
X#define	INFINITY 0x7fffffff		/* infinite value */
X#define	LIFE	9			/* live cell value in neighbor count */
X#define	STDIN	0			/* standard input */
X#define	STDOUT	1			/* standard output */
X#define	STDERR	2			/* standard error */
X#define	RELATIVE 0			/* do object additions relatively */
X#define	ABSOLUTE 1			/* do object additions absolutely */
X#define	M_MOVE	0			/* movement mode */
X#define	M_INSERT 1			/* insertion mode */
X#define	M_DELETE 2			/* deletion mode */
X#define	MARK_ANY 0x1			/* mark always set */
X#define	MARK_USR 0x2			/* marked due to user specification */
X#define	MARK_CMD 0x4			/* marked only for current command */
X#define	MARK_SRC 0x8			/* marked by searching */
X#define	MARK_SEE (MARK_USR)		/* marks seen by user */
X#define	MARK_ALL (MARK_ANY|MARK_USR|MARK_CMD|MARK_SRC)	/* all non-null marks */
X#define	INP_TTY	0			/* input is terminal */
X#define	INP_FILE 1			/* input is file */
X#define	INP_LOOP 2			/* input is command loop */
X#define	INP_MACRO 3			/* input is command macro */
X#define	SCAN_ABORT 1			/* setjmp value for aborted command */
X#define	SCAN_EDIT 2			/* setjmp value for edited command */
X#define	SCAN_EOF 3			/* setjmp value for no command data */
X#define	crow	curobj->o_currow	/* current row of current object */
X#define	ccol	curobj->o_curcol	/* current column of current object */
X#define	cmark	curobj->o_mark		/* current mark being applied */
X#define	cscale	curobj->o_scale		/* current scale of current object */
X#define	prow	curobj->o_prow		/* current pointer row */
X#define	pcol	curobj->o_pcol		/* current pointer column */
X
X/* macro to detect reserved names */
X#define	BADNAME(s) ((s[0] == '.') && ((s[1] == '\0') || (s[1] == '.')))
X
X
Xstruct	object	{			/* structure for each object */
X	struct	row	*o_firstrow;	/* first row */
X	struct	row	*o_lastrow;	/* last row */
X	struct	object	*o_next;	/* next object */
X	long	o_count;		/* number of live cells */
X	long	o_born;			/* number of cells born */
X	long	o_died;			/* number of cells died */
X	long	o_currow;		/* current row */
X	long	o_curcol;		/* current column */
X	long	o_minrow;		/* minimum row seen in window */
X	long	o_maxrow;		/* maximum row seen in window */
X	long	o_mincol;		/* minimum column seen in window */
X	long	o_maxcol;		/* maximum column seen in window */
X	long	o_prow;			/* currently pointed at row */
X	long	o_pcol;			/* currently pointed at column */
X	long	o_gen;			/* current generation */
X	short	o_scale;		/* current scaling factor for view */
X	char	o_autoscale;		/* doing autoscaling */
X	char	o_mark;			/* mark value for new cells */
X	char	o_reserved;		/* reserved object */
X	char	o_lock;			/* locked against computing gens */
X	char	o_name[MAXNAME+1];	/* name of object */
X};
X
Xstruct	row	{			/* structure for each row of cells */
X	struct	row	*r_next;	/* link to next row */
X	struct	cell	*r_firstcell;	/* link to first cell */
X	struct	cell	*r_lastcell;	/* link to last real cell */
X	long	r_row;			/* row number */
X	long	r_count;		/* number of cells in this row */
X};
X
Xstruct	cell	{			/* structure for each cell */
X	struct	cell	*c_next;	/* link to next cell */
X	long	c_col;			/* column number */
X	int	c_marks;		/* marking values for cell */
X};
X
X
X/*
X * The following structure holds all data necessary for processing
X * characters from some source.
X */
Xstruct	input	{
X	int	(*i_getchar)();		/* routine to read next character */
X	int	(*i_term)();		/* routine to terminate reading */
X	char	*i_begptr;		/* beginning of command data */
X	char	*i_endptr;		/* end of command data */
X	char	*i_curptr;		/* current character */
X	char	i_type;			/* type of input */
X			/* following data for file reading only */
X	short	i_file;			/* file descriptor */
X	struct	object	*i_obj;		/* object to restore on reentry */
X	long	i_row;			/* row to restore */
X	long	i_col;			/* column to restore */
X	long	i_prow;			/* pointer row to restore */
X	long	i_pcol;			/* pointer column to restore */
X			/* following data for loop or macro reading only */
X	long	i_curval;		/* current iteration value */
X	long	i_endval;		/* ending iteration value */
X	int	i_first;		/* processing new chars */
X	char	i_macro;		/* macro being defined */
X};
X
X
Xstruct	macro	{			/* structure for command macros */
X	char	*m_begptr;		/* beginning of data (NULL if none) */
X	char	*m_endptr;		/* end of data */
X};
X
X
Xstruct	object	*objects;		/* list of active objects */
Xstruct	object	*curobj;		/* currently selected object */
Xstruct	object	*prevobj;		/* previously selected object */
Xstruct	object	*mainobject;		/* the main object */
Xstruct	object	*deleteobject;		/* object last deleted */
Xstruct	object	*backupobject;		/* backup object */
Xstruct	object	*tempobject;		/* temporary object */
Xstruct	object	*freeobjects;		/* list of free objects */
Xstruct	object	*newobjects;		/* top of new object allocation */
Xstruct	object	*endobjects;		/* end of new objects */
X
Xstruct	row	*freerows;		/* list of free row structures */
Xstruct	row	*newrows;		/* top of new row allocation */
Xstruct	row	*endrows;		/* end of new rows */
Xstruct	row	*termrow;		/* terminus row */
Xstruct	row	initrow;		/* row to initialize list */
X
Xstruct	cell	*freecells;		/* list of free cell structures */
Xstruct	cell	*newcells;		/* top of new cell allocation */
Xstruct	cell	*endcells;		/* end of new cells */
Xstruct	cell	*termcell;		/* terminus cell */
Xstruct	cell	initcell;		/* cell to initialize list */
X
Xstruct	input	*curinput;		/* current input being read from */
X
Xint	seecount;			/* number of cells we can see */
Xint	frequency;			/* typeout frequency */
Xint	freqcount;			/* current count */
Xint	genleft;			/* generations left before stopping */
Xshort	rowradius;			/* half of length of screen */
Xshort	colradius;			/* half of width of screen */
Xchar	reserve;			/* reserved object names allowed */
Xchar	dowait;				/* must wait for input */
Xchar	update;				/* status or position needs updating */
Xchar	redraw;				/* whole screen needs updating */
Xchar	interact;			/* still being interactive */
Xchar	stop;				/* user wants to stop current action */
Xchar	mode;				/* mode of movement */
Xchar	gridchar;			/* character to use for grid */
Xchar	*errorstring;			/* error string to type */
Xchar	*userlib;			/* user's life library if any */
Xjmp_buf	ttyjmp;				/* jump buffer */
Xstruct	input	inputs[MAXINPUT];	/* list of input environments */
Xstruct	macro	macros[26];		/* list of macros */
Xchar	stringbuf[FILESIZE];		/* characters for string value */
Xchar	rulestring[20];			/* string describing rules */
X
Xextern	char	rules[];		/* life rules */
X
Xstruct	object	*allocobject();		/* allocate new object */
Xstruct	object	*findobject();		/* find object with certain name */
Xstruct	object	*getobject();		/* get new object with certain name */
Xstruct	row	*allocrow();		/* allocate new row */
Xstruct	row	*findrow();		/* find a row */
Xstruct	row	*getrow();		/* get a new row */
Xstruct	row	*computerow();		/* compute a new row */
Xstruct	cell	*alloccell();		/* allocate new cell */
Xstruct	cell	*findcell();		/* find a cell */
X
Xchar	*readstring();			/* read input */
//E*O*F life.h//
echo done

dbell@daisy.UUCP (David I. Bell) (02/03/85)

#---Cut here and place in it's own directory, then feed to Bourne shell---
# This is a shell archive.  Remove anything before this line, then
# unpack it by saving it in a file and typing "sh file".  (Files
# unpacked will be owned by you and have default permissions.)
# This archive contains:
#   alloc.c (2055 chars)
#   cell.c (3720 chars)
#   cmd.c (14613 chars)
#   cmdl.c (11895 chars)
#   debug.c (1295 chars)
#   gen.c (4492 chars)
#   io.c (16638 chars)
#
echo x - alloc.c
sed -e 's/^X//' > "alloc.c" << '//E*O*F alloc.c//'
X#ifdef SCCS
Xstatic char *sccsid = "@(#)alloc.c	1.5	2/1/85";
Xstatic char *cpyrid = "@(#)Copyright (C) 1985 by D Bell";
X#endif
X
X#include "life.h"
X
X
X/*
X * Allocate a new object structure.  It contains one row element, the termrow.
X */
Xstruct object *
Xallocobject()
X{
X	register struct	object	*obj;
X
X	obj = freeobjects;
X	if (obj)
X		freeobjects = obj->o_next;
X	else {
X		obj = newobjects;
X		if (obj >= endobjects) {	/* allocate new storage */
X			obj = (struct object *)
X				sbrk(sizeof(struct object) * ALLOCOBJ);
X			if ((int)obj == -1) {
X				perror("sbrk");
X				exit(1);
X			}
X			newobjects = obj;
X			endobjects = obj + ALLOCOBJ;
X		}
X		newobjects++;
X	}
X	obj->o_next = NULL;
X	obj->o_firstrow = termrow;
X	obj->o_lastrow = NULL;
X	obj->o_reserved = reserve;
X	obj->o_name[0] = '\0';
X	obj->o_count = 0;
X	obj->o_gen = 0;
X	obj->o_lock = 0;
X	obj->o_currow = 0;
X	obj->o_curcol = 0;
X	obj->o_prow = 0;
X	obj->o_pcol = 0;
X	obj->o_mark = 0;
X	obj->o_autoscale = 0;
X	setscale(obj, 1);
X	return(obj);
X}
X
X
X/*
X * Allocate a new row structure.  It contains one row element, the termcell.
X */
Xstruct row *
Xallocrow()
X{
X	register struct	row	*rp;
X
X	rp = freerows;
X	if (rp)
X		freerows = rp->r_next;
X	else {
X		rp = newrows;
X		if (rp >= endrows) {		/* allocate new storage */
X			rp = (struct row *) sbrk(sizeof(struct row) * ALLOCROW);
X			if ((int)rp == -1) {
X				perror("sbrk");
X				exit(1);
X			}
X			newrows = rp;
X			endrows = rp + ALLOCROW;
X		}
X		newrows++;
X	}
X	rp->r_next = NULL;
X	rp->r_firstcell = termcell;
X	rp->r_lastcell = NULL;
X	rp->r_count = 0;
X	return(rp);
X}
X
X
X/* Allocate a new cell structure */
Xstruct cell *
Xalloccell()
X{
X	register struct	cell	*cp;
X
X	cp = freecells;
X	if (cp) {
X		freecells = cp->c_next;
X		cp->c_next = NULL;
X		cp->c_marks = MARK_ANY;
X		return(cp);
X	}
X	cp = newcells;
X	if (cp >= endcells) {		/* allocate new storage */
X		cp = (struct cell *) sbrk(sizeof(struct cell) * ALLOCCELL);
X		if ((int)cp == -1) {
X			perror("sbrk");
X			exit(1);
X		}
X		newcells = cp;
X		endcells = cp + ALLOCCELL;
X	}
X	newcells++;
X	cp->c_next = NULL;
X	cp->c_marks = MARK_ANY;
X	return(cp);
X}
//E*O*F alloc.c//
echo x - cell.c
sed -e 's/^X//' > "cell.c" << '//E*O*F cell.c//'
X#ifdef SCCS
Xstatic char *sccsid = "@(#)cell.c	1.3	2/1/85";
Xstatic char *cpyrid = "@(#)Copyright (C) 1985 by D Bell";
X#endif
X
X#include "life.h"
X
X
X/*
X * Return a row structure for a given row number, returning NULL if not there.
X */
Xstruct row *
Xfindrow(obj, row)
X	struct	object	*obj;
X	register int	row;
X{
X	register struct	row	*rp;
X
X	for (rp = obj->o_firstrow; (row > rp->r_row); rp = rp->r_next) ;
X	if (rp->r_row != row) return(NULL);
X	return(rp);
X}
X
X
X/*
X * Return a row structure for a given row number, creating it if needed.
X */
Xstruct row *
Xgetrow(obj, row)
X	register struct	object	*obj;
X	register int	row;
X{
X	register struct	row	*rp;		/* current row */
X	register struct	row	*nrp;		/* next row */
X	register struct	row	*prp;		/* previous row */
X
X	rp = obj->o_firstrow;
X	if (row < rp->r_row) {			/* at front */
X		nrp = allocrow();
X		nrp->r_row = row;
X		nrp->r_next = obj->o_firstrow;
X		obj->o_firstrow = nrp;
X		if (nrp->r_next == termrow) obj->o_lastrow = nrp;
X		return(nrp);
X	}
X	if (row >= obj->o_lastrow->r_row) rp = obj->o_lastrow;
X	while (row > rp->r_row) {
X		prp = rp;
X		rp = rp->r_next;
X	}
X	if (row == rp->r_row) {
X		return(rp);
X	}
X	nrp = allocrow();
X	nrp->r_row = row;
X	nrp->r_next = rp;
X	prp->r_next = nrp;
X	if (nrp->r_next == termrow) obj->o_lastrow = nrp;
X	return(nrp);
X}
X
X
X/*
X * Find a cell given its coordinates, returning NULL if not found.
X */
Xstruct cell *
Xfindcell(obj, row, col)
X	register struct object	*obj;
X	register int	col;
X{
X	register struct	row	*rp;
X	register struct	cell	*cp;
X
X	rp = findrow(obj, row);
X	if (rp == NULL) return(NULL);
X	for (cp = rp->r_firstcell; col > cp->c_col; cp = cp->c_next) ;
X	if (col != cp->c_col) return(NULL);
X	return(cp);
X}
X
X
X/*
X * Create a cell at a given row and column.  Returns nonzero if the cell
X * already existed.  If the cell is new, it is marked with the current
X * mark value of the object.
X */
Xaddcell(obj, row, col)
X	struct	object	*obj;
X	register int	col;
X{
X	register struct	row	*rp;
X	register struct	cell	*cp;		/* current cell */
X	register struct	cell	*ncp;		/* next cell */
X	register struct	cell	*pcp;		/* previous cell */
X
X	rp = getrow(obj, row);
X	cp = rp->r_firstcell;
X	if ((cp != termcell) && (col >= rp->r_lastcell->c_col)) {
X		pcp = rp->r_lastcell;			/* at end */
X		if (col == pcp->c_col) return(1);
X		ncp = alloccell();
X		ncp->c_col = col;
X		ncp->c_marks |= obj->o_mark;
X		ncp->c_next = termcell;
X		pcp->c_next = ncp;
X		rp->r_lastcell = ncp;
X		rp->r_count++;
X		obj->o_count++;
X		return(0);
X	}
X	if (col < cp->c_col) {
X		ncp = alloccell();			/* at front */
X		ncp->c_col = col;
X		ncp->c_marks |= obj->o_mark;
X		ncp->c_next = cp;
X		rp->r_firstcell = ncp;
X		if (cp == termcell) rp->r_lastcell = ncp;
X		rp->r_count++;
X		obj->o_count++;
X		return(0);
X	}
X	while (col > cp->c_col) {			/* in middle */
X		pcp = cp;
X		cp = pcp->c_next;
X	}
X	if (col == cp->c_col) {
X		return(1);
X	}
X	ncp = alloccell();
X	ncp->c_col = col;
X	ncp->c_marks |= obj->o_mark;
X	ncp->c_next = cp;
X	pcp->c_next = ncp;
X	if (cp == termcell) rp->r_lastcell = ncp;
X	rp->r_count++;
X	obj->o_count++;
X	return(0);
X}
X
X
X/*
X * Delete a cell at a given coordinate.  Returns nonzero if it did not exist.
X */
Xdelcell(obj, row, col)
X	register struct	object	*obj;
X	register int	col;
X{
X	register struct	row	*rp;
X	register struct	cell	*pcp;		/* previous cell */
X	register struct	cell	*cp;		/* current cell */
X
X	rp = findrow(obj, row);
X	if (rp == NULL) return(1);
X	pcp = NULL;
X	cp = rp->r_firstcell;
X	while (col > cp->c_col) {
X		pcp = cp;
X		cp = cp->c_next;
X	}
X	if (col != cp->c_col) return(1);
X	if (pcp)
X		pcp->c_next = cp->c_next;
X	else
X		rp->r_firstcell = cp->c_next;
X	if (cp->c_next == termcell) rp->r_lastcell = pcp;
X	cp->c_next = freecells;
X	freecells = cp;
X	rp->r_count--;
X	obj->o_count--;
X	return(0);
X}
//E*O*F cell.c//
echo x - cmd.c
sed -e 's/^X//' > "cmd.c" << '//E*O*F cmd.c//'
X#ifdef SCCS
Xstatic char *sccsid = "@(#)cmd.c	1.21	2/2/85";
Xstatic char *cpyrid = "@(#)Copyright (C) 1985 by D Bell";
X#endif
X
X#include "life.h"
X
X
X/*
X * Read commands if available, and execute them.  Since we call scanchar for
X * our characters, the code after the setjmp can be reentered many times in
X * order to finish any command.  This allows commands to be typed without
X * stopping the computation of generations of an object, and allows editing
X * of partially completed commands.
X */
Xdocommand()
X{
X	register int	defarg;		/* first argument defaulted to one */
X	register int	ch;		/* character read */
X	register struct	object	*obj;	/* object being manipulated */
X	int	arg1;			/* first command argument */
X	int	arg2;			/* second command argument */
X	int	got1;			/* got first argument flag */
X	int	got2;			/* got second argument flag */
X	char	*saveloopptr;		/* crock for loop definitions */
X
X	switch (setjmp(ttyjmp)) {
X		case SCAN_EDIT:		/* command edited before completion */
X			curinput->i_endptr = saveloopptr;
X			break;
X		case SCAN_EOF:		/* not yet enough chars for a command */
X			curinput->i_endptr = saveloopptr;
X			return;
X		case SCAN_ABORT:	/* normal command completion */
X		default:		/* normal entry point */
X			saveloopptr = curinput->i_endptr;
X			break;
X	}
X	if (stop) error("Command aborted");
X	obj = curobj;
X	cmark = 0;
X	arg2 = 0;
X	got2 = 0;
X	ch = readvalue(&arg1, &got1);
X	if (ch == ',') ch = readvalue(&arg2, &got2);
X	defarg = 1;
X	if (got1) defarg = arg1;
X	switch (ch) {
X#ifdef DEBUG
X		case '\005':		/* ^E - show debugging info */
X			dumpdata();
X			break;
X#endif DEBUG
X		case '\004':		/* ^D - end terminal input */
X			curinput->i_term(curinput);
X			break;
X		case '\014':		/* refresh screen */
X			dpyredraw();
X			redraw = 1;
X			break;
X		case '\n':		/* move to next "line" */
X			crow += defarg;
X			ccol = pcol;
X			update = 1;
X			break;
X		case '\t':		/* move to next tab stop */
X			while ((++ccol - pcol) % 8) ;
X			update = 1;
X			break;
X		case ESC:		/* execute a macro command */
X			ch = scanchar();
X			if (ch == ESC) break;	/* ignore double escape */
X			backup();
X			if (setmacro(arg1, arg2, ch)) error("Undefined macro");
X			update = 1;
X			break;
X		case ' ':		/* move to right */
X		case '.':
X			ccol += defarg;
X			update = 1;
X			break;
X		case ':':		/* execute line style command */
X		case ';':
X			dolinecommand(arg1, arg2, got1, got2);
X			break;
X		case '<':		/* begin loop or macro definition */
X			if (got1 || got2) {
X				if (got2) setloop(defarg, arg2, NULL);
X				else setloop(1, defarg, NULL);
X				update = 1;
X				break;
X			}
X			ch = scanchar();	/* defining macro */
X			if ((ch < 'a') || (ch > 'z')) {
X				error("Bad macro character");
X			}
X			setloop(1, 1, ch);
X			update = 1;
X			break;
X		case '>':		/* end loop */
X			endloop();
X			break;
X		case '+':		/* increment single char variable */
X			ch = scanchar();
X			if (ch == '$') ch = scanchar();
X			setvariable1(ch, getvariable1(ch) + defarg);
X			break;
X		case 'b':		/* move lower left with action */
X			domove(defarg, -defarg);
X			break;
X		case 'B':		/* shift to lower left */
X			doshift(defarg, -defarg);
X			break;
X		case 'c':		/* pick cell as current location */
X			crow = prow + arg1;
X			ccol = pcol + arg2;
X			update = 1;
X			break;
X		case 'd':		/* delete selection */
X			doselect(ch);
X			backup();
X			movemarkedobject(obj, deleteobject, MARK_CMD);
X			redraw = 1;
X			break;
X		case 'f':		/* flip selection */
X			doselect(ch);
X			cmark = MARK_USR;
X			flipmarkedobject(obj, MARK_CMD);
X			redraw = 1;
X			break;
X		case 'g':		/* compute generations */
X			if (obj->o_lock) error("Object locked");
X			if (genleft <= 0) backup();
X			genleft = (genleft ? arg1 : defarg);
X			freqcount = frequency;
X			update = 1;
X			break;
X		case 'G':		/* compute infinite generations */
X			if (obj->o_lock) error("Object locked");
X			if (genleft <= 0) backup();
X			genleft = INFINITY;
X			freqcount = frequency;
X			update = 1;
X			break;
X		case 'h':		/* move left with action */
X			domove(0, -defarg);
X			break;
X		case 'H':		/* shift left lots */
X			doshift(0, -defarg);
X			break;
X		case 'i':		/* toggle insert mode */
X			checkrun();
X			if (mode == M_MOVE) mode = M_INSERT;
X			else if (mode == M_INSERT) mode = M_DELETE;
X			else mode = M_MOVE;
X			update = 1;
X			break;
X		case 'j':		/* move down with action */
X			domove(defarg, 0);
X			break;
X		case 'J':		/* shift down lots */
X			doshift(defarg, 0);
X			break;
X		case 'k':		/* move up with action */
X			domove(-defarg, 0);
X			break;
X		case 'K':		/* shift up lots */
X			doshift(-defarg, 0);
X			break;
X		case 'l':		/* move right with action */
X			domove(0, defarg);
X			break;
X		case 'L':		/* shift right lots */
X			doshift(0, defarg);
X			break;
X		case 'm':		/* mark current object */
X			doselect(ch);
X			copymarks(obj, MARK_CMD, MARK_USR);
X			redraw = 1;
X			break;
X		case 'M':		/* remove all marks */
X			checkrun();
X			clearmarks(obj, MARK_SEE);
X			redraw = 1;
X			break;
X		case 'n':		/* move lower right with action */
X			domove(defarg, defarg);
X			break;
X		case 'N':		/* shift down and right */
X			doshift(defarg, defarg);
X			break;
X		case 'o':		/* insert new cells */
X		case 'O':
X			checkrun();
X			backup();
X			while ((stop == 0) && (defarg-- > 0)) {
X				addcell(obj, crow, ccol++);
X			}
X			redraw = 1;
X			break;
X		case 'p':		/* place deleted object */
X			checkrun();
X			backup();
X			cmark = MARK_USR;
X			addobject(deleteobject, obj, RELATIVE);
X			redraw = 1;
X			break;
X		case 'r':		/* rotate selection */
X			doselect(ch);
X			cmark = MARK_USR;
X			rotatemarkedobject(obj, MARK_CMD);
X			redraw = 1;
X			break;
X		case 's':		/* set scale factor and center object */
X			obj->o_autoscale = 0;
X			if (got1 == 0) arg1 = obj->o_scale;
X			setscale(obj, arg1);
X			break;
X		case 'S':		/* perform auto-scaling */
X			if (got1 == 0) arg1 = obj->o_scale;
X			setscale(obj, arg1);
X			obj->o_autoscale = 1;
X			redraw = 1;
X			break;
X		case 't':		/* toggle current cell */
X			checkrun();
X			backup();
X			if (delcell(obj, crow, ccol))
X				addcell(obj, crow, ccol);
X			redraw = 1;
X			break;
X		case 'u':		/* move upper right with action */
X			domove(-defarg, defarg);
X			break;
X		case 'U':		/* shift to upper right */
X			doshift(-defarg, defarg);
X			break;
X		case 'x':		/* kill current cells */
X			checkrun();
X			backup();
X			while ((stop == 0) && (defarg-- > 0)) {
X				delcell(obj, crow, ccol++);
X			}
X			redraw = 1;
X			break;
X		case 'y':		/* move upper left with action */
X			domove(-defarg, -defarg);
X			break;
X		case 'Y':		/* shift to upper left */
X			doshift(-defarg, -defarg);
X			break;
X		case 'z':		/* clear or set generation number */
X			checkrun();
X			obj->o_gen = arg1;
X			obj->o_born = 0;
X			obj->o_died = 0;
X			update = 1;
X			break;
X		case '/':		/* search for next object */
X			{
X			int minr, maxr, minc, maxc;
X
X			checkrun();
X			if (searchobject(obj, defarg, 0)) error("Empty object");
X			clearmarks(obj, MARK_CMD);
X			markobject(obj, crow, ccol, MARK_CMD);
X			markminmax(obj, MARK_CMD, &minr, &maxr, &minc, &maxc);
X			positionview(minr, maxr, minc, maxc);
X			update = 1;
X			}
X			break;
X		case '@':		/* point at current location */
X			prow = crow;
X			pcol = ccol;
X			break;
X		case '!':		/* comment characters */
X		case '#':
X			while (scanchar() != '\n') ;
X			break;
X		default:		/* unknown commands */
X			error("Unknown command");
X	}
X	scanabort();			/* completed command */
X}
X
X
X/*
X * Read a numeric value (if any) to be used as an argument for a command.
X * Pointers to the returned value and returned flag are given.
X * The returned value is zero if no value is read.
X * The returned flag is nonzero if a value was read.
X * Return value is the first non-argument character read.
X */
Xreadvalue(valueptr, flagptr)
X	register int	*valueptr;	/* pointer to returned value */
X	register int	*flagptr;	/* pointer to got value flag */
X{
X	register int	ch;		/* character being read */
X	register struct	input	*ip;	/* input structure */
X	int	sign;			/* sign of result */
X
X	*valueptr = 0;
X	*flagptr = 0;
X	sign = 1;
X	ch = scanchar();
X	if (ch == '-') {			/* negative value */
X		sign = -1;
X		ch = scanchar();
X	}
X	if (ch == '$') {			/* get variable value */
X		*valueptr = sign * getvariable1(scanchar());
X		*flagptr = 1;
X		return(scanchar());
X	}
X	if (ch == '(') {			/* get expression */
X		*valueptr = sign * scanexpr();
X		*flagptr = 1;
X		return(scanchar());
X	}
X	while ((ch >= '0') && (ch <= '9')) {	/* get numeric value */
X		*valueptr = (*valueptr * 10) + ch - '0';
X		*flagptr = 1;
X		ch = scanchar();
X	}
X	if (ch == '%') {			/* get loop value */
X		ch = 1;
X		if (*flagptr) ch = *valueptr;
X		if (ch <= 0) error("Bad nest value");
X		ip = curinput + 1;
X		while (ch > 0) {
X			if (--ip < inputs) error("Bad nest value");
X			if (ip->i_type == INP_LOOP) ch--;
X		}
X		*valueptr = ip->i_curval;
X		*flagptr = 1;
X		ch = scanchar();
X	}
X	*valueptr *= sign;
X	return(ch);
X}
X
X
X/*
X * Routine called from above to scan and evaluate a parenthesized expression.
X * This routines knows that one parenthesis has already been read.  Stops
X * reading on the matching parenthesis.
X */
Xscanexpr()
X{
X	register char	*cp;		/* current character */
X	register int	nest;		/* nesting depth */
X	char	buf[100];		/* expression buffer */
X
X	cp = buf;
X	*cp++ = '(';			/* start with parenthesis */
X	nest = 1;
X	while (nest > 0) {
X		if (cp >= &buf[sizeof(buf)-2]) error("expression too long");
X		*cp = scanchar();
X		if (*cp == '(') nest++;
X		if (*cp == ')') nest--;
X		cp++;
X	}
X	*cp = '\0';
X	return(getexpression(buf));
X}
X
X
X/*
X * Select a set of cells to be used for some command.  This involves reading
X * the next character and marking cells based on that character.  Repeating
X * the command character is equivilant to selecting the current object.  On a
X * successful return, exactly those cells specified are marked with MARK_CMD.
X * If no cells are found, an error is generated.
X */
Xdoselect(cmd)
X{
X	register struct	object	*obj;	/* object to examine */
X	register long	minrow, maxrow, mincol, maxcol;	/* range for marks */
X	register struct	cell	*cp;	/* current cell */
X	int	ch;			/* character to select on */
X
X	checkrun();
X	ch = scanchar();
X	if (ch == cmd) ch = 'o';	/* repeated char is connected object */
X	minrow = -INFINITY;
X	maxrow = -minrow;
X	mincol = minrow;
X	maxcol = maxrow;
X	obj = curobj;
X	clearmarks(obj, MARK_CMD);
X	switch (ch) {
X		case 'a':		/* all of object */
X			break;
X		case 'b':		/* below and left of cursor */
X			minrow = crow;
X			maxcol = ccol;
X			break;
X		case 'c':		/* current cell */
X			cp = findcell(obj, crow, ccol);
X			if (cp == NULL) error("No cell at current location");
X			cp->c_marks |= MARK_CMD;
X			return;
X		case 'h':		/* left of cursor */
X			maxcol = ccol;
X			break;
X		case 'j':		/* below cursor */
X			minrow = crow;
X			break;
X		case 'k':		/* above cursor */
X			maxrow = crow;
X			break;
X		case 'l':		/* right of cursor */
X			mincol = ccol;
X			break;
X		case 'm':		/* marked cells */
X			if (copymarks(obj, MARK_USR, MARK_CMD))
X				error("No object marked");
X			return;
X		case 'n':		/* below and right of cursor */
X			minrow = crow;
X			mincol = ccol;
X			break;
X		case 'o':		/* connected object */
X			if (markobject(obj, crow, ccol, MARK_CMD))
X				error("No object at current location");
X			return;
X		case 'p':		/* rectangle to pointer */
X			minrow = crow;
X			maxrow = prow;
X			if (minrow > maxrow) {
X				minrow = prow;
X				maxrow = crow;
X			}
X			mincol = ccol;
X			maxcol = pcol;
X			if (mincol > maxcol) {
X				mincol = pcol;
X				maxcol = ccol;
X			}
X			break;
X		case 'u':		/* above and right of cursor */
X			maxrow = crow;
X			mincol = ccol;
X			break;
X		case 'v':		/* things visible in window */
X			minrow = obj->o_minrow;
X			maxrow = obj->o_maxrow;
X			mincol = obj->o_mincol;
X			maxcol = obj->o_maxcol;
X			break;
X		case 'y':		/* above and left of cursor */
X			maxrow = crow;
X			maxcol = ccol;
X			break;
X		default:		/* unknown */
X			error("Unknown selection command");
X	}
X	if (markregion(obj, MARK_CMD, minrow, maxrow, mincol, maxcol) == 0)
X		error("No cells in region");
X}
X
X
X/*
X * Move the current position by the indicated deltas, performing the
X * current action to the configuration.  The movement is scaled by
X * the current scaling factor.
X */
Xdomove(rowdelta, coldelta)
X	register int	rowdelta;	/* amount to change row by */
X	register int	coldelta;	/* amount to change column by */
X{
X	register int	row1;		/* increment for row */
X	register int	col1;		/* increment for column */
X
X	rowdelta *= curobj->o_scale;
X	coldelta *= curobj->o_scale;
X	if (mode == M_MOVE) {		/* just want to move */
X		crow += rowdelta;
X		ccol += coldelta;
X		update = 1;
X		return;
X	}
X	checkrun();
X	backup();
X	row1 = 0;			/* need to loop for insert or delete */
X	col1 = 0;
X	if (rowdelta > 0) row1 = 1;
X	if (rowdelta < 0) row1 = -1;
X	if (coldelta > 0) col1 = 1;
X	if (coldelta < 0) col1 = -1;
X	rowdelta += crow;
X	coldelta += ccol;
X	while ((stop == 0) && ((crow != rowdelta) || (ccol != coldelta))) {
X		crow += row1;
X		ccol += col1;
X		switch (mode) {
X			case M_INSERT:
X				addcell(curobj, crow, ccol);
X				break;
X			case M_DELETE:
X				delcell(curobj, crow, ccol);
X		}
X	}
X	redraw = 1;
X}
X
X
X/*
X * Shift the window lots in the indicated direction, and also shift the
X * cursor location the same amount so that the cursor location on the
X * screen doesn't change.  "Lots" is 1/4 of the screen width or height.
X * Special case: if both x and y are being shifted, we shift both by the
X * same amount, which is the minimum of the two shifts.
X */
Xdoshift(rowdelta, coldelta)
X	register int	rowdelta;	/* amount to change row by */
X	register int	coldelta;	/* amount to change column by */
X{
X	register struct	object	*obj;	/* current object */
X	register int	rowsign;	/* sign of row */
X	register int	colsign;	/* sign of column */
X
X	obj = curobj;
X	rowdelta *= ((rowradius * obj->o_scale) / 2);
X	coldelta *= ((colradius * obj->o_scale) / 2);
X	if (rowdelta && coldelta) {	/* take minimums of absolute values */
X		rowsign = 1;
X		colsign = 1;
X		if (rowdelta < 0) {
X			rowsign = -1;
X			rowdelta = -rowdelta;
X		}
X		if (coldelta < 0) {
X			colsign = -1;
X			coldelta = -coldelta;
X		}
X		if (rowdelta > coldelta) rowdelta = coldelta;
X		if (coldelta > rowdelta) coldelta = rowdelta;
X		rowdelta *= rowsign;
X		coldelta *= colsign;
X	}
X	obj->o_currow += rowdelta;
X	obj->o_minrow += rowdelta;
X	obj->o_maxrow += rowdelta;
X	obj->o_curcol += coldelta;
X	obj->o_mincol += coldelta;
X	obj->o_maxcol += coldelta;
X	redraw = 1;
X}
X
X
X/*
X * Perform a backup of the current object.  This is only done if reading
X * from the terminal, since it is from the point of view of the user.
X */
Xbackup()
X{
X	if (curinput->i_type == INP_TTY) copyobject(curobj, backupobject);
X}
X
X
X/*
X * Check to see that generations are not being computed before proceeding
X * with the current command.
X */
Xcheckrun()
X{
X	if (genleft > 0) error("Illegal while running");
X}
//E*O*F cmd.c//
echo x - cmdl.c
sed -e 's/^X//' > "cmdl.c" << '//E*O*F cmdl.c//'
X#ifdef SCCS
Xstatic char *sccsid = "@(#)cmdl.c	1.31	2/2/85";
Xstatic char *cpyrid = "@(#)Copyright (C) 1985 by D Bell";
X#endif
X
X#include "life.h"
X#include <sys/ioctl.h>
X
X/* list of line command routines */
Xint	c_create(), c_destroy(), c_edit(), c_dumpmacros(), c_quit();
Xint	c_frequency(), c_listobjects(), c_zero(), c_lock(), c_unlock();
Xint	c_read(), c_write(), c_ttyinput(), c_gridchar(), c_nogrid();
Xint	c_insert(), c_copy(), c_copyselect(), c_move(), c_moveselect();
Xint	c_help(), c_rules(), c_endinputlevel(), c_undo(), c_rename();
Xint	c_set(), c_variables(), c_type(), c_update(), c_wait();
X
X/* flags for line commands */
X#define	F_NONE	0x0		/* no special condition */
X#define	F_NOARG 0x1		/* must not have an argument */
X#define	F_ARG1	0x2		/* must have at least one argument */
X#define	F_NORUN	0x4		/* illegal if running generations */
X#define	F_UPDATE 0x8		/* update status if command completes */
X#define	F_REDRAW 0x10		/* redraw screen if command completes */
X#define	F_ABBR 0x20		/* abbreviation works even if ambiguous */
X
X
X/*
X * Dispatch table for line commands.  Those commands which are ambiguous
X * must be defined so as to be contiguous in the table.  A spaces delimits
X * the command itself from the help string for the command.
X */
Xstruct	cmdtab {
X	char	*c_name;		/* command name */
X	int	(*c_func)();		/* function to call */
X	int	c_flags;		/* flags for command */
X} cmdtab[] = {
X	"copy (current object to) obj", c_copy, F_ARG1|F_NORUN|F_ABBR,
X	"copyselection (to) obj", c_copyselect, F_ARG1|F_NORUN,
X	"create (object named) obj", c_create, F_ARG1|F_NORUN|F_REDRAW,
X	"destroy (object named) obj", c_destroy, F_ARG1|F_NORUN,
X	"dumpmacros (to) file", c_dumpmacros, F_ARG1|F_NORUN,
X	"edit (object named) obj", c_edit, F_NORUN|F_REDRAW|F_ABBR,
X	"endinputlevel", c_endinputlevel, F_NOARG|F_UPDATE,
X	"frequency (of typeout is) expr", c_frequency, F_UPDATE,
X	"gridcharacter (is) char", c_gridchar, F_REDRAW,
X	"help", c_help, F_NONE|F_ABBR,
X	"insert (object from) obj", c_insert, F_ARG1|F_NORUN|F_REDRAW|F_ABBR,
X	"list (all objects)", c_listobjects, F_NORUN|F_ABBR,
X	"lock (current object)", c_lock, F_NOARG|F_NORUN|F_UPDATE,
X	"move (current object to) obj", c_move, F_ARG1|F_NORUN|F_REDRAW|F_ABBR,
X	"moveselection (to) obj",c_moveselect,F_ARG1|F_NORUN|F_REDRAW,
X	"nogrid", c_nogrid, F_NOARG|F_REDRAW,
X	"quit (program)", c_quit, F_NOARG|F_ABBR,
X	"read (commands from) file", c_read, F_ARG1|F_NORUN|F_REDRAW|F_ABBR,
X	"rename (current object to) obj", c_rename, F_ARG1|F_UPDATE,
X	"rules (for life are) born live", c_rules, F_NORUN|F_UPDATE,
X	"set (variable) name (to) expr", c_set, F_ARG1|F_ABBR,
X	"ttyinput", c_ttyinput, F_REDRAW,
X	"type (value of expression) expr", c_type, F_ARG1|F_UPDATE|F_ABBR,
X	"undo (last change)", c_undo, F_NOARG|F_NORUN|F_REDRAW|F_ABBR,
X	"unlock (current object)", c_unlock, F_NOARG|F_NORUN|F_UPDATE,
X	"updateview (to be current)", c_update, F_NOARG,
X	"variables (are listed)", c_variables, F_NOARG|F_REDRAW|F_ABBR,
X	"wait (for computations)", c_wait, F_NOARG,
X	"write (current object to) file", c_write, F_ARG1|F_NORUN|F_ABBR,
X	"zero (current object)", c_zero, F_NOARG|F_NORUN|F_REDRAW|F_ABBR,
X	0					/* ends the table */
X};
X
X
X/*
X * Read and execute a command line style command.  This kind of command echoes,
X * and is terminated by an end of line.  Numeric arguments are available.
X */
Xdolinecommand(arg1, arg2, got1, got2)
X{
X	register char	*name;		/* command name */
X	register char	*args;		/* arguments for command */
X	register struct	cmdtab *cmd;	/* command structure */
X	register int	flag;		/* flags for command */
X
X	name = readstring("command: ");
X	if ((*name == '\0') || (*name == '\n')) return;
X	for (args = name; *args && (*args != ' ') && (*args != '\t'); args++) ;
X	while ((*args == ' ') || (*args == '\t')) *args++ = '\0';
X	for (cmd = cmdtab; ; cmd++) {
X		if (cmd->c_name == NULL) error("Unknown line command");
X		if (abbrev(name, cmd[0].c_name) == 0) continue;
X		if (cmd->c_flags & F_ABBR) break;
X		if (abbrev(name, cmd[1].c_name) == 0) break;
X		if (strcmp(name, cmd[0].c_name) == 0) break;
X		if (cmd[1].c_flags & F_ABBR) continue;
X		error("Ambiguous line command");
X	}
X	flag = cmd->c_flags;
X	if (flag & F_NORUN) checkrun();
X	if ((flag & F_ARG1) && (*args == '\0')) error("Missing argument");
X	if ((flag & F_NOARG) && *args) error("Argument not allowed");
X	cmd->c_func(args, arg1, arg2, got1, got2);
X	if (flag & F_UPDATE) update = 1;
X	if (flag & F_REDRAW) redraw = 1;
X}
X
X
X
X/* Copy the current object into another object */
Xc_copy(cp)
X	char	*cp;			/* destination object name */
X{
X	copyobject(curobj, getobject(cp));
X}
X
X
X/* Copy the current selection into another object */
Xc_copyselect(cp)
X	char	*cp;			/* destination object name */
X{
X	copymarkedobject(curobj, getobject(cp), MARK_USR);
X}
X
X
X/* Edit an object, creating it if necessary */
Xc_create(cp)
X	char	*cp;			/* object to create */
X{
X	setobject(getobject(cp));
X}
X
X
X/* Destroy an existing object */
Xc_destroy(cp)
X	char	*cp;			/* object name */
X{
X	register struct	object	*obj;	/* object to destroy */
X
X	obj = findobject(cp);
X	if (obj == NULL) error("No such object");
X	destroyobject(obj);
X}
X
X
X/* Dump list of macros to file */
Xc_dumpmacros(cp)
X	char	*cp;			/* file name */
X{
X	writemacros(cp);
X}
X
X
X/* Edit an existing object.  A null argument implies the previous object. */
Xc_edit(cp)
X	char	*cp;			/* object name */
X{
X	register struct	object	*obj;	/* object to edit */
X
X	obj = prevobj;
X	if (*cp) obj = findobject(cp);
X	if (obj == NULL) error("No such object");
X	setobject(obj);
X}
X
X
X/* Undo the last change made to the current object */
Xc_undo()
X{
X	moveobject(curobj, tempobject);
X	moveobject(backupobject, curobj);
X	moveobject(tempobject, backupobject);
X}
X
X
X/* End current input level */
Xc_endinputlevel()
X{
X	if (curinput <= inputs) error("Cannot end top level input");
X	curinput->i_term(curinput);
X}
X
X
X/* Update the view even if inside of a loop or macro */
Xc_update()
X{
X	update = 1;
X	updateview();
X}
X
X
X/* Wait until all outstanding generation computations are finished */
Xc_wait()
X{
X	while ((stop == 0) && (genleft > 0)) {
X		dogeneration(curobj);
X		updateview();
X	}
X}
X
X
X/* Set output frequency */
Xc_frequency(cp)
X	register char	*cp;		/* frequency string */
X{
X	register int	freq;		/* new frequency */
X
X	freq = 1;
X	if (*cp) freq = getexpression(cp);
X	if (freq <= 0) error("Illegal value");
X	frequency = freq;
X	freqcount = freq;
X}
X
X
X/* Select the character used for the background of the screen */
Xc_gridchar(cp)
X	register char	*cp;		/* grid character string */
X{
X	if (*cp == '\0') cp = ".";
X	if ((*cp < ' ') || (cp[1] != '\0')) error("Bad grid character");
X	gridchar = *cp;
X}
X
X
X/* Set so we don`t see a grid on the screen */
Xc_nogrid()
X{
X	gridchar = ' ';
X}
X
X
X/* Type list of line commands */
Xc_help()
X{
X	register struct	cmdtab *cmd;	/* command structure */
X	register int	count;
X
X	dpywindow(0, -1, 0, -1);
X	dpystr("\
XThe following table lists all line mode commands.  Unique abbreviations are\n\
Xallowed.  Commands shown with '*' can be abbreviated even when ambiguous.\n");
X	count = 0;
X	for (cmd = cmdtab; cmd->c_name; cmd++) {
X		if ((count++ % 2) == 0) dpychar('\n');
X		dpyprintf("%c %-35s", ((cmd->c_flags & F_ABBR) ? '*' : ' '),
X			cmd->c_name);
X	}
X	dpychar('\n');
X	spacewait();
X}
X
X
X/* Insert another object into this one */
Xc_insert(cp)
X	char	*cp;			/* object name */
X{
X	register struct	object	*obj;	/* object to insert */
X
X	obj = findobject(cp);
X	if (obj == NULL) error("No such object");
X	cmark = MARK_USR;
X	backup();
X	addobject(obj, curobj, RELATIVE);
X	cmark = MARK_ANY;
X}
X
X
X/* Show list of objects */
Xc_listobjects(cp)
X	register char	*cp;		/* option string */
X{
X	int	all;			/* true if want to see all objects */
X
X	all = ((*cp == '-') || (*cp == 'a'));
X	listobjects(all);
X}
X
X
X/* Lock object so it can't be run */
Xc_lock()
X{
X	curobj->o_lock = 1;
X	genleft = 0;
X	freqcount = frequency;
X}
X
X
X/* Unlock object so it can be run */
Xc_unlock()
X{
X	curobj->o_lock = 0;
X}
X
X
X/* Rename the current object to something else */
Xc_rename(cp)
X	register char	*cp;			/* new name */
X{
X	if (curobj->o_reserved) error("Cannot rename reserved object");
X	if (strlen(cp) > MAXNAME) error("Name too long");
X	if (findobject(cp)) error("Name already exists");
X	if (BADNAME(cp)) error("Cannot create reserved name");
X	strcpy(curobj->o_name, cp);
X}
X
X
X/* Move current object into another object */
Xc_move(cp)
X	char	*cp;			/* destination object name */
X{
X	moveobject(curobj, getobject(cp));
X}
X
X
X/* Move current selection into another object */
Xc_moveselect(cp)
X	char	*cp;			/* destination object name */
X{
X	movemarkedobject(curobj, getobject(cp), MARK_USR);
X}
X
X
X/* Set the value of a variable */
Xc_set(cp)
X	register char	*cp;		/* variable name */
X{
X	register char	*exp;		/* expression */
X
X	for (exp = cp; *exp && (*exp != ' ') && (*exp != '='); exp++) ;
X	if (*exp == ' ') {		/* skip spaces */
X		*exp++ = '\0';
X		while (*exp == ' ') exp++;
X	}
X	if (*exp == '\0') {		/* no expression, set to zero */
X		setvariable(cp, 0);
X		return;
X	}
X	if (*exp == '=') *exp++ = '\0';
X	setvariable(cp, getexpression(exp));
X}
X
X
X/* Type the value of an expression */
Xc_type(cp)
X	char	*cp;				/* expression */
X{
X	static	char	buf[20];		/* storage for string */
X
X	sprintf(buf, "%d\n", getexpression(cp));/* store value */
X	errorstring = buf;			/* make it seen */
X}
X
X
X/* Display the values of all the variables */
Xc_variables()
X{
X	listvariables();
X}
X
X
X/* Quit program */
Xc_quit()
X{
X	dpyclose();
X	exit(0);
X}
X
X
X/* Read commands or object from file, defaulting extension if needed */
Xc_read(cp)
X	register char	*cp;		/* file name */
X{
X	backup();
X	if (setfile(cp)) error("Cannot open input file");
X}
X
X
X/* Set new life rules */
Xc_rules(cp)
X	register char	*cp;		/* life rules string */
X{
X	register char	*bp;		/* born string */
X	register char	*lp;		/* live string */
X
X	if (*cp == '\0') cp = "3,23";
X	bp = cp;
X	while ((*cp >= '0') && (*cp <= '8')) cp++;
X	if (*cp == '\0') error("Missing rule string");
X	if ((*cp != ',') && (*cp != ' ') && (*cp != '\t')) {
X		error("Bad born string");
X	}
X	*cp++ = '\0';
X	while ((*cp == ',') || (*cp == ' ') || (*cp == '\t')) cp++;
X	lp = cp;
X	while ((*cp >= '0') && (*cp <= '8')) cp++;
X	if (*cp != '\0') error("Bad live string");
X	for (cp = rules; cp < &rules[18]; cp++) *cp = 0;
X	while (*bp) rules[*bp++ - '0'] = 1;
X	while (*lp) rules[*lp++ - '0' + LIFE] = 1;
X	bp = rulestring;
X	for (cp = rules; cp < &rules[LIFE]; cp++) {
X		if (*cp) *bp++ = '0' + (cp - rules);
X	}
X	*bp++ = ',';
X	for (cp = &rules[LIFE]; cp < &rules[18]; cp++) {
X		if (*cp) *bp++ = ('0' - LIFE) + (cp - rules);
X	}
X	*bp = '\0';
X}
X
X
X/*
X * Read commands from the terminal.  Useful in loops or command files.
X * If the -c argument is given, we don't do it if no input is ready.
X */
Xc_ttyinput(cp)
X	register char	*cp;		/* pointer to argument string */
X{
X	int	count;			/* character count */
X
X	if ((*cp == '-') || (*cp == 'c')) {	/* check for input */
X		count = 0;
X		ioctl(STDIN, FIONREAD, &count);
X		if (count == 0) return;
X	}
X	if (settty()) error("Nesting too deep");
X}
X
X
X/* Write object to file */
Xc_write(cp, arg1, arg2, got1, got2)
X	register char	*cp;		/* pointer to argument string */
X{
X	if (got1 == 0) arg1 = WRITECOLS;
X	writeobject(curobj, cp, arg1?WRITEROWS:0, arg1);
X}
X
X
X/* Zero out current object */
Xc_zero()
X{
X	register struct	object	*obj;	/* current object */
X
X	obj = curobj;
X	if (obj->o_lock) error("Object is locked");
X	backup();
X	zeroobject(obj);
X	obj->o_gen = 0;
X	obj->o_born = 0;
X	obj->o_died = 0;
X	obj->o_currow = 0;
X	obj->o_curcol = 0;
X	obj->o_prow = 0;
X	obj->o_pcol = 0;
X	obj->o_autoscale = 0;
X	setscale(obj, 1);
X	mode = M_MOVE;
X	frequency = 1;
X	freqcount = 1;
X}
X
X
X/*
X * See if one string is an abbreviation of another.  This knows that
X * the second string contains spaces which terminate the comparison.
X * Returns nonzero if first string is an abbreviation.
X */
Xabbrev(str1, str2)
X	register char	*str1, *str2;	/* strings */
X{
X	if ((str1 == NULL) || (str2 == NULL)) return(0);
X	while (*str1) if (*str1++ != *str2++) return(0);
X	return(1);
X}
//E*O*F cmdl.c//
echo x - debug.c
sed -e 's/^X//' > "debug.c" << '//E*O*F debug.c//'
X#ifdef SCCS
Xstatic char *sccsid = "@(#)debug.c	1.2	1/14/85";
Xstatic char *cpyrid = "@(#)Copyright (C) 1985 by D Bell";
X#endif
X
X#ifdef DEBUG
X#include "life.h"
X
X/*
X * Subroutine to dump out all object data structures for debugging.
X */
Xdumpdata()
X{
X	register struct	object	*obj;
X
X	printf("\ntermrow %x termcell %x\n", termrow, termcell);
X	for (obj = objects; obj; obj = obj->o_next) {
X		dumpobj(obj);
X	}
X	printf("done\n");
X}
X
X
X/*
X * Dump out an object.
X */
Xdumpobj(obj)
X	register struct	object	*obj;
X{
X	register struct	row	*rp;
X
X	printf("object %s (%x) firstrow %x lastrow %x\n", 
X		obj->o_name, obj, obj->o_firstrow, obj->o_lastrow);
X	rp = obj->o_firstrow;
X	while (1) {
X		dumprow(rp);
X		if (rp == termrow) break;
X		rp = rp->r_next;
X	}
X}
X
X
X/*
X * Dump out a row
X */
Xdumprow(rp)
X	register struct	row	*rp;
X{
X	register struct	cell	*cp;
X
X	if (rp == termrow) {
X		printf("  termrow\n");
X		return;
X	}
X	printf("  row %d (%x) firstcell %x lastcell %x\n",
X		rp->r_row, rp, rp->r_firstcell, rp->r_lastcell);
X	cp = rp->r_firstcell;
X	while (1) {
X		dumpcell(cp);
X		if (cp == termcell) break;
X		cp = cp->c_next;
X	}
X}
X
X
X/*
X * Dump out a cell
X */
Xdumpcell(cp)
X	register struct	cell	*cp;
X{
X	if (cp == termcell) {
X		printf("    termcell\n");
X		return;
X	}
X	printf("    cell %d (%x)\n", cp->c_col, cp);
X}
X
X#endif DEBUG
//E*O*F debug.c//
echo x - gen.c
sed -e 's/^X//' > "gen.c" << '//E*O*F gen.c//'
X#ifdef SCCS
Xstatic char *sccsid = "@(#)gen.c	1.4	2/2/85";
Xstatic char *cpyrid = "@(#)Copyright (C) 1985 by D Bell";
X#endif
X
X#include "life.h"
X
Xchar	rules[18] = {0,0,0,1,0,0,0,0,0,0,0,1,1,0,0,0,0,0};	/* life rules */
X
X
X/*
X * Compute one full generation of the current configuration.
X * This is done by calling computerow with each triple of rows which
X * contain any live cells, and remembering the result.  When all rows
X * are finished, we change the object.
X */
Xdogeneration(obj)
X	struct	object	*obj;			/* object to compute */
X{
X	register struct	row	*prp;		/* previous row */
X	register struct	row	*crp;		/* current row */
X	register struct	row	*nrp;		/* next row */
X	register struct	row	*newrp;		/* current row of new list */
X	register struct	row	*srp;		/* saved row pointer */
X	int	row;				/* current row number */
X
X	if (genleft <= 0) return;
X	if ((--genleft == 0) || (--freqcount <= 0)) redraw = 1;
X	obj->o_gen++;
X	obj->o_born = 0;
X	obj->o_died = 0;
X	newrp = &initrow;
X	prp = termrow;
X	crp = termrow;
X	nrp = obj->o_firstrow;
X	srp = nrp->r_next;
X	row = nrp->r_row - 1;
X	while (1) {			/* loop over each triple of rows */
X		if (row >= (INFINITY-1)) break;
X		prp = computerow(obj, prp, crp, nrp);
X		if (prp) {		/* have something in current row */
X			newrp->r_next = prp;
X			newrp = prp;
X			newrp->r_row = row;
X			obj->o_count += prp->r_count;
X		}
X		row++;
X		prp = crp;
X		crp = nrp;
X		nrp = srp;
X		if (nrp->r_row == row + 1) {
X			srp = nrp->r_next;
X			continue;
X		}
X		nrp = termrow;
X		if ((prp != termrow) || (crp != termrow)) continue;
X		nrp = srp;
X		srp = nrp->r_next;
X		row = nrp->r_row - 1;
X	}
X	zeroobject(obj);
X	if (newrp == &initrow) {			/* died off */
X		genleft = 0;
X		redraw = 1;
X		return;
X	}
X	obj->o_firstrow = initrow.r_next;
X	newrp->r_next = termrow;
X	obj->o_lastrow = newrp;
X	if ((obj->o_born == 0) && (obj->o_died == 0)) {	/* no change */
X		genleft = 0;
X		redraw = 1;
X	}
X}
X
X
X/*
X * Compute the result of three adjacent rows, and return a row structure
X * containing the new middle row, or NULL if no live cells are produced.
X * When determining if a cell is dead or alive, each live neighbor counts
X * as a 1, but the current cell counts as 9 when alive.  Indexing the rules
X * table with the sum then automatically produces the correct result.
X */
Xstruct row *
Xcomputerow(obj, prevrow, currow, nextrow)
X	struct	object	*obj;
X	struct	row	*prevrow, *currow, *nextrow;
X{
X	register struct	cell	*pcp;	/* head of previous row of cells */
X	register struct	cell	*ccp;	/* head of current row of cells */
X	register struct	cell	*ncp;	/* head of next row of cells */
X	register struct	cell	*tcp;	/* temporary cell */
X	register struct	cell	*newcp;	/* new row of cells */
X	register int	i;		/* sum of live cells and other uses */
X	struct	row	*rp;		/* new row pointer */
X	int	col;			/* current column being examined */
X	int	colp1;			/* one more than column */
X	int	count;			/* live cells */
X
X	pcp = prevrow->r_firstcell;
X	ccp = currow->r_firstcell;
X	ncp = nextrow->r_firstcell;
X	newcp = &initcell;
X	count = 0;
X	col = -INFINITY;
X	while (1) {			/* loop over cells of all 3 rows */
X		/*
X		 * Find next column where a cell exists on any row
X		 */
X		i = col - 1;
X		while (i > pcp->c_col) pcp = pcp->c_next;
X		while (i > ccp->c_col) ccp = ccp->c_next;
X		while (i > ncp->c_col) ncp = ncp->c_next;
X		i = ccp->c_col;
X		if (pcp->c_col < i) i = pcp->c_col;
X		if (ncp->c_col < i) i = ncp->c_col;
X		if (i == INFINITY) break;
X		i--;
X		if (col < i) col = i;
X		i = 0;
X		colp1 = col + 1;
X		if (pcp->c_col <= colp1) {	/* add cells in previous row */
X			i++;
X			tcp = pcp->c_next;
X			i += (tcp->c_col <= colp1);
X			tcp = tcp->c_next;
X			i += (tcp->c_col <= colp1);
X		}
X		if (ccp->c_col <= colp1) {	/* add cells on our row */
X			i++;
X			tcp = ccp->c_next;
X			if ((ccp->c_col == col) || (tcp->c_col == col)) {
X				i += (LIFE - 1);
X			}
X			i += (tcp->c_col <= colp1);
X			tcp = tcp->c_next;
X			i += (tcp->c_col <= colp1);
X		}
X		if (ncp->c_col <= colp1) {	/* add cells in next row */
X			i++;
X			tcp = ncp->c_next;
X			i += (tcp->c_col <= colp1);
X			tcp = tcp->c_next;
X			i += (tcp->c_col <= colp1);
X		}
X		if (rules[i]) {			/* cell is alive */
X			obj->o_born += (i < LIFE);
X			tcp = alloccell();
X			tcp->c_col = col;
X			newcp->c_next = tcp;
X			newcp = tcp;
X			count++;
X		} else				/* cell is dead */
X			obj->o_died += (i >= LIFE);
X		col++;
X	}
X	if (newcp == &initcell) return(NULL);
X	newcp->c_next = termcell;
X	rp = allocrow();
X	rp->r_firstcell = initcell.c_next;
X	rp->r_lastcell = newcp;
X	rp->r_count = count;
X	return(rp);
X}
//E*O*F gen.c//
echo x - io.c
sed -e 's/^X//' > "io.c" << '//E*O*F io.c//'
X#ifdef SCCS
Xstatic char *sccsid = "@(#)io.c	1.10	2/1/85";
Xstatic char *cpyrid = "@(#)Copyright (C) 1985 by D Bell";
X#endif
X
X#include "life.h"
X#include <errno.h>
X#include <sys/ioctl.h>
X
Xextern	int	errno;			/* error return value */
X
Xint	tty_char(), tty_term();		/* terminal routines */
Xint	file_char(), file_term();	/* file routines */
Xint	loop_char(), loop_term();	/* loop routines */
Xint	macro_char(), macro_term();	/* macro routines */
Xint	getdpychar();			/* for dpyread to call */
X
X
X/*
X * Read the next character from the appropriate input source.
X * Negative answer from source indicates end of file.
X * This routine is usually called indirectly by scanchar.
X */
Xreadchar()
X{
X	register struct	input	*ip;	/* input structure being used */
X	register int	ch;		/* current character */
X
X	while (1) {			/* continue until not end of file */
X		ip = curinput;
X		ch = ip->i_getchar(ip);	/* read next character */
X		if (ch >= 0) break;
X		ip->i_term(ip);		/* end of file, clean up */
X	}
X	return(ch);
X}
X
X
X/*
X * Determine if the next input character to be read is from the terminal.
X * Returns nonzero if so.
X */
Xttyisinput()
X{
X	register struct	input	*ip;	/* input structure being checked */
X
X	ip = curinput;
X	while ((ip->i_type == INP_LOOP) && (ip->i_first)) ip--;
X	return(ip->i_type == INP_TTY);
X}
X
X
X/*
X * Setup to read commands from the terminal.  The lowest level of input
X * never quits, and is unusual in that it doesn't usually block waiting for
X * input.  Returns nonzero if failed.
X */
Xsettty()
X{
X	register struct	input	*ip;		/* current input */
X
X	ip = curinput + 1;		/* allocate next structure */
X	if (ip >= &inputs[MAXINPUT]) {
X		return(1);
X	}
X	ip->i_getchar = tty_char;	/* set up for I/O */
X	ip->i_term = tty_term;
X	ip->i_type = INP_TTY;
X	update = 1;
X	curinput = ip;
X	return(0);
X}
X
X
X/*
X * Read next character from the terminal if it is ready.  If nothing is
X * going on we will wait for it anyway, to prevent excessive runtimes.
X * We set the interactive flag to indicate we are talking to user.
X */
Xtty_char()
X{
X	long	n;			/* char count */
X	int	ch;			/* char to return */
X
X	interact = 1;
X	if ((dowait == 0) && (redraw || update || (genleft > 0))) {
X		if ((ioctl(STDIN, FIONREAD, &n) == 0) && (n <= 0)) {
X			scaneof();		/* no char available now */
X		}
X	}
X	do {
X		if (stop) return('\0');		/* stop will be seen later */
X		errno = 0;
X		n = read(STDIN, &ch, 1);	/* read one char */
X	} while ((n < 0) && (errno == EINTR));
X	if (n <= 0) {
X		return(-1);			/* error or end of file */
X	}
X	if (errorstring) {			/* disable error message */
X		errorstring = NULL;
X		update = 1;
X	}
X	return(ch &= 0x7f);
X}
X
X
X/*
X * Terminate reading from the terminal.  If we are reading from the lowest
X * level of terminal input, this is a no-op.
X */
Xtty_term(ip)
X	register struct	input	*ip;	/* input structure */
X{
X	if (ip > inputs) ip--;
X	curinput = ip;
X	update = 1;
X}
X
X
X/*
X * Setup to read commands from a given file name.   When the file is complete,
X * some parameters (like the current cursor position) will be restored to their
X * original value.  Returns nonzero if cannot set up to read the file.
X */
Xsetfile(name)
X	register char	*name;		/* file name to read from */
X{
X	register struct	input	*ip;	/* current input structure */
X
X	ip = curinput + 1;			/* use next structure */
X	if (ip >= &inputs[MAXINPUT]) {
X		return(1);
X	}
X	ip->i_file = openlibfile(name, 0);	/* open the file */
X	if (ip->i_file < 0) {
X		return(1);
X	}
X	ip->i_begptr = (char *) malloc(FILESIZE); /* allocate data buffer */
X	if (ip->i_begptr == NULL) {
X		close(ip->i_file);
X		return(1);
X	}
X	ip->i_endptr = ip->i_begptr;		/* set up rest of io */
X	ip->i_curptr = ip->i_begptr;
X	ip->i_getchar = file_char;
X	ip->i_term = file_term;
X	ip->i_type = INP_FILE;
X	ip->i_obj = curobj;			/* save current state */
X	ip->i_row = crow;
X	ip->i_col = ccol;
X	ip->i_prow = prow;
X	ip->i_pcol = pcol;
X	prow = crow;
X	pcol = ccol;
X	update = 1;
X	curinput = ip;				/* this is now current input */
X	return(0);
X}
X
X
X/*
X * Open a life library file, trying various transformed names if necessary.
X * The order of the transformations which are tried is:
X *   1.	Name exactly as given.
X *   2.	Name with ".l" appended to it.
X *   3.	Name in the user's library directory.
X *   4.	Name with ".l" appended to it in the user's library.
X *   5  Name in the system's library directory.
X *   6.	Name with ".l" appended to it in the system library.
X * Returns the file descriptor if the open is successful, or -1 if all the
X * open attempts failed.
X */
Xopenlibfile(name, mode)
X	register char	*name;		/* original file name */
X	register int	mode;		/* desired open mode */
X{
X	register int	fd;		/* file descriptor */
X	char	buf[2000];		/* transformed names */
X
X	fd = open(name, mode);			/* try name straight */
X	if (fd >= 0) return(fd);
X	sprintf(buf, "%s.l", name);		/* try name with .l appended */
X	fd = open(buf, mode);
X	if (fd >= 0) return(fd);
X	if (*name == '/') return(-1);		/* quit now if absolute name */
X	if (userlib) {				/* look in user's lib area */
X		sprintf(buf, "%s/%s", userlib, name);
X		fd = open(buf, mode);
X		if (fd >= 0) return(fd);
X		strcat(buf, ".l");		/* try with .l */
X		fd = open(buf, mode);
X		if (fd >= 0) return(fd);
X	}
X	sprintf(buf, "%s/%s", LIFELIB, name);	/* look in general lib area */
X	fd = open(buf, mode);
X	if (fd >= 0) return(fd);
X	strcat(buf, ".l");			/* last try with .l */
X	return(open(buf, mode));
X}
X
X
X/*
X * Here to read next character from a file.  Called by readchar when
X * input source is a file.
X */
Xfile_char(ip)
X	register struct	input	*ip;		/* input structure */
X{
X	if (ip->i_curptr >= ip->i_endptr) {	/* get next chunk of file */
X		ip->i_curptr = ip->i_begptr;
X		ip->i_endptr = ip->i_begptr;
X		ip->i_endptr += read(ip->i_file, ip->i_begptr, FILESIZE);
X		if (ip->i_endptr <= ip->i_begptr) {
X			return(-1);		/* end of file */
X		}
X	}
X	return(*ip->i_curptr++ & 0x7f);
X}
X
X
X/*
X * Here on end of file or error to close the input file and restore some
X * of the previous state such as the cursor location.
X */
Xfile_term(ip)
X	register struct	input	*ip;	/* input structure */
X{
X	close(ip->i_file);
X	free(ip->i_begptr);
X	curobj = ip->i_obj;
X	crow = ip->i_row;
X	ccol = ip->i_col;
X	prow = ip->i_prow;
X	pcol = ip->i_pcol;
X	update = 1;
X	curinput = (ip - 1);
X}
X
X
X/*
X * Setup for execution of a loop.  This remembers the initial and final
X * loop values, and sets up to remember commands as they are given.
X * This is also used for defining a macro command.  The given character
X * will be assigned the string defined by the loop.
X * Returns nonzero if failed.
X */
Xsetloop(begval, endval, ch)
X{
X	register struct	input	*ip;		/* input structure */
X
X	ip = curinput + 1;		/* allocate next structure */
X	if (ip >= &inputs[MAXINPUT]) {
X		return(1);
X	}
X	ip->i_begptr = (char *) malloc(LOOPSIZE);	/* allocate buffer */
X	if (ip->i_begptr == NULL) {
X		return(1);
X	}
X	ip->i_endptr = ip->i_begptr + LOOPSIZE;		/* set up for I/O */
X	ip->i_curptr = ip->i_begptr;
X	ip->i_first = 1;
X	ip->i_getchar = loop_char;
X	ip->i_term = loop_term;
X	ip->i_type = INP_LOOP;
X	ip->i_curval = begval;
X	ip->i_endval = endval;
X	ip->i_macro = ch;
X	update = 1;
X	curinput = ip;
X	return(0);
X}
X
X
X/*
X * End the range of the currently defined loop.  At this point, all of
X * the characters of the loop have been read in and saved, and we can
X * just proceed to iterate over them.  The next read will find out that
X * the first iteration of the loop is over.
X */
Xendloop()
X{
X	register struct	input	*ip;	/* current input */
X
X	ip = curinput;
X	if (ip->i_type != INP_LOOP) error("Loop not being defined");
X	ip->i_endptr = ip->i_curptr - 1;	/* end before loop term cmd */
X	ip->i_first = 0;
X}
X
X
X/*
X * Read one character from a loop buffer.  If at the end of the buffer, the
X * pointer is reset so that the buffer is reread.  When enough iterations
X * have been processed, we are done.  A special case exists the first time
X * through the loop at the first nesting level, in that we don't yet have
X * the characters necessary for the loop, and so we have to read them by
X * ourself.
X */
Xloop_char(ip)
X	register struct	input	*ip;		/* input structure */
X{
X	register int	ch;
X
X	if (ip->i_first) {			/* collecting input chars */
X		if (ip->i_curptr >= ip->i_endptr) error("Loop too long");
X		ch = ip[-1].i_getchar(ip - 1);	/* char from previous level */
X		if (ch < 0) error("End of file in loop");
X		*ip->i_curptr++ = ch;
X		return(ch);
X	}
X	if (ip->i_curptr >= ip->i_endptr) {	/* done with one iteration */
X		if (ip->i_curval == ip->i_endval) {
X			return(-1);		/* end of file */
X		}
X		ip->i_curval += ((ip->i_curval < ip->i_endval) ? 1 : -1);
X		ip->i_curptr = ip->i_begptr;
X	}
X	return(*ip->i_curptr++);
X}
X
X
X/*
X * Terminate reading from a loop buffer.  If this was the definition of
X * a macro character, remember it for later.
X */
Xloop_term(ip)
X	struct	input	*ip;		/* input structure */
X{
X	register struct	macro	*mp;	/* macro being defined */
X
X	mp = &macros[ip->i_macro - 'a'];
X	if ((mp >= macros) && (mp < &macros[26])) {
X		if (mp->m_begptr) free(mp->m_begptr);
X		mp->m_begptr = ip->i_begptr;
X		mp->m_endptr = ip->i_endptr;
X	} else
X		free(ip->i_begptr);		/* or free buffer */
X	update = 1;
X	curinput = (ip - 1);
X}
X
X
X/* Set up to read a defined macro command.  Returns nonzero if failed. */
Xsetmacro(arg1, arg2, ch)
X{
X	register struct	input	*ip;	/* current input */
X	register struct	macro	*mp;	/* macro command */
X
X	mp = &macros[ch - 'a'];		/* verify macro character */
X	if ((mp < macros) || (mp >= &macros[26]) || (mp->m_begptr == NULL)) {
X		return(1);
X	}
X	ip = curinput + 1;		/* use next input structure */
X	if (ip >= &inputs[MAXINPUT]) {
X		return(1);
X	}
X	ip->i_getchar = macro_char;	/* set up for I/O */
X	ip->i_term = macro_term;
X	ip->i_begptr = mp->m_begptr;
X	ip->i_curptr = mp->m_begptr;
X	ip->i_endptr = mp->m_endptr;
X	ip->i_type = INP_MACRO;
X	ip->i_macro = ch;
X	ip->i_curval = arg1;
X	ip->i_endval = arg2;
X	update = 1;
X	curinput = ip;
X	return(0);
X}
X
X
X/* Here to read next character from macro definition. */
Xmacro_char(ip)
X	register struct	input	*ip;		/* input structure */
X{
X	if (ip->i_curptr >= ip->i_endptr) {
X		return(-1);		/* end of file */
X	}
X	return(*ip->i_curptr++);
X}
X
X
X/* Here to terminate reading from a macro definition. */
Xmacro_term(ip)
X	struct	input	*ip;			/* input structure */
X{
X	curinput = (ip - 1);
X	update = 1;
X}
X
X
X/*
X * Read a line from the user terminated by a newline (which is removed).
X * Editing of the input line is fully handled.  The prompt string and the
X * input line are only visible if the current input is from the terminal.
X * Returns a pointer to the null-terminated string.
X */
Xchar *
Xreadstring(prompt)
X	register char	*prompt;	/* prompt string */
X{
X	int	i;			/* number of characters read */
X
X	scanreset();				/* no more scan interference */
X	if (prompt == NULL) prompt = "";	/* set prompt if given NULL */
X	if (ttyisinput() == 0) prompt = NULL;	/* no window stuff if not tty */
X	if (prompt) dpywindow(0, 0, 0, -1);	/* show input in top line */
X	dowait = 1;				/* must wait for tty chars */
X	i = dpyread(prompt, getdpychar, stringbuf, FILESIZE);	/* read it */
X	dowait = 0;				/* back to normal */
X	stringbuf[i] = '\0';			/* terminate line */
X	update = 1;
X	return(stringbuf);
X}
X
X
X/*
X * Routine called by dpyread to read the next character of input.
X * We return end of input on the newline character.
X */
Xgetdpychar()
X{
X	register int	ch;		/* character just read */
X
X	ch = readchar();
X	if (stop || (ch == '\n')) return(-1);
X	return(ch);
X}
X
X
X/*
X * Routine called to wait until a space, newline, or escape character
X * is typed.  It is assumed that the screen contains a display which
X * we can append our message to.
X */
Xspacewait()
X{
X	register int	ch;			/* read character */
X
X	scanreset();				/* throw out stored chars */
X	dpystr("\nType <space> to return\n");	/* append our message */
X	dpyclearwindow();
X	dpyhome();
X	dpyupdate();				/* show the result */
X	redraw = 1;
X	dowait = 1;				/* must wait for chars */
X	do {
X		if (stop) break;
X		ch = readchar();
X	} while ((ch != ' ') && (ch != ESC) && (ch != '\n'));
X	dowait = 0;
X}
X
X
X/*
X * Write the current object out to the named file, with the given maximum
X * sizes for "pretty" output.  If the size is exceeded, the output is
X * compressed.  The file as written can be read in as commands which will
X * regenerate the object.
X */
Xwriteobject(obj, name, maxrows, maxcols)
X	struct	object	*obj;		/* object to write */
X	char	*name;			/* filename to use */
X{
X	register FILE	*fd;		/* file structure */
X	register struct	row	*rp;	/* current row structure */
X	register struct	cell	*cp;	/* current cell */
X	register int	row;		/* current row value */
X	register int	col;		/* current column value */
X	struct	row	*trp;		/* temporary row structure */
X	int	minrow;			/* minimum row of object */
X	int	maxrow;			/* maximum row of object */
X	int	mincol;			/* minimum column of object */
X	int	maxcol;			/* maximum column of object */
X	int	curmin;			/* current minimum column */
X	int	curmax;			/* current maximum column */
X	int	testmin;		/* test minimum column of rows */
X	int	testmax;		/* test maximum column of rows */
X
X	minmax(obj, &minrow, &maxrow, &mincol, &maxcol);
X	if (minrow > maxrow) error("Null object");	/* nothing to write */
X	fd = fopen(name, "w");
X	if (fd == NULL) error("Cannot open output file");
X	fprintf(fd, "! \"%s\" (cells %d length %d width %d generation %d)\n",
X		obj->o_name, obj->o_count, maxrow - minrow + 1,
X		maxcol - mincol + 1, obj->o_gen);
X	if (obj->o_currow > minrow) fprintf(fd, "%dk", obj->o_currow - minrow);
X	if (obj->o_currow < minrow) fprintf(fd, "%dj", minrow - obj->o_currow);
X	if (obj->o_curcol > mincol) fprintf(fd, "%dh", obj->o_curcol - mincol);
X	if (obj->o_curcol < mincol) fprintf(fd, "%dl", mincol - obj->o_curcol);
X	fprintf(fd, "@!\n");
X	curmin = INFINITY;
X	curmax = -INFINITY;
X	rp = obj->o_firstrow;
X	row = minrow;
X	for (; rp != termrow; rp = rp->r_next) {
X		/*
X		 * See if user wants to stop.
X		 */
X		if (stop) {
X			fclose(fd);
X			return;
X		}
X		/*
X		 * Skip down to the next row with something in it.
X		 */
X		if (rp->r_firstcell == termcell) continue;
X		if (rp->r_row > (row + maxrows)) {	/* skip to right row */
X			fprintf(fd, "%d\n", rp->r_row - row);
X			row = rp->r_row;
X		}
X		while (rp->r_row > row) {
X			fputs(".\n", fd);
X			row++;
X		}
X		/*
X		 * Output the current row, compressing if it is too wide.
X		 */
X		col = mincol;
X		cp = rp->r_firstcell;
X		if ((cp->c_col + maxcols) < rp->r_lastcell->c_col) {
X			while (cp != termcell) {
X				/*
X				 * Write all adjacent blanks.
X				 */
X				if (cp->c_col > col + 2) {
X					fprintf(fd, "%d.", cp->c_col - col);
X					col = cp->c_col;
X				}
X				while (cp->c_col > col) {
X					fputc('.', fd);
X					col++;
X				}
X				/*
X				 * Write all adjacent cells.
X				 */
X				while ((cp->c_col + 1) == cp->c_next->c_col) {
X					cp = cp->c_next;
X				}
X				if (cp->c_col >= col + 2) {
X					fprintf(fd, "%dO", cp->c_col - col + 1);
X					col = cp->c_col + 1;
X				}
X				while (col <= cp->c_col) {
X					fputc('O', fd);
X					col++;
X				}
X				cp = cp->c_next;
X			}
X			fputc('\n', fd);
X			row++;
X			curmin = INFINITY;
X			curmax = -INFINITY;
X			continue;
X		}
X		/*
X		 * Here if the row doesn't need compressing.  See if several
X		 * rows from this one on can all fit in the same range.  If
X		 * so, set things up so they will align themselves nicely.
X		 */
X		if ((cp->c_col < curmin) || (rp->r_lastcell->c_col > curmax)) {
X			curmin = cp->c_col;
X			curmax = rp->r_lastcell->c_col;
X			trp = rp;
X			for (; rp != termrow; rp = rp->r_next) {
X				if (rp->r_firstcell == termcell) continue;
X				testmin = rp->r_firstcell->c_col;
X				if (testmin > curmin) testmin = curmin;
X				testmax = rp->r_lastcell->c_col;
X				if (testmax < curmax) testmax = curmax;
X				if ((testmax - testmin) >= maxcols) break;
X				curmin = testmin;
X				curmax = testmax;
X			}
X			rp = trp;
X		}
X		/*
X		 * Type the row, with the initial shift if necessary.
X		 */
X		if (curmin != mincol) {
X			fprintf(fd, "%d.", curmin - mincol);
X			col = curmin;
X		}
X		for (cp = rp->r_firstcell; cp != termcell; cp = cp->c_next) {
X			while (cp->c_col > col) {
X				fputc('.', fd);
X				col++;
X			}
X			fputc('O', fd);
X			col++;
X		}
X		fputc('\n', fd);
X		row++;
X	}
X	fclose(fd);
X}
X
X
X
X/* Write all defined macros to a file so they can be read back in later. */
Xwritemacros(name)
X	char	*name;			/* file name to write to */
X{
X	register int	f;		/* file descriptor */
X	register struct	macro	*mp;	/* current macro */
X	int	ch;			/* character */
X
X	f = creat(name, 0666);
X	if (f < 0) error("Cannot create file");
X	for (mp = macros; mp < &macros[26]; mp++) {
X		if ((mp->m_begptr == NULL) || (mp->m_begptr >= mp->m_endptr)) {
X			continue;
X		}
X		ch = 'a' + (mp - macros);
X		write(f, "<", 1);
X		write(f, &ch, 1);
X		write(f, mp->m_begptr, mp->m_endptr - mp->m_begptr);
X		write(f, ">!\n", 3);
X	}
X	close(f);
X}
//E*O*F io.c//
echo done

dbell@daisy.UUCP (David I. Bell) (02/03/85)

This is the last part of the actual life sources.  The final article just
contains life objects.

#---Cut here and place in it's own directory, then feed to Bourne shell---
# This is a shell archive.  Remove anything before this line, then
# unpack it by saving it in a file and typing "sh file".  (Files
# unpacked will be owned by you and have default permissions.)
# This archive contains:
#   main.c (2817 chars)
#   mark.c (9268 chars)
#   object.c (8845 chars)
#   scan.c (3526 chars)
#   vars.c (9419 chars)
#   view.c (9535 chars)
#
echo x - main.c
sed -e 's/^X//' > "main.c" << '//E*O*F main.c//'
Xstatic char *sccsid = "@(#)main.c	1.12	2/2/85";
Xstatic char *cpyrid = "@(#)Copyright (C) 1985 by D Bell";
X
X/*
X * The game of life on an infinite board (by David I. Bell).
X * These life sources are in the public domain, and can be copied
X * or used as desired, with the following restrictions:
X * 1.	All copyright notices (and this notice) must be preserved.
X * 2.	The life sources (even if modified) cannot be sold for profit.
X * 3.	If any sources are modified, a sentence must exist by the
X *	copyright notice of each modified source file which says that
X *	the file has been modified.
X */
X
X#include "life.h"
X#include <sgtty.h>
X
Xint	intint(), readchar();		/* routines */
Xchar	*getenv();			/* another one */
X
X
Xmain(argc, argv)
X	char	**argv;			/* argument is a life object */
X{
X	userlib = getenv(LIFEVAR);
X	strcpy(rulestring, "3,23");
X	termcell = alloccell();
X	termcell->c_next = termcell;
X	termcell->c_col = INFINITY;
X	termrow = allocrow();
X	termrow->r_next = termrow;
X	termrow->r_firstcell = termcell;
X	termrow->r_row = INFINITY;
X	mode = M_MOVE;
X	gridchar = ' ';
X	frequency = 1;
X	freqcount = 1;
X	rowradius = 1;				/* temp until find real sizes */
X	colradius = 1;
X	reserve = 1;				/* creating special objects */
X	deleteobject = getobject("..delete");
X	tempobject = getobject("..temp");
X	backupobject = getobject("..backup");
X	mainobject = getobject("main");
X	curobj = mainobject;
X	prevobj = mainobject;
X	reserve = 0;				/* no more special objects */
X	curinput = &inputs[-1];			/* initialize for tty input */
X	settty();
X	if ((argc > 1) && setfile(argv[1])) {	/* set to get input file */
X		perror(argv[1]);
X		exit(1);
X	}
X	if (dpyinit(NULL, CBREAK, ECHO)) {	/* home up and clear screen */
X		exit(1);
X	}
X	dpymove(-1, -1);			/* get screen size */
X	rowradius = (dpygetrow() - 1) / 2;
X	colradius = (dpygetcol() - 1) / 2;
X	setscale(deleteobject, 1);		/* fix scale factors now */
X	setscale(tempobject, 1);
X	setscale(backupobject, 1);
X	setscale(mainobject, 1);
X	signal(SIGINT, intint);
X	scaninit(readchar, ttyjmp);
X	while (1) {
X		docommand();
X		dogeneration(curobj);
X		updateview();
X	}
X}
X
X
X/*
X * Here on an interrupt character.  Remember to stop what we are doing soon.
X * We cannot just longjmp away since things may be in an inconsistent state.
X */
Xintint()
X{
X	signal(SIGINT, intint);		/* not needed in 4.2, but so what */
X	genleft = 0;
X	stop = 1;
X	redraw = 1;
X#ifdef DEBUG
X	dumpdata();
X#endif DEBUG
X}
X
X
X/*
X * Here on an error.  Close all but the top input level, cancel the
X * current command, beep, and set up to display the indicated message.
X * The message will remain until the next command is typed by the user.
X */
Xerror(str)
X	char	*str;		/* message to type */
X{
X	while (curinput > inputs) curinput->i_term(curinput);
X	errorstring = str;
X	redraw = 1;
X	stop = 0;
X	dowait = 0;
X	write(STDERR, "\007", 1);
X	scanabort();
X}
//E*O*F main.c//
echo x - mark.c
sed -e 's/^X//' > "mark.c" << '//E*O*F mark.c//'
X#ifdef SCCS
Xstatic char *sccsid = "@(#)mark.c	1.7	2/2/85";
Xstatic char *cpyrid = "@(#)Copyright (C) 1985 by D Bell";
X#endif
X
X#include "life.h"
X
Xstruct	loc	{		/* structure to hold row and column pairs */
X	long	l_row;
X	long	l_col;
X};
X
Xchar	rowwalk[9] = {-1,0,0,1,1,0,0,-1,0};	/* row deltas to walk a cell */
Xchar	colwalk[9] = {-1,1,1,0,0,-1,-1,0,1};	/* col deltas to walk a cell */
X
X
X/*
X * Mark the object at a given location as specified.  Returns nonzero if
X * no object exists there.  An object for this purpose is considered as a
X * king-wise connected set of live cells.
X */
Xmarkobject(obj, row, col, mark)
X	register struct	object	*obj;	/* object to mark up */
X{
X	register struct	cell	*cp;	/* object being marked */
X
X	cp = findcell(obj, row, col);
X	if (cp == NULL) return(1);
X	cp->c_marks |= mark;
X	markloop(obj, row, col, mark);
X	return(0);
X}
X
X
X/* Recursive subroutine called from markobject */
Xmarkloop(obj, row, col, mark)
X	struct	object	*obj;		/* object begin marked */
X	register int	row;		/* current row */
X	register int	col;		/* current column */
X	register int	mark;		/* marking value */
X{
X	register struct	cell	*cp;	/* current cell */
X	register struct	loc	*rp;	/* pointer into list table */
X	int	i;			/* to iterate over directions */
X	struct	loc	rclist[8];	/* row and column list */
X
X	while (1) {
X		if (stop) return;
X		rp = rclist;
X		for (i = 0; i < 8; i++) {	/* find neighbors */
X			row += rowwalk[i];
X			col += colwalk[i];
X			cp = findcell(obj, row, col);
X			if (cp == NULL) continue;
X			if (cp->c_marks & mark) continue;
X			cp->c_marks |= mark;
X			rp->l_row = row;
X			rp->l_col = col;
X			rp++;
X		}
X		if (--rp != rclist) {		/* recurse if more than one */
X			for (; rp >= rclist; rp--) {
X				markloop(obj, rp->l_row, rp->l_col, mark);
X			}
X			return;
X		}
X		row = rp->l_row;		/* else follow single cell */
X		col = rp->l_col;
X	}
X}
X
X
X/* Mark a whole object as specified. */
Xsetmarks(obj, mark)
X	struct	object	*obj;		/* object to mark */
X	register int	mark;		/* mark to be applied */
X{
X	register struct	row	*rp;	/* row pointer */
X	register struct	cell	*cp;	/* cell pointer */
X
X	mark |= MARK_ANY;
X	for (rp = obj->o_firstrow; rp != termrow; rp = rp->r_next) {
X		for (cp = rp->r_firstcell; cp != termcell; cp = cp->c_next) {
X			cp->c_marks |= mark;
X		}
X	}
X}
X
X
X/*
X * Copy the marks from one type to another for an object.  Returns nonzero
X * if there were no cells with the given mark.
X */
Xcopymarks(obj, srcmark, destmark)
X	struct	object	*obj;		/* object to mark */
X	register int	srcmark;	/* mark to be found */
X	register int	destmark;	/* mark to be set */
X{
X	register struct	row	*rp;	/* row pointer */
X	register struct	cell	*cp;	/* cell pointer */
X	int	error;
X
X	error = 1;
X	for (rp = obj->o_firstrow; rp != termrow; rp = rp->r_next) {
X		for (cp = rp->r_firstcell; cp != termcell; cp = cp->c_next) {
X			if ((cp->c_marks & srcmark) == 0) continue;
X			cp->c_marks |= destmark;
X			error = 0;
X		}
X	}
X	return(error);
X}
X
X
X/* Clear marks for a whole object as specified. */
Xclearmarks(obj, mark)
X	struct	object	*obj;		/* object to mark */
X	register int	mark;		/* mark to be cleared */
X{
X	register struct	row	*rp;	/* row pointer */
X	register struct	cell	*cp;	/* cell pointer */
X
X	mark = ~mark;
X	mark |= MARK_ANY;
X	for (rp = obj->o_firstrow; rp != termrow; rp = rp->r_next) {
X		for (cp = rp->r_firstcell; cp != termcell; cp = cp->c_next) {
X			cp->c_marks &= mark;
X		}
X	}
X}
X
X
X/*
X * Mark the cells in a specified rectangular region as desired.
X * Returns the number of cells which were marked.
X */
Xmarkregion(obj, mark, minrow, maxrow, mincol, maxcol)
X	struct	object	*obj;			/* object to mark */
X	register int	mark;			/* value to mark with */
X	long	minrow, maxrow, mincol, maxcol;	/* range to mark */
X{
X	register struct	row	*rp;		/* current row */
X	register struct	cell	*cp;		/* current cell */
X	register long	count;			/* count of cells */
X
X	count = 0;
X	for (rp = obj->o_firstrow; rp != termrow; rp = rp->r_next) {
X		if (rp->r_row < minrow) continue;
X		if (rp->r_row > maxrow) break;
X		for (cp = rp->r_firstcell; cp != termcell; cp = cp->c_next) {
X			if (cp->c_col < mincol) continue;
X			if (cp->c_col > maxcol) break;
X			cp->c_marks |= mark;
X			count++;
X		}
X	}
X	return(count);
X}
X
X
X/*
X * Find the range of all marked cells for an object.  Returns nonzero if
X * no cells were marked.
X */
Xmarkminmax(obj, mark, minrow, maxrow, mincol, maxcol)
X	struct	object	*obj;		/* object to search */
X	register int	mark;		/* mark to search for */
X	long	*minrow, *maxrow, *mincol, *maxcol;	/* results */
X{
X	register struct	row	*rp;	/* current row */
X	register struct	cell	*cp;	/* current cell */
X	register int	row;		/* current row */
X	long	minr, maxr, minc, maxc;	/* temp variables */
X
X	minr = INFINITY;
X	maxr = -INFINITY;
X	minc = INFINITY;
X	maxc = -INFINITY;
X	for (rp = obj->o_firstrow; rp != termrow; rp = rp->r_next) {
X		row = rp->r_row;
X		for (cp = rp->r_firstcell; cp != termcell; cp = cp->c_next) {
X			if ((cp->c_marks & mark) == 0) continue;
X			if (row < minr) minr = row;
X			if (row > maxr) maxr = row;
X			if (cp->c_col < minc) minc = cp->c_col;
X			if (cp->c_col > maxc) maxc = cp->c_col;
X		}
X	}
X	if (minr > maxr) return(1);
X	*minrow = minr;
X	*maxrow = maxr;
X	*mincol = minc;
X	*maxcol = maxc;
X	return(0);
X}
X
X
X/* Count the number of marked cells in an object */
Xcountmarks(obj, mark)
X	struct	object	*obj;		/* object to check */
X	register int	mark;		/* mark to be counted */
X{
X	register struct	row	*rp;	/* row pointer */
X	register struct	cell	*cp;	/* cell pointer */
X	register long	count;		/* number of cells */
X
X	count = 0;
X	for (rp = obj->o_firstrow; rp != termrow; rp = rp->r_next) {
X		for (cp = rp->r_firstcell; cp != termcell; cp = cp->c_next) {
X			if (cp->c_marks & mark) count++;
X		}
X	}
X	return(count);
X}
X
X
X/*
X * Move marked cells to another object.  If the destination object is NULL
X * the cells are just deleted.  The previous cells of the destination object
X * are deleted.
X */
Xmovemarkedobject(sobj, dobj, mark)
X	register struct	object	*sobj;	/* source object */
X	struct	object	*dobj;		/* destination object */
X{
X	register struct	row	*rp;	/* row pointer */
X	register struct	cell	*cp;	/* current cell pointer */
X	register struct	cell	*pcp;	/* previous cell pointer */
X	register struct	cell	*ncp;	/* next cell pointer */
X
X	if (sobj == dobj) error("Moving object to itself");
X	if (dobj) {
X		zeroobject(dobj);
X		dobj->o_currow = sobj->o_currow;
X		dobj->o_curcol = sobj->o_curcol;
X	}
X	for (rp = sobj->o_firstrow; rp != termrow; rp = rp->r_next) {
X		pcp = NULL;
X		cp = rp->r_firstcell;
X		while (cp != termcell) {
X			if ((cp->c_marks & mark) == 0) {
X				pcp = cp;
X				cp = cp->c_next;
X				continue;
X			}
X			if (dobj) addcell(dobj, rp->r_row, cp->c_col);
X			ncp = cp->c_next;
X			if (pcp == NULL)
X				rp->r_firstcell = ncp;
X			else
X				pcp->c_next = ncp;
X			if (ncp == termcell) rp->r_lastcell = pcp;
X			cp->c_next = freecells;
X			freecells = cp;
X			cp = ncp;
X			rp->r_count--;
X			sobj->o_count--;
X		}
X	}
X}
X
X
X/*
X * Copy marked cells to another object. The previous cells of the destination
X * object are deleted.  The source object is unaffected.
X */
Xcopymarkedobject(sobj, dobj, mark)
X	register struct	object	*sobj;	/* source object */
X	register struct	object	*dobj;	/* destination object */
X	register int	mark;		/* mark value */
X{
X	register struct	row	*rp;	/* row pointer */
X	register struct	cell	*cp;	/* current cell */
X
X	if (sobj == dobj) error("Copying object to itself");
X	zeroobject(dobj);
X	dobj->o_currow = sobj->o_currow;
X	dobj->o_curcol = sobj->o_curcol;
X	for (rp = sobj->o_firstrow; rp != termrow; rp = rp->r_next) {
X		for (cp = rp->r_firstcell; cp != termcell; cp = cp->c_next) {
X			if ((cp->c_marks & mark) == 0) continue;
X			addcell(dobj, rp->r_row, cp->c_col);
X		}
X	}
X}
X
X
X/*
X * Rotate the marked cells in the given object around the current cursor
X * location.  This deletes the marked cells, and reinserts them after
X * their position has been rotated by 90 degrees clockwise.
X */
Xrotatemarkedobject(obj, mark)
X	register struct	object	*obj;		/* current object */
X{
X	register struct	row	*rp;		/* current row */
X	register struct	cell	*cp;		/* current cell */
X	register int	row, col;		/* current row and column */
X
X	if (obj == tempobject) error("Using temp object");
X	movemarkedobject(obj, tempobject, mark);
X	for (rp = tempobject->o_firstrow; rp != termrow; rp = rp->r_next) {
X		row = rp->r_row - obj->o_currow;
X		for (cp = rp->r_firstcell; cp != termcell; cp = cp->c_next) {
X			col = cp->c_col - obj->o_curcol;
X			addcell(obj, obj->o_currow + col, obj->o_curcol - row);
X		}
X	}
X}
X
X
X/*
X * Flip the marked cells in the given object around the column of the current
X * cursor location.  This deletes the marked cells, and reinserts them after
X * their position has been flipped around the vertical axis.
X */
Xflipmarkedobject(obj, mark)
X	register struct	object	*obj;		/* current object */
X{
X	register struct	row	*rp;		/* current row */
X	register struct	cell	*cp;		/* current cell */
X	register int	row, col;		/* current row and column */
X
X	if (obj == tempobject) error("Using temp object");
X	movemarkedobject(obj, tempobject, mark);
X	for (rp = tempobject->o_firstrow; rp != termrow; rp = rp->r_next) {
X		row = rp->r_row;
X		for (cp = rp->r_firstcell; cp != termcell; cp = cp->c_next) {
X			col = cp->c_col - obj->o_curcol;
X			addcell(obj, row, obj->o_curcol - col);
X		}
X	}
X}
//E*O*F mark.c//
echo x - object.c
sed -e 's/^X//' > "object.c" << '//E*O*F object.c//'
X#ifdef SCCS
Xstatic char *sccsid = "@(#)object.c	1.17	2/2/85";
Xstatic char *cpyrid = "@(#)Copyright (C) 1985 by D Bell";
X#endif
X
X#include "life.h"
X
X
X/*
X * Find the given named object.  Returns NULL if nonexistant.  The special
X * name of "." means the current object.  The special name of ".." means
X * the previous object.
X */
Xstruct object *
Xfindobject(str)
X	register char	*str;		/* name to find */
X{
X	register struct	object	*obj;	/* current object */
X
X	if (str[0] == '.') {		/* check for "." or ".." */
X		if (str[1] == '\0') return(curobj);
X		if ((str[1] == '.') && (str[2] == '\0')) return(prevobj);
X	}
X	for (obj = objects; obj; obj = obj->o_next) {
X		if (strcmp(obj->o_name, str) == 0) return(obj);
X	}
X	return(NULL);
X}
X
X
X/* Create the given named object, or return it if it already exists. */
Xstruct object *
Xgetobject(str)
X	register char	*str;		/* name to find */
X{
X	register struct	object	*obj;	/* current object */
X
X	for (obj = objects; obj; obj = obj->o_next) {
X		if (strcmp(obj->o_name, str) == 0) return(obj);
X	}
X	if (strlen(str) > MAXNAME) error("Object name too long");
X	if ((reserve==0) && BADNAME(str)) error("Cannot create reserved name");
X	obj = allocobject();
X	obj->o_next = objects;
X	objects = obj;
X	strcpy(obj->o_name, str);
X	return(obj);
X}
X
X
X/*
X * Set an object as the current one.  The old current object is remembered
X * so that it can be referenced using "..".  This cancels any insert mode.
X */
Xsetobject(obj)
X	register struct	object	*obj;		/* new object to set */
X{
X	mode = M_MOVE;
X	if (obj == curobj) return;
X	prevobj = curobj;
X	curobj = obj;
X	redraw = 1;
X}
X
X
X/* Delete all cells of an object */
Xzeroobject(obj)
X	register struct	object	*obj;
X{
X	register struct	row	*rp;
X
X	rp = obj->o_firstrow;
X	if (rp == termrow) return;
X	for (; rp != termrow; rp = rp->r_next) {
X		if (rp->r_firstcell == termcell) continue;
X		rp->r_lastcell->c_next = freecells;
X		freecells = rp->r_firstcell;
X		obj->o_count -= rp->r_count;
X	}
X	obj->o_lastrow->r_next = freerows;
X	freerows = obj->o_firstrow;
X	obj->o_firstrow = termrow;
X	obj->o_lastrow = NULL;
X}
X
X
X/*
X * Destroy the existence of an object.  If it is the current object,
X * switch the current object back to the previous object.
X */
Xdestroyobject(obj)
X	register struct	object	*obj;		/* object to delete */
X{
X	register struct	object	*pobj;		/* previous object */
X
X	if (obj == NULL) return;
X	if (obj->o_reserved) error("Cannot destroy reserved object");
X	if (obj == prevobj) prevobj = mainobject;
X	if (obj == curobj) {
X		curobj = prevobj;
X		prevobj = mainobject;
X		redraw = 1;
X	}
X	zeroobject(obj);
X	if (objects == obj) {		/* first object in list */
X		objects = obj->o_next;
X		obj->o_next = freeobjects;
X		freeobjects = obj;
X		return;
X	}
X	for (pobj = objects; pobj->o_next != obj; pobj = pobj->o_next) ;
X	pobj->o_next = obj->o_next;
X	obj->o_next = freeobjects;
X	freeobjects = obj;
X}
X
X
X/*
X * Move one object to another.  The source object is zeroed, and the
X * previous contents of the destination object are lost.
X */
Xmoveobject(sobj, dobj)
X	register struct	object	*sobj;		/* source object */
X	register struct	object	*dobj;		/* destination object */
X{
X	if (sobj == dobj) error("Moving object to itself");
X	zeroobject(dobj);
X	dobj->o_currow = sobj->o_currow;
X	dobj->o_curcol = sobj->o_curcol;
X	dobj->o_minrow = sobj->o_minrow;
X	dobj->o_maxrow = sobj->o_maxrow;
X	dobj->o_mincol = sobj->o_mincol;
X	dobj->o_maxcol = sobj->o_maxcol;
X	dobj->o_scale = sobj->o_scale;
X	dobj->o_autoscale = sobj->o_autoscale;
X	dobj->o_prow = sobj->o_prow;
X	dobj->o_pcol = sobj->o_pcol;
X	dobj->o_firstrow = sobj->o_firstrow;
X	dobj->o_lastrow = sobj->o_lastrow;
X	dobj->o_count = sobj->o_count;
X	sobj->o_firstrow = termrow;
X	sobj->o_lastrow = NULL;
X	sobj->o_count = 0;
X}
X
X
X/*
X * Add one object to another.  The source object is unchanged.  The 
X * destination object will get all cells from both objects.  If disp is
X * RELATIVE, the object is displaced as specified by the two object's cursor
X * positions. Otherwise, the addition is performed with absolute coordinates.
X */
Xaddobject(sobj, dobj, disp)
X	register struct	object	*sobj;		/* source object */
X	register struct	object	*dobj;		/* destination object */
X{
X	register struct	row	*rp;		/* current row */
X	register struct	cell	*cp;		/* current cell */
X	register int	newrow;			/* new row number */
X	int	rowdisp, coldisp;		/* displacements */
X
X	if (sobj == dobj) error("Adding object to itself");
X	rowdisp = 0;
X	coldisp = 0;
X	if (disp == RELATIVE) {
X		rowdisp = dobj->o_currow - sobj->o_currow;
X		coldisp = dobj->o_curcol - sobj->o_curcol;
X	}
X	for (rp = sobj->o_firstrow; rp != termrow; rp = rp->r_next) {
X		newrow = rp->r_row + rowdisp;
X		for (cp = rp->r_firstcell; cp != termcell; cp = cp->c_next) {
X			addcell(dobj, newrow, cp->c_col + coldisp);
X		}
X	}
X}
X
X
X/*
X * Copy one object to another.  The source object is unchanged.
X * The current contents of the destination object are lost.
X */
Xcopyobject(sobj, dobj)
X	register struct	object	*sobj;		/* source object */
X	register struct	object	*dobj;		/* destination object */
X{
X	if (sobj == dobj) error("Copying object to itself");
X	zeroobject(dobj);
X	addobject(sobj, dobj, ABSOLUTE);
X	dobj->o_currow = sobj->o_currow;
X	dobj->o_curcol = sobj->o_curcol;
X	dobj->o_minrow = sobj->o_minrow;
X	dobj->o_maxrow = sobj->o_maxrow;
X	dobj->o_mincol = sobj->o_mincol;
X	dobj->o_maxcol = sobj->o_maxcol;
X	dobj->o_scale = sobj->o_scale;
X	dobj->o_autoscale = sobj->o_autoscale;
X	dobj->o_prow = sobj->o_prow;
X	dobj->o_pcol = sobj->o_pcol;
X}
X
X
X/*
X * Show the list of objects.  If all is nonzero, all objects will be
X * shown.  Otherwise, only objects not starting with a period are shown.
X */
Xlistobjects(all)
X{
X	register struct	object	*obj;		/* current object */
X	register int	ch;			/* current character */
X	int	minrow, maxrow, mincol, maxcol;	/* current bounds */
X
X	dpywindow(0, -1, 0, -1);
X	dpystr("cells	height	width	gen	scale	object\n");
X	dpystr("-----	------	-----	---	-----	------\n");
X	for (obj = objects; obj; obj = obj->o_next) {
X		if ((all == 0) && (obj->o_name[0] == '.')) continue;
X		ch = ' ';
X		if (obj == prevobj) ch = '+';
X		if (obj == curobj) ch = '*';
X		minmax(obj, &minrow, &maxrow, &mincol, &maxcol);
X		dpyprintf("%d\t%d\t%d\t%d\t%d\t%c %s\n",
X			obj->o_count, (maxrow - minrow + 1),
X			(maxcol - mincol + 1), obj->o_gen,
X			obj->o_scale, ch, obj->o_name);
X	}
X	dpyprintf("\n\
XIn object column, '*' = current object, '+' = previous object\n");
X	if (all == 0)
X		dpyprintf("Use -a to show objects beginning with '.'\n");
X	spacewait();
X}
X
X
X/*
X * Find the minimum and maximum row and column numbers for an object.
X * If there are no cells in the object, the mins will be one more than
X * the maxes.  Returns nonzero if the object has no cells.
X */
Xminmax(obj, minrow, maxrow, mincol, maxcol)
X	struct	object	*obj;				/* object to examine */
X	long	*minrow, *maxrow, *mincol, *maxcol;	/* pointers to result */
X{
X	register struct	row	*rp;		/* current row */
X	register int	maxr, minr, maxc, minc;	/* current results */
X	int	err;				/* return value */
X
X	minr = INFINITY;
X	maxr = -INFINITY;
X	minc = INFINITY;
X	maxc = -INFINITY;
X	err = 1;
X	for (rp = obj->o_firstrow; rp != termrow; rp = rp->r_next) {
X		if (rp->r_firstcell == termcell) continue;
X		if (rp->r_row < minr) minr = rp->r_row;
X		maxr = rp->r_row;
X		if (rp->r_firstcell->c_col<minc) minc = rp->r_firstcell->c_col;
X		if (rp->r_lastcell->c_col>maxc) maxc = rp->r_lastcell->c_col;
X		err = 0;
X	}
X	if (err) {			/* no cells in object */
X		minr = 1;
X		maxr = 0;
X		minc = 1;
X		maxc = 0;
X	}
X	*minrow = minr;
X	*maxrow = maxr;
X	*mincol = minc;
X	*maxcol = maxc;
X	return(err);
X}
X
X
X/*
X * Search forwards for the nth next object, restarting at the top if necessary.
X * If all is nonzero, or if wrap around occurs, the search will be over all
X * objects.  Otherwise, objects found in previous searches will be skipped.
X * Returns nonzero if nothing was found.
X */
Xsearchobject(obj, count, all)
X	register struct	object	*obj;		/* object to search through */
X{
X	register struct	row	*rp;	/* current row being examined */
X	register struct	cell	*cp;	/* current cell begin examined */
X	register long	row;		/* current row */
X	register long	col;		/* current column */
X
X	if (all) clearmarks(obj, MARK_SRC);
X	row = obj->o_currow;
X	col = obj->o_curcol;
X	for (rp = obj->o_firstrow; row > rp->r_row; rp = rp->r_next) ;
X	for (cp = rp->r_firstcell; col > cp->c_col; cp = cp->c_next) ;
X	if ((row == rp->r_row) && (col == cp->c_col) &&
X		((cp->c_marks & MARK_SRC) == 0)) count++;
X	while (1) {
X		if (stop) return(0);
X		if (cp == termcell) {
X			rp = rp->r_next;
X			if (rp == termrow) {
X				clearmarks(obj, MARK_SRC);
X				rp = obj->o_firstrow;
X			}
X			cp = rp->r_firstcell;
X			continue;
X		}
X		if ((cp->c_marks & MARK_SRC) == 0) {
X			markobject(obj, rp->r_row, cp->c_col, MARK_SRC);
X			if (--count <= 0) break;
X		}
X		cp = cp->c_next;
X	}
X	obj->o_currow = rp->r_row;
X	obj->o_curcol = cp->c_col;
X	return(0);
X}
//E*O*F object.c//
echo x - scan.c
sed -e 's/^X//' > "scan.c" << '//E*O*F scan.c//'
X#ifdef SCCS
Xstatic char *sccsid = "@(#)scan.c	1.4	1/27/85";
Xstatic char *cpyrid = "@(#)Copyright (C) 1985 by D Bell";
X#endif
X
X/* Module to read self-terminating input while allowing editing of the input */
X
X#include <sgtty.h>
X#include "life.h"
X
Xjmp_buf	*scanjumpbuf;			/* jump buffer to use in scanchar */
Xchar	scanbuffer[SCAN_SIZE+1];	/* storage for characters */
Xchar	*scanreadptr;			/* current read pointer */
Xchar	*scanwriteptr;			/* current write pointer */
Xchar	(*scanroutine)();		/* routine to read characters */
Xstatic	char	rubchar;		/* erase letter character */
Xstatic	char	rubword;		/* erase word character */
Xstatic	char	rubline;		/* erase line character */
Xstatic	char	litchar;		/* literal input */
X
X
X/*
X * Initialize for later calls to scanchar.
X */
Xscaninit(routine, jumpbuf)
X	char	(*routine)();		/* routine to get characters */
X	jmp_buf	*jumpbuf;		/* jump buffer to use later */
X{
X	struct	sgttyb	sgbuf;		/* basic tty structure */
X	struct	ltchars	ltbuf;		/* local tty structure */
X
X	scanroutine = routine;		/* init static variables */
X	scanjumpbuf = jumpbuf;
X	scanwriteptr = scanbuffer;
X	scanreadptr = scanbuffer;
X	sgbuf.sg_erase = CERASE;	/* set defaults in case ioctls fail */
X	sgbuf.sg_kill = CKILL;
X	ltbuf.t_werasc = CWERASE;
X	ltbuf.t_lnextc = CLNEXT;
X	ioctl(STDIN, TIOCGETP, &sgbuf);	/* get and save editing characters */
X	ioctl(STDIN, TIOCGLTC, &ltbuf);
X	rubchar = sgbuf.sg_erase;
X	rubline = sgbuf.sg_kill;
X	rubword = ltbuf.t_werasc;
X	litchar = ltbuf.t_lnextc;
X}
X
X
X/*
X * Read the next input character.  If it is an editing character,
X * abort the current context and longjmp back to the last setjmp.
X * NOTE: for proper results, the caller should not alter the global
X * state until the full command has been read in.  This includes such
X * things as prompting for input or saving values.  Otherwise, improper
X * results will occur if the user edits the command.
X */
Xscanchar()
X{
X	register int	ch;			/* current character */
X
Xloop:	if (scanreadptr < scanwriteptr)		/* get saved char if have any */
X		return(*scanreadptr++);
X	ch = scanroutine() & 0x7f;		/* get new character */
X	if (ch == litchar) {			/* literal input */
X		ch = scanroutine() & 0x7f;
X		goto store;
X	}
X	if (ch == rubchar) {			/* character erase */
X		if (scanwriteptr <= scanbuffer) {
X			write(STDERR, "\007", 1);
X			goto loop;
X		}
X		scanwriteptr--;
X		scanreadptr = scanbuffer;
X		longjmp(scanjumpbuf, SCAN_EDIT);
X	}
X	if (ch == rubword) {			/* word erase */
X		if (scanwriteptr <= scanbuffer) goto loop;
X		while ((--scanwriteptr >= scanbuffer) &&
X			((*scanwriteptr == ' ') || (*scanwriteptr == '\t'))) ;
X		scanwriteptr++;
X		while ((--scanwriteptr >= scanbuffer) &&
X			((*scanwriteptr != ' ') && (*scanwriteptr != '\t'))) ;
X		scanwriteptr++;
X		scanreadptr = scanbuffer;
X		longjmp(scanjumpbuf, SCAN_EDIT);
X	}
X	if (ch == rubline) {			/* line erase */
X		if (scanwriteptr <= scanbuffer) goto loop;
X		scanwriteptr = scanbuffer;
X		scanreadptr = scanbuffer;
X		longjmp(scanjumpbuf, SCAN_EDIT);
X	}
X
Xstore:	if (scanwriteptr >= scanbuffer + SCAN_SIZE) {
X		write(STDERR, "\007", 1);
X		goto loop;
X	}
X	*scanwriteptr++ = ch;
X	return(*scanreadptr++);
X}
X
X
X/* Abort reading of the current command */
Xscanabort()
X{
X	scanreadptr = scanbuffer;
X	scanwriteptr = scanbuffer;
X	longjmp(scanjumpbuf, SCAN_ABORT);
X}
X
X
X/* Indicate no more characters ready yet */
Xscaneof()
X{
X	scanreadptr = scanbuffer;
X	longjmp(scanjumpbuf, SCAN_EOF);
X}
X
X
X/* Simply reset input and output pointers without longjmping */
Xscanreset()
X{
X	scanreadptr = scanbuffer;
X	scanwriteptr = scanbuffer;
X}
//E*O*F scan.c//
echo x - vars.c
sed -e 's/^X//' > "vars.c" << '//E*O*F vars.c//'
X#ifdef SCCS
Xstatic char *sccsid = "@(#)vars.c	1.11	2/2/85";
Xstatic char *cpyrid = "@(#)Copyright (C) 1985 by D Bell";
X#endif
X
X#include "life.h"
X
Xenum	vars	{		/* ordering of variables */
X	v_minx, v_miny, v_maxx, v_maxy, v_cells, v_cx, v_cy, v_px, v_py,
X	v_vminx, v_vmaxx, v_vminy, v_vmaxy, v_vcells, v_vx, v_vy,
X	v_sminx, v_smaxx, v_sminy, v_smaxy, v_scells, v_gen, v_scale,
X	v_freq, v_born, v_died, v_endlist
X};
X
X
X/*
X * Table of multi-character variables.  These are arranged in this table
X * in rows of 3 so that you can see what the display will look like for
X * the listvariables routine.
X */
Xstruct	vartab	{
X	char	*v_name;	/* name of variable */
X	enum	vars v_type;	/* variable id */
X} vartab[] = {
X	"cx", v_cx,		"vx", v_vx,		"px", v_px,
X	"cy", v_cy,		"vy", v_vy,		"py", v_py,
X	"minx", v_minx,		"vminx", v_vminx,	"sminx", v_sminx,
X	"maxx", v_maxx,		"vmaxx", v_vmaxx,	"smaxx", v_smaxx,
X	"maxy", v_maxy,		"vmaxy", v_vmaxy,	"smaxy", v_smaxy,
X	"miny", v_miny,		"vminy", v_vminy,	"sminy", v_sminy,
X	"cells", v_cells,	"vcells", v_vcells,	"scells", v_scells,
X	"gen", v_gen,		"born", v_born,		"died", v_died,
X	"scale", v_scale,	"freq", v_freq,		NULL, v_endlist
X};
X
X
Xstatic	char	*curcp;		/* current character to parse */
Xstatic	long	lowervars[26];	/* lower case single-char variable values */
Xstatic	long	uppervars[26];	/* upper case single-char variable values */
X
X
X/*
X * Return the value of a multiple character variable name.  This can be
X * either a single character name, or else one of a fixed set of multi-
X * character names.  All variable names must start with either a letter
X * or a dollar sign followed by a letter.
X */
Xgetvariable(cp)
X	register char	*cp;			/* name of variable */
X{
X	register struct	vartab	*vp;		/* variable pointer */
X
X	if (*cp == '$') cp++;			/* skip any dollar sign */
X	if (cp[1] == '\0') {			/* single character name */
X		return(getvariable1(*cp));
X	}
X	if (((*cp < 'a') || (*cp > 'z')) && ((*cp < 'A') || (*cp > 'Z'))) {
X		error("Bad variable name");
X	}
X	for (vp = vartab; ; vp++)  {		/* find name in table */
X		if (vp->v_name == NULL) error("Unknown variable");
X		if (strcmp(vp->v_name, cp) == 0) break;
X	}
X	return(getvariablebytype(vp->v_type));	/* get value */
X}
X
X
X/*
X * Return the value of a variable given its enum value.
X */
Xgetvariablebytype(type)
X	enum	vars	type;			/* variable type to get */
X{
X	register struct	object	*obj;		/* current object */
X	register long	value;			/* value to return */
X	register long	*ptr;			/* pointer to minmax value */
X	register long	*sptr;			/* another one */
X	long	sign;				/* sign for result */
X	long	minrow, maxrow, mincol, maxcol;	/* results of minmax */
X
X	obj = curobj;
X	value = 0;
X	sign = 1;
X	ptr = NULL;
X	sptr = NULL;
X
X	switch (type) {			/* collect value */
X	case v_cx:	value = obj->o_curcol; break;
X	case v_cy:	value = -obj->o_currow; break;
X	case v_px:	value = obj->o_pcol; break;
X	case v_py:	value = -obj->o_prow; break;
X	case v_vx:	value = (obj->o_mincol + obj->o_maxcol) / 2; break;
X	case v_vy:	value = -(obj->o_minrow + obj->o_maxrow) / 2; break;
X	case v_vminx:	value = obj->o_mincol; break;
X	case v_vmaxx:	value = obj->o_maxcol; break;
X	case v_vminy:	value = -obj->o_maxrow; break;
X	case v_vmaxy:	value = -obj->o_minrow; break;
X	case v_vcells:	value = markregion(obj, MARK_ANY, obj->o_minrow,
X				obj->o_maxrow, obj->o_mincol, obj->o_maxcol);
X			break;
X	case v_cells:	value = obj->o_count; break;
X	case v_gen:	value = obj->o_gen; break;
X	case v_born:	value = obj->o_born; break;
X	case v_died:	value = obj->o_died; break;
X	case v_freq:	value = frequency; break;
X	case v_scale:	value = obj->o_scale; break;
X	case v_minx:	ptr = &mincol; break;
X	case v_maxx:	ptr = &maxcol; break;
X	case v_miny:	ptr = &maxrow; sign = -1; break;
X	case v_maxy:	ptr = &minrow; sign = -1; break;
X	case v_scells:	value = countmarks(obj, MARK_SEE); break;
X	case v_sminx:	sptr = &mincol; break;
X	case v_smaxx:	sptr = &maxcol; break;
X	case v_sminy:	sptr = &maxrow; sign = -1; break;
X	case v_smaxy:	sptr = &minrow; sign = -1; break;
X	}
X	/*
X	 * Call proper minmax routines if we need to
X	 */
X	if (ptr && (minmax(obj, &minrow, &maxrow, &mincol, &maxcol) == 0))
X		value = *ptr;
X	if (sptr&&(markminmax(obj,MARK_SEE,&minrow,&maxrow,&mincol,&maxcol)==0))
X		value = *sptr;
X	return(sign * value);
X}
X
X
X/*
X * Return the value of a single character variable name (a-z or A-Z).
X */
Xgetvariable1(ch)
X	register int	ch;		/* variable character */
X{
X	if ((ch >= 'a') && (ch <= 'z'))
X		return(lowervars[ch - 'a']);
X	if ((ch >= 'A') && (ch <= 'Z'))
X		return(lowervars[ch - 'A']);
X	error("Bad variable name");
X}
X
X
X/*
X * Set the value of a variable name.  Multi-character names cannot be set.
X */
Xsetvariable(cp, value)
X	register char	*cp;		/* name of variable to set */
X{
X	if (*cp == '$') cp++;		/* skip any dollar sign */
X	if (cp[1] != '\0') {
X		error("Cannot set multi-character variables");
X	}
X	setvariable1(*cp, value);	/* do it */
X}
X
X
X/*
X * Set the value of a single-character variable (a-z or A-Z).
X */
Xsetvariable1(ch, value)
X	register int	ch;		/* variable character */
X{
X	if ((ch >= 'a') && (ch <= 'z')) {
X		lowervars[ch - 'a'] = value;
X		return;
X	}
X	if ((ch >= 'A') && (ch <= 'Z')) {
X		lowervars[ch - 'A'] = value;
X		return;
X	}
X	error("Bad variable name");
X}
X
X
X/*
X * Display the current values of the variables.  Show all multi-character
X * variable values, and those single character variables which have a
X * nonzero value.  The output is given three variables per line.
X */
Xlistvariables()
X{
X	register struct	vartab	*vp;		/* variable table pointer */
X	register long	*var;			/* simple variable pointer */
X	register int	count;			/* counter for formatting */
X
X	dpywindow(0, -1, 0, -1);
X	dpyprintf("Variable names and values:");
X	count = 0;
X	for (vp = vartab; vp->v_name; vp++) {
X		dpyprintf("%s%s\t%d", (count++ % 3) ? "\t\t" : "\n",
X			vp->v_name, getvariablebytype(vp->v_type));
X	}
X	count = 0;			/* create blank line */
X	for (var = uppervars; var < &uppervars[26]; var++) {
X		if (*var == 0) continue;
X		if (count == 0) dpychar('\n');
X		dpyprintf("%s%c\t%d", (count++ % 3) ? "\t\t" : "\n",
X			'A' + (var - uppervars), *var);
X	}
X	for (var = lowervars; var < &lowervars[26]; var++) {
X		if (*var == 0) continue;
X		if (count == 0) dpychar('\n');
X		dpyprintf("%s%c\t%d", (count++ % 3) ? "\t\t" : "\n",
X			'a' + (var - lowervars), *var);
X	}
X	dpyprintf("\n\nMany names starting with 'v' refer to visible cells\n");
X	dpyprintf("Many names starting with 's' refer to selected cells\n");
X	spacewait();			/* wait for space before clearing */
X}
X
X
X/*
X * Evaluate an expression and return its value.  The expression can
X * contain numbers, variables, the normal arithmetic operators, and
X * parenthesized expressions.  The usual precedence rules are used.
X */
Xgetexpression(str)
X	register char	*str;		/* string to parse */
X{
X	long	value;			/* resulting value */
X
X	while (*str == ' ') str++;
X	if (*str == '\0') error("Null expression");
X	curcp = str;
X	value = parsesum();
X	if (*curcp != '\0') error("Bad expression");
X	return(value);
X}
X
X
X/* Parse the sum of products */
Xparsesum()
X{
X	register long	value;		/* value to return */
X
X	while (*curcp == ' ') curcp++;
X	switch (*curcp) {		/* check for uninary operators */
X		case '-':
X			curcp++;
X			value = -parseproduct();
X			break;
X		case '+':
X			curcp++;
X			/* proceed into default case */
X		default:
X			value = parseproduct();
X	}
X	while (1) switch (*curcp++) {
X		case '+':		/* sum of products */
X			value += parseproduct();
X			continue;
X		case '-':		/* difference of products */
X			value -= parseproduct();
X			continue;
X		case ' ':		/* space */
X			continue;
X		default:		/* end of sum */
X			curcp--;
X			return(value);
X	}
X}
X
X
X/* Parse the product of terms */
Xparseproduct()
X{
X	register long	value;		/* value to return */
X	register long	value2;		/* temporary value */
X
X	value = parseterm();
X	while (1) switch (*curcp++) {
X		case '*':		/* product of terms */
X			value *= parseterm();
X			continue;
X		case '/':		/* division of terms */
X			value2 = parseterm();
X			if (value2 == 0) error("division by zero");
X			value /= value2;
X			continue;
X		case '%':		/* modulo of terms */
X			value2 = parseterm();
X			if (value2 == 0) error("division by zero");
X			value %= value2;
X			continue;
X		case ' ':		/* space */
X			continue;
X		default:		/* end of product */
X			curcp--;
X			return(value);
X	}
X}
X
X
X/* Parse a single term */
Xparseterm()
X{
X	register long	value;		/* value to return */
X	register int	ch;		/* current character */
X
X	while (*curcp == ' ') curcp++;
X	ch = *curcp;
X	if ((ch >= '0') && (ch <= '9')) {	/* number */
X		value = 0;
X		do
X			value = (value * 10) + *curcp++ - '0';
X		while ((*curcp >= '0') && (*curcp <= '9'));
X		return(value);
X	}
X	if (ch == '(') {			/* parenthesized expression */
X		curcp++;
X		while (*curcp == ' ') curcp++;
X		if (*curcp == ')') error("Null expression");
X		value = parsesum();
X		while (*curcp == ' ') curcp++;
X		if (*curcp != ')') error("Unmatched parenthesis");
X		*curcp++;
X		return(value);
X	}
X	if (ch == ')') error("Unmatched parenthesis");
X	return(parsename());
X}
X
X
X/*
X * Parse a variable name and return its value.
X */
Xparsename()
X{
X	register char	*cp;		/* current character */
X	register long	value;		/* value of variable */
X	char	oldch;			/* old character after name */
X
X	cp = curcp;
X	if (*cp == '$') cp++;
X	while (((*cp >= 'a') && (*cp <= 'z')) ||
X		((*cp >= 'A') && (*cp <= 'Z')) ||
X		((*cp >= '0') && (*cp <= '9'))) cp++;
X	oldch = *cp;
X	*cp = '\0';
X	value = getvariable(curcp);
X	*cp = oldch;
X	curcp = cp;
X	return(value);
X}
//E*O*F vars.c//
echo x - view.c
sed -e 's/^X//' > "view.c" << '//E*O*F view.c//'
X#ifdef SCCS
Xstatic char *sccsid = "@(#)view.c	1.5	2/2/85";
Xstatic char *cpyrid = "@(#)Copyright (C) 1985 by D Bell";
X#endif
X
X#include "life.h"
X
X
X/*
X * Set the scaling factor for the specified object.  This also centers
X * the view around the current cursor location.
X */
Xsetscale(obj, sf)
X	register struct	object	*obj;	/* object to set scale of */
X	register int	sf;		/* scaling factor */
X{
X	if (sf <= 0) sf = 1;
X	if (sf > MAXSCALE) sf = MAXSCALE;
X	obj->o_scale = sf;
X	obj->o_minrow = obj->o_currow - (rowradius * sf) + (sf / 2);
X	obj->o_maxrow = obj->o_minrow + (2 * rowradius * sf);
X	obj->o_mincol = obj->o_curcol - (colradius * sf) + (sf / 2);
X	obj->o_maxcol = obj->o_mincol + (2 * colradius * sf);
X	if (obj == curobj) redraw = 1;		/* update if object visible */
X}
X
X
X/*
X * Perform auto-scaling of the current object.  This implies picking a
X * scaling factor such that the whole object fits in the screen.  The
X * scale factor is never decreased.  When the scale factor is large,
X * convenient ones are picked.  Returns the new scale factor.
X */
Xautoscale()
X{
X	register struct	object	*obj;		/* current object */
X	register int	sf;			/* scaling factor */
X	int	minrow, maxrow, mincol, maxcol;	/* limits of object */
X
X	obj = curobj;
X	minmax(obj, &minrow, &maxrow, &mincol, &maxcol);
X	sf = obj->o_scale;
X	if (mincol > maxcol) return(sf);
X	while ((sf <= MAXSCALE) &&
X		((minrow < obj->o_minrow) || (maxrow > obj->o_maxrow) ||
X		(mincol < obj->o_mincol) || (maxcol > obj->o_maxcol))) {
X			sf++;
X			if (sf > 20) sf += (5 - (sf % 5));
X			if (sf > 50) sf += (10 - (sf % 10));
X			if (sf > 200) sf += (100 - (sf % 100));
X			setscale(obj, sf);
X	}
X	return(obj->o_scale);
X}
X
X
X/*
X * Position the view of the current object to show both the given region and
X * the current cursor location.  If this is impossible, just the cursor
X * location will be positioned.
X */
Xpositionview(minrow, maxrow, mincol, maxcol)
X	register long	minrow, maxrow, mincol, maxcol;	/* region to show */
X{
X	register struct	object	*obj;	/* current object */
X	register int	sf;		/* current scale factor */
X
X	obj = curobj;
X	sf = obj->o_scale;
X	if (minrow > obj->o_currow) minrow = obj->o_currow;
X	if (maxrow < obj->o_currow) maxrow = obj->o_currow;
X	if (mincol > obj->o_curcol) mincol = obj->o_curcol;
X	if (maxcol < obj->o_curcol) maxcol = obj->o_curcol;
X	if ((maxrow - minrow) > (2 * sf * rowradius)) {	/* too many rows */
X		minrow = obj->o_currow;
X		maxrow = obj->o_currow;
X	}
X	if ((maxcol - mincol) > (2 * sf * colradius)) {	/* too many columns */
X		mincol = obj->o_curcol;
X		maxcol = obj->o_curcol;
X	}
X	if (minrow < obj->o_minrow) {
X		obj->o_minrow = minrow;
X		obj->o_maxrow = minrow + (rowradius * sf * 2) + sf - 1;
X		redraw = 1;
X	}
X	if (maxrow > obj->o_maxrow) {
X		obj->o_maxrow = maxrow;
X		obj->o_minrow = maxrow - (rowradius * sf * 2) + sf - 1;
X		redraw = 1;
X	}
X	if (mincol < obj->o_mincol) {
X		obj->o_mincol = mincol;
X		obj->o_maxcol = mincol + (colradius * sf * 2) + sf  - 1;
X		redraw = 1;
X	}
X	if (maxcol > obj->o_maxcol) {
X		obj->o_maxcol = maxcol;
X		obj->o_mincol = maxcol - (colradius * sf * 2) + sf - 1;
X		redraw = 1;
X	}
X}
X
X
X/*
X * Show the view around the current window location if the view has changed.
X * The update flag indicates that the status line and cursor need updating.
X * The redraw flag indicates that the view of the cells also needs updating.
X */
Xupdateview()
X{
X	register struct	object	*obj;		/* current object */
X
X	if ((interact | redraw | update) == 0) return;
X	obj = curobj;
X	positionview(obj->o_currow,obj->o_currow,obj->o_curcol,obj->o_curcol);
X	if (obj->o_autoscale) autoscale();
X	if (redraw) {			/* show visible cells */
X		freqcount = frequency;
X		dpywindow(1, -1, 0, -1);
X		if (obj->o_scale <= 1)
X			viewnormal();
X		else
X			viewscale(obj->o_scale);
X		dpyclearwindow();
X	}
X	if (redraw || update) {		/* show status and position cursor */
X		viewstatus();
X		dpywindow(1, -1, 0, -1);
X		dpymove((obj->o_currow - obj->o_minrow) / obj->o_scale,
X			(obj->o_curcol - obj->o_mincol) / obj->o_scale);
X		dpyupdate();
X	}
X	update = 0;			/* no more updates until prodded */
X	redraw = 0;
X	interact = 0;
X}
X
X
X
X/*
X * Update the status line for the object.
X */
Xviewstatus()
X{
X	register struct	object	*obj;	/* current object */
X
X	dpywindow(0, 0, 0, -1);		/* output in top line */
X	if (errorstring) {		/* show error string if present */
X		dpystr(errorstring);
X		dpyclearline();
X		return;
X	}
X	obj = curobj;
X	dpyprintf("Gen:%d cells:%d", obj->o_gen, obj->o_count);
X	if (obj->o_count > seecount)
X		dpyprintf("(%du)", obj->o_count - seecount);
X	if (obj->o_born) dpyprintf(" born:%d", obj->o_born);
X	if (obj->o_died) dpyprintf(" died:%d", obj->o_died);
X	if (frequency > 1) dpyprintf(" freq:%d", frequency);
X	if ((obj->o_scale > 1) || (obj->o_autoscale))
X		dpyprintf(" %scale:%d",
X			(obj->o_autoscale ? "autos" : "s") , obj->o_scale);
X	if (strcmp(rulestring, "3,23")) dpyprintf(" rules:%s", rulestring);
X	if (obj->o_lock) dpystr(" locked");
X	if (curinput > inputs) dpyprintf(" cmd-nest:%d", curinput - inputs);
X	switch (curinput->i_type) {
X		case INP_TTY:			/* reading from terminal */
X			if (curinput != inputs) dpystr(" tty-wait");
X			break;
X		case INP_FILE:			/* reading from file */
X			dpystr(" cmd-file");
X			break;
X		case INP_LOOP:			/* reading from loop */
X			if (curinput->i_macro) {
X				dpyprintf(" macro-define-%c",curinput->i_macro);
X				break;
X			}
X			dpyprintf(" loop%s (curval:%d end:%d)",
X				curinput->i_first ? "-define" : "",
X				curinput->i_curval, curinput->i_endval);
X			break;
X		case INP_MACRO:			/* reading from macro */
X			dpyprintf(" macro-%c", curinput->i_macro);
X			break;
X	}
X	if (mode == M_INSERT) dpystr(" inserting");
X	if (mode == M_DELETE) dpystr(" deleting");
X	if (curobj != mainobject) dpyprintf(" \"%s\"", curobj->o_name);
X	dpyclearwindow();
X}
X
X
X/* Show the cells around the cursor normally (scale factor of 1) */
Xviewnormal()
X{
X	register struct	row	*rp;		/* current row */
X	register struct	cell	*cp;		/* current cell */
X	register int	row;			/* current row number */
X	register int	col;			/* current column number */
X	register char	*str;			/* characters for line */
X	register char	*endstr;		/* end of characters for line */
X	register struct	object	*obj;		/* current object */
X
X	obj = curobj;
X	rp = obj->o_firstrow;
X	row = obj->o_minrow;
X	seecount = 0;
X	while (row > rp->r_row) rp = rp->r_next;
X	for (; row <= obj->o_maxrow; row++) {
X		if (row != rp->r_row) {		/* blank row */
X			if (gridchar == ' ') {
X				dpychar('\n');
X				continue;
X			}
X			str = stringbuf;
X			for (col = obj->o_mincol; col <= obj->o_maxcol; col++) {
X				*str++ = gridchar;
X			}
X			*str++ = '\n';
X			dpywrite(stringbuf, str - stringbuf);
X			continue;
X		}
X		str = stringbuf;
X		endstr = str;
X		cp = rp->r_firstcell;
X		col = obj->o_mincol;
X		while (col > cp->c_col) cp = cp->c_next;
X		for (; col <= obj->o_maxcol; col++) {
X			if (col != cp->c_col) {		/* blank cell */
X				*str++ = gridchar;
X				if (gridchar != ' ') endstr = str;
X				continue;
X			}
X			*str = '#';
X			if ((cp->c_marks & MARK_SEE) == 0) *str = 'O';
X			endstr = ++str;
X			seecount++;
X			cp = cp->c_next;
X		}
X		*endstr++ = '\n';
X		dpywrite(stringbuf, endstr - stringbuf);
X		rp = rp->r_next;
X	}
X}
X
X
X/*
X * Show the view around the cursor with an arbitrary scale factor.
X * When in this mode, characters from 1 to 9 (or * if 10 or more)
X * are used to indicate how many cells are in each n by n square.
X */
Xviewscale(sf)
X	register int	sf;		/* scale factor */
X{
X	register int	row;		/* current row number */
X	register int	col;		/* current column number */
X	register int	sum;		/* number of cells in square */
X	register struct	cell	*cp;	/* current cell structure */
X	register struct	object	*obj;	/* current object */
X	struct	cell	**cpp;		/* pointer into cell table */
X	struct	cell	**endcpp;	/* end of cell table */
X	struct	row	*rp;		/* row pointer */
X	char	*str;			/* buffer pointer */
X	char	*endstr;		/* end of buffer */
X	struct	cell	*cptab[MAXSCALE];	/* table of rows */
X
X	obj = curobj;
X	row = obj->o_minrow;
X	col = obj->o_mincol;
X	endcpp = &cptab[sf];
X	seecount = 0;
X	for (rp = curobj->o_firstrow; (rp->r_row < row); rp = rp->r_next) ;
X	while (row <= obj->o_maxrow) {
X		/*
X		 * If there is a large gap to the next row number then
X		 * the terminal line is empty.
X		 */
X		if (rp->r_row >= (row + sf)) {		/* no rows here */
X			if (gridchar == ' ') {
X				dpychar('\n');
X				row += sf;
X				continue;
X			}
X			str = stringbuf;
X			for (col=obj->o_mincol; col<=obj->o_maxcol; col+=sf) {
X				*str++ = gridchar;
X			}
X			*str++ = '\n';
X			dpywrite(stringbuf, str - stringbuf);
X			row += sf;
X			continue;
X		}
X		/*
X		 * Collect the rows to be searched for one terminal line.
X		 * Dummy up empty rows if necessary.
X		 */
X		for (cpp = cptab; cpp < endcpp; cpp++) {
X			*cpp = termcell;
X			if (rp->r_row > row++) continue;
X			*cpp = rp->r_firstcell;
X			rp = rp->r_next;
X		}
X		str = stringbuf;
X		endstr = str;
X		/*
X		 * Advance along each row to the next range of columns,
X		 * adding cells found to get the result for each square.
X		 */
X		for (col = obj->o_mincol; col <= obj->o_maxcol; col += sf) {
X			sum = 0;
X			for (cpp = cptab; cpp < endcpp; cpp++) {
X				cp = *cpp;
X				while (col > cp->c_col) cp = cp->c_next;
X				while ((col + sf) >= cp->c_col) {
X					sum++;
X					cp = cp->c_next;
X				}
X				*cpp = cp;
X			}
X			if (sum == 0) {		/* no cells in square */
X				*str++ = gridchar;
X				if (gridchar != ' ') endstr = str;
X				continue;
X			}
X			*str = '*';		/* show number of cells */
X			if (sum <= 9) *str = '0' + sum;
X			endstr = ++str;
X			seecount += sum;
X		}
X		*endstr++ = '\n';
X		dpywrite(stringbuf, endstr - stringbuf);
X	}
X}
//E*O*F view.c//
echo done

dbell@daisy.UUCP (David I. Bell) (02/03/85)

This article contains a collection of interesting life objects readable by
the life program which I just posted.  They should be put into the directory
/usr/games/lib/life.  Then the program can easily find them for anyone.

Most of these objects were gotten from a couple of issues of the "Lifeline"
magazine by Robert T. Wainwright, which existed around 1971.  A few objects
are gotten from Martin Gardner's book, "Wheels, Life, and other Amusements".
And finally a few (very few) objects are mine.

If you know of or find any other objects which are interesting, I would like
to have them to add to the library.  Thank you!

#---Cut here and place in it's own directory, then feed to Bourne shell---
# This is a shell archive.  Remove anything before this line, then
# unpack it by saving it in a file and typing "sh file".  (Files
# unpacked will be owned by you and have default permissions.)
# This archive contains:
#   acorn.l (7898 chars)
#   bigpuff.l (6222 chars)
#   blinkership.l (1156 chars)
#   eatblinker.l (777 chars)
#   escorts.l (348 chars)
#   gun1.l (590 chars)
#   gun2.l (850 chars)
#   puffer.l (328 chars)
#   pulsars.l (1783 chars)
#   rake.l (697 chars)
#   relay.l (498 chars)
#   round.l (2856 chars)
#   rpent.l (2092 chars)
#   shipgun.l (2431 chars)
#   shuttles.l (889 chars)
#   switch.l (588 chars)
#
echo x - acorn.l
sed -e 's/^X//' > "acorn.l" << '//E*O*F acorn.l//'
X! "acorn" (cells 640 length 2497 width 2325 generation 5206)
X! The cursor is positioned on the 7-bit starting pattern called the acorn.
X! This pattern evolves into the configuration shown around it in 5206 moves.
X1248k1126h@!
X2302.OO
X2302..OO
X2302.O
X173
X62..OO
X62.OO
X62...O
X248
X2012..OO
X2012.O.O
X2012...O
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X1831.OOO
X1831...O
X1831..O
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X415..OO
X415.OO
X415...O
X220
X656..O
X656.OO
X656.O.O
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X1399..O
X1399..OO
X1399.O.O
X198
X1002..O
X1002.OO
X1002.O.O
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X1002.........................................................................OO
X1002.........................................................................O.O
X1002..........................................................................OO
X.
X.
X.
X.
X.
X.
X.
X.
X1002................................................................OO
X1002................................................................OO
X.
X.
X.
X.
X.
X.
X1002....................................................................OO
X1002....................................................................OO
X.
X.
X1002.................................OOO
X.
X.
X.
X.
X.
X1010........................................................................O
X1010.......................................................................O.O
X1010.......................................................................O.O
X1010...............................................................OO.......O
X1010..................................................OO..........O..O
X1010...............................OO.................OO...O.......OO
X1010...............................OO.....................O.O
X1010......................................................O.O
X1010.......................................................O...........OO
X1010..................OO...............................................O.O
X1010.................O..O...............................................O
X1010..................OO
X.
X.
X1010...........................O
X1010..OO......................O.O
X1010.O..O.....................O..O
X1011.OO.......................OO.................................................OOO
X.
X.
X.
X1011..............................................................................OO
X1011......................................................O.......................OO
X1011.....................................................O.O
X1011....................................OO...............O.O
X1011...................................O..O...............O
X1011....................................OO
X.
X1011................................O.................O
X1011...............................O.O................O
X1041.O.O................O...............................OO
X1041..O.................................................OO
X.
X.
X1057......................................................................O
X1057..O..................................................................O.O
X1057.O.O....................OO............................................OO
X1028.O............................O..O..................O..O......................OO
X1027.O.O28.OO19.O.O23.OO8.3O
X1027.O.O..................................................O
X1028.O97.O
X1033.O25.OO23.OO39.O.O
X1032.O.O24.OO23.OO40.OO
X1032.O.O
X1033.O80.3O
X1090................OO
X1090.OO.............OO
X1090.OO....................................................O
X1090.......................................................O...................OO
X1090.......................................................O...................OO
X1042.OO81.O
X1011.3O28.OO80.O.O
X1125.OO.............................OO
X1009.O5.O140.OO
X1009.O5.O95.O
X1009.O5.O41.O52.O.O
X1056.O.O...................................................O.O
X1011.3O43.OO52.O63.OO
X1174.O.O
X1174.OO
X1045..O..........................................................................OO
X1045.O.O...............OOO......................................................O..O
X1045.O.O......................................O.................................O..O
X1045..O......................................O.O......................OO.........OO
X1045.........................................O..O.....................OO
X1045..........................................O.O
X1045..........OO........OO.....................O
X1045..........OO........OO
X1045............................OO....OO
X1045............................OO....OO
X.
X1045........OO.......................................O
X1045........OO......................................O.O
X1026.O46.OO17.OO48.OO
X1026.O46.OO67.OO
X1026.O
X.
X.
X1084.OO........OOO..................................................O
X1015.3O17.O11.O35.O.O3.O57.O
X1026.O7.O.O10.O36.O4.O57.O
X1025.O.O.......OO..........O.........................................O
X1025.O.O83.OO12.OO7.OO13.3O
X1026.O84.OO6.OO3.O..O5.O..O
X1043.O74.O..O3.OO7.OO
X1021.OO7.OO10.O.O73.O..O
X1020.O..O5.O..O9.O.O34.O39.OO9.O
X1021.OO7.OO11.O34.O.O48.O.O
X1078.O.O................................................O.O....................O
X1026.O52.O50.O14.O5.O.O
X1025.O.O117.O5.O.O
X1025.O.O117.O6.O
X1026.O
X1039.O80.OO
X1038.O.O79.OO
X1039.O.O
X1039..O
X1045.O..........................................................................OO
X1034.O10.O74.OO24.OO
X1033.O.O9.O8.O69.O21.OO
X1034.O.O17.O58.OO11.O
X1035.O18.O58.OO8.OO..3O4.OO
X1088.OO............................................OO
X1050.OOO...OOO.............................OO..................OOO
X1149.O
X1054.O93.O.O
X1054.O93.O.O
X1054.O94.O
X.
X.
X1056.O
X1056.O.......OO
X1056.O......O..O................................O
X1064.OO9.OO16.OO4.O73.3O
X1052.OOO...OOO..............OO................OO....O
X1070.O79.O
X1056.O12.O.O77.O.O
X1056.O12.O.O77.O.O
X1056.O13.O79.O
X.
X1089....................................OOO...........................OO
X1089.................................................................O..O
X1089..O...............................................................OO
X1089.O.O
X1089.O.O........................................................O
X1089..O.........................................................O
X1089...............................O............................O
X1089...............................O
X1089...............................O......................O.......OOO
X1089..........................................OO..........O
X1089.........................................O..O.........O.....O
X1057.OO71.OO16.O
X1056.O..O88.O
X1048..........O.O..........OO............OO
X1048...........O...........OO............OO
X.
X.
X.
X.
X.
X.
X.
X1048.OO.......................................OO
X1048.OO......................................O..O
X1048..........................................O.O
X1048...........................................O
X.
X.
X.
X1048..............................................OOO
X.
X1048............................................O.....O
X1048............................................O.....O
X1048..........................O.................O.....O
X1048.........................O.O
X1048.........................O.O..................OOO
X1048..........................O
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X1048....................................................OO
X1048....................................................O.O
X1048.....................................................OO
X576
X1788.O
X1788..OO
X1788.OO
X128
X326.O.O
X326.OO
X326..O
X148
X1979.O
X1979..OO
X1979.OO
X241
XO.O
XOO
X.O
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X2322...O
X2322.O.O
X2322..OO
//E*O*F acorn.l//
echo x - bigpuff.l
sed -e 's/^X//' > "bigpuff.l" << '//E*O*F bigpuff.l//'
X! "bigpuff" (cells 2115 length 556 width 1074 generation 0)
X! This shows what a typical dirty puffer train is like.  This object
X! started with only the engine and spaceships at the front (the right).
X! This is the result after it has settled down after about 1600 generations.
X! Try examining it at a scale of 8 to see its structure.  The random stuff
X! at the left is the "startup" exhaust, before it established its period.
X279k1073h@!
XOO
XO.O
XO
X48
X250.OO
X249.OO
X251.O
X15
X304.OO
X303.OO
X305.O
X15
X358.OO
X357.OO
X359.O
X15
X412.OO
X411.OO
X413.O
X15
X466.OO
X465.OO
X467.O
X15
X520.OO
X519.OO
X521.O
X15
X574.OO
X573.OO
X575.O
X15
X628.OO
X627.OO
X629.O
X15
X682.OO
X681.OO
X683.O
X8
X375.OO
X366.O8.OO
X366.O
X366.O
X3
X736.OO
X735.OO
X344.O392.O
X343.O.O
X343.O.O
X344.O27.O
X372.O
X339.OO7.OO22.O
X338.O..O5.O..O484.OO
X339.OO7.OO18.3O3.3O453.O.O.O
X835.OO
X344.O484.O3.5O
X343.O.O487.O..O.O
X343.O.O484.O5.O.O
X344.O486.OO
X826.O
X788.3O34.O.O
X789.O35.O.O..OO4.OO
X789.O36.O5.O3.O.O
X447.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO14.O.O17.OO21.OO5.O.O
X446.O..O32.O..O32.O..O32.O..O32.O..O32.O..O32.O..O32.O..O32.O..O32.O..O13.O.O16.O..O28.OO
X333.O113.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO29.O
X332.O.O453.OO48.O.O
X332.O.O452.OO38.OO10.OO
X333.O78.OO373.3O37.OO10.O3.3O21.O
X412.OO428.O..O21.O41.O
X345.3O440.O.OO49.OO3.O19.3O40.OO
X412.OO375.OO49.O6.O
X412.OO428.O.O.O17.O5.O34.O4.O.O
X765.O3.O75.O16.O9.O31.O5.O.O
X765.O..OO71.OO18.OO4.O4.OO30.OO
X349.O97.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO59.OO45.O16.OO4.3O4.OO34.3O
X348.OO74.OO20.O..O4.OO26.O..O4.OO26.O..O4.OO26.O..O4.OO26.O..O4.OO26.O..O4.OO26.O..O4.OO26.O..O4.OO26.O..O4.OO21.OO.OO8.OO15.O..O3.O.O9.OO25.OO18.O..3O.3O..O5.OO25.O3.O4.OO
X324.O23.OO73.O..O20.OO5.OO27.OO5.OO27.OO5.OO27.OO5.OO27.OO5.OO27.OO5.OO27.OO5.OO27.OO5.OO27.OO5.OO25.O8.OO15.O.O..3O.OO8.OO46.OO.O3.O.OO6.OO25.OO7.OO
X323.O.O22.O75.OO335.OO5.OO26.O..O4.O29.O28.OO5.OO34.O3.O26.3O5.O
X323.O.O434.O7.OO33.OO28.O.O71.OO.OO24.O3.O3.O.O.O.O
X324.O29.OO44.O359.O7.O29.O3.OO12.OO14.O3.O74.O24.OO.OO..O..O3.O
X353.O..O17.OO23.O.O13.O351.O30.OO33.O.OO68.O3.O32.OO
X354.O.O16.O.O23.O.O12.O.O344.O3.OO31.OO35.O30.OO22.OO11.4O..O33.O.6O
X355.O5.O11.OO25.O12.O..O345.O36.O35.O.O28.OO20.OO..O5.OO.O.OO3.O41.O
X360.O.O51.OO383.4O31.OO.OO48.O..O.O4.O..O3.3O37.O..4O
X359.O..O404.3O30.O..OO28.O52.OO5.O3.O..O5.O37.O3.O.O
X360.OO37.OO400.O..O.O26.O.OO26.3O21.O..O..O.OO.O7.O28.O14.O26.OO
X399.OO40.O5.O29.O5.O29.O5.O29.O5.O29.O5.O29.O5.O29.O5.O29.O5.O29.O5.O29.O5.O31.O3.OO26.O11.OO14.O..O21.3O..4O.O5.OO27.OO.OO11.O..O23.O..O
X389.OO50.O5.O29.O5.O29.O5.O29.O5.O29.O5.O29.O5.O29.O5.O29.O5.O29.O5.O29.O5.O36.O24.O.O11.OO12.O4.O24.O.3O3.O4.O.O25.O3.OO7.3O4.O22.OO..O
X364.OO23.OO50.O5.O29.O5.O29.O5.O29.O5.O29.O5.O29.O5.O29.O5.O29.O5.O29.O5.O29.O5.O32.O4.O23.OO7.O18.O.4O11.O18.3O14.O17.O5.O7.OO..3O23.O.O.OO
X364.OO5.OO88.3O33.3O33.3O33.3O33.3O33.3O33.3O33.3O33.3O33.3O21.O11.3O17.O.O17.O.OO12.O.O33.O.O19.OO7.O29.O.O..OO.O
X371.OO70.3O33.3O33.3O33.3O33.3O33.3O33.3O33.3O33.3O33.3O34.O3.O32.OO17.OO15.OO34.OO17.O.6O4.3O26.O..O4.3O
X423.O41.O35.O35.O35.O35.O35.O35.O35.O35.O35.O15.3O17.O106.O.O3.O5.OO3.3O30.3O
X423.O41.O35.O35.O35.O35.O35.O35.O35.O35.O35.O35.O107.3O9.O.3O22.3O6.O.O
X328.OO61.OO12.O12.O4.O41.O35.O35.O35.O35.O35.O35.O35.O35.O35.O35.O107.O..OO12.O19.OO..O..O4.O
X328.OO61.OO12.O11.O.O529.O15.OO3.O4.O.O
X405.O12.OO512.O11.O4.O15.O.O.O..O
X932.5O.4O23.3O..O5.O.O
X939.OO5.O19.O8.O.OO
X316.OO620.O6.O19.O4.O6.O33.O46.O..O
X316.OO614.O.7O3.O21.O9.O..O31.O50.O
X415.O514.O6.OO.O.OO28.4O..3O29.O.O45.O3.O
X414.O.O50.O35.O35.O35.O35.O35.O35.O35.O35.O35.O35.O35.O35.O36.3O32.O5.O30.O.O.O6.OO38.4O
X414.O..O48.O.O33.O.O33.O.O33.O.O33.O.O33.O.O33.O.O33.O.O33.O.O33.O.O33.O.O33.O.O33.O.O29.O3.OO3.3O28.O.O4.O.O29.OO.O6.OO
X366.OO47.OO13.3O33.O.O33.O.O33.O.O33.O.O33.O.O33.O.O33.O.O33.O.O33.O.O33.O.O33.O.O33.O.O33.O.O30.3O36.O.O..O.O30.3O40.OO
X342.OO22.OO99.O35.O35.O35.O35.O35.O35.O35.O35.O35.O35.O35.O35.O71.O4.O32.O3.O8.O28.O..OO13.O..O
X342.OO3.OO79.O5.O573.OO11.O.O21.O10.O16.O
X330.OO.OO11.O..O41.O26.3O7.O5.O573.O.O9.O22.OO3.O20.O3.O
X330.OO.OO12.OO41.O.O35.O5.O19.O35.O35.O35.O35.O35.O35.O35.O35.O35.O35.O35.O35.O35.O86.O9.OO..O18.O4.OO4.O16.4O
X391.OO60.O.O9.3O21.O.O9.3O21.O.O9.3O21.O.O9.3O21.O.O9.3O21.O.O9.3O21.O.O9.3O21.O.O9.3O21.O.O9.3O21.O.O9.3O21.O.O9.3O21.O.O9.3O21.O.O9.3O21.O.O9.3O36.O47.OO..O16.O3.O3.O3.O7.OO..3O
X430.3O20.O.O33.O.O33.O.O33.O.O33.O.O33.O.O33.O.O33.O.O33.O.O33.O.O33.O.O33.O.O33.O.O33.O.O34.OO11.O.O20.OO16.O.O25.O3.O3.O4.O3.O3.OO..3O
X454.O35.O35.O35.O35.O35.O35.O35.O35.O35.O35.O35.O35.O35.O35.O.O10.O.O10.O9.O.O30.O12.O..O5.O3.O3.O3.OO..3O
X366.O491.OO34.OO34.OO27.O.O7.4O10.O.O9.O.O10.OO..O.O11.O3.O9.O7.OO..4O14.4O
X365.O.O490.OO34.OO34.OO28.O8.3O11.O.O10.O16.O6.3O..OO3.O11.OO.OO.O3.3O.O12.O3.O
X365.OO601.O.O13.O24.O10.OO3.OO.O.O11.6O13.OO10.O
X336.OO105.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO60.O..O13.OO.O14.3O13.4O5.O..O
X336.OO75.OO28.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO61.OO8.6O..O31.OO.OO
X413.OO559.OO42.O3.OO4.O33.OO
X974.OO42.O4.O
X864.OO157.3O
X864.O.O49.4O97.O.O.O..O
X864.O51.OO..O30.OO34.OO28.O5.O
X916.O.OO31.OO34.OO30.3O
X442.OO575.3O
X372.OO34.OO32.OO36.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO
X372.OO34.OO70.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO
X4
X987.O
X987.OO
X986.O.O
X4
X328.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO12.OO20.OO34.OO
X328.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO12.O.O19.OO34.OO
X918.O
X937.O
X936.O..O
X935.OO41.OO
X934.O5.O37.O..O
X930.12O36.O..O
X324.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO26.OO.O.OO..O40.OO.OO
X324.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO24.OO3.O..3O42.OO.OO
X926.3O.OO.3O42.OO3.OO
X931.O..O15.3O25.O..3O
X932.OO23.OO19.O
X950.O6.O.O19.O.O
X951.O4.O..O20.O
X947.O10.O
X948.O
X1
X334.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO
X333.O.O33.O.O33.O.O33.O.O33.O.O33.O.O33.O.O33.O.O33.O.O33.O.O33.O.O33.O.O33.O.O33.O.O33.O.O33.O.O33.O.O33.O
X333.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO34.OO35.O
X946.O
X225
X34.O
X34.O.O
X34.OO
//E*O*F bigpuff.l//
echo x - blinkership.l
sed -e 's/^X//' > "blinkership.l" << '//E*O*F blinkership.l//'
X! "blinkership" (cells 72 length 17 width 30 generation 0)
X! This is a puffer train which leaves no permanent exhaust, and yet grows
X! arbitrarily large.  The engine and spaceships to the right of the blinker
X! produce a new blinker every 12 generations.  Without the object to the left
X! of the blinker (a predecessor to traffic lights), this puffer train would
X! just leave behind an infinite line of blinkers.  However, the object reacts
X! with the blinkers with a period of 26, and totally destroys them.  The speed
X! of the reaction is 6/13 the speed of light, which is slightly slower than the
X! rest of the spaceship.  Therefore, every 156 generations the puffer train
X! grows in size by one blinker.
X! 
X8k29h@!
X...............O..O
X...................O
X...............O...O
X................OOOO......OO
X.........................OOOO
X.........................OO.OO
X................OOO.OO.....OO
XOOO....O.......OO.....O
XO.O....O......O.......OO
XOOO....O.......OO.....O
X................OOO.OO.....OO
X.........................OO.OO
X.........................OOOO
X................OOOO......OO
X...............O...O
X...................O
X...............O..O
//E*O*F blinkership.l//
echo x - eatblinker.l
sed -e 's/^X//' > "eatblinker.l" << '//E*O*F eatblinker.l//'
X! "eatblinker" (cells 54 length 21 width 51 generation 0)
X! This shows a reaction in which a set of traffic lights is tugged around by
X! a string of blinkers.  The traffic lights eats each blinker it encounters,
X! leaving no debris.
X10k26h@!
X................OOO
X.
X..............O.....O..........O...............O
X..............O.....O.OOO......O......OOO......O
X..............O.....O..........O...............O
XOOO
X................OOO
X.
X.................................................O
X.................................................O
X.................................................O
X.
X.O
X.O
X.O
X.
X.
X................................................OOO
X...O...............O...............O
X...O......OOO......O......OOO......O......OOO
X...O...............O...............O
//E*O*F eatblinker.l//
echo x - escorts.l
sed -e 's/^X//' > "escorts.l" << '//E*O*F escorts.l//'
X! "escorts" (cells 57 length 16 width 15 generation 0)
X! This shows the largest overweight spaceship which can be safely escorted
X! by only two other spaceships.
X8k14h@!
X.......OOOO
X......OOOOOO
X......OOOO.OO
X..........OO
X.
X..OO
XO............O
X..............O
XO.............O
X.OOOOOOOOOOOOOO
X.
X.
X.......OOOO
X......OOOOOO
X......OOOO.OO
X..........OO
//E*O*F escorts.l//
echo x - gun1.l
sed -e 's/^X//' > "gun1.l" << '//E*O*F gun1.l//'
X! "gun1" (cells 44 length 12 width 36 generation 30)
X! This is the original glider gun, which creates a glider every 30 generations.
X! This was the first object found which grew without bound.  It is composed of
X! two identical but phase-shifted shuttle elements which collide properly to
X! produce a glider.
X11k10h@!
X............O
X............O.O
XOO...........O.O.......O
XOO...........O..O......OO
X.............O.O..OO....OO
X............O.O...OO....OOO.......OO
X............O.....OO....OO........OO
X.......................OO
X.......................O
X...........O
X..........O
X..........OOO
//E*O*F gun1.l//
echo x - gun2.l
sed -e 's/^X//' > "gun2.l" << '//E*O*F gun2.l//'
X! "gun2" (cells 61 length 34 width 41 generation 46)
X! This is the "newgun", which creates a glider every 46 generations.  It is
X! composed of two "busy bee" shuttles which are positioned at right angles
X! to each other, and which collide properly to produce a glider.   The cursor
X! is positioned at the approximate location where each glider is created.
X! One glider is shown having already been generated.
X10k7h@!
X.......................O
X............OO........OO...............OO
X............OO.......OO................OO
X.O....................OO..OO
XOO
XO.O
X.
X......................OO..OO
X.....................OO................OO
X......................OO...............OO
X.......................O
X.
X.
X.
X.
X.......OOO...OOO
X.......O..O.O..O
X.......O.......O
X.
X........O.....O
X.........OO.OO
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.......OO.....OO
X.......OO.....OO
//E*O*F gun2.l//
echo x - puffer.l
sed -e 's/^X//' > "puffer.l" << '//E*O*F puffer.l//'
X! "puffer" (cells 43 length 17 width 14 generation 0)
X! This is one of the simplest puffer trains.  It generates a beehive every
X! 12 generations.
X8k13h@!
X.OOOO
XO...O
X....O
XO..O
X..........OOOO
X.........O...O
X.............O
X.....OO..O..O
X.....OOO
X.....OO..O..O
X.............O
X.........O...O
X..........OOOO
XO..O
X....O
XO...O
X.OOOO
//E*O*F puffer.l//
echo x - pulsars.l
sed -e 's/^X//' > "pulsars.l" << '//E*O*F pulsars.l//'
X! "pulsars" (cells 237 length 23 width 75 generation 0)
X! This is a collection of well known (and not so well known) oscillators.
X! Their periods are 2, 3, 4, 5, 6, 8, 14, and 15.  I let you figure out
X! which is which!
X11k39h@!
X...O...........OO
X..O.O..........OO..............................OOO...OOO.............OO
X......................................................................O
X.OO...O........O.............................O....O.O....O.......OOO..OO
X.O.....O......O.O..............O....O........O....O.O....O.....OOO.O...O
X....O.O.......O..O..OO.......OO.OOOO.OO......O....O.O....O.....O....O.OO
X...OO.............O.OO.........O....O..........OOO...OOO...........O.O
X................OO...............................................OO.O....O
X...............................................OOO...OOO.........O...O.OOO
X.............................................O....O.O....O.......OO..OOO
XOO...........O...............................O....O.O....O........O
XOO.........O.O.......OOO.....................O....O.O....O........OO
X..OO........O.O.......OOO
X..OO........O..................................OOO...OOO
X.............................................................O
X.......O...........................OOO.....................OOO.......O..O
X.......O.O.........................OOO....................O..........O..O
X.....O.............OO...OO.........OOO...........O........OO.......OO.OO.OO
X..........OO.......O.O.O.O............OOO........O...................O..O
X....OO.............O.O.O.O............OOO........O......OO...........O..O
X..........O..........O.O..............OOO................O.........OO.OO.OO
X......O.O...........OO.OO.............................OOO............O..O
X........O...........OO.OO.............................O..............O..O
//E*O*F pulsars.l//
echo x - rake.l
sed -e 's/^X//' > "rake.l" << '//E*O*F rake.l//'
X! "rake" (cells 63 length 19 width 26 generation 20)
X! This is the "space rake", which is a puffer train which generates a
X! dense stream of forward-traveling gliders.  A new glider is generated
X! every 20 generations.  The debris near the rearward spaceship is
X! transformed into a glider.
X9k23h@!
X...............OO.....OOOO
X.............OO.OO...O...O
X.............OOOO........O
X..............OO.....O..O
X.
X....................O
X...............O...O..O
X..............OO.......O
X...............OO......O
X................OO....OO
XO.O..................O
XO..O..............OO
X.
X.OOO
X......................OOOO
X....O..O.............O...O
X........O................O
X....O...O............O..O
X.....OOOO
//E*O*F rake.l//
echo x - relay.l
sed -e 's/^X//' > "relay.l" << '//E*O*F relay.l//'
X! "relay" (cells 29 length 7 width 35 generation 0)
X! This shows a glider which travels back and forth between two pentadecathlons.
X! The distance between the endpoints can be made arbitrarily large.  This is
X! one of three known "shuttles", but is isn't as useful as the others (which
X! form the engines of the two kinds of glider guns).
X4k17h@!
X..O....O
XOO.OOOO.OO
X..O....O
X................O
X.................OO........O....O
X................OO.......OO.OOOO.OO
X...........................O....O
//E*O*F relay.l//
echo x - round.l
sed -e 's/^X//' > "round.l" << '//E*O*F round.l//'
X! "round" (cells 200 length 72 width 72 generation 0)
X! This shows four busy bee shuttles arranged so as to keep a collection
X! or gliders traveling constantly between them.  Each glider is turned 90
X! degrees by a busy bee.  The glider near the cursor is about to be turned.
X9k39h@!
X......................................O
X......................................OO
X.........................OO.........O.OOO...........OO
X.........................OO.............OO..........OO
X.......................................OO
X.......................................O
X.
X.......................................O
X.......................................OO
X.........................OO.............OO..........OO
X.........................OO.........O.OOO...........OO
X......................................OO
X......................................O
X.................................OO
X..................................OO
X.................................O
X.
X.
X..OO.....OO
X..OO.....OO
X.
X.............................................O
X..............................................OO
X.............................................OO
X......................O
X......................OO.....................................OO.....OO
X.....................O.O.....................................OO.....OO
X.
X.
X.
X...O.....O
X..OOO...OOO
X.OO.OO.OO.OO
XOOO.......OOO...........................................O.O
X.........................................................OO
X..O.......O..............................................O
X..............O..............................................O.......O
X.............OO
X.............O.O...........................................OOO.......OOO
X............................................................OO.OO.OO.OO
X.............................................................OOO...OOO
X..............................................................O.....O
X.
X.
X.
X..OO.....OO.....................................O.O
X..OO.....OO.....................................OO
X.................................................O
X.........................OO
X........................OO
X..........................O
X.
X.............................................................OO.....OO
X.............................................................OO.....OO
X.
X.
X......................................O
X....................................OO
X.....................................OO
X.................................O
X................................OO
X..................OO...........OOO.O.........OO
X..................OO..........OO.............OO
X...............................OO
X................................O
X.
X................................O
X...............................OO
X..................OO..........OO.............OO
X..................OO...........OOO.O.........OO
X................................OO
X.................................O
//E*O*F round.l//
echo x - rpent.l
sed -e 's/^X//' > "rpent.l" << '//E*O*F rpent.l//'
X! "rpent" (cells 121 length 501 width 525 generation 1103)
X! This is the result of the infamous R-pentomino.  The cursor is positioned
X! on the starting pattern.  This object takes 1103 generations to stabilize.
X241k265h@!
X492.OO
X492..OO
X492.O
X177
X373.OOO
X373...O
X373..O
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X233...............................OO
X233...............................O.O
X233................................OO
X.
X.
X.
X.
X.
X.
X.
X.
X233......................OO
X233......................OO
X.
X.
X.
X.
X.
X.
X233..........................OO
X233..........................OO
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X233.............................OO
X233............................O..O
X233.............................OO...............O
X233.............................................O.O
X233.............................................OO
X.
X.
X233..........................OO
X233.........................O..O....O...........OO
X233...................O......OO....OOO..........OO
X233...................O..............O
X233...................O
X.
X.
X.
X.
X.
X233.OO
X233.OO
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X233.....OO...........................................O
X233....O..O.........................................O.O
X233.....OO.........................................O..O
X233.................................................OO
X.
X233.....................OO
X233.....................OO
X233.........................................O
X233.........................................O
X233.........................................O
X.
X.
X.
X.
X.
X.
X.
X.
X.
X233...........OO
X233...........OO.............................O
X233..........................................O
X233..........................................O
X.
X.
X.
X.
X.
X.
X233......................................OO
X233......................................OO
X233........O.........OO
X233........O.........OO
X233........O
X.
X.
X.
X.
X.
X.
X.
X.
X233............OO
X233...........O..O
X233............OO
X145
XO.O
XOO
X.O
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.......................O.O
X.......................OO
X........................O
X.
X.
X522..O
X522...O
X522.OOO
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X14.O
X14.O.O
X14.OO
//E*O*F rpent.l//
echo x - shipgun.l
sed -e 's/^X//' > "shipgun.l" << '//E*O*F shipgun.l//'
X! "shipgun" (cells 241 length 72 width 98 generation 0)
X! This is a middle-weight spaceship factory.  It is by constructed by placing
X! three newguns (period 46) together such that the three gliders collide to
X! produce a middleweight spaceship every 46 turns.  The cursor is positioned
X! near the gliders which are about to collide, and a previously built spaceship
X! is to the right.  The smaller period 30 glider guns cannot be used in place
X! of the period 46 guns since there is not enough space for the spaceship to
X! escape before the next gliders appear.
X33k47h@!
X.........................OO.....OO
X.........................OO.....OO
X.
X.
X.
X.
X.
X.
X.
X.
X.
X.
X........................OOO.....OOO
X.........................OOO...OOO
X.......................O.OOOO.OOOO.O
X........................OO..O.O..OO
X.........................OOO...OOO
X..........................O.....O
X.
X.
X.
X.
X.
X............O...O
XOO.........O.....O
XOO...............O
X............O...OO
X.............OOO.......................O........................O
X.....................................O.O......................O...O
X.............OOO......................OO...........................O
X............O...OO.......................OO....O..............O....O
XOO...............O.........OO...........O.O...OO...............OOOOO
XOO.........O.....O.........OO.............O...O.O
X............O...O
X.
X.
X.
X.............................OO
X............OO...............O.O
X12.OO15.O.OO36.OO9.OO14.OO
X30.OO.....................................OO........OO...............OO
X30.O.................................................OOOOO
X30.............................OO.....................OOOO
X30.O............OO............OO
X30.OO...........OO..............O.....................OOOO
X12.OO...............O.OO...............................................OOOOO
X12.OO15.O.O47.OO15.OO
X29.OO.................................................OO..............OO
X.
X.
X.
X29........OOO...OOO
X29........O..O.O..O
X29.......O...O.O...O
X29.......OOOO...OOOO..................OOO...OOO
X29........O.......O..................O..O...O..O
X29.......................................O.O
X29.......................................O.O
X29.......................................O.O
X29...................................O..O...O..O
X29....................................O.......O
X.
X.
X.
X.
X.
X.
X.
X.
X.
X29........OO.....OO...................OO.....OO
X29........OO.....OO...................OO.....OO
//E*O*F shipgun.l//
echo x - shuttles.l
sed -e 's/^X//' > "shuttles.l" << '//E*O*F shuttles.l//'
X! "shuttles" (cells 49 length 11 width 62 generation 0)
X! These are two of the three known shuttles (the third is "relay" in which
X! a glider travels back and forth between two pentadecathalons).  These are
X! the "engines" which power the two kinds of glider guns.  The shuttle on the
X! left has a period of 30.  The shuttle on the right has a period of 46.
X5k29h@!
X..................................................O
X.................................OO...............OO........OO
X.........OO......................OO................OO.......OO
X.........O..O.....................................OO
X.............O
XOO...........O......OO
XOO...........O......OO
X.........O..O.....................................OO
X.........OO......................OO................OO.......OO
X.................................OO...............OO........OO
X..................................................O
//E*O*F shuttles.l//
echo x - switch.l
sed -e 's/^X//' > "switch.l" << '//E*O*F switch.l//'
X! "switch" (cells 11 length 7 width 28 generation 0)
X! This is the incredible switch engine.  This puffer train travels diagonally
X! at 1/12 the speed of light towards the upper left with a period of 96.
X! It's only exhaust is many sets of blocks arranged in a zig-zag.  This is the
X! smallest known object which grows without bound (11 cells).  The block to
X! the right is necessary to stabilize the exhaust.  Without it, the engine is
X! killed by the untamed exhaust before it reaches 12 full periods.
X1k@!
X.O.O
XO
X.O..O
X...OOO
X.
X..........................OO
X..........................O
//E*O*F switch.l//
echo done