some problems fixed

This commit is contained in:
2025-08-17 21:13:58 +02:00
parent d75bfcf4d0
commit b5ef9172b3
18 changed files with 1139 additions and 231 deletions

View File

@ -0,0 +1,480 @@
#include "GameRenderer.h"
#include "../gameplay/Game.h"
#include "../graphics/Font.h"
#include "../gameplay/LineEffect.h"
#include <algorithm>
#include <cmath>
#include <cstdio>
// Color constants (copied from main.cpp)
static const SDL_Color COLORS[] = {
{0, 0, 0, 255}, // 0: BLACK (empty)
{0, 255, 255, 255}, // 1: I-piece - cyan
{255, 255, 0, 255}, // 2: O-piece - yellow
{128, 0, 128, 255}, // 3: T-piece - purple
{0, 255, 0, 255}, // 4: S-piece - green
{255, 0, 0, 255}, // 5: Z-piece - red
{0, 0, 255, 255}, // 6: J-piece - blue
{255, 165, 0, 255} // 7: L-piece - orange
};
void GameRenderer::drawRect(SDL_Renderer* renderer, float x, float y, float w, float h, SDL_Color c) {
SDL_SetRenderDrawColor(renderer, c.r, c.g, c.b, c.a);
SDL_FRect fr{x, y, w, h};
SDL_RenderFillRect(renderer, &fr);
}
void GameRenderer::drawBlockTexture(SDL_Renderer* renderer, SDL_Texture* blocksTex, float x, float y, float size, int blockType) {
if (!blocksTex || blockType < 0 || blockType >= PIECE_COUNT) {
// Fallback to colored rectangle if texture isn't available
SDL_Color color = (blockType >= 0 && blockType < PIECE_COUNT) ? COLORS[blockType + 1] : SDL_Color{128, 128, 128, 255};
drawRect(renderer, x, y, size-1, size-1, color);
return;
}
// JavaScript uses: sx = type * spriteSize, sy = 0, with 2px padding
// Each sprite is 90px wide in the horizontal sprite sheet
const int SPRITE_SIZE = 90;
float srcX = blockType * SPRITE_SIZE + 2; // Add 2px padding like JS
float srcY = 2; // Add 2px padding from top like JS
float srcW = SPRITE_SIZE - 4; // Subtract 4px total padding like JS
float srcH = SPRITE_SIZE - 4; // Subtract 4px total padding like JS
SDL_FRect srcRect = {srcX, srcY, srcW, srcH};
SDL_FRect dstRect = {x, y, size, size};
SDL_RenderTexture(renderer, blocksTex, &srcRect, &dstRect);
}
void GameRenderer::drawPiece(SDL_Renderer* renderer, SDL_Texture* blocksTex, const Game::Piece& piece, float ox, float oy, float tileSize, bool isGhost) {
if (piece.type >= PIECE_COUNT) return;
for (int cy = 0; cy < 4; ++cy) {
for (int cx = 0; cx < 4; ++cx) {
if (Game::cellFilled(piece, cx, cy)) {
float px = ox + (piece.x + cx) * tileSize;
float py = oy + (piece.y + cy) * tileSize;
if (isGhost) {
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
// Draw ghost piece as barely visible gray outline
SDL_SetRenderDrawColor(renderer, 180, 180, 180, 20); // Very faint gray
SDL_FRect rect = {px + 2, py + 2, tileSize - 4, tileSize - 4};
SDL_RenderFillRect(renderer, &rect);
// Draw thin gray border
SDL_SetRenderDrawColor(renderer, 180, 180, 180, 30);
SDL_FRect border = {px + 1, py + 1, tileSize - 2, tileSize - 2};
SDL_RenderRect(renderer, &border);
} else {
drawBlockTexture(renderer, blocksTex, px, py, tileSize, piece.type);
}
}
}
}
}
void GameRenderer::drawSmallPiece(SDL_Renderer* renderer, SDL_Texture* blocksTex, PieceType pieceType, float x, float y, float tileSize) {
if (pieceType >= PIECE_COUNT) return;
// Use the first rotation (index 0) for preview
Game::Piece previewPiece;
previewPiece.type = pieceType;
previewPiece.rot = 0;
previewPiece.x = 0;
previewPiece.y = 0;
// Center the piece in the preview area
float offsetX = 0, offsetY = 0;
if (pieceType == 0) { offsetX = tileSize * 0.5f; } // I-piece centering (assuming I = 0)
else if (pieceType == 1) { offsetX = tileSize * 0.5f; } // O-piece centering (assuming O = 1)
// Use semi-transparent alpha for preview blocks
Uint8 previewAlpha = 180;
if (blocksTex) {
SDL_SetTextureAlphaMod(blocksTex, previewAlpha);
}
for (int cy = 0; cy < 4; ++cy) {
for (int cx = 0; cx < 4; ++cx) {
if (Game::cellFilled(previewPiece, cx, cy)) {
float px = x + offsetX + cx * tileSize;
float py = y + offsetY + cy * tileSize;
drawBlockTexture(renderer, blocksTex, px, py, tileSize, pieceType);
}
}
}
// Reset alpha
if (blocksTex) {
SDL_SetTextureAlphaMod(blocksTex, 255);
}
}
void GameRenderer::renderPlayingState(
SDL_Renderer* renderer,
Game* game,
FontAtlas* pixelFont,
LineEffect* lineEffect,
SDL_Texture* blocksTex,
float logicalW,
float logicalH,
float logicalScale,
float winW,
float winH,
bool showExitConfirmPopup
) {
if (!game || !pixelFont) return;
// Calculate actual content area (centered within the window)
float contentScale = logicalScale;
float contentW = logicalW * contentScale;
float contentH = logicalH * contentScale;
float contentOffsetX = (winW - contentW) * 0.5f / contentScale;
float contentOffsetY = (winH - contentH) * 0.5f / contentScale;
// Helper lambda for drawing rectangles with content offset
auto drawRectWithOffset = [&](float x, float y, float w, float h, SDL_Color c) {
SDL_SetRenderDrawColor(renderer, c.r, c.g, c.b, c.a);
SDL_FRect fr{x + contentOffsetX, y + contentOffsetY, w, h};
SDL_RenderFillRect(renderer, &fr);
};
// Responsive layout that scales with window size while maintaining margins
const float MIN_MARGIN = 40.0f;
const float TOP_MARGIN = 60.0f;
const float PANEL_WIDTH = 180.0f;
const float PANEL_SPACING = 30.0f;
const float NEXT_PIECE_HEIGHT = 120.0f;
const float BOTTOM_MARGIN = 60.0f;
// Calculate layout dimensions
const float availableWidth = logicalW - (MIN_MARGIN * 2) - (PANEL_WIDTH * 2) - (PANEL_SPACING * 2);
const float availableHeight = logicalH - TOP_MARGIN - BOTTOM_MARGIN - NEXT_PIECE_HEIGHT;
const float maxBlockSizeW = availableWidth / Game::COLS;
const float maxBlockSizeH = availableHeight / Game::ROWS;
const float BLOCK_SIZE = std::min(maxBlockSizeW, maxBlockSizeH);
const float finalBlockSize = std::max(20.0f, std::min(BLOCK_SIZE, 40.0f));
const float GRID_W = Game::COLS * finalBlockSize;
const float GRID_H = Game::ROWS * finalBlockSize;
// Calculate positions
const float totalContentHeight = NEXT_PIECE_HEIGHT + GRID_H;
const float availableVerticalSpace = logicalH - TOP_MARGIN - BOTTOM_MARGIN;
const float verticalCenterOffset = (availableVerticalSpace - totalContentHeight) * 0.5f;
const float contentStartY = TOP_MARGIN + verticalCenterOffset;
const float totalLayoutWidth = PANEL_WIDTH + PANEL_SPACING + GRID_W + PANEL_SPACING + PANEL_WIDTH;
const float layoutStartX = (logicalW - totalLayoutWidth) * 0.5f;
const float statsX = layoutStartX + contentOffsetX;
const float gridX = layoutStartX + PANEL_WIDTH + PANEL_SPACING + contentOffsetX;
const float scoreX = layoutStartX + PANEL_WIDTH + PANEL_SPACING + GRID_W + PANEL_SPACING + contentOffsetX;
const float gridY = contentStartY + NEXT_PIECE_HEIGHT + contentOffsetY;
const float statsY = gridY;
const float statsW = PANEL_WIDTH;
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;
// Handle line clearing effects
if (game->hasCompletedLines() && lineEffect && !lineEffect->isActive()) {
auto completedLines = game->getCompletedLines();
lineEffect->startLineClear(completedLines, static_cast<int>(gridX), static_cast<int>(gridY), static_cast<int>(finalBlockSize));
}
// 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});
drawRectWithOffset(gridX - contentOffsetX, gridY - contentOffsetY, GRID_W, GRID_H, {20, 25, 35, 255});
// Draw panel backgrounds
SDL_SetRenderDrawColor(renderer, 10, 15, 25, 160);
SDL_FRect lbg{statsX - 16, gridY - 10, statsW + 32, GRID_H + 20};
SDL_RenderFillRect(renderer, &lbg);
SDL_FRect rbg{scoreX - 16, gridY - 16, statsW + 32, GRID_H + 32};
SDL_RenderFillRect(renderer, &rbg);
// Draw grid lines
SDL_SetRenderDrawColor(renderer, 40, 45, 60, 255);
for (int x = 1; x < Game::COLS; ++x) {
float lineX = gridX + x * finalBlockSize;
SDL_RenderLine(renderer, lineX, gridY, lineX, gridY + GRID_H);
}
for (int y = 1; y < Game::ROWS; ++y) {
float lineY = gridY + y * finalBlockSize;
SDL_RenderLine(renderer, gridX, lineY, gridX + GRID_W, lineY);
}
// Draw block statistics panel border
drawRectWithOffset(statsX - 3 - contentOffsetX, statsY - 3 - contentOffsetY, statsW + 6, statsH + 6, {100, 120, 200, 255});
drawRectWithOffset(statsX - contentOffsetX, statsY - contentOffsetY, statsW, statsH, {30, 35, 50, 255});
// 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});
// Draw the game board
const auto &board = game->boardRef();
for (int y = 0; y < Game::ROWS; ++y) {
for (int x = 0; x < Game::COLS; ++x) {
int v = board[y * Game::COLS + x];
if (v > 0) {
float bx = gridX + x * finalBlockSize;
float by = gridY + y * finalBlockSize;
drawBlockTexture(renderer, blocksTex, bx, by, finalBlockSize, v - 1);
}
}
}
// Draw ghost piece (where current piece will land)
if (!game->isPaused()) {
Game::Piece ghostPiece = game->current();
// Find landing position
while (true) {
Game::Piece testPiece = ghostPiece;
testPiece.y++;
bool collision = false;
// Simple collision check
for (int cy = 0; cy < 4; ++cy) {
for (int cx = 0; cx < 4; ++cx) {
if (Game::cellFilled(testPiece, cx, cy)) {
int gx = testPiece.x + cx;
int gy = testPiece.y + cy;
if (gy >= Game::ROWS || gx < 0 || gx >= Game::COLS ||
(gy >= 0 && board[gy * Game::COLS + gx] != 0)) {
collision = true;
break;
}
}
}
if (collision) break;
}
if (collision) break;
ghostPiece = testPiece;
}
// Draw ghost piece
drawPiece(renderer, blocksTex, ghostPiece, gridX, gridY, finalBlockSize, true);
}
// Draw the falling piece
if (!game->isPaused()) {
drawPiece(renderer, blocksTex, game->current(), gridX, gridY, finalBlockSize, false);
}
// Draw line clearing effects
if (lineEffect && lineEffect->isActive()) {
lineEffect->render(renderer, static_cast<int>(gridX), static_cast<int>(gridY), static_cast<int>(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<PieceType>(game->next().type), nextX + 10, nextY + 10, finalBlockSize * 0.6f);
}
// Draw block statistics (left panel)
pixelFont->draw(renderer, statsX + 10, statsY + 10, "BLOCKS", 1.0f, {255, 220, 0, 255});
const auto& blockCounts = game->getBlockCounts();
int totalBlocks = 0;
for (int i = 0; i < PIECE_COUNT; ++i) totalBlocks += blockCounts[i];
const char* pieceNames[] = {"I", "O", "T", "S", "Z", "J", "L"};
float yCursor = statsY + 52;
for (int i = 0; i < PIECE_COUNT; ++i) {
float py = yCursor;
// Draw small piece icon
float previewSize = finalBlockSize * 0.55f;
drawSmallPiece(renderer, blocksTex, static_cast<PieceType>(i), statsX + 18, py, previewSize);
// Compute preview height
int maxCy = -1;
Game::Piece prev;
prev.type = static_cast<PieceType>(i);
prev.rot = 0;
prev.x = 0;
prev.y = 0;
for (int cy = 0; cy < 4; ++cy) {
for (int cx = 0; cx < 4; ++cx) {
if (Game::cellFilled(prev, cx, cy)) maxCy = std::max(maxCy, cy);
}
}
int tilesHigh = (maxCy >= 0 ? maxCy + 1 : 1);
float previewHeight = tilesHigh * previewSize;
// Count display
int count = blockCounts[i];
char countStr[16];
snprintf(countStr, sizeof(countStr), "%d", count);
pixelFont->draw(renderer, statsX + statsW - 20, py + 6, countStr, 1.1f, {240, 240, 245, 255});
// Percentage bar
int perc = (totalBlocks > 0) ? int(std::round(100.0 * double(count) / double(totalBlocks))) : 0;
char percStr[16];
snprintf(percStr, sizeof(percStr), "%d%%", perc);
float barX = statsX + 12;
float barY = py + previewHeight + 18.0f;
float barW = statsW - 24;
float barH = 6;
pixelFont->draw(renderer, barX, barY - 16, percStr, 0.8f, {230, 230, 235, 255});
// Progress bar
SDL_SetRenderDrawColor(renderer, 170, 170, 175, 200);
SDL_FRect track{barX, barY, barW, barH};
SDL_RenderFillRect(renderer, &track);
SDL_Color pc = COLORS[i + 1];
SDL_SetRenderDrawColor(renderer, pc.r, pc.g, pc.b, 230);
float fillW = barW * (perc / 100.0f);
if (fillW < 0) fillW = 0;
if (fillW > barW) fillW = barW;
SDL_FRect fill{barX, barY, fillW, barH};
SDL_RenderFillRect(renderer, &fill);
yCursor = barY + barH + 18.0f;
}
// Draw score panel (right side)
const float contentTopOffset = 0.0f;
const float contentBottomOffset = 290.0f;
const float contentPad = 36.0f;
float scoreContentH = (contentBottomOffset - contentTopOffset) + contentPad;
float baseY = gridY + (GRID_H - scoreContentH) * 0.5f;
pixelFont->draw(renderer, scoreX, baseY + 0, "SCORE", 1.0f, {255, 220, 0, 255});
char scoreStr[32];
snprintf(scoreStr, sizeof(scoreStr), "%d", game->score());
pixelFont->draw(renderer, scoreX, baseY + 25, scoreStr, 0.9f, {255, 255, 255, 255});
pixelFont->draw(renderer, scoreX, baseY + 70, "LINES", 1.0f, {255, 220, 0, 255});
char linesStr[16];
snprintf(linesStr, sizeof(linesStr), "%03d", game->lines());
pixelFont->draw(renderer, scoreX, baseY + 95, linesStr, 0.9f, {255, 255, 255, 255});
pixelFont->draw(renderer, scoreX, baseY + 140, "LEVEL", 1.0f, {255, 220, 0, 255});
char levelStr[16];
snprintf(levelStr, sizeof(levelStr), "%02d", game->level());
pixelFont->draw(renderer, scoreX, baseY + 165, levelStr, 0.9f, {255, 255, 255, 255});
// Next level progress
int startLv = game->startLevelBase();
int firstThreshold = (startLv + 1) * 10;
int linesDone = game->lines();
int nextThreshold = 0;
if (linesDone < firstThreshold) {
nextThreshold = firstThreshold;
} else {
int blocksPast = linesDone - firstThreshold;
nextThreshold = firstThreshold + ((blocksPast / 10) + 1) * 10;
}
int linesForNext = std::max(0, nextThreshold - linesDone);
pixelFont->draw(renderer, scoreX, baseY + 200, "NEXT LVL", 1.0f, {255, 220, 0, 255});
char nextStr[32];
snprintf(nextStr, sizeof(nextStr), "%d LINES", linesForNext);
pixelFont->draw(renderer, scoreX, baseY + 225, nextStr, 0.9f, {80, 255, 120, 255});
// Time display
pixelFont->draw(renderer, scoreX, baseY + 265, "TIME", 1.0f, {255, 220, 0, 255});
int totalSecs = static_cast<int>(game->elapsed());
int mins = totalSecs / 60;
int secs = totalSecs % 60;
char timeStr[16];
snprintf(timeStr, sizeof(timeStr), "%02d:%02d", mins, secs);
pixelFont->draw(renderer, scoreX, baseY + 290, timeStr, 0.9f, {255, 255, 255, 255});
// Gravity HUD
char gms[64];
double gms_val = game->getGravityMs();
double gfps = gms_val > 0.0 ? (1000.0 / gms_val) : 0.0;
snprintf(gms, sizeof(gms), "GRAV: %.0f ms (%.2f fps)", gms_val, gfps);
pixelFont->draw(renderer, logicalW - 260, 10, gms, 0.9f, {200, 200, 220, 255});
// Hold piece (if implemented)
if (game->held().type < PIECE_COUNT) {
pixelFont->draw(renderer, statsX + 10, statsY + statsH - 80, "HOLD", 1.0f, {255, 220, 0, 255});
drawSmallPiece(renderer, blocksTex, static_cast<PieceType>(game->held().type), statsX + 60, statsY + statsH - 80, finalBlockSize * 0.6f);
}
// Pause overlay
if (game->isPaused() && !showExitConfirmPopup) {
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 180);
SDL_FRect pauseOverlay{0, 0, logicalW, logicalH};
SDL_RenderFillRect(renderer, &pauseOverlay);
pixelFont->draw(renderer, logicalW * 0.5f - 80, logicalH * 0.5f - 20, "PAUSED", 2.0f, {255, 255, 255, 255});
pixelFont->draw(renderer, logicalW * 0.5f - 120, logicalH * 0.5f + 30, "Press P to resume", 0.8f, {200, 200, 220, 255});
}
// Exit confirmation popup
if (showExitConfirmPopup) {
float popupW = 420.0f, popupH = 180.0f;
float popupX = (logicalW - popupW) * 0.5f;
float popupY = (logicalH - popupH) * 0.5f;
// Dim entire window (do not change viewport/scales here)
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 200);
SDL_FRect fullWin{0.f, 0.f, winW, winH};
SDL_RenderFillRect(renderer, &fullWin);
// Draw popup box in logical coords with content offsets
drawRectWithOffset(popupX - 4.0f, popupY - 4.0f, popupW + 8.0f, popupH + 8.0f, {60, 70, 90, 255});
drawRectWithOffset(popupX, popupY, popupW, popupH, {20, 22, 28, 240});
// Text content (measure to perfectly center)
const std::string title = "Exit game?";
const std::string line1 = "Are you sure you want to";
const std::string line2 = "leave the current game?";
int wTitle=0,hTitle=0; pixelFont->measure(title, 1.6f, wTitle, hTitle);
int wL1=0,hL1=0; pixelFont->measure(line1, 0.9f, wL1, hL1);
int wL2=0,hL2=0; pixelFont->measure(line2, 0.9f, wL2, hL2);
float titleX = popupX + (popupW - (float)wTitle) * 0.5f + contentOffsetX;
float l1X = popupX + (popupW - (float)wL1) * 0.5f + contentOffsetX;
float l2X = popupX + (popupW - (float)wL2) * 0.5f + contentOffsetX;
pixelFont->draw(renderer, titleX, popupY + contentOffsetY + 20.0f, title, 1.6f, {255, 220, 0, 255});
pixelFont->draw(renderer, l1X, popupY + contentOffsetY + 60.0f, line1, 0.9f, {220, 220, 230, 255});
pixelFont->draw(renderer, l2X, popupY + contentOffsetY + 84.0f, line2, 0.9f, {220, 220, 230, 255});
// Buttons
float btnW = 140.0f, btnH = 46.0f;
float yesX = popupX + popupW * 0.25f - btnW * 0.5f;
float noX = popupX + popupW * 0.75f - btnW * 0.5f;
float btnY = popupY + popupH - 60.0f;
// YES button
drawRectWithOffset(yesX - 2.0f, btnY - 2.0f, btnW + 4.0f, btnH + 4.0f, {100, 120, 140, 255});
drawRectWithOffset(yesX, btnY, btnW, btnH, {200, 60, 60, 255});
const std::string yes = "YES";
int wYes=0,hYes=0; pixelFont->measure(yes, 1.0f, wYes, hYes);
pixelFont->draw(renderer, yesX + (btnW - (float)wYes) * 0.5f + contentOffsetX,
btnY + (btnH - (float)hYes) * 0.5f + contentOffsetY,
yes, 1.0f, {255, 255, 255, 255});
// NO button
drawRectWithOffset(noX - 2.0f, btnY - 2.0f, btnW + 4.0f, btnH + 4.0f, {100, 120, 140, 255});
drawRectWithOffset(noX, btnY, btnW, btnH, {80, 140, 80, 255});
const std::string no = "NO";
int wNo=0,hNo=0; pixelFont->measure(no, 1.0f, wNo, hNo);
pixelFont->draw(renderer, noX + (btnW - (float)wNo) * 0.5f + contentOffsetX,
btnY + (btnH - (float)hNo) * 0.5f + contentOffsetY,
no, 1.0f, {255, 255, 255, 255});
}
}