diff --git a/.gitignore b/.gitignore index 68c0d8f..0f304fa 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ *.o demo test +tetris diff --git a/Makefile b/Makefile index 8a0c939..7116cdb 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,8 @@ default: demo demo: main.o sdlpp.o $(CXX) $(CFLAGS) -o $@ $^ ${LDFLAGS} +tetris: tetris.o sdlpp.o + $(CXX) $(CFLAGS) -o $@ $^ ${LDFLAGS} test: test.o sdlpp.o $(CXX) $(CFLAGS) -o $@ $^ ${LDFLAGS} diff --git a/testfont.ttf b/testfont.ttf new file mode 100644 index 0000000..2b6392f Binary files /dev/null and b/testfont.ttf differ diff --git a/tetris.cpp b/tetris.cpp new file mode 100644 index 0000000..460da7c --- /dev/null +++ b/tetris.cpp @@ -0,0 +1,620 @@ +#include "sdlpp.hpp" +#include +#include +#include +#include + +#define COLIDER_ID 0x00000001 +#define BRICK_ID 0x00000002 +#define GAME_OVER 0x00000003 + +bool pause = false; +int pause_select = 0; +int pause_max = 1; +int ticks_till_next = 1500; +int ticks_till_fall = 500; +int ticks_till_descend = 50; +std::vector> pause_options; +std::shared_ptr score_texture; +std::shared_ptr active_renderer; +int score = 0; +bool update_score = false; + +std::shared_ptr font; +std::shared_ptr active_scene; +std::shared_ptr pause_scene; + +class TetrisPiece { +public: + void addPiece(std::shared_ptr piece, int x, int y) { + pieces.push_back(piece); + pieces_rel_position.push_back({0,0,0,0}); + // done this way for SPEEEEEEED + //left + pieces_rel_position.back()[0] = (x<0)*(-1)*x; + //right + pieces_rel_position.back()[1] = (x>0)*x; + //top + pieces_rel_position.back()[2] = (y<0)*(-1)*y; + //bottom + pieces_rel_position.back()[3] = (y>0)*y; + } + void rotate() { + for(unsigned long i = 0; i < pieces.size(); i++) { + auto &piece = pieces[i]; + auto &positions = pieces_rel_position[i]; + auto position = piece->getPos(); + position.first += positions[0] * 0.04; + position.first -= positions[1] * 0.04; + position.second += positions[2] * 0.04; + position.second -= positions[3] * 0.04; + auto bottom = positions[3]; + auto top = positions[2]; + positions[3] = positions[1]; + positions[2] = positions[0]; + positions[1] = top; + positions[0] = bottom; + position.first -= positions[0] * 0.04; + position.first += positions[1] * 0.04; + position.second -= positions[2] * 0.04; + position.second += positions[3] * 0.04; + piece->setPos(position.first, position.second); + } + } + std::vector> &getObjects() { + return pieces; + } + void setPos(double x, double y) { + for(unsigned long i = 0; i < pieces.size(); i++) { + auto &piece = pieces[i]; + auto pos = piece->getPos(); + piece->setPos(x + pos.first - default_x, + y + pos.second - default_y); + } + setDefPos(x, y); + } + void setDefPos(double x, double y) { + default_x = x; + default_y = y; + } + void clear() { + pieces.clear(); + pieces_rel_position.clear(); + } + void startDescend() { + descend = true; + } + void stopDescend() { + descend = false; + } + bool isDescending() { + return descend; + } +private: + std::vector> pieces_rel_position; + std::vector> pieces; + double default_x; + double default_y; + bool descend = false; +}; + +std::vector> line_coliders; +TetrisPiece cur_object; +TetrisPiece next_object; + +void doInput(std::shared_ptr scene); +void doInputPause(); +bool quit = false; + +std::mutex movement_mutex; + +std::shared_ptr createTetrisBlock(double x, double y, const std::string &color, const std::string &outline, std::shared_ptr renderer, std::shared_ptr scene) { + auto ret = std::make_shared(x, y, 0.04, 0.04, renderer, color, true); + ret->setOutlineColor(outline); + ret->addCollision(SDLPP::Rect(0.1,0.1,0.8,0.8)); + ret->setId(BRICK_ID); + ret->centerX(); + scene->addObject(ret); + return ret; +} + +TetrisPiece tetrisBrick(std::shared_ptr renderer, std::shared_ptr scene) { + TetrisPiece retPiece{}; + auto color = "#FF0000"; + auto outline = "#AA0000"; + retPiece.addPiece(createTetrisBlock(0.46, 0.16, color, outline, renderer, scene), 0, 0); + retPiece.addPiece(createTetrisBlock(0.5, 0.16, color, outline, renderer, scene), 0, 0); + retPiece.addPiece(createTetrisBlock(0.46, 0.20, color, outline, renderer, scene), 0, 0); + retPiece.addPiece(createTetrisBlock(0.5, 0.20, color, outline, renderer, scene), 0, 0); + retPiece.setDefPos(0.5, 0.16); + return retPiece; +} + +TetrisPiece tetrisT(std::shared_ptr renderer, std::shared_ptr scene) { + TetrisPiece retPiece{}; + auto color = "#00FF00"; + auto outline = "#00AA00"; + retPiece.addPiece(createTetrisBlock(0.46, 0.20, color, outline, renderer, scene), -1, 0); + retPiece.addPiece(createTetrisBlock(0.5, 0.20, color, outline, renderer, scene), 0, 0); + retPiece.addPiece(createTetrisBlock(0.5, 0.16, color, outline, renderer, scene), 0, -1); + retPiece.addPiece(createTetrisBlock(0.54, 0.20, color, outline, renderer, scene), 1, 0); + retPiece.setDefPos(0.5, 0.16); + return retPiece; +} + +TetrisPiece tetrisLRight(std::shared_ptr renderer, std::shared_ptr scene) { + TetrisPiece retPiece{}; + auto color = "#0000FF"; + auto outline = "#0000AA"; + retPiece.addPiece(createTetrisBlock(0.46, 0.20, color, outline, renderer, scene), -2, 0); + retPiece.addPiece(createTetrisBlock(0.5, 0.20, color, outline, renderer, scene), -1, 0); + retPiece.addPiece(createTetrisBlock(0.54, 0.20, color, outline, renderer, scene), 0, 0); + retPiece.addPiece(createTetrisBlock(0.54, 0.16, color, outline, renderer, scene), 0, -1); + retPiece.setDefPos(0.5, 0.16); + return retPiece; +} + +TetrisPiece tetrisZRight(std::shared_ptr renderer, std::shared_ptr scene) { + TetrisPiece retPiece{}; + auto color = "#FF00FF"; + auto outline = "#AA00AA"; + retPiece.addPiece(createTetrisBlock(0.46, 0.20, color, outline, renderer, scene), -1, 0); + retPiece.addPiece(createTetrisBlock(0.5, 0.20, color, outline, renderer, scene), 0, 0); + retPiece.addPiece(createTetrisBlock(0.5, 0.16, color, outline, renderer, scene), 0, -1); + retPiece.addPiece(createTetrisBlock(0.54, 0.16, color, outline, renderer, scene), 1, -1); + retPiece.setDefPos(0.5, 0.16); + return retPiece; +} + +TetrisPiece tetrisLine(std::shared_ptr renderer, std::shared_ptr scene) { + TetrisPiece retPiece{}; + auto color = "#FFFF00"; + auto outline = "#AAAA00"; + retPiece.addPiece(createTetrisBlock(0.42, 0.16, color, outline, renderer, scene), -1, 0); + retPiece.addPiece(createTetrisBlock(0.46, 0.16, color, outline, renderer, scene), 0, 0); + retPiece.addPiece(createTetrisBlock(0.5, 0.16, color, outline, renderer, scene), 1, 0); + retPiece.addPiece(createTetrisBlock(0.54, 0.16, color, outline, renderer, scene), 2, 0); + retPiece.setDefPos(0.5, 0.16); + return retPiece; +} + +TetrisPiece tetrisLLeft(std::shared_ptr renderer, std::shared_ptr scene) { + TetrisPiece retPiece{}; + auto color = "#00FFFF"; + auto outline = "#00AAAA"; + retPiece.addPiece(createTetrisBlock(0.46, 0.16, color, outline, renderer, scene), 0, -1); + retPiece.addPiece(createTetrisBlock(0.46, 0.20, color, outline, renderer, scene), 0, 0); + retPiece.addPiece(createTetrisBlock(0.5, 0.20, color, outline, renderer, scene), 1, 0); + retPiece.addPiece(createTetrisBlock(0.54, 0.20, color, outline, renderer, scene), 2, 0); + retPiece.setDefPos(0.5, 0.16); + return retPiece; +} + +TetrisPiece tetrisZLeft(std::shared_ptr renderer, std::shared_ptr scene) { + TetrisPiece retPiece{}; + auto color = "#FFFFFF"; + auto outline = "#AAAAAA"; + retPiece.addPiece(createTetrisBlock(0.46, 0.16, color, outline, renderer, scene), -1, 0); + retPiece.addPiece(createTetrisBlock(0.5, 0.16, color, outline, renderer, scene), 0, 0); + retPiece.addPiece(createTetrisBlock(0.5, 0.20, color, outline, renderer, scene), 0, 1); + retPiece.addPiece(createTetrisBlock(0.54, 0.20, color, outline, renderer, scene), 1, 1); + retPiece.setDefPos(0.5, 0.16); + return retPiece; +} + +std::vector, std::shared_ptr)> tetrisFunctions = { + tetrisBrick, + tetrisT, + tetrisLRight, + tetrisZRight, + tetrisLine, + tetrisLLeft, + tetrisZLeft, +}; + +void addStuff(SDLPP::Scene &scene, std::shared_ptr &r) { + auto bg = std::make_shared(0,0,10,10,r,"#101090FF", true); + bg->setPermanent(true); + scene.addObject(bg); + auto left_barrier = std::make_shared(0.28,0,0.02,1,r,"#FF000080", true); + left_barrier->centerX(); + scene.addObject(left_barrier); + auto right_barrier = std::make_shared(0.7,0,0.02,1,r,"#FF000080", true); + right_barrier->centerX(); + scene.addObject(right_barrier); + auto bottom_barrier = std::make_shared(0.28,1,0.44,0.02,r,"#FF000080", true); + bottom_barrier->centerX(); + scene.addObject(bottom_barrier); + auto tetris = std::make_shared(0.4, 0, 0.2, 0.1, r); + tetris->setTexture(*font, "TETRIS", "#FFFFFF", "#000000", 5); + tetris->centerX(); + scene.addObject(tetris); + auto next = std::make_shared(0.8, 0.35, 0.2, 0.1, r); + next->setTexture(*font, "NEXT", "#FFFFFF", "#000000", 5); + next->centerX(); + scene.addObject(next); + double posy = 1; + auto gameover = std::make_shared(0.5,0,0,0.195, r); + auto gameover_collision = SDLPP::Rect(-1,0,-1,1); + gameover_collision.setInfinite(); + gameover->addCollision(gameover_collision); + gameover->setId(GAME_OVER); + gameover->setColiderColor("FF0000"); + scene.addObject(gameover); + auto score_text = std::make_shared(0.8, 0.1, 0.2, 0.1, r); + score_text->setTexture(*font, "SCORE", "#FFFFFF", "#000000", 5); + score_text->centerX(); + scene.addObject(score_text); + + score_texture = std::make_shared(0.8, 0.2, 0.2, 0.1, r); + score_texture->setTexture(*font, std::to_string(score), "#FFFFFF", "#000000", 5); + score_texture->centerX(); + score_texture->setId(123); + scene.addObject(score_texture); + for(int i = 0; i < 20; i++) { + posy -= 0.04; + auto colider = std::make_shared(0.3, posy, 0.04, 0.04, r); + auto colider_colider = SDLPP::Rect(-1,0.1,-1,0.8); + colider_colider.setInfinite(); + colider->addCollision(colider_colider); + colider->setId(COLIDER_ID); + line_coliders.push_back(colider); + scene.addObject(colider); + } +} + +void updateScore() { + score_texture->setTexture(*font, std::to_string(score), "#FFFFFF", "#000000", 5); +} + +void addPause(SDLPP::Scene &scene, std::shared_ptr &r) { + auto bg = std::make_shared(0,0,10,10,r,"#00000080", true); + bg->setId(123); + bg->setPermanent(true); + scene.addObject(bg); + auto y = std::make_shared(0.25, 0.1, 0.5, 0.3, r); + y->setTexture(*font, "PAUSED", "#FFFFFF", "#000000", 5); + y->setId(0); + y->centerX(); + scene.addObject(y); + auto resume = std::make_shared(0.4, 0.5, 0.2, 0.1, r); + resume->setTexture(*font, "Resume", "#FFFFFF", "#000000", 5); + resume->setColor("#FFFFFF40"); + resume->centerX(); + scene.addObject(resume); + pause_options.push_back(resume); + auto quit = std::make_shared(0.4, 0.7, 0.2, 0.1, r); + quit->setTexture(*font, "Quit Game", "#FFFFFF", "#000000", 5); + quit->centerX(); + scene.addObject(quit); + pause_options.push_back(quit); +} + +void quitGame() { + std::cout << "Quitting!" << std::endl; + quit = true; +} + +void handleKeyDown(SDL_Keycode key, SDLPP::Scene &scene) { + bool crash = false; + std::lock_guard guard(movement_mutex); + switch(key) { + case SDLK_ESCAPE: { + pause = true; + pause_scene->updateSizeAndPosition(); + std::thread pauseThread(doInputPause); + pauseThread.detach(); + } + break; + case SDLK_LEFT: + case SDLK_a: + for(auto &x : cur_object.getObjects()) { + auto pos = x->getPos(); + // 0.31 because doubles + if(pos.first < 0.31) + crash = true; + x->setPos(pos.first - 0.04, pos.second); + } + for(auto &x : cur_object.getObjects()) { + auto collisions = scene.getCollisions(*x, {BRICK_ID}); + if(collisions.size() > 1) + crash = true; + } + if(crash) { + for(auto &x : cur_object.getObjects()) { + auto pos = x->getPos(); + x->setPos(pos.first + 0.04, pos.second); + } + } + break; + case SDLK_RIGHT: + case SDLK_d: + for(auto &x : cur_object.getObjects()) { + auto pos = x->getPos(); + // 0.65 because doubles + if(pos.first > 0.65) { + crash = true; + } + x->setPos(pos.first + 0.04, pos.second); + } + for(auto &x : cur_object.getObjects()) { + auto collisions = scene.getCollisions(*x, {BRICK_ID}); + if(collisions.size() > 1) { + crash = true; + } + } + if(crash) { + for(auto &x : cur_object.getObjects()) { + auto pos = x->getPos(); + x->setPos(pos.first - 0.04, pos.second); + } + } + break; + case SDLK_DOWN: + case SDLK_s: + cur_object.startDescend(); + break; + case SDLK_UP: + case SDLK_w: + cur_object.rotate(); + break; + case SDLK_r: + scene.getRenderer().setRenderColiders(!scene.getRenderer().getRenderColiders()); + default: + break; + } +} + +void handleKeyUp(SDL_Keycode key) { + if(key == SDLK_DOWN || key == SDLK_s) { + cur_object.stopDescend(); + } +} + +void handleKeyDownPause(SDL_Keycode key) { + switch(key) { + case SDLK_ESCAPE: { + pause = false; + active_scene->setPrevTicks(SDL_GetTicks()); + std::thread inputThread(doInput, active_scene); + inputThread.detach(); + } + break; + case SDLK_r: + active_scene->getRenderer().setRenderColiders(!active_scene->getRenderer().getRenderColiders()); + break; + case SDLK_s: + case SDLK_DOWN: + pause_options[pause_select]->unsetColor(); + pause_select++; + if(pause_select > pause_max) + pause_select = 0; + pause_options[pause_select]->setColor("FFFFFF40"); + break; + case SDLK_w: + case SDLK_UP: + pause_options[pause_select]->unsetColor(); + pause_select--; + if(pause_select < 0) + pause_select = pause_max; + pause_options[pause_select]->setColor("FFFFFF40"); + break; + case SDLK_RETURN: + switch(pause_select) { + case 0:{ + pause = false; + active_scene->setPrevTicks(SDL_GetTicks()); + std::thread inputThread(doInput, active_scene); + inputThread.detach(); + } + break; + case 1: + quitGame(); + default: + break; + } + default: + break; + } +} + +void pollEvents(SDLPP::Scene &scene) { + SDL_Event event; + while( SDL_PollEvent( &event ) != 0 ) { + switch(event.type) { + case SDL_QUIT: + quitGame(); + break; + case SDL_KEYDOWN: + if( !event.key.repeat ) + handleKeyDown(event.key.keysym.sym, scene); + break; + case SDL_KEYUP: + handleKeyUp(event.key.keysym.sym); + break; + case SDL_WINDOWEVENT: + if(event.window.event == SDL_WINDOWEVENT_RESIZED) + scene.updateSizeAndPosition(); + default: + break; + } + } +} + +void pollEventsPause() { + SDL_Event event; + while( SDL_PollEvent( &event ) != 0 ) { + switch(event.type) { + case SDL_QUIT: + quitGame(); + break; + case SDL_KEYDOWN: + if( !event.key.repeat ) + handleKeyDownPause(event.key.keysym.sym); + break; + case SDL_WINDOWEVENT: + if(event.window.event == SDL_WINDOWEVENT_RESIZED) { + active_scene->updateSizeAndPosition(); + pause_scene->updateSizeAndPosition(); + } + default: + break; + } + } +} + +void moveThem(std::shared_ptr scene, int ticks) { + std::lock_guard guard(movement_mutex); + ticks_till_fall -= ticks; + if(cur_object.isDescending()) + ticks_till_descend -= ticks; + if(ticks_till_fall > 0) { + if(cur_object.isDescending() && ticks_till_descend <= 0) { + ticks_till_descend = 50; + goto fall; + } + return; + } + ticks_till_fall = 500; +fall: + for(auto &x : cur_object.getObjects()) { + auto pos = x->getPos(); + x->setPos(pos.first, pos.second + 0.04); + } + bool fell = false; + for(auto &x : cur_object.getObjects()) { + auto collisions = scene->getCollisions(*x, {BRICK_ID}); + if(collisions.size() > 1) { + fell = true; + break; + } + if(x->getPos().second >= 1) { + fell = true; + break; + } + } + if(fell) { + for(auto &x : cur_object.getObjects()) { + auto pos = x->getPos(); + x->setPos(pos.first, pos.second - 0.04); + } + for(auto &block : cur_object.getObjects()) { + if(scene->getCollisions(*block, {GAME_OVER}).size() > 0) { + std::cout << "You lost" << std::endl; + quitGame(); + } + } + cur_object.clear(); + } +} + +void doInput(std::shared_ptr scene) { + FPSmanager gFPS; + SDL_initFramerate(&gFPS); + SDL_setFramerate(&gFPS, 200); + auto base = SDL_GetTicks(); + while(!quit && !pause) { + base = SDL_GetTicks(); + SDL_framerateDelay(&gFPS); + pollEvents(*scene); + scene->movement(); + if(cur_object.getObjects().size() != 0) { + moveThem(scene, SDL_GetTicks() - base); + continue; + } + std::lock_guard guard(movement_mutex); + for( auto &colider : line_coliders ) { + auto collisions = scene->getCollisions(*colider, {BRICK_ID}); + while(collisions.size() == 10) { + score += 10; +// updateScore(); + update_score = true; + for(auto &col : collisions) { + col->destroy(); + } + auto colider_y = colider->getPos().second; + for(auto &elem : scene->getObjects()) { + if(elem->getId() != BRICK_ID) + continue; + auto pos = elem->getPos(); + if(pos.second < colider_y) { + elem->setPos(pos.first, pos.second + 0.04); + } + } + using namespace std::chrono_literals; + std::this_thread::sleep_for(0.1s); + collisions = scene->getCollisions(*colider, {BRICK_ID}); + } + } + } +} + +void doInputPause() { + FPSmanager gFPS; + SDL_initFramerate(&gFPS); + SDL_setFramerate(&gFPS, 200); + while(pause) { + SDL_framerateDelay(&gFPS); + pollEventsPause(); + if(!pause) + break; + } +} + +int main() { + SDLPP::init(); + SDLPP::Window w("Tetris clone!"); + auto renderer = std::make_shared(w); + active_renderer = renderer; + renderer->setBlendMode(SDL_BLENDMODE_BLEND); + auto main_scene = std::make_shared(renderer); + active_scene = main_scene; + font = std::make_shared("testfont.ttf", 96); + addStuff(*main_scene, renderer); + + pause_scene = std::make_shared(renderer); + addPause(*pause_scene, renderer); + + int base = SDL_GetTicks(); + int frames = 0; + + std::srand(std::time(nullptr)); + FPSmanager gFPS; + SDL_initFramerate(&gFPS); + SDL_setFramerate(&gFPS, 60); + std::thread inputThread(doInput, main_scene); + inputThread.detach(); + next_object = tetrisFunctions[std::rand()/((RAND_MAX + 1u)/6)](renderer, main_scene); + next_object.setPos(0.9, 0.5); + while( !quit ) { + SDL_framerateDelay(&gFPS); + if(cur_object.getObjects().size() != 0) { + ticks_till_next = 1500; + } else { + ticks_till_next -= SDL_GetTicks() - base; + if(ticks_till_next <= 0 && cur_object.getObjects().size() == 0) { + std::lock_guard guard(movement_mutex); + cur_object = next_object; + cur_object.setPos(0.5, 0.16); + next_object = tetrisFunctions[std::rand()/((RAND_MAX + 1u)/6)](renderer, main_scene); + next_object.setPos(0.9, 0.5); + } + } + if(update_score) { + updateScore(); + update_score = false; + } + + main_scene->renderScene(); + if(pause) { + pause_scene->renderScene(false); + } + main_scene->presentScene(); + frames++; + if(SDL_GetTicks() - base >= 1000) { + base = SDL_GetTicks(); + printf("FPS: %d\n", frames); + frames = 0; + } + } +}