sonartk 0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. sonartk/__init__.py +34 -0
  2. sonartk/map_builder/__init__.py +14 -0
  3. sonartk/map_builder/map_2d/__init__.py +25 -0
  4. sonartk/map_builder/map_2d/direction_tools.py +29 -0
  5. sonartk/map_builder/map_2d/map_2d.py +257 -0
  6. sonartk/map_builder/map_2d/map_loader.py +33 -0
  7. sonartk/map_builder/map_2d/map_object/__init__.py +4 -0
  8. sonartk/map_builder/map_2d/map_object/character.py +15 -0
  9. sonartk/map_builder/map_2d/map_object/map_object.py +11 -0
  10. sonartk/map_builder/map_2d/map_queries.py +1330 -0
  11. sonartk/map_builder/map_2d/map_tile.py +37 -0
  12. sonartk/map_builder/map_2d/map_tools.py +56 -0
  13. sonartk/map_builder/map_2d/parser/__init__.py +5 -0
  14. sonartk/map_builder/map_2d/parser/csv_parser.py +38 -0
  15. sonartk/map_builder/map_2d/parser/json_parser.py +36 -0
  16. sonartk/map_builder/map_2d/parser/map_parser.py +33 -0
  17. sonartk/map_builder/map_2d/parser/stop_parsing_exception.py +2 -0
  18. sonartk/orchestration/__init__.py +76 -0
  19. sonartk/orchestration/audio_controls.py +66 -0
  20. sonartk/orchestration/audio_lifecycle.py +24 -0
  21. sonartk/orchestration/game_builder.py +169 -0
  22. sonartk/orchestration/input_gate.py +17 -0
  23. sonartk/orchestration/map_object_collection.py +183 -0
  24. sonartk/orchestration/map_sound_navigation.py +384 -0
  25. sonartk/orchestration/message_action_state.py +91 -0
  26. sonartk/orchestration/proximity_audio.py +134 -0
  27. sonartk/orchestration/scene_audio_state.py +100 -0
  28. sonartk/py.typed +0 -0
  29. sonartk/sound/__init__.py +10 -0
  30. sonartk/sound/openal_lite/__init__.py +39 -0
  31. sonartk/sound/openal_lite/openal.py +5402 -0
  32. sonartk/sound/player_pool.py +93 -0
  33. sonartk/sound/sound_manager.py +473 -0
  34. sonartk/sound/sound_pool.py +158 -0
  35. sonartk/ui/__init__.py +22 -0
  36. sonartk/ui/element/__init__.py +21 -0
  37. sonartk/ui/element/button.py +36 -0
  38. sonartk/ui/element/checkbox.py +58 -0
  39. sonartk/ui/element/element.py +114 -0
  40. sonartk/ui/element/grid.py +274 -0
  41. sonartk/ui/element/menu.py +239 -0
  42. sonartk/ui/element/text_box.py +742 -0
  43. sonartk/ui/element/text_label.py +33 -0
  44. sonartk/ui/element/toggle_button.py +64 -0
  45. sonartk/ui/focusable_container.py +26 -0
  46. sonartk/ui/screen/__init__.py +5 -0
  47. sonartk/ui/screen/container_screen.py +65 -0
  48. sonartk/ui/screen/dialog.py +68 -0
  49. sonartk/ui/screen/screen.py +116 -0
  50. sonartk/ui/ui_component.py +80 -0
  51. sonartk/ui/window.py +411 -0
  52. sonartk/util/__init__.py +32 -0
  53. sonartk/util/key_handler.py +429 -0
  54. sonartk/util/speech_manager.py +234 -0
  55. sonartk/util/state.py +107 -0
  56. sonartk/util/state_machine.py +139 -0
  57. sonartk-0.1.dist-info/METADATA +426 -0
  58. sonartk-0.1.dist-info/RECORD +61 -0
  59. sonartk-0.1.dist-info/WHEEL +5 -0
  60. sonartk-0.1.dist-info/licenses/LICENSE +21 -0
  61. sonartk-0.1.dist-info/top_level.txt +1 -0
sonartk/__init__.py ADDED
@@ -0,0 +1,34 @@
1
+ from importlib import import_module
2
+ from typing import TYPE_CHECKING, Any
3
+
4
+ __all__ = [
5
+ "Window",
6
+ "MapGridGameBuilder",
7
+ "BuiltMapGridGame",
8
+ "MapSoundNavigationController",
9
+ ]
10
+
11
+ if TYPE_CHECKING:
12
+ from sonartk.orchestration.game_builder import (
13
+ BuiltMapGridGame,
14
+ MapGridGameBuilder,
15
+ )
16
+ from sonartk.orchestration.map_sound_navigation import (
17
+ MapSoundNavigationController,
18
+ )
19
+ from sonartk.ui.window import Window
20
+
21
+
22
+ def __getattr__(name: str) -> Any:
23
+ if name == "Window":
24
+ from sonartk.ui.window import Window
25
+
26
+ return Window
27
+ if name in {
28
+ "MapGridGameBuilder",
29
+ "BuiltMapGridGame",
30
+ "MapSoundNavigationController",
31
+ }:
32
+ orchestration = import_module("sonartk.orchestration")
33
+ return getattr(orchestration, name)
34
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@@ -0,0 +1,14 @@
1
+ from importlib import import_module
2
+ from typing import TYPE_CHECKING, Any
3
+
4
+ __all__ = ["Map2d", "MapTile", "load_2d_map"]
5
+
6
+ if TYPE_CHECKING:
7
+ from sonartk.map_builder.map_2d import Map2d, MapTile, load_2d_map
8
+
9
+
10
+ def __getattr__(name: str) -> Any:
11
+ if name in __all__:
12
+ module = import_module("sonartk.map_builder.map_2d")
13
+ return getattr(module, name)
14
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@@ -0,0 +1,25 @@
1
+ from sonartk.map_builder.map_2d.map_loader import load_2d_map
2
+ from sonartk.map_builder.map_2d.map_2d import Map2d
3
+ from sonartk.map_builder.map_2d.map_queries import (
4
+ Map2dQueries,
5
+ MapQueryResult,
6
+ PathfindingResult,
7
+ PathfindingState,
8
+ )
9
+ from sonartk.map_builder.map_2d.map_tile import MapTile
10
+ from sonartk.map_builder.map_2d.direction_tools import (
11
+ direction_offset,
12
+ step_coordinates,
13
+ )
14
+
15
+ __all__ = [
16
+ "Map2d",
17
+ "Map2dQueries",
18
+ "MapQueryResult",
19
+ "PathfindingResult",
20
+ "PathfindingState",
21
+ "MapTile",
22
+ "direction_offset",
23
+ "step_coordinates",
24
+ "load_2d_map",
25
+ ]
@@ -0,0 +1,29 @@
1
+ from sonartk.util import Coordinates, Direction
2
+
3
+
4
+ def direction_offset(direction: Direction) -> tuple[int, int]:
5
+ """Return the coordinate delta for a Direction value."""
6
+ direction_to_offset: dict[Direction, tuple[int, int]] = {
7
+ Direction.UP: (0, 1),
8
+ Direction.DIAGONAL_UPPER_RIGHT: (1, 1),
9
+ Direction.RIGHT: (1, 0),
10
+ Direction.DIAGONAL_LOWER_RIGHT: (1, -1),
11
+ Direction.DOWN: (0, -1),
12
+ Direction.DIAGONAL_LOWER_LEFT: (-1, -1),
13
+ Direction.LEFT: (-1, 0),
14
+ Direction.DIAGONAL_UPPER_LEFT: (-1, 1),
15
+ }
16
+ return direction_to_offset[direction]
17
+
18
+
19
+ def step_coordinates(
20
+ origin: Coordinates,
21
+ direction: Direction,
22
+ distance: int = 1,
23
+ ) -> Coordinates:
24
+ """Translate origin by direction and integer distance."""
25
+ if distance < 0:
26
+ raise ValueError("distance must be greater than or equal to 0")
27
+
28
+ dx, dy = direction_offset(direction)
29
+ return (origin[0] + (dx * distance), origin[1] + (dy * distance))
@@ -0,0 +1,257 @@
1
+ from types import MappingProxyType
2
+ from typing import Literal, Mapping, Optional
3
+
4
+ from sonartk.map_builder.map_2d.map_object.character import Character
5
+ from sonartk.map_builder.map_2d.map_tile import MapTile
6
+ from sonartk.map_builder.map_2d.map_object import MapObject
7
+ from sonartk.map_builder.map_2d.map_queries import Map2dQueries
8
+ from sonartk.util import Coordinates
9
+
10
+ CharacterCollisionPolicy = Literal["error", "replace"]
11
+ ObjectCollisionPolicy = Literal["error", "replace"]
12
+
13
+
14
+ class Map2d:
15
+ def __init__(
16
+ self,
17
+ name: str,
18
+ character: Character,
19
+ *,
20
+ character_collision_policy: CharacterCollisionPolicy = "error",
21
+ object_collision_policy: ObjectCollisionPolicy = "error",
22
+ ) -> None:
23
+ self.name: str = name
24
+ self.character: Character = character
25
+ self.tile_map: list[MapTile] = []
26
+ self.height: int = 0
27
+ self.width: int = 0
28
+ self.character_collision_policy = character_collision_policy
29
+ self.object_collision_policy = object_collision_policy
30
+ self._character_index: dict[Coordinates, Character] = {}
31
+ self._object_index: dict[Coordinates, MapObject] = {}
32
+ self._character_index_view: Mapping[Coordinates, Character] = (
33
+ MappingProxyType(self._character_index)
34
+ )
35
+ self._object_index_view: Mapping[Coordinates, MapObject] = (
36
+ MappingProxyType(self._object_index)
37
+ )
38
+ self.queries: Map2dQueries = Map2dQueries(self)
39
+ self.register_character(character)
40
+
41
+ @property
42
+ def character_index(self) -> Mapping[Coordinates, Character]:
43
+ """Read-only coordinate index for registered characters."""
44
+ return self._character_index_view
45
+
46
+ @property
47
+ def object_index(self) -> Mapping[Coordinates, MapObject]:
48
+ """Read-only coordinate index for registered map objects."""
49
+ return self._object_index_view
50
+
51
+ def add_row(self, row: list[MapTile]) -> None:
52
+ """Insert a map row and update map dimensions.
53
+
54
+ New rows are prepended so ``y=0`` points at the most recently inserted
55
+ row, matching the current map coordinate convention.
56
+ """
57
+ if self.tile_map and self.width != len(row):
58
+ raise IndexError(
59
+ f"The width of the new row must match the width of the map. Row Width: {len(row)} - Map Width: {self.width}"
60
+ )
61
+ if not self.tile_map:
62
+ self.width = len(row)
63
+
64
+ self.height += 1
65
+ self.tile_map[:0] = row
66
+
67
+ def get_tile(self, coordinates: Coordinates) -> MapTile:
68
+ """Return the tile at coordinates, raising on out-of-range access."""
69
+ x, y = coordinates
70
+ if x >= self.width or x < 0:
71
+ raise IndexError(
72
+ f"x value is out of bounds. x cannot be less than0 or greater than map width. x: {x} - map width: {self.width}"
73
+ )
74
+ if y >= self.height or y < 0:
75
+ raise IndexError(
76
+ f"y value is out of bounds. y cannot be less than 0 or greater than map height. y: {y} - map height: {self.height}"
77
+ )
78
+
79
+ return self.tile_map[y * self.width + x]
80
+
81
+ def set_tile(self, coordinates: Coordinates, tile: MapTile) -> None:
82
+ """Replace the tile at coordinates, raising on out-of-range access."""
83
+ x, y = coordinates
84
+ if x >= self.width or x < 0:
85
+ raise IndexError(
86
+ f"x value is out of bounds. x cannot be less than0 or greater than map width. x: {x} - map width: {self.width}"
87
+ )
88
+ if y >= self.height or y < 0:
89
+ raise IndexError(
90
+ f"y value is out of bounds. y cannot be less than 0 or greater than map height. y: {y} - map height: {self.height}"
91
+ )
92
+
93
+ self.tile_map[y * self.width + x] = tile
94
+
95
+ def set_tiles(self, updates: Mapping[Coordinates, MapTile]) -> None:
96
+ """Apply multiple tile replacements as a single convenience operation."""
97
+ for coordinates, tile in updates.items():
98
+ self.set_tile(coordinates, tile)
99
+
100
+ def register_character(self, character: Character) -> Optional[Character]:
101
+ """Register a character and return any displaced occupant at that coordinate."""
102
+ self._validate_coordinate_for_registration(character.coordinates)
103
+ displaced = self._ensure_character_slot_available(
104
+ character.coordinates, character
105
+ )
106
+ self._character_index[character.coordinates] = character
107
+ return displaced
108
+
109
+ def move_character(
110
+ self, character: Character, new_coordinates: Coordinates
111
+ ) -> Optional[Character]:
112
+ """Move a character and return any displaced occupant at destination."""
113
+ self._validate_coordinate_for_registration(new_coordinates)
114
+ current_coordinates = character.coordinates
115
+ if self._character_index.get(current_coordinates) is not character:
116
+ raise LookupError(
117
+ f"The character {character.name} is not registered at coordinates {current_coordinates}."
118
+ )
119
+
120
+ displaced = self._ensure_character_slot_available(
121
+ new_coordinates, character
122
+ )
123
+
124
+ del self._character_index[current_coordinates]
125
+ character.coordinates = new_coordinates
126
+ self._character_index[new_coordinates] = character
127
+ return displaced
128
+
129
+ def remove_character(self, character: Character) -> None:
130
+ """Remove a registered character from the coordinate index."""
131
+ current_coordinates = character.coordinates
132
+ if self._character_index.get(current_coordinates) is not character:
133
+ raise LookupError(
134
+ f"The character {character.name} is not registered at coordinates {current_coordinates}."
135
+ )
136
+
137
+ del self._character_index[current_coordinates]
138
+
139
+ def register_map_object(
140
+ self, map_object: MapObject
141
+ ) -> Optional[MapObject]:
142
+ """Register a map object and return any displaced occupant at that coordinate."""
143
+ self._validate_coordinate_for_registration(map_object.coordinates)
144
+ displaced = self._ensure_object_slot_available(
145
+ map_object.coordinates, map_object
146
+ )
147
+ self._object_index[map_object.coordinates] = map_object
148
+ return displaced
149
+
150
+ def move_map_object(
151
+ self, map_object: MapObject, new_coordinates: Coordinates
152
+ ) -> Optional[MapObject]:
153
+ """Move a map object and return any displaced occupant at destination."""
154
+ self._validate_coordinate_for_registration(new_coordinates)
155
+ current_coordinates = map_object.coordinates
156
+ if self._object_index.get(current_coordinates) is not map_object:
157
+ raise LookupError(
158
+ f"The map object {map_object.name} is not registered at coordinates {current_coordinates}."
159
+ )
160
+
161
+ displaced = self._ensure_object_slot_available(
162
+ new_coordinates, map_object
163
+ )
164
+
165
+ del self._object_index[current_coordinates]
166
+ map_object.coordinates = new_coordinates
167
+ self._object_index[new_coordinates] = map_object
168
+ return displaced
169
+
170
+ def remove_map_object(self, map_object: MapObject) -> None:
171
+ """Remove a registered map object from the coordinate index."""
172
+ current_coordinates = map_object.coordinates
173
+ if self._object_index.get(current_coordinates) is not map_object:
174
+ raise LookupError(
175
+ f"The map object {map_object.name} is not registered at coordinates {current_coordinates}."
176
+ )
177
+
178
+ del self._object_index[current_coordinates]
179
+
180
+ def find_map_object_coordinates(
181
+ self, map_object: MapObject
182
+ ) -> Optional[Coordinates]:
183
+ """Return coordinates for a registered map object by identity."""
184
+ for coordinates, registered_object in self._object_index.items():
185
+ if registered_object is map_object:
186
+ return coordinates
187
+
188
+ return None
189
+
190
+ def is_map_object_registered(self, map_object: MapObject) -> bool:
191
+ """Return whether a map object is currently registered by identity."""
192
+ return self.find_map_object_coordinates(map_object) is not None
193
+
194
+ def get_map_object_at(
195
+ self, coordinates: Coordinates
196
+ ) -> Optional[MapObject]:
197
+ """Return the registered map object at coordinates, if present."""
198
+ return self._object_index.get(coordinates)
199
+
200
+ def remove_map_object_at(
201
+ self, coordinates: Coordinates
202
+ ) -> Optional[MapObject]:
203
+ """Remove and return the map object at coordinates, if present."""
204
+ removed = self._object_index.get(coordinates)
205
+ if removed is None:
206
+ return None
207
+
208
+ del self._object_index[coordinates]
209
+ return removed
210
+
211
+ def _ensure_character_slot_available(
212
+ self, coordinates: Coordinates, character: Character
213
+ ) -> Optional[Character]:
214
+ existing = self._character_index.get(coordinates)
215
+ if existing is None or existing is character:
216
+ return None
217
+
218
+ if self.character_collision_policy == "replace":
219
+ del self._character_index[coordinates]
220
+ return existing
221
+
222
+ raise ValueError(
223
+ f"Character coordinate collision at {coordinates} for '{character.name}'."
224
+ )
225
+
226
+ def _validate_coordinate_for_registration(
227
+ self, coordinates: Coordinates
228
+ ) -> None:
229
+ """Validate coordinates when map dimensions are known.
230
+
231
+ Map entities are constructed before rows are loaded in many flows, so
232
+ bounds checks are deferred until width/height are available.
233
+ """
234
+ if self.width == 0 or self.height == 0:
235
+ return
236
+
237
+ x, y = coordinates
238
+ if x < 0 or y < 0 or x >= self.width or y >= self.height:
239
+ raise ValueError(
240
+ "Coordinates out of map bounds: "
241
+ f"{coordinates} for map size ({self.width}, {self.height})."
242
+ )
243
+
244
+ def _ensure_object_slot_available(
245
+ self, coordinates: Coordinates, map_object: MapObject
246
+ ) -> Optional[MapObject]:
247
+ existing = self._object_index.get(coordinates)
248
+ if existing is None or existing is map_object:
249
+ return None
250
+
251
+ if self.object_collision_policy == "replace":
252
+ del self._object_index[coordinates]
253
+ return existing
254
+
255
+ raise ValueError(
256
+ f"Map object coordinate collision at {coordinates} for '{map_object.name}'."
257
+ )
@@ -0,0 +1,33 @@
1
+ from typing import Callable, TypeVar
2
+
3
+ from sonartk.map_builder.map_2d.map_2d import Map2d
4
+ from sonartk.map_builder.map_2d.map_object.character import Character
5
+ from sonartk.map_builder.map_2d.map_tile import MapTile
6
+ from sonartk.map_builder.map_2d.parser.map_parser import MapParser
7
+ from sonartk.map_builder.map_2d.parser.stop_parsing_exception import (
8
+ StopParsingException,
9
+ )
10
+
11
+ T = TypeVar("T")
12
+
13
+
14
+ def load_2d_map(
15
+ map_name: str,
16
+ file_name: str,
17
+ parser: MapParser[T],
18
+ mapper: Callable[[T], MapTile],
19
+ character: Character,
20
+ ) -> Map2d:
21
+ """Load a 2D map file with a parser and convert each parsed entry to a MapTile."""
22
+ map2d: Map2d = Map2d(map_name, character)
23
+ with parser:
24
+ parser.open(file_name)
25
+ while True:
26
+ try:
27
+ row: list[T] = parser.read()
28
+ mapped_row: list[MapTile] = [mapper(tile) for tile in row]
29
+ map2d.add_row(mapped_row)
30
+ except StopParsingException:
31
+ break
32
+
33
+ return map2d
@@ -0,0 +1,4 @@
1
+ from sonartk.map_builder.map_2d.map_object.map_object import MapObject
2
+ from sonartk.map_builder.map_2d.map_object.character import Character
3
+
4
+ __all__ = ["MapObject", "Character"]
@@ -0,0 +1,15 @@
1
+ from sonartk.map_builder.map_2d.map_object import MapObject
2
+ from sonartk.util import Coordinates, Direction
3
+
4
+
5
+ class Character(MapObject):
6
+ def __init__(
7
+ self,
8
+ name: str,
9
+ coordinates: Coordinates,
10
+ directional_orientation: Direction,
11
+ radius: int = 5,
12
+ ) -> None:
13
+ super().__init__(name, coordinates)
14
+ self.directional_orientation: Direction = directional_orientation
15
+ self.radius: int = radius
@@ -0,0 +1,11 @@
1
+ from sonartk.util import Coordinates
2
+
3
+
4
+ class MapObject:
5
+ def __init__(self, name: str, coordinates: Coordinates) -> None:
6
+ self.name: str = name
7
+ self.coordinates: Coordinates = coordinates
8
+
9
+ def interact(self) -> None:
10
+ """Interact."""
11
+ pass