This is TetMatrix.m in view mode; [Download] [Up]
#import <appkit/NXImage.h> #import <ctype.h> #import <dpsclient/wraps.h> #import <math.h> #import <appkit/graphics.h> #import "TetMatrix.h" #import "Piece.h" #import "ScoreKeeper.h" #import "TetApp.h" #import "wraps.h" // The number of pixels a block moves every step // Make it a divisor of the block size static const float ANM_DELTA = 8.0; const float GAMEOVER_BMAP_H = 100.0; extern long random(); extern void srandom(int seed); extern int getpid(); @implementation TetMatrix /* * Some things should be set differently for color machines for either * performances or aesthetic reasons. */ - initForColor { const NXScreen *deepestScreen; deepestScreen=[NXApp colorScreen]; if ( deepestScreen->depth == NX_TwoBitGrayDepth ) { anm_gameover_delta = 1.0; } else { anm_gameover_delta = 2.0; } return self; } - initFrame:(const NXRect *)frameRect { NXSize size, interCell; extern BOOL resize(NXSize *aSize); float viewWidth, viewHeight; // Set the # of rows and columns in the view [super initFrame:frameRect numRows:TETRIS_ROWS numCols:TETRIS_COLUMNS]; [self setBackgroundGray:NX_BLACK]; size.width = size.height = 6.0; [self setInset:&size]; // Set the amount of space to leave b/w each block interCell.width = interCell.height = 1.0; // 1 pixel [self setIntercell:&interCell]; pieceVisible = active = NO; thePiece = [[Piece alloc] init]; [thePiece getBlockSize:&size]; // Should only resize in Piece.m resize(&size); // Make sure block image isn't too large [super setElementSize:&size]; // Size Game View. // Default view size is (182, 438) // Getting (176, 416) viewWidth = 6. + TETRIS_COLUMNS * (size.width + interCell.width) + 6.; viewHeight = 6. + (TETRIS_ROWS)* (size.height + interCell.height) + 6.; #ifdef DEBUG fprintf(stderr, "Sizing the Tetris view to (%f,%f)\n", viewWidth, viewHeight); #endif [self sizeTo: viewWidth :viewHeight]; scoreKeeper = nil; anmBitmap = [[NXImage allocFromZone:[self zone]] initSize:&bounds.size]; // Eventually want the background to be setable // anmBitmap = [NXImage initSize:&bounds.size]; // [anmBitmap useFromFile:"Snow.tiff"]; showNext = nil; [self initForColor]; gameRunning = NO; srandom(getpid()); // Seed random # generator return self; } - setupRandomFill:(int)max { int blockNum; int row, col; if (max > TETRIS_ROWS) max = TETRIS_ROWS; if (max > 10) max = 10; // fprintf (stderr, "Start random fill ...\n"); for (row = 0; row < max; row++) { for (col = 0; col < TETRIS_COLUMNS; col++) { if ((random () & 0x3) == 0) { /* set 1/4 */ blockNum = (random () & 0x3); // Rand num 0..3 [super setBitmap: [thePiece getBlockImage:blockNum] at:row :col]; } } } return self; } - (BOOL)acceptsFirstResponder { return active; } - drawSelf:(const NXRect *)rects :(int)rectCount { [super drawSelf:rects :rectCount]; if (pieceVisible) { NXRectClip(&insetBounds); [thePiece draw:self]; } return self; } /* * Act upon keyboard input from the user. * */ - keyDown:(NXEvent *)theEvent { unsigned short charCode; charCode = theEvent->data.key.charCode; if (isupper(charCode)) charCode = tolower(charCode); switch (charCode) { case 'k': case '5': if (charCode == 'k' || theEvent->flags & NX_NUMERICPADMASK) [thePiece turn:self]; break; case 'j': case '4': if (charCode == 'j' || theEvent->flags & NX_NUMERICPADMASK) [thePiece left:self]; break; case 'l': case '6': if (charCode == 'l' || theEvent->flags & NX_NUMERICPADMASK) [thePiece right:self]; break; case ' ': case '2': case '0': if (charCode == ' ' || theEvent->flags & NX_NUMERICPADMASK) { [self stopTimedEntry]; [thePiece drop:self]; [self startTimedEntry]; } break; case 13: // Pause if the user hits return [NXApp pause:self]; } return self; } - setPieceVisible:(BOOL)flag { pieceVisible = flag; return self; } - setScoreKeeper:keeper { scoreKeeper = keeper; return self; } /* * Determine how fast a piece will drop. */ -(double) newDelay { return 0.5 - 0.1 * sqrt(2.5 * level); } - newGame:(int)theLevel { [[super setBitmap:nil] display]; [window makeFirstResponder:self]; [self setPieceVisible:YES]; [thePiece reset:self piece:((showNext) ? [showNext pieceInfo] : PIECE_INFO_NULL)]; [self setupRandomFill:[randomFields intValue]]; [self display]; level = (float)theLevel; teDelay = [self newDelay]; [self startTimedEntry]; active = YES; return self; } - pause:sender { // Make TetApp the first responder [window makeFirstResponder:window]; // Will making this object the first responder cause problems? // [window makeFirstResponder:self]; active = NO; return [self stopTimedEntry]; } - continue:sender { active = YES; [window makeFirstResponder:self]; return [self startTimedEntry]; } - stop:sender { active = NO; [window makeFirstResponder:window]; return [self stopTimedEntry]; } /* * Scroll "Game Over" down the screen after the game is finished. */ - animateGameOver { NXSize tmpSize; id anmGameOver; NXRect destRect, unionRect; tmpSize.height = GAMEOVER_BMAP_H; tmpSize.width = insetBounds.size.width; anmGameOver = [[NXImage alloc] initSize:&tmpSize]; // Composite "Game Over" to a window [anmGameOver lockFocus]; PSsetalpha(0.0); PSrectfill(0.0, 0.0, insetBounds.size.width, GAMEOVER_BMAP_H); PSsetalpha(1.0); PSgameover(insetBounds.size.width); [anmGameOver unlockFocus]; [super getRect:&destRect for:(int)(TETRIS_ROWS / 2) :0]; [super getRect:&unionRect for:TETRIS_ROWS - 1 :TETRIS_COLUMNS - 1]; NXUnionRect(&unionRect, &destRect); [anmBitmap lockFocus]; [self drawSelf:&destRect :1]; [anmBitmap unlockFocus]; NXSetRect(&unionRect, destRect.origin.x, unionRect.origin.y, destRect.size.width, GAMEOVER_BMAP_H); [self lockFocus]; NXRectClip(&destRect); while (unionRect.origin.y - anm_gameover_delta >= destRect.origin.y) { unionRect.origin.y -= anm_gameover_delta; [anmGameOver composite:NX_SOVER toPoint:&unionRect.origin]; [[self window] flushWindow]; NXPing(); // Redraw the background to erase the "Game Over" letters // [anmBitmap composite:NX_COPY fromRect:&unionRect toPoint:&unionRect.origin]; [anmBitmap composite:NX_SOVER fromRect:&unionRect toPoint:&unionRect.origin]; NXPing(); } [anmGameOver composite:NX_SOVER toPoint:&unionRect.origin]; [self unlockFocus]; [anmGameOver free]; return self; } /* The following methods: * Animate a piece dropping. * Dissolve filled rows. * Removes filled rows from the game. */ - (BOOL) rowFilled:(int)row { int xc; for (xc = 0; xc < TETRIS_COLUMNS; xc++) if (![super bitmapAt:row :xc]) break; return (xc == TETRIS_COLUMNS); } - (BOOL) rowEmpty:(int) row { int xc; for (xc = 0; xc < TETRIS_COLUMNS; xc++) if ([super bitmapAt:row :xc]) break; return (xc == TETRIS_COLUMNS); } - fadeFilledRows:(int) from :(int) to { float gc; NXRect destRect; NXRect unionRect; [super getRect:&destRect for:from :0]; [super getRect:&unionRect for:to :TETRIS_COLUMNS - 1]; NXUnionRect(&unionRect, &destRect); // Composite the rows we are about to fade to anmBitmap [anmBitmap lockFocus]; [self drawSelf:&destRect :1]; [anmBitmap unlockFocus]; // Fade the blocks [self lockFocus]; for (gc = 1.0; gc > 0.3; gc -= .03) { PSsetgray(gc); PScompositerect(destRect.origin.x, destRect.origin.y, destRect.size.width, destRect.size.height, NX_PLUSD); [[self window] flushWindow]; // Now redraw the blocks so we can fade them a little more // [anmBitmap composite:NX_COPY fromRect:&destRect toPoint:&destRect.origin]; [anmBitmap composite:NX_SOVER fromRect:&destRect toPoint:&destRect.origin]; NXPing(); } // Now make the blocks totally disappear. PSsetgray(NX_BLACK); PScompositerect(destRect.origin.x, destRect.origin.y, destRect.size.width, destRect.size.height, NX_SOVER); // destRect.size.height, NX_COPY); [self unlockFocus]; return self; } /* * Move the pieces as they are moved down on the screen after one or more rows * has been cleared. */ - animateDrop:(int) firstFullRow :(int)firstNonFullRow :(int) lastNonEmptyRow { NXPoint dPt; NXRect destRect; NXRect unionRect; [super point:&dPt for:firstFullRow :0]; // Place where to put falling blocks [super getRect:&destRect for:firstNonFullRow :0]; // First row to move down // Last row to move down [super getRect:&unionRect for:lastNonEmptyRow :TETRIS_COLUMNS - 1]; // We want to move everything down from the last non-full row to // the last row with a piece in it. NXUnionRect(&unionRect, &destRect); unionRect = destRect; // Create the image of the blocks [anmBitmap lockFocus]; [self drawSelf:&destRect :1]; [anmBitmap unlockFocus]; [self lockFocus]; PSsetgray(NX_BLACK); // The destination Rect is Black PScompositerect(destRect.origin.x, destRect.origin.y, destRect.size.width, destRect.size.height, NX_COPY); while (unionRect.origin.y - ANM_DELTA > dPt.y) { unionRect.origin.y -= ANM_DELTA; // First clear the window [anmBitmap composite:NX_COPY fromRect:&destRect toPoint:&unionRect.origin]; [[self window] flushWindow]; PScompositerect(unionRect.origin.x, unionRect.origin.y, unionRect.size.width, unionRect.size.height, NX_COPY); NXPing(); } [anmBitmap composite:NX_COPY fromRect:&destRect toPoint:&dPt]; [[self window] flushWindow]; // NXPing(); [self unlockFocus]; return self; } /* * Remove the filled rows after they have dissolved */ - removeFilledRows { BOOL filled; int ycfrom, ycto, yctop; int xc, yc; int lastFilledRow, lastNonEmptyRow, firstFilled; int piecey = [thePiece getCurRow]; // Get the bottom-most row of the piece. do { filled = NO; // This should only execute once?!?! // Work our way up from the current piece's row. for (ycfrom = piecey; ycfrom < piecey + MAX_SHAPE_SIZE; ycfrom++) { if ([self rowFilled: ycfrom]) { filled = YES; firstFilled = ycfrom; break; } } if (filled) { // Find the first row that isn't completely filled. lastFilledRow = firstFilled; // Loop can execute 0 times for (ycto = firstFilled + 1; ycto < firstFilled + MAX_SHAPE_SIZE; ycto++) { if ([self rowFilled: ycto]) { lastFilledRow = ycto; } else { break; } } // Find the topmost row in the program. A row with at least one block. // Assert: Must enter loop at least once. for (yctop = lastFilledRow+1; yctop < TETRIS_ROWS; yctop++) { if (! [self rowEmpty: yctop]) { lastNonEmptyRow = yctop; // But do I have to execute this once? } else { break; } } [self fadeFilledRows: firstFilled :lastFilledRow]; // Animate the all of the pieces as the move down. [self animateDrop:firstFilled :lastFilledRow+1 :lastNonEmptyRow]; // Move the rows above the faded rows down to take their place. for (yc = lastFilledRow+1; yc <=lastNonEmptyRow; yc++) { for (xc = 0; xc < TETRIS_COLUMNS; xc++) // For each block in the row. [super setBitmap:[self bitmapAt:yc :xc] at:firstFilled :xc]; firstFilled++; } // Clear the rows at the very top that have been moved down // and which have nothing to take their place. for (yc = firstFilled; yc <=lastNonEmptyRow; yc++) { #ifdef DEBUG printf("Removing row %d\n", yc); #endif for (xc = 0; xc < TETRIS_COLUMNS; xc++) [super setBitmap:nil at:yc :xc]; } piecey++; } } while (filled); return self; } /* The main animation routines follow. A timed entry routine is setup * so that it calls teHandler at a regular interval (depending on the * level) */ /* * The timed entry handler that gets called every teDelay seconds. * */ - step { static int downWait = 0; // Move the piece down. If it can't move down make it stick, // remove filled rows, and generate a new piece. if (![thePiece down:self]) { // After the piece sticks to the bottom, wait for a while before // we send the next piece. The wait depends on the current level. if (++downWait >= level) { // Don't commit the piece immediately downWait = 0; [self stopTimedEntry]; [self setPieceVisible:NO]; [thePiece stick:self]; // Make the piece stick [self removeFilledRows]; [self setPieceVisible:YES]; if (scoreKeeper) { [scoreKeeper addScore:[thePiece points]]; level += (MAX_LEVEL - level) / 400.0; teDelay = [self newDelay]; } // show the next piece. if ([thePiece reset:self piece:((showNext) ? [showNext pieceInfo] : PIECE_INFO_NULL)]) [self startTimedEntry]; else { [self animateGameOver]; [[self window] makeFirstResponder:[self window]]; active = NO; [NXApp gameOver]; } } } else { // The piece can't go down downWait = 0; // Set a delay before showing next piece } return self; } static void runOneStep (DPSTimedEntry timedEntry, double timeNow, void *data) { [(id)data step]; } - startTimedEntry { if (!gameRunning) { timedEntryNum = DPSAddTimedEntry(teDelay,&runOneStep, self, NX_BASETHRESHOLD); gameRunning = YES; } return self; } - stopTimedEntry { if (gameRunning) { DPSRemoveTimedEntry(timedEntryNum); gameRunning = NO; } return self; } /* * Clean up. */ - free { [thePiece free]; [anmBitmap free]; return [super free]; } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.