def load_smuggler(self, s: str): actions = phase_subtokens(s) for idx, action in enumerate(actions): if not action: assert idx == len(actions) - 1 break subtokens = action_subtokens(action) if action.upper().startswith('CARD'): card = self.determine_card(action, self.gs) yield load_all_phase_card_action(card, subtokens[1:]) continue # Just 'Y' could match if we don't require the length of name_subtokens to be 2, which would be an issue name_subtokens = tokens(subtokens[0]) if tokens_match(name_subtokens, ['YELLOW', 'TILE']) and len(name_subtokens) == 2: assert len( subtokens) == 2, 'Location required with yellow tile' yield YellowTileAction( self.location_transformer.apply(Location(int( subtokens[1])))) continue gain, cost, roll = subtokens yield EncounterSmuggler(load_good(gain), Pay() if cost == '-2' else load_good(cost), load_roll(roll))
def load_choose_reward(self, s: str) -> typing.Iterator[PlayerAction]: actions = phase_subtokens(s) for idx, action in enumerate(actions): if not action: assert idx == len(actions) - 1 break subtokens = action_subtokens(action) if action.upper().startswith('CARD'): card = self.determine_card(action, self.gs) yield load_all_phase_card_action(card, subtokens[1:]) continue # Just 'Y' could match if we don't require the length of name_subtokens to be 2, which would be an issue name_subtokens = tokens(subtokens[0]) if tokens_match(name_subtokens, ['YELLOW', 'TILE']) and len(name_subtokens) == 2: assert len( subtokens) == 2, 'Location required with yellow tile' yield YellowTileAction( self.location_transformer.apply(Location(int( subtokens[1])))) continue for subtoken in subtokens: if subtoken in {'3', '6', '9', '12'}: for _ in range(int(subtoken) // 3): yield ChooseReward(ChooseReward.LIRA) continue card = load_exact_card(subtoken) yield ChooseReward(card)
def load_possible_red_tile_action(s: str) -> typing.Union[Roll, RedTileAction]: subtokens = action_subtokens(s) if len(subtokens) == 1: return load_roll(subtokens[0]) first = subtokens[0] assert tokens_match(tokens(first), ['RED', 'TILE']), '{} is not RedTile'.format(first) assert len(subtokens) == 4, 'RedTile requires 3 inputs, got'.format(len(subtokens) - 1) initial_roll = load_roll(subtokens[1]) final_roll = load_roll(subtokens[3]) method = {'F': RedTileAction.TO_FOUR, 'R': RedTileAction.REROLL, '4': RedTileAction.TO_FOUR}[subtokens[2]] return RedTileAction(initial_roll, final_roll, method)
def load_market_action(s: str, ps: PlayerState, ts: MarketTileState) -> MarketAction: subtokens = action_subtokens(s) assert len(subtokens) == 2, f'Expected 2 subtokens for market action; got {s}' assert ts.demand is not None, 'No demand currently set on tile' goods, new_demand = subtokens if 'ALL'.startswith(goods.upper()): max_demand: typing.Counter[Good] = Counter() for good in Good: max_demand[good] = min(ps.cart_contents[good], ts.demand[good]) assert 0 < sum(max_demand.values()) <= 5, f'All would imply {sum(max_demand.values())} goods' goods = max_demand else: goods = load_good_counter(goods) new_demand = load_good_counter(new_demand) return MarketAction(goods, new_demand)
def load_caravansary_action(s: str) -> CaravansaryAction: subtokens = action_subtokens(s) assert len(subtokens) == 3, f'Expected 3 subtokens for caravansary action; got {s}' first, second = subtokens[:2] if 'DISCARD'.startswith(first.upper()): first_gain = CaravansaryAction.DISCARD else: first_gain = load_exact_card(first) if 'DISCARD'.startswith(second.upper()): second_gain = CaravansaryAction.DISCARD else: second_gain = load_exact_card(second) cost = load_exact_card(subtokens[2]) return CaravansaryAction((first_gain, second_gain), cost)
def determine_card(cls, card_action: str, gs: GameState, tile: typing.Optional[Tile] = None) -> Card: subtokens = action_subtokens(card_action) player_state = gs.player_states[gs.turn_state.current_player] if subtokens[0].upper() == 'CARD': cards: typing.Set[Card] = { k for k, v in player_state.hand.items() if v > 0 } else: pre, card_desc = subtokens[0].split('-') assert pre.upper() == 'CARD' cards = load_card(card_desc) possible_cards = cards & cls.allowed_cards(gs, tile) assert possible_cards, f'{card_action} does not match any currently legal cards' assert len( possible_cards ) == 1, f'{card_action} matched multiple legal cards: {possible_cards}' return possible_cards.pop()
def load_warehouse_action(s: str) -> typing.Union[GenericTileAction, GreenTileAction]: if not s: return GenericTileAction() gt, good = action_subtokens(s) assert tokens_match(tokens(gt), ['GREEN', 'TILE']), '{} is not GreenTile'.format(gt) return GreenTileAction(load_good(good))
def load_phase_3( self, s: str, tile: typing.Optional[Tile] = None ) -> typing.Iterator[PlayerAction]: actions = phase_subtokens(s) player_state = self.gs.player_states[self.gs.turn_state.current_player] tile = self.gs.location_map[ player_state.location] if tile is None else tile tile_state = typing.cast(MarketTileState, self.gs.tile_states[tile]) for idx, action in enumerate(actions): if action == '!': yield SkipTileAction() return if action == '': assert tile in self.GENERIC_ACTION_TILES, f'{tile} is not generic' yield GenericTileAction() continue subtokens = action_subtokens(action) if action.upper().startswith('CARD'): card = self.determine_card(action, self.gs, tile) if card in {Card.DOUBLE_PO, Card.DOUBLE_DEALER}: assert len(subtokens) == 1 yield DoubleCardAction( card, (GenericTileAction(), GenericTileAction())) continue if card is Card.DOUBLE_SULTAN: _, first_cost, second_cost = subtokens yield DoubleCardAction( card, (SultansPalaceAction(load_good_counter(first_cost)), SultansPalaceAction(load_good_counter(second_cost)))) continue if card is Card.SELL_ANY: assert len( subtokens ) == 3, 'Incorrect number of arguments for sell any goods card' assert tile is Tile.SMALL_MARKET, 'Can only use sell any goods card at small marker' if not 'ALL'.startswith(subtokens[1].upper()): yield SellAnyCardAction( load_market_action(' '.join(subtokens[1:]), player_state, tile_state)) continue assert 0 < sum(player_state.cart_contents.values()) <= 5, \ f'All is ambiguous with {sum(player_state.cart_contents.values())} goods' yield SellAnyCardAction( MarketAction(player_state.cart_contents.copy(), load_good_counter(subtokens[2]))) continue yield load_all_phase_card_action(card, subtokens[1:]) continue # Just 'Y' could match if we don't require the length of name_subtokens to be 2, which would be an issue name_subtokens = tokens(subtokens[0]) if tokens_match(name_subtokens, ['YELLOW', 'TILE']) and len(name_subtokens) == 2: assert len( subtokens) == 2, 'Location required with yellow tile' yield YellowTileAction( self.location_transformer.apply(Location(int( subtokens[1])))) continue if tile is Tile.POLICE_STATION: location = Location(int(subtokens[0])) dest_tile = self.gs.location_map[ self.location_transformer.apply(location)] args = ' '.join(subtokens[1:]) dest_actions = list(self.load_phase_3(args, tile=dest_tile)) assert len(dest_actions) == 1 # This is manually type checked in the constructor dest_action = typing.cast( typing.Union[PlaceTileAction, GreenTileAction, DoubleCardAction, SellAnyCardAction], dest_actions[0]) yield PoliceStationAction(location, dest_action) continue if tile is Tile.FOUNTAIN: if len(subtokens) == 1 and subtokens[0].upper() == 'ALL': yield GenericTileAction() continue locations = map( self.location_transformer.apply, typing.cast(typing.Iterator[Location], map(int, subtokens))) yield FountainAction(locations) continue if tile in {Tile.SMALL_MARKET, Tile.LARGE_MARKET}: yield load_market_action(action, player_state, tile_state) continue lookup_table = { Tile.GREAT_MOSQUE: load_mosque_action, # Post office is always generic or card Tile.FABRIC_WAREHOUSE: load_warehouse_action, Tile.SMALL_MOSQUE: load_mosque_action, Tile.FRUIT_WAREHOUSE: load_warehouse_action, # Police station is special, above # Fountain is special, above Tile.SPICE_WAREHOUSE: load_warehouse_action, Tile.BLACK_MARKET: load_black_market_action, Tile.CARAVANSARY: load_caravansary_action, # Small market is special, above Tile.TEA_HOUSE: load_tea_house_action, Tile.SULTANS_PALACE: load_sultans_palace_action, # Large market is special, above # Wainwright is always generic # Gemstone dealer is always generic } yield lookup_table[tile](action)
def load_phases_12(self, s: str) -> typing.Iterator[PlayerAction]: dont_pay = False if s.endswith('!$'): dont_pay = True s = s[:-2].strip() actions = phase_subtokens(s) player_state = self.gs.player_states[self.gs.turn_state.current_player] for idx, action in enumerate(actions): skip_assistant = False if action.endswith('!'): skip_assistant = True action = action[:-1] action = action.rstrip() if action.isdigit(): yield Move(self.location_transformer.apply( Location(int(action))), skip_assistant=skip_assistant) if skip_assistant: assert idx == len( actions) - 1, 'Assistant skip was not last action' yield YieldTurn() return continue subtokens = action_subtokens(action) assert subtokens, 'Empty action not allowed in phase 1' if action.upper().startswith('CARD'): card = self.determine_card(action, self.gs) if card is Card.NO_MOVE: assert len(subtokens) == 1 yield NoMoveCardAction(skip_assistant) if skip_assistant: assert idx == len( actions) - 1, 'Assistant skip was not last action' yield YieldTurn() return continue if card is Card.EXTRA_MOVE: assert len(subtokens ) == 2, 'Location required with extra move card' yield ExtraMoveCardAction( Move(self.location_transformer.apply( Location(int(subtokens[1]))), skip_assistant=skip_assistant)) if skip_assistant: assert idx == len( actions) - 1, 'Assistant skip was not last action' yield YieldTurn() return continue if card is Card.RETURN_ASSISTANT: assert len( subtokens ) == 2, 'Location required with return assistant card' yield ReturnAssistantCardAction( self.location_transformer.apply( Location(int(subtokens[1])))) continue yield load_all_phase_card_action(card, subtokens[1:]) continue assert tokens_match(tokens(subtokens[0]), ['YELLOW', 'TILE']), f'Unknown action {action}' assert len(subtokens) == 2, 'Location required with yellow tile' yield YellowTileAction( self.location_transformer.apply(Location(int(subtokens[1])))) continue # After applying all the explicit actions tile_state = self.gs.tile_states[self.gs.location_map[ player_state.location]] if len(tile_state.players) > 1 and self.gs.location_map[ player_state.location] is not Tile.FOUNTAIN: if dont_pay: yield YieldTurn() else: yield Pay() else: assert not dont_pay, 'Payment not required'