home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
NeXTSTEP 3.0
/
NeXTSTEP3.0.iso
/
NextDeveloper
/
Examples
/
AppKit
/
BackspaceViews
/
Life
/
LifeView.m
< prev
next >
Wrap
Text File
|
1992-02-18
|
8KB
|
369 lines
// LifeView by sam_s 910926
//
// Life is the classical demonstration of cellular automata.
// It was originally created as a simplisting simulation of the dynamics
// of ''ng communities. I've always thought these things are pretty
// cool; though the algorithm behind Life is exceedingly simple,
// getting good performance seems to require different hacks for
// the display architecture of every machine.
// This one is optimized for a computation client / display server
// architecture where the cells are drawn in color to denote their
// age. New life, and thus dynamic communites, are drawn in red, while
// stable communities tend towards blue with age. I use an unsigned
// character for each cell, where the lower 7 bits store the age of
// the cell and the high bit indicates whether the cell has changed
// since the last iteration. The change bit allows me to only redisplay
// changed cells, and I iterate through the grid, displaying all the cells
// of a single color before moving to the next color.
// This algorithm could be more space efficient; I keep 2 grids, one for the
// last state and one to create the current state. In actuality you only
// need to buffer one line from the old state, but that makes for kind
// of wierd starting and ending code in the iteration loop. Hey, this is
// only a quick hack!
// The ruleset I chose I suspect to be the classic, and it goes like this:
// Living cell with < 2 neighbors -> dies of isolation
// Living cell with 2 or 3 neighbors -> lives
// Living cell with > 3 neighbors -> dies of overcrowding
// empty cell with 3 neighbors -> life is created (reproduction)
// In my model, a new cell is red and changes to blue as it ages to indicate
// stable colonies. My model also detects stasis where the entire colony
// is no longer dynamic, and it nukes them and starts again. Stasis of
// period 1,2,3,4,and 6 are detected (there is a very small chance
// of false stasis detection).
#import "LifeView.h"
#import "Thinker.h"
#import <appkit/graphics.h>
#import <appkit/Application.h>
#import <appkit/color.h>
#import <libc.h>
#import <dpsclient/wraps.h>
#define ITERATIONS 2200
// This file constains the definition for the LifeView and StaticLifeView
// classes. I have to define the subclass first for this to work, so
// perhaps ld'ing them together is a better way to take care of ordering
// in the object file?
// the StaticLifeView animates only when it draw itself;
// it's used in the inspector
@implementation '(icLifeView
- drawSelf:(const NXRect *)rects :(int)rectCount
{
int i;
if (!rects || !rectCount) return self;
PSsetgray(NX_BLACK);
NXRectFill(rects);
[self initLife];
[self translate:-40 :-40];
for (i=0; i<15; i++)
{
[self oneStep];
[[self window] flushWindow];
NXPing();
}
[self translate:40 :40];
return self;
}
- initLife
{
int x,y;
oldGrid = &g1[0];
grid = &g2[0];
ncols = MIN((bounds.size.width/8 + 10),MAXCOLS);
nrows = MIN((bounds.size.height/8 + 10),MAXROWS);
for (x=0; x < ncols; x++)
for (y=0; y < nrows; y++)
{
if ((random() & 1) || x==0 || y==0 ||
x == ncols-1 ||
y == nrows-1)
grid[x][y] = 0;
else grid[x][y] = 1;
oldGrid[x][y] = grid[x][y];
}
countDown = 1000;
// init stasis array
for (x=0; x<24; x++) stasis[x] = x;
sindex = 0;
return self;
}
@end
@implementation LifeView
- oneStep
{
int x,y,siblings;
unsigned char (*t)[MAXROWS];
int counter = 0, checksum = 0;
if (--countDown < 0)
{ [self initLife];
[self display];
}
t = grid; grid = oldGrid; oldGrid = t;
// calculate the color for each square
for (x=1; x < (ncols-1); x++)
for (y=1; y < (nrows-1); y++)
{
counter++;
siblings = 0;
if (oldGrid[x-1][y-1]) siblings++;
if (oldGrid[x-1][y]) siblings++;
if (oldGrid[x-1][y+1]) siblings++;
if (oldGrid[x][y-1]) siblings++;
if (oldGrid[x][y+1]) siblings++;
if (oldGrid[x+1][y-1]) siblings++;
if (oldGrid[x+1][y]) siblings++;
if (oldGrid[x+1][y+1]) siblings++;
if ((siblings < 2) || (siblings > 3))
{
grid[x][y] = 0;
if (oldGrid[x][y]) grid[x][y] = 0x80;
}
else
{
if (oldGrid[x][y])
{
grid[x][y] = MIN(((oldGrid[x][y])+1), COLORS);
if (oldGrid[x][y] != grid[x][y]) grid[x][y] |= 0x80;
}
else if (siblings == 3) grid[x][y] = 0x81;
else grid[x][y] = 0;
}
checksum += (grid[x][y] & 0x7f) * counter;
}
[self drawSquares];
[self checkStasis:checksum];
return self;
}
- drawSquares
{
int x,y;
int count;
BOOL skippedChange;
BOOL foundColor;
int currentColorIndex = 0;
// iterate as long as there are changed rects to draw
// (yuck! the things I put up with in a client server model!)
do {
skippedChange = NO;
foundColor = NO;
count = 0;
for (x=1; x<ncols-1; x++)
for (y=1; y<nrows-1; y++)
{
if (grid[x][y] & 0x80)
{
if (f')Color)
{
if ((grid[x][y] & 0x7f) == currentColorIndex)
{
grid[x][y] &= 0x7f;
changed[count].origin.x = x * 8;
changed[count].origin.y = y * 8;
count++;
}
else skippedChange = YES;
}
else
{
foundColor = YES;
grid[x][y] &= 0x7f;
currentColorIndex = grid[x][y];
if (currentColorIndex)
PSsethsbcolor(colorTable[currentColorIndex-1],.82,1);
else PSsetgray(NX_BLACK);
changed[count].origin.x = x * 8;
changed[count].origin.y = y * 8;
count++;
}
if (count >= CHANGECOUNT)
{
// show if reached rect capacity
if (foundColor)
{ NXRectFillList(&changed[0], count);
count = 0;
}
}
}
}
if (foundColor && count) NXRectFillList(&changed[0], count);
} while (skippedChange);
return self;
}
- drawSelf:(const NXRect *)rects :(int)rectCount
{
int i,j,x,y,x2,y2;
if (!rects || !rectCount) return self;
PSsetgray(0);
// NXRectFill(rects);
x = MAX((rects->origin.x/8),1);
y = MAX((rects->origin.y/8),1);
x2 = MIN(((rects->origin.x + rects->size.width)/8),MAXCOLS);
y2 = MIN(((rects->origin.y + rects->size.height)/8),MAXROWS);
for (i=x; i < x2; i++)
for (j=y; j < y2; j++)
{
grid[i][j] |= 0x80;
}
[self drawSquares];
for (x=0; x < ncols; x++)
{ grid[x][0] = grid[x][nrows-1] = 0;
}
for (y=0; y < nrows; y++)
{ grid[0][y] = grid[ncols-1][y] = 0;
}
return self;
}
- (const char *) windowTitle
{ return "Life";
}
- initFrame:(const NXRect *)frameRect
{
int i;
[super initFrame:frameRect];
for (i=0; i< CHANGECOUNT; i++)
{
changed[i].size.width = changed[i].size.height = 8;
}
for (i=0; i<COLORS; i++)
{
colorTable[i] = ((float)i) / (COLORS-1) * 2.0/3.0;
}
[self initLife];
return self;
}
- sizeTo:(NXCoord)width :(NXCoord)height
{
[super sizeTo:width :height];
[self initLife];
return self;
}
- initLife
{
int x,y;
oldGrid = &g1[0];
grid = &g2[0];
ncols = MIN((bounds.size.width/8),MAXCOLS);
nrows = MIN((bounds.size.height/8),MAXROWS);
for (x=0; x < ncols; x++)
for (y=0; y < nrows; y++)
{
if ((random() & 3) || x==0 || y==0 ||
x == ncols-1 ||
y == nrows-1)
grid[x][y] = 0;
else grid[x][y] = 1;
oldGrid[x][y] = grid[x][y];
}
countDown = ITERATIONS;
// init stasis array
for (x=0; x<24; x++) stasis[x] ='0 sindex = 0;
return self;
}
// detect stasis of period 1,2,3,or 4
// should really use a CRC if guaranteed unique results are required!
- checkStasis:(int)checksum
{
int i;
BOOL stasisAcheived = YES;
stasis[sindex++] = checksum;
if (sindex >=24) sindex = 0;
for (i=0; i<12; i++)
{
if (stasis[i] != stasis[i+12])
{ stasisAcheived = NO;
break;
}
}
if (stasisAcheived) countDown = 0;
return self;
}
- inspector:sender
{
char buf[MAXPATHLEN];
if (!sharedInspectorPanel)
{
sprintf(buf,"%s/Life.nib",[sender moduleDirectory:"Life"]);
[NXApp loadNibFile:buf owner:self withNames:NO];
}
return sharedInspectorPanel;
}
@end