// 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 #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; nau_.centre.y = 240; 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 = (std::rand() % 580) + 30; // 30-610 orni_[i].centre.y = (std::rand() % 420) + 30; // 30-450 // Angle aleatori orni_[i].angle = (std::rand() % 360) * Constants::PI / 180.0f; // Està actiu orni_[i].esta = true; } } 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 (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) // 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 if (dy > Constants::MARGE_DALT && dy < Constants::MARGE_BAIX) { nau_.centre.y = static_cast(std::round(dy)); } if (dx > Constants::MARGE_ESQ && dx < Constants::MARGE_DRET) { nau_.centre.x = static_cast(std::round(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(nau_.velocitat) << " px/s" << " angle=" << static_cast(nau_.angle * 180.0f / Constants::PI) << "°" << " dt=" << static_cast(delta_time * 1000.0f) << "ms" << std::endl; time_accumulator -= 1.0f; } // Actualitzar rotació dels enemics // Basat en el codi Pascal original: lines 429-432 for (auto& enemy : orni_) { if (enemy.esta) { enemy.rotacio += enemy.drotacio; } } // TODO: Actualitzar moviment ORNIs (Fase 8) // TODO: Actualitzar bales (Fase 9) } 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) // 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; 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); } } // TODO: Dibuixar bales (Fase 9) // 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) 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(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(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(p.x) / static_cast(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; pol.centre.y = 200; pol.angle = 0.0f; pol.velocitat = static_cast(Constants::VELOCITAT); pol.n = n; pol.drotacio = 0.078539816f; // ~4.5 graus per frame 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(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) { // TODO: Implementar moviment d'ORNI } void JocAsteroides::mou_bales(Poligon& bala) { // TODO: Implementar moviment de bala } void JocAsteroides::tocado() { // TODO: Implementar seqüència de mort }