Being able to calculate paths is now a property of fighting entities.

This commit is contained in:
eichhornchen 2020-12-18 15:31:23 +01:00
parent a3e059a97b
commit 8ecbf13eae
2 changed files with 56 additions and 53 deletions

View File

@ -1,8 +1,6 @@
# 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
@ -15,7 +13,6 @@ class Player(InventoryHolder, FightingEntity):
""" """
current_xp: int = 0 current_xp: int = 0
max_xp: int = 10 max_xp: int = 10
paths: Dict[Tuple[int, int], Tuple[int, int]]
def __init__(self, name: str = "player", maxhealth: int = 20, def __init__(self, name: str = "player", maxhealth: int = 20,
strength: int = 5, intelligence: int = 1, charisma: int = 1, strength: int = 5, intelligence: int = 1, charisma: int = 1,
@ -87,55 +84,6 @@ class Player(InventoryHolder, FightingEntity):
entity.hold(self) entity.hold(self)
return super().check_move(y, x, move_if_possible) return super().check_move(y, x, move_if_possible)
def recalculate_paths(self, max_distance: int = 8) -> None:
"""
Uses Dijkstra algorithm to calculate best paths for monsters to go to
the player.
"""
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
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
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], 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:
""" """
Saves the state of the entity into a dictionary Saves the state of the entity into a dictionary

View File

@ -4,7 +4,9 @@
from enum import Enum, auto from enum import Enum, auto
from math import sqrt from math import sqrt
from random import choice, randint from random import choice, randint
from typing import List, Optional, Any from typing import List, Optional, Any, Dict, Tuple
from queue import PriorityQueue
from functools import reduce
from .display.texturepack import TexturePack from .display.texturepack import TexturePack
from .translations import gettext as _ from .translations import gettext as _
@ -237,6 +239,7 @@ class Entity:
x: int x: int
name: str name: str
map: Map map: Map
paths: Dict[Tuple[int, int], Tuple[int, int]]
# noinspection PyShadowingBuiltins # noinspection PyShadowingBuiltins
def __init__(self, y: int = 0, x: int = 0, name: Optional[str] = None, def __init__(self, y: int = 0, x: int = 0, name: Optional[str] = None,
@ -245,6 +248,7 @@ class Entity:
self.x = x self.x = x
self.name = name self.name = name
self.map = map self.map = map
self.paths = None
def check_move(self, y: int, x: int, move_if_possible: bool = False)\ def check_move(self, y: int, x: int, move_if_possible: bool = False)\
-> bool: -> bool:
@ -292,6 +296,57 @@ class Entity:
return self.move(self.y, self.x + 1) if force else \ return self.move(self.y, self.x + 1) if force else \
self.check_move(self.y, self.x + 1, True) self.check_move(self.y, self.x + 1, True)
def recalculate_paths(self, max_distance: int = 8) -> None:
"""
Uses Dijkstra algorithm to calculate best paths for other entities to
go to this entity. If self.paths is None, does nothing.
"""
if self.paths == None :
return
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
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
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], 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 act(self, m: Map) -> None: def act(self, m: Map) -> None:
""" """
Defines the action the entity will do at each tick. Defines the action the entity will do at each tick.