Climax: Passages
Hello, loves!
Rested, I feel ready to do the passage-generating code. I’ll TDD what I can. Maybe random seed will help.
This morning’s test got us close to what we need:
def test_border_dictionary(self):
layout = DungeonLayout(6,6)
r1 = [(1,1), (2,1), (3,1), (4,1),
(1,2), (2,2), (3,2), (4,2),
(3,3),]
r2 = [(1,3), (2,3), (4,3),
(1,4), (2, 4), (3,4), (4,4)]
r1_cells = [layout.at(*r) for r in r1]
r2_cells = [layout.at(*r) for r in r2]
room_1 = Room(r1_cells, 'room_1')
room_2 = Room(r2_cells, 'room_2')
layout.add_room(room_1)
layout.add_room(room_2)
b_d = layout.border_dictionary(room_1, room_2)
assert len(b_d) == 8
print(b_d)
entry_3_3 = b_d[layout.at(3,3)]
assert len(entry_3_3) == 3
assert layout.at(2,3) in entry_3_3
assert layout.at(3,4) in entry_3_3
assert layout.at(4,3) in entry_3_3
class DungeonLayout:
def border_dictionary(self, room_1, room_2):
result = dict()
for cell_1 in room_1:
for cell_2 in room_2:
if cell_1.manhattan_distance(cell_2) == 1:
self.add_borders(cell_1, cell_2, result)
return result
Now, if I recall correctly, DungeonView is currently maintaining a passages collection but I think that belongs in the layout, so we’ll work there. Let’s posit a new method there called, oh, random_passage(room1, room2).
I did it differently than I expected to: when I started writing the code a better way came to me.
class DungeonLayout:
def random_passage(self, room_1, room_2):
b_d = self.border_dictionary(room_1, room_2)
if not b_d: return None
possibles = []
for cell, neighbors in b_d.items():
for n in neighbors:
possibles.append((cell,n))
return random.choice(possibles)
The border_dictionary is a map from each border cell to the neighbors it can connect to. So if we just make an entry for each, (c, u), (c, y), (c, v), c gets three chances to connect, just as we wanted (until we need weight adjustment.
I think we can just make all the passages in one go, somewhere inside DungeonLayout.
I do a bit of bashing to get this to work:
main.py
...
layout.make_passages()
layout.make_borders()
...
class DungeonLayout:
def make_passages(self):
self.passages = dict()
for r1, r2 in itertools.combinations(self.rooms, 2):
self.add_passage(self.random_passage(r1, r2))
def add_passage(self, passage):
if passage:
c1, c2 = passage
self.passages[(c1, c2)] = True
self.passages[(c2, c1)] = True
class DungeonView:
def draw_passages(self):
for c1, c2 in self.dungeon.layout.passages:
cx, cy = c1.center_position(cell_size)
arcade.draw_circle_filled(cx, cy, cell_size //8, arcade.color.YELLOW)
class Dungeon:
def attempt_move_to_offset(self, cell, offset):
new_cell = self.layout.at_offset(cell.xy, offset)
if new_cell is None or self.layout.is_available(new_cell):
return
if self.in_different_rooms(cell, new_cell):
if not self.layout.passages.get((cell, new_cell), False):
return
self.place_player_at(new_cell)
With that in place, we get a map with lots of pairs of dots, and we can move across the dotted borders, and we cannot move across un-dotted borders, just as intended. Here, Dot has wandered far from home:

So that works. It surely isn’t quite right, with changes required in several places. And a test is failing, because it makes a bad assumption about where to find passages, I think. And the tests are Very Slow. They seem to be taking three or four seconds, which is an age: I have to wait to see my results.
Let’s find the broken one. It just needed to use the new layout.add_passage instead of dungeon. All better. Commit: dungeon allocates passages randomly.
Wandering the dungeon, it seems to me that the passages are decently placed. They tend to be in corners and niches but sometimes appear along straight segments of walls.
I think we’ve done enough. We’ll commit this and improve it next time.
Summary
I think the passage locations are good enough. We need some game-level improvements, possibly including:
- Make the floor textures sensitive to passages, so that they leave a passage more visible, remove yellow dots or images;
- Make some passages display an image right over the seam between the related cells, signifying a door.
- Make some passages invisible or optionally visible;
- Dot should be drawn last so as to always appear on top of things;
- Etc.
The code itself needs a review, for things being in the right place, for clarity, and possibly for efficiency. The game runs fast enough but takes a few seconds to start up.
The tests are too slow and that needs to be addressed. I’m not sure quite how we’ll figure that out, perhaps pytest has some help for us. With any luck, when we find the issue it will lead us to a speed improvement for the game setup.
But now we have passages between rooms, allocated randomly and in a fashion that looks good enough, a tendency to be in tight places but not always.
Life is good in the dungeon … so far.
See you next time!