Files
orni_attack/source/joc_asteroides.cpp
2025-11-27 17:32:01 +01:00

484 lines
17 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// joc_asteroides.cpp - Implementació de la lògica del joc
// © 1999 Visente i Sergi (versió Pascal)
// © 2025 Port a C++20 amb SDL3
#include "joc_asteroides.hpp"
#include <cmath>
#include <iostream>
#include <cstdlib>
#include <ctime>
JocAsteroides::JocAsteroides(SDL_Renderer* renderer)
: renderer_(renderer), itocado_(0) {
}
void JocAsteroides::inicialitzar() {
// Inicialitzar generador de números aleatoris
// Basat en el codi Pascal original: line 376
std::srand(static_cast<unsigned>(std::time(nullptr)));
// Inicialització de la nau (triangle)
// Basat en el codi Pascal original: lines 380-384
nau_.p1.r = 12.0f;
nau_.p1.angle = 3.0f * Constants::PI / 2.0f; // Apunta amunt (270°)
nau_.p2.r = 12.0f;
nau_.p2.angle = Constants::PI / 4.0f; // 45°
nau_.p3.r = 12.0f;
nau_.p3.angle = (3.0f * Constants::PI) / 4.0f; // 135°
nau_.angle = 0.0f;
nau_.centre.x = 320.0f;
nau_.centre.y = 240.0f;
nau_.velocitat = 0.0f;
// Inicialitzar estat de col·lisió
itocado_ = 0;
// Inicialitzar enemics (ORNIs)
// Basat en el codi Pascal original: line 386
for (int i = 0; i < Constants::MAX_ORNIS; i++) {
// Crear pentàgon regular (5 costats, radi 20)
crear_poligon_regular(orni_[i], 5, 20.0f);
// Posició aleatòria dins de l'àrea de joc
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;
// 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) {
// Actualització de la física de la nau (TIME-BASED)
// Basat en el codi Pascal original: lines 394-417
// Convertit a time-based per ser independent del framerate
// 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);
// Processar input continu (com teclapuls() del Pascal original)
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]) {
if (nau_.velocitat < MAX_VELOCITY) {
nau_.velocitat += ACCELERATION * delta_time;
if (nau_.velocitat > MAX_VELOCITY) {
nau_.velocitat = MAX_VELOCITY;
}
}
}
// Calcular nova posició basada en velocitat i angle
// S'usa (angle - PI/2) perquè angle=0 apunte cap amunt, no cap a la dreta
// velocitat està en px/s, així que multipliquem per delta_time
float dy = (nau_.velocitat * delta_time) * std::sin(nau_.angle - Constants::PI / 2.0f)
+ nau_.centre.y;
float dx = (nau_.velocitat * delta_time) * std::cos(nau_.angle - Constants::PI / 2.0f)
+ 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 = dy;
}
if (dx > Constants::MARGE_ESQ && dx < Constants::MARGE_DRET) {
nau_.centre.x = dx;
}
// Fricció - desacceleració gradual (time-based)
if (nau_.velocitat > 0.1f) {
nau_.velocitat -= FRICTION * delta_time;
if (nau_.velocitat < 0.0f) {
nau_.velocitat = 0.0f;
}
}
// 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<int>(nau_.velocitat) << " px/s"
<< " angle=" << static_cast<int>(nau_.angle * 180.0f / Constants::PI) << "°"
<< " dt=" << static_cast<int>(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
for (auto& enemy : orni_) {
if (enemy.esta) {
// 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;
}
}
// 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 (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-120).
float velocitat_visual = nau_.velocitat / 20.0f;
rota_tri(nau_, nau_.angle, velocitat_visual, true);
}
// Dibuixar ORNIs (enemics)
// Basat en el codi Pascal original: lines 429-432
for (const auto& enemy : orni_) {
if (enemy.esta) {
rota_pol(enemy, enemy.rotacio, true);
}
}
// 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)
}
void JocAsteroides::processar_input(const SDL_Event& event) {
// Processament d'input per events puntuals (no continus)
// L'input continu (fletxes) es processa en actualitzar() amb SDL_GetKeyboardState()
if (event.type == SDL_EVENT_KEY_DOWN) {
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:
break;
}
}
}
// 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<float>(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<int>(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<float>(p.x) / static_cast<float>(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<float>(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;
}
bool JocAsteroides::linea(int x1, int y1, int x2, int y2, bool dibuixar) {
// Algorisme de Bresenham per dibuixar línies
// Basat en el codi Pascal original
// Helper function: retorna el signe d'un nombre
auto sign = [](int x) -> int {
if (x < 0) return -1;
if (x > 0) return 1;
return 0;
};
// Variables per a l'algorisme (no utilitzades fins Fase 10 - detecció de col·lisions)
// int x = x1, y = y1;
// int xs = x2 - x1;
// int ys = y2 - y1;
// int xm = sign(xs);
// int ym = sign(ys);
// xs = std::abs(xs);
// ys = std::abs(ys);
// Suprimir warning de variable no usada
(void)sign;
// Detecció de col·lisió (TODO per Fase 10)
// El codi Pascal original llegia pixels del framebuffer bit-packed
// i comptava col·lisions. Per ara, usem SDL_RenderDrawLine i retornem false.
bool colisio = false;
// Dibuixar amb SDL3 (més eficient que Bresenham píxel a píxel)
if (dibuixar && renderer_) {
SDL_SetRenderDrawColor(renderer_, 255, 255, 255, 255); // Blanc
SDL_RenderLine(renderer_,
static_cast<float>(x1),
static_cast<float>(y1),
static_cast<float>(x2),
static_cast<float>(y2));
}
// Algorisme de Bresenham original (conservat per a futura detecció de col·lisió)
/*
if (xs > ys) {
// Línia plana (<45 graus)
int count = -(xs / 2);
while (x != x2) {
count = count + ys;
x = x + xm;
if (count > 0) {
y = y + ym;
count = count - xs;
}
// Aquí aniria la detecció de col·lisió píxel a píxel
}
} else {
// Línia pronunciada (>=45 graus)
int count = -(ys / 2);
while (y != y2) {
count = count + xs;
y = y + ym;
if (count > 0) {
x = x + xm;
count = count - ys;
}
// Aquí aniria la detecció de col·lisió píxel a píxel
}
}
*/
return colisio;
}
void JocAsteroides::rota_tri(const Triangle& tri, float angul, float velocitat, bool dibuixar) {
// Rotar i dibuixar triangle (nau)
// Conversió de coordenades polars a cartesianes amb rotació
// Basat en el codi Pascal original: lines 271-284
// Convertir cada punt polar a cartesià
// x = (r + velocitat) * cos(angle_punt + angle_nau) + centre.x
// y = (r + velocitat) * sin(angle_punt + angle_nau) + centre.y
int x1 = static_cast<int>(std::round(
(tri.p1.r + velocitat) * std::cos(tri.p1.angle + angul)
)) + tri.centre.x;
int y1 = static_cast<int>(std::round(
(tri.p1.r + velocitat) * std::sin(tri.p1.angle + angul)
)) + tri.centre.y;
int x2 = static_cast<int>(std::round(
(tri.p2.r + velocitat) * std::cos(tri.p2.angle + angul)
)) + tri.centre.x;
int y2 = static_cast<int>(std::round(
(tri.p2.r + velocitat) * std::sin(tri.p2.angle + angul)
)) + tri.centre.y;
int x3 = static_cast<int>(std::round(
(tri.p3.r + velocitat) * std::cos(tri.p3.angle + angul)
)) + tri.centre.x;
int y3 = static_cast<int>(std::round(
(tri.p3.r + velocitat) * std::sin(tri.p3.angle + angul)
)) + tri.centre.y;
// Dibuixar les 3 línies que formen el triangle
linea(x1, y1, x2, y2, dibuixar);
linea(x1, y1, x3, y3, dibuixar);
linea(x3, y3, x2, y2, dibuixar);
}
void JocAsteroides::rota_pol(const Poligon& pol, float angul, bool dibuixar) {
// Rotar i dibuixar polígon (enemics i bales)
// Conversió de coordenades polars a cartesianes amb rotació
// Basat en el codi Pascal original: lines 286-296
// Array temporal per emmagatzemar punts convertits a cartesianes
std::array<Punt, Constants::MAX_IPUNTS> xy;
// Convertir cada punt polar a cartesià
for (uint8_t i = 0; i < pol.n; i++) {
xy[i].x = static_cast<int>(std::round(
pol.ipuntx[i].r * std::cos(pol.ipuntx[i].angle + angul)
)) + pol.centre.x;
xy[i].y = static_cast<int>(std::round(
pol.ipuntx[i].r * std::sin(pol.ipuntx[i].angle + angul)
)) + pol.centre.y;
}
// Dibuixar línies entre punts consecutius
for (uint8_t i = 0; i < pol.n - 1; i++) {
linea(xy[i].x, xy[i].y, xy[i + 1].x, xy[i + 1].y, dibuixar);
}
// Tancar el polígon (últim punt → primer punt)
linea(xy[pol.n - 1].x, xy[pol.n - 1].y, xy[0].x, xy[0].y, 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
// 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, 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() {
// TODO: Implementar seqüència de mort
}