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