Files
basic1/REFACTORING_PLAN.md
2026-01-29 15:38:20 -05:00

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_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

/**
 * @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_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

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_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

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