# 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.)