def take_turn(self): monster = self.owner try: if self.path==None: self.path = libtcod.path_new_using_map(copy.deepcopy(path_map)) except: self.path = libtcod.path_new_using_map(copy.deepcopy(path_map)) try: if self.target_x is None or self.target_y is None: self.randomize_target() except: self.randomize_target() stepx,stepy=0,0 moved=False if monster.distance_to(Point(self.target_x,self.target_y)) >= 2 and self.success: stepx,stepy=libtcod.path_walk(self.path,False) moved=monster.move(stepx-monster.x,stepy-monster.y) if not moved: if stepx!=0 and stepy!=0: blocking=get_object(stepx,stepy) if blocking is not None and hasattr(blocking,"fighter") and blocking.fighter is not None: self.recalculate() if blocking.fighter.hp > 0 and monster.distance_to(blocking) < 2: monster.fighter.attack(blocking) else: self.randomize_target() else: self.randomize_target()
def move_astar(self, target, game_map): """ """ logger.debug(f"{self.name} A* Observe") fov = tcod.map_new(game_map.w, game_map.h) for tile in game_map: tcod.map_set_properties(fov, tile.x, tile.y, not tile.opaque, not tile.blocked) for entity in game_map.entities: if entity == self: continue if entity.kind != Entities.ITEM: tcod.map_set_properties(fov, entity.x, entity.y, True, False) my_path = tcod.path_new_using_map(fov, 1.41) logger.debug(f"{self.name} A* Orient") tcod.path_compute(my_path, self.x, self.y, target.x, target.y) logger.debug(f"{self.name} A* Decide") if not tcod.path_is_empty(my_path) and tcod.path_size(my_path) < 25: logger.debug("{self.name} A* Act 0") self.position = tcod.path_walk(my_path, True) else: logger.debug(f"{self.name} A* Act 1") self.move_towards(target.x, target.y, game_map) tcod.path_delete(my_path)
def move_astar(self, target, entities, game_map): #Create an FOV map with the dimensions of the map fov = tcod.map_new(game_map.width, game_map.height) #Scan the map each turn and set all the walls as unwalkable for y1 in range(game_map.height): for x1 in range(game_map.width): tcod.map_set_properties(fov, x1, y1, not game_map.tiles[x1][y1].block_sight, not game_map.tiles[x1][y1].blocked) #Scan all objects to see if there are objects that must be navigated around (generally enemies) #Checks if each object isn't self or target (so the start and end points are not marked as unwalkable) for entity in entities: if entity.blocks and entity != self and entity != target: tcod.map_set_properties(fov, entity.x, entity.y, True, False) #Allocate the A* path #1.41 is the rough cost of diagonal movement (sqrt 2) my_path = tcod.path_new_using_map(fov, 1.41) # tcod.path_compute(my_path, self.x, self.y, target.x, target.y) if not tcod.path_is_empty(my_path) and tcod.path_size(my_path) < 25: x, y = tcod.path_walk(my_path, True) if x or y: self.x = x self.y = y else: self.move_towards(target.x, target.y, game_map, entities) tcod.path_delete(my_path)
def move_astar(self, target, entities, game_map): fov = libtcod.map_new(game_map.width, game_map.height) for y1 in range(game_map.height): for x1 in range(game_map.width): libtcod.map_set_properties(fov, x1, y1, not game_map.tiles[x1][y1].block_sight, not game_map.tiles[x1][y1].blocked) for entity in entities: if entity.blocks and entity != self and entity != target: libtcod.map_set_properties(fov, entity.x, entity.y, True, False) my_path = libtcod.path_new_using_map(fov, 1.41) libtcod.path_compute(my_path, self.x, self.y, target.x, target.y) if not libtcod.path_is_empty(my_path) and libtcod.path_size(my_path) < 25: x, y = libtcod.path_walk(my_path, True) if x or y: self.x = x self.y = y else: self.move_towards(target.x, target.y, game_map, entities) libtcod.path_delete(my_path)
def move_astar(self, entity, viewshed, position_component, player_x, player_y): # target, entities, game_map): # /!\ On utilise visible_tiles, qui concerne ce que le mob voit = le transparent est consideré comme walkable. # /!\ Libtcod fonctionne en y,x et pas en x, y. Melange facile à faire, a ameliorer! # /!\ Pas de second check sur le deplacement, on teleporte le mob. Danger si walkable. # Create a FOV map that has the dimensions of the map fov = viewshed.visible_tiles current_map = World.fetch('current_map') # Allocate a A* path # The 1.41 is the normal diagonal cost of moving, it can be set as 0.0 if diagonal moves are prohibited my_path = tcod.path_new_using_map(fov, 1.41) # Compute the path between self's coordinates and the target's coordinates tcod.path_compute(my_path, position_component.y, position_component.x, player_y, player_x) # Check if the path exists, and in this case, also the path is shorter than 25 tiles # The path size matters if you want the monster to use alternative longer paths # (for example through other rooms) if for example the player is in a corridor # It makes sense to keep path size relatively low to keep the monsters from running around the map # if there's an alternative path really far away if not tcod.path_is_empty(my_path) and tcod.path_size(my_path) < 25: # Find the next coordinates in the computed full path y, x = tcod.path_walk(my_path, True) # tcod : [y][x] if x or y: tcod.path_delete(my_path) return x, y # Set self's coordinates to the next path tile tcod.path_delete(my_path) return False
def move_astar(self, target, entities, game_map): fov = tcod.map_new(game_map.width, game_map.height) # Walls are impassable of course for y1 in range(game_map.height): for x1 in range(game_map.width): tcod.map_set_properties( fov, x1, y1, not game_map.tiles[x1][y1].block_sight, not game_map.tiles[x1][y1].block_movement) for entity in entities: if entity.block_movement and entity != self and entity != target: # Treat allies as transparent but impassable tcod.map_set_properties(fov, entity.x, entity.y, True, False) # The 1 is diagonal cost, normally would be 1.41, but my_path = tcod.path_new_using_map(fov, 1) tcod.path_compute(my_path, self.x, self.y, target.x, target.y) # Low path size so that monsters won't run off in some random lil corridor if not tcod.path_is_empty(my_path) and tcod.path_size(my_path) < 20: x, y = tcod.path_walk(my_path, True) # True means to recompute path if x or y: # wtf self.x = x self.y = y else: # Backup option if not movable (corridor stuck) self.move_towards(target.x, target.y, game_map, entities) # Freeing some ram tcod.path_delete(my_path)
def move_astar(self, target, game_map, mobs): if not self.stats or time.time( ) - self.stats.mv_time < self.stats.mv_wait: return self.stats.mv_time = time.time() fov = tc.map_new(game_map.wd, game_map.ht) for x in range(0, game_map.wd): for y in range(0, game_map.ht): tc.map_set_properties(fov, x, y, not game_map.cells[x][y].is_discovered, not game_map.cells[x][y].is_blocked) for mob in mobs: if mob != self and mob != target: tc.map_set_properties(fov, mob.x, mob.y, True, False) path = tc.path_new_using_map(fov, 0) tc.path_compute(path, self.x, self.y, target.x, target.y) if not tc.path_is_empty(path) and tc.path_size(path) < 30: x, y = tc.path_walk(path, True) if x or y: self.x = x self.y = y else: self.move_to_target(target.x, target.y, game_map, mobs) tc.path_delete(path)
def move_astar(self, alvo, entidades, mapa): fov = libtcod.map_new(mapa.largura, mapa.altura) for y1 in range(mapa.altura): for x1 in range(mapa.largura): libtcod.map_set_properties(fov, x1, y1, not mapa.tiles[x1][y1].bloqueio_visao, not mapa.tiles[x1][y1].bloqueado) for entidade in entidades: if entidade.bloqueia and entidade != self and entidade != alvo: libtcod.map_set_properties(fov, entidade.x, entidade.y, True, False) # Allocate a A* path # The 1.41 is the normal diagonal cost of moving, it can be set as 0.0 if diagonal moves are prohibited my_path = libtcod.path_new_using_map(fov, 1.41) # Compute the path between self's coordinates and the target's coordinates libtcod.path_compute(my_path, self.x, self.y, alvo.x, alvo.y) # Check if the path exists, and in this case, also the path is shorter than 25 tiles # The path size matters if you want the monster to use alternative longer paths (for example through other rooms) if for example the player is in a corridor # It makes sense to keep path size relatively low to keep the monsters from running around the map if there's an alternative path really far away if not libtcod.path_is_empty(my_path) and libtcod.path_size(my_path) < 25: # Find the next coordinates in the computed full path x, y = libtcod.path_walk(my_path, True) if x or y: # Set self's coordinates to the next path tile self.x = x self.y = y else: self.mover_para(alvo.x, alvo.y, mapa, entidades) # Delete libtcod.path_delete(my_path)
def test_astar_single(): astar = tcod.path_new_using_map(maps[0]) for _ in range(PATH_NUMBER): tcod.path_compute(astar, 0, 0, MAP_WIDTH - 1 , MAP_HEIGHT - 1) x, y = tcod.path_walk(astar, False) while x is not None: x, y = tcod.path_walk(astar, False)
def move_to_coordinates_step(x, y, dest_x, dest_y, map): fov_map = map.fov_map path = tcod.path_new_using_map(fov_map, 1) tcod.path_compute(path, x, y, dest_x, dest_y) x, y = tcod.path_walk(path, True) tcod.path_delete(path) return x, y
def move_astar(self, target_x, target_y, entities, nav_map): for entity in entities: if entity.blocks and entity != self and entity.x != target_x and entity.y != target_y: libtcod.map_set_properties(nav_map, entity.x, entity.y, True, False) my_path = libtcod.path_new_using_map(nav_map, 1.41) libtcod.path_compute(my_path, self.x, self.y, target_x, target_y) if not libtcod.path_is_empty( my_path) and libtcod.path_size(my_path) < 25: x, y = libtcod.path_walk(my_path, True) if x or y: if abs(x) == abs(y) and self.energy >= 141: self.x = x self.y = y self.energy = 0 elif self.energy >= 100: self.x = x self.y = y self.energy = 0 else: self.move_towards(target_x, target_y, nav_map, entities) # Delete the path to free memory libtcod.path_delete(my_path)
def move_to(self, target, game_map, entities, tile_map, player): move_results = [] path_map = tile_map add_entities_to_path_map(path_map, entities, self, player) tcod.map_set_properties(path_map, self.x, self.y, True, True) # Allocate a A* path my_path = tcod.path_new_using_map(path_map, 1.41) # Compute the path between self's coordinates and the target's coordinates tcod.path_compute(my_path, self.x, self.y, target.x, target.y) if not tcod.path_is_empty(my_path) and tcod.path_size(my_path) < 25: # Find the next coordinates in the computed full path x, y = tcod.path_walk(my_path, True) if x or y: # Set self's coordinates to the next path tile self.x = x self.y = y else: self.move_towards(target.x, target.y, game_map, entities) tcod.map_set_properties(path_map, self.x, self.y, True, False) tcod.path_delete(my_path) # Interact with landed on tile move_results.extend( game_map.tiles[self.x][self.y].overlap_entity(self)) return move_results
def test_astar_single(): astar = tcod.path_new_using_map(maps[0]) for _ in range(PATH_NUMBER): tcod.path_compute(astar, 0, 0, MAP_WIDTH - 1, MAP_HEIGHT - 1) x, y = tcod.path_walk(astar, False) while x is not None: x, y = tcod.path_walk(astar, False)
def move_astar(self, target): fov = tcod.map_new(MAP_WIDTH, MAP_HEIGHT) for y1 in range(MAP_HEIGHT): for x1 in range(MAP_WIDTH): tcod.map_set_properties(fov, x1, y1, not map[x1][y1].block_sight, not map[x1][y1].blocked) for obj in objects: if obj.blocks and obj != self and obj != target: tcod.map_set_properties(fov, obj.x, obj.y, True, False) path = tcod.path_new_using_map(fov, 0) # 0: 대각선 금지, 1.41: 대각선 코스트(루트 2) tcod.path_compute(path, self.x, self.y, target.x, target.y) if not tcod.path_is_empty(path) and tcod.path_size(path) < 25: x, y = tcod.path_walk(path, True) if x or y: self.x = x self.y = y self.wait = self.speed else: self.move_towards(target.x, target.y) tcod.path_delete(path)
def move_astar(self, target, entities, game_map): # mapの寸法を持つfovマップを作製する fov = libtcod.map_new(game_map.width, game_map.height) # 現在の地図を毎回スキャンし、全ての壁を歩けないように設定する for y1, x1 in itertools.product(range(game_map.height), range(game_map.width)): libtcod.map_set_properties(fov, x1, y1, not game_map.tiles[x1][y1].block_sight, not game_map.tiles[x1][y1].blocked) # 全てのオブジェクトをスキャンして移動させるオブジェクトを確認する for entity in entities: if entity.blocks and entity != self and entity != target: libtcod.map_set_properties(fov, entity.x, entity.y, True, False) my_path = libtcod.path_new_using_map(fov, 1.41) libtcod.path_compute(my_path, self.x, self.y, target.x, target.y) if not libtcod.path_is_empty( my_path) and libtcod.path_size(my_path) < 25: x, y = libtcod.path_walk(my_path, True) if x or y: self.x = x self.y = y else: self.move_towards(target.x, target.y, game_map, entities) libtcod.path_delete(my_path)
def move_astar(self, target, entities, game_map): # Create FOV map with dimensions of game map fov = libtcod.map_new(game_map.width, game_map.height) # Set walls as blocked for y1 in range(game_map.height): for x1 in range(game_map.width): libtcod.map_set_properties(fov, x1, y1, not game_map.tiles[x1][y1].block_sight, not game_map.tiles[x1][y1].blocked) # Check for entities that are blocked and need to be navigated around for entity in entities: if entity.blocks and entity != self and entity != target: libtcod.map_set_properties(fov, entity.x, entity.y, True, False) # Allocate an A* path my_path = libtcod.path_new_using_map(fov, 1.41) libtcod.path_compute(my_path, self.x, self.y, target.x, target.y) if not libtcod.path_is_empty(my_path) and libtcod.path_size(my_path) < 25: x, y = libtcod.path_walk(my_path, True) if x or y: self.x = x self.y = y else: self.move_towards(target.x, target.y, game_map, entities) libtcod.path_delete(my_path)
def move_astar(self, target, entities, game_map): fov = libtcod.map.Map(game_map.width, game_map.height) for y1 in range(game_map.height): for x1 in range(game_map.width): fov.transparent[y1, x1] = not game_map.tiles[x1][y1].block_sight fov.walkable[y1, x1] = not game_map.tiles[x1][y1].blocked for entity in entities: if entity.blocks and entity != self and entity != target: fov.transparent[entity.y, entity.x] = True fov.walkable[entity.y, entity.x] = False my_path = libtcod.path_new_using_map(fov, 1.41) libtcod.path_compute(my_path, self.x, self.y, target.x, target.y) if not libtcod.path_is_empty( my_path) and libtcod.path_size(my_path) < 25: x, y = libtcod.path_walk(my_path, True) if x or y: self.x = x self.y = y else: self.move_towards(target.x, target.y, game_map, entities)
def move_astar(self, target, entities, game_map): # Create a FOV map that has the dimensions of the map fov = libtcod.map_new(game_map.width, game_map.height) # Scan the current map each turn and set all the walls as unwalkable for y1 in range(game_map.height): for x1 in range(game_map.width): libtcod.map_set_properties(fov, x1, y1, not game_map.tiles[x1][y1].block_sight, not game_map.tiles[x1][y1].blocked) # Scan all the objects to see if there are objects that must be navigated around # Check also that the object isn't self or the target (so that the start and the end points are free) # The AI class handles the situation if self is next to the target so it will not use this A* function anyway # # THIS IS THE PROBLEM! We're not passing entities for movement, we're using Coords so the player blocks the target for entity in entities: if entity.blocks and entity != self and not (entity.x == target.x and entity.y == target.y): # Set the tile as a wall so it must be navigated around libtcod.map_set_properties(fov, entity.x, entity.y, True, False) # Allocate a A* path # The 1.41 is the normal diagonal cost of moving, it can be set as 0.0 if diagonal moves are prohibited if constants['AI_allow_diagonal']: diagonal_cost=1.41 else: diagonal_cost=0.0 my_path = libtcod.path_new_using_map(fov, diagonal_cost) # Compute the path between self's coordinates and the target's coordinates libtcod.path_compute(my_path, self.x, self.y, target.x, target.y) # Check if the path exists, and in this case, also the path is shorter than 25 tiles # The path size matters if you want the monster to use alternative longer paths (for example through other rooms) if for example the player is in a corridor # It makes sense to keep path size relatively low to keep the monsters from running around the map if there's an alternative path really far away #Print path debugging info #print("Pathing from [" + str(self.x) + ", "+ str(self.y) + "] to ["+ str(target.x) +", "+ str(target.y) +"]") #print (my_path) #for i in range(0, libtcod.path_size(my_path)): # print (libtcod.path_get(my_path, i)) if not libtcod.path_is_empty(my_path) and libtcod.path_size(my_path) < 200: # Find the next coordinates in the computed full path x, y = libtcod.path_walk(my_path, True) #print ("moving to [" +str(x)+ "," +str(y)+ "]") #DEBUG if x or y: # Set self's coordinates to the next path tile self.x = x self.y = y else: #print("using fallback pathing method") #DEBUG # Keep the old move function as a backup so that if there are no paths (for example another monster blocks a corridor) # it will still try to move towards the player (closer to the corridor opening) self.move_towards(target.x, target.y, game_map, entities) # Delete the path to free memory libtcod.path_delete(my_path)
def move_astar(self, target, entities, game_map): """Moves towards a target using the A* algorithm. TODO: Refactor this method to use new TCOD functions. TODO: Rewrite docstring to better document A* algorithm. TODO: Study and pick apart A* to understand how it works. Args: target(Entity): The Entity object to plot a path to. entities(list): A list of Entities. game_map(Map): The Map object used for displaying the game. """ # Create a FOV map that has the dimensions of the map fov = libtcod.map_new(game_map.width, game_map.height) # Scan the current map each turn and set all the walls as unwalkable for y1 in range(game_map.height): for x1 in range(game_map.width): # libtcod.map_set_properties(fov, x1, y1, not game_map.tiles[x1][y1].block_sight, not game_map.tiles[x1][y1].blocked) fov.transparent[y1, x1] = not game_map.tiles[x1][y1].block_sight fov.walkable[y1, x1] = not game_map.tiles[x1][y1].blocked # Scan all the objects to see if there are objects that must be navigated around # Check also that the object isn't self or the target (so that the start and the end points are free) # The AI class handles the situation if self is next to the target so it will not use this A* function anyway for entity in entities: if entity.blocks and entity != self and entity != target: # Set the tile as a wall so it must be navigated around # libtcod.map_set_properties(fov, entity.x, entity.y, True, False) fov.transparent[entity.y, entity.x] = True fov.walkable[entity.y, entity.x] = False # Allocate a A* path # The 1.41 is the normal diagonal cost of moving, it can be set as 0.0 if diagonal moves are prohibited my_path = libtcod.path_new_using_map(fov, 1.41) # Compute the path between self's coordinates and the target's coordinates libtcod.path_compute(my_path, self.x, self.y, target.x, target.y) # Check if the path exists, and in this case, also the path is shorter than 25 tiles # The path size matters if you want the monster to use alternative longer paths (for example through other rooms) if for example the player is in a corridor # It makes sense to keep path size relatively low to keep the monsters from running around the map if there's an alternative path really far away if not libtcod.path_is_empty(my_path) and libtcod.path_size(my_path) < 25: # Find the next coordinates in the computed full path x, y = libtcod.path_walk(my_path, True) if x or y: # Set self's coordinates to the next path tile self.x = x self.y = y else: # Keep the old move function as a backup so that if there are no paths (for example another monster blocks a corridor) # it will still try to move towards the player (closer to the corridor opening) self.move_towards(target.x, target.y, game_map, entities) # Delete the path to free memory libtcod.path_delete(my_path)
def take_turn(self): self.tick+=1 moved=False monster = self.owner if self.tick%self.period==0: try: if self.path==None: self.path = libtcod.path_new_using_map(path_map) except: self.path = libtcod.path_new_using_map(path_map) success=libtcod.path_compute(self.path, monster.x, monster.y, player.x, player.y) stepx,stepy=libtcod.path_walk(self.path,True) #move towards player if far away if monster.distance_to(player) >= 2 and success: moved=monster.move(stepx-monster.x,stepy-monster.y) #close enough, attack! (if the player is still alive.) for object in objects: if object!=monster and hasattr(object,"fighter") and object.fighter is not None and monster.distance_to(object) < 2 and object.fighter.hp>0 and not (object==player and moved): monster.fighter.attack(object)
def add_shortcuts(self, map_width, map_height): """ I use libtcodpy's built in pathfinding here, since I'm already using libtcodpy for the iu. At the moment, the way I find the distance between two points to see if I should put a shortcut there is horrible, and its easily the slowest part of this algorithm. If I think of a better way to do this in the future, I'll implement it. """ # initialize the libtcodpy map path_map = None libtcod_map = libtcod.map_new(map_width, map_height) self.recompute_path_map(map_width, map_height, libtcod_map) for i in range(self.shortcut_attempts): # check i times for places where shortcuts can be made while True: # Pick a random floor tile floor_x = randint(self.shortcut_length + 1, (map_width - self.shortcut_length - 1)) floor_y = randint(self.shortcut_length + 1, (map_height - self.shortcut_length - 1)) if self.game_map[floor_x][floor_y] == 0: if (self.game_map[floor_x - 1][floor_y] == 1 or self.game_map[floor_x + 1][floor_y] == 1 or self.game_map[floor_x][floor_y - 1] == 1 or self.game_map[floor_x][floor_y + 1] == 1): break # look around the tile for other floor tiles for x in range(-1, 2): for y in range(-1, 2): if x != 0 or y != 0: # Exclude the center tile new_x = floor_x + (x * self.shortcut_length) new_y = floor_y + (y * self.shortcut_length) if self.game_map[new_x][new_y] == 0: # run pathfinding algorithm between the two points # back to the libtcodpy nonesense path_map = libtcod.path_new_using_map(libtcod_map) libtcod.path_compute(path_map, floor_x, floor_y, new_x, new_y) distance = libtcod.path_size(path_map) if distance > self.min_path_finding_distance: # make shortcut self.carve_shortcut(floor_x, floor_y, new_x, new_y) self.recompute_path_map( map_width, map_height, libtcod_map) # destroy the path object if path_map: libtcod.path_delete(path_map)
def move_astar(self, game_map, target): width, height = game_map.get_map_sizes() # Create a FOV map that has the dimensions of the map fov = libtcod.map_new(width, height) # Scan the current map each turn and set all the walls as unwalkable for y1 in range(height): for x1 in range(width): libtcod.map_set_properties( fov, x1, y1, not game_map.tiles[x1][y1].block_sight, not game_map.tiles[x1][y1].blocked, ) # Scan all the objects to see if there are objects that must be navigated around # Check also that the object isn't self or the target (so that the start and the end points are free) # The AI class handles the situation if self is next to the target so it will not use this A* function anyway for entity in game_map.get_entities(): if entity.blocks and entity != self and entity != target: # Set the tile as a wall so it must be navigated around libtcod.map_set_properties(fov, entity.x, entity.y, True, False) # Allocate a A* path # The 1.41 is the normal diagonal cost of moving, it can be set as 0.0 if diagonal moves are prohibited my_path = libtcod.path_new_using_map(fov, 1.41) # Compute the path between self's coordinates and the target's coordinates libtcod.path_compute(my_path, self.x, self.y, target.x, target.y) # Check if the path exists, and in this case, also the path is shorter than 25 tiles # The path size matters if you want the monster to use alternative longer paths # (for example through other rooms) if for example the player is in a corridor # It makes sense to keep path size relatively low to keep the monsters from running around the map # if there's an alternative path really far away if not libtcod.path_is_empty( my_path) and libtcod.path_size(my_path) < 25: # Find the next coordinates in the computed full path x, y = libtcod.path_walk(my_path, True) if x or y: # Set self's coordinates to the next path tile self.x = x self.y = y self.end_turn() else: # Keep the old move function as a backup so that if there are no paths # (for example another monster blocks a corridor) # it will still try to move towards the player (closer to the corridor opening) self.move_towards(target.x, target.y) # Delete the path to free memory libtcod.path_delete(my_path)
def move_astar(self, target, game): game_map = game.map blocking_ents = game.walk_blocking_ents # Create a FOV map that has the dimensions of the map fov = tcod.map_new(game_map.width, game_map.height) # Scan the current map each turn and set all the walls as unwalkable for y1 in range(game_map.height): for x1 in range(game_map.width): tcod.map_set_properties( fov, x1, y1, not game_map.tiles[(x1, y1)].block_sight, not game_map.tiles[(x1, y1)].blocked) # Scan all the objects to see if there are objects that must be navigated around # Check also that the object isn't self or the target (so that the start and the end points are free) # The AI class handles the situation if self is next to the target so it will not use this A* function anyway for entity in blocking_ents: if entity != self and entity != target: # Set the tile as a wall so it must be navigated around tcod.map_set_properties(fov, entity.x, entity.y, True, False) # Allocate a A* path # The 1.41 is the normal diagonal cost of moving, it can be set as 0.0 if diagonal moves are prohibited my_path = tcod.path_new_using_map(fov, 1.41) # Compute the path between self's coordinates and the target's coordinates tcod.path_compute(my_path, self.x, self.y, target.x, target.y) # Check if the path exists, and in this case, also the path is shorter than 25 tiles # The path size matters if you want the monster to use alternative longer paths (for example through other rooms) if for example the player is in a corridor # It makes sense to keep path size relatively low to keep the monsters from running around the map if there's an alternative path really far away if not tcod.path_is_empty(my_path) and tcod.path_size(my_path) < 25: # Find the next coordinates in the computed full path x, y = tcod.path_walk(my_path, True) if x or y: # Set self's coordinates to the next path tile logging.debug(f'{self} is A*-moving.') if target.pos == ( x, y ): # At some edge-cases NPCs might attempt a* movement when they are adjacent to the target self.f.attack_setup(target, game) else: self.try_move(x, y, game, absolute=True) logging.debug(f'{self} finished A*-moving.') else: # Keep the old move function as a backup so that if there are no paths (for example another monster blocks a corridor) # it will still try to move towards the player (closer to the corridor opening) logging.debug( f'{self} could not find a* path. falling back to regular movement' ) self.move_towards(target, game)
def addShortcuts(self, mapWidth, mapHeight): ''' I use libtcodpy's built in pathfinding here, since I'm already using libtcodpy for the iu. At the moment, the way I find the distance between two points to see if I should put a shortcut there is horrible, and its easily the slowest part of this algorithm. If I think of a better way to do this in the future, I'll implement it. ''' #initialize the libtcodpy map libtcodMap = libtcod.map_new(mapWidth, mapHeight) self.recomputePathMap(mapWidth, mapHeight, libtcodMap) for i in range(self.shortcutAttempts): # check i times for places where shortcuts can be made while True: #Pick a random floor tile floorX = random.randint(self.shortcutLength + 1, (mapWidth - self.shortcutLength - 1)) floorY = random.randint(self.shortcutLength + 1, (mapHeight - self.shortcutLength - 1)) if self.level[floorX][floorY] == 0: if (self.level[floorX - 1][floorY] == 1 or self.level[floorX + 1][floorY] == 1 or self.level[floorX][floorY - 1] == 1 or self.level[floorX][floorY + 1] == 1): break # look around the tile for other floor tiles for x in range(-1, 2): for y in range(-1, 2): if x != 0 or y != 0: # Exclude the center tile newX = floorX + (x * self.shortcutLength) newY = floorY + (y * self.shortcutLength) if self.level[newX][newY] == 0: # run pathfinding algorithm between the two points #back to the libtcodpy nonesense pathMap = libtcod.path_new_using_map(libtcodMap) libtcod.path_compute(pathMap, floorX, floorY, newX, newY) distance = libtcod.path_size(pathMap) if distance > self.minPathfindingDistance: # make shortcut self.carveShortcut(floorX, floorY, newX, newY) self.recomputePathMap(mapWidth, mapHeight, libtcodMap) # destroy the path object libtcod.path_delete(pathMap)
def move_astar(self, target: 'Entity', entities: List['Entity'], game_map: 'GameMap') -> None: # Create a FOV map that has the dimensions of the map fov = tcod.map.Map(game_map.width, game_map.height, order='F') # Scan the current map each turn and set all the walls as unwalkable for y in range(game_map.height): for x in range(game_map.width): fov.transparent[x, y] = not game_map.tiles[x][y].block_sight fov.walkable[x, y] = not game_map.tiles[x][y].blocked # Scan all the objects to see if there are objects that must be # navigated around # Check also that the object isn't self or the target (so that the # start and the end points are free) # The AI class handles the situation if self is next to the target so # it will not use this A* function anyway for entity in entities: if entity.blocks and entity != self and entity != target: # Set the tile as a wall so it must be navigated around fov.walkable[entity.x, entity.y] = False # Allocate a A* path # The 1.41 is the normal diagonal cost of moving, it can be set as 0.0 # if diagonal moves are prohibited my_path = tcod.path_new_using_map(fov, 1.41) # Compute the path between self's coordinates and the target's # coordinates tcod.path_compute(my_path, self.x, self.y, target.x, target.y) # Check if the path exists, and in this case, also the path is shorter # than 25 tiles # The path size matters if you want the monster to use alternative # longer paths (for example through other rooms) if for example the # player is in a corridor # It makes sense to keep path size relatively low to keep the monsters # from running around the map if there's an alternative path really far # away if not tcod.path_is_empty(my_path) and tcod.path_size(my_path) < 25: # Find the next coordinates in the computed full path next_x, next_y = tcod.path_walk(my_path, True) if next_x is not None and next_y is not None: # Set self's coordinates to the next path tile self.x = next_x self.y = next_y else: # Keep the old move function as a backup so that if there are no # paths (for example another monster blocks a corridor) it will # still try to move towards the player (closer to the corridor # opening) self.move_towards(target.x, target.y, game_map, entities)
def move_astar(self, target, entities, game_map): """ Cette fonction permet aux monstres d'avoir des déplacements plus sophistiqués. Ils peuvent se déplacer en diagonal. """ # Créer une carte FOV qui a les dimensions de la carte fov = libtcod.map_new(game_map.width, game_map.height) # On scanne la carte actuelle à chaque tour et on définit tous les murs comme inviolables for y1 in range(game_map.height): for x1 in range(game_map.width): libtcod.map_set_properties( fov, x1, y1, not game_map.tiles[x1][y1].block_sight, not game_map.tiles[x1][y1].blocked) # On Scanne tous les objets pour voir s'il y a des objets qui doivent être parcourus # On Vérifie également que l'objet n'est pas lui-même ou la cible (pour que les points de départ et d'arrivée soient libres) # La classe AI gère la situation si le self est à côté de la cible, de sorte qu'elle n'utilisera pas cette fonction A * de toute façon for entity in entities: if entity.blocks and entity != self and entity != target: # Set the tile as a wall so it must be navigated around libtcod.map_set_properties(fov, entity.x, entity.y, True, False) # Allouer un chemin A * # Le 1,41 (racine carré de 2) est le coût diagonal normal du déplacement, il peut être réglé sur 0,0 si les déplacements diagonaux sont interdits my_path = libtcod.path_new_using_map(fov, 1.41) # Calculer le chemin entre les coordonnées de self et les coordonnées de la cible libtcod.path_compute(my_path, self.x, self.y, target.x, target.y) # Vérifiez si le chemin existe, et dans ce cas, si le chemin est également plus court que 25 tuiles # La taille du chemin est importante si vous voulez que le monstre utilise d'autres chemins plus longs (par exemple à travers d'autres pièces) si par exemple le joueur est dans un couloir # Il est logique de garder la taille du chemin relativement faible pour empêcher les monstres de courir sur la carte s'il y a un chemin alternatif très loin if not libtcod.path_is_empty( my_path) and libtcod.path_size(my_path) < 25: # Trouver les coordonnées suivantes dans le chemin complet calculé x, y = libtcod.path_walk(my_path, True) if x or y: # Set self's coordinates to the next path tile # On attribue ces coordonnées à self self.x = x self.y = y else: # Conservez l'ancienne fonction de déplacement comme sauvegarde afin que s'il n'y a pas de chemin (par exemple, un autre monstre bloque un couloir) # il essaiera toujours de se déplacer vers le joueur (plus près de l'ouverture du couloir) self.move_towards(target.x, target.y, game_map, entities) # Supprimer le chemin pour libérer de la mémoire libtcod.path_delete(my_path)
def defaultExploration(self, x, y, worldMap): longestPathLength = 0 if self.FOV < 3: return False for i in range(4): path = libtcod.path_new_using_map(self.FOV, 1) libtcod.path_compute(path, x, y, randint(0, worldMap.MAP_WIDTH), randint(0, worldMap.MAP_HEIGHT)) if not libtcod.path_is_empty(path): length = libtcod.path_size(path) if length > longestPathLength: longestPathLength = length self.path = path self.newPath = True return True
def move_astar(self, target: "Entity", entities: List["Entity"], game_map: GameMap): # Create a FOV map that has the dimensions of the map fov: tcod.map.Map = tcod.map.Map(width=game_map.width, height=game_map.height, order="F") # Scan the current map each turn and set all the walls as unwalkable for tile in game_map.tiles: fov.transparent[tile.x, tile.y] = not tile.blocks_sight fov.walkable[tile.x, tile.y] = not tile.blocked # Scan all the objects to see if there are objects that must be navigated around # Check also that the object isn't self or the target (so that the start and the end points are free) # The AI class handles the situation is self is next to the target so it will not use this A* function anyway for entity in entities: if entity.blocks and entity != self and entity != target: # Set the tile as a wall so it must be navigated around fov.transparent[entity.x, entity.y] = True fov.walkable[entity.x, entity.y] = False # Allocate an A* path # The 1.41 is the normal diagonal cost of moving, it can be set as 0.0 if diagonal moves are prohibited my_path = tcod.path_new_using_map(fov, 1.41) # Compute the path between self's coordinates and the target's coordinates tcod.path_compute(my_path, self.x, self.y, target.x, target.y) # Check if the path exists, and in this case, also the path is shorter than 25 tiles # The path size matters if you want the monster to use alternative longer paths (for # example through other rooms) if for example the player is in a corridor # It makes sense to keep the path size relatively low to keep monsters from running around # the map if there's an alternative path really far away if not tcod.path_is_empty(my_path) and tcod.path_size(my_path) < 25: # Find the next coordinates in the computed full path x, y = tcod.path_walk(my_path, True) if x or y: # Set self's coordinates to the next path tile self.x = x self.y = y else: # Keep the old move function as a backup so that is there are no paths (for example # another monster blocks a corridor it will still try to move towards the player # (closer to the corridor opening) self.move_towards(target_position=target.position, game_map=game_map, entities=entities) # Delete the path to free memory tcod.path_delete(my_path)
def _astar_step(self, src: Vector, dst: Vector) -> Vector: # Create a FOV map that has the dimensions of the map game_map = self.world.get_resource(Map) fov = tcod.map_new(game_map.width, game_map.height) # Scan the current map each turn and set all the walls as unwalkable for y1 in range(game_map.height): for x1 in range(game_map.width): tcod.map_set_properties( fov, x1, y1, not game_map.tiles[x1][y1].block_sight, not game_map.tiles[x1][y1].blocked, ) # Scan all the objects to see if there are objects that must be navigated around # Check also that the object isn't self or the target (so that the start and the end points are free) # The AI class handles the situation if self is next to the target so it will not use this A* function anyway for _, (position, _) in self.world.get_components(Position, Collider): if position.vector not in (src, dst): # Set the tile as a wall so it must be navigated around tcod.map_set_properties(fov, position.x, position.y, True, False) # Allocate a A* path # The 1.41 is the normal diagonal cost of moving, it can be set as 0.0 if diagonal moves are prohibited my_path = tcod.path_new_using_map(fov, 1.41) # Compute the path between self's coordinates and the target's coordinates tcod.path_compute(my_path, src.x, src.y, dst.x, dst.y) vector = None # Check if the path exists, and in this case, also the path is shorter than 25 tiles # The path size matters if you want the monster to use alternative longer paths (for example through other rooms) if for example the player is in a corridor # It makes sense to keep path size relatively low to keep the monsters from running around the map if there's an alternative path really far away if not tcod.path_is_empty(my_path) and tcod.path_size(my_path) < 25: # Find the next coordinates in the computed full path x, y = tcod.path_walk(my_path, True) if x is not None and y is not None: vector = (Vector(x, y) - src).norm() if vector is None: # Keep the old move function as a backup so that if there are no paths (for example another monster blocks a corridor) # it will still try to move towards the player (closer to the corridor opening) vector = (dst - src).norm() # Delete the path to free memory tcod.path_delete(my_path) return vector
def ai(self): if not self.dead: super().ai() fov = compute_fov(self.map.transparent, (self.x, self.y), self.fov, True, tcod.constants.FOV_BASIC) player = self.level.get_visible_player(fov) if player: path = tcod.path_new_using_map(self.map, 0) tcod.path_compute(path, self.x, self.y, player.x, player.y) if tcod.path_size(path) == 1: self.attack(player) elif not tcod.path_is_empty(path): px, py = tcod.path_walk(path, True) self.move(px - self.x, py - self.y)
def move_astar(self, target, entities, game_map): # A* search algorithm lets our monsters move diagonally, smartly # Create a FOV map that has the dimensions of the map fov = libtcod.map_new(game_map.width, game_map.height) # Scan the current map each turn and set all the walls as unwalkable for y1 in range(game_map.height): for x1 in range(game_map.width): libtcod.map_set_properties( fov, x1, y1, not game_map.tiles[x1][y1].block_sight, not game_map.tiles[x1][y1].blocked) # Scan all the objects to see if there are objects that must be # navigated around. Check also that the object isn't self or the # target (so that the start/end points are free) # The AI class handles the situation if the self is next to the # target so it won't use this A* function anyway. for entity in entities: if entity.blocks and entity != self and entity != target: # Set the tile as a 'wall' so it must be navigated around libtcod.map_set_properties(fov, entity.x, entity.y, True, False) # Allocate a A* path # The 1.41 is the normal diagonal cost of moving # it can be set as 0.0 to disable diagonal moving path = libtcod.path_new_using_map(fov, 1.41) # Check if the path exists, and in this case, also the path is # shorter than 25 tiles. THe path size matters if you want the # monster to use alt longer paths (eg through other rooms) if eg # the player is in a corridor. It makes sense to keep path size # relatively low to keep monsters from running around the map if # there's an alternative path really far away if not libtcod.path_is_empty(path) and libtcod.path_size(path) < 25: # Find the next coordinates in the computer full path x, y = libtcod.path_walk(path, True) if x or y: # Set self's coordinates to the next path tile self.x = x self.y = y else: # Keep the old move function as a backup so that if there are # no paths (eg another monster blocks a corridor) it will still # try to move towards the player (closer to the corridor) self.move_towards(target.x, target.y, game_map, entities) # Delete the path to free memory libtcod.path_delete(path)
def test_astar(map_): astar = libtcodpy.path_new_using_map(map_) assert not libtcodpy.path_compute(astar, *POINTS_AC) assert libtcodpy.path_size(astar) == 0 assert libtcodpy.path_compute(astar, *POINTS_AB) assert libtcodpy.path_get_origin(astar) == POINT_A assert libtcodpy.path_get_destination(astar) == POINT_B libtcodpy.path_reverse(astar) assert libtcodpy.path_get_origin(astar) == POINT_B assert libtcodpy.path_get_destination(astar) == POINT_A assert libtcodpy.path_size(astar) != 0 assert libtcodpy.path_size(astar) > 0 assert not libtcodpy.path_is_empty(astar) for i in range(libtcodpy.path_size(astar)): x, y = libtcodpy.path_get(astar, i) while (x, y) != (None, None): x, y = libtcodpy.path_walk(astar, False) libtcodpy.path_delete(astar)
def __init__(self, i): threading.Thread.__init__(self) self.daemon = True self.astar = tcod.path_new_using_map(maps[i])