Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Collection of minor improvements #257

Closed
wants to merge 15 commits into from
Closed
16 changes: 10 additions & 6 deletions .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Build the UI Docker image
run: docker build ui/ -t bcollazo/catanatron-react-ui:latest
- name: Build the Server Docker image
Expand All @@ -26,15 +26,19 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Set up Python 3.8
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: 3.8
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r all-requirements.txt
pip install -e catanatron_experimental
- name: Test dependency
run: |
python -c "import catanatron_experimental; print([name for name in dir(catanatron_experimental) if not name.startswith('__')])"
- name: Lint with black
run: |
black catanatron_core --check
Expand All @@ -57,9 +61,9 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Set up Python 3.8
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: 3.8
- name: Install dependencies
Expand All @@ -74,7 +78,7 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
Expand Down
2 changes: 1 addition & 1 deletion catanatron_core/catanatron/models/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ def robber_possibilities(state, color) -> List[Action]:
# 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_id in tile.nodes.items():
for node_id in tile.nodes.values():
building = state.board.buildings.get(node_id, None)
if building is not None:
candidate_color = building[0]
Expand Down
49 changes: 19 additions & 30 deletions catanatron_core/catanatron/models/map.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,41 +45,30 @@ class EdgeRef(Enum):
Coordinate = Tuple[int, int, int]


@dataclass
class LandTile:
def __init__(
self,
tile_id: int,
resource: Union[FastResource, None],
number: Union[int, None],
nodes: Dict[NodeRef, NodeId],
edges: Dict[EdgeRef, EdgeId],
):
self.id = tile_id

self.resource = resource # None means desert tile
self.number = number # None if desert
id: int
resource: Union[FastResource, None] # None means desert tile
number: Union[int, None] # None if desert
nodes: Dict[NodeRef, NodeId] # node_ref => node_id
edges: Dict[EdgeRef, EdgeId] # edge_ref => edge

self.nodes = nodes # node_ref => node_id
self.edges = edges # edge_ref => edge

def __repr__(self):
if self.resource is None:
return "Tile:Desert"
return f"Tile:{self.number}{self.resource}"
# The id is unique among the tiles, so we can use it as the hash.
def __hash__(self):
return self.id


@dataclass
class Port:
def __init__(
self, port_id, resource: Union[FastResource, None], direction, nodes, edges
):
self.id = port_id
self.resource = resource # None means its a 3:1 port.
self.direction = direction
self.nodes = nodes
self.edges = edges
id: int
resource: Union[FastResource, None] # None means desert tile
direction: Direction
nodes: Dict[NodeRef, NodeId] # node_ref => node_id
edges: Dict[EdgeRef, EdgeId] # edge_ref => edge

def __repr__(self):
return "Port:" + str(self.resource)
# The id is unique among the tiles, so we can use it as the hash.
def __hash__(self):
return self.id


@dataclass(frozen=True)
Expand Down Expand Up @@ -264,7 +253,7 @@ def from_tiles(tiles: Dict[Coordinate, Tile]):
self.port_nodes = init_port_nodes_cache(self.tiles)

land_nodes_list = map(lambda t: set(t.nodes.values()), self.land_tiles.values())
self.land_nodes = frozenset(set.union(*land_nodes_list))
self.land_nodes = frozenset().union(*land_nodes_list)

# TODO: Rename to self.node_to_tiles
self.adjacent_tiles = init_adjacent_tiles(self.land_tiles)
Expand Down
3 changes: 1 addition & 2 deletions catanatron_core/catanatron/models/player.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,5 +76,4 @@ class RandomPlayer(Player):
"""Random AI player that selects an action randomly from the list of playable_actions"""

def decide(self, game, playable_actions):
index = random.randrange(0, len(playable_actions))
return playable_actions[index]
return random.choice(playable_actions)
3 changes: 1 addition & 2 deletions catanatron_core/catanatron/players/weighted_random.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,4 @@ def decide(self, game, playable_actions):
weight = WEIGHTS_BY_ACTION_TYPE.get(action.action_type, 1)
bloated_actions.extend([action] * weight)

index = random.randrange(0, len(bloated_actions))
return bloated_actions[index]
return random.choice(bloated_actions)
2 changes: 1 addition & 1 deletion catanatron_core/catanatron/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ def yield_resources(board: Board, resource_freqdeck, number):
if tile.number != number or board.robber_coordinate == coordinate:
continue # doesn't yield

for _, node_id in tile.nodes.items():
for node_id in tile.nodes.values():
building = board.buildings.get(node_id, None)
assert tile.resource is not None
if building is None:
Expand Down
66 changes: 36 additions & 30 deletions catanatron_core/catanatron/state_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,41 +25,47 @@ def maintain_longest_road(state, previous_road_color, road_color, road_lengths):
for color, length in road_lengths.items():
key = player_key(state, color)
state.player_state[f"{key}_LONGEST_ROAD_LENGTH"] = length
if road_color is None:
return # do nothing

if previous_road_color != road_color:
winner_key = player_key(state, road_color)
state.player_state[f"{winner_key}_HAS_ROAD"] = True
state.player_state[f"{winner_key}_VICTORY_POINTS"] += 2
state.player_state[f"{winner_key}_ACTUAL_VICTORY_POINTS"] += 2
if previous_road_color is not None:
loser_key = player_key(state, previous_road_color)
state.player_state[f"{loser_key}_HAS_ROAD"] = False
state.player_state[f"{loser_key}_VICTORY_POINTS"] -= 2
state.player_state[f"{loser_key}_ACTUAL_VICTORY_POINTS"] -= 2
# If road_color is not set or is the same as before, do nothing.
if road_color is None or (previous_road_color == road_color):
return

# Set new longest road player and unset previous if any.
winner_key = player_key(state, road_color)
state.player_state[f"{winner_key}_HAS_ROAD"] = True
state.player_state[f"{winner_key}_VICTORY_POINTS"] += 2
state.player_state[f"{winner_key}_ACTUAL_VICTORY_POINTS"] += 2
if previous_road_color is not None:
loser_key = player_key(state, previous_road_color)
state.player_state[f"{loser_key}_HAS_ROAD"] = False
state.player_state[f"{loser_key}_VICTORY_POINTS"] -= 2
state.player_state[f"{loser_key}_ACTUAL_VICTORY_POINTS"] -= 2


def maintain_largest_army(state, color, previous_army_color, previous_army_size):
candidate_size = get_played_dev_cards(state, color, "KNIGHT")
if candidate_size >= 3:
if previous_army_color is None:
winner_key = player_key(state, color)
state.player_state[f"{winner_key}_HAS_ARMY"] = True
state.player_state[f"{winner_key}_VICTORY_POINTS"] += 2
state.player_state[f"{winner_key}_ACTUAL_VICTORY_POINTS"] += 2
elif previous_army_size < candidate_size and previous_army_color != color:
# switch, remove previous points and award to new king
winner_key = player_key(state, color)
state.player_state[f"{winner_key}_HAS_ARMY"] = True
state.player_state[f"{winner_key}_VICTORY_POINTS"] += 2
state.player_state[f"{winner_key}_ACTUAL_VICTORY_POINTS"] += 2
if previous_army_color is not None:
loser_key = player_key(state, previous_army_color)
state.player_state[f"{loser_key}_HAS_ARMY"] = False
state.player_state[f"{loser_key}_VICTORY_POINTS"] -= 2
state.player_state[f"{loser_key}_ACTUAL_VICTORY_POINTS"] -= 2
# else: someone else has army and we dont compete

# Skip if army is too small to be considered.
if candidate_size < 3:
return

if previous_army_color is None:
winner_key = player_key(state, color)
state.player_state[f"{winner_key}_HAS_ARMY"] = True
state.player_state[f"{winner_key}_VICTORY_POINTS"] += 2
state.player_state[f"{winner_key}_ACTUAL_VICTORY_POINTS"] += 2
elif previous_army_size < candidate_size and previous_army_color != color:
# switch, remove previous points and award to new king
winner_key = player_key(state, color)
state.player_state[f"{winner_key}_HAS_ARMY"] = True
state.player_state[f"{winner_key}_VICTORY_POINTS"] += 2
state.player_state[f"{winner_key}_ACTUAL_VICTORY_POINTS"] += 2

loser_key = player_key(state, previous_army_color)
state.player_state[f"{loser_key}_HAS_ARMY"] = False
state.player_state[f"{loser_key}_VICTORY_POINTS"] -= 2
state.player_state[f"{loser_key}_ACTUAL_VICTORY_POINTS"] -= 2
# else: someone else has army and we dont compete


# ===== State Getters
Expand Down
Loading