From aa354f32581667e0fb36f544dc98e55aa2813c51 Mon Sep 17 00:00:00 2001 From: Rorikstr | Rust Dev Date: Sun, 19 Oct 2025 19:01:17 +0300 Subject: [PATCH] dvi --- src/Makefile | 44 +++--- src/brick_game/tetris/00_tetris.h | 159 ++++++++++++++++++++-- src/doc.md | 218 ++++++++++++++++++++++++++++++ 3 files changed, 391 insertions(+), 30 deletions(-) create mode 100644 src/doc.md diff --git a/src/Makefile b/src/Makefile index 3d82150..fbd5e34 100644 --- a/src/Makefile +++ b/src/Makefile @@ -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) \ No newline at end of file +-include $(TETRIS_OBJ:.o=.d) $(CLI_OBJ:.o=.d) diff --git a/src/brick_game/tetris/00_tetris.h b/src/brick_game/tetris/00_tetris.h index dce87ab..3e0624a 100644 --- a/src/brick_game/tetris/00_tetris.h +++ b/src/brick_game/tetris/00_tetris.h @@ -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 #include +/** + * @brief Width of the game field in blocks + */ #define FIELD_WIDTH 10 + +/** + * @brief Height of the game field in blocks + */ #define FIELD_HEIGHT 20 +/** + * @enum UserAction_t + * @brief User input actions + * + * Represents all possible user actions in the game. + */ typedef enum { - Start, - 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 \ No newline at end of file +#endif /* TETRIS_H */ diff --git a/src/doc.md b/src/doc.md new file mode 100644 index 0000000..847cd44 --- /dev/null +++ b/src/doc.md @@ -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