home *** CD-ROM | disk | FTP | other *** search
/ NeXTSTEP 3.3 (Developer) / NeXT_Developer-3.3.iso / NextDeveloper / Examples / AppKit / BackspaceViews / Life / LifeView.m < prev    next >
Encoding:
Text File  |  1993-06-18  |  7.7 KB  |  367 lines

  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
  367.