|
|
1.1 root 1: // LifeView by sam_s 910926
2: //
3: // Life is the classical demonstration of cellular automata.
4: // It was originally created as a simplisting simulation of the dynamics
5: // of living communities. I've always thought these things are pretty
6: // cool; though the algorithm behind Life is exceedingly simple,
7: // getting good performance seems to require different hacks for
8: // the display architecture of every machine.
9:
10: // This one is optimized for a computation client / display server
11: // architecture where the cells are drawn in color to denote their
12: // age. New life, and thus dynamic communites, are drawn in red, while
13: // stable communities tend towards blue with age. I use an unsigned
14: // character for each cell, where the lower 7 bits store the age of
15: // the cell and the high bit indicates whether the cell has changed
16: // since the last iteration. The change bit allows me to only redisplay
17: // changed cells, and I iterate through the grid, displaying all the cells
18: // of a single color before moving to the next color.
19:
20: // This algorithm could be more space efficient; I keep 2 grids, one for the
21: // last state and one to create the current state. In actuality you only
22: // need to buffer one line from the old state, but that makes for kind
23: // of wierd starting and ending code in the iteration loop. Hey, this is
24: // only a quick hack!
25:
26: // The ruleset I chose I suspect to be the classic, and it goes like this:
27:
28: // Living cell with < 2 neighbors -> dies of isolation
29: // Living cell with 2 or 3 neighbors -> lives
30: // Living cell with > 3 neighbors -> dies of overcrowding
31: // empty cell with 3 neighbors -> life is created (reproduction)
32:
33: // In my model, a new cell is red and changes to blue as it ages to indicate
34: // stable colonies. My model also detects stasis where the entire colony
35: // is no longer dynamic, and it nukes them and starts again. Stasis of
36: // period 1,2,3,4,and 6 are detected (there is a very small chance
37: // of false stasis detection).
38:
39: #import <libc.h>
40: #import <appkit/appkit.h>
41:
42: #import "LifeView.h"
43: #import "Thinker.h"
44:
45: #define ITERATIONS 2200
46:
47: // This file constains the definition for the LifeView and StaticLifeView
48: // classes. I have to define the subclass first for this to work, so
49: // perhaps ld'ing them together is a better way to take care of ordering
50: // in the object file?
51:
52: // the StaticLifeView animates only when it draw itself;
53: // it's used in the inspector
54: @implementation StaticLifeView
55:
56: - drawSelf:(const NXRect *)rects :(int)rectCount
57: {
58: int i;
59:
60: if (!rects || !rectCount) return self;
61:
62: PSsetgray(NX_BLACK);
63: NXRectFill(rects);
64:
65: [self initLife];
66: [self translate:-40 :-40];
67: for (i=0; i<15; i++)
68: {
69: [self oneStep];
70: [[self window] flushWindow];
71: NXPing();
72: }
73: [self translate:40 :40];
74:
75: return self;
76: }
77:
78: - initLife
79: {
80: int x,y;
81:
82: oldGrid = &g1[0];
83: grid = &g2[0];
84:
85: ncols = MIN((bounds.size.width/8 + 10),MAXCOLS);
86: nrows = MIN((bounds.size.height/8 + 10),MAXROWS);
87:
88: for (x=0; x < ncols; x++)
89: for (y=0; y < nrows; y++)
90: {
91: if ((random() & 1) || x==0 || y==0 ||
92: x == ncols-1 ||
93: y == nrows-1)
94: grid[x][y] = 0;
95: else grid[x][y] = 1;
96:
97: oldGrid[x][y] = grid[x][y];
98: }
99:
100: countDown = 1000;
101:
102: // init stasis array
103: for (x=0; x<24; x++) stasis[x] = x;
104: sindex = 0;
105:
106: return self;
107: }
108:
109: @end
110:
111:
112:
113:
114:
115:
116: @implementation LifeView
117:
118: - oneStep
119: {
120: int x,y,siblings;
121: unsigned char (*t)[MAXROWS];
122: int counter = 0, checksum = 0;
123:
124: if (--countDown < 0)
125: { [self initLife];
126: [self display];
127: }
128:
129: t = grid; grid = oldGrid; oldGrid = t;
130:
131: // calculate the color for each square
132: for (x=1; x < (ncols-1); x++)
133: for (y=1; y < (nrows-1); y++)
134: {
135: counter++;
136: siblings = 0;
137:
138: if (oldGrid[x-1][y-1]) siblings++;
139: if (oldGrid[x-1][y]) siblings++;
140: if (oldGrid[x-1][y+1]) siblings++;
141:
142: if (oldGrid[x][y-1]) siblings++;
143: if (oldGrid[x][y+1]) siblings++;
144:
145: if (oldGrid[x+1][y-1]) siblings++;
146: if (oldGrid[x+1][y]) siblings++;
147: if (oldGrid[x+1][y+1]) siblings++;
148:
149: if ((siblings < 2) || (siblings > 3))
150: {
151: grid[x][y] = 0;
152: if (oldGrid[x][y]) grid[x][y] = 0x80;
153: }
154: else
155: {
156: if (oldGrid[x][y])
157: {
158: grid[x][y] = MIN(((oldGrid[x][y])+1), COLORS);
159: if (oldGrid[x][y] != grid[x][y]) grid[x][y] |= 0x80;
160: }
161: else if (siblings == 3) grid[x][y] = 0x81;
162: else grid[x][y] = 0;
163: }
164:
165: checksum += (grid[x][y] & 0x7f) * counter;
166: }
167:
168: [self drawSquares];
169:
170: [self checkStasis:checksum];
171:
172: return self;
173: }
174:
175: - drawSquares
176: {
177: int x,y;
178: int count;
179: BOOL skippedChange;
180: BOOL foundColor;
181: int currentColorIndex = 0;
182:
183: // iterate as long as there are changed rects to draw
184: // (yuck! the things I put up with in a client server model!)
185:
186: do {
187: skippedChange = NO;
188: foundColor = NO;
189: count = 0;
190:
191: for (x=1; x<ncols-1; x++)
192: for (y=1; y<nrows-1; y++)
193: {
194: if (grid[x][y] & 0x80)
195: {
196: if (foundColor)
197: {
198: if ((grid[x][y] & 0x7f) == currentColorIndex)
199: {
200: grid[x][y] &= 0x7f;
201: changed[count].origin.x = x * 8;
202: changed[count].origin.y = y * 8;
203: count++;
204: }
205: else skippedChange = YES;
206: }
207: else
208: {
209: foundColor = YES;
210: grid[x][y] &= 0x7f;
211: currentColorIndex = grid[x][y];
212: if (currentColorIndex)
213: PSsethsbcolor(colorTable[currentColorIndex-1],.82,1);
214: else PSsetgray(NX_BLACK);
215:
216: changed[count].origin.x = x * 8;
217: changed[count].origin.y = y * 8;
218: count++;
219: }
220:
221: if (count >= CHANGECOUNT)
222: {
223: // show if reached rect capacity
224: if (foundColor)
225: { NXRectFillList(&changed[0], count);
226: count = 0;
227: }
228: }
229: }
230: }
231:
232: if (foundColor && count) NXRectFillList(&changed[0], count);
233:
234: } while (skippedChange);
235:
236: return self;
237: }
238:
239:
240: - drawSelf:(const NXRect *)rects :(int)rectCount
241: {
242: int i,j,x,y,x2,y2;
243:
244: if (!rects || !rectCount) return self;
245: PSsetgray(0);
246: // NXRectFill(rects);
247:
248: x = MAX((rects->origin.x/8),1);
249: y = MAX((rects->origin.y/8),1);
250: x2 = MIN(((rects->origin.x + rects->size.width)/8),MAXCOLS);
251: y2 = MIN(((rects->origin.y + rects->size.height)/8),MAXROWS);
252:
253: for (i=x; i < x2; i++)
254: for (j=y; j < y2; j++)
255: {
256: grid[i][j] |= 0x80;
257: }
258:
259: [self drawSquares];
260:
261: for (x=0; x < ncols; x++)
262: { grid[x][0] = grid[x][nrows-1] = 0;
263: }
264: for (y=0; y < nrows; y++)
265: { grid[0][y] = grid[ncols-1][y] = 0;
266: }
267:
268: return self;
269: }
270:
271: - (const char *) windowTitle
272: { return "Life";
273: }
274:
275: - initFrame:(const NXRect *)frameRect
276: {
277: int i;
278:
279: [super initFrame:frameRect];
280: for (i=0; i< CHANGECOUNT; i++)
281: {
282: changed[i].size.width = changed[i].size.height = 8;
283: }
284:
285: for (i=0; i<COLORS; i++)
286: {
287: colorTable[i] = ((float)i) / (COLORS-1) * 2.0/3.0;
288: }
289: [self initLife];
290: return self;
291: }
292:
293: - sizeTo:(NXCoord)width :(NXCoord)height
294: {
295: [super sizeTo:width :height];
296: [self initLife];
297: return self;
298: }
299:
300: - initLife
301: {
302: int x,y;
303:
304: oldGrid = &g1[0];
305: grid = &g2[0];
306:
307: ncols = MIN((bounds.size.width/8),MAXCOLS);
308: nrows = MIN((bounds.size.height/8),MAXROWS);
309:
310: for (x=0; x < ncols; x++)
311: for (y=0; y < nrows; y++)
312: {
313: if ((random() & 3) || x==0 || y==0 ||
314: x == ncols-1 ||
315: y == nrows-1)
316: grid[x][y] = 0;
317: else grid[x][y] = 1;
318:
319: oldGrid[x][y] = grid[x][y];
320: }
321:
322: countDown = ITERATIONS;
323:
324: // init stasis array
325: for (x=0; x<24; x++) stasis[x] = x;
326: sindex = 0;
327:
328: return self;
329: }
330:
331: // detect stasis of period 1,2,3,or 4
332: // should really use a CRC if guaranteed unique results are required!
333: - checkStasis:(int)checksum
334: {
335: int i;
336: BOOL stasisAcheived = YES;
337:
338: stasis[sindex++] = checksum;
339: if (sindex >=24) sindex = 0;
340:
341: for (i=0; i<12; i++)
342: {
343: if (stasis[i] != stasis[i+12])
344: { stasisAcheived = NO;
345: break;
346: }
347: }
348:
349: if (stasisAcheived) countDown = 0;
350:
351: return self;
352: }
353:
354: - inspector:sender
355: {
356: char buf[MAXPATHLEN];
357:
358: if (!sharedInspectorPanel)
359: {
360: [NXBundle getPath:buf forResource:"Life" ofType:"nib" inDirectory:[sender moduleDirectory:"Life"] withVersion:0];
361: [NXApp loadNibFile:buf owner:self withNames:NO];
362: }
363: return sharedInspectorPanel;
364: }
365:
366: @end
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.