Skip to content

Commit

Permalink
Merge pull request #53 from bcollazo/bryan/dev
Browse files Browse the repository at this point in the history
Bryan/dev
  • Loading branch information
bcollazo committed Nov 21, 2020
2 parents 26bc109 + 44757d1 commit 6d1eae1
Show file tree
Hide file tree
Showing 7 changed files with 179 additions and 111 deletions.
109 changes: 77 additions & 32 deletions catanatron/game.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import random
import sys
import copy
from enum import Enum
from typing import Iterable
from collections import namedtuple, defaultdict

Expand Down Expand Up @@ -52,7 +51,7 @@ def yield_resources(board, resource_deck, number):
if tile.number != number or board.robber_coordinate == coordinate:
continue # doesn't yield

for node_ref, node in tile.nodes.items():
for _, node in tile.nodes.items():
building = board.buildings.get(node)
if building == None:
continue
Expand Down Expand Up @@ -221,7 +220,7 @@ def execute(self, action, action_callback=None):
)
action.player.settlements_available -= 1
# yield resources of second settlement
for tile in self.board.get_adjacent_tiles(action.value):
for tile in self.board.get_adjacent_tiles(node):
if tile.resource != None:
action.player.resource_deck.replenish(1, tile.resource)
elif action.action_type == ActionType.BUILD_SETTLEMENT:
Expand Down Expand Up @@ -257,14 +256,19 @@ def execute(self, action, action_callback=None):
):
raise ValueError("No money to buy development card")

development_card = self.development_deck.random_draw()
action.player.development_deck.replenish(1, development_card)
if action.value is None:
card = self.development_deck.random_draw()
else:
card = action.value
self.development_deck.draw(1, card)

action.player.development_deck.replenish(1, card)
action.player.resource_deck -= ResourceDeck.development_card_cost()
self.resource_deck += ResourceDeck.development_card_cost()

action = Action(action.player, action.action_type, development_card)
action = Action(action.player, action.action_type, card)
elif action.action_type == ActionType.ROLL:
dices = roll_dice()
dices = action.value or roll_dice()
number = dices[0] + dices[1]

if number == 7:
Expand All @@ -276,9 +280,7 @@ def execute(self, action, action_callback=None):
)
self.tick_queue.append((action.player, ActionPrompt.MOVE_ROBBER))
else:
payout, depleted = yield_resources(
self.board, self.resource_deck, number
)
payout, _ = yield_resources(self.board, self.resource_deck, number)
for color, resource_deck in payout.items():
player = self.players_by_color[color]

Expand All @@ -290,30 +292,55 @@ 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:
# 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)

if action.value is None:
# 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)
else:
discarded = action.value # for replay functionality
to_discard = ResourceDeck.from_array(discarded)

action.player.resource_deck -= to_discard
self.resource_deck += to_discard
action = Action(action.player, action.action_type, discarded)
elif action.action_type == ActionType.MOVE_ROBBER:
(coordinate, color_to_steal_from) = action.value
(coordinate, robbed_color, robbed_resource) = action.value
self.board.robber_coordinate = coordinate
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()
if robbed_color is None:
pass # do nothing
else:
player_to_steal_from = self.players_by_color[robbed_color]
if robbed_resource is None:
resource = player_to_steal_from.resource_deck.random_draw()
action = Action(
action.player,
action.action_type,
(coordinate, robbed_color, resource),
)
else: # for replay functionality
resource = robbed_resource
player_to_steal_from.resource_deck.draw(1, resource)
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, color_to_steal_from) = action.value
(coordinate, robbed_color, robbed_resource) = action.value
self.board.robber_coordinate = coordinate
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()
if robbed_color is None:
pass # do nothing
else:
player_to_steal_from = self.players_by_color[robbed_color]
if robbed_resource is None:
resource = player_to_steal_from.resource_deck.random_draw()
action = Action(
action.player,
action.action_type,
(coordinate, robbed_color, resource),
)
else: # for replay functionality
resource = robbed_resource
player_to_steal_from.resource_deck.draw(1, resource)
action.player.resource_deck.replenish(1, resource)
action.player.mark_played_dev_card(DevelopmentCard.KNIGHT)
elif action.action_type == ActionType.PLAY_YEAR_OF_PLENTY:
Expand All @@ -328,25 +355,23 @@ def execute(self, action, action_callback=None):
self.resource_deck -= cards_selected
action.player.mark_played_dev_card(DevelopmentCard.YEAR_OF_PLENTY)
elif action.action_type == ActionType.PLAY_MONOPOLY:
card_type_to_steal = action.value
mono_resource = action.value
cards_stolen = ResourceDeck()
if not action.player.can_play_monopoly():
raise ValueError("Player cant play monopoly now")
for player in self.players:
if not action.player.color == player.color:
number_of_cards_to_steal = player.resource_deck.count(
card_type_to_steal
)
cards_stolen.replenish(number_of_cards_to_steal, card_type_to_steal)
player.resource_deck.draw(
number_of_cards_to_steal, card_type_to_steal
)
number_of_cards_to_steal = player.resource_deck.count(mono_resource)
cards_stolen.replenish(number_of_cards_to_steal, mono_resource)
player.resource_deck.draw(number_of_cards_to_steal, mono_resource)
action.player.resource_deck += cards_stolen
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
first_edge_id, second_edge_id = action.value
first_edge = self.board.get_edge_by_id(first_edge_id)
second_edge = self.board.get_edge_by_id(second_edge_id)
self.board.build_road(action.player.color, first_edge)
self.board.build_road(action.player.color, second_edge)
action.player.roads_available -= 2
Expand Down Expand Up @@ -408,22 +433,42 @@ def count_victory_points(self):
def replay_game(game):
game_copy = copy.deepcopy(game)

# Can't use player.__class__(player.color, player.name) b.c. actions tied to player instances
for player in game_copy.players:
player.public_victory_points = 0
player.actual_victory_points = 0

player.resource_deck = ResourceDeck()
player.development_deck = DevelopmentDeck()
player.played_development_cards = DevelopmentDeck()

player.has_road = False
player.has_army = False

player.roads_available = 15
player.settlements_available = 5
player.cities_available = 4

player.has_rolled = False
player.playable_development_cards = player.development_deck.to_array()

tmp_game = Game(game_copy.players, seed=game.seed)
tmp_game.id = game_copy.id
tmp_game.players = game_copy.players # use same seating order
tmp_game.players_by_color = {p.color: p for p in game_copy.players}
tmp_game.map = game_copy.map
tmp_game.board = game_copy.board
tmp_game.board.buildings = {}
tmp_game.board.robber_coordinate = filter(
lambda coordinate: tmp_game.board.tiles[coordinate].resource == None,
tmp_game.board.tiles.keys(),
).__next__()
tmp_game.actions = [] # log of all action taken by players
tmp_game.resource_deck = ResourceDeck.starting_bank()
tmp_game.development_deck = DevelopmentDeck.starting_bank()
tmp_game.tick_queue = initialize_tick_queue(tmp_game.players)
tmp_game.current_player_index = 0
tmp_game.num_turns = 0

for action in game_copy.actions:
tmp_game.execute(action)
Expand Down
91 changes: 54 additions & 37 deletions catanatron/models/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,30 +21,39 @@ class ActionPrompt(Enum):


class ActionType(Enum):
ROLL = "ROLL" # value is None or rolled value.
MOVE_ROBBER = "MOVE_ROBBER" # value is (coordinate, Player|None).
DISCARD = "DISCARD" # value is None or discarded cards
"""
Action types are associated with a "value" that can be seen as the "params"
of such action. They usually hold None for to-be-defined values by the
execution of the action. After execution, the Actions will be hydrated
so that they can be used in reproducing a game.
"""

ROLL = "ROLL" # value is None|(int, int)
MOVE_ROBBER = "MOVE_ROBBER" # value is (coordinate, Color|None, None|Resource)
DISCARD = "DISCARD" # value is None|Resource[]

# Building/Buying
BUILD_FIRST_SETTLEMENT = "BUILD_FIRST_SETTLEMENT" # value is node_id
BUILD_SECOND_SETTLEMENT = "BUILD_SECOND_SETTLEMENT" # value is node id
BUILD_INITIAL_ROAD = "BUILD_INITIAL_ROAD" # value is edge id
BUILD_ROAD = "BUILD_ROAD" # value is edge id
BUILD_SETTLEMENT = "BUILD_SETTLEMENT" # value is node id
BUILD_CITY = "BUILD_CITY" # value is node id
BUY_DEVELOPMENT_CARD = "BUY_DEVELOPMENT_CARD" # value is None
BUILD_SECOND_SETTLEMENT = "BUILD_SECOND_SETTLEMENT" # value is node_id
BUILD_INITIAL_ROAD = "BUILD_INITIAL_ROAD" # value is edge_id
BUILD_ROAD = "BUILD_ROAD" # value is edge_id
BUILD_SETTLEMENT = "BUILD_SETTLEMENT" # value is node_id
BUILD_CITY = "BUILD_CITY" # value is node_id
BUY_DEVELOPMENT_CARD = "BUY_DEVELOPMENT_CARD" # value is None|DevelopmentCard

# Dev Card Plays
PLAY_KNIGHT_CARD = "PLAY_KNIGHT_CARD" # value is (coordinate, player)
PLAY_YEAR_OF_PLENTY = "PLAY_YEAR_OF_PLENTY"
PLAY_MONOPOLY = "PLAY_MONOPOLY"
PLAY_ROAD_BUILDING = "PLAY_ROAD_BUILDING" # value is (edge_1, edge_2)
PLAY_KNIGHT_CARD = (
"PLAY_KNIGHT_CARD" # value is (coordinate, Color|None, None|Resource)
)
PLAY_YEAR_OF_PLENTY = "PLAY_YEAR_OF_PLENTY" # value is [Resource, Resource]
PLAY_MONOPOLY = "PLAY_MONOPOLY" # value is Resource
PLAY_ROAD_BUILDING = "PLAY_ROAD_BUILDING" # value is (edge_id1, edge_id2)

# Trade
MARITIME_TRADE = "MARITIME_TRADE" # value is TradeOffer
MARITIME_TRADE = "MARITIME_TRADE" # value is TradeOffer(offering=Resource[], asking=Resource, tradee=None)
# TODO: Domestic trade. Im thinking should contain SUGGEST_TRADE, ACCEPT_TRADE actions...

END_TURN = "END_TURN"
END_TURN = "END_TURN" # value is None


# TODO: Distinguish between PossibleAction and FinalizedAction?
Expand Down Expand Up @@ -88,9 +97,10 @@ def road_possible_actions(player, board):
has_roads_available = player.roads_available > 0

if has_money and has_roads_available:
buildable_edges = board.buildable_edges(player.color)
buildable_edge_ids = board.buildable_edge_ids(player.color)
return [
Action(player, ActionType.BUILD_ROAD, edge.id) for edge in buildable_edges
Action(player, ActionType.BUILD_ROAD, edge_id)
for edge_id in buildable_edge_ids
]
else:
return []
Expand All @@ -101,10 +111,10 @@ def settlement_possible_actions(player, board):
has_settlements_available = player.settlements_available > 0

if has_money and has_settlements_available:
buildable_nodes = board.buildable_nodes(player.color)
buildable_node_ids = board.buildable_node_ids(player.color)
return [
Action(player, ActionType.BUILD_SETTLEMENT, node.id)
for node in buildable_nodes
Action(player, ActionType.BUILD_SETTLEMENT, node_id)
for node_id in buildable_node_ids
]
else:
return []
Expand Down Expand Up @@ -135,7 +145,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.
to_steal_from = set() # set of player_indexs
for node_ref, node in tile.nodes.items():
for _, node in tile.nodes.items():
building = board.buildings.get(node)
if building is not None:
candidate = players_by_color[building.color]
Expand All @@ -146,10 +156,10 @@ def robber_possibilities(player, board, players, is_dev_card):
to_steal_from.add(candidate.color)

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

return actions

Expand All @@ -160,8 +170,12 @@ def initial_settlement_possibilites(player, board, is_first):
if is_first
else ActionType.BUILD_SECOND_SETTLEMENT
)
buildable_nodes = board.buildable_nodes(player.color, initial_build_phase=True)
return list(map(lambda node: Action(player, action_type, node.id), buildable_nodes))
buildable_node_ids = board.buildable_node_ids(
player.color, initial_build_phase=True
)
return list(
map(lambda node_id: Action(player, action_type, node_id), buildable_node_ids)
)


def initial_road_possibilities(player, board, actions):
Expand All @@ -175,14 +189,14 @@ def initial_road_possibilities(player, board, actions):
last_settlement_node_id = list(node_building_actions_by_player)[-1].value
last_settlement_node = board.get_node_by_id(last_settlement_node_id)

buildable_edges = filter(
lambda edge: last_settlement_node in edge.nodes,
board.buildable_edges(player.color),
buildable_edge_ids = filter(
lambda edge_id: last_settlement_node in board.get_edge_by_id(edge_id).nodes,
board.buildable_edge_ids(player.color),
)
return list(
map(
lambda edge: Action(player, ActionType.BUILD_INITIAL_ROAD, edge.id),
buildable_edges,
lambda edge_id: Action(player, ActionType.BUILD_INITIAL_ROAD, edge_id),
buildable_edge_ids,
)
)

Expand Down Expand Up @@ -260,18 +274,21 @@ def road_building_possibilities(player, board):
On purpose we _dont_ remove equivalent possibilities, since we need to be
able to handle high branching degree anyway in AI.
"""
first_edges = board.buildable_edges(player.color)
first_edge_ids = board.buildable_edge_ids(player.color)
possibilities = []
for first_edge in first_edges:
for first_edge_id in first_edge_ids:
board_copy = copy.deepcopy(board)
first_edge_copy = board_copy.get_edge_by_id(first_edge.id)
first_edge_copy = board_copy.get_edge_by_id(first_edge_id)
board_copy.build_road(player.color, first_edge_copy)
second_edges_copy = board_copy.buildable_edges(player.color)
second_edge_ids_copy = board_copy.buildable_edge_ids(player.color)

for second_edge_copy in second_edges_copy:
second_edge = board.get_edge_by_id(second_edge_copy.id)
for second_edge_id_copy in second_edge_ids_copy:
possibilities.append(
Action(player, ActionType.PLAY_ROAD_BUILDING, (first_edge, second_edge))
Action(
player,
ActionType.PLAY_ROAD_BUILDING,
(first_edge_id, second_edge_id_copy),
)
)

return possibilities
Loading

0 comments on commit 6d1eae1

Please sign in to comment.