primer commit

This commit is contained in:
2025-04-08 08:23:18 +02:00
parent 94c89b7ced
commit b6682fe7ff
30 changed files with 18573 additions and 1 deletions

6
.gitignore vendored
View File

@@ -1,3 +1,6 @@
.vscode/
build/
# ---> C++
# Prerequisites
*.d
@@ -65,7 +68,8 @@ $RECYCLE.BIN/
.LSOverride
# Icon must end with two \r
Icon
Icon
# Thumbnails
._*

49
CMakeLists.txt Normal file
View File

@@ -0,0 +1,49 @@
cmake_minimum_required(VERSION 3.20)
project(logo_02)
# Establecer el estándar de C++
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Opciones comunes de compilación
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Os -ffunction-sections -fdata-sections")
# Buscar SDL3 automáticamente
find_package(SDL3 REQUIRED)
# Si no se encuentra SDL3, generar un error
if (NOT SDL3_FOUND)
message(FATAL_ERROR "SDL3 no encontrado. Por favor, verifica su instalación.")
endif()
# Archivos fuente
file(GLOB SOURCE_FILES source/*.cpp)
# Comprobar si se encontraron archivos fuente
if(NOT SOURCE_FILES)
message(FATAL_ERROR "No se encontraron archivos fuente en el directorio 'source/'. Verifica la ruta.")
endif()
# Detectar la plataforma y configuraciones específicas
if(WIN32)
set(PLATFORM windows)
set(LINK_LIBS ${SDL3_LIBRARIES} mingw32 ws2_32)
elseif(UNIX AND NOT APPLE)
set(PLATFORM linux)
set(LINK_LIBS ${SDL3_LIBRARIES})
elseif(APPLE)
set(PLATFORM macos)
set(LINK_LIBS ${SDL3_LIBRARIES})
endif()
# Incluir directorios de SDL3
include_directories(${SDL3_INCLUDE_DIRS})
# Añadir el ejecutable reutilizando el nombre del proyecto
add_executable(${PROJECT_NAME} ${SOURCE_FILES})
# Especificar la ubicación del ejecutable (en la raíz del proyecto)
set_target_properties(${PROJECT_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR})
# Enlazar las bibliotecas necesarias
target_link_libraries(${PROJECT_NAME} ${LINK_LIBS})

22
Makefile Normal file
View File

@@ -0,0 +1,22 @@
# Variables comunes
SOURCE := source/*.cpp
EXECUTABLE_NAME := logo_02
CXXFLAGS := -std=c++20 -Wall -Os -ffunction-sections -fdata-sections # Opciones comunes de compilación
LDFLAGS := -lSDL3 # Flags de enlace comunes
OUTPUT_EXT :=
# Detectar plataforma y configurar
ifeq ($(OS),Windows_NT)
LDFLAGS += -lmingw32 -lws2_32
OUTPUT_EXT := .exe
else
OUTPUT_EXT := .out
endif
# Regla principal: compilar el ejecutable
all:
$(CXX) $(SOURCE) $(CXXFLAGS) $(LDFLAGS) -o $(EXECUTABLE_NAME)$(OUTPUT_EXT)
# Regla para limpiar archivos generados
clean:
rm -f $(EXECUTABLE_NAME)$(OUTPUT_EXT)

BIN
jailgames.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

73
jailgames.pal Normal file
View File

@@ -0,0 +1,73 @@
JASC-PAL
0100
70
6 6 8
166 15 161
77 5 242
213 171 88
101 119 93
205 217 83
163 51 174
48 200 48
59 23 37
115 23 45
180 32 42
223 62 35
250 106 10
249 163 27
255 213 65
255 252 64
214 242 100
156 219 67
89 193 53
20 160 46
26 122 62
36 82 59
18 32 32
20 52 100
40 92 196
36 159 222
32 214 199
166 252 219
255 255 255
254 243 192
250 214 184
245 160 151
232 106 115
188 74 155
121 58 128
64 51 83
36 34 52
34 28 26
50 43 40
113 65 59
187 117 71
219 164 99
244 210 156
218 224 234
179 185 209
139 147 175
109 117 141
74 84 98
51 57 65
66 36 51
91 49 56
142 82 82
186 117 106
233 181 163
227 230 255
185 191 251
132 155 228
88 141 190
71 125 133
35 103 78
50 132 100
93 175 141
146 220 186
205 247 226
228 210 170
199 176 139
160 134 98
121 103 85
90 78 68
66 57 52

134
source/audio.cpp Normal file
View File

@@ -0,0 +1,134 @@
#include "audio.h"
#include <SDL3/SDL_audio.h> // for SDL_AudioFormat
#include <SDL3/SDL_error.h> // for SDL_GetError
#include <SDL3/SDL_init.h> // for SDL_Init, SDL_INIT_AUDIO
#include <SDL3/SDL_log.h> // for SDL_LogInfo, SDL_LogCategory, SDL_LogError
#include <algorithm> // for clamp
#include "jail_audio.h" // for JA_FadeOutMusic, JA_Init, JA_PauseMusic
#include "options.h" // for Options, OptionsAudio, options, OptionsM...
struct JA_Music_t;
struct JA_Sound_t;
// [SINGLETON] Hay que definir las variables estáticas, desde el .h sólo la hemos declarado
Audio *Audio::audio_ = nullptr;
// [SINGLETON] Crearemos el objeto asset con esta función estática
void Audio::init()
{
Audio::audio_ = new Audio();
}
// [SINGLETON] Destruiremos el objeto asset con esta función estática
void Audio::destroy()
{
delete Audio::audio_;
}
// [SINGLETON] Con este método obtenemos el objeto asset y podemos trabajar con él
Audio *Audio::get()
{
return Audio::audio_;
}
// Constructor
Audio::Audio()
{
// Inicializa SDL
if (!SDL_Init(SDL_INIT_AUDIO))
{
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_AUDIO could not initialize! SDL Error: %s", SDL_GetError());
}
else
{
SDL_LogInfo(SDL_LOG_CATEGORY_TEST, "\n** SDL_AUDIO: INITIALIZING\n");
JA_Init(48000, SDL_AUDIO_S16LE, 2);
enable(options.audio.enabled);
setMusicVolume(options.audio.music.volume);
setSoundVolume(options.audio.sound.volume);
SDL_LogInfo(SDL_LOG_CATEGORY_TEST, "** SDL_AUDIO: INITIALIZATION COMPLETE\n");
}
}
// Destructor
Audio::~Audio()
{
JA_Quit();
}
// Reproduce la música
void Audio::playMusic(JA_Music_t *music, const int loop)
{
if (enabled_ && music_enabled_)
{
JA_PlayMusic(music, loop);
}
}
// Pausa la música
void Audio::pauseMusic()
{
if (enabled_ && music_enabled_)
{
JA_PauseMusic();
}
}
// Detiene la música
void Audio::stopMusic()
{
if (enabled_ && music_enabled_)
{
JA_StopMusic();
}
}
// Reproduce un sonido
void Audio::playSound(JA_Sound_t *sound)
{
if (enabled_ && sound_enabled_)
{
JA_PlaySound(sound);
}
}
// Detiene todos los sonidos
void Audio::stopAllSounds()
{
if (enabled_ && sound_enabled_)
{
JA_StopChannel(-1);
}
}
// Realiza un fundido de salida de la música
void Audio::fadeOutMusic(int milliseconds)
{
if (enabled_ && music_enabled_)
{
JA_FadeOutMusic(milliseconds);
}
}
// Establece el volumen de los sonidos
void Audio::setSoundVolume(int volume)
{
if (enabled_ && sound_enabled_)
{
volume = std::clamp(volume, 0, 100);
const int COVERTED_VOLUME = static_cast<int>((volume / 100.0) * 128);
JA_SetSoundVolume(COVERTED_VOLUME);
}
}
// Establece el volumen de la música
void Audio::setMusicVolume(int volume)
{
if (enabled_ && music_enabled_)
{
volume = std::clamp(volume, 0, 100);
const int COVERTED_VOLUME = static_cast<int>((volume / 100.0) * 128);
JA_SetMusicVolume(COVERTED_VOLUME);
}
}

63
source/audio.h Normal file
View File

@@ -0,0 +1,63 @@
#pragma once
struct JA_Music_t;
struct JA_Sound_t;
class Audio
{
private:
// [SINGLETON] Objeto audio privado
static Audio *audio_;
// Constructor
Audio(); // Constructor privado para el patrón Singleton
// Destructor
~Audio(); // Destructor privado para el patrón Singleton
// Variables
bool enabled_ = true; // Indica si el audio está habilitado
bool sound_enabled_ = true; // Indica si los sonidos están habilitados
bool music_enabled_ = true; // Indica si la música está habilitada
public:
// [SINGLETON] Crearemos el objeto con esta función estática
static void init(); // Inicializa el objeto Singleton
// [SINGLETON] Destruiremos el objeto con esta función estática
static void destroy(); // Destruye el objeto Singleton
// [SINGLETON] Con este método obtenemos el objeto y podemos trabajar con él
static Audio *get(); // Devuelve la instancia del Singleton
void playMusic(JA_Music_t *music, const int loop = -1); // Reproduce la música
void pauseMusic(); // Pausa la música
void stopMusic(); // Detiene la música
void playSound(JA_Sound_t *sound); // Reproduce un sonido
void stopAllSounds(); // Detiene todos los sonidos
void fadeOutMusic(int milliseconds); // Realiza un fundido de salida de la música
// Audio
void enable() { enabled_ = true; } // Habilita el audio
void disable() { enabled_ = false; } // Deshabilita el audio
void enable(bool value) { enabled_ = value; } // Habilita o deshabilita el audio
void toggleEnabled() { enabled_ = !enabled_; } // Alterna el estado del audio
// Sound
void enableSound() { sound_enabled_ = true; } // Habilita los sonidos
void disableSound() { sound_enabled_ = false; } // Deshabilita los sonidos
void enableSound(bool value) { sound_enabled_ = value; } // Habilita o deshabilita los sonidos
void toggleSound() { sound_enabled_ = !sound_enabled_; } // Alterna el estado de los sonidos
// Music
void enableMusic() { music_enabled_ = true; } // Habilita la música
void disableMusic() { music_enabled_ = false; } // Deshabilita la música
void enableMusic(bool value) { music_enabled_ = value; } // Habilita o deshabilita la música
void toggleMusic() { music_enabled_ = !music_enabled_; } // Alterna el estado de la música
// Volume
void setSoundVolume(int volume); // Establece el volumen de los sonidos
void setMusicVolume(int volume); // Establece el volumen de la música
};

316
source/gif.cpp Normal file
View File

@@ -0,0 +1,316 @@
#include "gif.h"
#include <iostream> // Para std::cout
#include <cstring> // Para memcpy, size_t
#include <stdexcept> // Para runtime_error
#include <string> // Para allocator, char_traits, operator==, basic_string
namespace GIF
{
// Función inline para reemplazar el macro READ.
// Actualiza el puntero 'buffer' tras copiar 'size' bytes a 'dst'.
inline void readBytes(const uint8_t *&buffer, void *dst, size_t size)
{
std::memcpy(dst, buffer, size);
buffer += size;
}
void Gif::decompress(int code_length, const uint8_t *input, int input_length, uint8_t *out)
{
// Verifica que el code_length tenga un rango razonable.
if (code_length < 2 || code_length > 12)
{
throw std::runtime_error("Invalid LZW code length");
}
int i, bit;
int prev = -1;
std::vector<DictionaryEntry> dictionary;
int dictionary_ind;
unsigned int mask = 0x01;
int reset_code_length = code_length;
int clear_code = 1 << code_length;
int stop_code = clear_code + 1;
int match_len = 0;
// Inicializamos el diccionario con el tamaño correspondiente.
dictionary.resize(1 << (code_length + 1));
for (dictionary_ind = 0; dictionary_ind < (1 << code_length); dictionary_ind++)
{
dictionary[dictionary_ind].byte = static_cast<uint8_t>(dictionary_ind);
dictionary[dictionary_ind].prev = -1;
dictionary[dictionary_ind].len = 1;
}
dictionary_ind += 2; // Reservamos espacio para clear y stop codes
// Bucle principal: procesar el stream comprimido.
while (input_length > 0)
{
int code = 0;
// Lee (code_length + 1) bits para formar el código.
for (i = 0; i < (code_length + 1); i++)
{
if (input_length <= 0)
{
throw std::runtime_error("Unexpected end of input in decompress");
}
bit = ((*input & mask) != 0) ? 1 : 0;
mask <<= 1;
if (mask == 0x100)
{
mask = 0x01;
input++;
input_length--;
}
code |= (bit << i);
}
if (code == clear_code)
{
// Reinicia el diccionario.
code_length = reset_code_length;
dictionary.resize(1 << (code_length + 1));
for (dictionary_ind = 0; dictionary_ind < (1 << code_length); dictionary_ind++)
{
dictionary[dictionary_ind].byte = static_cast<uint8_t>(dictionary_ind);
dictionary[dictionary_ind].prev = -1;
dictionary[dictionary_ind].len = 1;
}
dictionary_ind += 2;
prev = -1;
continue;
}
else if (code == stop_code)
{
break;
}
if (prev > -1 && code_length < 12)
{
if (code > dictionary_ind)
{
std::cerr << "code = " << std::hex << code
<< ", but dictionary_ind = " << dictionary_ind << std::endl;
throw std::runtime_error("LZW error: code exceeds dictionary_ind.");
}
int ptr;
if (code == dictionary_ind)
{
ptr = prev;
while (dictionary[ptr].prev != -1)
ptr = dictionary[ptr].prev;
dictionary[dictionary_ind].byte = dictionary[ptr].byte;
}
else
{
ptr = code;
while (dictionary[ptr].prev != -1)
ptr = dictionary[ptr].prev;
dictionary[dictionary_ind].byte = dictionary[ptr].byte;
}
dictionary[dictionary_ind].prev = prev;
dictionary[dictionary_ind].len = dictionary[prev].len + 1;
dictionary_ind++;
if ((dictionary_ind == (1 << (code_length + 1))) && (code_length < 11))
{
code_length++;
dictionary.resize(1 << (code_length + 1));
}
}
prev = code;
// Verifica que 'code' sea un índice válido antes de usarlo.
if (code < 0 || static_cast<size_t>(code) >= dictionary.size())
{
std::cerr << "Invalid LZW code " << code
<< ", dictionary size " << dictionary.size() << std::endl;
throw std::runtime_error("LZW error: invalid code encountered");
}
int curCode = code; // Variable temporal para recorrer la cadena.
match_len = dictionary[curCode].len;
while (curCode != -1)
{
// Se asume que dictionary[curCode].len > 0.
out[dictionary[curCode].len - 1] = dictionary[curCode].byte;
if (dictionary[curCode].prev == curCode)
{
std::cerr << "Internal error; self-reference detected." << std::endl;
throw std::runtime_error("Internal error in decompress: self-reference");
}
curCode = dictionary[curCode].prev;
}
out += match_len;
}
}
std::vector<uint8_t> Gif::readSubBlocks(const uint8_t *&buffer)
{
std::vector<uint8_t> data;
uint8_t block_size = *buffer;
buffer++;
while (block_size != 0)
{
data.insert(data.end(), buffer, buffer + block_size);
buffer += block_size;
block_size = *buffer;
buffer++;
}
return data;
}
std::vector<uint8_t> Gif::processImageDescriptor(const uint8_t *&buffer, const std::vector<RGB> &gct, int resolution_bits)
{
ImageDescriptor image_descriptor;
// Lee 9 bytes para el image descriptor.
readBytes(buffer, &image_descriptor, sizeof(ImageDescriptor));
uint8_t lzw_code_size;
readBytes(buffer, &lzw_code_size, sizeof(uint8_t));
std::vector<uint8_t> compressed_data = readSubBlocks(buffer);
int uncompressed_data_length = image_descriptor.image_width * image_descriptor.image_height;
std::vector<uint8_t> uncompressed_data(uncompressed_data_length);
decompress(lzw_code_size, compressed_data.data(), static_cast<int>(compressed_data.size()), uncompressed_data.data());
return uncompressed_data;
}
std::vector<uint32_t> Gif::loadPalette(const uint8_t *buffer)
{
uint8_t header[6];
std::memcpy(header, buffer, 6);
buffer += 6;
ScreenDescriptor screen_descriptor;
std::memcpy(&screen_descriptor, buffer, sizeof(ScreenDescriptor));
buffer += sizeof(ScreenDescriptor);
std::vector<uint32_t> global_color_table;
if (screen_descriptor.fields & 0x80)
{
int global_color_table_size = 1 << (((screen_descriptor.fields & 0x07) + 1));
global_color_table.resize(global_color_table_size);
for (int i = 0; i < global_color_table_size; ++i)
{
uint8_t r = buffer[0];
uint8_t g = buffer[1];
uint8_t b = buffer[2];
global_color_table[i] = (r << 16) | (g << 8) | b;
buffer += 3;
}
}
return global_color_table;
}
std::vector<uint8_t> Gif::processGifStream(const uint8_t *buffer, uint16_t &w, uint16_t &h)
{
// Leer la cabecera de 6 bytes ("GIF87a" o "GIF89a")
uint8_t header[6];
std::memcpy(header, buffer, 6);
buffer += 6;
// Opcional: Validar header
std::string headerStr(reinterpret_cast<char *>(header), 6);
if (headerStr != "GIF87a" && headerStr != "GIF89a")
{
throw std::runtime_error("Formato de archivo GIF inválido.");
}
// Leer el Screen Descriptor (7 bytes, empaquetado sin padding)
ScreenDescriptor screen_descriptor;
readBytes(buffer, &screen_descriptor, sizeof(ScreenDescriptor));
// Asigna ancho y alto
w = screen_descriptor.width;
h = screen_descriptor.height;
int color_resolution_bits = ((screen_descriptor.fields & 0x70) >> 4) + 1;
std::vector<RGB> global_color_table;
if (screen_descriptor.fields & 0x80)
{
int global_color_table_size = 1 << (((screen_descriptor.fields & 0x07) + 1));
global_color_table.resize(global_color_table_size);
std::memcpy(global_color_table.data(), buffer, 3 * global_color_table_size);
buffer += 3 * global_color_table_size;
}
// Supongamos que 'buffer' es el puntero actual y TRAILER es 0x3B
uint8_t block_type = *buffer++;
while (block_type != TRAILER)
{
if (block_type == EXTENSION_INTRODUCER) // 0x21
{
// Se lee la etiqueta de extensión, la cual indica el tipo de extensión.
uint8_t extension_label = *buffer++;
switch (extension_label)
{
case GRAPHIC_CONTROL: // 0xF9
{
// Procesar Graphic Control Extension:
uint8_t blockSize = *buffer++; // Normalmente, blockSize == 4
buffer += blockSize; // Saltamos los 4 bytes del bloque fijo
// Saltar los sub-bloques
uint8_t subBlockSize = *buffer++;
while (subBlockSize != 0)
{
buffer += subBlockSize;
subBlockSize = *buffer++;
}
break;
}
case APPLICATION_EXTENSION: // 0xFF
case COMMENT_EXTENSION: // 0xFE
case PLAINTEXT_EXTENSION: // 0x01
{
// Para estas extensiones, saltamos el bloque fijo y los sub-bloques.
uint8_t blockSize = *buffer++;
buffer += blockSize;
uint8_t subBlockSize = *buffer++;
while (subBlockSize != 0)
{
buffer += subBlockSize;
subBlockSize = *buffer++;
}
break;
}
default:
{
// Si la etiqueta de extensión es desconocida, saltarla también:
uint8_t blockSize = *buffer++;
buffer += blockSize;
uint8_t subBlockSize = *buffer++;
while (subBlockSize != 0)
{
buffer += subBlockSize;
subBlockSize = *buffer++;
}
break;
}
}
}
else if (block_type == IMAGE_DESCRIPTOR)
{
// Procesar el Image Descriptor y retornar los datos de imagen
return processImageDescriptor(buffer, global_color_table, color_resolution_bits);
}
else
{
std::cerr << "Unrecognized block type " << std::hex << static_cast<int>(block_type) << std::endl;
return std::vector<uint8_t>{};
}
block_type = *buffer++;
}
return std::vector<uint8_t>{};
}
std::vector<uint8_t> Gif::loadGif(const uint8_t *buffer, uint16_t &w, uint16_t &h)
{
return processGifStream(buffer, w, h);
}
} // namespace GIF

102
source/gif.h Normal file
View File

@@ -0,0 +1,102 @@
#pragma once
#include <cstdint> // Para uint8_t, uint16_t, uint32_t
#include <vector> // Para vector
namespace GIF
{
// Constantes definidas con constexpr, en lugar de macros
constexpr uint8_t EXTENSION_INTRODUCER = 0x21;
constexpr uint8_t IMAGE_DESCRIPTOR = 0x2C;
constexpr uint8_t TRAILER = 0x3B;
constexpr uint8_t GRAPHIC_CONTROL = 0xF9;
constexpr uint8_t APPLICATION_EXTENSION = 0xFF;
constexpr uint8_t COMMENT_EXTENSION = 0xFE;
constexpr uint8_t PLAINTEXT_EXTENSION = 0x01;
#pragma pack(push, 1)
struct ScreenDescriptor
{
uint16_t width;
uint16_t height;
uint8_t fields;
uint8_t background_color_index;
uint8_t pixel_aspect_ratio;
};
struct RGB
{
uint8_t r, g, b;
};
struct ImageDescriptor
{
uint16_t image_left_position;
uint16_t image_top_position;
uint16_t image_width;
uint16_t image_height;
uint8_t fields;
};
#pragma pack(pop)
struct DictionaryEntry
{
uint8_t byte;
int prev;
int len;
};
struct Extension
{
uint8_t extension_code;
uint8_t block_size;
};
struct GraphicControlExtension
{
uint8_t fields;
uint16_t delay_time;
uint8_t transparent_color_index;
};
struct ApplicationExtension
{
uint8_t application_id[8];
uint8_t version[3];
};
struct PlaintextExtension
{
uint16_t left, top, width, height;
uint8_t cell_width, cell_height;
uint8_t foreground_color, background_color;
};
class Gif
{
public:
// Descompone (uncompress) el bloque comprimido usando LZW.
// Este método puede lanzar std::runtime_error en caso de error.
void decompress(int code_length, const uint8_t *input, int input_length, uint8_t *out);
// Carga la paleta (global color table) a partir de un buffer,
// retornándola en un vector de uint32_t (cada color se compone de R, G, B).
std::vector<uint32_t> loadPalette(const uint8_t *buffer);
// Carga el stream GIF; devuelve un vector con los datos de imagen sin comprimir y
// asigna el ancho y alto mediante referencias.
std::vector<uint8_t> loadGif(const uint8_t *buffer, uint16_t &w, uint16_t &h);
private:
// Lee los sub-bloques de datos y los acumula en un std::vector<uint8_t>.
std::vector<uint8_t> readSubBlocks(const uint8_t *&buffer);
// Procesa el Image Descriptor y retorna el vector de datos sin comprimir.
std::vector<uint8_t> processImageDescriptor(const uint8_t *&buffer, const std::vector<RGB> &gct, int resolution_bits);
// Procesa el stream completo del GIF y devuelve los datos sin comprimir.
std::vector<uint8_t> processGifStream(const uint8_t *buffer, uint16_t &w, uint16_t &h);
};
} // namespace GIF

47
source/global_inputs.cpp Normal file
View File

@@ -0,0 +1,47 @@
#include "global_inputs.h"
#include <SDL3/SDL_keycode.h> // for SDLK_ESCAPE, SDLK_F1, SDLK_F2, SDLK_F3
#include "options.h" // for Options, OptionsLogo, options
#include "screen.h" // for Screen
namespace globalInputs
{
// Comprueba los inputs que se pueden introducir en cualquier sección del juego
void check(const SDL_Event &event)
{
if (event.type == SDL_EVENT_KEY_DOWN && event.key.repeat == 0)
{
switch (event.key.key)
{
// Salir
case SDLK_ESCAPE:
options.logo.running = false;
return;
// Decrementar el tamaño de la ventana
case SDLK_F1:
Screen::get()->decWindowZoom();
return;
// Incrementar el tamaño de la ventana
case SDLK_F2:
Screen::get()->incWindowZoom();
return;
// Cambiar entre pantalla completa y ventana
case SDLK_F3:
Screen::get()->toggleFullscreen();
return;
// Integer Scale
case SDLK_F5:
Screen::get()->toggleIntegerScale();
return;
// VSync
case SDLK_F6:
Screen::get()->toggleVSync();
return;
}
}
}
}

9
source/global_inputs.h Normal file
View File

@@ -0,0 +1,9 @@
#pragma once
#include <SDL3/SDL_events.h> // for SDL_Event
namespace globalInputs
{
// Comprueba los inputs que se pueden introducir en cualquier sección del juego
void check(const SDL_Event &event);
}

544
source/jail_audio.cpp Normal file
View File

@@ -0,0 +1,544 @@
#ifndef JA_USESDLMIXER
#include "jail_audio.h"
#include <SDL3/SDL_iostream.h> // Para SDL_IOFromMem
#include <SDL3/SDL_log.h> // Para SDL_Log, SDL_SetLogPriority, SDL_LogC...
#include <SDL3/SDL_timer.h> // Para SDL_GetTicks, SDL_AddTimer, SDL_Remov...
#include <stdint.h> // Para uint32_t, uint8_t
#include <stdio.h> // Para NULL, fseek, printf, fclose, fopen
#include <stdlib.h> // Para free, malloc
#include "stb_vorbis.c" // Para stb_vorbis_decode_memory
#define JA_MAX_SIMULTANEOUS_CHANNELS 20
struct JA_Sound_t
{
SDL_AudioSpec spec{SDL_AUDIO_S16, 2, 48000};
Uint32 length{0};
Uint8 *buffer{NULL};
};
struct JA_Channel_t
{
JA_Sound_t *sound{nullptr};
int pos{0};
int times{0};
SDL_AudioStream *stream{nullptr};
JA_Channel_state state{JA_CHANNEL_FREE};
};
struct JA_Music_t
{
SDL_AudioSpec spec{SDL_AUDIO_S16, 2, 48000};
Uint32 length{0};
Uint8 *buffer{nullptr};
int pos{0};
int times{0};
SDL_AudioStream *stream{nullptr};
JA_Music_state state{JA_MUSIC_INVALID};
};
JA_Music_t *current_music{nullptr};
JA_Channel_t channels[JA_MAX_SIMULTANEOUS_CHANNELS];
SDL_AudioSpec JA_audioSpec{SDL_AUDIO_S16, 2, 48000};
float JA_musicVolume{1.0f};
float JA_soundVolume{0.5f};
bool JA_musicEnabled{true};
bool JA_soundEnabled{true};
SDL_AudioDeviceID sdlAudioDevice{0};
SDL_TimerID JA_timerID{0};
bool fading = false;
int fade_start_time;
int fade_duration;
int fade_initial_volume;
/*
void audioCallback(void * userdata, uint8_t * stream, int len) {
SDL_memset(stream, 0, len);
if (current_music != NULL && current_music->state == JA_MUSIC_PLAYING) {
const int size = SDL_min(len, current_music->samples*2-current_music->pos);
SDL_MixAudioFormat(stream, (Uint8*)(current_music->output+current_music->pos), AUDIO_S16, size, JA_musicVolume);
current_music->pos += size/2;
if (size < len) {
if (current_music->times != 0) {
SDL_MixAudioFormat(stream+size, (Uint8*)current_music->output, AUDIO_S16, len-size, JA_musicVolume);
current_music->pos = (len-size)/2;
if (current_music->times > 0) current_music->times--;
} else {
current_music->pos = 0;
current_music->state = JA_MUSIC_STOPPED;
}
}
}
// Mixar els channels mi amol
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++) {
if (channels[i].state == JA_CHANNEL_PLAYING) {
const int size = SDL_min(len, channels[i].sound->length - channels[i].pos);
SDL_MixAudioFormat(stream, channels[i].sound->buffer + channels[i].pos, AUDIO_S16, size, JA_soundVolume);
channels[i].pos += size;
if (size < len) {
if (channels[i].times != 0) {
SDL_MixAudioFormat(stream + size, channels[i].sound->buffer, AUDIO_S16, len-size, JA_soundVolume);
channels[i].pos = len-size;
if (channels[i].times > 0) channels[i].times--;
} else {
JA_StopChannel(i);
}
}
}
}
}
*/
Uint32 JA_UpdateCallback(void *userdata, SDL_TimerID timerID, Uint32 interval)
{
if (JA_musicEnabled && current_music && current_music->state == JA_MUSIC_PLAYING)
{
if (fading)
{
int time = SDL_GetTicks();
if (time > (fade_start_time + fade_duration))
{
fading = false;
JA_StopMusic();
return 30;
}
else
{
const int time_passed = time - fade_start_time;
const float percent = (float)time_passed / (float)fade_duration;
SDL_SetAudioStreamGain(current_music->stream, 1.0 - percent);
}
}
if (current_music->times != 0)
{
if (SDL_GetAudioStreamAvailable(current_music->stream) < static_cast<int>(current_music->length / 2))
{
SDL_PutAudioStreamData(current_music->stream, current_music->buffer, current_music->length);
}
if (current_music->times > 0)
current_music->times--;
}
else
{
if (SDL_GetAudioStreamAvailable(current_music->stream) == 0)
JA_StopMusic();
}
}
if (JA_soundEnabled)
{
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; ++i)
if (channels[i].state == JA_CHANNEL_PLAYING)
{
if (channels[i].times != 0)
{
if (SDL_GetAudioStreamAvailable(channels[i].stream) < static_cast<int>(channels[i].sound->length / 2))
SDL_PutAudioStreamData(channels[i].stream, channels[i].sound->buffer, channels[i].sound->length);
if (channels[i].times > 0)
channels[i].times--;
}
}
else
{
if (channels[i].stream && SDL_GetAudioStreamAvailable(channels[i].stream) == 0)
{
JA_StopChannel(i);
}
}
}
return 30;
}
void JA_Init(const int freq, const SDL_AudioFormat format, const int channels)
{
#ifdef DEBUG
SDL_SetLogPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_DEBUG);
#endif
SDL_LogInfo(SDL_LOG_CATEGORY_TEST, "Iniciant JailAudio...");
JA_audioSpec = {format, channels, freq};
if (!sdlAudioDevice)
SDL_CloseAudioDevice(sdlAudioDevice);
sdlAudioDevice = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &JA_audioSpec);
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, (sdlAudioDevice == 0) ? "Failed to initialize SDL audio!\n" : "OK!\n");
// SDL_PauseAudioDevice(sdlAudioDevice);
JA_timerID = SDL_AddTimer(30, JA_UpdateCallback, nullptr);
}
void JA_Quit()
{
if (JA_timerID)
SDL_RemoveTimer(JA_timerID);
if (!sdlAudioDevice)
SDL_CloseAudioDevice(sdlAudioDevice);
sdlAudioDevice = 0;
}
JA_Music_t *JA_LoadMusic(Uint8 *buffer, Uint32 length)
{
JA_Music_t *music = new JA_Music_t();
int chan, samplerate;
short *output;
music->length = stb_vorbis_decode_memory(buffer, length, &chan, &samplerate, &output) * chan * 2;
music->spec.channels = chan;
music->spec.freq = samplerate;
music->spec.format = SDL_AUDIO_S16;
music->buffer = (Uint8 *)SDL_malloc(music->length);
SDL_memcpy(music->buffer, output, music->length);
free(output);
music->pos = 0;
music->state = JA_MUSIC_STOPPED;
return music;
}
JA_Music_t *JA_LoadMusic(const char *filename)
{
// [RZC 28/08/22] Carreguem primer el arxiu en memòria i després el descomprimim. Es algo més rapid.
FILE *f = fopen(filename, "rb");
fseek(f, 0, SEEK_END);
long fsize = ftell(f);
fseek(f, 0, SEEK_SET);
Uint8 *buffer = (Uint8 *)malloc(fsize + 1);
if (fread(buffer, fsize, 1, f) != 1)
return NULL;
fclose(f);
JA_Music_t *music = JA_LoadMusic(buffer, fsize);
free(buffer);
return music;
}
void JA_PlayMusic(JA_Music_t *music, const int loop)
{
if (!JA_musicEnabled)
return;
JA_StopMusic();
current_music = music;
current_music->pos = 0;
current_music->state = JA_MUSIC_PLAYING;
current_music->times = loop;
current_music->stream = SDL_CreateAudioStream(&current_music->spec, &JA_audioSpec);
if (!SDL_PutAudioStreamData(current_music->stream, current_music->buffer, current_music->length))
printf("[ERROR] SDL_PutAudioStreamData failed!\n");
SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume);
if (!SDL_BindAudioStream(sdlAudioDevice, current_music->stream))
printf("[ERROR] SDL_BindAudioStream failed!\n");
}
void JA_PauseMusic()
{
if (!JA_musicEnabled)
return;
if (!current_music || current_music->state == JA_MUSIC_INVALID)
return;
current_music->state = JA_MUSIC_PAUSED;
// SDL_PauseAudioStreamDevice(current_music->stream);
SDL_UnbindAudioStream(current_music->stream);
}
void JA_ResumeMusic()
{
if (!JA_musicEnabled)
return;
if (!current_music || current_music->state == JA_MUSIC_INVALID)
return;
current_music->state = JA_MUSIC_PLAYING;
// SDL_ResumeAudioStreamDevice(current_music->stream);
SDL_BindAudioStream(sdlAudioDevice, current_music->stream);
}
void JA_StopMusic()
{
if (!JA_musicEnabled)
return;
if (!current_music || current_music->state == JA_MUSIC_INVALID)
return;
current_music->pos = 0;
current_music->state = JA_MUSIC_STOPPED;
// SDL_PauseAudioStreamDevice(current_music->stream);
SDL_DestroyAudioStream(current_music->stream);
current_music->stream = nullptr;
}
void JA_FadeOutMusic(const int milliseconds)
{
if (!JA_musicEnabled)
return;
if (current_music == NULL || current_music->state == JA_MUSIC_INVALID)
return;
fading = true;
fade_start_time = SDL_GetTicks();
fade_duration = milliseconds;
fade_initial_volume = JA_musicVolume;
}
JA_Music_state JA_GetMusicState()
{
if (!JA_musicEnabled)
return JA_MUSIC_DISABLED;
if (!current_music)
return JA_MUSIC_INVALID;
return current_music->state;
}
void JA_DeleteMusic(JA_Music_t *music)
{
if (current_music == music)
current_music = nullptr;
SDL_free(music->buffer);
if (music->stream)
SDL_DestroyAudioStream(music->stream);
delete music;
}
float JA_SetMusicVolume(float volume)
{
JA_musicVolume = SDL_clamp(volume, 0.0f, 1.0f);
if (current_music)
SDL_SetAudioStreamGain(current_music->stream, JA_musicVolume);
return JA_musicVolume;
}
void JA_SetMusicPosition(float value)
{
if (!current_music)
return;
current_music->pos = value * current_music->spec.freq;
}
float JA_GetMusicPosition()
{
if (!current_music)
return 0;
return float(current_music->pos) / float(current_music->spec.freq);
}
void JA_EnableMusic(const bool value)
{
if (!value && current_music && (current_music->state == JA_MUSIC_PLAYING))
JA_StopMusic();
JA_musicEnabled = value;
}
JA_Sound_t *JA_NewSound(Uint8 *buffer, Uint32 length)
{
JA_Sound_t *sound = new JA_Sound_t();
sound->buffer = buffer;
sound->length = length;
return sound;
}
JA_Sound_t *JA_LoadSound(uint8_t *buffer, uint32_t size)
{
JA_Sound_t *sound = new JA_Sound_t();
SDL_LoadWAV_IO(SDL_IOFromMem(buffer, size), 1, &sound->spec, &sound->buffer, &sound->length);
return sound;
}
JA_Sound_t *JA_LoadSound(const char *filename)
{
JA_Sound_t *sound = new JA_Sound_t();
SDL_LoadWAV(filename, &sound->spec, &sound->buffer, &sound->length);
return sound;
}
int JA_PlaySound(JA_Sound_t *sound, const int loop)
{
if (!JA_soundEnabled)
return -1;
int channel = 0;
while (channel < JA_MAX_SIMULTANEOUS_CHANNELS && channels[channel].state != JA_CHANNEL_FREE)
{
channel++;
}
if (channel == JA_MAX_SIMULTANEOUS_CHANNELS)
channel = 0;
JA_StopChannel(channel);
channels[channel].sound = sound;
channels[channel].times = loop;
channels[channel].pos = 0;
channels[channel].state = JA_CHANNEL_PLAYING;
channels[channel].stream = SDL_CreateAudioStream(&channels[channel].sound->spec, &JA_audioSpec);
SDL_PutAudioStreamData(channels[channel].stream, channels[channel].sound->buffer, channels[channel].sound->length);
SDL_SetAudioStreamGain(channels[channel].stream, JA_soundVolume);
SDL_BindAudioStream(sdlAudioDevice, channels[channel].stream);
return channel;
}
int JA_PlaySoundOnChannel(JA_Sound_t *sound, const int channel, const int loop)
{
if (!JA_soundEnabled)
return -1;
if (channel < 0 || channel >= JA_MAX_SIMULTANEOUS_CHANNELS)
return -1;
JA_StopChannel(channel);
channels[channel].sound = sound;
channels[channel].times = loop;
channels[channel].pos = 0;
channels[channel].state = JA_CHANNEL_PLAYING;
channels[channel].stream = SDL_CreateAudioStream(&channels[channel].sound->spec, &JA_audioSpec);
SDL_PutAudioStreamData(channels[channel].stream, channels[channel].sound->buffer, channels[channel].sound->length);
SDL_SetAudioStreamGain(channels[channel].stream, JA_soundVolume);
SDL_BindAudioStream(sdlAudioDevice, channels[channel].stream);
return channel;
}
void JA_DeleteSound(JA_Sound_t *sound)
{
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++)
{
if (channels[i].sound == sound)
JA_StopChannel(i);
}
SDL_free(sound->buffer);
delete sound;
}
void JA_PauseChannel(const int channel)
{
if (!JA_soundEnabled)
return;
if (channel == -1)
{
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++)
if (channels[i].state == JA_CHANNEL_PLAYING)
{
channels[i].state = JA_CHANNEL_PAUSED;
// SDL_PauseAudioStreamDevice(channels[i].stream);
SDL_UnbindAudioStream(channels[i].stream);
}
}
else if (channel >= 0 && channel < JA_MAX_SIMULTANEOUS_CHANNELS)
{
if (channels[channel].state == JA_CHANNEL_PLAYING)
{
channels[channel].state = JA_CHANNEL_PAUSED;
// SDL_PauseAudioStreamDevice(channels[channel].stream);
SDL_UnbindAudioStream(channels[channel].stream);
}
}
}
void JA_ResumeChannel(const int channel)
{
if (!JA_soundEnabled)
return;
if (channel == -1)
{
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++)
if (channels[i].state == JA_CHANNEL_PAUSED)
{
channels[i].state = JA_CHANNEL_PLAYING;
// SDL_ResumeAudioStreamDevice(channels[i].stream);
SDL_BindAudioStream(sdlAudioDevice, channels[i].stream);
}
}
else if (channel >= 0 && channel < JA_MAX_SIMULTANEOUS_CHANNELS)
{
if (channels[channel].state == JA_CHANNEL_PAUSED)
{
channels[channel].state = JA_CHANNEL_PLAYING;
// SDL_ResumeAudioStreamDevice(channels[channel].stream);
SDL_BindAudioStream(sdlAudioDevice, channels[channel].stream);
}
}
}
void JA_StopChannel(const int channel)
{
if (!JA_soundEnabled)
return;
if (channel == -1)
{
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++)
{
if (channels[i].state != JA_CHANNEL_FREE)
SDL_DestroyAudioStream(channels[i].stream);
channels[i].stream = nullptr;
channels[i].state = JA_CHANNEL_FREE;
channels[i].pos = 0;
channels[i].sound = NULL;
}
}
else if (channel >= 0 && channel < JA_MAX_SIMULTANEOUS_CHANNELS)
{
if (channels[channel].state != JA_CHANNEL_FREE)
SDL_DestroyAudioStream(channels[channel].stream);
channels[channel].stream = nullptr;
channels[channel].state = JA_CHANNEL_FREE;
channels[channel].pos = 0;
channels[channel].sound = NULL;
}
}
JA_Channel_state JA_GetChannelState(const int channel)
{
if (!JA_soundEnabled)
return JA_SOUND_DISABLED;
if (channel < 0 || channel >= JA_MAX_SIMULTANEOUS_CHANNELS)
return JA_CHANNEL_INVALID;
return channels[channel].state;
}
float JA_SetSoundVolume(float volume)
{
JA_soundVolume = SDL_clamp(volume, 0.0f, 1.0f);
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++)
if ((channels[i].state == JA_CHANNEL_PLAYING) || (channels[i].state == JA_CHANNEL_PAUSED))
SDL_SetAudioStreamGain(channels[i].stream, JA_soundVolume);
return JA_soundVolume;
}
void JA_EnableSound(const bool value)
{
for (int i = 0; i < JA_MAX_SIMULTANEOUS_CHANNELS; i++)
{
if (channels[i].state == JA_CHANNEL_PLAYING)
JA_StopChannel(i);
}
JA_soundEnabled = value;
}
float JA_SetVolume(float volume)
{
JA_SetSoundVolume(JA_SetMusicVolume(volume) / 2.0f);
return JA_musicVolume;
}
#endif

58
source/jail_audio.h Normal file
View File

@@ -0,0 +1,58 @@
#pragma once
#include <SDL3/SDL_audio.h> // for SDL_AudioFormat
#include <SDL3/SDL_stdinc.h> // for Uint32, Uint8
struct JA_Music_t; // lines 4-4
struct JA_Sound_t; // lines 5-5
enum JA_Channel_state
{
JA_CHANNEL_INVALID,
JA_CHANNEL_FREE,
JA_CHANNEL_PLAYING,
JA_CHANNEL_PAUSED,
JA_SOUND_DISABLED
};
enum JA_Music_state
{
JA_MUSIC_INVALID,
JA_MUSIC_PLAYING,
JA_MUSIC_PAUSED,
JA_MUSIC_STOPPED,
JA_MUSIC_DISABLED
};
struct JA_Sound_t;
struct JA_Music_t;
void JA_Init(const int freq, const SDL_AudioFormat format, const int channels);
void JA_Quit();
JA_Music_t *JA_LoadMusic(const char *filename);
JA_Music_t *JA_LoadMusic(Uint8 *buffer, Uint32 length);
void JA_PlayMusic(JA_Music_t *music, const int loop = -1);
void JA_PauseMusic();
void JA_ResumeMusic();
void JA_StopMusic();
void JA_FadeOutMusic(const int milliseconds);
JA_Music_state JA_GetMusicState();
void JA_DeleteMusic(JA_Music_t *music);
float JA_SetMusicVolume(float volume);
void JA_SetMusicPosition(float value);
float JA_GetMusicPosition();
void JA_EnableMusic(const bool value);
JA_Sound_t *JA_NewSound(Uint8 *buffer, Uint32 length);
JA_Sound_t *JA_LoadSound(Uint8 *buffer, Uint32 length);
JA_Sound_t *JA_LoadSound(const char *filename);
int JA_PlaySound(JA_Sound_t *sound, const int loop = 0);
int JA_PlaySoundOnChannel(JA_Sound_t *sound, const int channel, const int loop = 0);
void JA_PauseChannel(const int channel);
void JA_ResumeChannel(const int channel);
void JA_StopChannel(const int channel);
JA_Channel_state JA_GetChannelState(const int channel);
void JA_DeleteSound(JA_Sound_t *sound);
float JA_SetSoundVolume(float volume);
void JA_EnableSound(const bool value);
float JA_SetVolume(float volume);

112
source/logo.cpp Normal file
View File

@@ -0,0 +1,112 @@
#include "logo.h"
#include <SDL3/SDL_events.h> // Manejo de eventos de SDL (teclado, ratón, etc.)
#include <SDL3/SDL_init.h> // Inicialización y limpieza de SDL
#include <SDL3/SDL_log.h> // Manejo de logs en SDL
#include <SDL3/SDL_timer.h> // Funciones de temporización de SDL
#include "global_inputs.h" // Funciones para manejar entradas globales
#include "mouse.h" // Manejo de eventos del ratón
#include "options.h" // Configuración global de opciones
#include "s_sprite.h" // Clase para manejar sprites
#include "screen.h" // Clase para manejar la pantalla
#include "surface.h" // Clase para manejar superficies
// Constructor de la clase Logo: inicializa los recursos necesarios
Logo::Logo()
{
init();
}
// Destructor de la clase Logo: libera los recursos utilizados
Logo::~Logo()
{
close();
}
// Método privado para inicializar los recursos del logotipo
void Logo::init()
{
// Configura las prioridades de los logs
SDL_SetLogPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO);
SDL_SetLogPriority(SDL_LOG_CATEGORY_TEST, SDL_LOG_PRIORITY_ERROR);
// Inicializa la pantalla
Screen::get()->init();
// Carga la superficie del logotipo desde un archivo y la escala
logo_surface = std::make_shared<Surface>("jailgames.gif");
logo_surface->setTransparentColor(0);
// Crea el sprite del logotipo y lo posiciona en el centro de la pantalla
logo_sprite = std::make_unique<SSprite>(logo_surface);
logo_sprite->setPosition(
(options.logo.width - logo_sprite->getWidth()) / 2,
(options.logo.height - logo_sprite->getHeight()) / 2);
// Log de inicio del logotipo
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Logo start");
}
// Método privado para liberar los recursos del logotipo
void Logo::close()
{
// Destruye la pantalla
Screen::get()->destroy();
// Log de finalización del logotipo
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "\nBye!");
// Finaliza SDL
SDL_Quit();
}
// Método privado para manejar los eventos de entrada
void Logo::checkEvents()
{
SDL_Event event;
while (SDL_PollEvent(&event)) // Procesa todos los eventos en la cola
{
switch (event.type)
{
case SDL_EVENT_QUIT: // Evento de salida (cerrar la ventana)
options.logo.running = false; // Detiene el bucle principal
return;
}
// Maneja entradas globales y eventos del ratón
globalInputs::check(event);
Mouse::handleEvent(event);
}
}
// Método privado para actualizar el estado del logotipo
void Logo::update()
{
// Comprueba si ha pasado suficiente tiempo para actualizar
if (SDL_GetTicks() - ticks > options.logo.speed)
{
ticks = SDL_GetTicks(); // Actualiza el contador de ticks
// Actualiza la pantalla
Screen::get()->update();
}
}
// Método privado para renderizar el logotipo en pantalla
void Logo::render()
{
Screen::get()->start(); // Inicia el proceso de renderizado
logo_sprite->render(); // Renderiza el sprite del logotipo
Screen::get()->render(); // Finaliza el renderizado
}
// Método principal que ejecuta el ciclo de vida del logotipo
int Logo::run()
{
while (options.logo.running) // Bucle principal mientras el logotipo esté activo
{
update(); // Actualiza el estado del logotipo
checkEvents(); // Maneja los eventos de entrada
render(); // Renderiza el logotipo
}
return 0; // Devuelve 0 al finalizar
}

28
source/logo.h Normal file
View File

@@ -0,0 +1,28 @@
#pragma once // Evita inclusiones múltiples del archivo de encabezado
#include <SDL3/SDL_stdinc.h> // Incluye definiciones estándar de SDL, como Uint64
#include <memory> // Proporciona soporte para punteros inteligentes (shared_ptr, unique_ptr)
#include "s_sprite.h" // Declara la clase SSprite, utilizada para manejar sprites
class Surface; // Declaración adelantada de la clase Surface
// Clase Logo: Maneja la lógica principal de un logotipo animado o interactivo
class Logo
{
private:
Uint64 ticks = 0; // Contador de ticks para medir el tiempo o controlar animaciones
std::shared_ptr<Surface> logo_surface = nullptr; // Superficie del logotipo, compartida entre múltiples objetos
std::unique_ptr<SSprite> logo_sprite = nullptr; // Sprite del logotipo, propiedad exclusiva de esta clase
// Métodos privados para manejar el ciclo de vida y la lógica del logotipo
void init(); // Inicializa los recursos necesarios para el logotipo
void close(); // Libera los recursos utilizados por el logotipo
void checkEvents(); // Maneja los eventos de entrada (teclado, ratón, etc.)
void update(); // Actualiza el estado del logotipo (animaciones, lógica, etc.)
void render(); // Renderiza el logotipo en pantalla
public:
Logo(); // Constructor: Inicializa la clase Logo
~Logo(); // Destructor: Limpia los recursos utilizados por la clase Logo
int run(); // Método principal que ejecuta el ciclo de vida del logotipo
};

11
source/main.cpp Normal file
View File

@@ -0,0 +1,11 @@
#include <memory> // for make_unique, unique_ptr
#include "logo.h" // for Logo
int main()
{
// Crea el objeto Logo
auto logo = std::make_unique<Logo>();
// Bucle principal
return logo->run();
}

33
source/mouse.cpp Normal file
View File

@@ -0,0 +1,33 @@
#include "mouse.h"
#include <SDL3/SDL_mouse.h> // Para SDL_ShowCursor
#include <SDL3/SDL_timer.h> // Para SDL_GetTicks
namespace Mouse
{
Uint64 cursor_hide_time = 3000; // Tiempo en milisegundos para ocultar el cursor
Uint64 last_mouse_move_time = 0; // Última vez que el ratón se movió
bool cursor_visible = true; // Estado del cursor
void handleEvent(const SDL_Event &event)
{
if (event.type == SDL_EVENT_MOUSE_MOTION)
{
last_mouse_move_time = SDL_GetTicks();
if (!cursor_visible)
{
SDL_ShowCursor();
cursor_visible = true;
}
}
}
void updateCursorVisibility()
{
Uint64 current_time = SDL_GetTicks();
if (cursor_visible && (current_time - last_mouse_move_time > cursor_hide_time))
{
SDL_HideCursor();
cursor_visible = false;
}
}
}

14
source/mouse.h Normal file
View File

@@ -0,0 +1,14 @@
#pragma once
#include <SDL3/SDL_events.h> // Para SDL_Event
#include <SDL3/SDL_stdinc.h> // Para Uint32
namespace Mouse
{
extern Uint64 cursor_hide_time; // Tiempo en milisegundos para ocultar el cursor
extern Uint64 last_mouse_move_time; // Última vez que el ratón se movió
extern bool cursor_visible; // Estado del cursor
void handleEvent(const SDL_Event &event);
void updateCursorVisibility();
}

10
source/options.cpp Normal file
View File

@@ -0,0 +1,10 @@
#include "options.h"
// Variables
Options options;
// Crea e inicializa las opciones del programa
void initOptions()
{
options = Options();
}

209
source/options.h Normal file
View File

@@ -0,0 +1,209 @@
#pragma once
#include <SDL3/SDL_stdinc.h> // for Uint64
#include <SDL3/SDL_surface.h> // for SDL_ScaleMode
#include <algorithm> // for clamp
#include <string> // for string, basic_string
// Constantes
constexpr int DEFAULT_GAME_WIDTH = 480; // Ancho de la ventana por defecto
constexpr int DEFAULT_GAME_HEIGHT = 270; // Alto de la ventana por defecto
constexpr Uint64 DEFAUL_LOGO_SPEED = 1000 / 60; // Velocidad a la que se ejecuta el logo
constexpr bool DEFAUL_LOGO_RUNNING = true; // Flag para el bucle principal
constexpr int DEFAULT_WINDOW_ZOOM = 2; // Zoom de la ventana por defecto
constexpr int DEFAULT_VIDEO_FULLSCREEN = false; // Modo de pantalla completa por defecto
constexpr SDL_ScaleMode DEFAULT_SCALE_MODE = SDL_SCALEMODE_NEAREST; // Modo de pantalla completa por defecto
constexpr bool DEFAULT_VIDEO_VERTICAL_SYNC = true; // Vsync activado por defecto
constexpr bool DEFAULT_VIDEO_INTEGER_SCALE = true; // Escalado entero activado por defecto
constexpr bool DEFAULT_VIDEO_KEEP_ASPECT = true; // Mantener aspecto activado por defecto
constexpr int DEFAULT_SOUND_VOLUME = 100; // Volumen por defecto de los efectos de sonido
constexpr bool DEFAULT_SOUND_ENABLED = true; // Sonido habilitado por defecto
constexpr int DEFAULT_MUSIC_VOLUME = 80; // Volumen por defecto de la musica
constexpr bool DEFAULT_MUSIC_ENABLED = true; // Musica habilitada por defecto
constexpr int DEFAULT_AUDIO_VOLUME = 100; // Volumen por defecto
constexpr bool DEFAULT_AUDIO_ENABLED = true; // Audio por defecto
constexpr bool DEFAULT_CONSOLE = false; // Consola desactivada por defecto
constexpr const char *DEFAULT_WINDOW_CAPTION = "logo_02"; // Versión por defecto
// Estructura con opciones de la ventana
struct OptionsWindow
{
std::string caption; // Texto que aparece en la barra de titulo de la ventana
int zoom; // Zoom de la ventana
int max_zoom; // Máximo tamaño de zoom para la ventana
// Constructor por defecto
OptionsWindow()
: caption(DEFAULT_WINDOW_CAPTION),
zoom(DEFAULT_WINDOW_ZOOM),
max_zoom(DEFAULT_WINDOW_ZOOM) {}
// Constructor
OptionsWindow(std::string caption, int zoom, int max_zoom)
: caption(caption),
zoom(zoom),
max_zoom(max_zoom) {}
};
// Estructura para las opciones de video
struct OptionsVideo
{
bool fullscreen; // Contiene el valor del modo de pantalla completav
SDL_ScaleMode scale_mode; // Filtro usado para el escalado de la imagen
bool vertical_sync; // Indica si se quiere usar vsync o no
bool integer_scale; // Indica si el escalado de la imagen ha de ser entero en el modo a pantalla completa
bool keep_aspect; // Indica si se ha de mantener la relación de aspecto al poner el modo a pantalla completa
std::string info; // Información sobre el modo de video
// Constructor por defecto
OptionsVideo()
: fullscreen(DEFAULT_VIDEO_FULLSCREEN),
scale_mode(DEFAULT_SCALE_MODE),
vertical_sync(DEFAULT_VIDEO_VERTICAL_SYNC),
integer_scale(DEFAULT_VIDEO_INTEGER_SCALE),
keep_aspect(DEFAULT_VIDEO_KEEP_ASPECT),
info(std::string()) {}
// Constructor
OptionsVideo(bool fs, SDL_ScaleMode sm, bool vs, bool is, bool ka, std::string i)
: fullscreen(fs),
scale_mode(sm),
vertical_sync(vs),
integer_scale(is),
keep_aspect(ka),
info(i) {}
};
// Estructura para las opciones de musica
struct OptionsMusic
{
bool enabled; // Indica si la música suena o no
int volume; // Volumen al que suena la música (0 a 128 internamente)
// Constructor por defecto
OptionsMusic()
: enabled(DEFAULT_MUSIC_ENABLED),
volume(convertVolume(DEFAULT_MUSIC_VOLUME)) {} // Usa el método estático para la conversión
// Constructor con parámetros
OptionsMusic(bool e, int v)
: enabled(e),
volume(convertVolume(v)) {} // Convierte el volumen usando el método estático
// Método para establecer el volumen
void setVolume(int v)
{
v = std::clamp(v, 0, 100); // Ajusta v al rango [0, 100]
volume = convertVolume(v); // Convierte al rango interno
}
// Método estático para convertir de 0-100 a 0-128
static int convertVolume(int v)
{
return (v * 128) / 100;
}
};
// Estructura para las opciones de sonido
struct OptionsSound
{
bool enabled; // Indica si los sonidos suenan o no
int volume; // Volumen al que suenan los sonidos (0 a 128 internamente)
// Constructor por defecto
OptionsSound()
: enabled(DEFAULT_SOUND_ENABLED),
volume(convertVolume(DEFAULT_SOUND_VOLUME)) {} // Usa el método estático para la conversión
// Constructor con parámetros
OptionsSound(bool e, int v)
: enabled(e),
volume(convertVolume(v)) {} // También lo integra aquí
// Método para establecer el volumen
void setVolume(int v)
{
v = std::clamp(v, 0, 100); // Ajusta v al rango [0, 100]
volume = convertVolume(v); // Convierte al rango interno
}
// Método estático para convertir de 0-100 a 0-128
static int convertVolume(int v)
{
return (v * 128) / 100;
}
};
// Estructura para las opciones de audio
struct OptionsAudio
{
OptionsMusic music; // Opciones para la música
OptionsSound sound; // Opciones para los efectos de sonido
bool enabled; // Indica si el audio está activo o no
int volume; // Volumen al que suenan el audio
// Constructor por defecto
OptionsAudio()
: music(OptionsMusic()),
sound(OptionsSound()),
enabled(DEFAULT_AUDIO_ENABLED),
volume(DEFAULT_AUDIO_VOLUME) {}
// Constructor
OptionsAudio(OptionsMusic m, OptionsSound s, bool e, int v)
: music(m),
sound(s),
enabled(e),
volume(v) {}
};
// Estructura para las opciones del logo
struct OptionsLogo
{
int width; // Ancho de la resolucion del logo
int height; // Alto de la resolucion del logo
Uint64 speed; // Velocidad de ejecución del logo
bool running; // Para gestionar el bucle principal
// Constructor por defecto
OptionsLogo()
: width(DEFAULT_GAME_WIDTH),
height(DEFAULT_GAME_HEIGHT),
speed(DEFAUL_LOGO_SPEED),
running(DEFAUL_LOGO_RUNNING) {}
// Constructor
OptionsLogo(int w, int h, Uint64 s, bool r)
: width(w),
height(h),
speed(s),
running(r) {}
};
// Estructura con todas las opciones de configuración del programa
struct Options
{
OptionsLogo logo; // Opciones de juego
OptionsVideo video; // Opciones de video
OptionsWindow window; // Opciones de la ventana
OptionsAudio audio; // Opciones del audio
// Constructor por defecto
Options()
: logo(OptionsLogo()),
video(OptionsVideo()),
window(OptionsWindow()),
audio(OptionsAudio()) {}
// Constructor
Options(OptionsLogo l, OptionsVideo v, OptionsWindow sw, OptionsAudio a)
: logo(l),
video(v),
window(sw),
audio(a) {}
};
extern Options options;
// Crea e inicializa las opciones del programa
void initOptions();

50
source/s_sprite.cpp Normal file
View File

@@ -0,0 +1,50 @@
#include "s_sprite.h"
#include "surface.h" // Para Surface
// Constructor
SSprite::SSprite(std::shared_ptr<Surface> surface, int x, int y, int w, int h)
: surface_(surface),
pos_((SDL_Rect){x, y, w, h}),
clip_((SDL_Rect){0, 0, pos_.w, pos_.h}) {}
SSprite::SSprite(std::shared_ptr<Surface> surface, SDL_Rect rect)
: surface_(surface),
pos_(rect),
clip_((SDL_Rect){0, 0, pos_.w, pos_.h}) {}
SSprite::SSprite(std::shared_ptr<Surface> surface)
: surface_(surface),
pos_({0, 0, surface_->getWidth(), surface_->getHeight()}),
clip_(pos_) {}
// Muestra el sprite por pantalla
void SSprite::render()
{
surface_->render(pos_.x, pos_.y, &clip_);
}
void SSprite::render(Uint8 source_color, Uint8 target_color)
{
surface_->renderWithColorReplace(pos_.x, pos_.y, source_color, target_color, &clip_);
}
// Establece la posición del objeto
void SSprite::setPosition(int x, int y)
{
pos_.x = x;
pos_.y = y;
}
// Establece la posición del objeto
void SSprite::setPosition(SDL_Point p)
{
pos_.x = p.x;
pos_.y = p.y;
}
// Reinicia las variables a cero
void SSprite::clear()
{
pos_ = {0, 0, 0, 0};
clip_ = {0, 0, 0, 0};
}

70
source/s_sprite.h Normal file
View File

@@ -0,0 +1,70 @@
#pragma once
#include <SDL3/SDL_rect.h> // for SDL_Rect, SDL_Point
#include <SDL3/SDL_stdinc.h> // for Uint8
#include <memory> // for shared_ptr
class Surface; // lines 5-5
// Clase SSprite
class SSprite
{
protected:
// Variables
std::shared_ptr<Surface> surface_; // Surface donde estan todos los dibujos del sprite
SDL_Rect pos_; // Posición y tamaño donde dibujar el sprite
SDL_Rect clip_; // Rectangulo de origen de la surface que se dibujará en pantalla
public:
// Constructor
SSprite(std::shared_ptr<Surface>, int x, int y, int w, int h);
SSprite(std::shared_ptr<Surface>, SDL_Rect rect);
explicit SSprite(std::shared_ptr<Surface>);
// Destructor
virtual ~SSprite() = default;
// Muestra el sprite por pantalla
virtual void render();
virtual void render(Uint8 source_color, Uint8 target_color);
// Reinicia las variables a cero
virtual void clear();
// Obtiene la posición y el tamaño
int getX() const { return pos_.x; }
int getY() const { return pos_.y; }
int getWidth() const { return pos_.w; }
int getHeight() const { return pos_.h; }
// Devuelve el rectangulo donde está el sprite
SDL_Rect getPosition() const { return pos_; }
SDL_Rect &getRect() { return pos_; }
// Establece la posición y el tamaño
void setX(int x) { pos_.x = x; }
void setY(int y) { pos_.y = y; }
void setWidth(int w) { pos_.w = w; }
void setHeight(int h) { pos_.h = h; }
// Establece la posición del objeto
void setPosition(int x, int y);
void setPosition(SDL_Point p);
void setPosition(SDL_Rect r) { pos_ = r; }
// Aumenta o disminuye la posición
void incX(int value) { pos_.x += value; }
void incY(int value) { pos_.y += value; }
// Obtiene el rectangulo que se dibuja de la surface
SDL_Rect getClip() const { return clip_; }
// Establece el rectangulo que se dibuja de la surface
void setClip(SDL_Rect rect) { clip_ = rect; }
void setClip(int x, int y, int w, int h) { clip_ = (SDL_Rect){x, y, w, h}; }
// Obtiene un puntero a la surface
std::shared_ptr<Surface> getSurface() const { return surface_; }
// Establece la surface a utilizar
void setSurface(std::shared_ptr<Surface> surface) { surface_ = surface; }
};

324
source/screen.cpp Normal file
View File

@@ -0,0 +1,324 @@
#include "screen.h"
#include <SDL3/SDL_blendmode.h> // for SDL_BLENDMODE_BLEND
#include <SDL3/SDL_error.h> // for SDL_GetError
#include <SDL3/SDL_hints.h> // for SDL_SetHint, SDL_HINT_RENDER_DRIVER
#include <SDL3/SDL_init.h> // for SDL_Init, SDL_INIT_VIDEO
#include <SDL3/SDL_log.h> // for SDL_LogCategory, SDL_LogInfo, SDL_Lo...
#include <SDL3/SDL_pixels.h> // for SDL_PixelFormat
#include <algorithm> // for min, max
#include <string> // for char_traits, operator+, to_string
#include "mouse.h" // for updateCursorVisibility
#include "options.h" // for Options, options, OptionsWindow, Opt...
#include "surface.h" // for Surface, readPalFile
// [SINGLETON]
Screen *Screen::screen_ = nullptr;
// [SINGLETON] Crearemos el objeto con esta función estática
void Screen::init()
{
Screen::screen_ = new Screen();
}
// [SINGLETON] Destruiremos el objeto con esta función estática
void Screen::destroy()
{
delete Screen::screen_;
}
// [SINGLETON] Con este método obtenemos el objeto y podemos trabajar con él
Screen *Screen::get()
{
return Screen::screen_;
}
// Constructor
Screen::Screen()
{
// Arranca SDL VIDEO, crea la ventana y el renderizador
initSDL();
// Obtiene información sobre la pantalla
getDisplayInfo();
// Ajusta los tamaños
adjustWindowSize();
// Crea la textura donde se dibujan los graficos del juego
game_texture_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, options.logo.width, options.logo.height);
if (!game_texture_)
{
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: game_texture_ could not be created! SDL Error: %s", SDL_GetError());
}
SDL_SetTextureScaleMode(game_texture_, options.video.scale_mode);
// Crea la surface donde se dibujan los graficos del juego
game_surface_ = std::make_shared<Surface>(options.logo.width, options.logo.height);
game_surface_->setPalette(readPalFile("jailgames.pal"));
game_surface_->clear(0);
// Establece la surface que actuará como renderer para recibir las llamadas a render()
renderer_surface_ = std::make_shared<std::shared_ptr<Surface>>(game_surface_);
// Establece el modo de video
setFullscreenMode(options.video.fullscreen);
// Muestra la ventana
show();
}
// Destructor
Screen::~Screen()
{
SDL_DestroyTexture(game_texture_);
SDL_DestroyRenderer(renderer_);
SDL_DestroyWindow(window_);
}
// Limpia el renderer
void Screen::clearRenderer(Color color)
{
SDL_SetRenderDrawColor(renderer_, color.r, color.g, color.b, 0xFF);
SDL_RenderClear(renderer_);
}
// Prepara para empezar a dibujar en la textura de juego
void Screen::start() { setRendererSurface(nullptr); }
// Vuelca el contenido del renderizador en pantalla
void Screen::render()
{
// Copia la surface a la textura
surfaceToTexture();
// Copia la textura al renderizador
textureToRenderer();
}
// Establece el modo de video
void Screen::setFullscreenMode(bool mode)
{
// Actualiza las opciones
options.video.fullscreen = mode;
// Configura el modo de pantalla
SDL_SetWindowFullscreen(window_, options.video.fullscreen);
}
// Camibia entre pantalla completa y ventana
void Screen::toggleFullscreen()
{
options.video.fullscreen = !options.video.fullscreen;
setFullscreenMode();
}
// Reduce el tamaño de la ventana
bool Screen::decWindowZoom()
{
if (!options.video.fullscreen)
{
const int PREVIOUS_ZOOM = options.window.zoom;
--options.window.zoom;
options.window.zoom = std::max(options.window.zoom, 1);
if (options.window.zoom != PREVIOUS_ZOOM)
{
adjustWindowSize();
return true;
}
}
return false;
}
// Aumenta el tamaño de la ventana
bool Screen::incWindowZoom()
{
if (!options.video.fullscreen)
{
const int PREVIOUS_ZOOM = options.window.zoom;
++options.window.zoom;
options.window.zoom = std::min(options.window.zoom, options.window.max_zoom);
if (options.window.zoom != PREVIOUS_ZOOM)
{
adjustWindowSize();
return true;
}
}
return false;
}
// Actualiza la lógica de la clase
void Screen::update()
{
Mouse::updateCursorVisibility();
}
// Calcula el tamaño de la ventana
void Screen::adjustWindowSize()
{
window_width_ = options.logo.width;
window_height_ = options.logo.height;
// Establece el nuevo tamaño
if (!options.video.fullscreen)
{
int old_width, old_height;
SDL_GetWindowSize(window_, &old_width, &old_height);
int old_pos_x, old_pos_y;
SDL_GetWindowPosition(window_, &old_pos_x, &old_pos_y);
const int NEW_POS_X = old_pos_x + (old_width - (window_width_ * options.window.zoom)) / 2;
const int NEW_POS_Y = old_pos_y + (old_height - (window_height_ * options.window.zoom)) / 2;
SDL_SetWindowSize(window_, window_width_ * options.window.zoom, window_height_ * options.window.zoom);
SDL_SetWindowPosition(window_, std::max(NEW_POS_X, WINDOWS_DECORATIONS_), std::max(NEW_POS_Y, 0));
}
}
// Establece el renderizador para las surfaces
void Screen::setRendererSurface(std::shared_ptr<Surface> surface)
{
(surface) ? renderer_surface_ = std::make_shared<std::shared_ptr<Surface>>(surface) : renderer_surface_ = std::make_shared<std::shared_ptr<Surface>>(game_surface_);
}
// Copia la surface a la textura
void Screen::surfaceToTexture()
{
game_surface_->copyToTexture(renderer_, game_texture_);
}
// Copia la textura al renderizador
void Screen::textureToRenderer()
{
SDL_SetRenderTarget(renderer_, nullptr);
SDL_SetRenderDrawColor(renderer_, 0x00, 0x00, 0x00, 0xFF);
SDL_RenderClear(renderer_);
SDL_RenderTexture(renderer_, game_texture_, nullptr, nullptr);
SDL_RenderPresent(renderer_);
}
// Limpia la game_surface_
void Screen::clearSurface(Uint8 index) { game_surface_->clear(index); }
// Muestra la ventana
void Screen::show() { SDL_ShowWindow(window_); }
// Oculta la ventana
void Screen::hide() { SDL_HideWindow(window_); }
// Getters
SDL_Renderer *Screen::getRenderer() { return renderer_; }
std::shared_ptr<Surface> Screen::getRendererSurface() { return (*renderer_surface_); }
// Arranca SDL VIDEO y crea la ventana
bool Screen::initSDL()
{
// Indicador de éxito
auto success = true;
// Inicializa SDL
if (!SDL_Init(SDL_INIT_VIDEO))
{
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_VIDEO could not initialize! SDL Error: %s", SDL_GetError());
success = false;
}
else
{
SDL_LogInfo(SDL_LOG_CATEGORY_TEST, "\n** SDL_VIDEO: INITIALIZING\n");
if (!SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl"))
{
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Warning: opengl not enabled!");
}
// Crea la ventana
window_ = SDL_CreateWindow(options.window.caption.c_str(), options.logo.width * options.window.zoom, options.logo.height * options.window.zoom, SDL_WINDOW_OPENGL);
if (!window_)
{
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Window could not be created! SDL Error: %s", SDL_GetError());
success = false;
}
else
{
renderer_ = SDL_CreateRenderer(window_, nullptr);
if (!renderer_)
{
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Renderer could not be created! SDL Error: %s", SDL_GetError());
success = false;
}
else
{
SDL_SetRenderDrawColor(renderer_, 0x00, 0x00, 0x00, 0xFF);
SDL_SetRenderLogicalPresentation(renderer_, options.logo.width, options.logo.height, SDL_LOGICAL_PRESENTATION_INTEGER_SCALE);
SDL_SetWindowFullscreen(window_, options.video.fullscreen);
SDL_SetRenderDrawBlendMode(renderer_, SDL_BLENDMODE_BLEND);
SDL_SetRenderVSync(renderer_, options.video.vertical_sync ? 1 : SDL_RENDERER_VSYNC_DISABLED);
}
}
}
SDL_LogInfo(SDL_LOG_CATEGORY_TEST, "** SDL_VIDEO: INITIALIZATION COMPLETE\n");
return success;
}
// Obtiene información sobre la pantalla
void Screen::getDisplayInfo()
{
int i, num_displays = 0;
SDL_DisplayID *displays = SDL_GetDisplays(&num_displays);
if (displays)
{
for (i = 0; i < num_displays; ++i)
{
SDL_DisplayID instance_id = displays[i];
const char *name = SDL_GetDisplayName(instance_id);
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Display %" SDL_PRIu32 ": %s", instance_id, name ? name : "Unknown");
}
auto DM = SDL_GetCurrentDisplayMode(displays[0]);
// Calcula el máximo factor de zoom que se puede aplicar a la pantalla
options.window.max_zoom = std::min(DM->w / options.logo.width, DM->h / options.logo.height);
options.window.zoom = std::min(options.window.zoom, options.window.max_zoom);
// Muestra información sobre el tamaño de la pantalla y de la ventana de juego
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Current display mode: %dx%d @ %dHz",
static_cast<int>(DM->w), static_cast<int>(DM->h), static_cast<int>(DM->refresh_rate));
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Window resolution: %dx%d x%d",
static_cast<int>(options.logo.width), static_cast<int>(options.logo.height), options.window.zoom);
options.video.info = std::to_string(static_cast<int>(DM->w)) + " X " +
std::to_string(static_cast<int>(DM->h)) + " AT " +
std::to_string(static_cast<int>(DM->refresh_rate)) + " HZ";
// Calcula el máximo factor de zoom que se puede aplicar a la pantalla
const int MAX_ZOOM = std::min(DM->w / options.logo.width, (DM->h - WINDOWS_DECORATIONS_) / options.logo.height);
// Normaliza los valores de zoom
options.window.zoom = std::min(options.window.zoom, MAX_ZOOM);
SDL_free(displays);
}
}
// Activa / desactiva el escalado entero
void Screen::toggleIntegerScale()
{
options.video.integer_scale = !options.video.integer_scale;
SDL_SetRenderLogicalPresentation(Screen::get()->getRenderer(), options.logo.width, options.logo.height, options.video.integer_scale ? SDL_LOGICAL_PRESENTATION_INTEGER_SCALE : SDL_LOGICAL_PRESENTATION_LETTERBOX);
}
// Activa / desactiva el vsync
void Screen::toggleVSync()
{
options.video.vertical_sync = !options.video.vertical_sync;
SDL_SetRenderVSync(renderer_, options.video.vertical_sync ? 1 : SDL_RENDERER_VSYNC_DISABLED);
}

109
source/screen.h Normal file
View File

@@ -0,0 +1,109 @@
#pragma once
#include <SDL3/SDL_render.h> // for SDL_Renderer, SDL_Texture
#include <SDL3/SDL_stdinc.h> // for Uint8
#include <SDL3/SDL_video.h> // for SDL_Window
#include <memory> // for shared_ptr
#include "options.h" // for Options, OptionsVideo, options
#include "surface.h" // for Surface
#include "utils.h" // for Color
class Screen
{
private:
// Constantes
static constexpr int WINDOWS_DECORATIONS_ = 35;
// [SINGLETON] Objeto privado
static Screen *screen_;
// Objetos y punteros
SDL_Window *window_; // Ventana de la aplicación
SDL_Renderer *renderer_; // El renderizador de la ventana
SDL_Texture *game_texture_; // Textura donde se dibuja el juego
std::shared_ptr<Surface> game_surface_; // Surface principal para manejar game_surface_data_
std::shared_ptr<std::shared_ptr<Surface>> renderer_surface_; // Puntero a la Surface que actua
// Variables
int window_width_; // Ancho de la pantalla o ventana
int window_height_; // Alto de la pantalla o ventana
// Arranca SDL VIDEO y crea la ventana
bool initSDL();
// Calcula el tamaño de la ventana
void adjustWindowSize();
// Copia la surface a la textura
void surfaceToTexture();
// Copia la textura al renderizador
void textureToRenderer();
// Obtiene información sobre la pantalla
void getDisplayInfo();
// Constructor
Screen();
// Destructor
~Screen();
public:
// [SINGLETON] Crearemos el objeto con esta función estática
static void init();
// [SINGLETON] Destruiremos el objeto con esta función estática
static void destroy();
// [SINGLETON] Con este método obtenemos el objeto y podemos trabajar con él
static Screen *get();
// Limpia el renderer
void clearRenderer(Color color = {0x00, 0x00, 0x00});
// Limpia la game_surface_
void clearSurface(Uint8 index);
// Prepara para empezar a dibujar en la textura de juego
void start();
// Vuelca el contenido del renderizador en pantalla
void render();
// Actualiza la lógica de la clase
void update();
// Establece el modo de video
void setFullscreenMode(bool mode = options.video.fullscreen);
// Cambia entre pantalla completa y ventana
void toggleFullscreen();
// Reduce el tamaño de la ventana
bool decWindowZoom();
// Aumenta el tamaño de la ventana
bool incWindowZoom();
// Muestra la ventana
void show();
// Oculta la ventana
void hide();
// Establece el renderizador para las surfaces
void setRendererSurface(std::shared_ptr<Surface> surface = nullptr);
// Activa / desactiva el escalado entero
void toggleIntegerScale();
// Activa / desactiva el vsync
void toggleVSync();
// Getters
SDL_Renderer *getRenderer();
std::shared_ptr<Surface> getRendererSurface();
};

9251
source/stb_image.h Normal file

File diff suppressed because it is too large Load Diff

5565
source/stb_vorbis.c Normal file

File diff suppressed because it is too large Load Diff

636
source/surface.cpp Normal file
View File

@@ -0,0 +1,636 @@
// IWYU pragma: no_include <bits/std_abs.h>
#include "surface.h"
#include <SDL3/SDL_error.h> // for SDL_GetError
#include <SDL3/SDL_timer.h> // for SDL_GetTicks
#include <stdlib.h> // for abs
#include <algorithm> // for min, max, copy_n, fill
#include <cstdint> // for uint32_t
#include <cstring> // for memcpy, size_t
#include <fstream> // for basic_ifstream, basic_ostream, basic_ist...
#include <iostream> // for cerr
#include <memory> // for shared_ptr, __shared_ptr_access, default...
#include <sstream> // for basic_istringstream
#include <stdexcept> // for runtime_error
#include <vector> // for vector
#include "gif.h" // for Gif
#include "screen.h" // for Screen
#include "utils.h" // for printWithDots
// Carga una paleta desde un archivo .gif
Palette loadPalette(const std::string &file_path)
{
// Abrir el archivo en modo binario
std::ifstream file(file_path, std::ios::binary | std::ios::ate);
if (!file.is_open())
{
throw std::runtime_error("Error opening file: " + file_path);
}
// Obtener el tamaño del archivo y leerlo en un buffer
std::streamsize size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<Uint8> buffer(size);
if (!file.read(reinterpret_cast<char *>(buffer.data()), size))
{
throw std::runtime_error("Error reading file: " + file_path);
}
// Cargar la paleta usando los datos del buffer
GIF::Gif gif;
std::vector<uint32_t> pal = gif.loadPalette(buffer.data());
if (pal.empty())
{
throw std::runtime_error("No palette found in GIF file: " + file_path);
}
// Crear la paleta y copiar los datos desde 'pal'
Palette palette = {}; // Inicializa la paleta con ceros
std::copy_n(pal.begin(), std::min(pal.size(), palette.size()), palette.begin());
// Mensaje de depuración
printWithDots("Palette : ", file_path.substr(file_path.find_last_of("\\/") + 1), "[ LOADED ]");
return palette;
}
// Carga una paleta desde un archivo .pal
Palette readPalFile(const std::string &file_path)
{
Palette palette{};
palette.fill(0); // Inicializar todo con 0 (transparente por defecto)
std::ifstream file(file_path);
if (!file.is_open())
{
throw std::runtime_error("No se pudo abrir el archivo .pal");
}
std::string line;
int line_number = 0;
int color_index = 0;
while (std::getline(file, line))
{
++line_number;
// Ignorar las tres primeras líneas del archivo
if (line_number <= 3)
{
continue;
}
// Procesar las líneas restantes con valores RGB
std::istringstream ss(line);
int r, g, b;
if (ss >> r >> g >> b)
{
// Construir el color ARGB (A = 255 por defecto)
Uint32 color = (255 << 24) | (r << 16) | (g << 8) | b;
palette[color_index++] = color;
// Limitar a un máximo de 256 colores (opcional)
if (color_index >= 256)
{
break;
}
}
}
file.close();
return palette;
}
// Constructor
Surface::Surface(int w, int h)
: surface_data_(std::make_shared<SurfaceData>(w, h)),
transparent_color_(255) { initializeSubPalette(sub_palette_); }
Surface::Surface(const std::string &file_path)
: transparent_color_(255)
{
SurfaceData loadedData = loadSurface(file_path);
surface_data_ = std::make_shared<SurfaceData>(std::move(loadedData));
initializeSubPalette(sub_palette_);
}
// Carga una superficie desde un archivo
SurfaceData Surface::loadSurface(const std::string &file_path)
{
// Abrir el archivo usando std::ifstream para manejo automático del recurso
std::ifstream file(file_path, std::ios::binary | std::ios::ate);
if (!file.is_open())
{
std::cerr << "Error opening file: " << file_path << std::endl;
throw std::runtime_error("Error opening file");
}
// Obtener el tamaño del archivo
std::streamsize size = file.tellg();
file.seekg(0, std::ios::beg);
// Leer el contenido del archivo en un buffer
std::vector<Uint8> buffer(size);
if (!file.read(reinterpret_cast<char *>(buffer.data()), size))
{
std::cerr << "Error reading file: " << file_path << std::endl;
throw std::runtime_error("Error reading file");
}
// Crear un objeto Gif y llamar a la función loadGif
GIF::Gif gif;
Uint16 w = 0, h = 0;
std::vector<Uint8> rawPixels = gif.loadGif(buffer.data(), w, h);
if (rawPixels.empty())
{
std::cerr << "Error loading GIF from file: " << file_path << std::endl;
throw std::runtime_error("Error loading GIF");
}
// Si el constructor de Surface espera un std::shared_ptr<Uint8[]>,
// reservamos un bloque dinámico y copiamos los datos del vector.
size_t pixelCount = rawPixels.size();
auto pixels = std::shared_ptr<Uint8[]>(new Uint8[pixelCount], std::default_delete<Uint8[]>());
std::memcpy(pixels.get(), rawPixels.data(), pixelCount);
// Crear y devolver directamente el objeto SurfaceData
printWithDots("Surface : ", file_path.substr(file_path.find_last_of("\\/") + 1), "[ LOADED ]");
return SurfaceData(w, h, pixels);
}
// Carga una paleta desde un archivo
void Surface::loadPalette(const std::string &file_path)
{
palette_ = ::loadPalette(file_path);
}
// Carga una paleta desde otra paleta
void Surface::loadPalette(Palette palette)
{
palette_ = palette;
}
// Establece un color en la paleta
void Surface::setColor(int index, Uint32 color)
{
palette_.at(index) = color;
}
// Rellena la superficie con un color
void Surface::clear(Uint8 color)
{
const size_t total_pixels = surface_data_->width * surface_data_->height;
Uint8 *data_ptr = surface_data_->data.get();
std::fill(data_ptr, data_ptr + total_pixels, color);
}
// Pone un pixel en la SurfaceData
void Surface::putPixel(int x, int y, Uint8 color)
{
if (x < 0 || y < 0 || x >= surface_data_->width || y >= surface_data_->height)
{
return; // Coordenadas fuera de rango
}
const int index = x + y * surface_data_->width;
surface_data_->data.get()[index] = color;
}
// Obtiene el color de un pixel de la surface_data
Uint8 Surface::getPixel(int x, int y) { return surface_data_->data.get()[x + y * surface_data_->width]; }
// Dibuja un rectangulo relleno
void Surface::fillRect(const SDL_Rect *rect, Uint8 color)
{
// Limitar los valores del rectángulo al tamaño de la superficie
int x_start = std::max(0, rect->x);
int y_start = std::max(0, rect->y);
int x_end = std::min(rect->x + rect->w, static_cast<int>(surface_data_->width));
int y_end = std::min(rect->y + rect->h, static_cast<int>(surface_data_->height));
// Recorrer cada píxel dentro del rectángulo directamente
for (int y = y_start; y < y_end; ++y)
{
for (int x = x_start; x < x_end; ++x)
{
const int index = x + y * surface_data_->width;
surface_data_->data.get()[index] = color;
}
}
}
// Dibuja el borde de un rectangulo
void Surface::drawRectBorder(const SDL_Rect *rect, Uint8 color)
{
// Limitar los valores del rectángulo al tamaño de la superficie
int x_start = std::max(0, rect->x);
int y_start = std::max(0, rect->y);
int x_end = std::min(rect->x + rect->w, static_cast<int>(surface_data_->width));
int y_end = std::min(rect->y + rect->h, static_cast<int>(surface_data_->height));
// Dibujar bordes horizontales
for (int x = x_start; x < x_end; ++x)
{
// Borde superior
const int top_index = x + y_start * surface_data_->width;
surface_data_->data.get()[top_index] = color;
// Borde inferior
const int bottom_index = x + (y_end - 1) * surface_data_->width;
surface_data_->data.get()[bottom_index] = color;
}
// Dibujar bordes verticales
for (int y = y_start; y < y_end; ++y)
{
// Borde izquierdo
const int left_index = x_start + y * surface_data_->width;
surface_data_->data.get()[left_index] = color;
// Borde derecho
const int right_index = (x_end - 1) + y * surface_data_->width;
surface_data_->data.get()[right_index] = color;
}
}
// Dibuja una linea
void Surface::drawLine(int x1, int y1, int x2, int y2, Uint8 color)
{
// Calcula las diferencias
int dx = std::abs(x2 - x1);
int dy = std::abs(y2 - y1);
// Determina la dirección del incremento
int sx = (x1 < x2) ? 1 : -1;
int sy = (y1 < y2) ? 1 : -1;
int err = dx - dy;
while (true)
{
// Asegúrate de no dibujar fuera de los límites de la superficie
if (x1 >= 0 && x1 < surface_data_->width && y1 >= 0 && y1 < surface_data_->height)
{
surface_data_->data.get()[x1 + y1 * surface_data_->width] = color;
}
// Si alcanzamos el punto final, salimos
if (x1 == x2 && y1 == y2)
break;
int e2 = 2 * err;
if (e2 > -dy)
{
err -= dy;
x1 += sx;
}
if (e2 < dx)
{
err += dx;
y1 += sy;
}
}
}
void Surface::render(int dx, int dy, int sx, int sy, int w, int h)
{
auto surface_data = Screen::get()->getRendererSurface()->getSurfaceData();
// Limitar la región para evitar accesos fuera de rango en origen
w = std::min(w, surface_data_->width - sx);
h = std::min(h, surface_data_->height - sy);
// Limitar la región para evitar accesos fuera de rango en destino
w = std::min(w, surface_data->width - dx);
h = std::min(h, surface_data->height - dy);
for (int iy = 0; iy < h; ++iy)
{
for (int ix = 0; ix < w; ++ix)
{
// Verificar que las coordenadas de destino están dentro de los límites
if (int dest_x = dx + ix; dest_x >= 0 && dest_x < surface_data->width)
{
if (int dest_y = dy + iy; dest_y >= 0 && dest_y < surface_data->height)
{
int src_x = sx + ix;
int src_y = sy + iy;
Uint8 color = surface_data_->data.get()[src_x + src_y * surface_data_->width];
if (color != transparent_color_)
{
surface_data->data.get()[dest_x + dest_y * surface_data->width] = sub_palette_[color];
}
}
}
}
}
}
void Surface::render(int x, int y, SDL_Rect *srcRect, SDL_FlipMode flip)
{
auto surface_data_dest = Screen::get()->getRendererSurface()->getSurfaceData();
// Determina la región de origen (clip) a renderizar
int sx = (srcRect) ? srcRect->x : 0;
int sy = (srcRect) ? srcRect->y : 0;
int w = (srcRect) ? srcRect->w : surface_data_->width;
int h = (srcRect) ? srcRect->h : surface_data_->height;
// Limitar la región para evitar accesos fuera de rango en origen
w = std::min(w, surface_data_->width - sx);
h = std::min(h, surface_data_->height - sy);
w = std::min(w, surface_data_dest->width - x);
h = std::min(h, surface_data_dest->height - y);
// Limitar la región para evitar accesos fuera de rango en destino
w = std::min(w, surface_data_dest->width - x);
h = std::min(h, surface_data_dest->height - y);
// Renderiza píxel por píxel aplicando el flip si es necesario
for (int iy = 0; iy < h; ++iy)
{
for (int ix = 0; ix < w; ++ix)
{
// Coordenadas de origen
int src_x = (flip == SDL_FLIP_HORIZONTAL) ? (sx + w - 1 - ix) : (sx + ix);
int src_y = (flip == SDL_FLIP_VERTICAL) ? (sy + h - 1 - iy) : (sy + iy);
// Coordenadas de destino
int dest_x = x + ix;
int dest_y = y + iy;
// Verificar que las coordenadas de destino están dentro de los límites
if (dest_x >= 0 && dest_x < surface_data_dest->width && dest_y >= 0 && dest_y < surface_data_dest->height)
{
// Copia el píxel si no es transparente
Uint8 color = surface_data_->data.get()[src_x + src_y * surface_data_->width];
if (color != transparent_color_)
{
surface_data_dest->data[dest_x + dest_y * surface_data_dest->width] = sub_palette_[color];
}
}
}
}
}
// Copia una región de la superficie de origen a la de destino
void Surface::render(SDL_Rect *srcRect, SDL_Rect *dstRect, SDL_FlipMode flip)
{
auto surface_data = Screen::get()->getRendererSurface()->getSurfaceData();
// Si srcRect es nullptr, tomar toda la superficie fuente
int sx = (srcRect) ? srcRect->x : 0;
int sy = (srcRect) ? srcRect->y : 0;
int sw = (srcRect) ? srcRect->w : surface_data_->width;
int sh = (srcRect) ? srcRect->h : surface_data_->height;
// Si dstRect es nullptr, asignar las mismas dimensiones que srcRect
int dx = (dstRect) ? dstRect->x : 0;
int dy = (dstRect) ? dstRect->y : 0;
int dw = (dstRect) ? dstRect->w : sw;
int dh = (dstRect) ? dstRect->h : sh;
// Asegurarse de que srcRect y dstRect tienen las mismas dimensiones
if (sw != dw || sh != dh)
{
dw = sw; // Respetar las dimensiones de srcRect
dh = sh;
}
// Limitar la región para evitar accesos fuera de rango en src y dst
sw = std::min(sw, surface_data_->width - sx);
sh = std::min(sh, surface_data_->height - sy);
dw = std::min(dw, surface_data->width - dx);
dh = std::min(dh, surface_data->height - dy);
int final_width = std::min(sw, dw);
int final_height = std::min(sh, dh);
// Renderiza píxel por píxel aplicando el flip si es necesario
for (int iy = 0; iy < final_height; ++iy)
{
for (int ix = 0; ix < final_width; ++ix)
{
// Coordenadas de origen
int src_x = (flip == SDL_FLIP_HORIZONTAL) ? (sx + final_width - 1 - ix) : (sx + ix);
int src_y = (flip == SDL_FLIP_VERTICAL) ? (sy + final_height - 1 - iy) : (sy + iy);
// Coordenadas de destino
if (int dest_x = dx + ix; dest_x >= 0 && dest_x < surface_data->width)
{
if (int dest_y = dy + iy; dest_y >= 0 && dest_y < surface_data->height)
{
// Copiar el píxel si no es transparente
Uint8 color = surface_data_->data.get()[src_x + src_y * surface_data_->width];
if (color != transparent_color_)
{
surface_data->data[dest_x + dest_y * surface_data->width] = sub_palette_[color];
}
}
}
}
}
}
// Copia una región de la SurfaceData de origen a la SurfaceData de destino reemplazando un color por otro
void Surface::renderWithColorReplace(int x, int y, Uint8 source_color, Uint8 target_color, SDL_Rect *srcRect, SDL_FlipMode flip)
{
auto surface_data = Screen::get()->getRendererSurface()->getSurfaceData();
// Determina la región de origen (clip) a renderizar
int sx = (srcRect) ? srcRect->x : 0;
int sy = (srcRect) ? srcRect->y : 0;
int w = (srcRect) ? srcRect->w : surface_data_->width;
int h = (srcRect) ? srcRect->h : surface_data_->height;
// Limitar la región para evitar accesos fuera de rango
w = std::min(w, surface_data_->width - sx);
h = std::min(h, surface_data_->height - sy);
// Renderiza píxel por píxel aplicando el flip si es necesario
for (int iy = 0; iy < h; ++iy)
{
for (int ix = 0; ix < w; ++ix)
{
// Coordenadas de origen
int src_x = (flip == SDL_FLIP_HORIZONTAL) ? (sx + w - 1 - ix) : (sx + ix);
int src_y = (flip == SDL_FLIP_VERTICAL) ? (sy + h - 1 - iy) : (sy + iy);
// Coordenadas de destino
int dest_x = x + ix;
int dest_y = y + iy;
// Verifica que las coordenadas de destino estén dentro de los límites
if (dest_x < 0 || dest_y < 0 || dest_x >= surface_data->width || dest_y >= surface_data->height)
{
continue; // Saltar píxeles fuera del rango del destino
}
// Copia el píxel si no es transparente
Uint8 color = surface_data_->data.get()[src_x + src_y * surface_data_->width];
if (color != transparent_color_)
{
surface_data->data[dest_x + dest_y * surface_data->width] =
(color == source_color) ? target_color : color;
}
}
}
}
// Vuelca la superficie a una textura
void Surface::copyToTexture(SDL_Renderer *renderer, SDL_Texture *texture)
{
if (!renderer || !texture || !surface_data_)
{
throw std::runtime_error("Renderer or texture is null.");
}
if (surface_data_->width <= 0 || surface_data_->height <= 0 || !surface_data_->data.get())
{
throw std::runtime_error("Invalid surface dimensions or data.");
}
Uint32 *pixels = nullptr;
int pitch = 0;
// Bloquea la textura para modificar los píxeles directamente
if (!SDL_LockTexture(texture, nullptr, reinterpret_cast<void **>(&pixels), &pitch))
{
throw std::runtime_error("Failed to lock texture: " + std::string(SDL_GetError()));
}
// Convertir `pitch` de bytes a Uint32 (asegurando alineación correcta en hardware)
int row_stride = pitch / sizeof(Uint32);
for (int y = 0; y < surface_data_->height; ++y)
{
for (int x = 0; x < surface_data_->width; ++x)
{
// Calcular la posición correcta en la textura teniendo en cuenta el stride
int texture_index = y * row_stride + x;
int surface_index = y * surface_data_->width + x;
pixels[texture_index] = palette_[surface_data_->data.get()[surface_index]];
}
}
SDL_UnlockTexture(texture); // Desbloquea la textura
// Renderiza la textura en la pantalla completa
if (!SDL_RenderTexture(renderer, texture, nullptr, nullptr))
{
throw std::runtime_error("Failed to copy texture to renderer: " + std::string(SDL_GetError()));
}
}
// Vuelca la superficie a una textura
void Surface::copyToTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL_FRect *srcRect, SDL_FRect *destRect)
{
if (!renderer || !texture || !surface_data_)
{
throw std::runtime_error("Renderer or texture is null.");
}
if (surface_data_->width <= 0 || surface_data_->height <= 0 || !surface_data_->data.get())
{
throw std::runtime_error("Invalid surface dimensions or data.");
}
Uint32 *pixels = nullptr;
int pitch = 0;
SDL_Rect destSDLRect = {
static_cast<int>(destRect->x),
static_cast<int>(destRect->y),
static_cast<int>(destRect->w),
static_cast<int>(destRect->h)
};
if (SDL_LockTexture(texture, &destSDLRect, reinterpret_cast<void **>(&pixels), &pitch) != 0)
{
throw std::runtime_error("Failed to lock texture: " + std::string(SDL_GetError()));
}
int row_stride = pitch / sizeof(Uint32);
for (int y = 0; y < surface_data_->height; ++y)
{
for (int x = 0; x < surface_data_->width; ++x)
{
int texture_index = y * row_stride + x;
int surface_index = y * surface_data_->width + x;
pixels[texture_index] = palette_[surface_data_->data.get()[surface_index]];
}
}
SDL_UnlockTexture(texture);
// Renderiza la textura con los rectángulos especificados
if (!SDL_RenderTexture(renderer, texture, srcRect, destRect))
{
throw std::runtime_error("Failed to copy texture to renderer: " + std::string(SDL_GetError()));
}
}
// Realiza un efecto de fundido en la paleta principal
bool Surface::fadePalette()
{
// Verificar que el tamaño mínimo de palette_ sea adecuado
static constexpr int palette_size = 19;
if (sizeof(palette_) / sizeof(palette_[0]) < palette_size)
{
throw std::runtime_error("Palette size is insufficient for fadePalette operation.");
}
// Desplazar colores (pares e impares)
for (int i = 18; i > 1; --i)
{
palette_[i] = palette_[i - 2];
}
// Ajustar el primer color
palette_[1] = palette_[0];
// Devolver si el índice 15 coincide con el índice 0
return palette_[15] == palette_[0];
}
// Realiza un efecto de fundido en la paleta secundaria
bool Surface::fadeSubPalette(Uint32 delay)
{
// Variable estática para almacenar el último tick
static Uint32 last_tick = 0;
// Obtener el tiempo actual
Uint32 current_tick = SDL_GetTicks();
// Verificar si ha pasado el tiempo de retardo
if (current_tick - last_tick < delay)
{
return false; // No se realiza el fade
}
// Actualizar el último tick
last_tick = current_tick;
// Verificar que el tamaño mínimo de sub_palette_ sea adecuado
static constexpr int sub_palette_size = 19;
if (sizeof(sub_palette_) / sizeof(sub_palette_[0]) < sub_palette_size)
{
throw std::runtime_error("Palette size is insufficient for fadePalette operation.");
}
// Desplazar colores (pares e impares)
for (int i = 18; i > 1; --i)
{
sub_palette_[i] = sub_palette_[i - 2];
}
// Ajustar el primer color
sub_palette_[1] = sub_palette_[0];
// Devolver si el índice 15 coincide con el índice 0
return sub_palette_[15] == sub_palette_[0];
}

170
source/surface.h Normal file
View File

@@ -0,0 +1,170 @@
#pragma once
#include <SDL3/SDL_rect.h> // for SDL_Rect, SDL_FRect
#include <SDL3/SDL_render.h> // for SDL_Renderer, SDL_Texture
#include <SDL3/SDL_stdinc.h> // for Uint8, Uint16, Uint32
#include <SDL3/SDL_surface.h> // for SDL_FlipMode
#include <array> // for array
#include <memory> // for default_delete, shared_ptr, __shared_p...
#include <numeric> // for iota
#include <stdexcept> // for invalid_argument
#include <string> // for string
#include <utility> // for move
// Alias
using Palette = std::array<Uint32, 256>;
using SubPalette = std::array<Uint8, 256>;
// Carga una paleta desde un archivo .gif
Palette loadPalette(const std::string &file_path);
// Carga una paleta desde un archivo .pal
Palette readPalFile(const std::string &file_path);
struct SurfaceData
{
std::shared_ptr<Uint8[]> data; // Usa std::shared_ptr para gestión automática
Uint16 width; // Ancho de la imagen
Uint16 height; // Alto de la imagen
// Constructor por defecto
SurfaceData() : data(nullptr), width(0), height(0) {}
// Constructor que inicializa dimensiones y asigna memoria
SurfaceData(Uint16 w, Uint16 h)
: data(std::shared_ptr<Uint8[]>(new Uint8[w * h](), std::default_delete<Uint8[]>())), width(w), height(h) {}
// Constructor para inicializar directamente con datos
SurfaceData(Uint16 w, Uint16 h, std::shared_ptr<Uint8[]> pixels)
: data(std::move(pixels)), width(w), height(h) {}
// Constructor de movimiento
SurfaceData(SurfaceData &&other) noexcept = default;
// Operador de movimiento
SurfaceData &operator=(SurfaceData &&other) noexcept = default;
// Evita copias accidentales
SurfaceData(const SurfaceData &) = delete;
SurfaceData &operator=(const SurfaceData &) = delete;
// Escala la superficie por un factor entero
void scale(int factor)
{
if (factor <= 1)
{
throw std::invalid_argument("El factor debe ser mayor a 1.");
}
// Calcular nuevas dimensiones
Uint16 newWidth = width * factor;
Uint16 newHeight = height * factor;
// Crear un nuevo buffer para los datos redimensionados
auto newData = std::shared_ptr<Uint8[]>(new Uint8[newWidth * newHeight](), std::default_delete<Uint8[]>());
// Rellenar los datos del nuevo buffer replicando los píxeles
for (Uint16 y = 0; y < height; ++y)
{
for (Uint16 x = 0; x < width; ++x)
{
Uint8 value = data[y * width + x]; // Obtener el valor del píxel original
// Copiar el píxel a la región escalada
for (int dy = 0; dy < factor; ++dy)
{
for (int dx = 0; dx < factor; ++dx)
{
newData[(y * factor + dy) * newWidth + (x * factor + dx)] = value;
}
}
}
}
// Actualizar los datos de la superficie
data = std::move(newData);
width = newWidth;
height = newHeight;
}
};
class Surface
{
private:
std::shared_ptr<SurfaceData> surface_data_; // Datos a dibujar
Palette palette_; // Paleta para volcar la SurfaceData a una Textura
SubPalette sub_palette_; // Paleta para reindexar colores
int transparent_color_; // Indice de la paleta que se omite en la copia de datos
public:
// Constructor
Surface(int w, int h);
explicit Surface(const std::string &file_path);
// Destructor
~Surface() = default;
// Carga una SurfaceData desde un archivo
SurfaceData loadSurface(const std::string &file_path);
// Carga una paleta desde un archivo
void loadPalette(const std::string &file_path);
void loadPalette(Palette palette);
// Copia una región de la SurfaceData de origen a la SurfaceData de destino
void render(int dx, int dy, int sx, int sy, int w, int h);
void render(int x, int y, SDL_Rect *clip = nullptr, SDL_FlipMode flip = SDL_FLIP_NONE);
void render(SDL_Rect *srcRect = nullptr, SDL_Rect *dstRect = nullptr, SDL_FlipMode flip = SDL_FLIP_NONE);
// Copia una región de la SurfaceData de origen a la SurfaceData de destino reemplazando un color por otro
void renderWithColorReplace(int x, int y, Uint8 source_color = 0, Uint8 target_color = 0, SDL_Rect *srcRect = nullptr, SDL_FlipMode flip = SDL_FLIP_NONE);
// Establece un color en la paleta
void setColor(int index, Uint32 color);
// Rellena la SurfaceData con un color
void clear(Uint8 color);
// Vuelca la SurfaceData a una textura
void copyToTexture(SDL_Renderer *renderer, SDL_Texture *texture);
void copyToTexture(SDL_Renderer *renderer, SDL_Texture *texture, SDL_FRect *srcRect, SDL_FRect *destRect);
// Realiza un efecto de fundido en las paletas
bool fadePalette();
bool fadeSubPalette(Uint32 delay = 0);
// Pone un pixel en la SurfaceData
void putPixel(int x, int y, Uint8 color);
// Obtiene el color de un pixel de la surface_data
Uint8 getPixel(int x, int y);
// Dibuja un rectangulo relleno
void fillRect(const SDL_Rect *rect, Uint8 color);
// Dibuja el borde de un rectangulo
void drawRectBorder(const SDL_Rect *rect, Uint8 color);
// Dibuja una linea
void drawLine(int x1, int y1, int x2, int y2, Uint8 color);
// Metodos para gestionar surface_data_
std::shared_ptr<SurfaceData> getSurfaceData() const { return surface_data_; }
void setSurfaceData(std::shared_ptr<SurfaceData> new_data) { surface_data_ = new_data; }
// Obtien ancho y alto
int getWidth() const { return surface_data_->width; }
int getHeight() const { return surface_data_->height; }
// Color transparente
Uint8 getTransparentColor() const { return transparent_color_; }
void setTransparentColor(Uint8 color = 255) { transparent_color_ = color; }
// Paleta
void setPalette(const std::array<Uint32, 256> &palette) { palette_ = palette; }
// Inicializa la sub paleta
void initializeSubPalette(SubPalette &palette) { std::iota(palette.begin(), palette.end(), 0); }
// Escala el tamaño de la Surface
void scale(int factor) { surface_data_->scale(factor); }
};

440
source/utils.cpp Normal file
View File

@@ -0,0 +1,440 @@
#include "utils.h"
#include <stdlib.h> // for abs
#include <algorithm> // for transform, find
#include <cctype> // for tolower, toupper
#include <cmath> // for round
#include <exception> // for exception
#include <filesystem> // for path
#include <iostream> // for basic_ostream, cout, basic_ios, ios, endl
#include <string> // for string, char_traits, allocator, operator==
// Calcula el cuadrado de la distancia entre dos puntos
double distanceSquared(int x1, int y1, int x2, int y2)
{
const int deltaX = x2 - x1;
const int deltaY = y2 - y1;
return deltaX * deltaX + deltaY * deltaY;
}
// Detector de colisiones entre dos circulos
bool checkCollision(const Circle &a, const Circle &b)
{
// Calcula el radio total al cuadrado
int totalRadiusSquared = a.r + b.r;
totalRadiusSquared = totalRadiusSquared * totalRadiusSquared;
// Si la distancia entre el centro de los circulos es inferior a la suma de sus radios
if (distanceSquared(a.x, a.y, b.x, b.y) < (totalRadiusSquared))
{
// Los circulos han colisionado
return true;
}
// En caso contrario
return false;
}
// Detector de colisiones entre un circulo y un rectangulo
bool checkCollision(const Circle &a, const SDL_Rect &b)
{
// Closest point on collision box
int cX, cY;
// Find closest x offset
if (a.x < b.x)
{
cX = b.x;
}
else if (a.x > b.x + b.w)
{
cX = b.x + b.w;
}
else
{
cX = a.x;
}
// Find closest y offset
if (a.y < b.y)
{
cY = b.y;
}
else if (a.y > b.y + b.h)
{
cY = b.y + b.h;
}
else
{
cY = a.y;
}
// If the closest point is inside the circle_t
if (distanceSquared(a.x, a.y, cX, cY) < a.r * a.r)
{
// This box and the circle_t have collided
return true;
}
// If the shapes have not collided
return false;
}
// Detector de colisiones entre dos rectangulos
bool checkCollision(const SDL_Rect &a, const SDL_Rect &b)
{
// Calcula las caras del rectangulo a
const int leftA = a.x;
const int rightA = a.x + a.w;
const int topA = a.y;
const int bottomA = a.y + a.h;
// Calcula las caras del rectangulo b
const int leftB = b.x;
const int rightB = b.x + b.w;
const int topB = b.y;
const int bottomB = b.y + b.h;
// Si cualquiera de las caras de a está fuera de b
if (bottomA <= topB)
{
return false;
}
if (topA >= bottomB)
{
return false;
}
if (rightA <= leftB)
{
return false;
}
if (leftA >= rightB)
{
return false;
}
// Si ninguna de las caras está fuera de b
return true;
}
// Detector de colisiones entre un punto y un rectangulo
bool checkCollision(const SDL_Point &p, const SDL_Rect &r)
{
// Comprueba si el punto está a la izquierda del rectangulo
if (p.x < r.x)
{
return false;
}
// Comprueba si el punto está a la derecha del rectangulo
if (p.x > r.x + r.w)
{
return false;
}
// Comprueba si el punto está por encima del rectangulo
if (p.y < r.y)
{
return false;
}
// Comprueba si el punto está por debajo del rectangulo
if (p.y > r.y + r.h)
{
return false;
}
// Si no está fuera, es que está dentro
return true;
}
// Detector de colisiones entre una linea horizontal y un rectangulo
bool checkCollision(const LineHorizontal &l, const SDL_Rect &r)
{
// Comprueba si la linea esta por encima del rectangulo
if (l.y < r.y)
{
return false;
}
// Comprueba si la linea esta por debajo del rectangulo
if (l.y >= r.y + r.h)
{
return false;
}
// Comprueba si el inicio de la linea esta a la derecha del rectangulo
if (l.x1 >= r.x + r.w)
{
return false;
}
// Comprueba si el final de la linea esta a la izquierda del rectangulo
if (l.x2 < r.x)
{
return false;
}
// Si ha llegado hasta aquí, hay colisión
return true;
}
// Detector de colisiones entre una linea vertical y un rectangulo
bool checkCollision(const LineVertical &l, const SDL_Rect &r)
{
// Comprueba si la linea esta por la izquierda del rectangulo
if (l.x < r.x)
{
return false;
}
// Comprueba si la linea esta por la derecha del rectangulo
if (l.x >= r.x + r.w)
{
return false;
}
// Comprueba si el inicio de la linea esta debajo del rectangulo
if (l.y1 >= r.y + r.h)
{
return false;
}
// Comprueba si el final de la linea esta encima del rectangulo
if (l.y2 < r.y)
{
return false;
}
// Si ha llegado hasta aquí, hay colisión
return true;
}
// Detector de colisiones entre una linea horizontal y un punto
bool checkCollision(const LineHorizontal &l, const SDL_Point &p)
{
// Comprueba si el punto esta sobre la linea
if (p.y > l.y)
{
return false;
}
// Comprueba si el punto esta bajo la linea
if (p.y < l.y)
{
return false;
}
// Comprueba si el punto esta a la izquierda de la linea
if (p.x < l.x1)
{
return false;
}
// Comprueba si el punto esta a la derecha de la linea
if (p.x > l.x2)
{
return false;
}
// Si ha llegado aquí, hay colisión
return true;
}
// Detector de colisiones entre dos lineas
SDL_Point checkCollision(const Line &l1, const Line &l2)
{
const float x1 = l1.x1;
const float y1 = l1.y1;
const float x2 = l1.x2;
const float y2 = l1.y2;
const float x3 = l2.x1;
const float y3 = l2.y1;
const float x4 = l2.x2;
const float y4 = l2.y2;
// calculate the direction of the lines
float uA = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / ((y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1));
float uB = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / ((y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1));
// if uA and uB are between 0-1, lines are colliding
if (uA >= 0 && uA <= 1 && uB >= 0 && uB <= 1)
{
// Calcula la intersección
const float x = x1 + (uA * (x2 - x1));
const float y = y1 + (uA * (y2 - y1));
return {(int)round(x), (int)round(y)};
}
return {-1, -1};
}
// Detector de colisiones entre dos lineas
SDL_Point checkCollision(const LineDiagonal &l1, const LineVertical &l2)
{
const float x1 = l1.x1;
const float y1 = l1.y1;
const float x2 = l1.x2;
const float y2 = l1.y2;
const float x3 = l2.x;
const float y3 = l2.y1;
const float x4 = l2.x;
const float y4 = l2.y2;
// calculate the direction of the lines
float uA = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / ((y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1));
float uB = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / ((y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1));
// if uA and uB are between 0-1, lines are colliding
if (uA >= 0 && uA <= 1 && uB >= 0 && uB <= 1)
{
// Calcula la intersección
const float x = x1 + (uA * (x2 - x1));
const float y = y1 + (uA * (y2 - y1));
return {(int)x, (int)y};
}
return {-1, -1};
}
// Normaliza una linea diagonal
void normalizeLine(LineDiagonal &l)
{
// Las lineas diagonales van de izquierda a derecha
// x2 mayor que x1
if (l.x2 < l.x1)
{
const int x = l.x1;
const int y = l.y1;
l.x1 = l.x2;
l.y1 = l.y2;
l.x2 = x;
l.y2 = y;
}
}
// Detector de colisiones entre un punto y una linea diagonal
bool checkCollision(const SDL_Point &p, const LineDiagonal &l)
{
// Comprueba si el punto está en alineado con la linea
if (abs(p.x - l.x1) != abs(p.y - l.y1))
{
return false;
}
// Comprueba si está a la derecha de la linea
if (p.x > l.x1 && p.x > l.x2)
{
return false;
}
// Comprueba si está a la izquierda de la linea
if (p.x < l.x1 && p.x < l.x2)
{
return false;
}
// Comprueba si está por encima de la linea
if (p.y > l.y1 && p.y > l.y2)
{
return false;
}
// Comprueba si está por debajo de la linea
if (p.y < l.y1 && p.y < l.y2)
{
return false;
}
// En caso contrario, el punto está en la linea
return true;
}
// Convierte una cadena a un entero de forma segura
int safeStoi(const std::string &value, int defaultValue)
{
try
{
return std::stoi(value);
}
catch (const std::exception &)
{
return defaultValue;
}
}
// Convierte una cadena a un booleano
bool stringToBool(const std::string &str)
{
std::string lowerStr = str;
std::transform(lowerStr.begin(), lowerStr.end(), lowerStr.begin(), ::tolower);
return (lowerStr == "true" || lowerStr == "1" || lowerStr == "yes" || lowerStr == "on");
}
// Convierte un booleano a una cadena
std::string boolToString(bool value)
{
return value ? "1" : "0";
}
// Compara dos colores
bool colorAreEqual(Color color1, Color color2)
{
const bool r = color1.r == color2.r;
const bool g = color1.g == color2.g;
const bool b = color1.b == color2.b;
return (r && g && b);
}
// Función para convertir un string a minúsculas
std::string toLower(const std::string &str)
{
std::string lower_str = str;
std::transform(lower_str.begin(), lower_str.end(), lower_str.begin(), ::tolower);
return lower_str;
}
// Función para convertir un string a mayúsculas
std::string toUpper(const std::string &str)
{
std::string upper_str = str;
std::transform(upper_str.begin(), upper_str.end(), upper_str.begin(), ::toupper);
return upper_str;
}
// Obtiene el nombre de un fichero a partir de una ruta completa
std::string getFileName(const std::string &path)
{
return std::filesystem::path(path).filename().string();
}
// Obtiene la ruta eliminando el nombre del fichero
std::string getPath(const std::string &full_path)
{
std::filesystem::path path(full_path);
return path.parent_path().string();
}
// Imprime por pantalla una linea de texto de tamaño fijo rellena con puntos
void printWithDots(const std::string &text1, const std::string &text2, const std::string &text3)
{
std::cout.setf(std::ios::left, std::ios::adjustfield);
std::cout << text1;
std::cout.width(50 - text1.length() - text3.length());
std::cout.fill('.');
std::cout << text2;
std::cout << text3 << std::endl;
}
// Comprueba si una vector contiene una cadena
bool stringInVector(const std::vector<std::string> &vec, const std::string &str)
{
return std::find(vec.begin(), vec.end(), str) != vec.end();
}

119
source/utils.h Normal file
View File

@@ -0,0 +1,119 @@
#pragma once
#include <SDL3/SDL_rect.h> // for SDL_Rect, SDL_Point
#include <SDL3/SDL_stdinc.h> // for Uint8
#include <string> // for string
#include <vector> // for vector
// Estructura para definir un circulo
struct Circle
{
int x;
int y;
int r;
};
// Estructura para definir una linea horizontal
struct LineHorizontal
{
int x1, x2, y;
};
// Estructura para definir una linea vertical
struct LineVertical
{
int x, y1, y2;
};
// Estructura para definir una linea diagonal
struct LineDiagonal
{
int x1, y1, x2, y2;
};
// Estructura para definir una linea
struct Line
{
int x1, y1, x2, y2;
};
// Estructura para definir un color
struct Color
{
Uint8 r;
Uint8 g;
Uint8 b;
// Constructor por defecto
Color() : r(0), g(0), b(0) {}
// Constructor
Color(Uint8 red, Uint8 green, Uint8 blue)
: r(red), g(green), b(blue) {}
};
// Calcula el cuadrado de la distancia entre dos puntos
double distanceSquared(int x1, int y1, int x2, int y2);
// Detector de colisiones entre dos circulos
bool checkCollision(const Circle &a, const Circle &b);
// Detector de colisiones entre un circulo y un rectangulo
bool checkCollision(const Circle &a, const SDL_Rect &b);
// Detector de colisiones entre un dos rectangulos
bool checkCollision(const SDL_Rect &a, const SDL_Rect &b);
// Detector de colisiones entre un punto y un rectangulo
bool checkCollision(const SDL_Point &p, const SDL_Rect &r);
// Detector de colisiones entre una linea horizontal y un rectangulo
bool checkCollision(const LineHorizontal &l, const SDL_Rect &r);
// Detector de colisiones entre una linea vertical y un rectangulo
bool checkCollision(const LineVertical &l, const SDL_Rect &r);
// Detector de colisiones entre una linea horizontal y un punto
bool checkCollision(const LineHorizontal &l, const SDL_Point &p);
// Detector de colisiones entre dos lineas
SDL_Point checkCollision(const Line &l1, const Line &l2);
// Detector de colisiones entre dos lineas
SDL_Point checkCollision(const LineDiagonal &l1, const LineVertical &l2);
// Detector de colisiones entre un punto y una linea diagonal
bool checkCollision(const SDL_Point &p, const LineDiagonal &l);
// Normaliza una linea diagonal
void normalizeLine(LineDiagonal &l);
// Convierte una cadena a un entero de forma segura
int safeStoi(const std::string &value, int defaultValue = 0);
// Convierte una cadena a un booleano
bool stringToBool(const std::string &str);
// Convierte un booleano a una cadena
std::string boolToString(bool value);
// Compara dos colores
bool colorAreEqual(Color color1, Color color2);
// Convierte una cadena a minusculas
std::string toLower(const std::string &str);
// Convierte una cadena a mayúsculas
std::string toUpper(const std::string &str);
// Obtiene el nombre de un fichero a partir de una ruta
std::string getFileName(const std::string &path);
// Obtiene la ruta eliminando el nombre del fichero
std::string getPath(const std::string &full_path);
// Imprime por pantalla una linea de texto de tamaño fijo rellena con puntos
void printWithDots(const std::string &text1, const std::string &text2, const std::string &text3);
// Comprueba si una vector contiene una cadena
bool stringInVector(const std::vector<std::string> &vec, const std::string &str);