Kacemath's picture
Simple deployment: Grid Search Pathfinding with frontend and backend
e067c2d
raw
history blame
3.83 kB
"""SearchNode class for the search tree."""
from dataclasses import dataclass, field
from typing import Optional, List, Tuple, Any
@dataclass
class SearchNode:
"""
Represents a node in the search tree.
Attributes:
state: Current position (x, y) on the grid
parent: Parent node for path reconstruction
action: Action taken to reach this node (up/down/left/right/tunnel)
path_cost: g(n) - cost from start to this node
depth: Depth in search tree
"""
state: Tuple[int, int]
parent: Optional['SearchNode'] = None
action: Optional[str] = None
path_cost: float = 0.0
depth: int = 0
# For priority queue - lower is better
priority: float = field(default=0.0, compare=False)
def __lt__(self, other: 'SearchNode') -> bool:
"""Compare nodes by priority for priority queue."""
return self.priority < other.priority
def __eq__(self, other: Any) -> bool:
"""Nodes are equal if they have the same state."""
if not isinstance(other, SearchNode):
return False
return self.state == other.state
def __hash__(self) -> int:
"""Hash by state for set membership."""
return hash(self.state)
def get_path(self) -> List[Tuple[int, int]]:
"""
Reconstruct the path from root to this node.
Returns:
List of positions from start to current node
"""
path = []
node: Optional[SearchNode] = self
while node is not None:
path.append(node.state)
node = node.parent
path.reverse()
return path
def get_actions(self) -> List[str]:
"""
Reconstruct the sequence of actions from root to this node.
Returns:
List of actions taken from start to current node
"""
actions = []
node: Optional[SearchNode] = self
while node is not None and node.action is not None:
actions.append(node.action)
node = node.parent
actions.reverse()
return actions
def get_solution(self) -> str:
"""
Get the solution as a comma-separated string of actions.
Returns:
String in format "action1,action2,action3,..."
"""
actions = self.get_actions()
return ",".join(actions) if actions else ""
def expand(
self,
actions_func,
result_func,
cost_func,
heuristic_func=None
) -> List['SearchNode']:
"""
Expand this node by generating all child nodes.
Args:
actions_func: Function(state) -> List[str] of valid actions
result_func: Function(state, action) -> new_state
cost_func: Function(state, action, new_state) -> step_cost
heuristic_func: Optional Function(state, goal) -> h(n) for A*/Greedy
Returns:
List of child SearchNode objects
"""
children = []
for action in actions_func(self.state):
new_state = result_func(self.state, action)
step_cost = cost_func(self.state, action, new_state)
child = SearchNode(
state=new_state,
parent=self,
action=action,
path_cost=self.path_cost + step_cost,
depth=self.depth + 1
)
# Set priority if heuristic is provided (for A*)
if heuristic_func is not None:
child.priority = child.path_cost + heuristic_func(new_state)
else:
child.priority = child.path_cost
children.append(child)
return children
def __repr__(self) -> str:
return f"SearchNode(state={self.state}, depth={self.depth}, cost={self.path_cost})"