Repository: tmcclintock/donjuan

Issue:
Create a Hallway class
Create a `Hallway` class and put a dictionary in the `Dungeon` to keep track of corridors. Functionally the `Hallway` should resemble the `Grid`, since it is just a collection of `Cell` objects.

This issue should be expanded on.

Discussion:

Patch:
diff --git a/donjuan/dungeon.py b/donjuan/dungeon.py
index f72ecde..d93e10d 100644
--- a/donjuan/dungeon.py
+++ b/donjuan/dungeon.py
@@ -1,7 +1,9 @@
 from typing import Dict, List, Optional
 
 from donjuan.grid import Grid, SquareGrid
+from donjuan.hallway import Hallway
 from donjuan.room import Room
+from donjuan.space import Space
 
 
 class Dungeon:
@@ -11,19 +13,28 @@ class Dungeon:
         n_cols: Optional[int] = 5,
         grid: Optional[Grid] = None,
         rooms: Optional[Dict[str, Room]] = None,
+        hallways: Optional[Dict[str, Hallway]] = None,
         randomizers: Optional[List["Randomizer"]] = None,
     ):
         self._grid = grid or SquareGrid(n_rows, n_cols)
         self._rooms = rooms or {}
+        self._hallways = hallways or {}
         self._randomizers = randomizers or []
 
     def add_room(self, room: Room) -> None:
         self._rooms[room.name] = room
 
+    def add_hallway(self, hallway: Hallway) -> None:
+        self.hallways[hallway.name] = hallway
+
     @property
     def grid(self) -> Grid:
         return self._grid
 
+    @property
+    def hallways(self) -> Dict[str, Hallway]:
+        return self._hallways
+
     @property
     def n_cols(self) -> int:
         return self.grid.n_cols
@@ -56,18 +67,18 @@ class Dungeon:
         """
 
         for room_name, room in self.rooms.items():
-            self.emplace_room(room)
+            self.emplace_space(room)
         return
 
-    def emplace_room(self, room: Room) -> None:
+    def emplace_space(self, space: Space) -> None:
         """
-        Replace the cells in the :attr:`grid` with the cells of the `Room`,
+        Replace the cells in the :attr:`grid` with the cells of the `Space`,
         and automatically link them with the `Edge`s in the `Grid`.
 
         Args:
-            room (Room): room to emplace in the :attr:`grid`
+            space (Space): room to emplace in the :attr:`grid`
         """
-        for cell in room.cells:
+        for cell in space.cells:
             self.grid.cells[cell.y][cell.x] = cell
         self.grid.link_edges_to_cells()
         return
diff --git a/donjuan/hallway.py b/donjuan/hallway.py
index 5ef0325..02858ed 100644
--- a/donjuan/hallway.py
+++ b/donjuan/hallway.py
@@ -1,3 +1,7 @@
+from collections.abc import Sequence
+from typing import List, Optional, Tuple, Union
+
+from donjuan.cell import Cell
 from donjuan.space import Space
 
 
@@ -5,5 +9,65 @@ class Hallway(Space):
     """
     A hallway in a dungeon. It has a start and end cell.
 
-    .. todo:: Finish this class. It is incomplete.
+    Args:
+        ordered_cells (Optional[Sequence[Cell]]): ordered list of cells, where
+            the order defines the path of the hallway. If ``None`` defaults
+            to an empty list.
+        name (Union[int, str], optional): defaults to '', the name of the
+            hallway
     """
+
+    def __init__(
+        self,
+        ordered_cells: Optional[Sequence[Cell]] = None,
+        name: Union[int, str] = "",
+    ):
+        self._ordered_cells = ordered_cells or list()
+        super().__init__(cells=set(self.ordered_cells), name=name)
+
+    @property
+    def ordered_cells(self) -> List[Cell]:
+        """
+        Cells that make up the path of the hallway. Does not contain
+        extra cells that may be associated with this object (i.e. those
+        off of the "path"). For the set of all cells, use :attr:`cells`.
+        """
+        return list(self._ordered_cells)
+
+    @property
+    def end_cell(self) -> Cell:
+        return self.ordered_cells[-1]
+
+    @property
+    def start_cell(self) -> Cell:
+        return self.ordered_cells[0]
+
+    def append_ordered_cell_list(self, cells: List[Cell]) -> None:
+        """
+        Append cells in order to the hallway. To add a cell to the hallway
+        without including it in the hallways path, use
+        :meth:`add_cells`.
+
+        Args:
+            cells: (List[Cell]): cells to append to the hallway
+        """
+        assert isinstance(cells, list)
+        self.add_cells(cells)
+        for cell in cells:
+            assert isinstance(cell, Cell)
+            self.ordered_cells.append(cell)
+        return
+
+    def get_coordinate_path(self) -> List[Tuple[int, int]]:
+        """
+        Get the coordinates of the cells that make up this path this
+        hallway takes. Does not contain coordinates of extra cells on this
+        object.
+
+        Returns:
+            coordinates of the hallway path
+        """
+        path = []
+        for cell in self.ordered_cells:
+            path.append(cell.coordinates)
+        return path
diff --git a/donjuan/randomizer.py b/donjuan/randomizer.py
index 7023ea9..8b9437d 100644
--- a/donjuan/randomizer.py
+++ b/donjuan/randomizer.py
@@ -4,6 +4,7 @@ from typing import Optional
 from donjuan.cell import Cell
 from donjuan.dungeon import Dungeon
 from donjuan.grid import Grid
+from donjuan.hallway import Hallway
 from donjuan.room import Room
 
 
@@ -24,6 +25,10 @@ class Randomizer:
         """Randomize properties of the `Grid`"""
         pass  # pragma: no cover
 
+    def randomize_hallway(self, hallway: Hallway) -> None:
+        """Randomize properties of the `Hallway`"""
+        pass  # pragma: no cover
+
     def randomize_room_size(self, room: Room, *args) -> None:
         """Randomize the size of the `Room`"""
         pass  # pragma: no cover
diff --git a/donjuan/room.py b/donjuan/room.py
index c754dc8..d067f52 100644
--- a/donjuan/room.py
+++ b/donjuan/room.py
@@ -1,6 +1,3 @@
-from typing import Optional, Set, Union
-
-from donjuan.cell import Cell
 from donjuan.space import Space
 
 
@@ -8,18 +5,3 @@ class Room(Space):
     """
     A room in a dungeon. A room has is a named ``Space``.
     """
-
-    def __init__(
-        self, cells: Optional[Set[Cell]] = None, name: Union[int, str] = "",
-    ):
-        super().__init__(cells=cells)
-        assert isinstance(name, (int, str))
-        self._name = name
-
-    @property
-    def name(self) -> str:
-        return str(self._name)
-
-    def set_name(self, name: Union[int, str]) -> None:
-        self._name = name
-        return
diff --git a/donjuan/room_randomizer.py b/donjuan/room_randomizer.py
index cf3bc97..bad84df 100644
--- a/donjuan/room_randomizer.py
+++ b/donjuan/room_randomizer.py
@@ -40,7 +40,7 @@ class RoomSizeRandomizer(Randomizer):
             for j in range(height)
             for i in range(width)
         ]
-        room.insert_cell_list(cells)
+        room.add_cells(cells)
         return
 
 
diff --git a/donjuan/space.py b/donjuan/space.py
index d8f98a6..dd1c262 100644
--- a/donjuan/space.py
+++ b/donjuan/space.py
@@ -1,5 +1,6 @@
 from abc import ABC
-from typing import List, Optional, Set, Tuple
+from collections.abc import Iterable
+from typing import Optional, Set, Tuple, Union
 
 from donjuan.cell import Cell
 
@@ -13,7 +14,9 @@ class Space(ABC):
         cells (Optional[Set[Cell]]): cells that make up this space
     """
 
-    def __init__(self, cells: Optional[Set[Cell]] = None):
+    def __init__(self, cells: Optional[Set[Cell]] = None, name: Union[int, str] = ""):
+        assert isinstance(name, (int, str))
+        self._name = name
         self._cells = cells or set()
         self.assign_space_to_cells()
         self.reset_cell_coordinates()
@@ -34,19 +37,23 @@ class Space(ABC):
     def cell_coordinates(self) -> Set[Tuple[int, int]]:
         return self._cell_coordinates
 
-    def insert_cell_list(self, cells: List[Cell]) -> None:
+    @property
+    def name(self) -> Union[int, str]:
+        return str(self._name)
+
+    def add_cells(self, cells: Iterable[Cell]) -> None:
         """
-        Insert a list of cells into the :attr:`cells` set, with
-        keys being the coordinates of the cells.
+        Add cells to the set of cells in this space. Cells are added to
+        both the :attr:`cells` set and the cell coordinates to the
+        :attr:`cell_coordinates` set.
 
         Args:
-            cells (List[Cell]): list of cells to insert
+            cells (Iterable[Cell]): any iterable collection of cells
         """
-        if len(cells) > 0:
-            assert isinstance(cells[0], Cell)
         for cell in cells:
             self.cells.add(cell)
             self.cell_coordinates.add(cell.coordinates)
+        return
 
     def overlaps(self, other: "Space") -> bool:
         """
@@ -69,6 +76,10 @@ class Space(ABC):
         # No overlap
         return False
 
+    def set_name(self, name: Union[int, str]) -> None:
+        assert isinstance(name, (int, str))
+        self._name = name
+
     def shift_vertical(self, n: int) -> None:
         """
         Change the ``y`` coordinates of all :attr:`cells` by ``n``.


Test patch:
diff --git a/tests/dungeon_test.py b/tests/dungeon_test.py
index 2f41802..9dc808c 100644
--- a/tests/dungeon_test.py
+++ b/tests/dungeon_test.py
@@ -52,12 +52,12 @@ class DungeonTest(TestCase):
         assert d.n_rows == 3
         assert d.n_cols == 4
 
-    def test_emplace_room(self):
+    def test_emplace_space(self):
         grid = SquareGrid(3, 4)
         d = Dungeon(grid=grid)
         cs = set([SquareCell(filled=False, coordinates=(1, 2))])
         room = Room(cells=cs)
-        d.emplace_room(room)
+        d.emplace_space(room)
         for i in range(d.n_rows):
             for j in range(d.n_cols):
                 if i == 1 and j == 2:
diff --git a/tests/hallway_test.py b/tests/hallway_test.py
index 65c27bd..f1af668 100644
--- a/tests/hallway_test.py
+++ b/tests/hallway_test.py
@@ -1,9 +1,29 @@
 from unittest import TestCase
 
+from donjuan import Hallway, SquareCell
+
 
 class HallwayTest(TestCase):
-    """
-    .. todo:: Make tests once the `Hallway` class has content.
-    """
+    def setUp(self):
+        super().setUp()
+        self.cells = [SquareCell() for _ in range(3)]
+
+    def test_smoke(self):
+        h = Hallway()
+        assert h is not None
+        assert h.name == ""
+
+    def test_cells_ordered(self):
+        h = Hallway(self.cells)
+        assert h.start_cell is self.cells[0]
+        assert h.end_cell is self.cells[-1]
+        assert len(h.ordered_cells) == 3
+        assert len(h.cells) == 3
+        for cell in h.ordered_cells:
+            assert cell in h.cells
 
-    pass
+    def test_get_coordinate_path(self):
+        cells = [SquareCell(coordinates=(i, 0)) for i in range(10)]
+        h = Hallway(cells)
+        for i, coords in enumerate(h.get_coordinate_path()):
+            assert coords == (i, 0)
diff --git a/tests/room_test.py b/tests/room_test.py
index b6e30cd..f1cd9ce 100644
--- a/tests/room_test.py
+++ b/tests/room_test.py
@@ -29,10 +29,10 @@ class RoomTest(TestCase):
         r.set_name("catdog")
         assert r.name == "catdog"
 
-    def test_insert_cell_list(self):
+    def test_add_cells(self):
         r = Room()
         assert len(r.cells) == 0
-        r.insert_cell_list([SquareCell()])
+        r.add_cells([SquareCell()])
         assert len(r.cells) == 1
         assert len(r.cell_coordinates) == 1
         assert r.cell_coordinates == set(((0, 0),))