diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index bef6fab..0000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "Tetris C Development", - "image": "ubuntu:24.04", - "features": { - "ghcr.io/devcontainers/features/common-utils:2": { - "installZsh": false - } - }, - "customizations": { - "vscode": { - "extensions": [ - "ms-vscode.cpptools", - "ms-vscode.cmake-tools", - "llvm-vs-code-extensions.vscode-clangd", - "vadimcn.vscode-lldb", - "ritwickdey.liveserver" - ], - "settings": { - "C_Cpp.default.compilerPath": "/usr/bin/gcc", - "terminal.integrated.defaultProfile.linux": "bash" - } - } - }, - "postCreateCommand": "apt-get update && apt-get install -y gcc make libncurses-dev check lcov doxygen gdb valgrind clang-format git xdg-utils", - "remoteUser": "root", - "mounts": [ - "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached" - ] -} diff --git a/.gitignore b/.gitignore index 5716886..c6127b3 100644 --- a/.gitignore +++ b/.gitignore @@ -42,8 +42,6 @@ *.idb *.pdb -*.gc* - # Kernel Module Compile Results *.mod* *.cmd @@ -52,18 +50,3 @@ modules.order Module.symvers Mkfile.old dkms.conf -src/project.md -src/tetris_bin -src/.gpskip -.gpskip -ginpee.toml -src/ginpee.toml -.vscode/launch.json -src/tetris.log -src/high_score.txt -src/build/high_score.txt -code-samples/frogger/project.md -dvi/ -gcov_report/ -build/ -src/tetris.tar.gz diff --git a/README_RUS.md b/README_RUS.md index 0a9de77..e4f84dc 100644 --- a/README_RUS.md +++ b/README_RUS.md @@ -1,6 +1,81 @@ # BrickGame Тетрис Резюме: в данном проекте тебе предстоит реализовать игру «Тетрис» на языке программирования С с использованием структурного подхода. +💡 [Нажми сюда](https://new.oprosso.net/p/4cb31ec3f47a4596bc758ea1861fb624), **чтобы поделиться с нами обратной связью на этот проект**. Это анонимно и поможет нашей команде сделать обучение лучше. Рекомендуем заполнить опрос сразу после выполнения проекта. + +## Содержание + +- [BrickGame Тетрис](#brickgame-тетрис) + - [Содержание](#содержание) + - [Введение](#введение) + - [Chapter I ](#chapter-i-) + - [Общая информация](#общая-информация) + - [BrickGame](#brickgame) + - [История тетриса](#история-тетриса) + - [Конечные автоматы](#конечные-автоматы) + - [Фроггер](#фроггер) + - [Тетрис](#тетрис) + - [Chapter II ](#chapter-ii-) + - [Требования к проекту](#требования-к-проекту) + - [Часть 1. Основное задание](#часть-1-основное-задание) + - [Часть 2. Дополнительно. Подсчет очков и рекорд в игре](#часть-2-дополнительно-подсчет-очков-и-рекорд-в-игре) + - [Часть 3. Дополнительно. Механика уровней](#часть-3-дополнительно-механика-уровней) + +## Введение + +Для реализации игры «Тетрис» проект должен состоять из двух частей: библиотеки, реализующей логику работы игры, которую можно в будущем подключать к различным GUI, и терминального интерфейса. Логика работы библиотеки должна быть реализована с использованием конечных автоматов, одно из возможных описаний которого будет дано ниже. + +## Chapter I
+## Общая информация +### BrickGame + +BrickGame — популярная портативная консоль 90-ых годов с несколькими ~~тысячами~~ встроенными играми, разработана она была в Китае. Изначально эта игра была копией, разработанной в СССР и выпущенной Nintendo в рамках платформы GameBoy игры «Тетрис», но включала в себя также и множество других игр, которые добавлялись с течением времени. Консоль имела небольшой экранчик с игровым полем размера 10 х 20, представляющим из себя матрицу «пикселей». Справа от поля находилось табло с цифровой индикацией состояния текущей игры, рекордами и прочей дополнительной информацией. Самыми распространенными играми на BrickGame были: тетрис, танки, гонки, фроггер и змейка. + +![BrickGameConsole](misc/images/brickgame-console.jpg) + +### История тетриса + +«Тетрис» был написан Алексеем Пажитновым 6 июня 1984 года на компьютере Электроника-60. Игра представляла собой головоломку, построенную на использовании геометрических фигур «тетрамино», состоящих из четырех квадратов. Первая коммерческая версия игры была выпущена в Америке в 1987 году. В последующие годы «Тетрис» был портирован на множество различных устройств, в том числе на мобильные телефоны, калькуляторы и карманные персональные компьютеры. + +Наибольшую популярность приобрела реализация «Тетриса» для игровой консоли Game Boy и видеоприставки NES. Но кроме нее существуют различные версии игры. Например, есть версия с трехмерными фигурами или дуэльная версия, в которой два игрока получают одинаковые фигуры и пытаются обойти друг друга по очкам. + +### Конечные автоматы + +Конечный автомат (КА) в теории алгоритмов — математическая абстракция, модель дискретного устройства, имеющего один вход, один выход и в каждый момент времени находящегося в одном состоянии из множества возможных. + +При работе КА на вход последовательно поступают входные воздействия, а на выходе КА формирует выходные сигналы. Переход из одного внутреннего состояния КА в другое может происходить не только от внешнего воздействия, но и самопроизвольно. + +КА можно использовать для описания алгоритмов, позволяющих решать те или иные задачи, а также для моделирования практически любого процесса. Несколько примеров: + +- Логика искусственного интеллекта для игр; +- Синтаксический и лексический анализ; +- Сложные прикладные сетевые протоколы; +- Потоковая обработка данных. + +Ниже представлены примеры использования КА для формализации игровой логики нескольких игр из BrickGame. + +### Фроггер + +![Фроггер](misc/images/frogger-game.png) + +«Фроггер» — одна из поздних игр, выходящих на консолях Brickgame. Игра представляет собой игровое поле, по которому движутся бревна, и, перепрыгивая по ним, игроку необходимо перевести лягушку с одного берега на другой. Если игрок попадает в воду или лягушка уходит за пределы игрового поля, то лягушка погибает. Игра завершается, когда игрок доводит лягушку до другого берега или погибает последняя лягушка. + +Для формализации логики данной игры можно представить следующий вариант конечного автомата: + +![Конечный автомат фроггера](misc/images/frogger.jpg) + +Данный КА имеет следующие состояния: + +- Старт — состояние, в котором игра ждет, пока игрок нажмет кнопку готовности к игре. +- Спавн — состояние, в котором создается очередная лягушка. +- Перемещение — основное игровое состояние с обработкой ввода от пользователя: движение лягушки по полосе влево/право или прыжки вперед/назад. +- Сдвиг — состояние, которое наступает после истечения таймера, при котором все объекты на полосах сдвигаются вправо вместе с лягушкой. +- Столкновение — состояние, которое наступает, если после прыжка лягушка попадает в воду, или если после смещения бревен лягушка оказывается за пределами игрового поля. +- Достигнут другой берег — состояние, которое наступает при достижении лягушкой другого берега. +- Игра окончена — состояние, которое наступает после достижения другого берега или смерти последней лягушки. + +Пример реализации фроггера с использованием КА ты можешь найти в папке `code-samples`. + ### Тетрис ![Тетрис](misc/images/tetris-game.png) @@ -85,50 +160,3 @@ ### Часть 3. Дополнительно. Механика уровней Добавь в игру механику уровней. Каждый раз, когда игрок набирает 600 очков, уровень увеличивается на 1. Повышение уровня увеличивает скорость движения фигур. Максимальное количество уровней — 10. - -# Tetris Game - -Classic Tetris implementation in C11 with ncurses interface. - -## Requirements - -- GCC or Clang -- ncurses library -- Check framework (for tests) - -## Building - -make # Build the game -make run # Build and run -make test # Run unit tests -make install # Install to ~/.local/bin - -text - -## Controls - -- **Arrow Keys**: Move left/right, rotate (up) -- **Down Arrow**: Instant drop -- **Space/R**: Rotate figure -- **P**: Pause -- **S**: Restart game -- **Q**: Quit - -## Features - -- 7 classic Tetris figures -- Score tracking with persistent high score -- 10 speed levels -- Smooth time-based movement using POSIX `clock_gettime()` -- Structured programming principles - -## Architecture - -- **FSM-based game logic** with states: Init, Spawn, Move, Moving, Attaching, GameOver -- **Separated frontend/backend**: `brick_game/` (logic) and `gui/cli/` (display) -- **Time-based delays** for precise falling speed - -## License - -School 21 educational project - diff --git a/flake.lock b/flake.lock deleted file mode 100644 index 4d1227e..0000000 --- a/flake.lock +++ /dev/null @@ -1,61 +0,0 @@ -{ - "nodes": { - "flake-utils": { - "inputs": { - "systems": "systems" - }, - "locked": { - "lastModified": 1731533236, - "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "nixpkgs": { - "locked": { - "lastModified": 1758427187, - "narHash": "sha256-pHpxZ/IyCwoTQPtFIAG2QaxuSm8jWzrzBGjwQZIttJc=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "554be6495561ff07b6c724047bdd7e0716aa7b46", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "root": { - "inputs": { - "flake-utils": "flake-utils", - "nixpkgs": "nixpkgs" - } - }, - "systems": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/flake.nix b/flake.nix deleted file mode 100644 index a030f24..0000000 --- a/flake.nix +++ /dev/null @@ -1,42 +0,0 @@ -{ - description = "C Project with Check, Valgrind, Gcov support"; - - inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; - flake-utils.url = "github:numtide/flake-utils"; - }; - - outputs = { self, nixpkgs, flake-utils }: - flake-utils.lib.eachDefaultSystem (system: let - pkgs = nixpkgs.legacyPackages.${system}; - - buildInputs = with pkgs; [ - check # для unit-тестов - valgrind # для проверки утечек - lcov # для отчетов gcov - clang-tools # clang-format - gcc # компилятор - gnumake # make - ncurses # для TUI/CLI приложений - readline # для readline поддержки - zlib # сжатие данных - libxml2 # XML parsing - curl # HTTP клиент - openssl # криптография - sqlite # база данных - ]; - in { - devShells.default = pkgs.mkShell { - inherit buildInputs; - - shellHook = '' - export PATH="$HOME/.cargo/bin:$PATH" - echo "✅ C Project Dev Environment Loaded" - echo "🔧 Available tools:" - echo " - gcc, make, clang-format" - echo " - checkmk, valgrind, lcov" - echo "🚀 Run 'make test' to build and run tests" - ''; - }; - }); -} diff --git a/src/.gpskip b/src/.gpskip deleted file mode 100644 index 2c29003..0000000 --- a/src/.gpskip +++ /dev/null @@ -1,72 +0,0 @@ -.git/ -.vscode/ -.idea/ -target/ -build/ -dist/ -node_modules/ -*.log -*.tmp -*.swp -*.bak -.DS_Store -Thumbs.db - -# Prerequisites -*.d - -# Object files -*.o -*.ko -*.obj -*.elf - -# Linker output -*.ilk -*.map -*.exp - -# Precompiled Headers -*.gch -*.pch - -# Libraries -*.lib -*.a -*.la -*.lo - -# Shared objects (inc. Windows DLLs) -*.dll -*.so -*.so.* -*.dylib - -# Executables -*.exe -*.out -*.app -*.i*86 -*.x86_64 -*.hex - -# Debug files -*.dSYM/ -*.su -*.idb -*.pdb - -# Kernel Module Compile Results -*.mod* -*.cmd -.tmp_versions/ -modules.order -Module.symvers -Mkfile.old -dkms.conf -src/project.md -src/tetris_bin -src/.gpskip -.gpskip -ginpee.toml - diff --git a/src/Doxyfile b/src/Doxyfile deleted file mode 100644 index 72a050c..0000000 --- a/src/Doxyfile +++ /dev/null @@ -1,23 +0,0 @@ -# Doxyfile для генерации документации -PROJECT_NAME = "Tetris Game C API" -PROJECT_NUMBER = "1.0" -PROJECT_BRIEF = "Classic Tetris implementation with separated backend/frontend" -OUTPUT_DIRECTORY = dvi -INPUT = brick_game/tetris/00_tetris.h doc.md -RECURSIVE = NO -GENERATE_HTML = YES -GENERATE_LATEX = NO -EXTRACT_ALL = YES -EXTRACT_PRIVATE = NO -EXTRACT_STATIC = NO -FILE_PATTERNS = *.h *.md -HTML_OUTPUT = html -USE_MDFILE_AS_MAINPAGE = doc.md -JAVADOC_AUTOBRIEF = YES -OPTIMIZE_OUTPUT_FOR_C = YES -TYPEDEF_HIDES_STRUCT = YES -SHOW_INCLUDE_FILES = YES -SHOW_NAMESPACES = NO -QUIET = YES -WARNINGS = YES -WARN_IF_UNDOCUMENTED = YES diff --git a/src/Makefile b/src/Makefile deleted file mode 100644 index e4afbbd..0000000 --- a/src/Makefile +++ /dev/null @@ -1,129 +0,0 @@ -.PHONY: all clean install uninstall test test_coverage gcov_report dvi dist run style format - -CC ?= gcc -CFLAGS ?= -Wall -Wextra -Werror -std=c11 -g -D_POSIX_C_SOURCE=199309L -MMD -MP -CHECK_CFLAGS ?= -I/usr/include/check - -# Проверяем наличие библиотек -CHECK_LIBS_AVAILABLE := $(shell ldconfig -p 2>/dev/null | grep -q libsubunit && echo yes || echo no) - -ifeq ($(CHECK_LIBS_AVAILABLE),yes) - LDFLAGS ?= -lcheck -lrt -lpthread -lm -lncurses -lsubunit -else - LDFLAGS ?= -lcheck -lrt -lpthread -lm -lncurses -endif - -BUILDDIR = build -TETRISDIR = brick_game/tetris -CLIDIR = gui/cli -TESTDIR = test -GCOV_DIR = gcov_report -DVI_DIR = dvi - -# Файлы -TETRIS_SRC = $(shell find $(TETRISDIR) -name "*.c") -TETRIS_OBJ = $(TETRIS_SRC:.c=.o) -CLI_SRC = $(shell find $(CLIDIR) -name "*.c") -CLI_OBJ = $(CLI_SRC:.c=.o) -TEST_SRC = $(filter-out $(TESTDIR)/test.c, $(shell find $(TESTDIR) -name "*.c")) - -LIB_TETRIS = $(BUILDDIR)/libtetris.a -TARGET = $(BUILDDIR)/tetris_bin.out -TEST_TARGET = $(BUILDDIR)/test.out - -# Установка -PREFIX ?= $(HOME)/.local -BINDIR = $(PREFIX)/bin - -all: gcov_report dvi - -run: clean $(TARGET) - ./$(TARGET) - -install: clean $(TARGET) - mkdir -p $(BINDIR) - install -m 755 $(TARGET) $(BINDIR)/tetris - @echo "installed $(BINDIR)/tetris" - -uninstall: - rm -f $(BINDIR)/tetris - @echo "uninstalled $(BINDIR)/tetris" - -clean: - rm -rf $(CLI_OBJ) $(TETRIS_OBJ) $(TARGET) $(LIB_TETRIS) $(TEST_TARGET) - rm -rf $(TETRISDIR)/*.d $(CLIDIR)/*.d - rm -rf $(TETRISDIR)/*.gcda $(TETRISDIR)/*.gcno $(TETRISDIR)/*.gcov - rm -rf $(GCOV_DIR) $(DVI_DIR) - -test: clean $(LIB_TETRIS) - $(CC) $(CFLAGS) $(TEST_SRC) -L$(BUILDDIR) -ltetris $(LDFLAGS) -o $(TEST_TARGET) - ./$(TEST_TARGET) - -test_coverage: CFLAGS += --coverage -test_coverage: LDFLAGS += --coverage -test_coverage: clean $(LIB_TETRIS) - $(CC) $(CFLAGS) $(TEST_SRC) -L$(BUILDDIR) -ltetris $(LDFLAGS) -o $(TEST_TARGET) - ./$(TEST_TARGET) - -gcov_report: test_coverage - @mkdir -p $(GCOV_DIR)/obj - @find $(TETRISDIR) -name "*.gcda" -exec mv {} $(GCOV_DIR)/obj/ \; - @find $(TETRISDIR) -name "*.gcno" -exec mv {} $(GCOV_DIR)/obj/ \; - @cd $(GCOV_DIR) && for src in $(addprefix ../,$(TETRIS_SRC)); do \ - gcov $$src -o obj/ 2>/dev/null; \ - done - lcov --capture --directory $(GCOV_DIR)/obj \ - --output-file $(GCOV_DIR)/coverage.info \ - --ignore-errors unused - lcov --extract $(GCOV_DIR)/coverage.info '*/brick_game/tetris/*' \ - -o $(GCOV_DIR)/coverage.info \ - --ignore-errors unused - genhtml $(GCOV_DIR)/coverage.info \ - --output-directory $(GCOV_DIR) - @echo "Report: $(GCOV_DIR)/index.html" - xdg-open $(GCOV_DIR)/index.html - - -dvi: - @mkdir -p $(DVI_DIR) - @echo "Generating documentation with Doxygen..." - @if command -v doxygen >/dev/null 2>&1; then \ - doxygen Doxyfile && echo "HTML docs: $(DVI_DIR)/html/index.html"; \ - else \ - echo "Error: Doxygen not found. Install: nix-shell -p doxygen"; \ - echo "Copying doc.md as fallback..."; \ - cp doc.md $(DVI_DIR)/; \ - fi - xdg-open $(DVI_DIR)/html/index.html - -dist: clean - tar -czf tetris.tar.gz Makefile $(TETRISDIR) $(CLIDIR) $(TESTDIR) ../README.md doc.md - -style: - @if [ -f ../materials/linters/.clang-format ]; then \ - clang-format -n $(TETRIS_SRC) $(CLI_SRC) $(TEST_SRC); \ - else \ - echo ".clang-format not found"; \ - fi - -format: - @if [ -f ../materials/linters/.clang-format ]; then \ - clang-format -i $(TETRIS_SRC) $(CLI_SRC) $(TEST_SRC); \ - else \ - echo ".clang-format not found"; \ - fi - -$(LIB_TETRIS): $(TETRIS_OBJ) - mkdir -p $(BUILDDIR) - ar rcs $@ $^ - -$(TARGET): $(LIB_TETRIS) $(CLI_OBJ) - $(CC) $(CLI_OBJ) -L$(BUILDDIR) -ltetris -o $@ $(LDFLAGS) - -brick_game/tetris/%.o: brick_game/tetris/%.c - $(CC) $(CFLAGS) -c $< -o $@ - -gui/cli/%.o: gui/cli/%.c - $(CC) $(CFLAGS) -c $< -o $@ - --include $(TETRIS_OBJ:.o=.d) $(CLI_OBJ:.o=.d) diff --git a/src/brick_game/tetris/00_tetris.h b/src/brick_game/tetris/00_tetris.h deleted file mode 100644 index 3e0624a..0000000 --- a/src/brick_game/tetris/00_tetris.h +++ /dev/null @@ -1,165 +0,0 @@ -/** - * @file 00_tetris.h - * @brief Public API for Tetris game backend - * - * This header defines the public interface for Tetris game logic. - * The backend is completely separated from UI, allowing integration - * with any frontend (ncurses, SDL, Qt, Rust, etc.). - * - * @note Thread safety: NOT thread-safe! Use from single thread only. - * @note Memory management: All memory is managed internally. Call - * userInput(Terminate, false) before exit to free resources. - */ - -#ifndef TETRIS_H -#define TETRIS_H - -#include -#include - -/** - * @brief Width of the game field in blocks - */ -#define FIELD_WIDTH 10 - -/** - * @brief Height of the game field in blocks - */ -#define FIELD_HEIGHT 20 - -/** - * @enum UserAction_t - * @brief User input actions - * - * Represents all possible user actions in the game. - */ -typedef enum { - Start, /**< Initialize or restart the game */ - Pause, /**< Toggle pause state */ - Terminate, /**< Quit game and free all resources */ - Left, /**< Move figure left */ - Right, /**< Move figure right */ - Up, /**< Release instant drop flag (for next drop) */ - Down, /**< Instant drop figure to bottom */ - Action /**< Rotate figure clockwise */ -} UserAction_t; - -/** - * @struct GameInfo_t - * @brief Game state information for rendering - * - * Contains all information needed to render the game state. - * This structure is returned by updateCurrentState() every frame. - * - * @note Do not free the pointers - memory is managed internally - */ -typedef struct { - /** - * @brief 2D game field [FIELD_HEIGHT][FIELD_WIDTH] - * - * Values: - * - 0: Empty cell - * - 1: Active (falling) figure - * - 2: Placed (fixed) blocks - */ - int **field; - - /** - * @brief Preview of next figure [4][4] - * - * 4x4 matrix containing the next figure shape. - * Values: 0 (empty) or 1 (figure block) - */ - int **next; - - int score; /**< Current score */ - int high_score; /**< Best score (persistent across sessions) */ - int level; /**< Current level (1-10) */ - int speed; /**< Current speed multiplier */ - int pause; /**< Pause state: 0=playing, 1=paused */ -} GameInfo_t; - -/** - * @brief Process user input and update game state - * - * This function handles all user actions and updates the internal FSM state. - * Should be called when user performs an action (key press, button click, etc.). - * - * @param action User action from UserAction_t enum - * @param hold Reserved for future use (currently ignored) - * - * @note Actions are blocked during pause (except Pause, Terminate, Start) - * @note Actions are blocked during Attaching state (except Pause, Terminate, Start) - * @note Down action requires key release between uses to prevent double-drop - * - * @par Example: - * @code - * // Handle left arrow key press - * userInput(Left, false); - * - * // Start new game - * userInput(Start, false); - * - * // Quit and cleanup - * userInput(Terminate, false); - * @endcode - * - * @par FSI Integration (Rust example): - * @code{.rs} - * extern "C" { - * fn userInput(action: UserAction_t, hold: bool); - * } - * - * unsafe { - * userInput(UserAction_t::Left, false); - * } - * @endcode - */ -void userInput(UserAction_t action, bool hold); - -/** - * @brief Update game state and return current game information - * - * This function must be called every game loop iteration (recommended ~10ms). - * It updates the internal FSM, handles timing, figure movement, collisions, - * and returns the current state for rendering. - * - * @return GameInfo_t structure with current game state - * - * @note Call frequency: ~100 times per second (every 10ms) - * @note Timing is handled internally using POSIX clock_gettime(CLOCK_MONOTONIC) - * @note The returned structure contains pointers to internal memory - do not free! - * - * @par Example: - * @code - * // Game loop - * while (running) { - * GameInfo_t state = updateCurrentState(); - * - * // Render game field - * for (int i = 0; i < FIELD_HEIGHT; i++) { - * for (int j = 0; j < FIELD_WIDTH; j++) { - * if (state.field[i][j] == 1) { - * draw_active_block(j, i); - * } else if (state.field[i][j] == 2) { - * draw_placed_block(j, i); - * } - * } - * } - * - * // Display score - * printf("Score: %d Level: %d\n", state.score, state.level); - * - * sleep_ms(10); - * } - * @endcode - * - * @par Timing behavior: - * - Base fall delay: 1100ms - (speed × 100ms) - * - Instant drop: 30ms per step - * - Attach delay: 350ms (prevents instant spawn) - * - Pause correctly compensates all timings - */ -GameInfo_t updateCurrentState(); - -#endif /* TETRIS_H */ diff --git a/src/brick_game/tetris/01_automato.h b/src/brick_game/tetris/01_automato.h deleted file mode 100644 index 7551fda..0000000 --- a/src/brick_game/tetris/01_automato.h +++ /dev/null @@ -1,126 +0,0 @@ -#ifndef AUTOMATO_H -#define AUTOMATO_H - -#define _POSIX_C_SOURCE 199309L - -#include "00_tetris.h" -#include -#include -#include - -#define ATTACH_DELAY_MS 350 -#define INSTANT_DROP_DELAY_MS 30 -#define BASE_FALL_DELAY_MS 1100 -#define SPEED_MULTIPLIER_MS 100 -#define MAX_LEVEL 10 - -#define SCORE_PER_LEVEL 600 -#define POINTS_ONE_LINE 100 -#define POINTS_TWO_LINES 300 -#define POINTS_THREE_LINES 700 -#define POINTS_FOUR_LINES 1500 - - -typedef enum { - Init, - Spawn, - Moving, - Move, - Attaching, - GameOver -} Automato_t; - -typedef enum { - RightDown, - LeftDown, - Rotate, - ToDown, - DoNothing -} Moving_t; - -typedef enum { - I = 0, - J, - L, - O, - S, - T, - Z, - FIGURE_COUNT -} Sprite_t; - -typedef struct { - int x, y; - int mtrx[4][4]; - Sprite_t sprite; - int rotation; -} Figure_t; - -typedef struct { - Figure_t curr; - Figure_t next; - Automato_t state; - Moving_t moving_type; - int field[FIELD_HEIGHT][FIELD_WIDTH]; - GameInfo_t* info; - long long last_move_time; - long long pause_start_time; - long long attach_start_time; - int attach_completed; - int down_key_was_released; -} GameState_t; - -GameState_t* get_game_state(void); - -void do_init(void); -int load_high_score(); -void save_high_score(int score); -void generate_next_figure(void); -void terminate_and_free(void); - -long long get_current_time_ms(void); - -void do_spawn(void); - -void do_move(void); - -void do_moving(void); - -void do_attaching(void); -int check_collision(); -void place_figure(); -void clear_lines(); - -void do_gameover(void); -int is_game_over(); - -const int (*get_figure_shape(Sprite_t sprite, int rotation))[4]; - -const int (*i_fig_up())[4]; -const int (*i_fig_right())[4]; -const int (*i_fig_down())[4]; -const int (*i_fig_left())[4]; -const int (*o_fig())[4]; -const int (*t_fig_up())[4]; -const int (*t_fig_right())[4]; -const int (*t_fig_down())[4]; -const int (*t_fig_left())[4]; -const int (*l_fig_up())[4]; -const int (*l_fig_right())[4]; -const int (*l_fig_down())[4]; -const int (*l_fig_left())[4]; -const int (*j_fig_up())[4]; -const int (*j_fig_right())[4]; -const int (*j_fig_down())[4]; -const int (*j_fig_left())[4]; -const int (*s_fig_up())[4]; -const int (*s_fig_right())[4]; -const int (*s_fig_down())[4]; -const int (*s_fig_left())[4]; -const int (*z_fig_up())[4]; -const int (*z_fig_right())[4]; -const int (*z_fig_down())[4]; -const int (*z_fig_left())[4]; -const int (*empty_fig())[4]; - -#endif diff --git a/src/brick_game/tetris/02_tetris.c b/src/brick_game/tetris/02_tetris.c deleted file mode 100644 index e8210c4..0000000 --- a/src/brick_game/tetris/02_tetris.c +++ /dev/null @@ -1,136 +0,0 @@ -#include "01_automato.h" - -void userInput(UserAction_t action, bool hold) { - (void)hold; - GameState_t *state = get_game_state(); - - int should_process = 1; - - if (state->info->pause) { - if (action == Left || action == Right || action == Down || action == Up || - action == Action || action == Start) { - should_process = 0; - } - } - - if (state->state == Attaching && !state->attach_completed) { - if (action == Left || action == Right || action == Down || action == Up || - action == Action) { - should_process = 0; - } - } - - if (should_process) { - switch (action) { - case Start: - if (state->info->score >= state->info->high_score) { - state->info->high_score = state->info->score; - save_high_score(state->info->high_score); - } - state->info->high_score = load_high_score(); - state->state = Init; - state->down_key_was_released = 1; - break; - case Terminate: - if (state->info->score > state->info->high_score) { - state->info->high_score = state->info->score; - save_high_score(state->info->high_score); - } - terminate_and_free(); - state->state = GameOver; - break; - case Left: - state->state = Moving; - state->moving_type = LeftDown; - break; - case Right: - state->state = Moving; - state->moving_type = RightDown; - break; - case Action: - state->state = Moving; - state->moving_type = Rotate; - break; - case Down: - if (state->down_key_was_released) { - state->state = Moving; - state->moving_type = ToDown; - state->down_key_was_released = 0; - } - break; - case Up: - state->down_key_was_released = 1; - break; - case Pause: - if (!state->info->pause) { - state->pause_start_time = get_current_time_ms(); - } else { - long long pause_duration = - get_current_time_ms() - state->pause_start_time; - state->last_move_time += pause_duration; - // Корректируем attach_start_time если мы в Attaching - state->attach_start_time += - (state->state == Attaching) * pause_duration; - } - state->info->pause = !state->info->pause; - break; - default: - break; - } - } -} - -GameInfo_t updateCurrentState() { - GameState_t *state = get_game_state(); - - int should_update = (!state->info->pause || state->state == GameOver); - if (should_update) { - switch (state->state) { - case Init: - do_init(); - break; - case Spawn: - do_spawn(); - break; - case Move: - do_move(); - break; - case Moving: - do_moving(); - break; - case Attaching: - do_attaching(); - break; - case GameOver: - do_gameover(); - break; - } - } - - for (int i = 0; i < FIELD_HEIGHT; i++) { - for (int j = 0; j < FIELD_WIDTH; j++) { - state->info->field[i][j] = state->field[i][j]; - } - } - - Figure_t *fig = &state->curr; - for (int i = 0; i < 4; i++) { - for (int j = 0; j < 4; j++) { - if (fig->mtrx[i][j]) { - int x = fig->x + j; - int y = fig->y + i; - if (y >= 0 && y < FIELD_HEIGHT && x >= 0 && x < FIELD_WIDTH) { - state->info->field[y][x] = 1; - } - } - } - } - - for (int i = 0; i < 4; i++) { - for (int j = 0; j < 4; j++) { - state->info->next[i][j] = state->next.mtrx[i][j]; - } - } - - return *state->info; -} diff --git a/src/brick_game/tetris/03_automato.c b/src/brick_game/tetris/03_automato.c deleted file mode 100644 index ce84744..0000000 --- a/src/brick_game/tetris/03_automato.c +++ /dev/null @@ -1,91 +0,0 @@ -#include "01_automato.h" - -long long get_current_time_ms(void) { - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - return ts.tv_sec * 1000LL + ts.tv_nsec / 1000000LL; -} - -int load_high_score() { - FILE *file = fopen("build/high_score.txt", "r"); - int high_score = 0; - if (file) { - if (fscanf(file, "%d", &high_score) != 1) { - high_score = 0; - } - fclose(file); - } - return high_score; -} - -void save_high_score(int score) { - FILE *file = fopen("build/high_score.txt", "w"); - if (file) { - fprintf(file, "%d", score); - fclose(file); - } -} - -GameState_t *get_game_state(void) { - static GameState_t state = {0}; - static int initialized = 0; - - if (!initialized) { - state.info = malloc(sizeof(GameInfo_t)); - state.info->field = malloc(FIELD_HEIGHT * sizeof(int *)); - for (int i = 0; i < FIELD_HEIGHT; i++) { - state.info->field[i] = malloc(FIELD_WIDTH * sizeof(int)); - } - - state.info->next = malloc(4 * sizeof(int *)); - for (int i = 0; i < 4; i++) { - state.info->next[i] = malloc(4 * sizeof(int)); - } - state.info->speed = 1; - state.info->score = 0; - state.info->level = 1; - state.info->pause = 0; - state.last_move_time = get_current_time_ms(); - state.pause_start_time = 0; - state.attach_start_time = 0; - state.attach_completed = 0; - state.down_key_was_released = 1; - state.info->high_score = load_high_score(); - - state.state = GameOver; - initialized = 1; - } - - return &state; -} - -void terminate_and_free() { - GameState_t *state = get_game_state(); - - if (state->info) { - if (state->info->field != NULL) { - for (int i = 0; i < FIELD_HEIGHT; i++) { - if (state->info->field[i] != NULL) { - free(state->info->field[i]); - state->info->field[i] = NULL; - } - } - free(state->info->field); - state->info->field = NULL; - } - - if (state->info->next != NULL) { - for (int i = 0; i < 4; i++) { - if (state->info->next[i] != NULL) { - free(state->info->next[i]); - state->info->next[i] = NULL; - } - } - free(state->info->next); - state->info->next = NULL; - } - - free(state->info); - state->info = NULL; - } -} diff --git a/src/brick_game/tetris/04_init.c b/src/brick_game/tetris/04_init.c deleted file mode 100644 index e32e6b3..0000000 --- a/src/brick_game/tetris/04_init.c +++ /dev/null @@ -1,23 +0,0 @@ -#include "01_automato.h" - -void clear_field(void) { - GameState_t *state = get_game_state(); - for (int i = 0; i < FIELD_HEIGHT; ++i) - for (int j = 0; j < FIELD_WIDTH; ++j) - state->field[i][j] = 0; -} - -void reset_game_stats(void) { - GameState_t *state = get_game_state(); - state->info->score = 0; - state->info->level = 1; - state->info->speed = 1; - state->last_move_time = get_current_time_ms(); -} - -void do_init(void) { - clear_field(); - reset_game_stats(); - generate_next_figure(); - get_game_state()->state = Spawn; -} diff --git a/src/brick_game/tetris/05_spawn.c b/src/brick_game/tetris/05_spawn.c deleted file mode 100644 index 51e5874..0000000 --- a/src/brick_game/tetris/05_spawn.c +++ /dev/null @@ -1,37 +0,0 @@ -#include "01_automato.h" - -void set_current_figure_from_next(void) { - GameState_t *state = get_game_state(); - state->curr = state->next; - state->curr.x = FIELD_WIDTH / 2 - 2; - state->curr.y = 0; - state->moving_type = DoNothing; -} - -void generate_next_figure(void) { - GameState_t *state = get_game_state(); - state->next.sprite = rand() % FIGURE_COUNT; - state->next.rotation = 0; - const int(*shape)[4] = get_figure_shape(state->next.sprite, 0); - for (int i = 0; i < 4; ++i) { - for (int j = 0; j < 4; ++j) { - state->next.mtrx[i][j] = shape[i][j]; - } - } -} - -void do_spawn(void) { - GameState_t *state = get_game_state(); - - set_current_figure_from_next(); - generate_next_figure(); - - int has_collision = check_collision(); - - if (has_collision) { - state->state = GameOver; - } else { - state->state = Move; - state->down_key_was_released = 1; - } -} diff --git a/src/brick_game/tetris/06_move.c b/src/brick_game/tetris/06_move.c deleted file mode 100644 index 89d92cf..0000000 --- a/src/brick_game/tetris/06_move.c +++ /dev/null @@ -1,38 +0,0 @@ -#include "01_automato.h" - -int get_milliseconds_to_wait(void) { - GameState_t *state = get_game_state(); - int result = 0; - - if (state->moving_type == ToDown) { - result = INSTANT_DROP_DELAY_MS; - } else { - int base_delay = - BASE_FALL_DELAY_MS - (state->info->speed * SPEED_MULTIPLIER_MS); - result = - (base_delay > SPEED_MULTIPLIER_MS) ? base_delay : SPEED_MULTIPLIER_MS; - } - - return result; -} - -void do_move(void) { - GameState_t *state = get_game_state(); - - long long current_time = get_current_time_ms(); - int ms_to_wait = get_milliseconds_to_wait(); - - int should_move = (current_time - state->last_move_time >= ms_to_wait); - - if (should_move) { - state->last_move_time = current_time; - - state->curr.y++; - int has_collision = check_collision(); - - if (has_collision) { - state->curr.y--; - state->state = Attaching; - } - } -} diff --git a/src/brick_game/tetris/07_moving.c b/src/brick_game/tetris/07_moving.c deleted file mode 100644 index 14c8fe3..0000000 --- a/src/brick_game/tetris/07_moving.c +++ /dev/null @@ -1,74 +0,0 @@ -#include "01_automato.h" - -void handle_move_direction(Moving_t direction) { - GameState_t *state = get_game_state(); - switch (direction) { - case LeftDown: - state->curr.x--; - break; - case RightDown: - state->curr.x++; - break; - default: - break; - } -} - -void handle_rotate(void) { - GameState_t *state = get_game_state(); - state->curr.rotation = (state->curr.rotation + 1) % 4; - const int(*shape)[4] = - get_figure_shape(state->curr.sprite, state->curr.rotation); - for (int i = 0; i < 4; ++i) - for (int j = 0; j < 4; ++j) - state->curr.mtrx[i][j] = shape[i][j]; -} - -void handle_horizontal_rotate_move(void) { - GameState_t *state = get_game_state(); - Figure_t old = state->curr; - - switch (state->moving_type) { - case LeftDown: - case RightDown: - handle_move_direction(state->moving_type); - break; - case Rotate: - handle_rotate(); - break; - default: - break; - } - - if (check_collision()) { - state->curr = old; - } - state->state = Move; -} - -void handle_to_down_move(void) { - GameState_t *state = get_game_state(); - while (!check_collision()) { - state->curr.y++; - } - state->curr.y--; - state->state = Attaching; -} - -void do_moving(void) { - GameState_t *state = get_game_state(); - - switch (state->moving_type) { - case LeftDown: - case RightDown: - case Rotate: - handle_horizontal_rotate_move(); - break; - case ToDown: - handle_to_down_move(); - break; - case DoNothing: - state->state = Move; - break; - } -} \ No newline at end of file diff --git a/src/brick_game/tetris/08_attaching.c b/src/brick_game/tetris/08_attaching.c deleted file mode 100644 index 6d443a2..0000000 --- a/src/brick_game/tetris/08_attaching.c +++ /dev/null @@ -1,124 +0,0 @@ -#include "01_automato.h" - -void do_attaching(void) { - GameState_t *state = get_game_state(); - long long current_time = get_current_time_ms(); - - if (!state->attach_completed) { - if (state->attach_start_time == 0) { - place_figure(); - clear_lines(); - state->attach_start_time = current_time; - state->attach_completed = 0; - } - - if (current_time - state->attach_start_time >= ATTACH_DELAY_MS) { - state->attach_completed = 1; - state->attach_start_time = 0; - - int game_over = is_game_over(); - - if (game_over) { - state->state = GameOver; - } else { - state->state = Spawn; - } - - state->attach_completed = 0; - } - } -} - -int check_collision() { - GameState_t *state = get_game_state(); - Figure_t *fig = &state->curr; - int collision_detected = 0; - - for (int i = 0; i < 4 && !collision_detected; ++i) { - for (int j = 0; j < 4 && !collision_detected; ++j) { - if (fig->mtrx[i][j]) { - int x = fig->x + j; - int y = fig->y + i; - - int out_of_bounds = (x < 0 || x >= FIELD_WIDTH || y >= FIELD_HEIGHT); - int hits_placed_block = (y >= 0 && state->field[y][x]); - - if (out_of_bounds || hits_placed_block) { - collision_detected = 1; - } - } - } - } - - return collision_detected; -} - -void place_figure() { - GameState_t *state = get_game_state(); - Figure_t *fig = &state->curr; - - for (int i = 0; i < 4; ++i) { - for (int j = 0; j < 4; ++j) { - if (fig->mtrx[i][j]) { - int x = fig->x + j; - int y = fig->y + i; - if (y >= 0 && y < FIELD_HEIGHT && x >= 0 && x < FIELD_WIDTH) { - state->field[y][x] = 2; - } - } - } - } -} - -void shift_lines_down(int from_row) { - GameState_t *state = get_game_state(); - for (int y = from_row; y > 0; --y) { - for (int x = 0; x < FIELD_WIDTH; ++x) { - state->field[y][x] = state->field[y - 1][x]; - } - } - for (int x = 0; x < FIELD_WIDTH; ++x) { - state->field[0][x] = 0; - } -} - -void clear_lines() { - GameState_t *state = get_game_state(); - int lines_cleared = 0; - - for (int i = FIELD_HEIGHT - 1; i >= 0; --i) { - int full = 1; - - for (int j = 0; j < FIELD_WIDTH; ++j) { - if (state->field[i][j] != 2) { - full = 0; - } - } - - if (full) { - shift_lines_down(i); - lines_cleared++; - i++; - } - } - - if (lines_cleared > 0) { - int points[] = {0, POINTS_ONE_LINE, POINTS_TWO_LINES, POINTS_THREE_LINES, - POINTS_FOUR_LINES}; - state->info->score += points[lines_cleared]; - - if (state->info->score > state->info->high_score) { - state->info->high_score = state->info->score; - } - - int new_level = (state->info->score / SCORE_PER_LEVEL) + 1; - if (new_level > MAX_LEVEL) { - new_level = MAX_LEVEL; - } - - if (new_level > state->info->level) { - state->info->level = new_level; - state->info->speed = new_level + (new_level / 2); - } - } -} diff --git a/src/brick_game/tetris/09_gameover.c b/src/brick_game/tetris/09_gameover.c deleted file mode 100644 index 6c38c32..0000000 --- a/src/brick_game/tetris/09_gameover.c +++ /dev/null @@ -1,30 +0,0 @@ -#include "01_automato.h" - -void do_gameover(void) { - GameState_t *state = get_game_state(); - - if (state->info->score > state->info->high_score) { - state->info->high_score = state->info->score; - save_high_score(state->info->high_score); - } - - const int(*shape)[4] = empty_fig(); - for (int i = 0; i < 4; ++i) { - for (int j = 0; j < 4; ++j) { - state->next.mtrx[i][j] = shape[i][j]; - } - } -} - -int is_game_over() { - GameState_t *state = get_game_state(); - int game_over = 0; - - for (int j = 0; j < FIELD_WIDTH; ++j) { - if (state->field[0][j] || state->field[1][j]) { - game_over = 1; - } - } - - return game_over; -} diff --git a/src/brick_game/tetris/figure_sprites.c b/src/brick_game/tetris/figure_sprites.c deleted file mode 100644 index 6803c60..0000000 --- a/src/brick_game/tetris/figure_sprites.c +++ /dev/null @@ -1,272 +0,0 @@ -#include "01_automato.h" - -const int (*get_figure_shape(Sprite_t sprite, int rotation))[4] { - const int(*result)[4] = NULL; - switch (sprite) { - case I: - switch (rotation % 4) { - case 0: - result = i_fig_up(); - break; - case 1: - result = i_fig_right(); - break; - case 2: - result = i_fig_down(); - break; - case 3: - result = i_fig_left(); - break; - } - break; - case O: - result = o_fig(); - break; - case T: - switch (rotation % 4) { - case 0: - result = t_fig_up(); - break; - case 1: - result = t_fig_right(); - break; - case 2: - result = t_fig_down(); - break; - case 3: - result = t_fig_left(); - break; - } - break; - case L: - switch (rotation % 4) { - case 0: - result = l_fig_up(); - break; - case 1: - result = l_fig_right(); - break; - case 2: - result = l_fig_down(); - break; - case 3: - result = l_fig_left(); - break; - } - break; - case J: - switch (rotation % 4) { - case 0: - result = j_fig_up(); - break; - case 1: - result = j_fig_right(); - break; - case 2: - result = j_fig_down(); - break; - case 3: - result = j_fig_left(); - break; - } - break; - case S: - switch (rotation % 4) { - case 0: - result = s_fig_up(); - break; - case 1: - result = s_fig_right(); - break; - case 2: - result = s_fig_down(); - break; - case 3: - result = s_fig_left(); - break; - } - break; - case Z: - switch (rotation % 4) { - case 0: - result = z_fig_up(); - break; - case 1: - result = z_fig_right(); - break; - case 2: - result = z_fig_down(); - break; - case 3: - result = z_fig_left(); - break; - } - break; - default: - result = NULL; - } - return result; -} - -// I -const int (*i_fig_up())[4] { - static const int shape[4][4] = { - {0, 0, 0, 0}, {1, 1, 1, 1}, {0, 0, 0, 0}, {0, 0, 0, 0}}; - return (const int(*)[4])shape; -} - -const int (*i_fig_right())[4] { - static const int shape[4][4] = { - {0, 0, 1, 0}, {0, 0, 1, 0}, {0, 0, 1, 0}, {0, 0, 1, 0}}; - return (const int(*)[4])shape; -} - -const int (*i_fig_down())[4] { - static const int shape[4][4] = { - {0, 0, 0, 0}, {0, 0, 0, 0}, {1, 1, 1, 1}, {0, 0, 0, 0}}; - return (const int(*)[4])shape; -} - -const int (*i_fig_left())[4] { - static const int shape[4][4] = { - {0, 1, 0, 0}, {0, 1, 0, 0}, {0, 1, 0, 0}, {0, 1, 0, 0}}; - return (const int(*)[4])shape; -} - -// O -const int (*o_fig())[4] { - static const int shape[4][4] = { - {0, 1, 1, 0}, {0, 1, 1, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}}; - return (const int(*)[4])shape; -} - -// T -const int (*t_fig_up())[4] { - static const int shape[4][4] = { - {0, 1, 0, 0}, {1, 1, 1, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}}; - return (const int(*)[4])shape; -} - -const int (*t_fig_right())[4] { - static const int shape[4][4] = { - {0, 1, 0, 0}, {0, 1, 1, 0}, {0, 1, 0, 0}, {0, 0, 0, 0}}; - return (const int(*)[4])shape; -} - -const int (*t_fig_down())[4] { - static const int shape[4][4] = { - {0, 0, 0, 0}, {1, 1, 1, 0}, {0, 1, 0, 0}, {0, 0, 0, 0}}; - return (const int(*)[4])shape; -} - -const int (*t_fig_left())[4] { - static const int shape[4][4] = { - {0, 1, 0, 0}, {1, 1, 0, 0}, {0, 1, 0, 0}, {0, 0, 0, 0}}; - return (const int(*)[4])shape; -} - -// L -const int (*l_fig_up())[4] { - static const int shape[4][4] = { - {0, 0, 1, 0}, {1, 1, 1, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}}; - return (const int(*)[4])shape; -} - -const int (*l_fig_right())[4] { - static const int shape[4][4] = { - {1, 0, 0, 0}, {1, 0, 0, 0}, {1, 1, 0, 0}, {0, 0, 0, 0}}; - return (const int(*)[4])shape; -} - -const int (*l_fig_down())[4] { - static const int shape[4][4] = { - {0, 0, 0, 0}, {1, 1, 1, 0}, {1, 0, 0, 0}, {0, 0, 0, 0}}; - return (const int(*)[4])shape; -} - -const int (*l_fig_left())[4] { - static const int shape[4][4] = { - {1, 1, 0, 0}, {0, 1, 0, 0}, {0, 1, 0, 0}, {0, 0, 0, 0}}; - return (const int(*)[4])shape; -} - -// J -const int (*j_fig_up())[4] { - static const int shape[4][4] = { - {1, 0, 0, 0}, {1, 1, 1, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}}; - return (const int(*)[4])shape; -} - -const int (*j_fig_right())[4] { - static const int shape[4][4] = { - {0, 1, 1, 0}, {0, 1, 0, 0}, {0, 1, 0, 0}, {0, 0, 0, 0}}; - return (const int(*)[4])shape; -} - -const int (*j_fig_down())[4] { - static const int shape[4][4] = { - {0, 0, 0, 0}, {1, 1, 1, 0}, {0, 0, 1, 0}, {0, 0, 0, 0}}; - return (const int(*)[4])shape; -} - -const int (*j_fig_left())[4] { - static const int shape[4][4] = { - {0, 1, 0, 0}, {0, 1, 0, 0}, {1, 1, 0, 0}, {0, 0, 0, 0}}; - return (const int(*)[4])shape; -} - -// S -const int (*s_fig_up())[4] { - static const int shape[4][4] = { - {0, 1, 1, 0}, {1, 1, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}}; - return (const int(*)[4])shape; -} - -const int (*s_fig_right())[4] { - static const int shape[4][4] = { - {0, 1, 0, 0}, {0, 1, 1, 0}, {0, 0, 1, 0}, {0, 0, 0, 0}}; - return (const int(*)[4])shape; -} - -const int (*s_fig_down())[4] { - static const int shape[4][4] = { - {0, 1, 1, 0}, {1, 1, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}}; - return (const int(*)[4])shape; -} - -const int (*s_fig_left())[4] { - static const int shape[4][4] = { - {0, 1, 0, 0}, {0, 1, 1, 0}, {0, 0, 1, 0}, {0, 0, 0, 0}}; - return (const int(*)[4])shape; -} - -// Z -const int (*z_fig_up())[4] { - static const int shape[4][4] = { - {1, 1, 0, 0}, {0, 1, 1, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}}; - return (const int(*)[4])shape; -} - -const int (*z_fig_right())[4] { - static const int shape[4][4] = { - {0, 0, 1, 0}, {0, 1, 1, 0}, {0, 1, 0, 0}, {0, 0, 0, 0}}; - return (const int(*)[4])shape; -} - -const int (*z_fig_down())[4] { - static const int shape[4][4] = { - {1, 1, 0, 0}, {0, 1, 1, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}}; - return (const int(*)[4])shape; -} - -const int (*z_fig_left())[4] { - static const int shape[4][4] = { - {0, 0, 1, 0}, {0, 1, 1, 0}, {0, 1, 0, 0}, {0, 0, 0, 0}}; - return (const int(*)[4])shape; -} - -const int (*empty_fig())[4] { - static const int shape[4][4] = { - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}}; - return (const int(*)[4])shape; -} \ No newline at end of file diff --git a/src/diagram.pdf b/src/diagram.pdf deleted file mode 100644 index fc85637..0000000 Binary files a/src/diagram.pdf and /dev/null differ diff --git a/src/diagram_also.pdf b/src/diagram_also.pdf deleted file mode 100644 index 2d8ae01..0000000 Binary files a/src/diagram_also.pdf and /dev/null differ diff --git a/src/doc.md b/src/doc.md deleted file mode 100644 index 847cd44..0000000 --- a/src/doc.md +++ /dev/null @@ -1,218 +0,0 @@ -# Tetris Game API Documentation - -## Overview - -This is a Tetris game implementation following FSM (Finite State Machine) pattern. -The backend is completely separated from frontend, allowing easy integration with any UI. - -## Public API - -### `void userInput(UserAction_t action, bool hold)` - -Processes user input and updates game state accordingly. - -**Parameters:** - -- `action`: User action from enum `UserAction_t` - - `Start`: Initialize/restart game - - `Pause`: Toggle pause state - - `Terminate`: Quit game and free resources - - `Left`: Move figure left - - `Right`: Move figure right - - `Up`: Release instant drop flag (for next drop) - - `Down`: Instant drop figure - - `Action`: Rotate figure -- `hold`: Reserved for future use (currently ignored) - -**Behavior:** - -- Blocked during pause (except Pause/Terminate/Start) -- Blocked during Attaching state (except Pause/Terminate/Start) -- Down action requires key release between uses (prevents double-drop) - -**Example (Rust FFI):** - -```rust -extern "C" { - fn userInput(action: UserAction_t, hold: bool); -} - -// In your Rust code: -unsafe { - userInput(UserAction_t::Left, false); -} -``` - ---- - -### `GameInfo_t updateCurrentState()` - -Updates game state machine and returns current game information for rendering. - -**Returns:** - -- `GameInfo_t` structure containing: - - `field`: 2D array `[FIELD_HEIGHT][FIELD_WIDTH]` representing game field - - `0`: Empty cell - - `1`: Active (falling) figure - - `2`: Placed (fixed) blocks - - `next`: 2D array `[4][4]` with next figure preview - - `score`: Current score - - `high_score`: Best score (persistent) - - `level`: Current level (1-10) - - `speed`: Current speed multiplier - - `pause`: Pause state (0=playing, 1=paused) - -**Call frequency:** -Should be called every game loop iteration (~10ms recommended). -The function handles timing internally using `clock_gettime(CLOCK_MONOTONIC)`. - -**Example (Rust FFI):** - -```rust -#[repr(C)] -struct GameInfo_t { - field: *mut *mut c_int, - next: *mut *mut c_int, - score: c_int, - high_score: c_int, - level: c_int, - speed: c_int, - pause: c_int, -} - -extern "C" { - fn updateCurrentState() -> GameInfo_t; -} - -// In your game loop: -loop { - let state = unsafe { updateCurrentState() }; - render_game(state); - std::thread::sleep(Duration::from_millis(10)); -} -``` - ---- - -## Game Constants - -```c -#define FIELD_WIDTH 10 -#define FIELD_HEIGHT 20 -``` - -## FSM States (Internal) - -- **Init**: Initialize game field and stats -- **Spawn**: Create new figure at top -- **Move**: Automatic figure falling -- **Moving**: User-controlled movement/rotation -- **Attaching**: Figure placement with delay -- **GameOver**: Game ended, waiting for restart - -## Timing - -- Base fall delay: 1100ms - (speed × 100ms) -- Instant drop delay: 30ms per step -- Attach delay: 350ms (prevents double-spawn) -- Pause compensates all timings correctly - -## Score System - -- 1 line: 100 points -- 2 lines: 300 points -- 3 lines: 700 points -- 4 lines: 1500 points -- Level up: every 600 points -- Speed increases with level (max level 10) - -## Memory Management - -All memory is managed internally. Call `userInput(Terminate, false)` before exit -to properly free resources. - -## Thread Safety - -**Not thread-safe!** Use from single thread only or add external synchronization. - ---- - -## Integration Example (Full Rust Frontend) - -```rust -use std::ffi::c_int; -use std::time::Duration; - -#[repr(C)] -#[derive(Copy, Clone)] -enum UserAction_t { - Start = 0, - Pause, - Terminate, - Left, - Right, - Up, - Down, - Action, -} - -#[repr(C)] -struct GameInfo_t { - field: *mut *mut c_int, - next: *mut *mut c_int, - score: c_int, - high_score: c_int, - level: c_int, - speed: c_int, - pause: c_int, -} - -#[link(name = "tetris", kind = "static")] -extern "C" { - fn userInput(action: UserAction_t, hold: bool); - fn updateCurrentState() -> GameInfo_t; -} - -fn main() { - // Initialize game - unsafe { userInput(UserAction_t::Start, false); } - - // Game loop - loop { - // Get input (your input handling) - let action = get_user_input(); - if let Some(a) = action { - unsafe { userInput(a, false); } - } - - // Update and render - let state = unsafe { updateCurrentState() }; - render_with_your_ui(state); - - std::thread::sleep(Duration::from_millis(10)); - } -} -``` - ---- - -## Building for FFI - -```sh -# Build static library -make - -# Use in your project -gcc your_frontend.c -L./build -ltetris -lncurses -lm -lrt -lpthread -o game -# or with Rust -cargo build --release -``` - -## Notes for Frontend Developers - -1. **Field coordinates**: [y][x] where y=0 is top, x=0 is left -2. **Next preview**: Always 4×4 matrix, figure may not fill entire space -3. **High score**: Automatically saved to `build/high_score.txt` -4. **No need to free** GameInfo_t - it's internal storage -5. **Call sequence**: Start → (updateCurrentState + userInput)* → Terminate diff --git a/src/gui/cli/display.c b/src/gui/cli/display.c deleted file mode 100644 index 8b1bbf1..0000000 --- a/src/gui/cli/display.c +++ /dev/null @@ -1,41 +0,0 @@ -// src/gui/cli/display.c -#include "../../brick_game/tetris/00_tetris.h" -#include - -void display_game(GameInfo_t game_state) { - clear(); - - for (int i = 0; i < FIELD_HEIGHT; ++i) { - for (int j = 0; j < FIELD_WIDTH; ++j) { - if (game_state.field[i][j] == 2) { - mvaddch(i + 1, j * 2 + 1, '#'); - } else if (game_state.field[i][j] == 1) { - mvaddch(i + 1, j * 2 + 1, '$'); - } else { - mvaddch(i + 1, j * 2 + 1, '.'); - } - } - } - - mvaddstr(1, FIELD_WIDTH * 2 + 5, "Next figure:"); - - for (int i = 0; i < 4; ++i) { - for (int j = 0; j < 4; ++j) { - if (game_state.next[i][j]) { - mvaddch(i + 3, (FIELD_WIDTH * 2 + 5) + j * 2, '@'); - } else { - mvaddch(i + 3, (FIELD_WIDTH * 2 + 5) + j * 2, ' '); - } - } - } - - mvprintw(FIELD_HEIGHT + 2, 1, "Score: %d", game_state.score); - mvprintw(FIELD_HEIGHT + 3, 1, "High Score: %d", game_state.high_score); - mvprintw(FIELD_HEIGHT + 4, 1, "Level: %d", game_state.level); - mvprintw(FIELD_HEIGHT + 5, 1, "Speed: %d", game_state.speed); - - if (game_state.pause) { - mvprintw(FIELD_HEIGHT / 2, FIELD_WIDTH * 2 + 1, "PAUSED"); - } - refresh(); -} \ No newline at end of file diff --git a/src/gui/cli/main.c b/src/gui/cli/main.c deleted file mode 100644 index ca9ea44..0000000 --- a/src/gui/cli/main.c +++ /dev/null @@ -1,81 +0,0 @@ -#include "../../brick_game/tetris/00_tetris.h" -#include -#include -#include -#include - -void display_game(GameInfo_t game_state); - -int main() { - srand(time(NULL)); - - initscr(); - cbreak(); - noecho(); - keypad(stdscr, TRUE); - nodelay(stdscr, FALSE); - curs_set(0); - - mvprintw(FIELD_HEIGHT / 2, FIELD_WIDTH - 4, "Press F to Start"); - refresh(); - - int ch = 0; - int started = 0; - while (!started) { - ch = getch(); - if (ch == 'f' || ch == 'F') { - userInput(Start, false); - started = 1; - } - } - - nodelay(stdscr, TRUE); - timeout(10); - - UserAction_t current_action = {0}; - bool action_valid = false; - bool running = true; - - while (running) { - ch = getch(); - action_valid = false; - - if (ch == 'q') { - userInput(Terminate, false); - running = false; - } else if (ch == 'r' || ch == ' ') { - current_action = Action; - action_valid = true; - } else if (ch == KEY_LEFT) { - current_action = Left; - action_valid = true; - } else if (ch == KEY_RIGHT) { - current_action = Right; - action_valid = true; - } else if (ch == KEY_DOWN) { - current_action = Down; - action_valid = true; - } else if (ch == KEY_UP) { - current_action = Up; - action_valid = true; - } else if (ch == 's' || ch == 'S') { - current_action = Start; - action_valid = true; - } else if (ch == 'p' || ch == 'P') { - current_action = Pause; - action_valid = true; - } - - if (action_valid) { - userInput(current_action, false); - } - - if (running) { - GameInfo_t game_state = updateCurrentState(); - display_game(game_state); - } - } - - endwin(); - return 0; -} diff --git a/src/test/test.c b/src/test/test.c deleted file mode 100644 index 74a7640..0000000 --- a/src/test/test.c +++ /dev/null @@ -1,86 +0,0 @@ -#include -#include "../brick_game/tetris/01_automato.h" - -START_TEST(test_collision_bottom_boundary) { - GameState_t* state = get_game_state(); - state->curr.y = FIELD_HEIGHT - 1; - state->curr.x = 5; - state->curr.mtrx[0][0] = 1; - state->curr.y++; - ck_assert_int_eq(check_collision(), 1); -} -END_TEST - -START_TEST(test_collision_left_boundary) { - GameState_t* state = get_game_state(); - state->curr.x = -1; - state->curr.mtrx[0][0] = 1; - ck_assert_int_eq(check_collision(), 1); -} -END_TEST - -START_TEST(test_collision_with_placed_block) { - GameState_t* state = get_game_state(); - state->field[10][5] = 2; - state->curr.y = 10; - state->curr.x = 5; - state->curr.mtrx[0][0] = 1; - ck_assert_int_eq(check_collision(), 1); -} -END_TEST - -START_TEST(test_no_collision) { - GameState_t* state = get_game_state(); - state->curr.y = 5; - state->curr.x = 5; - state->curr.mtrx[0][0] = 1; - ck_assert_int_eq(check_collision(), 0); -} -END_TEST - -START_TEST(test_game_over_detection) { - GameState_t* state = get_game_state(); - state->field[0][5] = 2; - ck_assert_int_eq(is_game_over(), 1); -} -END_TEST - -START_TEST(test_clear_single_line) { - GameState_t* state = get_game_state(); - state->info->score = 0; - - // Заполняем нижнюю линию - for (int j = 0; j < FIELD_WIDTH; j++) { - state->field[FIELD_HEIGHT - 1][j] = 2; - } - - clear_lines(); - ck_assert_int_eq(state->info->score, 100); -} -END_TEST - -Suite* tetris_suite(void) { - Suite* s = suite_create("Tetris"); - TCase* tc_core = tcase_create("Core"); - - tcase_add_test(tc_core, test_collision_bottom_boundary); - tcase_add_test(tc_core, test_collision_left_boundary); - tcase_add_test(tc_core, test_collision_with_placed_block); - tcase_add_test(tc_core, test_no_collision); - tcase_add_test(tc_core, test_game_over_detection); - tcase_add_test(tc_core, test_clear_single_line); - - suite_add_tcase(s, tc_core); - return s; -} - -int main(void) { - Suite* s = tetris_suite(); - SRunner* sr = srunner_create(s); - - srunner_run_all(sr, CK_NORMAL); - int number_failed = srunner_ntests_failed(sr); - srunner_free(sr); - - return (number_failed == 0) ? 0 : 1; -} diff --git a/src/test/test_collision.c b/src/test/test_collision.c deleted file mode 100644 index 657639b..0000000 --- a/src/test/test_collision.c +++ /dev/null @@ -1,76 +0,0 @@ -#include "test_helper.h" - -START_TEST(test_collision_bottom) { - test_setup(); - GameState_t *state = get_game_state(); - - state->curr.y = FIELD_HEIGHT; - state->curr.x = 5; - state->curr.mtrx[0][0] = 1; - - ck_assert_int_eq(check_collision(), 1); -} -END_TEST - -START_TEST(test_collision_left) { - test_setup(); - GameState_t *state = get_game_state(); - - state->curr.x = -1; - state->curr.y = 5; - state->curr.mtrx[0][0] = 1; - - ck_assert_int_eq(check_collision(), 1); -} -END_TEST - -START_TEST(test_collision_right) { - test_setup(); - GameState_t *state = get_game_state(); - - state->curr.x = FIELD_WIDTH; - state->curr.y = 5; - state->curr.mtrx[0][0] = 1; - - ck_assert_int_eq(check_collision(), 1); -} -END_TEST - -START_TEST(test_collision_placed_block) { - test_setup(); - GameState_t *state = get_game_state(); - - state->field[10][5] = 2; - state->curr.y = 10; - state->curr.x = 5; - state->curr.mtrx[0][0] = 1; - - ck_assert_int_eq(check_collision(), 1); -} -END_TEST - -START_TEST(test_no_collision) { - test_setup(); - GameState_t *state = get_game_state(); - - state->curr.y = 5; - state->curr.x = 5; - state->curr.mtrx[0][0] = 1; - - ck_assert_int_eq(check_collision(), 0); -} -END_TEST - -Suite *collision_suite(void) { - Suite *s = suite_create("Collision"); - TCase *tc = tcase_create("Core"); - - tcase_add_test(tc, test_collision_bottom); - tcase_add_test(tc, test_collision_left); - tcase_add_test(tc, test_collision_right); - tcase_add_test(tc, test_collision_placed_block); - tcase_add_test(tc, test_no_collision); - - suite_add_tcase(s, tc); - return s; -} diff --git a/src/test/test_figures.c b/src/test/test_figures.c deleted file mode 100644 index c5fab70..0000000 --- a/src/test/test_figures.c +++ /dev/null @@ -1,57 +0,0 @@ -#include "test_helper.h" - -START_TEST(test_generate_figure) { - generate_next_figure(); - GameState_t *state = get_game_state(); - - ck_assert(state->next.sprite >= I && state->next.sprite < FIGURE_COUNT); - ck_assert_int_eq(state->next.rotation, 0); -} -END_TEST - -START_TEST(test_all_shapes_exist) { - for (int sprite = I; sprite < FIGURE_COUNT; sprite++) { - for (int rotation = 0; rotation < 4; rotation++) { - const int(*shape)[4] = get_figure_shape(sprite, rotation); - ck_assert_ptr_nonnull(shape); - } - } -} -END_TEST - -START_TEST(test_i_figure_horizontal) { - const int(*shape)[4] = get_figure_shape(I, 0); - - int count = 0; - for (int j = 0; j < 4; j++) { - if (shape[1][j]) - count++; - } - ck_assert_int_eq(count, 4); -} -END_TEST - -START_TEST(test_o_figure_unchanged) { - const int(*s1)[4] = get_figure_shape(O, 0); - const int(*s2)[4] = get_figure_shape(O, 2); - - for (int i = 0; i < 4; i++) { - for (int j = 0; j < 4; j++) { - ck_assert_int_eq(s1[i][j], s2[i][j]); - } - } -} -END_TEST - -Suite *figures_suite(void) { - Suite *s = suite_create("Figures"); - TCase *tc = tcase_create("Core"); - - tcase_add_test(tc, test_generate_figure); - tcase_add_test(tc, test_all_shapes_exist); - tcase_add_test(tc, test_i_figure_horizontal); - tcase_add_test(tc, test_o_figure_unchanged); - - suite_add_tcase(s, tc); - return s; -} diff --git a/src/test/test_fsm.c b/src/test/test_fsm.c deleted file mode 100644 index 4ceb5f7..0000000 --- a/src/test/test_fsm.c +++ /dev/null @@ -1,354 +0,0 @@ -#include "test_helper.h" -#include -#include - -// ============================================================================ -// Тесты FSM и интеграции -// ============================================================================ - -START_TEST(test_game_start) { - userInput(Start, false); - GameInfo_t state = updateCurrentState(); - - // После Start игра должна инициализироваться - ck_assert_int_eq(state.level, 1); - ck_assert_int_eq(state.score, 0); - ck_assert_int_eq(state.pause, 0); -} -END_TEST - -START_TEST(test_pause_toggle) { - userInput(Start, false); - updateCurrentState(); - - userInput(Pause, false); - GameInfo_t state = updateCurrentState(); - ck_assert_int_eq(state.pause, 1); - - userInput(Pause, false); - state = updateCurrentState(); - ck_assert_int_eq(state.pause, 0); -} -END_TEST - -START_TEST(test_field_initialization) { - userInput(Start, false); - GameInfo_t state = updateCurrentState(); - - int non_zero_count = 0; - for (int i = 0; i < FIELD_HEIGHT; i++) { - for (int j = 0; j < FIELD_WIDTH; j++) { - if (state.field[i][j] != 0) { - non_zero_count++; - } - } - } - - ck_assert_int_le(non_zero_count, 4); -} -END_TEST - -START_TEST(test_next_figure_exists) { - userInput(Start, false); - GameInfo_t state = updateCurrentState(); - - int has_blocks = 0; - for (int i = 0; i < 4; i++) { - for (int j = 0; j < 4; j++) { - if (state.next[i][j]) { - has_blocks = 1; - } - } - } - ck_assert_int_eq(has_blocks, 1); -} -END_TEST - -START_TEST(test_movement_left) { - userInput(Start, false); - updateCurrentState(); - - userInput(Left, false); - GameInfo_t state = updateCurrentState(); - - ck_assert_int_eq(state.pause, 0); -} -END_TEST - -START_TEST(test_movement_right) { - userInput(Start, false); - updateCurrentState(); - - userInput(Right, false); - GameInfo_t state = updateCurrentState(); - - ck_assert_int_eq(state.pause, 0); - ck_assert_int_ge(state.level, 1); -} -END_TEST - -START_TEST(test_rotation) { - userInput(Start, false); - updateCurrentState(); - - userInput(Action, false); - GameInfo_t state = updateCurrentState(); - - int has_figure = 0; - for (int i = 0; i < FIELD_HEIGHT; i++) { - for (int j = 0; j < FIELD_WIDTH; j++) { - if (state.field[i][j] == 1) { - has_figure = 1; - } - } - } - ck_assert_int_eq(has_figure, 1); -} -END_TEST - -START_TEST(test_user_input_actions) { - userInput(Start, false); - userInput(Left, false); - userInput(Right, false); - userInput(Action, false); - userInput(Pause, false); - userInput(Terminate, false); - - ck_assert_int_eq(1, 1); -} -END_TEST - -START_TEST(test_multiple_updates) { - userInput(Start, false); - - for (int i = 0; i < 10; i++) { - updateCurrentState(); - } - - GameInfo_t state = updateCurrentState(); - ck_assert_int_eq(state.level, 1); -} -END_TEST - -START_TEST(test_instant_drop_down_key) { - userInput(Start, false); - updateCurrentState(); - - GameState_t *state = get_game_state(); - state->down_key_was_released = 1; - - userInput(Down, false); - - ck_assert_int_eq(state->down_key_was_released, 0); -} -END_TEST - -START_TEST(test_up_key_release) { - userInput(Start, false); - updateCurrentState(); - - GameState_t *state = get_game_state(); - state->down_key_was_released = 0; - - userInput(Up, false); - updateCurrentState(); - - ck_assert_int_eq(state->down_key_was_released, 1); -} -END_TEST - -START_TEST(test_terminate_with_high_score) { - userInput(Start, false); - updateCurrentState(); - - GameState_t *state = get_game_state(); - state->info->high_score = 100; - state->info->score = 500; - - userInput(Terminate, false); - - int loaded = load_high_score(); - ck_assert_int_ge(loaded, 500); -} -END_TEST - -START_TEST(test_game_over_state) { - userInput(Start, false); - updateCurrentState(); - - GameState_t *state = get_game_state(); - - for (int i = 0; i < 2; i++) { - for (int j = 0; j < FIELD_WIDTH; j++) { - state->field[i][j] = 2; - } - } - - state->state = Spawn; - updateCurrentState(); - - ck_assert_int_eq(state->state, GameOver); -} -END_TEST - -START_TEST(test_do_gameover_clears_next) { - userInput(Start, false); - updateCurrentState(); - - GameState_t *state = get_game_state(); - - for (int i = 0; i < 2; i++) { - for (int j = 0; j < FIELD_WIDTH; j++) { - state->field[i][j] = 2; - } - } - - state->state = Spawn; - updateCurrentState(); - - state->state = GameOver; - updateCurrentState(); - - int has_blocks = 0; - for (int i = 0; i < 4; i++) { - for (int j = 0; j < 4; j++) { - if (state->next.mtrx[i][j]) { - has_blocks = 1; - } - } - } - ck_assert_int_eq(has_blocks, 0); -} -END_TEST - -START_TEST(test_place_figure_directly) { - GameState_t *state = get_game_state(); - - for (int i = 0; i < FIELD_HEIGHT; i++) - for (int j = 0; j < FIELD_WIDTH; j++) - state->field[i][j] = 0; - - state->curr.x = 5; - state->curr.y = 10; - for (int i = 0; i < 4; i++) - for (int j = 0; j < 4; j++) - state->curr.mtrx[i][j] = 0; - state->curr.mtrx[0][0] = 1; - state->curr.mtrx[0][1] = 1; - - place_figure(); - - ck_assert_int_eq(state->field[10][5], 2); - ck_assert_int_eq(state->field[10][6], 2); -} -END_TEST - -START_TEST(test_attach_state_transition) { - userInput(Start, false); - updateCurrentState(); - - GameState_t *state = get_game_state(); - - state->state = Attaching; - state->attach_start_time = 0; - state->attach_completed = 0; - - updateCurrentState(); - - ck_assert_int_gt(state->attach_start_time, 0); -} -END_TEST - -START_TEST(test_moving_to_down) { - userInput(Start, false); - updateCurrentState(); - - GameState_t *state = get_game_state(); - - state->curr.y = 5; - state->curr.x = 5; - for (int i = 0; i < 4; i++) - for (int j = 0; j < 4; j++) - state->curr.mtrx[i][j] = 0; - state->curr.mtrx[0][0] = 1; - - int initial_y = state->curr.y; - - state->state = Moving; - state->moving_type = ToDown; - - long long start = get_current_time_ms(); - updateCurrentState(); - long long elapsed = get_current_time_ms() - start; - - ck_assert(state->curr.y > initial_y || state->state == Attaching); - ck_assert_int_lt(elapsed, 1000); -} -END_TEST - -START_TEST(test_moving_do_nothing) { - userInput(Start, false); - updateCurrentState(); - - GameState_t *state = get_game_state(); - - state->state = Moving; - state->moving_type = DoNothing; - updateCurrentState(); - - ck_assert_int_eq(state->state, Move); -} -END_TEST - -START_TEST(test_automatic_falling) { - userInput(Start, false); - updateCurrentState(); - - GameState_t *state = get_game_state(); - - state->curr.y = 5; - state->curr.x = 5; - for (int i = 0; i < 4; i++) - for (int j = 0; j < 4; j++) - state->curr.mtrx[i][j] = 0; - state->curr.mtrx[0][0] = 1; - - state->state = Move; - state->last_move_time = 0; - - int initial_y = state->curr.y; - - updateCurrentState(); - - ck_assert(state->curr.y > initial_y || state->state == Attaching); -} -END_TEST - -Suite *fsm_suite(void) { - Suite *s = suite_create("FSM"); - TCase *tc = tcase_create("Core"); - - tcase_add_test(tc, test_game_start); - tcase_add_test(tc, test_pause_toggle); - tcase_add_test(tc, test_field_initialization); - tcase_add_test(tc, test_next_figure_exists); - tcase_add_test(tc, test_movement_left); - tcase_add_test(tc, test_movement_right); - tcase_add_test(tc, test_rotation); - tcase_add_test(tc, test_user_input_actions); - tcase_add_test(tc, test_multiple_updates); - - tcase_add_test(tc, test_instant_drop_down_key); - tcase_add_test(tc, test_up_key_release); - tcase_add_test(tc, test_terminate_with_high_score); - tcase_add_test(tc, test_game_over_state); - tcase_add_test(tc, test_do_gameover_clears_next); - tcase_add_test(tc, test_place_figure_directly); - tcase_add_test(tc, test_attach_state_transition); - tcase_add_test(tc, test_moving_to_down); - tcase_add_test(tc, test_moving_do_nothing); - tcase_add_test(tc, test_automatic_falling); - - suite_add_tcase(s, tc); - return s; -} diff --git a/src/test/test_helper.h b/src/test/test_helper.h deleted file mode 100644 index 75e1b72..0000000 --- a/src/test/test_helper.h +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef TEST_HELPER_H -#define TEST_HELPER_H - -#include -#include "../brick_game/tetris/01_automato.h" - -// Хелпер для очистки поля -static inline void clear_test_field(void) { - GameState_t* state = get_game_state(); - for (int i = 0; i < FIELD_HEIGHT; i++) - for (int j = 0; j < FIELD_WIDTH; j++) - state->field[i][j] = 0; -} - -// Хелпер для очистки матрицы фигуры -static inline void clear_figure_matrix(void) { - GameState_t* state = get_game_state(); - for (int i = 0; i < 4; i++) - for (int j = 0; j < 4; j++) - state->curr.mtrx[i][j] = 0; -} - -// Хелпер для заполнения линии -static inline void fill_line(int row) { - GameState_t* state = get_game_state(); - for (int j = 0; j < FIELD_WIDTH; j++) { - state->field[row][j] = 2; - } -} - -// Setup функция (вызывается перед каждым тестом) -static inline void test_setup(void) { - GameState_t* state = get_game_state(); - clear_test_field(); - state->info->score = 0; - state->info->level = 1; - state->info->speed = 1; -} - -#endif diff --git a/src/test/test_lines.c b/src/test/test_lines.c deleted file mode 100644 index 617790f..0000000 --- a/src/test/test_lines.c +++ /dev/null @@ -1,76 +0,0 @@ -#include "test_helper.h" - -START_TEST(test_clear_one_line) { - test_setup(); - fill_line(FIELD_HEIGHT - 1); - - clear_lines(); - - ck_assert_int_eq(get_game_state()->info->score, 100); -} -END_TEST - -START_TEST(test_clear_two_lines) { - test_setup(); - fill_line(FIELD_HEIGHT - 1); - fill_line(FIELD_HEIGHT - 2); - - clear_lines(); - - ck_assert_int_eq(get_game_state()->info->score, 300); -} -END_TEST - -START_TEST(test_clear_three_lines) { - test_setup(); - fill_line(FIELD_HEIGHT - 1); - fill_line(FIELD_HEIGHT - 2); - fill_line(FIELD_HEIGHT - 3); - - clear_lines(); - - ck_assert_int_eq(get_game_state()->info->score, 700); -} -END_TEST - -START_TEST(test_clear_four_lines) { - test_setup(); - fill_line(FIELD_HEIGHT - 1); - fill_line(FIELD_HEIGHT - 2); - fill_line(FIELD_HEIGHT - 3); - fill_line(FIELD_HEIGHT - 4); - - clear_lines(); - - ck_assert_int_eq(get_game_state()->info->score, 1500); -} -END_TEST - -START_TEST(test_incomplete_line) { - test_setup(); - GameState_t *state = get_game_state(); - - // Не полная линия - for (int j = 0; j < FIELD_WIDTH - 1; j++) { - state->field[FIELD_HEIGHT - 1][j] = 2; - } - - clear_lines(); - - ck_assert_int_eq(state->info->score, 0); -} -END_TEST - -Suite *lines_suite(void) { - Suite *s = suite_create("ClearLines"); - TCase *tc = tcase_create("Core"); - - tcase_add_test(tc, test_clear_one_line); - tcase_add_test(tc, test_clear_two_lines); - tcase_add_test(tc, test_clear_three_lines); - tcase_add_test(tc, test_clear_four_lines); - tcase_add_test(tc, test_incomplete_line); - - suite_add_tcase(s, tc); - return s; -} diff --git a/src/test/test_main.c b/src/test/test_main.c deleted file mode 100644 index 3476cca..0000000 --- a/src/test/test_main.c +++ /dev/null @@ -1,21 +0,0 @@ -#include - -Suite *collision_suite(void); -Suite *lines_suite(void); -Suite *figures_suite(void); -Suite *score_suite(void); -Suite *fsm_suite(void); - -int main(void) { - SRunner *sr = srunner_create(collision_suite()); - srunner_add_suite(sr, lines_suite()); - srunner_add_suite(sr, figures_suite()); - srunner_add_suite(sr, score_suite()); - srunner_add_suite(sr, fsm_suite()); - - srunner_run_all(sr, CK_VERBOSE); - int failed = srunner_ntests_failed(sr); - srunner_free(sr); - - return (failed == 0) ? 0 : 1; -} diff --git a/src/test/test_score.c b/src/test/test_score.c deleted file mode 100644 index 8bf2f91..0000000 --- a/src/test/test_score.c +++ /dev/null @@ -1,60 +0,0 @@ -#include "test_helper.h" - -START_TEST(test_level_up) { - test_setup(); - GameState_t *state = get_game_state(); - - fill_line(FIELD_HEIGHT - 1); - clear_lines(); - - fill_line(FIELD_HEIGHT - 1); - fill_line(FIELD_HEIGHT - 2); - clear_lines(); - - fill_line(FIELD_HEIGHT - 1); - fill_line(FIELD_HEIGHT - 2); - clear_lines(); - - ck_assert_int_eq(state->info->level, 2); -} -END_TEST - -START_TEST(test_max_level) { - test_setup(); - GameState_t *state = get_game_state(); - - state->info->score = 10000; - fill_line(FIELD_HEIGHT - 1); - clear_lines(); - - ck_assert_int_le(state->info->level, 10); -} -END_TEST - -START_TEST(test_high_score_save) { - save_high_score(9999); - ck_assert_int_eq(load_high_score(), 9999); -} -END_TEST - -START_TEST(test_game_over_top) { - test_setup(); - GameState_t *state = get_game_state(); - - state->field[0][5] = 2; - ck_assert_int_eq(is_game_over(), 1); -} -END_TEST - -Suite *score_suite(void) { - Suite *s = suite_create("Score"); - TCase *tc = tcase_create("Core"); - - tcase_add_test(tc, test_level_up); - tcase_add_test(tc, test_max_level); - tcase_add_test(tc, test_high_score_save); - tcase_add_test(tc, test_game_over_top); - - suite_add_tcase(s, tc); - return s; -}