diff --git a/AGENT.md b/AGENT.md index 127eb99..7403a06 100644 --- a/AGENT.md +++ b/AGENT.md @@ -303,9 +303,9 @@ After successful build: View serial output: -```bash screen /dev/cu.usbmodem101 # Exit: Ctrl-A, then K, then Y ``` Or use VS Code's serial monitor extension. + diff --git a/emulator/basic1_emulator b/emulator/basic1_emulator index 76f221d..089c710 100755 Binary files a/emulator/basic1_emulator and b/emulator/basic1_emulator differ diff --git a/games/monopoly/AGENT.md b/games/monopoly/AGENT.md new file mode 100644 index 0000000..b392c24 --- /dev/null +++ b/games/monopoly/AGENT.md @@ -0,0 +1,43 @@ +# 🎲 Monopoly Game Design & Operations + +This section caches the current architecture, conventions, and implementation standards for the Monopoly project. + +## 🚀 Architecture Overview + +The game follows a polymorphic architecture based on a `Game` interface. + +- **Game Controller**: `MonopolyGame` (Inherits from `Game`). +- **Modal System**: Modals (Dice, Property, Board) are also `Game` objects. When active, the main game delegates `update()` and `draw()` to `active_modal`. +- **System Constraints**: 1-bit Monochrome (ST7789/E-Ink). **RTTI IS DISABLED** (`-fno-rtti`). Use `active_modal->get_type()` for safe casting. + +## 🛠️ Design Patterns + +### 1. Cycle/Execute Input Pattern +To prevent accidental choices on simple 2-button hardware: +- **Button 0 (A)**: Cycles through options (increments selection). +- **Button 1 (B)**: Executes/Confirms current selection or dismisses modal. +- **Visual**: Selected items MUST be prefixed with `> `. + +### 2. Centralized Rendering +- **`MonopolyBoardRenderer`**: All shared board drawing logic (40-tile perimeter) MUST live here. +- **Tile Inversion**: Highlight tiles (current position or property view) using solid black fill + white text. +- **Legibility**: Use Scale 2 (`draw_string_scaled`) for player names and primary titles. Scale 1 for details. + +### 3. Logic Conventions +- **Rent Calculation**: + - **Properties**: `tile->rent[0]` (Base). + - **Railroads**: $25 / $50 / $100 / $200 based on owner count. + - **Utilities**: Dice Total * 4 (1 owned) or * 10 (2 owned). +- **Modal Chaining**: Dice Modal dismissal triggers `modal_property_index` check in `MonopolyGame::update` to immediately launch the Property Modal. + +## ➕ How to Implement New Features + +### Adding a New Tile +1. Update `TileType` and `MONOPOLY_BOARD` in [games/monopoly/monopoly_board.h](games/monopoly/monopoly_board.h). +2. Update `MonopolyBoardRenderer.h` if a new symbol or boundary is needed. + +### Adding a New Modal +1. Create `NewModalGame.h` inheriting from `Game`. +2. Register a new `Type` in [lib/game.h](lib/game.h) and override `get_type()`. +3. Use the **Cycle/Execute** pattern for input. +4. Add handling logic in `MonopolyGame::update` near where `DiceModalGame` or `PropertyModalGame` are handled. diff --git a/games/monopoly/BoardModalGame.h b/games/monopoly/BoardModalGame.h index bba11e1..9d23d68 100644 --- a/games/monopoly/BoardModalGame.h +++ b/games/monopoly/BoardModalGame.h @@ -18,6 +18,7 @@ public: : Game(width, height, renderer, gui, input_manager), dismissed(false), players(p), players_count(count) {} void init() override { dismissed = false; } + Type get_type() const override { return Type::MONOPOLY_BOARD; } bool update(const InputEvent& event) override { // Any button dismisses the board view @@ -31,7 +32,9 @@ public: void draw() override { renderer->clear_buffer(); - MonopolyBoardRenderer::draw_board_perimeter(renderer, width, height, players, players_count); + MonopolyBoardRenderer::draw_board_perimeter(renderer, width, height, players, players_count, players[0].position /* Just for example, or -1 */); + // We'll pass -1 for the general board view to keep it clean + MonopolyBoardRenderer::draw_board_perimeter(renderer, width, height, players, players_count, -1); // --- Inner UI --- int cw = width / 7; diff --git a/games/monopoly/DiceModalGame.h b/games/monopoly/DiceModalGame.h index 9bf0922..5152669 100644 --- a/games/monopoly/DiceModalGame.h +++ b/games/monopoly/DiceModalGame.h @@ -50,8 +50,10 @@ 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; } + Type get_type() const override { return Type::MONOPOLY_DICE; } bool update(const InputEvent& event) override { - if (event.type == INPUT_BUTTON_0 || event.type == INPUT_BUTTON_1) { + // Only B dismisses now, so A can still be used for "change action" (even if it does nothing here) + if (event.type == INPUT_BUTTON_1) { dismissed = true; return true; } @@ -60,7 +62,11 @@ public: void draw() override { renderer->clear_buffer(); - MonopolyBoardRenderer::draw_board_perimeter(renderer, width, height, players, players_count); + MonopolyBoardRenderer::draw_board_perimeter(renderer, width, height, players, players_count, (to_tile ? -1 /* or some index */ : -1)); // Keep it simple for now or pass relevant pos + // Let's pass the destination position to highlight it during the dice roll + int target_pos = -1; + for(int i=0; i<40; i++) if(&MONOPOLY_BOARD[i] == to_tile) target_pos = i; + MonopolyBoardRenderer::draw_board_perimeter(renderer, width, height, players, players_count, target_pos); // --- Inner UI (Center Area) --- int cw = width / 7; @@ -106,7 +112,7 @@ public: int btn_y = iy + ih - 35; renderer->draw_filled_rectangle(btn_x, btn_y, btn_w, btn_h, true, 1); renderer->set_text_color(false); - renderer->draw_string_scaled(btn_x + 5, btn_y + 5, ">A CONTINUE", 2); + renderer->draw_string_scaled(btn_x + 5, btn_y + 5, ">B CONTINUE", 2); renderer->set_text_color(true); } bool is_dismissed() const { return dismissed; } diff --git a/games/monopoly/MonopolyBoardRenderer.h b/games/monopoly/MonopolyBoardRenderer.h index 839ebc2..3d57ea2 100644 --- a/games/monopoly/MonopolyBoardRenderer.h +++ b/games/monopoly/MonopolyBoardRenderer.h @@ -6,10 +6,16 @@ class MonopolyBoardRenderer { public: - static void draw_tile(LowLevelRenderer* renderer, int x, int y, int w, int h, int index, bool is_corner, Player* players, int players_count, int orientation = 0) { + static void draw_tile(LowLevelRenderer* renderer, int x, int y, int w, int h, int index, bool is_corner, Player* players, int players_count, int orientation = 0, int currentPlayerPos = -1) { if (index < 0 || index >= BOARD_SIZE) return; - renderer->draw_rectangle(x, y, w, h, true, 1); + bool isInverted = (index == currentPlayerPos); + if (isInverted) { + renderer->draw_filled_rectangle(x, y, w, h, true, 1); + renderer->set_text_color(false); // Black text on white background + } else { + renderer->draw_rectangle(x, y, w, h, true, 1); + } const BoardTile& tile = MONOPOLY_BOARD[index]; int content_x = x, content_y = y, content_w = w, content_h = h; @@ -32,13 +38,21 @@ public: content_x += bar_size; content_w -= bar_size; } - renderer->draw_filled_rectangle(bx, by, bw, bh, true, 1); + if (isInverted) { + // In inverted mode, the bar should be black (transparent/inverted) + renderer->draw_filled_rectangle(bx, by, bw, bh, false, 0); // Clear the bar area back to black + renderer->set_text_color(true); // Groups on the bar are usually white text on black bar in this mode + } else { + renderer->draw_filled_rectangle(bx, by, bw, bh, true, 1); + renderer->set_text_color(false); // Black text on white bar + } // Group number - renderer->set_text_color(false); // Black text on white bar char gbuf[2] = { (char)('0' + tile.group[0]), '\0' }; renderer->draw_string_scaled(bx + (bw - 6) / 2, by + (bh - 8) / 2, gbuf, 1); - renderer->set_text_color(true); + + if (isInverted) renderer->set_text_color(false); // Back to black text for the rest of the tile + else renderer->set_text_color(true); } char short_name[5] = {0}; @@ -65,36 +79,40 @@ public: p_count++; } } + + if (isInverted) { + renderer->set_text_color(true); // Reset for next tiles + } } - static void draw_board_perimeter(LowLevelRenderer* renderer, int width, int height, Player* players, int players_count) { + static void draw_board_perimeter(LowLevelRenderer* renderer, int width, int height, Player* players, int players_count, int currentPlayerPos = -1) { int cw = width / 7; // Corner width int ch = height / 7; // Corner height int rw = (width - 2 * cw) / 9; // Regular tile width int rh = (height - 2 * ch) / 9; // Regular tile height // --- Bottom Row: 0 to 10 (Right to Left) --- - draw_tile(renderer, width - cw, height - ch, cw, ch, 0, true, players, players_count, 0); // GO + draw_tile(renderer, width - cw, height - ch, cw, ch, 0, true, players, players_count, 0, currentPlayerPos); // GO for (int i = 1; i < 10; ++i) { - draw_tile(renderer, width - cw - i * rw, height - ch, rw, ch, i, false, players, players_count, 0); + draw_tile(renderer, width - cw - i * rw, height - ch, rw, ch, i, false, players, players_count, 0, currentPlayerPos); } - draw_tile(renderer, 0, height - ch, cw, ch, 10, true, players, players_count, 1); // JAIL + draw_tile(renderer, 0, height - ch, cw, ch, 10, true, players, players_count, 1, currentPlayerPos); // JAIL // --- Left Column: 11 to 19 (Bottom to Top) --- for (int i = 11; i < 20; ++i) { - draw_tile(renderer, 0, height - ch - (i - 10) * rh, cw, rh, i, false, players, players_count, 1); + draw_tile(renderer, 0, height - ch - (i - 10) * rh, cw, rh, i, false, players, players_count, 1, currentPlayerPos); } // --- Top Row: 20 to 30 (Left to Right) --- - draw_tile(renderer, 0, 0, cw, ch, 20, true, players, players_count, 2); // FREE PARKING + draw_tile(renderer, 0, 0, cw, ch, 20, true, players, players_count, 2, currentPlayerPos); // FREE PARKING for (int i = 21; i < 30; ++i) { - draw_tile(renderer, cw + (i - 21) * rw, 0, rw, ch, i, false, players, players_count, 2); + draw_tile(renderer, cw + (i - 21) * rw, 0, rw, ch, i, false, players, players_count, 2, currentPlayerPos); } - draw_tile(renderer, width - cw, 0, cw, ch, 30, true, players, players_count, 3); // GO TO JAIL + draw_tile(renderer, width - cw, 0, cw, ch, 30, true, players, players_count, 3, currentPlayerPos); // GO TO JAIL // --- Right Column: 31 to 39 (Top to Bottom) --- for (int i = 31; i < 40; ++i) { - draw_tile(renderer, width - cw, ch + (i - 31) * rh, cw, rh, i, false, players, players_count, 3); + draw_tile(renderer, width - cw, ch + (i - 31) * rh, cw, rh, i, false, players, players_count, 3, currentPlayerPos); } } }; diff --git a/games/monopoly/PropertyModalGame.h b/games/monopoly/PropertyModalGame.h index 04ca6cf..c58ecdf 100644 --- a/games/monopoly/PropertyModalGame.h +++ b/games/monopoly/PropertyModalGame.h @@ -13,32 +13,49 @@ class PropertyModalGame : public Game { bool dismissed; bool is_owned; const char* owner_name; + int owner_id; // -1 if not owned bool can_afford; int selected_choice; // 0: Buy, 1: Cancel bool buy_requested; + bool rent_requested; Player* players; int players_count; public: - PropertyModalGame(uint16_t width, uint16_t height, LowLevelRenderer* renderer, LowLevelGUI* gui, InputManager* input_manager, const BoardTile* prop, bool owned, const char* owner, bool affordable, Player* p_list = nullptr, int p_count = 0) - : Game(width, height, renderer, gui, input_manager), property(prop), dismissed(false), is_owned(owned), owner_name(owner), can_afford(affordable), selected_choice(0), buy_requested(false), players(p_list), players_count(p_count) { + PropertyModalGame(uint16_t width, uint16_t height, LowLevelRenderer* renderer, LowLevelGUI* gui, InputManager* input_manager, const BoardTile* prop, bool owned, const char* owner, int o_id, bool affordable, Player* p_list = nullptr, int p_count = 0) + : 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) { if (is_owned || !can_afford) selected_choice = 1; } - void init() override { dismissed = false; buy_requested = false; } + void init() override { dismissed = false; buy_requested = false; rent_requested = false; selected_choice = 0; } + Type get_type() const override { return Type::MONOPOLY_PROPERTY; } bool update(const InputEvent& event) override { - if (event.type == INPUT_BUTTON_0) { // BUTTON A -> BUY - if (!is_owned && can_afford) { - buy_requested = true; + // If rent is owed, any button pays it (it's forced) + if (is_owned && owner_id != -1) { + if (event.type == INPUT_BUTTON_0 || event.type == INPUT_BUTTON_1) { + rent_requested = true; dismissed = true; return true; - } else if (is_owned || !can_afford) { - // If it's just the "OK" state, either button works? - // Image shows >B CONTINUE in my code, so maybe Button 1. + } + return false; + } + + // Otherwise (Buy/Pass), use A to cycle and B to select + if (event.type == INPUT_BUTTON_0) { // BUTTON A -> Change selection + if (!is_owned && can_afford) { + selected_choice = (selected_choice + 1) % 2; + return true; } } - if (event.type == INPUT_BUTTON_1) { // BUTTON B -> AUCTION / DISMISS - dismissed = true; - return true; + if (event.type == INPUT_BUTTON_1) { // BUTTON B -> Select action + if (!is_owned && can_afford) { + if (selected_choice == 0) buy_requested = true; + dismissed = true; + return true; + } else { + // Only option was Pass + dismissed = true; + return true; + } } return false; } @@ -51,7 +68,12 @@ public: char buf[128]; if (players && players_count > 0) { - MonopolyBoardRenderer::draw_board_perimeter(renderer, width, height, players, players_count); + // Find current player position (who is interacting with the modal) + // In MonopolyGame, p is players[current_player_idx] + // We don't have the idx here directly, but we can highlight the property itself + int property_idx = -1; + for(int i=0; i<40; i++) if(&MONOPOLY_BOARD[i] == property) property_idx = i; + MonopolyBoardRenderer::draw_board_perimeter(renderer, width, height, players, players_count, property_idx); } // Window background (White box) @@ -104,26 +126,70 @@ public: int btn_w = win_w - 30; int btn_h = 25; - if (is_owned || !can_afford) { - // Only one option: CONTINUE (B) + if (is_owned && owner_id != -1) { + // Option: Pay Rent (A or B) renderer->draw_filled_rectangle(win_x + 15, btn_y, btn_w, btn_h, true, 1); renderer->set_text_color(false); - renderer->draw_string_scaled(win_x + 25, btn_y + 8, ">B CONTINUE", 1); + int rent = 0; + if (property->type == TILE_PROPERTY) { + rent = property->rent[0]; + } else if (property->type == TILE_RAILROAD) { + // Calculate Railroad rent based on owner's count + int rr_count = 0; + if (owner_id != -1 && owner_id < players_count) { + for (int i = 0; i < players[owner_id].property_count; ++i) { + // Find the property in the global board to check type (or just trust the index) + int prop_idx = players[owner_id].properties_owned[i]; + if (MONOPOLY_BOARD[prop_idx].type == TILE_RAILROAD) { + rr_count++; + } + } + } + if (rr_count == 1) rent = 25; + else if (rr_count == 2) rent = 50; + else if (rr_count == 3) rent = 100; + else if (rr_count == 4) rent = 200; + else rent = 25; // Fallback + } else if (property->type == TILE_UTILITY) { + // Simplified 40 or use a more complex check + rent = 40; + } + + snprintf(buf, sizeof(buf), ">PAY RENT ($%d)", rent); + renderer->draw_string_scaled(win_x + (win_w - (int)strlen(buf) * 6) / 2, btn_y + 8, buf, 1); renderer->set_text_color(true); - } else { - // Choice: Buy (A) or Auction (B) + } else if (!is_owned && can_afford) { + // Choice: Buy (A) or Pass (B). Changed to: A cycles, B selects. // Buy Button - renderer->draw_filled_rectangle(win_x + 15, btn_y, btn_w, btn_h, true, 1); - renderer->set_text_color(false); - snprintf(buf, sizeof(buf), ">A BUY ($%d)", property->cost); + if (selected_choice == 0) renderer->draw_filled_rectangle(win_x + 15, btn_y, btn_w, btn_h, true, 1); + else renderer->draw_rectangle(win_x + 15, btn_y, btn_w, btn_h, true, 1); + + if (selected_choice == 0) renderer->set_text_color(false); + snprintf(buf, sizeof(buf), "%sBUY ($%d)", (selected_choice == 0 ? "> " : " "), property->cost); renderer->draw_string_scaled(win_x + 20, btn_y + 8, buf, 1); renderer->set_text_color(true); btn_y += 30; - // Auction Button - renderer->draw_string_scaled(win_x + 20, btn_y + 8, ">B AUCTION", 1); + // Pass Button + if (selected_choice == 1) renderer->draw_filled_rectangle(win_x + 15, btn_y, btn_w, btn_h, true, 1); + + if (selected_choice == 1) renderer->set_text_color(false); + snprintf(buf, sizeof(buf), "%sPASS", (selected_choice == 1 ? "> " : " ")); + renderer->draw_string_scaled(win_x + 20, btn_y + 8, buf, 1); + renderer->set_text_color(true); + + // Helpful hint + renderer->draw_string_scaled(win_x + 15, win_y + win_h - 15, "A:Next B:Sel", 1); + } else { + // Only one option: PASS (B) (e.g. if owned by self or can't afford) + renderer->draw_filled_rectangle(win_x + 15, btn_y, btn_w, btn_h, true, 1); + renderer->set_text_color(false); + renderer->draw_string_scaled(win_x + 25, btn_y + 8, ">B PASS", 1); + renderer->set_text_color(true); } } bool is_dismissed() const { return dismissed; } bool wants_to_buy() const { return buy_requested; } + bool wants_to_pay_rent() const { return rent_requested; } + int get_owner_id() const { return owner_id; } }; diff --git a/games/monopoly/monopoly_game.cpp b/games/monopoly/monopoly_game.cpp index 01ba5e5..34e40ac 100644 --- a/games/monopoly/monopoly_game.cpp +++ b/games/monopoly/monopoly_game.cpp @@ -59,20 +59,92 @@ bool MonopolyGame::update(const InputEvent& event) { bool modal_redraw = active_modal->update(event); if (modal_redraw) needs_redraw = true; - // Check for specific modal types to handle their results - auto dice_modal = dynamic_cast(active_modal); - auto prop_modal = dynamic_cast(active_modal); - auto board_modal = dynamic_cast(active_modal); + // Check for specific modal types to handle their results using custom get_type() + DiceModalGame* dice_modal = (active_modal->get_type() == Game::Type::MONOPOLY_DICE) ? static_cast(active_modal) : nullptr; + PropertyModalGame* prop_modal = (active_modal->get_type() == Game::Type::MONOPOLY_PROPERTY) ? static_cast(active_modal) : nullptr; + BoardModalGame* board_modal = (active_modal->get_type() == Game::Type::MONOPOLY_BOARD) ? static_cast(active_modal) : nullptr; if (dice_modal && dice_modal->is_dismissed()) { delete active_modal; active_modal = nullptr; needs_redraw = true; - } else if (prop_modal && prop_modal->is_dismissed()) { + + // Immediately check if we need to show a property modal + if (modal_property_index >= 0) { + bool is_owned = false; + const char* owner_name = nullptr; + int owner_id = -1; + for (int i = 0; i < players_count; ++i) { + for (int j = 0; j < players[i].property_count; ++j) { + if (players[i].properties_owned[j] == modal_property_index) { + is_owned = true; + owner_name = players[i].name; + owner_id = i; + break; + } + } + if (is_owned) break; + } + bool can_afford = (p->balance >= MONOPOLY_BOARD[modal_property_index].cost); + active_modal = new PropertyModalGame(width, height, renderer, gui, input_manager, &MONOPOLY_BOARD[modal_property_index], is_owned, owner_name, owner_id, can_afford, players, players_count); + modal_property_index = -1; + } + return needs_redraw; + } else if (prop_modal && prop_modal->is_dismissed()) { if (prop_modal->wants_to_buy()) { const BoardTile* tile = &MONOPOLY_BOARD[p->position]; - p->balance -= tile->cost; - p->properties_owned[p->property_count++] = p->position; + if (p->balance >= tile->cost) { + p->balance -= tile->cost; + p->properties_owned[p->property_count++] = p->position; + } + } else if (prop_modal->wants_to_pay_rent()) { + const BoardTile* tile = &MONOPOLY_BOARD[p->position]; + int rent = 0; + if (tile->type == TILE_PROPERTY) { + // Logic for rent: If owner has all properties of group, rent is doubled (base only) + // For now, let's just use rent[0] as requested, but we should probably eventually + // check for houses. Let's stick to rent[0] to match the modal's display. + rent = tile->rent[0]; + } + else if (tile->type == TILE_RAILROAD) { + // Utility logic for Railroads: 1:25, 2:50, 3:100, 4:200 + int owner_id = prop_modal->get_owner_id(); + int rr_count = 0; + if (owner_id != -1) { + for (int i = 0; i < players[owner_id].property_count; ++i) { + if (MONOPOLY_BOARD[players[owner_id].properties_owned[i]].type == TILE_RAILROAD) { + rr_count++; + } + } + } + if (rr_count == 1) rent = 25; + else if (rr_count == 2) rent = 50; + else if (rr_count == 3) rent = 100; + else if (rr_count == 4) rent = 200; + else rent = 25; // Fallback + } + else if (tile->type == TILE_UTILITY) { + // Utility: 4x dice if 1 owned, 10x if both. + // Since we don't have the dice roll here, let's use a fixed 40 for now + // or calculate it from last_dice1 + last_dice2. + int total_dice = last_dice1 + last_dice2; + int owner_id = prop_modal->get_owner_id(); + int utility_count = 0; + if (owner_id != -1) { + for (int i = 0; i < players[owner_id].property_count; ++i) { + if (MONOPOLY_BOARD[players[owner_id].properties_owned[i]].type == TILE_UTILITY) { + utility_count++; + } + } + } + rent = (utility_count == 2) ? (total_dice * 10) : (total_dice * 4); + } + + int o_id = prop_modal->get_owner_id(); + if (o_id != -1 && (int)current_player_idx != o_id) { + p->balance -= rent; + players[o_id].balance += rent; + } } delete active_modal; active_modal = nullptr; @@ -135,27 +207,6 @@ 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) { - // Evaluate ownership and affordability - bool is_owned = false; - const char* owner_name = nullptr; - for (int i = 0; i < players_count; ++i) { - for (int j = 0; j < players[i].property_count; ++j) { - if (players[i].properties_owned[j] == modal_property_index) { - is_owned = true; - owner_name = players[i].name; - break; - } - } - if (is_owned) break; - } - bool can_afford = (p->balance >= MONOPOLY_BOARD[modal_property_index].cost); - - active_modal = new PropertyModalGame(width, height, renderer, gui, input_manager, &MONOPOLY_BOARD[modal_property_index], is_owned, owner_name, can_afford, players, players_count); - modal_property_index = -1; - needs_redraw = true; - } return needs_redraw; } @@ -169,8 +220,11 @@ void MonopolyGame::draw() { renderer->clear_buffer(); + Player* p = &players[current_player_idx]; + const BoardTile* tile = &MONOPOLY_BOARD[p->position]; + // --- Draw Board Perimeter --- - MonopolyBoardRenderer::draw_board_perimeter(renderer, width, height, players, players_count); + MonopolyBoardRenderer::draw_board_perimeter(renderer, width, height, players, players_count, p->position); // --- Inner Dashboard (Center Area) --- int cw = width / 7; @@ -178,17 +232,14 @@ void MonopolyGame::draw() { int ix = cw + 2, iy = ch + 2; int iw = width - 2 * cw - 4, ih = height - 2 * ch - 4; - Player* p = &players[current_player_idx]; - const BoardTile* tile = &MONOPOLY_BOARD[p->position]; - // Stats Window in center char buf[128]; renderer->draw_string_scaled(ix + 5, iy + 5, "Monopoly", 2); int content_y = iy + 25; - snprintf(buf, sizeof(buf), "TURN: %s", p->name); - renderer->draw_string_scaled(ix + 5, content_y, buf, 1); - content_y += 12; + snprintf(buf, sizeof(buf), "%s", p->name); + renderer->draw_string_scaled(ix + 5, content_y, buf, 2); + content_y += 20; snprintf(buf, sizeof(buf), "BAL: $%d", p->balance); renderer->draw_string_scaled(ix + 5, content_y, buf, 1); diff --git a/lib/game.h b/lib/game.h index 75884e5..27d179c 100644 --- a/lib/game.h +++ b/lib/game.h @@ -28,6 +28,13 @@ class InputManager; */ class Game { public: + enum class Type { + BASE, + MONOPOLY_DICE, + MONOPOLY_PROPERTY, + MONOPOLY_BOARD + }; + /** * @brief Construct a new Game * @param width Display width in pixels @@ -83,6 +90,11 @@ public: */ virtual bool wants_to_exit() const { return false; } + /** + * @brief Get the type of game for safe downcasting without RTTI + */ + virtual Type get_type() const { return Type::BASE; } + protected: // Display dimensions uint16_t width;