diff --git a/doc/game_logic.md b/doc/game_logic.md index 62643fa..b5cb2cd 100644 --- a/doc/game_logic.md +++ b/doc/game_logic.md @@ -92,8 +92,17 @@ Since we work with a great deal of different size and shapes, the rules for spin ## Score calculation -- For every position soft dropped, add 1 to the score -- For every position hard dropped, add 2 to the score +- When locking a piece, add 1 to the score if it is due to lock delay, or 10 if it was hard dropped. - When clearing one line, add 100 to the score, 200 for 2 lines, 400 for 3 lines, 800 for 4 lines, 1600 for 5 lines, etc. - If the line clear is a spin, count the score like a normal clear of 2x more line (200 for 1-line spin, 800 for 2, 3200 for 3, etc.) - When performing a spin, a mini spin, or clearing 4 or more lines, B2B is activated, every subsequent line clear that is a spin, a mini spin, or clear 4 or more lines, scores twice as much + +## Grade calculation + +Grade is an alternate system to line clears. + +- Each time a piece is dropped, 1 point is added to the total. +- For each cleared line, 1 point is added to the total. + +The only exception occurs when the total ends in '99', a line must be cleared to progress. +When this line is cleared, the point of the piece is also counted towards the total. diff --git a/src/Core/Game.cpp b/src/Core/Game.cpp index 3131ee9..c303c9e 100644 --- a/src/Core/Game.cpp +++ b/src/Core/Game.cpp @@ -9,8 +9,8 @@ #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 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) @@ -140,16 +140,14 @@ void Game::nextFrame(const std::set& playerActions) { // SDR=0 -> instant drop if (appliedSDR == 0) { - while (this->board.moveDown()) { - this->score += SOFT_DROP_SCORE; - } + 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; - this->score += (this->board.moveDown() * SOFT_DROP_SCORE); } } } @@ -157,11 +155,10 @@ void Game::nextFrame(const std::set& playerActions) { /* 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; - } + 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 { @@ -192,6 +189,7 @@ void Game::nextFrame(const std::set& playerActions) { if ((this->totalLockDelay > this->parameters.getLockDelay()) || (this->totalForcedLockDelay > this->parameters.getForcedLockDelay())) { this->lockPiece(); pieceJustLocked = true; + this->score += LOCK_DELAY_SCORE; } } @@ -290,7 +288,7 @@ void Game::rotatePiece(Rotation rotation) { void Game::lockPiece() { LineClear clear = this->board.lockPiece(); - this->parameters.clearLines(clear.lines); + this->parameters.lockedPiece(clear); bool B2BConditionsAreMet = ((clear.lines > B2B_MIN_LINE_NUMBER) || clear.isSpin || clear.isMiniSpin); if (clear.lines > 0) { @@ -330,6 +328,10 @@ int Game::getClearedLines() const { return this->parameters.getClearedLines(); } +int Game::getGrade() const { + return this->parameters.getGrade(); +} + int Game::getLevel() const { return this->parameters.getLevel(); } diff --git a/src/Core/Game.h b/src/Core/Game.h index f07f5d8..71473ef 100644 --- a/src/Core/Game.h +++ b/src/Core/Game.h @@ -101,6 +101,11 @@ class Game { */ int getClearedLines() const; + /** + * @return The current grade + */ + int getGrade() const; + /** * @return The number of frames passed since the start of the game */ diff --git a/src/Core/GameParameters.cpp b/src/Core/GameParameters.cpp index 87621a1..cb22008 100644 --- a/src/Core/GameParameters.cpp +++ b/src/Core/GameParameters.cpp @@ -13,6 +13,8 @@ GameParameters::GameParameters(Gamemode gamemode, const Player& controls) : void GameParameters::reset() { this->clearedLines = 0; + this->grade = 0; + switch (this->gamemode) { // lowest gravity case SPRINT : {this->level = 1; break;} @@ -30,13 +32,13 @@ void GameParameters::reset() { this->updateStats(); } -void GameParameters::clearLines(int lineNumber) { +void GameParameters::lockedPiece(const LineClear& lineClear) { switch (this->gamemode) { // modes where level increases case MARATHON : case MASTER : { int previousLines = this->clearedLines; - this->clearedLines += lineNumber; + this->clearedLines += lineClear.lines; // level increments every 10 lines, stats only changes on level up if (previousLines / 10 < this->clearedLines / 10) { @@ -46,7 +48,11 @@ void GameParameters::clearLines(int lineNumber) { break; } // other modes - default : this->clearedLines += lineNumber; + default : this->clearedLines += lineClear.lines; + } + + if (!((lineClear.lines == 0) && ((this->grade % 100) == 99))) { + this->grade += (1 + lineClear.lines); } } @@ -210,6 +216,10 @@ int GameParameters::getLevel() const { return this->level; } +int GameParameters::getGrade() const { + return this->grade; +} + int GameParameters::getNextQueueLength() const { return this->nextQueueLength; } diff --git a/src/Core/GameParameters.h b/src/Core/GameParameters.h index ed6b0f6..7751765 100644 --- a/src/Core/GameParameters.h +++ b/src/Core/GameParameters.h @@ -2,6 +2,7 @@ #include "Gamemode.h" #include "Player.h" +#include "LineClear.h" /** @@ -14,6 +15,7 @@ class GameParameters { Player controls; // the player's controls int clearedLines; // the number of cleared lines int level; // the current level + int grade; // the current amount of points int nextQueueLength; // the number of pieces visibles in the next queue bool boneBlocks; // wheter all blocks are bone blocks int gravity; // the gravity at which pieces drop @@ -39,7 +41,7 @@ class GameParameters { /** * Counts the newly cleared lines and update level and stats if needed */ - void clearLines(int lineNumber); + void lockedPiece(const LineClear& lineClear); /** * Checks if the game ended based on the current states and time passed, accorind to the gamemode @@ -63,6 +65,11 @@ class GameParameters { * @return The current level */ int getLevel() const; + + /** + * @return The current grade + */ + int getGrade() const; /** * @return The length of the next queue diff --git a/src/Core/Gamemode.h b/src/Core/Gamemode.h index 15f7531..62e60be 100644 --- a/src/Core/Gamemode.h +++ b/src/Core/Gamemode.h @@ -1,5 +1,7 @@ #pragma once +#include + /** * Every gamemode supported by the game @@ -11,3 +13,34 @@ enum Gamemode { MASTER, ZEN }; + + +/** + * @return A string containing the name of the gamemode + */ +inline std::string getGamemodeName(Gamemode gamemode) { + static const std::string GAMEMODE_NAMES[] = { + "SPRINT", + "MARATHON", + "ULTRA", + "MASTER", + "ZEN" + }; + + return GAMEMODE_NAMES[gamemode]; +} + +/** + * @return A tiny string containing the goal of the gamemode + */ +inline std::string getGamemodeGoal(Gamemode gamemode) { + static const std::string GAMEMODE_DESCRIPTIONS[] = { + "40 lines", + "200 lines", + "2 minutes", + "200 lines", + "Chill" + }; + + return GAMEMODE_DESCRIPTIONS[gamemode]; +} diff --git a/src/GraphicalUI/AppMenus/AppMenu.h b/src/GraphicalUI/AppMenus/AppMenu.h index 9a527c6..f10c27c 100644 --- a/src/GraphicalUI/AppMenus/AppMenu.h +++ b/src/GraphicalUI/AppMenus/AppMenu.h @@ -64,9 +64,6 @@ class AppMenu { if (playerCursor.has_value() && cursorPos.has_value()) { text.setOutlineThickness((playerCursor.value().getPosition() == cursorPos.value()) ? (sizeMultiplier / 2) : 0); } - else { - text.setOutlineThickness(0); - } text.setOrigin(sf::Vector2f({0, text.getLocalBounds().size.y / 2})); text.setPosition(sf::Vector2f({sizeMultiplier * xPos, sizeMultiplier * yPos})); this->renderWindow->draw(text); @@ -79,9 +76,6 @@ class AppMenu { if (playerCursor.has_value() && cursorPos.has_value()) { text.setOutlineThickness((playerCursor.value().getPosition() == cursorPos.value()) ? (sizeMultiplier / 2) : 0); } - else { - text.setOutlineThickness(0); - } text.setOrigin({text.getLocalBounds().getCenter().x, text.getLocalBounds().size.y / 2}); text.setPosition(sf::Vector2f({sizeMultiplier * 40.f, sizeMultiplier * yPos})); this->renderWindow->draw(text); diff --git a/src/GraphicalUI/AppMenus/GamePlayingAppMenu.cpp b/src/GraphicalUI/AppMenus/GamePlayingAppMenu.cpp index 5f0e963..e3b71d9 100644 --- a/src/GraphicalUI/AppMenus/GamePlayingAppMenu.cpp +++ b/src/GraphicalUI/AppMenus/GamePlayingAppMenu.cpp @@ -6,14 +6,17 @@ #include #include #include +#include #include +static const int TIME_BEFORE_STARTING = 60; + GamePlayingAppMenu::GamePlayingAppMenu(std::shared_ptr menuStack, std::shared_ptr settings, std::shared_ptr renderWindow) : AppMenu(menuStack, settings, renderWindow), game(this->settings->getMenu().startGame(this->settings->getGamemode())) { - this->game.start(); + this->startTimer = TIME_BEFORE_STARTING; this->paused = false; this->pausePressed = false; this->retryPressed = false; @@ -24,14 +27,14 @@ GamePlayingAppMenu::GamePlayingAppMenu(std::shared_ptr menuStack, std float boardWidth = this->game.getBoard().getWidth() * this->cellSizeZoom; float boardHeight = (this->game.getBoard().getBaseHeight() + 10) * this->cellSizeZoom; - this->boardPosition = sf::Rect(sf::Vector2f((this->settings->getWindowSizeMultiplier() * 40) - (boardWidth / 2), - (this->settings->getWindowSizeMultiplier() * 25) - (boardHeight / 2)), - sf::Vector2f(boardWidth, boardHeight)); + this->boardPosition = sf::FloatRect(sf::Vector2f((this->settings->getWindowSizeMultiplier() * 40) - (boardWidth / 2), + (this->settings->getWindowSizeMultiplier() * 25) - (boardHeight / 2)), + sf::Vector2f(boardWidth, boardHeight)); for (int i = 0; i < 5; i++) { - this->nextQueuePosition[i] = sf::Rect(sf::Vector2f(this->boardPosition.position.x + boardWidth + (5.f * this->settings->getWindowSizeMultiplier()), (1.f + (10.f * i)) * this->settings->getWindowSizeMultiplier()), - sf::Vector2f(8.f * this->settings->getWindowSizeMultiplier(), 8.f * this->settings->getWindowSizeMultiplier())); + this->nextQueuePosition[i] = sf::FloatRect(sf::Vector2f(this->boardPosition.position.x + boardWidth + (5.f * this->settings->getWindowSizeMultiplier()), (10.f + (10.f * i)) * this->settings->getWindowSizeMultiplier()), + sf::Vector2f(8.f * this->settings->getWindowSizeMultiplier(), 8.f * this->settings->getWindowSizeMultiplier())); } this->nextCellSizeZoom = this->nextQueuePosition[0].size.y; @@ -40,14 +43,21 @@ GamePlayingAppMenu::GamePlayingAppMenu(std::shared_ptr menuStack, std this->nextCellSizeZoom = std::min(this->nextCellSizeZoom, nextPieceCellSizeZoom); } - this->holdBoxPosition = sf::Rect(sf::Vector2f(this->boardPosition.position.x - ((8.f + 5.f) * this->settings->getWindowSizeMultiplier()), (11.f) * this->settings->getWindowSizeMultiplier()), - sf::Vector2f(8.f * this->settings->getWindowSizeMultiplier(), 8.f * this->settings->getWindowSizeMultiplier())); + this->holdBoxPosition = sf::FloatRect(sf::Vector2f(this->boardPosition.position.x - ((8.f + 5.f) * this->settings->getWindowSizeMultiplier()), (10.f) * this->settings->getWindowSizeMultiplier()), + sf::Vector2f(8.f * this->settings->getWindowSizeMultiplier(), 8.f * this->settings->getWindowSizeMultiplier())); this->holdCellSizeZoom = this->nextCellSizeZoom; } void GamePlayingAppMenu::computeFrame() { this->updateMetaBinds(); + if (this->startTimer > 0) { + this->startTimer--; + if (this->startTimer == 0) { + this->game.start(); + } + } + if (this->escReleased) { this->menuStack->pop(); } @@ -67,7 +77,7 @@ void GamePlayingAppMenu::computeFrame() { else { if (this->retryPressed) { this->game.reset(); - this->game.start(); + this->startTimer = TIME_BEFORE_STARTING; } this->retryPressed = false; } @@ -153,32 +163,40 @@ void GamePlayingAppMenu::drawFrame() const { } // next queue + int upShift = 0; for (int i = 0; i < std::min((int) this->game.getNextPieces().size(), 5); i++) { + sf::FloatRect nextBox = this->nextQueuePosition[i]; + nextBox.position.y -= upShift; + sf::Vector2f nextCellSize(this->nextCellSizeZoom, this->nextCellSizeZoom); sf::Color color = this->getColorOfBlock(this->game.getNextPieces().at(i).getBlockType(), 0); - sf::Color boxColor = sf::Color(150, 150, 150); + sf::Color boxColor = sf::Color(180, 180, 180); + int lowestRank = 0; for (int y = 0; y < this->game.getNextPieces().at(i).getLength(); y++) { for (int x = 0; x < this->game.getNextPieces().at(i).getLength(); x++) { sf::RectangleShape cell(nextCellSize); if (this->game.getNextPieces().at(i).getPositions().contains(Position{x, y})) { cell.setFillColor(color); + lowestRank = y; } else { cell.setFillColor(boxColor); } - cell.setPosition(sf::Vector2f(this->nextQueuePosition[i].position.x + (x * this->nextCellSizeZoom), - this->nextQueuePosition[i].position.y + ((this->game.getNextPieces().at(i).getLength() - y - 1) * this->nextCellSizeZoom))); + cell.setPosition(sf::Vector2f(nextBox.position.x + (x * this->nextCellSizeZoom), + nextBox.position.y + ((this->game.getNextPieces().at(i).getLength() - y - 1) * this->nextCellSizeZoom))); this->renderWindow->draw(cell); } } + + upShift += nextBox.size.y - (this->game.getNextPieces().at(i).getLength() * this->nextCellSizeZoom); } // hold box if (this->game.getHeldPiece() != nullptr) { sf::Vector2f holdCellSize(this->holdCellSizeZoom, this->holdCellSizeZoom); sf::Color color = this->getColorOfBlock(this->game.getHeldPiece()->getBlockType(), 0); - sf::Color boxColor = sf::Color(150, 150, 150); + sf::Color boxColor = sf::Color(180, 180, 180); for (int y = 0; y < this->game.getHeldPiece()->getLength(); y++) { for (int x = 0; x < this->game.getHeldPiece()->getLength(); x++) { @@ -196,6 +214,51 @@ void GamePlayingAppMenu::drawFrame() const { } } + // stats + int windowSizeMultiplier = this->settings->getWindowSizeMultiplier(); + int fontSize = (this->boardPosition.size.x > (windowSizeMultiplier * 30.f)) ? (windowSizeMultiplier) : (windowSizeMultiplier * 2); + sf::Text text(this->pressStartFont, "", fontSize); + text.setFillColor(sf::Color(0, 0, 0)); + + int millisecondes = this->game.getFramesPassed() * (1000.f / FRAMES_PER_SECOND); + std::string showedMillisecondes = std::to_string(millisecondes % 1000); + while (showedMillisecondes.size() < 3) { + showedMillisecondes = "0" + showedMillisecondes; + } + std::string showedSecondes = std::to_string((millisecondes / 1000) % 60); + while (showedSecondes.size() < 2) { + showedSecondes = "0" + showedSecondes; + } + std::string showedMinutes = std::to_string((millisecondes / (60 * 1000))); + std::string showedTime = showedMinutes + ":" + showedSecondes + "." + showedMillisecondes; + + this->placeText(text, {}, getGamemodeName(this->settings->getGamemode()), 1.f, 3.f, {}); + this->placeText(text, {}, getGamemodeGoal(this->settings->getGamemode()), 1.f, 6.f, {}); + + this->placeText(text, {}, "LINES:" + std::to_string(this->game.getClearedLines()), 1.f, 25.f, {}); + this->placeText(text, {}, "LEVEL:" + std::to_string(this->game.getLevel()), 1.f, 30.f, {}); + this->placeText(text, {}, "SCORE:" + std::to_string(this->game.getScore()), 1.f, 35.f, {}); + this->placeText(text, {}, "GRADE:" + std::to_string(this->game.getGrade()), 1.f, 40.f, {}); + this->placeText(text, {}, showedTime, 1.f, 45.f, {}); + + // game state + text.setOutlineColor(sf::Color(255, 255, 255)); + text.setOutlineThickness(windowSizeMultiplier / 2.f); + + if (this->game.hasWon()) { + this->placeTitle(text, {}, "WIN", 25.f, {}); + } + else if (this->game.hasLost()) { + this->placeTitle(text, {}, "LOSE", 25.f, {}); + } + else if (this->paused) { + this->placeTitle(text, {}, "PAUSE", 25.f, {}); + } + else if (this->startTimer > 0) { + text.setCharacterSize(windowSizeMultiplier * 4); + this->placeTitle(text, {}, std::to_string(((this->startTimer - 1) / (TIME_BEFORE_STARTING / 4))), 25.f, {}); + } + this->renderWindow->display(); } diff --git a/src/GraphicalUI/AppMenus/GamePlayingAppMenu.h b/src/GraphicalUI/AppMenus/GamePlayingAppMenu.h index bbcaeca..c233115 100644 --- a/src/GraphicalUI/AppMenus/GamePlayingAppMenu.h +++ b/src/GraphicalUI/AppMenus/GamePlayingAppMenu.h @@ -10,14 +10,15 @@ class GamePlayingAppMenu : public AppMenu { private: Game game; + int startTimer; bool paused; bool pausePressed; bool retryPressed; - sf::Rect boardPosition; + sf::FloatRect boardPosition; float cellSizeZoom; - sf::Rect holdBoxPosition; + sf::FloatRect holdBoxPosition; float holdCellSizeZoom; - sf::Rect nextQueuePosition[5]; + sf::FloatRect nextQueuePosition[5]; float nextCellSizeZoom; public: diff --git a/src/GraphicalUI/AppMenus/InfoAppMenu.cpp b/src/GraphicalUI/AppMenus/InfoAppMenu.cpp index 4dcfd94..e5bb28e 100644 --- a/src/GraphicalUI/AppMenus/InfoAppMenu.cpp +++ b/src/GraphicalUI/AppMenus/InfoAppMenu.cpp @@ -33,7 +33,7 @@ InfoAppMenu::InfoAppMenu(std::shared_ptr menuStack, std::shared_ptrplaceText(text, this->playerCursor, setStringToUpperCase(ACTION_NAMES[action]), 15.f, ((i - firstElem) * 10) + 25.f, sf::Vector2u{0, (unsigned int) i + 1}); + text.setOutlineThickness(0); this->placeText(text, {}, string, 40.f, ((i - firstElem) * 10) + 25.f, {}); sf::Sprite sprite(this->iconTextures[action]);