# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## 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. **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). ## Build System Based on `/home/sergio/gitea/pollo` project structure. ### Build Commands ```bash # Clean + compile make clean && make # Run ./asteroids # Individual targets make linux # Linux build make macos # macOS build make windows # Windows build (MinGW) ``` ### Build Files - **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 ### File Structure ``` 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** for all geometric objects (preserved from Pascal original): ```cpp struct IPunt { float r; // Radius float angle; // Angle in radians }; struct Punt { int x, y; // Cartesian coordinates }; 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) }; 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? }; ``` ### Constants (joc_asteroides.hpp) ```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; } ``` ## Game Loop (main.cpp) **Time-based physics** with real delta_time: ```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; // Cap at 50ms (20 FPS minimum) if (delta_time > 0.05f) delta_time = 0.05f; // Process events while (SDL_PollEvent(&event)) { joc.processar_input(event); // Handle quit/ESC } // Update + render joc.actualitzar(delta_time); sdl.neteja(0, 0, 0); joc.dibuixar(); sdl.presenta(); } ``` **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