Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 90170cf719 | |||
| 484d3f9972 | |||
| a26d699388 | |||
| 23068b8e61 |
36
.github/workflows/ubuntu.yml
vendored
@@ -1,36 +0,0 @@
|
|||||||
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
@@ -10,10 +10,7 @@ build/
|
|||||||
|
|
||||||
# personnal documentation
|
# personnal documentation
|
||||||
doc/*.txt
|
doc/*.txt
|
||||||
doc/diagrams/*.violet.html
|
doc/*.violet.html
|
||||||
doc/mockups/*
|
|
||||||
|
|
||||||
# data files
|
# pieces files
|
||||||
data/pieces/*.bin
|
data/pieces/*.bin
|
||||||
data/config/*.bin
|
|
||||||
data/config/keybinds/*.bin
|
|
||||||
|
|||||||
4
.vscode/c_cpp_properties.json
vendored
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"configurations": [
|
"configurations": [
|
||||||
{
|
{
|
||||||
"name": "jminos",
|
"name": "Tetris",
|
||||||
"cppStandard": "c++20",
|
"cppStandard": "c++20",
|
||||||
"compileCommands": ".vscode/compile_commands.json"
|
"compileCommands": ".vscode/compile_commands.json"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"version": 4
|
"version": 4
|
||||||
}
|
}
|
||||||
151
README.md
@@ -1,151 +0,0 @@
|
|||||||
# jminos
|
|
||||||
|
|
||||||
Modern stacker game with every polyominoes from size 1 to 15, made in C++ with [SFML 3](https://www.sfml-dev.org/)!
|
|
||||||
|
|
||||||
- [Download](#download)
|
|
||||||
- [How to play](#how-to-play)
|
|
||||||
- [Features](#features)
|
|
||||||
- [Manual build](#manual-build)
|
|
||||||
- [Benchmarks](#benchmarks)
|
|
||||||
- [Credits](#credits)
|
|
||||||
|
|
||||||
## Download
|
|
||||||
|
|
||||||
You can download the latest release [here](https://git.ale-pri.com/TetrisNerd/jminos/releases)!
|
|
||||||
This game has been tested on Windows 11 and Linux, and releases are provided for theses two systems.
|
|
||||||
If your OS isn't compatible, 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
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Pieces select screen
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
AutoRS demonstration
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
0° spins demonstration
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
If you want to contribute or are simply curious, you can check the [wiki](https://git.ale-pri.com/TetrisNerd/jminos/wiki)!
|
|
||||||
|
|
||||||
### Build the project
|
|
||||||
|
|
||||||
``cd jminos``
|
|
||||||
|
|
||||||
``xmake``
|
|
||||||
|
|
||||||
If you need to change the toolchain (for example using gcc):
|
|
||||||
``xmake f --toolchain=gcc``
|
|
||||||
|
|
||||||
If you want to build for another platform (for example with mingw):
|
|
||||||
``xmake f -p mingw``
|
|
||||||
|
|
||||||
### Run the project
|
|
||||||
|
|
||||||
``xmake run``
|
|
||||||
|
|
||||||
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:
|
|
||||||
``xmake build text``
|
|
||||||
``xmake run text``
|
|
||||||
The command line version is **not** updated.
|
|
||||||
|
|
||||||
### Package the project
|
|
||||||
|
|
||||||
To package the executable manually properly, follow theses steps:
|
|
||||||
|
|
||||||
1. Clone the project into a new folder.
|
|
||||||
2. If you already have all 15 pieces files, copy them over to the ``data/pieces`` folder, else you will be generating them at the next step.
|
|
||||||
3. Go into release mode (``xmake f -m release``) and run the project once (``xmake`` and ``xmake run``). This is to generate the default config files, close the game immediately after it started.
|
|
||||||
4. Make a new folder named ``jminos`` and copy the executable (should be located somewhere like ``build/linux/x86_64/release/graph``) as well as the ``data/pieces`` and ``data/config`` folders.
|
|
||||||
5. Zip the newly created ``jminos`` folder into a file named ``jminos_{platform}``.
|
|
||||||
|
|
||||||
## Benchmarks
|
|
||||||
|
|
||||||
### One-sided n-polyominoes
|
|
||||||
|
|
||||||
| n | Number | Generation | File storing | File retrieving | File size |
|
|
||||||
| -: | -: | :-: | :-: | :-: | -: |
|
|
||||||
| 1 | 1 | 0s 0.005471ms | 0s 0.14436ms | 0s 0.022223ms | 3 bytes |
|
|
||||||
| 2 | 1 | 0s 0.006979ms | 0s 0.036624ms | 0s 0.011424ms | 4 bytes |
|
|
||||||
| 3 | 2 | 0s 0.018718ms | 0s 0.035885ms | 0s 0.013246ms | 9 bytes |
|
|
||||||
| 4 | 7 | 0s 0.060544ms | 0s 0.056277ms | 0s 0.019395ms | 36 bytes |
|
|
||||||
| 5 | 18 | 0s 0.220348ms | 0s 0.166593ms | 0s 0.036526ms | 76 bytes |
|
|
||||||
| 6 | 60 | 0s 0.773924ms | 0s 0.283423ms | 0s 0.063492ms | 186 bytes |
|
|
||||||
| 7 | 196 | 0s 3.00331ms | 0s 0.827344ms | 0s 0.163653ms | 546 bytes |
|
|
||||||
| 8 | 704 | 0s 13.142ms | 0s 3.68255ms | 0s 0.630044ms | 1 898 bytes |
|
|
||||||
| 9 | 2500 | 0s 50.9272ms | 0s 16.1929ms | 0s 2.35157ms | 6889 bytes |
|
|
||||||
| 10 | 9189 | 0s 204.031ms | 0s 87.1819ms | 0s 10.5841ms | 25302 bytes |
|
|
||||||
| 11 | 33896 | 0s 832.82ms | 0s 412.466ms | 0s 57.6399ms | 93711 bytes |
|
|
||||||
| 12 | 126759 | 3s 425.907ms | 1s 982.715ms | 0s 226.816ms | 350325 bytes |
|
|
||||||
| 13 | 476270 | 14s 570.595ms | 9s 945.511ms | 0s 972.036ms | 1327156 bytes |
|
|
||||||
| 14 | 1802312 | 56s 394.426ms | 41s 675.672ms | 4s 79.0436ms | 5035148 bytes |
|
|
||||||
| 15 | 6849777 | 258s 219.666ms | 223s 386.329ms | 16s 483.426ms | 19392417 bytes |
|
|
||||||
|
|
||||||
_File storing includes type checking and sorting all polyominoes before writing them to the file._
|
|
||||||
The files are compressed, they used to be about 5x as large.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
Special thanks to my friend [Simon](https://git.ale-pri.com/Persson-dev) who did most of the outside stuff (github actions, files compression, asset manager, xmake).
|
|
||||||
All the code in src/Common/, src/Utils/ and xmake/ comes from him.
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
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/ ]
|
|
||||||
|
Before Width: | Height: | Size: 619 B |
|
Before Width: | Height: | Size: 618 B |
|
Before Width: | Height: | Size: 604 B |
|
Before Width: | Height: | Size: 603 B |
|
Before Width: | Height: | Size: 591 B |
|
Before Width: | Height: | Size: 600 B |
|
Before Width: | Height: | Size: 624 B |
|
Before Width: | Height: | Size: 627 B |
|
Before Width: | Height: | Size: 622 B |
|
Before Width: | Height: | Size: 623 B |
|
Before Width: | Height: | Size: 606 B |
BIN
doc/class_diagramm_core.png
Normal file
|
After Width: | Height: | Size: 118 KiB |
BIN
doc/class_diagramm_pieces.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 164 KiB |
|
Before Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 13 KiB |
@@ -1,58 +0,0 @@
|
|||||||
# Files format
|
|
||||||
|
|
||||||
## Pieces
|
|
||||||
|
|
||||||
_Note: the current algorithm has been adapted to use file compression. There is currently no documentation on how the compression work, but you can check the code in the [src/Common](https://git.ale-pri.com/TetrisNerd/jminos/src/branch/main/src/Common) folder._
|
|
||||||
|
|
||||||
Generating polyominoes of size n is exponential in regard to n. Because of this, we will store the pieces beforehand and load them upon launching the game.
|
|
||||||
|
|
||||||
We want the pieces to be always sorted in the same order, always attributed the same block type, and always set at the same spawn position, no matter how they were generated. We also want them to be separated in 3 categories : convex, not convex but without a hole, and with a hole. Theses problematics are already solved internally, but will be calculated before storage as to not need extra calculcations upon load (except for the block type which is trivially computed).
|
|
||||||
|
|
||||||
Pieces are stored in binary files. Each file simply contains every polyomino of one size, one after the other. Since each file contains all polyominoes of the same size, we know how much stuff to read and don't need delimiters. We know we've read all pieces simply when we reach the end of file character.
|
|
||||||
|
|
||||||
Each piece is stored as follows:
|
|
||||||
- 1 byte for the characteristics of the piece: ``ABCCCCCC`` where A indicates if the piece is convex, B indicates if it has a hole, and C is the length of the piece
|
|
||||||
- 1 byte for each position: ``XXXXYYYY`` where X is the x coordinate of the position and Y is the y coordinate of the position
|
|
||||||
|
|
||||||
The current implementation only allows to generate polyominoes up to size 16, but can be upgraded by storing coordinates on 8 bits instead of 4.
|
|
||||||
It has been currently choosen to use pieces only up to size 15 for this game.
|
|
||||||
|
|
||||||
## Config
|
|
||||||
|
|
||||||
When compiling a release version, default files will be used, theses will then be impacted by the user and used for their next sessions.
|
|
||||||
|
|
||||||
### Keybinds
|
|
||||||
|
|
||||||
The games has 4 keyboard configs by default that never changes, and a modifiable 5th one, but theses are all stored the same.
|
|
||||||
|
|
||||||
Each keybinds files has the the following format:
|
|
||||||
|
|
||||||
- The number of the action (converted from an Enum), stored with 1 byte
|
|
||||||
- The number of each binded keys (also converted from an Enum), stored with 1 byte
|
|
||||||
- A separator characters which is 0xFF
|
|
||||||
|
|
||||||
_Repeat for every avaible actions._
|
|
||||||
|
|
||||||
### Settings
|
|
||||||
|
|
||||||
The settings file has the following format:
|
|
||||||
|
|
||||||
- The versions of the file format, stored with 1 byte
|
|
||||||
- The previous maximum pieces size selected, stored with 1 byte
|
|
||||||
- The number of the chosen keybinds (from 0 to 4), stored with 1 byte
|
|
||||||
- The DAS of the player, stored with 1 byte
|
|
||||||
- The ARR of the player, stored with 1 byte
|
|
||||||
- The SDR of the player, stored with 1 byte
|
|
||||||
- The window size mode, stored with 1 byte
|
|
||||||
- The master volume, stored with 1 byte
|
|
||||||
- The number of the last selected gamemode (converted from an Enum), stored with 1 byte
|
|
||||||
- The last selected width of the board, stored with 1 byte
|
|
||||||
- The last selected height of the board, stored with 1 byte
|
|
||||||
- The uniformity mode (0 for default distribution, 1 for uniformous distribution, 2 for custom distribution), stored with 1 byte
|
|
||||||
- For each size, store the custom proportion (from 0 to 20) (15x1 byte total)
|
|
||||||
- Every selected pieces
|
|
||||||
- For every groupe of piece (ALL, CONVEX, HOLELESS, OTHER), use 1 byte for the type (once again converted from an Enum) and 1 byte for the size
|
|
||||||
- For every single piece, use 1 byte for (the size + encoding that it is a single piece),
|
|
||||||
and 3 bytes to store the number of the piece (allows number up to 16M, size 15 has 6M pieces).
|
|
||||||
|
|
||||||
When starting the game, it will verify if the current settings file is in the latest format, if it is not it will be regenerated with default values.
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
# Game logic
|
# Game logic
|
||||||
|
|
||||||
This section will detail how the player's action are interpreted into the game.
|
This section will detail how the player's action are interpreted into the game.
|
||||||
We will only talk about pieces and not polyominoes. In this project, pieces are an abstraction of a polyomino.
|
We will only talk about pieces and not polyominos. In this project, pieces are an abstraction of a polyomino. Though if you want to know more the polyominos in this project, check [this other file](Pieces_representation.md).
|
||||||
|
|
||||||
## Order of operations
|
## Order of operations
|
||||||
|
|
||||||
@@ -12,7 +12,6 @@ Each frame, the UI will translate the user's input into a series of action to ap
|
|||||||
- Hold
|
- Hold
|
||||||
- Move left
|
- Move left
|
||||||
- Move right
|
- Move right
|
||||||
- Rotate 0°
|
|
||||||
- Rotate clockwise (CW)
|
- Rotate clockwise (CW)
|
||||||
- Rotate 180°
|
- Rotate 180°
|
||||||
- Rotate counter-clockwise (CCW)
|
- Rotate counter-clockwise (CCW)
|
||||||
@@ -25,39 +24,12 @@ Then the rest of actions will be tested in the list's order.
|
|||||||
Finally, gravity and lock delay are applied last.
|
Finally, gravity and lock delay are applied last.
|
||||||
Moving and soft dropping can be held but hard dropping, holding and rotating needs to be released and pressed again to happen.
|
Moving and soft dropping can be held but hard dropping, holding and rotating needs to be released and pressed again to happen.
|
||||||
|
|
||||||
Menu navigation is managed by the UI and uses static keybinds to prevent the user from softlocking themselves.
|
|
||||||
|
|
||||||
## ARR and DAS
|
|
||||||
|
|
||||||
The sidewise movement of the piece is defined by two parameters: DAS and ARR.
|
|
||||||
**DAS** stands for Delayed Auto Shift and **ARR** for Auto Repeat Rate.
|
|
||||||
Theses can be changed by the player.
|
|
||||||
The parameters will be refered to as DAS and ARR, while the in-game values will be refered to as the piece's DAS ans ARR.
|
|
||||||
|
|
||||||
- Initially, both the piece DAS and piece ARR are at 0. When moving the piece in one direction,
|
|
||||||
the piece will move one position and then wait for the player to hold that direction as long as the DAS value.
|
|
||||||
_So for exemple if DAS is set to 20, on the first frame piece DAS will be set to 1 and the piece will move once.
|
|
||||||
The next movement will be delayed by 20 frames, so the piece will move again 20 frames later, on the 21th frame, or when the piece DAS = (DAS +1)._
|
|
||||||
- Then, if the player still holds the same direction, ARR takes on.
|
|
||||||
The piece will move every ARR frames.
|
|
||||||
_So in our exemple if ARR is set to 5, this means the piece will move every 5 frames after the 21th frame.
|
|
||||||
So on the 21th frame piece DAS = 21 and piece ARR = 0.
|
|
||||||
On the 26th frame, piece ARR = 5 so the piece moves and piece ARR is set back to 0._
|
|
||||||
|
|
||||||
Now DAS can be buffered, that is if a direction is held before the piece spawn, it will have an initial DAS value corresponding to the number of frames held.
|
|
||||||
_So in our example where DAS = 20 and ARR = 5, if we held a direction during 30f then hold without releasing this direction, the piece will move instantly on the frame it spawns.
|
|
||||||
Then, ARR will start counting on the next frame. So the piece will move on frames 1, 6, 11, etc._
|
|
||||||
|
|
||||||
When trying to hold two directions at the same time, only the direction held last will be applied.
|
|
||||||
So for example if you were holding left and you suddendly start holding the two directions at the same time, the piece will go right.
|
|
||||||
In the two directions are first pressed at the same frame, left will take priority.
|
|
||||||
|
|
||||||
## Leniency mechanics
|
## Leniency mechanics
|
||||||
|
|
||||||
In a general sense, we try to be kind to the players. Some of the following mechanics are just standards in a lot of stacker games, while other have been added because of this game using polyominoes of high sizes and thus being way harder to play.
|
In a general sense, we try to be kind to the players. Some of the following mechanics are just standards in a lot of stacker games, while other have been added because of this game using polyominos of high sizes and thus being way harder to play.
|
||||||
|
|
||||||
When a piece touches the ground, there is a short time period before it automatically locks. This period is called _lock delay_. Lock delay is reset everytime the piece move. To not allow for infinite stalling, there is another longer period called _forced lock delay_ that does not reset when moving the piece.
|
When a piece touches the ground, there is a short time period before she automatically locks. This period is called _lock delay_. Lock delay is reset everytime the piece move. To not allow for infinite stalling, there is another longer period called _forced lock delay_ that does not reset when moving the piece.
|
||||||
The player has a hold box in which they can temporarly store a piece. In this game, we allow the player to swap between the held piece and the active piece as much as they want. Once again to not allow for infinite stalling, forced lock delay does not reset when holding a piece.
|
The player as a hold box in which they can temporarly store a piece. In this game, we allow the player to swap between the held piece and the active piece as much as they want. Once again to not allow for infinite stalling, forced lock delay does not reset when holding a piece.
|
||||||
|
|
||||||
If either holding or rotating happens during frames where no piece is in the board, they will be memorized and immediately applied upon spawning the next piece. This can sometime prevent the player from loosing when the default spawn would have lost the game. This is called IRS and IHS, for Instant Rotation/Hold System.
|
If either holding or rotating happens during frames where no piece is in the board, they will be memorized and immediately applied upon spawning the next piece. This can sometime prevent the player from loosing when the default spawn would have lost the game. This is called IRS and IHS, for Instant Rotation/Hold System.
|
||||||
IRS and IHS will fail if they actually loose the player the game when it would have not happened otherwise. In the same sense, holding always fails if it would loose the game.
|
IRS and IHS will fail if they actually loose the player the game when it would have not happened otherwise. In the same sense, holding always fails if it would loose the game.
|
||||||
@@ -66,57 +38,30 @@ IRS and IHS will fail if they actually loose the player the game when it would h
|
|||||||
|
|
||||||
A common mechanic of stacker games is kicking. This happen when rotating a piece makes it collide with a wall. The game will try to move the piece in a few predetermined spot close to the current position until one of them allow the piece to not be in a wall again. If no positions allow for that, the rotation is simply cancelled.
|
A common mechanic of stacker games is kicking. This happen when rotating a piece makes it collide with a wall. The game will try to move the piece in a few predetermined spot close to the current position until one of them allow the piece to not be in a wall again. If no positions allow for that, the rotation is simply cancelled.
|
||||||
|
|
||||||
This concept works very well for games with up to tetrominos or pentominos, but when using polyominoes of any size we can't choose a few predetermined spots for each piece.
|
This concept works very well for games with up to tetrominos or pentominos, but when using polyominos of any size we can't choose a few predetermined spots for each piece.
|
||||||
|
|
||||||
Since this game uses polyomino of high sizes which are very unplayable, we will try to be complaisant to the player and allow as much kicking spots as possible, while trying not to make him feel like the piece is going through walls. To solve this problem, this game introduce a new Rotation System called **AutoRS**, which does not have a predetermined list of spots to fit the piece but instead adapt to its shape. Its algorithm goes as follow:
|
Since this game uses polyomino of high sizes which are very unplayable, we will try to be complaisant to the player and allow as much kicking spots as possible, while trying not to make him feel like the piece is going through walls. To solve this problem, this game introduce a new Rotation System called **AutoRS**, which does not have a predetermined list of spots to fit the piece but instead adapt to its shape. Its algorithm goes as follow:
|
||||||
|
|
||||||
1. Before rotating, mark every position containing the piece or touching the piece, we will call the set of all theses positions the ``safePositions``
|
1. Before rotating, mark every cell containing the piece or touching the piece, we will call the set of all theses cells the ``safeCells``
|
||||||
2. Rotate the piece, if it fit stop the algorithm
|
2. Rotate the piece, if it fit stop the algorithm
|
||||||
3. Try fitting the piece, going from the center to the sides, that means we try to move the piece 1 position right, then 1 position left, then 2 position right, etc. until it fit (and then stop the algorithm), if at one point none of the squares of the pieces are in one of the ``safePositions`` we stop trying in this direction
|
3. Try fitting the piece, going from the center to the sides, that means we try to move the piece 1 cell right, then 1 cell left, then 2 cell right, etc. until it fit (and then stop the algorithm), if at one point a position doesn't touch one of the ``safeCells`` we stop trying in this direction
|
||||||
4. Move the piece one line down, and repeat step 3 again, until we hit a line were the first position (shifted by 0 positions horizontally) touched none of the ``safePositions``
|
4. Move the piece one line down, and repeat step 3 again, until we hit a line were the first position (shifted by 0 cells horizontally) touched none of the ``safeCells``
|
||||||
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
|
||||||
|
|
||||||
_Note: at step 3, the direction which is checked first is actually the last movement done by the player._
|
|
||||||
Kicking is primarly designed for rotating, but there is also a 0° rotation applied when a piece spawns into a wall.
|
|
||||||
0° rotations will first try to move the piece one position down, and if it can't, try kicking the piece, only registering a kick if the piece couldn't move down.
|
|
||||||
|
|
||||||
## Detecting spins
|
## Detecting spins
|
||||||
|
|
||||||
Another common mechanic of stacker games that goes alongside kicking is spinning. A spin is a special move (a move is calculated once a piece has been locked to the board) which usually happen when the last move a piece did was a kick or a rotation and the piece is locked in place or is locked in certain corners, but the rules varies a lot from game to game.
|
Another common mechanic of stacker games that goes alongside kicking is spinning. A spin is a special move (a move is calculated once a piece has been locked to the board) which usually happen when the last move a piece did was a kick or a rotation and the piece is locked in place, but the rules varies a lot from game to game.
|
||||||
|
|
||||||
Since we work with a great deal of different size and shapes, the rules for spin dectection have been simplified greatly:
|
Since we deal with a great deal of different size and shapes, the rules for spin dectection have been simplified greatly:
|
||||||
|
|
||||||
- A move is a _spin_ if the piece is locked in place, that is it can't be moved one position up, down, left or right without hitting a wall
|
- A move is a _spin_ if the piece is locked in place, that is it can't be moved one cell up, down, left or right without hitting a wall
|
||||||
- A move is a _mini spin_ if the move isn't a spin and the last action of the piece was a kick (dropping down because of gravity counts as an action)
|
- A move is a _mini spin_ if the move isn't a spin and the last action of the piece was a kick (dropping down because of gravity counts as an action)
|
||||||
|
|
||||||
## Score calculation
|
## Score calculation
|
||||||
|
|
||||||
- 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 cell soft dropped, add 1 to the score
|
||||||
|
- For every cell 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.
|
|
||||||
|
|||||||
@@ -1,110 +1,108 @@
|
|||||||
# Pieces representation
|
# Pieces representation
|
||||||
|
|
||||||
## What are polyominoes ?
|
## What are polyominos ?
|
||||||
|
|
||||||
In this game, pieces are represented as a polyomino and a block type.
|
In this game, pieces are represented as a polyomino with a color.
|
||||||
Polyominoes are mathematical objects consisting of multiple edge-touching squares.
|
Polyominos are mathematical objects consisting of multiple edge-touching squares.
|
||||||
There must be a path from every square to every other square, going from square to square only through the sides and not the corners.
|
There must be a path from every cell to every other cell, going from square to square only through the sides and not the corners.
|
||||||
Polyominoes can be classified in 3 ways:
|
Polyominos can be classified in 3 ways:
|
||||||
|
|
||||||
- Fixed polyominoes : only translation is allowed
|
- Fixed polyominos : only translation is allowed
|
||||||
- One-sided polyominoes : only translation and rotation are allowed
|
- One-sided polyominos : only translation and rotation are allowed
|
||||||
- Free polyomins : translation, rotation, and reflection are allowed
|
- Free polyomins : translation, rotation, and reflection are allowed
|
||||||
|
|
||||||
For more detailed informations about polyominoes, check the [Wikipedia page](https://en.wikipedia.org/wiki/Polyomino).
|
For more detailed informations about polyominos, check the [Wikipedia page](https://en.wikipedia.org/wiki/Polyomino).
|
||||||
|
|
||||||
Most stacker game uses all one-sided polyominoes of size 4 (called tetrominos), which results in 7 distinct polyominoes.
|
Most stacker game uses one-sided polyominos, which results in 7 polyominos of size 4, also known as tetrominos.
|
||||||
In this game too, one-sided polyominoes will be used since we will only allow to move and rotate the pieces.
|
In this game too, one-sided polyominos will be used since we will only allow to move and rotate the pieces.
|
||||||
|
|
||||||
Internally, polyominoes are represented as a set of positions on a 2D grid.
|
Internally, Polyominos are represented as a set of Cell.
|
||||||
This means that 2 polyominoes of same shape but at different places will be interpreted as different polyominoes.
|
This means the cells can be in any order but can't be duplicates.
|
||||||
To solve this, when doing equality checks, we normalize the polyominoes so that their left-most column is at x=0 and their bottom-most row at y=0.
|
A cell is simply a position on a 2D grid, so a polyomino is determined by the position of its cells.
|
||||||
|
This means however that 2 polyominos of same shape but different positions will be interpreted as different polyominos.
|
||||||
|
To solve this, we normalize the position of the polyominos so that their left-most column is at x=0 and their bottom-most row at y=0.
|
||||||
|
|
||||||
Even if there is only 7 one-sided 4-minos, there is already more than 9,000 one-sided 10-minos.
|
Even if there is only 7 one-sided 4-minos, there is already more than 9,000 one-sided 10-minos.
|
||||||
Since the aim of this game is to be playable with polyominoes of any size, listing all polyominoes manually isn't viable.
|
Since the aim of this game is to be playable with polyominos of any size, listing all polyominos manually isn't viable.
|
||||||
We will need a way to automatically:
|
We will need a way to:
|
||||||
|
|
||||||
1. Generate all one-sided polyominoes of size n
|
1. Generate all one-sided polyominos of size n
|
||||||
2. Set their spawn positions and rotation centers
|
2. Set their spawn positions and rotation centers
|
||||||
|
|
||||||
Aditionally, for this game we will also a want a way to separate the polyominoes into multiple categories, specifically to allow removing those with holes who are harder to play with.
|
Aditionally, for this game we will also a want a way to separate the polyominos into multiple categories, specifically to allow removing those with holes who are more unplayable.
|
||||||
|
|
||||||
## Ordering the polyominoes
|
## Ordering the polyominos
|
||||||
|
|
||||||
For practical reasons, we want to be able to sort all polyominoes of the same size.
|
For practical reasons, we want to be able to sort all polyominos of the same size.
|
||||||
But to sort objects we need a way to compare them.
|
This is done very simply:
|
||||||
|
|
||||||
When a polyomino is created, an attribute named its length is computed. This is simply the max between its width and its height. Thus the polyomino can be inscribed in a box of size ``length``.
|
|
||||||
We will now assume that our polyominoes are always inscribed in a box of origin (0,0) and size ``length`` (which should always be the case under normal circumstances).
|
|
||||||
|
|
||||||
We can now compare polyominoes using this method:
|
|
||||||
|
|
||||||
- If one polyomino has an inferior length than another, it is deemed inferior
|
- If one polyomino has an inferior length than another, it is deemed inferior
|
||||||
- If two polyomino have the same length, we check all the positions of their box, from left to right, and repeating from up to bottom, for the first position where a polyomino has a square and the another doesn't, the present polyomino which has a square is deemed inferior
|
- If two polyomino have the same length, we check all the cells of their square, from left to right, and repeating from up to bottom, for the first position where a polyomino has a cell that another doesn't, the polyomino with the cell is deemed inferior
|
||||||
|
|
||||||
A nice side-effect is that, once the polyomino are ordered, it is very simple to attribute them a block type, we can simply iterate through the list while looping over the block list.
|
Once the polyomino are ordered, it is very simple to attribute them a color, we can simply iterate through the list while looping over the color list.
|
||||||
|
|
||||||
## 1. Generating polyominoes
|
## 1. Generating polyominos
|
||||||
|
|
||||||
The method used to generate polyominoes is similar to the [inductive method](https://en.wikipedia.org/wiki/Polyomino#Inductive_algorithms) described in the previously mentionned Wikipedia page.
|
The method used to generate polyominos is similar to the [inductive method](https://en.wikipedia.org/wiki/Polyomino#Inductive_algorithms) described in the previously mentionned Wikipedia page.
|
||||||
|
|
||||||
The algorithm is the following:
|
The algorithm is the following:
|
||||||
|
|
||||||
1. Add a single square at position (0,0), and number it with 0
|
1. Add a single cell at position (0,0), and number it with 0
|
||||||
2. Call the generator function
|
2. Call the generator function
|
||||||
1. If we get a polyomino of the size we want:
|
1. If we get a polyomino of the size we want:
|
||||||
1. We rotate it in its 4 possible rotations and sort them
|
1. We rotate it in its 4 possible rotations and sort them
|
||||||
2. If the polyomino was generated in its lowest rotation, we add it to the list, else we discard it
|
2. If the polyomino was generated in its lowest rotation, we add it to the list, else we discard it
|
||||||
3. Stop this instance of the function (the function is recursive, see step 2.3.2)
|
3. Stop this instance of the function (the function is recursive, see step 2.3.2)
|
||||||
2. Else we number each adjacent square to the polyomino with a number higher than the last numbered square, unless:
|
2. Else we number each adjacent cell to the polyomino with a number higher than the last numbered cell, unless:
|
||||||
1. If a square was already numbered then we don't touch it
|
1. If a cell was already numbered then we don't touch it
|
||||||
2. If a square is on top of the polyomino then we don't number it
|
2. If a cell is on top of the polyomino then we don't number it
|
||||||
3. If a square is below y=0, or at exactly x=0 and y<0, then we don't number it
|
3. If a cell is below y=0, or at exactly x=0 and y<0, then we don't number it
|
||||||
3. For each square with a higher number than the last added one:
|
3. For each cell with a higher number than the last added one:
|
||||||
1. We add this square to the polyomino
|
1. We add this cell to the polyomino
|
||||||
2. We call the generator function (recursive function!)
|
2. We call the generator function (recursive function!)
|
||||||
3. We remove this square from the polyomino
|
3. We remove this cell from the polyomino
|
||||||
3. Return the list of polyominoes
|
3. Return the list of polyominos
|
||||||
|
|
||||||
The exact number of one-sided polyominoes up to size 15 (and higher, but we only generated up to size 15) is known, and this method generated exactly theses numbers, without duplicates.
|
The exact number of one-sided polyominos up to size 12 (and higher, but we only generated up to size 12) is known, and this method generated exactly theses numbers, without duplicates.
|
||||||
|
|
||||||
By marking squares and adding only ones that have a higher number than the last one everytime, we generate each fixed polyomino exactly n times. By ignoring the squares below y=0, or at exactly x=0 and y<0, we generate each fixed polyomino exactly 1 time.
|
By marking cells and adding only ones that have a higher number than the last one everytime, we generate each fixed polyomino exactly n times. By ignoring the cells below y=0, or at exactly x=0 and y<0, we generate each fixed polyomino exactly 1 time.
|
||||||
An one-sided polyomino has 4 rotations, some of which can be the same if the polyomino has symmetries. 2 rotations that are the same corresponds to 1 fixed polyomino, we will refer to theses 2 rotation as 1 "unique" rotation.
|
An one-sided polyomino has 4 rotations, some of which can be the same if the polyomino has symmetries. 2 rotations that are the same corresponds to 1 fixed polyomino, we will refer to theses 2 rotation as 1 "unique" rotation.
|
||||||
Because we generate every fixed polyomino exactly 1 time, that means each "unique" rotation of an one-sided polyomino is generated exactly one-time, which includes its lowest rotation. That's how we can get away with only checking the rotation of the polyomino and not comparing it to the rest of the generated polyominoes.
|
Because we generate every fixed polyomino exactly 1 time, that means each "unique" rotation of an one-sided polyomino is generated exactly one-time, which includes its lowest rotation. That's how we can get away with only checking the rotation of the polyomino and not comparing it to the rest of the generated polyominos.
|
||||||
|
|
||||||
## 2. Setting the spawn position of polyominoes
|
## 2. Setting the spawn position of polyominos
|
||||||
|
|
||||||
Since we assume the polyomino is always inscribed in a box at origin (0,0), we can use formulae close to matrix rotations to rotate the piece:
|
When a polyomino is created, an attribute named its length is computed. This is simply the max between its width and its height. Thus the polyomino can be inscribed in a square of size ``length``.
|
||||||
|
So now we can assume the polyomino is always inscribed in a square of origin (0,0) and size ``length`` (which should always be the case under normal circumstances). This make the rotation center very easy to find as it is simply the center of this square, and rotating is as simple as rotating a matrix:
|
||||||
|
|
||||||
- Clockwise (CW) rotation : ``x, y = y, (length - 1) - x``
|
- Clockwise (CW) rotation : ``x, y = y, (length - 1) - x``
|
||||||
- 180° rotation : ``x, y = (length - 1) - x, (length - 1) - y``
|
- 180° rotation : ``x, y = (length - 1) - x, (length - 1) - y``
|
||||||
- Counter-clockwise (CCW) rotation : ``x, y = (length - 1) - y, x``
|
- Counter-clockwise (CCW) rotation : ``x, y = (length - 1) - y, x``
|
||||||
|
|
||||||
_Note: we set the origin at the bottom-left corner instead of the up-left corner in a matrix, so the formulae aren't exactly the same._
|
_Note we set the origin at the bottom-left corner instead of the up-left corner in a matrix, so the formulae aren't exactly the same._
|
||||||
|
|
||||||
The second challenge comes in finding a normalized spawn position for pieces. To do this, we first need to find which rotation the piece needs to spawn on, and then center it in the middle of its box.
|
The second challenge comes in finding a normalized spawn position for pieces. To do this, we first need to find which rotation the piece needs to spawn on, and then center it in the middle of its square.
|
||||||
The very arbitrary rules used in this game for finding the spawn rotation is the following: **we want to find the side which is both the widest and the flattest**.
|
For the rotation, **we want to find the side which is both the widest and the flattest**.
|
||||||
**Widest** means that we prefer if the piece is oriented horizontally rather than vertically.
|
**Widest** means that we prefer if the piece is oriented horizontally rather than vertically.
|
||||||
**Flattest** means we prefer the side with the most square at the bottom of the piece.
|
**Flattest** means we prefer the side with the most cell at the bottom of the piece.
|
||||||
The current algorithm for doing so is the following:
|
The current algorithm for doing so is the following:
|
||||||
|
|
||||||
1. Check if the polyomino has more lines horizontally or vertically, this will determine either 2 or 4 sides which are wider than the others, and the others will be discarded
|
1. Check if the polyomino has more lines horizontally or vertically, this will determine either 2 or 4 sides which are widest than the others, and the others will be discarded
|
||||||
2. For each potential side check the number of square on the first line, if one has more than every others, then this side is choosed, else we only keep the sides that tied and repeat for the next line
|
2. For each potential side check the number of cell on the first line, if one has more than every others, then this side is choosed, else we only keep the sides that tied and repeat for the next line
|
||||||
3. If we still have at least 2 sides tied, we do the same again but check the flatness of the side to the left instead, this will make the pieces overall have more squares to their left at spawn
|
3. If we still have at least 2 sides tied, we do the same again but check the flatness of the side to the left instead, this will make the pieces overall have more cells to their left at spawn
|
||||||
4. If there is still no winner, we sort the remaining sides (by simulating having them selectionned and then sorting the resulting polyominoes) and keep the lowest
|
4. If there is still no winner, we sort the remaining sides (by simulating having them selectionned and then sort the resulting polyominos) and keep the lowest
|
||||||
5. We rotate the piece so that the chosen side ends up at the bottom of the polyomino
|
5. We rotate the piece so that the chosen side ends up at the bottom of the polyomino
|
||||||
6. We center the polyomino inside its box, since we chose the widest side it will always fill the width of the square, but for the height it can be asymmetric, in that case we place it one line closer to the top than the bottom
|
6. We center the polyomino inside its square, since we chose the widest side it will always fill the width of the square, but for the height it can be asymmetric, in that case we place it one line closer to the top than the bottom
|
||||||
|
|
||||||
_Note we could actually just skip straight to step 4 because it always give the same orientation, but we want the spawn rotations to follow somewhat of a logic. Step 1 to 3 actually already works for 99% of polyominoes and they constrain step 4 too by preselecting certain sides._
|
_Note we could actually just skip straight to step 4 because it always give the same orientation, but we want the spawn rotations to follow somewhat of a logic. Step 1 to 3 actually already works for 99% of polyominos and they constrain step 4 too by preselecting certain sides._
|
||||||
|
|
||||||
## 3. Separating the polyominoes into categories
|
## 3. Separating the polyominos into categories
|
||||||
|
|
||||||
For this game, we want the polyominoes to be broken down into 3 categories:
|
For this game, we want the polyominos to be broken down into 3 categories:
|
||||||
|
|
||||||
- Convex: this is said of a polyomino where every row and column is formed of at most one continous line of squares
|
- Convex: this is said of a polyomino where every row and column is formed of at most one continous line of cells
|
||||||
- Holeless: the polyominoes which are neither convex nor have a hole
|
- Holeless: the polyominos which are neither convex nor have a hole
|
||||||
- Others: the polyominoes who have a hole (thoses are by definition not convex)
|
- Others: the polyominos who have a hole (thoses are by definition not convex)
|
||||||
|
|
||||||
To check for convexity, we simply iterate trough each row and column, and check if all the squares are contiguous.
|
To check for convexity, we simply iterate trough each row and column, and check cell by cell if they are all contiguous.
|
||||||
|
|
||||||
To check for holes, we list every empty squares starting from the exterior of the box of the polyomino, then add every adjacent empty square recursively. If the polyomino has an hole then there is at least one empty square we could not attaign, and since we know the size of the square and of the polyomino, we can compute wheter we have the right number of empty squares.
|
To check for holes, we list every empty cells starting from the exterior of the square of the polyomino, then add every adjacent empty cell recursively. If the cell has an hole then there is at least one empty cell we could not attaign, and since we know the size of the square and of the polyomino, we can compute wheter we have the right number of empty cells.
|
||||||
|
|||||||
20
doc/pieces_storage.md
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# Pieces storage
|
||||||
|
|
||||||
|
## What is stored
|
||||||
|
|
||||||
|
If you don't know what a polyomino is, check [this other file](Pieces_representation.md#what-are-polyominos).
|
||||||
|
|
||||||
|
Generating polyominos of size n is exponential in regard to n. Because of this, we will store the pieces beforehand and load them upon launching the game.
|
||||||
|
|
||||||
|
We want the pieces to be always sorted in the same order, always attributed the same color, and always set at the same spawn position, no matter how they were generated. We also want them to be separated in 3 categories : convex, not convex but wihtout a hole, and with a hole. Theses problematics are already resolved internally, but will be calculated before storage as to not need extra calculcations upon load.
|
||||||
|
|
||||||
|
## How is it stored
|
||||||
|
|
||||||
|
Pieces are stored in binary files. Each file simply contains every polyomino of one size, one after the other. Since each file contains all polyominos of the same size, we know how much stuff to read and don't need delimiters. We know we've read all pieces simply when we reach the end of file character.
|
||||||
|
|
||||||
|
Each piece is stored as follows:
|
||||||
|
- 1 byte for the length of the piece
|
||||||
|
- 1 byte for the other characteristics of the piece: ``ABCCCCCC`` where A indicates if the piece is convex, B indicates if it has a hole, and C is the color number of the piece
|
||||||
|
- 1 byte for each cell: ``XXXXYYYY`` where X is the x coordinate of the cell and Y is the y coordinate of the cell
|
||||||
|
|
||||||
|
The current implementation only allows to generate polyominos up to size 16, but can be upgraded by storing coordinates on 8 bits instead of 4. It has been choosen to use pieces only up to size 15 for this game.
|
||||||
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 614 KiB |
|
Before Width: | Height: | Size: 779 KiB |
@@ -1,102 +0,0 @@
|
|||||||
#include "../Pieces/Generator.h"
|
|
||||||
#include "../Pieces/PiecesFiles.h"
|
|
||||||
|
|
||||||
#include <chrono>
|
|
||||||
#include <filesystem>
|
|
||||||
#include <cmath>
|
|
||||||
|
|
||||||
static const int BENCHMARK_PIECES_SIZE = 15;
|
|
||||||
|
|
||||||
void benchmarking(int min_size, int max_size);
|
|
||||||
|
|
||||||
|
|
||||||
int main(int argc, char** argv) {
|
|
||||||
std::srand(std::time(NULL));
|
|
||||||
|
|
||||||
#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
|
|
||||||
|
|
||||||
benchmarking(1, BENCHMARK_PIECES_SIZE);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
auto t1 = high_resolution_clock::now();
|
|
||||||
auto t2 = high_resolution_clock::now();
|
|
||||||
duration<double, std::milli> ms_double;
|
|
||||||
bool ok;
|
|
||||||
|
|
||||||
for (int i = min_size; i <= max_size; i++) {
|
|
||||||
std::cout << "| " << i;
|
|
||||||
|
|
||||||
t1 = high_resolution_clock::now();
|
|
||||||
std::vector<Polyomino> polyominoes = gen.generatePolyominoes(i);
|
|
||||||
t2 = high_resolution_clock::now();
|
|
||||||
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();
|
|
||||||
ok = 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);
|
|
||||||
|
|
||||||
if (!ok) {
|
|
||||||
std::cerr << "\nERROR: Could not save pieces to file." << std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
ok = 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);
|
|
||||||
|
|
||||||
if (!ok) {
|
|
||||||
std::cerr << "\nERROR: Could not load pieces from file." << std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
#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());
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
#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);
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
#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;
|
|
||||||
}
|
|
||||||
@@ -1,284 +0,0 @@
|
|||||||
#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);
|
|
||||||
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
#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(¤tByte, 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;
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
#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);
|
|
||||||
};
|
|
||||||
@@ -1,10 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The list of in-game actions that can be taken by the player
|
* The list of actions that can be taken by the player
|
||||||
*/
|
*/
|
||||||
enum Action {
|
enum Action {
|
||||||
PAUSE,
|
PAUSE,
|
||||||
@@ -14,45 +12,7 @@ enum Action {
|
|||||||
HARD_DROP,
|
HARD_DROP,
|
||||||
MOVE_LEFT,
|
MOVE_LEFT,
|
||||||
MOVE_RIGHT,
|
MOVE_RIGHT,
|
||||||
ROTATE_0,
|
|
||||||
ROTATE_CW,
|
ROTATE_CW,
|
||||||
ROTATE_180,
|
ROTATE_180,
|
||||||
ROTATE_CCW
|
ROTATE_CCW
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
static const Action ACTION_LIST_IN_ORDER[] = { // the list of possible actions in a sorted order
|
|
||||||
MOVE_LEFT,
|
|
||||||
MOVE_RIGHT,
|
|
||||||
SOFT_DROP,
|
|
||||||
HARD_DROP,
|
|
||||||
ROTATE_CW,
|
|
||||||
ROTATE_CCW,
|
|
||||||
ROTATE_180,
|
|
||||||
ROTATE_0,
|
|
||||||
HOLD,
|
|
||||||
PAUSE,
|
|
||||||
RETRY
|
|
||||||
};
|
|
||||||
static const std::string ACTION_NAMES[] = { // name representation for each actions
|
|
||||||
"Pause",
|
|
||||||
"Retry",
|
|
||||||
"Hold",
|
|
||||||
"Soft drop",
|
|
||||||
"Hard drop",
|
|
||||||
"Move left",
|
|
||||||
"Move right",
|
|
||||||
"Rotate 0",
|
|
||||||
"Rotate CW",
|
|
||||||
"Rotate 180",
|
|
||||||
"Rotate CCW"
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stream output operator, adds the name of the action
|
|
||||||
* @return A reference to the output stream
|
|
||||||
*/
|
|
||||||
inline std::ostream& operator<<(std::ostream& os, const Action action) {
|
|
||||||
os << ACTION_NAMES[action];
|
|
||||||
return os;
|
|
||||||
}
|
|
||||||
|
|||||||
106
src/Core/Bag.cpp
@@ -1,110 +1,50 @@
|
|||||||
#include "Bag.h"
|
#include "Bag.h"
|
||||||
|
|
||||||
#include "../Pieces/Piece.h"
|
#include "../Pieces/Piece.h"
|
||||||
#include "PiecesList.h"
|
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#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::vector<Piece>& pieces) : pieces(pieces) {
|
||||||
Bag::Bag(const std::shared_ptr<PiecesList>& piecesList) :
|
// initialize bags
|
||||||
piecesList(piecesList) {
|
this->currentBag.clear();
|
||||||
|
for (int i = 0; i < this->pieces.size(); i++) {
|
||||||
this->highestSize = this->piecesList->getHighestLoadedSize();
|
this->currentBag.push_back(i);
|
||||||
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().getSize();
|
|
||||||
this->currentBags.at(pieceSize).push_back(piece);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this->prepareNext();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Bag::jumpToNextBag() {
|
|
||||||
for (int i = 0; i < this->currentBags.size(); i++) {
|
|
||||||
if (this->currentBags.at(i).size() < this->nextBags.at(i).size()) {
|
|
||||||
std::swap(this->currentBags.at(i), this->nextBags.at(i));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto& piece : this->nextBags.at(i)) {
|
|
||||||
this->currentBags.at(i).push_back(piece);
|
|
||||||
}
|
|
||||||
this->nextBags.at(i).clear();
|
|
||||||
}
|
}
|
||||||
|
this->nextBag.clear();
|
||||||
|
|
||||||
|
// prepare first piece
|
||||||
this->prepareNext();
|
this->prepareNext();
|
||||||
}
|
}
|
||||||
|
|
||||||
Piece Bag::lookNext() {
|
Piece Bag::lookNext() {
|
||||||
return this->piecesList->getPiece(this->next);
|
// return the next piece
|
||||||
|
return this->pieces.at(this->next);
|
||||||
}
|
}
|
||||||
|
|
||||||
Piece Bag::getNext() {
|
Piece Bag::getNext() {
|
||||||
std::pair<int, int> nextIndex = this->next;
|
// get the piece to return
|
||||||
|
int nextIndex = this->next;
|
||||||
|
|
||||||
|
// prepare the piece even after the next
|
||||||
this->prepareNext();
|
this->prepareNext();
|
||||||
|
|
||||||
return this->piecesList->getPiece(nextIndex);
|
// return the next piece
|
||||||
|
return this->pieces.at(nextIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Bag::prepareNext() {
|
void Bag::prepareNext() {
|
||||||
if (this->distributionMode == DEFAULT) {
|
// if the bag is empty switch to the next bag
|
||||||
this->getNextPieceFromBag(0);
|
if (this->currentBag.empty()) {
|
||||||
|
std::swap(this->currentBag, this->nextBag);
|
||||||
}
|
}
|
||||||
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;
|
// pick a random piece from the current bag
|
||||||
}
|
int indexIndex = std::rand() % this->currentBag.size();
|
||||||
}
|
this->next = this->currentBag.at(indexIndex);
|
||||||
}
|
|
||||||
|
|
||||||
int nextSizeIndex = std::rand() % this->sizesBag.size();
|
// move the piece over to the next bag
|
||||||
int nextSize = this->sizesBag.at(nextSizeIndex);
|
this->nextBag.push_back(this->next);
|
||||||
this->sizesBag.erase(this->sizesBag.begin() + nextSizeIndex);
|
this->currentBag.erase(this->currentBag.begin() + indexIndex);
|
||||||
|
|
||||||
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->currentBags.at(bagIndex).size();
|
|
||||||
this->next = this->currentBags.at(bagIndex).at(indexIndex);
|
|
||||||
|
|
||||||
this->nextBags.at(bagIndex).push_back(this->next);
|
|
||||||
this->currentBags.at(bagIndex).erase(this->currentBags.at(bagIndex).begin() + indexIndex);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "../Pieces/Piece.h"
|
#include "../Pieces/Piece.h"
|
||||||
#include "PiecesList.h"
|
|
||||||
#include "DistributionMode.h"
|
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <memory>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
using PieceBag = std::vector<std::pair<int, int>>;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -16,48 +10,30 @@ using PieceBag = std::vector<std::pair<int, int>>;
|
|||||||
*/
|
*/
|
||||||
class Bag {
|
class Bag {
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<PiecesList> piecesList; // the list of loaded pieces
|
std::vector<Piece> pieces; // the pieces the bag can dispense
|
||||||
int highestSize; // the highest size of piece in the bag
|
int next; // the next piece to give
|
||||||
std::vector<std::pair<int, int>> selectedPieces; // the list of pieces that can be given to the player
|
std::vector<int> currentBag; // the list of pieces that are still to be taken out before starting a new bag
|
||||||
DistributionMode distributionMode; // the distribution mode
|
std::vector<int> nextBag; // the list of pieces that have been taken out of the current bag and have been placed in the next
|
||||||
std::vector<double> propotionsPerSize; // the proportion of pieces for each size
|
|
||||||
std::pair<int, int> next; // the next piece to give
|
|
||||||
std::vector<PieceBag> currentBags; // for each size, the list of pieces that are still to be taken out before starting a new bag
|
|
||||||
std::vector<PieceBag> nextBags; // for each size, the list of pieces that have been taken out of the current bag and have been placed in the next
|
|
||||||
std::vector<int> sizesBag; // the list each of bags that are still to have a piece taken out of them
|
|
||||||
std::vector<double> sizesProgression; // how close each size is to meet its quota of pieces
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* Creates a new bag with the pieces currently selected in the piece list
|
* Creates a new bag of the specified list of pieces
|
||||||
*/
|
*/
|
||||||
Bag(const std::shared_ptr<PiecesList>& piecesList);
|
Bag(const std::vector<Piece>& pieces);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ignores the remaining pieces in the current bag and start fresh from a new bag
|
* Looks at what the next picked piece will be
|
||||||
*/
|
|
||||||
void jumpToNextBag();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Looks at what the next picked piece will be, without removing it from the bag
|
|
||||||
* @return The next piece
|
|
||||||
*/
|
*/
|
||||||
Piece lookNext();
|
Piece lookNext();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Picks a new piece from the current bag, removing it from the bag
|
* Picks a new piece from the current bag
|
||||||
* @return The next piece
|
|
||||||
*/
|
*/
|
||||||
Piece getNext();
|
Piece getNext();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* Prepares the next picked piece in advance
|
* Prepare the next picked piece in advance
|
||||||
*/
|
*/
|
||||||
void prepareNext();
|
void prepareNext();
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the next picked piece from the specified bag
|
|
||||||
*/
|
|
||||||
void getNextPieceFromBag(int bagIndex);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,63 +7,59 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
|
|
||||||
Board::Board(int width, int height) :
|
Board::Board(int width, int height) : width(width), height(height) {
|
||||||
width(width),
|
std::vector<ColorEnum> emptyRow;
|
||||||
height(height) {
|
|
||||||
|
|
||||||
this->emptyRow = std::vector<Block>(width);
|
|
||||||
for (int i = 0; i < width; i ++) {
|
for (int i = 0; i < width; i ++) {
|
||||||
this->emptyRow.push_back(NOTHING);
|
emptyRow.push_back(NOTHING);
|
||||||
}
|
}
|
||||||
|
|
||||||
this->clearBoard();
|
// initialize grid
|
||||||
|
this->grid.clear();
|
||||||
|
for (int j = 0; j < height; j++) {
|
||||||
|
this->grid.push_back(emptyRow);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Board::changeBlock(const Position& position, Block block) {
|
void Board::addBlock(const Cell& position, ColorEnum block) {
|
||||||
if (position.x < 0 || static_cast<unsigned>(position.x) >= this->width || position.y < 0) return;
|
// if the block is out of bounds we discard it
|
||||||
|
if (position.x < 0 || position.x >= this->width || position.y < 0) return;
|
||||||
|
|
||||||
// resize the grid if needed
|
// resize the grid if needed
|
||||||
if (static_cast<unsigned>(position.y) >= this->grid.size()) {
|
if (position.y >= this->grid.size()) {
|
||||||
|
std::vector<ColorEnum> emptyRow;
|
||||||
|
for (int i = 0; i < width; i ++) {
|
||||||
|
emptyRow.push_back(NOTHING);
|
||||||
|
}
|
||||||
for (int j = this->grid.size(); j <= position.y; j++) {
|
for (int j = this->grid.size(); j <= position.y; j++) {
|
||||||
this->grid.push_back(this->emptyRow);
|
this->grid.push_back(emptyRow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// change the block in the grid
|
||||||
this->grid.at(position.y).at(position.x) = block;
|
this->grid.at(position.y).at(position.x) = block;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Board::insertRow(int height, int holePosition, Block blockType) {
|
int Board::clearRows() {
|
||||||
std::vector<Block> insertedRow;
|
std::vector<ColorEnum> emptyRow;
|
||||||
for (unsigned i = 0; i < this->width; i++) {
|
for (int i = 0; i < width; i ++) {
|
||||||
if (i == holePosition) {
|
emptyRow.push_back(NOTHING);
|
||||||
insertedRow.push_back(NOTHING);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
insertedRow.push_back(blockType);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this->grid.insert(this->grid.begin() + height, insertedRow);
|
// check from top to bottom
|
||||||
}
|
|
||||||
|
|
||||||
int Board::clearRows() {
|
|
||||||
// check from top to bottom, so that erasing lines don't screw up the looping
|
|
||||||
int clearedLines = 0;
|
int clearedLines = 0;
|
||||||
for (int j = this->grid.size() - 1; j >= 0; j--) {
|
for (int j = this->grid.size() - 1; j >= 0; j--) {
|
||||||
bool lineIsFull = true;
|
// check if a line has a block on every column
|
||||||
unsigned i = 0;
|
bool isFull = true;
|
||||||
while (lineIsFull && (i < width)) {
|
for (int i = 0; i < this->width; i++) {
|
||||||
if (this->grid.at(j).at(i) == NOTHING) {
|
if (this->grid.at(j).at(i) == NOTHING) {
|
||||||
lineIsFull = false;
|
isFull = false;
|
||||||
}
|
}
|
||||||
i++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lineIsFull) {
|
// if it has, erase it and add a new row at the top
|
||||||
|
if (isFull) {
|
||||||
this->grid.erase(this->grid.begin() + j);
|
this->grid.erase(this->grid.begin() + j);
|
||||||
if(this->grid.size() < this->height) {
|
if(this->grid.size() < height) this->grid.push_back(emptyRow);
|
||||||
this->grid.push_back(this->emptyRow);
|
|
||||||
}
|
|
||||||
clearedLines++;
|
clearedLines++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,29 +67,23 @@ int Board::clearRows() {
|
|||||||
return clearedLines;
|
return clearedLines;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Board::clearBoard() {
|
ColorEnum Board::getBlock(const Cell& position) const {
|
||||||
this->grid.clear();
|
// if the block is out of bounds
|
||||||
for (int j = 0; j < this->height; j++) {
|
if (position.x < 0 || position.x >= this->width || position.y < 0)
|
||||||
this->grid.push_back(this->emptyRow);
|
return OUT_OF_BOUNDS;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Block Board::getBlock(const Position& position) const {
|
// if the block is higher than the current grid, since it can grow indefinitely we do as if it was there but empty
|
||||||
if (position.x < 0 || position.x >= this->width || position.y < 0) return OUT_OF_BOUNDS;
|
if (position.y >= this->grid.size())
|
||||||
|
return NOTHING;
|
||||||
if (position.y >= this->grid.size()) return NOTHING;
|
|
||||||
|
|
||||||
|
// else get the color in the grid
|
||||||
return this->grid.at(position.y).at(position.x);
|
return this->grid.at(position.y).at(position.x);
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<std::vector<Block>>& Board::getBlocks() const {
|
std::vector<std::vector<ColorEnum>> Board::getBlocks() const {
|
||||||
return this->grid;
|
return this->grid;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Board::getWidth() const {
|
|
||||||
return this->width;
|
|
||||||
}
|
|
||||||
|
|
||||||
int Board::getGridHeight() const {
|
int Board::getGridHeight() const {
|
||||||
return this->grid.size();
|
return this->grid.size();
|
||||||
}
|
}
|
||||||
@@ -102,11 +92,16 @@ int Board::getBaseHeight() const {
|
|||||||
return this->height;
|
return this->height;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int Board::getWidth() const {
|
||||||
|
return this->width;
|
||||||
|
}
|
||||||
|
|
||||||
std::ostream& operator<<(std::ostream& os, const Board& board) {
|
std::ostream& operator<<(std::ostream& os, const Board& board) {
|
||||||
|
// print the board
|
||||||
for (int y = board.grid.size() - 1; y >= 0; y--) {
|
for (int y = board.grid.size() - 1; y >= 0; y--) {
|
||||||
for (int x = 0; x < board.width; x++) {
|
for (int x = 0; x < board.width; x++) {
|
||||||
Block block = board.grid.at(y).at(x);
|
ColorEnum block = board.grid.at(y).at(x);
|
||||||
os << getConsoleColorCode(block);
|
os << getColorCode(block);
|
||||||
if (block != NOTHING) {
|
if (block != NOTHING) {
|
||||||
os << "*";
|
os << "*";
|
||||||
}
|
}
|
||||||
@@ -117,7 +112,8 @@ std::ostream& operator<<(std::ostream& os, const Board& board) {
|
|||||||
os << std::endl;
|
os << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
os << getResetConsoleColorCode();
|
// reset console color
|
||||||
|
os << getColorCode(NOTHING);
|
||||||
|
|
||||||
return os;
|
return os;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,10 +11,9 @@
|
|||||||
*/
|
*/
|
||||||
class Board {
|
class Board {
|
||||||
private:
|
private:
|
||||||
std::vector<std::vector<Block>> grid; // the grid, (0,0) is downleft
|
std::vector<std::vector<ColorEnum>> grid; // the grid, (0,0) is downleft
|
||||||
std::vector<Block> emptyRow; // an empty row of blocks
|
int width; // the width of the grid
|
||||||
unsigned width; // the width of the grid
|
int height; // the base height of the grid, which can extends indefinitely
|
||||||
unsigned height; // the base height of the grid, which can extend indefinitely
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
@@ -23,54 +22,42 @@ class Board {
|
|||||||
Board(int width, int height);
|
Board(int width, int height);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Changes the block at the specified position, if the block is out of bounds it is simply ignored
|
* Change the color of the specified block, if the block is out of bounds it is simply ignored
|
||||||
*/
|
*/
|
||||||
void changeBlock(const Position& position, Block block);
|
void addBlock(const Cell& position, ColorEnum block);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inserts a row of the specified block type (unless on the specified column that has a hole), at the specified height
|
* Clears any complete row and moves down the rows on top, returns the number of cleared rows
|
||||||
*/
|
|
||||||
void insertRow(int height, int holePosition, Block blockType);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clears any complete row and moves down the rows on top
|
|
||||||
* @return The number of cleared rows
|
|
||||||
*/
|
*/
|
||||||
int clearRows();
|
int clearRows();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes any block currently on the board
|
* Returns the color of the block at the specified position
|
||||||
*/
|
*/
|
||||||
void clearBoard();
|
ColorEnum getBlock(const Cell& position) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The block at the specified position
|
* Returns a copy of the grid
|
||||||
*/
|
*/
|
||||||
Block getBlock(const Position& position) const;
|
std::vector<std::vector<ColorEnum>> getBlocks() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The grid
|
* Returns the actual height of the grid
|
||||||
*/
|
|
||||||
const std::vector<std::vector<Block>>& getBlocks() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The width of the grid
|
|
||||||
*/
|
|
||||||
int getWidth() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The actual height of the grid
|
|
||||||
*/
|
*/
|
||||||
int getGridHeight() const;
|
int getGridHeight() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The base height of the grid
|
* Returns the base height of the grid
|
||||||
*/
|
*/
|
||||||
int getBaseHeight() const;
|
int getBaseHeight() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the width of the grid
|
||||||
|
*/
|
||||||
|
int getWidth() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stream output operator, adds a 2D grid representing the board
|
* Stream output operator, adds a 2D grid representing the board
|
||||||
* @return A reference to the output stream
|
|
||||||
*/
|
*/
|
||||||
friend std::ostream& operator<<(std::ostream& os, const Board& board);
|
friend std::ostream& operator<<(std::ostream& os, const Board& board);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
#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];
|
|
||||||
}
|
|
||||||
@@ -4,59 +4,45 @@
|
|||||||
#include "GameParameters.h"
|
#include "GameParameters.h"
|
||||||
#include "Action.h"
|
#include "Action.h"
|
||||||
|
|
||||||
#include <set>
|
#include <vector>
|
||||||
#include <algorithm>
|
|
||||||
#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 LOCK_DELAY_SCORE = 1; // the score gained when the piece locks due to lock delay
|
static const int SOFT_DROP_SCORE = 1; // the score gained by line soft dropped
|
||||||
static const int HARD_DROP_SCORE = 10; // the score gained when the piece locks due to an hard drop
|
static const int HARD_DROP_SCORE = 2; // the score gained by line hard dropped
|
||||||
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)
|
||||||
|
|
||||||
|
|
||||||
Game::Game(Gamemode gamemode, const Player& controls, int boardWidth, int boardHeight, const std::shared_ptr<PiecesList>& piecesList) :
|
Game::Game(Gamemode gamemode, const Player& controls, int boardWidth, int boardHeight, const std::vector<Piece>& bag) : parameters(gamemode, controls), board(boardWidth, boardHeight, bag, parameters.getNextQueueLength()) {
|
||||||
parameters(gamemode, controls),
|
// the game has not yet started
|
||||||
board(boardWidth, boardHeight, piecesList, parameters.getNextQueueLength()) {
|
|
||||||
|
|
||||||
this->initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Game::start() {
|
|
||||||
this->started = true;
|
|
||||||
this->leftARETime = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Game::reset() {
|
|
||||||
this->initialize();
|
|
||||||
|
|
||||||
this->parameters.reset();
|
|
||||||
this->board.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Game::initialize() {
|
|
||||||
this->started = false;
|
this->started = false;
|
||||||
this->lost = false;
|
this->lost = false;
|
||||||
|
|
||||||
|
// initialize stats
|
||||||
this->score = 0;
|
this->score = 0;
|
||||||
this->framesPassed = 0;
|
this->framesPassed = 0;
|
||||||
this->B2BChain = 0;
|
this->B2BChain = 0;
|
||||||
|
|
||||||
|
// nothing happened yet
|
||||||
this->heldActions.clear();
|
this->heldActions.clear();
|
||||||
this->initialActions.clear();
|
this->initialActions.clear();
|
||||||
this->heldDAS = 0;
|
this->heldDAS = 0;
|
||||||
this->heldARR = 0;
|
this->heldARR = 0;
|
||||||
this->subVerticalPosition = 0;
|
this->subVerticalPosition = 0;
|
||||||
|
this->leftARETime = 0;
|
||||||
this->totalLockDelay = 0;
|
this->totalLockDelay = 0;
|
||||||
this->totalForcedLockDelay = 0;
|
this->totalForcedLockDelay = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Game::start() {
|
||||||
|
this->started = true;
|
||||||
|
this->lost = this->board.spawnNextPiece();
|
||||||
|
}
|
||||||
|
|
||||||
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,75 +51,57 @@ void Game::nextFrame(const std::set<Action>& playerActions) {
|
|||||||
|
|
||||||
if (this->leftARETime == 0) {
|
if (this->leftARETime == 0) {
|
||||||
if (AREJustEnded) {
|
if (AREJustEnded) {
|
||||||
this->lost = this->board.spawnNextPiece();
|
this->board.spawnNextPiece();
|
||||||
this->resetPiece(true);
|
}
|
||||||
|
|
||||||
/* IRS and IHS */
|
/* IRS and IHS */
|
||||||
bool initialRotated = (this->initialActions.contains(ROTATE_0) || this->initialActions.contains(ROTATE_CW)
|
Rotation initialRotation = NONE
|
||||||
|| this->initialActions.contains(ROTATE_180) || this->initialActions.contains(ROTATE_CCW));
|
+ (this->initialActions.contains(ROTATE_CW)) ? CLOCKWISE : NONE
|
||||||
Rotation initialRotation = NONE
|
+ (this->initialActions.contains(ROTATE_180)) ? DOUBLE : NONE
|
||||||
+ ((this->initialActions.contains(ROTATE_CW)) ? CLOCKWISE : NONE)
|
+ (this->initialActions.contains(ROTATE_CCW)) ? COUNTERCLOCKWISE : 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->board.hold(initialRotation);
|
if (this->board.hold(initialRotation)) {
|
||||||
|
this->subVerticalPosition = 0;
|
||||||
|
this->totalLockDelay = 0;
|
||||||
|
this->heldARR = 0;
|
||||||
}
|
}
|
||||||
else {
|
}
|
||||||
if (initialRotated) {
|
else {
|
||||||
this->board.rotate(initialRotation);
|
if (initialRotation != NONE) {
|
||||||
}
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 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->subVerticalPosition = 0;
|
||||||
|
this->totalLockDelay = 0;
|
||||||
|
this->heldARR = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* MOVE LEFT/RIGHT */
|
/* MOVE LEFT/RIGHT */
|
||||||
Position before = this->board.getActivePiecePosition();
|
if (playerActions.contains(MOVE_LEFT)) {
|
||||||
|
this->movePiece(-1, (this->heldDAS >= 0));
|
||||||
if (this->heldDAS >= 0) {
|
|
||||||
if (playerActions.contains(MOVE_LEFT) && (!heldActions.contains(MOVE_LEFT))) this->movePiece(-1);
|
|
||||||
else if (playerActions.contains(MOVE_RIGHT)) this->movePiece(1);
|
|
||||||
else this->heldDAS = 0;
|
|
||||||
}
|
}
|
||||||
else if (this->heldDAS < 0) {
|
if (playerActions.contains(MOVE_RIGHT)) {
|
||||||
if (playerActions.contains(MOVE_RIGHT) && (!heldActions.contains(MOVE_RIGHT))) this->movePiece(1);
|
this->movePiece(1, (this->heldDAS <= 0));
|
||||||
else if (playerActions.contains(MOVE_LEFT)) this->movePiece(-1);
|
|
||||||
else this->heldDAS = 0;
|
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
if (before != this->board.getActivePiecePosition()) {
|
this->heldDAS = 0;
|
||||||
this->totalLockDelay = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ROTATIONS */
|
/* ROTATIONS */
|
||||||
if (playerActions.contains(ROTATE_0) && (!this->heldActions.contains(ROTATE_0))) {
|
|
||||||
this->rotatePiece(NONE);
|
|
||||||
}
|
|
||||||
if (playerActions.contains(ROTATE_CW) && (!this->heldActions.contains(ROTATE_CW))) {
|
if (playerActions.contains(ROTATE_CW) && (!this->heldActions.contains(ROTATE_CW))) {
|
||||||
this->rotatePiece(CLOCKWISE);
|
this->board.rotate(CLOCKWISE);
|
||||||
}
|
}
|
||||||
if (playerActions.contains(ROTATE_180) && (!this->heldActions.contains(ROTATE_180))) {
|
if (playerActions.contains(ROTATE_180) && (!this->heldActions.contains(ROTATE_180))) {
|
||||||
this->rotatePiece(DOUBLE);
|
this->board.rotate(DOUBLE);
|
||||||
}
|
}
|
||||||
if (playerActions.contains(ROTATE_CCW) && (!this->heldActions.contains(ROTATE_CCW))) {
|
if (playerActions.contains(ROTATE_CCW) && (!this->heldActions.contains(ROTATE_CCW))) {
|
||||||
this->rotatePiece(COUNTERCLOCKWISE);
|
this->board.rotate(COUNTERCLOCKWISE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* SOFT DROP */
|
/* SOFT DROP */
|
||||||
@@ -142,14 +110,16 @@ 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -157,28 +127,23 @@ 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 {
|
||||||
/* GRAVITY */
|
/* GRAVITY */
|
||||||
if (this->parameters.getLevel() >= 20) {
|
// parameters.getGravity() gives the gravity for an assumed 20-line high board
|
||||||
while (this->board.moveDown());
|
int appliedGravity = this->parameters.getGravity() * (this->board.getBoard().getBaseHeight() / 20.0);
|
||||||
}
|
|
||||||
else {
|
|
||||||
// parameters.getGravity() gives the gravity for an assumed 20-line high board
|
|
||||||
int appliedGravity = this->parameters.getGravity() * std::max((double) this->board.getBoard().getBaseHeight() / 20.0, 1.0);
|
|
||||||
|
|
||||||
this->subVerticalPosition += appliedGravity;
|
this->subVerticalPosition += appliedGravity;
|
||||||
while (this->subVerticalPosition >= SUBPX_PER_ROW) {
|
while (this->subVerticalPosition >= SUBPX_PER_ROW) {
|
||||||
this->subVerticalPosition -= SUBPX_PER_ROW;
|
this->subVerticalPosition -= SUBPX_PER_ROW;
|
||||||
this->board.moveDown();
|
this->board.moveDown();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* LOCK DELAY */
|
/* LOCK DELAY */
|
||||||
if (this->board.touchesGround()) {
|
if (this->board.touchesGround()) {
|
||||||
this->totalLockDelay++;
|
this->totalLockDelay++;
|
||||||
@@ -190,85 +155,59 @@ 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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// remove initial actions only once they've been applied
|
||||||
if (AREJustEnded) {
|
if (AREJustEnded) {
|
||||||
this->initialActions.clear();
|
this->initialActions.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this->framesPassed++;
|
this->framesPassed++;
|
||||||
if (this->lost) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// update remembered actions
|
||||||
if ((!this->started) || this->leftARETime > 0) {
|
if ((!this->started) || this->leftARETime > 0) {
|
||||||
for (Action action : playerActions) {
|
for (Action action : playerActions) {
|
||||||
if ((!pieceJustLocked) && (!heldActions.contains(action))) {
|
this->initialActions.insert(action);
|
||||||
this->initialActions.insert(action);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->heldDAS >= 0) {
|
|
||||||
if (playerActions.contains(MOVE_LEFT)) this->heldDAS = -1;
|
|
||||||
else if (playerActions.contains(MOVE_RIGHT)) this->heldDAS++;
|
|
||||||
else this->heldDAS = 0;
|
|
||||||
}
|
|
||||||
else if (this->heldDAS < 0) {
|
|
||||||
if (playerActions.contains(MOVE_RIGHT)) this->heldDAS = +1;
|
|
||||||
else if (playerActions.contains(MOVE_LEFT)) this->heldDAS--;
|
|
||||||
else this->heldDAS = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this->heldActions = playerActions;
|
this->heldActions = playerActions;
|
||||||
|
|
||||||
|
if (playerActions.contains(MOVE_LEFT)) {
|
||||||
|
this->heldDAS = std::min(-1, this->heldDAS - 1);
|
||||||
|
}
|
||||||
|
if (playerActions.contains(MOVE_RIGHT)) {
|
||||||
|
this->heldDAS = std::max(1, this->heldDAS + 1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this->heldDAS = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Game::resetPiece(bool newPiece) {
|
void Game::movePiece(int movement, bool resetDirection) {
|
||||||
int appliedDAS = this->parameters.getDAS();
|
if (resetDirection) {
|
||||||
|
|
||||||
this->subVerticalPosition = 0;
|
|
||||||
this->totalLockDelay = 0;
|
|
||||||
if (newPiece) {
|
|
||||||
this->totalForcedLockDelay = 0;
|
|
||||||
}
|
|
||||||
if (abs(this->heldDAS) > appliedDAS) {
|
|
||||||
this->heldDAS = (this->heldDAS > 0) ? (+appliedDAS) : (-appliedDAS);
|
|
||||||
}
|
|
||||||
this->heldARR = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Game::movePiece(int movement) {
|
|
||||||
int appliedDAS = this->parameters.getDAS();
|
|
||||||
int appliedARR = this->parameters.getARR();
|
|
||||||
|
|
||||||
if ((this->heldDAS * movement) <= 0) {
|
|
||||||
this->heldDAS = movement;
|
this->heldDAS = movement;
|
||||||
this->heldARR = 0;
|
this->heldARR = 0;
|
||||||
if (movement == -1) this->board.moveLeft();
|
|
||||||
if (movement == 1) this->board.moveRight();
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this->heldDAS += movement;
|
this->heldDAS += movement;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (abs(this->heldDAS) > appliedDAS) {
|
if (abs(this->heldDAS) > this->parameters.getDAS()) {
|
||||||
|
int appliedARR = this->parameters.getARR();
|
||||||
|
|
||||||
// ARR=0 -> instant movement
|
// ARR=0 -> instant movement
|
||||||
if (appliedARR == 0) {
|
if (appliedARR == 0) {
|
||||||
if (movement == -1) while (this->board.moveLeft());
|
if (movement == -1) while (this->board.moveLeft());
|
||||||
if (movement == 1) while (this->board.moveRight());
|
if (movement == 1) while (this->board.moveRight());
|
||||||
}
|
}
|
||||||
|
|
||||||
// ARR>1 -> move by specified amount
|
// ARR>1 -> move by specified amount
|
||||||
else {
|
else {
|
||||||
if (abs(this->heldDAS) > appliedDAS + 1) {
|
this->heldARR++;
|
||||||
this->heldARR++;
|
if (this->heldARR == appliedARR) {
|
||||||
}
|
|
||||||
if ((this->heldARR == appliedARR) || (abs(this->heldDAS) == (appliedDAS + 1))) {
|
|
||||||
this->heldARR = 0;
|
this->heldARR = 0;
|
||||||
if (movement == -1) this->board.moveLeft();
|
if (movement == -1) this->board.moveLeft();
|
||||||
if (movement == 1) this->board.moveRight();
|
if (movement == 1) this->board.moveRight();
|
||||||
@@ -277,119 +216,84 @@ void Game::movePiece(int movement) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Game::rotatePiece(Rotation rotation) {
|
|
||||||
Position before = this->board.getActivePiecePosition();
|
|
||||||
|
|
||||||
if (this->board.rotate(rotation)) {
|
|
||||||
this->totalLockDelay = 0;
|
|
||||||
if (before != this->board.getActivePiecePosition()) {
|
|
||||||
this->subVerticalPosition = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Game::lockPiece() {
|
void Game::lockPiece() {
|
||||||
LineClear clear = this->board.lockPiece();
|
LineClear clear = this->board.lockPiece();
|
||||||
this->parameters.lockedPiece(clear);
|
this->parameters.clearLines(clear.lines);
|
||||||
|
|
||||||
|
// update B2B and score
|
||||||
|
bool B2BConditions = ((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 / 2;
|
long int clearScore = LINE_CLEAR_BASE_SCORE;
|
||||||
clearScore = clearScore << (clear.lines << clear.isSpin);
|
clearScore = clearScore << (clear.lines << (clear.isSpin));
|
||||||
|
|
||||||
if (this->B2BChain && B2BConditionsAreMet) {
|
if (this->B2BChain && B2BConditions) clearScore *= B2B_SCORE_MULTIPLIER;
|
||||||
clearScore *= B2B_SCORE_MULTIPLIER;
|
|
||||||
}
|
|
||||||
this->score += clearScore;
|
this->score += clearScore;
|
||||||
|
|
||||||
this->B2BChain = B2BConditionsAreMet;
|
|
||||||
}
|
}
|
||||||
|
this->B2BChain = B2BConditions;
|
||||||
|
|
||||||
|
// reset active piece
|
||||||
|
this->subVerticalPosition = 0;
|
||||||
|
this->totalLockDelay = 0;
|
||||||
|
this->totalForcedLockDelay = 0;
|
||||||
|
this->heldARR = 0;
|
||||||
|
|
||||||
if (!this->hasWon()) {
|
// check for ARE
|
||||||
this->leftARETime = this->parameters.getARE();
|
this->leftARETime = this->parameters.getARE();
|
||||||
if (this->leftARETime == 0) {
|
if (this->leftARETime == 0) {
|
||||||
this->lost = this->board.spawnNextPiece();
|
this->board.spawnNextPiece();
|
||||||
this->resetPiece(true);
|
}
|
||||||
|
|
||||||
if (this->lost) {
|
|
||||||
this->board.rotate(NONE);
|
|
||||||
this->lost = this->board.activePieceInWall();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Game::hasWon() const {
|
bool Game::hasWon() {
|
||||||
return this->parameters.hasWon(this->framesPassed);
|
return this->parameters.hasWon(this->framesPassed);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Game::hasLost() const {
|
bool Game::hasLost() {
|
||||||
return this->lost;
|
return this->lost;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Game::getClearedLines() const {
|
int Game::getClearedLines() {
|
||||||
return this->parameters.getClearedLines();
|
return this->parameters.getClearedLines();
|
||||||
}
|
}
|
||||||
|
|
||||||
int Game::getGrade() const {
|
int Game::getLevel() {
|
||||||
return this->parameters.getGrade();
|
|
||||||
}
|
|
||||||
|
|
||||||
int Game::getLevel() const {
|
|
||||||
return this->parameters.getLevel();
|
return this->parameters.getLevel();
|
||||||
}
|
}
|
||||||
|
|
||||||
int Game::getFramesPassed() const {
|
int Game::getFramesPassed() {
|
||||||
return this->framesPassed;
|
return this->framesPassed;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Game::getScore() const {
|
int Game::getScore() {
|
||||||
return this->score;
|
return this->score;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Game::isOnB2BChain() const {
|
bool Game::isOnB2BChain() {
|
||||||
return this->B2BChain;
|
return this->B2BChain;
|
||||||
}
|
}
|
||||||
|
|
||||||
float Game::getLockDelayProgression() const {
|
bool Game::areBlocksBones() {
|
||||||
return (float) this->totalLockDelay / this->parameters.getLockDelay();
|
|
||||||
}
|
|
||||||
|
|
||||||
float Game::getForcedLockDelayProgression() const {
|
|
||||||
return (float) this->totalForcedLockDelay / this->parameters.getForcedLockDelay();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Game::areBlocksBones() const {
|
|
||||||
return this->parameters.getBoneBlocks();
|
return this->parameters.getBoneBlocks();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Game::isBoardInvisible() const {
|
Board Game::getBoard() {
|
||||||
return this->parameters.getInvisibleBoard();
|
|
||||||
}
|
|
||||||
|
|
||||||
const Board& Game::getBoard() const {
|
|
||||||
return this->board.getBoard();
|
return this->board.getBoard();
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::shared_ptr<Piece>& Game::getActivePiece() const {
|
Piece Game::getActivePiece() {
|
||||||
return this->board.getActivePiece();
|
return this->board.getActivePiece();
|
||||||
}
|
}
|
||||||
|
|
||||||
const Position& Game::getActivePiecePosition() const {
|
Cell Game::getActivePiecePosition() {
|
||||||
return this->board.getActivePiecePosition();
|
return this->board.getActivePiecePosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
Position Game::getGhostPiecePosition() const {
|
Piece Game::getHeldPiece() {
|
||||||
return this->board.lowestPosition();
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::shared_ptr<Piece>& Game::getHeldPiece() const {
|
|
||||||
return this->board.getHeldPiece();
|
return this->board.getHeldPiece();
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<Piece>& Game::getNextPieces() const {
|
std::vector<Piece> Game::getNextPieces() {
|
||||||
return this->board.getNextPieces();
|
return this->board.getNextPieces();
|
||||||
}
|
}
|
||||||
|
|||||||
141
src/Core/Game.h
@@ -5,7 +5,6 @@
|
|||||||
#include "Action.h"
|
#include "Action.h"
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -13,27 +12,27 @@
|
|||||||
*/
|
*/
|
||||||
class Game {
|
class Game {
|
||||||
private:
|
private:
|
||||||
GameParameters parameters; // the current parameters of the game
|
GameParameters parameters; // the current parameters of the game
|
||||||
GameBoard board; // the board in which the game is played
|
GameBoard board; // the board in which the game is played
|
||||||
bool started; // wheter the game has started
|
bool started; // wheter the game has started
|
||||||
bool lost; // wheter the game is lost
|
bool lost; // wheter the game is lost
|
||||||
long int score; // the current score
|
long int score; // the current score
|
||||||
int framesPassed; // how many frames have passed since the start of the game
|
int framesPassed; // how many frames have passed since the start of the game
|
||||||
bool B2BChain; // wheter the player is currently on a B2B chain
|
bool B2BChain; // wheter the player is currently on a B2B chain
|
||||||
std::set<Action> heldActions; // the list of actions that were pressed last frame
|
std::set<Action> heldActions; // the list of actions that were pressed last frame
|
||||||
std::set<Action> initialActions; // the list of actions that have been pressed while there was no active piece
|
std::set<Action> initialActions; // the list of actions that have been pressed while there was no active piece
|
||||||
int heldDAS; // the number of frames DAS has been held, positive for right or negative for left
|
int heldDAS; // the number of frames DAS has been held, positive for right or negative for left
|
||||||
int heldARR; // the number of frames ARR has been held
|
int heldARR; // the number of frames ARR has been held
|
||||||
int subVerticalPosition; // how far the active piece is to go down one line
|
int subVerticalPosition; // how far the active piece is to go down one line
|
||||||
int leftARETime; // how many frames are left before ARE period finishes
|
int leftARETime; // how many frames are left before ARE period finishes
|
||||||
int totalLockDelay; // how many frames has the active piece touched the ground without moving
|
int totalLockDelay; // how many frames has the active piece touched the ground without moving
|
||||||
int totalForcedLockDelay; // how many frames the active piece has touched the ground since the last spawned piece
|
int totalForcedLockDelay; // how many frames the active piece has touched the ground since the last spawned piece
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* Initialize the parameters and creates a new board
|
* Initialize the parameters and creates a new board
|
||||||
*/
|
*/
|
||||||
Game(Gamemode gamemode, const Player& controls, int boardWidth, int boardHeight, const std::shared_ptr<PiecesList>& piecesList);
|
Game(Gamemode gamemode, const Player& controls, int boardWidth, int boardHeight, const std::vector<Piece>& bag);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts the game
|
* Starts the game
|
||||||
@@ -41,133 +40,85 @@ class Game {
|
|||||||
void start();
|
void start();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resets the game
|
* Advance to the next frame while excecuting the actions taken by the player,
|
||||||
*/
|
|
||||||
void reset();
|
|
||||||
|
|
||||||
private:
|
|
||||||
/**
|
|
||||||
* Initializes the game
|
|
||||||
*/
|
|
||||||
void initialize();
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Advances to the next frame while excecuting the actions taken by the player,
|
|
||||||
* this is where the main game logic takes place
|
* this is where the main game logic takes place
|
||||||
*/
|
*/
|
||||||
void nextFrame(const std::set<Action>& playerActions);
|
void nextFrame(const std::set<Action>& playerActions);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* Resets the piece's parameter
|
* Move the piece in the specified direction
|
||||||
*/
|
*/
|
||||||
void resetPiece(bool newPiece);
|
void movePiece(int movement, bool resetDirection);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Moves the piece in the specified direction (1 for right and -1 for left)
|
* Locks the piece, updates level and score and spawn the next piece if necessary
|
||||||
*/
|
|
||||||
void movePiece(int movement);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rotates the piece with the specified rotation
|
|
||||||
*/
|
|
||||||
void rotatePiece(Rotation rotation);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Locks the piece, updates level and score and spawns the next piece if necessary
|
|
||||||
*/
|
*/
|
||||||
void lockPiece();
|
void lockPiece();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* @return If the player has won
|
* Returns wheter the player has won
|
||||||
*/
|
*/
|
||||||
bool hasWon() const;
|
bool hasWon();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return If the player has lost
|
* Returns wheter the player has lost
|
||||||
*/
|
*/
|
||||||
bool hasLost() const;
|
bool hasLost();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The current level
|
* Returns the current level
|
||||||
*/
|
*/
|
||||||
int getLevel() const;
|
int getLevel();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The current number of cleared lines
|
* Returns the current number of cleared lines
|
||||||
*/
|
*/
|
||||||
int getClearedLines() const;
|
int getClearedLines();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The current grade
|
* Returns the number of frames passed since the start of the game
|
||||||
*/
|
*/
|
||||||
int getGrade() const;
|
int getFramesPassed();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The number of frames passed since the start of the game
|
* Returns the current score
|
||||||
*/
|
*/
|
||||||
int getFramesPassed() const;
|
int getScore();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The current score
|
* Returns wheter the player is currently on a B2B chain
|
||||||
*/
|
*/
|
||||||
int getScore() const;
|
bool isOnB2BChain();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return If the player is currently on a B2B chain
|
* Returns wheter all blocks are currently bone blocks
|
||||||
*/
|
*/
|
||||||
bool isOnB2BChain() const;
|
bool areBlocksBones();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return How close the active piece's lock delay is to the maximum allowed, betwwen 0 and 1
|
* Returns a copy of the board
|
||||||
*/
|
*/
|
||||||
float getLockDelayProgression() const;
|
Board getBoard();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return How close the active piece's forced lock delay is to the maximum allowed, betwwen 0 and 1
|
* Returns a copy of the active piece
|
||||||
*/
|
*/
|
||||||
float getForcedLockDelayProgression() const;
|
Piece getActivePiece();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return If all blocks are currently bone blocks
|
* Returns a copy of the active piece position
|
||||||
*/
|
*/
|
||||||
bool areBlocksBones() const;
|
Cell getActivePiecePosition();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return If the board is currently invisible
|
* Returns a copy of the held piece
|
||||||
*/
|
*/
|
||||||
bool isBoardInvisible() const;
|
Piece getHeldPiece();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The board
|
* Return a copy of the next pieces queue
|
||||||
*/
|
*/
|
||||||
const Board& getBoard() const;
|
std::vector<Piece> getNextPieces();
|
||||||
|
|
||||||
/**
|
|
||||||
* @return A pointer to the active piece, can be null
|
|
||||||
*/
|
|
||||||
const std::shared_ptr<Piece>& getActivePiece() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The position of the active piece
|
|
||||||
*/
|
|
||||||
const Position& getActivePiecePosition() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The position of the ghost piece
|
|
||||||
*/
|
|
||||||
Position getGhostPiecePosition() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return A pointer to the held piece, can be null
|
|
||||||
*/
|
|
||||||
const std::shared_ptr<Piece>& getHeldPiece() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The next piece queue, can be empty
|
|
||||||
*/
|
|
||||||
const std::vector<Piece>& getNextPieces() const;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,43 +8,19 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <utility>
|
|
||||||
#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::vector<Piece>& bag, int nextQueueLength) : board(boardWidth, boardHeight), generator(bag), nextQueueLength(nextQueueLength) {
|
||||||
board(boardWidth, boardHeight),
|
// initialize queue
|
||||||
generator(piecesList),
|
|
||||||
nextQueueLength(nextQueueLength) {
|
|
||||||
|
|
||||||
this->initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GameBoard::reset() {
|
|
||||||
this->board.clearBoard();
|
|
||||||
this->generator.jumpToNextBag();
|
|
||||||
|
|
||||||
this->initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GameBoard::initialize() {
|
|
||||||
this->nextQueue.clear();
|
this->nextQueue.clear();
|
||||||
for (int i = 0; i < nextQueueLength; i++) {
|
for (int i = 0; i < nextQueueLength; i++) {
|
||||||
this->nextQueue.push_back(this->generator.getNext());
|
this->nextQueue.push_back(this->generator.getNext());
|
||||||
}
|
}
|
||||||
|
|
||||||
this->activePiece = nullptr;
|
|
||||||
this->heldPiece = nullptr;
|
|
||||||
this->isLastMoveKick = false;
|
|
||||||
this->movedLeftLast = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GameBoard::moveLeft() {
|
bool GameBoard::moveLeft() {
|
||||||
this->movedLeftLast = true;
|
// check if the piece can be moved one cell left
|
||||||
|
if (this->isActivePieceInWall(Cell{-1, 0})) {
|
||||||
if (this->activePieceInWall(Position(-1, 0))) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -55,9 +31,8 @@ bool GameBoard::moveLeft() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool GameBoard::moveRight() {
|
bool GameBoard::moveRight() {
|
||||||
this->movedLeftLast = false;
|
// check if the piece can be moved one cell right
|
||||||
|
if (this->isActivePieceInWall(Cell{1, 0})) {
|
||||||
if (this->activePieceInWall(Position(1, 0))) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -68,7 +43,8 @@ bool GameBoard::moveRight() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool GameBoard::moveDown() {
|
bool GameBoard::moveDown() {
|
||||||
if (this->activePieceInWall(Position(0, -1))) {
|
// check if the piece can be moved one cell down
|
||||||
|
if (this->isActivePieceInWall(Cell{0, -1})) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -79,48 +55,36 @@ bool GameBoard::moveDown() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool GameBoard::rotate(Rotation rotation) {
|
bool GameBoard::rotate(Rotation rotation) {
|
||||||
|
// copy the original piece before rotating it
|
||||||
Piece stored = *this->activePiece;
|
Piece stored = *this->activePiece;
|
||||||
this->activePiece->rotate(rotation);
|
this->rotate(rotation);
|
||||||
|
|
||||||
// before trying to kick, check if the piece can rotate without kicking
|
// check if the piece can rotate
|
||||||
if (rotation == NONE) {
|
if (!this->isActivePieceInWall()) {
|
||||||
if (this->moveDown()) {
|
this->isLastMoveKick = false;
|
||||||
this->isLastMoveKick = false;
|
return true;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (!this->activePieceInWall()) {
|
|
||||||
this->isLastMoveKick = false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::set<Position> safePositions;
|
// get the list of cells that touches the original piece
|
||||||
for (Position position : stored.getPositions()) {
|
std::set<Cell> safeCells;
|
||||||
Position positionInGrid(position + this->activePiecePosition);
|
for (Cell cell : stored.getPositions()) {
|
||||||
safePositions.insert(positionInGrid);
|
Cell cellInGrid(cell + this->activePiecePosition);
|
||||||
safePositions.insert(positionInGrid + Position(0, 1));
|
safeCells.insert(cellInGrid);
|
||||||
safePositions.insert(positionInGrid + Position(1, 0));
|
safeCells.insert(cellInGrid + Cell{0, 1});
|
||||||
safePositions.insert(positionInGrid + Position(0, -1));
|
safeCells.insert(cellInGrid + Cell{1, 0});
|
||||||
safePositions.insert(positionInGrid + Position(-1, 0));
|
safeCells.insert(cellInGrid + Cell{0, -1});
|
||||||
|
safeCells.insert(cellInGrid + Cell{-1, 0});
|
||||||
}
|
}
|
||||||
|
|
||||||
// first try kicking the piece down
|
// try kicking the piece down
|
||||||
if (rotation == NONE) {
|
bool suceeded = this->tryKicking(true, safeCells);
|
||||||
this->activePiecePosition.y -= 1;
|
|
||||||
}
|
|
||||||
bool suceeded = this->tryKicking(true, safePositions);
|
|
||||||
if (suceeded) {
|
if (suceeded) {
|
||||||
this->isLastMoveKick = true;
|
this->isLastMoveKick = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if it doesn't work try kicking the piece up
|
// if it doesn't work try kicking the piece up
|
||||||
if (rotation == NONE) {
|
suceeded = this->tryKicking(false, safeCells);
|
||||||
this->activePiecePosition.y += 1;
|
|
||||||
}
|
|
||||||
suceeded = this->tryKicking(false, safePositions);
|
|
||||||
if (suceeded) {
|
if (suceeded) {
|
||||||
this->isLastMoveKick = true;
|
this->isLastMoveKick = true;
|
||||||
return true;
|
return true;
|
||||||
@@ -128,79 +92,74 @@ bool GameBoard::rotate(Rotation rotation) {
|
|||||||
|
|
||||||
// if it still doesn't work, abort the rotation
|
// if it still doesn't work, abort the rotation
|
||||||
this->activePiece = std::make_shared<Piece>(stored);
|
this->activePiece = std::make_shared<Piece>(stored);
|
||||||
if (rotation == NONE) {
|
return false;
|
||||||
this->isLastMoveKick = false;
|
|
||||||
}
|
|
||||||
return (rotation == NONE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GameBoard::tryKicking(bool testingBottom, const std::set<Position>& safePositions) {
|
bool GameBoard::tryKicking(bool testingBottom, const std::set<Cell>& safeCells) {
|
||||||
// we try from the original height of the piece, moving vertically as long as the kicked piece touches the original at least once on this row
|
// we try from the original height of the piece, moving vertically as long as the kicked piece touches the original
|
||||||
bool overlapsVertically = true;
|
bool overlapsVertically = true;
|
||||||
int j = 0;
|
int j = 0;
|
||||||
do {
|
do {
|
||||||
// we try from the center to the sides as long as the kicked piece touches the original
|
// we try from the center to the sides as long as the kicked piece touches the original
|
||||||
bool overlapsLeft = true;
|
bool overlapsLeft = true;
|
||||||
bool overlapsRight = true;
|
bool overlapsRight = true;
|
||||||
int i = (j == 0) ? 1 : 0;
|
int i = 0;
|
||||||
do {
|
do {
|
||||||
// we first check the side to which the player moved last
|
// check right before right arbitrarly, we don't decide this with rotations since it would still be arbitrary with 180° rotations
|
||||||
if (movedLeftLast) {
|
if (overlapsRight) {
|
||||||
if (overlapsLeft) {
|
Cell shift{+i, j};
|
||||||
if (this->tryFittingKickedPiece(safePositions, Position(-i, j), overlapsLeft)) return true;
|
// the kicked position must touch the original piece
|
||||||
|
if (!this->activePieceOverlapsOneCell(safeCells, shift)) {
|
||||||
|
overlapsLeft = false;
|
||||||
}
|
}
|
||||||
if (overlapsRight) {
|
else {
|
||||||
if (this->tryFittingKickedPiece(safePositions, Position(+i, j), overlapsRight)) return true;
|
// if the position is valid we place the active piece there
|
||||||
|
if (!this->isActivePieceInWall(shift)) {
|
||||||
|
this->activePiecePosition += shift;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
if (overlapsRight) {
|
// do the same on the left side
|
||||||
if (this->tryFittingKickedPiece(safePositions, Position(+i, j), overlapsRight)) return true;
|
if (overlapsLeft) {
|
||||||
|
Cell shift{-i, j};
|
||||||
|
if (!this->activePieceOverlapsOneCell(safeCells, shift)) {
|
||||||
|
overlapsLeft = false;
|
||||||
}
|
}
|
||||||
if (overlapsLeft) {
|
else {
|
||||||
if (this->tryFittingKickedPiece(safePositions, Position(-i, j), overlapsLeft)) return true;
|
if (!this->isActivePieceInWall(shift)) {
|
||||||
|
this->activePiecePosition += shift;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
i++;
|
i++;
|
||||||
} while (overlapsLeft && overlapsRight);
|
} while (overlapsLeft && overlapsRight);
|
||||||
|
|
||||||
|
// test if no position touched the original piece
|
||||||
if (i == 1) {
|
if (i == 1) {
|
||||||
overlapsVertically = false;
|
overlapsVertically = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// move one line up or down
|
||||||
(testingBottom) ? j-- : j++;
|
(testingBottom) ? j-- : j++;
|
||||||
} while (overlapsVertically);
|
} while (overlapsVertically);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GameBoard::tryFittingKickedPiece(const std::set<Position>& safePositions, const Position& shift, bool& overlaps) {
|
bool GameBoard::hold(Rotation initialRotation) {
|
||||||
if (!this->activePieceOverlaps(safePositions, shift)) {
|
// swap with held piece
|
||||||
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);
|
// if it's the first time holding try the next piece
|
||||||
if (isFirstTimeHolding) {
|
bool isFirstTimeHolding = false;
|
||||||
|
if (this->activePiece == nullptr) {
|
||||||
|
isFirstTimeHolding = true;
|
||||||
|
|
||||||
|
// if no pieces in next queue look at what the next would be
|
||||||
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());
|
||||||
}
|
}
|
||||||
@@ -209,143 +168,142 @@ bool GameBoard::hold(std::optional<Rotation> initialRotation) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// try initial rotation
|
// set the spawned piece to the correct position
|
||||||
this->goToSpawnPosition();
|
this->goToSpawnPosition();
|
||||||
if (initialRotation.has_value()) {
|
|
||||||
std::shared_ptr<Piece> storedPiece(this->activePiece);
|
|
||||||
this->rotate(initialRotation.value());
|
|
||||||
|
|
||||||
if (this->activePieceInWall()) {
|
// apply initial rotation
|
||||||
this->activePiece = storedPiece;
|
Piece stored = *this->activePiece;
|
||||||
}
|
this->rotate(initialRotation);
|
||||||
}
|
|
||||||
|
|
||||||
// if the piece can't spawn, try 0° rotation
|
|
||||||
if (this->activePieceInWall()) {
|
|
||||||
std::shared_ptr<Piece> storedPiece(this->activePiece);
|
|
||||||
this->rotate(NONE);
|
|
||||||
|
|
||||||
|
// if the piece can't spawn, abort initial rotation
|
||||||
|
if (this->isActivePieceInWall()) {
|
||||||
|
this->activePiece = std::make_shared<Piece>(stored);
|
||||||
|
|
||||||
// if the piece still can't spawn, abort holding
|
// if the piece still can't spawn, abort holding
|
||||||
if (this->activePieceInWall()) {
|
if (this->isActivePieceInWall()) {
|
||||||
this->activePiece = (isFirstTimeHolding) ? nullptr : storedPiece;
|
if (isFirstTimeHolding) {
|
||||||
|
this->activePiece = nullptr;
|
||||||
|
}
|
||||||
std::swap(this->activePiece, this->heldPiece);
|
std::swap(this->activePiece, this->heldPiece);
|
||||||
this->activePiecePosition = storedPosition;
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if it's the first time holding, confirm we keep this piece
|
||||||
if (isFirstTimeHolding) {
|
if (isFirstTimeHolding) {
|
||||||
this->nextQueue.push_back(this->generator.getNext());
|
if (this->nextQueueLength == 0) {
|
||||||
this->nextQueue.erase(this->nextQueue.begin());
|
this->generator.getNext();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this->spawnNextPiece();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this->heldPiece->defaultRotation();
|
// this piece has done nothing yet
|
||||||
|
|
||||||
this->isLastMoveKick = false;
|
this->isLastMoveKick = false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GameBoard::spawnNextPiece() {
|
bool GameBoard::spawnNextPiece() {
|
||||||
|
// add a piece to the queue
|
||||||
this->nextQueue.push_back(this->generator.getNext());
|
this->nextQueue.push_back(this->generator.getNext());
|
||||||
|
|
||||||
|
// get next piece from queue
|
||||||
this->activePiece = std::make_shared<Piece>(this->nextQueue.front());
|
this->activePiece = std::make_shared<Piece>(this->nextQueue.front());
|
||||||
this->nextQueue.erase(this->nextQueue.begin());
|
this->nextQueue.erase(this->nextQueue.begin());
|
||||||
|
|
||||||
|
// set the spawned piece to the correct position
|
||||||
this->goToSpawnPosition();
|
this->goToSpawnPosition();
|
||||||
|
|
||||||
|
// this piece has done nothing yet
|
||||||
this->isLastMoveKick = false;
|
this->isLastMoveKick = false;
|
||||||
|
|
||||||
return this->activePieceInWall();
|
// returns wheter the piece can spawn correctly
|
||||||
|
return !this->isActivePieceInWall();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GameBoard::activePieceInWall(const Position& shift) const {
|
bool GameBoard::touchesGround() {
|
||||||
for (Position position : this->activePiece->getPositions()) {
|
return this->isActivePieceInWall(Cell{0, -1});
|
||||||
if (this->board.getBlock(position + this->activePiecePosition + shift) != NOTHING) return true;
|
}
|
||||||
|
|
||||||
|
LineClear GameBoard::lockPiece() {
|
||||||
|
// check if the piece is locked in place
|
||||||
|
bool isLocked = (this->isActivePieceInWall(Cell{0, 1}) && this->isActivePieceInWall(Cell{1, 0}) &&
|
||||||
|
this->isActivePieceInWall(Cell{-1, 0}) && this->isActivePieceInWall(Cell{0, -1}));
|
||||||
|
|
||||||
|
// put the piece in the board
|
||||||
|
for (Cell cell : this->activePiece->getPositions()) {
|
||||||
|
this->board.addBlock(cell + this->activePiecePosition, this->activePiece->getColor());
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for lines to clear
|
||||||
|
return LineClear{this->board.clearRows(), isLocked, (!isLocked) && this->isLastMoveKick};
|
||||||
|
}
|
||||||
|
|
||||||
|
Board GameBoard::getBoard() const {
|
||||||
|
return this->board;
|
||||||
|
}
|
||||||
|
|
||||||
|
Piece GameBoard::getActivePiece() const {
|
||||||
|
return *this->activePiece;
|
||||||
|
}
|
||||||
|
|
||||||
|
Cell GameBoard::getActivePiecePosition() const {
|
||||||
|
return this->activePiecePosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
Piece GameBoard::getHeldPiece() const {
|
||||||
|
return *this->heldPiece;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Piece> GameBoard::getNextPieces() const {
|
||||||
|
return this->nextQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GameBoard::isActivePieceInWall(const Cell& shift) const {
|
||||||
|
// check if every cell of the active piece is in an empty spot
|
||||||
|
for (Cell cell : this->activePiece->getPositions()) {
|
||||||
|
if (this->board.getBlock(cell + this->activePiecePosition + shift) != NOTHING)
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GameBoard::touchesGround() const {
|
bool GameBoard::activePieceOverlapsOneCell(const std::set<Cell>& safeCells, const Cell& shift) const {
|
||||||
return this->activePieceInWall(Position(0, -1));
|
// check if one cell of the translated active piece overlaps with one cell of the given piece set
|
||||||
}
|
for (Cell cell : this->activePiece->getPositions()) {
|
||||||
|
if (safeCells.contains(cell + shift)) return true;
|
||||||
Position GameBoard::lowestPosition() const {
|
|
||||||
Position shift = Position(0, -1);
|
|
||||||
while (!activePieceInWall(shift)) {
|
|
||||||
shift.y -= 1;
|
|
||||||
}
|
}
|
||||||
shift.y += 1;
|
return false;
|
||||||
return (this->activePiecePosition + shift);
|
|
||||||
}
|
|
||||||
|
|
||||||
LineClear GameBoard::lockPiece() {
|
|
||||||
bool isLockedInPlace = (this->activePieceInWall(Position(0, 1)) && this->activePieceInWall(Position(1, 0))
|
|
||||||
&& this->activePieceInWall(Position(-1, 0)) && this->activePieceInWall(Position(0, -1)));
|
|
||||||
|
|
||||||
for (Position position : this->activePiece->getPositions()) {
|
|
||||||
this->board.changeBlock(position + this->activePiecePosition, this->activePiece->getBlockType());
|
|
||||||
}
|
|
||||||
|
|
||||||
this->activePiece = nullptr;
|
|
||||||
|
|
||||||
return LineClear{this->board.clearRows(), isLockedInPlace, (!isLockedInPlace) && this->isLastMoveKick};
|
|
||||||
}
|
|
||||||
|
|
||||||
void GameBoard::addGarbageRows(int number) {
|
|
||||||
int holePosition = std::rand() % this->board.getWidth();
|
|
||||||
|
|
||||||
for (int i = 0; i < number; i++) {
|
|
||||||
this->board.insertRow(0, holePosition, GARBAGE);
|
|
||||||
if (this->touchesGround()) {
|
|
||||||
this->activePiecePosition.y += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const Board& GameBoard::getBoard() const {
|
|
||||||
return this->board;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::shared_ptr<Piece>& GameBoard::getActivePiece() const {
|
|
||||||
return this->activePiece;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Position& GameBoard::getActivePiecePosition() const {
|
|
||||||
return this->activePiecePosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::shared_ptr<Piece>& GameBoard::getHeldPiece() const {
|
|
||||||
return this->heldPiece;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::vector<Piece>& GameBoard::getNextPieces() const {
|
|
||||||
return this->nextQueue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameBoard::goToSpawnPosition() {
|
void GameBoard::goToSpawnPosition() {
|
||||||
int lowestPosition = this->activePiece->getLength() - 1;
|
// get the lowest cell of the piece
|
||||||
for (Position position : this->activePiece->getPositions()) {
|
int lowestCell = this->activePiece->getLength() - 1;
|
||||||
if (position.y < lowestPosition) lowestPosition = position.y;
|
for (Cell cell : this->activePiece->getPositions()) {
|
||||||
|
if (cell.y < lowestCell) lowestCell = cell.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
// set the piece one line above the board
|
// set the piece one line above the board
|
||||||
this->activePiecePosition.y = this->board.getBaseHeight() - lowestPosition;
|
this->activePiecePosition.y = this->board.getBaseHeight() - lowestCell;
|
||||||
|
|
||||||
// center the piece horizontally, biased towards left
|
// center the piece horizontally, biased towards left
|
||||||
this->activePiecePosition.x = (this->board.getWidth() - this->activePiece->getLength()) / 2;
|
this->activePiecePosition.x = (this->board.getWidth() - this->activePiece->getLength()) / 2;
|
||||||
|
|
||||||
this->activePiece->defaultRotation();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::ostream& operator<<(std::ostream& os, const GameBoard& gameboard) {
|
std::ostream& operator<<(std::ostream& os, const GameBoard& gameboard) {
|
||||||
// print over the board (only the active piece if it is there)
|
// print over the board (only the active piece if it is there)
|
||||||
if (gameboard.activePiece != nullptr) {
|
if (gameboard.activePiece != nullptr) {
|
||||||
Block pieceBlockType = gameboard.activePiece->getBlockType();
|
|
||||||
os << getConsoleColorCode(pieceBlockType);
|
|
||||||
|
|
||||||
// print only the position were the active piece is
|
// change to the color of the active piece
|
||||||
|
ColorEnum pieceColor = gameboard.activePiece->getColor();
|
||||||
|
os << getColorCode(pieceColor);
|
||||||
|
|
||||||
|
// print only the cell were the active piece is
|
||||||
for (int y = gameboard.activePiecePosition.y + gameboard.activePiece->getLength() - 1; y >= gameboard.board.getBaseHeight(); y--) {
|
for (int y = gameboard.activePiecePosition.y + gameboard.activePiece->getLength() - 1; y >= gameboard.board.getBaseHeight(); y--) {
|
||||||
for (int x = 0; x < gameboard.board.getWidth(); x++) {
|
for (int x = 0; x < gameboard.board.getWidth(); x++) {
|
||||||
bool hasActivePiece = gameboard.activePiece->containsSquare(Position(x, y) - gameboard.activePiecePosition);
|
bool hasActivePiece = gameboard.activePiece->getPositions().contains(Cell{x, y} - gameboard.activePiecePosition);
|
||||||
if (hasActivePiece) {
|
if (hasActivePiece) {
|
||||||
os << "*";
|
os << "*";
|
||||||
}
|
}
|
||||||
@@ -358,19 +316,21 @@ std::ostream& operator<<(std::ostream& os, const GameBoard& gameboard) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// print the board
|
// print the board
|
||||||
Block pieceBlockType = (gameboard.activePiece == nullptr) ? NOTHING : gameboard.activePiece->getBlockType();
|
ColorEnum pieceColor = (gameboard.activePiece == nullptr) ? NOTHING : gameboard.activePiece->getColor();
|
||||||
for (int y = gameboard.board.getBaseHeight() - 1; y >= 0; y--) {
|
for (int y = gameboard.board.getBaseHeight() - 1; y >= 0; y--) {
|
||||||
for (int x = 0; x < gameboard.board.getWidth(); x++) {
|
for (int x = 0; x < gameboard.board.getWidth(); x++) {
|
||||||
bool hasActivePiece = (gameboard.activePiece == nullptr) ? false : gameboard.activePiece->containsSquare(Position(x, y) - gameboard.activePiecePosition);
|
bool hasActivePiece = (gameboard.activePiece == nullptr) ? false : gameboard.activePiece->getPositions().contains(Cell{x, y} - gameboard.activePiecePosition);
|
||||||
|
|
||||||
// the active piece takes visual priority over the board
|
// if the active piece is on this cell, print it
|
||||||
if (hasActivePiece) {
|
if (hasActivePiece) {
|
||||||
os << getConsoleColorCode(pieceBlockType);
|
os << getColorCode(pieceColor);
|
||||||
os << "*";
|
os << "*";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// else print the cell of the board
|
||||||
else {
|
else {
|
||||||
Block block = gameboard.board.getBlock(Position(x, y));
|
ColorEnum block = gameboard.board.getBlock(Cell{x, y});
|
||||||
os << getConsoleColorCode(block);
|
os << getColorCode(block);
|
||||||
if (block != NOTHING) {
|
if (block != NOTHING) {
|
||||||
os << "*";
|
os << "*";
|
||||||
}
|
}
|
||||||
@@ -382,7 +342,7 @@ std::ostream& operator<<(std::ostream& os, const GameBoard& gameboard) {
|
|||||||
os << std::endl;
|
os << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
// print hold box
|
// print held piece
|
||||||
os << "Hold:" << std::endl;
|
os << "Hold:" << std::endl;
|
||||||
if (!(gameboard.heldPiece == nullptr)) {
|
if (!(gameboard.heldPiece == nullptr)) {
|
||||||
os << *gameboard.heldPiece;
|
os << *gameboard.heldPiece;
|
||||||
@@ -394,7 +354,8 @@ std::ostream& operator<<(std::ostream& os, const GameBoard& gameboard) {
|
|||||||
os << piece;
|
os << piece;
|
||||||
}
|
}
|
||||||
|
|
||||||
os << getResetConsoleColorCode();
|
// reset console color
|
||||||
|
os << getColorCode(NOTHING);
|
||||||
|
|
||||||
return os;
|
return os;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -15,145 +14,105 @@
|
|||||||
*/
|
*/
|
||||||
class GameBoard {
|
class GameBoard {
|
||||||
private:
|
private:
|
||||||
Board board; // the board in which pieces moves, (0, 0) is downleft
|
Board board; // the board in which pieces moves, (0, 0) is downleft
|
||||||
Bag generator; // the piece generator
|
Bag generator; // the piece generator
|
||||||
std::shared_ptr<Piece> activePiece; // the piece currently in the board
|
std::shared_ptr<Piece> activePiece; // the piece currently in the board
|
||||||
Position activePiecePosition; // the position of the piece currently in the board
|
Cell activePiecePosition; // the position of the piece currently in the board
|
||||||
std::shared_ptr<Piece> heldPiece; // a piece being holded
|
std::shared_ptr<Piece> heldPiece; // a piece being holded
|
||||||
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:
|
||||||
/**
|
/**
|
||||||
* Creates a new board, generator, and next queue
|
* Creates a new board, generator, and next queue
|
||||||
*/
|
*/
|
||||||
GameBoard(int boardWidth, int boardHeight, const std::shared_ptr<PiecesList>& piecesList, int nextQueueLength);
|
GameBoard(int boardWidth, int boardHeight, const std::vector<Piece>& bag, int nextQueueLength);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resets the board as if it was newly created
|
* Try moving the piece one cell to the left, and returns wheter it was sucessfull
|
||||||
*/
|
|
||||||
void reset();
|
|
||||||
|
|
||||||
private:
|
|
||||||
/**
|
|
||||||
* Initializes the board
|
|
||||||
*/
|
|
||||||
void initialize();
|
|
||||||
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* Tries moving the piece one position to the left
|
|
||||||
* @return If it suceeded
|
|
||||||
*/
|
*/
|
||||||
bool moveLeft();
|
bool moveLeft();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tries moving the piece one position to the right
|
* Try moving the piece one cell to the right, and returns wheter it was sucessfull
|
||||||
* @return If it suceeded
|
|
||||||
*/
|
*/
|
||||||
bool moveRight();
|
bool moveRight();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tries moving the piece one position down
|
* Try moving the piece one cell down, and returns wheter it was sucessfull
|
||||||
* @return If it suceeded
|
|
||||||
*/
|
*/
|
||||||
bool moveDown();
|
bool moveDown();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tries rotating the piece and kicking it if necessary, if it's a 0° rotation, it will forcefully try kicking
|
* Try rotating the piece and kicking it if necessary, and returns wheter it was sucessfull
|
||||||
* @return If it suceeded
|
|
||||||
*/
|
*/
|
||||||
bool rotate(Rotation rotation);
|
bool rotate(Rotation rotation);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* Tries kicking the piece, testing position either above or below the piece's initial position
|
* Try kicking the piece, testing position either above or below the piece's initial position
|
||||||
* @return If it suceeded
|
|
||||||
*/
|
*/
|
||||||
bool tryKicking(bool testingBottom, const std::set<Position>& safePositions);
|
bool tryKicking(bool testingBottom, const std::set<Cell>& safeCells);
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
* Try 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
|
* and returns wheter it was sucessfull
|
||||||
*/
|
*/
|
||||||
bool hold(std::optional<Rotation> initialRotation);
|
bool hold(Rotation initialRotation = NONE);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Spawns the next piece from the queue
|
* Spawns the next piece from the queue, and returns wheter it spawns in a wall
|
||||||
* @return If it spawned in a wall
|
|
||||||
*/
|
*/
|
||||||
bool spawnNextPiece();
|
bool spawnNextPiece();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if one of the active piece's positions touches a wall in the board
|
* Returns wheter the active piece is touching walls directly below it
|
||||||
* @return If the active piece is in a wall
|
|
||||||
*/
|
*/
|
||||||
bool activePieceInWall(const Position& shift = Position(0, 0)) const;
|
bool touchesGround();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks is the active piece as a wall directly below one of its position
|
* Lock the active piece into the board and returns the resulting line clear
|
||||||
* @return If it touches a ground
|
|
||||||
*/
|
|
||||||
bool touchesGround() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Computes what the piece position would be if it were to be dropped down as much as possible
|
|
||||||
* @return The lowest position before hitting a wall
|
|
||||||
*/
|
|
||||||
Position lowestPosition() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Locks the active piece into the board and clears lines if needed
|
|
||||||
* @return The resulting line clear
|
|
||||||
*/
|
*/
|
||||||
LineClear lockPiece();
|
LineClear lockPiece();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a specified number of garbage rows to the bottom of the board, the hole position being random but the same for all of them
|
* Returns a copy of the board
|
||||||
*/
|
*/
|
||||||
void addGarbageRows(int number);
|
Board getBoard() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The board
|
* Returns a copy of the active piece
|
||||||
*/
|
*/
|
||||||
const Board& getBoard() const;
|
Piece getActivePiece() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return A pointer to the active piece, can be null
|
* Returns a copy of the position of the active piece
|
||||||
*/
|
*/
|
||||||
const std::shared_ptr<Piece>& getActivePiece() const;
|
Cell getActivePiecePosition() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The position of the active piece
|
* Returns a copy of the held piece
|
||||||
*/
|
*/
|
||||||
const Position& getActivePiecePosition() const;
|
Piece getHeldPiece() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return A pointer to the held piece, can be null
|
* Returns a copy of the next piece queue
|
||||||
*/
|
*/
|
||||||
const std::shared_ptr<Piece>& getHeldPiece() const;
|
std::vector<Piece> getNextPieces() const;
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The next piece queue, can be empty
|
|
||||||
*/
|
|
||||||
const std::vector<Piece>& getNextPieces() const;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/**
|
||||||
|
* Returns wheter the translated active piece is in a wall
|
||||||
|
*/
|
||||||
|
bool isActivePieceInWall(const Cell& shift = Cell{0, 0}) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns wheter the translated active piece overlaps with at least one of the cells
|
||||||
|
*/
|
||||||
|
bool activePieceOverlapsOneCell(const std::set<Cell>& safeCells, const Cell& shift = Cell{0, 0}) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the active piece to its spawn position
|
* Sets the active piece to its spawn position
|
||||||
*/
|
*/
|
||||||
@@ -162,7 +121,6 @@ class GameBoard {
|
|||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* Stream output operator, adds the board, the hold box and the next queue
|
* Stream output operator, adds the board, the hold box and the next queue
|
||||||
* @return A reference to the output stream
|
|
||||||
*/
|
*/
|
||||||
friend std::ostream& operator<<(std::ostream& os, const GameBoard& gameboard);
|
friend std::ostream& operator<<(std::ostream& os, const GameBoard& gameboard);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,17 +4,9 @@
|
|||||||
#include "Player.h"
|
#include "Player.h"
|
||||||
|
|
||||||
|
|
||||||
GameParameters::GameParameters(Gamemode gamemode, const Player& controls) :
|
GameParameters::GameParameters(Gamemode gamemode, const Player& controls) : gamemode(gamemode), controls(controls) {
|
||||||
gamemode(gamemode),
|
// initialize lines and level
|
||||||
controls(controls) {
|
|
||||||
|
|
||||||
this->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;}
|
||||||
@@ -24,55 +16,36 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// initialize stats
|
||||||
this->updateStats();
|
this->updateStats();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameParameters::lockedPiece(const LineClear& lineClear) {
|
void GameParameters::clearLines(int lineNumber) {
|
||||||
|
// update lines and level
|
||||||
switch (this->gamemode) {
|
switch (this->gamemode) {
|
||||||
// modes where level increases with lines
|
// modes where level increases
|
||||||
case MARATHON :
|
case MARATHON :
|
||||||
case MASTER : {
|
case MASTER : {
|
||||||
|
// update cleared lines
|
||||||
int previousLines = this->clearedLines;
|
int previousLines = this->clearedLines;
|
||||||
this->clearedLines += lineClear.lines;
|
this->clearedLines += lineNumber;
|
||||||
|
|
||||||
// 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 - previousLines / 10);
|
this->level = this->clearedLines / 10;
|
||||||
this->updateStats();
|
this->updateStats();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// other modes
|
// other modes
|
||||||
default : this->clearedLines += lineClear.lines;
|
default : this->clearedLines += lineNumber;
|
||||||
}
|
|
||||||
|
|
||||||
int previousGrade = this->grade;
|
|
||||||
if (!((lineClear.lines == 0) && ((this->grade % 100) == 99))) {
|
|
||||||
this->grade += (1 + lineClear.lines);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (this->gamemode) {
|
|
||||||
// modes where level increases with grade
|
|
||||||
case INVISIBLE : {
|
|
||||||
if (previousGrade / 100 < this->grade / 100) {
|
|
||||||
this->level += 2;
|
|
||||||
this->updateStats();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// other modes
|
|
||||||
default : break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GameParameters::hasWon(int framesPassed) const {
|
bool GameParameters::hasWon(int framesPassed) {
|
||||||
switch (this->gamemode) {
|
switch (this->gamemode) {
|
||||||
// win once 40 lines have been cleared
|
// win once 40 lines have been cleared
|
||||||
case SPRINT : return this->clearedLines >= 40;
|
case SPRINT : return this->clearedLines >= 40;
|
||||||
@@ -82,10 +55,6 @@ 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,17 +62,15 @@ bool GameParameters::hasWon(int framesPassed) const {
|
|||||||
void GameParameters::updateStats() {
|
void GameParameters::updateStats() {
|
||||||
/* NEXT QUEUE */
|
/* NEXT QUEUE */
|
||||||
switch (this->gamemode) {
|
switch (this->gamemode) {
|
||||||
// 5 for fast-controls gamemodes
|
// 5 for rapidity gamemodes
|
||||||
case SPRINT :
|
case SPRINT :
|
||||||
case ULTRA :
|
case ULTRA : {
|
||||||
case ZEN : {
|
|
||||||
this->nextQueueLength = 5;
|
this->nextQueueLength = 5;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// 3 for slow-controls gamemodes
|
// 3 for endurance gamemodes
|
||||||
case MARATHON :
|
case MARATHON :
|
||||||
case MASTER :
|
case MASTER : {
|
||||||
case INVISIBLE : {
|
|
||||||
this->nextQueueLength = 3;
|
this->nextQueueLength = 3;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -113,54 +80,45 @@ 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); break;}
|
case MASTER : this->boneBlocks = (this->level >= 30);
|
||||||
default : this->boneBlocks = false;
|
default : this->boneBlocks = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* INVISIBLE */
|
|
||||||
this->invisibleBoard = (this->gamemode == INVISIBLE);
|
|
||||||
|
|
||||||
/* GRAVITY */
|
/* GRAVITY */
|
||||||
// get gravity for an assumed 20-rows board
|
if (level >= 20) {
|
||||||
static const int gravityPerLevel[] = {
|
// all levels above 20 are instant gravity
|
||||||
0, // lvl0 = no gravity
|
this->gravity = 20 * 60;
|
||||||
1, // 60f/line, 20s total
|
|
||||||
2, // 30f/line, 10s total
|
|
||||||
3, // 20f/line, 6.66s total
|
|
||||||
4, // 15f/line, 5s total
|
|
||||||
5, // 12f/line, 4s total
|
|
||||||
6, // 10f/line, 3.33 total
|
|
||||||
7, // 8.57f/line, 2.85s total
|
|
||||||
8, // 7.5f/line, 2.5s total
|
|
||||||
10, // 6f/line, 2s total
|
|
||||||
12, // 5f/line, 1.66s total
|
|
||||||
14, // 4.28f/line, 1.42s total
|
|
||||||
17, // 3.52f/line, 1.17s total
|
|
||||||
20, // 3f/line, 60f total
|
|
||||||
24, // 2.5f/line, 50f total
|
|
||||||
30, // 2f/line, 40f total
|
|
||||||
40, // 1.5f/line, 30f total
|
|
||||||
1 * 60, // 1line/f, 20f total
|
|
||||||
2 * 60, // 2line/f, 10f total
|
|
||||||
4 * 60, // 4line/f, 5f total
|
|
||||||
20 * 60 // lvl20 = instant gravity
|
|
||||||
};
|
|
||||||
if (this->level < 0) {
|
|
||||||
this->gravity = gravityPerLevel[0];
|
|
||||||
}
|
|
||||||
else if (this->level > 20) {
|
|
||||||
this->gravity = gravityPerLevel[20];
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this->gravity = gravityPerLevel[this->level];
|
// get gravity for an assumed 20-rows board
|
||||||
|
switch (this->level) {
|
||||||
|
case 1 : {this->gravity = 1; break;} // 60f/line, 20s total
|
||||||
|
case 2 : {this->gravity = 2; break;} // 30f/line, 10s total
|
||||||
|
case 3 : {this->gravity = 3; break;} // 20f/line, 6.66s total
|
||||||
|
case 4 : {this->gravity = 4; break;} // 15f/line, 5s total
|
||||||
|
case 5 : {this->gravity = 5; break;} // 12f/line, 4s total
|
||||||
|
case 6 : {this->gravity = 6; break;} // 10f/line, 3.33 total
|
||||||
|
case 7 : {this->gravity = 7; break;} // 8.57f/line, 2.85s total
|
||||||
|
case 8 : {this->gravity = 8; break;} // 7.5f/line, 2.5s total
|
||||||
|
case 9 : {this->gravity = 10; break;} // 6f/line, 2s total
|
||||||
|
case 10 : {this->gravity = 12; break;} // 5f/line, 1.66s total
|
||||||
|
case 11 : {this->gravity = 14; break;} // 4.28f/line, 1.42s total
|
||||||
|
case 12 : {this->gravity = 17; break;} // 3.52f/line, 1.17s total
|
||||||
|
case 13 : {this->gravity = 20; break;} // 3f/line, 60f total
|
||||||
|
case 14 : {this->gravity = 24; break;} // 2.5f/line, 50f total
|
||||||
|
case 15 : {this->gravity = 30; break;} // 2f/line, 40f total
|
||||||
|
case 16 : {this->gravity = 40; break;} // 1.5f/line, 30f total
|
||||||
|
case 17 : {this->gravity = 1 * 60; break;} // 1line/f, 20f total
|
||||||
|
case 18 : {this->gravity = 2 * 60; break;} // 2line/f, 10f total
|
||||||
|
case 19 : {this->gravity = 4 * 60; break;} // 4line/f, 5f total
|
||||||
|
default : this->gravity = 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* LOCK DELAY */
|
/* LOCK DELAY */
|
||||||
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;
|
||||||
}
|
}
|
||||||
@@ -174,8 +132,6 @@ 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;
|
||||||
}
|
}
|
||||||
@@ -232,58 +188,50 @@ void GameParameters::updateStats() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int GameParameters::getClearedLines() const {
|
int GameParameters::getClearedLines() {
|
||||||
return this->clearedLines;
|
return this->clearedLines;
|
||||||
}
|
}
|
||||||
|
|
||||||
int GameParameters::getLevel() const {
|
int GameParameters::getLevel() {
|
||||||
return this->level;
|
return this->level;
|
||||||
}
|
}
|
||||||
|
|
||||||
int GameParameters::getGrade() const {
|
int GameParameters::getNextQueueLength() {
|
||||||
return this->grade;
|
|
||||||
}
|
|
||||||
|
|
||||||
int GameParameters::getNextQueueLength() const {
|
|
||||||
return this->nextQueueLength;
|
return this->nextQueueLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GameParameters::getBoneBlocks() const {
|
bool GameParameters::getBoneBlocks() {
|
||||||
return this->boneBlocks;
|
return this->boneBlocks;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GameParameters::getInvisibleBoard() const {
|
int GameParameters::getGravity() {
|
||||||
return this->invisibleBoard;
|
|
||||||
}
|
|
||||||
|
|
||||||
int GameParameters::getGravity() const {
|
|
||||||
return this->gravity;
|
return this->gravity;
|
||||||
}
|
}
|
||||||
|
|
||||||
int GameParameters::getLockDelay() const {
|
int GameParameters::getLockDelay() {
|
||||||
return this->lockDelay;
|
return this->lockDelay;
|
||||||
}
|
}
|
||||||
|
|
||||||
int GameParameters::getForcedLockDelay() const {
|
int GameParameters::getForcedLockDelay() {
|
||||||
return this->forcedLockDelay;
|
return this->forcedLockDelay;
|
||||||
}
|
}
|
||||||
|
|
||||||
int GameParameters::getARE() const {
|
int GameParameters::getARE() {
|
||||||
return this->ARE;
|
return this->ARE;
|
||||||
}
|
}
|
||||||
|
|
||||||
int GameParameters::getLineARE() const {
|
int GameParameters::getLineARE() {
|
||||||
return this->lineARE;
|
return this->lineARE;
|
||||||
}
|
}
|
||||||
|
|
||||||
int GameParameters::getDAS() const {
|
int GameParameters::getDAS() {
|
||||||
return this->DAS;
|
return this->DAS;
|
||||||
}
|
}
|
||||||
|
|
||||||
int GameParameters::getARR() const {
|
int GameParameters::getARR() {
|
||||||
return this->ARR;
|
return this->ARR;
|
||||||
}
|
}
|
||||||
|
|
||||||
int GameParameters::getSDR() const {
|
int GameParameters::getSDR() {
|
||||||
return this->SDR;
|
return this->SDR;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
#include "Gamemode.h"
|
#include "Gamemode.h"
|
||||||
#include "Player.h"
|
#include "Player.h"
|
||||||
#include "LineClear.h"
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -11,22 +10,20 @@
|
|||||||
*/
|
*/
|
||||||
class GameParameters {
|
class GameParameters {
|
||||||
private:
|
private:
|
||||||
Gamemode gamemode; // the current gamemode
|
Gamemode gamemode; // the current gamemode
|
||||||
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
|
||||||
int ARE; // the time before the next piece spawn
|
int ARE; // the time before the next piece spawn
|
||||||
int lineARE; // the time before the next piece spawn, after clearing a line
|
int lineARE; // the time before the next piece spawn, after clearing a line
|
||||||
int DAS; // the time before the piece repeats moving
|
int DAS; // the time before the piece repeats moving
|
||||||
int ARR; // the rate at which the piece repeats moving
|
int ARR; // the rate at which the piece repeats moving
|
||||||
int SDR; // the rate at which the piece soft drops
|
int SDR; // the rate at which the piece soft drops
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
@@ -35,20 +32,14 @@ class GameParameters {
|
|||||||
GameParameters(Gamemode gamemode, const Player& controls);
|
GameParameters(Gamemode gamemode, const Player& controls);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resets all stats and parameters
|
* Count the newly cleared lines and update level and stats if needed
|
||||||
*/
|
*/
|
||||||
void reset();
|
void clearLines(int lineNumber);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Counts the newly cleared lines and update level and stats if needed
|
* Returns wheter the game ended
|
||||||
*/
|
*/
|
||||||
void lockedPiece(const LineClear& lineClear);
|
bool hasWon(int framesPassed);
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the game ended based on the current states and time passed, accorind to the gamemode
|
|
||||||
* @return If the player has won
|
|
||||||
*/
|
|
||||||
bool hasWon(int framesPassed) const;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
@@ -58,72 +49,62 @@ class GameParameters {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* @return The current number of cleared line
|
* Returns the current number of cleared line
|
||||||
*/
|
*/
|
||||||
int getClearedLines() const;
|
int getClearedLines();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The current level
|
* Returns the current level
|
||||||
*/
|
*/
|
||||||
int getLevel() const;
|
int getLevel();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the length of the next queue
|
||||||
|
*/
|
||||||
|
int getNextQueueLength();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns wheter the blocks are currently bone blocks
|
||||||
|
*/
|
||||||
|
bool getBoneBlocks();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The current grade
|
* Returns the current gravity for a 20-line high board
|
||||||
*/
|
*/
|
||||||
int getGrade() const;
|
int getGravity();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The length of the next queue
|
* Returns the current lock delay
|
||||||
*/
|
*/
|
||||||
int getNextQueueLength() const;
|
int getLockDelay();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Wheter the blocks are currently bone blocks
|
* Returns the current forced lock delay
|
||||||
*/
|
*/
|
||||||
bool getBoneBlocks() const;
|
int getForcedLockDelay();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Wheter the board is currently invisible
|
* Returns the current ARE
|
||||||
*/
|
*/
|
||||||
bool getInvisibleBoard() const;
|
int getARE();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current line ARE
|
||||||
|
*/
|
||||||
|
int getLineARE();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The current gravity for a 20-line high board
|
* Returns the current DAS
|
||||||
*/
|
*/
|
||||||
int getGravity() const;
|
int getDAS();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The current lock delay
|
* Returns the current ARR
|
||||||
*/
|
*/
|
||||||
int getLockDelay() const;
|
int getARR();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The current forced lock delay
|
* Returns the current SDR
|
||||||
*/
|
*/
|
||||||
int getForcedLockDelay() const;
|
int getSDR();
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The current ARE
|
|
||||||
*/
|
|
||||||
int getARE() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The current line ARE
|
|
||||||
*/
|
|
||||||
int getLineARE() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The current DAS
|
|
||||||
*/
|
|
||||||
int getDAS() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The current ARR
|
|
||||||
*/
|
|
||||||
int getARR() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The current SDR
|
|
||||||
*/
|
|
||||||
int getSDR() const;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Every gamemode supported by the game
|
* Every gamemode supported by the game
|
||||||
@@ -10,40 +8,5 @@ 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];
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
* Specify how many lines were cleared and how
|
* Specify how many lines were cleared and how
|
||||||
*/
|
*/
|
||||||
struct LineClear {
|
struct LineClear {
|
||||||
int lines; // the number of lines cleared
|
int lines; // the number of lines cleared
|
||||||
bool isSpin; // if the move was a spin
|
bool isSpin; // if the move was a spin
|
||||||
bool isMiniSpin; // if the move was a spin mini
|
bool isMiniSpin; // if the move was a spin mini
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
#include "Menu.h"
|
|
||||||
|
|
||||||
#include "PiecesList.h"
|
|
||||||
#include "Player.h"
|
|
||||||
#include "Game.h"
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
static const int DEFAULT_BOARD_WIDTH = 10; // the default width of the board when starting the menu
|
|
||||||
static const int DEFAULT_BOARD_HEIGHT = 20; // the default height of the board when starting the menu
|
|
||||||
|
|
||||||
|
|
||||||
Menu::Menu() {
|
|
||||||
this->piecesList = std::make_shared<PiecesList>(PiecesList());
|
|
||||||
|
|
||||||
this->boardWidth = DEFAULT_BOARD_WIDTH;
|
|
||||||
this->boardHeight = DEFAULT_BOARD_HEIGHT;
|
|
||||||
}
|
|
||||||
|
|
||||||
Game Menu::startGame(Gamemode gamemode) const {
|
|
||||||
return Game(gamemode, this->playerControls, this->boardWidth, this->boardHeight, this->piecesList);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Menu::setBoardWidth(int width) {
|
|
||||||
if (width < 1) return false;
|
|
||||||
|
|
||||||
this->boardWidth = width;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Menu::setBoardHeight(int height) {
|
|
||||||
if (height < 1) return false;
|
|
||||||
|
|
||||||
this->boardHeight = height;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int Menu::getBoardWidth() const {
|
|
||||||
return this->boardWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
int Menu::getBoardHeight() const {
|
|
||||||
return this->boardHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
Player& Menu::getPlayerControls() {
|
|
||||||
return this->playerControls;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Player& Menu::readPlayerControls() const {
|
|
||||||
return this->playerControls;
|
|
||||||
}
|
|
||||||
|
|
||||||
PiecesList& Menu::getPiecesList() {
|
|
||||||
return *this->piecesList;
|
|
||||||
}
|
|
||||||
|
|
||||||
const PiecesList& Menu::readPiecesList() const {
|
|
||||||
return *this->piecesList;
|
|
||||||
}
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "PiecesList.h"
|
|
||||||
#include "Player.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
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The interface between an UI and the core of the app
|
|
||||||
*/
|
|
||||||
class Menu {
|
|
||||||
private:
|
|
||||||
std::shared_ptr<PiecesList> piecesList; // the list of pieces used by the app
|
|
||||||
Player playerControls; // the controls of the player
|
|
||||||
int boardWidth; // the width of the board for the next game
|
|
||||||
int boardHeight; // the height of the board for the next game
|
|
||||||
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* Initializes the board size and player controls to their default values
|
|
||||||
*/
|
|
||||||
Menu();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts a new game with the current settings
|
|
||||||
* @return The game that has been created
|
|
||||||
*/
|
|
||||||
Game startGame(Gamemode gamemode) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the width of the board, which must be greater than 0
|
|
||||||
* @return If the width has been changed
|
|
||||||
*/
|
|
||||||
bool setBoardWidth(int width);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the height of the board, which must be greater than 0
|
|
||||||
* @return If the height has been changed
|
|
||||||
*/
|
|
||||||
bool setBoardHeight(int height);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The width of the board
|
|
||||||
*/
|
|
||||||
int getBoardWidth() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The height of the board
|
|
||||||
*/
|
|
||||||
int getBoardHeight() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return A reference to the player's controls
|
|
||||||
*/
|
|
||||||
Player& getPlayerControls();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return A reference to the player's controls
|
|
||||||
*/
|
|
||||||
const Player& readPlayerControls() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return A reference to the pieces list
|
|
||||||
*/
|
|
||||||
PiecesList& getPiecesList();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return A reference to the pieces list
|
|
||||||
*/
|
|
||||||
const PiecesList& readPiecesList() const;
|
|
||||||
};
|
|
||||||
@@ -1,155 +0,0 @@
|
|||||||
#include "PiecesList.h"
|
|
||||||
|
|
||||||
#include "../Pieces/Piece.h"
|
|
||||||
#include "../Pieces/PiecesFiles.h"
|
|
||||||
#include "DistributionMode.h"
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
|
|
||||||
PiecesList::PiecesList() {
|
|
||||||
this->highestLoadedSize = 0;
|
|
||||||
this->selectedPieces.clear();
|
|
||||||
|
|
||||||
this->distributionMode = DEFAULT;
|
|
||||||
this->proportionsPerSize.clear();
|
|
||||||
this->customProportionsPerSize.clear();
|
|
||||||
|
|
||||||
// we need to have something at index 0 even if there is no pieces of size 0
|
|
||||||
this->loadedPieces.clear();
|
|
||||||
this->convexPieces.clear();
|
|
||||||
this->holelessPieces.clear();
|
|
||||||
this->otherPieces.clear();
|
|
||||||
this->pushBackEmptyVectors();
|
|
||||||
|
|
||||||
// we always prepare vectors of the next size to be generated
|
|
||||||
this->pushBackEmptyVectors();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PiecesList::loadPieces(unsigned max_size) {
|
|
||||||
if (max_size < 1) return false;
|
|
||||||
if (max_size <= this->highestLoadedSize) return true;
|
|
||||||
|
|
||||||
PiecesFiles piecesFiles;
|
|
||||||
for (unsigned i = this->highestLoadedSize + 1; i <= max_size; i++) {
|
|
||||||
if (!piecesFiles.loadPieces(i, this->loadedPieces.at(i), this->convexPieces.at(i), this->holelessPieces.at(i), this->otherPieces.at(i))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this->highestLoadedSize++;
|
|
||||||
this->pushBackEmptyVectors();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PiecesList::selectPiece(unsigned size, unsigned number) {
|
|
||||||
if (size < 1 || size > this->highestLoadedSize || number >= this->loadedPieces.at(size).size()) return false;
|
|
||||||
|
|
||||||
this->selectedPieces.push_back(std::pair<int, int>(size, number));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PiecesList::selectAllPieces(unsigned size) {
|
|
||||||
if (size < 1 || size > this->highestLoadedSize) return false;
|
|
||||||
|
|
||||||
for (int i = 0; i < this->loadedPieces.at(size).size(); i++) {
|
|
||||||
this->selectedPieces.push_back(std::pair<int, int>(size, i));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PiecesList::selectConvexPieces(int size) {
|
|
||||||
if (size < 1 || size > this->highestLoadedSize) return false;
|
|
||||||
|
|
||||||
for (int index : this->convexPieces.at(size)) {
|
|
||||||
this->selectedPieces.push_back(std::pair<int, int>(size, index));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PiecesList::selectHolelessPieces(int size) {
|
|
||||||
if (size < 1 || size > this->highestLoadedSize) return false;
|
|
||||||
|
|
||||||
for (int index : this->holelessPieces.at(size)) {
|
|
||||||
this->selectedPieces.push_back(std::pair<int, int>(size, index));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PiecesList::selectOtherPieces(int size) {
|
|
||||||
if (size < 1 || size > this->highestLoadedSize) return false;
|
|
||||||
|
|
||||||
for (int index : this->otherPieces.at(size)) {
|
|
||||||
this->selectedPieces.push_back(std::pair<int, int>(size, index));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PiecesList::unselectAll() {
|
|
||||||
this->selectedPieces.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PiecesList::setDistributionMode(DistributionMode distributionMode) {
|
|
||||||
if (distributionMode == DEFAULT || distributionMode == UNIFORM || distributionMode == CUSTOM) {
|
|
||||||
this->distributionMode = distributionMode;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int PiecesList::getHighestLoadedSize() const {
|
|
||||||
return this->highestLoadedSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
int PiecesList::getNumberOfPieces(int size) const {
|
|
||||||
if (size < 1 || size > this->highestLoadedSize) return 0;
|
|
||||||
|
|
||||||
return this->loadedPieces.at(size).size();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::pair<int, int>> PiecesList::getSelectedPieces() const {
|
|
||||||
return this->selectedPieces;
|
|
||||||
}
|
|
||||||
|
|
||||||
DistributionMode PiecesList::getDistributionMode() const {
|
|
||||||
return this->distributionMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PiecesList::changeCustomDistribution(int size, double distribution) {
|
|
||||||
if (size < 1 || size > this->highestLoadedSize || distribution > 1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->customProportionsPerSize.at(size) = distribution;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<double> PiecesList::getProportionsPerSize() const {
|
|
||||||
if (this->distributionMode == CUSTOM) {
|
|
||||||
return this->customProportionsPerSize;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return this->proportionsPerSize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Piece PiecesList::getPiece(const std::pair<int, int>& pieceIndex) const {
|
|
||||||
return this->loadedPieces.at(pieceIndex.first).at(pieceIndex.second);
|
|
||||||
}
|
|
||||||
|
|
||||||
const Piece& PiecesList::lookAtPiece(const std::pair<int, int>& pieceIndex) const {
|
|
||||||
return this->loadedPieces.at(pieceIndex.first).at(pieceIndex.second);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PiecesList::pushBackEmptyVectors() {
|
|
||||||
this->loadedPieces.push_back(std::vector<Piece>());
|
|
||||||
this->convexPieces.push_back(std::vector<int>());
|
|
||||||
this->holelessPieces.push_back(std::vector<int>());
|
|
||||||
this->otherPieces.push_back(std::vector<int>());
|
|
||||||
|
|
||||||
this->proportionsPerSize.push_back(1);
|
|
||||||
this->customProportionsPerSize.push_back(1);
|
|
||||||
}
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "../Pieces/Piece.h"
|
|
||||||
#include "DistributionMode.h"
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A container for all loaded pieces to prevent loading and copying them multiple times,
|
|
||||||
* also allows for the player to select a list of pieces to be used in a game
|
|
||||||
*/
|
|
||||||
class PiecesList {
|
|
||||||
private:
|
|
||||||
unsigned highestLoadedSize; // the highest size of pieces currently loaded
|
|
||||||
std::vector<std::vector<Piece>> loadedPieces; // every loaded pieces by size
|
|
||||||
std::vector<std::vector<int>> convexPieces; // the list of convex 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::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:
|
|
||||||
/**
|
|
||||||
* Initializes a list of pieces up to size 0 (so no pieces)
|
|
||||||
*/
|
|
||||||
PiecesList();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Makes the list load all pieces up to the specified size
|
|
||||||
* @return If all pieces up to the specified size are correctly loaded
|
|
||||||
*/
|
|
||||||
[[nodiscard]] bool loadPieces(unsigned max_size);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Selects the specified piece
|
|
||||||
* @return If the piece could be selected
|
|
||||||
*/
|
|
||||||
bool selectPiece(unsigned size, unsigned number);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Selects all pieces of the specified size
|
|
||||||
* @return If the pieces could be selected
|
|
||||||
*/
|
|
||||||
bool selectAllPieces(unsigned size);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Selects all convex pieces of the specified size
|
|
||||||
* @return If the pieces could be selected
|
|
||||||
*/
|
|
||||||
bool selectConvexPieces(int size);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Selects all holeless pieces of the specified size
|
|
||||||
* @return If the pieces could be selected
|
|
||||||
*/
|
|
||||||
bool selectHolelessPieces(int size);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Selects all other pieces of the specified size
|
|
||||||
* @return If the pieces could be selected
|
|
||||||
*/
|
|
||||||
bool selectOtherPieces(int size);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unselects all previously selected pieces
|
|
||||||
*/
|
|
||||||
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
|
|
||||||
*/
|
|
||||||
int getHighestLoadedSize() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The number of pieces of the specified size
|
|
||||||
*/
|
|
||||||
int getNumberOfPieces(int size) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return A copy of the indexes of all selected pieces
|
|
||||||
*/
|
|
||||||
std::vector<std::pair<int, int>> getSelectedPieces() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The current distribution mode
|
|
||||||
*/
|
|
||||||
DistributionMode getDistributionMode() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The proportion of pieces for each loaded size
|
|
||||||
*/
|
|
||||||
std::vector<double> getProportionsPerSize() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return A copy of the piece corresponding to the specified index
|
|
||||||
*/
|
|
||||||
Piece getPiece(const std::pair<int, int>& pieceIndex) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The piece corresponding to the specified index
|
|
||||||
*/
|
|
||||||
const Piece& lookAtPiece(const std::pair<int, int>& pieceIndex) const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
/**
|
|
||||||
* Adds empty vectors at the end of every pieces list
|
|
||||||
*/
|
|
||||||
void pushBackEmptyVectors();
|
|
||||||
};
|
|
||||||
@@ -1,5 +1,12 @@
|
|||||||
#include "Player.h"
|
#include "Player.h"
|
||||||
|
|
||||||
|
static const int DAS_MIN_VALUE = 0;
|
||||||
|
static const int DAS_MAX_VALUE = 30;
|
||||||
|
static const int ARR_MIN_VALUE = 0;
|
||||||
|
static const int ARR_MAX_VALUE = 30;
|
||||||
|
static const int SDR_MIN_VALUE = 0;
|
||||||
|
static const int SDR_MAX_VALUE = 6;
|
||||||
|
|
||||||
|
|
||||||
Player::Player() {
|
Player::Player() {
|
||||||
// default settings
|
// default settings
|
||||||
@@ -29,14 +36,14 @@ bool Player::setSDR(int SDR) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Player::getDAS() const {
|
int Player::getDAS() {
|
||||||
return this->DAS;
|
return this->DAS;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Player::getARR() const {
|
int Player::getARR() {
|
||||||
return this->ARR;
|
return this->ARR;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Player::getSDR() const {
|
int Player::getSDR() {
|
||||||
return this->SDR;
|
return this->SDR;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
static const int DAS_MIN_VALUE = 0; // the minimal selectable DAS value, equals to 0ms
|
|
||||||
static const int DAS_MAX_VALUE = 30; // the maximal selectable DAS value, equals to 500ms
|
|
||||||
static const int ARR_MIN_VALUE = 0; // the minimal selectable ARR value, equals to 0ms
|
|
||||||
static const int ARR_MAX_VALUE = 30; // the maximal selectable ARR value, equals to 500ms
|
|
||||||
static const int SDR_MIN_VALUE = 0; // the minimal selectable SDR value, equals to 0ms
|
|
||||||
static const int SDR_MAX_VALUE = 6; // the maximal selectable SDR value, equals to 100ms
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The controls of a player
|
* The controls of a player
|
||||||
@@ -24,35 +17,32 @@ class Player {
|
|||||||
Player();
|
Player();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Try setting DAS to the desired value
|
* Try setting DAS to the desired value, and returns wheter it is possible
|
||||||
* @return If it is possible
|
|
||||||
*/
|
*/
|
||||||
bool setDAS(int DAS);
|
bool setDAS(int DAS);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Try setting ARR to the desired value
|
* Try setting ARR to the desired value, and returns wheter it is possible
|
||||||
* @return If it is possible
|
|
||||||
*/
|
*/
|
||||||
bool setARR(int ARR);
|
bool setARR(int ARR);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Try setting SDR to the desired value
|
* Try setting SDR to the desired value, and returns wheter it is possible
|
||||||
* @return If it is possible
|
|
||||||
*/
|
*/
|
||||||
bool setSDR(int SDR);
|
bool setSDR(int SDR);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return DAS value
|
* Returns DAS value
|
||||||
*/
|
*/
|
||||||
int getDAS() const;
|
int getDAS();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return ARR value
|
* Returns ARR value
|
||||||
*/
|
*/
|
||||||
int getARR() const;
|
int getARR();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return SDR value
|
* Returns SDR value
|
||||||
*/
|
*/
|
||||||
int getSDR() const;
|
int getSDR();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,60 +1,44 @@
|
|||||||
#include "../Pieces/Generator.h"
|
|
||||||
#include "../Pieces/PiecesFiles.h"
|
#include "../Pieces/PiecesFiles.h"
|
||||||
#include "TextApp.h"
|
#include "../Pieces/Generator.h"
|
||||||
|
#include "GameBoard.h"
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <string>
|
||||||
|
#include <algorithm>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <cmath>
|
#include <fstream>
|
||||||
|
|
||||||
static const int MAXIMUM_PIECES_SIZE = 10;
|
|
||||||
|
|
||||||
void testGeneratorForAllSizes(int max_size);
|
void testGeneratorForAllSizes(int amount);
|
||||||
void testGeneratorForOneSize(int size);
|
void testGeneratorForOneSize(int size);
|
||||||
void printPiecesByTypesForOneSize(int size);
|
void testGeneratorByprintingAllNminos(int n);
|
||||||
void readStatsFromFilesForAllSizes(int max_size);
|
void testStoringAndRetrievingPieces(int size);
|
||||||
|
void generateFilesForAllSizes(int amount);
|
||||||
|
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));
|
||||||
|
|
||||||
// CHECK PIECES FILES
|
|
||||||
|
|
||||||
PiecesFiles pf;
|
|
||||||
bool warned = false;
|
|
||||||
for (int i = 1; i <= MAXIMUM_PIECES_SIZE; i++) {
|
|
||||||
if (!std::filesystem::exists("data/pieces/" + std::to_string(i) + "minos.bin")) {
|
|
||||||
if (!warned) {
|
|
||||||
std::cout << "INFO: Pieces files for size " << i << " not found, generating..." << std::endl;
|
|
||||||
warned = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
pf.savePieces(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// LAUNCH APP
|
|
||||||
|
|
||||||
TextApp UI;
|
|
||||||
UI.run();
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void testGeneratorForAllSizes(int max_size) {
|
void testGeneratorForAllSizes(int amount) {
|
||||||
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 <= max_size; i++) {
|
for (int i = 1; i <= amount; 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.generatePolyominos(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() << " polyominos of size " << i << " in " << ms_double.count() << "ms" << std::endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +52,7 @@ void testGeneratorForOneSize(int size) {
|
|||||||
std::cout << "Generating " << size << "-minos" << std::endl;
|
std::cout << "Generating " << size << "-minos" << std::endl;
|
||||||
for (int i = 0; i < 10; i++) {
|
for (int i = 0; i < 10; i++) {
|
||||||
auto t1 = high_resolution_clock::now();
|
auto t1 = high_resolution_clock::now();
|
||||||
std::vector<Polyomino> n_minos = generator.generatePolyominoes(size);
|
std::vector<Polyomino> n_minos = generator.generatePolyominos(size);
|
||||||
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;
|
||||||
@@ -76,8 +60,24 @@ void testGeneratorForOneSize(int size) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void printPiecesByTypesForOneSize(int size) {
|
void testGeneratorByprintingAllNminos(int n) {
|
||||||
|
Generator generator;
|
||||||
|
std::vector<Polyomino> n_minos = generator.generatePolyominos(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;
|
||||||
@@ -101,9 +101,39 @@ void printPiecesByTypesForOneSize(int size) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void readStatsFromFilesForAllSizes(int max_size) {
|
void generateFilesForAllSizes(int amount) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 1; i <= amount; i++) {
|
||||||
|
auto t1 = high_resolution_clock::now();
|
||||||
|
std::vector<Piece> pieces;
|
||||||
|
std::vector<int> convexPieces;
|
||||||
|
std::vector<int> holelessPieces;
|
||||||
|
std::vector<int> otherPieces;
|
||||||
|
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 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;
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
#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));
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "../Settings.h"
|
|
||||||
#include "../PlayerCursor.h"
|
|
||||||
|
|
||||||
#include <stack>
|
|
||||||
#include <memory>
|
|
||||||
#include <optional>
|
|
||||||
#include <SFML/Graphics.hpp>
|
|
||||||
|
|
||||||
class AppMenu;
|
|
||||||
using MenuStack = std::stack<std::shared_ptr<AppMenu>>;
|
|
||||||
|
|
||||||
|
|
||||||
class AppMenu {
|
|
||||||
protected:
|
|
||||||
std::shared_ptr<MenuStack> menuStack;
|
|
||||||
std::shared_ptr<Settings> settings;
|
|
||||||
std::shared_ptr<sf::RenderWindow> renderWindow;
|
|
||||||
bool enterPressed = false;
|
|
||||||
bool enterReleased = false;
|
|
||||||
bool escPressed = false;
|
|
||||||
bool escReleased = false;
|
|
||||||
sf::Font pressStartFont;
|
|
||||||
|
|
||||||
public:
|
|
||||||
AppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow);
|
|
||||||
|
|
||||||
virtual void computeFrame() = 0;
|
|
||||||
|
|
||||||
virtual void drawFrame() const = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void updateMetaBinds();
|
|
||||||
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
#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();
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
#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;
|
|
||||||
};
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
#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->getLoadablePiecesSize(); 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->getLoadablePiecesSize() - 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();
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
#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;
|
|
||||||
};
|
|
||||||
@@ -1,234 +0,0 @@
|
|||||||
#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->getLoadablePiecesSize(); i++) {
|
|
||||||
this->playerCursor.addRow(i + 1, this->settings->getMenu().readPiecesList().getNumberOfPieces(i) + 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->settings->getLoadablePiecesSize() < MAXIMUM_PIECES_SIZE) {
|
|
||||||
this->playerCursor.addRow(this->settings->getLoadablePiecesSize() + 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->getLoadablePiecesSize() + 2)) {
|
|
||||||
int newMaxSize = this->settings->getLoadablePiecesSize() + 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->getLoadablePiecesSize() < MAXIMUM_PIECES_SIZE);
|
|
||||||
int firstElem = std::clamp(((int) this->playerCursor.getPosition().y) - 2, 1, this->settings->getLoadablePiecesSize() - 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->getLoadablePiecesSize())) {
|
|
||||||
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->getLoadablePiecesSize())) {
|
|
||||||
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->getLoadablePiecesSize()) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
#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;
|
|
||||||
};
|
|
||||||
@@ -1,293 +0,0 @@
|
|||||||
#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).containsSquare(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()->containsSquare(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));
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
#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;
|
|
||||||
};
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
#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();
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
#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;
|
|
||||||
};
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
#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();
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
#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;
|
|
||||||
};
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
#include "MainAppMenu.h"
|
|
||||||
|
|
||||||
#include "AppMenu.h"
|
|
||||||
#include "GameSettingsAppMenu.h"
|
|
||||||
#include "SettingsMainAppMenu.h"
|
|
||||||
#include "InfoAppMenu.h"
|
|
||||||
#include "../PlayerCursor.h"
|
|
||||||
|
|
||||||
#include <stack>
|
|
||||||
#include <memory>
|
|
||||||
#include <SFML/Graphics.hpp>
|
|
||||||
|
|
||||||
|
|
||||||
MainAppMenu::MainAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow) :
|
|
||||||
AppMenu(menuStack, settings, renderWindow),
|
|
||||||
playerCursor({1, 1, 1}) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void MainAppMenu::computeFrame() {
|
|
||||||
this->updateMetaBinds();
|
|
||||||
this->playerCursor.updatePosition();
|
|
||||||
|
|
||||||
if (this->enterReleased) {
|
|
||||||
if (this->playerCursor.getPosition().y == 0) {
|
|
||||||
this->menuStack->push(std::make_shared<GameSettingsAppMenu>(this->menuStack, this->settings, this->renderWindow));
|
|
||||||
}
|
|
||||||
if (this->playerCursor.getPosition().y == 1) {
|
|
||||||
this->menuStack->push(std::make_shared<SettingsMainAppMenu>(this->menuStack, this->settings, this->renderWindow));
|
|
||||||
}
|
|
||||||
if (this->playerCursor.getPosition().y == 2) {
|
|
||||||
this->menuStack->push(std::make_shared<InfoAppMenu>(this->menuStack, this->settings, this->renderWindow));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this->escReleased) {
|
|
||||||
this->menuStack->pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MainAppMenu::drawFrame() const {
|
|
||||||
this->renderWindow->clear(sf::Color(200, 200, 200));
|
|
||||||
|
|
||||||
sf::Text text(this->pressStartFont, "", this->settings->getWindowSizeMultiplier() * 2);
|
|
||||||
text.setFillColor(sf::Color(0, 0, 0));
|
|
||||||
text.setOutlineColor(sf::Color(255, 255, 255));
|
|
||||||
|
|
||||||
this->placeTitle(text, {}, "JMINOS", 10.f, {});
|
|
||||||
|
|
||||||
this->placeTitle(text, this->playerCursor, "PLAY", 20.f, sf::Vector2u{0, 0});
|
|
||||||
this->placeTitle(text, this->playerCursor, "SETTINGS", 30.f, sf::Vector2u{0, 1});
|
|
||||||
this->placeTitle(text, this->playerCursor, "INFO", 40.f, sf::Vector2u{0, 2});
|
|
||||||
|
|
||||||
this->renderWindow->display();
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "AppMenu.h"
|
|
||||||
#include "../PlayerCursor.h"
|
|
||||||
|
|
||||||
#include <stack>
|
|
||||||
#include <memory>
|
|
||||||
#include <SFML/Graphics.hpp>
|
|
||||||
|
|
||||||
|
|
||||||
class MainAppMenu : public AppMenu {
|
|
||||||
private:
|
|
||||||
PlayerCursor playerCursor;
|
|
||||||
|
|
||||||
public:
|
|
||||||
MainAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow);
|
|
||||||
|
|
||||||
void computeFrame() override;
|
|
||||||
|
|
||||||
void drawFrame() const override;
|
|
||||||
};
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
#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();
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
#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;
|
|
||||||
};
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
#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();
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
#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;
|
|
||||||
};
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
#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();
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
#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;
|
|
||||||
};
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
#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->getLoadablePiecesSize(), 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();
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
#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;
|
|
||||||
};
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
#include "GraphApp.h"
|
|
||||||
|
|
||||||
#include "AppMenus/AppMenu.h"
|
|
||||||
#include "AppMenus/StartUpAppMenu.h"
|
|
||||||
#include "Settings.h"
|
|
||||||
|
|
||||||
#include <stack>
|
|
||||||
#include <memory>
|
|
||||||
#include <SFML/Graphics.hpp>
|
|
||||||
|
|
||||||
static const double TIME_BETWEEN_FRAMES = (1000.f / FRAMES_PER_SECOND);
|
|
||||||
|
|
||||||
|
|
||||||
GraphApp::GraphApp() {
|
|
||||||
this->settings = std::make_shared<Settings>(false);
|
|
||||||
this->menuStack = std::make_shared<MenuStack>();
|
|
||||||
this->renderWindow = std::make_shared<sf::RenderWindow>();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GraphApp::run() {
|
|
||||||
this->settings->changeVideoMode(*this->renderWindow);
|
|
||||||
this->menuStack->push(std::make_shared<StartUpAppMenu>(this->menuStack, this->settings, this->renderWindow));
|
|
||||||
|
|
||||||
bool quit = false;
|
|
||||||
double timeAtNextFrame = 0;
|
|
||||||
sf::Clock clock;
|
|
||||||
while (!quit) {
|
|
||||||
while (const std::optional event = this->renderWindow->pollEvent()) {
|
|
||||||
if (event->is<sf::Event::Closed>()) {
|
|
||||||
quit = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!quit) {
|
|
||||||
if (clock.getElapsedTime().asMilliseconds() > timeAtNextFrame) {
|
|
||||||
this->menuStack->top()->computeFrame();
|
|
||||||
|
|
||||||
if (this->menuStack->empty()) {
|
|
||||||
quit = true;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this->menuStack->top()->drawFrame();
|
|
||||||
}
|
|
||||||
|
|
||||||
while (clock.getElapsedTime().asMilliseconds() > timeAtNextFrame) {
|
|
||||||
timeAtNextFrame += TIME_BETWEEN_FRAMES;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this->settings->saveSettingsToFile();
|
|
||||||
renderWindow->close();
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "AppMenus/AppMenu.h"
|
|
||||||
#include "Settings.h"
|
|
||||||
|
|
||||||
#include <stack>
|
|
||||||
#include <memory>
|
|
||||||
#include <SFML/Graphics.hpp>
|
|
||||||
|
|
||||||
|
|
||||||
class GraphApp {
|
|
||||||
private:
|
|
||||||
std::shared_ptr<Settings> settings;
|
|
||||||
std::shared_ptr<MenuStack> menuStack;
|
|
||||||
std::shared_ptr<sf::RenderWindow> renderWindow;
|
|
||||||
|
|
||||||
public:
|
|
||||||
GraphApp();
|
|
||||||
|
|
||||||
void run();
|
|
||||||
};
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
#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);
|
|
||||||
}
|
|
||||||
@@ -1,156 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "../Core/Action.h"
|
|
||||||
|
|
||||||
#include <map>
|
|
||||||
#include <set>
|
|
||||||
#include <algorithm>
|
|
||||||
#include <SFML/Graphics.hpp>
|
|
||||||
|
|
||||||
using sfKey = sf::Keyboard::Key;
|
|
||||||
|
|
||||||
static const int NUMBER_OF_KEYBINDS = 5;
|
|
||||||
static const int CUSTOMIZABLE_KEYBINDS = NUMBER_OF_KEYBINDS - 1;
|
|
||||||
|
|
||||||
|
|
||||||
class Keybinds {
|
|
||||||
private:
|
|
||||||
std::map<Action, std::set<sfKey>> keybinds;
|
|
||||||
int layoutNumber;
|
|
||||||
bool modifiable;
|
|
||||||
|
|
||||||
public:
|
|
||||||
Keybinds(int layoutNumber);
|
|
||||||
|
|
||||||
void loadKeybindsFromFile();
|
|
||||||
|
|
||||||
void saveKeybindsToFile() const;
|
|
||||||
|
|
||||||
void addKey(Action action, sfKey key);
|
|
||||||
|
|
||||||
void clearKeys(Action action);
|
|
||||||
|
|
||||||
bool isModifiable() const;
|
|
||||||
|
|
||||||
const std::set<Action> getActions(sfKey key) const;
|
|
||||||
|
|
||||||
const std::set<sfKey>& getKeybinds(Action action) const;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
inline std::string setStringToUpperCase(std::string&& str) {
|
|
||||||
std::transform(str.begin(), str.end(), str.begin(), ::toupper);
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline std::string setStringToUpperCase(const std::string& str) {
|
|
||||||
std::string result = str;
|
|
||||||
std::transform(result.begin(), result.end(), result.begin(), ::toupper);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define INSERT_MAPPING(identifier) {sfKey::identifier, setStringToUpperCase(#identifier)}
|
|
||||||
static const std::map<sfKey, sf::String> KEYS_TO_STRING = {
|
|
||||||
INSERT_MAPPING(A),
|
|
||||||
INSERT_MAPPING(B),
|
|
||||||
INSERT_MAPPING(C),
|
|
||||||
INSERT_MAPPING(D),
|
|
||||||
INSERT_MAPPING(E),
|
|
||||||
INSERT_MAPPING(F),
|
|
||||||
INSERT_MAPPING(G),
|
|
||||||
INSERT_MAPPING(H),
|
|
||||||
INSERT_MAPPING(I),
|
|
||||||
INSERT_MAPPING(J),
|
|
||||||
INSERT_MAPPING(K),
|
|
||||||
INSERT_MAPPING(L),
|
|
||||||
INSERT_MAPPING(M),
|
|
||||||
INSERT_MAPPING(N),
|
|
||||||
INSERT_MAPPING(O),
|
|
||||||
INSERT_MAPPING(P),
|
|
||||||
INSERT_MAPPING(Q),
|
|
||||||
INSERT_MAPPING(R),
|
|
||||||
INSERT_MAPPING(S),
|
|
||||||
INSERT_MAPPING(T),
|
|
||||||
INSERT_MAPPING(U),
|
|
||||||
INSERT_MAPPING(V),
|
|
||||||
INSERT_MAPPING(W),
|
|
||||||
INSERT_MAPPING(X),
|
|
||||||
INSERT_MAPPING(Y),
|
|
||||||
INSERT_MAPPING(Z),
|
|
||||||
INSERT_MAPPING(Num0),
|
|
||||||
INSERT_MAPPING(Num1),
|
|
||||||
INSERT_MAPPING(Num2),
|
|
||||||
INSERT_MAPPING(Num3),
|
|
||||||
INSERT_MAPPING(Num4),
|
|
||||||
INSERT_MAPPING(Num5),
|
|
||||||
INSERT_MAPPING(Num6),
|
|
||||||
INSERT_MAPPING(Num7),
|
|
||||||
INSERT_MAPPING(Num8),
|
|
||||||
INSERT_MAPPING(Num9),
|
|
||||||
INSERT_MAPPING(Escape),
|
|
||||||
INSERT_MAPPING(LControl),
|
|
||||||
INSERT_MAPPING(LShift),
|
|
||||||
INSERT_MAPPING(LAlt),
|
|
||||||
INSERT_MAPPING(LSystem),
|
|
||||||
INSERT_MAPPING(RControl),
|
|
||||||
INSERT_MAPPING(RShift),
|
|
||||||
INSERT_MAPPING(RAlt),
|
|
||||||
INSERT_MAPPING(RSystem),
|
|
||||||
INSERT_MAPPING(Menu),
|
|
||||||
INSERT_MAPPING(LBracket),
|
|
||||||
INSERT_MAPPING(RBracket),
|
|
||||||
INSERT_MAPPING(Semicolon),
|
|
||||||
INSERT_MAPPING(Comma),
|
|
||||||
INSERT_MAPPING(Period),
|
|
||||||
INSERT_MAPPING(Apostrophe),
|
|
||||||
INSERT_MAPPING(Slash),
|
|
||||||
INSERT_MAPPING(Backslash),
|
|
||||||
INSERT_MAPPING(Grave),
|
|
||||||
INSERT_MAPPING(Equal),
|
|
||||||
INSERT_MAPPING(Hyphen),
|
|
||||||
INSERT_MAPPING(Space),
|
|
||||||
INSERT_MAPPING(Enter),
|
|
||||||
INSERT_MAPPING(Backspace),
|
|
||||||
INSERT_MAPPING(Tab),
|
|
||||||
INSERT_MAPPING(PageUp),
|
|
||||||
INSERT_MAPPING(PageDown),
|
|
||||||
INSERT_MAPPING(End),
|
|
||||||
INSERT_MAPPING(Home),
|
|
||||||
INSERT_MAPPING(Insert),
|
|
||||||
INSERT_MAPPING(Delete),
|
|
||||||
INSERT_MAPPING(Add),
|
|
||||||
INSERT_MAPPING(Subtract),
|
|
||||||
INSERT_MAPPING(Multiply),
|
|
||||||
INSERT_MAPPING(Divide),
|
|
||||||
INSERT_MAPPING(Left),
|
|
||||||
INSERT_MAPPING(Right),
|
|
||||||
INSERT_MAPPING(Up),
|
|
||||||
INSERT_MAPPING(Down),
|
|
||||||
INSERT_MAPPING(Numpad0),
|
|
||||||
INSERT_MAPPING(Numpad1),
|
|
||||||
INSERT_MAPPING(Numpad2),
|
|
||||||
INSERT_MAPPING(Numpad3),
|
|
||||||
INSERT_MAPPING(Numpad4),
|
|
||||||
INSERT_MAPPING(Numpad5),
|
|
||||||
INSERT_MAPPING(Numpad6),
|
|
||||||
INSERT_MAPPING(Numpad7),
|
|
||||||
INSERT_MAPPING(Numpad8),
|
|
||||||
INSERT_MAPPING(Numpad9),
|
|
||||||
INSERT_MAPPING(F1),
|
|
||||||
INSERT_MAPPING(F2),
|
|
||||||
INSERT_MAPPING(F3),
|
|
||||||
INSERT_MAPPING(F4),
|
|
||||||
INSERT_MAPPING(F5),
|
|
||||||
INSERT_MAPPING(F6),
|
|
||||||
INSERT_MAPPING(F7),
|
|
||||||
INSERT_MAPPING(F8),
|
|
||||||
INSERT_MAPPING(F9),
|
|
||||||
INSERT_MAPPING(F10),
|
|
||||||
INSERT_MAPPING(F11),
|
|
||||||
INSERT_MAPPING(F12),
|
|
||||||
INSERT_MAPPING(F13),
|
|
||||||
INSERT_MAPPING(F14),
|
|
||||||
INSERT_MAPPING(F15),
|
|
||||||
INSERT_MAPPING(Pause)
|
|
||||||
};
|
|
||||||
#undef INSERT_MAPPING
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
#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];
|
|
||||||
}
|
|
||||||
@@ -1,163 +0,0 @@
|
|||||||
#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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
#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();
|
|
||||||
};
|
|
||||||
@@ -1,416 +0,0 @@
|
|||||||
#include "Settings.h"
|
|
||||||
|
|
||||||
#include "../Core/Menu.h"
|
|
||||||
#include "Keybinds.h"
|
|
||||||
#include "PiecesType.h"
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
#include <optional>
|
|
||||||
#include <fstream>
|
|
||||||
#include <algorithm>
|
|
||||||
#include <SFML/Graphics.hpp>
|
|
||||||
|
|
||||||
static const sf::Vector2u BASE_WINDOW_SIZE = {80, 50};
|
|
||||||
static const int WINDOW_SIZE_MULTIPLIERS[] = {4, 6, 10, 14, 20, 30, 40};
|
|
||||||
static const int WINDOW_SIZE_LAST_MODE = (sizeof(WINDOW_SIZE_MULTIPLIERS) / sizeof(int)) - 1;
|
|
||||||
static const int START_TIMER_MAX = 4;
|
|
||||||
static const int DISTRIBUTION_MAX = 20;
|
|
||||||
|
|
||||||
|
|
||||||
Settings::Settings(bool loadPieces) {
|
|
||||||
this->keybinds.clear();
|
|
||||||
this->keybinds.reserve(NUMBER_OF_KEYBINDS);
|
|
||||||
for (int i = 0; i < NUMBER_OF_KEYBINDS; i++) {
|
|
||||||
this->keybinds.emplace_back(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
this->loadSettingsFromFile(loadPieces, {});
|
|
||||||
}
|
|
||||||
|
|
||||||
void Settings::loadPieces(int loadablePiecesSizeRequest) {
|
|
||||||
if (loadablePiecesSizeRequest < MINIMUM_PIECES_SIZE) {
|
|
||||||
loadablePiecesSizeRequest = MINIMUM_PIECES_SIZE;
|
|
||||||
}
|
|
||||||
else if (loadablePiecesSizeRequest > MAXIMUM_PIECES_SIZE) {
|
|
||||||
loadablePiecesSizeRequest = MAXIMUM_PIECES_SIZE;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool succeeded = true;
|
|
||||||
int i = 1;
|
|
||||||
while (succeeded && (i <= loadablePiecesSizeRequest)) {
|
|
||||||
succeeded = this->menu.getPiecesList().loadPieces(i);
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (succeeded) {
|
|
||||||
this->loadablePiecesSize = loadablePiecesSizeRequest;
|
|
||||||
}
|
|
||||||
this->loadedPieces = succeeded;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Settings::loadSettingsFromFile(bool loadPieces, std::optional<int> loadablePiecesSizeRequest) {
|
|
||||||
std::ifstream settingsFile("data/config/settings.bin", std::ios::binary);
|
|
||||||
char byte;
|
|
||||||
|
|
||||||
// file format version
|
|
||||||
settingsFile.get(byte);
|
|
||||||
|
|
||||||
// loadable pieces size
|
|
||||||
settingsFile.get(byte);
|
|
||||||
this->loadablePiecesSize = byte;
|
|
||||||
|
|
||||||
if (loadPieces) {
|
|
||||||
if (loadablePiecesSizeRequest.has_value()) {
|
|
||||||
this->loadPieces(loadablePiecesSizeRequest.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 {
|
|
||||||
if (!this->loadedPieces) return;
|
|
||||||
|
|
||||||
this->keybinds.at(CUSTOMIZABLE_KEYBINDS).saveKeybindsToFile();
|
|
||||||
|
|
||||||
std::ofstream settingsFile("data/config/settings.bin", std::ios::trunc | std::ios::binary);
|
|
||||||
char byte;
|
|
||||||
|
|
||||||
// file format version
|
|
||||||
byte = CURRENT_FILE_FORMAT_VERSION;
|
|
||||||
settingsFile.write(&byte, 1);
|
|
||||||
|
|
||||||
// loadable pieces size
|
|
||||||
byte = this->loadablePiecesSize;
|
|
||||||
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() {
|
|
||||||
if (this->chosenKeybinds < (NUMBER_OF_KEYBINDS - 1)) {
|
|
||||||
this->chosenKeybinds++;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Settings::selectPreviousKeybinds() {
|
|
||||||
if (this->chosenKeybinds > 0) {
|
|
||||||
this->chosenKeybinds--;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Settings::canModifyCurrentKeybinds() const {
|
|
||||||
return (this->chosenKeybinds == CUSTOMIZABLE_KEYBINDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Settings::widenWindow() {
|
|
||||||
if (this->windowSizeMode < WINDOW_SIZE_LAST_MODE) {
|
|
||||||
this->windowSizeMode++;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Settings::shortenWindow() {
|
|
||||||
if (this->windowSizeMode > 0) {
|
|
||||||
this->windowSizeMode--;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Settings::changeVideoMode(sf::RenderWindow& window) const {
|
|
||||||
sf::VideoMode videoMode(BASE_WINDOW_SIZE * (unsigned int) WINDOW_SIZE_MULTIPLIERS[this->windowSizeMode]);
|
|
||||||
window.create(videoMode, "jminos", sf::Style::Close | sf::Style::Titlebar);
|
|
||||||
|
|
||||||
sf::Vector2u desktopSize = sf::VideoMode::getDesktopMode().size;
|
|
||||||
sf::Vector2u windowSize = window.getSize();
|
|
||||||
window.setPosition(sf::Vector2i((desktopSize.x / 2) - (windowSize.x / 2), (desktopSize.y / 2) - (windowSize.y / 2)));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Settings::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) {
|
|
||||||
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->loadablePiecesSize)) {
|
|
||||||
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->loadablePiecesSize)) {
|
|
||||||
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->loadablePiecesSize) 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->loadablePiecesSize) 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() {
|
|
||||||
return this->menu;
|
|
||||||
}
|
|
||||||
|
|
||||||
Keybinds& Settings::getKeybinds() {
|
|
||||||
return this->keybinds.at(this->chosenKeybinds);
|
|
||||||
}
|
|
||||||
|
|
||||||
int Settings::getLoadablePiecesSize() const {
|
|
||||||
return this->loadablePiecesSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Settings::hasLoadedPieces() const {
|
|
||||||
return this->loadedPieces;
|
|
||||||
}
|
|
||||||
|
|
||||||
int Settings::getKeybindsLayout() const {
|
|
||||||
return this->chosenKeybinds;
|
|
||||||
}
|
|
||||||
|
|
||||||
Gamemode Settings::getGamemode() const {
|
|
||||||
return this->gamemode;
|
|
||||||
}
|
|
||||||
|
|
||||||
int Settings::getWindowSizeMultiplier() const {
|
|
||||||
return WINDOW_SIZE_MULTIPLIERS[this->windowSizeMode];
|
|
||||||
}
|
|
||||||
|
|
||||||
int Settings::getStartTimerLength() const {
|
|
||||||
return this->startTimerLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::vector<std::pair<PiecesType, int>>& Settings::getSelectedPieces() const {
|
|
||||||
return this->selectedPieces;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::vector<int>& Settings::getDistributions() const {
|
|
||||||
return this->distributions;
|
|
||||||
}
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "../Core/Menu.h"
|
|
||||||
#include "Keybinds.h"
|
|
||||||
#include "PiecesType.h"
|
|
||||||
|
|
||||||
#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 {
|
|
||||||
private:
|
|
||||||
Menu menu;
|
|
||||||
int loadablePiecesSize;
|
|
||||||
bool loadedPieces;
|
|
||||||
std::vector<Keybinds> keybinds;
|
|
||||||
int chosenKeybinds;
|
|
||||||
int windowSizeMode;
|
|
||||||
int startTimerLength;
|
|
||||||
Gamemode gamemode;
|
|
||||||
std::vector<std::pair<PiecesType, int>> selectedPieces;
|
|
||||||
std::vector<int> distributions;
|
|
||||||
|
|
||||||
public:
|
|
||||||
Settings(bool loadPieces);
|
|
||||||
|
|
||||||
void loadPieces(int loadablePiecesSizeRequest);
|
|
||||||
|
|
||||||
void loadSettingsFromFile(bool loadPieces, std::optional<int> loadablePiecesSizeRequest);
|
|
||||||
|
|
||||||
void saveSettingsToFile() const;
|
|
||||||
|
|
||||||
bool selectNextKeybinds();
|
|
||||||
|
|
||||||
bool selectPreviousKeybinds();
|
|
||||||
|
|
||||||
bool canModifyCurrentKeybinds() const;
|
|
||||||
|
|
||||||
bool widenWindow();
|
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
Keybinds& getKeybinds();
|
|
||||||
|
|
||||||
int getLoadablePiecesSize() const;
|
|
||||||
|
|
||||||
bool hasLoadedPieces() const;
|
|
||||||
|
|
||||||
int getKeybindsLayout() const;
|
|
||||||
|
|
||||||
Gamemode getGamemode() const;
|
|
||||||
|
|
||||||
int getWindowSizeMultiplier() const;
|
|
||||||
|
|
||||||
int getStartTimerLength() const;
|
|
||||||
|
|
||||||
const std::vector<std::pair<PiecesType, int>>& getSelectedPieces() const;
|
|
||||||
|
|
||||||
const std::vector<int>& getDistributions() const;
|
|
||||||
};
|
|
||||||
@@ -1,265 +0,0 @@
|
|||||||
#include "GraphApp.h"
|
|
||||||
#include "../Pieces/PiecesFiles.h"
|
|
||||||
|
|
||||||
#include <filesystem>
|
|
||||||
#include <fstream>
|
|
||||||
|
|
||||||
[[nodiscard]] bool resetSettingsFile();
|
|
||||||
[[nodiscard]] bool resetKeybindFile(int layout);
|
|
||||||
|
|
||||||
|
|
||||||
int main() {
|
|
||||||
std::srand(std::time(NULL));
|
|
||||||
|
|
||||||
bool everythingIsOK = true;
|
|
||||||
|
|
||||||
// CHECK PIECES FILES
|
|
||||||
|
|
||||||
PiecesFiles pf;
|
|
||||||
bool warned = false;
|
|
||||||
for (int i = 1; i <= MAXIMUM_PIECES_SIZE; i++) {
|
|
||||||
if (!std::filesystem::exists("data/pieces/" + std::to_string(i) + "minos.bin")) {
|
|
||||||
#ifndef DEBUG
|
|
||||||
if (!warned && i > DEBUG_PIECES_SIZE) {
|
|
||||||
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;
|
|
||||||
warned = true;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
std::cout << "INFO: Pieces files for size " << i << " not found, generating..." << std::endl;
|
|
||||||
everythingIsOK &= 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 releasePiecesGenerated = true;
|
|
||||||
for (int i = DEBUG_PIECES_SIZE + 1; i <= RELEASE_PIECES_SIZE; i++) {
|
|
||||||
if (!std::filesystem::exists("data/pieces/" + std::to_string(i) + "minos.bin")) {
|
|
||||||
releasePiecesGenerated = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!releasePiecesGenerated) {
|
|
||||||
std::cout << "NOTE: You do not have all pieces generated, generating can take several minutes." << std::endl;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
bool everythingGenerated = true;
|
|
||||||
for (int i = 1; i <= MAXIMUM_PIECES_SIZE; i++) {
|
|
||||||
std::string filePath = "data/pieces/" + std::to_string(i) + "minos.bin";
|
|
||||||
|
|
||||||
if (!std::filesystem::exists(filePath)) {
|
|
||||||
std::cout << "ERROR: Could not open file " + filePath << std::endl;
|
|
||||||
everythingIsOK &= false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CHECK CONFIG FILES
|
|
||||||
|
|
||||||
if (!std::filesystem::exists("data/config/settings.bin")) {
|
|
||||||
std::cout << "INFO: Settings file not found, generating..." << std::endl;
|
|
||||||
everythingIsOK &= resetSettingsFile();
|
|
||||||
|
|
||||||
for (int i = 0; i < NUMBER_OF_KEYBINDS; i++) {
|
|
||||||
if (!std::filesystem::exists("data/config/keybinds/layout" + std::to_string(i) + ".bin")) {
|
|
||||||
std::cout << "INFO: Keybind file number " << (i + 1) << "/" << NUMBER_OF_KEYBINDS << " not found, generating..." << std::endl;
|
|
||||||
everythingIsOK &= resetKeybindFile(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
everythingIsOK &= resetSettingsFile();
|
|
||||||
for (int i = 0; i < NUMBER_OF_KEYBINDS; i++) {
|
|
||||||
everythingIsOK &= resetKeybindFile(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// LAUNCH APP
|
|
||||||
|
|
||||||
if (everythingIsOK) {
|
|
||||||
GraphApp UI;
|
|
||||||
UI.run();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
std::cout << "ERROR: The game could not launch properly." << std::endl;
|
|
||||||
std::cout << "Press enter to quit. ";
|
|
||||||
std::cin.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool resetSettingsFile() {
|
|
||||||
if (!std::filesystem::exists("data/config")) {
|
|
||||||
std::filesystem::create_directories("data/config");
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string filePath ="data/config/settings.bin";
|
|
||||||
std::ofstream settingsFile(filePath, std::ios::trunc | std::ios::binary);
|
|
||||||
if (!settingsFile.good()) {
|
|
||||||
std::cerr << "ERROR: Could not open file " + filePath << std::endl;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
char byte;
|
|
||||||
Menu menu;
|
|
||||||
|
|
||||||
// file format version
|
|
||||||
byte = CURRENT_FILE_FORMAT_VERSION;
|
|
||||||
settingsFile.write(&byte, 1);
|
|
||||||
|
|
||||||
// loadable pieces size
|
|
||||||
byte = MINIMUM_PIECES_SIZE;
|
|
||||||
settingsFile.write(&byte, 1);
|
|
||||||
|
|
||||||
// keybind layout
|
|
||||||
byte = 0;
|
|
||||||
settingsFile.write(&byte, 1);
|
|
||||||
|
|
||||||
// DAS tuning
|
|
||||||
byte = menu.getPlayerControls().getDAS();
|
|
||||||
settingsFile.write(&byte, 1);
|
|
||||||
|
|
||||||
// ARR tuning
|
|
||||||
byte = menu.getPlayerControls().getARR();
|
|
||||||
settingsFile.write(&byte, 1);
|
|
||||||
|
|
||||||
// SDR tuning
|
|
||||||
byte = menu.getPlayerControls().getSDR();
|
|
||||||
settingsFile.write(&byte, 1);
|
|
||||||
|
|
||||||
// window size mode
|
|
||||||
byte = 2;
|
|
||||||
settingsFile.write(&byte, 1);
|
|
||||||
|
|
||||||
// start timer length
|
|
||||||
byte = 2;
|
|
||||||
settingsFile.write(&byte, 1);
|
|
||||||
|
|
||||||
// gamemode
|
|
||||||
byte = Gamemode(0);
|
|
||||||
settingsFile.write(&byte, 1);
|
|
||||||
|
|
||||||
// board width
|
|
||||||
byte = menu.getBoardWidth();
|
|
||||||
settingsFile.write(&byte, 1);
|
|
||||||
|
|
||||||
// board height
|
|
||||||
byte = menu.getBoardHeight();
|
|
||||||
settingsFile.write(&byte, 1);
|
|
||||||
|
|
||||||
// piece distribution
|
|
||||||
byte = DEFAULT;
|
|
||||||
settingsFile.write(&byte, 1);
|
|
||||||
for (int i = 1; i <= 15; i++) {
|
|
||||||
byte = 1;
|
|
||||||
settingsFile.write(&byte, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// selected pieces
|
|
||||||
byte = DEFAULT_SELECTION.first;
|
|
||||||
settingsFile.write(&byte, 1);
|
|
||||||
byte = DEFAULT_SELECTION.second;
|
|
||||||
settingsFile.write(&byte, 1);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool resetKeybindFile(int layout) {
|
|
||||||
if (layout < 0 || layout > NUMBER_OF_KEYBINDS) {
|
|
||||||
std::cerr << "ERROR: Trying to create keybind layout number " << layout << " which is outside of range (" << NUMBER_OF_KEYBINDS << ")." << std::endl;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!std::filesystem::exists("data/config/keybinds")) {
|
|
||||||
std::filesystem::create_directories("data/config/keybinds");
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string filePath = "data/config/keybinds/layout" + std::to_string(layout) + ".bin";
|
|
||||||
std::ofstream layoutFile(filePath, std::ios::trunc | std::ios::binary);
|
|
||||||
if (!layoutFile.good()) {
|
|
||||||
std::cerr << "ERROR: Could not open file " + filePath << std::endl;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (layout == NUMBER_OF_KEYBINDS) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::map<Action, sfKey> keybinds;
|
|
||||||
|
|
||||||
if (layout != 4) {
|
|
||||||
keybinds.insert({PAUSE, sfKey::P});
|
|
||||||
keybinds.insert({RETRY, sfKey::R});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (layout == 0) {
|
|
||||||
keybinds.insert({MOVE_LEFT, sfKey::Left});
|
|
||||||
keybinds.insert({MOVE_RIGHT, sfKey::Right});
|
|
||||||
keybinds.insert({SOFT_DROP, sfKey::Down});
|
|
||||||
keybinds.insert({HARD_DROP, sfKey::Space});
|
|
||||||
keybinds.insert({ROTATE_CW, sfKey::Up});
|
|
||||||
keybinds.insert({ROTATE_CCW, sfKey::Z});
|
|
||||||
keybinds.insert({ROTATE_180, sfKey::X});
|
|
||||||
keybinds.insert({ROTATE_0, sfKey::LShift});
|
|
||||||
keybinds.insert({HOLD, sfKey::C});
|
|
||||||
}
|
|
||||||
if (layout == 1) {
|
|
||||||
keybinds.insert({MOVE_LEFT, sfKey::Z});
|
|
||||||
keybinds.insert({MOVE_RIGHT, sfKey::C});
|
|
||||||
keybinds.insert({SOFT_DROP, sfKey::X});
|
|
||||||
keybinds.insert({HARD_DROP, sfKey::S});
|
|
||||||
keybinds.insert({ROTATE_CW, sfKey::M});
|
|
||||||
keybinds.insert({ROTATE_CCW, sfKey::Comma});
|
|
||||||
keybinds.insert({ROTATE_180, sfKey::J});
|
|
||||||
keybinds.insert({ROTATE_0, sfKey::K});
|
|
||||||
keybinds.insert({HOLD, sfKey::LShift});
|
|
||||||
}
|
|
||||||
if (layout == 2) {
|
|
||||||
keybinds.insert({MOVE_LEFT, sfKey::A});
|
|
||||||
keybinds.insert({MOVE_RIGHT, sfKey::D});
|
|
||||||
keybinds.insert({SOFT_DROP, sfKey::W});
|
|
||||||
keybinds.insert({HARD_DROP, sfKey::S});
|
|
||||||
keybinds.insert({ROTATE_CW, sfKey::Left});
|
|
||||||
keybinds.insert({ROTATE_CCW, sfKey::Right});
|
|
||||||
keybinds.insert({ROTATE_180, sfKey::Up});
|
|
||||||
keybinds.insert({ROTATE_0, sfKey::Down});
|
|
||||||
keybinds.insert({HOLD, sfKey::RShift});
|
|
||||||
}
|
|
||||||
if (layout == 3) {
|
|
||||||
keybinds.insert({MOVE_LEFT, sfKey::Left});
|
|
||||||
keybinds.insert({MOVE_RIGHT, sfKey::Right});
|
|
||||||
keybinds.insert({SOFT_DROP, sfKey::Down});
|
|
||||||
keybinds.insert({HARD_DROP, sfKey::Up});
|
|
||||||
keybinds.insert({ROTATE_CW, sfKey::E});
|
|
||||||
keybinds.insert({ROTATE_CCW, sfKey::A});
|
|
||||||
keybinds.insert({ROTATE_180, sfKey::Num2});
|
|
||||||
keybinds.insert({ROTATE_0, sfKey::Tab});
|
|
||||||
keybinds.insert({HOLD, sfKey::Z});
|
|
||||||
}
|
|
||||||
|
|
||||||
char byte;
|
|
||||||
for (Action action : ACTION_LIST_IN_ORDER) {
|
|
||||||
byte = action;
|
|
||||||
layoutFile.write(&byte, 1);
|
|
||||||
|
|
||||||
if (keybinds.contains(action)) {
|
|
||||||
byte = (int) keybinds.at(action);
|
|
||||||
layoutFile.write(&byte, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
byte = 0xFF;
|
|
||||||
layoutFile.write(&byte, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Every possible block type
|
|
||||||
*/
|
|
||||||
enum Block {
|
|
||||||
NOTHING,
|
|
||||||
OUT_OF_BOUNDS,
|
|
||||||
GARBAGE,
|
|
||||||
PURPLE,
|
|
||||||
ORANGE,
|
|
||||||
CYAN,
|
|
||||||
PINK,
|
|
||||||
YELLOW,
|
|
||||||
RED,
|
|
||||||
BLUE,
|
|
||||||
GREEN
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the first block type a piece can be
|
|
||||||
* @return The block type
|
|
||||||
*/
|
|
||||||
inline Block firstPieceBlockType() {
|
|
||||||
return Block(PURPLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the block to the next available piece block type
|
|
||||||
*/
|
|
||||||
inline void nextPieceBlockType(Block& block) {
|
|
||||||
block = (block == GREEN) ? PURPLE : Block(block + 1);
|
|
||||||
}
|
|
||||||
66
src/Pieces/Cell.h
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A cell on a 2D grid
|
||||||
|
*/
|
||||||
|
struct Cell {
|
||||||
|
int x; // x position
|
||||||
|
int y; // y position
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Addition operator, returns the sums of the coordinates of both cells
|
||||||
|
*/
|
||||||
|
inline Cell operator+(const Cell& left, const Cell& right) {
|
||||||
|
return Cell{left.x + right.x, left.y + right.y};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Additive assignation operator, adds the coordinates of the right cell to the left one
|
||||||
|
*/
|
||||||
|
inline Cell& operator+=(Cell& left, const Cell& right) {
|
||||||
|
left = left + right;
|
||||||
|
return left;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Substraction operator, returns the difference of the coordinate between the left and right cell
|
||||||
|
*/
|
||||||
|
inline Cell operator-(const Cell& left, const Cell& right) {
|
||||||
|
return Cell{left.x - right.x, left.y - right.y};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Substractive assignation operator, substract the coordinates of the right cell from the left one
|
||||||
|
*/
|
||||||
|
inline Cell& operator-=(Cell& left, const Cell& right) {
|
||||||
|
left = left - right;
|
||||||
|
return left;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strict inferiority operator, a cell is inferior to another if it is lower or at the same height and more to the left
|
||||||
|
*/
|
||||||
|
inline bool operator<(const Cell& left, const Cell& right) {
|
||||||
|
return (left.x == right.x) ? (left.y < right.y) : (left.x < right.x);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Equality operator, two cells are equal if they have the same coordinates
|
||||||
|
*/
|
||||||
|
inline bool operator==(const Cell& left, const Cell& right) {
|
||||||
|
return (left.x == right.x) && (left.y == right.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stream output operator, adds the coordinates of the cell to the stream
|
||||||
|
*/
|
||||||
|
inline std::ostream& operator<<(std::ostream& os, const Cell& cell) {
|
||||||
|
os << "x: " << cell.x << " y: " << cell.y;
|
||||||
|
return os;
|
||||||
|
}
|
||||||
38
src/Pieces/Color.cpp
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
#include "Color.h"
|
||||||
|
|
||||||
|
static const Color C_NOTHING = {255, 255, 255};
|
||||||
|
static const Color C_OUT_OF_BOUNDS = C_NOTHING;
|
||||||
|
static const Color C_GARBAGE = {150, 150, 150};
|
||||||
|
static const Color C_PURPLE = {150, 0, 255};
|
||||||
|
static const Color C_ORANGE = {255, 150, 0};
|
||||||
|
static const Color C_CYAN = {0, 255, 0};
|
||||||
|
static const Color C_PINK = {255, 0, 200};
|
||||||
|
static const Color C_YELLOW = {255, 255, 0};
|
||||||
|
static const Color C_RED = {255, 0, 0};
|
||||||
|
static const Color C_BLUE = {0, 100, 255};
|
||||||
|
static const Color C_GREEN = {0, 255, 0};
|
||||||
|
|
||||||
|
static const Color colors[] = {
|
||||||
|
C_NOTHING,
|
||||||
|
C_OUT_OF_BOUNDS,
|
||||||
|
C_GARBAGE,
|
||||||
|
C_PURPLE,
|
||||||
|
C_ORANGE,
|
||||||
|
C_CYAN,
|
||||||
|
C_PINK,
|
||||||
|
C_YELLOW,
|
||||||
|
C_RED,
|
||||||
|
C_BLUE,
|
||||||
|
C_GREEN
|
||||||
|
};
|
||||||
|
|
||||||
|
const Color& getColor(ColorEnum color) {
|
||||||
|
return colors[color];
|
||||||
|
}
|
||||||
|
|
||||||
|
void setNextPieceColor(ColorEnum& color) {
|
||||||
|
if (color == GREEN)
|
||||||
|
color = PURPLE;
|
||||||
|
else
|
||||||
|
color = ColorEnum(color + 1);
|
||||||
|
}
|
||||||
@@ -1,55 +1,48 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Block.h"
|
#include <string>
|
||||||
|
|
||||||
#include "string"
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A color encoded in RGB
|
* Every possible colors a block can take
|
||||||
*/
|
*/
|
||||||
|
enum ColorEnum {
|
||||||
|
NOTHING,
|
||||||
|
OUT_OF_BOUNDS,
|
||||||
|
GARBAGE,
|
||||||
|
PURPLE,
|
||||||
|
ORANGE,
|
||||||
|
CYAN,
|
||||||
|
PINK,
|
||||||
|
YELLOW,
|
||||||
|
RED,
|
||||||
|
BLUE,
|
||||||
|
GREEN
|
||||||
|
};
|
||||||
|
|
||||||
struct Color {
|
struct Color {
|
||||||
unsigned char red; // the red component of the color
|
std::uint8_t r;
|
||||||
unsigned char green; // the green component of the color
|
std::uint8_t g;
|
||||||
unsigned char blue; // the blue component of the color
|
std::uint8_t b;
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
static const Color EMPTY_BLOCK_COLOR = {255, 255, 255}; // color of an empty block
|
|
||||||
static const Color BLOCKS_COLOR[] = { // color for each block type
|
|
||||||
EMPTY_BLOCK_COLOR, // NOTHING
|
|
||||||
EMPTY_BLOCK_COLOR, // OUT_OF_BOUNDS
|
|
||||||
{150, 150, 150}, // GARBAGE
|
|
||||||
{150, 0, 255}, // PURPLE
|
|
||||||
{255, 150, 0}, // ORANGE
|
|
||||||
{0, 255, 255}, // CYAN
|
|
||||||
{255, 0, 200}, // PINK
|
|
||||||
{255, 255, 0}, // YELLOW
|
|
||||||
{255, 0, 0}, // RED
|
|
||||||
{0, 100, 255}, // BLUE
|
|
||||||
{0, 255, 0} // GREEN
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Translates the color into a color code to change the console's color
|
* Returns the first color a piece can take
|
||||||
* @return A string to print in the console
|
|
||||||
*/
|
*/
|
||||||
inline std::string getConsoleColorCode(const Color& color) {
|
inline ColorEnum firstPieceColor() {
|
||||||
return "\033[38;2;" + std::to_string(color.red) + ";" + std::to_string(color.green) + ";" + std::to_string(color.blue) + "m";
|
return PURPLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
const Color& getColor(ColorEnum color);
|
||||||
* Translates the color into a color code to change the console's color
|
|
||||||
* @return A string to print in the console
|
|
||||||
*/
|
|
||||||
inline std::string getConsoleColorCode(const Block block) {
|
|
||||||
return getConsoleColorCode(BLOCKS_COLOR[block]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a color code to reset the console's color
|
* Sets the color to the next available piece color
|
||||||
* @return A string to print in the console
|
|
||||||
*/
|
*/
|
||||||
inline std::string getResetConsoleColorCode() {
|
void setNextPieceColor(ColorEnum& color);
|
||||||
return getConsoleColorCode(EMPTY_BLOCK_COLOR);
|
|
||||||
|
inline std::string getColorCode(const Color& color) {
|
||||||
|
return "\033[38;2;" + std::to_string(color.r) + ";" + std::to_string(color.g) + ";" + std::to_string(color.b) + "m";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline std::string getColorCode(ColorEnum color) {
|
||||||
|
return getColorCode(color);
|
||||||
|
}
|
||||||