12 Commits

Author SHA1 Message Date
6da3cb66fa changed InfoAppMenu 2025-05-23 23:57:13 +02:00
e0ab6a4828 update readme and main.cpp debug messages 2025-05-23 22:35:31 +02:00
a62b3c018d fixed holding spwaning the piece 1 row lower 2025-04-01 18:45:01 +02:00
46ebb88ef2 we can load polyos in the app now 2025-03-31 21:14:06 +02:00
de14978b01 support unsupported sizes 2025-03-31 20:29:34 +02:00
d5ac79559e updated file format 2025-03-31 19:22:52 +02:00
5ea47ddd25 added startup menu 2025-03-31 18:59:42 +02:00
1a95765877 variable max polyo size 2025-03-31 14:12:36 +02:00
6bff555cbc fix les gros polyos 2025-03-31 11:25:23 +02:00
f4b58fb67e omg omg 2025-03-30 22:43:41 +02:00
0271b56542 better piece scrolling 2025-03-30 20:16:14 +02:00
5d73e751d7 début de qqch 2025-03-30 13:07:17 +02:00
27 changed files with 707 additions and 157 deletions

View File

@@ -4,19 +4,26 @@ Modern stacker game with every polyominos from size 1 to 15, made in C++ with [S
## Download ## Download
// to bo included when release version is done // // TODO when the game is finished //
This game has been tested on and provides executables for Windows 11 and Linux under Ubuntu only. This game has been tested on and built on Windows 11 and WSL2 Ubuntu only.
If your OS isn't compactible with either of theses two, you can try [manually building the project](#manual-build-and-run). If your OS isn't compactible with either of theses two, you can try [manually building the project](#manual-build).
## How to play ## How to play
### General
You can see and change in-game keybinds in the **SETTINGS** section of the main menu! You can see and change in-game keybinds in the **SETTINGS** section of the main menu!
All of in-menu navigation is done with the arrow keys, the Enter key and the Escape key. Theses are unmutable keybinds. All of in-menu navigation is done with the **arrow keys**, the **Enter key** and the **Escape key**. Theses are unchangeable keybinds.
You will find some more infos about the Rotation System and the scoring in the **INFO** section of the main menu. You will find more infos about the Rotation System, the scoring system, or the different pieces type in the **INFO** section of the main menu.
If you want to know more details about the generation and classification of polyominoes, [check the documentation](/doc/)! If you want to know more details about the generation of polyominoes, [check the documentation](/doc/)!
## Features
- Every polyominoes from size 1 to 15, selectable as you wish, with customizable propotionnality for each size!
- Customizable keybinds!
- 0° rotations!
- AutoRS as the Rotation System!
- IRS, IHS, infinite hold, and other leniency mechanics!
- Very bland interface!! (i'm not a designer)
### Available gamemodes ### Available gamemodes
@@ -26,6 +33,10 @@ If you want to know more details about the generation and classification of poly
- MASTER : clear 200 lines at levels higher than maximum gravity! - MASTER : clear 200 lines at levels higher than maximum gravity!
- ZEN : practice indefinitely in this mode with no gravity! - ZEN : practice indefinitely in this mode with no gravity!
### Screenshots
// TODO when the game is finished //
## Manual build ## Manual build
This project uses xmake for compiling, xmake is cross-platform and works in most OS, xmake also automatically install supported librairies. This project uses xmake for compiling, xmake is cross-platform and works in most OS, xmake also automatically install supported librairies.
@@ -43,12 +54,19 @@ If you need to change the toolchain (for example using gcc):
### Run the project ### Run the project
``xmake run`` ``xmake run``
Note that the program will generate the polyomino files for you the first time. This lasts several minutes so it only does it up to size 10. If you want to use the full range up to size 15, you will need to uncomment the ``#define`` at line 13 of file ``src/GraphicalUI/Settings.h``.
If for some reasons you wanna run the command line version: The program will generate the polyomino files for you if you don't have them.
As this is a lengthy process, debug mode limits pieces size to 10.
To switch between debug and release mode:
``xmake f -m debug``
``xmake f -m release``
If for some reasons you wanna run the command line version (not updated):
``xmake run text`` ``xmake run text``
### Create a release version (xmake packaging ???) ### Package the project
// TODO when the game is finished //
## Credits ## Credits

View File

@@ -37,6 +37,8 @@ _Repeat for every avaible actions._
The settings file has the following format: The settings file has the following format:
- The versions of the file format, stored with 1 byte
- The previous maximum pieces size selected, stored with 1 byte
- The number of the chosen keybinds (from 0 to 4), stored with 1 byte - The number of the chosen keybinds (from 0 to 4), stored with 1 byte
- The DAS of the player, stored with 1 byte - The DAS of the player, stored with 1 byte
- The ARR of the player, stored with 1 byte - The ARR of the player, stored with 1 byte
@@ -47,5 +49,11 @@ The settings file has the following format:
- The last selected width of the board, stored with 1 byte - The last selected width of the board, stored with 1 byte
- The last selected height of the board, stored with 1 byte - The last selected height of the board, stored with 1 byte
- The uniformity mode (0 for default distribution, 1 for uniformous distribution, 2 for custom distribution), stored with 1 byte - The uniformity mode (0 for default distribution, 1 for uniformous distribution, 2 for custom distribution), stored with 1 byte
- If custom distribution is set, store the proportion of each size (15x1 byte total) - For each size, store the custom proportion (from 0 to 20) (15x1 byte total)
- Every selected pieces, using 1 byte for the type of selection (once again converted from an Enum) and 1 byte for the actual value - Every selected pieces
- For every groupe of piece (ALL, CONVEX, HOLELESS, OTHER), use 1 byte for the type (once again converted from an Enum) and 1 byte for the size
- For every single piece, use 1 byte for (the size + encoding that it is a single piece),
and 3 bytes to store the number of the piece (allows number up to 16M, size 15 has 6M pieces).
The current file format version is 11.
If the file starts with a number lower than that, it will be regenerated upon launching the game.

View File

@@ -67,27 +67,29 @@ void Game::nextFrame(const std::set<Action>& playerActions) {
if (AREJustEnded) { if (AREJustEnded) {
this->lost = this->board.spawnNextPiece(); this->lost = this->board.spawnNextPiece();
this->resetPiece(true); this->resetPiece(true);
}
/* IRS and IHS */ /* IRS and IHS */
bool initialRotated = (this->initialActions.contains(ROTATE_0) || this->initialActions.contains(ROTATE_CW)
|| this->initialActions.contains(ROTATE_180) || this->initialActions.contains(ROTATE_CCW));
Rotation initialRotation = NONE Rotation initialRotation = NONE
+ ((this->initialActions.contains(ROTATE_CW)) ? CLOCKWISE : NONE) + ((this->initialActions.contains(ROTATE_CW)) ? CLOCKWISE : NONE)
+ ((this->initialActions.contains(ROTATE_180)) ? DOUBLE : NONE) + ((this->initialActions.contains(ROTATE_180)) ? DOUBLE : NONE)
+ ((this->initialActions.contains(ROTATE_CCW)) ? COUNTERCLOCKWISE : NONE); + ((this->initialActions.contains(ROTATE_CCW)) ? COUNTERCLOCKWISE : NONE);
if (this->initialActions.contains(HOLD)) { if (this->initialActions.contains(HOLD)) {
this->lost = (!this->board.hold(initialRotation)); this->board.hold(initialRotation);
} }
else { else {
if ((initialRotation != NONE) || this->initialActions.contains(ROTATE_0)) { if (initialRotated) {
this->lost = (!this->board.rotate(initialRotation)); this->board.rotate(initialRotation);
} }
} }
this->lost = this->board.activePieceInWall();
if (this->lost) { if (this->lost) {
if (initialRotation == NONE) {
this->board.rotate(NONE); this->board.rotate(NONE);
this->lost = this->board.activePieceInWall(); this->lost = this->board.activePieceInWall();
if (this->lost) { if (this->lost) {
this->framesPassed++; this->framesPassed++;
return; return;
@@ -97,7 +99,7 @@ void Game::nextFrame(const std::set<Action>& playerActions) {
/* HOLD */ /* HOLD */
if (playerActions.contains(HOLD) && (!this->heldActions.contains(HOLD))) { if (playerActions.contains(HOLD) && (!this->heldActions.contains(HOLD))) {
if (this->board.hold()) { if (this->board.hold({})) {
this->resetPiece(false); this->resetPiece(false);
} }
} }

View File

@@ -10,6 +10,8 @@
#include <memory> #include <memory>
#include <utility> #include <utility>
#include <cstdlib> #include <cstdlib>
#include <iostream>
#include <optional>
GameBoard::GameBoard(int boardWidth, int boardHeight, const std::shared_ptr<PiecesList>& piecesList, int nextQueueLength) : GameBoard::GameBoard(int boardWidth, int boardHeight, const std::shared_ptr<PiecesList>& piecesList, int nextQueueLength) :
@@ -193,12 +195,12 @@ bool GameBoard::activePieceOverlaps(const std::set<Position>& safePositions, con
return false; return false;
} }
bool GameBoard::hold(Rotation initialRotation) { bool GameBoard::hold(std::optional<Rotation> initialRotation) {
Position storedPosition = this->activePiecePosition;
std::swap(this->activePiece, this->heldPiece); std::swap(this->activePiece, this->heldPiece);
bool isFirstTimeHolding = (this->activePiece == nullptr); bool isFirstTimeHolding = (this->activePiece == nullptr);
if (isFirstTimeHolding) { if (isFirstTimeHolding) {
// try with the next piece in queue since there is no piece in the hold box yet
if (this->nextQueueLength == 0) { if (this->nextQueueLength == 0) {
this->activePiece = std::make_shared<Piece>(this->generator.lookNext()); this->activePiece = std::make_shared<Piece>(this->generator.lookNext());
} }
@@ -207,21 +209,25 @@ bool GameBoard::hold(Rotation initialRotation) {
} }
} }
Piece stored = *this->activePiece; // try initial rotation
Position storedPosition = this->activePiecePosition;
this->goToSpawnPosition(); this->goToSpawnPosition();
this->rotate(initialRotation); if (initialRotation.has_value()) {
std::shared_ptr<Piece> storedPiece(this->activePiece);
this->rotate(initialRotation.value());
// if the piece can't spawn, abort initial rotation
if (this->activePieceInWall()) { if (this->activePieceInWall()) {
this->activePiece = std::make_shared<Piece>(stored); this->activePiece = storedPiece;
this->goToSpawnPosition(); }
}
// if the piece can't spawn, try 0° rotation
if (this->activePieceInWall()) {
std::shared_ptr<Piece> storedPiece(this->activePiece);
this->rotate(NONE);
// if the piece still can't spawn, abort holding // if the piece still can't spawn, abort holding
if (this->activePieceInWall()) { if (this->activePieceInWall()) {
if (isFirstTimeHolding) { this->activePiece = (isFirstTimeHolding) ? nullptr : storedPiece;
this->activePiece = nullptr;
}
std::swap(this->activePiece, this->heldPiece); std::swap(this->activePiece, this->heldPiece);
this->activePiecePosition = storedPosition; this->activePiecePosition = storedPosition;
return false; return false;
@@ -229,7 +235,6 @@ bool GameBoard::hold(Rotation initialRotation) {
} }
if (isFirstTimeHolding) { if (isFirstTimeHolding) {
// confirm we keep the piece we tried with
this->nextQueue.push_back(this->generator.getNext()); this->nextQueue.push_back(this->generator.getNext());
this->nextQueue.erase(this->nextQueue.begin()); this->nextQueue.erase(this->nextQueue.begin());
} }

View File

@@ -7,6 +7,7 @@
#include <vector> #include <vector>
#include <memory> #include <memory>
#include <optional>
/** /**
@@ -90,7 +91,7 @@ class GameBoard {
* 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 * 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
* @return If it suceeded * @return If it suceeded
*/ */
bool hold(Rotation initialRotation = NONE); bool hold(std::optional<Rotation> initialRotation);
/** /**
* Spawns the next piece from the queue * Spawns the next piece from the queue

View File

@@ -39,7 +39,7 @@ inline std::string getGamemodeGoal(Gamemode gamemode) {
"200 lines", "200 lines",
"2 minutes", "2 minutes",
"200 lines", "200 lines",
"Chill" "Infinite"
}; };
return GAMEMODE_DESCRIPTIONS[gamemode]; return GAMEMODE_DESCRIPTIONS[gamemode];

View File

@@ -29,6 +29,7 @@ PiecesList::PiecesList() {
bool PiecesList::loadPieces(int size) { bool PiecesList::loadPieces(int size) {
if (size < 1) return false; if (size < 1) return false;
if (size <= this->highestLoadedSize) return true;
PiecesFiles piecesFiles; PiecesFiles piecesFiles;
for (int i = this->highestLoadedSize + 1; i <= size; i++) { for (int i = this->highestLoadedSize + 1; i <= size; i++) {

View File

@@ -57,6 +57,33 @@ class AppMenu {
} }
} }
sf::Text createText(int fontSize = 2) const {
sf::Text newText(this->pressStartFont, "", this->settings->getWindowSizeMultiplier() * fontSize);
newText.setFillColor(sf::Color::Black);
newText.setOutlineColor(sf::Color::White);
newText.setOutlineThickness(0);
return newText;
}
void setTextPosition(sf::Text& text, float xPos, float yPos) const {
float sizeMultiplier = this->settings->getWindowSizeMultiplier();
text.setOrigin(sf::Vector2f({0, text.getLocalBounds().size.y / 2}));
text.setPosition(sf::Vector2f({sizeMultiplier * xPos, sizeMultiplier * yPos}));
}
void setTitlePosition(sf::Text& text, float yPos) const {
float sizeMultiplier = this->settings->getWindowSizeMultiplier();
text.setOrigin({text.getLocalBounds().getCenter().x, text.getLocalBounds().size.y / 2});
text.setPosition(sf::Vector2f({sizeMultiplier * 40.f, sizeMultiplier * yPos}));
}
void setTextOutline(sf::Text& text, bool hasOutline) const {
text.setOutlineThickness(hasOutline * (this->settings->getWindowSizeMultiplier() / 2));
}
void placeText(sf::Text& text, const std::optional<PlayerCursor>& playerCursor, const sf::String& string, float xPos, float yPos, const std::optional<sf::Vector2u>& cursorPos) const { void placeText(sf::Text& text, const std::optional<PlayerCursor>& playerCursor, const sf::String& string, float xPos, float yPos, const std::optional<sf::Vector2u>& cursorPos) const {
float sizeMultiplier = this->settings->getWindowSizeMultiplier(); float sizeMultiplier = this->settings->getWindowSizeMultiplier();
@@ -80,4 +107,11 @@ class AppMenu {
text.setPosition(sf::Vector2f({sizeMultiplier * 40.f, sizeMultiplier * yPos})); text.setPosition(sf::Vector2f({sizeMultiplier * 40.f, sizeMultiplier * yPos}));
this->renderWindow->draw(text); this->renderWindow->draw(text);
} }
sf::Color getColorOfBlock(Block block, int luminosityShift) const {
Color rgbColor = BLOCKS_COLOR[block];
return sf::Color(std::clamp(rgbColor.red + luminosityShift, 0, 255),
std::clamp(rgbColor.green + luminosityShift, 0, 255),
std::clamp(rgbColor.blue + luminosityShift, 0, 255));
}
}; };

View File

@@ -12,7 +12,7 @@ GameDistributionAppMenu::GameDistributionAppMenu(std::shared_ptr<MenuStack> menu
AppMenu(menuStack, settings, renderWindow), AppMenu(menuStack, settings, renderWindow),
playerCursor({1}) { playerCursor({1}) {
for (int i = 1; i <= MAXIMUM_PIECES_SIZE; i++) { for (int i = 1; i <= this->settings->getMaximumPiecesSize(); i++) {
this->playerCursor.addRow(i, 1); this->playerCursor.addRow(i, 1);
} }
} }
@@ -65,7 +65,7 @@ void GameDistributionAppMenu::drawFrame() const {
const DistributionMode distributionMode = this->settings->getMenu().readPiecesList().getDistributionMode(); const DistributionMode distributionMode = this->settings->getMenu().readPiecesList().getDistributionMode();
const std::vector<int>& distributions = this->settings->getDistributions(); const std::vector<int>& distributions = this->settings->getDistributions();
int firstElem = std::clamp(((int) this->playerCursor.getPosition().y) - 1, 0, MAXIMUM_PIECES_SIZE - 3); int firstElem = std::clamp(((int) this->playerCursor.getPosition().y) - 1, 0, this->settings->getMaximumPiecesSize() - 3);
if (firstElem == 0) { if (firstElem == 0) {
this->placeText(text, this->playerCursor, "< DISTRIBUTION MODE: " + getPiecesDistributionName(distributionMode) + " >", 5.f, 15.f, sf::Vector2u{0, 0}); this->placeText(text, this->playerCursor, "< DISTRIBUTION MODE: " + getPiecesDistributionName(distributionMode) + " >", 5.f, 15.f, sf::Vector2u{0, 0});
} }

View File

@@ -11,30 +11,66 @@
GamePiecesAppMenu::GamePiecesAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow) : GamePiecesAppMenu::GamePiecesAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow) :
AppMenu(menuStack, settings, renderWindow), AppMenu(menuStack, settings, renderWindow),
playerCursor({1, 1}) { playerCursor({1, (unsigned int) this->settings->getSelectedPieces().size() + 1u}) {
for (int i = 1; i <= MAXIMUM_PIECES_SIZE; i++) { for (int i = 1; i <= this->settings->getMaximumPiecesSize(); i++) {
this->playerCursor.addRow(i + 1, this->settings->getMenu().readPiecesList().getNumberOfPieces(i) + 4); this->playerCursor.addRow(i + 1, this->settings->getMenu().readPiecesList().getNumberOfPieces(i) + 4);
} }
if (this->settings->getMaximumPiecesSize() < MAXIMUM_PIECES_SIZE) {
this->playerCursor.addRow(this->settings->getMaximumPiecesSize() + 2, 1);
}
} }
void GamePiecesAppMenu::computeFrame() { void GamePiecesAppMenu::computeFrame() {
this->updateMetaBinds(); this->updateMetaBinds();
this->playerCursor.updatePosition(); this->playerCursor.updatePosition();
if (this->playerCursor.movedDown() && this->playerCursor.getPosition().y == 2) {
this->playerCursor.goToPosition({0, 2});
}
if (this->enterReleased) { if (this->enterReleased) {
if (this->playerCursor.getPosition().y == 0) { if (this->playerCursor.getPosition().y == 0) {
this->menuStack->push(std::make_shared<GameDistributionAppMenu>(this->menuStack, this->settings, this->renderWindow)); this->menuStack->push(std::make_shared<GameDistributionAppMenu>(this->menuStack, this->settings, this->renderWindow));
} }
if (this->playerCursor.getPosition().y > 1) {
//TODO if (this->playerCursor.getPosition().y == (this->settings->getMaximumPiecesSize() + 2)) {
int newMaxSize = this->settings->getMaximumPiecesSize() + 1;
this->settings->loadPieces(newMaxSize);
this->playerCursor.removeRow(newMaxSize + 1);
this->playerCursor.addRow(newMaxSize + 1, this->settings->getMenu().readPiecesList().getNumberOfPieces(newMaxSize) + 4);
this->playerCursor.goToPosition({0u, newMaxSize + 1u});
if (newMaxSize < MAXIMUM_PIECES_SIZE) {
this->playerCursor.addRow(newMaxSize + 2, 1);
}
}
else if (this->playerCursor.getPosition().y > 1) {
if (this->playerCursor.getPosition().x >= 4) {
this->settings->selectPieces(createSinglePieceType(this->playerCursor.getPosition().y - 1), this->playerCursor.getPosition().x - 4);
}
else {
switch (this->playerCursor.getPosition().x) {
case 0 : {this->settings->selectPieces(ALL_PIECES, this->playerCursor.getPosition().y - 1); break;}
case 1 : {this->settings->selectPieces(CONVEX_PIECES, this->playerCursor.getPosition().y - 1); break;}
case 2 : {this->settings->selectPieces(HOLELESS_PIECES, this->playerCursor.getPosition().y - 1); break;}
case 3 : {this->settings->selectPieces(OTHER_PIECES, this->playerCursor.getPosition().y - 1); break;}
}
}
this->playerCursor.addPosition(0, 1);
} }
} }
if (this->escReleased) { if (this->escReleased) {
if (this->playerCursor.getPosition().y == 1) { if (this->playerCursor.getPosition().y == 1) {
//TODO if (this->playerCursor.getPosition().x > 0) {
this->settings->unselectPieces(this->playerCursor.getPosition().x - 1);
this->playerCursor.removePosition(this->playerCursor.getPosition().x - 1, 1);
}
} }
else { else {
this->settings->confirmSelectedPieces();
this->menuStack->pop(); this->menuStack->pop();
} }
} }
@@ -51,21 +87,148 @@ void GamePiecesAppMenu::drawFrame() const {
if (this->playerCursor.getPosition().y == 0) { if (this->playerCursor.getPosition().y == 0) {
this->placeText(text, this->playerCursor, "PIECES DISTRIBUTION", 5.f, 15.f, sf::Vector2u{0, 0}); this->placeText(text, this->playerCursor, "PIECES DISTRIBUTION", 5.f, 15.f, sf::Vector2u{0, 0});
// draw pieces line this->drawSelectedPiecesRow(25.f);
this->drawRow(1, 35.f); this->drawRow(1, 35.f, true);
this->drawRow(2, 45.f); this->drawRow(2, 45.f, true);
} }
else { else {
// draw pieces line this->drawSelectedPiecesRow(15.f);
int firstElem = std::clamp(((int) this->playerCursor.getPosition().y) - 2, 1, MAXIMUM_PIECES_SIZE - 1);
this->drawRow(firstElem, 25.f); bool drawFromFirstElem = (this->playerCursor.getPosition().y == 1);
this->drawRow(firstElem, 35.f); bool addExtraLine = (this->settings->getMaximumPiecesSize() < MAXIMUM_PIECES_SIZE);
this->drawRow(firstElem, 45.f); int firstElem = std::clamp(((int) this->playerCursor.getPosition().y) - 2, 1, this->settings->getMaximumPiecesSize() - 2 + (addExtraLine ? 1 : 0));
this->drawRow(firstElem, 25.f, drawFromFirstElem);
this->drawRow(firstElem + 1, 35.f, drawFromFirstElem);
this->drawRow(firstElem + 2, 45.f, drawFromFirstElem);
} }
this->renderWindow->display(); this->renderWindow->display();
} }
void GamePiecesAppMenu::drawRow(int piecesSize, float yPos) const { void GamePiecesAppMenu::drawSelectedPiecesRow(float yPos) const {
//TODO sf::RectangleShape rect({(float) this->renderWindow->getSize().x, 8.f * this->settings->getWindowSizeMultiplier()});
rect.setPosition({0.f, (yPos - 4.f) * this->settings->getWindowSizeMultiplier()});
rect.setFillColor({240, 240, 240});
this->renderWindow->draw(rect);
sf::Text text(this->pressStartFont, "", this->settings->getWindowSizeMultiplier());
text.setFillColor({0, 0, 0});
int elem = (this->playerCursor.getPosition().y == 1) ? std::max(((int) this->playerCursor.getPosition().x) - 4, 0) : 0;
float xProgress = 1.f;
bool first = true;
while (true) {
if ((this->playerCursor.getPosition().y == 1) && (elem == this->playerCursor.getPosition().x)) {
this->placeText(text, {}, "|", xProgress, yPos, {});
xProgress += (1.f + (text.getGlobalBounds().size.x / this->settings->getWindowSizeMultiplier()));
}
if (elem >= this->settings->getSelectedPieces().size()) return;
const auto& [pieceType, value] = this->settings->getSelectedPieces().at(elem);
int pieceSize = getSizeOfPieces(pieceType);
if (pieceSize > 0) {
if (!(pieceSize > this->settings->getMaximumPiecesSize())) {
const Piece& piece = this->settings->getMenu().readPiecesList().lookAtPiece({pieceSize, value});
int cellSize = (8 * this->settings->getWindowSizeMultiplier()) / (piece.getLength());
sf::FloatRect piecePosition(sf::Vector2f(xProgress, yPos - 4.f) * (float) this->settings->getWindowSizeMultiplier(), sf::Vector2f(8 , 8) * (float) this->settings->getWindowSizeMultiplier());
this->drawPiece(piece, cellSize, piecePosition, false);
xProgress += (1.f + 8.f);
}
else {
this->placeText(text, {}, "UNLOADED_" + std::to_string(pieceSize), xProgress, yPos, {});
xProgress += (1.f + (text.getGlobalBounds().size.x / this->settings->getWindowSizeMultiplier()));
}
}
else {
if (!(value > this->settings->getMaximumPiecesSize())) {
this->placeText(text, {}, ((first) ? "" : " ") + getPiecesTypeName(pieceType) + "_" + std::to_string(value), xProgress, yPos, {});
xProgress += (1.f + (text.getGlobalBounds().size.x / this->settings->getWindowSizeMultiplier()));
}
else {
this->placeText(text, {}, "UNLOADED_" + std::to_string(pieceSize), xProgress, yPos, {});
xProgress += (1.f + (text.getGlobalBounds().size.x / this->settings->getWindowSizeMultiplier()));
}
}
elem++;
}
}
void GamePiecesAppMenu::drawRow(int piecesSize, float yPos, bool drawFromFirstElem) const {
if (piecesSize > this->settings->getMaximumPiecesSize()) {
sf::Text text(this->pressStartFont, "", this->settings->getWindowSizeMultiplier() * 2);
text.setOutlineThickness(this->settings->getWindowSizeMultiplier() / 2);
if (this->playerCursor.getPosition().y == (piecesSize + 1)) {
text.setOutlineColor({255, 255, 255});
}
else {
text.setOutlineColor({0, 0, 0});
}
std::string sizeString = "LOAD SIZE " + std::to_string(piecesSize) + "? ";
if (piecesSize <= 10) {
text.setFillColor({0, 255, 0});
this->placeText(text, {}, sizeString + "(LOW LOAD TIME)", 1.f, yPos, {});
}
else if (piecesSize <= 13) {
text.setFillColor({255, 255, 0});
this->placeText(text, {}, sizeString + "(MEDIUM LOAD TIME)", 1.f, yPos, {});
}
else {
text.setFillColor({255, 0, 0});
this->placeText(text, {}, sizeString + "(LONG LOAD TIME)", 1.f, yPos, {});
}
}
else {
int numberOfPieces = this->settings->getMenu().readPiecesList().getNumberOfPieces(piecesSize);
int firstElem = (drawFromFirstElem) ? -4 : std::max(((int) this->playerCursor.getPosition().x) - 7, -4);
sf::Text text(this->pressStartFont, "", this->settings->getWindowSizeMultiplier());
text.setFillColor({0, 0, 0});
text.setOutlineColor({255, 255, 255});
this->placeText(text, {}, "SIZE " + std::to_string(piecesSize), 1.f, yPos, {});
for (int i = 0; i < 7; i++) {
if (i + firstElem >= numberOfPieces) return;
if ((i + firstElem) < 0) {
switch (i + firstElem) {
case -4 : {this->placeText(text, this->playerCursor, "ALL", 10.f + (i * 10.f), yPos, sf::Vector2u{0, piecesSize + 1u}); break;}
case -3 : {this->placeText(text, this->playerCursor, "CONVEX", 10.f + (i * 10.f), yPos, sf::Vector2u{1, piecesSize + 1u}); break;}
case -2 : {this->placeText(text, this->playerCursor, "HOLELESS", 10.f + (i * 10.f), yPos, sf::Vector2u{2, piecesSize + 1u}); break;}
case -1 : {this->placeText(text, this->playerCursor, "OTHER", 10.f + (i * 10.f), yPos, sf::Vector2u{3, piecesSize + 1u}); break;}
}
}
else {
const Piece& piece = this->settings->getMenu().readPiecesList().lookAtPiece({piecesSize, firstElem + i});
int cellSize = (8 * this->settings->getWindowSizeMultiplier()) / (piece.getLength());
sf::FloatRect piecePosition(sf::Vector2f(10.f + (i * 10.f), yPos - 4.f) * (float) this->settings->getWindowSizeMultiplier(), sf::Vector2f(8 , 8) * (float) this->settings->getWindowSizeMultiplier());
this->drawPiece(piece, cellSize, piecePosition, this->playerCursor.getPosition() == sf::Vector2u{i + firstElem + 4u, piecesSize + 1u});
}
}
}
}
void GamePiecesAppMenu::drawPiece(const Piece& piece, int cellSize, const sf::FloatRect& piecePosition, bool selected) const {
sf::RectangleShape rect(piecePosition.size);
rect.setPosition(piecePosition.position);
rect.setFillColor({180, 180, 180});
if (selected) {
rect.setOutlineColor({0, 0, 0});
rect.setOutlineThickness(this->settings->getWindowSizeMultiplier() / 2);
}
this->renderWindow->draw(rect);
sf::RectangleShape cell(sf::Vector2f(cellSize, cellSize));
cell.setFillColor(this->getColorOfBlock(piece.getBlockType(), 0));
for (const Position& cellPosition : piece.getPositions()) {
cell.setPosition(sf::Vector2f(piecePosition.position.x + (cellPosition.x * cellSize),
piecePosition.position.y + ((piece.getLength() - cellPosition.y - 1) * cellSize) ));
this->renderWindow->draw(cell);
}
} }

View File

@@ -5,6 +5,7 @@
#include <stack> #include <stack>
#include <memory> #include <memory>
#include <vector>
#include <SFML/Graphics.hpp> #include <SFML/Graphics.hpp>
@@ -20,5 +21,9 @@ class GamePiecesAppMenu : public AppMenu {
void drawFrame() const override; void drawFrame() const override;
private: private:
void drawRow(int piecesSize, float yPos) const; void drawSelectedPiecesRow(float yPos) const;
void drawRow(int piecesSize, float yPos, bool drawFromFirstElem) const;
void drawPiece(const Piece& piece, int cellSize, const sf::FloatRect& pos, bool selected) const;
}; };

View File

@@ -120,8 +120,6 @@ void GamePlayingAppMenu::drawFrame() const {
} }
} }
// end coutdown
if (drawActivePiece) { if (drawActivePiece) {
// ghost piece // ghost piece
sf::Color ghostColor = this->getColorOfBlock(this->game.getActivePiece()->getBlockType(), 100); sf::Color ghostColor = this->getColorOfBlock(this->game.getActivePiece()->getBlockType(), 100);
@@ -271,13 +269,6 @@ void GamePlayingAppMenu::drawFrame() const {
this->renderWindow->display(); this->renderWindow->display();
} }
sf::Color GamePlayingAppMenu::getColorOfBlock(Block block, int luminosityShift) const {
Color rgbColor = BLOCKS_COLOR[block];
return sf::Color(std::clamp(rgbColor.red + luminosityShift, 0, 255),
std::clamp(rgbColor.green + luminosityShift, 0, 255),
std::clamp(rgbColor.blue + luminosityShift, 0, 255));
}
sf::Vector2f GamePlayingAppMenu::getBoardBlockPosition(int x, int y) const { sf::Vector2f GamePlayingAppMenu::getBoardBlockPosition(int x, int y) const {
return sf::Vector2f(this->boardPosition.position.x + (x * this->cellSizeZoom), return sf::Vector2f(this->boardPosition.position.x + (x * this->cellSizeZoom),
this->boardPosition.position.y + ((this->game.getBoard().getBaseHeight() + 9 - y) * this->cellSizeZoom)); this->boardPosition.position.y + ((this->game.getBoard().getBaseHeight() + 9 - y) * this->cellSizeZoom));

View File

@@ -28,7 +28,5 @@ class GamePlayingAppMenu : public AppMenu {
void drawFrame() const override; void drawFrame() const override;
sf::Color getColorOfBlock(Block block, int luminosityShift) const;
sf::Vector2f getBoardBlockPosition(int x, int y) const; sf::Vector2f getBoardBlockPosition(int x, int y) const;
}; };

View File

@@ -10,12 +10,13 @@
InfoAppMenu::InfoAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow) : InfoAppMenu::InfoAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow) :
AppMenu(menuStack, settings, renderWindow), AppMenu(menuStack, settings, renderWindow),
playerCursor({4}), playerCursor({INFO_SECTIONS_COUNT}),
sectionsName( sectionsName(
"< ABOUT >", "< ABOUT >",
"< PIECES TYPES >",
"< 0 DEGREES ROTATIONS >",
"< ROTATION SYSTEM >", "< ROTATION SYSTEM >",
"< SCORING >", "< SCORING >"
"< 0 DEGREES ROTATIONS >"
), ),
sectionsContent( sectionsContent(
"This game is written in C++,\n" "This game is written in C++,\n"
@@ -27,6 +28,25 @@ InfoAppMenu::InfoAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<S
"to them in any ways.\n" "to them in any ways.\n"
"Current version: beta.", "Current version: beta.",
"There is multiple pieces type in\n"
"the selection screen. Use theses\n"
"categories for size of at least 7.\n"
"Convex, Holeless and Others are\n"
"all mutually exclusive.\n"
"Others have holes inside them, and\n"
"Convex are presumably easier to\n"
"play with than Holeless.",
"This games introduces 0 degrees\n"
"rotations, which work by simpling\n"
"moving the piece down and kicking\n"
"it as is, allowing for new kinds\n"
"of kicks.\n"
"As a leniency mechanic, when a\n"
"piece spawns it will automatically\n"
"try a 0 degrees rotations if it\n"
"spawned inside a wall.",
"This game uses its own\n" "This game uses its own\n"
"Rotation Sytem, called AutoRS.\n" "Rotation Sytem, called AutoRS.\n"
"The rotation center is always the\n" "The rotation center is always the\n"
@@ -46,25 +66,48 @@ InfoAppMenu::InfoAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<S
"and doubles the score gained.\n" "and doubles the score gained.\n"
"A spin is detected when the piece is\n" "A spin is detected when the piece is\n"
"locked in place, a mini-spin simply\n" "locked in place, a mini-spin simply\n"
"when the last move was a kick.", "when the last move was a kick."
),
sectionNameText(this->createText()),
sectionContentText(this->createText()),
renderTexture(this->renderWindow->getSize()),
sprite(this->renderTexture.getTexture()) {
"This games introduces 0 degrees\n" this->setTextOutline(this->sectionNameText, true);
"rotations, which work by simpling\n"
"moving the piece down and kicking\n"
"it as is, allowing for new kinds\n"
"of kicks.\n"
"As a leniency mechanic, when a\n"
"piece spawns it will automatically\n"
"try a 0 degrees rotations if it\n"
"spawned inside a wall."
) {
this->sectionContentText.setLineSpacing((float) this->settings->getWindowSizeMultiplier() / 8);
this->sectionNameText.setString(this->sectionsName[this->playerCursor.getPosition().x]);
this->setTitlePosition(this->sectionNameText, 10.f);
this->sectionContentText.setString(this->sectionsContent[this->playerCursor.getPosition().x]);
this->setTextPosition(this->sectionContentText, 5.f, 30.f);
this->renderTexture.clear(sf::Color(200, 200, 200));
this->renderTexture.draw(this->sectionNameText);
this->renderTexture.draw(this->sectionContentText);
this->renderTexture.display();
this->sprite.setTexture(this->renderTexture.getTexture());
} }
void InfoAppMenu::computeFrame() { void InfoAppMenu::computeFrame() {
this->updateMetaBinds(); this->updateMetaBinds();
this->playerCursor.updatePosition(); this->playerCursor.updatePosition();
if (this->playerCursor.movedLeft() || this->playerCursor.movedRight()) {
this->sectionNameText.setString(this->sectionsName[this->playerCursor.getPosition().x]);
this->setTitlePosition(this->sectionNameText, 10.f);
this->sectionContentText.setString(this->sectionsContent[this->playerCursor.getPosition().x]);
this->setTextPosition(this->sectionContentText, 5.f, 30.f);
this->renderTexture.clear(sf::Color(200, 200, 200));
this->renderTexture.draw(this->sectionNameText);
this->renderTexture.draw(this->sectionContentText);
this->renderTexture.display();
this->sprite.setTexture(this->renderTexture.getTexture());
}
if (this->escReleased) { if (this->escReleased) {
this->menuStack->pop(); this->menuStack->pop();
} }
@@ -73,15 +116,7 @@ void InfoAppMenu::computeFrame() {
void InfoAppMenu::drawFrame() const { void InfoAppMenu::drawFrame() const {
this->renderWindow->clear(sf::Color(200, 200, 200)); this->renderWindow->clear(sf::Color(200, 200, 200));
sf::Text text(this->pressStartFont, "", this->settings->getWindowSizeMultiplier() * 2); this->renderWindow->draw(sprite);
text.setFillColor(sf::Color(0, 0, 0));
text.setOutlineColor(sf::Color(255, 255, 255));
this->placeTitle(text, this->playerCursor, this->sectionsName[this->playerCursor.getPosition().x], 10.f, this->playerCursor.getPosition());
text.setLineSpacing((float) this->settings->getWindowSizeMultiplier() / 8);
text.setOutlineThickness(0);
this->placeText(text, {}, this->sectionsContent[this->playerCursor.getPosition().x], 5.f, 30.f, {});
this->renderWindow->display(); this->renderWindow->display();
} }

View File

@@ -7,12 +7,18 @@
#include <memory> #include <memory>
#include <SFML/Graphics.hpp> #include <SFML/Graphics.hpp>
static const int INFO_SECTIONS_COUNT = 5;
class InfoAppMenu : public AppMenu { class InfoAppMenu : public AppMenu {
private: private:
PlayerCursor playerCursor; PlayerCursor playerCursor;
sf::String sectionsName[4]; sf::String sectionsName[INFO_SECTIONS_COUNT];
sf::String sectionsContent[4]; sf::String sectionsContent[INFO_SECTIONS_COUNT];
sf::Text sectionNameText;
sf::Text sectionContentText;
sf::RenderTexture renderTexture;
sf::Sprite sprite;
public: public:
InfoAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow); InfoAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow);

View File

@@ -0,0 +1,75 @@
#include "StartUpAppMenu.h"
#include "AppMenu.h"
#include "MainAppMenu.h"
#include "../PlayerCursor.h"
#include <stack>
#include <memory>
#include <algorithm>
#include <SFML/Graphics.hpp>
StartUpAppMenu::StartUpAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow) :
AppMenu(menuStack, settings, renderWindow),
playerCursor({MAXIMUM_PIECES_SIZE + 1}) {
this->playerCursor.goToPosition({(unsigned int) std::clamp(this->settings->getMaximumPiecesSize(), MINIMUM_PIECES_SIZE, MAXIMUM_PIECES_SIZE), 0u});
}
void StartUpAppMenu::computeFrame() {
this->updateMetaBinds();
this->playerCursor.updatePosition();
if (this->playerCursor.getPosition().x < MINIMUM_PIECES_SIZE) {
if (this->playerCursor.movedLeft()) {
this->playerCursor.goToPosition({MAXIMUM_PIECES_SIZE, 0});
}
else {
this->playerCursor.goToPosition({MINIMUM_PIECES_SIZE, 0});
}
}
if (this->enterReleased) {
this->settings->loadSettingsFromFile(true, {this->playerCursor.getPosition().x});
this->menuStack->pop();
if (this->settings->hasLoadedPieces()) {
this->menuStack->push(std::make_shared<MainAppMenu>(this->menuStack, this->settings, this->renderWindow));
}
else {
std::cout << "ERROR: COULD NOT LOAD PIECES" << std::endl;
std::cout << "ARGUMENT WAS: " << this->playerCursor.getPosition().x << std::endl;
}
}
else if (this->escReleased) {
this->menuStack->pop();
}
}
void StartUpAppMenu::drawFrame() const {
this->renderWindow->clear(sf::Color(200, 200, 200));
sf::Text text(this->pressStartFont, "", this->settings->getWindowSizeMultiplier() * 2);
text.setFillColor(sf::Color(0, 0, 0));
text.setOutlineColor(sf::Color(255, 255, 255));
this->placeTitle(text, {}, "SELECT THE LOADED PIECES MAXIMUM SIZE", 10.f, {});
this->placeTitle(text, this->playerCursor, "< " + std::to_string(this->playerCursor.getPosition().x) + " >", 25.f, this->playerCursor.getPosition());
text.setOutlineColor({0, 0, 0});
if (this->playerCursor.getPosition().x <= 10) {
text.setFillColor({0, 255, 0});
this->placeTitle(text, {}, "LOW LOAD TIME", 40.f, {});
}
else if (this->playerCursor.getPosition().x <= 13) {
text.setFillColor({255, 255, 0});
this->placeTitle(text, {}, "MEDIUM LOAD TIME", 40.f, {});
}
else {
text.setFillColor({255, 0, 0});
this->placeTitle(text, {}, "LONG LOAD TIME", 40.f, {});
}
this->renderWindow->display();
}

View File

@@ -0,0 +1,21 @@
#pragma once
#include "AppMenu.h"
#include "../PlayerCursor.h"
#include <stack>
#include <memory>
#include <SFML/Graphics.hpp>
class StartUpAppMenu : public AppMenu {
private:
PlayerCursor playerCursor;
public:
StartUpAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow);
void computeFrame() override;
void drawFrame() const override;
};

View File

@@ -1,7 +1,7 @@
#include "GraphApp.h" #include "GraphApp.h"
#include "AppMenus/AppMenu.h" #include "AppMenus/AppMenu.h"
#include "AppMenus/MainAppMenu.h" #include "AppMenus/StartUpAppMenu.h"
#include "Settings.h" #include "Settings.h"
#include <stack> #include <stack>
@@ -12,14 +12,14 @@ static const double TIME_BETWEEN_FRAMES = (1000.f / FRAMES_PER_SECOND);
GraphApp::GraphApp() { GraphApp::GraphApp() {
this->settings = std::make_shared<Settings>(); this->settings = std::make_shared<Settings>(false);
this->menuStack = std::make_shared<MenuStack>(); this->menuStack = std::make_shared<MenuStack>();
this->renderWindow = std::make_shared<sf::RenderWindow>(); this->renderWindow = std::make_shared<sf::RenderWindow>();
} }
void GraphApp::run() { void GraphApp::run() {
this->settings->changeVideoMode(*this->renderWindow); this->settings->changeVideoMode(*this->renderWindow);
this->menuStack->push(std::make_shared<MainAppMenu>(this->menuStack, this->settings, this->renderWindow)); this->menuStack->push(std::make_shared<StartUpAppMenu>(this->menuStack, this->settings, this->renderWindow));
bool quit = false; bool quit = false;
double timeAtNextFrame = 0; double timeAtNextFrame = 0;

View File

@@ -1,5 +1,7 @@
#pragma once #pragma once
#include <string>
enum PiecesType { enum PiecesType {
CONVEX_PIECES, CONVEX_PIECES,
@@ -19,3 +21,15 @@ inline int getSizeOfPieces(PiecesType type) {
inline PiecesType createSinglePieceType(int size) { inline PiecesType createSinglePieceType(int size) {
return PiecesType(SINGLE_PIECE + size - 1); return PiecesType(SINGLE_PIECE + size - 1);
} }
inline std::string getPiecesTypeName(PiecesType piecesType) {
static const std::string PIECES_TYPE_NAME[] = {
"CONVEX",
"HOLELESS",
"OTHER",
"ALL",
"SINGLE"
};
return PIECES_TYPE_NAME[piecesType];
}

View File

@@ -42,6 +42,13 @@ void PlayerCursor::updatePosition() {
} }
} }
bool PlayerCursor::moved() const {
return (this->movedLeft()
|| this->movedRight()
|| this->movedUp()
|| this->movedDown());
}
bool PlayerCursor::movedLeft() const { bool PlayerCursor::movedLeft() const {
return this->shouldMove(this->leftDAS); return this->shouldMove(this->leftDAS);
} }
@@ -71,7 +78,7 @@ bool PlayerCursor::addPosition(unsigned int x, unsigned int y) {
if (x > this->rows.at(y)) return false; if (x > this->rows.at(y)) return false;
this->rows.at(y)++; this->rows.at(y)++;
if (x <= this->position.x) { if ((y == this->position.y) && (x <= this->position.x)) {
this->position.x++; this->position.x++;
} }
return true; return true;
@@ -82,7 +89,7 @@ bool PlayerCursor::removePosition(unsigned int x, unsigned int y) {
if (x >= this->rows.at(y)) return false; if (x >= this->rows.at(y)) return false;
this->rows.at(y)--; this->rows.at(y)--;
if (x < this->position.x) { if ((y == this->position.y) && (x < this->position.x)) {
this->position.x--; this->position.x--;
} }
return true; return true;
@@ -115,7 +122,7 @@ const sf::Vector2u& PlayerCursor::getPosition() const {
bool PlayerCursor::shouldMove(int DAS) const { bool PlayerCursor::shouldMove(int DAS) const {
return (DAS == 1 return (DAS == 1
|| (DAS > MENU_DAS && (DAS % 5) == 0) || (DAS > MENU_DAS && (DAS % 5) == 0)
|| (DAS > (FRAMES_PER_SECOND * 2))); || (DAS > (MENU_DAS * 4)));
} }
void PlayerCursor::moveLeft() { void PlayerCursor::moveLeft() {

View File

@@ -18,6 +18,8 @@ class PlayerCursor {
void updatePosition(); void updatePosition();
bool moved() const;
bool movedLeft() const; bool movedLeft() const;
bool movedRight() const; bool movedRight() const;

View File

@@ -2,7 +2,10 @@
#include "../Core/Menu.h" #include "../Core/Menu.h"
#include "Keybinds.h" #include "Keybinds.h"
#include "PiecesType.h"
#include <vector>
#include <optional>
#include <fstream> #include <fstream>
#include <algorithm> #include <algorithm>
#include <SFML/Graphics.hpp> #include <SFML/Graphics.hpp>
@@ -11,26 +14,63 @@ 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 START_TIMER_MAX = 4;
static const int DISTRIBUTION_MAX = 10; static const int DISTRIBUTION_MAX = 20;
Settings::Settings() { Settings::Settings(bool loadPieces) {
for (int i = 1; i <= MAXIMUM_PIECES_SIZE; i++) { this->keybinds.clear();
this->menu.getPiecesList().loadPieces(i);
}
this->keybinds.reserve(NUMBER_OF_KEYBINDS); this->keybinds.reserve(NUMBER_OF_KEYBINDS);
for (int i = 0; i < NUMBER_OF_KEYBINDS; i++) { for (int i = 0; i < NUMBER_OF_KEYBINDS; i++) {
this->keybinds.emplace_back(i); this->keybinds.emplace_back(i);
} }
this->loadSettingsFromFile(); this->loadSettingsFromFile(loadPieces, {});
} }
void Settings::loadSettingsFromFile() { void Settings::loadPieces(int maximumPiecesSizeRequest) {
if (maximumPiecesSizeRequest < MINIMUM_PIECES_SIZE) {
maximumPiecesSizeRequest = MINIMUM_PIECES_SIZE;
}
else if (maximumPiecesSizeRequest > MAXIMUM_PIECES_SIZE) {
maximumPiecesSizeRequest = MAXIMUM_PIECES_SIZE;
}
bool succeeded = true;
int i = 1;
while (succeeded && (i <= maximumPiecesSizeRequest)) {
succeeded = this->menu.getPiecesList().loadPieces(i);
i++;
}
if (succeeded) {
this->maximumPiecesSize = maximumPiecesSizeRequest;
}
this->loadedPieces = succeeded;
}
void Settings::loadSettingsFromFile(bool loadPieces, std::optional<int> maximumPiecesSizeRequest) {
std::ifstream settingsFile("data/config/settings.bin", std::ios::binary); std::ifstream settingsFile("data/config/settings.bin", std::ios::binary);
char byte; char byte;
// file format version
settingsFile.get(byte);
// maximum pieces size
settingsFile.get(byte);
this->maximumPiecesSize = byte;
if (loadPieces) {
if (maximumPiecesSizeRequest.has_value()) {
this->loadPieces(maximumPiecesSizeRequest.value());
}
else {
this->loadPieces(byte);
}
}
else {
this->loadedPieces = false;
}
// keybind layout // keybind layout
settingsFile.get(byte); settingsFile.get(byte);
this->chosenKeybinds = byte; this->chosenKeybinds = byte;
@@ -67,6 +107,7 @@ void Settings::loadSettingsFromFile() {
settingsFile.get(byte); settingsFile.get(byte);
this->menu.setBoardHeight(byte); this->menu.setBoardHeight(byte);
if (this->loadedPieces) {
// piece distribution // piece distribution
settingsFile.get(byte); settingsFile.get(byte);
this->menu.getPiecesList().setDistributionMode(DistributionMode(byte)); this->menu.getPiecesList().setDistributionMode(DistributionMode(byte));
@@ -81,23 +122,53 @@ void Settings::loadSettingsFromFile() {
// selected pieces // selected pieces
char pieceType; char pieceType;
char pieceValue; char pieceSize;
char lowByte;
char midByte;
char highByte;
this->selectedPieces.clear(); this->selectedPieces.clear();
while (settingsFile.get(pieceType)) { while (settingsFile.get(pieceType)) {
if (settingsFile.eof()) break; if (settingsFile.eof()) break;
settingsFile.get(pieceValue); if (getSizeOfPieces(PiecesType(pieceType)) == 0) {
this->selectedPieces.push_back({PiecesType(pieceType), pieceValue}); settingsFile.get(pieceSize);
this->selectedPieces.emplace_back(PiecesType(pieceType), pieceSize);
}
else {
settingsFile.get(lowByte);
settingsFile.get(midByte);
settingsFile.get(highByte);
int pieceNumber = ((unsigned char) lowByte) + ((unsigned char) midByte << 8) + ((unsigned char) highByte << 16);
this->selectedPieces.emplace_back(PiecesType(pieceType), pieceNumber);
}
} }
this->confirmSelectedPieces(); this->confirmSelectedPieces();
}
else {
this->distributions.clear();
this->selectedPieces.clear();
}
} }
void Settings::saveSettingsToFile() const { void Settings::saveSettingsToFile() const {
if (!this->loadedPieces) return;
this->keybinds.at(CUSTOMIZABLE_KEYBINDS).saveKeybindsToFile(); this->keybinds.at(CUSTOMIZABLE_KEYBINDS).saveKeybindsToFile();
std::ofstream settingsFile("data/config/settings.bin", std::ios::trunc | std::ios::binary); std::ofstream settingsFile("data/config/settings.bin", std::ios::trunc | std::ios::binary);
char byte; char byte;
// file format version
byte = CURRENT_FILE_FORMAT_VERSION;
settingsFile.write(&byte, 1);
// maximum pieces size
byte = this->maximumPiecesSize;
settingsFile.write(&byte, 1);
// keybind layout // keybind layout
byte = this->chosenKeybinds; byte = this->chosenKeybinds;
settingsFile.write(&byte, 1); settingsFile.write(&byte, 1);
@@ -147,9 +218,19 @@ void Settings::saveSettingsToFile() const {
for (const auto& [type, value] : this->selectedPieces) { for (const auto& [type, value] : this->selectedPieces) {
byte = type; byte = type;
settingsFile.write(&byte, 1); settingsFile.write(&byte, 1);
if (getSizeOfPieces(type) == 0) {
byte = value; byte = value;
settingsFile.write(&byte, 1); settingsFile.write(&byte, 1);
} }
else {
int number = value;
for (int i = 0; i < 3; i++) {
byte = (number % 256);
settingsFile.write(&byte, 1);
number = (number >> 8);
}
}
}
} }
bool Settings::selectNextKeybinds() { bool Settings::selectNextKeybinds() {
@@ -218,39 +299,55 @@ void Settings::setGamemode(Gamemode gamemode) {
} }
void Settings::selectPieces(PiecesType type, int value) { void Settings::selectPieces(PiecesType type, int value) {
if (!this->loadedPieces) return;
this->selectedPieces.emplace_back(type, value); this->selectedPieces.emplace_back(type, value);
} }
void Settings::unselectPieces(int index) { void Settings::unselectPieces(int index) {
if (!this->loadedPieces) return;
if (index >= this->selectedPieces.size()) return; if (index >= this->selectedPieces.size()) return;
this->selectedPieces.erase(this->selectedPieces.begin() + index); this->selectedPieces.erase(this->selectedPieces.begin() + index);
} }
void Settings::confirmSelectedPieces() { void Settings::confirmSelectedPieces() {
if (!this->loadedPieces) return;
this->menu.getPiecesList().unselectAll(); this->menu.getPiecesList().unselectAll();
bool selectedNone = true;
for (const auto& [type, value] : this->selectedPieces) { for (const auto& [type, value] : this->selectedPieces) {
int size = getSizeOfPieces(type); int size = getSizeOfPieces(type);
if (size == 0) { if (size == 0) {
if (!(value > this->maximumPiecesSize)) {
switch (type) { switch (type) {
case CONVEX_PIECES : {this->menu.getPiecesList().selectConvexPieces(value); break;} case CONVEX_PIECES : {this->menu.getPiecesList().selectConvexPieces(value); break;}
case HOLELESS_PIECES : {this->menu.getPiecesList().selectHolelessPieces(value); break;} case HOLELESS_PIECES : {this->menu.getPiecesList().selectHolelessPieces(value); break;}
case OTHER_PIECES : {this->menu.getPiecesList().selectOtherPieces(value); break;} case OTHER_PIECES : {this->menu.getPiecesList().selectOtherPieces(value); break;}
case ALL_PIECES : {this->menu.getPiecesList().selectAllPieces(value); break;} case ALL_PIECES : {this->menu.getPiecesList().selectAllPieces(value); break;}
} }
selectedNone = false;
}
} }
else { else {
if (size > MAXIMUM_PIECES_SIZE) return; if (!(getSizeOfPieces(type) > this->maximumPiecesSize)) {
this->menu.getPiecesList().selectPiece(size, value); this->menu.getPiecesList().selectPiece(size, value);
selectedNone = false;
} }
} }
}
if (selectedNone) {
this->selectedPieces.push_back(DEFAULT_SELECTION);
this->confirmSelectedPieces();
}
} }
bool Settings::increaseDistribution(int size) { bool Settings::increaseDistribution(int size) {
if (size < 1 || size > MAXIMUM_PIECES_SIZE) return false; if (!this->loadedPieces) return false;
if (size < 1 || size > this->maximumPiecesSize) return false;
if (this->distributions.at(size) < DISTRIBUTION_MAX) { if (this->distributions.at(size) < DISTRIBUTION_MAX) {
this->distributions.at(size)++; this->distributions.at(size)++;
@@ -260,7 +357,8 @@ bool Settings::increaseDistribution(int size) {
} }
bool Settings::decreaseDistribution(int size) { bool Settings::decreaseDistribution(int size) {
if (size < 1 || size > MAXIMUM_PIECES_SIZE) return false; if (!this->loadedPieces) return false;
if (size < 1 || size > this->maximumPiecesSize) return false;
if (this->distributions.at(size) > 0) { if (this->distributions.at(size) > 0) {
this->distributions.at(size)--; this->distributions.at(size)--;
@@ -270,8 +368,10 @@ bool Settings::decreaseDistribution(int size) {
} }
void Settings::confirmDistribution() { void Settings::confirmDistribution() {
if (!this->loadedPieces) return;
for (int i = 1; i <= 15; i++) { for (int i = 1; i <= 15; i++) {
this->menu.getPiecesList().changeCustomDistribution(i, (double) this->distributions.at(i) / (DISTRIBUTION_MAX + 0.001)); this->menu.getPiecesList().changeCustomDistribution(i, (double) 1 / (this->distributions.at(i) + 0.001));
} }
} }
@@ -283,6 +383,14 @@ Keybinds& Settings::getKeybinds() {
return this->keybinds.at(this->chosenKeybinds); return this->keybinds.at(this->chosenKeybinds);
} }
int Settings::getMaximumPiecesSize() const {
return this->maximumPiecesSize;
}
bool Settings::hasLoadedPieces() const {
return this->loadedPieces;
}
int Settings::getKeybindsLayout() const { int Settings::getKeybindsLayout() const {
return this->chosenKeybinds; return this->chosenKeybinds;
} }

View File

@@ -4,23 +4,33 @@
#include "Keybinds.h" #include "Keybinds.h"
#include "PiecesType.h" #include "PiecesType.h"
#include <SFML/Graphics.hpp>
#include <vector> #include <vector>
#include <optional>
#include <SFML/Graphics.hpp>
static const int CURRENT_FILE_FORMAT_VERSION = 11;
static const int MAXIMUM_BOARD_WIDTH = 40; static const int MAXIMUM_BOARD_WIDTH = 40;
static const int MAXIMUM_BOARD_HEIGHT = 40; static const int MAXIMUM_BOARD_HEIGHT = 40;
//#define __JMINOS_RELEASE__ static const int RELEASE_PIECES_SIZE = 15;
#ifdef __JMINOS_RELEASE__ static const int DEBUG_PIECES_SIZE = 10;
static const int MAXIMUM_PIECES_SIZE = 15;
static const int MINIMUM_PIECES_SIZE = 4;
#ifdef NDEBUG
static const int MAXIMUM_PIECES_SIZE = RELEASE_PIECES_SIZE;
#else #else
static const int MAXIMUM_PIECES_SIZE = 10; static const int MAXIMUM_PIECES_SIZE = DEBUG_PIECES_SIZE;
#endif #endif
static const std::pair<PiecesType, int> DEFAULT_SELECTION = {ALL_PIECES, MINIMUM_PIECES_SIZE};
class Settings { class Settings {
private: private:
Menu menu; Menu menu;
int maximumPiecesSize;
bool loadedPieces;
std::vector<Keybinds> keybinds; std::vector<Keybinds> keybinds;
int chosenKeybinds; int chosenKeybinds;
int windowSizeMode; int windowSizeMode;
@@ -30,9 +40,11 @@ class Settings {
std::vector<int> distributions; std::vector<int> distributions;
public: public:
Settings(); Settings(bool loadPieces);
void loadSettingsFromFile(); void loadPieces(int maximumPiecesSizeRequest);
void loadSettingsFromFile(bool loadPieces, std::optional<int> maximumPiecesSizeRequest);
void saveSettingsToFile() const; void saveSettingsToFile() const;
@@ -70,6 +82,10 @@ class Settings {
Keybinds& getKeybinds(); Keybinds& getKeybinds();
int getMaximumPiecesSize() const;
bool hasLoadedPieces() const;
int getKeybindsLayout() const; int getKeybindsLayout() const;
Gamemode getGamemode() const; Gamemode getGamemode() const;

View File

@@ -15,17 +15,49 @@ int main() {
PiecesFiles pf; PiecesFiles pf;
for (int i = 1; i <= MAXIMUM_PIECES_SIZE; i++) { for (int i = 1; i <= MAXIMUM_PIECES_SIZE; i++) {
if (!std::filesystem::exists("data/pieces/" + std::to_string(i) + "minos.bin")) { if (!std::filesystem::exists("data/pieces/" + std::to_string(i) + "minos.bin")) {
std::cout << "pieces files for size " << i << " not found, generating..." << std::endl; #ifdef NDEBUG
std::cout << "IMPORTANT: You are currently in release mode, if you do not wish to generate big pieces (can take several minutes), type 'xmake f -m debug'." << std::endl;
#endif
std::cout << "INFO: Pieces files for size " << i << " not found, generating..." << std::endl;
pf.savePieces(i); pf.savePieces(i);
} }
} }
#ifndef NDEBUG
std::cout << "IMPORTANT: you are currently in debug mode, if you wish to use bigger pieces, type 'xmake f -m release'." << std::endl;
bool everythingGenerated = true;
for (int i = DEBUG_PIECES_SIZE; i <= RELEASE_PIECES_SIZE; i++) {
if (!std::filesystem::exists("data/pieces/" + std::to_string(i) + "minos.bin")) {
everythingGenerated = false;
}
}
if (!everythingGenerated) {
std::cout << "NOTE : you do not have all pieces generated, generating can take several minutes." << std::endl;
}
#endif
if (!std::filesystem::exists("data/config/settings.bin")) { if (!std::filesystem::exists("data/config/settings.bin")) {
std::cout << "settings file not found, generating..." << std::endl; std::cout << "INFO: Settings file not found, generating..." << std::endl;
resetSettingsFile(); resetSettingsFile();
} }
else {
std::ifstream settingsFile("data/config/settings.bin", std::ios::binary);
char byte;
settingsFile.get(byte);
if ((unsigned char) byte < CURRENT_FILE_FORMAT_VERSION) {
std::cout << "INFO: Files format changed, regenerating..." << std::endl;
resetSettingsFile();
for (int i = 0; i < NUMBER_OF_KEYBINDS; i++) {
resetKeybindFile(i);
}
}
}
for (int i = 0; i < NUMBER_OF_KEYBINDS; i++) { for (int i = 0; i < NUMBER_OF_KEYBINDS; i++) {
if (!std::filesystem::exists("data/config/keybinds/layout" + std::to_string(i) + ".bin")) { if (!std::filesystem::exists("data/config/keybinds/layout" + std::to_string(i) + ".bin")) {
std::cout << "keybind file n°" << (i + 1) << "/" << NUMBER_OF_KEYBINDS << " not found, generating..." << std::endl; std::cout << "INFO: Keybind file n°" << (i + 1) << "/" << NUMBER_OF_KEYBINDS << " not found, generating..." << std::endl;
resetKeybindFile(i); resetKeybindFile(i);
} }
} }
@@ -43,6 +75,14 @@ void resetSettingsFile() {
Menu menu; Menu menu;
// file format version
byte = CURRENT_FILE_FORMAT_VERSION;
settingsFile.write(&byte, 1);
// maximum pieces size
byte = MINIMUM_PIECES_SIZE;
settingsFile.write(&byte, 1);
// keybind layout // keybind layout
byte = 0; byte = 0;
settingsFile.write(&byte, 1); settingsFile.write(&byte, 1);

View File

@@ -91,7 +91,7 @@ bool PiecesFiles::loadPieces(int polyominoSize, std::vector<Piece>& pieces, std:
char positionByte; char positionByte;
for (int i = 0; i < polyominoSize; i++) { for (int i = 0; i < polyominoSize; i++) {
piecesFile.get(positionByte); piecesFile.get(positionByte);
int x = (positionByte & xMask) >> 4; int x = ((unsigned char) positionByte & xMask) >> 4;
int y = positionByte & yMask; int y = positionByte & yMask;
piecePositions.insert(Position{x, y}); piecePositions.insert(Position{x, y});
} }