zombie-escape 1.12.3__py3-none-any.whl → 1.13.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.
@@ -16,7 +16,7 @@
16
16
  "settings": "設定",
17
17
  "quit": "終了",
18
18
  "locked_suffix": "[Locked]",
19
- "window_hint": "[キーでウィンドウを小さく、]キーで大きく。\nF: フルスクリーン切替。\n(タイトル、設定画面のみ)",
19
+ "window_hint": "[キーでウィンドウを小さく、]キーで大きく。\nF: フルスクリーン切替。",
20
20
  "seed_label": "シード: %{value}",
21
21
  "seed_hint": "数字キーで入力、BSでクリア",
22
22
  "seed_empty": "(自動)",
@@ -25,6 +25,7 @@
25
25
  "resources": "リソース"
26
26
  },
27
27
  "readme": "README を開く",
28
+ "readme_stage6": "ステージ6以降の説明を開く",
28
29
  "hints": {
29
30
  "navigate": "上下: 項目選択",
30
31
  "page_switch": "左右: ステージ群切替",
@@ -33,7 +34,8 @@
33
34
  "option_help": {
34
35
  "settings": "設定画面を開いて言語や補助オプションを変更します。",
35
36
  "quit": "ゲームを終了します。",
36
- "readme": "GitHub の README ページをブラウザで開きます。"
37
+ "readme": "GitHub の README ページをブラウザで開きます。",
38
+ "readme_stage6": "ステージ6以降の説明ページをブラウザで開きます。"
37
39
  }
38
40
  },
39
41
  "stages": {
@@ -127,6 +129,10 @@
127
129
  "stage15": {
128
130
  "name": "#15 分断ライン",
129
131
  "description": "建物中央を分断する危険地帯。横断には注意。"
132
+ },
133
+ "stage16": {
134
+ "name": "#16 奈落",
135
+ "description": "崩落した床パネルを回り込んでゾンビを誘い込もう。"
130
136
  }
131
137
  },
132
138
  "status": {
@@ -198,10 +204,14 @@
198
204
  "game_over": {
199
205
  "win": "脱出成功!",
200
206
  "lose": "ゲームオーバー",
207
+ "fell_into_abyss": "奈落の底へ落ちてしまった...",
201
208
  "prompt": "ESC/Space/Select/South: タイトルへ R: 再挑戦",
202
209
  "scream": "ぎゃあーーー!!",
203
210
  "survivors_summary": "救出人数: %{count}",
204
211
  "endurance_duration": "逃げ延びた時間 %{time}"
212
+ },
213
+ "errors": {
214
+ "map_generation_failed": "マップの生成に失敗しました。シード値を変えるか、別のステージを選んでください。"
205
215
  }
206
216
  }
207
217
  }
zombie_escape/models.py CHANGED
@@ -31,13 +31,15 @@ class LevelLayout:
31
31
  walkable_cells: list[tuple[int, int]]
32
32
  outer_wall_cells: set[tuple[int, int]]
33
33
  wall_cells: set[tuple[int, int]]
34
+ pitfall_cells: set[tuple[int, int]]
35
+ car_walkable_cells: set[tuple[int, int]]
34
36
  fall_spawn_cells: set[tuple[int, int]]
35
37
  bevel_corners: dict[tuple[int, int], tuple[bool, bool, bool, bool]]
36
38
 
37
39
 
38
40
  @dataclass
39
41
  class FallingZombie:
40
- """Represents a zombie falling toward a target position."""
42
+ """Represents a zombie falling toward a target position or into a pit."""
41
43
 
42
44
  start_pos: tuple[int, int]
43
45
  target_pos: tuple[int, int]
@@ -46,8 +48,9 @@ class FallingZombie:
46
48
  fall_duration_ms: int
47
49
  dust_duration_ms: int
48
50
  tracker: bool
49
- wall_follower: bool
51
+ wall_hugging: bool
50
52
  dust_started: bool = False
53
+ mode: str = "spawn" # "spawn" (falling in) or "pitfall" (falling out)
51
54
 
52
55
 
53
56
  @dataclass
@@ -102,6 +105,7 @@ class ProgressState:
102
105
  last_zombie_spawn_time: int
103
106
  dawn_carbonized: bool
104
107
  debug_mode: bool
108
+ show_fps: bool
105
109
  falling_zombies: list[FallingZombie]
106
110
  falling_spawn_carry: int
107
111
  dust_rings: list[DustRing]
@@ -166,12 +170,14 @@ class Stage:
166
170
  interior_fall_spawn_weight: float = 0.0
167
171
  fall_spawn_zones: list[tuple[int, int, int, int]] = field(default_factory=list)
168
172
  fall_spawn_floor_ratio: float = 0.0
173
+ pitfall_density: float = 0.0
169
174
  zombie_tracker_ratio: float = 0.0
170
- zombie_wall_follower_ratio: float = 0.0
175
+ zombie_wall_hugging_ratio: float = 0.0
171
176
  zombie_normal_ratio: float = 1.0
172
177
  zombie_aging_duration_frames: int = ZOMBIE_AGING_DURATION_FRAMES
173
178
  waiting_car_target_count: int = 1
174
179
  wall_algorithm: str = "default"
180
+ wall_rubble_ratio: float = 0.0
175
181
 
176
182
  @property
177
183
  def name(self) -> str:
zombie_escape/render.py CHANGED
@@ -34,6 +34,8 @@ from .entities_constants import (
34
34
  FLASHLIGHT_WIDTH,
35
35
  FUEL_CAN_HEIGHT,
36
36
  FUEL_CAN_WIDTH,
37
+ INTERNAL_WALL_BEVEL_DEPTH,
38
+ JUMP_SHADOW_OFFSET,
37
39
  SHOES_HEIGHT,
38
40
  SHOES_WIDTH,
39
41
  ZOMBIE_RADIUS,
@@ -63,6 +65,11 @@ from .render_constants import (
63
65
  FALLING_ZOMBIE_COLOR,
64
66
  FLASHLIGHT_FOG_SCALE_ONE,
65
67
  FLASHLIGHT_FOG_SCALE_TWO,
68
+ PITFALL_ABYSS_COLOR,
69
+ PITFALL_EDGE_DEPTH_OFFSET,
70
+ PITFALL_EDGE_METAL_COLOR,
71
+ PITFALL_EDGE_STRIPE_COLOR,
72
+ PITFALL_EDGE_STRIPE_SPACING,
66
73
  PLAYER_SHADOW_ALPHA_MULT,
67
74
  PLAYER_SHADOW_RADIUS_MULT,
68
75
  SHADOW_MIN_RATIO,
@@ -314,9 +321,7 @@ def draw_level_overview(
314
321
  if flashlights:
315
322
  for flashlight in flashlights:
316
323
  if flashlight.alive():
317
- pygame.draw.rect(
318
- surface, YELLOW, flashlight.rect, border_radius=2
319
- )
324
+ pygame.draw.rect(surface, YELLOW, flashlight.rect, border_radius=2)
320
325
  pygame.draw.rect(
321
326
  surface, BLACK, flashlight.rect, width=2, border_radius=2
322
327
  )
@@ -587,6 +592,8 @@ def _draw_fall_whirlwind(
587
592
  camera: Camera,
588
593
  center: tuple[int, int],
589
594
  progress: float,
595
+ *,
596
+ scale: float = 1.0,
590
597
  ) -> None:
591
598
  base_alpha = FALLING_WHIRLWIND_COLOR[3]
592
599
  alpha = int(max(0, min(255, base_alpha * (1.0 - progress))))
@@ -598,8 +605,9 @@ def _draw_fall_whirlwind(
598
605
  FALLING_WHIRLWIND_COLOR[2],
599
606
  alpha,
600
607
  )
601
- swirl_radius = max(2, int(ZOMBIE_RADIUS * 1.1))
602
- offset = max(1, int(ZOMBIE_RADIUS * 0.6))
608
+ safe_scale = max(0.4, scale)
609
+ swirl_radius = max(2, int(ZOMBIE_RADIUS * 1.1 * safe_scale))
610
+ offset = max(1, int(ZOMBIE_RADIUS * 0.6 * safe_scale))
603
611
  size = swirl_radius * 4
604
612
  swirl = pygame.Surface((size, size), pygame.SRCALPHA)
605
613
  cx = cy = size // 2
@@ -632,22 +640,42 @@ def _draw_falling_fx(
632
640
  if now < fall_start:
633
641
  if flashlight_count > 0 and pre_fx_ms > 0:
634
642
  fx_progress = max(0.0, min(1.0, (now - fall.started_at_ms) / pre_fx_ms))
635
- _draw_fall_whirlwind(screen, camera, fall.target_pos, fx_progress)
643
+ # Make the premonition grow with the impending drop scale.
644
+ pre_scale = 1.0 + (0.9 * fx_progress)
645
+ _draw_fall_whirlwind(
646
+ screen,
647
+ camera,
648
+ fall.start_pos,
649
+ fx_progress,
650
+ scale=pre_scale,
651
+ )
636
652
  continue
637
653
  if now >= impact_at:
638
654
  continue
639
655
  fall_progress = max(0.0, min(1.0, (now - fall_start) / fall_duration_ms))
640
- eased = 1.0 - (1.0 - fall_progress) * (1.0 - fall_progress)
641
- x = fall.target_pos[0]
642
- y = int(fall.start_pos[1] + (fall.target_pos[1] - fall.start_pos[1]) * eased)
643
- world_rect = pygame.Rect(0, 0, ZOMBIE_RADIUS * 2, ZOMBIE_RADIUS * 2)
644
- world_rect.center = (x, y)
656
+
657
+ if getattr(fall, "mode", "spawn") == "pitfall":
658
+ scale = 1.0 - fall_progress
659
+ scale = scale * scale
660
+ y_offset = 0.0
661
+ else:
662
+ eased = 1.0 - (1.0 - fall_progress) * (1.0 - fall_progress)
663
+ scale = 2.0 - (1.0 * eased)
664
+ # Add an extra vertical drop from above (1.5x wall depth)
665
+ y_offset = -INTERNAL_WALL_BEVEL_DEPTH * 1.5 * (1.0 - eased)
666
+
667
+ radius = ZOMBIE_RADIUS * scale
668
+ cx = fall.target_pos[0]
669
+ cy = fall.target_pos[1] + ZOMBIE_RADIUS - radius + y_offset
670
+
671
+ world_rect = pygame.Rect(0, 0, radius * 2, radius * 2)
672
+ world_rect.center = (int(cx), int(cy))
645
673
  screen_rect = camera.apply_rect(world_rect)
646
674
  pygame.draw.circle(
647
675
  screen,
648
676
  FALLING_ZOMBIE_COLOR,
649
677
  screen_rect.center,
650
- ZOMBIE_RADIUS,
678
+ max(1, int(screen_rect.width / 2)),
651
679
  )
652
680
 
653
681
  for ring in list(dust_rings):
@@ -725,6 +753,7 @@ def _draw_status_bar(
725
753
  debug_mode: bool = False,
726
754
  zombie_group: sprite.Group | None = None,
727
755
  falling_spawn_carry: int | None = None,
756
+ show_fps: bool = False,
728
757
  fps: float | None = None,
729
758
  ) -> None:
730
759
  """Render a compact status bar with current config flags and stage info."""
@@ -766,14 +795,12 @@ def _draw_status_bar(
766
795
  zombies = [z for z in zombie_group if z.alive()]
767
796
  total = len(zombies)
768
797
  tracker = sum(1 for z in zombies if z.tracker)
769
- wall = sum(1 for z in zombies if z.wall_follower)
798
+ wall = sum(1 for z in zombies if z.wall_hugging)
770
799
  normal = max(0, total - tracker - wall)
771
- parts.append(f"Z:{total} N:{normal} T:{tracker} W:{wall}")
800
+ debug_counts = f"Z:{total} N:{normal} T:{tracker} W:{wall}"
772
801
  if falling_spawn_carry is not None:
773
- parts.append(f"C:{max(0, falling_spawn_carry)}")
774
- if fps is not None:
775
- parts.append(f"FPS:{fps:.1f}")
776
-
802
+ debug_counts = f"{debug_counts} C:{max(0, falling_spawn_carry)}"
803
+ parts.append(debug_counts)
777
804
  status_text = " | ".join(parts)
778
805
  color = LIGHT_GRAY
779
806
 
@@ -790,7 +817,7 @@ def _draw_status_bar(
790
817
  right=bar_rect.right - 12, centery=bar_rect.centery
791
818
  )
792
819
  screen.blit(seed_surface, seed_rect)
793
- if debug_mode and fps is not None:
820
+ if show_fps and fps is not None:
794
821
  fps_text = f"FPS:{fps:.1f}"
795
822
  fps_surface = font.render(fps_text, False, LIGHT_GRAY)
796
823
  fps_rect = fps_surface.get_rect(
@@ -810,6 +837,7 @@ def _draw_play_area(
810
837
  field_rect: pygame.Rect,
811
838
  outside_cells: set[tuple[int, int]],
812
839
  fall_spawn_cells: set[tuple[int, int]],
840
+ pitfall_cells: set[tuple[int, int]],
813
841
  ) -> tuple[int, int, int, int, set[tuple[int, int]]]:
814
842
  xs, ys, xe, ye = (
815
843
  field_rect.left,
@@ -865,6 +893,43 @@ def _draw_play_area(
865
893
  if sr.colliderect(screen.get_rect()):
866
894
  pygame.draw.rect(screen, palette.outside, sr)
867
895
  continue
896
+
897
+ if (x, y) in pitfall_cells:
898
+ lx, ly = (
899
+ x * assets.internal_wall_grid_snap,
900
+ y * assets.internal_wall_grid_snap,
901
+ )
902
+ r = pygame.Rect(
903
+ lx,
904
+ ly,
905
+ assets.internal_wall_grid_snap,
906
+ assets.internal_wall_grid_snap,
907
+ )
908
+ sr = camera.apply_rect(r)
909
+ if not sr.colliderect(screen.get_rect()):
910
+ continue
911
+ pygame.draw.rect(screen, PITFALL_ABYSS_COLOR, sr)
912
+
913
+ if (x, y - 1) not in pitfall_cells:
914
+ edge_h = max(
915
+ 1, INTERNAL_WALL_BEVEL_DEPTH - PITFALL_EDGE_DEPTH_OFFSET
916
+ )
917
+ pygame.draw.rect(
918
+ screen, PITFALL_EDGE_METAL_COLOR, (sr.x, sr.y, sr.w, edge_h)
919
+ )
920
+ for sx in range(
921
+ sr.x - edge_h, sr.right, PITFALL_EDGE_STRIPE_SPACING
922
+ ):
923
+ pygame.draw.line(
924
+ screen,
925
+ PITFALL_EDGE_STRIPE_COLOR,
926
+ (max(sr.x, sx), sr.y),
927
+ (min(sr.right - 1, sx + edge_h), sr.y + edge_h - 1),
928
+ width=2,
929
+ )
930
+
931
+ continue
932
+
868
933
  use_secondary = ((x // 2) + (y // 2)) % 2 == 0
869
934
  if (x, y) in fall_spawn_cells:
870
935
  color = (
@@ -1021,8 +1086,13 @@ def _draw_entity_shadows(
1021
1086
  else:
1022
1087
  offset_x = 0.0
1023
1088
  offset_y = 0.0
1089
+
1090
+ jump_dy = 0.0
1091
+ if getattr(entity, "is_jumping", False):
1092
+ jump_dy = JUMP_SHADOW_OFFSET
1093
+
1024
1094
  shadow_rect = shadow_surface.get_rect(
1025
- center=(int(cx + offset_x), int(cy + offset_y))
1095
+ center=(int(cx + offset_x), int(cy + offset_y + jump_dy))
1026
1096
  )
1027
1097
  shadow_screen_rect = camera.apply_rect(shadow_rect)
1028
1098
  if not shadow_screen_rect.colliderect(screen_rect):
@@ -1081,8 +1151,13 @@ def _draw_single_entity_shadow(
1081
1151
  else:
1082
1152
  offset_x = 0.0
1083
1153
  offset_y = 0.0
1154
+
1155
+ jump_dy = 0.0
1156
+ if getattr(entity, "is_jumping", False):
1157
+ jump_dy = JUMP_SHADOW_OFFSET
1158
+
1084
1159
  shadow_rect = shadow_surface.get_rect(
1085
- center=(int(cx + offset_x), int(cy + offset_y))
1160
+ center=(int(cx + offset_x), int(cy + offset_y + jump_dy))
1086
1161
  )
1087
1162
  shadow_screen_rect = camera.apply_rect(shadow_rect)
1088
1163
  if not shadow_screen_rect.colliderect(screen_rect):
@@ -1525,6 +1600,7 @@ def draw(
1525
1600
  field_rect,
1526
1601
  outside_cells,
1527
1602
  game_data.layout.fall_spawn_cells,
1603
+ game_data.layout.pitfall_cells,
1528
1604
  )
1529
1605
  shadow_layer = _get_shadow_layer(screen.get_size())
1530
1606
  shadow_layer.fill((0, 0, 0, 0))
@@ -1660,6 +1736,7 @@ def draw(
1660
1736
  debug_mode=state.debug_mode,
1661
1737
  zombie_group=zombie_group,
1662
1738
  falling_spawn_carry=state.falling_spawn_carry,
1739
+ show_fps=state.show_fps,
1663
1740
  fps=fps,
1664
1741
  )
1665
1742
  if do_flip:
@@ -18,6 +18,7 @@ from .colors import (
18
18
  EnvironmentPalette,
19
19
  get_environment_palette,
20
20
  )
21
+ from .entities_constants import INTERNAL_WALL_BEVEL_DEPTH
21
22
  from .render_constants import (
22
23
  ANGLE_BINS,
23
24
  BUDDY_COLOR,
@@ -61,6 +62,22 @@ _SURVIVOR_DIRECTIONAL_CACHE: dict[
61
62
  tuple[int, bool, bool, int], list[pygame.Surface]
62
63
  ] = {}
63
64
  _ZOMBIE_DIRECTIONAL_CACHE: dict[tuple[int, bool, int], list[pygame.Surface]] = {}
65
+ _RUBBLE_SURFACE_CACHE: dict[tuple, pygame.Surface] = {}
66
+
67
+ RUBBLE_ROTATION_DEG = 5.0
68
+ RUBBLE_OFFSET_RATIO = 0.06
69
+ RUBBLE_SCALE_RATIO = 0.9
70
+ RUBBLE_SHADOW_RATIO = 0.9
71
+
72
+
73
+ def _scale_color(
74
+ color: tuple[int, int, int], *, ratio: float
75
+ ) -> tuple[int, int, int]:
76
+ return tuple(max(0, min(255, int(c * ratio + 0.5))) for c in color)
77
+
78
+
79
+ def rubble_offset_for_size(size: int) -> int:
80
+ return max(1, int(round(size * RUBBLE_OFFSET_RATIO)))
64
81
 
65
82
 
66
83
  def angle_bin_from_vector(
@@ -706,6 +723,80 @@ def paint_wall_surface(
706
723
  _draw_face(surface)
707
724
 
708
725
 
726
+ def build_rubble_wall_surface(
727
+ size: int,
728
+ *,
729
+ fill_color: tuple[int, int, int],
730
+ border_color: tuple[int, int, int],
731
+ angle_deg: float,
732
+ offset_px: int | None = None,
733
+ scale_ratio: float = RUBBLE_SCALE_RATIO,
734
+ shadow_ratio: float = RUBBLE_SHADOW_RATIO,
735
+ bevel_depth: int = INTERNAL_WALL_BEVEL_DEPTH,
736
+ ) -> pygame.Surface:
737
+ offset_px = offset_px if offset_px is not None else rubble_offset_for_size(size)
738
+ safe_size = max(1, size)
739
+ base_size = max(1, int(round(safe_size * scale_ratio)))
740
+ tuned_bevel = min(bevel_depth, max(1, base_size // 2))
741
+ cache_key = (
742
+ safe_size,
743
+ fill_color,
744
+ border_color,
745
+ angle_deg,
746
+ offset_px,
747
+ scale_ratio,
748
+ shadow_ratio,
749
+ tuned_bevel,
750
+ )
751
+ cached = _RUBBLE_SURFACE_CACHE.get(cache_key)
752
+ if cached is not None:
753
+ return cached
754
+
755
+ top_surface = pygame.Surface((base_size, base_size), pygame.SRCALPHA)
756
+ paint_wall_surface(
757
+ top_surface,
758
+ fill_color=fill_color,
759
+ border_color=border_color,
760
+ bevel_depth=tuned_bevel,
761
+ bevel_mask=(False, False, False, False),
762
+ draw_bottom_side=False,
763
+ bottom_side_ratio=0.1,
764
+ side_shade_ratio=0.9,
765
+ )
766
+
767
+ shadow_fill = _scale_color(fill_color, ratio=shadow_ratio)
768
+ shadow_border = _scale_color(border_color, ratio=shadow_ratio)
769
+ shadow_surface = pygame.Surface((base_size, base_size), pygame.SRCALPHA)
770
+ paint_wall_surface(
771
+ shadow_surface,
772
+ fill_color=shadow_fill,
773
+ border_color=shadow_border,
774
+ bevel_depth=tuned_bevel,
775
+ bevel_mask=(False, False, False, False),
776
+ draw_bottom_side=False,
777
+ bottom_side_ratio=0.1,
778
+ side_shade_ratio=0.9,
779
+ )
780
+
781
+ if angle_deg:
782
+ top_surface = pygame.transform.rotate(top_surface, angle_deg)
783
+ shadow_surface = pygame.transform.rotate(shadow_surface, angle_deg)
784
+
785
+ final_surface = pygame.Surface((safe_size, safe_size), pygame.SRCALPHA)
786
+ center = final_surface.get_rect().center
787
+
788
+ shadow_rect = shadow_surface.get_rect(
789
+ center=(center[0] + offset_px, center[1] + offset_px)
790
+ )
791
+ final_surface.blit(shadow_surface, shadow_rect.topleft)
792
+
793
+ top_rect = top_surface.get_rect(center=center)
794
+ final_surface.blit(top_surface, top_rect.topleft)
795
+
796
+ _RUBBLE_SURFACE_CACHE[cache_key] = final_surface
797
+ return final_surface
798
+
799
+
709
800
  def paint_steel_beam_surface(
710
801
  surface: pygame.Surface,
711
802
  *,
@@ -788,6 +879,9 @@ __all__ = [
788
879
  "build_car_directional_surfaces",
789
880
  "paint_car_surface",
790
881
  "paint_wall_surface",
882
+ "build_rubble_wall_surface",
883
+ "rubble_offset_for_size",
884
+ "RUBBLE_ROTATION_DEG",
791
885
  "paint_steel_beam_surface",
792
886
  "build_fuel_can_surface",
793
887
  "build_flashlight_surface",
@@ -67,6 +67,15 @@ ENTITY_SHADOW_EDGE_SOFTNESS = 0.32
67
67
  PLAYER_SHADOW_RADIUS_MULT = 1.6
68
68
  PLAYER_SHADOW_ALPHA_MULT = 0.8
69
69
 
70
+ # --- Pitfall rendering ---
71
+ PITFALL_ABYSS_COLOR = (21, 20, 20)
72
+ PITFALL_SHADOW_RIM_COLOR = (38, 34, 34)
73
+ PITFALL_SHADOW_WIDTH = 6
74
+ PITFALL_EDGE_METAL_COLOR = (110, 110, 115)
75
+ PITFALL_EDGE_STRIPE_COLOR = (75, 75, 80)
76
+ PITFALL_EDGE_STRIPE_SPACING = 6
77
+ PITFALL_EDGE_DEPTH_OFFSET = 3
78
+
70
79
  FOG_RINGS = [
71
80
  FogRing(radius_factor=0.536, thickness=2),
72
81
  FogRing(radius_factor=0.645, thickness=3),
@@ -121,6 +130,13 @@ __all__ = [
121
130
  "ENTITY_SHADOW_EDGE_SOFTNESS",
122
131
  "PLAYER_SHADOW_RADIUS_MULT",
123
132
  "PLAYER_SHADOW_ALPHA_MULT",
133
+ "PITFALL_ABYSS_COLOR",
134
+ "PITFALL_SHADOW_RIM_COLOR",
135
+ "PITFALL_SHADOW_WIDTH",
136
+ "PITFALL_EDGE_METAL_COLOR",
137
+ "PITFALL_EDGE_STRIPE_COLOR",
138
+ "PITFALL_EDGE_STRIPE_SPACING",
139
+ "PITFALL_EDGE_DEPTH_OFFSET",
124
140
  "FLASHLIGHT_HATCH_EXTRA_SCALE",
125
141
  "build_render_assets",
126
142
  ]
@@ -18,8 +18,10 @@ from ..input_utils import is_confirm_event, is_select_event
18
18
  from ..screens import (
19
19
  ScreenID,
20
20
  ScreenTransition,
21
+ nudge_window_scale,
21
22
  present,
22
23
  sync_window_size,
24
+ toggle_fullscreen,
23
25
  )
24
26
  from ..gameplay_constants import SURVIVAL_FAKE_CLOCK_RATIO
25
27
 
@@ -174,6 +176,15 @@ def game_over_screen(
174
176
  sync_window_size(event, game_data=game_data)
175
177
  continue
176
178
  if event.type == pygame.KEYDOWN:
179
+ if event.key == pygame.K_LEFTBRACKET:
180
+ nudge_window_scale(0.5, game_data=game_data)
181
+ continue
182
+ if event.key == pygame.K_RIGHTBRACKET:
183
+ nudge_window_scale(2.0, game_data=game_data)
184
+ continue
185
+ if event.key == pygame.K_f:
186
+ toggle_fullscreen(game_data=game_data)
187
+ continue
177
188
  if event.key in (pygame.K_ESCAPE, pygame.K_SPACE):
178
189
  return ScreenTransition(ScreenID.TITLE)
179
190
  if event.key == pygame.K_r and stage is not None: