From 1cf5e7bd8b7329b6d3254a83685a3a3b11de0860 Mon Sep 17 00:00:00 2001 From: Nicolas Margulies Date: Fri, 11 Dec 2020 19:23:21 +0100 Subject: [PATCH 1/6] First implementation of visibility, not tested, nor used for now --- .gitignore | 1 + squirrelbattle/interfaces.py | 148 ++++++++++++++++++++++++++++++++++- 2 files changed, 147 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 8499d7c..e477e04 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ env/ venv/ +local/ .coverage .pytest_cache/ diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 3567ea0..91a2188 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -4,7 +4,7 @@ from enum import Enum, auto from math import sqrt from random import choice, randint -from typing import List, Optional +from typing import List, Optional, Union, Tuple from .display.texturepack import TexturePack from .translations import gettext as _ @@ -30,6 +30,28 @@ class Logs: 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: Union[Tuple[int, int], "Slope"]) -> int: + if isinstance(other, Slope): + y, x = other.Y, other.X + else: + y, x = other + return self.Y * x - self.X * y + + def __lt__(self, other: Union[Tuple[int, int], "Slope"]) -> bool: + return self.compare(other) < 0 + + def __eq__(self, other: Union[Tuple[int, int], "Slope"]) -> bool: + return self.compare(other) == 0 + + class Map: """ Object that represents a Map with its width, height @@ -40,6 +62,7 @@ class Map: start_y: int start_x: int tiles: List[List["Tile"]] + visibility: List[List[bool]] entities: List["Entity"] logs: Logs # coordinates of the point that should be @@ -54,6 +77,8 @@ class Map: self.start_y = start_y self.start_x = start_x self.tiles = tiles + self.visibility = [[False for _ in range(len(tiles[0]))] + for _ in range(len(tiles))] self.entities = [] self.logs = Logs() @@ -129,7 +154,7 @@ class Map: """ Put randomly {count} hedgehogs on the map, where it is available. """ - for ignored in range(count): + for _ignored in range(count): y, x = 0, 0 while True: y, x = randint(0, self.height - 1), randint(0, self.width - 1) @@ -140,6 +165,125 @@ class Map: entity.move(y, x) 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.visibility[y][x] = True + 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 = ((x * 2 - 1) * top.Y + top.X) / (top.X * 2) + if self.is_wall(top_y, x, octant, origin): + if top >= (top_y * 2 + 1, x * 2) and not\ + self.is_wall(top_y + 1, x, octant, origin): + top_y += 1 + else: + ax = x * 2 + if self.is_wall(top_y + 1, x + 1, octant, origin): + ax += 1 + if top > (top_y * 2 + 1, ax): + top_y += 1 + 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 = ((x * 2 + 1) * bottom.Y + bottom.X) /\ + (bottom.X * 2) + if bottom >= (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): + bottom_y += 1 + 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): + 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 sqrt(x**2 + y**2) > max_range: + continue + is_opaque = self.is_wall(y, x, octant, origin) + is_visible = is_opaque\ + or ((y != top_y or top > (y * 4 - 1, x * 4 - 1)) + and (y != bottom_y or bottom < (y * 4 + 1, x * 4 + 1))) + 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 + if self.is_wall(y + 1, x, octant, origin): + nx -= 1 + if top > (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)) + else: + if y == bottom_y: + return + elif not is_opaque and was_opaque == 1: + nx, ny = x * 2, y * 2 + 1 + if self.is_wall(y + 1, x + 1, octant, origin): + nx += 1 + if bottom >= (ny, nx): + return + 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 nx + x, ny - y + elif octant == 1: + return nx + y, ny - x + elif octant == 2: + return nx - y, ny - x + elif octant == 3: + return nx - x, ny - y + elif octant == 4: + return nx - x, ny + y + elif octant == 5: + return nx - y, ny + x + elif octant == 6: + return nx + y, ny + x + elif octant == 7: + return nx + x, ny + y + + 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 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) + self.visibility[y][x] = True + def tick(self) -> None: """ Trigger all entity events. From 86628fdea6b6f9b279ae65c9562e3a2086b93f1f Mon Sep 17 00:00:00 2001 From: Nicolas Margulies Date: Fri, 18 Dec 2020 17:04:45 +0100 Subject: [PATCH 2/6] Working visibility and displaying it, still need to hide things that aren't visible --- squirrelbattle/display/mapdisplay.py | 11 ++++- squirrelbattle/display/texturepack.py | 19 ++++--- squirrelbattle/entities/player.py | 4 +- squirrelbattle/interfaces.py | 71 ++++++++++++++++----------- 4 files changed, 64 insertions(+), 41 deletions(-) diff --git a/squirrelbattle/display/mapdisplay.py b/squirrelbattle/display/mapdisplay.py index 54d9432..c4f29e3 100644 --- a/squirrelbattle/display/mapdisplay.py +++ b/squirrelbattle/display/mapdisplay.py @@ -15,8 +15,15 @@ class MapDisplay(Display): self.pad = self.newpad(m.height, self.pack.tile_width * m.width + 1) def update_pad(self) -> None: - self.addstr(self.pad, 0, 0, self.map.draw_string(self.pack), - self.pack.tile_fg_color, self.pack.tile_bg_color) + for j in range(len(self.map.tiles)): + for i in range(len(self.map.tiles[j])): + color = self.pack.tile_fg_visible_color if \ + self.map.visibility[j][i] else self.pack.tile_fg_color + self.addstr(self.pad, j, self.pack.tile_width * i, + self.map.tiles[j][i].char(self.pack), + color, self.pack.tile_bg_color) + # self.addstr(self.pad, 0, 0, self.map.draw_string(self.pack), + # self.pack.tile_fg_color, self.pack.tile_bg_color) for e in self.map.entities: self.addstr(self.pad, e.y, self.pack.tile_width * e.x, self.pack[e.name.upper()], diff --git a/squirrelbattle/display/texturepack.py b/squirrelbattle/display/texturepack.py index f72cd97..fb56dd5 100644 --- a/squirrelbattle/display/texturepack.py +++ b/squirrelbattle/display/texturepack.py @@ -2,7 +2,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later import curses -from typing import Any +from typing import Any, Union, Tuple class TexturePack: @@ -10,10 +10,11 @@ class TexturePack: name: str tile_width: int - tile_fg_color: int - tile_bg_color: int - entity_fg_color: int - entity_bg_color: int + tile_fg_color: Union[int, Tuple[int, int, int]] + tile_fg_visible_color: Union[int, Tuple[int, int, int]] + tile_bg_color: Union[int, Tuple[int, int, int]] + entity_fg_color: Union[int, Tuple[int, int, int]] + entity_bg_color: Union[int, Tuple[int, int, int]] BODY_SNATCH_POTION: str BOMB: str @@ -54,9 +55,10 @@ class TexturePack: TexturePack.ASCII_PACK = TexturePack( name="ascii", tile_width=1, + tile_fg_visible_color=(1000, 1000, 1000), tile_fg_color=curses.COLOR_WHITE, tile_bg_color=curses.COLOR_BLACK, - entity_fg_color=curses.COLOR_WHITE, + entity_fg_color=(1000, 1000, 1000), entity_bg_color=curses.COLOR_BLACK, BODY_SNATCH_POTION='S', @@ -80,10 +82,11 @@ TexturePack.ASCII_PACK = TexturePack( TexturePack.SQUIRREL_PACK = TexturePack( name="squirrel", tile_width=2, + tile_fg_visible_color=(1000, 1000, 1000), tile_fg_color=curses.COLOR_WHITE, tile_bg_color=curses.COLOR_BLACK, - entity_fg_color=curses.COLOR_WHITE, - entity_bg_color=curses.COLOR_WHITE, + entity_fg_color=(1000, 1000, 1000), + entity_bg_color=(1000, 1000, 1000), BODY_SNATCH_POTION='🔀', BOMB='💣', diff --git a/squirrelbattle/entities/player.py b/squirrelbattle/entities/player.py index 19c8348..6d18884 100644 --- a/squirrelbattle/entities/player.py +++ b/squirrelbattle/entities/player.py @@ -21,7 +21,7 @@ class Player(InventoryHolder, FightingEntity): strength: int = 5, intelligence: int = 1, charisma: int = 1, dexterity: int = 1, constitution: int = 1, level: int = 1, 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: super().__init__(name=name, maxhealth=maxhealth, strength=strength, intelligence=intelligence, charisma=charisma, @@ -32,6 +32,7 @@ class Player(InventoryHolder, FightingEntity): self.inventory = self.translate_inventory(inventory or []) self.paths = dict() self.hazel = hazel + self.vision = vision def move(self, y: int, x: int) -> None: """ @@ -42,6 +43,7 @@ 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: """ diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 6458df7..89399f5 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -4,7 +4,8 @@ from enum import Enum, auto from math import sqrt from random import choice, randint -from typing import List, Optional, Union, Tuple, Any +from typing import List, Optional, Tuple, Any +from math import ceil from .display.texturepack import TexturePack from .translations import gettext as _ @@ -38,19 +39,25 @@ class Slope(): self.Y = y self.X = x - def compare(self, other: Union[Tuple[int, int], "Slope"]) -> int: - if isinstance(other, Slope): - y, x = other.Y, other.X - else: - y, x = other + def compare(self, other: "Slope") -> int: + y, x = other.Y, other.X return self.Y * x - self.X * y - def __lt__(self, other: Union[Tuple[int, int], "Slope"]) -> bool: + def __lt__(self, other: "Slope") -> bool: return self.compare(other) < 0 - def __eq__(self, other: Union[Tuple[int, int], "Slope"]) -> bool: + 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: """ @@ -194,16 +201,16 @@ class Map: if top.X == 1: top_y = x else: - top_y = ((x * 2 - 1) * top.Y + top.X) / (top.X * 2) + top_y = ceil(((x * 2 - 1) * top.Y + top.X) / (top.X * 2)) if self.is_wall(top_y, x, octant, origin): - if top >= (top_y * 2 + 1, x * 2) and not\ + if top >= Slope(top_y * 2 + 1, x * 2) and not\ self.is_wall(top_y + 1, x, octant, origin): top_y += 1 else: ax = x * 2 if self.is_wall(top_y + 1, x + 1, octant, origin): ax += 1 - if top > (top_y * 2 + 1, ax): + if top > Slope(top_y * 2 + 1, ax): top_y += 1 return top_y @@ -212,9 +219,9 @@ class Map: if bottom.Y == 0: bottom_y = 0 else: - bottom_y = ((x * 2 + 1) * bottom.Y + bottom.X) /\ - (bottom.X * 2) - if bottom >= (bottom_y * 2 + 1, x * 2) and\ + bottom_y = ceil(((x * 2 - 1) * bottom.Y + bottom.X) + / (bottom.X * 2)) + if 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): bottom_y += 1 @@ -223,17 +230,18 @@ class Map: 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): + 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 sqrt(x**2 + y**2) > max_range: + 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 > (y * 4 - 1, x * 4 - 1)) - and (y != bottom_y or bottom < (y * 4 + 1, x * 4 + 1))) + 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))) if is_visible: self.set_visible(y, x, octant, origin) if x == max_range: @@ -242,7 +250,7 @@ class Map: nx, ny = x * 2, y * 2 + 1 if self.is_wall(y + 1, x, octant, origin): nx -= 1 - if top > (ny, nx): + if top > Slope(ny, nx): if y == bottom_y: bottom = Slope(ny, nx) break @@ -257,8 +265,9 @@ class Map: nx, ny = x * 2, y * 2 + 1 if self.is_wall(y + 1, x + 1, octant, origin): nx += 1 - if bottom >= (ny, nx): + if bottom >= Slope(ny, nx): return + top = Slope(ny, nx) was_opaque = is_opaque if was_opaque != 0: break @@ -268,31 +277,33 @@ class Map: origin: Tuple[int, int]) -> Tuple[int, int]: ny, nx = origin if octant == 0: - return nx + x, ny - y + return ny - y, nx + x elif octant == 1: - return nx + y, ny - x + return ny - x, nx + y elif octant == 2: - return nx - y, ny - x + return ny - x, nx - y elif octant == 3: - return nx - x, ny - y + return ny - y, nx - x elif octant == 4: - return nx - x, ny + y + return ny + y, nx - x elif octant == 5: - return nx - y, ny + x + return ny + x, nx - y elif octant == 6: - return nx + y, ny + x + return ny + x, nx + y elif octant == 7: - return nx + x, ny + y + 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 self.tiles[y][x].is_wall() + 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) - self.visibility[y][x] = True + if 0 <= y < len(self.tiles) and 0 <= x < len(self.tiles[0]): + self.visibility[y][x] = True def tick(self) -> None: """ From 762bed888af208d9df1ba2eeb5846187811f8bc9 Mon Sep 17 00:00:00 2001 From: Nicolas Margulies Date: Fri, 18 Dec 2020 21:21:00 +0100 Subject: [PATCH 3/6] Working visibility (at least relatively good), but a few lines untested --- squirrelbattle/assets/example_map_3.txt | 41 +++++++++++++++++++++++++ squirrelbattle/display/mapdisplay.py | 10 ++++-- squirrelbattle/entities/player.py | 2 +- squirrelbattle/interfaces.py | 9 +++++- squirrelbattle/tests/entities_test.py | 2 +- squirrelbattle/tests/interfaces_test.py | 20 +++++++++++- 6 files changed, 77 insertions(+), 7 deletions(-) create mode 100644 squirrelbattle/assets/example_map_3.txt diff --git a/squirrelbattle/assets/example_map_3.txt b/squirrelbattle/assets/example_map_3.txt new file mode 100644 index 0000000..c5dd8e3 --- /dev/null +++ b/squirrelbattle/assets/example_map_3.txt @@ -0,0 +1,41 @@ +1 6 +################################################################################ +#..............................................................................# +#..#...........................................................................# +#...........#..................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +#..............................................................................# +################################################################################ \ No newline at end of file diff --git a/squirrelbattle/display/mapdisplay.py b/squirrelbattle/display/mapdisplay.py index c4f29e3..17d7d6d 100644 --- a/squirrelbattle/display/mapdisplay.py +++ b/squirrelbattle/display/mapdisplay.py @@ -17,6 +17,8 @@ class MapDisplay(Display): def update_pad(self) -> None: for j in range(len(self.map.tiles)): for i in range(len(self.map.tiles[j])): + if not self.map.seen_tiles[j][i]: + continue color = self.pack.tile_fg_visible_color if \ self.map.visibility[j][i] else self.pack.tile_fg_color self.addstr(self.pad, j, self.pack.tile_width * i, @@ -25,9 +27,11 @@ class MapDisplay(Display): # self.addstr(self.pad, 0, 0, self.map.draw_string(self.pack), # self.pack.tile_fg_color, self.pack.tile_bg_color) for e in self.map.entities: - self.addstr(self.pad, e.y, self.pack.tile_width * e.x, - self.pack[e.name.upper()], - self.pack.entity_fg_color, self.pack.entity_bg_color) + if self.map.visibility[e.y][e.x]: + self.addstr(self.pad, e.y, self.pack.tile_width * e.x, + self.pack[e.name.upper()], + self.pack.entity_fg_color, + self.pack.entity_bg_color) # Display Path map for debug purposes # from squirrelbattle.entities.player import Player diff --git a/squirrelbattle/entities/player.py b/squirrelbattle/entities/player.py index 6d18884..aad35dd 100644 --- a/squirrelbattle/entities/player.py +++ b/squirrelbattle/entities/player.py @@ -21,7 +21,7 @@ class Player(InventoryHolder, FightingEntity): strength: int = 5, intelligence: int = 1, charisma: int = 1, dexterity: int = 1, constitution: int = 1, level: int = 1, current_xp: int = 0, max_xp: int = 10, inventory: list = None, - hazel: int = 42, vision: int = 5, *args, **kwargs) \ + hazel: int = 42, vision: int = 50, *args, **kwargs) \ -> None: super().__init__(name=name, maxhealth=maxhealth, strength=strength, intelligence=intelligence, charisma=charisma, diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 89399f5..30e59f4 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -70,6 +70,7 @@ class Map: start_x: int tiles: List[List["Tile"]] visibility: List[List[bool]] + seen_tiles: List[List[bool]] entities: List["Entity"] logs: Logs # coordinates of the point that should be @@ -86,6 +87,8 @@ class Map: 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.logs = Logs() @@ -191,7 +194,7 @@ class Map: for line in self.visibility: for i in range(len(line)): line[i] = False - self.visibility[y][x] = True + 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)) @@ -242,6 +245,9 @@ class Map: 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: @@ -304,6 +310,7 @@ class Map: 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) -> None: """ diff --git a/squirrelbattle/tests/entities_test.py b/squirrelbattle/tests/entities_test.py index 70e3748..c51fd56 100644 --- a/squirrelbattle/tests/entities_test.py +++ b/squirrelbattle/tests/entities_test.py @@ -133,7 +133,7 @@ class TestEntities(unittest.TestCase): self.assertEqual(item.y, 42) self.assertEqual(item.x, 42) # Wait for the explosion - for ignored in range(5): + for _ignored in range(5): item.act(self.map) self.assertTrue(hedgehog.dead) self.assertTrue(teddy_bear.dead) diff --git a/squirrelbattle/tests/interfaces_test.py b/squirrelbattle/tests/interfaces_test.py index c9f7253..1713f77 100644 --- a/squirrelbattle/tests/interfaces_test.py +++ b/squirrelbattle/tests/interfaces_test.py @@ -4,7 +4,7 @@ import unittest from squirrelbattle.display.texturepack import TexturePack -from squirrelbattle.interfaces import Map, Tile +from squirrelbattle.interfaces import Map, Tile, Slope from squirrelbattle.resources import ResourceManager @@ -37,3 +37,21 @@ class TestInterfaces(unittest.TestCase): self.assertFalse(Tile.WALL.can_walk()) self.assertFalse(Tile.EMPTY.can_walk()) 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) From e9c8f43e7eb92d32090f6f80d61ae071fdbf643b Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 7 Jan 2021 16:31:39 +0100 Subject: [PATCH 4/6] Use ternary conditions to add coverage --- squirrelbattle/interfaces.py | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 30e59f4..c1c224f 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -206,15 +206,12 @@ class Map: else: top_y = ceil(((x * 2 - 1) * top.Y + top.X) / (top.X * 2)) if self.is_wall(top_y, x, octant, origin): - if top >= Slope(top_y * 2 + 1, x * 2) and not\ - self.is_wall(top_y + 1, x, octant, origin): - top_y += 1 + 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 - if self.is_wall(top_y + 1, x + 1, octant, origin): - ax += 1 - if top > Slope(top_y * 2 + 1, ax): - top_y += 1 + 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], @@ -224,10 +221,9 @@ class Map: else: bottom_y = ceil(((x * 2 - 1) * bottom.Y + bottom.X) / (bottom.X * 2)) - if 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): - bottom_y += 1 + 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], @@ -254,8 +250,7 @@ class Map: continue if is_opaque and was_opaque == 0: nx, ny = x * 2, y * 2 + 1 - if self.is_wall(y + 1, x, octant, origin): - nx -= 1 + nx -= self.is_wall(y + 1, x, octant, origin) if top > Slope(ny, nx): if y == bottom_y: bottom = Slope(ny, nx) @@ -264,14 +259,12 @@ class Map: self.compute_visibility_octant( octant, origin, max_range, x + 1, top, Slope(ny, nx)) - else: - if y == bottom_y: - return + elif y == bottom_y: # pragma: no cover + return elif not is_opaque and was_opaque == 1: nx, ny = x * 2, y * 2 + 1 - if self.is_wall(y + 1, x + 1, octant, origin): - nx += 1 - if bottom >= Slope(ny, nx): + 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 From c36e68d6e43579d0d3a604be637b0a47060ac332 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 7 Jan 2021 16:34:12 +0100 Subject: [PATCH 5/6] Reduce player vision --- squirrelbattle/entities/player.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/squirrelbattle/entities/player.py b/squirrelbattle/entities/player.py index aad35dd..6d18884 100644 --- a/squirrelbattle/entities/player.py +++ b/squirrelbattle/entities/player.py @@ -21,7 +21,7 @@ class Player(InventoryHolder, FightingEntity): strength: int = 5, intelligence: int = 1, charisma: int = 1, dexterity: int = 1, constitution: int = 1, level: int = 1, current_xp: int = 0, max_xp: int = 10, inventory: list = None, - hazel: int = 42, vision: int = 50, *args, **kwargs) \ + hazel: int = 42, vision: int = 5, *args, **kwargs) \ -> None: super().__init__(name=name, maxhealth=maxhealth, strength=strength, intelligence=intelligence, charisma=charisma, From 478a655751a53221a5cc7c5dda875acca3c777bf Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 7 Jan 2021 16:49:40 +0100 Subject: [PATCH 6/6] Fix fg/bg custom colors --- squirrelbattle/display/mapdisplay.py | 10 ++++------ squirrelbattle/display/texturepack.py | 3 ++- squirrelbattle/interfaces.py | 13 +++++++++++-- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/squirrelbattle/display/mapdisplay.py b/squirrelbattle/display/mapdisplay.py index 6cbf9ae..701f33e 100644 --- a/squirrelbattle/display/mapdisplay.py +++ b/squirrelbattle/display/mapdisplay.py @@ -26,13 +26,11 @@ class MapDisplay(Display): for i in range(len(self.map.tiles[j])): if not self.map.seen_tiles[j][i]: continue - color = self.pack.tile_fg_visible_color if \ - self.map.visibility[j][i] else self.pack.tile_fg_color + fg, bg = self.map.tiles[j][i].visible_color(self.pack) if \ + 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), - color, self.pack.tile_bg_color) - # self.addstr(self.pad, 0, 0, self.map.draw_string(self.pack), - # self.pack.tile_fg_color, self.pack.tile_bg_color) + self.map.tiles[j][i].char(self.pack), fg, bg) for e in self.map.entities: if self.map.visibility[e.y][e.x]: self.addstr(self.pad, e.y, self.pack.tile_width * e.x, diff --git a/squirrelbattle/display/texturepack.py b/squirrelbattle/display/texturepack.py index 796b32a..dae0763 100644 --- a/squirrelbattle/display/texturepack.py +++ b/squirrelbattle/display/texturepack.py @@ -99,7 +99,8 @@ TexturePack.SQUIRREL_PACK = TexturePack( EMPTY=' ', EXPLOSION='💥', FLOOR='██', - LADDER=('🪜', curses.COLOR_WHITE, curses.COLOR_WHITE), + LADDER=('🪜', curses.COLOR_WHITE, (1000, 1000, 1000), + curses.COLOR_WHITE, (1000, 1000, 1000)), HAZELNUT='🌰', HEART='💜', HEDGEHOG='🦔', diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index c1e509b..601229b 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -383,12 +383,21 @@ class Tile(Enum): val = getattr(pack, self.name) 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. """ 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) def is_wall(self) -> bool: