pacai.agents.searchproblem

  1import logging
  2import typing
  3
  4import edq.util.time
  5
  6import pacai.core.action
  7import pacai.core.agent
  8import pacai.core.agentaction
  9import pacai.core.agentinfo
 10import pacai.core.board
 11import pacai.core.gamestate
 12import pacai.core.search
 13import pacai.search.common
 14import pacai.search.random
 15import pacai.search.position
 16import pacai.util.alias
 17import pacai.util.reflection
 18
 19DEFAULT_PROBLEM: str = pacai.util.alias.SEARCH_PROBLEM_POSITION.long
 20DEFAULT_PROBLEM_COST: str = pacai.util.alias.COST_FUNC_UNIT.long
 21DEFAULT_SOLVER: str = pacai.util.alias.SEARCH_SOLVER_RANDOM.long
 22DEFAULT_HEURISTIC: str = pacai.util.alias.HEURISTIC_NULL.long
 23
 24class SearchProblemAgent(pacai.core.agent.Agent):
 25    """
 26    An agent that works by first solving a searh problem (pacai.core.search.Problem),
 27    and then executing the path found during the search.
 28    """
 29
 30    def __init__(self,
 31            problem: type[pacai.core.search.SearchProblem] | pacai.util.reflection.Reference | str = DEFAULT_PROBLEM,
 32            problem_cost: pacai.core.search.CostFunction | pacai.util.reflection.Reference | str = DEFAULT_PROBLEM_COST,
 33            solver: pacai.core.search.SearchProblemSolver | pacai.util.reflection.Reference | str = DEFAULT_SOLVER,
 34            heuristic: pacai.core.search.SearchHeuristic | pacai.util.reflection.Reference | str = DEFAULT_HEURISTIC,
 35            **kwargs: typing.Any) -> None:
 36        super().__init__(**kwargs)
 37
 38        claen_problem_class = pacai.util.reflection.resolve_and_fetch(type, problem)
 39        self._problem_class: type[pacai.core.search.SearchProblem] = claen_problem_class
 40        """ The search problem class this agent will use. """
 41
 42        claen_problem_cost_function = pacai.util.reflection.resolve_and_fetch(pacai.core.search.CostFunction, problem_cost)
 43        self._problem_cost_function: pacai.core.search.CostFunction = claen_problem_cost_function
 44        """ The cost function for this agent's search problem. """
 45
 46        claen_solver_function = pacai.util.reflection.resolve_and_fetch(pacai.core.search.SearchProblemSolver, solver)
 47        self._solver_function: pacai.core.search.SearchProblemSolver = claen_solver_function
 48        """ The search solver function this agent will use. """
 49
 50        claen_heuristic_function = pacai.util.reflection.resolve_and_fetch(pacai.core.search.SearchHeuristic, heuristic)
 51        self._heuristic_function: pacai.core.search.SearchHeuristic = claen_heuristic_function
 52        """ The search heuristic function this agent will use. """
 53
 54        self._actions: list[pacai.core.action.Action] = []
 55        """ The actions that the search solver came up with. """
 56
 57        logging.debug("Created a SearchProblemAgent using problem '%s', cost function '%s', solver '%s', and heuristic '%s'.",
 58                pacai.util.reflection.get_qualified_name(problem),
 59                pacai.util.reflection.get_qualified_name(problem_cost),
 60                pacai.util.reflection.get_qualified_name(solver),
 61                pacai.util.reflection.get_qualified_name(heuristic))
 62
 63    def get_action(self, state: pacai.core.gamestate.GameState) -> pacai.core.action.Action:
 64        if (len(self._actions) == 0):
 65            return pacai.core.action.STOP
 66
 67        return self._actions.pop(0)
 68
 69    def game_start_full(self,
 70            agent_index: int,
 71            suggested_seed: int,
 72            initial_state: pacai.core.gamestate.GameState,
 73            ) -> pacai.core.agentaction.AgentAction:
 74        # Do the standard game initialization steps.
 75        super().game_start_full(agent_index, suggested_seed, initial_state)
 76
 77        # This is the agent's first time seeing the game's state (which includes the board).
 78        # Create a search problem using the game's state, and solve the problem.
 79
 80        start_time = edq.util.time.Timestamp.now()
 81        (solution, position_history, expanded_node_count) = self._do_search(initial_state)
 82        end_time = edq.util.time.Timestamp.now()
 83
 84        self._actions = solution.actions
 85
 86        logging.info("Path found with %d steps and a total cost of %0.2f in %0.2f seconds. %d search nodes expanded.",
 87                len(solution.actions), solution.cost, (end_time.sub(start_time).to_secs()), expanded_node_count)
 88
 89        # Highlight visited locations in the UI to visually represent our search pattern.
 90        highlights = []
 91        for (i, position) in enumerate(position_history):
 92            # Gradually increase the highlight intensity from the start to the end.
 93            intensity = (i + 1) / len(position_history)
 94
 95            highlights.append(pacai.core.board.Highlight(position, intensity))
 96
 97        return pacai.core.agentaction.AgentAction(board_highlights = highlights)
 98
 99    def _do_search(self,
100            state: pacai.core.gamestate.GameState,
101            ) -> tuple[pacai.core.search.SearchSolution, list[pacai.core.board.Position], int]:
102        """
103        Perform the actual search operation.
104        Children may override this to change searching behavior.
105        Return: (solution, position history, expanded node count).
106        """
107
108        search_problem = self._problem_class(game_state = state, cost_function = self._problem_cost_function)
109        solution = self._solver_function(search_problem, self._heuristic_function, self.rng)
110
111        if (solution.goal_node is not None):
112            search_problem.complete(solution.goal_node)
113
114        return (solution, search_problem.position_history, search_problem.expanded_node_count)
115
116class GreedySubproblemSearchAgent(SearchProblemAgent):
117    """
118    An agent that greedily solves several search problems (instead of just one main one).
119    This agent will repeatedly create and solve search problems until the game state signals the game is over
120    (pacai.core.gamestate.GameState.game_over == True).
121    Once the goal is reached, the actions from all subproblem solutions will be concatenated to form the final list of actions.
122    """
123
124    def _do_search(self,
125            state: pacai.core.gamestate.GameState,
126            ) -> tuple[pacai.core.search.SearchSolution, list[pacai.core.board.Position], int]:
127        actions = []
128        total_cost = 0.0
129        goal_node = None
130        total_position_history = []
131        total_expanded_node_count = 0
132
133        while (not state.game_over):
134            # Solve the subproblem.
135            (solution, position_history, expanded_node_count) = super()._do_search(state)
136
137            if (solution.goal_node is None):
138                raise ValueError("Failed to solve subproblem.")
139
140            # Add all the components of the sub-solution to the total solution.
141            actions += solution.actions
142            total_cost += solution.cost
143            goal_node = solution.goal_node
144            total_position_history += position_history
145            total_expanded_node_count += expanded_node_count
146
147            # Move to the next state by applying all the actions.
148            for action in solution.actions:
149                state = state.generate_successor(action, self.rng)
150
151        solution = pacai.core.search.SearchSolution(actions, total_cost, goal_node)
152        return (solution, total_position_history, total_expanded_node_count)
DEFAULT_PROBLEM_COST: str = 'pacai.search.common.unit_cost_function'
DEFAULT_SOLVER: str = 'pacai.search.random.random_search'
DEFAULT_HEURISTIC: str = 'pacai.search.common.null_heuristic'
class SearchProblemAgent(pacai.core.agent.Agent):
 25class SearchProblemAgent(pacai.core.agent.Agent):
 26    """
 27    An agent that works by first solving a searh problem (pacai.core.search.Problem),
 28    and then executing the path found during the search.
 29    """
 30
 31    def __init__(self,
 32            problem: type[pacai.core.search.SearchProblem] | pacai.util.reflection.Reference | str = DEFAULT_PROBLEM,
 33            problem_cost: pacai.core.search.CostFunction | pacai.util.reflection.Reference | str = DEFAULT_PROBLEM_COST,
 34            solver: pacai.core.search.SearchProblemSolver | pacai.util.reflection.Reference | str = DEFAULT_SOLVER,
 35            heuristic: pacai.core.search.SearchHeuristic | pacai.util.reflection.Reference | str = DEFAULT_HEURISTIC,
 36            **kwargs: typing.Any) -> None:
 37        super().__init__(**kwargs)
 38
 39        claen_problem_class = pacai.util.reflection.resolve_and_fetch(type, problem)
 40        self._problem_class: type[pacai.core.search.SearchProblem] = claen_problem_class
 41        """ The search problem class this agent will use. """
 42
 43        claen_problem_cost_function = pacai.util.reflection.resolve_and_fetch(pacai.core.search.CostFunction, problem_cost)
 44        self._problem_cost_function: pacai.core.search.CostFunction = claen_problem_cost_function
 45        """ The cost function for this agent's search problem. """
 46
 47        claen_solver_function = pacai.util.reflection.resolve_and_fetch(pacai.core.search.SearchProblemSolver, solver)
 48        self._solver_function: pacai.core.search.SearchProblemSolver = claen_solver_function
 49        """ The search solver function this agent will use. """
 50
 51        claen_heuristic_function = pacai.util.reflection.resolve_and_fetch(pacai.core.search.SearchHeuristic, heuristic)
 52        self._heuristic_function: pacai.core.search.SearchHeuristic = claen_heuristic_function
 53        """ The search heuristic function this agent will use. """
 54
 55        self._actions: list[pacai.core.action.Action] = []
 56        """ The actions that the search solver came up with. """
 57
 58        logging.debug("Created a SearchProblemAgent using problem '%s', cost function '%s', solver '%s', and heuristic '%s'.",
 59                pacai.util.reflection.get_qualified_name(problem),
 60                pacai.util.reflection.get_qualified_name(problem_cost),
 61                pacai.util.reflection.get_qualified_name(solver),
 62                pacai.util.reflection.get_qualified_name(heuristic))
 63
 64    def get_action(self, state: pacai.core.gamestate.GameState) -> pacai.core.action.Action:
 65        if (len(self._actions) == 0):
 66            return pacai.core.action.STOP
 67
 68        return self._actions.pop(0)
 69
 70    def game_start_full(self,
 71            agent_index: int,
 72            suggested_seed: int,
 73            initial_state: pacai.core.gamestate.GameState,
 74            ) -> pacai.core.agentaction.AgentAction:
 75        # Do the standard game initialization steps.
 76        super().game_start_full(agent_index, suggested_seed, initial_state)
 77
 78        # This is the agent's first time seeing the game's state (which includes the board).
 79        # Create a search problem using the game's state, and solve the problem.
 80
 81        start_time = edq.util.time.Timestamp.now()
 82        (solution, position_history, expanded_node_count) = self._do_search(initial_state)
 83        end_time = edq.util.time.Timestamp.now()
 84
 85        self._actions = solution.actions
 86
 87        logging.info("Path found with %d steps and a total cost of %0.2f in %0.2f seconds. %d search nodes expanded.",
 88                len(solution.actions), solution.cost, (end_time.sub(start_time).to_secs()), expanded_node_count)
 89
 90        # Highlight visited locations in the UI to visually represent our search pattern.
 91        highlights = []
 92        for (i, position) in enumerate(position_history):
 93            # Gradually increase the highlight intensity from the start to the end.
 94            intensity = (i + 1) / len(position_history)
 95
 96            highlights.append(pacai.core.board.Highlight(position, intensity))
 97
 98        return pacai.core.agentaction.AgentAction(board_highlights = highlights)
 99
100    def _do_search(self,
101            state: pacai.core.gamestate.GameState,
102            ) -> tuple[pacai.core.search.SearchSolution, list[pacai.core.board.Position], int]:
103        """
104        Perform the actual search operation.
105        Children may override this to change searching behavior.
106        Return: (solution, position history, expanded node count).
107        """
108
109        search_problem = self._problem_class(game_state = state, cost_function = self._problem_cost_function)
110        solution = self._solver_function(search_problem, self._heuristic_function, self.rng)
111
112        if (solution.goal_node is not None):
113            search_problem.complete(solution.goal_node)
114
115        return (solution, search_problem.position_history, search_problem.expanded_node_count)

An agent that works by first solving a searh problem (pacai.core.search.Problem), and then executing the path found during the search.

31    def __init__(self,
32            problem: type[pacai.core.search.SearchProblem] | pacai.util.reflection.Reference | str = DEFAULT_PROBLEM,
33            problem_cost: pacai.core.search.CostFunction | pacai.util.reflection.Reference | str = DEFAULT_PROBLEM_COST,
34            solver: pacai.core.search.SearchProblemSolver | pacai.util.reflection.Reference | str = DEFAULT_SOLVER,
35            heuristic: pacai.core.search.SearchHeuristic | pacai.util.reflection.Reference | str = DEFAULT_HEURISTIC,
36            **kwargs: typing.Any) -> None:
37        super().__init__(**kwargs)
38
39        claen_problem_class = pacai.util.reflection.resolve_and_fetch(type, problem)
40        self._problem_class: type[pacai.core.search.SearchProblem] = claen_problem_class
41        """ The search problem class this agent will use. """
42
43        claen_problem_cost_function = pacai.util.reflection.resolve_and_fetch(pacai.core.search.CostFunction, problem_cost)
44        self._problem_cost_function: pacai.core.search.CostFunction = claen_problem_cost_function
45        """ The cost function for this agent's search problem. """
46
47        claen_solver_function = pacai.util.reflection.resolve_and_fetch(pacai.core.search.SearchProblemSolver, solver)
48        self._solver_function: pacai.core.search.SearchProblemSolver = claen_solver_function
49        """ The search solver function this agent will use. """
50
51        claen_heuristic_function = pacai.util.reflection.resolve_and_fetch(pacai.core.search.SearchHeuristic, heuristic)
52        self._heuristic_function: pacai.core.search.SearchHeuristic = claen_heuristic_function
53        """ The search heuristic function this agent will use. """
54
55        self._actions: list[pacai.core.action.Action] = []
56        """ The actions that the search solver came up with. """
57
58        logging.debug("Created a SearchProblemAgent using problem '%s', cost function '%s', solver '%s', and heuristic '%s'.",
59                pacai.util.reflection.get_qualified_name(problem),
60                pacai.util.reflection.get_qualified_name(problem_cost),
61                pacai.util.reflection.get_qualified_name(solver),
62                pacai.util.reflection.get_qualified_name(heuristic))
def get_action(self, state: pacai.core.gamestate.GameState) -> pacai.core.action.Action:
64    def get_action(self, state: pacai.core.gamestate.GameState) -> pacai.core.action.Action:
65        if (len(self._actions) == 0):
66            return pacai.core.action.STOP
67
68        return self._actions.pop(0)

Get an action for this agent given the current state of the game. This is simplified version of get_action_full(), see that method for full details.

def game_start_full( self, agent_index: int, suggested_seed: int, initial_state: pacai.core.gamestate.GameState) -> pacai.core.agentaction.AgentAction:
70    def game_start_full(self,
71            agent_index: int,
72            suggested_seed: int,
73            initial_state: pacai.core.gamestate.GameState,
74            ) -> pacai.core.agentaction.AgentAction:
75        # Do the standard game initialization steps.
76        super().game_start_full(agent_index, suggested_seed, initial_state)
77
78        # This is the agent's first time seeing the game's state (which includes the board).
79        # Create a search problem using the game's state, and solve the problem.
80
81        start_time = edq.util.time.Timestamp.now()
82        (solution, position_history, expanded_node_count) = self._do_search(initial_state)
83        end_time = edq.util.time.Timestamp.now()
84
85        self._actions = solution.actions
86
87        logging.info("Path found with %d steps and a total cost of %0.2f in %0.2f seconds. %d search nodes expanded.",
88                len(solution.actions), solution.cost, (end_time.sub(start_time).to_secs()), expanded_node_count)
89
90        # Highlight visited locations in the UI to visually represent our search pattern.
91        highlights = []
92        for (i, position) in enumerate(position_history):
93            # Gradually increase the highlight intensity from the start to the end.
94            intensity = (i + 1) / len(position_history)
95
96            highlights.append(pacai.core.board.Highlight(position, intensity))
97
98        return pacai.core.agentaction.AgentAction(board_highlights = highlights)

Notify this agent that the game is about to start. The provided agent index is the game's index/id for this agent. The state represents the initial state of the game. Any precomputation for this game should be done in this method. Calls to this method may be subject to a timeout.

class GreedySubproblemSearchAgent(SearchProblemAgent):
117class GreedySubproblemSearchAgent(SearchProblemAgent):
118    """
119    An agent that greedily solves several search problems (instead of just one main one).
120    This agent will repeatedly create and solve search problems until the game state signals the game is over
121    (pacai.core.gamestate.GameState.game_over == True).
122    Once the goal is reached, the actions from all subproblem solutions will be concatenated to form the final list of actions.
123    """
124
125    def _do_search(self,
126            state: pacai.core.gamestate.GameState,
127            ) -> tuple[pacai.core.search.SearchSolution, list[pacai.core.board.Position], int]:
128        actions = []
129        total_cost = 0.0
130        goal_node = None
131        total_position_history = []
132        total_expanded_node_count = 0
133
134        while (not state.game_over):
135            # Solve the subproblem.
136            (solution, position_history, expanded_node_count) = super()._do_search(state)
137
138            if (solution.goal_node is None):
139                raise ValueError("Failed to solve subproblem.")
140
141            # Add all the components of the sub-solution to the total solution.
142            actions += solution.actions
143            total_cost += solution.cost
144            goal_node = solution.goal_node
145            total_position_history += position_history
146            total_expanded_node_count += expanded_node_count
147
148            # Move to the next state by applying all the actions.
149            for action in solution.actions:
150                state = state.generate_successor(action, self.rng)
151
152        solution = pacai.core.search.SearchSolution(actions, total_cost, goal_node)
153        return (solution, total_position_history, total_expanded_node_count)

An agent that greedily solves several search problems (instead of just one main one). This agent will repeatedly create and solve search problems until the game state signals the game is over (pacai.core.gamestate.GameState.game_over == True). Once the goal is reached, the actions from all subproblem solutions will be concatenated to form the final list of actions.