migrant a SDL3
This commit is contained in:
610
CLAUDE.md
610
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<IPunt, MAX_IPUNTS> 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<Punt, MAX_IPUNTS> 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
|
||||
|
||||
@@ -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<float>((std::rand() % 580) + 30); // 30-610
|
||||
orni_[i].centre.y = static_cast<float>((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<int>(std::round(dy));
|
||||
nau_.centre.y = dy;
|
||||
}
|
||||
|
||||
if (dx > Constants::MARGE_ESQ && dx < Constants::MARGE_DRET) {
|
||||
nau_.centre.x = static_cast<int>(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<float>(Constants::VELOCITAT);
|
||||
// Convertir velocitat de px/frame a px/s: 2 px/frame × 20 FPS = 40 px/s
|
||||
pol.velocitat = static_cast<float>(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<float>(std::rand()) / static_cast<float>(RAND_MAX);
|
||||
if (random_val < 0.05f) {
|
||||
// Assignar un angle completament aleatori (0-360°)
|
||||
orni.angle = (static_cast<float>(std::rand()) / static_cast<float>(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() {
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user