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