Merge branch 'lighting' into 'master'
Lighting Closes #27 See merge request ynerant/squirrel-battle!34
This commit was merged in pull request #115.
	This commit is contained in:
		
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -3,6 +3,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
env/
 | 
					env/
 | 
				
			||||||
venv/
 | 
					venv/
 | 
				
			||||||
 | 
					local/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.coverage
 | 
					.coverage
 | 
				
			||||||
.pytest_cache/
 | 
					.pytest_cache/
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										41
									
								
								squirrelbattle/assets/example_map_3.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								squirrelbattle/assets/example_map_3.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
				
			|||||||
 | 
					1 6
 | 
				
			||||||
 | 
					################################################################################
 | 
				
			||||||
 | 
					#..............................................................................#
 | 
				
			||||||
 | 
					#..#...........................................................................#
 | 
				
			||||||
 | 
					#...........#..................................................................#
 | 
				
			||||||
 | 
					#..............................................................................#
 | 
				
			||||||
 | 
					#..............................................................................#
 | 
				
			||||||
 | 
					#..............................................................................#
 | 
				
			||||||
 | 
					#..............................................................................#
 | 
				
			||||||
 | 
					#..............................................................................#
 | 
				
			||||||
 | 
					#..............................................................................#
 | 
				
			||||||
 | 
					#..............................................................................#
 | 
				
			||||||
 | 
					#..............................................................................#
 | 
				
			||||||
 | 
					#..............................................................................#
 | 
				
			||||||
 | 
					#..............................................................................#
 | 
				
			||||||
 | 
					#..............................................................................#
 | 
				
			||||||
 | 
					#..............................................................................#
 | 
				
			||||||
 | 
					#..............................................................................#
 | 
				
			||||||
 | 
					#..............................................................................#
 | 
				
			||||||
 | 
					#..............................................................................#
 | 
				
			||||||
 | 
					#..............................................................................#
 | 
				
			||||||
 | 
					#..............................................................................#
 | 
				
			||||||
 | 
					#..............................................................................#
 | 
				
			||||||
 | 
					#..............................................................................#
 | 
				
			||||||
 | 
					#..............................................................................#
 | 
				
			||||||
 | 
					#..............................................................................#
 | 
				
			||||||
 | 
					#..............................................................................#
 | 
				
			||||||
 | 
					#..............................................................................#
 | 
				
			||||||
 | 
					#..............................................................................#
 | 
				
			||||||
 | 
					#..............................................................................#
 | 
				
			||||||
 | 
					#..............................................................................#
 | 
				
			||||||
 | 
					#..............................................................................#
 | 
				
			||||||
 | 
					#..............................................................................#
 | 
				
			||||||
 | 
					#..............................................................................#
 | 
				
			||||||
 | 
					#..............................................................................#
 | 
				
			||||||
 | 
					#..............................................................................#
 | 
				
			||||||
 | 
					#..............................................................................#
 | 
				
			||||||
 | 
					#..............................................................................#
 | 
				
			||||||
 | 
					#..............................................................................#
 | 
				
			||||||
 | 
					#..............................................................................#
 | 
				
			||||||
 | 
					################################################################################
 | 
				
			||||||
@@ -22,16 +22,21 @@ class MapDisplay(Display):
 | 
				
			|||||||
                               self.pack.tile_width * self.map.width + 1)
 | 
					                               self.pack.tile_width * self.map.width + 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def update_pad(self) -> None:
 | 
					    def update_pad(self) -> None:
 | 
				
			||||||
        self.pad.resize(500, 500)
 | 
					        for j in range(len(self.map.tiles)):
 | 
				
			||||||
        for i in range(self.map.height):
 | 
					            for i in range(len(self.map.tiles[j])):
 | 
				
			||||||
            for j in range(self.map.width):
 | 
					                if not self.map.seen_tiles[j][i]:
 | 
				
			||||||
                self.addstr(self.pad, i, j * self.pack.tile_width,
 | 
					                    continue
 | 
				
			||||||
                            self.map.tiles[i][j].char(self.pack),
 | 
					                fg, bg = self.map.tiles[j][i].visible_color(self.pack) if \
 | 
				
			||||||
                            *self.map.tiles[i][j].color(self.pack))
 | 
					                    self.map.visibility[j][i] else \
 | 
				
			||||||
 | 
					                    self.map.tiles[j][i].hidden_color(self.pack)
 | 
				
			||||||
 | 
					                self.addstr(self.pad, j, self.pack.tile_width * i,
 | 
				
			||||||
 | 
					                            self.map.tiles[j][i].char(self.pack), fg, bg)
 | 
				
			||||||
        for e in self.map.entities:
 | 
					        for e in self.map.entities:
 | 
				
			||||||
            self.addstr(self.pad, e.y, self.pack.tile_width * e.x,
 | 
					            if self.map.visibility[e.y][e.x]:
 | 
				
			||||||
                        self.pack[e.name.upper()],
 | 
					                self.addstr(self.pad, e.y, self.pack.tile_width * e.x,
 | 
				
			||||||
                        self.pack.entity_fg_color, self.pack.entity_bg_color)
 | 
					                            self.pack[e.name.upper()],
 | 
				
			||||||
 | 
					                            self.pack.entity_fg_color,
 | 
				
			||||||
 | 
					                            self.pack.entity_bg_color)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Display Path map for debug purposes
 | 
					        # Display Path map for debug purposes
 | 
				
			||||||
        # from squirrelbattle.entities.player import Player
 | 
					        # from squirrelbattle.entities.player import Player
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,7 @@
 | 
				
			|||||||
# SPDX-License-Identifier: GPL-3.0-or-later
 | 
					# SPDX-License-Identifier: GPL-3.0-or-later
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import curses
 | 
					import curses
 | 
				
			||||||
from typing import Any
 | 
					from typing import Any, Union, Tuple
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TexturePack:
 | 
					class TexturePack:
 | 
				
			||||||
@@ -13,10 +13,11 @@ class TexturePack:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    name: str
 | 
					    name: str
 | 
				
			||||||
    tile_width: int
 | 
					    tile_width: int
 | 
				
			||||||
    tile_fg_color: int
 | 
					    tile_fg_color: Union[int, Tuple[int, int, int]]
 | 
				
			||||||
    tile_bg_color: int
 | 
					    tile_fg_visible_color: Union[int, Tuple[int, int, int]]
 | 
				
			||||||
    entity_fg_color: int
 | 
					    tile_bg_color: Union[int, Tuple[int, int, int]]
 | 
				
			||||||
    entity_bg_color: int
 | 
					    entity_fg_color: Union[int, Tuple[int, int, int]]
 | 
				
			||||||
 | 
					    entity_bg_color: Union[int, Tuple[int, int, int]]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    BODY_SNATCH_POTION: str
 | 
					    BODY_SNATCH_POTION: str
 | 
				
			||||||
    BOMB: str
 | 
					    BOMB: str
 | 
				
			||||||
@@ -58,9 +59,10 @@ class TexturePack:
 | 
				
			|||||||
TexturePack.ASCII_PACK = TexturePack(
 | 
					TexturePack.ASCII_PACK = TexturePack(
 | 
				
			||||||
    name="ascii",
 | 
					    name="ascii",
 | 
				
			||||||
    tile_width=1,
 | 
					    tile_width=1,
 | 
				
			||||||
 | 
					    tile_fg_visible_color=(1000, 1000, 1000),
 | 
				
			||||||
    tile_fg_color=curses.COLOR_WHITE,
 | 
					    tile_fg_color=curses.COLOR_WHITE,
 | 
				
			||||||
    tile_bg_color=curses.COLOR_BLACK,
 | 
					    tile_bg_color=curses.COLOR_BLACK,
 | 
				
			||||||
    entity_fg_color=curses.COLOR_WHITE,
 | 
					    entity_fg_color=(1000, 1000, 1000),
 | 
				
			||||||
    entity_bg_color=curses.COLOR_BLACK,
 | 
					    entity_bg_color=curses.COLOR_BLACK,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    BODY_SNATCH_POTION='S',
 | 
					    BODY_SNATCH_POTION='S',
 | 
				
			||||||
@@ -86,17 +88,19 @@ TexturePack.ASCII_PACK = TexturePack(
 | 
				
			|||||||
TexturePack.SQUIRREL_PACK = TexturePack(
 | 
					TexturePack.SQUIRREL_PACK = TexturePack(
 | 
				
			||||||
    name="squirrel",
 | 
					    name="squirrel",
 | 
				
			||||||
    tile_width=2,
 | 
					    tile_width=2,
 | 
				
			||||||
 | 
					    tile_fg_visible_color=(1000, 1000, 1000),
 | 
				
			||||||
    tile_fg_color=curses.COLOR_WHITE,
 | 
					    tile_fg_color=curses.COLOR_WHITE,
 | 
				
			||||||
    tile_bg_color=curses.COLOR_BLACK,
 | 
					    tile_bg_color=curses.COLOR_BLACK,
 | 
				
			||||||
    entity_fg_color=curses.COLOR_WHITE,
 | 
					    entity_fg_color=(1000, 1000, 1000),
 | 
				
			||||||
    entity_bg_color=curses.COLOR_WHITE,
 | 
					    entity_bg_color=(1000, 1000, 1000),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    BODY_SNATCH_POTION='🔀',
 | 
					    BODY_SNATCH_POTION='🔀',
 | 
				
			||||||
    BOMB='💣',
 | 
					    BOMB='💣',
 | 
				
			||||||
    EMPTY='  ',
 | 
					    EMPTY='  ',
 | 
				
			||||||
    EXPLOSION='💥',
 | 
					    EXPLOSION='💥',
 | 
				
			||||||
    FLOOR='██',
 | 
					    FLOOR='██',
 | 
				
			||||||
    LADDER=('🪜', curses.COLOR_WHITE, curses.COLOR_WHITE),
 | 
					    LADDER=('🪜', curses.COLOR_WHITE, (1000, 1000, 1000),
 | 
				
			||||||
 | 
					            curses.COLOR_WHITE, (1000, 1000, 1000)),
 | 
				
			||||||
    HAZELNUT='🌰',
 | 
					    HAZELNUT='🌰',
 | 
				
			||||||
    HEART='💜',
 | 
					    HEART='💜',
 | 
				
			||||||
    HEDGEHOG='🦔',
 | 
					    HEDGEHOG='🦔',
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,7 +17,7 @@ class Player(InventoryHolder, FightingEntity):
 | 
				
			|||||||
                 strength: int = 5, intelligence: int = 1, charisma: int = 1,
 | 
					                 strength: int = 5, intelligence: int = 1, charisma: int = 1,
 | 
				
			||||||
                 dexterity: int = 1, constitution: int = 1, level: int = 1,
 | 
					                 dexterity: int = 1, constitution: int = 1, level: int = 1,
 | 
				
			||||||
                 current_xp: int = 0, max_xp: int = 10, inventory: list = None,
 | 
					                 current_xp: int = 0, max_xp: int = 10, inventory: list = None,
 | 
				
			||||||
                 hazel: int = 42, *args, **kwargs) \
 | 
					                 hazel: int = 42, vision: int = 5, *args, **kwargs) \
 | 
				
			||||||
            -> None:
 | 
					            -> None:
 | 
				
			||||||
        super().__init__(name=name, maxhealth=maxhealth, strength=strength,
 | 
					        super().__init__(name=name, maxhealth=maxhealth, strength=strength,
 | 
				
			||||||
                         intelligence=intelligence, charisma=charisma,
 | 
					                         intelligence=intelligence, charisma=charisma,
 | 
				
			||||||
@@ -28,6 +28,7 @@ class Player(InventoryHolder, FightingEntity):
 | 
				
			|||||||
        self.inventory = self.translate_inventory(inventory or [])
 | 
					        self.inventory = self.translate_inventory(inventory or [])
 | 
				
			||||||
        self.paths = dict()
 | 
					        self.paths = dict()
 | 
				
			||||||
        self.hazel = hazel
 | 
					        self.hazel = hazel
 | 
				
			||||||
 | 
					        self.vision = vision
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def move(self, y: int, x: int) -> None:
 | 
					    def move(self, y: int, x: int) -> None:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
@@ -38,6 +39,7 @@ class Player(InventoryHolder, FightingEntity):
 | 
				
			|||||||
        self.map.currenty = y
 | 
					        self.map.currenty = y
 | 
				
			||||||
        self.map.currentx = x
 | 
					        self.map.currentx = x
 | 
				
			||||||
        self.recalculate_paths()
 | 
					        self.recalculate_paths()
 | 
				
			||||||
 | 
					        self.map.compute_visibility(self.y, self.x, self.vision)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def level_up(self) -> None:
 | 
					    def level_up(self) -> None:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,7 @@
 | 
				
			|||||||
# SPDX-License-Identifier: GPL-3.0-or-later
 | 
					# SPDX-License-Identifier: GPL-3.0-or-later
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from enum import Enum, auto
 | 
					from enum import Enum, auto
 | 
				
			||||||
from math import sqrt
 | 
					from math import ceil, sqrt
 | 
				
			||||||
from random import choice, randint
 | 
					from random import choice, randint
 | 
				
			||||||
from typing import List, Optional, Any, Dict, Tuple
 | 
					from typing import List, Optional, Any, Dict, Tuple
 | 
				
			||||||
from queue import PriorityQueue
 | 
					from queue import PriorityQueue
 | 
				
			||||||
@@ -32,6 +32,34 @@ class Logs:
 | 
				
			|||||||
        self.messages = []
 | 
					        self.messages = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Slope():
 | 
				
			||||||
 | 
					    X: int
 | 
				
			||||||
 | 
					    Y: int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, y: int, x: int) -> None:
 | 
				
			||||||
 | 
					        self.Y = y
 | 
				
			||||||
 | 
					        self.X = x
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def compare(self, other: "Slope") -> int:
 | 
				
			||||||
 | 
					        y, x = other.Y, other.X
 | 
				
			||||||
 | 
					        return self.Y * x - self.X * y
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __lt__(self, other: "Slope") -> bool:
 | 
				
			||||||
 | 
					        return self.compare(other) < 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __eq__(self, other: "Slope") -> bool:
 | 
				
			||||||
 | 
					        return self.compare(other) == 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __gt__(self, other: "Slope") -> bool:
 | 
				
			||||||
 | 
					        return self.compare(other) > 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __le__(self, other: "Slope") -> bool:
 | 
				
			||||||
 | 
					        return self.compare(other) <= 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __ge__(self, other: "Slope") -> bool:
 | 
				
			||||||
 | 
					        return self.compare(other) >= 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Map:
 | 
					class Map:
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    The Map object represents a with its width, height
 | 
					    The Map object represents a with its width, height
 | 
				
			||||||
@@ -43,6 +71,8 @@ class Map:
 | 
				
			|||||||
    start_y: int
 | 
					    start_y: int
 | 
				
			||||||
    start_x: int
 | 
					    start_x: int
 | 
				
			||||||
    tiles: List[List["Tile"]]
 | 
					    tiles: List[List["Tile"]]
 | 
				
			||||||
 | 
					    visibility: List[List[bool]]
 | 
				
			||||||
 | 
					    seen_tiles: List[List[bool]]
 | 
				
			||||||
    entities: List["Entity"]
 | 
					    entities: List["Entity"]
 | 
				
			||||||
    logs: Logs
 | 
					    logs: Logs
 | 
				
			||||||
    # coordinates of the point that should be
 | 
					    # coordinates of the point that should be
 | 
				
			||||||
@@ -58,6 +88,10 @@ class Map:
 | 
				
			|||||||
        self.start_y = start_y
 | 
					        self.start_y = start_y
 | 
				
			||||||
        self.start_x = start_x
 | 
					        self.start_x = start_x
 | 
				
			||||||
        self.tiles = tiles
 | 
					        self.tiles = tiles
 | 
				
			||||||
 | 
					        self.visibility = [[False for _ in range(len(tiles[0]))]
 | 
				
			||||||
 | 
					                           for _ in range(len(tiles))]
 | 
				
			||||||
 | 
					        self.seen_tiles = [[False for _ in range(len(tiles[0]))]
 | 
				
			||||||
 | 
					                           for _ in range(len(tiles))]
 | 
				
			||||||
        self.entities = []
 | 
					        self.entities = []
 | 
				
			||||||
        self.logs = Logs()
 | 
					        self.logs = Logs()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -147,7 +181,8 @@ class Map:
 | 
				
			|||||||
        """
 | 
					        """
 | 
				
			||||||
        Puts randomly {count} entities on the map, only on empty ground tiles.
 | 
					        Puts randomly {count} entities on the map, only on empty ground tiles.
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        for ignored in range(count):
 | 
					        for _ignored in range(count):
 | 
				
			||||||
 | 
					            y, x = 0, 0
 | 
				
			||||||
            while True:
 | 
					            while True:
 | 
				
			||||||
                y, x = randint(0, self.height - 1), randint(0, self.width - 1)
 | 
					                y, x = randint(0, self.height - 1), randint(0, self.width - 1)
 | 
				
			||||||
                tile = self.tiles[y][x]
 | 
					                tile = self.tiles[y][x]
 | 
				
			||||||
@@ -157,6 +192,126 @@ class Map:
 | 
				
			|||||||
            entity.move(y, x)
 | 
					            entity.move(y, x)
 | 
				
			||||||
            self.add_entity(entity)
 | 
					            self.add_entity(entity)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def compute_visibility(self, y: int, x: int, max_range: int) -> None:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Sets the visible tiles to be the ones visible by an entity at point
 | 
				
			||||||
 | 
					        (y, x), using a twaked shadow casting algorithm
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for line in self.visibility:
 | 
				
			||||||
 | 
					            for i in range(len(line)):
 | 
				
			||||||
 | 
					                line[i] = False
 | 
				
			||||||
 | 
					        self.set_visible(0, 0, 0, (y, x))
 | 
				
			||||||
 | 
					        for octant in range(8):
 | 
				
			||||||
 | 
					            self.compute_visibility_octant(octant, (y, x), max_range, 1,
 | 
				
			||||||
 | 
					                                           Slope(1, 1), Slope(0, 1))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def crop_top_visibility(self, octant: int, origin: Tuple[int, int],
 | 
				
			||||||
 | 
					                            x: int, top: Slope) -> int:
 | 
				
			||||||
 | 
					        if top.X == 1:
 | 
				
			||||||
 | 
					            top_y = x
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            top_y = ceil(((x * 2 - 1) * top.Y + top.X) / (top.X * 2))
 | 
				
			||||||
 | 
					            if self.is_wall(top_y, x, octant, origin):
 | 
				
			||||||
 | 
					                top_y += top >= Slope(top_y * 2 + 1, x * 2) and not \
 | 
				
			||||||
 | 
					                    self.is_wall(top_y + 1, x, octant, origin)
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                ax = x * 2
 | 
				
			||||||
 | 
					                ax += self.is_wall(top_y + 1, x + 1, octant, origin)
 | 
				
			||||||
 | 
					                top_y += top > Slope(top_y * 2 + 1, ax)
 | 
				
			||||||
 | 
					        return top_y
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def crop_bottom_visibility(self, octant: int, origin: Tuple[int, int],
 | 
				
			||||||
 | 
					                               x: int, bottom: Slope) -> int:
 | 
				
			||||||
 | 
					        if bottom.Y == 0:
 | 
				
			||||||
 | 
					            bottom_y = 0
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            bottom_y = ceil(((x * 2 - 1) * bottom.Y + bottom.X)
 | 
				
			||||||
 | 
					                            / (bottom.X * 2))
 | 
				
			||||||
 | 
					            bottom_y += bottom >= Slope(bottom_y * 2 + 1, x * 2) and \
 | 
				
			||||||
 | 
					                self.is_wall(bottom_y, x, octant, origin) and \
 | 
				
			||||||
 | 
					                not self.is_wall(bottom_y + 1, x, octant, origin)
 | 
				
			||||||
 | 
					        return bottom_y
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def compute_visibility_octant(self, octant: int, origin: Tuple[int, int],
 | 
				
			||||||
 | 
					                                  max_range: int, distance: int, top: Slope,
 | 
				
			||||||
 | 
					                                  bottom: Slope) -> None:
 | 
				
			||||||
 | 
					        for x in range(distance, max_range + 1):
 | 
				
			||||||
 | 
					            top_y = self.crop_top_visibility(octant, origin, x, top)
 | 
				
			||||||
 | 
					            bottom_y = self.crop_bottom_visibility(octant, origin, x, bottom)
 | 
				
			||||||
 | 
					            was_opaque = -1
 | 
				
			||||||
 | 
					            for y in range(top_y, bottom_y - 1, -1):
 | 
				
			||||||
 | 
					                if x + y > max_range:
 | 
				
			||||||
 | 
					                    continue
 | 
				
			||||||
 | 
					                is_opaque = self.is_wall(y, x, octant, origin)
 | 
				
			||||||
 | 
					                is_visible = is_opaque\
 | 
				
			||||||
 | 
					                    or ((y != top_y or top > Slope(y * 4 - 1, x * 4 + 1))
 | 
				
			||||||
 | 
					                        and (y != bottom_y
 | 
				
			||||||
 | 
					                             or bottom < Slope(y * 4 + 1, x * 4 - 1)))
 | 
				
			||||||
 | 
					                # is_visible = is_opaque\
 | 
				
			||||||
 | 
					                #     or ((y != top_y or top >= Slope(y, x))
 | 
				
			||||||
 | 
					                #         and (y != bottom_y or bottom <= Slope(y, x)))
 | 
				
			||||||
 | 
					                if is_visible:
 | 
				
			||||||
 | 
					                    self.set_visible(y, x, octant, origin)
 | 
				
			||||||
 | 
					                if x == max_range:
 | 
				
			||||||
 | 
					                    continue
 | 
				
			||||||
 | 
					                if is_opaque and was_opaque == 0:
 | 
				
			||||||
 | 
					                    nx, ny = x * 2, y * 2 + 1
 | 
				
			||||||
 | 
					                    nx -= self.is_wall(y + 1, x, octant, origin)
 | 
				
			||||||
 | 
					                    if top > Slope(ny, nx):
 | 
				
			||||||
 | 
					                        if y == bottom_y:
 | 
				
			||||||
 | 
					                            bottom = Slope(ny, nx)
 | 
				
			||||||
 | 
					                            break
 | 
				
			||||||
 | 
					                        else:
 | 
				
			||||||
 | 
					                            self.compute_visibility_octant(
 | 
				
			||||||
 | 
					                                octant, origin, max_range, x + 1, top,
 | 
				
			||||||
 | 
					                                Slope(ny, nx))
 | 
				
			||||||
 | 
					                    elif y == bottom_y:  # pragma: no cover
 | 
				
			||||||
 | 
					                        return
 | 
				
			||||||
 | 
					                elif not is_opaque and was_opaque == 1:
 | 
				
			||||||
 | 
					                    nx, ny = x * 2, y * 2 + 1
 | 
				
			||||||
 | 
					                    nx += self.is_wall(y + 1, x + 1, octant, origin)
 | 
				
			||||||
 | 
					                    if bottom >= Slope(ny, nx):  # pragma: no cover
 | 
				
			||||||
 | 
					                        return
 | 
				
			||||||
 | 
					                    top = Slope(ny, nx)
 | 
				
			||||||
 | 
					                was_opaque = is_opaque
 | 
				
			||||||
 | 
					            if was_opaque != 0:
 | 
				
			||||||
 | 
					                break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @staticmethod
 | 
				
			||||||
 | 
					    def translate_coord(y: int, x: int, octant: int,
 | 
				
			||||||
 | 
					                        origin: Tuple[int, int]) -> Tuple[int, int]:
 | 
				
			||||||
 | 
					        ny, nx = origin
 | 
				
			||||||
 | 
					        if octant == 0:
 | 
				
			||||||
 | 
					            return ny - y, nx + x
 | 
				
			||||||
 | 
					        elif octant == 1:
 | 
				
			||||||
 | 
					            return ny - x, nx + y
 | 
				
			||||||
 | 
					        elif octant == 2:
 | 
				
			||||||
 | 
					            return ny - x, nx - y
 | 
				
			||||||
 | 
					        elif octant == 3:
 | 
				
			||||||
 | 
					            return ny - y, nx - x
 | 
				
			||||||
 | 
					        elif octant == 4:
 | 
				
			||||||
 | 
					            return ny + y, nx - x
 | 
				
			||||||
 | 
					        elif octant == 5:
 | 
				
			||||||
 | 
					            return ny + x, nx - y
 | 
				
			||||||
 | 
					        elif octant == 6:
 | 
				
			||||||
 | 
					            return ny + x, nx + y
 | 
				
			||||||
 | 
					        elif octant == 7:
 | 
				
			||||||
 | 
					            return ny + y, nx + x
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def is_wall(self, y: int, x: int, octant: int,
 | 
				
			||||||
 | 
					                origin: Tuple[int, int]) -> bool:
 | 
				
			||||||
 | 
					        y, x = self.translate_coord(y, x, octant, origin)
 | 
				
			||||||
 | 
					        return 0 <= y < len(self.tiles) and 0 <= x < len(self.tiles[0]) and \
 | 
				
			||||||
 | 
					            self.tiles[y][x].is_wall()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def set_visible(self, y: int, x: int, octant: int,
 | 
				
			||||||
 | 
					                    origin: Tuple[int, int]) -> None:
 | 
				
			||||||
 | 
					        y, x = self.translate_coord(y, x, octant, origin)
 | 
				
			||||||
 | 
					        if 0 <= y < len(self.tiles) and 0 <= x < len(self.tiles[0]):
 | 
				
			||||||
 | 
					            self.visibility[y][x] = True
 | 
				
			||||||
 | 
					            self.seen_tiles[y][x] = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def tick(self, p: Any) -> None:
 | 
					    def tick(self, p: Any) -> None:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Triggers all entity events.
 | 
					        Triggers all entity events.
 | 
				
			||||||
@@ -228,12 +383,21 @@ class Tile(Enum):
 | 
				
			|||||||
        val = getattr(pack, self.name)
 | 
					        val = getattr(pack, self.name)
 | 
				
			||||||
        return val[0] if isinstance(val, tuple) else val
 | 
					        return val[0] if isinstance(val, tuple) else val
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def color(self, pack: TexturePack) -> Tuple[int, int]:
 | 
					    def visible_color(self, pack: TexturePack) -> Tuple[int, int]:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Retrieve the tuple (fg_color, bg_color) of the current Tile
 | 
				
			||||||
 | 
					        if it is visible.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        val = getattr(pack, self.name)
 | 
				
			||||||
 | 
					        return (val[2], val[4]) if isinstance(val, tuple) else \
 | 
				
			||||||
 | 
					            (pack.tile_fg_visible_color, pack.tile_bg_color)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def hidden_color(self, pack: TexturePack) -> Tuple[int, int]:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Retrieve the tuple (fg_color, bg_color) of the current Tile.
 | 
					        Retrieve the tuple (fg_color, bg_color) of the current Tile.
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        val = getattr(pack, self.name)
 | 
					        val = getattr(pack, self.name)
 | 
				
			||||||
        return (val[1], val[2]) if isinstance(val, tuple) else \
 | 
					        return (val[1], val[3]) if isinstance(val, tuple) else \
 | 
				
			||||||
            (pack.tile_fg_color, pack.tile_bg_color)
 | 
					            (pack.tile_fg_color, pack.tile_bg_color)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def is_wall(self) -> bool:
 | 
					    def is_wall(self) -> bool:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -175,7 +175,7 @@ class TestEntities(unittest.TestCase):
 | 
				
			|||||||
        self.assertEqual(item.y, 42)
 | 
					        self.assertEqual(item.y, 42)
 | 
				
			||||||
        self.assertEqual(item.x, 42)
 | 
					        self.assertEqual(item.x, 42)
 | 
				
			||||||
        # Wait for the explosion
 | 
					        # Wait for the explosion
 | 
				
			||||||
        for ignored in range(5):
 | 
					        for _ignored in range(5):
 | 
				
			||||||
            item.act(self.map)
 | 
					            item.act(self.map)
 | 
				
			||||||
        self.assertTrue(hedgehog.dead)
 | 
					        self.assertTrue(hedgehog.dead)
 | 
				
			||||||
        self.assertTrue(teddy_bear.dead)
 | 
					        self.assertTrue(teddy_bear.dead)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,7 +4,7 @@
 | 
				
			|||||||
import unittest
 | 
					import unittest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from squirrelbattle.display.texturepack import TexturePack
 | 
					from squirrelbattle.display.texturepack import TexturePack
 | 
				
			||||||
from squirrelbattle.interfaces import Map, Tile
 | 
					from squirrelbattle.interfaces import Map, Tile, Slope
 | 
				
			||||||
from squirrelbattle.resources import ResourceManager
 | 
					from squirrelbattle.resources import ResourceManager
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -37,3 +37,21 @@ class TestInterfaces(unittest.TestCase):
 | 
				
			|||||||
        self.assertFalse(Tile.WALL.can_walk())
 | 
					        self.assertFalse(Tile.WALL.can_walk())
 | 
				
			||||||
        self.assertFalse(Tile.EMPTY.can_walk())
 | 
					        self.assertFalse(Tile.EMPTY.can_walk())
 | 
				
			||||||
        self.assertRaises(ValueError, Tile.from_ascii_char, 'unknown')
 | 
					        self.assertRaises(ValueError, Tile.from_ascii_char, 'unknown')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_slope(self) -> None:
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Test good behaviour of slopes (basically vectors, compared according to
 | 
				
			||||||
 | 
					        the determinant)
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        a = Slope(1, 1)
 | 
				
			||||||
 | 
					        b = Slope(0, 1)
 | 
				
			||||||
 | 
					        self.assertTrue(b < a)
 | 
				
			||||||
 | 
					        self.assertTrue(b <= a)
 | 
				
			||||||
 | 
					        self.assertTrue(a <= a)
 | 
				
			||||||
 | 
					        self.assertTrue(a == a)
 | 
				
			||||||
 | 
					        self.assertTrue(a > b)
 | 
				
			||||||
 | 
					        self.assertTrue(a >= b)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # def test_visibility(self) -> None:
 | 
				
			||||||
 | 
					        # m = Map.load(ResourceManager.get_asset_path("example_map_3.txt"))
 | 
				
			||||||
 | 
					        # m.compute_visibility(1, 1, 50)
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user