// ============================================================================ // MONOPOLY GAME IMPLEMENTATION (for custom console) // ============================================================================ // Refactored from console version to use Game interface and rendering/input system #include "monopoly_game.h" #ifdef __cplusplus extern "C" { #endif #include "player.h" #ifdef __cplusplus } #endif #include #include #include #include #include "DiceModalGame.h" #include "PropertyModalGame.h" #include "BoardModalGame.h" #include "ChanceModalGame.h" #include "CommunityChestModalGame.h" #include "TurnModalGame.h" #include "PaymentModalGame.h" #include "ModalButtonHelper.h" #include "sprites.h" #include "MonopolyBoardRenderer.h" // --- Constructor --- MonopolyGame::MonopolyGame(uint16_t width, uint16_t height, LowLevelRenderer* renderer, LowLevelGUI* gui, InputManager* input_manager) : Game(width, height, renderer, gui, input_manager) { players_count = 2; current_player_idx = 0; has_rolled = false; double_rolls = 0; just_sent_to_jail = false; } // --- 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"); players_count = 2; current_player_idx = 0; has_rolled = false; double_rolls = 0; just_sent_to_jail = false; selected_action = -1; srand(time(NULL)); shuffle_chance_deck(); shuffle_community_deck(); //renderer->set_font(&font_tama_mini02_obj); if (active_modal) { delete active_modal; active_modal = nullptr; } active_modal = new TurnModalGame(width, height, renderer, gui, input_manager, &players[current_player_idx]); // TODO: Reset all board state, property ownership, etc. } void MonopolyGame::shuffle_chance_deck() { for (int i = 0; i < CHANCE_DECK_SIZE; i++) { chance_deck[i] = i; } for (int i = CHANCE_DECK_SIZE - 1; i > 0; i--) { int j = rand() % (i + 1); int temp = chance_deck[i]; chance_deck[i] = chance_deck[j]; chance_deck[j] = temp; } current_chance_idx = 0; } void MonopolyGame::shuffle_community_deck() { for (int i = 0; i < COMMUNITY_DECK_SIZE; i++) { community_deck[i] = i; } for (int i = COMMUNITY_DECK_SIZE - 1; i > 0; i--) { int j = rand() % (i + 1); int temp = community_deck[i]; community_deck[i] = community_deck[j]; community_deck[j] = temp; } current_community_idx = 0; } // --- Handle input events (minimal: roll, buy, end turn) --- bool MonopolyGame::update(const InputEvent& event) { Player* p = &players[current_player_idx]; bool needs_redraw = false; // Calculate available actions int menu_count = 0; if (!has_rolled) { menu_count++; // Roll Dice if (p->is_in_jail) menu_count++; // Pay $50 } else { menu_count++; // End Turn } menu_count++; // View Board // 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; // 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; ChanceModalGame* chance_modal = (active_modal->get_type() == Game::Type::MONOPOLY_CHANCE) ? static_cast(active_modal) : nullptr; CommunityChestModalGame* community_modal = (active_modal->get_type() == Game::Type::MONOPOLY_COMMUNITY_CHEST) ? static_cast(active_modal) : nullptr; TurnModalGame* turn_modal = (active_modal->get_type() == Game::Type::MONOPOLY_TURN) ? static_cast(active_modal) : nullptr; PaymentModalGame* pay_modal = (active_modal->get_type() == Game::Type::MONOPOLY_PAYMENT) ? static_cast(active_modal) : nullptr; if (pay_modal && pay_modal->is_dismissed()) { delete active_modal; active_modal = nullptr; selected_action = -1; needs_redraw = true; ModalButtonHelper::set_monopoly_regions(input_manager, width, height); return needs_redraw; } if (dice_modal && dice_modal->is_dismissed()) { 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) { 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, current_player_idx); modal_property_index = -1; } else if (last_drawn_chance_idx >= 0) { active_modal = new ChanceModalGame(width, height, renderer, gui, input_manager, &CHANCE_DECK[last_drawn_chance_idx], players, players_count, p->position); // We'll apply the effect when ChanceModal is dismissed } else if (last_drawn_community_idx >= 0) { 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(); else selected_action = -1; 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; int old_pos = p->position; switch (card->type) { case CHANCE_EARN: p->balance += card->value; break; case CHANCE_SPEND: active_modal = new PaymentModalGame(width, height, renderer, gui, input_manager, p, nullptr, card->value, "CHANCE CARD"); if (active_modal) active_modal->init(); break; case CHANCE_ADVANCE: { int target = card->value; if (target == TARGET_NEAREST_UTILITY) { target = (p->position + 1) % BOARD_SIZE; while (MONOPOLY_BOARD[target].type != TILE_UTILITY) { target = (target + 1) % BOARD_SIZE; } force_utility_10x = true; } else if (target == TARGET_NEAREST_RAILROAD) { target = (p->position + 1) % BOARD_SIZE; while (MONOPOLY_BOARD[target].type != TILE_RAILROAD) { target = (target + 1) % BOARD_SIZE; } rent_multiplier = 2; } p->position = target; if (p->position < old_pos) p->balance += 200; position_changed = true; break; } case CHANCE_BACK: p->position = (p->position - card->value + BOARD_SIZE) % BOARD_SIZE; position_changed = true; break; case CHANCE_JAIL: p->position = 10; // Jail p->is_in_jail = true; break; case CHANCE_JAIL_FREE: p->jail_free_cards++; break; case CHANCE_SPEND_EACH_PLAYER: for (int i = 0; i < players_count; i++) { if (i != (int)current_player_idx) { p->balance -= card->value; players[i].balance += card->value; } } break; case CHANCE_REPAIRS: // For now, simplify or implement if houses are tracked break; } needs_redraw = true; ModalButtonHelper::set_monopoly_regions(input_manager, width, height); if (active_modal) { active_modal->init(); } else if (position_changed) { // If we moved, check if we landed on a property const BoardTile* landed = &MONOPOLY_BOARD[p->position]; if (landed->type == TILE_PROPERTY || landed->type == TILE_RAILROAD || landed->type == TILE_UTILITY) { 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] == p->position) { is_owned = true; owner_name = players[i].name; owner_id = i; break; } } } bool can_afford = (p->balance >= landed->cost); active_modal = new PropertyModalGame(width, height, renderer, gui, input_manager, landed, is_owned, owner_name, owner_id, can_afford, players, players_count, current_player_idx); if (active_modal) active_modal->init(); } } } else if (community_modal && community_modal->is_dismissed()) { const CommunityCard* card = &COMMUNITY_DECK[last_drawn_community_idx]; last_drawn_community_idx = -1; bool position_changed = false; int old_pos = p->position; switch (card->type) { case COMMUNITY_EARN: p->balance += card->value; break; case COMMUNITY_SPEND: active_modal = new PaymentModalGame(width, height, renderer, gui, input_manager, p, nullptr, card->value, "COMMUNITY CHEST"); if (active_modal) active_modal->init(); break; case COMMUNITY_ADVANCE: p->position = card->value; if (p->position < old_pos) p->balance += 200; position_changed = true; break; case COMMUNITY_JAIL: p->position = 10; p->is_in_jail = true; break; case COMMUNITY_JAIL_FREE: p->jail_free_cards++; break; case COMMUNITY_EARN_EACH_PLAYER: for (int i = 0; i < players_count; i++) { if (i != (int)current_player_idx) { p->balance += card->value; players[i].balance -= card->value; } } break; case COMMUNITY_REPAIRS: // p->balance -= (houses * card->value) + (hotels * card->value2); break; } needs_redraw = true; ModalButtonHelper::set_monopoly_regions(input_manager, width, height); if (active_modal) { active_modal->init(); } else if (position_changed) { const BoardTile* landed = &MONOPOLY_BOARD[p->position]; if (landed->type == TILE_PROPERTY || landed->type == TILE_RAILROAD || landed->type == TILE_UTILITY) { 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] == p->position) { is_owned = true; owner_name = players[i].name; owner_id = i; break; } } } bool can_afford = (p->balance >= landed->cost); active_modal = new PropertyModalGame(width, height, renderer, gui, input_manager, landed, is_owned, owner_name, owner_id, can_afford, players, players_count, current_player_idx); if (active_modal) active_modal->init(); } } } else if (prop_modal && prop_modal->is_dismissed()) { bool wants_buy = prop_modal->wants_to_buy(); bool wants_rent = prop_modal->wants_to_pay_rent(); int owner_id_from_modal = prop_modal->get_owner_id(); delete active_modal; active_modal = nullptr; if (wants_buy) { const BoardTile* tile = &MONOPOLY_BOARD[p->position]; if (p->balance >= tile->cost) { active_modal = new PaymentModalGame(width, height, renderer, gui, input_manager, p, nullptr, tile->cost, "BUY PROPERTY"); p->properties_owned[p->property_count++] = p->position; } } else if (wants_rent) { const BoardTile* tile = &MONOPOLY_BOARD[p->position]; int rent = 0; if (tile->type == TILE_PROPERTY) { rent = tile->rent[0]; } else if (tile->type == TILE_RAILROAD) { int rr_count = 0; if (owner_id_from_modal != -1) { for (int i = 0; i < players[owner_id_from_modal].property_count; ++i) { if (MONOPOLY_BOARD[players[owner_id_from_modal].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; } else if (tile->type == TILE_UTILITY) { int total_dice = last_dice1 + last_dice2; int utility_count = 0; if (owner_id_from_modal != -1) { for (int i = 0; i < players[owner_id_from_modal].property_count; ++i) { if (MONOPOLY_BOARD[players[owner_id_from_modal].properties_owned[i]].type == TILE_UTILITY) { utility_count++; } } } if (force_utility_10x) { rent = total_dice * 10; } else { rent = (utility_count == 2) ? (total_dice * 10) : (total_dice * 4); } } rent *= rent_multiplier; if (owner_id_from_modal != -1 && (int)current_player_idx != owner_id_from_modal) { active_modal = new PaymentModalGame(width, height, renderer, gui, input_manager, p, &players[owner_id_from_modal], rent, "RENT"); } } // Reset multipliers rent_multiplier = 1; force_utility_10x = false; needs_redraw = true; ModalButtonHelper::set_monopoly_regions(input_manager, width, height); if (active_modal) active_modal->init(); } 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); } if (active_modal == nullptr && needs_redraw) { selected_action = -1; } return needs_redraw; } switch (event.type) { case INPUT_BUTTON_0: selected_action = (selected_action + 1) % menu_count; needs_redraw = true; break; case INPUT_BUTTON_1: if (selected_action == -1) return false; if (p->is_in_jail && !has_rolled && selected_action == 1) { active_modal = new PaymentModalGame(width, height, renderer, gui, input_manager, p, nullptr, 50, "JAIL BAIL"); if (active_modal) active_modal->init(); p->is_in_jail = false; p->jail_turns = 0; selected_action = -1; needs_redraw = true; return true; } if (active_modal) delete active_modal; if (selected_action == (menu_count - 1)) { active_modal = new BoardModalGame(width, height, renderer, gui, input_manager, players, players_count, current_player_idx); needs_redraw = true; } else if (!has_rolled) { roll_dice_logic: int d1 = (rand() % 6) + 1; int d2 = (rand() % 6) + 1; bool is_db = (d1 == d2); int old_pos = p->position; if (p->is_in_jail) { if (is_db) { p->is_in_jail = false; p->jail_turns = 0; } else { p->jail_turns++; if (p->jail_turns >= 3) { active_modal = new PaymentModalGame(width, height, renderer, gui, input_manager, p, nullptr, 50, "JAIL BAIL (FORCED)"); if (active_modal) active_modal->init(); p->is_in_jail = false; p->jail_turns = 0; } else { has_rolled = true; last_dice1 = d1; last_dice2 = d2; active_modal = new DiceModalGame(width, height, renderer, gui, input_manager, d1, d2, &MONOPOLY_BOARD[old_pos], &MONOPOLY_BOARD[old_pos], players, players_count); needs_redraw = true; return true; } } } else if (is_db) { double_rolls++; if (double_rolls >= 3) { p->position = 10; p->is_in_jail = true; p->jail_turns = 0; has_rolled = true; double_rolls = 0; last_dice1 = d1; last_dice2 = d2; active_modal = new DiceModalGame(width, height, renderer, gui, input_manager, d1, d2, &MONOPOLY_BOARD[old_pos], &MONOPOLY_BOARD[10], players, players_count); needs_redraw = true; return true; } } else { double_rolls = 0; } int total = d1 + d2; p->position = (p->position + total) % BOARD_SIZE; if (p->position < old_pos) p->balance += 200; has_rolled = !is_db; last_dice1 = d1; last_dice2 = d2; active_modal = new DiceModalGame(width, height, renderer, gui, input_manager, d1, d2, &MONOPOLY_BOARD[old_pos], &MONOPOLY_BOARD[p->position], players, players_count); const BoardTile* lnd = &MONOPOLY_BOARD[p->position]; if (lnd->type == TILE_GO_TO_JAIL) { p->position = 10; p->is_in_jail = true; p->jail_turns = 0; has_rolled = true; double_rolls = 0; } else if (lnd->type == TILE_PROPERTY || lnd->type == TILE_RAILROAD || lnd->type == TILE_UTILITY) { modal_property_index = p->position; } else if (lnd->type == TILE_CHANCE) { last_drawn_chance_idx = chance_deck[current_chance_idx]; current_chance_idx = (current_chance_idx + 1) % CHANCE_DECK_SIZE; if (current_chance_idx == 0) shuffle_chance_deck(); } else if (lnd->type == TILE_COMMUNITY_CHEST) { last_drawn_community_idx = community_deck[current_community_idx]; current_community_idx = (current_community_idx + 1) % COMMUNITY_DECK_SIZE; if (current_community_idx == 0) shuffle_community_deck(); } else if (lnd->type == TILE_TAX) { active_modal = new PaymentModalGame(width, height, renderer, gui, input_manager, p, nullptr, lnd->cost, "TAXES"); if (active_modal) active_modal->init(); } needs_redraw = true; } else { current_player_idx = (current_player_idx + 1) % players_count; has_rolled = false; double_rolls = 0; just_sent_to_jail = false; selected_action = -1; active_modal = new TurnModalGame(width, height, renderer, gui, input_manager, &players[current_player_idx]); needs_redraw = true; } break; default: break; } 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; } 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, p->position); // --- Inner Dashboard (Center Area) --- int cw = width / 7; int ch = height / 7; int ix = cw + 2, iy = ch + 2; int iw = width - 2 * cw - 4, ih = height - 2 * ch - 4; // --- Inner Dashboard UI --- // Window Border renderer->draw_rectangle(ix, iy, iw, ih, true, 2); renderer->draw_rectangle(ix + 3, iy + 3, iw - 6, ih - 6, true, 1); // Header Title Bar (Player Name) char buf[128]; renderer->draw_filled_rectangle(ix + 4, iy + 4, iw - 8, 35, true, 1); renderer->set_text_color(false); // White text snprintf(buf, sizeof(buf), "%s'S TURN", p->name); renderer->draw_string_scaled(ix + (iw - (int)strlen(buf) * 12) / 2, iy + 12, buf, 2); renderer->set_text_color(true); renderer->draw_string_scaled(ix + (iw - 8 * 18) / 2, iy + ih - 35, "Monopoly", 3); int content_y = iy + 50; snprintf(buf, sizeof(buf), "BALANCE: $%d", p->balance); renderer->draw_string_scaled(ix + 10, content_y, buf, 2); content_y += 25; snprintf(buf, sizeof(buf), "TILE: %s", tile->name); renderer->draw_string_scaled(ix + 10, content_y, buf, 1); content_y += 20; // Draw special tile sprite const unsigned char* sprite = nullptr; if (tile->type == TILE_COMMUNITY_CHEST) sprite = epd_bitmap_CommunityChest; else if (tile->type == TILE_CHANCE) sprite = epd_bitmap_Chance; else if (tile->type == TILE_FREE_PARKING) sprite = epd_bitmap_FreeParking; else if (tile->type == TILE_GO_TO_JAIL) sprite = epd_bitmap_GoToJail; else if (tile->type == TILE_UTILITY) { if (strstr(tile->name, "Electric")) sprite = epd_bitmap_ElectricCompany; else if (strstr(tile->name, "Water")) sprite = epd_bitmap_WaterWorks; } if (sprite) { // Draw at bottom right of dashboard renderer->draw_bitmap(sprite, ix + iw - 105, iy + ih - 105, 100, 100, true); } // Separator line renderer->draw_line(ix + 10, content_y, ix + iw - 10, content_y, true); content_y += 15; // Draw action menu const char* actions[3]; int menu_count = 0; if (!has_rolled) { actions[menu_count++] = "Roll Dice"; if (p->is_in_jail) actions[menu_count++] = "Pay $50"; } else { actions[menu_count++] = "End Turn"; } actions[menu_count++] = "View Board"; for (int i = 0; i < menu_count; ++i) { snprintf(buf, sizeof(buf), "%s%s", (i == selected_action) ? "> " : " ", actions[i]); renderer->draw_string_scaled(ix + 15, content_y, buf, 2); content_y += 25; } ModalButtonHelper::draw_virtual_buttons(renderer, input_manager); }