Source code for src.Ai_Player_Logic

# Property Tycoon ai_player_logic.py
# Contains the classes for the AI players, such as the AI player logic, the AI player strategy, and the AI player actions.

import random
from src.Property import Property
from src.Player import Player


[docs] class EasyAIPlayer: def __init__(self, difficulty="easy"): self.difficulty = "easy" self.strategy = { "easy": { "max_bid_multiplier": 0.8, "development_threshold": 0.7, "jail_stay_threshold": 0.3, "mortgage_threshold": 0.2, } }
[docs] def get_group_properties(self, color_group, board_properties): return [ prop for prop in board_properties if hasattr(prop, "group") and prop.group == color_group ]
[docs] def check_group_ownership(self, color_group, board_properties, player_name): if not color_group: return False group_properties = [ p for p in board_properties if hasattr(p, "group") and p.group == color_group ] return all(p.owner == player_name for p in group_properties)
[docs] def can_build_house(self, property, color_group_properties): if property.houses >= 4: return False min_houses = min(prop.houses for prop in color_group_properties) return property.houses <= min_houses
def get_property_value(self, property_data, ai_player, board_properties): print("=== AI Property Value Calculation Debug ===") print( f"Evaluating property: {property_data.name if hasattr(property_data, 'name') else 'Unknown'}" ) base_value = property_data.price multiplier = 1.0 print(f"Base value: £{base_value}") if hasattr(property_data, "group"): owned_in_group = sum( 1 for p in board_properties if hasattr(p, "group") and p.group == property_data.group and p.owner == ai_player ) total_in_group = sum( 1 for p in board_properties if hasattr(p, "group") and p.group == property_data.group ) print("Color 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) multiplier += group_bonus print(f"- Adding group ownership bonus: +{group_bonus:.2f}x") if property_data.is_station: owned_stations = sum( 1 for p in board_properties if p.is_station and p.owner == ai_player ) station_bonus = 0.25 * owned_stations multiplier += station_bonus print("Station analysis:") print(f"- Owned stations: {owned_stations}") print(f"- Adding station bonus: +{station_bonus:.2f}x") elif property_data.is_utility: owned_utilities = sum( 1 for p in board_properties if p.is_utility and p.owner == ai_player ) print("Utility analysis:") print(f"- Owned utilities: {owned_utilities}") if owned_utilities > 0: multiplier += 0.5 print("- Adding utility bonus: +0.5x") final_value = base_value * multiplier print("Final calculations:") print(f"- Total multiplier: {multiplier:.2f}x") print(f"- Final perceived value: £{final_value:.2f}") return final_value
[docs] def handle_turn( self, ai_player, current_location, board_properties, is_first_round=False ): actions = {"bought_property": False, "built_house": False, "paid_rent": False} if current_location.name == "GO": return actions elif isinstance(current_location, Property): if current_location.owner and current_location.owner != ai_player: rent = current_location.calculate_rent( None, current_location.owner.properties ) if ai_player.money >= rent: actions["paid_rent"] = True elif not current_location.owner: if random.random() < 0.7 and ai_player.money >= current_location.price: success = ai_player.buy_property(current_location) actions["bought_property"] = success if ( success and not is_first_round and hasattr(current_location, "group") ): color_group = current_location.group if self.check_group_ownership( color_group, board_properties, ai_player.name ): if random.random() < 0.5: group_properties = self.get_group_properties( color_group, board_properties ) if self.can_build_house( current_location, group_properties ): house_cost = current_location.price / 2 if ai_player.money >= house_cost: current_location.houses += 1 ai_player.pay(house_cost) actions["built_house"] = True elif current_location.name == "JAIL": pass elif current_location.name == "FREE PARKING": pass return actions
[docs] def should_use_get_out_of_jail_card(self): return True
[docs] def should_pay_jail_fine(self, ai_player): return ai_player.money >= 250
[docs] def should_mortgage_property(self, ai_player, required_money): if ai_player.money >= required_money: return [] to_mortgage = [] need_amount = required_money - ai_player.money unset_properties = [ prop for prop in ai_player.properties if not self.check_group_ownership( prop.group, ai_player.properties, ai_player.name ) ] for prop in sorted(unset_properties, key=lambda x: x.price): if need_amount <= 0: break if not prop.is_mortgaged: to_mortgage.append(prop) need_amount -= prop.price / 2 if need_amount > 0: set_properties = [ prop for prop in ai_player.properties if self.check_group_ownership( prop.group, ai_player.properties, ai_player.name ) ] for prop in sorted(set_properties, key=lambda x: x.price): if need_amount <= 0: break if not prop.is_mortgaged: to_mortgage.append(prop) need_amount -= prop.price / 2 return to_mortgage
[docs] def handle_ai_turn(self, ai_player, board_properties): current_location_type = self.get_location_type( ai_player.position, board_properties ) property_name = self.get_property_name(ai_player.position, board_properties) if current_location_type == "Go": return True elif current_location_type == "property": property_data = next( (p for p in board_properties if p.name == property_name), None ) if not property_data: return True if property_data.owner and property_data.owner != ai_player: return True elif not property_data.owner: if random.random() < 0.7 and ai_player.money >= property_data.price: return True return False elif current_location_type == "lucky_area": return True elif current_location_type == "jail": return True return True
[docs] def get_location_type(self, position, board_properties): property_data = next( (p for p in board_properties if p.position == position), None ) if not property_data: if position == 1: return "Go" elif position == 11: return "jail" elif position == 21: return "free_parking" elif position in [3, 18, 34]: return "lucky_area" elif position in [8, 23, 37]: return "lucky_area" return "other" return "property"
[docs] def get_property_name(self, position, board_properties): property_data = next( (p for p in board_properties if p.position == position), None ) return property_data.name if property_data else None
[docs] def get_auction_bid( self, current_minimum, property_data, ai_player, board_properties ): if hasattr(self, "last_bid_property"): if self.last_bid_property != property_data["name"]: self.last_bid_amount = 0 self.last_bid_property = property_data["name"] else: self.last_bid_amount = 0 self.last_bid_property = property_data["name"] print("\n=== AI Auction Bid Logic Debug ===") print(f"AI Player: {ai_player}") print(f"Current minimum bid: £{current_minimum}") print(f"Available money: £{ai_player.money}") print(f"Previous bid on this property: £{self.last_bid_amount}") if ai_player.money < current_minimum: print("DECISION: Cannot bid - insufficient funds") return None perceived_value = self.get_property_value( property_data, ai_player, board_properties ) max_bid = min( ai_player.money, perceived_value * self.strategy["easy"]["max_bid_multiplier"], ) if self.last_bid_amount >= max_bid * 0.8: print("DECISION: Already reached maximum desired bid") return None print(f"\nBid limit calculation:") print(f"- Perceived value: £{perceived_value}") print( f"- Easy difficulty multiplier: {self.strategy['easy']['max_bid_multiplier']}x" ) print(f"- Maximum possible bid: £{max_bid}") if max_bid <= current_minimum: print("DECISION: Cannot bid - maximum possible bid is below minimum") return None bid_headroom = max_bid - current_minimum increment = min(50, max(10, int(bid_headroom * 0.2))) print(f"\nBid increment analysis:") print(f"- Bid headroom: £{bid_headroom}") print(f"- Calculated increment: £{increment}") import random bid = current_minimum + random.randint(10, increment) bid = min(bid, max_bid) print(f"- Initial bid calculation: £{bid}") if bid > perceived_value * 0.6: risky_bid_chance = random.random() print(f"\nRisk assessment:") print( f"- Bid (£{bid}) is above 60% of perceived value (£{perceived_value * 0.6:.2f})" ) print( f"- Random chance to pass: {risky_bid_chance:.2f} (will pass if < 0.3)" ) if risky_bid_chance < 0.3: print("DECISION: Passing due to risk assessment") return None self.last_bid_amount = bid print(f"FINAL DECISION: Bidding £{bid}") return bid
[docs] def handle_jail_strategy(self, ai_player, jail_free_cards): if not ai_player.get("in_jail", False): return None if jail_free_cards.get(ai_player["name"], 0) > 0: return "use_card" if ai_player["money"] >= 250: total_property_value = sum(prop["price"] for prop in ai_player.properties) if total_property_value > 500: return "pay_fine" return "roll_doubles"
[docs] def handle_property_development(self, ai_player, board_properties): if ai_player["money"] < 200: return None groups = set() for prop in board_properties.values(): if "group" in prop: groups.add(prop["group"]) for color_group in groups: group_properties = [ p for p in board_properties.values() if p.get("group") == color_group and p.get("owner") == ai_player["name"] ] total_in_group = sum( 1 for p in board_properties.values() if p.get("group") == color_group ) if len(group_properties) == total_in_group: for prop in group_properties: current_houses = prop.get("houses", 0) if current_houses < 4: min_houses = min(p.get("houses", 0) for p in group_properties) if current_houses <= min_houses: house_cost = prop["price"] / 2 if ai_player["money"] >= house_cost * 1.5: return prop return None
[docs] def handle_emergency_cash(self, ai_player, required_amount, board_properties): if ai_player["money"] >= required_amount: return [] properties_to_mortgage = [] for prop in board_properties.values(): if prop.get("owner") == ai_player["name"] and not prop.get("is_mortgaged"): if not self.check_group_ownership( prop.get("group"), board_properties, ai_player["name"] ): properties_to_mortgage.append(prop) properties_to_mortgage.sort(key=lambda x: x["price"]) potential_cash = 0 final_properties = [] for prop in properties_to_mortgage: potential_cash += prop["price"] / 2 final_properties.append(prop) if potential_cash >= required_amount: break return final_properties if potential_cash >= required_amount else []
[docs] def consider_trade_offer( self, ai_player, offered_properties, requested_properties, cash_difference, board_properties, ): offered_value = sum(prop["price"] for prop in offered_properties) for prop in offered_properties: if prop.get("group"): owned_in_group = sum( 1 for p in board_properties if p.get("group") == prop.get("group") and p.get("owner") == ai_player["name"] ) total_in_group = sum( 1 for p in board_properties if p.get("group") == prop.get("group") ) if owned_in_group + 1 == total_in_group: offered_value += prop["price"] requested_value = sum(prop["price"] for prop in requested_properties) for prop in requested_properties: if prop.get("group"): if self.check_group_ownership( prop.get("group"), board_properties, ai_player["name"] ): requested_value *= 1.5 total_value_difference = (offered_value + cash_difference) - requested_value return total_value_difference > 0 and random.random() < 0.8
[docs] def get_property_value(self, property_data, owned_properties, total_money): print("\n=== AI Property Valuation Debug ===") print(f"DEBUG: Evaluating value of {property_data['name']}") base_value = property_data["price"] value_multiplier = 1.0 if "group" in property_data: owned_in_group = sum( 1 for p in owned_properties if p.get("group") == property_data["group"] ) total_in_group = property_data.get("group_size", 3) print(f"DEBUG: Color group: {property_data['group']}") print(f"DEBUG: Owned in group: {owned_in_group}/{total_in_group}") if owned_in_group > 0: value_multiplier += 0.3 * (owned_in_group / total_in_group) print( f"DEBUG: Group ownership bonus applied: {0.3 * (owned_in_group / total_in_group)}" ) if "Station" in property_data["name"]: owned_stations = sum( 1 for p in owned_properties if "Station" in p.get("name", "") ) value_multiplier += 0.25 * owned_stations print(f"DEBUG: Owned stations: {owned_stations}") print(f"DEBUG: Station bonus applied: {0.25 * owned_stations}") elif property_data["name"] in ["Tesla Power Co", "Edison Water"]: owned_utilities = sum( 1 for p in owned_properties if p.get("name") in ["Tesla Power Co", "Edison Water"] ) if owned_utilities > 0: value_multiplier += 0.5 print(f"DEBUG: Utility bonus applied: 0.5") value_multiplier *= self.strategy["easy"]["max_bid_multiplier"] final_value = base_value * value_multiplier print(f"DEBUG: Base value: £{base_value}") print(f"DEBUG: Final multiplier: {value_multiplier}") print(f"DEBUG: Final calculated value: £{final_value}") return final_value
[docs] def should_buy_property(self, property_data, player_money, owned_properties): print("\n=== AI Purchase Decision Debug ===") print(f"DEBUG: Evaluating purchase of {property_data['name']}") print(f"DEBUG: Property price: £{property_data['price']}") print(f"DEBUG: AI money available: £{player_money}") print( f"DEBUG: Currently owned properties: {[p.get('name', 'Unknown') for p in owned_properties]}" ) if player_money < property_data["price"]: print("DEBUG: AI cannot afford property") return False property_value = self.get_property_value( property_data, owned_properties, player_money ) print(f"DEBUG: Calculated property value: £{property_value}") print( f"DEBUG: Decision: {'Buy' if property_value >= property_data['price'] else 'Pass'}" ) return ( player_money >= property_data["price"] and property_value >= property_data["price"] )
[docs] def make_auction_bid( self, property_data, current_bid, player_money, owned_properties ): print("\n=== AI Auction Bid Debug ===") print(f"DEBUG: AI evaluating bid for {property_data['name']}") print(f"DEBUG: Current bid: £{current_bid}") print(f"DEBUG: AI money available: £{player_money}") if player_money <= current_bid: print("DEBUG: AI cannot afford current bid") return None property_value = self.get_property_value( property_data, owned_properties, player_money ) print(f"DEBUG: Calculated property value: £{property_value}") max_bid = min(player_money * 0.7, property_value * 1.1) print(f"DEBUG: Maximum bid calculated: £{max_bid}") if current_bid >= max_bid: print("DEBUG: Maximum bid too low - passing") return None bid_increment = 10 final_bid = min(current_bid + bid_increment, max_bid) print(f"DEBUG: Final bid decision: £{final_bid}") return final_bid
[docs] def should_develop_property(self, property_data, player_money, owned_properties): print("\n=== AI Development Decision Debug ===") print( f"DEBUG: Evaluating development for {property_data.name if hasattr(property_data, 'name') else 'Unknown'}" ) print(f"DEBUG: AI money available: £{player_money}") if not property_data or not hasattr(property_data, "price"): print("DEBUG: Invalid property data") return False development_cost = property_data.price / 2 print(f"DEBUG: Development cost: £{development_cost}") if development_cost >= player_money / 2: print("DEBUG: Development cost too high relative to available funds") return False money_threshold = self.strategy["easy"]["development_threshold"] * player_money print(f"DEBUG: Money threshold for development: £{money_threshold}") potential_rent = property_data.calculate_rent() * 2 print(f"DEBUG: Potential rent after development: £{potential_rent}") should_develop = ( development_cost <= money_threshold and potential_rent >= development_cost * 0.2 * 2 ) print(f"DEBUG: Development decision: {'Develop' if should_develop else 'Skip'}") return should_develop
[docs] def should_unmortgage_property(self, property_data, player_money): print("\n=== AI Unmortgage Decision Debug ===") print( f"DEBUG: Evaluating unmortgage for {property_data.name if hasattr(property_data, 'name') else 'Unknown'}" ) print(f"DEBUG: AI money available: £{player_money}") if not property_data or not hasattr(property_data, "price"): print("DEBUG: Invalid property data") return False unmortgage_cost = property_data.price * 0.55 print(f"DEBUG: Unmortgage cost: £{unmortgage_cost}") money_threshold = player_money * 0.3 print(f"DEBUG: Money threshold for unmortgage: £{money_threshold}") should_unmortgage = unmortgage_cost < money_threshold print( f"DEBUG: Unmortgage decision: {'Unmortgage' if should_unmortgage else 'Keep mortgaged'}" ) return should_unmortgage
[docs] def should_sell_houses(self, property_data, player_money, target_amount): print("\n=== AI House Selling Decision Debug ===") print( f"DEBUG: Evaluating house selling for {property_data.name if hasattr(property_data, 'name') else 'Unknown'}" ) print(f"DEBUG: AI money available: £{player_money}") print(f"DEBUG: Target amount needed: £{target_amount}") if ( not property_data or not hasattr(property_data, "houses") or property_data.houses <= 0 ): print("DEBUG: No houses to sell") return False print(f"DEBUG: Current houses on property: {property_data.houses}") sell_threshold = target_amount * 1.2 print(f"DEBUG: Sell threshold: £{sell_threshold}") should_sell = player_money < sell_threshold print(f"DEBUG: House selling decision: {'Sell' if should_sell else 'Keep'}") return should_sell
[docs] def get_development_priority(self, properties): priorities = [] for prop in properties: if prop.get("group"): rent_to_cost_ratio = max(prop.get("house_rents", [0])) / prop.get( "house_cost", float("inf") ) group_completion = 1.0 priority_score = rent_to_cost_ratio * group_completion priorities.append((prop, priority_score)) return sorted(priorities, key=lambda x: x[1], reverse=True)
[docs] class HardAIPlayer: def __init__(self): self.difficulty = "hard" self.mood_modifier = 0.0 self.easy_ai = EasyAIPlayer() print("HardAIPlayer initialized with emotion system")
[docs] def update_mood(self, is_happy): if is_happy: self.mood_modifier = max(-0.3, self.mood_modifier - 0.05) print(f"DEBUG: AI is happier! Mood modifier: {self.mood_modifier}") else: self.mood_modifier = min(0.3, self.mood_modifier + 0.05) print(f"DEBUG: AI is angrier! Mood modifier: {self.mood_modifier}")
[docs] def get_adjusted_probability(self, base_probability): # Amplified mood effect on probability adjustments (1.5x more impact) adjusted = base_probability + (self.mood_modifier * 1.5) return max(0.0, min(1.0, adjusted))
[docs] def get_property_value(self, property_data, ai_player, board_properties): print("\n=== HARD AI Property Value Calculation Debug ===") print( f"DEBUG: Hard AI evaluating property: {property_data.name if hasattr(property_data, 'name') else 'Unknown'}" ) print(f"DEBUG: Current mood modifier: {self.mood_modifier}") base_value = self.easy_ai.get_property_value( property_data, ai_player, board_properties ) mood_multiplier = 1.0 + (self.mood_modifier * 2.0) final_value = base_value * mood_multiplier print(f"DEBUG: Base value: £{base_value}") print(f"DEBUG: Amplified mood multiplier: {mood_multiplier}") print(f"DEBUG: Final value: £{final_value}") return final_value
[docs] def get_auction_bid( self, current_minimum, property_data, ai_player, board_properties ): print("\n=== HARD AI Auction Bid Logic Debug ===") print( f"DEBUG: Hard AI evaluating bid for: {property_data['name'] if isinstance(property_data, dict) and 'name' in property_data else 'Unknown'}" ) print(f"DEBUG: Current minimum bid: £{current_minimum}") print(f"DEBUG: Current mood modifier: {self.mood_modifier}") base_bid = self.easy_ai.get_auction_bid( current_minimum, property_data, ai_player, board_properties ) if base_bid is None: angry_bid_chance = self.get_adjusted_probability(0.0) print(f"DEBUG: Chance to bid anyway: {angry_bid_chance}") if random.random() < angry_bid_chance: bid = current_minimum + random.randint( 10, 50 + int(100 * max(0, self.mood_modifier)) ) bid = min( bid, ai_player.money * (0.7 + max(0, self.mood_modifier * 0.2)) ) print(f"DEBUG: Emotion triggered bid: £{bid}") return bid return None mood_multiplier = 1.0 + (self.mood_modifier * 1.5) final_bid = int(base_bid * mood_multiplier) max_percentage = 0.9 + max(0, self.mood_modifier * 0.1) final_bid = min(final_bid, int(ai_player.money * max_percentage)) print(f"DEBUG: Base bid: £{base_bid}") print(f"DEBUG: Amplified mood multiplier: {mood_multiplier}") print(f"DEBUG: Final bid: £{final_bid}") return final_bid
[docs] def handle_jail_strategy(self, ai_player, jail_free_cards): print("\n=== HARD AI Jail Strategy Debug ===") print("DEBUG: Hard AI evaluating jail strategy") print(f"DEBUG: Current mood modifier: {self.mood_modifier}") if not ai_player.get("in_jail", False): print("DEBUG: Not in jail - no action needed") return None base_strategy = self.easy_ai.handle_jail_strategy(ai_player, jail_free_cards) if base_strategy == "roll_doubles" and ai_player["money"] >= 50: pay_chance = self.get_adjusted_probability(0.5) print(f"DEBUG: Chance to pay fine: {pay_chance}") if random.random() < pay_chance: print("DEBUG: Emotion triggered decision to pay fine") return "pay_fine" print(f"DEBUG: Using strategy: {base_strategy}") return base_strategy
[docs] def should_mortgage_property(self, ai_player, required_money): print("\n=== HARD AI Mortgage Strategy Debug ===") print("DEBUG: Hard AI evaluating mortgage strategy") print(f"DEBUG: Required money: £{required_money}") print(f"DEBUG: Current mood modifier: {self.mood_modifier}") return self.easy_ai.should_mortgage_property(ai_player, required_money)
[docs] def handle_property_development(self, ai_player, board_properties): print("\n=== HARD AI Development Strategy Debug ===") print("DEBUG: Hard AI evaluating development strategy") print(f"DEBUG: Current mood modifier: {self.mood_modifier}") base_result = self.easy_ai.handle_property_development( ai_player, board_properties ) if not base_result: money_threshold = 200 - (self.mood_modifier * 100) if ai_player["money"] >= money_threshold: develop_chance = self.get_adjusted_probability(0.3) if random.random() < develop_chance: player_properties = [] for prop_key, prop in board_properties.items(): if ( isinstance(prop, dict) and prop.get("owner") == ai_player["name"] ): player_properties.append(prop) elif hasattr(prop, "owner") and prop.owner == ai_player["name"]: player_properties.append(prop) for prop in player_properties: if hasattr( prop, "group" ) and self.easy_ai.check_group_ownership( prop.group, board_properties, ai_player["name"] ): money_reserve = 0.3 - (self.mood_modifier * 0.1) house_cost = prop.price / 2 money_after_purchase = ai_player["money"] - house_cost minimum_reserve = ai_player["money"] * money_reserve if money_after_purchase >= minimum_reserve: return prop return base_result
[docs] def should_buy_property(self, property_data, player_money, owned_properties): print("\n=== HARD AI Purchase Decision Debug ===") print(f"DEBUG: Hard AI evaluating purchase of {property_data['name']}") print(f"DEBUG: Property price: £{property_data['price']}") print(f"DEBUG: AI money available: £{player_money}") print(f"DEBUG: Current mood modifier: {self.mood_modifier}") base_decision = self.easy_ai.should_buy_property( property_data, player_money, owned_properties ) if not base_decision and player_money >= property_data["price"]: buy_chance = self.get_adjusted_probability(0.2) print(f"DEBUG: Chance to buy anyway: {buy_chance}") max_price_ratio = 0.7 + (self.mood_modifier * 0.2) can_afford = player_money * max_price_ratio >= property_data["price"] if can_afford and random.random() < buy_chance: print("DEBUG: Emotion triggered decision to buy property") print(f"DEBUG: Max price ratio: {max_price_ratio}") return True elif base_decision and self.mood_modifier < 0: pass_chance = -self.mood_modifier * 1.5 print(f"DEBUG: Amplified chance to pass: {pass_chance}") if random.random() < pass_chance: print("DEBUG: Emotion triggered decision to pass on property") return False print(f"DEBUG: Final decision: {'Buy' if base_decision else 'Pass'}") return base_decision