home *** CD-ROM | disk | FTP | other *** search
/ Nebula 2 / Nebula Two.iso / Apps / ScreenSavers / BackSpaceViews / LifeView.BackModule / LifeView.m < prev    next >
Encoding:
Text File  |  1995-06-12  |  25.3 KB  |  843 lines

  1. // LifeView by David Bau
  2. // Copyright 1994 by David Bau.  All rights reserved.
  3. //
  4. // I feel silly even saying this, but please don't charge for this
  5. // module or any product containing a portion or modification of it,
  6. // and when distributing, please give credit where credit is due.
  7. //
  8. // If you add anything to this module or derive anything interesting
  9. // from it, please let me know!
  10. //
  11. // I'll probably be bau@cs.cornell.edu for a while.
  12. //
  13. // David Bau 01/13/94
  14. // 777 South Avenue, Weston, MA 02193
  15. // 
  16. // This code is shamelessly ripped off of Sam Streeper's Life module.
  17. // Here is Sam's original description.
  18. //
  19. // ****
  20. // Life is the classical demonstration of cellular automata.
  21. // It was originally created as a simplisting simulation of the dynamics
  22. // of living communities.  I've always thought these things are pretty
  23. // cool; though the algorithm behind Life is exceedingly simple,
  24. // getting good performance seems to require different hacks for
  25. // the display architecture of every machine.
  26. // ...
  27. // Living cell with < 2 neighbors    -> dies of isolation
  28. // Living cell with 2 or 3 neighbors -> lives
  29. // Living cell with > 3 neighbors    -> dies of overcrowding
  30. // empty cell with 3 neighbors       -> life is created (reproduction)
  31. // ...
  32. // ****
  33. // 
  34. // I've changed several things.  First, the method for computing and
  35. // drawing cells was changed to a sparse algorithm that traverses a
  36. // list of live cells.  This way empty cells are looked at as little
  37. // as possible, which is a big win when the field is mostly empty.
  38. // As a nice side effect, the squares get drawn in a (more or less)
  39. // uniformly random order, which gives a smoother visual effect
  40. // without any left-to-right flickering.
  41. //
  42. // The drawing-buffering code was cleaned up.  Smaller lists of rects
  43. // are used, but one for each color.  No noticable performance hit.
  44. //
  45. // A hack in drawSelf was added to work around the double-refresh
  46. // problem when the screensaver first kicks in. (countDown!=ITERATIONS)
  47. //
  48. // The initLife seeding algorithm was changed.  Now it puts inital
  49. // cells in a small circular patch, leaving the rest of the field
  50. // empty, which is much better for the sparse algorithm.  For big
  51. // windows, the circular patch appears in a random starting place.
  52. //
  53. // Stasis checking was changed.  Now it automatically deduces any
  54. // stasis period (up to the size of the stasis buffer), but it takes
  55. // more generations for stasis to be detected.
  56. //
  57. // The color table was generalized to dish out NXColors, instead of
  58. // just hues.  Cell size was generalized.
  59. //
  60. // The control panel was souped up.  Now the color table can be
  61. // changed by the user.  The cell sizes can be changed.  The panel
  62. // animation appears (slowly) partially obscured by the panel
  63. // controls.  The panel animation can be stepped manually by the
  64. // user. Defaults are saved in the user's defaults database.
  65. // Programming the panel was the hardest part, but the result
  66. // looks pretty nice.  I'm beginning to appreciate IB.
  67. //
  68. // 10/20/93 fixes and improvements: used perform:with:afterDelay to
  69. // credits button remove itself correctly.  Also used the same method
  70. // to do the panel animation in a more polite, lazy way.  Changed
  71. // "step" button to a continuous button.  Fixed the timed panel
  72. // animation so it doesn't draw if the view is gone from the window.
  73. //
  74. // 10/23/93 Changed sizing buttons to a matrix of buttons.  Stop
  75. // timed panel animation if already doing an animation somewhere else
  76. // (window or background) by making sure countDown isn't changing.
  77. //
  78. // 10/27/93 Added LIVEEDGE boundary convention.  (Later removed.)
  79. // Made it so drawing can go right up to the edge of the window,
  80. // using a random offset when it can't completely fill it. Removed
  81. // unneeded calls to flushColor to avoid NXSetColor calls.
  82. //
  83. // 10/29/93 Added DEEPEDGE, so field can extend beyond the visible screen.
  84. // turned off LIVEEDGE feature.  Fine tuned seeding parameters.
  85. //
  86. // 11/05/93 Changed MINCELLSIZE to 2, which can be obtained by a dwrite
  87. // to SparseLifeCellSize only.  Played with matrix of radio buttons for
  88. // so none are highlighed when cellSize matches none of them (e.g.=2),
  89. // but so that once the user starts selecting them, they go into
  90. // setEmptySelectionEnabled:NO mode.
  91. //
  92. // 11/12/93 Changed seeding to make more patches when field is very large.
  93. //
  94. // 11/13/93 Modified by Richard Hess (rhess@consilium.com)
  95. // Added a "transparent" button in the upper right corner of the
  96. // control panel which sets the cell size to MINCELLSIZE if the 
  97. // mouse is clicked in it...
  98. //
  99. // 01/09/94 Fixed allocation of Grid so only as much memory is allocated
  100. // as is needed.
  101. //
  102. // 01/12/94 Put a limit on the maximum animation speed to fight flickering.
  103. // Changed name from SparseLife to just Life.
  104. //
  105. // 01/13/94 Fixed a memory leak pointed out by Sam Streeper.
  106. // 01/13/94 Modified by Richard Hess (rhess@consilium.com)
  107. // Added a LifeCluster default mechanism for setting the number of patches
  108. // to be generated when your running very small cell sizes on a big screen...
  109. //
  110. // 01/14/94 Eliminated lockfocusing when [self canDraw]==NO.  Centered
  111. // static life view correctly.
  112. //
  113. // 01/15/94 Put lower bound on ncols, nrows, as in MazeView.  Added
  114. // LifeFrameDelay default.
  115.  
  116. #import <appkit/appkit.h>
  117. #import <defaults/defaults.h>
  118. #import <libc.h>
  119. #import <time.h>
  120. #import <dpsclient/wraps.h>
  121. #import "LifeView.h"
  122. #import "Thinker.h"
  123.  
  124.  
  125. /* the kind of Life view that appears in the control panel */
  126. @implementation StaticLifeView
  127.  
  128. - initLife
  129. {
  130.     int x,y,count,t;
  131.  
  132.     /* frame the field 2 cells bigger than the view */
  133.     ncols=MIN((bounds.size.width/cellSize+2+2*DEEPEDGE+2),MAXCOLS);
  134.     nrows=MIN((bounds.size.height/cellSize+2+2*DEEPEDGE+2),MAXROWS);
  135.     if (ncols<4) ncols=4;
  136.     if (nrows<4) nrows=4;
  137.     t=(int)bounds.size.width-(ncols-2-2*DEEPEDGE)*cellSize;
  138.     xoffset=(t<0 ? t/2-(1+DEEPEDGE)*cellSize :
  139.              random()%(t+1)-(1+DEEPEDGE)*cellSize);
  140.     t=(int)bounds.size.height-(nrows-2-2*DEEPEDGE)*cellSize;
  141.     yoffset=(t<0 ? t/2-(1+DEEPEDGE)*cellSize :
  142.              random()%(t+1)-(1+DEEPEDGE)*cellSize);
  143.  
  144.     /* allocate grid */
  145.     if (Grid) NX_ZONEREALLOC([self zone],Grid,lifecell,nrows*ncols);
  146.     NX_ZONEMALLOC([self zone],Grid,lifecell,nrows*ncols);
  147.  
  148.     /* clear the field */
  149.     [self clearLife];
  150.  
  151.     /* use uniform random seeding */
  152.     for (count=ncols*nrows/5; count; count--) {
  153.         x=random()%(ncols-2)+1;
  154.         y=random()%(nrows-2)+1;
  155.         if (Grid[x+y*ncols].color==(-1)) {
  156.             Grid[x+y*ncols].color=0;
  157.             Grid[x+y*ncols].next=ifirst;
  158.             ifirst=x+y*ncols;
  159.         }
  160.         if (Grid[x+y*ncols].color<COLORS-1) Grid[x+y*ncols].color++;
  161.     }
  162.  
  163.     /* don't go for too many generations.  Seems it would be boring. */
  164.     countDown = 1000;
  165.     lasttime = currentTimeInMs();
  166.  
  167.     return self;
  168. }
  169.         
  170. /* main view can tell panel view what colors to use */
  171. - setYoungColor:(NXColor)yc MediumColor:(NXColor)mc OldColor:(NXColor)oc
  172. {
  173.     youngColor=yc;
  174.     mediumColor=mc;
  175.     oldColor=oc;
  176.     [self computeColors];
  177.     return self;
  178. }
  179.  
  180. /* main view can tell panel what cell size to use */
  181. - setLifeCellSize:(int)cs;
  182. {
  183.     cellSize=cs;
  184.     [self setupSquareBuffer];
  185.     return self;
  186. }
  187.  
  188. @end
  189.  
  190.  
  191.  
  192.  
  193.  
  194.  
  195. @implementation LifeView
  196.  
  197. /* here's where the sparse computation and drawing is done */
  198. - oneStep
  199. {
  200.     BStimeval curtime;
  201.     int icur, *picur;
  202.     int checksum = 0;
  203.  
  204.     running=YES;
  205.     curtime=currentTimeInMs();
  206.     if (curtime-lasttime<delay) {
  207.         if (delay-(curtime-lasttime)>30) {
  208.             usleep(15000);
  209.             return self;
  210.         }
  211.         usleep((delay-(curtime-lasttime))*1000);
  212.         curtime=currentTimeInMs();
  213.     }
  214.     lasttime=curtime;
  215.  
  216.     /* finished */
  217.     if (--countDown < 0)
  218.     {
  219.         [self initLife];
  220.         [self display];
  221.     }
  222.  
  223.     /* pass one: count up neighbors.  Hope gcc does cse well... */
  224. #define CONTRIBUTE_TO_GRID(iadj)         \
  225.     if (Grid[iadj].color==(-1)) {        \
  226.         Grid[iadj].color=0;              \
  227.         Grid[iadj].next=ifirst;          \
  228.         ifirst=iadj;                     \
  229.     }                                    \
  230.     Grid[iadj].neighbors++;
  231.  
  232.     for (icur=ifirst; icur>=0; icur=Grid[icur].next) {
  233.         /* contribute to the west */
  234.         CONTRIBUTE_TO_GRID(icur-1);
  235.         CONTRIBUTE_TO_GRID(icur-1-ncols);
  236.         CONTRIBUTE_TO_GRID(icur-ncols);
  237.         CONTRIBUTE_TO_GRID(icur+1-ncols);
  238.         CONTRIBUTE_TO_GRID(icur+1);
  239.         CONTRIBUTE_TO_GRID(icur+1+ncols);
  240.         CONTRIBUTE_TO_GRID(icur+ncols);
  241.         CONTRIBUTE_TO_GRID(icur-1+ncols);
  242.     }
  243. #undef CONTRIBUTE_TO_GRID
  244.  
  245.     /* pass two: redraw and prune */
  246.     picur=&ifirst;
  247.     while (*picur>=0) {
  248.         /* a cell only if 2 neighbors and was a cell, or 3 neighbors */
  249.         if (((Grid[*picur].neighbors==2 && Grid[*picur].color) ||
  250.              Grid[*picur].neighbors==3)) {
  251.             if (Grid[*picur].color<COLORS-1) {
  252.                 Grid[*picur].color++;
  253.                 [self putSquare:(*picur)%ncols :(*picur)/ncols
  254.                       Color: Grid[*picur].color];
  255.             }
  256.             Grid[*picur].neighbors=0;
  257.             picur=&(Grid[*picur].next); /* advance */
  258.         } else {
  259.             if (Grid[*picur].color) {
  260.                 [self putSquare:(*picur)%ncols :(*picur)/ncols
  261.                           Color: 0];
  262.                 checksum += (*picur * *picur);
  263.             }
  264.             Grid[*picur].color=(-1);
  265.             Grid[*picur].neighbors=0;
  266.             *picur=Grid[*picur].next; /* delete from list and advance */
  267.         }
  268.     }
  269.  
  270.     /* empty anything left in the drawing buffers */
  271.     [self flushSquares];
  272.     
  273.     /* check for termination if things are cycling */
  274.     [self checkStasis:checksum];
  275.  
  276.     return self;
  277. }
  278.  
  279.  
  280. - drawSquares
  281. {
  282.     int icur;
  283.  
  284.     for (icur=ifirst; icur>=0; icur=Grid[icur].next) {
  285.         [self putSquare:(icur%ncols):(icur/ncols)
  286.               Color:Grid[icur].color];
  287.     }
  288.     [self flushSquares];
  289.     return self;
  290. }
  291.  
  292. - putSquare:(int)x :(int)y Color:(int)color
  293. {
  294.     NXRect *newsquare;
  295. #if DEEPEDGE
  296.     if (y<(1+DEEPEDGE) || y>=nrows-(1+DEEPEDGE) ||
  297.         x<(1+DEEPEDGE) || x>=ncols-(1+DEEPEDGE)) return self;
  298. #endif
  299.     if (squareCount[color]>=SQUAREBLOCK) [self flushColor:color];
  300.     newsquare = squareBuffer[color]+squareCount[color];
  301.     newsquare->origin.x=x*cellSize+xoffset;
  302.     newsquare->origin.y=y*cellSize+yoffset;
  303.     squareCount[color]++;
  304.  
  305.     return self;
  306. }
  307.  
  308. - flushColor:(int)color
  309. {
  310.     if (squareCount[color]) {
  311.         NXSetColor(colorTable[color]);
  312.         NXRectFillList(squareBuffer[color],squareCount[color]);
  313.         squareCount[color]=0;
  314.     }
  315.     return self;
  316. }
  317.  
  318. - flushSquares
  319. {
  320.     int color;
  321.     for (color = 0; color<COLORS; color++) {
  322.         [self flushColor: color];
  323.     }
  324.     return self;
  325. }
  326.         
  327. - drawSelf:(const NXRect *)rects :(int)rectCount
  328. {
  329.     PSsetgray(NX_BLACK);
  330.     if (rectCount>1) {
  331.         NXRectFillList(rects+1,rectCount-1);
  332.     } else {
  333.         NXRectFill(rects);
  334.     }
  335.     if (countDown!=ITERATIONS) [self drawSquares];
  336.     return self;
  337. }
  338.  
  339. - (const char *) windowTitle
  340. {return "Life";}
  341.  
  342. - setupSquareBuffer
  343. {
  344.     int i,b;
  345.     for (i=0; i<COLORS; i++) {
  346.         squareCount[i]=0;
  347.         for (b=0; b<SQUAREBLOCK; b++) {
  348.             squareBuffer[i][b].origin.x=0;
  349.             squareBuffer[i][b].origin.y=0;
  350.             squareBuffer[i][b].size.width=cellSize;
  351.             squareBuffer[i][b].size.height=cellSize;
  352.         }
  353.     }
  354.     return self;
  355. }    
  356.  
  357. - initFrame:(const NXRect *)frameRect
  358. {
  359.     [super initFrame:frameRect];
  360.     
  361.     Grid=NULL;
  362.     [self getLifeDefaults];
  363.     [self computeColors];
  364.  
  365.     srandom(time(NULL));
  366.     [self setupSquareBuffer];
  367.     [self initLife];
  368.  
  369.     return self;
  370. }
  371.  
  372. - free
  373. {
  374.     if (Grid) NXZoneFree([self zone], Grid);
  375.     Grid=NULL;
  376.     return [super free];
  377. }
  378.  
  379. static void ColorToString(NXColor color, char *str)
  380. {
  381.     sprintf(str,"%f %f %f",NXRedComponent(color),
  382.             NXGreenComponent(color),NXBlueComponent(color));
  383. }
  384.  
  385. static NXColor ColorFromString(const char *str)
  386. {
  387.     NXColor color;
  388.     float r,g,b;
  389.     sscanf(str,"%f %f %f",&r,&g,&b);
  390.     r=(r<0.0 ? 0.0 : (r>1.0 ? 1.0 : r));
  391.     g=(g<0.0 ? 0.0 : (g>1.0 ? 1.0 : g));
  392.     b=(b<0.0 ? 0.0 : (b>1.0 ? 1.0 : b));
  393.     color=NXConvertRGBToColor(r,g,b);
  394.     return color;
  395. }
  396.     
  397. - getLifeDefaults
  398. {
  399.     static NXDefaultsVector LifeDefaults = {
  400.         {"LifeYoungColor", "0.083331 0.000000 1.000000"},
  401.         {"LifeMediumColor","0.916669 1.000000 0.000000"},
  402.         {"LifeOldColor",   "0.400005 0.000000 0.066668"},
  403.         {"LifeCellSize",   "8"},
  404.         {"LifeClusters",   "2"},
  405.         {"LifeFrameDelay", "16"},
  406.         {NULL}
  407.     };
  408.     
  409.     NXRegisterDefaults([NXApp appName],LifeDefaults);
  410.     youngColor=
  411.       ColorFromString(NXGetDefaultValue([NXApp appName],"LifeYoungColor"));
  412.     mediumColor=
  413.       ColorFromString(NXGetDefaultValue([NXApp appName],"LifeMediumColor"));
  414.     oldColor=
  415.       ColorFromString(NXGetDefaultValue([NXApp appName],"LifeOldColor"));
  416.     sscanf(NXGetDefaultValue([NXApp appName],"LifeCellSize"),"%d",&cellSize);
  417.     sscanf(NXGetDefaultValue([NXApp appName],"LifeClusters"),"%d",&clusters);
  418.     sscanf(NXGetDefaultValue([NXApp appName],"LifeFrameDelay"),"%d",&delay);
  419.     if (cellSize<MINCELLSIZE) cellSize=MINCELLSIZE;
  420.     if (cellSize>MAXCELLSIZE) cellSize=MAXCELLSIZE;
  421.     if (clusters<MINCLUSTERS) clusters=MINCLUSTERS;
  422.     if (clusters>MAXCLUSTERS) clusters=MAXCLUSTERS;
  423.     if (delay<MINDELAY) delay=MINDELAY;
  424.     if (delay>MAXDELAY) delay=MAXDELAY;
  425.  
  426.     return self;
  427. }
  428.  
  429.  
  430. - sizeTo:(NXCoord)width :(NXCoord)height
  431. {
  432.     [super sizeTo:width :height];
  433.     [self initLife];
  434.     return self;
  435. }
  436.  
  437. /* clear the field and do some random seeding */
  438. - initLife
  439. {
  440.     int x,y,xr,yr,xo,yo,xa,ya,i,count,repeat,size,t;
  441.  
  442.     /* frame the field */
  443.     ncols=MIN((bounds.size.width/cellSize+2+2*DEEPEDGE),MAXCOLS);
  444.     nrows=MIN((bounds.size.height/cellSize+2+2*DEEPEDGE),MAXROWS);
  445.     if (ncols<4) ncols=4;
  446.     if (nrows<4) nrows=4;
  447.     t=(int)bounds.size.width-(ncols-2-2*DEEPEDGE)*cellSize;
  448.     xoffset=(t<0 ? t/2-(1+DEEPEDGE)*cellSize :
  449.              random()%(t+1)-(1+DEEPEDGE)*cellSize);
  450.     t=(int)bounds.size.height-(nrows-2-2*DEEPEDGE)*cellSize;
  451.     yoffset=(t<0 ? t/2-(1+DEEPEDGE)*cellSize :
  452.              random()%(t+1)-(1+DEEPEDGE)*cellSize);
  453.  
  454.     /* allocate grid */
  455.     if (Grid) NX_ZONEREALLOC([self zone],Grid,lifecell,nrows*ncols);
  456.     else NX_ZONEMALLOC([self zone],Grid,lifecell,nrows*ncols);
  457.  
  458.     /* clear the field */
  459.     [self clearLife];
  460.  
  461.     /* do some seeding: multiple patches if big field, one patch if smaller */
  462.     if (ncols>256 && nrows>256) {
  463.         size=96;
  464.         repeat=clusters;
  465.     } else {
  466.         size=128;
  467.         repeat=1;
  468.     }
  469.     while (repeat) {
  470.         if ((ncols-3)>size){xr=size/8+1;xo=1+random()%((ncols-3)-size+1);xa=1;}
  471.         else {xr=((ncols-3)/8)+1; xo=1; xa=((ncols-3)%8)+1;}
  472.         if ((nrows-3)>size){yr=size/8+1;yo=1+random()%((nrows-3)-size+1);ya=1;}
  473.         else {yr=((nrows-3)/8)+1; yo=1; ya=((nrows-3)%8)+1;}
  474.         for (count=MAX(xr*yr*4,100); count; count--) {
  475.             /* add up 8 rolls of dice for each coordinate */
  476.             for (i=0, x=xo+random()%xa; i<8; i++) x+=random()%xr;
  477.             for (i=0, y=yo+random()%ya; i<8; i++) y+=random()%yr;
  478.             if (Grid[x+y*ncols].color==(-1)) {
  479.                 Grid[x+y*ncols].color=0;
  480.                 Grid[x+y*ncols].next = ifirst;
  481.                 ifirst=x+y*ncols;
  482.             }
  483.             if (Grid[x+y*ncols].color<COLORS-1) Grid[x+y*ncols].color++;
  484.         }
  485.         repeat--;
  486.     }
  487.  
  488.     countDown = ITERATIONS;
  489.     lasttime = currentTimeInMs();
  490.  
  491.     return self;
  492. }
  493.  
  494. - clearLife
  495. {
  496.     int x,y,i;
  497.  
  498.     /* empty linked list of interesting cells */
  499.     ifirst = -1;
  500.  
  501.     /* clear the field */
  502.     for (x=0; x<ncols; x++) {
  503.         for (y=0; y<nrows; y++) {
  504.             if (x==0 || y==0 || x==ncols-1 || y==nrows-1) {
  505.                 Grid[x+y*ncols].color=(-2);
  506.             } else {
  507.                 Grid[x+y*ncols].neighbors=0;
  508.                 Grid[x+y*ncols].color=(-1);
  509.             }
  510.         }
  511.     }
  512.  
  513.     /* init stasis array */
  514.     for (i=0; i<STATSIZE; i++) stasis[i] = i;
  515.     strack = 0;
  516.     sindex = 0;
  517.     spass = 0;
  518.  
  519.     return self;
  520. }
  521.  
  522.  
  523. /* check for stasis (any period of repetition up to STATSIZE) */
  524. - checkStasis:(int)checksum
  525. {
  526.     int i;
  527.     if (!strack || stasis[(sindex+STATSIZE-strack)%STATSIZE]!=checksum) {
  528.         spass=0;
  529.         strack=0;
  530.         for (i=0; i<STATSIZE; i+=STATIVAL) {
  531.             if (stasis[i]==checksum) {
  532.                 if (i==sindex) strack=STATSIZE;
  533.                 else strack=(sindex+STATSIZE-i)%STATSIZE;
  534.                 break;
  535.             }
  536.         }
  537.     } else {
  538.         spass++;
  539.         if (spass>=STATIVAL) countDown=0; /* must match STATIVAL generations */
  540.                                           /* STATIVAL should be more than 2 */
  541.     }
  542.  
  543.     stasis[sindex++] = checksum;
  544.     if (sindex>=STATSIZE) sindex = 0;
  545.     return self;
  546. }
  547.  
  548. /* given old-cell color, young-cell color, and medium-cell color, */
  549. /* linearly interpolate the whole color table in HSB space */
  550. - computeColors
  551. {
  552.     float yhue, ysat, ybri;
  553.     float mhue, msat, mbri;
  554.     float ohue, osat, obri;
  555.     float chue;
  556.     int i;
  557.  
  558.     NXConvertColorToHSB(youngColor, &yhue, &ysat, &ybri);
  559.     NXConvertColorToHSB(mediumColor, &mhue, &msat, &mbri);
  560.     NXConvertColorToHSB(oldColor, &ohue, &osat, &obri);
  561.  
  562.     /* hue space is circular, so decide which direction to go */
  563.     /* (take the shortest path out of the two possible) */
  564.     if (yhue>mhue && yhue-mhue>0.5) yhue -= 1.0;
  565.     else if (mhue>yhue && mhue-yhue>0.5) yhue += 1.0;
  566.     if (ohue>mhue && ohue-mhue>0.5) ohue -= 1.0;
  567.     else if (mhue>ohue && mhue-ohue>0.5) ohue += 1.0;
  568.  
  569.     colorTable[0]=NXConvertGrayToColor(NX_BLACK);
  570.     /* interpolate from young to medium */
  571.     for (i=1; i<COLORS/3; i++) {
  572.         chue=(float)(yhue*(COLORS/3-i)+mhue*(i-1))/(COLORS/3-1);
  573.         if (chue<0.0) chue += 1.0;
  574.         else if (chue>1.0) chue -= 1.0;
  575.         colorTable[i]=NXConvertHSBToColor(chue,
  576.             (float)(ysat*(COLORS/3-i)+msat*(i-1))/(COLORS/3-1),
  577.             (float)(ybri*(COLORS/3-i)+mbri*(i-1))/(COLORS/3-1));
  578.     }
  579.     /* from medium to old */
  580.     for (i=COLORS/3; i<COLORS; i++) {
  581.         chue=(float)(mhue*(COLORS-1-i)+ohue*(i-COLORS/3))/(COLORS-1-COLORS/3);
  582.         if (chue<0.0) chue += 1.0;
  583.         else if (chue>1.0) chue -= 1.0;
  584.         colorTable[i]=NXConvertHSBToColor(chue,
  585.             (float)(msat*(COLORS-1-i)+osat*(i-COLORS/3))/(COLORS-1-COLORS/3),
  586.             (float)(mbri*(COLORS-1-i)+obri*(i-COLORS/3))/(COLORS-1-COLORS/3));
  587.     }
  588.  
  589.     /* pass colors on to panel also, if needed */
  590.     if (panelLifeView && sharedInspectorPanel) {
  591.         [panelLifeView setYoungColor:youngColor
  592.                        MediumColor:mediumColor
  593.                        OldColor:oldColor];
  594.     }
  595.  
  596.     return self;
  597. }
  598.  
  599. /* quick update called when color has been changed */
  600. - updateViews
  601. {
  602.     /* update the panel view, if needed */
  603.     if (sharedInspectorPanel) [sharedInspectorPanel display];
  604.  
  605.     /* update myself */
  606.     if ([self canDraw]) {
  607.         [self lockFocus];
  608.         [self drawSquares];
  609.         [self unlockFocus];
  610.     }
  611.     return self;
  612. }
  613.  
  614. - inspector:sender
  615. {
  616.     char buf[MAXPATHLEN];
  617.     if (!sharedInspectorPanel) {
  618.         sprintf(buf,"%s/LifeInspector.nib",[sender moduleDirectory:"Life"]);
  619.         [NXApp loadNibFile:buf owner:self withNames:NO];
  620.         /* initialize some of the panel objects... */
  621.         if (panelYoungColorWell) [panelYoungColorWell setColor:youngColor];
  622.         if (panelMediumColorWell) [panelMediumColorWell setColor:mediumColor];
  623.         if (panelOldColorWell) [panelOldColorWell setColor:oldColor];
  624.         if (panelSizeMatrix) {
  625.             [panelSizeMatrix setEmptySelectionEnabled:YES];
  626.             if (![panelSizeMatrix selectCellWithTag:cellSize]) {
  627.                 [panelSizeMatrix selectCellAt:-1:-1];
  628.             } else {
  629.                 [panelSizeMatrix setEmptySelectionEnabled:NO];
  630.             }
  631.         }
  632.         if (panelStepButton) {
  633.             [panelStepButton sendActionOn:NX_MOUSEDOWNMASK];
  634.             [panelStepButton setContinuous:YES];
  635.             [panelStepButton setPeriodicDelay:PANELTIME/1000.0
  636.                              andInterval:PANELTIME/1000.0];
  637.         }
  638.         [self computeColors]; /* updates color table inside panelLifeView */
  639.     }
  640.     return sharedInspectorPanel;
  641. }
  642.  
  643. - inspectorInstalled
  644. {
  645.     [self hideRHSizeButton:self];
  646.     [self hideCredits:self];
  647.     if (sharedInspectorPanel) [sharedInspectorPanel display];
  648.     if (panelLifeView && sharedInspectorPanel) {
  649.         running=NO;
  650.         [self perform:@selector(animateSingleStep:) with: (id)5
  651.               afterDelay:PANELTIME cancelPrevious:YES];
  652.     }
  653.     return self;
  654. }
  655.  
  656. - takeYoungColorFrom:sender
  657. {
  658.     char str[80];
  659.     youngColor=[(NXColorWell *)sender color];
  660.     [self computeColors];
  661.     [self updateViews];
  662.     ColorToString(youngColor,str);
  663.     NXWriteDefault([NXApp appName],"LifeYoungColor",str);
  664.     return self;
  665. }
  666.  
  667. - takeMediumColorFrom:sender
  668. {
  669.     char str[80];
  670.     mediumColor=[(NXColorWell *)sender color];
  671.     [self computeColors];
  672.     [self updateViews];
  673.     ColorToString(mediumColor,str);
  674.     NXWriteDefault([NXApp appName],"LifeMediumColor",str);
  675.     return self;
  676. }
  677.  
  678. - takeOldColorFrom:sender
  679. {
  680.     char str[80];
  681.     oldColor=[(NXColorWell *)sender color];
  682.     [self computeColors];
  683.     [self updateViews];
  684.     ColorToString(oldColor,str);
  685.     NXWriteDefault([NXApp appName],"LifeOldColor",str);
  686.     return self;
  687. }
  688.  
  689. - doSizeMatrix:sender
  690. {
  691.     char str[80];
  692.     int newSize;
  693.     id selected;
  694.     selected=[sender selectedCell];
  695.     if (selected) newSize=[selected tag];
  696.     if (!selected || cellSize==newSize ||
  697.         newSize<MINCELLSIZE || newSize>MAXCELLSIZE) {
  698.         if (![panelSizeMatrix selectCellWithTag:cellSize]) {
  699.             [panelSizeMatrix selectCellAt:-1:-1];
  700.         }
  701.         return self;
  702.     }
  703.     if ([sender isEmptySelectionEnabled]) [sender setEmptySelectionEnabled:NO];
  704.     cellSize=newSize;
  705.     sprintf(str,"%d",cellSize);
  706.     NXWriteDefault([NXApp appName],"LifeCellSize",str);
  707.     [self setupSquareBuffer];
  708.     [self initLife];
  709.     [self display];
  710.     if (panelLifeView) {
  711.         [panelLifeView setLifeCellSize:cellSize];
  712.         [panelLifeView initLife];
  713.     }
  714.     if (sharedInspectorPanel) [sharedInspectorPanel display];
  715.     return self;
  716. }
  717.  
  718. - animateSingleStep:count
  719. {
  720.     if (running) return self;
  721.     [self doSingleStep:self];
  722.     if ((int)count) [self perform:@selector(animateSingleStep:)
  723.                           with:((id)((int)count-1))
  724.                           afterDelay:PANELTIME cancelPrevious:YES];
  725.     return self;
  726. }
  727.  
  728. - doSingleStep:sender
  729. {
  730.     /* should not do the panel animation if the view is not loaded or */
  731.     /* if it has been removed from the inspector window */
  732.     if (panelLifeView && sharedInspectorPanel &&
  733.         [panelLifeView canDraw] && [sharedInspectorPanel canDraw]) {
  734.         [panelLifeView lockFocus];
  735.         [panelLifeView oneStep];
  736.         [panelLifeView unlockFocus];
  737.         [sharedInspectorPanel display];
  738.     }
  739.     return self;
  740. }
  741.  
  742. - doRestart:sender
  743. {
  744.     if (panelLifeView) [panelLifeView initLife];
  745.     if (sharedInspectorPanel) [sharedInspectorPanel display];
  746.     return self;
  747. }
  748.  
  749. - doRHSizeButton:sender
  750. {
  751.     if ([sender isTransparent] && cellSize!=MINCELLSIZE) {
  752.         [self perform:@selector(showRHSizeButton:)
  753.               with:self afterDelay:0 cancelPrevious:YES];
  754.     } else {
  755.         [self perform:@selector(hideRHSizeButton:)
  756.               with:self afterDelay:0 cancelPrevious:YES];
  757.         if (cellSize!=MINCELLSIZE) {
  758.             char str[80];
  759.             [panelSizeMatrix setEmptySelectionEnabled:YES];
  760.             [panelSizeMatrix selectCellAt:-1:-1];
  761.             cellSize=MINCELLSIZE;
  762.             sprintf(str,"%d",cellSize);
  763.             NXWriteDefault([NXApp appName],"LifeCellSize",str);
  764.             [self setupSquareBuffer];
  765.             [self initLife];
  766.             [self display];
  767.             if (sharedInspectorPanel) {
  768.                 if (panelLifeView) {
  769.                     [panelLifeView setLifeCellSize:cellSize];
  770.                     [panelLifeView initLife];
  771.                 }
  772.                 [sharedInspectorPanel display];
  773.             }
  774.         }
  775.     }
  776.     return self;
  777. }
  778.  
  779. - showRHSizeButton:sender
  780. {
  781.     if (panelRHSizeButton && sharedInspectorPanel &&
  782.         [panelRHSizeButton isTransparent]) {
  783.         [panelRHSizeButton setTransparent:NO];
  784.     }
  785.     return self;
  786. }
  787.  
  788. - hideRHSizeButton:sender
  789. {
  790.     if (panelRHSizeButton && sharedInspectorPanel &&
  791.         ![panelRHSizeButton isTransparent]) {
  792.         [panelRHSizeButton setTransparent:YES];
  793.     }
  794.     return self;
  795. }    
  796.         
  797. - showCredits:sender
  798. {
  799.     if (panelCreditsView && sharedInspectorPanel) {
  800.         if ([panelCreditsView isDescendantOf:sharedInspectorPanel]) {
  801.             [panelCreditsView removeFromSuperview];
  802.         }
  803.         [sharedInspectorPanel addSubview:panelCreditsView];
  804.         [sharedInspectorPanel display];
  805.     }
  806.     return self;
  807. }
  808.  
  809. - hideCredits:sender
  810. {
  811.     if (panelCreditsView && sharedInspectorPanel) {
  812.         if ([panelCreditsView isDescendantOf:sharedInspectorPanel]) {
  813.             [panelCreditsView removeFromSuperview];
  814.         }
  815.         [sharedInspectorPanel display];
  816.     }
  817.     return self;
  818. }
  819.  
  820. /* these methods are needed because we should not rearrange the view */
  821. /* hierarchy while a button is active; we queue the rearranging to be */
  822. /* done later as an event, when nothing is locked on the view */
  823.  
  824. - doShowCredits:sender
  825. {
  826.     [self perform:@selector(hideRHSizeButton:)
  827.           with:self afterDelay:0 cancelPrevious:YES];
  828.     [self perform:@selector(showCredits:)
  829.           with:self afterDelay:0 cancelPrevious:YES];
  830.     return self;
  831. }
  832.  
  833. - doHideCredits:sender
  834. {
  835.     [self perform:@selector(hideCredits:)
  836.           with:self afterDelay:0 cancelPrevious:YES];
  837.     return self;
  838. }
  839.  
  840. @end
  841.  
  842.  
  843.