diff --git a/src/graphics/renderers/GameRenderer.cpp b/src/graphics/renderers/GameRenderer.cpp index 5eaa425..d882e45 100644 --- a/src/graphics/renderers/GameRenderer.cpp +++ b/src/graphics/renderers/GameRenderer.cpp @@ -156,6 +156,99 @@ void GameRenderer::drawSmallPiece(SDL_Renderer* renderer, SDL_Texture* blocksTex } } +void GameRenderer::renderNextPanel( + SDL_Renderer* renderer, + FontAtlas* pixelFont, + SDL_Texture* blocksTex, + const Game::Piece& nextPiece, + float panelX, + float panelY, + float panelW, + float panelH, + float tileSize +) { + if (!renderer || !pixelFont) { + return; + } + + const SDL_Color gridBorderColor{60, 80, 160, 255}; // matches main grid outline + const SDL_Color bayColor{8, 12, 24, 235}; + const SDL_Color bayOutline{25, 62, 86, 220}; + const SDL_Color labelColor{255, 220, 0, 255}; + + SDL_FRect bayRect{panelX, panelY, panelW, panelH}; + SDL_SetRenderDrawColor(renderer, bayColor.r, bayColor.g, bayColor.b, bayColor.a); + SDL_RenderFillRect(renderer, &bayRect); + + SDL_FRect thinOutline{panelX - 1.0f, panelY - 1.0f, panelW + 2.0f, panelH + 2.0f}; + auto drawOutlineNoBottom = [&](const SDL_FRect& rect, SDL_Color color) { + SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a); + const float left = rect.x; + const float top = rect.y; + const float right = rect.x + rect.w; + const float bottom = rect.y + rect.h; + SDL_RenderLine(renderer, left, top, right, top); // top edge + SDL_RenderLine(renderer, left, top, left, bottom); // left edge + SDL_RenderLine(renderer, right, top, right, bottom); // right edge + }; + + drawOutlineNoBottom(thinOutline, gridBorderColor); + drawOutlineNoBottom(bayRect, bayOutline); + + const float labelPad = tileSize * 0.25f; + pixelFont->draw(renderer, panelX + labelPad, panelY + labelPad * 0.5f, "NEXT", 0.9f, labelColor); + + if (nextPiece.type >= PIECE_COUNT) { + return; + } + + // Determine the occupied bounds of the tetromino within its 4x4 local grid. + int minCx = 4; + int maxCx = -1; + int minCy = 4; + int maxCy = -1; + for (int cy = 0; cy < 4; ++cy) { + for (int cx = 0; cx < 4; ++cx) { + if (!Game::cellFilled(nextPiece, cx, cy)) { + continue; + } + minCx = std::min(minCx, cx); + maxCx = std::max(maxCx, cx); + minCy = std::min(minCy, cy); + maxCy = std::max(maxCy, cy); + } + } + + if (maxCx < minCx || maxCy < minCy) { + return; + } + + // Reserve a little headroom for the NEXT label, then center the piece in screen-space. + const float labelReserve = tileSize * 0.9f; + const float previewTop = panelY + std::min(labelReserve, panelH * 0.45f); + const float previewBottom = panelY + panelH - tileSize * 0.25f; + const float previewCenterY = (previewTop + previewBottom) * 0.5f; + const float previewCenterX = panelX + panelW * 0.5f; + + const float pieceWidth = static_cast(maxCx - minCx + 1) * tileSize; + const float pieceHeight = static_cast(maxCy - minCy + 1) * tileSize; + // Nudge preview slightly to the right so it aligns with the main grid's visual columns + const float previewNudgeX = tileSize * 0.5f; + const float startX = previewCenterX - pieceWidth * 0.5f - static_cast(minCx) * tileSize + previewNudgeX; + const float startY = previewCenterY - pieceHeight * 0.5f - static_cast(minCy) * tileSize; + + for (int cy = 0; cy < 4; ++cy) { + for (int cx = 0; cx < 4; ++cx) { + if (!Game::cellFilled(nextPiece, cx, cy)) { + continue; + } + const float px = startX + static_cast(cx) * tileSize; + const float py = startY + static_cast(cy) * tileSize; + drawBlockTexture(renderer, blocksTex, px, py, tileSize, nextPiece.type); + } + } +} + void GameRenderer::renderPlayingState( SDL_Renderer* renderer, Game* game, @@ -235,10 +328,10 @@ void GameRenderer::renderPlayingState( const float statsH = GRID_H; // Next piece preview position - const float nextW = finalBlockSize * 4 + 20; - const float nextH = finalBlockSize * 2 + 20; - const float nextX = gridX + (GRID_W - nextW) * 0.5f; - const float nextY = contentStartY + contentOffsetY; + const float NEXT_PANEL_WIDTH = finalBlockSize * 6.0f; // +1 cell padding on each horizontal side + const float NEXT_PANEL_HEIGHT = finalBlockSize * 3.0f; + const float NEXT_PANEL_X = gridX + (GRID_W - NEXT_PANEL_WIDTH) * 0.5f; + const float NEXT_PANEL_Y = gridY - NEXT_PANEL_HEIGHT - 2.0f; // nudge up ~2px // Handle line clearing effects if (game->hasCompletedLines() && lineEffect && !lineEffect->isActive()) { @@ -248,7 +341,17 @@ void GameRenderer::renderPlayingState( // Draw game grid border drawRectWithOffset(gridX - 3 - contentOffsetX, gridY - 3 - contentOffsetY, GRID_W + 6, GRID_H + 6, {100, 120, 200, 255}); - drawRectWithOffset(gridX - 1 - contentOffsetX, gridY - 1 - contentOffsetY, GRID_W + 2, GRID_H + 2, {60, 80, 160, 255}); + // Draw a 1px blue border around grid but omit the top horizontal so the NEXT panel + // can visually join seamlessly. We'll draw left, right and bottom bands manually. + { + SDL_Color blue{60, 80, 160, 255}; + // left vertical band (1px wide) + drawRectWithOffset(gridX - 1 - contentOffsetX, gridY - contentOffsetY, 1.0f, GRID_H, blue); + // right vertical band (1px wide) + drawRectWithOffset(gridX + GRID_W - contentOffsetX, gridY - contentOffsetY, 1.0f, GRID_H, blue); + // bottom horizontal band (1px high) + drawRectWithOffset(gridX - 1 - contentOffsetX, gridY + GRID_H - contentOffsetY, GRID_W + 2.0f, 1.0f, blue); + } drawRectWithOffset(gridX - contentOffsetX, gridY - contentOffsetY, GRID_W, GRID_H, {20, 25, 35, 255}); // Draw stats panel backdrop using the same art as the score panel @@ -442,10 +545,16 @@ void GameRenderer::renderPlayingState( } SDL_SetRenderDrawBlendMode(renderer, oldBlend); - - // Draw next piece preview panel border - drawRectWithOffset(nextX - 3 - contentOffsetX, nextY - 3 - contentOffsetY, nextW + 6, nextH + 6, {100, 120, 200, 255}); - drawRectWithOffset(nextX - contentOffsetX, nextY - contentOffsetY, nextW, nextH, {30, 35, 50, 255}); + + renderNextPanel(renderer, pixelFont, blocksTex, game->next(), NEXT_PANEL_X, NEXT_PANEL_Y, NEXT_PANEL_WIDTH, NEXT_PANEL_HEIGHT, finalBlockSize); + + // Draw a thin horizontal connector at the bottom of the NEXT panel so it visually + // connects to the top of the grid (appears as a single continuous frame). + SDL_SetRenderDrawColor(renderer, 60, 80, 160, 255); // same as grid border + float connectorY = NEXT_PANEL_Y + NEXT_PANEL_HEIGHT; // bottom of next panel (near grid top) + // Draw a 2px-high filled connector to overwrite any existing grid border pixels + SDL_FRect connRect{ NEXT_PANEL_X, connectorY - 1.0f, NEXT_PANEL_WIDTH, 2.0f }; + SDL_RenderFillRect(renderer, &connRect); // Precompute row drop offsets (line collapse effect) std::array rowDropOffsets{}; @@ -695,12 +804,6 @@ void GameRenderer::renderPlayingState( lineEffect->render(renderer, blocksTex, static_cast(gridX), static_cast(gridY), static_cast(finalBlockSize)); } - // Draw next piece preview - pixelFont->draw(renderer, nextX + 10, nextY - 20, "NEXT", 1.0f, {255, 220, 0, 255}); - if (game->next().type < PIECE_COUNT) { - drawSmallPiece(renderer, blocksTex, static_cast(game->next().type), nextX + 10, nextY + 10, finalBlockSize * 0.6f); - } - // Draw block statistics (left panel) const auto& blockCounts = game->getBlockCounts(); int totalBlocks = 0; diff --git a/src/graphics/renderers/GameRenderer.h b/src/graphics/renderers/GameRenderer.h index ad3287b..6d96a6a 100644 --- a/src/graphics/renderers/GameRenderer.h +++ b/src/graphics/renderers/GameRenderer.h @@ -53,6 +53,7 @@ private: static void drawBlockTexture(SDL_Renderer* renderer, SDL_Texture* blocksTex, float x, float y, float size, int blockType); static void drawPiece(SDL_Renderer* renderer, SDL_Texture* blocksTex, const Game::Piece& piece, float ox, float oy, float tileSize, bool isGhost = false, float pixelOffsetX = 0.0f, float pixelOffsetY = 0.0f); static void drawSmallPiece(SDL_Renderer* renderer, SDL_Texture* blocksTex, PieceType pieceType, float x, float y, float tileSize); + static void renderNextPanel(SDL_Renderer* renderer, FontAtlas* pixelFont, SDL_Texture* blocksTex, const Game::Piece& nextPiece, float panelX, float panelY, float panelW, float panelH, float tileSize); // Helper function for drawing rectangles static void drawRect(SDL_Renderer* renderer, float x, float y, float w, float h, SDL_Color c);