Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 34 additions & 6 deletions src/generators/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ def generate(self, new_seed=None) -> None:
Generates the rooms for the floor.
Like in "The Binding of Isaac", a floor is generated again if it does not
reach the room amount or has not enough dead ends for the special rooms.
Dead ends directly next to the start room do not count,
so the item and shop room are not placed right next to the start.
"""
if new_seed is not None:
self._seed_random(new_seed)
Expand All @@ -133,8 +135,13 @@ def generate(self, new_seed=None) -> None:
last_attempt = attempt == my_globals.MAX_GENERATION_ATTEMPTS - 1
start_room, complete = self._place_rooms()
dead_ends = self.mark_dead_ends()
usable = [
index
for index in dead_ends
if not self._is_next_to(self._floor.get_rooms()[index], start_room)
]
if last_attempt or (
complete and len(dead_ends) >= my_globals.REQUIRED_DEAD_ENDS
complete and len(usable) >= my_globals.REQUIRED_DEAD_ENDS
):
break
self.add_boss_room(dead_ends, start_room)
Expand Down Expand Up @@ -184,6 +191,13 @@ def _place_rooms(self) -> Tuple[Tuple[int, int], bool]:
floor.add_room(room_tuple[0], room_tuple[1])
return start_room, number_of_current_rooms >= number_of_rooms

@staticmethod
def _is_next_to(room: Room, position: Tuple[int, int]) -> bool:
"""
Returns if the room is directly next to the given position.
"""
return abs(room[0] - position[0]) + abs(room[1] - position[1]) == 1

def mark_dead_ends(self) -> list:
"""
Searches for dead ends in the floor and marks them ands returns their indices in a list.
Expand Down Expand Up @@ -352,14 +366,28 @@ def _check_if_not_contains_room_and_has_no_neighbours(

def add_special_rooms(self, dead_ends: list) -> None:
"""
Places the special rooms on the dead ends of the floor.
Places the special rooms on randomly chosen dead ends of the floor.
Dead ends directly next to the start room are only used
if there are not enough other dead ends.
@param dead_ends: indices of all dead ends
"""
floor = self._floor
i = 0
while i < len(room_type.SPECIAL_ROOMS) and i < len(dead_ends):
floor.get_rooms()[dead_ends[i]].set_type(room_type.SPECIAL_ROOMS[i])
i += 1
rooms = floor.get_rooms()
start = next(
(room for room in rooms if room.get_type() == RoomType.START_ROOM), None
)

def next_to_start(index: int) -> bool:
return start is not None and self._is_next_to(
rooms[index], (start[0], start[1])
)

preferred = [index for index in dead_ends if not next_to_start(index)]
fallback = [index for index in dead_ends if next_to_start(index)]
self._random.shuffle(preferred)
self._random.shuffle(fallback)
for special_room, index in zip(room_type.SPECIAL_ROOMS, preferred + fallback):
rooms[index].set_type(special_room)

def place_super_secret_room(self) -> None:
"""
Expand Down
36 changes: 34 additions & 2 deletions test/generator_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,25 @@ def test_add_special_rooms_assigns_item_and_shop_room(self):

self._generator.add_special_rooms([1, 2])

self.assertEqual(RoomType.ITEM_ROOM, floor.get_rooms()[1].get_type())
self.assertEqual(RoomType.SHOP_ROOM, floor.get_rooms()[2].get_type())
self.assertEqual(
{RoomType.ITEM_ROOM, RoomType.SHOP_ROOM},
{floor.get_rooms()[1].get_type(), floor.get_rooms()[2].get_type()},
)

def test_add_special_rooms_prefers_dead_ends_away_from_the_start(self):
floor = self._generator._floor
floor.add_room(2, 2, RoomType.START_ROOM)
floor.add_room(1, 2, RoomType.DEAD_END) # next to the start room
floor.add_room(5, 5, RoomType.DEAD_END)
floor.add_room(6, 6, RoomType.DEAD_END)

self._generator.add_special_rooms([1, 2, 3])

self.assertEqual(RoomType.DEAD_END, floor.get_rooms()[1].get_type())
self.assertEqual(
{RoomType.ITEM_ROOM, RoomType.SHOP_ROOM},
{floor.get_rooms()[2].get_type(), floor.get_rooms()[3].get_type()},
)

def test_add_special_rooms_stops_when_there_are_fewer_dead_ends_than_special_rooms(
self,
Expand Down Expand Up @@ -352,6 +369,21 @@ def test_set_stage_id_rejects_negative_ids(self):
with self.assertRaises(ValueError):
self._generator.set_stage_id(-1)

def test_special_rooms_are_never_next_to_the_start(self):
for stage_id in (0, 1, 2, 5):
for seed in range(100):
with self.subTest(stage_id=stage_id, seed=seed):
generator = Generator(str(seed), "test", "", stage_id)
generator.generate()
rooms = generator._floor.get_rooms()
start = next(
r for r in rooms if r.get_type() == RoomType.START_ROOM
)
for room in rooms:
if room.get_type() in (RoomType.ITEM_ROOM, RoomType.SHOP_ROOM):
distance = abs(room[0] - start[0]) + abs(room[1] - start[1])
self.assertGreater(distance, 1)

def test_save_writes_the_generated_floor_to_the_given_path(self):
self._generator.generate()
with tempfile.TemporaryDirectory() as tmp_dir:
Expand Down
Loading
Loading