#include "Game.h" #include "GameBoard.h" #include "GameParameters.h" #include "Action.h" #include #include #include static const int SUBPX_PER_ROW = 60; // the number of position the active piece can take "between" two rows static const int SOFT_DROP_SCORE = 1; // the score gained by line soft dropped static const int HARD_DROP_SCORE = 2; // the score gained by line hard dropped static const int LINE_CLEAR_BASE_SCORE = 100; // the score value of clearing a single line static const int B2B_SCORE_MULTIPLIER = 2; // by how much havaing B2B on multiplies the score of the line clear static const int B2B_MIN_LINE_NUMBER = 4; // the minimum number of lines needed to be cleared at once to gain B2B (without a spin) Game::Game(Gamemode gamemode, const Player& controls, int boardWidth, int boardHeight, const std::shared_ptr& piecesList) : parameters(gamemode, controls), board(boardWidth, boardHeight, piecesList, parameters.getNextQueueLength()) { this->initialize(); } void Game::start() { this->started = true; this->leftARETime = 1; } void Game::reset() { this->initialize(); this->parameters.reset(); this->board.reset(); } void Game::initialize() { this->started = false; this->lost = false; this->score = 0; this->framesPassed = 0; this->B2BChain = 0; this->heldActions.clear(); this->initialActions.clear(); this->heldDAS = 0; this->heldARR = 0; this->subVerticalPosition = 0; this->totalLockDelay = 0; this->totalForcedLockDelay = 0; } void Game::nextFrame(const std::set& playerActions) { if (this->lost || this->hasWon()) return; if (this->started) { bool AREJustEnded = (this->leftARETime == 1); if (this->leftARETime > 0) { this->leftARETime--; } if (this->leftARETime == 0) { if (AREJustEnded) { this->lost = this->board.spawnNextPiece(); this->resetPiece(true); } /* IRS and IHS */ Rotation initialRotation = NONE + ((this->initialActions.contains(ROTATE_CW)) ? CLOCKWISE : NONE) + ((this->initialActions.contains(ROTATE_180)) ? DOUBLE : NONE) + ((this->initialActions.contains(ROTATE_CCW)) ? COUNTERCLOCKWISE : NONE); if (this->initialActions.contains(HOLD)) { this->lost = (!this->board.hold(initialRotation)); } else { if ((initialRotation != NONE) || this->initialActions.contains(ROTATE_0)) { this->lost = (!this->board.rotate(initialRotation)); } } if (this->lost) { if (initialRotation == NONE) { this->lost = (!this->board.rotate(initialRotation)); } } if (this->lost) { this->framesPassed++; return; } /* HOLD */ if (playerActions.contains(HOLD) && (!this->heldActions.contains(HOLD))) { if (this->board.hold()) { this->resetPiece(false); } } /* MOVE LEFT/RIGHT */ Position before = this->board.getActivePiecePosition(); if (this->heldDAS >= 0) { if (playerActions.contains(MOVE_LEFT) && (!heldActions.contains(MOVE_LEFT))) this->movePiece(-1); else if (playerActions.contains(MOVE_RIGHT)) this->movePiece(1); else this->heldDAS = 0; } else if (this->heldDAS < 0) { if (playerActions.contains(MOVE_RIGHT) && (!heldActions.contains(MOVE_RIGHT))) this->movePiece(1); else if (playerActions.contains(MOVE_LEFT)) this->movePiece(-1); else this->heldDAS = 0; } if (before != this->board.getActivePiecePosition()) { this->totalLockDelay = 0; } /* ROTATIONS */ if (playerActions.contains(ROTATE_0) && (!this->heldActions.contains(ROTATE_0))) { this->rotatePiece(NONE); } if (playerActions.contains(ROTATE_CW) && (!this->heldActions.contains(ROTATE_CW))) { this->rotatePiece(CLOCKWISE); } if (playerActions.contains(ROTATE_180) && (!this->heldActions.contains(ROTATE_180))) { this->rotatePiece(DOUBLE); } if (playerActions.contains(ROTATE_CCW) && (!this->heldActions.contains(ROTATE_CCW))) { this->rotatePiece(COUNTERCLOCKWISE); } /* SOFT DROP */ if (playerActions.contains(SOFT_DROP)) { int appliedSDR = this->parameters.getSDR(); // SDR=0 -> instant drop if (appliedSDR == 0) { while (this->board.moveDown()) { this->score += SOFT_DROP_SCORE; } } // SDR>1 -> move down by specified amount else { this->subVerticalPosition += (SUBPX_PER_ROW / appliedSDR); while (this->subVerticalPosition >= SUBPX_PER_ROW) { this->subVerticalPosition -= SUBPX_PER_ROW; this->score += (this->board.moveDown() * SOFT_DROP_SCORE); } } } /* HARD DROP */ // needs to be done last because we can enter ARE period afterwards if (this->initialActions.contains(HARD_DROP) || (playerActions.contains(HARD_DROP) && (!this->heldActions.contains(HARD_DROP)))) { while (this->board.moveDown()) { this->score += HARD_DROP_SCORE; } this->lockPiece(); } // no need to apply gravity and lock delay if the piece was hard dropped else { /* GRAVITY */ if (this->parameters.getLevel() >= 20) { while (this->board.moveDown()); } else { // parameters.getGravity() gives the gravity for an assumed 20-line high board int appliedGravity = this->parameters.getGravity() * std::max((double) this->board.getBoard().getBaseHeight() / 20.0, 1.0); this->subVerticalPosition += appliedGravity; while (this->subVerticalPosition >= SUBPX_PER_ROW) { this->subVerticalPosition -= SUBPX_PER_ROW; this->board.moveDown(); } } /* LOCK DELAY */ if (this->board.touchesGround()) { this->totalLockDelay++; this->totalForcedLockDelay++; } else { this->totalLockDelay = 0; } if ((this->totalLockDelay > this->parameters.getLockDelay()) || (this->totalForcedLockDelay > this->parameters.getForcedLockDelay())) { this->lockPiece(); } } if (AREJustEnded) { this->initialActions.clear(); } } this->framesPassed++; if (this->lost) { return; } } this->heldActions = playerActions; if ((!this->started) || this->leftARETime > 0) { for (Action action : playerActions) { this->initialActions.insert(action); } if (this->heldDAS >= 0) { if (playerActions.contains(MOVE_LEFT)) this->heldDAS = -1; else if (playerActions.contains(MOVE_RIGHT)) this->heldDAS++; else this->heldDAS = 0; } else if (this->heldDAS < 0) { if (playerActions.contains(MOVE_RIGHT)) this->heldDAS = +1; else if (playerActions.contains(MOVE_LEFT)) this->heldDAS--; else this->heldDAS = 0; } } } void Game::resetPiece(bool newPiece) { int appliedDAS = this->parameters.getDAS(); this->subVerticalPosition = 0; this->totalLockDelay = 0; if (newPiece) { this->totalForcedLockDelay = 0; } if (abs(this->heldDAS) > appliedDAS) { this->heldDAS = (this->heldDAS > 0) ? (+appliedDAS) : (-appliedDAS); } this->heldARR = 0; } void Game::movePiece(int movement) { int appliedDAS = this->parameters.getDAS(); int appliedARR = this->parameters.getARR(); if ((this->heldDAS * movement) <= 0) { this->heldDAS = movement; this->heldARR = 0; if (movement == -1) this->board.moveLeft(); if (movement == 1) this->board.moveRight(); } else { this->heldDAS += movement; } if (abs(this->heldDAS) > appliedDAS) { // ARR=0 -> instant movement if (appliedARR == 0) { if (movement == -1) while (this->board.moveLeft()); if (movement == 1) while (this->board.moveRight()); } // ARR>1 -> move by specified amount else { if (abs(this->heldDAS) > appliedDAS + 1) { this->heldARR++; } if ((this->heldARR == appliedARR) || (abs(this->heldDAS) == (appliedDAS + 1))) { this->heldARR = 0; if (movement == -1) this->board.moveLeft(); if (movement == 1) this->board.moveRight(); } } } } void Game::rotatePiece(Rotation rotation) { Position before = this->board.getActivePiecePosition(); if (this->board.rotate(rotation)) { this->totalLockDelay = 0; if (before != this->board.getActivePiecePosition()) { this->subVerticalPosition = 0; } } } void Game::lockPiece() { LineClear clear = this->board.lockPiece(); this->parameters.clearLines(clear.lines); bool B2BConditionsAreMet = ((clear.lines > B2B_MIN_LINE_NUMBER) || clear.isSpin || clear.isMiniSpin); if (clear.lines > 0) { /* clearing one more line is worth 2x more clearing with a spin is worth as much as clearing 2x more lines */ long int clearScore = LINE_CLEAR_BASE_SCORE; clearScore = clearScore << (clear.lines << (clear.isSpin)); if (this->B2BChain && B2BConditionsAreMet) clearScore *= B2B_SCORE_MULTIPLIER; this->score += clearScore; } this->B2BChain = B2BConditionsAreMet; if (!this->hasWon()) { this->leftARETime = this->parameters.getARE(); if (this->leftARETime == 0) { this->lost = this->board.spawnNextPiece(); this->resetPiece(true); } } } bool Game::hasWon() const { return this->parameters.hasWon(this->framesPassed); } bool Game::hasLost() const { return this->lost; } int Game::getClearedLines() const { return this->parameters.getClearedLines(); } int Game::getLevel() const { return this->parameters.getLevel(); } int Game::getFramesPassed() const { return this->framesPassed; } int Game::getScore() const { return this->score; } bool Game::isOnB2BChain() const { return this->B2BChain; } bool Game::areBlocksBones() const { return this->parameters.getBoneBlocks(); } Position Game::ghostPiecePosition() const { return this->board.lowestPosition(); } const Board& Game::getBoard() const { return this->board.getBoard(); } const std::shared_ptr& Game::getActivePiece() const { return this->board.getActivePiece(); } const Position& Game::getActivePiecePosition() const { return this->board.getActivePiecePosition(); } const std::shared_ptr& Game::getHeldPiece() const { return this->board.getHeldPiece(); } const std::vector& Game::getNextPieces() const { return this->board.getNextPieces(); }