New pathfinding that avoids most of the mobs getting stuck, closes #35
This commit is contained in:
parent
50d806cdcf
commit
cc6033e8e4
|
@ -3,7 +3,6 @@
|
|||
|
||||
from squirrelbattle.interfaces import Map
|
||||
from .display import Display
|
||||
from squirrelbattle.entities.player import Player
|
||||
|
||||
|
||||
class MapDisplay(Display):
|
||||
|
@ -23,24 +22,27 @@ class MapDisplay(Display):
|
|||
for e in self.map.entities:
|
||||
self.addstr(self.pad, e.y, self.pack.tile_width * e.x,
|
||||
self.pack[e.name.upper()], self.color_pair(2))
|
||||
players = [ p for p in self.map.entities if isinstance(p,Player) ]
|
||||
player = players[0] if len(players) > 0 else None
|
||||
if player:
|
||||
for x in range(self.map.width):
|
||||
for y in range(self.map.height):
|
||||
if (y,x) in player.paths:
|
||||
deltay, deltax = (y - player.paths[(y, x)][0],
|
||||
x - player.paths[(y, x)][1])
|
||||
if (deltay, deltax) == (-1, 0):
|
||||
character = '↓'
|
||||
elif (deltay, deltax) == (1, 0):
|
||||
character = '↑'
|
||||
elif (deltay, deltax) == (0, -1):
|
||||
character = '→'
|
||||
else:
|
||||
character = '←'
|
||||
self.addstr(self.pad, y, self.pack.tile_width * x,
|
||||
character, self.color_pair(1))
|
||||
|
||||
# Display Path map for deubg purposes
|
||||
# from squirrelbattle.entities.player import Player
|
||||
# players = [ p for p in self.map.entities if isinstance(p,Player) ]
|
||||
# player = players[0] if len(players) > 0 else None
|
||||
# if player:
|
||||
# for x in range(self.map.width):
|
||||
# for y in range(self.map.height):
|
||||
# if (y,x) in player.paths:
|
||||
# deltay, deltax = (y - player.paths[(y, x)][0],
|
||||
# x - player.paths[(y, x)][1])
|
||||
# if (deltay, deltax) == (-1, 0):
|
||||
# character = '↓'
|
||||
# elif (deltay, deltax) == (1, 0):
|
||||
# character = '↑'
|
||||
# elif (deltay, deltax) == (0, -1):
|
||||
# character = '→'
|
||||
# else:
|
||||
# character = '←'
|
||||
# self.addstr(self.pad, y, self.pack.tile_width * x,
|
||||
# character, self.color_pair(1))
|
||||
|
||||
def display(self) -> None:
|
||||
y, x = self.map.currenty, self.pack.tile_width * self.map.currentx
|
||||
|
|
|
@ -43,11 +43,14 @@ class Monster(FightingEntity):
|
|||
# If they can't move and they are already close to the player,
|
||||
# They hit.
|
||||
if target and (self.y, self.x) in target.paths:
|
||||
# Move to target player
|
||||
next_y, next_x = target.paths[(self.y, self.x)]
|
||||
moved = self.check_move(next_y, next_x, True)
|
||||
if not moved and self.distance_squared(target) <= 1:
|
||||
self.map.logs.add_message(self.hit(target))
|
||||
# Move to target player by choosing the best avaliable path
|
||||
for next_y, next_x in target.paths[(self.y, self.x)]:
|
||||
moved = self.check_move(next_y, next_x, True)
|
||||
if moved:
|
||||
break
|
||||
if self.distance_squared(target) <= 1:
|
||||
self.map.logs.add_message(self.hit(target))
|
||||
break
|
||||
else:
|
||||
# Move in a random direction
|
||||
# If the direction is not available, try another one
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from functools import reduce
|
||||
from queue import PriorityQueue
|
||||
from random import randint
|
||||
from typing import Dict, Tuple
|
||||
from queue import PriorityQueue
|
||||
|
||||
from ..interfaces import FightingEntity
|
||||
|
||||
|
@ -93,32 +94,52 @@ class Player(FightingEntity):
|
|||
|
||||
def recalculate_paths(self, max_distance: int = 8) -> None:
|
||||
"""
|
||||
Use Dijkstra algorithm to calculate best paths
|
||||
for monsters to go to the player.
|
||||
Use Dijkstra algorithm to calculate best paths for monsters to go to
|
||||
the player. Actually, the paths are computed for each tile adjacent to
|
||||
the player then for each step the monsters use the best path avaliable.
|
||||
"""
|
||||
queue = PriorityQueue()
|
||||
queue.put((0, (self.y, self.x)))
|
||||
visited = []
|
||||
distances = {(self.y, self.x): 0}
|
||||
predecessors = {}
|
||||
while not queue.empty():
|
||||
dist, (y, x) = queue.get()
|
||||
if dist >= max_distance or (y,x) in visited:
|
||||
distances = []
|
||||
predecessors = []
|
||||
# four Dijkstras, one for each adjacent tile
|
||||
for dir_y, dir_x in [(1, 0), (-1, 0), (0, 1), (0, -1)]:
|
||||
queue = PriorityQueue()
|
||||
new_y, new_x = self.y + dir_y, self.x + dir_x
|
||||
if not 0 <= new_y < self.map.height or \
|
||||
not 0 <= new_x < self.map.width or \
|
||||
not self.map.tiles[new_y][new_x].can_walk():
|
||||
continue
|
||||
visited.append((y, x))
|
||||
for diff_y, diff_x in [(1, 0), (-1, 0), (0, 1), (0, -1)]:
|
||||
new_y, new_x = y + diff_y, x + diff_x
|
||||
if not 0 <= new_y < self.map.height or \
|
||||
not 0 <= new_x < self.map.width or \
|
||||
not self.map.tiles[new_y][new_x].can_walk():
|
||||
queue.put(((1, 0), (new_y, new_x)))
|
||||
visited = [(self.y, self.x)]
|
||||
distances.append({(self.y, self.x): (0, 0), (new_y, new_x): (1, 0)})
|
||||
predecessors.append({(new_y, new_x): (self.y, self.x)})
|
||||
while not queue.empty():
|
||||
dist, (y, x) = queue.get()
|
||||
if dist[0] >= max_distance or (y, x) in visited:
|
||||
continue
|
||||
new_distance = dist + 1
|
||||
if not (new_y, new_x) in distances or \
|
||||
distances[(new_y, new_x)] > new_distance:
|
||||
predecessors[(new_y, new_x)] = (y, x)
|
||||
distances[(new_y, new_x)] = new_distance
|
||||
queue.put((new_distance, (new_y, new_x)))
|
||||
self.paths = predecessors
|
||||
visited.append((y, x))
|
||||
for diff_y, diff_x in [(1, 0), (-1, 0), (0, 1), (0, -1)]:
|
||||
new_y, new_x = y + diff_y, x + diff_x
|
||||
if not 0 <= new_y < self.map.height or \
|
||||
not 0 <= new_x < self.map.width or \
|
||||
not self.map.tiles[new_y][new_x].can_walk():
|
||||
continue
|
||||
new_distance = (dist[0] + 1,
|
||||
dist[1] + (not self.map.is_free(y, x)))
|
||||
if not (new_y, new_x) in distances[-1] or \
|
||||
distances[-1][(new_y, new_x)] > new_distance:
|
||||
predecessors[-1][(new_y, new_x)] = (y, x)
|
||||
distances[-1][(new_y, new_x)] = new_distance
|
||||
queue.put((new_distance, (new_y, new_x)))
|
||||
# For each tile that is reached by at least one Dijkstra, sort the
|
||||
# different paths by distance to the player. For the technical bits :
|
||||
# The reduce function is a fold starting on the first element of the
|
||||
# iterable, and we associate the points to their distance, sort
|
||||
# along the distance, then only keep the points.
|
||||
self.paths = {}
|
||||
for y, x in reduce(set.union, [set(p.keys()) for p in predecessors]):
|
||||
self.paths[(y, x)] = [p for d, p in sorted(
|
||||
[(distances[i][(y, x)], predecessors[i][(y, x)])
|
||||
for i in range(len(distances)) if (y, x) in predecessors[i]])]
|
||||
|
||||
def save_state(self) -> dict:
|
||||
"""
|
||||
|
|
Loading…
Reference in New Issue