70 Commits

Author SHA1 Message Date
17459c4026 use VarInt
All checks were successful
Linux arm64 / Build (push) Successful in 12m53s
2025-06-12 21:38:47 +02:00
3e40ff7252 add compression
All checks were successful
Linux arm64 / Build (push) Successful in 1m58s
2025-06-12 21:28:46 +02:00
dd8314f76c ff windaube veut toujours pas
All checks were successful
Linux arm64 / Build (push) Successful in 2m45s
2025-06-12 17:35:39 +02:00
d7f52239f0 and again
All checks were successful
Linux arm64 / Build (push) Successful in 1m57s
2025-06-03 12:03:59 +02:00
72f4ea75ff fix again 2025-06-03 12:03:47 +02:00
b1b7277666 fix array initialization
Some checks failed
Linux arm64 / Build (push) Failing after 1m53s
2025-06-03 11:55:12 +02:00
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
e0de2b5f90 aaaa 2025-03-28 21:51:30 +01:00
df7c9bd211 fixes B2B 2025-03-28 21:45:50 +01:00
f883bd5bab ajout board menu 2025-03-28 21:31:24 +01:00
0f026635f6 stats in-game 2025-03-28 21:00:46 +01:00
be071bd606 ajout holdbox 2025-03-28 16:32:44 +01:00
3320545465 next queue fixed 2025-03-28 15:53:21 +01:00
d1646d0fb5 next queue lezgo 2025-03-28 14:58:52 +01:00
3d74ef7cd5 added ZEN gamemode 2025-03-27 21:50:36 +01:00
88cb44c5fe update readme.md 2025-03-27 21:03:32 +01:00
009ed8edc3 ajout menu info 2025-03-26 00:39:39 +01:00
b2567844fc fini menu keybinds 2025-03-25 20:06:02 +01:00
de8a5e6e34 keybinds menu 2025-03-25 17:17:36 +01:00
c168cd68d7 refactoring 2025-03-24 22:39:16 +01:00
321271b748 ?? 2025-03-24 16:25:34 +01:00
c601424481 on track pas les settings wtf 2025-03-24 16:25:30 +01:00
fd9fd4586a on peut changer les settings wtf 2025-03-24 16:20:37 +01:00
8a4c4201fe taille board adaptative 2025-03-24 14:59:02 +01:00
c08cfc2255 ingame moins moche (mais pas fini) 2025-03-24 13:28:38 +01:00
38008e00bc fixed game logic 2025-03-23 21:41:37 +01:00
507bc9cc86 omg on a un deuxième menu 2025-03-23 20:15:05 +01:00
e721a71894 omg on a un menu 2025-03-23 18:45:34 +01:00
1781b85332 update readme 2025-03-23 16:53:58 +01:00
92b58c4b98 l'espoir renaît???? 2025-03-23 11:50:55 +01:00
8635d4b853 abandon 2025-03-23 00:16:59 +01:00
9780a36af4 ff 2025-03-22 23:30:52 +01:00
6b16abda6a fichier settings 2025-03-22 22:03:57 +01:00
30dd323e22 trop de trucs oscours 2025-03-22 17:41:33 +01:00
d87ddcdc22 Merge branch 'main' of https://git.ale-pri.com/TetrisNerd/jminos 2025-03-22 11:00:34 +01:00
8aaced68d0 merging aftermath 2025-03-22 11:00:31 +01:00
be6c8d9f77 Actualiser .vscode/c_cpp_properties.json 2025-03-22 09:56:42 +00:00
d9ccecfdd8 Merge branch 'main' of https://git.ale-pri.com/TetrisNerd/jminos 2025-03-22 10:53:30 +01:00
02bab6ed87 Merge pull request 'Better xmake.lua + Intellisense' (#2) from xmake into main
Reviewed-on: https://127.0.0.1:3000/TetrisNerd/jminos/pulls/2
2025-03-22 09:51:36 +00:00
0e17996c35 toujours plus de settings 2025-03-22 10:49:55 +01:00
98 changed files with 4451 additions and 519 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

7
.gitignore vendored
View File

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

View File

@@ -1,10 +1,10 @@
{ {
"configurations": [ "configurations": [
{ {
"name": "JMinos", "name": "jminos",
"cppStandard": "c++20", "cppStandard": "c++20",
"compileCommands": ".vscode/compile_commands.json" "compileCommands": ".vscode/compile_commands.json"
} }
], ],
"version": 4 "version": 4
} }

114
README.md
View File

@@ -1,8 +1,64 @@
# jminos # jminos
## Manual build and run Modern stacker game with every polyominoes from size 1 to 15, made in C++ with [SFML 3](https://www.sfml-dev.org/)!
You need to install xmake and have a compiler with c++20 compatibility ## Download
// TODO when the game is finished //
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
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 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
- SPRINT : clear 40 lines as fast as possible!
- MARATHON : clear 200 lines with increasing gravity!
- ULTRA : scores as much as possible in only 2 minutes!
- MASTER : clear 200 lines at levels higher than maximum gravity!
- INVISIBLE : get 1000 grade while not being able to see the board!
- ZEN : practice indefinitely in this mode with no gravity!
### 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
This project uses xmake for compiling, xmake is cross-platform and works in most OS, xmake also automatically install supported librairies.
To be able to build this project, you need to [have xmake installed](https://xmake.io) and have a compiler with C++20 compatibility.
### Build the project ### Build the project
@@ -15,8 +71,56 @@ If you need to change the toolchain (for example using gcc):
### Run the project ### Run the project
Graphical version: ``xmake run``
``xmake run graph``
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`` ``xmake run text``
### 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 type checking 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 f -m release``
``xmake build bmark``
``xmake run bmark``
## Credits
Library used: [SFML 3](https://www.sfml-dev.org/).
Font used: [Press Start](https://www.zone38.net/font/#pressstart).
Inspired by other modern stacker games such as Techmino, jstris, tetr.io, etc.
This game isn't affiliated with any of them.

Binary file not shown.

View File

@@ -0,0 +1,17 @@
Thanks for downloading one of codeman38's retro video game fonts, as seen on Memepool, BoingBoing, and all around the blogosphere.
So, you're wondering what the license is for these fonts? Pretty simple; it's based upon that used for Bitstream's Vera font set <http://www.gnome.org/fonts/>.
Basically, here are the key points summarized, in as little legalese as possible; I hate reading license agreements as much as you probably do:
With one specific exception, you have full permission to bundle these fonts in your own free or commercial projects-- and by projects, I'm referring to not just software but also electronic documents and print publications.
So what's the exception? Simple: you can't re-sell these fonts in a commercial font collection. I've seen too many font CDs for sale in stores that are just a repackaging of thousands of freeware fonts found on the internet, and in my mind, that's quite a bit like highway robbery. Note that this *only* applies to products that are font collections in and of themselves; you may freely bundle these fonts with an operating system, application program, or the like.
Feel free to modify these fonts and even to release the modified versions, as long as you change the original font names (to ensure consistency among people with the font installed) and as long as you give credit somewhere in the font file to codeman38 or zone38.net. I may even incorporate these changes into a later version of my fonts if you wish to send me the modifed fonts via e-mail.
Also, feel free to mirror these fonts on your own site, as long as you make it reasonably clear that these fonts are not your own work. I'm not asking for much; linking to zone38.net or even just mentioning the nickname codeman38 should be enough.
Well, that pretty much sums it up... so without further ado, install and enjoy these fonts from the golden age of video games.
[ codeman38 | cody@zone38.net | http://www.zone38.net/ ]

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 619 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 618 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 604 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 603 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 591 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 600 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 624 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 627 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 622 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 623 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 606 B

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

View File

@@ -7,7 +7,6 @@ We will only talk about pieces and not polyominoes. In this project, pieces are
Each frame, the UI will translate the user's input into a series of action to apply to the game. The list of action is the following: Each frame, the UI will translate the user's input into a series of action to apply to the game. The list of action is the following:
- Quit the game
- Pause - Pause
- Retry - Retry
- Hold - Hold
@@ -78,8 +77,9 @@ Since this game uses polyomino of high sizes which are very unplayable, we will
5. Do the same as step 4 but now we move the piece one line up every time 5. Do the same as step 4 but now we move the piece one line up every time
6. Cancel the rotation 6. Cancel the rotation
Kicking is primarly designed for rotating, but it is also applied when a piece spawns into a wall. _Note: at step 3, the direction which is checked first is actually the last movement done by the player._
0° rotations will first try to move the piece one position down, and if it can't try kicking the piece, only registering a kick if the piece couldn't move down. Kicking is primarly designed for rotating, but there is also a 0° rotation applied when a piece spawns into a wall.
0° rotations will first try to move the piece one position down, and if it can't, try kicking the piece, only registering a kick if the piece couldn't move down.
## Detecting spins ## Detecting spins
@@ -92,8 +92,31 @@ Since we work with a great deal of different size and shapes, the rules for spin
## Score calculation ## Score calculation
- For every position soft dropped, add 1 to the score - When locking a piece, add 1 to the score if it is due to lock delay, or 10 if it was hard dropped.
- For every position hard dropped, add 2 to the score
- When clearing one line, add 100 to the score, 200 for 2 lines, 400 for 3 lines, 800 for 4 lines, 1600 for 5 lines, etc. - When clearing one line, add 100 to the score, 200 for 2 lines, 400 for 3 lines, 800 for 4 lines, 1600 for 5 lines, etc.
- If the line clear is a spin, count the score like a normal clear of 2x more line (200 for 1-line spin, 800 for 2, 3200 for 3, etc.) - If the line clear is a spin, count the score like a normal clear of 2x more line (200 for 1-line spin, 800 for 2, 3200 for 3, etc.)
- When performing a spin, a mini spin, or clearing 4 or more lines, B2B is activated, every subsequent line clear that is a spin, a mini spin, or clear 4 or more lines, scores twice as much - When performing a spin, a mini spin, or clearing 4 or more lines, B2B is activated, every subsequent line clear that is a spin, a mini spin, or clear 4 or more lines, scores twice as much
## Grade calculation
Grade is an alternate system to line clears.
- Each time a piece is dropped, 1 point is added to the total.
- For each cleared line, 1 point is added to the total.
The only exception occurs when the total ends in '99', a line must be cleared to progress.
When this line is cleared, the point of the piece is also counted towards the total.
## 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

@@ -0,0 +1,68 @@
#include "Compression.h"
#include "VarInt.h"
#include <cassert>
#include <zlib.h>
#define COMPRESSION_THRESHOLD 64
static DataBuffer Inflate(const std::uint8_t* source, std::size_t size, std::size_t uncompressedSize) {
DataBuffer result;
result.Resize(uncompressedSize);
uncompress(reinterpret_cast<Bytef*>(result.data()), reinterpret_cast<uLongf*>(&uncompressedSize),
reinterpret_cast<const Bytef*>(source), static_cast<uLong>(size));
assert(result.GetSize() == uncompressedSize);
return result;
}
static DataBuffer Deflate(const std::uint8_t* source, std::size_t size) {
DataBuffer result;
uLongf compressedSize = size;
result.Resize(size); // Resize for the compressed data to fit into
compress(
reinterpret_cast<Bytef*>(result.data()), &compressedSize, reinterpret_cast<const Bytef*>(source), static_cast<uLong>(size));
result.Resize(compressedSize); // Resize to cut useless data
return result;
}
DataBuffer Compress(const DataBuffer& buffer) {
DataBuffer packet;
if (buffer.GetSize() < COMPRESSION_THRESHOLD) {
// Don't compress since it's a small packet
VarInt compressedDataLength = 0;
packet << compressedDataLength;
packet << buffer;
return packet;
}
DataBuffer compressedData = Deflate(buffer.data(), buffer.GetSize());
VarInt uncompressedDataLength = buffer.GetSize();
packet << uncompressedDataLength;
packet.WriteSome(compressedData.data(), compressedData.GetSize());
return packet;
}
DataBuffer Decompress(DataBuffer& buffer, std::uint64_t packetLength) {
VarInt uncompressedLength;
buffer >> uncompressedLength;
std::uint64_t compressedLength = packetLength - uncompressedLength.GetSerializedLength();
if (uncompressedLength.GetValue() == 0) {
// Data already uncompressed. Nothing to do
DataBuffer ret;
buffer.ReadSome(ret, compressedLength);
return ret;
}
assert(buffer.GetReadOffset() + compressedLength <= buffer.GetSize());
return Inflate(buffer.data() + buffer.GetReadOffset(), compressedLength, uncompressedLength.GetValue());
}

23
src/Common/Compression.h Normal file
View File

@@ -0,0 +1,23 @@
#pragma once
/**
* \file Compression.h
* \brief File containing compress utilities
*/
#include "DataBuffer.h"
/**
* \brief Compress some data
* \param buffer the data to compress
* \return the compressed data
*/
DataBuffer Compress(const DataBuffer& buffer);
/**
* \brief Uncompress some data
* \param buffer the data to uncompress
* \param packetLength lenght of data
* \return the uncompressed data
*/
DataBuffer Decompress(DataBuffer& buffer, std::uint64_t packetLength);

98
src/Common/DataBuffer.cpp Normal file
View File

@@ -0,0 +1,98 @@
#include "DataBuffer.h"
#include <fstream>
#include <iomanip>
#include <sstream>
#include <iostream>
DataBuffer::DataBuffer(std::size_t initalCapacity) : m_ReadOffset(0) {
m_Buffer.reserve(initalCapacity);
}
DataBuffer::DataBuffer() : m_ReadOffset(0) {}
DataBuffer::DataBuffer(const DataBuffer& other) : m_Buffer(other.m_Buffer), m_ReadOffset(other.m_ReadOffset) {}
DataBuffer::DataBuffer(DataBuffer&& other) : m_Buffer(std::move(other.m_Buffer)), m_ReadOffset(std::move(other.m_ReadOffset)) {}
DataBuffer::DataBuffer(const std::string& str) : m_Buffer(str.begin(), str.end()), m_ReadOffset(0) {}
DataBuffer::DataBuffer(const DataBuffer& other, Data::difference_type offset) : m_ReadOffset(0) {
m_Buffer.reserve(other.GetSize() - static_cast<std::size_t>(offset));
std::copy(other.m_Buffer.begin() + offset, other.m_Buffer.end(), std::back_inserter(m_Buffer));
}
DataBuffer& DataBuffer::operator=(const DataBuffer& other) {
m_Buffer = other.m_Buffer;
m_ReadOffset = other.m_ReadOffset;
return *this;
}
DataBuffer& DataBuffer::operator=(DataBuffer&& other) {
m_Buffer = std::move(other.m_Buffer);
m_ReadOffset = std::move(other.m_ReadOffset);
return *this;
}
void DataBuffer::SetReadOffset(std::size_t pos) {
assert(pos <= GetSize());
m_ReadOffset = pos;
}
std::size_t DataBuffer::GetSize() const {
return m_Buffer.size();
}
std::size_t DataBuffer::GetRemaining() const {
return m_Buffer.size() - m_ReadOffset;
}
DataBuffer::iterator DataBuffer::begin() {
return m_Buffer.begin();
}
DataBuffer::iterator DataBuffer::end() {
return m_Buffer.end();
}
DataBuffer::const_iterator DataBuffer::begin() const {
return m_Buffer.begin();
}
DataBuffer::const_iterator DataBuffer::end() const {
return m_Buffer.end();
}
std::ostream& operator<<(std::ostream& os, const DataBuffer& buffer) {
for (unsigned char u : buffer)
os << std::hex << std::setfill('0') << std::setw(2) << static_cast<int>(u) << " ";
os << std::dec;
return os;
}
bool DataBuffer::ReadFile(const std::string& fileName) {
try {
std::ifstream file(fileName, std::istream::binary);
std::ostringstream ss;
ss << file.rdbuf();
const std::string& s = ss.str();
m_Buffer = DataBuffer::Data(s.begin(), s.end());
m_ReadOffset = 0;
} catch (std::exception& e) {
std::cerr << "Failed to read file : " << e.what() << std::endl;
return false;
}
return m_Buffer.size() > 0;
}
bool DataBuffer::WriteFile(const std::string& fileName) const {
try {
std::ofstream file(fileName, std::ostream::binary);
file.write(reinterpret_cast<const char*>(m_Buffer.data()), static_cast<std::streamsize>(m_Buffer.size()));
file.flush();
} catch (std::exception& e) {
std::cerr << "Failed to write file : " << e.what() << std::endl;
return false;
}
return true;
}

284
src/Common/DataBuffer.h Normal file
View File

@@ -0,0 +1,284 @@
#pragma once
/**
* \file DataBuffer.h
* \brief File containing the blitz::DataBuffer class
*/
#include <algorithm>
#include <cassert>
#include <cstdint>
#include <cstring>
#include <string>
#include <vector>
/**
* \class DataBuffer
* \brief Class used to manipulate memory
*/
class DataBuffer {
private:
typedef std::vector<std::uint8_t> Data;
Data m_Buffer;
std::size_t m_ReadOffset;
public:
typedef Data::iterator iterator;
typedef Data::const_iterator const_iterator;
typedef Data::reference reference;
typedef Data::const_reference const_reference;
typedef Data::difference_type difference_type;
DataBuffer();
DataBuffer(std::size_t initalCapacity);
DataBuffer(const DataBuffer& other);
DataBuffer(const DataBuffer& other, difference_type offset);
DataBuffer(DataBuffer&& other);
DataBuffer(const std::string& str);
DataBuffer& operator=(const DataBuffer& other);
DataBuffer& operator=(DataBuffer&& other);
/**
* \brief Append data to the buffer
*/
template <typename T>
void Append(const T& data) {
std::size_t size = sizeof(data);
std::size_t end_pos = m_Buffer.size();
m_Buffer.resize(m_Buffer.size() + size);
std::memcpy(&m_Buffer[end_pos], &data, size);
}
/**
* \brief Append data to the buffer
*/
template <typename T>
DataBuffer& operator<<(const T& data) {
Append(data);
return *this;
}
/**
* \brief Append a string to the buffer
* \warning Don't use it for binary data !
* \param str The string to append
*/
DataBuffer& operator<<(const std::string& str) {
std::size_t strlen = str.length() + 1; // including null character
Resize(GetSize() + strlen);
std::memcpy(m_Buffer.data() + GetSize() - strlen, str.data(), strlen);
return *this;
}
/**
* \brief Append data to the buffer from another const buffer
* \param data The buffer to append
*/
DataBuffer& operator<<(const DataBuffer& data) {
m_Buffer.insert(m_Buffer.end(), data.begin(), data.end());
return *this;
}
/**
* \brief Read some data from the buffer and assign to desired variable
*/
template <typename T>
DataBuffer& operator>>(T& data) {
assert(m_ReadOffset + sizeof(T) <= GetSize());
data = *(reinterpret_cast<T*>(&m_Buffer[m_ReadOffset]));
m_ReadOffset += sizeof(T);
return *this;
}
/**
* \brief Read some data from the buffer and assign to the new buffer
* \param data The buffer to assign
*/
DataBuffer& operator>>(DataBuffer& data) {
data.Resize(GetSize() - m_ReadOffset);
std::copy(m_Buffer.begin() + static_cast<difference_type>(m_ReadOffset), m_Buffer.end(), data.begin());
m_ReadOffset = m_Buffer.size();
return *this;
}
/**
* \brief Read a string from the buffer
* \param str The string to assign in the new buffer
* \warning Don't use it for binary data !
*/
DataBuffer& operator>>(std::string& str) {
std::size_t stringSize =
strlen(reinterpret_cast<const char*>(m_Buffer.data()) + m_ReadOffset) + 1; // including null character
str.resize(stringSize);
std::copy(m_Buffer.begin() + static_cast<difference_type>(m_ReadOffset),
m_Buffer.begin() + static_cast<difference_type>(m_ReadOffset + stringSize), str.begin());
m_ReadOffset += stringSize;
return *this;
}
/**
* \brief Write some data to the buffer
* \param buffer The buffer to write
* \param amount The amount of data to write
*/
void WriteSome(const char* buffer, std::size_t amount) {
std::size_t end_pos = m_Buffer.size();
m_Buffer.resize(m_Buffer.size() + amount);
std::memcpy(m_Buffer.data() + end_pos, buffer, amount);
}
/**
* \brief Write some data to the buffer
* \param buffer The buffer to write
* \param amount The amount of data to write
*/
void WriteSome(const std::uint8_t* buffer, std::size_t amount) {
std::size_t end_pos = m_Buffer.size();
m_Buffer.resize(m_Buffer.size() + amount);
std::memcpy(m_Buffer.data() + end_pos, buffer, amount);
}
/**
* \brief Read some data from the buffer
* \param buffer The buffer to Read
* \param amount The amount of data from the buffer
*/
void ReadSome(char* buffer, std::size_t amount) {
assert(m_ReadOffset + amount <= GetSize());
std::copy_n(m_Buffer.begin() + static_cast<difference_type>(m_ReadOffset), amount, buffer);
m_ReadOffset += amount;
}
/**
* \brief Read some data from the buffer
* \param buffer The buffer to Read
* \param amount The amount of data from the buffer
*/
void ReadSome(std::uint8_t* buffer, std::size_t amount) {
assert(m_ReadOffset + amount <= GetSize());
std::copy_n(m_Buffer.begin() + static_cast<difference_type>(m_ReadOffset), amount, buffer);
m_ReadOffset += amount;
}
/**
* \brief Read some data from the buffer
* \param buffer The buffer to Read
* \param amount The amount of data from the buffer
*/
void ReadSome(DataBuffer& buffer, std::size_t amount) {
assert(m_ReadOffset + amount <= GetSize());
buffer.Resize(amount);
std::copy_n(m_Buffer.begin() + static_cast<difference_type>(m_ReadOffset), amount, buffer.begin());
m_ReadOffset += amount;
}
/**
* \brief Resize the buffer
* \param size The new size of the buffer
*/
void Resize(std::size_t size) {
m_Buffer.resize(size);
}
/**
* \brief Reserve some space in the buffer
* \param amount The amount of space to reserve
*/
void Reserve(std::size_t amount) {
m_Buffer.reserve(amount);
}
/**
* \brief Clear the buffer
*/
void Clear() {
m_Buffer.clear();
m_ReadOffset = 0;
}
/**
* \brief When the buffer has been read entirely
*/
bool IsFinished() const {
return m_ReadOffset >= m_Buffer.size();
}
/**
* \brief Get the buffer data
*/
std::uint8_t* data() {
return m_Buffer.data();
}
/**
* \brief Get the buffer data
*/
const std::uint8_t* data() const {
return m_Buffer.data();
}
/**
* \brief Get the read offset
*/
std::size_t GetReadOffset() const {
return m_ReadOffset;
}
/**
* \brief Set the read offset
* \param pos The new read offset
*/
void SetReadOffset(std::size_t pos);
/**
* \brief Get the size of the buffer
*/
std::size_t GetSize() const;
/**
* \brief Get the remaining size of the buffer
*/
std::size_t GetRemaining() const;
/**
* \brief Read a file into the buffer
* \param fileName The name of the file to read
*/
bool ReadFile(const std::string& fileName);
/**
* \brief Write a file into the buffer
* \param fileName The name of the file to write to
*/
bool WriteFile(const std::string& fileName) const;
/**
* \brief Allocate the buffer on the heap
* \warning Don't forget to free the data !
*/
std::uint8_t* HeapAllocatedData() const {
std::uint8_t* newBuffer = new std::uint8_t[GetSize()];
std::memcpy(newBuffer, data(), GetSize());
return newBuffer;
}
/**
* \brief Operator == to compare two DataBuffer
*/
bool operator==(const DataBuffer& other) const {
return m_Buffer == other.m_Buffer;
}
iterator begin();
iterator end();
const_iterator begin() const;
const_iterator end() const;
};
/**
* \brief Operator << to write a DataBuffer to an ostream
*/
std::ostream& operator<<(std::ostream& os, const DataBuffer& buffer);

48
src/Common/VarInt.cpp Normal file
View File

@@ -0,0 +1,48 @@
#include "VarInt.h"
#include "DataBuffer.h"
#include <stdexcept>
static constexpr int SEGMENT_BITS = 0x7F;
static constexpr int CONTINUE_BIT = 0x80;
std::size_t VarInt::GetSerializedLength() const {
DataBuffer buffer;
buffer << *this;
return buffer.GetSize();
}
DataBuffer& operator<<(DataBuffer& out, const VarInt& var) {
std::uint64_t value = var.m_Value;
while (true) {
if ((value & ~static_cast<std::uint64_t>(SEGMENT_BITS)) == 0) {
out << static_cast<std::uint8_t>(value);
return out;
}
out << static_cast<std::uint8_t>((value & SEGMENT_BITS) | CONTINUE_BIT);
value >>= 7;
}
}
DataBuffer& operator>>(DataBuffer& in, VarInt& var) {
var.m_Value = 0;
unsigned int position = 0;
std::uint8_t currentByte;
while (true) {
in.ReadSome(&currentByte, 1);
var.m_Value |= static_cast<std::uint64_t>(currentByte & SEGMENT_BITS) << position;
if ((currentByte & CONTINUE_BIT) == 0)
break;
position += 7;
if (position >= 8 * sizeof(var.m_Value))
throw std::runtime_error("VarInt is too big");
}
return in;
}

54
src/Common/VarInt.h Normal file
View File

@@ -0,0 +1,54 @@
#pragma once
/**
* \file VarInt.h
* \brief File containing the blitz::VarInt class
*/
#include <cstddef>
#include <cstdint>
class DataBuffer;
/**
* \class VarInt
* \brief Variable-length format such that smaller numbers use fewer bytes.
*/
class VarInt {
private:
std::uint64_t m_Value;
public:
VarInt() : m_Value(0) {}
/**
* \brief Construct a variable integer from a value
* \param value The value of the variable integer
*/
VarInt(std::uint64_t value) : m_Value(value) {}
/**
* \brief Get the value of the variable integer
*/
std::uint64_t GetValue() const {
return m_Value;
}
/**
* \brief Get the length of the serialized variable integer
*/
std::size_t GetSerializedLength() const;
/**
* \brief Serialize the variable integer
* \param out The buffer to write the serialized variable integer to
* \param var The variable integer to serialize
*/
friend DataBuffer& operator<<(DataBuffer& out, const VarInt& var);
/**
* \brief Deserialize the variable integer
* \param in The buffer to read the serialized variable integer from
* \param var The variable integer to deserialize
*/
friend DataBuffer& operator>>(DataBuffer& in, VarInt& var);
};

View File

@@ -4,10 +4,9 @@
/** /**
* The list of actions that can be taken by the player * The list of in-game actions that can be taken by the player
*/ */
enum Action { enum Action {
QUIT,
PAUSE, PAUSE,
RETRY, RETRY,
HOLD, HOLD,
@@ -22,8 +21,20 @@ enum Action {
}; };
static const std::string ACTION_NAMES[] = { // name for each action static const Action ACTION_LIST_IN_ORDER[] = { // the list of possible actions in a sorted order
"Quit", MOVE_LEFT,
MOVE_RIGHT,
SOFT_DROP,
HARD_DROP,
ROTATE_CW,
ROTATE_CCW,
ROTATE_180,
ROTATE_0,
HOLD,
PAUSE,
RETRY
};
static const std::string ACTION_NAMES[] = { // name representation for each actions
"Pause", "Pause",
"Retry", "Retry",
"Hold", "Hold",
@@ -31,9 +42,9 @@ static const std::string ACTION_NAMES[] = { // name for each action
"Hard drop", "Hard drop",
"Move left", "Move left",
"Move right", "Move right",
"Rotate 0°", "Rotate 0",
"Rotate CW", "Rotate CW",
"Rotate 180°", "Rotate 180",
"Rotate CCW" "Rotate CCW"
}; };

View File

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

View File

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

View File

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

@@ -9,8 +9,8 @@
#include <memory> #include <memory>
static const int SUBPX_PER_ROW = 60; // the number of position the active piece can take "between" two rows static const int SUBPX_PER_ROW = 60; // the number of position the active piece can take "between" two rows
static const int SOFT_DROP_SCORE = 1; // the score gained by line soft dropped static const int LOCK_DELAY_SCORE = 1; // the score gained when the piece locks due to lock delay
static const int HARD_DROP_SCORE = 2; // the score gained by line hard dropped static const int HARD_DROP_SCORE = 10; // the score gained when the piece locks due to an hard drop
static const int LINE_CLEAR_BASE_SCORE = 100; // the score value of clearing a single line static const int LINE_CLEAR_BASE_SCORE = 100; // the score value of clearing a single line
static const int B2B_SCORE_MULTIPLIER = 2; // by how much havaing B2B on multiplies the score of the line clear static const int B2B_SCORE_MULTIPLIER = 2; // by how much havaing B2B on multiplies the score of the line clear
static const int B2B_MIN_LINE_NUMBER = 4; // the minimum number of lines needed to be cleared at once to gain B2B (without a spin) static const int B2B_MIN_LINE_NUMBER = 4; // the minimum number of lines needed to be cleared at once to gain B2B (without a spin)
@@ -55,6 +55,8 @@ void Game::initialize() {
void Game::nextFrame(const std::set<Action>& playerActions) { void Game::nextFrame(const std::set<Action>& playerActions) {
if (this->lost || this->hasWon()) return; if (this->lost || this->hasWon()) return;
bool pieceJustLocked = false;
if (this->started) { if (this->started) {
bool AREJustEnded = (this->leftARETime == 1); bool AREJustEnded = (this->leftARETime == 1);
if (this->leftARETime > 0) { if (this->leftARETime > 0) {
@@ -65,36 +67,39 @@ void Game::nextFrame(const std::set<Action>& playerActions) {
if (AREJustEnded) { if (AREJustEnded) {
this->lost = this->board.spawnNextPiece(); this->lost = this->board.spawnNextPiece();
this->resetPiece(true); this->resetPiece(true);
}
/* IRS and IHS */ /* IRS and IHS */
Rotation initialRotation = NONE bool initialRotated = (this->initialActions.contains(ROTATE_0) || this->initialActions.contains(ROTATE_CW)
+ ((this->initialActions.contains(ROTATE_CW)) ? CLOCKWISE : NONE) || this->initialActions.contains(ROTATE_180) || this->initialActions.contains(ROTATE_CCW));
+ ((this->initialActions.contains(ROTATE_180)) ? DOUBLE : NONE) Rotation initialRotation = NONE
+ ((this->initialActions.contains(ROTATE_CCW)) ? COUNTERCLOCKWISE : 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)) { if (this->initialActions.contains(HOLD)) {
this->lost = (!this->board.hold(initialRotation)); this->board.hold(initialRotation);
}
else {
if ((initialRotation != NONE) || this->initialActions.contains(ROTATE_0)) {
this->lost = (!this->board.rotate(initialRotation));
} }
} else {
if (initialRotated) {
if (this->lost) { this->board.rotate(initialRotation);
if (initialRotation == NONE) { }
this->lost = (!this->board.rotate(initialRotation)); }
this->lost = this->board.activePieceInWall();
if (this->lost) {
this->board.rotate(NONE);
this->lost = this->board.activePieceInWall();
if (this->lost) {
this->framesPassed++;
return;
}
} }
}
if (this->lost) {
this->framesPassed++;
return;
} }
/* HOLD */ /* HOLD */
if (playerActions.contains(HOLD) && (!this->heldActions.contains(HOLD))) { if (playerActions.contains(HOLD) && (!this->heldActions.contains(HOLD))) {
if (this->board.hold()) { if (this->board.hold({})) {
this->resetPiece(false); this->resetPiece(false);
} }
} }
@@ -137,16 +142,14 @@ void Game::nextFrame(const std::set<Action>& playerActions) {
// SDR=0 -> instant drop // SDR=0 -> instant drop
if (appliedSDR == 0) { if (appliedSDR == 0) {
while (this->board.moveDown()) { while (this->board.moveDown());
this->score += SOFT_DROP_SCORE;
}
} }
// SDR>1 -> move down by specified amount // SDR>1 -> move down by specified amount
else { else {
this->subVerticalPosition += (SUBPX_PER_ROW / appliedSDR); this->subVerticalPosition += (SUBPX_PER_ROW / appliedSDR);
while (this->subVerticalPosition >= SUBPX_PER_ROW) { while (this->subVerticalPosition >= SUBPX_PER_ROW) {
this->board.moveDown();
this->subVerticalPosition -= SUBPX_PER_ROW; this->subVerticalPosition -= SUBPX_PER_ROW;
this->score += (this->board.moveDown() * SOFT_DROP_SCORE);
} }
} }
} }
@@ -154,10 +157,10 @@ void Game::nextFrame(const std::set<Action>& playerActions) {
/* HARD DROP */ /* HARD DROP */
// needs to be done last because we can enter ARE period afterwards // needs to be done last because we can enter ARE period afterwards
if (this->initialActions.contains(HARD_DROP) || (playerActions.contains(HARD_DROP) && (!this->heldActions.contains(HARD_DROP)))) { if (this->initialActions.contains(HARD_DROP) || (playerActions.contains(HARD_DROP) && (!this->heldActions.contains(HARD_DROP)))) {
while (this->board.moveDown()) { while (this->board.moveDown());
this->score += HARD_DROP_SCORE;
}
this->lockPiece(); this->lockPiece();
pieceJustLocked = true;
this->score += HARD_DROP_SCORE;
} }
// no need to apply gravity and lock delay if the piece was hard dropped // no need to apply gravity and lock delay if the piece was hard dropped
else { else {
@@ -187,6 +190,8 @@ void Game::nextFrame(const std::set<Action>& playerActions) {
if ((this->totalLockDelay > this->parameters.getLockDelay()) || (this->totalForcedLockDelay > this->parameters.getForcedLockDelay())) { if ((this->totalLockDelay > this->parameters.getLockDelay()) || (this->totalForcedLockDelay > this->parameters.getForcedLockDelay())) {
this->lockPiece(); this->lockPiece();
pieceJustLocked = true;
this->score += LOCK_DELAY_SCORE;
} }
} }
@@ -201,11 +206,11 @@ void Game::nextFrame(const std::set<Action>& playerActions) {
} }
} }
this->heldActions = playerActions;
if ((!this->started) || this->leftARETime > 0) { if ((!this->started) || this->leftARETime > 0) {
for (Action action : playerActions) { for (Action action : playerActions) {
this->initialActions.insert(action); if ((!pieceJustLocked) && (!heldActions.contains(action))) {
this->initialActions.insert(action);
}
} }
if (this->heldDAS >= 0) { if (this->heldDAS >= 0) {
@@ -219,6 +224,8 @@ void Game::nextFrame(const std::set<Action>& playerActions) {
else this->heldDAS = 0; else this->heldDAS = 0;
} }
} }
this->heldActions = playerActions;
} }
void Game::resetPiece(bool newPiece) { void Game::resetPiece(bool newPiece) {
@@ -283,25 +290,34 @@ void Game::rotatePiece(Rotation rotation) {
void Game::lockPiece() { void Game::lockPiece() {
LineClear clear = this->board.lockPiece(); LineClear clear = this->board.lockPiece();
this->parameters.clearLines(clear.lines); this->parameters.lockedPiece(clear);
bool B2BConditionsAreMet = ((clear.lines > B2B_MIN_LINE_NUMBER) || clear.isSpin || clear.isMiniSpin);
if (clear.lines > 0) { if (clear.lines > 0) {
bool B2BConditionsAreMet = ((clear.lines >= B2B_MIN_LINE_NUMBER) || clear.isSpin || clear.isMiniSpin);
/* clearing one more line is worth 2x more /* clearing one more line is worth 2x more
clearing with a spin is worth as much as clearing 2x more lines */ clearing with a spin is worth as much as clearing 2x more lines */
long int clearScore = LINE_CLEAR_BASE_SCORE; long int clearScore = LINE_CLEAR_BASE_SCORE;
clearScore = clearScore << (clear.lines << (clear.isSpin)); clearScore = clearScore << (clear.lines << (clear.isSpin));
if (this->B2BChain && B2BConditionsAreMet) clearScore *= B2B_SCORE_MULTIPLIER; if (this->B2BChain && B2BConditionsAreMet) {
clearScore *= B2B_SCORE_MULTIPLIER;
}
this->score += clearScore; this->score += clearScore;
this->B2BChain = B2BConditionsAreMet;
} }
this->B2BChain = B2BConditionsAreMet;
if (!this->hasWon()) { if (!this->hasWon()) {
this->leftARETime = this->parameters.getARE(); this->leftARETime = this->parameters.getARE();
if (this->leftARETime == 0) { if (this->leftARETime == 0) {
this->lost = this->board.spawnNextPiece(); this->lost = this->board.spawnNextPiece();
this->resetPiece(true); this->resetPiece(true);
if (this->lost) {
this->board.rotate(NONE);
this->lost = this->board.activePieceInWall();
}
} }
} }
} }
@@ -318,6 +334,10 @@ int Game::getClearedLines() const {
return this->parameters.getClearedLines(); return this->parameters.getClearedLines();
} }
int Game::getGrade() const {
return this->parameters.getGrade();
}
int Game::getLevel() const { int Game::getLevel() const {
return this->parameters.getLevel(); return this->parameters.getLevel();
} }
@@ -334,12 +354,20 @@ bool Game::isOnB2BChain() const {
return this->B2BChain; return this->B2BChain;
} }
float Game::getLockDelayProgression() const {
return (float) this->totalLockDelay / this->parameters.getLockDelay();
}
float Game::getForcedLockDelayProgression() const {
return (float) this->totalForcedLockDelay / this->parameters.getForcedLockDelay();
}
bool Game::areBlocksBones() const { bool Game::areBlocksBones() const {
return this->parameters.getBoneBlocks(); return this->parameters.getBoneBlocks();
} }
Position Game::ghostPiecePosition() const { bool Game::isBoardInvisible() const {
return this->board.lowestPosition(); return this->parameters.getInvisibleBoard();
} }
const Board& Game::getBoard() const { const Board& Game::getBoard() const {
@@ -354,6 +382,10 @@ const Position& Game::getActivePiecePosition() const {
return this->board.getActivePiecePosition(); return this->board.getActivePiecePosition();
} }
Position Game::getGhostPiecePosition() const {
return this->board.lowestPosition();
}
const std::shared_ptr<Piece>& Game::getHeldPiece() const { const std::shared_ptr<Piece>& Game::getHeldPiece() const {
return this->board.getHeldPiece(); return this->board.getHeldPiece();
} }

View File

@@ -101,6 +101,11 @@ class Game {
*/ */
int getClearedLines() const; int getClearedLines() const;
/**
* @return The current grade
*/
int getGrade() const;
/** /**
* @return The number of frames passed since the start of the game * @return The number of frames passed since the start of the game
*/ */
@@ -116,15 +121,25 @@ class Game {
*/ */
bool isOnB2BChain() const; bool isOnB2BChain() const;
/**
* @return How close the active piece's lock delay is to the maximum allowed, betwwen 0 and 1
*/
float getLockDelayProgression() const;
/**
* @return How close the active piece's forced lock delay is to the maximum allowed, betwwen 0 and 1
*/
float getForcedLockDelayProgression() const;
/** /**
* @return If all blocks are currently bone blocks * @return If all blocks are currently bone blocks
*/ */
bool areBlocksBones() const; bool areBlocksBones() const;
/** /**
* @return The position of the ghost piece * @return If the board is currently invisible
*/ */
Position ghostPiecePosition() const; bool isBoardInvisible() const;
/** /**
* @return The board * @return The board
@@ -141,6 +156,11 @@ class Game {
*/ */
const Position& getActivePiecePosition() const; const Position& getActivePiecePosition() const;
/**
* @return The position of the ghost piece
*/
Position getGhostPiecePosition() const;
/** /**
* @return A pointer to the held piece, can be null * @return A pointer to the held piece, can be null
*/ */

View File

@@ -10,6 +10,8 @@
#include <memory> #include <memory>
#include <utility> #include <utility>
#include <cstdlib> #include <cstdlib>
#include <iostream>
#include <optional>
GameBoard::GameBoard(int boardWidth, int boardHeight, const std::shared_ptr<PiecesList>& piecesList, int nextQueueLength) : GameBoard::GameBoard(int boardWidth, int boardHeight, const std::shared_ptr<PiecesList>& piecesList, int nextQueueLength) :
@@ -36,9 +38,12 @@ void GameBoard::initialize() {
this->activePiece = nullptr; this->activePiece = nullptr;
this->heldPiece = nullptr; this->heldPiece = nullptr;
this->isLastMoveKick = false; this->isLastMoveKick = false;
this->movedLeftLast = false;
} }
bool GameBoard::moveLeft() { bool GameBoard::moveLeft() {
this->movedLeftLast = true;
if (this->activePieceInWall(Position{-1, 0})) { if (this->activePieceInWall(Position{-1, 0})) {
return false; return false;
} }
@@ -50,6 +55,8 @@ bool GameBoard::moveLeft() {
} }
bool GameBoard::moveRight() { bool GameBoard::moveRight() {
this->movedLeftLast = false;
if (this->activePieceInWall(Position{1, 0})) { if (this->activePieceInWall(Position{1, 0})) {
return false; return false;
} }
@@ -137,30 +144,21 @@ bool GameBoard::tryKicking(bool testingBottom, const std::set<Position>& safePos
bool overlapsRight = true; bool overlapsRight = true;
int i = (j == 0) ? 1 : 0; int i = (j == 0) ? 1 : 0;
do { do {
// check right before left arbitrarly, we don't decide this with rotations since it would still be arbitrary with 180° rotations // we first check the side to which the player moved last
if (overlapsRight) { if (movedLeftLast) {
Position shift{+i, j}; if (overlapsLeft) {
if (!this->activePieceOverlaps(safePositions, shift)) { if (this->tryFittingKickedPiece(safePositions, Position({-i, j}), overlapsLeft)) return true;
overlapsLeft = false;
} }
else { if (overlapsRight) {
if (!this->activePieceInWall(shift)) { if (this->tryFittingKickedPiece(safePositions, Position({+i, j}), overlapsRight)) return true;
this->activePiecePosition += shift;
return true;
}
} }
} }
else {
if (overlapsLeft) { if (overlapsRight) {
Position shift{-i, j}; if (this->tryFittingKickedPiece(safePositions, Position({+i, j}), overlapsRight)) return true;
if (!this->activePieceOverlaps(safePositions, shift)) {
overlapsLeft = false;
} }
else { if (overlapsLeft) {
if (!this->activePieceInWall(shift)) { if (this->tryFittingKickedPiece(safePositions, Position({-i, j}), overlapsLeft)) return true;
this->activePiecePosition += shift;
return true;
}
} }
} }
@@ -177,12 +175,32 @@ bool GameBoard::tryKicking(bool testingBottom, const std::set<Position>& safePos
return false; return false;
} }
bool GameBoard::hold(Rotation initialRotation) { bool GameBoard::tryFittingKickedPiece(const std::set<Position>& safePositions, const Position& shift, bool& overlaps) {
if (!this->activePieceOverlaps(safePositions, shift)) {
overlaps = false;
}
else {
if (!this->activePieceInWall(shift)) {
this->activePiecePosition += shift;
return true;
}
}
return false;
}
bool GameBoard::activePieceOverlaps(const std::set<Position>& safePositions, const Position& shift) const {
for (Position position : this->activePiece->getPositions()) {
if (safePositions.contains(position + this->activePiecePosition + shift)) return true;
}
return false;
}
bool GameBoard::hold(std::optional<Rotation> initialRotation) {
Position storedPosition = this->activePiecePosition;
std::swap(this->activePiece, this->heldPiece); std::swap(this->activePiece, this->heldPiece);
bool isFirstTimeHolding = (this->activePiece == nullptr); bool isFirstTimeHolding = (this->activePiece == nullptr);
if (isFirstTimeHolding) { if (isFirstTimeHolding) {
// try with the next piece in queue since there is no piece in the hold box yet
if (this->nextQueueLength == 0) { if (this->nextQueueLength == 0) {
this->activePiece = std::make_shared<Piece>(this->generator.lookNext()); this->activePiece = std::make_shared<Piece>(this->generator.lookNext());
} }
@@ -191,21 +209,25 @@ bool GameBoard::hold(Rotation initialRotation) {
} }
} }
Piece stored = *this->activePiece; // try initial rotation
Position storedPosition = this->activePiecePosition;
this->goToSpawnPosition(); this->goToSpawnPosition();
this->rotate(initialRotation); if (initialRotation.has_value()) {
std::shared_ptr<Piece> storedPiece(this->activePiece);
this->rotate(initialRotation.value());
// if the piece can't spawn, abort initial rotation if (this->activePieceInWall()) {
this->activePiece = storedPiece;
}
}
// if the piece can't spawn, try 0° rotation
if (this->activePieceInWall()) { if (this->activePieceInWall()) {
this->activePiece = std::make_shared<Piece>(stored); std::shared_ptr<Piece> storedPiece(this->activePiece);
this->goToSpawnPosition(); this->rotate(NONE);
// if the piece still can't spawn, abort holding // if the piece still can't spawn, abort holding
if (this->activePieceInWall()) { if (this->activePieceInWall()) {
if (isFirstTimeHolding) { this->activePiece = (isFirstTimeHolding) ? nullptr : storedPiece;
this->activePiece = nullptr;
}
std::swap(this->activePiece, this->heldPiece); std::swap(this->activePiece, this->heldPiece);
this->activePiecePosition = storedPosition; this->activePiecePosition = storedPosition;
return false; return false;
@@ -213,7 +235,6 @@ bool GameBoard::hold(Rotation initialRotation) {
} }
if (isFirstTimeHolding) { if (isFirstTimeHolding) {
// confirm we keep the piece we tried with
this->nextQueue.push_back(this->generator.getNext()); this->nextQueue.push_back(this->generator.getNext());
this->nextQueue.erase(this->nextQueue.begin()); this->nextQueue.erase(this->nextQueue.begin());
} }
@@ -236,6 +257,13 @@ bool GameBoard::spawnNextPiece() {
return this->activePieceInWall(); return this->activePieceInWall();
} }
bool GameBoard::activePieceInWall(const Position& shift) const {
for (Position position : this->activePiece->getPositions()) {
if (this->board.getBlock(position + this->activePiecePosition + shift) != NOTHING) return true;
}
return false;
}
bool GameBoard::touchesGround() const { bool GameBoard::touchesGround() const {
return this->activePieceInWall(Position{0, -1}); return this->activePieceInWall(Position{0, -1});
} }
@@ -257,6 +285,8 @@ LineClear GameBoard::lockPiece() {
this->board.changeBlock(position + this->activePiecePosition, this->activePiece->getBlockType()); this->board.changeBlock(position + this->activePiecePosition, this->activePiece->getBlockType());
} }
this->activePiece = nullptr;
return LineClear{this->board.clearRows(), isLockedInPlace, (!isLockedInPlace) && this->isLastMoveKick}; return LineClear{this->board.clearRows(), isLockedInPlace, (!isLockedInPlace) && this->isLastMoveKick};
} }
@@ -291,20 +321,6 @@ const std::vector<Piece>& GameBoard::getNextPieces() const {
return this->nextQueue; return this->nextQueue;
} }
bool GameBoard::activePieceInWall(const Position& shift) const {
for (Position position : this->activePiece->getPositions()) {
if (this->board.getBlock(position + this->activePiecePosition + shift) != NOTHING) return true;
}
return false;
}
bool GameBoard::activePieceOverlaps(const std::set<Position>& safePositions, const Position& shift) const {
for (Position position : this->activePiece->getPositions()) {
if (safePositions.contains(position + this->activePiecePosition + shift)) return true;
}
return false;
}
void GameBoard::goToSpawnPosition() { void GameBoard::goToSpawnPosition() {
int lowestPosition = this->activePiece->getLength() - 1; int lowestPosition = this->activePiece->getLength() - 1;
for (Position position : this->activePiece->getPositions()) { for (Position position : this->activePiece->getPositions()) {

View File

@@ -7,6 +7,7 @@
#include <vector> #include <vector>
#include <memory> #include <memory>
#include <optional>
/** /**
@@ -22,6 +23,7 @@ class GameBoard {
int nextQueueLength; // the number of next pieces seeable at a time int nextQueueLength; // the number of next pieces seeable at a time
std::vector<Piece> nextQueue; // the list of the next pieces to spawn in the board std::vector<Piece> nextQueue; // the list of the next pieces to spawn in the board
bool isLastMoveKick; // wheter the last action the piece did was kicking bool isLastMoveKick; // wheter the last action the piece did was kicking
bool movedLeftLast; // wheter the last sideway movement was a left one
public: public:
/** /**
@@ -72,12 +74,24 @@ class GameBoard {
*/ */
bool tryKicking(bool testingBottom, const std::set<Position>& safePositions); bool tryKicking(bool testingBottom, const std::set<Position>& safePositions);
/**
* Tries fitting the kicked active piece at the specified position
* @return If it suceeded
*/
bool tryFittingKickedPiece(const std::set<Position>& safePositions, const Position& shift, bool& overlaps);
/**
* Check if one of the active piece's positions shifted by a specified position would overlap with a set of positions
* @return If the shifted active piece overlaps with one of the position
*/
bool activePieceOverlaps(const std::set<Position>& safePositions, const Position& shift = Position{0, 0}) const;
public: public:
/** /**
* Tries holding the active piece or swapping it if one was already stocked, while trying to apply an initial rotation to the newly spawned piece * Tries holding the active piece or swapping it if one was already stocked, while trying to apply an initial rotation to the newly spawned piece
* @return If it suceeded * @return If it suceeded
*/ */
bool hold(Rotation initialRotation = NONE); bool hold(std::optional<Rotation> initialRotation);
/** /**
* Spawns the next piece from the queue * Spawns the next piece from the queue
@@ -85,6 +99,12 @@ class GameBoard {
*/ */
bool spawnNextPiece(); bool spawnNextPiece();
/**
* Checks if one of the active piece's positions touches a wall in the board
* @return If the active piece is in a wall
*/
bool activePieceInWall(const Position& shift = Position{0, 0}) const;
/** /**
* Checks is the active piece as a wall directly below one of its position * Checks is the active piece as a wall directly below one of its position
* @return If it touches a ground * @return If it touches a ground
@@ -134,18 +154,6 @@ class GameBoard {
const std::vector<Piece>& getNextPieces() const; const std::vector<Piece>& getNextPieces() const;
private: private:
/**
* Checks if one of the active piece's positions touches a wall in the board
* @return If the active piece spawned in a wall
*/
bool activePieceInWall(const Position& shift = Position{0, 0}) const;
/**
* Check if one of the active piece's positions shifted by a specified position would overlap with a set of positions
* @return If the shifted active piece overlaps with one of the position
*/
bool activePieceOverlaps(const std::set<Position>& safePositions, const Position& shift = Position{0, 0}) const;
/** /**
* Sets the active piece to its spawn position * Sets the active piece to its spawn position
*/ */

View File

@@ -7,12 +7,14 @@
GameParameters::GameParameters(Gamemode gamemode, const Player& controls) : GameParameters::GameParameters(Gamemode gamemode, const Player& controls) :
gamemode(gamemode), gamemode(gamemode),
controls(controls) { controls(controls) {
this->reset(); this->reset();
} }
void GameParameters::reset() { void GameParameters::reset() {
this->clearedLines = 0; this->clearedLines = 0;
this->grade = 0;
switch (this->gamemode) { switch (this->gamemode) {
// lowest gravity // lowest gravity
case SPRINT : {this->level = 1; break;} case SPRINT : {this->level = 1; break;}
@@ -22,29 +24,51 @@ void GameParameters::reset() {
case MARATHON : {this->level = 1; break;} case MARATHON : {this->level = 1; break;}
// goes from level 20 to 39 // goes from level 20 to 39
case MASTER : {this->level = 20; break;} 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; default : this->level = 1;
} }
this->updateStats(); this->updateStats();
} }
void GameParameters::clearLines(int lineNumber) { void GameParameters::lockedPiece(const LineClear& lineClear) {
switch (this->gamemode) { switch (this->gamemode) {
// modes where level increases // modes where level increases with lines
case MARATHON : case MARATHON :
case MASTER : { case MASTER : {
int previousLines = this->clearedLines; int previousLines = this->clearedLines;
this->clearedLines += lineNumber; this->clearedLines += lineClear.lines;
// level increments every 10 lines, stats only changes on level up // level increments every 10 lines, stats only changes on level up
if (previousLines / 10 < this->clearedLines / 10) { if (previousLines / 10 < this->clearedLines / 10) {
this->level = this->clearedLines / 10; this->level += (this->clearedLines / 10 - previousLines / 10);
this->updateStats(); this->updateStats();
} }
break; break;
} }
// other modes // other modes
default : this->clearedLines += lineNumber; 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;
} }
} }
@@ -58,6 +82,10 @@ bool GameParameters::hasWon(int framesPassed) const {
case MARATHON : return this->clearedLines >= 200; case MARATHON : return this->clearedLines >= 200;
// win once 200 lines have been cleared // win once 200 lines have been cleared
case MASTER : return this->clearedLines >= 200; 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; default : return false;
} }
} }
@@ -65,15 +93,17 @@ bool GameParameters::hasWon(int framesPassed) const {
void GameParameters::updateStats() { void GameParameters::updateStats() {
/* NEXT QUEUE */ /* NEXT QUEUE */
switch (this->gamemode) { switch (this->gamemode) {
// 5 for rapidity gamemodes // 5 for fast-controls gamemodes
case SPRINT : case SPRINT :
case ULTRA : { case ULTRA :
case ZEN : {
this->nextQueueLength = 5; this->nextQueueLength = 5;
break; break;
} }
// 3 for endurance gamemodes // 3 for slow-controls gamemodes
case MARATHON : case MARATHON :
case MASTER : { case MASTER :
case INVISIBLE : {
this->nextQueueLength = 3; this->nextQueueLength = 3;
break; break;
} }
@@ -83,10 +113,13 @@ void GameParameters::updateStats() {
/* BONE BLOCKS */ /* BONE BLOCKS */
switch (this->gamemode) { switch (this->gamemode) {
// blocks turns into bone blocks at level 30 // 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; default : this->boneBlocks = false;
} }
/* INVISIBLE */
this->invisibleBoard = (this->gamemode == INVISIBLE);
/* GRAVITY */ /* GRAVITY */
// get gravity for an assumed 20-rows board // get gravity for an assumed 20-rows board
static const int gravityPerLevel[] = { static const int gravityPerLevel[] = {
@@ -126,6 +159,8 @@ void GameParameters::updateStats() {
switch (this->gamemode) { switch (this->gamemode) {
// starts at 500ms (30f) at lvl 20 and ends at 183ms (11f) at lvl 39 // starts at 500ms (30f) at lvl 20 and ends at 183ms (11f) at lvl 39
case MASTER : {this->lockDelay = 30 - (this->level - 20); break;} case MASTER : {this->lockDelay = 30 - (this->level - 20); break;}
// 10s
case ZEN : {this->lockDelay = 60 * 10; break;}
// 1s by default // 1s by default
default : this->lockDelay = 60; default : this->lockDelay = 60;
} }
@@ -139,6 +174,8 @@ void GameParameters::updateStats() {
case MARATHON : {this->ARE = 24 - (this->level - 1); break;} case MARATHON : {this->ARE = 24 - (this->level - 1); break;}
// starts at 400ms (24f) at lvl 20 and ends at 083ms (5f) at lvl 39 // starts at 400ms (24f) at lvl 20 and ends at 083ms (5f) at lvl 39
case MASTER : {this->ARE = 24 - (this->level - 20); break;} case MASTER : {this->ARE = 24 - (this->level - 20); break;}
// fixed at 250ms (15f)
case INVISIBLE : {this->ARE = 15; break;}
// no ARE by default // no ARE by default
default : this->ARE = 0; default : this->ARE = 0;
} }
@@ -203,6 +240,10 @@ int GameParameters::getLevel() const {
return this->level; return this->level;
} }
int GameParameters::getGrade() const {
return this->grade;
}
int GameParameters::getNextQueueLength() const { int GameParameters::getNextQueueLength() const {
return this->nextQueueLength; return this->nextQueueLength;
} }
@@ -211,6 +252,10 @@ bool GameParameters::getBoneBlocks() const {
return this->boneBlocks; return this->boneBlocks;
} }
bool GameParameters::getInvisibleBoard() const {
return this->invisibleBoard;
}
int GameParameters::getGravity() const { int GameParameters::getGravity() const {
return this->gravity; return this->gravity;
} }

View File

@@ -2,6 +2,7 @@
#include "Gamemode.h" #include "Gamemode.h"
#include "Player.h" #include "Player.h"
#include "LineClear.h"
/** /**
@@ -14,8 +15,10 @@ class GameParameters {
Player controls; // the player's controls Player controls; // the player's controls
int clearedLines; // the number of cleared lines int clearedLines; // the number of cleared lines
int level; // the current level int level; // the current level
int grade; // the current amount of points
int nextQueueLength; // the number of pieces visibles in the next queue int nextQueueLength; // the number of pieces visibles in the next queue
bool boneBlocks; // wheter all blocks are bone blocks bool boneBlocks; // wheter all blocks are bone blocks
bool invisibleBoard; // wheter the board is invisible
int gravity; // the gravity at which pieces drop int gravity; // the gravity at which pieces drop
int lockDelay; // the time before the piece lock in place int lockDelay; // the time before the piece lock in place
int forcedLockDelay; // the forced time before the piece lock in place int forcedLockDelay; // the forced time before the piece lock in place
@@ -39,7 +42,7 @@ class GameParameters {
/** /**
* Counts the newly cleared lines and update level and stats if needed * Counts the newly cleared lines and update level and stats if needed
*/ */
void clearLines(int lineNumber); void lockedPiece(const LineClear& lineClear);
/** /**
* Checks if the game ended based on the current states and time passed, accorind to the gamemode * Checks if the game ended based on the current states and time passed, accorind to the gamemode
@@ -63,6 +66,11 @@ class GameParameters {
* @return The current level * @return The current level
*/ */
int getLevel() const; int getLevel() const;
/**
* @return The current grade
*/
int getGrade() const;
/** /**
* @return The length of the next queue * @return The length of the next queue
@@ -70,9 +78,14 @@ class GameParameters {
int getNextQueueLength() const; int getNextQueueLength() const;
/** /**
* Returns wheter the blocks are currently bone blocks * @return Wheter the blocks are currently bone blocks
*/ */
bool getBoneBlocks() const; bool getBoneBlocks() const;
/**
* @return Wheter the board is currently invisible
*/
bool getInvisibleBoard() const;
/** /**
* @return The current gravity for a 20-line high board * @return The current gravity for a 20-line high board

View File

@@ -1,5 +1,7 @@
#pragma once #pragma once
#include <string>
/** /**
* Every gamemode supported by the game * Every gamemode supported by the game
@@ -8,5 +10,40 @@ enum Gamemode {
SPRINT, SPRINT,
MARATHON, MARATHON,
ULTRA, ULTRA,
MASTER MASTER,
INVISIBLE,
ZEN
}; };
/**
* @return A string containing the name of the gamemode
*/
inline std::string getGamemodeName(Gamemode gamemode) {
static const std::string GAMEMODE_NAMES[] = {
"SPRINT",
"MARATHON",
"ULTRA",
"MASTER",
"INVISIBLE",
"ZEN"
};
return GAMEMODE_NAMES[gamemode];
}
/**
* @return A tiny string containing the goal of the gamemode
*/
inline std::string getGamemodeGoal(Gamemode gamemode) {
static const std::string GAMEMODE_DESCRIPTIONS[] = {
"40 lines",
"200 lines",
"2 minutes",
"200 lines",
"1000 grade",
"Infinite"
};
return GAMEMODE_DESCRIPTIONS[gamemode];
}

View File

@@ -6,6 +6,9 @@
#include <memory> #include <memory>
static const int DEFAULT_BOARD_WIDTH = 10; // the default width of the board when starting the menu
static const int DEFAULT_BOARD_HEIGHT = 20; // the default height of the board when starting the menu
Menu::Menu() { Menu::Menu() {
this->piecesList = std::make_shared<PiecesList>(PiecesList()); this->piecesList = std::make_shared<PiecesList>(PiecesList());
@@ -44,6 +47,14 @@ Player& Menu::getPlayerControls() {
return this->playerControls; return this->playerControls;
} }
const Player& Menu::readPlayerControls() const {
return this->playerControls;
}
PiecesList& Menu::getPiecesList() { PiecesList& Menu::getPiecesList() {
return *this->piecesList; return *this->piecesList;
} }
const PiecesList& Menu::readPiecesList() const {
return *this->piecesList;
}

View File

@@ -4,9 +4,7 @@
#include "Player.h" #include "Player.h"
#include "Game.h" #include "Game.h"
static const int FRAMES_PER_SECOND = 60; // the number of frames per second, all the values in the app were choosen with this number in mind static const int FRAMES_PER_SECOND = 60; // the number of frames per second, all the values in the app were choosen with this number in mind
static const int DEFAULT_BOARD_WIDTH = 10; // the default width of the board when starting the menu
static const int DEFAULT_BOARD_HEIGHT = 20; // the default height of the board when starting the menu
/** /**
@@ -52,14 +50,24 @@ class Menu {
* @return The height of the board * @return The height of the board
*/ */
int getBoardHeight() const; int getBoardHeight() const;
/** /**
* @return A reference to the player's controls * @return A reference to the player's controls
*/ */
Player& getPlayerControls(); Player& getPlayerControls();
/**
* @return A reference to the player's controls
*/
const Player& readPlayerControls() const;
/** /**
* @return A reference to the pieces list * @return A reference to the pieces list
*/ */
PiecesList& getPiecesList(); PiecesList& getPiecesList();
/**
* @return A reference to the pieces list
*/
const PiecesList& readPiecesList() const;
}; };

View File

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

View File

@@ -1,6 +1,7 @@
#pragma once #pragma once
#include "../Pieces/Piece.h" #include "../Pieces/Piece.h"
#include "DistributionMode.h"
#include <vector> #include <vector>
#include <utility> #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>> holelessPieces; // the list of holeless loaded pieces by size
std::vector<std::vector<int>> otherPieces; // the list of other loaded pieces by size std::vector<std::vector<int>> otherPieces; // the list of other loaded pieces by size
std::vector<std::pair<int, int>> selectedPieces; // the list of all currently selected pieces std::vector<std::pair<int, int>> selectedPieces; // the list of all currently selected pieces
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: public:
/** /**
@@ -66,6 +70,19 @@ class PiecesList {
*/ */
void unselectAll(); 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 * @return The highest loaded size of pieces
*/ */
@@ -81,10 +98,25 @@ class PiecesList {
*/ */
std::vector<std::pair<int, int>> getSelectedPieces() const; 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 * @return A copy of the piece corresponding to the specified index
*/ */
Piece getPiece(const std::pair<int, int>& pieceIndex) const; Piece getPiece(const std::pair<int, int>& pieceIndex) const;
/**
* @return The piece corresponding to the specified index
*/
const Piece& lookAtPiece(const std::pair<int, int>& pieceIndex) const;
private: private:
/** /**

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

@@ -1,36 +1,41 @@
#pragma once #pragma once
#include "Settings.h" #include "../Settings.h"
#include "../PlayerCursor.h"
#include <stack> #include <stack>
#include <memory> #include <memory>
#include <optional>
#include <SFML/Graphics.hpp> #include <SFML/Graphics.hpp>
class AppMenu;
using MenuStack = std::stack<std::shared_ptr<AppMenu>>;
class AppMenu { class AppMenu {
protected: protected:
std::shared_ptr<std::stack<AppMenu>> menuStack; std::shared_ptr<MenuStack> menuStack;
std::shared_ptr<Settings> settings; std::shared_ptr<Settings> settings;
std::shared_ptr<sf::RenderWindow> renderWindow; std::shared_ptr<sf::RenderWindow> renderWindow;
bool enterPressed = false;
bool enterReleased = false;
bool escPressed = false;
bool escReleased = false;
sf::Font pressStartFont;
public: public:
AppMenu(std::shared_ptr<std::stack<AppMenu>> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow) : AppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow);
menuStack(menuStack),
settings(settings),
renderWindow(renderWindow)
{
}
virtual void computeFrame() = 0; virtual void computeFrame() = 0;
virtual void drawFrame() const = 0; virtual void drawFrame() const = 0;
protected:
void updateMetaBinds();
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 placeTitle(sf::Text& text, const std::optional<PlayerCursor>& playerCursor, const sf::String& string, float yPos, const std::optional<sf::Vector2u>& cursorPos) const;
sf::Color getColorOfBlock(Block block, int luminosityShift) const;
}; };
inline void changeVideoMode(sf::RenderWindow& window, const sf::VideoMode& videoMode) {
window.create(videoMode, "jminos", sf::Style::Close | sf::Style::Titlebar);
sf::Vector2u desktopSize = sf::VideoMode::getDesktopMode().size;
sf::Vector2u windowSize = window.getSize();
window.setPosition(sf::Vector2i((desktopSize.x / 2) - (windowSize.x / 2), (desktopSize.y / 2) - (windowSize.y / 2)));
}

View File

@@ -0,0 +1,64 @@
#include "GameBoardAppMenu.h"
#include "AppMenu.h"
#include "../PlayerCursor.h"
#include <stack>
#include <memory>
#include <SFML/Graphics.hpp>
GameBoardAppMenu::GameBoardAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow) :
AppMenu(menuStack, settings, renderWindow),
playerCursor({1, 1}) {
}
void GameBoardAppMenu::computeFrame() {
this->updateMetaBinds();
this->playerCursor.updatePosition();
Menu& menu = this->settings->getMenu();
switch (this->playerCursor.getPosition().y) {
case 0 : {
if (this->playerCursor.movedLeft()) {
menu.setBoardWidth(std::max(1, menu.getBoardWidth() - 1));
}
if (this->playerCursor.movedRight()) {
menu.setBoardWidth(std::min(MAXIMUM_BOARD_WIDTH, menu.getBoardWidth() + 1));
}
break;
}
case 1 : {
if (this->playerCursor.movedLeft()) {
menu.setBoardHeight(std::max(1, menu.getBoardHeight() - 1));
}
if (this->playerCursor.movedRight()) {
menu.setBoardHeight(std::min(MAXIMUM_BOARD_HEIGHT, menu.getBoardHeight() + 1));
}
break;
}
}
if (this->escReleased) {
this->menuStack->pop();
}
}
void GameBoardAppMenu::drawFrame() const {
this->renderWindow->clear(sf::Color(200, 200, 200));
const Menu& menu = this->settings->getMenu();
sf::Text text(this->pressStartFont, "", this->settings->getWindowSizeMultiplier() * 2);
text.setFillColor(sf::Color(0, 0, 0));
text.setOutlineColor(sf::Color(255, 255, 255));
this->placeTitle(text, {}, "BOARD SETTINGS", 5.f, {});
this->placeText(text, this->playerCursor, "< BOARD WIDTH: " + std::to_string(menu.getBoardWidth()) + " >", 5.f, 15.f, sf::Vector2u{0, 0});
this->placeText(text, this->playerCursor, "< BOARD HEIGHT: " + std::to_string(menu.getBoardHeight()) + " >", 5.f, 25.f, sf::Vector2u{0, 1});
this->renderWindow->display();
}

View File

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

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

@@ -0,0 +1,293 @@
#include "GamePlayingAppMenu.h"
#include "AppMenu.h"
#include <stack>
#include <memory>
#include <algorithm>
#include <cmath>
#include <string>
#include <SFML/Graphics.hpp>
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 = this->settings->getStartTimerLength() * FRAMES_PER_SECOND;
if (this->startTimer == 0) {
this->game.start();
}
this->paused = false;
this->pausePressed = false;
this->retryPressed = false;
int maxWidthMultiplier = (this->settings->getWindowSizeMultiplier() * 40) / (this->game.getBoard().getWidth());
int maxHeightMultiplier = (this->settings->getWindowSizeMultiplier() * 50) / (this->game.getBoard().getBaseHeight() + 10);
this->cellSizeZoom = std::min(maxWidthMultiplier, maxHeightMultiplier);
float boardWidth = this->game.getBoard().getWidth() * this->cellSizeZoom;
float boardHeight = (this->game.getBoard().getBaseHeight() + 10) * this->cellSizeZoom;
this->boardPosition = sf::FloatRect(sf::Vector2f((this->settings->getWindowSizeMultiplier() * 40) - (boardWidth / 2),
(this->settings->getWindowSizeMultiplier() * 25) - (boardHeight / 2)),
sf::Vector2f(boardWidth, boardHeight));
for (int i = 0; i < 5; i++) {
this->nextQueuePosition[i] = sf::FloatRect(sf::Vector2f(this->boardPosition.position.x + boardWidth + (5.f * this->settings->getWindowSizeMultiplier()), (10.f + (10.f * i)) * this->settings->getWindowSizeMultiplier()),
sf::Vector2f(8.f * this->settings->getWindowSizeMultiplier(), 8.f * this->settings->getWindowSizeMultiplier()));
}
this->nextCellSizeZoom = this->nextQueuePosition[0].size.y;
for (const auto& piece : this->settings->getMenu().getPiecesList().getSelectedPieces()) {
float nextPieceCellSizeZoom = ((int) this->nextQueuePosition[0].size.y) / this->settings->getMenu().getPiecesList().lookAtPiece(piece).getLength();
this->nextCellSizeZoom = std::min(this->nextCellSizeZoom, nextPieceCellSizeZoom);
}
this->holdBoxPosition = sf::FloatRect(sf::Vector2f(this->boardPosition.position.x - ((8.f + 5.f) * this->settings->getWindowSizeMultiplier()), (10.f) * this->settings->getWindowSizeMultiplier()),
sf::Vector2f(8.f * this->settings->getWindowSizeMultiplier(), 8.f * this->settings->getWindowSizeMultiplier()));
this->holdCellSizeZoom = this->nextCellSizeZoom;
}
void GamePlayingAppMenu::computeFrame() {
this->updateMetaBinds();
if (this->startTimer > 0) {
this->startTimer--;
if (this->startTimer == 0) {
this->game.start();
}
}
if (this->escReleased) {
this->menuStack->pop();
}
else {
std::set<Action> actions;
for (Action action : ACTION_LIST_IN_ORDER) {
for (sfKey key : this->settings->getKeybinds().getKeybinds(action)) {
if (sf::Keyboard::isKeyPressed(key)) {
actions.insert(action);
}
}
}
if (actions.contains(RETRY)) {
this->retryPressed = true;
}
else {
if (this->retryPressed) {
this->game.reset();
this->startTimer = this->settings->getStartTimerLength() * FRAMES_PER_SECOND;
if (this->startTimer == 0) {
this->game.start();
}
}
this->retryPressed = false;
}
if (actions.contains(PAUSE)) {
this->pausePressed = true;
}
else {
if (this->pausePressed) {
this->paused = (!this->paused);
}
this->pausePressed = false;
}
if (!paused) {
this->game.nextFrame(actions);
}
}
}
void GamePlayingAppMenu::drawFrame() const {
this->renderWindow->clear(sf::Color(200, 200, 200));
sf::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((areBlockBones && block != NOTHING)
? bonesBlockColor
: this->getColorOfBlock(block, (block == NOTHING) ? 0 : -30));
cell.setPosition(this->getBoardBlockPosition(x, y));
this->renderWindow->draw(cell);
}
}
if (drawActivePiece) {
// ghost piece
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);
sf::RectangleShape cell(cellSize);
cell.setFillColor(ghostColor);
cell.setPosition(this->getBoardBlockPosition(cellPosition.x, cellPosition.y));
this->renderWindow->draw(cell);
}
// active piece outline
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(cellOutlineThickness);
cell.setOutlineColor(pieceOultlineColor);
cell.setPosition(this->getBoardBlockPosition(cellPosition.x, cellPosition.y));
this->renderWindow->draw(cell);
}
}
// top out line
sf::RectangleShape topOutLine(sf::Vector2f(this->cellSizeZoom * this->game.getBoard().getWidth(), std::roundf(this->cellSizeZoom / 4)));
topOutLine.setPosition(this->getBoardBlockPosition(0, this->game.getBoard().getBaseHeight() - 1));
topOutLine.setFillColor(sf::Color(255, 0, 0));
this->renderWindow->draw(topOutLine);
if (drawActivePiece) {
// active piece
sf::Color pieceColor = 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);
sf::RectangleShape cell(cellSize);
cell.setFillColor(pieceColor);
cell.setPosition(this->getBoardBlockPosition(cellPosition.x, cellPosition.y));
this->renderWindow->draw(cell);
}
}
// next queue
int upShift = 0;
for (int i = 0; i < std::min((int) this->game.getNextPieces().size(), 5); i++) {
sf::FloatRect nextBox = this->nextQueuePosition[i];
nextBox.position.y -= upShift;
sf::Vector2f nextCellSize(this->nextCellSizeZoom, this->nextCellSizeZoom);
sf::Color pieceColor = areBlockBones
? bonesBlockColor
: this->getColorOfBlock(this->game.getNextPieces().at(i).getBlockType(), 0);
sf::Color boxColor = sf::Color(180, 180, 180);
int lowestRank = 0;
for (int y = 0; y < this->game.getNextPieces().at(i).getLength(); y++) {
for (int x = 0; x < this->game.getNextPieces().at(i).getLength(); x++) {
sf::RectangleShape cell(nextCellSize);
if (this->game.getNextPieces().at(i).getPositions().contains(Position{x, y})) {
cell.setFillColor(pieceColor);
lowestRank = y;
}
else {
cell.setFillColor(boxColor);
}
cell.setPosition(sf::Vector2f(nextBox.position.x + (x * this->nextCellSizeZoom),
nextBox.position.y + ((this->game.getNextPieces().at(i).getLength() - y - 1) * this->nextCellSizeZoom)));
this->renderWindow->draw(cell);
}
}
upShift += nextBox.size.y - (this->game.getNextPieces().at(i).getLength() * this->nextCellSizeZoom);
}
// hold box
if (this->game.getHeldPiece() != nullptr) {
sf::Vector2f holdCellSize(this->holdCellSizeZoom, this->holdCellSizeZoom);
sf::Color color = 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);
if (this->game.getHeldPiece()->getPositions().contains(Position{x, y})) {
cell.setFillColor(color);
}
else {
cell.setFillColor(boxColor);
}
cell.setPosition(sf::Vector2f(this->holdBoxPosition.position.x + (x * this->nextCellSizeZoom),
this->holdBoxPosition.position.y + ((this->game.getHeldPiece()->getLength() - y - 1) * this->holdCellSizeZoom)));
this->renderWindow->draw(cell);
}
}
}
// stats
int windowSizeMultiplier = this->settings->getWindowSizeMultiplier();
int fontSize = (this->boardPosition.size.x > (windowSizeMultiplier * 30.f)) ? (windowSizeMultiplier) : (windowSizeMultiplier * 2);
sf::Text text(this->pressStartFont, "", fontSize);
text.setFillColor(sf::Color(0, 0, 0));
int millisecondes = this->game.getFramesPassed() * (1000.f / FRAMES_PER_SECOND);
std::string showedMillisecondes = std::to_string(millisecondes % 1000);
while (showedMillisecondes.size() < 3) {
showedMillisecondes = "0" + showedMillisecondes;
}
std::string showedSecondes = std::to_string((millisecondes / 1000) % 60);
while (showedSecondes.size() < 2) {
showedSecondes = "0" + showedSecondes;
}
std::string showedMinutes = std::to_string((millisecondes / (60 * 1000)));
std::string showedTime = showedMinutes + ":" + showedSecondes + "." + showedMillisecondes;
this->placeText(text, {}, getGamemodeName(this->settings->getGamemode()), 1.f, 3.f, {});
this->placeText(text, {}, getGamemodeGoal(this->settings->getGamemode()), 1.f, 6.f, {});
if (this->game.isOnB2BChain()) {
this->placeText(text, {}, "B2B", 1.f, 22.f, {});
}
this->placeText(text, {}, "LINES:" + std::to_string(this->game.getClearedLines()), 1.f, 27.f, {});
this->placeText(text, {}, "LEVEL:" + std::to_string(this->game.getLevel()), 1.f, 32.f, {});
this->placeText(text, {}, "SCORE:" + std::to_string(this->game.getScore()), 1.f, 37.f, {});
this->placeText(text, {}, "GRADE:" + std::to_string(this->game.getGrade()), 1.f, 42.f, {});
this->placeText(text, {}, showedTime, 1.f, 47.f, {});
// game state
text.setOutlineColor(sf::Color(255, 255, 255));
text.setOutlineThickness(windowSizeMultiplier / 2.f);
text.setCharacterSize(windowSizeMultiplier * 4);
if (this->game.hasWon()) {
this->placeTitle(text, {}, "WIN", 25.f, {});
}
else if (this->game.hasLost()) {
this->placeTitle(text, {}, "LOSE", 25.f, {});
}
else if (this->paused) {
this->placeTitle(text, {}, "PAUSE", 25.f, {});
}
else if (this->startTimer > 0) {
this->placeTitle(text, {}, std::to_string(((this->startTimer - 1) / ((this->settings->getStartTimerLength() * FRAMES_PER_SECOND) / 4))), 25.f, {});
}
this->renderWindow->display();
}
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

@@ -0,0 +1,32 @@
#pragma once
#include "AppMenu.h"
#include <stack>
#include <memory>
#include <SFML/Graphics.hpp>
class GamePlayingAppMenu : public AppMenu {
private:
Game game;
int startTimer;
bool paused;
bool pausePressed;
bool retryPressed;
sf::FloatRect boardPosition;
float cellSizeZoom;
sf::FloatRect holdBoxPosition;
float holdCellSizeZoom;
sf::FloatRect nextQueuePosition[5];
float nextCellSizeZoom;
public:
GamePlayingAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow);
void computeFrame() override;
void drawFrame() const override;
sf::Vector2f getBoardBlockPosition(int x, int y) const;
};

View File

@@ -0,0 +1,84 @@
#include "GameSettingsAppMenu.h"
#include "AppMenu.h"
#include "GamePiecesAppMenu.h"
#include "GameBoardAppMenu.h"
#include "GamePlayingAppMenu.h"
#include "../PlayerCursor.h"
#include <stack>
#include <memory>
#include <SFML/Graphics.hpp>
GameSettingsAppMenu::GameSettingsAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow) :
AppMenu(menuStack, settings, renderWindow),
playerCursor({2, 3, 3}) {
}
void GameSettingsAppMenu::computeFrame() {
this->updateMetaBinds();
this->playerCursor.updatePosition();
switch (this->playerCursor.getPosition().y) {
case 1 : {
switch (this->playerCursor.getPosition().x) {
case 0 : {this->settings->setGamemode(SPRINT); break;}
case 1 : {this->settings->setGamemode(MARATHON); break;}
case 2 : {this->settings->setGamemode(ULTRA); break;}
}
break;
}
case 2 : {
switch (this->playerCursor.getPosition().x) {
case 0 : {this->settings->setGamemode(MASTER); break;}
case 1 : {this->settings->setGamemode(INVISIBLE); break;}
case 2 : {this->settings->setGamemode(ZEN); break;}
}
break;
}
}
if (this->enterReleased) {
if (this->playerCursor.getPosition().y == 0) {
if (this->playerCursor.getPosition().x == 0) {
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));
}
}
if (this->playerCursor.getPosition().y > 0) {
this->menuStack->push(std::make_shared<GamePlayingAppMenu>(this->menuStack, this->settings, this->renderWindow));
}
}
if (this->escReleased) {
this->menuStack->pop();
}
}
void GameSettingsAppMenu::drawFrame() const {
this->renderWindow->clear(sf::Color(200, 200, 200));
sf::Text text(this->pressStartFont, "", this->settings->getWindowSizeMultiplier() * 2);
text.setFillColor(sf::Color(0, 0, 0));
text.setOutlineColor(sf::Color(255, 255, 255));
this->placeTitle(text, {}, "GAME SETTINGS", 5.f, {});
this->placeText(text, this->playerCursor, "PIECES SELECT", 5.f, 15.f, sf::Vector2u{0, 0});
this->placeText(text, this->playerCursor, "BOARD SELECT", 40.f, 15.f, sf::Vector2u{1, 0});
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

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

View File

@@ -0,0 +1,97 @@
#include "InfoAppMenu.h"
#include "AppMenu.h"
#include "../PlayerCursor.h"
#include <stack>
#include <memory>
#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({INFO_SECTIONS_COUNT}),
sectionsName({
"< ABOUT >",
"< PIECES TYPES >",
"< 0 DEGREES ROTATIONS >",
"< ROTATION SYSTEM >",
"< SCORING >"
}),
sectionsContent({
"This game is written in C++,\n"
"using SFML 3 for the GUI.\n"
"It has been inspired by other\n"
"stacker games, such as\n"
"Techmino, jstris, tetr.io, etc.\n"
"This project isn't affiliated\n"
"to them in any ways.\n"
"Current version: beta.",
"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"
"center of the piece by default.\n"
"When kicking the piece, it will\n"
"compute and try (most) position that\n"
"touches the original piece,\n"
"prioritizing sides over depth and\n"
"firstly going down before going up.",
"The score gained from a line clear\n"
"doubles when clearing one more line.\n"
"Clearing with a spin scores as much\n"
"as clearing 2x more lines normally.\n"
"B2B is granted by clearing at least\n"
"4 lines or doing a spin or mini-spin,\n"
"and doubles the score gained.\n"
"A spin is detected when the piece is\n"
"locked in place, a mini-spin simply\n"
"when the last move was a kick."
}) {
}
void InfoAppMenu::computeFrame() {
this->updateMetaBinds();
this->playerCursor.updatePosition();
if (this->escReleased) {
this->menuStack->pop();
}
}
void InfoAppMenu::drawFrame() const {
this->renderWindow->clear(sf::Color(200, 200, 200));
sf::Text text(this->pressStartFont, "", this->settings->getWindowSizeMultiplier() * 2);
text.setFillColor(sf::Color(0, 0, 0));
text.setOutlineColor(sf::Color(255, 255, 255));
this->placeTitle(text, this->playerCursor, this->sectionsName[this->playerCursor.getPosition().x], 10.f, this->playerCursor.getPosition());
text.setLineSpacing((float) this->settings->getWindowSizeMultiplier() / 8);
text.setOutlineThickness(0);
this->placeText(text, {}, this->sectionsContent[this->playerCursor.getPosition().x], 5.f, 30.f, {});
this->renderWindow->display();
}

View File

@@ -0,0 +1,24 @@
#pragma once
#include "AppMenu.h"
#include "../PlayerCursor.h"
#include <stack>
#include <memory>
#include <SFML/Graphics.hpp>
static const int INFO_SECTIONS_COUNT = 5;
class InfoAppMenu : public AppMenu {
private:
PlayerCursor playerCursor;
std::array<std::string, INFO_SECTIONS_COUNT> sectionsName;
std::array<std::string, INFO_SECTIONS_COUNT> sectionsContent;
public:
InfoAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow);
void computeFrame() override;
void drawFrame() const override;
};

View File

@@ -1,21 +1,54 @@
#include "MainAppMenu.h" #include "MainAppMenu.h"
#include "AppMenu.h" #include "AppMenu.h"
#include "GameSettingsAppMenu.h"
#include "SettingsMainAppMenu.h"
#include "InfoAppMenu.h"
#include "../PlayerCursor.h"
#include <stack> #include <stack>
#include <memory> #include <memory>
#include <SFML/Graphics.hpp> #include <SFML/Graphics.hpp>
MainAppMenu::MainAppMenu(std::shared_ptr<std::stack<AppMenu>> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow) : MainAppMenu::MainAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow) :
AppMenu(menuStack, settings, renderWindow) { AppMenu(menuStack, settings, renderWindow),
playerCursor({1, 1, 1}) {
} }
void MainAppMenu::computeFrame() { void MainAppMenu::computeFrame() {
this->updateMetaBinds();
this->playerCursor.updatePosition();
if (this->enterReleased) {
if (this->playerCursor.getPosition().y == 0) {
this->menuStack->push(std::make_shared<GameSettingsAppMenu>(this->menuStack, this->settings, this->renderWindow));
}
if (this->playerCursor.getPosition().y == 1) {
this->menuStack->push(std::make_shared<SettingsMainAppMenu>(this->menuStack, this->settings, this->renderWindow));
}
if (this->playerCursor.getPosition().y == 2) {
this->menuStack->push(std::make_shared<InfoAppMenu>(this->menuStack, this->settings, this->renderWindow));
}
}
if (this->escReleased) {
this->menuStack->pop();
}
} }
void MainAppMenu::drawFrame() const { void MainAppMenu::drawFrame() const {
this->renderWindow->clear(sf::Color(200, 200, 200));
sf::Text text(this->pressStartFont, "", this->settings->getWindowSizeMultiplier() * 2);
text.setFillColor(sf::Color(0, 0, 0));
text.setOutlineColor(sf::Color(255, 255, 255));
this->placeTitle(text, {}, "JMINOS", 10.f, {});
this->placeTitle(text, this->playerCursor, "PLAY", 20.f, sf::Vector2u{0, 0});
this->placeTitle(text, this->playerCursor, "SETTINGS", 30.f, sf::Vector2u{0, 1});
this->placeTitle(text, this->playerCursor, "INFO", 40.f, sf::Vector2u{0, 2});
this->renderWindow->display();
} }

View File

@@ -1,6 +1,7 @@
#pragma once #pragma once
#include "AppMenu.h" #include "AppMenu.h"
#include "../PlayerCursor.h"
#include <stack> #include <stack>
#include <memory> #include <memory>
@@ -8,10 +9,13 @@
class MainAppMenu : public AppMenu { class MainAppMenu : public AppMenu {
private:
PlayerCursor playerCursor;
public: public:
MainAppMenu(std::shared_ptr<std::stack<AppMenu>> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow); MainAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow);
void computeFrame(); void computeFrame() override;
void drawFrame() const; void drawFrame() const override;
}; };

View File

@@ -0,0 +1,76 @@
#include "SettingsControlsAppMenu.h"
#include "AppMenu.h"
#include "../PlayerCursor.h"
#include <stack>
#include <memory>
#include <SFML/Graphics.hpp>
SettingsControlsAppMenu::SettingsControlsAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow) :
AppMenu(menuStack, settings, renderWindow),
playerCursor({1, 1, 1}) {
}
void SettingsControlsAppMenu::computeFrame() {
this->updateMetaBinds();
this->playerCursor.updatePosition();
Player& playerControls = this->settings->getMenu().getPlayerControls();
switch (this->playerCursor.getPosition().y) {
case 0 : {
if (this->playerCursor.movedLeft()) {
playerControls.setDAS(playerControls.getDAS() - 1);
}
if (this->playerCursor.movedRight()) {
playerControls.setDAS(playerControls.getDAS() + 1);
}
break;
}
case 1 : {
if (this->playerCursor.movedLeft()) {
playerControls.setARR(playerControls.getARR() - 1);
}
if (this->playerCursor.movedRight()) {
playerControls.setARR(playerControls.getARR() + 1);
}
break;
}
case 2 : {
if (this->playerCursor.movedLeft()) {
playerControls.setSDR(playerControls.getSDR() - 1);
}
if (this->playerCursor.movedRight()) {
playerControls.setSDR(playerControls.getSDR() + 1);
}
break;
}
}
if (this->escReleased) {
this->menuStack->pop();
}
}
void SettingsControlsAppMenu::drawFrame() const {
this->renderWindow->clear(sf::Color(200, 200, 200));
const Player& playerControls = this->settings->getMenu().readPlayerControls();
sf::Text text(this->pressStartFont, "", this->settings->getWindowSizeMultiplier() * 2);
text.setFillColor(sf::Color(0, 0, 0));
text.setOutlineColor(sf::Color(255, 255, 255));
this->placeTitle(text, {}, "CONTROLS SETTINGS", 5.f, {});
sf::Vector2u windowSize = this->renderWindow->getSize();
this->placeText(text, this->playerCursor, "< DAS: " + std::to_string(playerControls.getDAS()) + " >", 5.f, 15.f, sf::Vector2u{0, 0});
this->placeText(text, this->playerCursor, "< ARR: " + std::to_string(playerControls.getARR()) + " >", 5.f, 25.f, sf::Vector2u{0, 1});
this->placeText(text, this->playerCursor, "< SDR: " + std::to_string(playerControls.getSDR()) + " >", 5.f, 35.f, sf::Vector2u{0, 2});
this->renderWindow->display();
}

View File

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

View File

@@ -0,0 +1,129 @@
#include "SettingsKeybindsAppMenu.h"
#include "AppMenu.h"
#include "../PlayerCursor.h"
#include "../../Utils/AssetManager.h"
#include <stack>
#include <memory>
#include <string>
#include <regex>
#include <filesystem>
#include <algorithm>
#include <SFML/Graphics.hpp>
SettingsKeybindsAppMenu::SettingsKeybindsAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow) :
AppMenu(menuStack, settings, renderWindow),
playerCursor({1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}) {
this->selectedAnAction = false;
for (Action action : ACTION_LIST_IN_ORDER) {
std::string textureName = ACTION_NAMES[action];
textureName = std::regex_replace(textureName, std::regex(" "), "");
const Asset& textureData = getResource("data/images/keybinds/" + textureName + ".png");
this->iconTextures[action] = sf::Texture(textureData.data, textureData.size, false, {{0, 0}, {16, 16}});
}
}
void SettingsKeybindsAppMenu::computeFrame() {
this->updateMetaBinds();
if (!this->selectedAnAction) {
this->playerCursor.updatePosition();
if (this->playerCursor.movedLeft()) {
this->settings->selectPreviousKeybinds();
}
if (this->playerCursor.movedRight()) {
this->settings->selectNextKeybinds();
}
}
else {
bool addedKeybind = false;
for (const auto& [key, string] : KEYS_TO_STRING) {
if (sf::Keyboard::isKeyPressed(key) && (key != sfKey::Enter) && (key != sfKey::Escape)) {
this->settings->getKeybinds().addKey(this->actionSelected, key);
addedKeybind = true;
}
if (addedKeybind) {
this->selectedAnAction = false;
break;
}
}
}
if (this->enterReleased && this->settings->getKeybinds().isModifiable()) {
this->selectedAnAction = !selectedAnAction;
this->actionSelected = ACTION_LIST_IN_ORDER[this->playerCursor.getPosition().y - 1];
}
if (this->escReleased) {
if (this->selectedAnAction) {
this->settings->getKeybinds().clearKeys(this->actionSelected);
this->selectedAnAction = false;
}
else {
this->menuStack->pop();
}
}
}
void SettingsKeybindsAppMenu::drawFrame() const {
this->renderWindow->clear(sf::Color(200, 200, 200));
sf::Text text(this->pressStartFont, "", this->settings->getWindowSizeMultiplier() * 2);
text.setFillColor(sf::Color(0, 0, 0));
text.setOutlineColor(sf::Color(255, 255, 255));
this->placeTitle(text, {}, "KEYBINDS SETTINGS", 5.f, {});
if (this->settings->getKeybindsLayout() == CUSTOMIZABLE_KEYBINDS) {
this->placeText(text, this->playerCursor, "< CUSTOM >", 5.f, 15.f, sf::Vector2u{0, 0});
}
else {
this->placeText(text, this->playerCursor, "< DEFAULT " + std::to_string(this->settings->getKeybindsLayout() + 1) + " >", 5.f, 15.f, sf::Vector2u{0, 0});
}
if (this->selectedAnAction) {
text.setOutlineColor(sf::Color(255, 0, 0));
}
int i = 0;
int firstElem = std::clamp(((int) this->playerCursor.getPosition().y) - 2, 0, 8);
for (Action action : ACTION_LIST_IN_ORDER) {
if (i >= firstElem && i < (firstElem + 3)) {
sf::String string;
bool firstKey = true;
for (sfKey key : this->settings->getKeybinds().getKeybinds(action)) {
if (KEYS_TO_STRING.contains(key)) {
std::string keyString = KEYS_TO_STRING.at(key);
if (firstKey) {
string += keyString;
firstKey = false;
}
else {
string += ", " + keyString;
}
}
}
this->placeText(text, this->playerCursor, setStringToUpperCase(ACTION_NAMES[action]), 15.f, ((i - firstElem) * 10) + 25.f, sf::Vector2u{0, (unsigned int) i + 1});
text.setOutlineThickness(0);
this->placeText(text, {}, string, 40.f, ((i - firstElem) * 10) + 25.f, {});
sf::Sprite sprite(this->iconTextures[action]);
sprite.setOrigin(sprite.getLocalBounds().getCenter());
sprite.setPosition(sf::Vector2f(8.f, ((i - firstElem) * 10) + 25.f) * (float) this->settings->getWindowSizeMultiplier());
sprite.setScale(sprite.getScale() * ((float) this->settings->getWindowSizeMultiplier() / 2));
this->renderWindow->draw(sprite);
}
i++;
}
this->renderWindow->display();
}

View File

@@ -0,0 +1,24 @@
#pragma once
#include "AppMenu.h"
#include "../PlayerCursor.h"
#include <stack>
#include <memory>
#include <SFML/Graphics.hpp>
class SettingsKeybindsAppMenu : public AppMenu {
private:
PlayerCursor playerCursor;
sf::Texture iconTextures[11];
bool selectedAnAction;
Action actionSelected;
public:
SettingsKeybindsAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow);
void computeFrame() override;
void drawFrame() const override;
};

View File

@@ -0,0 +1,78 @@
#include "SettingsMainAppMenu.h"
#include "AppMenu.h"
#include "SettingsKeybindsAppMenu.h"
#include "SettingsControlsAppMenu.h"
#include "../PlayerCursor.h"
#include <stack>
#include <memory>
#include <SFML/Graphics.hpp>
SettingsMainAppMenu::SettingsMainAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow) :
AppMenu(menuStack, settings, renderWindow),
playerCursor({1, 1, 1, 1}) {
}
void SettingsMainAppMenu::computeFrame() {
this->updateMetaBinds();
this->playerCursor.updatePosition();
switch (this->playerCursor.getPosition().y) {
case 2 : {
if (this->playerCursor.movedLeft()) {
if (this->settings->shortenWindow()) {
this->settings->changeVideoMode(*this->renderWindow);
}
}
if (this->playerCursor.movedRight()) {
if (this->settings->widenWindow()) {
this->settings->changeVideoMode(*this->renderWindow);
}
}
break;
}
case 3 : {
if (this->playerCursor.movedLeft()) {
this->settings->shortenStartTimer();
}
if (this->playerCursor.movedRight()) {
this->settings->lengthenStartTimer();
}
break;
}
}
if (this->enterReleased) {
if (this->playerCursor.getPosition().y == 0) {
this->menuStack->push(std::make_shared<SettingsKeybindsAppMenu>(this->menuStack, this->settings, this->renderWindow));
}
if (this->playerCursor.getPosition().y == 1) {
this->menuStack->push(std::make_shared<SettingsControlsAppMenu>(this->menuStack, this->settings, this->renderWindow));
}
}
if (this->escReleased) {
this->menuStack->pop();
}
}
void SettingsMainAppMenu::drawFrame() const {
this->renderWindow->clear(sf::Color(200, 200, 200));
sf::Text text(this->pressStartFont, "", this->settings->getWindowSizeMultiplier() * 2);
text.setFillColor(sf::Color(0, 0, 0));
text.setOutlineColor(sf::Color(255, 255, 255));
this->placeTitle(text, {}, "SETTINGS", 5.f, {});
sf::Vector2u windowSize = this->renderWindow->getSize();
this->placeText(text, this->playerCursor, "CHANGE KEYBINDS", 5.f, 15.f, sf::Vector2u{0, 0});
this->placeText(text, this->playerCursor, "CHANGE CONTROLS", 5.f, 25.f, sf::Vector2u{0, 1});
this->placeText(text, this->playerCursor, "< WINDOW SIZE: " + std::to_string(windowSize.x) + "x" + std::to_string(windowSize.y) + " >", 5.f, 35.f, sf::Vector2u{0, 2});
this->placeText(text, this->playerCursor, "< 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,21 @@
#pragma once
#include "AppMenu.h"
#include "../PlayerCursor.h"
#include <stack>
#include <memory>
#include <SFML/Graphics.hpp>
class SettingsMainAppMenu : public AppMenu {
private:
PlayerCursor playerCursor;
public:
SettingsMainAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow);
void computeFrame() override;
void drawFrame() const override;
};

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,31 +1,31 @@
#include "GraphApp.h" #include "GraphApp.h"
#include "AppMenus/AppMenu.h" #include "AppMenus/AppMenu.h"
#include "AppMenus/MainAppMenu.h" #include "AppMenus/StartUpAppMenu.h"
#include "Settings.h" #include "Settings.h"
#include <stack> #include <stack>
#include <memory> #include <memory>
#include <SFML/Graphics.hpp> #include <SFML/Graphics.hpp>
static const double TIME_BETWEEN_FRAMES = (1000.f / 60.f); static const double TIME_BETWEEN_FRAMES = (1000.f / FRAMES_PER_SECOND);
GraphApp::GraphApp() { GraphApp::GraphApp() {
this->settings = std::make_shared<Settings>(); this->settings = std::make_shared<Settings>(false);
this->menuStack = std::make_shared<std::stack<AppMenu>>(); this->menuStack = std::make_shared<MenuStack>();
this->window = std::make_shared<sf::RenderWindow>(); this->renderWindow = std::make_shared<sf::RenderWindow>();
} }
void GraphApp::startApp() { void GraphApp::run() {
changeVideoMode(*this->window, this->settings->getVideoMode()); this->settings->changeVideoMode(*this->renderWindow);
this->menuStack->push(MainAppMenu(this->menuStack, this->settings, this->window)); this->menuStack->push(std::make_shared<StartUpAppMenu>(this->menuStack, this->settings, this->renderWindow));
bool quit = false; bool quit = false;
double timeAtNextFrame = 0; double timeAtNextFrame = 0;
sf::Clock clock; sf::Clock clock;
while (!quit) { while (!quit) {
while (const std::optional event = this->window->pollEvent()) { while (const std::optional event = this->renderWindow->pollEvent()) {
if (event->is<sf::Event::Closed>()) { if (event->is<sf::Event::Closed>()) {
quit = true; quit = true;
} }
@@ -33,17 +33,21 @@ void GraphApp::startApp() {
if (!quit) { if (!quit) {
if (clock.getElapsedTime().asMilliseconds() > timeAtNextFrame) { if (clock.getElapsedTime().asMilliseconds() > timeAtNextFrame) {
timeAtNextFrame += TIME_BETWEEN_FRAMES; this->menuStack->top()->computeFrame();
this->menuStack->top().computeFrame();
if (this->menuStack->empty()) { if (this->menuStack->empty()) {
quit = true; quit = true;
} }
else { else {
this->menuStack->top().drawFrame(); this->menuStack->top()->drawFrame();
}
while (clock.getElapsedTime().asMilliseconds() > timeAtNextFrame) {
timeAtNextFrame += TIME_BETWEEN_FRAMES;
} }
} }
} }
} }
window->close(); this->settings->saveSettingsToFile();
renderWindow->close();
} }

View File

@@ -11,11 +11,11 @@
class GraphApp { class GraphApp {
private: private:
std::shared_ptr<Settings> settings; std::shared_ptr<Settings> settings;
std::shared_ptr<std::stack<AppMenu>> menuStack; std::shared_ptr<MenuStack> menuStack;
std::shared_ptr<sf::RenderWindow> window; std::shared_ptr<sf::RenderWindow> renderWindow;
public: public:
GraphApp(); GraphApp();
void startApp(); void run();
}; };

View File

@@ -0,0 +1,98 @@
#include "Keybinds.h"
#include "../Core/Action.h"
#include <map>
#include <set>
#include <fstream>
#include <SFML/Graphics.hpp>
Keybinds::Keybinds(int layoutNumber) :
layoutNumber(layoutNumber) {
for (Action action : ACTION_LIST_IN_ORDER) {
this->keybinds.insert({action, std::set<sfKey>()});
}
this->modifiable = (layoutNumber == CUSTOMIZABLE_KEYBINDS);
this->loadKeybindsFromFile();
}
void Keybinds::loadKeybindsFromFile() {
std::ifstream layoutFile("data/config/keybinds/layout" + std::to_string(this->layoutNumber) + ".bin", std::ios::binary);
for (Action action : ACTION_LIST_IN_ORDER) {
this->keybinds.at(action).clear();
}
char byte;
while (layoutFile.peek() != EOF) {
layoutFile.get(byte);
Action action = Action(byte);
bool separatorMet = false;
while (!separatorMet) {
layoutFile.get(byte);
if (byte == (char) 0xFF) {
separatorMet = true;
}
else {
this->keybinds.at(action).insert(sfKey(byte));
}
}
}
}
void Keybinds::saveKeybindsToFile() const {
if (!this->modifiable) return;
std::ofstream layoutFile("data/config/keybinds/layout" + std::to_string(this->layoutNumber) + ".bin", std::ios::trunc | std::ios::binary);
char byte;
for (Action action : ACTION_LIST_IN_ORDER) {
byte = action;
layoutFile.write(&byte, 1);
for (sfKey key : this->keybinds.at(action)) {
byte = (int) key;
layoutFile.write(&byte, 1);
}
byte = 0xFF;
layoutFile.write(&byte, 1);
}
}
void Keybinds::addKey(Action action, sfKey key) {
if (!this->modifiable) return;
if (!KEYS_TO_STRING.contains(key)) return;
this->keybinds.at(action).insert(key);
}
void Keybinds::clearKeys(Action action) {
if (!this->modifiable) return;
this->keybinds.at(action).clear();
}
bool Keybinds::isModifiable() const {
return this->modifiable;
}
const std::set<Action> Keybinds::getActions(sfKey key) const {
std::set<Action> actions;
for (const auto& [action, keys] : this->keybinds) {
if (keys.contains(key)) {
actions.insert(action);
}
}
return actions;
}
const std::set<sfKey>& Keybinds::getKeybinds(Action action) const {
return this->keybinds.at(action);
}

View File

@@ -3,30 +3,154 @@
#include "../Core/Action.h" #include "../Core/Action.h"
#include <map> #include <map>
#include <vector> #include <set>
#include <algorithm>
#include <SFML/Graphics.hpp> #include <SFML/Graphics.hpp>
using sfKey = sf::Keyboard::Key; using sfKey = sf::Keyboard::Key;
static const int NUMBER_OF_KEYBINDS = 5;
static const int CUSTOMIZABLE_KEYBINDS = NUMBER_OF_KEYBINDS - 1;
class Keybinds { class Keybinds {
private: private:
std::map<Action, std::vector<sfKey>> keybinds; std::map<Action, std::set<sfKey>> keybinds;
int layoutNumber;
bool modifiable;
public: public:
Keybinds(); Keybinds(int layoutNumber);
void loadKeybindsFromFile(); void loadKeybindsFromFile();
void saveKeybindsToFile() const; void saveKeybindsToFile() const;
void createDefaultKeybindsFile() const;
void addKey(Action action, sfKey key); void addKey(Action action, sfKey key);
void clearKeys(Action action); void clearKeys(Action action);
const std::vector<Action>& getActions(sfKey key) const; bool isModifiable() const;
const std::vector<sfKey>& getKeybinds(Action action) const; const std::set<Action> getActions(sfKey key) const;
const std::set<sfKey>& getKeybinds(Action action) const;
}; };
inline std::string setStringToUpperCase(std::string&& str) {
std::transform(str.begin(), str.end(), str.begin(), ::toupper);
return str;
}
inline std::string setStringToUpperCase(const std::string& str) {
std::string result = str;
std::transform(result.begin(), result.end(), result.begin(), ::toupper);
return result;
}
#define INSERT_MAPPING(identifier) {sfKey::identifier, setStringToUpperCase(#identifier)}
static const std::map<sfKey, sf::String> KEYS_TO_STRING = {
INSERT_MAPPING(A),
INSERT_MAPPING(B),
INSERT_MAPPING(C),
INSERT_MAPPING(D),
INSERT_MAPPING(E),
INSERT_MAPPING(F),
INSERT_MAPPING(G),
INSERT_MAPPING(H),
INSERT_MAPPING(I),
INSERT_MAPPING(J),
INSERT_MAPPING(K),
INSERT_MAPPING(L),
INSERT_MAPPING(M),
INSERT_MAPPING(N),
INSERT_MAPPING(O),
INSERT_MAPPING(P),
INSERT_MAPPING(Q),
INSERT_MAPPING(R),
INSERT_MAPPING(S),
INSERT_MAPPING(T),
INSERT_MAPPING(U),
INSERT_MAPPING(V),
INSERT_MAPPING(W),
INSERT_MAPPING(X),
INSERT_MAPPING(Y),
INSERT_MAPPING(Z),
INSERT_MAPPING(Num0),
INSERT_MAPPING(Num1),
INSERT_MAPPING(Num2),
INSERT_MAPPING(Num3),
INSERT_MAPPING(Num4),
INSERT_MAPPING(Num5),
INSERT_MAPPING(Num6),
INSERT_MAPPING(Num7),
INSERT_MAPPING(Num8),
INSERT_MAPPING(Num9),
INSERT_MAPPING(Escape),
INSERT_MAPPING(LControl),
INSERT_MAPPING(LShift),
INSERT_MAPPING(LAlt),
INSERT_MAPPING(LSystem),
INSERT_MAPPING(RControl),
INSERT_MAPPING(RShift),
INSERT_MAPPING(RAlt),
INSERT_MAPPING(RSystem),
INSERT_MAPPING(Menu),
INSERT_MAPPING(LBracket),
INSERT_MAPPING(RBracket),
INSERT_MAPPING(Semicolon),
INSERT_MAPPING(Comma),
INSERT_MAPPING(Period),
INSERT_MAPPING(Apostrophe),
INSERT_MAPPING(Slash),
INSERT_MAPPING(Backslash),
INSERT_MAPPING(Grave),
INSERT_MAPPING(Equal),
INSERT_MAPPING(Hyphen),
INSERT_MAPPING(Space),
INSERT_MAPPING(Enter),
INSERT_MAPPING(Backspace),
INSERT_MAPPING(Tab),
INSERT_MAPPING(PageUp),
INSERT_MAPPING(PageDown),
INSERT_MAPPING(End),
INSERT_MAPPING(Home),
INSERT_MAPPING(Insert),
INSERT_MAPPING(Delete),
INSERT_MAPPING(Add),
INSERT_MAPPING(Subtract),
INSERT_MAPPING(Multiply),
INSERT_MAPPING(Divide),
INSERT_MAPPING(Left),
INSERT_MAPPING(Right),
INSERT_MAPPING(Up),
INSERT_MAPPING(Down),
INSERT_MAPPING(Numpad0),
INSERT_MAPPING(Numpad1),
INSERT_MAPPING(Numpad2),
INSERT_MAPPING(Numpad3),
INSERT_MAPPING(Numpad4),
INSERT_MAPPING(Numpad5),
INSERT_MAPPING(Numpad6),
INSERT_MAPPING(Numpad7),
INSERT_MAPPING(Numpad8),
INSERT_MAPPING(Numpad9),
INSERT_MAPPING(F1),
INSERT_MAPPING(F2),
INSERT_MAPPING(F3),
INSERT_MAPPING(F4),
INSERT_MAPPING(F5),
INSERT_MAPPING(F6),
INSERT_MAPPING(F7),
INSERT_MAPPING(F8),
INSERT_MAPPING(F9),
INSERT_MAPPING(F10),
INSERT_MAPPING(F11),
INSERT_MAPPING(F12),
INSERT_MAPPING(F13),
INSERT_MAPPING(F14),
INSERT_MAPPING(F15),
INSERT_MAPPING(Pause)
};
#undef INSERT_MAPPING

View File

@@ -0,0 +1,35 @@
#pragma once
#include <string>
enum PiecesType {
CONVEX_PIECES,
HOLELESS_PIECES,
OTHER_PIECES,
ALL_PIECES,
SINGLE_PIECE
};
inline int getSizeOfPieces(PiecesType type) {
if (type < SINGLE_PIECE) return 0;
else return (type - SINGLE_PIECE + 1);
}
inline PiecesType createSinglePieceType(int size) {
return PiecesType(SINGLE_PIECE + size - 1);
}
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

@@ -0,0 +1,163 @@
#include "PlayerCursor.h"
#include "Keybinds.h"
#include "Settings.h"
#include <vector>
#include <algorithm>
#include <SFML/Graphics.hpp>
static const int MENU_DAS = FRAMES_PER_SECOND / 2;
PlayerCursor::PlayerCursor(std::vector<unsigned int> rows) :
rows(rows) {
this->position = sf::Vector2u({0, 0});
this->leftDAS = 0;
this->rightDAS = 0;
this->upDAS = 0;
this->downDAS = 0;
}
void PlayerCursor::updatePosition() {
(sf::Keyboard::isKeyPressed(sfKey::Left)) ? (this->leftDAS++) : (this->leftDAS = 0);
if (this->shouldMove(this->leftDAS)) {
this->moveLeft();
}
(sf::Keyboard::isKeyPressed(sfKey::Right)) ? (this->rightDAS++) : (this->rightDAS = 0);
if (this->shouldMove(this->rightDAS)) {
this->moveRight();
}
(sf::Keyboard::isKeyPressed(sfKey::Up)) ? (this->upDAS++) : (this->upDAS = 0);
if (this->shouldMove(this->upDAS)) {
this->moveUp();
}
(sf::Keyboard::isKeyPressed(sfKey::Down)) ? (this->downDAS++) : (this->downDAS = 0);
if (this->shouldMove(this->downDAS)) {
this->moveDown();
}
}
bool PlayerCursor::movedLeft() const {
return this->shouldMove(this->leftDAS);
}
bool PlayerCursor::movedRight() const {
return this->shouldMove(this->rightDAS);
}
bool PlayerCursor::movedUp() const {
return this->shouldMove(this->upDAS);
}
bool PlayerCursor::movedDown() const {
return this->shouldMove(this->downDAS);
}
void PlayerCursor::goToPosition(const sf::Vector2u& newPosition) {
if (this->rows.size() > newPosition.y) {
if (this->rows.at(newPosition.y) > newPosition.x) {
this->position = newPosition;
}
}
}
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;
}
bool PlayerCursor::shouldMove(int DAS) const {
return (DAS == 1
|| (DAS > MENU_DAS && (DAS % 5) == 0)
|| (DAS > (FRAMES_PER_SECOND * 2)));
}
void PlayerCursor::moveLeft() {
if (this->position.x == 0) {
this->position.x = this->rows.at(this->position.y) - 1;
}
else {
this->position.x--;
}
}
void PlayerCursor::moveRight() {
if (this->position.x == this->rows.at(this->position.y) - 1) {
this->position.x = 0;
}
else {
this->position.x++;
}
}
void PlayerCursor::moveUp() {
if (this->position.y == 0) {
this->position.y = this->rows.size() - 1;
}
else {
this->position.y--;
}
if (this->position.x >= this->rows.at(this->position.y)) {
this->position.x = this->rows.at(this->position.y) - 1;
}
}
void PlayerCursor::moveDown() {
if (this->position.y == this->rows.size() - 1) {
this->position.y = 0;
}
else {
this->position.y++;
}
if (this->position.x >= this->rows.at(this->position.y)) {
this->position.x = this->rows.at(this->position.y) - 1;
}
}

View File

@@ -0,0 +1,51 @@
#pragma once
#include <vector>
#include <SFML/Graphics.hpp>
class PlayerCursor {
private:
std::vector<unsigned int> rows;
sf::Vector2u position;
int leftDAS;
int rightDAS;
int upDAS;
int downDAS;
public:
PlayerCursor(std::vector<unsigned int> rows);
void updatePosition();
bool movedLeft() const;
bool movedRight() const;
bool movedUp() const;
bool movedDown() const;
void goToPosition(const sf::Vector2u& newPosition);
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:
bool shouldMove(int DAS) const;
void moveLeft();
void moveRight();
void moveUp();
void moveDown();
};

View File

@@ -2,48 +2,251 @@
#include "../Core/Menu.h" #include "../Core/Menu.h"
#include "Keybinds.h" #include "Keybinds.h"
#include "PiecesType.h"
#include <vector>
#include <optional>
#include <fstream>
#include <algorithm>
#include <SFML/Graphics.hpp> #include <SFML/Graphics.hpp>
static const int NUMBER_OF_KEYBINDS = 5;
static const int CUSTOMIZABLE_KEYBINDS = NUMBER_OF_KEYBINDS - 1;
static const sf::Vector2u BASE_WINDOW_SIZE = {80, 50}; static const sf::Vector2u BASE_WINDOW_SIZE = {80, 50};
static const int WINDOW_SIZE_MULTIPLIERS[] = {4, 6, 9, 14, 20}; static const int WINDOW_SIZE_MULTIPLIERS[] = {4, 6, 10, 14, 20, 30, 40};
static const int WINDOW_SIZE_LAST_MODE = (sizeof(WINDOW_SIZE_MULTIPLIERS) / sizeof(int)) - 1; static const int WINDOW_SIZE_LAST_MODE = (sizeof(WINDOW_SIZE_MULTIPLIERS) / sizeof(int)) - 1;
static const int START_TIMER_MAX = 4;
static const int DISTRIBUTION_MAX = 20;
Settings::Settings() { Settings::Settings(bool loadPieces) {
for (int i = 1; i <= 15; i++) { this->keybinds.clear();
this->menu.getPiecesList().loadPieces(i); this->keybinds.reserve(NUMBER_OF_KEYBINDS);
for (int i = 0; i < NUMBER_OF_KEYBINDS; i++) {
this->keybinds.emplace_back(i);
} }
this->loadSettingsFromFile(); this->loadSettingsFromFile(loadPieces, {});
} }
void Settings::loadSettingsFromFile() { void Settings::loadPieces(int maximumPiecesSizeRequest) {
this->menu.getPiecesList().unselectAll(); if (maximumPiecesSizeRequest < MINIMUM_PIECES_SIZE) {
this->menu.getPiecesList().selectAllPieces(4); maximumPiecesSizeRequest = MINIMUM_PIECES_SIZE;
this->windowSizeMode = 2; }
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;
// DAS tuning
settingsFile.get(byte);
this->menu.getPlayerControls().setDAS(byte);
// ARR tuning
settingsFile.get(byte);
this->menu.getPlayerControls().setARR(byte);
// SDR tuning
settingsFile.get(byte);
this->menu.getPlayerControls().setSDR(byte);
// window size mode
settingsFile.get(byte);
this->windowSizeMode = byte;
// start timer length
settingsFile.get(byte);
this->startTimerLength = byte;
// gamemode
settingsFile.get(byte);
this->gamemode = Gamemode(byte);
// board width
settingsFile.get(byte);
this->menu.setBoardWidth(byte);
// board height
settingsFile.get(byte);
this->menu.setBoardHeight(byte);
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);
this->distributions.push_back(byte);
}
this->confirmDistribution();
// 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();
}
} }
void Settings::saveSettingsToFile() const { void Settings::saveSettingsToFile() const {
if (!this->loadedPieces) return;
} this->keybinds.at(CUSTOMIZABLE_KEYBINDS).saveKeybindsToFile();
void Settings::createDefaultSettingsFile() const { 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);
// DAS tuning
byte = this->menu.readPlayerControls().getDAS();
settingsFile.write(&byte, 1);
// ARR tuning
byte = this->menu.readPlayerControls().getARR();
settingsFile.write(&byte, 1);
// SDR tuning
byte = this->menu.readPlayerControls().getSDR();
settingsFile.write(&byte, 1);
// window size mode
byte = this->windowSizeMode;
settingsFile.write(&byte, 1);
// start timer length
byte = this->startTimerLength;
settingsFile.write(&byte, 1);
// gamemode
byte = this->gamemode;
settingsFile.write(&byte, 1);
// board width
byte = this->menu.getBoardWidth();
settingsFile.write(&byte, 1);
// board height
byte = this->menu.getBoardHeight();
settingsFile.write(&byte, 1);
// piece distribution
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);
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);
}
}
}
} }
bool Settings::selectNextKeybinds() { bool Settings::selectNextKeybinds() {
if (this->chosenKeybinds < NUMBER_OF_KEYBINDS) { if (this->chosenKeybinds < (NUMBER_OF_KEYBINDS - 1)) {
this->chosenKeybinds++; this->chosenKeybinds++;
return true;
} }
return false;
} }
bool Settings::selectPreviousKeybinds() { bool Settings::selectPreviousKeybinds() {
if (this->chosenKeybinds > 0) { if (this->chosenKeybinds > 0) {
this->chosenKeybinds--; this->chosenKeybinds--;
return true;
} }
return false;
} }
bool Settings::canModifyCurrentKeybinds() const { bool Settings::canModifyCurrentKeybinds() const {
@@ -53,19 +256,125 @@ bool Settings::canModifyCurrentKeybinds() const {
bool Settings::widenWindow() { bool Settings::widenWindow() {
if (this->windowSizeMode < WINDOW_SIZE_LAST_MODE) { if (this->windowSizeMode < WINDOW_SIZE_LAST_MODE) {
this->windowSizeMode++; this->windowSizeMode++;
return true;
} }
return false;
} }
bool Settings::shortenWindow() { bool Settings::shortenWindow() {
if (this->windowSizeMode > 0) { if (this->windowSizeMode > 0) {
this->windowSizeMode--; this->windowSizeMode--;
return true;
} }
return false;
}
void Settings::changeVideoMode(sf::RenderWindow& window) const {
sf::VideoMode videoMode(BASE_WINDOW_SIZE * (unsigned int) WINDOW_SIZE_MULTIPLIERS[this->windowSizeMode]);
window.create(videoMode, "jminos", sf::Style::Close | sf::Style::Titlebar);
sf::Vector2u desktopSize = sf::VideoMode::getDesktopMode().size;
sf::Vector2u windowSize = window.getSize();
window.setPosition(sf::Vector2i((desktopSize.x / 2) - (windowSize.x / 2), (desktopSize.y / 2) - (windowSize.y / 2)));
}
bool Settings::lengthenStartTimer() {
if (this->startTimerLength < START_TIMER_MAX) {
this->startTimerLength++;
return true;
}
return false;
}
bool Settings::shortenStartTimer() {
if (this->startTimerLength > 0) {
this->startTimerLength--;
return true;
}
return false;
} }
void Settings::setGamemode(Gamemode gamemode) { void Settings::setGamemode(Gamemode gamemode) {
this->gamemode = gamemode; this->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) {
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 (!(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() { Menu& Settings::getMenu() {
return this->menu; return this->menu;
} }
@@ -74,6 +383,18 @@ Keybinds& Settings::getKeybinds() {
return this->keybinds.at(this->chosenKeybinds); return this->keybinds.at(this->chosenKeybinds);
} }
int Settings::getMaximumPiecesSize() const {
return this->maximumPiecesSize;
}
bool Settings::hasLoadedPieces() const {
return this->loadedPieces;
}
int Settings::getKeybindsLayout() const {
return this->chosenKeybinds;
}
Gamemode Settings::getGamemode() const { Gamemode Settings::getGamemode() const {
return this->gamemode; return this->gamemode;
} }
@@ -82,6 +403,14 @@ int Settings::getWindowSizeMultiplier() const {
return WINDOW_SIZE_MULTIPLIERS[this->windowSizeMode]; return WINDOW_SIZE_MULTIPLIERS[this->windowSizeMode];
} }
const sf::VideoMode& Settings::getVideoMode() const { int Settings::getStartTimerLength() const {
return sf::VideoMode(BASE_WINDOW_SIZE * (unsigned int) WINDOW_SIZE_MULTIPLIERS[this->windowSizeMode]); 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

@@ -2,47 +2,99 @@
#include "../Core/Menu.h" #include "../Core/Menu.h"
#include "Keybinds.h" #include "Keybinds.h"
#include "PiecesType.h"
#include <SFML/Graphics.hpp>
#include <vector> #include <vector>
#include <optional>
#include <SFML/Graphics.hpp>
static const int CURRENT_FILE_FORMAT_VERSION = 11;
static const int MAXIMUM_BOARD_WIDTH = 40;
static const int MAXIMUM_BOARD_HEIGHT = 40;
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 = DEBUG_PIECES_SIZE;
#endif
static const std::pair<PiecesType, int> DEFAULT_SELECTION = {ALL_PIECES, MINIMUM_PIECES_SIZE};
class Settings { class Settings {
private: private:
Menu menu; Menu menu;
int maximumPiecesSize;
bool loadedPieces;
std::vector<Keybinds> keybinds; std::vector<Keybinds> keybinds;
int chosenKeybinds; int chosenKeybinds;
Gamemode gamemode;
int windowSizeMode; int windowSizeMode;
int startTimerLength;
Gamemode gamemode;
std::vector<std::pair<PiecesType, int>> selectedPieces;
std::vector<int> distributions;
public: public:
Settings(); Settings(bool loadPieces);
void loadSettingsFromFile(); void loadPieces(int maximumPiecesSizeRequest);
void loadSettingsFromFile(bool loadPieces, std::optional<int> maximumPiecesSizeRequest);
void saveSettingsToFile() const; void saveSettingsToFile() const;
void createDefaultSettingsFile() const;
bool selectNextKeybinds(); bool selectNextKeybinds();
bool selectPreviousKeybinds(); bool selectPreviousKeybinds();
bool canModifyCurrentKeybinds() const; bool canModifyCurrentKeybinds() const;
void setGamemode(Gamemode gamemode);
bool widenWindow(); bool widenWindow();
bool shortenWindow(); bool shortenWindow();
void changeVideoMode(sf::RenderWindow& window) const;
bool lengthenStartTimer();
bool shortenStartTimer();
void setGamemode(Gamemode gamemode);
void selectPieces(PiecesType type, int value);
void unselectPieces(int index);
void confirmSelectedPieces();
bool increaseDistribution(int size);
bool decreaseDistribution(int size);
void confirmDistribution();
Menu& getMenu(); Menu& getMenu();
Keybinds& getKeybinds(); Keybinds& getKeybinds();
int getMaximumPiecesSize() const;
bool hasLoadedPieces() const;
int getKeybindsLayout() const;
Gamemode getGamemode() const; Gamemode getGamemode() const;
int getWindowSizeMultiplier() const; int getWindowSizeMultiplier() const;
const sf::VideoMode& getVideoMode() const; int getStartTimerLength() const;
const std::vector<std::pair<PiecesType, int>>& getSelectedPieces() const;
const std::vector<int>& getDistributions() const;
}; };

View File

@@ -1,138 +1,210 @@
#include <SFML/Graphics.hpp> #include "GraphApp.h"
#include "../Core/Menu.h"
#include "../Pieces/PiecesFiles.h" #include "../Pieces/PiecesFiles.h"
#include <iostream>
void setToDefaultConfig(); #include <filesystem>
#include <fstream>
void resetSettingsFile();
void resetKeybindFile(int layout);
int main() { int main() {
std::srand(std::time(NULL)); std::srand(std::time(NULL));
sf::RenderWindow window(sf::VideoMode({800, 640}), "My window", sf::Style::Titlebar | sf::Style::Close);
window.setPosition(sf::Vector2i(sf::VideoMode::getDesktopMode().size.x / 2 - 400, sf::VideoMode::getDesktopMode().size.y / 2 - 320));
PiecesFiles pf; PiecesFiles pf;
for (int i = 1; i <= 10; i++) { for (int i = 1; i <= MAXIMUM_PIECES_SIZE; i++) {
pf.savePieces(i); if (!std::filesystem::exists("data/pieces/" + std::to_string(i) + "minos.bin")) {
#ifndef DEBUG
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);
}
}
#ifdef DEBUG
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 << "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);
}
}
} }
Menu m; for (int i = 0; i < NUMBER_OF_KEYBINDS; i++) {
m.getPiecesList().loadPieces(10); if (!std::filesystem::exists("data/config/keybinds/layout" + std::to_string(i) + ".bin")) {
m.getPiecesList().selectAllPieces(4); std::cout << "INFO: Keybind file n°" << (i + 1) << "/" << NUMBER_OF_KEYBINDS << " not found, generating..." << std::endl;
m.setBoardWidth(10); resetKeybindFile(i);
m.getPlayerControls().setDAS(6); }
m.getPlayerControls().setARR(0);
m.getPlayerControls().setSDR(0);
Game game = m.startGame(SPRINT);
game.start();
sf::Clock clock;
sf::Font font;
if (!font.openFromFile("data/fonts/arial.ttf")) {
std::cout << "aaaaaaaaaaaaaa";
} }
sf::Text text(font);
text.setCharacterSize(20);
text.setFillColor(sf::Color::White);
while (window.isOpen()) { GraphApp UI;
while (const std::optional event = window.pollEvent()) { UI.run();
if (event->is<sf::Event::Closed>())
window.close(); return 0;
} }
if (clock.getElapsedTime().asMilliseconds() > 16) {
clock.restart(); void resetSettingsFile() {
if (!std::filesystem::exists("data/config")) {
window.clear(sf::Color::Black); std::filesystem::create_directories("data/config");
}
std::set<Action> actions;
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Left)) { std::ofstream settingsFile("data/config/settings.bin", std::ios::trunc | std::ios::binary);
actions.insert(MOVE_LEFT); char byte;
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Right)) { Menu menu;
actions.insert(MOVE_RIGHT);
} // file format version
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Up)) { byte = CURRENT_FILE_FORMAT_VERSION;
actions.insert(HARD_DROP); settingsFile.write(&byte, 1);
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Down)) { // maximum pieces size
actions.insert(SOFT_DROP); byte = MINIMUM_PIECES_SIZE;
} settingsFile.write(&byte, 1);
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::A)) {
actions.insert(ROTATE_CCW); // keybind layout
} byte = 0;
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::E)) { settingsFile.write(&byte, 1);
actions.insert(ROTATE_CW);
} // DAS tuning
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Z)) { byte = menu.getPlayerControls().getDAS();
actions.insert(HOLD); settingsFile.write(&byte, 1);
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Tab)) { // ARR tuning
actions.insert(ROTATE_0); byte = menu.getPlayerControls().getARR();
} settingsFile.write(&byte, 1);
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Num2)) {
actions.insert(ROTATE_180); // SDR tuning
} byte = menu.getPlayerControls().getSDR();
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::R)) { settingsFile.write(&byte, 1);
game.reset();
game.start(); // window size mode
} byte = 2;
game.nextFrame(actions); settingsFile.write(&byte, 1);
for (int y = game.getBoard().getBaseHeight() + 5; y >= 0; y--) { // start timer length
for (int x = 0; x < game.getBoard().getWidth(); x++) { byte = 2;
bool isActivePieceHere = (game.getActivePiece() != nullptr) && (game.getActivePiece()->getPositions().contains(Position{x, y} - game.getActivePiecePosition())); settingsFile.write(&byte, 1);
bool isGhostPieceHere = (game.getActivePiece() != nullptr) && (game.getActivePiece()->getPositions().contains(Position{x, y} - game.ghostPiecePosition()));
Block block = (isActivePieceHere || isGhostPieceHere) ? game.getActivePiece()->getBlockType() : game.getBoard().getBlock(Position{x, y}); // gamemode
byte = Gamemode(0);
sf::RectangleShape cell(sf::Vector2f(20.f, 20.f)); settingsFile.write(&byte, 1);
cell.setFillColor(sf::Color(BLOCKS_COLOR[block].red, BLOCKS_COLOR[block].green, BLOCKS_COLOR[block].blue, (isGhostPieceHere && !isActivePieceHere) ? 150 : 255));
cell.setPosition(sf::Vector2f(x*20, (game.getBoard().getBaseHeight() + 10 - y)*20)); // board width
window.draw(cell); byte = menu.getBoardWidth();
} settingsFile.write(&byte, 1);
}
// board height
if (game.getNextPieces().size() > 0) { byte = menu.getBoardHeight();
for (int y = 10; y >= 0; y--) { settingsFile.write(&byte, 1);
for (int x = 0; x <= 10; x++) {
Block block = game.getNextPieces().at(0).getBlockType(); // piece distribution
sf::RectangleShape cell(sf::Vector2f(20.f, 20.f)); byte = DEFAULT;
cell.setPosition(sf::Vector2f((x + 2 + game.getBoard().getWidth())*20, (game.getBoard().getBaseHeight() - y)*20)); settingsFile.write(&byte, 1);
if (game.getNextPieces().at(0).getPositions().contains(Position({x, y}))) { for (int i = 1; i <= 15; i++) {
cell.setFillColor(sf::Color(BLOCKS_COLOR[block].red, BLOCKS_COLOR[block].green, BLOCKS_COLOR[block].blue)); byte = 1;
} settingsFile.write(&byte, 1);
else { }
cell.setFillColor(sf::Color(0, 0, 0));
} // selected pieces
window.draw(cell); byte = ALL_PIECES;
} settingsFile.write(&byte, 1);
} byte = 4;
} settingsFile.write(&byte, 1);
}
if (game.getHeldPiece() != nullptr) {
for (int y = 10; y >= 0; y--) { void resetKeybindFile(int layout) {
for (int x = 0; x <= 10; x++) { if (layout < 0 || layout > 4) return;
Block block = game.getHeldPiece()->getBlockType();
sf::RectangleShape cell(sf::Vector2f(20.f, 20.f)); std::ofstream layoutFile("data/config/keybinds/layout" + std::to_string(layout) + ".bin", std::ios::trunc | std::ios::binary);
cell.setPosition(sf::Vector2f((x + 12 + game.getBoard().getWidth())*20, (game.getBoard().getBaseHeight() - y)*20)); std::map<Action, sfKey> keybinds;
if (game.getHeldPiece()->getPositions().contains(Position({x, y}))) {
cell.setFillColor(sf::Color(BLOCKS_COLOR[block].red, BLOCKS_COLOR[block].green, BLOCKS_COLOR[block].blue)); if (layout != 4) {
} keybinds.insert({PAUSE, sfKey::P});
else { keybinds.insert({RETRY, sfKey::R});
cell.setFillColor(sf::Color(0, 0, 0)); }
}
window.draw(cell); if (layout == 0) {
} keybinds.insert({MOVE_LEFT, sfKey::Left});
} keybinds.insert({MOVE_RIGHT, sfKey::Right});
} keybinds.insert({SOFT_DROP, sfKey::Down});
keybinds.insert({HARD_DROP, sfKey::Space});
text.setPosition(sf::Vector2f(12*20, (game.getBoard().getBaseHeight() - 5)*20)); keybinds.insert({ROTATE_CW, sfKey::Up});
text.setString(sf::String(std::to_string(game.getClearedLines()))); keybinds.insert({ROTATE_CCW, sfKey::Z});
window.draw(text); keybinds.insert({ROTATE_180, sfKey::X});
keybinds.insert({ROTATE_0, sfKey::LShift});
window.display(); keybinds.insert({HOLD, sfKey::C});
}
if (layout == 1) {
keybinds.insert({MOVE_LEFT, sfKey::Z});
keybinds.insert({MOVE_RIGHT, sfKey::C});
keybinds.insert({SOFT_DROP, sfKey::X});
keybinds.insert({HARD_DROP, sfKey::S});
keybinds.insert({ROTATE_CW, sfKey::M});
keybinds.insert({ROTATE_CCW, sfKey::Comma});
keybinds.insert({ROTATE_180, sfKey::J});
keybinds.insert({ROTATE_0, sfKey::K});
keybinds.insert({HOLD, sfKey::LShift});
}
if (layout == 2) {
keybinds.insert({MOVE_LEFT, sfKey::A});
keybinds.insert({MOVE_RIGHT, sfKey::D});
keybinds.insert({SOFT_DROP, sfKey::W});
keybinds.insert({HARD_DROP, sfKey::S});
keybinds.insert({ROTATE_CW, sfKey::Left});
keybinds.insert({ROTATE_CCW, sfKey::Right});
keybinds.insert({ROTATE_180, sfKey::Up});
keybinds.insert({ROTATE_0, sfKey::Down});
keybinds.insert({HOLD, sfKey::RShift});
}
if (layout == 3) {
keybinds.insert({MOVE_LEFT, sfKey::Left});
keybinds.insert({MOVE_RIGHT, sfKey::Right});
keybinds.insert({SOFT_DROP, sfKey::Down});
keybinds.insert({HARD_DROP, sfKey::Up});
keybinds.insert({ROTATE_CW, sfKey::E});
keybinds.insert({ROTATE_CCW, sfKey::A});
keybinds.insert({ROTATE_180, sfKey::Num2});
keybinds.insert({ROTATE_0, sfKey::Tab});
keybinds.insert({HOLD, sfKey::Z});
}
char byte;
for (Action action : ACTION_LIST_IN_ORDER) {
byte = action;
layoutFile.write(&byte, 1);
if (keybinds.contains(action)) {
byte = (int) keybinds.at(action);
layoutFile.write(&byte, 1);
} }
byte = 0xFF;
layoutFile.write(&byte, 1);
} }
} }

View File

@@ -50,7 +50,7 @@ void Generator::generate(int polyominoSize, int lastAddedPositionNumber, int nex
} }
// generate the list of candidate positions // generate the list of candidate positions
for (Position position : this->currentTestedShape) { for (const Position position : this->currentTestedShape) {
this->tryToAddCandidatePosition(Position{position.x, position.y + 1}, nextAvaibleNumber, candidatePositions); this->tryToAddCandidatePosition(Position{position.x, position.y + 1}, nextAvaibleNumber, candidatePositions);
this->tryToAddCandidatePosition(Position{position.x + 1, position.y}, nextAvaibleNumber, candidatePositions); this->tryToAddCandidatePosition(Position{position.x + 1, position.y}, nextAvaibleNumber, candidatePositions);
this->tryToAddCandidatePosition(Position{position.x, position.y - 1}, nextAvaibleNumber, candidatePositions); this->tryToAddCandidatePosition(Position{position.x, position.y - 1}, nextAvaibleNumber, candidatePositions);
@@ -58,7 +58,7 @@ void Generator::generate(int polyominoSize, int lastAddedPositionNumber, int nex
} }
// try adding a square only to positions with a higher number than the last one // try adding a square only to positions with a higher number than the last one
for (auto [key, val] : candidatePositions) { for (const auto [key, val] : candidatePositions) {
if (val > lastAddedPositionNumber) { if (val > lastAddedPositionNumber) {
this->currentTestedShape.insert(key); this->currentTestedShape.insert(key);
this->generate(polyominoSize, val, nextAvaibleNumber, (polyominoSize == this->currentTestedShape.size()) ? std::map<Position, int>() : candidatePositions); this->generate(polyominoSize, val, nextAvaibleNumber, (polyominoSize == this->currentTestedShape.size()) ? std::map<Position, int>() : candidatePositions);

View File

@@ -10,6 +10,8 @@
#include <filesystem> #include <filesystem>
#include <algorithm> #include <algorithm>
#include "../Common/Compression.h"
PiecesFiles::PiecesFiles() { PiecesFiles::PiecesFiles() {
} }
@@ -19,34 +21,46 @@ bool PiecesFiles::savePieces(int polyominoSize) const {
if (!this->getFilePath(polyominoSize, filePath)) { if (!this->getFilePath(polyominoSize, filePath)) {
return false; return false;
} }
std::ofstream piecesFile(filePath, std::ios::trunc | std::ios::binary);
if (!piecesFile.good()) { Generator generator;
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; return false;
} }
Generator generator; constexpr std::size_t INITIAL_CAPACITY = 2048;
std::vector<Polyomino> nMinos = generator.generatePolyominoes(polyominoSize); DataBuffer buffer(INITIAL_CAPACITY);
// sorting the polyominoes is done after setting spawn position to ensure the order is always the same // 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(); 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 // write the characteristics of the piece
char infoByte = (nMino.isConvex() << 7) + (nMino.hasHole() << 6) + nMino.getLength(); bool isConvex = polyomino.isConvex();
piecesFile.write(&infoByte, 1); bool hasHole = (isConvex) ? false : polyomino.hasHole();
std::uint8_t infoByte = (isConvex << 7) + (hasHole << 6) + polyomino.getLength();
buffer << infoByte;
// write the positions of the piece // write the positions of the piece
char positionByte; std::uint8_t positionByte;
for (Position position : nMino.getPositions()) { for (const Position position : polyomino.getPositions()) {
positionByte = (position.x << 4) + position.y; positionByte = (position.x << 4) + position.y;
piecesFile.write(&positionByte, 1); buffer << positionByte;
} }
} }
return true; DataBuffer compressed = Compress(buffer);
return compressed.WriteFile(filePath);
} }
bool PiecesFiles::loadPieces(int polyominoSize, std::vector<Piece>& pieces, std::vector<int>& convexPieces, std::vector<int>& holelessPieces, std::vector<int>& otherPieces) const { bool PiecesFiles::loadPieces(int polyominoSize, std::vector<Piece>& pieces, std::vector<int>& convexPieces, std::vector<int>& holelessPieces, std::vector<int>& otherPieces) const {
@@ -54,6 +68,13 @@ bool PiecesFiles::loadPieces(int polyominoSize, std::vector<Piece>& pieces, std:
if (!this->getFilePath(polyominoSize, filePath)) { if (!this->getFilePath(polyominoSize, filePath)) {
return false; return false;
} }
DataBuffer compressed;
if(!compressed.ReadFile(filePath))
return false;
DataBuffer buffer = Decompress(compressed, compressed.GetSize());
std::ifstream piecesFile(filePath, std::ios::binary); std::ifstream piecesFile(filePath, std::ios::binary);
if (!piecesFile.good()) { if (!piecesFile.good()) {
return false; return false;
@@ -76,10 +97,11 @@ bool PiecesFiles::loadPieces(int polyominoSize, std::vector<Piece>& pieces, std:
char xMask = 0b1111'0000; char xMask = 0b1111'0000;
char yMask = 0b0000'1111; char yMask = 0b0000'1111;
char infoByte; std::uint8_t infoByte;
int i = 0; int i = 0;
while (piecesFile.get(infoByte)) { while (!buffer.IsFinished()) {
if (piecesFile.eof()) break; // if (piecesFile.eof()) break;
buffer >> infoByte;
// read piece infos // read piece infos
bool isConvex = (infoByte & convexMask) >> 7; bool isConvex = (infoByte & convexMask) >> 7;
@@ -88,10 +110,10 @@ bool PiecesFiles::loadPieces(int polyominoSize, std::vector<Piece>& pieces, std:
// read positions // read positions
std::set<Position> piecePositions; std::set<Position> piecePositions;
char positionByte; std::uint8_t positionByte;
for (int i = 0; i < polyominoSize; i++) { for (int i = 0; i < polyominoSize; i++) {
piecesFile.get(positionByte); buffer >> positionByte;
int x = (positionByte & xMask) >> 4; int x = ((unsigned char) positionByte & xMask) >> 4;
int y = positionByte & yMask; int y = positionByte & yMask;
piecePositions.insert(Position{x, y}); piecePositions.insert(Position{x, y});
} }
@@ -119,6 +141,11 @@ bool PiecesFiles::loadPieces(int polyominoSize, std::vector<Piece>& pieces, std:
bool PiecesFiles::getFilePath(int polyominoSize, std::string& filePath) const { bool PiecesFiles::getFilePath(int polyominoSize, std::string& filePath) const {
std::string dataFolderPath = "data/pieces/"; std::string dataFolderPath = "data/pieces/";
if (!std::filesystem::exists(dataFolderPath)) {
std::filesystem::create_directories(dataFolderPath);
}
if (!std::filesystem::is_directory(dataFolderPath)) { if (!std::filesystem::is_directory(dataFolderPath)) {
return false; return false;
} }

View File

@@ -22,13 +22,18 @@ class PiecesFiles {
*/ */
bool savePieces(int polyominoSize) const; 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 * 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 * @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; 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 * 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 * @return If the data folder was found

View File

@@ -15,7 +15,7 @@ Polyomino::Polyomino(const std::set<Position>& positions) {
int maxX = INT_MIN; int maxX = INT_MIN;
int minY = INT_MAX; int minY = INT_MAX;
int maxY = INT_MIN; int maxY = INT_MIN;
for (Position position : positions) { for (const Position position : positions) {
if (position.x < minX) minX = position.x; if (position.x < minX) minX = position.x;
if (position.x > maxX) maxX = position.x; if (position.x > maxX) maxX = position.x;
if (position.y < minY) minY = position.y; if (position.y < minY) minY = position.y;
@@ -40,13 +40,13 @@ Polyomino::Polyomino(const std::set<Position>& positions, int length) :
void Polyomino::normalize() { void Polyomino::normalize() {
int minX = INT_MAX; int minX = INT_MAX;
int minY = INT_MAX; int minY = INT_MAX;
for (Position position : this->positions) { for (const Position position : this->positions) {
if (position.x < minX) minX = position.x; if (position.x < minX) minX = position.x;
if (position.y < minY) minY = position.y; if (position.y < minY) minY = position.y;
} }
std::set<Position> newPositions; std::set<Position> newPositions;
for (Position position : this->positions) { for (const Position position : this->positions) {
newPositions.insert(Position{position.x - minX, position.y - minY}); newPositions.insert(Position{position.x - minX, position.y - minY});
} }
this->positions = std::move(newPositions); this->positions = std::move(newPositions);
@@ -54,7 +54,7 @@ void Polyomino::normalize() {
void Polyomino::rotateCW() { void Polyomino::rotateCW() {
std::set<Position> newPositions; std::set<Position> newPositions;
for (Position position : this->positions) { for (const Position position : this->positions) {
newPositions.insert(Position{position.y, (length - 1) - (position.x)}); newPositions.insert(Position{position.y, (length - 1) - (position.x)});
} }
this->positions = std::move(newPositions); this->positions = std::move(newPositions);
@@ -62,7 +62,7 @@ void Polyomino::rotateCW() {
void Polyomino::rotate180() { void Polyomino::rotate180() {
std::set<Position> newPositions; std::set<Position> newPositions;
for (Position position : this->positions) { for (const Position position : this->positions) {
newPositions.insert(Position{(length - 1) - (position.x), (length - 1) - (position.y)}); newPositions.insert(Position{(length - 1) - (position.x), (length - 1) - (position.y)});
} }
this->positions = std::move(newPositions); this->positions = std::move(newPositions);
@@ -70,7 +70,7 @@ void Polyomino::rotate180() {
void Polyomino::rotateCCW() { void Polyomino::rotateCCW() {
std::set<Position> newPositions; std::set<Position> newPositions;
for (Position position : this->positions) { for (const Position position : this->positions) {
newPositions.insert(Position{(length - 1) - (position.y), position.x}); newPositions.insert(Position{(length - 1) - (position.y), position.x});
} }
this->positions = std::move(newPositions); this->positions = std::move(newPositions);
@@ -89,7 +89,7 @@ void Polyomino::goToSpawnPosition() {
} }
// calculates amount of squares per rows and columns // calculates amount of squares per rows and columns
for (Position position : this->positions) { for (const Position position : this->positions) {
linesCompleteness.at(0).at(position.y) += 1; // 0 = bottom to top = no rotation linesCompleteness.at(0).at(position.y) += 1; // 0 = bottom to top = no rotation
linesCompleteness.at(1).at((length - 1) - position.x) += 1; // 1 = right to left = CW linesCompleteness.at(1).at((length - 1) - position.x) += 1; // 1 = right to left = CW
linesCompleteness.at(2).at((length - 1) - position.y) += 1; // 2 = top to bottom = 180 linesCompleteness.at(2).at((length - 1) - position.y) += 1; // 2 = top to bottom = 180
@@ -158,14 +158,14 @@ void Polyomino::goToSpawnPosition() {
int minX = INT_MAX; int minX = INT_MAX;
int minY = INT_MAX; int minY = INT_MAX;
for (Position position : this->positions) { for (const Position position : this->positions) {
if (position.x < minX) minX = position.x; if (position.x < minX) minX = position.x;
if (position.y < minY) minY = position.y; if (position.y < minY) minY = position.y;
} }
// center the piece with an up bias // center the piece with an up bias
std::set<Position> newPositions; std::set<Position> newPositions;
for (Position position : positions) { for (const Position position : positions) {
newPositions.insert(Position{(position.x - minX) + (verticalEmptyLines / 2), (position.y - minY) + ((horizontalEmptyLines + 1) / 2)}); newPositions.insert(Position{(position.x - minX) + (verticalEmptyLines / 2), (position.y - minY) + ((horizontalEmptyLines + 1) / 2)});
} }
this->positions = std::move(newPositions); this->positions = std::move(newPositions);

View File

@@ -58,7 +58,7 @@ void TextApp::run() {
default : std::cout << "Invalid answer!" << std::endl; default : std::cout << "Invalid answer!" << std::endl;
} }
} }
std::cout << "===| SEE YA NEXT TIME! |==="; std::cout << "===| SEE YA NEXT TIME! |===" << std::endl;
} }
void TextApp::choosePieces() { void TextApp::choosePieces() {
@@ -136,7 +136,6 @@ void TextApp::seeKeybinds() const {
void TextApp::defaultKeybinds() { void TextApp::defaultKeybinds() {
this->keybinds.clear(); this->keybinds.clear();
this->keybinds.insert({"quit", QUIT});
this->keybinds.insert({"pause", PAUSE}); this->keybinds.insert({"pause", PAUSE});
this->keybinds.insert({"retry", RETRY}); this->keybinds.insert({"retry", RETRY});
this->keybinds.insert({"h", HOLD}); this->keybinds.insert({"h", HOLD});
@@ -171,15 +170,21 @@ void TextApp::startGame() const {
std::set<Action> playerActions; std::set<Action> playerActions;
std::set<Action> lastFrameActions; std::set<Action> lastFrameActions;
std::set<Action> metaActions; bool retrying = false;
for (std::string action : actions) { for (std::string action : actions) {
try { if (action == "quit") {
Action playerAction = this->keybinds.at(action); quit = true;
if (playerAction == PAUSE || playerAction == RETRY || playerAction == quit) { }
metaActions.insert(playerAction); else {
} try {
else { Action playerAction = this->keybinds.at(action);
if (playerAction == SOFT_DROP || playerAction == MOVE_LEFT || playerAction == MOVE_RIGHT) { if (playerAction == RETRY) {
retrying = true;
}
else if (playerAction == PAUSE) {
paused = (!paused);
}
else if (playerAction == SOFT_DROP || playerAction == MOVE_LEFT || playerAction == MOVE_RIGHT) {
playerActions.insert(playerAction); playerActions.insert(playerAction);
lastFrameActions.insert(playerAction); lastFrameActions.insert(playerAction);
} }
@@ -190,19 +195,12 @@ void TextApp::startGame() const {
playerActions.insert(playerAction); playerActions.insert(playerAction);
} }
} }
catch (std::exception ignored) {}
} }
catch (std::exception ignored) {}
} }
if (metaActions.contains(PAUSE)) { if (!paused && !quit) {
paused = (!paused); if (retrying) {
}
if (!paused) {
if (metaActions.contains(QUIT)) {
quit = true;
}
else if (metaActions.contains(RETRY)) {
game.reset(); game.reset();
game.start(); game.start();
} }
@@ -253,7 +251,7 @@ void TextApp::printGame(const Game& game) const {
for (int x = 0; x < game.getBoard().getWidth(); x++) { for (int x = 0; x < game.getBoard().getWidth(); x++) {
/* BOARD PRINTING */ /* BOARD PRINTING */
bool isActivePieceHere = (game.getActivePiece() != nullptr) && (game.getActivePiece()->getPositions().contains(Position{x, y} - game.getActivePiecePosition())); bool isActivePieceHere = (game.getActivePiece() != nullptr) && (game.getActivePiece()->getPositions().contains(Position{x, y} - game.getActivePiecePosition()));
bool isGhostPieceHere = (game.getActivePiece() != nullptr) && (game.getActivePiece()->getPositions().contains(Position{x, y} - game.ghostPiecePosition())); bool isGhostPieceHere = (game.getActivePiece() != nullptr) && (game.getActivePiece()->getPositions().contains(Position{x, y} - game.getGhostPiecePosition()));
Block block = (isActivePieceHere || isGhostPieceHere) ? game.getActivePiece()->getBlockType() : game.getBoard().getBlock(Position{x, y}); Block block = (isActivePieceHere || isGhostPieceHere) ? game.getActivePiece()->getBlockType() : game.getBoard().getBlock(Position{x, y});
if (isActivePieceHere || isGhostPieceHere) { if (isActivePieceHere || isGhostPieceHere) {

View File

@@ -3,45 +3,61 @@
#include "TextApp.h" #include "TextApp.h"
#include <chrono> #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 testGeneratorForOneSize(int size);
void testGeneratorByprintingAllNminos(int n); void printPiecesByTypesForOneSize(int size);
void testStoringAndRetrievingPieces(int size); void readStatsFromFilesForAllSizes(int max_size);
void generateFilesForAllSizes(int amount);
void generateFilesForOneSize(int size); void benchmarking(int min_size, int max_size);
void loadFromFilesForOneSize(int size);
void readStatsFromFilesForAllSizes(int amount);
int main(int argc, char** argv) { int main(int argc, char** argv) {
std::srand(std::time(NULL)); std::srand(std::time(NULL));
// dev: generate files if it hasn't been done before, UI will NOT generate the files #ifdef BENCHMARK
//generateFilesForAllSizes(10); #ifdef DEBUG
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; benchmarking(1, BENCHMARK_PIECES_SIZE);
UI.run(); #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; return 0;
} }
void testGeneratorForAllSizes(int amount) { void testGeneratorForAllSizes(int max_size) {
using std::chrono::high_resolution_clock; using std::chrono::high_resolution_clock;
using std::chrono::duration_cast; using std::chrono::duration_cast;
using std::chrono::duration; using std::chrono::duration;
using std::chrono::milliseconds; using std::chrono::milliseconds;
Generator generator; Generator generator;
for (int i = 1; i <= amount; i++) { for (int i = 1; i <= max_size; i++) {
auto t1 = high_resolution_clock::now(); auto t1 = high_resolution_clock::now();
std::vector<Polyomino> n_minos = generator.generatePolyominoes(i); std::vector<Polyomino> n_minos = generator.generatePolyominoes(i);
auto t2 = high_resolution_clock::now(); auto t2 = high_resolution_clock::now();
duration<double, std::milli> ms_double = t2 - t1; 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) { void printPiecesByTypesForOneSize(int size) {
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) {
PiecesFiles piecesFiles; PiecesFiles piecesFiles;
piecesFiles.savePieces(size);
std::vector<Piece> pieces; std::vector<Piece> pieces;
std::vector<int> convexPieces; std::vector<int> convexPieces;
@@ -104,79 +104,9 @@ void testStoringAndRetrievingPieces(int size) {
} }
} }
void generateFilesForAllSizes(int amount) { void readStatsFromFilesForAllSizes(int max_size) {
using std::chrono::high_resolution_clock;
using std::chrono::duration_cast;
using std::chrono::duration;
using std::chrono::milliseconds;
PiecesFiles piecesFiles; PiecesFiles piecesFiles;
for (int i = 1; i <= max_size; i++) {
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++) {
std::vector<Piece> pieces; std::vector<Piece> pieces;
std::vector<int> convexPieces; std::vector<int> convexPieces;
std::vector<int> holelessPieces; std::vector<int> holelessPieces;
@@ -189,3 +119,66 @@ void readStatsFromFilesForAllSizes(int amount) {
std::cout << "Others " << i << "-minos : " << otherPieces.size() << std::endl; 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,6 +1,8 @@
add_rules("mode.debug", "mode.release") add_rules("mode.debug", "mode.release")
add_requires("sfml 3.0.0") includes("xmake/bin2c.lua")
add_requires("sfml 3.0.0", "zlib")
set_languages("c++20") set_languages("c++20")
@@ -8,21 +10,44 @@ set_rundir(".")
target("core") target("core")
set_kind("$(kind)") set_kind("$(kind)")
add_files("src/Pieces/*.cpp") add_files("src/Pieces/*.cpp", "src/Core/*.cpp", "src/Common/*.cpp")
add_files("src/Core/*.cpp") add_packages("zlib")
target("text") target("text")
set_kind("binary")
set_default(false) set_default(false)
set_kind("binary")
add_files("./src/TextUI/*.cpp") add_files("./src/TextUI/*.cpp")
add_deps("core") add_deps("core")
target("bmark")
set_default(false)
set_kind("binary")
add_files("./src/TextUI/*.cpp")
add_deps("core")
add_defines("BENCHMARK")
target("graph") target("graph")
set_default(true)
add_rules("bin2c", {
extensions = {".png", ".ttf"},
outputSource = {"src/Utils/AssetManager.cpp"},
outputHeader = {"src/Utils/AssetManager.h"}
})
set_kind("binary") set_kind("binary")
add_files("./src/GraphicalUI/**.cpp") add_files("./src/GraphicalUI/**.cpp")
add_files("data/fonts/**.ttf", "data/images/**.png")
add_deps("core") add_deps("core")
add_packages("sfml") add_packages("sfml")
if is_mode("debug") then
add_defines("DEBUG")
end
if is_plat("mingw") then
add_ldflags("-static-libstdc++", "-static")
end
-- --
-- If you want to known more usage about xmake, please see https://xmake.io -- 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)