Annotation of Examples/AppKit/BreakApp/BreakView.m, revision 1.1.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.