zombie-escape 1.13.1__py3-none-any.whl → 1.14.4__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 (41) hide show
  1. zombie_escape/__about__.py +1 -1
  2. zombie_escape/colors.py +7 -21
  3. zombie_escape/entities.py +100 -191
  4. zombie_escape/export_images.py +39 -33
  5. zombie_escape/gameplay/ambient.py +2 -6
  6. zombie_escape/gameplay/footprints.py +8 -11
  7. zombie_escape/gameplay/interactions.py +17 -58
  8. zombie_escape/gameplay/layout.py +20 -46
  9. zombie_escape/gameplay/movement.py +7 -21
  10. zombie_escape/gameplay/spawn.py +12 -40
  11. zombie_escape/gameplay/state.py +1 -0
  12. zombie_escape/gameplay/survivors.py +5 -16
  13. zombie_escape/gameplay/utils.py +4 -13
  14. zombie_escape/input_utils.py +8 -31
  15. zombie_escape/level_blueprints.py +112 -69
  16. zombie_escape/level_constants.py +8 -0
  17. zombie_escape/locales/ui.en.json +12 -0
  18. zombie_escape/locales/ui.ja.json +12 -0
  19. zombie_escape/localization.py +3 -11
  20. zombie_escape/models.py +26 -9
  21. zombie_escape/render/__init__.py +30 -0
  22. zombie_escape/render/core.py +992 -0
  23. zombie_escape/render/hud.py +444 -0
  24. zombie_escape/render/overview.py +218 -0
  25. zombie_escape/render/shadows.py +343 -0
  26. zombie_escape/render_assets.py +11 -33
  27. zombie_escape/rng.py +4 -8
  28. zombie_escape/screens/__init__.py +14 -30
  29. zombie_escape/screens/game_over.py +43 -15
  30. zombie_escape/screens/gameplay.py +41 -104
  31. zombie_escape/screens/settings.py +19 -104
  32. zombie_escape/screens/title.py +36 -176
  33. zombie_escape/stage_constants.py +192 -67
  34. zombie_escape/zombie_escape.py +1 -1
  35. {zombie_escape-1.13.1.dist-info → zombie_escape-1.14.4.dist-info}/METADATA +100 -39
  36. zombie_escape-1.14.4.dist-info/RECORD +53 -0
  37. zombie_escape/render.py +0 -1746
  38. zombie_escape-1.13.1.dist-info/RECORD +0 -49
  39. {zombie_escape-1.13.1.dist-info → zombie_escape-1.14.4.dist-info}/WHEEL +0 -0
  40. {zombie_escape-1.13.1.dist-info → zombie_escape-1.14.4.dist-info}/entry_points.txt +0 -0
  41. {zombie_escape-1.13.1.dist-info → zombie_escape-1.14.4.dist-info}/licenses/LICENSE.txt +0 -0
@@ -7,6 +7,84 @@ from .gameplay_constants import SURVIVOR_SPAWN_RATE
7
7
  from .level_constants import DEFAULT_GRID_COLS, DEFAULT_GRID_ROWS
8
8
  from .models import Stage
9
9
 
10
+
11
+ def _build_stage18_pitfall_zones(
12
+ *,
13
+ grid_cols: int,
14
+ grid_rows: int,
15
+ rooms_per_side: int,
16
+ room_size: int,
17
+ gap_width: int,
18
+ ) -> list[tuple[int, int, int, int]]:
19
+ pitfall_cells: set[tuple[int, int]] = set()
20
+
21
+ # Outer pitfall ring inside the outer wall band.
22
+ for y in range(2, grid_rows - 2):
23
+ pitfall_cells.add((2, y))
24
+ pitfall_cells.add((grid_cols - 3, y))
25
+ for x in range(2, grid_cols - 2):
26
+ pitfall_cells.add((x, 2))
27
+ pitfall_cells.add((x, grid_rows - 3))
28
+
29
+ # Room gap bands.
30
+ inner_start = 3
31
+ gap_cols: list[int] = []
32
+ gap_rows: list[int] = []
33
+ step = room_size + gap_width
34
+ for idx in range(1, rooms_per_side):
35
+ gap_start = inner_start + idx * step - gap_width
36
+ gap_cols.extend(range(gap_start, gap_start + gap_width))
37
+ gap_rows.extend(range(gap_start, gap_start + gap_width))
38
+ for x in gap_cols:
39
+ for y in range(3, grid_rows - 3):
40
+ pitfall_cells.add((x, y))
41
+ for y in gap_rows:
42
+ for x in range(3, grid_cols - 3):
43
+ pitfall_cells.add((x, y))
44
+
45
+ # Corridor openings (width 1) through the gap bands.
46
+ room_centers = [inner_start + (room_size // 2) + idx * step for idx in range(rooms_per_side)]
47
+ for y in room_centers:
48
+ for x in gap_cols:
49
+ pitfall_cells.discard((x, y))
50
+ for x in room_centers:
51
+ for y in gap_rows:
52
+ pitfall_cells.discard((x, y))
53
+
54
+ # Faux corridors through the outer pitfall ring.
55
+ for y in room_centers:
56
+ pitfall_cells.discard((2, y))
57
+ pitfall_cells.discard((grid_cols - 3, y))
58
+ for x in room_centers:
59
+ pitfall_cells.discard((x, 2))
60
+ pitfall_cells.discard((x, grid_rows - 3))
61
+
62
+ # Jagged room edges: every 2 tiles, let the room "bite" into pitfall bands.
63
+ for row in range(rooms_per_side):
64
+ for col in range(rooms_per_side):
65
+ start_x = 3 + col * (room_size + gap_width)
66
+ start_y = 3 + row * (room_size + gap_width)
67
+ for offset in range(0, room_size, 2):
68
+ x = start_x + offset
69
+ y = start_y + offset
70
+ pitfall_cells.discard((x, start_y - 1))
71
+ pitfall_cells.discard((x, start_y + room_size))
72
+ pitfall_cells.discard((start_x - 1, y))
73
+ pitfall_cells.discard((start_x + room_size, y))
74
+
75
+ pitfall_zones = [(x, y, 1, 1) for x, y in sorted(pitfall_cells)]
76
+ room_cells: set[tuple[int, int]] = set()
77
+ for row in range(rooms_per_side):
78
+ for col in range(rooms_per_side):
79
+ start_x = 3 + col * (room_size + gap_width)
80
+ start_y = 3 + row * (room_size + gap_width)
81
+ for y in range(start_y, start_y + room_size):
82
+ for x in range(start_x, start_x + room_size):
83
+ room_cells.add((x, y))
84
+
85
+ return pitfall_zones
86
+
87
+
10
88
  STAGES: list[Stage] = [
11
89
  Stage(
12
90
  id="stage1",
@@ -22,20 +100,20 @@ STAGES: list[Stage] = [
22
100
  description_key="stages.stage2.description",
23
101
  available=True,
24
102
  requires_fuel=True,
103
+ initial_interior_spawn_rate=0.007,
25
104
  exterior_spawn_weight=0.97,
26
105
  interior_spawn_weight=0.03,
27
- initial_interior_spawn_rate=0.007,
28
106
  ),
29
107
  Stage(
30
108
  id="stage3",
31
109
  name_key="stages.stage3.name",
32
110
  description_key="stages.stage3.description",
33
111
  available=True,
34
- buddy_required_count=1,
35
112
  requires_fuel=True,
113
+ buddy_required_count=1,
114
+ initial_interior_spawn_rate=0.007,
36
115
  exterior_spawn_weight=0.97,
37
116
  interior_spawn_weight=0.03,
38
- initial_interior_spawn_rate=0.007,
39
117
  ),
40
118
  Stage(
41
119
  id="stage4",
@@ -44,8 +122,8 @@ STAGES: list[Stage] = [
44
122
  available=True,
45
123
  rescue_stage=True,
46
124
  waiting_car_target_count=2,
47
- survivor_spawn_rate=SURVIVOR_SPAWN_RATE,
48
125
  initial_interior_spawn_rate=0.007,
126
+ survivor_spawn_rate=SURVIVOR_SPAWN_RATE,
49
127
  ),
50
128
  Stage(
51
129
  id="stage5",
@@ -56,9 +134,9 @@ STAGES: list[Stage] = [
56
134
  endurance_stage=True,
57
135
  endurance_goal_ms=1_200_000,
58
136
  fuel_spawn_count=0,
137
+ initial_interior_spawn_rate=0.04,
59
138
  exterior_spawn_weight=0.4,
60
139
  interior_spawn_weight=0.6,
61
- initial_interior_spawn_rate=0.04,
62
140
  ),
63
141
  Stage(
64
142
  id="stage6",
@@ -66,12 +144,12 @@ STAGES: list[Stage] = [
66
144
  description_key="stages.stage6.description",
67
145
  available=True,
68
146
  requires_fuel=True,
147
+ initial_interior_spawn_rate=0.01,
69
148
  exterior_spawn_weight=0.8,
70
149
  interior_spawn_weight=0.2,
71
- zombie_normal_ratio=0.4,
72
150
  zombie_tracker_ratio=0.6,
151
+ zombie_normal_ratio=0.4,
73
152
  zombie_aging_duration_frames=ZOMBIE_AGING_DURATION_FRAMES * 2,
74
- initial_interior_spawn_rate=0.01,
75
153
  ),
76
154
  Stage(
77
155
  id="stage7",
@@ -79,15 +157,15 @@ STAGES: list[Stage] = [
79
157
  description_key="stages.stage7.description",
80
158
  available=True,
81
159
  wall_algorithm="grid_wire",
82
- buddy_required_count=1,
83
160
  requires_fuel=True,
161
+ buddy_required_count=1,
162
+ initial_interior_spawn_rate=0.01,
84
163
  exterior_spawn_weight=0.7,
85
164
  interior_spawn_weight=0.3,
86
- zombie_normal_ratio=0.4,
87
165
  zombie_tracker_ratio=0.3,
88
166
  zombie_wall_hugging_ratio=0.3,
167
+ zombie_normal_ratio=0.4,
89
168
  zombie_aging_duration_frames=ZOMBIE_AGING_DURATION_FRAMES * 2,
90
- initial_interior_spawn_rate=0.01,
91
169
  ),
92
170
  Stage(
93
171
  id="stage8",
@@ -97,87 +175,87 @@ STAGES: list[Stage] = [
97
175
  tile_size=35,
98
176
  wall_algorithm="grid_wire",
99
177
  requires_fuel=True,
178
+ initial_interior_spawn_rate=0.01,
100
179
  exterior_spawn_weight=0.4,
101
180
  interior_spawn_weight=0.6,
102
- zombie_normal_ratio=0,
103
181
  zombie_tracker_ratio=0.3,
104
182
  zombie_wall_hugging_ratio=0.7,
183
+ zombie_normal_ratio=0,
105
184
  zombie_aging_duration_frames=ZOMBIE_AGING_DURATION_FRAMES * 2,
106
- initial_interior_spawn_rate=0.01,
107
185
  ),
108
186
  Stage(
109
187
  id="stage9",
110
188
  name_key="stages.stage9.name",
111
189
  description_key="stages.stage9.description",
112
190
  available=True,
113
- rescue_stage=True,
114
191
  tile_size=35,
115
192
  requires_fuel=True,
193
+ rescue_stage=True,
194
+ waiting_car_target_count=1,
195
+ initial_interior_spawn_rate=0.01,
116
196
  exterior_spawn_weight=0.4,
117
197
  interior_spawn_weight=0.6,
118
- waiting_car_target_count=1,
119
- zombie_normal_ratio=0,
120
198
  zombie_tracker_ratio=0.3,
121
199
  zombie_wall_hugging_ratio=0.7,
122
- survivor_spawn_rate=SURVIVOR_SPAWN_RATE,
200
+ zombie_normal_ratio=0,
123
201
  zombie_aging_duration_frames=ZOMBIE_AGING_DURATION_FRAMES * 2,
124
- initial_interior_spawn_rate=0.01,
202
+ survivor_spawn_rate=SURVIVOR_SPAWN_RATE,
125
203
  ),
126
204
  Stage(
127
205
  id="stage10",
128
206
  name_key="stages.stage10.name",
129
207
  description_key="stages.stage10.description",
130
208
  available=True,
131
- rescue_stage=True,
132
209
  tile_size=40,
133
210
  wall_algorithm="sparse_moore.10%",
211
+ rescue_stage=True,
212
+ waiting_car_target_count=1,
213
+ initial_interior_spawn_rate=0.02,
134
214
  exterior_spawn_weight=0.7,
135
215
  interior_spawn_weight=0.3,
136
- zombie_normal_ratio=0.4,
137
216
  zombie_tracker_ratio=0.4,
138
217
  zombie_wall_hugging_ratio=0.2,
218
+ zombie_normal_ratio=0.4,
139
219
  zombie_aging_duration_frames=ZOMBIE_AGING_DURATION_FRAMES * 2,
140
- initial_interior_spawn_rate=0.02,
141
- waiting_car_target_count=1,
142
220
  survivor_spawn_rate=0.35,
143
221
  ),
144
222
  Stage(
145
223
  id="stage11",
146
224
  name_key="stages.stage11.name",
147
225
  description_key="stages.stage11.description",
226
+ available=True,
148
227
  grid_cols=120,
149
228
  grid_rows=7,
150
- available=True,
151
229
  wall_algorithm="sparse_moore.10%",
230
+ initial_shoes_count=1,
231
+ waiting_car_target_count=1,
232
+ initial_interior_spawn_rate=0.1,
152
233
  exterior_spawn_weight=0.3,
153
234
  interior_spawn_weight=0.7,
154
- zombie_normal_ratio=0.5,
155
235
  zombie_tracker_ratio=0.5,
236
+ zombie_normal_ratio=0.5,
156
237
  zombie_aging_duration_frames=ZOMBIE_AGING_DURATION_FRAMES * 2,
157
- initial_interior_spawn_rate=0.1,
158
- waiting_car_target_count=1,
159
- initial_shoes_count=1,
160
238
  ),
161
239
  Stage(
162
240
  id="stage12",
163
241
  name_key="stages.stage12.name",
164
242
  description_key="stages.stage12.description",
243
+ available=True,
165
244
  grid_cols=32,
166
245
  grid_rows=32,
167
- available=True,
168
- requires_fuel=True,
169
- exterior_spawn_weight=0.5,
170
- interior_spawn_weight=0.2,
171
- interior_fall_spawn_weight=0.3,
172
246
  fall_spawn_zones=[
173
247
  (4, 4, 10, 10),
174
248
  (4, 18, 10, 10),
175
- (18, 18, 10, 10),
249
+ (18, 4, 10, 10),
176
250
  (18, 18, 10, 10),
177
251
  ],
252
+ requires_fuel=True,
178
253
  initial_flashlight_count=5,
179
- zombie_aging_duration_frames=ZOMBIE_AGING_DURATION_FRAMES * 2,
180
254
  initial_shoes_count=1,
255
+ exterior_spawn_weight=0.5,
256
+ interior_spawn_weight=0.2,
257
+ interior_fall_spawn_weight=0.3,
258
+ zombie_aging_duration_frames=ZOMBIE_AGING_DURATION_FRAMES * 2,
181
259
  ),
182
260
  Stage(
183
261
  id="stage13",
@@ -185,97 +263,144 @@ STAGES: list[Stage] = [
185
263
  description_key="stages.stage13.description",
186
264
  available=True,
187
265
  wall_algorithm="grid_wire",
188
- buddy_required_count=1,
266
+ fall_spawn_zones=[
267
+ (x, y, 2, 2) for y in range(2, DEFAULT_GRID_ROWS - 2, 4) for x in range(2, DEFAULT_GRID_COLS - 2, 4)
268
+ ],
189
269
  requires_fuel=True,
270
+ buddy_required_count=1,
271
+ initial_flashlight_count=3,
272
+ initial_shoes_count=1,
190
273
  exterior_spawn_weight=0.6,
191
274
  interior_spawn_weight=0.1,
192
275
  interior_fall_spawn_weight=0.3,
193
- zombie_normal_ratio=0.4,
194
276
  zombie_tracker_ratio=0.3,
195
277
  zombie_wall_hugging_ratio=0.3,
278
+ zombie_normal_ratio=0.4,
196
279
  zombie_aging_duration_frames=ZOMBIE_AGING_DURATION_FRAMES * 2,
197
- initial_flashlight_count=3,
198
- fall_spawn_zones=[
199
- (x, y, 2, 2)
200
- for y in range(2, DEFAULT_GRID_ROWS - 2, 4)
201
- for x in range(2, DEFAULT_GRID_COLS - 2, 4)
202
- ],
203
- initial_shoes_count=1,
204
280
  ),
205
281
  Stage(
206
282
  id="stage14",
207
283
  name_key="stages.stage14.name",
208
284
  description_key="stages.stage14.description",
285
+ available=True,
209
286
  grid_cols=34,
210
287
  grid_rows=20,
211
- available=True,
288
+ wall_rubble_ratio=0.35,
289
+ fall_spawn_floor_ratio=0.05,
212
290
  requires_fuel=True,
291
+ initial_flashlight_count=3,
292
+ initial_shoes_count=1,
213
293
  exterior_spawn_weight=0.2,
214
294
  interior_spawn_weight=0.1,
215
295
  interior_fall_spawn_weight=0.7,
216
- fall_spawn_floor_ratio=0.05,
217
- wall_rubble_ratio=0.35,
218
- initial_flashlight_count=3,
219
296
  zombie_aging_duration_frames=ZOMBIE_AGING_DURATION_FRAMES * 2,
220
- initial_shoes_count=1,
221
297
  ),
222
298
  Stage(
223
299
  id="stage15",
224
300
  name_key="stages.stage15.name",
225
301
  description_key="stages.stage15.description",
226
302
  available=True,
227
- buddy_required_count=1,
303
+ tile_size=35,
228
304
  grid_cols=64,
229
305
  grid_rows=24,
230
- tile_size=35,
231
306
  wall_algorithm="grid_wire",
307
+ fall_spawn_zones=[
308
+ (33, 2, 4, 18),
309
+ ],
232
310
  requires_fuel=True,
311
+ buddy_required_count=1,
312
+ initial_flashlight_count=3,
313
+ initial_shoes_count=1,
233
314
  initial_interior_spawn_rate=0.02,
234
315
  exterior_spawn_weight=0.2,
235
316
  interior_spawn_weight=0.1,
236
317
  interior_fall_spawn_weight=0.7,
237
- initial_flashlight_count=3,
238
- zombie_normal_ratio=0.5,
239
318
  zombie_wall_hugging_ratio=0.5,
240
- fall_spawn_zones=[
241
- (33, 2, 4, 18),
242
- ],
319
+ zombie_normal_ratio=0.5,
243
320
  zombie_aging_duration_frames=ZOMBIE_AGING_DURATION_FRAMES * 2,
244
- initial_shoes_count=1,
245
321
  ),
246
322
  Stage(
247
323
  id="stage16",
248
324
  name_key="stages.stage16.name",
249
325
  description_key="stages.stage16.description",
250
326
  available=True,
251
- requires_fuel=True,
252
- wall_algorithm="sparse_moore.25%",
327
+ tile_size=60,
253
328
  grid_cols=40,
254
329
  grid_rows=25,
255
- tile_size=60,
330
+ wall_algorithm="sparse_moore.25%",
256
331
  pitfall_density=0.04,
332
+ requires_fuel=True,
257
333
  initial_flashlight_count=1,
258
334
  initial_shoes_count=1,
259
- initial_interior_spawn_rate=0.12,
260
- exterior_spawn_weight=0.1,
261
- interior_spawn_weight=0.9,
335
+ initial_interior_spawn_rate=0.05,
336
+ exterior_spawn_weight=0.7,
337
+ interior_spawn_weight=0.3,
262
338
  zombie_aging_duration_frames=ZOMBIE_AGING_DURATION_FRAMES * 2,
263
339
  ),
264
340
  Stage(
265
341
  id="stage17",
266
342
  name_key="stages.stage17.name",
267
343
  description_key="stages.stage17.description",
268
- available=False,
344
+ available=True,
345
+ grid_cols=40,
346
+ grid_rows=26,
347
+ wall_algorithm="sparse_moore.25%",
348
+ wall_rubble_ratio=0.25,
349
+ pitfall_density=0.08,
269
350
  requires_fuel=True,
270
- wall_algorithm="grid_wire",
271
- pitfall_density=0.04,
272
351
  initial_flashlight_count=1,
273
352
  initial_shoes_count=1,
274
353
  initial_interior_spawn_rate=0.1,
275
- exterior_spawn_weight=0.1,
276
- interior_spawn_weight=0.9,
354
+ exterior_spawn_weight=0.5,
355
+ interior_spawn_weight=0.5,
277
356
  zombie_tracker_ratio=1.0,
278
- wall_rubble_ratio=0.25,
357
+ zombie_aging_duration_frames=ZOMBIE_AGING_DURATION_FRAMES * 2,
358
+ ),
359
+ Stage(
360
+ id="stage18",
361
+ name_key="stages.stage18.name",
362
+ description_key="stages.stage18.description",
363
+ available=True,
364
+ grid_cols=36,
365
+ grid_rows=36,
366
+ wall_algorithm="sparse_ortho.30%",
367
+ wall_rubble_ratio=0.15,
368
+ fall_spawn_floor_ratio=0.03,
369
+ pitfall_zones=_build_stage18_pitfall_zones(
370
+ grid_cols=36,
371
+ grid_rows=36,
372
+ rooms_per_side=3,
373
+ room_size=8,
374
+ gap_width=3,
375
+ ),
376
+ requires_fuel=True,
377
+ initial_interior_spawn_rate=0.08,
378
+ exterior_spawn_weight=0.6,
379
+ interior_spawn_weight=0.4,
380
+ zombie_tracker_ratio=0.5,
381
+ zombie_normal_ratio=0.5,
382
+ zombie_aging_duration_frames=ZOMBIE_AGING_DURATION_FRAMES * 2,
383
+ ),
384
+ Stage(
385
+ id="stage19",
386
+ name_key="stages.stage19.name",
387
+ description_key="stages.stage19.description",
388
+ available=False,
389
+ grid_cols=35,
390
+ grid_rows=35,
391
+ tile_size=35,
392
+ wall_algorithm="grid_wire.170%",
393
+ fall_spawn_floor_ratio=0.02,
394
+ pitfall_density=0.008,
395
+ requires_fuel=True,
396
+ buddy_required_count=1,
397
+ initial_interior_spawn_rate=0.08,
398
+ exterior_spawn_weight=0.4,
399
+ interior_spawn_weight=0.0,
400
+ interior_fall_spawn_weight=0.6,
401
+ zombie_tracker_ratio=0.5,
402
+ zombie_wall_hugging_ratio=0.5,
403
+ zombie_normal_ratio=0,
279
404
  zombie_aging_duration_frames=ZOMBIE_AGING_DURATION_FRAMES * 2,
280
405
  ),
281
406
  ]
@@ -61,7 +61,7 @@ def _parse_cli_args(argv: list[str]) -> Tuple[argparse.Namespace, list[str]]:
61
61
  parser.add_argument(
62
62
  "--export-images",
63
63
  action="store_true",
64
- help="Export documentation images to imgs/exports and exit",
64
+ help="Export documentation images to imgs/exports at 4x size and exit",
65
65
  )
66
66
  parser.add_argument("--seed")
67
67
  return parser.parse_known_args(argv)
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: zombie-escape
3
- Version: 1.13.1
3
+ Version: 1.14.4
4
4
  Summary: Top-down zombie survival game built with pygame.
5
5
  Project-URL: Homepage, https://github.com/tos-kamiya/zombie-escape
6
6
  Author-email: Toshihiro Kamiya <kamiya@mbj.nifty.com>
7
7
  License-Expression: MIT
8
8
  License-File: LICENSE.txt
9
- Classifier: Development Status :: 4 - Beta
9
+ Classifier: Development Status :: 5 - Production/Stable
10
10
  Classifier: License :: OSI Approved :: MIT License
11
11
  Classifier: Programming Language :: Python
12
12
  Classifier: Programming Language :: Python :: 3.10
@@ -99,49 +99,110 @@ Open **Settings** from the title to toggle gameplay assists:
99
99
 
100
100
  #### Characters
101
101
 
102
- | Name | Image | Notes |
103
- | --- | --- | --- |
104
- | Player | <img src="https://raw.githubusercontent.com/tos-kamiya/zombie-escape/main/imgs/exports/player.png" width="64"> | Blue circle with small hands. |
105
- | Zombie (Normal) | <img src="https://raw.githubusercontent.com/tos-kamiya/zombie-escape/main/imgs/exports/zombie-normal.png" width="64"> | Chases the player once detected. |
106
- | Car | <img src="https://raw.githubusercontent.com/tos-kamiya/zombie-escape/main/imgs/exports/car.png" width="64"> | Driveable escape vehicle with durability. |
107
- | Buddy (Stage 3) | <img src="https://raw.githubusercontent.com/tos-kamiya/zombie-escape/main/imgs/exports/buddy.png" width="64"> | A green survivor you can rescue. |
108
- | Survivors (Stage 4) | <img src="https://raw.githubusercontent.com/tos-kamiya/zombie-escape/main/imgs/exports/survivor.png" width="64"> | Civilians to evacuate by car. |
109
-
110
- - **Player:** Controlled with the WASD or arrow keys. When carrying fuel a tiny yellow square appears near the sprite so you can immediately see whether you're ready to drive.
111
- - **Zombie:** When out of sight, the zombie's movement mode will randomly switch every certain time (moving horizontally/vertically only, side-to-side movement, random movement, etc.).
112
- - **Car:**
113
- - The car has durability. Durability decreases when colliding with internal walls or hitting zombies.
114
- - If durability reaches 0, the car is destroyed and you are dumped on foot; you must track down another parked car hidden in the level.
115
- - When you're already driving, ramming a parked car instantly restores your current car's health. On Stage 4 this also increases the safe passenger limit by five.
116
- - After roughly 5 minutes of play, a small triangle near the player points toward the objective: fuel first (Stage 2 before pickup), car after fuel is collected (Stage 2/3), or car directly (Stage 1/4).
117
- - **Buddy (Stage 3):**
118
- - Zombies only choose to pursue the buddy if they are on-screen; otherwise they ignore them.
119
- - If a zombie tags the buddy off-screen, the buddy quietly respawns somewhere else instead of ending the run.
120
- - Touch the buddy on foot to make them follow you (at 70% of player speed). Touch them while driving to pick them up.
121
- - If you bash an inner wall or steel beam, the buddy will drift toward that spot and help chip away at it.
122
- - **Survivors (Stage 4):**
123
- - They stand still until you get close, then shuffle toward you at about one-third of player speed.
124
- - Zombies can convert them if both are on-screen; the survivor shouts a line and turns instantly.
125
- - They only board the car; your safe capacity starts at five but grows by five each time you sideswipe a parked car while already driving. Speed loss is based on how full the car is relative to that capacity, so extra slots mean quicker getaways.
102
+ <table>
103
+ <colgroup>
104
+ <col style="width:20%">
105
+ <col>
106
+ <col>
107
+ </colgroup>
108
+ <thead>
109
+ <tr>
110
+ <th>Name</th>
111
+ <th>Image</th>
112
+ <th>Notes</th>
113
+ </tr>
114
+ </thead>
115
+ <tbody>
116
+ <tr>
117
+ <td>Player</td>
118
+ <td><img src="https://raw.githubusercontent.com/tos-kamiya/zombie-escape/main/imgs/exports/player.png" width="64"></td>
119
+ <td>Blue circle with small hands; controlled with WASD/arrow keys. When carrying fuel, a tiny yellow square appears near the sprite.</td>
120
+ </tr>
121
+ <tr>
122
+ <td>Zombie (Normal)</td>
123
+ <td><img src="https://raw.githubusercontent.com/tos-kamiya/zombie-escape/main/imgs/exports/zombie-normal.png" width="64"></td>
124
+ <td>Chases the player once detected; out of sight it periodically switches movement modes.</td>
125
+ </tr>
126
+ <tr>
127
+ <td>Car</td>
128
+ <td><img src="https://raw.githubusercontent.com/tos-kamiya/zombie-escape/main/imgs/exports/car.png" width="64"></td>
129
+ <td>Driveable escape vehicle with durability; wall hits and zombie collisions reduce health. If it breaks, you're on foot until you find another car. Ramming a parked car restores health (and in Stage 4 increases safe passenger capacity). After ~5 minutes, a small triangle points to the current objective.</td>
130
+ </tr>
131
+ <tr>
132
+ <td>Buddy (Stage 3)</td>
133
+ <td><img src="https://raw.githubusercontent.com/tos-kamiya/zombie-escape/main/imgs/exports/buddy.png" width="64"></td>
134
+ <td>Green survivor you can rescue; zombies only target them on-screen and off-screen catches just respawn them. Touch on foot to follow (70% speed), touch while driving to pick up. Helps chip away at walls you bash.</td>
135
+ </tr>
136
+ <tr>
137
+ <td>Survivors (Stage 4)</td>
138
+ <td><img src="https://raw.githubusercontent.com/tos-kamiya/zombie-escape/main/imgs/exports/survivor.png" width="64"></td>
139
+ <td>Civilians to evacuate by car; they idle until approached, then follow at ~1/3 speed. On-screen zombie contact converts them. They only board cars; safe capacity starts at five and grows by five when you sideswipe parked cars, with speed loss based on how full the car is.</td>
140
+ </tr>
141
+ </tbody>
142
+ </table>
126
143
 
127
144
  #### Items
128
145
 
129
- | Name | Image | Notes |
130
- | --- | --- | --- |
131
- | Flashlight | <img src="https://raw.githubusercontent.com/tos-kamiya/zombie-escape/main/imgs/exports/flashlight.png" width="64"> | Each pickup expands your visible radius by about 20% (grab two to reach the max boost). |
132
- | Fuel Can (Stages 2 & 3) | <img src="https://raw.githubusercontent.com/tos-kamiya/zombie-escape/main/imgs/exports/fuel.png" width="64"> | Must be collected before driving the car in fuel-run stages. |
133
- | Steel Beam (optional) | <img src="https://raw.githubusercontent.com/tos-kamiya/zombie-escape/main/imgs/exports/steel-beam.png" width="64"> | Same collision as inner walls but with triple durability. |
146
+ <table>
147
+ <colgroup>
148
+ <col style="width:20%">
149
+ <col>
150
+ <col>
151
+ </colgroup>
152
+ <thead>
153
+ <tr>
154
+ <th>Name</th>
155
+ <th>Image</th>
156
+ <th>Notes</th>
157
+ </tr>
158
+ </thead>
159
+ <tbody>
160
+ <tr>
161
+ <td>Flashlight</td>
162
+ <td><img src="https://raw.githubusercontent.com/tos-kamiya/zombie-escape/main/imgs/exports/flashlight.png" width="64"></td>
163
+ <td>Each pickup expands your visible radius by about 20% (grab two to reach the max boost).</td>
164
+ </tr>
165
+ <tr>
166
+ <td>Fuel Can (Stages 2 & 3)</td>
167
+ <td><img src="https://raw.githubusercontent.com/tos-kamiya/zombie-escape/main/imgs/exports/fuel.png" width="64"></td>
168
+ <td>Must be collected before driving the car in fuel-run stages.</td>
169
+ </tr>
170
+ <tr>
171
+ <td>Steel Beam (optional)</td>
172
+ <td><img src="https://raw.githubusercontent.com/tos-kamiya/zombie-escape/main/imgs/exports/steel-beam.png" width="64"></td>
173
+ <td>Same collision as inner walls but with 1.5x durability.</td>
174
+ </tr>
175
+ </tbody>
176
+ </table>
134
177
 
135
178
  #### Environment
136
179
 
137
- | Name | Image | Notes |
138
- | --- | --- | --- |
139
- | Outer Wall | <img src="https://raw.githubusercontent.com/tos-kamiya/zombie-escape/main/imgs/exports/wall-outer.png" width="64"> | Nearly indestructible; each side has exits. |
140
- | Inner Wall | <img src="https://raw.githubusercontent.com/tos-kamiya/zombie-escape/main/imgs/exports/wall-inner.png" width="64"> | Breakable by the player; zombies chip away slowly. |
141
-
142
- - **Walls:** Outer walls are gray; inner walls are beige.
143
- - **Outer Walls:** Walls surrounding the stage that are nearly indestructible. Each side has at least three openings (exits).
144
- - **Inner Walls:** Beige walls randomly placed inside the building. Inner wall segments each have durability. **The player can break these walls** by repeatedly colliding with a segment to reduce its durability; when it reaches 0, the segment is destroyed and disappears. Zombies can also wear down walls, but far more slowly. The car cannot break walls.
180
+ <table>
181
+ <colgroup>
182
+ <col style="width:20%">
183
+ <col>
184
+ <col>
185
+ </colgroup>
186
+ <thead>
187
+ <tr>
188
+ <th>Name</th>
189
+ <th>Image</th>
190
+ <th>Notes</th>
191
+ </tr>
192
+ </thead>
193
+ <tbody>
194
+ <tr>
195
+ <td>Outer Wall</td>
196
+ <td><img src="https://raw.githubusercontent.com/tos-kamiya/zombie-escape/main/imgs/exports/wall-outer.png" width="64"></td>
197
+ <td>Gray perimeter walls that are nearly indestructible; each side has a single opening (exit).</td>
198
+ </tr>
199
+ <tr>
200
+ <td>Inner Wall</td>
201
+ <td><img src="https://raw.githubusercontent.com/tos-kamiya/zombie-escape/main/imgs/exports/wall-inner.png" width="64"></td>
202
+ <td>Beige interior walls with durability. The player can break them by repeated collisions; zombies wear them down slowly; the car cannot break them.</td>
203
+ </tr>
204
+ </tbody>
205
+ </table>
145
206
 
146
207
  ### Win/Lose Conditions
147
208