Files
orni_attack/source/game/joc_asteroides.cpp
2025-11-27 21:47:08 +01:00

428 lines
15 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 "core/rendering/primitives.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;
}
}
// 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 de dibuix
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 EXACTAMENT en el codi Pascal original: ASTEROID.PAS lines 279-293
//
// IMPORTANT: El Pascal original NO té canvi aleatori continu!
// Només ajusta l'angle quan toca una paret.
// Calcular nova posició PROPUESTA (time-based, però lògica Pascal)
// velocitat ja està en px/s (40 px/s), multiplicar per delta_time
float velocitat_efectiva = orni.velocitat * delta_time;
// Calcular desplaçament (angle-PI/2 perquè angle=0 apunta amunt)
float dy = velocitat_efectiva * std::sin(orni.angle - Constants::PI / 2.0f);
float dx = velocitat_efectiva * std::cos(orni.angle - Constants::PI / 2.0f);
float new_y = orni.centre.y + dy;
float new_x = orni.centre.x + dx;
// Lògica Pascal: Actualitza Y si dins, sinó ajusta angle aleatòriament
// if (dy>marge_dalt) and (dy<marge_baix) then orni.centre.y:=round(Dy)
// else orni.angle:=orni.angle+(random(256)/512)*(random(3)-1);
if (new_y > Constants::MARGE_DALT && new_y < Constants::MARGE_BAIX) {
orni.centre.y = new_y;
} else {
// Pequeño ajuste aleatorio: (random(256)/512)*(random(3)-1)
// random(256) = 0..255, /512 = 0..0.498
// random(3) = 0,1,2, -1 = -1,0,1
// Resultado: ±0.5 rad aprox
float rand1 = (static_cast<float>(std::rand() % 256) / 512.0f);
int rand2 = (std::rand() % 3) - 1; // -1, 0, o 1
orni.angle += rand1 * static_cast<float>(rand2);
}
// Lògica Pascal: Actualitza X si dins, sinó ajusta angle aleatòriament
// if (dx>marge_esq) and (dx<marge_dret) then orni.centre.x:=round(Dx)
// else orni.angle:=orni.angle+(random(256)/512)*(random(3)-1);
if (new_x > Constants::MARGE_ESQ && new_x < Constants::MARGE_DRET) {
orni.centre.x = new_x;
} else {
float rand1 = (static_cast<float>(std::rand() % 256) / 512.0f);
int rand2 = (std::rand() % 3) - 1;
orni.angle += rand1 * static_cast<float>(rand2);
}
// 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
}