# Base on PropertyTycoonCardData.xlsx from canvas
# script based on Eric's provided flowchart photo (flowchart.drawio.png)
# will add more comment later to reference for which part of code is based on which part of the flowchart
import pygame
import random
from src.Loadexcel import load_property_data
from src.Sound_Manager import sound_manager
from src.Ai_Player_Logic import EasyAIPlayer
pot_luck_cards = [
{
"text": "You inherit £200",
"action": lambda player, bank, free_parking: (
player["money"] + 200,
bank - 200,
free_parking,
),
},
{
"text": "You have won 2nd prize in a beauty contest, collect £50",
"action": lambda player, bank, free_parking: (
player["money"] + 50,
bank - 50,
free_parking,
),
},
{
"text": "You are up the creek with no paddle - go back to the Old Creek",
"action": lambda player, bank, free_parking: (2, bank, free_parking),
},
{
"text": "Student loan refund. Collect £20",
"action": lambda player, bank, free_parking: (
player["money"] + 20,
bank - 20,
free_parking,
),
},
{
"text": "Bank error in your favour. Collect £200",
"action": lambda player, bank, free_parking: (
player["money"] + 200,
bank - 200,
free_parking,
),
},
{
"text": "Pay bill for text books of £100",
"action": lambda player, bank, free_parking, board=None: board.handle_payment_to_bank(
player, 100
),
},
{
"text": "Mega late night taxi bill pay £50",
"action": lambda player, bank, free_parking, board=None: board.handle_payment_to_bank(
player, 50
),
},
{
"text": "Advance to go",
"action": lambda player, bank, free_parking: (1, bank, free_parking),
},
{
"text": "From sale of Bitcoin you get £50",
"action": lambda player, bank, free_parking: (
player["money"] + 50,
bank - 50,
free_parking,
),
},
{
"text": "Bitcoin assets fall - pay off Bitcoin short fall",
"action": lambda player, bank, free_parking, board=None: board.handle_payment_to_bank(
player, 50
),
},
{
"text": "Pay a £10 fine or take opportunity knocks",
"action": lambda player, bank, free_parking, board=None: board.handle_payment_to_bank(
player, 10, True
),
},
{
"text": "Pay insurance fee of £50",
"action": lambda player, bank, free_parking, board=None: board.handle_payment_to_bank(
player, 50, True
),
},
{
"text": "Savings bond matures, collect £100",
"action": lambda player, bank, free_parking: (
player["money"] + 100,
bank - 100,
free_parking,
),
},
{
"text": "Go to jail. Do not pass GO, do not collect £200",
"action": lambda player, bank, free_parking: (11, bank, free_parking),
},
{
"text": "Received interest on shares of £25",
"action": lambda player, bank, free_parking: (
player["money"] + 25,
bank - 25,
free_parking,
),
},
{
"text": "It's your birthday. Collect £10 from each player",
"action": lambda player, bank, free_parking, board=None: board.handle_birthday_collection(
player
),
},
{
"text": "Get out of jail free",
"action": lambda player, bank, free_parking: (
player["position"],
bank,
free_parking,
),
},
]
opportunity_knocks_cards = [
{
"text": "Bank pays you divided of £50",
"action": lambda player, bank, free_parking: (
player["money"] + 50,
bank - 50,
free_parking,
),
},
{
"text": "You have won a lip sync battle. Collect £100",
"action": lambda player, bank, free_parking: (
player["money"] + 100,
bank - 100,
free_parking,
),
},
{
"text": "Advance to Turing Heights",
"action": lambda player, bank, free_parking: (40, bank, free_parking),
},
{
"text": "Advance to Han Xin Gardens",
"action": lambda player, bank, free_parking: (
25,
bank + (200 if player["position"] > 25 else 0),
free_parking,
),
},
{
"text": "Fined £15 for speeding",
"action": lambda player, bank, free_parking, board=None: board.handle_payment_to_bank(
player, 15, True
),
},
{
"text": "Pay university fees of £150",
"action": lambda player, bank, free_parking, board=None: board.handle_payment_to_bank(
player, 150
),
},
{
"text": "Take a trip to Hove station",
"action": lambda player, bank, free_parking: (
16,
bank + (200 if player["position"] > 16 else 0),
free_parking,
),
},
{
"text": "Loan matures, collect £150",
"action": lambda player, bank, free_parking: (
player["money"] + 150,
bank - 150,
free_parking,
),
},
{
"text": "You are assessed for repairs, £40/house, £115/hotel",
"action": lambda player, bank, free_parking, board=None: board.handle_repair_assessment(
player, 40, 115
),
},
{
"text": "Advance to GO",
"action": lambda player, bank, free_parking: (1, bank, free_parking),
},
{
"text": "You are assessed for repairs, £25/house, £100/hotel",
"action": lambda player, bank, free_parking, board=None: board.handle_repair_assessment(
player, 25, 100
),
},
{
"text": "Go back 3 spaces",
"action": lambda player, bank, free_parking: (
((player["position"] - 1 - 3) % 40) + 1,
bank,
free_parking,
),
},
{
"text": "Advance to Skywalker Drive",
"action": lambda player, bank, free_parking: (
12,
bank + (200 if player["position"] > 12 else 0),
free_parking,
),
},
{
"text": "Go to jail. Do not pass GO, do not collect £200",
"action": lambda player, bank, free_parking: (11, bank, free_parking),
},
{
"text": "Drunk in charge of a hoverboard. Fine £30",
"action": lambda player, bank, free_parking, board=None: board.handle_payment_to_bank(
player, 30, True
),
},
{
"text": "Get out of jail free",
"action": lambda player, bank, free_parking: (
player["position"],
bank,
free_parking,
),
},
]
[docs]
class GameLogic:
BANK_LIMIT = 50000
MAX_HOUSES_PER_PROPERTY = 4
MAX_HOTELS_PER_PROPERTY = 1
GAME_TOKENS = ["boot", "smartphone", "ship", "hatstand", "cat", "iron"]
def __init__(self):
self.players = []
self.bank_money = self.BANK_LIMIT
self.free_parking_fund = 0
self.current_player_index = 0
self.properties = load_property_data()
self.is_going_to_jail = False
self.last_dice_roll = None
self.doubles_count = 0
self.message_queue = []
self.bankrupted_players = []
self.voluntary_exits = []
self.jail_free_cards = {}
self.completed_circuits = {}
self.available_tokens = self.GAME_TOKENS.copy()
self.pot_luck_cards = pot_luck_cards.copy()
self.opportunity_knocks_cards = opportunity_knocks_cards.copy()
random.shuffle(self.pot_luck_cards)
random.shuffle(self.opportunity_knocks_cards)
self.ai_difficulty = "easy"
self.ai_player = EasyAIPlayer()
self.game = None
[docs]
def validate_bank_transaction(self, amount):
if amount > self.bank_money:
raise ValueError("Bank does not have sufficient funds")
return True
[docs]
def pay_from_bank(self, player, amount):
if self.validate_bank_transaction(amount):
self.bank_money -= amount
player["money"] += amount
return True
return False
[docs]
def pay_to_bank(self, player, amount):
if player["money"] >= amount:
player["money"] -= amount
self.bank_money += amount
return True
return False
[docs]
def game_start(self):
self.properties = load_property_data()
if self.properties is None:
return False
return True
[docs]
def add_player(self, player):
if len(self.players) < 5:
player_name = player.name if hasattr(player, "name") else player
player_is_ai = (
getattr(player, "is_ai", False) if hasattr(player, "is_ai") else False
)
token_index = (
getattr(player, "player_number", 1)
if hasattr(player, "player_number")
else 1
)
token = self.GAME_TOKENS[
min(token_index - 1, len(self.GAME_TOKENS) - 1) % len(self.GAME_TOKENS)
]
new_player = {
"name": player_name,
"money": 1500,
"position": 1,
"is_ai": player_is_ai,
"properties": [],
"token": token,
}
self.players.append(new_player)
self.completed_circuits[player_name] = 0
return True, f"Added player {player_name} with {token} token"
return False, "Maximum number of players reached"
[docs]
def advance_to_next_player(self):
if not self.players:
return
self.current_player_index = (self.current_player_index + 1) % len(self.players)
current_player = self.players[self.current_player_index]
while current_player.get("exited", False) or current_player.get(
"bankrupt", False
):
self.current_player_index = (self.current_player_index + 1) % len(
self.players
)
current_player = self.players[self.current_player_index]
return current_player
[docs]
def play_turn(self):
if not self.players:
return None, None
current_player = self.players[self.current_player_index]
while current_player.get("exited", False) or current_player.get(
"bankrupt", False
):
self.current_player_index = (self.current_player_index + 1) % len(
self.players
)
current_player = self.players[self.current_player_index]
self.is_going_to_jail = False
dice1 = random.randint(1, 6)
dice2 = random.randint(1, 6)
self.last_dice_roll = (dice1, dice2)
if current_player.get("in_jail", False):
success, message = self.try_leave_jail(current_player, dice1, dice2)
if not success:
self.advance_to_next_player()
return dice1, dice2
if dice1 == dice2:
sound_manager.play_sound("roll_doubles")
self.doubles_count += 1
if self.doubles_count == 3:
print(
f"{current_player['name']} rolled three doubles and goes to jail!"
)
self.handle_jail(current_player)
self.advance_to_next_player()
return dice1, dice2
else:
self.doubles_count = 0
new_pos = current_player["position"] + dice1 + dice2
current_player["position"] = new_pos % 40
if current_player["position"] == 0:
current_player["position"] = 40
if new_pos >= 40 and not self.is_going_to_jail:
current_player["money"] += 200
self.bank_money -= 200
self.completed_circuits[current_player["name"]] += 1
print(f"{current_player['name']} collected £200 for passing GO")
result, message = self.handle_space(current_player)
if result == "bankrupt":
print(f"{current_player['name']} is bankrupt!")
elif result == "jail":
self.doubles_count = 0
if not dice1 == dice2 or self.is_going_to_jail:
self.advance_to_next_player()
self.doubles_count = 0
return dice1, dice2
[docs]
def add_message(self, message):
self.message_queue.append(message)
print(f"[GAME] {message}")
[docs]
def handle_space(self, player):
position = str(player["position"])
if position not in self.properties:
return None, None
space = self.properties[position]
space_type = space.get("type", "")
if position == "20":
sound_manager.play_sound("land_free_parking")
return "free_parking", None
if space_type == "special":
return None, None
elif space_type == "tax":
player["money"] -= space["amount"]
self.bank_money += space["amount"]
self.add_message(
f"{player['name']} paid £{space['amount']} {space['name']}"
)
if hasattr(self, "game") and self.game:
self.game.show_tax_popup(player, space["name"], space["amount"])
return None, None
if not space.get("owner"):
if player.get("in_jail", False):
return None, "Cannot buy property while in jail"
if not space.get("can_be_bought", False):
return None, None
if self.completed_circuits.get(player["name"], 0) < 1:
return None, None
self.add_message(
f"Would you like to buy {space['name']} for £{space['price']}?"
)
return "can_buy", None
elif space["owner"] != player["name"]:
owner = next((p for p in self.players if p["name"] == space["owner"]), None)
if owner.get("in_jail", False):
self.add_message(f"{owner['name']} is in jail and cannot collect rent")
return None, None
rent = self.calculate_space_rent(space, player)
if player["money"] >= rent:
player["money"] -= rent
owner["money"] += rent
message = f"{player['name']} paid £{rent} rent to {owner['name']}"
self.add_message(message)
if hasattr(self, "game") and self.game:
self.game.show_rent_popup(player, owner, space["name"], rent)
else:
message = f"{player['name']} went bankrupt!"
self.add_message(message)
self.handle_bankruptcy(player)
return "bankrupt", message
return None, None
[docs]
def handle_jail(self, player):
player["position"] = 11
player["in_jail"] = True
player["jail_turns"] = 0
self.is_going_to_jail = True
self.doubles_count = 0
self.add_message("Go to jail. Do not pass GO, do not collect £200")
return "Go to jail. Do not pass GO, do not collect £200"
[docs]
def try_leave_jail(self, player, dice1, dice2):
if not player.get("in_jail", False):
return True, None
if "jail_turns" not in player:
player["jail_turns"] = 0
if self.jail_free_cards.get(player["name"], 0) > 0 and (
player.get("is_ai", False)
or self.game
and self.game.get_jail_choice(player) == "card"
):
self.jail_free_cards[player["name"]] -= 1
player["in_jail"] = False
player["jail_turns"] = 0
return (
True,
f"{player['name']} uses Get Out of Jail Free card and left jail!",
)
if dice1 == dice2:
player["in_jail"] = False
player["jail_turns"] = 0
self.doubles_count = 0
return (
True,
f"{player['name']} rolled doubles ({dice1},{dice2}) and left jail!",
)
if player["money"] >= 50 and (
player.get("is_ai", False)
or self.game
and self.game.get_jail_choice(player) == "pay"
):
player["money"] -= 50
self.free_parking_fund += 50
player["in_jail"] = False
player["jail_turns"] = 0
return True, f"{player['name']} paid £50 and left jail"
player["jail_turns"] += 1
if player["jail_turns"] >= 3:
if player["money"] >= 50:
player["money"] -= 50
self.free_parking_fund += 50
player["in_jail"] = False
player["jail_turns"] = 0
return True, f"{player['name']} paid £50 after 3 turns and left jail"
else:
self.handle_bankruptcy(player)
player["in_jail"] = False
player["jail_turns"] = 0
return (
True,
f"{player['name']} couldn't pay jail fine and left jail bankrupt",
)
return False, f"{player['name']} stays in jail (turn {player['jail_turns']}/3)"
[docs]
def check_game_over(self):
if self.game_mode == "full":
active_players = [p for p in self.players if p["money"] > 0]
if len(active_players) <= 1:
return True, active_players[0] if active_players else None
elif self.game_mode == "abridged":
min_rounds = min(self.rounds_completed.values())
if min_rounds > 0 and all(
rounds == min_rounds for rounds in self.rounds_completed.values()
):
assets = {}
for player in self.players:
total = player["money"]
for prop in self.properties.values():
if prop.get("owner") == player["name"]:
total += prop["price"]
if not prop.get("is_mortgaged", False):
total += prop.get("houses", 0) * prop.get(
"house_cost", 0
)
assets[player["name"]] = total
winner = max(assets.items(), key=lambda x: x[1])[0]
return True, winner
return False, None
[docs]
def calculate_space_rent(self, space, player):
if space.get("is_mortgaged", False):
return 0
if "Station" in space["name"]:
station_count = sum(
1
for prop in self.properties.values()
if "Station" in prop.get("name", "")
and prop.get("owner") == space["owner"]
)
return 25 * (2 ** (station_count - 1))
elif space["name"] in ["Tesla Power Co", "Edison Water"]:
utility_count = sum(
1
for prop in self.properties.values()
if prop.get("name") in ["Tesla Power Co", "Edison Water"]
and prop.get("owner") == space["owner"]
)
dice_total = sum(self.last_dice_roll) if self.last_dice_roll else 7
return dice_total * (10 if utility_count > 1 else 4)
base_rent = space.get("rent", 0)
if space.get("houses", 0) > 0:
house_rents = space.get("house_costs", [])
house_index = min(space["houses"] - 1, len(house_rents) - 1)
return house_rents[house_index]
color_group = space.get("group")
if color_group:
all_owned = all(
p.get("owner") == space["owner"]
for p in self.properties.values()
if p.get("group") == color_group
)
if all_owned:
return base_rent * 2
return base_rent
[docs]
def handle_card_draw(self, player, card_type):
cards = (
self.pot_luck_cards
if card_type == "Pot Luck"
else self.opportunity_knocks_cards
)
card = cards.pop(0)
message = card["text"]
self.add_message(message)
if hasattr(self, "game") and self.game and not player.get("is_ai", False):
self.game.show_card_popup(card_type, message)
result = None
if message == "Get out of jail free":
self.jail_free_cards[player["name"]] = (
self.jail_free_cards.get(player["name"], 0) + 1
)
result = None
else:
try:
action_result, new_bank, new_parking = card["action"](
player, self.bank_money, self.free_parking_fund, self
)
except TypeError:
action_result, new_bank, new_parking = card["action"](
player, self.bank_money, self.free_parking_fund
)
if isinstance(action_result, int) and action_result <= 40:
player["position"] = action_result
if action_result < player["position"] and action_result != 11:
player["money"] += 200
self.bank_money -= 200
self.add_message("Collected £200 for passing GO")
result = "moved"
else:
print(
f"Updating player money from {player['money']} to {action_result}"
)
player["money"] = action_result
self.bank_money = new_bank
self.free_parking_fund = new_parking
cards.append(card)
return result, message
[docs]
def calculate_repair_cost(self, player, house_cost, hotel_cost):
total_cost = 0
for prop in self.properties.values():
if prop.get("owner") == player["name"]:
houses = prop.get("houses", 0)
if houses == 5:
total_cost += hotel_cost
else:
total_cost += houses * house_cost
return total_cost
[docs]
def check_property_group_completion(self, player_name):
groups = {}
for prop in self.properties.values():
if prop.get("group") and prop.get("owner") == player_name:
groups[prop["group"]] = groups.get(prop["group"], 0) + 1
group_totals = {}
for prop in self.properties.values():
if prop.get("group"):
group = prop["group"]
group_totals[group] = group_totals.get(group, 0) + 1
completed_groups = []
for group, count in groups.items():
if group in group_totals and count == group_totals[group]:
completed_groups.append(group)
self.add_message("🎊 MONOPOLY! 🎊")
self.add_message(f"{player_name} completed the {group} set!")
if group not in ["Utilities", "Stations"]:
self.add_message("Houses can now be built on these properties!")
return len(completed_groups) > 0
[docs]
def buy_property(self, player):
if player.get("in_jail", False):
self.add_message(f"{player['name']} cannot buy property while in jail")
return False
if self.completed_circuits.get(player["name"], 0) < 1:
self.add_message(f"{player['name']} must pass GO before buying property")
return False
position = str(player["position"])
property_data = self.properties[position]
price = property_data["price"]
if player["money"] >= price:
if self.validate_bank_transaction(price):
player["money"] -= price
self.bank_money += price
property_data["owner"] = player["name"]
self.check_property_group_completion(player["name"])
return True
return False
[docs]
def auction_property(self, position):
position = str(position)
print(f"\n=== Starting Auction for Property at Position {position} ===")
if position not in self.properties:
self.add_message(f"Error: Invalid property position {position}")
print(f"Error: Invalid property position {position}")
return "auction_completed"
property_data = self.properties[position]
print(f"Property: {property_data['name']}")
if property_data.get("type", "") not in ["property", "station", "utility"]:
self.add_message(f"Error: Cannot auction non-purchasable property")
print(f"Error: Cannot auction non-purchasable property")
return "auction_completed"
eligible_players = [
p
for p in self.players
if not p.get("in_jail", False)
and not p.get("exited", False)
and p["money"] >= (property_data["price"] // 2)
]
print(f"Eligible players: {len(eligible_players)}")
for player in eligible_players:
print(f"- {player['name']} (Money: £{player['money']})")
if not eligible_players:
property_data["owner"] = None
print("No eligible players for auction - property remains unsold")
return "auction_completed"
starting_bid = property_data["price"] // 2
print(f"Starting bid: £{starting_bid}")
self.current_auction = {
"property": property_data,
"property_position": position,
"active_players": eligible_players.copy(),
"passed_players": set(),
"highest_bidder": None,
"current_bid": 0,
"minimum_bid": starting_bid,
"current_bidder_index": 0,
"start_time": pygame.time.get_ticks(),
"duration": 30000,
"completed": False,
"message": f"Auction started for {property_data['name']} - Starting bid: £{starting_bid}",
}
print(f"Auction initialized with {len(eligible_players)} active players")
print(f"First bidder: {self.current_auction['active_players'][0]['name']}")
self.add_message(f"\n🔨 AUCTION: {property_data['name']}")
self.add_message(f"Starting bid: £{starting_bid}")
sound_manager.play_sound("auction_start")
return "auction_in_progress"
[docs]
def process_auction_bid(self, player, bid_amount):
print(f"\n=== Processing Bid: {player['name']} ===")
print(f"Attempted bid amount: £{bid_amount}")
if not hasattr(self, "current_auction") or self.current_auction["completed"]:
print("Error: No active auction")
return False, "No active auction in progress"
if player["name"] in self.current_auction["passed_players"]:
print(f"Error: {player['name']} has already passed")
return False, "You have already passed on this auction"
if bid_amount < self.current_auction["minimum_bid"]:
print(
f"Error: Bid too low (minimum: £{self.current_auction['minimum_bid']})"
)
return False, f"Bid must be at least £{self.current_auction['minimum_bid']}"
if bid_amount > player["money"]:
print(f"Error: Insufficient funds (available: £{player['money']})")
return False, "You don't have enough money"
current_time = pygame.time.get_ticks()
remaining_time = max(
0,
self.current_auction["start_time"]
+ self.current_auction["duration"]
- current_time,
)
if remaining_time <= 0:
print("Error: Bid timeout")
self.current_auction["passed_players"].add(player["name"])
return (
False,
f"{player['name']} took too long to bid - automatically passed",
)
print("Bid accepted!")
print(f"- Previous high bid: £{self.current_auction['current_bid']}")
print(f"- New high bid: £{bid_amount}")
print(
f"- Previous leader: {self.current_auction['highest_bidder']['name'] if self.current_auction['highest_bidder'] else 'None'}"
)
print(f"- New leader: {player['name']}")
self.current_auction["current_bid"] = bid_amount
self.current_auction["highest_bidder"] = player
self.current_auction["minimum_bid"] = bid_amount + 10
self.add_message(f"{player['name']} bids £{bid_amount}")
self.move_to_next_bidder()
return True, f"{player['name']} bids £{bid_amount}"
[docs]
def process_auction_pass(self, player):
print(f"\n=== Processing Pass: {player['name']} ===")
if not hasattr(self, "current_auction") or self.current_auction["completed"]:
print("Error: No active auction")
return False, "No active auction in progress"
if player["name"] in self.current_auction["passed_players"]:
print(f"Error: {player['name']} has already passed")
return False, "You have already passed on this auction"
print(f"{player['name']} passes on bidding")
self.current_auction["passed_players"].add(player["name"])
self.add_message(f"{player['name']} passes")
active_bidders = [
p
for p in self.current_auction["active_players"]
if p["name"] not in self.current_auction["passed_players"]
]
print(f"Active bidders remaining: {len(active_bidders)}")
for bidder in active_bidders:
print(f"- {bidder['name']}")
self.move_to_next_bidder()
return True, f"{player['name']} passes"
[docs]
def check_auction_end(self):
if not hasattr(self, "current_auction") or not self.current_auction:
return None
if self.current_auction.get("completed", False):
print("Auction is marked as completed - processing outcome")
property_data = self.current_auction["property"]
highest_bidder = self.current_auction["highest_bidder"]
if highest_bidder:
bid_amount = self.current_auction["current_bid"]
highest_bidder["money"] -= bid_amount
self.bank_money += bid_amount
self.buy_property_after_auction(highest_bidder, property_data)
result_message = f"{highest_bidder['name']} bought {property_data['name']} for £{bid_amount}"
print(f"Auction completed - {result_message}")
self.add_message(result_message)
else:
result_message = (
f"No one bid on {property_data['name']}, it remains unsold"
)
print(f"Auction completed - {result_message}")
self.add_message(result_message)
return "auction_completed"
active_bidders = [
p
for p in self.current_auction["active_players"]
if p["name"] not in self.current_auction["passed_players"]
]
if (
not active_bidders
or len(active_bidders) <= 1
and self.current_auction["highest_bidder"]
):
print(
"All players have passed or only highest bidder remains - auction is complete"
)
self.current_auction["completed"] = True
return self.check_auction_end()
current_time = pygame.time.get_ticks()
if (
current_time - self.current_auction["start_time"]
> self.current_auction["duration"]
):
print("Auction timed out - marking as complete")
self.current_auction["completed"] = True
return self.check_auction_end()
return None
[docs]
def move_to_next_bidder(self):
if not hasattr(self, "current_auction"):
print("Error: No active auction")
return
print(f"\n=== Moving to Next Bidder ===")
active_players = [
p
for p in self.current_auction["active_players"]
if p["name"] not in self.current_auction["passed_players"]
and p["money"] >= self.current_auction["minimum_bid"]
]
print(f"Active players remaining: {len(active_players)}")
for player in active_players:
print(f"- {player['name']} (Money: £{player['money']})")
if len(active_players) <= 1:
print("Only one or zero active players remaining")
if self.current_auction["highest_bidder"]:
print(
f"Auction ending - {self.current_auction['highest_bidder']['name']} wins with bid of £{self.current_auction['current_bid']}"
)
self.current_auction["completed"] = True
return
elif (
len(active_players) == 1 and not self.current_auction["highest_bidder"]
):
print(
f"One active player remaining ({active_players[0]['name']}) with no current bids - giving them a chance to bid"
)
for i, player in enumerate(self.current_auction["active_players"]):
if player["name"] == active_players[0]["name"]:
self.current_auction["current_bidder_index"] = i
break
else:
print("No active players or highest bidder - auction will end")
self.current_auction["completed"] = True
return
else:
current_index = self.current_auction["current_bidder_index"]
original_index = current_index
print(f"Current bidder index: {current_index}")
print(
f"Current bidder: {self.current_auction['active_players'][current_index]['name']}"
)
found_next_bidder = False
while not found_next_bidder:
next_index = (current_index + 1) % len(
self.current_auction["active_players"]
)
next_player = self.current_auction["active_players"][next_index]
if (
next_player["name"] not in self.current_auction["passed_players"]
and next_player["money"] >= self.current_auction["minimum_bid"]
):
found_next_bidder = True
self.current_auction["current_bidder_index"] = next_index
print(f"Next bidder: {next_player['name']} (index {next_index})")
break
current_index = next_index
if current_index == original_index:
print("Cycled through all players - no eligible bidders found")
self.current_auction["completed"] = True
return
self.current_auction["start_time"] = pygame.time.get_ticks()
print(f"Timer reset for next bidder")
[docs]
def is_game_over(self):
active_players = [
p for p in self.players if p["money"] > 0 and not p.get("exited", False)
]
return len(active_players) <= 1
[docs]
def get_winner(self):
if self.is_game_over():
active_players = [
p for p in self.players if p["money"] > 0 and not p.get("exited", False)
]
if active_players:
return active_players[0]["name"]
return None
[docs]
def remove_player(self, player_name, voluntary=False):
player = next((p for p in self.players if p["name"] == player_name), None)
if player:
for prop in self.properties.values():
if prop.get("owner") == player_name:
prop["owner"] = None
if "houses" in prop:
prop["houses"] = 0
if voluntary:
player["exited"] = True
self.voluntary_exits.append(player_name)
else:
player["bankrupt"] = True
self.bankrupted_players.append(player_name)
if len(self.players) > 0:
self.current_player_index = self.current_player_index % len(
self.players
)
return True
return False
[docs]
def auction(self, property_data):
self.message_queue = [
msg
for msg in self.message_queue
if not msg.startswith("Would you like to buy")
]
auction_message = f"🔨 AUCTION: {property_data['name']} is up for auction!"
self.add_message(auction_message)
eligible_players = [
p
for p in self.players
if not p.get("in_jail", False)
and not p.get("exited", False)
and p["money"] >= (property_data["price"] // 2)
]
if not eligible_players:
self.add_message(
f"No eligible players for auction. {property_data['name']} remains unsold."
)
property_data["owner"] = None
return
winning_bid_info = self.placeBids(eligible_players, property_data)
if not winning_bid_info:
self.add_message(f"{property_data['name']} remains unsold")
property_data["owner"] = None
else:
winner, bid = winning_bid_info
if winner["money"] >= bid:
winner["money"] -= bid
self.bank_money += bid
property_data["owner"] = winner["name"]
self.add_message(
f"🎊 {winner['name']} won {property_data['name']} for £{bid}"
)
self.check_property_group_completion(winner["name"])
else:
self.add_message(
f"Error: {winner['name']} cannot afford the winning bid"
)
property_data["owner"] = None
[docs]
def placeBids(self, player_list, property_data):
print("\n=== Property Auction Debug ===")
print("Starting auction for", property_data['name'])
print("Property position:", property_data.get('position', 'Unknown'))
print("Starting price: £", property_data['price'] // 2)
current_minimum = property_data["price"] // 2
active_players = player_list[:]
current_bids = {}
round_count = 1
while len(active_players) > 1:
print("\n=== Auction Round", round_count, "===")
print("Active players:", [p['name'] for p in active_players])
print("Current minimum bid: £", current_minimum)
round_bids = {}
for player in active_players[:]:
print("\nPlayer", player['name'], "'s turn")
print("Available money: £", player['money'])
if player.get("in_jail", False):
print(player['name'], "is in jail - skipping")
active_players.remove(player)
continue
bid = None
if player.get("is_ai", False):
bid = self.get_ai_bid(player, current_minimum, property_data)
if bid:
print(f"AI {player['name']} bids £{bid}")
self.add_message(f"AI {player['name']} bids £{bid}")
else:
print(f"AI {player['name']} passes")
self.add_message(f"AI {player['name']} passes")
active_players.remove(player)
else:
current_bids[player["name"]] = current_minimum
continue
if bid and bid >= current_minimum and bid <= player["money"]:
round_bids[player["name"]] = bid
current_bids[player["name"]] = bid
else:
if player in active_players:
active_players.remove(player)
if not round_bids and not any(
p for p in active_players if not p.get("is_ai", False)
):
print("\nNo valid bids this round")
break
if round_bids:
highest_bid = max(round_bids.values())
highest_bidders = [
p
for p in active_players
if round_bids.get(p["name"], 0) == highest_bid
]
print("\nRound Results:")
print(f"Highest bid: £{highest_bid}")
print(f"Highest bidders: {[p['name'] for p in highest_bidders]}")
active_players = highest_bidders
current_minimum = highest_bid + 10
round_count += 1
winner = None
final_bid = 0
if len(active_players) == 1:
winner = active_players[0]
final_bid = current_bids.get(winner["name"], current_minimum)
if winner:
print("\n=== Auction Complete ===")
print(f"Winner: {winner['name']}")
print(f"Winning bid: £{final_bid}")
return winner, final_bid
print("\nAuction ended with no winner")
return None
[docs]
def get_ai_bid(self, player, current_minimum, property_data):
print("\n=== AI Bid Evaluation ===")
print(f"AI Player: {player['name']}")
print(f"Property: {property_data['name']}")
print(f"Current minimum: £{current_minimum}")
print(f"Available money: £{player['money']}")
if player["money"] < current_minimum:
print("DECISION: Cannot bid - insufficient funds")
return None
base_value = property_data["price"]
value_multiplier = 1.0
print("Base value assessment:")
print(f"- Property price: £{base_value}")
if "group" in property_data:
owned_in_group = sum(
1
for p in self.properties.values()
if p.get("group") == property_data["group"]
and p.get("owner") == player["name"]
)
total_in_group = sum(
1
for p in self.properties.values()
if p.get("group") == property_data["group"]
)
print("Group analysis:")
print(f"- Group: {property_data['group']}")
print(f"- Owned in group: {owned_in_group}/{total_in_group}")
if owned_in_group > 0:
group_bonus = 0.3 * (owned_in_group / total_in_group)
value_multiplier += group_bonus
print(f"- Adding group bonus: +{group_bonus:.2f}x")
if "Station" in property_data["name"]:
owned_stations = sum(
1
for p in self.properties.values()
if "Station" in p.get("name", "") and p.get("owner") == player["name"]
)
station_bonus = 0.25 * owned_stations
value_multiplier += station_bonus
print("Station analysis:")
print(f"- Owned stations: {owned_stations}")
print(f"- Adding station bonus: +{station_bonus:.2f}x")
elif property_data["name"] in ["Tesla Power Co", "Edison Water"]:
owned_utilities = sum(
1
for p in self.properties.values()
if p.get("name") in ["Tesla Power Co", "Edison Water"]
and p.get("owner") == player["name"]
)
print("Utility analysis:")
print(f"- Owned utilities: {owned_utilities}")
if owned_utilities > 0:
value_multiplier += 0.5
print("- Adding utility bonus: +0.5x")
perceived_value = base_value * value_multiplier
print("\nValue calculation:")
print(f"- Base value: £{base_value}")
print(f"- Final multiplier: {value_multiplier:.2f}x")
print(f"- Perceived value: £{perceived_value}")
max_bid = min(player["money"], perceived_value)
print(f"Maximum possible bid: £{max_bid}")
if max_bid <= current_minimum:
print("DECISION: Pass - maximum bid below minimum")
return None
import random
bid_headroom = max_bid - current_minimum
increment = min(50, max(10, int(bid_headroom * 0.2)))
bid = current_minimum + random.randint(10, increment)
bid = min(bid, max_bid)
print("Bid calculation:")
print(f"- Bid headroom: £{bid_headroom}")
print(f"- Chosen increment: £{increment}")
print(f"- Initial bid: £{bid}")
if bid > perceived_value * 0.8:
risky_bid_chance = random.random()
print("\nRisk assessment:")
print(f"- Bid (£{bid}) is above 80% of perceived value")
print(f"- Risk check: {risky_bid_chance:.2f} (will pass if < 0.3)")
if risky_bid_chance < 0.3:
print("DECISION: Pass due to risk assessment")
return None
print(f"FINAL DECISION: Bid £{bid}")
return bid
[docs]
def get_human_bid(self, player, current_minimum, property_data):
import time
self.add_message(
f"Player {player['name']}, would you like to bid? (Yes/No) Current minimum bid: £{current_minimum}"
)
start_time = time.time()
while True:
try:
response = input(
f"{player['name']}, enter 'Yes' or 'No' (30s timeout): "
)
except Exception as e:
self.add_message(f"Error: {e}")
return None
if time.time() - start_time > 30:
self.add_message(
"Timeout: Player did not respond in time, skipping bid"
)
return None
if response.strip().lower() == "no":
return None
elif response.strip().lower() == "yes":
try:
bid_input = input(
f"{player['name']}, what is your bid? (Enter amount): "
)
if time.time() - start_time > 30:
self.add_message(
"Timeout: Player took too long to enter bid amount"
)
return None
bid = int(bid_input)
if bid < current_minimum:
self.add_message(f"Bid must be at least £{current_minimum}")
start_time = time.time()
continue
if bid > player["money"]:
self.add_message(f"Insufficient funds")
return None
return bid
except Exception as e:
self.add_message(f"Error processing bid: {e}")
return None
else:
self.add_message("Please enter 'Yes' or 'No'")
start_time = time.time()
continue
[docs]
def buy_property_after_auction(self, player, property_data):
if self.completed_circuits.get(player["name"], 0) < 1:
self.add_message(f"{player['name']} must pass GO before buying property")
return False
position = str(property_data.get("position", ""))
if position in self.properties:
self.properties[position]["owner"] = player["name"]
if "properties" not in player:
player["properties"] = []
player["properties"].append(property_data["name"])
self.add_message(f"{player['name']} now owns {property_data['name']}.")
self.check_property_group_completion(player["name"])
return True
else:
print(f"Warning: Property position {position} not found in properties list")
return False
[docs]
def handle_bankruptcy(self, player):
total_liquidated = 0
property_list = []
for prop in self.properties.values():
if prop.get("owner") == player["name"]:
value = prop["price"]
if "houses" in prop and prop["houses"] > 0:
house_costs = prop.get("house_costs", [])
if house_costs and isinstance(house_costs, list):
house_value = sum(house_costs[: prop["houses"]])
else:
house_value = 0
value += house_value
property_list.append((prop["name"], value))
total_liquidated += value
if property_list:
self.add_message(f"🏦 Liquidating {player['name']}'s properties:")
for prop_name, value in property_list:
self.add_message(f"- {prop_name}: £{value}")
self.add_message(f"Total liquidated: £{total_liquidated}")
for prop in self.properties.values():
if prop.get("owner") == player["name"]:
prop["owner"] = None
if "houses" in prop:
prop["houses"] = 0
player["bankrupt"] = True
self.bankrupted_players.append(player["name"])
return True
[docs]
def can_build_house(self, property_data, player):
if not hasattr(self, "_last_build_check_time"):
self._last_build_check_time = 0
self._build_check_debug_enabled = False
current_time = pygame.time.get_ticks()
if (
self._build_check_debug_enabled
and current_time - self._last_build_check_time > 1000
):
self._last_build_check_time = current_time
print(f"\n=== DEBUG: can_build_house CHECK ===")
print(f"Property: {property_data.get('name', 'Unknown')}")
print(f"Player: {player['name'] if player else 'None'}")
print(f"Property position: {property_data.get('position', 'Unknown')}")
if property_data.get("is_mortgaged", False):
if (
self._build_check_debug_enabled
and current_time - self._last_build_check_time <= 1000
):
print("Cannot build - property is mortgaged")
return False, "Cannot build on mortgaged property"
color_group = property_data.get("group")
if (
self._build_check_debug_enabled
and current_time - self._last_build_check_time <= 1000
):
print(f"Color group: {color_group}")
if not color_group:
if (
self._build_check_debug_enabled
and current_time - self._last_build_check_time <= 1000
):
print("Cannot build - not a valid property group")
return False, "Cannot build houses on this type of property"
color_group_properties = [
p for p in self.properties.values() if p.get("group") == color_group
]
if (
self._build_check_debug_enabled
and current_time - self._last_build_check_time <= 1000
):
print(f"Total properties in group: {len(color_group_properties)}")
for prop in color_group_properties:
prop_owner = prop.get("owner", "None")
print(
f" - {prop.get('name', 'Unknown')} (Position: {prop.get('position', 'Unknown')}, Owner: {prop_owner})"
)
for prop in color_group_properties:
if prop.get("owner") != player["name"]:
if (
self._build_check_debug_enabled
and current_time - self._last_build_check_time <= 1000
):
print(
f"Cannot build - {prop.get('name', 'Unknown')} is owned by {prop.get('owner', 'None')}, not {player['name']}"
)
return False, f"Must own all {color_group} properties to build"
current_houses = property_data.get("houses", 0)
if (
self._build_check_debug_enabled
and current_time - self._last_build_check_time <= 1000
):
print(f"Current houses on property: {current_houses}")
for prop in color_group_properties:
if prop != property_data:
other_houses = prop.get("houses", 0)
if (
self._build_check_debug_enabled
and current_time - self._last_build_check_time <= 1000
):
print(
f" - {prop.get('name', 'Unknown')} has {other_houses} houses"
)
if current_houses + 1 > other_houses + 1:
if (
self._build_check_debug_enabled
and current_time - self._last_build_check_time <= 1000
):
print(
f"Cannot build - houses must be built evenly. This property would have {current_houses + 1} houses while {prop.get('name', 'Unknown')} has {other_houses}"
)
return False, "Must build houses evenly across properties"
if current_houses >= 5:
if (
self._build_check_debug_enabled
and current_time - self._last_build_check_time <= 1000
):
print("Cannot build - maximum development reached")
return False, "Maximum development reached"
if (
self._build_check_debug_enabled
and current_time - self._last_build_check_time <= 1000
):
print("Can build house: YES")
return True, None
[docs]
def can_build_hotel(self, property_data, player):
if not hasattr(self, "_last_build_check_time"):
self._last_build_check_time = 0
self._build_check_debug_enabled = False
current_time = pygame.time.get_ticks()
if (
self._build_check_debug_enabled
and current_time - self._last_build_check_time > 1000
):
self._last_build_check_time = current_time
print(f"\n=== DEBUG: can_build_hotel CHECK ===")
print(f"Property: {property_data.get('name', 'Unknown')}")
print(f"Player: {player['name'] if player else 'None'}")
is_group_complete = self.check_property_group_completion(player["name"])
if not is_group_complete:
if (
self._build_check_debug_enabled
and current_time - self._last_build_check_time <= 1000
):
print(
"Cannot build hotel - player does not own all properties in the group"
)
return False, "You must own all properties in the color group"
color_group = [
p
for p in self.properties.values()
if p.get("color") == property_data.get("color")
and p.get("owner") == player["name"]
]
current_houses = property_data.get("houses", 0)
if (
self._build_check_debug_enabled
and current_time - self._last_build_check_time <= 1000
):
print(f"Current houses on property: {current_houses}")
if current_houses != 4:
if (
self._build_check_debug_enabled
and current_time - self._last_build_check_time <= 1000
):
print("Cannot build hotel - property must have exactly 4 houses")
return False, "Must have 4 houses before building a hotel"
for prop in color_group:
if prop != property_data:
other_houses = prop.get("houses", 0)
if (
self._build_check_debug_enabled
and current_time - self._last_build_check_time <= 1000
):
print(
f" - {prop.get('name', 'Unknown')} has {other_houses} houses"
)
if other_houses < 4:
if (
self._build_check_debug_enabled
and current_time - self._last_build_check_time <= 1000
):
print(
"Cannot build hotel - all properties must have at least 4 houses"
)
return (
False,
"All properties in the set must have at least 4 houses before building a hotel",
)
if (
self._build_check_debug_enabled
and current_time - self._last_build_check_time <= 1000
):
print("Can build hotel: YES")
return True, None
[docs]
def build_house(self, property_data, player):
can_build, error = self.can_build_house(property_data, player)
if not can_build:
self.add_message(error)
return False
current_houses = property_data.get("houses", 0)
if current_houses >= self.MAX_HOUSES_PER_PROPERTY:
self.add_message("Maximum number of houses already built")
return False
house_cost = property_data.get("house_cost", 0)
if player["money"] >= house_cost:
if self.validate_bank_transaction(house_cost):
player["money"] -= house_cost
self.bank_money += house_cost
property_data["houses"] = current_houses + 1
self.add_message(
f"{player['name']} built a house on {property_data['name']}"
)
return True
self.add_message("Not enough money to build a house")
return False
[docs]
def build_hotel(self, property_data, player):
can_build, error = self.can_build_hotel(property_data, player)
if not can_build:
self.add_message(error)
return False
hotel_cost = property_data.get("house_cost", 0)
if player["money"] >= hotel_cost:
if self.validate_bank_transaction(hotel_cost):
player["money"] -= hotel_cost
self.bank_money += hotel_cost
property_data["houses"] = 5
self.add_message(
f"{player['name']} built a hotel on {property_data['name']}"
)
return True
self.add_message("Not enough money to build a hotel")
return False
[docs]
def sell_house(self, property_data, player):
current_houses = property_data.get("houses", 0)
if current_houses == 0:
self.add_message("No houses to sell")
return False
color_group = [
p
for p in self.properties.values()
if p.get("color") == property_data.get("color")
and p.get("owner") == player["name"]
]
for prop in color_group:
if prop != property_data:
other_houses = prop.get("houses", 0)
if current_houses - 1 < other_houses - 1:
self.add_message(
"Cannot have more than 1 house difference between properties in a set"
)
return False
house_cost = property_data.get("house_cost", 0)
player["money"] += house_cost // 2
self.bank_money -= house_cost // 2
property_data["houses"] = current_houses - 1
self.add_message(f"{player['name']} sold a house from {property_data['name']}")
return True
[docs]
def sell_hotel(self, property_data, player):
if property_data.get("houses", 0) != 5:
self.add_message("No hotel to sell")
return False
hotel_cost = property_data.get("house_cost", 0) * 5
player["money"] += hotel_cost // 2
self.bank_money -= hotel_cost // 2
property_data["houses"] = 4
self.add_message(f"{player['name']} sold a hotel from {property_data['name']}")
return True
[docs]
def mortgage_property(self, property_data, player):
if property_data.get("is_mortgaged", False):
self.add_message(f"{property_data['name']} is already mortgaged")
return False
if property_data.get("houses", 0) > 0:
self.add_message("Cannot mortgage property with houses/hotels")
return False
mortgage_value = property_data["price"] // 2
self.pay_from_bank(player, mortgage_value)
property_data["is_mortgaged"] = True
self.add_message(
f"{player['name']} mortgaged {property_data['name']} for £{mortgage_value}"
)
sound_manager.play_sound("mortgage")
return True
[docs]
def unmortgage_property(self, property_data, player):
if not property_data.get("is_mortgaged", False):
self.add_message(f"{property_data['name']} is not mortgaged")
return False
unmortgage_cost = (property_data["price"] // 2) * 1.1
if player["money"] >= unmortgage_cost:
self.pay_to_bank(player, unmortgage_cost)
property_data["is_mortgaged"] = False
self.add_message(
f"{player['name']} unmortgaged {property_data['name']} for £{unmortgage_cost}"
)
sound_manager.play_sound("unmortgage")
return True
else:
self.add_message(f"Not enough money to unmortgage {property_data['name']}")
return False
[docs]
def handle_rent_payment(self, landing_player, property_data):
if property_data.get("is_mortgaged", False):
self.add_message(f"Property is mortgaged - no rent due")
return True
owner = next(
(p for p in self.players if p["name"] == property_data["owner"]), None
)
if not owner or owner.get("in_jail", False):
self.add_message(f"Owner is in jail - no rent collected")
return True
rent = self.calculate_space_rent(property_data, landing_player)
if landing_player["money"] >= rent:
landing_player["money"] -= rent
owner["money"] += rent
self.add_message(
f"{landing_player['name']} paid £{rent} rent to {owner['name']}"
)
if hasattr(self, "game") and self.game:
self.game.show_rent_popup(
landing_player, owner, property_data["name"], rent
)
return True
else:
self.handle_bankruptcy(landing_player)
return False
[docs]
def calculate_house_difference(self, color_group_properties):
house_counts = [p.get("houses", 0) for p in color_group_properties]
return max(house_counts) - min(house_counts)
[docs]
def handle_ai_turn(self, player):
if not player.get("is_ai", False):
return None
if player.get("in_jail", False):
jail_decision = self.ai_player.get_jail_decision(
player, *self.last_dice_roll
)
if (
jail_decision == "use_card"
and self.jail_free_cards.get(player["name"], 0) > 0
):
self.jail_free_cards[player["name"]] -= 1
player["in_jail"] = False
player["jail_turns"] = 0
elif jail_decision == "pay" and player["money"] >= 50:
player["money"] -= 50
self.free_parking_fund += 50
player["in_jail"] = False
player["jail_turns"] = 0
owned_properties = [
prop
for prop in self.properties.values()
if prop.get("owner") == player["name"]
]
development_priorities = self.ai_player.get_development_priority(
owned_properties
)
for property_data, _ in development_priorities:
if self.ai_player.should_develop_property(
property_data, player["money"], owned_properties
):
if property_data.get("houses", 0) == 4:
self.build_hotel(property_data, player)
else:
self.build_house(property_data, player)
if player["money"] < 200:
for prop in owned_properties:
if self.ai_player.should_mortgage_property(prop, player["money"]):
self.mortgage_property(prop, player)
elif player["money"] > 500:
mortgaged_properties = [
p for p in owned_properties if p.get("is_mortgaged", True)
]
for prop in mortgaged_properties:
if self.ai_player.should_unmortgage_property(prop, player["money"]):
self.unmortgage_property(prop, player)
return None
[docs]
def process_ai_property_purchase(self, player, property_data):
if not player.get("is_ai", False):
return False
owned_properties = [
prop
for prop in self.properties.values()
if prop.get("owner") == player["name"]
]
if self.ai_player.should_buy_property(
property_data, player["money"], owned_properties
):
if property_data["price"] <= player["money"]:
self.pay_to_bank(player, property_data["price"])
property_data["owner"] = player["name"]
self.add_message(
f"{player['name']} buys {property_data['name']} for £{property_data['price']}"
)
return True
return False
[docs]
def get_ai_auction_bid(self, player, property_data, current_bid):
if not player.get("is_ai", False):
return None
try:
print(f"\n=== AI Auction Bid Logic ===")
print(f"AI Player: {player['name']}")
print(f"Property: {property_data['name']}")
print(f"Current bid: £{current_bid}")
print(f"Player money: £{player['money']}")
minimum_bid = max(current_bid + 10, property_data["price"] // 2)
if player["money"] < minimum_bid:
print(f"AI can't afford minimum bid of £{minimum_bid}")
return None
owned_properties = [
prop
for prop in self.properties.values()
if prop.get("owner") == player["name"]
]
bid_amount = self.ai_player.make_auction_bid(
property_data, current_bid, player["money"], owned_properties
)
if (
bid_amount
and bid_amount > current_bid
and bid_amount <= player["money"]
):
bid_amount = max(bid_amount, current_bid + 10)
print(f"AI {player['name']} decided to bid £{bid_amount}")
return bid_amount
else:
print(f"AI {player['name']} decided to pass")
return None
except Exception as e:
print(f"Error in AI auction bidding: {e}")
return None
[docs]
def handle_ai_bankruptcy_prevention(self, player, amount_needed):
owned_properties = [
prop
for prop in self.properties.values()
if prop.get("owner") == player["name"]
]
if not owned_properties:
return False
for prop in owned_properties:
while prop.get("houses", 0) > 0 and player["money"] < amount_needed:
if prop.get("houses", 0) == 5:
self.sell_hotel(prop, player)
self.add_message(
f"{player['name']} sold hotel on {prop['name']} to raise funds"
)
else:
self.sell_house(prop, player)
self.add_message(
f"{player['name']} sold house on {prop['name']} to raise funds"
)
if player["money"] >= amount_needed:
return True
for prop in owned_properties:
if not prop.get("is_mortgaged", False) and player["money"] < amount_needed:
if prop.get("houses", 0) == 0:
self.mortgage_property(prop, player)
self.add_message(
f"{player['name']} mortgaged {prop['name']} to raise funds"
)
if player["money"] >= amount_needed:
return True
return player["money"] >= amount_needed
[docs]
def handle_buy_decision(self, player, decision):
position = str(player["position"])
property_data = self.properties[position]
if player.get("in_jail", False):
return "Cannot buy property while in jail"
if property_data.get("owner") is not None:
return "Property is already owned"
if property_data.get("is_mortgaged", False):
return "Property is mortgaged and cannot be purchased"
print("\n=== Property Purchase Decision ===")
print(f"Property: {property_data['name']}")
print(f"Player: {player['name']}")
print(f"Decision: {decision}")
if player.get("is_ai", False):
if self.process_ai_property_purchase(player, property_data):
self.add_message(f"{player['name']} bought {property_data['name']}")
return "purchase_completed"
else:
self.add_message(f"\n{property_data['name']} goes to auction!")
self.auction_property(position)
return "auction_started"
if decision == "buy":
if self.buy_property(player):
self.add_message(f"{player['name']} bought {property_data['name']}")
return "purchase_completed"
else:
self.add_message(
f"\nInsufficient funds - {property_data['name']} goes to auction!"
)
self.auction_property(position)
return "auction_started"
elif decision == "auction":
self.add_message(
f"\n{player['name']} declined to buy {property_data['name']}"
)
self.auction_property(position)
return "auction_started"
else:
return "Invalid decision. Must be 'buy' or 'auction'"
[docs]
def handle_birthday_collection(self, birthday_player):
total_collected = 0
self.add_message(
f"\n🎂 {birthday_player['name']}'s Birthday! Each player must pay £10"
)
for player in self.players:
if player["name"] == birthday_player["name"]:
continue
self.add_message(f"\n{player['name']} needs to pay £10")
if player["money"] >= 10:
player["money"] -= 10
total_collected += 10
self.add_message(f"{player['name']} paid £10")
else:
needed_amount = 10 - player["money"]
self.add_message(f"{player['name']} needs to raise £{needed_amount}")
if self.handle_ai_bankruptcy_prevention(player, needed_amount):
if player["money"] >= 10:
player["money"] -= 10
total_collected += 10
self.add_message(f"{player['name']} raised funds and paid £10")
else:
self.add_message(f"{player['name']} cannot pay and goes bankrupt")
self.handle_bankruptcy(player)
if player["money"] > 0:
total_collected += player["money"]
player["money"] = 0
birthday_player["money"] += total_collected
self.add_message(
f"\n{birthday_player['name']} collected £{total_collected} in total!"
)
return birthday_player["money"], self.bank_money, self.free_parking_fund
[docs]
def handle_payment_to_bank(self, player, amount, to_free_parking=False):
self.add_message(f"\n{player['name']} needs to pay £{amount}")
if player["money"] >= amount:
player["money"] -= amount
if to_free_parking:
self.free_parking_fund += amount
else:
self.bank_money += amount
self.add_message(f"{player['name']} paid £{amount}")
return player["money"], self.bank_money, self.free_parking_fund
else:
needed_amount = amount - player["money"]
self.add_message(f"{player['name']} needs to raise £{needed_amount}")
if self.handle_ai_bankruptcy_prevention(player, needed_amount):
if player["money"] >= amount:
player["money"] -= amount
if to_free_parking:
self.free_parking_fund += amount
else:
self.bank_money += amount
self.add_message(
f"{player['name']} raised funds and paid £{amount}"
)
else:
self.add_message(f"{player['name']} cannot pay and goes bankrupt")
remaining_money = player["money"]
self.handle_bankruptcy(player)
if remaining_money > 0:
if to_free_parking:
self.free_parking_fund += remaining_money
else:
self.bank_money += remaining_money
return player["money"], self.bank_money, self.free_parking_fund
[docs]
def handle_repair_assessment(self, player, house_cost, hotel_cost):
repair_cost = self.calculate_repair_cost(player, house_cost, hotel_cost)
if repair_cost > 0:
self.add_message(
f"\n{player['name']} is assessed £{repair_cost} for repairs (£{house_cost}/house, £{hotel_cost}/hotel)"
)
return self.handle_payment_to_bank(player, repair_cost)
else:
self.add_message(
f"{player['name']} has no houses or hotels - no repair costs"
)
return player["money"], self.bank_money, self.free_parking_fund