diff --git a/board.csv b/board.csv index e9e81ef..eb388ad 100644 --- a/board.csv +++ b/board.csv @@ -1,41 +1,41 @@ -Name,Space,Color,Position,Price,PriceBuild,Rent -Go,Go,None,0,0,0,0 -Mediterranean Avenue,Street,Brown,1,60,50,2 -Community Chest,Chest,None,2,0,0,0 -Baltic Avenue,Street,Brown,3,60,50,4 -Income Tax,Tax,None,4,200,0,200 -Reading Railroad,Railroad,None,5,200,0,25 -Oriental Avenue,Street,LightBlue,6,100,50,6 -Chance,Chance,None,7,0,0,0 -Vermont Avenue,Street,LightBlue,8,100,50,6 -Connecticut Avenue,Street,LightBlue,9,120,50,8 -Jail,Jail,None,10,0,0,0 -St. Charles Place,Street,Pink,11,140,100,10 -Electric Company,Utility,None,12,150,0,4 -States Avenue,Street,Pink,13,140,100,10 -Virginia Avenue,Street,Pink,14,160,100,12 -Pennsylvania Railroad,Railroad,None,15,200,0,25 -St. James Place,Street,Orange,16,180,100,14 -Community Chest,Chest,None,17,0,0,0 -Tennessee Avenue,Street,Orange,18,180,100,14 -New York Avenue,Street,Orange,19,200,100,16 -Free Parking,Parking,None,20,0,0,0 -Kentucky Avenue,Street,Red,21,220,150,18 -Chance,Chance,None,22,0,0,0 -Indiana Avenue,Street,Red,23,220,150,18 -Illinois Avenue,Street,Red,24,240,150,20 -B. & O. Railroad,Railroad,None,25,200,0,25 -Atlantic Avenue,Street,Yellow,26,260,150,22 -Ventnor Avenue,Street,Yellow,27,260,150,22 -Water Works,Utility,None,28,150,0,4 -Marvin Gardens,Street,Yellow,29,280,150,24 -Go To Jail,GoToJail,None,30,0,0,0 -Pacific Avenue,Street,Green,31,300,200,26 -North Carolina Avenue,Street,Green,32,300,200,26 -Community Chest,Chest,None,33,0,0,0 -Pennsylvania Avenue,Street,Green,34,320,200,28 -Short Line,Railroad,None,35,200,0,25 -Chance,Chance,None,36,0,0,0 -Park Place,Street,Blue,37,350,200,35 -Luxury Tax,Tax,None,38,100,0,75 -Boardwalk,Street,Blue,39,400,200,50 \ No newline at end of file +Name,Space,Color,Position,Price,PriceBuild,Rent,Rent1,Rent2,Rent3,Rent4,Rent5 +Go,Go,None,0,0,0,0,0,0,0,0,0 +Mediterranean Avenue,Street,Brown,1,60,50,2,10,30,90,160,250 +Community Chest,Chest,None,2,0,0,0,0,0,0,0,0 +Baltic Avenue,Street,Brown,3,60,50,4,20,60,180,320,450 +Income Tax,Tax,None,4,200,0,200,0,0,0,0,0 +Reading Railroad,Railroad,None,5,200,0,25,0,0,0,0,0 +Oriental Avenue,Street,LightBlue,6,100,50,6,30,90,270,400,550 +Chance,Chance,None,7,0,0,0,0,0,0,0,0 +Vermont Avenue,Street,LightBlue,8,100,50,6,30,90,270,400,550 +Connecticut Avenue,Street,LightBlue,9,120,50,8,40,100,300,450,600 +Jail,Jail,None,10,0,0,0,0,0,0,0,0 +St. Charles Place,Street,Pink,11,140,100,10,50,150,450,625,750 +Electric Company,Utility,None,12,150,0,4,0,0,0,0,0 +States Avenue,Street,Pink,13,140,100,10,50,150,450,625,750 +Virginia Avenue,Street,Pink,14,160,100,12,60,180,500,700,900 +Pennsylvania Railroad,Railroad,None,15,200,0,25,0,0,0,0,0 +St. James Place,Street,Orange,16,180,100,14,70,200,550,750,950 +Community Chest,Chest,None,17,0,0,0,0,0,0,0,0 +Tennessee Avenue,Street,Orange,18,180,100,14,70,200,550,750,950 +New York Avenue,Street,Orange,19,200,100,16,80,220,600,800,1000 +Free Parking,Parking,None,20,0,0,0,0,0,0,0,0 +Kentucky Avenue,Street,Red,21,220,150,18,90,250,700,875,1050 +Chance,Chance,None,22,0,0,0,0,0,0,0,0 +Indiana Avenue,Street,Red,23,220,150,18,90,250,700,875,1050 +Illinois Avenue,Street,Red,24,240,150,20,100,300,750,925,1100 +B. & O. Railroad,Railroad,None,25,200,0,25,0,0,0,0,0 +Atlantic Avenue,Street,Yellow,26,260,150,22,110,330,800,975,1150 +Ventnor Avenue,Street,Yellow,27,260,150,22,110,330,800,975,1150 +Water Works,Utility,None,28,150,0,4,0,0,0,0,0 +Marvin Gardens,Street,Yellow,29,280,150,24,120,360,850,1025,1200 +Go To Jail,GoToJail,None,30,0,0,0,0,0,0,0,0 +Pacific Avenue,Street,Green,31,300,200,26,130,390,900,1100,1275 +North Carolina Avenue,Street,Green,32,300,200,26,130,390,900,1100,1275 +Community Chest,Chest,None,33,0,0,0,0,0,0,0,0 +Pennsylvania Avenue,Street,Green,34,320,200,28,150,450,1000,1200,1400 +Short Line,Railroad,None,35,200,0,25,0,0,0,0,0 +Chance,Chance,None,36,0,0,0,0,0,0,0,0 +Park Place,Street,Blue,37,350,200,35,175,500,1100,1300,1500 +Luxury Tax,Tax,None,38,100,0,75,0,0,0,0,0 +Boardwalk,Street,Blue,39,400,200,50,200,600,1400,1700,2000 \ No newline at end of file diff --git a/main.py b/main.py index 03ed1b4..9494714 100644 --- a/main.py +++ b/main.py @@ -69,7 +69,7 @@ def chance_node(main_player: int, state: MonopolyGame, depth: int) -> tuple: expected_utility += eval * PROBS[dice] return expected_utility, None -def expectiminimax(main_player: int, state: MonopolyGame, depth: int=4, chance: bool=False) -> tuple: +def expectiminimax(main_player: int, state: MonopolyGame, depth: int=8, chance: bool=False) -> tuple: # Expectiminimax algorithm to search for the best action if state.is_terminal() or depth == 0: return state.evaluate_utility(), None @@ -90,13 +90,12 @@ def play(state: MonopolyGame) -> None: curr_player = state.players[state.current_player] print(f"{curr_player.name} is on {curr_player.position} and has {curr_player.money}$,") - if curr_player.in_jail: + if curr_player.is_in_jail: print(f"turns in jail {curr_player.turns_in_jail}") - d1, d2 = curr_player.roll_dice() - print(f"{curr_player.name} rolls dice: {(d1, d2)},") + total_dice = curr_player.roll_dice() # Moving the player based on the dice outcome - state.move_player(d1 + d2) + state.move_player(total_dice) print(f"{curr_player.name} lands on {curr_player.position}!", end=" ") # Determining the best possible action @@ -106,14 +105,16 @@ def play(state: MonopolyGame) -> None: state = state.take_action(best_action) print(f"{curr_player.name} {ACTIONS[best_action]}.") print(state.players[state.current_player]) + print(f"Current net worth: {state.evaluate_utility()}") if state.is_terminal(): state.game_over = True else: state.switch_player() num_of_rounds += 1 + print(f"Round: {num_of_rounds}") print("====================================================") - print(f"{state.players[0 if state.current_player else 1].name} Won :) rounds played: {num_of_rounds}") + print(f"{state.players[0 if state.current_player else 1].name} Won!") # Driver code to start the Monopoly game if __name__ == "__main__": diff --git a/monopoly_game.py b/monopoly_game.py index d1e27ae..5f88e6c 100644 --- a/monopoly_game.py +++ b/monopoly_game.py @@ -2,27 +2,36 @@ from property import * from player import * from math import ceil +import pandas as pd # Define the Monopoly game class class MonopolyGame: - def __init__(self, board: list=[], players: list=[], current_player: int=0, game_over: bool=False): + def __init__(self, board: list=[], players: list=[], current_player: int=0, other_player: int=1, game_over: bool=False): # Initializing the game state self.board = board # List to represent the game board self.players = players # List to represent the players self.current_player = current_player # Index of the current player in the players list + self.other_player = other_player self.game_over = game_over # Boolean flag to indicate if the game is over - def initialize_board(self, file_name: str) -> None: + def initialize_board(self, file_name: str): # Initializing the game board from a csv file - with open(file_name) as file: - next(file) - for line in file: - name, space, color, position, price, build_price, rent = line.rstrip().split(",") - self.board.append( - Property(name, space, color, int(position), int(price), int(rent), int(build_price)) - ) - - + df = pd.read_csv(file_name) + + for row in df.itertuples(): + name = row[1] + space = row[2] + position = row[4] + price = row[5] + build_price = row[6] + rent = row[7] + rent1 = row[8] + rent2 = row[9] + rent3 = row[10] + rent4 = row[11] + rent5 = row[12] + + self.board.append(Property(name, space, int(position), int(price), int(rent), int(build_price), int(rent1), int(rent2), int(rent3), int(rent4), int(rent5))) def initialize_players(self) -> None: # Initializing two players with their starting positions, money, and other attributes @@ -41,28 +50,29 @@ def take_action(self, action: int): if action == 0: pass elif action == 1: - curr_player.pay(curr_prop.price) + curr_player.pay_money(curr_prop.price) curr_player.properties.append(curr_position) curr_prop.owner = self.current_player elif action == 2: - curr_player.pay(curr_prop.rent) - new_players[curr_prop.owner].receive(curr_prop.rent) + curr_player.pay_money(curr_prop.rent) + new_players[curr_prop.owner].get_money(curr_prop.rent) elif action == 3: curr_prop.rent *= 1.5 curr_prop.rent = ceil(curr_prop.rent) + curr_player.pay_money(curr_prop.build_price) curr_prop.level += 1 elif action == 4: curr_player.position = 10 - curr_player.in_jail = True + curr_player.is_in_jail = True curr_player.turns_in_jail += 1 elif action == 5: curr_player.turns_in_jail += 1 elif action == 6: - curr_player.pay(50) - curr_player.in_jail = False + curr_player.pay_money(50) + curr_player.is_in_jail = False curr_player.turns_in_jail = 0 elif action == 7: - curr_player.in_jail = False + curr_player.is_in_jail = False curr_player.turns_in_jail = 0 return MonopolyGame(new_board, new_players, self.current_player, self.game_over) @@ -71,15 +81,15 @@ def get_possible_actions(self) -> list: curr_player = self.players[self.current_player] curr_position = curr_player.position curr_prop = self.board[curr_position] - if curr_player.in_jail: + if curr_player.is_in_jail: if curr_player.rolled_doubles: return [7] if curr_player.turns_in_jail >= 3: return [6] return [5, 6] if curr_prop.ownable: - if curr_prop.owner == self.current_player: - if curr_prop.level < 5: + if curr_prop.owner == self.current_player and curr_prop.upgradable: + if curr_player.money > curr_prop.build_price: return [3, 0] return [0] elif curr_prop.owner == None: @@ -94,14 +104,18 @@ def get_possible_actions(self) -> list: def move_player(self, dice_result:int) -> None: # Pass if the player is in jail - if self.players[self.current_player].in_jail: + if self.players[self.current_player].is_in_jail: return curr_player = self.players[self.current_player] curr_position = curr_player.position # Update the player's position based on the dice roll result curr_position = (curr_position + dice_result) % len(self.board) + # Add 200 to the player's balance if they passed the "Go" cell + if curr_position < curr_player.position: + curr_player.money += 200 + #print(f"{curr_player.name} collect 200$") curr_player.position = curr_position - + def is_terminal(self) -> bool: # Check if the game has reached a terminal state curr_player = self.players[self.current_player] @@ -109,10 +123,11 @@ def is_terminal(self) -> bool: return True return False - def evaluate_utility(self): + def evaluate_utility(self) -> int: curr_player = self.players[self.current_player] + curr_net_worth = curr_player.net_worth(self.board) # Evaluate the utility of the current game state for the current player - return curr_player.net_worth(self.board) + return curr_net_worth def switch_player(self): # Switch to the next player's turn diff --git a/player.py b/player.py index 8ea5cc1..b127156 100644 --- a/player.py +++ b/player.py @@ -1,32 +1,44 @@ import random -# Define the Player class -class Player: - def __init__(self, name: str, position: int, money: int): +class Player(): + def __init__(self, name, position, money): self.name = name self.position = position self.money = money self.properties = [] - self.in_jail = False + self.is_in_jail = False self.turns_in_jail = 0 self.rolled_doubles = False + - def pay(self, amount) -> None: + def pay_money(self, amount)-> None: self.money -= amount - def receive(self, amount) -> None: + def get_money(self, amount) -> None: self.money += amount - - def roll_dice(self) -> tuple: - # Roll the dice and return the result + + def roll_dice(self) -> int: dice1 = random.randint(1, 6) dice2 = random.randint(1, 6) - self.rolled_doubles = True if dice1 == dice2 else False - return (dice1, dice2) + total = dice1 + dice2 + print(f"{self.name} rolled {dice1} and {dice2} ({total})") + return total def net_worth(self, props) -> int: - return self.money + sum([props[i].price for i in self.properties]) + sum([props[i].rent for i in self.properties]) - - def __str__(self) -> str: - return f"{self.name} (Position: {self.position}, Money: {self.money}$, Properties: {self.properties})" + # Calculate current rent + current_rent = sum([props[i].rent for i in self.properties]) + + # Calculate potential rent with all properties fully developed + potential_rent = sum([props[i].max_rent for i in self.properties]) + + # Calculate current property value + prop_value = sum([props[i].price for i in self.properties]) + + # Calculate net worth + return self.money + current_rent + potential_rent + prop_value + def __str__(self) -> str: + return f"{self.name}\n" \ + f"Position: {self.position}\n" \ + f"Money: {self.money}$\n" \ + f"Properties: {self.properties}" \ No newline at end of file diff --git a/property.py b/property.py index c2eb75b..38cbdb0 100644 --- a/property.py +++ b/property.py @@ -1,19 +1,34 @@ -# Define the Property class -class Property: - def __init__(self, name: str, space: str, color: str, position: int, price: int, rent: int, build_price: int): + +class Property(): + def __init__(self, name, space, position, price, build_price, rent0, rent1, rent2, rent3, rent4, rent5) -> None: self.name = name self.space = space - self.color = color self.position = position self.price = price - self.rent = rent self.build_price = build_price + self.rentl = [] + self.rentl.append(rent0) + self.rentl.append(rent1) + self.rentl.append(rent2) + self.rentl.append(rent3) + self.rentl.append(rent4) + self.rentl.append(rent5) + self.level = 0 self.ownable = False + self.upgradable = True if space in ["Street"] else False if space in ["Street", "Railroad", "Utility"]: self.ownable = True self.owner = None - self.level = 1 - - def __str__(self) -> str: - return f"{self.name} (Price: {self.price}, Rent: {self.rent})" + self.level = 0 + self.max_rent = self.rentl[5] + self.rent = self.rentl[self.level] + + def upgrade(self) -> None: + self.level += 1 + if self.level >= 5: + self.upgradable = False + self.rent = self.rentl[self.level] + def __str__(self): + # Format the string using f-strings and return it + return f"{self.name} - Price: {self.price}$, Rent: {self.rent}$" \ No newline at end of file