439 lines
14 KiB
Markdown
439 lines
14 KiB
Markdown
# Reactive Game Template Refactoring Plan
|
|
|
|
**Goal**: Transform `basic1.cpp` into a clean, event-driven reactive game template where:
|
|
- Display only updates when input is received (ideal for e-ink displays)
|
|
- All drawing happens inside dedicated functions
|
|
- Game state is separate from input handling
|
|
- Clear structure for developers to customize game logic
|
|
|
|
---
|
|
|
|
## Phase 1: Define Core Structures and Types
|
|
|
|
### ☐ Step 1.1: Create InputEvent structure
|
|
**Location**: After includes, before interrupt handlers
|
|
**Action**: Define a unified `InputEvent` structure to normalize all input types (touch, button, gesture)
|
|
```cpp
|
|
// Input event types
|
|
enum InputType {
|
|
INPUT_NONE = 0,
|
|
INPUT_TOUCH_DOWN,
|
|
INPUT_TOUCH_MOVE,
|
|
INPUT_TOUCH_UP,
|
|
INPUT_BUTTON_0,
|
|
INPUT_BUTTON_1,
|
|
INPUT_GESTURE
|
|
};
|
|
|
|
// Unified input event structure
|
|
struct InputEvent {
|
|
InputType type;
|
|
int16_t x;
|
|
int16_t y;
|
|
uint8_t gesture_code; // For gesture events
|
|
uint8_t button_id; // For button events
|
|
uint8_t pressure; // Touch pressure/weight
|
|
bool valid; // Set to true if event is valid
|
|
};
|
|
```
|
|
|
|
### ☐ Step 1.2: Create GameState structure
|
|
**Location**: After InputEvent definition
|
|
**Action**: Define a `GameState` structure to hold all game-specific data (example for drawing game)
|
|
```cpp
|
|
// Game state - customize this for your game
|
|
struct GameState {
|
|
// Drawing game state
|
|
int16_t last_x;
|
|
int16_t last_y;
|
|
bool is_drawing;
|
|
|
|
// General game state
|
|
uint32_t score;
|
|
bool game_over;
|
|
uint32_t frame_count;
|
|
|
|
// Statistics
|
|
uint32_t touch_success_count;
|
|
uint32_t touch_fail_count;
|
|
};
|
|
```
|
|
|
|
### ☐ Step 1.3: Create GameConfig structure
|
|
**Location**: After GameState definition
|
|
**Action**: Define configurable parameters for the game
|
|
```cpp
|
|
// Game configuration - adjust these for your game
|
|
struct GameConfig {
|
|
uint32_t touch_debounce_ms; // Touch polling rate
|
|
uint32_t button_debounce_ms; // Button debounce delay
|
|
bool enable_gestures; // Enable gesture recognition
|
|
bool enable_continuous_draw; // Allow continuous drawing while touched
|
|
bool debug_verbose; // Print debug messages
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## Phase 2: Refactor Input System
|
|
|
|
### ☐ Step 2.1: Simplify touch interrupt handler
|
|
**Location**: `touch_interrupt_handler()` function (lines ~52-83)
|
|
**Action**: Remove all processing logic, keep only flag setting
|
|
- Remove `touch->read_touch()` call from ISR
|
|
- Remove printf statements from ISR
|
|
- Keep only `touch_interrupt_flag` and `touch_event_down` flag setting
|
|
|
|
### ☐ Step 2.2: Simplify button interrupt handler
|
|
**Location**: `button_interrupt_handler()` function (lines ~98-113)
|
|
**Action**: Remove printf from ISR, keep only flag setting
|
|
- Remove `printf()` calls from interrupt handler
|
|
- Keep only flag setting logic
|
|
|
|
### ☐ Step 2.3: Create process_touch_input() function
|
|
**Location**: After interrupt handlers, before refresh_screen()
|
|
**Action**: Create dedicated function to process touch events
|
|
```cpp
|
|
/**
|
|
* @brief Process touch input and convert to InputEvent
|
|
*
|
|
* Reads touch data from controller and creates appropriate InputEvent.
|
|
* Handles debouncing and filtering internally.
|
|
*
|
|
* @param config Game configuration
|
|
* @param last_time Pointer to last touch time for debouncing
|
|
* @return InputEvent structure (valid=false if no valid input)
|
|
*/
|
|
InputEvent process_touch_input(const GameConfig& config, uint32_t* last_time);
|
|
```
|
|
|
|
### ☐ Step 2.4: Create process_button_input() function
|
|
**Location**: After process_touch_input()
|
|
**Action**: Create dedicated function to process button events
|
|
```cpp
|
|
/**
|
|
* @brief Process button input and convert to InputEvent
|
|
*
|
|
* Checks button flags and verifies button state with debouncing.
|
|
* Clears flags after processing.
|
|
*
|
|
* @param config Game configuration
|
|
* @return InputEvent structure (valid=false if no valid input)
|
|
*/
|
|
InputEvent process_button_input(const GameConfig& config);
|
|
```
|
|
|
|
### ☐ Step 2.5: Create get_gesture_name() helper function
|
|
**Location**: After process_button_input()
|
|
**Action**: Extract gesture name lookup into helper function
|
|
```cpp
|
|
/**
|
|
* @brief Get human-readable gesture name
|
|
*
|
|
* @param gesture_code Gesture code from touch controller
|
|
* @return Constant string with gesture name
|
|
*/
|
|
const char* get_gesture_name(uint8_t gesture_code);
|
|
```
|
|
|
|
---
|
|
|
|
## Phase 3: Create Game Logic Functions
|
|
|
|
### ☐ Step 3.1: Create game_init() function
|
|
**Location**: After input processing functions
|
|
**Action**: Initialize game state
|
|
```cpp
|
|
/**
|
|
* @brief Initialize game state
|
|
*
|
|
* Called once at startup to set initial game values.
|
|
* Customize this for your game.
|
|
*
|
|
* @param state Pointer to GameState to initialize
|
|
*/
|
|
void game_init(GameState* state);
|
|
```
|
|
|
|
### ☐ Step 3.2: Create game_update() function
|
|
**Location**: After game_init()
|
|
**Action**: Update game state based on input
|
|
```cpp
|
|
/**
|
|
* @brief Update game state based on input event
|
|
*
|
|
* This is where your game logic goes.
|
|
* Called whenever an input event occurs.
|
|
*
|
|
* @param state Pointer to GameState to update
|
|
* @param input Input event to process
|
|
* @param config Game configuration
|
|
* @return true if screen needs refresh (drawing occurred)
|
|
*/
|
|
bool game_update(GameState* state, const InputEvent& input, const GameConfig& config);
|
|
```
|
|
|
|
### ☐ Step 3.3: Create game_draw() function
|
|
**Location**: After game_update()
|
|
**Action**: Draw game visuals based on state
|
|
```cpp
|
|
/**
|
|
* @brief Draw game graphics to screen buffer
|
|
*
|
|
* All drawing operations go here.
|
|
* Called only when game_update() returns true.
|
|
*
|
|
* @param state Pointer to current GameState
|
|
* @param renderer Renderer for drawing primitives
|
|
* @param gui GUI system for widgets (optional)
|
|
*/
|
|
void game_draw(const GameState* state, LowLevelRenderer* renderer, LowLevelGUI* gui);
|
|
```
|
|
|
|
---
|
|
|
|
## Phase 4: Refactor Main Function
|
|
|
|
### ☐ Step 4.1: Move global variables into main()
|
|
**Location**: main() function, beginning
|
|
**Action**: Move state variables into main as local variables
|
|
- Convert `last_x`, `last_y`, `was_touched` into GameState
|
|
- Convert `touch_fail_count`, `touch_success_count` into GameState
|
|
- Keep only interrupt flags as globals
|
|
|
|
### ☐ Step 4.2: Create and initialize GameConfig
|
|
**Location**: main() function, after display init
|
|
**Action**: Create GameConfig instance with sensible defaults
|
|
```cpp
|
|
GameConfig config = {
|
|
.touch_debounce_ms = 10,
|
|
.button_debounce_ms = 50,
|
|
.enable_gestures = true,
|
|
.enable_continuous_draw = true,
|
|
.debug_verbose = false
|
|
};
|
|
```
|
|
|
|
### ☐ Step 4.3: Create and initialize GameState
|
|
**Location**: main() function, after GameConfig
|
|
**Action**: Create GameState and call game_init()
|
|
```cpp
|
|
GameState game_state;
|
|
game_init(&game_state);
|
|
```
|
|
|
|
### ☐ Step 4.4: Call game_draw() for initial screen
|
|
**Location**: main() function, replace GUI setup code
|
|
**Action**: Replace hardcoded GUI drawing with game_draw() call
|
|
- Remove lines creating windows, status bars, gauges
|
|
- Call `game_draw(&game_state, &renderer, &gui);`
|
|
- Keep initial refresh_screen() call
|
|
|
|
### ☐ Step 4.5: Refactor main loop with reactive pattern
|
|
**Location**: main() while loop (lines ~321-423)
|
|
**Action**: Replace polling logic with clean reactive pattern
|
|
```cpp
|
|
// Reactive game loop pattern:
|
|
while (1) {
|
|
__wfi(); // Sleep until interrupt
|
|
|
|
InputEvent input = {0};
|
|
bool needs_refresh = false;
|
|
|
|
// 1. Process button input first (higher priority)
|
|
input = process_button_input(config);
|
|
if (input.valid) {
|
|
needs_refresh = game_update(&game_state, input, config);
|
|
}
|
|
|
|
// 2. Process touch input
|
|
if (!input.valid) {
|
|
input = process_touch_input(config, &last_touch_time);
|
|
if (input.valid) {
|
|
needs_refresh = game_update(&game_state, input, config);
|
|
}
|
|
}
|
|
|
|
// 3. Draw and refresh only if needed
|
|
if (needs_refresh) {
|
|
game_draw(&game_state, &renderer, &gui);
|
|
refresh_screen(bit_buffer, display);
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Phase 5: Implement Function Bodies
|
|
|
|
### ☐ Step 5.1: Implement process_touch_input()
|
|
**Action**: Move touch reading logic from main loop into this function
|
|
- Check `touch_interrupt_flag`, return invalid event if not set
|
|
- Implement debouncing using `last_time` parameter
|
|
- Read `TouchData` from touch controller
|
|
- Convert to `InputEvent` structure
|
|
- Handle `touch_event_down` flag for TOUCH_DOWN vs TOUCH_MOVE vs TOUCH_UP
|
|
- Return populated InputEvent
|
|
|
|
### ☐ Step 5.2: Implement process_button_input()
|
|
**Action**: Move button handling logic from main loop into this function
|
|
- Check button flags (`button_key0_pressed`, `button_key1_pressed`)
|
|
- Implement debouncing with `sleep_ms()` and `gpio_get()` verification
|
|
- Clear flags after processing
|
|
- Return InputEvent with button_id set
|
|
|
|
### ☐ Step 5.3: Implement get_gesture_name()
|
|
**Action**: Move gesture switch statement into this function
|
|
- Simple switch on gesture_code
|
|
- Return const char* string
|
|
|
|
### ☐ Step 5.4: Implement game_init()
|
|
**Action**: Initialize GameState fields
|
|
```cpp
|
|
void game_init(GameState* state) {
|
|
state->last_x = -1;
|
|
state->last_y = -1;
|
|
state->is_drawing = false;
|
|
state->score = 0;
|
|
state->game_over = false;
|
|
state->frame_count = 0;
|
|
state->touch_success_count = 0;
|
|
state->touch_fail_count = 0;
|
|
}
|
|
```
|
|
|
|
### ☐ Step 5.5: Implement game_update()
|
|
**Action**: Move game logic (drawing lines) into this function
|
|
- Handle different InputEvent types
|
|
- Update GameState based on input
|
|
- Return true if drawing occurred (needs refresh)
|
|
- Example: For drawing game, draw line from last_x/y to current x/y
|
|
|
|
### ☐ Step 5.6: Implement game_draw()
|
|
**Action**: Create initial UI or game graphics
|
|
- Can draw GUI elements (windows, gauges, etc.)
|
|
- Or draw game-specific graphics
|
|
- For drawing game, no additional drawing needed (done in game_update)
|
|
- Can add score display, game over screen, etc.
|
|
|
|
---
|
|
|
|
## Phase 6: Documentation and Comments
|
|
|
|
### ☐ Step 6.1: Update file header comment
|
|
**Action**: Change description to reflect template nature
|
|
- Update title to "Reactive Game Template"
|
|
- Add description of architecture
|
|
- Add usage instructions
|
|
|
|
### ☐ Step 6.2: Add section separator comments
|
|
**Action**: Add clear comment blocks separating each section
|
|
```cpp
|
|
// ============================================================================
|
|
// INPUT EVENT STRUCTURES
|
|
// ============================================================================
|
|
|
|
// ============================================================================
|
|
// GAME STATE AND CONFIGURATION
|
|
// ============================================================================
|
|
|
|
// ============================================================================
|
|
// INTERRUPT HANDLERS (Keep these minimal!)
|
|
// ============================================================================
|
|
|
|
// ============================================================================
|
|
// INPUT PROCESSING
|
|
// ============================================================================
|
|
|
|
// ============================================================================
|
|
// GAME LOGIC (Customize this section for your game!)
|
|
// ============================================================================
|
|
|
|
// ============================================================================
|
|
// MAIN PROGRAM
|
|
// ============================================================================
|
|
```
|
|
|
|
### ☐ Step 6.3: Add customization guide comments
|
|
**Action**: Add comments explaining what to modify for new games
|
|
```cpp
|
|
// ============================================================================
|
|
// HOW TO CREATE YOUR OWN GAME:
|
|
// ============================================================================
|
|
// 1. Modify GameState structure with your game variables
|
|
// 2. Implement game_init() to set initial values
|
|
// 3. Implement game_update() to handle input and update state
|
|
// 4. Implement game_draw() to render your game graphics
|
|
// 5. Adjust GameConfig for your game's needs
|
|
// 6. The reactive loop and input system work automatically!
|
|
// ============================================================================
|
|
```
|
|
|
|
---
|
|
|
|
## Phase 7: Testing and Validation
|
|
|
|
### ☐ Step 7.1: Verify compilation
|
|
**Action**: Build the project and fix any compilation errors
|
|
- Run `./build_and_flash.sh`
|
|
- Fix any syntax errors or missing definitions
|
|
|
|
### ☐ Step 7.2: Test on hardware (if available)
|
|
**Action**: Flash to device and test input/drawing
|
|
- Verify touch input works
|
|
- Verify buttons work (on e-ink board)
|
|
- Verify display only updates on input
|
|
|
|
### ☐ Step 7.3: Create example game variations
|
|
**Action**: Create commented-out example implementations in the file
|
|
- Add example game_update() for different game types
|
|
- Add example game_draw() for different visualizations
|
|
- Keep default drawing game as primary example
|
|
|
|
---
|
|
|
|
## Phase 8: Create Template Documentation
|
|
|
|
### ☐ Step 8.1: Create TEMPLATE_USAGE.md
|
|
**Action**: Write comprehensive guide for using the template
|
|
- Architecture overview
|
|
- How to customize for different games
|
|
- Example game ideas (snake, pong, tic-tac-toe, etc.)
|
|
- Input handling best practices
|
|
- E-ink optimization tips
|
|
|
|
### ☐ Step 8.2: Update README.md
|
|
**Action**: Add section about the reactive game template
|
|
- Link to TEMPLATE_USAGE.md
|
|
- Highlight key features
|
|
- Show before/after code structure
|
|
|
|
---
|
|
|
|
## Completion Checklist
|
|
|
|
- [ ] All Phase 1 steps completed (Core Structures)
|
|
- [ ] All Phase 2 steps completed (Input System)
|
|
- [ ] All Phase 3 steps completed (Game Logic)
|
|
- [ ] All Phase 4 steps completed (Main Function)
|
|
- [ ] All Phase 5 steps completed (Function Bodies)
|
|
- [ ] All Phase 6 steps completed (Documentation)
|
|
- [ ] All Phase 7 steps completed (Testing)
|
|
- [ ] All Phase 8 steps completed (Template Documentation)
|
|
|
|
---
|
|
|
|
## Notes for Implementation
|
|
|
|
**Work one step at a time**: Each checkbox should be completed and verified before moving to the next.
|
|
|
|
**Test incrementally**: After each phase, verify the code still compiles.
|
|
|
|
**Keep it simple**: The template should be easy to understand and modify.
|
|
|
|
**Optimize for e-ink**: Minimize refreshes, only redraw on input.
|
|
|
|
**Power efficiency**: Use `__wfi()` to sleep between inputs.
|
|
|
|
**Flexibility**: Template should work on all board types (TFT with touch, e-ink with buttons, etc.)
|