This is MazeView.m in view mode; [Download] [Up]
// MazeView 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/21/94 // 777 South Avenue, Weston, MA 02193 // // 01/13/94 Fixed panel maze so that it doesn't change the first // time a main-view maze begins drawing; it only changes after the // main-view maze finishes. Also, fixed drawSelf to be more efficient. // Removed a floaing-point problem with the exponential speed dial. // Changed the default colors. // // 01/14/94 Eliminated lockFocus when [self canDraw]==NO // 01/14/94 Modified by Richard Hess (rhess@consilium.com) // Fixed startup problems when using MazeView with Engage! Desktop... // // 02/01/94 Changed maze generation algorithm to make trickier mazes, // using a second RandomIntPicker. #import <appkit/appkit.h> #import <defaults/defaults.h> #import <libc.h> #import <time.h> #import <dpsclient/wraps.h> #import "MazeView.h" #import "RandomIntPicker.h" #import "Thinker.h" static void ColorToString(NXColor color, char *str); static NXColor ColorFromString(const char *str); static NXColor RandomColor(); /* the kind of Maze view that appears in the control panel */ @implementation StaticMazeView - initMaze { /* frame the field 2 cells bigger than the view */ ncols=MIN((((int)bounds.size.width-wallSize)/cellSize+2),MAXCOLS); nrows=MIN((((int)bounds.size.height-wallSize)/cellSize+2),MAXROWS); if (ncols<4) ncols=4; if (nrows<4) nrows=4; xoffset=((int)bounds.size.width-(ncols-2)*cellSize-wallSize)/2-cellSize; yoffset=((int)bounds.size.height-(nrows-2)*cellSize-wallSize)/2-cellSize; /* allocate grid */ if (Grid) NX_ZONEREALLOC([self zone],Grid,mazecell,nrows*ncols); else NX_ZONEMALLOC([self zone],Grid,mazecell,nrows*ncols); [self computeMaze]; /* choose a random starting place and direction */ icur=(random()%(ncols-2)+1)+(random()%(nrows-2)+1)*ncols; dcur=random()%4; Grid[icur].bdir=4; ifirst=icur; Grid[icur].next=(-1); /* set the current colors */ curWallColor=wallColor; curPathColor=pathColor; /* clear drawing buffers */ wallRectListSize=0; pathRectListSize=0; /* quickly do half-of-a-maze-exploration */ countDown=((nrows-2)*(ncols-2)*2-2)/2; while (countDown--) { int inew; inew=icur; switch (dcur) { case 0: if (!Grid[icur].eastwall) {dcur=0; inew=icur+1; break;} case 1: if (!Grid[icur].northwall) {dcur=1; inew=icur+ncols; break;} case 2: if (!Grid[icur].westwall) {dcur=2; inew=icur-1; break;} case 3: if (!Grid[icur].southwall) {dcur=3; inew=icur-ncols; break;} if (!Grid[icur].eastwall) {dcur=0; inew=icur+1; break;} if (!Grid[icur].northwall) {dcur=1; inew=icur+ncols; break;} if (!Grid[icur].westwall) {dcur=2; inew=icur-1; break;} } if (Grid[icur].bdir==dcur) { Grid[icur].bdir=(-1); } else { Grid[inew].next=ifirst; ifirst=inew; Grid[inew].bdir=(dcur+2)%4; } icur=inew; dcur=(dcur+3)%4; } return self; } - drawSelf:(const NXRect *)rects :(int)rectCount { PSsetgray(NX_BLACK); if (rectCount>1) { NXRectFillList(rects+1,rectCount-1); } else { NXRectFill(rects); } return [super drawSelf:rects:rectCount]; } /* main view can tell panel view what colors to use */ - setWallColor:(NXColor)wc pathColor:(NXColor)pc { curWallColor=wallColor=wc; curPathColor=pathColor=pc; return self; } @end @implementation MazeView - initMaze { int t; /* frame the field 2 cells bigger than the view */ if (!cellSize) NXLogError("MazeView: cellSize is 0 in initMaze\n"); ncols=MIN((((int)bounds.size.width-wallSize)/cellSize+2),MAXCOLS); nrows=MIN((((int)bounds.size.height-wallSize)/cellSize+2),MAXROWS); if (ncols<4) ncols=4; if (nrows<4) nrows=4; t=(int)bounds.size.width-(ncols-2)*cellSize-wallSize; xoffset=(t<0 ? t/2-cellSize : random()%(t+1)-cellSize); t=(int)bounds.size.height-(nrows-2)*cellSize-wallSize; yoffset=(t<0 ? t/2-cellSize : random()%(t+1)-cellSize); /* allocate grid */ if (Grid) NX_ZONEREALLOC([self zone],Grid,mazecell,nrows*ncols); else NX_ZONEMALLOC([self zone],Grid,mazecell,nrows*ncols); [self computeMaze]; countDown=(-1); ifirst=(-1); return self; } - computeMaze { RandomIntPicker *picker1, *picker2; /* clear the "last" fields */ [self clearMaze]; /* compute the maze */ icur=(random()%(ncols-2)+1)+(random()%(nrows-2)+1)*ncols; Grid[icur].bdir=4; Grid[icur].eastwall=Grid[icur].northwall= Grid[icur].westwall=Grid[icur].southwall=1; picker1=[[RandomIntPicker alloc] init]; picker2=[[RandomIntPicker alloc] init]; while (1) { [picker1 reset]; [picker2 reset]; if (Grid[icur+1].bdir<0) { [picker1 insert:0]; if (Grid[icur+2].bdir<0) [picker2 insert:0]; } if (Grid[icur+ncols].bdir<0) { [picker1 insert:1]; if (Grid[icur+2*ncols].bdir<0) [picker2 insert:1]; } if (Grid[icur-1].bdir<0) { [picker1 insert:2]; if (Grid[icur-2].bdir<0) [picker2 insert:2]; } if (Grid[icur-ncols].bdir<0) { [picker1 insert:3]; if (Grid[icur-2*ncols].bdir<0) [picker2 insert:3]; } if ([picker1 count]) { int choice; choice=[picker2 count] ? [picker2 choice] : [picker1 choice]; switch(choice) { case 0: icur=icur+1; break; case 1: icur=icur+ncols; break; case 2: icur=icur-1; break; case 3: icur=icur-ncols; break; } Grid[icur].bdir=(choice+2)%4; Grid[icur].eastwall=Grid[icur].northwall= Grid[icur].westwall=Grid[icur].southwall=1; } else { switch (Grid[icur].bdir) { case 0: Grid[icur].eastwall=0; icur=icur+1; Grid[icur].westwall=0; break; case 1: Grid[icur].northwall=0; icur=icur+ncols; Grid[icur].southwall=0; break; case 2: Grid[icur].westwall=0; icur=icur-1; Grid[icur].eastwall=0; break; case 3: Grid[icur].southwall=0; icur=icur-ncols; Grid[icur].northwall=0; break; case 4: goto finish; } } } finish: [picker1 free]; [picker2 free]; /* walls are set; clear the "last" fields again */ [self clearMaze]; return self; } - firstStep { /* choose a random starting place and direction */ icur=(random()%(ncols-2)+1)+(random()%(nrows-2)+1)*ncols; dcur=random()%4; Grid[icur].bdir=4; ifirst=icur; Grid[icur].next=(-1); /* set the current colors */ if (randomColor) { curWallColor=RandomColor(); curPathColor=RandomColor(); } else { curWallColor=wallColor; curPathColor=pathColor; } if (sharedInspectorPanel) { if (panelMazeView) { [panelMazeView setWallColor:curWallColor pathColor:curPathColor]; } [sharedInspectorPanel display]; } /* clear drawing buffers */ wallRectListSize=0; pathRectListSize=0; /* clear drawing etc */ PSsetgray(NX_BLACK); NXRectFill(&bounds); [self drawForward:icur]; [self flushDrawing]; /* start countDown */ countDown=(nrows-2)*(ncols-2)*2-2; return self; } /* here's where the maze solving and drawing is done */ - oneStep { int inew; BStimeval curtime; /* enforce the delay between frames */ curtime=currentTimeInMs(); if (curtime-lasttime<delay) { if (delay-(curtime-lasttime)>30) { usleep(15000); return self; } usleep((delay-(curtime-lasttime))*1000); curtime=currentTimeInMs(); } lasttime=curtime; /* check if finished */ if (countDown<=0) { /* do the following only when recycling, not when running 1st time */ if (!countDown) { [self drawBack:icur]; [self flushDrawing]; NXPing(); if (panelMazeView) { [panelMazeView initMaze]; } [self initMaze]; } [self firstStep]; return self; } countDown--; /* follow-the-right-wall algorithm... switch fallthrough intentional */ inew=icur; switch (dcur) { case 0: if (!Grid[icur].eastwall) {dcur=0; inew=icur+1; break;} case 1: if (!Grid[icur].northwall) {dcur=1; inew=icur+ncols; break;} case 2: if (!Grid[icur].westwall) {dcur=2; inew=icur-1; break;} case 3: if (!Grid[icur].southwall) {dcur=3; inew=icur-ncols; break;} if (!Grid[icur].eastwall) {dcur=0; inew=icur+1; break;} if (!Grid[icur].northwall) {dcur=1; inew=icur+ncols; break;} if (!Grid[icur].westwall) {dcur=2; inew=icur-1; break;} } if (Grid[icur].bdir==dcur) { [self drawBack:icur]; Grid[icur].bdir=(-1); } else { Grid[inew].next=ifirst; ifirst=inew; Grid[inew].bdir=(dcur+2)%4; [self drawForward:inew]; } [self flushDrawing]; icur=inew; dcur=(dcur+3)%4; return self; } - drawForward:(int) index { int x,y; x=(index%ncols)*cellSize+xoffset; y=(index/ncols)*cellSize+yoffset; switch (Grid[index].bdir) { case 0: [self addPathRectOrigin: x+wallSize+gapSize:y+wallSize+gapSize size: cellSize:pathSize]; break; case 1: [self addPathRectOrigin: x+wallSize+gapSize:y+wallSize+gapSize size: pathSize:cellSize]; break; case 2: [self addPathRectOrigin: x-gapSize:y+wallSize+gapSize size: cellSize:pathSize]; break; case 3: [self addPathRectOrigin: x+wallSize+gapSize:y-gapSize size: pathSize:cellSize]; break; case 4: [self addPathRectOrigin:x+wallSize+gapSize:y+wallSize+gapSize size: pathSize:pathSize]; break; } if (Grid[index].eastwall && Grid[index+1].next==(-2)) { [self addWallRectOrigin:x+cellSize:y size:wallSize:cellSize+wallSize]; } if (Grid[index].northwall && Grid[index+ncols].next==(-2)) { [self addWallRectOrigin:x:y+cellSize size:cellSize+wallSize:wallSize]; } if (Grid[index].westwall) { [self addWallRectOrigin:x:y size:wallSize:cellSize+wallSize]; } if (Grid[index].southwall) { [self addWallRectOrigin:x:y size:cellSize+wallSize:wallSize]; } return self; } - drawBack:(int) index { int x,y; x=(index%ncols)*cellSize+xoffset; y=(index/ncols)*cellSize+yoffset; [self addEraseRectOrigin:x+wallSize+gapSize:y+wallSize+gapSize size:pathSize:pathSize]; switch (Grid[index].bdir) { case 0: [self addEraseRectOrigin: x+wallSize+gapSize:y+wallSize+gapSize size: cellSize:pathSize]; break; case 1: [self addEraseRectOrigin: x+wallSize+gapSize:y+wallSize+gapSize size: pathSize:cellSize]; break; case 2: [self addEraseRectOrigin: x-gapSize:y+wallSize+gapSize size: cellSize:pathSize]; break; case 3: [self addEraseRectOrigin: x+wallSize+gapSize:y-gapSize size: pathSize:cellSize]; break; case 4: [self addEraseRectOrigin:x+wallSize+gapSize:y+wallSize+gapSize size: pathSize:pathSize]; break; } return self; } - addWallRectOrigin:(int) x :(int) y size:(int)w :(int) h { wallRectList[wallRectListSize].origin.x=x; wallRectList[wallRectListSize].origin.y=y; wallRectList[wallRectListSize].size.height=h; wallRectList[wallRectListSize].size.width=w; if (++wallRectListSize>=RECTBUFSIZE) { [self flushWallRects]; } return self; } - flushWallRects { if (wallRectListSize) { NXSetColor(curWallColor); NXRectFillList(wallRectList, wallRectListSize); wallRectListSize=0; } return self; } - addPathRectOrigin:(int) x :(int) y size:(int)w :(int) h { pathRectList[pathRectListSize].origin.x=x; pathRectList[pathRectListSize].origin.y=y; pathRectList[pathRectListSize].size.height=h; pathRectList[pathRectListSize].size.width=w; if (++pathRectListSize>=RECTBUFSIZE) { [self flushPathRects]; } return self; } - flushPathRects { if (pathRectListSize) { NXSetColor(curPathColor); NXRectFillList(pathRectList, pathRectListSize); pathRectListSize=0; } return self; } - addEraseRectOrigin:(int) x :(int) y size:(int)w :(int) h { eraseRectList[eraseRectListSize].origin.x=x; eraseRectList[eraseRectListSize].origin.y=y; eraseRectList[eraseRectListSize].size.height=h; eraseRectList[eraseRectListSize].size.width=w; if (++eraseRectListSize>=RECTBUFSIZE) { [self flushEraseRects]; } return self; } - flushEraseRects { if (eraseRectListSize) { PSsetgray(NX_BLACK); NXRectFillList(eraseRectList, eraseRectListSize); eraseRectListSize=0; } return self; } - flushDrawing { [self flushWallRects]; [self flushPathRects]; [self flushEraseRects]; return self; } - drawSelf:(const NXRect *)rects :(int)rectCount { int i; for (i=ifirst; i>=0; i=Grid[i].next) { int x,y; x=i%ncols; y=i/ncols; if (x*cellSize-gapSize+xoffset<rects[0].origin.x+rects[0].size.width && x*cellSize+cellSize+wallSize+gapSize+xoffset>rects[0].origin.x && y*cellSize-gapSize+yoffset<rects[0].origin.y+rects[0].size.height && y*cellSize+cellSize+wallSize+gapSize+yoffset>rects[0].origin.y) { [self drawForward:i]; } } [self flushDrawing]; return self; } - (const char *) windowTitle { return "Maze"; } - initFrame:(const NXRect *)frameRect { Grid=NULL; [self getMazeDefaults]; [super initFrame:frameRect]; [self initMaze]; 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; } static NXColor RandomColor() { NXColor color; float r,g,b; r=((random()%65536)/65536.0)/2; g=((random()%65536)/65536.0)/2; b=((random()%65536)/65536.0)/2; color=NXConvertRGBToColor(r,g,b); return color; } - getMazeDefaults { char c; static NXDefaultsVector MazeDefaults = { {"MazeFrameDelay", "16"}, {"MazeRandomColor", "No"}, {"MazeWallColor", "0.333338 0.066668 0.066668"}, {"MazePathColor", "0.200003 0.333338 0.466674"}, {"MazeWallSize", "2"}, {"MazePathSize", "4"}, {"MazeGapSize", "1"}, {NULL} }; NXRegisterDefaults([NXApp appName],MazeDefaults); sscanf(NXGetDefaultValue([NXApp appName],"MazeFrameDelay"),"%d",&delay); if (delay<0) delay=0; if (delay>MAXDELAY) delay=MAXDELAY; if ((c=*NXGetDefaultValue([NXApp appName],"MazeRandomColor"))=='y' || c=='Y') { randomColor=YES; } else { randomColor=NO; } wallColor= ColorFromString(NXGetDefaultValue([NXApp appName],"MazeWallColor")); pathColor= ColorFromString(NXGetDefaultValue([NXApp appName],"MazePathColor")); sscanf(NXGetDefaultValue([NXApp appName],"MazeWallSize"),"%d",&wallSize); sscanf(NXGetDefaultValue([NXApp appName],"MazePathSize"),"%d",&pathSize); sscanf(NXGetDefaultValue([NXApp appName],"MazeGapSize"),"%d",&gapSize); cellSize=pathSize+wallSize+2*gapSize; if (cellSize<MINCELLSIZE) { pathSize+=MINCELLSIZE-cellSize; cellSize=MINCELLSIZE; } return self; } - sizeTo:(NXCoord)width :(NXCoord)height { [super sizeTo:width :height]; [self initMaze]; return self; } - clearMaze { int x,y; icur = -1; /* clear the field */ for (y=0; y<nrows; y++) { for (x=0; x<ncols; x++) { if (x==0 || y==0 || x==ncols-1 || y==nrows-1) { Grid[x+y*ncols].bdir=4; } else { Grid[x+y*ncols].bdir=(-1); } Grid[x+y*ncols].next=(-2); } } return self; } /* quick update called when color has been changed */ - updateViews: sender { /* update the panel view, if needed */ if (sharedInspectorPanel) { if (panelMazeView) { [panelMazeView setWallColor:curWallColor pathColor:curPathColor]; } [sharedInspectorPanel display]; } /* update myself */ if ([self canDraw]) { [self lockFocus]; [self display]; [self unlockFocus]; } return self; } - inspector:sender { char buf[MAXPATHLEN]; if (!sharedInspectorPanel) { sprintf(buf,"%s/MazeInspector.nib",[sender moduleDirectory:"Maze"]); [NXApp loadNibFile:buf owner:self withNames:NO]; /* initialize some of the panel objects... */ if (panelWallColorWell) { [panelWallColorWell setColor:wallColor]; [panelWallColorWell setEnabled:!randomColor]; } if (panelPathColorWell) { [panelPathColorWell setColor:pathColor]; [panelPathColorWell setEnabled:!randomColor]; } if (randomColorSwitch) [randomColorSwitch setState:randomColor]; if (panelSpeedSlider) { if (delay<1) { [panelSpeedSlider setFloatValue:1.0]; } else { [panelSpeedSlider setFloatValue: (float)(-log(delay/99.0)/log(100.0))]; } } } return sharedInspectorPanel; } - inspectorInstalled { [self hideCredits:self]; if (sharedInspectorPanel) { [sharedInspectorPanel display]; } return self; } - doRandomColorSwitch:sender { if ([sender state]) { [panelWallColorWell setEnabled:NO]; [panelPathColorWell setEnabled:NO]; randomColor=YES; curWallColor=RandomColor(); curPathColor=RandomColor(); NXWriteDefault([NXApp appName],"MazeRandomColor","Yes"); } else { [panelWallColorWell setEnabled:YES]; [panelPathColorWell setEnabled:YES]; randomColor=NO; curWallColor=wallColor; curPathColor=pathColor; NXWriteDefault([NXApp appName],"MazeRandomColor","No"); } [self perform:@selector(updateViews:) with:sender afterDelay:0 cancelPrevious:YES]; return self; } - doSpeedSlider:sender { char str[80]; delay=99.0*exp(-[sender floatValue]*log(100.0)); sprintf(str,"%d",delay); NXWriteDefault([NXApp appName],"MazeFrameDelay",str); return self; } - takeWallColorFrom:sender { char str[80]; curWallColor=wallColor=[(NXColorWell *)sender color]; [self perform:@selector(updateViews:) with:sender afterDelay:0 cancelPrevious:YES]; ColorToString(wallColor,str); NXWriteDefault([NXApp appName],"MazeWallColor",str); return self; } - takePathColorFrom:sender { char str[80]; curPathColor=pathColor=[(NXColorWell *)sender color]; [self perform:@selector(updateViews:) with:sender afterDelay:0 cancelPrevious:YES]; ColorToString(pathColor,str); NXWriteDefault([NXApp appName],"MazePathColor",str); 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, when nothing is locked on the view */ - doShowCredits:sender { [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.