From 2b1311042f1c9cad31a5cbbae3bfea4efd6a0b90 Mon Sep 17 00:00:00 2001 From: Sergio Date: Thu, 27 Nov 2025 21:47:08 +0100 Subject: [PATCH] creat project.h --- .gitignore | 92 ++++++++++- CLAUDE.md | 212 ++++++++++++++++++++++++-- CMakeLists.txt | 15 +- Makefile | 9 +- asteroids | Bin 28112 -> 33200 bytes release/Info.plist | 8 +- release/{asteroids.rc => orni.rc} | 0 source/core/rendering/sdl_manager.cpp | 212 ++++++++++++++++++++++++++ source/core/rendering/sdl_manager.hpp | 49 ++++++ source/game/constants.hpp | 26 ++++ source/{ => game}/joc_asteroides.cpp | 120 ++++----------- source/game/joc_asteroides.hpp | 46 ++++++ source/joc_asteroides.hpp | 94 ------------ source/{ => legacy}/asteroids.cpp | 0 source/main.cpp | 30 +++- source/project.h.in | 9 ++ source/sdl_manager.cpp | 91 ----------- source/sdl_manager.hpp | 33 ---- 18 files changed, 708 insertions(+), 338 deletions(-) rename release/{asteroids.rc => orni.rc} (100%) create mode 100644 source/core/rendering/sdl_manager.cpp create mode 100644 source/core/rendering/sdl_manager.hpp create mode 100644 source/game/constants.hpp rename source/{ => game}/joc_asteroides.cpp (79%) create mode 100644 source/game/joc_asteroides.hpp delete mode 100644 source/joc_asteroides.hpp rename source/{ => legacy}/asteroids.cpp (100%) create mode 100644 source/project.h.in delete mode 100644 source/sdl_manager.cpp delete mode 100644 source/sdl_manager.hpp 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 679760aab71b3c3d53b0508be0a98597a50bcbde..9f0079bd2b05493e2e36cbe2f7047dfe0994b219 100755 GIT binary patch literal 33200 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 Xmi0LY4mx3SS~1 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