//============================================================================ //---------------------------------------------------------------------------- // Interface.c //---------------------------------------------------------------------------- //============================================================================ // I put all interface related code in here. Interface would include eventÉ // handling, menus, dialog boxes, etc. All the user interaction that takesÉ // place before and after an actual game is in play. #include "Externs.h" #include #define kAppleMenuID 128 #define iAbout 1 #define kGameMenuID 129 #define iNewGame 1 #define iPauseGame 2 #define iEndGame 3 #define iQuit 5 #define kOptionsMenuID 130 #define iSettings 1 #define iHelp 2 #define iHighScores 3 #define kAboutPictID 132 void DoAppleMenu (short); void DoGameMenu (short); void DoOptionsMenu (short); void UpdateMainWindow (void); void HandleMouseEvent (EventRecord *); void HandleKeyEvent (EventRecord *); void HandleUpdateEvent (EventRecord *); void HandleOSEvent (EventRecord *); void HandleHighLevelEvent (EventRecord *); void DoAbout (void); void DoGameSettings (void); Rect mainWindowRect; WindowPtr mainWindow; MenuHandle appleMenu, gameMenu, optionsMenu; Boolean switchedOut, quitting, canPlay, openTheScores; extern prefsInfo thePrefs; extern Rect backSrcRect, workSrcRect; extern CGrafPtr backSrcMap, workSrcMap; extern Boolean pausing, playing, helpOpen, scoresOpen; //============================================================== Functions //-------------------------------------------------------------- MenusReflectMode // Depending on whether a game is in progress (paused) or not, I wantÉ // menu items grayed out in one case and not grayed out in the other. // This function, when called, displays the menus correctly dependingÉ // on the mode we're in (playing or not playing, pausing or not). void MenusReflectMode (void) { if (playing) // If a game is in progressÉ { DisableItem(gameMenu, iNewGame); // Cannot begin another New Game. EnableItem(gameMenu, iPauseGame); // Can Pause Game. if (pausing) // If we are pausedÉ SetItem(gameMenu, iPauseGame, "\pResume Game"); // Rename item "Resume Game". else // If we are not pausedÉ SetItem(gameMenu, iPauseGame, "\pPause Game"); // Rename item "Pause Game". EnableItem(gameMenu, iEndGame); // Can End Game. DisableItem(optionsMenu, 0); // Cannot change game settings. } else // Else, if Glypha is idleÉ { EnableItem(gameMenu, iNewGame); // Can begin a New Game. DisableItem(gameMenu, iPauseGame); // Cannot Pause Game. SetItem(gameMenu, iPauseGame, "\pPause Game"); // Rename item "Pause Game". DisableItem(gameMenu, iEndGame); // Cannot End Game. EnableItem(optionsMenu, 0); // Can change game settings. } } //-------------------------------------------------------------- DoAppleMenu // This function takes care of handling the Apple menu (Desk Assecories and theÉ // About box). void DoAppleMenu (short theItem) { Str255 daName; GrafPtr wasPort; short daNumber; switch (theItem) // Depending on the item selectedÉ { case iAbout: // If the About item was selectedÉ if ((scoresOpen) || (helpOpen)) // If high scores or help screens upÉ { CloseWall(); // hide them. scoresOpen = FALSE; // High scores no longer open. helpOpen = FALSE; // Help screen is no longer open. // Uncheck help & high scores menu items. CheckItem(optionsMenu, iHelp, helpOpen); CheckItem(optionsMenu, iHighScores, scoresOpen); } DoAbout(); // Bring up the About dialog. break; default: // If any other item was selected (DA)É GetItem(appleMenu, theItem, daName); // Get the name of the item selected. GetPort(&wasPort); // Remember our port. daNumber = OpenDeskAcc(daName); // Launch the Desk Accesory. SetPort((GrafPtr)wasPort); // When we return, restore port. break; } } //-------------------------------------------------------------- DoGameMenu // This function handles a users interaction with the Game menu. QuittingÉ // Glypha, starting a new game, resuming a paused game are handled here. void DoGameMenu (short theItem) { switch (theItem) // Depending on menu item selectedÉ { case iNewGame: // If user selected New Game itemÉ if ((scoresOpen) || (helpOpen)) // If high scores or help screen is up,É { // close them first. CloseWall(); scoresOpen = FALSE; helpOpen = FALSE; CheckItem(optionsMenu, iHelp, helpOpen); CheckItem(optionsMenu, iHighScores, scoresOpen); } InitNewGame(); // Initialize variables for a new game. MenusReflectMode(); // Properly gray out the right menu items. break; case iPauseGame: // If user selected Pause Game itemÉ if (pausing) // If we are paused, resume playing. { pausing = FALSE; // Turn off pausing flag. DumpBackToWorkMap(); // Restore off screen just in case. } // Actually pausing a game (not resuming)É break; // is not really handled here. It's handledÉ // directly within the main game loop. case iEndGame: // Ending a game in progress isn't reallyÉ break; // handled here - this is a dummy item. // Ending a game is handled within the mainÉ // game loop by looking for the 'command'É // and 'E' key explicitly. case iQuit: // If user selected Quit itemÉ quitting = TRUE; // Set quitting flag to TRUE. break; } } //-------------------------------------------------------------- DoOptionsMenu // This function handles the Options menu. Options include game settings,É // displaying the high scores, and bringing up the Help screen. void DoOptionsMenu (short theItem) { switch (theItem) // Depending on which item the user selectedÉ { case iSettings: // If user selected Game Settings itemÉ if ((scoresOpen) || (helpOpen)) // Close high scores or help screen. { CloseWall(); scoresOpen = FALSE; helpOpen = FALSE; CheckItem(optionsMenu, iHelp, helpOpen); CheckItem(optionsMenu, iHighScores, scoresOpen); } DoGameSettings(); // Bring up game settings dialog. break; case iHelp: // If user selected Help itemÉ if (helpOpen) // If Help open, close it. { CloseWall(); helpOpen = FALSE; } else // Else, if Help is not open - open it. { if (scoresOpen) // If the High Scores are up though,É { CloseWall(); // Close them first. scoresOpen = FALSE; CheckItem(optionsMenu, iHighScores, scoresOpen); } OpenHelp(); // Now open the Help screen. } CheckItem(optionsMenu, iHelp, helpOpen); break; case iHighScores: // If user selected High ScoresÉ if (scoresOpen) // If the High Scores are up, close them. { CloseWall(); scoresOpen = FALSE; } else // If the High Scores are not upÉ { if (helpOpen) // First see if Help is open. { CloseWall(); // And close the Help screen. helpOpen = FALSE; CheckItem(optionsMenu, iHelp, helpOpen); } OpenHighScores(); // Now open the High Scores. } CheckItem(optionsMenu, iHighScores, scoresOpen); break; } } //-------------------------------------------------------------- DoMenuChoice // This is the main menu-handling function. It examines which menu was selectedÉ // by the user and passes on to the appropriate function, the item within thatÉ // menu that was selected. void DoMenuChoice (long menuChoice) { short theMenu, theItem; if (menuChoice == 0) // A little error checking. return; theMenu = HiWord(menuChoice); // Extract which menu was selected. theItem = LoWord(menuChoice); // Extract which item it was that was selected. switch (theMenu) // Now, depending upon which menu was selectedÉ { case kAppleMenuID: // If the Apple menu selectedÉ DoAppleMenu(theItem); // Call the function that handles the Apple menu. break; case kGameMenuID: // If the Game menu selectedÉ DoGameMenu(theItem); // Call the function that handles the Game menu. break; case kOptionsMenuID: // If the Options menu selectedÉ DoOptionsMenu(theItem); // Call the function that handles the Options menu. break; } HiliteMenu(0); // "De-invert" menu. } //-------------------------------------------------------------- UpdateMainWindow // This is a simple function that simply copies the contents from theÉ // background offscreen pixmap to the main screen. It is primarilyÉ // called in response to an update event, but could be called any timeÉ // when I want to force the screen to be redrawn. void UpdateMainWindow (void) { CopyBits(&((GrafPtr)backSrcMap)->portBits, &(((GrafPtr)mainWindow)->portBits), &mainWindowRect, &mainWindowRect, srcCopy, 0L); } //-------------------------------------------------------------- HandleMouseEvent // Mouse clicks come here. This is standard event-handling drivel. No different // from any other standard Mac program (game or otherwise). void HandleMouseEvent (EventRecord *theEvent) { WindowPtr whichWindow; Point localPoint; long menuChoice; short thePart; // Determine window and where in window. thePart = FindWindow(theEvent->where, &whichWindow); switch (thePart) // Depending on where mouse was clickedÉ { case inSysWindow: // In a Desk Accesory. SystemClick(theEvent, whichWindow); // (Is this stuff obsolete yet?) break; case inMenuBar: // Selected a menu item. menuChoice = MenuSelect(theEvent->where); if (canPlay) // Call menu handling routine. DoMenuChoice(menuChoice); break; case inDrag: // Like the lazy bastard I amÉ case inGoAway: // I'll just ignore these. case inGrow: // But, hey, the window isn'tÉ case inZoomIn: // movable or growable! case inZoomOut: break; case inContent: // Click in the window itself. FlashObelisks(TRUE); // Do lightning animation. LogNextTick(3); // Lightning will hit cursor location. localPoint = theEvent->where; GlobalToLocal(&localPoint); GenerateLightning(localPoint.h, localPoint.v); StrikeLightning(); WaitForNextTick(); StrikeLightning(); LogNextTick(2); WaitForNextTick(); PlayExternalSound(kLightningSound, kLightningPriority); LogNextTick(3); GenerateLightning(localPoint.h, localPoint.v); StrikeLightning(); WaitForNextTick(); StrikeLightning(); LogNextTick(2); WaitForNextTick(); LogNextTick(3); GenerateLightning(localPoint.h, localPoint.v); StrikeLightning(); WaitForNextTick(); StrikeLightning(); LogNextTick(2); WaitForNextTick(); PlayExternalSound(kLightningSound, kLightningPriority); LogNextTick(3); GenerateLightning(localPoint.h, localPoint.v); StrikeLightning(); WaitForNextTick(); StrikeLightning(); LogNextTick(2); WaitForNextTick(); FlashObelisks(FALSE); break; } } //-------------------------------------------------------------- HandleKeyEvent // More standard issue. This function handles any keystrokes when no game is // in session. Command-key strokes handled here too. void HandleKeyEvent (EventRecord *theEvent) { char theChar; Boolean commandDown; theChar = theEvent->message & charCodeMask; // Extract key hit. commandDown = ((theEvent->modifiers & cmdKey) != 0); // See if command key down. if (commandDown) // If command key down, call menuÉ { // handling routine. if (canPlay) DoMenuChoice(MenuKey(theChar)); } else { if (helpOpen) // Handle special keys if the helpÉ { // screen is up. if (theChar == kUpArrowKeyASCII) // Up arrow key scrolls help down. { if (theEvent->what == autoKey) ScrollHelp(-3); else ScrollHelp(-1); } else if (theChar == kDownArrowKeyASCII) // Down arrow key scrolls help up. { if (theEvent->what == autoKey) ScrollHelp(3); else ScrollHelp(1); } else if (theChar == kPageDownKeyASCII) // Handle page down for help screen. { ScrollHelp(199); } else if (theChar == kPageUpKeyASCII) // Handle page up for help. { ScrollHelp(-199); } else if ((theChar == kHelpKeyASCII) && (!playing)) { // Hitting Help key closes helpÉ CloseWall(); // (if it's already open). helpOpen = FALSE; CheckItem(optionsMenu, iHelp, helpOpen); } } else if ((theChar == kHelpKeyASCII) && (!playing)) { // Else, if help not open and HelpÉ if (scoresOpen) // key is hit, open Help. { // Close high scores if open. CloseWall(); scoresOpen = FALSE; CheckItem(optionsMenu, iHighScores, scoresOpen); } OpenHelp(); // Open help. CheckItem(optionsMenu, iHelp, helpOpen); } } } //-------------------------------------------------------------- HandleUpdateEvent // This function handles update events. Standard event-handling stuff. void HandleUpdateEvent (EventRecord *theEvent) { if ((WindowPtr)theEvent->message == mainWindow) { SetPort((GrafPtr)mainWindow); // Don't forget this line, BTW. BeginUpdate((GrafPtr)mainWindow); // I did once and it took meÉ UpdateMainWindow(); // ages to track down that bug. EndUpdate((GrafPtr)mainWindow); // Well, it took me a week I think. canPlay = TRUE; } } //-------------------------------------------------------------- HandleOSEvent // Handle switchin in and out events. Standard event-handling stuff. void HandleOSEvent (EventRecord *theEvent) { if (theEvent->message & 0x01000000) // If suspend or resume eventÉ { if (theEvent->message & 0x00000001) // Specifically, if resume eventÉ switchedOut = FALSE; // I keep thinking I should do more here. else // Or if suspend eventÉ switchedOut = TRUE; // What am I forgetting? } } //-------------------------------------------------------------- HandleHighLevelEvent // Again, it's a fact I'm lazy. AppleEvents are fairly easy to implement butÉ // a nightmare to try and explain. Filling out the below function is left asÉ // and exercise to the reader. void HandleHighLevelEvent (EventRecord *theEvent) { // theErr = AEProcessAppleEvent(theEvent); } //-------------------------------------------------------------- HandleEvent // Standard event stuff. This is the culling function that calls all the aboveÉ // functions. It looks for an event and if it detects one, it calls the appropriateÉ // function above to handle it. void HandleEvent (void) { EventRecord theEvent; long sleep = 1L; Boolean itHappened; // See if an event is queued up. itHappened = WaitNextEvent(everyEvent, &theEvent, sleep, 0L); if (itHappened) // Ah, an event. I live for events! { switch (theEvent.what) // And what kind of event be ya'? { case mouseDown: // Aiy! Y' be a mouse click do ya'? HandleMouseEvent(&theEvent); break; case keyDown: // Key down, key held down events. case autoKey: HandleKeyEvent(&theEvent); break; case updateEvt: // Something needs redrawing! HandleUpdateEvent(&theEvent); break; case osEvt: // Switching in and out events. HandleOSEvent(&theEvent); break; case kHighLevelEvent: // Hmmmm. A "what" event? HandleHighLevelEvent(&theEvent); break; } } else if (openTheScores) // Check for "auto open" flag. { // If TRUE, set the flag back toÉ openTheScores = FALSE; // FALSE and open the high scores. OpenHighScores(); } } //-------------------------------------------------------------- DoAbout // This handles the About dialog. It brings up the About box in aÉ // simple centered window with no drag bar, close box or anything. // Leaving the dialog is handled with a simple mouse click. void DoAbout (void) { Rect aboutRect; WindowPtr aboutWindow; SetRect(&aboutRect, 0, 0, 325, 318); // Bring up centered window. CenterRectInRect(&aboutRect, &qd.screenBits.bounds); aboutWindow = GetNewCWindow(129, 0L, kPutInFront); MoveWindow((GrafPtr)aboutWindow, aboutRect.left, aboutRect.top, TRUE); ShowWindow((GrafPtr)aboutWindow); SetPort((GrafPtr)aboutWindow); LoadGraphic(kAboutPictID); // Draw About dialog graphic. do // Make sure button not downÉ { // before proceeding. } while (Button()); // Proceed. do // And now wait until the mouseÉ { // is pressed before closing theÉ } // window (ABout dialog). while (!Button()); FlushEvents(everyEvent, 0); // Flush the queue. if (aboutWindow != 0L) DisposeWindow(aboutWindow); // Close the About dialog. } //-------------------------------------------------------------- DoGameSettings // This one however is a good and proper dialog box. It handles the meagerÉ // preference settings for Glypha. Nothing fancy here to report. Just aÉ // straight-forward dialog calling routine. void DoGameSettings (void) { #define kGameSettingsDialogID 133 DialogPtr theDial; long newVolume; short i, item; Boolean leaving; CenterDialog(kGameSettingsDialogID); // Center dialog, then call up. theDial = GetNewDialog(kGameSettingsDialogID, 0L, kPutInFront); SetPort((GrafPtr)theDial); ShowWindow((GrafPtr)theDial); // Make visible (after centering). DrawDefaultButton(theDial); // Draw border around Okay button. FlushEvents(everyEvent, 0); // Put in a default sound volume. SetDialogNumToStr(theDial, 3, (long)thePrefs.wasVolume); SelIText(theDial, 3, 0, 1024); // Select it. leaving = FALSE; while (!leaving) { ModalDialog(0L, &item); // Simple modal dialog filtering. if (item == 1) // Did user hit the Okay button? { // Well see if volume entered is legal. GetDialogNumFromStr(theDial, 3, &newVolume); if ((newVolume >= 0) && (newVolume <= 7)) { // If it is legal, we'll note it and quit. thePrefs.wasVolume = (short)newVolume; SetSoundVol((short)newVolume); leaving = TRUE; // Bye. } else // Otherwise, the volume entered is wrong. { // So we'll Beep, enter the last legalÉ SysBeep(1); // value and select the text again. SetDialogNumToStr(theDial, 3, (long)thePrefs.wasVolume); SelIText(theDial, 3, 0, 1024); } } else if (item == 2) // Did the user hit the "Clear Scores"É { // button? for (i = 0; i < 10; i++) // Walk through and zero scores. { PasStringCopy("\pNemo", thePrefs.highNames[i]); thePrefs.highScores[i] = 0L; thePrefs.highLevel[i] = 0; openTheScores = TRUE; // Bring up scores when dialog quits. } DisableControl(theDial, 2); // Gray out Clear Scores button. } } DisposDialog(theDial); // Clean up before going. }