From cad1aad2c8c16089f7abe03f834f77464d3939a2 Mon Sep 17 00:00:00 2001 From: Adolfo Reyna Date: Sat, 31 Jan 2026 09:45:40 -0500 Subject: [PATCH] initial monopoly test --- CMakeLists.txt | 3 + basic1.cpp | 6 + board_config.h | 4 +- display/low_level_gui.h | 3 +- emulator/Makefile | 4 +- emulator/basic1_emulator | Bin 140736 -> 142032 bytes emulator/main.cpp | 5 + games/monopoly/chance.h | 56 +++ games/monopoly/community_chest.h | 43 +++ games/monopoly/monopoly.cpp | 632 +++++++++++++++++++++++++++++++ games/monopoly/monopoly_board.h | 76 ++++ games/monopoly/monopoly_game.cpp | 218 +++++++++++ games/monopoly/monopoly_game.h | 47 +++ games/monopoly/player.c | 18 + games/monopoly/player.h | 41 ++ 15 files changed, 1152 insertions(+), 4 deletions(-) create mode 100644 games/monopoly/chance.h create mode 100644 games/monopoly/community_chest.h create mode 100644 games/monopoly/monopoly.cpp create mode 100644 games/monopoly/monopoly_board.h create mode 100644 games/monopoly/monopoly_game.cpp create mode 100644 games/monopoly/monopoly_game.h create mode 100644 games/monopoly/player.c create mode 100644 games/monopoly/player.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 105d920..7900419 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,6 +45,8 @@ add_executable(basic1 lib/game_launcher.cpp games/tic_tac_toe.cpp games/demo_game.cpp + games/monopoly/monopoly_game.cpp + games/monopoly/player.c lib/st7796/st7796.c lib/ft6336u/ft6336u.c lib/sd_card/sd_card.c @@ -80,6 +82,7 @@ target_link_libraries(basic1 target_include_directories(basic1 PRIVATE ${CMAKE_CURRENT_LIST_DIR} ${CMAKE_CURRENT_LIST_DIR}/games + ${CMAKE_CURRENT_LIST_DIR}/games/monopoly ${CMAKE_CURRENT_LIST_DIR}/lib ${CMAKE_CURRENT_LIST_DIR}/lib/fatfs/source ${CMAKE_CURRENT_LIST_DIR}/lib/st7796 diff --git a/basic1.cpp b/basic1.cpp index 9fc92f0..25fc2f2 100644 --- a/basic1.cpp +++ b/basic1.cpp @@ -52,6 +52,7 @@ #include "game_launcher.h" #include "tic_tac_toe.h" #include "demo_game.h" +#include "monopoly_game.h" // Binary info for RP2350 - ensures proper boot image structure @@ -349,6 +350,11 @@ int main() [](uint16_t w, uint16_t h, LowLevelRenderer* r, LowLevelGUI* g, InputManager* im) -> Game* { return new TicTacToeGame(w, h, r, g, im); }); + + launcher.register_game("Monopoly", "Classic property trading game", + [](uint16_t w, uint16_t h, LowLevelRenderer* r, LowLevelGUI* g, InputManager* im) -> Game* { + return new MonopolyGame(w, h, r, g, im); + }); launcher.register_game("Demo Game", "Simple test game", [](uint16_t w, uint16_t h, LowLevelRenderer* r, LowLevelGUI* g, InputManager* im) -> Game* { diff --git a/board_config.h b/board_config.h index 1d2ddd8..b725ad3 100644 --- a/board_config.h +++ b/board_config.h @@ -16,9 +16,9 @@ // ============================================================================ // ---- SELECT YOUR BOARD HERE ---- -#define BOARD_FEATHER_TFT // Feather RP2350 + 4.0" TFT ST7796 +// #define BOARD_FEATHER_TFT // Feather RP2350 + 4.0" TFT ST7796 // #define BOARD_PICO2_TFT // Pico 2 + 4.0" TFT ST7796 -// #define BOARD_PICO2_EINK // Pico 2 + E-Ink Display +#define BOARD_PICO2_EINK // Pico 2 + E-Ink Display // -------------------------------- // ============================================================================ diff --git a/display/low_level_gui.h b/display/low_level_gui.h index ae2e438..4b2859a 100644 --- a/display/low_level_gui.h +++ b/display/low_level_gui.h @@ -19,8 +19,9 @@ class LowLevelGUI private: LowLevelRenderer* renderer; bool use_rounded_corners = true; - const Font* current_font; + public: + const Font* current_font; LowLevelGUI(LowLevelRenderer* rend, const Font& font); LowLevelWindow* draw_new_window(int x, int y, int width, int height, const char *title); void draw_window(LowLevelWindow* window); diff --git a/emulator/Makefile b/emulator/Makefile index 8af17ea..37e4686 100644 --- a/emulator/Makefile +++ b/emulator/Makefile @@ -24,7 +24,9 @@ SRC = main.cpp low_level_display_sfml.cpp input_manager.cpp game_launcher.cpp \ ../display/low_level_render.cpp \ ../display/low_level_gui.cpp \ ../games/demo_game.cpp \ - ../games/tic_tac_toe.cpp + ../games/tic_tac_toe.cpp \ + ../games/monopoly/monopoly_game.cpp \ + ../games/monopoly/player.c $(TARGET): $(SRC) $(CXX) $(CXXFLAGS) $(SRC) -o $(TARGET) $(INCLUDES) $(LIBS) diff --git a/emulator/basic1_emulator b/emulator/basic1_emulator index 8f23672a09f4e7d20d353e999827ef38ebfb8a33..f5c37c6c435545962bd5c802ed8d25874a3e3588 100755 GIT binary patch delta 31305 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 #include @@ -37,6 +38,10 @@ int main() { [](uint16_t w, uint16_t h, LowLevelRenderer* r, LowLevelGUI* g, InputManager* im) -> Game* { return new TicTacToeGame(w, h, r, g, im); }); + launcher.register_game("Monopoly", "Classic property trading game", + [](uint16_t w, uint16_t h, LowLevelRenderer* r, LowLevelGUI* g, InputManager* im) -> Game* { + return new MonopolyGame(w, h, r, g, im); + }); launcher.register_game("Demo Game", "Simple test game", [](uint16_t w, uint16_t h, LowLevelRenderer* r, LowLevelGUI* g, InputManager* im) -> Game* { return new DemoGame(w, h, r, g, im); diff --git a/games/monopoly/chance.h b/games/monopoly/chance.h new file mode 100644 index 0000000..a51b444 --- /dev/null +++ b/games/monopoly/chance.h @@ -0,0 +1,56 @@ +#ifndef CHANCE_H +#define CHANCE_H + +typedef enum { + CHANCE_ADVANCE, // Move to a specific index or category + CHANCE_EARN, // Add money + CHANCE_SPEND, // Subtract money + CHANCE_SPEND_EACH_PLAYER, // Pay every other player + CHANCE_BACK, // Move back X spaces + CHANCE_JAIL, // Go to jail + CHANCE_JAIL_FREE, // Keep a get out of jail free card + CHANCE_REPAIRS // Pay per house/hotel +} ChanceType; + +// Sentinel values for "nearest" targets +#define TARGET_NEAREST_UTILITY -1 +#define TARGET_NEAREST_RAILROAD -2 + +typedef struct { + const char* description; + ChanceType type; + int value; // Used for index, amount, or spaces back + int value2; // Specifically for repairs (Hotel cost) +} ChanceCard; + +#define CHANCE_DECK_SIZE 16 + +static const ChanceCard CHANCE_DECK[CHANCE_DECK_SIZE] = { + {"Advance to Go", CHANCE_ADVANCE, 0, 0}, + {"Advance to Illinois Ave", CHANCE_ADVANCE, 24, 0}, + {"Advance to nearest Utility. If unowned, buy it. If owned, pay 10x dice.", CHANCE_ADVANCE, TARGET_NEAREST_UTILITY, 0}, + {"Advance to nearest Railroad. If unowned, buy it. If owned, pay twice rental.", CHANCE_ADVANCE, TARGET_NEAREST_RAILROAD, 0}, + {"Advance to St. Charles Place – if you pass Go, collect $200", CHANCE_ADVANCE, 11, 0}, + {"Bank pays you dividend of $50", CHANCE_EARN, 50, 0}, + {"Get out of Jail free", CHANCE_JAIL_FREE, 0, 0}, + {"Go back 3 spaces", CHANCE_BACK, 3, 0}, + {"Go directly to Jail", CHANCE_JAIL, 0, 0}, + {"Make general repairs - $25 per house, $100 per hotel", CHANCE_REPAIRS, 25, 100}, + {"Pay poor tax of $15", CHANCE_SPEND, 15, 0}, + {"Take a trip to Reading Railroad", CHANCE_ADVANCE, 5, 0}, + {"Take a walk on the Boardwalk", CHANCE_ADVANCE, 39, 0}, + {"Elected chairman of the board – pay each player $50", CHANCE_SPEND_EACH_PLAYER, 50, 0}, + {"Your building loan matures – collect $150", CHANCE_EARN, 150, 0}, + {"You have won a crossword competition - collect $100", CHANCE_EARN, 100, 0} +}; + +#endif + +/* +Implementation Tips for your main.c: +Handling "Nearest": When you draw a card with TARGET_NEAREST_UTILITY, use a loop starting from the player's current position to find the next index where MONOPOLY_BOARD[i].type == TILE_UTILITY. + +The Repair Card: When the type is CHANCE_REPAIRS, you'll need to look at the player's owned properties and count how many houses/hotels they have (once you implement the building logic), then multiply by value (25) and value2 (100). + +Shuffling: In C, you can shuffle the deck at the start of the game using a Fisher-Yates shuffle algorithm on an array of indices [0...15]. +*/ \ No newline at end of file diff --git a/games/monopoly/community_chest.h b/games/monopoly/community_chest.h new file mode 100644 index 0000000..2727d57 --- /dev/null +++ b/games/monopoly/community_chest.h @@ -0,0 +1,43 @@ +#ifndef COMMUNITY_H +#define COMMUNITY_H + +typedef enum { + COMMUNITY_ADVANCE, + COMMUNITY_EARN, + COMMUNITY_SPEND, + COMMUNITY_EARN_EACH_PLAYER, + COMMUNITY_JAIL, + COMMUNITY_JAIL_FREE, + COMMUNITY_REPAIRS +} CommunityType; + +typedef struct { + const char* description; + CommunityType type; + int value; // Amount or position + int value2; // Second repair cost (Hotels) +} CommunityCard; + +#define COMMUNITY_DECK_SIZE 17 + +static const CommunityCard COMMUNITY_DECK[COMMUNITY_DECK_SIZE] = { + {"Advance to Go (Collect $200)", COMMUNITY_ADVANCE, 0, 0}, + {"Bank error in your favor – collect $75", COMMUNITY_EARN, 75, 0}, + {"Doctor's fees – Pay $50", COMMUNITY_SPEND, 50, 0}, + {"Get out of jail free", COMMUNITY_JAIL_FREE, 0, 0}, + {"Go to jail – Do not pass Go, do not collect $200", COMMUNITY_JAIL, 0, 0}, + {"It is your birthday - Collect $10 from each player", COMMUNITY_EARN_EACH_PLAYER, 10, 0}, + {"Grand Opera Night – collect $50 from every player", COMMUNITY_EARN_EACH_PLAYER, 50, 0}, + {"Income Tax refund – collect $20", COMMUNITY_EARN, 20, 0}, + {"Life Insurance Matures – collect $100", COMMUNITY_EARN, 100, 0}, + {"Pay Hospital Fees of $100", COMMUNITY_SPEND, 100, 0}, + {"Receive $25 Consultancy Fee", COMMUNITY_EARN, 25, 0}, + {"Pay School Fees of $50", COMMUNITY_SPEND, 50, 0}, + {"Street repairs – $40 per house, $115 per hotel", COMMUNITY_REPAIRS, 40, 115}, + {"Won second prize in a beauty contest – collect $10", COMMUNITY_EARN, 10, 0}, + {"You inherit $100", COMMUNITY_EARN, 100, 0}, + {"From sale of stock you get $50", COMMUNITY_EARN, 50, 0}, + {"Holiday Fund matures - Receive $100", COMMUNITY_EARN, 100, 0} +}; + +#endif \ No newline at end of file diff --git a/games/monopoly/monopoly.cpp b/games/monopoly/monopoly.cpp new file mode 100644 index 0000000..08ebbd8 --- /dev/null +++ b/games/monopoly/monopoly.cpp @@ -0,0 +1,632 @@ +#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); +void handle_buy(Player *p, bool has_rolled); +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_jail_options(Player *p); +bool attempt_jail_escape(Player *p); + +// Global State +Player players[MAX_PLAYERS]; +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)); + + // 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"); + + 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"); + + 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): "); + + 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"); + } + } + + 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; + } + + 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; + } + } + 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); + } + } +} + +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; + 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) % 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; + } + + 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; + } +} + +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 new file mode 100644 index 0000000..ed26ad5 --- /dev/null +++ b/games/monopoly/monopoly_board.h @@ -0,0 +1,76 @@ +#ifndef MONOPOLY_BOARD_H +#include +#define MONOPOLY_BOARD_H + +#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 +} 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 +} BoardTile; + +#define BOARD_SIZE 40 + +static const BoardTile MONOPOLY_BOARD[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}, + {"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}, + {"Income Tax", TILE_TAX, false, 200, NULL, {0}, {0}, 0}, + {"Reading Railroad", TILE_RAILROAD, false, 200, NULL, {0}, {9, 1, 4}, 0}, + {"Oriental 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}, + {"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}, + {"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}, + {"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}, + {"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}, + {"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}, + {"Illnois 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}, + {"Atlatic Avenue", TILE_PROPERTY, false, 260, "#fef200", {22, 110, 330, 800, 975, 1150}, {6, 1, 3}, 150}, + {"Ventura 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}, + {"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}, + {"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}, + {"Shortline", 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}, + {"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} +}; + +#endif \ No newline at end of file diff --git a/games/monopoly/monopoly_game.cpp b/games/monopoly/monopoly_game.cpp new file mode 100644 index 0000000..5c2f4ed --- /dev/null +++ b/games/monopoly/monopoly_game.cpp @@ -0,0 +1,218 @@ +// ============================================================================ +// MONOPOLY GAME IMPLEMENTATION (for custom console) +// ============================================================================ +// Refactored from console version to use Game interface and rendering/input system + +#include "monopoly_game.h" +#ifdef __cplusplus +extern "C" { +#endif +#include "player.h" +#ifdef __cplusplus +} +#endif +#include +#include + + +// --- Constructor --- +MonopolyGame::MonopolyGame(uint16_t width, uint16_t height, LowLevelRenderer* renderer, LowLevelGUI* gui, InputManager* input_manager) + : Game(width, height, renderer, gui, input_manager) { + players_count = 2; + current_player_idx = 0; + has_rolled = false; + double_rolls = 0; + just_sent_to_jail = false; +} + +// --- Initialize game state --- +void MonopolyGame::init() { + // 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 = 0; + // TODO: Reset all board state, property ownership, etc. +} + +// --- Handle input events (minimal: roll, buy, end turn) --- +bool MonopolyGame::update(const InputEvent& event) { + Player* p = &players[current_player_idx]; + bool needs_redraw = false; + + // If modal is open, any button closes it + if (show_property_modal) { + if (event.type == INPUT_BUTTON_0 || event.type == INPUT_BUTTON_1) { + show_property_modal = false; + modal_property_index = -1; + needs_redraw = true; + } + return needs_redraw; + } + + switch (event.type) { + case INPUT_BUTTON_0: // Cycle options + selected_action = (selected_action + 1) % ACTION_COUNT; + needs_redraw = true; + break; + case INPUT_BUTTON_1: // Select option + switch (selected_action) { + case 0: // Roll + if (!has_rolled && !p->is_in_jail) { + int dice1 = (rand() % 6) + 1; + int dice2 = (rand() % 6) + 1; + int total = dice1 + dice2; + int old_pos = p->position; + p->position = (p->position + total) % BOARD_SIZE; + if (p->position < old_pos) p->balance += 200; + has_rolled = true; + needs_redraw = true; + // Show property modal if landed on property/railroad/utility + const BoardTile* landed = &MONOPOLY_BOARD[p->position]; + if (landed->type == TILE_PROPERTY || landed->type == TILE_RAILROAD || landed->type == TILE_UTILITY) { + show_property_modal = true; + modal_property_index = p->position; + } + // TODO: Handle doubles, jail, landing effects + } + break; + case 1: // Buy + if (has_rolled) { + const BoardTile* tile = &MONOPOLY_BOARD[p->position]; + if ((tile->type == TILE_PROPERTY || tile->type == TILE_RAILROAD || tile->type == TILE_UTILITY) && p->balance >= tile->cost) { + bool owned = false; + for (int i = 0; i < p->property_count; ++i) { + if (p->properties_owned[i] == p->position) owned = true; + } + if (!owned) { + p->balance -= tile->cost; + p->properties_owned[p->property_count++] = p->position; + needs_redraw = true; + } + } + // TODO: Check for ownership by other players + } + break; + case 2: // End Turn + if (has_rolled) { + current_player_idx = (current_player_idx + 1) % players_count; + has_rolled = false; + double_rolls = 0; + just_sent_to_jail = false; + needs_redraw = true; + } + break; + } + break; + default: + break; + } + return needs_redraw; +} + +// --- Draw game state (minimal: player info, current tile, actions) --- +void MonopolyGame::draw() { + Player* p = &players[current_player_idx]; + const BoardTile* tile = &MONOPOLY_BOARD[p->position]; + + // Title + renderer->draw_string_scaled(10, 10, "Monopoly (Minimal)", 2); + + // --- Player Stats (Right Side) --- + int stats_x = width - 180; + int y = 20; + char buf[128]; + // Name (Big) + renderer->draw_string_scaled(stats_x, y, p->name, 2); + y += 40; + // Money + snprintf(buf, sizeof(buf), "$%d", p->balance); + renderer->draw_string_scaled(stats_x, y, buf, 2); + y += 30; + // Properties + int prop_count = 0; + for (int i = 0; i < p->property_count; ++i) { + int prop_idx = p->properties_owned[i]; + if (prop_idx >= 0 && MONOPOLY_BOARD[prop_idx].type == TILE_PROPERTY) prop_count++; + } + snprintf(buf, sizeof(buf), "Properties: %d", prop_count); + renderer->draw_string_scaled(stats_x, y, buf, 2); + y += 30; + // Monopoly count + int monopoly_count = 0; + // For each group, check if player owns all properties in group + for (int group = 1; group <= 8; ++group) { + int group_total = 0, group_owned = 0; + for (int i = 0; i < BOARD_SIZE; ++i) { + if (MONOPOLY_BOARD[i].type == TILE_PROPERTY && MONOPOLY_BOARD[i].group[0] == group) { + group_total++; + for (int j = 0; j < p->property_count; ++j) { + if (p->properties_owned[j] == i) group_owned++; + } + } + } + if (group_total > 0 && group_total == group_owned) monopoly_count++; + } + snprintf(buf, sizeof(buf), "Monopolies: %d", monopoly_count); + renderer->draw_string_scaled(stats_x, y, buf, 2); + + // --- Main Info (Left Side) --- + snprintf(buf, sizeof(buf), "Player: %s ($%d)", p->name, p->balance); + renderer->draw_string_scaled(10, 30, buf, 2); + + snprintf(buf, sizeof(buf), "Location: %s", tile->name); + renderer->draw_string_scaled(10, 50, buf, 2); + + // Show property modal window if needed + if (show_property_modal && modal_property_index >= 0) { + const BoardTile* mprop = &MONOPOLY_BOARD[modal_property_index]; + int win_w = 320, win_h = 180; + int win_x = (width - win_w) / 2, win_y = (height - win_h) / 2; + LowLevelWindow* win = gui->draw_new_window(win_x, win_y, win_w, win_h, "Property Info"); + // gui->current_font = renderer->get_current_font(); + int py = win_y + 30; + char pbuf[128]; + if (mprop->type == TILE_PROPERTY) { + snprintf(pbuf, sizeof(pbuf), "%s", mprop->name); + renderer->draw_string_scaled(win_x + 20, py, pbuf, 2); + py += 30; + snprintf(pbuf, sizeof(pbuf), "Color: %s", mprop->color ? mprop->color : "-"); + renderer->draw_string_scaled(win_x + 20, py, pbuf, 2); + py += 25; + snprintf(pbuf, sizeof(pbuf), "Cost: $%d", mprop->cost); + renderer->draw_string_scaled(win_x + 20, py, pbuf, 2); + py += 25; + snprintf(pbuf, sizeof(pbuf), "Rent: $%d", mprop->rent[0]); + renderer->draw_string_scaled(win_x + 20, py, pbuf, 2); + py += 25; + snprintf(pbuf, sizeof(pbuf), "House Cost: $%d", mprop->house_cost); + renderer->draw_string_scaled(win_x + 20, py, pbuf, 2); + } else if (mprop->type == TILE_RAILROAD || mprop->type == TILE_UTILITY) { + snprintf(pbuf, sizeof(pbuf), "%s", mprop->name); + renderer->draw_string_scaled(win_x + 20, py, pbuf, 2); + py += 30; + snprintf(pbuf, sizeof(pbuf), "Cost: $%d", mprop->cost); + renderer->draw_string_scaled(win_x + 20, py, pbuf, 2); + } + renderer->draw_string_scaled(win_x + 20, win_y + win_h - 40, "Press any button...", 2); + return; + } + + // Draw action menu (highlight selected) + const char* actions[ACTION_COUNT] = {"Roll Dice", "Buy Property", "End Turn"}; + for (int i = 0; i < ACTION_COUNT; ++i) { + int y = height - 80 + i * 20; + snprintf(buf, sizeof(buf), "%s%s", (i == selected_action) ? "> " : " ", actions[i]); + renderer->draw_string_scaled(10, y, buf, 2); + } + + // renderer->draw_string_scaled(10, height - 20, "BTN0: Next Option BTN1: Select", 2); + + // TODO: Draw board, all players, property ownership, jail, chance, etc. + // TODO: Add win/lose/game over conditions + // TODO: Add touch support, more UI, etc. +} diff --git a/games/monopoly/monopoly_game.h b/games/monopoly/monopoly_game.h new file mode 100644 index 0000000..82fede8 --- /dev/null +++ b/games/monopoly/monopoly_game.h @@ -0,0 +1,47 @@ +// ============================================================================ +// MONOPOLY GAME HEADER +// ============================================================================ +// Concrete implementation of the Game interface for Monopoly + +#ifndef MONOPOLY_GAME_H +#define MONOPOLY_GAME_H + +#include "../../lib/game.h" +#include "player.h" +#include "monopoly_board.h" +#include "chance.h" +#include "community_chest.h" + +/** + * @brief Monopoly game implementation for the custom console + * + * - Button/touch input for actions + * - Board and player state + * - Rendering and input handling + */ +class MonopolyGame : public Game { +public: + MonopolyGame(uint16_t width, uint16_t height, LowLevelRenderer* renderer, LowLevelGUI* gui, InputManager* input_manager); + void init() override; + bool update(const InputEvent& event) override; + void draw() override; + +private: + // Game state and helpers + Player players[MAX_PLAYERS]; + int players_count; + int current_player_idx; + bool has_rolled; + int double_rolls; + bool just_sent_to_jail; + + // UI selection state + int selected_action; // 0: Roll, 1: Buy, 2: End Turn + static constexpr int ACTION_COUNT = 3; + + // Modal property window state + bool show_property_modal = false; + int modal_property_index = -1; +}; + +#endif // MONOPOLY_GAME_H diff --git a/games/monopoly/player.c b/games/monopoly/player.c new file mode 100644 index 0000000..8c8ea63 --- /dev/null +++ b/games/monopoly/player.c @@ -0,0 +1,18 @@ +// Implementation of init_player for Monopoly +#include "player.h" + +void init_player(Player* p, int id, const char* name, const char* token) { + p->id = id; + p->name = name; + p->token = token; + p->balance = 1500; // Standard starting cash + p->position = 0; // Start at "Go" + p->is_in_jail = false; + p->jail_turns = 0; + p->jail_free_cards = 0; + p->is_bankrupt = false; + p->property_count = 0; + for(int i = 0; i < MAX_PROPERTIES; i++) { + p->properties_owned[i] = -1; // -1 indicates an empty slot + } +} \ No newline at end of file diff --git a/games/monopoly/player.h b/games/monopoly/player.h new file mode 100644 index 0000000..94c179f --- /dev/null +++ b/games/monopoly/player.h @@ -0,0 +1,41 @@ +#ifndef PLAYER_H +#define PLAYER_H + +#include +#include "monopoly_board.h" + +#define MAX_PLAYERS 4 +#define MAX_PROPERTIES 40 + +typedef struct { + int id; // Player number (0-3) + const char* name; // Player display name + int balance; // Current cash + int position; // Current index on the MONOPOLY_BOARD (0-39) + + // Status flags + bool is_in_jail; + int jail_turns; // How many turns they've spent in jail + int jail_free_cards; // Get Out of Jail Free cards + bool is_bankrupt; + + // Inventory + int properties_owned[MAX_PROPERTIES]; // Indices of owned tiles + int property_count; + + // Piece representation + const char* token; // e.g., "Top Hat", "Iron" +} Player; + +/** + * Initializes a player with starting values + */ +#ifdef __cplusplus +extern "C" { +#endif +void init_player(Player* p, int id, const char* name, const char* token); +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file