commit 730df043f7b06f8e76435e1eea7eadda561d818e Author: Rene Kievits Date: Mon Oct 14 04:28:43 2024 +0200 one should be in the correct folder when initializing the git project... diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6f31401 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build/ +.vscode/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..c3c527a --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,38 @@ +cmake_minimum_required(VERSION 3.5) + +project(SDL_TD VERSION 0.1 LANGUAGES CXX) + +find_package(SDL2 QUIET) + +set(SDL_INCLUDE_DIRS ${SDL2_INCLUDE_DIRS}) +set(SDL_LIBRARIES ${SDL2_LIBRARIES}) + +find_library(SDL_MIXER_LIBRARY NAMES SDL2_mixer) +find_library(SDL_IMAGE_LIBRARY NAMES SDL2_image) +find_library(SDL_TTF_LIBRARY NAMES SDL2_ttf) + +include_directories(${SDL_INCLUDE_DIRS}) + +file(GLOB_RECURSE PROJECT_SOURCES src/*.cpp) +file(GLOB_RECURSE PROJECT_HEADERS src/*.hpp) + +add_executable(SDL_TD + ${PROJECT_SOURCES} + ${PROJECT_HEADERS} +) + +target_link_libraries(SDL_TD + ${SDL_LIBRARIES} + ${SDL_MIXER_LIBRARY} + ${SDL_IMAGE_LIBRARY} + ${SDL_TTF_LIBRARY} + fmt +) + +file(GLOB ASSETS + "assets/*.png" +) + +foreach(ASSET ${ASSETS}) + file(COPY ${ASSET} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/assets) +endforeach() diff --git a/README.md b/README.md new file mode 100644 index 0000000..bb04e26 --- /dev/null +++ b/README.md @@ -0,0 +1,14 @@ +# TODO + +## Needs + +- Input handler +- Collision +- Playing Field +- Scoreboard + +## Optional + +- Start Page +- Textures +- Audio diff --git a/assets/I.png b/assets/I.png new file mode 100644 index 0000000..ab42d1c Binary files /dev/null and b/assets/I.png differ diff --git a/assets/J.png b/assets/J.png new file mode 100644 index 0000000..70aa092 Binary files /dev/null and b/assets/J.png differ diff --git a/assets/L.png b/assets/L.png new file mode 100644 index 0000000..9edaaec Binary files /dev/null and b/assets/L.png differ diff --git a/assets/O.png b/assets/O.png new file mode 100644 index 0000000..1258fe2 Binary files /dev/null and b/assets/O.png differ diff --git a/assets/S.png b/assets/S.png new file mode 100644 index 0000000..152fa39 Binary files /dev/null and b/assets/S.png differ diff --git a/assets/Z.png b/assets/Z.png new file mode 100644 index 0000000..09db90e Binary files /dev/null and b/assets/Z.png differ diff --git a/assets/bgm.mp3 b/assets/bgm.mp3 new file mode 100644 index 0000000..54cc595 Binary files /dev/null and b/assets/bgm.mp3 differ diff --git a/assets/single.png b/assets/single.png new file mode 100644 index 0000000..e2bda38 Binary files /dev/null and b/assets/single.png differ diff --git a/src/Game.cpp b/src/Game.cpp new file mode 100644 index 0000000..bd24426 --- /dev/null +++ b/src/Game.cpp @@ -0,0 +1,142 @@ +#include "Game.hpp" + +Game::Game( ) : + window(nullptr, SDL_DestroyWindow), + gameOver(false), + startSequence(true), + quit(false) { } + +bool Game::init(const char* title, int w, int h) { + window.reset(SDL_CreateWindow( + title, + SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, + w, h, + SDL_WINDOW_SHOWN | SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE + )); + + if (!window) { + SDL_Log("Failed to create window: %s", SDL_GetError( )); + return false; + } + + renderer = std::shared_ptr( + SDL_CreateRenderer(window.get( ), -1, SDL_RENDERER_ACCELERATED), + [ ](SDL_Renderer* r) { SDL_DestroyRenderer(r); } + ); + + if (!renderer) { + SDL_Log("Failed to create renderer: %s", SDL_GetError( )); + return false; + } + + gameRenderer = make_shared(renderer); + gameBoard = make_shared( ); + + bgm = std::shared_ptr(Mix_LoadMUS("/home/crylia/Dokumente/git/tetris_sdl/assets/bgm.mp3"), [ ](Mix_Music* m) { + Mix_FreeMusic(m); + }); + + if (!bgm) { + SDL_Log("Failed to load background music: %s", Mix_GetError( )); + return false; + } + + return true; +} + +void Game::run( ) { + while (startSequence) { + if (quit) return; + inputHandler( ); + gameRenderer->renderStartScreen( ); + } + + Mix_PlayMusic(bgm.get( ), -1); + + lastUpdateTime = SDL_GetTicks( ); + while (!gameOver && !gameBoard->isCollision( )) { + if (quit) return; + inputHandler( ); + update( ); + render( ); + } + gameOver = true; + Mix_PauseMusic( ); + + while (gameOver) { + if (quit) return; + inputHandler( ); + gameRenderer->renderGameOver(gameBoard->getScore( ), gameBoard->getLevel( )); + } +} + +void Game::inputHandler( ) { + SDL_Event event; + while (SDL_PollEvent(&event)) { + if (event.type == SDL_QUIT) { SDL_Quit( ); } else if (event.type == SDL_KEYDOWN) { + switch (event.key.keysym.sym) { + case SDLK_LEFT: + case SDLK_a: + gameBoard->tryMoveCurrentTetromino(-1, 0); + break; + case SDLK_RIGHT: + case SDLK_d: + gameBoard->tryMoveCurrentTetromino(1, 0); + break; + case SDLK_DOWN: + case SDLK_s: + gameBoard->moveToBottom( ); + break; + case SDLK_SPACE: + gameBoard->tryRotateCurrentTetromino( ); + break; + case SDLK_ESCAPE: + break; + case SDLK_g: + if (startSequence) + startSequence = false; + break; + case SDLK_r: + if (isGameOver( )) + restart( ); + break; + case SDLK_q: + SDL_Quit( ); + quit = true; + default: + break; + } + } + } +} + +void Game::update( ) { + Uint32 currentTime = SDL_GetTicks( ); + Uint32 deltaTime = currentTime - lastUpdateTime; + + if (deltaTime >= max(50, 1000 - (gameBoard->getLevel( ) * 100))) { + gameBoard->update( ); + lastUpdateTime = currentTime; + } +} + +void Game::render( ) { + //Set default color to black + SDL_SetRenderDrawColor(renderer.get( ), 20, 20, 20, 255); + SDL_RenderClear(renderer.get( )); + + gameRenderer->renderBoard(*gameBoard); + + SDL_RenderPresent(renderer.get( )); +} + +bool Game::isGameOver( ) { return gameOver; } +void Game::setGameOver(bool value) { gameOver = value; } + +void Game::restart( ) { + gameOver = false; + startSequence = true; + gameBoard = make_shared( ); +} + +const bool Game::isGameQuit( )const { return quit; } diff --git a/src/Game.hpp b/src/Game.hpp new file mode 100644 index 0000000..50d3cdd --- /dev/null +++ b/src/Game.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include + +extern "C" { +#include +#include +} + +#include "Renderer.hpp" +#include "GameBoard.hpp" + +using namespace std; + +class Game { +private: + void update( ); + void render( ); + void inputHandler( ); + + unique_ptr window; + shared_ptr renderer; + + shared_ptr gameBoard; + shared_ptr gameRenderer; + + shared_ptr bgm; + + Uint32 lastUpdateTime = 0; + int dropInterval = 1000; + + bool gameOver; + bool startSequence; + bool quit; + +public: + Game( ); + + bool init(const char* title, int w, int h); + void run( ); + void restart( ); + bool isGameOver( ); + void setGameOver(bool value); + + const bool isGameQuit( ) const; +}; diff --git a/src/GameBoard.cpp b/src/GameBoard.cpp new file mode 100644 index 0000000..f4ee67b --- /dev/null +++ b/src/GameBoard.cpp @@ -0,0 +1,120 @@ +#include "GameBoard.hpp" +#include + +GameBoard::GameBoard( ) + : lockedTetrominos(20, vector(10, 0)), lockedColors(20, std::vector(10, { 0, 0, 0, 0 })), score(0), level(1), collision(false) { + spawnNewTetromino( ); +} + +bool GameBoard::tryMoveCurrentTetromino(int dx, int dy) { + currentTetromino->move(dx, dy); + if (checkCollision(*currentTetromino)) { + currentTetromino->move(-dx, -dy); + return false; + } + return true; +} + +void GameBoard::tryRotateCurrentTetromino( ) { + currentTetromino->rotate(*this); + if (checkCollision(*currentTetromino)) + for (int i = 0; i < 3; i++) + currentTetromino->rotate(*this); +} + +bool GameBoard::checkCollision(const Tetromino& tetromino) const { + const auto& shape = tetromino.getShape( ); + int x = tetromino.getX( ), y = tetromino.getY( ); + + for (int row = 0; row < shape.size( ); ++row) + for (int col = 0; col < shape[row].size( ); ++col) + if (shape[row][col] != 0) { + int newX = x + col; + int newY = y + row; + + if (newX < 0 || newX >= width || newY >= height || (newY >= 0 && lockedTetrominos[newY][newX] != 0)) { return true; } + } + + return false; +} + +void GameBoard::lockTetromino( ) { + const auto& shape = currentTetromino->getShape( ); + int x = currentTetromino->getX( ), y = currentTetromino->getY( ); + + for (int row = 0; row < shape.size( ); ++row) + for (int col = 0; col < shape[row].size( ); ++col) + if (shape[row][col] != 0) { + int lockedTetrominosX = x + col; + int lockedTetrominosY = y + row; + if (lockedTetrominosY >= 0 && lockedTetrominosX < height) { + lockedTetrominos[lockedTetrominosY][lockedTetrominosX] = 1; + lockedColors[lockedTetrominosY][lockedTetrominosX] = currentTetromino->getColor( ); + } + } +} + +void GameBoard::clearLines( ) { + 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); + lockedTetrominos.insert(lockedTetrominos.begin( ), vector(width, 0)); + score += 100; + if ((score % 1000) == 0) + level++; + } +} + +void GameBoard::spawnNewTetromino( ) { + 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->setTexture(shape); + currentTetromino->move(width / 2 - 1, 0); + + if (checkCollision(*currentTetromino)) + collision = true; +} + +void GameBoard::update( ) { + if (!tryMoveCurrentTetromino(0, 1)) { + lockTetromino( ); + clearLines( ); + spawnNewTetromino( ); + } else + clearLines( ); +} + +bool GameBoard::isValidPosition(const vector>& shape, int x, int y) const { + for (int row = 0; row < shape.size( ); ++row) + for (int col = 0; col < shape[row].size( ); ++col) + if (shape[row][col]) { + int newX = x + col; + int newY = y + row; + + if (newX < 0 || newX >= width || newY < 0 || newY >= height) return false; + if (lockedTetrominos[newY][newX]) return false; + } + return true; +} + +void GameBoard::moveToBottom( ) { + while (isValidPosition(currentTetromino->getShape( ), currentTetromino->getX( ), currentTetromino->getY( ) + 1)) currentTetromino->move(0, 1); +} + +const bool GameBoard::isCollision( ) const { return collision; } +const int GameBoard::getScore( ) const { return score; } +const int GameBoard::getLevel( ) const { return level; } + +const vector>& GameBoard::getLockedTetrominos( ) const { return lockedTetrominos; } +const vector>& GameBoard::getLockedColors( ) const { return lockedColors; } +const Tetromino& GameBoard::getCurrentTetromino( ) const { return *currentTetromino; } + +void GameBoard::reset( ) { + lockedTetrominos.clear( ); + lockedColors.clear( ); + score = 0; + level = 1; +} diff --git a/src/GameBoard.hpp b/src/GameBoard.hpp new file mode 100644 index 0000000..65e9802 --- /dev/null +++ b/src/GameBoard.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include +#include "Tetromino.hpp" + +class GameBoard { +private: + void spawnNewTetromino( ); + bool checkCollision(const Tetromino& tetromino) const; + void lockTetromino( ); + void clearLines( ); + + vector> lockedTetrominos; + vector> lockedColors; + unique_ptr currentTetromino; + const int width = 10; + const int height = 20; + bool collision; + int score; + int level; + +public: + GameBoard( ); + void update( ); + bool tryMoveCurrentTetromino(int dx, int dy); + void tryRotateCurrentTetromino( ); + bool isValidPosition(const vector>& shape, int x, int y) const; + void moveToBottom( ); + void reset( ); + + const bool isCollision( ) const; + const int getScore( ) const; + const int getLevel( ) const; + + const vector>& getLockedTetrominos( ) const; + const vector>& getLockedColors( ) const; + const Tetromino& getCurrentTetromino( ) const; +}; diff --git a/src/Renderer.cpp b/src/Renderer.cpp new file mode 100644 index 0000000..130646b --- /dev/null +++ b/src/Renderer.cpp @@ -0,0 +1,151 @@ +#include "Renderer.hpp" +#include + +Renderer::Renderer(shared_ptr renderer) : renderer(renderer), blockSize(30) { + setRenderer(renderer); +} + +void Renderer::setRenderer(shared_ptr renderer) { + this->renderer = renderer; + loadTexture("/home/crylia/Dokumente/git/tetris_sdl/assets/single.png", TetrominoShape::I); + loadTexture("/home/crylia/Dokumente/git/tetris_sdl/assets/single.png", TetrominoShape::J); + loadTexture("/home/crylia/Dokumente/git/tetris_sdl/assets/single.png", TetrominoShape::L); + loadTexture("/home/crylia/Dokumente/git/tetris_sdl/assets/single.png", TetrominoShape::S); + loadTexture("/home/crylia/Dokumente/git/tetris_sdl/assets/single.png", TetrominoShape::O); + loadTexture("/home/crylia/Dokumente/git/tetris_sdl/assets/single.png", TetrominoShape::Z); +} + +void Renderer::renderBoard(const GameBoard& gameBoard) { + drawGrid(gameBoard); + drawLockedBlocks(gameBoard); + drawTetromino(gameBoard.getCurrentTetromino( )); + drawScoreboard(gameBoard.getScore( ), gameBoard.getLevel( )); +} + +void Renderer::drawScoreboard(int score, int level) { + 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 }); +} + +void Renderer::drawGrid(const GameBoard& board) { + SDL_SetRenderDrawColor(renderer.get( ), 100, 100, 100, 255); + + int w = board.getLockedTetrominos( )[0].size( ), h = board.getLockedTetrominos( ).size( ); + for (int x = 0; x <= w; ++x) + SDL_RenderDrawLine(renderer.get( ), x * blockSize, 0, x * blockSize, h * blockSize); + + for (int y = 0; y <= h; ++y) + SDL_RenderDrawLine(renderer.get( ), 0, y * blockSize, w * blockSize, y * blockSize); +} + +void Renderer::drawLockedBlocks(const GameBoard& gameBoard) { + const auto& lockedTetrominos = gameBoard.getLockedTetrominos( ); + const auto& lockedColors = gameBoard.getLockedColors( ); + + for (int row = 0; row < lockedTetrominos.size( ); ++row) { + for (int col = 0; col < lockedTetrominos[row].size( ); ++col) { + int blockType = lockedTetrominos[row][col]; + if (blockType != 0) { + SDL_Rect block = { col * blockSize, row * blockSize, blockSize, blockSize }; + + SDL_SetTextureBlendMode(textures[TetrominoShape::I], SDL_BLENDMODE_BLEND); + + SDL_Color color = lockedColors[row][col]; + if (textures[TetrominoShape::I]) { + SDL_SetTextureColorMod(textures[TetrominoShape::I], color.r, color.g, color.b); + renderTexture(TetrominoShape::I, block.x, block.y, block.w, block.h); + } else { + SDL_SetRenderDrawColor(renderer.get( ), color.r, color.g, color.b, 255); + SDL_RenderFillRect(renderer.get( ), &block); + } + } + } + } +} + +void Renderer::drawTetromino(const Tetromino& tetromino) { + const auto& shape = tetromino.getShape( ); + int x = tetromino.getX( ), y = tetromino.getY( ); + auto t = tetromino.getTexture( ); + + double angle = tetromino.getRotationAngle( ); + + SDL_SetTextureBlendMode(textures[t], SDL_BLENDMODE_BLEND); + SDL_Color color = tetromino.getColor( ); + SDL_SetTextureColorMod(textures[t], color.r, color.g, color.b); + + 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 = { (x + col) * blockSize, (y + row) * blockSize, blockSize, blockSize }; + SDL_Point center = { blockSize / 2, blockSize / 2 }; + SDL_RenderCopyEx(renderer.get( ), textures[t], nullptr, &destRect, angle, ¢er, SDL_FLIP_NONE); + } + } + } +} + +bool Renderer::loadTexture(const string& filePath, const TetrominoShape shape) { + SDL_Texture* texture = IMG_LoadTexture(renderer.get( ), filePath.c_str( )); + if (!texture) { + SDL_Log("Failed to load Texture: %s\n%s", filePath, SDL_GetError( )); + return false; + } + textures[shape] = texture; + return true; +} + +void Renderer::renderTexture(const TetrominoShape shape, int x, int y, int width, int height) { + SDL_Rect destRect{ x,y,width,height }; + SDL_RenderCopy(renderer.get( ), textures[shape], nullptr, &destRect); +} + +void Renderer::renderStartScreen( ) { + SDL_SetRenderDrawColor(renderer.get( ), 0, 0, 0, 255); + SDL_RenderClear(renderer.get( )); + + renderText("Tetris", 100, 100, 128, SDL_Color{ 200,200,200 }); + + SDL_RenderPresent(renderer.get( )); +} + +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 }); + + + SDL_RenderPresent(renderer.get( )); +} + +void Renderer::renderText(string text, int x, int y, int size, SDL_Color color) { + TTF_Font* font = TTF_OpenFont("/usr/share/fonts/TTF/OpenSans-Regular.ttf", size); + SDL_Surface* surface = TTF_RenderText_Solid(font, text.c_str( ), color); + if (!surface) { + SDL_Log("Failed to create surface: %s", TTF_GetError( )); + return; + } + + SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer.get( ), surface); + if (!texture) { + SDL_Log("Failed to create texture from surface: %s", SDL_GetError( )); + SDL_FreeSurface(surface); + TTF_CloseFont(font); + return; + } + + int width = surface->w; + int height = surface->h; + + SDL_Rect destRect = { x, y, width, height }; + + SDL_RenderCopy(renderer.get( ), texture, nullptr, &destRect); + + SDL_FreeSurface(surface); + SDL_DestroyTexture(texture); + TTF_CloseFont(font); +} diff --git a/src/Renderer.hpp b/src/Renderer.hpp new file mode 100644 index 0000000..df17a25 --- /dev/null +++ b/src/Renderer.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include + +extern "C" { +#include +#include +#include +} + +#include "GameBoard.hpp" + +class Renderer { +private: + void drawGrid(const GameBoard& gameBoard); + void drawLockedBlocks(const GameBoard& gameBoard); + void drawTetromino(const Tetromino& tetromino); + void drawScoreboard(int score, int level); + + shared_ptr renderer; + int blockSize; + + unordered_map textures; +public: + Renderer(shared_ptr renderer); + + void setRenderer(shared_ptr renderer); + void renderBoard(const GameBoard& gb); + + bool loadTexture(const string& filePath, const TetrominoShape shape); + void renderTexture(const TetrominoShape shape, int x, int y, int width, int height); + void renderStartScreen( ); + void renderGameOver(int score, int level); + void renderText(string text, int x, int y, int size, SDL_Color color); +}; diff --git a/src/Tetromino.cpp b/src/Tetromino.cpp new file mode 100644 index 0000000..26d97eb --- /dev/null +++ b/src/Tetromino.cpp @@ -0,0 +1,112 @@ +#include "Tetromino.hpp" +#include "GameBoard.hpp" + +Tetromino::Tetromino(TetrominoShape shape) : x(0), y(0) { + initializeShape(shape); + currentRotationState = 1; + + color.a = 255; + color.r = 0; + color.g = 0; + color.b = 0; + + random_device dev; + mt19937 rng(dev( )); + uniform_int_distribution dist6(0, 5); + + switch (dist6(rng)) { + case 0: + color.r = 255; + break; + case 1: + color.g = 255; + break; + case 2: + color.b = 255; + break; + case 3: + color.r = 255; + color.g = 255; + break; + case 4: + color.g = 255; + color.b = 255; + break; + case 5: + color.b = 255; + color.r = 255; + break; + default: + break; + } +} + +void Tetromino::initializeShape(TetrominoShape s) { + switch (s) { + case TetrominoShape::I: + shape = { {1, 1, 1, 1} }; + break; + case TetrominoShape::O: + shape = { {1, 1}, {1, 1} }; + break; + case TetrominoShape::S: + shape = { {0, 1, 1}, {1, 1, 0} }; + break; + case TetrominoShape::Z: + shape = { {1, 1, 0}, {0, 1, 1} }; + break; + case TetrominoShape::J: + shape = { {1, 0, 0}, {1, 1, 1} }; + break; + case TetrominoShape::L: + shape = { {0, 0, 1}, {1, 1, 1} }; + break; + default: + shape = { {0} }; + break; + } +} + +void Tetromino::rotate(GameBoard& gameBoard) { + vector> rotated(shape[0].size( ), vector(shape.size( ))); + for (int row = 0; row < shape.size( ); ++row) + for (int col = 0; col < shape[0].size( ); ++col) + rotated[col][shape.size( ) - 1 - row] = shape[row][col]; + + if (gameBoard.isValidPosition(rotated, x, y)) { + shape = rotated; + currentRotationState = (currentRotationState + 1) % 4; + } else { + int dx = x - 1; + while (!gameBoard.isValidPosition(rotated, dx, y)) { + //If the shape before rotation wont have a valid position then rotation is not possible and we abort + if (!gameBoard.isValidPosition(shape, dx, y)) { + shape = rotated; + return; + } + dx--; + } + x = dx; + shape = rotated; + currentRotationState = (currentRotationState + 1) % 4; + } +} + +void Tetromino::move(int dx, int dy) { + x += dx; + y += dy; +} + +double Tetromino::getRotationAngle( ) const { + return currentRotationState * 90.0; +} + +const vector>& Tetromino::getShape( ) const { return shape; } + +int Tetromino::getX( ) const { return x; } +int Tetromino::getY( ) const { return y; } + +void Tetromino::setTexture(const TetrominoShape shape) { this->textureShape = shape; } +const TetrominoShape Tetromino::getTexture( ) const { return textureShape; } + +SDL_Color Tetromino::getColor( ) const { return color; } diff --git a/src/Tetromino.hpp b/src/Tetromino.hpp new file mode 100644 index 0000000..db49086 --- /dev/null +++ b/src/Tetromino.hpp @@ -0,0 +1,51 @@ +#pragma once + +extern "C" { +#include +} + +#include +#include +#include + +using namespace std; + +enum class TetrominoShape { + L, + I, + O, + S, + Z, + J, + COUNT //Used to know the ammount of shapes +}; + +class GameBoard; + +class Tetromino { +private: + void initializeShape(TetrominoShape shape); + vector> shape; + + int x, y; + + int currentRotationState; + + TetrominoShape textureShape; + + SDL_Color color; +public: + Tetromino(TetrominoShape shape); + + void rotate(GameBoard& gameBoard); + void move(int dx, int dy); + double getRotationAngle( ) const; + + const vector>& getShape( ) const; + int getX( ) const; + int getY( ) const; + + void setTexture(const TetrominoShape textureName); + const TetrominoShape getTexture( ) const; + SDL_Color getColor( ) const; +}; diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..c193fca --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,46 @@ +#include + +extern "C" { +#include +#include +} + +#include "Game.hpp" + +int main( ) { + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) != 0) { + SDL_Log("Couldn't init SDL: %s", SDL_GetError( )); + return 1; + } + + if (IMG_Init(IMG_INIT_PNG) == 0) { + std::cerr << "Failed to initialize SDL_image: " << IMG_GetError( ) << std::endl; + SDL_Quit( ); + return 1; + } + + if (TTF_Init( ) == -1) { + std::cerr << "Failed to initialize SDL_ttf: " << TTF_GetError( ) << std::endl; + SDL_Quit( ); + return 1; + } + + if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048) < 0) { + SDL_Log("SDL_mixer could not initialize! SDL_mixer Error: %s\n", Mix_GetError( )); + return 1; + } + + Game game; + if (!game.init("Tetris", 800, 600)) { + SDL_Log("Failed to init game"); + SDL_Quit( ); + return 1; + } + + while (!game.isGameQuit( )) + game.run( ); + + SDL_Quit( ); + + return 0; +}