Merge branch 'master' into 'equipment'
# Conflicts: # squirrelbattle/display/statsdisplay.py # squirrelbattle/entities/items.py # squirrelbattle/entities/player.py # squirrelbattle/interfaces.py # squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po # squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po # squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po # squirrelbattle/tests/game_test.py
This commit is contained in:
		@@ -1,17 +1,18 @@
 | 
			
		||||
from ..interfaces import FriendlyEntity, InventoryHolder
 | 
			
		||||
from ..interfaces import FriendlyEntity, InventoryHolder, Map, FightingEntity
 | 
			
		||||
from ..translations import gettext as _
 | 
			
		||||
from .player import Player
 | 
			
		||||
from .monsters import Monster
 | 
			
		||||
from .items import Item
 | 
			
		||||
from random import choice
 | 
			
		||||
from random import choice, shuffle
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Merchant(InventoryHolder, FriendlyEntity):
 | 
			
		||||
    """
 | 
			
		||||
    The class for merchants in the dungeon
 | 
			
		||||
    The class of merchants in the dungeon.
 | 
			
		||||
    """
 | 
			
		||||
    def keys(self) -> list:
 | 
			
		||||
        """
 | 
			
		||||
        Returns a friendly entitie's specific attributes
 | 
			
		||||
        Returns a friendly entitie's specific attributes.
 | 
			
		||||
        """
 | 
			
		||||
        return super().keys() + ["inventory", "hazel"]
 | 
			
		||||
 | 
			
		||||
@@ -20,7 +21,6 @@ class Merchant(InventoryHolder, FriendlyEntity):
 | 
			
		||||
        super().__init__(name=name, *args, **kwargs)
 | 
			
		||||
        self.inventory = self.translate_inventory(inventory or [])
 | 
			
		||||
        self.hazel = hazel
 | 
			
		||||
 | 
			
		||||
        if not self.inventory:
 | 
			
		||||
            for i in range(5):
 | 
			
		||||
                self.inventory.append(choice(Item.get_all_items())())
 | 
			
		||||
@@ -28,20 +28,20 @@ class Merchant(InventoryHolder, FriendlyEntity):
 | 
			
		||||
    def talk_to(self, player: Player) -> str:
 | 
			
		||||
        """
 | 
			
		||||
        This function is used to open the merchant's inventory in a menu,
 | 
			
		||||
        and allow the player to buy/sell objects
 | 
			
		||||
        and allows the player to buy/sell objects.
 | 
			
		||||
        """
 | 
			
		||||
        return _("I don't sell any squirrel")
 | 
			
		||||
 | 
			
		||||
    def change_hazel_balance(self, hz: int) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        Change the number of hazel the merchant has by hz.
 | 
			
		||||
        Changes the number of hazel the merchant has by hz.
 | 
			
		||||
        """
 | 
			
		||||
        self.hazel += hz
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Sunflower(FriendlyEntity):
 | 
			
		||||
    """
 | 
			
		||||
    A friendly sunflower
 | 
			
		||||
    A friendly sunflower.
 | 
			
		||||
    """
 | 
			
		||||
    def __init__(self, maxhealth: int = 15,
 | 
			
		||||
                 *args, **kwargs) -> None:
 | 
			
		||||
@@ -49,4 +49,80 @@ class Sunflower(FriendlyEntity):
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def dialogue_option(self) -> list:
 | 
			
		||||
        """
 | 
			
		||||
        Lists all that a sunflower can say to the player.
 | 
			
		||||
        """
 | 
			
		||||
        return [_("Flower power!!"), _("The sun is warm today")]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Familiar(FightingEntity):
 | 
			
		||||
    """
 | 
			
		||||
    A friendly familiar that helps the player defeat monsters.
 | 
			
		||||
    """
 | 
			
		||||
    def __init__(self, maxhealth: int = 25,
 | 
			
		||||
                 *args, **kwargs) -> None:
 | 
			
		||||
        super().__init__(maxhealth=maxhealth, *args, **kwargs)
 | 
			
		||||
        self.target = None
 | 
			
		||||
 | 
			
		||||
#    @property
 | 
			
		||||
#    def dialogue_option(self) -> list:
 | 
			
		||||
#        """
 | 
			
		||||
#        Debug function (to see if used in the real game)
 | 
			
		||||
#        """
 | 
			
		||||
#        return [_("My target is"+str(self.target))]
 | 
			
		||||
 | 
			
		||||
    def act(self, p: Player, m: Map) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        By default, the familiar tries to stay at distance at most 2 of the
 | 
			
		||||
        player and if a monster comes in range 3, it focuses on the monster
 | 
			
		||||
        and attacks it.
 | 
			
		||||
        """
 | 
			
		||||
        if self.target is None:
 | 
			
		||||
            # If the previous target is dead(or if there was no previous target)
 | 
			
		||||
            # the familiar tries to get closer to the player.
 | 
			
		||||
            self.target = p
 | 
			
		||||
        elif self.target.dead:
 | 
			
		||||
            self.target = p
 | 
			
		||||
        if self.target == p:
 | 
			
		||||
            # Look for monsters around the player to kill TOFIX : if monster is
 | 
			
		||||
            # out of range, continue targetting player.
 | 
			
		||||
            for entity in m.entities:
 | 
			
		||||
                if (p.y - entity.y) ** 2 + (p.x - entity.x) ** 2 <= 9 and\
 | 
			
		||||
                        isinstance(entity, Monster):
 | 
			
		||||
                    self.target = entity
 | 
			
		||||
                    entity.paths = dict()  # Allows the paths to be calculated.
 | 
			
		||||
                    break
 | 
			
		||||
 | 
			
		||||
        # Familiars move according to a Dijkstra algorithm
 | 
			
		||||
        # that targets their target.
 | 
			
		||||
        # If they can not move and are already close to their target,
 | 
			
		||||
        # they hit, except if their target is the player.
 | 
			
		||||
        if self.target and (self.y, self.x) in self.target.paths:
 | 
			
		||||
            # Moves to target player by choosing the best available path
 | 
			
		||||
            for next_y, next_x in self.target.paths[(self.y, self.x)]:
 | 
			
		||||
                moved = self.check_move(next_y, next_x, True)
 | 
			
		||||
                if moved:
 | 
			
		||||
                    break
 | 
			
		||||
                if self.distance_squared(self.target) <= 1 and \
 | 
			
		||||
                        not isinstance(self.target, Player):
 | 
			
		||||
                    self.map.logs.add_message(self.hit(self.target))
 | 
			
		||||
                    break
 | 
			
		||||
        else:
 | 
			
		||||
            # Moves in a random direction
 | 
			
		||||
            # If the direction is not available, tries another one
 | 
			
		||||
            moves = [self.move_up, self.move_down,
 | 
			
		||||
                     self.move_left, self.move_right]
 | 
			
		||||
            shuffle(moves)
 | 
			
		||||
            for move in moves:
 | 
			
		||||
                if move():
 | 
			
		||||
                    break
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Trumpet(Familiar):
 | 
			
		||||
    """
 | 
			
		||||
    A class of familiars.
 | 
			
		||||
    """
 | 
			
		||||
    def __init__(self, name: str = "trumpet", strength: int = 3,
 | 
			
		||||
                 maxhealth: int = 20, *args, **kwargs) -> None:
 | 
			
		||||
        super().__init__(name=name, strength=strength,
 | 
			
		||||
                         maxhealth=maxhealth, *args, **kwargs)
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@ from ..translations import gettext as _
 | 
			
		||||
 | 
			
		||||
class Item(Entity):
 | 
			
		||||
    """
 | 
			
		||||
    A class for items
 | 
			
		||||
    A class for items.
 | 
			
		||||
    """
 | 
			
		||||
    held: bool
 | 
			
		||||
    held_by: Optional[InventoryHolder]
 | 
			
		||||
@@ -26,7 +26,7 @@ class Item(Entity):
 | 
			
		||||
 | 
			
		||||
    def drop(self) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        The item is dropped from the inventory onto the floor
 | 
			
		||||
        The item is dropped from the inventory onto the floor.
 | 
			
		||||
        """
 | 
			
		||||
        if self.held:
 | 
			
		||||
            self.held_by.remove_from_inventory(self)
 | 
			
		||||
@@ -59,7 +59,7 @@ class Item(Entity):
 | 
			
		||||
 | 
			
		||||
    def hold(self, holder: InventoryHolder) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        The item is taken from the floor and put into the inventory
 | 
			
		||||
        The item is taken from the floor and put into the inventory.
 | 
			
		||||
        """
 | 
			
		||||
        self.held = True
 | 
			
		||||
        self.held_by = holder
 | 
			
		||||
@@ -68,7 +68,7 @@ class Item(Entity):
 | 
			
		||||
 | 
			
		||||
    def save_state(self) -> dict:
 | 
			
		||||
        """
 | 
			
		||||
        Saves the state of the entity into a dictionary
 | 
			
		||||
        Saves the state of the item into a dictionary.
 | 
			
		||||
        """
 | 
			
		||||
        d = super().save_state()
 | 
			
		||||
        d["held"] = self.held
 | 
			
		||||
@@ -76,6 +76,9 @@ class Item(Entity):
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def get_all_items() -> list:
 | 
			
		||||
        """
 | 
			
		||||
        Returns the list of all item classes.
 | 
			
		||||
        """
 | 
			
		||||
        return [BodySnatchPotion, Bomb, Heart, Shield, Sword,
 | 
			
		||||
                Chestplate, Helmet, RingCritical, RingXP]
 | 
			
		||||
 | 
			
		||||
@@ -83,7 +86,7 @@ class Item(Entity):
 | 
			
		||||
        """
 | 
			
		||||
        Does all necessary actions when an object is to be sold.
 | 
			
		||||
        Is overwritten by some classes that cannot exist in the player's
 | 
			
		||||
        inventory
 | 
			
		||||
        inventory.
 | 
			
		||||
        """
 | 
			
		||||
        if buyer.hazel >= self.price:
 | 
			
		||||
            self.hold(buyer)
 | 
			
		||||
@@ -97,7 +100,7 @@ class Item(Entity):
 | 
			
		||||
 | 
			
		||||
class Heart(Item):
 | 
			
		||||
    """
 | 
			
		||||
    A heart item to return health to the player
 | 
			
		||||
    A heart item to return health to the player.
 | 
			
		||||
    """
 | 
			
		||||
    healing: int
 | 
			
		||||
 | 
			
		||||
@@ -108,14 +111,15 @@ class Heart(Item):
 | 
			
		||||
 | 
			
		||||
    def hold(self, entity: InventoryHolder) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        When holding a heart, heal the player and don't put item in inventory.
 | 
			
		||||
        When holding a heart, the player is healed and
 | 
			
		||||
        the item is not put in the inventory.
 | 
			
		||||
        """
 | 
			
		||||
        entity.health = min(entity.maxhealth, entity.health + self.healing)
 | 
			
		||||
        entity.map.remove_entity(self)
 | 
			
		||||
 | 
			
		||||
    def save_state(self) -> dict:
 | 
			
		||||
        """
 | 
			
		||||
        Saves the state of the header into a dictionary
 | 
			
		||||
        Saves the state of the heart into a dictionary.
 | 
			
		||||
        """
 | 
			
		||||
        d = super().save_state()
 | 
			
		||||
        d["healing"] = self.healing
 | 
			
		||||
@@ -141,7 +145,7 @@ class Bomb(Item):
 | 
			
		||||
 | 
			
		||||
    def use(self) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        When the bomb is used, throw it and explodes it.
 | 
			
		||||
        When the bomb is used, it is thrown and then it explodes.
 | 
			
		||||
        """
 | 
			
		||||
        if self.held:
 | 
			
		||||
            self.owner = self.held_by
 | 
			
		||||
@@ -150,7 +154,7 @@ class Bomb(Item):
 | 
			
		||||
 | 
			
		||||
    def act(self, m: Map) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        Special exploding action of the bomb
 | 
			
		||||
        Special exploding action of the bomb.
 | 
			
		||||
        """
 | 
			
		||||
        if self.exploding:
 | 
			
		||||
            if self.tick > 0:
 | 
			
		||||
@@ -176,7 +180,7 @@ class Bomb(Item):
 | 
			
		||||
 | 
			
		||||
    def save_state(self) -> dict:
 | 
			
		||||
        """
 | 
			
		||||
        Saves the state of the bomb into a dictionary
 | 
			
		||||
        Saves the state of the bomb into a dictionary.
 | 
			
		||||
        """
 | 
			
		||||
        d = super().save_state()
 | 
			
		||||
        d["exploding"] = self.exploding
 | 
			
		||||
@@ -193,13 +197,13 @@ class Explosion(Item):
 | 
			
		||||
 | 
			
		||||
    def act(self, m: Map) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        The explosion instant dies.
 | 
			
		||||
        The bomb disappears after exploding.
 | 
			
		||||
        """
 | 
			
		||||
        m.remove_entity(self)
 | 
			
		||||
 | 
			
		||||
    def hold(self, player: InventoryHolder) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        The player can't hold any explosion.
 | 
			
		||||
        The player can't hold an explosion.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -10,8 +10,8 @@ from ..interfaces import FightingEntity, Map
 | 
			
		||||
class Monster(FightingEntity):
 | 
			
		||||
    """
 | 
			
		||||
    The class for all monsters in the dungeon.
 | 
			
		||||
    A monster must override this class, and the parameters are given
 | 
			
		||||
    in the __init__ function.
 | 
			
		||||
    All specific monster classes overwrite this class,
 | 
			
		||||
    and the parameters are given in the __init__ function.
 | 
			
		||||
    An example of the specification of a monster that has a strength of 4
 | 
			
		||||
    and 20 max HP:
 | 
			
		||||
 | 
			
		||||
@@ -21,7 +21,7 @@ class Monster(FightingEntity):
 | 
			
		||||
            super().__init__(name="my_monster", strength=strength,
 | 
			
		||||
                             maxhealth=maxhealth, *args, **kwargs)
 | 
			
		||||
 | 
			
		||||
    With that way, attributes can be overwritten when the entity got created.
 | 
			
		||||
    With that way, attributes can be overwritten when the entity is created.
 | 
			
		||||
    """
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        super().__init__(*args, **kwargs)
 | 
			
		||||
@@ -29,7 +29,7 @@ class Monster(FightingEntity):
 | 
			
		||||
    def act(self, m: Map) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        By default, a monster will move randomly where it is possible
 | 
			
		||||
        And if a player is close to the monster, the monster run on the player.
 | 
			
		||||
        If the player is closeby, the monster runs to the player.
 | 
			
		||||
        """
 | 
			
		||||
        target = None
 | 
			
		||||
        for entity in m.entities:
 | 
			
		||||
@@ -38,12 +38,12 @@ class Monster(FightingEntity):
 | 
			
		||||
                target = entity
 | 
			
		||||
                break
 | 
			
		||||
 | 
			
		||||
        # A Dijkstra algorithm has ran that targets the player.
 | 
			
		||||
        # With that way, monsters can simply follow the path.
 | 
			
		||||
        # If they can't move and they are already close to the player,
 | 
			
		||||
        # They hit.
 | 
			
		||||
        # Monsters move according to a Dijkstra algorithm
 | 
			
		||||
        # that targets the player.
 | 
			
		||||
        # If they can not move and are already close to the player,
 | 
			
		||||
        # they hit.
 | 
			
		||||
        if target and (self.y, self.x) in target.paths:
 | 
			
		||||
            # Move to target player by choosing the best avaliable path
 | 
			
		||||
            # Moves to target player by choosing the best available path
 | 
			
		||||
            for next_y, next_x in target.paths[(self.y, self.x)]:
 | 
			
		||||
                moved = self.check_move(next_y, next_x, True)
 | 
			
		||||
                if moved:
 | 
			
		||||
@@ -52,8 +52,8 @@ class Monster(FightingEntity):
 | 
			
		||||
                    self.map.logs.add_message(self.hit(target))
 | 
			
		||||
                    break
 | 
			
		||||
        else:
 | 
			
		||||
            # Move in a random direction
 | 
			
		||||
            # If the direction is not available, try another one
 | 
			
		||||
            # Moves in a random direction
 | 
			
		||||
            # If the direction is not available, tries another one
 | 
			
		||||
            moves = [self.move_up, self.move_down,
 | 
			
		||||
                     self.move_left, self.move_right]
 | 
			
		||||
            shuffle(moves)
 | 
			
		||||
@@ -61,10 +61,17 @@ class Monster(FightingEntity):
 | 
			
		||||
                if move():
 | 
			
		||||
                    break
 | 
			
		||||
 | 
			
		||||
    def move(self, y: int, x: int) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        Overwrites the move function to recalculate paths.
 | 
			
		||||
        """
 | 
			
		||||
        super().move(y, x)
 | 
			
		||||
        self.recalculate_paths()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Tiger(Monster):
 | 
			
		||||
    """
 | 
			
		||||
    A tiger monster
 | 
			
		||||
    A tiger monster.
 | 
			
		||||
    """
 | 
			
		||||
    def __init__(self, name: str = "tiger", strength: int = 2,
 | 
			
		||||
                 maxhealth: int = 20, *args, **kwargs) -> None:
 | 
			
		||||
@@ -74,7 +81,7 @@ class Tiger(Monster):
 | 
			
		||||
 | 
			
		||||
class Hedgehog(Monster):
 | 
			
		||||
    """
 | 
			
		||||
    A really mean hedgehog monster
 | 
			
		||||
    A really mean hedgehog monster.
 | 
			
		||||
    """
 | 
			
		||||
    def __init__(self, name: str = "hedgehog", strength: int = 3,
 | 
			
		||||
                 maxhealth: int = 10, *args, **kwargs) -> None:
 | 
			
		||||
@@ -84,7 +91,7 @@ class Hedgehog(Monster):
 | 
			
		||||
 | 
			
		||||
class Rabbit(Monster):
 | 
			
		||||
    """
 | 
			
		||||
    A rabbit monster
 | 
			
		||||
    A rabbit monster.
 | 
			
		||||
    """
 | 
			
		||||
    def __init__(self, name: str = "rabbit", strength: int = 1,
 | 
			
		||||
                 maxhealth: int = 15, critical: int = 30,
 | 
			
		||||
@@ -96,7 +103,7 @@ class Rabbit(Monster):
 | 
			
		||||
 | 
			
		||||
class TeddyBear(Monster):
 | 
			
		||||
    """
 | 
			
		||||
    A cute teddybear monster
 | 
			
		||||
    A cute teddybear monster.
 | 
			
		||||
    """
 | 
			
		||||
    def __init__(self, name: str = "teddy_bear", strength: int = 0,
 | 
			
		||||
                 maxhealth: int = 50, *args, **kwargs) -> None:
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,11 @@
 | 
			
		||||
# 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
 | 
			
		||||
<<<<<<< squirrelbattle/entities/player.py
 | 
			
		||||
from typing import Dict, Optional, Tuple
 | 
			
		||||
=======
 | 
			
		||||
>>>>>>> squirrelbattle/entities/player.py
 | 
			
		||||
 | 
			
		||||
from .items import Item
 | 
			
		||||
from ..interfaces import FightingEntity, InventoryHolder
 | 
			
		||||
@@ -12,7 +13,7 @@ from ..interfaces import FightingEntity, InventoryHolder
 | 
			
		||||
 | 
			
		||||
class Player(InventoryHolder, FightingEntity):
 | 
			
		||||
    """
 | 
			
		||||
    The class of the player
 | 
			
		||||
    The class of the player.
 | 
			
		||||
    """
 | 
			
		||||
    current_xp: int = 0
 | 
			
		||||
    max_xp: int = 10
 | 
			
		||||
@@ -31,7 +32,7 @@ class Player(InventoryHolder, FightingEntity):
 | 
			
		||||
                 equipped_armor: Optional[Item] = None, critical: int = 5,
 | 
			
		||||
                 equipped_secondary: Optional[Item] = None,
 | 
			
		||||
                 equipped_helmet: Optional[Item] = None, xp_buff: float = 1,
 | 
			
		||||
                 *args, **kwargs) -> None:
 | 
			
		||||
                 vision: int = 5, *args, **kwargs) -> None:
 | 
			
		||||
        super().__init__(name=name, maxhealth=maxhealth, strength=strength,
 | 
			
		||||
                         intelligence=intelligence, charisma=charisma,
 | 
			
		||||
                         dexterity=dexterity, constitution=constitution,
 | 
			
		||||
@@ -50,6 +51,7 @@ class Player(InventoryHolder, FightingEntity):
 | 
			
		||||
            if isinstance(equipped_secondary, dict) else equipped_secondary
 | 
			
		||||
        self.equipped_helmet = self.dict_to_item(equipped_helmet) \
 | 
			
		||||
            if isinstance(equipped_helmet, dict) else equipped_helmet
 | 
			
		||||
        self.vision = vision
 | 
			
		||||
 | 
			
		||||
    def move(self, y: int, x: int) -> None:
 | 
			
		||||
        """
 | 
			
		||||
@@ -60,10 +62,11 @@ class Player(InventoryHolder, FightingEntity):
 | 
			
		||||
        self.map.currenty = y
 | 
			
		||||
        self.map.currentx = x
 | 
			
		||||
        self.recalculate_paths()
 | 
			
		||||
        self.map.compute_visibility(self.y, self.x, self.vision)
 | 
			
		||||
 | 
			
		||||
    def level_up(self) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        Add levels to the player as much as it is possible.
 | 
			
		||||
        Add as many levels as possible to the player.
 | 
			
		||||
        """
 | 
			
		||||
        while self.current_xp > self.max_xp:
 | 
			
		||||
            self.level += 1
 | 
			
		||||
@@ -77,8 +80,8 @@ class Player(InventoryHolder, FightingEntity):
 | 
			
		||||
 | 
			
		||||
    def add_xp(self, xp: int) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        Add some experience to the player.
 | 
			
		||||
        If the required amount is reached, level up.
 | 
			
		||||
        Adds some experience to the player.
 | 
			
		||||
        If the required amount is reached, the player levels up.
 | 
			
		||||
        """
 | 
			
		||||
        self.current_xp += int(xp * self.xp_buff)
 | 
			
		||||
        self.level_up()
 | 
			
		||||
@@ -120,56 +123,6 @@ class Player(InventoryHolder, FightingEntity):
 | 
			
		||||
                    entity.hold(self)
 | 
			
		||||
        return super().check_move(y, x, move_if_possible)
 | 
			
		||||
 | 
			
		||||
    def recalculate_paths(self, max_distance: int = 8) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        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.
 | 
			
		||||
        """
 | 
			
		||||
        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:
 | 
			
		||||
        """
 | 
			
		||||
        Saves the state of the entity into a dictionary
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user