-
Notifications
You must be signed in to change notification settings - Fork 6
Create puzzle.py #18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Create puzzle.py #18
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,205 @@ | ||
| # Q2: Robot Evacuation Planning using Best First Search | ||
|
|
||
| # Grid dimensions: 10 rows, 20 columns | ||
| # 0 = Walkable (Hallway/Room interior) | ||
| # 1 = Wall/Blocked | ||
|
|
||
| # Approximated Floor Plan based on image | ||
| # Row 4 is the main hallway | ||
| # Entry at (8, 4), Exit at (4, 18) | ||
|
|
||
| grid = [ | ||
| [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], # 0: Top Wall | ||
| [1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1], # 1: Rooms | ||
| [1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1], # 2: Rooms | ||
| [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1], # 3: Wall separating rooms from hall | ||
| [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # 4: Main Hallway (Exit at end) | ||
| [1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1], # 5: Structures below hall | ||
| [1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1], # 6: More structures | ||
| [1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1], # 7: Walls | ||
| [1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], # 8: Entry area (at 8,4) | ||
| [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] # 9: Bottom Wall | ||
| ] | ||
|
|
||
| rows = 10 | ||
| cols = 20 | ||
|
|
||
| # Entry and Exit | ||
| START = (8, 4) # Entry (Row 8, Col 4) | ||
| GOAL = (4, 18) # Exit (Row 4, Col 18) | ||
|
|
||
| class Node: | ||
| def __init__(self, state, parent=None, action=None, path_cost=0): | ||
| self.state = state # (row, col) | ||
| self.parent = parent | ||
| self.action = action # "Up", "Down", "Left", "Right" | ||
| self.path_cost = path_cost | ||
|
|
||
| class PriorityQueue: | ||
| def __init__(self, f): | ||
| self.data = [] | ||
| self.f = f | ||
|
|
||
| def add(self, node): | ||
| self.data.append(node) | ||
| self.data.sort(key=self.f) | ||
|
|
||
| def pop(self): | ||
| return self.data.pop(0) | ||
|
|
||
| def top(self): | ||
| return self.data[0] | ||
|
|
||
| def is_empty(self): | ||
| return len(self.data) == 0 | ||
|
|
||
| class Problem: | ||
| def __init__(self, initial, goal, grid): | ||
| self.initial = initial | ||
| self.goal = goal | ||
| self.grid = grid | ||
|
|
||
| def is_goal(self, state): | ||
| return state == self.goal | ||
|
|
||
| def ACTIONS(self, state): | ||
| r, c = state | ||
| actions = [] | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The It's generally better practice to encapsulate grid dimensions within the Suggestion:
This approach makes the |
||
| # Down, Up, Right, Left | ||
| # Using simple step cost 1 for all moves | ||
| moves = [ | ||
| ("Down", (1, 0)), | ||
| ("Up", (-1, 0)), | ||
| ("Right", (0, 1)), | ||
| ("Left", (0, -1)) | ||
| ] | ||
|
|
||
| for name, (dr, dc) in moves: | ||
| nr, nc = r + dr, c + dc | ||
| # Check boundaries and walls | ||
| if 0 <= nr < rows and 0 <= nc < cols: | ||
| if self.grid[nr][nc] == 0: # 0 is walkable | ||
| actions.append(name) | ||
| return actions | ||
|
|
||
| def RESULT(self, state, action): | ||
| r, c = state | ||
| if action == "Down": return (r + 1, c) | ||
| if action == "Up": return (r - 1, c) | ||
| if action == "Right": return (r, c + 1) | ||
| if action == "Left": return (r, c - 1) | ||
| return state | ||
|
|
||
| def ACTION_COST(self, s, action, s_prime): | ||
| return 1 # Uniform cost for grid movement | ||
|
|
||
| # Heuristic: Manhattan Distance | ||
| def heuristic(node): | ||
| r1, c1 = node.state | ||
| r2, c2 = GOAL | ||
| # h(n) = |x1 - x2| + |y1 - y2| | ||
| return abs(r1 - r2) + abs(c1 - c2) | ||
|
|
||
| # Evaluation function f(n) = g(n) for Uniform Cost Search (UCS) | ||
| def f(node): | ||
| # Justification: UCS uses path cost g(n) to find the optimal path. | ||
| return node.path_cost | ||
|
|
||
| def EXPAND(problem, node): | ||
| s = node.state | ||
| for action in problem.ACTIONS(s): | ||
| s_prime = problem.RESULT(s, action) | ||
| cost = node.path_cost + problem.ACTION_COST(s, action, s_prime) | ||
| yield Node(state=s_prime, parent=node, action=action, path_cost=cost) | ||
|
|
||
| def BEST_FIRST_SEARCH(problem, f): | ||
| node = Node(problem.initial) | ||
| frontier = PriorityQueue(f) | ||
| frontier.add(node) | ||
|
|
||
| # 2D reached table | ||
| reached = [[None for _ in range(cols)] for _ in range(rows)] | ||
| reached[node.state[0]][node.state[1]] = node | ||
|
|
||
| explored = 0 | ||
|
|
||
| while not frontier.is_empty(): | ||
| node = frontier.pop() | ||
| explored += 1 | ||
|
|
||
| if problem.is_goal(node.state): | ||
| return node, explored | ||
|
|
||
| for child in EXPAND(problem, node): | ||
| r, c = child.state | ||
| if reached[r][c] is None or child.path_cost < reached[r][c].path_cost: | ||
| reached[r][c] = child | ||
| frontier.add(child) | ||
|
|
||
| return None, explored | ||
|
|
||
| def get_path(node): | ||
| path = [] | ||
| while node: | ||
| path.append(node.state) | ||
| node = node.parent | ||
| return path[::-1] | ||
|
|
||
| def print_grid_with_path(grid, path): | ||
| print("\nEvaluated Path on Grid:") | ||
| path_set = set(path) | ||
|
|
||
| # Header | ||
| print(" ", end="") | ||
| for c in range(cols): print(f"{c%10}", end=" ") | ||
| print() | ||
|
|
||
| for r in range(rows): | ||
| print(f"{r:<2} ", end="") | ||
| for c in range(cols): | ||
| if (r, c) == START: | ||
| print("S", end=" ") | ||
| elif (r, c) == GOAL: | ||
| print("E", end=" ") | ||
| elif (r, c) in path_set: | ||
| print(".", end=" ") # Path marker | ||
| elif grid[r][c] == 1: | ||
| print("#", end=" ") # Wall | ||
| else: | ||
| print(" ", end=" ") # Empty space | ||
| print() | ||
|
|
||
| if __name__ == "__main__": | ||
| problem = Problem(START, GOAL, grid) | ||
|
|
||
| print("Starting Uniform Cost Search (UCS)...") | ||
| print(f"Start: {START}, Goal: {GOAL}") | ||
|
|
||
| solution, explored = BEST_FIRST_SEARCH(problem, f) | ||
|
|
||
| if solution: | ||
| path = get_path(solution) | ||
| print("\nGoal Found!") | ||
| print(f"Path Length: {len(path)}") | ||
| print(f"Total Cost: {solution.path_cost}") | ||
| print(f"Nodes Explored: {explored}") | ||
|
|
||
| print_grid_with_path(grid, path) | ||
|
|
||
| print("\nPath Steps:") | ||
| curr = solution | ||
| steps = [] | ||
| while curr.parent: | ||
| steps.append(f"{curr.action} -> {curr.state}") | ||
| curr = curr.parent | ||
| for s in reversed(steps): | ||
| print(s) | ||
|
|
||
| print("\nEvaluation Cost Function Justification:") | ||
| print("Function: Path Cost f(n) = g(n)") | ||
| print("Reason: Uniform Cost Search expands nodes based on total path cost from the start.") | ||
| print("This guarantees shortest path finding in a grid with uniform step costs,") | ||
| print("exploring in 'waves' rather than just aiming for the goal.") | ||
|
|
||
| else: | ||
| print("No path found!") | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
PriorityQueueimplementation is inefficient.addusesself.data.sort(key=self.f)which takesO(N log N)time for each addition, whereNis the number of elements in the queue.popusesself.data.pop(0)which takesO(N)time as it involves shifting all subsequent elements.For search algorithms where the frontier can grow large, this quadratic complexity (
O(N^2)or worse foradd/popoperations over many iterations) will significantly degrade performance. Python's built-inheapqmodule provides an efficient min-heap implementation, allowingO(log N)for bothadd(push) andpopoperations.Consider replacing this custom implementation with
heapqfor better performance and scalability. For example: