// 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 #include #include #include 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(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((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; // 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 ara definides a core/defaults.hpp (Defaults::Physics) // 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 += Defaults::Physics::ROTATION_SPEED * delta_time; } if (keyboard_state[SDL_SCANCODE_LEFT]) { nau_.angle -= Defaults::Physics::ROTATION_SPEED * delta_time; } if (keyboard_state[SDL_SCANCODE_UP]) { if (nau_.velocitat < Defaults::Physics::MAX_VELOCITY) { nau_.velocitat += Defaults::Physics::ACCELERATION * delta_time; if (nau_.velocitat > Defaults::Physics::MAX_VELOCITY) { nau_.velocitat = Defaults::Physics::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 -= Defaults::Physics::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(x1), static_cast(y1), static_cast(x2), static_cast(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(std::round((tri.p1.r + velocitat) * std::cos(tri.p1.angle + angul))) + tri.centre.x; int y1 = static_cast(std::round((tri.p1.r + velocitat) * std::sin(tri.p1.angle + angul))) + tri.centre.y; int x2 = static_cast(std::round((tri.p2.r + velocitat) * std::cos(tri.p2.angle + angul))) + tri.centre.x; int y2 = static_cast(std::round((tri.p2.r + velocitat) * std::sin(tri.p2.angle + angul))) + tri.centre.y; int x3 = static_cast(std::round((tri.p3.r + velocitat) * std::cos(tri.p3.angle + angul))) + tri.centre.x; int y3 = static_cast(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 xy; // Convertir cada punt polar a cartesià for (uint8_t i = 0; i < pol.n; i++) { xy[i].x = static_cast(std::round( pol.ipuntx[i].r * std::cos(pol.ipuntx[i].angle + angul))) + pol.centre.x; xy[i].y = static_cast(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 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(std::rand() % 256) / 512.0f); int rand2 = (std::rand() % 3) - 1; // -1, 0, o 1 orni.angle += rand1 * static_cast(rand2); } // Lògica Pascal: Actualitza X si dins, sinó ajusta angle aleatòriament // if (dx>marge_esq) and (dx Constants::MARGE_ESQ && new_x < Constants::MARGE_DRET) { orni.centre.x = new_x; } else { float rand1 = (static_cast(std::rand() % 256) / 512.0f); int rand2 = (std::rand() % 3) - 1; orni.angle += rand1 * static_cast(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 }