zombie-escape 1.5.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.
@@ -0,0 +1,4 @@
1
+ # SPDX-FileCopyrightText: 2025-present Toshihiro Kamiya <kamiya@mbj.nifty.com>
2
+ #
3
+ # SPDX-License-Identifier: MIT
4
+ __version__ = "1.5.4"
@@ -0,0 +1,7 @@
1
+ # SPDX-FileCopyrightText: 2025-present Toshihiro Kamiya <kamiya@mbj.nifty.com>
2
+ #
3
+ # SPDX-License-Identifier: MIT
4
+
5
+ from .zombie_escape import main as main
6
+
7
+ __all__ = ["main"]
@@ -0,0 +1,170 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+
5
+ # Basic palette
6
+ WHITE: tuple[int, int, int] = (255, 255, 255)
7
+ BLACK: tuple[int, int, int] = (0, 0, 0)
8
+ RED: tuple[int, int, int] = (255, 0, 0)
9
+ GREEN: tuple[int, int, int] = (0, 255, 0)
10
+ BLUE: tuple[int, int, int] = (0, 0, 255)
11
+ GRAY: tuple[int, int, int] = (100, 100, 100)
12
+ LIGHT_GRAY: tuple[int, int, int] = (200, 200, 200)
13
+ YELLOW: tuple[int, int, int] = (255, 255, 0)
14
+ ORANGE: tuple[int, int, int] = (255, 165, 0)
15
+ DARK_RED: tuple[int, int, int] = (139, 0, 0)
16
+ TRACKER_OUTLINE_COLOR: tuple[int, int, int] = (170, 70, 220)
17
+ WALL_FOLLOWER_OUTLINE_COLOR: tuple[int, int, int] = (140, 140, 140)
18
+
19
+
20
+ @dataclass(frozen=True)
21
+ class EnvironmentPalette:
22
+ """Collection of colors that define the ambient environment."""
23
+
24
+ floor_primary: tuple[int, int, int]
25
+ floor_secondary: tuple[int, int, int]
26
+ outside: tuple[int, int, int]
27
+ inner_wall: tuple[int, int, int]
28
+ inner_wall_border: tuple[int, int, int]
29
+ outer_wall: tuple[int, int, int]
30
+ outer_wall_border: tuple[int, int, int]
31
+
32
+
33
+ def _clamp(value: float) -> int:
34
+ return max(0, min(255, int(value)))
35
+
36
+
37
+ def _adjust_color(
38
+ color: tuple[int, int, int], *, brightness: float = 1.0, saturation: float = 1.0
39
+ ) -> tuple[int, int, int]:
40
+ """Return color tinted by brightness/saturation multipliers."""
41
+
42
+ r, g, b = color
43
+ gray = 0.2126 * r + 0.7152 * g + 0.0722 * b
44
+ def mix(component: int) -> int:
45
+ value = gray + (component - gray) * saturation
46
+ value *= brightness
47
+ return _clamp(value)
48
+
49
+ return mix(r), mix(g), mix(b)
50
+
51
+
52
+ DEFAULT_AMBIENT_PALETTE_KEY = "default"
53
+ NO_FLASHLIGHT_PALETTE_KEY = "no_flashlight"
54
+ DAWN_AMBIENT_PALETTE_KEY = "dawn"
55
+
56
+ # Base palette used throughout gameplay (matches the previous constants).
57
+ _DEFAULT_ENVIRONMENT_PALETTE = EnvironmentPalette(
58
+ floor_primary=(43, 57, 70),
59
+ floor_secondary=(50, 64, 79),
60
+ outside=(32, 60, 40),
61
+ inner_wall=(125, 101, 78),
62
+ inner_wall_border=(136, 110, 85),
63
+ outer_wall=(136, 135, 128),
64
+ outer_wall_border=(147, 146, 138),
65
+ )
66
+
67
+ # Dark, desaturated palette that sells the "alone without a flashlight" vibe.
68
+ _GLOOM_ENVIRONMENT_PALETTE = EnvironmentPalette(
69
+ floor_primary=_adjust_color(
70
+ _DEFAULT_ENVIRONMENT_PALETTE.floor_primary, brightness=0.725, saturation=0.675
71
+ ),
72
+ floor_secondary=_adjust_color(
73
+ _DEFAULT_ENVIRONMENT_PALETTE.floor_secondary, brightness=0.74, saturation=0.65
74
+ ),
75
+ outside=_adjust_color(
76
+ _DEFAULT_ENVIRONMENT_PALETTE.outside, brightness=0.7, saturation=0.625
77
+ ),
78
+ inner_wall=_adjust_color(
79
+ _DEFAULT_ENVIRONMENT_PALETTE.inner_wall, brightness=0.775, saturation=0.7
80
+ ),
81
+ inner_wall_border=_adjust_color(
82
+ _DEFAULT_ENVIRONMENT_PALETTE.inner_wall_border, brightness=0.775, saturation=0.7
83
+ ),
84
+ outer_wall=_adjust_color(
85
+ _DEFAULT_ENVIRONMENT_PALETTE.outer_wall, brightness=0.75, saturation=0.675
86
+ ),
87
+ outer_wall_border=_adjust_color(
88
+ _DEFAULT_ENVIRONMENT_PALETTE.outer_wall_border, brightness=0.75, saturation=0.675
89
+ ),
90
+ )
91
+
92
+ _DAWN_ENVIRONMENT_PALETTE = EnvironmentPalette(
93
+ floor_primary=(58, 70, 84),
94
+ floor_secondary=(66, 78, 92),
95
+ outside=(118, 140, 104),
96
+ inner_wall=(125, 101, 78),
97
+ inner_wall_border=(136, 110, 85),
98
+ outer_wall=(136, 135, 128),
99
+ outer_wall_border=(147, 146, 138),
100
+ )
101
+
102
+ ENVIRONMENT_PALETTES: dict[str, EnvironmentPalette] = {
103
+ DEFAULT_AMBIENT_PALETTE_KEY: _DEFAULT_ENVIRONMENT_PALETTE,
104
+ NO_FLASHLIGHT_PALETTE_KEY: _GLOOM_ENVIRONMENT_PALETTE,
105
+ DAWN_AMBIENT_PALETTE_KEY: _DAWN_ENVIRONMENT_PALETTE,
106
+ }
107
+
108
+
109
+ def get_environment_palette(key: str | None) -> EnvironmentPalette:
110
+ """Return the color palette for the provided key (falls back to default)."""
111
+
112
+ if not key:
113
+ return ENVIRONMENT_PALETTES[DEFAULT_AMBIENT_PALETTE_KEY]
114
+ return ENVIRONMENT_PALETTES.get(key, ENVIRONMENT_PALETTES[DEFAULT_AMBIENT_PALETTE_KEY])
115
+
116
+
117
+ def ambient_palette_key_for_flashlights(count: int) -> str:
118
+ """Return the palette key for the provided flashlight inventory count."""
119
+
120
+ return (
121
+ DEFAULT_AMBIENT_PALETTE_KEY
122
+ if max(0, count) > 0
123
+ else NO_FLASHLIGHT_PALETTE_KEY
124
+ )
125
+
126
+
127
+ # World colors (default palette versions preserved for backwards compatibility).
128
+ INTERNAL_WALL_COLOR: tuple[int, int, int] = _DEFAULT_ENVIRONMENT_PALETTE.inner_wall
129
+ INTERNAL_WALL_BORDER_COLOR: tuple[int, int, int] = _DEFAULT_ENVIRONMENT_PALETTE.inner_wall_border
130
+ OUTER_WALL_COLOR: tuple[int, int, int] = _DEFAULT_ENVIRONMENT_PALETTE.outer_wall
131
+ OUTER_WALL_BORDER_COLOR: tuple[int, int, int] = _DEFAULT_ENVIRONMENT_PALETTE.outer_wall_border
132
+ FLOOR_COLOR_PRIMARY: tuple[int, int, int] = _DEFAULT_ENVIRONMENT_PALETTE.floor_primary
133
+ FLOOR_COLOR_SECONDARY: tuple[int, int, int] = _DEFAULT_ENVIRONMENT_PALETTE.floor_secondary
134
+ FLOOR_COLOR_OUTSIDE: tuple[int, int, int] = _DEFAULT_ENVIRONMENT_PALETTE.outside
135
+ FOOTPRINT_COLOR: tuple[int, int, int] = (110, 200, 255)
136
+ STEEL_BEAM_COLOR: tuple[int, int, int] = (110, 50, 50)
137
+ STEEL_BEAM_LINE_COLOR: tuple[int, int, int] = (180, 90, 90)
138
+
139
+
140
+ __all__ = [
141
+ "WHITE",
142
+ "BLACK",
143
+ "RED",
144
+ "GREEN",
145
+ "BLUE",
146
+ "GRAY",
147
+ "LIGHT_GRAY",
148
+ "YELLOW",
149
+ "ORANGE",
150
+ "DARK_RED",
151
+ "TRACKER_OUTLINE_COLOR",
152
+ "WALL_FOLLOWER_OUTLINE_COLOR",
153
+ "DAWN_AMBIENT_PALETTE_KEY",
154
+ "INTERNAL_WALL_COLOR",
155
+ "INTERNAL_WALL_BORDER_COLOR",
156
+ "OUTER_WALL_COLOR",
157
+ "OUTER_WALL_BORDER_COLOR",
158
+ "FLOOR_COLOR_PRIMARY",
159
+ "FLOOR_COLOR_SECONDARY",
160
+ "FLOOR_COLOR_OUTSIDE",
161
+ "FOOTPRINT_COLOR",
162
+ "STEEL_BEAM_COLOR",
163
+ "STEEL_BEAM_LINE_COLOR",
164
+ "EnvironmentPalette",
165
+ "DEFAULT_AMBIENT_PALETTE_KEY",
166
+ "NO_FLASHLIGHT_PALETTE_KEY",
167
+ "ENVIRONMENT_PALETTES",
168
+ "get_environment_palette",
169
+ "ambient_palette_key_for_flashlights",
170
+ ]
@@ -0,0 +1,58 @@
1
+ import json
2
+ from copy import deepcopy
3
+ from pathlib import Path
4
+ from typing import Any
5
+
6
+ from platformdirs import user_config_dir
7
+
8
+ APP_NAME = "ZombieEscape"
9
+
10
+ # Defaults for all configurable options
11
+ DEFAULT_CONFIG: dict[str, Any] = {
12
+ "language": "en",
13
+ "footprints": {"enabled": True},
14
+ "fast_zombies": {"enabled": False, "ratio": 0.1},
15
+ "car_hint": {"enabled": True, "delay_ms": 180_000},
16
+ "steel_beams": {"enabled": False, "chance": 0.05},
17
+ }
18
+
19
+
20
+ def user_config_path() -> Path:
21
+ """Return the platform-specific config file path."""
22
+ return Path(user_config_dir(APP_NAME, APP_NAME)) / "config.json"
23
+
24
+
25
+ def _deep_merge(base: dict[str, Any], override: dict[str, Any]) -> dict[str, Any]:
26
+ """Deep-merge dictionaries, with override winning on conflicts."""
27
+ merged: dict[str, Any] = deepcopy(base)
28
+ for key, val in override.items():
29
+ if isinstance(val, dict) and isinstance(merged.get(key), dict):
30
+ merged[key] = _deep_merge(merged[key], val)
31
+ else:
32
+ merged[key] = val
33
+ return merged
34
+
35
+
36
+ def load_config(*, path: Path | None = None) -> tuple[dict[str, Any], Path]:
37
+ """Load config from disk, falling back to defaults on errors."""
38
+ config_path = path or user_config_path()
39
+ config: dict[str, Any] = deepcopy(DEFAULT_CONFIG)
40
+
41
+ try:
42
+ if config_path.exists():
43
+ loaded = json.loads(config_path.read_text(encoding="utf-8"))
44
+ if isinstance(loaded, dict):
45
+ config = _deep_merge(config, loaded)
46
+ except Exception as exc: # noqa: BLE001
47
+ print(f"Failed to load config ({config_path}): {exc}")
48
+
49
+ return config, config_path
50
+
51
+
52
+ def save_config(config: dict[str, Any], path: Path) -> None:
53
+ """Persist config to disk, creating parent dirs as needed."""
54
+ try:
55
+ path.parent.mkdir(parents=True, exist_ok=True)
56
+ path.write_text(json.dumps(config, indent=2), encoding="utf-8")
57
+ except Exception as exc: # noqa: BLE001
58
+ print(f"Failed to save config ({path}): {exc}")