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 8f23672..f5c37c6 100755 Binary files a/emulator/basic1_emulator and b/emulator/basic1_emulator differ diff --git a/emulator/main.cpp b/emulator/main.cpp index 6eb4000..b7ec687 100644 --- a/emulator/main.cpp +++ b/emulator/main.cpp @@ -5,6 +5,7 @@ #include "game_launcher.h" #include "../games/demo_game.h" #include "../games/tic_tac_toe.h" +#include "../games/monopoly/monopoly_game.h" #include "input_manager.h" #include #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