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
49 changes: 32 additions & 17 deletions src/generators/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
The generation is inspired by the floor generation of the game "The Binding of Isaac".
"""

import itertools
import os
import random
from collections import deque
from datetime import datetime
from typing import List, TypeVar, Tuple
from typing import List, Optional, TypeVar, Tuple

from floors.floor import Floor
from rooms.room import Room
Expand Down Expand Up @@ -266,26 +267,40 @@ def _place_big_boss_room(self, possible_locations: List, boss_room: Room) -> boo

def _place_boss_with_teleport_room(self, boss_room) -> None:
"""
Searches for a free places in the corners of the floor to replace the boss room.
@param boss_room: boss room to replace
Moves the boss room to a free location without neighbours
and places a teleport room at its old position.
If no such location exists, the boss room stays where it is.
@param boss_room: boss room to move
"""
destination = self._find_teleport_destination()
if destination is None:
return
floor = self._floor
floor.add_teleport_room(boss_room)
if self._check_if_not_contains_room_and_has_no_neighbours(floor.top_left()):
boss_room.set_cord(0, 0)

elif self._check_if_not_contains_room_and_has_no_neighbours(floor.top_right()):
boss_room.set_cord(0, my_globals.FLOOR_HEIGHT - 1)
boss_room.set_cord(*destination)
floor.add_to_floor_grid(*destination)

elif self._check_if_not_contains_room_and_has_no_neighbours(
floor.bottom_left()
):
boss_room.set_cord(my_globals.FLOOR_WIDTH - 1, my_globals.FLOOR_HEIGHT - 1)

elif self._check_if_not_contains_room_and_has_no_neighbours(
floor.bottom_right()
):
boss_room.set_cord(my_globals.FLOOR_WIDTH - 1, 0)
def _find_teleport_destination(self) -> Optional[Tuple[int, int]]:
"""
Searches for a free location without neighbours, preferring the corners.
@return: coordinates of the location or None if there is none
"""
floor = self._floor
corners = (
floor.top_left(),
floor.top_right(),
floor.bottom_left(),
floor.bottom_right(),
)
all_points = (
(x, y)
for y in range(my_globals.FLOOR_HEIGHT)
for x in range(my_globals.FLOOR_WIDTH)
)
for point in itertools.chain(corners, all_points):
if self._check_if_not_contains_room_and_has_no_neighbours(point):
return point
return None

def _check_if_not_contains_room_and_has_no_neighbours(
self, point: Tuple[int, int]
Expand Down
9 changes: 5 additions & 4 deletions src/rooms/tkinter/tkinter_teleport_room.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@
from utils import globals as my_globals
from rooms.teleport_room import TeleportRoom
from rooms.tkinter.tkinter_room import TkinterRoom
from utils.room_type import RoomType


class TkinterTeleportRoom(TkinterRoom, TeleportRoom):
class TkinterTeleportRoom(TeleportRoom, TkinterRoom):
"""
Tkinter version of teleport room.
"""
Expand All @@ -24,6 +23,8 @@ def __init__(
):
"""
Creates a new teleport room with the given values.
@param teleport_room_id: id of the room the teleport room is connected to
"""
super().__init__(x, y, room_id, RoomType.TELEPORT_ROOM, width, height)
self._teleport_room_id = teleport_room_id
super().__init__(x, y, room_id, teleport_room_id)
self._width = width
self._height = height
51 changes: 51 additions & 0 deletions test/generator_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,57 @@ def test_generate_terminates_for_many_seeds_on_small_floors(self):
seed=str(seed), output_file_name="floor", stage_id=stage_id
).generate()

def test_teleported_boss_room_is_moved_to_the_checked_corner(self):
self._generator._create_floor()
floor = self._generator._floor
floor.add_room(1, 0) # blocks the top left corner
floor.add_room(4, 4)
boss_room = floor.get_rooms()[1]

self._generator._place_boss_with_teleport_room(boss_room)

self.assertEqual((FLOOR_WIDTH - 1, 0), (boss_room[0], boss_room[1]))
self.assertTrue(floor.contains_room((FLOOR_WIDTH - 1, 0)))
teleport_room = floor.get_rooms()[-1]
self.assertIsInstance(teleport_room, TeleportRoom)
self.assertEqual((4, 4), (teleport_room[0], teleport_room[1]))
self.assertEqual(boss_room.get_id(), teleport_room.get_connected_room_id())

def test_boss_room_stays_when_there_is_no_free_location(self):
self._generator._create_floor()
floor = self._generator._floor
for x in range(FLOOR_WIDTH):
for y in range(FLOOR_HEIGHT):
floor.add_to_floor_grid(x, y)
floor.add_room(4, 4)
boss_room = floor.get_rooms()[0]

self._generator._place_boss_with_teleport_room(boss_room)

self.assertEqual((4, 4), (boss_room[0], boss_room[1]))
self.assertEqual(1, len(floor.get_rooms()))

def test_teleported_boss_rooms_are_isolated_and_on_the_grid(self):
for stage_id in (0, 1, 2, 3):
for seed in range(200):
with self.subTest(stage_id=stage_id, seed=seed):
generator = Generator(str(seed), "test", "", stage_id)
generator.generate()
floor = generator._floor
rooms = floor.get_rooms()
coordinates = [(room[0], room[1]) for room in rooms]
self.assertEqual(len(coordinates), len(set(coordinates)))
for point in coordinates:
self.assertTrue(floor.contains_room(point))
rooms_by_id = {room.get_id(): room for room in rooms}
for room in rooms:
if isinstance(room, TeleportRoom):
boss_room = rooms_by_id[room.get_connected_room_id()]
self.assertEqual(RoomType.BOSS_ROOM, boss_room.get_type())
self.assertEqual(
0, floor.count_neighbours(boss_room[0], boss_room[1])
)

def test_save_writes_the_generated_floor_to_the_given_path(self):
self._generator.generate()
with tempfile.TemporaryDirectory() as tmp_dir:
Expand Down
29 changes: 29 additions & 0 deletions test/tkinter_teleport_room_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""Unit tests for the TkinterTeleportRoom class."""

import importlib.util
import unittest

from utils.room_type import RoomType

_HAS_TKINTER = importlib.util.find_spec("tkinter") is not None


@unittest.skipUnless(_HAS_TKINTER, "tkinter is not available")
class TkinterTeleportRoomTest(unittest.TestCase):
def setUp(self) -> None:
# pylint: disable=import-outside-toplevel
from rooms.tkinter.tkinter_teleport_room import TkinterTeleportRoom

self._t_room = TkinterTeleportRoom(1, 2, room_id=3, teleport_room_id=4)

def test_get_connected_room_id(self):
self.assertEqual(4, self._t_room.get_connected_room_id())

def test_room_values(self):
self.assertEqual((1, 2), (self._t_room[0], self._t_room[1]))
self.assertEqual(3, self._t_room.get_id())
self.assertEqual(RoomType.TELEPORT_ROOM, self._t_room.get_type())


if __name__ == "__main__":
unittest.main()
Loading