From 04b9b3692bf265aac328bc3241f0175fe9f9df93 Mon Sep 17 00:00:00 2001 From: Joltras <72395471+Joltras@users.noreply.github.com> Date: Mon, 2 Nov 2026 11:37:55 +0000 Subject: [PATCH 1/4] fix: build floor json with json.dumps --- src/floors/floor.py | 38 +++++++++--------------- src/generators/generator.py | 38 ++++++++++-------------- src/rooms/room.py | 58 ++++++++++++------------------------- test/generator_test.py | 11 +++++++ test/room_test.py | 40 ++++++++++++------------- 5 files changed, 78 insertions(+), 107 deletions(-) diff --git a/src/floors/floor.py b/src/floors/floor.py index e96ecc9..5872272 100644 --- a/src/floors/floor.py +++ b/src/floors/floor.py @@ -63,33 +63,23 @@ def bottom_right(self) -> Tuple[int, int]: """ return self._width - 1, self._height - 1 - def to_json(self, indent: int): + def to_dict(self) -> dict: """ - Creates a json string for the current room object. + Creates a dictionary representation of the floor object. + @return: dictionary with the seed and the rooms of the floor + """ + return { + "_seed": self.seed, + "_rooms": [room.to_dict() for room in self._rooms], + } + + def to_json(self, indent: int = 1) -> str: + """ + Creates a json string for the current floor object. + @param indent: Number of indentation levels per nesting level @return: json string """ - indent_s: str = my_globals.BASE_INDENT * indent - current_index: int = 0 - max_index: int = len(self._rooms) - json_string = ( - "{\n" - + f'{indent_s}"_seed": "{self.seed}",\n' - + indent_s - + '"_rooms"' - + ": [" - ) - if len(self._rooms) == 0: - json_string += "]" - else: - json_string += "\n" - for room in self._rooms: - if current_index < max_index - 1: - json_string += room.to_json(indent + 2) + ",\n" - else: - json_string += room.to_json(indent + 2) - current_index += 1 - json_string += "\n" + indent_s + "]" - return json_string + "\n}" + return json.dumps(self.to_dict(), indent=len(my_globals.BASE_INDENT) * indent) @classmethod def from_json(cls, json_string: str): diff --git a/src/generators/generator.py b/src/generators/generator.py index 3a01253..8e7c97f 100644 --- a/src/generators/generator.py +++ b/src/generators/generator.py @@ -5,6 +5,7 @@ """ import itertools +import json import os import random from collections import deque @@ -48,32 +49,25 @@ def __init__( ) self._random = random.Random(seed) - def to_json(self, indent: int) -> str: + def to_dict(self) -> dict: + """ + Creates a dictionary representation of the generator object. + @return: dictionary with the size and the floor of the generator + """ + return { + "_width": my_globals.FLOOR_WIDTH, + "_height": my_globals.FLOOR_HEIGHT, + "_floor": self._floor.to_dict(), + "_generated_by": "python", + } + + def to_json(self, indent: int = 1) -> str: """ Creates a string representation of the generator object. + @param indent: Number of indentation levels per nesting level @return: json string of the generator """ - indent_s = my_globals.BASE_INDENT * indent - - j = ( - "{\n" - + indent_s - + '"_width": ' - + str(my_globals.FLOOR_WIDTH) - + ",\n" - + indent_s - + '"_height": ' - + str(my_globals.FLOOR_HEIGHT) - + ",\n" - + indent_s - + '"_floor": ' - + self._floor.to_json(indent + 1) - + ",\n" - + indent_s - + '"_generated_by": "python"' - + "\n}" - ) - return j + return json.dumps(self.to_dict(), indent=len(my_globals.BASE_INDENT) * indent) def _create_floor(self) -> None: """ diff --git a/src/rooms/room.py b/src/rooms/room.py index f5ac45d..0adfcab 100644 --- a/src/rooms/room.py +++ b/src/rooms/room.py @@ -4,6 +4,7 @@ A room has a type, coordinates and doors. """ +import json from typing import List from utils import globals as my_globals @@ -44,49 +45,26 @@ def __getitem__(self, index: int) -> int: return self._x return self._y - def to_json(self, indent: int) -> str: + def to_dict(self) -> dict: + """ + Creates a dictionary representation of the room object. + @return: dictionary with the values of the room + """ + return { + "_doors": [door.value for door in self._doors], + "_id": self._id, + "_type": self._type.value, + "_x": self._x, + "_y": self._y, + } + + def to_json(self, indent: int = 1) -> str: """ Creates a json representation of the room object. - @param indent: Amount of indentation the json representation should have + @param indent: Number of indentation levels per nesting level @return: json string """ - indent_s: str = my_globals.BASE_INDENT * indent - json_string: str = ( - str(my_globals.BASE_INDENT * (indent - 1)) - + "{\n" - + indent_s - + '"_doors": [\n' - ) - i = 0 - while i < len(self._doors): - json_string += indent_s + my_globals.BASE_INDENT + str(self._doors[i].value) - if i != len(self._doors) - 1: - json_string += ",\n" - i += 1 - json_string += ( - "\n" - + indent_s - + "],\n" - + indent_s - + '"_id": ' - + str(self._id) - + ",\n" - + indent_s - + '"_type": ' - + str(self._type.value) - + ",\n" - + indent_s - + '"_x": ' - + str(self._x) - + ",\n" - + indent_s - + '"_y": ' - + str(self._y) - + "\n" - + str(my_globals.BASE_INDENT * (indent - 1)) - + "}" - ) - return json_string + return json.dumps(self.to_dict(), indent=len(my_globals.BASE_INDENT) * indent) @classmethod def from_dict(cls, json_dict: dict): @@ -169,4 +147,4 @@ def __str__(self) -> str: Returns a string representation of a room object. @return: String representation """ - return self.to_json(0) + return self.to_json() diff --git a/test/generator_test.py b/test/generator_test.py index 6ca175d..96ef399 100644 --- a/test/generator_test.py +++ b/test/generator_test.py @@ -242,6 +242,17 @@ def test_teleported_boss_rooms_are_isolated_and_on_the_grid(self): 0, floor.count_neighbours(boss_room[0], boss_room[1]) ) + def test_to_json_escapes_special_characters_in_the_seed(self): + for seed in ('a"b', "a\\b", "a\nb", "รค๐Ÿ™‚"): + with self.subTest(seed=seed): + generator = Generator(seed, "test", "", 1) + generator.generate() + + result = json.loads(generator.to_json(1)) + + self.assertEqual(seed, result["_floor"]["_seed"]) + self.assertEqual(seed, Floor.from_json(generator.to_json(1)).seed) + 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/room_test.py b/test/room_test.py index 239039e..2a90ad0 100644 --- a/test/room_test.py +++ b/test/room_test.py @@ -10,31 +10,29 @@ def setUp(self) -> None: self._room = Room(10, 20, 1, RoomType.NORMAL_ROOM) def test_to_json(self): - expected = """ { - "_doors": [ - - ], - "_id": 1, - "_type": 0, - "_x": 10, - "_y": 20 - }""" - self.assertEqual(expected, self._room.to_json(2)) + expected = """{ + "_doors": [], + "_id": 1, + "_type": 0, + "_x": 10, + "_y": 20 +}""" + self.assertEqual(expected, self._room.to_json()) def test_to_json_with_doors(self): - expected = """ { - "_doors": [ - 0, - 1 - ], - "_id": 1, - "_type": 0, - "_x": 10, - "_y": 20 - }""" + expected = """{ + "_doors": [ + 0, + 1 + ], + "_id": 1, + "_type": 0, + "_x": 10, + "_y": 20 +}""" self._room.add_door(DoorFace.TOP) self._room.add_door(DoorFace.EAST) - self.assertEqual(expected, self._room.to_json(2)) + self.assertEqual(expected, self._room.to_json()) def test_get_doors(self): self.assertEqual([], self._room.get_doors()) From 8fef35eb0e2094238b27915686a1f1ea5955acf4 Mon Sep 17 00:00:00 2001 From: Joltras <72395471+Joltras@users.noreply.github.com> Date: Mon, 2 Nov 2026 11:38:16 +0000 Subject: [PATCH 2/4] fix: restore floor grid and room ids when loading json --- src/floors/floor.py | 8 +++++--- test/generator_test.py | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/floors/floor.py b/src/floors/floor.py index 5872272..2ca64f8 100644 --- a/src/floors/floor.py +++ b/src/floors/floor.py @@ -90,9 +90,11 @@ def from_json(cls, json_string: str): floor = Floor( json_dict["_height"], json_dict["_width"], json_dict["_floor"]["_seed"] ) - rooms = json_dict["_floor"]["_rooms"] - for room in rooms: - floor._rooms.append(Room.from_dict(room)) + for room_dict in json_dict["_floor"]["_rooms"]: + room = Room.from_dict(room_dict) + floor._rooms.append(room) + floor.add_to_floor_grid(room[0], room[1]) + floor._room_id = max((room.get_id() for room in floor._rooms), default=-1) + 1 return floor def add_to_floor_grid(self, x: int, y: int) -> None: diff --git a/test/generator_test.py b/test/generator_test.py index 96ef399..b48b954 100644 --- a/test/generator_test.py +++ b/test/generator_test.py @@ -9,6 +9,8 @@ import unittest from collections import deque +import numpy + from floors.floor import Floor from generators.generator import Generator from rooms.teleport_room import TeleportRoom @@ -242,6 +244,26 @@ def test_teleported_boss_rooms_are_isolated_and_on_the_grid(self): 0, floor.count_neighbours(boss_room[0], boss_room[1]) ) + def test_from_json_restores_the_floor_grid(self): + self._generator.generate() + + restored = Floor.from_json(self._generator.to_json(1)) + + self.assertTrue( + numpy.array_equal( + self._generator._floor.get_floor_grid(), restored.get_floor_grid() + ) + ) + + def test_from_json_continues_the_room_ids(self): + self._generator.generate() + restored = Floor.from_json(self._generator.to_json(1)) + + restored.add_room(0, 0) + + ids = [room.get_id() for room in restored.get_rooms()] + self.assertEqual(len(ids), len(set(ids))) + def test_to_json_escapes_special_characters_in_the_seed(self): for seed in ('a"b', "a\\b", "a\nb", "รค๐Ÿ™‚"): with self.subTest(seed=seed): From c03f6e2bfea2ed1c6a20831bd537be4e0e78d8d9 Mon Sep 17 00:00:00 2001 From: Joltras <72395471+Joltras@users.noreply.github.com> Date: Mon, 2 Nov 2026 11:39:07 +0000 Subject: [PATCH 3/4] fix: keep teleport rooms when saving and loading floors --- src/floors/floor.py | 6 +++++- src/rooms/teleport_room.py | 25 +++++++++++++++++++++++++ test/generator_test.py | 21 +++++++++++++++++++++ test/teleport_room_test.py | 14 ++++++++++++++ 4 files changed, 65 insertions(+), 1 deletion(-) diff --git a/src/floors/floor.py b/src/floors/floor.py index 2ca64f8..616489f 100644 --- a/src/floors/floor.py +++ b/src/floors/floor.py @@ -91,7 +91,11 @@ def from_json(cls, json_string: str): json_dict["_height"], json_dict["_width"], json_dict["_floor"]["_seed"] ) for room_dict in json_dict["_floor"]["_rooms"]: - room = Room.from_dict(room_dict) + room = ( + TeleportRoom.from_dict(room_dict) + if "_connected_room_id" in room_dict + else Room.from_dict(room_dict) + ) floor._rooms.append(room) floor.add_to_floor_grid(room[0], room[1]) floor._room_id = max((room.get_id() for room in floor._rooms), default=-1) + 1 diff --git a/src/rooms/teleport_room.py b/src/rooms/teleport_room.py index d3022b2..306cad1 100644 --- a/src/rooms/teleport_room.py +++ b/src/rooms/teleport_room.py @@ -3,6 +3,7 @@ """ from rooms.room import Room +from utils.globals import DoorFace from utils.room_type import RoomType @@ -31,3 +32,27 @@ def get_connected_room_id(self) -> int: @return: room id """ return self._connected_room + + def to_dict(self) -> dict: + """ + Creates a dictionary representation of the teleport room object. + @return: dictionary with the values of the room and the connected room id + """ + room_dict = super().to_dict() + room_dict["_connected_room_id"] = self._connected_room + return room_dict + + @classmethod + def from_dict(cls, json_dict: dict): + """ + Creates a teleport room object from a dictionary. + """ + room = TeleportRoom( + json_dict["_x"], + json_dict["_y"], + json_dict["_id"], + json_dict["_connected_room_id"], + ) + for door in json_dict["_doors"]: + room.add_door(DoorFace(door)) + return room diff --git a/test/generator_test.py b/test/generator_test.py index b48b954..d94118f 100644 --- a/test/generator_test.py +++ b/test/generator_test.py @@ -264,6 +264,27 @@ def test_from_json_continues_the_room_ids(self): ids = [room.get_id() for room in restored.get_rooms()] self.assertEqual(len(ids), len(set(ids))) + def test_from_json_restores_teleport_rooms(self): + generator = Generator("2", "test", "", 2) + generator.generate() + original = [ + room + for room in generator._floor.get_rooms() + if isinstance(room, TeleportRoom) + ] + self.assertEqual(1, len(original)) + + restored = Floor.from_json(generator.to_json(1)) + + teleport_rooms = [ + room for room in restored.get_rooms() if isinstance(room, TeleportRoom) + ] + self.assertEqual(1, len(teleport_rooms)) + self.assertEqual( + original[0].get_connected_room_id(), + teleport_rooms[0].get_connected_room_id(), + ) + def test_to_json_escapes_special_characters_in_the_seed(self): for seed in ('a"b', "a\\b", "a\nb", "รค๐Ÿ™‚"): with self.subTest(seed=seed): diff --git a/test/teleport_room_test.py b/test/teleport_room_test.py index 7f506da..20859d2 100644 --- a/test/teleport_room_test.py +++ b/test/teleport_room_test.py @@ -1,5 +1,7 @@ import unittest + from rooms.teleport_room import TeleportRoom +from utils.globals import DoorFace class TeleportRoomTest(unittest.TestCase): @@ -9,6 +11,18 @@ def setUp(self) -> None: def test_get_connected_room(self): self.assertEqual(4, self._t_room.get_connected_room_id()) + def test_to_dict_contains_the_connected_room_id(self): + self.assertEqual(4, self._t_room.to_dict()["_connected_room_id"]) + + def test_from_dict_restores_the_teleport_room(self): + self._t_room.add_door(DoorFace.EAST) + + restored = TeleportRoom.from_dict(self._t_room.to_dict()) + + self.assertEqual(self._t_room, restored) + self.assertEqual(4, restored.get_connected_room_id()) + self.assertEqual([DoorFace.EAST], restored.get_doors()) + if __name__ == "__main__": unittest.main() From 09204a930b9a05e3c3ec15046db3a75716260f5b Mon Sep 17 00:00:00 2001 From: Joltras <72395471+Joltras@users.noreply.github.com> Date: Mon, 2 Nov 2026 11:39:52 +0000 Subject: [PATCH 4/4] fix: keep doors and teleport rooms when opening floors in the ui --- src/floors/tkinter_floor.py | 21 ++++++++--- src/rooms/tkinter/tkinter_room.py | 7 ++-- test/tkinter_floor_test.py | 62 +++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 8 deletions(-) create mode 100644 test/tkinter_floor_test.py diff --git a/src/floors/tkinter_floor.py b/src/floors/tkinter_floor.py index 90718db..05672e1 100644 --- a/src/floors/tkinter_floor.py +++ b/src/floors/tkinter_floor.py @@ -8,6 +8,7 @@ from floors.floor import Floor from rooms.room import Room +from rooms.teleport_room import TeleportRoom from rooms.tkinter.tkinter_room import TkinterRoom from rooms.tkinter.tkinter_teleport_room import TkinterTeleportRoom @@ -55,17 +56,27 @@ def from_floor(cls, floor: Floor, canvas, name: str): @param name: name of the floor @return: new TkinterFloor """ + # pylint: disable=protected-access tkinter_floor = TkinterFloor( floor._height, floor._width, canvas, name, - floor.seed, # pylint: disable=protected-access + floor.seed, ) - for room in floor._rooms: # pylint: disable=protected-access - tkinter_floor._rooms.append( - TkinterRoom.from_room(room) - ) # pylint: disable=protected-access + for room in floor.get_rooms(): + tkinter_room: TkinterRoom + if isinstance(room, TeleportRoom): + tkinter_room = TkinterTeleportRoom( + room[0], room[1], room.get_id(), room.get_connected_room_id() + ) + for door in room.get_doors(): + tkinter_room.add_door(door) + else: + tkinter_room = TkinterRoom.from_room(room) + tkinter_floor._rooms.append(tkinter_room) + tkinter_floor._floor_grid = floor.get_floor_grid().copy() + tkinter_floor._room_id = floor._room_id return tkinter_floor def _tkinter_rooms(self) -> List[TkinterRoom]: diff --git a/src/rooms/tkinter/tkinter_room.py b/src/rooms/tkinter/tkinter_room.py index 724b00c..19e9624 100644 --- a/src/rooms/tkinter/tkinter_room.py +++ b/src/rooms/tkinter/tkinter_room.py @@ -53,6 +53,7 @@ def from_room(cls, room: Room): """ Creates a new TkinterRoom from a room. """ - return TkinterRoom( - room._x, room._y, room._id, room._type - ) # pylint: disable=protected-access + tkinter_room = TkinterRoom(room[0], room[1], room.get_id(), room.get_type()) + for door in room.get_doors(): + tkinter_room.add_door(door) + return tkinter_room diff --git a/test/tkinter_floor_test.py b/test/tkinter_floor_test.py new file mode 100644 index 0000000..5904b5e --- /dev/null +++ b/test/tkinter_floor_test.py @@ -0,0 +1,62 @@ +"""Unit tests for the TkinterFloor class.""" + +import importlib.util +import unittest + +import numpy + +from floors.floor import Floor +from generators.generator import Generator +from rooms.teleport_room import TeleportRoom + +_HAS_TKINTER = importlib.util.find_spec("tkinter") is not None + + +@unittest.skipUnless(_HAS_TKINTER, "tkinter is not available") +class TkinterFloorTest(unittest.TestCase): + def setUp(self) -> None: + # pylint: disable=import-outside-toplevel + from floors.tkinter_floor import TkinterFloor + + generator = Generator("2", "test", "", 2) + generator.generate() + self._floor = Floor.from_json(generator.to_json(1)) + self._tkinter_floor = TkinterFloor.from_floor(self._floor, None, "floor.json") + + def test_from_floor_keeps_the_rooms(self): + self.assertEqual(self._floor.get_rooms(), self._tkinter_floor.get_rooms()) + + def test_from_floor_keeps_the_doors(self): + for original, converted in zip( + self._floor.get_rooms(), self._tkinter_floor.get_rooms() + ): + with self.subTest(room=original.get_id()): + self.assertEqual(original.get_doors(), converted.get_doors()) + + def test_from_floor_keeps_teleport_rooms(self): + teleport_rooms = [ + room + for room in self._tkinter_floor.get_rooms() + if isinstance(room, TeleportRoom) + ] + self.assertEqual(1, len(teleport_rooms)) + boss_room_ids = [ + room.get_id() + for room in self._floor.get_rooms() + if room.get_id() == teleport_rooms[0].get_connected_room_id() + ] + self.assertEqual(1, len(boss_room_ids)) + + def test_from_floor_keeps_the_floor_grid(self): + self.assertTrue( + numpy.array_equal( + self._floor.get_floor_grid(), self._tkinter_floor.get_floor_grid() + ) + ) + + def test_from_floor_keeps_the_saved_json(self): + self.assertEqual(self._floor.to_json(), self._tkinter_floor.to_json()) + + +if __name__ == "__main__": + unittest.main()