diff --git a/squirrelbattle/display/mapdisplay.py b/squirrelbattle/display/mapdisplay.py index 445f736..d403f7f 100644 --- a/squirrelbattle/display/mapdisplay.py +++ b/squirrelbattle/display/mapdisplay.py @@ -23,6 +23,27 @@ class MapDisplay(Display): self.addstr(self.pad, e.y, self.pack.tile_width * e.x, 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: y, x = self.map.currenty, self.pack.tile_width * self.map.currentx deltay, deltax = (self.height // 2) + 1, (self.width // 2) + 1 diff --git a/squirrelbattle/entities/monsters.py b/squirrelbattle/entities/monsters.py index feff81a..34cd4bf 100644 --- a/squirrelbattle/entities/monsters.py +++ b/squirrelbattle/entities/monsters.py @@ -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 diff --git a/squirrelbattle/entities/player.py b/squirrelbattle/entities/player.py index 3f1d941..45e2bdf 100644 --- a/squirrelbattle/entities/player.py +++ b/squirrelbattle/entities/player.py @@ -1,6 +1,8 @@ # 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 @@ -92,30 +94,53 @@ 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 = [(self.y, self.x)] - visited = [] - distances = {(self.y, self.x): 0} - predecessors = {} - while queue: - y, x = queue.pop(0) - visited.append((y, x)) - if distances[(y, x)] >= max_distance: + 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 - 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[y][x].can_walk() or \ - (new_y, new_x) in visited or \ - (new_y, new_x) in queue: + 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 - predecessors[(new_y, new_x)] = (y, x) - distances[(new_y, new_x)] = distances[(y, x)] + 1 - queue.append((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], 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: """