//旋转变换根据http://tetris.wikia.com/wiki/SRS void Game::rotateTetromino(bool clockwise) { //保存旋转后的数组形状,用来检测碰撞 int rotated[TETROMINO_SIZE][TETROMINO_SIZE]; //如果是O形,不用旋转 if(mFallingBlock.type == TETROMINO_O) { return; } setMatrixCells(&rotated[0][0], TETROMINO_SIZE, TETROMINO_SIZE, EMPTY_CELL); //将旋转后的cell存入rotated数组 for (int i = 0; i < mFallingBlock.size; ++i) { for (int j = 0; j < mFallingBlock.size; ++j) { if(clockwise) { rotated[mFallingBlock.size - j - 1][i] = mFallingBlock.cells[i][j]; } else { rotated[j][mFallingBlock.size - i - 1] = mFallingBlock.cells[i][j]; } } } /* 这里为了简单,我不允许kick wall,也就是当方块贴住墙壁的时候,不允许旋转 */ for (int i = 0; i < mFallingBlock.size; ++i) { for (int j = 0; j < mFallingBlock.size; ++j) { if (rotated[i][j] != EMPTY_CELL) { //检测和墙壁的碰撞 if (mFallingBlock.x + i < 0 //碰到左墙壁 //必须在墙内 || mFallingBlock.x + i >= BOARD_TILEMAP_WIDTH //碰到右墙壁 || mFallingBlock.y + j >= BOARD_TILEMAP_HEIGHT) {//碰到地面 return; } //检测和已有方块的碰撞 if(mMap[mFallingBlock.x + i][mFallingBlock.y + j] != EMPTY_CELL) { return; } } } } //如果没有碰撞,那么就用rotated替代mFallingBlock for (int i = 0; i < mFallingBlock.size; ++i) { for (int j = 0; j < mFallingBlock.size; ++j) { mFallingBlock.cells[i][j] = rotated[i][j]; } } onTetrominoMoved(); }
/* Start a new game */ static void startGame(StcGame *game) { int i; /* Initialize game data */ game->errorCode = ERROR_NONE; game->data->systemTime = platformGetSystemTime(); game->data->lastFallTime = game->data->systemTime; game->data->isOver = 0; game->isPaused = 0; game->showPreview = 1; game->data->events = EVENT_NONE; game->data->fallingDelay = STC_INIT_DELAY_FALL; #ifdef STC_SHOW_GHOST_PIECE game->showShadow = 1; #endif /* Initialize game statistics */ game->stats.score = 0; game->stats.lines = 0; game->stats.totalPieces = 0; game->stats.level = 0; for (i = 0; i < TETROMINO_TYPES; ++i) { game->stats.pieces[i] = 0; } /* Initialize game tile map */ setMatrixCells(&game->map[0][0], BOARD_TILEMAP_WIDTH, BOARD_TILEMAP_HEIGHT, EMPTY_CELL); /* Initialize falling tetromino */ setTetromino(platformRandom() % TETROMINO_TYPES, &game->fallingBlock); game->fallingBlock.x = (BOARD_TILEMAP_WIDTH - game->fallingBlock.size) / 2; game->fallingBlock.y = 0; /* Initialize preview tetromino */ setTetromino(platformRandom() % TETROMINO_TYPES, &game->nextBlock); /* Initialize events */ onTetrominoMoved(game); /* Initialize delayed autoshift */ game->data->delayLeft = -1; game->data->delayRight = -1; game->data->delayDown = -1; #ifdef STC_AUTO_ROTATION game->data->delayRotation = -1; #endif }
// Start a new game void Game::start() { // Initialize game data mErrorCode = ERROR_NONE; mSystemTime = mPlatform->getSystemTime(); mLastFallTime = mSystemTime; mIsOver = false; mIsPaused = false; mShowPreview = true; mEvents = EVENT_NONE; mFallingDelay = INIT_DELAY_FALL; #ifdef STC_SHOW_GHOST_PIECE mShowShadow = true; #endif // Initialize game statistics mStats.score = 0; mStats.lines = 0; mStats.totalPieces = 0; mStats.level = 0; for (int i = 0; i < TETROMINO_TYPES; ++i) { mStats.pieces[i] = 0; } // Initialize game tile map setMatrixCells(&mMap[0][0], BOARD_TILEMAP_WIDTH, BOARD_TILEMAP_HEIGHT, EMPTY_CELL); // Initialize falling tetromino setTetromino(mPlatform->random() % TETROMINO_TYPES, &mFallingBlock); mFallingBlock.x = (BOARD_TILEMAP_WIDTH - mFallingBlock.size) / 2; mFallingBlock.y = 0; // Initialize preview tetromino setTetromino(mPlatform->random() % TETROMINO_TYPES, &mNextBlock); // Initialize events onTetrominoMoved(); // Initialize delayed autoshift mDelayLeft = -1; mDelayRight = -1; mDelayDown = -1; #ifdef STC_AUTO_ROTATION mDelayRotation = -1; #endif }
void Game::start() { mErrorCode = ERROR_NONE; mSystemTime = mPlatform->getSystemTime(); mLastFallTime = mSystemTime; mIsOver = false; mIsPaused = false; mShowPreview = true; mEvents = EVENT_NONE; mFallingDelay = INIT_DELAY_FALL; mShowShadow = false; mStats.score = 0; mStats.lines = 0; mStats.totalPieces = 0; mStats.level = 0; for (int i = 0; i < TETROMINO_TYPES; ++i) { mStats.pieces[i] = 0; } //初始化整个棋盘 setMatrixCells(&mMap[0][0], BOARD_TILEMAP_WIDTH, BOARD_TILEMAP_HEIGHT, EMPTY_CELL); //开始时随机设置一个nextblock作为第一个形状 setTetromino(mPlatform->random() % TETROMINO_TYPES, &mFallingBlock); //初始化为mFallingBlock的up-left mFallingBlock.x = BOARD_TILEMAP_WIDTH / 2 - mFallingBlock.size / 2; mFallingBlock.y = 0; setTetromino(mPlatform->random() % TETROMINO_TYPES, &mNextBlock); onTetrominoMoved(); mDelayDown = -1; mDelayLeft = -1; mDelayRight = -1; mDelayRotation = -1; }
/* * Move tetromino in the direction specified by (x, y) (in tile units) * This function detects if there are filled rows or if the move * lands a falling tetromino, also checks for game over condition. */ static void moveTetromino(StcGame *game, int x, int y) { int i, j, hasFullRow, numFilledRows; /* Check if the move would create a collision */ if (checkCollision(game, x, y)) { /* In case of collision check if move was downwards (y == 1) */ if (y == 1) { /* Check if collision occurs when the falling * tetromino is on the 1st or 2nd row */ if (game->fallingBlock.y <= 1) { game->data->isOver = 1; /* if this happens the game is over */ } else { /* The falling tetromino has reached the bottom, * so we copy their cells to the board map */ for (i = 0; i < game->fallingBlock.size; ++i) { for (j = 0; j < game->fallingBlock.size; ++j) { if (game->fallingBlock.cells[i][j] != EMPTY_CELL) { game->map[game->fallingBlock.x + i][game->fallingBlock.y + j] = game->fallingBlock.cells[i][j]; } } } /* Check if the landing tetromino has created full rows */ numFilledRows = 0; for (j = 1; j < BOARD_TILEMAP_HEIGHT; ++j) { hasFullRow = 1; for (i = 0; i < BOARD_TILEMAP_WIDTH; ++i) { if (game->map[i][j] == EMPTY_CELL) { hasFullRow = 0; break; } } /* If we found a full row we need to remove that row from the map * we do that by just moving all the above rows one row below */ if (hasFullRow != 0) { for (x = 0; x < BOARD_TILEMAP_WIDTH; ++x) { for (y = j; y > 0; --y) { game->map[x][y] = game->map[x][y - 1]; } } numFilledRows++; /* increase filled row counter */ } } /* Update game statistics */ if (numFilledRows > 0) { onFilledRows(game, numFilledRows); } game->stats.totalPieces++; game->stats.pieces[game->fallingBlock.type]++; /* Use preview tetromino as falling tetromino. * Copy preview tetromino for falling tetromino */ for (i = 0; i < TETROMINO_SIZE; ++i) { for (j = 0; j < TETROMINO_SIZE; ++j) { game->fallingBlock.cells[i][j] = game->nextBlock.cells[i][j]; } } game->fallingBlock.size = game->nextBlock.size; game->fallingBlock.type = game->nextBlock.type; /* Reset position */ game->fallingBlock.y = 0; game->fallingBlock.x = (BOARD_TILEMAP_WIDTH - game->fallingBlock.size) / 2; onTetrominoMoved(game); /* Create next preview tetromino */ setTetromino(platformRandom() % TETROMINO_TYPES, &game->nextBlock); } } } else { /* There are no collisions, just move the tetromino */ game->fallingBlock.x += x; game->fallingBlock.y += y; } onTetrominoMoved(game); }
/* * Rotate falling tetromino. If there are no collisions when the * tetromino is rotated this modifies the tetromino's cell buffer. */ void rotateTetromino(StcGame *game, int clockwise) { int i, j; #ifdef STC_WALL_KICK_ENABLED int wallDisplace; #endif int rotated[TETROMINO_SIZE][TETROMINO_SIZE]; /* temporary array to hold rotated cells */ /* If TETROMINO_O is falling return immediately */ if (game->fallingBlock.type == TETROMINO_O) { return; /* rotation doesn't require any changes */ } /* Initialize rotated cells to blank */ setMatrixCells(&rotated[0][0], TETROMINO_SIZE, TETROMINO_SIZE, EMPTY_CELL); /* Copy rotated cells to the temporary array */ for (i = 0; i < game->fallingBlock.size; ++i) { for (j = 0; j < game->fallingBlock.size; ++j) { if (clockwise) { rotated[game->fallingBlock.size - j - 1][i] = game->fallingBlock.cells[i][j]; } else { rotated[j][game->fallingBlock.size - i - 1] = game->fallingBlock.cells[i][j]; } } } #ifdef STC_WALL_KICK_ENABLED wallDisplace = 0; /* Check collision with left wall */ if (game->fallingBlock.x < 0) { for (i = 0; (wallDisplace == 0) && (i < -game->fallingBlock.x); ++i) { for (j = 0; j < game->fallingBlock.size; ++j) { if (rotated[i][j] != EMPTY_CELL) { wallDisplace = i - game->fallingBlock.x; break; } } } } /* Or check collision with right wall */ else if (game->fallingBlock.x > BOARD_TILEMAP_WIDTH - game->fallingBlock.size) { i = game->fallingBlock.size - 1; for (; (wallDisplace == 0) && (i >= BOARD_TILEMAP_WIDTH - game->fallingBlock.x); --i) { for (j = 0; j < game->fallingBlock.size; ++j) { if (rotated[i][j] != EMPTY_CELL) { wallDisplace = -game->fallingBlock.x - i + BOARD_TILEMAP_WIDTH - 1; break; } } } } /* Check collision with board floor and other cells on board */ for (i = 0; i < game->fallingBlock.size; ++i) { for (j = 0; j < game->fallingBlock.size; ++j) { if (rotated[i][j] != EMPTY_CELL) { /* Check collision with bottom border of the map */ if (game->fallingBlock.y + j >= BOARD_TILEMAP_HEIGHT) { return; /* there was collision therefore return */ } /* Check collision with existing cells in the map */ if (game->map[i + game->fallingBlock.x + wallDisplace][j + game->fallingBlock.y] != EMPTY_CELL) { return; /* there was collision therefore return */ } } } } /* Move the falling piece if there was wall collision and it's a legal move */ if (wallDisplace != 0) { game->fallingBlock.x += wallDisplace; } #else /* Check collision of the temporary array */ for (i = 0; i < game->fallingBlock.size; ++i) { for (j = 0; j < game->fallingBlock.size; ++j) { if (rotated[i][j] != EMPTY_CELL) { /* Check collision with left, right or bottom borders of the map */ if ((game->fallingBlock.x + i < 0) || (game->fallingBlock.x + i >= BOARD_TILEMAP_WIDTH) || (game->fallingBlock.y + j >= BOARD_TILEMAP_HEIGHT)) { return; /* there was collision therefore return */ } /* Check collision with existing cells in the map */ if (game->map[i + game->fallingBlock.x][j + game->fallingBlock.y] != EMPTY_CELL) { return; /* there was collision therefore return */ } } } } #endif /* There are no collisions, replace tetromino cells with rotated cells */ for (i = 0; i < TETROMINO_SIZE; ++i) { for (j = 0; j < TETROMINO_SIZE; ++j) { game->fallingBlock.cells[i][j] = rotated[i][j]; } } onTetrominoMoved(game); }
// Move tetromino in the direction specified by (x, y) (in tile units) // This function detects if there are filled rows or if the move // lands a falling tetromino, also checks for game over condition. void Game::moveTetromino(int x, int y) { int i, j; // Check if the move would create a collision if (checkCollision(x, y)) { // In case of collision check if move was downwards (y == 1) if (y == 1) { // Check if collision occurs when the falling // tetromino is on the 1st or 2nd row if (mFallingBlock.y <= 1) { mIsOver = true; // if this happens the game is over } else { // The falling tetromino has reached the bottom, // so we copy their cells to the board map for (i = 0; i < mFallingBlock.size; ++i) { for (j = 0; j < mFallingBlock.size; ++j) { if (mFallingBlock.cells[i][j] != EMPTY_CELL) { mMap[mFallingBlock.x + i][mFallingBlock.y + j] = mFallingBlock.cells[i][j]; } } } // Check if the landing tetromino has created full rows int numFilledRows = 0; for (j = 1; j < BOARD_TILEMAP_HEIGHT; ++j) { bool hasFullRow = true; for (i = 0; i < BOARD_TILEMAP_WIDTH; ++i) { if (mMap[i][j] == EMPTY_CELL) { hasFullRow = false; break; } } // If we found a full row we need to remove that row from the map // we do that by just moving all the above rows one row below if (hasFullRow) { for (x = 0; x < BOARD_TILEMAP_WIDTH; ++x) { for (y = j; y > 0; --y) { mMap[x][y] = mMap[x][y - 1]; } } numFilledRows++; // increase filled row counter } } // Update game statistics if (numFilledRows > 0) { onFilledRows(numFilledRows); } mStats.totalPieces++; mStats.pieces[mFallingBlock.type]++; // Use preview tetromino as falling tetromino. // Copy preview tetromino for falling tetromino for (i = 0; i < TETROMINO_SIZE; ++i) { for (j = 0; j < TETROMINO_SIZE; ++j) { mFallingBlock.cells[i][j] = mNextBlock.cells[i][j]; } } mFallingBlock.size = mNextBlock.size; mFallingBlock.type = mNextBlock.type; // Reset position mFallingBlock.y = 0; mFallingBlock.x = (BOARD_TILEMAP_WIDTH - mFallingBlock.size) / 2; onTetrominoMoved(); // Create next preview tetromino setTetromino(mPlatform->random() % TETROMINO_TYPES, &mNextBlock); } } } else { // There are no collisions, just move the tetromino mFallingBlock.x += x; mFallingBlock.y += y; } onTetrominoMoved(); }
// Rotate falling tetromino. If there are no collisions when the // tetromino is rotated this modifies the tetromino's cell buffer. void Game::rotateTetromino(bool clockwise) { int i, j; int rotated[TETROMINO_SIZE][TETROMINO_SIZE]; // temporary array to hold rotated cells // If TETROMINO_O is falling return immediately if (mFallingBlock.type == TETROMINO_O) { return; // rotation doesn't require any changes } // Initialize rotated cells to blank setMatrixCells(&rotated[0][0], TETROMINO_SIZE, TETROMINO_SIZE, EMPTY_CELL); // Copy rotated cells to the temporary array for (i = 0; i < mFallingBlock.size; ++i) { for (j = 0; j < mFallingBlock.size; ++j) { if (clockwise) { rotated[mFallingBlock.size - j - 1][i] = mFallingBlock.cells[i][j]; } else { rotated[j][mFallingBlock.size - i - 1] = mFallingBlock.cells[i][j]; } } } #define STC_WALL_KICK_ENABLED #ifdef STC_WALL_KICK_ENABLED int wallDisplace = 0; // Check collision with left wall if (mFallingBlock.x < 0) { for (i = 0; (wallDisplace == 0) && (i < -mFallingBlock.x); ++i) { for (j = 0; j < mFallingBlock.size; ++j) { if (rotated[i][j] != EMPTY_CELL) { wallDisplace = i - mFallingBlock.x; break; } } } } // Or check collision with right wall else if (mFallingBlock.x > BOARD_TILEMAP_WIDTH - mFallingBlock.size) { i = mFallingBlock.size - 1; for (; (wallDisplace == 0) && (i >= BOARD_TILEMAP_WIDTH - mFallingBlock.x); --i) { for (j = 0; j < mFallingBlock.size; ++j) { if (rotated[i][j] != EMPTY_CELL) { wallDisplace = -mFallingBlock.x - i + BOARD_TILEMAP_WIDTH - 1; break; } } } } // Check collision with board floor and other cells on board for (i = 0; i < mFallingBlock.size; ++i) { for (j = 0; j < mFallingBlock.size; ++j) { if (rotated[i][j] != EMPTY_CELL) { // Check collision with bottom border of the map if (mFallingBlock.y + j >= BOARD_TILEMAP_HEIGHT) { return; // there was collision therefore return } // Check collision with existing cells in the map if (mMap[i + mFallingBlock.x + wallDisplace][j + mFallingBlock.y] != EMPTY_CELL) { return; // there was collision therefore return } } } } // Move the falling piece if there was wall collision and it's a legal move if (wallDisplace != 0) { mFallingBlock.x += wallDisplace; } #else // Check collision of the temporary array for (i = 0; i < mFallingBlock.size; ++i) { for (j = 0; j < mFallingBlock.size; ++j) { if (rotated[i][j] != EMPTY_CELL) { // Check collision with left, right or bottom borders of the map if ((mFallingBlock.x + i < 0) || (mFallingBlock.x + i >= BOARD_TILEMAP_WIDTH) || (mFallingBlock.y + j >= BOARD_TILEMAP_HEIGHT)) { return; // there was collision therefore return } // Check collision with existing cells in the map if (mMap[i + mFallingBlock.x][j + mFallingBlock.y] != EMPTY_CELL) { return; // there was collision therefore return } } } } #endif // STC_WALL_KICK_ENABLED // There are no collisions, replace tetromino cells with rotated cells for (i = 0; i < TETROMINO_SIZE; ++i) { for (j = 0; j < TETROMINO_SIZE; ++j) { mFallingBlock.cells[i][j] = rotated[i][j]; } } onTetrominoMoved(); }
//将方块向x,y方向移动 //检测是否与已有的cell冲突或者已经落地,同时检查游戏是否结束 void Game::moveTetromino(int x, int y) { //如果有冲突 if (checkCollision(x, y)) { //如果是往下移动的 if (y == 1) { //如果mFallingBlock刚向下移动了一格,或者还没移动就冲突了,代表游戏结束 if (mFallingBlock.y <= 1) { mIsOver = true; } else { //游戏没有结束,mFallingBlock落地 //那么把mFallingBlock内的元素copy到mMap中 for (int i = 0; i < mFallingBlock.size; ++i) { for (int j = 0; j < mFallingBlock.size; ++j) { if (mFallingBlock.cells[i][j] != EMPTY_CELL) { mMap[mFallingBlock.x + i][mFallingBlock.y + j] = mFallingBlock.cells[i][j]; } } } } //检查落地是否能够得分 /* x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x x 这是mMap的形状 */ int numFilledRows = 0; for (int j = 1; j < BOARD_TILEMAP_HEIGHT; ++j) { bool hasFullRow = true; for (int i = 0; i < BOARD_TILEMAP_WIDTH; ++i) { if (mMap[i][j] == EMPTY_CELL) { hasFullRow = false; break; } } //这一排满了 //将这一排上面的都往下移动一排,相当于消除这一排 //注意:(0, 0)点在上面图形的右上角,也就是屏幕的左上角 if (hasFullRow) { for (int t = j; t > 0; --t) { for (int p = 0; p < BOARD_TILEMAP_WIDTH; ++p) { mMap[p][t] = mMap[p][t - 1]; } } numFilledRows++; } } //计算分数 if (numFilledRows > 0) { onFilledRows(numFilledRows); } mStats.totalPieces++; mStats.pieces[mFallingBlock.type]++; //用下一个形状代替fallingblock for (int i = 0; i < TETROMINO_SIZE; ++i) { for (int j = 0; j < TETROMINO_SIZE; ++j) { mFallingBlock.cells[i][j] = mNextBlock.cells[i][j]; } } mFallingBlock.size = mNextBlock.size; mFallingBlock.type = mNextBlock.type; mFallingBlock.y = 0; mFallingBlock.x = BOARD_TILEMAP_WIDTH / 2 - mFallingBlock.size / 2; onTetrominoMoved(); //随机选择下一个方块 setTetromino(mPlatform->random() % TETROMINO_TYPES, &mNextBlock); } } else { //没有冲突,直接移动 mFallingBlock.x += x; mFallingBlock.y += y; } onTetrominoMoved(); }