stats in-game
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
#include <memory>
|
||||
|
||||
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<Action>& 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<Action>& 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<Action>& 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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -64,6 +66,11 @@ class GameParameters {
|
||||
*/
|
||||
int getLevel() const;
|
||||
|
||||
/**
|
||||
* @return The current grade
|
||||
*/
|
||||
int getGrade() const;
|
||||
|
||||
/**
|
||||
* @return The length of the next queue
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
|
||||
/**
|
||||
* 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];
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -6,14 +6,17 @@
|
||||
#include <memory>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
static const int TIME_BEFORE_STARTING = 60;
|
||||
|
||||
|
||||
GamePlayingAppMenu::GamePlayingAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> 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,13 +27,13 @@ GamePlayingAppMenu::GamePlayingAppMenu(std::shared_ptr<MenuStack> menuStack, std
|
||||
|
||||
float boardWidth = this->game.getBoard().getWidth() * this->cellSizeZoom;
|
||||
float boardHeight = (this->game.getBoard().getBaseHeight() + 10) * this->cellSizeZoom;
|
||||
this->boardPosition = sf::Rect<float>(sf::Vector2f((this->settings->getWindowSizeMultiplier() * 40) - (boardWidth / 2),
|
||||
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<float>(sf::Vector2f(this->boardPosition.position.x + boardWidth + (5.f * this->settings->getWindowSizeMultiplier()), (1.f + (10.f * i)) * 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()));
|
||||
}
|
||||
|
||||
@@ -40,7 +43,7 @@ GamePlayingAppMenu::GamePlayingAppMenu(std::shared_ptr<MenuStack> menuStack, std
|
||||
this->nextCellSizeZoom = std::min(this->nextCellSizeZoom, nextPieceCellSizeZoom);
|
||||
}
|
||||
|
||||
this->holdBoxPosition = sf::Rect<float>(sf::Vector2f(this->boardPosition.position.x - ((8.f + 5.f) * this->settings->getWindowSizeMultiplier()), (11.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;
|
||||
}
|
||||
@@ -48,6 +51,13 @@ GamePlayingAppMenu::GamePlayingAppMenu(std::shared_ptr<MenuStack> menuStack, std
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
@@ -10,14 +10,15 @@
|
||||
class GamePlayingAppMenu : public AppMenu {
|
||||
private:
|
||||
Game game;
|
||||
int startTimer;
|
||||
bool paused;
|
||||
bool pausePressed;
|
||||
bool retryPressed;
|
||||
sf::Rect<float> boardPosition;
|
||||
sf::FloatRect boardPosition;
|
||||
float cellSizeZoom;
|
||||
sf::Rect<float> holdBoxPosition;
|
||||
sf::FloatRect holdBoxPosition;
|
||||
float holdCellSizeZoom;
|
||||
sf::Rect<float> nextQueuePosition[5];
|
||||
sf::FloatRect nextQueuePosition[5];
|
||||
float nextCellSizeZoom;
|
||||
|
||||
public:
|
||||
|
||||
@@ -33,7 +33,7 @@ InfoAppMenu::InfoAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<S
|
||||
"The rotation center is always the\n"
|
||||
"center of the piece by default.\n"
|
||||
"When kicking the piece, it will\n"
|
||||
"compute and try every position that\n"
|
||||
"compute and try (most) position that\n"
|
||||
"touches the original piece,\n"
|
||||
"prioritizing sides over depth and\n"
|
||||
"firstly going down before going up.",
|
||||
|
||||
@@ -112,6 +112,7 @@ void SettingsKeybindsAppMenu::drawFrame() const {
|
||||
}
|
||||
|
||||
this->placeText(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]);
|
||||
|
||||
Reference in New Issue
Block a user