This is TileControl.m in view mode; [Download] [Up]
// TileSlide v1.0 // By Kevin Brain (ksbrain@zeus.UWaterloo.ca) // with SoundGenerator class by Ali Ozer // Released to the public domain September, 1991 #import "TileControl.h" #import "SoundGenerator.h" #import <appkit/nextstd.h> //imports math.h,stdio.h,libc.h // (libc.h imports string.h) #import <appkit/Box.h> #import <appkit/Button.h> #import <appkit/Cell.h> #import <appkit/Text.h> // NXOrderStrings #import <appkit/Matrix.h> // cellAt:: method #import <appkit/Panel.h> // NXRunAlertPanel #import <appkit/OpenPanel.h> // for loading background #import <appkit/Application.h> // NX_BASETHRESHOLD #import <appkit/FontManager.h> // for changing size of button fonts #import <appkit/publicWraps.h> // for NXConvertWinNumToGlobal #import <appkit/Listener.h> // for NX_WORKSPACEREQUEST #import <appkit/Speaker.h> // for setSendPort #import <defaults.h> // for using defaults system #import <musickit/TuningSystem.h> #import <musickit/pitches.h> #import <next/machparam.h> // for DELAY (playing ending tune) // The scheme for the "music" is to play 2 note chords, where the notes are // selected from arrays of chords. The musical key changes every NOTESPERBAR notes. // The two arrays rowNotes and colNotes store the pitches. (The notes of the // rowNotes array are generally lower in pitch). A note from each array is // played whenever a tile is moved. The note from the rowNotes array is selected // by the row number clicked, while the notes within a given key (row) of the // colNotes array are played sequentially. #define NUMBEROFBARS 8 #define NOTESPERBAR 4 #define COMPUTEBAR ((int)((numberOfMoves%(NUMBEROFBARS*NOTESPERBAR)) /NOTESPERBAR)) #define SEQUENTIALNOTES ((numberOfMoves%(NUMBEROFBARS*NOTESPERBAR)) %NOTESPERBAR) #define ROWNOTE(k,n) (MKKeyNumToFreq (rowNotes[(k%NUMBEROFBARS)][(n%5)])) #define COLNOTE(k,n) (MKKeyNumToFreq (colNotes[(k%NUMBEROFBARS)][(n%5)])) static MKKeyNum rowNotes[NUMBEROFBARS][5] = { {g3k,c4k,e4k,g4k,c5k}, /* C major */ {c4k,f4k,a4k,c5k,f5k}, /* F major */ {e4k,g3k,c4k,g4k,c5k}, /* C major */ {g3k,b3k,d4k,g4k,b4k}, /* G major */ {g4k,c5k,g3k,c4k,e4k}, /* C major */ {c4k,e4k,a4k,c5k,a3k}, /* A minor */ {b3k,d4k,g4k,b4k,d5k}, /* G major */ {c5k,c4k,e4k,g4k,e5k}}; /* C major */ static MKKeyNum colNotes[NUMBEROFBARS][5] = { {e4k,g5k,e5k,c5k,g4k}, /* C major */ {a5k,f5k,c5k,a4k,f4k}, /* F major */ {c5k,e4k,g5k,e5k,g4k}, /* C major */ {b4k,d4k,d5k,g4k,g3k}, /* G major */ {c5k,g4k,g5k,e5k,e4k}, /* C major */ {c5k,e5k,a5k,e4k,a4k}, /* A major */ {d4k,g5k,d5k,b4k,g4k}, /* G major */ {c5k,g5k,e5k,g4k,c6k}}; /* C major */ @implementation TileControl - appDidInit:sender // responds as application's delegate { NXRect contentViewFrame; NXSize theSize; char titleTemp[3]; const char *tmpstr; int i,row,col,x,y; int tempIconPosition; unsigned int windowNum; id speaker = [NXApp appSpeaker]; static NXDefaultsVector TileSlideDefaults = { {"Picture", ""}, {"Difficulty", "Normal"}, {"UsePictures", "YES"}, {"UseNumbers", "NO"}, {"EasyBests", "999,999,999,999"}, {"NormalBests", "999,999,999,999"}, {"HardBests", "999,999,999,999"}, {"BoardLocation","314 314"}, {NULL}}; NXRegisterDefaults([NXApp appName], TileSlideDefaults); tileSlideCV = [slideWindowOut contentView]; infoPanelCV = [infoPanelOut contentView]; /* set initial difficulty */ tmpstr = NXGetDefaultValue ([NXApp appName], "Difficulty"); if (sscanf(tmpstr, "%d", &NumTilesX) != 1) NumTilesX = 4; NumTilesY = NumTilesX; /* retrieve bests for four challenges in each difficulty */ tmpstr = NXGetDefaultValue ([NXApp appName], "EasyBests"); if (sscanf(tmpstr, "%d %d %d %d", &bests[0][0],&bests[0][1],&bests[0][2],&bests[0][3]) != 4) for (i=0;i<4;i++) bests[0][i] = MAXMOVES; tmpstr = NXGetDefaultValue ([NXApp appName], "NormalBests"); if (sscanf(tmpstr, "%d %d %d %d", &bests[1][0],&bests[1][1],&bests[1][2],&bests[1][3]) != 4) for (i=0;i<4;i++) bests[1][i] = MAXMOVES; tmpstr = NXGetDefaultValue ([NXApp appName], "HardBests"); if (sscanf(tmpstr, "%d %d %d %d", &bests[2][0],&bests[2][1],&bests[2][2],&bests[2][3]) != 4) for (i=0;i<4;i++) bests[2][i] = MAXMOVES; for (row=0;row < 3;row++) for (col=0; col < 4; col++) if (bests[row][col] < MAXMOVES) [[bestsMatrixOut cellAt:row:col] setIntValue:bests[row][col]]; else [[bestsMatrixOut cellAt:row:col] setStringValue:"±"]; /* initialize info panel animation variables */ [infoPanelCV getFrame:&contentViewFrame]; stripe1Positiony = 5; stripe2Positiony = (contentViewFrame.size.height-33); stripe1Positionx = 5; stripe2Positionx = (contentViewFrame.size.width-33); stripe1Movement = 5; stripe2Movement = -5; tmpstr = NXGetDefaultValue ([NXApp appName], "Picture"); if ([self initAndCheckPicture:(char *)tmpstr] == NO) { pictureLoaded = NO; usePictures = NO; useNumbers = YES; [useNumbersSwitch setEnabled:NO]; [useNumbersSwitch setIntValue:1]; [usePicturesSwitch setEnabled:NO]; [usePicturesSwitch setIntValue:0]; tempIconPosition = NX_TITLEONLY; } else { [picture getSize:(NXSize *)&theSize]; tmpstr = NXGetDefaultValue ([NXApp appName], "UsePictures"); if (NXOrderStrings((unsigned char *) "YES",(unsigned char *) tmpstr, YES, 4, NULL) == 0) { usePictures = YES; [usePicturesSwitch setIntValue:1]; } else { usePictures = NO; [usePicturesSwitch setIntValue:0]; } if ((theSize.height >= NUMBERMINHEIGHT) && (theSize.width >= NUMBERMINWIDTH) && ([usePicturesSwitch intValue] == 1)) [useNumbersSwitch setEnabled:YES]; else [useNumbersSwitch setEnabled:NO]; tmpstr = NXGetDefaultValue ([NXApp appName], "UseNumbers"); if ((NXOrderStrings((unsigned char *) "YES",(unsigned char *) tmpstr, YES, 4, NULL) == 0) && (theSize.height >= NUMBERMINHEIGHT) && (theSize.width >= NUMBERMINWIDTH)) { useNumbers = YES; [useNumbersSwitch setIntValue:1]; [usePicturesSwitch setEnabled:YES]; if (usePictures == YES) tempIconPosition = NX_ICONOVERLAPS; else tempIconPosition = NX_TITLEONLY; } else { useNumbers = NO; [useNumbersSwitch setIntValue:0]; [usePicturesSwitch setEnabled:NO]; if (usePictures == YES) tempIconPosition = NX_ICONONLY; else tempIconPosition = NX_TITLEONLY; } tempIconPosition = NX_ICONONLY; } myFontManager = [FontManager new]; /* create the box that holds the tiles */ tileBox = [Box new]; [tileBox setBorderType:NX_BEZEL]; [tileBox setTitlePosition:NX_NOTITLE]; [tileBox setOffsets:(NXCoord)0 :(NXCoord)0]; /* create a box for erasing the old button position when a button is moved */ clearBox = [Box new]; [clearBox setBorderType:NX_NOBORDER]; [clearBox setTitlePosition:NX_NOTITLE]; [clearBox setOffsets:(NXCoord)0 :(NXCoord)0]; for (i=0; i<(MAXSIZEX*MAXSIZEY); i++) { tile[i] = [Button new]; [tile[i] setAutodisplay:NO]; sprintf(titleTemp,"%d",i+1); [tile[i] setTitle:titleTemp]; [tile[i] setIconPosition:tempIconPosition]; } /* Create a matrix of invisible buttons that detect button presses */ /* Tag numbers of the form (row * MAXSIZEX + col) tell the slideTile: */ /* method where the button was pressed */ for (row=0; row<MAXSIZEY; row++) for (col=0; col<MAXSIZEX; col++) { tileInPosition[row][col].invisibleID = [Button new]; [tileInPosition[row][col].invisibleID setTransparent:(BOOL)YES]; [tileInPosition[row][col].invisibleID setTag:(row * MAXSIZEX + col)]; [tileInPosition[row][col].invisibleID setTarget:self]; [tileInPosition[row][col].invisibleID setAction:@selector(slideTile:)]; } /* add the tile to the tileBox */ for (row=0; row<NumTilesY; row++) for (col=0; col<NumTilesX; col++) { [tileBox addSubview:tile[row * NumTilesX + col]]; } [tile[NumTilesX*NumTilesY-1] removeFromSuperview]; // remove one for the space /* add the invisible tiles to the tileBox (added last so they are on top) */ for (row=0; row<NumTilesY; row++) for (col=0; col<NumTilesX; col++) { [tileBox addSubview:tileInPosition[row][col].invisibleID]; } [tileSlideCV addSubview:tileBox]; tmpstr = NXGetDefaultValue ([NXApp appName], "BoardLocation"); if (sscanf(tmpstr, "%d %d", &x,&y) != 2) { x = 314; y = 314; } [slideWindowOut moveTo:(NXCoord)x :(NXCoord)y]; [self shuffle:mixPattern]; [self randomMix:self]; [self sizeSlideBoard]; [slideWindowOut orderFront:self]; /* register the tile slide window with the workspace (to accept pictures) */ /* (see Controller.m from Acceptor demo app) */ listener = [Listener new]; [listener setDelegate:self]; [listener usePrivatePort]; [listener addPort]; NXConvertWinNumToGlobal([slideWindowOut windowNum], &windowNum); [speaker setSendPort:NXPortFromName(NX_WORKSPACEREQUEST, NULL)]; [speaker registerWindow:windowNum toPort:[listener listenPort]]; /* enable the soundGenerator object (get the DSP) */ if ([soundGenerator enable] == NO) { NXRunAlertPanel(NULL,"Can't open the DSP.", "That's life.", NULL, NULL); [soundSwitch setIntValue:0]; } return self; } - appDidHide:app // responds as application's delegate { [soundGenerator disable]; if ([infoPanelOut isVisible] == YES) // if info panel is open, animation is happening DPSRemoveTimedEntry (infoTimer); // STOP IT! return self; } - appDidUnhide:app // responds as application's delegate { void infoAnimatorTimeout(); if ([soundGenerator enable] == NO) { NXRunAlertPanel(NULL,"Can't open the DSP.", "That's life.", NULL, NULL); [soundSwitch setIntValue:0]; } if ([infoPanelOut isVisible] == YES) // if info panel is open, restart animation infoTimer = DPSAddTimedEntry((double) INFOTIMERPERIOD, &infoAnimatorTimeout, self, NX_BASETHRESHOLD); return self; } - init // Initialize a few variables. Some of these things could be changed while // running if a user interface to them is added (perhaps a preferrences panel) { [super init]; movementSteps = 3; // number of animation steps when moving tiles currentStep = 0; slidePeriod = .01; // seconds of delay between animation frames topMargin = 15; // does not include window's titlebar bottomMargin = 53; // between sizebar and bottom of tilebox leftMargin = 10; rightMargin = 15; interTileDistance = 2; buttonBorderWidth = 3; // lines which make up border of tile buttons pictureType = TYPENONE; infoTimer = NULL; mixPattern = RANDOMFLAG; solved = NO; return self; } - free { int i,row,col; [picture free]; [tileBox free]; [clearBox free]; [myFontManager free]; for (i=0; i<(MAXSIZEX*MAXSIZEY); i++) [tile[i] free]; for (row=0; row<MAXSIZEY; row++) for (col=0; col<MAXSIZEX; col++) [tileInPosition[row][col].invisibleID free]; return [super free]; } - openInfoPanel:sender // show info panel and start info panel animation { void infoAnimatorTimeout(); [infoPanelOut orderFront:self]; if (!infoTimer) infoTimer = DPSAddTimedEntry((double) INFOTIMERPERIOD, &infoAnimatorTimeout, self, NX_BASETHRESHOLD); return self; } - windowWillClose:sender // respond as delegate for info panel { DPSRemoveTimedEntry (infoTimer); infoTimer = NULL; return self; } - windowDidMove:sender; // save new board position in defaults database { NXRect theRect; char str[20]; [slideWindowOut getFrame:(NXRect *)&theRect]; sprintf (str, "%d %d",(int)theRect.origin.x,(int)theRect.origin.y); NXWriteDefault ([NXApp appName], "BoardLocation", str); return self; } void infoAnimatorTimeout (DPSTimedEntry timedEntry, double timeNow, void *data) // dummy method to invoke changeInfoPanel method in response to info panel animation timeout { [(id)data changeInfoPanel]; } - (void)changeInfoPanel // Move buttons on info panel to create animation!! { NXRect contentViewFrame; [infoPanelCV getFrame:&contentViewFrame]; [stripe1Blanker moveTo:(NXCoord)stripe1Positionx:(NXCoord)stripe1Positiony]; if (stripe1Positiony+stripe1Movement < 5) stripe1Movement = -stripe1Movement; if (stripe1Positiony+stripe1Movement+33 > contentViewFrame.size.height) stripe1Movement = -stripe1Movement; stripe1Positiony += stripe1Movement; [stripe1 moveTo:(NXCoord)stripe1Positionx :(NXCoord)stripe1Positiony]; [stripe1Blanker display]; [stripe1 display]; [stripe2Blanker moveTo:(NXCoord)stripe2Positionx:(NXCoord)stripe2Positiony]; if (stripe2Positiony+stripe2Movement < 5) stripe2Movement = -stripe2Movement; if (stripe2Positiony+stripe2Movement+33 > contentViewFrame.size.height) stripe2Movement = -stripe2Movement; stripe2Positiony += stripe2Movement; [stripe2 moveTo:(NXCoord)stripe2Positionx :(NXCoord)stripe2Positiony]; [stripe2Blanker display]; [stripe2 display]; return; } - infoPanelNotes:sender // plays a random 2-note chord when a button on info panel is pressed { int key; key = random() % NUMBEROFBARS; [soundGenerator playNoteAtFreq:ROWNOTE(key,random() % NOTESPERBAR)]; [soundGenerator playNoteAtFreq:COLNOTE(key,random() % NOTESPERBAR)]; return self; } - openBackground:sender // select background to load via open panel { char *theFile,*theDirectory; const char *const *returnedFileList; char **fileTypeList; char *typeList[3]; NXSize theSize; int length; typeList[0] = "tiff"; typeList[1] = "eps"; typeList[2] = 0; fileTypeList = typeList; myOpenPanel = [OpenPanel new]; [myOpenPanel allowMultipleFiles:(BOOL)NO]; if ([myOpenPanel runModalForTypes:(const char *const *)fileTypeList] == 0) return self; // CANCEL selected returnedFileList = [myOpenPanel filenames]; if (returnedFileList == NULL) return self; theFile = (char *) *returnedFileList; theDirectory = (char *) [myOpenPanel directory]; length = (strlen(theFile) + strlen(theDirectory)+1); if (filePathLength <= length) { if (filePath) { free(filePath); } filePath = (char *)malloc(length + 1); filePathLength = length; } strcpy(filePath, theDirectory); strcat(filePath, "/"); strcat(filePath, theFile); if ([self initAndCheckPicture:(char *)filePath] == NO) return self; // selected file is not loadable else { if (usePictures == NO){ usePictures = YES; [usePicturesSwitch setIntValue:1]; NXWriteDefault ([NXApp appName], "UsePictures", "YES"); } [picture getSize:&theSize]; [picture getSize:(NXSize *)&theSize]; if ((theSize.height >= NUMBERMINHEIGHT) && (theSize.width >= NUMBERMINWIDTH)) { [useNumbersSwitch setEnabled:YES]; if (useNumbers == YES) [usePicturesSwitch setEnabled:YES]; } else { // picture too small to allow using numbers useNumbers = NO; [useNumbersSwitch setEnabled:NO]; [useNumbersSwitch setIntValue:0]; [usePicturesSwitch setEnabled:NO]; } [self sizeSlideBoard]; } return self; } - togglePictures:sender // Toggles pictures on and off. Receives action messages from the "Picture" switch. { if ([sender intValue] == 1) { usePictures = YES; NXWriteDefault ([NXApp appName], "UsePictures", "YES"); [useNumbersSwitch setEnabled:YES]; } else { usePictures = NO; NXWriteDefault ([NXApp appName], "UsePictures", "NO"); [useNumbersSwitch setEnabled:NO]; }; [self sizeSlideBoard]; return self; } - toggleNumbers:sender // Toggles visibility of numbers on and off. // Receives action messages from the "Numbers" switch. { if ([sender intValue] == 1) { useNumbers = YES; NXWriteDefault ([NXApp appName], "UseNumbers", "YES"); if (pictureLoaded == YES) [usePicturesSwitch setEnabled:YES]; } else { useNumbers = NO; NXWriteDefault ([NXApp appName], "UseNumbers", "NO"); [usePicturesSwitch setEnabled:NO]; }; [self sizeSlideBoard]; return self; } - sizeSlideBoard // Draws the tile board at the correct size, with the NumTilesX * NumTilesY tiles // Determines the size from the current size of "picture" if the "Picture" switch // is checked, and from the size of the window if not. { NXRect tempRect; NXSize tempSize; NXPoint tempPoint; id tempImage,newFont; int i,row,col,minSize; float tempFontSize; if (usePictures == YES) { [slideWindowOut disableFlushWindow]; [picture getSize:&tempSize]; sizex = (int)((tempSize.width/NumTilesX)-buttonBorderWidth); sizey = (int)((tempSize.height/NumTilesY)-buttonBorderWidth); tempSize.width = (NXCoord)sizex; tempSize.height = (NXCoord)sizey; tempRect.size = tempSize; if (sizex>sizey) minSize = sizey; else minSize = sizex; tempFontSize = (float)((minSize-5+buttonBorderWidth)/1.6); newFont = [myFontManager findFont:(const char *)"Helvetica" traits: (NXFontTraitMask)NX_UNBOLD weight:(int)0 size:tempFontSize]; for (row=0; row<NumTilesY; row++) for (col=0; col<NumTilesX; col++){ tempPoint.x = (NXCoord)((sizex+buttonBorderWidth) * col); tileInPosition[row][col].xPosition = tempPoint.x; tempPoint.y = (NXCoord)((sizey+buttonBorderWidth) * (NumTilesY-row-1)); tileInPosition[row][col].yPosition = tempPoint.y; tempRect.origin = tempPoint; tempImage = [[NXImage alloc] initFromImage:(NXImage *)picture rect:(const NXRect *)&tempRect]; [tile[row*NumTilesX+col] setImage:tempImage]; [tile[row*NumTilesX+col] sizeTo:(NXCoord)(sizex+buttonBorderWidth) :(NXCoord)(sizey+buttonBorderWidth)]; [tile[tileInPosition[row][col].tileNumber] moveTo: (tileInPosition[row][col].xPosition) :(tileInPosition[row][col].yPosition)]; [tileInPosition[row][col].invisibleID moveTo: (tileInPosition[row][col].xPosition) :(tileInPosition[row][col].yPosition)]; [tileInPosition[row][col].invisibleID sizeTo: (NXCoord)(sizex+buttonBorderWidth) :(NXCoord)(sizey+buttonBorderWidth)]; if (useNumbers == YES) [tile[row*NumTilesX+col] setIconPosition:NX_ICONOVERLAPS]; else [tile[row*NumTilesX+col] setIconPosition:NX_ICONONLY]; } [clearBox sizeTo:(NXCoord)(sizex+buttonBorderWidth) :(NXCoord)(sizey+buttonBorderWidth)]; [tileBox setBorderType:NX_BEZEL]; [tileBox sizeToFit]; [tileBox getFrame:&tempRect]; windowSize.width = (NXCoord)(tempRect.size.width + leftMargin + rightMargin); windowSize.height = (NXCoord)(tempRect.size.height + topMargin + bottomMargin); [slideWindowOut sizeWindow:windowSize.width :windowSize.height]; [tileBox moveTo:(NXCoord)leftMargin :(NXCoord)bottomMargin]; // I don't think the display in the following loop should be necessary, but set font // doesn't seem to work unless you do a display after setting icon position if (useNumbers == YES) { for (i=0; i<(MAXSIZEX*MAXSIZEY); i++) { [tile[i] display]; [tile[i] setFont:newFont]; } } [slideWindowOut reenableFlushWindow]; [slideWindowOut display]; } else { [slideWindowOut disableFlushWindow]; [tileSlideCV getFrame:&tempRect]; sizex = (int) ((tempRect.size.width - leftMargin - rightMargin -((NumTilesX-1) * interTileDistance))/NumTilesX); sizey = (int) ((tempRect.size.height - topMargin - bottomMargin -((NumTilesY-1) * interTileDistance))/NumTilesY); for (row=0; row<NumTilesY; row++) for (col=0; col<NumTilesX; col++){ tileInPosition[row][col].xPosition =(NXCoord)((sizex+interTileDistance) * col); tileInPosition[row][col].yPosition =(NXCoord)((sizey+interTileDistance) * (NumTilesY-row-1)); [tile[tileInPosition[row][col].tileNumber] moveTo: (tileInPosition[row][col].xPosition) :(tileInPosition[row][col].yPosition)]; [tileInPosition[row][col].invisibleID moveTo: (tileInPosition[row][col].xPosition) :(tileInPosition[row][col].yPosition)]; [tile[tileInPosition[row][col].tileNumber] sizeTo:(NXCoord)sizex :(NXCoord)sizey]; [tileInPosition[row][col].invisibleID sizeTo:(NXCoord)sizex :(NXCoord)sizey]; } if (sizex>sizey) minSize = sizey; else minSize = sizex; for (i=0; i<(MAXSIZEX*MAXSIZEY); i++) { [tile[i] setIconPosition:NX_TITLEONLY]; [tile[i] display]; [tile[i] setFont:[myFontManager findFont:(const char *)"Helvetica" traits:(NXFontTraitMask)NX_UNBOLD weight:(int)0 size:(float)((minSize-5)/1.6)]]; } [clearBox sizeTo:(NXCoord)sizex :(NXCoord)sizey]; [tileBox setBorderType:NX_BEZEL]; [tileBox sizeToFit]; [tileBox moveTo:(NXCoord)leftMargin :(NXCoord)bottomMargin]; [slideWindowOut reenableFlushWindow]; [slideWindowOut display]; } return self; } - slideTile:sender // Receives action messages from the invisible buttons over the tiles // Starts sliding animation timer if sliding is to be done. { void slideTimeout (); int row,col; if (solved == YES) return self; if (sliding == YES) return self; row = (int) [sender tag]/MAXSIZEX; col = (int) [sender tag]-(row*MAXSIZEX); if ((rowOfSpace != row) && (colOfSpace != col)) return self; if ((rowOfSpace == row) && (colOfSpace == col)) return self; [soundGenerator playNoteAtFreq:ROWNOTE(COMPUTEBAR,row)]; [soundGenerator playNoteAtFreq:COLNOTE(COMPUTEBAR,SEQUENTIALNOTES)]; if (rowOfSpace == row) { slideRowOrCol = ROW; howManyToMove = colOfSpace - col; } if (colOfSpace == col) { slideRowOrCol = COL; howManyToMove = rowOfSpace - row; } numberOfMoves++; [movesOut setIntValue:numberOfMoves]; sliding = (BOOL)YES; fromRow = row; fromCol = col; slideTimer = DPSAddTimedEntry((double) slidePeriod, &slideTimeout, self, NX_BASETHRESHOLD); return self; } void slideTimeout (DPSTimedEntry timedEntry, double timeNow, void *data) // Dummy method to invoke slideABit method in response to tile sliding timeout { [(id)data slideABit]; } - (void) slideABit // Slide tiles one step; re-arrange position of tiles when done sliding. // Check for puzzle solved, congratulate and re-shuffle if it is { int distance,i,direction; int row,col,difficulty; char str[100]; currentStep++; if (howManyToMove < 0) direction = (-1); else direction = 1; if (currentStep == movementSteps) { // done sliding; re-arrange position of tiles DPSRemoveTimedEntry (slideTimer); sliding = (BOOL)NO; if (slideRowOrCol == ROW) { for (i=(fromCol + howManyToMove);i!= fromCol;i=i-direction) { [tile[tileInPosition[fromRow][i-direction].tileNumber] addSubview:clearBox]; [clearBox display]; [clearBox removeFromSuperview]; tileInPosition[fromRow][i].tileNumber = tileInPosition[fromRow][i-direction].tileNumber; [tile[tileInPosition[fromRow][i].tileNumber] moveTo: (tileInPosition[fromRow][i].xPosition) :(tileInPosition[fromRow][i].yPosition)]; [tile[tileInPosition[fromRow][i].tileNumber] display]; } } else { for (i=(fromRow + howManyToMove);i!= fromRow;i=i-direction) { [tile[tileInPosition[i-direction][fromCol].tileNumber] addSubview:clearBox]; [clearBox display]; [clearBox removeFromSuperview]; tileInPosition[i][fromCol].tileNumber = tileInPosition[i-direction][fromCol].tileNumber; [tile[tileInPosition[i][fromCol].tileNumber] moveTo: (tileInPosition[i][fromCol].xPosition) :(tileInPosition[i][fromCol].yPosition)]; [tile[tileInPosition[i][fromCol].tileNumber] display]; } } tileInPosition[fromRow][fromCol].tileNumber = NumTilesX*NumTilesY-1; // check for puzzle solved solved = YES; for (row=0; row<NumTilesY; row++) for (col=0; col<NumTilesX; col++) if (tileInPosition[row][col].tileNumber != (row * NumTilesX + col)) solved = NO; rowOfSpace = fromRow; colOfSpace = fromCol; if (solved == YES) { [tileInPosition[NumTilesY-1][NumTilesX-1].invisibleID removeFromSuperview]; [tileBox addSubview:tile[NumTilesX*NumTilesY-1]]; [tileBox addSubview:tileInPosition[NumTilesY-1][NumTilesX-1].invisibleID]; [tile[NumTilesX*NumTilesY-1] moveTo:(tileInPosition[NumTilesY-1][NumTilesX-1].xPosition) :(tileInPosition [NumTilesY-1][NumTilesX-1].yPosition)]; [tile[NumTilesX*NumTilesY-1] display]; [self playFinishMusic]; difficulty = NumTilesX-3; if ((mixPattern != RANDOMFLAG) && (numberOfMoves < bests[difficulty][mixPattern])) { bests[difficulty][mixPattern] = numberOfMoves; [[bestsMatrixOut cellAt:difficulty:mixPattern] setIntValue:bests[difficulty][mixPattern]]; [[bestsMatrixOut cellAt:difficulty:mixPattern] setBackgroundGray:(float)NX_WHITE]; [bestsPanelOut orderFront:self]; NXRunAlertPanel("Congratulations!", "You have set a new 'best'!", "Yippee!", NULL, NULL); switch (difficulty) { case 0: sprintf (str, "%d %d %d %d\0", bests[0][0], bests[0][1], bests[0][2], bests[0][3]); NXWriteDefault ([NXApp appName], "EasyBests", str); break; case 1: sprintf (str, "%d %d %d %d\0", bests[1][0], bests[1][1], bests[1][2], bests[1][3]); NXWriteDefault ([NXApp appName], "NormalBests", str); break; case 2: sprintf (str, "%d %d %d %d\0", bests[2][0], bests[2][1], bests[2][2], bests[2][3]); NXWriteDefault ([NXApp appName], "HardBests", str); break; } [[bestsMatrixOut cellAt:difficulty:mixPattern] setBackgroundGray:(float)NX_LTGRAY]; } } currentStep = 0; } else if (slideRowOrCol == ROW) { // not done sliding; move tiles one step distance = (int) (((float)currentStep/(float)movementSteps) * sizex * direction); for (i=(fromCol + howManyToMove-direction);i!= fromCol-direction;i=i-direction) { [tile[tileInPosition[fromRow][i].tileNumber] addSubview:clearBox]; [clearBox display]; [clearBox removeFromSuperview]; [tile[tileInPosition[fromRow][i].tileNumber] moveTo: (NXCoord)(tileInPosition[fromRow][i].xPosition + distance) :(tileInPosition[fromRow][i].yPosition)]; [tile[tileInPosition[fromRow][i].tileNumber] display]; } } else { distance = (int) (((float)currentStep/(float)movementSteps)*sizey*direction * -1); for (i=(fromRow + howManyToMove-direction);i!= fromRow-direction;i=i-direction) { [tile[tileInPosition[i][fromCol].tileNumber] addSubview:clearBox]; [clearBox display]; [clearBox removeFromSuperview]; [tile[tileInPosition[i][fromCol].tileNumber] moveTo: (tileInPosition[i][fromCol].xPosition) :(NXCoord)(tileInPosition[i][fromCol].yPosition + distance)]; [tile[tileInPosition[i][fromCol].tileNumber] display]; } } return; } - playFinishMusic // plays the run of notes when the puzzle is solved { [soundGenerator playNoteAtFreq:MKKeyNumToFreq (c4k)]; DELAY(500000); // busy-wait 400 000 microseconds [soundGenerator playNoteAtFreq:MKKeyNumToFreq (e4k)]; DELAY(500000); [soundGenerator playNoteAtFreq:MKKeyNumToFreq (g4k)]; DELAY(500000); [soundGenerator playNoteAtFreq:MKKeyNumToFreq (c5k)]; DELAY(500000); [soundGenerator playNoteAtFreq:MKKeyNumToFreq (e5k)]; DELAY(500000); [soundGenerator playNoteAtFreq:MKKeyNumToFreq (g5k)]; DELAY(500000); [soundGenerator playNoteAtFreq:MKKeyNumToFreq (c6k)]; return self; } - selectSize:sender // Receives action messages from the "Difficulty" menu cells // Selects x and y size of tile board (currently the same) and invokes "shuffle:" { int row,col; char str[3]; for (row=0; row<NumTilesY; row++) for (col=0; col<NumTilesX; col++){ [tile[tileInPosition[row][col].tileNumber] removeFromSuperview]; [tileInPosition[row][col].invisibleID removeFromSuperview]; } [tileBox setBorderType:NX_NOBORDER]; // clear old board [tileSlideCV display]; // set number of tiles NumTilesX = [[sender selectedCell] tag]; NumTilesY = NumTilesX; sprintf (str, "%d\0", NumTilesX); NXWriteDefault ([NXApp appName], "Difficulty", str); for (row=0; row<NumTilesY; row++) for (col=0; col<NumTilesX; col++) { tileInPosition[row][col].tileNumber = (row * NumTilesX)+ col; [tileBox addSubview:tile[tileInPosition[row][col].tileNumber]]; } [tile[NumTilesX*NumTilesY-1] removeFromSuperview]; for (row=0; row<NumTilesY; row++) // invisible buttons added last so they are on top! for (col=0; col<NumTilesX; col++) { [tileBox addSubview:tileInPosition[row][col].invisibleID]; } [tile[NumTilesX*NumTilesY-1] removeFromSuperview]; if (mixPattern != RANDOMFLAG) { [self shuffle:mixPattern]; [self sizeSlideBoard]; [slideWindowOut orderFront:self]; } else { [self shuffle:mixPattern]; [self randomMix:self]; } return self; } - shuffle:(int)pattern // shuffle the board (to a default shuffled pattern) and reset numberOfMoves // Currently, this only handles 3*3, 4*4, and 5*5 boards { static int scrambledSize5[NUMOFCHALLENGES][25]= {{24,10,8,22,5,7,15,16,20,23,17,12,2,14,6,3,13,4,18,1,0,19,11,9,21}, {24,10,14,13,22,9,21,1,3,11,4,8,16,23,18,2,6,20,15,17,0,7,19,5,12}, {24,19,14,9,4,23,18,13,8,3,22,17,12,7,2,21,16,11,6,1,20,15,10,5,0}, {24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0}}; static int scrambledSize4[NUMOFCHALLENGES][16]= {{15,14,3,13,0,9,11,4,10,7,5,2,8,1,6,12}, {15,3,5,13,1,11,12,2,4,10,0,8,6,14,9,7}, {15,11,7,3,14,10,6,2,13,9,5,1,12,8,4,0}, {15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0}}; static int scrambledSize3[NUMOFCHALLENGES][9]= {{8,0,4,2,5,3,6,1,7}, {8,1,5,6,4,3,0,2,7}, {8,5,2,6,4,1,7,3,0}, {8,1,5,7,4,6,2,3,0}}; int row,col,patternUsed; patternUsed = pattern % NUMOFCHALLENGES; switch (NumTilesX) { case 3: for (row=0;row<NumTilesY;row++) for (col=0;col<NumTilesX;col++) tileInPosition[row][col].tileNumber = (int)scrambledSize3[patternUsed][col + (row *NumTilesX)]; break; case 4: for (row=0;row<NumTilesY;row++) for (col=0;col<NumTilesX;col++) tileInPosition[row][col].tileNumber = (int)scrambledSize4[patternUsed][col + (row *NumTilesX)]; break; case 5: for (row=0;row<NumTilesY;row++) for (col=0;col<NumTilesX;col++) tileInPosition[row][col].tileNumber = (int)scrambledSize5[patternUsed][col + (row *NumTilesX)]; break; } rowOfSpace = 0; colOfSpace = 0; numberOfMoves = 0; [movesOut setIntValue:numberOfMoves]; solved = NO; return self; } - challengeMix:sender // select next pattern, call shuffle, change window title, and redraw // target of the 'Challenge Mix' menu item { char buffer[30],bufferNum[5]; [tile[NumTilesX*NumTilesY-1] removeFromSuperview]; solved = NO; if (mixPattern == RANDOMFLAG) mixPattern = NUMOFCHALLENGES; // wraps around when used mixPattern ++; if (mixPattern >= NUMOFCHALLENGES) mixPattern = 0; [self shuffle:mixPattern]; strcpy(buffer,"Tile Slide - Challenge#"); sprintf(bufferNum," %d", (mixPattern+1)); strcat(buffer,bufferNum); [slideWindowOut setTitle:(const char *)buffer]; [self sizeSlideBoard]; [slideWindowOut orderFront:self]; return self; } - randomMix:sender // make 100 random slides, change window title, and redraw // target of the 'Random Mix' menu item { int i,dir,tempRow,tempCol; int tempTileNum; static int moves[4][2] = {{0,1},{1,0},{0,-1},{-1,0}}; char buffer[30]; [tile[NumTilesX*NumTilesY-1] removeFromSuperview]; solved = NO; mixPattern = RANDOMFLAG; for (i=0;i<100;i++) { dir = random() % 4; while ((rowOfSpace+moves[dir][1] >= NumTilesY) || (rowOfSpace+moves[dir][1] < 0) || (colOfSpace+moves[dir][0] >= NumTilesX) || (colOfSpace+moves[dir][0] < 0)) dir = random() % 4; tempRow = rowOfSpace+ moves[dir][1]; tempCol = colOfSpace+moves[dir][0]; tempTileNum = tileInPosition[tempRow][tempCol].tileNumber; tileInPosition[tempRow][tempCol].tileNumber = tileInPosition[rowOfSpace][colOfSpace].tileNumber; tileInPosition[rowOfSpace][colOfSpace].tileNumber = tempTileNum; rowOfSpace = tempRow; colOfSpace = tempCol; } strcpy(buffer,"Tile Slide - Random"); [slideWindowOut setTitle:(const char *)buffer]; numberOfMoves = 0; [movesOut setIntValue:numberOfMoves]; [self sizeSlideBoard]; [slideWindowOut orderFront:self]; return self; } - (int)iconEntered:(int)windowNum at:(double)x :(double)y iconWindow:(int)iconWindowNum iconX:(double)iconX iconY:(double)iconY iconWidth:(double)iconWidth iconHeight:(double)iconHeight pathList:(char *)pathList // respond as delegate for main window { char *stringPosition; int length; /* save the file's path for later */ length = strlen(pathList); if (filePathLength <= length) { if (filePath) { free(filePath); } filePath = (char *)malloc(length + 1); filePathLength = length; } strcpy(filePath, pathList); stringPosition = filePath; /* the number of tabs + 1 equals the number of files dragged in */ files = 1; while (stringPosition = index(stringPosition, '\t')) { files++; stringPosition++; } return 0; } - (int)iconReleasedAt:(double)x :(double)y ok:(int *)flag // respond as delegate for main window { NXSize theSize; if (files!=1) { *flag = 0; // tell Workspace to animate the ICON back to its source window return 0; } if ([self initAndCheckPicture:(char *)filePath] == NO) { *flag = 0; // do not accept the icon } else { *flag = 1; // accept the icon if (usePictures == NO){ usePictures = YES; [usePicturesSwitch setIntValue:1]; NXWriteDefault ([NXApp appName], "UsePictures", "YES"); } [picture getSize:&theSize]; [picture getSize:(NXSize *)&theSize]; if ((theSize.height >= NUMBERMINHEIGHT) && (theSize.width >= NUMBERMINWIDTH)) { [useNumbersSwitch setEnabled:YES]; if (useNumbers == YES) [usePicturesSwitch setEnabled:YES]; } else { // picture too small to allow using numbers useNumbers = NO; [useNumbersSwitch setEnabled:NO]; [useNumbersSwitch setIntValue:0]; [usePicturesSwitch setEnabled:NO]; } [self sizeSlideBoard]; } return 0; } - (BOOL)initAndCheckPicture:(char *)pictureName // Checks out the dragged in icon. Makes sure it is tiff or eps // and resizes if too big or small. Returns YES if it loads a picture. { NXStream *input; id newPicture; NXSize theSize,tempSize; NXRect tempRect; int tempPictureType; char *rightmostPeriod; if (pictureName == NULL) return NO; rightmostPeriod = strrchr(pictureName, '.'); if (rightmostPeriod == NULL) return NO; tempPictureType = TYPENONE; if (NXOrderStrings((unsigned char *) ".tiff",(unsigned char *) rightmostPeriod, YES, 5, NULL) == 0) tempPictureType = TYPETIFF; if (NXOrderStrings((unsigned char *) ".eps",(unsigned char *) rightmostPeriod, YES, 4, NULL) == 0) tempPictureType = TYPEEPS; if (tempPictureType == TYPENONE) return NO; // unsuccessful load input = NXMapFile(pictureName, NX_READONLY); if (input == NULL) return NO; // unsuccessful load newPicture = [[NXImage alloc] initFromStream:input]; [newPicture getSize:(NXSize *)&theSize]; /* if a tiff picture is too small, it is rejected */ /* (We could just resize it, as we do for eps pictures, */ /* but I don't like the look of resized tiffs anyways.) */ if (((theSize.height < IMAGEMINHEIGHT) || (theSize.width < IMAGEMINWIDTH)) && (tempPictureType == TYPETIFF)) { [newPicture free]; NXCloseMemory(input,NX_FREEBUFFER); return NO; // unsuccessful load } /* we now know we have a valid picture, so get rid of the old one */ [picture free]; picture = newPicture; /* if a tiff picture is too large, use only the bottom left section */ if (((theSize.height > IMAGEMAXHEIGHT) || (theSize.width > IMAGEMAXWIDTH)) && (tempPictureType == TYPETIFF)) { if (theSize.width > IMAGEMAXWIDTH) tempRect.size.width = IMAGEMAXWIDTH; else tempRect.size.width = theSize.width; if (theSize.height > IMAGEMAXHEIGHT) tempRect.size.height = IMAGEMAXHEIGHT; else tempRect.size.height = theSize.height; tempRect.origin.x = 0; tempRect.origin.y = 0; picture = [[NXImage alloc] initFromImage:(NXImage *)newPicture rect:(const NXRect *)&tempRect]; } /* if an eps picture is too small, we resize it */ if (((theSize.height < IMAGEMINHEIGHT) || (theSize.width < IMAGEMINWIDTH)) && (tempPictureType == TYPEEPS)) { if (theSize.width < IMAGEMINWIDTH) tempSize.width = IMAGEMINWIDTH; else tempSize.width = theSize.width; if (theSize.height < IMAGEMINHEIGHT) tempSize.height = IMAGEMINHEIGHT; else tempSize.height = theSize.height; [newPicture setSize:&tempSize]; } /* if an eps picture is too large, we resize it */ if (((theSize.height > IMAGEMAXHEIGHT) || (theSize.width > IMAGEMAXWIDTH)) && (tempPictureType == TYPEEPS)) { if (theSize.width > IMAGEMAXWIDTH) tempSize.width = IMAGEMAXWIDTH; else tempSize.width = theSize.width; if (theSize.height > IMAGEMAXHEIGHT) tempSize.height = IMAGEMAXHEIGHT; else tempSize.height = theSize.height; [newPicture setSize:&tempSize]; } /* set eps pictures to be scalable */ if (tempPictureType == TYPEEPS) { [picture setScalable:(BOOL)YES]; [picture setDataRetained:(BOOL)YES]; } pictureType = tempPictureType; pictureLoaded = YES; NXCloseMemory(input,NX_FREEBUFFER); NXWriteDefault ([NXApp appName], "Picture", filePath); return YES; } - windowWillResize:sender toSize:(NXSize *)frameSize // Acts as delegate for main window confining window resizing { if ( useNumbers == YES) { if (frameSize->width < (NXCoord)(NUMBERMINWIDTH+leftMargin+rightMargin+2)) frameSize->width = (NXCoord)(NUMBERMINWIDTH+leftMargin+rightMargin+2); if (frameSize->height < (NXCoord)(NUMBERMINHEIGHT+23+9+topMargin+bottomMargin)) frameSize->height = (NXCoord)(NUMBERMINHEIGHT+23+9+topMargin+bottomMargin); } if ( usePictures == YES) if (pictureType == TYPEEPS) { if (frameSize->width < (NXCoord)(IMAGEMINWIDTH+leftMargin+rightMargin+2)) frameSize->width = (NXCoord)(IMAGEMINWIDTH+leftMargin+rightMargin+2); if (frameSize->height < (NXCoord)(IMAGEMINHEIGHT+23+9+topMargin+bottomMargin)) frameSize->height = (NXCoord)(IMAGEMINHEIGHT+23+9+topMargin+bottomMargin); if (frameSize->width > (NXCoord)(IMAGEMAXWIDTH+leftMargin+rightMargin+2)) frameSize->width = (NXCoord)(IMAGEMAXWIDTH+leftMargin+rightMargin+2); if (frameSize->height > (NXCoord)(IMAGEMAXHEIGHT+23+9+topMargin+bottomMargin)) frameSize->height = (NXCoord)(IMAGEMAXHEIGHT+23+9+topMargin+bottomMargin); } else { // I don't like the look of resized tiffs, so don't allow resizing frameSize->width = (windowSize.width+2); frameSize->height = (windowSize.height+23+9); } return self; } - windowDidResize:sender; // Acts as delegate for main window. Enable or disable usePicturesSwitch and // useNumbersSwitch according to new size. Resize picture and redraw. { NXRect theRect; NXSize theSize; [tileSlideCV getFrame:(NXRect *)&theRect]; theSize.width = theRect.size.width - leftMargin - rightMargin; theSize.height = theRect.size.height - topMargin - bottomMargin; if ((theSize.height >= IMAGEMINHEIGHT) && (theSize.width >= IMAGEMINWIDTH) && (theSize.height <= IMAGEMAXHEIGHT) && (theSize.width <= IMAGEMAXWIDTH) && (pictureLoaded == YES) && (useNumbers == TRUE)) [usePicturesSwitch setEnabled:YES]; else [usePicturesSwitch setEnabled:NO]; if ((theSize.height >= NUMBERMINHEIGHT) && (theSize.width >= NUMBERMINWIDTH) && (usePictures == TRUE)) [useNumbersSwitch setEnabled:YES]; else [useNumbersSwitch setEnabled:NO]; if ( usePictures == NO) { if (pictureType == TYPEEPS) [picture setSize:&theSize]; [self sizeSlideBoard]; } else if (pictureType == TYPEEPS) { [picture setSize:&theSize]; [self sizeSlideBoard]; } return self; } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.