From 461139835f67020ebd8919977a806bc0ea53a7c8 Mon Sep 17 00:00:00 2001 From: lejulien Date: Fri, 9 Jan 2026 14:03:09 +0100 Subject: [PATCH] Initial commit --- CMakeLists.txt | 25 +++++ Makefile | 27 +++++ README.md | 24 +++++ includes/Board.hpp | 22 +++++ includes/Game.hpp | 14 +++ includes/Tetrominos.hpp | 212 ++++++++++++++++++++++++++++++++++++++++ src/Board.cpp | 103 +++++++++++++++++++ src/Game.cpp | 35 +++++++ src/main.cpp | 206 ++++++++++++++++++++++++++++++++++++++ 9 files changed, 668 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 Makefile create mode 100644 README.md create mode 100644 includes/Board.hpp create mode 100644 includes/Game.hpp create mode 100644 includes/Tetrominos.hpp create mode 100644 src/Board.cpp create mode 100644 src/Game.cpp create mode 100644 src/main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..36ddab9 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.16) +project(CelebratingTetris LANGUAGES CXX) + +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +option(BUILD_SHARED_LIBS "Build shared libraries" OFF) + +include(FetchContent) +FetchContent_Declare(SFML + GIT_REPOSITORY https://github.com/SFML/SFML.git + GIT_TAG 2.6.x) +FetchContent_MakeAvailable(SFML) + +add_executable(CelebratingTetris src/main.cpp src/Board.cpp src/Game.cpp) +target_link_libraries(CelebratingTetris PRIVATE sfml-graphics) +target_compile_features(CelebratingTetris PRIVATE cxx_std_17) + +if(WIN32) + add_custom_command( + TARGET CelebratingTetris + COMMENT "Copy OpenAL DLL" + PRE_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${SFML_SOURCE_DIR}/extlibs/bin/$,x64,x86>/openal32.dll $ + VERBATIM) +endif() + +install(TARGETS CelebratingTetris) diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0f47c9b --- /dev/null +++ b/Makefile @@ -0,0 +1,27 @@ + + +SRCS = ./src/main.cpp \ + ./src/Board.cpp \ + ./src/Game.cpp + +NAME = CelebratingTetris + +FLAGS = -lsfml-graphics -lsfml-window -lsfml-system + +OBJS = $(SRCS:.cpp=.o) + +COMPILER = clang++ + +all: $(NAME) + +$(NAME): $(OBJS) + $(COMPILER) -o $(NAME) $(FLAGS) $(OBJS) + +clean: + rm -fr $(OBJS) + +fclean: clean + rm -fr $(NAME) + +re: fclean all + diff --git a/README.md b/README.md new file mode 100644 index 0000000..0b308b5 --- /dev/null +++ b/README.md @@ -0,0 +1,24 @@ +# Celebrating Tetris + +This is a little trial to see if I could make a tetris game within 24 hours. + +Linux dependencies : +* libxrandr-dev +* libxcursor-dev +* libudev-dev +* libfreetype-dev +* libopenal-dev +* libflac-dev +* libvorbis-dev +* libgl1-mesa-dev +* libegl1-mesa-dev + +## Building + +```bash +mkdir build +cd build +cmake .. +make +./build/CelebratingTetris +``` diff --git a/includes/Board.hpp b/includes/Board.hpp new file mode 100644 index 0000000..4c1a045 --- /dev/null +++ b/includes/Board.hpp @@ -0,0 +1,22 @@ +// Celebrating tetris by Ley 2024 + +#include "./Game.hpp" + +#pragma once + +class Board { + public: + Board(); + ~Board(); + void Draw(sf::RenderWindow &win); + void Clear(); + int DoesFit(const Tetrominos::Names name, const int rot, + const sf::Vector2 pos); + void PlaceTetromino(const Tetrominos::Names name, const int rot, + const sf::Vector2 pos); + void CheckLines(); + protected: + int isInBoard(const sf::Vector2 pos); + private: + int buffer_[10][20]; +}; diff --git a/includes/Game.hpp b/includes/Game.hpp new file mode 100644 index 0000000..f2a156c --- /dev/null +++ b/includes/Game.hpp @@ -0,0 +1,14 @@ +// Celebrating tetris by Ley 2024 + +#pragma once + +// SMFL headers +#include +#include + +// Cxx headers +#include + +#include "./Tetrominos.hpp" + +sf::Color getTetrominoColor(const Tetrominos::Names &name); diff --git a/includes/Tetrominos.hpp b/includes/Tetrominos.hpp new file mode 100644 index 0000000..b50f8f3 --- /dev/null +++ b/includes/Tetrominos.hpp @@ -0,0 +1,212 @@ +// Celebrating tetris by Ley 2024 + +#pragma once + +#include + +struct Tetrominos { + enum Names { + I, + J, + L, + O, + S, + T, + Z, + None + }; + + // [shape name][rot pos][y][z] + constexpr static int shapes[8][4][4][4] = { + { // I + { + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {1, 1, 1, 1}, + {0, 0, 0, 0} + }, + { + {0, 0, 1, 0}, + {0, 0, 1, 0}, + {0, 0, 1, 0}, + {0, 0, 1, 0} + }, + { + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {1, 1, 1, 1}, + {0, 0, 0, 0} + }, + { + {0, 0, 1, 0}, + {0, 0, 1, 0}, + {0, 0, 1, 0}, + {0, 0, 1, 0} + } + }, + { // J + { + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 1, 1, 1}, + {0, 0, 0, 1} + }, + { + {0, 0, 0, 0}, + {0, 0, 1, 0}, + {0, 0, 1, 0}, + {0, 1, 1, 0} + }, + { + {0, 0, 0, 0}, + {0, 1, 0, 0}, + {0, 1, 1, 1}, + {0, 0, 0, 0} + }, + { + {0, 0, 0, 0}, + {0, 0, 1, 1}, + {0, 0, 1, 0}, + {0, 0, 1, 0} + } + }, + { // L + { + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 1, 1, 1}, + {0, 1, 0, 0} + }, + { + {0, 0, 0, 0}, + {0, 1, 1, 0}, + {0, 0, 1, 0}, + {0, 0, 1, 0} + }, + { + {0, 0, 0, 0}, + {0, 0, 0, 1}, + {0, 1, 1, 1}, + {0, 0, 0, 0} + }, + { + {0, 0, 0, 0}, + {0, 0, 1, 0}, + {0, 0, 1, 0}, + {0, 0, 1, 1} + } + }, + { // 0 + { + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 1, 1}, + {0, 0, 1, 1} + }, + { + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 1, 1}, + {0, 0, 1, 1} + }, + { + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 1, 1}, + {0, 0, 1, 1} + }, + { + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 1, 1}, + {0, 0, 1, 1} + } + }, + { // S + { + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 1, 1}, + {0, 1, 1, 0} + }, + { + {0, 0, 0, 0}, + {0, 0, 1, 0}, + {0, 0, 1, 1}, + {0, 0, 0, 1} + }, + { + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 1, 1}, + {0, 1, 1, 0} + }, + { + {0, 0, 0, 0}, + {0, 0, 1, 0}, + {0, 0, 1, 1}, + {0, 0, 0, 1} + } + }, + { // T + { + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 1, 1, 1}, + {0, 0, 1, 0} + }, + { + {0, 0, 0, 0}, + {0, 0, 1, 0}, + {0, 1, 1, 0}, + {0, 0, 1, 0} + }, + { + {0, 0, 0, 0}, + {0, 0, 1, 0}, + {0, 1, 1, 1}, + {0, 0, 0, 0} + }, + { + {0, 0, 0, 0}, + {0, 0, 1, 0}, + {0, 0, 1, 1}, + {0, 0, 1, 0} + } + }, + { // Z + { + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 1, 1, 0}, + {0, 0, 1, 1} + }, + { + {0, 0, 0, 0}, + {0, 0, 1, 0}, + {0, 1, 1, 0}, + {0, 1, 0, 0} + }, + { + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 1, 1, 0}, + {0, 0, 1, 1} + }, + { + {0, 0, 0, 0}, + {0, 0, 1, 0}, + {0, 1, 1, 0}, + {0, 1, 0, 0} + } + }, + { // None + { + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0} + } + } + }; +}; diff --git a/src/Board.cpp b/src/Board.cpp new file mode 100644 index 0000000..91819c7 --- /dev/null +++ b/src/Board.cpp @@ -0,0 +1,103 @@ +// Celebrating tetris by Ley 2024 + +#include "../includes/Board.hpp" + +Board::Board() { + Clear(); +} + +Board::~Board() { +} + +void Board::Draw(sf::RenderWindow &win) { + sf::RectangleShape board({320.f, 640.f}); + board.setOutlineThickness(4.f); + board.setOutlineColor({}); + sf::RectangleShape cell({28.f, 28.f}); + cell.setOutlineThickness(4.f); + cell.setOutlineColor({}); + + // Draw board + board.setFillColor(sf::Color()); + board.setPosition(16.f, 16.f); + win.draw(board); + + // Draw cells + for (int y = 0; y < 20; y++) { + for (int x = 0; x < 10; x++) { + cell.setFillColor(getTetrominoColor(static_cast(buffer_[x][y]))); + cell.setPosition(x * 34.f + 16.f, y * 34.f + 16.f); + win.draw(cell); + } + } +} + +void Board::Clear() { + for (int i = 0; i < 10; i++) { + for (int j = 0; j < 20; j++) { + buffer_[i][j] = Tetrominos::Names::None; + } + } +} + +int Board::isInBoard(const sf::Vector2 pos) { + if (pos.y >= 20) { + return 1; + } + if (pos.x < 0 || pos.x >= 10) { + return 1; + } + return 0; +} + +int Board::DoesFit(const Tetrominos::Names name, const int rot, + const sf::Vector2 pos) { + for (int y = 0; y < 4; y++) { + for (int x = 0; x < 4; x++) { + if (Tetrominos::shapes[name][rot][y][x] == 1) { + if (y + pos.y >= 0) { + if (isInBoard({x + pos.x, y + pos.y})) { + return 0; + } + if (buffer_[x + pos.x][y + pos.y] != Tetrominos::Names::None){ + return 0; + } + } + } + } + } + return 1; +} + + +void Board::PlaceTetromino(const Tetrominos::Names name, const int rot, + const sf::Vector2 pos) { + for (int y = 0; y < 4; y++) { + for (int x = 0; x < 4; x++) { + if (Tetrominos::shapes[name][rot][y][x] == 1) { + buffer_[x + pos.x][y + pos.y] = name; + } + } + } +} + +void Board::CheckLines() { + bool is_line; + for (int j = 0; j < 20; j++) { + is_line = true; + for (int i = 0; i < 10; i++) { + if (buffer_[i][j] == Tetrominos::Names::None) { + is_line = false; + } + } + if (is_line) { + for (int y = j; y > 0; y--) { + for (int x = 0; x < 10; x++) { + buffer_[x][y] = buffer_[x][y - 1]; + } + } + CheckLines(); + return; + } + } +} diff --git a/src/Game.cpp b/src/Game.cpp new file mode 100644 index 0000000..1808476 --- /dev/null +++ b/src/Game.cpp @@ -0,0 +1,35 @@ +// Celebrating tetris by Ley 2024 + +#include "../includes/Game.hpp" + +sf::Color getTetrominoColor(const Tetrominos::Names &name) { + switch (name) { + case Tetrominos::Names::I: + return(sf::Color::Cyan); + break; + case Tetrominos::Names::J: + return(sf::Color::Blue); + break; + case Tetrominos::Names::L: + return(sf::Color(255, 127, 0, 255)); + break; + case Tetrominos::Names::O: + return(sf::Color::Yellow); + break; + case Tetrominos::Names::S: + return(sf::Color::Green); + break; + case Tetrominos::Names::T: + return(sf::Color::Magenta); + break; + case Tetrominos::Names::Z: + return(sf::Color::Red); + break; + case Tetrominos::Names::None: + return(sf::Color::Black); + break; + default: + break; + }; + return sf::Color::Black; +} diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..b2e2e07 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,206 @@ +// Celebrating tetris by Ley 2024 + +#include "../includes/Game.hpp" +#include "../includes/Board.hpp" + +#include +#include + + +void draw_tetromino(sf::RenderWindow &window, const Tetrominos::Names &name, + const sf::Vector2 &pos, int anim) { + sf::RectangleShape cell({28.f, 28.f}); + cell.setOutlineThickness(4.f); + cell.setOutlineColor({}); + + for (int y = 0; y < 4; y++) { + for (int x = 0; x < 4; x++) { + if (Tetrominos::shapes[name][anim][y][x] == 1) { + cell.setPosition(x * 34.f + (16.f + pos.x * 34.f), + y * 34.f + (16.f + pos.y * 34.f)); + cell.setFillColor(getTetrominoColor(name)); + window.draw(cell); + } + } + } +} + + +int main(int ac, char **av) { + (void)ac; + sf::Clock clock; + sf::Clock input_clock; + bool left_rotating = false; + bool right_rotating = false; + sf::RectangleShape top_hider({400.f, 12.f}); + sf::RectangleShape pred_box({155.f, 77.f}); + pred_box.setPosition(365.f, 586.f); + sf::RenderWindow window(sf::VideoMode(520, 700), av[0]+2); + Board board; + top_hider.setFillColor({40, 40, 40, 255}); + float descending_delay = 500.f; + float input_delay = 100.f; + sf::Vector2 pos = {3, -2}; + int anim_id = 0; + std::srand(std::time(nullptr)); + int pred_shape = static_cast(std::rand() / ((RAND_MAX + 1u) / 7)); + int shape = static_cast(std::rand() / ((RAND_MAX + 1u) / 7)); + + while (window.isOpen()) + { + auto ElapsedTime = clock.getElapsedTime(); + auto KeyElapsedTime = input_clock.getElapsedTime(); + + sf::Joystick::update(); + // Joystick + if (sf::Joystick::isConnected(0)) { + auto buttonCount = sf::Joystick::getButtonCount(0); + for (int i = 0; i <= buttonCount; i++) { + std::cout << i << " : " << sf::Joystick::isButtonPressed(0, i) << std::endl; + } + // Moving + if (KeyElapsedTime.asMilliseconds() > input_delay) { + // Left & Right + if (!(sf::Keyboard::isKeyPressed(sf::Keyboard::A) && sf::Keyboard::isKeyPressed(sf::Keyboard::D))) { + if (sf::Keyboard::isKeyPressed(sf::Keyboard::A)) { + if (board.DoesFit(static_cast(shape), anim_id, {pos.x - 1, pos.y})) { + pos.x--; + } + } + if (sf::Keyboard::isKeyPressed(sf::Keyboard::D)) { + if (board.DoesFit(static_cast(shape), anim_id, {pos.x + 1, pos.y})) { + pos.x++; + } + } + } + // Speeding down + if (sf::Keyboard::isKeyPressed(sf::Keyboard::S)) { + if (board.DoesFit(static_cast(shape), anim_id, {pos.x, pos.y + 1})) { + pos.y++; + } + } + input_clock.restart(); + } + // Rotating + if (!(sf::Keyboard::isKeyPressed(sf::Keyboard::K) && sf::Keyboard::isKeyPressed(sf::Keyboard::L))) { + if (sf::Keyboard::isKeyPressed(sf::Keyboard::K) && left_rotating == false) { + int next_anim = anim_id - 1; + if (next_anim < 0) { + next_anim = 3; + } + if (board.DoesFit(static_cast(shape), next_anim, {pos.x, pos.y})) { + anim_id = next_anim; + } + left_rotating = true; + } + if (sf::Keyboard::isKeyPressed(sf::Keyboard::L) && right_rotating == false) { + int next_anim = anim_id + 1; + if (next_anim > 3) { + next_anim = 0; + } + if (board.DoesFit(static_cast(shape), next_anim, {pos.x, pos.y})) { + anim_id = next_anim; + } + right_rotating = true; + } + } + if (!sf::Keyboard::isKeyPressed(sf::Keyboard::K) && left_rotating == true) { + left_rotating = false; + } + if (!sf::Keyboard::isKeyPressed(sf::Keyboard::L) && right_rotating == true) { + right_rotating = false; + } + } else { // Keyboard + // Moving + if (KeyElapsedTime.asMilliseconds() > input_delay) { + // Left & Right + if (!(sf::Keyboard::isKeyPressed(sf::Keyboard::A) && sf::Keyboard::isKeyPressed(sf::Keyboard::D))) { + if (sf::Keyboard::isKeyPressed(sf::Keyboard::A)) { + if (board.DoesFit(static_cast(shape), anim_id, {pos.x - 1, pos.y})) { + pos.x--; + } + } + if (sf::Keyboard::isKeyPressed(sf::Keyboard::D)) { + if (board.DoesFit(static_cast(shape), anim_id, {pos.x + 1, pos.y})) { + pos.x++; + } + } + } + // Speeding down + if (sf::Keyboard::isKeyPressed(sf::Keyboard::S)) { + if (board.DoesFit(static_cast(shape), anim_id, {pos.x, pos.y + 1})) { + pos.y++; + } + } + input_clock.restart(); + } + // Rotating + if (!(sf::Keyboard::isKeyPressed(sf::Keyboard::K) && sf::Keyboard::isKeyPressed(sf::Keyboard::L))) { + if (sf::Keyboard::isKeyPressed(sf::Keyboard::K) && left_rotating == false) { + int next_anim = anim_id - 1; + if (next_anim < 0) { + next_anim = 3; + } + if (board.DoesFit(static_cast(shape), next_anim, {pos.x, pos.y})) { + anim_id = next_anim; + } + left_rotating = true; + } + if (sf::Keyboard::isKeyPressed(sf::Keyboard::L) && right_rotating == false) { + int next_anim = anim_id + 1; + if (next_anim > 3) { + next_anim = 0; + } + if (board.DoesFit(static_cast(shape), next_anim, {pos.x, pos.y})) { + anim_id = next_anim; + } + right_rotating = true; + } + } + if (!sf::Keyboard::isKeyPressed(sf::Keyboard::K) && left_rotating == true) { + left_rotating = false; + } + if (!sf::Keyboard::isKeyPressed(sf::Keyboard::L) && right_rotating == true) { + right_rotating = false; + } + } + + // Scrolling down + if (ElapsedTime.asMilliseconds() > descending_delay) { + if (board.DoesFit(static_cast(shape), anim_id, + {pos.x, pos.y + 1})) { + pos.y++; + } else { + board.PlaceTetromino(static_cast(shape), anim_id, {pos.x, pos.y}); + board.CheckLines(); + pos = {3, -2}; + anim_id = 0; + shape = pred_shape; + pred_shape = static_cast(std::rand() / ((RAND_MAX + 1u) / 7)); + } + //anim_id++; + if (anim_id >= 4) { + anim_id = 0; + } + clock.restart(); + } + sf::Event event; + while (window.pollEvent(event)) + { + if (event.type == sf::Event::Closed) + window.close(); + } + + // UI + window.clear(sf::Color(40, 40, 40, 255)); + board.Draw(window); + window.draw(pred_box); + + // Game + draw_tetromino(window, static_cast(shape), {static_cast(pos.x), static_cast(pos.y)}, anim_id); + window.draw(top_hider); + draw_tetromino(window, static_cast(pred_shape), {10.5f, 15.f}, 0); + window.display(); + } + return 0; +}