First implementation of visibility, not tested, nor used for now

This commit is contained in:
Nicolas Margulies 2020-12-11 19:23:21 +01:00
parent 53cb6a89ae
commit 1cf5e7bd8b
2 changed files with 147 additions and 2 deletions

1
.gitignore vendored
View File

@ -3,6 +3,7 @@
env/ env/
venv/ venv/
local/
.coverage .coverage
.pytest_cache/ .pytest_cache/

View File

@ -4,7 +4,7 @@
from enum import Enum, auto from enum import Enum, auto
from math import sqrt from math import sqrt
from random import choice, randint from random import choice, randint
from typing import List, Optional from typing import List, Optional, Union, Tuple
from .display.texturepack import TexturePack from .display.texturepack import TexturePack
from .translations import gettext as _ from .translations import gettext as _
@ -30,6 +30,28 @@ 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: 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: class Map:
""" """
Object that represents a Map with its width, height Object that represents a Map with its width, height
@ -40,6 +62,7 @@ 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]]
entities: List["Entity"] entities: List["Entity"]
logs: Logs logs: Logs
# coordinates of the point that should be # coordinates of the point that should be
@ -54,6 +77,8 @@ 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.entities = [] self.entities = []
self.logs = Logs() self.logs = Logs()
@ -129,7 +154,7 @@ class Map:
""" """
Put randomly {count} hedgehogs on the map, where it is available. 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 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)
@ -140,6 +165,125 @@ 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.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: def tick(self) -> None:
""" """
Trigger all entity events. Trigger all entity events.