Compare commits
25 Commits
9894198f01
...
master
Author | SHA1 | Date | |
---|---|---|---|
1bae5b1d68 | |||
946dcb70e9 | |||
1ec6accc5b | |||
5ab7b40154 | |||
549214014d | |||
472b439bed | |||
27ac1468c6 | |||
3c4dc154b1 | |||
5e9082b327 | |||
5e728417aa | |||
97d2e1913f | |||
67318b1be9 | |||
7e508cfc85 | |||
c609d5ccb0 | |||
d99633e7cf | |||
98a2879129 | |||
86a805b6e9 | |||
2ac8c05e1d | |||
7c62375761 | |||
cf538f2cf5 | |||
50fe9d9c6d | |||
91328c4aca | |||
cf7a9a1f1a | |||
21aeaab088 | |||
9e3bac710f |
@ -9,22 +9,33 @@ endif ()
|
||||
|
||||
find_package(SDL2 CONFIG REQUIRED)
|
||||
find_package(SDL2_ttf CONFIG REQUIRED)
|
||||
find_package(CURL REQUIRED)
|
||||
find_package(SDL2_image CONFIG REQUIRED)
|
||||
|
||||
|
||||
file(GLOB KULLE_SOURCES src/data/kulle_*_png.cpp)
|
||||
file(GLOB KULLE_HEADERS src/data/kulle_*_png.hpp)
|
||||
|
||||
add_executable(hang_man src/main.cpp
|
||||
src/Game.cpp
|
||||
src/Game.hpp
|
||||
src/State.hpp
|
||||
src/words.hpp
|
||||
src/words.cpp
|
||||
src/data/words.hpp
|
||||
src/data/words.cpp
|
||||
src/utils.hpp
|
||||
src/GuessCorrector.cpp
|
||||
src/GuessCorrector.hpp
|
||||
src/default_font.cpp
|
||||
src/default_font.hpp
|
||||
${KULLE_SOURCES}
|
||||
${KULLE_HEADERS}
|
||||
src/data/hills.cpp
|
||||
src/data/hills.hpp
|
||||
src/utils.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(hang_man PRIVATE
|
||||
SDL2::SDL2
|
||||
SDL2::SDL2main
|
||||
SDL2_ttf::SDL2_ttf
|
||||
CURL::libcurl
|
||||
SDL2_image::SDL2_image
|
||||
)
|
||||
|
61
bin-to-array.py
Normal file
61
bin-to-array.py
Normal file
@ -0,0 +1,61 @@
|
||||
"""
|
||||
Use this script to write data files as c arrays. This is used to stay cross-platform for embedding.
|
||||
"""
|
||||
import argparse
|
||||
import os.path
|
||||
|
||||
|
||||
def format_hex_line(data):
|
||||
hex_data = ', '.join(f'0x{byte:02x}' for byte in data)
|
||||
return f' {hex_data},\n'
|
||||
|
||||
|
||||
def hexdump_to_cpp_array(file_name: str, output_dir: str, variable_name: str):
|
||||
path = os.path.join(output_dir, variable_name)
|
||||
header_path, source_path = f"{path}.hpp", f"{path}.cpp"
|
||||
|
||||
# Header
|
||||
with open(header_path, 'w') as file:
|
||||
file.write("#pragma once\n\n")
|
||||
file.write("#include <cstddef>\n\n")
|
||||
file.write(f"extern const unsigned char {variable_name}[];\n")
|
||||
file.write(f"extern const size_t {variable_name}_length;\n")
|
||||
|
||||
# Source
|
||||
byte_count = 0
|
||||
with open(file_name, 'rb') as bin_file, open(source_path, 'w') as source_file:
|
||||
source_file.write(f"// This is the bytes of {file_name}\n")
|
||||
source_file.write(f'#include "{variable_name}.hpp"\n\n')
|
||||
source_file.write(f"const unsigned char {variable_name}[] = {{\n")
|
||||
while True:
|
||||
chunk = bin_file.read(16)
|
||||
if not chunk:
|
||||
break
|
||||
byte_count += len(chunk)
|
||||
source_file.write(format_hex_line(chunk))
|
||||
source_file.write("};\n")
|
||||
source_file.write(f"const size_t {variable_name}_length = {byte_count};\n")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Hexdump a file to a cpp array.")
|
||||
parser.add_argument("file", help="Path to the binary file to be dumped")
|
||||
parser.add_argument("-d", "--dir", help="Output directory", metavar="OUTPUT")
|
||||
parser.add_argument("-n", "--name", help="Name of the array and header/source files", default="data")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.dir:
|
||||
try:
|
||||
hexdump_to_cpp_array(args.file, args.dir, args.name)
|
||||
path = os.path.join(args.dir, args.name)
|
||||
print(f"Hex dump written to {path}.hpp & {path}.cpp")
|
||||
except FileNotFoundError:
|
||||
print(f"File not found: {args.file}")
|
||||
except IOError as e:
|
||||
print(f"Error: {e}")
|
||||
else:
|
||||
print("No output dir specified. Exiting.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
141
src/Game.cpp
141
src/Game.cpp
@ -1,12 +1,20 @@
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
#include "Game.hpp"
|
||||
#include "SDL.h"
|
||||
#include "SDL_ttf.h"
|
||||
#include "words.hpp"
|
||||
#include "data/words.hpp"
|
||||
#include "utils.hpp"
|
||||
#include "default_font.hpp"
|
||||
#include "State.hpp"
|
||||
|
||||
const int CHAR_SIZE = 30;
|
||||
const int STEP_SIZE = CHAR_SIZE + CHAR_SIZE / 2;
|
||||
const int UNDERSCORE_DY = 10;
|
||||
const SDL_Color TEXT_COLOR = {255, 255, 255};
|
||||
const int MAX_GUESSES = 8;
|
||||
|
||||
void Game::Run() {
|
||||
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
|
||||
@ -37,20 +45,27 @@ void Game::Run() {
|
||||
SDL_RenderClear(renderer);
|
||||
SDL_RenderPresent(renderer);
|
||||
|
||||
Game game;
|
||||
bool quit = false;
|
||||
SDL_Event event;
|
||||
while (!quit) {
|
||||
while (SDL_WaitEvent(&event)) {
|
||||
switch (event.type) {
|
||||
case SDL_QUIT:
|
||||
quit = true;
|
||||
break;
|
||||
case SDL_KEYDOWN :
|
||||
game.handle_key(event.key.keysym.sym);
|
||||
{
|
||||
Game game;
|
||||
SDL_Event event;
|
||||
while (true) {
|
||||
game.draw(renderer);
|
||||
while (SDL_WaitEvent(&event)) {
|
||||
switch (event.type) {
|
||||
case SDL_QUIT:
|
||||
goto quit;
|
||||
case SDL_KEYDOWN:
|
||||
game.handle_key(event.key.keysym.sym);
|
||||
game.draw(renderer);
|
||||
break;
|
||||
case SDL_WINDOWEVENT:
|
||||
if (event.window.event == SDL_WINDOWEVENT_CLOSE)
|
||||
goto quit;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
quit:;
|
||||
}
|
||||
|
||||
SDL_DestroyRenderer(renderer);
|
||||
@ -59,7 +74,27 @@ void Game::Run() {
|
||||
SDL_Quit();
|
||||
}
|
||||
|
||||
Game::Game() {
|
||||
Game::Game() :
|
||||
m_wrong_guesses(0),
|
||||
m_game_state(State::PLAY),
|
||||
m_hills(get_resized(get_hills(), 400, 400)) {
|
||||
const char *defaultFontPath = getDefaultFontPath();
|
||||
if (defaultFontPath == nullptr) {
|
||||
std::stringstream ss;
|
||||
ss << "Font path is not set for this platform (null)";
|
||||
auto s = ss.str();
|
||||
std::cerr << s << std::endl;
|
||||
throw std::runtime_error(s);
|
||||
}
|
||||
font = TTF_OpenFont(defaultFontPath, CHAR_SIZE);
|
||||
if (font == nullptr) {
|
||||
std::stringstream ss;
|
||||
ss << "Failed to load font: " << TTF_GetError();
|
||||
auto s = ss.str();
|
||||
std::cerr << s << std::endl;
|
||||
throw std::runtime_error(s);
|
||||
}
|
||||
|
||||
std::random_device random_device{};
|
||||
std::mt19937 rng(random_device());
|
||||
|
||||
@ -69,11 +104,85 @@ Game::Game() {
|
||||
for (int i = 0; i < words_len; i++)
|
||||
all_words.push_back(words[i]);
|
||||
std::shuffle(all_words.begin(), all_words.end(), rng);
|
||||
word = all_words.back();
|
||||
all_words.pop_back();
|
||||
do {
|
||||
word = all_words.back();
|
||||
all_words.pop_back();
|
||||
} while (strlen(word) > 15);
|
||||
guess_corrector = std::make_unique<GuessCorrector>(word);
|
||||
std::cout << "Word: " << word << std::endl;
|
||||
}
|
||||
|
||||
void Game::handle_key(SDL_Keycode event) {
|
||||
if (!isalpha(event))
|
||||
return;
|
||||
bool is_valid = guess_corrector->has_char(event);
|
||||
if (is_valid) {
|
||||
guess_corrector->add(event);
|
||||
if (guess_corrector->is_filled_out())
|
||||
m_game_state = State::WIN;
|
||||
} else {
|
||||
m_wrong_guesses += 1;
|
||||
if (m_wrong_guesses >= MAX_GUESSES) {
|
||||
m_game_state = State::GAME_OVER;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Game::draw(SDL_Renderer *renderer) {
|
||||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
|
||||
SDL_RenderClear(renderer);
|
||||
|
||||
draw_guesses(renderer);
|
||||
}
|
||||
|
||||
void Game::draw_hang_man(SDL_Renderer *renderer) {
|
||||
|
||||
}
|
||||
|
||||
void Game::draw_guesses(SDL_Renderer *renderer) {
|
||||
size_t len = strlen(word);
|
||||
int total_width = (len - 1) * STEP_SIZE + CHAR_SIZE;
|
||||
int start_x = (SCREEN_SIZE.x - total_width) / 2;
|
||||
int char_y = (SCREEN_SIZE.y / 4) * 3 - CHAR_SIZE / 2;
|
||||
|
||||
for (int i = 0; i < len; i++) {
|
||||
int here = start_x + i * STEP_SIZE;
|
||||
const SDL_Rect rect = {here, char_y + UNDERSCORE_DY, CHAR_SIZE, 5};
|
||||
SDL_SetRenderDrawColor(renderer, 255, 0, 0, SDL_ALPHA_OPAQUE);
|
||||
SDL_RenderFillRect(renderer, &rect);
|
||||
|
||||
std::optional<char> current_char = guess_corrector->guessed().lock()[i];
|
||||
if (current_char) {
|
||||
const char text_to_write[] = {*current_char, '\0'};
|
||||
|
||||
SDL_Surface *surface = TTF_RenderText_Blended(font, text_to_write, TEXT_COLOR);
|
||||
if (surface == nullptr) {
|
||||
std::cerr << "Failed to create surface: " << TTF_GetError() << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
SDL_Texture *txt = SDL_CreateTextureFromSurface(renderer, surface);
|
||||
if (txt == nullptr) {
|
||||
std::cerr << "Failed to create texture: " << SDL_GetError() << std::endl;
|
||||
SDL_FreeSurface(surface);
|
||||
continue;
|
||||
}
|
||||
|
||||
int text_width = surface->w;
|
||||
int text_height = surface->h;
|
||||
SDL_Rect text_rect = {here + (CHAR_SIZE - text_width) / 2, char_y - text_height, text_width, text_height};
|
||||
|
||||
SDL_RenderCopy(renderer, txt, nullptr, &text_rect);
|
||||
|
||||
SDL_DestroyTexture(txt);
|
||||
SDL_FreeSurface(surface);
|
||||
}
|
||||
}
|
||||
|
||||
SDL_RenderPresent(renderer);
|
||||
}
|
||||
|
||||
Game::~Game() {
|
||||
for (SDL_Surface *surface: m_hills)
|
||||
SDL_FreeSurface(surface);
|
||||
}
|
||||
|
19
src/Game.hpp
19
src/Game.hpp
@ -1,10 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL_rect.h>
|
||||
#include <SDL_events.h>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
#include "SDL_rect.h"
|
||||
#include "SDL_events.h"
|
||||
#include "SDL_render.h"
|
||||
#include "SDL_ttf.h"
|
||||
#include "GuessCorrector.hpp"
|
||||
#include "State.hpp"
|
||||
|
||||
const SDL_Point SCREEN_SIZE{800, 800};
|
||||
|
||||
@ -13,12 +16,24 @@ private:
|
||||
std::vector<const char *> all_words;
|
||||
std::unique_ptr<GuessCorrector> guess_corrector;
|
||||
const char *word;
|
||||
_TTF_Font *font;
|
||||
int m_wrong_guesses;
|
||||
State m_game_state;
|
||||
std::vector<SDL_Surface *> m_hills;
|
||||
|
||||
public:
|
||||
static void Run();
|
||||
|
||||
Game();
|
||||
~Game();
|
||||
|
||||
void handle_key(SDL_Keycode event);
|
||||
|
||||
void draw(SDL_Renderer *renderer);
|
||||
|
||||
private:
|
||||
void draw_guesses(SDL_Renderer *renderer);
|
||||
|
||||
void draw_hang_man(SDL_Renderer *renderer);
|
||||
};
|
||||
|
||||
|
13
src/data/hills.cpp
Normal file
13
src/data/hills.cpp
Normal file
@ -0,0 +1,13 @@
|
||||
#include "hills.hpp"
|
||||
|
||||
const HillData hills[] = {
|
||||
{kulle_0_png, kulle_0_png_length},
|
||||
{kulle_1_png, kulle_1_png_length},
|
||||
{kulle_2_png, kulle_2_png_length},
|
||||
{kulle_3_png, kulle_3_png_length},
|
||||
{kulle_4_png, kulle_4_png_length},
|
||||
{kulle_5_png, kulle_5_png_length},
|
||||
{kulle_6_png, kulle_6_png_length},
|
||||
{kulle_7_png, kulle_7_png_length},
|
||||
};
|
||||
extern const size_t hills_length = 8;
|
37
src/data/hills.hpp
Normal file
37
src/data/hills.hpp
Normal file
@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
extern const unsigned char kulle_0_png[];
|
||||
extern const size_t kulle_0_png_length;
|
||||
|
||||
extern const unsigned char kulle_1_png[];
|
||||
extern const size_t kulle_1_png_length;
|
||||
|
||||
extern const unsigned char kulle_2_png[];
|
||||
extern const size_t kulle_2_png_length;
|
||||
|
||||
extern const unsigned char kulle_3_png[];
|
||||
extern const size_t kulle_3_png_length;
|
||||
|
||||
extern const unsigned char kulle_4_png[];
|
||||
extern const size_t kulle_4_png_length;
|
||||
|
||||
extern const unsigned char kulle_5_png[];
|
||||
extern const size_t kulle_5_png_length;
|
||||
|
||||
extern const unsigned char kulle_6_png[];
|
||||
extern const size_t kulle_6_png_length;
|
||||
|
||||
extern const unsigned char kulle_7_png[];
|
||||
extern const size_t kulle_7_png_length;
|
||||
|
||||
|
||||
struct HillData {
|
||||
unsigned const char *data;
|
||||
const size_t length;
|
||||
};
|
||||
|
||||
extern const HillData hills[];
|
||||
extern const size_t hills_length;
|
||||
|
4024
src/data/kulle_0_png.cpp
Normal file
4024
src/data/kulle_0_png.cpp
Normal file
File diff suppressed because it is too large
Load Diff
3976
src/data/kulle_1_png.cpp
Normal file
3976
src/data/kulle_1_png.cpp
Normal file
File diff suppressed because it is too large
Load Diff
4123
src/data/kulle_2_png.cpp
Normal file
4123
src/data/kulle_2_png.cpp
Normal file
File diff suppressed because it is too large
Load Diff
4165
src/data/kulle_3_png.cpp
Normal file
4165
src/data/kulle_3_png.cpp
Normal file
File diff suppressed because it is too large
Load Diff
4192
src/data/kulle_4_png.cpp
Normal file
4192
src/data/kulle_4_png.cpp
Normal file
File diff suppressed because it is too large
Load Diff
4219
src/data/kulle_5_png.cpp
Normal file
4219
src/data/kulle_5_png.cpp
Normal file
File diff suppressed because it is too large
Load Diff
4260
src/data/kulle_6_png.cpp
Normal file
4260
src/data/kulle_6_png.cpp
Normal file
File diff suppressed because it is too large
Load Diff
4298
src/data/kulle_7_png.cpp
Normal file
4298
src/data/kulle_7_png.cpp
Normal file
File diff suppressed because it is too large
Load Diff
31
src/default_font.cpp
Normal file
31
src/default_font.cpp
Normal file
@ -0,0 +1,31 @@
|
||||
#include "default_font.hpp"
|
||||
|
||||
#ifdef __linux__
|
||||
#include <filesystem>
|
||||
|
||||
constexpr const char *getLinuxFilePath() {
|
||||
const char *fonts[] = {"/usr/share/fonts/truetype/DejaVuSans-Bold.ttf", // openSUSE
|
||||
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", // Debian
|
||||
"/usr/share/fonts/TTF/DejaVuSans-Bold.ttf", // Arch
|
||||
"/usr/share/fonts/dejavu-sans-fonts/DejaVuSans-Bold.ttf", // Fedora
|
||||
};
|
||||
for (const char *font: fonts)
|
||||
if (std::filesystem::exists(font))
|
||||
return font;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
const char *getDefaultFontPath() {
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
return R"(C:\Windows\Fonts\Arial.ttf)";
|
||||
#elif defined(__linux__)
|
||||
return getLinuxFilePath();
|
||||
#elif defined(__APPLE__) || defined(__MACH__)
|
||||
return "/System/Library/Fonts/Supplemental/Arial.ttf";
|
||||
#else
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
3
src/default_font.hpp
Normal file
3
src/default_font.hpp
Normal file
@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
const char *getDefaultFontPath();
|
@ -1,6 +1,9 @@
|
||||
#include <iostream>
|
||||
#define SDL_MAIN_HANDLED
|
||||
|
||||
#include <SDL.h>
|
||||
#include "Game.hpp"
|
||||
|
||||
int main() {
|
||||
int SDL_main(int argc, char *argv[]) {
|
||||
Game::Run();
|
||||
return 0;
|
||||
}
|
||||
|
78
src/utils.cpp
Normal file
78
src/utils.cpp
Normal file
@ -0,0 +1,78 @@
|
||||
#include <stdexcept>
|
||||
#include "utils.hpp"
|
||||
#include "SDL_image.h"
|
||||
#include "data/hills.hpp"
|
||||
|
||||
|
||||
std::vector<SDL_Surface *> get_hills() {
|
||||
std::vector<SDL_Surface *> surfaces;
|
||||
|
||||
for (int i = 0; i < hills_length; i++) {
|
||||
HillData png = hills[i];
|
||||
SDL_RWops *rw = SDL_RWFromMem(const_cast<unsigned char *>(png.data), png.length);
|
||||
if (!rw)
|
||||
throw std::runtime_error("Failed to create RWops from memory");
|
||||
|
||||
SDL_Surface *surface = IMG_Load_RW(rw, 1);
|
||||
if (!surface) {
|
||||
SDL_RWclose(rw);
|
||||
throw std::runtime_error("Failed to load image from memory: " + std::string(IMG_GetError()));
|
||||
}
|
||||
surfaces.push_back(surface);
|
||||
}
|
||||
|
||||
return surfaces;
|
||||
}
|
||||
|
||||
SDL_Surface *resize_surface(SDL_Surface *t_surface, int t_width, int t_height) {
|
||||
if (!t_surface)
|
||||
throw std::runtime_error("Original surface is null.");
|
||||
|
||||
if (t_surface->w == t_width && t_surface->h == t_height)
|
||||
return t_surface;
|
||||
|
||||
SDL_Surface *resizedSurface = SDL_CreateRGBSurface(
|
||||
0,
|
||||
t_width,
|
||||
t_height,
|
||||
t_surface->format->BitsPerPixel,
|
||||
t_surface->format->Rmask,
|
||||
t_surface->format->Gmask,
|
||||
t_surface->format->Bmask,
|
||||
t_surface->format->Amask
|
||||
);
|
||||
|
||||
if (!resizedSurface)
|
||||
throw std::runtime_error("Failed to create resized surface: " + std::string(SDL_GetError()));
|
||||
|
||||
SDL_Rect src_rect, dest_rect;
|
||||
for (int y = 0; y < t_height; ++y) {
|
||||
for (int x = 0; x < t_width; ++x) {
|
||||
src_rect.x = x * t_surface->w / t_width;
|
||||
src_rect.y = y * t_surface->h / t_height;
|
||||
src_rect.w = 1;
|
||||
src_rect.h = 1;
|
||||
|
||||
dest_rect.x = x;
|
||||
dest_rect.y = y;
|
||||
dest_rect.w = 1;
|
||||
dest_rect.h = 1;
|
||||
|
||||
SDL_BlitSurface(t_surface, &src_rect, resizedSurface, &dest_rect);
|
||||
}
|
||||
}
|
||||
|
||||
return resizedSurface;
|
||||
}
|
||||
|
||||
|
||||
std::vector<SDL_Surface *> get_resized(const std::vector<SDL_Surface *> &t_originals, int t_width, int t_height) {
|
||||
std::vector<SDL_Surface *> ret;
|
||||
ret.reserve(t_originals.size());
|
||||
for (SDL_Surface *original: t_originals) {
|
||||
SDL_Surface *resized = resize_surface(original, t_width, t_height);
|
||||
SDL_FreeSurface(original);
|
||||
ret.push_back(resized);
|
||||
}
|
||||
return ret;
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
#include "SDL_surface.h"
|
||||
|
||||
template<typename T>
|
||||
constexpr size_t array_len(T *array[]) {
|
||||
@ -10,3 +10,9 @@ constexpr size_t array_len(T *array[]) {
|
||||
i++;
|
||||
return i;
|
||||
}
|
||||
|
||||
std::vector<SDL_Surface *> get_resized(const std::vector<SDL_Surface *>& t_original, int t_width, int t_height);
|
||||
|
||||
std::vector<SDL_Surface *> get_hills();
|
||||
|
||||
SDL_Surface *resize_surface(SDL_Surface *t_surface, int t_width, int t_height);
|
||||
|
2
vcpkg
2
vcpkg
Submodule vcpkg updated: 5c7d3a872d...1de2026f28
@ -2,7 +2,7 @@
|
||||
"$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json",
|
||||
"name": "hang-man",
|
||||
"version": "1.0.0",
|
||||
"builtin-baseline": "5c7d3a872dd861817fc812647176d5076085a7eb",
|
||||
"builtin-baseline": "1de2026f28ead93ff1773e6e680387643e914ea1",
|
||||
"dependencies": [
|
||||
{
|
||||
"name": "sdl2",
|
||||
@ -12,6 +12,6 @@
|
||||
"version>=": "2.0.20"
|
||||
},
|
||||
"sdl2-ttf",
|
||||
"curl"
|
||||
"sdl2-image"
|
||||
]
|
||||
}
|
||||
|
Reference in New Issue
Block a user