diff --git a/.gitignore b/.gitignore index 60d91cb..f61a98c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,92 @@ +# IDEs and Editors .vscode/* +.idea/ +*.swp +*.swo +*~ + +# Build directories +build/ +bin/ +out/ +cmake-build-*/ + +# Executables +orni +asteroids +*.exe +*.out +*.app + +# Compiled Object files +*.o +*.obj +*.ko +*.elf + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.so.* +*.dylib +*.dll + +# Compiled Static libraries +*.a +*.lib +*.la +*.lo + +# CMake +CMakeCache.txt +CMakeFiles/ +CMakeScripts/ +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps/ + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb +*.ilk + +# Core dumps +core +core.* +*.core + +# macOS .DS_Store -thumbs.db -bin/* +.AppleDouble +.LSOverride +._* + +# Windows +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db +*.stackdump +[Dd]esktop.ini + +# Linux +*~ +.directory +.fuse_hidden* +.Trash-* +.nfs* + +# Temporary files +*.tmp +*.temp +*.log +*.bak +*.swp +*.swo diff --git a/CLAUDE.md b/CLAUDE.md index a1ea342..093d510 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,11 +4,11 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Project Overview -This is an **Asteroids-style game** originally written in **Turbo Pascal 7 for DOS** (1999), now being **migrated to modern C++20 with SDL3**. The game features a spaceship that must avoid and destroy enemies (pentagonal "ORNIs"). This is a **phased migration** preserving the original game feel. +This is **Orni Attack**, an **Asteroids-style game** originally written in **Turbo Pascal 7 for DOS** (1999), now being **migrated to modern C++20 with SDL3**. The game features a spaceship that must avoid and destroy enemies (pentagonal "ORNIs"). This is a **phased migration** preserving the original game feel. **Language**: All code, comments, and variable names are in **Catalan/Valencian** (preserved from original). -**Current Status**: **BETA 2.2** - Phases 0-9 completed (playable with ship, enemies, and bullets). +**Current Status**: **BETA 3.0** - Modernized architecture with dynamic windows, modular code organization, viewport scaling, and auto-generated project metadata. ## Build System @@ -21,7 +21,7 @@ Based on `/home/sergio/gitea/pollo` project structure. make clean && make # Run -./asteroids +./orni # Individual targets make linux # Linux build @@ -31,22 +31,68 @@ make windows # Windows build (MinGW) ### Build Files -- **CMakeLists.txt** - CMake configuration (C++20, SDL3) +- **CMakeLists.txt** - CMake configuration (C++20, SDL3, project metadata) - **Makefile** - Cross-platform wrapper, extracts project info from CMakeLists.txt +- **source/project.h.in** - Template for auto-generated project.h +- **build/project.h** - Auto-generated (by CMake) with project constants - **release/** - Platform-specific resources (icons, .rc, .plist) +### Project Metadata System + +**Auto-generation with CMake**: + +CMake generates `build/project.h` from `source/project.h.in` template on every compilation: + +```cpp +// build/project.h (generated automatically) +namespace Project { + constexpr const char* NAME = "orni"; // From project(orni ...) + constexpr const char* LONG_NAME = "Orni Attack"; // From PROJECT_LONG_NAME + constexpr const char* VERSION = "0.1.0"; // From VERSION + constexpr const char* COPYRIGHT = "© 1999..."; // From PROJECT_COPYRIGHT + constexpr const char* GIT_HASH = "abc1234"; // From git rev-parse +} +``` + +**Window title format** (dynamic, in sdl_manager.cpp): +```cpp +std::format("{} v{} ({})", + Project::LONG_NAME, // "Orni Attack" + Project::VERSION, // "0.1.0" + Project::COPYRIGHT) // "© 1999 Visente i Sergi, 2025 Port" +``` + +Result: `Orni Attack v0.1.0 (© 1999 Visente i Sergi, 2025 Port)` + +**Single source of truth**: All project info in CMakeLists.txt, no hardcoded strings. + ## Architecture -### File Structure +### File Structure (BETA 3.0) ``` source/ -├── main.cpp # Entry point, game loop, delta_time calculation -├── sdl_manager.hpp/cpp # SDL3 initialization, window, renderer -├── joc_asteroides.hpp # Game structures, constants -└── joc_asteroides.cpp # Game logic, physics, rendering +├── core/ - Reusable engine layer +│ ├── defaults.hpp - Configuration constants (SINGLE SOURCE OF TRUTH) +│ ├── types.hpp - Data structures (IPunt, Punt, Triangle, Poligon) +│ └── rendering/ +│ ├── sdl_manager.hpp/cpp - SDL3 window management + viewport scaling +│ └── primitives.hpp/cpp - Pure geometric functions +├── game/ - Asteroids-specific game logic +│ ├── constants.hpp - Legacy constant aliases +│ └── joc_asteroides.hpp/cpp - Game loop, physics, rendering +├── utils/ - Shared utilities (empty for now) +├── main.cpp - Entry point, F1/F2/F3 window controls +└── legacy/ + └── asteroids.cpp - Original Pascal code (reference only) ``` +**Key architectural decisions:** +- **core/** contains reusable, game-agnostic code +- **game/** contains Asteroids-specific logic +- All constants centralized in `core/defaults.hpp` +- Backward compatibility via `game/constants.hpp` aliases + ### Core Data Structures The game uses **polar coordinates** for all geometric objects (preserved from Pascal original): @@ -571,6 +617,153 @@ if (time_accumulator >= 1.0f) { - Cross-platform testing - Final physics tuning +## IMPORTANT: Modernization Architecture (BETA 3.0) + +Starting from BETA 3.0, the project has evolved into a professional modular architecture: + +### Structural Changes + +**Before (BETA 2.2):** +``` +source/ +├── main.cpp +├── sdl_manager.hpp/cpp +├── joc_asteroides.hpp/cpp +└── asteroids.cpp (Pascal) +``` + +**Now (BETA 3.0):** +``` +source/ +├── core/ - Reusable engine +│ ├── defaults.hpp - SINGLE SOURCE OF TRUTH for constants +│ ├── types.hpp - Data structures +│ └── rendering/ +│ ├── sdl_manager - Dynamic windows + viewport +│ └── primitives - Pure geometric functions +├── game/ - Asteroids-specific logic +│ ├── constants.hpp - Aliases for backward compatibility +│ └── joc_asteroides - Game loop +├── utils/ - Shared utilities +├── main.cpp - Entry point +└── legacy/ + └── asteroids.cpp - Original Pascal code (reference) +``` + +### Dynamic Window System + +**Controls:** +- **F1**: Decrease window (-100px width/height) +- **F2**: Increase window (+100px width/height) +- **F3**: Toggle fullscreen +- **ESC**: Exit (unchanged) + +**Behavior:** +- Window starts at 640x480 centered on screen +- Each resize keeps window centered on itself +- Minimum size: 320x240 +- Maximum size: Calculated from display resolution (limit -100px) + +**Viewport Scaling (SDL3):** +- Game ALWAYS renders in logical coordinates 640x480 +- SDL3 automatically scales to any physical window size +- Aspect ratio preserved (4:3 with letterboxing) +- Vectors look SHARPER in larger windows (higher resolution) +- Game physics UNCHANGED (still px/s relative to 640x480 logical) + +**Implementation:** +```cpp +SDL_SetRenderLogicalPresentation( + renderer_, + 640, 480, // Fixed logical size + SDL_LOGICAL_PRESENTATION_LETTERBOX // Maintain aspect ratio +); +``` + +### Configuration System + +**core/defaults.hpp** - Only place to change constants: + +```cpp +namespace Defaults { + namespace Window { + constexpr int WIDTH = 640; + constexpr int HEIGHT = 480; + constexpr int SIZE_INCREMENT = 100; // F1/F2 + } + + namespace Game { + constexpr int WIDTH = 640; // Logical + constexpr int HEIGHT = 480; + constexpr int MARGIN_LEFT = 20; // MARGE_ESQ + constexpr int MARGIN_RIGHT = 620; // MARGE_DRET + // ... + } + + namespace Physics { + constexpr float ROTATION_SPEED = 2.5f; + constexpr float ACCELERATION = 100.0f; + // ... + } +} +``` + +**game/constants.hpp** - Backward compatibility: +```cpp +using Defaults::Game::MARGIN_LEFT; // For legacy code using MARGE_ESQ +// ... +``` + +### Important Reminders + +**1. Logical vs Physical Coordinates** +- ALL game code uses logical coordinates (640x480) +- NO need to adjust physics or collision calculations +- SDL3 handles conversion automatically + +**2. Rendering** +- `linea()`, `rota_tri()`, `rota_pol()` still use direct coords +- NO manual transformation, SDL does it internally + +**3. Configuration** +- NEVER use magic numbers in new code +- Always reference `Defaults::*` +- For game values, create aliases in `game/constants.hpp` if needed + +**4. Future OpenGL** +- Current system allows migrating to OpenGL without changing game code +- Would only require changing SDLManager and rendering function implementations +- Postponed until needing >50 enemies or complex effects + +### Compilation + +**No changes:** +```bash +make clean && make +./asteroids +``` + +**Files modified by CMake:** +- Updated to include subdirectories core/, game/ +- Include path: `${CMAKE_SOURCE_DIR}/source` (for relative includes) + +### Migration for Future Sessions + +If you find code using magic numbers: +1. Add constant in `core/defaults.hpp` in the appropriate namespace +2. If it's a frequently used game value, create alias in `game/constants.hpp` +3. Replace the number with the constant +4. Compile and verify + +Example: +```cpp +// Before (bad): +if (enemy.centre.x < 20 || enemy.centre.x > 620) { ... } + +// After (good): +if (enemy.centre.x < MARGIN_LEFT || enemy.centre.x > MARGIN_RIGHT) { ... } +``` + ## Tips for Future Claude Code Sessions - **Always read this file first** before making changes @@ -582,3 +775,4 @@ if (time_accumulator >= 1.0f) { - **Angle convention**: angle=0 points UP (not right), hence `angle - PI/2` in calculations - **One bullet at a time**: Original game limitation, preserve it - **Simple code style**: Avoid over-engineering, keep "small DOS program" feel +- **Use defaults.hpp**: Never hardcode constants, always use Defaults namespace diff --git a/CMakeLists.txt b/CMakeLists.txt index fee747d..08369fd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,10 +1,10 @@ # CMakeLists.txt cmake_minimum_required(VERSION 3.10) -project(asteroids VERSION 0.1.0) +project(orni VERSION 0.1.0) # Info del proyecto -set(PROJECT_LONG_NAME "Asteroides") +set(PROJECT_LONG_NAME "Orni Attack") set(PROJECT_COPYRIGHT "© 1999 Visente i Sergi, 2025 Port") # Establecer estándar de C++ @@ -28,16 +28,15 @@ else() set(GIT_HASH "unknown") endif() -# Configurar archivo de versión (si existe project.h.in en el futuro) -if(EXISTS "${CMAKE_SOURCE_DIR}/source/project.h.in") - configure_file(${CMAKE_SOURCE_DIR}/source/project.h.in ${CMAKE_BINARY_DIR}/project.h @ONLY) -endif() +# Configurar archivo de versión +configure_file(${CMAKE_SOURCE_DIR}/source/project.h.in ${CMAKE_BINARY_DIR}/project.h @ONLY) # --- LISTA DE FUENTES --- set(APP_SOURCES source/main.cpp - source/sdl_manager.cpp - source/joc_asteroides.cpp + source/core/rendering/sdl_manager.cpp + source/game/joc_asteroides.cpp + source/core/rendering/primitives.cpp ) # Configuración de SDL3 diff --git a/Makefile b/Makefile index 3ea9f68..b0bfb2f 100644 --- a/Makefile +++ b/Makefile @@ -34,8 +34,9 @@ endif # ============================================================================== APP_SOURCES := \ source/main.cpp \ - source/sdl_manager.cpp \ - source/joc_asteroides.cpp + source/core/rendering/sdl_manager.cpp \ + source/game/joc_asteroides.cpp \ + source/core/rendering/primitives.cpp # ============================================================================== # INCLUDES @@ -57,7 +58,7 @@ ifeq ($(OS),Windows_NT) CXXFLAGS_DEBUG := -std=$(CPP_STANDARD) -Wall -g -D_DEBUG -DWINDOWS_BUILD LDFLAGS := -lmingw32 -lSDL3 WINDRES := windres - RESOURCE_FILE := release/asteroids.res + RESOURCE_FILE := release/orni.res RM := del /Q RMDIR := rmdir /S /Q MKDIR := mkdir @@ -95,7 +96,7 @@ all: $(TARGET_FILE) $(TARGET_FILE): $(APP_SOURCES) ifeq ($(OS),Windows_NT) @if not exist build $(MKDIR) build - @if not exist release\\asteroids.res $(WINDRES) release\\asteroids.rc -O coff -o release\\asteroids.res + @if not exist release\\orni.res $(WINDRES) release\\orni.rc -O coff -o release\\orni.res $(CXX) $(CXXFLAGS) $(INCLUDES) $(APP_SOURCES) $(RESOURCE_FILE) $(LDFLAGS) -o $(TARGET_FILE).exe else @$(MKDIR) build diff --git a/asteroids b/asteroids index 679760a..9f0079b 100755 Binary files a/asteroids and b/asteroids differ diff --git a/release/Info.plist b/release/Info.plist index ca1f6f6..2b03629 100644 --- a/release/Info.plist +++ b/release/Info.plist @@ -5,19 +5,19 @@ CFBundleDevelopmentRegion en CFBundleDisplayName - Asteroides + Orni Attack CFBundleExecutable - asteroids + orni CFBundleIconFile icon CFBundleIconName icon CFBundleIdentifier - org.jailgames.asteroids + org.jailgames.orni CFBundleInfoDictionaryVersion 6.0 CFBundleName - Asteroides + Orni Attack CFBundlePackageType APPL CFBundleShortVersionString diff --git a/release/asteroids.rc b/release/orni.rc similarity index 100% rename from release/asteroids.rc rename to release/orni.rc diff --git a/source/core/rendering/sdl_manager.cpp b/source/core/rendering/sdl_manager.cpp new file mode 100644 index 0000000..0916c4f --- /dev/null +++ b/source/core/rendering/sdl_manager.cpp @@ -0,0 +1,212 @@ +// sdl_manager.cpp - Implementació del gestor SDL3 +// © 2025 Port a C++20 amb SDL3 + +#include "sdl_manager.hpp" +#include "core/defaults.hpp" +#include "project.h" +#include +#include +#include + +SDLManager::SDLManager() + : finestra_(nullptr) + , renderer_(nullptr) + , current_width_(Defaults::Window::WIDTH) + , current_height_(Defaults::Window::HEIGHT) + , is_fullscreen_(false) + , max_width_(1920) + , max_height_(1080) +{ + // Inicialitzar SDL3 + if (!SDL_Init(SDL_INIT_VIDEO)) { + std::cerr << "Error inicialitzant SDL3: " << SDL_GetError() << std::endl; + return; + } + + // Calcular mida màxima des del display + calculateMaxWindowSize(); + + // Construir títol dinàmic igual que en pollo + std::string window_title = std::format("{} v{} ({})", + Project::LONG_NAME, + Project::VERSION, + Project::COPYRIGHT + ); + + // Crear finestra CENTRADA (SDL ho fa automàticament amb CENTERED) + finestra_ = SDL_CreateWindow( + window_title.c_str(), + current_width_, + current_height_, + SDL_WINDOW_RESIZABLE // Permetre resize manual també + ); + + if (!finestra_) { + std::cerr << "Error creant finestra: " << SDL_GetError() << std::endl; + SDL_Quit(); + return; + } + + // IMPORTANT: Centrar explícitament la finestra + SDL_SetWindowPosition(finestra_, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); + + // Crear renderer amb acceleració + renderer_ = SDL_CreateRenderer(finestra_, nullptr); + + if (!renderer_) { + std::cerr << "Error creant renderer: " << SDL_GetError() << std::endl; + SDL_DestroyWindow(finestra_); + SDL_Quit(); + return; + } + + // CRÍTIC: Configurar viewport scaling + updateLogicalPresentation(); + + std::cout << "SDL3 inicialitzat: " << current_width_ << "x" << current_height_ + << " (logic: " << Defaults::Game::WIDTH << "x" << Defaults::Game::HEIGHT << ")" << std::endl; +} + +SDLManager::~SDLManager() { + if (renderer_) { + SDL_DestroyRenderer(renderer_); + renderer_ = nullptr; + } + + if (finestra_) { + SDL_DestroyWindow(finestra_); + finestra_ = nullptr; + } + + SDL_Quit(); + std::cout << "SDL3 netejat correctament" << std::endl; +} + +void SDLManager::calculateMaxWindowSize() { + SDL_DisplayID display = SDL_GetPrimaryDisplay(); + const SDL_DisplayMode* mode = SDL_GetCurrentDisplayMode(display); + + if (mode) { + // Deixar marge de 100px per a decoracions de l'OS + max_width_ = mode->w - 100; + max_height_ = mode->h - 100; + std::cout << "Display detectat: " << mode->w << "x" << mode->h + << " (max finestra: " << max_width_ << "x" << max_height_ << ")" << std::endl; + } else { + // Fallback conservador + max_width_ = 1920; + max_height_ = 1080; + std::cerr << "No s'ha pogut detectar el display, usant fallback: " + << max_width_ << "x" << max_height_ << std::endl; + } +} + +void SDLManager::updateLogicalPresentation() { + // AIXÒ ÉS LA MÀGIA: El joc SEMPRE dibuixa en 640x480, + // SDL escala automàticament a la mida física de la finestra + SDL_SetRenderLogicalPresentation( + renderer_, + Defaults::Game::WIDTH, // 640 (lògic) + Defaults::Game::HEIGHT, // 480 (lògic) + SDL_LOGICAL_PRESENTATION_LETTERBOX // Mantenir aspect ratio 4:3 + ); +} + +void SDLManager::increaseWindowSize() { + if (is_fullscreen_) return; // No operar en fullscreen + + int new_width = current_width_ + Defaults::Window::SIZE_INCREMENT; + int new_height = current_height_ + Defaults::Window::SIZE_INCREMENT; + + // Clamp a màxim + new_width = std::min(new_width, max_width_); + new_height = std::min(new_height, max_height_); + + if (new_width != current_width_ || new_height != current_height_) { + applyWindowSize(new_width, new_height); + std::cout << "F2: Finestra augmentada a " << new_width << "x" << new_height << std::endl; + } +} + +void SDLManager::decreaseWindowSize() { + if (is_fullscreen_) return; + + int new_width = current_width_ - Defaults::Window::SIZE_INCREMENT; + int new_height = current_height_ - Defaults::Window::SIZE_INCREMENT; + + // Clamp a mínim + new_width = std::max(new_width, Defaults::Window::MIN_WIDTH); + new_height = std::max(new_height, Defaults::Window::MIN_HEIGHT); + + if (new_width != current_width_ || new_height != current_height_) { + applyWindowSize(new_width, new_height); + std::cout << "F1: Finestra reduïda a " << new_width << "x" << new_height << std::endl; + } +} + +void SDLManager::applyWindowSize(int new_width, int new_height) { + // Obtenir posició actual ABANS del resize + int old_x, old_y; + SDL_GetWindowPosition(finestra_, &old_x, &old_y); + + int old_width = current_width_; + int old_height = current_height_; + + // Actualitzar mida + SDL_SetWindowSize(finestra_, new_width, new_height); + current_width_ = new_width; + current_height_ = new_height; + + // CENTRADO INTEL·LIGENT (algoritme de pollo) + // Calcular nova posició per mantenir la finestra centrada sobre si mateixa + int delta_width = old_width - new_width; + int delta_height = old_height - new_height; + + int new_x = old_x + (delta_width / 2); + int new_y = old_y + (delta_height / 2); + + // Evitar que la finestra surti de la pantalla + constexpr int TITLEBAR_HEIGHT = 35; // Alçada aproximada de la barra de títol + new_x = std::max(new_x, 0); + new_y = std::max(new_y, TITLEBAR_HEIGHT); + + SDL_SetWindowPosition(finestra_, new_x, new_y); + + // NO cal actualitzar el logical presentation aquí, + // SDL ho maneja automàticament +} + +void SDLManager::toggleFullscreen() { + is_fullscreen_ = !is_fullscreen_; + SDL_SetWindowFullscreen(finestra_, is_fullscreen_); + + std::cout << "F3: Fullscreen " << (is_fullscreen_ ? "activat" : "desactivat") << std::endl; + + // En fullscreen, SDL gestiona tot automàticament + // En sortir, restaura la mida anterior +} + +bool SDLManager::handleWindowEvent(const SDL_Event& event) { + if (event.type == SDL_EVENT_WINDOW_RESIZED) { + // Usuari ha redimensionat manualment (arrossegar vora) + // Actualitzar el nostre tracking + SDL_GetWindowSize(finestra_, ¤t_width_, ¤t_height_); + std::cout << "Finestra redimensionada manualment a " + << current_width_ << "x" << current_height_ << std::endl; + return true; + } + return false; +} + +void SDLManager::neteja(uint8_t r, uint8_t g, uint8_t b) { + if (!renderer_) return; + + SDL_SetRenderDrawColor(renderer_, r, g, b, 255); + SDL_RenderClear(renderer_); +} + +void SDLManager::presenta() { + if (!renderer_) return; + + SDL_RenderPresent(renderer_); +} diff --git a/source/core/rendering/sdl_manager.hpp b/source/core/rendering/sdl_manager.hpp new file mode 100644 index 0000000..fdbacd1 --- /dev/null +++ b/source/core/rendering/sdl_manager.hpp @@ -0,0 +1,49 @@ +// sdl_manager.hpp - Gestor d'inicialització de SDL3 +// © 2025 Port a C++20 amb SDL3 + +#ifndef SDL_MANAGER_HPP +#define SDL_MANAGER_HPP + +#include +#include + +class SDLManager { +public: + SDLManager(); + ~SDLManager(); + + // No permetre còpia ni assignació + SDLManager(const SDLManager&) = delete; + SDLManager& operator=(const SDLManager&) = delete; + + // [NUEVO] Gestió de finestra dinàmica + void increaseWindowSize(); // F2: +100px + void decreaseWindowSize(); // F1: -100px + void toggleFullscreen(); // F3 + bool handleWindowEvent(const SDL_Event& event); // Per a SDL_EVENT_WINDOW_RESIZED + + // Funcions principals (renderitzat) + void neteja(uint8_t r = 0, uint8_t g = 0, uint8_t b = 0); + void presenta(); + + // Getters + SDL_Renderer* obte_renderer() { return renderer_; } + +private: + SDL_Window* finestra_; + SDL_Renderer* renderer_; + + // [NUEVO] Estat de la finestra + int current_width_; // Mida física actual + int current_height_; + bool is_fullscreen_; + int max_width_; // Calculat des del display + int max_height_; + + // [NUEVO] Funcions internes + void calculateMaxWindowSize(); // Llegir resolució del display + void applyWindowSize(int width, int height); // Canviar mida + centrar + void updateLogicalPresentation(); // Actualitzar viewport +}; + +#endif // SDL_MANAGER_HPP diff --git a/source/game/constants.hpp b/source/game/constants.hpp new file mode 100644 index 0000000..1572a65 --- /dev/null +++ b/source/game/constants.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include "core/defaults.hpp" + +// Aliases per a backward compatibility amb codi existent +// Permet usar Constants::MARGE_ESQ en lloc de Defaults::Game::MARGIN_LEFT + +namespace Constants { + // Marges de l'àrea de joc + constexpr int MARGE_DALT = Defaults::Game::MARGIN_TOP; + constexpr int MARGE_BAIX = Defaults::Game::MARGIN_BOTTOM; + constexpr int MARGE_ESQ = Defaults::Game::MARGIN_LEFT; + constexpr int MARGE_DRET = Defaults::Game::MARGIN_RIGHT; + + // Límits de polígons i objectes + constexpr int MAX_IPUNTS = Defaults::Entities::MAX_IPUNTS; + constexpr int MAX_ORNIS = Defaults::Entities::MAX_ORNIS; + constexpr int MAX_BALES = Defaults::Entities::MAX_BALES; + + // Velocitats (valors legacy del codi Pascal) + constexpr int VELOCITAT = static_cast(Defaults::Physics::ENEMY_SPEED); + constexpr int VELOCITAT_MAX = static_cast(Defaults::Physics::BULLET_SPEED); + + // Matemàtiques + constexpr float PI = Defaults::Math::PI; +} diff --git a/source/joc_asteroides.cpp b/source/game/joc_asteroides.cpp similarity index 79% rename from source/joc_asteroides.cpp rename to source/game/joc_asteroides.cpp index edfd209..6eee04d 100644 --- a/source/joc_asteroides.cpp +++ b/source/game/joc_asteroides.cpp @@ -3,6 +3,7 @@ // © 2025 Port a C++20 amb SDL3 #include "joc_asteroides.hpp" +#include "core/rendering/primitives.hpp" #include #include #include @@ -122,18 +123,6 @@ void JocAsteroides::actualitzar(float delta_time) { } } - // DEBUG: Mostrar info de la nau (temporal) - static float time_accumulator = 0.0f; - time_accumulator += delta_time; - - if (time_accumulator >= 1.0f) { // Cada segon - std::cout << "Nau: pos(" << nau_.centre.x << "," << nau_.centre.y - << ") vel=" << static_cast(nau_.velocitat) << " px/s" - << " angle=" << static_cast(nau_.angle * 180.0f / Constants::PI) << "°" - << " dt=" << static_cast(delta_time * 1000.0f) << "ms" - << std::endl; - time_accumulator -= 1.0f; - } // Actualitzar moviment i rotació dels enemics (ORNIs) // Basat en el codi Pascal original: lines 429-432 @@ -224,60 +213,7 @@ void JocAsteroides::processar_input(const SDL_Event& event) { } } -// Funcions utilitàries - Geometria i matemàtiques -// Basades en el codi Pascal original - -float JocAsteroides::modul(const Punt& p) const { - // Càlcul de la magnitud d'un vector: sqrt(x² + y²) - return std::sqrt(static_cast(p.x * p.x + p.y * p.y)); -} - -void JocAsteroides::diferencia(const Punt& o, const Punt& d, Punt& p) const { - // Resta de vectors (origen - destí) - p.x = o.x - d.x; - p.y = o.y - d.y; -} - -int JocAsteroides::distancia(const Punt& o, const Punt& d) const { - // Distància entre dos punts - Punt p; - diferencia(o, d, p); - return static_cast(std::round(modul(p))); -} - -float JocAsteroides::angle_punt(const Punt& p) const { - // Càlcul de l'angle d'un punt (arctan) - if (p.y != 0) { - return std::atan(static_cast(p.x) / static_cast(p.y)); - } - return 0.0f; -} - -void JocAsteroides::crear_poligon_regular(Poligon& pol, uint8_t n, float r) { - // Crear un polígon regular amb n costats i radi r - // Distribueix els punts uniformement al voltant d'un cercle - - float interval = 2.0f * Constants::PI / n; - float act = 0.0f; - - for (uint8_t i = 0; i < n; i++) { - pol.ipuntx[i].r = r; - pol.ipuntx[i].angle = act; - act += interval; - } - - // Inicialitzar propietats del polígon - pol.centre.x = 320.0f; - pol.centre.y = 200.0f; - pol.angle = 0.0f; - // Convertir velocitat de px/frame a px/s: 2 px/frame × 20 FPS = 40 px/s - pol.velocitat = static_cast(Constants::VELOCITAT) * 20.0f; - pol.n = n; - // Convertir rotació de rad/frame a rad/s: 0.0785 rad/frame × 20 FPS = 1.57 rad/s (~90°/s) - pol.drotacio = 0.078539816f * 20.0f; - pol.rotacio = 0.0f; - pol.esta = true; -} +// Funcions de dibuix bool JocAsteroides::linea(int x1, int y1, int x2, int y2, bool dibuixar) { // Algorisme de Bresenham per dibuixar línies @@ -418,38 +354,46 @@ void JocAsteroides::rota_pol(const Poligon& pol, float angul, bool dibuixar) { void JocAsteroides::mou_orni(Poligon& orni, float delta_time) { // Moviment autònom d'ORNI (enemic pentàgon) - // Basat en el codi Pascal original: procedure mou_orni + // Basat EXACTAMENT en el codi Pascal original: ASTEROID.PAS lines 279-293 + // + // IMPORTANT: El Pascal original NO té canvi aleatori continu! + // Només ajusta l'angle quan toca una paret. - // Cambio aleatori d'angle (5% probabilitat per crida) - // En el Pascal original: if (random<0.05) then orni.angle:=random*2*pi - float random_val = static_cast(std::rand()) / static_cast(RAND_MAX); - if (random_val < 0.05f) { - // Assignar un angle completament aleatori (0-360°) - orni.angle = (static_cast(std::rand()) / static_cast(RAND_MAX)) - * 2.0f * Constants::PI; - } - - // Calcular nova posició (moviment polar time-based) - // velocitat ja està en px/s (40 px/s), només cal multiplicar per delta_time + // Calcular nova posició PROPUESTA (time-based, però lògica Pascal) + // velocitat ja està en px/s (40 px/s), multiplicar per delta_time float velocitat_efectiva = orni.velocitat * delta_time; // Calcular desplaçament (angle-PI/2 perquè angle=0 apunta amunt) float dy = velocitat_efectiva * std::sin(orni.angle - Constants::PI / 2.0f); float dx = velocitat_efectiva * std::cos(orni.angle - Constants::PI / 2.0f); - // Acumulació directa amb precisió subpíxel - orni.centre.y += dy; - orni.centre.x += dx; + float new_y = orni.centre.y + dy; + float new_x = orni.centre.x + dx; - // Boundary checking amb rebot (reflexió d'angle) - // Si toca paret esquerra/dreta: angle = PI - angle - if (orni.centre.x < Constants::MARGE_ESQ || orni.centre.x > Constants::MARGE_DRET) { - orni.angle = Constants::PI - orni.angle; + // Lògica Pascal: Actualitza Y si dins, sinó ajusta angle aleatòriament + // if (dy>marge_dalt) and (dy Constants::MARGE_DALT && new_y < Constants::MARGE_BAIX) { + orni.centre.y = new_y; + } else { + // Pequeño ajuste aleatorio: (random(256)/512)*(random(3)-1) + // random(256) = 0..255, /512 = 0..0.498 + // random(3) = 0,1,2, -1 = -1,0,1 + // Resultado: ±0.5 rad aprox + float rand1 = (static_cast(std::rand() % 256) / 512.0f); + int rand2 = (std::rand() % 3) - 1; // -1, 0, o 1 + orni.angle += rand1 * static_cast(rand2); } - // Si toca paret dalt/baix: angle = 2*PI - angle - if (orni.centre.y < Constants::MARGE_DALT || orni.centre.y > Constants::MARGE_BAIX) { - orni.angle = 2.0f * Constants::PI - orni.angle; + // Lògica Pascal: Actualitza X si dins, sinó ajusta angle aleatòriament + // if (dx>marge_esq) and (dx Constants::MARGE_ESQ && new_x < Constants::MARGE_DRET) { + orni.centre.x = new_x; + } else { + float rand1 = (static_cast(std::rand() % 256) / 512.0f); + int rand2 = (std::rand() % 3) - 1; + orni.angle += rand1 * static_cast(rand2); } // Nota: La rotació visual (orni.rotacio += orni.drotacio) ja es fa a actualitzar() diff --git a/source/game/joc_asteroides.hpp b/source/game/joc_asteroides.hpp new file mode 100644 index 0000000..f0a380d --- /dev/null +++ b/source/game/joc_asteroides.hpp @@ -0,0 +1,46 @@ +// joc_asteroides.hpp - Lògica principal del joc +// © 1999 Visente i Sergi (versió Pascal) +// © 2025 Port a C++20 amb SDL3 + +#ifndef JOC_ASTEROIDES_HPP +#define JOC_ASTEROIDES_HPP + +#include +#include +#include +#include "core/types.hpp" +#include "game/constants.hpp" + +// Classe principal del joc +class JocAsteroides { +public: + JocAsteroides(SDL_Renderer* renderer); + ~JocAsteroides() = default; + + void inicialitzar(); + void actualitzar(float delta_time); + void dibuixar(); + void processar_input(const SDL_Event& event); + +private: + SDL_Renderer* renderer_; + + // Estat del joc + Triangle nau_; + std::array orni_; + std::array bales_; + Poligon chatarra_cosmica_; + uint16_t itocado_; + + // Funcions de dibuix + bool linea(int x1, int y1, int x2, int y2, bool dibuixar); + void rota_tri(const Triangle& tri, float angul, float velocitat, bool dibuixar); + void rota_pol(const Poligon& pol, float angul, bool dibuixar); + + // Moviment + void mou_orni(Poligon& orni, float delta_time); + void mou_bales(Poligon& bala, float delta_time); + void tocado(); +}; + +#endif // JOC_ASTEROIDES_HPP diff --git a/source/joc_asteroides.hpp b/source/joc_asteroides.hpp deleted file mode 100644 index aae8ddc..0000000 --- a/source/joc_asteroides.hpp +++ /dev/null @@ -1,94 +0,0 @@ -// joc_asteroides.hpp - Lògica principal del joc -// © 1999 Visente i Sergi (versió Pascal) -// © 2025 Port a C++20 amb SDL3 - -#ifndef JOC_ASTEROIDES_HPP -#define JOC_ASTEROIDES_HPP - -#include -#include -#include - -// Espai de noms per a les constants del joc -namespace Constants { - constexpr int MARGE_DALT = 20; - constexpr int MARGE_BAIX = 460; - constexpr int MARGE_ESQ = 20; - constexpr int MARGE_DRET = 620; - constexpr int MAX_IPUNTS = 30; - constexpr int MAX_ORNIS = 15; - constexpr int VELOCITAT = 2; - constexpr int VELOCITAT_MAX = 6; - constexpr int MAX_BALES = 3; - constexpr float PI = 3.14159265359f; -} - -// Estructures de dades (coordenades polars i cartesianes) -struct IPunt { - float r; - float angle; -}; - -struct Punt { - float x; - float y; -}; - -struct Triangle { - IPunt p1, p2, p3; - Punt centre; - float angle; - float velocitat; -}; - -struct Poligon { - std::array ipuntx; - Punt centre; - float angle; - float velocitat; - uint8_t n; - float drotacio; - float rotacio; - bool esta; -}; - -// Classe principal del joc -class JocAsteroides { -public: - JocAsteroides(SDL_Renderer* renderer); - ~JocAsteroides() = default; - - void inicialitzar(); - void actualitzar(float delta_time); - void dibuixar(); - void processar_input(const SDL_Event& event); - -private: - SDL_Renderer* renderer_; - - // Estat del joc - Triangle nau_; - std::array orni_; - std::array bales_; - Poligon chatarra_cosmica_; - uint16_t itocado_; - - // Funcions utilitàries (geometria) - void crear_poligon_regular(Poligon& pol, uint8_t n, float r); - float modul(const Punt& p) const; - void diferencia(const Punt& o, const Punt& d, Punt& p) const; - int distancia(const Punt& o, const Punt& d) const; - float angle_punt(const Punt& p) const; - - // Funcions de dibuix - bool linea(int x1, int y1, int x2, int y2, bool dibuixar); - void rota_tri(const Triangle& tri, float angul, float velocitat, bool dibuixar); - void rota_pol(const Poligon& pol, float angul, bool dibuixar); - - // Moviment - void mou_orni(Poligon& orni, float delta_time); - void mou_bales(Poligon& bala, float delta_time); - void tocado(); -}; - -#endif // JOC_ASTEROIDES_HPP diff --git a/source/asteroids.cpp b/source/legacy/asteroids.cpp similarity index 100% rename from source/asteroids.cpp rename to source/legacy/asteroids.cpp diff --git a/source/main.cpp b/source/main.cpp index b49ecbc..036875e 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -2,12 +2,12 @@ // © 1999 Visente i Sergi (versió Pascal) // © 2025 Port a C++20 amb SDL3 -#include "sdl_manager.hpp" -#include "joc_asteroides.hpp" +#include "core/rendering/sdl_manager.hpp" +#include "game/joc_asteroides.hpp" int main(int argc, char* argv[]) { - // Crear gestor SDL amb finestra de 640x480 - SDLManager sdl(640, 480, "Asteroides - BETA 2.2"); + // Crear gestor SDL (finestra centrada 640x480 per defecte) + SDLManager sdl; // Crear instància del joc JocAsteroides joc(sdl.obte_renderer()); @@ -33,7 +33,27 @@ int main(int argc, char* argv[]) { // Processar events SDL while (SDL_PollEvent(&event)) { - // Processar input del joc + // [NUEVO] Manejo de ventana ANTES de lógica del juego + if (sdl.handleWindowEvent(event)) { + continue; // Evento procesado, siguiente + } + + // [NUEVO] Teclas globales de ventana + if (event.type == SDL_EVENT_KEY_DOWN) { + switch (event.key.key) { + case SDLK_F1: + sdl.decreaseWindowSize(); + continue; + case SDLK_F2: + sdl.increaseWindowSize(); + continue; + case SDLK_F3: + sdl.toggleFullscreen(); + continue; + } + } + + // Procesamiento normal del juego joc.processar_input(event); // Detectar tancament de finestra o ESC diff --git a/source/project.h.in b/source/project.h.in new file mode 100644 index 0000000..34941db --- /dev/null +++ b/source/project.h.in @@ -0,0 +1,9 @@ +#pragma once + +namespace Project { +constexpr const char* NAME = "@PROJECT_NAME@"; +constexpr const char* LONG_NAME = "@PROJECT_LONG_NAME@"; +constexpr const char* VERSION = "@PROJECT_VERSION@"; +constexpr const char* COPYRIGHT = "@PROJECT_COPYRIGHT@"; +constexpr const char* GIT_HASH = "@GIT_HASH@"; +} // namespace Project diff --git a/source/sdl_manager.cpp b/source/sdl_manager.cpp deleted file mode 100644 index a99e9f0..0000000 --- a/source/sdl_manager.cpp +++ /dev/null @@ -1,91 +0,0 @@ -// sdl_manager.cpp - Implementació del gestor SDL3 -// © 2025 Port a C++20 amb SDL3 - -#include "sdl_manager.hpp" -#include - -SDLManager::SDLManager(int amplada, int alcada, const char* titol) - : finestra_(nullptr), renderer_(nullptr), inicialitzat_(false) { - - // Inicialitzar SDL3 - if (!SDL_Init(SDL_INIT_VIDEO)) { - std::cerr << "Error inicialitzant SDL3: " << SDL_GetError() << std::endl; - return; - } - - // Crear finestra - finestra_ = SDL_CreateWindow( - titol, - amplada, alcada, - SDL_WINDOW_RESIZABLE - ); - - if (!finestra_) { - std::cerr << "Error creant finestra: " << SDL_GetError() << std::endl; - SDL_Quit(); - return; - } - - // Crear renderer amb acceleració i vsync - // En SDL3, els flags són diferents o s'especifiquen diferent - renderer_ = SDL_CreateRenderer(finestra_, nullptr); - - if (!renderer_) { - std::cerr << "Error creant renderer: " << SDL_GetError() << std::endl; - SDL_DestroyWindow(finestra_); - SDL_Quit(); - return; - } - - inicialitzat_ = true; - std::cout << "SDL3 inicialitzat correctament" << std::endl; -} - -SDLManager::~SDLManager() { - if (renderer_) { - SDL_DestroyRenderer(renderer_); - renderer_ = nullptr; - } - - if (finestra_) { - SDL_DestroyWindow(finestra_); - finestra_ = nullptr; - } - - SDL_Quit(); - std::cout << "SDL3 netejat correctament" << std::endl; -} - -void SDLManager::neteja(uint8_t r, uint8_t g, uint8_t b) { - if (!renderer_) return; - - SDL_SetRenderDrawColor(renderer_, r, g, b, 255); - SDL_RenderClear(renderer_); -} - -void SDLManager::presenta() { - if (!renderer_) return; - - SDL_RenderPresent(renderer_); -} - -bool SDLManager::processar_events() { - if (!inicialitzat_) return false; - - SDL_Event event; - while (SDL_PollEvent(&event)) { - switch (event.type) { - case SDL_EVENT_QUIT: - return false; - - case SDL_EVENT_KEY_DOWN: - // En SDL3, la tecla està en event.key.key en lloc de event.key.keysym.sym - if (event.key.key == SDLK_ESCAPE) { - return false; - } - break; - } - } - - return true; -} diff --git a/source/sdl_manager.hpp b/source/sdl_manager.hpp deleted file mode 100644 index 5b13a4d..0000000 --- a/source/sdl_manager.hpp +++ /dev/null @@ -1,33 +0,0 @@ -// sdl_manager.hpp - Gestor d'inicialització de SDL3 -// © 2025 Port a C++20 amb SDL3 - -#ifndef SDL_MANAGER_HPP -#define SDL_MANAGER_HPP - -#include -#include - -class SDLManager { -public: - SDLManager(int amplada, int alcada, const char* titol); - ~SDLManager(); - - // No permetre còpia ni assignació - SDLManager(const SDLManager&) = delete; - SDLManager& operator=(const SDLManager&) = delete; - - // Funcions principals - void neteja(uint8_t r = 0, uint8_t g = 0, uint8_t b = 0); - void presenta(); - bool processar_events(); - - // Getters - SDL_Renderer* obte_renderer() { return renderer_; } - -private: - SDL_Window* finestra_; - SDL_Renderer* renderer_; - bool inicialitzat_; -}; - -#endif // SDL_MANAGER_HPP