diff --git a/emulator/basic1_emulator b/emulator/basic1_emulator index 089c710..b168b16 100755 Binary files a/emulator/basic1_emulator and b/emulator/basic1_emulator differ diff --git a/games/monopoly/monopoly_game.cpp b/games/monopoly/monopoly_game.cpp index 34e40ac..97a3d43 100644 --- a/games/monopoly/monopoly_game.cpp +++ b/games/monopoly/monopoly_game.cpp @@ -20,6 +20,8 @@ extern "C" { #include "DiceModalGame.h" #include "PropertyModalGame.h" #include "BoardModalGame.h" +#include "ChanceModalGame.h" +#include "CommunityChestModalGame.h" #include "MonopolyBoardRenderer.h" @@ -45,15 +47,53 @@ void MonopolyGame::init() { just_sent_to_jail = false; selected_action = 0; srand(time(NULL)); + shuffle_chance_deck(); + shuffle_community_deck(); if (active_modal) { delete active_modal; active_modal = nullptr; } // 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); @@ -63,7 +103,9 @@ bool MonopolyGame::update(const InputEvent& event) { 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; + if (dice_modal && dice_modal->is_dismissed()) { delete active_modal; active_modal = nullptr; @@ -88,9 +130,164 @@ bool MonopolyGame::update(const InputEvent& event) { 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; + } 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 } return needs_redraw; - } else if (prop_modal && prop_modal->is_dismissed()) { + } else if (chance_modal && chance_modal->is_dismissed()) { + const ChanceCard* card = &CHANCE_DECK[last_drawn_chance_idx]; + last_drawn_chance_idx = -1; + + // 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: + p->balance -= card->value; + 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; + } + + delete active_modal; + active_modal = nullptr; + needs_redraw = true; + + 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); + } + } + return needs_redraw; + } 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: + p->balance -= card->value; + 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; + } + + delete active_modal; + active_modal = nullptr; + needs_redraw = true; + + 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); + } + } + return needs_redraw; + } else if (prop_modal && prop_modal->is_dismissed()) { if (prop_modal->wants_to_buy()) { const BoardTile* tile = &MONOPOLY_BOARD[p->position]; if (p->balance >= tile->cost) { @@ -137,15 +334,25 @@ bool MonopolyGame::update(const InputEvent& event) { } } } - rent = (utility_count == 2) ? (total_dice * 10) : (total_dice * 4); + if (force_utility_10x) { + rent = total_dice * 10; + } else { + rent = (utility_count == 2) ? (total_dice * 10) : (total_dice * 4); + } } + rent *= rent_multiplier; + 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; } } + // Reset multipliers + rent_multiplier = 1; + force_utility_10x = false; + delete active_modal; active_modal = nullptr; needs_redraw = true; @@ -158,54 +365,90 @@ bool MonopolyGame::update(const InputEvent& event) { } switch (event.type) { - case INPUT_BUTTON_0: // Cycle options - selected_action = (selected_action + 1) % ACTION_COUNT; + case INPUT_BUTTON_0: + selected_action = (selected_action + 1) % menu_count; needs_redraw = true; break; - case INPUT_BUTTON_1: // Select option - switch (selected_action) { - case 0: // Context Action - if (!has_rolled) { - // Roll Dice - if (!p->is_in_jail) { - int dice1 = (rand() % 6) + 1; - int dice2 = (rand() % 6) + 1; - int total = dice1 + dice2; - int old_pos = p->position; - p->position = (p->position + total) % BOARD_SIZE; - 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, &MONOPOLY_BOARD[old_pos], &MONOPOLY_BOARD[p->position], players, players_count); - // 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) { - modal_property_index = p->position; - } - } + case INPUT_BUTTON_1: + if (p->is_in_jail && !has_rolled && selected_action == 1) { + p->balance -= 50; + p->is_in_jail = false; + p->jail_turns = 0; + selected_action = 0; + 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); + 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 { - // End Turn - current_player_idx = (current_player_idx + 1) % players_count; - has_rolled = false; - double_rolls = 0; - just_sent_to_jail = false; - selected_action = 0; // Reset selection for next player - needs_redraw = true; + p->jail_turns++; + if (p->jail_turns >= 3) { + p->balance -= 50; 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; + } } - break; - case 1: // View Board - if (active_modal) delete active_modal; - active_modal = new BoardModalGame(width, height, renderer, gui, input_manager, players, players_count); - needs_redraw = true; - break; + } 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) { + p->balance -= lnd->cost; + } + 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 = 0; + needs_redraw = true; } break; - default: - break; + default: break; } return needs_redraw; } @@ -234,33 +477,34 @@ void MonopolyGame::draw() { // Stats Window in center char buf[128]; - renderer->draw_string_scaled(ix + 5, iy + 5, "Monopoly", 2); + renderer->draw_string_scaled(ix + 70, iy + 180, "Monopoly", 3); - int content_y = iy + 25; + int content_y = iy + 0; snprintf(buf, sizeof(buf), "%s", p->name); - renderer->draw_string_scaled(ix + 5, content_y, buf, 2); - content_y += 20; + renderer->draw_string_scaled(ix + 5, content_y, buf, 3); + content_y += 40; snprintf(buf, sizeof(buf), "BAL: $%d", p->balance); - renderer->draw_string_scaled(ix + 5, content_y, buf, 1); - content_y += 12; + renderer->draw_string_scaled(ix + 5, content_y, buf, 2); + content_y += 20; snprintf(buf, sizeof(buf), "POS: %s", tile->name); - renderer->draw_string_scaled(ix + 5, content_y, buf, 1); - content_y += 15; - + renderer->draw_string_scaled(ix + 5, content_y, buf, 2); + content_y += 20; // Draw action menu - const char* actions[ACTION_COUNT]; + const char* actions[3]; + int menu_count = 0; if (!has_rolled) { - actions[0] = "Roll Dice"; + actions[menu_count++] = "Roll Dice"; + if (p->is_in_jail) actions[menu_count++] = "Pay $50"; } else { - actions[0] = "End Turn"; + actions[menu_count++] = "End Turn"; } - actions[1] = "View Board"; + actions[menu_count++] = "View Board"; - for (int i = 0; i < ACTION_COUNT; ++i) { + for (int i = 0; i < menu_count; ++i) { snprintf(buf, sizeof(buf), "%s%s", (i == selected_action) ? "> " : " ", actions[i]); - renderer->draw_string_scaled(ix + 5, content_y, buf, 1); - content_y += 12; + renderer->draw_string_scaled(ix + 5, content_y, buf, 2); + content_y += 20; } } diff --git a/games/monopoly/monopoly_game.h b/games/monopoly/monopoly_game.h index 0b87773..fe58a6e 100644 --- a/games/monopoly/monopoly_game.h +++ b/games/monopoly/monopoly_game.h @@ -37,14 +37,25 @@ private: bool just_sent_to_jail; // UI selection state - int selected_action; // 0: Context action (Roll or End Turn), 1: View Board - static constexpr int ACTION_COUNT = 2; + int selected_action; // 0: Context action, 1: Pay $50 (Jail), 2: View Board + int current_action_count = 2; // Modal games Game* active_modal = nullptr; int last_dice1 = 0; int last_dice2 = 0; int modal_property_index = -1; + int chance_deck[CHANCE_DECK_SIZE]; + int current_chance_idx; + int last_drawn_chance_idx = -1; + int community_deck[COMMUNITY_DECK_SIZE]; + int current_community_idx; + int last_drawn_community_idx = -1; + int rent_multiplier = 1; + bool force_utility_10x = false; + + void shuffle_chance_deck(); + void shuffle_community_deck(); }; #endif // MONOPOLY_GAME_H diff --git a/lib/game.h b/lib/game.h index 27d179c..c17b694 100644 --- a/lib/game.h +++ b/lib/game.h @@ -32,7 +32,9 @@ public: BASE, MONOPOLY_DICE, MONOPOLY_PROPERTY, - MONOPOLY_BOARD + MONOPOLY_BOARD, + MONOPOLY_CHANCE, + MONOPOLY_COMMUNITY_CHEST }; /**