THE WAY OF LIFE
By Stefan Posthuma
It had been quite a long day at work, it had started off with an
early-morning meeting in a smelly room, full of smoking and
droning people. I had a headache for the rest of the day, idly
staring at my computer screen, browsing through some network
news, polishing up some programs I had been working on. Dinner
afterwards with some clients that wasn't particularly exciting
too. Well, at least the food was good. The drive back from
Amsterdam was a long one, road construction causing queues even
at 10 in the evening. I pulled into the parking lot while
observing that old lady that always seemed to walk her dog. The
contradiction between these two was remarkable. The old lady was
bent and grey, the dog an energetic, and somewhat nervous inbreed
that was yapping frantically at the dark silhouette of my car.
It's eyes lit up in my headlights and I slowed down to allow the
old lady to pass, she was trying to control the struggling dog
while talking to it. What she said was drowned in the loud Dr.
Jeep playing on my car CD player.
I felt the engine stop as I turned the key and the CD slid out
of the player when I removed the little panel. I locked the door
and walked towards the brightly lit entrance of the appartment
building. At the door, the fumbling with the keys, opening the
mailbox finding a pile of junkmail that I dropped in the
container next to the elevator. I wondered why the light in the
stairway was always on, it seemed a waste of energy to me.
I unlocked the door of my place on the third floor and the
familiar smell of incence and garlic made me feel welcome
immediately. I dropped my briefcase on the table and did my usual
round. Yes, VCR recorded the stuff that had been on that night,
both hamsters were alive and kicking, puttering around their
cages. The little light on the answering machine was blinking,
people had called. Better water that plant, it looks a bit
dehydrated. I opened the door of one of the hamster cage next to
the telephone, the little animal immediately getting out, ready
to roam.
"Hi, this is Caroline.", the answering machine said as I walked
to the fridge.
"It's about Friday night, I can't make it."
"Shit", I said while opening a beer.
"Call me okay?".
Some beeping sounds, "Yo Stef. Eric here. I did it, 5
generations per second at 4000 cells. Beat that, sucker!"
I sat down by the phone and punched Caroline's number. While
talking to her, I watched the hamster crawl into the box of
hamster food, I was too late to stop it. She laughed about it.
Damn, date postponed for two weeks. Ah well, can't have it all.
I watched in fascination as the hamster tried to get out of the
box again. Obviously, it hadn't accounted for pouches filled with
hamsterfood being able to fit through the hole and it was
struggling fiercely to get out.
"Eric. So tell me the deal. Five generations huh?".
"Yeah, I figured out this really smart algorithm. It caches the
neighbour counts for three rows, making it some 15% faster."
"Cool", I said. "Now let me try. I'll get back to you."
"You'll never make it! By the way, I had some help from
Hubert J."
"Right. Well, just wait and see."
I hung up the phone and watched the hamster finally release
itself from the box. It immediately hurried back to the cage to
store the prize it just got. I made a mental note not to feed it
for a couple of days.
That Friday night when I should have been out with Caroline I
wrote the game of Life. A pretty fast one too.
So what the hell is the game of Life you might wonder. Well,
picture a screen full of little blocks. Every block can be either
on or off, or in Life terms alive or dead. A block is called a
cell by the way. Right. So what's the big deal?
Well, by applying certain rules to these cells, we can have them
die and being born on our screen. These rules are simple. You
scan the screen, and for every cell, you count the number of
alive neighbours it has. If a cell has two or three neighbours,
it stays alive, otherwise it dies. Also, if there isn't a cell in
the spot you are looking at, and there are three cells
surrounding the empty spot, a cell gets 'born', i.e. you put one
there. This sounds a bit silly, but if you keep displaying the
results of these rules on the screen, you get some fascinating
patterns and animations on your screen.
The way it works is that you have a field with let's say bytes
that are either 0 (dead) or $FF (alive). If you want to make a
scan, called a generation, you look at the first one, count its
neighbours and store the result (it lives or dies or gets born)
in another field. This to make sure that changes that are made to
the field don't influence the other cells. I mean if a cell dies,
and you would delete it in the current field, the neighbour
counts for the surrounding cells would change and things would go
wrong.
So after you have scanned the entire field, you clear the screen
and draw the field you have just created. Then you repeat the
process, starting from the second field. Now this is the
traditional and slow way. I mean having to count the neighbours
for each cell every time is a pretty slow process and redrawing
the entire field every time is madness.
I thought about it a bit and came up with the following. Why not
have a field that for each cell has a count of the number of
living neighbours that cell has? In that case you only have to
look at the number and decide whether or not the cell lives or
dies. I look at the screen memory to see if there is a cell or
not. The only trick here is to keep track of the number of
neighbours of each cell. If a cell gets born, you add one to the
neighbour count of the surrounding cells, and if a cell dies, you
subtract one. Also, while going through the field, I keep a list
of changes that have to be made to the screen. The only thing I
do really, is keep a list of screen addresses where cells have to
be toggled.
That's it really, time for some code.
First some equates I use. Number of lines and columns on the
screen plus the size of a scanline. The field is actually 50 by
82 because the cells at the edges are always 0 (dead). This
causes Life to fail around the edges, but there is no other way
really. Some people make it wrap around, but this is too slow,
you have to look at the position all the time then. This is why
COLS2 is defined, the actual width of the field to be scanned.
This also means that there are 46 lines of cells on the screen.
The top and bottom two lines are used for status messages. You
must have figured that the cells are 8 by 8 pixels. The system
font used is 16 pixels, two rows of cells.
LINES EQU 50
COLS EQU 80
COLS2 EQU COLS+2
SCANLINE EQU 80
Then I declare two macros, used to adjust the neighbour counts
for cells when one gets born or one dies. A1 has to point to the
current cell in the field.
* SUBSTRACT 1 FROM ALL THE CELLS AROUND A1
SUB_AROUND MACRO
SUBQ.B #1,-1(A1)
SUBQ.B #1,1(A1)
SUBQ.B #1,-COLS2-1(A1)
SUBQ.B #1,-COLS2(A1)
SUBQ.B #1,-COLS2+1(A1)
SUBQ.B #1,COLS2-1(A1)
SUBQ.B #1,COLS2(A1)
SUBQ.B #1,COLS2+1(A1)
ENDM
* ADD 1 TO ALL THE CELLS AROUND A1
ADD_AROUND MACRO
ADDQ.B #1,-1(A1)
ADDQ.B #1,1(A1)
ADDQ.B #1,-COLS2-1(A1)
ADDQ.B #1,-COLS2(A1)
ADDQ.B #1,-COLS2+1(A1)
ADDQ.B #1,COLS2-1(A1)
ADDQ.B #1,COLS2(A1)
ADDQ.B #1,COLS2+1(A1)
ENDM
In the real source, a lot of stuff follows to set up things, the
main loop and the editor used. This is trivial stuff, I will
continue at the actual loop that does a generation of Life.
GENCOUNT is a generation counter that is used to display the
number of generations per second. This handled by the VBL, after
70 vbls (monchrome) it will display this number.
USE_FIELD contains the address of the current Life field. FIELD1
and FIELD2 are the fields.
SCREEN is obvious of course, DRAW_LIST is the list of cells to
be toggled on screen.
First of all, swap the fields and store the used one in
USE_FIELD.
DO_IT ADDQ.W #1,GENCOUNT
MOVE.L USE_FIELD(PC),A0
CMP.L #FIELD1,A0
BNE.S .1
LEA FIELD2(PC),A1
BRA.S .2
.1 LEA FIELD1(PC),A1
.2 MOVE.L A1,USE_FIELD
Then skip the edge, 82 on top plus one on the left.
LEA COLS+3(A0),A0 ADJUST
LEA COLS+3(A1),A1
The first two lines contain the status line, so these have to be
skipped. D1 and D2 are filled with important compare values, it's
faster to compare with a data register than it is with a constant
value. 2 and 3 are the crucial number of cells. D3 counts the
columns on the screen, and D4 counts the lines. D5 is used to
clear values.
MOVE.L SCREEN(PC),A2
LEA SCANLINE*16(A2),A2 SKIP STATUSLINE
LEA DRAW_LIST(PC),A3
MOVEQ #2,D1 TEST VALUES
MOVEQ #3,D2
MOVEQ #COLS-1,D3 COLUMN COUNTER
MOVEQ #LINES-1,D4 LINE COUNTER
MOVEQ #0,D5
The the loop follows. A2 is the screen address and we test this
to see if there is a cell or not. If there is a cell, we move the
number of neighbours into D0 and compare this with 2 and 3, D1
and D2. This to see if the cell will stay alive.
THE_LOOP: TST.B (A2) CELL ON SCREEN?
BEQ.S NOCELL
CELL: MOVE.B (A0),D0 NR OF SURROUNDING CELLS
CMP.B D1,D0 TWO?
BEQ.S NEXTCELL FINE
CMP.B D2,D0 THREE?
BEQ.S NEXTCELL
Here the cell dies. We subtract the neighbour counters for the
surrounding cells and put the screen addres in the draw list to
be toggled later.
DIE: SUB_AROUND NO, DECREASE COUNTERS
MOVE.L A2,(A3)+ PUT IN DRAWLIST
BRA.S NEXTCELL
Now there is no cell on the screen. We get the number of
surrounding cells. First BEQ.S if the number is zero, this will
happen a lot. In this case we skip everything and jump straight
to the end of the loop. Then we compare the number with D2 to see
if there are three surrounding cells. If not, skip the birth
code.
NOCELL: MOVE.B (A0),D0 GET NR OF SURR. CELLS
BEQ.S N1 SKIP 0 CELLS (FASTER)
CMP.W D2,D0 THREE CELLS SURROUND?
BNE.S NEXTCELL NO WAY
This is where a cell is born, just add one to all the neighbours
and put the screen address in the draw list.
BIRTH: ADD_AROUND
MOVE.L A2,(A3)+ PUT IN DRAWLIST
Here we add the original number of neighbours to the value
present. We can't move it since the value might have been altered
by other cells around it dying or getting born.
NEXTCELL: ADD.B D0,(A1) TRANSPORT VALUE
Clear the used cell, increase the field address pointer and
screen address. After one row has been done, increase screen
address and skip the edge of the fields. Then the last DBRA is
for the row counter, D4.
N1: MOVE.B D5,(A0)+ CLEAR USED CELL
ADDQ.L #1,A1
ADDQ.L #1,A2 INC. SCREEN ADDR.
DBRA D3,THE_LOOP NOT YET AT EDGE
MOVE.W #COLS-1,D3
LEA SCANLINE*7(A2),A2
ADDQ.L #2,A1 SKIP EDGES
ADDQ.L #2,A0
DBRA D4,THE_LOOP
The generation has been completed. Now we calculate the number
of cells by subtracting the DRAW_LIST address from A3 and
dividing it by four, it contains longwords. The routine DRAW_THEM
actually draws and erases the cells in DRAW_LIST.
SUB.L #DRAW_LIST,A3 GET NR. OF CELLS
MOVE.L A3,D6
LSR.W #2,D6 DIVIDE BY FOUR
SUBQ.W #1,D6
BSR DRAW_THEM DRAW THE CELLS
BRA RUNLOOP BACK TO START!
This routine goes through the DRAW_LIST and uses the screen
addresses there to toggle the cells. It takes the first byte on
screen, NOTs it and puts this value in the 8 scanlines. Notice
the REPT 8 for loop-elimination.
DRAW_THEM: LEA DRAW_LIST(PC),A0
TST.W D6
BPL.S .1
RTS
.1 MOVE.L (A0)+,A1
MOVE.B (A1),D0
NOT.B D0
VAL SET 0
REPT 8
MOVE.B D0,VAL(A1)
VAL SET VAL+SCANLINE
ENDR
DBRA D6,.1
RTS
Well, that's it really. Very simple if you ask me. Have a look
at LIFEMONO.S in the PROGRAMS folder on this disk for the full
source. Monochrome only.
When I was at the DeltaForce ICC #2 in July 1991 (or was it
June?) Tim was finishing off the main menu for the Ooh Crikey Wot
a Scorcher demo. Now he was adding nice bits like the little man
falling out of the ship and he also wanted to add a helpscreen.
Now to make this a bit more than just a screen full of text, we
decided it needed some kind of animation or something in the
background. I then suggested using Life and so we did. I didn't
bring my original source, so I had to rewrite it from scratch. I
also got the idea to have cells not immediately disappear when
they died, but to let them shrink or something. The cell would be
marked as dead for the algorithm to work properly (so a dying
cell could be replaced by a live one at any stage) but it worked
out beautifully. Since this was in lo-res, and I still wanted to
used bytes (lazy git) there were only 40*25 = 2000 cells,
resulting in an incredibly fast Life. I actually had to slow it
down a bit (to 3 VBLs) to make it a reasonable speed.
That was the last of Life programming for me until a friend of
mine from the States (the first ever USA demo crew I know) asked
me if I wanted to do a guest screen for their demo. Now I have
little or no time to code these days so I polished up the old
Life source a bit. Added some status lines, some colors, an
editor and another screen was born.
If you run the version on this ST NEWS disk, you will get an
empty field with a cursor in the left-hand corner. This cursor
can be moved with the cursor keys (how imaginative) and you can
toggle cells on screen using the space bar. If you used the
keypad, you will move the cursor and draw cells at the same time.
Press F1 and the game will begin. Play around with it a little.
By placing a few random cells here and there, you can get some
fascinating results. Sometimes it can take hundreds of
generations before the colony stabilizes i.e. all cells die or
the patterns keep repeating themselves. If you have a repeating
pattern, adding or deleting one cell can have drastic
consequences, the whole thing quickly disappearing or blowing
itself up into a myriad of patterns. Also, sometimes cells just
get 'swallowed' by a pattern. You can make crawlers that move
across the screen:
*
*
****
This is the simplest one, it will travel diagonally down and to
the right. If it bumps into something it will mostly disrupt that
object. There are entire studies dedicated to this kind of
patterns, people have come up with the most amazing stuff. Like
'guns' that emit one of these crawlers at intervals, and
'absorbers' that can absorb a crawler. Thus you can have two
pulsating patterns with crawlers moving between them. Also,
people have made huge crawlers, patterns made out of hundreds of
cells that move across the screen, sometimes even accompanied by
smaller 'sattelites'.
One day I will make a Life where the cells are represented by
much smaller units than bytes (pixels maybe?) and I can try some
really wild patterns. These can be found in magazines and stuff.
So much for Life.
Disclaimer
The text of the articles is identical to the originals like they appeared
in old ST NEWS issues. Please take into consideration that the author(s)
was (were) a lot younger and less responsible back then. So bad jokes,
bad English, youthful arrogance, insults, bravura, over-crediting and
tastelessness should be taken with at least a grain of salt. Any contact
and/or payment information, as well as deadlines/release dates of any
kind should be regarded as outdated. Due to the fact that these pages are
not actually contained in an Atari executable here, references to scroll
texts, featured demo screens and hidden articles may also be irrelevant.