From e2817262b00e1a3fbfd8d7f005beb97192db95ac Mon Sep 17 00:00:00 2001 From: Adolfo Reyna Date: Fri, 6 Feb 2026 22:11:24 -0500 Subject: [PATCH] Add virtual touch buttons for Monopoly game and centralize configuration in ModalButtonHelper --- board_config.h | 4 +- games/monopoly/BoardModalGame.h | 8 +++- games/monopoly/ChanceModalGame.h | 8 +++- games/monopoly/CommunityChestModalGame.h | 8 +++- games/monopoly/DiceModalGame.h | 8 +++- games/monopoly/ModalButtonHelper.h | 60 ++++++++++++++++++++++++ games/monopoly/PropertyModalGame.h | 8 +++- games/monopoly/TurnModalGame.h | 8 +++- games/monopoly/monopoly_game.cpp | 16 +++++++ lib/input_manager.cpp | 43 +++++++++++++++++ lib/input_manager.h | 31 ++++++++++++ 11 files changed, 194 insertions(+), 8 deletions(-) create mode 100644 games/monopoly/ModalButtonHelper.h diff --git a/board_config.h b/board_config.h index b725ad3..1d2ddd8 100644 --- a/board_config.h +++ b/board_config.h @@ -16,9 +16,9 @@ // ============================================================================ // ---- SELECT YOUR BOARD HERE ---- -// #define BOARD_FEATHER_TFT // Feather RP2350 + 4.0" TFT ST7796 +#define BOARD_FEATHER_TFT // Feather RP2350 + 4.0" TFT ST7796 // #define BOARD_PICO2_TFT // Pico 2 + 4.0" TFT ST7796 -#define BOARD_PICO2_EINK // Pico 2 + E-Ink Display +// #define BOARD_PICO2_EINK // Pico 2 + E-Ink Display // -------------------------------- // ============================================================================ diff --git a/games/monopoly/BoardModalGame.h b/games/monopoly/BoardModalGame.h index 476bcc7..6b9109d 100644 --- a/games/monopoly/BoardModalGame.h +++ b/games/monopoly/BoardModalGame.h @@ -7,6 +7,7 @@ #include "monopoly_board.h" #include "player.h" #include "MonopolyBoardRenderer.h" +#include "ModalButtonHelper.h" class BoardModalGame : public Game { bool dismissed; @@ -18,7 +19,10 @@ public: BoardModalGame(uint16_t width, uint16_t height, LowLevelRenderer* renderer, LowLevelGUI* gui, InputManager* input_manager, Player* p, int count, int current_idx) : Game(width, height, renderer, gui, input_manager), dismissed(false), players(p), players_count(count), observer_idx(current_idx) {} - void init() override { dismissed = false; } + void init() override { + dismissed = false; + ModalButtonHelper::set_monopoly_regions(input_manager, width, height); + } Type get_type() const override { return Type::MONOPOLY_BOARD; } bool update(const InputEvent& event) override { @@ -119,6 +123,8 @@ public: } renderer->draw_string_scaled(ix + (iw - 12 * 6) / 2, iy + ih - 15, "PRESS BUTTON", 1); + + ModalButtonHelper::draw_virtual_buttons(renderer, input_manager); } public: diff --git a/games/monopoly/ChanceModalGame.h b/games/monopoly/ChanceModalGame.h index 4688389..6433eb5 100644 --- a/games/monopoly/ChanceModalGame.h +++ b/games/monopoly/ChanceModalGame.h @@ -9,6 +9,7 @@ #include "MonopolyBoardRenderer.h" #include "sprites.h" +#include "ModalButtonHelper.h" class ChanceModalGame : public Game { const ChanceCard* card; @@ -21,7 +22,10 @@ public: ChanceModalGame(uint16_t width, uint16_t height, LowLevelRenderer* renderer, LowLevelGUI* gui, InputManager* input_manager, const ChanceCard* c, Player* p_list = nullptr, int p_count = 0, int p_pos = -1) : Game(width, height, renderer, gui, input_manager), card(c), dismissed(false), players(p_list), players_count(p_count), player_pos(p_pos) {} - void init() override { dismissed = false; } + void init() override { + dismissed = false; + ModalButtonHelper::set_monopoly_regions(input_manager, width, height); + } Type get_type() const override { return Type::MONOPOLY_CHANCE; } bool update(const InputEvent& event) override { @@ -89,6 +93,8 @@ public: // Prompt renderer->draw_string_scaled(ix + (iw - 12 * 12) / 2, iy + ih - 20, "Press BUTTON", 2); + + ModalButtonHelper::draw_virtual_buttons(renderer, input_manager); } bool is_dismissed() const { return dismissed; } diff --git a/games/monopoly/CommunityChestModalGame.h b/games/monopoly/CommunityChestModalGame.h index 90eacbc..b02fed8 100644 --- a/games/monopoly/CommunityChestModalGame.h +++ b/games/monopoly/CommunityChestModalGame.h @@ -8,6 +8,7 @@ #include "player.h" #include "MonopolyBoardRenderer.h" #include "sprites.h" +#include "ModalButtonHelper.h" class CommunityChestModalGame : public Game { const CommunityCard* card; @@ -20,7 +21,10 @@ public: CommunityChestModalGame(uint16_t width, uint16_t height, LowLevelRenderer* renderer, LowLevelGUI* gui, InputManager* input_manager, const CommunityCard* c, Player* p_list = nullptr, int p_count = 0, int p_pos = -1) : Game(width, height, renderer, gui, input_manager), card(c), dismissed(false), players(p_list), players_count(p_count), player_pos(p_pos) {} - void init() override { dismissed = false; } + void init() override { + dismissed = false; + ModalButtonHelper::set_monopoly_regions(input_manager, width, height); + } Type get_type() const override { return Type::MONOPOLY_COMMUNITY_CHEST; } bool update(const InputEvent& event) override { @@ -86,6 +90,8 @@ public: // Prompt renderer->draw_string_scaled(ix + (iw - 12 * 12) / 2, iy + ih - 20, "Press BUTTON", 2); + + ModalButtonHelper::draw_virtual_buttons(renderer, input_manager); } bool is_dismissed() const { return dismissed; } diff --git a/games/monopoly/DiceModalGame.h b/games/monopoly/DiceModalGame.h index 0277ccd..e6e5424 100644 --- a/games/monopoly/DiceModalGame.h +++ b/games/monopoly/DiceModalGame.h @@ -5,6 +5,7 @@ #include "../../display/low_level_gui.h" #include "input_manager.h" #include "MonopolyBoardRenderer.h" +#include "ModalButtonHelper.h" class DiceModalGame : public Game { int dice1, dice2; @@ -49,7 +50,10 @@ class DiceModalGame : public Game { public: DiceModalGame(uint16_t width, uint16_t height, LowLevelRenderer* renderer, LowLevelGUI* gui, InputManager* input_manager, int d1, int d2, const BoardTile* from, const BoardTile* to, Player* p, int count) : Game(width, height, renderer, gui, input_manager), dice1(d1), dice2(d2), from_tile(from), to_tile(to), players(p), players_count(count), dismissed(false) {} - void init() override { dismissed = false; } + void init() override { + dismissed = false; + ModalButtonHelper::set_monopoly_regions(input_manager, width, height); + } Type get_type() const override { return Type::MONOPOLY_DICE; } bool update(const InputEvent& event) override { // Only B dismisses now, so A can still be used for "change action" (even if it does nothing here) @@ -114,6 +118,8 @@ public: renderer->set_text_color(false); renderer->draw_string_scaled(btn_x + 5, btn_y + 5, "> CONTINUE", 2); renderer->set_text_color(true); + + ModalButtonHelper::draw_virtual_buttons(renderer, input_manager); } bool is_dismissed() const { return dismissed; } }; diff --git a/games/monopoly/ModalButtonHelper.h b/games/monopoly/ModalButtonHelper.h new file mode 100644 index 0000000..496618f --- /dev/null +++ b/games/monopoly/ModalButtonHelper.h @@ -0,0 +1,60 @@ +// ModalButtonHelper.h +#pragma once +#include "../../lib/input_manager.h" +#include "../../display/low_level_render.h" + +class ModalButtonHelper { +public: + /** + * @brief Sets the standard Monopoly virtual button regions + */ + static void set_monopoly_regions(InputManager* input_manager, uint16_t width, uint16_t height) { + if (!input_manager) return; + + // --- BUTTON CONFIGURATION --- + // Adjust these variables to move the virtual buttons easily + int btn_w = 60; + int btn_h = 60; + int margin_right = 135; // Positioned for the inner board area (W - 135 = 345) + int start_y = 80; // Vertical start position + int spacing = 30; // Gap between buttons + + int btn_x = width - margin_right; + int btn_a_y = start_y; + int btn_b_y = btn_a_y + btn_h + spacing; + + // Apply regions to input manager + input_manager->set_virtual_button_regions( + btn_x, btn_a_y, btn_w, btn_h, // Button A (Top) + btn_x, btn_b_y, btn_w, btn_h // Button B (Bottom) + ); + } + + /** + * @brief Draws virtual A and B buttons at consistent screen locations + */ + static void draw_virtual_buttons(LowLevelRenderer* renderer, InputManager* input_manager) { + if (!renderer || !input_manager) return; + + int a[4], b[4]; + input_manager->get_virtual_button_regions(a, b); + + // Save current color + bool original_color = renderer->get_current_text_color(); + + // Draw Button A (Top) + renderer->draw_filled_rectangle(a[0], a[1], a[2], a[3], false, 0); // White back + renderer->draw_rectangle(a[0], a[1], a[2], a[3], true, 2); // Black border + renderer->set_text_color(true); + renderer->draw_string_scaled(a[0] + (a[2] - 12) / 2, a[1] + (a[3] - 16) / 2, "A", 2); + + // Draw Button B (Under A) + renderer->draw_filled_rectangle(b[0], b[1], b[2], b[3], false, 0); // White back + renderer->draw_rectangle(b[0], b[1], b[2], b[3], true, 2); // Black border + renderer->set_text_color(true); + renderer->draw_string_scaled(b[0] + (b[2] - 12) / 2, b[1] + (b[3] - 16) / 2, "B", 2); + + // Restore color + renderer->set_text_color(original_color); + } +}; diff --git a/games/monopoly/PropertyModalGame.h b/games/monopoly/PropertyModalGame.h index a4f4771..18e6dc0 100644 --- a/games/monopoly/PropertyModalGame.h +++ b/games/monopoly/PropertyModalGame.h @@ -8,6 +8,7 @@ #include "player.h" #include "MonopolyBoardRenderer.h" #include "sprites.h" +#include "ModalButtonHelper.h" class PropertyModalGame : public Game { const BoardTile* property; @@ -28,7 +29,10 @@ public: : Game(width, height, renderer, gui, input_manager), property(prop), dismissed(false), is_owned(owned), owner_name(owner), owner_id(o_id), can_afford(affordable), selected_choice(0), buy_requested(false), rent_requested(false), players(p_list), players_count(p_count), observer_idx(obs_idx) { if (is_owned || !can_afford) selected_choice = 1; } - void init() override { dismissed = false; buy_requested = false; rent_requested = false; selected_choice = 0; } + void init() override { + dismissed = false; buy_requested = false; rent_requested = false; selected_choice = 0; + ModalButtonHelper::set_monopoly_regions(input_manager, width, height); + } Type get_type() const override { return Type::MONOPOLY_PROPERTY; } bool update(const InputEvent& event) override { // If rent is owed, any button pays it (it's forced) @@ -189,6 +193,8 @@ public: renderer->draw_string_scaled(win_x + (win_w - 7 * 12) / 2, btn_y + 10, ">PASS", 2); renderer->set_text_color(true); } + + ModalButtonHelper::draw_virtual_buttons(renderer, input_manager); } bool is_dismissed() const { return dismissed; } bool wants_to_buy() const { return buy_requested; } diff --git a/games/monopoly/TurnModalGame.h b/games/monopoly/TurnModalGame.h index df4797d..6009db1 100644 --- a/games/monopoly/TurnModalGame.h +++ b/games/monopoly/TurnModalGame.h @@ -5,6 +5,7 @@ #include "../../display/low_level_gui.h" #include "input_manager.h" #include "player.h" +#include "ModalButtonHelper.h" #include #include @@ -16,7 +17,10 @@ public: TurnModalGame(uint16_t width, uint16_t height, LowLevelRenderer* renderer, LowLevelGUI* gui, InputManager* input_manager, Player* next) : Game(width, height, renderer, gui, input_manager), next_player(next), dismissed(false) {} - void init() override { dismissed = false; } + void init() override { + dismissed = false; + ModalButtonHelper::set_monopoly_regions(input_manager, width, height); + } Type get_type() const override { return Type::MONOPOLY_TURN; } bool update(const InputEvent& event) override { @@ -61,6 +65,8 @@ public: // Prompt const char* prompt = "Press B to start"; renderer->draw_string_scaled(ix + (iw - (int)strlen(prompt) * 6) / 2, iy + ih - 30, prompt, 1); + + ModalButtonHelper::draw_virtual_buttons(renderer, input_manager); } bool is_dismissed() const { return dismissed; } diff --git a/games/monopoly/monopoly_game.cpp b/games/monopoly/monopoly_game.cpp index be7cd23..91fd347 100644 --- a/games/monopoly/monopoly_game.cpp +++ b/games/monopoly/monopoly_game.cpp @@ -23,6 +23,7 @@ extern "C" { #include "ChanceModalGame.h" #include "CommunityChestModalGame.h" #include "TurnModalGame.h" +#include "ModalButtonHelper.h" #include "sprites.h" #include "MonopolyBoardRenderer.h" @@ -39,6 +40,9 @@ MonopolyGame::MonopolyGame(uint16_t width, uint16_t height, LowLevelRenderer* re // --- Initialize game state --- void MonopolyGame::init() { + // Initialize regions for touch buttons + ModalButtonHelper::set_monopoly_regions(input_manager, width, height); + // Hardcoded 2 players for minimal version init_player(&players[0], 0, "Elias", "Top Hat"); init_player(&players[1], 1, "Adolfo", "Racecar"); @@ -114,6 +118,7 @@ bool MonopolyGame::update(const InputEvent& event) { delete active_modal; active_modal = nullptr; needs_redraw = true; + ModalButtonHelper::set_monopoly_regions(input_manager, width, height); // Immediately check if we need to show a property modal if (modal_property_index >= 0) { @@ -141,10 +146,14 @@ bool MonopolyGame::update(const InputEvent& event) { active_modal = new CommunityChestModalGame(width, height, renderer, gui, input_manager, &COMMUNITY_DECK[last_drawn_community_idx], players, players_count, p->position); // We'll apply the effect when CommunityChestModal is dismissed } + if (active_modal) active_modal->init(); return needs_redraw; } else if (chance_modal && chance_modal->is_dismissed()) { const ChanceCard* card = &CHANCE_DECK[last_drawn_chance_idx]; last_drawn_chance_idx = -1; + delete active_modal; + active_modal = nullptr; + ModalButtonHelper::set_monopoly_regions(input_manager, width, height); // Apply card effects bool position_changed = false; @@ -204,6 +213,7 @@ bool MonopolyGame::update(const InputEvent& event) { delete active_modal; active_modal = nullptr; needs_redraw = true; + ModalButtonHelper::set_monopoly_regions(input_manager, width, height); if (position_changed) { // If we moved, check if we landed on a property @@ -269,6 +279,7 @@ bool MonopolyGame::update(const InputEvent& event) { delete active_modal; active_modal = nullptr; needs_redraw = true; + ModalButtonHelper::set_monopoly_regions(input_manager, width, height); if (position_changed) { const BoardTile* landed = &MONOPOLY_BOARD[p->position]; @@ -360,14 +371,17 @@ bool MonopolyGame::update(const InputEvent& event) { delete active_modal; active_modal = nullptr; needs_redraw = true; + ModalButtonHelper::set_monopoly_regions(input_manager, width, height); } else if (board_modal && board_modal->is_dismissed()) { delete active_modal; active_modal = nullptr; needs_redraw = true; + ModalButtonHelper::set_monopoly_regions(input_manager, width, height); } else if (turn_modal && turn_modal->is_dismissed()) { delete active_modal; active_modal = nullptr; needs_redraw = true; + ModalButtonHelper::set_monopoly_regions(input_manager, width, height); } return needs_redraw; } @@ -544,4 +558,6 @@ void MonopolyGame::draw() { renderer->draw_string_scaled(ix + 15, content_y, buf, 2); content_y += 25; } + + ModalButtonHelper::draw_virtual_buttons(renderer, input_manager); } diff --git a/lib/input_manager.cpp b/lib/input_manager.cpp index e023629..7f61047 100644 --- a/lib/input_manager.cpp +++ b/lib/input_manager.cpp @@ -93,6 +93,14 @@ InputEvent InputManager::process_touch_input(uint32_t* last_time) { // Determine event type if (*last_time == 0) { event.type = INPUT_TOUCH_DOWN; + + // Check for virtual buttons + InputType virtual_type; + if (check_virtual_buttons(event.x, event.y, virtual_type)) { + event.type = virtual_type; + event.button_id = (virtual_type == INPUT_BUTTON_0) ? 0 : 1; + printf("Virtual button %d pressed via touch\n", event.button_id); + } } else { event.type = INPUT_TOUCH_MOVE; } @@ -164,3 +172,38 @@ const char* InputManager::get_gesture_name(uint8_t gesture_code) { default: return "Unknown"; } } + +void InputManager::get_virtual_button_regions(int* a_rect, int* b_rect) const { + for (int i = 0; i < 4; i++) { + a_rect[i] = v_button_a[i]; + b_rect[i] = v_button_b[i]; + } +} + +void InputManager::set_virtual_button_regions(int ax, int ay, int aw, int ah, int bx, int by, int bw, int bh) { + v_button_a[0] = ax; v_button_a[1] = ay; v_button_a[2] = aw; v_button_a[3] = ah; + v_button_b[0] = bx; v_button_b[1] = by; v_button_b[2] = bw; v_button_b[3] = bh; + v_buttons_active = true; +} + +void InputManager::clear_virtual_button_regions() { + v_buttons_active = false; +} + +bool InputManager::check_virtual_buttons(int16_t x, int16_t y, InputType& out_type) const { + if (!v_buttons_active) return false; + + if (x >= v_button_a[0] && x <= v_button_a[0] + v_button_a[2] && + y >= v_button_a[1] && y <= v_button_a[1] + v_button_a[3]) { + out_type = INPUT_BUTTON_0; + return true; + } + + if (x >= v_button_b[0] && x <= v_button_b[0] + v_button_b[2] && + y >= v_button_b[1] && y <= v_button_b[1] + v_button_b[3]) { + out_type = INPUT_BUTTON_1; + return true; + } + + return false; +} diff --git a/lib/input_manager.h b/lib/input_manager.h index ae65c11..e9f45a4 100644 --- a/lib/input_manager.h +++ b/lib/input_manager.h @@ -48,6 +48,32 @@ public: * @return InputEvent (valid=false if no valid input) */ InputEvent process_button_input(); + + /** + * @brief Get virtual button regions for drawing + * @param a_rect Pointer to rectangle for Button A [x, y, w, h] + * @param b_rect Pointer to rectangle for Button B [x, y, w, h] + */ + void get_virtual_button_regions(int* a_rect, int* b_rect) const; + + /** + * @brief Set virtual button regions + */ + void set_virtual_button_regions(int ax, int ay, int aw, int ah, int bx, int by, int bw, int bh); + + /** + * @brief Clear virtual button regions (disables detection) + */ + void clear_virtual_button_regions(); + + /** + * @brief Check if a touch event hits a virtual button + * @param x Touch X coordinate + * @param y Touch Y coordinate + * @param out_type Output parameter for the button type if hit + * @return true if a virtual button was hit + */ + bool check_virtual_buttons(int16_t x, int16_t y, InputType& out_type) const; /** * @brief Get human-readable gesture name @@ -71,6 +97,11 @@ public: private: LowLevelTouch* touch; const GameConfig* config; + + // Virtual button regions + int v_button_a[4] = {0, 0, 0, 0}; // [x, y, w, h] + int v_button_b[4] = {0, 0, 0, 0}; + bool v_buttons_active = false; }; #endif // INPUT_MANAGER_H