def get_player_multistatements( player_objs: tuple[Player, ...] ) -> tuple[Statement, ...]: """ Returns array of each player's statements. TODO players should choose with what priority they want to speak """ knowledge_base = KnowledgeBase() heap = [(i, i) for i in range(const.NUM_PLAYERS)] # (priority, player_index) tuples heapq.heapify(heap) while heap: _, curr_ind = heapq.heappop(heap) if knowledge_base.final_claims[curr_ind].priority < StatementLevel.PRIMARY: player_objs[curr_ind].analyze(knowledge_base) statement = player_objs[curr_ind].get_statement(knowledge_base) player_objs[curr_ind].prev_priority = statement.priority knowledge_base.add(statement, curr_ind) logger.info(f"Player {curr_ind}: {statement.sentence}") if statement.priority < StatementLevel.PRIMARY: # New statements have priority at least NUM_PLAYERS # to ensure everyone spoke once new_priority = const.NUM_PLAYERS + random.randrange(len(heap) + 1) heapq.heappush(heap, (new_priority, curr_ind)) return tuple(knowledge_base.final_claims)
def print_statements(all_statements: tuple[Statement, ...]) -> None: """Prints all statements that have been said so far.""" if DISABLE_USER_INPUT: return logger.info("\n-- GAME BEGINS --\n", cache=True) for j, statement in enumerate(all_statements): logger.info(f"Player {j}: {statement.sentence}", cache=True) UserState.print_cache()
def awake_init(cls, player_index: int, game_roles: list[Role]) -> Minion: """Initializes Minion - gets Wolf indices.""" is_user = const.IS_USER[player_index] wolf_indices = find_all_player_indices(game_roles, Role.WOLF) logger.debug(f"[Hidden] Wolves are at indices: {list(wolf_indices)}") if is_user: logger.info(f"Wolves are at indices: {list(wolf_indices)}", cache=True) return cls(player_index, wolf_indices)
def awake_init(cls, player_index: int, game_roles: list[Role]) -> Insomniac: """Initializes Insomniac - learns new role.""" is_user = const.IS_USER[player_index] insomniac_new_role = game_roles[player_index] logger.debug(f"[Hidden] Insomniac wakes up as a {insomniac_new_role}.") if is_user: logger.info(f"You woke up as a {insomniac_new_role}!", cache=True) return cls(player_index, insomniac_new_role)
def awake_init(cls, player_index: int, game_roles: list[Role]) -> Drunk: """Initializes Drunk - switches with a card in the center.""" is_user = const.IS_USER[player_index] choice_ind = get_center(is_user) logger.debug( f"[Hidden] Drunk switches with Center Card {choice_ind - const.NUM_PLAYERS}" f" and unknowingly becomes a {game_roles[choice_ind]}.") if is_user: logger.info("You do not know your new role.", cache=True) swap_characters(game_roles, player_index, choice_ind) return cls(player_index, choice_ind)
def get_player_statements(player_objs: tuple[Player, ...]) -> tuple[Statement, ...]: """Returns array of each player's statements.""" knowledge_base = KnowledgeBase() curr_ind = 0 while curr_ind < const.NUM_PLAYERS: player_objs[curr_ind].analyze(knowledge_base) statement = player_objs[curr_ind].get_statement(knowledge_base) knowledge_base.add(statement, curr_ind) logger.info(f"Player {curr_ind}: {statement.sentence}") curr_ind += 1 return tuple(knowledge_base.final_claims)
def intro(original_roles: Sequence[Role]) -> None: """Tells the player what their assigned role is.""" if DISABLE_USER_INPUT: return input("Press Enter to continue...") logger.info(CLEAR_TERMINAL) logger.clear() user_index = const.IS_USER.index(True) logger.info( f"Player {user_index}, you are a {original_roles[user_index]}!", cache=True ) UserState.print_cache()
def get_player(is_user: bool, exclude: tuple[int, ...] = ()) -> int: """Gets a random player index (not in the center) or prompts the user.""" if is_user: choice_ind = -1 while choice_ind < 0 or choice_ind >= const.NUM_PLAYERS: user_input = "" while not user_input.isdigit(): user_input = input( f"Which player index (0 - {const.NUM_PLAYERS - 1})? ") choice_ind = int(user_input) if choice_ind in exclude: logger.info("You cannot choose yourself or an index twice.") choice_ind = -1 return choice_ind return random.choice( [i for i in range(const.NUM_PLAYERS) if i not in exclude])
def awake_init(cls, player_index: int, game_roles: list[Role]) -> Troublemaker: """Initializes Troublemaker - switches one player with another player.""" is_user = const.IS_USER[player_index] if is_user: logger.info("Choose two players to switch places:") choice_1 = get_player(is_user, (player_index,)) choice_2 = get_player(is_user, (player_index, choice_1)) swap_characters(game_roles, choice_1, choice_2) logger.debug( f"[Hidden] Troublemaker switches Player {choice_1} and Player {choice_2}." ) if is_user: logger.info( f"You switch Player {choice_1} with Player {choice_2}.", cache=True ) return cls(player_index, choice_1, choice_2)
def print_game_result(game_roles: tuple[Role, ...], winning_team: Team) -> None: if DISABLE_USER_INPUT: return user_index = const.IS_USER.index(True) player_role = game_roles[user_index] if ( (winning_team == Team.VILLAGE and player_role in const.VILLAGE_ROLES) or (winning_team == Team.WEREWOLF and player_role in const.EVIL_ROLES) or (winning_team == Team.TANNER and player_role == Role.TANNER) ): outcome = "won" else: outcome = "lost" logger.info( f"You were a {player_role} at the end of the game, " f"so you {outcome}!" )
def get_center(is_user: bool, exclude: tuple[int, ...] = ()) -> int: """Gets a random index of a center card or prompts the user.""" if is_user: choice_ind = -1 while choice_ind < 0 or choice_ind >= const.NUM_CENTER: user_input = "" while not user_input.isdigit(): user_input = input( f"Which center card (0 - {const.NUM_CENTER - 1})? ") choice_ind = int(user_input) if choice_ind + const.NUM_PLAYERS in exclude: logger.info("You cannot choose an index twice.") choice_ind = -1 return choice_ind + const.NUM_PLAYERS return random.choice([ const.NUM_PLAYERS + i for i in range(const.NUM_CENTER) if const.NUM_PLAYERS + i not in exclude ])
def get_statement_rl(player_obj: Any, knowledge_base: KnowledgeBase, default_answer: Statement) -> Statement: """Gets Reinforcement Learning Wolf statement.""" statements = get_wolf_statements(player_obj, knowledge_base) # Necessary to put this here to avoid circular import from wolfbot.encoder import WolfBotDecoder exp_dict: dict[str, dict[Statement, int]] = {} with open(const.EXPERIENCE_PATH, encoding="utf-8") as exp_file: exp_dict = json.load(exp_file, cls=WolfBotDecoder) experience = defaultdict(lambda: defaultdict(int), exp_dict) if not experience: logger.info("Experience dict did not load properly.") state = (tuple(player_obj.wolf_indices), tuple(knowledge_base.all_statements)) scores = experience[str(state)] logger.info(f"Experience dict loaded.\n {len(scores)} matching states.") best_choice = (default_answer, -100) for potential_statement, score in scores.items(): if score > best_choice[1]: best_choice = (potential_statement, score) if best_choice[0] is None: logger.info("Using default statement...") return default_answer for statement in statements: if best_choice[0] == statement: return statement raise RuntimeError("Reached end of rl_wolf code.")
def get_voting_result( player_objs: tuple[Player, ...], all_predictions: tuple[tuple[Role, ...], ...] ) -> tuple[tuple[Role, ...], tuple[int, ...], tuple[int, ...]]: """ Creates confidence levels for each prediction and takes most common role guess array as the final guess for that index. - guess_histogram stores counts of prediction arrays. - wolf_votes stores individual votes for Wolves. """ wolf_votes = [0] * const.NUM_PLAYERS if const.INTERACTIVE_MODE and const.INFLUENCE_PROB == 1: # Convince other players to vote with you. logger.info( "\nAll players trust you. Who should everyone vote for? " "(If you think there are no Wolves, vote for yourself.)" ) vote_ind = get_player(is_user=True) if vote_ind == const.IS_USER.index(True): wolf_votes = [1] * const.NUM_PLAYERS player_votes = [ (i + 1) % const.NUM_PLAYERS for i in range(const.NUM_PLAYERS) ] else: wolf_votes[vote_ind] += const.NUM_PLAYERS player_votes = [vote_ind] * const.NUM_PLAYERS else: player_votes = [] for i, prediction in enumerate(all_predictions): vote_ind = player_objs[i].vote(prediction) wolf_votes[vote_ind] += 1 player_votes.append(vote_ind) assert len(player_votes) == const.NUM_PLAYERS logger.info(f"\nVote Array: {wolf_votes}\n") [(avg_role_guesses, _)] = Counter(all_predictions).most_common(1) max_votes = max(wolf_votes) guessed_wolf_inds = [i for i, count in enumerate(wolf_votes) if count == max_votes] return avg_role_guesses, tuple(guessed_wolf_inds), tuple(player_votes)
def awake_init(cls, player_index: int, game_roles: list[Role]) -> Doppelganger: """Initializes Doppelganger - learns new role.""" from wolfbot.roles import get_role_obj is_user = const.IS_USER[player_index] choice_ind = get_player(is_user, (player_index, )) choice_char = game_roles[choice_ind] logger.debug(f"[Hidden] Doppelganger copies Player {choice_ind} " f"and becomes a {choice_char}.") if is_user: logger.info( f"You copied Player {choice_ind} and are now a {choice_char}!", cache=True, ) # Temporarily set Doppelganger in game_roles to the new role # so she wakes up with the other characters. game_roles[player_index] = choice_char if choice_char in (Role.WOLF, Role.MASON): get_role_obj(choice_char).awake_init(player_index, game_roles) # else do switches later return cls(player_index, choice_ind, choice_char)
def awake_init(cls, player_index: int, game_roles: list[Role]) -> Wolf: """ Constructor: original_roles defaults to [] when a player becomes a Wolf and realizes it. Initializes Wolf - gets Wolf indices and a random center card, if applicable. """ is_user = const.IS_USER[player_index] center_index, center_role = None, None wolf_indices = find_all_player_indices(game_roles, Role.WOLF) if len(wolf_indices) == 1 and const.NUM_CENTER > 0: center_index = get_center(is_user) center_role = game_roles[center_index] if is_user: logger.info( f"You see Center {center_index - const.NUM_PLAYERS} " f"is a {center_role}.", cache=True, ) logger.debug(f"[Hidden] Wolves are at indices: {list(wolf_indices)}") if is_user: logger.info(f"Wolves are at indices: {list(wolf_indices)}", cache=True) return cls(player_index, wolf_indices, center_index, center_role)
def eval_winning_team( game_roles: tuple[Role, ...], guessed_wolf_inds: list[int], player_votes: tuple[int, ...], ) -> Team: """Decide which team won based on the final vote.""" killed_wolf, killed_tanner, villager_win = False, False, False if len(guessed_wolf_inds) == const.NUM_PLAYERS: logger.info("No wolves were found.") if final_wolf_inds := find_all_player_indices(game_roles, Role.WOLF): logger.info(f"But Player(s) {list(final_wolf_inds)} was a Wolf!\n") else: logger.info("That was correct!\n") villager_win = True
def awake_init(cls, player_index: int, game_roles: list[Role]) -> Seer: """Initializes Seer - either sees 2 center cards or 1 player card.""" is_user = const.IS_USER[player_index] if const.NUM_CENTER > 1: if is_user: logger.info("Do you want to see 1 player card or 2 center cards?") choose_center = bool(get_numeric_input(1, 3) - 1) else: # Pick two center cards more often, because # that generally yields higher win rates. choose_center = weighted_coin_flip(const.CENTER_SEER_PROB) if choose_center: peek_ind1 = get_center(is_user) peek_ind2 = get_center(is_user, (peek_ind1,)) peek_char1 = game_roles[peek_ind1] peek_char2 = game_roles[peek_ind2] logger.debug( f"[Hidden] Seer sees that Center {peek_ind1 - const.NUM_PLAYERS} " f"is a {peek_char1}, Center {peek_ind2 - const.NUM_PLAYERS} " f"is a {peek_char2}." ) if is_user: logger.info( f"You see that Center {peek_ind1 - const.NUM_PLAYERS} " f"is a {peek_char1}, and " f"Center {peek_ind2 - const.NUM_PLAYERS} is a {peek_char2}.", cache=True, ) return cls( player_index, (peek_ind1, peek_char1), (peek_ind2, peek_char2) ) peek_ind = get_player(is_user, (player_index,)) peek_char = game_roles[peek_ind] logger.debug(f"[Hidden] Seer sees that Player {peek_ind} is a {peek_char}.") if is_user: logger.info(f"You see that Player {peek_ind} is a {peek_char}.", cache=True) return cls(player_index, (peek_ind, peek_char))
player_votes: tuple[int, ...], ) -> Team: """Decide which team won based on the final vote.""" killed_wolf, killed_tanner, villager_win = False, False, False if len(guessed_wolf_inds) == const.NUM_PLAYERS: logger.info("No wolves were found.") if final_wolf_inds := find_all_player_indices(game_roles, Role.WOLF): logger.info(f"But Player(s) {list(final_wolf_inds)} was a Wolf!\n") else: logger.info("That was correct!\n") villager_win = True else: # Hunter kills the player he voted for if he dies. for i in guessed_wolf_inds: logger.info( f"Player {i} was chosen as a Wolf.\nPlayer {i} was a {game_roles[i]}!\n" ) if game_roles[i] is Role.HUNTER: if player_votes[i] not in guessed_wolf_inds: guessed_wolf_inds.append(player_votes[i]) logger.info( f"(Player {i}) Hunter died and killed " f"Player {player_votes[i]} too!\n" ) elif game_roles[i] is Role.WOLF: killed_wolf = True elif game_roles[i] is Role.TANNER: killed_tanner = True if villager_win or killed_wolf: logger.info("Village Team wins!")
def print_cache() -> None: """Clears console and then output all lines stored in the logging cache.""" logger.info(CLEAR_TERMINAL) for log_level, line in logger.output_cache: logger.log(log_level, line)
def night_falls( original_roles: tuple[Role, ...] ) -> tuple[tuple[Player, ...], tuple[Role, ...]]: """ Initialize role object list and perform all switching and peeking actions. Roles that find other roles go first in AWAKE_ORDER (e.g. Masons), which means we don't need to pass in original_roles to these constructors. The Doppelganger can take actions after these role types complete. """ logger.info("\n-- NIGHT FALLS --\n") print_roles(original_roles, "Hidden") # Awaken each player in order and initialize the Player object. game_roles = list(original_roles) player_objs = cast(list[Player], [None] * const.NUM_PLAYERS) for awaken_role in const.AWAKE_ORDER: if awaken_role is Role.NONE and Role.DOPPELGANGER in const.ROLE_SET: logger.info("Doppelganger, wake up again!") # Revert Doppelganger roles back to normal, and perform any actions now. for i in range(const.NUM_PLAYERS): if original_roles[i] is Role.DOPPELGANGER: game_roles[i] = Role.DOPPELGANGER if player_objs[i].new_role in const.AWAKE_ORDER[5:-1]: role_obj = get_role_obj(player_objs[i].new_role) _ = role_obj.awake_init(i, game_roles) # TODO: save this info logger.info("Doppelganger, go back to sleep.\n") if awaken_role in const.ROLE_SET: logger.info(f"{awaken_role}, wake up.") role_obj = get_role_obj(awaken_role) for i in range(const.NUM_PLAYERS): if original_roles[i] is awaken_role: player_objs[i] = role_obj.awake_init(i, game_roles) logger.info(f"{awaken_role}, go to sleep.\n") # All other players wake up at the same time. logger.info("Everyone, wake up!\n") for i, role_name in enumerate(original_roles[: const.NUM_PLAYERS]): if role_name in const.ROLE_SET - set(const.AWAKE_ORDER): role_obj = get_role_obj(role_name) player_objs[i] = role_obj.awake_init(i, game_roles) UserState.night_falls() logger.info("\n-- GAME BEGINS --\n") return tuple(player_objs[: const.NUM_PLAYERS]), tuple(game_roles)