diff --git a/README.md b/README.md index 832ebe9..af31828 100644 --- a/README.md +++ b/README.md @@ -1 +1,16 @@ # Tetris written in SDL2 and C++ + +## TODO + +- Game Over screen +- Add Gamemodes + - Gamemode selection screen +- Save score + - Think of a better scoring system + - Save to a file +- Maybe Multiplayer + - Select 1 Player/2 Player in the title + - Peer2Peer or server based? + - Server based could also cloud save the savegame +- Cleanup code/Refractor +- Comment/Document stuff diff --git a/assets/BORDER.png b/assets/border.png similarity index 100% rename from assets/BORDER.png rename to assets/border.png diff --git a/assets/scoreboard.png b/assets/scoreboard.png new file mode 100644 index 0000000..9c250db Binary files /dev/null and b/assets/scoreboard.png differ diff --git a/assets/sound_effects/game_over.wav b/assets/sound_effects/game_over.wav new file mode 100644 index 0000000..8c9521b Binary files /dev/null and b/assets/sound_effects/game_over.wav differ diff --git a/assets/sound_effects/level_up.wav b/assets/sound_effects/level_up.wav new file mode 100644 index 0000000..73842ca Binary files /dev/null and b/assets/sound_effects/level_up.wav differ diff --git a/assets/sound_effects/line_clear.wav b/assets/sound_effects/line_clear.wav new file mode 100644 index 0000000..b12f7fa Binary files /dev/null and b/assets/sound_effects/line_clear.wav differ diff --git a/assets/sound_effects/menu.wav b/assets/sound_effects/menu.wav new file mode 100644 index 0000000..def5098 Binary files /dev/null and b/assets/sound_effects/menu.wav differ diff --git a/assets/sound_effects/move_piece.wav b/assets/sound_effects/move_piece.wav new file mode 100644 index 0000000..f42c5d2 Binary files /dev/null and b/assets/sound_effects/move_piece.wav differ diff --git a/assets/sound_effects/piece_falling_after_line_clear.wav b/assets/sound_effects/piece_falling_after_line_clear.wav new file mode 100644 index 0000000..b2c722c Binary files /dev/null and b/assets/sound_effects/piece_falling_after_line_clear.wav differ diff --git a/assets/sound_effects/piece_landed.wav b/assets/sound_effects/piece_landed.wav new file mode 100644 index 0000000..71317a9 Binary files /dev/null and b/assets/sound_effects/piece_landed.wav differ diff --git a/assets/sound_effects/player_sending_blocks.wav b/assets/sound_effects/player_sending_blocks.wav new file mode 100644 index 0000000..3694d17 Binary files /dev/null and b/assets/sound_effects/player_sending_blocks.wav differ diff --git a/assets/sound_effects/rocket_ending.wav b/assets/sound_effects/rocket_ending.wav new file mode 100644 index 0000000..f6f9639 Binary files /dev/null and b/assets/sound_effects/rocket_ending.wav differ diff --git a/assets/sound_effects/rotate_piece.wav b/assets/sound_effects/rotate_piece.wav new file mode 100644 index 0000000..2d0c0b0 Binary files /dev/null and b/assets/sound_effects/rotate_piece.wav differ diff --git a/assets/sound_effects/tetris_line_clear.wav b/assets/sound_effects/tetris_line_clear.wav new file mode 100644 index 0000000..e59c361 Binary files /dev/null and b/assets/sound_effects/tetris_line_clear.wav differ diff --git a/src/Game.cpp b/src/Game.cpp index 3488619..ee42b35 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -11,7 +11,7 @@ bool Game::init(const char* title, int w, int h) { title, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, w, h, - SDL_WINDOW_SHOWN | SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE + SDL_WINDOW_SHOWN | SDL_WINDOW_VULKAN )); if (!window) { @@ -19,8 +19,6 @@ bool Game::init(const char* title, int w, int h) { return false; } - SDL_SetWindowMinimumSize(window.get( ), 800, 650); - renderer = std::shared_ptr( SDL_CreateRenderer(window.get( ), -1, SDL_RENDERER_ACCELERATED), [ ](SDL_Renderer* r) { SDL_DestroyRenderer(r); } @@ -69,15 +67,24 @@ void Game::run( ) { gameOver = true; Mix_PauseMusic( ); + Mix_Chunk* rotateSound = Mix_LoadWAV("assets/sound_effects/game_over.wav"); + if (rotateSound == nullptr) + SDL_Log("Failed to play rotate sound effect: %s", Mix_GetError( )); + else { + Mix_PlayChannel(-1, rotateSound, 0); + } + while (gameOver) { if (quit) return; inputHandler( ); - gameRenderer->renderGameOver(gameBoard->getScore( ), gameBoard->getLevel( )); + render( ); + gameRenderer->renderGameOver(gameBoard->getScore( ), gameBoard->getLevel( ), gameBoard->getLines( )); } } void Game::inputHandler( ) { SDL_Event event; + Mix_Chunk* movePieceSound = Mix_LoadWAV("assets/sound_effects/move_piece.wav"); while (SDL_PollEvent(&event)) { if (event.type == SDL_QUIT) { SDL_Quit( ); @@ -87,10 +94,20 @@ void Game::inputHandler( ) { case SDLK_LEFT: case SDLK_a: gameBoard->tryMoveCurrentTetromino(-1, 0); + if (movePieceSound == nullptr) + SDL_Log("Failed to play rotate sound effect: %s", Mix_GetError( )); + else { + Mix_PlayChannel(-1, movePieceSound, 0); + } break; case SDLK_RIGHT: case SDLK_d: gameBoard->tryMoveCurrentTetromino(1, 0); + if (movePieceSound == nullptr) + SDL_Log("Failed to play rotate sound effect: %s", Mix_GetError( )); + else { + Mix_PlayChannel(-1, movePieceSound, 0); + } break; case SDLK_DOWN: case SDLK_s: @@ -102,8 +119,15 @@ void Game::inputHandler( ) { case SDLK_ESCAPE: break; case SDLK_g: - if (startSequence) + if (startSequence) { startSequence = false; + Mix_Chunk* menuSound = Mix_LoadWAV("assets/sound_effects/menu.wav"); + if (menuSound == nullptr) + SDL_Log("Failed to play rotate sound effect: %s", Mix_GetError( )); + else { + Mix_PlayChannel(-1, menuSound, 0); + } + } break; case SDLK_r: if (isGameOver( )) @@ -112,10 +136,38 @@ void Game::inputHandler( ) { case SDLK_q: SDL_Quit( ); quit = true; + case SDLK_EQUALS: + SDL_Log("Test %d", Mix_GetMusicVolume(bgm.get( ))); + Mix_VolumeMusic(Mix_GetMusicVolume(bgm.get( )) + 8); + break; + case SDLK_MINUS: + SDL_Log("Test %d", Mix_GetMusicVolume(bgm.get( ))); + + Mix_VolumeMusic(Mix_GetMusicVolume(bgm.get( )) - 8); + break; + case SDLK_m: + if (Mix_PausedMusic( )) + Mix_ResumeMusic( ); + else + Mix_PauseMusic( ); + + break; default: break; } } else if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_RESIZED) { + int TARGET_ASPECT_RATIO = 3 / 4; + int newHeight = event.window.data2, newWidth = event.window.data1; + + float newAspectRatio = static_cast(newWidth) / newHeight; + + if (newAspectRatio > TARGET_ASPECT_RATIO) + newWidth = static_cast(newHeight * TARGET_ASPECT_RATIO); + else + newHeight = static_cast(newWidth / TARGET_ASPECT_RATIO); + + SDL_SetWindowSize(window.get( ), newWidth, newHeight); + handleWindowResize( ); } } @@ -150,6 +202,7 @@ void Game::render( ) { SDL_RenderClear(renderer.get( )); gameRenderer->renderBoard(*gameBoard); + gameRenderer->renderTetrominoPreview(gameBoard->getNextTetromino( )); SDL_RenderPresent(renderer.get( )); } diff --git a/src/GameBoard.cpp b/src/GameBoard.cpp index 1669046..1ce0705 100644 --- a/src/GameBoard.cpp +++ b/src/GameBoard.cpp @@ -2,7 +2,7 @@ #include GameBoard::GameBoard( ) - : lockedTetrominos(20, vector(10, 0)), lockedColors(20, std::vector(10, { 0, 0, 0, 255 })), score(0), level(1), collision(false) { + : lockedTetrominos(20, vector(10, 0)), lockedColors(20, std::vector(10, { 0, 0, 0, 255 })), score(0), level(0), lines(0), collision(false) { spawnNewTetromino( ); } @@ -20,6 +20,14 @@ void GameBoard::tryRotateCurrentTetromino( ) { if (checkCollision(*currentTetromino)) for (int i = 0; i < 3; i++) currentTetromino->rotate(*this); + else { + Mix_Chunk* rotateSound = Mix_LoadWAV("assets/sound_effects/rotate_piece.wav"); + if (rotateSound == nullptr) + SDL_Log("Failed to play rotate sound effect: %s", Mix_GetError( )); + else { + Mix_PlayChannel(-1, rotateSound, 0); + } + } } bool GameBoard::checkCollision(const Tetromino& tetromino) const { @@ -97,9 +105,17 @@ void GameBoard::lockTetromino( ) { } } } + + Mix_Chunk* pieceLanded = Mix_LoadWAV("assets/sound_effects/piece_landed.wav"); + if (pieceLanded == nullptr) + SDL_Log("Failed to play sound effect: %s", Mix_GetError( )); + else { + Mix_PlayChannel(-1, pieceLanded, 0); + } } void GameBoard::clearLines( ) { + int clearedLines = 0; for (int row = 0; row < height; row++) { if (all_of(lockedTetrominos[row].begin( ), lockedTetrominos[row].end( ), [ ](int cell) { return cell != 0; })) { lockedTetrominos.erase(lockedTetrominos.begin( ) + row); @@ -111,20 +127,61 @@ void GameBoard::clearLines( ) { score += 100; if (score % 1000 == 0) { level++; + Mix_Chunk* levelUpSound = Mix_LoadWAV("assets/sound_effects/level_up.wav"); + if (levelUpSound == nullptr) + SDL_Log("Failed to play rotate sound effect: %s", Mix_GetError( )); + else { + Mix_PlayChannel(-1, levelUpSound, 0); + } } + clearedLines++; + lines++; + } + } + + if (clearedLines >= 4) { + Mix_Chunk* clearedLinesSound = Mix_LoadWAV("assets/sound_effects/tetris_line_clear.wav"); + if (clearedLinesSound == nullptr) + SDL_Log("Failed to play rotate sound effect: %s", Mix_GetError( )); + else { + Mix_PlayChannel(-1, clearedLinesSound, 0); + } + } else if (clearedLines > 0) { + Mix_Chunk* clearedLinesSound = Mix_LoadWAV("assets/sound_effects/line_clear.wav"); + if (clearedLinesSound == nullptr) + SDL_Log("Failed to play rotate sound effect: %s", Mix_GetError( )); + else { + Mix_PlayChannel(-1, clearedLinesSound, 0); } } } + void GameBoard::spawnNewTetromino( ) { + //Ensure on startup that we have a tetromino + if (!nextTetromino) { + random_device dev; + mt19937 rng(dev( )); + uniform_int_distribution dist6(0, static_cast(TetrominoShape::COUNT) - 1); + TetrominoShape shape = static_cast(dist6(rng)); + + nextTetromino = make_shared(shape); + } + + currentTetromino = move(nextTetromino); + currentTetromino->move(width / 2 - 1, 0); + + // Generate next tetromino random_device dev; mt19937 rng(dev( )); uniform_int_distribution dist6(0, static_cast(TetrominoShape::COUNT) - 1); TetrominoShape shape = static_cast(dist6(rng)); - currentTetromino = make_unique(shape); - currentTetromino->move(width / 2 - 1, 0); + nextTetromino = make_shared(shape); - if (checkCollision(*currentTetromino)) + if (checkCollision(*currentTetromino)) { collision = true; + lockedTetrominos.clear( ); + lockedColors.clear( ); + } } void GameBoard::update( ) { @@ -132,8 +189,7 @@ void GameBoard::update( ) { lockTetromino( ); clearLines( ); spawnNewTetromino( ); - } else - clearLines( ); + } } bool GameBoard::isValidPosition(const vector>& shape, int x, int y) const { @@ -156,6 +212,9 @@ void GameBoard::moveToBottom( ) { const bool GameBoard::isCollision( ) const { return collision; } const int GameBoard::getScore( ) const { return score; } const int GameBoard::getLevel( ) const { return level; } +const int GameBoard::getLines( ) const { return lines; } +const shared_ptr GameBoard::getNextTetromino( ) const { return nextTetromino; } + const vector>& GameBoard::getLockedTetrominos( ) const { return lockedTetrominos; } const vector>& GameBoard::getLockedColors( ) const { return lockedColors; } diff --git a/src/GameBoard.hpp b/src/GameBoard.hpp index 65e9802..c291e79 100644 --- a/src/GameBoard.hpp +++ b/src/GameBoard.hpp @@ -1,5 +1,9 @@ #pragma once +extern "C" { +#include +} + #include #include #include @@ -14,12 +18,14 @@ private: vector> lockedTetrominos; vector> lockedColors; - unique_ptr currentTetromino; + shared_ptr currentTetromino; + shared_ptr nextTetromino; const int width = 10; const int height = 20; bool collision; int score; int level; + int lines; public: GameBoard( ); @@ -33,6 +39,8 @@ public: const bool isCollision( ) const; const int getScore( ) const; const int getLevel( ) const; + const int getLines( ) const; + const shared_ptr getNextTetromino( ) const; const vector>& getLockedTetrominos( ) const; const vector>& getLockedColors( ) const; diff --git a/src/Renderer.cpp b/src/Renderer.cpp index b0d0778..3ee3718 100644 --- a/src/Renderer.cpp +++ b/src/Renderer.cpp @@ -28,13 +28,68 @@ void Renderer::renderBoard(const GameBoard& gameBoard) { drawGrid(gameBoard); drawLockedBlocks(gameBoard); drawTetromino(gameBoard.getCurrentTetromino( )); - drawScoreboard(gameBoard.getScore( ), gameBoard.getLevel( )); + drawScoreboard(gameBoard.getScore( ), gameBoard.getLevel( ), gameBoard.getLines( )); } -void Renderer::drawScoreboard(int score, int level) { +void Renderer::drawScoreboard(int score, int level, int lines) { + int gapSize = blockSize / 8; + + // 6 Because the gameBoard is 10 blocks, half that is 5 + 1 for the wall = 6 + int blackAreaWidth = (windowWidth / 2) - (blockSize * 6) - gapSize; + + SDL_Rect blackRect = { 0, 0, blackAreaWidth, windowHeight }; SDL_SetRenderDrawColor(renderer.get( ), 0, 0, 0, 255); - renderText(fmt::format("Score: {0}", score), 350, 100, 24, SDL_Color{ 200,128,200 }); - renderText(fmt::format("Level: {0}", level), 350, 200, 24, SDL_Color{ 128,200,200 }); + SDL_RenderFillRect(renderer.get( ), &blackRect); + + SDL_Surface* scoreboardSurface = IMG_Load("assets/scoreboard.png"); + if (!scoreboardSurface) { + SDL_Log("Failed to load title surface: %s", SDL_GetError( )); + return; + } + + auto scoreboardTexture = SDL_CreateTextureFromSurface(renderer.get( ), scoreboardSurface); + SDL_FreeSurface(scoreboardSurface); + if (!scoreboardTexture) { + SDL_Log("Failed to create title texture: %s", SDL_GetError( )); + return; + } + + int width, height; + SDL_QueryTexture(scoreboardTexture, nullptr, nullptr, &width, &height); + + int scoreboardWidth = static_cast(width * static_cast(windowHeight) / height); + + SDL_Rect scoreboardRect = { + windowWidth - scoreboardWidth, + 0, + scoreboardWidth, + windowHeight + }; + + + SDL_RenderCopy(renderer.get( ), scoreboardTexture, nullptr, &scoreboardRect); + SDL_DestroyTexture(scoreboardTexture); + + renderRightAlignedText(fmt::format("{0}", score), windowWidth - 30, 97, 32, SDL_Color{ 0,0,0 }); + renderText(fmt::format("{0}", level), windowWidth - 92, 230, 32, SDL_Color{ 0,0,0 }); + renderText(fmt::format("{0}", lines), windowWidth - 92, 330, 32, SDL_Color{ 0,0,0 }); +} + +void Renderer::renderRightAlignedText(const std::string& text, int x, int y, int fontSize, const SDL_Color& color) { + TTF_Font* font = TTF_OpenFont("assets/tetris-gb.ttf", fontSize); + if (!font) { + SDL_Log("Failed to open font: %s", TTF_GetError( )); + return; + } + + int textWidth; + TTF_SizeText(font, text.c_str( ), &textWidth, nullptr); + + int rightAlignedX = x - textWidth; + + renderText(text, rightAlignedX, y, fontSize, color); + + TTF_CloseFont(font); } void Renderer::drawGrid(const GameBoard& board) { @@ -47,6 +102,9 @@ void Renderer::drawGrid(const GameBoard& board) { int totalWidth = w * blockSize; int totalHeight = h * blockSize; + SDL_SetTextureBlendMode(textures[TetrisAssets::BORDER], SDL_BLENDMODE_BLEND); + SDL_SetTextureColorMod(textures[TetrisAssets::BORDER], 165, 42, 42); + for (int y = 0; y < (h + (innerBorderThickness * h)); ++y) { SDL_Rect leftBlock = { offsetX - blockSize, offsetY + y * blockSize, blockSize, blockSize }; renderTexture(TetrisAssets::BORDER, leftBlock.x, leftBlock.y - (innerBorderThickness * y + 1), leftBlock.w, leftBlock.h); @@ -130,33 +188,25 @@ void Renderer::drawTetromino(const Tetromino& tetromino) { SDL_SetTextureColorMod(textures[shapeToAsset(tetrominoShape)], color.r, color.g, color.b); if (tetrominoShape == TetrominoShape::I) { - SDL_SetTextureColorMod(textures[TetrisAssets::I_START], color.r, color.g, color.b); - SDL_SetTextureColorMod(textures[TetrisAssets::I_MID], color.r, color.g, color.b); - SDL_SetTextureColorMod(textures[TetrisAssets::I_END], color.r, color.g, color.b); - - if (angle == 90) { + if (angle == 90 || angle == 270) { + SDL_SetTextureColorMod(textures[TetrisAssets::I_STARTR], color.r, color.g, color.b); + SDL_SetTextureColorMod(textures[TetrisAssets::I_MIDR], color.r, color.g, color.b); + SDL_SetTextureColorMod(textures[TetrisAssets::I_ENDR], color.r, color.g, color.b); for (int i = 0; i < 4; ++i) { SDL_Rect destRect = { offsetX + (x + i) * blockSize, offsetY + y * blockSize, blockSize, blockSize }; - SDL_RenderCopyEx(renderer.get( ), textures[TetrisAssets::I_MID], nullptr, &destRect, angle, nullptr, SDL_FLIP_NONE); + SDL_RenderCopy(renderer.get( ), textures[TetrisAssets::I_MIDR], nullptr, &destRect); } SDL_Rect startRect = { offsetX + (x + 0) * blockSize, offsetY + y * blockSize, blockSize, blockSize }; - SDL_RenderCopyEx(renderer.get( ), textures[TetrisAssets::I_START], nullptr, &startRect, angle, nullptr, SDL_FLIP_NONE); + SDL_RenderCopy(renderer.get( ), textures[TetrisAssets::I_ENDR], nullptr, &startRect); SDL_Rect endRect = { offsetX + (x + 3) * blockSize, offsetY + y * blockSize, blockSize, blockSize }; - SDL_RenderCopyEx(renderer.get( ), textures[TetrisAssets::I_END], nullptr, &endRect, angle, nullptr, SDL_FLIP_NONE); - } else if (angle == 270) { - for (int i = 0; i < 4; ++i) { - SDL_Rect destRect = { offsetX + (x + i) * blockSize, offsetY + y * blockSize, blockSize, blockSize }; - SDL_RenderCopyEx(renderer.get( ), textures[TetrisAssets::I_MID], nullptr, &destRect, angle, nullptr, SDL_FLIP_NONE); - } - - SDL_Rect startRect = { offsetX + (x + 0) * blockSize, offsetY + y * blockSize, blockSize, blockSize }; - SDL_RenderCopyEx(renderer.get( ), textures[TetrisAssets::I_END], nullptr, &startRect, angle, nullptr, SDL_FLIP_NONE); - - SDL_Rect endRect = { offsetX + (x + 3) * blockSize, offsetY + y * blockSize, blockSize, blockSize }; - SDL_RenderCopyEx(renderer.get( ), textures[TetrisAssets::I_START], nullptr, &endRect, angle, nullptr, SDL_FLIP_NONE); + SDL_RenderCopy(renderer.get( ), textures[TetrisAssets::I_STARTR], nullptr, &endRect); } else { + SDL_SetTextureColorMod(textures[TetrisAssets::I_START], color.r, color.g, color.b); + SDL_SetTextureColorMod(textures[TetrisAssets::I_MID], color.r, color.g, color.b); + SDL_SetTextureColorMod(textures[TetrisAssets::I_END], color.r, color.g, color.b); + SDL_Rect destRect1 = { offsetX + (x + 0) * blockSize, offsetY + (y + 0) * blockSize, blockSize, blockSize }; SDL_Rect destRect2 = { offsetX + (x + 0) * blockSize, offsetY + (y + 1) * blockSize, blockSize, blockSize }; SDL_Rect destRect3 = { offsetX + (x + 0) * blockSize, offsetY + (y + 2) * blockSize, blockSize, blockSize }; @@ -274,25 +324,70 @@ void Renderer::renderStartScreen( ) { TTF_SizeText(font, "PRESS START", &w, &h); TTF_CloseFont(font); - renderText("press G to start", (windowWidth / 2) - (w / 2), windowHeight - 100, 16, SDL_Color{ 0, 0, 0 }); + renderText("press G to start", (windowWidth / 2) - (w / 2), windowHeight - 70, 16, SDL_Color{ 0, 0, 0 }); SDL_RenderPresent(renderer.get( )); } +void Renderer::renderGameOver(int score, int level, int lines) { + int fontSize = 24; + int lineSpacing = fontSize + 10; + int totalHeight = 5 * lineSpacing; -void Renderer::renderGameOver(int score, int level) { - SDL_SetRenderDrawColor(renderer.get( ), 0, 0, 0, 128); - SDL_RenderClear(renderer.get( )); - - renderText("Game Over", 100, 100, 128, SDL_Color{ 200, 200, 200 }); - renderText(fmt::format("Score: {0}", score), 100, 250, 64, SDL_Color{ 200, 128, 200 }); - renderText(fmt::format("Level: {0}", level), 100, 350, 64, SDL_Color{ 128, 200, 200 }); + int centerX = (windowWidth / 2); + int centerY = (windowHeight / 2) - (totalHeight / 2); + renderText("game", centerX, centerY, fontSize, SDL_Color{ 0, 0, 0 }); + renderText("over", centerX, centerY + lineSpacing, fontSize, SDL_Color{ 0, 0, 0 }); + renderText("please", centerX, centerY + 2 * lineSpacing, fontSize, SDL_Color{ 0, 0, 0 }); + renderText("try", centerX, centerY + 3 * lineSpacing, fontSize, SDL_Color{ 0, 0, 0 }); + renderText("again @", centerX, centerY + 4 * lineSpacing, fontSize, SDL_Color{ 0, 0, 0 }); SDL_RenderPresent(renderer.get( )); } +void Renderer::renderTetrominoPreview(const shared_ptr nextTetromino) { + const auto& shape = nextTetromino->getShape( ); + int x = nextTetromino->getX( ), y = nextTetromino->getY( ); + + double angle = nextTetromino->getRotationAngle( ); + + TetrominoShape tetrominoShape = nextTetromino->getShapeEnumn( ); + + SDL_SetTextureBlendMode(textures[shapeToAsset(tetrominoShape)], SDL_BLENDMODE_BLEND); + SDL_Color color = nextTetromino->getColor( ); + SDL_SetTextureColorMod(textures[shapeToAsset(tetrominoShape)], color.r, color.g, color.b); + + if (tetrominoShape == TetrominoShape::I) { + SDL_SetTextureColorMod(textures[TetrisAssets::I_STARTR], color.r, color.g, color.b); + SDL_SetTextureColorMod(textures[TetrisAssets::I_MIDR], color.r, color.g, color.b); + SDL_SetTextureColorMod(textures[TetrisAssets::I_ENDR], color.r, color.g, color.b); + + for (int i = 0; i < 4; ++i) { + SDL_Rect destRect = { (windowWidth - 155) + (x + i) * blockSize, (windowHeight - 120) + y * blockSize, blockSize, blockSize }; + SDL_RenderCopy(renderer.get( ), textures[TetrisAssets::I_MIDR], nullptr, &destRect); + } + + SDL_Rect startRect = { (windowWidth - 155) + (x + 0) * blockSize, (windowHeight - 120) + y * blockSize, blockSize, blockSize }; + SDL_RenderCopy(renderer.get( ), textures[TetrisAssets::I_ENDR], nullptr, &startRect); + + SDL_Rect endRect = { (windowWidth - 155) + (x + 3) * blockSize, (windowHeight - 120) + y * blockSize, blockSize, blockSize }; + SDL_RenderCopy(renderer.get( ), textures[TetrisAssets::I_STARTR], nullptr, &endRect); + + } else { + for (int row = 0; row < shape.size( ); ++row) { + for (int col = 0; col < shape[row].size( ); ++col) { + if (shape[row][col] != 0) { + SDL_Rect destRect = { (windowWidth - 140) + col * blockSize, (windowHeight - 130) + row * blockSize, blockSize, blockSize }; + SDL_Point center = { blockSize / 2, blockSize / 2 }; + SDL_RenderCopy(renderer.get( ), textures[shapeToAsset(tetrominoShape)], nullptr, &destRect); + } + } + } + } +} + void Renderer::renderText(string text, int x, int y, int size, SDL_Color color) { TTF_Font* font = TTF_OpenFont("assets/tetris-gb.ttf", size); SDL_Surface* surface = TTF_RenderText_Solid(font, text.c_str( ), color); diff --git a/src/Renderer.hpp b/src/Renderer.hpp index 79e62ba..06a48e7 100644 --- a/src/Renderer.hpp +++ b/src/Renderer.hpp @@ -35,7 +35,7 @@ private: void drawGrid(const GameBoard& gameBoard); void drawLockedBlocks(const GameBoard& gameBoard); void drawTetromino(const Tetromino& tetromino); - void drawScoreboard(int score, int level); + void drawScoreboard(int score, int level, int lines); TetrisAssets shapeToAsset(const TetrominoShape shape); @@ -58,8 +58,10 @@ public: bool loadTexture(const string& filePath, const TetrisAssets shape); void renderTexture(const TetrisAssets shape, int x, int y, int width, int height); void renderStartScreen( ); - void renderGameOver(int score, int level); + void renderGameOver(int score, int level, int lines); void renderText(string text, int x, int y, int size, SDL_Color color); + void renderRightAlignedText(const std::string& text, int x, int y, int fontSize, const SDL_Color& color); + void renderTetrominoPreview(const shared_ptr nextTetromino); const int getBlockSize( ) const; void setBlockSize(int newBlockSize); diff --git a/src/Tetromino.cpp b/src/Tetromino.cpp index 47f42b6..4cd86db 100644 --- a/src/Tetromino.cpp +++ b/src/Tetromino.cpp @@ -1,6 +1,6 @@ #include "Tetromino.hpp" #include "GameBoard.hpp" -#include + Tetromino::Tetromino(TetrominoShape shape) : x(0), y(0), textureShape(shape) { initializeShape(shape); currentRotationState = 1; diff --git a/src/main.cpp b/src/main.cpp index c193fca..8515f9e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -31,7 +31,7 @@ int main( ) { } Game game; - if (!game.init("Tetris", 800, 600)) { + if (!game.init("Tetris", 810, 600)) { SDL_Log("Failed to init game"); SDL_Quit( ); return 1;