dvi
This commit is contained in:
parent
31562af99d
commit
aa354f3258
3 changed files with 391 additions and 30 deletions
44
src/Makefile
44
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)
|
||||
-include $(TETRIS_OBJ:.o=.d) $(CLI_OBJ:.o=.d)
|
||||
|
|
|
|||
|
|
@ -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
218
src/doc.md
Normal 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue