def solve_by_bfs(self, head: TreeNode): """ Search the state space using BFS search. :param head: Tree head node. """ # visited list to store visited position on the world map. visited_pos = list() visited_pos.append(head.brick.pos) # queue to hold nodes encountered at each level of the tree. node_queue = list() node_queue.append(head) steps = 0 while len(node_queue) > 0: node = node_queue.pop(0) self.debug("{:10s}: {:21s} - {}".format("removed", "frontier node", str(node))) # show the BFS tree. print("Step: {}, Depth: {}, - {}".format(steps, self.get_node_depth(node), str(node))) self.show(node.brick) steps += 1 if self.is_target_state(node.brick.pos): print("\nBFS SEARCH COMPLETED !") print("Optimal path is as below -> \n") self.show_optimal_path(node) return for next_pos, direction in self.next_valid_move(node, visited_pos): # create a new brick with next_pos, initialize a new node with brick position new_brick = Brick(next_pos) new_node = TreeNode(new_brick) # set the 4 direction attributes setattr(node, direction.name.lower(), new_node) # and parent node of the new node. new_node.parent = node new_node.dir_from_parent = direction node_queue.append(new_node) visited_pos.append(next_pos) self.debug("{:10s}: {:21s} - {}".format( "added", "new node", str(new_node))) return
def solve_by_dfs(self, node: TreeNode, visited_pos: List = None): """ Search the state space using DFS algorithm. :param node: Tree node. :param visited_pos: List containing visited positions. """ if visited_pos is None: visited_pos = list() visited_pos.append(node.brick.pos) print("Step: {}, Depth: {} - {}".format(self.dfs_steps, self.get_node_depth(node), str(node))) self.show(node.brick) self.dfs_steps += 1 if self.is_target_state(node.brick.pos): # with dfs, we are in deep recursion, 'return' won't exit the entire stack. exit(0) for next_pos, direction in self.next_valid_move(node, visited_pos): # create a new brick with next_pos, initialize a new node with brick position # and recursively make the state tree. new_brick = Brick(next_pos) new_node = TreeNode(new_brick) # set the 4 direction attributes setattr(node, direction.name.lower(), new_node) # and parent node of the new node. new_node.parent = node new_node.dir_from_parent = direction visited_pos.append(next_pos) self.debug("{:10s}: {:21s} - {}".format("to visit", "new node", str(new_node))) self.solve_by_dfs(new_node, visited_pos) return
def solve_by_greedy_best_first(self, head: TreeNode, target_pos: Pos): """ Solve the Bloxorz problem using A* algorithm. :param head: head node. :param target_pos: target position for heuristic estimates. """ # compute the heuristic cost from all valid positions to the target positions heuristic_costs = self.compute_heuristic_costs(target_pos) head.f_cost = self.min_h_cost(heuristic_costs, head) self.set_cost_visited(head.brick.pos, 0) expanded_nodes = list() steps = 0 node = head print("Step: {}, Depth: {}, Cost: {} - {}".format( steps, self.get_node_depth(head), self.get_cost_visited(head.brick.pos), str(head))) self.show(head.brick) while True: for next_pos, direction in self.next_valid_move(node, []): # new node and estimated cost. new_node = TreeNode(Brick(next_pos)) h_cost = self.min_h_cost(heuristic_costs, new_node) new_node.f_cost = h_cost # set current node's child pointer. setattr(node, direction.name.lower(), new_node) # node.{left|right|up|down} -> new_node # link new_node to the current node. new_node.parent = node new_node.dir_from_parent = direction heappush(expanded_nodes, new_node) self.debug("{:10s}: {:21s} - {} [f_cost: {:.2f}] ".format( "added", "new", str(new_node), new_node.f_cost)) node = heappop(expanded_nodes) self.debug("{:10s}: {:21s} - {}".format("removed", "frontier node", str(node))) # update cost of this node self.set_cost_visited( node.brick.pos, self.get_cost_visited(node.parent.brick.pos) + 1) steps += 1 print("Step: {}, Depth: {}, Cost: {} - {} [f_cost: {:.2f}]".format( steps, self.get_node_depth(node), self.get_cost_visited(node.brick.pos), str(node), node.f_cost)) self.show(node.brick) # if goal state is dequeued, mark the search as completed. if node.brick.pos == target_pos: break print("\nGreedy Best First SEARCH COMPLETED !") return
def solve_by_astar(self, head: TreeNode, target_pos: Pos): """ Solve the Bloxorz problem using A* algorithm. :param head: head node. :param target_pos: target position for heuristic estimates. """ # compute the heuristic cost from all valid positions to the target positions heuristic_costs = self.compute_heuristic_costs(target_pos) head.f_cost = self.min_h_cost(heuristic_costs, head) self.set_cost_visited(head.brick.pos, 0) expanded_nodes = list() steps = 0 node = head print("Step: {}, Depth: {}, Cost: {} - {}".format( steps, self.get_node_depth(head), self.get_cost_visited(head.brick.pos), str(head))) self.show(head.brick) while True: for next_pos, direction in self.next_valid_move(node, []): g_cost = self.get_cost_visited(node.brick.pos) + 1 # if the node is not visited, add to expanded queue. # if the node is visited, but has lower actual cost than previously recorded, add to expanded queue. if next_pos not in self.cost_visited or g_cost < self.get_cost_visited( next_pos): # new node and estimated cost. new_node = TreeNode(Brick(next_pos)) h_cost = self.min_h_cost(heuristic_costs, new_node) new_node.f_cost = g_cost + h_cost # set current node's child pointer. setattr(node, direction.name.lower(), new_node) # node.{left|right|up|down} -> new_node # link new_node to the current node. new_node.parent = node new_node.dir_from_parent = direction heappush(expanded_nodes, new_node) self.debug( "{:10s}: {:21s} - {} [f_cost: {:.2f} = {} + {:.2f}] ". format("added", "new | visited & cheap", str(new_node), new_node.f_cost, g_cost, h_cost)) else: self.debug( "{:10s}: {:21s} - [hash(Parent): {}, Parent->{}] [Cost now: {}, earlier: {}]" .format("rejected", "visited & costly", hash(node), direction.name.lower(), g_cost, self.get_cost_visited(next_pos))) node = heappop(expanded_nodes) self.debug("{:10s}: {:21s} - {}".format("removed", "frontier node", str(node))) # update cost of this node self.set_cost_visited( node.brick.pos, self.get_cost_visited(node.parent.brick.pos) + 1) steps += 1 print("Step: {}, Depth: {}, Cost: {} - {} [f_cost: {:.2f}]".format( steps, self.get_node_depth(node), self.get_cost_visited(node.brick.pos), str(node), node.f_cost)) self.show(node.brick) # if goal state is dequeued, mark the search as completed. if node.brick.pos == target_pos: break print("\nA* SEARCH COMPLETED !") print("Optimal path is as below -> \n") self.show_optimal_path(node) return