ftp.nice.ch/pub/next/tools/screen/BackSpace.1.02.N.bs.tar.gz#/BackSpace/backspaceViews/LifeViewPart.m

This is LifeViewPart.m in view mode; [Download] [Up]

// 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 living 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!

#import "LifeViewPart.h"
#import <appkit/graphics.h> 
#import <appkit/color.h>
#import <libc.h>
#import <dpsclient/wraps.h>

#define ITERATIONS 2200
//#define ITERATIONS 1500

@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 (foundColor)
				{
					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] = x;
	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;
}

@end


These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.