zombie-escape 1.3.5__tar.gz

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 (30) hide show
  1. zombie_escape-1.3.5/.gitignore +8 -0
  2. zombie_escape-1.3.5/LICENSE.txt +9 -0
  3. zombie_escape-1.3.5/PKG-INFO +158 -0
  4. zombie_escape-1.3.5/README.md +137 -0
  5. zombie_escape-1.3.5/pyproject.toml +51 -0
  6. zombie_escape-1.3.5/src/zombie_escape/__about__.py +4 -0
  7. zombie_escape-1.3.5/src/zombie_escape/__init__.py +7 -0
  8. zombie_escape-1.3.5/src/zombie_escape/assets/fonts/Silkscreen-Regular.ttf +0 -0
  9. zombie_escape-1.3.5/src/zombie_escape/assets/fonts/misaki_gothic.ttf +0 -0
  10. zombie_escape-1.3.5/src/zombie_escape/colors.py +166 -0
  11. zombie_escape-1.3.5/src/zombie_escape/config.py +58 -0
  12. zombie_escape-1.3.5/src/zombie_escape/constants.py +223 -0
  13. zombie_escape-1.3.5/src/zombie_escape/entities.py +838 -0
  14. zombie_escape-1.3.5/src/zombie_escape/font_utils.py +45 -0
  15. zombie_escape-1.3.5/src/zombie_escape/gameplay/__init__.py +7 -0
  16. zombie_escape-1.3.5/src/zombie_escape/gameplay/logic.py +1689 -0
  17. zombie_escape-1.3.5/src/zombie_escape/level_blueprints.py +182 -0
  18. zombie_escape-1.3.5/src/zombie_escape/locales/ui.en.json +119 -0
  19. zombie_escape-1.3.5/src/zombie_escape/locales/ui.ja.json +119 -0
  20. zombie_escape-1.3.5/src/zombie_escape/localization.py +186 -0
  21. zombie_escape-1.3.5/src/zombie_escape/models.py +175 -0
  22. zombie_escape-1.3.5/src/zombie_escape/render.py +823 -0
  23. zombie_escape-1.3.5/src/zombie_escape/render_assets.py +32 -0
  24. zombie_escape-1.3.5/src/zombie_escape/rng.py +132 -0
  25. zombie_escape-1.3.5/src/zombie_escape/screens/__init__.py +109 -0
  26. zombie_escape-1.3.5/src/zombie_escape/screens/game_over.py +171 -0
  27. zombie_escape-1.3.5/src/zombie_escape/screens/gameplay.py +335 -0
  28. zombie_escape-1.3.5/src/zombie_escape/screens/settings.py +356 -0
  29. zombie_escape-1.3.5/src/zombie_escape/screens/title.py +186 -0
  30. zombie_escape-1.3.5/src/zombie_escape/zombie_escape.py +191 -0
@@ -0,0 +1,8 @@
1
+ .codex/
2
+ node_modules/
3
+ **/__pycache__/**
4
+ dist/
5
+
6
+ package-lock.json
7
+ package.json
8
+ uv.lock
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-present Toshihiro Kamiya <kamiya@mbj.nifty.com>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,158 @@
1
+ Metadata-Version: 2.4
2
+ Name: zombie-escape
3
+ Version: 1.3.5
4
+ Summary: Top-down zombie survival game built with pygame.
5
+ Project-URL: Homepage, https://github.com/tos-kamiya/zombie-escape
6
+ Author-email: Toshihiro Kamiya <kamiya@mbj.nifty.com>
7
+ License-Expression: MIT
8
+ License-File: LICENSE.txt
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: Implementation :: CPython
16
+ Requires-Python: >=3.10
17
+ Requires-Dist: platformdirs
18
+ Requires-Dist: pygame
19
+ Requires-Dist: python-i18n
20
+ Description-Content-Type: text/markdown
21
+
22
+ # Zombie Escape
23
+
24
+ The city is overrun with zombies!
25
+ You fled the horde, taking refuge in an abandoned factory.
26
+
27
+ Inside, it's a maze. They won't get in easily.
28
+ But you have no weapons. Night has fallen. The power's out, plunging the factory into darkness.
29
+
30
+ Your only tool: a single flashlight.
31
+ A car... somewhere inside... it's your only hope.
32
+
33
+ Pierce the darkness and find the car!
34
+ Then, escape this nightmare city!
35
+
36
+ ## Overview
37
+
38
+ This game is a simple 2D top-down action game where the player aims to escape by finding and driving a car out of a large building infested with zombies. The player must evade zombies, break through walls to find a path, and then escape the building in a car.
39
+
40
+ <img src="https://raw.githubusercontent.com/tos-kamiya/zombie-escape/main/imgs/screenshot1.png" width="400">
41
+ <img src="https://raw.githubusercontent.com/tos-kamiya/zombie-escape/main/imgs/screenshot2.png" width="400">
42
+ <img src="https://raw.githubusercontent.com/tos-kamiya/zombie-escape/main/imgs/screenshot3.png" width="400">
43
+
44
+ ## Controls
45
+
46
+ - **Player/Car Movement:** `W` / `↑` (Up), `A` / `←` (Left), `S` / `↓` (Down), `D` / `→` (Right)
47
+ - **Enter Car:** Overlap the player with the car.
48
+ - **Quit Game:** `ESC` key
49
+ - **Restart:** `R` key (on Game Over/Clear screen)
50
+ - **Window Scale (title/settings only):** `[` to shrink, `]` to enlarge
51
+ - **Time Acceleration:** Hold either `Shift` key to run the entire world 4x faster; release to return to normal speed.
52
+
53
+ ## Settings (Title Screen)
54
+
55
+ Open **Settings** from the title to toggle gameplay assists:
56
+
57
+ - **Footprints:** Leave breadcrumb trails so you can backtrack in the dark.
58
+ - **Fast zombies:** Allow faster zombie variants; each zombie rolls a random speed between the normal and fast ranges.
59
+ - **Car hint:** After a delay, show a small triangle pointing toward the fuel (Stage 2 before pickup) or the car.
60
+ - **Steel beams:** Adds tougher single-cell obstacles (5% density) that block movement; hidden when stacked with an inner wall until that wall is destroyed.
61
+
62
+ ### Shared Seeds
63
+
64
+ The title screen also lets you enter a numeric **seed**. Type digits (or pass `--seed <number>` on the CLI) to lock the procedural layout, wall placement, and pickups; share that seed with a friend and you will both play the exact same stage even on different machines. The current seed is shown at the bottom right of the title screen and in-game HUD. Backspace reverts to an automatically generated value so you can quickly roll a fresh challenge.
65
+
66
+ ## Game Rules
67
+
68
+ ### Stages
69
+
70
+ At the title screen you can pick a stage:
71
+
72
+ - **Stage 1: Find the Car** — locate the car and drive out (you already start with fuel).
73
+ - **Stage 2: Fuel Run** — you start with no fuel; find a fuel can first, pick it up, then find the car and escape.
74
+ - **Stage 3: Rescue Buddy** — same fuel hunt as Stage 2 (you begin empty) plus grab your buddy, pick them up with the car, then escape together.
75
+ - **Stage 4: Evacuate Survivors** — start fueled, find the car, gather nearby civilians, and escape before zombies reach them. Stage 4 sprinkles extra parked cars across the map; slamming into one while already driving fully repairs your current ride and adds five more safe seats.
76
+ - **Stage 5: Survive Until Dawn** — every car is bone-dry. Endure until the sun rises while the horde presses in from every direction. Once dawn hits, outdoor zombies carbonize and you must walk out through an existing exterior gap to win; cars remain unusable.
77
+
78
+ An objective reminder is shown at the top-left during play.
79
+
80
+ ### Characters/Items
81
+
82
+ - **Player:** A blue circle. 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.
83
+ - **Zombie:** A red circle. Will chase the player (or car) once detected.
84
+ - 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.).
85
+ - **Car:** A yellow rectangle. The player can enter by making contact with it.
86
+ - The car has durability. Durability decreases when colliding with internal walls or hitting zombies.
87
+ - 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.
88
+ - 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.
89
+ - 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).
90
+ - **Walls:** Outer walls are gray; inner walls are beige.
91
+ - **Outer Walls:** Walls surrounding the stage that are nearly indestructible. Each side has at least three openings (exits).
92
+ - **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.
93
+ - **Flashlight:** Each pickup expands your visible radius by about 20% (grab two to reach the max boost).
94
+ - **Steel Beam (optional):** A square post with crossed diagonals; same collision as inner walls but with triple durability. Spawns independently of inner walls (may overlap them). If an inner wall covers a beam, the beam appears once the wall is destroyed.
95
+ - **Fuel Can (Stages 2 & 3):** A yellow jerrycan that only spawns on the fuel-run stages. Pick it up before driving the car; once collected the on-player indicator appears until you refuel the car.
96
+ - **Buddy (Stage 3):** A green circle survivor who spawns somewhere in the building and waits.
97
+ - Zombies only choose to pursue the buddy if they are on-screen; otherwise they ignore them.
98
+ - If a zombie tags the buddy off-screen, the buddy quietly respawns somewhere else instead of ending the run.
99
+ - Touch the buddy on foot to make them follow you (at 70% of player speed). Touch them while driving to pick them up.
100
+ - **Survivors (Stage 4):** Light blue civilians scattered indoors.
101
+ - They stand still until you get close, then shuffle toward you at about one-third of player speed.
102
+ - Zombies can convert them if both are on-screen; the survivor shouts a line and turns instantly.
103
+ - 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.
104
+
105
+ ### Win/Lose Conditions
106
+
107
+ - **Win Condition:** Escape the stage (level) boundaries while inside the car.
108
+ - Stage 1 and Stage 4 follow the base rule: find the car (already fueled) and drive out.
109
+ - Stage 2 also requires that you have collected the fuel can before driving out.
110
+ - Stage 3 requires both fuel and having picked up your buddy with the car before driving out.
111
+ - Stage 5 has no working cars; survive until dawn, then walk out through an exterior opening on foot.
112
+ - **Lose Condition:**
113
+ - The player is touched by a zombie while *not* inside a car.
114
+ - In Stage 3, if your buddy is caught (when visible), it's game over.
115
+ - (Note: In the current implementation, the game does not end immediately when the car is destroyed. The player can search for another car and continue trying to escape.)
116
+
117
+ ## How to Run
118
+
119
+ **Requirements: Python 3.10 or higher**
120
+
121
+ Install using pipx:
122
+
123
+ ```sh
124
+ pipx install zombie-escape
125
+ ```
126
+
127
+ Alternatively, you can install using pip in a virtual environment:
128
+
129
+ ```sh
130
+ pip install zombie-escape
131
+ ```
132
+
133
+ Launch using the following command line:
134
+
135
+ ```sh
136
+ zombie-escape
137
+ ```
138
+
139
+ ### Debug Mode
140
+
141
+ <!-- For Stage 5 balancing or capture needs, run `zombie-escape --debug`. This hides the pause overlay and starts survival stages with 3 minutes remaining so you can immediately test late-night behavior. -->
142
+
143
+ ## License
144
+
145
+ This project is licensed under the MIT License - see the [LICENSE.txt](LICENSE.txt) file for details.
146
+
147
+ The bundled Silkscreen-Regular.ttf font follows the license terms of its original distribution.
148
+ Please refer to the upstream website for details: https://fonts.google.com/specimen/Silkscreen
149
+
150
+ The bundled misaki_gothic.ttf font (Misaki font by Num Kadoma) follows the license terms provided by Little Limit.
151
+ Please refer to the official site for details: https://littlelimit.net/misaki.htm
152
+
153
+ ## Acknowledgements
154
+
155
+ Significant assistance for many technical implementation and documentation aspects of this game's development was received from Google's large language model, Gemini (accessed during development), and from OpenAI's GPT-5. This included generating Python/Pygame code, suggesting rule adjustments, providing debugging support, and creating this README. Their rapid coding capabilities and contributions to problem-solving are greatly appreciated.
156
+
157
+ Thanks to Jason Kottke, the author of the Silkscreen-Regular.ttf font used in the game.
158
+ Thanks to Num Kadoma, the author of the Misaki font (misaki_gothic.ttf) distributed via Little Limit.
@@ -0,0 +1,137 @@
1
+ # Zombie Escape
2
+
3
+ The city is overrun with zombies!
4
+ You fled the horde, taking refuge in an abandoned factory.
5
+
6
+ Inside, it's a maze. They won't get in easily.
7
+ But you have no weapons. Night has fallen. The power's out, plunging the factory into darkness.
8
+
9
+ Your only tool: a single flashlight.
10
+ A car... somewhere inside... it's your only hope.
11
+
12
+ Pierce the darkness and find the car!
13
+ Then, escape this nightmare city!
14
+
15
+ ## Overview
16
+
17
+ This game is a simple 2D top-down action game where the player aims to escape by finding and driving a car out of a large building infested with zombies. The player must evade zombies, break through walls to find a path, and then escape the building in a car.
18
+
19
+ <img src="https://raw.githubusercontent.com/tos-kamiya/zombie-escape/main/imgs/screenshot1.png" width="400">
20
+ <img src="https://raw.githubusercontent.com/tos-kamiya/zombie-escape/main/imgs/screenshot2.png" width="400">
21
+ <img src="https://raw.githubusercontent.com/tos-kamiya/zombie-escape/main/imgs/screenshot3.png" width="400">
22
+
23
+ ## Controls
24
+
25
+ - **Player/Car Movement:** `W` / `↑` (Up), `A` / `←` (Left), `S` / `↓` (Down), `D` / `→` (Right)
26
+ - **Enter Car:** Overlap the player with the car.
27
+ - **Quit Game:** `ESC` key
28
+ - **Restart:** `R` key (on Game Over/Clear screen)
29
+ - **Window Scale (title/settings only):** `[` to shrink, `]` to enlarge
30
+ - **Time Acceleration:** Hold either `Shift` key to run the entire world 4x faster; release to return to normal speed.
31
+
32
+ ## Settings (Title Screen)
33
+
34
+ Open **Settings** from the title to toggle gameplay assists:
35
+
36
+ - **Footprints:** Leave breadcrumb trails so you can backtrack in the dark.
37
+ - **Fast zombies:** Allow faster zombie variants; each zombie rolls a random speed between the normal and fast ranges.
38
+ - **Car hint:** After a delay, show a small triangle pointing toward the fuel (Stage 2 before pickup) or the car.
39
+ - **Steel beams:** Adds tougher single-cell obstacles (5% density) that block movement; hidden when stacked with an inner wall until that wall is destroyed.
40
+
41
+ ### Shared Seeds
42
+
43
+ The title screen also lets you enter a numeric **seed**. Type digits (or pass `--seed <number>` on the CLI) to lock the procedural layout, wall placement, and pickups; share that seed with a friend and you will both play the exact same stage even on different machines. The current seed is shown at the bottom right of the title screen and in-game HUD. Backspace reverts to an automatically generated value so you can quickly roll a fresh challenge.
44
+
45
+ ## Game Rules
46
+
47
+ ### Stages
48
+
49
+ At the title screen you can pick a stage:
50
+
51
+ - **Stage 1: Find the Car** — locate the car and drive out (you already start with fuel).
52
+ - **Stage 2: Fuel Run** — you start with no fuel; find a fuel can first, pick it up, then find the car and escape.
53
+ - **Stage 3: Rescue Buddy** — same fuel hunt as Stage 2 (you begin empty) plus grab your buddy, pick them up with the car, then escape together.
54
+ - **Stage 4: Evacuate Survivors** — start fueled, find the car, gather nearby civilians, and escape before zombies reach them. Stage 4 sprinkles extra parked cars across the map; slamming into one while already driving fully repairs your current ride and adds five more safe seats.
55
+ - **Stage 5: Survive Until Dawn** — every car is bone-dry. Endure until the sun rises while the horde presses in from every direction. Once dawn hits, outdoor zombies carbonize and you must walk out through an existing exterior gap to win; cars remain unusable.
56
+
57
+ An objective reminder is shown at the top-left during play.
58
+
59
+ ### Characters/Items
60
+
61
+ - **Player:** A blue circle. 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.
62
+ - **Zombie:** A red circle. Will chase the player (or car) once detected.
63
+ - 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.).
64
+ - **Car:** A yellow rectangle. The player can enter by making contact with it.
65
+ - The car has durability. Durability decreases when colliding with internal walls or hitting zombies.
66
+ - 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.
67
+ - 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.
68
+ - 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).
69
+ - **Walls:** Outer walls are gray; inner walls are beige.
70
+ - **Outer Walls:** Walls surrounding the stage that are nearly indestructible. Each side has at least three openings (exits).
71
+ - **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.
72
+ - **Flashlight:** Each pickup expands your visible radius by about 20% (grab two to reach the max boost).
73
+ - **Steel Beam (optional):** A square post with crossed diagonals; same collision as inner walls but with triple durability. Spawns independently of inner walls (may overlap them). If an inner wall covers a beam, the beam appears once the wall is destroyed.
74
+ - **Fuel Can (Stages 2 & 3):** A yellow jerrycan that only spawns on the fuel-run stages. Pick it up before driving the car; once collected the on-player indicator appears until you refuel the car.
75
+ - **Buddy (Stage 3):** A green circle survivor who spawns somewhere in the building and waits.
76
+ - Zombies only choose to pursue the buddy if they are on-screen; otherwise they ignore them.
77
+ - If a zombie tags the buddy off-screen, the buddy quietly respawns somewhere else instead of ending the run.
78
+ - Touch the buddy on foot to make them follow you (at 70% of player speed). Touch them while driving to pick them up.
79
+ - **Survivors (Stage 4):** Light blue civilians scattered indoors.
80
+ - They stand still until you get close, then shuffle toward you at about one-third of player speed.
81
+ - Zombies can convert them if both are on-screen; the survivor shouts a line and turns instantly.
82
+ - 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.
83
+
84
+ ### Win/Lose Conditions
85
+
86
+ - **Win Condition:** Escape the stage (level) boundaries while inside the car.
87
+ - Stage 1 and Stage 4 follow the base rule: find the car (already fueled) and drive out.
88
+ - Stage 2 also requires that you have collected the fuel can before driving out.
89
+ - Stage 3 requires both fuel and having picked up your buddy with the car before driving out.
90
+ - Stage 5 has no working cars; survive until dawn, then walk out through an exterior opening on foot.
91
+ - **Lose Condition:**
92
+ - The player is touched by a zombie while *not* inside a car.
93
+ - In Stage 3, if your buddy is caught (when visible), it's game over.
94
+ - (Note: In the current implementation, the game does not end immediately when the car is destroyed. The player can search for another car and continue trying to escape.)
95
+
96
+ ## How to Run
97
+
98
+ **Requirements: Python 3.10 or higher**
99
+
100
+ Install using pipx:
101
+
102
+ ```sh
103
+ pipx install zombie-escape
104
+ ```
105
+
106
+ Alternatively, you can install using pip in a virtual environment:
107
+
108
+ ```sh
109
+ pip install zombie-escape
110
+ ```
111
+
112
+ Launch using the following command line:
113
+
114
+ ```sh
115
+ zombie-escape
116
+ ```
117
+
118
+ ### Debug Mode
119
+
120
+ <!-- For Stage 5 balancing or capture needs, run `zombie-escape --debug`. This hides the pause overlay and starts survival stages with 3 minutes remaining so you can immediately test late-night behavior. -->
121
+
122
+ ## License
123
+
124
+ This project is licensed under the MIT License - see the [LICENSE.txt](LICENSE.txt) file for details.
125
+
126
+ The bundled Silkscreen-Regular.ttf font follows the license terms of its original distribution.
127
+ Please refer to the upstream website for details: https://fonts.google.com/specimen/Silkscreen
128
+
129
+ The bundled misaki_gothic.ttf font (Misaki font by Num Kadoma) follows the license terms provided by Little Limit.
130
+ Please refer to the official site for details: https://littlelimit.net/misaki.htm
131
+
132
+ ## Acknowledgements
133
+
134
+ Significant assistance for many technical implementation and documentation aspects of this game's development was received from Google's large language model, Gemini (accessed during development), and from OpenAI's GPT-5. This included generating Python/Pygame code, suggesting rule adjustments, providing debugging support, and creating this README. Their rapid coding capabilities and contributions to problem-solving are greatly appreciated.
135
+
136
+ Thanks to Jason Kottke, the author of the Silkscreen-Regular.ttf font used in the game.
137
+ Thanks to Num Kadoma, the author of the Misaki font (misaki_gothic.ttf) distributed via Little Limit.
@@ -0,0 +1,51 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "zombie-escape"
7
+ dynamic = ["version"]
8
+ description = "Top-down zombie survival game built with pygame."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ keywords = []
13
+ authors = [
14
+ { name = "Toshihiro Kamiya", email = "kamiya@mbj.nifty.com" },
15
+ ]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Programming Language :: Python",
19
+ "Programming Language :: Python :: 3.10",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Programming Language :: Python :: Implementation :: CPython",
23
+ "License :: OSI Approved :: MIT License",
24
+ ]
25
+ dependencies = [
26
+ "pygame",
27
+ "platformdirs",
28
+ "python-i18n",
29
+ ]
30
+
31
+ [project.urls]
32
+ Homepage = "https://github.com/tos-kamiya/zombie-escape"
33
+
34
+ [tool.hatch.version]
35
+ path = "src/zombie_escape/__about__.py"
36
+
37
+ [tool.hatch.build.targets.wheel]
38
+ packages = ["src/zombie_escape"]
39
+ include = [
40
+ "src/zombie_escape/assets/**",
41
+ "src/zombie_escape/locales/**",
42
+ ]
43
+
44
+ [tool.hatch.build.targets.sdist]
45
+ include = ["src/zombie_escape/**"]
46
+
47
+ [project.gui-scripts]
48
+ zombie-escape = "zombie_escape:main"
49
+
50
+ [tool.ty.rules]
51
+ unresolved-import = "ignore"
@@ -0,0 +1,4 @@
1
+ # SPDX-FileCopyrightText: 2025-present Toshihiro Kamiya <kamiya@mbj.nifty.com>
2
+ #
3
+ # SPDX-License-Identifier: MIT
4
+ __version__ = "1.3.5"
@@ -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,166 @@
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
+
17
+
18
+ @dataclass(frozen=True)
19
+ class EnvironmentPalette:
20
+ """Collection of colors that define the ambient environment."""
21
+
22
+ floor_primary: tuple[int, int, int]
23
+ floor_secondary: tuple[int, int, int]
24
+ outside: tuple[int, int, int]
25
+ inner_wall: tuple[int, int, int]
26
+ inner_wall_border: tuple[int, int, int]
27
+ outer_wall: tuple[int, int, int]
28
+ outer_wall_border: tuple[int, int, int]
29
+
30
+
31
+ def _clamp(value: float) -> int:
32
+ return max(0, min(255, int(value)))
33
+
34
+
35
+ def _adjust_color(
36
+ color: tuple[int, int, int], *, brightness: float = 1.0, saturation: float = 1.0
37
+ ) -> tuple[int, int, int]:
38
+ """Return color tinted by brightness/saturation multipliers."""
39
+
40
+ r, g, b = color
41
+ gray = 0.2126 * r + 0.7152 * g + 0.0722 * b
42
+ def mix(component: int) -> int:
43
+ value = gray + (component - gray) * saturation
44
+ value *= brightness
45
+ return _clamp(value)
46
+
47
+ return mix(r), mix(g), mix(b)
48
+
49
+
50
+ DEFAULT_AMBIENT_PALETTE_KEY = "default"
51
+ NO_FLASHLIGHT_PALETTE_KEY = "no_flashlight"
52
+ DAWN_AMBIENT_PALETTE_KEY = "dawn"
53
+
54
+ # Base palette used throughout gameplay (matches the previous constants).
55
+ _DEFAULT_ENVIRONMENT_PALETTE = EnvironmentPalette(
56
+ floor_primary=(43, 57, 70),
57
+ floor_secondary=(50, 64, 79),
58
+ outside=(32, 60, 40),
59
+ inner_wall=(125, 101, 78),
60
+ inner_wall_border=(136, 110, 85),
61
+ outer_wall=(136, 135, 128),
62
+ outer_wall_border=(147, 146, 138),
63
+ )
64
+
65
+ # Dark, desaturated palette that sells the "alone without a flashlight" vibe.
66
+ _GLOOM_ENVIRONMENT_PALETTE = EnvironmentPalette(
67
+ floor_primary=_adjust_color(
68
+ _DEFAULT_ENVIRONMENT_PALETTE.floor_primary, brightness=0.725, saturation=0.675
69
+ ),
70
+ floor_secondary=_adjust_color(
71
+ _DEFAULT_ENVIRONMENT_PALETTE.floor_secondary, brightness=0.74, saturation=0.65
72
+ ),
73
+ outside=_adjust_color(
74
+ _DEFAULT_ENVIRONMENT_PALETTE.outside, brightness=0.7, saturation=0.625
75
+ ),
76
+ inner_wall=_adjust_color(
77
+ _DEFAULT_ENVIRONMENT_PALETTE.inner_wall, brightness=0.775, saturation=0.7
78
+ ),
79
+ inner_wall_border=_adjust_color(
80
+ _DEFAULT_ENVIRONMENT_PALETTE.inner_wall_border, brightness=0.775, saturation=0.7
81
+ ),
82
+ outer_wall=_adjust_color(
83
+ _DEFAULT_ENVIRONMENT_PALETTE.outer_wall, brightness=0.75, saturation=0.675
84
+ ),
85
+ outer_wall_border=_adjust_color(
86
+ _DEFAULT_ENVIRONMENT_PALETTE.outer_wall_border, brightness=0.75, saturation=0.675
87
+ ),
88
+ )
89
+
90
+ _DAWN_ENVIRONMENT_PALETTE = EnvironmentPalette(
91
+ floor_primary=(58, 70, 84),
92
+ floor_secondary=(66, 78, 92),
93
+ outside=(118, 140, 104),
94
+ inner_wall=(125, 101, 78),
95
+ inner_wall_border=(136, 110, 85),
96
+ outer_wall=(136, 135, 128),
97
+ outer_wall_border=(147, 146, 138),
98
+ )
99
+
100
+ ENVIRONMENT_PALETTES: dict[str, EnvironmentPalette] = {
101
+ DEFAULT_AMBIENT_PALETTE_KEY: _DEFAULT_ENVIRONMENT_PALETTE,
102
+ NO_FLASHLIGHT_PALETTE_KEY: _GLOOM_ENVIRONMENT_PALETTE,
103
+ DAWN_AMBIENT_PALETTE_KEY: _DAWN_ENVIRONMENT_PALETTE,
104
+ }
105
+
106
+
107
+ def get_environment_palette(key: str | None) -> EnvironmentPalette:
108
+ """Return the color palette for the provided key (falls back to default)."""
109
+
110
+ if not key:
111
+ return ENVIRONMENT_PALETTES[DEFAULT_AMBIENT_PALETTE_KEY]
112
+ return ENVIRONMENT_PALETTES.get(key, ENVIRONMENT_PALETTES[DEFAULT_AMBIENT_PALETTE_KEY])
113
+
114
+
115
+ def ambient_palette_key_for_flashlights(count: int) -> str:
116
+ """Return the palette key for the provided flashlight inventory count."""
117
+
118
+ return (
119
+ DEFAULT_AMBIENT_PALETTE_KEY
120
+ if max(0, count) > 0
121
+ else NO_FLASHLIGHT_PALETTE_KEY
122
+ )
123
+
124
+
125
+ # World colors (default palette versions preserved for backwards compatibility).
126
+ INTERNAL_WALL_COLOR: tuple[int, int, int] = _DEFAULT_ENVIRONMENT_PALETTE.inner_wall
127
+ INTERNAL_WALL_BORDER_COLOR: tuple[int, int, int] = _DEFAULT_ENVIRONMENT_PALETTE.inner_wall_border
128
+ OUTER_WALL_COLOR: tuple[int, int, int] = _DEFAULT_ENVIRONMENT_PALETTE.outer_wall
129
+ OUTER_WALL_BORDER_COLOR: tuple[int, int, int] = _DEFAULT_ENVIRONMENT_PALETTE.outer_wall_border
130
+ FLOOR_COLOR_PRIMARY: tuple[int, int, int] = _DEFAULT_ENVIRONMENT_PALETTE.floor_primary
131
+ FLOOR_COLOR_SECONDARY: tuple[int, int, int] = _DEFAULT_ENVIRONMENT_PALETTE.floor_secondary
132
+ FLOOR_COLOR_OUTSIDE: tuple[int, int, int] = _DEFAULT_ENVIRONMENT_PALETTE.outside
133
+ FOOTPRINT_COLOR: tuple[int, int, int] = (110, 200, 255)
134
+ STEEL_BEAM_COLOR: tuple[int, int, int] = (110, 50, 50)
135
+ STEEL_BEAM_LINE_COLOR: tuple[int, int, int] = (180, 90, 90)
136
+
137
+
138
+ __all__ = [
139
+ "WHITE",
140
+ "BLACK",
141
+ "RED",
142
+ "GREEN",
143
+ "BLUE",
144
+ "GRAY",
145
+ "LIGHT_GRAY",
146
+ "YELLOW",
147
+ "ORANGE",
148
+ "DARK_RED",
149
+ "DAWN_AMBIENT_PALETTE_KEY",
150
+ "INTERNAL_WALL_COLOR",
151
+ "INTERNAL_WALL_BORDER_COLOR",
152
+ "OUTER_WALL_COLOR",
153
+ "OUTER_WALL_BORDER_COLOR",
154
+ "FLOOR_COLOR_PRIMARY",
155
+ "FLOOR_COLOR_SECONDARY",
156
+ "FLOOR_COLOR_OUTSIDE",
157
+ "FOOTPRINT_COLOR",
158
+ "STEEL_BEAM_COLOR",
159
+ "STEEL_BEAM_LINE_COLOR",
160
+ "EnvironmentPalette",
161
+ "DEFAULT_AMBIENT_PALETTE_KEY",
162
+ "NO_FLASHLIGHT_PALETTE_KEY",
163
+ "ENVIRONMENT_PALETTES",
164
+ "get_environment_palette",
165
+ "ambient_palette_key_for_flashlights",
166
+ ]
@@ -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}")