diff --git a/README.md b/README.md index eeb3e1a..f64097c 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ If you need to change the toolchain (for example using gcc): ### Run the project ``xmake run`` -Note that the program will genereate the polyomino files for you. This can be quite long so it only does it up to size 10. +Note that the program will generate the polyomino files for you. This can be quite long so it only does it up to size 10. If for some reasons you wanna run the command line version: ``xmake run text`` diff --git a/doc/game_logic.md b/doc/game_logic.md index 7c80d0f..62643fa 100644 --- a/doc/game_logic.md +++ b/doc/game_logic.md @@ -77,8 +77,9 @@ Since this game uses polyomino of high sizes which are very unplayable, we will 5. Do the same as step 4 but now we move the piece one line up every time 6. Cancel the rotation -Kicking is primarly designed for rotating, but it is also applied when a piece spawns into a wall. -0° rotations will first try to move the piece one position down, and if it can't try kicking the piece, only registering a kick if the piece couldn't move down. +_Note: at step 3, the direction which is checked first is actually the last movement done by the player._ +Kicking is primarly designed for rotating, but there is also a 0° rotation applied when a piece spawns into a wall. +0° rotations will first try to move the piece one position down, and if it can't, try kicking the piece, only registering a kick if the piece couldn't move down. ## Detecting spins diff --git a/src/Core/Game.cpp b/src/Core/Game.cpp index d9c2e01..4c7993c 100644 --- a/src/Core/Game.cpp +++ b/src/Core/Game.cpp @@ -55,6 +55,8 @@ void Game::initialize() { void Game::nextFrame(const std::set& playerActions) { if (this->lost || this->hasWon()) return; + bool pieceJustLocked = false; + if (this->started) { bool AREJustEnded = (this->leftARETime == 1); if (this->leftARETime > 0) { @@ -84,13 +86,14 @@ void Game::nextFrame(const std::set& playerActions) { if (this->lost) { if (initialRotation == NONE) { - this->lost = (!this->board.rotate(initialRotation)); + this->board.rotate(NONE); + this->lost = this->board.activePieceInWall(); + if (this->lost) { + this->framesPassed++; + return; + } } } - if (this->lost) { - this->framesPassed++; - return; - } /* HOLD */ if (playerActions.contains(HOLD) && (!this->heldActions.contains(HOLD))) { @@ -158,6 +161,7 @@ void Game::nextFrame(const std::set& playerActions) { this->score += HARD_DROP_SCORE; } this->lockPiece(); + pieceJustLocked = true; } // no need to apply gravity and lock delay if the piece was hard dropped else { @@ -187,6 +191,7 @@ void Game::nextFrame(const std::set& playerActions) { if ((this->totalLockDelay > this->parameters.getLockDelay()) || (this->totalForcedLockDelay > this->parameters.getForcedLockDelay())) { this->lockPiece(); + pieceJustLocked = true; } } @@ -201,11 +206,11 @@ void Game::nextFrame(const std::set& playerActions) { } } - this->heldActions = playerActions; - if ((!this->started) || this->leftARETime > 0) { for (Action action : playerActions) { - this->initialActions.insert(action); + if ((!pieceJustLocked) && (!heldActions.contains(action))) { + this->initialActions.insert(action); + } } if (this->heldDAS >= 0) { @@ -219,6 +224,8 @@ void Game::nextFrame(const std::set& playerActions) { else this->heldDAS = 0; } } + + this->heldActions = playerActions; } void Game::resetPiece(bool newPiece) { @@ -302,6 +309,11 @@ void Game::lockPiece() { if (this->leftARETime == 0) { this->lost = this->board.spawnNextPiece(); this->resetPiece(true); + + if (this->lost) { + this->board.rotate(NONE); + this->lost = this->board.activePieceInWall(); + } } } } diff --git a/src/Core/GameBoard.cpp b/src/Core/GameBoard.cpp index bcbd490..d449ff5 100644 --- a/src/Core/GameBoard.cpp +++ b/src/Core/GameBoard.cpp @@ -36,9 +36,12 @@ void GameBoard::initialize() { this->activePiece = nullptr; this->heldPiece = nullptr; this->isLastMoveKick = false; + this->movedLeftLast = false; } bool GameBoard::moveLeft() { + this->movedLeftLast = true; + if (this->activePieceInWall(Position{-1, 0})) { return false; } @@ -50,6 +53,8 @@ bool GameBoard::moveLeft() { } bool GameBoard::moveRight() { + this->movedLeftLast = false; + if (this->activePieceInWall(Position{1, 0})) { return false; } @@ -137,30 +142,21 @@ bool GameBoard::tryKicking(bool testingBottom, const std::set& safePos bool overlapsRight = true; int i = (j == 0) ? 1 : 0; do { - // check right before left arbitrarly, we don't decide this with rotations since it would still be arbitrary with 180° rotations - if (overlapsRight) { - Position shift{+i, j}; - if (!this->activePieceOverlaps(safePositions, shift)) { - overlapsLeft = false; + // we first check the side to which the player moved last + if (movedLeftLast) { + if (overlapsLeft) { + if (this->tryFittingKickedPiece(safePositions, Position({-i, j}), overlapsLeft)) return true; } - else { - if (!this->activePieceInWall(shift)) { - this->activePiecePosition += shift; - return true; - } + if (overlapsRight) { + if (this->tryFittingKickedPiece(safePositions, Position({+i, j}), overlapsRight)) return true; } } - - if (overlapsLeft) { - Position shift{-i, j}; - if (!this->activePieceOverlaps(safePositions, shift)) { - overlapsLeft = false; + else { + if (overlapsRight) { + if (this->tryFittingKickedPiece(safePositions, Position({+i, j}), overlapsRight)) return true; } - else { - if (!this->activePieceInWall(shift)) { - this->activePiecePosition += shift; - return true; - } + if (overlapsLeft) { + if (this->tryFittingKickedPiece(safePositions, Position({-i, j}), overlapsLeft)) return true; } } @@ -177,6 +173,26 @@ bool GameBoard::tryKicking(bool testingBottom, const std::set& safePos return false; } +bool GameBoard::tryFittingKickedPiece(const std::set& safePositions, const Position& shift, bool& overlaps) { + if (!this->activePieceOverlaps(safePositions, shift)) { + overlaps = false; + } + else { + if (!this->activePieceInWall(shift)) { + this->activePiecePosition += shift; + return true; + } + } + return false; +} + +bool GameBoard::activePieceOverlaps(const std::set& safePositions, const Position& shift) const { + for (Position position : this->activePiece->getPositions()) { + if (safePositions.contains(position + this->activePiecePosition + shift)) return true; + } + return false; +} + bool GameBoard::hold(Rotation initialRotation) { std::swap(this->activePiece, this->heldPiece); @@ -236,6 +252,13 @@ bool GameBoard::spawnNextPiece() { return this->activePieceInWall(); } +bool GameBoard::activePieceInWall(const Position& shift) const { + for (Position position : this->activePiece->getPositions()) { + if (this->board.getBlock(position + this->activePiecePosition + shift) != NOTHING) return true; + } + return false; +} + bool GameBoard::touchesGround() const { return this->activePieceInWall(Position{0, -1}); } @@ -257,6 +280,8 @@ LineClear GameBoard::lockPiece() { this->board.changeBlock(position + this->activePiecePosition, this->activePiece->getBlockType()); } + this->activePiece = nullptr; + return LineClear{this->board.clearRows(), isLockedInPlace, (!isLockedInPlace) && this->isLastMoveKick}; } @@ -291,20 +316,6 @@ const std::vector& GameBoard::getNextPieces() const { return this->nextQueue; } -bool GameBoard::activePieceInWall(const Position& shift) const { - for (Position position : this->activePiece->getPositions()) { - if (this->board.getBlock(position + this->activePiecePosition + shift) != NOTHING) return true; - } - return false; -} - -bool GameBoard::activePieceOverlaps(const std::set& safePositions, const Position& shift) const { - for (Position position : this->activePiece->getPositions()) { - if (safePositions.contains(position + this->activePiecePosition + shift)) return true; - } - return false; -} - void GameBoard::goToSpawnPosition() { int lowestPosition = this->activePiece->getLength() - 1; for (Position position : this->activePiece->getPositions()) { diff --git a/src/Core/GameBoard.h b/src/Core/GameBoard.h index b55097f..97b1b41 100644 --- a/src/Core/GameBoard.h +++ b/src/Core/GameBoard.h @@ -22,6 +22,7 @@ class GameBoard { int nextQueueLength; // the number of next pieces seeable at a time std::vector nextQueue; // the list of the next pieces to spawn in the board bool isLastMoveKick; // wheter the last action the piece did was kicking + bool movedLeftLast; // wheter the last sideway movement was a left one public: /** @@ -72,6 +73,18 @@ class GameBoard { */ bool tryKicking(bool testingBottom, const std::set& safePositions); + /** + * Tries fitting the kicked active piece at the specified position + * @return If it suceeded + */ + bool tryFittingKickedPiece(const std::set& safePositions, const Position& shift, bool& overlaps); + + /** + * Check if one of the active piece's positions shifted by a specified position would overlap with a set of positions + * @return If the shifted active piece overlaps with one of the position + */ + bool activePieceOverlaps(const std::set& safePositions, const Position& shift = Position{0, 0}) const; + public: /** * Tries holding the active piece or swapping it if one was already stocked, while trying to apply an initial rotation to the newly spawned piece @@ -85,6 +98,12 @@ class GameBoard { */ bool spawnNextPiece(); + /** + * Checks if one of the active piece's positions touches a wall in the board + * @return If the active piece is in a wall + */ + bool activePieceInWall(const Position& shift = Position{0, 0}) const; + /** * Checks is the active piece as a wall directly below one of its position * @return If it touches a ground @@ -134,18 +153,6 @@ class GameBoard { const std::vector& getNextPieces() const; private: - /** - * Checks if one of the active piece's positions touches a wall in the board - * @return If the active piece spawned in a wall - */ - bool activePieceInWall(const Position& shift = Position{0, 0}) const; - - /** - * Check if one of the active piece's positions shifted by a specified position would overlap with a set of positions - * @return If the shifted active piece overlaps with one of the position - */ - bool activePieceOverlaps(const std::set& safePositions, const Position& shift = Position{0, 0}) const; - /** * Sets the active piece to its spawn position */ diff --git a/src/Core/GameParameters.cpp b/src/Core/GameParameters.cpp index bbf8206..625c206 100644 --- a/src/Core/GameParameters.cpp +++ b/src/Core/GameParameters.cpp @@ -38,7 +38,7 @@ void GameParameters::clearLines(int lineNumber) { // level increments every 10 lines, stats only changes on level up if (previousLines / 10 < this->clearedLines / 10) { - this->level = this->clearedLines / 10; + this->level += (this->clearedLines / 10 - previousLines / 10); this->updateStats(); } break; diff --git a/src/GraphicalUI/AppMenus/GameSettingsAppMenu.cpp b/src/GraphicalUI/AppMenus/GameSettingsAppMenu.cpp index a16d878..9133a5e 100644 --- a/src/GraphicalUI/AppMenus/GameSettingsAppMenu.cpp +++ b/src/GraphicalUI/AppMenus/GameSettingsAppMenu.cpp @@ -74,7 +74,7 @@ void GameSettingsAppMenu::drawFrame() const { this->placeText(text, "ULTRA", 50.f, 25.f, 2, 1); this->placeText(text, "MASTER", 5.f, 35.f, 0, 2); this->placeText(text, "TODO", 25.f, 35.f, 1, 2); - this->placeText(text, "TOOD", 50.f, 35.f, 2, 2); + this->placeText(text, "TODO", 50.f, 35.f, 2, 2); this->renderWindow->display(); }