diff --git a/src/generators/generator.py b/src/generators/generator.py index a75b44a..3bdb3e5 100644 --- a/src/generators/generator.py +++ b/src/generators/generator.py @@ -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 @@ -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] diff --git a/src/rooms/tkinter/tkinter_teleport_room.py b/src/rooms/tkinter/tkinter_teleport_room.py index ecf8b8d..46c0902 100644 --- a/src/rooms/tkinter/tkinter_teleport_room.py +++ b/src/rooms/tkinter/tkinter_teleport_room.py @@ -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. """ @@ -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 diff --git a/test/generator_test.py b/test/generator_test.py index 4de80d3..cee5d3e 100644 --- a/test/generator_test.py +++ b/test/generator_test.py @@ -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: diff --git a/test/tkinter_teleport_room_test.py b/test/tkinter_teleport_room_test.py new file mode 100644 index 0000000..aaff632 --- /dev/null +++ b/test/tkinter_teleport_room_test.py @@ -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()