In [ ]:
import numpy as np
import scipy
from scipy.stats import randint
In [ ]:
NUM_PLAYERS = 4
NUM_DEEDS = 18
NUM_DEED_TYPES = NUM_DEEDS // 3
NUM_TRANSIT = 9
NUM_TRANSIT_TYPES = NUM_TRANSIT // 3
NUM_ANTI_TRUST = 3
NUM_PAYDAY = 2
INFINITE_VALUE = 1000000
ANTI_TRUST_FINE = 20
MIN_BID = 5
RESORT_COST = 20
HOTEL_COST = 10
DEED_CARD = 0
TRANSIT_CARD = 1
ANTI_TRUST_CARD = 2
PAYDAY_CARD = 3
DEED_CARD_NAMES = [
"Male - Maldives",
"Mpumalanga - South Africa",
"Dubai - United Arab Emirates",
"Saint Elizabeth Parish - Jamaica",
"Kamalame Cay - The Bahamas",
"St Lucia - West Indies",
"Ambergris Caye - Belize",
"Valparaíso - Chile",
"Fernando de Noronha - Brazil",
"Siargao - Philippines",
"Sanya - China",
"Phuket - Thailand",
"Naples - Florida, USA",
"Kauai - Hawaii, USA",
"Laguna Beach - California, USA",
"Capri - Italy",
"Eze - France",
"Santorini - Greece",
]
TRANSIT_CARD_NAMES = [
"Hartsfield–Jackson Atlanta International Airport - USA",
"Beijing Capital International Airport - China",
"Dubai International Airport - Dubai",
"Tanggula Railway Station - Tibet",
"Shinjuku Station - Japan",
"Grand Central Terminal - USA",
"Port of Shanghai - China",
"Port of Singapore - Singapore",
"Port of Rotterdam - Netherlands",
]
In [ ]:
class Player(object):
def __init__(self, name):
self.cash = 150
self.deeds = set()
self.hotels = set()
self.resorts = set()
self.transit = set()
self.name = name
self.game_over = False
def countDeedType(self, deed_type):
count = 0
for deed in self.deeds:
if deedType(deed) == deed_type:
count += 1
return count
def countTransitType(self, transit_type):
count = 0
for transit in self.transit:
if transitType(transit) == transit_type:
count += 1
return count
def addCard(self, card_type, card):
if card_type == DEED_CARD:
self.deeds.add(card)
elif card_type == TRANSIT_CARD:
self.transit.add(card)
else:
raise Exception("Oops",card,card_type)
def gameOver(self):
self.deeds = set()
self.hotels = set()
self.resorts = set()
self.transit = set()
self.name = "XX" + self.name + "XX"
self.game_over = True
def deedType(deed):
if deed >= NUM_DEEDS:
raise Exception("Invalid card",deed)
return deed // 3
def transitType(transit):
if transit >= NUM_TRANSIT:
raise Exception("Invalid card",transit)
return transit // 3
def rentCost(deed,owner):
deed_type = deedType(deed)
if deed_type in owner.resorts:
return 25
if deed_type in owner.hotels:
return 10
owned = owner.countDeedType(deed_type)
if owned == 0:
return 0
elif owned == 1:
return 1
elif owned == 2:
return 2
elif owned == 3:
return 5
else:
print(self.deeds)
raise Exception("Oops " + str(owned))
def transitCost(transit, owner):
transit_type = transitType(transit)
owned = owner.countTransitType(transit_type)
if owned == 0:
return 0
elif owned == 1:
return 2
elif owned == 2:
return 5
elif owned == 3:
return 10
else:
raise Exception("Oops",owned)
def incPlayer(i):
return (i+1)%NUM_PLAYERS
def rebase_onto_card_type(card):
if card < NUM_DEEDS:
return card,DEED_CARD
elif card < NUM_DEEDS+NUM_TRANSIT:
return card-NUM_DEEDS,TRANSIT_CARD
elif card < NUM_DEEDS+NUM_TRANSIT+NUM_ANTI_TRUST:
return card-NUM_DEEDS-NUM_TRANSIT,ANTI_TRUST_CARD
elif card < NUM_DEEDS+NUM_TRANSIT+NUM_ANTI_TRUST+NUM_PAYDAY:
return card-NUM_DEEDS-NUM_TRANSIT-NUM_ANTI_TRUST,PAYDAY_CARD
else:
raise Exception("Oops",card)
def getCardName(card_type, card):
if card_type == DEED_CARD:
return DEED_CARD_NAMES[card]
elif card_type == TRANSIT_CARD:
return TRANSIT_CARD_NAMES[card]
elif card_type == ANTI_TRUST_CARD:
return "ANTI-TRUST/" + str(card)
elif card_type == PAYDAY_CARD:
return "PAYDAY/" + str(card)
In [ ]:
# All of the AI can be derived from one question: What is a deed worth?
def computeDeedValue(deed,buyer_index,players):
player = players[buyer_index]
already_owns = False
if deed in player.deeds:
deed_type = deedType(deed)
if deed_type in player.hotels or deed_type in player.resorts:
return INFINITE_VALUE # Can't get rid of these, so assume high value
already_owns = True
player.deeds.remove(deed)
valueWithoutDeed = rentCost(deed,player)*8
player.deeds.add(deed)
valueWithDeed = rentCost(deed,player)*8
if player.countDeedType(deedType(deed)) == 3:
valueWithDeed = 7*8 # hack to account for the fact that now the player has the opportunity to buy a hotel.
if not already_owns:
player.deeds.remove(deed)
return valueWithDeed - valueWithoutDeed
def computeTransitValue(transit, buyer_index, players):
player = players[buyer_index]
already_owns = False
if transit in player.transit:
transit_type = transitType(transit)
already_owns = True
player.transit.remove(transit)
valueWithout = transitCost(transit,player)*8
player.transit.add(transit)
valueWith = transitCost(transit,player)*8
if not already_owns:
player.transit.remove(transit)
return valueWith - valueWithout
def computeValue(card_type, card, buyer_index, players):
if card_type == DEED_CARD:
return computeDeedValue(card, buyer_index, players)
elif card_type == TRANSIT_CARD:
return computeTransitValue(card, buyer_index, players)
else:
raise Exception("Oops",card_type,card)
In [ ]:
np.random.seed(1)
players = []
for x in range(NUM_PLAYERS):
players.append(Player("Player_" + str(x + 1)))
visit_cards = np.arange(NUM_DEEDS + NUM_TRANSIT + NUM_ANTI_TRUST + NUM_PAYDAY)
on_card = 0
np.random.shuffle(visit_cards)
def auction(card_type, card, seller, first_to_bid):
top_bid = None
player_bid_map = {}
for x in range(NUM_PLAYERS):
if x != seller:
player_bid_map[x] = 0
player_to_bid = first_to_bid
while len(player_bid_map)>1:
if player_to_bid not in player_bid_map:
player_to_bid = incPlayer(player_to_bid)
continue
# TODO: Consider the value to the other players in the game
value_to_player = min(players[player_to_bid].cash, computeValue(card_type, card, player_to_bid, players))
if value_to_player < MIN_BID:
# Fold
del player_bid_map[player_to_bid]
elif top_bid is not None and value_to_player <= top_bid:
# Fold
del player_bid_map[player_to_bid]
else:
# Bid
top_bid = value_to_player
player_bid_map[player_to_bid] = value_to_player
player_to_bid = incPlayer(player_to_bid)
if len(player_bid_map)==0:
return # No one bought
else:
winning_player_index = list(player_bid_map)[0]
winning_player_bid = player_bid_map[winning_player_index]
if winning_player_bid == 0:
return # No one bought
players[winning_player_index].cash -= winning_player_bid
players[winning_player_index].addCard(card_type,card)
if seller:
players[seller].cash += winning_player_bid
print(players[winning_player_index].name,'bid',winning_player_bid,'for',getCardName(card_type,card))
for turn in range(100):
for player_index in range(len(players)):
player = players[player_index]
if player.cash < 0:
continue
# Draw a card
card = visit_cards[on_card]
on_card += 1
card,card_type = rebase_onto_card_type(card)
print(player.name,'draws',getCardName(card_type,card))
if card_type == PAYDAY_CARD:
# Shuffle the visit deck
on_card = 0
np.random.shuffle(visit_cards)
# When we shuffle, give each player some money
print('Shuffle, everyone gets money!')
for p in players:
p.cash += 5
elif card_type == ANTI_TRUST_CARD:
# Anti-trust card. Loop through player's deeds and try to sell one
worst_deed_value = -1
worst_deed = None
for deed in player.deeds:
value = computeDeedValue(deed,player_index,players)
if value == INFINITE_VALUE:
continue
if worst_deed_value == -1 or worst_deed_value > value:
worst_deed_value = value
worst_deed = deed
worst_transit_value = -1
worst_transit = None
for transit in player.transit:
value = computeTransitValue(transit,player_index,players)
if worst_transit_value == -1 or worst_transit_value > value:
worst_transit_value = value
worst_transit = transit
#print(worst_deed,worst_deed_value,worst_transit,worst_transit_value)
if worst_deed is None and worst_transit is None:
if len(player.resorts)>0 or len(player.hotels)>0:
# Can't sell anything because of hotels/resorts, pay a fine
player.cash -= ANTI_TRUST_FINE
print(player.name,'charged anti-trust fine of',ANTI_TRUST_FINE)
else:
print(player.name,'avoided anti-trust')
else:
if worst_transit is None or (worst_deed is not None and worst_transit_value > worst_deed_value):
# Sell the least valuable deed (TODO: Consider the value to other players)
player.deeds.remove(worst_deed)
print(player.name,'forced to auction',getCardName(DEED_CARD,worst_deed))
auction(DEED_CARD, worst_deed,player_index,incPlayer(player_index))
else:
# Sell the least valuable transit (TODO: Consider the value to other players)
player.transit.remove(worst_transit)
print(player.name,'forced to auction',getCardName(TRANSIT_CARD,worst_transit))
auction(TRANSIT_CARD, worst_transit,player_index,incPlayer(player_index))
elif card_type == TRANSIT_CARD:
owned = False
for p in players:
if card in p.transit:
owned = True
if p == player:
continue # Landed on your own card
# Landed on someone else's card, need to pay a fee
transit_cost = transitCost(card,p)
player.cash -= transit_cost
p.cash += transit_cost
print(player.name,'charged',transit_cost,'transit fee on',card,'by',p.name)
if not owned:
auction(card_type,card,None,player_index)
elif card_type == DEED_CARD:
owned = False
for p in players:
if card in p.deeds:
owned = True
if p == player:
continue # Landed on your own card
# Landed on someone else's card, need to pay a fee
rent_cost = rentCost(card,p)
player.cash -= rent_cost
p.cash += rent_cost
print(player.name,'charged',rent_cost,'rent on',card,'by',p.name)
if not owned:
auction(card_type,card,None,player_index)
else:
raise Exception("Oops",card_type)
if player.cash < 0 and player.game_over == False:
# Player is knocked out of the game
print(player.name,'is knocked out of the game')
player.gameOver()
# Check if game is over
players_alive = 0
for p in players:
if p.cash >= 0:
players_alive += 1
if players_alive < 2:
break
if not player.game_over:
# Opportunity to buy hotel/resort
for deed_type in range(NUM_DEED_TYPES):
if deed_type in player.resorts:
continue # Nothing more to buy
elif deed_type in player.hotels:
# Opportunity to upgrade to resort
if player.cash > RESORT_COST + 10: # dummy ai: buy resort/hotel if you have 10 left over
print(player.name,'bought a resort on',deed_type)
player.cash -= RESORT_COST
player.hotels.remove(deed_type)
player.resorts.add(deed_type)
elif player.countDeedType(deed_type)==3:
# Opportunity to buy hotel
if player.cash > HOTEL_COST + 10: # dummy ai: buy resort/hotel if you have 10 left over
print(player.name,'bought a hotel on',deed_type)
player.cash -= HOTEL_COST
player.hotels.add(deed_type)
print("TURN",turn)
for player in players:
print(player.name,player.cash,player.deeds,player.hotels,player.resorts,player.transit)
# Check if game is over
players_alive = 0
for p in players:
if p.cash >= 0:
players_alive += 1
if players_alive < 2:
print('GAME OVER')
break
In [ ]: