// playfield.cpp - Implementació del fons del playfield // © 2026 JailDesigner #include "core/graphics/playfield.hpp" #include #include "core/defaults.hpp" #include "core/rendering/line_renderer.hpp" namespace Graphics { Playfield::Playfield(Rendering::Renderer* renderer) : renderer_(renderer) { buildLines(); total_slots_ = static_cast(lines_.size()); // Distribuïm els spawns de manera que la última línia acabe just a TOTAL_ANIMATION_DURATION_S. // last_line_start = (N-1) * spawn_interval // last_line_end = last_line_start + LINE_GROWTH_DURATION_S = TOTAL_ANIMATION_DURATION_S if (total_slots_ > 1) { spawn_interval_s_ = (Defaults::Playfield::TOTAL_ANIMATION_DURATION_S - Defaults::Playfield::LINE_GROWTH_DURATION_S) / static_cast(total_slots_ - 1); } } void Playfield::update(float delta_time) { elapsed_s_ += delta_time; } void Playfield::buildLines() { const SDL_FRect& zona = Defaults::Zones::PLAYAREA; const float CELL_W = zona.w / static_cast(Defaults::Playfield::COLUMNS); const float CELL_H = zona.h / static_cast(Defaults::Playfield::ROWS); const float SUB_W = CELL_W / static_cast(Defaults::Playfield::SUBDIVISIONS); const float SUB_H = CELL_H / static_cast(Defaults::Playfield::SUBDIVISIONS); const int SUB_VERTS = Defaults::Playfield::COLUMNS * Defaults::Playfield::SUBDIVISIONS; const int SUB_HORIZ = Defaults::Playfield::ROWS * Defaults::Playfield::SUBDIVISIONS; std::vector verticals; std::vector horizontals; // Verticals: posicions i ∈ [1, SUB_VERTS-1]. Si i % SUBDIVISIONS == 0 → línia // de la graella principal; si no, sub-graella. for (int i = 1; i < SUB_VERTS; i++) { const float X = zona.x + (static_cast(i) * SUB_W); const bool IS_MAIN = (i % Defaults::Playfield::SUBDIVISIONS) == 0; const float BRIGHTNESS = IS_MAIN ? Defaults::Playfield::GRID_BRIGHTNESS : Defaults::Playfield::SUBGRID_BRIGHTNESS; verticals.push_back(Line{ .start = {.x = X, .y = zona.y}, .end = {.x = X, .y = zona.y + zona.h}, .brightness = BRIGHTNESS, .slot = 0}); } // Horitzontals: posicions j ∈ [1, SUB_HORIZ-1]. Mateix criteri main/sub. for (int j = 1; j < SUB_HORIZ; j++) { const float Y = zona.y + (static_cast(j) * SUB_H); const bool IS_MAIN = (j % Defaults::Playfield::SUBDIVISIONS) == 0; const float BRIGHTNESS = IS_MAIN ? Defaults::Playfield::GRID_BRIGHTNESS : Defaults::Playfield::SUBGRID_BRIGHTNESS; horizontals.push_back(Line{ .start = {.x = zona.x, .y = Y}, .end = {.x = zona.x + zona.w, .y = Y}, .brightness = BRIGHTNESS, .slot = 0}); } // Verticals ja venen ordenats per x ascendent (loop sobre i). Assignem slots 0..N-1. // Horitzontals ja venen ordenats per y ascendent. Assignem slots N..total-1. lines_.clear(); lines_.reserve(verticals.size() + horizontals.size()); int slot = 0; for (auto& line : verticals) { line.slot = slot++; lines_.push_back(line); } for (auto& line : horizontals) { line.slot = slot++; lines_.push_back(line); } } auto Playfield::computeLineProgress(int slot) const -> float { const float LINE_START = static_cast(slot) * spawn_interval_s_; const float LINE_ELAPSED = elapsed_s_ - LINE_START; return std::clamp(LINE_ELAPSED / Defaults::Playfield::LINE_GROWTH_DURATION_S, 0.0F, 1.0F); } void Playfield::draw() const { for (const auto& line : lines_) { const float P = computeLineProgress(line.slot); if (P <= 0.0F) { continue; } const float END_X = line.start.x + ((line.end.x - line.start.x) * P); const float END_Y = line.start.y + ((line.end.y - line.start.y) * P); Rendering::linea( renderer_, static_cast(line.start.x), static_cast(line.start.y), static_cast(END_X), static_cast(END_Y), line.brightness); } } } // namespace Graphics