defines
This commit is contained in:
parent
411b2e4bb3
commit
31562af99d
6 changed files with 193 additions and 111 deletions
122
README_RUS.md
122
README_RUS.md
|
|
@ -1,81 +1,6 @@
|
|||
# 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`.
|
||||
|
||||
### Тетрис
|
||||
|
||||

|
||||
|
|
@ -160,3 +85,50 @@ BrickGame — популярная портативная консоль 90-ых
|
|||
### Часть 3. Дополнительно. Механика уровней
|
||||
|
||||
Добавь в игру механику уровней. Каждый раз, когда игрок набирает 600 очков, уровень увеличивается на 1. Повышение уровня увеличивает скорость движения фигур. Максимальное количество уровней — 10.
|
||||
|
||||
# Tetris Game
|
||||
|
||||
Classic Tetris implementation in C11 with ncurses interface.
|
||||
|
||||
## Requirements
|
||||
|
||||
- GCC or Clang
|
||||
- ncurses library
|
||||
- Check framework (for tests)
|
||||
|
||||
## Building
|
||||
|
||||
make # Build the game
|
||||
make run # Build and run
|
||||
make test # Run unit tests
|
||||
make install # Install to ~/.local/bin
|
||||
|
||||
text
|
||||
|
||||
## Controls
|
||||
|
||||
- **Arrow Keys**: Move left/right, rotate (up)
|
||||
- **Down Arrow**: Instant drop
|
||||
- **Space/R**: Rotate figure
|
||||
- **P**: Pause
|
||||
- **S**: Restart game
|
||||
- **Q**: Quit
|
||||
|
||||
## Features
|
||||
|
||||
- 7 classic Tetris figures
|
||||
- Score tracking with persistent high score
|
||||
- 10 speed levels
|
||||
- Smooth time-based movement using POSIX `clock_gettime()`
|
||||
- Structured programming principles
|
||||
|
||||
## Architecture
|
||||
|
||||
- **FSM-based game logic** with states: Init, Spawn, Move, Moving, Attaching, GameOver
|
||||
- **Separated frontend/backend**: `brick_game/` (logic) and `gui/cli/` (display)
|
||||
- **Time-based delays** for precise falling speed
|
||||
|
||||
## License
|
||||
|
||||
School 21 educational project
|
||||
|
||||
|
|
|
|||
63
src/Makefile
63
src/Makefile
|
|
@ -1,8 +1,7 @@
|
|||
.PHONY: all clean valgrind test style format gcov_report all install uninstall
|
||||
.PHONY: all clean install uninstall test gcov_report dvi dist run style format
|
||||
|
||||
# Автоопределение компилятора и флагов
|
||||
CC ?= gcc
|
||||
CFLAGS ?= -Wall -Wextra -std=c11 -g -D_POSIX_C_SOURCE=199309L
|
||||
CFLAGS ?= -Wall -Wextra -Werror -std=c11 -g -D_POSIX_C_SOURCE=199309L -MMD -MP
|
||||
CHECK_CFLAGS ?= -I/usr/include/check
|
||||
|
||||
# Проверяем наличие библиотек
|
||||
|
|
@ -14,11 +13,7 @@ else
|
|||
LDFLAGS ?= -lcheck -lrt -lpthread -lm -lncurses
|
||||
endif
|
||||
|
||||
SRCDIR = .
|
||||
TESTDIR = test
|
||||
BUILDDIR = build
|
||||
|
||||
# src/Makefile
|
||||
TETRISDIR = brick_game/tetris
|
||||
CLIDIR = gui/cli
|
||||
|
||||
|
|
@ -32,10 +27,10 @@ LIB_TETRIS = $(BUILDDIR)/libtetris.a
|
|||
TARGET = $(BUILDDIR)/tetris_bin.out
|
||||
|
||||
# Установка
|
||||
PREFIX ?= /usr/local
|
||||
PREFIX ?= $(HOME)/.local
|
||||
BINDIR = $(PREFIX)/bin
|
||||
|
||||
all: clean $(TARGET)
|
||||
all: $(TARGET)
|
||||
|
||||
$(LIB_TETRIS): $(TETRIS_OBJ)
|
||||
mkdir -p $(BUILDDIR)
|
||||
|
|
@ -43,7 +38,6 @@ $(LIB_TETRIS): $(TETRIS_OBJ)
|
|||
|
||||
$(TARGET): $(LIB_TETRIS) $(CLI_OBJ)
|
||||
$(CC) $(CLI_OBJ) -L$(BUILDDIR) -ltetris -o $@ $(LDFLAGS)
|
||||
rm -f $(CLI_OBJ) $(TETRIS_OBJ)
|
||||
|
||||
brick_game/tetris/%.o: brick_game/tetris/%.c
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
|
@ -51,36 +45,51 @@ brick_game/tetris/%.o: brick_game/tetris/%.c
|
|||
gui/cli/%.o: gui/cli/%.c
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
%.o: %.c
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
install: $(TARGET)
|
||||
install -m 755 $(TARGET) $(BINDIR)/
|
||||
install -m 755 $(TARGET) $(BINDIR)/tetris_bin.out
|
||||
|
||||
uninstall:
|
||||
rm -f $(BINDIR)/$(TARGET)
|
||||
rm -f $(BINDIR)/tetris_bin.out
|
||||
|
||||
clean:
|
||||
rm -f $(CLI_OBJ) $(TETRIS_OBJ) $(TARGET) $(LIB_TETRIS) *.gcda *.gcno *.gcov
|
||||
rm -rf $(CLI_OBJ) $(TETRIS_OBJ) $(TARGET) $(LIB_TETRIS)
|
||||
rm -rf $(BUILDDIR)/*.gcda $(BUILDDIR)/*.gcno *.gcda *.gcno *.gcov $(TETRISDIR)/*.d $(CLIDIR)/*.d
|
||||
rm -rf coverage.info coverage_report
|
||||
|
||||
test:
|
||||
@echo "Running tests..."
|
||||
# Здесь будет вызов тестов, если они есть
|
||||
test: $(LIB_TETRIS)
|
||||
$(CC) $(CFLAGS) test/test.c -L$(BUILDDIR) -ltetris $(LDFLAGS) -o $(BUILDDIR)/test.out
|
||||
./$(BUILDDIR)/test.out
|
||||
|
||||
gcov_report: CFLAGS += --coverage
|
||||
gcov_report: LDFLAGS += --coverage
|
||||
gcov_report: clean $(TARGET)
|
||||
./$(TARGET)
|
||||
@echo "Note: Run automated tests for proper coverage"
|
||||
gcov $(TETRIS_SRC)
|
||||
lcov --capture --directory . --output-file coverage.info
|
||||
genhtml coverage.info --output-directory coverage_report
|
||||
@echo "Coverage report generated in coverage_report/index.html"
|
||||
@echo "Coverage report: coverage_report/index.html"
|
||||
|
||||
dvi:
|
||||
doxygen Doxyfile 2>/dev/null || echo "Doxyfile not found"
|
||||
|
||||
dist: clean
|
||||
tar -czf tetris.tar.gz Makefile $(TETRISDIR) $(CLIDIR) README.md
|
||||
|
||||
run: $(TARGET)
|
||||
./$(TARGET)
|
||||
|
||||
style:
|
||||
@cp ../materials/linters/.clang-format .
|
||||
@clang-format -n *.c *.h
|
||||
@rm .clang-format
|
||||
@if [ -f .clang-format ]; then \
|
||||
clang-format -n $(TETRIS_SRC) $(CLI_SRC); \
|
||||
else \
|
||||
echo ".clang-format not found"; \
|
||||
fi
|
||||
|
||||
format:
|
||||
cp ../materials/linters/.clang-format .
|
||||
clang-format -i *.c *.h
|
||||
rm .clang-format
|
||||
@if [ -f .clang-format ]; then \
|
||||
clang-format -i $(TETRIS_SRC) $(CLI_SRC); \
|
||||
else \
|
||||
echo ".clang-format not found"; \
|
||||
fi
|
||||
|
||||
-include $(TETRIS_OBJ:.o=.d) $(CLI_OBJ:.o=.d)
|
||||
|
|
@ -8,6 +8,21 @@
|
|||
#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,
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@ int get_milliseconds_to_wait(void) {
|
|||
int result = 0;
|
||||
|
||||
if (state->moving_type == ToDown) {
|
||||
result = 30;
|
||||
result = INSTANT_DROP_DELAY_MS;
|
||||
} else {
|
||||
int base_delay = 1100 - (state->info->speed * 100);
|
||||
result = (base_delay > 100) ? base_delay : 100;
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ void do_attaching(void) {
|
|||
}
|
||||
|
||||
// Проверяем, прошло ли 350мс
|
||||
if (current_time - state->attach_start_time >= 350) {
|
||||
if (current_time - state->attach_start_time >= ATTACH_DELAY_MS) {
|
||||
state->attach_completed = 1;
|
||||
state->attach_start_time = 0; // Сбрасываем таймер
|
||||
|
||||
|
|
@ -108,21 +108,21 @@ void clear_lines() {
|
|||
}
|
||||
|
||||
if (lines_cleared > 0) {
|
||||
int points[] = {0, 100, 300, 700, 1500};
|
||||
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 / 600) + 1;
|
||||
if (new_level > 10) {
|
||||
new_level = 10;
|
||||
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 * 3;
|
||||
state->info->speed = new_level + (new_level / 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
86
src/test/test.c
Normal file
86
src/test/test.c
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
#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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue