Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e0de2b5f90 | |||
| df7c9bd211 | |||
| f883bd5bab | |||
| 0f026635f6 | |||
| be071bd606 | |||
| 3320545465 | |||
| d1646d0fb5 | |||
| 3d74ef7cd5 | |||
| 88cb44c5fe | |||
| 009ed8edc3 | |||
| b2567844fc | |||
| de8a5e6e34 | |||
| c168cd68d7 | |||
| 321271b748 | |||
| c601424481 | |||
| fd9fd4586a | |||
| 8a4c4201fe | |||
| c08cfc2255 | |||
| 38008e00bc | |||
| 507bc9cc86 | |||
| e721a71894 | |||
| 1781b85332 | |||
| 92b58c4b98 | |||
| 8635d4b853 | |||
| 9780a36af4 | |||
| 6b16abda6a | |||
| 30dd323e22 | |||
| d87ddcdc22 | |||
| 8aaced68d0 | |||
| be6c8d9f77 | |||
| d9ccecfdd8 | |||
| 02bab6ed87 | |||
| 0e17996c35 | |||
| ea8478275b | |||
| 29177bc94d | |||
| c25abec6ba |
5
.gitignore
vendored
@@ -11,6 +11,9 @@ build/
|
||||
# personnal documentation
|
||||
doc/*.txt
|
||||
doc/*.violet.html
|
||||
doc/mockups/*
|
||||
|
||||
# pieces files
|
||||
# data files
|
||||
data/pieces/*.bin
|
||||
data/config/*.bin
|
||||
data/config/keybinds/*.bin
|
||||
|
||||
10
.vscode/c_cpp_properties.json
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"configurations": [
|
||||
{
|
||||
"name": "jminos",
|
||||
"cppStandard": "c++20",
|
||||
"compileCommands": ".vscode/compile_commands.json"
|
||||
}
|
||||
],
|
||||
"version": 4
|
||||
}
|
||||
60
README.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# jminos
|
||||
|
||||
Modern stacker game with every polyominos from size 1 to 15, made in C++ with [SFML 3](https://www.sfml-dev.org/)!
|
||||
|
||||
## Download
|
||||
|
||||
// to bo included when release version is done //
|
||||
|
||||
This game has been tested on and provides executables for Windows 11 and Linux under Ubuntu only.
|
||||
If your OS isn't compactible with either of theses two, you can try [manually building the project](#manual-build-and-run).
|
||||
|
||||
## How to play
|
||||
|
||||
### General
|
||||
|
||||
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.
|
||||
You will find some more infos about the Rotation System and the scoring 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/)!
|
||||
|
||||
### Available gamemodes
|
||||
|
||||
- SPRINT : clear 40 lines as fast as possible!
|
||||
- MARATHON : clear 200 lines with increasing gravity!
|
||||
- ULTRA : scores as much as possible in only 2 minutes!
|
||||
- MASTER : clear 200 lines at levels higher than maximum gravity!
|
||||
- ZEN : practice indefinitely in this mode with no gravity!
|
||||
- ??? : still to do
|
||||
|
||||
## Manual build
|
||||
|
||||
This project uses xmake for compiling, xmake is cross-platform and works in most OS, xmake also automatically install supported librairies.
|
||||
To be able to build this project, you need to [have xmake installed](https://xmake.io) and have a compiler with C++20 compatibility.
|
||||
|
||||
### Build the project
|
||||
|
||||
``cd jminos``
|
||||
|
||||
``xmake``
|
||||
|
||||
If you need to change the toolchain (for example using gcc):
|
||||
``xmake f --toolchain=gcc``
|
||||
|
||||
### Run the project
|
||||
|
||||
``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:
|
||||
``xmake run text``
|
||||
|
||||
### Create a release version (xmake packaging ???)
|
||||
|
||||
## Credits
|
||||
|
||||
Library used: [SFML 3](https://www.sfml-dev.org/).
|
||||
Font used: [Press Start](https://www.zone38.net/font/#pressstart).
|
||||
|
||||
Inspired by other modern stacker games such as Techmino, jstris, tetr.io, etc.
|
||||
This game isn't affiliated with any of them.
|
||||
0
data/config/.gitkeep
Normal file
0
data/config/keybinds/.gitkeep
Normal file
17
data/fonts/pressstart/license.txt
Normal file
@@ -0,0 +1,17 @@
|
||||
Thanks for downloading one of codeman38's retro video game fonts, as seen on Memepool, BoingBoing, and all around the blogosphere.
|
||||
|
||||
So, you're wondering what the license is for these fonts? Pretty simple; it's based upon that used for Bitstream's Vera font set <http://www.gnome.org/fonts/>.
|
||||
|
||||
Basically, here are the key points summarized, in as little legalese as possible; I hate reading license agreements as much as you probably do:
|
||||
|
||||
With one specific exception, you have full permission to bundle these fonts in your own free or commercial projects-- and by projects, I'm referring to not just software but also electronic documents and print publications.
|
||||
|
||||
So what's the exception? Simple: you can't re-sell these fonts in a commercial font collection. I've seen too many font CDs for sale in stores that are just a repackaging of thousands of freeware fonts found on the internet, and in my mind, that's quite a bit like highway robbery. Note that this *only* applies to products that are font collections in and of themselves; you may freely bundle these fonts with an operating system, application program, or the like.
|
||||
|
||||
Feel free to modify these fonts and even to release the modified versions, as long as you change the original font names (to ensure consistency among people with the font installed) and as long as you give credit somewhere in the font file to codeman38 or zone38.net. I may even incorporate these changes into a later version of my fonts if you wish to send me the modifed fonts via e-mail.
|
||||
|
||||
Also, feel free to mirror these fonts on your own site, as long as you make it reasonably clear that these fonts are not your own work. I'm not asking for much; linking to zone38.net or even just mentioning the nickname codeman38 should be enough.
|
||||
|
||||
Well, that pretty much sums it up... so without further ado, install and enjoy these fonts from the golden age of video games.
|
||||
|
||||
[ codeman38 | cody@zone38.net | http://www.zone38.net/ ]
|
||||
BIN
data/fonts/pressstart/prstart.ttf
Normal file
BIN
data/fonts/pressstart/prstartk.ttf
Normal file
BIN
data/images/keybinds/Harddrop.png
Normal file
|
After Width: | Height: | Size: 619 B |
BIN
data/images/keybinds/Hold.png
Normal file
|
After Width: | Height: | Size: 618 B |
BIN
data/images/keybinds/Moveleft.png
Normal file
|
After Width: | Height: | Size: 604 B |
BIN
data/images/keybinds/Moveright.png
Normal file
|
After Width: | Height: | Size: 603 B |
BIN
data/images/keybinds/Pause.png
Normal file
|
After Width: | Height: | Size: 591 B |
BIN
data/images/keybinds/Retry.png
Normal file
|
After Width: | Height: | Size: 600 B |
BIN
data/images/keybinds/Rotate0.png
Normal file
|
After Width: | Height: | Size: 624 B |
BIN
data/images/keybinds/Rotate180.png
Normal file
|
After Width: | Height: | Size: 627 B |
BIN
data/images/keybinds/RotateCCW.png
Normal file
|
After Width: | Height: | Size: 622 B |
BIN
data/images/keybinds/RotateCW.png
Normal file
|
After Width: | Height: | Size: 623 B |
BIN
data/images/keybinds/Softdrop.png
Normal file
|
After Width: | Height: | Size: 606 B |
@@ -1,6 +1,6 @@
|
||||
# Pieces storage
|
||||
# Files format
|
||||
|
||||
## What is stored
|
||||
## Pieces
|
||||
|
||||
If you don't know what a polyomino is, check [this other file](Pieces_representation.md#what-are-polyominoes).
|
||||
|
||||
@@ -8,8 +8,6 @@ Generating polyominoes of size n is exponential in regard to n. Because of this,
|
||||
|
||||
We want the pieces to be always sorted in the same order, always attributed the same block type, and always set at the same spawn position, no matter how they were generated. We also want them to be separated in 3 categories : convex, not convex but without a hole, and with a hole. Theses problematics are already resolved internally, but will be calculated before storage as to not need extra calculcations upon load (except for the block type which is trivially computed).
|
||||
|
||||
## How is it stored
|
||||
|
||||
Pieces are stored in binary files. Each file simply contains every polyomino of one size, one after the other. Since each file contains all polyominoes of the same size, we know how much stuff to read and don't need delimiters. We know we've read all pieces simply when we reach the end of file character.
|
||||
|
||||
Each piece is stored as follows:
|
||||
@@ -18,3 +16,36 @@ Each piece is stored as follows:
|
||||
|
||||
The current implementation only allows to generate polyominoes up to size 16, but can be upgraded by storing coordinates on 8 bits instead of 4.
|
||||
It has been currently choosen to use pieces only up to size 15 for this game.
|
||||
|
||||
## Config
|
||||
|
||||
When compiling a release version, default files will be used, theses will then be impacted by the user and used for their next sessions.
|
||||
|
||||
### Keybinds
|
||||
|
||||
The games has 4 keyboard configs by default that never changes, and a modifiable 5th one, but theses are all stored the same.
|
||||
|
||||
Each keybinds files has the the following format:
|
||||
|
||||
- The number of the action (converted from an Enum), stored with 1 byte
|
||||
- The number of each binded keys (also converted from an Enum), stored with 1 byte
|
||||
- A separator characters which is 0xFF
|
||||
|
||||
_Repeat for every avaible actions._
|
||||
|
||||
### Settings
|
||||
|
||||
The settings file has the following format:
|
||||
|
||||
- The number of the chosen keybinds (from 0 to 4), stored with 1 byte
|
||||
- The DAS of the player, stored with 1 byte
|
||||
- The ARR of the player, stored with 1 byte
|
||||
- The SDR of the player, stored with 1 byte
|
||||
- The window size mode, stored with 1 byte
|
||||
- The master volume, stored with 1 byte
|
||||
- The number of the last selected gamemode (converted from an Enum), 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 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)
|
||||
- Every selected pieces, using 1 byte for the type of selection (once again converted from an Enum) and 1 byte for the actual value
|
||||
@@ -7,7 +7,6 @@ We will only talk about pieces and not polyominoes. In this project, pieces are
|
||||
|
||||
Each frame, the UI will translate the user's input into a series of action to apply to the game. The list of action is the following:
|
||||
|
||||
- Quit the game
|
||||
- Pause
|
||||
- Retry
|
||||
- Hold
|
||||
@@ -26,6 +25,8 @@ Then the rest of actions will be tested in the list's order.
|
||||
Finally, gravity and lock delay are applied last.
|
||||
Moving and soft dropping can be held but hard dropping, holding and rotating needs to be released and pressed again to happen.
|
||||
|
||||
Menu navigation is managed by the UI and uses static keybinds to prevent the user from softlocking themselves.
|
||||
|
||||
## ARR and DAS
|
||||
|
||||
The sidewise movement of the piece is defined by two parameters: DAS and ARR.
|
||||
@@ -76,8 +77,9 @@ Since this game uses polyomino of high sizes which are very unplayable, we will
|
||||
5. Do the same as step 4 but now we move the piece one line up every time
|
||||
6. Cancel the rotation
|
||||
|
||||
Kicking is primarly designed for rotating, but it is also applied when a piece spawns into a wall.
|
||||
0° rotations will first try to move the piece one position down, and if it can't try kicking the piece, only registering a kick if the piece couldn't move down.
|
||||
_Note: at step 3, the direction which is checked first is actually the last movement done by the player._
|
||||
Kicking is primarly designed for rotating, but there is also a 0° rotation applied when a piece spawns into a wall.
|
||||
0° rotations will first try to move the piece one position down, and if it can't, try kicking the piece, only registering a kick if the piece couldn't move down.
|
||||
|
||||
## Detecting spins
|
||||
|
||||
@@ -90,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.
|
||||
|
||||
@@ -4,10 +4,9 @@
|
||||
|
||||
|
||||
/**
|
||||
* The list of actions that can be taken by the player
|
||||
* The list of in-game actions that can be taken by the player
|
||||
*/
|
||||
enum Action {
|
||||
QUIT,
|
||||
PAUSE,
|
||||
RETRY,
|
||||
HOLD,
|
||||
@@ -22,8 +21,20 @@ enum Action {
|
||||
};
|
||||
|
||||
|
||||
static const std::string ACTION_NAMES[] = { // name for each action
|
||||
"Quit",
|
||||
static const Action ACTION_LIST_IN_ORDER[] = { // the list of possible actions in a sorted order
|
||||
MOVE_LEFT,
|
||||
MOVE_RIGHT,
|
||||
SOFT_DROP,
|
||||
HARD_DROP,
|
||||
ROTATE_CW,
|
||||
ROTATE_CCW,
|
||||
ROTATE_180,
|
||||
ROTATE_0,
|
||||
HOLD,
|
||||
PAUSE,
|
||||
RETRY
|
||||
};
|
||||
static const std::string ACTION_NAMES[] = { // name representation for each actions
|
||||
"Pause",
|
||||
"Retry",
|
||||
"Hold",
|
||||
@@ -31,9 +42,9 @@ static const std::string ACTION_NAMES[] = { // name for each action
|
||||
"Hard drop",
|
||||
"Move left",
|
||||
"Move right",
|
||||
"Rotate 0°",
|
||||
"Rotate 0",
|
||||
"Rotate CW",
|
||||
"Rotate 180°",
|
||||
"Rotate 180",
|
||||
"Rotate CCW"
|
||||
};
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -55,6 +55,8 @@ void Game::initialize() {
|
||||
void Game::nextFrame(const std::set<Action>& playerActions) {
|
||||
if (this->lost || this->hasWon()) return;
|
||||
|
||||
bool pieceJustLocked = false;
|
||||
|
||||
if (this->started) {
|
||||
bool AREJustEnded = (this->leftARETime == 1);
|
||||
if (this->leftARETime > 0) {
|
||||
@@ -84,13 +86,14 @@ void Game::nextFrame(const std::set<Action>& playerActions) {
|
||||
|
||||
if (this->lost) {
|
||||
if (initialRotation == NONE) {
|
||||
this->lost = (!this->board.rotate(initialRotation));
|
||||
}
|
||||
}
|
||||
this->board.rotate(NONE);
|
||||
this->lost = this->board.activePieceInWall();
|
||||
if (this->lost) {
|
||||
this->framesPassed++;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* HOLD */
|
||||
if (playerActions.contains(HOLD) && (!this->heldActions.contains(HOLD))) {
|
||||
@@ -137,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -154,10 +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 {
|
||||
@@ -187,6 +188,8 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,12 +204,12 @@ void Game::nextFrame(const std::set<Action>& playerActions) {
|
||||
}
|
||||
}
|
||||
|
||||
this->heldActions = playerActions;
|
||||
|
||||
if ((!this->started) || this->leftARETime > 0) {
|
||||
for (Action action : playerActions) {
|
||||
if ((!pieceJustLocked) && (!heldActions.contains(action))) {
|
||||
this->initialActions.insert(action);
|
||||
}
|
||||
}
|
||||
|
||||
if (this->heldDAS >= 0) {
|
||||
if (playerActions.contains(MOVE_LEFT)) this->heldDAS = -1;
|
||||
@@ -219,6 +222,8 @@ void Game::nextFrame(const std::set<Action>& playerActions) {
|
||||
else this->heldDAS = 0;
|
||||
}
|
||||
}
|
||||
|
||||
this->heldActions = playerActions;
|
||||
}
|
||||
|
||||
void Game::resetPiece(bool newPiece) {
|
||||
@@ -283,25 +288,34 @@ 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) {
|
||||
bool B2BConditionsAreMet = ((clear.lines >= B2B_MIN_LINE_NUMBER) || clear.isSpin || clear.isMiniSpin);
|
||||
|
||||
/* clearing one more line is worth 2x more
|
||||
clearing with a spin is worth as much as clearing 2x more lines */
|
||||
long int clearScore = LINE_CLEAR_BASE_SCORE;
|
||||
clearScore = clearScore << (clear.lines << (clear.isSpin));
|
||||
|
||||
if (this->B2BChain && B2BConditionsAreMet) clearScore *= B2B_SCORE_MULTIPLIER;
|
||||
this->score += clearScore;
|
||||
if (this->B2BChain && B2BConditionsAreMet) {
|
||||
clearScore *= B2B_SCORE_MULTIPLIER;
|
||||
}
|
||||
this->score += clearScore;
|
||||
|
||||
this->B2BChain = B2BConditionsAreMet;
|
||||
}
|
||||
|
||||
if (!this->hasWon()) {
|
||||
this->leftARETime = this->parameters.getARE();
|
||||
if (this->leftARETime == 0) {
|
||||
this->lost = this->board.spawnNextPiece();
|
||||
this->resetPiece(true);
|
||||
|
||||
if (this->lost) {
|
||||
this->board.rotate(NONE);
|
||||
this->lost = this->board.activePieceInWall();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -318,6 +332,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();
|
||||
}
|
||||
@@ -334,12 +352,16 @@ bool Game::isOnB2BChain() const {
|
||||
return this->B2BChain;
|
||||
}
|
||||
|
||||
bool Game::areBlocksBones() const {
|
||||
return this->parameters.getBoneBlocks();
|
||||
float Game::getLockDelayProgression() const {
|
||||
return (float) this->totalLockDelay / this->parameters.getLockDelay();
|
||||
}
|
||||
|
||||
Position Game::ghostPiecePosition() const {
|
||||
return this->board.lowestPosition();
|
||||
float Game::getForcedLockDelayProgression() const {
|
||||
return (float) this->totalForcedLockDelay / this->parameters.getForcedLockDelay();
|
||||
}
|
||||
|
||||
bool Game::areBlocksBones() const {
|
||||
return this->parameters.getBoneBlocks();
|
||||
}
|
||||
|
||||
const Board& Game::getBoard() const {
|
||||
@@ -354,6 +376,10 @@ const Position& Game::getActivePiecePosition() const {
|
||||
return this->board.getActivePiecePosition();
|
||||
}
|
||||
|
||||
Position Game::getGhostPiecePosition() const {
|
||||
return this->board.lowestPosition();
|
||||
}
|
||||
|
||||
const std::shared_ptr<Piece>& Game::getHeldPiece() const {
|
||||
return this->board.getHeldPiece();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
@@ -116,16 +121,21 @@ class Game {
|
||||
*/
|
||||
bool isOnB2BChain() const;
|
||||
|
||||
/**
|
||||
* @return How close the active piece's lock delay is to the maximum allowed, betwwen 0 and 1
|
||||
*/
|
||||
float getLockDelayProgression() const;
|
||||
|
||||
/**
|
||||
* @return How close the active piece's forced lock delay is to the maximum allowed, betwwen 0 and 1
|
||||
*/
|
||||
float getForcedLockDelayProgression() const;
|
||||
|
||||
/**
|
||||
* @return If all blocks are currently bone blocks
|
||||
*/
|
||||
bool areBlocksBones() const;
|
||||
|
||||
/**
|
||||
* @return The position of the ghost piece
|
||||
*/
|
||||
Position ghostPiecePosition() const;
|
||||
|
||||
/**
|
||||
* @return The board
|
||||
*/
|
||||
@@ -141,6 +151,11 @@ class Game {
|
||||
*/
|
||||
const Position& getActivePiecePosition() const;
|
||||
|
||||
/**
|
||||
* @return The position of the ghost piece
|
||||
*/
|
||||
Position getGhostPiecePosition() const;
|
||||
|
||||
/**
|
||||
* @return A pointer to the held piece, can be null
|
||||
*/
|
||||
|
||||
@@ -36,9 +36,12 @@ void GameBoard::initialize() {
|
||||
this->activePiece = nullptr;
|
||||
this->heldPiece = nullptr;
|
||||
this->isLastMoveKick = false;
|
||||
this->movedLeftLast = false;
|
||||
}
|
||||
|
||||
bool GameBoard::moveLeft() {
|
||||
this->movedLeftLast = true;
|
||||
|
||||
if (this->activePieceInWall(Position{-1, 0})) {
|
||||
return false;
|
||||
}
|
||||
@@ -50,6 +53,8 @@ bool GameBoard::moveLeft() {
|
||||
}
|
||||
|
||||
bool GameBoard::moveRight() {
|
||||
this->movedLeftLast = false;
|
||||
|
||||
if (this->activePieceInWall(Position{1, 0})) {
|
||||
return false;
|
||||
}
|
||||
@@ -137,30 +142,21 @@ bool GameBoard::tryKicking(bool testingBottom, const std::set<Position>& safePos
|
||||
bool overlapsRight = true;
|
||||
int i = (j == 0) ? 1 : 0;
|
||||
do {
|
||||
// check right before left arbitrarly, we don't decide this with rotations since it would still be arbitrary with 180° rotations
|
||||
if (overlapsRight) {
|
||||
Position shift{+i, j};
|
||||
if (!this->activePieceOverlaps(safePositions, shift)) {
|
||||
overlapsLeft = false;
|
||||
}
|
||||
else {
|
||||
if (!this->activePieceInWall(shift)) {
|
||||
this->activePiecePosition += shift;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we first check the side to which the player moved last
|
||||
if (movedLeftLast) {
|
||||
if (overlapsLeft) {
|
||||
Position shift{-i, j};
|
||||
if (!this->activePieceOverlaps(safePositions, shift)) {
|
||||
overlapsLeft = false;
|
||||
if (this->tryFittingKickedPiece(safePositions, Position({-i, j}), overlapsLeft)) return true;
|
||||
}
|
||||
if (overlapsRight) {
|
||||
if (this->tryFittingKickedPiece(safePositions, Position({+i, j}), overlapsRight)) return true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!this->activePieceInWall(shift)) {
|
||||
this->activePiecePosition += shift;
|
||||
return true;
|
||||
if (overlapsRight) {
|
||||
if (this->tryFittingKickedPiece(safePositions, Position({+i, j}), overlapsRight)) return true;
|
||||
}
|
||||
if (overlapsLeft) {
|
||||
if (this->tryFittingKickedPiece(safePositions, Position({-i, j}), overlapsLeft)) return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,6 +173,26 @@ bool GameBoard::tryKicking(bool testingBottom, const std::set<Position>& safePos
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GameBoard::tryFittingKickedPiece(const std::set<Position>& safePositions, const Position& shift, bool& overlaps) {
|
||||
if (!this->activePieceOverlaps(safePositions, shift)) {
|
||||
overlaps = false;
|
||||
}
|
||||
else {
|
||||
if (!this->activePieceInWall(shift)) {
|
||||
this->activePiecePosition += shift;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GameBoard::activePieceOverlaps(const std::set<Position>& safePositions, const Position& shift) const {
|
||||
for (Position position : this->activePiece->getPositions()) {
|
||||
if (safePositions.contains(position + this->activePiecePosition + shift)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GameBoard::hold(Rotation initialRotation) {
|
||||
std::swap(this->activePiece, this->heldPiece);
|
||||
|
||||
@@ -236,6 +252,13 @@ bool GameBoard::spawnNextPiece() {
|
||||
return this->activePieceInWall();
|
||||
}
|
||||
|
||||
bool GameBoard::activePieceInWall(const Position& shift) const {
|
||||
for (Position position : this->activePiece->getPositions()) {
|
||||
if (this->board.getBlock(position + this->activePiecePosition + shift) != NOTHING) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GameBoard::touchesGround() const {
|
||||
return this->activePieceInWall(Position{0, -1});
|
||||
}
|
||||
@@ -257,6 +280,8 @@ LineClear GameBoard::lockPiece() {
|
||||
this->board.changeBlock(position + this->activePiecePosition, this->activePiece->getBlockType());
|
||||
}
|
||||
|
||||
this->activePiece = nullptr;
|
||||
|
||||
return LineClear{this->board.clearRows(), isLockedInPlace, (!isLockedInPlace) && this->isLastMoveKick};
|
||||
}
|
||||
|
||||
@@ -291,20 +316,6 @@ const std::vector<Piece>& GameBoard::getNextPieces() const {
|
||||
return this->nextQueue;
|
||||
}
|
||||
|
||||
bool GameBoard::activePieceInWall(const Position& shift) const {
|
||||
for (Position position : this->activePiece->getPositions()) {
|
||||
if (this->board.getBlock(position + this->activePiecePosition + shift) != NOTHING) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GameBoard::activePieceOverlaps(const std::set<Position>& safePositions, const Position& shift) const {
|
||||
for (Position position : this->activePiece->getPositions()) {
|
||||
if (safePositions.contains(position + this->activePiecePosition + shift)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void GameBoard::goToSpawnPosition() {
|
||||
int lowestPosition = this->activePiece->getLength() - 1;
|
||||
for (Position position : this->activePiece->getPositions()) {
|
||||
|
||||
@@ -22,6 +22,7 @@ class GameBoard {
|
||||
int nextQueueLength; // the number of next pieces seeable at a time
|
||||
std::vector<Piece> nextQueue; // the list of the next pieces to spawn in the board
|
||||
bool isLastMoveKick; // wheter the last action the piece did was kicking
|
||||
bool movedLeftLast; // wheter the last sideway movement was a left one
|
||||
|
||||
public:
|
||||
/**
|
||||
@@ -72,6 +73,18 @@ class GameBoard {
|
||||
*/
|
||||
bool tryKicking(bool testingBottom, const std::set<Position>& safePositions);
|
||||
|
||||
/**
|
||||
* Tries fitting the kicked active piece at the specified position
|
||||
* @return If it suceeded
|
||||
*/
|
||||
bool tryFittingKickedPiece(const std::set<Position>& safePositions, const Position& shift, bool& overlaps);
|
||||
|
||||
/**
|
||||
* Check if one of the active piece's positions shifted by a specified position would overlap with a set of positions
|
||||
* @return If the shifted active piece overlaps with one of the position
|
||||
*/
|
||||
bool activePieceOverlaps(const std::set<Position>& safePositions, const Position& shift = Position{0, 0}) const;
|
||||
|
||||
public:
|
||||
/**
|
||||
* 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
|
||||
@@ -85,6 +98,12 @@ class GameBoard {
|
||||
*/
|
||||
bool spawnNextPiece();
|
||||
|
||||
/**
|
||||
* Checks if one of the active piece's positions touches a wall in the board
|
||||
* @return If the active piece is in a wall
|
||||
*/
|
||||
bool activePieceInWall(const Position& shift = Position{0, 0}) const;
|
||||
|
||||
/**
|
||||
* Checks is the active piece as a wall directly below one of its position
|
||||
* @return If it touches a ground
|
||||
@@ -134,18 +153,6 @@ class GameBoard {
|
||||
const std::vector<Piece>& getNextPieces() const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Checks if one of the active piece's positions touches a wall in the board
|
||||
* @return If the active piece spawned in a wall
|
||||
*/
|
||||
bool activePieceInWall(const Position& shift = Position{0, 0}) const;
|
||||
|
||||
/**
|
||||
* Check if one of the active piece's positions shifted by a specified position would overlap with a set of positions
|
||||
* @return If the shifted active piece overlaps with one of the position
|
||||
*/
|
||||
bool activePieceOverlaps(const std::set<Position>& safePositions, const Position& shift = Position{0, 0}) const;
|
||||
|
||||
/**
|
||||
* Sets the active piece to its spawn position
|
||||
*/
|
||||
|
||||
@@ -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;}
|
||||
@@ -22,29 +24,35 @@ void GameParameters::reset() {
|
||||
case MARATHON : {this->level = 1; break;}
|
||||
// goes from level 20 to 39
|
||||
case MASTER : {this->level = 20; break;}
|
||||
// no gravity
|
||||
case ZEN : {this->level = 0; break;}
|
||||
default : this->level = 1;
|
||||
}
|
||||
|
||||
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) {
|
||||
this->level = this->clearedLines / 10;
|
||||
this->level += (this->clearedLines / 10 - previousLines / 10);
|
||||
this->updateStats();
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +66,8 @@ bool GameParameters::hasWon(int framesPassed) const {
|
||||
case MARATHON : return this->clearedLines >= 200;
|
||||
// win once 200 lines have been cleared
|
||||
case MASTER : return this->clearedLines >= 200;
|
||||
// infinite mode
|
||||
case ZEN :
|
||||
default : return false;
|
||||
}
|
||||
}
|
||||
@@ -65,13 +75,14 @@ bool GameParameters::hasWon(int framesPassed) const {
|
||||
void GameParameters::updateStats() {
|
||||
/* NEXT QUEUE */
|
||||
switch (this->gamemode) {
|
||||
// 5 for rapidity gamemodes
|
||||
// 5 for fast-controls gamemodes
|
||||
case SPRINT :
|
||||
case ULTRA : {
|
||||
case ULTRA :
|
||||
case ZEN : {
|
||||
this->nextQueueLength = 5;
|
||||
break;
|
||||
}
|
||||
// 3 for endurance gamemodes
|
||||
// 3 for slow-controls gamemodes
|
||||
case MARATHON :
|
||||
case MASTER : {
|
||||
this->nextQueueLength = 3;
|
||||
@@ -126,6 +137,8 @@ void GameParameters::updateStats() {
|
||||
switch (this->gamemode) {
|
||||
// starts at 500ms (30f) at lvl 20 and ends at 183ms (11f) at lvl 39
|
||||
case MASTER : {this->lockDelay = 30 - (this->level - 20); break;}
|
||||
// 10s
|
||||
case ZEN : {this->lockDelay = 60 * 10; break;}
|
||||
// 1s by default
|
||||
default : this->lockDelay = 60;
|
||||
}
|
||||
@@ -203,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
|
||||
@@ -8,5 +10,37 @@ enum Gamemode {
|
||||
SPRINT,
|
||||
MARATHON,
|
||||
ULTRA,
|
||||
MASTER
|
||||
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];
|
||||
}
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
static const int DEFAULT_BOARD_WIDTH = 10; // the default width of the board when starting the menu
|
||||
static const int DEFAULT_BOARD_HEIGHT = 20; // the default height of the board when starting the menu
|
||||
|
||||
|
||||
Menu::Menu() {
|
||||
this->piecesList = std::make_shared<PiecesList>(PiecesList());
|
||||
@@ -44,6 +47,14 @@ Player& Menu::getPlayerControls() {
|
||||
return this->playerControls;
|
||||
}
|
||||
|
||||
const Player& Menu::readPlayerControls() const {
|
||||
return this->playerControls;
|
||||
}
|
||||
|
||||
PiecesList& Menu::getPiecesList() {
|
||||
return *this->piecesList;
|
||||
}
|
||||
|
||||
const PiecesList& Menu::readPiecesList() const {
|
||||
return *this->piecesList;
|
||||
}
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
#include "Game.h"
|
||||
|
||||
static const int FRAMES_PER_SECOND = 60; // the number of frames per second, all the values in the app were choosen with this number in mind
|
||||
static const int DEFAULT_BOARD_WIDTH = 10; // the default width of the board when starting the menu
|
||||
static const int DEFAULT_BOARD_HEIGHT = 20; // the default height of the board when starting the menu
|
||||
|
||||
|
||||
/**
|
||||
@@ -58,8 +56,18 @@ class Menu {
|
||||
*/
|
||||
Player& getPlayerControls();
|
||||
|
||||
/**
|
||||
* @return A reference to the player's controls
|
||||
*/
|
||||
const Player& readPlayerControls() const;
|
||||
|
||||
/**
|
||||
* @return A reference to the pieces list
|
||||
*/
|
||||
PiecesList& getPiecesList();
|
||||
|
||||
/**
|
||||
* @return A reference to the pieces list
|
||||
*/
|
||||
const PiecesList& readPiecesList() const;
|
||||
};
|
||||
|
||||
83
src/GraphicalUI/AppMenus/AppMenu.h
Normal file
@@ -0,0 +1,83 @@
|
||||
#pragma once
|
||||
|
||||
#include "../Settings.h"
|
||||
#include "../PlayerCursor.h"
|
||||
|
||||
#include <stack>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
class AppMenu;
|
||||
using MenuStack = std::stack<std::shared_ptr<AppMenu>>;
|
||||
|
||||
|
||||
class AppMenu {
|
||||
protected:
|
||||
std::shared_ptr<MenuStack> menuStack;
|
||||
std::shared_ptr<Settings> settings;
|
||||
std::shared_ptr<sf::RenderWindow> renderWindow;
|
||||
bool enterPressed = false;
|
||||
bool enterReleased = false;
|
||||
bool escPressed = false;
|
||||
bool escReleased = false;
|
||||
sf::Font pressStartFont = sf::Font("data/fonts/pressstart/prstartk.ttf");
|
||||
|
||||
public:
|
||||
AppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow) :
|
||||
menuStack(menuStack),
|
||||
settings(settings),
|
||||
renderWindow(renderWindow)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
virtual void computeFrame() = 0;
|
||||
|
||||
virtual void drawFrame() const = 0;
|
||||
|
||||
protected:
|
||||
void updateMetaBinds() {
|
||||
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Enter)) {
|
||||
enterPressed = true;
|
||||
enterReleased = false;
|
||||
}
|
||||
else {
|
||||
enterReleased = enterPressed;
|
||||
enterPressed = false;
|
||||
}
|
||||
|
||||
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Escape)) {
|
||||
escPressed = true;
|
||||
escReleased = false;
|
||||
}
|
||||
else {
|
||||
escReleased = escPressed;
|
||||
escPressed = false;
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
text.setString(string);
|
||||
if (playerCursor.has_value() && cursorPos.has_value()) {
|
||||
text.setOutlineThickness((playerCursor.value().getPosition() == cursorPos.value()) ? (sizeMultiplier / 2) : 0);
|
||||
}
|
||||
text.setOrigin(sf::Vector2f({0, text.getLocalBounds().size.y / 2}));
|
||||
text.setPosition(sf::Vector2f({sizeMultiplier * xPos, sizeMultiplier * yPos}));
|
||||
this->renderWindow->draw(text);
|
||||
}
|
||||
|
||||
void placeTitle(sf::Text& text, const std::optional<PlayerCursor>& playerCursor, const sf::String& string, float yPos, const std::optional<sf::Vector2u>& cursorPos) const {
|
||||
float sizeMultiplier = this->settings->getWindowSizeMultiplier();
|
||||
|
||||
text.setString(string);
|
||||
if (playerCursor.has_value() && cursorPos.has_value()) {
|
||||
text.setOutlineThickness((playerCursor.value().getPosition() == cursorPos.value()) ? (sizeMultiplier / 2) : 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);
|
||||
}
|
||||
};
|
||||
67
src/GraphicalUI/AppMenus/GameBoardAppMenu.cpp
Normal file
@@ -0,0 +1,67 @@
|
||||
#include "GameBoardAppMenu.h"
|
||||
|
||||
#include "AppMenu.h"
|
||||
#include "../PlayerCursor.h"
|
||||
|
||||
#include <stack>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
|
||||
GameBoardAppMenu::GameBoardAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow) :
|
||||
AppMenu(menuStack, settings, renderWindow),
|
||||
playerCursor({1, 1}) {
|
||||
|
||||
}
|
||||
|
||||
void GameBoardAppMenu::computeFrame() {
|
||||
this->updateMetaBinds();
|
||||
this->playerCursor.updatePosition();
|
||||
|
||||
Menu& menu = this->settings->getMenu();
|
||||
|
||||
switch (this->playerCursor.getPosition().y) {
|
||||
case 0 : {
|
||||
if (this->playerCursor.movedLeft()) {
|
||||
menu.setBoardWidth(std::max(1, menu.getBoardWidth() - 1));
|
||||
}
|
||||
if (this->playerCursor.movedRight()) {
|
||||
menu.setBoardWidth(std::min(MAXIMUM_BOARD_WIDTH, menu.getBoardWidth() + 1));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 1 : {
|
||||
if (this->playerCursor.movedLeft()) {
|
||||
menu.setBoardHeight(std::max(1, menu.getBoardHeight() - 1));
|
||||
}
|
||||
if (this->playerCursor.movedRight()) {
|
||||
menu.setBoardHeight(std::min(MAXIMUM_BOARD_HEIGHT, menu.getBoardHeight() + 1));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (this->escReleased) {
|
||||
this->menuStack->pop();
|
||||
}
|
||||
}
|
||||
|
||||
void GameBoardAppMenu::drawFrame() const {
|
||||
this->renderWindow->clear(sf::Color(200, 200, 200));
|
||||
|
||||
const Menu& menu = this->settings->getMenu();
|
||||
|
||||
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, {}, "BOARD SETTINGS", 5.f, {});
|
||||
|
||||
sf::Vector2u windowSize = this->renderWindow->getSize();
|
||||
|
||||
this->placeText(text, this->playerCursor, "< BOARD WIDTH: " + std::to_string(menu.getBoardWidth()) + " >", 5.f, 15.f, sf::Vector2u{0, 0});
|
||||
this->placeText(text, this->playerCursor, "< BOARD HEIGHT: " + std::to_string(menu.getBoardHeight()) + " >", 5.f, 25.f, sf::Vector2u{0, 1});
|
||||
|
||||
this->renderWindow->display();
|
||||
}
|
||||
21
src/GraphicalUI/AppMenus/GameBoardAppMenu.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "AppMenu.h"
|
||||
#include "../PlayerCursor.h"
|
||||
|
||||
#include <stack>
|
||||
#include <memory>
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
|
||||
class GameBoardAppMenu : public AppMenu {
|
||||
private:
|
||||
PlayerCursor playerCursor;
|
||||
|
||||
public:
|
||||
GameBoardAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow);
|
||||
|
||||
void computeFrame() override;
|
||||
|
||||
void drawFrame() const override;
|
||||
};
|
||||
278
src/GraphicalUI/AppMenus/GamePlayingAppMenu.cpp
Normal file
@@ -0,0 +1,278 @@
|
||||
#include "GamePlayingAppMenu.h"
|
||||
|
||||
#include "AppMenu.h"
|
||||
|
||||
#include <stack>
|
||||
#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->startTimer = TIME_BEFORE_STARTING;
|
||||
this->paused = false;
|
||||
this->pausePressed = false;
|
||||
this->retryPressed = false;
|
||||
|
||||
int maxWidthMultiplier = (this->settings->getWindowSizeMultiplier() * 40) / (this->game.getBoard().getWidth());
|
||||
int maxHeightMultiplier = (this->settings->getWindowSizeMultiplier() * 50) / (this->game.getBoard().getBaseHeight() + 10);
|
||||
this->cellSizeZoom = std::min(maxWidthMultiplier, maxHeightMultiplier);
|
||||
|
||||
float boardWidth = this->game.getBoard().getWidth() * this->cellSizeZoom;
|
||||
float boardHeight = (this->game.getBoard().getBaseHeight() + 10) * this->cellSizeZoom;
|
||||
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::FloatRect(sf::Vector2f(this->boardPosition.position.x + boardWidth + (5.f * this->settings->getWindowSizeMultiplier()), (10.f + (10.f * i)) * this->settings->getWindowSizeMultiplier()),
|
||||
sf::Vector2f(8.f * this->settings->getWindowSizeMultiplier(), 8.f * this->settings->getWindowSizeMultiplier()));
|
||||
}
|
||||
|
||||
this->nextCellSizeZoom = this->nextQueuePosition[0].size.y;
|
||||
for (const auto& piece : this->settings->getMenu().getPiecesList().getSelectedPieces()) {
|
||||
float nextPieceCellSizeZoom = ((int) this->nextQueuePosition[0].size.y) / this->settings->getMenu().getPiecesList().getPiece(piece).getLength();
|
||||
this->nextCellSizeZoom = std::min(this->nextCellSizeZoom, nextPieceCellSizeZoom);
|
||||
}
|
||||
|
||||
this->holdBoxPosition = sf::FloatRect(sf::Vector2f(this->boardPosition.position.x - ((8.f + 5.f) * this->settings->getWindowSizeMultiplier()), (10.f) * this->settings->getWindowSizeMultiplier()),
|
||||
sf::Vector2f(8.f * this->settings->getWindowSizeMultiplier(), 8.f * this->settings->getWindowSizeMultiplier()));
|
||||
this->holdCellSizeZoom = this->nextCellSizeZoom;
|
||||
}
|
||||
|
||||
void GamePlayingAppMenu::computeFrame() {
|
||||
this->updateMetaBinds();
|
||||
|
||||
if (this->startTimer > 0) {
|
||||
this->startTimer--;
|
||||
if (this->startTimer == 0) {
|
||||
this->game.start();
|
||||
}
|
||||
}
|
||||
|
||||
if (this->escReleased) {
|
||||
this->menuStack->pop();
|
||||
}
|
||||
else {
|
||||
std::set<Action> actions;
|
||||
for (Action action : ACTION_LIST_IN_ORDER) {
|
||||
for (sfKey key : this->settings->getKeybinds().getKeybinds(action)) {
|
||||
if (sf::Keyboard::isKeyPressed(key)) {
|
||||
actions.insert(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (actions.contains(RETRY)) {
|
||||
this->retryPressed = true;
|
||||
}
|
||||
else {
|
||||
if (this->retryPressed) {
|
||||
this->game.reset();
|
||||
this->startTimer = TIME_BEFORE_STARTING;
|
||||
}
|
||||
this->retryPressed = false;
|
||||
}
|
||||
|
||||
if (actions.contains(PAUSE)) {
|
||||
this->pausePressed = true;
|
||||
}
|
||||
else {
|
||||
if (this->pausePressed) {
|
||||
this->paused = (!this->paused);
|
||||
}
|
||||
this->pausePressed = false;
|
||||
}
|
||||
|
||||
if (!paused) {
|
||||
this->game.nextFrame(actions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GamePlayingAppMenu::drawFrame() const {
|
||||
this->renderWindow->clear(sf::Color(200, 200, 200));
|
||||
|
||||
sf::Vector2f cellSize(this->cellSizeZoom, this->cellSizeZoom);
|
||||
bool drawActivePiece = (this->game.getActivePiece() != nullptr) && (!this->game.hasLost());
|
||||
|
||||
// board
|
||||
for (int y = this->game.getBoard().getBaseHeight() + 9; y >= 0; y--) {
|
||||
for (int x = 0; x < this->game.getBoard().getWidth(); x++) {
|
||||
Block block = this->game.getBoard().getBlock(Position{x, y});
|
||||
|
||||
sf::RectangleShape cell(cellSize);
|
||||
cell.setFillColor(this->getColorOfBlock(block, (block == NOTHING) ? 0 : -30));
|
||||
cell.setPosition(this->getBoardBlockPosition(x, y));
|
||||
this->renderWindow->draw(cell);
|
||||
}
|
||||
}
|
||||
|
||||
if (drawActivePiece) {
|
||||
// ghost piece
|
||||
sf::Color ghostColor = this->getColorOfBlock(this->game.getActivePiece()->getBlockType(), 100);
|
||||
for (const Position& position : this->game.getActivePiece()->getPositions()) {
|
||||
Position cellPosition = (this->game.getGhostPiecePosition() + position);
|
||||
|
||||
sf::RectangleShape cell(cellSize);
|
||||
cell.setFillColor(ghostColor);
|
||||
cell.setPosition(this->getBoardBlockPosition(cellPosition.x, cellPosition.y));
|
||||
this->renderWindow->draw(cell);
|
||||
}
|
||||
|
||||
// active piece outline
|
||||
float pieceOutlineSize = std::roundf(this->cellSizeZoom / 4);
|
||||
sf::Color pieceOultlineColor = sf::Color(255, 255 - (255 * this->game.getForcedLockDelayProgression()), 255 - (255 * this->game.getForcedLockDelayProgression()));
|
||||
for (const Position& position : this->game.getActivePiece()->getPositions()) {
|
||||
Position cellPosition = (this->game.getActivePiecePosition() + position);
|
||||
|
||||
sf::RectangleShape cell(cellSize);
|
||||
cell.setOutlineThickness(pieceOutlineSize);
|
||||
cell.setOutlineColor(pieceOultlineColor);
|
||||
cell.setPosition(this->getBoardBlockPosition(cellPosition.x, cellPosition.y));
|
||||
this->renderWindow->draw(cell);
|
||||
}
|
||||
}
|
||||
|
||||
// top out line
|
||||
sf::RectangleShape topOutLine(sf::Vector2f(this->cellSizeZoom * this->game.getBoard().getWidth(), std::roundf(this->cellSizeZoom / 4)));
|
||||
topOutLine.setPosition(this->getBoardBlockPosition(0, this->game.getBoard().getBaseHeight() - 1));
|
||||
topOutLine.setFillColor(sf::Color(255, 0, 0));
|
||||
this->renderWindow->draw(topOutLine);
|
||||
|
||||
if (drawActivePiece) {
|
||||
// active piece
|
||||
sf::Color pieceColor = this->getColorOfBlock(this->game.getActivePiece()->getBlockType(), -200 * (this->game.getLockDelayProgression()));
|
||||
|
||||
for (const Position& position : this->game.getActivePiece()->getPositions()) {
|
||||
Position cellPosition = (this->game.getActivePiecePosition() + position);
|
||||
|
||||
sf::RectangleShape cell(cellSize);
|
||||
cell.setFillColor(pieceColor);
|
||||
cell.setPosition(this->getBoardBlockPosition(cellPosition.x, cellPosition.y));
|
||||
this->renderWindow->draw(cell);
|
||||
}
|
||||
}
|
||||
|
||||
// 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(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(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(180, 180, 180);
|
||||
|
||||
for (int y = 0; y < this->game.getHeldPiece()->getLength(); y++) {
|
||||
for (int x = 0; x < this->game.getHeldPiece()->getLength(); x++) {
|
||||
sf::RectangleShape cell(holdCellSize);
|
||||
if (this->game.getHeldPiece()->getPositions().contains(Position{x, y})) {
|
||||
cell.setFillColor(color);
|
||||
}
|
||||
else {
|
||||
cell.setFillColor(boxColor);
|
||||
}
|
||||
cell.setPosition(sf::Vector2f(this->holdBoxPosition.position.x + (x * this->nextCellSizeZoom),
|
||||
this->holdBoxPosition.position.y + ((this->game.getHeldPiece()->getLength() - y - 1) * this->holdCellSizeZoom)));
|
||||
this->renderWindow->draw(cell);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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, {});
|
||||
|
||||
if (this->game.isOnB2BChain()) {
|
||||
this->placeText(text, {}, "B2B", 1.f, 22.f, {});
|
||||
}
|
||||
this->placeText(text, {}, "LINES:" + std::to_string(this->game.getClearedLines()), 1.f, 27.f, {});
|
||||
this->placeText(text, {}, "LEVEL:" + std::to_string(this->game.getLevel()), 1.f, 32.f, {});
|
||||
this->placeText(text, {}, "SCORE:" + std::to_string(this->game.getScore()), 1.f, 37.f, {});
|
||||
this->placeText(text, {}, "GRADE:" + std::to_string(this->game.getGrade()), 1.f, 42.f, {});
|
||||
this->placeText(text, {}, showedTime, 1.f, 47.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();
|
||||
}
|
||||
|
||||
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 {
|
||||
return sf::Vector2f(this->boardPosition.position.x + (x * this->cellSizeZoom),
|
||||
this->boardPosition.position.y + ((this->game.getBoard().getBaseHeight() + 9 - y) * this->cellSizeZoom));
|
||||
}
|
||||
34
src/GraphicalUI/AppMenus/GamePlayingAppMenu.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include "AppMenu.h"
|
||||
|
||||
#include <stack>
|
||||
#include <memory>
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
|
||||
class GamePlayingAppMenu : public AppMenu {
|
||||
private:
|
||||
Game game;
|
||||
int startTimer;
|
||||
bool paused;
|
||||
bool pausePressed;
|
||||
bool retryPressed;
|
||||
sf::FloatRect boardPosition;
|
||||
float cellSizeZoom;
|
||||
sf::FloatRect holdBoxPosition;
|
||||
float holdCellSizeZoom;
|
||||
sf::FloatRect nextQueuePosition[5];
|
||||
float nextCellSizeZoom;
|
||||
|
||||
public:
|
||||
GamePlayingAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow);
|
||||
|
||||
void computeFrame() override;
|
||||
|
||||
void drawFrame() const override;
|
||||
|
||||
sf::Color getColorOfBlock(Block block, int luminosityShift) const;
|
||||
|
||||
sf::Vector2f getBoardBlockPosition(int x, int y) const;
|
||||
};
|
||||
81
src/GraphicalUI/AppMenus/GameSettingsAppMenu.cpp
Normal file
@@ -0,0 +1,81 @@
|
||||
#include "GameSettingsAppMenu.h"
|
||||
|
||||
#include "AppMenu.h"
|
||||
#include "GameBoardAppMenu.h"
|
||||
#include "GamePlayingAppMenu.h"
|
||||
#include "../PlayerCursor.h"
|
||||
|
||||
#include <stack>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
|
||||
GameSettingsAppMenu::GameSettingsAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow) :
|
||||
AppMenu(menuStack, settings, renderWindow),
|
||||
playerCursor({2, 3, 3}) {
|
||||
|
||||
}
|
||||
|
||||
void GameSettingsAppMenu::computeFrame() {
|
||||
this->updateMetaBinds();
|
||||
this->playerCursor.updatePosition();
|
||||
|
||||
switch (this->playerCursor.getPosition().y) {
|
||||
case 1 : {
|
||||
switch (this->playerCursor.getPosition().x) {
|
||||
case 0 : {this->settings->setGamemode(SPRINT); break;}
|
||||
case 1 : {this->settings->setGamemode(MARATHON); break;}
|
||||
case 2 : {this->settings->setGamemode(ULTRA); break;}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 2 : {
|
||||
switch (this->playerCursor.getPosition().x) {
|
||||
case 0 : {this->settings->setGamemode(MASTER); break;}
|
||||
case 1 : {this->settings->setGamemode(ZEN); break;}
|
||||
case 2 : break; //TODO
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (this->enterReleased) {
|
||||
if (this->playerCursor.getPosition().y == 0) {
|
||||
if (this->playerCursor.getPosition().x == 0) {
|
||||
//TODO
|
||||
}
|
||||
if (this->playerCursor.getPosition().x == 1) {
|
||||
this->menuStack->push(std::make_shared<GameBoardAppMenu>(this->menuStack, this->settings, this->renderWindow));
|
||||
}
|
||||
}
|
||||
if (this->playerCursor.getPosition().y > 0) {
|
||||
this->menuStack->push(std::make_shared<GamePlayingAppMenu>(this->menuStack, this->settings, this->renderWindow));
|
||||
}
|
||||
}
|
||||
if (this->escReleased) {
|
||||
this->menuStack->pop();
|
||||
}
|
||||
}
|
||||
|
||||
void GameSettingsAppMenu::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, {}, "GAME SETTINGS", 5.f, {});
|
||||
|
||||
this->placeText(text, this->playerCursor, "PIECES SELECT (TODO)", 5.f, 15.f, sf::Vector2u{0, 0});
|
||||
this->placeText(text, this->playerCursor, "BOARD SELECT", 40.f, 15.f, sf::Vector2u{1, 0});
|
||||
|
||||
this->placeText(text, this->playerCursor, "SPRINT", 5.f, 25.f, sf::Vector2u{0, 1});
|
||||
this->placeText(text, this->playerCursor, "MARATHON", 25.f, 25.f, sf::Vector2u{1, 1});
|
||||
this->placeText(text, this->playerCursor, "ULTRA", 50.f, 25.f, sf::Vector2u{2, 1});
|
||||
this->placeText(text, this->playerCursor, "MASTER", 5.f, 35.f, sf::Vector2u{0, 2});
|
||||
this->placeText(text, this->playerCursor, "ZEN", 25.f, 35.f, sf::Vector2u{1, 2});
|
||||
this->placeText(text, this->playerCursor, "??? (TODO)", 50.f, 35.f, sf::Vector2u{2, 2});
|
||||
|
||||
this->renderWindow->display();
|
||||
}
|
||||
21
src/GraphicalUI/AppMenus/GameSettingsAppMenu.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "AppMenu.h"
|
||||
#include "../PlayerCursor.h"
|
||||
|
||||
#include <stack>
|
||||
#include <memory>
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
|
||||
class GameSettingsAppMenu : public AppMenu {
|
||||
private:
|
||||
PlayerCursor playerCursor;
|
||||
|
||||
public:
|
||||
GameSettingsAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow);
|
||||
|
||||
void computeFrame() override;
|
||||
|
||||
void drawFrame() const override;
|
||||
};
|
||||
88
src/GraphicalUI/AppMenus/InfoAppMenu.cpp
Normal file
@@ -0,0 +1,88 @@
|
||||
#include "InfoAppMenu.h"
|
||||
|
||||
#include "AppMenu.h"
|
||||
#include "../PlayerCursor.h"
|
||||
|
||||
#include <stack>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
|
||||
InfoAppMenu::InfoAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow) :
|
||||
AppMenu(menuStack, settings, renderWindow),
|
||||
playerCursor({4}),
|
||||
sectionsName(
|
||||
"< ABOUT >",
|
||||
"< ROTATION SYSTEM >",
|
||||
"< SCORING >",
|
||||
"< 0 DEGREES ROTATIONS >"
|
||||
),
|
||||
sectionsContent(
|
||||
"This game is written in C++,\n"
|
||||
"using SFML 3 for the GUI.\n"
|
||||
"It has been inspired by other\n"
|
||||
"stacker games, such as\n"
|
||||
"Techmino, jstris, tetr.io, etc.\n"
|
||||
"This project isn't affiliated\n"
|
||||
"to them in any ways.\n"
|
||||
"Current version: beta.",
|
||||
|
||||
"This game uses its own\n"
|
||||
"Rotation Sytem, called AutoRS.\n"
|
||||
"The rotation center is always the\n"
|
||||
"center of the piece by default.\n"
|
||||
"When kicking the piece, it will\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.",
|
||||
|
||||
"The score gained from a line clear\n"
|
||||
"doubles when clearing one more line.\n"
|
||||
"Clearing with a spin scores as much\n"
|
||||
"as clearing 2x more lines normally.\n"
|
||||
"B2B is granted by clearing at least\n"
|
||||
"4 lines or doing a spin or mini-spin,\n"
|
||||
"and doubles the score gained.\n"
|
||||
"A spin is detected when the piece is\n"
|
||||
"locked in place, a mini-spin simply\n"
|
||||
"when the last move was a kick.",
|
||||
|
||||
"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."
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
void InfoAppMenu::computeFrame() {
|
||||
this->updateMetaBinds();
|
||||
this->playerCursor.updatePosition();
|
||||
|
||||
if (this->escReleased) {
|
||||
this->menuStack->pop();
|
||||
}
|
||||
}
|
||||
|
||||
void InfoAppMenu::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, 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();
|
||||
}
|
||||
23
src/GraphicalUI/AppMenus/InfoAppMenu.h
Normal file
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "AppMenu.h"
|
||||
#include "../PlayerCursor.h"
|
||||
|
||||
#include <stack>
|
||||
#include <memory>
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
|
||||
class InfoAppMenu : public AppMenu {
|
||||
private:
|
||||
PlayerCursor playerCursor;
|
||||
sf::String sectionsName[4];
|
||||
sf::String sectionsContent[4];
|
||||
|
||||
public:
|
||||
InfoAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow);
|
||||
|
||||
void computeFrame() override;
|
||||
|
||||
void drawFrame() const override;
|
||||
};
|
||||
55
src/GraphicalUI/AppMenus/MainAppMenu.cpp
Normal file
@@ -0,0 +1,55 @@
|
||||
#include "MainAppMenu.h"
|
||||
|
||||
#include "AppMenu.h"
|
||||
#include "GameSettingsAppMenu.h"
|
||||
#include "SettingsMainAppMenu.h"
|
||||
#include "InfoAppMenu.h"
|
||||
#include "../PlayerCursor.h"
|
||||
|
||||
#include <stack>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
|
||||
MainAppMenu::MainAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow) :
|
||||
AppMenu(menuStack, settings, renderWindow),
|
||||
playerCursor({1, 1, 1}) {
|
||||
|
||||
}
|
||||
|
||||
void MainAppMenu::computeFrame() {
|
||||
this->updateMetaBinds();
|
||||
this->playerCursor.updatePosition();
|
||||
|
||||
if (this->enterReleased) {
|
||||
if (this->playerCursor.getPosition().y == 0) {
|
||||
this->menuStack->push(std::make_shared<GameSettingsAppMenu>(this->menuStack, this->settings, this->renderWindow));
|
||||
}
|
||||
if (this->playerCursor.getPosition().y == 1) {
|
||||
this->menuStack->push(std::make_shared<SettingsMainAppMenu>(this->menuStack, this->settings, this->renderWindow));
|
||||
}
|
||||
if (this->playerCursor.getPosition().y == 2) {
|
||||
this->menuStack->push(std::make_shared<InfoAppMenu>(this->menuStack, this->settings, this->renderWindow));
|
||||
}
|
||||
}
|
||||
if (this->escReleased) {
|
||||
this->menuStack->pop();
|
||||
}
|
||||
}
|
||||
|
||||
void MainAppMenu::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, {}, "JMINOS", 10.f, {});
|
||||
|
||||
this->placeTitle(text, this->playerCursor, "PLAY", 20.f, sf::Vector2u{0, 0});
|
||||
this->placeTitle(text, this->playerCursor, "SETTINGS", 30.f, sf::Vector2u{0, 1});
|
||||
this->placeTitle(text, this->playerCursor, "INFO", 40.f, sf::Vector2u{0, 2});
|
||||
|
||||
this->renderWindow->display();
|
||||
}
|
||||
21
src/GraphicalUI/AppMenus/MainAppMenu.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "AppMenu.h"
|
||||
#include "../PlayerCursor.h"
|
||||
|
||||
#include <stack>
|
||||
#include <memory>
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
|
||||
class MainAppMenu : public AppMenu {
|
||||
private:
|
||||
PlayerCursor playerCursor;
|
||||
|
||||
public:
|
||||
MainAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow);
|
||||
|
||||
void computeFrame() override;
|
||||
|
||||
void drawFrame() const override;
|
||||
};
|
||||
77
src/GraphicalUI/AppMenus/SettingsControlsAppMenu.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
#include "SettingsControlsAppMenu.h"
|
||||
|
||||
#include "AppMenu.h"
|
||||
#include "../PlayerCursor.h"
|
||||
|
||||
#include <stack>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
|
||||
SettingsControlsAppMenu::SettingsControlsAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow) :
|
||||
AppMenu(menuStack, settings, renderWindow),
|
||||
playerCursor({1, 1, 1}) {
|
||||
|
||||
}
|
||||
|
||||
void SettingsControlsAppMenu::computeFrame() {
|
||||
this->updateMetaBinds();
|
||||
this->playerCursor.updatePosition();
|
||||
|
||||
Player& playerControls = this->settings->getMenu().getPlayerControls();
|
||||
|
||||
switch (this->playerCursor.getPosition().y) {
|
||||
case 0 : {
|
||||
if (this->playerCursor.movedLeft()) {
|
||||
playerControls.setDAS(playerControls.getDAS() - 1);
|
||||
}
|
||||
if (this->playerCursor.movedRight()) {
|
||||
playerControls.setDAS(playerControls.getDAS() + 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 1 : {
|
||||
if (this->playerCursor.movedLeft()) {
|
||||
playerControls.setARR(playerControls.getARR() - 1);
|
||||
}
|
||||
if (this->playerCursor.movedRight()) {
|
||||
playerControls.setARR(playerControls.getARR() + 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 2 : {
|
||||
if (this->playerCursor.movedLeft()) {
|
||||
playerControls.setSDR(playerControls.getSDR() - 1);
|
||||
}
|
||||
if (this->playerCursor.movedRight()) {
|
||||
playerControls.setSDR(playerControls.getSDR() + 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (this->escReleased) {
|
||||
this->menuStack->pop();
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsControlsAppMenu::drawFrame() const {
|
||||
this->renderWindow->clear(sf::Color(200, 200, 200));
|
||||
|
||||
const Player& playerControls = this->settings->getMenu().readPlayerControls();
|
||||
|
||||
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, {}, "CONTROLS SETTINGS", 5.f, {});
|
||||
|
||||
sf::Vector2u windowSize = this->renderWindow->getSize();
|
||||
|
||||
this->placeText(text, this->playerCursor, "< DAS: " + std::to_string(playerControls.getDAS()) + " >", 5.f, 15.f, sf::Vector2u{0, 0});
|
||||
this->placeText(text, this->playerCursor, "< ARR: " + std::to_string(playerControls.getARR()) + " >", 5.f, 25.f, sf::Vector2u{0, 1});
|
||||
this->placeText(text, this->playerCursor, "< SDR: " + std::to_string(playerControls.getSDR()) + " >", 5.f, 35.f, sf::Vector2u{0, 2});
|
||||
|
||||
this->renderWindow->display();
|
||||
}
|
||||
21
src/GraphicalUI/AppMenus/SettingsControlsAppMenu.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "AppMenu.h"
|
||||
#include "../PlayerCursor.h"
|
||||
|
||||
#include <stack>
|
||||
#include <memory>
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
|
||||
class SettingsControlsAppMenu : public AppMenu {
|
||||
private:
|
||||
PlayerCursor playerCursor;
|
||||
|
||||
public:
|
||||
SettingsControlsAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow);
|
||||
|
||||
void computeFrame() override;
|
||||
|
||||
void drawFrame() const override;
|
||||
};
|
||||
128
src/GraphicalUI/AppMenus/SettingsKeybindsAppMenu.cpp
Normal file
@@ -0,0 +1,128 @@
|
||||
#include "SettingsKeybindsAppMenu.h"
|
||||
|
||||
#include "AppMenu.h"
|
||||
#include "../PlayerCursor.h"
|
||||
|
||||
#include <stack>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <regex>
|
||||
#include <filesystem>
|
||||
#include <algorithm>
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
|
||||
SettingsKeybindsAppMenu::SettingsKeybindsAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow) :
|
||||
AppMenu(menuStack, settings, renderWindow),
|
||||
playerCursor({12, 1}) {
|
||||
|
||||
this->selectedAnAction = false;
|
||||
|
||||
for (Action action : ACTION_LIST_IN_ORDER) {
|
||||
std::string textureName = ACTION_NAMES[action];
|
||||
textureName = std::regex_replace(textureName, std::regex(" "), "");
|
||||
|
||||
std::filesystem::path texturePath("data/images/keybinds/" + textureName + ".png");
|
||||
this->iconTextures[action] = sf::Texture(texturePath, false, {{0, 0}, {16, 16}});
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsKeybindsAppMenu::computeFrame() {
|
||||
this->updateMetaBinds();
|
||||
|
||||
if (!this->selectedAnAction) {
|
||||
this->playerCursor.updatePosition();
|
||||
|
||||
if (this->playerCursor.movedLeft()) {
|
||||
this->settings->selectPreviousKeybinds();
|
||||
}
|
||||
if (this->playerCursor.movedRight()) {
|
||||
this->settings->selectNextKeybinds();
|
||||
}
|
||||
}
|
||||
else {
|
||||
bool addedKeybind = false;
|
||||
for (const auto& [key, string] : KEYS_TO_STRING) {
|
||||
if (sf::Keyboard::isKeyPressed(key) && (key != sfKey::Enter) && (key != sfKey::Escape)) {
|
||||
this->settings->getKeybinds().addKey(this->actionSelected, key);
|
||||
addedKeybind = true;
|
||||
}
|
||||
|
||||
if (addedKeybind) {
|
||||
this->selectedAnAction = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this->enterReleased && this->settings->getKeybinds().isModifiable()) {
|
||||
this->selectedAnAction = !selectedAnAction;
|
||||
this->actionSelected = ACTION_LIST_IN_ORDER[this->playerCursor.getPosition().y - 1];
|
||||
}
|
||||
if (this->escReleased) {
|
||||
if (this->selectedAnAction) {
|
||||
this->settings->getKeybinds().clearKeys(this->actionSelected);
|
||||
this->selectedAnAction = false;
|
||||
}
|
||||
else {
|
||||
this->menuStack->pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsKeybindsAppMenu::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, {}, "KEYBINDS SETTINGS", 5.f, {});
|
||||
|
||||
if (this->settings->getKeybindsLayout() == CUSTOMIZABLE_KEYBINDS) {
|
||||
this->placeText(text, this->playerCursor, "< CUSTOM >", 5.f, 15.f, sf::Vector2u{0, 0});
|
||||
}
|
||||
else {
|
||||
this->placeText(text, this->playerCursor, "< DEFAULT " + std::to_string(this->settings->getKeybindsLayout() + 1) + " >", 5.f, 15.f, sf::Vector2u{0, 0});
|
||||
}
|
||||
|
||||
if (this->selectedAnAction) {
|
||||
text.setOutlineColor(sf::Color(255, 0, 0));
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
int firstElem = std::clamp(((int) this->playerCursor.getPosition().y) - 2, 0, 8);
|
||||
|
||||
for (Action action : ACTION_LIST_IN_ORDER) {
|
||||
if (i >= firstElem && i < (firstElem + 3)) {
|
||||
sf::String string;
|
||||
bool firstKey = true;
|
||||
for (sfKey key : this->settings->getKeybinds().getKeybinds(action)) {
|
||||
if (KEYS_TO_STRING.contains(key)) {
|
||||
std::string keyString = KEYS_TO_STRING.at(key);
|
||||
if (firstKey) {
|
||||
string += keyString;
|
||||
firstKey = false;
|
||||
}
|
||||
else {
|
||||
string += ", " + keyString;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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]);
|
||||
sprite.setOrigin(sprite.getLocalBounds().getCenter());
|
||||
sprite.setPosition(sf::Vector2f(8.f, ((i - firstElem) * 10) + 25.f) * (float) this->settings->getWindowSizeMultiplier());
|
||||
sprite.setScale(sprite.getScale() * ((float) this->settings->getWindowSizeMultiplier() / 2));
|
||||
this->renderWindow->draw(sprite);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
this->renderWindow->display();
|
||||
}
|
||||
24
src/GraphicalUI/AppMenus/SettingsKeybindsAppMenu.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "AppMenu.h"
|
||||
#include "../PlayerCursor.h"
|
||||
|
||||
#include <stack>
|
||||
#include <memory>
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
|
||||
class SettingsKeybindsAppMenu : public AppMenu {
|
||||
private:
|
||||
PlayerCursor playerCursor;
|
||||
sf::Texture iconTextures[11];
|
||||
bool selectedAnAction;
|
||||
Action actionSelected;
|
||||
|
||||
public:
|
||||
SettingsKeybindsAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow);
|
||||
|
||||
void computeFrame() override;
|
||||
|
||||
void drawFrame() const override;
|
||||
};
|
||||
79
src/GraphicalUI/AppMenus/SettingsMainAppMenu.cpp
Normal file
@@ -0,0 +1,79 @@
|
||||
#include "SettingsMainAppMenu.h"
|
||||
|
||||
#include "AppMenu.h"
|
||||
#include "SettingsKeybindsAppMenu.h"
|
||||
#include "SettingsControlsAppMenu.h"
|
||||
#include "../PlayerCursor.h"
|
||||
|
||||
#include <stack>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
|
||||
SettingsMainAppMenu::SettingsMainAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow) :
|
||||
AppMenu(menuStack, settings, renderWindow),
|
||||
playerCursor({1, 1, 1, 1}) {
|
||||
|
||||
}
|
||||
|
||||
void SettingsMainAppMenu::computeFrame() {
|
||||
this->updateMetaBinds();
|
||||
this->playerCursor.updatePosition();
|
||||
|
||||
switch (this->playerCursor.getPosition().y) {
|
||||
case 2 : {
|
||||
if (this->playerCursor.movedLeft()) {
|
||||
if (this->settings->shortenWindow()) {
|
||||
this->settings->changeVideoMode(*this->renderWindow);
|
||||
}
|
||||
}
|
||||
if (this->playerCursor.movedRight()) {
|
||||
if (this->settings->widenWindow()) {
|
||||
this->settings->changeVideoMode(*this->renderWindow);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 3 : {
|
||||
if (this->playerCursor.movedLeft()) {
|
||||
this->settings->lowerMasterVolume();
|
||||
}
|
||||
if (this->playerCursor.movedRight()) {
|
||||
this->settings->raiseMasterVolume();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (this->enterReleased) {
|
||||
if (this->playerCursor.getPosition().y == 0) {
|
||||
this->menuStack->push(std::make_shared<SettingsKeybindsAppMenu>(this->menuStack, this->settings, this->renderWindow));
|
||||
}
|
||||
if (this->playerCursor.getPosition().y == 1) {
|
||||
this->menuStack->push(std::make_shared<SettingsControlsAppMenu>(this->menuStack, this->settings, this->renderWindow));
|
||||
}
|
||||
}
|
||||
if (this->escReleased) {
|
||||
this->menuStack->pop();
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsMainAppMenu::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, {}, "SETTINGS", 5.f, {});
|
||||
|
||||
sf::Vector2u windowSize = this->renderWindow->getSize();
|
||||
|
||||
this->placeText(text, this->playerCursor, "CHANGE KEYBINDS", 5.f, 15.f, sf::Vector2u{0, 0});
|
||||
this->placeText(text, this->playerCursor, "CHANGE CONTROLS", 5.f, 25.f, sf::Vector2u{0, 1});
|
||||
this->placeText(text, this->playerCursor, "< WINDOW SIZE: " + std::to_string(windowSize.x) + "x" + std::to_string(windowSize.y) + " >", 5.f, 35.f, sf::Vector2u{0, 2});
|
||||
this->placeText(text, this->playerCursor, "< VOLUME: " + std::to_string(this->settings->getMasterVolume()) + "% >", 5.f, 45.f, sf::Vector2u{0, 3});
|
||||
|
||||
this->renderWindow->display();
|
||||
}
|
||||
21
src/GraphicalUI/AppMenus/SettingsMainAppMenu.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "AppMenu.h"
|
||||
#include "../PlayerCursor.h"
|
||||
|
||||
#include <stack>
|
||||
#include <memory>
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
|
||||
class SettingsMainAppMenu : public AppMenu {
|
||||
private:
|
||||
PlayerCursor playerCursor;
|
||||
|
||||
public:
|
||||
SettingsMainAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow);
|
||||
|
||||
void computeFrame() override;
|
||||
|
||||
void drawFrame() const override;
|
||||
};
|
||||
53
src/GraphicalUI/GraphApp.cpp
Normal file
@@ -0,0 +1,53 @@
|
||||
#include "GraphApp.h"
|
||||
|
||||
#include "AppMenus/AppMenu.h"
|
||||
#include "AppMenus/MainAppMenu.h"
|
||||
#include "Settings.h"
|
||||
|
||||
#include <stack>
|
||||
#include <memory>
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
static const double TIME_BETWEEN_FRAMES = (1000.f / FRAMES_PER_SECOND);
|
||||
|
||||
|
||||
GraphApp::GraphApp() {
|
||||
this->settings = std::make_shared<Settings>();
|
||||
this->menuStack = std::make_shared<MenuStack>();
|
||||
this->renderWindow = std::make_shared<sf::RenderWindow>();
|
||||
}
|
||||
|
||||
void GraphApp::run() {
|
||||
this->settings->changeVideoMode(*this->renderWindow);
|
||||
this->menuStack->push(std::make_shared<MainAppMenu>(this->menuStack, this->settings, this->renderWindow));
|
||||
|
||||
bool quit = false;
|
||||
double timeAtNextFrame = 0;
|
||||
sf::Clock clock;
|
||||
while (!quit) {
|
||||
while (const std::optional event = this->renderWindow->pollEvent()) {
|
||||
if (event->is<sf::Event::Closed>()) {
|
||||
quit = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!quit) {
|
||||
if (clock.getElapsedTime().asMilliseconds() > timeAtNextFrame) {
|
||||
this->menuStack->top()->computeFrame();
|
||||
|
||||
if (this->menuStack->empty()) {
|
||||
quit = true;
|
||||
}
|
||||
else {
|
||||
this->menuStack->top()->drawFrame();
|
||||
}
|
||||
|
||||
while (clock.getElapsedTime().asMilliseconds() > timeAtNextFrame) {
|
||||
timeAtNextFrame += TIME_BETWEEN_FRAMES;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this->settings->saveSettingsToFile();
|
||||
renderWindow->close();
|
||||
}
|
||||
21
src/GraphicalUI/GraphApp.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "AppMenus/AppMenu.h"
|
||||
#include "Settings.h"
|
||||
|
||||
#include <stack>
|
||||
#include <memory>
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
|
||||
class GraphApp {
|
||||
private:
|
||||
std::shared_ptr<Settings> settings;
|
||||
std::shared_ptr<MenuStack> menuStack;
|
||||
std::shared_ptr<sf::RenderWindow> renderWindow;
|
||||
|
||||
public:
|
||||
GraphApp();
|
||||
|
||||
void run();
|
||||
};
|
||||
98
src/GraphicalUI/Keybinds.cpp
Normal file
@@ -0,0 +1,98 @@
|
||||
#include "Keybinds.h"
|
||||
|
||||
#include "../Core/Action.h"
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <fstream>
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
|
||||
Keybinds::Keybinds(int layoutNumber) :
|
||||
layoutNumber(layoutNumber) {
|
||||
|
||||
for (Action action : ACTION_LIST_IN_ORDER) {
|
||||
this->keybinds.insert({action, std::set<sfKey>()});
|
||||
}
|
||||
this->modifiable = (layoutNumber == CUSTOMIZABLE_KEYBINDS);
|
||||
|
||||
this->loadKeybindsFromFile();
|
||||
}
|
||||
|
||||
void Keybinds::loadKeybindsFromFile() {
|
||||
std::ifstream layoutFile("data/config/keybinds/layout" + std::to_string(this->layoutNumber) + ".bin", std::ios::binary);
|
||||
|
||||
for (Action action : ACTION_LIST_IN_ORDER) {
|
||||
this->keybinds.at(action).clear();
|
||||
}
|
||||
|
||||
char byte;
|
||||
while (layoutFile.peek() != EOF) {
|
||||
layoutFile.get(byte);
|
||||
Action action = Action(byte);
|
||||
|
||||
bool separatorMet = false;
|
||||
while (!separatorMet) {
|
||||
layoutFile.get(byte);
|
||||
if (byte == (char) 0xFF) {
|
||||
separatorMet = true;
|
||||
}
|
||||
else {
|
||||
this->keybinds.at(action).insert(sfKey(byte));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Keybinds::saveKeybindsToFile() const {
|
||||
if (!this->modifiable) return;
|
||||
|
||||
std::ofstream layoutFile("data/config/keybinds/layout" + std::to_string(this->layoutNumber) + ".bin", std::ios::trunc | std::ios::binary);
|
||||
|
||||
char byte;
|
||||
for (Action action : ACTION_LIST_IN_ORDER) {
|
||||
byte = action;
|
||||
layoutFile.write(&byte, 1);
|
||||
|
||||
for (sfKey key : this->keybinds.at(action)) {
|
||||
byte = (int) key;
|
||||
layoutFile.write(&byte, 1);
|
||||
}
|
||||
|
||||
byte = 0xFF;
|
||||
layoutFile.write(&byte, 1);
|
||||
}
|
||||
}
|
||||
|
||||
void Keybinds::addKey(Action action, sfKey key) {
|
||||
if (!this->modifiable) return;
|
||||
if (!KEYS_TO_STRING.contains(key)) return;
|
||||
|
||||
this->keybinds.at(action).insert(key);
|
||||
}
|
||||
|
||||
void Keybinds::clearKeys(Action action) {
|
||||
if (!this->modifiable) return;
|
||||
|
||||
this->keybinds.at(action).clear();
|
||||
}
|
||||
|
||||
bool Keybinds::isModifiable() const {
|
||||
return this->modifiable;
|
||||
}
|
||||
|
||||
const std::set<Action> Keybinds::getActions(sfKey key) const {
|
||||
std::set<Action> actions;
|
||||
|
||||
for (const auto& [action, keys] : this->keybinds) {
|
||||
if (keys.contains(key)) {
|
||||
actions.insert(action);
|
||||
}
|
||||
}
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
const std::set<sfKey>& Keybinds::getKeybinds(Action action) const {
|
||||
return this->keybinds.at(action);
|
||||
}
|
||||
156
src/GraphicalUI/Keybinds.h
Normal file
@@ -0,0 +1,156 @@
|
||||
#pragma once
|
||||
|
||||
#include "../Core/Action.h"
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <optional>
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
using sfKey = sf::Keyboard::Key;
|
||||
|
||||
static const int NUMBER_OF_KEYBINDS = 5;
|
||||
static const int CUSTOMIZABLE_KEYBINDS = NUMBER_OF_KEYBINDS - 1;
|
||||
|
||||
|
||||
class Keybinds {
|
||||
private:
|
||||
std::map<Action, std::set<sfKey>> keybinds;
|
||||
int layoutNumber;
|
||||
bool modifiable;
|
||||
|
||||
public:
|
||||
Keybinds(int layoutNumber);
|
||||
|
||||
void loadKeybindsFromFile();
|
||||
|
||||
void saveKeybindsToFile() const;
|
||||
|
||||
void addKey(Action action, sfKey key);
|
||||
|
||||
void clearKeys(Action action);
|
||||
|
||||
bool isModifiable() const;
|
||||
|
||||
const std::set<Action> getActions(sfKey key) const;
|
||||
|
||||
const std::set<sfKey>& getKeybinds(Action action) const;
|
||||
};
|
||||
|
||||
|
||||
inline std::string setStringToUpperCase(std::string&& str) {
|
||||
std::transform(str.begin(), str.end(), str.begin(), ::toupper);
|
||||
return str;
|
||||
}
|
||||
|
||||
inline std::string setStringToUpperCase(const std::string& str) {
|
||||
std::string result = str;
|
||||
std::transform(result.begin(), result.end(), result.begin(), ::toupper);
|
||||
return result;
|
||||
}
|
||||
|
||||
#define INSERT_MAPPING(identifier) {sfKey::identifier, setStringToUpperCase(#identifier)}
|
||||
static const std::map<sfKey, sf::String> KEYS_TO_STRING = {
|
||||
INSERT_MAPPING(A),
|
||||
INSERT_MAPPING(B),
|
||||
INSERT_MAPPING(C),
|
||||
INSERT_MAPPING(D),
|
||||
INSERT_MAPPING(E),
|
||||
INSERT_MAPPING(F),
|
||||
INSERT_MAPPING(G),
|
||||
INSERT_MAPPING(H),
|
||||
INSERT_MAPPING(I),
|
||||
INSERT_MAPPING(J),
|
||||
INSERT_MAPPING(K),
|
||||
INSERT_MAPPING(L),
|
||||
INSERT_MAPPING(M),
|
||||
INSERT_MAPPING(N),
|
||||
INSERT_MAPPING(O),
|
||||
INSERT_MAPPING(P),
|
||||
INSERT_MAPPING(Q),
|
||||
INSERT_MAPPING(R),
|
||||
INSERT_MAPPING(S),
|
||||
INSERT_MAPPING(T),
|
||||
INSERT_MAPPING(U),
|
||||
INSERT_MAPPING(V),
|
||||
INSERT_MAPPING(W),
|
||||
INSERT_MAPPING(X),
|
||||
INSERT_MAPPING(Y),
|
||||
INSERT_MAPPING(Z),
|
||||
INSERT_MAPPING(Num0),
|
||||
INSERT_MAPPING(Num1),
|
||||
INSERT_MAPPING(Num2),
|
||||
INSERT_MAPPING(Num3),
|
||||
INSERT_MAPPING(Num4),
|
||||
INSERT_MAPPING(Num5),
|
||||
INSERT_MAPPING(Num6),
|
||||
INSERT_MAPPING(Num7),
|
||||
INSERT_MAPPING(Num8),
|
||||
INSERT_MAPPING(Num9),
|
||||
INSERT_MAPPING(Escape),
|
||||
INSERT_MAPPING(LControl),
|
||||
INSERT_MAPPING(LShift),
|
||||
INSERT_MAPPING(LAlt),
|
||||
INSERT_MAPPING(LSystem),
|
||||
INSERT_MAPPING(RControl),
|
||||
INSERT_MAPPING(RShift),
|
||||
INSERT_MAPPING(RAlt),
|
||||
INSERT_MAPPING(RSystem),
|
||||
INSERT_MAPPING(Menu),
|
||||
INSERT_MAPPING(LBracket),
|
||||
INSERT_MAPPING(RBracket),
|
||||
INSERT_MAPPING(Semicolon),
|
||||
INSERT_MAPPING(Comma),
|
||||
INSERT_MAPPING(Period),
|
||||
INSERT_MAPPING(Apostrophe),
|
||||
INSERT_MAPPING(Slash),
|
||||
INSERT_MAPPING(Backslash),
|
||||
INSERT_MAPPING(Grave),
|
||||
INSERT_MAPPING(Equal),
|
||||
INSERT_MAPPING(Hyphen),
|
||||
INSERT_MAPPING(Space),
|
||||
INSERT_MAPPING(Enter),
|
||||
INSERT_MAPPING(Backspace),
|
||||
INSERT_MAPPING(Tab),
|
||||
INSERT_MAPPING(PageUp),
|
||||
INSERT_MAPPING(PageDown),
|
||||
INSERT_MAPPING(End),
|
||||
INSERT_MAPPING(Home),
|
||||
INSERT_MAPPING(Insert),
|
||||
INSERT_MAPPING(Delete),
|
||||
INSERT_MAPPING(Add),
|
||||
INSERT_MAPPING(Subtract),
|
||||
INSERT_MAPPING(Multiply),
|
||||
INSERT_MAPPING(Divide),
|
||||
INSERT_MAPPING(Left),
|
||||
INSERT_MAPPING(Right),
|
||||
INSERT_MAPPING(Up),
|
||||
INSERT_MAPPING(Down),
|
||||
INSERT_MAPPING(Numpad0),
|
||||
INSERT_MAPPING(Numpad1),
|
||||
INSERT_MAPPING(Numpad2),
|
||||
INSERT_MAPPING(Numpad3),
|
||||
INSERT_MAPPING(Numpad4),
|
||||
INSERT_MAPPING(Numpad5),
|
||||
INSERT_MAPPING(Numpad6),
|
||||
INSERT_MAPPING(Numpad7),
|
||||
INSERT_MAPPING(Numpad8),
|
||||
INSERT_MAPPING(Numpad9),
|
||||
INSERT_MAPPING(F1),
|
||||
INSERT_MAPPING(F2),
|
||||
INSERT_MAPPING(F3),
|
||||
INSERT_MAPPING(F4),
|
||||
INSERT_MAPPING(F5),
|
||||
INSERT_MAPPING(F6),
|
||||
INSERT_MAPPING(F7),
|
||||
INSERT_MAPPING(F8),
|
||||
INSERT_MAPPING(F9),
|
||||
INSERT_MAPPING(F10),
|
||||
INSERT_MAPPING(F11),
|
||||
INSERT_MAPPING(F12),
|
||||
INSERT_MAPPING(F13),
|
||||
INSERT_MAPPING(F14),
|
||||
INSERT_MAPPING(F15),
|
||||
INSERT_MAPPING(Pause)
|
||||
};
|
||||
#undef INSERT_MAPPING
|
||||
21
src/GraphicalUI/PiecesType.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
enum PiecesType {
|
||||
CONVEX_PIECES,
|
||||
HOLELESS_PIECES,
|
||||
OTHER_PIECES,
|
||||
ALL_PIECES,
|
||||
SINGLE_PIECE
|
||||
};
|
||||
|
||||
|
||||
inline int getSizeOfPieces(PiecesType type) {
|
||||
if (type < SINGLE_PIECE) return 0;
|
||||
|
||||
else return (type - SINGLE_PIECE + 1);
|
||||
}
|
||||
|
||||
inline PiecesType createSinglePieceType(int size) {
|
||||
return PiecesType(SINGLE_PIECE + size - 1);
|
||||
}
|
||||
121
src/GraphicalUI/PlayerCursor.cpp
Normal file
@@ -0,0 +1,121 @@
|
||||
#include "PlayerCursor.h"
|
||||
|
||||
#include "Keybinds.h"
|
||||
#include "Settings.h"
|
||||
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
static const int MENU_DAS = FRAMES_PER_SECOND / 2;
|
||||
|
||||
|
||||
PlayerCursor::PlayerCursor(std::vector<unsigned int> rows) :
|
||||
rows(rows) {
|
||||
|
||||
this->position = sf::Vector2u({0, 0});
|
||||
this->leftDAS = 0;
|
||||
this->rightDAS = 0;
|
||||
this->upDAS = 0;
|
||||
this->downDAS = 0;
|
||||
}
|
||||
|
||||
void PlayerCursor::updatePosition() {
|
||||
(sf::Keyboard::isKeyPressed(sfKey::Left)) ? (this->leftDAS++) : (this->leftDAS = 0);
|
||||
if (this->shouldMove(this->leftDAS)) {
|
||||
this->moveLeft();
|
||||
}
|
||||
|
||||
(sf::Keyboard::isKeyPressed(sfKey::Right)) ? (this->rightDAS++) : (this->rightDAS = 0);
|
||||
if (this->shouldMove(this->rightDAS)) {
|
||||
this->moveRight();
|
||||
}
|
||||
|
||||
(sf::Keyboard::isKeyPressed(sfKey::Up)) ? (this->upDAS++) : (this->upDAS = 0);
|
||||
if (this->shouldMove(this->upDAS)) {
|
||||
this->moveUp();
|
||||
}
|
||||
|
||||
(sf::Keyboard::isKeyPressed(sfKey::Down)) ? (this->downDAS++) : (this->downDAS = 0);
|
||||
if (this->shouldMove(this->downDAS)) {
|
||||
this->moveDown();
|
||||
}
|
||||
}
|
||||
|
||||
bool PlayerCursor::movedLeft() const {
|
||||
return this->shouldMove(this->leftDAS);
|
||||
}
|
||||
|
||||
bool PlayerCursor::movedRight() const {
|
||||
return this->shouldMove(this->rightDAS);
|
||||
}
|
||||
|
||||
bool PlayerCursor::movedUp() const {
|
||||
return this->shouldMove(this->upDAS);
|
||||
}
|
||||
|
||||
bool PlayerCursor::movedDown() const {
|
||||
return this->shouldMove(this->downDAS);
|
||||
}
|
||||
|
||||
void PlayerCursor::goToPosition(const sf::Vector2u& newPosition) {
|
||||
if (this->rows.size() > newPosition.y) {
|
||||
if (this->rows.at(newPosition.y) > newPosition.x) {
|
||||
this->position = newPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const sf::Vector2u& PlayerCursor::getPosition() const {
|
||||
return this->position;
|
||||
}
|
||||
|
||||
bool PlayerCursor::shouldMove(int DAS) const {
|
||||
return (DAS == 1
|
||||
|| (DAS > MENU_DAS && (DAS % 5) == 0)
|
||||
|| (DAS > (FRAMES_PER_SECOND * 2)));
|
||||
}
|
||||
|
||||
void PlayerCursor::moveLeft() {
|
||||
if (this->position.x == 0) {
|
||||
this->position.x = this->rows.at(this->position.y) - 1;
|
||||
}
|
||||
else {
|
||||
this->position.x--;
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerCursor::moveRight() {
|
||||
if (this->position.x == this->rows.at(this->position.y) - 1) {
|
||||
this->position.x = 0;
|
||||
}
|
||||
else {
|
||||
this->position.x++;
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerCursor::moveUp() {
|
||||
if (this->position.y == 0) {
|
||||
this->position.y = this->rows.size() - 1;
|
||||
}
|
||||
else {
|
||||
this->position.y--;
|
||||
}
|
||||
|
||||
if (this->position.x >= this->rows.at(this->position.y)) {
|
||||
this->position.x = this->rows.at(this->position.y) - 1;
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerCursor::moveDown() {
|
||||
if (this->position.y == this->rows.size() - 1) {
|
||||
this->position.y = 0;
|
||||
}
|
||||
else {
|
||||
this->position.y++;
|
||||
}
|
||||
|
||||
if (this->position.x >= this->rows.at(this->position.y)) {
|
||||
this->position.x = this->rows.at(this->position.y) - 1;
|
||||
}
|
||||
}
|
||||
43
src/GraphicalUI/PlayerCursor.h
Normal file
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
|
||||
class PlayerCursor {
|
||||
private:
|
||||
std::vector<unsigned int> rows;
|
||||
sf::Vector2u position;
|
||||
int leftDAS;
|
||||
int rightDAS;
|
||||
int upDAS;
|
||||
int downDAS;
|
||||
|
||||
public:
|
||||
PlayerCursor(std::vector<unsigned int> rows);
|
||||
|
||||
void updatePosition();
|
||||
|
||||
bool movedLeft() const;
|
||||
|
||||
bool movedRight() const;
|
||||
|
||||
bool movedUp() const;
|
||||
|
||||
bool movedDown() const;
|
||||
|
||||
void goToPosition(const sf::Vector2u& newPosition);
|
||||
|
||||
const sf::Vector2u& getPosition() const;
|
||||
|
||||
private:
|
||||
bool shouldMove(int DAS) const;
|
||||
|
||||
void moveLeft();
|
||||
|
||||
void moveRight();
|
||||
|
||||
void moveUp();
|
||||
|
||||
void moveDown();
|
||||
};
|
||||
269
src/GraphicalUI/Settings.cpp
Normal file
@@ -0,0 +1,269 @@
|
||||
#include "Settings.h"
|
||||
|
||||
#include "../Core/Menu.h"
|
||||
#include "Keybinds.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <algorithm>
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
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;
|
||||
|
||||
|
||||
Settings::Settings() {
|
||||
for (int i = 1; i <= MAXIMUM_PIECES_SIZE; i++) {
|
||||
this->menu.getPiecesList().loadPieces(i);
|
||||
}
|
||||
|
||||
this->keybinds.reserve(NUMBER_OF_KEYBINDS);
|
||||
for (int i = 0; i < NUMBER_OF_KEYBINDS; i++) {
|
||||
this->keybinds.emplace_back(i);
|
||||
}
|
||||
|
||||
this->loadSettingsFromFile();
|
||||
}
|
||||
|
||||
void Settings::loadSettingsFromFile() {
|
||||
std::ifstream settingsFile("data/config/settings.bin", std::ios::binary);
|
||||
char byte;
|
||||
|
||||
// keybind layout
|
||||
settingsFile.get(byte);
|
||||
this->chosenKeybinds = byte;
|
||||
|
||||
// DAS tuning
|
||||
settingsFile.get(byte);
|
||||
this->menu.getPlayerControls().setDAS(byte);
|
||||
|
||||
// ARR tuning
|
||||
settingsFile.get(byte);
|
||||
this->menu.getPlayerControls().setARR(byte);
|
||||
|
||||
// SDR tuning
|
||||
settingsFile.get(byte);
|
||||
this->menu.getPlayerControls().setSDR(byte);
|
||||
|
||||
// window size mode
|
||||
settingsFile.get(byte);
|
||||
this->windowSizeMode = byte;
|
||||
|
||||
// master volume
|
||||
settingsFile.get(byte);
|
||||
this->masterVolume = byte;
|
||||
|
||||
// gamemode
|
||||
settingsFile.get(byte);
|
||||
this->gamemode = Gamemode(byte);
|
||||
|
||||
// board width
|
||||
settingsFile.get(byte);
|
||||
this->menu.setBoardWidth(byte);
|
||||
|
||||
// board height
|
||||
settingsFile.get(byte);
|
||||
this->menu.setBoardHeight(byte);
|
||||
|
||||
// piece distribution
|
||||
settingsFile.get(byte);
|
||||
//TODO
|
||||
if (byte == 2) {
|
||||
for (int i = 1; i <= 15; i++) {
|
||||
settingsFile.get(byte);
|
||||
//TODO
|
||||
}
|
||||
}
|
||||
|
||||
// selected pieces
|
||||
char pieceType;
|
||||
char pieceValue;
|
||||
this->selectedPieces.clear();
|
||||
while (settingsFile.get(pieceType)) {
|
||||
if (settingsFile.eof()) break;
|
||||
|
||||
settingsFile.get(pieceValue);
|
||||
this->selectedPieces.push_back({PiecesType(pieceType), pieceValue});
|
||||
}
|
||||
this->confirmSelectedPieces();
|
||||
}
|
||||
|
||||
void Settings::saveSettingsToFile() const {
|
||||
this->keybinds.at(CUSTOMIZABLE_KEYBINDS).saveKeybindsToFile();
|
||||
|
||||
std::ofstream settingsFile("data/config/settings.bin", std::ios::trunc | std::ios::binary);
|
||||
char byte;
|
||||
|
||||
// keybind layout
|
||||
byte = this->chosenKeybinds;
|
||||
settingsFile.write(&byte, 1);
|
||||
|
||||
// DAS tuning
|
||||
byte = this->menu.readPlayerControls().getDAS();
|
||||
settingsFile.write(&byte, 1);
|
||||
|
||||
// ARR tuning
|
||||
byte = this->menu.readPlayerControls().getARR();
|
||||
settingsFile.write(&byte, 1);
|
||||
|
||||
// SDR tuning
|
||||
byte = this->menu.readPlayerControls().getSDR();
|
||||
settingsFile.write(&byte, 1);
|
||||
|
||||
// window size mode
|
||||
byte = this->windowSizeMode;
|
||||
settingsFile.write(&byte, 1);
|
||||
|
||||
// master volume
|
||||
byte = this->masterVolume;
|
||||
settingsFile.write(&byte, 1);
|
||||
|
||||
// gamemode
|
||||
byte = this->gamemode;
|
||||
settingsFile.write(&byte, 1);
|
||||
|
||||
// board width
|
||||
byte = this->menu.getBoardWidth();
|
||||
settingsFile.write(&byte, 1);
|
||||
|
||||
// board height
|
||||
byte = this->menu.getBoardHeight();
|
||||
settingsFile.write(&byte, 1);
|
||||
|
||||
// piece distribution
|
||||
//TODO
|
||||
settingsFile.write(&byte, 1);
|
||||
|
||||
// selected pieces
|
||||
for (const auto& [type, value] : this->selectedPieces) {
|
||||
byte = type;
|
||||
settingsFile.write(&byte, 1);
|
||||
byte = value;
|
||||
settingsFile.write(&byte, 1);
|
||||
}
|
||||
}
|
||||
|
||||
bool Settings::selectNextKeybinds() {
|
||||
if (this->chosenKeybinds < (NUMBER_OF_KEYBINDS - 1)) {
|
||||
this->chosenKeybinds++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Settings::selectPreviousKeybinds() {
|
||||
if (this->chosenKeybinds > 0) {
|
||||
this->chosenKeybinds--;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Settings::canModifyCurrentKeybinds() const {
|
||||
return (this->chosenKeybinds == CUSTOMIZABLE_KEYBINDS);
|
||||
}
|
||||
|
||||
bool Settings::widenWindow() {
|
||||
if (this->windowSizeMode < WINDOW_SIZE_LAST_MODE) {
|
||||
this->windowSizeMode++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Settings::shortenWindow() {
|
||||
if (this->windowSizeMode > 0) {
|
||||
this->windowSizeMode--;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Settings::changeVideoMode(sf::RenderWindow& window) const {
|
||||
sf::VideoMode videoMode(BASE_WINDOW_SIZE * (unsigned int) WINDOW_SIZE_MULTIPLIERS[this->windowSizeMode]);
|
||||
window.create(videoMode, "jminos", sf::Style::Close | sf::Style::Titlebar);
|
||||
|
||||
sf::Vector2u desktopSize = sf::VideoMode::getDesktopMode().size;
|
||||
sf::Vector2u windowSize = window.getSize();
|
||||
window.setPosition(sf::Vector2i((desktopSize.x / 2) - (windowSize.x / 2), (desktopSize.y / 2) - (windowSize.y / 2)));
|
||||
}
|
||||
|
||||
bool Settings::raiseMasterVolume() {
|
||||
if (this->masterVolume < 100) {
|
||||
this->masterVolume = std::min(this->masterVolume + 5, 100);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Settings::lowerMasterVolume() {
|
||||
if (this->masterVolume > 0) {
|
||||
this->masterVolume = std::max(this->masterVolume - 5, 0);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Settings::setGamemode(Gamemode gamemode) {
|
||||
this->gamemode = gamemode;
|
||||
}
|
||||
|
||||
void Settings::selectPieces(PiecesType type, int value) {
|
||||
this->selectedPieces.emplace_back(type, value);
|
||||
}
|
||||
|
||||
void Settings::unselectPieces(int index) {
|
||||
if (index >= this->selectedPieces.size()) return;
|
||||
|
||||
this->selectedPieces.erase(this->selectedPieces.begin() + index);
|
||||
}
|
||||
|
||||
void Settings::confirmSelectedPieces() {
|
||||
this->menu.getPiecesList().unselectAll();
|
||||
|
||||
for (const auto& [type, value] : this->selectedPieces) {
|
||||
int size = getSizeOfPieces(type);
|
||||
|
||||
if (size == 0) {
|
||||
switch (type) {
|
||||
case CONVEX_PIECES : {this->menu.getPiecesList().selectConvexPieces(value); break;}
|
||||
case HOLELESS_PIECES : {this->menu.getPiecesList().selectHolelessPieces(value); break;}
|
||||
case OTHER_PIECES : {this->menu.getPiecesList().selectOtherPieces(value); break;}
|
||||
case ALL_PIECES : {this->menu.getPiecesList().selectAllPieces(value); break;}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (size > MAXIMUM_PIECES_SIZE) return;
|
||||
|
||||
this->menu.getPiecesList().selectPiece(size, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Menu& Settings::getMenu() {
|
||||
return this->menu;
|
||||
}
|
||||
|
||||
Keybinds& Settings::getKeybinds() {
|
||||
return this->keybinds.at(this->chosenKeybinds);
|
||||
}
|
||||
|
||||
int Settings::getKeybindsLayout() const {
|
||||
return this->chosenKeybinds;
|
||||
}
|
||||
|
||||
Gamemode Settings::getGamemode() const {
|
||||
return this->gamemode;
|
||||
}
|
||||
|
||||
int Settings::getWindowSizeMultiplier() const {
|
||||
return WINDOW_SIZE_MULTIPLIERS[this->windowSizeMode];
|
||||
}
|
||||
|
||||
int Settings::getMasterVolume() const {
|
||||
return this->masterVolume;
|
||||
}
|
||||
|
||||
const std::vector<std::pair<PiecesType, int>>& Settings::getSelectedPieces() const {
|
||||
return this->selectedPieces;
|
||||
}
|
||||
75
src/GraphicalUI/Settings.h
Normal file
@@ -0,0 +1,75 @@
|
||||
#pragma once
|
||||
|
||||
#include "../Core/Menu.h"
|
||||
#include "Keybinds.h"
|
||||
#include "PiecesType.h"
|
||||
|
||||
#include <SFML/Graphics.hpp>
|
||||
#include <vector>
|
||||
|
||||
static const int MAXIMUM_BOARD_WIDTH = 40;
|
||||
static const int MAXIMUM_BOARD_HEIGHT = 40;
|
||||
|
||||
//#define __JMINOS_RELEASE__
|
||||
#ifdef __JMINOS_RELEASE__
|
||||
static const int MAXIMUM_PIECES_SIZE = 15;
|
||||
#else
|
||||
static const int MAXIMUM_PIECES_SIZE = 10;
|
||||
#endif
|
||||
|
||||
|
||||
class Settings {
|
||||
private:
|
||||
Menu menu;
|
||||
std::vector<Keybinds> keybinds;
|
||||
int chosenKeybinds;
|
||||
int windowSizeMode;
|
||||
int masterVolume;
|
||||
Gamemode gamemode;
|
||||
std::vector<std::pair<PiecesType, int>> selectedPieces;
|
||||
|
||||
public:
|
||||
Settings();
|
||||
|
||||
void loadSettingsFromFile();
|
||||
|
||||
void saveSettingsToFile() const;
|
||||
|
||||
bool selectNextKeybinds();
|
||||
|
||||
bool selectPreviousKeybinds();
|
||||
|
||||
bool canModifyCurrentKeybinds() const;
|
||||
|
||||
bool widenWindow();
|
||||
|
||||
bool shortenWindow();
|
||||
|
||||
void changeVideoMode(sf::RenderWindow& window) const;
|
||||
|
||||
bool raiseMasterVolume();
|
||||
|
||||
bool lowerMasterVolume();
|
||||
|
||||
void setGamemode(Gamemode gamemode);
|
||||
|
||||
void selectPieces(PiecesType type, int value);
|
||||
|
||||
void unselectPieces(int index);
|
||||
|
||||
void confirmSelectedPieces();
|
||||
|
||||
Menu& getMenu();
|
||||
|
||||
Keybinds& getKeybinds();
|
||||
|
||||
int getKeybindsLayout() const;
|
||||
|
||||
Gamemode getGamemode() const;
|
||||
|
||||
int getWindowSizeMultiplier() const;
|
||||
|
||||
int getMasterVolume() const;
|
||||
|
||||
const std::vector<std::pair<PiecesType, int>>& getSelectedPieces() const;
|
||||
};
|
||||
162
src/GraphicalUI/main.cpp
Normal file
@@ -0,0 +1,162 @@
|
||||
#include "GraphApp.h"
|
||||
#include "../Pieces/PiecesFiles.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
|
||||
void resetSettingsFile();
|
||||
void resetKeybindFile(int layout);
|
||||
|
||||
|
||||
int main() {
|
||||
std::srand(std::time(NULL));
|
||||
|
||||
PiecesFiles pf;
|
||||
for (int i = 1; i <= MAXIMUM_PIECES_SIZE; i++) {
|
||||
if (!std::filesystem::exists("data/pieces/" + std::to_string(i) + "minos.bin")) {
|
||||
std::cout << "pieces files for size " << i << " not found, generating..." << std::endl;
|
||||
pf.savePieces(i);
|
||||
}
|
||||
}
|
||||
if (!std::filesystem::exists("data/config/settings.bin")) {
|
||||
std::cout << "settings file not found, generating..." << std::endl;
|
||||
resetSettingsFile();
|
||||
}
|
||||
for (int i = 0; i < NUMBER_OF_KEYBINDS; i++) {
|
||||
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;
|
||||
resetKeybindFile(i);
|
||||
}
|
||||
}
|
||||
|
||||
GraphApp UI;
|
||||
UI.run();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void resetSettingsFile() {
|
||||
std::ofstream settingsFile("data/config/settings.bin", std::ios::trunc | std::ios::binary);
|
||||
char byte;
|
||||
|
||||
Menu menu;
|
||||
|
||||
// keybind layout
|
||||
byte = 0;
|
||||
settingsFile.write(&byte, 1);
|
||||
|
||||
// DAS tuning
|
||||
byte = menu.getPlayerControls().getDAS();
|
||||
settingsFile.write(&byte, 1);
|
||||
|
||||
// ARR tuning
|
||||
byte = menu.getPlayerControls().getARR();
|
||||
settingsFile.write(&byte, 1);
|
||||
|
||||
// SDR tuning
|
||||
byte = menu.getPlayerControls().getSDR();
|
||||
settingsFile.write(&byte, 1);
|
||||
|
||||
// window size mode
|
||||
byte = 2;
|
||||
settingsFile.write(&byte, 1);
|
||||
|
||||
// master volume
|
||||
byte = 50;
|
||||
settingsFile.write(&byte, 1);
|
||||
|
||||
// gamemode
|
||||
byte = Gamemode(0);
|
||||
settingsFile.write(&byte, 1);
|
||||
|
||||
// board width
|
||||
byte = menu.getBoardWidth();
|
||||
settingsFile.write(&byte, 1);
|
||||
|
||||
// board height
|
||||
byte = menu.getBoardHeight();
|
||||
settingsFile.write(&byte, 1);
|
||||
|
||||
// piece distribution
|
||||
byte = 0;
|
||||
settingsFile.write(&byte, 1);
|
||||
|
||||
// selected pieces
|
||||
byte = ALL_PIECES;
|
||||
settingsFile.write(&byte, 1);
|
||||
byte = 4;
|
||||
settingsFile.write(&byte, 1);
|
||||
}
|
||||
|
||||
void resetKeybindFile(int layout) {
|
||||
if (layout < 0 || layout > 4) return;
|
||||
|
||||
std::ofstream layoutFile("data/config/keybinds/layout" + std::to_string(layout) + ".bin", std::ios::trunc | std::ios::binary);
|
||||
std::map<Action, sfKey> keybinds;
|
||||
|
||||
if (layout != 4) {
|
||||
keybinds.insert({PAUSE, sfKey::P});
|
||||
keybinds.insert({RETRY, sfKey::R});
|
||||
}
|
||||
|
||||
if (layout == 0) {
|
||||
keybinds.insert({MOVE_LEFT, sfKey::Left});
|
||||
keybinds.insert({MOVE_RIGHT, sfKey::Right});
|
||||
keybinds.insert({SOFT_DROP, sfKey::Down});
|
||||
keybinds.insert({HARD_DROP, sfKey::Space});
|
||||
keybinds.insert({ROTATE_CW, sfKey::Up});
|
||||
keybinds.insert({ROTATE_CCW, sfKey::Z});
|
||||
keybinds.insert({ROTATE_180, sfKey::X});
|
||||
keybinds.insert({ROTATE_0, sfKey::LShift});
|
||||
keybinds.insert({HOLD, sfKey::C});
|
||||
}
|
||||
if (layout == 1) {
|
||||
keybinds.insert({MOVE_LEFT, sfKey::Z});
|
||||
keybinds.insert({MOVE_RIGHT, sfKey::C});
|
||||
keybinds.insert({SOFT_DROP, sfKey::X});
|
||||
keybinds.insert({HARD_DROP, sfKey::S});
|
||||
keybinds.insert({ROTATE_CW, sfKey::M});
|
||||
keybinds.insert({ROTATE_CCW, sfKey::Comma});
|
||||
keybinds.insert({ROTATE_180, sfKey::J});
|
||||
keybinds.insert({ROTATE_0, sfKey::K});
|
||||
keybinds.insert({HOLD, sfKey::LShift});
|
||||
}
|
||||
if (layout == 2) {
|
||||
keybinds.insert({MOVE_LEFT, sfKey::A});
|
||||
keybinds.insert({MOVE_RIGHT, sfKey::D});
|
||||
keybinds.insert({SOFT_DROP, sfKey::W});
|
||||
keybinds.insert({HARD_DROP, sfKey::S});
|
||||
keybinds.insert({ROTATE_CW, sfKey::Left});
|
||||
keybinds.insert({ROTATE_CCW, sfKey::Right});
|
||||
keybinds.insert({ROTATE_180, sfKey::Up});
|
||||
keybinds.insert({ROTATE_0, sfKey::Down});
|
||||
keybinds.insert({HOLD, sfKey::RShift});
|
||||
}
|
||||
if (layout == 3) {
|
||||
keybinds.insert({MOVE_LEFT, sfKey::Left});
|
||||
keybinds.insert({MOVE_RIGHT, sfKey::Right});
|
||||
keybinds.insert({SOFT_DROP, sfKey::Down});
|
||||
keybinds.insert({HARD_DROP, sfKey::Up});
|
||||
keybinds.insert({ROTATE_CW, sfKey::E});
|
||||
keybinds.insert({ROTATE_CCW, sfKey::A});
|
||||
keybinds.insert({ROTATE_180, sfKey::Num2});
|
||||
keybinds.insert({ROTATE_0, sfKey::Tab});
|
||||
keybinds.insert({HOLD, sfKey::Z});
|
||||
}
|
||||
|
||||
char byte;
|
||||
for (Action action : ACTION_LIST_IN_ORDER) {
|
||||
byte = action;
|
||||
layoutFile.write(&byte, 1);
|
||||
|
||||
if (keybinds.contains(action)) {
|
||||
byte = (int) keybinds.at(action);
|
||||
layoutFile.write(&byte, 1);
|
||||
}
|
||||
|
||||
byte = 0xFF;
|
||||
layoutFile.write(&byte, 1);
|
||||
}
|
||||
}
|
||||
@@ -58,7 +58,7 @@ void TextApp::run() {
|
||||
default : std::cout << "Invalid answer!" << std::endl;
|
||||
}
|
||||
}
|
||||
std::cout << "===| SEE YA NEXT TIME! |===";
|
||||
std::cout << "===| SEE YA NEXT TIME! |===" << std::endl;
|
||||
}
|
||||
|
||||
void TextApp::choosePieces() {
|
||||
@@ -136,7 +136,6 @@ void TextApp::seeKeybinds() const {
|
||||
|
||||
void TextApp::defaultKeybinds() {
|
||||
this->keybinds.clear();
|
||||
this->keybinds.insert({"quit", QUIT});
|
||||
this->keybinds.insert({"pause", PAUSE});
|
||||
this->keybinds.insert({"retry", RETRY});
|
||||
this->keybinds.insert({"h", HOLD});
|
||||
@@ -171,15 +170,21 @@ void TextApp::startGame() const {
|
||||
|
||||
std::set<Action> playerActions;
|
||||
std::set<Action> lastFrameActions;
|
||||
std::set<Action> metaActions;
|
||||
bool retrying = false;
|
||||
for (std::string action : actions) {
|
||||
try {
|
||||
Action playerAction = this->keybinds.at(action);
|
||||
if (playerAction == PAUSE || playerAction == RETRY || playerAction == quit) {
|
||||
metaActions.insert(playerAction);
|
||||
if (action == "quit") {
|
||||
quit = true;
|
||||
}
|
||||
else {
|
||||
if (playerAction == SOFT_DROP || playerAction == MOVE_LEFT || playerAction == MOVE_RIGHT) {
|
||||
try {
|
||||
Action playerAction = this->keybinds.at(action);
|
||||
if (playerAction == RETRY) {
|
||||
retrying = true;
|
||||
}
|
||||
else if (playerAction == PAUSE) {
|
||||
paused = (!paused);
|
||||
}
|
||||
else if (playerAction == SOFT_DROP || playerAction == MOVE_LEFT || playerAction == MOVE_RIGHT) {
|
||||
playerActions.insert(playerAction);
|
||||
lastFrameActions.insert(playerAction);
|
||||
}
|
||||
@@ -190,19 +195,12 @@ void TextApp::startGame() const {
|
||||
playerActions.insert(playerAction);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (std::exception ignored) {}
|
||||
}
|
||||
|
||||
if (metaActions.contains(PAUSE)) {
|
||||
paused = (!paused);
|
||||
}
|
||||
|
||||
if (!paused) {
|
||||
if (metaActions.contains(QUIT)) {
|
||||
quit = true;
|
||||
}
|
||||
else if (metaActions.contains(RETRY)) {
|
||||
if (!paused && !quit) {
|
||||
if (retrying) {
|
||||
game.reset();
|
||||
game.start();
|
||||
}
|
||||
@@ -253,7 +251,7 @@ void TextApp::printGame(const Game& game) const {
|
||||
for (int x = 0; x < game.getBoard().getWidth(); x++) {
|
||||
/* BOARD PRINTING */
|
||||
bool isActivePieceHere = (game.getActivePiece() != nullptr) && (game.getActivePiece()->getPositions().contains(Position{x, y} - game.getActivePiecePosition()));
|
||||
bool isGhostPieceHere = (game.getActivePiece() != nullptr) && (game.getActivePiece()->getPositions().contains(Position{x, y} - game.ghostPiecePosition()));
|
||||
bool isGhostPieceHere = (game.getActivePiece() != nullptr) && (game.getActivePiece()->getPositions().contains(Position{x, y} - game.getGhostPiecePosition()));
|
||||
Block block = (isActivePieceHere || isGhostPieceHere) ? game.getActivePiece()->getBlockType() : game.getBoard().getBlock(Position{x, y});
|
||||
|
||||
if (isActivePieceHere || isGhostPieceHere) {
|
||||
|
||||
28
xmake.lua
@@ -1,13 +1,27 @@
|
||||
add_rules("mode.debug", "mode.release")
|
||||
|
||||
add_requires("sfml 3.0.0")
|
||||
|
||||
set_languages("c++20")
|
||||
|
||||
target("main")
|
||||
set_kind("binary")
|
||||
set_rundir(".")
|
||||
add_files("./src/Pieces/*.cpp")
|
||||
add_files("./src/Core/*.cpp")
|
||||
add_files("./src/TextUI/*.cpp")
|
||||
set_optimize("fastest")
|
||||
set_rundir(".")
|
||||
|
||||
target("core")
|
||||
set_kind("$(kind)")
|
||||
add_files("src/Pieces/*.cpp")
|
||||
add_files("src/Core/*.cpp")
|
||||
|
||||
target("text")
|
||||
set_kind("binary")
|
||||
set_default(false)
|
||||
add_files("./src/TextUI/*.cpp")
|
||||
add_deps("core")
|
||||
|
||||
target("graph")
|
||||
set_kind("binary")
|
||||
add_files("./src/GraphicalUI/**.cpp")
|
||||
add_deps("core")
|
||||
add_packages("sfml")
|
||||
|
||||
--
|
||||
-- If you want to known more usage about xmake, please see https://xmake.io
|
||||
|
||||