Add reactive game template refactoring plan

This commit is contained in:
Adolfo Reyna
2026-01-29 15:38:20 -05:00
parent 26e67d13d3
commit de566223b9

438
REFACTORING_PLAN.md Normal file
View File

@@ -0,0 +1,438 @@
# 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.)