Merge branch '35-better-pathfinding' into 'master'
Resolve "Better pathfinding" Closes #35 See merge request ynerant/squirrel-battle!43
This commit was merged in pull request #124.
	This commit is contained in:
		@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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,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:
 | 
			
		||||
        """
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user