Files
jminos/src/Core/Game.cpp

392 lines
13 KiB
C++

#include "Game.h"
#include "GameBoard.h"
#include "GameParameters.h"
#include "Action.h"
#include <set>
#include <algorithm>
#include <memory>
static const int SUBPX_PER_ROW = 60; // the number of position the active piece can take "between" two rows
static const int LOCK_DELAY_SCORE = 1; // the score gained when the piece locks due to lock delay
static const int HARD_DROP_SCORE = 10; // the score gained when the piece locks due to an hard drop
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>& 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<Action>& playerActions) {
if (this->lost || this->hasWon()) return;
bool pieceJustLocked = false;
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 */
bool initialRotated = (this->initialActions.contains(ROTATE_0) || this->initialActions.contains(ROTATE_CW)
|| this->initialActions.contains(ROTATE_180) || this->initialActions.contains(ROTATE_CCW));
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->board.hold(initialRotation);
}
else {
if (initialRotated) {
this->board.rotate(initialRotation);
}
}
this->lost = this->board.activePieceInWall();
if (this->lost) {
this->board.rotate(NONE);
this->lost = this->board.activePieceInWall();
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());
}
// SDR>1 -> move down by specified amount
else {
this->subVerticalPosition += (SUBPX_PER_ROW / appliedSDR);
while (this->subVerticalPosition >= SUBPX_PER_ROW) {
this->board.moveDown();
this->subVerticalPosition -= SUBPX_PER_ROW;
}
}
}
/* 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->lockPiece();
pieceJustLocked = true;
this->score += HARD_DROP_SCORE;
}
// 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();
pieceJustLocked = true;
this->score += LOCK_DELAY_SCORE;
}
}
if (AREJustEnded) {
this->initialActions.clear();
}
}
this->framesPassed++;
if (this->lost) {
return;
}
}
if ((!this->started) || this->leftARETime > 0) {
for (Action action : playerActions) {
if ((!pieceJustLocked) && (!heldActions.contains(action))) {
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;
}
}
this->heldActions = playerActions;
}
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.lockedPiece(clear);
if (clear.lines > 0) {
bool B2BConditionsAreMet = ((clear.lines >= B2B_MIN_LINE_NUMBER) || clear.isSpin || clear.isMiniSpin);
/* 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);
if (this->lost) {
this->board.rotate(NONE);
this->lost = this->board.activePieceInWall();
}
}
}
}
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::getGrade() const {
return this->parameters.getGrade();
}
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;
}
float Game::getLockDelayProgression() const {
return (float) this->totalLockDelay / this->parameters.getLockDelay();
}
float Game::getForcedLockDelayProgression() const {
return (float) this->totalForcedLockDelay / this->parameters.getForcedLockDelay();
}
bool Game::areBlocksBones() const {
return this->parameters.getBoneBlocks();
}
const Board& Game::getBoard() const {
return this->board.getBoard();
}
const std::shared_ptr<Piece>& Game::getActivePiece() const {
return this->board.getActivePiece();
}
const Position& Game::getActivePiecePosition() const {
return this->board.getActivePiecePosition();
}
Position Game::getGhostPiecePosition() const {
return this->board.lowestPosition();
}
const std::shared_ptr<Piece>& Game::getHeldPiece() const {
return this->board.getHeldPiece();
}
const std::vector<Piece>& Game::getNextPieces() const {
return this->board.getNextPieces();
}