dvi
This commit is contained in:
parent
31562af99d
commit
aa354f3258
3 changed files with 391 additions and 30 deletions
42
src/Makefile
42
src/Makefile
|
|
@ -16,6 +16,9 @@ endif
|
||||||
BUILDDIR = build
|
BUILDDIR = build
|
||||||
TETRISDIR = brick_game/tetris
|
TETRISDIR = brick_game/tetris
|
||||||
CLIDIR = gui/cli
|
CLIDIR = gui/cli
|
||||||
|
TESTDIR = test
|
||||||
|
GCOV_DIR = gcov_report
|
||||||
|
DVI_DIR = dvi
|
||||||
|
|
||||||
# Файлы
|
# Файлы
|
||||||
TETRIS_SRC = $(shell find $(TETRISDIR) -name "*.c")
|
TETRIS_SRC = $(shell find $(TETRISDIR) -name "*.c")
|
||||||
|
|
@ -25,6 +28,7 @@ CLI_OBJ = $(CLI_SRC:.c=.o)
|
||||||
|
|
||||||
LIB_TETRIS = $(BUILDDIR)/libtetris.a
|
LIB_TETRIS = $(BUILDDIR)/libtetris.a
|
||||||
TARGET = $(BUILDDIR)/tetris_bin.out
|
TARGET = $(BUILDDIR)/tetris_bin.out
|
||||||
|
TEST_TARGET = $(BUILDDIR)/test.out
|
||||||
|
|
||||||
# Установка
|
# Установка
|
||||||
PREFIX ?= $(HOME)/.local
|
PREFIX ?= $(HOME)/.local
|
||||||
|
|
@ -46,34 +50,42 @@ gui/cli/%.o: gui/cli/%.c
|
||||||
$(CC) $(CFLAGS) -c $< -o $@
|
$(CC) $(CFLAGS) -c $< -o $@
|
||||||
|
|
||||||
install: $(TARGET)
|
install: $(TARGET)
|
||||||
install -m 755 $(TARGET) $(BINDIR)/tetris_bin.out
|
mkdir -p $(BINDIR)
|
||||||
|
install -m 755 $(TARGET) $(BINDIR)/tetris
|
||||||
|
|
||||||
uninstall:
|
uninstall:
|
||||||
rm -f $(BINDIR)/tetris_bin.out
|
rm -f $(BINDIR)/tetris
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf $(CLI_OBJ) $(TETRIS_OBJ) $(TARGET) $(LIB_TETRIS)
|
rm -rf $(CLI_OBJ) $(TETRIS_OBJ) $(TARGET) $(LIB_TETRIS) $(TEST_TARGET)
|
||||||
rm -rf $(BUILDDIR)/*.gcda $(BUILDDIR)/*.gcno *.gcda *.gcno *.gcov $(TETRISDIR)/*.d $(CLIDIR)/*.d
|
rm -rf $(TETRISDIR)/*.d $(CLIDIR)/*.d
|
||||||
rm -rf coverage.info coverage_report
|
rm -rf $(GCOV_DIR) $(DVI_DIR)
|
||||||
|
|
||||||
test: $(LIB_TETRIS)
|
test: $(LIB_TETRIS)
|
||||||
$(CC) $(CFLAGS) test/test.c -L$(BUILDDIR) -ltetris $(LDFLAGS) -o $(BUILDDIR)/test.out
|
$(CC) $(CFLAGS) $(TESTDIR)/test.c -L$(BUILDDIR) -ltetris $(LDFLAGS) -o $(TEST_TARGET)
|
||||||
./$(BUILDDIR)/test.out
|
./$(TEST_TARGET)
|
||||||
|
|
||||||
gcov_report: CFLAGS += --coverage
|
gcov_report: CFLAGS += --coverage
|
||||||
gcov_report: LDFLAGS += --coverage
|
gcov_report: LDFLAGS += --coverage
|
||||||
gcov_report: clean $(TARGET)
|
gcov_report: clean test
|
||||||
@echo "Note: Run automated tests for proper coverage"
|
@mkdir -p $(GCOV_DIR)
|
||||||
gcov $(TETRIS_SRC)
|
gcov $(TETRIS_SRC) -o $(TETRISDIR)
|
||||||
lcov --capture --directory . --output-file coverage.info
|
lcov --capture --directory . --output-file $(GCOV_DIR)/coverage.info
|
||||||
genhtml coverage.info --output-directory coverage_report
|
lcov --remove $(GCOV_DIR)/coverage.info '/usr/*' '*/nix/store/*' -o $(GCOV_DIR)/coverage.info
|
||||||
@echo "Coverage report: coverage_report/index.html"
|
genhtml $(GCOV_DIR)/coverage.info --output-directory $(GCOV_DIR)
|
||||||
|
mv *.gcov $(GCOV_DIR)/ 2>/dev/null || true
|
||||||
|
@echo "Report: $(GCOV_DIR)/index.html"
|
||||||
|
|
||||||
dvi:
|
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
|
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)
|
run: $(TARGET)
|
||||||
./$(TARGET)
|
./$(TARGET)
|
||||||
|
|
|
||||||
|
|
@ -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
|
#ifndef TETRIS_H
|
||||||
#define TETRIS_H
|
#define TETRIS_H
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Width of the game field in blocks
|
||||||
|
*/
|
||||||
#define FIELD_WIDTH 10
|
#define FIELD_WIDTH 10
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Height of the game field in blocks
|
||||||
|
*/
|
||||||
#define FIELD_HEIGHT 20
|
#define FIELD_HEIGHT 20
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @enum UserAction_t
|
||||||
|
* @brief User input actions
|
||||||
|
*
|
||||||
|
* Represents all possible user actions in the game.
|
||||||
|
*/
|
||||||
typedef enum {
|
typedef enum {
|
||||||
Start,
|
Start, /**< Initialize or restart the game */
|
||||||
Pause,
|
Pause, /**< Toggle pause state */
|
||||||
Terminate,
|
Terminate, /**< Quit game and free all resources */
|
||||||
Left,
|
Left, /**< Move figure left */
|
||||||
Right,
|
Right, /**< Move figure right */
|
||||||
Up,
|
Up, /**< Release instant drop flag (for next drop) */
|
||||||
Down,
|
Down, /**< Instant drop figure to bottom */
|
||||||
Action
|
Action /**< Rotate figure clockwise */
|
||||||
} UserAction_t;
|
} 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 {
|
typedef struct {
|
||||||
|
/**
|
||||||
|
* @brief 2D game field [FIELD_HEIGHT][FIELD_WIDTH]
|
||||||
|
*
|
||||||
|
* Values:
|
||||||
|
* - 0: Empty cell
|
||||||
|
* - 1: Active (falling) figure
|
||||||
|
* - 2: Placed (fixed) blocks
|
||||||
|
*/
|
||||||
int **field;
|
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 **next;
|
||||||
int score;
|
|
||||||
int high_score;
|
int score; /**< Current score */
|
||||||
int level;
|
int high_score; /**< Best score (persistent across sessions) */
|
||||||
int speed;
|
int level; /**< Current level (1-10) */
|
||||||
int pause;
|
int speed; /**< Current speed multiplier */
|
||||||
|
int pause; /**< Pause state: 0=playing, 1=paused */
|
||||||
} GameInfo_t;
|
} 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);
|
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();
|
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