diff --git a/MultigameRefactorPlan.md b/MultigameRefactorPlan.md deleted file mode 100644 index df4681c..0000000 --- a/MultigameRefactorPlan.md +++ /dev/null @@ -1,155 +0,0 @@ -Multigame Migration Plan -Overview -Refactor the existing monolithic tic-tac-toe implementation into a modular game architecture that allows multiple games to run on the RP2350 platform while preserving the interrupt-driven, dual-core reactive architecture. - -Goals -Abstract game logic from hardware/display management -Create reusable input processing system -Enable multiple games to coexist in the codebase -Maintain the efficient reactive architecture (event-driven, dual-core) -Keep hardware-specific code isolated in basic1.cpp -Architecture Components -1. Input Event System -Files: lib/input_event.h - -Defines shared input event structures used by both InputManager and Game classes: - -InputType enum (NONE, TOUCH_DOWN, TOUCH_MOVE, TOUCH_UP, BUTTON_0, BUTTON_1, GESTURE) -InputEvent struct with coordinates, gesture codes, button IDs, pressure, validity flag -2. Input Manager -Files: lib/input_manager.h, lib/input_manager.cpp - -Handles all input processing and debouncing: - -Constructor: InputManager(LowLevelTouch* touch, GameConfig* config) -Methods: process_touch_input(uint32_t* last_time), process_button_input() -Returns InputEvent objects to be passed to games -Manages touch debouncing and gesture recognition -Does NOT handle button GPIO setup (stays in basic1.cpp) -3. Abstract Game Base Class -Files: lib/game.h - -Defines the interface all games must implement: - -Constructor: Game(uint16_t width, uint16_t height, LowLevelRenderer* renderer, LowLevelGUI* gui) -Protected members: width, height, renderer, gui -Pure virtual methods: -virtual void init() = 0 - Initialize game state -virtual bool update(const InputEvent& event) = 0 - Process input, return true if redraw needed -virtual void draw() = 0 - Render game to buffer -Virtual destructor: virtual ~Game() {} -4. Tic-Tac-Toe Game Implementation -Files: games/tic_tac_toe.h, games/tic_tac_toe.cpp - -Concrete implementation of the Game interface: - -Private GameState struct with board, current player, winner, statistics -Private method: check_winner() -Private constants: BOARD_SIZE, CELL_SIZE, BOARD_Y, LINE_WIDTH -Overrides: init(), update(const InputEvent& event), draw() -Encapsulates all tic-tac-toe logic extracted from basic1.cpp lines 162-770 -5. Main Loop Refactor -File: basic1.cpp - -Simplified main program focusing on hardware management: - -Keep: Display initialization, dual-core setup, interrupt handlers, GPIO configuration -Remove: Game-specific logic (moves to TicTacToeGame) -Add: InputManager instantiation, Game* pointer instantiation -Modify main loop: -Migration Steps -Step 1: Create Input Event Header -Extract input structures from basic1.cpp (lines 134-156) to new file lib/input_event.h. - -Validation: - -Compile successfully -No duplicate definitions -Both basic1.cpp and new files include this header -Step 2: Create Input Manager -Move input processing functions (lines 313-442) to new InputManager class. - -Validation: - -Constructor properly stores touch pointer and config -process_touch_input() returns valid InputEvent on touch -process_button_input() returns valid InputEvent on button press -Debouncing still works correctly -Compile and run - verify touch/button detection unchanged -Step 3: Create Abstract Game Class -Create lib/game.h with base class definition. - -Validation: - -Header compiles successfully -Pure virtual methods defined correctly -Protected members accessible to derived classes -Step 4: Extract Tic-Tac-Toe Game -Create TicTacToeGame class inheriting from Game. - -Validation: - -Move GameState struct (lines 162-176) - verify all members present -Move check_winner() (lines 447-495) - verify logic identical -Move game_init() to init() override (lines 505-522) -Move game_update() to update() override (lines 536-668) -Move game_draw() to draw() override (lines 671-770) -Move constants: BOARD_SIZE, CELL_SIZE, BOARD_Y -Compile successfully -Step 5: Refactor Main Loop -Update basic1.cpp to use new architecture. - -Validation: - -Remove old GameState state variable -Remove old game functions -Instantiate InputManager after hardware init -Instantiate TicTacToeGame as Game* -Update main loop to use polymorphic calls -Compile successfully -Run and verify: Touch input works, buttons work, game plays correctly -Verify dual-core refresh still works (Core 1 handles display) -Test full game: place pieces, win conditions, restart -Step 6: Update Build System -Modify CMakeLists.txt to include new source files. - -Validation: - -Add lib/input_manager.cpp -Add games/tic_tac_toe.cpp -Clean build succeeds -Binary size reasonable (no major bloat) -Step 7: Final Testing -Comprehensive system test. - -Validation: - -Touch detection works (tap cells) -Button navigation works (KEY0/KEY1) -Game logic correct (wins, ties, restart) -Display refreshes properly (no artifacts) -Statistics persist across games -Debug output shows correct events -System remains responsive during e-ink refresh -Future Extensions -Adding New Games -To add a new game (e.g., Snake, Pong): - -Create games/snake.h and games/snake.cpp -Inherit from Game base class -Implement init(), update(), draw() -Define game-specific state as private members -Add to CMakeLists.txt -Change game selection in basic1.cpp: Game* current_game = new SnakeGame(...) -Game Launcher (Future) -Create GameLauncher class to display menu -Allow runtime game switching -Handle cleanup: delete current_game; current_game = new SnakeGame(...) -Store game selection preference in flash -Key Design Principles -Hardware Isolation: All GPIO, interrupts, hardware config stay in basic1.cpp -State Encapsulation: Each game owns its state completely -Polymorphism: Use Game* pointer for runtime flexibility -Event-Driven: Games respond to InputEvent objects only -Reactive Rendering: Only redraw when update() returns true -Dual-Core Efficiency: Core 0 handles logic, Core 1 handles display refresh \ No newline at end of file diff --git a/basic1.cpp b/basic1.cpp index 25fc2f2..7501e71 100644 --- a/basic1.cpp +++ b/basic1.cpp @@ -505,10 +505,10 @@ int main() game_start_time = 0; // Force full clear for clean transition to game display->clear(false); - if (display->get_type() == DISPLAY_TYPE_EPAPER) { - LowLevelDisplayEPaper* epaper = static_cast(display); - epaper->full_refresh(); - } + // if (display->get_type() == DISPLAY_TYPE_EPAPER) { + // LowLevelDisplayEPaper* epaper = static_cast(display); + // epaper->full_refresh(); + // } } needs_refresh = true; } diff --git a/games/monopoly/DiceModalGame.h b/games/monopoly/DiceModalGame.h new file mode 100644 index 0000000..74e7a51 --- /dev/null +++ b/games/monopoly/DiceModalGame.h @@ -0,0 +1,32 @@ +// DiceModalGame.h +#pragma once +#include "../../lib/game.h" +#include "../../display/low_level_render.h" +#include "../../display/low_level_gui.h" +#include "input_manager.h" + +class DiceModalGame : public Game { + int dice1, dice2; + bool dismissed; +public: + DiceModalGame(uint16_t width, uint16_t height, LowLevelRenderer* renderer, LowLevelGUI* gui, InputManager* input_manager, int d1, int d2) + : Game(width, height, renderer, gui, input_manager), dice1(d1), dice2(d2), dismissed(false) {} + void init() override { dismissed = false; } + bool update(const InputEvent& event) override { + if (event.type == INPUT_BUTTON_0 || event.type == INPUT_BUTTON_1) { + dismissed = true; + return true; + } + return false; + } + void draw() override { + int win_w = 220, win_h = 120; + int win_x = (width - win_w) / 2, win_y = (height - win_h) / 2; + char buf[64]; + gui->draw_new_window(win_x, win_y, win_w, win_h, "Dice Roll"); + snprintf(buf, sizeof(buf), "You rolled: %d + %d", dice1, dice2); + renderer->draw_string_scaled(win_x + 30, win_y + 40, buf, 2); + renderer->draw_string_scaled(10, height - 20, "Press any button...", 2); + } + bool is_dismissed() const { return dismissed; } +}; diff --git a/games/monopoly/PropertyModalGame.cpp b/games/monopoly/PropertyModalGame.cpp new file mode 100644 index 0000000..e69de29 diff --git a/games/monopoly/PropertyModalGame.h b/games/monopoly/PropertyModalGame.h new file mode 100644 index 0000000..e4ce039 --- /dev/null +++ b/games/monopoly/PropertyModalGame.h @@ -0,0 +1,47 @@ +// PropertyModalGame.h +#pragma once +#include "../../lib/game.h" +#include "../../display/low_level_render.h" +#include "../../display/low_level_gui.h" +#include "input_manager.h" +#include "monopoly_board.h" + +class PropertyModalGame : public Game { + const BoardTile* property; + bool dismissed; +public: + PropertyModalGame(uint16_t width, uint16_t height, LowLevelRenderer* renderer, LowLevelGUI* gui, InputManager* input_manager, const BoardTile* prop) + : Game(width, height, renderer, gui, input_manager), property(prop), dismissed(false) {} + void init() override { dismissed = false; } + bool update(const InputEvent& event) override { + if (event.type == INPUT_BUTTON_0 || event.type == INPUT_BUTTON_1) { + dismissed = true; + return true; + } + return false; + } + void draw() override { + int win_w = 320, win_h = 180; + int win_x = (width - win_w) / 2, win_y = (height - win_h) / 2; + char buf[128]; + snprintf(buf, sizeof(buf), "Property: %s", property->name); + gui->draw_new_window(win_x, win_y, win_w, win_h, buf); + int py = win_y + 30; + char pbuf[128]; + if (property->type == TILE_PROPERTY) { + snprintf(pbuf, sizeof(pbuf), "Cost: $%d", property->cost); + renderer->draw_string_scaled(win_x + 20, py, pbuf, 2); + py += 25; + snprintf(pbuf, sizeof(pbuf), "Rent: $%d", property->rent[0]); + renderer->draw_string_scaled(win_x + 20, py, pbuf, 2); + py += 25; + snprintf(pbuf, sizeof(pbuf), "House Cost: $%d", property->house_cost); + renderer->draw_string_scaled(win_x + 20, py, pbuf, 2); + } else if (property->type == TILE_RAILROAD || property->type == TILE_UTILITY) { + snprintf(pbuf, sizeof(pbuf), "Cost: $%d", property->cost); + renderer->draw_string_scaled(win_x + 20, py, pbuf, 2); + } + renderer->draw_string_scaled(10, height - 20, "Press any button...", 2); + } + bool is_dismissed() const { return dismissed; } +}; diff --git a/games/monopoly/monopoly_game.cpp b/games/monopoly/monopoly_game.cpp index 5c2f4ed..94a1c09 100644 --- a/games/monopoly/monopoly_game.cpp +++ b/games/monopoly/monopoly_game.cpp @@ -11,8 +11,14 @@ extern "C" { #ifdef __cplusplus } #endif + + #include #include +#include +#include +#include "DiceModalGame.h" +#include "PropertyModalGame.h" // --- Constructor --- @@ -36,6 +42,8 @@ void MonopolyGame::init() { double_rolls = 0; just_sent_to_jail = false; selected_action = 0; + srand(time(NULL)); + if (active_modal) { delete active_modal; active_modal = nullptr; } // TODO: Reset all board state, property ownership, etc. } @@ -44,11 +52,17 @@ bool MonopolyGame::update(const InputEvent& event) { Player* p = &players[current_player_idx]; bool needs_redraw = false; - // If modal is open, any button closes it - if (show_property_modal) { - if (event.type == INPUT_BUTTON_0 || event.type == INPUT_BUTTON_1) { - show_property_modal = false; - modal_property_index = -1; + // If a modal is active, delegate input and check for dismissal + if (active_modal) { + bool modal_redraw = active_modal->update(event); + if (modal_redraw) needs_redraw = true; + // If modal is dismissed, delete and return control + // Use dynamic_cast to check for modal type and dismissal + auto dice_modal = dynamic_cast(active_modal); + auto prop_modal = dynamic_cast(active_modal); + if ((dice_modal && dice_modal->is_dismissed()) || (prop_modal && prop_modal->is_dismissed())) { + delete active_modal; + active_modal = nullptr; needs_redraw = true; } return needs_redraw; @@ -71,11 +85,16 @@ bool MonopolyGame::update(const InputEvent& event) { if (p->position < old_pos) p->balance += 200; has_rolled = true; needs_redraw = true; + // Store dice values and show dice modal + last_dice1 = dice1; + last_dice2 = dice2; + if (active_modal) delete active_modal; + active_modal = new DiceModalGame(width, height, renderer, gui, input_manager, dice1, dice2); // Show property modal if landed on property/railroad/utility const BoardTile* landed = &MONOPOLY_BOARD[p->position]; if (landed->type == TILE_PROPERTY || landed->type == TILE_RAILROAD || landed->type == TILE_UTILITY) { - show_property_modal = true; modal_property_index = p->position; + // Queue property modal after dice modal is dismissed } // TODO: Handle doubles, jail, landing effects } @@ -111,27 +130,42 @@ bool MonopolyGame::update(const InputEvent& event) { default: break; } + // If dice modal was just dismissed and a property modal is queued, show it + if (!active_modal && modal_property_index >= 0) { + active_modal = new PropertyModalGame(width, height, renderer, gui, input_manager, &MONOPOLY_BOARD[modal_property_index]); + modal_property_index = -1; + needs_redraw = true; + } return needs_redraw; } // --- Draw game state (minimal: player info, current tile, actions) --- void MonopolyGame::draw() { + // If a modal is active, draw it and return + if (active_modal) { + active_modal->draw(); + return; + } Player* p = &players[current_player_idx]; const BoardTile* tile = &MONOPOLY_BOARD[p->position]; // Title - renderer->draw_string_scaled(10, 10, "Monopoly (Minimal)", 2); + renderer->draw_string_scaled(10, 10, "Monopoly", 3); // --- Player Stats (Right Side) --- - int stats_x = width - 180; - int y = 20; + int stats_x = width - 200; + int y = 40 + 15 * current_player_idx; char buf[128]; - // Name (Big) - renderer->draw_string_scaled(stats_x, y, p->name, 2); - y += 40; + // make a window showing player stats + LowLevelWindow* stats_win = gui->draw_new_window(stats_x - 10, y - 10 , 190, 120, p->name); + y += 20; + renderer->draw_string_scaled(stats_x, y, "Location:", 1); + renderer->draw_string_scaled(stats_x, y + 10, tile->name, 2); + y += 30; // Money snprintf(buf, sizeof(buf), "$%d", p->balance); - renderer->draw_string_scaled(stats_x, y, buf, 2); + renderer->draw_string_scaled(stats_x, y, "Money:", 1); + renderer->draw_string_scaled(stats_x, y + 10, buf, 2); y += 30; // Properties int prop_count = 0; @@ -140,8 +174,8 @@ void MonopolyGame::draw() { if (prop_idx >= 0 && MONOPOLY_BOARD[prop_idx].type == TILE_PROPERTY) prop_count++; } snprintf(buf, sizeof(buf), "Properties: %d", prop_count); - renderer->draw_string_scaled(stats_x, y, buf, 2); - y += 30; + renderer->draw_string_scaled(stats_x, y, buf, 1); + y += 10; // Monopoly count int monopoly_count = 0; // For each group, check if player owns all properties in group @@ -158,49 +192,7 @@ void MonopolyGame::draw() { if (group_total > 0 && group_total == group_owned) monopoly_count++; } snprintf(buf, sizeof(buf), "Monopolies: %d", monopoly_count); - renderer->draw_string_scaled(stats_x, y, buf, 2); - - // --- Main Info (Left Side) --- - snprintf(buf, sizeof(buf), "Player: %s ($%d)", p->name, p->balance); - renderer->draw_string_scaled(10, 30, buf, 2); - - snprintf(buf, sizeof(buf), "Location: %s", tile->name); - renderer->draw_string_scaled(10, 50, buf, 2); - - // Show property modal window if needed - if (show_property_modal && modal_property_index >= 0) { - const BoardTile* mprop = &MONOPOLY_BOARD[modal_property_index]; - int win_w = 320, win_h = 180; - int win_x = (width - win_w) / 2, win_y = (height - win_h) / 2; - LowLevelWindow* win = gui->draw_new_window(win_x, win_y, win_w, win_h, "Property Info"); - // gui->current_font = renderer->get_current_font(); - int py = win_y + 30; - char pbuf[128]; - if (mprop->type == TILE_PROPERTY) { - snprintf(pbuf, sizeof(pbuf), "%s", mprop->name); - renderer->draw_string_scaled(win_x + 20, py, pbuf, 2); - py += 30; - snprintf(pbuf, sizeof(pbuf), "Color: %s", mprop->color ? mprop->color : "-"); - renderer->draw_string_scaled(win_x + 20, py, pbuf, 2); - py += 25; - snprintf(pbuf, sizeof(pbuf), "Cost: $%d", mprop->cost); - renderer->draw_string_scaled(win_x + 20, py, pbuf, 2); - py += 25; - snprintf(pbuf, sizeof(pbuf), "Rent: $%d", mprop->rent[0]); - renderer->draw_string_scaled(win_x + 20, py, pbuf, 2); - py += 25; - snprintf(pbuf, sizeof(pbuf), "House Cost: $%d", mprop->house_cost); - renderer->draw_string_scaled(win_x + 20, py, pbuf, 2); - } else if (mprop->type == TILE_RAILROAD || mprop->type == TILE_UTILITY) { - snprintf(pbuf, sizeof(pbuf), "%s", mprop->name); - renderer->draw_string_scaled(win_x + 20, py, pbuf, 2); - py += 30; - snprintf(pbuf, sizeof(pbuf), "Cost: $%d", mprop->cost); - renderer->draw_string_scaled(win_x + 20, py, pbuf, 2); - } - renderer->draw_string_scaled(win_x + 20, win_y + win_h - 40, "Press any button...", 2); - return; - } + renderer->draw_string_scaled(stats_x, y, buf, 1); // Draw action menu (highlight selected) const char* actions[ACTION_COUNT] = {"Roll Dice", "Buy Property", "End Turn"}; @@ -210,8 +202,6 @@ void MonopolyGame::draw() { renderer->draw_string_scaled(10, y, buf, 2); } - // renderer->draw_string_scaled(10, height - 20, "BTN0: Next Option BTN1: Select", 2); - // TODO: Draw board, all players, property ownership, jail, chance, etc. // TODO: Add win/lose/game over conditions // TODO: Add touch support, more UI, etc. diff --git a/games/monopoly/monopoly_game.h b/games/monopoly/monopoly_game.h index 82fede8..08a829b 100644 --- a/games/monopoly/monopoly_game.h +++ b/games/monopoly/monopoly_game.h @@ -26,6 +26,7 @@ public: bool update(const InputEvent& event) override; void draw() override; + private: // Game state and helpers Player players[MAX_PLAYERS]; @@ -39,8 +40,10 @@ private: int selected_action; // 0: Roll, 1: Buy, 2: End Turn static constexpr int ACTION_COUNT = 3; - // Modal property window state - bool show_property_modal = false; + // Modal games + Game* active_modal = nullptr; + int last_dice1 = 0; + int last_dice2 = 0; int modal_property_index = -1; };