Compare commits
No commits in common. "main" and "master" have entirely different histories.
32 changed files with 75 additions and 2646 deletions
|
|
@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
17
.gitignore
vendored
17
.gitignore
vendored
|
|
@ -42,8 +42,6 @@
|
||||||
*.idb
|
*.idb
|
||||||
*.pdb
|
*.pdb
|
||||||
|
|
||||||
*.gc*
|
|
||||||
|
|
||||||
# Kernel Module Compile Results
|
# Kernel Module Compile Results
|
||||||
*.mod*
|
*.mod*
|
||||||
*.cmd
|
*.cmd
|
||||||
|
|
@ -52,18 +50,3 @@ modules.order
|
||||||
Module.symvers
|
Module.symvers
|
||||||
Mkfile.old
|
Mkfile.old
|
||||||
dkms.conf
|
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
|
|
||||||
|
|
|
||||||
122
README_RUS.md
122
README_RUS.md
|
|
@ -1,6 +1,81 @@
|
||||||
# BrickGame Тетрис
|
# 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 <div id="chapter-i"></div>
|
||||||
|
## Общая информация
|
||||||
|
### BrickGame
|
||||||
|
|
||||||
|
BrickGame — популярная портативная консоль 90-ых годов с несколькими ~~тысячами~~ встроенными играми, разработана она была в Китае. Изначально эта игра была копией, разработанной в СССР и выпущенной Nintendo в рамках платформы GameBoy игры «Тетрис», но включала в себя также и множество других игр, которые добавлялись с течением времени. Консоль имела небольшой экранчик с игровым полем размера 10 х 20, представляющим из себя матрицу «пикселей». Справа от поля находилось табло с цифровой индикацией состояния текущей игры, рекордами и прочей дополнительной информацией. Самыми распространенными играми на BrickGame были: тетрис, танки, гонки, фроггер и змейка.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### История тетриса
|
||||||
|
|
||||||
|
«Тетрис» был написан Алексеем Пажитновым 6 июня 1984 года на компьютере Электроника-60. Игра представляла собой головоломку, построенную на использовании геометрических фигур «тетрамино», состоящих из четырех квадратов. Первая коммерческая версия игры была выпущена в Америке в 1987 году. В последующие годы «Тетрис» был портирован на множество различных устройств, в том числе на мобильные телефоны, калькуляторы и карманные персональные компьютеры.
|
||||||
|
|
||||||
|
Наибольшую популярность приобрела реализация «Тетриса» для игровой консоли Game Boy и видеоприставки NES. Но кроме нее существуют различные версии игры. Например, есть версия с трехмерными фигурами или дуэльная версия, в которой два игрока получают одинаковые фигуры и пытаются обойти друг друга по очкам.
|
||||||
|
|
||||||
|
### Конечные автоматы
|
||||||
|
|
||||||
|
Конечный автомат (КА) в теории алгоритмов — математическая абстракция, модель дискретного устройства, имеющего один вход, один выход и в каждый момент времени находящегося в одном состоянии из множества возможных.
|
||||||
|
|
||||||
|
При работе КА на вход последовательно поступают входные воздействия, а на выходе КА формирует выходные сигналы. Переход из одного внутреннего состояния КА в другое может происходить не только от внешнего воздействия, но и самопроизвольно.
|
||||||
|
|
||||||
|
КА можно использовать для описания алгоритмов, позволяющих решать те или иные задачи, а также для моделирования практически любого процесса. Несколько примеров:
|
||||||
|
|
||||||
|
- Логика искусственного интеллекта для игр;
|
||||||
|
- Синтаксический и лексический анализ;
|
||||||
|
- Сложные прикладные сетевые протоколы;
|
||||||
|
- Потоковая обработка данных.
|
||||||
|
|
||||||
|
Ниже представлены примеры использования КА для формализации игровой логики нескольких игр из BrickGame.
|
||||||
|
|
||||||
|
### Фроггер
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
«Фроггер» — одна из поздних игр, выходящих на консолях Brickgame. Игра представляет собой игровое поле, по которому движутся бревна, и, перепрыгивая по ним, игроку необходимо перевести лягушку с одного берега на другой. Если игрок попадает в воду или лягушка уходит за пределы игрового поля, то лягушка погибает. Игра завершается, когда игрок доводит лягушку до другого берега или погибает последняя лягушка.
|
||||||
|
|
||||||
|
Для формализации логики данной игры можно представить следующий вариант конечного автомата:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Данный КА имеет следующие состояния:
|
||||||
|
|
||||||
|
- Старт — состояние, в котором игра ждет, пока игрок нажмет кнопку готовности к игре.
|
||||||
|
- Спавн — состояние, в котором создается очередная лягушка.
|
||||||
|
- Перемещение — основное игровое состояние с обработкой ввода от пользователя: движение лягушки по полосе влево/право или прыжки вперед/назад.
|
||||||
|
- Сдвиг — состояние, которое наступает после истечения таймера, при котором все объекты на полосах сдвигаются вправо вместе с лягушкой.
|
||||||
|
- Столкновение — состояние, которое наступает, если после прыжка лягушка попадает в воду, или если после смещения бревен лягушка оказывается за пределами игрового поля.
|
||||||
|
- Достигнут другой берег — состояние, которое наступает при достижении лягушкой другого берега.
|
||||||
|
- Игра окончена — состояние, которое наступает после достижения другого берега или смерти последней лягушки.
|
||||||
|
|
||||||
|
Пример реализации фроггера с использованием КА ты можешь найти в папке `code-samples`.
|
||||||
|
|
||||||
### Тетрис
|
### Тетрис
|
||||||
|
|
||||||

|

|
||||||
|
|
@ -85,50 +160,3 @@
|
||||||
### Часть 3. Дополнительно. Механика уровней
|
### Часть 3. Дополнительно. Механика уровней
|
||||||
|
|
||||||
Добавь в игру механику уровней. Каждый раз, когда игрок набирает 600 очков, уровень увеличивается на 1. Повышение уровня увеличивает скорость движения фигур. Максимальное количество уровней — 10.
|
Добавь в игру механику уровней. Каждый раз, когда игрок набирает 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
|
|
||||||
|
|
||||||
|
|
|
||||||
61
flake.lock
generated
61
flake.lock
generated
|
|
@ -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
|
|
||||||
}
|
|
||||||
42
flake.nix
42
flake.nix
|
|
@ -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"
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
72
src/.gpskip
72
src/.gpskip
|
|
@ -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
|
|
||||||
|
|
||||||
23
src/Doxyfile
23
src/Doxyfile
|
|
@ -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
|
|
||||||
129
src/Makefile
129
src/Makefile
|
|
@ -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)
|
|
||||||
|
|
@ -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 <stdbool.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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 */
|
|
||||||
|
|
@ -1,126 +0,0 @@
|
||||||
#ifndef AUTOMATO_H
|
|
||||||
#define AUTOMATO_H
|
|
||||||
|
|
||||||
#define _POSIX_C_SOURCE 199309L
|
|
||||||
|
|
||||||
#include "00_tetris.h"
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <time.h>
|
|
||||||
|
|
||||||
#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
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
BIN
src/diagram.pdf
BIN
src/diagram.pdf
Binary file not shown.
Binary file not shown.
218
src/doc.md
218
src/doc.md
|
|
@ -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
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
// src/gui/cli/display.c
|
|
||||||
#include "../../brick_game/tetris/00_tetris.h"
|
|
||||||
#include <ncurses.h>
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
@ -1,81 +0,0 @@
|
||||||
#include "../../brick_game/tetris/00_tetris.h"
|
|
||||||
#include <ncurses.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <time.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
@ -1,86 +0,0 @@
|
||||||
#include <check.h>
|
|
||||||
#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;
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
@ -1,354 +0,0 @@
|
||||||
#include "test_helper.h"
|
|
||||||
#include <check.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Тесты 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;
|
|
||||||
}
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
#ifndef TEST_HELPER_H
|
|
||||||
#define TEST_HELPER_H
|
|
||||||
|
|
||||||
#include <check.h>
|
|
||||||
#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
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
#include <check.h>
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue