Merge branch '35-better-pathfinding' into 'master'
Resolve "Better pathfinding" Closes #35 See merge request ynerant/squirrel-battle!43
This commit is contained in:
commit
53cb6a89ae
|
@ -23,6 +23,27 @@ class MapDisplay(Display):
|
||||||
self.addstr(self.pad, e.y, self.pack.tile_width * e.x,
|
self.addstr(self.pad, e.y, self.pack.tile_width * e.x,
|
||||||
self.pack[e.name.upper()], self.color_pair(2))
|
self.pack[e.name.upper()], self.color_pair(2))
|
||||||
|
|
||||||
|
# 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:
|
def display(self) -> None:
|
||||||
y, x = self.map.currenty, self.pack.tile_width * self.map.currentx
|
y, x = self.map.currenty, self.pack.tile_width * self.map.currentx
|
||||||
deltay, deltax = (self.height // 2) + 1, (self.width // 2) + 1
|
deltay, deltax = (self.height // 2) + 1, (self.width // 2) + 1
|
||||||
|
|
|
@ -43,11 +43,14 @@ class Monster(FightingEntity):
|
||||||
# If they can't move and they are already close to the player,
|
# If they can't move and they are already close to the player,
|
||||||
# They hit.
|
# They hit.
|
||||||
if target and (self.y, self.x) in target.paths:
|
if target and (self.y, self.x) in target.paths:
|
||||||
# Move to target player
|
# Move to target player by choosing the best avaliable path
|
||||||
next_y, next_x = target.paths[(self.y, self.x)]
|
for next_y, next_x in target.paths[(self.y, self.x)]:
|
||||||
moved = self.check_move(next_y, next_x, True)
|
moved = self.check_move(next_y, next_x, True)
|
||||||
if not moved and self.distance_squared(target) <= 1:
|
if moved:
|
||||||
self.map.logs.add_message(self.hit(target))
|
break
|
||||||
|
if self.distance_squared(target) <= 1:
|
||||||
|
self.map.logs.add_message(self.hit(target))
|
||||||
|
break
|
||||||
else:
|
else:
|
||||||
# Move in a random direction
|
# Move in a random direction
|
||||||
# If the direction is not available, try another one
|
# If the direction is not available, try another one
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse
|
# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from functools import reduce
|
||||||
|
from queue import PriorityQueue
|
||||||
from random import randint
|
from random import randint
|
||||||
from typing import Dict, Tuple
|
from typing import Dict, Tuple
|
||||||
|
|
||||||
|
@ -92,30 +94,53 @@ class Player(FightingEntity):
|
||||||
|
|
||||||
def recalculate_paths(self, max_distance: int = 8) -> None:
|
def recalculate_paths(self, max_distance: int = 8) -> None:
|
||||||
"""
|
"""
|
||||||
Use Dijkstra algorithm to calculate best paths
|
Use Dijkstra algorithm to calculate best paths for monsters to go to
|
||||||
for monsters to go to the player.
|
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 = [(self.y, self.x)]
|
distances = []
|
||||||
visited = []
|
predecessors = []
|
||||||
distances = {(self.y, self.x): 0}
|
# four Dijkstras, one for each adjacent tile
|
||||||
predecessors = {}
|
for dir_y, dir_x in [(1, 0), (-1, 0), (0, 1), (0, -1)]:
|
||||||
while queue:
|
queue = PriorityQueue()
|
||||||
y, x = queue.pop(0)
|
new_y, new_x = self.y + dir_y, self.x + dir_x
|
||||||
visited.append((y, x))
|
if not 0 <= new_y < self.map.height or \
|
||||||
if distances[(y, x)] >= max_distance:
|
not 0 <= new_x < self.map.width or \
|
||||||
|
not self.map.tiles[new_y][new_x].can_walk():
|
||||||
continue
|
continue
|
||||||
for diff_y, diff_x in [(1, 0), (-1, 0), (0, 1), (0, -1)]:
|
queue.put(((1, 0), (new_y, new_x)))
|
||||||
new_y, new_x = y + diff_y, x + diff_x
|
visited = [(self.y, self.x)]
|
||||||
if not 0 <= new_y < self.map.height or \
|
distances.append({(self.y, self.x): (0, 0), (new_y, new_x): (1, 0)})
|
||||||
not 0 <= new_x < self.map.width or \
|
predecessors.append({(new_y, new_x): (self.y, self.x)})
|
||||||
not self.map.tiles[y][x].can_walk() or \
|
while not queue.empty():
|
||||||
(new_y, new_x) in visited or \
|
dist, (y, x) = queue.get()
|
||||||
(new_y, new_x) in queue:
|
if dist[0] >= max_distance or (y, x) in visited:
|
||||||
continue
|
continue
|
||||||
predecessors[(new_y, new_x)] = (y, x)
|
visited.append((y, x))
|
||||||
distances[(new_y, new_x)] = distances[(y, x)] + 1
|
for diff_y, diff_x in [(1, 0), (-1, 0), (0, 1), (0, -1)]:
|
||||||
queue.append((new_y, new_x))
|
new_y, new_x = y + diff_y, x + diff_x
|
||||||
self.paths = predecessors
|
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], set()):
|
||||||
|
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:
|
def save_state(self) -> dict:
|
||||||
"""
|
"""
|
||||||
|
|
Loading…
Reference in New Issue