diff --git a/games/monopoly/DiceModalGame.h b/games/monopoly/DiceModalGame.h index 984a4d7..e4661f0 100644 --- a/games/monopoly/DiceModalGame.h +++ b/games/monopoly/DiceModalGame.h @@ -1,198 +1,222 @@ // DiceModalGame.h #pragma once -#include "../../lib/game.h" -#include "../../display/low_level_render.h" #include "../../display/low_level_gui.h" -#include "input_manager.h" -#include "MonopolyBoardRenderer.h" +#include "../../display/low_level_render.h" +#include "../../lib/game.h" #include "ModalButtonHelper.h" -#include +#include "MonopolyBoardRenderer.h" +#include "input_manager.h" #include +#include class DiceModalGame : public Game { - int dice1, dice2; - const BoardTile *from_tile, *to_tile; - Player* players; - int players_count; - bool dismissed; + int dice1, dice2; + const BoardTile *from_tile, *to_tile; + Player *players; + int players_count; + bool dismissed; - int from_pos; - int correct_destination; - int options[3]; - bool option_visible[3]; - int selected_choice; - bool show_error; + int from_pos; + int correct_destination; + int options[3]; + bool option_visible[3]; + int selected_choice; + bool show_error; - void draw_die(int x, int y, int size, int value) { - // Die base - renderer->draw_rounded_rectangle(x, y, size, size, 4, true, false); - // Shadow (offset 2,2) - renderer->draw_line(x + size, y + 2, x + size, y + size, true); - renderer->draw_line(x + 2, y + size, x + size, y + size, true); + void draw_die(int x, int y, int size, int value) { + // Die base + renderer->draw_rounded_rectangle(x, y, size, size, 4, true, false); + // Shadow (offset 2,2) + renderer->draw_line(x + size, y + 2, x + size, y + size, true); + renderer->draw_line(x + 2, y + size, x + size, y + size, true); - int dot_size = size / 6; - int m = size / 2; - int l = size / 4; - int r = 3 * size / 4; - int t = size / 4; - int b = 3 * size / 4; + int dot_size = size / 6; + int m = size / 2; + int l = size / 4; + int r = 3 * size / 4; + int t = size / 4; + int b = 3 * size / 4; - auto draw_dot = [&](int dx, int dy) { - renderer->draw_filled_rectangle(x + dx - dot_size / 2, y + dy - dot_size / 2, dot_size, dot_size, true, 1); - }; + auto draw_dot = [&](int dx, int dy) { + renderer->draw_filled_rectangle(x + dx - dot_size / 2, + y + dy - dot_size / 2, dot_size, dot_size, + true, 1); + }; - if (value % 2 == 1) draw_dot(m, m); // Center dot for 1, 3, 5 - if (value > 1) { - draw_dot(l, t); - draw_dot(r, b); - } - if (value > 3) { - draw_dot(r, t); - draw_dot(l, b); - } - if (value == 6) { - draw_dot(l, m); - draw_dot(r, m); - } + if (value % 2 == 1) + draw_dot(m, m); // Center dot for 1, 3, 5 + if (value > 1) { + draw_dot(l, t); + draw_dot(r, b); } + if (value > 3) { + draw_dot(r, t); + draw_dot(l, b); + } + if (value == 6) { + draw_dot(l, m); + draw_dot(r, m); + } + } 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) { - - // Find from_pos - from_pos = 0; - for(int i=0; i<40; i++) { - if(&MONOPOLY_BOARD[i] == from_tile) { - from_pos = i; - break; - } - } - correct_destination = (from_pos + dice1 + dice2) % BOARD_SIZE; - selected_choice = -1; - show_error = false; - for(int i=0; i<3; i++) option_visible[i] = true; + 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) { - // Generate fake options - int fake1 = (from_pos + (rand() % 11 + 2)) % BOARD_SIZE; - if (fake1 == correct_destination) fake1 = (fake1 + 1) % BOARD_SIZE; - - int fake2 = (from_pos + (rand() % 11 + 2)) % BOARD_SIZE; - while (fake2 == correct_destination || fake2 == fake1) { - fake2 = (from_pos + (rand() % 11 + 2)) % BOARD_SIZE; - } + // Find from_pos + from_pos = 0; + for (int i = 0; i < 40; i++) { + if (&MONOPOLY_BOARD[i] == from_tile) { + from_pos = i; + break; + } + } + correct_destination = (from_pos + dice1 + dice2) % MONOPOLY_BOARD_SIZE; + selected_choice = -1; + show_error = false; + for (int i = 0; i < 3; i++) + option_visible[i] = true; - int rand_pos = rand() % 3; - if (rand_pos == 0) { - options[0] = correct_destination; - options[1] = fake1; - options[2] = fake2; - } else if (rand_pos == 1) { - options[0] = fake1; - options[1] = correct_destination; - options[2] = fake2; - } else { - options[0] = fake1; - options[1] = fake2; - options[2] = correct_destination; - } + // Generate fake options + int fake1 = (from_pos + (rand() % 11 + 2)) % MONOPOLY_BOARD_SIZE; + if (fake1 == correct_destination) + fake1 = (fake1 + 1) % MONOPOLY_BOARD_SIZE; + + int fake2 = (from_pos + (rand() % 11 + 2)) % MONOPOLY_BOARD_SIZE; + while (fake2 == correct_destination || fake2 == fake1) { + fake2 = (from_pos + (rand() % 11 + 2)) % MONOPOLY_BOARD_SIZE; } - void init() override { - dismissed = false; - selected_choice = -1; - show_error = false; - for(int i=0; i<3; i++) option_visible[i] = true; - ModalButtonHelper::set_monopoly_regions(input_manager, width, height); + int rand_pos = rand() % 3; + if (rand_pos == 0) { + options[0] = correct_destination; + options[1] = fake1; + options[2] = fake2; + } else if (rand_pos == 1) { + options[0] = fake1; + options[1] = correct_destination; + options[2] = fake2; + } else { + options[0] = fake1; + options[1] = fake2; + options[2] = correct_destination; } - Type get_type() const override { return Type::MONOPOLY_DICE; } + } - bool update(const InputEvent& event) override { - if (event.type == INPUT_BUTTON_0) { // Select - int start_choice = selected_choice; - do { - selected_choice = (selected_choice + 1) % 3; - } while (!option_visible[selected_choice] && selected_choice != start_choice); - - show_error = false; - return true; - } + void init() override { + dismissed = false; + selected_choice = -1; + show_error = false; + for (int i = 0; i < 3; i++) + option_visible[i] = true; + ModalButtonHelper::set_monopoly_regions(input_manager, width, height); + } + Type get_type() const override { return Type::MONOPOLY_DICE; } - if (event.type == INPUT_BUTTON_1) { - if (selected_choice == -1 || !option_visible[selected_choice]) return false; + bool update(const InputEvent &event) override { + if (event.type == INPUT_BUTTON_0) { // Select + int start_choice = selected_choice; + do { + selected_choice = (selected_choice + 1) % 3; + } while (!option_visible[selected_choice] && + selected_choice != start_choice); - if (options[selected_choice] == correct_destination) { - dismissed = true; - } else { - option_visible[selected_choice] = false; - show_error = true; - selected_choice = -1; - } - return true; - } + show_error = false; + return true; + } + + if (event.type == INPUT_BUTTON_1) { + if (selected_choice == -1 || !option_visible[selected_choice]) return false; + + if (options[selected_choice] == correct_destination) { + dismissed = true; + } else { + option_visible[selected_choice] = false; + show_error = true; + selected_choice = -1; + } + return true; } + return false; + } - void draw() override { - renderer->clear_buffer(); - - // Draw the restricted board perimeter - MonopolyBoardRenderer::draw_board_perimeter(renderer, width, height, players, players_count, -1, -1, from_pos, (from_pos + 12) % BOARD_SIZE); + void draw() override { + renderer->clear_buffer(); - // --- Inner UI (Center Area) --- - int cw = width / 7; - int ch = height / 7; - int ix = cw + 2; - int iy = ch + 2; - int iw = width - 2 * cw - 4; - int ih = height - 2 * ch - 4; - - // Window background (White box) - renderer->draw_filled_rectangle(ix, iy, iw, ih, false, 0); - renderer->draw_rectangle(ix, iy, iw, ih, true, 2); - renderer->draw_rectangle(ix + 3, iy + 3, iw - 6, ih - 6, true, 1); - - // Header - renderer->draw_filled_rectangle(ix + 4, iy + 4, iw - 8, 30, true, 1); + // Draw the restricted board perimeter + MonopolyBoardRenderer::draw_board_perimeter( + renderer, width, height, players, players_count, -1, -1, from_pos, + (from_pos + 12) % MONOPOLY_BOARD_SIZE); + + // --- Inner UI (Center Area) --- + int cw = width / 7; + int ch = height / 7; + int ix = cw + 2; + int iy = ch + 2; + int iw = width - 2 * cw - 4; + int ih = height - 2 * ch - 4; + + // Window background (White box) + renderer->draw_filled_rectangle(ix, iy, iw, ih, false, 0); + renderer->draw_rectangle(ix, iy, iw, ih, true, 2); + renderer->draw_rectangle(ix + 3, iy + 3, iw - 6, ih - 6, true, 1); + + // Header + renderer->draw_filled_rectangle(ix + 4, iy + 4, iw - 8, 30, true, 1); + renderer->set_text_color(false); + renderer->draw_string_scaled( + ix + (iw - (int)strlen("DICE CHALLENGE") * 12) / 2, iy + 10, + "DICE CHALLENGE", 2); + renderer->set_text_color(true); + + // Dice + int dice_size = 40; + int dice_y = iy + 45; + draw_die(ix + 20, dice_y, dice_size, dice1); + draw_die(ix + 20 + dice_size + 10, dice_y, dice_size, dice2); + + // --- Prompt --- + int prompt_y = dice_y + dice_size + 10; + renderer->draw_string_scaled(ix + 20, prompt_y, "WHERE WILL YOU LAND?", 1); + + // --- Options --- + int opt_y = prompt_y + 20; + for (int i = 0; i < 3; i++) { + if (!option_visible[i]) { + opt_y += 25; + continue; + } + char opt_buf[64]; + snprintf(opt_buf, sizeof(opt_buf), "%s %d: %s", + (selected_choice == i ? ">" : " "), i + 1, + MONOPOLY_BOARD[options[i]].name); + + if (selected_choice == i) + renderer->draw_filled_rectangle(ix + 15, opt_y - 2, iw - 30, 22, true, + 1); + if (selected_choice == i) renderer->set_text_color(false); - renderer->draw_string_scaled(ix + (iw - (int)strlen("DICE CHALLENGE") * 12) / 2, iy + 10, "DICE CHALLENGE", 2); - renderer->set_text_color(true); - // Dice - int dice_size = 40; - int dice_y = iy + 45; - draw_die(ix + 20, dice_y, dice_size, dice1); - draw_die(ix + 20 + dice_size + 10, dice_y, dice_size, dice2); - - // --- Prompt --- - int prompt_y = dice_y + dice_size + 10; - renderer->draw_string_scaled(ix + 20, prompt_y, "WHERE WILL YOU LAND?", 1); - - // --- Options --- - int opt_y = prompt_y + 20; - for (int i = 0; i < 3; i++) { - if (!option_visible[i]) { - opt_y += 25; - continue; - } - char opt_buf[64]; - snprintf(opt_buf, sizeof(opt_buf), "%s %d: %s", (selected_choice == i ? ">" : " "), i+1, MONOPOLY_BOARD[options[i]].name); - - if (selected_choice == i) renderer->draw_filled_rectangle(ix + 15, opt_y - 2, iw - 30, 22, true, 1); - if (selected_choice == i) renderer->set_text_color(false); - - // Truncate name if too long - if (strlen(opt_buf) > 30) opt_buf[30] = '\0'; - renderer->draw_string_scaled(ix + 20, opt_y, opt_buf, 2); - renderer->set_text_color(true); - opt_y += 25; - } - - if (show_error) { - renderer->draw_string_scaled(ix + (iw - 10 * 12) / 2, iy + ih - 30, "TRY AGAIN!", 2); - } - - ModalButtonHelper::draw_virtual_buttons(renderer, input_manager); + // Truncate name if too long + if (strlen(opt_buf) > 30) + opt_buf[30] = '\0'; + renderer->draw_string_scaled(ix + 20, opt_y, opt_buf, 2); + renderer->set_text_color(true); + opt_y += 25; } - bool is_dismissed() const { return dismissed; } + + if (show_error) { + renderer->draw_string_scaled(ix + (iw - 10 * 12) / 2, iy + ih - 30, + "TRY AGAIN!", 2); + } + + ModalButtonHelper::draw_virtual_buttons(renderer, input_manager); + } + bool is_dismissed() const { return dismissed; } }; diff --git a/games/monopoly/MonopolyBoardRenderer.h b/games/monopoly/MonopolyBoardRenderer.h index b208346..9150055 100644 --- a/games/monopoly/MonopolyBoardRenderer.h +++ b/games/monopoly/MonopolyBoardRenderer.h @@ -6,154 +6,197 @@ 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, int currentPlayerPos = -1, int observer_idx = -1) { - if (index < 0 || index >= BOARD_SIZE) return; - - // Find owner - 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] == index) { - owner_id = i; - break; - } - } - if (owner_id != -1) break; - } - - bool isInverted = false; - if (observer_idx != -1) { - isInverted = (owner_id == observer_idx); - } else { - 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; - - if (!is_corner && tile.type == TILE_PROPERTY) { - int bar_size = 10; - int bx = x, by = y, bw = w, bh = h; - - if (orientation == 0) { // Bottom row (Bar on top) - bh = bar_size; - content_y += bar_size; content_h -= bar_size; - } else if (orientation == 1) { // Left column (Bar on right) - bx = x + w - bar_size; bw = bar_size; - content_w -= bar_size; - } else if (orientation == 2) { // Top row (Bar on bottom) - by = y + h - bar_size; bh = bar_size; - content_h -= bar_size; - } else if (orientation == 3) { // Right column (Bar on left) - bw = bar_size; - content_x += bar_size; content_w -= bar_size; - } - - if (isInverted) { - // Background is white, so bar is black - renderer->draw_filled_rectangle(bx, by, bw, bh, false, 0); - renderer->set_text_color(true); // White text - } else { - renderer->draw_filled_rectangle(bx, by, bw, bh, true, 1); - renderer->set_text_color(false); // Black text - } - - // Group number - char gbuf[2] = { (char)('0' + tile.group[0]), '\0' }; - renderer->draw_string_scaled(bx + (bw - 6) / 2, by + (bh - 8) / 2, gbuf, 1); - - if (isInverted) renderer->set_text_color(false); - else renderer->set_text_color(true); - } - - char short_name[10] = {0}; - int s_ptr = 0; - bool isCurrentPos = (index == currentPlayerPos && observer_idx != -1); - - if (isCurrentPos) short_name[s_ptr++] = '-'; - - // Add * if owned by someone else - if (owner_id != -1 && observer_idx != -1 && owner_id != observer_idx) { - short_name[s_ptr++] = '*'; - } - - const char* full_name = tile.name; - if (is_corner) { - int len = strlen(full_name); - if (len > 3) len = 3; - for(int i=0; i= 'a' && short_name[i] <= 'z') short_name[i] -= 32; - - renderer->draw_string_scaled(content_x + (content_w - (int)strlen(short_name) * 6) / 2, content_y + (content_h - 8) / 2, short_name, 1); - - // Draw player markers - int p_count = 0; - for (int i = 0; i < players_count; ++i) { - if (players[i].position == index) { - char mark[2] = { (players[i].token ? players[i].token[0] : 'P'), '\0' }; - renderer->draw_string_scaled(content_x + 2 + (p_count * 8), content_y + 2, mark, 1); - p_count++; - } - } - - if (isInverted) { - renderer->set_text_color(true); + 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, int observer_idx = -1) { + if (index < 0 || index >= MONOPOLY_BOARD_SIZE) + return; + + // Find owner + 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] == index) { + owner_id = i; + break; } + } + if (owner_id != -1) + break; } - static void draw_board_perimeter(LowLevelRenderer* renderer, int width, int height, Player* players, int players_count, int currentPlayerPos = -1, int observer_idx = -1, int limit_start = -1, int limit_end = -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 - - auto should_draw = [&](int index) { - if (limit_start == -1 || limit_end == -1) return true; - if (limit_start <= limit_end) { - return index >= limit_start && index <= limit_end; - } else { - return index >= limit_start || index <= limit_end; - } - }; - - // --- Bottom Row: 0 to 10 (Right to Left) --- - if (should_draw(0)) draw_tile(renderer, width - cw, height - ch, cw, ch, 0, true, players, players_count, 0, currentPlayerPos, observer_idx); // GO - for (int i = 1; i < 10; ++i) { - if (should_draw(i)) draw_tile(renderer, width - cw - i * rw, height - ch, rw, ch, i, false, players, players_count, 0, currentPlayerPos, observer_idx); - } - if (should_draw(10)) draw_tile(renderer, 0, height - ch, cw, ch, 10, true, players, players_count, 1, currentPlayerPos, observer_idx); // JAIL - - // --- Left Column: 11 to 19 (Bottom to Top) --- - for (int i = 11; i < 20; ++i) { - if (should_draw(i)) draw_tile(renderer, 0, height - ch - (i - 10) * rh, cw, rh, i, false, players, players_count, 1, currentPlayerPos, observer_idx); - } - - // --- Top Row: 20 to 30 (Left to Right) --- - if (should_draw(20)) draw_tile(renderer, 0, 0, cw, ch, 20, true, players, players_count, 2, currentPlayerPos, observer_idx); // FREE PARKING - for (int i = 21; i < 30; ++i) { - if (should_draw(i)) draw_tile(renderer, cw + (i - 21) * rw, 0, rw, ch, i, false, players, players_count, 2, currentPlayerPos, observer_idx); - } - if (should_draw(30)) draw_tile(renderer, width - cw, 0, cw, ch, 30, true, players, players_count, 3, currentPlayerPos, observer_idx); // GO TO JAIL - - // --- Right Column: 31 to 39 (Top to Bottom) --- - for (int i = 31; i < 40; ++i) { - if (should_draw(i)) draw_tile(renderer, width - cw, ch + (i - 31) * rh, cw, rh, i, false, players, players_count, 3, currentPlayerPos, observer_idx); - } + bool isInverted = false; + if (observer_idx != -1) { + isInverted = (owner_id == observer_idx); + } else { + 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; + + if (!is_corner && tile.type == TILE_PROPERTY) { + int bar_size = 10; + int bx = x, by = y, bw = w, bh = h; + + if (orientation == 0) { // Bottom row (Bar on top) + bh = bar_size; + content_y += bar_size; + content_h -= bar_size; + } else if (orientation == 1) { // Left column (Bar on right) + bx = x + w - bar_size; + bw = bar_size; + content_w -= bar_size; + } else if (orientation == 2) { // Top row (Bar on bottom) + by = y + h - bar_size; + bh = bar_size; + content_h -= bar_size; + } else if (orientation == 3) { // Right column (Bar on left) + bw = bar_size; + content_x += bar_size; + content_w -= bar_size; + } + + if (isInverted) { + // Background is white, so bar is black + renderer->draw_filled_rectangle(bx, by, bw, bh, false, 0); + renderer->set_text_color(true); // White text + } else { + renderer->draw_filled_rectangle(bx, by, bw, bh, true, 1); + renderer->set_text_color(false); // Black text + } + + // Group number + char gbuf[2] = {(char)('0' + tile.group[0]), '\0'}; + renderer->draw_string_scaled(bx + (bw - 6) / 2, by + (bh - 8) / 2, gbuf, + 1); + + if (isInverted) + renderer->set_text_color(false); + else + renderer->set_text_color(true); + } + + char short_name[10] = {0}; + int s_ptr = 0; + bool isCurrentPos = (index == currentPlayerPos && observer_idx != -1); + + if (isCurrentPos) + short_name[s_ptr++] = '-'; + + // Add * if owned by someone else + if (owner_id != -1 && observer_idx != -1 && owner_id != observer_idx) { + short_name[s_ptr++] = '*'; + } + + const char *full_name = tile.name; + if (is_corner) { + int len = strlen(full_name); + if (len > 3) + len = 3; + for (int i = 0; i < len; i++) + short_name[s_ptr++] = full_name[i]; + } else { + short_name[s_ptr++] = full_name[0]; + const char *space = strchr(full_name, ' '); + if (space && space[1] != '\0') + short_name[s_ptr++] = space[1]; + } + + if (isCurrentPos) + short_name[s_ptr++] = '-'; + short_name[s_ptr] = '\0'; + + for (int i = 0; short_name[i]; i++) + if (short_name[i] >= 'a' && short_name[i] <= 'z') + short_name[i] -= 32; + + renderer->draw_string_scaled( + content_x + (content_w - (int)strlen(short_name) * 6) / 2, + content_y + (content_h - 8) / 2, short_name, 1); + + // Draw player markers + int p_count = 0; + for (int i = 0; i < players_count; ++i) { + if (players[i].position == index) { + char mark[2] = {(players[i].token ? players[i].token[0] : 'P'), '\0'}; + renderer->draw_string_scaled(content_x + 2 + (p_count * 8), + content_y + 2, mark, 1); + p_count++; + } + } + + if (isInverted) { + renderer->set_text_color(true); + } + } + + static void draw_board_perimeter(LowLevelRenderer *renderer, int width, + int height, Player *players, + int players_count, int currentPlayerPos = -1, + int observer_idx = -1, int limit_start = -1, + int limit_end = -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 + + auto should_draw = [&](int index) { + if (limit_start == -1 || limit_end == -1) + return true; + if (limit_start <= limit_end) { + return index >= limit_start && index <= limit_end; + } else { + return index >= limit_start || index <= limit_end; + } + }; + + // --- Bottom Row: 0 to 10 (Right to Left) --- + if (should_draw(0)) + draw_tile(renderer, width - cw, height - ch, cw, ch, 0, true, players, + players_count, 0, currentPlayerPos, observer_idx); // GO + for (int i = 1; i < 10; ++i) { + if (should_draw(i)) + draw_tile(renderer, width - cw - i * rw, height - ch, rw, ch, i, false, + players, players_count, 0, currentPlayerPos, observer_idx); + } + if (should_draw(10)) + draw_tile(renderer, 0, height - ch, cw, ch, 10, true, players, + players_count, 1, currentPlayerPos, observer_idx); // JAIL + + // --- Left Column: 11 to 19 (Bottom to Top) --- + for (int i = 11; i < 20; ++i) { + if (should_draw(i)) + draw_tile(renderer, 0, height - ch - (i - 10) * rh, cw, rh, i, false, + players, players_count, 1, currentPlayerPos, observer_idx); + } + + // --- Top Row: 20 to 30 (Left to Right) --- + if (should_draw(20)) + draw_tile(renderer, 0, 0, cw, ch, 20, true, players, players_count, 2, + currentPlayerPos, observer_idx); // FREE PARKING + for (int i = 21; i < 30; ++i) { + if (should_draw(i)) + draw_tile(renderer, cw + (i - 21) * rw, 0, rw, ch, i, false, players, + players_count, 2, currentPlayerPos, observer_idx); + } + if (should_draw(30)) + draw_tile(renderer, width - cw, 0, cw, ch, 30, true, players, + players_count, 3, currentPlayerPos, observer_idx); // GO TO JAIL + + // --- Right Column: 31 to 39 (Top to Bottom) --- + for (int i = 31; i < 40; ++i) { + if (should_draw(i)) + draw_tile(renderer, width - cw, ch + (i - 31) * rh, cw, rh, i, false, + players, players_count, 3, currentPlayerPos, observer_idx); + } + } }; diff --git a/games/monopoly/monopoly.cpp b/games/monopoly/monopoly.cpp index 08ebbd8..1c0ecad 100644 --- a/games/monopoly/monopoly.cpp +++ b/games/monopoly/monopoly.cpp @@ -1,10 +1,10 @@ +#include "chance.h" +#include "community_chest.h" +#include "monopoly_board.h" +#include "player.h" #include #include #include -#include "monopoly_board.h" -#include "player.h" -#include "chance.h" -#include "community_chest.h" // Forward Declarations void handle_roll(Player *p, bool *has_rolled, int *double_rolls); @@ -13,9 +13,10 @@ void handle_trade(Player *p); void handle_build(Player *p); void process_landing(Player *p); void handle_property_landing(Player *p, const BoardTile *tile); -void handle_chance(Player* p); -void find_properties_on_group_from_position(int position, int positions_found[4], int *count); -void handle_community_chest(Player* p); +void handle_chance(Player *p); +void find_properties_on_group_from_position(int position, + int positions_found[4], int *count); +void handle_community_chest(Player *p); void handle_jail_options(Player *p); bool attempt_jail_escape(Player *p); @@ -26,607 +27,551 @@ int players_count = 2; int current_player_idx = 0; bool just_sent_to_jail = false; // Flag to end turn when sent to jail -int main() -{ - srand(time(NULL)); +int main() { + srand(time(NULL)); - // Initialize hardcoded players - init_player(&players[0], 0, "Elias", "Top Hat"); - init_player(&players[1], 1, "Adolfo", "Racecar"); - init_player(&players[2], 2, "Grace", "Thimble"); - init_player(&players[3], 3, "Alicia", "Dog"); + // Initialize hardcoded players + init_player(&players[0], 0, "Elias", "Top Hat"); + init_player(&players[1], 1, "Adolfo", "Racecar"); + init_player(&players[2], 2, "Grace", "Thimble"); + init_player(&players[3], 3, "Alicia", "Dog"); - printf("\n╔════════════════════════════════════════╗\n"); - printf("║ Welcome to C-Monopoly! ║\n"); - printf("╚════════════════════════════════════════╝\n\n"); + printf("\n╔════════════════════════════════════════╗\n"); + printf("║ Welcome to C-Monopoly! ║\n"); + printf("╚════════════════════════════════════════╝\n\n"); - bool running = true; - bool has_rolled = false; // Turn state tracker - int double_rolls = 0; - current_player_idx = 0; - while (running) - { - Player *p = &players[current_player_idx]; - printf("\n╔═════════════════════════════════════════╗\n"); - printf("║ %s's Turn (%s)\n", p->name, p->token); - printf("║ 💰 Balance: $%d | 📍 %s\n", p->balance, MONOPOLY_BOARD[p->position].name); - printf("╚═════════════════════════════════════════╝\n\n"); - // Handle jail status at start of turn - if (p->is_in_jail) - { - if (just_sent_to_jail) - { - // Player was just sent to jail this turn, end their turn - printf("\n🚨 You have been sent to Jail! Your turn ends.\n"); - current_player_idx = (current_player_idx + 1) % players_count; - has_rolled = false; - just_sent_to_jail = false; - continue; - } - - printf("\n🚨 YOU ARE IN JAIL!\n"); - printf(" Turns in jail: %d/3\n", p->jail_turns); - handle_jail_options(p); - - if (!p->is_in_jail) - { - // Player escaped jail, now they can roll - printf("\n📋 %s escaped jail!\n\n", p->name); - } - else - { - // Player remains in jail, end turn - printf("\n📋 %s remains in jail.\n", p->name); - current_player_idx = (current_player_idx + 1) % players_count; - has_rolled = false; - continue; - } - } - if (!has_rolled) - printf("⚠️ You must roll the dice first!\n\n"); + bool running = true; + bool has_rolled = false; // Turn state tracker + int double_rolls = 0; + current_player_idx = 0; + while (running) { + Player *p = &players[current_player_idx]; + printf("\n╔═════════════════════════════════════════╗\n"); + printf("║ %s's Turn (%s)\n", p->name, p->token); + printf("║ 💰 Balance: $%d | 📍 %s\n", p->balance, + MONOPOLY_BOARD[p->position].name); + printf("╚═════════════════════════════════════════╝\n\n"); + // Handle jail status at start of turn + if (p->is_in_jail) { + if (just_sent_to_jail) { + // Player was just sent to jail this turn, end their turn + printf("\n🚨 You have been sent to Jail! Your turn ends.\n"); + current_player_idx = (current_player_idx + 1) % players_count; + has_rolled = false; + just_sent_to_jail = false; + continue; + } - printf("╔─ Available Actions ──────────────────────╗\n"); - printf("║ 1. 🎲 Roll Dice\n"); - printf("║ 2. 🏠 Buy Property\n"); - printf("║ 3. 🏗️ Build Houses/Hotels\n"); - printf("║ 4. 🤝 Trade (Empty)\n"); - printf("║ 5. ⏭️ End Turn\n"); - printf("║ 6. ❌ Exit Game\n"); - printf("╚═════════════════════════════════════════╝\n"); - printf("\nChoose action (1-6): "); + printf("\n🚨 YOU ARE IN JAIL!\n"); + printf(" Turns in jail: %d/3\n", p->jail_turns); + handle_jail_options(p); - int choice; - scanf("%d", &choice); - - switch (choice) - { - case 1: - handle_roll(p, &has_rolled, &double_rolls); - break; - case 2: - handle_buy(p, has_rolled); - break; - case 3: - handle_build(p); - break; - case 4: - handle_trade(p); - break; - case 5: - if (has_rolled) - { - current_player_idx = (current_player_idx + 1) % players_count; - has_rolled = false; // Reset for next player - just_sent_to_jail = false; // Reset jail flag - printf("✓ Turn ended.\n"); - } - else - { - printf("❌ You must roll before ending your turn!\n"); - } - break; - case 6: - running = false; - break; - default: - printf("❌ Invalid choice! Please enter 1-6.\n"); - } + if (!p->is_in_jail) { + // Player escaped jail, now they can roll + printf("\n📋 %s escaped jail!\n\n", p->name); + } else { + // Player remains in jail, end turn + printf("\n📋 %s remains in jail.\n", p->name); + current_player_idx = (current_player_idx + 1) % players_count; + has_rolled = false; + continue; + } } - - return 0; -} - -void find_properties_on_group_from_position(int position, int positions_found[4], int *count) -{ - const BoardTile *tile = &MONOPOLY_BOARD[position]; - if (tile->type == TILE_PROPERTY || tile->type == TILE_RAILROAD || tile->type == TILE_UTILITY) - { - int group_id = tile->group[0]; - *count = 0; - for (int i = 0; i < BOARD_SIZE; i++) - { - if (i == position) - continue; - const BoardTile *t = &MONOPOLY_BOARD[i]; - if (t->group[0] == group_id) - { - positions_found[(*count)++] = i; - } - } - } -} - -bool handle_if_owned(Player *p, const BoardTile *tile){ - if (tile->type == TILE_PROPERTY || tile->type == TILE_RAILROAD || tile->type == TILE_UTILITY){ - bool is_owned = false; - int i = 0; - for (i = 0; i < MAX_PLAYERS; i++) - { - for (int j = 0; j < players[i].property_count; j++) - { - if (players[i].properties_owned[j] == p->position) - { - is_owned = true; - printf("🏠 This property is owned by %s.\n", players[i].name); - break; - } - } - if (is_owned) - break; - } - if(is_owned) - { - if(players[i].id == p->id) - { - printf("✓ You own this property.\n"); - return true; - } - // pay rent logic would go here - p->balance -= tile->rent[0]; // Simplistic: always pay base rent - players[i].balance += tile->rent[0]; - printf("💰 Paid $%d in rent to the owner.\n", tile->rent[0]); - return true; - } - } - return false; -} - -void handle_roll(Player *p, bool *has_rolled, int *double_rolls) -{ - if (*has_rolled) - { - printf("❌ You have already moved this turn!\n"); - return; - } - - // Check if player is in jail and trying to escape - if (p->is_in_jail) - { - printf("❌ You are in jail! Use the jail menu to escape first.\n"); - return; - } - - int dice1 = (rand() % 6) + 1; - int dice2 = (rand() % 6) + 1; - printf("\n🎲 Rolled: [%d] + [%d] = %d\n", dice1, dice2, dice1 + dice2); - int total = dice1 + dice2; - p->position = (p->position + total) % BOARD_SIZE; - - if(dice1 == dice2) - { - (*double_rolls)++; - if(*double_rolls >= 3) - { - printf("🚨 Three doubles in a row! Sent directly to JAIL!\n"); - p->position = 10; // Jail position - p->jail_turns = 0; - p->is_in_jail = true; - *double_rolls = 0; - *has_rolled = true; - just_sent_to_jail = true; - return; - } - else - { - printf("✨ Doubles! You get another turn!\n"); - } - } - else - { - *double_rolls = 0; // Reset double rolls count - } - - *has_rolled = true; - - // Check for passing GO - if (p->position < (p->position - total)) - { // Simplistic wrap-around check - p->balance += 200; - printf("Passed GO! Collected $200.\n"); - } - - printf("➜ Moved %d spaces to: %s\n\n", total, MONOPOLY_BOARD[p->position].name); - process_landing(p); -} - -void handle_buy(Player *p, bool has_rolled) -{ if (!has_rolled) - { - printf("❌ You can't buy anything until you roll and land on a tile!\n"); - return; - } + printf("⚠️ You must roll the dice first!\n\n"); - const BoardTile *tile = &MONOPOLY_BOARD[p->position]; + printf("╔─ Available Actions ──────────────────────╗\n"); + printf("║ 1. 🎲 Roll Dice\n"); + printf("║ 2. 🏠 Buy Property\n"); + printf("║ 3. 🏗️ Build Houses/Hotels\n"); + printf("║ 4. 🤝 Trade (Empty)\n"); + printf("║ 5. ⏭️ End Turn\n"); + printf("║ 6. ❌ Exit Game\n"); + printf("╚═════════════════════════════════════════╝\n"); + printf("\nChoose action (1-6): "); - // Check if it's even a purchasable type - if (tile->type != TILE_PROPERTY && tile->type != TILE_RAILROAD && tile->type != TILE_UTILITY) - { - printf("❌ This location (%s) cannot be purchased.\n", tile->name); - return; - } - - // Check if someone already owns it (basic check) - // In a full game, we'd iterate through all players' properties_owned arrays - for (int i = 0; i < MAX_PLAYERS; i++) - { - for (int j = 0; j < players[i].property_count; j++) - { - if (players[i].properties_owned[j] == p->position) - { - printf("❌ This property is already owned by %s.\n", players[i].name); - return; - } - } - } - - if (p->balance >= tile->cost) - { - p->balance -= tile->cost; - p->properties_owned[p->property_count++] = p->position; - printf("✓ Bought %s for $%d! (Balance: $%d)\n", tile->name, tile->cost, p->balance); - } - else - { - printf("❌ Insufficient funds! Cost: $%d | Your Balance: $%d\n", tile->cost, p->balance); - } -} - -void process_landing(Player *p) -{ - const BoardTile *tile = &MONOPOLY_BOARD[p->position]; - - printf("\n─────────────────────────────────────────\n"); - printf("📍 Landed on: %s\n", tile->name); - printf("─────────────────────────────────────────\n"); - - switch (tile->type) - { - case TILE_PROPERTY: - case TILE_RAILROAD: - case TILE_UTILITY: - handle_property_landing(p, tile); - break; - - case TILE_TAX: - p->balance -= tile->cost; - printf("💸 Tax payment: $%d (Balance: $%d)\n", tile->cost, p->balance); - break; - - case TILE_CHANCE: - printf("\n⚡ CHANCE CARD! ⚡\n"); - handle_chance(p); - break; - - case TILE_COMMUNITY_CHEST: - printf("\n📦 COMMUNITY CHEST CARD! 📦\n"); - handle_community_chest(p); - break; - - case TILE_FREE_PARKING: - case TILE_GO: - case TILE_JAIL: - // Corner tiles (GO, Free Parking, Go to Jail, Just Visiting) - printf("📌 You're at: %s\n", tile->name); - break; - - - default: - break; - } -} - -void handle_property_landing(Player *p, const BoardTile *tile) -{ - bool is_owned = handle_if_owned(p, tile); - if (is_owned) - { - return; - } - - printf("\n💰 Purchase Price: $%d\n", tile->cost); - if (tile->type == TILE_PROPERTY) - { - // rent data - printf("\n📋 Rent Schedule:\n"); - printf(" Vacant.....................$%d\n", tile->rent[0]); - printf(" 1 House.....................$%d\n", tile->rent[1]); - printf(" 2 Houses.....................$%d\n", tile->rent[2]); - printf(" 3 Houses.....................$%d\n", tile->rent[3]); - printf(" 4 Houses.....................$%d\n", tile->rent[4]); - printf(" Hotel.......................$%d\n", tile->rent[5]); - printf("\n🏗️ House Cost: $%d\n", tile->house_cost); - } - - // show if the other properties in the group are owned - printf("\n🏘️ Property Group: %d | Position %d/%d in group\n", - tile->group[0], tile->group[1], tile->group[2]); - int group_positions[4]; - int group_count = 0; - find_properties_on_group_from_position(p->position, group_positions, &group_count); - if (group_count > 0) - { - printf("\n Related properties:\n"); - for (int i = 0; i < group_count; i++) - { - const BoardTile *gtile = &MONOPOLY_BOARD[group_positions[i]]; - // Check if owned by any player - bool is_owned_in_group = false; - for (int j = 0; j < MAX_PLAYERS; j++) - { - for (int k = 0; k < players[j].property_count; k++) - { - if (players[j].properties_owned[k] == group_positions[i]) - { - is_owned_in_group = true; - printf(" 🔒 %s (Owned by %s)\n", gtile->name, players[j].name); - break; - } - } - if (is_owned_in_group) - break; - } - if (!is_owned_in_group) - { - printf(" 🔓 %s - $%d (available)\n", gtile->name, gtile->cost); - } - } - } -} - -void handle_trade(Player *p) -{ - printf("⚙️ Trade functionality is not yet implemented.\n"); -} - -void handle_jail_options(Player *p) -{ - bool escaped = false; - - printf("\n╔─ Jail Options ──────────────────────────────╗\n"); - printf("║ 1. 🎲 Roll Doubles to Escape\n"); - printf("║ 2. 💰 Pay $50 Bail\n"); - if (p->jail_free_cards > 0) - printf("║ 3. 🔑 Use Get Out of Jail Free Card\n"); - printf("╚─────────────────────────────────────────────╝\n"); - printf("Choose option: "); - int choice; scanf("%d", &choice); - - switch(choice) - { - case 1: - // Try to roll doubles - escaped = attempt_jail_escape(p); - break; - case 2: - // Pay bail - if (p->balance >= 50) - { - p->balance -= 50; - p->is_in_jail = false; - p->jail_turns = 0; - escaped = true; - printf("\n✓ Paid $50 bail! You are now free.\n"); - } - else - { - printf("\n❌ Insufficient funds! You need $50.\n"); - } - break; - case 3: - if (p->jail_free_cards > 0) - { - p->jail_free_cards--; - p->is_in_jail = false; - p->jail_turns = 0; - escaped = true; - printf("\n✓ Used a Get Out of Jail Free card! You are now free.\n"); - printf(" Cards remaining: %d\n", p->jail_free_cards); - } - else - { - printf("\n❌ You don't have any Get Out of Jail Free cards!\n"); - } - break; - default: - printf("\n❌ Invalid choice!\n"); - break; - } - - if (!escaped) - { - p->jail_turns++; - if (p->jail_turns >= 3) - { - // Force payment after 3 turns - printf("\n⏰ You've been in jail for 3 turns. You must pay $50 bail!\n"); - p->balance -= 50; - p->is_in_jail = false; - p->jail_turns = 0; - printf(" Balance: $%d\n", p->balance); - } + + switch (choice) { + case 1: + handle_roll(p, &has_rolled, &double_rolls); + break; + case 2: + handle_buy(p, has_rolled); + break; + case 3: + handle_build(p); + break; + case 4: + handle_trade(p); + break; + case 5: + if (has_rolled) { + current_player_idx = (current_player_idx + 1) % players_count; + has_rolled = false; // Reset for next player + just_sent_to_jail = false; // Reset jail flag + printf("✓ Turn ended.\n"); + } else { + printf("❌ You must roll before ending your turn!\n"); + } + break; + case 6: + running = false; + break; + default: + printf("❌ Invalid choice! Please enter 1-6.\n"); } + } + + return 0; } -bool attempt_jail_escape(Player *p) -{ - int dice1 = (rand() % 6) + 1; - int dice2 = (rand() % 6) + 1; - printf("\n🎲 Rolling to escape...\n"); - printf(" Rolled: [%d] + [%d]\n", dice1, dice2); - - if (dice1 == dice2) - { - printf("\n✨ DOUBLES! You escaped jail!\n"); - p->is_in_jail = false; - p->jail_turns = 0; +void find_properties_on_group_from_position(int position, + int positions_found[4], + int *count) { + const BoardTile *tile = &MONOPOLY_BOARD[position]; + if (tile->type == TILE_PROPERTY || tile->type == TILE_RAILROAD || + tile->type == TILE_UTILITY) { + int group_id = tile->group[0]; + *count = 0; + for (int i = 0; i < MONOPOLY_BOARD_SIZE; i++) { + if (i == position) + continue; + const BoardTile *t = &MONOPOLY_BOARD[i]; + if (t->group[0] == group_id) { + positions_found[(*count)++] = i; + } + } + } +} + +bool handle_if_owned(Player *p, const BoardTile *tile) { + if (tile->type == TILE_PROPERTY || tile->type == TILE_RAILROAD || + tile->type == TILE_UTILITY) { + bool is_owned = false; + int i = 0; + for (i = 0; i < MAX_PLAYERS; i++) { + for (int j = 0; j < players[i].property_count; j++) { + if (players[i].properties_owned[j] == p->position) { + is_owned = true; + printf("🏠 This property is owned by %s.\n", players[i].name); + break; + } + } + if (is_owned) + break; + } + if (is_owned) { + if (players[i].id == p->id) { + printf("✓ You own this property.\n"); return true; + } + // pay rent logic would go here + p->balance -= tile->rent[0]; // Simplistic: always pay base rent + players[i].balance += tile->rent[0]; + printf("💰 Paid $%d in rent to the owner.\n", tile->rent[0]); + return true; } - else - { - printf("\n❌ No doubles. You remain in jail.\n"); - p->jail_turns++; - if (p->jail_turns >= 3) - { - printf("\n⏰ You've been in jail for 3 turns. You must pay $50 bail!\n"); - p->balance -= 50; - p->is_in_jail = false; - p->jail_turns = 0; - printf(" Balance: $%d\n", p->balance); + } + return false; +} + +void handle_roll(Player *p, bool *has_rolled, int *double_rolls) { + if (*has_rolled) { + printf("❌ You have already moved this turn!\n"); + return; + } + + // Check if player is in jail and trying to escape + if (p->is_in_jail) { + printf("❌ You are in jail! Use the jail menu to escape first.\n"); + return; + } + + int dice1 = (rand() % 6) + 1; + int dice2 = (rand() % 6) + 1; + printf("\n🎲 Rolled: [%d] + [%d] = %d\n", dice1, dice2, dice1 + dice2); + int total = dice1 + dice2; + p->position = (p->position + total) % MONOPOLY_BOARD_SIZE; + + if (dice1 == dice2) { + (*double_rolls)++; + if (*double_rolls >= 3) { + printf("🚨 Three doubles in a row! Sent directly to JAIL!\n"); + p->position = 10; // Jail position + p->jail_turns = 0; + p->is_in_jail = true; + *double_rolls = 0; + *has_rolled = true; + just_sent_to_jail = true; + return; + } else { + printf("✨ Doubles! You get another turn!\n"); + } + } else { + *double_rolls = 0; // Reset double rolls count + } + + *has_rolled = true; + + // Check for passing GO + if (p->position < (p->position - total)) { // Simplistic wrap-around check + p->balance += 200; + printf("Passed GO! Collected $200.\n"); + } + + printf("➜ Moved %d spaces to: %s\n\n", total, + MONOPOLY_BOARD[p->position].name); + process_landing(p); +} + +void handle_buy(Player *p, bool has_rolled) { + if (!has_rolled) { + printf("❌ You can't buy anything until you roll and land on a tile!\n"); + return; + } + + const BoardTile *tile = &MONOPOLY_BOARD[p->position]; + + // Check if it's even a purchasable type + if (tile->type != TILE_PROPERTY && tile->type != TILE_RAILROAD && + tile->type != TILE_UTILITY) { + printf("❌ This location (%s) cannot be purchased.\n", tile->name); + return; + } + + // Check if someone already owns it (basic check) + // In a full game, we'd iterate through all players' properties_owned arrays + for (int i = 0; i < MAX_PLAYERS; i++) { + for (int j = 0; j < players[i].property_count; j++) { + if (players[i].properties_owned[j] == p->position) { + printf("❌ This property is already owned by %s.\n", players[i].name); + return; + } + } + } + + if (p->balance >= tile->cost) { + p->balance -= tile->cost; + p->properties_owned[p->property_count++] = p->position; + printf("✓ Bought %s for $%d! (Balance: $%d)\n", tile->name, tile->cost, + p->balance); + } else { + printf("❌ Insufficient funds! Cost: $%d | Your Balance: $%d\n", tile->cost, + p->balance); + } +} + +void process_landing(Player *p) { + const BoardTile *tile = &MONOPOLY_BOARD[p->position]; + + printf("\n─────────────────────────────────────────\n"); + printf("📍 Landed on: %s\n", tile->name); + printf("─────────────────────────────────────────\n"); + + switch (tile->type) { + case TILE_PROPERTY: + case TILE_RAILROAD: + case TILE_UTILITY: + handle_property_landing(p, tile); + break; + + case TILE_TAX: + p->balance -= tile->cost; + printf("💸 Tax payment: $%d (Balance: $%d)\n", tile->cost, p->balance); + break; + + case TILE_CHANCE: + printf("\n⚡ CHANCE CARD! ⚡\n"); + handle_chance(p); + break; + + case TILE_COMMUNITY_CHEST: + printf("\n📦 COMMUNITY CHEST CARD! 📦\n"); + handle_community_chest(p); + break; + + case TILE_FREE_PARKING: + case TILE_GO: + case TILE_JAIL: + // Corner tiles (GO, Free Parking, Go to Jail, Just Visiting) + printf("📌 You're at: %s\n", tile->name); + break; + + default: + break; + } +} + +void handle_property_landing(Player *p, const BoardTile *tile) { + bool is_owned = handle_if_owned(p, tile); + if (is_owned) { + return; + } + + printf("\n💰 Purchase Price: $%d\n", tile->cost); + if (tile->type == TILE_PROPERTY) { + // rent data + printf("\n📋 Rent Schedule:\n"); + printf(" Vacant.....................$%d\n", tile->rent[0]); + printf(" 1 House.....................$%d\n", tile->rent[1]); + printf(" 2 Houses.....................$%d\n", tile->rent[2]); + printf(" 3 Houses.....................$%d\n", tile->rent[3]); + printf(" 4 Houses.....................$%d\n", tile->rent[4]); + printf(" Hotel.......................$%d\n", tile->rent[5]); + printf("\n🏗️ House Cost: $%d\n", tile->house_cost); + } + + // show if the other properties in the group are owned + printf("\n🏘️ Property Group: %d | Position %d/%d in group\n", tile->group[0], + tile->group[1], tile->group[2]); + int group_positions[4]; + int group_count = 0; + find_properties_on_group_from_position(p->position, group_positions, + &group_count); + if (group_count > 0) { + printf("\n Related properties:\n"); + for (int i = 0; i < group_count; i++) { + const BoardTile *gtile = &MONOPOLY_BOARD[group_positions[i]]; + // Check if owned by any player + bool is_owned_in_group = false; + for (int j = 0; j < MAX_PLAYERS; j++) { + for (int k = 0; k < players[j].property_count; k++) { + if (players[j].properties_owned[k] == group_positions[i]) { + is_owned_in_group = true; + printf(" 🔒 %s (Owned by %s)\n", gtile->name, players[j].name); + break; + } } - return false; + if (is_owned_in_group) + break; + } + if (!is_owned_in_group) { + printf(" 🔓 %s - $%d (available)\n", gtile->name, gtile->cost); + } } + } } -void handle_build(Player *p) -{ - printf("⚙️ Build functionality is not yet implemented.\n"); +void handle_trade(Player *p) { + printf("⚙️ Trade functionality is not yet implemented.\n"); } -void handle_chance(Player* p) { - // In a real game, you'd pull from a shuffled deck of indices - int card_idx = rand() % CHANCE_DECK_SIZE; - const ChanceCard* card = &CHANCE_DECK[card_idx]; +void handle_jail_options(Player *p) { + bool escaped = false; - printf("\n✨ %s\n", card->description); + printf("\n╔─ Jail Options ──────────────────────────────╗\n"); + printf("║ 1. 🎲 Roll Doubles to Escape\n"); + printf("║ 2. 💰 Pay $50 Bail\n"); + if (p->jail_free_cards > 0) + printf("║ 3. 🔑 Use Get Out of Jail Free Card\n"); + printf("╚─────────────────────────────────────────────╝\n"); + printf("Choose option: "); - switch (card->type) { - case CHANCE_ADVANCE: { - int target = card->value; - - // Handle special "Nearest" logic - if (target == TARGET_NEAREST_UTILITY) { - while (MONOPOLY_BOARD[p->position].type != TILE_UTILITY) { - p->position = (p->position + 1) % BOARD_SIZE; - } - } else if (target == TARGET_NEAREST_RAILROAD) { - while (MONOPOLY_BOARD[p->position].type != TILE_RAILROAD) { - p->position = (p->position + 1) % BOARD_SIZE; - } - } else { - // Check for passing GO during standard advance - if (target < p->position) { - p->balance += 200; - printf("🎉 Passed GO! Collected $200.\n"); - } - p->position = target; - } - printf("➜ Moved to: %s\n", MONOPOLY_BOARD[p->position].name); - process_landing(p); // Re-evaluate logic for new square - break; - } + int choice; + scanf("%d", &choice); - case CHANCE_EARN: - p->balance += card->value; - break; - - case CHANCE_SPEND: - p->balance -= card->value; - break; - - case CHANCE_BACK: - p->position = (p->position - card->value + BOARD_SIZE) % BOARD_SIZE; - printf("⬅️ Moved back to: %s\n", MONOPOLY_BOARD[p->position].name); - process_landing(p); - break; - - case CHANCE_JAIL: - p->position = 10; // Index of Jail - p->jail_turns = 0; - p->is_in_jail = true; - just_sent_to_jail = true; - printf("🚨 Sent directly to JAIL!\n"); - break; - - case CHANCE_SPEND_EACH_PLAYER: - for (int i = 0; i < MAX_PLAYERS; i++) { - if (players[i].id != p->id && !players[i].is_bankrupt) { - p->balance -= card->value; - players[i].balance += card->value; - } - } - break; - - case CHANCE_JAIL_FREE: - p->jail_free_cards++; - printf("🔑 You received a 'Get Out of Jail Free' card! (Total: %d)\n", p->jail_free_cards); - break; - - case CHANCE_REPAIRS: - // TODO: Logic placeholder: calculate based on houses/hotels owned - printf("🔧 Repairs calculated based on your property development.\n"); - break; + switch (choice) { + case 1: + // Try to roll doubles + escaped = attempt_jail_escape(p); + break; + case 2: + // Pay bail + if (p->balance >= 50) { + p->balance -= 50; + p->is_in_jail = false; + p->jail_turns = 0; + escaped = true; + printf("\n✓ Paid $50 bail! You are now free.\n"); + } else { + printf("\n❌ Insufficient funds! You need $50.\n"); } + break; + case 3: + if (p->jail_free_cards > 0) { + p->jail_free_cards--; + p->is_in_jail = false; + p->jail_turns = 0; + escaped = true; + printf("\n✓ Used a Get Out of Jail Free card! You are now free.\n"); + printf(" Cards remaining: %d\n", p->jail_free_cards); + } else { + printf("\n❌ You don't have any Get Out of Jail Free cards!\n"); + } + break; + default: + printf("\n❌ Invalid choice!\n"); + break; + } + + if (!escaped) { + p->jail_turns++; + if (p->jail_turns >= 3) { + // Force payment after 3 turns + printf("\n⏰ You've been in jail for 3 turns. You must pay $50 bail!\n"); + p->balance -= 50; + p->is_in_jail = false; + p->jail_turns = 0; + printf(" Balance: $%d\n", p->balance); + } + } } -void handle_community_chest(Player* p) { - int card_idx = rand() % COMMUNITY_DECK_SIZE; - const CommunityCard* card = &COMMUNITY_DECK[card_idx]; +bool attempt_jail_escape(Player *p) { + int dice1 = (rand() % 6) + 1; + int dice2 = (rand() % 6) + 1; + printf("\n🎲 Rolling to escape...\n"); + printf(" Rolled: [%d] + [%d]\n", dice1, dice2); - printf("\n📦 %s\n", card->description); - - switch (card->type) { - case COMMUNITY_ADVANCE: - p->position = card->value; - p->balance += 200; // Always Go in this set - printf("➜ Moved to Go! Balance: $%d\n", p->balance); - break; - - case COMMUNITY_EARN: - p->balance += card->value; - break; - - case COMMUNITY_SPEND: - p->balance -= card->value; - break; - - case COMMUNITY_EARN_EACH_PLAYER: - for (int i = 0; i < MAX_PLAYERS; i++) { - if (players[i].id != p->id && !players[i].is_bankrupt) { - players[i].balance -= card->value; - p->balance += card->value; - } - } - break; - - case COMMUNITY_JAIL: - p->position = 10; - p->jail_turns = 0; - p->is_in_jail = true; - just_sent_to_jail = true; - printf("🚨 Go to Jail! (Do not pass Go, do not collect $200)\n"); - break; - - case COMMUNITY_REPAIRS: - // logic for $40/house and $115/hotel - printf("🔧 Repairs assessed.\n"); - break; - - case COMMUNITY_JAIL_FREE: - p->jail_free_cards++; - printf("🔑 Card stored. (Total: %d)\n", p->jail_free_cards); - break; + if (dice1 == dice2) { + printf("\n✨ DOUBLES! You escaped jail!\n"); + p->is_in_jail = false; + p->jail_turns = 0; + return true; + } else { + printf("\n❌ No doubles. You remain in jail.\n"); + p->jail_turns++; + if (p->jail_turns >= 3) { + printf("\n⏰ You've been in jail for 3 turns. You must pay $50 bail!\n"); + p->balance -= 50; + p->is_in_jail = false; + p->jail_turns = 0; + printf(" Balance: $%d\n", p->balance); } + return false; + } +} + +void handle_build(Player *p) { + printf("⚙️ Build functionality is not yet implemented.\n"); +} + +void handle_chance(Player *p) { + // In a real game, you'd pull from a shuffled deck of indices + int card_idx = rand() % CHANCE_DECK_SIZE; + const ChanceCard *card = &CHANCE_DECK[card_idx]; + + printf("\n✨ %s\n", card->description); + + switch (card->type) { + case CHANCE_ADVANCE: { + int target = card->value; + + // Handle special "Nearest" logic + if (target == TARGET_NEAREST_UTILITY) { + while (MONOPOLY_BOARD[p->position].type != TILE_UTILITY) { + p->position = (p->position + 1) % MONOPOLY_BOARD_SIZE; + } + } else if (target == TARGET_NEAREST_RAILROAD) { + while (MONOPOLY_BOARD[p->position].type != TILE_RAILROAD) { + p->position = (p->position + 1) % MONOPOLY_BOARD_SIZE; + } + } else { + // Check for passing GO during standard advance + if (target < p->position) { + p->balance += 200; + printf("🎉 Passed GO! Collected $200.\n"); + } + p->position = target; + } + printf("➜ Moved to: %s\n", MONOPOLY_BOARD[p->position].name); + process_landing(p); // Re-evaluate logic for new square + break; + } + + case CHANCE_EARN: + p->balance += card->value; + break; + + case CHANCE_SPEND: + p->balance -= card->value; + break; + + case CHANCE_BACK: + p->position = + (p->position - card->value + MONOPOLY_BOARD_SIZE) % MONOPOLY_BOARD_SIZE; + printf("⬅️ Moved back to: %s\n", MONOPOLY_BOARD[p->position].name); + process_landing(p); + break; + + case CHANCE_JAIL: + p->position = 10; // Index of Jail + p->jail_turns = 0; + p->is_in_jail = true; + just_sent_to_jail = true; + printf("🚨 Sent directly to JAIL!\n"); + break; + + case CHANCE_SPEND_EACH_PLAYER: + for (int i = 0; i < MAX_PLAYERS; i++) { + if (players[i].id != p->id && !players[i].is_bankrupt) { + p->balance -= card->value; + players[i].balance += card->value; + } + } + break; + + case CHANCE_JAIL_FREE: + p->jail_free_cards++; + printf("🔑 You received a 'Get Out of Jail Free' card! (Total: %d)\n", + p->jail_free_cards); + break; + + case CHANCE_REPAIRS: + // TODO: Logic placeholder: calculate based on houses/hotels owned + printf("🔧 Repairs calculated based on your property development.\n"); + break; + } +} + +void handle_community_chest(Player *p) { + int card_idx = rand() % COMMUNITY_DECK_SIZE; + const CommunityCard *card = &COMMUNITY_DECK[card_idx]; + + printf("\n📦 %s\n", card->description); + + switch (card->type) { + case COMMUNITY_ADVANCE: + p->position = card->value; + p->balance += 200; // Always Go in this set + printf("➜ Moved to Go! Balance: $%d\n", p->balance); + break; + + case COMMUNITY_EARN: + p->balance += card->value; + break; + + case COMMUNITY_SPEND: + p->balance -= card->value; + break; + + case COMMUNITY_EARN_EACH_PLAYER: + for (int i = 0; i < MAX_PLAYERS; i++) { + if (players[i].id != p->id && !players[i].is_bankrupt) { + players[i].balance -= card->value; + p->balance += card->value; + } + } + break; + + case COMMUNITY_JAIL: + p->position = 10; + p->jail_turns = 0; + p->is_in_jail = true; + just_sent_to_jail = true; + printf("🚨 Go to Jail! (Do not pass Go, do not collect $200)\n"); + break; + + case COMMUNITY_REPAIRS: + // logic for $40/house and $115/hotel + printf("🔧 Repairs assessed.\n"); + break; + + case COMMUNITY_JAIL_FREE: + p->jail_free_cards++; + printf("🔑 Card stored. (Total: %d)\n", p->jail_free_cards); + break; + } } \ No newline at end of file diff --git a/games/monopoly/monopoly_board.h b/games/monopoly/monopoly_board.h index 5a59213..5748eb9 100644 --- a/games/monopoly/monopoly_board.h +++ b/games/monopoly/monopoly_board.h @@ -5,72 +5,232 @@ #include typedef enum { - TILE_GO, - TILE_PROPERTY, - TILE_COMMUNITY_CHEST, - TILE_TAX, - TILE_RAILROAD, - TILE_CHANCE, - TILE_JAIL, - TILE_UTILITY, - TILE_FREE_PARKING, - TILE_GO_TO_JAIL + TILE_GO, + TILE_PROPERTY, + TILE_COMMUNITY_CHEST, + TILE_TAX, + TILE_RAILROAD, + TILE_CHANCE, + TILE_JAIL, + TILE_UTILITY, + TILE_FREE_PARKING, + TILE_GO_TO_JAIL } TileType; typedef struct { - const char* name; - TileType type; - bool is_corner; - int cost; // 0 if not applicable - const char* color; // Hex string, NULL if not property - int rent[6]; // Base, 1H, 2H, 3H, 4H, Hotel - int group[3]; // Group ID, Position in group, Total in group - int house_cost; // Cost to build + const char *name; + TileType type; + bool is_corner; + int cost; // 0 if not applicable + const char *color; // Hex string, NULL if not property + int rent[6]; // Base, 1H, 2H, 3H, 4H, Hotel + int group[3]; // Group ID, Position in group, Total in group + int house_cost; // Cost to build } BoardTile; -#define BOARD_SIZE 40 +#define MONOPOLY_BOARD_SIZE 40 -static const BoardTile MONOPOLY_BOARD[BOARD_SIZE] = { +static const BoardTile MONOPOLY_BOARD[MONOPOLY_BOARD_SIZE] = { {"Go", TILE_GO, true, 0, NULL, {0}, {0}, 0}, - {"Mediterranean Avenue", TILE_PROPERTY, false, 60, "#955438", {2, 10, 30, 90, 160, 250}, {1, 1, 2}, 50}, + {"Mediterranean Avenue", + TILE_PROPERTY, + false, + 60, + "#955438", + {2, 10, 30, 90, 160, 250}, + {1, 1, 2}, + 50}, {"Community Chest", TILE_COMMUNITY_CHEST, false, 0, NULL, {0}, {0}, 0}, - {"Baltic Avenue", TILE_PROPERTY, false, 60, "#955438", {4, 20, 60, 180, 320, 450}, {1, 2, 2}, 50}, + {"Baltic Avenue", + TILE_PROPERTY, + false, + 60, + "#955438", + {4, 20, 60, 180, 320, 450}, + {1, 2, 2}, + 50}, {"Income Tax", TILE_TAX, false, 200, NULL, {0}, {0}, 0}, {"Reading Railroad", TILE_RAILROAD, false, 200, NULL, {0}, {9, 1, 4}, 0}, - {"Rhode Island Avenue", TILE_PROPERTY, false, 100, "#aae0fa", {6, 30, 90, 270, 400, 550}, {2, 1, 3}, 50}, + {"Rhode Island Avenue", + TILE_PROPERTY, + false, + 100, + "#aae0fa", + {6, 30, 90, 270, 400, 550}, + {2, 1, 3}, + 50}, {"Chance", TILE_CHANCE, false, 0, NULL, {0}, {0}, 0}, - {"Vermont Avenue", TILE_PROPERTY, false, 100, "#aae0fa", {6, 30, 90, 270, 400, 550}, {2, 2, 3}, 50}, - {"Connecticut Avenue", TILE_PROPERTY, false, 120, "#aae0fa", {8, 40, 100, 300, 450, 600}, {2, 3, 3}, 50}, + {"Vermont Avenue", + TILE_PROPERTY, + false, + 100, + "#aae0fa", + {6, 30, 90, 270, 400, 550}, + {2, 2, 3}, + 50}, + {"Connecticut Avenue", + TILE_PROPERTY, + false, + 120, + "#aae0fa", + {8, 40, 100, 300, 450, 600}, + {2, 3, 3}, + 50}, {"Jail", TILE_JAIL, true, 0, NULL, {0}, {0}, 0}, - {"St. Charles Place", TILE_PROPERTY, false, 140, "#d93a96", {10, 50, 150, 450, 625, 750}, {3, 1, 3}, 100}, + {"St. Charles Place", + TILE_PROPERTY, + false, + 140, + "#d93a96", + {10, 50, 150, 450, 625, 750}, + {3, 1, 3}, + 100}, {"Electric Company", TILE_UTILITY, false, 150, NULL, {0}, {10, 1, 2}, 0}, - {"States Avenue", TILE_PROPERTY, false, 140, "#d93a96", {10, 50, 150, 450, 625, 750}, {3, 2, 3}, 100}, - {"Virginia Avenue", TILE_PROPERTY, false, 160, "#d93a96", {12, 60, 180, 500, 700, 900}, {3, 3, 3}, 100}, - {"Pennsylvania Railroad", TILE_RAILROAD, false, 200, NULL, {0}, {9, 2, 4}, 0}, - {"St. James Place", TILE_PROPERTY, false, 180, "#f7941d", {14, 70, 200, 550, 750, 950}, {4, 1, 3}, 100}, + {"States Avenue", + TILE_PROPERTY, + false, + 140, + "#d93a96", + {10, 50, 150, 450, 625, 750}, + {3, 2, 3}, + 100}, + {"Virginia Avenue", + TILE_PROPERTY, + false, + 160, + "#d93a96", + {12, 60, 180, 500, 700, 900}, + {3, 3, 3}, + 100}, + {"Pennsylvania Railroad", + TILE_RAILROAD, + false, + 200, + NULL, + {0}, + {9, 2, 4}, + 0}, + {"St. James Place", + TILE_PROPERTY, + false, + 180, + "#f7941d", + {14, 70, 200, 550, 750, 950}, + {4, 1, 3}, + 100}, {"Community Chest", TILE_COMMUNITY_CHEST, false, 0, NULL, {0}, {0}, 0}, - {"Tennessee Avenue", TILE_PROPERTY, false, 180, "#f7941d", {14, 70, 200, 550, 750, 950}, {4, 2, 3}, 100}, - {"New York Avenue", TILE_PROPERTY, false, 200, "#f7941d", {16, 80, 220, 600, 800, 1000}, {4, 3, 3}, 100}, + {"Tennessee Avenue", + TILE_PROPERTY, + false, + 180, + "#f7941d", + {14, 70, 200, 550, 750, 950}, + {4, 2, 3}, + 100}, + {"New York Avenue", + TILE_PROPERTY, + false, + 200, + "#f7941d", + {16, 80, 220, 600, 800, 1000}, + {4, 3, 3}, + 100}, {"Free Parking", TILE_FREE_PARKING, true, 0, NULL, {0}, {0}, 0}, - {"Kentucky Avenue", TILE_PROPERTY, false, 220, "#ed1b24", {18, 90, 250, 700, 875, 1050}, {5, 1, 3}, 150}, + {"Kentucky Avenue", + TILE_PROPERTY, + false, + 220, + "#ed1b24", + {18, 90, 250, 700, 875, 1050}, + {5, 1, 3}, + 150}, {"Chance", TILE_CHANCE, false, 0, NULL, {0}, {0}, 0}, - {"Indiana Avenue", TILE_PROPERTY, false, 220, "#ed1b24", {18, 90, 250, 700, 875, 1050}, {5, 2, 3}, 150}, - {"Illinois Avenue", TILE_PROPERTY, false, 240, "#ed1b24", {20, 100, 300, 750, 925, 1100}, {5, 3, 3}, 150}, + {"Indiana Avenue", + TILE_PROPERTY, + false, + 220, + "#ed1b24", + {18, 90, 250, 700, 875, 1050}, + {5, 2, 3}, + 150}, + {"Illinois Avenue", + TILE_PROPERTY, + false, + 240, + "#ed1b24", + {20, 100, 300, 750, 925, 1100}, + {5, 3, 3}, + 150}, {"B. & O. Railroad", TILE_RAILROAD, false, 200, NULL, {0}, {9, 3, 4}, 0}, - {"Atlantic Avenue", TILE_PROPERTY, false, 260, "#fef200", {22, 110, 330, 800, 975, 1150}, {6, 1, 3}, 150}, - {"Ventnor Avenue", TILE_PROPERTY, false, 260, "#fef200", {22, 110, 330, 800, 975, 1150}, {6, 2, 3}, 150}, + {"Atlantic Avenue", + TILE_PROPERTY, + false, + 260, + "#fef200", + {22, 110, 330, 800, 975, 1150}, + {6, 1, 3}, + 150}, + {"Ventnor Avenue", + TILE_PROPERTY, + false, + 260, + "#fef200", + {22, 110, 330, 800, 975, 1150}, + {6, 2, 3}, + 150}, {"Water Works", TILE_UTILITY, false, 150, NULL, {0}, {10, 2, 2}, 0}, - {"Marvin Gardens", TILE_PROPERTY, false, 280, "#fef200", {24, 120, 360, 850, 1025, 1200}, {6, 3, 3}, 150}, + {"Marvin Gardens", + TILE_PROPERTY, + false, + 280, + "#fef200", + {24, 120, 360, 850, 1025, 1200}, + {6, 3, 3}, + 150}, {"Go To Jail", TILE_GO_TO_JAIL, true, 0, NULL, {0}, {0}, 0}, - {"Pacific Avenue", TILE_PROPERTY, false, 300, "#1fb25a", {26, 130, 390, 900, 1100, 1275}, {7, 1, 3}, 200}, - {"North Carolina Avenue", TILE_PROPERTY, false, 300, "#1fb25a", {26, 130, 390, 900, 1100, 1275}, {7, 2, 3}, 200}, + {"Pacific Avenue", + TILE_PROPERTY, + false, + 300, + "#1fb25a", + {26, 130, 390, 900, 1100, 1275}, + {7, 1, 3}, + 200}, + {"North Carolina Avenue", + TILE_PROPERTY, + false, + 300, + "#1fb25a", + {26, 130, 390, 900, 1100, 1275}, + {7, 2, 3}, + 200}, {"Community Chest", TILE_COMMUNITY_CHEST, false, 0, NULL, {0}, {0}, 0}, - {"Pennsylvania Avenue", TILE_PROPERTY, false, 320, "#1fb25a", {28, 150, 450, 1000, 1200, 1400}, {7, 3, 3}, 200}, + {"Pennsylvania Avenue", + TILE_PROPERTY, + false, + 320, + "#1fb25a", + {28, 150, 450, 1000, 1200, 1400}, + {7, 3, 3}, + 200}, {"Short Line", TILE_RAILROAD, false, 200, NULL, {0}, {9, 4, 4}, 0}, {"Chance", TILE_CHANCE, false, 0, NULL, {0}, {0}, 0}, - {"Park Place", TILE_PROPERTY, false, 350, "#0072bb", {35, 175, 500, 1100, 1300, 1500}, {8, 1, 2}, 200}, + {"Park Place", + TILE_PROPERTY, + false, + 350, + "#0072bb", + {35, 175, 500, 1100, 1300, 1500}, + {8, 1, 2}, + 200}, {"Luxury Tax", TILE_TAX, false, 100, NULL, {0}, {0}, 0}, - {"Boardwalk", TILE_PROPERTY, false, 400, "#0072bb", {50, 200, 600, 1400, 1700, 2000}, {8, 2, 2}, 200} -}; + {"Boardwalk", + TILE_PROPERTY, + false, + 400, + "#0072bb", + {50, 200, 600, 1400, 1700, 2000}, + {8, 2, 2}, + 200}}; #endif \ No newline at end of file diff --git a/games/monopoly/monopoly_game.cpp b/games/monopoly/monopoly_game.cpp index 19bc15c..e2681ac 100644 --- a/games/monopoly/monopoly_game.cpp +++ b/games/monopoly/monopoly_game.cpp @@ -1,7 +1,8 @@ // ============================================================================ // MONOPOLY GAME IMPLEMENTATION (for custom console) // ============================================================================ -// Refactored from console version to use Game interface and rendering/input system +// Refactored from console version to use Game interface and rendering/input +// system #include "monopoly_game.h" #ifdef __cplusplus @@ -12,575 +13,705 @@ extern "C" { } #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 "DiceModalGame.h" #include "ModalButtonHelper.h" -#include "sprites.h" #include "MonopolyBoardRenderer.h" - +#include "PaymentModalGame.h" +#include "PropertyModalGame.h" +#include "TurnModalGame.h" +#include "sprites.h" +#include +#include +#include +#include // --- Constructor --- -MonopolyGame::MonopolyGame(uint16_t width, uint16_t height, LowLevelRenderer* renderer, LowLevelGUI* gui, InputManager* input_manager) +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; + 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); + // 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. + // 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; + 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; + 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; +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 + // 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 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 (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; - } + // 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 (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; - - delete active_modal; - active_modal = nullptr; - - 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; + 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; } - 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; + 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; - default: 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) % MONOPOLY_BOARD_SIZE; + while (MONOPOLY_BOARD[target].type != TILE_UTILITY) { + target = (target + 1) % MONOPOLY_BOARD_SIZE; + } + force_utility_10x = true; + } else if (target == TARGET_NEAREST_RAILROAD) { + target = (p->position + 1) % MONOPOLY_BOARD_SIZE; + while (MONOPOLY_BOARD[target].type != TILE_RAILROAD) { + target = (target + 1) % MONOPOLY_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 + MONOPOLY_BOARD_SIZE) % + MONOPOLY_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; + + delete active_modal; + active_modal = nullptr; + + 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) % MONOPOLY_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(); + // 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]; + renderer->clear_buffer(); - // --- Draw Board Perimeter --- - MonopolyBoardRenderer::draw_board_perimeter(renderer, width, height, players, players_count, p->position); + Player *p = &players[current_player_idx]; + const BoardTile *tile = &MONOPOLY_BOARD[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; + // --- Draw Board Perimeter --- + MonopolyBoardRenderer::draw_board_perimeter(renderer, width, height, players, + players_count, p->position); - // --- 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); + // --- 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; - // 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); + // --- 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); - 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); + // 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; + } - 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); + ModalButtonHelper::draw_virtual_buttons(renderer, input_manager); }