def test_invalid_state(example_statement: Statement) -> None: """Should return None for inconsistent states.""" start_state = SolverState((frozenset({Role.VILLAGER}),) * 3, path=(True,)) invalid_state = start_state.is_consistent(example_statement) assert invalid_state is None
def test_is_consistent_on_empty_state( example_small_solverstate: SolverState, example_statement: Statement ) -> None: """ Should check a new statement against an empty SolverState for consistency. """ start_state = SolverState() result = start_state.is_consistent(example_statement) assert result == example_small_solverstate
def example_medium_solved_list( medium_game_roles: tuple[Role, ...] ) -> tuple[SolverState, ...]: possible_roles_1 = ( frozenset({Role.SEER}), frozenset({Role.ROBBER, Role.TROUBLEMAKER, Role.WOLF, Role.DRUNK, Role.MINION}), frozenset({Role.DRUNK}), frozenset({Role.ROBBER}), frozenset({Role.ROBBER, Role.TROUBLEMAKER, Role.WOLF, Role.DRUNK, Role.MINION}), frozenset( { Role.ROBBER, Role.TROUBLEMAKER, Role.WOLF, Role.DRUNK, Role.SEER, Role.MINION, } ), ) possible_roles_2 = ( frozenset({Role.ROBBER, Role.TROUBLEMAKER, Role.WOLF, Role.DRUNK, Role.MINION}), frozenset({Role.WOLF}), frozenset({Role.DRUNK}), frozenset({Role.ROBBER}), frozenset({Role.SEER}), frozenset( { Role.ROBBER, Role.TROUBLEMAKER, Role.WOLF, Role.DRUNK, Role.SEER, Role.MINION, } ), ) return ( SolverState( possible_roles_1, ((SwitchPriority.DRUNK, 2, 5), (SwitchPriority.ROBBER, 3, 2)), (True, False, True, True, False), ), SolverState( possible_roles_2, ((SwitchPriority.DRUNK, 2, 5), (SwitchPriority.ROBBER, 3, 2)), (False, False, True, True, True), ), )
def test_repr() -> None: """Should pretty-print SolverStates using the custom formatter.""" result = SolverState((frozenset({Role.VILLAGER}),), path=(True,)) assert ( str(result) == repr(result) == ( "SolverState(\n" " possible_roles=(frozenset([Role.VILLAGER]),),\n" " path=(True,),\n" " role_counts={\n" " Role.INSOMNIAC: 1,\n" " Role.VILLAGER: 2,\n" " Role.ROBBER: 1,\n" " Role.DRUNK: 1,\n" " Role.WOLF: 2,\n" " Role.SEER: 1,\n" " Role.TANNER: 1,\n" " Role.MASON: 2,\n" " Role.MINION: 1,\n" " Role.TROUBLEMAKER: 1,\n" " Role.HUNTER: 1\n" " },\n" " count_true=1\n" ")" ) )
def test_solver_medium_known_true( medium_statement_list: tuple[Statement, ...], medium_game_roles: tuple[Role, ...], ) -> None: """Should return a SolverState with the most likely solution.""" possible_roles = ( frozenset({ Role.DRUNK, Role.MINION, Role.TROUBLEMAKER, Role.WOLF, Role.ROBBER }), frozenset({Role.SEER}), frozenset({Role.DRUNK}), frozenset({Role.MINION}), frozenset({ Role.DRUNK, Role.MINION, Role.TROUBLEMAKER, Role.WOLF, Role.ROBBER }), frozenset({ Role.ROBBER, Role.MINION, Role.TROUBLEMAKER, Role.SEER, Role.WOLF, Role.DRUNK, }), ) result = solvers.relaxed_solver(medium_statement_list, (1, )) assert (SolverState( possible_roles, ((SwitchPriority.DRUNK, 2, 5), ), (False, True, True, False, False), ) in result)
def test_get_empty_switch_dict(small_game_roles: tuple[Role, ...]) -> None: """Should return the identity switch dict.""" possible_roles = (frozenset({Role.ROBBER, Role.VILLAGER, Role.SEER}),) * 3 state = SolverState(possible_roles) result = predictions.get_switch_dict(state) assert result == {i: i for i in range(const.NUM_ROLES)}
def example_small_solverstate_solved(small_game_roles: tuple[Role, ...]) -> SolverState: possible_roles = ( frozenset({Role.VILLAGER}), frozenset({Role.ROBBER}), frozenset({Role.SEER}), ) return SolverState( possible_roles, ((SwitchPriority.ROBBER, 1, 2),), (True, True, True) )
def test_is_consistent_on_existing_state( example_medium_solverstate: SolverState, ) -> None: """ Should check a new statement against accumulated statements for consistency. Should not change result.path - that is done in the switching_solver function. """ possible_roles = (frozenset({Role.SEER}),) + (const.ROLE_SET,) * ( const.NUM_ROLES - 1 ) example_solverstate = SolverState(possible_roles, path=(True,)) new_statement = Statement( "next", ((2, frozenset({Role.DRUNK})),), ((SwitchPriority.DRUNK, 2, 5),) ) result = example_solverstate.is_consistent(new_statement) assert result == example_medium_solverstate
def example_small_solverstate(small_game_roles: tuple[Role, ...]) -> SolverState: possible_roles = ( frozenset({Role.SEER}), frozenset({Role.ROBBER, Role.VILLAGER, Role.SEER}), frozenset({Role.ROBBER}), ) return SolverState( possible_roles, ((SwitchPriority.ROBBER, 2, 0),), (True,), role_counts={Role.VILLAGER: 1, Role.SEER: 0, Role.ROBBER: 0}, )
def test_is_consistent_deepcopy_mechanics( example_medium_solverstate: SolverState, ) -> None: """ Modifying one SolverState should not affect other SolverStates created by is_consistent. """ possible_roles = (frozenset({Role.SEER}),) + (const.ROLE_SET,) * ( const.NUM_ROLES - 1 ) example = SolverState(possible_roles, path=(True,)) new_statement = Statement( "next", ((2, frozenset({Role.DRUNK})),), ((SwitchPriority.DRUNK, 2, 5),) ) result = example.is_consistent(new_statement) example.possible_roles += (frozenset({Role.NONE}),) example.switches += ((SwitchPriority.DRUNK, 5, 5),) example.possible_roles = (example.possible_roles[0] & {Role.NONE},) assert result == example_medium_solverstate
def test_eq(example_small_solverstate: SolverState) -> None: """Should be able to compare two identical SolverStates.""" possible_roles = ( frozenset({Role.SEER}), frozenset({Role.ROBBER, Role.VILLAGER, Role.SEER}), frozenset({Role.ROBBER}), ) switches = ((SwitchPriority.ROBBER, 2, 0),) path = (True,) result = SolverState(possible_roles, switches, path) assert result == example_small_solverstate
def test_empty_unrestricted_prediction(medium_game_roles: tuple[Role, ...]) -> None: """Should return an empty list to denote that no prediction could be made.""" solution = SolverState() result = predictions.make_unrestricted_prediction(solution) assert result == ( Role.TROUBLEMAKER, Role.DRUNK, Role.WOLF, Role.SEER, Role.ROBBER, Role.MINION, )
def expectimax( player_obj: Any, expected_statements: dict[int, tuple[Statement, ...]], statement_list: tuple[Statement, ...], state: SolverState, ind: int, ) -> tuple[float, Statement | None]: """ Runs expectimax on the list of statements and current state up to a max depth. """ if ( ind == const.NUM_PLAYERS or ind - player_obj.player_index == const.EXPECTIMAX_DEPTH ): return player_obj.eval_fn(statement_list), None next_statements = player_obj.statements # Randomly choose a subset of the expected player statements and get expected value # of remaining statements. Adjust sample size based on const.BRANCH_FACTOR. if ind != player_obj.player_index: sample_size = const.BRANCH_FACTOR * player_obj.player_index indices = random.sample(range(len(expected_statements[ind])), sample_size) next_statements = [expected_statements[ind][i] for i in sorted(indices)] # Evaluate current state (value of consistent statements) and return values. vals = [] for statement in next_statements: # If you are a Wolf, let yourself be inconsistent (each state needs a value). new_state = state if player_obj.is_evil() else state.is_consistent(statement) if new_state is not None: val, _ = expectimax( player_obj, expected_statements, statement_list + (statement,), new_state, ind + 1, ) vals.append(val) if not vals: return 10, None # Choose your own move to maximize val if ind == player_obj.player_index: max_value = max(vals) best_indices = [index for index, value in enumerate(vals) if value == max_value] return max_value, player_obj.statements[random.choice(best_indices)] return sum(vals) / len(vals), None
def test_get_role_counts() -> None: """ Should return True if there is a a dict with counts of all certain roles. """ set_roles(Role.WOLF, Role.SEER, Role.VILLAGER, Role.ROBBER, Role.VILLAGER) possible_roles_list = ( frozenset({Role.VILLAGER}), frozenset({Role.SEER}), frozenset({Role.VILLAGER}), ) + (const.ROLE_SET,) * 2 result = SolverState(possible_roles_list).get_role_counts() assert result == {Role.SEER: 0, Role.VILLAGER: 0, Role.WOLF: 1, Role.ROBBER: 1}
def example_medium_solverstate(medium_game_roles: tuple[Role, ...]) -> SolverState: possible_roles = ( frozenset({Role.SEER}), frozenset( { Role.TROUBLEMAKER, Role.WOLF, Role.DRUNK, Role.ROBBER, Role.SEER, Role.MINION, } ), frozenset({Role.DRUNK}), frozenset( { Role.TROUBLEMAKER, Role.WOLF, Role.DRUNK, Role.ROBBER, Role.SEER, Role.MINION, } ), frozenset( { Role.TROUBLEMAKER, Role.WOLF, Role.DRUNK, Role.ROBBER, Role.SEER, Role.MINION, } ), frozenset( { Role.TROUBLEMAKER, Role.WOLF, Role.DRUNK, Role.ROBBER, Role.SEER, Role.MINION, } ), ) return SolverState(possible_roles, ((SwitchPriority.DRUNK, 2, 5),), (True, True))
def example_large_solverstate(large_game_roles: tuple[Role, ...]) -> SolverState: possible_roles = ( frozenset({Role.ROBBER}), frozenset( { Role.SEER, Role.HUNTER, Role.DRUNK, Role.TANNER, Role.WOLF, Role.INSOMNIAC, Role.MASON, Role.MINION, Role.VILLAGER, Role.TROUBLEMAKER, } ), frozenset({Role.SEER}), frozenset({Role.VILLAGER}), frozenset({Role.MASON}), frozenset({Role.MASON}), frozenset({Role.DRUNK}), frozenset( { Role.SEER, Role.HUNTER, Role.DRUNK, Role.TANNER, Role.WOLF, Role.INSOMNIAC, Role.MASON, Role.MINION, Role.VILLAGER, Role.TROUBLEMAKER, } ), ) + (const.ROLE_SET,) * 7 switches = ((SwitchPriority.ROBBER, 6, 0), (SwitchPriority.ROBBER, 9, 6)) path = (True, False, True, True, True, True, True, False) return SolverState(possible_roles, switches, path)
def example_medium_solverstate_solved( medium_game_roles: tuple[Role, ...] ) -> SolverState: possible_roles = ( frozenset({Role.SEER}), frozenset({Role.ROBBER, Role.DRUNK, Role.WOLF, Role.TROUBLEMAKER, Role.MINION}), frozenset({Role.DRUNK}), frozenset({Role.ROBBER}), frozenset({Role.ROBBER, Role.DRUNK, Role.WOLF, Role.TROUBLEMAKER, Role.MINION}), frozenset( { Role.DRUNK, Role.ROBBER, Role.SEER, Role.WOLF, Role.TROUBLEMAKER, Role.MINION, } ), ) switches = ((SwitchPriority.DRUNK, 2, 5), (SwitchPriority.ROBBER, 3, 2)) path = (True, False, True, True, False) return SolverState(possible_roles, switches, path)
def test_constructor() -> None: """Should initialize a SolverState.""" result = SolverState((frozenset({Role.VILLAGER}),), path=(True,)) assert isinstance(result, SolverState)