def destroy(self, abilities, context: Context, player: Player, game: Game): # cripple the lead destroy = abilities[0] assert isinstance(destroy, commands.Destroy) if len( context.builder_player.city ) >= 6 and context.builder_player != player and context.builder_player in destroy.choices( player, game): destroy.select(context.builder_player) district = max(destroy.choices(player, game), key=lambda d: DistrictInfo(d).cost) destroy.select(district) return destroy # otherwise fire at second to lead for victim in sorted( (p for p in destroy.choices(player, game) if p != player), key=lambda p: len(p.city)): destroy.select(victim) if len(player.city) >= len( context.builder_player.city) or player.gold >= 4: district = max(destroy.choices(player, game), key=lambda d: DistrictInfo(d).cost) else: district = min(destroy.choices(player, game), key=lambda d: DistrictInfo(d).cost) destroy.select(district) return destroy
def _try_find_biased_color(self, player: Player): colors = defaultdict(int) for district in player.city: colors[DistrictInfo(district).color] += 1 if colors: first_color, first_count = max(colors.items(), key=lambda p: p[1]) del colors[first_color] second_count = max(colors.values()) if colors else 0 if first_count - second_count >= 2: return first_color
def help_str(val): if isinstance(val, Character): info = CharacterInfo(val) if COLORING: return f'{color(info.color)}{info.name}{Style.RESET_ALL}' else: if info.color: return '{name} ({color})'.format(name=info.name, color=help_str(info.color)) else: return info.name elif isinstance(val, District): info = DistrictInfo(val) if COLORING: return f'{color(info.color)}{info.name}{Style.RESET_ALL} ({info.cost})' else: return '{name} ({cost} {color})'.format(name=info.name, cost=info.cost, color=help_str(info.color)) elif isinstance(val, Color): return { Color.Red: 'R', Color.Blue: 'B', Color.Green: 'G', Color.Yellow: 'Y', Color.Purple: 'P', }[val] elif isinstance(val, Card): return '?' if not val else help_str(val) elif isinstance(val, shadow.ShadowPlayer): if COLORING: return f'{Fore.LIGHTWHITE_EX}{Style.BRIGHT}{val.name}{Style.RESET_ALL}' else: return val.name elif isinstance(val, Player): if COLORING: return f'{Fore.LIGHTWHITE_EX}{Style.BRIGHT}{val.name}{Style.RESET_ALL}' else: return val.name elif isinstance(val, int): if COLORING: return f'{Fore.LIGHTWHITE_EX}{Style.BRIGHT}{val}{Style.RESET_ALL}' else: return str(int) else: if COLORING: return f'{Fore.LIGHTWHITE_EX}{Style.BRIGHT}{val}{Style.RESET_ALL}' else: return str(val)
def score(player: Player, game: Game, with_bonuses=True): # SCORE-1 score = sum(DistrictInfo(district).cost for district in player.city) if not with_bonuses: return score # SCORE-2 built_colors = set() for district in player.city: built_colors.add(DistrictInfo(district).color) if built_colors == set(all_colors): score += 3 # SCORE-3, SCORE-4 if is_city_complete(player): if game.turn.first_completer == player: score += 4 else: score += 2 return score
def _update(self): self._possible_commands.clear() if not self._used_commands[CommandSpecifier.Action]: self._possible_commands[CommandSpecifier.Action] = rules.possible_actions(self._game) if not self._used_commands[CommandSpecifier.Ability]: char_workflow = rules.CharacterWorkflow(self._player.char) for ability in char_workflow.abilities: if isinstance(ability, commands.InteractiveCommand): if not ability.ready and not ability.choices(self._player, self._game): # rare case when Destroy cannot be applied continue if ability.restriction & commands.Restriction.OnAfterAction: if not self._used_commands[CommandSpecifier.Action]: continue if ability.restriction & commands.Restriction.Compulsory: assert not isinstance(ability, commands.InteractiveCommand) ability.apply(self._player, self._game) self._used_commands[CommandSpecifier.Ability].append(ability) continue if ability.restriction & commands.Restriction.OnEndTurn: if not self._used_commands[CommandSpecifier.Action]: continue self._possible_commands[CommandSpecifier.Ability].append(ability) # BUILD if self._used_commands[CommandSpecifier.Action]: if len(self._used_commands[CommandSpecifier.Build]) < rules.how_many_districts_can_build(self._player): build_command = commands.Build() if build_command.choices(self._player, self._game): self._possible_commands[CommandSpecifier.Build].append(build_command) # INCOME if not self._used_commands[CommandSpecifier.Income]: color = CharacterInfo(self._player.char).color income = sum(DistrictInfo(district).color == color for district in self._player.city) if income: self._possible_commands[CommandSpecifier.Income].append(commands.CashIn(income, source='income', restriction=commands.Restriction.Compulsory)) self._assign_specifiers()
def how_much_cost_to_destroy(district: District, player: Player): return DistrictInfo(district).cost - 1
def how_much_cost_to_build(district: District, player: Player): return DistrictInfo(district).cost
def decide(self, player: Player, game: Game, sink: CommandsSink): """ Should execute commands via sink """ assert player in game.players context = self.create_context(player, game) # take income first if sink.possible_income: return sink.possible_income[0] # build if sink.possible_builds: build = sink.possible_builds[0] best_builds = sorted(build.choices(player, game), key=lambda d: DistrictInfo(d).cost, reverse=True) build.select(best_builds[0]) return build # draw cards or take money if sink.possible_actions: take_gold = next(action for action in sink.possible_actions if isinstance(action, commands.CashIn)) take_cards = next((action for action in sink.possible_actions if isinstance(action, commands.DrawSomeCards)), None) if not take_cards: return take_gold # architect may always take gold if player.char == Character.Architect: return take_gold if player.gold < 4: return take_gold best_card = next( (card for card in take_cards.choices(player, game) if rules.how_much_cost_to_build(card, player) <= player.gold and rules.can_be_built(card, player)), None) if not best_card: best_card = next((card for card in take_cards.choices(player, game) if rules.how_much_cost_to_build( card, player) <= player.gold), None) if not best_card: best_card = take_cards.choices(player, game)[0] assert best_card take_cards.select(best_card) return take_cards # play powers if sink.possible_abilities: handlers = { Character.Thief: self.rob, Character.Warlord: self.destroy, Character.Assassin: self.kill, Character.Magician: self.do_tricks, } if player.char in handlers: command = handlers[player.char](sink.possible_abilities, context, player, game) if isinstance(command, commands.InteractiveCommand): assert command.ready return command