14 KiB
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)
// 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)
// 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
// 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_flagandtouch_event_downflag 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
/**
* @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
/**
* @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
/**
* @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
/**
* @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
/**
* @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
/**
* @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_touchedinto GameState - Convert
touch_fail_count,touch_success_countinto 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
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()
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
// 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_timeparameter - Read
TouchDatafrom touch controller - Convert to
InputEventstructure - Handle
touch_event_downflag 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()andgpio_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
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
// ============================================================================
// 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
// ============================================================================
// 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.)