ftp.nice.ch/pub/next/tools/screen/backspace/Life.NIHS.bs.tar.gz#/LifeView.BackModule/LifeView.m

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

// LifeView by David Bau
// Copyright 1994 by David Bau.  All rights reserved.
//
// I feel silly even saying this, but please don't charge for this
// module or any product containing a portion or modification of it,
// and when distributing, please give credit where credit is due.
//
// If you add anything to this module or derive anything interesting
// from it, please let me know!
//
// I'll probably be bau@cs.cornell.edu for a while.
//
// David Bau 01/13/94
// 777 South Avenue, Weston, MA 02193
// 
// This code is shamelessly ripped off of Sam Streeper's Life module.
// Here is Sam's original description.
//
// ****
// 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.
// ...
// 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)
// ...
// ****
// 
// I've changed several things.  First, the method for computing and
// drawing cells was changed to a sparse algorithm that traverses a
// list of live cells.  This way empty cells are looked at as little
// as possible, which is a big win when the field is mostly empty.
// As a nice side effect, the squares get drawn in a (more or less)
// uniformly random order, which gives a smoother visual effect
// without any left-to-right flickering.
//
// The drawing-buffering code was cleaned up.  Smaller lists of rects
// are used, but one for each color.  No noticable performance hit.
//
// A hack in drawSelf was added to work around the double-refresh
// problem when the screensaver first kicks in. (countDown!=ITERATIONS)
//
// The initLife seeding algorithm was changed.  Now it puts inital
// cells in a small circular patch, leaving the rest of the field
// empty, which is much better for the sparse algorithm.  For big
// windows, the circular patch appears in a random starting place.
//
// Stasis checking was changed.  Now it automatically deduces any
// stasis period (up to the size of the stasis buffer), but it takes
// more generations for stasis to be detected.
//
// The color table was generalized to dish out NXColors, instead of
// just hues.  Cell size was generalized.
//
// The control panel was souped up.  Now the color table can be
// changed by the user.  The cell sizes can be changed.  The panel
// animation appears (slowly) partially obscured by the panel
// controls.  The panel animation can be stepped manually by the
// user. Defaults are saved in the user's defaults database.
// Programming the panel was the hardest part, but the result
// looks pretty nice.  I'm beginning to appreciate IB.
//
// 10/20/93 fixes and improvements: used perform:with:afterDelay to
// credits button remove itself correctly.  Also used the same method
// to do the panel animation in a more polite, lazy way.  Changed
// "step" button to a continuous button.  Fixed the timed panel
// animation so it doesn't draw if the view is gone from the window.
//
// 10/23/93 Changed sizing buttons to a matrix of buttons.  Stop
// timed panel animation if already doing an animation somewhere else
// (window or background) by making sure countDown isn't changing.
//
// 10/27/93 Added LIVEEDGE boundary convention.  (Later removed.)
// Made it so drawing can go right up to the edge of the window,
// using a random offset when it can't completely fill it. Removed
// unneeded calls to flushColor to avoid NXSetColor calls.
//
// 10/29/93 Added DEEPEDGE, so field can extend beyond the visible screen.
// turned off LIVEEDGE feature.  Fine tuned seeding parameters.
//
// 11/05/93 Changed MINCELLSIZE to 2, which can be obtained by a dwrite
// to SparseLifeCellSize only.  Played with matrix of radio buttons for
// so none are highlighed when cellSize matches none of them (e.g.=2),
// but so that once the user starts selecting them, they go into
// setEmptySelectionEnabled:NO mode.
//
// 11/12/93 Changed seeding to make more patches when field is very large.
//
// 11/13/93 Modified by Richard Hess (rhess@consilium.com)
// Added a "transparent" button in the upper right corner of the
// control panel which sets the cell size to MINCELLSIZE if the 
// mouse is clicked in it...
//
// 01/09/94 Fixed allocation of Grid so only as much memory is allocated
// as is needed.
//
// 01/12/94 Put a limit on the maximum animation speed to fight flickering.
// Changed name from SparseLife to just Life.
//
// 01/13/94 Fixed a memory leak pointed out by Sam Streeper.
// 01/13/94 Modified by Richard Hess (rhess@consilium.com)
// Added a LifeCluster default mechanism for setting the number of patches
// to be generated when your running very small cell sizes on a big screen...
//
// 01/14/94 Eliminated lockfocusing when [self canDraw]==NO.  Centered
// static life view correctly.
//
// 01/15/94 Put lower bound on ncols, nrows, as in MazeView.  Added
// LifeFrameDelay default.

#import <appkit/appkit.h>
#import <defaults/defaults.h>
#import <libc.h>
#import <time.h>
#import <dpsclient/wraps.h>
#import "LifeView.h"
#import "Thinker.h"


/* the kind of Life view that appears in the control panel */
@implementation StaticLifeView

- initLife
{
    int x,y,count,t;

    /* frame the field 2 cells bigger than the view */
    ncols=MIN((bounds.size.width/cellSize+2+2*DEEPEDGE+2),MAXCOLS);
    nrows=MIN((bounds.size.height/cellSize+2+2*DEEPEDGE+2),MAXROWS);
    if (ncols<4) ncols=4;
    if (nrows<4) nrows=4;
    t=(int)bounds.size.width-(ncols-2-2*DEEPEDGE)*cellSize;
    xoffset=(t<0 ? t/2-(1+DEEPEDGE)*cellSize :
             random()%(t+1)-(1+DEEPEDGE)*cellSize);
    t=(int)bounds.size.height-(nrows-2-2*DEEPEDGE)*cellSize;
    yoffset=(t<0 ? t/2-(1+DEEPEDGE)*cellSize :
             random()%(t+1)-(1+DEEPEDGE)*cellSize);

    /* allocate grid */
    if (Grid) NX_ZONEREALLOC([self zone],Grid,lifecell,nrows*ncols);
    NX_ZONEMALLOC([self zone],Grid,lifecell,nrows*ncols);

    /* clear the field */
    [self clearLife];

    /* use uniform random seeding */
    for (count=ncols*nrows/5; count; count--) {
        x=random()%(ncols-2)+1;
        y=random()%(nrows-2)+1;
        if (Grid[x+y*ncols].color==(-1)) {
            Grid[x+y*ncols].color=0;
            Grid[x+y*ncols].next=ifirst;
            ifirst=x+y*ncols;
        }
        if (Grid[x+y*ncols].color<COLORS-1) Grid[x+y*ncols].color++;
    }

    /* don't go for too many generations.  Seems it would be boring. */
    countDown = 1000;
    lasttime = currentTimeInMs();

    return self;
}
        
/* main view can tell panel view what colors to use */
- setYoungColor:(NXColor)yc MediumColor:(NXColor)mc OldColor:(NXColor)oc
{
    youngColor=yc;
    mediumColor=mc;
    oldColor=oc;
    [self computeColors];
    return self;
}

/* main view can tell panel what cell size to use */
- setLifeCellSize:(int)cs;
{
    cellSize=cs;
    [self setupSquareBuffer];
    return self;
}

@end






@implementation LifeView

/* here's where the sparse computation and drawing is done */
- oneStep
{
    BStimeval curtime;
    int icur, *picur;
    int checksum = 0;

    running=YES;
    curtime=currentTimeInMs();
    if (curtime-lasttime<delay) {
        if (delay-(curtime-lasttime)>30) {
            usleep(15000);
            return self;
        }
        usleep((delay-(curtime-lasttime))*1000);
        curtime=currentTimeInMs();
    }
    lasttime=curtime;

    /* finished */
    if (--countDown < 0)
    {
        [self initLife];
        [self display];
    }

    /* pass one: count up neighbors.  Hope gcc does cse well... */
#define CONTRIBUTE_TO_GRID(iadj)         \
    if (Grid[iadj].color==(-1)) {        \
        Grid[iadj].color=0;              \
        Grid[iadj].next=ifirst;          \
        ifirst=iadj;                     \
    }                                    \
    Grid[iadj].neighbors++;

    for (icur=ifirst; icur>=0; icur=Grid[icur].next) {
        /* contribute to the west */
        CONTRIBUTE_TO_GRID(icur-1);
        CONTRIBUTE_TO_GRID(icur-1-ncols);
        CONTRIBUTE_TO_GRID(icur-ncols);
        CONTRIBUTE_TO_GRID(icur+1-ncols);
        CONTRIBUTE_TO_GRID(icur+1);
        CONTRIBUTE_TO_GRID(icur+1+ncols);
        CONTRIBUTE_TO_GRID(icur+ncols);
        CONTRIBUTE_TO_GRID(icur-1+ncols);
    }
#undef CONTRIBUTE_TO_GRID

    /* pass two: redraw and prune */
    picur=&ifirst;
    while (*picur>=0) {
        /* a cell only if 2 neighbors and was a cell, or 3 neighbors */
        if (((Grid[*picur].neighbors==2 && Grid[*picur].color) ||
             Grid[*picur].neighbors==3)) {
            if (Grid[*picur].color<COLORS-1) {
                Grid[*picur].color++;
                [self putSquare:(*picur)%ncols :(*picur)/ncols
                      Color: Grid[*picur].color];
            }
            Grid[*picur].neighbors=0;
            picur=&(Grid[*picur].next); /* advance */
        } else {
            if (Grid[*picur].color) {
                [self putSquare:(*picur)%ncols :(*picur)/ncols
                          Color: 0];
                checksum += (*picur * *picur);
            }
            Grid[*picur].color=(-1);
            Grid[*picur].neighbors=0;
            *picur=Grid[*picur].next; /* delete from list and advance */
        }
    }

    /* empty anything left in the drawing buffers */
    [self flushSquares];
    
    /* check for termination if things are cycling */
    [self checkStasis:checksum];

    return self;
}


- drawSquares
{
    int icur;

    for (icur=ifirst; icur>=0; icur=Grid[icur].next) {
        [self putSquare:(icur%ncols):(icur/ncols)
              Color:Grid[icur].color];
    }
    [self flushSquares];
    return self;
}

- putSquare:(int)x :(int)y Color:(int)color
{
    NXRect *newsquare;
#if DEEPEDGE
    if (y<(1+DEEPEDGE) || y>=nrows-(1+DEEPEDGE) ||
        x<(1+DEEPEDGE) || x>=ncols-(1+DEEPEDGE)) return self;
#endif
    if (squareCount[color]>=SQUAREBLOCK) [self flushColor:color];
    newsquare = squareBuffer[color]+squareCount[color];
    newsquare->origin.x=x*cellSize+xoffset;
    newsquare->origin.y=y*cellSize+yoffset;
    squareCount[color]++;

    return self;
}

- flushColor:(int)color
{
    if (squareCount[color]) {
        NXSetColor(colorTable[color]);
        NXRectFillList(squareBuffer[color],squareCount[color]);
        squareCount[color]=0;
    }
    return self;
}

- flushSquares
{
    int color;
    for (color = 0; color<COLORS; color++) {
        [self flushColor: color];
    }
    return self;
}
        
- drawSelf:(const NXRect *)rects :(int)rectCount
{
    PSsetgray(NX_BLACK);
    if (rectCount>1) {
        NXRectFillList(rects+1,rectCount-1);
    } else {
        NXRectFill(rects);
    }
    if (countDown!=ITERATIONS) [self drawSquares];
    return self;
}

- (const char *) windowTitle
{return "Life";}

- setupSquareBuffer
{
    int i,b;
    for (i=0; i<COLORS; i++) {
        squareCount[i]=0;
        for (b=0; b<SQUAREBLOCK; b++) {
            squareBuffer[i][b].origin.x=0;
            squareBuffer[i][b].origin.y=0;
            squareBuffer[i][b].size.width=cellSize;
            squareBuffer[i][b].size.height=cellSize;
        }
    }
    return self;
}    

- initFrame:(const NXRect *)frameRect
{
    [super initFrame:frameRect];
    
    Grid=NULL;
    [self getLifeDefaults];
    [self computeColors];

    srandom(time(NULL));
    [self setupSquareBuffer];
    [self initLife];

    return self;
}

- free
{
    if (Grid) NXZoneFree([self zone], Grid);
    Grid=NULL;
    return [super free];
}

static void ColorToString(NXColor color, char *str)
{
    sprintf(str,"%f %f %f",NXRedComponent(color),
            NXGreenComponent(color),NXBlueComponent(color));
}

static NXColor ColorFromString(const char *str)
{
    NXColor color;
    float r,g,b;
    sscanf(str,"%f %f %f",&r,&g,&b);
    r=(r<0.0 ? 0.0 : (r>1.0 ? 1.0 : r));
    g=(g<0.0 ? 0.0 : (g>1.0 ? 1.0 : g));
    b=(b<0.0 ? 0.0 : (b>1.0 ? 1.0 : b));
    color=NXConvertRGBToColor(r,g,b);
    return color;
}
    
- getLifeDefaults
{
    static NXDefaultsVector LifeDefaults = {
        {"LifeYoungColor", "0.083331 0.000000 1.000000"},
        {"LifeMediumColor","0.916669 1.000000 0.000000"},
        {"LifeOldColor",   "0.400005 0.000000 0.066668"},
        {"LifeCellSize",   "8"},
        {"LifeClusters",   "2"},
        {"LifeFrameDelay", "16"},
        {NULL}
    };
    
    NXRegisterDefaults([NXApp appName],LifeDefaults);
    youngColor=
      ColorFromString(NXGetDefaultValue([NXApp appName],"LifeYoungColor"));
    mediumColor=
      ColorFromString(NXGetDefaultValue([NXApp appName],"LifeMediumColor"));
    oldColor=
      ColorFromString(NXGetDefaultValue([NXApp appName],"LifeOldColor"));
    sscanf(NXGetDefaultValue([NXApp appName],"LifeCellSize"),"%d",&cellSize);
    sscanf(NXGetDefaultValue([NXApp appName],"LifeClusters"),"%d",&clusters);
    sscanf(NXGetDefaultValue([NXApp appName],"LifeFrameDelay"),"%d",&delay);
    if (cellSize<MINCELLSIZE) cellSize=MINCELLSIZE;
    if (cellSize>MAXCELLSIZE) cellSize=MAXCELLSIZE;
    if (clusters<MINCLUSTERS) clusters=MINCLUSTERS;
    if (clusters>MAXCLUSTERS) clusters=MAXCLUSTERS;
    if (delay<MINDELAY) delay=MINDELAY;
    if (delay>MAXDELAY) delay=MAXDELAY;

    return self;
}


- sizeTo:(NXCoord)width :(NXCoord)height
{
    [super sizeTo:width :height];
    [self initLife];
    return self;
}

/* clear the field and do some random seeding */
- initLife
{
    int x,y,xr,yr,xo,yo,xa,ya,i,count,repeat,size,t;

    /* frame the field */
    ncols=MIN((bounds.size.width/cellSize+2+2*DEEPEDGE),MAXCOLS);
    nrows=MIN((bounds.size.height/cellSize+2+2*DEEPEDGE),MAXROWS);
    if (ncols<4) ncols=4;
    if (nrows<4) nrows=4;
    t=(int)bounds.size.width-(ncols-2-2*DEEPEDGE)*cellSize;
    xoffset=(t<0 ? t/2-(1+DEEPEDGE)*cellSize :
             random()%(t+1)-(1+DEEPEDGE)*cellSize);
    t=(int)bounds.size.height-(nrows-2-2*DEEPEDGE)*cellSize;
    yoffset=(t<0 ? t/2-(1+DEEPEDGE)*cellSize :
             random()%(t+1)-(1+DEEPEDGE)*cellSize);

    /* allocate grid */
    if (Grid) NX_ZONEREALLOC([self zone],Grid,lifecell,nrows*ncols);
    else NX_ZONEMALLOC([self zone],Grid,lifecell,nrows*ncols);

    /* clear the field */
    [self clearLife];

    /* do some seeding: multiple patches if big field, one patch if smaller */
    if (ncols>256 && nrows>256) {
        size=96;
        repeat=clusters;
    } else {
        size=128;
        repeat=1;
    }
    while (repeat) {
        if ((ncols-3)>size){xr=size/8+1;xo=1+random()%((ncols-3)-size+1);xa=1;}
        else {xr=((ncols-3)/8)+1; xo=1; xa=((ncols-3)%8)+1;}
        if ((nrows-3)>size){yr=size/8+1;yo=1+random()%((nrows-3)-size+1);ya=1;}
        else {yr=((nrows-3)/8)+1; yo=1; ya=((nrows-3)%8)+1;}
        for (count=MAX(xr*yr*4,100); count; count--) {
            /* add up 8 rolls of dice for each coordinate */
            for (i=0, x=xo+random()%xa; i<8; i++) x+=random()%xr;
            for (i=0, y=yo+random()%ya; i<8; i++) y+=random()%yr;
            if (Grid[x+y*ncols].color==(-1)) {
                Grid[x+y*ncols].color=0;
                Grid[x+y*ncols].next = ifirst;
                ifirst=x+y*ncols;
            }
            if (Grid[x+y*ncols].color<COLORS-1) Grid[x+y*ncols].color++;
        }
        repeat--;
    }

    countDown = ITERATIONS;
    lasttime = currentTimeInMs();

    return self;
}

- clearLife
{
    int x,y,i;

    /* empty linked list of interesting cells */
    ifirst = -1;

    /* clear the field */
    for (x=0; x<ncols; x++) {
        for (y=0; y<nrows; y++) {
            if (x==0 || y==0 || x==ncols-1 || y==nrows-1) {
                Grid[x+y*ncols].color=(-2);
            } else {
                Grid[x+y*ncols].neighbors=0;
                Grid[x+y*ncols].color=(-1);
            }
        }
    }

    /* init stasis array */
    for (i=0; i<STATSIZE; i++) stasis[i] = i;
    strack = 0;
    sindex = 0;
    spass = 0;

    return self;
}


/* check for stasis (any period of repetition up to STATSIZE) */
- checkStasis:(int)checksum
{
    int i;
    if (!strack || stasis[(sindex+STATSIZE-strack)%STATSIZE]!=checksum) {
        spass=0;
        strack=0;
        for (i=0; i<STATSIZE; i+=STATIVAL) {
            if (stasis[i]==checksum) {
                if (i==sindex) strack=STATSIZE;
                else strack=(sindex+STATSIZE-i)%STATSIZE;
                break;
            }
        }
    } else {
        spass++;
        if (spass>=STATIVAL) countDown=0; /* must match STATIVAL generations */
                                          /* STATIVAL should be more than 2 */
    }

    stasis[sindex++] = checksum;
    if (sindex>=STATSIZE) sindex = 0;
    return self;
}

/* given old-cell color, young-cell color, and medium-cell color, */
/* linearly interpolate the whole color table in HSB space */
- computeColors
{
    float yhue, ysat, ybri;
    float mhue, msat, mbri;
    float ohue, osat, obri;
    float chue;
    int i;

    NXConvertColorToHSB(youngColor, &yhue, &ysat, &ybri);
    NXConvertColorToHSB(mediumColor, &mhue, &msat, &mbri);
    NXConvertColorToHSB(oldColor, &ohue, &osat, &obri);

    /* hue space is circular, so decide which direction to go */
    /* (take the shortest path out of the two possible) */
    if (yhue>mhue && yhue-mhue>0.5) yhue -= 1.0;
    else if (mhue>yhue && mhue-yhue>0.5) yhue += 1.0;
    if (ohue>mhue && ohue-mhue>0.5) ohue -= 1.0;
    else if (mhue>ohue && mhue-ohue>0.5) ohue += 1.0;

    colorTable[0]=NXConvertGrayToColor(NX_BLACK);
    /* interpolate from young to medium */
    for (i=1; i<COLORS/3; i++) {
        chue=(float)(yhue*(COLORS/3-i)+mhue*(i-1))/(COLORS/3-1);
        if (chue<0.0) chue += 1.0;
        else if (chue>1.0) chue -= 1.0;
        colorTable[i]=NXConvertHSBToColor(chue,
            (float)(ysat*(COLORS/3-i)+msat*(i-1))/(COLORS/3-1),
            (float)(ybri*(COLORS/3-i)+mbri*(i-1))/(COLORS/3-1));
    }
    /* from medium to old */
    for (i=COLORS/3; i<COLORS; i++) {
        chue=(float)(mhue*(COLORS-1-i)+ohue*(i-COLORS/3))/(COLORS-1-COLORS/3);
        if (chue<0.0) chue += 1.0;
        else if (chue>1.0) chue -= 1.0;
        colorTable[i]=NXConvertHSBToColor(chue,
            (float)(msat*(COLORS-1-i)+osat*(i-COLORS/3))/(COLORS-1-COLORS/3),
            (float)(mbri*(COLORS-1-i)+obri*(i-COLORS/3))/(COLORS-1-COLORS/3));
    }

    /* pass colors on to panel also, if needed */
    if (panelLifeView && sharedInspectorPanel) {
        [panelLifeView setYoungColor:youngColor
                       MediumColor:mediumColor
                       OldColor:oldColor];
    }

    return self;
}

/* quick update called when color has been changed */
- updateViews
{
    /* update the panel view, if needed */
    if (sharedInspectorPanel) [sharedInspectorPanel display];

    /* update myself */
    if ([self canDraw]) {
        [self lockFocus];
        [self drawSquares];
        [self unlockFocus];
    }
    return self;
}

- inspector:sender
{
    char buf[MAXPATHLEN];
    if (!sharedInspectorPanel) {
        sprintf(buf,"%s/LifeInspector.nib",[sender moduleDirectory:"Life"]);
        [NXApp loadNibFile:buf owner:self withNames:NO];
        /* initialize some of the panel objects... */
        if (panelYoungColorWell) [panelYoungColorWell setColor:youngColor];
        if (panelMediumColorWell) [panelMediumColorWell setColor:mediumColor];
        if (panelOldColorWell) [panelOldColorWell setColor:oldColor];
        if (panelSizeMatrix) {
            [panelSizeMatrix setEmptySelectionEnabled:YES];
            if (![panelSizeMatrix selectCellWithTag:cellSize]) {
                [panelSizeMatrix selectCellAt:-1:-1];
            } else {
                [panelSizeMatrix setEmptySelectionEnabled:NO];
            }
        }
        if (panelStepButton) {
            [panelStepButton sendActionOn:NX_MOUSEDOWNMASK];
            [panelStepButton setContinuous:YES];
            [panelStepButton setPeriodicDelay:PANELTIME/1000.0
                             andInterval:PANELTIME/1000.0];
        }
        [self computeColors]; /* updates color table inside panelLifeView */
    }
    return sharedInspectorPanel;
}

- inspectorInstalled
{
    [self hideRHSizeButton:self];
    [self hideCredits:self];
    if (sharedInspectorPanel) [sharedInspectorPanel display];
    if (panelLifeView && sharedInspectorPanel) {
        running=NO;
        [self perform:@selector(animateSingleStep:) with: (id)5
              afterDelay:PANELTIME cancelPrevious:YES];
    }
    return self;
}

- takeYoungColorFrom:sender
{
    char str[80];
    youngColor=[(NXColorWell *)sender color];
    [self computeColors];
    [self updateViews];
    ColorToString(youngColor,str);
    NXWriteDefault([NXApp appName],"LifeYoungColor",str);
    return self;
}

- takeMediumColorFrom:sender
{
    char str[80];
    mediumColor=[(NXColorWell *)sender color];
    [self computeColors];
    [self updateViews];
    ColorToString(mediumColor,str);
    NXWriteDefault([NXApp appName],"LifeMediumColor",str);
    return self;
}

- takeOldColorFrom:sender
{
    char str[80];
    oldColor=[(NXColorWell *)sender color];
    [self computeColors];
    [self updateViews];
    ColorToString(oldColor,str);
    NXWriteDefault([NXApp appName],"LifeOldColor",str);
    return self;
}

- doSizeMatrix:sender
{
    char str[80];
    int newSize;
    id selected;
    selected=[sender selectedCell];
    if (selected) newSize=[selected tag];
    if (!selected || cellSize==newSize ||
        newSize<MINCELLSIZE || newSize>MAXCELLSIZE) {
        if (![panelSizeMatrix selectCellWithTag:cellSize]) {
            [panelSizeMatrix selectCellAt:-1:-1];
        }
        return self;
    }
    if ([sender isEmptySelectionEnabled]) [sender setEmptySelectionEnabled:NO];
    cellSize=newSize;
    sprintf(str,"%d",cellSize);
    NXWriteDefault([NXApp appName],"LifeCellSize",str);
    [self setupSquareBuffer];
    [self initLife];
    [self display];
    if (panelLifeView) {
        [panelLifeView setLifeCellSize:cellSize];
        [panelLifeView initLife];
    }
    if (sharedInspectorPanel) [sharedInspectorPanel display];
    return self;
}

- animateSingleStep:count
{
    if (running) return self;
    [self doSingleStep:self];
    if ((int)count) [self perform:@selector(animateSingleStep:)
                          with:((id)((int)count-1))
                          afterDelay:PANELTIME cancelPrevious:YES];
    return self;
}

- doSingleStep:sender
{
    /* should not do the panel animation if the view is not loaded or */
    /* if it has been removed from the inspector window */
    if (panelLifeView && sharedInspectorPanel &&
        [panelLifeView canDraw] && [sharedInspectorPanel canDraw]) {
        [panelLifeView lockFocus];
        [panelLifeView oneStep];
        [panelLifeView unlockFocus];
        [sharedInspectorPanel display];
    }
    return self;
}

- doRestart:sender
{
    if (panelLifeView) [panelLifeView initLife];
    if (sharedInspectorPanel) [sharedInspectorPanel display];
    return self;
}

- doRHSizeButton:sender
{
    if ([sender isTransparent] && cellSize!=MINCELLSIZE) {
        [self perform:@selector(showRHSizeButton:)
              with:self afterDelay:0 cancelPrevious:YES];
    } else {
        [self perform:@selector(hideRHSizeButton:)
              with:self afterDelay:0 cancelPrevious:YES];
        if (cellSize!=MINCELLSIZE) {
            char str[80];
            [panelSizeMatrix setEmptySelectionEnabled:YES];
            [panelSizeMatrix selectCellAt:-1:-1];
            cellSize=MINCELLSIZE;
            sprintf(str,"%d",cellSize);
            NXWriteDefault([NXApp appName],"LifeCellSize",str);
            [self setupSquareBuffer];
            [self initLife];
            [self display];
            if (sharedInspectorPanel) {
                if (panelLifeView) {
                    [panelLifeView setLifeCellSize:cellSize];
                    [panelLifeView initLife];
                }
                [sharedInspectorPanel display];
            }
        }
    }
    return self;
}

- showRHSizeButton:sender
{
    if (panelRHSizeButton && sharedInspectorPanel &&
        [panelRHSizeButton isTransparent]) {
        [panelRHSizeButton setTransparent:NO];
    }
    return self;
}

- hideRHSizeButton:sender
{
    if (panelRHSizeButton && sharedInspectorPanel &&
        ![panelRHSizeButton isTransparent]) {
        [panelRHSizeButton setTransparent:YES];
    }
    return self;
}    
        
- showCredits:sender
{
    if (panelCreditsView && sharedInspectorPanel) {
        if ([panelCreditsView isDescendantOf:sharedInspectorPanel]) {
            [panelCreditsView removeFromSuperview];
        }
        [sharedInspectorPanel addSubview:panelCreditsView];
        [sharedInspectorPanel display];
    }
    return self;
}

- hideCredits:sender
{
    if (panelCreditsView && sharedInspectorPanel) {
        if ([panelCreditsView isDescendantOf:sharedInspectorPanel]) {
            [panelCreditsView removeFromSuperview];
        }
        [sharedInspectorPanel display];
    }
    return self;
}

/* these methods are needed because we should not rearrange the view */
/* hierarchy while a button is active; we queue the rearranging to be */
/* done later as an event, when nothing is locked on the view */

- doShowCredits:sender
{
    [self perform:@selector(hideRHSizeButton:)
          with:self afterDelay:0 cancelPrevious:YES];
    [self perform:@selector(showCredits:)
          with:self afterDelay:0 cancelPrevious:YES];
    return self;
}

- doHideCredits:sender
{
    [self perform:@selector(hideCredits:)
          with:self afterDelay:0 cancelPrevious:YES];
    return self;
}

@end


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