zombie-escape 1.14.4__py3-none-any.whl → 1.15.2__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 (42) hide show
  1. zombie_escape/__about__.py +1 -1
  2. zombie_escape/config.py +1 -0
  3. zombie_escape/entities.py +126 -199
  4. zombie_escape/entities_constants.py +11 -1
  5. zombie_escape/export_images.py +4 -4
  6. zombie_escape/font_utils.py +47 -0
  7. zombie_escape/gameplay/__init__.py +2 -1
  8. zombie_escape/gameplay/constants.py +4 -0
  9. zombie_escape/gameplay/interactions.py +83 -16
  10. zombie_escape/gameplay/layout.py +9 -15
  11. zombie_escape/gameplay/movement.py +45 -29
  12. zombie_escape/gameplay/spawn.py +15 -29
  13. zombie_escape/gameplay/state.py +62 -7
  14. zombie_escape/gameplay/survivors.py +61 -10
  15. zombie_escape/gameplay/utils.py +33 -0
  16. zombie_escape/level_blueprints.py +35 -31
  17. zombie_escape/level_constants.py +2 -2
  18. zombie_escape/locales/ui.en.json +19 -8
  19. zombie_escape/locales/ui.ja.json +19 -8
  20. zombie_escape/localization.py +7 -1
  21. zombie_escape/models.py +21 -6
  22. zombie_escape/render/__init__.py +2 -2
  23. zombie_escape/render/core.py +113 -81
  24. zombie_escape/render/hud.py +112 -40
  25. zombie_escape/render/overview.py +93 -2
  26. zombie_escape/render/shadows.py +2 -2
  27. zombie_escape/render_constants.py +12 -0
  28. zombie_escape/screens/__init__.py +6 -189
  29. zombie_escape/screens/game_over.py +8 -21
  30. zombie_escape/screens/gameplay.py +71 -26
  31. zombie_escape/screens/settings.py +114 -43
  32. zombie_escape/screens/title.py +128 -47
  33. zombie_escape/stage_constants.py +37 -8
  34. zombie_escape/windowing.py +508 -0
  35. zombie_escape/world_grid.py +7 -5
  36. zombie_escape/zombie_escape.py +26 -13
  37. {zombie_escape-1.14.4.dist-info → zombie_escape-1.15.2.dist-info}/METADATA +24 -24
  38. zombie_escape-1.15.2.dist-info/RECORD +54 -0
  39. zombie_escape-1.14.4.dist-info/RECORD +0 -53
  40. {zombie_escape-1.14.4.dist-info → zombie_escape-1.15.2.dist-info}/WHEEL +0 -0
  41. {zombie_escape-1.14.4.dist-info → zombie_escape-1.15.2.dist-info}/entry_points.txt +0 -0
  42. {zombie_escape-1.14.4.dist-info → zombie_escape-1.15.2.dist-info}/licenses/LICENSE.txt +0 -0
@@ -19,14 +19,8 @@ from ..input_utils import (
19
19
  from ..localization import translate as tr
20
20
  from ..models import GameData, Stage
21
21
  from ..render import RenderAssets, _draw_status_bar, compute_floor_cells, draw_level_overview, show_message
22
- from ..screens import (
23
- ScreenID,
24
- ScreenTransition,
25
- nudge_window_scale,
26
- present,
27
- sync_window_size,
28
- toggle_fullscreen,
29
- )
22
+ from ..screens import ScreenID, ScreenTransition
23
+ from ..windowing import nudge_window_scale, present, sync_window_size, toggle_fullscreen
30
24
 
31
25
 
32
26
  def game_over_screen(
@@ -87,6 +81,7 @@ def game_over_screen(
87
81
  if survivor.alive() and survivor.is_buddy and not survivor.rescued
88
82
  ],
89
83
  survivors=list(game_data.groups.survivor_group),
84
+ fall_spawn_cells=game_data.layout.fall_spawn_cells,
90
85
  palette_key=state.ambient_palette_key,
91
86
  )
92
87
 
@@ -113,7 +108,7 @@ def game_over_screen(
113
108
  show_message(
114
109
  screen,
115
110
  tr("game_over.win"),
116
- 22,
111
+ 11,
117
112
  GREEN,
118
113
  (screen_width // 2, screen_height // 2 - 26),
119
114
  )
@@ -121,18 +116,10 @@ def game_over_screen(
121
116
  show_message(
122
117
  screen,
123
118
  tr("game_over.lose"),
124
- 22,
119
+ 11,
125
120
  RED,
126
121
  (screen_width // 2, screen_height // 2 - 26),
127
122
  )
128
- if state.game_over_message:
129
- show_message(
130
- screen,
131
- state.game_over_message,
132
- 18,
133
- LIGHT_GRAY,
134
- (screen_width // 2, screen_height // 2 + 6),
135
- )
136
123
  summary_y = screen_height // 2 + 70
137
124
  if stage and (stage.rescue_stage or stage.buddy_required_count > 0):
138
125
  total_rescued = state.survivors_rescued + state.buddy_rescued
@@ -140,7 +127,7 @@ def game_over_screen(
140
127
  show_message(
141
128
  screen,
142
129
  msg,
143
- 18,
130
+ 11,
144
131
  LIGHT_GRAY,
145
132
  (screen_width // 2, summary_y),
146
133
  )
@@ -157,7 +144,7 @@ def game_over_screen(
157
144
  show_message(
158
145
  screen,
159
146
  msg,
160
- 18,
147
+ 11,
161
148
  LIGHT_GRAY,
162
149
  (screen_width // 2, summary_y),
163
150
  )
@@ -165,7 +152,7 @@ def game_over_screen(
165
152
  show_message(
166
153
  screen,
167
154
  tr("game_over.prompt"),
168
- 18,
155
+ 11,
169
156
  WHITE,
170
157
  (screen_width // 2, screen_height // 2 + 24),
171
158
  )
@@ -5,7 +5,8 @@ from typing import TYPE_CHECKING, Any
5
5
  import pygame
6
6
  from pygame import surface, time
7
7
 
8
- from ..colors import RED, YELLOW
8
+ from ..colors import LIGHT_GRAY, RED, WHITE, YELLOW
9
+ from ..font_utils import load_font
9
10
  from ..gameplay_constants import (
10
11
  CAR_HINT_DELAY_MS_DEFAULT,
11
12
  SURVIVAL_TIME_ACCEL_SUBSTEPS,
@@ -20,6 +21,7 @@ from ..gameplay import (
20
21
  initialize_game_state,
21
22
  maintain_waiting_car_supply,
22
23
  nearest_waiting_car,
24
+ schedule_timed_message,
23
25
  place_flashlights,
24
26
  place_fuel_can,
25
27
  place_shoes,
@@ -32,6 +34,8 @@ from ..gameplay import (
32
34
  update_footprints,
33
35
  update_endurance_timer,
34
36
  )
37
+ from ..gameplay.state import frames_to_ms, ms_to_frames
38
+ from ..gameplay.constants import INTRO_MESSAGE_DISPLAY_FRAMES
35
39
  from ..input_utils import (
36
40
  CONTROLLER_BUTTON_DOWN,
37
41
  CONTROLLER_DEVICE_ADDED,
@@ -45,19 +49,14 @@ from ..input_utils import (
45
49
  )
46
50
  from ..gameplay.spawn import _alive_waiting_cars
47
51
  from ..world_grid import build_wall_index
48
- from ..localization import translate as tr
52
+ from ..localization import get_font_settings, translate as tr
49
53
  from ..models import Stage
50
- from ..render import draw, draw_debug_overview, draw_pause_overlay, prewarm_fog_overlays, show_message, show_message_wrapped
54
+ from ..render import draw, draw_debug_overview, draw_pause_overlay, prewarm_fog_overlays, show_message_wrapped
55
+ from ..render_constants import GAMEPLAY_FONT_SIZE, TIMED_MESSAGE_LEFT_X, TIMED_MESSAGE_TOP_Y
51
56
  from ..rng import generate_seed, seed_rng
52
57
  from ..progress import record_stage_clear
53
- from ..screens import (
54
- ScreenID,
55
- ScreenTransition,
56
- nudge_window_scale,
57
- present,
58
- sync_window_size,
59
- toggle_fullscreen,
60
- )
58
+ from ..screens import ScreenID, ScreenTransition
59
+ from ..windowing import nudge_window_scale, present, sync_window_size, toggle_fullscreen
61
60
 
62
61
  if TYPE_CHECKING:
63
62
  from ..render import RenderAssets
@@ -93,9 +92,32 @@ def gameplay_screen(
93
92
  _set_mouse_hidden(False)
94
93
  return transition
95
94
 
95
+ def _show_loading_still() -> None:
96
+ screen.fill((0, 0, 0))
97
+ if stage.intro_key:
98
+ intro_text = tr(stage.intro_key)
99
+ font_settings = get_font_settings()
100
+ font_size = font_settings.scaled_size(GAMEPLAY_FONT_SIZE * 2)
101
+ font = load_font(font_settings.resource, font_size)
102
+ line_height = int(round(font.get_linesize() * font_settings.line_height_scale))
103
+ x = TIMED_MESSAGE_LEFT_X
104
+ y = TIMED_MESSAGE_TOP_Y
105
+ for line in intro_text.splitlines():
106
+ if not line:
107
+ y += line_height
108
+ continue
109
+ surface = font.render(line, False, WHITE)
110
+ screen.blit(surface, (x, y))
111
+ y += line_height
112
+ present(screen)
113
+ pygame.event.pump()
114
+
96
115
  seed_value = seed if seed is not None else generate_seed()
97
116
  applied_seed = seed_rng(seed_value)
98
117
 
118
+ loading_started_ms = pygame.time.get_ticks()
119
+ _show_loading_still()
120
+
99
121
  game_data = initialize_game_state(config, stage)
100
122
  game_data.state.seed = applied_seed
101
123
  game_data.state.debug_mode = debug_mode
@@ -113,6 +135,18 @@ def gameplay_screen(
113
135
  render_assets,
114
136
  stage=stage,
115
137
  )
138
+ if stage.intro_key and game_data.state.timed_message:
139
+ loading_elapsed_ms = max(0, pygame.time.get_ticks() - loading_started_ms)
140
+ remaining_ms = max(0, frames_to_ms(INTRO_MESSAGE_DISPLAY_FRAMES) - loading_elapsed_ms)
141
+ schedule_timed_message(
142
+ game_data.state,
143
+ tr(stage.intro_key),
144
+ duration_frames=max(0, ms_to_frames(remaining_ms)),
145
+ clear_on_input=True,
146
+ color=LIGHT_GRAY,
147
+ align="left",
148
+ now_ms=game_data.state.elapsed_play_ms,
149
+ )
116
150
  paused_manual = False
117
151
  paused_focus = False
118
152
  ignore_focus_loss_until = 0
@@ -154,6 +188,8 @@ def gameplay_screen(
154
188
  cell_size = game_data.cell_size
155
189
  if stage.requires_fuel:
156
190
  fuel_spawn_count = stage.fuel_spawn_count
191
+ if stage.endurance_stage:
192
+ fuel_spawn_count = 0
157
193
  fuel_can = place_fuel_can(
158
194
  layout_data["fuel_cells"] or layout_data["walkable_cells"],
159
195
  cell_size,
@@ -214,14 +250,6 @@ def gameplay_screen(
214
250
  hint_color=None,
215
251
  fps=current_fps,
216
252
  )
217
- if game_data.state.game_over_message:
218
- show_message(
219
- screen,
220
- game_data.state.game_over_message,
221
- 18,
222
- RED,
223
- (screen_width // 2, screen_height // 2 - 24),
224
- )
225
253
  present(screen)
226
254
  continue
227
255
  return _finalize(
@@ -315,13 +343,24 @@ def gameplay_screen(
315
343
 
316
344
  paused = paused_manual or paused_focus
317
345
  if paused:
318
- draw(
319
- render_assets,
320
- screen,
321
- game_data,
322
- config=config,
323
- fps=current_fps,
324
- )
346
+ if debug_overview:
347
+ draw_debug_overview(
348
+ render_assets,
349
+ screen,
350
+ overview_surface,
351
+ game_data,
352
+ config,
353
+ screen_width=screen_width,
354
+ screen_height=screen_height,
355
+ )
356
+ else:
357
+ draw(
358
+ render_assets,
359
+ screen,
360
+ game_data,
361
+ config=config,
362
+ fps=current_fps,
363
+ )
325
364
  if show_pause_overlay:
326
365
  draw_pause_overlay(screen)
327
366
  present(screen)
@@ -347,6 +386,12 @@ def gameplay_screen(
347
386
  shoes_count=game_data.state.shoes_count,
348
387
  pad_input=pad_vector,
349
388
  )
389
+ if (
390
+ game_data.state.timed_message
391
+ and game_data.state.timed_message.clear_on_input
392
+ and (player_dx or player_dy or car_dx or car_dy)
393
+ ):
394
+ game_data.state.timed_message = None
350
395
  update_entities(
351
396
  game_data,
352
397
  player_dx,
@@ -9,7 +9,7 @@ from pygame import surface, time
9
9
 
10
10
  from ..colors import BLACK, GREEN, LIGHT_GRAY, WHITE
11
11
  from ..config import DEFAULT_CONFIG, save_config
12
- from ..font_utils import load_font
12
+ from ..font_utils import blit_text_scaled, load_font, render_text_scaled
13
13
  from ..input_utils import (
14
14
  CONTROLLER_BUTTON_DOWN,
15
15
  CONTROLLER_BUTTON_DPAD_DOWN,
@@ -36,13 +36,18 @@ from ..localization import (
36
36
  from ..render import show_message, wrap_text
37
37
  from ..progress import user_progress_path
38
38
  from ..screens import (
39
- nudge_window_scale,
39
+ TITLE_FONT_SCALE,
40
+ TITLE_HEADER_Y,
41
+ TITLE_SECTION_TOP,
42
+ )
43
+ from ..windowing import (
44
+ adjust_menu_logical_size,
45
+ nudge_menu_window_scale,
40
46
  present,
41
47
  sync_window_size,
42
48
  toggle_fullscreen,
43
49
  )
44
50
 
45
-
46
51
  def settings_screen(
47
52
  screen: surface.Surface,
48
53
  clock: time.Clock,
@@ -54,7 +59,10 @@ def settings_screen(
54
59
  ) -> dict[str, Any]:
55
60
  """Settings menu shown from the title screen."""
56
61
 
57
- screen_width, screen_height = screen_size
62
+ settings_font_scale = TITLE_FONT_SCALE
63
+ screen_width, screen_height = screen.get_size()
64
+ if screen_width <= 0 or screen_height <= 0:
65
+ screen_width, screen_height = screen_size
58
66
  working = copy.deepcopy(config)
59
67
  set_language(working.get("language"))
60
68
  selected = 0
@@ -170,6 +178,18 @@ def settings_screen(
170
178
  },
171
179
  ],
172
180
  },
181
+ {
182
+ "label": tr("settings.sections.visual"),
183
+ "rows": [
184
+ {
185
+ "label": tr("settings.rows.shadows"),
186
+ "path": ("visual", "shadows", "enabled"),
187
+ "easy_value": True,
188
+ "left_label": tr("common.on"),
189
+ "right_label": tr("common.off"),
190
+ },
191
+ ],
192
+ },
173
193
  ]
174
194
 
175
195
  def rebuild_rows() -> tuple[list[dict], list[dict], list[str]]:
@@ -196,6 +216,7 @@ def settings_screen(
196
216
  return config
197
217
  if event.type in (pygame.WINDOWSIZECHANGED, pygame.VIDEORESIZE):
198
218
  sync_window_size(event)
219
+ adjust_menu_logical_size()
199
220
  continue
200
221
  if event.type == pygame.JOYDEVICEADDED or (
201
222
  CONTROLLER_DEVICE_ADDED is not None and event.type == CONTROLLER_DEVICE_ADDED
@@ -215,13 +236,14 @@ def settings_screen(
215
236
  return _exit_settings()
216
237
  if event.type == pygame.KEYDOWN:
217
238
  if event.key == pygame.K_LEFTBRACKET:
218
- nudge_window_scale(0.5)
239
+ nudge_menu_window_scale(0.5)
219
240
  continue
220
241
  if event.key == pygame.K_RIGHTBRACKET:
221
- nudge_window_scale(2.0)
242
+ nudge_menu_window_scale(2.0)
222
243
  continue
223
244
  if event.key == pygame.K_f:
224
245
  toggle_fullscreen()
246
+ adjust_menu_logical_size()
225
247
  continue
226
248
  if event.key in (pygame.K_ESCAPE, pygame.K_BACKSPACE):
227
249
  return _exit_settings()
@@ -316,23 +338,25 @@ def settings_screen(
316
338
  show_message(
317
339
  screen,
318
340
  tr("settings.title"),
319
- 26,
341
+ 26 * settings_font_scale,
320
342
  LIGHT_GRAY,
321
- (screen_width // 2, 20),
343
+ (screen_width // 2, TITLE_HEADER_Y),
344
+ scale_factor=settings_font_scale,
322
345
  )
323
346
 
324
347
  try:
325
348
  font_settings = get_font_settings()
326
- label_font = load_font(font_settings.resource, font_settings.scaled_size(12))
327
- value_font = load_font(font_settings.resource, font_settings.scaled_size(12))
328
- section_font = load_font(font_settings.resource, font_settings.scaled_size(13))
349
+ label_size = font_settings.scaled_size(11 * settings_font_scale)
350
+ value_size = font_settings.scaled_size(11 * settings_font_scale)
351
+ section_size = font_settings.scaled_size(12 * settings_font_scale)
352
+ label_font = load_font(font_settings.resource, label_size)
329
353
  highlight_color = (70, 70, 70)
330
354
 
331
- row_height = 20
332
- start_y = 46
355
+ row_height = 16
356
+ start_y = TITLE_SECTION_TOP
333
357
 
334
- segment_width = 30
335
- segment_height = 18
358
+ segment_width = int(round(30 * 1.5 * 0.8))
359
+ segment_height = int(round(18 * 0.8))
336
360
  segment_gap = 10
337
361
  segment_total_width = segment_width * 2 + segment_gap
338
362
 
@@ -345,7 +369,13 @@ def settings_screen(
345
369
  section_states: dict[str, dict] = {}
346
370
  y_cursor = start_y
347
371
  for section in sections:
348
- header_surface = section_font.render(section["label"], False, LIGHT_GRAY)
372
+ header_surface = render_text_scaled(
373
+ font_settings.resource,
374
+ section_size,
375
+ section["label"],
376
+ LIGHT_GRAY,
377
+ scale_factor=settings_font_scale,
378
+ )
349
379
  section_states[section["label"]] = {
350
380
  "next_y": y_cursor + header_surface.get_height() + 4,
351
381
  "header_surface": header_surface,
@@ -376,25 +406,34 @@ def settings_screen(
376
406
  if idx == selected:
377
407
  pygame.draw.rect(screen, highlight_color, highlight_rect)
378
408
 
379
- label_surface = label_font.render(row["label"], False, WHITE)
380
- label_rect = label_surface.get_rect(
409
+ label_height = label_font.get_linesize()
410
+ blit_text_scaled(
411
+ screen,
412
+ font_settings.resource,
413
+ label_size,
414
+ row["label"],
415
+ WHITE,
416
+ scale_factor=settings_font_scale,
381
417
  topleft=(
382
418
  col_x,
383
- row_y_current + (row_height - label_surface.get_height()) // 2,
384
- )
419
+ row_y_current + (row_height - label_height) // 2,
420
+ ),
385
421
  )
386
- screen.blit(label_surface, label_rect)
387
422
  if row_type == "choice":
388
423
  display_fn = row.get("get_display")
389
424
  display_text = display_fn(value) if display_fn and value is not None else str(value)
390
- value_surface = value_font.render(display_text, False, WHITE)
391
- value_rect = value_surface.get_rect(
425
+ blit_text_scaled(
426
+ screen,
427
+ font_settings.resource,
428
+ value_size,
429
+ display_text,
430
+ WHITE,
431
+ scale_factor=settings_font_scale,
392
432
  midright=(
393
433
  col_x + row_width,
394
434
  row_y_current + row_height // 2,
395
- )
435
+ ),
396
436
  )
397
- screen.blit(value_surface, value_rect)
398
437
  elif row_type == "toggle":
399
438
  slider_y = row_y_current + (row_height - segment_height) // 2 - 2
400
439
  slider_x = col_x + row_width - segment_total_width
@@ -415,16 +454,23 @@ def settings_screen(
415
454
  outline_color = GREEN if active else LIGHT_GRAY
416
455
  pygame.draw.rect(screen, active_color, rect)
417
456
  pygame.draw.rect(screen, outline_color, rect, width=2)
418
- text_surface = value_font.render(text, False, WHITE)
419
- text_rect = text_surface.get_rect(center=rect.center)
420
- screen.blit(text_surface, text_rect)
457
+ blit_text_scaled(
458
+ screen,
459
+ font_settings.resource,
460
+ value_size,
461
+ text,
462
+ WHITE,
463
+ scale_factor=settings_font_scale,
464
+ center=rect.center,
465
+ )
421
466
 
422
467
  draw_segment(left_rect, row["left_label"], left_active)
423
468
  draw_segment(right_rect, row["right_label"], right_active)
424
469
 
425
470
  hint_start_y = start_y
426
471
  hint_start_x = screen_width // 2 + 16
427
- hint_font = load_font(font_settings.resource, font_settings.scaled_size(11))
472
+ hint_size = font_settings.scaled_size(11 * settings_font_scale)
473
+ hint_font = load_font(font_settings.resource, hint_size)
428
474
  hint_lines = [
429
475
  tr("settings.hints.navigate"),
430
476
  tr("settings.hints.adjust"),
@@ -432,33 +478,58 @@ def settings_screen(
432
478
  tr("settings.hints.reset"),
433
479
  tr("settings.hints.exit"),
434
480
  ]
435
- hint_line_height = hint_font.get_linesize()
481
+ hint_line_height = int(round(hint_font.get_linesize() * font_settings.line_height_scale))
436
482
  hint_max_width = screen_width - hint_start_x - 16
437
483
  y_cursor = hint_start_y
438
484
  for line in hint_lines:
439
- hint_surface = hint_font.render(line, False, WHITE)
440
- hint_rect = hint_surface.get_rect(topleft=(hint_start_x, y_cursor))
441
- screen.blit(hint_surface, hint_rect)
485
+ blit_text_scaled(
486
+ screen,
487
+ font_settings.resource,
488
+ hint_size,
489
+ line,
490
+ WHITE,
491
+ scale_factor=settings_font_scale,
492
+ topleft=(hint_start_x, y_cursor),
493
+ )
442
494
  y_cursor += hint_line_height
443
495
 
444
496
  y_cursor += 26
445
497
  window_hint = tr("menu.window_hint")
446
498
  for line in wrap_text(window_hint, hint_font, hint_max_width):
447
- hint_surface = hint_font.render(line, False, WHITE)
448
- hint_rect = hint_surface.get_rect(topleft=(hint_start_x, y_cursor))
449
- screen.blit(hint_surface, hint_rect)
499
+ blit_text_scaled(
500
+ screen,
501
+ font_settings.resource,
502
+ hint_size,
503
+ line,
504
+ WHITE,
505
+ scale_factor=settings_font_scale,
506
+ topleft=(hint_start_x, y_cursor),
507
+ )
450
508
  y_cursor += hint_line_height
451
509
 
452
- path_font = load_font(font_settings.resource, font_settings.scaled_size(11))
510
+ path_size = font_settings.scaled_size(11 * settings_font_scale)
511
+ path_font = load_font(font_settings.resource, path_size)
453
512
  config_text = tr("settings.config_path", path=str(config_path))
454
513
  progress_text = tr("settings.progress_path", path=str(user_progress_path()))
455
514
  line_height = path_font.get_linesize()
456
- config_surface = path_font.render(config_text, False, LIGHT_GRAY)
457
- progress_surface = path_font.render(progress_text, False, LIGHT_GRAY)
458
- config_rect = config_surface.get_rect(midtop=(screen_width // 2, screen_height - 32 - line_height))
459
- progress_rect = progress_surface.get_rect(midtop=(screen_width // 2, screen_height - 32))
460
- screen.blit(config_surface, config_rect)
461
- screen.blit(progress_surface, progress_rect)
515
+ blit_text_scaled(
516
+ screen,
517
+ font_settings.resource,
518
+ path_size,
519
+ config_text,
520
+ LIGHT_GRAY,
521
+ scale_factor=settings_font_scale,
522
+ midtop=(screen_width // 2, screen_height - 32 - line_height),
523
+ )
524
+ blit_text_scaled(
525
+ screen,
526
+ font_settings.resource,
527
+ path_size,
528
+ progress_text,
529
+ LIGHT_GRAY,
530
+ scale_factor=settings_font_scale,
531
+ midtop=(screen_width // 2, screen_height - 32),
532
+ )
462
533
  except pygame.error as e:
463
534
  print(f"Error rendering settings: {e}")
464
535