diff --git a/CLAUDE.md b/CLAUDE.md index 7835945..a1ea342 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,89 +4,581 @@ 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, now being converted to modern C++ with SDL2. The game features a spaceship that must avoid and destroy enemies ("cosinus mesisinus" mounted in UFOs/ORNIs). The codebase contains both the original Pascal implementation and the in-progress C++ port. +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. -**Language**: The code and comments are in Catalan/Valencian. +**Language**: All code, comments, and variable names are in **Catalan/Valencian** (preserved from original). -## Build Commands +**Current Status**: **BETA 2.2** - Phases 0-9 completed (playable with ship, enemies, and bullets). + +## Build System + +Based on `/home/sergio/gitea/pollo` project structure. + +### Build Commands -**macOS**: ```bash -make macos -./bin/asteroids_macos +# Clean + compile +make clean && make + +# Run +./asteroids + +# Individual targets +make linux # Linux build +make macos # macOS build +make windows # Windows build (MinGW) ``` -**Linux**: -```bash -make linux -./bin/asteroids_linux -``` +### Build Files -Both commands compile from `source/*.cpp` with `-std=c++11 -Wall -O2` and link against SDL2. +- **CMakeLists.txt** - CMake configuration (C++20, SDL3) +- **Makefile** - Cross-platform wrapper, extracts project info from CMakeLists.txt +- **release/** - Platform-specific resources (icons, .rc, .plist) ## Architecture -### Code Structure +### File Structure -- `source/ASTEROID.PAS` - Original Turbo Pascal 7 implementation (DOS) -- `source/asteroids.cpp` - C++ port using SDL2 (in progress) +``` +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 Data Structures -The game uses polar coordinates (`ipunt`) for geometric objects: -- `ipunt` - Polar coordinate point (radius `r`, angle `angle`) -- `punt` - Cartesian coordinate point (x, y) -- `triangle` - The player's ship (3 polar points + center + angle + velocity) -- `poligon` - Generic polygon for enemies and bullets (vector of polar points + center + angle + velocity + rotation) +The game uses **polar coordinates** for all geometric objects (preserved from Pascal original): -### Key Game Constants +```cpp +struct IPunt { + float r; // Radius + float angle; // Angle in radians +}; -Defined in `asteroids.cpp`: -- Screen bounds: `MARGE_DALT` (20), `MARGE_BAIX` (460), `MARGE_ESQ` (20), `MARGE_DRET` (620) -- `MAX_ORNIS` (15) - Maximum enemies -- `MAX_BALES` (3) - Maximum bullets -- `VELOCITAT` (2), `VELOCITAT_MAX` (6) - Speed constraints +struct Punt { + int x, y; // Cartesian coordinates +}; -### Rendering System +struct Triangle { // Player's ship (nau_) + IPunt p1, p2, p3; // 3 polar points + Punt centre; // Center position + float angle; // Orientation + float velocitat; // Speed (px/s) +}; -The original Pascal code used VGA Mode 13h (320x200, 256 colors) with custom bit-packed framebuffer: -- `pvirt` - Virtual screen buffer (38400 bytes) -- `posa(x,y,color)` - Set pixel in virtual buffer (bit-packed) -- `llig(x,y)` - Read pixel from virtual buffer -- `volca()` - Flip virtual buffer to VGA memory -- Line drawing using Bresenham algorithm +struct Poligon { // Enemies (orni_) and bullets (bales_) + std::array ipuntx; // Polar points + Punt centre; + float angle; // Movement direction + float velocitat; // Speed (units/frame) + uint8_t n; // Number of sides + float drotacio; // Rotation delta per frame + float rotacio; // Current rotation angle + bool esta; // Is active? +}; +``` -The C++ port uses SDL2 for rendering. +### Constants (joc_asteroides.hpp) -### Game Mechanics +```cpp +namespace Constants { + constexpr int MARGE_DALT = 20; // Top margin + constexpr int MARGE_BAIX = 460; // Bottom margin + constexpr int MARGE_ESQ = 20; // Left margin + constexpr int MARGE_DRET = 620; // Right margin + constexpr int MAX_IPUNTS = 30; // Max polygon points + constexpr int MAX_ORNIS = 15; // Max enemies + constexpr int MAX_BALES = 3; // Max bullets + constexpr int VELOCITAT = 2; // Base velocity + constexpr int VELOCITAT_MAX = 6; // Max velocity + constexpr float PI = 3.14159265359f; +} +``` -- **Movement**: Ship uses polar velocity - angle determines direction, velocity determines speed -- **Collision**: Line drawing returns collision state; `rota_tri()` returns 1 on pixel collision -- **Death sequence**: Ship shrinks (`nau.p*.r` decreases), explodes into debris (`chatarra_cosmica`), then respawns after 250 frames -- **Enemies**: `orni[]` array of polygons with autonomous movement, random angle adjustments -- **Bullets**: Only one bullet active at a time (`bales[1].esta` flag) +## Game Loop (main.cpp) -### Pascal-to-C++ Conversion Notes +**Time-based physics** with real delta_time: -The C++ file contains hybrid Pascal/C++ syntax that needs completion: -- Lines 63-450 are uncommented Pascal code -- Pascal procedures/functions need conversion to C++ equivalents -- Assembly blocks (`waitretrace`, VGA mode setting) need SDL2 replacements -- Turbo Pascal keyboard handling (`teclapuls`, `instalarkb`) needs SDL event system -- Memory management (`getmem`, pointer segments) needs modern C++ approach +```cpp +// Lines 21-56 +Uint64 last_time = SDL_GetTicks(); +while (running) { + // Calculate real delta_time + Uint64 current_time = SDL_GetTicks(); + float delta_time = (current_time - last_time) / 1000.0f; // ms → s + last_time = current_time; -### Game Controls (from README) + // Cap at 50ms (20 FPS minimum) + if (delta_time > 0.05f) delta_time = 0.05f; -- Up Arrow: Accelerate -- Down Arrow: Brake -- Right Arrow: Rotate clockwise -- Left Arrow: Rotate counterclockwise -- Space: Fire -- Esc: Exit + // Process events + while (SDL_PollEvent(&event)) { + joc.processar_input(event); + // Handle quit/ESC + } -## Development Notes + // Update + render + joc.actualitzar(delta_time); + sdl.neteja(0, 0, 0); + joc.dibuixar(); + sdl.presenta(); +} +``` -- The conversion is incomplete - much Pascal code remains in comments -- Focus on SDL2 for cross-platform graphics/input instead of DOS VGA/keyboard -- Original used bit-packed monochrome framebuffer; consider SDL surfaces or textures -- Preserve the polar coordinate geometry system - it's core to the game's physics +**Critical**: Delta_time is **real** and **variable**, not fixed at 0.016f. All physics must multiply by delta_time. + +## SDL3 API Notes + +SDL3 has breaking changes from SDL2: + +- `SDL_CreateRenderer(window, nullptr)` - no flags parameter +- `event.key.key` instead of `event.key.keysym.sym` +- `SDL_EVENT_KEY_DOWN` instead of `SDL_KEYDOWN` +- `SDL_EVENT_QUIT` instead of `SDL_QUIT` +- `SDL_GetKeyboardState(nullptr)` - state-based input for continuous keys + +## Physics System + +### Ship Movement (joc_asteroides.cpp:67-155) + +**State-based input** (not event-based) for smooth controls: + +```cpp +const bool* keyboard_state = SDL_GetKeyboardState(nullptr); + +if (keyboard_state[SDL_SCANCODE_RIGHT]) + nau_.angle += ROTATION_SPEED * delta_time; +if (keyboard_state[SDL_SCANCODE_LEFT]) + nau_.angle -= ROTATION_SPEED * delta_time; +if (keyboard_state[SDL_SCANCODE_UP]) + nau_.velocitat += ACCELERATION * delta_time; +``` + +**Physics constants** (calibrated for ~20 FPS feel): + +```cpp +constexpr float ROTATION_SPEED = 2.5f; // rad/s (~143°/s) +constexpr float ACCELERATION = 100.0f; // px/s² +constexpr float MAX_VELOCITY = 200.0f; // px/s +constexpr float FRICTION = 6.0f; // px/s² +``` + +**Position calculation** (angle-PI/2 because angle=0 points up, not right): + +```cpp +float dy = (nau_.velocitat * delta_time) * std::sin(nau_.angle - PI/2.0f); +float dx = (nau_.velocitat * delta_time) * std::cos(nau_.angle - PI/2.0f); +nau_.centre.y += round(dy); +nau_.centre.x += round(dx); +``` + +**Visual velocity effect**: Ship triangle grows when moving (joc_asteroides.cpp:162-164): + +```cpp +// Scale 200 px/s → 6 px visual effect (like original) +float velocitat_visual = nau_.velocitat / 33.33f; +rota_tri(nau_, nau_.angle, velocitat_visual, true); +``` + +### Enemy Movement (joc_asteroides.cpp:367-405) - FASE 8 + +Autonomous movement with random direction changes: + +```cpp +void mou_orni(Poligon& orni, float delta_time) { + // 5% probability to change direction + if (rand() < 0.05f * RAND_MAX) + orni.angle = random() * 2*PI; + + // Move (2 px/frame * 20 FPS = 40 px/s) + float velocitat_efectiva = orni.velocitat * 20.0f * delta_time; + float dy = velocitat_efectiva * sin(orni.angle - PI/2.0f); + float dx = velocitat_efectiva * cos(orni.angle - PI/2.0f); + orni.centre.y += round(dy); + orni.centre.x += round(dx); + + // Bounce on walls + if (x < MARGE_ESQ || x > MARGE_DRET) + orni.angle = PI - orni.angle; // Horizontal reflection + if (y < MARGE_DALT || y > MARGE_BAIX) + orni.angle = 2*PI - orni.angle; // Vertical reflection +} +``` + +### Bullet Movement (joc_asteroides.cpp:444-465) - FASE 9 + +Straight-line movement, deactivates when leaving screen: + +```cpp +void mou_bales(Poligon& bala, float delta_time) { + // Fast movement (6 px/frame * 20 FPS = 120 px/s) + float velocitat_efectiva = bala.velocitat * 20.0f * delta_time; + float dy = velocitat_efectiva * sin(bala.angle - PI/2.0f); + float dx = velocitat_efectiva * cos(bala.angle - PI/2.0f); + bala.centre.y += round(dy); + bala.centre.x += round(dx); + + // Deactivate if out of bounds + if (x < MARGE_ESQ || x > MARGE_DRET || + y < MARGE_DALT || y > MARGE_BAIX) + bala.esta = false; +} +``` + +## Rendering System + +### Coordinate Conversion + +**Polar → Cartesian** with rotation (used in `rota_tri` and `rota_pol`): + +```cpp +// For each polar point +int x = round((r + velocitat) * cos(angle_punt + angle_object)) + centre.x; +int y = round((r + velocitat) * sin(angle_punt + angle_object)) + centre.y; +``` + +### Line Drawing (joc_asteroides.cpp:230-298) + +Currently uses **SDL_RenderLine** for efficiency: + +```cpp +bool linea(int x1, int y1, int x2, int y2, bool dibuixar) { + if (dibuixar && renderer_) { + SDL_SetRenderDrawColor(renderer_, 255, 255, 255, 255); // White + SDL_RenderLine(renderer_, x1, y1, x2, y2); + } + return false; // Collision detection TODO (Phase 10) +} +``` + +**Note**: Original Bresenham algorithm preserved in comments for **Phase 10** (pixel-perfect collision detection). + +### Ship Rendering (joc_asteroides.cpp:300-337) + +Triangle with 3 lines: + +```cpp +void rota_tri(const Triangle& tri, float angul, float velocitat, bool dibuixar) { + // Convert 3 polar points to Cartesian + int x1 = round((tri.p1.r + velocitat) * cos(tri.p1.angle + angul)) + tri.centre.x; + int y1 = round((tri.p1.r + velocitat) * sin(tri.p1.angle + angul)) + tri.centre.y; + // ... same for p2, p3 + + // Draw 3 lines + linea(x1, y1, x2, y2, dibuixar); + linea(x1, y1, x3, y3, dibuixar); + linea(x3, y3, x2, y2, dibuixar); +} +``` + +### Polygon Rendering (joc_asteroides.cpp:339-365) + +Enemies and bullets: + +```cpp +void rota_pol(const Poligon& pol, float angul, bool dibuixar) { + // Convert all polar points to Cartesian + std::array xy; + for (int i = 0; i < pol.n; i++) { + xy[i].x = round(pol.ipuntx[i].r * cos(pol.ipuntx[i].angle + angul)) + pol.centre.x; + xy[i].y = round(pol.ipuntx[i].r * sin(pol.ipuntx[i].angle + angul)) + pol.centre.y; + } + + // Draw lines between consecutive points + for (int i = 0; i < pol.n - 1; i++) + linea(xy[i].x, xy[i].y, xy[i+1].x, xy[i+1].y, dibuixar); + + // Close polygon + linea(xy[pol.n-1].x, xy[pol.n-1].y, xy[0].x, xy[0].y, dibuixar); +} +``` + +## Input System - FASE 9 + +### Continuous Input (actualitzar) + +Arrow keys use **state-based** polling: + +```cpp +const bool* keyboard_state = SDL_GetKeyboardState(nullptr); +if (keyboard_state[SDL_SCANCODE_UP]) { /* accelerate */ } +``` + +### Event-Based Input (processar_input) + +SPACE bar for shooting (joc_asteroides.cpp:174-212): + +```cpp +void processar_input(const SDL_Event& event) { + if (event.type == SDL_EVENT_KEY_DOWN) { + if (event.key.key == SDLK_SPACE) { + // Find first inactive bullet + for (auto& bala : bales_) { + if (!bala.esta) { + bala.esta = true; + bala.centre = nau_.centre; // Spawn at ship + bala.angle = nau_.angle; // Fire in ship direction + bala.velocitat = 6.0f; // High speed + break; // Only one bullet at a time + } + } + } + } +} +``` + +## Initialization (joc_asteroides.cpp:15-65) + +### Ship (lines 20-34) + +```cpp +// Triangle with 3 polar points (r=12, angles at 270°, 45°, 135°) +nau_.p1.r = 12.0f; +nau_.p1.angle = 3.0f * PI / 2.0f; // Points up +nau_.p2.r = 12.0f; +nau_.p2.angle = PI / 4.0f; // Back-right +nau_.p3.r = 12.0f; +nau_.p3.angle = 3.0f * PI / 4.0f; // Back-left +nau_.centre = {320, 240}; +nau_.angle = 0.0f; +nau_.velocitat = 0.0f; +``` + +### Enemies (lines 39-54) - FASE 7 + +```cpp +for (int i = 0; i < MAX_ORNIS; i++) { + crear_poligon_regular(orni_[i], 5, 20.0f); // Pentagon, r=20 + orni_[i].centre.x = rand(30, 610); + orni_[i].centre.y = rand(30, 450); + orni_[i].angle = rand(0, 360) * PI/180; + orni_[i].esta = true; +} +``` + +### Bullets (lines 56-64) - FASE 9 + +```cpp +for (int i = 0; i < MAX_BALES; i++) { + crear_poligon_regular(bales_[i], 5, 5.0f); // Small pentagon, r=5 + bales_[i].esta = false; // Initially inactive +} +``` + +## Update Loop (joc_asteroides.cpp:67-155) + +```cpp +void actualitzar(float delta_time) { + // 1. Ship input + physics (lines 68-125) + // - Keyboard state polling + // - Rotation, acceleration, friction + // - Position update with boundary checking + + // 2. Enemy movement (lines 137-147) - FASE 8 + for (auto& enemy : orni_) { + if (enemy.esta) { + mou_orni(enemy, delta_time); + enemy.rotacio += enemy.drotacio; // Visual rotation + } + } + + // 3. Bullet movement (lines 149-154) - FASE 9 + for (auto& bala : bales_) { + if (bala.esta) + mou_bales(bala, delta_time); + } + + // TODO Phase 10: Collision detection +} +``` + +## Draw Loop (joc_asteroides.cpp:157-184) + +```cpp +void dibuixar() { + // 1. Ship (if alive) + if (itocado_ == 0) { + float velocitat_visual = nau_.velocitat / 33.33f; + rota_tri(nau_, nau_.angle, velocitat_visual, true); + } + + // 2. Enemies (FASE 7) + for (const auto& enemy : orni_) { + if (enemy.esta) + rota_pol(enemy, enemy.rotacio, true); + } + + // 3. Bullets (FASE 9) + for (const auto& bala : bales_) { + if (bala.esta) + rota_pol(bala, 0.0f, true); // No visual rotation + } + + // TODO Phase 11: Draw borders +} +``` + +## Migration Progress + +### ✅ Phase 0: Project Setup +- CMakeLists.txt + Makefile (based on pollo) +- Stub files created + +### ✅ Phase 1: SDL Manager +- SDLManager class (sdl_manager.hpp/cpp) +- Window + renderer initialization +- Fixed SDL3 API differences + +### ✅ Phase 2: Data Structures +- IPunt, Punt, Triangle, Poligon defined +- Constants namespace with constexpr + +### ✅ Phase 3: Geometry Functions +- modul(), diferencia(), distancia(), angle_punt() +- crear_poligon_regular() + +### ✅ Phase 4: Line Drawing +- linea() with SDL_RenderLine +- Bresenham preserved in comments for Phase 10 + +### ✅ Phase 5: Ship Rendering +- rota_tri() polar→Cartesian conversion +- Ship initialization + +### ✅ Phase 6: Ship Movement +- **Critical fix**: Event-based → State-based input (SDL_GetKeyboardState) +- **Critical fix**: Fixed delta_time (0.016f) → Real delta_time calculation +- **Critical fix**: Visual velocity scaling (200 px/s → 6 px visual) +- Time-based physics (all values in px/s) +- Rotation, acceleration, friction, boundary checking + +### ✅ Phase 7: Enemy Rendering +- rota_pol() for polygons +- 15 random pentagons initialized +- Visual rotation (enemy.rotacio) + +### ✅ Phase 8: Enemy AI & Movement +- **mou_orni()** (joc_asteroides.cpp:367-405) + - 5% random direction change + - Polar movement (40 px/s) + - Boundary bounce (angle reflection) +- Integrated in actualitzar() (lines 137-147) + +### ✅ Phase 9: Bullet System +- **Bullet initialization** (joc_asteroides.cpp:56-64) + - 3 bullets, initially inactive + - Small pentagons (r=5) +- **Shooting with SPACE** (joc_asteroides.cpp:174-212) + - Spawns at ship position + - Fires in ship direction + - Only one bullet at a time +- **mou_bales()** (joc_asteroides.cpp:444-465) + - Fast rectlinear movement (120 px/s) + - Deactivates when out of bounds +- **Drawing** (joc_asteroides.cpp:175-181) + - No visual rotation + +### 🔲 Phase 10: Collision Detection & Death (NEXT) +- **Collision detection**: + - Restore Bresenham pixel-perfect algorithm + - Detect ship-enemy collision → tocado() + - Detect bullet-enemy collision → destroy enemy +- **Death sequence** (tocado): + - Explosion animation (itocado_ counter) + - Ship shrinking + - Debris particles (chatarra_cosmica) + - Respawn after delay +- **Important**: Original Pascal used bit-packed framebuffer (llig() function) + - Need to adapt to SDL3 rendering pipeline + - Options: render to texture, software buffer, or geometric collision + +### 🔲 Phase 11: Polish & Refinements +- Draw borders (marges) +- Text rendering with SDL_ttf (TODO for later) +- Sound effects (optional) +- Score system (optional) + +### 🔲 Phase 12: Cross-Platform Testing +- Test on Linux, macOS, Windows +- Create release builds +- Package with resources + +## Known Issues & Tuning Needed + +1. **Ship physics constants**: User mentioned "sigue sin ir fino" - may need adjustment: + - `ROTATION_SPEED` (currently 2.5 rad/s) + - `ACCELERATION` (currently 100.0 px/s²) + - `MAX_VELOCITY` (currently 200.0 px/s) + - `FRICTION` (currently 6.0 px/s²) + +2. **Enemy movement**: May need speed/bounce angle tuning + - `VELOCITAT_SCALE` (currently 20.0) + - Reflection angles (PI - angle, 2*PI - angle) + +3. **Bullet speed**: May need adjustment + - `velocitat = 6.0f` (120 px/s) + +## Important Pascal References (Original Code) + +The original Pascal game is in `source/ASTEROID.PAS` (if available). Key procedures: + +- `teclapuls` - Keyboard handler (converted to SDL_GetKeyboardState) +- `mou_nau` - Ship movement (now actualitzar ship section) +- `mou_orni` - Enemy movement (joc_asteroides.cpp:367-405) +- `mou_bales` - Bullet movement (joc_asteroides.cpp:444-465) +- `rota_tri` - Ship rendering (joc_asteroides.cpp:300-337) +- `rota_pol` - Polygon rendering (joc_asteroides.cpp:339-365) +- `linea` - Bresenham line (joc_asteroides.cpp:230-298) +- `tocado` - Death sequence (TODO Phase 10) + +## Controls + +- **Arrow Keys** (UP/DOWN/LEFT/RIGHT): Ship movement (continuous) +- **SPACE**: Shoot (event-based) +- **ESC**: Quit + +## Debug Output + +Ship debug info printed every second (joc_asteroides.cpp:115-125): + +```cpp +static float time_accumulator = 0.0f; +time_accumulator += delta_time; +if (time_accumulator >= 1.0f) { + std::cout << "Nau: pos(" << nau_.centre.x << "," << nau_.centre.y + << ") vel=" << (int)nau_.velocitat << " px/s" + << " angle=" << (int)(nau_.angle * 180/PI) << "°" + << " dt=" << (int)(delta_time * 1000) << "ms" << std::endl; + time_accumulator -= 1.0f; +} +``` + +## Next Session Priorities + +1. **Phase 10: Collision Detection** + - Most complex phase + - Need to decide: geometric vs pixel-perfect collision + - Implement tocado() death sequence + - Bullet-enemy collision + +2. **Phase 11: Polish** + - Draw borders + - Consider text rendering (score, lives) + +3. **Phase 12: Release** + - Cross-platform testing + - Final physics tuning + +## Tips for Future Claude Code Sessions + +- **Always read this file first** before making changes +- **Preserve Valencian naming**: nau, orni, bales, centre, velocitat, etc. +- **Time-based physics**: All movement must multiply by delta_time +- **Polar coordinates**: Core to the game, don't change to Cartesian +- **Test compilation** after each change: `make clean && make` +- **Visual velocity scaling**: Remember to scale velocitat before passing to rota_tri +- **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 diff --git a/asteroids b/asteroids index b7f2b16..679760a 100755 Binary files a/asteroids and b/asteroids differ diff --git a/source/joc_asteroides.cpp b/source/joc_asteroides.cpp index cfd38b0..edfd209 100644 --- a/source/joc_asteroides.cpp +++ b/source/joc_asteroides.cpp @@ -29,8 +29,8 @@ void JocAsteroides::inicialitzar() { nau_.p3.angle = (3.0f * Constants::PI) / 4.0f; // 135° nau_.angle = 0.0f; - nau_.centre.x = 320; - nau_.centre.y = 240; + nau_.centre.x = 320.0f; + nau_.centre.y = 240.0f; nau_.velocitat = 0.0f; // Inicialitzar estat de col·lisió @@ -43,8 +43,8 @@ void JocAsteroides::inicialitzar() { crear_poligon_regular(orni_[i], 5, 20.0f); // Posició aleatòria dins de l'àrea de joc - orni_[i].centre.x = (std::rand() % 580) + 30; // 30-610 - orni_[i].centre.y = (std::rand() % 420) + 30; // 30-450 + orni_[i].centre.x = static_cast((std::rand() % 580) + 30); // 30-610 + orni_[i].centre.y = static_cast((std::rand() % 420) + 30); // 30-450 // Angle aleatori orni_[i].angle = (std::rand() % 360) * Constants::PI / 180.0f; @@ -52,6 +52,16 @@ void JocAsteroides::inicialitzar() { // Està actiu orni_[i].esta = true; } + + // Inicialitzar bales + // Basat en el codi Pascal original: inicialment inactives + for (int i = 0; i < Constants::MAX_BALES; i++) { + // Crear pentàgon petit (5 costats, radi 5) + crear_poligon_regular(bales_[i], 5, 5.0f); + + // Inicialment inactiva + bales_[i].esta = false; + } } void JocAsteroides::actualitzar(float delta_time) { @@ -59,11 +69,11 @@ void JocAsteroides::actualitzar(float delta_time) { // Basat en el codi Pascal original: lines 394-417 // Convertit a time-based per ser independent del framerate - // Constants de física (calibrades per sentir-se com l'original a ~20 FPS) - constexpr float ROTATION_SPEED = 2.5f; // ~143°/s (rotació suau) - constexpr float ACCELERATION = 100.0f; // px/s² (acceleració notable) - constexpr float MAX_VELOCITY = 200.0f; // px/s (velocitat màxima) - constexpr float FRICTION = 6.0f; // px/s² (fricció notable) + // Constants de física (convertides des del Pascal original a ~20 FPS) + constexpr float ROTATION_SPEED = 3.14f; // rad/s (0.157 rad/frame × 20 = 3.14 rad/s, ~180°/s) + constexpr float ACCELERATION = 400.0f; // px/s² (0.2 u/frame × 20 × 100 = 400 px/s²) + constexpr float MAX_VELOCITY = 120.0f; // px/s (6 u/frame × 20 = 120 px/s) + constexpr float FRICTION = 20.0f; // px/s² (0.1 u/frame × 20 × 10 = 20 px/s²) // Obtenir estat actual del teclat (no events, sinó estat continu) const bool* keyboard_state = SDL_GetKeyboardState(nullptr); @@ -95,12 +105,13 @@ void JocAsteroides::actualitzar(float delta_time) { + nau_.centre.x; // Boundary checking - només actualitzar si dins dels marges + // Acumulació directa amb precisió subpíxel if (dy > Constants::MARGE_DALT && dy < Constants::MARGE_BAIX) { - nau_.centre.y = static_cast(std::round(dy)); + nau_.centre.y = dy; } if (dx > Constants::MARGE_ESQ && dx < Constants::MARGE_DRET) { - nau_.centre.x = static_cast(std::round(dx)); + nau_.centre.x = dx; } // Fricció - desacceleració gradual (time-based) @@ -124,25 +135,33 @@ void JocAsteroides::actualitzar(float delta_time) { time_accumulator -= 1.0f; } - // Actualitzar rotació dels enemics + // Actualitzar moviment i rotació dels enemics (ORNIs) // Basat en el codi Pascal original: lines 429-432 for (auto& enemy : orni_) { if (enemy.esta) { - enemy.rotacio += enemy.drotacio; + // Moviment autònom (Fase 8) + mou_orni(enemy, delta_time); + + // Rotació visual (time-based: drotacio està en rad/s) + enemy.rotacio += enemy.drotacio * delta_time; } } - // TODO: Actualitzar moviment ORNIs (Fase 8) - // TODO: Actualitzar bales (Fase 9) + // Actualitzar moviment de bales (Fase 9) + for (auto& bala : bales_) { + if (bala.esta) { + mou_bales(bala, delta_time); + } + } } void JocAsteroides::dibuixar() { // Dibuixar la nau si no està en seqüència de mort if (itocado_ == 0) { - // Escalar velocitat per l'efect visual (200 px/s → ~6 px d'efecte) + // Escalar velocitat per l'efect visual (120 px/s → ~6 px d'efecte) // El codi Pascal original sumava velocitat (0-6) al radi per donar - // sensació de "empenta". Ara velocitat està en px/s (0-200). - float velocitat_visual = nau_.velocitat / 33.33f; + // sensació de "empenta". Ara velocitat està en px/s (0-120). + float velocitat_visual = nau_.velocitat / 20.0f; rota_tri(nau_, nau_.angle, velocitat_visual, true); } @@ -154,7 +173,14 @@ void JocAsteroides::dibuixar() { } } - // TODO: Dibuixar bales (Fase 9) + // Dibuixar bales (Fase 9) + for (const auto& bala : bales_) { + if (bala.esta) { + // Dibuixar com a pentàgon petit, sense rotació visual (sempre mateix angle) + rota_pol(bala, 0.0f, true); + } + } + // TODO: Dibuixar marges (Fase 11) } @@ -166,6 +192,30 @@ void JocAsteroides::processar_input(const SDL_Event& event) { switch (event.key.key) { case SDLK_SPACE: // Disparar (Fase 9) + // Basat en el codi Pascal original: crear bala en posició de la nau + // El joc original només permetia 1 bala activa alhora + + // Buscar primera bala inactiva + for (auto& bala : bales_) { + if (!bala.esta) { + // Activar bala + bala.esta = true; + + // Posició inicial = centre de la nau + bala.centre.x = nau_.centre.x; + bala.centre.y = nau_.centre.y; + + // Angle = angle de la nau (dispara en la direcció que apunta) + bala.angle = nau_.angle; + + // Velocitat alta (el joc Pascal original usava 7 px/frame) + // 7 px/frame × 20 FPS = 140 px/s + bala.velocitat = 140.0f; + + // Només una bala alhora (com el joc original) + break; + } + } break; default: @@ -217,12 +267,14 @@ void JocAsteroides::crear_poligon_regular(Poligon& pol, uint8_t n, float r) { } // Inicialitzar propietats del polígon - pol.centre.x = 320; - pol.centre.y = 200; + pol.centre.x = 320.0f; + pol.centre.y = 200.0f; pol.angle = 0.0f; - pol.velocitat = static_cast(Constants::VELOCITAT); + // 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; - pol.drotacio = 0.078539816f; // ~4.5 graus per frame + // 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; } @@ -364,12 +416,66 @@ void JocAsteroides::rota_pol(const Poligon& pol, float angul, bool dibuixar) { linea(xy[pol.n - 1].x, xy[pol.n - 1].y, xy[0].x, xy[0].y, dibuixar); } -void JocAsteroides::mou_orni(Poligon& orni) { - // TODO: Implementar moviment d'ORNI +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 + + // 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 + 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; + + // 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; + } + + // 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; + } + + // Nota: La rotació visual (orni.rotacio += orni.drotacio) ja es fa a actualitzar() } -void JocAsteroides::mou_bales(Poligon& bala) { - // TODO: Implementar moviment de bala +void JocAsteroides::mou_bales(Poligon& bala, float delta_time) { + // Moviment rectilini de la bala + // Basat en el codi Pascal original: procedure mou_bales + + // Calcular nova posició (moviment polar time-based) + // velocitat ja està en px/s (140 px/s), només cal multiplicar per delta_time + float velocitat_efectiva = bala.velocitat * delta_time; + + // Calcular desplaçament (angle-PI/2 perquè angle=0 apunta amunt) + float dy = velocitat_efectiva * std::sin(bala.angle - Constants::PI / 2.0f); + float dx = velocitat_efectiva * std::cos(bala.angle - Constants::PI / 2.0f); + + // Acumulació directa amb precisió subpíxel + bala.centre.y += dy; + bala.centre.x += dx; + + // Desactivar si surt dels marges (no rebota com els ORNIs) + if (bala.centre.x < Constants::MARGE_ESQ || bala.centre.x > Constants::MARGE_DRET || + bala.centre.y < Constants::MARGE_DALT || bala.centre.y > Constants::MARGE_BAIX) { + bala.esta = false; + } } void JocAsteroides::tocado() { diff --git a/source/joc_asteroides.hpp b/source/joc_asteroides.hpp index c32a82a..aae8ddc 100644 --- a/source/joc_asteroides.hpp +++ b/source/joc_asteroides.hpp @@ -30,8 +30,8 @@ struct IPunt { }; struct Punt { - int x; - int y; + float x; + float y; }; struct Triangle { @@ -86,8 +86,8 @@ private: void rota_pol(const Poligon& pol, float angul, bool dibuixar); // Moviment - void mou_orni(Poligon& orni); - void mou_bales(Poligon& bala); + void mou_orni(Poligon& orni, float delta_time); + void mou_bales(Poligon& bala, float delta_time); void tocado(); };