def suffer_damage(self, clearing: Clearing, hits: int, opponent: Player, is_attacker: bool) -> DamageResult: removed_pieces = [] points_awarded = 0 if hits: warriors = clearing.get_warriors_for_player(self) # TODO: Mercenaries amount_of_warriors_removed = min(hits, len(warriors)) hits -= amount_of_warriors_removed for i in range(amount_of_warriors_removed): removed_pieces.append(warriors[i]) points_awarded += warriors[i].get_score_for_removal() if hits: tokens = clearing.get_tokens_for_player(self) random.shuffle(tokens) amount_of_tokens_removed = min(hits, len(tokens)) hits -= amount_of_tokens_removed for i in range(amount_of_tokens_removed): removed_pieces.append(tokens[i]) # If the Popularity trait is used, players can't score for sympathy if they already have since last turn if (not self.has_trait(TRAIT_POPULARITY) or opponent not in self.players_who_have_removed_sympathy_since_last_turn): points_awarded += tokens[i].get_score_for_removal() self.players_who_have_removed_sympathy_since_last_turn.add(opponent) if hits: buildings = clearing.get_buildings_for_player(self) random.shuffle(buildings) amount_of_buildings_removed = min(hits, len(buildings)) hits -= amount_of_buildings_removed for i in range(amount_of_buildings_removed): removed_pieces.append(buildings[i]) points_awarded += buildings[i].get_score_for_removal() clearing.remove_pieces(self, removed_pieces) return DamageResult(removed_pieces=removed_pieces, points_awarded=points_awarded)
def get_bonus_hits(self, clearing: Clearing, opponent: Player, is_attacker: bool = True) -> int: bonus_hits = 0 # Automated Ambush if clearing.get_warrior_count_for_player(self) > 0: bonus_hits += 1 elif self.has_trait(TRAIT_INFORMANTS) and clearing.get_token_count_for_player(self) > 0: bonus_hits += 1 return bonus_hits
def test_get_corner_clearings(self): mock_game = Mock() clearing1 = Clearing(mock_game, Suit.FOX, priority=1, total_building_slots=1, is_corner_clearing=True) clearing2 = Clearing(mock_game, Suit.FOX, priority=2, total_building_slots=1, is_corner_clearing=False) board_map = BoardMap(mock_game) board_map.clearings = [clearing1, clearing2] self.assertEqual(board_map.get_corner_clearings(), [clearing1])
def sort_players_by_cardboard_in_clearing( players: list[Player], clearing: Clearing, descending: bool = True) -> list[Player]: return sorted(players, key=lambda p: (clearing.get_building_count_for_player(p) + clearing.get_token_count_for_player(p)), reverse=descending)
def get_defenseless_enemy_buildings_in_clearing(clearing: Clearing, acting_player: Player) -> int: defenseless_buildings = 0 for player in clearing.get_all_other_players_in_location(acting_player): if player.is_defenseless(clearing): defenseless_buildings += clearing.get_building_count_for_player( player) return defenseless_buildings
def test_get_clearing(self): mock_game = Mock() clearing1 = Clearing(mock_game, Suit.FOX, priority=1, total_building_slots=1) clearing2 = Clearing(mock_game, Suit.FOX, priority=2, total_building_slots=1) board_map = BoardMap(mock_game) board_map.clearings = [clearing1, clearing2] self.assertEqual(board_map.get_clearing(1), clearing1) self.assertEqual(board_map.get_clearing(2), clearing2)
def test_get_clearings_of_suit_bird(self): mock_game = Mock() clearing1 = Clearing(mock_game, Suit.FOX, priority=1, total_building_slots=1) clearing2 = Clearing(mock_game, Suit.RABBIT, priority=2, total_building_slots=1) clearing3 = Clearing(mock_game, Suit.FOX, priority=3, total_building_slots=1) clearing4 = Clearing(mock_game, Suit.MOUSE, priority=4, total_building_slots=1) board_map = BoardMap(mock_game) board_map.clearings = [clearing1, clearing2, clearing3, clearing4] self.assertEqual(board_map.get_clearings_of_suit(Suit.BIRD), [clearing1, clearing2, clearing3, clearing4])
def create_clearings(self, suits: list[Suit], ruins: list[Ruin]) -> None: for i in range(12): clearing = Clearing( self.game, suit=suits[i], priority=i + 1, total_building_slots= AUTUMN_MAP_BUILDING_SLOTS_FOR_PRIORITY_CLEARING[i], is_corner_clearing=(i < 4)) if i + 1 in AUTUMN_MAP_RUINS_LOCATIONS and ruins: clearing.add_ruin(ruins.pop()) self.clearings.append(clearing)
def test_get_clearing(self): game = Game() clearing1 = Clearing(game, Suit.FOX, priority=1, total_building_slots=1) clearing2 = Clearing(game, Suit.FOX, priority=2, total_building_slots=1) game.board_map.clearings = [clearing1, clearing2] self.assertEqual(game.clearings(), [clearing1, clearing2])
def get_warriors_to_move(self, origin_clearing: Clearing, suit: Suit) -> list[Warrior]: own_rule_value = self.get_rule_value(origin_clearing) max_enemy_rule_value = self.get_max_enemy_rule_value_in_clearing( origin_clearing) warrior_count_to_move_and_keep_rule = own_rule_value - max_enemy_rule_value # Move the minimum between (most you can without losing rule) and (X - #cards in column) warrior_count_to_move = min( warrior_count_to_move_and_keep_rule, (origin_clearing.get_warrior_count_for_player(self) - self.decree.get_count_of_suited_cards_in_decree(suit))) return origin_clearing.get_warriors_for_player( self)[:warrior_count_to_move]
def sort_players_by_tokens_in_clearing( players: list[Player], clearing: Clearing, descending: bool = True) -> list[Player]: return sorted(players, key=lambda p: clearing.get_token_count_for_player(p), reverse=descending)
def berserker_initiate_battle(self, clearing: Clearing) -> None: potential_targets = clearing.get_all_other_players_in_location(self) # Sort in reverse order from our tie-breaking priority: Setup order -> most pieces potential_targets = sort_players_by_setup_order(potential_targets) potential_targets = sort_players_by_pieces_in_clearing(potential_targets, clearing) if potential_targets: self.battle(clearing, potential_targets[0])
def get_max_enemy_rule_value_in_clearing(self, clearing: Clearing) -> int: max_enemy_rule_value = 0 for other_player in clearing.get_all_other_players_in_location(self): rule_value = other_player.get_rule_value(clearing) if rule_value > max_enemy_rule_value: max_enemy_rule_value = rule_value return max_enemy_rule_value
def get_lowest_sorted_player_index_clearing(clearing: Clearing, players: list[Player], descending: bool = False) -> int: for idx, player in players: if player in clearing.get_all_players_in_location(): return idx * (not descending) return 100 * (not descending) # TODO: Implement this better
def test_get_clearings_of_suit_bird(self): game = Game() clearing1 = Clearing(game, Suit.FOX, priority=1, total_building_slots=1) clearing2 = Clearing(game, Suit.FOX, priority=2, total_building_slots=1) clearing3 = Clearing(game, Suit.MOUSE, priority=2, total_building_slots=1) game.board_map.get_clearings_of_suit.return_value = lambda c: [ clearing1, clearing2, clearing3 ] self.assertEqual(game.get_clearings_of_suit(Suit.BIRD), [clearing1, clearing2, clearing3])
def does_rule_clearing(self, clearing: Clearing) -> bool: all_players_in_clearing = clearing.get_all_other_players_in_location( self) own_rule_value_in_clearing = self.get_rule_value(clearing) if own_rule_value_in_clearing == 0: return False for player in all_players_in_clearing: # LORDS OF THE FOREST: Eyrie Dynasties rule tied clearings if player.get_rule_value(clearing) > own_rule_value_in_clearing: return False return True
def suffer_damage(self, clearing: Clearing, hits: int, opponent: Player, is_attacker: bool) -> DamageResult: removed_pieces = [] points_awarded = 0 if hits: warriors = clearing.get_warriors_for_player( self) # TODO: Mercenaries amount_of_warriors_removed = min(hits, len(warriors)) hits -= amount_of_warriors_removed for i in range(amount_of_warriors_removed): removed_pieces.append(warriors[i]) points_awarded += warriors[i].get_score_for_removal() if hits: tokens = clearing.get_tokens_for_player(self) random.shuffle(tokens) amount_of_tokens_removed = min(hits, len(tokens)) hits -= amount_of_tokens_removed for i in range(amount_of_tokens_removed): removed_pieces.append(tokens[i]) points_awarded += tokens[i].get_score_for_removal() if hits: buildings = clearing.get_buildings_for_player(self) random.shuffle(buildings) if self.has_trait(TRAIT_FORTIFIED): amount_of_buildings_removed = min(hits // 2, len(buildings)) else: amount_of_buildings_removed = min(hits, len(buildings)) for i in range(amount_of_buildings_removed): removed_pieces.append(buildings[i]) points_awarded += buildings[i].get_score_for_removal() clearing.remove_pieces(self, removed_pieces) if not is_attacker: # Hospitals only works as the defender self.apply_field_hospitals(removed_pieces) return DamageResult(removed_pieces=removed_pieces, points_awarded=points_awarded)
def find_shortest_legal_paths_to_destination_clearing( self, player: Player, moving_piece: Piece, destination: Clearing, ignore_move: bool = False) -> list[list[Clearing]]: if not destination.can_move_piece_into(player, moving_piece): return [] # TODO: Test and clean clearing_paths: deque[list[Clearing]] = deque([[]]) all_shortest_paths: list[list[Clearing]] = [] while clearing_paths: current_path = clearing_paths.popleft() if not current_path: current_location = self else: current_location = current_path[-1] for adjacent_clearing in player.get_adjacent_clearings( current_location): # Don't double back on yourself - that adds to the path length uselessly if adjacent_clearing in current_path: continue # Skip impossible moves, unless ignore_move is True if not ignore_move and not current_location.can_move_piece( player, moving_piece, adjacent_clearing): continue next_path = [c for c in current_path] next_path.append(adjacent_clearing) if adjacent_clearing == destination: all_shortest_paths.append( [c for c in current_path if isinstance(c, Clearing)]) # Breadth-first-search: Once we find a path to the destination N clearings away, we know no path to # the destination is shorter than N, so don't add any more to the clearing_paths queue elif not all_shortest_paths: clearing_paths.append(next_path) return all_shortest_paths
def halves_damage(self, battle_clearing: Clearing) -> bool: return (self.has_trait(TRAIT_FORTIFIED) and battle_clearing.get_building_count_for_player(self) == battle_clearing.get_piece_count_for_player(self))