diff --git a/CLAUDE.md b/CLAUDE.md index 46700e2..4abb55c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -55,6 +55,26 @@ cmake --build build --clean-first **Important:** The build directory is `/Users/sergio/Gitea/jaildoctors_dilemma/build` and the output executable is placed in the project root directory. +### Linter (clang-tidy) + +**IMPORTANT:** Always run the linter on specific files, NOT on the entire project, to avoid long execution times and errors in unrelated files. + +```bash +# Analyze a specific file (RECOMMENDED) +tools/linter/run_clang-tidy.sh source/game/entities/player.cpp + +# Analyze multiple specific files +tools/linter/run_clang-tidy.sh source/game/entities/player.cpp source/game/entities/enemy.cpp + +# Apply fixes automatically to a specific file +tools/linter/run_clang-tidy.sh --fix source/game/entities/player.cpp + +# Analyze entire project (SLOW, may have errors in defaults.hpp) +tools/linter/run_clang-tidy.sh +``` + +**Note:** Running the linter on the entire project can produce errors in files like `defaults.hpp` that are unrelated to your changes. Always target specific files you're working on. + --- ## 1. High-Level Architecture Overview diff --git a/source/core/rendering/gif.cpp b/source/core/rendering/gif.cpp index 364e49b..729e41a 100644 --- a/source/core/rendering/gif.cpp +++ b/source/core/rendering/gif.cpp @@ -14,14 +14,89 @@ inline void readBytes(const uint8_t*& buffer, void* dst, size_t size) { buffer += size; } +// Inicializa el diccionario LZW con los valores iniciales +inline void initializeDictionary(std::vector& dictionary, int code_length, int& dictionary_ind) { + int size = 1 << code_length; + dictionary.resize(1 << (code_length + 1)); + for (dictionary_ind = 0; dictionary_ind < size; dictionary_ind++) { + dictionary[dictionary_ind].byte = static_cast(dictionary_ind); + dictionary[dictionary_ind].prev = -1; + dictionary[dictionary_ind].len = 1; + } + dictionary_ind += 2; // Reservamos espacio para clear y stop codes +} + +// Lee los próximos bits del stream de entrada para formar un código +inline int readNextCode(const uint8_t*& input, int& input_length, unsigned int& mask, int code_length) { + int code = 0; + for (int i = 0; i < (code_length + 1); i++) { + if (input_length <= 0) { + throw std::runtime_error("Unexpected end of input in decompress"); + } + int bit = ((*input & mask) != 0) ? 1 : 0; + mask <<= 1; + if (mask == 0x100) { + mask = 0x01; + input++; + input_length--; + } + code |= (bit << i); + } + return code; +} + +// Encuentra el primer byte de una cadena del diccionario +inline uint8_t findFirstByte(const std::vector& dictionary, int code) { + int ptr = code; + while (dictionary[ptr].prev != -1) { + ptr = dictionary[ptr].prev; + } + return dictionary[ptr].byte; +} + +// Agrega una nueva entrada al diccionario +inline void addDictionaryEntry(std::vector& dictionary, int& dictionary_ind, + int& code_length, int prev, int code) { + uint8_t first_byte; + if (code == dictionary_ind) { + first_byte = findFirstByte(dictionary, prev); + } else { + first_byte = findFirstByte(dictionary, code); + } + + dictionary[dictionary_ind].byte = first_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)); + } +} + +// Escribe la cadena decodificada al buffer de salida +inline int writeDecodedString(const std::vector& dictionary, int code, uint8_t*& out) { + int cur_code = code; + int match_len = dictionary[cur_code].len; + while (cur_code != -1) { + out[dictionary[cur_code].len - 1] = dictionary[cur_code].byte; + if (dictionary[cur_code].prev == cur_code) { + std::cerr << "Internal error; self-reference detected." << std::endl; + throw std::runtime_error("Internal error in decompress: self-reference"); + } + cur_code = dictionary[cur_code].prev; + } + out += match_len; + return match_len; +} + 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; - int bit; int prev = -1; std::vector dictionary; int dictionary_ind; @@ -29,48 +104,22 @@ void Gif::decompress(int code_length, const uint8_t* input, int input_length, ui 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(dictionary_ind); - dictionary[dictionary_ind].prev = -1; - dictionary[dictionary_ind].len = 1; - } - dictionary_ind += 2; // Reservamos espacio para clear y stop codes + initializeDictionary(dictionary, code_length, dictionary_ind); // 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); - } + int code = readNextCode(input, input_length, mask, code_length); 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(dictionary_ind); - dictionary[dictionary_ind].prev = -1; - dictionary[dictionary_ind].len = 1; - } - dictionary_ind += 2; + initializeDictionary(dictionary, code_length, dictionary_ind); prev = -1; continue; } + if (code == stop_code) { break; } @@ -82,28 +131,7 @@ void Gif::decompress(int code_length, const uint8_t* input, int input_length, ui 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)); - } + addDictionaryEntry(dictionary, dictionary_ind, code_length, prev, code); } prev = code; @@ -115,18 +143,7 @@ void Gif::decompress(int code_length, const uint8_t* input, int input_length, ui throw std::runtime_error("LZW error: invalid code encountered"); } - int cur_code = code; // Variable temporal para recorrer la cadena. - match_len = dictionary[cur_code].len; - while (cur_code != -1) { - // Se asume que dictionary[curCode].len > 0. - out[dictionary[cur_code].len - 1] = dictionary[cur_code].byte; - if (dictionary[cur_code].prev == cur_code) { - std::cerr << "Internal error; self-reference detected." << std::endl; - throw std::runtime_error("Internal error in decompress: self-reference"); - } - cur_code = dictionary[cur_code].prev; - } - out += match_len; + writeDecodedString(dictionary, code, out); } } diff --git a/source/core/rendering/opengl/opengl_shader.hpp b/source/core/rendering/opengl/opengl_shader.hpp index 3717251..35066b5 100644 --- a/source/core/rendering/opengl/opengl_shader.hpp +++ b/source/core/rendering/opengl/opengl_shader.hpp @@ -30,7 +30,7 @@ class OpenGLShader : public ShaderBackend { void render() override; void setTextureSize(float width, float height) override; - void cleanup() override; + void cleanup() final; bool isHardwareAccelerated() const override { return is_initialized_; } private: diff --git a/source/core/rendering/surface.cpp b/source/core/rendering/surface.cpp index 0cc4e8b..2c97c2a 100644 --- a/source/core/rendering/surface.cpp +++ b/source/core/rendering/surface.cpp @@ -127,10 +127,9 @@ SurfaceData Surface::loadSurface(const std::string& file_path) { } // Crear un objeto Gif y llamar a la función loadGif - GIF::Gif gif; Uint16 w = 0; Uint16 h = 0; - std::vector raw_pixels = gif.loadGif(buffer.data(), w, h); + std::vector raw_pixels = GIF::Gif::loadGif(buffer.data(), w, h); if (raw_pixels.empty()) { std::cerr << "Error loading GIF from file: " << file_path << std::endl; throw std::runtime_error("Error loading GIF"); @@ -336,6 +335,24 @@ void Surface::render(int x, int y, SDL_FRect* src_rect, SDL_FlipMode flip) { } } +// Helper para calcular coordenadas con flip +void Surface::calculateFlippedCoords(int ix, int iy, float sx, float sy, float w, float h, SDL_FlipMode flip, int& src_x, int& src_y) { + src_x = (flip == SDL_FLIP_HORIZONTAL) ? (sx + w - 1 - ix) : (sx + ix); + src_y = (flip == SDL_FLIP_VERTICAL) ? (sy + h - 1 - iy) : (sy + iy); +} + +// Helper para copiar un pixel si no es transparente +void Surface::copyPixelIfNotTransparent(Uint8* dest_data, int dest_x, int dest_y, int dest_width, int src_x, int src_y) const { + if (dest_x < 0 || dest_y < 0) { + return; + } + + Uint8 color = surface_data_->data.get()[static_cast(src_x + (src_y * surface_data_->width))]; + if (color != transparent_color_) { + dest_data[dest_x + (dest_y * dest_width)] = sub_palette_[color]; + } +} + // Copia una región de la superficie de origen a la de destino void Surface::render(SDL_FRect* src_rect, SDL_FRect* dst_rect, SDL_FlipMode flip) { auto surface_data = Screen::get()->getRendererSurface()->getSurfaceData(); @@ -370,19 +387,16 @@ void Surface::render(SDL_FRect* src_rect, SDL_FRect* dst_rect, SDL_FlipMode flip // 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); + int src_x = 0; + int src_y = 0; + calculateFlippedCoords(ix, iy, sx, sy, final_width, final_height, flip, src_x, src_y); - // 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()[static_cast(src_x + (src_y * surface_data_->width))]; - if (color != transparent_color_) { - surface_data->data[dest_x + (dest_y * surface_data->width)] = sub_palette_[color]; - } - } + int dest_x = dx + ix; + int dest_y = dy + iy; + + // Verificar límites de destino antes de copiar + if (dest_x >= 0 && dest_x < surface_data->width && dest_y >= 0 && dest_y < surface_data->height) { + copyPixelIfNotTransparent(surface_data->data.get(), dest_x, dest_y, surface_data->width, src_x, src_y); } } } diff --git a/source/core/rendering/surface.hpp b/source/core/rendering/surface.hpp index 3acaf78..1e8baa2 100644 --- a/source/core/rendering/surface.hpp +++ b/source/core/rendering/surface.hpp @@ -78,7 +78,7 @@ class Surface { // Copia una región de la SurfaceData de origen a la SurfaceData de destino void render(float dx, float dy, float sx, float sy, float w, float h); - void render(int x, int y, SDL_FRect* clip = nullptr, SDL_FlipMode flip = SDL_FLIP_NONE); + void render(int x, int y, SDL_FRect* src_rect = nullptr, SDL_FlipMode flip = SDL_FLIP_NONE); void render(SDL_FRect* src_rect = nullptr, SDL_FRect* dst_rect = 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 @@ -130,4 +130,11 @@ class Surface { // Inicializa la sub paleta static void initializeSubPalette(SubPalette& palette) { std::iota(palette.begin(), palette.end(), 0); } + + private: + // Helper para calcular coordenadas con flip + static void calculateFlippedCoords(int ix, int iy, float sx, float sy, float w, float h, SDL_FlipMode flip, int& src_x, int& src_y); + + // Helper para copiar un pixel si no es transparente + void copyPixelIfNotTransparent(Uint8* dest_data, int dest_x, int dest_y, int dest_width, int src_x, int src_y) const; }; diff --git a/source/core/rendering/surface_animated_sprite.cpp b/source/core/rendering/surface_animated_sprite.cpp index b43d8ef..18646a7 100644 --- a/source/core/rendering/surface_animated_sprite.cpp +++ b/source/core/rendering/surface_animated_sprite.cpp @@ -135,6 +135,96 @@ void SurfaceAnimatedSprite::resetAnimation() { animations_[current_animation_].completed = false; } +// Helper: Parsea los parámetros de configuración globales (frame_width, frame_height) +bool parseGlobalParameter(const std::string& line, float& frame_width, float& frame_height) { + size_t pos = line.find("="); + if (pos == std::string::npos) { + return false; + } + + std::string key = line.substr(0, pos); + int value = std::stoi(line.substr(pos + 1)); + + if (key == "frame_width") { + frame_width = value; + return true; + } + if (key == "frame_height") { + frame_height = value; + return true; + } + + std::cout << "Warning: unknown parameter " << key << std::endl; + return false; +} + +// Helper: Parsea los frames de una animación desde una cadena separada por comas +void parseAnimationFrames(const std::string& value, AnimationData& animation, + float frame_width, float frame_height, + int frames_per_row, int max_tiles) { + std::stringstream ss(value); + std::string tmp; + SDL_FRect rect = {0.0F, 0.0F, frame_width, frame_height}; + + while (getline(ss, tmp, ',')) { + const int NUM_TILE = std::stoi(tmp); + if (NUM_TILE <= max_tiles) { + rect.x = (NUM_TILE % frames_per_row) * frame_width; + rect.y = (NUM_TILE / frames_per_row) * frame_height; + animation.frames.emplace_back(rect); + } + } +} + +// Helper: Parsea un parámetro de animación individual +bool parseAnimationParameter(const std::string& key, const std::string& value, + AnimationData& animation, + float frame_width, float frame_height, + int frames_per_row, int max_tiles) { + if (key == "name") { + animation.name = value; + return true; + } + if (key == "speed") { + animation.speed = std::stoi(value); + return true; + } + if (key == "loop") { + animation.loop = std::stoi(value); + return true; + } + if (key == "frames") { + parseAnimationFrames(value, animation, frame_width, frame_height, frames_per_row, max_tiles); + return true; + } + + std::cout << "Warning: unknown parameter " << key << std::endl; + return false; +} + +// Helper: Parsea una animación completa +AnimationData parseAnimation(const Animations& animations, size_t& index, + float frame_width, float frame_height, + int frames_per_row, int max_tiles) { + AnimationData animation; + std::string line; + + do { + index++; + line = animations.at(index); + size_t pos = line.find("="); + + if (pos != std::string::npos) { + std::string key = line.substr(0, pos); + std::string value = line.substr(pos + 1); + parseAnimationParameter(key, value, animation, frame_width, frame_height, + frames_per_row, max_tiles); + } + } while (line != "[/animation]"); + + return animation; +} + // Carga la animación desde un vector de cadenas void SurfaceAnimatedSprite::setAnimations(const Animations& animations) { float frame_width = 1.0F; @@ -148,21 +238,7 @@ void SurfaceAnimatedSprite::setAnimations(const Animations& animations) { // Parsea el fichero para buscar variables y valores if (line != "[animation]") { - // Encuentra la posición del caracter '=' - size_t pos = line.find("="); - - // Procesa las dos subcadenas - if (pos != std::string::npos) { - std::string key = line.substr(0, pos); - int value = std::stoi(line.substr(pos + 1)); - if (key == "frame_width") { - frame_width = value; - } else if (key == "frame_height") { - frame_height = value; - } else { - std::cout << "Warning: unknown parameter " << key << std::endl; - } - + if (parseGlobalParameter(line, frame_width, frame_height)) { frames_per_row = surface_->getWidth() / frame_width; const int W = surface_->getWidth() / frame_width; const int H = surface_->getHeight() / frame_height; @@ -172,45 +248,8 @@ void SurfaceAnimatedSprite::setAnimations(const Animations& animations) { // Si la linea contiene el texto [animation] se realiza el proceso de carga de una animación if (line == "[animation]") { - AnimationData animation; - do { - index++; - line = animations.at(index); - size_t pos = line.find("="); - - if (pos != std::string::npos) { - std::string key = line.substr(0, pos); - std::string value = line.substr(pos + 1); - - if (key == "name") { - animation.name = value; - } else if (key == "speed") { - animation.speed = std::stoi(value); - } else if (key == "loop") { - animation.loop = std::stoi(value); - } else if (key == "frames") { - // Se introducen los valores separados por comas en un vector - std::stringstream ss(value); - std::string tmp; - SDL_FRect rect = {0.0F, 0.0F, frame_width, frame_height}; - while (getline(ss, tmp, ',')) { - // Comprueba que el tile no sea mayor que el maximo indice permitido - const int NUM_TILE = std::stoi(tmp); - if (NUM_TILE <= max_tiles) { - rect.x = (NUM_TILE % frames_per_row) * frame_width; - rect.y = (NUM_TILE / frames_per_row) * frame_height; - animation.frames.emplace_back(rect); - } - } - } - - else { - std::cout << "Warning: unknown parameter " << key << std::endl; - } - } - } while (line != "[/animation]"); - - // Añade la animación al vector de animaciones + AnimationData animation = parseAnimation(animations, index, frame_width, frame_height, + frames_per_row, max_tiles); animations_.emplace_back(animation); } diff --git a/source/game/entities/enemy.cpp b/source/game/entities/enemy.cpp index 19acef4..c7c9e3f 100644 --- a/source/game/entities/enemy.cpp +++ b/source/game/entities/enemy.cpp @@ -25,8 +25,8 @@ Enemy::Enemy(const EnemyData& enemy) sprite_->setWidth(enemy.w); sprite_->setHeight(enemy.h); - const SDL_FlipMode FLIP = (should_flip_ && enemy.vx < 0.0F) ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE; - const SDL_FlipMode MIRROR = should_mirror_ ? SDL_FLIP_VERTICAL : SDL_FLIP_NONE; + const int FLIP = (should_flip_ && enemy.vx < 0.0F) ? SDL_FLIP_HORIZONTAL : SDL_FLIP_NONE; + const int MIRROR = should_mirror_ ? SDL_FLIP_VERTICAL : SDL_FLIP_NONE; sprite_->setFlip(static_cast(FLIP | MIRROR)); collider_ = getRect(); diff --git a/source/game/entities/player.cpp b/source/game/entities/player.cpp index 31f5b37..4a6ada5 100644 --- a/source/game/entities/player.cpp +++ b/source/game/entities/player.cpp @@ -3,6 +3,7 @@ #include // Para max, min #include // Para ceil, abs +#include // Para std::ranges::any_of #include "core/input/input.hpp" // Para Input, InputAction #include "core/rendering/surface_animated_sprite.hpp" // Para SAnimatedSprite @@ -213,6 +214,167 @@ void Player::applyGravity() { } } +// Maneja el movimiento horizontal hacia la izquierda +void Player::moveHorizontalLeft() { + // Crea el rectangulo de proyección en el eje X para ver si colisiona + SDL_FRect proj; + proj.x = static_cast(x_ + vx_); + proj.y = static_cast(y_); + proj.h = HEIGHT; + proj.w = static_cast(std::ceil(std::fabs(vx_))); // Para evitar que tenga un ancho de 0 pixels + +#ifdef _DEBUG + debug_rect_x_ = proj; +#endif + + // Comprueba la colisión con las superficies + const int POS = room_->checkRightSurfaces(&proj); + + // Calcula la nueva posición + if (POS == -1) { + // Si no hay colisión + x_ += vx_; + } else { + // Si hay colisión lo mueve hasta donde no colisiona + x_ = POS + 1; + } + + // Si ha tocado alguna rampa mientras camina (sin saltar), asciende + if (state_ != PlayerState::JUMPING) { + const LineVertical LEFT_SIDE = {static_cast(x_), static_cast(y_) + static_cast(HEIGHT) - 2, static_cast(y_) + static_cast(HEIGHT) - 1}; // Comprueba solo los dos pixels de abajo + const int LY = room_->checkLeftSlopes(&LEFT_SIDE); + if (LY > -1) { + y_ = LY - HEIGHT; + } + } + + // Si está bajando la rampa, recoloca al jugador + if (isOnDownSlope() && state_ != PlayerState::JUMPING) { + y_ += 1; + } +} + +// Maneja el movimiento horizontal hacia la derecha +void Player::moveHorizontalRight() { + // Crea el rectangulo de proyección en el eje X para ver si colisiona + SDL_FRect proj; + proj.x = x_ + WIDTH; + proj.y = y_; + proj.h = HEIGHT; + proj.w = ceil(vx_); // Para evitar que tenga un ancho de 0 pixels + +#ifdef _DEBUG + debug_rect_x_ = proj; +#endif + + // Comprueba la colisión + const int POS = room_->checkLeftSurfaces(&proj); + + // Calcula la nueva posición + if (POS == -1) { + // Si no hay colisión + x_ += vx_; + } else { + // Si hay colisión lo mueve hasta donde no colisiona + x_ = POS - WIDTH; + } + + // Si ha tocado alguna rampa mientras camina (sin saltar), asciende + if (state_ != PlayerState::JUMPING) { + const LineVertical RIGHT_SIDE = {static_cast(x_) + static_cast(WIDTH) - 1, static_cast(y_) + static_cast(HEIGHT) - 2, static_cast(y_) + static_cast(HEIGHT) - 1}; // Comprueba solo los dos pixels de abajo + const int RY = room_->checkRightSlopes(&RIGHT_SIDE); + if (RY > -1) { + y_ = RY - HEIGHT; + } + } + + // Si está bajando la rampa, recoloca al jugador + if (isOnDownSlope() && state_ != PlayerState::JUMPING) { + y_ += 1; + } +} + +// Maneja el movimiento vertical hacia arriba +void Player::moveVerticalUp() { + // Crea el rectangulo de proyección en el eje Y para ver si colisiona + SDL_FRect proj; + proj.x = static_cast(x_); + proj.y = static_cast(y_ + vy_); + proj.h = static_cast(std::ceil(std::fabs(vy_))); // Para evitar que tenga una altura de 0 pixels + proj.w = WIDTH; + +#ifdef _DEBUG + debug_rect_y_ = proj; +#endif + + // Comprueba la colisión + const int POS = room_->checkBottomSurfaces(&proj); + + // Calcula la nueva posición + if (POS == -1) { + // Si no hay colisión + y_ += vy_; + } else { + // Si hay colisión lo mueve hasta donde no colisiona y entra en caída + y_ = POS + 1; + setState(PlayerState::FALLING); + } +} + +// Maneja el movimiento vertical hacia abajo +void Player::moveVerticalDown() { + // Crea el rectangulo de proyección en el eje Y para ver si colisiona + SDL_FRect proj; + proj.x = x_; + proj.y = y_ + HEIGHT; + proj.h = ceil(vy_); // Para evitar que tenga una altura de 0 pixels + proj.w = WIDTH; + +#ifdef _DEBUG + debug_rect_y_ = proj; +#endif + + // Comprueba la colisión con las superficies normales y las automáticas + const float POS = std::max(room_->checkTopSurfaces(&proj), room_->checkAutoSurfaces(&proj)); + if (POS > -1) { + // Si hay colisión lo mueve hasta donde no colisiona y pasa a estar sobre la superficie + y_ = POS - HEIGHT; + setState(PlayerState::STANDING); + + // Deja de estar enganchado a la superficie automatica + auto_movement_ = false; + } else { + // Si no hay colisión con los muros, comprueba la colisión con las rampas + if (state_ != PlayerState::JUMPING) { // Las rampas no se miran si se está saltando + auto rect = toSDLRect(proj); + const LineVertical LEFT_SIDE = {rect.x, rect.y, rect.y + rect.h - 1}; + const LineVertical RIGHT_SIDE = {rect.x + rect.w - 1, rect.y, rect.y + rect.h - 1}; + const float POINT = std::max(room_->checkRightSlopes(&RIGHT_SIDE), room_->checkLeftSlopes(&LEFT_SIDE)); + if (POINT > -1) { + // No está saltando y hay colisión con una rampa + // Calcula la nueva posición + y_ = POINT - HEIGHT; + setState(PlayerState::STANDING); +#ifdef _DEBUG + debug_color_ = static_cast(PaletteColor::YELLOW); + debug_point_ = {x_ + (WIDTH / 2), POINT}; +#endif + } else { + // No está saltando y no hay colisón con una rampa + // Calcula la nueva posición + y_ += vy_; +#ifdef _DEBUG + debug_color_ = static_cast(PaletteColor::RED); +#endif + } + } else { + // Esta saltando y no hay colisión con los muros + // Calcula la nueva posición + y_ += vy_; + } + } +} + // Recalcula la posición del jugador y su animación void Player::move() { last_position_ = {x_, y_}; // Guarda la posicion actual antes de modificarla @@ -223,179 +385,29 @@ void Player::move() { debug_color_ = static_cast(PaletteColor::GREEN); #endif - // Se mueve hacia la izquierda + // Movimiento horizontal if (vx_ < 0.0F) { - // Crea el rectangulo de proyección en el eje X para ver si colisiona - SDL_FRect proj; - proj.x = static_cast(x_ + vx_); - proj.y = static_cast(y_); - proj.h = HEIGHT; - proj.w = static_cast(std::ceil(std::fabs(vx_))); // Para evitar que tenga un ancho de 0 pixels - -#ifdef _DEBUG - debug_rect_x_ = proj; -#endif - - // Comprueba la colisión con las superficies - const int POS = room_->checkRightSurfaces(&proj); - - // Calcula la nueva posición - if (POS == -1) { - // Si no hay colisión - x_ += vx_; - } else { - // Si hay colisión lo mueve hasta donde no colisiona - x_ = POS + 1; - } - - // Si ha tocado alguna rampa mientras camina (sin saltar), asciende - if (state_ != PlayerState::JUMPING) { - const LineVertical LEFT_SIDE = {static_cast(x_), static_cast(y_) + static_cast(HEIGHT) - 2, static_cast(y_) + static_cast(HEIGHT) - 1}; // Comprueba solo los dos pixels de abajo - const int LY = room_->checkLeftSlopes(&LEFT_SIDE); - if (LY > -1) { - y_ = LY - HEIGHT; - } - } - - // Si está bajando la rampa, recoloca al jugador - if (isOnDownSlope() && state_ != PlayerState::JUMPING) { - y_ += 1; - } - } - - // Se mueve hacia la derecha - else if (vx_ > 0.0F) { - // Crea el rectangulo de proyección en el eje X para ver si colisiona - SDL_FRect proj; - proj.x = x_ + WIDTH; - proj.y = y_; - proj.h = HEIGHT; - proj.w = ceil(vx_); // Para evitar que tenga un ancho de 0 pixels - -#ifdef _DEBUG - debug_rect_x_ = proj; -#endif - - // Comprueba la colisión - const int POS = room_->checkLeftSurfaces(&proj); - - // Calcula la nueva posición - if (POS == -1) { - // Si no hay colisión - x_ += vx_; - } else { - // Si hay colisión lo mueve hasta donde no colisiona - x_ = POS - WIDTH; - } - - // Si ha tocado alguna rampa mientras camina (sin saltar), asciende - if (state_ != PlayerState::JUMPING) { - const LineVertical RIGHT_SIDE = {static_cast(x_) + static_cast(WIDTH) - 1, static_cast(y_) + static_cast(HEIGHT) - 2, static_cast(y_) + static_cast(HEIGHT) - 1}; // Comprueba solo los dos pixels de abajo - const int RY = room_->checkRightSlopes(&RIGHT_SIDE); - if (RY > -1) { - y_ = RY - HEIGHT; - } - } - - // Si está bajando la rampa, recoloca al jugador - if (isOnDownSlope() && state_ != PlayerState::JUMPING) { - y_ += 1; - } + moveHorizontalLeft(); + } else if (vx_ > 0.0F) { + moveHorizontalRight(); } // Si ha salido del suelo, el jugador cae if (state_ == PlayerState::STANDING && !isOnFloor()) { setState(PlayerState::FALLING); - - // Deja de estar enganchado a la superficie automatica auto_movement_ = false; } // Si ha salido de una superficie automatica, detiene el movimiento automatico if (state_ == PlayerState::STANDING && isOnFloor() && !isOnAutoSurface()) { - // Deja de estar enganchado a la superficie automatica auto_movement_ = false; } - // Se mueve hacia arriba + // Movimiento vertical if (vy_ < 0.0F) { - // Crea el rectangulo de proyección en el eje Y para ver si colisiona - SDL_FRect proj; - proj.x = static_cast(x_); - proj.y = static_cast(y_ + vy_); - proj.h = static_cast(std::ceil(std::fabs(vy_))); // Para evitar que tenga una altura de 0 pixels - proj.w = WIDTH; - -#ifdef _DEBUG - debug_rect_y_ = proj; -#endif - - // Comprueba la colisión - const int POS = room_->checkBottomSurfaces(&proj); - - // Calcula la nueva posición - if (POS == -1) { - // Si no hay colisión - y_ += vy_; - } else { - // Si hay colisión lo mueve hasta donde no colisiona y entra en caída - y_ = POS + 1; - setState(PlayerState::FALLING); - } - } - - // Se mueve hacia abajo - else if (vy_ > 0.0F) { - // Crea el rectangulo de proyección en el eje Y para ver si colisiona - SDL_FRect proj; - proj.x = x_; - proj.y = y_ + HEIGHT; - proj.h = ceil(vy_); // Para evitar que tenga una altura de 0 pixels - proj.w = WIDTH; - -#ifdef _DEBUG - debug_rect_y_ = proj; -#endif - - // Comprueba la colisión con las superficies normales y las automáticas - const float POS = std::max(room_->checkTopSurfaces(&proj), room_->checkAutoSurfaces(&proj)); - if (POS > -1) { - // Si hay colisión lo mueve hasta donde no colisiona y pasa a estar sobre la superficie - y_ = POS - HEIGHT; - setState(PlayerState::STANDING); - - // Deja de estar enganchado a la superficie automatica - auto_movement_ = false; - } else { - // Si no hay colisión con los muros, comprueba la colisión con las rampas - if (state_ != PlayerState::JUMPING) { // Las rampas no se miran si se está saltando - auto rect = toSDLRect(proj); - const LineVertical LEFT_SIDE = {rect.x, rect.y, rect.y + rect.h - 1}; - const LineVertical RIGHT_SIDE = {rect.x + rect.w - 1, rect.y, rect.y + rect.h - 1}; - const float POINT = std::max(room_->checkRightSlopes(&RIGHT_SIDE), room_->checkLeftSlopes(&LEFT_SIDE)); - if (POINT > -1) { - // No está saltando y hay colisión con una rampa - // Calcula la nueva posición - y_ = POINT - HEIGHT; - setState(PlayerState::STANDING); -#ifdef _DEBUG - debug_color_ = static_cast(PaletteColor::YELLOW); - debug_point_ = {x_ + (WIDTH / 2), POINT}; -#endif - } else { - // No está saltando y no hay colisón con una rampa - // Calcula la nueva posición - y_ += vy_; -#ifdef _DEBUG - debug_color_ = static_cast(PaletteColor::RED); -#endif - } - } else { - // Esta saltando y no hay colisión con los muros - // Calcula la nueva posición - y_ += vy_; - } - } + moveVerticalUp(); + } else if (vy_ > 0.0F) { + moveVerticalDown(); } placeSprite(); // Coloca el sprite en la nueva posición @@ -534,12 +546,12 @@ bool Player::checkKillingTiles() { // Actualiza los puntos de colisión updateColliderPoints(); - // Comprueba si hay contacto y retorna en cuanto se encuentra colisión - for (const auto& c : collider_points_) { - if (room_->getTile(c) == TileType::KILL) { - is_alive_ = false; // Mata al jugador inmediatamente - return true; // Retorna en cuanto se detecta una colisión - } + // Comprueba si hay contacto con algún tile que mata + if (std::ranges::any_of(collider_points_, [this](const auto& c) { + return room_->getTile(c) == TileType::KILL; + })) { + is_alive_ = false; // Mata al jugador inmediatamente + return true; // Retorna en cuanto se detecta una colisión } return false; // No se encontró ninguna colisión diff --git a/source/game/entities/player.hpp b/source/game/entities/player.hpp index 99398d7..d897a2c 100644 --- a/source/game/entities/player.hpp +++ b/source/game/entities/player.hpp @@ -120,6 +120,18 @@ class Player { // Recalcula la posición del jugador y su animación void move(); + // Maneja el movimiento horizontal hacia la izquierda + void moveHorizontalLeft(); + + // Maneja el movimiento horizontal hacia la derecha + void moveHorizontalRight(); + + // Maneja el movimiento vertical hacia arriba + void moveVerticalUp(); + + // Maneja el movimiento vertical hacia abajo + void moveVerticalDown(); + // Establece la animación del jugador void animate(); @@ -163,7 +175,7 @@ class Player { void applySpawnValues(const PlayerSpawn& spawn); // Inicializa el sprite del jugador - void initSprite(const std::string& texture_path, const std::string& animations_path); + void initSprite(const std::string& surface_path, const std::string& animations_path); #ifdef _DEBUG // Pinta la información de debug del jugador diff --git a/source/game/gameplay/room.cpp b/source/game/gameplay/room.cpp index 744af8c..1a7f94d 100644 --- a/source/game/gameplay/room.cpp +++ b/source/game/gameplay/room.cpp @@ -1,5 +1,6 @@ #include "game/gameplay/room.hpp" +#include // Para std::ranges::any_of #include // Para exception #include // Para basic_ostream, operator<<, basic_istream #include // Para cout, cerr @@ -60,6 +61,61 @@ std::vector loadRoomTileFile(const std::string& file_path, bool verbose) { return tile_map_file; } +// Parsea una línea en key y value separados por '=' +std::pair parseKeyValue(const std::string& line) { + int pos = line.find("="); + std::string key = line.substr(0, pos); + std::string value = line.substr(pos + 1, line.length()); + return {key, value}; +} + +// Muestra un warning de parámetro desconocido +void logUnknownParameter(const std::string& file_name, const std::string& key, bool verbose) { + if (verbose) { + std::cout << "Warning: file " << file_name.c_str() << "\n, unknown parameter \"" << key.c_str() << "\"" << std::endl; + } +} + +// Carga un bloque [enemy]...[/enemy] desde un archivo +EnemyData loadEnemyFromFile(std::ifstream& file, const std::string& file_name, bool verbose) { + EnemyData enemy; + enemy.flip = false; + enemy.mirror = false; + enemy.frame = -1; + + std::string line; + do { + std::getline(file, line); + auto [key, value] = parseKeyValue(line); + + if (!setEnemy(&enemy, key, value)) { + logUnknownParameter(file_name, key, verbose); + } + } while (line != "[/enemy]"); + + return enemy; +} + +// Carga un bloque [item]...[/item] desde un archivo +ItemData loadItemFromFile(std::ifstream& file, const std::string& file_name, bool verbose) { + ItemData item; + item.counter = 0; + item.color1 = stringToColor("yellow"); + item.color2 = stringToColor("magenta"); + + std::string line; + do { + std::getline(file, line); + auto [key, value] = parseKeyValue(line); + + if (!setItem(&item, key, value)) { + logUnknownParameter(file_name, key, verbose); + } + } while (line != "[/item]"); + + return item; +} + // Carga las variables desde un fichero de mapa RoomData loadRoomFile(const std::string& file_path, bool verbose) { RoomData room; @@ -79,70 +135,17 @@ RoomData loadRoomFile(const std::string& file_path, bool verbose) { while (std::getline(file, line)) { // Si la linea contiene el texto [enemy] se realiza el proceso de carga de un enemigo if (line == "[enemy]") { - EnemyData enemy; - enemy.flip = false; - enemy.mirror = false; - enemy.frame = -1; - - do { - std::getline(file, line); - - // Encuentra la posición del caracter '=' - int pos = line.find("="); - - // Procesa las dos subcadenas - std::string key = line.substr(0, pos); - std::string value = line.substr(pos + 1, line.length()); - if (!setEnemy(&enemy, key, value)) { - if (verbose) { - std::cout << "Warning: file " << FILE_NAME.c_str() << "\n, unknown parameter \"" << line.substr(0, pos).c_str() << "\"" << std::endl; - } - } - } while (line != "[/enemy]"); - - // Añade el enemigo al vector de enemigos - room.enemies.push_back(enemy); + room.enemies.push_back(loadEnemyFromFile(file, FILE_NAME, verbose)); } - // Si la linea contiene el texto [item] se realiza el proceso de carga de un item else if (line == "[item]") { - ItemData item; - item.counter = 0; - item.color1 = stringToColor("yellow"); - item.color2 = stringToColor("magenta"); - - do { - std::getline(file, line); - - // Encuentra la posición del caracter '=' - int pos = line.find("="); - - // Procesa las dos subcadenas - std::string key = line.substr(0, pos); - std::string value = line.substr(pos + 1, line.length()); - if (!setItem(&item, key, value)) { - if (verbose) { - std::cout << "Warning: file " << FILE_NAME.c_str() << "\n, unknown parameter \"" << line.substr(0, pos).c_str() << "\"" << std::endl; - } - } - - } while (line != "[/item]"); - - room.items.push_back(item); + room.items.push_back(loadItemFromFile(file, FILE_NAME, verbose)); } - // En caso contrario se parsea el fichero para buscar las variables y los valores else { - // Encuentra la posición del caracter '=' - int pos = line.find("="); - - // Procesa las dos subcadenas - std::string key = line.substr(0, pos); - std::string value = line.substr(pos + 1, line.length()); + auto [key, value] = parseKeyValue(line); if (!setRoom(&room, key, value)) { - if (verbose) { - std::cout << "Warning: file " << FILE_NAME.c_str() << "\n, unknown parameter \"" << line.substr(0, pos).c_str() << "\"" << std::endl; - } + logUnknownParameter(FILE_NAME, key, verbose); } } } @@ -155,9 +158,7 @@ RoomData loadRoomFile(const std::string& file_path, bool verbose) { } // El fichero no se puede abrir else { - { - std::cout << "Warning: Unable to open " << FILE_NAME.c_str() << " file" << std::endl; - } + std::cout << "Warning: Unable to open " << FILE_NAME.c_str() << " file" << std::endl; } return room; @@ -555,7 +556,7 @@ TileType Room::getTile(int index) { } // La fila 23 es de tiles t_kill - else if ((tile_map_[index] >= 23 * tile_set_width_) && (tile_map_[index] < 24 * tile_set_width_)) { + if ((tile_map_[index] >= 23 * tile_set_width_) && (tile_map_[index] < 24 * tile_set_width_)) { return TileType::KILL; } } @@ -565,12 +566,9 @@ TileType Room::getTile(int index) { // Indica si hay colision con un enemigo a partir de un rectangulo bool Room::enemyCollision(SDL_FRect& rect) { - for (const auto& enemy : enemies_) { - if (checkCollision(rect, enemy->getCollider())) { - return true; - } - } - return false; + return std::ranges::any_of(enemies_, [&rect](const auto& enemy) { + return checkCollision(rect, enemy->getCollider()); + }); } // Indica si hay colision con un objeto a partir de un rectangulo @@ -619,8 +617,8 @@ int Room::getSlopeHeight(SDL_FPoint p, TileType slope) { return base; } -// Calcula las superficies inferiores -void Room::setBottomSurfaces() { +// Helper: recopila tiles inferiores (muros sin muro debajo) +std::vector Room::collectBottomTiles() { std::vector tile; // Busca todos los tiles de tipo muro que no tengan debajo otro muro @@ -638,40 +636,11 @@ void Room::setBottomSurfaces() { // Añade un terminador tile.push_back(-1); - - // Recorre el vector de tiles buscando tiles consecutivos para localizar las superficies - if ((int)tile.size() > 1) { - int i = 0; - do { - LineHorizontal line; - line.x1 = (tile[i] % MAP_WIDTH) * TILE_SIZE; - line.y = ((tile[i] / MAP_WIDTH) * TILE_SIZE) + TILE_SIZE - 1; - int last_one = i; - i++; - - if (i <= (int)tile.size() - 1) { - while (tile[i] == tile[i - 1] + 1) { - last_one = i; - if (i == (int)tile.size() - 1) { - break; - } - i++; - } - } - - line.x2 = ((tile[last_one] % MAP_WIDTH) * TILE_SIZE) + TILE_SIZE - 1; - bottom_floors_.push_back(line); - if (i <= (int)tile.size() - 1) { - if (tile[i] == -1) { // Si el siguiente elemento es un separador, hay que saltarlo - i++; - } - } - } while (i < (int)tile.size() - 1); - } + return tile; } -// Calcula las superficies superiores -void Room::setTopSurfaces() { +// Helper: recopila tiles superiores (muros o pasables sin muro encima) +std::vector Room::collectTopTiles() { std::vector tile; // Busca todos los tiles de tipo muro o pasable que no tengan encima un muro @@ -689,36 +658,61 @@ void Room::setTopSurfaces() { // Añade un terminador tile.push_back(-1); + return tile; +} - // Recorre el vector de tiles buscando tiles consecutivos para localizar las superficies - if ((int)tile.size() > 1) { - int i = 0; - do { - LineHorizontal line; - line.x1 = (tile[i] % MAP_WIDTH) * TILE_SIZE; - line.y = (tile[i] / MAP_WIDTH) * TILE_SIZE; - int last_one = i; - i++; - - if (i <= (int)tile.size() - 1) { - while (tile[i] == tile[i - 1] + 1) { - last_one = i; - if (i == (int)tile.size() - 1) { - break; - } - i++; - } - } - - line.x2 = ((tile[last_one] % MAP_WIDTH) * TILE_SIZE) + TILE_SIZE - 1; - top_floors_.push_back(line); - if (i <= (int)tile.size() - 1) { - if (tile[i] == -1) { // Si el siguiente elemento es un separador, hay que saltarlo - i++; - } - } - } while (i < (int)tile.size() - 1); +// Helper: construye lineas horizontales a partir de tiles consecutivos +void Room::buildHorizontalLines(const std::vector& tiles, std::vector& lines, bool is_bottom_surface) { + if (tiles.size() <= 1) { + return; } + + int i = 0; + while (i < (int)tiles.size() - 1) { + LineHorizontal line; + line.x1 = (tiles[i] % MAP_WIDTH) * TILE_SIZE; + + // Calcula Y segun si es superficie inferior o superior + if (is_bottom_surface) { + line.y = ((tiles[i] / MAP_WIDTH) * TILE_SIZE) + TILE_SIZE - 1; + } else { + line.y = (tiles[i] / MAP_WIDTH) * TILE_SIZE; + } + + int last_one = i; + i++; + + // Encuentra tiles consecutivos + if (i < (int)tiles.size()) { + while (tiles[i] == tiles[i - 1] + 1) { + last_one = i; + i++; + if (i >= (int)tiles.size()) { + break; + } + } + } + + line.x2 = ((tiles[last_one] % MAP_WIDTH) * TILE_SIZE) + TILE_SIZE - 1; + lines.push_back(line); + + // Salta separadores + if (i < (int)tiles.size() && tiles[i] == -1) { + i++; + } + } +} + +// Calcula las superficies inferiores +void Room::setBottomSurfaces() { + std::vector tile = collectBottomTiles(); + buildHorizontalLines(tile, bottom_floors_, true); +} + +// Calcula las superficies superiores +void Room::setTopSurfaces() { + std::vector tile = collectTopTiles(); + buildHorizontalLines(tile, top_floors_, false); } // Calcula las superficies laterales izquierdas @@ -872,7 +866,8 @@ void Room::setRightSlopes() { } // Calcula las superficies automaticas -void Room::setAutoSurfaces() { +// Helper: recopila tiles animados (para superficies automaticas/conveyor belts) +std::vector Room::collectAnimatedTiles() { std::vector tile; // Busca todos los tiles de tipo animado @@ -888,35 +883,17 @@ void Room::setAutoSurfaces() { } } - // Recorre el vector de tiles buscando tiles consecutivos para localizar las superficies - if ((int)tile.size() > 0) { - int i = 0; - do { - LineHorizontal line; - line.x1 = (tile[i] % MAP_WIDTH) * TILE_SIZE; - line.y = (tile[i] / MAP_WIDTH) * TILE_SIZE; - int last_one = i; - i++; - - if (i <= (int)tile.size() - 1) { - while (tile[i] == tile[i - 1] + 1) { - last_one = i; - if (i == (int)tile.size() - 1) { - break; - } - i++; - } - } - - line.x2 = ((tile[last_one] % MAP_WIDTH) * TILE_SIZE) + TILE_SIZE - 1; - conveyor_belt_floors_.push_back(line); - if (i <= (int)tile.size() - 1) { - if (tile[i] == -1) { // Si el siguiente elemento es un separador, hay que saltarlo - i++; - } - } - } while (i < (int)tile.size() - 1); + // Añade un terminador si hay tiles + if (!tile.empty()) { + tile.push_back(-1); } + + return tile; +} + +void Room::setAutoSurfaces() { + std::vector tile = collectAnimatedTiles(); + buildHorizontalLines(tile, conveyor_belt_floors_, false); } // Localiza todos los tiles animados de la habitación @@ -1022,24 +999,16 @@ int Room::checkAutoSurfaces(SDL_FRect* rect) { // Comprueba las colisiones bool Room::checkTopSurfaces(SDL_FPoint* p) { - for (const auto& s : top_floors_) { - if (checkCollision(s, *p)) { - return true; - } - } - - return false; + return std::ranges::any_of(top_floors_, [&](const auto& s) { + return checkCollision(s, *p); + }); } // Comprueba las colisiones bool Room::checkAutoSurfaces(SDL_FPoint* p) { - for (const auto& s : conveyor_belt_floors_) { - if (checkCollision(s, *p)) { - return true; - } - } - - return false; + return std::ranges::any_of(conveyor_belt_floors_, [&](const auto& s) { + return checkCollision(s, *p); + }); } // Comprueba las colisiones @@ -1056,13 +1025,9 @@ int Room::checkLeftSlopes(const LineVertical* line) { // Comprueba las colisiones bool Room::checkLeftSlopes(SDL_FPoint* p) { - for (const auto& slope : left_slopes_) { - if (checkCollision(*p, slope)) { - return true; - } - } - - return false; + return std::ranges::any_of(left_slopes_, [&](const auto& slope) { + return checkCollision(*p, slope); + }); } // Comprueba las colisiones @@ -1079,13 +1044,9 @@ int Room::checkRightSlopes(const LineVertical* line) { // Comprueba las colisiones bool Room::checkRightSlopes(SDL_FPoint* p) { - for (const auto& slope : right_slopes_) { - if (checkCollision(*p, slope)) { - return true; - } - } - - return false; + return std::ranges::any_of(right_slopes_, [&](const auto& slope) { + return checkCollision(*p, slope); + }); } // Abre la Jail si se da el caso diff --git a/source/game/gameplay/room.hpp b/source/game/gameplay/room.hpp index 2c45689..a12263b 100644 --- a/source/game/gameplay/room.hpp +++ b/source/game/gameplay/room.hpp @@ -115,6 +115,18 @@ class Room { // Pinta el mapa de la habitación en la textura void fillMapTexture(); + // Helper para recopilar tiles inferiores + std::vector collectBottomTiles(); + + // Helper para recopilar tiles superiores + std::vector collectTopTiles(); + + // Helper para recopilar tiles animados (para superficies automaticas) + std::vector collectAnimatedTiles(); + + // Helper para construir lineas horizontales a partir de tiles consecutivos + static void buildHorizontalLines(const std::vector& tiles, std::vector& lines, bool is_bottom_surface); + // Calcula las superficies inferiores void setBottomSurfaces(); diff --git a/source/game/gameplay/room_tracker.cpp b/source/game/gameplay/room_tracker.cpp index e5051bc..784278f 100644 --- a/source/game/gameplay/room_tracker.cpp +++ b/source/game/gameplay/room_tracker.cpp @@ -1,14 +1,10 @@ #include "game/gameplay/room_tracker.hpp" +#include // Para std::ranges::any_of + // Comprueba si la habitación ya ha sido visitada bool RoomTracker::hasBeenVisited(const std::string& name) { - for (const auto& l : list_) { - if (l == name) { - return true; - } - } - - return false; + return std::ranges::any_of(list_, [&name](const auto& l) { return l == name; }); } // Añade la habitación a la lista diff --git a/source/game/options.cpp b/source/game/options.cpp index 9e8ec5e..bd31bc4 100644 --- a/source/game/options.cpp +++ b/source/game/options.cpp @@ -15,8 +15,12 @@ #include "utils/utils.hpp" // Para stringToBool, boolToString, safeStoi namespace Options { -// Declaración de función interna +// Declaración de funciones internas bool setOptions(const std::string& var, const std::string& value); +std::string trimLine(const std::string& line); +bool isCommentOrEmpty(const std::string& line); +bool processConfigLine(const std::string& line); +bool readConfigFile(const std::string& file_path); // Crea e inicializa las opciones del programa void init() { @@ -27,62 +31,82 @@ void init() { #endif } +// Elimina espacios en blanco al inicio y final de una línea +std::string trimLine(const std::string& line) { + auto start = std::find_if(line.begin(), line.end(), [](int ch) { return !std::isspace(ch); }); + auto end = std::find_if(line.rbegin(), line.rend(), [](int ch) { return !std::isspace(ch); }).base(); + return std::string(start, end); +} + +// Verifica si una línea es comentario o está vacía +bool isCommentOrEmpty(const std::string& line) { + return line.empty() || line[0] == '#'; +} + +// Procesa una línea de configuración individual +bool processConfigLine(const std::string& line) { + std::istringstream iss(line); + std::string key; + std::string value; + + if (iss >> key >> value) { + if (!setOptions(key, value)) { + if (console) { + std::cout << "Warning: file config.txt\n"; + std::cout << "unknown parameter " << key << std::endl; + } + return false; + } + } + return true; +} + +// Lee y procesa el fichero de configuración +bool readConfigFile(const std::string& file_path) { + std::ifstream file(file_path); + if (!file.good()) { + return false; + } + + bool success = true; + if (console) { + std::cout << "Reading file config.txt\n"; + } + + std::string line; + while (std::getline(file, line)) { + line = trimLine(line); + + if (isCommentOrEmpty(line)) { + continue; + } + + if (!processConfigLine(line)) { + success = false; + } + } + + if (console) { + std::cout << "Closing file config.txt\n\n"; + } + file.close(); + + return success; +} + // Carga las opciones desde un fichero bool loadFromFile(const std::string& file_path) { - // Indicador de éxito en la carga - bool success = true; - // Versión actual del fichero const std::string CONFIG_VERSION = version; version = ""; - // Variables para manejar el fichero - std::ifstream file(file_path); + // Intenta leer el fichero + bool success = readConfigFile(file_path); - // Si el fichero se puede abrir - if (file.good()) { - // Procesa el fichero línea a línea - if (console) { - std::cout << "Reading file config.txt\n"; - } - std::string line; - while (std::getline(file, line)) { - // Elimina espacios en blanco iniciales y finales - line = std::string(std::find_if(line.begin(), line.end(), [](int ch) { return !std::isspace(ch); }), - line.end()); - line.erase(std::find_if(line.rbegin(), line.rend(), [](int ch) { return !std::isspace(ch); }) - .base(), - line.end()); - - // Ignora líneas vacías o comentarios - if (line.empty() || line[0] == '#') { - continue; - } - - // Usa un stringstream para dividir la línea en dos partes - std::istringstream iss(line); - std::string key; - std::string value; - - if (iss >> key >> value) { - if (!setOptions(key, value)) { - if (console) { - std::cout << "Warning: file config.txt\n"; - std::cout << "unknown parameter " << key << std::endl; - } - success = false; - } - } - } - - // Cierra el fichero - if (console) { - std::cout << "Closing file config.txt\n\n"; - } - file.close(); - } else { - // Crea el fichero con los valores por defecto + // Si no se pudo leer, crea el fichero con valores por defecto + if (!success) { saveToFile(file_path); + success = true; } // Si la versión de fichero no coincide, crea un fichero nuevo con los valores por defecto diff --git a/source/game/scenes/game.hpp b/source/game/scenes/game.hpp index c106b6c..1fc1b57 100644 --- a/source/game/scenes/game.hpp +++ b/source/game/scenes/game.hpp @@ -89,7 +89,7 @@ class Game { void renderRoomName(); // Cambia de habitación - bool changeRoom(const std::string& file); + bool changeRoom(const std::string& room_path); // Comprueba el teclado void checkInput(); diff --git a/source/game/scenes/loading_screen.cpp b/source/game/scenes/loading_screen.cpp index 45fac6b..953d197 100644 --- a/source/game/scenes/loading_screen.cpp +++ b/source/game/scenes/loading_screen.cpp @@ -351,7 +351,7 @@ void LoadingScreen::update() { } // Singletones - Audio::get()->update(); // Actualiza el objeto Audio + Audio::update(); // Actualiza el objeto Audio Screen::get()->update(); // Actualiza el objeto Screen } diff --git a/source/utils/utils.cpp b/source/utils/utils.cpp index 6604bf2..e6495b4 100644 --- a/source/utils/utils.cpp +++ b/source/utils/utils.cpp @@ -29,13 +29,7 @@ bool checkCollision(const Circle& a, const Circle& b) { total_radius_squared = total_radius_squared * total_radius_squared; // 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) < (total_radius_squared)) { - // Los circulos han colisionado - return true; - } - - // En caso contrario - return false; + return distanceSquared(a.x, a.y, b.x, b.y) < total_radius_squared; } // Detector de colisiones entre un circulo y un rectangulo diff --git a/source/utils/utils.hpp b/source/utils/utils.hpp index 8e456d3..4f0c40c 100644 --- a/source/utils/utils.hpp +++ b/source/utils/utils.hpp @@ -86,7 +86,7 @@ double distanceSquared(int x1, int y1, int x2, int y2); bool checkCollision(const Circle& a, const Circle& b); // Detector de colisiones entre un circulo y un rectangulo -bool checkCollision(const Circle& a, const SDL_FRect& b); +bool checkCollision(const Circle& a, const SDL_FRect& rect); // Detector de colisiones entre un dos rectangulos bool checkCollision(const SDL_FRect& a, const SDL_FRect& b); diff --git a/tools/linter/run_clang-tidy.sh b/tools/linter/run_clang-tidy.sh index 5145bb2..7a47ead 100755 --- a/tools/linter/run_clang-tidy.sh +++ b/tools/linter/run_clang-tidy.sh @@ -1,8 +1,56 @@ #!/bin/bash # Script para ejecutar clang-tidy recursivamente en todo el código fuente -# Uso: ./run_clang-tidy.sh [--fix] +# Uso: +# ./run_clang-tidy.sh [--fix] [--help] [archivos...] # --fix: Aplica las correcciones automáticamente (opcional) +# --help: Muestra la ayuda +# archivos: Archivos específicos a analizar (opcional, si no se especifica analiza todos) + +# Función de ayuda +show_help() { + cat << EOF +Uso: ./run_clang-tidy.sh [OPCIONES] [ARCHIVOS...] + +Script para ejecutar clang-tidy en el código fuente del proyecto. + +OPCIONES: + --fix Aplica las correcciones automáticamente + --help Muestra este mensaje de ayuda + +ARCHIVOS: + Si no se especifican archivos, se analizan todos los archivos del proyecto + (excluyendo la carpeta external/). Si se especifican archivos, solo se + analizan esos archivos específicos. + +EJEMPLOS: + # Analizar todo el proyecto + ./run_clang-tidy.sh + + # Analizar todo el proyecto y aplicar correcciones + ./run_clang-tidy.sh --fix + + # Analizar un archivo específico + ./run_clang-tidy.sh source/game/entities/enemy.cpp + + # Analizar múltiples archivos + ./run_clang-tidy.sh source/game/entities/enemy.cpp source/game/entities/player.cpp + + # Analizar y corregir un archivo específico + ./run_clang-tidy.sh --fix source/game/entities/enemy.cpp + + # Analizar todos los archivos de una carpeta (usando glob) + ./run_clang-tidy.sh source/game/entities/*.cpp + +NOTAS: + - Se requiere que el directorio build/ exista y contenga compile_commands.json + - Los archivos se procesan en paralelo (4 procesos) + - Solo se procesan archivos .cpp, .hpp y .h + - Las rutas pueden ser relativas o absolutas + +EOF + exit 0 +} # Auto-detectar la ubicación del script y calcular rutas del proyecto SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) @@ -10,10 +58,22 @@ PROJECT_DIR=$(cd "$SCRIPT_DIR/../.." && pwd) SOURCE_DIR="$PROJECT_DIR/source" BUILD_DIR="$PROJECT_DIR/build" -# Detectar si se pasó el parámetro --fix +# Parsear argumentos: separar --fix, --help de archivos FIX_FLAG="" -if [[ "$1" == "--fix" ]]; then - FIX_FLAG="--fix" +FILES=() + +for arg in "$@"; do + if [[ "$arg" == "--fix" ]]; then + FIX_FLAG="--fix" + elif [[ "$arg" == "--help" || "$arg" == "-h" ]]; then + show_help + else + FILES+=("$arg") + fi +done + +# Mostrar modo +if [[ -n "$FIX_FLAG" ]]; then echo "Modo: Aplicando correcciones automáticamente (--fix)" else echo "Modo: Solo análisis (sin --fix)" @@ -37,10 +97,51 @@ echo "Fuentes: $SOURCE_DIR" echo "Build: $BUILD_DIR" echo -# Procesar todos los archivos C++ recursivamente desde source/ -echo "=== Escaneando recursivamente source/ ===" -find "$SOURCE_DIR" \( -name '*.cpp' -o -name '*.h' -o -name '*.hpp' \) -print0 | \ -xargs -0 -P4 -I{} bash -c 'echo "Procesando: {}"; clang-tidy {} -p '"$BUILD_DIR"' '"$FIX_FLAG" +# Si se especificaron archivos, procesarlos directamente +if [[ ${#FILES[@]} -gt 0 ]]; then + echo "=== Procesando ${#FILES[@]} archivo(s) específico(s) ===" -echo -echo "¡Proceso completado para todos los archivos!" + # Validar y procesar cada archivo + VALID_FILES=() + for file in "${FILES[@]}"; do + # Convertir a ruta absoluta si es relativa + if [[ ! "$file" = /* ]]; then + file="$PROJECT_DIR/$file" + fi + + # Verificar que existe + if [[ ! -f "$file" ]]; then + echo "Advertencia: Archivo no encontrado: $file (omitiendo)" + continue + fi + + # Verificar extensión + if [[ "$file" =~ \.(cpp|hpp|h)$ ]]; then + VALID_FILES+=("$file") + else + echo "Advertencia: Archivo no es C++: $file (omitiendo)" + fi + done + + # Si no hay archivos válidos, salir + if [[ ${#VALID_FILES[@]} -eq 0 ]]; then + echo "Error: No hay archivos válidos para procesar" + exit 1 + fi + + echo + # Procesar archivos en paralelo + printf '%s\n' "${VALID_FILES[@]}" | \ + xargs -P4 -I{} bash -c 'echo "Procesando: {}"; clang-tidy {} -p '"$BUILD_DIR"' '"$FIX_FLAG" + + echo + echo "¡Proceso completado para ${#VALID_FILES[@]} archivo(s)!" +else + # Comportamiento original: procesar todos los archivos + echo "=== Escaneando recursivamente source/ (excluyendo external/) ===" + find "$SOURCE_DIR" \( -name '*.cpp' -o -name '*.h' -o -name '*.hpp' \) -not -path "*/external/*" -print0 | \ + xargs -0 -P4 -I{} bash -c 'echo "Procesando: {}"; clang-tidy {} -p '"$BUILD_DIR"' '"$FIX_FLAG" + + echo + echo "¡Proceso completado para todos los archivos!" +fi