Compare commits
56 Commits
507bc9cc86
...
compress
| Author | SHA1 | Date | |
|---|---|---|---|
| 17459c4026 | |||
| 3e40ff7252 | |||
| dd8314f76c | |||
| d7f52239f0 | |||
| 72f4ea75ff | |||
| b1b7277666 | |||
| fd6bdc2b09 | |||
| 69b91d6497 | |||
| d50714ef8c | |||
| 6ed85869ae | |||
| 3d1feb6295 | |||
| 7a96136631 | |||
| fc6348cebc | |||
| 0bb25d4628 | |||
| 69c07abcec | |||
| 774aa1422f | |||
| 4dfeda0957 | |||
| 05bb79d4a9 | |||
| e0ab6a4828 | |||
| a62b3c018d | |||
| 46ebb88ef2 | |||
| de14978b01 | |||
| d5ac79559e | |||
| 5ea47ddd25 | |||
| 1a95765877 | |||
| 6bff555cbc | |||
| f4b58fb67e | |||
| 0271b56542 | |||
| 5d73e751d7 | |||
| 314b7a8488 | |||
| 7151be0b1a | |||
| d124205a71 | |||
| 87920548e5 | |||
| 57620c70a2 | |||
| 3dac18c821 | |||
| 3538403f40 | |||
| ec40495328 | |||
| e0de2b5f90 | |||
| df7c9bd211 | |||
| f883bd5bab | |||
| 0f026635f6 | |||
| be071bd606 | |||
| 3320545465 | |||
| d1646d0fb5 | |||
| 3d74ef7cd5 | |||
| 88cb44c5fe | |||
| 009ed8edc3 | |||
| b2567844fc | |||
| de8a5e6e34 | |||
| c168cd68d7 | |||
| 321271b748 | |||
| c601424481 | |||
| fd9fd4586a | |||
| 8a4c4201fe | |||
| c08cfc2255 | |||
| 38008e00bc |
36
.github/workflows/ubuntu.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
name: Linux arm64
|
||||
run-name: Build And Test
|
||||
|
||||
on: [push]
|
||||
|
||||
|
||||
env:
|
||||
XMAKE_ROOT: y
|
||||
|
||||
jobs:
|
||||
Build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Prepare XMake
|
||||
uses: xmake-io/github-action-setup-xmake@v1
|
||||
with:
|
||||
xmake-version: latest
|
||||
actions-cache-folder: '.xmake-cache'
|
||||
actions-cache-key: 'ubuntu'
|
||||
|
||||
- name: Install SFML
|
||||
run: |
|
||||
apt update
|
||||
apt install -y libsfml-dev
|
||||
|
||||
- name: XMake config
|
||||
run: xmake f -p linux -y
|
||||
|
||||
- name: Build
|
||||
run: xmake
|
||||
|
||||
- name: Test
|
||||
run: xmake test
|
||||
6
.gitignore
vendored
@@ -10,8 +10,10 @@ build/
|
||||
|
||||
# personnal documentation
|
||||
doc/*.txt
|
||||
doc/*.violet.html
|
||||
doc/diagrams/*.violet.html
|
||||
doc/mockups/*
|
||||
|
||||
# pieces files
|
||||
# data files
|
||||
data/pieces/*.bin
|
||||
data/config/*.bin
|
||||
data/config/keybinds/*.bin
|
||||
|
||||
111
README.md
@@ -1,11 +1,64 @@
|
||||
# jminos
|
||||
|
||||
Modern stacker game with every polyominos from size 1 to 15, made in C++ with [SFML 3](https://www.sfml-dev.org/)!
|
||||
Modern stacker game with every polyominoes from size 1 to 15, made in C++ with [SFML 3](https://www.sfml-dev.org/)!
|
||||
|
||||
## Manual build and run
|
||||
## Download
|
||||
|
||||
This project uses xmake for compiling.
|
||||
To be able to build it, you need to [install xmake](https://xmake.io) and have a compiler with c++20 compatibility, xmake will install SFML for you.
|
||||
// TODO when the game is finished //
|
||||
|
||||
This game has been tested on and built for Windows 11 and WSL2 Ubuntu.
|
||||
If your OS isn't compactible with either of theses two, you can try [manually building the project](#manual-build).
|
||||
|
||||
## How to play
|
||||
|
||||
Choose which pieces you want to play with and stack them up until you either win or top out!
|
||||
Make full lines to make them dissapear.
|
||||
Use the different spins to kick the pieces in spot you couldn't imagine were attaignable!
|
||||
Each gamemode has its own objective, but you can play as you wish.
|
||||
|
||||
You can see and change in-game keybinds in the **SETTINGS** section of the main menu!
|
||||
All of in-menu navigation is done with the **arrow keys**, the **Enter key** and the **Escape key**. Theses are unchangeable keybinds.
|
||||
You will find more infos about the Rotation System, the scoring system, or the different pieces type in the **INFO** section of the main menu.
|
||||
|
||||
## Features
|
||||
|
||||
- Every polyominoes up to pentedecaminoes!
|
||||
- 7bag with proportionnality for each polyomino size!
|
||||
- AutoRS as the Rotation System!
|
||||
- 0° rotations!
|
||||
- All spin!
|
||||
- IRS, IHS, infinite hold, and other leniency mechanics!
|
||||
- Customizable board size!
|
||||
- Customizable keybinds!
|
||||
- Very bland interface!! (i'm not a designer)
|
||||
|
||||
### Available gamemodes
|
||||
|
||||
- SPRINT : clear 40 lines as fast as possible!
|
||||
- MARATHON : clear 200 lines with increasing gravity!
|
||||
- ULTRA : scores as much as possible in only 2 minutes!
|
||||
- MASTER : clear 200 lines at levels higher than maximum gravity!
|
||||
- INVISIBLE : get 1000 grade while not being able to see the board!
|
||||
- ZEN : practice indefinitely in this mode with no gravity!
|
||||
|
||||
### Screenshots
|
||||
|
||||
Pentedecamino jumpscare
|
||||

|
||||
|
||||
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.
|
||||
|
||||
### Build the project
|
||||
|
||||
@@ -19,11 +72,55 @@ If you need to change the toolchain (for example using gcc):
|
||||
### Run the project
|
||||
|
||||
``xmake run``
|
||||
Note that the program will genereate the polyomino files for you. This can be quite long so it only does it up to size 10.
|
||||
|
||||
If for some reasons you wanna run the command line version:
|
||||
The program will generate the polyomino files for you if you don't have them.
|
||||
As this is a lengthy process, debug mode limits pieces size to 10.
|
||||
To switch between debug and release mode:
|
||||
``xmake f -m debug``
|
||||
``xmake f -m release``
|
||||
|
||||
If for some reasons you wanna run the command line version (not updated):
|
||||
``xmake build text``
|
||||
``xmake run text``
|
||||
|
||||
### Package the project
|
||||
|
||||
// TODO when the game is finished //
|
||||
|
||||
## Benchmarks
|
||||
|
||||
### One-sided n-polyominoes
|
||||
|
||||
| n | Number | Generation | File storing | File retrieving | File size |
|
||||
| -: | -: | :-: | :-: | :-: | -: |
|
||||
| 1 | 1 | 0s 0.005622ms | 0s 0.750955ms | 0s 0.014016ms | 2 bytes |
|
||||
| 2 | 1 | 0s 0.005758ms | 0s 0.181323ms | 0s 0.012256ms | 3 bytes |
|
||||
| 3 | 2 | 0s 0.017525ms | 0s 0.054497ms | 0s 0.006431ms | 8 bytes |
|
||||
| 4 | 7 | 0s 0.050554ms | 0s 0.067617ms | 0s 0.010984ms | 35 bytes |
|
||||
| 5 | 18 | 0s 0.191971ms | 0s 0.109905ms | 0s 0.021234ms | 108 bytes |
|
||||
| 6 | 60 | 0s 0.651757ms | 0s 0.327465ms | 0s 0.04558ms | 420 bytes |
|
||||
| 7 | 196 | 0s 3.38847ms | 0s 1.94434ms | 0s 0.258777ms | 1568 bytes |
|
||||
| 8 | 704 | 0s 22.7411ms | 0s 10.0103ms | 0s 1.34813ms | 6336 bytes |
|
||||
| 9 | 2500 | 0s 66.2949ms | 0s 20.6137ms | 0s 2.56374ms | 25000 bytes |
|
||||
| 10 | 9189 | 0s 194.764ms | 0s 84.5884ms | 0s 9.64467ms | 101079 bytes |
|
||||
| 11 | 33896 | 0s 759.182ms | 0s 378.494ms | 0s 44.1424ms | 406752 bytes |
|
||||
| 12 | 126759 | 2s 709.277ms | 1s 530.34ms | 0s 155ms | 1647867 bytes |
|
||||
| 13 | 476270 | 10s 668.308ms | 7s 395.512ms | 0s 765.601ms | 6667780 bytes |
|
||||
| 14 | 1802312 | 45s 606.597ms | 32s 28.7977ms | 2s 919.653ms | 27034680 bytes |
|
||||
| 15 | ~6M | ~5mn | ~5mn | ~10s | ~100 MB |
|
||||
|
||||
_File storing includes type checking and sorting all polyominoes before writing them to the file._
|
||||
If you want to know more details about the generation and storage of polyominoes, [check the documentation](/doc/)!
|
||||
|
||||
Run it yourself by typing:
|
||||
``xmake f -m release``
|
||||
``xmake build bmark``
|
||||
``xmake run bmark``
|
||||
|
||||
## Credits
|
||||
|
||||
Font used: [Press Start](https://www.zone38.net/font/#pressstart).
|
||||
Library used: [SFML 3](https://www.sfml-dev.org/).
|
||||
Font used: [Press Start](https://www.zone38.net/font/#pressstart).
|
||||
|
||||
Inspired by other modern stacker games such as Techmino, jstris, tetr.io, etc.
|
||||
This game isn't affiliated with any of them.
|
||||
|
||||
0
data/config/.gitkeep
Normal file
0
data/config/keybinds/.gitkeep
Normal file
BIN
data/images/keybinds/Harddrop.png
Normal file
|
After Width: | Height: | Size: 619 B |
BIN
data/images/keybinds/Hold.png
Normal file
|
After Width: | Height: | Size: 618 B |
BIN
data/images/keybinds/Moveleft.png
Normal file
|
After Width: | Height: | Size: 604 B |
BIN
data/images/keybinds/Moveright.png
Normal file
|
After Width: | Height: | Size: 603 B |
BIN
data/images/keybinds/Pause.png
Normal file
|
After Width: | Height: | Size: 591 B |
BIN
data/images/keybinds/Retry.png
Normal file
|
After Width: | Height: | Size: 600 B |
BIN
data/images/keybinds/Rotate0.png
Normal file
|
After Width: | Height: | Size: 624 B |
BIN
data/images/keybinds/Rotate180.png
Normal file
|
After Width: | Height: | Size: 627 B |
BIN
data/images/keybinds/RotateCCW.png
Normal file
|
After Width: | Height: | Size: 622 B |
BIN
data/images/keybinds/RotateCW.png
Normal file
|
After Width: | Height: | Size: 623 B |
BIN
data/images/keybinds/Softdrop.png
Normal file
|
After Width: | Height: | Size: 606 B |
|
Before Width: | Height: | Size: 164 KiB After Width: | Height: | Size: 164 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
@@ -37,11 +37,23 @@ _Repeat for every avaible actions._
|
||||
|
||||
The settings file has the following format:
|
||||
|
||||
- The versions of the file format, stored with 1 byte
|
||||
- The previous maximum pieces size selected, stored with 1 byte
|
||||
- The number of the chosen keybinds (from 0 to 4), stored with 1 byte
|
||||
- The DAS of the player, stored with 1 byte
|
||||
- The ARR of the player, stored with 1 byte
|
||||
- The SDR of the player, stored with 1 byte
|
||||
- The window size mode, stored with 1 byte
|
||||
- The master volume, stored with 1 byte
|
||||
- The number of the last selected gamemode (converted from an Enum), stored with 1 byte
|
||||
- The last selected width of the board, stored with 1 byte
|
||||
- The last selected height of the board, stored with 1 byte
|
||||
- The uniformity mode (0 for default distribution, 1 for uniformous distribution, 2 for custom distribution), stored with 1 byte
|
||||
- If custom distribution is set, store the proportion of each size (15x1 byte total)
|
||||
- Every selected pieces, using 1 byte for the type of selection (once again converted from an Enum) and 1 byte for the actual value
|
||||
- For each size, store the custom proportion (from 0 to 20) (15x1 byte total)
|
||||
- Every selected pieces
|
||||
- For every groupe of piece (ALL, CONVEX, HOLELESS, OTHER), use 1 byte for the type (once again converted from an Enum) and 1 byte for the size
|
||||
- For every single piece, use 1 byte for (the size + encoding that it is a single piece),
|
||||
and 3 bytes to store the number of the piece (allows number up to 16M, size 15 has 6M pieces).
|
||||
|
||||
The current file format version is 11.
|
||||
If the file starts with a number lower than that, it will be regenerated upon launching the game.
|
||||
|
||||
@@ -77,8 +77,9 @@ Since this game uses polyomino of high sizes which are very unplayable, we will
|
||||
5. Do the same as step 4 but now we move the piece one line up every time
|
||||
6. Cancel the rotation
|
||||
|
||||
Kicking is primarly designed for rotating, but it is also applied when a piece spawns into a wall.
|
||||
0° rotations will first try to move the piece one position down, and if it can't try kicking the piece, only registering a kick if the piece couldn't move down.
|
||||
_Note: at step 3, the direction which is checked first is actually the last movement done by the player._
|
||||
Kicking is primarly designed for rotating, but there is also a 0° rotation applied when a piece spawns into a wall.
|
||||
0° rotations will first try to move the piece one position down, and if it can't, try kicking the piece, only registering a kick if the piece couldn't move down.
|
||||
|
||||
## Detecting spins
|
||||
|
||||
@@ -91,8 +92,31 @@ Since we work with a great deal of different size and shapes, the rules for spin
|
||||
|
||||
## Score calculation
|
||||
|
||||
- For every position soft dropped, add 1 to the score
|
||||
- For every position hard dropped, add 2 to the score
|
||||
- When locking a piece, add 1 to the score if it is due to lock delay, or 10 if it was hard dropped.
|
||||
- When clearing one line, add 100 to the score, 200 for 2 lines, 400 for 3 lines, 800 for 4 lines, 1600 for 5 lines, etc.
|
||||
- If the line clear is a spin, count the score like a normal clear of 2x more line (200 for 1-line spin, 800 for 2, 3200 for 3, etc.)
|
||||
- When performing a spin, a mini spin, or clearing 4 or more lines, B2B is activated, every subsequent line clear that is a spin, a mini spin, or clear 4 or more lines, scores twice as much
|
||||
|
||||
## Grade calculation
|
||||
|
||||
Grade is an alternate system to line clears.
|
||||
|
||||
- Each time a piece is dropped, 1 point is added to the total.
|
||||
- For each cleared line, 1 point is added to the total.
|
||||
|
||||
The only exception occurs when the total ends in '99', a line must be cleared to progress.
|
||||
When this line is cleared, the point of the piece is also counted towards the total.
|
||||
|
||||
## Pieces distribution
|
||||
|
||||
A bag is an object which contains a set of pieces.
|
||||
The principle of a bag generator is to put every available pieces into a bag and take them out one by one until the bag is empty, to then start with a new full bag.
|
||||
It's a way to have equal distribution of pieces while still allowing for a random order of pieces.
|
||||
|
||||
The game has 3 modes of pieces distribution:
|
||||
|
||||
1. Default. The simplest of them, all selected pieces are put in a single bag.
|
||||
2. Uniformous. The pieces are now separated by size and put in different bags. Each size will now appear as often as any other.
|
||||
3. Custom. The pieces are once again separated by size, but now the player specifies how likely each size is to appear.
|
||||
|
||||
Both system 2 and 3 uses a system analogous to a bag to determine which size of piece to take next.
|
||||
|
||||
BIN
doc/readme/big_piece.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
doc/readme/pieces_selection.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
doc/readme/rotation_0.gif
Normal file
|
After Width: | Height: | Size: 614 KiB |
BIN
doc/readme/rotations.gif
Normal file
|
After Width: | Height: | Size: 779 KiB |
68
src/Common/Compression.cpp
Normal file
@@ -0,0 +1,68 @@
|
||||
#include "Compression.h"
|
||||
|
||||
#include "VarInt.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <zlib.h>
|
||||
|
||||
#define COMPRESSION_THRESHOLD 64
|
||||
|
||||
static DataBuffer Inflate(const std::uint8_t* source, std::size_t size, std::size_t uncompressedSize) {
|
||||
DataBuffer result;
|
||||
result.Resize(uncompressedSize);
|
||||
|
||||
uncompress(reinterpret_cast<Bytef*>(result.data()), reinterpret_cast<uLongf*>(&uncompressedSize),
|
||||
reinterpret_cast<const Bytef*>(source), static_cast<uLong>(size));
|
||||
|
||||
assert(result.GetSize() == uncompressedSize);
|
||||
return result;
|
||||
}
|
||||
|
||||
static DataBuffer Deflate(const std::uint8_t* source, std::size_t size) {
|
||||
DataBuffer result;
|
||||
uLongf compressedSize = size;
|
||||
|
||||
result.Resize(size); // Resize for the compressed data to fit into
|
||||
compress(
|
||||
reinterpret_cast<Bytef*>(result.data()), &compressedSize, reinterpret_cast<const Bytef*>(source), static_cast<uLong>(size));
|
||||
result.Resize(compressedSize); // Resize to cut useless data
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
DataBuffer Compress(const DataBuffer& buffer) {
|
||||
DataBuffer packet;
|
||||
|
||||
if (buffer.GetSize() < COMPRESSION_THRESHOLD) {
|
||||
// Don't compress since it's a small packet
|
||||
VarInt compressedDataLength = 0;
|
||||
packet << compressedDataLength;
|
||||
packet << buffer;
|
||||
return packet;
|
||||
}
|
||||
|
||||
DataBuffer compressedData = Deflate(buffer.data(), buffer.GetSize());
|
||||
|
||||
VarInt uncompressedDataLength = buffer.GetSize();
|
||||
packet << uncompressedDataLength;
|
||||
packet.WriteSome(compressedData.data(), compressedData.GetSize());
|
||||
return packet;
|
||||
}
|
||||
|
||||
DataBuffer Decompress(DataBuffer& buffer, std::uint64_t packetLength) {
|
||||
VarInt uncompressedLength;
|
||||
buffer >> uncompressedLength;
|
||||
|
||||
std::uint64_t compressedLength = packetLength - uncompressedLength.GetSerializedLength();
|
||||
|
||||
if (uncompressedLength.GetValue() == 0) {
|
||||
// Data already uncompressed. Nothing to do
|
||||
DataBuffer ret;
|
||||
buffer.ReadSome(ret, compressedLength);
|
||||
return ret;
|
||||
}
|
||||
|
||||
assert(buffer.GetReadOffset() + compressedLength <= buffer.GetSize());
|
||||
|
||||
return Inflate(buffer.data() + buffer.GetReadOffset(), compressedLength, uncompressedLength.GetValue());
|
||||
}
|
||||
23
src/Common/Compression.h
Normal file
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* \file Compression.h
|
||||
* \brief File containing compress utilities
|
||||
*/
|
||||
|
||||
#include "DataBuffer.h"
|
||||
|
||||
/**
|
||||
* \brief Compress some data
|
||||
* \param buffer the data to compress
|
||||
* \return the compressed data
|
||||
*/
|
||||
DataBuffer Compress(const DataBuffer& buffer);
|
||||
|
||||
/**
|
||||
* \brief Uncompress some data
|
||||
* \param buffer the data to uncompress
|
||||
* \param packetLength lenght of data
|
||||
* \return the uncompressed data
|
||||
*/
|
||||
DataBuffer Decompress(DataBuffer& buffer, std::uint64_t packetLength);
|
||||
98
src/Common/DataBuffer.cpp
Normal file
@@ -0,0 +1,98 @@
|
||||
#include "DataBuffer.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
|
||||
DataBuffer::DataBuffer(std::size_t initalCapacity) : m_ReadOffset(0) {
|
||||
m_Buffer.reserve(initalCapacity);
|
||||
}
|
||||
|
||||
DataBuffer::DataBuffer() : m_ReadOffset(0) {}
|
||||
|
||||
DataBuffer::DataBuffer(const DataBuffer& other) : m_Buffer(other.m_Buffer), m_ReadOffset(other.m_ReadOffset) {}
|
||||
|
||||
DataBuffer::DataBuffer(DataBuffer&& other) : m_Buffer(std::move(other.m_Buffer)), m_ReadOffset(std::move(other.m_ReadOffset)) {}
|
||||
|
||||
DataBuffer::DataBuffer(const std::string& str) : m_Buffer(str.begin(), str.end()), m_ReadOffset(0) {}
|
||||
|
||||
DataBuffer::DataBuffer(const DataBuffer& other, Data::difference_type offset) : m_ReadOffset(0) {
|
||||
m_Buffer.reserve(other.GetSize() - static_cast<std::size_t>(offset));
|
||||
std::copy(other.m_Buffer.begin() + offset, other.m_Buffer.end(), std::back_inserter(m_Buffer));
|
||||
}
|
||||
|
||||
DataBuffer& DataBuffer::operator=(const DataBuffer& other) {
|
||||
m_Buffer = other.m_Buffer;
|
||||
m_ReadOffset = other.m_ReadOffset;
|
||||
return *this;
|
||||
}
|
||||
|
||||
DataBuffer& DataBuffer::operator=(DataBuffer&& other) {
|
||||
m_Buffer = std::move(other.m_Buffer);
|
||||
m_ReadOffset = std::move(other.m_ReadOffset);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void DataBuffer::SetReadOffset(std::size_t pos) {
|
||||
assert(pos <= GetSize());
|
||||
m_ReadOffset = pos;
|
||||
}
|
||||
|
||||
std::size_t DataBuffer::GetSize() const {
|
||||
return m_Buffer.size();
|
||||
}
|
||||
|
||||
std::size_t DataBuffer::GetRemaining() const {
|
||||
return m_Buffer.size() - m_ReadOffset;
|
||||
}
|
||||
|
||||
DataBuffer::iterator DataBuffer::begin() {
|
||||
return m_Buffer.begin();
|
||||
}
|
||||
|
||||
DataBuffer::iterator DataBuffer::end() {
|
||||
return m_Buffer.end();
|
||||
}
|
||||
|
||||
DataBuffer::const_iterator DataBuffer::begin() const {
|
||||
return m_Buffer.begin();
|
||||
}
|
||||
|
||||
DataBuffer::const_iterator DataBuffer::end() const {
|
||||
return m_Buffer.end();
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const DataBuffer& buffer) {
|
||||
for (unsigned char u : buffer)
|
||||
os << std::hex << std::setfill('0') << std::setw(2) << static_cast<int>(u) << " ";
|
||||
os << std::dec;
|
||||
return os;
|
||||
}
|
||||
|
||||
bool DataBuffer::ReadFile(const std::string& fileName) {
|
||||
try {
|
||||
std::ifstream file(fileName, std::istream::binary);
|
||||
std::ostringstream ss;
|
||||
ss << file.rdbuf();
|
||||
const std::string& s = ss.str();
|
||||
m_Buffer = DataBuffer::Data(s.begin(), s.end());
|
||||
m_ReadOffset = 0;
|
||||
} catch (std::exception& e) {
|
||||
std::cerr << "Failed to read file : " << e.what() << std::endl;
|
||||
return false;
|
||||
}
|
||||
return m_Buffer.size() > 0;
|
||||
}
|
||||
|
||||
bool DataBuffer::WriteFile(const std::string& fileName) const {
|
||||
try {
|
||||
std::ofstream file(fileName, std::ostream::binary);
|
||||
file.write(reinterpret_cast<const char*>(m_Buffer.data()), static_cast<std::streamsize>(m_Buffer.size()));
|
||||
file.flush();
|
||||
} catch (std::exception& e) {
|
||||
std::cerr << "Failed to write file : " << e.what() << std::endl;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
284
src/Common/DataBuffer.h
Normal file
@@ -0,0 +1,284 @@
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* \file DataBuffer.h
|
||||
* \brief File containing the blitz::DataBuffer class
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
/**
|
||||
* \class DataBuffer
|
||||
* \brief Class used to manipulate memory
|
||||
*/
|
||||
class DataBuffer {
|
||||
private:
|
||||
typedef std::vector<std::uint8_t> Data;
|
||||
Data m_Buffer;
|
||||
std::size_t m_ReadOffset;
|
||||
|
||||
public:
|
||||
typedef Data::iterator iterator;
|
||||
typedef Data::const_iterator const_iterator;
|
||||
typedef Data::reference reference;
|
||||
typedef Data::const_reference const_reference;
|
||||
typedef Data::difference_type difference_type;
|
||||
|
||||
DataBuffer();
|
||||
DataBuffer(std::size_t initalCapacity);
|
||||
DataBuffer(const DataBuffer& other);
|
||||
DataBuffer(const DataBuffer& other, difference_type offset);
|
||||
DataBuffer(DataBuffer&& other);
|
||||
DataBuffer(const std::string& str);
|
||||
|
||||
DataBuffer& operator=(const DataBuffer& other);
|
||||
DataBuffer& operator=(DataBuffer&& other);
|
||||
|
||||
/**
|
||||
* \brief Append data to the buffer
|
||||
*/
|
||||
template <typename T>
|
||||
void Append(const T& data) {
|
||||
std::size_t size = sizeof(data);
|
||||
std::size_t end_pos = m_Buffer.size();
|
||||
m_Buffer.resize(m_Buffer.size() + size);
|
||||
std::memcpy(&m_Buffer[end_pos], &data, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Append data to the buffer
|
||||
*/
|
||||
template <typename T>
|
||||
DataBuffer& operator<<(const T& data) {
|
||||
Append(data);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Append a string to the buffer
|
||||
* \warning Don't use it for binary data !
|
||||
* \param str The string to append
|
||||
*/
|
||||
DataBuffer& operator<<(const std::string& str) {
|
||||
std::size_t strlen = str.length() + 1; // including null character
|
||||
Resize(GetSize() + strlen);
|
||||
std::memcpy(m_Buffer.data() + GetSize() - strlen, str.data(), strlen);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Append data to the buffer from another const buffer
|
||||
* \param data The buffer to append
|
||||
*/
|
||||
DataBuffer& operator<<(const DataBuffer& data) {
|
||||
m_Buffer.insert(m_Buffer.end(), data.begin(), data.end());
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Read some data from the buffer and assign to desired variable
|
||||
*/
|
||||
template <typename T>
|
||||
DataBuffer& operator>>(T& data) {
|
||||
assert(m_ReadOffset + sizeof(T) <= GetSize());
|
||||
data = *(reinterpret_cast<T*>(&m_Buffer[m_ReadOffset]));
|
||||
m_ReadOffset += sizeof(T);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Read some data from the buffer and assign to the new buffer
|
||||
* \param data The buffer to assign
|
||||
*/
|
||||
DataBuffer& operator>>(DataBuffer& data) {
|
||||
data.Resize(GetSize() - m_ReadOffset);
|
||||
std::copy(m_Buffer.begin() + static_cast<difference_type>(m_ReadOffset), m_Buffer.end(), data.begin());
|
||||
m_ReadOffset = m_Buffer.size();
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Read a string from the buffer
|
||||
* \param str The string to assign in the new buffer
|
||||
* \warning Don't use it for binary data !
|
||||
*/
|
||||
DataBuffer& operator>>(std::string& str) {
|
||||
std::size_t stringSize =
|
||||
strlen(reinterpret_cast<const char*>(m_Buffer.data()) + m_ReadOffset) + 1; // including null character
|
||||
str.resize(stringSize);
|
||||
std::copy(m_Buffer.begin() + static_cast<difference_type>(m_ReadOffset),
|
||||
m_Buffer.begin() + static_cast<difference_type>(m_ReadOffset + stringSize), str.begin());
|
||||
m_ReadOffset += stringSize;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Write some data to the buffer
|
||||
* \param buffer The buffer to write
|
||||
* \param amount The amount of data to write
|
||||
*/
|
||||
void WriteSome(const char* buffer, std::size_t amount) {
|
||||
std::size_t end_pos = m_Buffer.size();
|
||||
m_Buffer.resize(m_Buffer.size() + amount);
|
||||
std::memcpy(m_Buffer.data() + end_pos, buffer, amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Write some data to the buffer
|
||||
* \param buffer The buffer to write
|
||||
* \param amount The amount of data to write
|
||||
*/
|
||||
void WriteSome(const std::uint8_t* buffer, std::size_t amount) {
|
||||
std::size_t end_pos = m_Buffer.size();
|
||||
m_Buffer.resize(m_Buffer.size() + amount);
|
||||
std::memcpy(m_Buffer.data() + end_pos, buffer, amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Read some data from the buffer
|
||||
* \param buffer The buffer to Read
|
||||
* \param amount The amount of data from the buffer
|
||||
*/
|
||||
void ReadSome(char* buffer, std::size_t amount) {
|
||||
assert(m_ReadOffset + amount <= GetSize());
|
||||
std::copy_n(m_Buffer.begin() + static_cast<difference_type>(m_ReadOffset), amount, buffer);
|
||||
m_ReadOffset += amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Read some data from the buffer
|
||||
* \param buffer The buffer to Read
|
||||
* \param amount The amount of data from the buffer
|
||||
*/
|
||||
void ReadSome(std::uint8_t* buffer, std::size_t amount) {
|
||||
assert(m_ReadOffset + amount <= GetSize());
|
||||
std::copy_n(m_Buffer.begin() + static_cast<difference_type>(m_ReadOffset), amount, buffer);
|
||||
m_ReadOffset += amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Read some data from the buffer
|
||||
* \param buffer The buffer to Read
|
||||
* \param amount The amount of data from the buffer
|
||||
*/
|
||||
void ReadSome(DataBuffer& buffer, std::size_t amount) {
|
||||
assert(m_ReadOffset + amount <= GetSize());
|
||||
buffer.Resize(amount);
|
||||
std::copy_n(m_Buffer.begin() + static_cast<difference_type>(m_ReadOffset), amount, buffer.begin());
|
||||
m_ReadOffset += amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Resize the buffer
|
||||
* \param size The new size of the buffer
|
||||
*/
|
||||
void Resize(std::size_t size) {
|
||||
m_Buffer.resize(size);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Reserve some space in the buffer
|
||||
* \param amount The amount of space to reserve
|
||||
*/
|
||||
void Reserve(std::size_t amount) {
|
||||
m_Buffer.reserve(amount);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* \brief Clear the buffer
|
||||
*/
|
||||
void Clear() {
|
||||
m_Buffer.clear();
|
||||
m_ReadOffset = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief When the buffer has been read entirely
|
||||
*/
|
||||
bool IsFinished() const {
|
||||
return m_ReadOffset >= m_Buffer.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Get the buffer data
|
||||
*/
|
||||
std::uint8_t* data() {
|
||||
return m_Buffer.data();
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Get the buffer data
|
||||
*/
|
||||
const std::uint8_t* data() const {
|
||||
return m_Buffer.data();
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Get the read offset
|
||||
*/
|
||||
std::size_t GetReadOffset() const {
|
||||
return m_ReadOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Set the read offset
|
||||
* \param pos The new read offset
|
||||
*/
|
||||
void SetReadOffset(std::size_t pos);
|
||||
|
||||
/**
|
||||
* \brief Get the size of the buffer
|
||||
*/
|
||||
std::size_t GetSize() const;
|
||||
|
||||
/**
|
||||
* \brief Get the remaining size of the buffer
|
||||
*/
|
||||
std::size_t GetRemaining() const;
|
||||
|
||||
/**
|
||||
* \brief Read a file into the buffer
|
||||
* \param fileName The name of the file to read
|
||||
*/
|
||||
bool ReadFile(const std::string& fileName);
|
||||
|
||||
/**
|
||||
* \brief Write a file into the buffer
|
||||
* \param fileName The name of the file to write to
|
||||
*/
|
||||
bool WriteFile(const std::string& fileName) const;
|
||||
|
||||
/**
|
||||
* \brief Allocate the buffer on the heap
|
||||
* \warning Don't forget to free the data !
|
||||
*/
|
||||
std::uint8_t* HeapAllocatedData() const {
|
||||
std::uint8_t* newBuffer = new std::uint8_t[GetSize()];
|
||||
std::memcpy(newBuffer, data(), GetSize());
|
||||
return newBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Operator == to compare two DataBuffer
|
||||
*/
|
||||
bool operator==(const DataBuffer& other) const {
|
||||
return m_Buffer == other.m_Buffer;
|
||||
}
|
||||
|
||||
iterator begin();
|
||||
iterator end();
|
||||
const_iterator begin() const;
|
||||
const_iterator end() const;
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Operator << to write a DataBuffer to an ostream
|
||||
*/
|
||||
std::ostream& operator<<(std::ostream& os, const DataBuffer& buffer);
|
||||
|
||||
48
src/Common/VarInt.cpp
Normal file
@@ -0,0 +1,48 @@
|
||||
#include "VarInt.h"
|
||||
|
||||
#include "DataBuffer.h"
|
||||
#include <stdexcept>
|
||||
|
||||
static constexpr int SEGMENT_BITS = 0x7F;
|
||||
static constexpr int CONTINUE_BIT = 0x80;
|
||||
|
||||
std::size_t VarInt::GetSerializedLength() const {
|
||||
DataBuffer buffer;
|
||||
buffer << *this;
|
||||
return buffer.GetSize();
|
||||
}
|
||||
|
||||
DataBuffer& operator<<(DataBuffer& out, const VarInt& var) {
|
||||
std::uint64_t value = var.m_Value;
|
||||
while (true) {
|
||||
if ((value & ~static_cast<std::uint64_t>(SEGMENT_BITS)) == 0) {
|
||||
out << static_cast<std::uint8_t>(value);
|
||||
return out;
|
||||
}
|
||||
|
||||
out << static_cast<std::uint8_t>((value & SEGMENT_BITS) | CONTINUE_BIT);
|
||||
|
||||
value >>= 7;
|
||||
}
|
||||
}
|
||||
|
||||
DataBuffer& operator>>(DataBuffer& in, VarInt& var) {
|
||||
var.m_Value = 0;
|
||||
unsigned int position = 0;
|
||||
std::uint8_t currentByte;
|
||||
|
||||
while (true) {
|
||||
in.ReadSome(¤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;
|
||||
}
|
||||
54
src/Common/VarInt.h
Normal file
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* \file VarInt.h
|
||||
* \brief File containing the blitz::VarInt class
|
||||
*/
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
class DataBuffer;
|
||||
|
||||
/**
|
||||
* \class VarInt
|
||||
* \brief Variable-length format such that smaller numbers use fewer bytes.
|
||||
*/
|
||||
class VarInt {
|
||||
private:
|
||||
std::uint64_t m_Value;
|
||||
|
||||
public:
|
||||
VarInt() : m_Value(0) {}
|
||||
/**
|
||||
* \brief Construct a variable integer from a value
|
||||
* \param value The value of the variable integer
|
||||
*/
|
||||
VarInt(std::uint64_t value) : m_Value(value) {}
|
||||
|
||||
/**
|
||||
* \brief Get the value of the variable integer
|
||||
*/
|
||||
std::uint64_t GetValue() const {
|
||||
return m_Value;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Get the length of the serialized variable integer
|
||||
*/
|
||||
std::size_t GetSerializedLength() const;
|
||||
|
||||
/**
|
||||
* \brief Serialize the variable integer
|
||||
* \param out The buffer to write the serialized variable integer to
|
||||
* \param var The variable integer to serialize
|
||||
*/
|
||||
friend DataBuffer& operator<<(DataBuffer& out, const VarInt& var);
|
||||
|
||||
/**
|
||||
* \brief Deserialize the variable integer
|
||||
* \param in The buffer to read the serialized variable integer from
|
||||
* \param var The variable integer to deserialize
|
||||
*/
|
||||
friend DataBuffer& operator>>(DataBuffer& in, VarInt& var);
|
||||
};
|
||||
@@ -42,9 +42,9 @@ static const std::string ACTION_NAMES[] = { // name representation for each a
|
||||
"Hard drop",
|
||||
"Move left",
|
||||
"Move right",
|
||||
"Rotate 0°",
|
||||
"Rotate 0",
|
||||
"Rotate CW",
|
||||
"Rotate 180°",
|
||||
"Rotate 180",
|
||||
"Rotate CCW"
|
||||
};
|
||||
|
||||
|
||||
@@ -7,26 +7,54 @@
|
||||
#include <utility>
|
||||
#include <cstdlib>
|
||||
|
||||
static const double SMALLEST_CONSIDERED_PROPORTION = 0.01; // the smallest a proportion can get before it is considered equal to 0
|
||||
|
||||
|
||||
Bag::Bag(const std::shared_ptr<PiecesList>& piecesList) :
|
||||
piecesList(piecesList) {
|
||||
|
||||
this->currentBag = this->piecesList->getSelectedPieces();
|
||||
this->nextBag.clear();
|
||||
|
||||
this->highestSize = this->piecesList->getHighestLoadedSize();
|
||||
this->selectedPieces = this->piecesList->getSelectedPieces();
|
||||
this->distributionMode = this->piecesList->getDistributionMode();
|
||||
this->propotionsPerSize = this->piecesList->getProportionsPerSize();
|
||||
|
||||
this->currentBags.clear();
|
||||
this->nextBags.clear();
|
||||
this->sizesBag.clear();
|
||||
this->sizesProgression.clear();
|
||||
|
||||
if (this->distributionMode == DEFAULT) {
|
||||
this->currentBags.push_back(this->selectedPieces);
|
||||
this->nextBags.push_back({});
|
||||
}
|
||||
else {
|
||||
for (int i = 0; i <= this->highestSize; i++) {
|
||||
this->currentBags.push_back(PieceBag());
|
||||
this->nextBags.push_back(PieceBag());
|
||||
this->sizesProgression.push_back(0);
|
||||
}
|
||||
|
||||
for (const auto& piece : this->selectedPieces) {
|
||||
int pieceSize = this->piecesList->lookAtPiece(piece).getPositions().size();
|
||||
this->currentBags.at(pieceSize).push_back(piece);
|
||||
}
|
||||
}
|
||||
|
||||
this->prepareNext();
|
||||
}
|
||||
|
||||
void Bag::jumpToNextBag() {
|
||||
if (this->currentBag.size() < this->nextBag.size()) {
|
||||
std::swap(this->currentBag, this->nextBag);
|
||||
for (int i = 0; i < this->currentBags.size(); i++) {
|
||||
if (this->currentBags.at(i).size() < this->nextBags.at(i).size()) {
|
||||
std::swap(this->currentBags.at(i), this->nextBags.at(i));
|
||||
}
|
||||
|
||||
for (const auto& piece : this->nextBags.at(i)) {
|
||||
this->currentBags.at(i).push_back(piece);
|
||||
}
|
||||
this->nextBags.at(i).clear();
|
||||
}
|
||||
|
||||
for (const std::pair<int, int>& pieceIndex : this->nextBag) {
|
||||
this->currentBag.push_back(pieceIndex);
|
||||
}
|
||||
this->nextBag.clear();
|
||||
|
||||
this->prepareNext();
|
||||
}
|
||||
|
||||
@@ -43,13 +71,40 @@ Piece Bag::getNext() {
|
||||
}
|
||||
|
||||
void Bag::prepareNext() {
|
||||
if (this->currentBag.empty()) {
|
||||
std::swap(this->currentBag, this->nextBag);
|
||||
if (this->distributionMode == DEFAULT) {
|
||||
this->getNextPieceFromBag(0);
|
||||
}
|
||||
else {
|
||||
if (this->sizesBag.empty()) {
|
||||
for (int i = 0; i <= this->highestSize; i++) {
|
||||
if (this->propotionsPerSize.at(i) >= SMALLEST_CONSIDERED_PROPORTION
|
||||
&& !(this->currentBags.at(i).empty() && this->nextBags.at(i).empty())) {
|
||||
while (this->sizesProgression.at(i) < 1) {
|
||||
this->sizesBag.push_back(i);
|
||||
this->sizesProgression.at(i) += this->propotionsPerSize.at(i);
|
||||
}
|
||||
|
||||
this->sizesProgression.at(i) -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int nextSizeIndex = std::rand() % this->sizesBag.size();
|
||||
int nextSize = this->sizesBag.at(nextSizeIndex);
|
||||
this->sizesBag.erase(this->sizesBag.begin() + nextSizeIndex);
|
||||
|
||||
this->getNextPieceFromBag(nextSize);
|
||||
}
|
||||
}
|
||||
|
||||
void Bag::getNextPieceFromBag(int bagIndex) {
|
||||
if (this->currentBags.at(bagIndex).empty()) {
|
||||
std::swap(this->currentBags.at(bagIndex), this->nextBags.at(bagIndex));
|
||||
}
|
||||
|
||||
int indexIndex = std::rand() % this->currentBag.size();
|
||||
this->next = this->currentBag.at(indexIndex);
|
||||
int indexIndex = std::rand() % this->currentBags.at(bagIndex).size();
|
||||
this->next = this->currentBags.at(bagIndex).at(indexIndex);
|
||||
|
||||
this->nextBag.push_back(this->next);
|
||||
this->currentBag.erase(this->currentBag.begin() + indexIndex);
|
||||
this->nextBags.at(bagIndex).push_back(this->next);
|
||||
this->currentBags.at(bagIndex).erase(this->currentBags.at(bagIndex).begin() + indexIndex);
|
||||
}
|
||||
|
||||
@@ -2,11 +2,14 @@
|
||||
|
||||
#include "../Pieces/Piece.h"
|
||||
#include "PiecesList.h"
|
||||
#include "DistributionMode.h"
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
using PieceBag = std::vector<std::pair<int, int>>;
|
||||
|
||||
|
||||
/**
|
||||
* A litteral bag of pieces, in which you take each of its piece randomly one by one then start again with a new bag
|
||||
@@ -14,10 +17,15 @@
|
||||
class Bag {
|
||||
private:
|
||||
std::shared_ptr<PiecesList> piecesList; // the list of loaded pieces
|
||||
int highestSize; // the highest size of piece in the bag
|
||||
std::vector<std::pair<int, int>> selectedPieces; // the list of pieces that can be given to the player
|
||||
DistributionMode distributionMode; // the distribution mode
|
||||
std::vector<double> propotionsPerSize; // the proportion of pieces for each size
|
||||
std::pair<int, int> next; // the next piece to give
|
||||
std::vector<std::pair<int, int>> currentBag; // the list of pieces that are still to be taken out before starting a new bag
|
||||
std::vector<std::pair<int, int>> nextBag; // the list of pieces that have been taken out of the current bag and have been placed in the next
|
||||
std::vector<PieceBag> currentBags; // for each size, the list of pieces that are still to be taken out before starting a new bag
|
||||
std::vector<PieceBag> nextBags; // for each size, the list of pieces that have been taken out of the current bag and have been placed in the next
|
||||
std::vector<int> sizesBag; // the list each of bags that are still to have a piece taken out of them
|
||||
std::vector<double> sizesProgression; // how close each size is to meet its quota of pieces
|
||||
|
||||
public:
|
||||
/**
|
||||
@@ -26,7 +34,7 @@ class Bag {
|
||||
Bag(const std::shared_ptr<PiecesList>& piecesList);
|
||||
|
||||
/**
|
||||
* Ignores the remaining pieces in the current bag and startd fresh from a new bag
|
||||
* Ignores the remaining pieces in the current bag and start fresh from a new bag
|
||||
*/
|
||||
void jumpToNextBag();
|
||||
|
||||
@@ -44,7 +52,12 @@ class Bag {
|
||||
|
||||
private:
|
||||
/**
|
||||
* Prepare the next picked piece in advance
|
||||
* Prepares the next picked piece in advance
|
||||
*/
|
||||
void prepareNext();
|
||||
|
||||
/**
|
||||
* Gets the next picked piece from the specified bag
|
||||
*/
|
||||
void getNextPieceFromBag(int bagIndex);
|
||||
};
|
||||
|
||||
27
src/Core/DistributionMode.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
|
||||
/**
|
||||
* The modes of pieces distribution managed by the app
|
||||
*/
|
||||
enum DistributionMode {
|
||||
DEFAULT,
|
||||
UNIFORM,
|
||||
CUSTOM
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return A string containing the name of the given distribution mode
|
||||
*/
|
||||
inline std::string getPiecesDistributionName(DistributionMode distributionMode) {
|
||||
static const std::string DISTRIBUTION_NAMES[] = {
|
||||
"DEFAULT",
|
||||
"UNIFORM",
|
||||
"CUSTOM"
|
||||
};
|
||||
|
||||
return DISTRIBUTION_NAMES[distributionMode];
|
||||
}
|
||||
@@ -9,8 +9,8 @@
|
||||
#include <memory>
|
||||
|
||||
static const int SUBPX_PER_ROW = 60; // the number of position the active piece can take "between" two rows
|
||||
static const int SOFT_DROP_SCORE = 1; // the score gained by line soft dropped
|
||||
static const int HARD_DROP_SCORE = 2; // the score gained by line hard dropped
|
||||
static const int LOCK_DELAY_SCORE = 1; // the score gained when the piece locks due to lock delay
|
||||
static const int HARD_DROP_SCORE = 10; // the score gained when the piece locks due to an hard drop
|
||||
static const int LINE_CLEAR_BASE_SCORE = 100; // the score value of clearing a single line
|
||||
static const int B2B_SCORE_MULTIPLIER = 2; // by how much havaing B2B on multiplies the score of the line clear
|
||||
static const int B2B_MIN_LINE_NUMBER = 4; // the minimum number of lines needed to be cleared at once to gain B2B (without a spin)
|
||||
@@ -55,6 +55,8 @@ void Game::initialize() {
|
||||
void Game::nextFrame(const std::set<Action>& playerActions) {
|
||||
if (this->lost || this->hasWon()) return;
|
||||
|
||||
bool pieceJustLocked = false;
|
||||
|
||||
if (this->started) {
|
||||
bool AREJustEnded = (this->leftARETime == 1);
|
||||
if (this->leftARETime > 0) {
|
||||
@@ -65,36 +67,39 @@ void Game::nextFrame(const std::set<Action>& playerActions) {
|
||||
if (AREJustEnded) {
|
||||
this->lost = this->board.spawnNextPiece();
|
||||
this->resetPiece(true);
|
||||
}
|
||||
|
||||
/* IRS and IHS */
|
||||
Rotation initialRotation = NONE
|
||||
+ ((this->initialActions.contains(ROTATE_CW)) ? CLOCKWISE : NONE)
|
||||
+ ((this->initialActions.contains(ROTATE_180)) ? DOUBLE : NONE)
|
||||
+ ((this->initialActions.contains(ROTATE_CCW)) ? COUNTERCLOCKWISE : NONE);
|
||||
/* IRS and IHS */
|
||||
bool initialRotated = (this->initialActions.contains(ROTATE_0) || this->initialActions.contains(ROTATE_CW)
|
||||
|| this->initialActions.contains(ROTATE_180) || this->initialActions.contains(ROTATE_CCW));
|
||||
Rotation initialRotation = NONE
|
||||
+ ((this->initialActions.contains(ROTATE_CW)) ? CLOCKWISE : NONE)
|
||||
+ ((this->initialActions.contains(ROTATE_180)) ? DOUBLE : NONE)
|
||||
+ ((this->initialActions.contains(ROTATE_CCW)) ? COUNTERCLOCKWISE : NONE);
|
||||
|
||||
if (this->initialActions.contains(HOLD)) {
|
||||
this->lost = (!this->board.hold(initialRotation));
|
||||
}
|
||||
else {
|
||||
if ((initialRotation != NONE) || this->initialActions.contains(ROTATE_0)) {
|
||||
this->lost = (!this->board.rotate(initialRotation));
|
||||
if (this->initialActions.contains(HOLD)) {
|
||||
this->board.hold(initialRotation);
|
||||
}
|
||||
}
|
||||
|
||||
if (this->lost) {
|
||||
if (initialRotation == NONE) {
|
||||
this->lost = (!this->board.rotate(initialRotation));
|
||||
else {
|
||||
if (initialRotated) {
|
||||
this->board.rotate(initialRotation);
|
||||
}
|
||||
}
|
||||
|
||||
this->lost = this->board.activePieceInWall();
|
||||
if (this->lost) {
|
||||
this->board.rotate(NONE);
|
||||
this->lost = this->board.activePieceInWall();
|
||||
|
||||
if (this->lost) {
|
||||
this->framesPassed++;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this->lost) {
|
||||
this->framesPassed++;
|
||||
return;
|
||||
}
|
||||
|
||||
/* HOLD */
|
||||
if (playerActions.contains(HOLD) && (!this->heldActions.contains(HOLD))) {
|
||||
if (this->board.hold()) {
|
||||
if (this->board.hold({})) {
|
||||
this->resetPiece(false);
|
||||
}
|
||||
}
|
||||
@@ -137,16 +142,14 @@ void Game::nextFrame(const std::set<Action>& playerActions) {
|
||||
|
||||
// SDR=0 -> instant drop
|
||||
if (appliedSDR == 0) {
|
||||
while (this->board.moveDown()) {
|
||||
this->score += SOFT_DROP_SCORE;
|
||||
}
|
||||
while (this->board.moveDown());
|
||||
}
|
||||
// SDR>1 -> move down by specified amount
|
||||
else {
|
||||
this->subVerticalPosition += (SUBPX_PER_ROW / appliedSDR);
|
||||
while (this->subVerticalPosition >= SUBPX_PER_ROW) {
|
||||
this->board.moveDown();
|
||||
this->subVerticalPosition -= SUBPX_PER_ROW;
|
||||
this->score += (this->board.moveDown() * SOFT_DROP_SCORE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -154,10 +157,10 @@ void Game::nextFrame(const std::set<Action>& playerActions) {
|
||||
/* HARD DROP */
|
||||
// needs to be done last because we can enter ARE period afterwards
|
||||
if (this->initialActions.contains(HARD_DROP) || (playerActions.contains(HARD_DROP) && (!this->heldActions.contains(HARD_DROP)))) {
|
||||
while (this->board.moveDown()) {
|
||||
this->score += HARD_DROP_SCORE;
|
||||
}
|
||||
while (this->board.moveDown());
|
||||
this->lockPiece();
|
||||
pieceJustLocked = true;
|
||||
this->score += HARD_DROP_SCORE;
|
||||
}
|
||||
// no need to apply gravity and lock delay if the piece was hard dropped
|
||||
else {
|
||||
@@ -187,6 +190,8 @@ void Game::nextFrame(const std::set<Action>& playerActions) {
|
||||
|
||||
if ((this->totalLockDelay > this->parameters.getLockDelay()) || (this->totalForcedLockDelay > this->parameters.getForcedLockDelay())) {
|
||||
this->lockPiece();
|
||||
pieceJustLocked = true;
|
||||
this->score += LOCK_DELAY_SCORE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,11 +206,11 @@ void Game::nextFrame(const std::set<Action>& playerActions) {
|
||||
}
|
||||
}
|
||||
|
||||
this->heldActions = playerActions;
|
||||
|
||||
if ((!this->started) || this->leftARETime > 0) {
|
||||
for (Action action : playerActions) {
|
||||
this->initialActions.insert(action);
|
||||
if ((!pieceJustLocked) && (!heldActions.contains(action))) {
|
||||
this->initialActions.insert(action);
|
||||
}
|
||||
}
|
||||
|
||||
if (this->heldDAS >= 0) {
|
||||
@@ -219,6 +224,8 @@ void Game::nextFrame(const std::set<Action>& playerActions) {
|
||||
else this->heldDAS = 0;
|
||||
}
|
||||
}
|
||||
|
||||
this->heldActions = playerActions;
|
||||
}
|
||||
|
||||
void Game::resetPiece(bool newPiece) {
|
||||
@@ -283,25 +290,34 @@ void Game::rotatePiece(Rotation rotation) {
|
||||
|
||||
void Game::lockPiece() {
|
||||
LineClear clear = this->board.lockPiece();
|
||||
this->parameters.clearLines(clear.lines);
|
||||
this->parameters.lockedPiece(clear);
|
||||
|
||||
bool B2BConditionsAreMet = ((clear.lines > B2B_MIN_LINE_NUMBER) || clear.isSpin || clear.isMiniSpin);
|
||||
if (clear.lines > 0) {
|
||||
bool B2BConditionsAreMet = ((clear.lines >= B2B_MIN_LINE_NUMBER) || clear.isSpin || clear.isMiniSpin);
|
||||
|
||||
/* clearing one more line is worth 2x more
|
||||
clearing with a spin is worth as much as clearing 2x more lines */
|
||||
clearing with a spin is worth as much as clearing 2x more lines */
|
||||
long int clearScore = LINE_CLEAR_BASE_SCORE;
|
||||
clearScore = clearScore << (clear.lines << (clear.isSpin));
|
||||
|
||||
if (this->B2BChain && B2BConditionsAreMet) clearScore *= B2B_SCORE_MULTIPLIER;
|
||||
if (this->B2BChain && B2BConditionsAreMet) {
|
||||
clearScore *= B2B_SCORE_MULTIPLIER;
|
||||
}
|
||||
this->score += clearScore;
|
||||
|
||||
this->B2BChain = B2BConditionsAreMet;
|
||||
}
|
||||
this->B2BChain = B2BConditionsAreMet;
|
||||
|
||||
if (!this->hasWon()) {
|
||||
this->leftARETime = this->parameters.getARE();
|
||||
if (this->leftARETime == 0) {
|
||||
this->lost = this->board.spawnNextPiece();
|
||||
this->resetPiece(true);
|
||||
|
||||
if (this->lost) {
|
||||
this->board.rotate(NONE);
|
||||
this->lost = this->board.activePieceInWall();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -318,6 +334,10 @@ int Game::getClearedLines() const {
|
||||
return this->parameters.getClearedLines();
|
||||
}
|
||||
|
||||
int Game::getGrade() const {
|
||||
return this->parameters.getGrade();
|
||||
}
|
||||
|
||||
int Game::getLevel() const {
|
||||
return this->parameters.getLevel();
|
||||
}
|
||||
@@ -334,12 +354,20 @@ bool Game::isOnB2BChain() const {
|
||||
return this->B2BChain;
|
||||
}
|
||||
|
||||
float Game::getLockDelayProgression() const {
|
||||
return (float) this->totalLockDelay / this->parameters.getLockDelay();
|
||||
}
|
||||
|
||||
float Game::getForcedLockDelayProgression() const {
|
||||
return (float) this->totalForcedLockDelay / this->parameters.getForcedLockDelay();
|
||||
}
|
||||
|
||||
bool Game::areBlocksBones() const {
|
||||
return this->parameters.getBoneBlocks();
|
||||
}
|
||||
|
||||
Position Game::ghostPiecePosition() const {
|
||||
return this->board.lowestPosition();
|
||||
bool Game::isBoardInvisible() const {
|
||||
return this->parameters.getInvisibleBoard();
|
||||
}
|
||||
|
||||
const Board& Game::getBoard() const {
|
||||
@@ -354,6 +382,10 @@ const Position& Game::getActivePiecePosition() const {
|
||||
return this->board.getActivePiecePosition();
|
||||
}
|
||||
|
||||
Position Game::getGhostPiecePosition() const {
|
||||
return this->board.lowestPosition();
|
||||
}
|
||||
|
||||
const std::shared_ptr<Piece>& Game::getHeldPiece() const {
|
||||
return this->board.getHeldPiece();
|
||||
}
|
||||
|
||||
@@ -101,6 +101,11 @@ class Game {
|
||||
*/
|
||||
int getClearedLines() const;
|
||||
|
||||
/**
|
||||
* @return The current grade
|
||||
*/
|
||||
int getGrade() const;
|
||||
|
||||
/**
|
||||
* @return The number of frames passed since the start of the game
|
||||
*/
|
||||
@@ -116,15 +121,25 @@ class Game {
|
||||
*/
|
||||
bool isOnB2BChain() const;
|
||||
|
||||
/**
|
||||
* @return How close the active piece's lock delay is to the maximum allowed, betwwen 0 and 1
|
||||
*/
|
||||
float getLockDelayProgression() const;
|
||||
|
||||
/**
|
||||
* @return How close the active piece's forced lock delay is to the maximum allowed, betwwen 0 and 1
|
||||
*/
|
||||
float getForcedLockDelayProgression() const;
|
||||
|
||||
/**
|
||||
* @return If all blocks are currently bone blocks
|
||||
*/
|
||||
bool areBlocksBones() const;
|
||||
|
||||
/**
|
||||
* @return The position of the ghost piece
|
||||
* @return If the board is currently invisible
|
||||
*/
|
||||
Position ghostPiecePosition() const;
|
||||
bool isBoardInvisible() const;
|
||||
|
||||
/**
|
||||
* @return The board
|
||||
@@ -141,6 +156,11 @@ class Game {
|
||||
*/
|
||||
const Position& getActivePiecePosition() const;
|
||||
|
||||
/**
|
||||
* @return The position of the ghost piece
|
||||
*/
|
||||
Position getGhostPiecePosition() const;
|
||||
|
||||
/**
|
||||
* @return A pointer to the held piece, can be null
|
||||
*/
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
|
||||
|
||||
GameBoard::GameBoard(int boardWidth, int boardHeight, const std::shared_ptr<PiecesList>& piecesList, int nextQueueLength) :
|
||||
@@ -36,9 +38,12 @@ void GameBoard::initialize() {
|
||||
this->activePiece = nullptr;
|
||||
this->heldPiece = nullptr;
|
||||
this->isLastMoveKick = false;
|
||||
this->movedLeftLast = false;
|
||||
}
|
||||
|
||||
bool GameBoard::moveLeft() {
|
||||
this->movedLeftLast = true;
|
||||
|
||||
if (this->activePieceInWall(Position{-1, 0})) {
|
||||
return false;
|
||||
}
|
||||
@@ -50,6 +55,8 @@ bool GameBoard::moveLeft() {
|
||||
}
|
||||
|
||||
bool GameBoard::moveRight() {
|
||||
this->movedLeftLast = false;
|
||||
|
||||
if (this->activePieceInWall(Position{1, 0})) {
|
||||
return false;
|
||||
}
|
||||
@@ -137,30 +144,21 @@ bool GameBoard::tryKicking(bool testingBottom, const std::set<Position>& safePos
|
||||
bool overlapsRight = true;
|
||||
int i = (j == 0) ? 1 : 0;
|
||||
do {
|
||||
// check right before left arbitrarly, we don't decide this with rotations since it would still be arbitrary with 180° rotations
|
||||
if (overlapsRight) {
|
||||
Position shift{+i, j};
|
||||
if (!this->activePieceOverlaps(safePositions, shift)) {
|
||||
overlapsLeft = false;
|
||||
// we first check the side to which the player moved last
|
||||
if (movedLeftLast) {
|
||||
if (overlapsLeft) {
|
||||
if (this->tryFittingKickedPiece(safePositions, Position({-i, j}), overlapsLeft)) return true;
|
||||
}
|
||||
else {
|
||||
if (!this->activePieceInWall(shift)) {
|
||||
this->activePiecePosition += shift;
|
||||
return true;
|
||||
}
|
||||
if (overlapsRight) {
|
||||
if (this->tryFittingKickedPiece(safePositions, Position({+i, j}), overlapsRight)) return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (overlapsLeft) {
|
||||
Position shift{-i, j};
|
||||
if (!this->activePieceOverlaps(safePositions, shift)) {
|
||||
overlapsLeft = false;
|
||||
else {
|
||||
if (overlapsRight) {
|
||||
if (this->tryFittingKickedPiece(safePositions, Position({+i, j}), overlapsRight)) return true;
|
||||
}
|
||||
else {
|
||||
if (!this->activePieceInWall(shift)) {
|
||||
this->activePiecePosition += shift;
|
||||
return true;
|
||||
}
|
||||
if (overlapsLeft) {
|
||||
if (this->tryFittingKickedPiece(safePositions, Position({-i, j}), overlapsLeft)) return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,12 +175,32 @@ bool GameBoard::tryKicking(bool testingBottom, const std::set<Position>& safePos
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GameBoard::hold(Rotation initialRotation) {
|
||||
bool GameBoard::tryFittingKickedPiece(const std::set<Position>& safePositions, const Position& shift, bool& overlaps) {
|
||||
if (!this->activePieceOverlaps(safePositions, shift)) {
|
||||
overlaps = false;
|
||||
}
|
||||
else {
|
||||
if (!this->activePieceInWall(shift)) {
|
||||
this->activePiecePosition += shift;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GameBoard::activePieceOverlaps(const std::set<Position>& safePositions, const Position& shift) const {
|
||||
for (Position position : this->activePiece->getPositions()) {
|
||||
if (safePositions.contains(position + this->activePiecePosition + shift)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GameBoard::hold(std::optional<Rotation> initialRotation) {
|
||||
Position storedPosition = this->activePiecePosition;
|
||||
std::swap(this->activePiece, this->heldPiece);
|
||||
|
||||
bool isFirstTimeHolding = (this->activePiece == nullptr);
|
||||
if (isFirstTimeHolding) {
|
||||
// try with the next piece in queue since there is no piece in the hold box yet
|
||||
if (this->nextQueueLength == 0) {
|
||||
this->activePiece = std::make_shared<Piece>(this->generator.lookNext());
|
||||
}
|
||||
@@ -191,21 +209,25 @@ bool GameBoard::hold(Rotation initialRotation) {
|
||||
}
|
||||
}
|
||||
|
||||
Piece stored = *this->activePiece;
|
||||
Position storedPosition = this->activePiecePosition;
|
||||
// try initial rotation
|
||||
this->goToSpawnPosition();
|
||||
this->rotate(initialRotation);
|
||||
if (initialRotation.has_value()) {
|
||||
std::shared_ptr<Piece> storedPiece(this->activePiece);
|
||||
this->rotate(initialRotation.value());
|
||||
|
||||
// if the piece can't spawn, abort initial rotation
|
||||
if (this->activePieceInWall()) {
|
||||
this->activePiece = storedPiece;
|
||||
}
|
||||
}
|
||||
|
||||
// if the piece can't spawn, try 0° rotation
|
||||
if (this->activePieceInWall()) {
|
||||
this->activePiece = std::make_shared<Piece>(stored);
|
||||
this->goToSpawnPosition();
|
||||
std::shared_ptr<Piece> storedPiece(this->activePiece);
|
||||
this->rotate(NONE);
|
||||
|
||||
// if the piece still can't spawn, abort holding
|
||||
if (this->activePieceInWall()) {
|
||||
if (isFirstTimeHolding) {
|
||||
this->activePiece = nullptr;
|
||||
}
|
||||
this->activePiece = (isFirstTimeHolding) ? nullptr : storedPiece;
|
||||
std::swap(this->activePiece, this->heldPiece);
|
||||
this->activePiecePosition = storedPosition;
|
||||
return false;
|
||||
@@ -213,7 +235,6 @@ bool GameBoard::hold(Rotation initialRotation) {
|
||||
}
|
||||
|
||||
if (isFirstTimeHolding) {
|
||||
// confirm we keep the piece we tried with
|
||||
this->nextQueue.push_back(this->generator.getNext());
|
||||
this->nextQueue.erase(this->nextQueue.begin());
|
||||
}
|
||||
@@ -236,6 +257,13 @@ bool GameBoard::spawnNextPiece() {
|
||||
return this->activePieceInWall();
|
||||
}
|
||||
|
||||
bool GameBoard::activePieceInWall(const Position& shift) const {
|
||||
for (Position position : this->activePiece->getPositions()) {
|
||||
if (this->board.getBlock(position + this->activePiecePosition + shift) != NOTHING) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GameBoard::touchesGround() const {
|
||||
return this->activePieceInWall(Position{0, -1});
|
||||
}
|
||||
@@ -257,6 +285,8 @@ LineClear GameBoard::lockPiece() {
|
||||
this->board.changeBlock(position + this->activePiecePosition, this->activePiece->getBlockType());
|
||||
}
|
||||
|
||||
this->activePiece = nullptr;
|
||||
|
||||
return LineClear{this->board.clearRows(), isLockedInPlace, (!isLockedInPlace) && this->isLastMoveKick};
|
||||
}
|
||||
|
||||
@@ -291,20 +321,6 @@ const std::vector<Piece>& GameBoard::getNextPieces() const {
|
||||
return this->nextQueue;
|
||||
}
|
||||
|
||||
bool GameBoard::activePieceInWall(const Position& shift) const {
|
||||
for (Position position : this->activePiece->getPositions()) {
|
||||
if (this->board.getBlock(position + this->activePiecePosition + shift) != NOTHING) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GameBoard::activePieceOverlaps(const std::set<Position>& safePositions, const Position& shift) const {
|
||||
for (Position position : this->activePiece->getPositions()) {
|
||||
if (safePositions.contains(position + this->activePiecePosition + shift)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void GameBoard::goToSpawnPosition() {
|
||||
int lowestPosition = this->activePiece->getLength() - 1;
|
||||
for (Position position : this->activePiece->getPositions()) {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
|
||||
/**
|
||||
@@ -22,6 +23,7 @@ class GameBoard {
|
||||
int nextQueueLength; // the number of next pieces seeable at a time
|
||||
std::vector<Piece> nextQueue; // the list of the next pieces to spawn in the board
|
||||
bool isLastMoveKick; // wheter the last action the piece did was kicking
|
||||
bool movedLeftLast; // wheter the last sideway movement was a left one
|
||||
|
||||
public:
|
||||
/**
|
||||
@@ -72,12 +74,24 @@ class GameBoard {
|
||||
*/
|
||||
bool tryKicking(bool testingBottom, const std::set<Position>& safePositions);
|
||||
|
||||
/**
|
||||
* Tries fitting the kicked active piece at the specified position
|
||||
* @return If it suceeded
|
||||
*/
|
||||
bool tryFittingKickedPiece(const std::set<Position>& safePositions, const Position& shift, bool& overlaps);
|
||||
|
||||
/**
|
||||
* Check if one of the active piece's positions shifted by a specified position would overlap with a set of positions
|
||||
* @return If the shifted active piece overlaps with one of the position
|
||||
*/
|
||||
bool activePieceOverlaps(const std::set<Position>& safePositions, const Position& shift = Position{0, 0}) const;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Tries holding the active piece or swapping it if one was already stocked, while trying to apply an initial rotation to the newly spawned piece
|
||||
* @return If it suceeded
|
||||
*/
|
||||
bool hold(Rotation initialRotation = NONE);
|
||||
bool hold(std::optional<Rotation> initialRotation);
|
||||
|
||||
/**
|
||||
* Spawns the next piece from the queue
|
||||
@@ -85,6 +99,12 @@ class GameBoard {
|
||||
*/
|
||||
bool spawnNextPiece();
|
||||
|
||||
/**
|
||||
* Checks if one of the active piece's positions touches a wall in the board
|
||||
* @return If the active piece is in a wall
|
||||
*/
|
||||
bool activePieceInWall(const Position& shift = Position{0, 0}) const;
|
||||
|
||||
/**
|
||||
* Checks is the active piece as a wall directly below one of its position
|
||||
* @return If it touches a ground
|
||||
@@ -134,18 +154,6 @@ class GameBoard {
|
||||
const std::vector<Piece>& getNextPieces() const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Checks if one of the active piece's positions touches a wall in the board
|
||||
* @return If the active piece spawned in a wall
|
||||
*/
|
||||
bool activePieceInWall(const Position& shift = Position{0, 0}) const;
|
||||
|
||||
/**
|
||||
* Check if one of the active piece's positions shifted by a specified position would overlap with a set of positions
|
||||
* @return If the shifted active piece overlaps with one of the position
|
||||
*/
|
||||
bool activePieceOverlaps(const std::set<Position>& safePositions, const Position& shift = Position{0, 0}) const;
|
||||
|
||||
/**
|
||||
* Sets the active piece to its spawn position
|
||||
*/
|
||||
|
||||
@@ -7,12 +7,14 @@
|
||||
GameParameters::GameParameters(Gamemode gamemode, const Player& controls) :
|
||||
gamemode(gamemode),
|
||||
controls(controls) {
|
||||
|
||||
|
||||
this->reset();
|
||||
}
|
||||
|
||||
void GameParameters::reset() {
|
||||
this->clearedLines = 0;
|
||||
this->grade = 0;
|
||||
|
||||
switch (this->gamemode) {
|
||||
// lowest gravity
|
||||
case SPRINT : {this->level = 1; break;}
|
||||
@@ -22,29 +24,51 @@ void GameParameters::reset() {
|
||||
case MARATHON : {this->level = 1; break;}
|
||||
// goes from level 20 to 39
|
||||
case MASTER : {this->level = 20; break;}
|
||||
// goes from level 1 to 19
|
||||
case INVISIBLE : {this->level = 1; break;}
|
||||
// no gravity
|
||||
case ZEN : {this->level = 0; break;}
|
||||
default : this->level = 1;
|
||||
}
|
||||
|
||||
this->updateStats();
|
||||
}
|
||||
|
||||
void GameParameters::clearLines(int lineNumber) {
|
||||
void GameParameters::lockedPiece(const LineClear& lineClear) {
|
||||
switch (this->gamemode) {
|
||||
// modes where level increases
|
||||
// modes where level increases with lines
|
||||
case MARATHON :
|
||||
case MASTER : {
|
||||
int previousLines = this->clearedLines;
|
||||
this->clearedLines += lineNumber;
|
||||
this->clearedLines += lineClear.lines;
|
||||
|
||||
// level increments every 10 lines, stats only changes on level up
|
||||
if (previousLines / 10 < this->clearedLines / 10) {
|
||||
this->level = this->clearedLines / 10;
|
||||
this->level += (this->clearedLines / 10 - previousLines / 10);
|
||||
this->updateStats();
|
||||
}
|
||||
break;
|
||||
}
|
||||
// other modes
|
||||
default : this->clearedLines += lineNumber;
|
||||
default : this->clearedLines += lineClear.lines;
|
||||
}
|
||||
|
||||
int previousGrade = this->grade;
|
||||
if (!((lineClear.lines == 0) && ((this->grade % 100) == 99))) {
|
||||
this->grade += (1 + lineClear.lines);
|
||||
}
|
||||
|
||||
switch (this->gamemode) {
|
||||
// modes where level increases with grade
|
||||
case INVISIBLE : {
|
||||
if (previousGrade / 100 < this->grade / 100) {
|
||||
this->level += 2;
|
||||
this->updateStats();
|
||||
}
|
||||
break;
|
||||
}
|
||||
// other modes
|
||||
default : break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +82,10 @@ bool GameParameters::hasWon(int framesPassed) const {
|
||||
case MARATHON : return this->clearedLines >= 200;
|
||||
// win once 200 lines have been cleared
|
||||
case MASTER : return this->clearedLines >= 200;
|
||||
// win once 1000 grade has been passed
|
||||
case INVISIBLE : return this->grade >= 1000;
|
||||
// infinite mode
|
||||
case ZEN :
|
||||
default : return false;
|
||||
}
|
||||
}
|
||||
@@ -65,15 +93,17 @@ bool GameParameters::hasWon(int framesPassed) const {
|
||||
void GameParameters::updateStats() {
|
||||
/* NEXT QUEUE */
|
||||
switch (this->gamemode) {
|
||||
// 5 for rapidity gamemodes
|
||||
// 5 for fast-controls gamemodes
|
||||
case SPRINT :
|
||||
case ULTRA : {
|
||||
case ULTRA :
|
||||
case ZEN : {
|
||||
this->nextQueueLength = 5;
|
||||
break;
|
||||
}
|
||||
// 3 for endurance gamemodes
|
||||
// 3 for slow-controls gamemodes
|
||||
case MARATHON :
|
||||
case MASTER : {
|
||||
case MASTER :
|
||||
case INVISIBLE : {
|
||||
this->nextQueueLength = 3;
|
||||
break;
|
||||
}
|
||||
@@ -83,10 +113,13 @@ void GameParameters::updateStats() {
|
||||
/* BONE BLOCKS */
|
||||
switch (this->gamemode) {
|
||||
// blocks turns into bone blocks at level 30
|
||||
case MASTER : this->boneBlocks = (this->level >= 30);
|
||||
case MASTER : {this->boneBlocks = (this->level >= 30); break;}
|
||||
default : this->boneBlocks = false;
|
||||
}
|
||||
|
||||
/* INVISIBLE */
|
||||
this->invisibleBoard = (this->gamemode == INVISIBLE);
|
||||
|
||||
/* GRAVITY */
|
||||
// get gravity for an assumed 20-rows board
|
||||
static const int gravityPerLevel[] = {
|
||||
@@ -126,6 +159,8 @@ void GameParameters::updateStats() {
|
||||
switch (this->gamemode) {
|
||||
// starts at 500ms (30f) at lvl 20 and ends at 183ms (11f) at lvl 39
|
||||
case MASTER : {this->lockDelay = 30 - (this->level - 20); break;}
|
||||
// 10s
|
||||
case ZEN : {this->lockDelay = 60 * 10; break;}
|
||||
// 1s by default
|
||||
default : this->lockDelay = 60;
|
||||
}
|
||||
@@ -139,6 +174,8 @@ void GameParameters::updateStats() {
|
||||
case MARATHON : {this->ARE = 24 - (this->level - 1); break;}
|
||||
// starts at 400ms (24f) at lvl 20 and ends at 083ms (5f) at lvl 39
|
||||
case MASTER : {this->ARE = 24 - (this->level - 20); break;}
|
||||
// fixed at 250ms (15f)
|
||||
case INVISIBLE : {this->ARE = 15; break;}
|
||||
// no ARE by default
|
||||
default : this->ARE = 0;
|
||||
}
|
||||
@@ -203,6 +240,10 @@ int GameParameters::getLevel() const {
|
||||
return this->level;
|
||||
}
|
||||
|
||||
int GameParameters::getGrade() const {
|
||||
return this->grade;
|
||||
}
|
||||
|
||||
int GameParameters::getNextQueueLength() const {
|
||||
return this->nextQueueLength;
|
||||
}
|
||||
@@ -211,6 +252,10 @@ bool GameParameters::getBoneBlocks() const {
|
||||
return this->boneBlocks;
|
||||
}
|
||||
|
||||
bool GameParameters::getInvisibleBoard() const {
|
||||
return this->invisibleBoard;
|
||||
}
|
||||
|
||||
int GameParameters::getGravity() const {
|
||||
return this->gravity;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "Gamemode.h"
|
||||
#include "Player.h"
|
||||
#include "LineClear.h"
|
||||
|
||||
|
||||
/**
|
||||
@@ -14,8 +15,10 @@ class GameParameters {
|
||||
Player controls; // the player's controls
|
||||
int clearedLines; // the number of cleared lines
|
||||
int level; // the current level
|
||||
int grade; // the current amount of points
|
||||
int nextQueueLength; // the number of pieces visibles in the next queue
|
||||
bool boneBlocks; // wheter all blocks are bone blocks
|
||||
bool invisibleBoard; // wheter the board is invisible
|
||||
int gravity; // the gravity at which pieces drop
|
||||
int lockDelay; // the time before the piece lock in place
|
||||
int forcedLockDelay; // the forced time before the piece lock in place
|
||||
@@ -39,7 +42,7 @@ class GameParameters {
|
||||
/**
|
||||
* Counts the newly cleared lines and update level and stats if needed
|
||||
*/
|
||||
void clearLines(int lineNumber);
|
||||
void lockedPiece(const LineClear& lineClear);
|
||||
|
||||
/**
|
||||
* Checks if the game ended based on the current states and time passed, accorind to the gamemode
|
||||
@@ -63,6 +66,11 @@ class GameParameters {
|
||||
* @return The current level
|
||||
*/
|
||||
int getLevel() const;
|
||||
|
||||
/**
|
||||
* @return The current grade
|
||||
*/
|
||||
int getGrade() const;
|
||||
|
||||
/**
|
||||
* @return The length of the next queue
|
||||
@@ -70,9 +78,14 @@ class GameParameters {
|
||||
int getNextQueueLength() const;
|
||||
|
||||
/**
|
||||
* Returns wheter the blocks are currently bone blocks
|
||||
* @return Wheter the blocks are currently bone blocks
|
||||
*/
|
||||
bool getBoneBlocks() const;
|
||||
|
||||
/**
|
||||
* @return Wheter the board is currently invisible
|
||||
*/
|
||||
bool getInvisibleBoard() const;
|
||||
|
||||
/**
|
||||
* @return The current gravity for a 20-line high board
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
|
||||
/**
|
||||
* Every gamemode supported by the game
|
||||
@@ -8,5 +10,40 @@ enum Gamemode {
|
||||
SPRINT,
|
||||
MARATHON,
|
||||
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];
|
||||
}
|
||||
|
||||
@@ -47,6 +47,14 @@ Player& Menu::getPlayerControls() {
|
||||
return this->playerControls;
|
||||
}
|
||||
|
||||
const Player& Menu::readPlayerControls() const {
|
||||
return this->playerControls;
|
||||
}
|
||||
|
||||
PiecesList& Menu::getPiecesList() {
|
||||
return *this->piecesList;
|
||||
}
|
||||
|
||||
const PiecesList& Menu::readPiecesList() const {
|
||||
return *this->piecesList;
|
||||
}
|
||||
|
||||
@@ -50,14 +50,24 @@ class Menu {
|
||||
* @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;
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "../Pieces/Piece.h"
|
||||
#include "../Pieces/PiecesFiles.h"
|
||||
#include "DistributionMode.h"
|
||||
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
@@ -11,6 +12,10 @@ PiecesList::PiecesList() {
|
||||
this->highestLoadedSize = 0;
|
||||
this->selectedPieces.clear();
|
||||
|
||||
this->distributionMode = DEFAULT;
|
||||
this->proportionsPerSize.clear();
|
||||
this->customProportionsPerSize.clear();
|
||||
|
||||
// we need to have something at index 0 even if there is no pieces of size 0
|
||||
this->loadedPieces.clear();
|
||||
this->convexPieces.clear();
|
||||
@@ -24,6 +29,7 @@ PiecesList::PiecesList() {
|
||||
|
||||
bool PiecesList::loadPieces(int size) {
|
||||
if (size < 1) return false;
|
||||
if (size <= this->highestLoadedSize) return true;
|
||||
|
||||
PiecesFiles piecesFiles;
|
||||
for (int i = this->highestLoadedSize + 1; i <= size; i++) {
|
||||
@@ -87,6 +93,14 @@ void PiecesList::unselectAll() {
|
||||
this->selectedPieces.clear();
|
||||
}
|
||||
|
||||
bool PiecesList::setDistributionMode(DistributionMode distributionMode) {
|
||||
if (distributionMode == DEFAULT || distributionMode == UNIFORM || distributionMode == CUSTOM) {
|
||||
this->distributionMode = distributionMode;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int PiecesList::getHighestLoadedSize() const {
|
||||
return this->highestLoadedSize;
|
||||
}
|
||||
@@ -101,13 +115,42 @@ std::vector<std::pair<int, int>> PiecesList::getSelectedPieces() const {
|
||||
return this->selectedPieces;
|
||||
}
|
||||
|
||||
DistributionMode PiecesList::getDistributionMode() const {
|
||||
return this->distributionMode;
|
||||
}
|
||||
|
||||
bool PiecesList::changeCustomDistribution(int size, double distribution) {
|
||||
if (size < 1 || size > this->highestLoadedSize || distribution > 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this->customProportionsPerSize.at(size) = distribution;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<double> PiecesList::getProportionsPerSize() const {
|
||||
if (this->distributionMode == CUSTOM) {
|
||||
return this->customProportionsPerSize;
|
||||
}
|
||||
else {
|
||||
return this->proportionsPerSize;
|
||||
}
|
||||
}
|
||||
|
||||
Piece PiecesList::getPiece(const std::pair<int, int>& pieceIndex) const {
|
||||
return this->loadedPieces.at(pieceIndex.first).at(pieceIndex.second);
|
||||
}
|
||||
|
||||
const Piece& PiecesList::lookAtPiece(const std::pair<int, int>& pieceIndex) const {
|
||||
return this->loadedPieces.at(pieceIndex.first).at(pieceIndex.second);
|
||||
}
|
||||
|
||||
void PiecesList::pushBackEmptyVectors() {
|
||||
this->loadedPieces.push_back(std::vector<Piece>());
|
||||
this->convexPieces.push_back(std::vector<int>());
|
||||
this->holelessPieces.push_back(std::vector<int>());
|
||||
this->otherPieces.push_back(std::vector<int>());
|
||||
|
||||
this->proportionsPerSize.push_back(1);
|
||||
this->customProportionsPerSize.push_back(1);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "../Pieces/Piece.h"
|
||||
#include "DistributionMode.h"
|
||||
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
@@ -18,6 +19,9 @@ class PiecesList {
|
||||
std::vector<std::vector<int>> holelessPieces; // the list of holeless loaded pieces by size
|
||||
std::vector<std::vector<int>> otherPieces; // the list of other loaded pieces by size
|
||||
std::vector<std::pair<int, int>> selectedPieces; // the list of all currently selected pieces
|
||||
DistributionMode distributionMode; // the current pieces distribution mode
|
||||
std::vector<double> proportionsPerSize; // the proportion of piece for each sizes
|
||||
std::vector<double> customProportionsPerSize; // the proportion of piece for each sizes when the distribution mode is set to custom
|
||||
|
||||
public:
|
||||
/**
|
||||
@@ -66,6 +70,19 @@ class PiecesList {
|
||||
*/
|
||||
void unselectAll();
|
||||
|
||||
/**
|
||||
* Changes the current pieces distribution mode
|
||||
* @return If the mode is supported
|
||||
*/
|
||||
bool setDistributionMode(DistributionMode distributionMode);
|
||||
|
||||
/**
|
||||
* Changes the distribution of the specified size for when the distribution mode is set to custom,
|
||||
* the specified distribution must lower or equal to 1
|
||||
* @return If the new distribution was applied
|
||||
*/
|
||||
bool changeCustomDistribution(int size, double distribution);
|
||||
|
||||
/**
|
||||
* @return The highest loaded size of pieces
|
||||
*/
|
||||
@@ -81,10 +98,25 @@ class PiecesList {
|
||||
*/
|
||||
std::vector<std::pair<int, int>> getSelectedPieces() const;
|
||||
|
||||
/**
|
||||
* @return The current distribution mode
|
||||
*/
|
||||
DistributionMode getDistributionMode() const;
|
||||
|
||||
/**
|
||||
* @return The proportion of pieces for each loaded size
|
||||
*/
|
||||
std::vector<double> getProportionsPerSize() const;
|
||||
|
||||
/**
|
||||
* @return A copy of the piece corresponding to the specified index
|
||||
*/
|
||||
Piece getPiece(const std::pair<int, int>& pieceIndex) const;
|
||||
|
||||
/**
|
||||
* @return The piece corresponding to the specified index
|
||||
*/
|
||||
const Piece& lookAtPiece(const std::pair<int, int>& pieceIndex) const;
|
||||
|
||||
private:
|
||||
/**
|
||||
|
||||
72
src/GraphicalUI/AppMenus/AppMenu.cpp
Normal file
@@ -0,0 +1,72 @@
|
||||
#include "AppMenu.h"
|
||||
|
||||
#include "../Settings.h"
|
||||
#include "../PlayerCursor.h"
|
||||
#include "../../Utils/AssetManager.h"
|
||||
|
||||
#include <stack>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
|
||||
AppMenu::AppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow) :
|
||||
menuStack(menuStack),
|
||||
settings(settings),
|
||||
renderWindow(renderWindow) {
|
||||
|
||||
const Asset& file = getResource(AssetName::data_fonts_pressstart_prstartk_ttf);
|
||||
|
||||
this->pressStartFont = sf::Font(file.data, file.size);
|
||||
}
|
||||
|
||||
void AppMenu::updateMetaBinds() {
|
||||
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Enter)) {
|
||||
this->enterPressed = true;
|
||||
this->enterReleased = false;
|
||||
}
|
||||
else {
|
||||
this->enterReleased = this->enterPressed;
|
||||
this->enterPressed = false;
|
||||
}
|
||||
|
||||
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Escape)) {
|
||||
this->escPressed = true;
|
||||
this->escReleased = false;
|
||||
}
|
||||
else {
|
||||
this->escReleased = this->escPressed;
|
||||
this->escPressed = false;
|
||||
}
|
||||
}
|
||||
|
||||
void AppMenu::placeText(sf::Text& text, const std::optional<PlayerCursor>& playerCursor, const sf::String& string, float xPos, float yPos, const std::optional<sf::Vector2u>& cursorPos) const {
|
||||
float sizeMultiplier = this->settings->getWindowSizeMultiplier();
|
||||
|
||||
text.setString(string);
|
||||
if (playerCursor.has_value() && cursorPos.has_value()) {
|
||||
text.setOutlineThickness((playerCursor.value().getPosition() == cursorPos.value()) ? (sizeMultiplier / 2) : 0);
|
||||
}
|
||||
text.setOrigin(sf::Vector2f({0, text.getLocalBounds().size.y / 2}));
|
||||
text.setPosition(sf::Vector2f({sizeMultiplier * xPos, sizeMultiplier * yPos}));
|
||||
this->renderWindow->draw(text);
|
||||
}
|
||||
|
||||
void AppMenu::placeTitle(sf::Text& text, const std::optional<PlayerCursor>& playerCursor, const sf::String& string, float yPos, const std::optional<sf::Vector2u>& cursorPos) const {
|
||||
float sizeMultiplier = this->settings->getWindowSizeMultiplier();
|
||||
|
||||
text.setString(string);
|
||||
if (playerCursor.has_value() && cursorPos.has_value()) {
|
||||
text.setOutlineThickness((playerCursor.value().getPosition() == cursorPos.value()) ? (sizeMultiplier / 2) : 0);
|
||||
}
|
||||
text.setOrigin({text.getLocalBounds().getCenter().x, text.getLocalBounds().size.y / 2});
|
||||
text.setPosition(sf::Vector2f({sizeMultiplier * 40.f, sizeMultiplier * yPos}));
|
||||
this->renderWindow->draw(text);
|
||||
}
|
||||
|
||||
sf::Color AppMenu::getColorOfBlock(Block block, int luminosityShift) const {
|
||||
Color rgbColor = BLOCKS_COLOR[block];
|
||||
return sf::Color(std::clamp(rgbColor.red + luminosityShift, 0, 255),
|
||||
std::clamp(rgbColor.green + luminosityShift, 0, 255),
|
||||
std::clamp(rgbColor.blue + luminosityShift, 0, 255));
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "../Settings.h"
|
||||
#include "../PlayerCursor.h"
|
||||
|
||||
#include <stack>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
class AppMenu;
|
||||
@@ -19,45 +21,21 @@ class AppMenu {
|
||||
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) :
|
||||
menuStack(menuStack),
|
||||
settings(settings),
|
||||
renderWindow(renderWindow)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void updateMetaBinds() {
|
||||
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Enter)) {
|
||||
enterPressed = true;
|
||||
enterReleased = false;
|
||||
}
|
||||
else {
|
||||
enterReleased = enterPressed;
|
||||
enterPressed = false;
|
||||
}
|
||||
|
||||
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Escape)) {
|
||||
escPressed = true;
|
||||
escReleased = false;
|
||||
}
|
||||
else {
|
||||
escReleased = escPressed;
|
||||
escPressed = false;
|
||||
}
|
||||
}
|
||||
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;
|
||||
virtual void drawFrame() const = 0;
|
||||
|
||||
protected:
|
||||
void updateMetaBinds();
|
||||
|
||||
void placeText(sf::Text& text, const std::optional<PlayerCursor>& playerCursor, const sf::String& string, float xPos, float yPos, const std::optional<sf::Vector2u>& cursorPos) const;
|
||||
|
||||
void placeTitle(sf::Text& text, const std::optional<PlayerCursor>& playerCursor, const sf::String& string, float yPos, const std::optional<sf::Vector2u>& cursorPos) const;
|
||||
|
||||
sf::Color getColorOfBlock(Block block, int luminosityShift) const;
|
||||
};
|
||||
|
||||
|
||||
inline void changeVideoMode(sf::RenderWindow& window, const sf::VideoMode& videoMode) {
|
||||
window.create(videoMode, "jminos", sf::Style::Close | sf::Style::Titlebar);
|
||||
sf::Vector2u desktopSize = sf::VideoMode::getDesktopMode().size;
|
||||
sf::Vector2u windowSize = window.getSize();
|
||||
window.setPosition(sf::Vector2i((desktopSize.x / 2) - (windowSize.x / 2), (desktopSize.y / 2) - (windowSize.y / 2)));
|
||||
}
|
||||
|
||||
64
src/GraphicalUI/AppMenus/GameBoardAppMenu.cpp
Normal file
@@ -0,0 +1,64 @@
|
||||
#include "GameBoardAppMenu.h"
|
||||
|
||||
#include "AppMenu.h"
|
||||
#include "../PlayerCursor.h"
|
||||
|
||||
#include <stack>
|
||||
#include <memory>
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
|
||||
GameBoardAppMenu::GameBoardAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow) :
|
||||
AppMenu(menuStack, settings, renderWindow),
|
||||
playerCursor({1, 1}) {
|
||||
|
||||
}
|
||||
|
||||
void GameBoardAppMenu::computeFrame() {
|
||||
this->updateMetaBinds();
|
||||
this->playerCursor.updatePosition();
|
||||
|
||||
Menu& menu = this->settings->getMenu();
|
||||
|
||||
switch (this->playerCursor.getPosition().y) {
|
||||
case 0 : {
|
||||
if (this->playerCursor.movedLeft()) {
|
||||
menu.setBoardWidth(std::max(1, menu.getBoardWidth() - 1));
|
||||
}
|
||||
if (this->playerCursor.movedRight()) {
|
||||
menu.setBoardWidth(std::min(MAXIMUM_BOARD_WIDTH, menu.getBoardWidth() + 1));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 1 : {
|
||||
if (this->playerCursor.movedLeft()) {
|
||||
menu.setBoardHeight(std::max(1, menu.getBoardHeight() - 1));
|
||||
}
|
||||
if (this->playerCursor.movedRight()) {
|
||||
menu.setBoardHeight(std::min(MAXIMUM_BOARD_HEIGHT, menu.getBoardHeight() + 1));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (this->escReleased) {
|
||||
this->menuStack->pop();
|
||||
}
|
||||
}
|
||||
|
||||
void GameBoardAppMenu::drawFrame() const {
|
||||
this->renderWindow->clear(sf::Color(200, 200, 200));
|
||||
|
||||
const Menu& menu = this->settings->getMenu();
|
||||
|
||||
sf::Text text(this->pressStartFont, "", this->settings->getWindowSizeMultiplier() * 2);
|
||||
text.setFillColor(sf::Color(0, 0, 0));
|
||||
text.setOutlineColor(sf::Color(255, 255, 255));
|
||||
|
||||
this->placeTitle(text, {}, "BOARD SETTINGS", 5.f, {});
|
||||
|
||||
this->placeText(text, this->playerCursor, "< BOARD WIDTH: " + std::to_string(menu.getBoardWidth()) + " >", 5.f, 15.f, sf::Vector2u{0, 0});
|
||||
this->placeText(text, this->playerCursor, "< BOARD HEIGHT: " + std::to_string(menu.getBoardHeight()) + " >", 5.f, 25.f, sf::Vector2u{0, 1});
|
||||
|
||||
this->renderWindow->display();
|
||||
}
|
||||
21
src/GraphicalUI/AppMenus/GameBoardAppMenu.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "AppMenu.h"
|
||||
#include "../PlayerCursor.h"
|
||||
|
||||
#include <stack>
|
||||
#include <memory>
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
|
||||
class GameBoardAppMenu : public AppMenu {
|
||||
private:
|
||||
PlayerCursor playerCursor;
|
||||
|
||||
public:
|
||||
GameBoardAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow);
|
||||
|
||||
void computeFrame() override;
|
||||
|
||||
void drawFrame() const override;
|
||||
};
|
||||
85
src/GraphicalUI/AppMenus/GameDistributionAppMenu.cpp
Normal file
@@ -0,0 +1,85 @@
|
||||
#include "GameDistributionAppMenu.h"
|
||||
|
||||
#include "AppMenu.h"
|
||||
#include "../PlayerCursor.h"
|
||||
|
||||
#include <stack>
|
||||
#include <memory>
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
|
||||
GameDistributionAppMenu::GameDistributionAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow) :
|
||||
AppMenu(menuStack, settings, renderWindow),
|
||||
playerCursor({1}) {
|
||||
|
||||
for (int i = 1; i <= this->settings->getMaximumPiecesSize(); i++) {
|
||||
this->playerCursor.addRow(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
void GameDistributionAppMenu::computeFrame() {
|
||||
this->updateMetaBinds();
|
||||
this->playerCursor.updatePosition();
|
||||
|
||||
PiecesList& piecesList = this->settings->getMenu().getPiecesList();
|
||||
|
||||
if (this->playerCursor.getPosition().y == 0) {
|
||||
if (this->playerCursor.movedLeft()) {
|
||||
piecesList.setDistributionMode(DistributionMode((int) piecesList.getDistributionMode() - 1));
|
||||
}
|
||||
if (this->playerCursor.movedRight()) {
|
||||
piecesList.setDistributionMode(DistributionMode((int) piecesList.getDistributionMode() + 1));
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (piecesList.getDistributionMode() != CUSTOM) {
|
||||
this->playerCursor.goToPosition({0, 0});
|
||||
}
|
||||
else {
|
||||
if (this->playerCursor.movedLeft()) {
|
||||
this->settings->decreaseDistribution(this->playerCursor.getPosition().y);
|
||||
}
|
||||
if (this->playerCursor.movedRight()) {
|
||||
this->settings->increaseDistribution(this->playerCursor.getPosition().y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this->escReleased) {
|
||||
this->settings->confirmDistribution();
|
||||
this->menuStack->pop();
|
||||
}
|
||||
}
|
||||
|
||||
void GameDistributionAppMenu::drawFrame() const {
|
||||
this->renderWindow->clear(sf::Color(200, 200, 200));
|
||||
|
||||
const Menu& menu = this->settings->getMenu();
|
||||
|
||||
sf::Text text(this->pressStartFont, "", this->settings->getWindowSizeMultiplier() * 2);
|
||||
text.setFillColor(sf::Color(0, 0, 0));
|
||||
text.setOutlineColor(sf::Color(255, 255, 255));
|
||||
|
||||
this->placeTitle(text, {}, "DISTRIBUTION SETTINGS", 5.f, {});
|
||||
|
||||
const DistributionMode distributionMode = this->settings->getMenu().readPiecesList().getDistributionMode();
|
||||
const std::vector<int>& distributions = this->settings->getDistributions();
|
||||
|
||||
int firstElem = std::clamp(((int) this->playerCursor.getPosition().y) - 1, 0, this->settings->getMaximumPiecesSize() - 3);
|
||||
if (firstElem == 0) {
|
||||
this->placeText(text, this->playerCursor, "< DISTRIBUTION MODE: " + getPiecesDistributionName(distributionMode) + " >", 5.f, 15.f, sf::Vector2u{0, 0});
|
||||
}
|
||||
else {
|
||||
this->placeText(text, this->playerCursor, "< SIZE " + std::to_string(firstElem) + " PROBABILITY: " + std::to_string(distributions.at(firstElem)) + " >", 5.f, 15.f, sf::Vector2u{(unsigned int) firstElem, 0});
|
||||
}
|
||||
|
||||
if (distributionMode != CUSTOM) {
|
||||
text.setFillColor(sf::Color(100, 100, 100));
|
||||
}
|
||||
|
||||
this->placeText(text, this->playerCursor, "< SIZE " + std::to_string(firstElem + 1) + " PROBABILITY: " + std::to_string(distributions.at(firstElem + 1)) + " >", 5.f, 25.f, sf::Vector2u{0, (unsigned int) firstElem + 1});
|
||||
this->placeText(text, this->playerCursor, "< SIZE " + std::to_string(firstElem + 2) + " PROBABILITY: " + std::to_string(distributions.at(firstElem + 2)) + " >", 5.f, 35.f, sf::Vector2u{0, (unsigned int) firstElem + 2});
|
||||
this->placeText(text, this->playerCursor, "< SIZE " + std::to_string(firstElem + 3) + " PROBABILITY: " + std::to_string(distributions.at(firstElem + 3)) + " >", 5.f, 45.f, sf::Vector2u{0, (unsigned int) firstElem + 3});
|
||||
|
||||
this->renderWindow->display();
|
||||
}
|
||||
21
src/GraphicalUI/AppMenus/GameDistributionAppMenu.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "AppMenu.h"
|
||||
#include "../PlayerCursor.h"
|
||||
|
||||
#include <stack>
|
||||
#include <memory>
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
|
||||
class GameDistributionAppMenu : public AppMenu {
|
||||
private:
|
||||
PlayerCursor playerCursor;
|
||||
|
||||
public:
|
||||
GameDistributionAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow);
|
||||
|
||||
void computeFrame() override;
|
||||
|
||||
void drawFrame() const override;
|
||||
};
|
||||
234
src/GraphicalUI/AppMenus/GamePiecesAppMenu.cpp
Normal file
@@ -0,0 +1,234 @@
|
||||
#include "GamePiecesAppMenu.h"
|
||||
|
||||
#include "AppMenu.h"
|
||||
#include "GameDistributionAppMenu.h"
|
||||
#include "../PlayerCursor.h"
|
||||
|
||||
#include <stack>
|
||||
#include <memory>
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
|
||||
GamePiecesAppMenu::GamePiecesAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow) :
|
||||
AppMenu(menuStack, settings, renderWindow),
|
||||
playerCursor({1, (unsigned int) this->settings->getSelectedPieces().size() + 1u}) {
|
||||
|
||||
for (int i = 1; i <= this->settings->getMaximumPiecesSize(); i++) {
|
||||
this->playerCursor.addRow(i + 1, this->settings->getMenu().readPiecesList().getNumberOfPieces(i) + 4);
|
||||
}
|
||||
|
||||
if (this->settings->getMaximumPiecesSize() < MAXIMUM_PIECES_SIZE) {
|
||||
this->playerCursor.addRow(this->settings->getMaximumPiecesSize() + 2, 1);
|
||||
}
|
||||
}
|
||||
|
||||
void GamePiecesAppMenu::computeFrame() {
|
||||
this->updateMetaBinds();
|
||||
this->playerCursor.updatePosition();
|
||||
|
||||
if (this->playerCursor.movedDown() && this->playerCursor.getPosition().y == 2) {
|
||||
this->playerCursor.goToPosition({0, 2});
|
||||
}
|
||||
|
||||
if (this->enterReleased) {
|
||||
if (this->playerCursor.getPosition().y == 0) {
|
||||
this->menuStack->push(std::make_shared<GameDistributionAppMenu>(this->menuStack, this->settings, this->renderWindow));
|
||||
}
|
||||
|
||||
if (this->playerCursor.getPosition().y == (this->settings->getMaximumPiecesSize() + 2)) {
|
||||
int newMaxSize = this->settings->getMaximumPiecesSize() + 1;
|
||||
this->settings->loadPieces(newMaxSize);
|
||||
|
||||
this->playerCursor.removeRow(newMaxSize + 1);
|
||||
this->playerCursor.addRow(newMaxSize + 1, this->settings->getMenu().readPiecesList().getNumberOfPieces(newMaxSize) + 4);
|
||||
this->playerCursor.goToPosition({0u, newMaxSize + 1u});
|
||||
|
||||
if (newMaxSize < MAXIMUM_PIECES_SIZE) {
|
||||
this->playerCursor.addRow(newMaxSize + 2, 1);
|
||||
}
|
||||
}
|
||||
else if (this->playerCursor.getPosition().y > 1) {
|
||||
if (this->playerCursor.getPosition().x >= 4) {
|
||||
this->settings->selectPieces(createSinglePieceType(this->playerCursor.getPosition().y - 1), this->playerCursor.getPosition().x - 4);
|
||||
}
|
||||
else {
|
||||
switch (this->playerCursor.getPosition().x) {
|
||||
case 0 : {this->settings->selectPieces(ALL_PIECES, this->playerCursor.getPosition().y - 1); break;}
|
||||
case 1 : {this->settings->selectPieces(CONVEX_PIECES, this->playerCursor.getPosition().y - 1); break;}
|
||||
case 2 : {this->settings->selectPieces(HOLELESS_PIECES, this->playerCursor.getPosition().y - 1); break;}
|
||||
case 3 : {this->settings->selectPieces(OTHER_PIECES, this->playerCursor.getPosition().y - 1); break;}
|
||||
}
|
||||
}
|
||||
this->playerCursor.addPosition(0, 1);
|
||||
}
|
||||
}
|
||||
if (this->escReleased) {
|
||||
if (this->playerCursor.getPosition().y == 1) {
|
||||
if (this->playerCursor.getPosition().x > 0) {
|
||||
this->settings->unselectPieces(this->playerCursor.getPosition().x - 1);
|
||||
this->playerCursor.removePosition(this->playerCursor.getPosition().x - 1, 1);
|
||||
}
|
||||
}
|
||||
else {
|
||||
this->settings->confirmSelectedPieces();
|
||||
this->menuStack->pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GamePiecesAppMenu::drawFrame() const {
|
||||
this->renderWindow->clear(sf::Color(200, 200, 200));
|
||||
|
||||
sf::Text text(this->pressStartFont, "", this->settings->getWindowSizeMultiplier() * 2);
|
||||
text.setFillColor(sf::Color(0, 0, 0));
|
||||
text.setOutlineColor(sf::Color(255, 255, 255));
|
||||
|
||||
this->placeTitle(text, {}, "PIECES SELECT", 5.f, {});
|
||||
|
||||
if (this->playerCursor.getPosition().y == 0) {
|
||||
this->placeText(text, this->playerCursor, "PIECES DISTRIBUTION", 5.f, 15.f, sf::Vector2u{0, 0});
|
||||
this->drawSelectedPiecesRow(25.f);
|
||||
this->drawRow(1, 35.f, true);
|
||||
this->drawRow(2, 45.f, true);
|
||||
}
|
||||
else {
|
||||
this->drawSelectedPiecesRow(15.f);
|
||||
|
||||
bool drawFromFirstElem = (this->playerCursor.getPosition().y == 1);
|
||||
bool addExtraLine = (this->settings->getMaximumPiecesSize() < MAXIMUM_PIECES_SIZE);
|
||||
int firstElem = std::clamp(((int) this->playerCursor.getPosition().y) - 2, 1, this->settings->getMaximumPiecesSize() - 2 + (addExtraLine ? 1 : 0));
|
||||
this->drawRow(firstElem, 25.f, drawFromFirstElem);
|
||||
this->drawRow(firstElem + 1, 35.f, drawFromFirstElem);
|
||||
this->drawRow(firstElem + 2, 45.f, drawFromFirstElem);
|
||||
}
|
||||
|
||||
this->renderWindow->display();
|
||||
}
|
||||
|
||||
void GamePiecesAppMenu::drawSelectedPiecesRow(float yPos) const {
|
||||
sf::RectangleShape rect({(float) this->renderWindow->getSize().x, 8.f * this->settings->getWindowSizeMultiplier()});
|
||||
rect.setPosition({0.f, (yPos - 4.f) * this->settings->getWindowSizeMultiplier()});
|
||||
rect.setFillColor({240, 240, 240});
|
||||
this->renderWindow->draw(rect);
|
||||
|
||||
sf::Text text(this->pressStartFont, "", this->settings->getWindowSizeMultiplier());
|
||||
text.setFillColor({0, 0, 0});
|
||||
|
||||
int elem = (this->playerCursor.getPosition().y == 1) ? std::max(((int) this->playerCursor.getPosition().x) - 4, 0) : 0;
|
||||
float xProgress = 1.f;
|
||||
|
||||
bool first = true;
|
||||
while (true) {
|
||||
if ((this->playerCursor.getPosition().y == 1) && (elem == this->playerCursor.getPosition().x)) {
|
||||
this->placeText(text, {}, "|", xProgress, yPos, {});
|
||||
xProgress += (1.f + (text.getGlobalBounds().size.x / this->settings->getWindowSizeMultiplier()));
|
||||
}
|
||||
|
||||
if (elem >= this->settings->getSelectedPieces().size()) return;
|
||||
|
||||
const auto& [pieceType, value] = this->settings->getSelectedPieces().at(elem);
|
||||
|
||||
int pieceSize = getSizeOfPieces(pieceType);
|
||||
if (pieceSize > 0) {
|
||||
if (!(pieceSize > this->settings->getMaximumPiecesSize())) {
|
||||
const Piece& piece = this->settings->getMenu().readPiecesList().lookAtPiece({pieceSize, value});
|
||||
int cellSize = (8 * this->settings->getWindowSizeMultiplier()) / (piece.getLength());
|
||||
sf::FloatRect piecePosition(sf::Vector2f(xProgress, yPos - 4.f) * (float) this->settings->getWindowSizeMultiplier(), sf::Vector2f(8 , 8) * (float) this->settings->getWindowSizeMultiplier());
|
||||
this->drawPiece(piece, cellSize, piecePosition, false);
|
||||
xProgress += (1.f + 8.f);
|
||||
}
|
||||
else {
|
||||
this->placeText(text, {}, "UNLOADED_" + std::to_string(pieceSize), xProgress, yPos, {});
|
||||
xProgress += (1.f + (text.getGlobalBounds().size.x / this->settings->getWindowSizeMultiplier()));
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!(value > this->settings->getMaximumPiecesSize())) {
|
||||
this->placeText(text, {}, ((first) ? "" : " ") + getPiecesTypeName(pieceType) + "_" + std::to_string(value), xProgress, yPos, {});
|
||||
xProgress += (1.f + (text.getGlobalBounds().size.x / this->settings->getWindowSizeMultiplier()));
|
||||
}
|
||||
else {
|
||||
this->placeText(text, {}, "UNLOADED_" + std::to_string(pieceSize), xProgress, yPos, {});
|
||||
xProgress += (1.f + (text.getGlobalBounds().size.x / this->settings->getWindowSizeMultiplier()));
|
||||
}
|
||||
}
|
||||
|
||||
elem++;
|
||||
}
|
||||
}
|
||||
|
||||
void GamePiecesAppMenu::drawRow(int piecesSize, float yPos, bool drawFromFirstElem) const {
|
||||
if (piecesSize > this->settings->getMaximumPiecesSize()) {
|
||||
sf::Text text(this->pressStartFont, "", this->settings->getWindowSizeMultiplier() * 2);
|
||||
text.setOutlineThickness(this->settings->getWindowSizeMultiplier() / 2);
|
||||
if (this->playerCursor.getPosition().y == (piecesSize + 1)) {
|
||||
text.setOutlineColor({255, 255, 255});
|
||||
}
|
||||
else {
|
||||
text.setOutlineColor({0, 0, 0});
|
||||
}
|
||||
|
||||
std::string sizeString = "LOAD SIZE " + std::to_string(piecesSize) + "? ";
|
||||
if (piecesSize <= 10) {
|
||||
text.setFillColor({0, 255, 0});
|
||||
this->placeText(text, {}, sizeString + "(LOW LOAD TIME)", 1.f, yPos, {});
|
||||
}
|
||||
else if (piecesSize <= 13) {
|
||||
text.setFillColor({255, 255, 0});
|
||||
this->placeText(text, {}, sizeString + "(MEDIUM LOAD TIME)", 1.f, yPos, {});
|
||||
}
|
||||
else {
|
||||
text.setFillColor({255, 0, 0});
|
||||
this->placeText(text, {}, sizeString + "(LONG LOAD TIME)", 1.f, yPos, {});
|
||||
}
|
||||
}
|
||||
else {
|
||||
int numberOfPieces = this->settings->getMenu().readPiecesList().getNumberOfPieces(piecesSize);
|
||||
int firstElem = (drawFromFirstElem) ? -4 : std::max(((int) this->playerCursor.getPosition().x) - 7, -4);
|
||||
|
||||
sf::Text text(this->pressStartFont, "", this->settings->getWindowSizeMultiplier());
|
||||
text.setFillColor({0, 0, 0});
|
||||
text.setOutlineColor({255, 255, 255});
|
||||
|
||||
this->placeText(text, {}, "SIZE " + std::to_string(piecesSize), 1.f, yPos, {});
|
||||
|
||||
for (int i = 0; i < 7; i++) {
|
||||
if (i + firstElem >= numberOfPieces) return;
|
||||
|
||||
if ((i + firstElem) < 0) {
|
||||
switch (i + firstElem) {
|
||||
case -4 : {this->placeText(text, this->playerCursor, "ALL", 10.f + (i * 10.f), yPos, sf::Vector2u{0, piecesSize + 1u}); break;}
|
||||
case -3 : {this->placeText(text, this->playerCursor, "CONVEX", 10.f + (i * 10.f), yPos, sf::Vector2u{1, piecesSize + 1u}); break;}
|
||||
case -2 : {this->placeText(text, this->playerCursor, "HOLELESS", 10.f + (i * 10.f), yPos, sf::Vector2u{2, piecesSize + 1u}); break;}
|
||||
case -1 : {this->placeText(text, this->playerCursor, "OTHER", 10.f + (i * 10.f), yPos, sf::Vector2u{3, piecesSize + 1u}); break;}
|
||||
}
|
||||
}
|
||||
else {
|
||||
const Piece& piece = this->settings->getMenu().readPiecesList().lookAtPiece({piecesSize, firstElem + i});
|
||||
int cellSize = (8 * this->settings->getWindowSizeMultiplier()) / (piece.getLength());
|
||||
sf::FloatRect piecePosition(sf::Vector2f(10.f + (i * 10.f), yPos - 4.f) * (float) this->settings->getWindowSizeMultiplier(), sf::Vector2f(8 , 8) * (float) this->settings->getWindowSizeMultiplier());
|
||||
this->drawPiece(piece, cellSize, piecePosition, this->playerCursor.getPosition() == sf::Vector2u{i + firstElem + 4u, piecesSize + 1u});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GamePiecesAppMenu::drawPiece(const Piece& piece, int cellSize, const sf::FloatRect& piecePosition, bool selected) const {
|
||||
sf::RectangleShape rect(piecePosition.size);
|
||||
rect.setPosition(piecePosition.position);
|
||||
rect.setFillColor({180, 180, 180});
|
||||
if (selected) {
|
||||
rect.setOutlineColor({0, 0, 0});
|
||||
rect.setOutlineThickness(this->settings->getWindowSizeMultiplier() / 2);
|
||||
}
|
||||
this->renderWindow->draw(rect);
|
||||
|
||||
sf::RectangleShape cell(sf::Vector2f(cellSize, cellSize));
|
||||
cell.setFillColor(this->getColorOfBlock(piece.getBlockType(), 0));
|
||||
|
||||
for (const Position& cellPosition : piece.getPositions()) {
|
||||
cell.setPosition(sf::Vector2f(piecePosition.position.x + (cellPosition.x * cellSize),
|
||||
piecePosition.position.y + ((piece.getLength() - cellPosition.y - 1) * cellSize) ));
|
||||
this->renderWindow->draw(cell);
|
||||
}
|
||||
}
|
||||
29
src/GraphicalUI/AppMenus/GamePiecesAppMenu.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include "AppMenu.h"
|
||||
#include "../PlayerCursor.h"
|
||||
|
||||
#include <stack>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
|
||||
class GamePiecesAppMenu : public AppMenu {
|
||||
private:
|
||||
PlayerCursor playerCursor;
|
||||
|
||||
public:
|
||||
GamePiecesAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow);
|
||||
|
||||
void computeFrame() override;
|
||||
|
||||
void drawFrame() const override;
|
||||
|
||||
private:
|
||||
void drawSelectedPiecesRow(float yPos) const;
|
||||
|
||||
void drawRow(int piecesSize, float yPos, bool drawFromFirstElem) const;
|
||||
|
||||
void drawPiece(const Piece& piece, int cellSize, const sf::FloatRect& pos, bool selected) const;
|
||||
};
|
||||
@@ -4,21 +4,61 @@
|
||||
|
||||
#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()))
|
||||
{
|
||||
game(this->settings->getMenu().startGame(this->settings->getGamemode())) {
|
||||
|
||||
this->game.start();
|
||||
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();
|
||||
}
|
||||
@@ -33,11 +73,27 @@ void GamePlayingAppMenu::computeFrame() {
|
||||
}
|
||||
|
||||
if (actions.contains(RETRY)) {
|
||||
this->game.reset();
|
||||
this->game.start();
|
||||
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->paused = (!this->paused);
|
||||
this->pausePressed = true;
|
||||
}
|
||||
else {
|
||||
if (this->pausePressed) {
|
||||
this->paused = (!this->paused);
|
||||
}
|
||||
this->pausePressed = false;
|
||||
}
|
||||
|
||||
if (!paused) {
|
||||
@@ -47,57 +103,191 @@ void GamePlayingAppMenu::computeFrame() {
|
||||
}
|
||||
|
||||
void GamePlayingAppMenu::drawFrame() const {
|
||||
this->renderWindow->clear(sf::Color::Black);
|
||||
this->renderWindow->clear(sf::Color(200, 200, 200));
|
||||
|
||||
int sizeMultiplier = this->settings->getWindowSizeMultiplier();
|
||||
sf::Vector2f cellSize((float) sizeMultiplier, (float) sizeMultiplier);
|
||||
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());
|
||||
|
||||
for (int y = this->game.getBoard().getBaseHeight() + 10; y >= 0; y--) {
|
||||
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++) {
|
||||
bool isActivePieceHere = (this->game.getActivePiece() != nullptr) && (this->game.getActivePiece()->getPositions().contains(Position{x, y} - this->game.getActivePiecePosition()));
|
||||
bool isGhostPieceHere = (this->game.getActivePiece() != nullptr) && (this->game.getActivePiece()->getPositions().contains(Position{x, y} - this->game.ghostPiecePosition()));
|
||||
Block block = (isActivePieceHere || isGhostPieceHere) ? this->game.getActivePiece()->getBlockType() : this->game.getBoard().getBlock(Position{x, y});
|
||||
Block block = this->game.getBoard().getBlock(Position{x, y});
|
||||
if (isBoardInvisible) block = NOTHING;
|
||||
|
||||
sf::RectangleShape cell(cellSize);
|
||||
cell.setFillColor(sf::Color(BLOCKS_COLOR[block].red, BLOCKS_COLOR[block].green, BLOCKS_COLOR[block].blue, (isGhostPieceHere && !isActivePieceHere) ? 150 : 255));
|
||||
cell.setPosition(sf::Vector2f(x * sizeMultiplier, (this->game.getBoard().getBaseHeight() + 10 - y) * sizeMultiplier));
|
||||
cell.setFillColor((areBlockBones && block != NOTHING)
|
||||
? bonesBlockColor
|
||||
: this->getColorOfBlock(block, (block == NOTHING) ? 0 : -30));
|
||||
cell.setPosition(this->getBoardBlockPosition(x, y));
|
||||
this->renderWindow->draw(cell);
|
||||
}
|
||||
}
|
||||
|
||||
if (this->game.getNextPieces().size() > 0) {
|
||||
for (int y = 10; y >= 0; y--) {
|
||||
for (int x = 0; x <= 10; x++) {
|
||||
Block block = this->game.getNextPieces().at(0).getBlockType();
|
||||
sf::RectangleShape cell(sf::Vector2f(20.f, 20.f));
|
||||
cell.setPosition(sf::Vector2f((x + 2 + this->game.getBoard().getWidth())*20, (this->game.getBoard().getBaseHeight() - y)*20));
|
||||
if (this->game.getNextPieces().at(0).getPositions().contains(Position({x, y}))) {
|
||||
cell.setFillColor(sf::Color(BLOCKS_COLOR[block].red, BLOCKS_COLOR[block].green, BLOCKS_COLOR[block].blue));
|
||||
if (drawActivePiece) {
|
||||
// ghost piece
|
||||
sf::Color ghostColor = areBlockBones
|
||||
? bonesBlockGhostColor
|
||||
: this->getColorOfBlock(this->game.getActivePiece()->getBlockType(), 100);
|
||||
|
||||
for (const Position& position : this->game.getActivePiece()->getPositions()) {
|
||||
Position cellPosition = (this->game.getGhostPiecePosition() + position);
|
||||
|
||||
sf::RectangleShape cell(cellSize);
|
||||
cell.setFillColor(ghostColor);
|
||||
cell.setPosition(this->getBoardBlockPosition(cellPosition.x, cellPosition.y));
|
||||
this->renderWindow->draw(cell);
|
||||
}
|
||||
|
||||
// active piece outline
|
||||
sf::Color pieceOultlineColor = sf::Color(255, 255 - (255 * this->game.getForcedLockDelayProgression()), 255 - (255 * this->game.getForcedLockDelayProgression()));
|
||||
|
||||
for (const Position& position : this->game.getActivePiece()->getPositions()) {
|
||||
Position cellPosition = (this->game.getActivePiecePosition() + position);
|
||||
|
||||
sf::RectangleShape cell(cellSize);
|
||||
cell.setOutlineThickness(cellOutlineThickness);
|
||||
cell.setOutlineColor(pieceOultlineColor);
|
||||
cell.setPosition(this->getBoardBlockPosition(cellPosition.x, cellPosition.y));
|
||||
this->renderWindow->draw(cell);
|
||||
}
|
||||
}
|
||||
|
||||
// top out line
|
||||
sf::RectangleShape topOutLine(sf::Vector2f(this->cellSizeZoom * this->game.getBoard().getWidth(), std::roundf(this->cellSizeZoom / 4)));
|
||||
topOutLine.setPosition(this->getBoardBlockPosition(0, this->game.getBoard().getBaseHeight() - 1));
|
||||
topOutLine.setFillColor(sf::Color(255, 0, 0));
|
||||
this->renderWindow->draw(topOutLine);
|
||||
|
||||
if (drawActivePiece) {
|
||||
// active piece
|
||||
sf::Color pieceColor = areBlockBones
|
||||
? bonesBlockColor
|
||||
: this->getColorOfBlock(this->game.getActivePiece()->getBlockType(), -200 * (this->game.getLockDelayProgression()));
|
||||
|
||||
for (const Position& position : this->game.getActivePiece()->getPositions()) {
|
||||
Position cellPosition = (this->game.getActivePiecePosition() + position);
|
||||
|
||||
sf::RectangleShape cell(cellSize);
|
||||
cell.setFillColor(pieceColor);
|
||||
cell.setPosition(this->getBoardBlockPosition(cellPosition.x, cellPosition.y));
|
||||
this->renderWindow->draw(cell);
|
||||
}
|
||||
}
|
||||
|
||||
// next queue
|
||||
int upShift = 0;
|
||||
for (int i = 0; i < std::min((int) this->game.getNextPieces().size(), 5); i++) {
|
||||
sf::FloatRect nextBox = this->nextQueuePosition[i];
|
||||
nextBox.position.y -= upShift;
|
||||
|
||||
sf::Vector2f nextCellSize(this->nextCellSizeZoom, this->nextCellSizeZoom);
|
||||
sf::Color pieceColor = areBlockBones
|
||||
? bonesBlockColor
|
||||
: this->getColorOfBlock(this->game.getNextPieces().at(i).getBlockType(), 0);
|
||||
sf::Color boxColor = sf::Color(180, 180, 180);
|
||||
|
||||
int lowestRank = 0;
|
||||
for (int y = 0; y < this->game.getNextPieces().at(i).getLength(); y++) {
|
||||
for (int x = 0; x < this->game.getNextPieces().at(i).getLength(); x++) {
|
||||
sf::RectangleShape cell(nextCellSize);
|
||||
if (this->game.getNextPieces().at(i).getPositions().contains(Position{x, y})) {
|
||||
cell.setFillColor(pieceColor);
|
||||
lowestRank = y;
|
||||
}
|
||||
else {
|
||||
cell.setFillColor(sf::Color(0, 0, 0));
|
||||
cell.setFillColor(boxColor);
|
||||
}
|
||||
cell.setPosition(sf::Vector2f(nextBox.position.x + (x * this->nextCellSizeZoom),
|
||||
nextBox.position.y + ((this->game.getNextPieces().at(i).getLength() - y - 1) * this->nextCellSizeZoom)));
|
||||
this->renderWindow->draw(cell);
|
||||
}
|
||||
}
|
||||
|
||||
upShift += nextBox.size.y - (this->game.getNextPieces().at(i).getLength() * this->nextCellSizeZoom);
|
||||
}
|
||||
|
||||
// hold box
|
||||
if (this->game.getHeldPiece() != nullptr) {
|
||||
sf::Vector2f holdCellSize(this->holdCellSizeZoom, this->holdCellSizeZoom);
|
||||
sf::Color color = areBlockBones
|
||||
? bonesBlockColor
|
||||
: this->getColorOfBlock(this->game.getHeldPiece()->getBlockType(), 0);
|
||||
sf::Color boxColor = sf::Color(180, 180, 180);
|
||||
|
||||
for (int y = 0; y < this->game.getHeldPiece()->getLength(); y++) {
|
||||
for (int x = 0; x < this->game.getHeldPiece()->getLength(); x++) {
|
||||
sf::RectangleShape cell(holdCellSize);
|
||||
if (this->game.getHeldPiece()->getPositions().contains(Position{x, y})) {
|
||||
cell.setFillColor(color);
|
||||
}
|
||||
else {
|
||||
cell.setFillColor(boxColor);
|
||||
}
|
||||
cell.setPosition(sf::Vector2f(this->holdBoxPosition.position.x + (x * this->nextCellSizeZoom),
|
||||
this->holdBoxPosition.position.y + ((this->game.getHeldPiece()->getLength() - y - 1) * this->holdCellSizeZoom)));
|
||||
this->renderWindow->draw(cell);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this->game.getHeldPiece() != nullptr) {
|
||||
for (int y = 10; y >= 0; y--) {
|
||||
for (int x = 0; x <= 10; x++) {
|
||||
Block block = this->game.getHeldPiece()->getBlockType();
|
||||
sf::RectangleShape cell(sf::Vector2f(20.f, 20.f));
|
||||
cell.setPosition(sf::Vector2f((x + 12 + this->game.getBoard().getWidth())*20, (this->game.getBoard().getBaseHeight() - y)*20));
|
||||
if (this->game.getHeldPiece()->getPositions().contains(Position({x, y}))) {
|
||||
cell.setFillColor(sf::Color(BLOCKS_COLOR[block].red, BLOCKS_COLOR[block].green, BLOCKS_COLOR[block].blue));
|
||||
}
|
||||
else {
|
||||
cell.setFillColor(sf::Color(0, 0, 0));
|
||||
}
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -10,12 +10,23 @@
|
||||
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();
|
||||
void computeFrame() override;
|
||||
|
||||
void drawFrame() const;
|
||||
void drawFrame() const override;
|
||||
|
||||
sf::Vector2f getBoardBlockPosition(int x, int y) const;
|
||||
};
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
#include "GameSettingsAppMenu.h"
|
||||
|
||||
#include "AppMenu.h"
|
||||
#include "GamePiecesAppMenu.h"
|
||||
#include "GameBoardAppMenu.h"
|
||||
#include "GamePlayingAppMenu.h"
|
||||
#include "PlayerCursor.h"
|
||||
#include "../PlayerCursor.h"
|
||||
|
||||
#include <stack>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
|
||||
GameSettingsAppMenu::GameSettingsAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow) :
|
||||
AppMenu(menuStack, settings, renderWindow),
|
||||
playerCursor(std::vector<unsigned int>({2, 3, 1})) {
|
||||
playerCursor({2, 3, 3}) {
|
||||
|
||||
}
|
||||
|
||||
@@ -32,8 +33,8 @@ void GameSettingsAppMenu::computeFrame() {
|
||||
case 2 : {
|
||||
switch (this->playerCursor.getPosition().x) {
|
||||
case 0 : {this->settings->setGamemode(MASTER); break;}
|
||||
case 1 : break; //TODO
|
||||
case 2 : break; //TODO
|
||||
case 1 : {this->settings->setGamemode(INVISIBLE); break;}
|
||||
case 2 : {this->settings->setGamemode(ZEN); break;}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -41,7 +42,12 @@ void GameSettingsAppMenu::computeFrame() {
|
||||
|
||||
if (this->enterReleased) {
|
||||
if (this->playerCursor.getPosition().y == 0) {
|
||||
//TODO
|
||||
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));
|
||||
@@ -55,37 +61,24 @@ void GameSettingsAppMenu::computeFrame() {
|
||||
void GameSettingsAppMenu::drawFrame() const {
|
||||
this->renderWindow->clear(sf::Color(200, 200, 200));
|
||||
|
||||
|
||||
sf::Font font("data/fonts/pressstart/prstartk.ttf");
|
||||
sf::Text text(font, "", this->settings->getWindowSizeMultiplier() * 2);
|
||||
sf::Text text(this->pressStartFont, "", this->settings->getWindowSizeMultiplier() * 2);
|
||||
text.setFillColor(sf::Color(0, 0, 0));
|
||||
text.setOutlineColor(sf::Color(255, 255, 255));
|
||||
|
||||
text.setString("GAME SETTINGS");
|
||||
text.setOrigin(text.getLocalBounds().getCenter());
|
||||
text.setPosition(sf::Vector2f({(float) this->settings->getWindowSizeMultiplier() * 40, (float) this->settings->getWindowSizeMultiplier() * 5}));
|
||||
this->renderWindow->draw(text);
|
||||
this->placeTitle(text, {}, "GAME SETTINGS", 5.f, {});
|
||||
|
||||
this->placeText(text, "PIECES SELECT", 5.f, 15.f, 0, 0);
|
||||
this->placeText(text, "BOARD SELECT", 40.f, 15.f, 1, 0);
|
||||
this->placeText(text, this->playerCursor, "PIECES SELECT", 5.f, 15.f, sf::Vector2u{0, 0});
|
||||
this->placeText(text, this->playerCursor, "BOARD SELECT", 40.f, 15.f, sf::Vector2u{1, 0});
|
||||
|
||||
this->placeText(text, "SPRINT", 5.f, 25.f, 0, 1);
|
||||
this->placeText(text, "MARATHON", 25.f, 25.f, 1, 1);
|
||||
this->placeText(text, "ULTRA", 50.f, 25.f, 2, 1);
|
||||
this->placeText(text, "MASTER", 5.f, 35.f, 0, 2);
|
||||
this->placeText(text, "TODO", 25.f, 35.f, 1, 2);
|
||||
this->placeText(text, "TOOD", 50.f, 35.f, 2, 2);
|
||||
text.setOutlineThickness(0);
|
||||
this->placeTitle(text, {}, "GAMEMODE SELECT", 25.f, {});
|
||||
|
||||
this->placeText(text, this->playerCursor, "SPRINT", 5.f, 35.f, sf::Vector2u{0, 1});
|
||||
this->placeText(text, this->playerCursor, "MARATHON", 25.f, 35.f, sf::Vector2u{1, 1});
|
||||
this->placeText(text, this->playerCursor, "ULTRA", 50.f, 35.f, sf::Vector2u{2, 1});
|
||||
this->placeText(text, this->playerCursor, "MASTER", 5.f, 45.f, sf::Vector2u{0, 2});
|
||||
this->placeText(text, this->playerCursor, "INVISIBLE", 25.f, 45.f, sf::Vector2u{1, 2});
|
||||
this->placeText(text, this->playerCursor, "ZEN", 50.f, 45.f, sf::Vector2u{2, 2});
|
||||
|
||||
this->renderWindow->display();
|
||||
}
|
||||
|
||||
void GameSettingsAppMenu::placeText(sf::Text& text, const sf::String& string, float xPos, float yPos, unsigned int xCursorOutline, unsigned int yCursorOutline) const {
|
||||
float sizeMultiplier = this->settings->getWindowSizeMultiplier();
|
||||
|
||||
text.setString(string);
|
||||
text.setOutlineThickness((this->playerCursor.getPosition().x == xCursorOutline
|
||||
&& this->playerCursor.getPosition().y == yCursorOutline) ? (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);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "AppMenu.h"
|
||||
#include "PlayerCursor.h"
|
||||
#include "../PlayerCursor.h"
|
||||
|
||||
#include <stack>
|
||||
#include <memory>
|
||||
@@ -15,9 +15,7 @@ class GameSettingsAppMenu : public AppMenu {
|
||||
public:
|
||||
GameSettingsAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow);
|
||||
|
||||
void computeFrame();
|
||||
void computeFrame() override;
|
||||
|
||||
void drawFrame() const;
|
||||
|
||||
void placeText(sf::Text& text, const sf::String& string, float xPos, float yPos, unsigned int xCursorOutline, unsigned int yCursorOutline) const ;
|
||||
void drawFrame() const override;
|
||||
};
|
||||
|
||||
97
src/GraphicalUI/AppMenus/InfoAppMenu.cpp
Normal file
@@ -0,0 +1,97 @@
|
||||
#include "InfoAppMenu.h"
|
||||
|
||||
#include "AppMenu.h"
|
||||
#include "../PlayerCursor.h"
|
||||
|
||||
#include <stack>
|
||||
#include <memory>
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
|
||||
InfoAppMenu::InfoAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow) :
|
||||
AppMenu(menuStack, settings, renderWindow),
|
||||
playerCursor({INFO_SECTIONS_COUNT}),
|
||||
sectionsName({
|
||||
"< ABOUT >",
|
||||
"< PIECES TYPES >",
|
||||
"< 0 DEGREES ROTATIONS >",
|
||||
"< ROTATION SYSTEM >",
|
||||
"< SCORING >"
|
||||
}),
|
||||
sectionsContent({
|
||||
"This game is written in C++,\n"
|
||||
"using SFML 3 for the GUI.\n"
|
||||
"It has been inspired by other\n"
|
||||
"stacker games, such as\n"
|
||||
"Techmino, jstris, tetr.io, etc.\n"
|
||||
"This project isn't affiliated\n"
|
||||
"to them in any ways.\n"
|
||||
"Current version: beta.",
|
||||
|
||||
"There is multiple pieces type in\n"
|
||||
"the selection screen. Use theses\n"
|
||||
"categories for size of at least 7.\n"
|
||||
"Convex, Holeless and Others are\n"
|
||||
"all mutually exclusive.\n"
|
||||
"Others have holes inside them, and\n"
|
||||
"Convex are presumably easier to\n"
|
||||
"play with than Holeless.",
|
||||
|
||||
"This games introduces 0 degrees\n"
|
||||
"rotations, which work by simpling\n"
|
||||
"moving the piece down and kicking\n"
|
||||
"it as is, allowing for new kinds\n"
|
||||
"of kicks.\n"
|
||||
"As a leniency mechanic, when a\n"
|
||||
"piece spawns it will automatically\n"
|
||||
"try a 0 degrees rotations if it\n"
|
||||
"spawned inside a wall.",
|
||||
|
||||
"This game uses its own\n"
|
||||
"Rotation Sytem, called AutoRS.\n"
|
||||
"The rotation center is always the\n"
|
||||
"center of the piece by default.\n"
|
||||
"When kicking the piece, it will\n"
|
||||
"compute and try (most) position that\n"
|
||||
"touches the original piece,\n"
|
||||
"prioritizing sides over depth and\n"
|
||||
"firstly going down before going up.",
|
||||
|
||||
"The score gained from a line clear\n"
|
||||
"doubles when clearing one more line.\n"
|
||||
"Clearing with a spin scores as much\n"
|
||||
"as clearing 2x more lines normally.\n"
|
||||
"B2B is granted by clearing at least\n"
|
||||
"4 lines or doing a spin or mini-spin,\n"
|
||||
"and doubles the score gained.\n"
|
||||
"A spin is detected when the piece is\n"
|
||||
"locked in place, a mini-spin simply\n"
|
||||
"when the last move was a kick."
|
||||
}) {
|
||||
|
||||
}
|
||||
|
||||
void InfoAppMenu::computeFrame() {
|
||||
this->updateMetaBinds();
|
||||
this->playerCursor.updatePosition();
|
||||
|
||||
if (this->escReleased) {
|
||||
this->menuStack->pop();
|
||||
}
|
||||
}
|
||||
|
||||
void InfoAppMenu::drawFrame() const {
|
||||
this->renderWindow->clear(sf::Color(200, 200, 200));
|
||||
|
||||
sf::Text text(this->pressStartFont, "", this->settings->getWindowSizeMultiplier() * 2);
|
||||
text.setFillColor(sf::Color(0, 0, 0));
|
||||
text.setOutlineColor(sf::Color(255, 255, 255));
|
||||
|
||||
this->placeTitle(text, this->playerCursor, this->sectionsName[this->playerCursor.getPosition().x], 10.f, this->playerCursor.getPosition());
|
||||
|
||||
text.setLineSpacing((float) this->settings->getWindowSizeMultiplier() / 8);
|
||||
text.setOutlineThickness(0);
|
||||
this->placeText(text, {}, this->sectionsContent[this->playerCursor.getPosition().x], 5.f, 30.f, {});
|
||||
|
||||
this->renderWindow->display();
|
||||
}
|
||||
24
src/GraphicalUI/AppMenus/InfoAppMenu.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "AppMenu.h"
|
||||
#include "../PlayerCursor.h"
|
||||
|
||||
#include <stack>
|
||||
#include <memory>
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
static const int INFO_SECTIONS_COUNT = 5;
|
||||
|
||||
class InfoAppMenu : public AppMenu {
|
||||
private:
|
||||
PlayerCursor playerCursor;
|
||||
std::array<std::string, INFO_SECTIONS_COUNT> sectionsName;
|
||||
std::array<std::string, INFO_SECTIONS_COUNT> sectionsContent;
|
||||
|
||||
public:
|
||||
InfoAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow);
|
||||
|
||||
void computeFrame() override;
|
||||
|
||||
void drawFrame() const override;
|
||||
};
|
||||
@@ -2,17 +2,18 @@
|
||||
|
||||
#include "AppMenu.h"
|
||||
#include "GameSettingsAppMenu.h"
|
||||
#include "PlayerCursor.h"
|
||||
#include "SettingsMainAppMenu.h"
|
||||
#include "InfoAppMenu.h"
|
||||
#include "../PlayerCursor.h"
|
||||
|
||||
#include <stack>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
|
||||
MainAppMenu::MainAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow) :
|
||||
AppMenu(menuStack, settings, renderWindow),
|
||||
playerCursor(std::vector<unsigned int>({1, 1, 1})) {
|
||||
playerCursor({1, 1, 1}) {
|
||||
|
||||
}
|
||||
|
||||
@@ -25,10 +26,10 @@ void MainAppMenu::computeFrame() {
|
||||
this->menuStack->push(std::make_shared<GameSettingsAppMenu>(this->menuStack, this->settings, this->renderWindow));
|
||||
}
|
||||
if (this->playerCursor.getPosition().y == 1) {
|
||||
//TODO
|
||||
this->menuStack->push(std::make_shared<SettingsMainAppMenu>(this->menuStack, this->settings, this->renderWindow));
|
||||
}
|
||||
if (this->playerCursor.getPosition().y == 2) {
|
||||
//TODO
|
||||
this->menuStack->push(std::make_shared<InfoAppMenu>(this->menuStack, this->settings, this->renderWindow));
|
||||
}
|
||||
}
|
||||
if (this->escReleased) {
|
||||
@@ -39,35 +40,15 @@ void MainAppMenu::computeFrame() {
|
||||
void MainAppMenu::drawFrame() const {
|
||||
this->renderWindow->clear(sf::Color(200, 200, 200));
|
||||
|
||||
float sizeMultiplier = this->settings->getWindowSizeMultiplier();
|
||||
|
||||
sf::Font font("data/fonts/pressstart/prstartk.ttf");
|
||||
sf::Text text(font, "", this->settings->getWindowSizeMultiplier() * 2);
|
||||
sf::Text text(this->pressStartFont, "", this->settings->getWindowSizeMultiplier() * 2);
|
||||
text.setFillColor(sf::Color(0, 0, 0));
|
||||
text.setOutlineColor(sf::Color(255, 255, 255));
|
||||
|
||||
text.setString("JMINOS");
|
||||
text.setOrigin(text.getLocalBounds().getCenter());
|
||||
text.setPosition(sf::Vector2f({sizeMultiplier * 40, sizeMultiplier * 10}));
|
||||
this->renderWindow->draw(text);
|
||||
this->placeTitle(text, {}, "JMINOS", 10.f, {});
|
||||
|
||||
text.setString("PLAY");
|
||||
text.setOutlineThickness((this->playerCursor.getPosition().y == 0) ? (sizeMultiplier / 2) : 0);
|
||||
text.setOrigin(text.getLocalBounds().getCenter());
|
||||
text.setPosition(sf::Vector2f({sizeMultiplier * 40, sizeMultiplier * 20}));
|
||||
this->renderWindow->draw(text);
|
||||
|
||||
text.setString("SETTINGS");
|
||||
text.setOutlineThickness((this->playerCursor.getPosition().y == 1) ? (sizeMultiplier / 2) : 0);
|
||||
text.setOrigin(text.getLocalBounds().getCenter());
|
||||
text.setPosition(sf::Vector2f({sizeMultiplier * 40, sizeMultiplier * 30}));
|
||||
this->renderWindow->draw(text);
|
||||
|
||||
text.setString("INFO");
|
||||
text.setOutlineThickness((this->playerCursor.getPosition().y == 2) ? (sizeMultiplier / 2) : 0);
|
||||
text.setOrigin(text.getLocalBounds().getCenter());
|
||||
text.setPosition(sf::Vector2f({sizeMultiplier * 40, sizeMultiplier * 40}));
|
||||
this->renderWindow->draw(text);
|
||||
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,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "AppMenu.h"
|
||||
#include "PlayerCursor.h"
|
||||
#include "../PlayerCursor.h"
|
||||
|
||||
#include <stack>
|
||||
#include <memory>
|
||||
@@ -15,7 +15,7 @@ class MainAppMenu : public AppMenu {
|
||||
public:
|
||||
MainAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow);
|
||||
|
||||
void computeFrame();
|
||||
void computeFrame() override;
|
||||
|
||||
void drawFrame() const;
|
||||
void drawFrame() const override;
|
||||
};
|
||||
|
||||
76
src/GraphicalUI/AppMenus/SettingsControlsAppMenu.cpp
Normal file
@@ -0,0 +1,76 @@
|
||||
#include "SettingsControlsAppMenu.h"
|
||||
|
||||
#include "AppMenu.h"
|
||||
#include "../PlayerCursor.h"
|
||||
|
||||
#include <stack>
|
||||
#include <memory>
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
|
||||
SettingsControlsAppMenu::SettingsControlsAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow) :
|
||||
AppMenu(menuStack, settings, renderWindow),
|
||||
playerCursor({1, 1, 1}) {
|
||||
|
||||
}
|
||||
|
||||
void SettingsControlsAppMenu::computeFrame() {
|
||||
this->updateMetaBinds();
|
||||
this->playerCursor.updatePosition();
|
||||
|
||||
Player& playerControls = this->settings->getMenu().getPlayerControls();
|
||||
|
||||
switch (this->playerCursor.getPosition().y) {
|
||||
case 0 : {
|
||||
if (this->playerCursor.movedLeft()) {
|
||||
playerControls.setDAS(playerControls.getDAS() - 1);
|
||||
}
|
||||
if (this->playerCursor.movedRight()) {
|
||||
playerControls.setDAS(playerControls.getDAS() + 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 1 : {
|
||||
if (this->playerCursor.movedLeft()) {
|
||||
playerControls.setARR(playerControls.getARR() - 1);
|
||||
}
|
||||
if (this->playerCursor.movedRight()) {
|
||||
playerControls.setARR(playerControls.getARR() + 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 2 : {
|
||||
if (this->playerCursor.movedLeft()) {
|
||||
playerControls.setSDR(playerControls.getSDR() - 1);
|
||||
}
|
||||
if (this->playerCursor.movedRight()) {
|
||||
playerControls.setSDR(playerControls.getSDR() + 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (this->escReleased) {
|
||||
this->menuStack->pop();
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsControlsAppMenu::drawFrame() const {
|
||||
this->renderWindow->clear(sf::Color(200, 200, 200));
|
||||
|
||||
const Player& playerControls = this->settings->getMenu().readPlayerControls();
|
||||
|
||||
sf::Text text(this->pressStartFont, "", this->settings->getWindowSizeMultiplier() * 2);
|
||||
text.setFillColor(sf::Color(0, 0, 0));
|
||||
text.setOutlineColor(sf::Color(255, 255, 255));
|
||||
|
||||
this->placeTitle(text, {}, "CONTROLS SETTINGS", 5.f, {});
|
||||
|
||||
sf::Vector2u windowSize = this->renderWindow->getSize();
|
||||
|
||||
this->placeText(text, this->playerCursor, "< DAS: " + std::to_string(playerControls.getDAS()) + " >", 5.f, 15.f, sf::Vector2u{0, 0});
|
||||
this->placeText(text, this->playerCursor, "< ARR: " + std::to_string(playerControls.getARR()) + " >", 5.f, 25.f, sf::Vector2u{0, 1});
|
||||
this->placeText(text, this->playerCursor, "< SDR: " + std::to_string(playerControls.getSDR()) + " >", 5.f, 35.f, sf::Vector2u{0, 2});
|
||||
|
||||
this->renderWindow->display();
|
||||
}
|
||||
21
src/GraphicalUI/AppMenus/SettingsControlsAppMenu.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "AppMenu.h"
|
||||
#include "../PlayerCursor.h"
|
||||
|
||||
#include <stack>
|
||||
#include <memory>
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
|
||||
class SettingsControlsAppMenu : public AppMenu {
|
||||
private:
|
||||
PlayerCursor playerCursor;
|
||||
|
||||
public:
|
||||
SettingsControlsAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow);
|
||||
|
||||
void computeFrame() override;
|
||||
|
||||
void drawFrame() const override;
|
||||
};
|
||||
129
src/GraphicalUI/AppMenus/SettingsKeybindsAppMenu.cpp
Normal file
@@ -0,0 +1,129 @@
|
||||
#include "SettingsKeybindsAppMenu.h"
|
||||
|
||||
#include "AppMenu.h"
|
||||
#include "../PlayerCursor.h"
|
||||
#include "../../Utils/AssetManager.h"
|
||||
|
||||
#include <stack>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <regex>
|
||||
#include <filesystem>
|
||||
#include <algorithm>
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
|
||||
SettingsKeybindsAppMenu::SettingsKeybindsAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow) :
|
||||
AppMenu(menuStack, settings, renderWindow),
|
||||
playerCursor({1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}) {
|
||||
|
||||
this->selectedAnAction = false;
|
||||
|
||||
for (Action action : ACTION_LIST_IN_ORDER) {
|
||||
std::string textureName = ACTION_NAMES[action];
|
||||
textureName = std::regex_replace(textureName, std::regex(" "), "");
|
||||
|
||||
const Asset& textureData = getResource("data/images/keybinds/" + textureName + ".png");
|
||||
|
||||
this->iconTextures[action] = sf::Texture(textureData.data, textureData.size, false, {{0, 0}, {16, 16}});
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsKeybindsAppMenu::computeFrame() {
|
||||
this->updateMetaBinds();
|
||||
|
||||
if (!this->selectedAnAction) {
|
||||
this->playerCursor.updatePosition();
|
||||
|
||||
if (this->playerCursor.movedLeft()) {
|
||||
this->settings->selectPreviousKeybinds();
|
||||
}
|
||||
if (this->playerCursor.movedRight()) {
|
||||
this->settings->selectNextKeybinds();
|
||||
}
|
||||
}
|
||||
else {
|
||||
bool addedKeybind = false;
|
||||
for (const auto& [key, string] : KEYS_TO_STRING) {
|
||||
if (sf::Keyboard::isKeyPressed(key) && (key != sfKey::Enter) && (key != sfKey::Escape)) {
|
||||
this->settings->getKeybinds().addKey(this->actionSelected, key);
|
||||
addedKeybind = true;
|
||||
}
|
||||
|
||||
if (addedKeybind) {
|
||||
this->selectedAnAction = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this->enterReleased && this->settings->getKeybinds().isModifiable()) {
|
||||
this->selectedAnAction = !selectedAnAction;
|
||||
this->actionSelected = ACTION_LIST_IN_ORDER[this->playerCursor.getPosition().y - 1];
|
||||
}
|
||||
if (this->escReleased) {
|
||||
if (this->selectedAnAction) {
|
||||
this->settings->getKeybinds().clearKeys(this->actionSelected);
|
||||
this->selectedAnAction = false;
|
||||
}
|
||||
else {
|
||||
this->menuStack->pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsKeybindsAppMenu::drawFrame() const {
|
||||
this->renderWindow->clear(sf::Color(200, 200, 200));
|
||||
|
||||
sf::Text text(this->pressStartFont, "", this->settings->getWindowSizeMultiplier() * 2);
|
||||
text.setFillColor(sf::Color(0, 0, 0));
|
||||
text.setOutlineColor(sf::Color(255, 255, 255));
|
||||
|
||||
this->placeTitle(text, {}, "KEYBINDS SETTINGS", 5.f, {});
|
||||
|
||||
if (this->settings->getKeybindsLayout() == CUSTOMIZABLE_KEYBINDS) {
|
||||
this->placeText(text, this->playerCursor, "< CUSTOM >", 5.f, 15.f, sf::Vector2u{0, 0});
|
||||
}
|
||||
else {
|
||||
this->placeText(text, this->playerCursor, "< DEFAULT " + std::to_string(this->settings->getKeybindsLayout() + 1) + " >", 5.f, 15.f, sf::Vector2u{0, 0});
|
||||
}
|
||||
|
||||
if (this->selectedAnAction) {
|
||||
text.setOutlineColor(sf::Color(255, 0, 0));
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
int firstElem = std::clamp(((int) this->playerCursor.getPosition().y) - 2, 0, 8);
|
||||
|
||||
for (Action action : ACTION_LIST_IN_ORDER) {
|
||||
if (i >= firstElem && i < (firstElem + 3)) {
|
||||
sf::String string;
|
||||
bool firstKey = true;
|
||||
for (sfKey key : this->settings->getKeybinds().getKeybinds(action)) {
|
||||
if (KEYS_TO_STRING.contains(key)) {
|
||||
std::string keyString = KEYS_TO_STRING.at(key);
|
||||
if (firstKey) {
|
||||
string += keyString;
|
||||
firstKey = false;
|
||||
}
|
||||
else {
|
||||
string += ", " + keyString;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this->placeText(text, this->playerCursor, setStringToUpperCase(ACTION_NAMES[action]), 15.f, ((i - firstElem) * 10) + 25.f, sf::Vector2u{0, (unsigned int) i + 1});
|
||||
text.setOutlineThickness(0);
|
||||
this->placeText(text, {}, string, 40.f, ((i - firstElem) * 10) + 25.f, {});
|
||||
|
||||
sf::Sprite sprite(this->iconTextures[action]);
|
||||
sprite.setOrigin(sprite.getLocalBounds().getCenter());
|
||||
sprite.setPosition(sf::Vector2f(8.f, ((i - firstElem) * 10) + 25.f) * (float) this->settings->getWindowSizeMultiplier());
|
||||
sprite.setScale(sprite.getScale() * ((float) this->settings->getWindowSizeMultiplier() / 2));
|
||||
this->renderWindow->draw(sprite);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
this->renderWindow->display();
|
||||
}
|
||||
24
src/GraphicalUI/AppMenus/SettingsKeybindsAppMenu.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "AppMenu.h"
|
||||
#include "../PlayerCursor.h"
|
||||
|
||||
#include <stack>
|
||||
#include <memory>
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
|
||||
class SettingsKeybindsAppMenu : public AppMenu {
|
||||
private:
|
||||
PlayerCursor playerCursor;
|
||||
sf::Texture iconTextures[11];
|
||||
bool selectedAnAction;
|
||||
Action actionSelected;
|
||||
|
||||
public:
|
||||
SettingsKeybindsAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow);
|
||||
|
||||
void computeFrame() override;
|
||||
|
||||
void drawFrame() const override;
|
||||
};
|
||||
78
src/GraphicalUI/AppMenus/SettingsMainAppMenu.cpp
Normal file
@@ -0,0 +1,78 @@
|
||||
#include "SettingsMainAppMenu.h"
|
||||
|
||||
#include "AppMenu.h"
|
||||
#include "SettingsKeybindsAppMenu.h"
|
||||
#include "SettingsControlsAppMenu.h"
|
||||
#include "../PlayerCursor.h"
|
||||
|
||||
#include <stack>
|
||||
#include <memory>
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
|
||||
SettingsMainAppMenu::SettingsMainAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow) :
|
||||
AppMenu(menuStack, settings, renderWindow),
|
||||
playerCursor({1, 1, 1, 1}) {
|
||||
|
||||
}
|
||||
|
||||
void SettingsMainAppMenu::computeFrame() {
|
||||
this->updateMetaBinds();
|
||||
this->playerCursor.updatePosition();
|
||||
|
||||
switch (this->playerCursor.getPosition().y) {
|
||||
case 2 : {
|
||||
if (this->playerCursor.movedLeft()) {
|
||||
if (this->settings->shortenWindow()) {
|
||||
this->settings->changeVideoMode(*this->renderWindow);
|
||||
}
|
||||
}
|
||||
if (this->playerCursor.movedRight()) {
|
||||
if (this->settings->widenWindow()) {
|
||||
this->settings->changeVideoMode(*this->renderWindow);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 3 : {
|
||||
if (this->playerCursor.movedLeft()) {
|
||||
this->settings->shortenStartTimer();
|
||||
}
|
||||
if (this->playerCursor.movedRight()) {
|
||||
this->settings->lengthenStartTimer();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (this->enterReleased) {
|
||||
if (this->playerCursor.getPosition().y == 0) {
|
||||
this->menuStack->push(std::make_shared<SettingsKeybindsAppMenu>(this->menuStack, this->settings, this->renderWindow));
|
||||
}
|
||||
if (this->playerCursor.getPosition().y == 1) {
|
||||
this->menuStack->push(std::make_shared<SettingsControlsAppMenu>(this->menuStack, this->settings, this->renderWindow));
|
||||
}
|
||||
}
|
||||
if (this->escReleased) {
|
||||
this->menuStack->pop();
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsMainAppMenu::drawFrame() const {
|
||||
this->renderWindow->clear(sf::Color(200, 200, 200));
|
||||
|
||||
sf::Text text(this->pressStartFont, "", this->settings->getWindowSizeMultiplier() * 2);
|
||||
text.setFillColor(sf::Color(0, 0, 0));
|
||||
text.setOutlineColor(sf::Color(255, 255, 255));
|
||||
|
||||
this->placeTitle(text, {}, "SETTINGS", 5.f, {});
|
||||
|
||||
sf::Vector2u windowSize = this->renderWindow->getSize();
|
||||
|
||||
this->placeText(text, this->playerCursor, "CHANGE KEYBINDS", 5.f, 15.f, sf::Vector2u{0, 0});
|
||||
this->placeText(text, this->playerCursor, "CHANGE CONTROLS", 5.f, 25.f, sf::Vector2u{0, 1});
|
||||
this->placeText(text, this->playerCursor, "< WINDOW SIZE: " + std::to_string(windowSize.x) + "x" + std::to_string(windowSize.y) + " >", 5.f, 35.f, sf::Vector2u{0, 2});
|
||||
this->placeText(text, this->playerCursor, "< START TIMER: " + std::to_string(this->settings->getStartTimerLength()) + "s >", 5.f, 45.f, sf::Vector2u{0, 3});
|
||||
|
||||
this->renderWindow->display();
|
||||
}
|
||||
21
src/GraphicalUI/AppMenus/SettingsMainAppMenu.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "AppMenu.h"
|
||||
#include "../PlayerCursor.h"
|
||||
|
||||
#include <stack>
|
||||
#include <memory>
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
|
||||
class SettingsMainAppMenu : public AppMenu {
|
||||
private:
|
||||
PlayerCursor playerCursor;
|
||||
|
||||
public:
|
||||
SettingsMainAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow);
|
||||
|
||||
void computeFrame() override;
|
||||
|
||||
void drawFrame() const override;
|
||||
};
|
||||
75
src/GraphicalUI/AppMenus/StartUpAppMenu.cpp
Normal file
@@ -0,0 +1,75 @@
|
||||
#include "StartUpAppMenu.h"
|
||||
|
||||
#include "AppMenu.h"
|
||||
#include "MainAppMenu.h"
|
||||
#include "../PlayerCursor.h"
|
||||
|
||||
#include <stack>
|
||||
#include <memory>
|
||||
#include <algorithm>
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
|
||||
StartUpAppMenu::StartUpAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow) :
|
||||
AppMenu(menuStack, settings, renderWindow),
|
||||
playerCursor({MAXIMUM_PIECES_SIZE + 1}) {
|
||||
|
||||
this->playerCursor.goToPosition({(unsigned int) std::clamp(this->settings->getMaximumPiecesSize(), MINIMUM_PIECES_SIZE, MAXIMUM_PIECES_SIZE), 0u});
|
||||
}
|
||||
|
||||
void StartUpAppMenu::computeFrame() {
|
||||
this->updateMetaBinds();
|
||||
this->playerCursor.updatePosition();
|
||||
|
||||
if (this->playerCursor.getPosition().x < MINIMUM_PIECES_SIZE) {
|
||||
if (this->playerCursor.movedLeft()) {
|
||||
this->playerCursor.goToPosition({MAXIMUM_PIECES_SIZE, 0});
|
||||
}
|
||||
else {
|
||||
this->playerCursor.goToPosition({MINIMUM_PIECES_SIZE, 0});
|
||||
}
|
||||
}
|
||||
|
||||
if (this->enterReleased) {
|
||||
this->settings->loadSettingsFromFile(true, {this->playerCursor.getPosition().x});
|
||||
this->menuStack->pop();
|
||||
|
||||
if (this->settings->hasLoadedPieces()) {
|
||||
this->menuStack->push(std::make_shared<MainAppMenu>(this->menuStack, this->settings, this->renderWindow));
|
||||
}
|
||||
else {
|
||||
std::cout << "ERROR: COULD NOT LOAD PIECES" << std::endl;
|
||||
std::cout << "ARGUMENT WAS: " << this->playerCursor.getPosition().x << std::endl;
|
||||
}
|
||||
}
|
||||
else if (this->escReleased) {
|
||||
this->menuStack->pop();
|
||||
}
|
||||
}
|
||||
|
||||
void StartUpAppMenu::drawFrame() const {
|
||||
this->renderWindow->clear(sf::Color(200, 200, 200));
|
||||
|
||||
sf::Text text(this->pressStartFont, "", this->settings->getWindowSizeMultiplier() * 2);
|
||||
text.setFillColor(sf::Color(0, 0, 0));
|
||||
text.setOutlineColor(sf::Color(255, 255, 255));
|
||||
|
||||
this->placeTitle(text, {}, "SELECT THE LOADED PIECES MAXIMUM SIZE", 10.f, {});
|
||||
this->placeTitle(text, this->playerCursor, "< " + std::to_string(this->playerCursor.getPosition().x) + " >", 25.f, this->playerCursor.getPosition());
|
||||
|
||||
text.setOutlineColor({0, 0, 0});
|
||||
if (this->playerCursor.getPosition().x <= 10) {
|
||||
text.setFillColor({0, 255, 0});
|
||||
this->placeTitle(text, {}, "LOW LOAD TIME", 40.f, {});
|
||||
}
|
||||
else if (this->playerCursor.getPosition().x <= 13) {
|
||||
text.setFillColor({255, 255, 0});
|
||||
this->placeTitle(text, {}, "MEDIUM LOAD TIME", 40.f, {});
|
||||
}
|
||||
else {
|
||||
text.setFillColor({255, 0, 0});
|
||||
this->placeTitle(text, {}, "LONG LOAD TIME", 40.f, {});
|
||||
}
|
||||
|
||||
this->renderWindow->display();
|
||||
}
|
||||
21
src/GraphicalUI/AppMenus/StartUpAppMenu.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "AppMenu.h"
|
||||
#include "../PlayerCursor.h"
|
||||
|
||||
#include <stack>
|
||||
#include <memory>
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
|
||||
class StartUpAppMenu : public AppMenu {
|
||||
private:
|
||||
PlayerCursor playerCursor;
|
||||
|
||||
public:
|
||||
StartUpAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow);
|
||||
|
||||
void computeFrame() override;
|
||||
|
||||
void drawFrame() const override;
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "GraphApp.h"
|
||||
|
||||
#include "AppMenus/AppMenu.h"
|
||||
#include "AppMenus/MainAppMenu.h"
|
||||
#include "AppMenus/StartUpAppMenu.h"
|
||||
#include "Settings.h"
|
||||
|
||||
#include <stack>
|
||||
@@ -12,14 +12,14 @@ static const double TIME_BETWEEN_FRAMES = (1000.f / FRAMES_PER_SECOND);
|
||||
|
||||
|
||||
GraphApp::GraphApp() {
|
||||
this->settings = std::make_shared<Settings>();
|
||||
this->settings = std::make_shared<Settings>(false);
|
||||
this->menuStack = std::make_shared<MenuStack>();
|
||||
this->renderWindow = std::make_shared<sf::RenderWindow>();
|
||||
}
|
||||
|
||||
void GraphApp::run() {
|
||||
changeVideoMode(*this->renderWindow, this->settings->getVideoMode());
|
||||
this->menuStack->push(std::make_shared<MainAppMenu>(this->menuStack, this->settings, this->renderWindow));
|
||||
this->settings->changeVideoMode(*this->renderWindow);
|
||||
this->menuStack->push(std::make_shared<StartUpAppMenu>(this->menuStack, this->settings, this->renderWindow));
|
||||
|
||||
bool quit = false;
|
||||
double timeAtNextFrame = 0;
|
||||
@@ -48,5 +48,6 @@ void GraphApp::run() {
|
||||
}
|
||||
}
|
||||
}
|
||||
this->settings->saveSettingsToFile();
|
||||
renderWindow->close();
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ Keybinds::Keybinds(int layoutNumber) :
|
||||
for (Action action : ACTION_LIST_IN_ORDER) {
|
||||
this->keybinds.insert({action, std::set<sfKey>()});
|
||||
}
|
||||
this->modifiable = (layoutNumber == CUSTOMIZABLE_KEYBINDS);
|
||||
|
||||
this->loadKeybindsFromFile();
|
||||
}
|
||||
@@ -44,6 +45,8 @@ void Keybinds::loadKeybindsFromFile() {
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -62,13 +65,22 @@ void Keybinds::saveKeybindsToFile() const {
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <algorithm>
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
using sfKey = sf::Keyboard::Key;
|
||||
@@ -16,7 +17,8 @@ class Keybinds {
|
||||
private:
|
||||
std::map<Action, std::set<sfKey>> keybinds;
|
||||
int layoutNumber;
|
||||
|
||||
bool modifiable;
|
||||
|
||||
public:
|
||||
Keybinds(int layoutNumber);
|
||||
|
||||
@@ -28,7 +30,127 @@ class Keybinds {
|
||||
|
||||
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,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
|
||||
enum PiecesType {
|
||||
CONVEX_PIECES,
|
||||
@@ -19,3 +21,15 @@ inline int getSizeOfPieces(PiecesType type) {
|
||||
inline PiecesType createSinglePieceType(int size) {
|
||||
return PiecesType(SINGLE_PIECE + size - 1);
|
||||
}
|
||||
|
||||
inline std::string getPiecesTypeName(PiecesType piecesType) {
|
||||
static const std::string PIECES_TYPE_NAME[] = {
|
||||
"CONVEX",
|
||||
"HOLELESS",
|
||||
"OTHER",
|
||||
"ALL",
|
||||
"SINGLE"
|
||||
};
|
||||
|
||||
return PIECES_TYPE_NAME[piecesType];
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "PlayerCursor.h"
|
||||
|
||||
#include "../Keybinds.h"
|
||||
#include "../Settings.h"
|
||||
#include "Keybinds.h"
|
||||
#include "Settings.h"
|
||||
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
@@ -42,6 +42,22 @@ void PlayerCursor::updatePosition() {
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -50,6 +66,48 @@ void PlayerCursor::goToPosition(const sf::Vector2u& newPosition) {
|
||||
}
|
||||
}
|
||||
|
||||
bool PlayerCursor::addPosition(unsigned int x, unsigned int y) {
|
||||
if (y >= this->rows.size()) return false;
|
||||
if (x > this->rows.at(y)) return false;
|
||||
|
||||
this->rows.at(y)++;
|
||||
if ((y == this->position.y) && (x <= this->position.x)) {
|
||||
this->position.x++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PlayerCursor::removePosition(unsigned int x, unsigned int y) {
|
||||
if (y >= this->rows.size()) return false;
|
||||
if (x >= this->rows.at(y)) return false;
|
||||
|
||||
this->rows.at(y)--;
|
||||
if ((y == this->position.y) && (x < this->position.x)) {
|
||||
this->position.x--;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PlayerCursor::addRow(unsigned int position, unsigned int width) {
|
||||
if (position > this->rows.size()) return false;
|
||||
|
||||
this->rows.insert(this->rows.begin() + position, width);
|
||||
if (position <= this->position.y) {
|
||||
this->position.y++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PlayerCursor::removeRow(unsigned int position) {
|
||||
if (position >= this->rows.size()) return false;
|
||||
|
||||
this->rows.erase(this->rows.begin() + position);
|
||||
if (position < this->position.y) {
|
||||
this->position.y--;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const sf::Vector2u& PlayerCursor::getPosition() const {
|
||||
return this->position;
|
||||
}
|
||||
@@ -18,8 +18,24 @@ class PlayerCursor {
|
||||
|
||||
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:
|
||||
@@ -2,40 +2,99 @@
|
||||
|
||||
#include "../Core/Menu.h"
|
||||
#include "Keybinds.h"
|
||||
#include "PiecesType.h"
|
||||
|
||||
#include <SFML/Graphics.hpp>
|
||||
#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};
|
||||
static const int WINDOW_SIZE_MULTIPLIERS[] = {4, 6, 10, 14, 20, 30, 40};
|
||||
static const int WINDOW_SIZE_LAST_MODE = (sizeof(WINDOW_SIZE_MULTIPLIERS) / sizeof(int)) - 1;
|
||||
static const int START_TIMER_MAX = 4;
|
||||
static const int DISTRIBUTION_MAX = 20;
|
||||
|
||||
|
||||
Settings::Settings() {
|
||||
for (int i = 1; i <= MAXIMUM_PIECES_SIZE; i++) {
|
||||
this->menu.getPiecesList().loadPieces(i);
|
||||
}
|
||||
|
||||
Settings::Settings(bool loadPieces) {
|
||||
this->keybinds.clear();
|
||||
this->keybinds.reserve(NUMBER_OF_KEYBINDS);
|
||||
for (int i = 0; i < NUMBER_OF_KEYBINDS; i++) {
|
||||
this->keybinds.emplace_back(i);
|
||||
}
|
||||
|
||||
this->loadSettingsFromFile();
|
||||
this->loadSettingsFromFile(loadPieces, {});
|
||||
}
|
||||
|
||||
void Settings::loadSettingsFromFile() {
|
||||
void Settings::loadPieces(int maximumPiecesSizeRequest) {
|
||||
if (maximumPiecesSizeRequest < MINIMUM_PIECES_SIZE) {
|
||||
maximumPiecesSizeRequest = MINIMUM_PIECES_SIZE;
|
||||
}
|
||||
else if (maximumPiecesSizeRequest > MAXIMUM_PIECES_SIZE) {
|
||||
maximumPiecesSizeRequest = MAXIMUM_PIECES_SIZE;
|
||||
}
|
||||
|
||||
bool succeeded = true;
|
||||
int i = 1;
|
||||
while (succeeded && (i <= maximumPiecesSizeRequest)) {
|
||||
succeeded = this->menu.getPiecesList().loadPieces(i);
|
||||
i++;
|
||||
}
|
||||
|
||||
if (succeeded) {
|
||||
this->maximumPiecesSize = maximumPiecesSizeRequest;
|
||||
}
|
||||
this->loadedPieces = succeeded;
|
||||
}
|
||||
|
||||
void Settings::loadSettingsFromFile(bool loadPieces, std::optional<int> maximumPiecesSizeRequest) {
|
||||
std::ifstream settingsFile("data/config/settings.bin", std::ios::binary);
|
||||
char byte;
|
||||
|
||||
// file format version
|
||||
settingsFile.get(byte);
|
||||
|
||||
// maximum pieces size
|
||||
settingsFile.get(byte);
|
||||
this->maximumPiecesSize = byte;
|
||||
|
||||
if (loadPieces) {
|
||||
if (maximumPiecesSizeRequest.has_value()) {
|
||||
this->loadPieces(maximumPiecesSizeRequest.value());
|
||||
}
|
||||
else {
|
||||
this->loadPieces(byte);
|
||||
}
|
||||
}
|
||||
else {
|
||||
this->loadedPieces = false;
|
||||
}
|
||||
|
||||
// keybind layout
|
||||
settingsFile.get(byte);
|
||||
this->chosenKeybinds = byte;
|
||||
|
||||
// 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);
|
||||
@@ -48,35 +107,92 @@ void Settings::loadSettingsFromFile() {
|
||||
settingsFile.get(byte);
|
||||
this->menu.setBoardHeight(byte);
|
||||
|
||||
// piece distribution
|
||||
settingsFile.get(byte);
|
||||
//TODO
|
||||
if (this->loadedPieces) {
|
||||
// piece distribution
|
||||
settingsFile.get(byte);
|
||||
this->menu.getPiecesList().setDistributionMode(DistributionMode(byte));
|
||||
|
||||
// selected pieces
|
||||
char pieceType;
|
||||
char pieceValue;
|
||||
this->selectedPieces.clear();
|
||||
while (settingsFile.get(pieceType)) {
|
||||
if (settingsFile.eof()) break;
|
||||
|
||||
settingsFile.get(pieceValue);
|
||||
this->selectedPieces.push_back({PiecesType(pieceType), pieceValue});
|
||||
this->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();
|
||||
}
|
||||
this->confirmSelectedPieces();
|
||||
}
|
||||
|
||||
void Settings::saveSettingsToFile() const {
|
||||
if (!this->loadedPieces) return;
|
||||
|
||||
this->keybinds.at(CUSTOMIZABLE_KEYBINDS).saveKeybindsToFile();
|
||||
|
||||
std::ofstream settingsFile("data/config/settings.bin", std::ios::trunc | std::ios::binary);
|
||||
char byte;
|
||||
|
||||
// file format version
|
||||
byte = CURRENT_FILE_FORMAT_VERSION;
|
||||
settingsFile.write(&byte, 1);
|
||||
|
||||
// maximum pieces size
|
||||
byte = this->maximumPiecesSize;
|
||||
settingsFile.write(&byte, 1);
|
||||
|
||||
// keybind layout
|
||||
byte = this->chosenKeybinds;
|
||||
settingsFile.write(&byte, 1);
|
||||
|
||||
// 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);
|
||||
@@ -90,20 +206,35 @@ void Settings::saveSettingsToFile() const {
|
||||
settingsFile.write(&byte, 1);
|
||||
|
||||
// piece distribution
|
||||
//TODO
|
||||
byte = this->menu.readPiecesList().getDistributionMode();
|
||||
settingsFile.write(&byte, 1);
|
||||
|
||||
for (int i = 1; i <= 15; i++) {
|
||||
byte = this->distributions.at(i);
|
||||
settingsFile.write(&byte, 1);
|
||||
}
|
||||
|
||||
// selected pieces
|
||||
for (const auto& [type, value] : this->selectedPieces) {
|
||||
byte = type;
|
||||
settingsFile.write(&byte, 1);
|
||||
byte = value;
|
||||
settingsFile.write(&byte, 1);
|
||||
if (getSizeOfPieces(type) == 0) {
|
||||
byte = value;
|
||||
settingsFile.write(&byte, 1);
|
||||
}
|
||||
else {
|
||||
int number = value;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
byte = (number % 256);
|
||||
settingsFile.write(&byte, 1);
|
||||
number = (number >> 8);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Settings::selectNextKeybinds() {
|
||||
if (this->chosenKeybinds < NUMBER_OF_KEYBINDS) {
|
||||
if (this->chosenKeybinds < (NUMBER_OF_KEYBINDS - 1)) {
|
||||
this->chosenKeybinds++;
|
||||
return true;
|
||||
}
|
||||
@@ -122,10 +253,6 @@ bool Settings::canModifyCurrentKeybinds() const {
|
||||
return (this->chosenKeybinds == CUSTOMIZABLE_KEYBINDS);
|
||||
}
|
||||
|
||||
void Settings::setGamemode(Gamemode gamemode) {
|
||||
this->gamemode = gamemode;
|
||||
}
|
||||
|
||||
bool Settings::widenWindow() {
|
||||
if (this->windowSizeMode < WINDOW_SIZE_LAST_MODE) {
|
||||
this->windowSizeMode++;
|
||||
@@ -142,36 +269,110 @@ bool Settings::shortenWindow() {
|
||||
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) {
|
||||
switch (type) {
|
||||
case CONVEX_PIECES : {this->menu.getPiecesList().selectConvexPieces(value); break;}
|
||||
case HOLELESS_PIECES : {this->menu.getPiecesList().selectHolelessPieces(value); break;}
|
||||
case OTHER_PIECES : {this->menu.getPiecesList().selectOtherPieces(value); break;}
|
||||
case ALL_PIECES : {this->menu.getPiecesList().selectAllPieces(value); break;}
|
||||
if (!(value > this->maximumPiecesSize)) {
|
||||
switch (type) {
|
||||
case CONVEX_PIECES : {this->menu.getPiecesList().selectConvexPieces(value); break;}
|
||||
case HOLELESS_PIECES : {this->menu.getPiecesList().selectHolelessPieces(value); break;}
|
||||
case OTHER_PIECES : {this->menu.getPiecesList().selectOtherPieces(value); break;}
|
||||
case ALL_PIECES : {this->menu.getPiecesList().selectAllPieces(value); break;}
|
||||
}
|
||||
selectedNone = false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (size > MAXIMUM_PIECES_SIZE) return;
|
||||
|
||||
this->menu.getPiecesList().selectPiece(size, value);
|
||||
if (!(getSizeOfPieces(type) > this->maximumPiecesSize)) {
|
||||
this->menu.getPiecesList().selectPiece(size, value);
|
||||
selectedNone = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedNone) {
|
||||
this->selectedPieces.push_back(DEFAULT_SELECTION);
|
||||
this->confirmSelectedPieces();
|
||||
}
|
||||
}
|
||||
|
||||
bool Settings::increaseDistribution(int size) {
|
||||
if (!this->loadedPieces) return false;
|
||||
if (size < 1 || size > this->maximumPiecesSize) return false;
|
||||
|
||||
if (this->distributions.at(size) < DISTRIBUTION_MAX) {
|
||||
this->distributions.at(size)++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Settings::decreaseDistribution(int size) {
|
||||
if (!this->loadedPieces) return false;
|
||||
if (size < 1 || size > this->maximumPiecesSize) return false;
|
||||
|
||||
if (this->distributions.at(size) > 0) {
|
||||
this->distributions.at(size)--;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Settings::confirmDistribution() {
|
||||
if (!this->loadedPieces) return;
|
||||
|
||||
for (int i = 1; i <= 15; i++) {
|
||||
this->menu.getPiecesList().changeCustomDistribution(i, (double) 1 / (this->distributions.at(i) + 0.001));
|
||||
}
|
||||
}
|
||||
|
||||
Menu& Settings::getMenu() {
|
||||
@@ -182,6 +383,18 @@ Keybinds& Settings::getKeybinds() {
|
||||
return this->keybinds.at(this->chosenKeybinds);
|
||||
}
|
||||
|
||||
int Settings::getMaximumPiecesSize() const {
|
||||
return this->maximumPiecesSize;
|
||||
}
|
||||
|
||||
bool Settings::hasLoadedPieces() const {
|
||||
return this->loadedPieces;
|
||||
}
|
||||
|
||||
int Settings::getKeybindsLayout() const {
|
||||
return this->chosenKeybinds;
|
||||
}
|
||||
|
||||
Gamemode Settings::getGamemode() const {
|
||||
return this->gamemode;
|
||||
}
|
||||
@@ -190,10 +403,14 @@ int Settings::getWindowSizeMultiplier() const {
|
||||
return WINDOW_SIZE_MULTIPLIERS[this->windowSizeMode];
|
||||
}
|
||||
|
||||
const sf::VideoMode Settings::getVideoMode() const {
|
||||
return sf::VideoMode(BASE_WINDOW_SIZE * (unsigned int) 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;
|
||||
}
|
||||
|
||||
@@ -4,33 +4,47 @@
|
||||
#include "Keybinds.h"
|
||||
#include "PiecesType.h"
|
||||
|
||||
#include <SFML/Graphics.hpp>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
static const int CURRENT_FILE_FORMAT_VERSION = 11;
|
||||
|
||||
static const int MAXIMUM_BOARD_WIDTH = 40;
|
||||
static const int MAXIMUM_BOARD_HEIGHT = 40;
|
||||
|
||||
//#define __JMINOS_RELEASE__
|
||||
#ifdef __JMINOS_RELEASE__
|
||||
static const int MAXIMUM_PIECES_SIZE = 15;
|
||||
static const int RELEASE_PIECES_SIZE = 15;
|
||||
static const int DEBUG_PIECES_SIZE = 10;
|
||||
|
||||
static const int MINIMUM_PIECES_SIZE = 4;
|
||||
#ifdef NDEBUG
|
||||
static const int MAXIMUM_PIECES_SIZE = RELEASE_PIECES_SIZE;
|
||||
#else
|
||||
static const int MAXIMUM_PIECES_SIZE = 10;
|
||||
static const int MAXIMUM_PIECES_SIZE = DEBUG_PIECES_SIZE;
|
||||
#endif
|
||||
|
||||
static const std::pair<PiecesType, int> DEFAULT_SELECTION = {ALL_PIECES, MINIMUM_PIECES_SIZE};
|
||||
|
||||
|
||||
class Settings {
|
||||
private:
|
||||
Menu menu;
|
||||
int maximumPiecesSize;
|
||||
bool loadedPieces;
|
||||
std::vector<Keybinds> keybinds;
|
||||
int chosenKeybinds;
|
||||
Gamemode gamemode;
|
||||
int windowSizeMode;
|
||||
int startTimerLength;
|
||||
Gamemode gamemode;
|
||||
std::vector<std::pair<PiecesType, int>> selectedPieces;
|
||||
std::vector<int> distributions;
|
||||
|
||||
public:
|
||||
Settings();
|
||||
Settings(bool loadPieces);
|
||||
|
||||
void loadSettingsFromFile();
|
||||
void loadPieces(int maximumPiecesSizeRequest);
|
||||
|
||||
void loadSettingsFromFile(bool loadPieces, std::optional<int> maximumPiecesSizeRequest);
|
||||
|
||||
void saveSettingsToFile() const;
|
||||
|
||||
@@ -40,27 +54,47 @@ class Settings {
|
||||
|
||||
bool canModifyCurrentKeybinds() const;
|
||||
|
||||
void setGamemode(Gamemode gamemode);
|
||||
|
||||
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 getMaximumPiecesSize() const;
|
||||
|
||||
bool hasLoadedPieces() const;
|
||||
|
||||
int getKeybindsLayout() const;
|
||||
|
||||
Gamemode getGamemode() const;
|
||||
|
||||
int getWindowSizeMultiplier() const;
|
||||
|
||||
const sf::VideoMode getVideoMode() const;
|
||||
int getStartTimerLength() const;
|
||||
|
||||
const std::vector<std::pair<PiecesType, int>>& getSelectedPieces() const;
|
||||
|
||||
const std::vector<int>& getDistributions() const;
|
||||
};
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#include "GraphApp.h"
|
||||
#include "../Pieces/PiecesFiles.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
|
||||
void resetConfigFiles();
|
||||
void resetSettingsFile();
|
||||
void resetKeybindFile(int layout);
|
||||
|
||||
@@ -12,28 +12,55 @@ void resetKeybindFile(int layout);
|
||||
int main() {
|
||||
std::srand(std::time(NULL));
|
||||
|
||||
#ifndef __JMINOS_RELEASE__
|
||||
|
||||
PiecesFiles pf;
|
||||
for (int i = 1; i <= MAXIMUM_PIECES_SIZE; i++) {
|
||||
if (!std::filesystem::exists("data/pieces/" + std::to_string(i) + "minos.bin")) {
|
||||
std::cout << "pieces files for size " << i << " not found, generating..." << std::endl;
|
||||
#ifndef DEBUG
|
||||
std::cout << "IMPORTANT: You are currently in release mode, if you do not wish to generate big pieces (can take several minutes), type 'xmake f -m debug'." << std::endl;
|
||||
#endif
|
||||
|
||||
std::cout << "INFO: Pieces files for size " << i << " not found, generating..." << std::endl;
|
||||
pf.savePieces(i);
|
||||
}
|
||||
}
|
||||
#ifdef DEBUG
|
||||
std::cout << "IMPORTANT: You are currently in debug mode, if you wish to use bigger pieces, type 'xmake f -m release'." << std::endl;
|
||||
|
||||
bool everythingGenerated = true;
|
||||
for (int i = DEBUG_PIECES_SIZE; i <= RELEASE_PIECES_SIZE; i++) {
|
||||
if (!std::filesystem::exists("data/pieces/" + std::to_string(i) + "minos.bin")) {
|
||||
everythingGenerated = false;
|
||||
}
|
||||
}
|
||||
if (!everythingGenerated) {
|
||||
std::cout << "NOTE: You do not have all pieces generated, generating can take several minutes." << std::endl;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!std::filesystem::exists("data/config/settings.bin")) {
|
||||
std::cout << "INFO: Settings file not found, generating..." << std::endl;
|
||||
resetSettingsFile();
|
||||
}
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if (!std::filesystem::exists("data/config/keybinds/layout" + std::to_string(i) + ".bin")) {
|
||||
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;
|
||||
resetSettingsFile();
|
||||
for (int i = 0; i < NUMBER_OF_KEYBINDS; i++) {
|
||||
resetKeybindFile(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// uncomment before compiling release version
|
||||
//resetConfigFiles();
|
||||
|
||||
#endif
|
||||
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 n°" << (i + 1) << "/" << NUMBER_OF_KEYBINDS << " not found, generating..." << std::endl;
|
||||
resetKeybindFile(i);
|
||||
}
|
||||
}
|
||||
|
||||
GraphApp UI;
|
||||
UI.run();
|
||||
@@ -42,40 +69,67 @@ int main() {
|
||||
}
|
||||
|
||||
|
||||
void resetConfigFiles() {
|
||||
resetSettingsFile;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
resetKeybindFile(i);
|
||||
}
|
||||
}
|
||||
|
||||
void resetSettingsFile() {
|
||||
if (!std::filesystem::exists("data/config")) {
|
||||
std::filesystem::create_directories("data/config");
|
||||
}
|
||||
|
||||
std::ofstream settingsFile("data/config/settings.bin", std::ios::trunc | std::ios::binary);
|
||||
char byte;
|
||||
|
||||
Menu menu;
|
||||
|
||||
// file format version
|
||||
byte = CURRENT_FILE_FORMAT_VERSION;
|
||||
settingsFile.write(&byte, 1);
|
||||
|
||||
// maximum pieces size
|
||||
byte = MINIMUM_PIECES_SIZE;
|
||||
settingsFile.write(&byte, 1);
|
||||
|
||||
// keybind layout
|
||||
byte = 0;
|
||||
settingsFile.write(&byte, 1);
|
||||
|
||||
// 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 = SPRINT;
|
||||
byte = Gamemode(0);
|
||||
settingsFile.write(&byte, 1);
|
||||
|
||||
// board width
|
||||
byte = 10;
|
||||
byte = menu.getBoardWidth();
|
||||
settingsFile.write(&byte, 1);
|
||||
|
||||
// board height
|
||||
byte = 20;
|
||||
byte = menu.getBoardHeight();
|
||||
settingsFile.write(&byte, 1);
|
||||
|
||||
// piece distribution
|
||||
byte = 0;
|
||||
byte = DEFAULT;
|
||||
settingsFile.write(&byte, 1);
|
||||
for (int i = 1; i <= 15; i++) {
|
||||
byte = 1;
|
||||
settingsFile.write(&byte, 1);
|
||||
}
|
||||
|
||||
// selected pieces
|
||||
byte = ALL_PIECES;
|
||||
|
||||
@@ -50,7 +50,7 @@ void Generator::generate(int polyominoSize, int lastAddedPositionNumber, int nex
|
||||
}
|
||||
|
||||
// generate the list of candidate positions
|
||||
for (Position position : this->currentTestedShape) {
|
||||
for (const Position position : this->currentTestedShape) {
|
||||
this->tryToAddCandidatePosition(Position{position.x, position.y + 1}, nextAvaibleNumber, candidatePositions);
|
||||
this->tryToAddCandidatePosition(Position{position.x + 1, position.y}, nextAvaibleNumber, candidatePositions);
|
||||
this->tryToAddCandidatePosition(Position{position.x, position.y - 1}, nextAvaibleNumber, candidatePositions);
|
||||
@@ -58,7 +58,7 @@ void Generator::generate(int polyominoSize, int lastAddedPositionNumber, int nex
|
||||
}
|
||||
|
||||
// try adding a square only to positions with a higher number than the last one
|
||||
for (auto [key, val] : candidatePositions) {
|
||||
for (const auto [key, val] : candidatePositions) {
|
||||
if (val > lastAddedPositionNumber) {
|
||||
this->currentTestedShape.insert(key);
|
||||
this->generate(polyominoSize, val, nextAvaibleNumber, (polyominoSize == this->currentTestedShape.size()) ? std::map<Position, int>() : candidatePositions);
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
#include <filesystem>
|
||||
#include <algorithm>
|
||||
|
||||
#include "../Common/Compression.h"
|
||||
|
||||
|
||||
PiecesFiles::PiecesFiles() {
|
||||
}
|
||||
@@ -19,34 +21,46 @@ bool PiecesFiles::savePieces(int polyominoSize) const {
|
||||
if (!this->getFilePath(polyominoSize, filePath)) {
|
||||
return false;
|
||||
}
|
||||
std::ofstream piecesFile(filePath, std::ios::trunc | std::ios::binary);
|
||||
if (!piecesFile.good()) {
|
||||
|
||||
Generator generator;
|
||||
std::vector<Polyomino> polyominoes = generator.generatePolyominoes(polyominoSize);
|
||||
|
||||
return this->savePieces(polyominoSize, polyominoes);
|
||||
}
|
||||
|
||||
bool PiecesFiles::savePieces(int polyominoSize, std::vector<Polyomino>& polyominoes) const {
|
||||
std::string filePath;
|
||||
if (!this->getFilePath(polyominoSize, filePath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Generator generator;
|
||||
std::vector<Polyomino> nMinos = generator.generatePolyominoes(polyominoSize);
|
||||
constexpr std::size_t INITIAL_CAPACITY = 2048;
|
||||
DataBuffer buffer(INITIAL_CAPACITY);
|
||||
|
||||
// sorting the polyominoes is done after setting spawn position to ensure the order is always the same
|
||||
for (Polyomino& nMino : nMinos) {
|
||||
for (Polyomino& nMino : polyominoes) {
|
||||
nMino.goToSpawnPosition();
|
||||
}
|
||||
std::sort(nMinos.begin(), nMinos.end());
|
||||
std::sort(polyominoes.begin(), polyominoes.end());
|
||||
|
||||
for (const Polyomino& nMino : nMinos) {
|
||||
for (const Polyomino& polyomino : polyominoes) {
|
||||
// write the characteristics of the piece
|
||||
char infoByte = (nMino.isConvex() << 7) + (nMino.hasHole() << 6) + nMino.getLength();
|
||||
piecesFile.write(&infoByte, 1);
|
||||
bool isConvex = polyomino.isConvex();
|
||||
bool hasHole = (isConvex) ? false : polyomino.hasHole();
|
||||
std::uint8_t infoByte = (isConvex << 7) + (hasHole << 6) + polyomino.getLength();
|
||||
buffer << infoByte;
|
||||
|
||||
// write the positions of the piece
|
||||
char positionByte;
|
||||
for (Position position : nMino.getPositions()) {
|
||||
std::uint8_t positionByte;
|
||||
for (const Position position : polyomino.getPositions()) {
|
||||
positionByte = (position.x << 4) + position.y;
|
||||
piecesFile.write(&positionByte, 1);
|
||||
buffer << positionByte;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
DataBuffer compressed = Compress(buffer);
|
||||
|
||||
return compressed.WriteFile(filePath);
|
||||
}
|
||||
|
||||
bool PiecesFiles::loadPieces(int polyominoSize, std::vector<Piece>& pieces, std::vector<int>& convexPieces, std::vector<int>& holelessPieces, std::vector<int>& otherPieces) const {
|
||||
@@ -54,6 +68,13 @@ bool PiecesFiles::loadPieces(int polyominoSize, std::vector<Piece>& pieces, std:
|
||||
if (!this->getFilePath(polyominoSize, filePath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DataBuffer compressed;
|
||||
if(!compressed.ReadFile(filePath))
|
||||
return false;
|
||||
|
||||
DataBuffer buffer = Decompress(compressed, compressed.GetSize());
|
||||
|
||||
std::ifstream piecesFile(filePath, std::ios::binary);
|
||||
if (!piecesFile.good()) {
|
||||
return false;
|
||||
@@ -76,10 +97,11 @@ bool PiecesFiles::loadPieces(int polyominoSize, std::vector<Piece>& pieces, std:
|
||||
char xMask = 0b1111'0000;
|
||||
char yMask = 0b0000'1111;
|
||||
|
||||
char infoByte;
|
||||
std::uint8_t infoByte;
|
||||
int i = 0;
|
||||
while (piecesFile.get(infoByte)) {
|
||||
if (piecesFile.eof()) break;
|
||||
while (!buffer.IsFinished()) {
|
||||
// if (piecesFile.eof()) break;
|
||||
buffer >> infoByte;
|
||||
|
||||
// read piece infos
|
||||
bool isConvex = (infoByte & convexMask) >> 7;
|
||||
@@ -88,10 +110,10 @@ bool PiecesFiles::loadPieces(int polyominoSize, std::vector<Piece>& pieces, std:
|
||||
|
||||
// read positions
|
||||
std::set<Position> piecePositions;
|
||||
char positionByte;
|
||||
std::uint8_t positionByte;
|
||||
for (int i = 0; i < polyominoSize; i++) {
|
||||
piecesFile.get(positionByte);
|
||||
int x = (positionByte & xMask) >> 4;
|
||||
buffer >> positionByte;
|
||||
int x = ((unsigned char) positionByte & xMask) >> 4;
|
||||
int y = positionByte & yMask;
|
||||
piecePositions.insert(Position{x, y});
|
||||
}
|
||||
@@ -119,6 +141,11 @@ bool PiecesFiles::loadPieces(int polyominoSize, std::vector<Piece>& pieces, std:
|
||||
|
||||
bool PiecesFiles::getFilePath(int polyominoSize, std::string& filePath) const {
|
||||
std::string dataFolderPath = "data/pieces/";
|
||||
|
||||
if (!std::filesystem::exists(dataFolderPath)) {
|
||||
std::filesystem::create_directories(dataFolderPath);
|
||||
}
|
||||
|
||||
if (!std::filesystem::is_directory(dataFolderPath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -22,13 +22,18 @@ class PiecesFiles {
|
||||
*/
|
||||
bool savePieces(int polyominoSize) const;
|
||||
|
||||
/**
|
||||
* Generate a file containing all the pieces of the specified size, assuming they have been correctly generated
|
||||
* @return If the file could be created
|
||||
*/
|
||||
bool savePieces(int polyominoSize, std::vector<Polyomino>& polyominoes) const;
|
||||
|
||||
/**
|
||||
* Replace the content of the vectors by the pieces of the specified size, if the file wasn't found the vectors stays untouched
|
||||
* @return If the file was found
|
||||
*/
|
||||
bool loadPieces(int polyominoSize, std::vector<Piece>& pieces, std::vector<int>& convexPieces, std::vector<int>& holelessPieces, std::vector<int>& otherPieces) const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Puts the path to the piece file of the specified size in order, if the data folder wasn't found the string stays untouched
|
||||
* @return If the data folder was found
|
||||
|
||||
@@ -15,7 +15,7 @@ Polyomino::Polyomino(const std::set<Position>& positions) {
|
||||
int maxX = INT_MIN;
|
||||
int minY = INT_MAX;
|
||||
int maxY = INT_MIN;
|
||||
for (Position position : positions) {
|
||||
for (const Position position : positions) {
|
||||
if (position.x < minX) minX = position.x;
|
||||
if (position.x > maxX) maxX = position.x;
|
||||
if (position.y < minY) minY = position.y;
|
||||
@@ -40,13 +40,13 @@ Polyomino::Polyomino(const std::set<Position>& positions, int length) :
|
||||
void Polyomino::normalize() {
|
||||
int minX = INT_MAX;
|
||||
int minY = INT_MAX;
|
||||
for (Position position : this->positions) {
|
||||
for (const Position position : this->positions) {
|
||||
if (position.x < minX) minX = position.x;
|
||||
if (position.y < minY) minY = position.y;
|
||||
}
|
||||
|
||||
std::set<Position> newPositions;
|
||||
for (Position position : this->positions) {
|
||||
for (const Position position : this->positions) {
|
||||
newPositions.insert(Position{position.x - minX, position.y - minY});
|
||||
}
|
||||
this->positions = std::move(newPositions);
|
||||
@@ -54,7 +54,7 @@ void Polyomino::normalize() {
|
||||
|
||||
void Polyomino::rotateCW() {
|
||||
std::set<Position> newPositions;
|
||||
for (Position position : this->positions) {
|
||||
for (const Position position : this->positions) {
|
||||
newPositions.insert(Position{position.y, (length - 1) - (position.x)});
|
||||
}
|
||||
this->positions = std::move(newPositions);
|
||||
@@ -62,7 +62,7 @@ void Polyomino::rotateCW() {
|
||||
|
||||
void Polyomino::rotate180() {
|
||||
std::set<Position> newPositions;
|
||||
for (Position position : this->positions) {
|
||||
for (const Position position : this->positions) {
|
||||
newPositions.insert(Position{(length - 1) - (position.x), (length - 1) - (position.y)});
|
||||
}
|
||||
this->positions = std::move(newPositions);
|
||||
@@ -70,7 +70,7 @@ void Polyomino::rotate180() {
|
||||
|
||||
void Polyomino::rotateCCW() {
|
||||
std::set<Position> newPositions;
|
||||
for (Position position : this->positions) {
|
||||
for (const Position position : this->positions) {
|
||||
newPositions.insert(Position{(length - 1) - (position.y), position.x});
|
||||
}
|
||||
this->positions = std::move(newPositions);
|
||||
@@ -89,7 +89,7 @@ void Polyomino::goToSpawnPosition() {
|
||||
}
|
||||
|
||||
// calculates amount of squares per rows and columns
|
||||
for (Position position : this->positions) {
|
||||
for (const Position position : this->positions) {
|
||||
linesCompleteness.at(0).at(position.y) += 1; // 0 = bottom to top = no rotation
|
||||
linesCompleteness.at(1).at((length - 1) - position.x) += 1; // 1 = right to left = CW
|
||||
linesCompleteness.at(2).at((length - 1) - position.y) += 1; // 2 = top to bottom = 180
|
||||
@@ -158,14 +158,14 @@ void Polyomino::goToSpawnPosition() {
|
||||
|
||||
int minX = INT_MAX;
|
||||
int minY = INT_MAX;
|
||||
for (Position position : this->positions) {
|
||||
for (const Position position : this->positions) {
|
||||
if (position.x < minX) minX = position.x;
|
||||
if (position.y < minY) minY = position.y;
|
||||
}
|
||||
|
||||
// center the piece with an up bias
|
||||
std::set<Position> newPositions;
|
||||
for (Position position : positions) {
|
||||
for (const Position position : positions) {
|
||||
newPositions.insert(Position{(position.x - minX) + (verticalEmptyLines / 2), (position.y - minY) + ((horizontalEmptyLines + 1) / 2)});
|
||||
}
|
||||
this->positions = std::move(newPositions);
|
||||
|
||||
@@ -251,7 +251,7 @@ void TextApp::printGame(const Game& game) const {
|
||||
for (int x = 0; x < game.getBoard().getWidth(); x++) {
|
||||
/* BOARD PRINTING */
|
||||
bool isActivePieceHere = (game.getActivePiece() != nullptr) && (game.getActivePiece()->getPositions().contains(Position{x, y} - game.getActivePiecePosition()));
|
||||
bool isGhostPieceHere = (game.getActivePiece() != nullptr) && (game.getActivePiece()->getPositions().contains(Position{x, y} - game.ghostPiecePosition()));
|
||||
bool isGhostPieceHere = (game.getActivePiece() != nullptr) && (game.getActivePiece()->getPositions().contains(Position{x, y} - game.getGhostPiecePosition()));
|
||||
Block block = (isActivePieceHere || isGhostPieceHere) ? game.getActivePiece()->getBlockType() : game.getBoard().getBlock(Position{x, y});
|
||||
|
||||
if (isActivePieceHere || isGhostPieceHere) {
|
||||
|
||||
@@ -3,45 +3,61 @@
|
||||
#include "TextApp.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <cmath>
|
||||
|
||||
static const int MAXIMUM_PIECES_SIZE = 10;
|
||||
static const int BENCHMARK_PIECES_SIZE = 15;
|
||||
|
||||
|
||||
void testGeneratorForAllSizes(int amount);
|
||||
void testGeneratorForAllSizes(int max_size);
|
||||
void testGeneratorForOneSize(int size);
|
||||
void testGeneratorByprintingAllNminos(int n);
|
||||
void testStoringAndRetrievingPieces(int size);
|
||||
void generateFilesForAllSizes(int amount);
|
||||
void generateFilesForOneSize(int size);
|
||||
void loadFromFilesForOneSize(int size);
|
||||
void readStatsFromFilesForAllSizes(int amount);
|
||||
void printPiecesByTypesForOneSize(int size);
|
||||
void readStatsFromFilesForAllSizes(int max_size);
|
||||
|
||||
void benchmarking(int min_size, int max_size);
|
||||
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
std::srand(std::time(NULL));
|
||||
|
||||
// dev: generate files if it hasn't been done before, UI will NOT generate the files
|
||||
//generateFilesForAllSizes(10);
|
||||
#ifdef BENCHMARK
|
||||
#ifdef DEBUG
|
||||
std::cout << "IMPORTANT: You are currently in debug mode, debug mode has lowest optimization settings and thus yields worse benchmarking results, to switch to release mode, type 'xmake f -m debug'." << std::endl;
|
||||
#endif
|
||||
|
||||
TextApp UI;
|
||||
UI.run();
|
||||
benchmarking(1, BENCHMARK_PIECES_SIZE);
|
||||
#else
|
||||
PiecesFiles pf;
|
||||
for (int i = 1; i <= MAXIMUM_PIECES_SIZE; i++) {
|
||||
if (!std::filesystem::exists("data/pieces/" + std::to_string(i) + "minos.bin")) {
|
||||
std::cout << "INFO: Pieces files for size " << i << " not found, generating..." << std::endl;
|
||||
pf.savePieces(i);
|
||||
}
|
||||
}
|
||||
|
||||
TextApp UI;
|
||||
UI.run();
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void testGeneratorForAllSizes(int amount) {
|
||||
void testGeneratorForAllSizes(int max_size) {
|
||||
using std::chrono::high_resolution_clock;
|
||||
using std::chrono::duration_cast;
|
||||
using std::chrono::duration;
|
||||
using std::chrono::milliseconds;
|
||||
Generator generator;
|
||||
|
||||
for (int i = 1; i <= amount; i++) {
|
||||
for (int i = 1; i <= max_size; i++) {
|
||||
auto t1 = high_resolution_clock::now();
|
||||
std::vector<Polyomino> n_minos = generator.generatePolyominoes(i);
|
||||
auto t2 = high_resolution_clock::now();
|
||||
|
||||
duration<double, std::milli> ms_double = t2 - t1;
|
||||
std::cout << "generated " << n_minos.size() << " polyominoes of size " << i << " in " << ms_double.count() << "ms" << std::endl;
|
||||
std::cout << "Generated " << n_minos.size() << " polyominoes of size " << i << " in " << ms_double.count() << "ms" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,24 +79,8 @@ void testGeneratorForOneSize(int size) {
|
||||
}
|
||||
}
|
||||
|
||||
void testGeneratorByprintingAllNminos(int n) {
|
||||
Generator generator;
|
||||
std::vector<Polyomino> n_minos = generator.generatePolyominoes(n);
|
||||
|
||||
for (Polyomino& n_mino : n_minos) {
|
||||
n_mino.goToSpawnPosition();
|
||||
}
|
||||
std::sort(n_minos.begin(), n_minos.end());
|
||||
|
||||
for (Polyomino& n_mino : n_minos) {
|
||||
|
||||
std::cout << n_mino << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void testStoringAndRetrievingPieces(int size) {
|
||||
void printPiecesByTypesForOneSize(int size) {
|
||||
PiecesFiles piecesFiles;
|
||||
piecesFiles.savePieces(size);
|
||||
|
||||
std::vector<Piece> pieces;
|
||||
std::vector<int> convexPieces;
|
||||
@@ -104,79 +104,9 @@ void testStoringAndRetrievingPieces(int size) {
|
||||
}
|
||||
}
|
||||
|
||||
void generateFilesForAllSizes(int amount) {
|
||||
using std::chrono::high_resolution_clock;
|
||||
using std::chrono::duration_cast;
|
||||
using std::chrono::duration;
|
||||
using std::chrono::milliseconds;
|
||||
void readStatsFromFilesForAllSizes(int max_size) {
|
||||
PiecesFiles piecesFiles;
|
||||
|
||||
for (int i = 1; i <= amount; i++) {
|
||||
auto t1 = high_resolution_clock::now();
|
||||
piecesFiles.savePieces(i);
|
||||
auto t2 = high_resolution_clock::now();
|
||||
|
||||
duration<double, std::milli> ms_double = t2 - t1;
|
||||
std::cout << "Generated pieces files for size " << i << " in " << ms_double.count() << "ms" << std::endl;
|
||||
}
|
||||
|
||||
std::vector<Piece> pieces;
|
||||
std::vector<int> convexPieces;
|
||||
std::vector<int> holelessPieces;
|
||||
std::vector<int> otherPieces;
|
||||
for (int i = 1; i <= amount; i++) {
|
||||
auto t1 = high_resolution_clock::now();
|
||||
piecesFiles.loadPieces(i, pieces, convexPieces, holelessPieces, otherPieces);
|
||||
auto t2 = high_resolution_clock::now();
|
||||
|
||||
duration<double, std::milli> ms_double = t2 - t1;
|
||||
std::cout << "Read pieces from files for size " << i << " in " << ms_double.count() << "ms" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void generateFilesForOneSize(int size) {
|
||||
using std::chrono::high_resolution_clock;
|
||||
using std::chrono::duration_cast;
|
||||
using std::chrono::duration;
|
||||
using std::chrono::milliseconds;
|
||||
PiecesFiles piecesFiles;
|
||||
|
||||
std::cout << "Generating " << size << "-minos files" << std::endl;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
auto t1 = high_resolution_clock::now();
|
||||
piecesFiles.savePieces(size);
|
||||
auto t2 = high_resolution_clock::now();
|
||||
|
||||
duration<double, std::milli> ms_double = t2 - t1;
|
||||
std::cout << ms_double.count() << "ms" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void loadFromFilesForOneSize(int size) {
|
||||
using std::chrono::high_resolution_clock;
|
||||
using std::chrono::duration_cast;
|
||||
using std::chrono::duration;
|
||||
using std::chrono::milliseconds;
|
||||
PiecesFiles piecesFiles;
|
||||
|
||||
std::vector<Piece> pieces;
|
||||
std::vector<int> convexPieces;
|
||||
std::vector<int> holelessPieces;
|
||||
std::vector<int> otherPieces;
|
||||
std::cout << "Loading " << size << "-minos from files" << std::endl;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
auto t1 = high_resolution_clock::now();
|
||||
piecesFiles.loadPieces(size, pieces, convexPieces, holelessPieces, otherPieces);
|
||||
auto t2 = high_resolution_clock::now();
|
||||
|
||||
duration<double, std::milli> ms_double = t2 - t1;
|
||||
std::cout << ms_double.count() << "ms" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void readStatsFromFilesForAllSizes(int amount) {
|
||||
PiecesFiles piecesFiles;
|
||||
for (int i = 1; i <= amount; i++) {
|
||||
for (int i = 1; i <= max_size; i++) {
|
||||
std::vector<Piece> pieces;
|
||||
std::vector<int> convexPieces;
|
||||
std::vector<int> holelessPieces;
|
||||
@@ -189,3 +119,66 @@ void readStatsFromFilesForAllSizes(int amount) {
|
||||
std::cout << "Others " << i << "-minos : " << otherPieces.size() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void benchmarking(int min_size, int max_size) {
|
||||
using std::chrono::high_resolution_clock;
|
||||
using std::chrono::duration_cast;
|
||||
using std::chrono::duration;
|
||||
using std::chrono::milliseconds;
|
||||
|
||||
std::cout << "| n | Number | Generation | File storing | File retrieving | File size |" << std::endl;
|
||||
std::cout << "| -: | -: | :-: | :-: | :-: | -: |" << std::endl;
|
||||
|
||||
Generator gen;
|
||||
PiecesFiles pf;
|
||||
|
||||
for (int i = min_size; i <= max_size; i++) {
|
||||
std::cout << "| " << i;
|
||||
|
||||
auto t1 = high_resolution_clock::now();
|
||||
std::vector<Polyomino> polyominoes = gen.generatePolyominoes(i);
|
||||
auto t2 = high_resolution_clock::now();
|
||||
duration<double, std::milli> ms_double = t2 - t1;
|
||||
std::cout << " | " << polyominoes.size();
|
||||
std::flush(std::cout);
|
||||
std::cout << " | " << (int) ms_double.count() / 1000 << "s " << std::fmod(ms_double.count(), 1000) << "ms ";
|
||||
std::flush(std::cout);
|
||||
|
||||
t1 = high_resolution_clock::now();
|
||||
pf.savePieces(i, polyominoes);
|
||||
t2 = high_resolution_clock::now();
|
||||
ms_double = t2 - t1;
|
||||
std::cout << " | " << (int) ms_double.count() / 1000 << "s " << std::fmod(ms_double.count(), 1000) << "ms ";
|
||||
std::flush(std::cout);
|
||||
|
||||
polyominoes.clear();
|
||||
polyominoes.shrink_to_fit();
|
||||
|
||||
std::vector<Piece> pieces;
|
||||
std::vector<int> convexPieces;
|
||||
std::vector<int> holelessPieces;
|
||||
std::vector<int> otherPieces;
|
||||
|
||||
t1 = high_resolution_clock::now();
|
||||
pf.loadPieces(i, pieces, convexPieces, holelessPieces, otherPieces);
|
||||
t2 = high_resolution_clock::now();
|
||||
ms_double = t2 - t1;
|
||||
std::cout << " | " << (int) ms_double.count() / 1000 << "s " << std::fmod(ms_double.count(), 1000) << "ms ";
|
||||
std::flush(std::cout);
|
||||
|
||||
pieces.clear();
|
||||
pieces.shrink_to_fit();
|
||||
convexPieces.clear();
|
||||
convexPieces.shrink_to_fit();
|
||||
holelessPieces.clear();
|
||||
holelessPieces.shrink_to_fit();
|
||||
otherPieces.clear();
|
||||
otherPieces.shrink_to_fit();
|
||||
|
||||
std::string filePath;
|
||||
pf.getFilePath(i, filePath);
|
||||
int fileSize = std::filesystem::file_size(filePath);
|
||||
std::cout << " | " << fileSize << " bytes |" << std::endl;
|
||||
std::flush(std::cout);
|
||||
}
|
||||
}
|
||||
|
||||
97
src/Utils/AssetManager.cpp
Normal file
@@ -0,0 +1,97 @@
|
||||
#include "./AssetManager.h"
|
||||
|
||||
#include <map>
|
||||
|
||||
static const unsigned char data_fonts_pressstart_prstart_ttf[] = {
|
||||
#include <data/fonts/pressstart/prstart.ttf.h>
|
||||
};
|
||||
|
||||
static const unsigned char data_fonts_pressstart_prstartk_ttf[] = {
|
||||
#include <data/fonts/pressstart/prstartk.ttf.h>
|
||||
};
|
||||
|
||||
static const unsigned char data_images_keybinds_Rotate180_png[] = {
|
||||
#include <data/images/keybinds/Rotate180.png.h>
|
||||
};
|
||||
|
||||
static const unsigned char data_images_keybinds_Rotate0_png[] = {
|
||||
#include <data/images/keybinds/Rotate0.png.h>
|
||||
};
|
||||
|
||||
static const unsigned char data_images_keybinds_RotateCCW_png[] = {
|
||||
#include <data/images/keybinds/RotateCCW.png.h>
|
||||
};
|
||||
|
||||
static const unsigned char data_images_keybinds_Retry_png[] = {
|
||||
#include <data/images/keybinds/Retry.png.h>
|
||||
};
|
||||
|
||||
static const unsigned char data_images_keybinds_RotateCW_png[] = {
|
||||
#include <data/images/keybinds/RotateCW.png.h>
|
||||
};
|
||||
|
||||
static const unsigned char data_images_keybinds_Moveright_png[] = {
|
||||
#include <data/images/keybinds/Moveright.png.h>
|
||||
};
|
||||
|
||||
static const unsigned char data_images_keybinds_Harddrop_png[] = {
|
||||
#include <data/images/keybinds/Harddrop.png.h>
|
||||
};
|
||||
|
||||
static const unsigned char data_images_keybinds_Moveleft_png[] = {
|
||||
#include <data/images/keybinds/Moveleft.png.h>
|
||||
};
|
||||
|
||||
static const unsigned char data_images_keybinds_Hold_png[] = {
|
||||
#include <data/images/keybinds/Hold.png.h>
|
||||
};
|
||||
|
||||
static const unsigned char data_images_keybinds_Softdrop_png[] = {
|
||||
#include <data/images/keybinds/Softdrop.png.h>
|
||||
};
|
||||
|
||||
static const unsigned char data_images_keybinds_Pause_png[] = {
|
||||
#include <data/images/keybinds/Pause.png.h>
|
||||
};
|
||||
|
||||
static const Asset assets[] = {
|
||||
{data_fonts_pressstart_prstart_ttf, sizeof(data_fonts_pressstart_prstart_ttf)},
|
||||
{data_fonts_pressstart_prstartk_ttf, sizeof(data_fonts_pressstart_prstartk_ttf)},
|
||||
{data_images_keybinds_Rotate180_png, sizeof(data_images_keybinds_Rotate180_png)},
|
||||
{data_images_keybinds_Rotate0_png, sizeof(data_images_keybinds_Rotate0_png)},
|
||||
{data_images_keybinds_RotateCCW_png, sizeof(data_images_keybinds_RotateCCW_png)},
|
||||
{data_images_keybinds_Retry_png, sizeof(data_images_keybinds_Retry_png)},
|
||||
{data_images_keybinds_RotateCW_png, sizeof(data_images_keybinds_RotateCW_png)},
|
||||
{data_images_keybinds_Moveright_png, sizeof(data_images_keybinds_Moveright_png)},
|
||||
{data_images_keybinds_Harddrop_png, sizeof(data_images_keybinds_Harddrop_png)},
|
||||
{data_images_keybinds_Moveleft_png, sizeof(data_images_keybinds_Moveleft_png)},
|
||||
{data_images_keybinds_Hold_png, sizeof(data_images_keybinds_Hold_png)},
|
||||
{data_images_keybinds_Softdrop_png, sizeof(data_images_keybinds_Softdrop_png)},
|
||||
{data_images_keybinds_Pause_png, sizeof(data_images_keybinds_Pause_png)},
|
||||
|
||||
};
|
||||
|
||||
static const std::map<std::string, AssetName> assetMap = {
|
||||
{"data/fonts/pressstart/prstart.ttf", AssetName::data_fonts_pressstart_prstart_ttf},
|
||||
{"data/fonts/pressstart/prstartk.ttf", AssetName::data_fonts_pressstart_prstartk_ttf},
|
||||
{"data/images/keybinds/Rotate180.png", AssetName::data_images_keybinds_Rotate180_png},
|
||||
{"data/images/keybinds/Rotate0.png", AssetName::data_images_keybinds_Rotate0_png},
|
||||
{"data/images/keybinds/RotateCCW.png", AssetName::data_images_keybinds_RotateCCW_png},
|
||||
{"data/images/keybinds/Retry.png", AssetName::data_images_keybinds_Retry_png},
|
||||
{"data/images/keybinds/RotateCW.png", AssetName::data_images_keybinds_RotateCW_png},
|
||||
{"data/images/keybinds/Moveright.png", AssetName::data_images_keybinds_Moveright_png},
|
||||
{"data/images/keybinds/Harddrop.png", AssetName::data_images_keybinds_Harddrop_png},
|
||||
{"data/images/keybinds/Moveleft.png", AssetName::data_images_keybinds_Moveleft_png},
|
||||
{"data/images/keybinds/Hold.png", AssetName::data_images_keybinds_Hold_png},
|
||||
{"data/images/keybinds/Softdrop.png", AssetName::data_images_keybinds_Softdrop_png},
|
||||
{"data/images/keybinds/Pause.png", AssetName::data_images_keybinds_Pause_png},
|
||||
|
||||
};
|
||||
|
||||
const Asset& getResource(AssetName fileName) {
|
||||
return assets[static_cast<std::size_t>(fileName)];
|
||||
}
|
||||
|
||||
const Asset& getResource(const std::string& fileName) {
|
||||
return getResource(assetMap.at(fileName));
|
||||
}
|
||||
30
src/Utils/AssetManager.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
struct Asset {
|
||||
const unsigned char* data;
|
||||
std::size_t size;
|
||||
};
|
||||
|
||||
enum class AssetName {
|
||||
data_fonts_pressstart_prstart_ttf,
|
||||
data_fonts_pressstart_prstartk_ttf,
|
||||
data_images_keybinds_Rotate180_png,
|
||||
data_images_keybinds_Rotate0_png,
|
||||
data_images_keybinds_RotateCCW_png,
|
||||
data_images_keybinds_Retry_png,
|
||||
data_images_keybinds_RotateCW_png,
|
||||
data_images_keybinds_Moveright_png,
|
||||
data_images_keybinds_Harddrop_png,
|
||||
data_images_keybinds_Moveleft_png,
|
||||
data_images_keybinds_Hold_png,
|
||||
data_images_keybinds_Softdrop_png,
|
||||
data_images_keybinds_Pause_png,
|
||||
|
||||
};
|
||||
|
||||
const Asset& getResource(AssetName fileName);
|
||||
|
||||
const Asset& getResource(const std::string& fileName);
|
||||
37
xmake.lua
@@ -1,30 +1,53 @@
|
||||
add_rules("mode.debug", "mode.release")
|
||||
|
||||
add_requires("sfml 3.0.0")
|
||||
includes("xmake/bin2c.lua")
|
||||
|
||||
add_requires("sfml 3.0.0", "zlib")
|
||||
|
||||
set_languages("c++20")
|
||||
|
||||
set_rundir(".")
|
||||
|
||||
set_optimize("fastest")
|
||||
|
||||
target("core")
|
||||
set_kind("$(kind)")
|
||||
add_files("src/Pieces/*.cpp")
|
||||
add_files("src/Core/*.cpp")
|
||||
add_files("src/Pieces/*.cpp", "src/Core/*.cpp", "src/Common/*.cpp")
|
||||
add_packages("zlib")
|
||||
|
||||
target("text")
|
||||
set_kind("binary")
|
||||
set_default(false)
|
||||
set_kind("binary")
|
||||
add_files("./src/TextUI/*.cpp")
|
||||
add_deps("core")
|
||||
|
||||
|
||||
target("bmark")
|
||||
set_default(false)
|
||||
set_kind("binary")
|
||||
add_files("./src/TextUI/*.cpp")
|
||||
add_deps("core")
|
||||
add_defines("BENCHMARK")
|
||||
|
||||
target("graph")
|
||||
set_default(true)
|
||||
add_rules("bin2c", {
|
||||
extensions = {".png", ".ttf"},
|
||||
outputSource = {"src/Utils/AssetManager.cpp"},
|
||||
outputHeader = {"src/Utils/AssetManager.h"}
|
||||
})
|
||||
set_kind("binary")
|
||||
add_files("./src/GraphicalUI/**.cpp")
|
||||
add_files("data/fonts/**.ttf", "data/images/**.png")
|
||||
add_deps("core")
|
||||
add_packages("sfml")
|
||||
|
||||
if is_mode("debug") then
|
||||
add_defines("DEBUG")
|
||||
end
|
||||
|
||||
if is_plat("mingw") then
|
||||
add_ldflags("-static-libstdc++", "-static")
|
||||
end
|
||||
|
||||
|
||||
--
|
||||
-- If you want to known more usage about xmake, please see https://xmake.io
|
||||
--
|
||||
|
||||
124
xmake/bin2c.lua
Normal file
@@ -0,0 +1,124 @@
|
||||
rule("bin2c")
|
||||
set_extensions(".bin")
|
||||
|
||||
on_load(function (target)
|
||||
local headerdir = path.join(target:autogendir(), "rules", "bin2c")
|
||||
|
||||
local outputSource = table.unpack(target:extraconf("rules", "bin2c", "outputSource"))
|
||||
|
||||
if not os.isdir(headerdir) then
|
||||
os.mkdir(headerdir)
|
||||
end
|
||||
|
||||
target:add("includedirs", headerdir)
|
||||
|
||||
target:add("files", outputSource)
|
||||
end)
|
||||
|
||||
before_buildcmd_files(function (target, batchcmds, sourcebatch, opt)
|
||||
local outputHeader = table.unpack(target:extraconf("rules", "bin2c", "outputHeader"))
|
||||
|
||||
local outputHeaderEnumContent = ""
|
||||
|
||||
for _, filePath in ipairs(sourcebatch.sourcefiles) do
|
||||
local escapedName = string.gsub(filePath, "[/|.]", "_")
|
||||
outputHeaderEnumContent = outputHeaderEnumContent .. "\t" .. escapedName .. ",\n"
|
||||
end
|
||||
|
||||
local outputHeaderContent = string.format([[
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
struct Asset {
|
||||
const unsigned char* data;
|
||||
std::size_t size;
|
||||
};
|
||||
|
||||
enum class AssetName {
|
||||
%s
|
||||
};
|
||||
|
||||
const Asset& getResource(AssetName fileName);
|
||||
|
||||
const Asset& getResource(const std::string& fileName);
|
||||
]], outputHeaderEnumContent)
|
||||
|
||||
local outputSource = table.unpack(target:extraconf("rules", "bin2c", "outputSource"))
|
||||
|
||||
local relativePath = path.join(path.relative(path.directory(outputHeader), path.directory(outputSource)), path.filename(outputHeader))
|
||||
|
||||
local outputSourceContent = string.format([[
|
||||
#include "%s"
|
||||
|
||||
#include <map>
|
||||
|
||||
]], relativePath)
|
||||
|
||||
|
||||
local outputSourceArrayVars = ""
|
||||
local outputSourceMapVars = ""
|
||||
|
||||
for _, filePath in ipairs(sourcebatch.sourcefiles) do
|
||||
local escapedName = string.gsub(filePath, "[/|.]", "_")
|
||||
local varDecl = string.format("static const unsigned char %s[] = {\n\t#include <%s>\n};\n\n", escapedName, filePath .. ".h")
|
||||
outputSourceContent = outputSourceContent .. varDecl
|
||||
outputSourceArrayVars = outputSourceArrayVars .. string.format("\t{%s, sizeof(%s)},\n", escapedName, escapedName)
|
||||
outputSourceMapVars = outputSourceMapVars .. string.format("\t{\"%s\", AssetName::%s},\n", filePath, escapedName)
|
||||
end
|
||||
|
||||
outputSourceContent = outputSourceContent .. string.format([[
|
||||
static const Asset assets[] = {
|
||||
%s
|
||||
};
|
||||
|
||||
static const std::map<std::string, AssetName> assetMap = {
|
||||
%s
|
||||
};
|
||||
|
||||
]], outputSourceArrayVars, outputSourceMapVars)
|
||||
|
||||
outputSourceContent = outputSourceContent .. [[
|
||||
const Asset& getResource(AssetName fileName) {
|
||||
return assets[static_cast<std::size_t>(fileName)];
|
||||
}
|
||||
|
||||
const Asset& getResource(const std::string& fileName) {
|
||||
return getResource(assetMap.at(fileName));
|
||||
}
|
||||
]]
|
||||
|
||||
for _, sourcefile_bin in ipairs(sourcebatch.sourcefiles) do
|
||||
-- get header file
|
||||
local headerdir = path.join(target:autogendir(), "rules", "bin2c")
|
||||
local headerfile = path.join(headerdir, sourcefile_bin .. ".h")
|
||||
target:add("includedirs", headerdir)
|
||||
|
||||
-- add commands
|
||||
batchcmds:show_progress(opt.progress, "${color.build.object}generating.bin2c %s", sourcefile_bin)
|
||||
batchcmds:mkdir(headerdir)
|
||||
local argv = {"lua", "private.utils.bin2c", "-i", path(sourcefile_bin), "-o", path(headerfile)}
|
||||
local linewidth = target:extraconf("rules", "bin2c", "linewidth")
|
||||
if linewidth then
|
||||
table.insert(argv, "-w")
|
||||
table.insert(argv, tostring(linewidth))
|
||||
end
|
||||
local nozeroend = target:extraconf("rules", "bin2c", "nozeroend")
|
||||
if nozeroend then
|
||||
table.insert(argv, "--nozeroend")
|
||||
end
|
||||
batchcmds:vrunv(os.programfile(), argv, {envs = {XMAKE_SKIP_HISTORY = "y"}})
|
||||
|
||||
-- add deps
|
||||
batchcmds:add_depfiles(sourcefile_bin)
|
||||
batchcmds:set_depmtime(os.mtime(headerfile))
|
||||
batchcmds:set_depcache(target:dependfile(headerfile))
|
||||
|
||||
end
|
||||
|
||||
batchcmds:show_progress(opt.progress, "${color.build.object}generating.bin2c %s", outputHeader)
|
||||
io.writefile(outputHeader, outputHeaderContent)
|
||||
batchcmds:show_progress(opt.progress, "${color.build.object}generating.bin2c %s", outputSource)
|
||||
io.writefile(outputSource, outputSourceContent)
|
||||
end)
|
||||