From 57620c70a2b92ea81b38dbe0f982f38dbc831f11 Mon Sep 17 00:00:00 2001 From: zulianc Date: Sat, 29 Mar 2025 18:48:37 +0100 Subject: [PATCH] added distribution modes --- doc/game_logic.md | 14 +++ src/Core/Bag.cpp | 87 +++++++++++++++---- src/Core/Bag.h | 20 ++++- src/Core/PiecesList.cpp | 41 +++++++++ src/Core/PiecesList.h | 41 +++++++++ .../AppMenus/GamePlayingAppMenu.cpp | 2 +- src/GraphicalUI/Settings.cpp | 58 +++++++++++-- src/GraphicalUI/Settings.h | 9 ++ src/GraphicalUI/main.cpp | 2 +- 9 files changed, 246 insertions(+), 28 deletions(-) diff --git a/doc/game_logic.md b/doc/game_logic.md index b5cb2cd..47d3ba9 100644 --- a/doc/game_logic.md +++ b/doc/game_logic.md @@ -106,3 +106,17 @@ Grade is an alternate system to line clears. 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. + +## Pieces distribution + +A bag is an object which contains a set of pieces. +The principle of a bag generator is to put every available pieces into a bag and take them out one by one until the bag is empty, to then start with a new full bag. +It's a way to have equal distribution of pieces while still allowing for a random order of pieces. + +The game has 3 modes of pieces distribution: + +1. Default. The simplest of them, all selected pieces are put in a single bag. +2. Uniformous. The pieces are now separated by size and put in different bags. Each size will now appear as often as any other. +3. Custom. The pieces are once again separated by size, but now the player specifies how likely each size is to appear. + +Both system 2 and 3 uses a system analogous to a bag to determine which size of piece to take next. diff --git a/src/Core/Bag.cpp b/src/Core/Bag.cpp index ae30431..1f5479d 100644 --- a/src/Core/Bag.cpp +++ b/src/Core/Bag.cpp @@ -7,26 +7,54 @@ #include #include +static const double SMALLEST_CONSIDERED_PROPORTION = 0.01; // the smallest a proportion can get before it is considered equal to 0 + Bag::Bag(const std::shared_ptr& piecesList) : piecesList(piecesList) { - - this->currentBag = this->piecesList->getSelectedPieces(); - this->nextBag.clear(); + this->highestSize = this->piecesList->getHighestLoadedSize(); + this->selectedPieces = this->piecesList->getSelectedPieces(); + this->distributionMode = this->piecesList->getDistributionMode(); + this->propotionsPerSize = this->piecesList->getProportionsPerSize(); + + this->currentBags.clear(); + this->nextBags.clear(); + this->sizesBag.clear(); + this->sizesProgression.clear(); + + if (this->distributionMode == DEFAULT) { + this->currentBags.push_back(this->selectedPieces); + this->nextBags.push_back({}); + } + else { + for (int i = 0; i <= this->highestSize; i++) { + this->currentBags.push_back(PieceBag()); + this->nextBags.push_back(PieceBag()); + this->sizesProgression.push_back(0); + } + + for (const auto& piece : this->selectedPieces) { + int pieceSize = this->piecesList->lookAtPiece(piece).getPositions().size(); + this->currentBags.at(pieceSize).push_back(piece); + } + } + this->prepareNext(); } void Bag::jumpToNextBag() { - if (this->currentBag.size() < this->nextBag.size()) { - std::swap(this->currentBag, this->nextBag); + for (int i = 0; i < this->currentBags.size(); i++) { + if (this->currentBags.at(i).size() < this->nextBags.at(i).size()) { + std::swap(this->currentBags.at(i), this->nextBags.at(i)); + } + + for (const auto& piece : this->nextBags.at(i)) { + this->currentBags.at(i).push_back(piece); + } + this->nextBags.at(i).clear(); } - for (const std::pair& pieceIndex : this->nextBag) { - this->currentBag.push_back(pieceIndex); - } - this->nextBag.clear(); - this->prepareNext(); } @@ -43,13 +71,40 @@ Piece Bag::getNext() { } void Bag::prepareNext() { - if (this->currentBag.empty()) { - std::swap(this->currentBag, this->nextBag); + if (this->distributionMode == DEFAULT) { + this->getNextPieceFromBag(0); + } + else { + if (this->sizesBag.empty()) { + for (int i = 0; i <= this->highestSize; i++) { + if (this->propotionsPerSize.at(i) >= SMALLEST_CONSIDERED_PROPORTION + && !(this->currentBags.at(i).empty() && this->nextBags.at(i).empty())) { + while (this->sizesProgression.at(i) < 1) { + this->sizesBag.push_back(i); + this->sizesProgression.at(i) += this->propotionsPerSize.at(i); + } + + this->sizesProgression.at(i) -= 1; + } + } + } + + int nextSizeIndex = std::rand() % this->sizesBag.size(); + int nextSize = this->sizesBag.at(nextSizeIndex); + this->sizesBag.erase(this->sizesBag.begin() + nextSizeIndex); + + this->getNextPieceFromBag(nextSize); + } +} + +void Bag::getNextPieceFromBag(int bagIndex) { + if (this->currentBags.at(bagIndex).empty()) { + std::swap(this->currentBags.at(bagIndex), this->nextBags.at(bagIndex)); } - int indexIndex = std::rand() % this->currentBag.size(); - this->next = this->currentBag.at(indexIndex); + int indexIndex = std::rand() % this->currentBags.at(bagIndex).size(); + this->next = this->currentBags.at(bagIndex).at(indexIndex); - this->nextBag.push_back(this->next); - this->currentBag.erase(this->currentBag.begin() + indexIndex); + this->nextBags.at(bagIndex).push_back(this->next); + this->currentBags.at(bagIndex).erase(this->currentBags.at(bagIndex).begin() + indexIndex); } diff --git a/src/Core/Bag.h b/src/Core/Bag.h index 9a6b76f..152c73c 100644 --- a/src/Core/Bag.h +++ b/src/Core/Bag.h @@ -7,6 +7,8 @@ #include #include +using PieceBag = std::vector>; + /** * A litteral bag of pieces, in which you take each of its piece randomly one by one then start again with a new bag @@ -14,10 +16,15 @@ class Bag { private: std::shared_ptr piecesList; // the list of loaded pieces + int highestSize; // the highest size of piece in the bag std::vector> selectedPieces; // the list of pieces that can be given to the player + PiecesDistributionMode distributionMode; // the distribution mode + std::vector propotionsPerSize; // the proportion of pieces for each size std::pair next; // the next piece to give - std::vector> currentBag; // the list of pieces that are still to be taken out before starting a new bag - std::vector> nextBag; // the list of pieces that have been taken out of the current bag and have been placed in the next + std::vector currentBags; // for each size, the list of pieces that are still to be taken out before starting a new bag + std::vector nextBags; // for each size, the list of pieces that have been taken out of the current bag and have been placed in the next + std::vector sizesBag; // the list each of bags that are still to have a piece taken out of them + std::vector sizesProgression; // how close each size is to meet its quota of pieces public: /** @@ -26,7 +33,7 @@ class Bag { Bag(const std::shared_ptr& piecesList); /** - * Ignores the remaining pieces in the current bag and startd fresh from a new bag + * Ignores the remaining pieces in the current bag and start fresh from a new bag */ void jumpToNextBag(); @@ -44,7 +51,12 @@ class Bag { private: /** - * Prepare the next picked piece in advance + * Prepares the next picked piece in advance */ void prepareNext(); + + /** + * Gets the next picked piece from the specified bag + */ + void getNextPieceFromBag(int bagIndex); }; diff --git a/src/Core/PiecesList.cpp b/src/Core/PiecesList.cpp index 58474df..84db76a 100644 --- a/src/Core/PiecesList.cpp +++ b/src/Core/PiecesList.cpp @@ -11,6 +11,10 @@ PiecesList::PiecesList() { this->highestLoadedSize = 0; this->selectedPieces.clear(); + this->distributionMode = DEFAULT; + this->proportionsPerSize.clear(); + this->customProportionsPerSize.clear(); + // we need to have something at index 0 even if there is no pieces of size 0 this->loadedPieces.clear(); this->convexPieces.clear(); @@ -87,6 +91,14 @@ void PiecesList::unselectAll() { this->selectedPieces.clear(); } +bool PiecesList::setDistributionMode(PiecesDistributionMode distributionMode) { + if (distributionMode == DEFAULT || distributionMode == UNIFORM || distributionMode == CUSTOM) { + this->distributionMode = distributionMode; + return true; + } + return false; +} + int PiecesList::getHighestLoadedSize() const { return this->highestLoadedSize; } @@ -101,13 +113,42 @@ std::vector> PiecesList::getSelectedPieces() const { return this->selectedPieces; } +PiecesDistributionMode PiecesList::getDistributionMode() const { + return this->distributionMode; +} + +bool PiecesList::changeCustomDistribution(int size, double distribution) { + if (size < 1 || size > this->highestLoadedSize || distribution > 1) { + return false; + } + + this->customProportionsPerSize.at(size) = distribution; + return true; +} + +std::vector PiecesList::getProportionsPerSize() const { + if (this->distributionMode == CUSTOM) { + return this->customProportionsPerSize; + } + else { + return this->proportionsPerSize; + } +} + Piece PiecesList::getPiece(const std::pair& pieceIndex) const { return this->loadedPieces.at(pieceIndex.first).at(pieceIndex.second); } +const Piece& PiecesList::lookAtPiece(const std::pair& pieceIndex) const { + return this->loadedPieces.at(pieceIndex.first).at(pieceIndex.second); +} + void PiecesList::pushBackEmptyVectors() { this->loadedPieces.push_back(std::vector()); this->convexPieces.push_back(std::vector()); this->holelessPieces.push_back(std::vector()); this->otherPieces.push_back(std::vector()); + + this->proportionsPerSize.push_back(1); + this->customProportionsPerSize.push_back(1); } diff --git a/src/Core/PiecesList.h b/src/Core/PiecesList.h index 4110ae4..9b5259d 100644 --- a/src/Core/PiecesList.h +++ b/src/Core/PiecesList.h @@ -6,6 +6,16 @@ #include +/** + * The type of pieces distribution managed by the game + */ +enum PiecesDistributionMode { + DEFAULT, + UNIFORM, + CUSTOM +}; + + /** * A container for all loaded pieces to prevent loading and copying them multiple times, * also allows for the player to select a list of pieces to be used in a game @@ -18,6 +28,9 @@ class PiecesList { std::vector> holelessPieces; // the list of holeless loaded pieces by size std::vector> otherPieces; // the list of other loaded pieces by size std::vector> selectedPieces; // the list of all currently selected pieces + PiecesDistributionMode distributionMode; // the current pieces distribution mode + std::vector proportionsPerSize; // the proportion of piece for each sizes + std::vector customProportionsPerSize; // the proportion of piece for each sizes when the distribution mode is set to custom public: /** @@ -66,6 +79,19 @@ class PiecesList { */ void unselectAll(); + /** + * Changes the current pieces distribution mode + * @return If the mode is supported + */ + bool setDistributionMode(PiecesDistributionMode distributionMode); + + /** + * Changes the distribution of the specified size for when the distribution mode is set to custom, + * the specified distribution must lower or equal to 1 + * @return If the new distribution was applied + */ + bool changeCustomDistribution(int size, double distribution); + /** * @return The highest loaded size of pieces */ @@ -81,10 +107,25 @@ class PiecesList { */ std::vector> getSelectedPieces() const; + /** + * @return The current distribution mode + */ + PiecesDistributionMode getDistributionMode() const; + + /** + * @return The proportion of pieces for each loaded size + */ + std::vector getProportionsPerSize() const; + /** * @return A copy of the piece corresponding to the specified index */ Piece getPiece(const std::pair& pieceIndex) const; + + /** + * @return The piece corresponding to the specified index + */ + const Piece& lookAtPiece(const std::pair& pieceIndex) const; private: /** diff --git a/src/GraphicalUI/AppMenus/GamePlayingAppMenu.cpp b/src/GraphicalUI/AppMenus/GamePlayingAppMenu.cpp index d58845c..823b4f3 100644 --- a/src/GraphicalUI/AppMenus/GamePlayingAppMenu.cpp +++ b/src/GraphicalUI/AppMenus/GamePlayingAppMenu.cpp @@ -40,7 +40,7 @@ GamePlayingAppMenu::GamePlayingAppMenu(std::shared_ptr menuStack, std this->nextCellSizeZoom = this->nextQueuePosition[0].size.y; for (const auto& piece : this->settings->getMenu().getPiecesList().getSelectedPieces()) { - float nextPieceCellSizeZoom = ((int) this->nextQueuePosition[0].size.y) / this->settings->getMenu().getPiecesList().getPiece(piece).getLength(); + float nextPieceCellSizeZoom = ((int) this->nextQueuePosition[0].size.y) / this->settings->getMenu().getPiecesList().lookAtPiece(piece).getLength(); this->nextCellSizeZoom = std::min(this->nextCellSizeZoom, nextPieceCellSizeZoom); } diff --git a/src/GraphicalUI/Settings.cpp b/src/GraphicalUI/Settings.cpp index 8825788..4777df5 100644 --- a/src/GraphicalUI/Settings.cpp +++ b/src/GraphicalUI/Settings.cpp @@ -10,6 +10,8 @@ static const sf::Vector2u BASE_WINDOW_SIZE = {80, 50}; static const int WINDOW_SIZE_MULTIPLIERS[] = {4, 6, 10, 14, 20, 30, 40}; static const int WINDOW_SIZE_LAST_MODE = (sizeof(WINDOW_SIZE_MULTIPLIERS) / sizeof(int)) - 1; +static const int START_TIMER_MAX = 4; +static const int DISTRIBUTION_MAX = 10; Settings::Settings() { @@ -67,13 +69,20 @@ void Settings::loadSettingsFromFile() { // piece distribution settingsFile.get(byte); - //TODO - if (byte == 2) { - for (int i = 1; i <= 15; i++) { + this->menu.getPiecesList().setDistributionMode(PiecesDistributionMode(byte)); + + this->distributions.clear(); + this->distributions.push_back(0); + for (int i = 1; i <= 15; i++) { + if (byte == CUSTOM) { settingsFile.get(byte); - //TODO + this->distributions.push_back(i); + } + else { + this->distributions.push_back(1); } } + this->confirmDistribution(); // selected pieces char pieceType; @@ -131,9 +140,16 @@ void Settings::saveSettingsToFile() const { settingsFile.write(&byte, 1); // piece distribution - //TODO + byte = this->menu.readPiecesList().getDistributionMode(); settingsFile.write(&byte, 1); + if (this->menu.readPiecesList().getDistributionMode() == CUSTOM) { + for (int i = 1; i <= 15; i++) { + byte = this->distributions.at(i); + settingsFile.write(&byte, 1); + } + } + // selected pieces for (const auto& [type, value] : this->selectedPieces) { byte = type; @@ -189,7 +205,7 @@ void Settings::changeVideoMode(sf::RenderWindow& window) const { } bool Settings::lengthenStartTimer() { - if (this->startTimerLength < 4) { + if (this->startTimerLength < START_TIMER_MAX) { this->startTimerLength++; return true; } @@ -240,6 +256,36 @@ void Settings::confirmSelectedPieces() { } } +bool Settings::setDistributionMode (PiecesDistributionMode distributionMode) { + return this->menu.getPiecesList().setDistributionMode(distributionMode); +} + +bool Settings::increaseDistribution(int size) { + if (size < 1 || size > MAXIMUM_PIECES_SIZE) return false; + + if (this->distributions.at(size) < DISTRIBUTION_MAX) { + this->distributions.at(size)++; + return true; + } + return false; +} + +bool Settings::decreaseDistribution(int size) { + if (size < 1 || size > MAXIMUM_PIECES_SIZE) return false; + + if (this->distributions.at(size) > 0) { + this->distributions.at(size)--; + return true; + } + return false; +} + +void Settings::confirmDistribution() { + for (int i = 1; i <= 15; i++) { + this->menu.getPiecesList().changeCustomDistribution(i, (double) this->distributions.at(i) / (DISTRIBUTION_MAX + 0.001)); + } +} + Menu& Settings::getMenu() { return this->menu; } diff --git a/src/GraphicalUI/Settings.h b/src/GraphicalUI/Settings.h index 1232573..9c14082 100644 --- a/src/GraphicalUI/Settings.h +++ b/src/GraphicalUI/Settings.h @@ -27,6 +27,7 @@ class Settings { int startTimerLength; Gamemode gamemode; std::vector> selectedPieces; + std::vector distributions; public: Settings(); @@ -59,6 +60,14 @@ class Settings { void confirmSelectedPieces(); + bool setDistributionMode (PiecesDistributionMode distributionMode); + + bool increaseDistribution(int size); + + bool decreaseDistribution(int size); + + void confirmDistribution(); + Menu& getMenu(); Keybinds& getKeybinds(); diff --git a/src/GraphicalUI/main.cpp b/src/GraphicalUI/main.cpp index f01b9f8..8b40ce1 100644 --- a/src/GraphicalUI/main.cpp +++ b/src/GraphicalUI/main.cpp @@ -80,7 +80,7 @@ void resetSettingsFile() { settingsFile.write(&byte, 1); // piece distribution - byte = 0; + byte = DEFAULT; settingsFile.write(&byte, 1); // selected pieces