10 Commits

Author SHA1 Message Date
17459c4026 use VarInt
All checks were successful
Linux arm64 / Build (push) Successful in 12m53s
2025-06-12 21:38:47 +02:00
3e40ff7252 add compression
All checks were successful
Linux arm64 / Build (push) Successful in 1m58s
2025-06-12 21:28:46 +02:00
dd8314f76c ff windaube veut toujours pas
All checks were successful
Linux arm64 / Build (push) Successful in 2m45s
2025-06-12 17:35:39 +02:00
d7f52239f0 and again
All checks were successful
Linux arm64 / Build (push) Successful in 1m57s
2025-06-03 12:03:59 +02:00
72f4ea75ff fix again 2025-06-03 12:03:47 +02:00
b1b7277666 fix array initialization
Some checks failed
Linux arm64 / Build (push) Failing after 1m53s
2025-06-03 11:55:12 +02:00
fd6bdc2b09 Merge branch 'assets'
All checks were successful
Linux arm64 / Build (push) Successful in 2m0s
2025-05-28 19:24:49 +02:00
69b91d6497 simon PR review 2025-05-28 19:24:36 +02:00
d50714ef8c working asset manager !
All checks were successful
Linux arm64 / Build (push) Successful in 1m58s
2025-05-27 21:29:56 +02:00
3d1feb6295 start asset manager
Some checks failed
Linux arm64 / Build (push) Failing after 1m58s
2025-05-27 17:09:36 +02:00
21 changed files with 1002 additions and 116 deletions

View File

@@ -92,7 +92,7 @@ If for some reasons you wanna run the command line version (not updated):
### 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 |
@@ -109,12 +109,13 @@ If for some reasons you wanna run the command line version (not updated):
| 14 | 1802312 | 45s 606.597ms | 32s 28.7977ms | 2s 919.653ms | 27034680 bytes |
| 15 | ~6M | ~5mn | ~5mn | ~10s | ~100 MB |
_File storing includes normalizing and sorting all polyominoes before writing them to the file._
_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 build benchmark``
``xmake run benchmark``
``xmake f -m release``
``xmake build bmark``
``xmake run bmark``
## Credits

View File

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

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

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

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

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

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

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

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

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

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

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

View File

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

View File

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

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,7 +67,7 @@ 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."
) {
}) {
}

View File

@@ -9,12 +9,11 @@
static const int INFO_SECTIONS_COUNT = 5;
class InfoAppMenu : public AppMenu {
private:
PlayerCursor playerCursor;
sf::String sectionsName[INFO_SECTIONS_COUNT];
sf::String sectionsContent[INFO_SECTIONS_COUNT];
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);

View File

@@ -2,6 +2,7 @@
#include "AppMenu.h"
#include "../PlayerCursor.h"
#include "../../Utils/AssetManager.h"
#include <stack>
#include <memory>
@@ -22,8 +23,9 @@ SettingsKeybindsAppMenu::SettingsKeybindsAppMenu(std::shared_ptr<MenuStack> menu
std::string textureName = ACTION_NAMES[action];
textureName = std::regex_replace(textureName, std::regex(" "), "");
std::filesystem::path texturePath("data/images/keybinds/" + textureName + ".png");
this->iconTextures[action] = sf::Texture(texturePath, false, {{0, 0}, {16, 16}});
const Asset& textureData = getResource("data/images/keybinds/" + textureName + ".png");
this->iconTextures[action] = sf::Texture(textureData.data, textureData.size, false, {{0, 0}, {16, 16}});
}
}

View File

@@ -15,7 +15,7 @@ int main() {
PiecesFiles pf;
for (int i = 1; i <= MAXIMUM_PIECES_SIZE; i++) {
if (!std::filesystem::exists("data/pieces/" + std::to_string(i) + "minos.bin")) {
#ifdef NDEBUG
#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
@@ -23,8 +23,8 @@ int main() {
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 everythingGenerated = true;
for (int i = DEBUG_PIECES_SIZE; i <= RELEASE_PIECES_SIZE; i++) {
@@ -33,7 +33,7 @@ int main() {
}
}
if (!everythingGenerated) {
std::cout << "NOTE : you do not have all pieces generated, generating can take several minutes." << std::endl;
std::cout << "NOTE: You do not have all pieces generated, generating can take several minutes." << std::endl;
}
#endif
@@ -70,6 +70,10 @@ int main() {
void resetSettingsFile() {
if (!std::filesystem::exists("data/config")) {
std::filesystem::create_directories("data/config");
}
std::ofstream settingsFile("data/config/settings.bin", std::ios::trunc | std::ios::binary);
char byte;

View File

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

View File

@@ -10,6 +10,8 @@
#include <filesystem>
#include <algorithm>
#include "../Common/Compression.h"
PiecesFiles::PiecesFiles() {
}
@@ -19,10 +21,6 @@ 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()) {
return false;
}
Generator generator;
std::vector<Polyomino> polyominoes = generator.generatePolyominoes(polyominoSize);
@@ -35,10 +33,9 @@ bool PiecesFiles::savePieces(int polyominoSize, std::vector<Polyomino>& polyomin
if (!this->getFilePath(polyominoSize, filePath)) {
return false;
}
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);
// sorting the polyominoes is done after setting spawn position to ensure the order is always the same
for (Polyomino& nMino : polyominoes) {
@@ -48,18 +45,22 @@ bool PiecesFiles::savePieces(int polyominoSize, std::vector<Polyomino>& polyomin
for (const Polyomino& polyomino : polyominoes) {
// write the characteristics of the piece
char infoByte = (polyomino.isConvex() << 7) + (polyomino.hasHole() << 6) + polyomino.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;
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 {
@@ -67,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;
@@ -89,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;
@@ -101,9 +110,9 @@ 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);
buffer >> positionByte;
int x = ((unsigned char) positionByte & xMask) >> 4;
int y = positionByte & yMask;
piecePositions.insert(Position{x, y});
@@ -132,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;
}

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 (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);

View File

@@ -22,11 +22,12 @@ int main(int argc, char** argv) {
std::srand(std::time(NULL));
#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
benchmarking(1, BENCHMARK_PIECES_SIZE);
#else
// dev: generate files if it hasn't been done before, UI will NOT generate the files
//generateFilesForAllSizes(10);
PiecesFiles pf;
for (int i = 1; i <= MAXIMUM_PIECES_SIZE; i++) {
if (!std::filesystem::exists("data/pieces/" + std::to_string(i) + "minos.bin")) {
@@ -126,7 +127,7 @@ void benchmarking(int min_size, int max_size) {
using std::chrono::milliseconds;
std::cout << "| n | Number | Generation | File storing | File retrieving | File size |" << std::endl;
std::cout << "| - | - | - | - | - | - |" << std::endl;
std::cout << "| -: | -: | :-: | :-: | :-: | -: |" << std::endl;
Generator gen;
PiecesFiles pf;

View File

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

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

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

View File

@@ -1,6 +1,8 @@
add_rules("mode.debug", "mode.release")
add_requires("sfml 3.0.0")
includes("xmake/bin2c.lua")
add_requires("sfml 3.0.0", "zlib")
set_languages("c++20")
@@ -8,15 +10,8 @@ set_rundir(".")
target("core")
set_kind("$(kind)")
add_files("src/Pieces/*.cpp")
add_files("src/Core/*.cpp")
target("graph")
set_default(true)
set_kind("binary")
add_files("./src/GraphicalUI/**.cpp")
add_deps("core")
add_packages("sfml")
add_files("src/Pieces/*.cpp", "src/Core/*.cpp", "src/Common/*.cpp")
add_packages("zlib")
target("text")
set_default(false)
@@ -24,13 +19,34 @@ target("text")
add_files("./src/TextUI/*.cpp")
add_deps("core")
target("benchmark")
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
View File

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