def plan(domain, problem, useheuristic=True): """ Find a solution to a planning problem in the given domain The parameters domain and problem are exactly what is returned from pddl.parse_domain and pddl.parse_problem. If useheuristic is true, a planning heuristic (developed in task 4) should be used, otherwise use pathfinding.default_heuristic. This allows you to compare the effect of your heuristic vs. the default one easily. The return value of this function should be a 4-tuple, with the exact same elements as returned by pathfinding.astar: - A plan, which is a sequence of graph.Edge objects that have to be traversed to reach a goal state from the start. Each Edge object represents an action, and the edge's name should be the name of the action, consisting of the name of the operator the action was derived from, followed by the parenthesized and comma-separated parameter values e.g. "move(agent-1,sq-1-1,sq-2-1)" - distance is the number of actions in the plan (i.e. each action has cost 1) - visited is the total number of nodes that were added to the frontier during the execution of the algorithm - expanded is the total number of nodes that were expanded (i.e. whose neighbors were added to the frontier) """ def heuristic(state, action): return pathfinding.default_heuristic def isgoal(state): return True start = graph.Node() return pathfinding.astar( start, heuristic if useheuristic else pathfinding.default_heuristic, isgoal)
def plan(domain, problem, useheuristic=True): """ Find a solution to a planning problem in the given domain The parameters domain and problem are exactly what is returned from pddl.parse_domain and pddl.parse_problem. If useheuristic is true, a planning heuristic (developed in task 4) should be used, otherwise use pathfinding.default_heuristic. This allows you to compare the effect of your heuristic vs. the default one easily. The return value of this function should be a 4-tuple, with the exact same elements as returned by pathfinding.astar: - A plan, which is a sequence of graph.Edge objects that have to be traversed to reach a goal state from the start. Each Edge object represents an action, and the edge's name should be the name of the action, consisting of the name of the operator the action was derived from, followed by the parenthesized and comma-separated parameter values e.g. "move(agent-1,sq-1-1,sq-2-1)" - distance is the number of actions in the plan (i.e. each action has cost 1) - visited is the total number of nodes that were added to the frontier during the execution of the algorithm - expanded is the total number of nodes that were expanded (i.e. whose neighbors were added to the frontier) """ goal = expressions.make_expression(problem[2][0]) def heuristic(state, action): num = 1 num = expressions.goalsAchieved(action.target.world, goal.getRoot(), num) num = 1 / num #print(num) neighNum = len(state.neighbors) #print(neighNum) return num def isgoal(state): return True #Problem = [typesDictionary, initialStates, rawGoals, ExpressionGoal] #Domain = [actionsDictionary, typesDictionary] #print(problem[1]) #expressions.printNAryTree(goal.getRoot()) allPossibleActions = [] #expressions.printNAryTree(goal.getRoot()) start = graph.PlanNode(problem[1], domain[1], problem[0], [], 'Root') allPossibleActions = start.set_possibleActions(domain[0]) start.set_initialStates() start.get_neighbors(allPossibleActions, domain[0], useheuristic) #for n in start.neighbors: # print(n.name) #print(allPossibleActions) #astar(start, heuristic, goal, allActions, actionsDictionary) #input("Press Enter to continue...") #print(useheuristic) edgesPassed, totalPathCost, numFront, numExpandedNodes = pathfinding.astar( start, heuristic if useheuristic else pathfinding.default_heuristic, goal, allPossibleActions, domain[0], useheuristic) #returns priorityNode.edgesPassed, priorityNode.currentPathCost, numFrontier, expandedNodes return edgesPassed, len(edgesPassed), numFront, numExpandedNodes
def GetResponseTime(grid, startTile, endTile, travelSpeed): path = pathfinding.astar(grid, startTile, endTile) distance = 0 #The height and width of each tile is 1m for i in range(len(path) - 1): if path[i][0] == path[i + 1][0] or path[i][1] == path[i + 1][1]: distance += 1 else: distance += math.sqrt(2) return (distance / travelSpeed) * 60.0 #convert hours to minutes
def find_path(self, start, end, avoid_blocking_entities=False): if avoid_blocking_entities == True: motionmap = deepcopy(self.terrain.motionmap) for x, y in [ e.loc() for e in self.entities if e.block.motion == True and e.loc() not in [start, end] ]: motionmap[x][y] = False else: motionmap = self.terrain.motionmap path = pf.astar(motionmap, start, end) return path
def get_path(self, x, y): """ Get a path to the target x,y - which will be looked up to row, column for A* purposes :return: """ end_row, end_column = Entity.grid.get_column_row_for_pixels(x, y) start_row, start_column = Entity.grid.get_column_row_for_pixels(self.x, self.y) # TODO this will fail if any of thses are 0 if start_row and start_column and end_row and end_column: path = astar(Entity.grid.grid_for_pathing(), (start_row, start_column), (end_row, end_column)) if path: # convert from row,col to pixels return [Entity.grid.get_pixel_center(p[0], p[1]) for p in path] return None
def generate_rivers(self, river_nb=1, waypoint_nb=2): def random_tile_at_range(grid, start, radius, forbidden): vis = [] for x in range(start.x - radius, start.x + radius): for y in range(start.y - radius, start.y + radius): if not grid.out_of_bound(x, y): t = grid.grid[x][y] if t != start and abs( utils.distance2p((start.x, start.y), (t.x, t.y)) - radius) < 1 and t.get_type() not in forbidden: vis.append(t) if vis == []: return None return np.random.choice(vis) for i in range(river_nb): start = self.get_random_border() while start.get_type() in ["HILL", "MOUNTAIN"]: start = self.get_random_border() waypoints = [start] for j in range(1, waypoint_nb + 1): curr = waypoints[j - 1] t = random_tile_at_range( self, curr, int(((self.width + self.height) / 2) / waypoint_nb), ["MOUNTAIN"]) if t == None: break waypoints.append(t) waypoints.append(self.get_opposite_border(start)) river = [] for j in range(len(waypoints) - 1): _start = waypoints[j] _goal = waypoints[j + 1] way = pf.astar(_start, _goal, self) for w in way: river.append(w) for r in river: r.set_type("SHALLOW_WATER")
def plan(domain, problem, useheuristic=True): # get all objects applying the typinh hierarchy allobjs = mergeObjs(domain, problem) # get all grounded actions groundActions(domain.actions, allobjs) goalExp = expressions.make_expression(problem.goal) def isgoal(state): return expressions.models(state.state, goalExp) def heuristic(state, action): return SuperHeuristic(state, action, problem.init, problem.goal, allobjs, isgoal) # pathfinding.default_heuristic start = PlanNode("init", expressions.make_world(problem.init, allobjs), allobjs) return pathfinding.astar( start, heuristic if useheuristic else pathfinding.default_heuristic, isgoal)
def generateregion(self, adjtiles): if (self.biome in ['water', 'ice cap']): # maybe small islands? # icy shit on ice caps (but then how to handle poles?) # otherwise, all water for tile in self.tiles: tile.allwater = True return else: terrainnoise = self.noisegrids[0].add(self.tnoisegrids[0]) terrainnoise = terrainnoise.sizedown(2) # first, do coasts and general dist2coast ''' if (self.biome == 'volcano'): # special case for volcanos self.genvolcanoregion() return ''' if (self.dist2coast < 2): # coastal, adjacent waterdirections = [direction \ for direction in adjtiles \ if (adjtiles[direction].biome == 'water')] for waterdir in waterdirections: start, stop = self.coaststartstop(waterdir, TILES2COAST_CONN) if (not 0 in waterdir): self.setneighbors(False) else: self.setneighbors(True) path = astar(start, stop, self, terrainnoise) for pos in path: self.regiontile(*pos).allwater = True dist = 1 newpos = (pos[0] + waterdir[0] * dist, pos[1] + waterdir[1] * dist) while (self.inbounds(*newpos)): self.regiontile(*newpos).allwater = True dist += 1 newpos = (pos[0] + waterdir[0] * dist, pos[1] + waterdir[1] * dist) # do general elevation lines # only interested in cardinal direction adjacent tiles downslopedirections = [direction \ for direction in adjtiles \ if (adjtiles[direction].dist2coast < self.dist2coast) and 0 in direction] self.setneighbors(True) for downslopedir in downslopedirections: start, stop = self.coaststartstop(downslopedir, TILES2ELEV_CONN) path = astar(start, stop, self, terrainnoise) for pos in path: if (self.regiontile(*pos).allwater): continue self.regiontile(*pos).elevationdir = downslopedir dist = 1 newpos = (pos[0] + downslopedir[0] * dist, pos[1] + downslopedir[1] * dist) while (self.inbounds(*newpos)): if (terrainnoise.get(*newpos) > 0.25): self.regiontile(*newpos).elevationdir = None else: self.regiontile(*newpos).elevationdir = \ downslopedir dist += 1 newpos = (pos[0] + downslopedir[0] * dist, pos[1] + downslopedir[1] * dist) # second, do hills and mountains if (self.biome in ['mountain', 'polar', 'volcano']): nummounts = MIN_MOUNTS + int( (MAX_MOUNTS-MIN_MOUNTS) * \ terrainnoise.tiles[0]) buffer = 6 if (self.biome == 'volcano'): nummounts = 1 buffer = 11 peaks = terrainnoise.extremes(mindist=5, buffer=buffer, num=nummounts) mountlayers = [] for i in range(nummounts): x, y = peaks[i] numlayers = MIN_MOUNTLAYERS + int( (MAX_MOUNTLAYERS-MIN_MOUNTLAYERS) * \ self.tnoisegrids[0].tiles[i * \ self.tnoisegrids[0].size // 2]) if (self.biome == 'volcano'): numlayers = VOLCANO_LAYERS radius = MOUNTLAYER_WIDTH + 0.2 for j in range(numlayers): mountlayers.append([x, y, radius]) radius += 1 if (self.biome == 'volcano'): self.regiontile(mountlayers[0][0], mountlayers[0][1]).islava = True for y in range(self.height): for x in range(self.width): for layer in mountlayers: xysq = (x - layer[0])**2 + (y - layer[1])**2 if (xysq <= layer[2]**2): if (self.regiontile(x, y).allwater): self.regiontile(x, y).allwater = False if (abs(xysq - layer[2]**2) <= \ MOUNTLAYER_WIDTH**2): diff = (x - layer[0], y - layer[1]) vec = vectorsbyclosestangle( diff, [(1, 0), (-1, 0), (0, 1), (0, -1), (1, 1), (-1, 1), (1, -1), (-1, -1)]) if (self.regiontile( x, y).elevationdir is None or dot( self.regiontile( x, y).elevationdir, vec) > 0): self.regiontile(x, y).elevationdir = vec elif (xysq == 0): self.regiontile(x, y).elevationdir = None else: # hills only have 1 layer numhills = int(self.numhills * terrainnoise.tiles[0]) if (numhills > 0): peaks = terrainnoise.extremes(mindist=5, buffer=6, num=numhills) hilllayers = [] hill = 0 peak = 0 while (hill < numhills and peak < len(peaks)): x, y = peaks[peak] radius = 1 if (not self.regiontile(x, y).allwater): hilllayers.append([x, y, radius]) hill += 1 peak += 1 for y in range(self.height): for x in range(self.width): for layer in hilllayers: xysq = (x - layer[0])**2 + (y - layer[1])**2 if (xysq <= layer[2]**2): if (self.regiontile(x, y).allwater): self.regiontile(x, y).allwater = False if (abs(xysq - layer[2]**2) <= 2 and xysq != 0): diff = (x - layer[0], y - layer[1]) vec = vectorsbyclosestangle( diff, [(1, 0), (-1, 0), (0, 1), (0, -1), (1, 1), (-1, 1), (1, -1), (-1, -1)]) self.regiontile(x, y).elevationdir = vec elif (xysq == 0): self.regiontile(x, y).elevationdir = None # third, do rivers if (not self.biome in ['ice cap', 'volcano', 'polar', 'mountain']): upslopedirections = [direction \ for direction in adjtiles \ if (adjtiles[direction].dist2coast > self.dist2coast) and 0 in direction] # 1) find center cx, cy = (self.width // 2, self.height // 2) # (16, 16) # 2) path from center to one output, and from inputs to center riveroutput = None if (len(downslopedirections) > 0): riveroutput = ( min(cx + downslopedirections[0][0] * self.width // 2, self.width - 1), min(cy + downslopedirections[0][1] * self.height // 2, self.height - 1)) riverinputs = [] riverinputs.extend(self.newriverinputs(upslopedirections)) for updir in upslopedirections: x, y = (min(cx + updir[0] * self.width // 2, self.width - 1), min(cy + updir[1] * self.height // 2, self.height - 1)) riverinputs.append((x, y)) self.setneighbors(True) riverpathtoout = None if (not riveroutput is None): riverpathtoout = astar((cx, cy), riveroutput, self, terrainnoise) riverpathsfromin = [] for riverin in riverinputs: path = astar(riverin, (cx, cy), self, terrainnoise) riverpathsfromin.append(path) # 3) draw paths, stop when hitting another river # first draw from center to output, if any if (not riverpathtoout is None): prevtile = riverpathtoout[0] for i in range(len(riverpathtoout)): x, y = riverpathtoout[i] riverdir = None if (i - 1 >= 0): prevx, prevy = riverpathtoout[i - 1] riverdir = (x - prevx, y - prevy) else: riverdir = downslopedirections[0] regtile = self.regiontile(x, y) if (not regtile.allwater): if (regtile.riverout is None): regtile.riverout = riverdir if (i - 1 >= 0): self.regiontile( *prevtile).riverin.append(riverdir) else: break for path in riverpathsfromin: previousriverdir = None for i in range(len(path)): x, y = path[i] riverdir = None # if not at the last node if (i + 1 < len(path)): # get the next node x2, y2 = path[i + 1] previousriverdir = riverdir riverdir = (x2 - x, y2 - y) regtile = self.regiontile(x, y) # if it doesn't already have a river, or is a lake/ocean if (not regtile.allwater): if (regtile.riverout is None): regtile.riverout = riverdir regtile.riverin.append(previousriverdir) else: break # 4) if hit a river going opposite directions, form a lake valleys = terrainnoise.extremes(mindist=5, minmax='min', buffer=6, num=self.numlakes) if (riverpathtoout is None): self.regiontile(cx, cy).allwater = True for v in valleys: self.regiontile(*v).allwater = True # last, do forests cx, cy = (self.width // 2, self.height // 2) for y in range(self.height): for x in range(self.width): p = 1.0 - terrainnoise.get(x, y) adjregtiles = self.adjacenttiles(x, y, True) adjregtiles.append((x, y)) for tile in adjregtiles: if (not self.regiontile(*tile).riverout is None or self.regiontile(*tile).allwater): p /= RIVERFORESTMOD if (not self.regiontile(x, y).riverout is None): p /= RIVERFORESTMOD if (self.biome == 'mountain' and not self.regiontile(x, y).elevationdir is None): p *= MOUNTSLOPEFORESTMOD if (p < self.forestdensity): self.regiontile(x, y).forest = True elif (x < FORESTBORDERBLEED or x >= self.width - FORESTBORDERBLEED or y < FORESTBORDERBLEED or y >= self.height - FORESTBORDERBLEED): diff = (x - cx, y - cy) vec = vectorsbyclosestangle(diff, [(1, 0), (-1, 0), (0, 1), (0, -1)]) adjbiome = adjtiles[vec].biome if (adjbiome in biomedata and adjbiome != self.biome): adjforestdensity = \ biomedata[adjbiome]['forestdensity'] dist = min(x, y, self.width - x, self.height - y) borderforestdensity = lerp( self.forestdensity, adjforestdensity, float(dist) / FORESTBORDERBLEED) if (p < adjforestdensity): self.regiontile(x, y).forest = True ##### END OF TERRAIN, BEGIN POIs AND METADATA ########### # first, do towns (no towns on ice caps for now) # also, remove forests immediately around towns p1 = self.tnoisegrids[0].tiles[0] p2 = self.tnoisegrids[1].tiles[0] numtowns = int(((p1 + p2) / 2.0 * float(self.maxtowns)) + 0.5) towngrid = self.tnoisegrids[2] towngrid = towngrid.sizedown(2) for y in range(self.height): for x in range(self.width): if (self.regiontile(x, y).riverout): towngrid.tiles[x + towngrid.size * y] *= \ TOWNRIVERMOD if (self.regiontile(x, y).allwater): adjwatertiles = self.adjacenttiles(x, y, True) for tile in adjwatertiles: if (not self.regiontile(*tile).allwater): towngrid.tiles[ tile[0] + towngrid.size * tile[1]] *= \ TOWNRIVERMOD townlocs = towngrid.extremes(mindist=3, buffer=2, minmax='max', num=numtowns) for x, y in townlocs: if (not self.regiontile(x, y).allwater): self.regiontile(x, y).poi = 'town' self.towns[(x, y)] = 1 #Town() # cut down nearby trees adjtiles = self.adjacenttiles(x, y, True) self.regiontile(x, y).forest = False for tile in adjtiles: self.regiontile(*tile).forest = False # second, do roads if (len(townlocs) > 1): # find average point of all town positions avgpos = self.avgtuples(townlocs) firsttown, dist = self.nextmindistnode(avgpos, townlocs) # calculate endpoints points = self.roadpaths(firsttown, townlocs) # calculate paths roadlist = [ ] # may want to turn this into a 2-dim dict, faster costmap = [4] * self.size**2 for y in range(self.height): for x in range(self.width): # avoid allwater completely if (self.regiontile(x, y).allwater or self.regiontile(x, y).islava): costmap[x + self.width * y] = self.size**2 # avoid rivers if possible elif (self.regiontile(x, y).riverout): costmap[x + self.width * y] = 40 # avoid changes in elevation somewhat elif (self.regiontile(x, y).elevationdir): costmap[x + self.width * y] = 40 # prefer non-forest to forest elif (self.regiontile(x, y).forest): costmap[x + self.width * y] = 20 # draw paths # diagonal paths? DIAGONAL_ROADS = False self.setneighbors(DIAGONAL_ROADS) for start, stop in points: path = basicastar(start, stop, self, costmap) for x, y in path: if not (x, y) in roadlist: roadlist.append((x, y)) costmap[x + self.width * y] = \ costmap[x + self.width * y] // 4 for x, y in roadlist: regtile = self.regiontile(x, y) if (regtile.forest and self.tnoisegrids[0].get(x, y) >= FORESTROADPROB): continue if x + 1 < self.width and (x + 1, y) in roadlist: regtile.roaddirs.append((1, 0)) if x - 1 >= 0 and (x - 1, y) in roadlist: regtile.roaddirs.append((-1, 0)) if y + 1 < self.height and (0, 1) in roadlist: regtile.roaddirs.append((0, 1)) if y - 1 >= 0 and (x, y - 1) in roadlist: regtile.roaddirs.append((0, -1)) if (DIAGONAL_ROADS): if (x + 1 < self.width and y + 1 < self.height and (x + 1, y + 1) in roadlist): regtile.roaddirs.append((1, 1)) if (x + 1 < self.width and y - 1 >= 0 and (x + 1, y - 1) in roadlist): regtile.roaddirs.append((1, -1)) if (x - 1 >= 0 and y + 1 < self.height and (x - 1, y + 1) in roadlist): regtile.roaddirs.append((-1, 1)) if (x - 1 >= 0 and y - 1 >= 0 and (x - 1, y - 1) in roadlist): regtile.roaddirs.append((-1, -1)) # third, do POIs # last, do dungeons
def generate_river_simple(self, nb_river=1, waypoints_nb=3, _start=None, _end=None): if (_start == None or _end == None) and (len([ t for t in self.get_tiles_1D() if t.get_type() in ["MOUNTAIN"] ]) <= 0 or len([ t for t in self.get_tiles_1D() if t.get_type() in ["SHALLOW_WATER", "DEEP_WATER"] ]) <= 0): return for i in range(nb_river): if _start == None: start = np.random.choice([ t for t in self.get_tiles_1D() if t.get_type() in ["MOUNTAIN"] ]) else: start = _start if _end == None: end = np.random.choice([ t for t in self.get_tiles_1D() if t.get_type() in ["SHALLOW_WATER", "DEEP_WATER"] ]) else: end = _end # river_path_length = pf.getPathLength(start, end, self, cost_dict=p.type2cost_river, dn=True) # print(river_path_length) # waypoints = [] # start_to_end_dir = utils.angle_from_points((start.x, start.y), (end.x, end.y)) # start_to_end_dist = utils.distance2p((start.x, start.y), (end.x, end.y)) # waypoints.append(utils.point_from_direction((start.x, start.y), 5, utils.random_angle_in_direction(start_to_end_dir, 45), as_int=True)) # _pass = 0 # while utils.distance2p(waypoints[-1], (end.x, end.y)) > start_to_end_dist*0.1 and _pass<10: # _pass += 1 # start_to_end_dir = utils.angle_from_points(waypoints[-1], (end.x, end.y)) # print(utils.distance2p(waypoints[-1], (end.x, end.y)), start_to_end_dir) # waypoints.append(utils.point_from_direction(waypoints[-1], start_to_end_dist*0.1, utils.random_angle_in_direction(start_to_end_dir, 45), as_int=True)) # print(waypoints, (end.x, end.y)) river_path = pf.astar(start, end, self, cost_dict=p.type2cost_river, dn=True) for i, r in enumerate(river_path): if r.get_type() in ["SHALLOW_WATER", "DEEP_WATER" ] or r.is_river: break r.is_river = True self.rivers_path.append(river_path[:i + 1]) self.river_tiles = utils.flatten(self.rivers_path)
[9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 1, 9], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [9, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 1, 9], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]], dtype=np.int32) # Testing top left to bottom right spawn1_correct = (40, 30) spawn2_correct = (150, 140) # amount = 100 solution, cost, closed_set = astar(spawn1_correct, spawn2_correct, pathing_grid, diagonal=1, debug=True) t0 = time.time() # for i in range(amount): # # solution, cost, closed_set = astar((0, 0), (10, 13), nmap, diagonal=True) # solution, cost, closed_set = astar(spawn1_correct, spawn2_correct, pathing_grid, diagonal=True) solution, cost, closed_set = astar(spawn1_correct, spawn2_correct, pathing_grid, diagonal=1, debug=True) t1 = time.time() np.save("path.npy", solution) # list(closed_set)) print(f"Time: {t1-t0}\nTotal path length: {cost}\nPath: {solution}") print(len(closed_set), closed_set)
def main(): tick_time = 10 size = ( 500, 500 ) # can't change this yet without creating an issue with the board scale cell_size = 10 Cube.rows = size[0] // cell_size background_colour = (20, 20, 20) # instantiate the rendering object (surface), BG colour, and title screen = pygame.display.set_mode(size) screen.fill(background_colour) pygame.display.set_caption("Maze Game") clock = pygame.time.Clock() # Create Maze maze = Maze(size, cell_size) distance = manhattan_distance(maze.goal) maze.zombies.append(Zombie(maze.start, Colour.ZOMBIE)) solution = pathfinding.astar(maze.start, maze.goal_test, maze.successors, distance) cached_path = pathfinding.node_to_path( solution) if solution is not None else [] # Game Loop running = True # len(cached_path) > 0 current = None while running: screen.fill(background_colour) maze.draw_grid(screen) maze.draw_cells(screen) for event in pygame.event.get(): if event.type == pygame.QUIT: running = False break # Check for a left mouse click down mouse_pressed = pygame.mouse.get_pressed() if mouse_pressed == (1, 0, 0): (x, y) = pygame.mouse.get_pos() maze.click_create_tower(x, y) clock.tick(tick_time) # 30 would be real time - slower < 30 > faster _temp = current if _temp is not None and maze.cells[_temp.row][ _temp.col].color != Colour.BLOCKED: maze.cells[_temp.row][_temp.col].color = Colour.EMPTY current = cached_path.pop(0) if maze.cells[current.row][current.col].color == Colour.EMPTY: maze.cells[current.row][current.col].color = Colour.ZOMBIE elif maze.cells[current.row][current.col].color != Colour.EMPTY: # need to invalidate the cache and start again if _temp is not None: # maze.cells[_temp.row][_temp.col].color = Colour.PATH maze.cells[current.row][current.col].color = Colour.BLOCKED solution = pathfinding.astar(_temp, maze.goal_test, maze.successors, distance) else: solution = pathfinding.astar(current, maze.goal_test, maze.successors, distance) if solution is None: print("No solution found using A*!") break else: # print("Problem: Recalculating!") cached_path = pathfinding.node_to_path(solution) maze.draw_cells(screen) pygame.display.update()
def plan(domain, problem, useheuristic=True): """ Find a solution to a planning problem in the given domain The parameters domain and problem are exactly what is returned from pddl.parse_domain and pddl.parse_problem. If useheuristic is true, a planning heuristic (developed in task 4) should be used, otherwise use pathfinding.default_heuristic. This allows you to compare the effect of your heuristic vs. the default one easily. The return value of this function should be a 4-tuple, with the exact same elements as returned by pathfinding.astar: - A plan, which is a sequence of graph.Edge objects that have to be traversed to reach a goal state from the start. Each Edge object represents an action, and the edge's name should be the name of the action, consisting of the name of the operator the action was derived from, followed by the parenthesized and comma-separated parameter values e.g. "move(agent-1,sq-1-1,sq-2-1)" - distance is the number of actions in the plan (i.e. each action has cost 1) - visited is the total number of nodes that were added to the frontier during the execution of the algorithm - expanded is the total number of nodes that were expanded (i.e. whose neighbors were added to the frontier) """ ''' domain[0] = pddl_types, domain[1] = pddl_constants, domain[2] = pddl_predicates, domain[3] = pddl_actions problem[0] = pddl_objects, problem[1] = pddl_init_exp, problem[2] = pddl_goal_exp ''' def heuristic(state, action): """Calculates a heuristic following some basic principles of Fast-Forward algorithm""" # initial state is a neighbor of a previous state that A* needs to evaluate props_layer = state # relaxed plan has layers, each with and action layer and a propositions layer relaxed_plan_graph = [[[], props_layer]] # extend one action layer and proposition layer at a time while goal is not reached while not isgoal(props_layer): # next action layer: get "relaxed" neighbors whose Add Lists have a real effect and ignoring Delete Lists actions_layer = props_layer.get_neighbors(True) # next props layer: start with propositions in current layer and add new ones generated by each new action next_props_layer = set(props_layer.world.atoms) for next_action in actions_layer: next_props_layer = next_props_layer.union( next_action.target.world.atoms) # stop if next propositions layer did not add any new propositions, otherwise continue in the loop if props_layer.world.atoms.issuperset(next_props_layer): break # new propositional layer new_world = expressions.World(next_props_layer, props_layer.world.sets) props_layer = graph.ExpressionNode(new_world, props_layer.actions, props_layer.preceding_action) # add new actions and props layer to relaxed plan relaxed_plan_graph.append([actions_layer, props_layer]) # extract relaxed plan size and return it as the heuristic value return extract_plan_size(relaxed_plan_graph) def extract_plan_size(rpg): """Extract relaxed plan size based on the number of actions required to complete it""" goal = problem[2] final_state = rpg[len(rpg) - 1][1] # if the world in final proposition layer does not contain the goal, return magic large number as h ... if not isgoal(final_state): return 1000 # find the layer where each sub-goal appears for the first time on the relaxed planning graph first_goal_levels = {} add_first_goal_levels(rpg, goal, first_goal_levels) # obtain maximum level number where a goal was found first_goal_levels_max = max(first_goal_levels.keys()) # backtrack starting on the last proposition layer we need to consider logger.debug("Goal Levels: %s" % first_goal_levels) for i in range(first_goal_levels_max, 0, -1): logger.debug("BACKTRACKING i: %s" % i) # if there is at least one sub-goal on level i if i in first_goal_levels: add_first_action_levels(rpg, first_goal_levels, i) logger.debug("Action-Goal Levels: %s" % first_goal_levels) h = 0 for layer, actions in first_goal_levels.items(): h += len(actions) return h def add_first_goal_levels(rpg, goal, first_goal_levels): """Find the layer where each sub-goal appears for the first time on the relaxed planning graph""" # handle special case when the goal is not a conjunction of atoms, but one atom if isinstance(goal, expressions.Atom): goal = expressions.And([goal]) # for each sub-goal in goal for sub_goal in goal.operands: level = 0 # for each layer in relaxed planning graph for layer in rpg: # if the world in this propositional layer models this sub-goal, add sub-goal to that level if layer[1].world.models(sub_goal): if level in first_goal_levels: first_goal_levels[level] = first_goal_levels[ level].union({sub_goal}) else: first_goal_levels[level] = {sub_goal} # break to guarantee we always only use only the first appearance break level += 1 def add_first_action_levels(rpg, first_goal_levels, layer): """Find the layer where each action whose effect is a sub-goal appears for the first time on the relaxed planning graph. Then consider its preconditions as new sub-goals and add them to preceding layers of first_goal_levels that will eventually be reached by the backtracking process to also process their preconditions""" for sub_goal in first_goal_levels[layer]: level = 0 # for each layer in the relaxed planning graph for layer_index in range(len(rpg)): # for each action on this layer of the relaxed planning graph for action in rpg[layer_index][0]: # determine if this action introduces sub_goal for the first time previous_props_layer = rpg[layer_index - 1][1] next_props_layer = action.target if next_props_layer.world.models( sub_goal ) and not previous_props_layer.world.models(sub_goal): # each precondition must now be considered a sub-goal preconditions = action.target.preceding_action.expression.operands[ 0] logger.debug("\tACTION: %s" % action.name) logger.debug("\tPRECONS: %s" % preconditions) # find the layer where each sub-goal appears for the first time on the relaxed planning graph add_first_goal_levels(rpg, preconditions, first_goal_levels) # break to guarantee we always only use only the first appearance break level += 1 def isgoal(state): """Check is goal is reached""" return state.world.models(problem[2]) # get the sets variable required to make a n initial world world_sets = build_world_sets(domain[1], problem[0], domain[0]) # get all expanded expressions for all actions expanded_expressions = [] # for each action in the domain for action in domain[3]: substitutions_per_action = [] # for each group of params of the same type for this action for parameter_type in action.parameters: logger.debug("Action: %s - Param Type: %s - Params: %s" % (action.name, parameter_type, action.parameters[parameter_type])) # for each param in each group of params of the same type for this action for parameter in action.parameters[parameter_type]: substitutions_per_param = [] # for each ground param as taken from world_sets based on type for ground_param in world_sets[parameter_type]: logger.debug("\tParam: %s, Ground Param: %s" % (parameter, ground_param)) substitutions_per_param.append([parameter, ground_param]) substitutions_per_action.append(substitutions_per_param) # expand the action with all possible substitutions expanded_expressions.extend( expand_action(action, substitutions_per_action)) # create the initial world with pddl_init_exp and world_sets and the start node for astar logger.info("Grounded Actions: %s" % len(expanded_expressions)) world = expressions.make_world(problem[1], world_sets) start = graph.ExpressionNode(world, expanded_expressions, None) return pathfinding.astar( start, heuristic if useheuristic else pathfinding.default_heuristic, isgoal)
def LevelGridUpdate(self): mx, my = PKFW.gtmp() #mouse x and y tx = int(mx/self.tileSize['width']) ty = int(my/self.tileSize['height']) #current tile mouse is over if(tx<0):tx=0 if(ty<0):ty=0 if(tx>self.levelSize['x']-1): tx = self.levelSize['x']-1 if(ty>self.levelSize['y']-1): ty = self.levelSize['y']-1 #current tile mouse is over window clamp #left mouse button sets tile to start #right mouse button sets tile to wall #middle mouse button sets tile to blank if(PKFW.gmpr(0) and self.buttonpressed[0] is False): self.buttonpressed[0] = True self.levelTiles[tx][ty].setS(sprites[1]) self.buttonpressed[0] = not PKFW.gtmr(0) if(PKFW.gmpr(1)): self.levelTiles[tx][ty].setS(sprites[3]) if(PKFW.gmpr(2)): self.levelTiles[tx][ty].setS(sprites[0]) #if the start exists and there is only one finish and the 'a' key is pressed #astar runs and sets the tiles in the returned path to the path sprite if isinstance(start, Tile) and len(finish)==1 and PKFW.gtkp(65) and self.buttonpressed[1] is False: self.buttonpressed[1] = True self.path = pathfinding.astar(start, finish[0], self.levelTiles, self.levelSize) if(isinstance(self.path, list)): for i in range(len(self.path)): self.path[i].setS(sprites[4]) self.buttonpressed[1] = not PKFW.gtkr(65) #if the start exists and there is at least 1 finish and the 'd' key is pressed #dijkstra runs (path tiles sprite set to path) if isinstance(start, Tile) and len(finish)>0 and PKFW.gtkp(68) and self.buttonpressed[2] is False: self.buttonpressed[2] = True self.path = pathfinding.dijkstra(start, finish, self.levelTiles, self.levelSize) if(isinstance(self.path,list)): for i in range(len(self.path)): self.path[i].setS(sprites[4]) self.buttonpressed[2] = not PKFW.gtkr(68) #checks if path is a list #if so then delete all the elements and set to None #in case previous tiles from previous paths are left over when attempting new path if(isinstance(self.path,list)): for i in range(len(self.path)): self.path[i].resetTile() del self.path[:] self.path = None #when the 'c' button is pressed all the tiles that are not walls are set to blank tiles if(PKFW.gtkp(67)): for x in range(len(self.levelTiles)): for y in range(len(self.levelTiles[x])): if(self.levelTiles[x][y].Sid != sprites[3]): self.levelTiles[x][y].resetTile() self.levelTiles[x][y].setS(sprites[0]) #the f1 button clears all the wanderes and tiles and initialises the flock class #to return to the wanderers and tiles press ESC if(PKFW.gtkp(kd.keys['F1']) and self.buttonpressed[3] is False): self.buttonpressed[3] = True self.flock = ai.flock(30, sprites[5]) for i in range (len(self.wanderers)): del self.wanderers[:] for x in range(len(self.levelTiles)): for y in range(len(self.levelTiles[x])): self.levelTiles[x][y].setS(sprites[0]) self.buttonpressed[3] = not PKFW.gtkr(kd.keys['F1']) #pressing f12 sets debug to !debug #used when creating boid steering behaviours if(PKFW.gtkp(kd.keys['F12']) and self.buttonpressed[4] is False): self.buttonpressed[4] = True kd.debug = not kd.debug self.buttonpressed[4] = not PKFW.gtkr(kd.keys['F12'])