zombie-escape 1.5.4__py3-none-any.whl → 1.7.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 (37) hide show
  1. zombie_escape/__about__.py +1 -1
  2. zombie_escape/entities.py +501 -537
  3. zombie_escape/entities_constants.py +102 -0
  4. zombie_escape/gameplay/__init__.py +75 -2
  5. zombie_escape/gameplay/ambient.py +50 -0
  6. zombie_escape/gameplay/constants.py +46 -0
  7. zombie_escape/gameplay/footprints.py +60 -0
  8. zombie_escape/gameplay/interactions.py +354 -0
  9. zombie_escape/gameplay/layout.py +190 -0
  10. zombie_escape/gameplay/movement.py +220 -0
  11. zombie_escape/gameplay/spawn.py +618 -0
  12. zombie_escape/gameplay/state.py +137 -0
  13. zombie_escape/gameplay/survivors.py +306 -0
  14. zombie_escape/gameplay/utils.py +147 -0
  15. zombie_escape/gameplay_constants.py +0 -148
  16. zombie_escape/level_blueprints.py +123 -10
  17. zombie_escape/level_constants.py +6 -13
  18. zombie_escape/locales/ui.en.json +10 -1
  19. zombie_escape/locales/ui.ja.json +10 -1
  20. zombie_escape/models.py +15 -9
  21. zombie_escape/render.py +42 -27
  22. zombie_escape/render_assets.py +533 -23
  23. zombie_escape/render_constants.py +57 -22
  24. zombie_escape/rng.py +9 -9
  25. zombie_escape/screens/__init__.py +59 -29
  26. zombie_escape/screens/game_over.py +3 -3
  27. zombie_escape/screens/gameplay.py +45 -27
  28. zombie_escape/screens/title.py +5 -2
  29. zombie_escape/stage_constants.py +34 -1
  30. zombie_escape/zombie_escape.py +30 -12
  31. {zombie_escape-1.5.4.dist-info → zombie_escape-1.7.1.dist-info}/METADATA +1 -1
  32. zombie_escape-1.7.1.dist-info/RECORD +45 -0
  33. zombie_escape/gameplay/logic.py +0 -1917
  34. zombie_escape-1.5.4.dist-info/RECORD +0 -35
  35. {zombie_escape-1.5.4.dist-info → zombie_escape-1.7.1.dist-info}/WHEEL +0 -0
  36. {zombie_escape-1.5.4.dist-info → zombie_escape-1.7.1.dist-info}/entry_points.txt +0 -0
  37. {zombie_escape-1.5.4.dist-info → zombie_escape-1.7.1.dist-info}/licenses/LICENSE.txt +0 -0
@@ -2,177 +2,29 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from .screen_constants import FPS
6
-
7
5
  # --- Survival stage settings ---
8
6
  SURVIVAL_TIME_ACCEL_SUBSTEPS = 4
9
7
  SURVIVAL_TIME_ACCEL_MAX_SUBSTEP = 1.0 / 30.0
10
8
  SURVIVAL_FAKE_CLOCK_RATIO = 12.0 # 20 min -> 4 hr clock
11
- SURVIVAL_NEAR_SPAWN_MIN_DISTANCE = 140
12
- SURVIVAL_NEAR_SPAWN_MAX_DISTANCE = 280
13
- SURVIVAL_NEAR_SPAWN_CAMERA_MARGIN = 20
14
-
15
- # --- Player and buddy settings ---
16
- HUMANOID_RADIUS = 6
17
- HUMANOID_OUTLINE_COLOR = (0, 80, 200)
18
- HUMANOID_OUTLINE_WIDTH = 1
19
-
20
-
21
- def interaction_radius(width: float, height: float) -> float:
22
- """Approximate interaction reach for a humanoid and an object."""
23
- return HUMANOID_RADIUS + (width + height) / 4
24
-
25
-
26
- def car_body_radius(width: float, height: float) -> float:
27
- """Approximate car collision radius using only its own dimensions."""
28
- return min(width, height) / 2
29
-
30
-
31
- PLAYER_RADIUS = HUMANOID_RADIUS
32
- PLAYER_SPEED = 1.4
33
- FOV_RADIUS = 124 # approximate legacy FOV (80) * 1.55 cap
34
- BUDDY_RADIUS = HUMANOID_RADIUS
35
- BUDDY_FOLLOW_SPEED = PLAYER_SPEED * 0.7
36
- BUDDY_COLOR = (0, 180, 63)
37
9
 
38
10
  # --- Survivor settings (Stage 4) ---
39
- SURVIVOR_RADIUS = HUMANOID_RADIUS
40
- SURVIVOR_COLOR = (198, 198, 198)
41
- SURVIVOR_APPROACH_RADIUS = 48
42
- SURVIVOR_APPROACH_SPEED = PLAYER_SPEED * 0.35
43
11
  SURVIVOR_SPAWN_RATE = 0.07
44
- SURVIVOR_MAX_SAFE_PASSENGERS = 5
45
- SURVIVOR_SPEED_PENALTY_PER_PASSENGER = 0.08
46
- SURVIVOR_MIN_SPEED_FACTOR = 0.35
47
- SURVIVOR_OVERLOAD_DAMAGE_RATIO = 0.2
48
- SURVIVOR_MESSAGE_DURATION_MS = 2000
49
- SURVIVOR_CONVERSION_LINE_KEYS = [
50
- "stages.stage4.conversion_lines.line1",
51
- "stages.stage4.conversion_lines.line2",
52
- "stages.stage4.conversion_lines.line3",
53
- ]
54
- SURVIVOR_STAGE_WAITING_CAR_COUNT = 2
55
12
 
56
13
  # --- Flashlight settings ---
57
- FLASHLIGHT_WIDTH = 10
58
- FLASHLIGHT_HEIGHT = 8
59
14
  DEFAULT_FLASHLIGHT_SPAWN_COUNT = 2
60
15
 
61
- # --- Footprint settings (gameplay) ---
62
- FOOTPRINT_STEP_DISTANCE = 40
63
- FOOTPRINT_MAX = 320
64
-
65
16
  # --- Zombie settings ---
66
- ZOMBIE_RADIUS = HUMANOID_RADIUS
67
- ZOMBIE_SPEED = PLAYER_SPEED * 0.45
68
17
  ZOMBIE_SPAWN_DELAY_MS = 4000
69
- MAX_ZOMBIES = 400
70
- ZOMBIE_SPAWN_PLAYER_BUFFER = 140
71
- ZOMBIE_WANDER_INTERVAL_MS = 5000
72
- ZOMBIE_SIGHT_RANGE = FOV_RADIUS * 1.2
73
- ZOMBIE_TRACKER_SIGHT_RANGE = ZOMBIE_SIGHT_RANGE * 0.2
74
- FAST_ZOMBIE_BASE_SPEED = PLAYER_SPEED * 0.77
75
- ZOMBIE_SEPARATION_DISTANCE = ZOMBIE_RADIUS * 2.2
76
- ZOMBIE_AGING_DURATION_FRAMES = FPS * 60 * 6 # ~6 minutes at target framerate
77
- ZOMBIE_AGING_MIN_SPEED_RATIO = 0.3
78
- ZOMBIE_TRACKER_SCENT_RADIUS = 70
79
- ZOMBIE_TRACKER_WANDER_INTERVAL_MS = 2500
80
- ZOMBIE_TRACKER_AGING_DURATION_FRAMES = ZOMBIE_AGING_DURATION_FRAMES
81
- ZOMBIE_WALL_FOLLOW_SENSOR_DISTANCE = 24
82
- ZOMBIE_WALL_FOLLOW_PROBE_ANGLE_DEG = 45
83
- ZOMBIE_WALL_FOLLOW_PROBE_STEP = 2.0
84
- ZOMBIE_WALL_FOLLOW_TARGET_GAP = 4.0
85
- ZOMBIE_WALL_FOLLOW_LOST_WALL_MS = 2500
86
18
 
87
19
  # --- Car and fuel settings ---
88
- CAR_WIDTH = 15
89
- CAR_HEIGHT = 25
90
- CAR_SPEED = 2
91
- CAR_HEALTH = 20
92
- CAR_WALL_DAMAGE = 1
93
- CAR_ZOMBIE_DAMAGE = 1
94
20
  CAR_HINT_DELAY_MS_DEFAULT = 300000
95
- FUEL_CAN_WIDTH = 11
96
- FUEL_CAN_HEIGHT = 15
97
- FUEL_HINT_DURATION_MS = 1600
98
-
99
- # --- Wall and beam settings ---
100
- INTERNAL_WALL_HEALTH = 40 * 100
101
- INTERNAL_WALL_BEVEL_DEPTH = 14
102
- OUTER_WALL_HEALTH = 999999
103
- STEEL_BEAM_HEALTH = int(INTERNAL_WALL_HEALTH * 1.5)
104
- PLAYER_WALL_DAMAGE = 100
105
- ZOMBIE_WALL_DAMAGE = 1
106
21
 
107
22
  __all__ = [
108
23
  "SURVIVAL_TIME_ACCEL_SUBSTEPS",
109
24
  "SURVIVAL_TIME_ACCEL_MAX_SUBSTEP",
110
25
  "SURVIVAL_FAKE_CLOCK_RATIO",
111
- "SURVIVAL_NEAR_SPAWN_MIN_DISTANCE",
112
- "SURVIVAL_NEAR_SPAWN_MAX_DISTANCE",
113
- "SURVIVAL_NEAR_SPAWN_CAMERA_MARGIN",
114
- "HUMANOID_RADIUS",
115
- "HUMANOID_OUTLINE_COLOR",
116
- "HUMANOID_OUTLINE_WIDTH",
117
- "interaction_radius",
118
- "car_body_radius",
119
- "PLAYER_RADIUS",
120
- "PLAYER_SPEED",
121
- "FOV_RADIUS",
122
- "BUDDY_RADIUS",
123
- "BUDDY_FOLLOW_SPEED",
124
- "BUDDY_COLOR",
125
- "SURVIVOR_RADIUS",
126
- "SURVIVOR_COLOR",
127
- "SURVIVOR_APPROACH_RADIUS",
128
- "SURVIVOR_APPROACH_SPEED",
129
26
  "SURVIVOR_SPAWN_RATE",
130
- "SURVIVOR_MAX_SAFE_PASSENGERS",
131
- "SURVIVOR_SPEED_PENALTY_PER_PASSENGER",
132
- "SURVIVOR_MIN_SPEED_FACTOR",
133
- "SURVIVOR_OVERLOAD_DAMAGE_RATIO",
134
- "SURVIVOR_MESSAGE_DURATION_MS",
135
- "SURVIVOR_CONVERSION_LINE_KEYS",
136
- "SURVIVOR_STAGE_WAITING_CAR_COUNT",
137
- "FLASHLIGHT_WIDTH",
138
- "FLASHLIGHT_HEIGHT",
139
27
  "DEFAULT_FLASHLIGHT_SPAWN_COUNT",
140
- "FOOTPRINT_STEP_DISTANCE",
141
- "FOOTPRINT_MAX",
142
- "ZOMBIE_RADIUS",
143
- "ZOMBIE_SPEED",
144
28
  "ZOMBIE_SPAWN_DELAY_MS",
145
- "MAX_ZOMBIES",
146
- "ZOMBIE_SPAWN_PLAYER_BUFFER",
147
- "ZOMBIE_WANDER_INTERVAL_MS",
148
- "ZOMBIE_SIGHT_RANGE",
149
- "ZOMBIE_TRACKER_SIGHT_RANGE",
150
- "FAST_ZOMBIE_BASE_SPEED",
151
- "ZOMBIE_SEPARATION_DISTANCE",
152
- "ZOMBIE_AGING_DURATION_FRAMES",
153
- "ZOMBIE_AGING_MIN_SPEED_RATIO",
154
- "ZOMBIE_TRACKER_SCENT_RADIUS",
155
- "ZOMBIE_TRACKER_WANDER_INTERVAL_MS",
156
- "ZOMBIE_TRACKER_AGING_DURATION_FRAMES",
157
- "ZOMBIE_WALL_FOLLOW_SENSOR_DISTANCE",
158
- "ZOMBIE_WALL_FOLLOW_PROBE_ANGLE_DEG",
159
- "ZOMBIE_WALL_FOLLOW_PROBE_STEP",
160
- "ZOMBIE_WALL_FOLLOW_TARGET_GAP",
161
- "ZOMBIE_WALL_FOLLOW_LOST_WALL_MS",
162
- "CAR_WIDTH",
163
- "CAR_HEIGHT",
164
- "CAR_SPEED",
165
- "CAR_HEALTH",
166
- "CAR_WALL_DAMAGE",
167
- "CAR_ZOMBIE_DAMAGE",
168
29
  "CAR_HINT_DELAY_MS_DEFAULT",
169
- "FUEL_CAN_WIDTH",
170
- "FUEL_CAN_HEIGHT",
171
- "FUEL_HINT_DURATION_MS",
172
- "INTERNAL_WALL_HEALTH",
173
- "INTERNAL_WALL_BEVEL_DEPTH",
174
- "OUTER_WALL_HEALTH",
175
- "STEEL_BEAM_HEALTH",
176
- "PLAYER_WALL_DAMAGE",
177
- "ZOMBIE_WALL_DAMAGE",
178
30
  ]
@@ -1,6 +1,5 @@
1
1
  # Blueprint generator for randomized layouts.
2
2
 
3
- from .level_constants import GRID_COLS, GRID_ROWS
4
3
  from .rng import get_rng
5
4
 
6
5
  EXITS_PER_SIDE = 1 # currently fixed to 1 per side (can be tuned)
@@ -63,7 +62,7 @@ def _place_exits(grid: list[list[str]], exits_per_side: int) -> None:
63
62
  rng = RNG.randint
64
63
  used = set()
65
64
 
66
- def pick_pos(side: str) -> tuple[int, int]:
65
+ def _pick_pos(side: str) -> tuple[int, int]:
67
66
  if side in ("top", "bottom"):
68
67
  x = rng(2, cols - 3)
69
68
  y = 1 if side == "top" else rows - 2
@@ -74,17 +73,17 @@ def _place_exits(grid: list[list[str]], exits_per_side: int) -> None:
74
73
 
75
74
  for side in ("top", "bottom", "left", "right"):
76
75
  for _ in range(exits_per_side):
77
- x, y = pick_pos(side)
76
+ x, y = _pick_pos(side)
78
77
  # avoid duplicates; retry a few times
79
78
  for _ in range(10):
80
79
  if (x, y) not in used:
81
80
  break
82
- x, y = pick_pos(side)
81
+ x, y = _pick_pos(side)
83
82
  used.add((x, y))
84
83
  grid[y][x] = "E"
85
84
 
86
85
 
87
- def _place_internal_walls(grid: list[list[str]]) -> None:
86
+ def _place_walls_default(grid: list[list[str]]) -> None:
88
87
  cols, rows = len(grid[0]), len(grid)
89
88
  rng = RNG.randint
90
89
  # Avoid placing walls adjacent to exits: collect forbidden cells (exits + neighbors)
@@ -111,6 +110,104 @@ def _place_internal_walls(grid: list[list[str]]) -> None:
111
110
  grid[y + i][x] = "1"
112
111
 
113
112
 
113
+ def _place_walls_empty(grid: list[list[str]]) -> None:
114
+ """Place no internal walls (open floor plan)."""
115
+ pass
116
+
117
+
118
+ def _place_walls_grid_wire(grid: list[list[str]]) -> None:
119
+ """
120
+ Place walls using a 2-pass approach with independent layers.
121
+ Vertical and horizontal walls are generated on separate grids to ensure
122
+ one orientation doesn't block the other during generation.
123
+ Finally, they are merged.
124
+ Strictly forbids parallel adjacency within the same orientation layer.
125
+ """
126
+ cols, rows = len(grid[0]), len(grid)
127
+ rng = RNG.randint
128
+ forbidden = _collect_exit_adjacent_cells(grid)
129
+
130
+ # Temporary grids for independent generation
131
+ # They only track the internal walls ("1").
132
+ grid_v = [["." for _ in range(cols)] for _ in range(rows)]
133
+ grid_h = [["." for _ in range(cols)] for _ in range(rows)]
134
+
135
+ # Use a similar density to default.
136
+ lines_per_pass = int(NUM_WALL_LINES * 0.7)
137
+
138
+ # --- Pass 1: Vertical Walls (on grid_v) ---
139
+ for _ in range(lines_per_pass):
140
+ length = rng(WALL_MIN_LEN, WALL_MAX_LEN)
141
+ x = rng(2, cols - 3)
142
+ y = rng(2, rows - 2 - length)
143
+
144
+ can_place = True
145
+ for i in range(length):
146
+ cy = y + i
147
+ # 1. Global forbidden check (exits, outer walls in main grid)
148
+ if (x, cy) in forbidden:
149
+ can_place = False
150
+ break
151
+ if grid[cy][x] not in (".",):
152
+ can_place = False
153
+ break
154
+ # 2. Local self-overlap check
155
+ if grid_v[cy][x] != ".":
156
+ can_place = False
157
+ break
158
+ # 3. Parallel adjacency check (only against other vertical walls)
159
+ if grid_v[cy][x - 1] == "1" or grid_v[cy][x + 1] == "1":
160
+ can_place = False
161
+ break
162
+
163
+ if can_place:
164
+ for i in range(length):
165
+ grid_v[y + i][x] = "1"
166
+
167
+ # --- Pass 2: Horizontal Walls (on grid_h) ---
168
+ for _ in range(lines_per_pass):
169
+ length = rng(WALL_MIN_LEN, WALL_MAX_LEN)
170
+ x = rng(2, cols - 2 - length)
171
+ y = rng(2, rows - 3)
172
+
173
+ can_place = True
174
+ for i in range(length):
175
+ cx = x + i
176
+ # 1. Global forbidden check
177
+ if (cx, y) in forbidden:
178
+ can_place = False
179
+ break
180
+ if grid[y][cx] not in (".",):
181
+ can_place = False
182
+ break
183
+ # 2. Local self-overlap check
184
+ if grid_h[y][cx] != ".":
185
+ can_place = False
186
+ break
187
+ # 3. Parallel adjacency check (only against other horizontal walls)
188
+ if grid_h[y - 1][cx] == "1" or grid_h[y + 1][cx] == "1":
189
+ can_place = False
190
+ break
191
+
192
+ if can_place:
193
+ for i in range(length):
194
+ grid_h[y][x + i] = "1"
195
+
196
+ # --- Merge Phase ---
197
+ for y in range(rows):
198
+ for x in range(cols):
199
+ # If either layer has a wall, and the main grid is empty, place it.
200
+ if (grid_v[y][x] == "1" or grid_h[y][x] == "1") and grid[y][x] == ".":
201
+ grid[y][x] = "1"
202
+
203
+
204
+ WALL_ALGORITHMS = {
205
+ "default": _place_walls_default,
206
+ "empty": _place_walls_empty,
207
+ "grid_wire": _place_walls_grid_wire,
208
+ }
209
+
210
+
114
211
  def _place_steel_beams(
115
212
  grid: list[list[str]], *, chance: float = STEEL_BEAM_CHANCE
116
213
  ) -> set[tuple[int, int]]:
@@ -150,10 +247,22 @@ def _pick_empty_cell(
150
247
  return cols // 2, rows // 2
151
248
 
152
249
 
153
- def generate_random_blueprint(steel_chance: float) -> dict:
154
- grid = _init_grid(GRID_COLS, GRID_ROWS)
250
+ def _generate_random_blueprint(
251
+ steel_chance: float, *, cols: int, rows: int, wall_algo: str = "default"
252
+ ) -> dict:
253
+ grid = _init_grid(cols, rows)
155
254
  _place_exits(grid, EXITS_PER_SIDE)
156
- _place_internal_walls(grid)
255
+
256
+ # Select and run the wall placement algorithm
257
+ if wall_algo not in WALL_ALGORITHMS:
258
+ print(
259
+ f"WARNING: Unknown wall algorithm '{wall_algo}'. Falling back to 'default'."
260
+ )
261
+ wall_algo = "default"
262
+
263
+ algo_func = WALL_ALGORITHMS[wall_algo]
264
+ algo_func(grid)
265
+
157
266
  steel_beams = _place_steel_beams(grid, chance=steel_chance)
158
267
 
159
268
  # Spawns: player, car, zombies
@@ -169,11 +278,15 @@ def generate_random_blueprint(steel_chance: float) -> dict:
169
278
  return {"grid": blueprint_rows, "steel_cells": steel_beams}
170
279
 
171
280
 
172
- def choose_blueprint(config: dict) -> dict:
281
+ def choose_blueprint(
282
+ config: dict, *, cols: int, rows: int, wall_algo: str = "default"
283
+ ) -> dict:
173
284
  # Currently only random generation; hook for future variants.
174
285
  steel_conf = config.get("steel_beams", {})
175
286
  try:
176
287
  steel_chance = float(steel_conf.get("chance", STEEL_BEAM_CHANCE))
177
288
  except (TypeError, ValueError):
178
289
  steel_chance = STEEL_BEAM_CHANCE
179
- return generate_random_blueprint(steel_chance=steel_chance)
290
+ return _generate_random_blueprint(
291
+ steel_chance=steel_chance, cols=cols, rows=rows, wall_algo=wall_algo
292
+ )
@@ -2,19 +2,12 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- GRID_COLS = 48
6
- GRID_ROWS = 30
7
- TILE_SIZE = 50 # world units per cell; adjust to scale the whole map
8
-
9
- CELL_SIZE = TILE_SIZE
10
- LEVEL_WIDTH = GRID_COLS * CELL_SIZE
11
- LEVEL_HEIGHT = GRID_ROWS * CELL_SIZE
5
+ DEFAULT_GRID_COLS = 48
6
+ DEFAULT_GRID_ROWS = 30
7
+ DEFAULT_TILE_SIZE = 50 # world units per cell; adjust to scale the whole map
12
8
 
13
9
  __all__ = [
14
- "GRID_COLS",
15
- "GRID_ROWS",
16
- "TILE_SIZE",
17
- "CELL_SIZE",
18
- "LEVEL_WIDTH",
19
- "LEVEL_HEIGHT",
10
+ "DEFAULT_GRID_COLS",
11
+ "DEFAULT_GRID_ROWS",
12
+ "DEFAULT_TILE_SIZE",
20
13
  ]
@@ -9,7 +9,7 @@
9
9
  "fonts": {
10
10
  "primary": {
11
11
  "resource": "assets/fonts/Silkscreen-Regular.ttf",
12
- "scale": 0.7
12
+ "scale": 0.75
13
13
  }
14
14
  },
15
15
  "menu": {
@@ -69,6 +69,14 @@
69
69
  "stage7": {
70
70
  "name": "#7 Rescue Buddy 2",
71
71
  "description": "Rescue your buddy. Wall-hugging zombies appear."
72
+ },
73
+ "stage8": {
74
+ "name": "#8 Assembly Floor",
75
+ "description": "Narrow corridors. Get surrounded, and there’s no escape."
76
+ },
77
+ "stage9": {
78
+ "name": "# Evacuate Survivors 2",
79
+ "description": "Evacuate like stage 4, but narrow corridors. Requires fuel."
72
80
  }
73
81
  },
74
82
  "status": {
@@ -100,6 +108,7 @@
100
108
  "pickup_buddy": "Pick up your buddy",
101
109
  "find_buddy": "Find your buddy",
102
110
  "escape_with_buddy": "Get your buddy in the car and escape",
111
+ "board_buddy": "Get your buddy in the car",
103
112
  "buddy_onboard": "Buddy onboard: %{count}",
104
113
  "escape_with_survivors": "Escape with the survivors aboard",
105
114
  "survivors_onboard": "Survivors aboard: %{count}/%{limit}",
@@ -67,8 +67,16 @@
67
67
  "description": "ステージ2と同じだが、一部のゾンビが足跡を追う。"
68
68
  },
69
69
  "stage7": {
70
- "name": "#7 相棒を救え2",
70
+ "name": "#7 相棒を救え 2",
71
71
  "description": "はぐれた相棒を救え。壁沿いゾンビが出現。"
72
+ },
73
+ "stage8": {
74
+ "name": "#8 組立工程",
75
+ "description": "狭い通路。挟まれると逃げ場なし。"
76
+ },
77
+ "stage9": {
78
+ "name": "#9 生存者救出 2",
79
+ "description": "ステージ4と同じだが、狭い通路。燃料も必要。"
72
80
  }
73
81
  },
74
82
  "status": {
@@ -100,6 +108,7 @@
100
108
  "pickup_buddy": "相棒を救出する",
101
109
  "find_buddy": "相棒を探す",
102
110
  "escape_with_buddy": "相棒を車に乗せて脱出する",
111
+ "board_buddy": "相棒を車に乗せる",
103
112
  "buddy_onboard": "相棒乗車中: %{count}",
104
113
  "escape_with_survivors": "生存者を車に乗せて脱出する",
105
114
  "survivors_onboard": "乗車中: %{count}/%{limit}",
zombie_escape/models.py CHANGED
@@ -8,11 +8,9 @@ from typing import TYPE_CHECKING
8
8
  import pygame
9
9
  from pygame import sprite, surface
10
10
 
11
- from .gameplay_constants import (
12
- SURVIVOR_SPAWN_RATE,
13
- ZOMBIE_AGING_DURATION_FRAMES,
14
- ZOMBIE_SPAWN_DELAY_MS,
15
- )
11
+ from .entities_constants import ZOMBIE_AGING_DURATION_FRAMES
12
+ from .gameplay_constants import SURVIVOR_SPAWN_RATE, ZOMBIE_SPAWN_DELAY_MS
13
+ from .level_constants import DEFAULT_GRID_COLS, DEFAULT_GRID_ROWS
16
14
  from .localization import translate as tr
17
15
 
18
16
  if TYPE_CHECKING: # pragma: no cover - typing-only imports
@@ -20,8 +18,8 @@ if TYPE_CHECKING: # pragma: no cover - typing-only imports
20
18
 
21
19
 
22
20
  @dataclass
23
- class Areas:
24
- """Container for level area rectangles."""
21
+ class LevelLayout:
22
+ """Container for level layout rectangles and cell sets."""
25
23
 
26
24
  outer_rect: tuple[int, int, int, int]
27
25
  inner_rect: tuple[int, int, int, int]
@@ -83,9 +81,12 @@ class GameData:
83
81
  state: ProgressState
84
82
  groups: Groups
85
83
  camera: Camera
86
- areas: Areas
84
+ layout: LevelLayout
87
85
  fog: dict
88
86
  stage: Stage
87
+ cell_size: int
88
+ level_width: int
89
+ level_height: int
89
90
  fuel: FuelCan | None = None
90
91
  flashlights: list[Flashlight] | None = None
91
92
  player: Player | None = None
@@ -100,6 +101,9 @@ class Stage:
100
101
  name_key: str
101
102
  description_key: str
102
103
  available: bool = True
104
+ tile_size: int = 50
105
+ grid_cols: int = DEFAULT_GRID_COLS
106
+ grid_rows: int = DEFAULT_GRID_ROWS
103
107
  requires_fuel: bool = False
104
108
  buddy_required_count: int = 0
105
109
  rescue_stage: bool = False
@@ -115,6 +119,8 @@ class Stage:
115
119
  zombie_wall_follower_ratio: float = 0.0
116
120
  zombie_normal_ratio: float = 1.0
117
121
  zombie_aging_duration_frames: int = ZOMBIE_AGING_DURATION_FRAMES
122
+ waiting_car_target_count: int = 1
123
+ wall_algorithm: str = "default"
118
124
 
119
125
  @property
120
126
  def name(self) -> str:
@@ -126,7 +132,7 @@ class Stage:
126
132
 
127
133
 
128
134
  __all__ = [
129
- "Areas",
135
+ "LevelLayout",
130
136
  "ProgressState",
131
137
  "Groups",
132
138
  "GameData",