This commit is contained in:
Rorikstr | Rust Dev 2025-10-19 19:01:17 +03:00
parent 31562af99d
commit aa354f3258
3 changed files with 391 additions and 30 deletions

View file

@ -16,6 +16,9 @@ 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")
@ -25,6 +28,7 @@ CLI_OBJ = $(CLI_SRC:.c=.o)
LIB_TETRIS = $(BUILDDIR)/libtetris.a
TARGET = $(BUILDDIR)/tetris_bin.out
TEST_TARGET = $(BUILDDIR)/test.out
# Установка
PREFIX ?= $(HOME)/.local
@ -46,34 +50,42 @@ gui/cli/%.o: gui/cli/%.c
$(CC) $(CFLAGS) -c $< -o $@
install: $(TARGET)
install -m 755 $(TARGET) $(BINDIR)/tetris_bin.out
mkdir -p $(BINDIR)
install -m 755 $(TARGET) $(BINDIR)/tetris
uninstall:
rm -f $(BINDIR)/tetris_bin.out
rm -f $(BINDIR)/tetris
clean:
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
rm -rf $(CLI_OBJ) $(TETRIS_OBJ) $(TARGET) $(LIB_TETRIS) $(TEST_TARGET)
rm -rf $(TETRISDIR)/*.d $(CLIDIR)/*.d
rm -rf $(GCOV_DIR) $(DVI_DIR)
test: $(LIB_TETRIS)
$(CC) $(CFLAGS) test/test.c -L$(BUILDDIR) -ltetris $(LDFLAGS) -o $(BUILDDIR)/test.out
./$(BUILDDIR)/test.out
$(CC) $(CFLAGS) $(TESTDIR)/test.c -L$(BUILDDIR) -ltetris $(LDFLAGS) -o $(TEST_TARGET)
./$(TEST_TARGET)
gcov_report: CFLAGS += --coverage
gcov_report: LDFLAGS += --coverage
gcov_report: clean $(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: coverage_report/index.html"
gcov_report: clean test
@mkdir -p $(GCOV_DIR)
gcov $(TETRIS_SRC) -o $(TETRISDIR)
lcov --capture --directory . --output-file $(GCOV_DIR)/coverage.info
lcov --remove $(GCOV_DIR)/coverage.info '/usr/*' '*/nix/store/*' -o $(GCOV_DIR)/coverage.info
genhtml $(GCOV_DIR)/coverage.info --output-directory $(GCOV_DIR)
mv *.gcov $(GCOV_DIR)/ 2>/dev/null || true
@echo "Report: $(GCOV_DIR)/index.html"
dvi:
doxygen Doxyfile 2>/dev/null || echo "Doxyfile not found"
@mkdir -p $(DVI_DIR)
@cp doc.md $(DVI_DIR)/ 2>/dev/null || echo "doc.md not found"
@if command -v doxygen >/dev/null 2>&1 && [ -f Doxyfile ]; then \
doxygen Doxyfile; \
fi
@echo "Documentation in $(DVI_DIR)/"
dist: clean
tar -czf tetris.tar.gz Makefile $(TETRISDIR) $(CLIDIR) README.md
tar -czf tetris.tar.gz Makefile $(TETRISDIR) $(CLIDIR) $(TESTDIR) README.md doc.md
run: $(TARGET)
./$(TARGET)
@ -92,4 +104,4 @@ format:
echo ".clang-format not found"; \
fi
-include $(TETRIS_OBJ:.o=.d) $(CLI_OBJ:.o=.d)
-include $(TETRIS_OBJ:.o=.d) $(CLI_OBJ:.o=.d)

View file

@ -1,34 +1,165 @@
/**
* @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,
Pause,
Terminate,
Left,
Right,
Up,
Down,
Action
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;
int high_score;
int level;
int speed;
int pause;
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
#endif /* TETRIS_H */

218
src/doc.md Normal file
View file

@ -0,0 +1,218 @@
# 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