Doors #156

Merged
ynerant merged 9 commits from doors into map_generation 2021-01-10 22:54:14 +00:00
9 changed files with 65 additions and 39 deletions

View File

@ -2,17 +2,17 @@
####### #############
#.H...# #...........#
#.....# #####...........#
#.....# #............H..#
#.....# #...&........H..#
#.##### #.###...........#
#.# #.# #...........#
#.# #.# #############
#.# #.#
#.#### #.#
#....# #.#
####.###################.#
####&###################&#
#.....................# #################
#.....................# #...............#
#.....................#######...............#
#...........................................#
#.....................&.....&...............#
#.....................#######...............#
####################### #################

View File

@ -82,6 +82,7 @@ TexturePack.ASCII_PACK = TexturePack(
BOW=')',
CHEST='',
CHESTPLATE='(',
DOOR='&',
EAGLE='µ',
EMPTY=' ',
EXPLOSION='%',
@ -124,6 +125,8 @@ TexturePack.SQUIRREL_PACK = TexturePack(
BOW='🏹',
CHEST='🧰',
CHESTPLATE='🦺',
DOOR=('🚪', curses.COLOR_WHITE, (1000, 1000, 1000),
curses.COLOR_WHITE, (1000, 1000, 1000)),
EAGLE='🦅',
EMPTY=' ',
EXPLOSION='💥',

View File

@ -6,7 +6,7 @@ from random import randint
from typing import Dict, Optional, Tuple
from .items import Item
from ..interfaces import FightingEntity, InventoryHolder
from ..interfaces import FightingEntity, InventoryHolder, Tile
from ..translations import gettext as _
@ -152,6 +152,12 @@ class Player(InventoryHolder, FightingEntity):
return True
elif entity.is_item():
entity.hold(self)
tile = self.map.tiles[y][x]
if tile == Tile.DOOR and move_if_possible:
# Open door
self.map.tiles[y][x] = Tile.FLOOR
self.map.compute_visibility(y, x, self.vision)
return super().check_move(y, x, move_if_possible)
return super().check_move(y, x, move_if_possible)
def save_state(self) -> dict:

View File

@ -199,7 +199,9 @@ class Game:
self.map_index = 0
return
while self.map_index >= len(self.maps):
self.maps.append(broguelike.Generator().run())
m = broguelike.Generator().run()
m.logs = self.logs
self.maps.append(m)
new_map = self.map
new_map.floor = self.map_index
old_map.remove_entity(self.player)
@ -417,6 +419,7 @@ class Game:
self.maps = [Map().load_state(map_dict) for map_dict in d["maps"]]
for i, m in enumerate(self.maps):
m.floor = i
m.logs = self.logs
except KeyError as error:
self.message = _("Some keys are missing in your save file.\n"
"Your save seems to be corrupt. It got deleted.")\

View File

@ -390,6 +390,7 @@ class Tile(Enum):
WALL = auto()
FLOOR = auto()
LADDER = auto()
DOOR = auto()
@staticmethod
def from_ascii_char(ch: str) -> "Tile":
@ -430,7 +431,7 @@ class Tile(Enum):
"""
Is this Tile a wall?
"""
return self == Tile.WALL
return self == Tile.WALL or self == Tile.DOOR
def is_ladder(self) -> bool:
"""

View File

@ -26,9 +26,11 @@ DEFAULT_PARAMS = {
"spawn_per_region": [1, 2],
}
def dist(level, y1, x1, y2, x2):
def dist(level: List[List[Tile]], y1: int, x1: int, y2: int, x2: int) -> int:
"""
Compute the minimum walking distance between points (y1, x1) and (y2, x2) on a Tile grid
Compute the minimum walking distance between points (y1, x1) and (y2, x2)
on a Tile grid
"""
# simple breadth first search
copy = [[t for t in row] for row in level]
@ -60,9 +62,9 @@ class Generator:
room: List[List[Tile]], door_y: int, door_x: int,
dy: int, dx: int) -> bool:
"""
Using point (door_y, door_x) in the room as a reference and placing it
Using point (door_y, door_x) in the room as a reference and placing it
over point (y, x) in the level, returns whether or not the room fits
here
here
"""
lh, lw = len(level), len(level[0])
rh, rw = len(room), len(room[0])
@ -93,12 +95,11 @@ class Generator:
def place_room(level: List[List[Tile]], y: int, x: int,
room: List[List[Tile]], door_y: int, door_x: int) -> None:
"""
Mutates level in place to add the room. Placement is determined by
Mutates level in place to add the room. Placement is determined by
making (door_y, door_x) in the room correspond with (y, x) in the level
"""
rh, rw = len(room), len(room[0])
# maybe place Tile.DOOR here instead ?
level[y][x] = Tile.FLOOR
level[y][x] = Tile.DOOR
for ry in range(rh):
for rx in range(rw):
if room[ry][rx] == Tile.FLOOR:
@ -107,11 +108,11 @@ class Generator:
@staticmethod
def add_loop(level: List[List[Tile]], y: int, x: int) -> bool:
"""
Try to add a corridor between two far apart floor tiles, passing
Try to add a corridor between two far apart floor tiles, passing
through point (y, x).
"""
h, w = len(level), len(level[0])
if level[y][x] != Tile.EMPTY:
return False
@ -129,8 +130,8 @@ class Generator:
continue
def verify_sides() -> bool:
# switching up dy and dx here pivots the axis, so
# (y+dx, x+dy) and (y-dx, x-dy) are the tiles adjacent to
# switching up dy and dx here pivots the axis, so
# (y+dx, x+dy) and (y-dx, x-dy) are the tiles adjacent to
# (y, x), but not on the original axis
for delta_x, delta_y in [[dy, dx], [-dy, -dx]]:
for i in range(1, y2 - y1 + x2 - x1):
@ -195,8 +196,8 @@ class Generator:
dy: int, dx: int, length: int) -> bool:
"""
Tries to build the exit from the room at given coordinates
Depending on parameter length, it will either attempt to build a
simple door, or a long corridor. Return value is a boolean
Depending on parameter length, it will either attempt to build a
simple door, or a long corridor. Return value is a boolean
signifying whether or not the exit was successfully built
"""
rh, rw = len(room), len(room[0])
@ -248,15 +249,15 @@ class Generator:
if room[y][x] == Tile.EMPTY and \
Generator.build_door(room, y, x, dy, dx, length):
break
else:
return None, None
else: # pragma: no cover
return None, None, None, None
return y + length * dy, x + length * dx, dy, dx
def create_circular_room(self, spawnable: bool = True) \
-> Tuple[List[List[Tile]], int, int, int, int]:
"""
Create and return as a tile grid a room that is circular in shape, and
Create and return as a tile grid a room that is circular in shape, and
may have a center, also circular hole
Also return door info so we know how to place the room in the level
"""
@ -299,7 +300,7 @@ class Generator:
def create_random_room(self, spawnable: bool = True) \
-> Tuple[List[list], int, int, int, int]:
"""
Randomly select a room shape and return one such room along with its
Randomly select a room shape and return one such room along with its
door info. Set spawnable to False is the room should be marked as a
potential spawning region on the map
"""
@ -320,12 +321,12 @@ class Generator:
def update_spawnable(self, y: int, x: int) -> None:
"""
Convert previous spawn positions relative to the room grid to actual
actual spawn positions on the level grid, using the position of the
top left corner of the room on the level, then log them as a
actual spawn positions on the level grid, using the position of the
top left corner of the room on the level, then log them as a
spawnable region
"""
if self.queued_area != None:
translated_area = [[y+ry, x+rx] for ry, rx in self.queued_area]
if self.queued_area is not None:
translated_area = [[y + ry, x + rx] for ry, rx in self.queued_area]
self.spawn_areas.append(translated_area)
self.queued_area = None
@ -334,11 +335,6 @@ class Generator:
Populate every spawnable area with some randomly chosen, randomly
placed entity
"""
if self.queued_area is not None:
translated_area = [[y + ry, x + rx] for ry, rx in self.queued_area]
self.spawn_areas.append(translated_area)
self.queued_area = None
min_c, max_c = self.params["spawn_per_region"]
for region in self.spawn_areas:
entity_count = randint(min_c, max_c)

View File

@ -134,13 +134,13 @@ class TestEntities(unittest.TestCase):
self.map.remove_entity(entity2)
# Test following the player and finding the player as target
self.player.move(5, 5)
fam.move(4, 5)
self.player.move(6, 5)
fam.move(5, 5)
fam.target = None
self.player.move_down()
self.map.tick(self.player)
self.assertTrue(fam.target == self.player)
self.assertEqual(fam.y, 5)
self.assertEqual(fam.y, 6)
self.assertEqual(fam.x, 5)
# Test random move

View File

@ -728,6 +728,7 @@ class TestGame(unittest.TestCase):
self.game.player.inventory.clear()
ring = RingCritical()
ring.hold(self.game.player)
self.game.display_actions(DisplayActions.REFRESH)
old_critical = self.game.player.critical
self.game.handle_key_pressed(KeyValues.EQUIP)
self.assertEqual(self.game.player.critical,
@ -951,3 +952,18 @@ class TestGame(unittest.TestCase):
# Exit the menu
self.game.handle_key_pressed(KeyValues.SPACE)
self.assertEqual(self.game.state, GameMode.PLAY)
def test_doors(self) -> None:
"""
Check that the user can open doors.
"""
self.game.state = GameMode.PLAY
self.game.player.move(9, 8)
self.assertEqual(self.game.map.tiles[10][8], Tile.DOOR)
# Open door
self.game.handle_key_pressed(KeyValues.DOWN)
self.assertEqual(self.game.map.tiles[10][8], Tile.FLOOR)
self.assertEqual(self.game.player.y, 10)
self.assertEqual(self.game.player.x, 8)
self.game.display_actions(DisplayActions.REFRESH)

View File

@ -26,16 +26,17 @@ class TestBroguelike(unittest.TestCase):
def is_connex(self, grid: List[List[Tile]]) -> bool:
h, w = len(grid), len(grid[0])
y, x = randint(0, h - 1), randint(0, w - 1)
while not (grid[y][x].can_walk()):
y, x = -1, -1
while not grid[y][x].can_walk():
y, x = randint(0, h - 1), randint(0, w - 1)
queue = Map.neighbourhood(grid, y, x)
while queue:
y, x = queue.pop()
if grid[y][x].can_walk():
if grid[y][x].can_walk() or grid[y][x] == Tile.DOOR:
grid[y][x] = Tile.WALL
queue += Map.neighbourhood(grid, y, x)
return not any([t.can_walk() for row in grid for t in row])
return not any([t.can_walk() or t == Tile.DOOR
for row in grid for t in row])
def test_build_doors(self) -> None:
m = self.stom(". .\n. .\n. .\n")