Doors #156
| @@ -2,17 +2,17 @@ | ||||
|     #######                    #############         | ||||
|     #.H...#                    #...........#         | ||||
|     #.....#                #####...........#         | ||||
|     #.....#                #............H..#         | ||||
|     #.....#                #...&........H..#         | ||||
|     #.#####                #.###...........#         | ||||
|     #.#                    #.# #...........#         | ||||
|     #.#                    #.# #############         | ||||
|     #.#                    #.#                       | ||||
|     #.####                 #.#                       | ||||
|     #....#                 #.#                       | ||||
|     ####.###################.#                       | ||||
|     ####&###################&#                       | ||||
|        #.....................#     ################# | ||||
|        #.....................#     #...............# | ||||
|        #.....................#######...............# | ||||
|        #...........................................# | ||||
|        #.....................&.....&...............# | ||||
|        #.....................#######...............# | ||||
|        #######################     ################# | ||||
|   | ||||
| @@ -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='💥', | ||||
|   | ||||
| @@ -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: | ||||
|   | ||||
| @@ -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.")\ | ||||
|   | ||||
| @@ -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: | ||||
|         """ | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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") | ||||
|   | ||||
		Reference in New Issue
	
	Block a user