Skip to content

Commit 61386ca

Browse files
Joltrasclaude
andauthored
test: increase coverage (#99)
* fix(utils): defer PIL/tkinter import in get_picture_for_room_type The module-level PIL/tkinter import made every module importing util_functions (including Floor/Generator) fail on systems without the Tk system library, blocking headless usage and testing of the generation logic. Moved it into the only function that needs it. * test(generator): add coverage for the Generator class generator_test.py previously only had a setUp with no actual test cases, leaving the core dungeon-generation algorithm untested. Adds tests for start/boss room placement, floor bounds, unique room coordinates, full reachability (including teleport-room links), seed determinism, dead-end marking, special room assignment, and JSON serialization/round-trip/save behavior. * chore(gitignore): ignore generated floor json files under src/utils/generation Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
1 parent 7994a7b commit 61386ca

3 files changed

Lines changed: 158 additions & 2 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
.idea
22
/generation
3+
src/utils/generation/
34
__pycache__/
45
.python-version

src/utils/util_functions.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,16 @@
99
import utils.globals as my_globals
1010
from utils.direction import Direction
1111
from utils.room_type import RoomType
12-
from PIL import Image, ImageTk
1312

1413

15-
def get_picture_for_room_type(room_type: RoomType) -> ImageTk:
14+
def get_picture_for_room_type(room_type: RoomType):
1615
"""
1716
Gets the picture for the given room type.
1817
@param room_type: room type to get the picture for
1918
@return: picture for the given room type
2019
"""
20+
from PIL import Image, ImageTk
21+
2122
# Get the directory of the current file
2223
current_dir = os.path.dirname(os.path.abspath(__file__))
2324
# Go up two levels to the project root directory

test/generator_test.py

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,169 @@
11
"""
22
This file contains the test cases for the generator class.
33
"""
4+
import json
5+
import os
6+
import tempfile
47
import unittest
8+
from collections import deque
59

10+
from floors.floor import Floor
611
from generators.generator import Generator
12+
from rooms.teleport_room import TeleportRoom
13+
from utils.globals import FLOOR_HEIGHT, FLOOR_WIDTH
14+
from utils.room_type import RoomType
15+
16+
_SAMPLE_SEEDS = ("1", "42", "abc", "test-seed", "zzz", "0")
17+
18+
19+
def _reachable_coordinates(floor):
20+
"""
21+
Returns which room coordinates are reachable from the start room by
22+
walking grid-adjacent rooms and following teleport-room connections,
23+
together with the set of all room coordinates on the floor.
24+
"""
25+
rooms = floor.get_rooms()
26+
rooms_by_id = {room.get_id(): room for room in rooms}
27+
coordinates = {(room[0], room[1]) for room in rooms}
28+
start_room = next(room for room in rooms if room.get_type() == RoomType.START_ROOM)
29+
30+
seen = {(start_room[0], start_room[1])}
31+
queue = deque([start_room])
32+
while queue:
33+
current = queue.popleft()
34+
neighbour_coordinates = [
35+
(current[0] + dx, current[1] + dy) for dx, dy in ((1, 0), (-1, 0), (0, 1), (0, -1))
36+
]
37+
if isinstance(current, TeleportRoom):
38+
connected_room = rooms_by_id[current.get_connected_room_id()]
39+
neighbour_coordinates.append((connected_room[0], connected_room[1]))
40+
for room in rooms:
41+
room_coordinates = (room[0], room[1])
42+
if room_coordinates in neighbour_coordinates and room_coordinates not in seen:
43+
seen.add(room_coordinates)
44+
queue.append(room)
45+
return seen, coordinates
746

847

948
class GeneratorTest(unittest.TestCase):
1049
def setUp(self) -> None:
1150
self._generator = Generator("1", "test", "", 1)
1251

52+
def test_generate_creates_exactly_one_start_room(self):
53+
self._generator.generate()
54+
start_rooms = [
55+
room
56+
for room in self._generator._floor.get_rooms()
57+
if room.get_type() == RoomType.START_ROOM
58+
]
59+
self.assertEqual(1, len(start_rooms))
60+
61+
def test_generate_creates_a_boss_room(self):
62+
self._generator.generate()
63+
self.assertIsNotNone(self._generator._floor.get_boss_room())
64+
65+
def test_generate_rooms_are_within_floor_bounds(self):
66+
for seed in _SAMPLE_SEEDS:
67+
with self.subTest(seed=seed):
68+
generator = Generator(seed, "test", "", 1)
69+
generator.generate()
70+
for room in generator._floor.get_rooms():
71+
self.assertTrue(0 <= room[0] < FLOOR_WIDTH)
72+
self.assertTrue(0 <= room[1] < FLOOR_HEIGHT)
73+
74+
def test_generate_does_not_place_two_rooms_on_the_same_tile(self):
75+
for seed in _SAMPLE_SEEDS:
76+
with self.subTest(seed=seed):
77+
generator = Generator(seed, "test", "", 1)
78+
generator.generate()
79+
coordinates = [
80+
(room[0], room[1]) for room in generator._floor.get_rooms()
81+
]
82+
self.assertEqual(len(coordinates), len(set(coordinates)))
83+
84+
def test_generate_produces_a_fully_reachable_dungeon(self):
85+
# Reachability also has to follow teleport rooms: the boss room can be
86+
# placed in a corner with no direct neighbours and only be reachable
87+
# through its connected teleport room.
88+
for seed in _SAMPLE_SEEDS:
89+
with self.subTest(seed=seed):
90+
generator = Generator(seed, "test", "", 1)
91+
generator.generate()
92+
seen, coordinates = _reachable_coordinates(generator._floor)
93+
self.assertEqual(coordinates, seen)
94+
95+
def test_generate_is_deterministic_for_the_same_seed(self):
96+
first = Generator("det-seed", "test", "", 1)
97+
first.generate()
98+
second = Generator("det-seed", "test", "", 1)
99+
second.generate()
100+
self.assertEqual(first._floor.get_rooms(), second._floor.get_rooms())
101+
102+
def test_mark_dead_ends_finds_all_rooms_with_a_single_neighbour(self):
103+
floor = self._generator._floor
104+
floor.add_room(2, 2, RoomType.START_ROOM)
105+
floor.add_room(1, 2) # only neighbour is (2, 2) -> dead end
106+
floor.add_room(3, 2) # neighbours are (2, 2) and (4, 2) -> not a dead end
107+
floor.add_room(4, 2) # only neighbour is (3, 2) -> dead end
108+
109+
dead_end_indices = self._generator.mark_dead_ends()
110+
111+
self.assertEqual([1, 3], dead_end_indices)
112+
for index in dead_end_indices:
113+
self.assertEqual(RoomType.DEAD_END, floor.get_rooms()[index].get_type())
114+
115+
def test_add_special_rooms_assigns_item_and_shop_room(self):
116+
floor = self._generator._floor
117+
floor.add_room(2, 2, RoomType.START_ROOM)
118+
floor.add_room(1, 2, RoomType.DEAD_END)
119+
floor.add_room(3, 2, RoomType.DEAD_END)
120+
121+
self._generator.add_special_rooms([1, 2])
122+
123+
self.assertEqual(RoomType.ITEM_ROOM, floor.get_rooms()[1].get_type())
124+
self.assertEqual(RoomType.SHOP_ROOM, floor.get_rooms()[2].get_type())
125+
126+
def test_add_special_rooms_stops_when_there_are_fewer_dead_ends_than_special_rooms(self):
127+
floor = self._generator._floor
128+
floor.add_room(2, 2, RoomType.START_ROOM)
129+
floor.add_room(1, 2, RoomType.DEAD_END)
130+
131+
self._generator.add_special_rooms([1])
132+
133+
self.assertEqual(RoomType.ITEM_ROOM, floor.get_rooms()[1].get_type())
134+
135+
def test_to_json_matches_the_generated_floor(self):
136+
self._generator.generate()
137+
138+
result = json.loads(self._generator.to_json(1))
139+
140+
self.assertEqual(FLOOR_WIDTH, result["_width"])
141+
self.assertEqual(FLOOR_HEIGHT, result["_height"])
142+
self.assertEqual("python", result["_generated_by"])
143+
self.assertEqual("1", result["_floor"]["_seed"])
144+
self.assertEqual(
145+
len(self._generator._floor.get_rooms()), len(result["_floor"]["_rooms"])
146+
)
147+
148+
def test_to_json_round_trips_through_floor_from_json(self):
149+
self._generator.generate()
150+
151+
restored = Floor.from_json(self._generator.to_json(1))
152+
153+
self.assertEqual(self._generator._floor.get_rooms(), restored.get_rooms())
154+
155+
def test_save_writes_the_generated_floor_to_the_given_path(self):
156+
self._generator.generate()
157+
with tempfile.TemporaryDirectory() as tmp_dir:
158+
output_path = os.path.join(tmp_dir, "floor.json")
159+
160+
result_path = self._generator.save(output_path)
161+
162+
self.assertEqual(output_path, result_path)
163+
with open(output_path, encoding="utf-8") as f:
164+
content = f.read()
165+
self.assertEqual(self._generator.to_json(1), content)
166+
13167

14168
if __name__ == "__main__":
15169
unittest.main()

0 commit comments

Comments
 (0)