def parse_header(header_str): """ Parse the header string. Return a dictionary with game_end, supply, and resigned fields, like parse_game. """ sections = [s for s in header_str.replace(' \n', '\n').split('\n\n') if s] end_str, supply_str = sections assert 'gone' in end_str or 'resigned' in end_str, "Not gone or resigned" if 'gone' in end_str: resigned = False gone = capture_cards(end_str.split('\n')[1]) else: resigned = True gone = [] supply = indexes(capture_cards(supply_str)) return {GAME_END: indexes(gone), SUPPLY: supply, RESIGNED: resigned}
def parse_player_start_decks(log_lines): start_decks = [] start_match = PLAYER_AND_START_DECK_RE.match(log_lines[0]) while start_match: line=log_lines.pop(0) name = start_match.group(1) start_deck = indexes(capture_cards(start_match.group(2))) start_decks.append({NAME:name, START_DECK:start_deck}) start_match = PLAYER_AND_START_DECK_RE.match(log_lines[0]) return start_decks
def parse_turn(turn_blob, names_list): """ Parse the information from a given turn. Return a dict containing the following fields. If any of the fields have a value that evaluates to False, do not keep it. name: player name. number: 1 indexed turn number. plays: List of cards played. buys: List of cards bought. gains: List of cards gained. trashes: List of cards trashed. returns: List of cards returned. ps_tokens: Number of pirate ship tokens gained. vp_tokens: Number of victory point tokens gained. money: Amount of money available during entire buy phase. opp: Dict keyed by opponent index in names_list, containing dicts with trashes/gains. """ lines = turn_blob.strip().split('\n') header = lines[0] parsed_header = parse_turn_header(header, names_list) poss, outpost = False, False if 'pname' in parsed_header: possessee_name = parsed_header['name'] possessee_index = names_list.index(possessee_name) poss = True if 'outpost' in parsed_header: outpost = True ret = {GAINS: [], TRASHES: [], BUYS: []} plays = [] returns = [] turn_money = 0 vp_tokens = 0 ps_tokens = 0 opp_turn_info = collections.defaultdict(lambda: {GAINS: [], TRASHES: [], BUYS: []}) tracker = PlayerTracker() for line_idx, line in enumerate(lines): active_player = tracker.get_active_player(line) if active_player == tracker.current_player(): targ_obj = ret else: # Stop using the player's name here, as it is used as a # key name in a dict, which can't be stored in MongoDB if # it contains a dot ('.') or starts with a dollar # sign. Instead, use the player index number so we can # extract the name later. # # targ_obj = opp_turn_info[names_list[active_player]] targ_obj = opp_turn_info[str(active_player)] has_trashing = KW_TRASHING in line has_trashes = KW_TRASHES in line has_gaining = KW_GAINING in line orig_buys_len = len(targ_obj.get(BUYS, [])) orig_gains_len = len(targ_obj.get(GAINS, [])) did_trading_post_gain = False if has_trashes: if has_gaining: # Trading post turn, first trashes, then gaining gain_start = line.find(KW_GAINING) targ_obj[TRASHES].extend(capture_cards(line[:gain_start])) targ_obj[GAINS].extend(capture_cards(line[gain_start:])) did_trading_post_gain = True else: targ_obj[TRASHES].extend(capture_cards(line)) if KW_WITH_A in line: if KW_REPLACING in line: new_gained_portion = line[line.find(KW_WITH_A):] targ_obj[GAINS].extend(capture_cards(new_gained_portion)) if KW_PLAYS in line or KW_PLAYING in line: plays.extend(capture_cards(line)) if has_gaining and not did_trading_post_gain: if KW_ANOTHER_ONE in line: # mints a gold gaining another one targ_obj[GAINS].extend(capture_cards(line)) else: # gaining always associated with current player? targ_obj[GAINS].extend( capture_cards(line[line.find(KW_GAINING):])) if KW_BUYS in line: targ_obj[BUYS].extend(capture_cards(line)) if KW_GAINS_THE in line: targ_obj[GAINS].extend(capture_cards(line)) if has_trashing: if KW_REVEALS in lines[line_idx - 1] and not KW_DRAWS in line: targ_obj[TRASHES].extend(capture_cards(lines[line_idx - 1])) if KW_REVEALING in line or KW_REVEALS in line: # reveals watchtower trashing ... # noble brigand reveals xx, yy and trashes yy trashed = capture_cards(line[line.find(KW_TRASHING):]) targ_obj[TRASHES].extend(trashed) else: rest = line if KW_GAINING in line: rest = line[:line.find(KW_GAINING)] targ_obj[TRASHES].extend(capture_cards(rest)) if KW_GAINS_A in line or KW_GAMES_A in line: if KW_TOKEN in line: assert get_card('Pirate Ship') in capture_cards(line), 'Pirate ship not in line' ps_tokens += 1 else: rest = line[max(line.find(KW_GAINS_A), line.find(KW_GAMES_A)):] targ_obj[GAINS].extend(capture_cards(rest)) if KW_IS_TRASHED in line: # Saboteur after revealing cards, name not mentioned on this line. cards = capture_cards(line) targ_obj[TRASHES].extend(cards) if KW_REVEALS in line: card_revealed = capture_cards(line) # arg, ambassador requires looking at the next line to figure # out how many copies were returned if (card_revealed and line_idx + 1 < len(lines) and KW_RETURNING in lines[line_idx + 1] and not KW_REVEALING in lines[line_idx + 1]): next_line = lines[line_idx + 1] num_copies = 1 num_copies_match = NUMBER_COPIES.search(next_line) if num_copies_match: num_copies = int(num_copies_match.group(1)) returns.extend(card_revealed * num_copies) if KW_REVEALING in line and KW_TO_THE_SUPPLY in line: # old style ambassador line returns.extend(capture_cards(line)) if KW_GETTING in line or KW_GETS in line or KW_GET in line: money_match = GETTING_MONEY_RE.search(line) if money_match: turn_money += int(money_match.group(1)) if KW_WHICH_IS_WORTH in line: worth_match = WHICH_IS_WORTH_RE.search(line) assert bool(worth_match), line turn_money += int(worth_match.group(1)) if KW_FOR_MONEY in line: worth_match = FOR_MONEY_RE.search(line) assert bool(worth_match), line turn_money += int(worth_match.group(1)) if u'▼' in line: vp_tokens += int(VP_TOKEN_RE.search(line).group('num')) if KW_INSTEAD in line and not KW_WISHING in line and 'Trader' in line: if 'buy_or_gain' in targ_obj: targ_list = targ_obj[targ_obj['buy_or_gain']] non_silver_ind = len(targ_list) - 1 while (non_silver_ind >= 0 and targ_list[non_silver_ind] == 'Silver'): non_silver_ind -= 1 # This shouldn't work when there is no non-silver, but then # non_silver_ind == -1 if there is no non-silver, # which magically pops the last item. <3 guido. targ_list.pop(non_silver_ind) else: assert 'Ill-Gotten Gains' in plays, ( "line %s: line\n, targ_obj: %s\n context: %s" % ( line, str(targ_obj), '\n'.join(lines[line_idx - 2: line_idx + 2]))) now_buys_len = len(targ_obj.get(BUYS, [])) now_gains_len = len(targ_obj.get(GAINS, [])) if now_buys_len > orig_buys_len: targ_obj['buy_or_gain'] = BUYS if now_gains_len > orig_gains_len: targ_obj['buy_or_gain'] = GAINS assert not (now_buys_len > orig_buys_len and now_gains_len > orig_gains_len), 'buys or gains mismatch' def _delete_if_exists(d, n): if n in d: del d[n] _delete_if_exists(ret, 'buy_or_gain') if poss: possessee_info = opp_turn_info[str(possessee_index)] for k in [GAINS, TRASHES]: _delete_if_exists(possessee_info, k) possessee_info[VP_TOKENS], vp_tokens = vp_tokens, 0 possessee_info[RETURNS], returns = returns, [] ret[BUYS] = [] # buys handled by possesion gain line. for opp in opp_turn_info.keys(): _delete_if_exists(opp_turn_info[opp], 'buy_or_gain') delete_keys_with_empty_vals(opp_turn_info[opp]) d = opp_turn_info[opp] for k, v in d.iteritems(): if k==VP_TOKENS: d[k] = v else: d[k] = indexes(v) ret[BUYS] = indexes(ret[BUYS]) ret[GAINS] = indexes(ret[GAINS]) ret[TRASHES] = indexes(ret[TRASHES]) ret.update({NAME: names_list[tracker.current_player()], PLAYS: indexes(plays) , RETURNS: indexes(returns), MONEY: count_money(plays) + turn_money, VP_TOKENS: vp_tokens, PIRATE_TOKENS: ps_tokens, POSSESSION: poss, OUTPOST: outpost, OPP: dict(opp_turn_info)}) delete_keys_with_empty_vals(ret) return ret
def parse_turn(log_lines, names_list, trash_pile, trade_route_set, removed_from_supply, masq_targets, previous_name): """ Parse the information from a given turn. Maintain the trash pile. This is necessary for Forager money counting. Maintains the trade route tokens AND the list of all cards gained, which we need to accurately know when Cities are activated. Return a dict containing the following fields. If any of the fields have a value that evaluates to False, do not keep it. name: player name. number: 1 indexed turn number. plays: List of cards played. buys: List of cards bought. gains: List of cards gained. trashes: List of cards trashed. returns: List of cards returned. passes: List of cards passed with Masquerade. receives: List of cards received from Masquerade. ps_tokens: Number of pirate ship tokens gained. vp_tokens: Number of victory point tokens gained. money: Amount of money available during entire buy phase. opp: Dict keyed by opponent index in names_list, containing dicts with trashes/gains/passes/receives. """ n_players = len(names_list) def pile_size(card, n_players): if card == dominioncards.Ruins or card == dominioncards.Curse: return max((n_players - 1)*10, 10) if card == dominioncards.Province: if n_players <= 2: return 8 if n_players == 3: return 12 return 12+(n_players - 4)*3 if card.is_victory(): if n_players <= 2: return 8 return 12 if card in [dominioncards.Spoils, dominioncards.Mercenary, dominioncards.Madman] or card.is_ruins() or card.is_shelter(): return 999 if card == dominioncards.Copper: if n_players < 5: return 60 return 120 if card == dominioncards.Silver: if n_players < 5: return 40 return 80 if card == dominioncards.Gold: if n_players < 5: return 30 return 60 if card == dominioncards.Platinum: return 12 if card == dominioncards.Potion: return 16 return 10 def empty_piles(removed_from_supply, n_players): # For cities... empty_piles = [] # First, piles of different cards if (sum([removed_from_supply[c] for c in removed_from_supply.keys() if c.is_knight()]) == pile_size(dominioncards.Knights, n_players)): empty_piles.append(dominioncards.Knights) if (sum([removed_from_supply[c] for c in removed_from_supply.keys() if c.is_ruins()]) == pile_size(dominioncards.Knights, n_players)): empty_piles.append(dominioncards.Knights) for pile,num in removed_from_supply.items(): if pile_size(pile, n_players) == num: empty_piles.append(pile) return empty_piles def _delete_if_exists(d, n): if n in d: del d[n] def fix_buys_and_gains(buys, gains): """Goko reports each buy and gain separately. This is correct, but having everything compatible with iso stats would be nice! So, here I 'fix' buys and gains so things which are bought and gained are only reported once, in 'buys', and things which are bought but not gained (such as due to trader or possession) are not listed. """ new_buys = [] for buy in buys: if buy in gains: gains.remove(buy) new_buys.append(buy) return (new_buys, gains) ret = {PLAYS: [], RETURNS: [], GAINS: [], TRASHES: [], BUYS: [], PASSES: [], RECEIVES: []} durations = [] turn_money = 0 turn_coin_tokens = 0 vp_tokens = 0 ps_tokens = 0 # Keep track of last play, and whether it is still 'active' # for stuff like trashing copper to Moneylender, gaining cards from the # trash, etc. Card effects which last more than one line. last_play = None harvest_reveal = [] trashed_to_mercenary = 0 current_phase = None dup_plays_remaining = -1 done_self_trashing = False bom_plays = 0 # For throne room/procession/KC - don't get to rechoose BoM bom_choice = None bom_processioned = False # to trash it properly storeroom_discards = [] done_resolving = True coin_tokens = 0 action_counter = 0 # All this... just for diadem. :/ opp_turn_info = collections.defaultdict(lambda: {GAINS: [], BUYS: [], TRASHES: [], PASSES: [], RECEIVES:[]}) while True: line = log_lines.pop(0) turn_start = START_TURN_RE.match(line) if turn_start: action_counter = 1 ret[NAME] = turn_start.group(1) ret[NUMBER] = int(turn_start.group(2)) current_phase = ACTION_PHASE if turn_start.group(3): ret[POSSESSION] = True if previous_name and previous_name not in masq_targets: masq_targets[previous_name] = ret[NAME] continue # empty line ends the turn, clean up and return if EMPTY_LINE_RE.match(line): # Current goko log bug - does not report 1 VP from Bishop vp_tokens += ret[PLAYS].count(dominioncards.Bishop) if last_play == dominioncards.Forager and not done_resolving: turn_money += sum([d.is_treasure() for d in set(trash_pile)]) if last_play == dominioncards.Harvest and not done_resolving: turn_money += len(set(harvest_reveal)) money = parse_common.count_money(ret[PLAYS], True) + \ turn_money + parse_common.count_money(durations, True) - \ durations.count(dominioncards.HorseTraders)*3 (buys, gains) = fix_buys_and_gains(ret[BUYS], ret[GAINS]) ret[BUYS] = indexes(buys) ret[PLAYS] = indexes(ret[PLAYS]) ret[RETURNS] = indexes(ret[RETURNS]) ret[GAINS] = indexes(gains) ret[TRASHES] = indexes(ret[TRASHES]) ret[PASSES] = indexes(ret[PASSES]) ret[RECEIVES] = indexes(ret[RECEIVES]) durations = indexes(durations) for opp in opp_turn_info.keys(): _delete_if_exists(opp_turn_info[opp], 'buy_or_gain') parse_common.delete_keys_with_empty_vals(opp_turn_info[opp]) d = opp_turn_info[opp] for k, v in d.iteritems(): if k==VP_TOKENS: d[k] = v else: d[k] = indexes(v) ret.update({MONEY:money, VP_TOKENS: vp_tokens, PIRATE_TOKENS: ps_tokens, COIN_TOKENS: coin_tokens, OPP: dict(opp_turn_info)}) return ret player_and_rest = HYPHEN_SPLIT_RE.match(line) active_player = player_and_rest.group(1) action_taken = player_and_rest.group(2) # Card-specific processing: # Will need to add Stash options whenever stash is implemented. # These cards all have unstated effects that last longer than one goko # line. For example, Forager will need to count coins after the next # 'trash' line - if there is one. Mining Village gives +$2 if trashed - # on the next line after it is played and it draws a card. if (last_play == dominioncards.Forager and not done_resolving and KW_TRASHES not in action_taken): turn_money += sum([d.is_treasure() for d in set(trash_pile)]) done_resolving = True elif (last_play == dominioncards.MiningVillage and KW_SHUFFLES not in action_taken and KW_DRAWS not in action_taken and KW_TRASHES not in action_taken): done_resolving = True elif (last_play == dominioncards.Tournament and KW_REVEALS not in action_taken and KW_REVEALS_C not in action_taken and KW_GAINS not in action_taken): done_resolving = True elif (last_play == dominioncards.Counterfeit and (KW_PLAYS not in action_taken or dominioncards.Spoils not in capture_cards(action_taken))): done_resolving = True elif (last_play == dominioncards.Thief and KW_TRASHES not in action_taken and KW_SHUFFLES not in action_taken and KW_REVEALS not in action_taken and KW_REVEALS_C not in action_taken and KW_DISCARDS not in action_taken and KW_DISCARDS_C not in action_taken and KW_GAINS not in action_taken): done_resolving = True elif (last_play == dominioncards.NobleBrigand and KW_GAINS in action_taken and dominioncards.NobleBrigand in capture_cards(action_taken)): done_resolving = True elif (last_play == dominioncards.NobleBrigand and KW_TRASHES not in action_taken and KW_SHUFFLES not in action_taken and KW_REVEALS not in action_taken and KW_REVEALS_C not in action_taken and KW_DISCARDS not in action_taken and KW_DISCARDS_C not in action_taken and KW_GAINS not in action_taken): done_resolving = True elif (last_play == dominioncards.Rogue and KW_GAINS not in action_taken): done_resolving = True elif (last_play == dominioncards.Graverobber and KW_GAINS not in action_taken): done_resolving = True elif (last_play == dominioncards.Mercenary and KW_TRASHES not in action_taken and KW_REVEALS not in action_taken and KW_DRAWS not in action_taken and KW_PLACES not in action_taken and KW_GAINS not in action_taken and KW_SHUFFLES not in action_taken): done_resolving = True trashed_to_mercenary = 0 elif (last_play == dominioncards.Moneylender and (KW_TRASHES not in action_taken or dominioncards.Copper not in capture_cards(action_taken))): done_resolving = True elif (last_play == dominioncards.Salvager and KW_TRASHES not in action_taken): done_resolving = True elif (last_play == dominioncards.Baron and (KW_DISCARDS not in action_taken or dominioncards.Estate not in capture_cards(action_taken))): done_resolving = True elif (last_play == dominioncards.Harvest and KW_REVEALS not in action_taken and KW_REVEALS_C not in action_taken and KW_SHUFFLES not in action_taken): turn_money += len(set(harvest_reveal)) harvest_reveal = [] done_resolving = True elif (last_play == dominioncards.BandofMisfits and KW_CHOOSES not in action_taken): done_resolving = True elif (last_play == dominioncards.Ironmonger and KW_DRAWS not in action_taken and KW_SHUFFLES not in action_taken and KW_REVEALS_C not in action_taken and KW_REVEALS not in action_taken): done_resolving = True elif (last_play == dominioncards.Ironworks and KW_GAINS not in action_taken): done_resolving = True elif (last_play == dominioncards.Storeroom and KW_DISCARDS not in action_taken and KW_DRAWS not in action_taken and KW_SHUFFLES not in action_taken): turn_money += len(storeroom_discards) storeroom_discards = [] done_resolving = True if KW_PLAYS in action_taken: played = capture_cards(action_taken) ret[PLAYS].extend(played) # special cases for play in played: if play.is_action(): if not(play == dominioncards.Cultist and play == last_play) and play != dominioncards.BandofMisfits: action_counter -= 1 action_counter += play.num_plus_actions() if bom_choice is not None: if bom_plays == 0: bom_choice = None else: bom_plays -= 1 if dup_plays_remaining >= 0 and play != dominioncards.BandofMisfits: dup_plays_remaining -= 1 if dup_plays_remaining < 0: done_self_trashing = False if play.is_treasure(): phase = BUY_PHASE elif (last_play == dominioncards.ThroneRoom or last_play == dominioncards.Procession or last_play == dominioncards.Golem): action_counter += 2 elif last_play == dominioncards.KingsCourt: action_counter += 3 if play == dominioncards.PoorHouse: turn_money += 4 # Subtraction will happen later elif play == dominioncards.ThroneRoom or play == dominioncards.Procession: dup_plays_remaining = 2 elif play == dominioncards.KingsCourt: dup_plays_remaining = 3 elif play == dominioncards.Madman and dup_plays_remaining <= 0: ret[RETURNS].append(play) elif play == dominioncards.Spoils: # Spoils always get returned on play... # ...unless it's Counterfeited. # Technically, this clause will be incorrect if someone # plays a counterfeit, selects no treasure to counterfeit, # and then just plays a spoils. if (last_play != dominioncards.Counterfeit or done_resolving): ret[RETURNS].append(play) elif play == dominioncards.TradeRoute: turn_money += len(trade_route_set) elif play == dominioncards.Tournament: turn_money += 1 # Might be canceled out later elif play == dominioncards.Diadem: turn_money += action_counter elif play == dominioncards.BandofMisfits: bom_processioned = False if last_play == dominioncards.ThroneRoom: bom_plays = 2 elif last_play == dominioncards.Procession: bom_plays = 2 bom_processioned = True elif last_play == dominioncards.KingsCourt: bom_plays = 3 else: bom_plays = 1 elif play == dominioncards.Conspirator and len(ret[PLAYS]) > 2: action_counter += 1 elif (play == dominioncards.Crossroads and ret[PLAYS].count(dominioncards.Crossroads) == 1): action_counter += 3 elif play == dominioncards.City: if len(empty_piles(removed_from_supply, n_players)) >= 2: turn_money += 1 last_play = play done_resolving = False continue if KW_BUYS in action_taken: buys = capture_cards(action_taken) ret[BUYS].extend(buys) # easier to deal with the on-buy attack by making it a fake play if dominioncards.NobleBrigand in buys: last_play == dominioncards.NobleBrigand done_resolving = False continue if KW_RETURNS in action_taken: returned = capture_cards(action_taken) ret[RETURNS].extend(returned) for r in returned: removed_from_supply[r] -= 1 continue if KW_GAINS in action_taken: gained = capture_cards(action_taken) trade_route_set.update([g for g in gained if g.is_victory()]) if active_player == ret[NAME]: ret[GAINS].extend(gained) if(not done_resolving and (last_play == dominioncards.Thief or last_play == dominioncards.NobleBrigand) and dominioncards.Mercenary not in gained): for c in gained: if not c.is_treasure(): done_resolving = True else: if c in trash_pile: # Early goko logs have bugs with who reported # trashing cards. trash_pile.remove(c) if(not done_resolving and (last_play == dominioncards.Rogue or last_play == dominioncards.Graverobber) and dominioncards.Mercenary not in gained): for c in gained: if c in trash_pile: # BoM as death cart is ambiguous trash_pile.remove(c) done_resolving = True else: for g in gained: removed_from_supply[g] += 1 if (last_play == dominioncards.Ironworks and not done_resolving): if gained[0].is_treasure(): turn_money += 1 if gained[0].is_action(): action_counter += 1 done_resolving = True else: opp_turn_info[str(names_list.index(active_player))][GAINS].extend(gained) for g in gained: removed_from_supply[g] += 1 continue # Some old Goko logs mis-attribute trashing from attacks. I'm not # going to special-case all the various goko bugs that have since been # fixed, though. So there will be bugs with some old logs. if KW_TRASHES in action_taken: trashed = capture_cards(action_taken) if active_player == ret[NAME]: # Making TR-feast not doublecount Feast trashing if(last_play not in trashed or not last_play.can_trash_self()): done_self_trashing = False if (dup_plays_remaining >= 0 and last_play in trashed and done_self_trashing and last_play.can_trash_self()): trashed.remove(last_play) #TR+feast if (last_play in trashed and dup_plays_remaining > 0 and last_play.can_trash_self()): done_self_trashing = True if (last_play == dominioncards.MiningVillage and not done_resolving and dominioncards.MiningVillage in trashed): turn_money += 2 elif (last_play == dominioncards.Moneylender and not done_resolving and dominioncards.Copper in trashed): turn_money += 3 elif last_play == dominioncards.Salvager and not done_resolving: turn_money += trashed[0].coin_cost elif last_play == dominioncards.Mercenary and not done_resolving: trashed_to_mercenary += len(trashed) if trashed_to_mercenary == 2: turn_money += 2 done_resolving = True trashed_to_mercenary = 0 while dominioncards.Fortress in trashed: trashed.remove(dominioncards.Fortress) if trashed == bom_choice and (bom_choice[0].can_trash_self() or bom_processioned): trashed = [dominioncards.BandofMisfits] if (bom_choice is not None and dominioncards.TreasureMap in bom_choice and dominioncards.TreasureMap in trashed): trashed.remove(dominioncards.TreasureMap) trashed.extend([dominioncards.BandofMisfits]) if POSSESSION not in ret: ret[TRASHES].extend(trashed) trash_pile.extend(trashed) if last_play == dominioncards.Forager and not done_resolving: turn_money +=sum([d.is_treasure() for d in set(trash_pile)]) done_resolving = True else: while dominioncards.Fortress in trashed: trashed.remove(dominioncards.Fortress) opp_turn_info[str(names_list.index(active_player))][TRASHES].extend(trashed) trash_pile.extend(trashed) if last_play in [dominioncards.MiningVillage, dominioncards.Forager, dominioncards.Salvager]: done_resolving = True continue match = USES_COIN_TOKENS_RE.match(action_taken) if match: turn_money += int(match.group(1)) turn_coin_tokens -= int(match.group(1)) continue match = RECEIVES_COIN_TOKENS_RE.match(action_taken) if match: turn_coin_tokens += int(match.group(1)) continue if KW_PASSES in action_taken: passed_cards = capture_cards(action_taken) receiver = masq_targets[active_player] if active_player == ret[NAME]: ret[PASSES].extend(passed_cards) else: opp_turn_info[str(names_list.index(active_player))][PASSES].extend(passed_cards) if receiver == ret[NAME]: ret[RECEIVES].extend(passed_cards) else: opp_turn_info[str(names_list.index(receiver))][RECEIVES].extend(passed_cards) continue if KW_DURATION in action_taken: duration = capture_cards(action_taken) durations.extend(duration) for d in duration: if d in [dominioncards.FishingVillage, dominioncards.Tactician]: action_counter += 1 continue if (KW_CHOOSES_TWO_CARDS_AND_ONE_ACTION in action_taken or KW_RECEIVES_ONE_ACTION in action_taken): action_counter += 1 continue if KW_CHOOSES_TWO_COINS in action_taken: turn_money += 2 continue if KW_CHOOSES in action_taken: if last_play == dominioncards.BandofMisfits and not done_resolving: bom_choice = capture_cards(action_taken) if bom_choice[0] == dominioncards.Knights: bom_choice[0] == dominioncards.SirMartin continue if KW_PIRATE_COIN in action_taken: ps_tokens += 1 continue if KW_VP_CHIPS in action_taken: vp_chips_match = VP_CHIPS_RE.match(action_taken) vp_tokens += int(vp_chips_match.group(1)) continue match = TAKES_COINS_RE.match(action_taken) if match: turn_money += int(match.group(1)) continue match = RECEIVES_COINS_RE.match(action_taken) if match: turn_money += int(match.group(1)) continue match = TAKES_ACTIONS_RE.match(action_taken) if match: action_counter += int(match.group(1)) continue match = RECEIVES_ACTIONS_RE.match(action_taken) if match: action_counter += int(match.group(1)) continue if KW_DISCARDS in action_taken or KW_DISCARDS_C in action_taken: if (dominioncards.Estate in capture_cards(action_taken) and last_play == dominioncards.Baron and not done_resolving): turn_money += 4 done_resolving = True elif (last_play == dominioncards.SecretChamber): turn_money += len(capture_cards(action_taken)) elif (last_play == dominioncards.Vault and active_player == ret[NAME]): turn_money += len(capture_cards(action_taken)) elif (last_play == dominioncards.Storeroom and not done_resolving): storeroom_discards.extend(capture_cards(action_taken)) continue if (KW_REVEALS_HAND in action_taken): if last_play == dominioncards.PoorHouse and not done_resolving: turn_money -= len([tr for tr in capture_cards(action_taken) if tr.is_treasure()]) if turn_money < 0: turn_money = 0 continue if (KW_REVEALS in action_taken or KW_REVEALS_C in action_taken): if last_play == dominioncards.Harvest: harvest_reveal.extend(capture_cards(action_taken)) elif last_play == dominioncards.Herald: c = capture_cards(action_taken) if len(c) > 0: if c[0].is_action(): action_counter += 1 if bom_plays > 0: bom_plays += 1 if dup_plays_remaining > 0: dup_plays_remaining += 1 elif last_play == dominioncards.Golem: action_counter += len([c for c in capture_cards(action_taken)if (c.is_action() and not c == dominioncards.Golem)]) elif (last_play == dominioncards.Tournament and not done_resolving and active_player != ret[NAME] and dominioncards.Province in capture_cards(action_taken)): turn_money -= 1 done_resolving = True elif (last_play == dominioncards.Ironmonger and not done_resolving): if capture_cards(action_taken)[0].is_treasure(): turn_money += 1 if capture_cards(action_taken)[0].is_action(): action_counter += 1 done_resolving = True continue if KW_DRAWS in action_taken: if last_play == dominioncards.Storeroom: storeroom_discards = [] continue # All remaining actions should be captured; the next few statements # are those which are not logged in any way (though they could be!) if (KW_LOOKS_AT in action_taken or KW_RECEIVES in action_taken or KW_PLACES in action_taken or KW_SETS_ASIDE in action_taken or KW_TAKES in action_taken or KW_EMBARGOES in action_taken or KW_OVERPAYS in action_taken or KW_NAMES in action_taken or KW_CARDS_IN_DISCARDS in action_taken or KW_APPLIED in action_taken or KW_APPLIES_WHEN_TRASHED in action_taken or KW_MOVES in action_taken or KW_MOVES_DECK_TO_DISCARD in action_taken or KW_SCHEME_CHOICE in action_taken or KW_SHUFFLES in action_taken or KW_TAKES_SET_ASIDE in action_taken): continue raise parse_common.BogusGameError('Line did not match any keywords!')