added distribution modes

This commit is contained in:
2025-03-29 18:48:37 +01:00
parent 3538403f40
commit 57620c70a2
9 changed files with 246 additions and 28 deletions

View File

@@ -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. 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. 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.

View File

@@ -7,25 +7,53 @@
#include <utility> #include <utility>
#include <cstdlib> #include <cstdlib>
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) : Bag::Bag(const std::shared_ptr<PiecesList>& piecesList) :
piecesList(piecesList) { piecesList(piecesList) {
this->currentBag = this->piecesList->getSelectedPieces(); this->highestSize = this->piecesList->getHighestLoadedSize();
this->nextBag.clear(); 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(); this->prepareNext();
} }
void Bag::jumpToNextBag() { void Bag::jumpToNextBag() {
if (this->currentBag.size() < this->nextBag.size()) { for (int i = 0; i < this->currentBags.size(); i++) {
std::swap(this->currentBag, this->nextBag); if (this->currentBags.at(i).size() < this->nextBags.at(i).size()) {
} std::swap(this->currentBags.at(i), this->nextBags.at(i));
}
for (const std::pair<int, int>& pieceIndex : this->nextBag) { for (const auto& piece : this->nextBags.at(i)) {
this->currentBag.push_back(pieceIndex); this->currentBags.at(i).push_back(piece);
}
this->nextBags.at(i).clear();
} }
this->nextBag.clear();
this->prepareNext(); this->prepareNext();
} }
@@ -43,13 +71,40 @@ Piece Bag::getNext() {
} }
void Bag::prepareNext() { void Bag::prepareNext() {
if (this->currentBag.empty()) { if (this->distributionMode == DEFAULT) {
std::swap(this->currentBag, this->nextBag); 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(); int indexIndex = std::rand() % this->currentBags.at(bagIndex).size();
this->next = this->currentBag.at(indexIndex); this->next = this->currentBags.at(bagIndex).at(indexIndex);
this->nextBag.push_back(this->next); this->nextBags.at(bagIndex).push_back(this->next);
this->currentBag.erase(this->currentBag.begin() + indexIndex); this->currentBags.at(bagIndex).erase(this->currentBags.at(bagIndex).begin() + indexIndex);
} }

View File

@@ -7,6 +7,8 @@
#include <memory> #include <memory>
#include <utility> #include <utility>
using PieceBag = std::vector<std::pair<int, int>>;
/** /**
* A litteral bag of pieces, in which you take each of its piece randomly one by one then start again with a new bag * 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 { class Bag {
private: private:
std::shared_ptr<PiecesList> piecesList; // the list of loaded pieces std::shared_ptr<PiecesList> piecesList; // the list of loaded pieces
int highestSize; // the highest size of piece in the bag
std::vector<std::pair<int, int>> selectedPieces; // the list of pieces that can be given to the player std::vector<std::pair<int, int>> selectedPieces; // the list of pieces that can be given to the player
PiecesDistributionMode distributionMode; // the distribution mode
std::vector<double> propotionsPerSize; // the proportion of pieces for each size
std::pair<int, int> next; // the next piece to give std::pair<int, int> next; // the next piece to give
std::vector<std::pair<int, int>> currentBag; // the list of pieces that are still to be taken out before starting a new bag std::vector<PieceBag> currentBags; // for each size, the list of pieces that are still to be taken out before starting a new bag
std::vector<std::pair<int, int>> nextBag; // the list of pieces that have been taken out of the current bag and have been placed in the next std::vector<PieceBag> 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<int> sizesBag; // the list each of bags that are still to have a piece taken out of them
std::vector<double> sizesProgression; // how close each size is to meet its quota of pieces
public: public:
/** /**
@@ -26,7 +33,7 @@ class Bag {
Bag(const std::shared_ptr<PiecesList>& piecesList); Bag(const std::shared_ptr<PiecesList>& 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(); void jumpToNextBag();
@@ -44,7 +51,12 @@ class Bag {
private: private:
/** /**
* Prepare the next picked piece in advance * Prepares the next picked piece in advance
*/ */
void prepareNext(); void prepareNext();
/**
* Gets the next picked piece from the specified bag
*/
void getNextPieceFromBag(int bagIndex);
}; };

View File

@@ -11,6 +11,10 @@ PiecesList::PiecesList() {
this->highestLoadedSize = 0; this->highestLoadedSize = 0;
this->selectedPieces.clear(); 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 // we need to have something at index 0 even if there is no pieces of size 0
this->loadedPieces.clear(); this->loadedPieces.clear();
this->convexPieces.clear(); this->convexPieces.clear();
@@ -87,6 +91,14 @@ void PiecesList::unselectAll() {
this->selectedPieces.clear(); 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 { int PiecesList::getHighestLoadedSize() const {
return this->highestLoadedSize; return this->highestLoadedSize;
} }
@@ -101,13 +113,42 @@ std::vector<std::pair<int, int>> PiecesList::getSelectedPieces() const {
return this->selectedPieces; 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<double> PiecesList::getProportionsPerSize() const {
if (this->distributionMode == CUSTOM) {
return this->customProportionsPerSize;
}
else {
return this->proportionsPerSize;
}
}
Piece PiecesList::getPiece(const std::pair<int, int>& pieceIndex) const { Piece PiecesList::getPiece(const std::pair<int, int>& pieceIndex) const {
return this->loadedPieces.at(pieceIndex.first).at(pieceIndex.second); return this->loadedPieces.at(pieceIndex.first).at(pieceIndex.second);
} }
const Piece& PiecesList::lookAtPiece(const std::pair<int, int>& pieceIndex) const {
return this->loadedPieces.at(pieceIndex.first).at(pieceIndex.second);
}
void PiecesList::pushBackEmptyVectors() { void PiecesList::pushBackEmptyVectors() {
this->loadedPieces.push_back(std::vector<Piece>()); this->loadedPieces.push_back(std::vector<Piece>());
this->convexPieces.push_back(std::vector<int>()); this->convexPieces.push_back(std::vector<int>());
this->holelessPieces.push_back(std::vector<int>()); this->holelessPieces.push_back(std::vector<int>());
this->otherPieces.push_back(std::vector<int>()); this->otherPieces.push_back(std::vector<int>());
this->proportionsPerSize.push_back(1);
this->customProportionsPerSize.push_back(1);
} }

View File

@@ -6,6 +6,16 @@
#include <utility> #include <utility>
/**
* 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, * 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 * 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<std::vector<int>> holelessPieces; // the list of holeless loaded pieces by size std::vector<std::vector<int>> holelessPieces; // the list of holeless loaded pieces by size
std::vector<std::vector<int>> otherPieces; // the list of other loaded pieces by size std::vector<std::vector<int>> otherPieces; // the list of other loaded pieces by size
std::vector<std::pair<int, int>> selectedPieces; // the list of all currently selected pieces std::vector<std::pair<int, int>> selectedPieces; // the list of all currently selected pieces
PiecesDistributionMode distributionMode; // the current pieces distribution mode
std::vector<double> proportionsPerSize; // the proportion of piece for each sizes
std::vector<double> customProportionsPerSize; // the proportion of piece for each sizes when the distribution mode is set to custom
public: public:
/** /**
@@ -66,6 +79,19 @@ class PiecesList {
*/ */
void unselectAll(); 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 * @return The highest loaded size of pieces
*/ */
@@ -81,11 +107,26 @@ class PiecesList {
*/ */
std::vector<std::pair<int, int>> getSelectedPieces() const; std::vector<std::pair<int, int>> getSelectedPieces() const;
/**
* @return The current distribution mode
*/
PiecesDistributionMode getDistributionMode() const;
/**
* @return The proportion of pieces for each loaded size
*/
std::vector<double> getProportionsPerSize() const;
/** /**
* @return A copy of the piece corresponding to the specified index * @return A copy of the piece corresponding to the specified index
*/ */
Piece getPiece(const std::pair<int, int>& pieceIndex) const; Piece getPiece(const std::pair<int, int>& pieceIndex) const;
/**
* @return The piece corresponding to the specified index
*/
const Piece& lookAtPiece(const std::pair<int, int>& pieceIndex) const;
private: private:
/** /**
* Adds empty vectors at the end of every pieces list * Adds empty vectors at the end of every pieces list

View File

@@ -40,7 +40,7 @@ GamePlayingAppMenu::GamePlayingAppMenu(std::shared_ptr<MenuStack> menuStack, std
this->nextCellSizeZoom = this->nextQueuePosition[0].size.y; this->nextCellSizeZoom = this->nextQueuePosition[0].size.y;
for (const auto& piece : this->settings->getMenu().getPiecesList().getSelectedPieces()) { 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); this->nextCellSizeZoom = std::min(this->nextCellSizeZoom, nextPieceCellSizeZoom);
} }

View File

@@ -10,6 +10,8 @@
static const sf::Vector2u BASE_WINDOW_SIZE = {80, 50}; 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_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 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() { Settings::Settings() {
@@ -67,13 +69,20 @@ void Settings::loadSettingsFromFile() {
// piece distribution // piece distribution
settingsFile.get(byte); settingsFile.get(byte);
//TODO this->menu.getPiecesList().setDistributionMode(PiecesDistributionMode(byte));
if (byte == 2) {
for (int i = 1; i <= 15; i++) { this->distributions.clear();
this->distributions.push_back(0);
for (int i = 1; i <= 15; i++) {
if (byte == CUSTOM) {
settingsFile.get(byte); settingsFile.get(byte);
//TODO this->distributions.push_back(i);
}
else {
this->distributions.push_back(1);
} }
} }
this->confirmDistribution();
// selected pieces // selected pieces
char pieceType; char pieceType;
@@ -131,9 +140,16 @@ void Settings::saveSettingsToFile() const {
settingsFile.write(&byte, 1); settingsFile.write(&byte, 1);
// piece distribution // piece distribution
//TODO byte = this->menu.readPiecesList().getDistributionMode();
settingsFile.write(&byte, 1); 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 // selected pieces
for (const auto& [type, value] : this->selectedPieces) { for (const auto& [type, value] : this->selectedPieces) {
byte = type; byte = type;
@@ -189,7 +205,7 @@ void Settings::changeVideoMode(sf::RenderWindow& window) const {
} }
bool Settings::lengthenStartTimer() { bool Settings::lengthenStartTimer() {
if (this->startTimerLength < 4) { if (this->startTimerLength < START_TIMER_MAX) {
this->startTimerLength++; this->startTimerLength++;
return true; 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() { Menu& Settings::getMenu() {
return this->menu; return this->menu;
} }

View File

@@ -27,6 +27,7 @@ class Settings {
int startTimerLength; int startTimerLength;
Gamemode gamemode; Gamemode gamemode;
std::vector<std::pair<PiecesType, int>> selectedPieces; std::vector<std::pair<PiecesType, int>> selectedPieces;
std::vector<int> distributions;
public: public:
Settings(); Settings();
@@ -59,6 +60,14 @@ class Settings {
void confirmSelectedPieces(); void confirmSelectedPieces();
bool setDistributionMode (PiecesDistributionMode distributionMode);
bool increaseDistribution(int size);
bool decreaseDistribution(int size);
void confirmDistribution();
Menu& getMenu(); Menu& getMenu();
Keybinds& getKeybinds(); Keybinds& getKeybinds();

View File

@@ -80,7 +80,7 @@ void resetSettingsFile() {
settingsFile.write(&byte, 1); settingsFile.write(&byte, 1);
// piece distribution // piece distribution
byte = 0; byte = DEFAULT;
settingsFile.write(&byte, 1); settingsFile.write(&byte, 1);
// selected pieces // selected pieces