1 Commits

Author SHA1 Message Date
6da3cb66fa changed InfoAppMenu 2025-05-23 23:57:13 +02:00
52 changed files with 400 additions and 1503 deletions

View File

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

2
.gitignore vendored
View File

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

100
README.md
View File

@@ -1,41 +1,28 @@
# jminos
Modern stacker game with every polyominoes from size 1 to 15, made in C++ with [SFML 3](https://www.sfml-dev.org/)!
- [Download](#download)
- [How to play](#how-to-play)
- [Features](#features)
- [Manual build](#manual-build)
- [Benchmarks](#benchmarks)
- [Credits](#credits)
Modern stacker game with every polyominos from size 1 to 15, made in C++ with [SFML 3](https://www.sfml-dev.org/)!
## Download
You can download the latest release [here](https://git.ale-pri.com/TetrisNerd/jminos/releases)!
This game has been tested on Windows 11 and Linux, and releases are provided for theses two systems.
If your OS isn't compatible, you can try [manually building the project](#manual-build).
// TODO when the game is finished //
This game has been tested on and built on Windows 11 and WSL2 Ubuntu only.
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.
If you want to know more details about the generation of polyominoes, [check the documentation](/doc/)!
## 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!
- Every polyominoes from size 1 to 15, selectable as you wish, with customizable propotionnality for each size!
- Customizable keybinds!
- 0° rotations!
- AutoRS as the Rotation System!
- IRS, IHS, infinite hold, and other leniency mechanics!
- Very bland interface!! (i'm not a designer)
### Available gamemodes
@@ -44,34 +31,17 @@ You will find more infos about the Rotation System, the scoring system, or the d
- 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
![Screenshot1](./doc/readme/big_piece.png)
Pieces select screen
![Screenshot2](./doc/readme/pieces_selection.png)
AutoRS demonstration
![Video1](./doc/readme/rotations.gif)
0° spins demonstration
![Video2](./doc/readme/rotation_0.gif)
// TODO when the game is finished //
## Manual build
This project uses xmake for compiling, xmake is cross-platform and works in most OS, xmake also automatically install supported librairies.
To be able to build this project, you need to [have xmake installed](https://xmake.io) and have a compiler with C++20 compatibility.
If you want to contribute or are simply curious, you can check the [wiki](https://git.ale-pri.com/TetrisNerd/jminos/wiki)!
### Build the project
``cd jminos``
@@ -81,9 +51,6 @@ If you want to contribute or are simply curious, you can check the [wiki](https:
If you need to change the toolchain (for example using gcc):
``xmake f --toolchain=gcc``
If you want to build for another platform (for example with mingw):
``xmake f -p mingw``
### Run the project
``xmake run``
@@ -94,50 +61,12 @@ To switch between debug and release mode:
``xmake f -m debug``
``xmake f -m release``
If for some reasons you wanna run the command line version:
``xmake build text``
If for some reasons you wanna run the command line version (not updated):
``xmake run text``
The command line version is **not** updated.
### Package the project
To package the executable manually properly, follow theses steps:
1. Clone the project into a new folder.
2. If you already have all 15 pieces files, copy them over to the ``data/pieces`` folder, else you will be generating them at the next step.
3. Go into release mode (``xmake f -m release``) and run the project once (``xmake`` and ``xmake run``). This is to generate the default config files, close the game immediately after it started.
4. Make a new folder named ``jminos`` and copy the executable (should be located somewhere like ``build/linux/x86_64/release/graph``) as well as the ``data/pieces`` and ``data/config`` folders.
5. Zip the newly created ``jminos`` folder into a file named ``jminos_{platform}``.
## Benchmarks
### One-sided n-polyominoes
| n | Number | Generation | File storing | File retrieving | File size |
| -: | -: | :-: | :-: | :-: | -: |
| 1 | 1 | 0s 0.005471ms | 0s 0.14436ms | 0s 0.022223ms | 3 bytes |
| 2 | 1 | 0s 0.006979ms | 0s 0.036624ms | 0s 0.011424ms | 4 bytes |
| 3 | 2 | 0s 0.018718ms | 0s 0.035885ms | 0s 0.013246ms | 9 bytes |
| 4 | 7 | 0s 0.060544ms | 0s 0.056277ms | 0s 0.019395ms | 36 bytes |
| 5 | 18 | 0s 0.220348ms | 0s 0.166593ms | 0s 0.036526ms | 76 bytes |
| 6 | 60 | 0s 0.773924ms | 0s 0.283423ms | 0s 0.063492ms | 186 bytes |
| 7 | 196 | 0s 3.00331ms | 0s 0.827344ms | 0s 0.163653ms | 546 bytes |
| 8 | 704 | 0s 13.142ms | 0s 3.68255ms | 0s 0.630044ms | 1898 bytes |
| 9 | 2500 | 0s 50.9272ms | 0s 16.1929ms | 0s 2.35157ms | 6889 bytes |
| 10 | 9189 | 0s 204.031ms | 0s 87.1819ms | 0s 10.5841ms | 25302 bytes |
| 11 | 33896 | 0s 832.82ms | 0s 412.466ms | 0s 57.6399ms | 93711 bytes |
| 12 | 126759 | 3s 425.907ms | 1s 982.715ms | 0s 226.816ms | 350325 bytes |
| 13 | 476270 | 14s 570.595ms | 9s 945.511ms | 0s 972.036ms | 1327156 bytes |
| 14 | 1802312 | 56s 394.426ms | 41s 675.672ms | 4s 79.0436ms | 5035148 bytes |
| 15 | 6849777 | 258s 219.666ms | 223s 386.329ms | 16s 483.426ms | 19392417 bytes |
_File storing includes type checking and sorting all polyominoes before writing them to the file._
The files are compressed, they used to be about 5x as large.
Run it yourself by typing:
``xmake f -m release``
``xmake build bmark``
``xmake run bmark``
// TODO when the game is finished //
## Credits
@@ -146,6 +75,3 @@ Font used: [Press Start](https://www.zone38.net/font/#pressstart).
Inspired by other modern stacker games such as Techmino, jstris, tetr.io, etc.
This game isn't affiliated with any of them.
Special thanks to my friend [Simon](https://git.ale-pri.com/Persson-dev) who did most of the outside stuff (github actions, files compression, asset manager, xmake).
All the code in src/Common/, src/Utils/ and xmake/ comes from him.

View File

Before

Width:  |  Height:  |  Size: 164 KiB

After

Width:  |  Height:  |  Size: 164 KiB

View File

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -2,11 +2,11 @@
## Pieces
_Note: the current algorithm has been adapted to use file compression. There is currently no documentation on how the compression work, but you can check the code in the [src/Common](https://git.ale-pri.com/TetrisNerd/jminos/src/branch/main/src/Common) folder._
If you don't know what a polyomino is, check [this other file](Pieces_representation.md#what-are-polyominoes).
Generating polyominoes of size n is exponential in regard to n. Because of this, we will store the pieces beforehand and load them upon launching the game.
We want the pieces to be always sorted in the same order, always attributed the same block type, and always set at the same spawn position, no matter how they were generated. We also want them to be separated in 3 categories : convex, not convex but without a hole, and with a hole. Theses problematics are already solved internally, but will be calculated before storage as to not need extra calculcations upon load (except for the block type which is trivially computed).
We want the pieces to be always sorted in the same order, always attributed the same block type, and always set at the same spawn position, no matter how they were generated. We also want them to be separated in 3 categories : convex, not convex but without a hole, and with a hole. Theses problematics are already resolved internally, but will be calculated before storage as to not need extra calculcations upon load (except for the block type which is trivially computed).
Pieces are stored in binary files. Each file simply contains every polyomino of one size, one after the other. Since each file contains all polyominoes of the same size, we know how much stuff to read and don't need delimiters. We know we've read all pieces simply when we reach the end of file character.
@@ -55,4 +55,5 @@ The settings file has the following format:
- For every single piece, use 1 byte for (the size + encoding that it is a single piece),
and 3 bytes to store the number of the piece (allows number up to 16M, size 15 has 6M pieces).
When starting the game, it will verify if the current settings file is in the latest format, if it is not it will be regenerated with default values.
The current file format version is 11.
If the file starts with a number lower than that, it will be regenerated upon launching the game.

View File

@@ -1,7 +1,7 @@
# Game logic
This section will detail how the player's action are interpreted into the game.
We will only talk about pieces and not polyominoes. In this project, pieces are an abstraction of a polyomino.
We will only talk about pieces and not polyominoes. In this project, pieces are an abstraction of a polyomino. Though if you want to know more the polyominoes in this project, check [this other file](Pieces_representation.md).
## Order of operations

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 614 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 779 KiB

View File

@@ -1,102 +0,0 @@
#include "../Pieces/Generator.h"
#include "../Pieces/PiecesFiles.h"
#include <chrono>
#include <filesystem>
#include <cmath>
static const int BENCHMARK_PIECES_SIZE = 15;
void benchmarking(int min_size, int max_size);
int main(int argc, char** argv) {
std::srand(std::time(NULL));
#ifdef DEBUG
std::cout << "IMPORTANT: You are currently in debug mode, debug mode has lowest optimization settings and thus yields worse benchmarking results, to switch to release mode, type 'xmake f -m debug'." << std::endl;
#endif
benchmarking(1, BENCHMARK_PIECES_SIZE);
return 0;
}
void benchmarking(int min_size, int max_size) {
using std::chrono::high_resolution_clock;
using std::chrono::duration_cast;
using std::chrono::duration;
using std::chrono::milliseconds;
std::cout << "| n | Number | Generation | File storing | File retrieving | File size |" << std::endl;
std::cout << "| -: | -: | :-: | :-: | :-: | -: |" << std::endl;
Generator gen;
PiecesFiles pf;
auto t1 = high_resolution_clock::now();
auto t2 = high_resolution_clock::now();
duration<double, std::milli> ms_double;
bool ok;
for (int i = min_size; i <= max_size; i++) {
std::cout << "| " << i;
t1 = high_resolution_clock::now();
std::vector<Polyomino> polyominoes = gen.generatePolyominoes(i);
t2 = high_resolution_clock::now();
ms_double = t2 - t1;
std::cout << " | " << polyominoes.size();
std::flush(std::cout);
std::cout << " | " << (int) ms_double.count() / 1000 << "s " << std::fmod(ms_double.count(), 1000) << "ms";
std::flush(std::cout);
t1 = high_resolution_clock::now();
ok = pf.savePieces(i, polyominoes);
t2 = high_resolution_clock::now();
ms_double = t2 - t1;
std::cout << " | " << (int) ms_double.count() / 1000 << "s " << std::fmod(ms_double.count(), 1000) << "ms";
std::flush(std::cout);
if (!ok) {
std::cerr << "\nERROR: Could not save pieces to file." << std::endl;
return;
}
polyominoes.clear();
polyominoes.shrink_to_fit();
std::vector<Piece> pieces;
std::vector<int> convexPieces;
std::vector<int> holelessPieces;
std::vector<int> otherPieces;
t1 = high_resolution_clock::now();
ok = pf.loadPieces(i, pieces, convexPieces, holelessPieces, otherPieces);
t2 = high_resolution_clock::now();
ms_double = t2 - t1;
std::cout << " | " << (int) ms_double.count() / 1000 << "s " << std::fmod(ms_double.count(), 1000) << "ms";
std::flush(std::cout);
if (!ok) {
std::cerr << "\nERROR: Could not load pieces from file." << std::endl;
return;
}
pieces.clear();
pieces.shrink_to_fit();
convexPieces.clear();
convexPieces.shrink_to_fit();
holelessPieces.clear();
holelessPieces.shrink_to_fit();
otherPieces.clear();
otherPieces.shrink_to_fit();
std::string filePath;
pf.getFilePath(i, filePath);
int fileSize = std::filesystem::file_size(filePath);
std::cout << " | " << fileSize << " bytes |" << std::endl;
std::flush(std::cout);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -297,8 +297,8 @@ void Game::lockPiece() {
/* clearing one more line is worth 2x more
clearing with a spin is worth as much as clearing 2x more lines */
long int clearScore = LINE_CLEAR_BASE_SCORE / 2;
clearScore = clearScore << (clear.lines << clear.isSpin);
long int clearScore = LINE_CLEAR_BASE_SCORE;
clearScore = clearScore << (clear.lines << (clear.isSpin));
if (this->B2BChain && B2BConditionsAreMet) {
clearScore *= B2B_SCORE_MULTIPLIER;
@@ -366,10 +366,6 @@ bool Game::areBlocksBones() const {
return this->parameters.getBoneBlocks();
}
bool Game::isBoardInvisible() const {
return this->parameters.getInvisibleBoard();
}
const Board& Game::getBoard() const {
return this->board.getBoard();
}

View File

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

View File

@@ -24,8 +24,6 @@ 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;
@@ -36,7 +34,7 @@ void GameParameters::reset() {
void GameParameters::lockedPiece(const LineClear& lineClear) {
switch (this->gamemode) {
// modes where level increases with lines
// modes where level increases
case MARATHON :
case MASTER : {
int previousLines = this->clearedLines;
@@ -53,23 +51,9 @@ void GameParameters::lockedPiece(const LineClear& lineClear) {
default : this->clearedLines += lineClear.lines;
}
int previousGrade = this->grade;
if (!((lineClear.lines == 0) && ((this->grade % 100) == 99))) {
this->grade += (1 + lineClear.lines);
}
switch (this->gamemode) {
// modes where level increases with grade
case INVISIBLE : {
if (previousGrade / 100 < this->grade / 100) {
this->level += 2;
this->updateStats();
}
break;
}
// other modes
default : break;
}
}
bool GameParameters::hasWon(int framesPassed) const {
@@ -82,8 +66,6 @@ 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;
@@ -102,8 +84,7 @@ void GameParameters::updateStats() {
}
// 3 for slow-controls gamemodes
case MARATHON :
case MASTER :
case INVISIBLE : {
case MASTER : {
this->nextQueueLength = 3;
break;
}
@@ -113,13 +94,10 @@ void GameParameters::updateStats() {
/* BONE BLOCKS */
switch (this->gamemode) {
// blocks turns into bone blocks at level 30
case MASTER : {this->boneBlocks = (this->level >= 30); break;}
case MASTER : this->boneBlocks = (this->level >= 30);
default : this->boneBlocks = false;
}
/* INVISIBLE */
this->invisibleBoard = (this->gamemode == INVISIBLE);
/* GRAVITY */
// get gravity for an assumed 20-rows board
static const int gravityPerLevel[] = {
@@ -174,8 +152,6 @@ 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;
}
@@ -252,10 +228,6 @@ bool GameParameters::getBoneBlocks() const {
return this->boneBlocks;
}
bool GameParameters::getInvisibleBoard() const {
return this->invisibleBoard;
}
int GameParameters::getGravity() const {
return this->gravity;
}

View File

@@ -18,7 +18,6 @@ class GameParameters {
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
@@ -78,15 +77,10 @@ class GameParameters {
int getNextQueueLength() const;
/**
* @return Wheter the blocks are currently bone blocks
* Returns wheter the blocks are currently bone blocks
*/
bool getBoneBlocks() const;
/**
* @return Wheter the board is currently invisible
*/
bool getInvisibleBoard() const;
/**
* @return The current gravity for a 20-line high board
*/

View File

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

View File

@@ -4,7 +4,7 @@
#include "Player.h"
#include "Game.h"
static const int FRAMES_PER_SECOND = 60; // the number of frames per second, all the values in the app were choosen with this number in mind
static const int FRAMES_PER_SECOND = 60; // the number of frames per second, all the values in the app were choosen with this number in mind
/**

View File

@@ -27,12 +27,13 @@ PiecesList::PiecesList() {
this->pushBackEmptyVectors();
}
bool PiecesList::loadPieces(int max_size) {
if (max_size < 1) return false;
if (max_size <= this->highestLoadedSize) return true;
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 <= max_size; i++) {
for (int i = this->highestLoadedSize + 1; i <= size; i++) {
if (!piecesFiles.loadPieces(i, this->loadedPieces.at(i), this->convexPieces.at(i), this->holelessPieces.at(i), this->otherPieces.at(i))) {
return false;
}

View File

@@ -30,10 +30,10 @@ class PiecesList {
PiecesList();
/**
* Makes the list load all pieces up to the specified size
* @return If all pieces up to the specified size are correctly loaded
* Makes the list load all pieces of the specified size
* @return If it sucessfully loaded the pieces
*/
[[nodiscard]] bool loadPieces(int max_size);
bool loadPieces(int size);
/**
* Selects the specified piece

View File

@@ -1,72 +0,0 @@
#include "AppMenu.h"
#include "../Settings.h"
#include "../PlayerCursor.h"
#include "../../Utils/AssetManager.h"
#include <stack>
#include <memory>
#include <optional>
#include <SFML/Graphics.hpp>
AppMenu::AppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow) :
menuStack(menuStack),
settings(settings),
renderWindow(renderWindow) {
const Asset& file = getResource(AssetName::data_fonts_pressstart_prstartk_ttf);
this->pressStartFont = sf::Font(file.data, file.size);
}
void AppMenu::updateMetaBinds() {
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Enter)) {
this->enterPressed = true;
this->enterReleased = false;
}
else {
this->enterReleased = this->enterPressed;
this->enterPressed = false;
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Escape)) {
this->escPressed = true;
this->escReleased = false;
}
else {
this->escReleased = this->escPressed;
this->escPressed = false;
}
}
void AppMenu::placeText(sf::Text& text, const std::optional<PlayerCursor>& playerCursor, const sf::String& string, float xPos, float yPos, const std::optional<sf::Vector2u>& cursorPos) const {
float sizeMultiplier = this->settings->getWindowSizeMultiplier();
text.setString(string);
if (playerCursor.has_value() && cursorPos.has_value()) {
text.setOutlineThickness((playerCursor.value().getPosition() == cursorPos.value()) ? (sizeMultiplier / 2) : 0);
}
text.setOrigin(sf::Vector2f({0, text.getLocalBounds().size.y / 2}));
text.setPosition(sf::Vector2f({sizeMultiplier * xPos, sizeMultiplier * yPos}));
this->renderWindow->draw(text);
}
void AppMenu::placeTitle(sf::Text& text, const std::optional<PlayerCursor>& playerCursor, const sf::String& string, float yPos, const std::optional<sf::Vector2u>& cursorPos) const {
float sizeMultiplier = this->settings->getWindowSizeMultiplier();
text.setString(string);
if (playerCursor.has_value() && cursorPos.has_value()) {
text.setOutlineThickness((playerCursor.value().getPosition() == cursorPos.value()) ? (sizeMultiplier / 2) : 0);
}
text.setOrigin({text.getLocalBounds().getCenter().x, text.getLocalBounds().size.y / 2});
text.setPosition(sf::Vector2f({sizeMultiplier * 40.f, sizeMultiplier * yPos}));
this->renderWindow->draw(text);
}
sf::Color AppMenu::getColorOfBlock(Block block, int luminosityShift) const {
Color rgbColor = BLOCKS_COLOR[block];
return sf::Color(std::clamp(rgbColor.red + luminosityShift, 0, 255),
std::clamp(rgbColor.green + luminosityShift, 0, 255),
std::clamp(rgbColor.blue + luminosityShift, 0, 255));
}

View File

@@ -21,21 +21,97 @@ class AppMenu {
bool enterReleased = false;
bool escPressed = false;
bool escReleased = false;
sf::Font pressStartFont;
sf::Font pressStartFont = sf::Font("data/fonts/pressstart/prstartk.ttf");
public:
AppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow);
AppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow) :
menuStack(menuStack),
settings(settings),
renderWindow(renderWindow)
{
}
virtual void computeFrame() = 0;
virtual void drawFrame() const = 0;
protected:
void updateMetaBinds();
void updateMetaBinds() {
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Enter)) {
enterPressed = true;
enterReleased = false;
}
else {
enterReleased = enterPressed;
enterPressed = false;
}
void placeText(sf::Text& text, const std::optional<PlayerCursor>& playerCursor, const sf::String& string, float xPos, float yPos, const std::optional<sf::Vector2u>& cursorPos) const;
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Escape)) {
escPressed = true;
escReleased = false;
}
else {
escReleased = escPressed;
escPressed = false;
}
}
void placeTitle(sf::Text& text, const std::optional<PlayerCursor>& playerCursor, const sf::String& string, float yPos, const std::optional<sf::Vector2u>& cursorPos) const;
sf::Text createText(int fontSize = 2) const {
sf::Text newText(this->pressStartFont, "", this->settings->getWindowSizeMultiplier() * fontSize);
newText.setFillColor(sf::Color::Black);
newText.setOutlineColor(sf::Color::White);
newText.setOutlineThickness(0);
sf::Color getColorOfBlock(Block block, int luminosityShift) const;
return newText;
}
void setTextPosition(sf::Text& text, float xPos, float yPos) const {
float sizeMultiplier = this->settings->getWindowSizeMultiplier();
text.setOrigin(sf::Vector2f({0, text.getLocalBounds().size.y / 2}));
text.setPosition(sf::Vector2f({sizeMultiplier * xPos, sizeMultiplier * yPos}));
}
void setTitlePosition(sf::Text& text, float yPos) const {
float sizeMultiplier = this->settings->getWindowSizeMultiplier();
text.setOrigin({text.getLocalBounds().getCenter().x, text.getLocalBounds().size.y / 2});
text.setPosition(sf::Vector2f({sizeMultiplier * 40.f, sizeMultiplier * yPos}));
}
void setTextOutline(sf::Text& text, bool hasOutline) const {
text.setOutlineThickness(hasOutline * (this->settings->getWindowSizeMultiplier() / 2));
}
void placeText(sf::Text& text, const std::optional<PlayerCursor>& playerCursor, const sf::String& string, float xPos, float yPos, const std::optional<sf::Vector2u>& cursorPos) const {
float sizeMultiplier = this->settings->getWindowSizeMultiplier();
text.setString(string);
if (playerCursor.has_value() && cursorPos.has_value()) {
text.setOutlineThickness((playerCursor.value().getPosition() == cursorPos.value()) ? (sizeMultiplier / 2) : 0);
}
text.setOrigin(sf::Vector2f({0, text.getLocalBounds().size.y / 2}));
text.setPosition(sf::Vector2f({sizeMultiplier * xPos, sizeMultiplier * yPos}));
this->renderWindow->draw(text);
}
void placeTitle(sf::Text& text, const std::optional<PlayerCursor>& playerCursor, const sf::String& string, float yPos, const std::optional<sf::Vector2u>& cursorPos) const {
float sizeMultiplier = this->settings->getWindowSizeMultiplier();
text.setString(string);
if (playerCursor.has_value() && cursorPos.has_value()) {
text.setOutlineThickness((playerCursor.value().getPosition() == cursorPos.value()) ? (sizeMultiplier / 2) : 0);
}
text.setOrigin({text.getLocalBounds().getCenter().x, text.getLocalBounds().size.y / 2});
text.setPosition(sf::Vector2f({sizeMultiplier * 40.f, sizeMultiplier * yPos}));
this->renderWindow->draw(text);
}
sf::Color getColorOfBlock(Block block, int luminosityShift) const {
Color rgbColor = BLOCKS_COLOR[block];
return sf::Color(std::clamp(rgbColor.red + luminosityShift, 0, 255),
std::clamp(rgbColor.green + luminosityShift, 0, 255),
std::clamp(rgbColor.blue + luminosityShift, 0, 255));
}
};

View File

@@ -12,7 +12,7 @@ GameDistributionAppMenu::GameDistributionAppMenu(std::shared_ptr<MenuStack> menu
AppMenu(menuStack, settings, renderWindow),
playerCursor({1}) {
for (int i = 1; i <= this->settings->getLoadablePiecesSize(); i++) {
for (int i = 1; i <= this->settings->getMaximumPiecesSize(); i++) {
this->playerCursor.addRow(i, 1);
}
}
@@ -65,7 +65,7 @@ void GameDistributionAppMenu::drawFrame() const {
const DistributionMode distributionMode = this->settings->getMenu().readPiecesList().getDistributionMode();
const std::vector<int>& distributions = this->settings->getDistributions();
int firstElem = std::clamp(((int) this->playerCursor.getPosition().y) - 1, 0, this->settings->getLoadablePiecesSize() - 3);
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});
}

View File

@@ -13,12 +13,12 @@ GamePiecesAppMenu::GamePiecesAppMenu(std::shared_ptr<MenuStack> menuStack, std::
AppMenu(menuStack, settings, renderWindow),
playerCursor({1, (unsigned int) this->settings->getSelectedPieces().size() + 1u}) {
for (int i = 1; i <= this->settings->getLoadablePiecesSize(); i++) {
for (int i = 1; i <= this->settings->getMaximumPiecesSize(); i++) {
this->playerCursor.addRow(i + 1, this->settings->getMenu().readPiecesList().getNumberOfPieces(i) + 4);
}
if (this->settings->getLoadablePiecesSize() < MAXIMUM_PIECES_SIZE) {
this->playerCursor.addRow(this->settings->getLoadablePiecesSize() + 2, 1);
if (this->settings->getMaximumPiecesSize() < MAXIMUM_PIECES_SIZE) {
this->playerCursor.addRow(this->settings->getMaximumPiecesSize() + 2, 1);
}
}
@@ -35,8 +35,8 @@ void GamePiecesAppMenu::computeFrame() {
this->menuStack->push(std::make_shared<GameDistributionAppMenu>(this->menuStack, this->settings, this->renderWindow));
}
if (this->playerCursor.getPosition().y == (this->settings->getLoadablePiecesSize() + 2)) {
int newMaxSize = this->settings->getLoadablePiecesSize() + 1;
if (this->playerCursor.getPosition().y == (this->settings->getMaximumPiecesSize() + 2)) {
int newMaxSize = this->settings->getMaximumPiecesSize() + 1;
this->settings->loadPieces(newMaxSize);
this->playerCursor.removeRow(newMaxSize + 1);
@@ -95,8 +95,8 @@ void GamePiecesAppMenu::drawFrame() const {
this->drawSelectedPiecesRow(15.f);
bool drawFromFirstElem = (this->playerCursor.getPosition().y == 1);
bool addExtraLine = (this->settings->getLoadablePiecesSize() < MAXIMUM_PIECES_SIZE);
int firstElem = std::clamp(((int) this->playerCursor.getPosition().y) - 2, 1, this->settings->getLoadablePiecesSize() - 2 + (addExtraLine ? 1 : 0));
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);
@@ -130,7 +130,7 @@ void GamePiecesAppMenu::drawSelectedPiecesRow(float yPos) const {
int pieceSize = getSizeOfPieces(pieceType);
if (pieceSize > 0) {
if (!(pieceSize > this->settings->getLoadablePiecesSize())) {
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());
@@ -143,7 +143,7 @@ void GamePiecesAppMenu::drawSelectedPiecesRow(float yPos) const {
}
}
else {
if (!(value > this->settings->getLoadablePiecesSize())) {
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()));
}
@@ -158,7 +158,7 @@ void GamePiecesAppMenu::drawSelectedPiecesRow(float yPos) const {
}
void GamePiecesAppMenu::drawRow(int piecesSize, float yPos, bool drawFromFirstElem) const {
if (piecesSize > this->settings->getLoadablePiecesSize()) {
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)) {

View File

@@ -105,25 +105,16 @@ void GamePlayingAppMenu::computeFrame() {
void GamePlayingAppMenu::drawFrame() const {
this->renderWindow->clear(sf::Color(200, 200, 200));
sf::Color bonesBlockColor(0, 0, 0);
sf::Color bonesBlockGhostColor(100, 100, 100);
bool areBlockBones = this->game.areBlocksBones();
bool isBoardInvisible = this->game.isBoardInvisible() && !(this->game.hasWon() || this->game.hasLost());
sf::Vector2f cellSize(this->cellSizeZoom, this->cellSizeZoom);
float cellOutlineThickness = this->cellSizeZoom / 4;
bool drawActivePiece = (this->game.getActivePiece() != nullptr) && (!this->game.hasLost());
// board
for (int y = this->game.getBoard().getBaseHeight() + 9; y >= 0; y--) {
for (int x = 0; x < this->game.getBoard().getWidth(); x++) {
Block block = this->game.getBoard().getBlock(Position{x, y});
if (isBoardInvisible) block = NOTHING;
sf::RectangleShape cell(cellSize);
cell.setFillColor((areBlockBones && block != NOTHING)
? bonesBlockColor
: this->getColorOfBlock(block, (block == NOTHING) ? 0 : -30));
cell.setFillColor(this->getColorOfBlock(block, (block == NOTHING) ? 0 : -30));
cell.setPosition(this->getBoardBlockPosition(x, y));
this->renderWindow->draw(cell);
}
@@ -131,10 +122,7 @@ void GamePlayingAppMenu::drawFrame() const {
if (drawActivePiece) {
// ghost piece
sf::Color ghostColor = areBlockBones
? bonesBlockGhostColor
: this->getColorOfBlock(this->game.getActivePiece()->getBlockType(), 100);
sf::Color ghostColor = this->getColorOfBlock(this->game.getActivePiece()->getBlockType(), 100);
for (const Position& position : this->game.getActivePiece()->getPositions()) {
Position cellPosition = (this->game.getGhostPiecePosition() + position);
@@ -145,13 +133,13 @@ void GamePlayingAppMenu::drawFrame() const {
}
// active piece outline
float pieceOutlineSize = std::roundf(this->cellSizeZoom / 4);
sf::Color pieceOultlineColor = sf::Color(255, 255 - (255 * this->game.getForcedLockDelayProgression()), 255 - (255 * this->game.getForcedLockDelayProgression()));
for (const Position& position : this->game.getActivePiece()->getPositions()) {
Position cellPosition = (this->game.getActivePiecePosition() + position);
sf::RectangleShape cell(cellSize);
cell.setOutlineThickness(cellOutlineThickness);
cell.setOutlineThickness(pieceOutlineSize);
cell.setOutlineColor(pieceOultlineColor);
cell.setPosition(this->getBoardBlockPosition(cellPosition.x, cellPosition.y));
this->renderWindow->draw(cell);
@@ -166,9 +154,7 @@ void GamePlayingAppMenu::drawFrame() const {
if (drawActivePiece) {
// active piece
sf::Color pieceColor = areBlockBones
? bonesBlockColor
: this->getColorOfBlock(this->game.getActivePiece()->getBlockType(), -200 * (this->game.getLockDelayProgression()));
sf::Color pieceColor = this->getColorOfBlock(this->game.getActivePiece()->getBlockType(), -200 * (this->game.getLockDelayProgression()));
for (const Position& position : this->game.getActivePiece()->getPositions()) {
Position cellPosition = (this->game.getActivePiecePosition() + position);
@@ -187,9 +173,7 @@ void GamePlayingAppMenu::drawFrame() const {
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 color = this->getColorOfBlock(this->game.getNextPieces().at(i).getBlockType(), 0);
sf::Color boxColor = sf::Color(180, 180, 180);
int lowestRank = 0;
@@ -197,7 +181,7 @@ void GamePlayingAppMenu::drawFrame() const {
for (int x = 0; x < this->game.getNextPieces().at(i).getLength(); x++) {
sf::RectangleShape cell(nextCellSize);
if (this->game.getNextPieces().at(i).getPositions().contains(Position{x, y})) {
cell.setFillColor(pieceColor);
cell.setFillColor(color);
lowestRank = y;
}
else {
@@ -215,9 +199,7 @@ void GamePlayingAppMenu::drawFrame() const {
// 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 color = this->getColorOfBlock(this->game.getHeldPiece()->getBlockType(), 0);
sf::Color boxColor = sf::Color(180, 180, 180);
for (int y = 0; y < this->game.getHeldPiece()->getLength(); y++) {
@@ -230,7 +212,7 @@ void GamePlayingAppMenu::drawFrame() const {
cell.setFillColor(boxColor);
}
cell.setPosition(sf::Vector2f(this->holdBoxPosition.position.x + (x * this->nextCellSizeZoom),
this->holdBoxPosition.position.y + ((this->game.getHeldPiece()->getLength() - y - 1) * this->holdCellSizeZoom)));
this->holdBoxPosition.position.y + ((this->game.getHeldPiece()->getLength() - y - 1) * this->holdCellSizeZoom)));
this->renderWindow->draw(cell);
}
}

View File

@@ -13,7 +13,7 @@
GameSettingsAppMenu::GameSettingsAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow) :
AppMenu(menuStack, settings, renderWindow),
playerCursor({2, 3, 3}) {
playerCursor({2, 3, 2}) {
}
@@ -33,8 +33,7 @@ void GameSettingsAppMenu::computeFrame() {
case 2 : {
switch (this->playerCursor.getPosition().x) {
case 0 : {this->settings->setGamemode(MASTER); break;}
case 1 : {this->settings->setGamemode(INVISIBLE); break;}
case 2 : {this->settings->setGamemode(ZEN); break;}
case 1 : {this->settings->setGamemode(ZEN); break;}
}
break;
}
@@ -77,8 +76,7 @@ void GameSettingsAppMenu::drawFrame() const {
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->placeText(text, this->playerCursor, "ZEN", 25.f, 45.f, sf::Vector2u{1, 2});
this->renderWindow->display();
}

View File

@@ -11,14 +11,14 @@
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({
sectionsName(
"< ABOUT >",
"< PIECES TYPES >",
"< 0 DEGREES ROTATIONS >",
"< ROTATION SYSTEM >",
"< SCORING >"
}),
sectionsContent({
),
sectionsContent(
"This game is written in C++,\n"
"using SFML 3 for the GUI.\n"
"It has been inspired by other\n"
@@ -67,14 +67,47 @@ InfoAppMenu::InfoAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<S
"A spin is detected when the piece is\n"
"locked in place, a mini-spin simply\n"
"when the last move was a kick."
}) {
),
sectionNameText(this->createText()),
sectionContentText(this->createText()),
renderTexture(this->renderWindow->getSize()),
sprite(this->renderTexture.getTexture()) {
this->setTextOutline(this->sectionNameText, true);
this->sectionContentText.setLineSpacing((float) this->settings->getWindowSizeMultiplier() / 8);
this->sectionNameText.setString(this->sectionsName[this->playerCursor.getPosition().x]);
this->setTitlePosition(this->sectionNameText, 10.f);
this->sectionContentText.setString(this->sectionsContent[this->playerCursor.getPosition().x]);
this->setTextPosition(this->sectionContentText, 5.f, 30.f);
this->renderTexture.clear(sf::Color(200, 200, 200));
this->renderTexture.draw(this->sectionNameText);
this->renderTexture.draw(this->sectionContentText);
this->renderTexture.display();
this->sprite.setTexture(this->renderTexture.getTexture());
}
void InfoAppMenu::computeFrame() {
this->updateMetaBinds();
this->playerCursor.updatePosition();
if (this->playerCursor.movedLeft() || this->playerCursor.movedRight()) {
this->sectionNameText.setString(this->sectionsName[this->playerCursor.getPosition().x]);
this->setTitlePosition(this->sectionNameText, 10.f);
this->sectionContentText.setString(this->sectionsContent[this->playerCursor.getPosition().x]);
this->setTextPosition(this->sectionContentText, 5.f, 30.f);
this->renderTexture.clear(sf::Color(200, 200, 200));
this->renderTexture.draw(this->sectionNameText);
this->renderTexture.draw(this->sectionContentText);
this->renderTexture.display();
this->sprite.setTexture(this->renderTexture.getTexture());
}
if (this->escReleased) {
this->menuStack->pop();
}
@@ -83,15 +116,7 @@ void InfoAppMenu::computeFrame() {
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->draw(sprite);
this->renderWindow->display();
}

View File

@@ -9,11 +9,16 @@
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;
sf::String sectionsName[INFO_SECTIONS_COUNT];
sf::String sectionsContent[INFO_SECTIONS_COUNT];
sf::Text sectionNameText;
sf::Text sectionContentText;
sf::RenderTexture renderTexture;
sf::Sprite sprite;
public:
InfoAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared_ptr<Settings> settings, std::shared_ptr<sf::RenderWindow> renderWindow);

View File

@@ -2,7 +2,6 @@
#include "AppMenu.h"
#include "../PlayerCursor.h"
#include "../../Utils/AssetManager.h"
#include <stack>
#include <memory>
@@ -23,9 +22,8 @@ SettingsKeybindsAppMenu::SettingsKeybindsAppMenu(std::shared_ptr<MenuStack> menu
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}});
std::filesystem::path texturePath("data/images/keybinds/" + textureName + ".png");
this->iconTextures[action] = sf::Texture(texturePath, false, {{0, 0}, {16, 16}});
}
}

View File

@@ -14,7 +14,7 @@ StartUpAppMenu::StartUpAppMenu(std::shared_ptr<MenuStack> menuStack, std::shared
AppMenu(menuStack, settings, renderWindow),
playerCursor({MAXIMUM_PIECES_SIZE + 1}) {
this->playerCursor.goToPosition({(unsigned int) std::clamp(this->settings->getLoadablePiecesSize(), MINIMUM_PIECES_SIZE, MAXIMUM_PIECES_SIZE), 0u});
this->playerCursor.goToPosition({(unsigned int) std::clamp(this->settings->getMaximumPiecesSize(), MINIMUM_PIECES_SIZE, MAXIMUM_PIECES_SIZE), 0u});
}
void StartUpAppMenu::computeFrame() {

View File

@@ -42,6 +42,13 @@ void PlayerCursor::updatePosition() {
}
}
bool PlayerCursor::moved() const {
return (this->movedLeft()
|| this->movedRight()
|| this->movedUp()
|| this->movedDown());
}
bool PlayerCursor::movedLeft() const {
return this->shouldMove(this->leftDAS);
}
@@ -115,7 +122,7 @@ const sf::Vector2u& PlayerCursor::getPosition() const {
bool PlayerCursor::shouldMove(int DAS) const {
return (DAS == 1
|| (DAS > MENU_DAS && (DAS % 5) == 0)
|| (DAS > (FRAMES_PER_SECOND * 2)));
|| (DAS > (MENU_DAS * 4)));
}
void PlayerCursor::moveLeft() {

View File

@@ -18,6 +18,8 @@ class PlayerCursor {
void updatePosition();
bool moved() const;
bool movedLeft() const;
bool movedRight() const;

View File

@@ -27,41 +27,41 @@ Settings::Settings(bool loadPieces) {
this->loadSettingsFromFile(loadPieces, {});
}
void Settings::loadPieces(int loadablePiecesSizeRequest) {
if (loadablePiecesSizeRequest < MINIMUM_PIECES_SIZE) {
loadablePiecesSizeRequest = MINIMUM_PIECES_SIZE;
void Settings::loadPieces(int maximumPiecesSizeRequest) {
if (maximumPiecesSizeRequest < MINIMUM_PIECES_SIZE) {
maximumPiecesSizeRequest = MINIMUM_PIECES_SIZE;
}
else if (loadablePiecesSizeRequest > MAXIMUM_PIECES_SIZE) {
loadablePiecesSizeRequest = MAXIMUM_PIECES_SIZE;
else if (maximumPiecesSizeRequest > MAXIMUM_PIECES_SIZE) {
maximumPiecesSizeRequest = MAXIMUM_PIECES_SIZE;
}
bool succeeded = true;
int i = 1;
while (succeeded && (i <= loadablePiecesSizeRequest)) {
while (succeeded && (i <= maximumPiecesSizeRequest)) {
succeeded = this->menu.getPiecesList().loadPieces(i);
i++;
}
if (succeeded) {
this->loadablePiecesSize = loadablePiecesSizeRequest;
this->maximumPiecesSize = maximumPiecesSizeRequest;
}
this->loadedPieces = succeeded;
}
void Settings::loadSettingsFromFile(bool loadPieces, std::optional<int> loadablePiecesSizeRequest) {
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);
// loadable pieces size
// maximum pieces size
settingsFile.get(byte);
this->loadablePiecesSize = byte;
this->maximumPiecesSize = byte;
if (loadPieces) {
if (loadablePiecesSizeRequest.has_value()) {
this->loadPieces(loadablePiecesSizeRequest.value());
if (maximumPiecesSizeRequest.has_value()) {
this->loadPieces(maximumPiecesSizeRequest.value());
}
else {
this->loadPieces(byte);
@@ -165,8 +165,8 @@ void Settings::saveSettingsToFile() const {
byte = CURRENT_FILE_FORMAT_VERSION;
settingsFile.write(&byte, 1);
// loadable pieces size
byte = this->loadablePiecesSize;
// maximum pieces size
byte = this->maximumPiecesSize;
settingsFile.write(&byte, 1);
// keybind layout
@@ -321,7 +321,7 @@ void Settings::confirmSelectedPieces() {
int size = getSizeOfPieces(type);
if (size == 0) {
if (!(value > this->loadablePiecesSize)) {
if (!(value > this->maximumPiecesSize)) {
switch (type) {
case CONVEX_PIECES : {this->menu.getPiecesList().selectConvexPieces(value); break;}
case HOLELESS_PIECES : {this->menu.getPiecesList().selectHolelessPieces(value); break;}
@@ -332,7 +332,7 @@ void Settings::confirmSelectedPieces() {
}
}
else {
if (!(getSizeOfPieces(type) > this->loadablePiecesSize)) {
if (!(getSizeOfPieces(type) > this->maximumPiecesSize)) {
this->menu.getPiecesList().selectPiece(size, value);
selectedNone = false;
}
@@ -347,7 +347,7 @@ void Settings::confirmSelectedPieces() {
bool Settings::increaseDistribution(int size) {
if (!this->loadedPieces) return false;
if (size < 1 || size > this->loadablePiecesSize) return false;
if (size < 1 || size > this->maximumPiecesSize) return false;
if (this->distributions.at(size) < DISTRIBUTION_MAX) {
this->distributions.at(size)++;
@@ -358,7 +358,7 @@ bool Settings::increaseDistribution(int size) {
bool Settings::decreaseDistribution(int size) {
if (!this->loadedPieces) return false;
if (size < 1 || size > this->loadablePiecesSize) return false;
if (size < 1 || size > this->maximumPiecesSize) return false;
if (this->distributions.at(size) > 0) {
this->distributions.at(size)--;
@@ -383,8 +383,8 @@ Keybinds& Settings::getKeybinds() {
return this->keybinds.at(this->chosenKeybinds);
}
int Settings::getLoadablePiecesSize() const {
return this->loadablePiecesSize;
int Settings::getMaximumPiecesSize() const {
return this->maximumPiecesSize;
}
bool Settings::hasLoadedPieces() const {

View File

@@ -29,7 +29,7 @@ static const std::pair<PiecesType, int> DEFAULT_SELECTION = {ALL_PIECES, MINIMUM
class Settings {
private:
Menu menu;
int loadablePiecesSize;
int maximumPiecesSize;
bool loadedPieces;
std::vector<Keybinds> keybinds;
int chosenKeybinds;
@@ -42,9 +42,9 @@ class Settings {
public:
Settings(bool loadPieces);
void loadPieces(int loadablePiecesSizeRequest);
void loadPieces(int maximumPiecesSizeRequest);
void loadSettingsFromFile(bool loadPieces, std::optional<int> loadablePiecesSizeRequest);
void loadSettingsFromFile(bool loadPieces, std::optional<int> maximumPiecesSizeRequest);
void saveSettingsToFile() const;
@@ -82,7 +82,7 @@ class Settings {
Keybinds& getKeybinds();
int getLoadablePiecesSize() const;
int getMaximumPiecesSize() const;
bool hasLoadedPieces() const;

View File

@@ -4,69 +4,42 @@
#include <filesystem>
#include <fstream>
[[nodiscard]] bool resetSettingsFile();
[[nodiscard]] bool resetKeybindFile(int layout);
void resetSettingsFile();
void resetKeybindFile(int layout);
int main() {
std::srand(std::time(NULL));
bool everythingIsOK = true;
// CHECK PIECES FILES
PiecesFiles pf;
bool warned = false;
for (int i = 1; i <= MAXIMUM_PIECES_SIZE; i++) {
if (!std::filesystem::exists("data/pieces/" + std::to_string(i) + "minos.bin")) {
#ifndef DEBUG
if (!warned && i > DEBUG_PIECES_SIZE) {
std::cout << "IMPORTANT: You are currently in release mode, if you do not wish to generate big pieces (can take several minutes), type 'xmake f -m debug'." << std::endl;
warned = true;
}
#ifdef NDEBUG
std::cout << "IMPORTANT: You are currently in release mode, if you do not wish to generate big pieces (can take several minutes), type 'xmake f -m debug'." << std::endl;
#endif
std::cout << "INFO: Pieces files for size " << i << " not found, generating..." << std::endl;
everythingIsOK &= pf.savePieces(i);
pf.savePieces(i);
}
}
#ifndef NDEBUG
std::cout << "IMPORTANT: you are currently in debug mode, if you wish to use bigger pieces, type 'xmake f -m release'." << std::endl;
#ifdef DEBUG
std::cout << "IMPORTANT: You are currently in debug mode, if you wish to use bigger pieces, type 'xmake f -m release'." << std::endl;
bool releasePiecesGenerated = true;
for (int i = DEBUG_PIECES_SIZE + 1; i <= RELEASE_PIECES_SIZE; i++) {
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")) {
releasePiecesGenerated = false;
everythingGenerated = false;
}
}
if (!releasePiecesGenerated) {
std::cout << "NOTE: You do not have all pieces generated, generating can take several minutes." << std::endl;
if (!everythingGenerated) {
std::cout << "NOTE : you do not have all pieces generated, generating can take several minutes." << std::endl;
}
#endif
bool everythingGenerated = true;
for (int i = 1; i <= MAXIMUM_PIECES_SIZE; i++) {
std::string filePath = "data/pieces/" + std::to_string(i) + "minos.bin";
if (!std::filesystem::exists(filePath)) {
std::cout << "ERROR: Could not open file " + filePath << std::endl;
everythingIsOK &= false;
}
}
// CHECK CONFIG FILES
if (!std::filesystem::exists("data/config/settings.bin")) {
std::cout << "INFO: Settings file not found, generating..." << std::endl;
everythingIsOK &= resetSettingsFile();
for (int i = 0; i < NUMBER_OF_KEYBINDS; i++) {
if (!std::filesystem::exists("data/config/keybinds/layout" + std::to_string(i) + ".bin")) {
std::cout << "INFO: Keybind file number " << (i + 1) << "/" << NUMBER_OF_KEYBINDS << " not found, generating..." << std::endl;
everythingIsOK &= resetKeybindFile(i);
}
}
resetSettingsFile();
}
else {
std::ifstream settingsFile("data/config/settings.bin", std::ios::binary);
@@ -75,49 +48,38 @@ int main() {
settingsFile.get(byte);
if ((unsigned char) byte < CURRENT_FILE_FORMAT_VERSION) {
std::cout << "INFO: Files format changed, regenerating..." << std::endl;
everythingIsOK &= resetSettingsFile();
resetSettingsFile();
for (int i = 0; i < NUMBER_OF_KEYBINDS; i++) {
everythingIsOK &= resetKeybindFile(i);
resetKeybindFile(i);
}
}
}
// LAUNCH APP
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);
}
}
if (everythingIsOK) {
GraphApp UI;
UI.run();
}
else {
std::cout << "ERROR: The game could not launch properly." << std::endl;
std::cout << "Press enter to quit. ";
std::cin.get();
}
GraphApp UI;
UI.run();
return 0;
}
bool resetSettingsFile() {
if (!std::filesystem::exists("data/config")) {
std::filesystem::create_directories("data/config");
}
std::string filePath ="data/config/settings.bin";
std::ofstream settingsFile(filePath, std::ios::trunc | std::ios::binary);
if (!settingsFile.good()) {
std::cerr << "ERROR: Could not open file " + filePath << std::endl;
return false;
}
void resetSettingsFile() {
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);
// loadable pieces size
// maximum pieces size
byte = MINIMUM_PIECES_SIZE;
settingsFile.write(&byte, 1);
@@ -166,35 +128,16 @@ bool resetSettingsFile() {
}
// selected pieces
byte = DEFAULT_SELECTION.first;
byte = ALL_PIECES;
settingsFile.write(&byte, 1);
byte = DEFAULT_SELECTION.second;
byte = 4;
settingsFile.write(&byte, 1);
return true;
}
bool resetKeybindFile(int layout) {
if (layout < 0 || layout > NUMBER_OF_KEYBINDS) {
std::cerr << "ERROR: Trying to create keybind layout number " << layout << " which is outside of range (" << NUMBER_OF_KEYBINDS << ")." << std::endl;
return false;
}
if (!std::filesystem::exists("data/config/keybinds")) {
std::filesystem::create_directories("data/config/keybinds");
}
std::string filePath = "data/config/keybinds/layout" + std::to_string(layout) + ".bin";
std::ofstream layoutFile(filePath, std::ios::trunc | std::ios::binary);
if (!layoutFile.good()) {
std::cerr << "ERROR: Could not open file " + filePath << std::endl;
return false;
}
if (layout == NUMBER_OF_KEYBINDS) {
return true;
}
void resetKeybindFile(int layout) {
if (layout < 0 || layout > 4) return;
std::ofstream layoutFile("data/config/keybinds/layout" + std::to_string(layout) + ".bin", std::ios::trunc | std::ios::binary);
std::map<Action, sfKey> keybinds;
if (layout != 4) {
@@ -260,6 +203,4 @@ bool resetKeybindFile(int layout) {
byte = 0xFF;
layoutFile.write(&byte, 1);
}
return true;
}

View File

@@ -50,7 +50,7 @@ void Generator::generate(int polyominoSize, int lastAddedPositionNumber, int nex
}
// generate the list of candidate positions
for (const Position position : this->currentTestedShape) {
for (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 (const auto [key, val] : candidatePositions) {
for (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);

View File

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

View File

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

View File

@@ -15,7 +15,7 @@ Polyomino::Polyomino(const std::set<Position>& positions) {
int maxX = INT_MIN;
int minY = INT_MAX;
int maxY = INT_MIN;
for (const Position position : positions) {
for (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 (const Position position : this->positions) {
for (Position position : this->positions) {
if (position.x < minX) minX = position.x;
if (position.y < minY) minY = position.y;
}
std::set<Position> newPositions;
for (const Position position : this->positions) {
for (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 (const Position position : this->positions) {
for (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 (const Position position : this->positions) {
for (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 (const Position position : this->positions) {
for (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 (const Position position : this->positions) {
for (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 (const Position position : this->positions) {
for (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 (const Position position : positions) {
for (Position position : positions) {
newPositions.insert(Position{(position.x - minX) + (verticalEmptyLines / 2), (position.y - minY) + ((horizontalEmptyLines + 1) / 2)});
}
this->positions = std::move(newPositions);

View File

@@ -3,36 +3,23 @@
#include "TextApp.h"
#include <chrono>
#include <filesystem>
#include <cmath>
static const int MAXIMUM_PIECES_SIZE = 10;
void testGeneratorForAllSizes(int max_size);
void testGeneratorForAllSizes(int amount);
void testGeneratorForOneSize(int size);
void printPiecesByTypesForOneSize(int size);
void readStatsFromFilesForAllSizes(int max_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);
int main(int argc, char** argv) {
std::srand(std::time(NULL));
// CHECK PIECES FILES
PiecesFiles pf;
bool warned = false;
for (int i = 1; i <= MAXIMUM_PIECES_SIZE; i++) {
if (!std::filesystem::exists("data/pieces/" + std::to_string(i) + "minos.bin")) {
if (!warned) {
std::cout << "INFO: Pieces files for size " << i << " not found, generating..." << std::endl;
warned = true;
}
pf.savePieces(i);
}
}
// LAUNCH APP
// dev: generate files if it hasn't been done before, UI will NOT generate the files
//generateFilesForAllSizes(10);
TextApp UI;
UI.run();
@@ -41,20 +28,20 @@ int main(int argc, char** argv) {
}
void testGeneratorForAllSizes(int max_size) {
void testGeneratorForAllSizes(int amount) {
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 <= max_size; i++) {
for (int i = 1; i <= amount; 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;
}
}
@@ -76,8 +63,24 @@ void testGeneratorForOneSize(int size) {
}
}
void printPiecesByTypesForOneSize(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) {
PiecesFiles piecesFiles;
piecesFiles.savePieces(size);
std::vector<Piece> pieces;
std::vector<int> convexPieces;
@@ -101,9 +104,79 @@ void printPiecesByTypesForOneSize(int size) {
}
}
void readStatsFromFilesForAllSizes(int max_size) {
void generateFilesForAllSizes(int amount) {
using std::chrono::high_resolution_clock;
using std::chrono::duration_cast;
using std::chrono::duration;
using std::chrono::milliseconds;
PiecesFiles piecesFiles;
for (int i = 1; i <= max_size; i++) {
for (int i = 1; i <= amount; i++) {
auto t1 = high_resolution_clock::now();
piecesFiles.savePieces(i);
auto t2 = high_resolution_clock::now();
duration<double, std::milli> ms_double = t2 - t1;
std::cout << "Generated pieces files for size " << i << " in " << ms_double.count() << "ms" << std::endl;
}
std::vector<Piece> pieces;
std::vector<int> convexPieces;
std::vector<int> holelessPieces;
std::vector<int> otherPieces;
for (int i = 1; i <= amount; i++) {
auto t1 = high_resolution_clock::now();
piecesFiles.loadPieces(i, pieces, convexPieces, holelessPieces, otherPieces);
auto t2 = high_resolution_clock::now();
duration<double, std::milli> ms_double = t2 - t1;
std::cout << "Read pieces from files for size " << i << " in " << ms_double.count() << "ms" << std::endl;
}
}
void generateFilesForOneSize(int size) {
using std::chrono::high_resolution_clock;
using std::chrono::duration_cast;
using std::chrono::duration;
using std::chrono::milliseconds;
PiecesFiles piecesFiles;
std::cout << "Generating " << size << "-minos files" << std::endl;
for (int i = 0; i < 10; i++) {
auto t1 = high_resolution_clock::now();
piecesFiles.savePieces(size);
auto t2 = high_resolution_clock::now();
duration<double, std::milli> ms_double = t2 - t1;
std::cout << ms_double.count() << "ms" << std::endl;
}
}
void loadFromFilesForOneSize(int size) {
using std::chrono::high_resolution_clock;
using std::chrono::duration_cast;
using std::chrono::duration;
using std::chrono::milliseconds;
PiecesFiles piecesFiles;
std::vector<Piece> pieces;
std::vector<int> convexPieces;
std::vector<int> holelessPieces;
std::vector<int> otherPieces;
std::cout << "Loading " << size << "-minos from files" << std::endl;
for (int i = 0; i < 10; i++) {
auto t1 = high_resolution_clock::now();
piecesFiles.loadPieces(size, pieces, convexPieces, holelessPieces, otherPieces);
auto t2 = high_resolution_clock::now();
duration<double, std::milli> ms_double = t2 - t1;
std::cout << ms_double.count() << "ms" << std::endl;
}
}
void readStatsFromFilesForAllSizes(int amount) {
PiecesFiles piecesFiles;
for (int i = 1; i <= amount; i++) {
std::vector<Piece> pieces;
std::vector<int> convexPieces;
std::vector<int> holelessPieces;

View File

@@ -1,97 +0,0 @@
#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));
}

View File

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

View File

@@ -1,8 +1,6 @@
add_rules("mode.debug", "mode.release")
includes("xmake/bin2c.lua")
add_requires("sfml 3.0.0", "zlib")
add_requires("sfml 3.0.0")
set_languages("c++20")
@@ -10,43 +8,21 @@ set_rundir(".")
target("core")
set_kind("$(kind)")
add_files("src/Pieces/*.cpp", "src/Core/*.cpp", "src/Common/*.cpp")
add_packages("zlib")
add_files("src/Pieces/*.cpp")
add_files("src/Core/*.cpp")
target("text")
set_default(false)
set_kind("binary")
set_default(false)
add_files("./src/TextUI/*.cpp")
add_deps("core")
target("bmark")
set_default(false)
set_kind("binary")
add_files("./src/Benchmark/*.cpp")
add_deps("core")
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
--

View File

@@ -1,124 +0,0 @@
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)