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/
venv/
local/
.coverage
.pytest_cache/

View File

@ -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.