def get_center_wolf_statements( player_obj: Any, knowledge_base: KnowledgeBase) -> tuple[Statement, ...]: """Center Wolf Player logic.""" statements: tuple[Statement, ...] = () player_index = player_obj.player_index wolf_indices = player_obj.wolf_indices center_role = player_obj.center_role center_index = player_obj.center_index if center_role is Role.VILLAGER: statements += Villager.get_villager_statements(player_index) elif center_role is Role.HUNTER: statements += Hunter.get_hunter_statements(player_index) elif center_role is Role.INSOMNIAC: # TODO check for switches and prioritize those statements statements += Insomniac.get_insomniac_statements( player_index, Role.INSOMNIAC) elif center_role is Role.ROBBER: for i, stated_role in enumerate(knowledge_base.stated_roles): if stated_role not in (Role.NONE, Role.ROBBER): statements += Robber.get_robber_statements( player_index, i, stated_role) elif center_role is Role.TROUBLEMAKER: num_stated = len(knowledge_base.stated_roles) for i in range(num_stated): for j in range(i + 1, num_stated): if j not in wolf_indices: statements += Troublemaker.get_troublemaker_statements( player_index, i, j) elif center_role is Role.DRUNK: statements += Drunk.get_drunk_statements(player_index, center_index) elif center_role is Role.SEER: for i, stated_role in enumerate(knowledge_base.stated_roles): # 'Hey, I'm a Seer and I saw another Seer...' if i not in wolf_indices and stated_role not in (Role.NONE, Role.SEER): statements += Seer.get_seer_statements(player_index, (i, stated_role)) for role2 in const.SORTED_ROLE_SET: for cent1 in range(const.NUM_CENTER): if cent1 != center_index: for role1 in const.SORTED_ROLE_SET: if role1 is not Role.SEER and role2 is not Role.SEER: if role1 != role2 or const.ROLE_COUNTS[role1] >= 2: statements += Seer.get_seer_statements( player_index, (cent1 + const.NUM_PLAYERS, role1), (center_index, role2), ) elif center_role is Role.DOPPELGANGER: statements += Doppelganger.get_doppelganger_statements( player_index, center_role) elif center_role in const.EVIL_ROLES: pass else: raise UnhandledEnumValueError(center_role) return statements
def test_lru_cache_hit() -> None: """Correctly cache a function call cache hit.""" result_1 = Seer.get_seer_statements(1, (6, Role.ROBBER)) result_2 = Seer.get_seer_statements(1, (6, Role.ROBBER)) info = Seer.get_seer_statements.cache_info() assert result_1 == result_2 assert info.hits == 1 assert info.misses == 1 assert info.currsize == 1
def test_json_repr(example_small_saved_game: SavedGame) -> None: """Should convert a SavedGame into a dict with all of its fields.""" result = example_small_saved_game.json_repr() assert result == { "all_statements": ( Statement("I am a Villager.", ((0, frozenset({Role.VILLAGER})), )), Statement( "I am a Robber and I swapped with Player 2. I am now a Seer.", ((1, frozenset({Role.ROBBER})), (2, frozenset({Role.SEER}))), ((SwitchPriority.ROBBER, 1, 2), ), ), Statement( "I am a Seer and I saw that Player 1 was a Robber.", ((2, frozenset({Role.SEER})), (1, frozenset({Role.ROBBER}))), ), ), "game_roles": (Role.VILLAGER, Role.SEER, Role.ROBBER), "original_roles": (Role.VILLAGER, Role.ROBBER, Role.SEER), "player_objs": ( Villager(0), Robber(1, 2, Role.SEER), Seer(2, (1, Role.ROBBER)), ), "type": "SavedGame", }
def test_medium_individual_preds( medium_game_roles: tuple[Role, ...], medium_statement_list: tuple[Statement, ...], ) -> None: """Should get the individual predictions for all players.""" player_objs: tuple[Player, ...] = ( Seer(0, (2, Role.DRUNK)), Wolf(1, (1, ), 5, Role.TROUBLEMAKER), Drunk(2, 5), Robber(3, 1, Role.WOLF), Minion(4, (1, )), ) result = one_night.get_individual_preds(player_objs, medium_statement_list) assert result == ( ( Role.SEER, Role.MINION, Role.TROUBLEMAKER, Role.DRUNK, Role.WOLF, Role.ROBBER, ), ( Role.MINION, Role.SEER, Role.TROUBLEMAKER, Role.WOLF, Role.ROBBER, Role.DRUNK, ), ( Role.SEER, Role.MINION, Role.TROUBLEMAKER, Role.DRUNK, Role.WOLF, Role.ROBBER, ), ( Role.SEER, Role.MINION, Role.TROUBLEMAKER, Role.DRUNK, Role.WOLF, Role.ROBBER, ), ( Role.MINION, Role.WOLF, Role.TROUBLEMAKER, Role.DRUNK, Role.SEER, Role.ROBBER, ), )
def test_get_seer_statements() -> None: """Execute initialization actions and return the possible statements.""" player_index = 1 result = Seer.get_seer_statements(player_index, (6, Role.ROBBER)) assert result == (Statement( "I am a Seer and I saw that Player 6 was a Robber.", ((1, frozenset({Role.SEER})), (6, frozenset({Role.ROBBER}))), ), )
def test_find_evil_players(medium_game_roles: tuple[str, ...]) -> None: """Should determine if a player has turned evil after night falls.""" player_list: tuple[Player, ...] = ( Seer(0, (2, Role.DRUNK)), Wolf(1, (1, ), 5, Role.TROUBLEMAKER), Drunk(2, 5), Robber(3, 2, Role.DRUNK), Minion(4, (1, )), ) result = [player.is_evil() for player in player_list] assert result == [False, True, False, False, True]
def example_small_saved_game(small_game_roles: tuple[Role, ...]) -> SavedGame: return SavedGame( (Role.VILLAGER, Role.ROBBER, Role.SEER), (Role.VILLAGER, Role.SEER, Role.ROBBER), ( Statement("I am a Villager.", ((0, frozenset({Role.VILLAGER})),)), Statement( "I am a Robber and I swapped with Player 2. I am now a Seer.", ((1, frozenset({Role.ROBBER})), (2, frozenset({Role.SEER}))), ((SwitchPriority.ROBBER, 1, 2),), ), Statement( "I am a Seer and I saw that Player 1 was a Robber.", ((2, frozenset({Role.SEER})), (1, frozenset({Role.ROBBER}))), ), ), (Villager(0), Robber(1, 2, Role.SEER), Seer(2, (1, Role.ROBBER), (None, None))), )
def test_default_saved_game(example_small_saved_game: SavedGame) -> None: """Should convert objects of different types to JSON.""" player_objs = [ Villager(0), Robber(1, 2, Role.SEER), Seer(2, (1, Role.ROBBER)) ] player_strs = ", ".join( [json.dumps(player, cls=WolfBotEncoder) for player in player_objs]) statement_objs = [ Statement( "I am a Villager.", ((0, frozenset({Role.VILLAGER})), ), speaker=Role.VILLAGER, ), Statement( "I am a Robber and I swapped with Player 2. I am now a Seer.", ((1, frozenset({Role.ROBBER})), (2, frozenset({Role.SEER}))), ((SwitchPriority.ROBBER, 1, 2), ), Role.ROBBER, ), Statement( "I am a Seer and I saw that Player 1 was a Robber.", ((2, frozenset({Role.SEER})), (1, frozenset({Role.ROBBER}))), speaker=Role.SEER, ), ] statement_strs = ", ".join([ json.dumps(statement, cls=WolfBotEncoder) for statement in statement_objs ]) result = json.dumps(example_small_saved_game, cls=WolfBotEncoder) assert result == ( '{"type": "SavedGame",' ' "original_roles": [{"type": "Role", "data": "Villager"},' ' {"type": "Role", "data": "Robber"}, {"type": "Role", "data": "Seer"}],' ' "game_roles": [{"type": "Role", "data": "Villager"},' ' {"type": "Role", "data": "Seer"}, {"type": "Role", "data": "Robber"}],' f' "all_statements": [{statement_strs}],' f' "player_objs": [{player_strs}]}}') reverted_result = json.loads(result, cls=WolfBotDecoder) assert reverted_result == example_small_saved_game
def test_awake_init_player_choice( large_game_roles: tuple[Role, ...]) -> None: """ Should initialize a Seer. Note that the player_index of the Seer is not necessarily the index where the true Seer is located. """ player_index = 11 const.CENTER_SEER_PROB = 0 game_roles = list(large_game_roles) expected = (Statement( "I am a Seer and I saw that Player 6 was a Mason.", ((11, frozenset({Role.SEER})), (6, frozenset({Role.MASON}))), ), ) seer = Seer.awake_init(player_index, game_roles) assert seer.choice_1 == (6, Role.MASON) assert seer.choice_2 == (None, None) assert seer.statements == expected
def example_medium_saved_game(medium_game_roles: tuple[Role, ...]) -> SavedGame: return SavedGame( (Role.SEER, Role.WOLF, Role.DRUNK, Role.ROBBER, Role.MINION, Role.TROUBLEMAKER), (Role.SEER, Role.WOLF, Role.TROUBLEMAKER, Role.DRUNK, Role.MINION, Role.ROBBER), ( Statement( "I am a Seer and I saw that Player 2 was a Drunk.", ((0, frozenset({Role.SEER})), (2, frozenset({Role.DRUNK}))), ), Statement( "I am a Robber and I swapped with Player 0. I am now a Seer.", ((1, frozenset({Role.ROBBER})), (0, frozenset({Role.SEER}))), ((SwitchPriority.ROBBER, 1, 0),), ), Statement( "I am a Drunk and I swapped with Center 0.", ((2, frozenset({Role.DRUNK})),), ((SwitchPriority.DRUNK, 2, 5),), ), Statement( "I am a Robber and I swapped with Player 2. I am now a Drunk.", ((3, frozenset({Role.ROBBER})), (2, frozenset({Role.DRUNK}))), ((SwitchPriority.ROBBER, 3, 2),), ), Statement( "I am a Seer and I saw that Player 3 was a Robber.", ((4, frozenset({Role.SEER})), (3, frozenset({Role.ROBBER}))), ), ), ( Seer(0, (2, Role.DRUNK)), Wolf(1, (1,), 5, Role.TROUBLEMAKER), Drunk(2, 5), Robber(3, 2, Role.DRUNK), Minion(4, (1,)), ), )
def test_awake_init_center_choice( large_game_roles: tuple[Role, ...]) -> None: """ Should initialize a Seer. Note that the player_index of the Seer is not necessarily the index where the true Seer is located. """ player_index = 11 const.CENTER_SEER_PROB = 1 game_roles = list(large_game_roles) expected = (Statement( ("I am a Seer and I saw that Center 1 was a Insomniac " "and that Center 0 was a Troublemaker."), ( (11, frozenset({Role.SEER})), (13, frozenset({Role.INSOMNIAC})), (12, frozenset({Role.TROUBLEMAKER})), ), ), ) seer = Seer.awake_init(player_index, game_roles) assert seer.choice_1 == (13, Role.INSOMNIAC) assert seer.choice_2 == (12, Role.TROUBLEMAKER) assert seer.statements == expected
def test_get_all_statements() -> None: """Should return possible statements from all possible initializations.""" player_index = 1 set_roles(Role.WOLF, Role.SEER, Role.VILLAGER, Role.WOLF) const.NUM_PLAYERS = 2 const.NUM_CENTER = 2 expected = ( Statement( "I am a Seer and I saw that Player 0 was a Villager.", ((1, frozenset({Role.SEER})), (0, frozenset({Role.VILLAGER}))), ), Statement( "I am a Seer and I saw that Player 1 was a Villager.", ((1, frozenset({Role.SEER})), (1, frozenset({Role.VILLAGER}))), ), Statement( "I am a Seer and I saw that Player 0 was a Wolf.", ((1, frozenset({Role.SEER})), (0, frozenset({Role.WOLF}))), ), Statement( "I am a Seer and I saw that Player 1 was a Wolf.", ((1, frozenset({Role.SEER})), (1, frozenset({Role.WOLF}))), ), Statement( "I am a Seer and I saw that Player 0 was a Seer.", ((1, frozenset({Role.SEER})), (0, frozenset({Role.SEER}))), ), Statement( "I am a Seer and I saw that Player 1 was a Seer.", ((1, frozenset({Role.SEER})), (1, frozenset({Role.SEER}))), ), Statement( ("I am a Seer and I saw that Center 0 was a Villager and " "that Center 1 was a Wolf."), ( (1, frozenset({Role.SEER})), (2, frozenset({Role.VILLAGER})), (3, frozenset({Role.WOLF})), ), ), Statement( ("I am a Seer and I saw that Center 0 was a Wolf and " "that Center 1 was a Villager."), ( (1, frozenset({Role.SEER})), (2, frozenset({Role.WOLF})), (3, frozenset({Role.VILLAGER})), ), ), Statement( ("I am a Seer and I saw that Center 0 was a Wolf " "and that Center 1 was a Wolf."), ( (1, frozenset({Role.SEER})), (2, frozenset({Role.WOLF})), (3, frozenset({Role.WOLF})), ), ), ) result = Seer.get_all_statements(player_index) assert set(result) == set(expected)
def example_large_saved_game(large_game_roles: tuple[Role, ...]) -> SavedGame: mason_roles = tuple( (i, const.ROLE_SET - frozenset({Role.MASON})) for i in range(const.NUM_PLAYERS) if i != 2 ) return SavedGame( ( Role.VILLAGER, Role.DRUNK, Role.MASON, Role.TANNER, Role.VILLAGER, Role.ROBBER, Role.SEER, Role.WOLF, Role.MINION, Role.VILLAGER, Role.WOLF, Role.HUNTER, Role.TROUBLEMAKER, Role.MASON, Role.INSOMNIAC, ), ( Role.VILLAGER, Role.INSOMNIAC, Role.MASON, Role.TANNER, Role.VILLAGER, Role.DRUNK, Role.SEER, Role.WOLF, Role.MINION, Role.VILLAGER, Role.WOLF, Role.HUNTER, Role.TROUBLEMAKER, Role.MASON, Role.ROBBER, ), ( Statement("I am a Villager.", ((0, frozenset({Role.VILLAGER})),)), Statement( "I am a Drunk and I swapped with Center 2.", ((1, frozenset({Role.DRUNK})),), ((SwitchPriority.DRUNK, 1, 14),), ), Statement( "I am a Mason. The other Mason is not present.", ((2, frozenset({Role.MASON})),) + mason_roles, ), Statement( "I am a Robber and I swapped with Player 10. I am now a Insomniac.", ((3, frozenset({Role.ROBBER})), (10, frozenset({Role.INSOMNIAC}))), ((SwitchPriority.ROBBER, 3, 10),), ), Statement("I am a Villager.", ((4, frozenset({Role.VILLAGER})),)), Statement( "I am a Robber and I swapped with Player 1. I am now a Drunk.", ((5, frozenset({Role.ROBBER})), (1, frozenset({Role.DRUNK}))), ((SwitchPriority.ROBBER, 5, 1),), ), Statement( ( "I am a Seer and I saw that Center 1 was a Mason and that " "Center 0 was a Troublemaker." ), ( (6, frozenset({Role.SEER})), (13, frozenset({Role.MASON})), (12, frozenset({Role.TROUBLEMAKER})), ), ), Statement( "I am a Seer and I saw that Player 3 was a Robber.", ((7, frozenset({Role.SEER})), (3, frozenset({Role.ROBBER}))), ), Statement( "I am a Troublemaker and I swapped Player 0 and Player 1.", ((8, frozenset({Role.TROUBLEMAKER})),), ((SwitchPriority.TROUBLEMAKER, 0, 1),), ), Statement("I am a Villager.", ((9, frozenset({Role.VILLAGER})),)), Statement( "I am a Troublemaker and I swapped Player 3 and Player 4.", ((10, frozenset({Role.TROUBLEMAKER})),), ((SwitchPriority.TROUBLEMAKER, 3, 4),), ), Statement("I am a Hunter.", ((11, frozenset({Role.HUNTER})),)), ), ( Villager(0), Drunk(1, 14), Mason(2, (2,)), Tanner(3), Villager(4), Robber(5, 1, Role.DRUNK), Seer(6, (13, Role.MASON), (12, Role.TROUBLEMAKER)), Wolf(7, (7, 10), None, None), Minion(8, (7, 10)), Villager(9), Wolf(10, (7, 10), None, None), Hunter(11), ), )
def get_wolf_statements( player_obj: Any, knowledge_base: KnowledgeBase ) -> tuple[Statement, ...]: """ Gets Regular Wolf statement, which chooses Villager statements with hardcoded logic to maximize Wolf win rate. """ statements: tuple[Statement, ...] = () stated_roles = knowledge_base.stated_roles player_index = player_obj.player_index counts_dict = Counter( { role: count for role, count in const.ROLE_COUNTS.items() if role in const.VILLAGE_ROLES } ) - Counter(stated_roles) if Role.VILLAGER in const.ROLE_SET and should_include_role( counts_dict, Role.VILLAGER ): statements += Villager.get_villager_statements(player_index) if Role.HUNTER in const.ROLE_SET and should_include_role(counts_dict, Role.HUNTER): statements += Hunter.get_hunter_statements(player_index) if Role.INSOMNIAC in const.ROLE_SET and should_include_role( counts_dict, Role.INSOMNIAC ): # TODO check for switches and prioritize those statements statements += Insomniac.get_insomniac_statements(player_index, Role.INSOMNIAC) if Role.MASON in const.ROLE_SET and should_include_role(counts_dict, Role.MASON): # Only say you are a Mason if you are the last player and there are no Masons. if player_index == const.NUM_PLAYERS - 1 and Role.MASON not in stated_roles: statements += Mason.get_mason_statements(player_index, (player_index,)) if Role.DRUNK in const.ROLE_SET and should_include_role(counts_dict, Role.DRUNK): statements += Drunk.get_all_statements(player_index) if Role.TROUBLEMAKER in const.ROLE_SET and should_include_role( counts_dict, Role.TROUBLEMAKER ): num_stated = len(stated_roles) for i in range(num_stated): for j in range(i + 1, num_stated): # Do not reference a Wolf as the second index. if j not in player_obj.wolf_indices: statements += Troublemaker.get_troublemaker_statements( player_index, i, j ) if Role.ROBBER in const.ROLE_SET and should_include_role(counts_dict, Role.ROBBER): for i, stated_role in enumerate(stated_roles): if stated_role not in (Role.NONE, Role.ROBBER): # Only say you robbed a Seer if the real # Seer did not reference a Robber. if stated_role is Role.SEER: use_index = True for _, poss_set in knowledge_base.all_statements[i].knowledge: if Role.ROBBER in poss_set: use_index = False break if not use_index: continue statements += Robber.get_robber_statements(player_index, i, stated_role) if Role.SEER in const.ROLE_SET and should_include_role(counts_dict, Role.SEER): for i, stated_role in enumerate(stated_roles): # 'Hey, I'm a Seer and I saw another Seer...' if ( stated_role not in (Role.NONE, Role.SEER) and i not in player_obj.wolf_indices ): statements += Seer.get_seer_statements(player_index, (i, stated_role)) return statements