31 Commits

Author SHA1 Message Date
fd6bdc2b09 Merge branch 'assets'
All checks were successful
Linux arm64 / Build (push) Successful in 2m0s
2025-05-28 19:24:49 +02:00
69b91d6497 simon PR review 2025-05-28 19:24:36 +02:00
d50714ef8c working asset manager !
All checks were successful
Linux arm64 / Build (push) Successful in 1m58s
2025-05-27 21:29:56 +02:00
6ed85869ae added bones block to master mode and added invisible mode
All checks were successful
Linux arm64 / Build (push) Successful in 1m55s
2025-05-27 18:52:07 +02:00
3d1feb6295 start asset manager
Some checks failed
Linux arm64 / Build (push) Failing after 1m58s
2025-05-27 17:09:36 +02:00
7a96136631 added xmake benchmark target
All checks were successful
Linux arm64 / Build (push) Successful in 1m52s
2025-05-27 14:52:34 +02:00
fc6348cebc added benchmarking and screenshots
All checks were successful
Linux arm64 / Build (push) Successful in 1m58s
2025-05-27 11:38:15 +02:00
0bb25d4628 benchmarking 1/2
All checks were successful
Linux arm64 / Build (push) Successful in 1m54s
2025-05-26 19:32:31 +02:00
69c07abcec update readme
All checks were successful
Linux arm64 / Build (push) Successful in 1m51s
2025-05-24 14:25:50 +02:00
774aa1422f Merge branch 'main' of https://git.ale-pri.com/TetrisNerd/jminos
All checks were successful
Linux arm64 / Build (push) Successful in 1m56s
2025-05-24 14:05:42 +02:00
4dfeda0957 update info menu 2025-05-24 14:05:37 +02:00
05bb79d4a9 Add CI (#3)
All checks were successful
Linux arm64 / Build (push) Successful in 1m56s
Reviewed-on: #3
Co-authored-by: Persson-dev <sim16.prib@gmail.com>
Co-committed-by: Persson-dev <sim16.prib@gmail.com>
2025-05-24 10:59:12 +00:00
e0ab6a4828 update readme and main.cpp debug messages 2025-05-23 22:35:31 +02:00
a62b3c018d fixed holding spwaning the piece 1 row lower 2025-04-01 18:45:01 +02:00
46ebb88ef2 we can load polyos in the app now 2025-03-31 21:14:06 +02:00
de14978b01 support unsupported sizes 2025-03-31 20:29:34 +02:00
d5ac79559e updated file format 2025-03-31 19:22:52 +02:00
5ea47ddd25 added startup menu 2025-03-31 18:59:42 +02:00
1a95765877 variable max polyo size 2025-03-31 14:12:36 +02:00
6bff555cbc fix les gros polyos 2025-03-31 11:25:23 +02:00
f4b58fb67e omg omg 2025-03-30 22:43:41 +02:00
0271b56542 better piece scrolling 2025-03-30 20:16:14 +02:00
5d73e751d7 début de qqch 2025-03-30 13:07:17 +02:00
314b7a8488 added distribution menu 2025-03-29 21:57:27 +01:00
7151be0b1a better cursor 2025-03-29 19:28:04 +01:00
d124205a71 ok fix ig 2025-03-29 18:52:06 +01:00
87920548e5 Merge branch 'main' of https://git.ale-pri.com/TetrisNerd/jminos 2025-03-29 18:50:54 +01:00
57620c70a2 added distribution modes 2025-03-29 18:48:37 +01:00
3dac18c821 fix linux build 2025-03-29 18:14:14 +01:00
3538403f40 finalized gamemode select menu 2025-03-29 12:31:54 +01:00
ec40495328 change volume to start timer 2025-03-29 11:49:19 +01:00
56 changed files with 1807 additions and 367 deletions

36
.github/workflows/ubuntu.yml vendored Normal file
View File

@@ -0,0 +1,36 @@
name: Linux arm64
run-name: Build And Test
on: [push]
env:
XMAKE_ROOT: y
jobs:
Build:
runs-on: ubuntu-latest
steps:
- name: Check out repository code
uses: actions/checkout@v3
- name: Prepare XMake
uses: xmake-io/github-action-setup-xmake@v1
with:
xmake-version: latest
actions-cache-folder: '.xmake-cache'
actions-cache-key: 'ubuntu'
- name: Install SFML
run: |
apt update
apt install -y libsfml-dev
- name: XMake config
run: xmake f -p linux -y
- name: Build
run: xmake
- name: Test
run: xmake test

2
.gitignore vendored
View File

@@ -10,7 +10,7 @@ build/
# personnal documentation
doc/*.txt
doc/*.violet.html
doc/diagrams/*.violet.html
doc/mockups/*
# data files

View File

@@ -1,22 +1,36 @@
# jminos
Modern stacker game with every polyominos from size 1 to 15, made in C++ with [SFML 3](https://www.sfml-dev.org/)!
Modern stacker game with every polyominoes 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 //
// TODO when the game is finished //
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).
This game has been tested on and built for Windows 11 and WSL2 Ubuntu.
If your OS isn't compactible with either of theses two, you can try [manually building the project](#manual-build).
## How to play
### General
Choose which pieces you want to play with and stack them up until you either win or top out!
Make full lines to make them dissapear.
Use the different spins to kick the pieces in spot you couldn't imagine were attaignable!
Each gamemode has its own objective, but you can play as you wish.
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/)!
All of in-menu navigation is done with the **arrow keys**, the **Enter key** and the **Escape key**. Theses are unchangeable keybinds.
You will find more infos about the Rotation System, the scoring system, or the different pieces type in the **INFO** section of the main menu.
## Features
- Every polyominoes up to pentedecaminoes!
- 7bag with proportionnality for each polyomino size!
- AutoRS as the Rotation System!
- 0° rotations!
- All spin!
- IRS, IHS, infinite hold, and other leniency mechanics!
- Customizable board size!
- Customizable keybinds!
- Very bland interface!! (i'm not a designer)
### Available gamemodes
@@ -24,8 +38,22 @@ If you want to know more details about the generation and classification of poly
- 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!
- INVISIBLE : get 1000 grade while not being able to see the board!
- ZEN : practice indefinitely in this mode with no gravity!
- ??? : still to do
### Screenshots
Pentedecamino jumpscare
![](./doc/readme/big_piece.png)
Pieces select screen
![](./doc/readme/pieces_selection.png)
AutoRS demonstration
![](./doc/readme/rotations.gif)
0° spins demonstration
![](./doc/readme/rotation_0.gif)
## Manual build
@@ -44,12 +72,49 @@ If you need to change the toolchain (for example using 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:
The program will generate the polyomino files for you if you don't have them.
As this is a lengthy process, debug mode limits pieces size to 10.
To switch between debug and release mode:
``xmake f -m debug``
``xmake f -m release``
If for some reasons you wanna run the command line version (not updated):
``xmake build text``
``xmake run text``
### Create a release version (xmake packaging ???)
### Package the project
// TODO when the game is finished //
## Benchmarks
### One-sided n-polyominoes
| n | Number | Generation | File storing | File retrieving | File size |
| - | - | - | - | - | - |
| 1 | 1 | 0s 0.005622ms | 0s 0.750955ms | 0s 0.014016ms | 2 bytes |
| 2 | 1 | 0s 0.005758ms | 0s 0.181323ms | 0s 0.012256ms | 3 bytes |
| 3 | 2 | 0s 0.017525ms | 0s 0.054497ms | 0s 0.006431ms | 8 bytes |
| 4 | 7 | 0s 0.050554ms | 0s 0.067617ms | 0s 0.010984ms | 35 bytes |
| 5 | 18 | 0s 0.191971ms | 0s 0.109905ms | 0s 0.021234ms | 108 bytes |
| 6 | 60 | 0s 0.651757ms | 0s 0.327465ms | 0s 0.04558ms | 420 bytes |
| 7 | 196 | 0s 3.38847ms | 0s 1.94434ms | 0s 0.258777ms | 1568 bytes |
| 8 | 704 | 0s 22.7411ms | 0s 10.0103ms | 0s 1.34813ms | 6336 bytes |
| 9 | 2500 | 0s 66.2949ms | 0s 20.6137ms | 0s 2.56374ms | 25000 bytes |
| 10 | 9189 | 0s 194.764ms | 0s 84.5884ms | 0s 9.64467ms | 101079 bytes |
| 11 | 33896 | 0s 759.182ms | 0s 378.494ms | 0s 44.1424ms | 406752 bytes |
| 12 | 126759 | 2s 709.277ms | 1s 530.34ms | 0s 155ms | 1647867 bytes |
| 13 | 476270 | 10s 668.308ms | 7s 395.512ms | 0s 765.601ms | 6667780 bytes |
| 14 | 1802312 | 45s 606.597ms | 32s 28.7977ms | 2s 919.653ms | 27034680 bytes |
| 15 | ~6M | ~5mn | ~5mn | ~10s | ~100 MB |
_File storing includes normalizing and sorting all polyominoes before writing them to the file._
If you want to know more details about the generation and storage of polyominoes, [check the documentation](/doc/)!
Run it yourself by typing:
``xmake build benchmark``
``xmake run benchmark``
## Credits

View File

Before

Width:  |  Height:  |  Size: 164 KiB

After

Width:  |  Height:  |  Size: 164 KiB

View File

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

View File

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

View File

@@ -106,3 +106,17 @@ Grade is an alternate system to line clears.
The only exception occurs when the total ends in '99', a line must be cleared to progress.
When this line is cleared, the point of the piece is also counted towards the total.
## Pieces distribution
A bag is an object which contains a set of pieces.
The principle of a bag generator is to put every available pieces into a bag and take them out one by one until the bag is empty, to then start with a new full bag.
It's a way to have equal distribution of pieces while still allowing for a random order of pieces.
The game has 3 modes of pieces distribution:
1. Default. The simplest of them, all selected pieces are put in a single bag.
2. Uniformous. The pieces are now separated by size and put in different bags. Each size will now appear as often as any other.
3. Custom. The pieces are once again separated by size, but now the player specifies how likely each size is to appear.
Both system 2 and 3 uses a system analogous to a bag to determine which size of piece to take next.

BIN
doc/readme/big_piece.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

BIN
doc/readme/rotation_0.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 614 KiB

BIN
doc/readme/rotations.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 779 KiB

View File

@@ -7,26 +7,54 @@
#include <utility>
#include <cstdlib>
static const double SMALLEST_CONSIDERED_PROPORTION = 0.01; // the smallest a proportion can get before it is considered equal to 0
Bag::Bag(const std::shared_ptr<PiecesList>& piecesList) :
piecesList(piecesList) {
this->currentBag = this->piecesList->getSelectedPieces();
this->nextBag.clear();
this->highestSize = this->piecesList->getHighestLoadedSize();
this->selectedPieces = this->piecesList->getSelectedPieces();
this->distributionMode = this->piecesList->getDistributionMode();
this->propotionsPerSize = this->piecesList->getProportionsPerSize();
this->currentBags.clear();
this->nextBags.clear();
this->sizesBag.clear();
this->sizesProgression.clear();
if (this->distributionMode == DEFAULT) {
this->currentBags.push_back(this->selectedPieces);
this->nextBags.push_back({});
}
else {
for (int i = 0; i <= this->highestSize; i++) {
this->currentBags.push_back(PieceBag());
this->nextBags.push_back(PieceBag());
this->sizesProgression.push_back(0);
}
for (const auto& piece : this->selectedPieces) {
int pieceSize = this->piecesList->lookAtPiece(piece).getPositions().size();
this->currentBags.at(pieceSize).push_back(piece);
}
}
this->prepareNext();
}
void Bag::jumpToNextBag() {
if (this->currentBag.size() < this->nextBag.size()) {
std::swap(this->currentBag, this->nextBag);
for (int i = 0; i < this->currentBags.size(); i++) {
if (this->currentBags.at(i).size() < this->nextBags.at(i).size()) {
std::swap(this->currentBags.at(i), this->nextBags.at(i));
}
for (const auto& piece : this->nextBags.at(i)) {
this->currentBags.at(i).push_back(piece);
}
this->nextBags.at(i).clear();
}
for (const std::pair<int, int>& pieceIndex : this->nextBag) {
this->currentBag.push_back(pieceIndex);
}
this->nextBag.clear();
this->prepareNext();
}
@@ -43,13 +71,40 @@ Piece Bag::getNext() {
}
void Bag::prepareNext() {
if (this->currentBag.empty()) {
std::swap(this->currentBag, this->nextBag);
if (this->distributionMode == DEFAULT) {
this->getNextPieceFromBag(0);
}
else {
if (this->sizesBag.empty()) {
for (int i = 0; i <= this->highestSize; i++) {
if (this->propotionsPerSize.at(i) >= SMALLEST_CONSIDERED_PROPORTION
&& !(this->currentBags.at(i).empty() && this->nextBags.at(i).empty())) {
while (this->sizesProgression.at(i) < 1) {
this->sizesBag.push_back(i);
this->sizesProgression.at(i) += this->propotionsPerSize.at(i);
}
this->sizesProgression.at(i) -= 1;
}
}
}
int nextSizeIndex = std::rand() % this->sizesBag.size();
int nextSize = this->sizesBag.at(nextSizeIndex);
this->sizesBag.erase(this->sizesBag.begin() + nextSizeIndex);
this->getNextPieceFromBag(nextSize);
}
}
void Bag::getNextPieceFromBag(int bagIndex) {
if (this->currentBags.at(bagIndex).empty()) {
std::swap(this->currentBags.at(bagIndex), this->nextBags.at(bagIndex));
}
int indexIndex = std::rand() % this->currentBag.size();
this->next = this->currentBag.at(indexIndex);
int indexIndex = std::rand() % this->currentBags.at(bagIndex).size();
this->next = this->currentBags.at(bagIndex).at(indexIndex);
this->nextBag.push_back(this->next);
this->currentBag.erase(this->currentBag.begin() + indexIndex);
this->nextBags.at(bagIndex).push_back(this->next);
this->currentBags.at(bagIndex).erase(this->currentBags.at(bagIndex).begin() + indexIndex);
}

View File

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

View File

@@ -0,0 +1,27 @@
#pragma once
#include <string>
/**
* The modes of pieces distribution managed by the app
*/
enum DistributionMode {
DEFAULT,
UNIFORM,
CUSTOM
};
/**
* @return A string containing the name of the given distribution mode
*/
inline std::string getPiecesDistributionName(DistributionMode distributionMode) {
static const std::string DISTRIBUTION_NAMES[] = {
"DEFAULT",
"UNIFORM",
"CUSTOM"
};
return DISTRIBUTION_NAMES[distributionMode];
}

View File

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

View File

@@ -136,6 +136,11 @@ class Game {
*/
bool areBlocksBones() const;
/**
* @return If the board is currently invisible
*/
bool isBoardInvisible() const;
/**
* @return The board
*/

View File

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

View File

@@ -7,6 +7,7 @@
#include <vector>
#include <memory>
#include <optional>
/**
@@ -90,7 +91,7 @@ class GameBoard {
* Tries holding the active piece or swapping it if one was already stocked, while trying to apply an initial rotation to the newly spawned piece
* @return If it suceeded
*/
bool hold(Rotation initialRotation = NONE);
bool hold(std::optional<Rotation> initialRotation);
/**
* Spawns the next piece from the queue

View File

@@ -7,7 +7,7 @@
GameParameters::GameParameters(Gamemode gamemode, const Player& controls) :
gamemode(gamemode),
controls(controls) {
this->reset();
}
@@ -24,6 +24,8 @@ void GameParameters::reset() {
case MARATHON : {this->level = 1; break;}
// goes from level 20 to 39
case MASTER : {this->level = 20; break;}
// goes from level 1 to 19
case INVISIBLE : {this->level = 1; break;}
// no gravity
case ZEN : {this->level = 0; break;}
default : this->level = 1;
@@ -34,7 +36,7 @@ void GameParameters::reset() {
void GameParameters::lockedPiece(const LineClear& lineClear) {
switch (this->gamemode) {
// modes where level increases
// modes where level increases with lines
case MARATHON :
case MASTER : {
int previousLines = this->clearedLines;
@@ -51,9 +53,23 @@ void GameParameters::lockedPiece(const LineClear& lineClear) {
default : this->clearedLines += lineClear.lines;
}
int previousGrade = this->grade;
if (!((lineClear.lines == 0) && ((this->grade % 100) == 99))) {
this->grade += (1 + lineClear.lines);
}
switch (this->gamemode) {
// modes where level increases with grade
case INVISIBLE : {
if (previousGrade / 100 < this->grade / 100) {
this->level += 2;
this->updateStats();
}
break;
}
// other modes
default : break;
}
}
bool GameParameters::hasWon(int framesPassed) const {
@@ -66,6 +82,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;
// win once 1000 grade has been passed
case INVISIBLE : return this->grade >= 1000;
// infinite mode
case ZEN :
default : return false;
@@ -84,7 +102,8 @@ void GameParameters::updateStats() {
}
// 3 for slow-controls gamemodes
case MARATHON :
case MASTER : {
case MASTER :
case INVISIBLE : {
this->nextQueueLength = 3;
break;
}
@@ -94,10 +113,13 @@ void GameParameters::updateStats() {
/* BONE BLOCKS */
switch (this->gamemode) {
// blocks turns into bone blocks at level 30
case MASTER : this->boneBlocks = (this->level >= 30);
case MASTER : {this->boneBlocks = (this->level >= 30); break;}
default : this->boneBlocks = false;
}
/* INVISIBLE */
this->invisibleBoard = (this->gamemode == INVISIBLE);
/* GRAVITY */
// get gravity for an assumed 20-rows board
static const int gravityPerLevel[] = {
@@ -152,6 +174,8 @@ void GameParameters::updateStats() {
case MARATHON : {this->ARE = 24 - (this->level - 1); break;}
// starts at 400ms (24f) at lvl 20 and ends at 083ms (5f) at lvl 39
case MASTER : {this->ARE = 24 - (this->level - 20); break;}
// fixed at 250ms (15f)
case INVISIBLE : {this->ARE = 15; break;}
// no ARE by default
default : this->ARE = 0;
}
@@ -228,6 +252,10 @@ bool GameParameters::getBoneBlocks() const {
return this->boneBlocks;
}
bool GameParameters::getInvisibleBoard() const {
return this->invisibleBoard;
}
int GameParameters::getGravity() const {
return this->gravity;
}

View File

@@ -15,9 +15,10 @@ 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 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
bool invisibleBoard; // wheter the board is invisible
int gravity; // the gravity at which pieces drop
int lockDelay; // the time before the piece lock in place
int forcedLockDelay; // the forced time before the piece lock in place
@@ -77,9 +78,14 @@ class GameParameters {
int getNextQueueLength() const;
/**
* Returns wheter the blocks are currently bone blocks
* @return Wheter the blocks are currently bone blocks
*/
bool getBoneBlocks() const;
/**
* @return Wheter the board is currently invisible
*/
bool getInvisibleBoard() const;
/**
* @return The current gravity for a 20-line high board

View File

@@ -11,6 +11,7 @@ enum Gamemode {
MARATHON,
ULTRA,
MASTER,
INVISIBLE,
ZEN
};
@@ -24,6 +25,7 @@ inline std::string getGamemodeName(Gamemode gamemode) {
"MARATHON",
"ULTRA",
"MASTER",
"INVISIBLE",
"ZEN"
};
@@ -39,7 +41,8 @@ inline std::string getGamemodeGoal(Gamemode gamemode) {
"200 lines",
"2 minutes",
"200 lines",
"Chill"
"1000 grade",
"Infinite"
};
return GAMEMODE_DESCRIPTIONS[gamemode];

View File

@@ -2,6 +2,7 @@
#include "../Pieces/Piece.h"
#include "../Pieces/PiecesFiles.h"
#include "DistributionMode.h"
#include <vector>
#include <utility>
@@ -11,6 +12,10 @@ PiecesList::PiecesList() {
this->highestLoadedSize = 0;
this->selectedPieces.clear();
this->distributionMode = DEFAULT;
this->proportionsPerSize.clear();
this->customProportionsPerSize.clear();
// we need to have something at index 0 even if there is no pieces of size 0
this->loadedPieces.clear();
this->convexPieces.clear();
@@ -24,6 +29,7 @@ PiecesList::PiecesList() {
bool PiecesList::loadPieces(int size) {
if (size < 1) return false;
if (size <= this->highestLoadedSize) return true;
PiecesFiles piecesFiles;
for (int i = this->highestLoadedSize + 1; i <= size; i++) {
@@ -87,6 +93,14 @@ void PiecesList::unselectAll() {
this->selectedPieces.clear();
}
bool PiecesList::setDistributionMode(DistributionMode distributionMode) {
if (distributionMode == DEFAULT || distributionMode == UNIFORM || distributionMode == CUSTOM) {
this->distributionMode = distributionMode;
return true;
}
return false;
}
int PiecesList::getHighestLoadedSize() const {
return this->highestLoadedSize;
}
@@ -101,13 +115,42 @@ std::vector<std::pair<int, int>> PiecesList::getSelectedPieces() const {
return this->selectedPieces;
}
DistributionMode PiecesList::getDistributionMode() const {
return this->distributionMode;
}
bool PiecesList::changeCustomDistribution(int size, double distribution) {
if (size < 1 || size > this->highestLoadedSize || distribution > 1) {
return false;
}
this->customProportionsPerSize.at(size) = distribution;
return true;
}
std::vector<double> PiecesList::getProportionsPerSize() const {
if (this->distributionMode == CUSTOM) {
return this->customProportionsPerSize;
}
else {
return this->proportionsPerSize;
}
}
Piece PiecesList::getPiece(const std::pair<int, int>& pieceIndex) const {
return this->loadedPieces.at(pieceIndex.first).at(pieceIndex.second);
}
const Piece& PiecesList::lookAtPiece(const std::pair<int, int>& pieceIndex) const {
return this->loadedPieces.at(pieceIndex.first).at(pieceIndex.second);
}
void PiecesList::pushBackEmptyVectors() {
this->loadedPieces.push_back(std::vector<Piece>());
this->convexPieces.push_back(std::vector<int>());
this->holelessPieces.push_back(std::vector<int>());
this->otherPieces.push_back(std::vector<int>());
this->proportionsPerSize.push_back(1);
this->customProportionsPerSize.push_back(1);
}

View File

@@ -1,6 +1,7 @@
#pragma once
#include "../Pieces/Piece.h"
#include "DistributionMode.h"
#include <vector>
#include <utility>
@@ -18,6 +19,9 @@ class PiecesList {
std::vector<std::vector<int>> holelessPieces; // the list of holeless loaded pieces by size
std::vector<std::vector<int>> otherPieces; // the list of other loaded pieces by size
std::vector<std::pair<int, int>> selectedPieces; // the list of all currently selected pieces
DistributionMode distributionMode; // the current pieces distribution mode
std::vector<double> proportionsPerSize; // the proportion of piece for each sizes
std::vector<double> customProportionsPerSize; // the proportion of piece for each sizes when the distribution mode is set to custom
public:
/**
@@ -66,6 +70,19 @@ class PiecesList {
*/
void unselectAll();
/**
* Changes the current pieces distribution mode
* @return If the mode is supported
*/
bool setDistributionMode(DistributionMode distributionMode);
/**
* Changes the distribution of the specified size for when the distribution mode is set to custom,
* the specified distribution must lower or equal to 1
* @return If the new distribution was applied
*/
bool changeCustomDistribution(int size, double distribution);
/**
* @return The highest loaded size of pieces
*/
@@ -81,10 +98,25 @@ class PiecesList {
*/
std::vector<std::pair<int, int>> getSelectedPieces() const;
/**
* @return The current distribution mode
*/
DistributionMode getDistributionMode() const;
/**
* @return The proportion of pieces for each loaded size
*/
std::vector<double> getProportionsPerSize() const;
/**
* @return A copy of the piece corresponding to the specified index
*/
Piece getPiece(const std::pair<int, int>& pieceIndex) const;
/**
* @return The piece corresponding to the specified index
*/
const Piece& lookAtPiece(const std::pair<int, int>& pieceIndex) const;
private:
/**

View File

@@ -0,0 +1,72 @@
#include "AppMenu.h"
#include "../Settings.h"
#include "../PlayerCursor.h"
#include "../../Utils/AssetManager.h"
#include <stack>
#include <memory>
#include <optional>
#include <SFML/Graphics.hpp>
AppMenu::AppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow) :
menuStack(menuStack),
settings(settings),
renderWindow(renderWindow) {
const Asset& file = getResource(AssetName::data_fonts_pressstart_prstartk_ttf);
this->pressStartFont = sf::Font(file.data, file.size);
}
void AppMenu::updateMetaBinds() {
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Enter)) {
this->enterPressed = true;
this->enterReleased = false;
}
else {
this->enterReleased = this->enterPressed;
this->enterPressed = false;
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Escape)) {
this->escPressed = true;
this->escReleased = false;
}
else {
this->escReleased = this->escPressed;
this->escPressed = false;
}
}
void AppMenu::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 AppMenu::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);
}
sf::Color AppMenu::getColorOfBlock(Block block, int luminosityShift) const {
Color rgbColor = BLOCKS_COLOR[block];
return sf::Color(std::clamp(rgbColor.red + luminosityShift, 0, 255),
std::clamp(rgbColor.green + luminosityShift, 0, 255),
std::clamp(rgbColor.blue + luminosityShift, 0, 255));
}

View File

@@ -21,63 +21,21 @@ class AppMenu {
bool enterReleased = false;
bool escPressed = false;
bool escReleased = false;
sf::Font pressStartFont = sf::Font("data/fonts/pressstart/prstartk.ttf");
sf::Font pressStartFont;
public:
AppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow) :
menuStack(menuStack),
settings(settings),
renderWindow(renderWindow)
{
}
AppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::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;
}
void updateMetaBinds();
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;
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();
void placeTitle(sf::Text& text, const std::optional<PlayerCursor>& playerCursor, const sf::String& string, float yPos, const std::optional<sf::Vector2u>& cursorPos) const;
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);
}
sf::Color getColorOfBlock(Block block, int luminosityShift) const;
};

View File

@@ -5,7 +5,6 @@
#include <stack>
#include <memory>
#include <vector>
#include <SFML/Graphics.hpp>
@@ -58,8 +57,6 @@ void GameBoardAppMenu::drawFrame() const {
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});

View File

@@ -0,0 +1,85 @@
#include "GameDistributionAppMenu.h"
#include "AppMenu.h"
#include "../PlayerCursor.h"
#include <stack>
#include <memory>
#include <SFML/Graphics.hpp>
GameDistributionAppMenu::GameDistributionAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow) :
AppMenu(menuStack, settings, renderWindow),
playerCursor({1}) {
for (int i = 1; i <= this->settings->getMaximumPiecesSize(); i++) {
this->playerCursor.addRow(i, 1);
}
}
void GameDistributionAppMenu::computeFrame() {
this->updateMetaBinds();
this->playerCursor.updatePosition();
PiecesList& piecesList = this->settings->getMenu().getPiecesList();
if (this->playerCursor.getPosition().y == 0) {
if (this->playerCursor.movedLeft()) {
piecesList.setDistributionMode(DistributionMode((int) piecesList.getDistributionMode() - 1));
}
if (this->playerCursor.movedRight()) {
piecesList.setDistributionMode(DistributionMode((int) piecesList.getDistributionMode() + 1));
}
}
else {
if (piecesList.getDistributionMode() != CUSTOM) {
this->playerCursor.goToPosition({0, 0});
}
else {
if (this->playerCursor.movedLeft()) {
this->settings->decreaseDistribution(this->playerCursor.getPosition().y);
}
if (this->playerCursor.movedRight()) {
this->settings->increaseDistribution(this->playerCursor.getPosition().y);
}
}
}
if (this->escReleased) {
this->settings->confirmDistribution();
this->menuStack->pop();
}
}
void GameDistributionAppMenu::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, {}, "DISTRIBUTION SETTINGS", 5.f, {});
const DistributionMode distributionMode = this->settings->getMenu().readPiecesList().getDistributionMode();
const std::vector<int>& distributions = this->settings->getDistributions();
int firstElem = std::clamp(((int) this->playerCursor.getPosition().y) - 1, 0, this->settings->getMaximumPiecesSize() - 3);
if (firstElem == 0) {
this->placeText(text, this->playerCursor, "< DISTRIBUTION MODE: " + getPiecesDistributionName(distributionMode) + " >", 5.f, 15.f, sf::Vector2u{0, 0});
}
else {
this->placeText(text, this->playerCursor, "< SIZE " + std::to_string(firstElem) + " PROBABILITY: " + std::to_string(distributions.at(firstElem)) + " >", 5.f, 15.f, sf::Vector2u{(unsigned int) firstElem, 0});
}
if (distributionMode != CUSTOM) {
text.setFillColor(sf::Color(100, 100, 100));
}
this->placeText(text, this->playerCursor, "< SIZE " + std::to_string(firstElem + 1) + " PROBABILITY: " + std::to_string(distributions.at(firstElem + 1)) + " >", 5.f, 25.f, sf::Vector2u{0, (unsigned int) firstElem + 1});
this->placeText(text, this->playerCursor, "< SIZE " + std::to_string(firstElem + 2) + " PROBABILITY: " + std::to_string(distributions.at(firstElem + 2)) + " >", 5.f, 35.f, sf::Vector2u{0, (unsigned int) firstElem + 2});
this->placeText(text, this->playerCursor, "< SIZE " + std::to_string(firstElem + 3) + " PROBABILITY: " + std::to_string(distributions.at(firstElem + 3)) + " >", 5.f, 45.f, sf::Vector2u{0, (unsigned int) firstElem + 3});
this->renderWindow->display();
}

View File

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

View File

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

View File

@@ -0,0 +1,29 @@
#pragma once
#include "AppMenu.h"
#include "../PlayerCursor.h"
#include <stack>
#include <memory>
#include <vector>
#include <SFML/Graphics.hpp>
class GamePiecesAppMenu : public AppMenu {
private:
PlayerCursor playerCursor;
public:
GamePiecesAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow);
void computeFrame() override;
void drawFrame() const override;
private:
void drawSelectedPiecesRow(float yPos) const;
void drawRow(int piecesSize, float yPos, bool drawFromFirstElem) const;
void drawPiece(const Piece& piece, int cellSize, const sf::FloatRect& pos, bool selected) const;
};

View File

@@ -9,14 +9,15 @@
#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->startTimer = this->settings->getStartTimerLength() * FRAMES_PER_SECOND;
if (this->startTimer == 0) {
this->game.start();
}
this->paused = false;
this->pausePressed = false;
this->retryPressed = false;
@@ -39,7 +40,7 @@ GamePlayingAppMenu::GamePlayingAppMenu(std::shared_ptr<MenuStack> menuStack, std
this->nextCellSizeZoom = this->nextQueuePosition[0].size.y;
for (const auto& piece : this->settings->getMenu().getPiecesList().getSelectedPieces()) {
float nextPieceCellSizeZoom = ((int) this->nextQueuePosition[0].size.y) / this->settings->getMenu().getPiecesList().getPiece(piece).getLength();
float nextPieceCellSizeZoom = ((int) this->nextQueuePosition[0].size.y) / this->settings->getMenu().getPiecesList().lookAtPiece(piece).getLength();
this->nextCellSizeZoom = std::min(this->nextCellSizeZoom, nextPieceCellSizeZoom);
}
@@ -77,7 +78,10 @@ void GamePlayingAppMenu::computeFrame() {
else {
if (this->retryPressed) {
this->game.reset();
this->startTimer = TIME_BEFORE_STARTING;
this->startTimer = this->settings->getStartTimerLength() * FRAMES_PER_SECOND;
if (this->startTimer == 0) {
this->game.start();
}
}
this->retryPressed = false;
}
@@ -100,17 +104,26 @@ void GamePlayingAppMenu::computeFrame() {
void GamePlayingAppMenu::drawFrame() const {
this->renderWindow->clear(sf::Color(200, 200, 200));
sf::Color bonesBlockColor(0, 0, 0);
sf::Color bonesBlockGhostColor(100, 100, 100);
bool areBlockBones = this->game.areBlocksBones();
bool isBoardInvisible = this->game.isBoardInvisible() && !(this->game.hasWon() || this->game.hasLost());
sf::Vector2f cellSize(this->cellSizeZoom, this->cellSizeZoom);
float cellOutlineThickness = this->cellSizeZoom / 4;
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});
if (isBoardInvisible) block = NOTHING;
sf::RectangleShape cell(cellSize);
cell.setFillColor(this->getColorOfBlock(block, (block == NOTHING) ? 0 : -30));
cell.setFillColor((areBlockBones && block != NOTHING)
? bonesBlockColor
: this->getColorOfBlock(block, (block == NOTHING) ? 0 : -30));
cell.setPosition(this->getBoardBlockPosition(x, y));
this->renderWindow->draw(cell);
}
@@ -118,7 +131,10 @@ void GamePlayingAppMenu::drawFrame() const {
if (drawActivePiece) {
// ghost piece
sf::Color ghostColor = this->getColorOfBlock(this->game.getActivePiece()->getBlockType(), 100);
sf::Color ghostColor = areBlockBones
? bonesBlockGhostColor
: this->getColorOfBlock(this->game.getActivePiece()->getBlockType(), 100);
for (const Position& position : this->game.getActivePiece()->getPositions()) {
Position cellPosition = (this->game.getGhostPiecePosition() + position);
@@ -129,13 +145,13 @@ void GamePlayingAppMenu::drawFrame() const {
}
// 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.setOutlineThickness(cellOutlineThickness);
cell.setOutlineColor(pieceOultlineColor);
cell.setPosition(this->getBoardBlockPosition(cellPosition.x, cellPosition.y));
this->renderWindow->draw(cell);
@@ -150,7 +166,9 @@ void GamePlayingAppMenu::drawFrame() const {
if (drawActivePiece) {
// active piece
sf::Color pieceColor = this->getColorOfBlock(this->game.getActivePiece()->getBlockType(), -200 * (this->game.getLockDelayProgression()));
sf::Color pieceColor = areBlockBones
? bonesBlockColor
: this->getColorOfBlock(this->game.getActivePiece()->getBlockType(), -200 * (this->game.getLockDelayProgression()));
for (const Position& position : this->game.getActivePiece()->getPositions()) {
Position cellPosition = (this->game.getActivePiecePosition() + position);
@@ -169,7 +187,9 @@ void GamePlayingAppMenu::drawFrame() const {
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 pieceColor = areBlockBones
? bonesBlockColor
: this->getColorOfBlock(this->game.getNextPieces().at(i).getBlockType(), 0);
sf::Color boxColor = sf::Color(180, 180, 180);
int lowestRank = 0;
@@ -177,7 +197,7 @@ void GamePlayingAppMenu::drawFrame() const {
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);
cell.setFillColor(pieceColor);
lowestRank = y;
}
else {
@@ -195,9 +215,11 @@ void GamePlayingAppMenu::drawFrame() const {
// 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 color = areBlockBones
? bonesBlockColor
: 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);
@@ -208,7 +230,7 @@ void GamePlayingAppMenu::drawFrame() const {
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->holdBoxPosition.position.y + ((this->game.getHeldPiece()->getLength() - y - 1) * this->holdCellSizeZoom)));
this->renderWindow->draw(cell);
}
}
@@ -247,6 +269,7 @@ void GamePlayingAppMenu::drawFrame() const {
// game state
text.setOutlineColor(sf::Color(255, 255, 255));
text.setOutlineThickness(windowSizeMultiplier / 2.f);
text.setCharacterSize(windowSizeMultiplier * 4);
if (this->game.hasWon()) {
this->placeTitle(text, {}, "WIN", 25.f, {});
@@ -258,20 +281,12 @@ void GamePlayingAppMenu::drawFrame() const {
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->placeTitle(text, {}, std::to_string(((this->startTimer - 1) / ((this->settings->getStartTimerLength() * FRAMES_PER_SECOND) / 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));

View File

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

View File

@@ -1,13 +1,13 @@
#include "GameSettingsAppMenu.h"
#include "AppMenu.h"
#include "GamePiecesAppMenu.h"
#include "GameBoardAppMenu.h"
#include "GamePlayingAppMenu.h"
#include "../PlayerCursor.h"
#include <stack>
#include <memory>
#include <vector>
#include <SFML/Graphics.hpp>
@@ -33,8 +33,8 @@ void GameSettingsAppMenu::computeFrame() {
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
case 1 : {this->settings->setGamemode(INVISIBLE); break;}
case 2 : {this->settings->setGamemode(ZEN); break;}
}
break;
}
@@ -43,7 +43,7 @@ void GameSettingsAppMenu::computeFrame() {
if (this->enterReleased) {
if (this->playerCursor.getPosition().y == 0) {
if (this->playerCursor.getPosition().x == 0) {
//TODO
this->menuStack->push(std::make_shared<GamePiecesAppMenu>(this->menuStack, this->settings, this->renderWindow));
}
if (this->playerCursor.getPosition().x == 1) {
this->menuStack->push(std::make_shared<GameBoardAppMenu>(this->menuStack, this->settings, this->renderWindow));
@@ -67,15 +67,18 @@ void GameSettingsAppMenu::drawFrame() const {
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, "PIECES SELECT", 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});
text.setOutlineThickness(0);
this->placeTitle(text, {}, "GAMEMODE SELECT", 25.f, {});
this->placeText(text, this->playerCursor, "SPRINT", 5.f, 35.f, sf::Vector2u{0, 1});
this->placeText(text, this->playerCursor, "MARATHON", 25.f, 35.f, sf::Vector2u{1, 1});
this->placeText(text, this->playerCursor, "ULTRA", 50.f, 35.f, sf::Vector2u{2, 1});
this->placeText(text, this->playerCursor, "MASTER", 5.f, 45.f, sf::Vector2u{0, 2});
this->placeText(text, this->playerCursor, "INVISIBLE", 25.f, 45.f, sf::Vector2u{1, 2});
this->placeText(text, this->playerCursor, "ZEN", 50.f, 45.f, sf::Vector2u{2, 2});
this->renderWindow->display();
}

View File

@@ -5,18 +5,18 @@
#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}),
playerCursor({INFO_SECTIONS_COUNT}),
sectionsName(
"< ABOUT >",
"< PIECES TYPES >",
"< 0 DEGREES ROTATIONS >",
"< ROTATION SYSTEM >",
"< SCORING >",
"< 0 DEGREES ROTATIONS >"
"< SCORING >"
),
sectionsContent(
"This game is written in C++,\n"
@@ -28,6 +28,25 @@ InfoAppMenu::InfoAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<S
"to them in any ways.\n"
"Current version: beta.",
"There is multiple pieces type in\n"
"the selection screen. Use theses\n"
"categories for size of at least 7.\n"
"Convex, Holeless and Others are\n"
"all mutually exclusive.\n"
"Others have holes inside them, and\n"
"Convex are presumably easier to\n"
"play with than Holeless.",
"This games introduces 0 degrees\n"
"rotations, which work by simpling\n"
"moving the piece down and kicking\n"
"it as is, allowing for new kinds\n"
"of kicks.\n"
"As a leniency mechanic, when a\n"
"piece spawns it will automatically\n"
"try a 0 degrees rotations if it\n"
"spawned inside a wall.",
"This game uses its own\n"
"Rotation Sytem, called AutoRS.\n"
"The rotation center is always the\n"
@@ -47,17 +66,7 @@ InfoAppMenu::InfoAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<S
"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."
"when the last move was a kick."
) {
}

View File

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

View File

@@ -8,7 +8,6 @@
#include <stack>
#include <memory>
#include <vector>
#include <SFML/Graphics.hpp>

View File

@@ -5,7 +5,6 @@
#include <stack>
#include <memory>
#include <vector>
#include <SFML/Graphics.hpp>

View File

@@ -2,10 +2,10 @@
#include "AppMenu.h"
#include "../PlayerCursor.h"
#include "../../Utils/AssetManager.h"
#include <stack>
#include <memory>
#include <vector>
#include <string>
#include <regex>
#include <filesystem>
@@ -15,7 +15,7 @@
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}) {
playerCursor({1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}) {
this->selectedAnAction = false;
@@ -23,8 +23,9 @@ SettingsKeybindsAppMenu::SettingsKeybindsAppMenu(std::shared_ptr<MenuStack> menu
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}});
const Asset& textureData = getResource("data/images/keybinds/" + textureName + ".png");
this->iconTextures[action] = sf::Texture(textureData.data, textureData.size, false, {{0, 0}, {16, 16}});
}
}

View File

@@ -7,7 +7,6 @@
#include <stack>
#include <memory>
#include <vector>
#include <SFML/Graphics.hpp>
@@ -37,10 +36,10 @@ void SettingsMainAppMenu::computeFrame() {
}
case 3 : {
if (this->playerCursor.movedLeft()) {
this->settings->lowerMasterVolume();
this->settings->shortenStartTimer();
}
if (this->playerCursor.movedRight()) {
this->settings->raiseMasterVolume();
this->settings->lengthenStartTimer();
}
break;
}
@@ -73,7 +72,7 @@ void SettingsMainAppMenu::drawFrame() const {
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->placeText(text, this->playerCursor, "< START TIMER: " + std::to_string(this->settings->getStartTimerLength()) + "s >", 5.f, 45.f, sf::Vector2u{0, 3});
this->renderWindow->display();
}

View File

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

View File

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

View File

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

View File

@@ -4,7 +4,7 @@
#include <map>
#include <set>
#include <optional>
#include <algorithm>
#include <SFML/Graphics.hpp>
using sfKey = sf::Keyboard::Key;

View File

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

View File

@@ -66,6 +66,48 @@ void PlayerCursor::goToPosition(const sf::Vector2u& newPosition) {
}
}
bool PlayerCursor::addPosition(unsigned int x, unsigned int y) {
if (y >= this->rows.size()) return false;
if (x > this->rows.at(y)) return false;
this->rows.at(y)++;
if ((y == this->position.y) && (x <= this->position.x)) {
this->position.x++;
}
return true;
}
bool PlayerCursor::removePosition(unsigned int x, unsigned int y) {
if (y >= this->rows.size()) return false;
if (x >= this->rows.at(y)) return false;
this->rows.at(y)--;
if ((y == this->position.y) && (x < this->position.x)) {
this->position.x--;
}
return true;
}
bool PlayerCursor::addRow(unsigned int position, unsigned int width) {
if (position > this->rows.size()) return false;
this->rows.insert(this->rows.begin() + position, width);
if (position <= this->position.y) {
this->position.y++;
}
return true;
}
bool PlayerCursor::removeRow(unsigned int position) {
if (position >= this->rows.size()) return false;
this->rows.erase(this->rows.begin() + position);
if (position < this->position.y) {
this->position.y--;
}
return true;
}
const sf::Vector2u& PlayerCursor::getPosition() const {
return this->position;
}

View File

@@ -28,6 +28,14 @@ class PlayerCursor {
void goToPosition(const sf::Vector2u& newPosition);
bool addPosition(unsigned int x, unsigned int y);
bool removePosition(unsigned int x, unsigned int y);
bool addRow(unsigned int position, unsigned int width);
bool removeRow(unsigned int position);
const sf::Vector2u& getPosition() const;
private:

View File

@@ -2,7 +2,10 @@
#include "../Core/Menu.h"
#include "Keybinds.h"
#include "PiecesType.h"
#include <vector>
#include <optional>
#include <fstream>
#include <algorithm>
#include <SFML/Graphics.hpp>
@@ -10,25 +13,64 @@
static const sf::Vector2u BASE_WINDOW_SIZE = {80, 50};
static const int WINDOW_SIZE_MULTIPLIERS[] = {4, 6, 10, 14, 20, 30, 40};
static const int WINDOW_SIZE_LAST_MODE = (sizeof(WINDOW_SIZE_MULTIPLIERS) / sizeof(int)) - 1;
static const int START_TIMER_MAX = 4;
static const int DISTRIBUTION_MAX = 20;
Settings::Settings() {
for (int i = 1; i <= MAXIMUM_PIECES_SIZE; i++) {
this->menu.getPiecesList().loadPieces(i);
}
Settings::Settings(bool loadPieces) {
this->keybinds.clear();
this->keybinds.reserve(NUMBER_OF_KEYBINDS);
for (int i = 0; i < NUMBER_OF_KEYBINDS; i++) {
this->keybinds.emplace_back(i);
}
this->loadSettingsFromFile();
this->loadSettingsFromFile(loadPieces, {});
}
void Settings::loadSettingsFromFile() {
void Settings::loadPieces(int maximumPiecesSizeRequest) {
if (maximumPiecesSizeRequest < MINIMUM_PIECES_SIZE) {
maximumPiecesSizeRequest = MINIMUM_PIECES_SIZE;
}
else if (maximumPiecesSizeRequest > MAXIMUM_PIECES_SIZE) {
maximumPiecesSizeRequest = MAXIMUM_PIECES_SIZE;
}
bool succeeded = true;
int i = 1;
while (succeeded && (i <= maximumPiecesSizeRequest)) {
succeeded = this->menu.getPiecesList().loadPieces(i);
i++;
}
if (succeeded) {
this->maximumPiecesSize = maximumPiecesSizeRequest;
}
this->loadedPieces = succeeded;
}
void Settings::loadSettingsFromFile(bool loadPieces, std::optional<int> maximumPiecesSizeRequest) {
std::ifstream settingsFile("data/config/settings.bin", std::ios::binary);
char byte;
// file format version
settingsFile.get(byte);
// maximum pieces size
settingsFile.get(byte);
this->maximumPiecesSize = byte;
if (loadPieces) {
if (maximumPiecesSizeRequest.has_value()) {
this->loadPieces(maximumPiecesSizeRequest.value());
}
else {
this->loadPieces(byte);
}
}
else {
this->loadedPieces = false;
}
// keybind layout
settingsFile.get(byte);
this->chosenKeybinds = byte;
@@ -49,9 +91,9 @@ void Settings::loadSettingsFromFile() {
settingsFile.get(byte);
this->windowSizeMode = byte;
// master volume
// start timer length
settingsFile.get(byte);
this->masterVolume = byte;
this->startTimerLength = byte;
// gamemode
settingsFile.get(byte);
@@ -65,35 +107,68 @@ void Settings::loadSettingsFromFile() {
settingsFile.get(byte);
this->menu.setBoardHeight(byte);
// piece distribution
settingsFile.get(byte);
//TODO
if (byte == 2) {
if (this->loadedPieces) {
// piece distribution
settingsFile.get(byte);
this->menu.getPiecesList().setDistributionMode(DistributionMode(byte));
this->distributions.clear();
this->distributions.push_back(0);
for (int i = 1; i <= 15; i++) {
settingsFile.get(byte);
//TODO
this->distributions.push_back(byte);
}
}
this->confirmDistribution();
// 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});
// selected pieces
char pieceType;
char pieceSize;
char lowByte;
char midByte;
char highByte;
this->selectedPieces.clear();
while (settingsFile.get(pieceType)) {
if (settingsFile.eof()) break;
if (getSizeOfPieces(PiecesType(pieceType)) == 0) {
settingsFile.get(pieceSize);
this->selectedPieces.emplace_back(PiecesType(pieceType), pieceSize);
}
else {
settingsFile.get(lowByte);
settingsFile.get(midByte);
settingsFile.get(highByte);
int pieceNumber = ((unsigned char) lowByte) + ((unsigned char) midByte << 8) + ((unsigned char) highByte << 16);
this->selectedPieces.emplace_back(PiecesType(pieceType), pieceNumber);
}
}
this->confirmSelectedPieces();
}
else {
this->distributions.clear();
this->selectedPieces.clear();
}
this->confirmSelectedPieces();
}
void Settings::saveSettingsToFile() const {
if (!this->loadedPieces) return;
this->keybinds.at(CUSTOMIZABLE_KEYBINDS).saveKeybindsToFile();
std::ofstream settingsFile("data/config/settings.bin", std::ios::trunc | std::ios::binary);
char byte;
// file format version
byte = CURRENT_FILE_FORMAT_VERSION;
settingsFile.write(&byte, 1);
// maximum pieces size
byte = this->maximumPiecesSize;
settingsFile.write(&byte, 1);
// keybind layout
byte = this->chosenKeybinds;
settingsFile.write(&byte, 1);
@@ -114,8 +189,8 @@ void Settings::saveSettingsToFile() const {
byte = this->windowSizeMode;
settingsFile.write(&byte, 1);
// master volume
byte = this->masterVolume;
// start timer length
byte = this->startTimerLength;
settingsFile.write(&byte, 1);
// gamemode
@@ -131,15 +206,30 @@ void Settings::saveSettingsToFile() const {
settingsFile.write(&byte, 1);
// piece distribution
//TODO
byte = this->menu.readPiecesList().getDistributionMode();
settingsFile.write(&byte, 1);
for (int i = 1; i <= 15; i++) {
byte = this->distributions.at(i);
settingsFile.write(&byte, 1);
}
// selected pieces
for (const auto& [type, value] : this->selectedPieces) {
byte = type;
settingsFile.write(&byte, 1);
byte = value;
settingsFile.write(&byte, 1);
if (getSizeOfPieces(type) == 0) {
byte = value;
settingsFile.write(&byte, 1);
}
else {
int number = value;
for (int i = 0; i < 3; i++) {
byte = (number % 256);
settingsFile.write(&byte, 1);
number = (number >> 8);
}
}
}
}
@@ -188,17 +278,17 @@ void Settings::changeVideoMode(sf::RenderWindow& window) const {
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);
bool Settings::lengthenStartTimer() {
if (this->startTimerLength < START_TIMER_MAX) {
this->startTimerLength++;
return true;
}
return false;
}
bool Settings::lowerMasterVolume() {
if (this->masterVolume > 0) {
this->masterVolume = std::max(this->masterVolume - 5, 0);
bool Settings::shortenStartTimer() {
if (this->startTimerLength > 0) {
this->startTimerLength--;
return true;
}
return false;
@@ -209,35 +299,80 @@ void Settings::setGamemode(Gamemode gamemode) {
}
void Settings::selectPieces(PiecesType type, int value) {
if (!this->loadedPieces) return;
this->selectedPieces.emplace_back(type, value);
}
void Settings::unselectPieces(int index) {
if (!this->loadedPieces) return;
if (index >= this->selectedPieces.size()) return;
this->selectedPieces.erase(this->selectedPieces.begin() + index);
}
void Settings::confirmSelectedPieces() {
if (!this->loadedPieces) return;
this->menu.getPiecesList().unselectAll();
bool selectedNone = true;
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;}
if (!(value > this->maximumPiecesSize)) {
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;}
}
selectedNone = false;
}
}
else {
if (size > MAXIMUM_PIECES_SIZE) return;
this->menu.getPiecesList().selectPiece(size, value);
if (!(getSizeOfPieces(type) > this->maximumPiecesSize)) {
this->menu.getPiecesList().selectPiece(size, value);
selectedNone = false;
}
}
}
if (selectedNone) {
this->selectedPieces.push_back(DEFAULT_SELECTION);
this->confirmSelectedPieces();
}
}
bool Settings::increaseDistribution(int size) {
if (!this->loadedPieces) return false;
if (size < 1 || size > this->maximumPiecesSize) return false;
if (this->distributions.at(size) < DISTRIBUTION_MAX) {
this->distributions.at(size)++;
return true;
}
return false;
}
bool Settings::decreaseDistribution(int size) {
if (!this->loadedPieces) return false;
if (size < 1 || size > this->maximumPiecesSize) return false;
if (this->distributions.at(size) > 0) {
this->distributions.at(size)--;
return true;
}
return false;
}
void Settings::confirmDistribution() {
if (!this->loadedPieces) return;
for (int i = 1; i <= 15; i++) {
this->menu.getPiecesList().changeCustomDistribution(i, (double) 1 / (this->distributions.at(i) + 0.001));
}
}
Menu& Settings::getMenu() {
@@ -248,6 +383,14 @@ Keybinds& Settings::getKeybinds() {
return this->keybinds.at(this->chosenKeybinds);
}
int Settings::getMaximumPiecesSize() const {
return this->maximumPiecesSize;
}
bool Settings::hasLoadedPieces() const {
return this->loadedPieces;
}
int Settings::getKeybindsLayout() const {
return this->chosenKeybinds;
}
@@ -260,10 +403,14 @@ int Settings::getWindowSizeMultiplier() const {
return WINDOW_SIZE_MULTIPLIERS[this->windowSizeMode];
}
int Settings::getMasterVolume() const {
return this->masterVolume;
int Settings::getStartTimerLength() const {
return this->startTimerLength;
}
const std::vector<std::pair<PiecesType, int>>& Settings::getSelectedPieces() const {
return this->selectedPieces;
}
const std::vector<int>& Settings::getDistributions() const {
return this->distributions;
}

View File

@@ -4,34 +4,47 @@
#include "Keybinds.h"
#include "PiecesType.h"
#include <SFML/Graphics.hpp>
#include <vector>
#include <optional>
#include <SFML/Graphics.hpp>
static const int CURRENT_FILE_FORMAT_VERSION = 11;
static const int MAXIMUM_BOARD_WIDTH = 40;
static const int MAXIMUM_BOARD_HEIGHT = 40;
//#define __JMINOS_RELEASE__
#ifdef __JMINOS_RELEASE__
static const int MAXIMUM_PIECES_SIZE = 15;
static const int RELEASE_PIECES_SIZE = 15;
static const int DEBUG_PIECES_SIZE = 10;
static const int MINIMUM_PIECES_SIZE = 4;
#ifdef NDEBUG
static const int MAXIMUM_PIECES_SIZE = RELEASE_PIECES_SIZE;
#else
static const int MAXIMUM_PIECES_SIZE = 10;
static const int MAXIMUM_PIECES_SIZE = DEBUG_PIECES_SIZE;
#endif
static const std::pair<PiecesType, int> DEFAULT_SELECTION = {ALL_PIECES, MINIMUM_PIECES_SIZE};
class Settings {
private:
Menu menu;
int maximumPiecesSize;
bool loadedPieces;
std::vector<Keybinds> keybinds;
int chosenKeybinds;
int windowSizeMode;
int masterVolume;
int startTimerLength;
Gamemode gamemode;
std::vector<std::pair<PiecesType, int>> selectedPieces;
std::vector<int> distributions;
public:
Settings();
Settings(bool loadPieces);
void loadSettingsFromFile();
void loadPieces(int maximumPiecesSizeRequest);
void loadSettingsFromFile(bool loadPieces, std::optional<int> maximumPiecesSizeRequest);
void saveSettingsToFile() const;
@@ -47,9 +60,9 @@ class Settings {
void changeVideoMode(sf::RenderWindow& window) const;
bool raiseMasterVolume();
bool lengthenStartTimer();
bool lowerMasterVolume();
bool shortenStartTimer();
void setGamemode(Gamemode gamemode);
@@ -59,17 +72,29 @@ class Settings {
void confirmSelectedPieces();
bool increaseDistribution(int size);
bool decreaseDistribution(int size);
void confirmDistribution();
Menu& getMenu();
Keybinds& getKeybinds();
int getMaximumPiecesSize() const;
bool hasLoadedPieces() const;
int getKeybindsLayout() const;
Gamemode getGamemode() const;
int getWindowSizeMultiplier() const;
int getMasterVolume() const;
int getStartTimerLength() const;
const std::vector<std::pair<PiecesType, int>>& getSelectedPieces() const;
const std::vector<int>& getDistributions() const;
};

View File

@@ -15,17 +15,49 @@ int main() {
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;
#ifdef NDEBUG
std::cout << "IMPORTANT: You are currently in release mode, if you do not wish to generate big pieces (can take several minutes), type 'xmake f -m debug'." << std::endl;
#endif
std::cout << "INFO: Pieces files for size " << i << " not found, generating..." << std::endl;
pf.savePieces(i);
}
}
#ifndef NDEBUG
std::cout << "IMPORTANT: You are currently in debug mode, if you wish to use bigger pieces, type 'xmake f -m release'." << std::endl;
bool everythingGenerated = true;
for (int i = DEBUG_PIECES_SIZE; i <= RELEASE_PIECES_SIZE; i++) {
if (!std::filesystem::exists("data/pieces/" + std::to_string(i) + "minos.bin")) {
everythingGenerated = false;
}
}
if (!everythingGenerated) {
std::cout << "NOTE: You do not have all pieces generated, generating can take several minutes." << std::endl;
}
#endif
if (!std::filesystem::exists("data/config/settings.bin")) {
std::cout << "settings file not found, generating..." << std::endl;
std::cout << "INFO: Settings file not found, generating..." << std::endl;
resetSettingsFile();
}
else {
std::ifstream settingsFile("data/config/settings.bin", std::ios::binary);
char byte;
settingsFile.get(byte);
if ((unsigned char) byte < CURRENT_FILE_FORMAT_VERSION) {
std::cout << "INFO: Files format changed, regenerating..." << std::endl;
resetSettingsFile();
for (int i = 0; i < NUMBER_OF_KEYBINDS; i++) {
resetKeybindFile(i);
}
}
}
for (int i = 0; i < NUMBER_OF_KEYBINDS; i++) {
if (!std::filesystem::exists("data/config/keybinds/layout" + std::to_string(i) + ".bin")) {
std::cout << "keybind file n°" << (i + 1) << "/" << NUMBER_OF_KEYBINDS << " not found, generating..." << std::endl;
std::cout << "INFO: Keybind file n°" << (i + 1) << "/" << NUMBER_OF_KEYBINDS << " not found, generating..." << std::endl;
resetKeybindFile(i);
}
}
@@ -38,11 +70,23 @@ int main() {
void resetSettingsFile() {
if (!std::filesystem::exists("data/config")) {
std::filesystem::create_directories("data/config");
}
std::ofstream settingsFile("data/config/settings.bin", std::ios::trunc | std::ios::binary);
char byte;
Menu menu;
// file format version
byte = CURRENT_FILE_FORMAT_VERSION;
settingsFile.write(&byte, 1);
// maximum pieces size
byte = MINIMUM_PIECES_SIZE;
settingsFile.write(&byte, 1);
// keybind layout
byte = 0;
settingsFile.write(&byte, 1);
@@ -63,8 +107,8 @@ void resetSettingsFile() {
byte = 2;
settingsFile.write(&byte, 1);
// master volume
byte = 50;
// start timer length
byte = 2;
settingsFile.write(&byte, 1);
// gamemode
@@ -80,8 +124,12 @@ void resetSettingsFile() {
settingsFile.write(&byte, 1);
// piece distribution
byte = 0;
byte = DEFAULT;
settingsFile.write(&byte, 1);
for (int i = 1; i <= 15; i++) {
byte = 1;
settingsFile.write(&byte, 1);
}
// selected pieces
byte = ALL_PIECES;

View File

@@ -25,22 +25,35 @@ bool PiecesFiles::savePieces(int polyominoSize) const {
}
Generator generator;
std::vector<Polyomino> nMinos = generator.generatePolyominoes(polyominoSize);
std::vector<Polyomino> polyominoes = generator.generatePolyominoes(polyominoSize);
return this->savePieces(polyominoSize, polyominoes);
}
bool PiecesFiles::savePieces(int polyominoSize, std::vector<Polyomino>& polyominoes) const {
std::string filePath;
if (!this->getFilePath(polyominoSize, filePath)) {
return false;
}
std::ofstream piecesFile(filePath, std::ios::trunc | std::ios::binary);
if (!piecesFile.good()) {
return false;
}
// sorting the polyominoes is done after setting spawn position to ensure the order is always the same
for (Polyomino& nMino : nMinos) {
for (Polyomino& nMino : polyominoes) {
nMino.goToSpawnPosition();
}
std::sort(nMinos.begin(), nMinos.end());
std::sort(polyominoes.begin(), polyominoes.end());
for (const Polyomino& nMino : nMinos) {
for (const Polyomino& polyomino : polyominoes) {
// write the characteristics of the piece
char infoByte = (nMino.isConvex() << 7) + (nMino.hasHole() << 6) + nMino.getLength();
char infoByte = (polyomino.isConvex() << 7) + (polyomino.hasHole() << 6) + polyomino.getLength();
piecesFile.write(&infoByte, 1);
// write the positions of the piece
char positionByte;
for (Position position : nMino.getPositions()) {
for (const Position position : polyomino.getPositions()) {
positionByte = (position.x << 4) + position.y;
piecesFile.write(&positionByte, 1);
}
@@ -91,7 +104,7 @@ bool PiecesFiles::loadPieces(int polyominoSize, std::vector<Piece>& pieces, std:
char positionByte;
for (int i = 0; i < polyominoSize; i++) {
piecesFile.get(positionByte);
int x = (positionByte & xMask) >> 4;
int x = ((unsigned char) positionByte & xMask) >> 4;
int y = positionByte & yMask;
piecePositions.insert(Position{x, y});
}
@@ -119,6 +132,11 @@ bool PiecesFiles::loadPieces(int polyominoSize, std::vector<Piece>& pieces, std:
bool PiecesFiles::getFilePath(int polyominoSize, std::string& filePath) const {
std::string dataFolderPath = "data/pieces/";
if (!std::filesystem::exists(dataFolderPath)) {
std::filesystem::create_directories(dataFolderPath);
}
if (!std::filesystem::is_directory(dataFolderPath)) {
return false;
}

View File

@@ -22,13 +22,18 @@ class PiecesFiles {
*/
bool savePieces(int polyominoSize) const;
/**
* Generate a file containing all the pieces of the specified size, assuming they have been correctly generated
* @return If the file could be created
*/
bool savePieces(int polyominoSize, std::vector<Polyomino>& polyominoes) const;
/**
* Replace the content of the vectors by the pieces of the specified size, if the file wasn't found the vectors stays untouched
* @return If the file was found
*/
bool loadPieces(int polyominoSize, std::vector<Piece>& pieces, std::vector<int>& convexPieces, std::vector<int>& holelessPieces, std::vector<int>& otherPieces) const;
private:
/**
* Puts the path to the piece file of the specified size in order, if the data folder wasn't found the string stays untouched
* @return If the data folder was found

View File

@@ -3,45 +3,61 @@
#include "TextApp.h"
#include <chrono>
#include <filesystem>
#include <cmath>
static const int MAXIMUM_PIECES_SIZE = 10;
static const int BENCHMARK_PIECES_SIZE = 15;
void testGeneratorForAllSizes(int amount);
void testGeneratorForAllSizes(int max_size);
void testGeneratorForOneSize(int size);
void testGeneratorByprintingAllNminos(int n);
void testStoringAndRetrievingPieces(int size);
void generateFilesForAllSizes(int amount);
void generateFilesForOneSize(int size);
void loadFromFilesForOneSize(int size);
void readStatsFromFilesForAllSizes(int amount);
void printPiecesByTypesForOneSize(int size);
void readStatsFromFilesForAllSizes(int max_size);
void benchmarking(int min_size, int max_size);
int main(int argc, char** argv) {
std::srand(std::time(NULL));
// dev: generate files if it hasn't been done before, UI will NOT generate the files
//generateFilesForAllSizes(10);
#ifdef BENCHMARK
#ifndef NDEBUG
std::cout << "IMPORTANT: You are currently in debug mode, debug mode has lowest optimization settings and thus yields worse benchmarking results, to switch to release mode, type 'xmake f -m debug'." << std::endl;
#endif
TextApp UI;
UI.run();
benchmarking(1, BENCHMARK_PIECES_SIZE);
#else
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 << "INFO: Pieces files for size " << i << " not found, generating..." << std::endl;
pf.savePieces(i);
}
}
TextApp UI;
UI.run();
#endif
return 0;
}
void testGeneratorForAllSizes(int amount) {
void testGeneratorForAllSizes(int max_size) {
using std::chrono::high_resolution_clock;
using std::chrono::duration_cast;
using std::chrono::duration;
using std::chrono::milliseconds;
Generator generator;
for (int i = 1; i <= amount; i++) {
for (int i = 1; i <= max_size; i++) {
auto t1 = high_resolution_clock::now();
std::vector<Polyomino> n_minos = generator.generatePolyominoes(i);
auto t2 = high_resolution_clock::now();
duration<double, std::milli> ms_double = t2 - t1;
std::cout << "generated " << n_minos.size() << " polyominoes of size " << i << " in " << ms_double.count() << "ms" << std::endl;
std::cout << "Generated " << n_minos.size() << " polyominoes of size " << i << " in " << ms_double.count() << "ms" << std::endl;
}
}
@@ -63,24 +79,8 @@ void testGeneratorForOneSize(int size) {
}
}
void testGeneratorByprintingAllNminos(int n) {
Generator generator;
std::vector<Polyomino> n_minos = generator.generatePolyominoes(n);
for (Polyomino& n_mino : n_minos) {
n_mino.goToSpawnPosition();
}
std::sort(n_minos.begin(), n_minos.end());
for (Polyomino& n_mino : n_minos) {
std::cout << n_mino << std::endl;
}
}
void testStoringAndRetrievingPieces(int size) {
void printPiecesByTypesForOneSize(int size) {
PiecesFiles piecesFiles;
piecesFiles.savePieces(size);
std::vector<Piece> pieces;
std::vector<int> convexPieces;
@@ -104,79 +104,9 @@ void testStoringAndRetrievingPieces(int size) {
}
}
void generateFilesForAllSizes(int amount) {
using std::chrono::high_resolution_clock;
using std::chrono::duration_cast;
using std::chrono::duration;
using std::chrono::milliseconds;
void readStatsFromFilesForAllSizes(int max_size) {
PiecesFiles piecesFiles;
for (int i = 1; i <= amount; i++) {
auto t1 = high_resolution_clock::now();
piecesFiles.savePieces(i);
auto t2 = high_resolution_clock::now();
duration<double, std::milli> ms_double = t2 - t1;
std::cout << "Generated pieces files for size " << i << " in " << ms_double.count() << "ms" << std::endl;
}
std::vector<Piece> pieces;
std::vector<int> convexPieces;
std::vector<int> holelessPieces;
std::vector<int> otherPieces;
for (int i = 1; i <= amount; i++) {
auto t1 = high_resolution_clock::now();
piecesFiles.loadPieces(i, pieces, convexPieces, holelessPieces, otherPieces);
auto t2 = high_resolution_clock::now();
duration<double, std::milli> ms_double = t2 - t1;
std::cout << "Read pieces from files for size " << i << " in " << ms_double.count() << "ms" << std::endl;
}
}
void generateFilesForOneSize(int size) {
using std::chrono::high_resolution_clock;
using std::chrono::duration_cast;
using std::chrono::duration;
using std::chrono::milliseconds;
PiecesFiles piecesFiles;
std::cout << "Generating " << size << "-minos files" << std::endl;
for (int i = 0; i < 10; i++) {
auto t1 = high_resolution_clock::now();
piecesFiles.savePieces(size);
auto t2 = high_resolution_clock::now();
duration<double, std::milli> ms_double = t2 - t1;
std::cout << ms_double.count() << "ms" << std::endl;
}
}
void loadFromFilesForOneSize(int size) {
using std::chrono::high_resolution_clock;
using std::chrono::duration_cast;
using std::chrono::duration;
using std::chrono::milliseconds;
PiecesFiles piecesFiles;
std::vector<Piece> pieces;
std::vector<int> convexPieces;
std::vector<int> holelessPieces;
std::vector<int> otherPieces;
std::cout << "Loading " << size << "-minos from files" << std::endl;
for (int i = 0; i < 10; i++) {
auto t1 = high_resolution_clock::now();
piecesFiles.loadPieces(size, pieces, convexPieces, holelessPieces, otherPieces);
auto t2 = high_resolution_clock::now();
duration<double, std::milli> ms_double = t2 - t1;
std::cout << ms_double.count() << "ms" << std::endl;
}
}
void readStatsFromFilesForAllSizes(int amount) {
PiecesFiles piecesFiles;
for (int i = 1; i <= amount; i++) {
for (int i = 1; i <= max_size; i++) {
std::vector<Piece> pieces;
std::vector<int> convexPieces;
std::vector<int> holelessPieces;
@@ -189,3 +119,66 @@ void readStatsFromFilesForAllSizes(int amount) {
std::cout << "Others " << i << "-minos : " << otherPieces.size() << std::endl;
}
}
void benchmarking(int min_size, int max_size) {
using std::chrono::high_resolution_clock;
using std::chrono::duration_cast;
using std::chrono::duration;
using std::chrono::milliseconds;
std::cout << "| n | Number | Generation | File storing | File retrieving | File size |" << std::endl;
std::cout << "| - | - | - | - | - | - |" << std::endl;
Generator gen;
PiecesFiles pf;
for (int i = min_size; i <= max_size; i++) {
std::cout << "| " << i;
auto t1 = high_resolution_clock::now();
std::vector<Polyomino> polyominoes = gen.generatePolyominoes(i);
auto t2 = high_resolution_clock::now();
duration<double, std::milli> ms_double = t2 - t1;
std::cout << " | " << polyominoes.size();
std::flush(std::cout);
std::cout << " | " << (int) ms_double.count() / 1000 << "s " << std::fmod(ms_double.count(), 1000) << "ms ";
std::flush(std::cout);
t1 = high_resolution_clock::now();
pf.savePieces(i, polyominoes);
t2 = high_resolution_clock::now();
ms_double = t2 - t1;
std::cout << " | " << (int) ms_double.count() / 1000 << "s " << std::fmod(ms_double.count(), 1000) << "ms ";
std::flush(std::cout);
polyominoes.clear();
polyominoes.shrink_to_fit();
std::vector<Piece> pieces;
std::vector<int> convexPieces;
std::vector<int> holelessPieces;
std::vector<int> otherPieces;
t1 = high_resolution_clock::now();
pf.loadPieces(i, pieces, convexPieces, holelessPieces, otherPieces);
t2 = high_resolution_clock::now();
ms_double = t2 - t1;
std::cout << " | " << (int) ms_double.count() / 1000 << "s " << std::fmod(ms_double.count(), 1000) << "ms ";
std::flush(std::cout);
pieces.clear();
pieces.shrink_to_fit();
convexPieces.clear();
convexPieces.shrink_to_fit();
holelessPieces.clear();
holelessPieces.shrink_to_fit();
otherPieces.clear();
otherPieces.shrink_to_fit();
std::string filePath;
pf.getFilePath(i, filePath);
int fileSize = std::filesystem::file_size(filePath);
std::cout << " | " << fileSize << " bytes |" << std::endl;
std::flush(std::cout);
}
}

View File

@@ -0,0 +1,97 @@
#include "./AssetManager.h"
#include <map>
static const unsigned char data_fonts_pressstart_prstart_ttf[] = {
#include <data/fonts/pressstart/prstart.ttf.h>
};
static const unsigned char data_fonts_pressstart_prstartk_ttf[] = {
#include <data/fonts/pressstart/prstartk.ttf.h>
};
static const unsigned char data_images_keybinds_Rotate180_png[] = {
#include <data/images/keybinds/Rotate180.png.h>
};
static const unsigned char data_images_keybinds_Rotate0_png[] = {
#include <data/images/keybinds/Rotate0.png.h>
};
static const unsigned char data_images_keybinds_RotateCCW_png[] = {
#include <data/images/keybinds/RotateCCW.png.h>
};
static const unsigned char data_images_keybinds_Retry_png[] = {
#include <data/images/keybinds/Retry.png.h>
};
static const unsigned char data_images_keybinds_RotateCW_png[] = {
#include <data/images/keybinds/RotateCW.png.h>
};
static const unsigned char data_images_keybinds_Moveright_png[] = {
#include <data/images/keybinds/Moveright.png.h>
};
static const unsigned char data_images_keybinds_Harddrop_png[] = {
#include <data/images/keybinds/Harddrop.png.h>
};
static const unsigned char data_images_keybinds_Moveleft_png[] = {
#include <data/images/keybinds/Moveleft.png.h>
};
static const unsigned char data_images_keybinds_Hold_png[] = {
#include <data/images/keybinds/Hold.png.h>
};
static const unsigned char data_images_keybinds_Softdrop_png[] = {
#include <data/images/keybinds/Softdrop.png.h>
};
static const unsigned char data_images_keybinds_Pause_png[] = {
#include <data/images/keybinds/Pause.png.h>
};
static const Asset assets[] = {
{data_fonts_pressstart_prstart_ttf, sizeof(data_fonts_pressstart_prstart_ttf)},
{data_fonts_pressstart_prstartk_ttf, sizeof(data_fonts_pressstart_prstartk_ttf)},
{data_images_keybinds_Rotate180_png, sizeof(data_images_keybinds_Rotate180_png)},
{data_images_keybinds_Rotate0_png, sizeof(data_images_keybinds_Rotate0_png)},
{data_images_keybinds_RotateCCW_png, sizeof(data_images_keybinds_RotateCCW_png)},
{data_images_keybinds_Retry_png, sizeof(data_images_keybinds_Retry_png)},
{data_images_keybinds_RotateCW_png, sizeof(data_images_keybinds_RotateCW_png)},
{data_images_keybinds_Moveright_png, sizeof(data_images_keybinds_Moveright_png)},
{data_images_keybinds_Harddrop_png, sizeof(data_images_keybinds_Harddrop_png)},
{data_images_keybinds_Moveleft_png, sizeof(data_images_keybinds_Moveleft_png)},
{data_images_keybinds_Hold_png, sizeof(data_images_keybinds_Hold_png)},
{data_images_keybinds_Softdrop_png, sizeof(data_images_keybinds_Softdrop_png)},
{data_images_keybinds_Pause_png, sizeof(data_images_keybinds_Pause_png)},
};
static const std::map<std::string, AssetName> assetMap = {
{"data/fonts/pressstart/prstart.ttf", AssetName::data_fonts_pressstart_prstart_ttf},
{"data/fonts/pressstart/prstartk.ttf", AssetName::data_fonts_pressstart_prstartk_ttf},
{"data/images/keybinds/Rotate180.png", AssetName::data_images_keybinds_Rotate180_png},
{"data/images/keybinds/Rotate0.png", AssetName::data_images_keybinds_Rotate0_png},
{"data/images/keybinds/RotateCCW.png", AssetName::data_images_keybinds_RotateCCW_png},
{"data/images/keybinds/Retry.png", AssetName::data_images_keybinds_Retry_png},
{"data/images/keybinds/RotateCW.png", AssetName::data_images_keybinds_RotateCW_png},
{"data/images/keybinds/Moveright.png", AssetName::data_images_keybinds_Moveright_png},
{"data/images/keybinds/Harddrop.png", AssetName::data_images_keybinds_Harddrop_png},
{"data/images/keybinds/Moveleft.png", AssetName::data_images_keybinds_Moveleft_png},
{"data/images/keybinds/Hold.png", AssetName::data_images_keybinds_Hold_png},
{"data/images/keybinds/Softdrop.png", AssetName::data_images_keybinds_Softdrop_png},
{"data/images/keybinds/Pause.png", AssetName::data_images_keybinds_Pause_png},
};
const Asset& getResource(AssetName fileName) {
return assets[static_cast<std::size_t>(fileName)];
}
const Asset& getResource(const std::string& fileName) {
return getResource(assetMap.at(fileName));
}

30
src/Utils/AssetManager.h Normal file
View File

@@ -0,0 +1,30 @@
#pragma once
#include <cstdint>
#include <string>
struct Asset {
const unsigned char* data;
std::size_t size;
};
enum class AssetName {
data_fonts_pressstart_prstart_ttf,
data_fonts_pressstart_prstartk_ttf,
data_images_keybinds_Rotate180_png,
data_images_keybinds_Rotate0_png,
data_images_keybinds_RotateCCW_png,
data_images_keybinds_Retry_png,
data_images_keybinds_RotateCW_png,
data_images_keybinds_Moveright_png,
data_images_keybinds_Harddrop_png,
data_images_keybinds_Moveleft_png,
data_images_keybinds_Hold_png,
data_images_keybinds_Softdrop_png,
data_images_keybinds_Pause_png,
};
const Asset& getResource(AssetName fileName);
const Asset& getResource(const std::string& fileName);

View File

@@ -1,5 +1,7 @@
add_rules("mode.debug", "mode.release")
includes("xmake/bin2c.lua")
add_requires("sfml 3.0.0")
set_languages("c++20")
@@ -12,17 +14,40 @@ target("core")
add_files("src/Core/*.cpp")
target("text")
set_kind("binary")
set_default(false)
set_kind("binary")
add_files("./src/TextUI/*.cpp")
add_deps("core")
target("benchmark")
set_default(false)
set_kind("binary")
add_files("./src/TextUI/*.cpp")
add_deps("core")
add_defines("BENCHMARK")
target("graph")
set_default(true)
add_rules("bin2c", {
extensions = {".png", ".ttf"},
outputSource = {"src/Utils/AssetManager.cpp"},
outputHeader = {"src/Utils/AssetManager.h"}
})
set_kind("binary")
add_files("./src/GraphicalUI/**.cpp")
add_files("data/fonts/**.ttf", "data/images/**.png")
add_deps("core")
add_packages("sfml")
if is_mode("release") then
add_defines("NDEBUG")
end
if is_plat("mingw") then
add_ldflags("-static-libstdc++")
end
--
-- If you want to known more usage about xmake, please see https://xmake.io
--

124
xmake/bin2c.lua Normal file
View File

@@ -0,0 +1,124 @@
rule("bin2c")
set_extensions(".bin")
on_load(function (target)
local headerdir = path.join(target:autogendir(), "rules", "bin2c")
local outputSource = table.unpack(target:extraconf("rules", "bin2c", "outputSource"))
if not os.isdir(headerdir) then
os.mkdir(headerdir)
end
target:add("includedirs", headerdir)
target:add("files", outputSource)
end)
before_buildcmd_files(function (target, batchcmds, sourcebatch, opt)
local outputHeader = table.unpack(target:extraconf("rules", "bin2c", "outputHeader"))
local outputHeaderEnumContent = ""
for _, filePath in ipairs(sourcebatch.sourcefiles) do
local escapedName = string.gsub(filePath, "[/|.]", "_")
outputHeaderEnumContent = outputHeaderEnumContent .. "\t" .. escapedName .. ",\n"
end
local outputHeaderContent = string.format([[
#pragma once
#include <cstdint>
#include <string>
struct Asset {
const unsigned char* data;
std::size_t size;
};
enum class AssetName {
%s
};
const Asset& getResource(AssetName fileName);
const Asset& getResource(const std::string& fileName);
]], outputHeaderEnumContent)
local outputSource = table.unpack(target:extraconf("rules", "bin2c", "outputSource"))
local relativePath = path.join(path.relative(path.directory(outputHeader), path.directory(outputSource)), path.filename(outputHeader))
local outputSourceContent = string.format([[
#include "%s"
#include <map>
]], relativePath)
local outputSourceArrayVars = ""
local outputSourceMapVars = ""
for _, filePath in ipairs(sourcebatch.sourcefiles) do
local escapedName = string.gsub(filePath, "[/|.]", "_")
local varDecl = string.format("static const unsigned char %s[] = {\n\t#include <%s>\n};\n\n", escapedName, filePath .. ".h")
outputSourceContent = outputSourceContent .. varDecl
outputSourceArrayVars = outputSourceArrayVars .. string.format("\t{%s, sizeof(%s)},\n", escapedName, escapedName)
outputSourceMapVars = outputSourceMapVars .. string.format("\t{\"%s\", AssetName::%s},\n", filePath, escapedName)
end
outputSourceContent = outputSourceContent .. string.format([[
static const Asset assets[] = {
%s
};
static const std::map<std::string, AssetName> assetMap = {
%s
};
]], outputSourceArrayVars, outputSourceMapVars)
outputSourceContent = outputSourceContent .. [[
const Asset& getResource(AssetName fileName) {
return assets[static_cast<std::size_t>(fileName)];
}
const Asset& getResource(const std::string& fileName) {
return getResource(assetMap.at(fileName));
}
]]
for _, sourcefile_bin in ipairs(sourcebatch.sourcefiles) do
-- get header file
local headerdir = path.join(target:autogendir(), "rules", "bin2c")
local headerfile = path.join(headerdir, sourcefile_bin .. ".h")
target:add("includedirs", headerdir)
-- add commands
batchcmds:show_progress(opt.progress, "${color.build.object}generating.bin2c %s", sourcefile_bin)
batchcmds:mkdir(headerdir)
local argv = {"lua", "private.utils.bin2c", "-i", path(sourcefile_bin), "-o", path(headerfile)}
local linewidth = target:extraconf("rules", "bin2c", "linewidth")
if linewidth then
table.insert(argv, "-w")
table.insert(argv, tostring(linewidth))
end
local nozeroend = target:extraconf("rules", "bin2c", "nozeroend")
if nozeroend then
table.insert(argv, "--nozeroend")
end
batchcmds:vrunv(os.programfile(), argv, {envs = {XMAKE_SKIP_HISTORY = "y"}})
-- add deps
batchcmds:add_depfiles(sourcefile_bin)
batchcmds:set_depmtime(os.mtime(headerfile))
batchcmds:set_depcache(target:dependfile(headerfile))
end
batchcmds:show_progress(opt.progress, "${color.build.object}generating.bin2c %s", outputHeader)
io.writefile(outputHeader, outputHeaderContent)
batchcmds:show_progress(opt.progress, "${color.build.object}generating.bin2c %s", outputSource)
io.writefile(outputSource, outputSourceContent)
end)