Skip to content

Commit

Permalink
Merge pull request #52 from bcollazo/bryan/dev
Browse files Browse the repository at this point in the history
Towards ML Infra
  • Loading branch information
bcollazo committed Nov 20, 2020
2 parents b61652e + 11e4c84 commit 26bc109
Show file tree
Hide file tree
Showing 15 changed files with 240 additions and 214 deletions.
35 changes: 21 additions & 14 deletions catanatron/game.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ def __init__(self, players: Iterable[Player], seed=None):
self.resource_deck = ResourceDeck.starting_bank()
self.development_deck = DevelopmentDeck.starting_bank()

self.tick_queue = initialize_tick_queue(players)
self.tick_queue = initialize_tick_queue(self.players)
self.current_player_index = 0
self.num_turns = 0

Expand Down Expand Up @@ -213,11 +213,13 @@ def execute(self, action, action_callback=None):
self.board.build_settlement(
action.player.color, node, initial_build_phase=True
)
action.player.settlements_available -= 1
elif action.action_type == ActionType.BUILD_SECOND_SETTLEMENT:
node = self.board.get_node_by_id(action.value)
self.board.build_settlement(
action.player.color, node, initial_build_phase=True
)
action.player.settlements_available -= 1
# yield resources of second settlement
for tile in self.board.get_adjacent_tiles(action.value):
if tile.resource != None:
Expand All @@ -228,18 +230,23 @@ def execute(self, action, action_callback=None):
action.player.color, node, initial_build_phase=False
)
action.player.resource_deck -= ResourceDeck.settlement_cost()
action.player.settlements_available -= 1
self.resource_deck += ResourceDeck.settlement_cost() # replenish bank
elif action.action_type == ActionType.BUILD_INITIAL_ROAD:
edge = self.board.get_edge_by_id(action.value)
self.board.build_road(action.player.color, edge)
action.player.roads_available -= 1
elif action.action_type == ActionType.BUILD_ROAD:
edge = self.board.get_edge_by_id(action.value)
self.board.build_road(action.player.color, edge)
action.player.roads_available -= 1
action.player.resource_deck -= ResourceDeck.road_cost()
self.resource_deck += ResourceDeck.road_cost() # replenish bank
elif action.action_type == ActionType.BUILD_CITY:
node = self.board.get_node_by_id(action.value)
self.board.build_city(action.player.color, node)
action.player.settlements_available += 1
action.player.cities_available -= 1
action.player.resource_deck -= ResourceDeck.city_cost()
self.resource_deck += ResourceDeck.city_cost() # replenish bank
elif action.action_type == ActionType.BUY_DEVELOPMENT_CARD:
Expand Down Expand Up @@ -283,40 +290,41 @@ def execute(self, action, action_callback=None):
self.tick_queue.append((action.player, ActionPrompt.PLAY_TURN))
action.player.has_rolled = True
elif action.action_type == ActionType.DISCARD:
discarded = action.value
# TODO: Forcefully discard randomly so that decision tree doesnt explode in possibilities.
hand = action.player.resource_deck.to_array()
num_to_discard = len(hand) // 2
discarded = random.sample(hand, k=num_to_discard)

to_discard = ResourceDeck()
for resource in discarded:
to_discard.replenish(1, resource)
to_discard = ResourceDeck.from_array(discarded)

action.player.resource_deck -= to_discard
self.resource_deck += to_discard
elif action.action_type == ActionType.MOVE_ROBBER:
(coordinate, player_to_steal_from) = action.value
(coordinate, color_to_steal_from) = action.value
self.board.robber_coordinate = coordinate
if player_to_steal_from is not None:
if color_to_steal_from is not None:
player_to_steal_from = self.players_by_color[color_to_steal_from]
resource = player_to_steal_from.resource_deck.random_draw()
action.player.resource_deck.replenish(1, resource)
elif action.action_type == ActionType.PLAY_KNIGHT_CARD:
if not action.player.can_play_knight():
raise ValueError("Player cant play knight card now")
(coordinate, player_to_steal_from) = action.value
(coordinate, color_to_steal_from) = action.value
self.board.robber_coordinate = coordinate
if player_to_steal_from is not None:
if color_to_steal_from is not None:
player_to_steal_from = self.players_by_color[color_to_steal_from]
resource = player_to_steal_from.resource_deck.random_draw()
action.player.resource_deck.replenish(1, resource)
action.player.development_deck.draw(1, DevelopmentCard.KNIGHT)
action.player.mark_played_dev_card(DevelopmentCard.KNIGHT)
elif action.action_type == ActionType.PLAY_YEAR_OF_PLENTY:
cards_selected = action.value # Assuming action.value is a resource deck
cards_selected = ResourceDeck.from_array(action.value)
if not action.player.can_play_year_of_plenty():
raise ValueError("Player cant play year of plenty now")
if not self.resource_deck.includes(cards_selected):
raise ValueError(
"Not enough resources of this type (these types?) in bank"
)
action.player.resource_deck += cards_selected
action.player.development_deck.draw(1, DevelopmentCard.YEAR_OF_PLENTY)
self.resource_deck -= cards_selected
action.player.mark_played_dev_card(DevelopmentCard.YEAR_OF_PLENTY)
elif action.action_type == ActionType.PLAY_MONOPOLY:
Expand All @@ -334,15 +342,14 @@ def execute(self, action, action_callback=None):
number_of_cards_to_steal, card_type_to_steal
)
action.player.resource_deck += cards_stolen
action.player.development_deck.draw(1, DevelopmentCard.MONOPOLY)
action.player.mark_played_dev_card(DevelopmentCard.MONOPOLY)
elif action.action_type == ActionType.PLAY_ROAD_BUILDING:
if not action.player.can_play_road_building():
raise ValueError("Player cant play road building now")
first_edge, second_edge = action.value
self.board.build_road(action.player.color, first_edge)
self.board.build_road(action.player.color, second_edge)
action.player.development_deck.draw(1, DevelopmentCard.ROAD_BUILDING)
action.player.roads_available -= 2
action.player.mark_played_dev_card(DevelopmentCard.ROAD_BUILDING)
elif action.action_type == ActionType.MARITIME_TRADE:
trade_offer = action.value
Expand Down
4 changes: 3 additions & 1 deletion catanatron/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,16 @@ def default(self, obj):
return {"type": "WATER"}
if isinstance(obj, Port):
return {
"id": obj.id,
"type": "PORT",
"direction": self.default(obj.direction),
"resource": self.default(obj.resource),
}
if isinstance(obj, Tile):
if obj.resource == None:
return {"type": "DESERT"}
return {"id": obj.id, "type": "DESERT"}
return {
"id": obj.id,
"type": "RESOURCE_TILE",
"resource": self.default(obj.resource),
"number": obj.number,
Expand Down
77 changes: 36 additions & 41 deletions catanatron/models/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,21 +62,20 @@ def monopoly_possible_actions(player):


def year_of_plenty_possible_actions(player, resource_deck):
possible_combinations = set()
actions = []
for first_card in Resource:
for second_card in Resource:
if (
resource_deck.can_draw(1, first_card)
and resource_deck.can_draw(1, second_card)
and (second_card, first_card) not in possible_combinations
resource_list = list(Resource)
for i, first_card in enumerate(resource_list):
for j in range(i, len(resource_list)):
second_card = resource_list[i] # doing it this way to not repeat
if resource_deck.can_draw(1, first_card) and resource_deck.can_draw(
1, second_card
):
possible_combinations.add((first_card, second_card))
cards_selected = ResourceDeck()
cards_selected.replenish(1, first_card)
cards_selected.replenish(1, second_card)
actions.append(
Action(player, ActionType.PLAY_YEAR_OF_PLENTY, cards_selected)
Action(
player,
ActionType.PLAY_YEAR_OF_PLENTY,
[first_card, second_card],
)
)

# TODO: If none of the combinations are possible due to shortages
Expand All @@ -86,9 +85,7 @@ def year_of_plenty_possible_actions(player, resource_deck):

def road_possible_actions(player, board):
has_money = player.resource_deck.includes(ResourceDeck.road_cost())

roads = board.get_player_buildings(player.color, BuildingType.ROAD)
has_roads_available = len(roads) < 15
has_roads_available = player.roads_available > 0

if has_money and has_roads_available:
buildable_edges = board.buildable_edges(player.color)
Expand All @@ -101,9 +98,7 @@ def road_possible_actions(player, board):

def settlement_possible_actions(player, board):
has_money = player.resource_deck.includes(ResourceDeck.settlement_cost())

settlements = board.get_player_buildings(player.color, BuildingType.SETTLEMENT)
has_settlements_available = len(settlements) < 5
has_settlements_available = player.settlements_available > 0

if has_money and has_settlements_available:
buildable_nodes = board.buildable_nodes(player.color)
Expand All @@ -117,9 +112,7 @@ def settlement_possible_actions(player, board):

def city_possible_actions(player, board):
has_money = player.resource_deck.includes(ResourceDeck.city_cost())

cities = board.get_player_buildings(player.color, BuildingType.CITY)
has_cities_available = len(cities) < 4
has_cities_available = player.cities_available > 0

if has_money and has_cities_available:
settlements = board.get_player_buildings(player.color, BuildingType.SETTLEMENT)
Expand All @@ -141,7 +134,7 @@ def robber_possibilities(player, board, players, is_dev_card):

# each tile can yield a (move-but-cant-steal) action or
# several (move-and-steal-from-x) actions.
players_to_steal_from = set()
to_steal_from = set() # set of player_indexs
for node_ref, node in tile.nodes.items():
building = board.buildings.get(node)
if building is not None:
Expand All @@ -150,13 +143,13 @@ def robber_possibilities(player, board, players, is_dev_card):
candidate.resource_deck.num_cards() >= 1
and candidate.color != player.color # can't play yourself
):
players_to_steal_from.add(candidate)
to_steal_from.add(candidate.color)

if len(players_to_steal_from) == 0:
if len(to_steal_from) == 0:
actions.append(Action(player, action_type, (coordinate, None)))
else:
for p in players_to_steal_from:
actions.append(Action(player, action_type, (coordinate, p)))
for color in to_steal_from:
actions.append(Action(player, action_type, (coordinate, color)))

return actions

Expand Down Expand Up @@ -195,21 +188,23 @@ def initial_road_possibilities(player, board, actions):


def discard_possibilities(player):
hand = player.resource_deck.to_array()
num_cards = player.resource_deck.num_cards()
num_to_discard = num_cards // 2

num_possibilities = ncr(num_cards, num_to_discard)
if num_possibilities > 100: # if too many, just take first N
return [Action(player, ActionType.DISCARD, hand[:num_to_discard])]

to_discard = itertools.combinations(hand, num_to_discard)
return list(
map(
lambda combination: Action(player, ActionType.DISCARD, combination),
to_discard,
)
)
return [Action(player, ActionType.DISCARD, None)]
# TODO: Be robust to high dimensionality of DISCARD
# hand = player.resource_deck.to_array()
# num_cards = player.resource_deck.num_cards()
# num_to_discard = num_cards // 2

# num_possibilities = ncr(num_cards, num_to_discard)
# if num_possibilities > 100: # if too many, just take first N
# return [Action(player, ActionType.DISCARD, hand[:num_to_discard])]

# to_discard = itertools.combinations(hand, num_to_discard)
# return list(
# map(
# lambda combination: Action(player, ActionType.DISCARD, combination),
# to_discard,
# )
# )


def ncr(n, r):
Expand Down
15 changes: 13 additions & 2 deletions catanatron/models/board.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from typing import Dict

from catanatron.models.player import Color
from catanatron.models.map import BaseMap, Water, Port
from catanatron.models.map import BaseMap, Water, Port, Tile
from catanatron.models.board_initializer import (
initialize_board,
Node,
Expand All @@ -12,7 +12,6 @@
)


# TODO: Build "deck" of these (14 roads, 5 settlements, 4 cities)
class BuildingType(Enum):
SETTLEMENT = "SETTLEMENT"
CITY = "CITY"
Expand Down Expand Up @@ -226,6 +225,18 @@ def get_node_by_id(self, node_id):
filtered = filter(lambda n: n.id == node_id, self.nodes.values())
return next(filtered, None)

def get_tile_by_id(self, tile_id):
filtered = filter(
lambda t: isinstance(t, Tile) and t.id == tile_id, self.tiles.values()
)
return next(filtered, None)

def get_port_by_id(self, port_id):
filtered = filter(
lambda t: isinstance(t, Port) and t.id == port_id, self.tiles.values()
)
return next(filtered, None)

def find_connected_components(self, color: Color):
"""returns connected subgraphs for a given player
Expand Down
Loading

0 comments on commit 26bc109

Please sign in to comment.