Annotation of Examples/AppKit/BreakApp/BreakView.m, revision 1.1

1.1     ! root        1: /*
        !             2:  * BreakView.m, view to implement the "BreakApp" game.
        !             3:  * Author: Ali Ozer
        !             4:  * Written for 0.8 October 88. 
        !             5:  * Modified for 0.9 March 89.
        !             6:  * Modified for 1.0 July 89.
        !             7:  * Removed use of Bitmap and threw away some classes May 90.
        !             8:  * Final 2.0 fixes/enhancements Sept 90.
        !             9:  * 3.0 update March 92.
        !            10:  * Sound-related changes for 3.1 March 92.
        !            11:  *
        !            12:  * BreakView implements an interactive custom view that allows the user
        !            13:  * to play "BreakApp," a game similar to a popular arcade classic.
        !            14:  *
        !            15:  * BreakView's main control methods are based on the target-action
        !            16:  * paradigm; thus you can include BreakView in an Interface-Builder based
        !            17:  * application. Please refer to BreakView.h for a list of "public" methods
        !            18:  * that you should provide links to in Interface Builder.
        !            19:  *
        !            20:  *  You may freely copy, distribute and reuse the code in this example.
        !            21:  *  NeXT disclaims any warranty of any kind, expressed or implied,
        !            22:  *  as to its fitness for any particular use.
        !            23:  */
        !            24: 
        !            25: #import <libc.h>
        !            26: #import <math.h>
        !            27: #import <defaults/defaults.h>  // For writing/reading high score
        !            28: #import <appkit/appkit.h>
        !            29: 
        !            30: #import "BreakView.h"
        !            31: #import "SoundEffect.h"
        !            32: 
        !            33: // Max absolute x and y velocities of the ball, in base coordinates per msec.
        !            34: 
        !            35: #define MAXXV     ((level > 6) ? 0.3 : 0.2) 
        !            36: #define MAXYV     (0.4)
        !            37: 
        !            38: // Maximum amount of time that is allowed to pass between two calls to the
        !            39: // step method. If the time is greater than MAXTIMEDIFFERENCE, then this
        !            40: // value is used instead. MAXTIMEDIFFERENCE should be no greater
        !            41: // than the time it takes for the ball to go the height of a tile
        !            42: // or the height of the ball + height of paddle. The units
        !            43: // are in milliseconds.
        !            44: 
        !            45: #define MAXTIMEDIFFERENCE (TILEHEIGHT * 0.8 / MAXYV)
        !            46: #define MINTIMEDIFFERENCE 1
        !            47: 
        !            48: // Max revolution speed of the ball; this is the maximum
        !            49: // number of radians it will turn per millisecond when rotating...
        !            50: 
        !            51: #define MAXREVOLUTIONSPEED (M_PI / 250.0)      // Max is 2 revs/sec
        !            52: 
        !            53: // The following values are the default sizes for the various pieces. 
        !            54: 
        !            55: #define RADIUS         8.0                     // Ball radius
        !            56: #define PADDLEWIDTH    (TILEWIDTH * 1.8)       // Paddle width
        !            57: #define PADDLEHEIGHT   (TILEHEIGHT * 0.6)      // Paddle height
        !            58: #define BALLWIDTH      (RADIUS * 2.0)          // Ball width
        !            59: #define BALLHEIGHT     (RADIUS * 2.0)          // Ball height
        !            60: 
        !            61: // SHADOWOFFSET defines the amount the shadow is offset from the piece. 
        !            62: 
        !            63: #define SHADOWOFFSET 3.0
        !            64: 
        !            65: #define LIVES     5                            // Number of lives per game
        !            66: 
        !            67: #define STOPGAMEAT (-10)                       // Number of loops through the
        !            68:                                                // game after all tiles die
        !            69: 
        !            70: #define LEVELBONUS 50                          // Bonus at the end of a level
        !            71: 
        !            72: // Starting locations...
        !            73:                                                
        !            74: #define PADDLEX ((gameSize.width - paddleSize.width) / 2.0)
        !            75: #define PADDLEY 1.0
        !            76: #define BALLX ((gameSize.width - ballSize.width) / 2.0)
        !            77: #define BALLY (paddleY + paddleSize.height)
        !            78: 
        !            79: // Accelaration & score values of the different tile types.
        !            80: 
        !            81: static const float tileAccs[NUMTILETYPES] = {1.0, 1.3};
        !            82: static const int tileScores[NUMTILETYPES] = {5, 25};
        !            83: 
        !            84: #define NOTILE -1
        !            85: 
        !            86: extern void srandom();                         // Hmm; not in libc.h
        !            87: #define RANDINT(n) (random() % ((n)+1))                // Random integer 0..n
        !            88: #define ONEIN(n)   ((random() % (n)) == 0)     // TRUE one in n times 
        !            89: #define INITRAND   srandom(time(0))            // Randomizer
        !            90: 
        !            91: #define gameSize  bounds.size
        !            92: 
        !            93: // Restrict a value to the range -max .. max.
        !            94: 
        !            95: inline float restrictValue(float val, float max)
        !            96: {
        !            97:     if (val > max) return max;
        !            98:     else if (val < -max) return -max;
        !            99:     else return val;
        !           100: }
        !           101: 
        !           102: // Convert x-location to left/right pan for playing sounds
        !           103: 
        !           104: @implementation BreakView
        !           105: 
        !           106: - initFrame:(const NXRect *)frm
        !           107: {
        !           108:     [super initFrame:frm];
        !           109:     
        !           110:     [self allocateGState];     // For faster lock/unlockFocus
        !           111:     
        !           112:     [(ball = [[NXImage allocFromZone:[self zone]] init]) setScalable:NO];
        !           113:     [ball useDrawMethod:@selector(drawBall:) inObject:self];
        !           114: 
        !           115:     [(paddle = [[NXImage allocFromZone:[self zone]] init]) setScalable:NO];
        !           116:     [paddle useDrawMethod:@selector(drawPaddle:) inObject:self];
        !           117: 
        !           118:     [(tile[0] = [[NXImage allocFromZone:[self zone]] init]) setScalable:NO];
        !           119:     [(tile[1] = [[NXImage allocFromZone:[self zone]] init]) setScalable:NO];
        !           120:     [tile[0] useDrawMethod:@selector(drawNormalTile:) inObject:self];
        !           121:     [tile[1] useDrawMethod:@selector(drawToughTile:) inObject:self];
        !           122: 
        !           123:     wallSound = [[SoundEffect allocFromZone:[self zone]] initFromSection:"Wall.snd"];
        !           124:     tileSound = [[SoundEffect allocFromZone:[self zone]] initFromSection:"Tile.snd"];
        !           125:     missSound = [[SoundEffect allocFromZone:[self zone]] initFromSection:"Miss.snd"];
        !           126:     paddleSound = [[SoundEffect allocFromZone:[self zone]] initFromSection:"Paddle.snd"];
        !           127: 
        !           128:     [self setBackgroundFile:NXGetDefaultValue([NXApp appName], "BackGround") andRemember:NO];
        !           129:            
        !           130:     [self resizePieces];
        !           131:     
        !           132:     [self getHighScore];
        !           133:     
        !           134:     demoMode = NO;
        !           135:     
        !           136:     INITRAND;
        !           137:     
        !           138:     return self;
        !           139: }
        !           140: 
        !           141: // free simply gets rid of everything we created for BreakView, including
        !           142: // the instance of BreakView itself. This is how nice objects clean up.
        !           143: 
        !           144: - free
        !           145: {
        !           146:     int cnt;
        !           147: 
        !           148:     if (gameRunning) {
        !           149:        DPSRemoveTimedEntry (timer);
        !           150:     }
        !           151:     for (cnt = 0; cnt < NUMTILETYPES; cnt++) {
        !           152:        [tile[cnt] free];
        !           153:     }
        !           154: 
        !           155:     [ball free];    
        !           156:     [paddle free];
        !           157:     [backGround free];
        !           158:     [wallSound free];
        !           159:     [tileSound free];
        !           160:     [missSound free];
        !           161:     [paddleSound free];
        !           162: 
        !           163:     return [super free];
        !           164: }
        !           165: 
        !           166: // resizePieces calculates the new sizes of all the pieces after the game is
        !           167: // started or the playing field (the BreakView) is resized.
        !           168: 
        !           169: - resizePieces
        !           170: {
        !           171:     int cnt;
        !           172:     float xRatio = gameSize.width / GAMEWIDTH;
        !           173:     float yRatio = gameSize.height / GAMEHEIGHT;
        !           174: 
        !           175:     [backGround setSize:&gameSize];
        !           176: 
        !           177:     tileSize.width = floor(xRatio * TILEWIDTH);
        !           178:     tileSize.height = floor(yRatio * TILEHEIGHT);
        !           179:     for (cnt = 0; cnt < NUMTILETYPES; cnt++) {
        !           180:        [tile[cnt] setSize:&tileSize];
        !           181:     }
        !           182:     leftMargin = floor((gameSize.width - (tileSize.width + INTERTILE) * 
        !           183:                                                NUMTILESX) / 2.0 + 1.0);
        !           184: 
        !           185:     paddleSize.width = floor(xRatio * PADDLEWIDTH);
        !           186:     paddleSize.height = floor(yRatio * PADDLEHEIGHT);
        !           187:     [paddle setSize:&paddleSize];
        !           188: 
        !           189:     ballSize.width = floor(xRatio * BALLWIDTH);
        !           190:     ballSize.height = floor(yRatio * BALLHEIGHT);
        !           191:     [ball setSize:&ballSize];
        !           192: 
        !           193:     return self;  
        !           194: }
        !           195: 
        !           196: // The following allows BreakView to grab the mousedown event that activates
        !           197: // the window. By default, the View's acceptsFirstMouse returns NO.
        !           198: 
        !           199: - (BOOL)acceptsFirstMouse
        !           200: {
        !           201:     return YES;
        !           202: }
        !           203: 
        !           204: // This methods allows changing the file used to paint the background of the
        !           205: // playing field. Set fileName to NULL to revert to the default. Set
        !           206: // remember to YES if you wish the write the value out in the defaults.
        !           207: 
        !           208: - setBackgroundFile:(const char *)fileName andRemember:(BOOL)remember
        !           209: {
        !           210:     [backGround free];
        !           211:     backGround = [[NXImage allocFromZone:[self zone]] initSize:&gameSize];
        !           212:     if (fileName) {
        !           213:        [backGround useFromFile:fileName];
        !           214:        [backGround setScalable:YES];
        !           215:        if (remember) {
        !           216:            NXWriteDefault ([NXApp appName], "BackGround", fileName);
        !           217:        }
        !           218:     } else {
        !           219:        [backGround useDrawMethod:@selector(drawDefaultBackground:) inObject:self];
        !           220:        [backGround setScalable:NO];
        !           221:        if (remember) {
        !           222:            NXRemoveDefault ([NXApp appName], "BackGround");
        !           223:        }
        !           224:     }
        !           225:     [backGround setBackgroundColor:NX_COLORWHITE];
        !           226:     [self display];
        !           227: 
        !           228:     return self;   
        !           229: }
        !           230: 
        !           231: // The following two methods allow changing the background image from
        !           232: // menu items or buttons.
        !           233: 
        !           234: - changeBackground:sender
        !           235: {
        !           236:     if ([[OpenPanel new] runModalForTypes:[NXImage imageFileTypes]]) {
        !           237:        [self setBackgroundFile:[[OpenPanel new] filename] andRemember:YES];
        !           238:        [self display];
        !           239:     }
        !           240: 
        !           241:     return self;
        !           242: }
        !           243: 
        !           244: - revertBackground:sender
        !           245: {
        !           246:     [self setBackgroundFile:NULL andRemember:YES];
        !           247:     [self display];
        !           248:     return self;
        !           249: }
        !           250: 
        !           251: // getHighScore reads the previous high score from the user's defaults file.
        !           252: // If no such default is found, then the high score is set to zero.
        !           253: 
        !           254: - getHighScore
        !           255: {
        !           256:     const char *tmpstr;
        !           257:     if (((tmpstr = NXGetDefaultValue ([NXApp appName], "HighScore")) &&
        !           258:        (sscanf(tmpstr, "%d", &highScore) != 1))) highScore = 0;
        !           259:     
        !           260:     return self;
        !           261: }
        !           262: 
        !           263: // setHighScore should be called when the user score for a game is above 
        !           264: // the current high score. setHighScore sets the high score and 
        !           265: // writes it out the defaults file so that it can be remembered for eternity.
        !           266: 
        !           267: - setHighScore:(int)hScore
        !           268: {
        !           269:     char str[10];
        !           270:     [hscoreView setIntValue:(highScore = hScore)];
        !           271:     sprintf (str, "%d", highScore);
        !           272:     NXWriteDefault ([NXApp appName], "HighScore", str);
        !           273:     return self;
        !           274: }
        !           275: 
        !           276: - (int)score
        !           277: {
        !           278:     return score;
        !           279: }
        !           280: 
        !           281: - (int)level
        !           282: {
        !           283:     return level;
        !           284: }
        !           285: 
        !           286: - (int)lives
        !           287: {
        !           288:     return lives;
        !           289: }
        !           290: 
        !           291: // gotoFirstLevel: sets everything up for a new game.
        !           292: 
        !           293: - gotoFirstLevel:sender
        !           294: {
        !           295:     score = 0;
        !           296:     level = 0;
        !           297:     lives = LIVES;
        !           298:     return [self gotoNextLevel:sender];
        !           299: }
        !           300: 
        !           301: // gotoNextLevel: sets everything up for the next level of the game; the level
        !           302: // count is incremented and the pieces are set up on the field. The ball and
        !           303: // the paddle are also brought to the starting locations.
        !           304: //
        !           305: // This routine can of course be made infinitely more complicated in
        !           306: // determining where the tiles go. Left as an exercise to the reader. 8-)
        !           307: 
        !           308: - gotoNextLevel:sender
        !           309: {
        !           310:     int xcnt, ycnt, yFrom, yTo, xFrom, xTo;
        !           311:     
        !           312:     // We are at the next level... Stop the game and increment the level.
        !           313:     
        !           314:     [self stop:sender];
        !           315:     
        !           316:     level++;
        !           317:     
        !           318:     // Now place the tiles. Here's where we could do some fancy tile layout,
        !           319:     // depending on the game level. yFrom, yTo, xFrom, and xTo define the "box"
        !           320:     // in which we will lay the tiles out. These values are inclusive.
        !           321:     
        !           322:     switch (level % 6) {
        !           323:        case 0: yTo = NUMTILESY-2; break;
        !           324:        case 4: yTo = NUMTILESY-4; break;
        !           325:        case 5: yTo = 2 * (NUMTILESY / 3); break;
        !           326:        default: yTo = 3 * (NUMTILESY / 4); break;
        !           327:     }
        !           328:     
        !           329:     xFrom = 0; xTo = NUMTILESX-1; yFrom = yTo - 3;
        !           330:     
        !           331:     switch (level % 10) {
        !           332:        case 1: yFrom++; break;   
        !           333:        case 2: yFrom--; xFrom++; xTo--; break;
        !           334:        case 4: xFrom += 2; xTo -= 2; break;
        !           335:        case 6: yFrom = MIN(yFrom, NUMTILESY / 4); xFrom++; xTo--; break;
        !           336:        case 7: xTo -= 3; break;
        !           337:        case 8: yFrom -= 2; xFrom += 2; xTo -= 2; break;
        !           338:        case 9: yFrom = MIN(yFrom, NUMTILESY / 4);
        !           339:                yTo = MAX(yTo, yFrom+4);
        !           340:                break;
        !           341:        case 0: yFrom = MIN(yFrom, NUMTILESY / 5);
        !           342:                xFrom += (NUMTILESX / 2);
        !           343:                break;
        !           344:        default: break;
        !           345:     }    
        !           346:     
        !           347:     // The area in the playing field where we place tiles is at least 3 tiles 
        !           348:     // high and at least NUMTILESX-4 tiles wide.
        !           349:     
        !           350:     // Empty out the whole playing field.
        !           351:     for (xcnt = 0; xcnt < NUMTILESX; xcnt++) {
        !           352:        for (ycnt = 0; ycnt < NUMTILESY; ycnt++) {
        !           353:            tiles[xcnt][ycnt] = NOTILE;
        !           354:        }
        !           355:     }
        !           356: 
        !           357:     // Fill up the tile area with wimpy tiles
        !           358:     for (ycnt = yFrom; ycnt <= yTo; ycnt++) {
        !           359:        for (xcnt = xFrom; xcnt <= xTo; xcnt++) {
        !           360:            tiles[xcnt][ycnt] = 0;
        !           361:        }
        !           362:     }
        !           363: 
        !           364:     // Erase or change some of the tiles, depending on the level.
        !           365:     // Assumption is that we have at least 3 rows of tiles, yFrom..yTo.
        !           366:     
        !           367:     switch (level % 7) {
        !           368:        case 2: // clear two rows in the middle       
        !           369:            for (xcnt = xFrom; xcnt <= xTo; xcnt++) {
        !           370:                tiles[xcnt][yFrom+1] = tiles[xcnt][yTo-1] = NOTILE;
        !           371:            }
        !           372:            break;
        !           373:        case 3: // randomly clear out some tiles
        !           374:            for (xcnt = 0; xcnt < 5; xcnt++) {
        !           375:                tiles[xFrom+RANDINT(xTo-xFrom)][yFrom+1+RANDINT(yTo-yFrom-2)] = 
        !           376:                    NOTILE;
        !           377:            }
        !           378:            break;
        !           379:        case 4: // clear middle columns
        !           380:            for (xcnt = xFrom +  2; xcnt <= xTo - 2; xcnt++) {
        !           381:                for (ycnt = yFrom; ycnt <= yTo; ycnt++) {
        !           382:                    tiles[xcnt][ycnt] = NOTILE;
        !           383:                }
        !           384:            }
        !           385:            break;
        !           386:        case 6: // clear out the insides
        !           387:            for (ycnt = yFrom+1; ycnt < yTo; ycnt++) {
        !           388:                for (xcnt = xFrom+1; xcnt < xTo; xcnt++) {
        !           389:                    tiles[xcnt][ycnt] = NOTILE;
        !           390:                }
        !           391:            }
        !           392:            break;
        !           393:        default:
        !           394:            break;    
        !           395:     }
        !           396:     
        !           397:     // Drop in some tough tiles in all rows except the first one
        !           398:     for (xcnt = 0; xcnt < 5; xcnt++) {         
        !           399:        tiles[xFrom+RANDINT(xTo-xFrom)][yFrom+1+RANDINT(yTo-yFrom-1)] = 1;
        !           400:     }
        !           401:     
        !           402:     // Compute the number of tiles we actually ended up putting down...
        !           403:     numTilesLeft = 0;
        !           404:     for (ycnt = yFrom; ycnt <= yTo; ycnt++) {
        !           405:        for (xcnt = xFrom; xcnt <= xTo; xcnt++) {
        !           406:            if (tiles[xcnt][ycnt] != NOTILE) numTilesLeft++;
        !           407:        }
        !           408:     }
        !           409: 
        !           410:     // Of course you might think there are too many braces in the above code,
        !           411:     // where probably none would've sufficed. Too many braces never hurt, & it
        !           412:     // will save you from some bozo bug some day. So use them! They're cheap!
        !           413:     
        !           414:     [self resetBallAndPaddle];
        !           415:     
        !           416:     [levelView setIntValue:level];
        !           417:     [scoreView setIntValue:score];
        !           418:     [livesView setIntValue:lives];
        !           419:     [hscoreView setIntValue:highScore];
        !           420:     [statusView setStringValue:NXLocalString("Game Ready", NULL, "Message indicating the game is ready to run")];
        !           421:     
        !           422:     killerBall = ((level % 12) == 0);  // Every 12 turns, the loses its 
        !           423:                                        // ability to bounce off tiles
        !           424:     niceBall = (level % 5 == 0);       // Every 5 turns, make the ball
        !           425:                                        // bounce towards the paddle
        !           426: 
        !           427:     // If the background image is not from a file but our own default,
        !           428:     // poke it so its redrawn. This way every level will look different.
        !           429:     // We could've simply used a BOOL to remember if the image is the default
        !           430:     // one, but this test here works as well.
        !           431: 
        !           432:     if ([[backGround lastRepresentation] isKindOf:[NXCustomImageRep class]]) {
        !           433:        [backGround recache];
        !           434:     }
        !           435:     
        !           436:     [self display];                    // Display the new arrangement
        !           437:     
        !           438:     if (demoMode) {
        !           439:        [self go:sender];       // If in demo mode, start rolling
        !           440:     }
        !           441:     
        !           442:     return self;
        !           443: }      
        !           444: 
        !           445: // setDemoMode: allows the user to put the game in a demo mode.
        !           446: // In the demo mode, the paddle constantly follows the ball.
        !           447: 
        !           448: - setDemoMode:sender
        !           449: {
        !           450:     if (demoMode = ([sender state] == 0 ? NO : YES)) {
        !           451:        [self go:sender];
        !           452:     } else {
        !           453:        [self stop:sender];
        !           454:     }
        !           455:     return self;
        !           456: }
        !           457: 
        !           458: // This method should be called when a new level or game is started or the
        !           459: // player misses the ball. It resets the ball & paddle locations back to
        !           460: // default.
        !           461: 
        !           462: - resetBallAndPaddle
        !           463: {
        !           464:     paddleX = PADDLEX;
        !           465:     paddleY = PADDLEY;
        !           466:     ballX = BALLX;
        !           467:     ballY = BALLY;
        !           468: 
        !           469:     ballXVel = 0.0;
        !           470:     ballYVel = 0.0;
        !           471: 
        !           472:     // The ball shouldn't start out rotating...
        !           473:     revolutionsLeft = 0;       
        !           474:    
        !           475:     return self;
        !           476: }
        !           477:         
        !           478: // The directBallAt: initializes the velocity vector of the ball so that
        !           479: // the ball will go from its current location to the specified destination  
        !           480: // point. The speed of the ball is determined by the current level. If ballYVel
        !           481: // is already set, then only the x velocity & y direction is changed.
        !           482: 
        !           483: - directBallAt:(NXPoint *)dest 
        !           484: {
        !           485:     float desiredYVel = dest->y - (ballY + ballSize.height / 2.0);
        !           486:     float desiredXVel = dest->x - (ballX + ballSize.width / 2.0);
        !           487: 
        !           488:     // Transform back to original game coords (velocity values are measured
        !           489:     // in these).
        !           490: 
        !           491:     desiredYVel /= (gameSize.height / GAMEHEIGHT);
        !           492:     desiredXVel /= (gameSize.width / GAMEWIDTH);
        !           493: 
        !           494:     if (fabs(desiredYVel) < 1.0) {
        !           495:        desiredYVel = desiredYVel < 0.0 ? -1.0 : 1.0;
        !           496:     }
        !           497:     if (ballYVel == 0.0) {
        !           498:        // Come up with a value between 60 and 100% of MAXYV.
        !           499:        ballYVel = restrictValue(((RANDINT(level * 8) + 60.0) / 100.0) * MAXYV, 
        !           500:                                MAXYV);
        !           501:     }
        !           502:     ballYVel = fabs(ballYVel) * (desiredYVel < 0.0 ? -1.0 : 1.0);
        !           503:     ballXVel = restrictValue(ballYVel * (desiredXVel / desiredYVel) ,MAXXV);
        !           504: 
        !           505:     return self;
        !           506: }    
        !           507: 
        !           508: // The stop method will pause a running game. The go method will start it up
        !           509: // again. They can be assigned to buttons or other appkit objects through IB.
        !           510: 
        !           511: - go:sender
        !           512: {
        !           513:     void runOneStep ();
        !           514:     if (lives && !gameRunning) {
        !           515:        // If the ball velocity wasn't initialized, start it rolling
        !           516:        // towards the mouse location...
        !           517:        if (ballXVel == 0.0 && ballYVel == 0.0) {
        !           518:            NXPoint mouseLoc;
        !           519:            [[self window] getMouseLocation:&mouseLoc];
        !           520:            [self convertPoint:&mouseLoc fromView:nil];
        !           521:            [self directBallAt:&mouseLoc];
        !           522:            ballYVel = fabs(ballYVel);
        !           523:        }
        !           524:        gameRunning = YES;
        !           525:        timer = DPSAddTimedEntry(0.03, &runOneStep, self, NX_BASETHRESHOLD);
        !           526:        [statusView setStringValue:NXLocalString("Running", NULL, "Message indicating that the game is running")];
        !           527:     }
        !           528:     return self;
        !           529: }
        !           530: 
        !           531: - stop:sender
        !           532: {
        !           533:     if (gameRunning) {
        !           534:        gameRunning = NO;
        !           535:        DPSRemoveTimedEntry (timer);
        !           536:        [statusView setStringValue:NXLocalString("Paused", NULL, "Message indicating that the game is paused")]; 
        !           537:     }
        !           538: 
        !           539:     return self;
        !           540: }
        !           541: 
        !           542: - sizeTo:(NXCoord)width :(NXCoord)height 
        !           543: {
        !           544:     NXSize oldSize = bounds.size;
        !           545:     
        !           546:     [super sizeTo:width :height];
        !           547:     
        !           548:     ballX = (ballX * width / oldSize.width);
        !           549:     ballY = (ballY * height / oldSize.height);
        !           550:     paddleX = (paddleX * width / oldSize.width);
        !           551:     paddleY = (paddleY * height / oldSize.height);
        !           552:     
        !           553:     [self resizePieces];
        !           554:     
        !           555:     [self display];
        !           556:     return self;
        !           557: }
        !           558: 
        !           559: // A mousedown effectively allows pausing and unpausing the game by
        !           560: // alternately calling one of the above two functions (stop/go).
        !           561: 
        !           562: - mouseDown:(NXEvent *)event
        !           563: {
        !           564:     if (gameRunning) {
        !           565:        [self stop:self]; 
        !           566:     } else if (lives) {
        !           567:        [self go:self];   
        !           568:     }
        !           569:     return self;
        !           570: }
        !           571: 
        !           572: // The following few methods draw the pieces.
        !           573: 
        !           574: - drawBall:imageRep
        !           575: {
        !           576:     PSscale (ballSize.width / BALLWIDTH, ballSize.height / BALLHEIGHT);
        !           577: 
        !           578:     // First draw the shadow under the ball.
        !           579: 
        !           580:     PSarc (RADIUS+SHADOWOFFSET/2, RADIUS-SHADOWOFFSET/2, 
        !           581:           RADIUS-SHADOWOFFSET-1.0, 0.0, 360.0);
        !           582:     PSsetgray (NX_BLACK);
        !           583:     if (NXDrawingStatus == NX_DRAWING) {
        !           584:        PSsetalpha (0.666);
        !           585:     }
        !           586:     PSfill ();
        !           587:     if (NXDrawingStatus == NX_DRAWING) {
        !           588:        PSsetalpha (1.0);
        !           589:     }
        !           590: 
        !           591:     // Then the ball.
        !           592: 
        !           593:     PSarc (RADIUS-SHADOWOFFSET/2, RADIUS+SHADOWOFFSET/2, 
        !           594:           RADIUS-SHADOWOFFSET-1.0, 0.0, 360.0);
        !           595:     PSsetgray (NX_LTGRAY);
        !           596:     PSfill ();
        !           597: 
        !           598:     // And the lighter & darker spots on the ball...
        !           599: 
        !           600:     PSarcn (RADIUS-SHADOWOFFSET/2, RADIUS+SHADOWOFFSET/2, 
        !           601:            RADIUS-SHADOWOFFSET-3.0, 170.0, 100.0);
        !           602:     PSarc (RADIUS-SHADOWOFFSET/2, RADIUS+SHADOWOFFSET/2, 
        !           603:           RADIUS-SHADOWOFFSET-2.0, 100.0, 170.0);
        !           604:     PSsetgray (NX_WHITE);
        !           605:     PSfill ();
        !           606:     PSarcn (RADIUS-SHADOWOFFSET/2, RADIUS+SHADOWOFFSET/2, 
        !           607:            RADIUS-SHADOWOFFSET-2.0, 350.0, 280.0);
        !           608:     PSarc (RADIUS-SHADOWOFFSET/2, RADIUS+SHADOWOFFSET/2, 
        !           609:           RADIUS-SHADOWOFFSET-2.0, 280.0, 350.0);
        !           610:     PSsetgray (NX_DKGRAY);
        !           611:     PSfill ();
        !           612: 
        !           613:     return self;
        !           614: }
        !           615: 
        !           616: // Function to draw a shadow under the given rectangle.
        !           617: 
        !           618: static void drawRectangularShadowUnder (NXRect *rect, float offset)
        !           619: {
        !           620:     NXRect shadeRect = *rect;
        !           621:     NXOffsetRect (&shadeRect, offset, -offset);
        !           622: 
        !           623:     PSsetgray (NX_BLACK);
        !           624:     if (NXDrawingStatus != NX_PRINTING) {
        !           625:        PSsetalpha (0.666);
        !           626:     }
        !           627:     NXRectFill (&shadeRect);
        !           628:     if (NXDrawingStatus != NX_PRINTING) {
        !           629:        PSsetalpha (1.0);
        !           630:     }
        !           631: }
        !           632: 
        !           633: - drawPaddle:imageRep
        !           634: {
        !           635:     NXRect pieceRect = {{0.0, SHADOWOFFSET},
        !           636:                        {(paddleSize.width-SHADOWOFFSET)-1,
        !           637:                         (paddleSize.height-SHADOWOFFSET)-1}};
        !           638: 
        !           639:     drawRectangularShadowUnder (&pieceRect, SHADOWOFFSET);
        !           640: 
        !           641:     NXDrawButton (&pieceRect, NULL);
        !           642: 
        !           643:     return self;
        !           644: }
        !           645: 
        !           646: - drawToughTile:imageRep 
        !           647: {
        !           648:     NXRect pieceRect = {{0.0, SHADOWOFFSET},
        !           649:                        {(tileSize.width-SHADOWOFFSET)-1, 
        !           650:                         (tileSize.height-SHADOWOFFSET)-1}};
        !           651: 
        !           652:     drawRectangularShadowUnder (&pieceRect, SHADOWOFFSET);
        !           653: 
        !           654:     NXDrawButton (&pieceRect, NULL);
        !           655:     NXInsetRect (&pieceRect, 3.0, 3.0);
        !           656:     NXDrawWhiteBezel (&pieceRect, NULL);
        !           657: 
        !           658:     return self;
        !           659: }
        !           660: 
        !           661: - drawNormalTile:imageRep
        !           662: {
        !           663:     NXRect pieceRect = {{0.0, SHADOWOFFSET},
        !           664:                        {(tileSize.width-SHADOWOFFSET)-1, 
        !           665:                         (tileSize.height-SHADOWOFFSET)-1}};
        !           666: 
        !           667:     drawRectangularShadowUnder (&pieceRect, SHADOWOFFSET);
        !           668: 
        !           669:     NXDrawButton (&pieceRect, NULL);
        !           670: 
        !           671:     return self;
        !           672: }
        !           673: 
        !           674: #define NUMYBOXES 10
        !           675: #define NUMXBOXES 6
        !           676: 
        !           677: // This method draws the default background. The default background consists of
        !           678: // NUMXBOXES x NUMYBOXES raised boxes. Each box is drawn as four triangles to
        !           679: // provide a raised effect. Boxes near the top left corner are lighter in color
        !           680: // than the ones near the bottom right.
        !           681: 
        !           682: - drawDefaultBackground:imageRep
        !           683: {
        !           684: #define NOTFOUND ((id)-1)
        !           685:     static NXColorList *colorList = nil;       // Static because it's shared
        !           686:     NXSize boxSize = {gameSize.width / NUMXBOXES, gameSize.height / NUMYBOXES};
        !           687:     int xCnt, yCnt;
        !           688:     NXColor color;
        !           689: 
        !           690:     // The first time we're here, we load and cache the color list. If we don't find
        !           691:     // it, we remember that fact so that we don't go through the search again.
        !           692:     if (colorList == nil) {
        !           693:        char colorListPath[MAXPATHLEN];
        !           694:        if ([[NXBundle mainBundle] getPath:colorListPath forResource:"BreakApp" ofType:"clr"]) {
        !           695:            colorList = [[NXColorList allocFromZone:NXDefaultMallocZone()] initWithName:NULL fromFile:colorListPath];
        !           696:        }
        !           697:        if (!colorList) {
        !           698:            NXLogError ("Can't find color list for backgrounds.");
        !           699:            colorList = NOTFOUND;
        !           700:        }
        !           701:     }
        !           702: 
        !           703:     // Now get the color. If the color list wasn't found, we use some default random color.
        !           704:     // Note that because colors in different color spaces might look different on
        !           705:     // different devices (although they might look identical on screen), it's
        !           706:     // important to always use colors from the same color space when creating
        !           707:     // a wash. Below we assure that our colors always start off in HSB color space.
        !           708: 
        !           709:     if (colorList != NOTFOUND) {
        !           710:        color = [colorList colorNamed:[colorList nameOfColorAt:(level % [colorList colorCount])]];
        !           711:        color = NXConvertHSBToColor (NXHueComponent(color), NXSaturationComponent(color), 1.0);
        !           712:     } else {
        !           713:        color = NXConvertHSBToColor((level % 8) / 7.0, 0.8, 1.0);
        !           714:     }
        !           715: 
        !           716:     for (yCnt = 0; yCnt < NUMYBOXES; yCnt++) {
        !           717:        for (xCnt = 0; xCnt < NUMXBOXES; xCnt++) {
        !           718:            // Determine brightness (each box has a different brightness)
        !           719:            color = NXChangeBrightnessComponent(color, 0.4 + (yCnt + (4 - xCnt)) * (0.2 / (NUMYBOXES + NUMXBOXES)));
        !           720:            // The bottom triangle
        !           721:            PSmoveto (xCnt * boxSize.width + boxSize.width / 2.0, yCnt * boxSize.height + boxSize.height / 2.0);
        !           722:            PSrlineto (-boxSize.width / 2.0, -boxSize.height / 2.0);
        !           723:            PSrlineto (boxSize.width, 0);
        !           724:            NXSetColor (color);
        !           725:            PSfill ();
        !           726:            // The right triangle
        !           727:            PSmoveto (xCnt * boxSize.width + boxSize.width / 2.0, yCnt * boxSize.height + boxSize.height / 2.0);
        !           728:            PSrlineto (boxSize.width / 2.0, boxSize.height / 2.0);
        !           729:            PSrlineto (0, -boxSize.height);
        !           730:            NXSetColor (NXChangeBrightnessComponent(color, NXBrightnessComponent(color) * 1.2));
        !           731:            PSfill ();
        !           732:            // The left triangle
        !           733:            PSmoveto (xCnt * boxSize.width + boxSize.width / 2.0, yCnt * boxSize.height + boxSize.height / 2.0);
        !           734:            PSrlineto (-boxSize.width / 2.0, boxSize.height / 2.0);
        !           735:            PSrlineto (0, -boxSize.height);
        !           736:            NXSetColor (NXChangeBrightnessComponent(color, NXBrightnessComponent(color) * 1.2 * 1.2));
        !           737:            PSfill ();
        !           738:            // The right triangle
        !           739:            PSmoveto (xCnt * boxSize.width + boxSize.width / 2.0, yCnt * boxSize.height + boxSize.height / 2.0);
        !           740:            PSrlineto (boxSize.width / 2.0, boxSize.height / 2.0);
        !           741:            PSrlineto (-boxSize.width, 0.0);
        !           742:            NXSetColor (NXChangeBrightnessComponent(color, NXBrightnessComponent(color) * 1.2 * 1.2 * 1.2));
        !           743:            PSfill ();
        !           744:        }
        !           745:     }
        !           746:     return self;
        !           747: }
        !           748: 
        !           749: // The following methods show or erase the ball and the paddle from the field.
        !           750: 
        !           751: - showBall 
        !           752: {
        !           753:     NXRect tmpRect = {{floor(ballX), floor(ballY)},
        !           754:                        {ballSize.width, ballSize.height}};
        !           755:     [ball composite:NX_SOVER toPoint:&tmpRect.origin];
        !           756:     return self;
        !           757: }
        !           758: 
        !           759: - showPaddle 
        !           760: {
        !           761:     NXRect tmpRect = {{floor(paddleX), floor(paddleY)},
        !           762:                        {paddleSize.width, paddleSize.height}};
        !           763:     [paddle composite:NX_SOVER toPoint:&tmpRect.origin];
        !           764:     return self;
        !           765: }
        !           766: 
        !           767: - eraseBall
        !           768: {
        !           769:     NXRect tmpRect = {{ballX, ballY}, {ballSize.width, ballSize.height}};
        !           770:     return [self drawBackground:&tmpRect];
        !           771: }
        !           772: 
        !           773: - erasePaddle
        !           774: {
        !           775:     NXRect tmpRect = {{paddleX, paddleY},
        !           776:                        {paddleSize.width, paddleSize.height}};
        !           777:     return [self drawBackground:&tmpRect];
        !           778: }
        !           779: 
        !           780: // drawBackground: just draws the specified piece of the background by
        !           781: // compositing from the background image.
        !           782: 
        !           783: - drawBackground:(NXRect *)rect
        !           784: {
        !           785:     NXRect tmpRect = *rect;
        !           786: 
        !           787:     NX_X(&tmpRect) = floor(NX_X(&tmpRect));
        !           788:     NX_Y(&tmpRect) = floor(NX_Y(&tmpRect));
        !           789:     if (NXDrawingStatus == NX_DRAWING) {
        !           790:        PSsetgray (NX_WHITE);
        !           791:        PScompositerect (NX_X(&tmpRect), NX_Y(&tmpRect),
        !           792:                         NX_WIDTH(&tmpRect), NX_HEIGHT(&tmpRect), NX_COPY);
        !           793:     }
        !           794:     [backGround composite:NX_SOVER fromRect:&tmpRect toPoint:&tmpRect.origin];
        !           795:     return self;
        !           796: }
        !           797: 
        !           798: // drawSelf::, a method every decent View should have, redraws the game
        !           799: // in its current state. This allows us to print the game very easily.
        !           800: 
        !           801: - drawSelf:(NXRect *)rects :(int)rectCount 
        !           802: {
        !           803:     int xcnt, ycnt;
        !           804: 
        !           805:     [self drawBackground:(rects ? rects : &bounds)];
        !           806: 
        !           807:     for (xcnt = 0; xcnt < NUMTILESX; xcnt++) { 
        !           808:        for (ycnt = 0; ycnt < NUMTILESY; ycnt++) {
        !           809:            if (tiles[xcnt][ycnt] != NOTILE) {
        !           810:                NXPoint tileLoc = {floor(leftMargin + (tileSize.width + INTERTILE) * xcnt), floor((tileSize.height + INTERTILE) * ycnt)};
        !           811:                [tile[tiles[xcnt][ycnt]] composite:NX_SOVER toPoint:&tileLoc];
        !           812:            }
        !           813:        }
        !           814:     }
        !           815: 
        !           816:     if (lives) {
        !           817:        [self showBall];
        !           818:        [self showPaddle];
        !           819:     }
        !           820: 
        !           821:     return self;
        !           822: }
        !           823: 
        !           824: // incrementGameScore: adds the value of the argument to the score if the game
        !           825: // is not in demo mode.
        !           826: 
        !           827: - incrementGameScore:(int)scoreIncrement
        !           828: {
        !           829:     if (demoMode == NO) {
        !           830:        score += scoreIncrement;
        !           831:     }
        !           832:     return self;
        !           833: }
        !           834: 
        !           835: // hitTileAt:: checks to see if there's a tile at tile location x, y;
        !           836: // if so, it is considered hit by the ball and cleared. hitTileTile:: also
        !           837: // updates the score and the ball velocity. hitTileAt:: returns YES if there
        !           838: // was a tile, NO otherwise.
        !           839: 
        !           840: -(BOOL) hitTileAt:(int)x :(int)y 
        !           841: {
        !           842:     NXRect rect = {{floor(leftMargin + (tileSize.width + INTERTILE) * x), 
        !           843:                    floor((tileSize.height + INTERTILE) * y)},
        !           844:                   {tileSize.width, tileSize.height}};
        !           845: 
        !           846:     if (x < NUMTILESX && y < NUMTILESY && x >= 0 && y >= 0 && 
        !           847:        (tiles[x][y] != NOTILE)) {
        !           848:        [self incrementGameScore:tileScores[tiles[x][y]]];
        !           849:        ballYVel = restrictValue(ballYVel * tileAccs[tiles[x][y]], MAXYV);
        !           850:        [self drawBackground:&rect];
        !           851:        tiles[x][y] = NOTILE;
        !           852:        numTilesLeft--;
        !           853:        return YES;
        !           854:     } else {
        !           855:        return NO;
        !           856:     }
        !           857: }
        !           858: 
        !           859: 
        !           860: // The paddleHit method is called whenever the ball hits the paddle.
        !           861: // This method bounces the ball back at an angle depending on what part of
        !           862: //  the paddle was hit.
        !           863: 
        !           864: - paddleHit
        !           865: {
        !           866:     float whereHit = ((ballX + RADIUS) - paddleX) / paddleSize.width;
        !           867: 
        !           868:     ballYVel = -ballYVel;
        !           869:     ballY = paddleSize.height;
        !           870: 
        !           871:     [self playSound:paddleSound atXLoc:paddleX];
        !           872: 
        !           873:     // Alter the x-velocity and make sure it is in the valid range.
        !           874:     // If the ball hits the edges of the paddle, bounce it back at some angle.
        !           875:     
        !           876:     if (whereHit < 0.1) {
        !           877:        ballXVel = - MAXXV;
        !           878:     } else if (whereHit > 0.9) {
        !           879:        ballXVel = MAXXV;
        !           880:     } else {
        !           881:        // Now whereHit is in the range 0.1 .. 0.9, with 0.5 indicating middle
        !           882:        // of the paddle.  Convert to a number in the range 0.2 to 1, with 0.2
        !           883:        // indicating the middle and 1 either end.
        !           884:        whereHit = (fabs(whereHit - 0.5) + 0.1) * 2.0;  
        !           885:        ballXVel = ((ballXVel > 0.0) ? 1.0 : -1.0) * MAXXV * whereHit;
        !           886:     }
        !           887: 
        !           888:     return self;
        !           889: }
        !           890: 
        !           891: // If upon launch we discover that there's no sound, then we fail
        !           892: // silently. Note that although the SoundEffect class has the ability
        !           893: // to enable/disable sounds, because we might have multiple
        !           894: // BreakViews each with its own sound state, we keep a local state in
        !           895: // addition to the one in SoundEffect.
        !           896: 
        !           897: // Note that although this is an outlet method, we do not actually
        !           898: // have an outlet (instance variable) named soundStateFrom; we just
        !           899: // want to take a look at the initial value of the button and see
        !           900: // if sound needs to be turned on...
        !           901: 
        !           902: - setSoundStateFrom:sender
        !           903: {
        !           904:     if ([sender state]) {
        !           905:        [SoundEffect setSoundEnabled:YES];
        !           906:        if (!(soundEnabled = [SoundEffect soundEnabled])) {
        !           907:            [sender setState:NO];       // Silently fail
        !           908:        }
        !           909:     } else {
        !           910:        soundEnabled = NO;
        !           911:     }
        !           912:     return self;
        !           913: }
        !           914: 
        !           915: // If user tries to enable sound once the game is launched, and it
        !           916: // fails, then we do tell him/her about it.
        !           917: 
        !           918: - setSoundMode:sender
        !           919: {
        !           920:     BOOL desiredState = [sender state];
        !           921:     [self setSoundStateFrom:sender];
        !           922:     if (desiredState && !soundEnabled) {
        !           923:        NXRunAlertPanel (
        !           924:            NXLocalString ("No Sound", NULL, "Title of alert indicating sounds aren't available"),
        !           925:            NXLocalString ("Can't play sounds.", NULL, "Contents of alert panel"),
        !           926:            NXLocalString ("Bummer", NULL, "Acceptance that sounds can't be played"),
        !           927:            NULL, NULL);
        !           928:     }
        !           929:     return self;
        !           930: }
        !           931: 
        !           932: - (void)playSound:sound atXLoc:(float)xLoc
        !           933: {
        !           934:     if (soundEnabled) {
        !           935:        [sound play:1.0 pan:restrictValue((xLoc / gameSize.width - 0.5) * 2.0, 1.0)];
        !           936:     }
        !           937: }
        !           938: 
        !           939: // Alters the given velocity vector so that it is 
        !           940: // rotated by the indicated amount. We restrict both the resulting x and v
        !           941: // velocity values to the maximum of their max possible values...
        !           942: 
        !           943: - rotate:(float *)xVel :(float *)yVel by:(float)radians
        !           944: {
        !           945:     float newAngle = atan2 (*yVel, *xVel) + radians;
        !           946:     float velocity = hypot (*xVel, *yVel); 
        !           947: 
        !           948:     *yVel = restrictValue(velocity * sin(newAngle), MAX(MAXYV, MAXXV));
        !           949:     *xVel = restrictValue(velocity * cos(newAngle), MAX(MAXYV, MAXXV));
        !           950: 
        !           951:     return self;
        !           952: }
        !           953: 
        !           954: // The step method implements one step through the main game loop.
        !           955: // The distance traveled by the ball is adjusted by the time between frames.
        !           956: 
        !           957: - step:(double)timeNow
        !           958: {
        !           959:     NXPoint mouseLoc;
        !           960:     float newX;
        !           961:     unsigned int timeDelta = MIN(MAX((timeNow - lastFrameTime) * 1000, MINTIMEDIFFERENCE), MAXTIMEDIFFERENCE);
        !           962:     lastFrameTime = timeNow;
        !           963:    
        !           964:     [self lockFocus];
        !           965:     
        !           966:     [self eraseBall];
        !           967:     
        !           968:     // If the ball is rotating, rotate it by the indicated amount.
        !           969: 
        !           970:     if (revolutionsLeft > 0.0) {
        !           971:        float revsThisTime = revolutionSpeed * timeDelta;
        !           972:        [self rotate:&ballXVel :&ballYVel by:revsThisTime];
        !           973:        revolutionsLeft -= revsThisTime;
        !           974:        if (revolutionsLeft <= 0.0 && (fabs(ballYVel) < MAXYV * 0.6)) {
        !           975:            // Done rotating; make sure we have a good y-velocity
        !           976:            ballYVel = MAXYV * 0.8 * (ballYVel < 0.0 ? -1 : 1);
        !           977:            ballXVel = restrictValue(ballXVel,MAXXV);
        !           978:        }
        !           979:     } else if (ONEIN(1000 + (level < 8 ? (8 - level) * 250 : 0)) && (ballY > gameSize.height * 0.6)) {
        !           980:        // If we're not rotating, we go into rotating mode one out of 
        !           981:        // 1500 or more steps, provided that the ball is not too close to
        !           982:        // the paddle at the time.
        !           983:        revolutionsLeft = M_PI * (2 + RANDINT(5)); // 1 to 3.5 full turns
        !           984:        revolutionSpeed = MAXREVOLUTIONSPEED * (RANDINT(8) + 2.0) / 10.0;
        !           985:     } 
        !           986: 
        !           987:     // Update the ball location
        !           988: 
        !           989:     ballX += ballXVel * timeDelta * gameSize.width / GAMEWIDTH; 
        !           990:     ballY += ballYVel * timeDelta * gameSize.height / GAMEHEIGHT;
        !           991: 
        !           992: 
        !           993:     if (gameRunning) {
        !           994: 
        !           995:        if (ballX < 0.0) { // Hit on the left wall
        !           996:            ballX = 0.0;
        !           997:            ballXVel = -ballXVel; 
        !           998:            [self playSound:wallSound atXLoc:ballX];
        !           999:        } else if (ballX > gameSize.width - ballSize.width) { // Right wall
        !          1000:            ballX = gameSize.width - ballSize.width;
        !          1001:            ballXVel = -ballXVel; 
        !          1002:            [self playSound:wallSound atXLoc:ballX];
        !          1003:        }
        !          1004: 
        !          1005:        if (ballY > gameSize.height - ballSize.height) { // Top wall
        !          1006:            ballY = gameSize.height - ballSize.height;
        !          1007:            ballYVel = -ballYVel;
        !          1008:            if (niceBall && !ONEIN(5) && !demoMode) {
        !          1009:                NXPoint mid = {paddleX + paddleSize.width / 2.0, paddleY};
        !          1010:                [self directBallAt:&mid];
        !          1011:            } else if (ONEIN(10)) {
        !          1012:                ballXVel = MAXXV-(RANDINT((int)(MAXXV*20))/10.0);
        !          1013:            }
        !          1014:            [self playSound:wallSound atXLoc:ballX];
        !          1015:        }
        !          1016: 
        !          1017:        // Now checking for collisions with tiles... 
        !          1018: 
        !          1019:        {
        !          1020:            int y1 = (int)(floor(ballY /
        !          1021:                                    (tileSize.height + INTERTILE)));
        !          1022:            int x1 = (int)(floor((ballX - leftMargin) /
        !          1023:                                    (tileSize.width + INTERTILE)));
        !          1024:            int y2 = (int)(floor((ballY + ballSize.height) / 
        !          1025:                                    (tileSize.height + INTERTILE)));
        !          1026:            int x2 = (int)(floor((ballX + ballSize.width - leftMargin) / 
        !          1027:                                    (tileSize.width + INTERTILE)));
        !          1028:     
        !          1029:            if ([self hitTileAt:x1 :y1] | [self hitTileAt:x2 :y1] |
        !          1030:                [self hitTileAt:x1 :y2] | [self hitTileAt:x2 :y2]) {
        !          1031:                [self playSound:tileSound atXLoc:ballX];
        !          1032:                if (!killerBall) {
        !          1033:                    ballYVel = -ballYVel;
        !          1034:                }
        !          1035:                [scoreView setIntValue:score];
        !          1036:                [[self window] flushWindow];
        !          1037:            }
        !          1038:        }
        !          1039:     }
        !          1040: 
        !          1041:     // Get the mouse location and convert from window to the view coords.
        !          1042:     // If in demo, mode, make the paddle track the ball. Endless fun.
        !          1043: 
        !          1044:     if (demoMode) {
        !          1045:        mouseLoc.x = ballX + ballSize.width / 2.0;
        !          1046:     } else {
        !          1047:        [[self window] getMouseLocation:&mouseLoc];
        !          1048:        [self convertPoint:&mouseLoc fromView:nil];
        !          1049:     }
        !          1050: 
        !          1051:     newX = MAX(MIN(mouseLoc.x - paddleSize.width/2,
        !          1052:                    gameSize.width - paddleSize.width), 0);
        !          1053: 
        !          1054:     if (ballY >= paddleY + paddleSize.height) {
        !          1055: 
        !          1056:        // Ball is above the paddle; redraw it and the paddle and continue
        !          1057:        // We flush twice as the ball and the paddle are not too close 
        !          1058:        // together
        !          1059: 
        !          1060:        [self showBall];
        !          1061:        [[self window] flushWindow];
        !          1062:        [self erasePaddle];
        !          1063:        paddleX = newX;
        !          1064:        [self showPaddle];
        !          1065:        [[self window] flushWindow];
        !          1066: 
        !          1067:     } else if (ballY + ballSize.height > 0) {
        !          1068:        
        !          1069:        // Ball is past the paddle but not totally gone...
        !          1070: 
        !          1071:        [self erasePaddle];
        !          1072:        paddleX = newX;
        !          1073: 
        !          1074:        // Check to see if the user managed to catch the ball after all
        !          1075: 
        !          1076:        if ((ballY > paddleY - ballSize.height / 2.0) &&
        !          1077:            (ballX <= paddleX + paddleSize.width) &&
        !          1078:            (ballX + ballSize.width > paddleX)) {
        !          1079:            [self paddleHit];
        !          1080:        }
        !          1081: 
        !          1082:        // The ball and the paddle are close, so one flushWindow is fine.
        !          1083: 
        !          1084:        [self showBall];
        !          1085:        [self showPaddle];
        !          1086:        [[self window] flushWindow];
        !          1087: 
        !          1088:     } else {
        !          1089: 
        !          1090:        // Too late; the ball is out of sight...
        !          1091: 
        !          1092:        [self erasePaddle];
        !          1093:        [self stop:self];
        !          1094:        [self playSound:missSound atXLoc:0.0];
        !          1095: 
        !          1096:        if (--lives == 0) {
        !          1097:            if (score > highScore) [self setHighScore:score];
        !          1098:            [statusView setStringValue:NXLocalString("Game Over", NULL, "Message indicating that the game is over...")];
        !          1099:        } else {
        !          1100:            [self resetBallAndPaddle]; 
        !          1101:            [self showBall];
        !          1102:            [self showPaddle]; 
        !          1103:            [statusView setStringValue:NXLocalString("Game Ready", NULL, "Message indicating the game is ready to run")];
        !          1104:        }
        !          1105:        [[self window] flushWindow];
        !          1106: 
        !          1107:        [livesView setIntValue:lives];
        !          1108:        
        !          1109:     }
        !          1110: 
        !          1111:     // numTilesLeft <= 0 indicates that we've blown away every tile. But,
        !          1112:     // to make the game more exciting, we start decrementing numTilesLeft, 
        !          1113:     // by one everytime through this loop, until it reaches the value 
        !          1114:     // STOPGAMEAT. This makes the ball move a bit more after all the tiles 
        !          1115:     // are gone. But, if gameRunning is NO, then it means we probably just
        !          1116:     // missed the ball, in which case we should go ahead and jump to the 
        !          1117:     // next level.
        !          1118:     
        !          1119:     if ((numTilesLeft <= 0) && 
        !          1120:        ((lives && !gameRunning) || (--numTilesLeft == STOPGAMEAT))) {
        !          1121:        [self incrementGameScore:LEVELBONUS];
        !          1122:        [self gotoNextLevel:self];
        !          1123:     }
        !          1124: 
        !          1125:     NXPing (); // Synchronize postscript for smoother animation
        !          1126: 
        !          1127:     [self unlockFocus];
        !          1128: 
        !          1129:     return self;
        !          1130: }
        !          1131: 
        !          1132: // Pretty much a dummy function to invoke the step method.
        !          1133: 
        !          1134: void runOneStep (DPSTimedEntry timedEntry, double timeNow, void *data)
        !          1135: {
        !          1136:     [(id)data step:timeNow];
        !          1137: }
        !          1138: 
        !          1139: 
        !          1140: @end

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.