Compare commits

..

20 Commits

Author SHA1 Message Date
wszqkzqk
c88a018209
升级CI到python 3.12 (#21)
Signed-off-by: Zhou Qiankang <wszqkzqk@qq.com>
2025-06-23 10:40:10 +08:00
sanshi
11c3129ea0
格式化代码以及添加依赖文件配置 (#19)
* refactor: 格式化代码

* 添加依赖文件
2025-06-23 10:21:58 +08:00
wszqkzqk
72915de297
更新CI配置:升级Python版本并添加发布条件 (#20)
* 将CI工作流的Python版本从3.11升级到3.12
* 为所有发布步骤添加仓库条件检测,避免在fork仓库的PR中触发发布

Signed-off-by: Zhou Qiankang <wszqkzqk@qq.com>
2025-06-21 22:06:19 +08:00
wszqkzqk
268b32bba5
启用pygame缩放 (#18)
* Closes #17

Signed-off-by: Zhou Qiankang <wszqkzqk@qq.com>
2025-06-12 23:34:52 +08:00
星外之神
47b4ba1783
在夜晚关卡不推荐选择咖啡豆 (#16) 2023-11-24 10:32:11 +08:00
星外之神
9e63c5bf57
Update audio files & CI scripts
* 手动指定依赖文件

* ci

* 包括所有dll

* 手动修复dll包含

* update audio files

* 更改潜水僵尸设定
2023-10-14 13:45:49 +08:00
星外之神
78ba0efbcd
Update audio files & CI scripts
* 手动指定依赖文件

* ci

* 包括所有dll

* 手动修复dll包含

* update audio files
2023-10-11 21:59:47 +08:00
星外之神
70ffd9489d
Dev (#12)
* 手动指定依赖文件

* ci

* 手动修复dll包含
2023-06-01 09:59:48 +08:00
星外之神
daee48eb6e
Merge pull request #11 from wszqkzqk/dev
尝试修复CI
2023-06-01 01:04:54 +08:00
wszqkzqk
5267003c6f
尝试修复CI 2023-06-01 01:04:21 +08:00
星外之神
431d4ae662
Merge pull request #9 from wszqkzqk/dev
小修改: 类型注释 & CI
2023-06-01 00:46:03 +08:00
wszqkzqk
7efd1fcadf
重命名截屏 2023-06-01 00:42:16 +08:00
wszqkzqk
1bcef4d141
移除python 3.10 CI 2023-06-01 00:42:16 +08:00
wszqkzqk
2d368f8bf5
添加类型注解 2023-06-01 00:42:16 +08:00
wszqkzqk
388b3175e0
避免重复构建 2023-06-01 00:42:16 +08:00
星外之神
ffb48381a5
Merge pull request #8 from wszqkzqk/dev
小:统一括号缩进
2022-12-25 16:00:31 +08:00
wszqkzqk
6ac010321a 小:统一括号缩进 2022-12-25 16:00:01 +08:00
星外之神
d07983dbbd
Merge pull request #7 from wszqkzqk/dev
在README中更新数据和日志存储路径
2022-12-25 15:52:24 +08:00
wszqkzqk
fc5029fad3 在README中更新数据和日志存储路径 2022-12-25 15:51:55 +08:00
wszqkzqk
b18e442f66 Linux可执行文件说明 2022-12-13 18:37:19 +08:00
49 changed files with 3674 additions and 1795 deletions

View File

@ -6,9 +6,6 @@ concurrency:
on: on:
pull_request: pull_request:
push:
branches:
- dev
jobs: jobs:
windows: windows:
@ -17,8 +14,7 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
python_version: python_version:
- "3.11" - "3.12"
- "3.10"
name: Windows Python ${{ matrix.python_version }} name: Windows Python ${{ matrix.python_version }}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@ -50,6 +46,7 @@ jobs:
-i ./pypvz.ico -i ./pypvz.ico
- name: Release the version built by pyinstaller - name: Release the version built by pyinstaller
if: github.event.pull_request.head.repo.full_name == github.repository
uses: ncipollo/release-action@v1 uses: ncipollo/release-action@v1
with: with:
allowUpdates: true allowUpdates: true
@ -71,17 +68,17 @@ jobs:
--show-memory ` --show-memory `
--output-dir=out ` --output-dir=out `
--windows-icon-from-ico=pypvz.ico ` --windows-icon-from-ico=pypvz.ico `
--include-data-dir=resources=resources `
--include-data-file=c:\hostedtoolcache\windows\python\${{ matrix.python_version }}*\x64\lib\site-packages\pygame\libogg-0.dll=libogg-0.dll ` --include-data-file=c:\hostedtoolcache\windows\python\${{ matrix.python_version }}*\x64\lib\site-packages\pygame\libogg-0.dll=libogg-0.dll `
--include-data-file=c:\hostedtoolcache\windows\python\${{ matrix.python_version }}*\x64\lib\site-packages\pygame\libopus-0.dll=libopus-0.dll ` --include-data-file=c:\hostedtoolcache\windows\python\${{ matrix.python_version }}*\x64\lib\site-packages\pygame\libopus-0.dll=libopus-0.dll `
--include-data-file=c:\hostedtoolcache\windows\python\${{ matrix.python_version }}*\x64\lib\site-packages\pygame\libopusfile-0.dll=libopusfile-0.dll ` --include-data-file=c:\hostedtoolcache\windows\python\${{ matrix.python_version }}*\x64\lib\site-packages\pygame\libopusfile-0.dll=libopusfile-0.dll `
--include-data-file=c:\hostedtoolcache\windows\python\${{ matrix.python_version }}*\x64\lib\site-packages\pygame\libvorbisfile-3.dll=libvorbisfile-3.dll ` --include-data-file=c:\hostedtoolcache\windows\python\${{ matrix.python_version }}*\x64\lib\site-packages\pygame\libjpeg-9.dll=libjpeg-9.dll `
--include-data-file=c:\hostedtoolcache\windows\python\${{ matrix.python_version }}*\x64\lib\site-packages\pygame\libvorbis-0.dll=libvorbis-0.dll ` --include-data-dir=resources=resources `
--windows-disable-console ` --windows-disable-console `
-o ./out/pypvz-with-python${{ matrix.python_version }}-nuitka-windows-x64.exe ` -o pypvz-with-python${{ matrix.python_version }}-nuitka-windows-x64.exe `
pypvz.py pypvz.py
- name: Release the version built by nuitka - name: Release the version built by nuitka
if: github.event.pull_request.head.repo.full_name == github.repository
uses: ncipollo/release-action@v1 uses: ncipollo/release-action@v1
with: with:
allowUpdates: true allowUpdates: true
@ -96,8 +93,7 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
python_version: python_version:
- "3.10" - "3.12"
- "3.11"
name: Ubuntu Python ${{ matrix.python_version }} name: Ubuntu Python ${{ matrix.python_version }}
steps: steps:
- name: 🛎️ Checkout - name: 🛎️ Checkout
@ -137,6 +133,7 @@ jobs:
pypvz.py pypvz.py
- name: Release the version built by nuitka - name: Release the version built by nuitka
if: github.event.pull_request.head.repo.full_name == github.repository
uses: ncipollo/release-action@v1 uses: ncipollo/release-action@v1
with: with:
allowUpdates: true allowUpdates: true

View File

@ -16,8 +16,7 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
python_version: python_version:
- "3.11" - "3.12"
- "3.10"
name: Windows Python ${{ matrix.python_version }} name: Windows Python ${{ matrix.python_version }}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@ -70,14 +69,13 @@ jobs:
--show-memory ` --show-memory `
--output-dir=out ` --output-dir=out `
--windows-icon-from-ico=pypvz.ico ` --windows-icon-from-ico=pypvz.ico `
--include-data-dir=resources=resources `
--include-data-file=c:\hostedtoolcache\windows\python\${{ matrix.python_version }}*\x64\lib\site-packages\pygame\libogg-0.dll=libogg-0.dll ` --include-data-file=c:\hostedtoolcache\windows\python\${{ matrix.python_version }}*\x64\lib\site-packages\pygame\libogg-0.dll=libogg-0.dll `
--include-data-file=c:\hostedtoolcache\windows\python\${{ matrix.python_version }}*\x64\lib\site-packages\pygame\libopus-0.dll=libopus-0.dll ` --include-data-file=c:\hostedtoolcache\windows\python\${{ matrix.python_version }}*\x64\lib\site-packages\pygame\libopus-0.dll=libopus-0.dll `
--include-data-file=c:\hostedtoolcache\windows\python\${{ matrix.python_version }}*\x64\lib\site-packages\pygame\libopusfile-0.dll=libopusfile-0.dll ` --include-data-file=c:\hostedtoolcache\windows\python\${{ matrix.python_version }}*\x64\lib\site-packages\pygame\libopusfile-0.dll=libopusfile-0.dll `
--include-data-file=c:\hostedtoolcache\windows\python\${{ matrix.python_version }}*\x64\lib\site-packages\pygame\libvorbisfile-3.dll=libvorbisfile-3.dll ` --include-data-file=c:\hostedtoolcache\windows\python\${{ matrix.python_version }}*\x64\lib\site-packages\pygame\libjpeg-9.dll=libjpeg-9.dll `
--include-data-file=c:\hostedtoolcache\windows\python\${{ matrix.python_version }}*\x64\lib\site-packages\pygame\libvorbis-0.dll=libvorbis-0.dll ` --include-data-dir=resources=resources `
--windows-disable-console ` --windows-disable-console `
-o ./out/pypvz-with-python${{ matrix.python_version }}-nuitka-windows-x64.exe ` -o pypvz-with-python${{ matrix.python_version }}-nuitka-windows-x64.exe `
pypvz.py pypvz.py
@ -95,8 +93,7 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
python_version: python_version:
- "3.10" - "3.12"
- "3.11"
name: Ubuntu Python ${{ matrix.python_version }} name: Ubuntu Python ${{ matrix.python_version }}
steps: steps:
- name: 🛎️ Checkout - name: 🛎️ Checkout

4
.gitignore vendored
View File

@ -4,7 +4,11 @@ test-build/
release/ release/
# 忽略调试内容 # 忽略调试内容
.vscode/ .vscode/
# 忽略 Pycharm 项目文件
.idea/
__pycache__/ __pycache__/
*/__pycache__/ */__pycache__/
# 忽略测试文件 # 忽略测试文件
test*.py test*.py
# uv 管理的虚拟环境
.venv

1
.python-version Normal file
View File

@ -0,0 +1 @@
3.12

View File

@ -22,14 +22,14 @@
* 夜晚模式支持墓碑以及从墓碑生成僵尸 * 夜晚模式支持墓碑以及从墓碑生成僵尸
* 含有泳池的模式支持在最后一波时从泳池中自动冒出僵尸 * 含有泳池的模式支持在最后一波时从泳池中自动冒出僵尸
* 支持保存进度 * 支持保存进度
* Windows下默认进度文件的保存路径为`~\AppData\Roaming\wszqkzqk.dev\pypvz\userdata.json` * Windows下默认进度文件的保存路径为`~\AppData\Roaming\pypvz\userdata.json`
* 其他操作系统为`~/.config/wszqkzqk.dev/pypvz/userdata.json` * 其他操作系统为`~/.config/pypvz/userdata.json`
* 存档为JSON文件如果出现因存档损坏而造成程序无法启动可以手动编辑修复或者删除该文件重试 * 存档为JSON文件如果出现因存档损坏而造成程序无法启动可以手动编辑修复或者删除该文件重试
* 0.8.12.0版本后理论上不可能因为存档损坏而无法启动,如果有,请在[issues](https://github.com/wszqkzqk/pypvz/issues)中报告bug * 0.8.12.0版本后理论上不可能因为存档损坏而无法启动,如果有,请在[issues](https://github.com/wszqkzqk/pypvz/issues)中报告bug
* 仍然有可能因为升级后变量名不同而丢失存档的进度信息,这种情况手动编辑恢复即可 * 仍然有可能因为升级后变量名不同而丢失存档的进度信息,这种情况手动编辑恢复即可
* 支持错误日志记录 * 支持错误日志记录
* Windows下默认日志文件的保存路径为`~\AppData\Roaming\wszqkzqk.dev\pypvz\run.log` * Windows下默认日志文件的保存路径为`~\AppData\Roaming\pypvz\run.log`
* 其他操作系统为`~/.config/wszqkzqk.dev/pypvz/run.log` * 其他操作系统为`~/.config/pypvz/run.log`
* 支持自定义游戏速度倍率 * 支持自定义游戏速度倍率
* 保存在游戏存档文件中,可以通过修改`game rate`值更改速度倍率 * 保存在游戏存档文件中,可以通过修改`game rate`值更改速度倍率
* 游戏完成成就显示 * 游戏完成成就显示
@ -38,7 +38,17 @@
* 光标移动到向日葵奖杯上是显示当前各个模式通关次数 * 光标移动到向日葵奖杯上是显示当前各个模式通关次数
* 含有游戏帮助界面 QwQ * 含有游戏帮助界面 QwQ
## 环境要求 ## 环境安装
建议使用 [uv](https://docs.astral.sh/uv/) 安装依赖:
```bash
git clone https://github.com/wszqkzqk/pypvz.git
cd pypvz
uv sync
```
或者参考:
* `Python3` (建议 >= 3.10,最好使用最新版) * `Python3` (建议 >= 3.10,最好使用最新版)
* `Python-Pygame` (建议 >= 2.0,最好使用最新版) * `Python-Pygame` (建议 >= 2.0,最好使用最新版)
@ -74,10 +84,14 @@ python pypvz.py
- 均仅支持64位操作系统 - 均仅支持64位操作系统
- 不依赖python、pygame等外部环境开箱即用 - 不依赖python、pygame等外部环境开箱即用
### 使用Linux可执行文件
由于Linux几乎都标配了Python环境因此本程序不太重视Linux下可执行的单文件的维护因此没有手动构建版只能下载自动构建的软件包。可以在仓库的[`Releases`](https://github.com/wszqkzqk/pypvz/releases)页面中[下载最新版(点击跳转)](https://github.com/wszqkzqk/pypvz/releases/latest)。
## 方法 ## 方法
* 使用鼠标收集阳光,种植植物 * 使用鼠标收集阳光,种植植物
* 对于已经存在存档的用户,可以在`~\AppData\Roaming\wszqkzqk.dev\pypvz\userdata.json`Windows`~/.config/wszqkzqk.dev/pypvz/userdata.json`(其他操作系统)中修改当前关卡: * 对于已经存在存档的用户,可以在`~\AppData\Roaming\pypvz\userdata.json`Windows`~/.config/pypvz/userdata.json`(其他操作系统)中修改当前关卡:
* 冒险模式: * 冒险模式:
* 白昼模式——单行草皮1 * 白昼模式——单行草皮1
* 白昼模式——三行草皮2 * 白昼模式——三行草皮2
@ -291,34 +305,34 @@ pyinstaller -F pypvz.py `
## 截屏 ## 截屏
![截屏1](/demo/demo1.webp) ![截屏1](/screenshots/screenshot-1.webp)
![截屏2](/demo/demo2.webp) ![截屏2](/screenshots/screenshot-2.webp)
![截屏3](/demo/demo3.webp) ![截屏3](/screenshots/screenshot-3.webp)
![截屏4](/demo/demo4.webp) ![截屏4](/screenshots/screenshot-4.webp)
![截屏5](/demo/demo5.webp) ![截屏5](/screenshots/screenshot-5.webp)
![截屏6](/demo/demo6.webp) ![截屏6](/screenshots/screenshot-6.webp)
![截屏7](/demo/demo7.webp) ![截屏7](/screenshots/screenshot-7.webp)
![截屏8](/demo/demo8.webp) ![截屏8](/screenshots/screenshot-8.webp)
![截屏9](/demo/demo9.webp) ![截屏9](/screenshots/screenshot-9.webp)
![截屏10](/demo/demo10.webp) ![截屏10](/screenshots/screenshot-10.webp)
![截屏11](/demo/demo11.webp) ![截屏11](/screenshots/screenshot-11.webp)
![截屏12](/demo/demo12.webp) ![截屏12](/screenshots/screenshot-12.webp)
![截屏13](/demo/demo13.webp) ![截屏13](/screenshots/screenshot-13.webp)
![截屏14](/demo/demo14.webp) ![截屏14](/screenshots/screenshot-14.webp)
![截屏15](/demo/demo15.webp) ![截屏15](/screenshots/screenshot-15.webp)
![截屏16](/demo/demo16.webp) ![截屏16](/screenshots/screenshot-16.webp)
![截屏17](/demo/demo17.webp) ![截屏17](/screenshots/screenshot-17.webp)
![截屏18](/demo/demo18.webp) ![截屏18](/screenshots/screenshot-18.webp)
![截屏19](/demo/demo19.webp) ![截屏19](/screenshots/screenshot-19.webp)
![截屏20](/demo/demo20.webp) ![截屏20](/screenshots/screenshot-20.webp)
![截屏21](/demo/demo21.webp) ![截屏21](/screenshots/screenshot-21.webp)
![截屏22](/demo/demo22.webp) ![截屏22](/screenshots/screenshot-22.webp)
![截屏23](/demo/demo23.webp) ![截屏23](/screenshots/screenshot-23.webp)
## 关于日志与反馈 ## 关于日志与反馈
对于闪退情况Linux用户与Windows下的python源代码运行用户可以直接在终端中复制出崩溃日志进行反馈。 对于闪退情况Linux用户与Windows下的python源代码运行用户可以直接在终端中复制出崩溃日志进行反馈。
Windows单文件封装版本无法通过终端显示日志需要在日志文件中寻找崩溃原因 Windows单文件封装版本无法通过终端显示日志需要在日志文件中寻找崩溃原因
* Windows默认日志文件路径为`~\AppData\Roaming\wszqkzqk.dev\pypvz\run.log` * Windows默认日志文件路径为`~\AppData\Roaming\pypvz\run.log`
* 其他操作系统为`~/.config/wszqkzqk.dev/pypvz/run.log`,但一般可以在终端中显示时用终端中的输出即可 * 其他操作系统为`~/.config/pypvz/run.log`,但一般可以在终端中显示时用终端中的输出即可

12
pyproject.toml Normal file
View File

@ -0,0 +1,12 @@
[project]
name = "pypvz"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"blue>=0.9.1",
"pygame>=2.6.1",
"setuptools>=80.9.0",
"wheel>=0.45.1",
]

View File

@ -1,24 +1,30 @@
#!/usr/bin/env python #!/usr/bin/env python
import logging import logging
import traceback
import os import os
import pygame as pg import traceback
from logging.handlers import RotatingFileHandler from logging.handlers import RotatingFileHandler
import pygame as pg
# 由于在后续本地模块中存在对pygame的调用在此处必须完成pygame的初始化 # 由于在后续本地模块中存在对pygame的调用在此处必须完成pygame的初始化
os.environ["SDL_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR"]="0" # 设置临时环境变量以避免Linux下禁用x11合成器 os.environ[
'SDL_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR'
] = '0' # 设置临时环境变量以避免Linux下禁用x11合成器
pg.init() pg.init()
from source import tool
from source import constants as c from source import constants as c
from source.state import mainmenu, screen, level from source import tool
from source.state import level, mainmenu, screen
if __name__ == "__main__": if __name__ == '__main__':
# 日志设置 # 日志设置
if not os.path.exists(os.path.dirname(c.USERLOG_PATH)): if not os.path.exists(os.path.dirname(c.USERLOG_PATH)):
os.makedirs(os.path.dirname(c.USERLOG_PATH)) os.makedirs(os.path.dirname(c.USERLOG_PATH))
logger = logging.getLogger("main") logger = logging.getLogger('main')
formatter = logging.Formatter("%(asctime)s - %(levelname)s: %(message)s") formatter = logging.Formatter('%(asctime)s - %(levelname)s: %(message)s')
fileHandler = RotatingFileHandler(c.USERLOG_PATH, "a", 1_000_000, 0, "utf-8") fileHandler = RotatingFileHandler(
c.USERLOG_PATH, 'a', 1_000_000, 0, 'utf-8'
)
# 设置日志文件权限Unix为644Windows为可读写Python的os.chmod与Unix chmod相同但要显式说明8进制 # 设置日志文件权限Unix为644Windows为可读写Python的os.chmod与Unix chmod相同但要显式说明8进制
os.chmod(c.USERLOG_PATH, 0o644) os.chmod(c.USERLOG_PATH, 0o644)
fileHandler.setFormatter(formatter) fileHandler.setFormatter(formatter)
@ -30,7 +36,8 @@ if __name__ == "__main__":
try: try:
# 控制状态机运行 # 控制状态机运行
game = tool.Control() game = tool.Control()
state_dict = { c.MAIN_MENU: mainmenu.Menu(), state_dict = {
c.MAIN_MENU: mainmenu.Menu(),
c.GAME_VICTORY: screen.GameVictoryScreen(), c.GAME_VICTORY: screen.GameVictoryScreen(),
c.GAME_LOSE: screen.GameLoseScreen(), c.GAME_LOSE: screen.GameLoseScreen(),
c.LEVEL: level.Level(), c.LEVEL: level.Level(),
@ -41,4 +48,4 @@ if __name__ == "__main__":
game.run() game.run()
except: except:
print() # 将日志输出与上文内容分隔开,增加可读性 print() # 将日志输出与上文内容分隔开,增加可读性
logger.error(f"\n{traceback.format_exc()}") logger.error(f'\n{traceback.format_exc()}')

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

View File

Before

Width:  |  Height:  |  Size: 124 KiB

After

Width:  |  Height:  |  Size: 124 KiB

View File

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 95 KiB

View File

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 116 KiB

View File

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 87 KiB

View File

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 69 KiB

View File

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 78 KiB

View File

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

View File

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

View File

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

View File

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 75 KiB

View File

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 80 KiB

View File

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 65 KiB

View File

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

View File

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 63 KiB

View File

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

View File

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 81 KiB

View File

Before

Width:  |  Height:  |  Size: 125 KiB

After

Width:  |  Height:  |  Size: 125 KiB

View File

Before

Width:  |  Height:  |  Size: 143 KiB

After

Width:  |  Height:  |  Size: 143 KiB

View File

Before

Width:  |  Height:  |  Size: 164 KiB

After

Width:  |  Height:  |  Size: 164 KiB

View File

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 96 KiB

View File

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 112 KiB

View File

Before

Width:  |  Height:  |  Size: 119 KiB

After

Width:  |  Height:  |  Size: 119 KiB

View File

@ -1,16 +1,21 @@
import random import random
from .. import constants as c from .. import constants as c
# 记录植物种植情况的地图管理工具 # 记录植物种植情况的地图管理工具
class Map(): class Map:
def __init__(self, background_type): def __init__(self, background_type: int):
self.background_type = background_type self.background_type = background_type
# 注意从0开始编号 # 注意从0开始编号
if self.background_type in c.POOL_EQUIPPED_BACKGROUNDS: if self.background_type in c.POOL_EQUIPPED_BACKGROUNDS:
self.width = c.GRID_POOL_X_LEN self.width = c.GRID_POOL_X_LEN
self.height = c.GRID_POOL_Y_LEN self.height = c.GRID_POOL_Y_LEN
self.grid_height_size = c.GRID_POOL_Y_SIZE self.grid_height_size = c.GRID_POOL_Y_SIZE
self.map = [ [self.initMapGrid(c.MAP_WATER) if 2 <= y <= 3 self.map = [
[
self.initMapGrid(c.MAP_WATER)
if 2 <= y <= 3
else self.initMapGrid(c.MAP_GRASS) else self.initMapGrid(c.MAP_GRASS)
for x in range(self.width) for x in range(self.width)
] ]
@ -20,16 +25,18 @@ class Map():
self.width = c.GRID_ROOF_X_LEN self.width = c.GRID_ROOF_X_LEN
self.height = c.GRID_ROOF_Y_LEN self.height = c.GRID_ROOF_Y_LEN
self.grid_height_size = c.GRID_ROOF_Y_SIZE self.grid_height_size = c.GRID_ROOF_Y_SIZE
self.map = [ [self.initMapGrid(c.MAP_TILE) self.map = [
for x in range(self.width) [self.initMapGrid(c.MAP_TILE) for x in range(self.width)]
]
for y in range(self.height) for y in range(self.height)
] ]
elif self.background_type == c.BACKGROUND_SINGLE: elif self.background_type == c.BACKGROUND_SINGLE:
self.width = c.GRID_X_LEN self.width = c.GRID_X_LEN
self.height = c.GRID_Y_LEN self.height = c.GRID_Y_LEN
self.grid_height_size = c.GRID_Y_SIZE self.grid_height_size = c.GRID_Y_SIZE
self.map = [ [self.initMapGrid(c.MAP_GRASS) if y ==2 self.map = [
[
self.initMapGrid(c.MAP_GRASS)
if y == 2
else self.initMapGrid(c.MAP_UNAVAILABLE) else self.initMapGrid(c.MAP_UNAVAILABLE)
for x in range(self.width) for x in range(self.width)
] ]
@ -39,7 +46,10 @@ class Map():
self.width = c.GRID_X_LEN self.width = c.GRID_X_LEN
self.height = c.GRID_Y_LEN self.height = c.GRID_Y_LEN
self.grid_height_size = c.GRID_Y_SIZE self.grid_height_size = c.GRID_Y_SIZE
self.map = [ [self.initMapGrid(c.MAP_GRASS) if 1 <= y <= 3 self.map = [
[
self.initMapGrid(c.MAP_GRASS)
if 1 <= y <= 3
else self.initMapGrid(c.MAP_UNAVAILABLE) else self.initMapGrid(c.MAP_UNAVAILABLE)
for x in range(self.width) for x in range(self.width)
] ]
@ -49,54 +59,66 @@ class Map():
self.width = c.GRID_X_LEN self.width = c.GRID_X_LEN
self.height = c.GRID_Y_LEN self.height = c.GRID_Y_LEN
self.grid_height_size = c.GRID_Y_SIZE self.grid_height_size = c.GRID_Y_SIZE
self.map = [ [self.initMapGrid(c.MAP_GRASS) self.map = [
for x in range(self.width) [self.initMapGrid(c.MAP_GRASS) for x in range(self.width)]
]
for y in range(self.height) for y in range(self.height)
] ]
def isValid(self, map_x, map_y): def isValid(self, map_x: int, map_y: int) -> bool:
if ((0 <= map_x < self.width) if (0 <= map_x < self.width) and (0 <= map_y < self.height):
and (0 <= map_y < self.height)):
return True return True
return False return False
# 地图单元格状态 # 地图单元格状态
# 注意是可变对象,不能直接引用 # 注意是可变对象,不能直接引用
# 由于同一格显然不可能种两个相同的植物,所以用集合 # 由于同一格显然不可能种两个相同的植物,所以用集合
def initMapGrid(self, plot_type): def initMapGrid(self, plot_type: str) -> set:
return {c.MAP_PLANT:set(), c.MAP_SLEEP:False, c.MAP_PLOT_TYPE:plot_type} return {
c.MAP_PLANT: set(),
c.MAP_SLEEP: False,
c.MAP_PLOT_TYPE: plot_type,
}
# 判断位置是否可用 # 判断位置是否可用
# 暂时没有写紫卡植物的判断方法 # 暂时没有写紫卡植物的判断方法
# 由于紫卡植物需要移除以前的植物,所以可用另外定义一个函数 # 由于紫卡植物需要移除以前的植物,所以可用另外定义一个函数
def isAvailable(self, map_x, map_y, plant_name): def isAvailable(self, map_x: int, map_y: int, plant_name: str) -> bool:
# 咖啡豆和墓碑吞噬者的判别最为特殊 # 咖啡豆和墓碑吞噬者的判别最为特殊
if plant_name == c.COFFEEBEAN: if plant_name == c.COFFEEBEAN:
if (self.map[map_y][map_x][c.MAP_SLEEP] if self.map[map_y][map_x][c.MAP_SLEEP] and (
and (plant_name not in self.map[map_y][map_x][c.MAP_PLANT])): plant_name not in self.map[map_y][map_x][c.MAP_PLANT]
):
return True return True
else: else:
return False return False
if plant_name == c.GRAVEBUSTER: if plant_name == c.GRAVEBUSTER:
if (c.GRAVE in self.map[map_y][map_x][c.MAP_PLANT] if c.GRAVE in self.map[map_y][map_x][c.MAP_PLANT] and (
and (plant_name not in self.map[map_y][map_x][c.MAP_PLANT])): plant_name not in self.map[map_y][map_x][c.MAP_PLANT]
):
return True return True
else: else:
return False return False
# 被非植物障碍占据的格子对于一般植物不可种植 # 被非植物障碍占据的格子对于一般植物不可种植
if any((i in c.NON_PLANT_OBJECTS) for i in self.map[map_y][map_x][c.MAP_PLANT]): if any(
(i in c.NON_PLANT_OBJECTS)
for i in self.map[map_y][map_x][c.MAP_PLANT]
):
return False return False
if self.map[map_y][map_x][c.MAP_PLOT_TYPE] == c.MAP_GRASS: # 草地 if self.map[map_y][map_x][c.MAP_PLOT_TYPE] == c.MAP_GRASS: # 草地
# 首先需要判断植物是否是水生植物,水生植物不能种植在陆地上 # 首先需要判断植物是否是水生植物,水生植物不能种植在陆地上
if plant_name not in c.WATER_PLANTS: if plant_name not in c.WATER_PLANTS:
if not self.map[map_y][map_x][c.MAP_PLANT]: # 没有植物肯定可以种植 if not self.map[map_y][map_x][c.MAP_PLANT]: # 没有植物肯定可以种植
return True return True
elif (all((i in {"花盆(未实现)", c.PUMPKINHEAD}) for i in self.map[map_y][map_x][c.MAP_PLANT]) elif all(
and (plant_name not in self.map[map_y][map_x][c.MAP_PLANT])): # 例外植物:集合中填花盆和南瓜头,只要这里没有这种植物就能种植 (i in {'花盆(未实现)', c.PUMPKINHEAD})
for i in self.map[map_y][map_x][c.MAP_PLANT]
) and (
plant_name not in self.map[map_y][map_x][c.MAP_PLANT]
): # 例外植物:集合中填花盆和南瓜头,只要这里没有这种植物就能种植
return True return True
elif ((plant_name == c.PUMPKINHEAD) elif (plant_name == c.PUMPKINHEAD) and (
and (c.PUMPKINHEAD not in self.map[map_y][map_x][c.MAP_PLANT])): # 没有南瓜头就能种南瓜头 c.PUMPKINHEAD not in self.map[map_y][map_x][c.MAP_PLANT]
): # 没有南瓜头就能种南瓜头
return True return True
else: else:
return False return False
@ -105,19 +127,25 @@ class Map():
elif self.map[map_y][map_x][c.MAP_PLOT_TYPE] == c.MAP_TILE: # 屋顶 elif self.map[map_y][map_x][c.MAP_PLOT_TYPE] == c.MAP_TILE: # 屋顶
# 首先需要判断植物是否是水生植物,水生植物不能种植在陆地上 # 首先需要判断植物是否是水生植物,水生植物不能种植在陆地上
if plant_name not in c.WATER_PLANTS: if plant_name not in c.WATER_PLANTS:
if "花盆(未实现)" in self.map[map_y][map_x][c.MAP_PLANT]: if '花盆(未实现)' in self.map[map_y][map_x][c.MAP_PLANT]:
if (all((i in {"花盆(未实现)", c.PUMPKINHEAD}) for i in self.map[map_y][map_x][c.MAP_PLANT]) if all(
and (plant_name not in self.map[map_y][map_x][c.MAP_PLANT])): # 例外植物:集合中填花盆和南瓜头,只要这里没有这种植物就能种植 (i in {'花盆(未实现)', c.PUMPKINHEAD})
for i in self.map[map_y][map_x][c.MAP_PLANT]
) and (
plant_name not in self.map[map_y][map_x][c.MAP_PLANT]
): # 例外植物:集合中填花盆和南瓜头,只要这里没有这种植物就能种植
if plant_name in {c.SPIKEWEED}: # 不能在花盆上种植的植物 if plant_name in {c.SPIKEWEED}: # 不能在花盆上种植的植物
return False return False
else: else:
return True return True
elif ((plant_name == c.PUMPKINHEAD) elif (plant_name == c.PUMPKINHEAD) and (
and (c.PUMPKINHEAD not in self.map[map_y][map_x][c.MAP_PLANT])): # 有花盆且没有南瓜头就能种南瓜头 c.PUMPKINHEAD
not in self.map[map_y][map_x][c.MAP_PLANT]
): # 有花盆且没有南瓜头就能种南瓜头
return True return True
else: else:
return False return False
elif plant_name == "花盆(未实现)": # 这一格本来没有花盆而且新来的植物是花盆,可以种 elif plant_name == '花盆(未实现)': # 这一格本来没有花盆而且新来的植物是花盆,可以种
return True return True
else: else:
return False return False
@ -125,20 +153,32 @@ class Map():
return False return False
elif self.map[map_y][map_x][c.MAP_PLOT_TYPE] == c.MAP_WATER: # 水里 elif self.map[map_y][map_x][c.MAP_PLOT_TYPE] == c.MAP_WATER: # 水里
if plant_name in c.WATER_PLANTS: # 是水生植物 if plant_name in c.WATER_PLANTS: # 是水生植物
if not self.map[map_y][map_x][c.MAP_PLANT]: # 只有无植物时才能在水里种植水生植物 if not self.map[map_y][map_x][
c.MAP_PLANT
]: # 只有无植物时才能在水里种植水生植物
return True return True
else: else:
return False return False
else: # 非水生植物,依赖睡莲 else: # 非水生植物,依赖睡莲
if c.LILYPAD in self.map[map_y][map_x][c.MAP_PLANT]: if c.LILYPAD in self.map[map_y][map_x][c.MAP_PLANT]:
if (all((i in {c.LILYPAD, c.PUMPKINHEAD}) for i in self.map[map_y][map_x][c.MAP_PLANT]) if all(
and (plant_name not in self.map[map_y][map_x][c.MAP_PLANT])): (i in {c.LILYPAD, c.PUMPKINHEAD})
if plant_name in {c.SPIKEWEED, c.POTATOMINE, "花盆(未实现)"}: # 不能在睡莲上种植的植物 for i in self.map[map_y][map_x][c.MAP_PLANT]
) and (
plant_name not in self.map[map_y][map_x][c.MAP_PLANT]
):
if plant_name in {
c.SPIKEWEED,
c.POTATOMINE,
'花盆(未实现)',
}: # 不能在睡莲上种植的植物
return False return False
else: else:
return True return True
elif ((plant_name == c.PUMPKINHEAD) elif (plant_name == c.PUMPKINHEAD) and (
and (c.PUMPKINHEAD not in self.map[map_y][map_x][c.MAP_PLANT])): # 在睡莲上且没有南瓜头就能种南瓜头 c.PUMPKINHEAD
not in self.map[map_y][map_x][c.MAP_PLANT]
): # 在睡莲上且没有南瓜头就能种南瓜头
return True return True
else: else:
return False return False
@ -147,7 +187,7 @@ class Map():
else: # 不可种植区域 else: # 不可种植区域
return False return False
def getMapIndex(self, x, y): def getMapIndex(self, x: int, y: int) -> tuple[int, int]:
if self.background_type in c.POOL_EQUIPPED_BACKGROUNDS: if self.background_type in c.POOL_EQUIPPED_BACKGROUNDS:
x -= c.MAP_POOL_OFFSET_X x -= c.MAP_POOL_OFFSET_X
y -= c.MAP_POOL_OFFSET_Y y -= c.MAP_POOL_OFFSET_Y
@ -166,153 +206,189 @@ class Map():
y -= c.MAP_OFFSET_Y y -= c.MAP_OFFSET_Y
return (x // c.GRID_X_SIZE, y // c.GRID_Y_SIZE) return (x // c.GRID_X_SIZE, y // c.GRID_Y_SIZE)
def getMapGridPos(self, map_x, map_y): def getMapGridPos(self, map_x: int, map_y: int) -> tuple[int, int]:
if self.background_type in c.POOL_EQUIPPED_BACKGROUNDS: if self.background_type in c.POOL_EQUIPPED_BACKGROUNDS:
return (map_x * c.GRID_POOL_X_SIZE + c.GRID_POOL_X_SIZE//2 + c.MAP_POOL_OFFSET_X, return (
map_y * c.GRID_POOL_Y_SIZE + c.GRID_POOL_Y_SIZE//5 * 3 + c.MAP_POOL_OFFSET_Y) map_x * c.GRID_POOL_X_SIZE
+ c.GRID_POOL_X_SIZE // 2
+ c.MAP_POOL_OFFSET_X,
map_y * c.GRID_POOL_Y_SIZE
+ c.GRID_POOL_Y_SIZE // 5 * 3
+ c.MAP_POOL_OFFSET_Y,
)
elif self.background_type in c.ON_ROOF_BACKGROUNDS: elif self.background_type in c.ON_ROOF_BACKGROUNDS:
return (map_x * c.GRID_ROOF_X_SIZE + c.GRID_ROOF_X_SIZE//2 + c.MAP_ROOF_OFFSET_X, return (
map_y * c.GRID_ROOF_Y_SIZE + 20 * max(0, (6 - map_y)) + c.GRID_ROOF_Y_SIZE//5 * 3 + c.MAP_POOL_OFFSET_Y) map_x * c.GRID_ROOF_X_SIZE
+ c.GRID_ROOF_X_SIZE // 2
+ c.MAP_ROOF_OFFSET_X,
map_y * c.GRID_ROOF_Y_SIZE
+ 20 * max(0, (6 - map_y))
+ c.GRID_ROOF_Y_SIZE // 5 * 3
+ c.MAP_POOL_OFFSET_Y,
)
else: else:
return (map_x * c.GRID_X_SIZE + c.GRID_X_SIZE//2 + c.MAP_OFFSET_X, return (
map_y * c.GRID_Y_SIZE + c.GRID_Y_SIZE//5 * 3 + c.MAP_OFFSET_Y) map_x * c.GRID_X_SIZE + c.GRID_X_SIZE // 2 + c.MAP_OFFSET_X,
map_y * c.GRID_Y_SIZE
+ c.GRID_Y_SIZE // 5 * 3
+ c.MAP_OFFSET_Y,
)
def setMapGridType(self, map_x, map_y, plot_type): def setMapGridType(self, map_x: int, map_y: int, plot_type: str):
self.map[map_y][map_x][c.MAP_PLOT_TYPE] = plot_type self.map[map_y][map_x][c.MAP_PLOT_TYPE] = plot_type
def addMapPlant(self, map_x, map_y, plant_name, sleep=False): def addMapPlant(
self, map_x: int, map_y: int, plant_name: int, sleep: bool = False
):
self.map[map_y][map_x][c.MAP_PLANT].add(plant_name) self.map[map_y][map_x][c.MAP_PLANT].add(plant_name)
self.map[map_y][map_x][c.MAP_SLEEP] = sleep self.map[map_y][map_x][c.MAP_SLEEP] = sleep
def removeMapPlant(self, map_x, map_y, plant_name): def removeMapPlant(self, map_x: int, map_y: int, plant_name: str):
self.map[map_y][map_x][c.MAP_PLANT].discard(plant_name) self.map[map_y][map_x][c.MAP_PLANT].discard(plant_name)
def getRandomMapIndex(self): def getRandomMapIndex(self) -> tuple[int, int]:
map_x = random.randint(0, self.width - 1) map_x = random.randint(0, self.width - 1)
map_y = random.randint(0, self.height - 1) map_y = random.randint(0, self.height - 1)
return (map_x, map_y) return (map_x, map_y)
def checkPlantToSeed(self, x, y, plant_name): def checkPlantToSeed(
self, x: int, y: int, plant_name: str
) -> tuple[int, int]:
pos = None pos = None
map_x, map_y = self.getMapIndex(x, y) map_x, map_y = self.getMapIndex(x, y)
if self.isValid(map_x, map_y) and self.isAvailable(map_x, map_y, plant_name): if self.isValid(map_x, map_y) and self.isAvailable(
map_x, map_y, plant_name
):
pos = self.getMapGridPos(map_x, map_y) pos = self.getMapGridPos(map_x, map_y)
return pos return pos
# 保存具体关卡地图信息常数 # 保存具体关卡地图信息常数
# 冒险模式地图 # 冒险模式地图
LEVEL_MAP_DATA = ( LEVEL_MAP_DATA = (
# 第0关测试模式地图 # 第0关测试模式地图
{ {
c.BACKGROUND_TYPE: 2, c.BACKGROUND_TYPE: 2,
c.GAME_TITLE: "隐藏测试关卡", c.GAME_TITLE: '隐藏测试关卡',
c.INIT_SUN_NAME: 5000, c.INIT_SUN_NAME: 5000,
c.SHOVEL: 1, c.SHOVEL: 1,
c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_LIST, c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_LIST,
c.ZOMBIE_LIST:[ c.ZOMBIE_LIST: (
{"time":0, "map_y":5, "name":"Zomboni"}, {'time': 0, 'map_y': 5, 'name': 'Zomboni'},
{"time":1000, "map_y":4, "name":"ScreenDoorZombie"}, {'time': 1000, 'map_y': 4, 'name': 'ScreenDoorZombie'},
{"time":2000, "map_y":4, "name":"ScreenDoorZombie"}, {'time': 2000, 'map_y': 4, 'name': 'ScreenDoorZombie'},
{"time":3100, "map_y":4, "name":"ScreenDoorZombie"}, {'time': 3100, 'map_y': 4, 'name': 'ScreenDoorZombie'},
{"time":4500, "map_y":4, "name":"ScreenDoorZombie"}, {'time': 4500, 'map_y': 4, 'name': 'ScreenDoorZombie'},
{"time":5000, "map_y":4, "name":"ScreenDoorZombie"}, {'time': 5000, 'map_y': 4, 'name': 'ScreenDoorZombie'},
{"time":6000, "map_y":4, "name":"ScreenDoorZombie"}, {'time': 6000, 'map_y': 4, 'name': 'ScreenDoorZombie'},
{"time":7000, "map_y":4, "name":"ScreenDoorZombie"}, {'time': 7000, 'map_y': 4, 'name': 'ScreenDoorZombie'},
{"time":8000, "map_y":4, "name":"ScreenDoorZombie"}, {'time': 8000, 'map_y': 4, 'name': 'ScreenDoorZombie'},
{"time":0, "map_y":1, "name":"NewspaperZombie"}, {'time': 0, 'map_y': 1, 'name': 'NewspaperZombie'},
{"time":0, "map_y":0, "name":"PoleVaultingZombie"}, {'time': 0, 'map_y': 0, 'name': 'PoleVaultingZombie'},
{"time":6000, "map_y":0, "name":"FootballZombie"}, {'time': 6000, 'map_y': 0, 'name': 'FootballZombie'},
{"time":0, "map_y":3, "name":"ConeheadDuckyTubeZombie"}, {'time': 0, 'map_y': 3, 'name': 'ConeheadDuckyTubeZombie'},
{"time":0, "map_y":2, "name":"SnorkelZombie"}, {'time': 0, 'map_y': 2, 'name': 'SnorkelZombie'},
{"time":90000, "map_y":2, "name":"ConeheadDuckyTubeZombie"} {'time': 90000, 'map_y': 2, 'name': 'ConeheadDuckyTubeZombie'},
] ),
}, },
# 第1关单行草皮 # 第1关单行草皮
{ {
c.BACKGROUND_TYPE: 7, c.BACKGROUND_TYPE: 7,
c.GAME_TITLE: "白天 1-1", c.GAME_TITLE: '白天 1-1',
c.INIT_SUN_NAME: 150, c.INIT_SUN_NAME: 150,
c.SHOVEL: 1, c.SHOVEL: 1,
c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO, c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO,
c.INCLUDED_ZOMBIES: (c.NORMAL_ZOMBIE,), c.INCLUDED_ZOMBIES: (c.NORMAL_ZOMBIE,),
c.NUM_FLAGS:1 c.NUM_FLAGS: 1,
}, },
# 第2关三行草皮 # 第2关三行草皮
{ {
c.BACKGROUND_TYPE: 8, c.BACKGROUND_TYPE: 8,
c.GAME_TITLE: "白天 1-2", c.GAME_TITLE: '白天 1-2',
c.INIT_SUN_NAME: 50, c.INIT_SUN_NAME: 50,
c.SHOVEL: 1, c.SHOVEL: 1,
c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO, c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO,
c.INCLUDED_ZOMBIES: (c.NORMAL_ZOMBIE,), c.INCLUDED_ZOMBIES: (c.NORMAL_ZOMBIE,),
c.NUM_FLAGS:1 c.NUM_FLAGS: 1,
}, },
# 第3关 # 第3关
{ {
c.BACKGROUND_TYPE: 0, c.BACKGROUND_TYPE: 0,
c.GAME_TITLE: "白天 1-3", c.GAME_TITLE: '白天 1-3',
c.INIT_SUN_NAME: 50, c.INIT_SUN_NAME: 50,
c.SHOVEL: 1, c.SHOVEL: 1,
c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO, c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO,
c.INCLUDED_ZOMBIES: (c.NORMAL_ZOMBIE,), c.INCLUDED_ZOMBIES: (c.NORMAL_ZOMBIE,),
c.NUM_FLAGS:2 c.NUM_FLAGS: 2,
}, },
# 第4关 # 第4关
{ {
c.BACKGROUND_TYPE: 0, c.BACKGROUND_TYPE: 0,
c.GAME_TITLE: "白天 1-4", c.GAME_TITLE: '白天 1-4',
c.INIT_SUN_NAME: 50, c.INIT_SUN_NAME: 50,
c.SHOVEL: 1, c.SHOVEL: 1,
c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO, c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO,
c.INCLUDED_ZOMBIES: (c.NORMAL_ZOMBIE, c.CONEHEAD_ZOMBIE, c.POLE_VAULTING_ZOMBIE), c.INCLUDED_ZOMBIES: (
c.NUM_FLAGS:2 c.NORMAL_ZOMBIE,
c.CONEHEAD_ZOMBIE,
c.POLE_VAULTING_ZOMBIE,
),
c.NUM_FLAGS: 2,
}, },
# 第5关 目前白天最后一关 # 第5关 目前白天最后一关
{ {
c.BACKGROUND_TYPE: 0, c.BACKGROUND_TYPE: 0,
c.GAME_TITLE: "白天 1-5", c.GAME_TITLE: '白天 1-5',
c.INIT_SUN_NAME: 50, c.INIT_SUN_NAME: 50,
c.SHOVEL: 1, c.SHOVEL: 1,
c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO, c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO,
c.INCLUDED_ZOMBIES: ( c.NORMAL_ZOMBIE, c.CONEHEAD_ZOMBIE, c.INCLUDED_ZOMBIES: (
c.POLE_VAULTING_ZOMBIE, c.BUCKETHEAD_ZOMBIE), c.NORMAL_ZOMBIE,
c.NUM_FLAGS:3 c.CONEHEAD_ZOMBIE,
c.POLE_VAULTING_ZOMBIE,
c.BUCKETHEAD_ZOMBIE,
),
c.NUM_FLAGS: 3,
}, },
# 第6关 目前夜晚第一关 # 第6关 目前夜晚第一关
{ {
c.BACKGROUND_TYPE: 1, c.BACKGROUND_TYPE: 1,
c.GAME_TITLE: "黑夜 2-1", c.GAME_TITLE: '黑夜 2-1',
c.INIT_SUN_NAME: 50, c.INIT_SUN_NAME: 50,
c.SHOVEL: 1, c.SHOVEL: 1,
c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO, c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO,
c.INCLUDED_ZOMBIES: ( c.NORMAL_ZOMBIE, c.INCLUDED_ZOMBIES: (c.NORMAL_ZOMBIE, c.NEWSPAPER_ZOMBIE),
c.NEWSPAPER_ZOMBIE), c.NUM_FLAGS: 2,
c.NUM_FLAGS:2
}, },
# 第7关 # 第7关
{ {
c.BACKGROUND_TYPE: 1, c.BACKGROUND_TYPE: 1,
c.GAME_TITLE: "黑夜 2-2", c.GAME_TITLE: '黑夜 2-2',
c.INIT_SUN_NAME: 50, c.INIT_SUN_NAME: 50,
c.SHOVEL: 1, c.SHOVEL: 1,
c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO, c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO,
c.INCLUDED_ZOMBIES: ( c.NORMAL_ZOMBIE, c.INCLUDED_ZOMBIES: (
c.SCREEN_DOOR_ZOMBIE,), c.NORMAL_ZOMBIE,
c.SCREEN_DOOR_ZOMBIE,
),
c.NUM_FLAGS: 2, c.NUM_FLAGS: 2,
c.GRADE_GRAVES: 2, c.GRADE_GRAVES: 2,
}, },
# 第8关 目前为夜晚最后一关 # 第8关 目前为夜晚最后一关
{ {
c.BACKGROUND_TYPE: 1, c.BACKGROUND_TYPE: 1,
c.GAME_TITLE: "黑夜 2-3", c.GAME_TITLE: '黑夜 2-3',
c.INIT_SUN_NAME: 50, c.INIT_SUN_NAME: 50,
c.SHOVEL: 1, c.SHOVEL: 1,
c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO, c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO,
c.INCLUDED_ZOMBIES: ( c.NORMAL_ZOMBIE, c.NEWSPAPER_ZOMBIE, c.INCLUDED_ZOMBIES: (
c.CONEHEAD_ZOMBIE, c.BUCKETHEAD_ZOMBIE, c.NORMAL_ZOMBIE,
c.SCREEN_DOOR_ZOMBIE, c.FOOTBALL_ZOMBIE), c.NEWSPAPER_ZOMBIE,
c.CONEHEAD_ZOMBIE,
c.BUCKETHEAD_ZOMBIE,
c.SCREEN_DOOR_ZOMBIE,
c.FOOTBALL_ZOMBIE,
),
c.INEVITABLE_ZOMBIE_DICT: { # 这里改用python实现了以后键不再用字符串改用数字 c.INEVITABLE_ZOMBIE_DICT: { # 这里改用python实现了以后键不再用字符串改用数字
# 仍然要注意字典值是元组 # 仍然要注意字典值是元组
10: (c.NEWSPAPER_ZOMBIE,), 10: (c.NEWSPAPER_ZOMBIE,),
@ -325,171 +401,224 @@ LEVEL_MAP_DATA = (
# 第9关 目前为泳池模式第一关 # 第9关 目前为泳池模式第一关
{ {
c.BACKGROUND_TYPE: 2, c.BACKGROUND_TYPE: 2,
c.GAME_TITLE: "泳池 3-1", c.GAME_TITLE: '泳池 3-1',
c.INIT_SUN_NAME: 50, c.INIT_SUN_NAME: 50,
c.SHOVEL: 1, c.SHOVEL: 1,
c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO, c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO,
c.INCLUDED_ZOMBIES: ( c.NORMAL_ZOMBIE, c.BUCKETHEAD_ZOMBIE, c.INCLUDED_ZOMBIES: (
c.CONEHEAD_ZOMBIE,), c.NORMAL_ZOMBIE,
c.NUM_FLAGS:2 c.BUCKETHEAD_ZOMBIE,
c.CONEHEAD_ZOMBIE,
),
c.NUM_FLAGS: 2,
}, },
# 第10关 # 第10关
{ {
c.BACKGROUND_TYPE: 2, c.BACKGROUND_TYPE: 2,
c.GAME_TITLE: "泳池 3-2", c.GAME_TITLE: '泳池 3-2',
c.INIT_SUN_NAME: 50, c.INIT_SUN_NAME: 50,
c.SHOVEL: 1, c.SHOVEL: 1,
c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO, c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO,
c.INCLUDED_ZOMBIES: ( c.NORMAL_ZOMBIE, c.BUCKETHEAD_ZOMBIE, c.INCLUDED_ZOMBIES: (
c.CONEHEAD_ZOMBIE, c.SNORKELZOMBIE), c.NORMAL_ZOMBIE,
c.BUCKETHEAD_ZOMBIE,
c.CONEHEAD_ZOMBIE,
c.SNORKELZOMBIE,
),
c.INEVITABLE_ZOMBIE_DICT: {30: (c.SNORKELZOMBIE,)}, c.INEVITABLE_ZOMBIE_DICT: {30: (c.SNORKELZOMBIE,)},
c.NUM_FLAGS:3 c.NUM_FLAGS: 3,
}, },
# 第11关 # 第11关
{ {
c.BACKGROUND_TYPE: 2, c.BACKGROUND_TYPE: 2,
c.GAME_TITLE: "泳池 3-3", c.GAME_TITLE: '泳池 3-3',
c.INIT_SUN_NAME: 50, c.INIT_SUN_NAME: 50,
c.SHOVEL: 1, c.SHOVEL: 1,
c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO, c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO,
c.INCLUDED_ZOMBIES: (c.NORMAL_ZOMBIE, c.ZOMBONI), c.INCLUDED_ZOMBIES: (c.NORMAL_ZOMBIE, c.ZOMBONI),
c.INEVITABLE_ZOMBIE_DICT: {30: (c.ZOMBONI,)}, c.INEVITABLE_ZOMBIE_DICT: {30: (c.ZOMBONI,)},
c.NUM_FLAGS:3 c.NUM_FLAGS: 3,
}, },
# 第12关 目前为泳池最后一关 # 第12关 目前为泳池最后一关
{ {
c.BACKGROUND_TYPE: 2, c.BACKGROUND_TYPE: 2,
c.GAME_TITLE: "泳池 3-4", c.GAME_TITLE: '泳池 3-4',
c.INIT_SUN_NAME: 50, c.INIT_SUN_NAME: 50,
c.SHOVEL: 1, c.SHOVEL: 1,
c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO, c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO,
c.INCLUDED_ZOMBIES: ( c.NORMAL_ZOMBIE, c.ZOMBONI, c.INCLUDED_ZOMBIES: (
c.NORMAL_ZOMBIE,
c.ZOMBONI,
c.BUCKETHEAD_ZOMBIE, c.BUCKETHEAD_ZOMBIE,
c.CONEHEAD_ZOMBIE, c.SNORKELZOMBIE), c.CONEHEAD_ZOMBIE,
c.SNORKELZOMBIE,
),
c.INEVITABLE_ZOMBIE_DICT: {40: (c.ZOMBONI,)}, c.INEVITABLE_ZOMBIE_DICT: {40: (c.ZOMBONI,)},
c.NUM_FLAGS:4 c.NUM_FLAGS: 4,
}, },
# 第13关 目前为浓雾第一关 尚未完善 # 第13关 目前为浓雾第一关 尚未完善
{ {
c.BACKGROUND_TYPE: 3, c.BACKGROUND_TYPE: 3,
c.GAME_TITLE: "浓雾 4-1", c.GAME_TITLE: '浓雾 4-1',
c.INIT_SUN_NAME: 50, c.INIT_SUN_NAME: 50,
c.SHOVEL: 1, c.SHOVEL: 1,
c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO, c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO,
c.INCLUDED_ZOMBIES: ( c.NORMAL_ZOMBIE, c.NEWSPAPER_ZOMBIE, c.INCLUDED_ZOMBIES: (
c.ZOMBONI, c.FOOTBALL_ZOMBIE, c.NORMAL_ZOMBIE,
c.CONEHEAD_ZOMBIE, c.BUCKETHEAD_ZOMBIE), c.NEWSPAPER_ZOMBIE,
c.NUM_FLAGS:4 c.ZOMBONI,
c.FOOTBALL_ZOMBIE,
c.CONEHEAD_ZOMBIE,
c.BUCKETHEAD_ZOMBIE,
),
c.NUM_FLAGS: 4,
}, },
) )
# 玩玩小游戏地图 # 玩玩小游戏地图
LITTLE_GAME_MAP_DATA = ( LITTLE_GAME_MAP_DATA = (
# 第0关 测试 # 第0关 测试
{ {
c.BACKGROUND_TYPE: 3, c.BACKGROUND_TYPE: 3,
c.GAME_TITLE: "隐藏测试关卡", c.GAME_TITLE: '隐藏测试关卡',
c.CHOOSEBAR_TYPE: c.CHOOSEBAR_MOVE, c.CHOOSEBAR_TYPE: c.CHOOSEBAR_MOVE,
c.SHOVEL: 1, c.SHOVEL: 1,
c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO, c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO,
c.INCLUDED_ZOMBIES: ( c.NORMAL_ZOMBIE, c.NEWSPAPER_ZOMBIE, c.INCLUDED_ZOMBIES: (
c.ZOMBONI, c.FOOTBALL_ZOMBIE, c.NORMAL_ZOMBIE,
c.CONEHEAD_ZOMBIE, c.BUCKETHEAD_ZOMBIE), c.NEWSPAPER_ZOMBIE,
c.ZOMBONI,
c.FOOTBALL_ZOMBIE,
c.CONEHEAD_ZOMBIE,
c.BUCKETHEAD_ZOMBIE,
),
c.NUM_FLAGS: 4, c.NUM_FLAGS: 4,
c.CARD_POOL: { c.LILYPAD: 300, c.CARD_POOL: {
c.LILYPAD: 300,
c.STARFRUIT: 400, c.STARFRUIT: 400,
c.PUMPKINHEAD: 100, c.PUMPKINHEAD: 100,
c.SEASHROOM: 100, c.SEASHROOM: 100,
c.SPIKEWEED: 100, c.SPIKEWEED: 100,
} },
}, },
# 第1关 坚果保龄球 # 第1关 坚果保龄球
{ {
c.BACKGROUND_TYPE: 6, c.BACKGROUND_TYPE: 6,
c.GAME_TITLE: "坚果保龄球", c.GAME_TITLE: '坚果保龄球',
c.CHOOSEBAR_TYPE: c.CHOOSEBAR_BOWLING, c.CHOOSEBAR_TYPE: c.CHOOSEBAR_BOWLING,
c.SHOVEL: 0, c.SHOVEL: 0,
c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO, c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO,
c.INCLUDED_ZOMBIES: ( c.NORMAL_ZOMBIE, c.CONEHEAD_ZOMBIE, c.INCLUDED_ZOMBIES: (
c.POLE_VAULTING_ZOMBIE, c.BUCKETHEAD_ZOMBIE,), c.NORMAL_ZOMBIE,
c.CONEHEAD_ZOMBIE,
c.POLE_VAULTING_ZOMBIE,
c.BUCKETHEAD_ZOMBIE,
),
c.NUM_FLAGS: 2, c.NUM_FLAGS: 2,
c.CARD_POOL: { c.WALLNUTBOWLING: 300, c.CARD_POOL: {
c.REDWALLNUTBOWLING: 100,} c.WALLNUTBOWLING: 300,
c.REDWALLNUTBOWLING: 100,
},
}, },
# 第2关 白天 大决战 # 第2关 白天 大决战
{ {
c.BACKGROUND_TYPE: 0, c.BACKGROUND_TYPE: 0,
c.GAME_TITLE: "大决战(白天)", c.GAME_TITLE: '大决战(白天)',
c.CHOOSEBAR_TYPE: c.CHOOSEBAR_MOVE, c.CHOOSEBAR_TYPE: c.CHOOSEBAR_MOVE,
c.SHOVEL: 1, c.SHOVEL: 1,
c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO, c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO,
c.INCLUDED_ZOMBIES: ( c.NORMAL_ZOMBIE, c.CONEHEAD_ZOMBIE, c.INCLUDED_ZOMBIES: (
c.POLE_VAULTING_ZOMBIE, c.BUCKETHEAD_ZOMBIE,), c.NORMAL_ZOMBIE,
c.CONEHEAD_ZOMBIE,
c.POLE_VAULTING_ZOMBIE,
c.BUCKETHEAD_ZOMBIE,
),
c.NUM_FLAGS: 3, c.NUM_FLAGS: 3,
c.CARD_POOL: { c.PEASHOOTER: 200, c.CARD_POOL: {
c.PEASHOOTER: 200,
c.SNOWPEASHOOTER: 100, c.SNOWPEASHOOTER: 100,
c.WALLNUT: 100, c.WALLNUT: 100,
c.CHERRYBOMB: 100, c.CHERRYBOMB: 100,
c.REPEATERPEA: 200, c.REPEATERPEA: 200,
c.CHOMPER: 100, c.CHOMPER: 100,
c.POTATOMINE: 100,} c.POTATOMINE: 100,
},
}, },
# 第3关 夜晚 大决战 # 第3关 夜晚 大决战
{ {
c.BACKGROUND_TYPE: 1, c.BACKGROUND_TYPE: 1,
c.GAME_TITLE: "大决战(黑夜)", c.GAME_TITLE: '大决战(黑夜)',
c.CHOOSEBAR_TYPE: c.CHOOSEBAR_MOVE, c.CHOOSEBAR_TYPE: c.CHOOSEBAR_MOVE,
c.SHOVEL: 1, c.SHOVEL: 1,
c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO, c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO,
c.INCLUDED_ZOMBIES: ( c.NORMAL_ZOMBIE, c.CONEHEAD_ZOMBIE, c.INCLUDED_ZOMBIES: (
c.FOOTBALL_ZOMBIE, c.BUCKETHEAD_ZOMBIE, c.NORMAL_ZOMBIE,
c.NEWSPAPER_ZOMBIE, c.SCREEN_DOOR_ZOMBIE), c.CONEHEAD_ZOMBIE,
c.FOOTBALL_ZOMBIE,
c.BUCKETHEAD_ZOMBIE,
c.NEWSPAPER_ZOMBIE,
c.SCREEN_DOOR_ZOMBIE,
),
c.NUM_FLAGS: 3, c.NUM_FLAGS: 3,
c.CARD_POOL: { c.PUFFSHROOM: 100, c.CARD_POOL: {
c.PUFFSHROOM: 100,
c.SCAREDYSHROOM: 100, c.SCAREDYSHROOM: 100,
c.ICESHROOM: 70, c.ICESHROOM: 70,
c.HYPNOSHROOM: 100, c.HYPNOSHROOM: 100,
c.DOOMSHROOM: 50, c.DOOMSHROOM: 50,
c.GRAVEBUSTER: 100, c.GRAVEBUSTER: 100,
c.FUMESHROOM: 200}, c.FUMESHROOM: 200,
c.GRADE_GRAVES:3 },
c.GRADE_GRAVES: 3,
}, },
# 第4关 泳池 大决战 # 第4关 泳池 大决战
{ {
c.BACKGROUND_TYPE: 2, c.BACKGROUND_TYPE: 2,
c.GAME_TITLE: "大决战(泳池)", c.GAME_TITLE: '大决战(泳池)',
c.CHOOSEBAR_TYPE: c.CHOOSEBAR_MOVE, c.CHOOSEBAR_TYPE: c.CHOOSEBAR_MOVE,
c.SHOVEL: 1, c.SHOVEL: 1,
c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO, c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO,
c.INCLUDED_ZOMBIES: ( c.NORMAL_ZOMBIE, c.CONEHEAD_ZOMBIE, c.INCLUDED_ZOMBIES: (
c.SNORKELZOMBIE, c.BUCKETHEAD_ZOMBIE, c.NORMAL_ZOMBIE,
c.ZOMBONI,), c.CONEHEAD_ZOMBIE,
c.SNORKELZOMBIE,
c.BUCKETHEAD_ZOMBIE,
c.ZOMBONI,
),
c.NUM_FLAGS: 4, c.NUM_FLAGS: 4,
c.CARD_POOL: { c.LILYPAD: 300, c.CARD_POOL: {
c.LILYPAD: 300,
c.TORCHWOOD: 100, c.TORCHWOOD: 100,
c.TALLNUT: 100, c.TALLNUT: 100,
c.TANGLEKLEP: 100, c.TANGLEKLEP: 100,
c.SPIKEWEED: 100, c.SPIKEWEED: 100,
c.SQUASH: 100, c.SQUASH: 100,
c.JALAPENO: 50, c.JALAPENO: 50,
c.THREEPEASHOOTER: 400,} c.THREEPEASHOOTER: 400,
},
}, },
# 第5关 坚果保龄球2 # 第5关 坚果保龄球2
{ {
c.BACKGROUND_TYPE: 6, c.BACKGROUND_TYPE: 6,
c.GAME_TITLE: "坚果保龄球(II)", c.GAME_TITLE: '坚果保龄球(II)',
c.CHOOSEBAR_TYPE: c.CHOOSEBAR_BOWLING, c.CHOOSEBAR_TYPE: c.CHOOSEBAR_BOWLING,
c.SHOVEL: 0, c.SHOVEL: 0,
c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO, c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO,
c.INCLUDED_ZOMBIES: ( c.NORMAL_ZOMBIE, c.CONEHEAD_ZOMBIE, c.INCLUDED_ZOMBIES: (
c.POLE_VAULTING_ZOMBIE, c.BUCKETHEAD_ZOMBIE, c.NORMAL_ZOMBIE,
c.NEWSPAPER_ZOMBIE, c.SCREEN_DOOR_ZOMBIE), c.CONEHEAD_ZOMBIE,
c.POLE_VAULTING_ZOMBIE,
c.BUCKETHEAD_ZOMBIE,
c.NEWSPAPER_ZOMBIE,
c.SCREEN_DOOR_ZOMBIE,
),
c.NUM_FLAGS: 3, c.NUM_FLAGS: 3,
c.CARD_POOL: { c.WALLNUTBOWLING: 500, c.CARD_POOL: {
c.WALLNUTBOWLING: 500,
c.REDWALLNUTBOWLING: 100, c.REDWALLNUTBOWLING: 100,
c.GIANTWALLNUT:100,} c.GIANTWALLNUT: 100,
},
}, },
) )

View File

@ -1,7 +1,9 @@
import random import random
import pygame as pg import pygame as pg
from .. import tool
from .. import constants as c from .. import constants as c
from .. import tool
def getSunValueImage(sun_value): def getSunValueImage(sun_value):
@ -21,13 +23,19 @@ def getSunValueImage(sun_value):
image.set_colorkey(c.BLACK) image.set_colorkey(c.BLACK)
return image return image
def getCardPool(data): def getCardPool(data):
card_pool = {c.PLANT_CARD_INFO[c.PLANT_CARD_INDEX[card_name]]: data[card_name] card_pool = {
for card_name in data} c.PLANT_CARD_INFO[c.PLANT_CARD_INDEX[card_name]]: data[card_name]
for card_name in data
}
return card_pool return card_pool
class Card():
def __init__(self, x:int, y:int, index:int, scale:float=0.5, not_recommend=0): class Card:
def __init__(
self, x: int, y: int, index: int, scale: float = 0.5, not_recommend=0
):
self.info = c.PLANT_CARD_INFO[index] self.info = c.PLANT_CARD_INFO[index]
self.loadFrame(self.info[c.CARD_INDEX], scale) self.loadFrame(self.info[c.CARD_INDEX], scale)
self.rect = self.orig_image.get_rect() self.rect = self.orig_image.get_rect()
@ -35,11 +43,20 @@ class Card():
self.rect.y = y self.rect.y = y
# 绘制植物阳光消耗大小 # 绘制植物阳光消耗大小
font = pg.font.Font(c.FONT_PATH, 12) font = pg.font.Font(c.FONT_PATH, 12)
self.sun_cost_img = font.render(str(self.info[c.SUN_INDEX]), True, c.BLACK) self.sun_cost_img = font.render(
str(self.info[c.SUN_INDEX]), True, c.BLACK
)
self.sun_cost_img_rect = self.sun_cost_img.get_rect() self.sun_cost_img_rect = self.sun_cost_img.get_rect()
sun_cost_img_x = 32 - self.sun_cost_img_rect.w sun_cost_img_x = 32 - self.sun_cost_img_rect.w
self.orig_image.blit(self.sun_cost_img, self.orig_image.blit(
(sun_cost_img_x, 52, self.sun_cost_img_rect.w, self.sun_cost_img_rect.h)) self.sun_cost_img,
(
sun_cost_img_x,
52,
self.sun_cost_img_rect.w,
self.sun_cost_img_rect.h,
),
)
self.index = index self.index = index
self.sun_cost = self.info[c.SUN_INDEX] self.sun_cost = self.info[c.SUN_INDEX]
@ -52,7 +69,9 @@ class Card():
if self.not_recommend: if self.not_recommend:
self.orig_image.set_alpha(128) self.orig_image.set_alpha(128)
self.image = pg.Surface((self.rect.w, self.rect.h)) # 黑底 self.image = pg.Surface((self.rect.w, self.rect.h)) # 黑底
self.image.blit(self.orig_image, (0,0), (0, 0, self.rect.w, self.rect.h)) self.image.blit(
self.orig_image, (0, 0), (0, 0, self.rect.w, self.rect.h)
)
else: else:
self.image = self.orig_image self.image = self.orig_image
self.image.set_alpha(255) self.image.set_alpha(255)
@ -62,18 +81,25 @@ class Card():
rect = frame.get_rect() rect = frame.get_rect()
width, height = rect.w, rect.h width, height = rect.w, rect.h
self.orig_image = tool.get_image(frame, 0, 0, width, height, c.BLACK, scale) self.orig_image = tool.get_image(
frame, 0, 0, width, height, c.BLACK, scale
)
self.image = self.orig_image self.image = self.orig_image
def checkMouseClick(self, mouse_pos): def checkMouseClick(self, mouse_pos):
x, y = mouse_pos x, y = mouse_pos
if (self.rect.x <= x <= self.rect.right and if (
self.rect.y <= y <= self.rect.bottom): self.rect.x <= x <= self.rect.right
and self.rect.y <= y <= self.rect.bottom
):
return True return True
return False return False
def canClick(self, sun_value, current_time): def canClick(self, sun_value, current_time):
if self.sun_cost <= sun_value and (current_time - self.frozen_timer) > self.frozen_time: if (
self.sun_cost <= sun_value
and (current_time - self.frozen_timer) > self.frozen_time
):
return True return True
return False return False
@ -86,14 +112,18 @@ class Card():
if self.not_recommend % 2: if self.not_recommend % 2:
self.orig_image.set_alpha(128) self.orig_image.set_alpha(128)
self.image = pg.Surface((self.rect.w, self.rect.h)) # 黑底 self.image = pg.Surface((self.rect.w, self.rect.h)) # 黑底
self.image.blit(self.orig_image, (0,0), (0, 0, self.rect.w, self.rect.h)) self.image.blit(
self.orig_image, (0, 0), (0, 0, self.rect.w, self.rect.h)
)
else: else:
self.image = self.orig_image self.image = self.orig_image
self.image.set_alpha(255) self.image.set_alpha(255)
else: else:
self.orig_image.set_alpha(64) self.orig_image.set_alpha(64)
self.image = pg.Surface((self.rect.w, self.rect.h)) # 黑底 self.image = pg.Surface((self.rect.w, self.rect.h)) # 黑底
self.image.blit(self.orig_image, (0,0), (0, 0, self.rect.w, self.rect.h)) self.image.blit(
self.orig_image, (0, 0), (0, 0, self.rect.w, self.rect.h)
)
def setFrozenTime(self, current_time): def setFrozenTime(self, current_time):
self.frozen_timer = current_time self.frozen_timer = current_time
@ -105,16 +135,25 @@ class Card():
image = pg.Surface((self.rect.w, self.rect.h)) # 黑底 image = pg.Surface((self.rect.w, self.rect.h)) # 黑底
frozen_image = self.orig_image frozen_image = self.orig_image
frozen_image.set_alpha(128) frozen_image.set_alpha(128)
frozen_height = ((self.frozen_time - time)/self.frozen_time) * self.rect.h frozen_height = (
(self.frozen_time - time) / self.frozen_time
) * self.rect.h
image.blit(frozen_image, (0,0), (0, 0, self.rect.w, frozen_height)) image.blit(
frozen_image, (0, 0), (0, 0, self.rect.w, frozen_height)
)
self.orig_image.set_alpha(192) self.orig_image.set_alpha(192)
image.blit(self.orig_image, (0,frozen_height), image.blit(
(0, frozen_height, self.rect.w, self.rect.h - frozen_height)) self.orig_image,
(0, frozen_height),
(0, frozen_height, self.rect.w, self.rect.h - frozen_height),
)
elif self.sun_cost > sun_value: # disable status elif self.sun_cost > sun_value: # disable status
image = pg.Surface((self.rect.w, self.rect.h)) # 黑底 image = pg.Surface((self.rect.w, self.rect.h)) # 黑底
self.orig_image.set_alpha(192) self.orig_image.set_alpha(192)
image.blit(self.orig_image, (0,0), (0, 0, self.rect.w, self.rect.h)) image.blit(
self.orig_image, (0, 0), (0, 0, self.rect.w, self.rect.h)
)
elif self.clicked: elif self.clicked:
image = pg.Surface((self.rect.w, self.rect.h)) # 黑底 image = pg.Surface((self.rect.w, self.rect.h)) # 黑底
chosen_image = self.orig_image chosen_image = self.orig_image
@ -134,8 +173,9 @@ class Card():
def draw(self, surface): def draw(self, surface):
surface.blit(self.image, self.rect) surface.blit(self.image, self.rect)
# 植物栏 # 植物栏
class MenuBar(): class MenuBar:
def __init__(self, card_list, sun_value): def __init__(self, card_list, sun_value):
self.loadFrame(c.MENUBAR_BACKGROUND) self.loadFrame(c.MENUBAR_BACKGROUND)
self.rect = self.image.get_rect() self.rect = self.image.get_rect()
@ -187,7 +227,10 @@ class MenuBar():
for card in self.card_list: for card in self.card_list:
if card.checkMouseClick(mouse_pos): if card.checkMouseClick(mouse_pos):
if card.canClick(self.sun_value, self.current_time): if card.canClick(self.sun_value, self.current_time):
result = (c.PLANT_CARD_INFO[card.index][c.PLANT_NAME_INDEX], card) result = (
c.PLANT_CARD_INFO[card.index][c.PLANT_NAME_INDEX],
card,
)
else: else:
# 播放无法使用该卡片的警告音 # 播放无法使用该卡片的警告音
c.SOUND_CANNOT_CHOOSE_WARNING.play() c.SOUND_CANNOT_CHOOSE_WARNING.play()
@ -196,8 +239,10 @@ class MenuBar():
def checkMenuBarClick(self, mouse_pos): def checkMenuBarClick(self, mouse_pos):
x, y = mouse_pos x, y = mouse_pos
if (self.rect.x <= x <= self.rect.right and if (
self.rect.y <= y <= self.rect.bottom): self.rect.x <= x <= self.rect.right
and self.rect.y <= y <= self.rect.bottom
):
return True return True
return False return False
@ -229,8 +274,9 @@ class MenuBar():
for card in self.card_list: for card in self.card_list:
card.draw(surface) card.draw(surface)
# 关卡模式选植物的界面 # 关卡模式选植物的界面
class Panel(): class Panel:
def __init__(self, card_list, sun_value, background_type=c.BACKGROUND_DAY): def __init__(self, card_list, sun_value, background_type=c.BACKGROUND_DAY):
self.loadImages(sun_value) self.loadImages(sun_value)
self.selected_cards = [] self.selected_cards = []
@ -256,7 +302,6 @@ class Panel():
self.panel_rect.x = 0 self.panel_rect.x = 0
self.panel_rect.y = c.PANEL_Y_START self.panel_rect.y = c.PANEL_Y_START
self.value_image = getSunValueImage(sun_value) self.value_image = getSunValueImage(sun_value)
self.value_rect = self.value_image.get_rect() self.value_rect = self.value_image.get_rect()
self.value_rect.x = 21 self.value_rect.x = 21
@ -277,15 +322,26 @@ class Panel():
y += c.PANEL_Y_INTERNAL y += c.PANEL_Y_INTERNAL
x += c.PANEL_X_INTERNAL x += c.PANEL_X_INTERNAL
plant_name = c.PLANT_CARD_INFO[index][c.PLANT_NAME_INDEX] plant_name = c.PLANT_CARD_INFO[index][c.PLANT_NAME_INDEX]
if (plant_name in c.WATER_PLANTS if (
and self.background_type not in c.POOL_EQUIPPED_BACKGROUNDS): plant_name in c.WATER_PLANTS
and self.background_type not in c.POOL_EQUIPPED_BACKGROUNDS
):
not_recommend = c.REASON_OTHER not_recommend = c.REASON_OTHER
elif (plant_name == c.GRAVEBUSTER elif (
and self.background_type != c.BACKGROUND_NIGHT): plant_name == c.GRAVEBUSTER
and self.background_type != c.BACKGROUND_NIGHT
):
not_recommend = c.REASON_OTHER not_recommend = c.REASON_OTHER
elif (plant_name in c.CAN_SLEEP_PLANTS elif (
and self.background_type in c.DAYTIME_BACKGROUNDS): plant_name in c.CAN_SLEEP_PLANTS
and self.background_type in c.DAYTIME_BACKGROUNDS
):
not_recommend = c.REASON_WILL_SLEEP not_recommend = c.REASON_WILL_SLEEP
elif (
plant_name == c.COFFEEBEAN
and self.background_type not in c.DAYTIME_BACKGROUNDS
):
not_recommend = c.REASON_OTHER
# 还有屋顶场景,以及其他植物没有实现的植物没有写进来 # 还有屋顶场景,以及其他植物没有实现的植物没有写进来
else: else:
not_recommend = 0 not_recommend = 0
@ -311,7 +367,9 @@ class Panel():
i.not_recommend = c.REASON_WILL_SLEEP i.not_recommend = c.REASON_WILL_SLEEP
i.orig_image.set_alpha(128) i.orig_image.set_alpha(128)
i.image = pg.Surface((i.rect.w, i.rect.h)) # 黑底 i.image = pg.Surface((i.rect.w, i.rect.h)) # 黑底
i.image.blit(i.orig_image, (0,0), (0, 0, i.rect.w, i.rect.h)) i.image.blit(
i.orig_image, (0, 0), (0, 0, i.rect.w, i.rect.h)
)
if self.selected_num >= c.CARD_MAX_NUM: if self.selected_num >= c.CARD_MAX_NUM:
return return
@ -325,7 +383,9 @@ class Panel():
if card.info[c.PLANT_NAME_INDEX] == c.COFFEEBEAN: if card.info[c.PLANT_NAME_INDEX] == c.COFFEEBEAN:
for i in self.card_list: for i in self.card_list:
if i.not_recommend == c.REASON_WILL_SLEEP: if i.not_recommend == c.REASON_WILL_SLEEP:
i.not_recommend = c.REASON_SLEEP_BUT_COFFEE_BEAN i.not_recommend = (
c.REASON_SLEEP_BUT_COFFEE_BEAN
)
i.image = i.orig_image i.image = i.orig_image
i.image.set_alpha(255) i.image.set_alpha(255)
break break
@ -345,8 +405,10 @@ class Panel():
return False return False
x, y = mouse_pos x, y = mouse_pos
if (self.button_rect.x <= x <= self.button_rect.right and if (
self.button_rect.y <= y <= self.button_rect.bottom): self.button_rect.x <= x <= self.button_rect.right
and self.button_rect.y <= y <= self.button_rect.bottom
):
return True return True
return False return False
@ -368,8 +430,9 @@ class Panel():
if self.selected_num >= c.CARD_LIST_NUM: if self.selected_num >= c.CARD_LIST_NUM:
surface.blit(self.button_image, self.button_rect) surface.blit(self.button_image, self.button_rect)
# 传送带模式的卡片 # 传送带模式的卡片
class MoveCard(): class MoveCard:
def __init__(self, x, y, card_name, plant_name, scale=0.5): def __init__(self, x, y, card_name, plant_name, scale=0.5):
self.loadFrame(card_name, scale) self.loadFrame(card_name, scale)
self.rect = self.orig_image.get_rect() self.rect = self.orig_image.get_rect()
@ -389,14 +452,18 @@ class MoveCard():
rect = frame.get_rect() rect = frame.get_rect()
width, height = rect.w, rect.h width, height = rect.w, rect.h
self.orig_image = tool.get_image(frame, 0, 0, width, height, c.BLACK, scale) self.orig_image = tool.get_image(
frame, 0, 0, width, height, c.BLACK, scale
)
self.orig_rect = self.orig_image.get_rect() self.orig_rect = self.orig_image.get_rect()
self.image = self.orig_image self.image = self.orig_image
def checkMouseClick(self, mouse_pos): def checkMouseClick(self, mouse_pos):
x, y = mouse_pos x, y = mouse_pos
if (self.rect.x <= x <= self.rect.right and if (
self.rect.y <= y <= self.rect.bottom): self.rect.x <= x <= self.rect.right
and self.rect.y <= y <= self.rect.bottom
):
return True return True
return False return False
@ -408,14 +475,18 @@ class MoveCard():
self.orig_image.set_alpha(128) self.orig_image.set_alpha(128)
else: else:
self.orig_image.set_alpha(255) self.orig_image.set_alpha(255)
image.blit(self.orig_image, (0, 0), (0, 0, self.rect.w, self.rect.h)) image.blit(
self.orig_image, (0, 0), (0, 0, self.rect.w, self.rect.h)
)
self.rect.w += 1 self.rect.w += 1
else: else:
if self.clicked: if self.clicked:
image = pg.Surface([self.rect.w, self.rect.h]) # 黑底 image = pg.Surface([self.rect.w, self.rect.h]) # 黑底
self.orig_image.set_alpha(128) self.orig_image.set_alpha(128)
image.blit(self.orig_image, (0,0), (0, 0, self.rect.w, self.rect.h)) image.blit(
self.orig_image, (0, 0), (0, 0, self.rect.w, self.rect.h)
)
else: else:
self.orig_image.set_alpha(255) self.orig_image.set_alpha(255)
image = self.orig_image image = self.orig_image
@ -433,8 +504,9 @@ class MoveCard():
def draw(self, surface): def draw(self, surface):
surface.blit(self.image, self.rect) surface.blit(self.image, self.rect)
# 传送带 # 传送带
class MoveBar(): class MoveBar:
def __init__(self, card_pool): def __init__(self, card_pool):
self.loadFrame(c.MOVEBAR_BACKGROUND) self.loadFrame(c.MOVEBAR_BACKGROUND)
self.rect = self.image.get_rect() self.rect = self.image.get_rect()
@ -457,12 +529,24 @@ class MoveBar():
self.image = tool.get_image(tool.GFX[name], *frame_rect, c.WHITE, 1) self.image = tool.get_image(tool.GFX[name], *frame_rect, c.WHITE, 1)
def createCard(self): def createCard(self):
if len(self.card_list) > 0 and self.card_list[-1].rect.right > self.card_end_x: if (
len(self.card_list) > 0
and self.card_list[-1].rect.right > self.card_end_x
):
return False return False
x = self.card_end_x x = self.card_end_x
y = 6 y = 6
selected_card = random.choices(self.card_pool_name, self.card_pool_weight)[0] selected_card = random.choices(
self.card_list.append(MoveCard(x, y, selected_card[c.CARD_INDEX], selected_card[c.PLANT_NAME_INDEX])) self.card_pool_name, self.card_pool_weight
)[0]
self.card_list.append(
MoveCard(
x,
y,
selected_card[c.CARD_INDEX],
selected_card[c.PLANT_NAME_INDEX],
)
)
return True return True
def update(self, current_time): def update(self, current_time):
@ -486,8 +570,10 @@ class MoveBar():
def checkMenuBarClick(self, mouse_pos): def checkMenuBarClick(self, mouse_pos):
x, y = mouse_pos x, y = mouse_pos
if (self.rect.x <= x <= self.rect.right and if (
self.rect.y <= y <= self.rect.bottom): self.rect.x <= x <= self.rect.right
and self.rect.y <= y <= self.rect.bottom
):
return True return True
return False return False

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,30 +1,49 @@
import os import os
import pygame as pg import pygame as pg
# 用户数据及日志存储路径 # 用户数据及日志存储路径
if os.name == "nt": # Windows系统存储路径 if os.name == 'nt': # Windows系统存储路径
USERDATA_PATH = os.path.expandvars(os.path.join("%APPDATA%", "pypvz", "userdata.json")) USERDATA_PATH = os.path.expandvars(
USERLOG_PATH = os.path.expandvars(os.path.join("%APPDATA%", "pypvz", "run.log")) os.path.join('%APPDATA%', 'pypvz', 'userdata.json')
)
USERLOG_PATH = os.path.expandvars(
os.path.join('%APPDATA%', 'pypvz', 'run.log')
)
else: # 非Windows系统存储路径 else: # 非Windows系统存储路径
USERDATA_PATH = os.path.expanduser(os.path.join("~", ".config", "pypvz", "userdata.json")) USERDATA_PATH = os.path.expanduser(
USERLOG_PATH = os.path.expanduser(os.path.join("~", ".config", "pypvz", "run.log")) os.path.join('~', '.config', 'pypvz', 'userdata.json')
)
USERLOG_PATH = os.path.expanduser(
os.path.join('~', '.config', 'pypvz', 'run.log')
)
# 游戏图片资源路径 # 游戏图片资源路径
PATH_IMG_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "resources", "graphics") PATH_IMG_DIR = os.path.join(
os.path.dirname(os.path.dirname(__file__)), 'resources', 'graphics'
)
# 游戏音乐文件夹路径 # 游戏音乐文件夹路径
PATH_MUSIC_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "resources","music") PATH_MUSIC_DIR = os.path.join(
os.path.dirname(os.path.dirname(__file__)), 'resources', 'music'
)
# 窗口图标 # 窗口图标
ORIGINAL_LOGO = os.path.join(os.path.dirname(os.path.dirname(__file__)), "pypvz-exec-logo.png") ORIGINAL_LOGO = os.path.join(
os.path.dirname(os.path.dirname(__file__)), 'pypvz-exec-logo.png'
)
# 字体路径 # 字体路径
FONT_PATH = os.path.join(os.path.dirname(os.path.dirname(__file__)), "resources", "DroidSansFallback.ttf") FONT_PATH = os.path.join(
os.path.dirname(os.path.dirname(__file__)),
'resources',
'DroidSansFallback.ttf',
)
# 窗口标题 # 窗口标题
ORIGINAL_CAPTION = "pypvz" ORIGINAL_CAPTION = 'pypvz'
# 游戏模式 # 游戏模式
GAME_MODE = "mode" GAME_MODE = 'mode'
MODE_ADVENTURE = "adventure" MODE_ADVENTURE = 'adventure'
MODE_LITTLEGAME = "littleGame" MODE_LITTLEGAME = 'littleGame'
# 窗口大小 # 窗口大小
SCREEN_WIDTH = 800 SCREEN_WIDTH = 800
@ -70,63 +89,63 @@ LIGHTGRAY = (107, 108, 145)
PARCHMENT_YELLOW = (207, 146, 83) PARCHMENT_YELLOW = (207, 146, 83)
# 退出游戏按钮 # 退出游戏按钮
EXIT = "exit" EXIT = 'exit'
HELP = "help" HELP = 'help'
# 游戏界面可选的菜单 # 游戏界面可选的菜单
LITTLE_MENU = "littleMenu" LITTLE_MENU = 'littleMenu'
BIG_MENU = "bigMenu" BIG_MENU = 'bigMenu'
RESTART_BUTTON = "restartButton" RESTART_BUTTON = 'restartButton'
MAINMENU_BUTTON = "mainMenuButton" MAINMENU_BUTTON = 'mainMenuButton'
LITTLEGAME_BUTTON = "littleGameButton" LITTLEGAME_BUTTON = 'littleGameButton'
OPTION_BUTTON = "optionButton" OPTION_BUTTON = 'optionButton'
SOUND_VOLUME_BUTTON = "volumeButton" SOUND_VOLUME_BUTTON = 'volumeButton'
UNIVERSAL_BUTTON = "universalButton" UNIVERSAL_BUTTON = 'universalButton'
# 金银向日葵奖杯 # 金银向日葵奖杯
TROPHY_SUNFLOWER = "sunflowerTrophy" TROPHY_SUNFLOWER = 'sunflowerTrophy'
# 小铲子 # 小铲子
SHOVEL = "shovel" SHOVEL = 'shovel'
SHOVEL_BOX = "shovelBox" SHOVEL_BOX = 'shovelBox'
# 一大波僵尸来袭图片 # 一大波僵尸来袭图片
HUGE_WAVE_APPROCHING = "Approching" HUGE_WAVE_APPROCHING = 'Approching'
# 关卡进程图片 # 关卡进程图片
LEVEL_PROGRESS_BAR = "LevelProgressBar" LEVEL_PROGRESS_BAR = 'LevelProgressBar'
LEVEL_PROGRESS_ZOMBIE_HEAD = "LevelProgressZombieHead" LEVEL_PROGRESS_ZOMBIE_HEAD = 'LevelProgressZombieHead'
LEVEL_PROGRESS_FLAG = "LevelProgressFlag" LEVEL_PROGRESS_FLAG = 'LevelProgressFlag'
# GAME INFO字典键值 # GAME INFO字典键值
CURRENT_TIME = "current time" CURRENT_TIME = 'current time'
PASSED_ALL = "passed all" # 已完成该模式下的所有游戏,应当显示向日葵奖杯获得界面 PASSED_ALL = 'passed all' # 已完成该模式下的所有游戏,应当显示向日葵奖杯获得界面
LEVEL_NUM = "level num" LEVEL_NUM = 'level num'
LITTLEGAME_NUM = "littleGame num" LITTLEGAME_NUM = 'littleGame num'
LEVEL_COMPLETIONS = "level completions" LEVEL_COMPLETIONS = 'level completions'
LITTLEGAME_COMPLETIONS = "littleGame completions" LITTLEGAME_COMPLETIONS = 'littleGame completions'
GAME_RATE = "game rate" GAME_RATE = 'game rate'
SOUND_VOLUME = "volume" SOUND_VOLUME = 'volume'
# 整个游戏的状态 # 整个游戏的状态
MAIN_MENU = "main menu" MAIN_MENU = 'main menu'
LOAD_SCREEN = "load screen" LOAD_SCREEN = 'load screen'
GAME_LOSE = "game lose" GAME_LOSE = 'game lose'
GAME_VICTORY = "game victory" GAME_VICTORY = 'game victory'
LEVEL = "level" LEVEL = 'level'
AWARD_SCREEN = "award screen" AWARD_SCREEN = 'award screen'
HELP_SCREEN = "help screen" HELP_SCREEN = 'help screen'
# 界面图片文件名 # 界面图片文件名
MAIN_MENU_IMAGE = "MainMenu" MAIN_MENU_IMAGE = 'MainMenu'
OPTION_ADVENTURE = "Adventure" OPTION_ADVENTURE = 'Adventure'
GAME_LOSE_IMAGE = "GameLose" GAME_LOSE_IMAGE = 'GameLose'
GAME_VICTORY_IMAGE = "GameVictory" GAME_VICTORY_IMAGE = 'GameVictory'
AWARD_SCREEN_IMAGE = "AwardScreen" AWARD_SCREEN_IMAGE = 'AwardScreen'
HELP_SCREEN_IMAGE = "HelpScreen" HELP_SCREEN_IMAGE = 'HelpScreen'
# 地图相关内容 # 地图相关内容
BACKGROUND_NAME = "Background" BACKGROUND_NAME = 'Background'
BACKGROUND_TYPE = "background_type" BACKGROUND_TYPE = 'background_type'
INIT_SUN_NAME = "init_sun_value" INIT_SUN_NAME = 'init_sun_value'
ZOMBIE_LIST = "zombie_list" ZOMBIE_LIST = 'zombie_list'
GAME_TITLE = "title" GAME_TITLE = 'title'
# 地图类型 # 地图类型
BACKGROUND_DAY = 0 BACKGROUND_DAY = 0
@ -142,50 +161,56 @@ BACKGROUND_TRIPLE = 8
# 地图类型集合 # 地图类型集合
# 白天场地(泛指蘑菇睡觉的场地) # 白天场地(泛指蘑菇睡觉的场地)
DAYTIME_BACKGROUNDS = { DAYTIME_BACKGROUNDS = {
BACKGROUND_DAY, BACKGROUND_POOL, BACKGROUND_DAY,
BACKGROUND_ROOF, BACKGROUND_WALLNUTBOWLING, BACKGROUND_POOL,
BACKGROUND_SINGLE, BACKGROUND_TRIPLE, BACKGROUND_ROOF,
BACKGROUND_WALLNUTBOWLING,
BACKGROUND_SINGLE,
BACKGROUND_TRIPLE,
} }
# 带有泳池的场地 # 带有泳池的场地
POOL_EQUIPPED_BACKGROUNDS = { POOL_EQUIPPED_BACKGROUNDS = {
BACKGROUND_POOL, BACKGROUND_FOG, BACKGROUND_POOL,
BACKGROUND_FOG,
} }
# 屋顶上的场地 # 屋顶上的场地
ON_ROOF_BACKGROUNDS = { ON_ROOF_BACKGROUNDS = {
BACKGROUND_ROOF, BACKGROUND_ROOFNIGHT, BACKGROUND_ROOF,
BACKGROUND_ROOFNIGHT,
} }
# BACKGROUND_DAY场地的变体 # BACKGROUND_DAY场地的变体
BACKGROUND_DAY_LIKE_BACKGROUNDS = { BACKGROUND_DAY_LIKE_BACKGROUNDS = {
BACKGROUND_DAY, BACKGROUND_SINGLE, BACKGROUND_DAY,
BACKGROUND_SINGLE,
BACKGROUND_TRIPLE, BACKGROUND_TRIPLE,
} }
# 夜晚地图的墓碑数量等级 # 夜晚地图的墓碑数量等级
GRADE_GRAVES = "grade_graves" GRADE_GRAVES = 'grade_graves'
# 不同墓碑等级对应的信息,列表位置对应的是墓碑等级 # 不同墓碑等级对应的信息,列表位置对应的是墓碑等级
GRAVES_GRADE_INFO = (0, 4, 7, 11) GRAVES_GRADE_INFO = (0, 4, 7, 11)
# 僵尸生成方式 # 僵尸生成方式
SPAWN_ZOMBIES = "spawn_zombies" SPAWN_ZOMBIES = 'spawn_zombies'
SPAWN_ZOMBIES_AUTO = 1 SPAWN_ZOMBIES_AUTO = 1
SPAWN_ZOMBIES_LIST = 0 SPAWN_ZOMBIES_LIST = 0
INCLUDED_ZOMBIES = "included_zombies" INCLUDED_ZOMBIES = 'included_zombies'
NUM_FLAGS = "num_flags" NUM_FLAGS = 'num_flags'
INEVITABLE_ZOMBIE_DICT = "inevitable_zombie_list" INEVITABLE_ZOMBIE_DICT = 'inevitable_zombie_list'
SURVIVAL_ROUNDS = "survival_rounds" SURVIVAL_ROUNDS = 'survival_rounds'
# 地图单元格属性 # 地图单元格属性
MAP_PLANT = "plantnames" MAP_PLANT = 'plantnames'
MAP_SLEEP = "sleep" # 有没有休眠的蘑菇,作是否能种植咖啡豆的判断 MAP_SLEEP = 'sleep' # 有没有休眠的蘑菇,作是否能种植咖啡豆的判断
MAP_PLOT_TYPE = "plot_type" MAP_PLOT_TYPE = 'plot_type'
# 地图单元格区域类型 # 地图单元格区域类型
MAP_GRASS = "grass" MAP_GRASS = 'grass'
MAP_WATER = "water" MAP_WATER = 'water'
MAP_TILE = "tile" # 指屋顶上的瓦片 MAP_TILE = 'tile' # 指屋顶上的瓦片
MAP_UNAVAILABLE = "unavailable" # 指完全不能种植物的地方,包括无草皮的荒地和坚果保龄球等红线右侧 MAP_UNAVAILABLE = 'unavailable' # 指完全不能种植物的地方,包括无草皮的荒地和坚果保龄球等红线右侧
# 地图相关像素数据 # 地图相关像素数据
BACKGROUND_OFFSET_X = 220 BACKGROUND_OFFSET_X = 220
@ -200,15 +225,15 @@ MAP_ROOF_OFFSET_Y = 105 # 暂时还不清楚数据
MAP_POOL_FRONT_X = SCREEN_WIDTH - 15 MAP_POOL_FRONT_X = SCREEN_WIDTH - 15
# 植物选择菜单栏、传送带菜单栏等类型设定 # 植物选择菜单栏、传送带菜单栏等类型设定
CHOOSEBAR_TYPE = "choosebar_type" CHOOSEBAR_TYPE = 'choosebar_type'
CHOOSEBAR_STATIC = 0 CHOOSEBAR_STATIC = 0
CHOOSEBAR_MOVE = 1 CHOOSEBAR_MOVE = 1
CHOOSEBAR_BOWLING = 2 CHOOSEBAR_BOWLING = 2
MENUBAR_BACKGROUND = "ChooserBackground" MENUBAR_BACKGROUND = 'ChooserBackground'
MOVEBAR_BACKGROUND = "MoveBackground" MOVEBAR_BACKGROUND = 'MoveBackground'
PANEL_BACKGROUND = "PanelBackground" PANEL_BACKGROUND = 'PanelBackground'
START_BUTTON = "StartButton" START_BUTTON = 'StartButton'
CARD_POOL = "card_pool" CARD_POOL = 'card_pool'
# 关于植物栏的像素设置 # 关于植物栏的像素设置
PANEL_Y_START = 87 PANEL_Y_START = 87
@ -228,155 +253,147 @@ MOVEBAR_CARD_FRESH_TIME = 6000
CARD_MOVE_TIME = 60 CARD_MOVE_TIME = 60
# 其他显示物 # 其他显示物
CAR = "car" CAR = 'car'
SUN = "Sun" SUN = 'Sun'
# plant子类非植物对象这里的是不包括阳光、子弹的拟植物对象 # plant子类非植物对象这里的是不包括阳光、子弹的拟植物对象
NON_PLANT_OBJECTS = { NON_PLANT_OBJECTS = {
HOLE := "Hole", HOLE := 'Hole',
ICEFROZENPLOT := "IceFrozenPlot", ICEFROZENPLOT := 'IceFrozenPlot',
GRAVE := "Grave", GRAVE := 'Grave',
} }
# 植物相关信息 # 植物相关信息
PLANT_IMAGE_RECT = "plant_image_rect" PLANT_IMAGE_RECT = 'plant_image_rect'
BOOM_IMAGE = "Boom" BOOM_IMAGE = 'Boom'
# 植物卡片信息汇总(包括植物名称, 卡片名称, 阳光, 冷却时间) # 植物卡片信息汇总(包括植物名称, 卡片名称, 阳光, 冷却时间)
PLANT_CARD_INFO = ( # 元组 (植物名称, 卡片名称, 阳光, 冷却时间) PLANT_CARD_INFO = ( # 元组 (植物名称, 卡片名称, 阳光, 冷却时间)
(PEASHOOTER := "Peashooter", (
CARD_PEASHOOTER := "card_peashooter", PEASHOOTER := 'Peashooter',
CARD_PEASHOOTER := 'card_peashooter',
100, 100,
7500), 7500,
(SUNFLOWER := "SunFlower", ),
CARD_SUNFLOWER := "card_sunflower", (SUNFLOWER := 'SunFlower', CARD_SUNFLOWER := 'card_sunflower', 50, 7500),
50, (
7500), CHERRYBOMB := 'CherryBomb',
(CHERRYBOMB := "CherryBomb", CARD_CHERRYBOMB := 'card_cherrybomb',
CARD_CHERRYBOMB := "card_cherrybomb",
150, 150,
50000), 50000,
(WALLNUT := "WallNut", ),
CARD_WALLNUT := "card_wallnut", (WALLNUT := 'WallNut', CARD_WALLNUT := 'card_wallnut', 50, 30000),
50, (
30000), POTATOMINE := 'PotatoMine',
(POTATOMINE := "PotatoMine", CARD_POTATOMINE := 'card_potatomine',
CARD_POTATOMINE := "card_potatomine",
25, 25,
30000), 30000,
(SNOWPEASHOOTER := "SnowPea", ),
CARD_SNOWPEASHOOTER := "card_snowpea", (
SNOWPEASHOOTER := 'SnowPea',
CARD_SNOWPEASHOOTER := 'card_snowpea',
175, 175,
7500), 7500,
(CHOMPER := "Chomper", ),
CARD_CHOMPER := "card_chomper", (CHOMPER := 'Chomper', CARD_CHOMPER := 'card_chomper', 150, 7500),
150, (
7500), REPEATERPEA := 'RepeaterPea',
(REPEATERPEA := "RepeaterPea", CARD_REPEATERPEA := 'card_repeaterpea',
CARD_REPEATERPEA := "card_repeaterpea",
200, 200,
7500), 7500,
(PUFFSHROOM := "PuffShroom", ),
CARD_PUFFSHROOM := "card_puffshroom", (
PUFFSHROOM := 'PuffShroom',
CARD_PUFFSHROOM := 'card_puffshroom',
0, 0,
7500), 7500,
(SUNSHROOM := "SunShroom", ),
CARD_SUNSHROOM := "card_sunshroom", (SUNSHROOM := 'SunShroom', CARD_SUNSHROOM := 'card_sunshroom', 25, 7500),
(
FUMESHROOM := 'FumeShroom',
CARD_FUMESHROOM := 'card_fumeshroom',
75,
7500,
),
(
GRAVEBUSTER := 'GraveBuster',
CARD_GRAVEBUSTER := 'card_gravebuster',
75,
7500,
),
(
HYPNOSHROOM := 'HypnoShroom',
CARD_HYPNOSHROOM := 'card_hypnoshroom',
75,
30000,
),
(
SCAREDYSHROOM := 'ScaredyShroom',
CARD_SCAREDYSHROOM := 'card_scaredyshroom',
25, 25,
7500), 7500,
(FUMESHROOM := "FumeShroom", ),
CARD_FUMESHROOM := "card_fumeshroom", (ICESHROOM := 'IceShroom', CARD_ICESHROOM := 'card_iceshroom', 75, 50000),
75, (
7500), DOOMSHROOM := 'DoomShroom',
(GRAVEBUSTER := "GraveBuster", CARD_DOOMSHROOM := 'card_doomshroom',
CARD_GRAVEBUSTER := "card_gravebuster",
75,
7500),
(HYPNOSHROOM := "HypnoShroom",
CARD_HYPNOSHROOM := "card_hypnoshroom",
75,
30000),
(SCAREDYSHROOM := "ScaredyShroom",
CARD_SCAREDYSHROOM := "card_scaredyshroom",
25,
7500),
(ICESHROOM := "IceShroom",
CARD_ICESHROOM := "card_iceshroom",
75,
50000),
(DOOMSHROOM := "DoomShroom",
CARD_DOOMSHROOM := "card_doomshroom",
125, 125,
50000), 50000,
(LILYPAD := "LilyPad", ),
CARD_LILYPAD := "card_lilypad", (LILYPAD := 'LilyPad', CARD_LILYPAD := 'card_lilypad', 25, 7500),
(SQUASH := 'Squash', CARD_SQUASH := 'card_squash', 50, 30000),
(
TANGLEKLEP := 'TangleKlep',
CARD_TANGLEKLEP := 'card_tangleklep',
25, 25,
7500), 30000,
(SQUASH := "Squash", ),
CARD_SQUASH := "card_squash", (
50, THREEPEASHOOTER := 'Threepeater',
30000), CARD_THREEPEASHOOTER := 'card_threepeashooter',
(TANGLEKLEP := "TangleKlep",
CARD_TANGLEKLEP := "card_tangleklep",
25,
30000),
(THREEPEASHOOTER := "Threepeater",
CARD_THREEPEASHOOTER := "card_threepeashooter",
325, 325,
7500), 7500,
(JALAPENO := "Jalapeno", ),
CARD_JALAPENO := "card_jalapeno", (JALAPENO := 'Jalapeno', CARD_JALAPENO := 'card_jalapeno', 125, 50000),
(SPIKEWEED := 'Spikeweed', CARD_SPIKEWEED := 'card_spikeweed', 100, 7500),
(TORCHWOOD := 'TorchWood', CARD_TORCHWOOD := 'card_torchwood', 175, 7500),
(TALLNUT := 'TallNut', CARD_TALLNUT := 'card_tallnut', 125, 30000),
(SEASHROOM := 'SeaShroom', CARD_SEASHROOM := 'card_seashroom', 0, 30000),
(STARFRUIT := 'StarFruit', CARD_STARFRUIT := 'card_starfruit', 125, 7500),
(
PUMPKINHEAD := 'PumpkinHead',
CARD_PUMPKINHEAD := 'card_pumpkinhead',
125, 125,
50000), 30000,
(SPIKEWEED := "Spikeweed", ),
CARD_SPIKEWEED := "card_spikeweed", (
100, COFFEEBEAN := 'CoffeeBean',
7500), CARD_COFFEEBEAN := 'card_coffeebean',
(TORCHWOOD := "TorchWood",
CARD_TORCHWOOD := "card_torchwood",
175,
7500),
(TALLNUT := "TallNut",
CARD_TALLNUT := "card_tallnut",
125,
30000),
(SEASHROOM := "SeaShroom",
CARD_SEASHROOM := "card_seashroom",
0,
30000),
(STARFRUIT := "StarFruit",
CARD_STARFRUIT := "card_starfruit",
125,
7500),
(PUMPKINHEAD := "PumpkinHead",
CARD_PUMPKINHEAD := "card_pumpkinhead",
125,
30000),
(COFFEEBEAN := "CoffeeBean",
CARD_COFFEEBEAN := "card_coffeebean",
75, 75,
7500), 7500,
(GARLIC := "Garlic", ),
CARD_GARLIC := "card_garlic", (GARLIC := 'Garlic', CARD_GARLIC := 'card_garlic', 50, 7500),
50,
7500),
# 应当保证这3个在一般模式下不可选的特殊植物恒在最后 # 应当保证这3个在一般模式下不可选的特殊植物恒在最后
(WALLNUTBOWLING := "WallNutBowling", (WALLNUTBOWLING := 'WallNutBowling', CARD_WALLNUT := 'card_wallnut', 0, 0),
CARD_WALLNUT := "card_wallnut", (
REDWALLNUTBOWLING := 'RedWallNutBowling',
CARD_REDWALLNUT := 'card_redwallnut',
0, 0,
0),
(REDWALLNUTBOWLING := "RedWallNutBowling",
CARD_REDWALLNUT := "card_redwallnut",
0, 0,
0), ),
(GIANTWALLNUT := "GiantWallNut", (
CARD_GIANTWALLNUT := "card_giantwallnut", GIANTWALLNUT := 'GiantWallNut',
CARD_GIANTWALLNUT := 'card_giantwallnut',
0, 0,
0), 0,
),
) )
# 卡片中的植物名称与索引序号的对应关系,指定名称以得到索引值 # 卡片中的植物名称与索引序号的对应关系,指定名称以得到索引值
PLANT_CARD_INDEX = {item[PLANT_NAME_INDEX]: index for (index, item) in enumerate(PLANT_CARD_INFO)} PLANT_CARD_INDEX = {
item[PLANT_NAME_INDEX]: index
for (index, item) in enumerate(PLANT_CARD_INFO)
}
# 指定了哪些卡可选(排除坚果保龄球特殊植物) # 指定了哪些卡可选(排除坚果保龄球特殊植物)
CARDS_TO_CHOOSE = range(len(PLANT_CARD_INFO) - 3) CARDS_TO_CHOOSE = range(len(PLANT_CARD_INFO) - 3)
@ -387,9 +404,12 @@ CARDS_TO_CHOOSE = range(len(PLANT_CARD_INFO) - 3)
# 在生效时不用与僵尸进行碰撞检测的对象(即生效时不可发生被僵尸啃食的事件) # 在生效时不用与僵尸进行碰撞检测的对象(即生效时不可发生被僵尸啃食的事件)
SKIP_ZOMBIE_COLLISION_CHECK_WHEN_WORKING = { SKIP_ZOMBIE_COLLISION_CHECK_WHEN_WORKING = {
# 注意爆炸坚果的触发也是啃食类碰撞,因此只能算作爆炸后不检测 # 注意爆炸坚果的触发也是啃食类碰撞,因此只能算作爆炸后不检测
SQUASH, ICESHROOM, SQUASH,
REDWALLNUTBOWLING, CHERRYBOMB, ICESHROOM,
JALAPENO, DOOMSHROOM, REDWALLNUTBOWLING,
CHERRYBOMB,
JALAPENO,
DOOMSHROOM,
POTATOMINE, POTATOMINE,
} }
@ -397,27 +417,35 @@ SKIP_ZOMBIE_COLLISION_CHECK_WHEN_WORKING = {
CAN_SKIP_ZOMBIE_COLLISION_CHECK = ( # 这里运用了集合运算 CAN_SKIP_ZOMBIE_COLLISION_CHECK = ( # 这里运用了集合运算
# 注意这个外围的小括号是用来换行的 # 注意这个外围的小括号是用来换行的
# 各个部分末!尾!千!万!不!能!加!逗!号!!! # 各个部分末!尾!千!万!不!能!加!逗!号!!!
# 生效时不检测的植物 # 生效时不检测的植物
SKIP_ZOMBIE_COLLISION_CHECK_WHEN_WORKING | SKIP_ZOMBIE_COLLISION_CHECK_WHEN_WORKING
|
# 非植物对象 # 非植物对象
NON_PLANT_OBJECTS | NON_PLANT_OBJECTS
|
# 地刺类 # 地刺类
{SPIKEWEED, } {
SPIKEWEED,
}
) )
# 死亡时不触发音效的对象 # 死亡时不触发音效的对象
PLANT_DIE_SOUND_EXCEPTIONS = { PLANT_DIE_SOUND_EXCEPTIONS = {
WALLNUTBOWLING, TANGLEKLEP, WALLNUTBOWLING,
ICEFROZENPLOT, HOLE, TANGLEKLEP,
GRAVE, JALAPENO, ICEFROZENPLOT,
REDWALLNUTBOWLING, CHERRYBOMB, HOLE,
GRAVE,
JALAPENO,
REDWALLNUTBOWLING,
CHERRYBOMB,
GIANTWALLNUT, GIANTWALLNUT,
} }
# 直接水生植物 # 直接水生植物
WATER_PLANTS = { WATER_PLANTS = {
LILYPAD, SEASHROOM, LILYPAD,
SEASHROOM,
TANGLEKLEP, TANGLEKLEP,
} }
@ -427,17 +455,23 @@ CHECK_ATTACK_ALWAYS = 1
# 范围爆炸植物,即灰烬植物与寒冰菇 # 范围爆炸植物,即灰烬植物与寒冰菇
ASH_PLANTS_AND_ICESHROOM = { ASH_PLANTS_AND_ICESHROOM = {
REDWALLNUTBOWLING, CHERRYBOMB, REDWALLNUTBOWLING,
JALAPENO, DOOMSHROOM, CHERRYBOMB,
JALAPENO,
DOOMSHROOM,
ICESHROOM, ICESHROOM,
} }
# 白天要睡觉的植物 # 白天要睡觉的植物
CAN_SLEEP_PLANTS = { CAN_SLEEP_PLANTS = {
PUFFSHROOM, SUNSHROOM, PUFFSHROOM,
FUMESHROOM, HYPNOSHROOM, SUNSHROOM,
SCAREDYSHROOM, ICESHROOM, FUMESHROOM,
DOOMSHROOM, SEASHROOM, HYPNOSHROOM,
SCAREDYSHROOM,
ICESHROOM,
DOOMSHROOM,
SEASHROOM,
} }
# 选卡不推荐选择理由 # 选卡不推荐选择理由
@ -468,65 +502,69 @@ SUN_VALUE = 25
# 僵尸冷冻 # 僵尸冷冻
ICE_SLOW_TIME = 10000 ICE_SLOW_TIME = 10000
MIN_FREEZE_TIME = 4000 MIN_FREEZE_TIME = 4000
ICETRAP = "IceTrap" ICETRAP = 'IceTrap'
# 子弹信息 # 子弹信息
# 子弹类型 # 子弹类型
BULLET_PEA = "PeaNormal" BULLET_PEA = 'PeaNormal'
BULLET_PEA_ICE = "PeaIce" BULLET_PEA_ICE = 'PeaIce'
BULLET_FIREBALL = "Fireball" BULLET_FIREBALL = 'Fireball'
BULLET_MUSHROOM = "BulletMushRoom" BULLET_MUSHROOM = 'BulletMushRoom'
BULLET_SEASHROOM = "BulletSeaShroom" BULLET_SEASHROOM = 'BulletSeaShroom'
FUME = "Fume" FUME = 'Fume'
# 子弹伤害 # 子弹伤害
BULLET_DAMAGE_NORMAL = 20 BULLET_DAMAGE_NORMAL = 20
BULLET_DAMAGE_FIREBALL_BODY = 27 # 这是火球本体的伤害注意不是40本体(27) + 溅射(13)才是40 BULLET_DAMAGE_FIREBALL_BODY = 27 # 这是火球本体的伤害注意不是40本体(27) + 溅射(13)才是40
BULLET_DAMAGE_FIREBALL_RANGE = 13 # 原版溅射伤害会随着僵尸数量增多而减少,这里相当于做了一个增强 BULLET_DAMAGE_FIREBALL_RANGE = 13 # 原版溅射伤害会随着僵尸数量增多而减少,这里相当于做了一个增强
# 子弹效果 # 子弹效果
BULLET_EFFECT_ICE = "ice" BULLET_EFFECT_ICE = 'ice'
BULLET_EFFECT_UNICE = "unice" BULLET_EFFECT_UNICE = 'unice'
# 特殊子弹 # 特殊子弹
# 杨桃子弹 # 杨桃子弹
# 子弹名称 # 子弹名称
BULLET_STAR = "StarBullet" BULLET_STAR = 'StarBullet'
# 子弹方向 # 子弹方向
STAR_FORWARD_UP = "forwardUp" # 向前上方 STAR_FORWARD_UP = 'forwardUp' # 向前上方
STAR_FORWARD_DOWN = "forwardDown" #向前下方 STAR_FORWARD_DOWN = 'forwardDown' # 向前下方
STAR_BACKWARD = "backward" #向后 STAR_BACKWARD = 'backward' # 向后
STAR_UPWARD = "upward" # 向上 STAR_UPWARD = 'upward' # 向上
STAR_DOWNWARD = "downward" # 向下 STAR_DOWNWARD = 'downward' # 向下
# 有爆炸图片的子弹 # 有爆炸图片的子弹
BULLET_INDEPENDENT_BOOM_IMG = { BULLET_PEA, BULLET_PEA_ICE, BULLET_INDEPENDENT_BOOM_IMG = {
BULLET_MUSHROOM, BULLET_SEASHROOM, BULLET_PEA,
BULLET_STAR, } BULLET_PEA_ICE,
BULLET_MUSHROOM,
BULLET_SEASHROOM,
BULLET_STAR,
}
# 僵尸信息 # 僵尸信息
ZOMBIE_IMAGE_RECT = "zombie_image_rect" ZOMBIE_IMAGE_RECT = 'zombie_image_rect'
ZOMBIE_HEAD = "ZombieHead" ZOMBIE_HEAD = 'ZombieHead'
NORMAL_ZOMBIE = "Zombie" NORMAL_ZOMBIE = 'Zombie'
CONEHEAD_ZOMBIE = "ConeheadZombie" CONEHEAD_ZOMBIE = 'ConeheadZombie'
BUCKETHEAD_ZOMBIE = "BucketheadZombie" BUCKETHEAD_ZOMBIE = 'BucketheadZombie'
FLAG_ZOMBIE = "FlagZombie" FLAG_ZOMBIE = 'FlagZombie'
NEWSPAPER_ZOMBIE = "NewspaperZombie" NEWSPAPER_ZOMBIE = 'NewspaperZombie'
FOOTBALL_ZOMBIE = "FootballZombie" FOOTBALL_ZOMBIE = 'FootballZombie'
DUCKY_TUBE_ZOMBIE = "DuckyTubeZombie" DUCKY_TUBE_ZOMBIE = 'DuckyTubeZombie'
CONEHEAD_DUCKY_TUBE_ZOMBIE = "ConeheadDuckyTubeZombie" CONEHEAD_DUCKY_TUBE_ZOMBIE = 'ConeheadDuckyTubeZombie'
BUCKETHEAD_DUCKY_TUBE_ZOMBIE = "BucketheadDuckyTubeZombie" BUCKETHEAD_DUCKY_TUBE_ZOMBIE = 'BucketheadDuckyTubeZombie'
SCREEN_DOOR_ZOMBIE = "ScreenDoorZombie" SCREEN_DOOR_ZOMBIE = 'ScreenDoorZombie'
POLE_VAULTING_ZOMBIE = "PoleVaultingZombie" POLE_VAULTING_ZOMBIE = 'PoleVaultingZombie'
ZOMBONI = "Zomboni" ZOMBONI = 'Zomboni'
SNORKELZOMBIE = "SnorkelZombie" SNORKELZOMBIE = 'SnorkelZombie'
BOOMDIE = "BoomDie" BOOMDIE = 'BoomDie'
# 对僵尸的攻击类型设置 # 对僵尸的攻击类型设置
ZOMBIE_DEAFULT_DAMAGE = ZOMBIE_HELMET_2_FIRST = "helmet2First" # 优先攻击二类防具 ZOMBIE_DEAFULT_DAMAGE = ZOMBIE_HELMET_2_FIRST = 'helmet2First' # 优先攻击二类防具
ZOMBIE_COMMON_DAMAGE = "commonDamage" # 优先攻击僵尸与一类防具的整体 ZOMBIE_COMMON_DAMAGE = 'commonDamage' # 优先攻击僵尸与一类防具的整体
ZOMBIE_RANGE_DAMAGE = "rangeDamage" # 范围攻击,同时伤害二类防具与(僵尸与一类防具的整体) ZOMBIE_RANGE_DAMAGE = 'rangeDamage' # 范围攻击,同时伤害二类防具与(僵尸与一类防具的整体)
ZOMBIE_ASH_DAMAGE = "ashDamage" # 灰烬植物攻击,直接伤害本体 ZOMBIE_ASH_DAMAGE = 'ashDamage' # 灰烬植物攻击,直接伤害本体
ZOMBIE_WALLNUT_BOWLING_DANMAGE = "wallnutBowlingDamage" # 坚果保龄球冲撞伤害 ZOMBIE_WALLNUT_BOWLING_DANMAGE = 'wallnutBowlingDamage' # 坚果保龄球冲撞伤害
# 僵尸生命值设置 # 僵尸生命值设置
# 有关本体 # 有关本体
@ -583,128 +621,143 @@ CONVERT_ZOMBIE_IN_POOL = {
# 水上僵尸集合 # 水上僵尸集合
WATER_ZOMBIE = { WATER_ZOMBIE = {
DUCKY_TUBE_ZOMBIE, CONEHEAD_DUCKY_TUBE_ZOMBIE, DUCKY_TUBE_ZOMBIE,
BUCKETHEAD_DUCKY_TUBE_ZOMBIE, SNORKELZOMBIE, CONEHEAD_DUCKY_TUBE_ZOMBIE,
BUCKETHEAD_DUCKY_TUBE_ZOMBIE,
SNORKELZOMBIE,
} }
# 状态类型 # 状态类型
IDLE = "idle" IDLE = 'idle'
FLY = "fly" FLY = 'fly'
EXPLODE = "explode" EXPLODE = 'explode'
ATTACK = "attack" ATTACK = 'attack'
ATTACKED = "attacked" ATTACKED = 'attacked'
DIGEST = "digest" DIGEST = 'digest'
WALK = "walk" WALK = 'walk'
DIE = "die" DIE = 'die'
CRY = "cry" CRY = 'cry'
FREEZE = "freeze" FREEZE = 'freeze'
SLEEP = "sleep" SLEEP = 'sleep'
# 关卡状态 # 关卡状态
CHOOSE = "choose" CHOOSE = 'choose'
PLAY = "play" PLAY = 'play'
# 加载矩形碰撞范围 用于消除文件边框影响 # 加载矩形碰撞范围 用于消除文件边框影响
# 植物 # 植物
PLANT_RECT = { PLANT_RECT = {
BULLET_PEA: {"x":28, "y":0, "width":28, "height":34}, BULLET_PEA: {'x': 28, 'y': 0, 'width': 28, 'height': 34},
BULLET_PEA_ICE: {"x":26, "y":0, "width":30, "height":34}, BULLET_PEA_ICE: {'x': 26, 'y': 0, 'width': 30, 'height': 34},
CHOMPER: {"x":0, "y":0, "width":100, "height":114}, CHOMPER: {'x': 0, 'y': 0, 'width': 100, 'height': 114},
PUFFSHROOM: {"x":0, "y":28, "width":35, "height":38}, PUFFSHROOM: {'x': 0, 'y': 28, 'width': 35, 'height': 38},
f"{PUFFSHROOM}Sleep": {"x":1, "y":0, "width":39, "height":65}, f'{PUFFSHROOM}Sleep': {'x': 1, 'y': 0, 'width': 39, 'height': 65},
BULLET_MUSHROOM: {"x":0, "y":1, "width":55, "height":21}, BULLET_MUSHROOM: {'x': 0, 'y': 1, 'width': 55, 'height': 21},
BULLET_SEASHROOM: {"x":0, "y":1, "width":55, "height":21}, BULLET_SEASHROOM: {'x': 0, 'y': 1, 'width': 55, 'height': 21},
POTATOMINE: {"x":0, "y":0, "width":75, "height":55}, POTATOMINE: {'x': 0, 'y': 0, 'width': 75, 'height': 55},
SQUASH: {"x":10, "y":140, "width":80, "height":86}, SQUASH: {'x': 10, 'y': 140, 'width': 80, 'height': 86},
f"{SQUASH}Aim": {"x":10, "y":140, "width":80, "height":86}, f'{SQUASH}Aim': {'x': 10, 'y': 140, 'width': 80, 'height': 86},
SPIKEWEED: {"x":3, "y":0, "width":80, "height":35} SPIKEWEED: {'x': 3, 'y': 0, 'width': 80, 'height': 35},
} }
# 僵尸 # 僵尸
ZOMBIE_RECT = { ZOMBIE_RECT = {
NORMAL_ZOMBIE: {"x":62, "width":90}, NORMAL_ZOMBIE: {'x': 62, 'width': 90},
f"{NORMAL_ZOMBIE}Attack": {"x":62, "width":90}, f'{NORMAL_ZOMBIE}Attack': {'x': 62, 'width': 90},
f"{NORMAL_ZOMBIE}LostHead": {"x":62, "width":90}, f'{NORMAL_ZOMBIE}LostHead': {'x': 62, 'width': 90},
f"{NORMAL_ZOMBIE}LostHeadAttack":{"x":62, "width":90}, f'{NORMAL_ZOMBIE}LostHeadAttack': {'x': 62, 'width': 90},
f"{NORMAL_ZOMBIE}Die": {"x":0, "width":164}, f'{NORMAL_ZOMBIE}Die': {'x': 0, 'width': 164},
BOOMDIE: {"x":68, "width":80}, BOOMDIE: {'x': 68, 'width': 80},
CONEHEAD_ZOMBIE: {"x":80, "width":80}, CONEHEAD_ZOMBIE: {'x': 80, 'width': 80},
f"{CONEHEAD_ZOMBIE}Attack": {"x":79, "width":87}, f'{CONEHEAD_ZOMBIE}Attack': {'x': 79, 'width': 87},
BUCKETHEAD_ZOMBIE: {"x":54, "width":90}, BUCKETHEAD_ZOMBIE: {'x': 54, 'width': 90},
f"{BUCKETHEAD_ZOMBIE}Attack": {"x":46, "width":90}, f'{BUCKETHEAD_ZOMBIE}Attack': {'x': 46, 'width': 90},
FLAG_ZOMBIE: {"x":56, "width":110}, FLAG_ZOMBIE: {'x': 56, 'width': 110},
f"{FLAG_ZOMBIE}Attack": {"x":60, "width":100}, f'{FLAG_ZOMBIE}Attack': {'x': 60, 'width': 100},
f"{FLAG_ZOMBIE}LostHead": {"x":55, "width":110}, f'{FLAG_ZOMBIE}LostHead': {'x': 55, 'width': 110},
f"{FLAG_ZOMBIE}LostHeadAttack": {"x":55, "width":110}, f'{FLAG_ZOMBIE}LostHeadAttack': {'x': 55, 'width': 110},
NEWSPAPER_ZOMBIE: {"x":48, "width":92}, NEWSPAPER_ZOMBIE: {'x': 48, 'width': 92},
f"{NEWSPAPER_ZOMBIE}Attack": {"x":48, "width":92}, f'{NEWSPAPER_ZOMBIE}Attack': {'x': 48, 'width': 92},
f"{NEWSPAPER_ZOMBIE}NoPaper": {"x":40, "width":98}, f'{NEWSPAPER_ZOMBIE}NoPaper': {'x': 40, 'width': 98},
f"{NEWSPAPER_ZOMBIE}NoPaperAttack":{"x":48, "width":92}, f'{NEWSPAPER_ZOMBIE}NoPaperAttack': {'x': 48, 'width': 92},
f"{NEWSPAPER_ZOMBIE}LostHead": {"x":44, "width":96}, f'{NEWSPAPER_ZOMBIE}LostHead': {'x': 44, 'width': 96},
f"{NEWSPAPER_ZOMBIE}LostHeadAttack":{"x":48, "width":92}, f'{NEWSPAPER_ZOMBIE}LostHeadAttack': {'x': 48, 'width': 92},
f"{NEWSPAPER_ZOMBIE}Die": {"x":0, "width":100}, f'{NEWSPAPER_ZOMBIE}Die': {'x': 0, 'width': 100},
f"{DUCKY_TUBE_ZOMBIE}Die": {"x":55, "width":105}, f'{DUCKY_TUBE_ZOMBIE}Die': {'x': 55, 'width': 105},
f"{DUCKY_TUBE_ZOMBIE}LostHead": {"x":55, "width":105}, f'{DUCKY_TUBE_ZOMBIE}LostHead': {'x': 55, 'width': 105},
SCREEN_DOOR_ZOMBIE: {"x":41, "width":100}, SCREEN_DOOR_ZOMBIE: {'x': 41, 'width': 100},
f"{SCREEN_DOOR_ZOMBIE}Attack": {"x":41, "width":100}, f'{SCREEN_DOOR_ZOMBIE}Attack': {'x': 41, 'width': 100},
} # 这里还有懒得写代码的补加,用循环实现 } # 这里还有懒得写代码的补加,用循环实现
for _part1 in (DUCKY_TUBE_ZOMBIE, CONEHEAD_DUCKY_TUBE_ZOMBIE, BUCKETHEAD_DUCKY_TUBE_ZOMBIE): for _part1 in (
for _part2 in ("", "Attack", "Swim"): DUCKY_TUBE_ZOMBIE,
ZOMBIE_RECT[f"{_part1}{_part2}"] = {"x":55, "width":105} CONEHEAD_DUCKY_TUBE_ZOMBIE,
BUCKETHEAD_DUCKY_TUBE_ZOMBIE,
):
for _part2 in ('', 'Attack', 'Swim'):
ZOMBIE_RECT[f'{_part1}{_part2}'] = {'x': 55, 'width': 105}
# 音效 # 音效
def _getSound(filename): def _getSound(filename):
return pg.mixer.Sound(os.path.join(os.path.dirname(os.path.dirname(__file__)) ,"resources", "sound", filename)) return pg.mixer.Sound(
# 所有音效的元组,用一波海象算子表达(>= python 3.8),免得要维护两个 os.path.join(
os.path.dirname(os.path.dirname(__file__)),
'resources',
'sound',
filename,
)
)
# 所有音效的元组,用一波海象算子表达,免得要维护两个
SOUNDS = ( # 程序交互等 SOUNDS = ( # 程序交互等
SOUND_TAPPING_CARD := _getSound("tap.ogg"), SOUND_TAPPING_CARD := _getSound('tap.ogg'),
SOUND_HELP_SCREEN := _getSound("helpScreen.ogg"), SOUND_HELP_SCREEN := _getSound('helpScreen.ogg'),
# 植物 # 植物
SOUND_FIREPEA_EXPLODE := _getSound("firepea.ogg"), SOUND_FIREPEA_EXPLODE := _getSound('firepea.ogg'),
SOUND_BULLET_EXPLODE := _getSound("bulletExplode.ogg"), SOUND_BULLET_EXPLODE := _getSound('bulletExplode.ogg'),
SOUND_SHOOT := _getSound("shoot.ogg"), SOUND_SHOOT := _getSound('shoot.ogg'),
SOUND_SNOWPEA_SPARKLES := _getSound("snowPeaSparkles.ogg"), SOUND_SNOWPEA_SPARKLES := _getSound('snowPeaSparkles.ogg'),
SOUND_BOMB := _getSound("bomb.ogg"), SOUND_BOMB := _getSound('bomb.ogg'),
SOUND_BIGCHOMP := _getSound("bigchomp.ogg"), SOUND_BIGCHOMP := _getSound('bigchomp.ogg'),
SOUND_PUFF := _getSound("puff.ogg"), SOUND_PUFF := _getSound('puff.ogg'),
SOUND_POTATOMINE := _getSound("potatomine.ogg"), SOUND_POTATOMINE := _getSound('potatomine.ogg'),
SOUND_SQUASHING := _getSound("squashing.ogg"), SOUND_SQUASHING := _getSound('squashing.ogg'),
SOUND_SQUASH_HMM := _getSound("squashHmm.ogg"), SOUND_SQUASH_HMM := _getSound('squashHmm.ogg'),
SOUND_PLANT_GROW := _getSound("plantGrow.ogg"), SOUND_PLANT_GROW := _getSound('plantGrow.ogg'),
SOUND_MUSHROOM_WAKEUP := _getSound("mushroomWakeup.ogg"), SOUND_MUSHROOM_WAKEUP := _getSound('mushroomWakeup.ogg'),
SOUND_TANGLE_KELP_DRAG := _getSound("tangleKelpDrag.ogg"), SOUND_TANGLE_KELP_DRAG := _getSound('tangleKelpDrag.ogg'),
SOUND_DOOMSHROOM := _getSound("doomshroom.ogg"), SOUND_DOOMSHROOM := _getSound('doomshroom.ogg'),
SOUND_GRAVEBUSTER_CHOMP := _getSound("gravebusterchomp.ogg"), SOUND_GRAVEBUSTER_CHOMP := _getSound('gravebusterchomp.ogg'),
SOUND_FUME := _getSound("fume.ogg"), SOUND_FUME := _getSound('fume.ogg'),
# 僵尸 # 僵尸
SOUND_ZOMBIE_ENTERING_WATER := _getSound("zombieEnteringWater.ogg"), SOUND_ZOMBIE_ENTERING_WATER := _getSound('zombieEnteringWater.ogg'),
SOUND_ZOMBIE_ATTACKING := _getSound("zombieAttack.ogg"), SOUND_ZOMBIE_ATTACKING := _getSound('zombieAttack.ogg'),
SOUND_FREEZE := _getSound("freeze.ogg"), SOUND_FREEZE := _getSound('freeze.ogg'),
SOUND_HYPNOED := _getSound("hypnoed.ogg"), SOUND_HYPNOED := _getSound('hypnoed.ogg'),
SOUND_NEWSPAPER_RIP := _getSound("newspaperRip.ogg"), SOUND_NEWSPAPER_RIP := _getSound('newspaperRip.ogg'),
SOUND_NEWSPAPER_ZOMBIE_ANGRY := _getSound("newspaperZombieAngry.ogg"), SOUND_NEWSPAPER_ZOMBIE_ANGRY := _getSound('newspaperZombieAngry.ogg'),
SOUND_POLEVAULT_JUMP := _getSound("polevaultjump.ogg"), SOUND_POLEVAULT_JUMP := _getSound('polevaultjump.ogg'),
SOUND_ZOMBONI := _getSound("zomboni.ogg"), SOUND_ZOMBONI := _getSound('zomboni.ogg'),
SOUND_ZOMBONI_EXPLOSION := _getSound("zomboniExplosion.ogg"), SOUND_ZOMBONI_EXPLOSION := _getSound('zomboniExplosion.ogg'),
# 关卡中 # 关卡中
SOUND_CAR_WALKING := _getSound("carWalking.ogg"), SOUND_CAR_WALKING := _getSound('carWalking.ogg'),
SOUND_ZOMBIE_COMING := _getSound("zombieComing.ogg"), SOUND_ZOMBIE_COMING := _getSound('zombieComing.ogg'),
SOUND_ZOMBIE_VOICE := _getSound("zombieVoice.ogg"), SOUND_ZOMBIE_VOICE := _getSound('zombieVoice.ogg'),
SOUND_HUGE_WAVE_APPROCHING := _getSound("hugeWaveApproching.ogg"), SOUND_HUGE_WAVE_APPROCHING := _getSound('hugeWaveApproching.ogg'),
SOUND_BUTTON_CLICK := _getSound("buttonclick.ogg"), SOUND_BUTTON_CLICK := _getSound('buttonclick.ogg'),
SOUND_COLLECT_SUN := _getSound("collectSun.ogg"), SOUND_COLLECT_SUN := _getSound('collectSun.ogg'),
SOUND_CLICK_CARD := _getSound("clickCard.ogg"), SOUND_CLICK_CARD := _getSound('clickCard.ogg'),
SOUND_SHOVEL := _getSound("shovel.ogg"), SOUND_SHOVEL := _getSound('shovel.ogg'),
SOUND_PLANT := _getSound("plant.ogg"), SOUND_PLANT := _getSound('plant.ogg'),
SOUND_BOWLING_IMPACT := _getSound("bowlingimpact.ogg"), SOUND_BOWLING_IMPACT := _getSound('bowlingimpact.ogg'),
SOUND_PLANT_DIE := _getSound("plantDie.ogg"), SOUND_PLANT_DIE := _getSound('plantDie.ogg'),
SOUND_EVILLAUGH := _getSound("evillaugh.ogg"), SOUND_EVILLAUGH := _getSound('evillaugh.ogg'),
SOUND_LOSE := _getSound("lose.ogg"), SOUND_LOSE := _getSound('lose.ogg'),
SOUND_WIN := _getSound("win.ogg"), SOUND_WIN := _getSound('win.ogg'),
SOUND_SCREAM := _getSound("scream.ogg"), SOUND_SCREAM := _getSound('scream.ogg'),
SOUND_CANNOT_CHOOSE_WARNING := _getSound("cannotChooseWarning.ogg"), SOUND_CANNOT_CHOOSE_WARNING := _getSound('cannotChooseWarning.ogg'),
SOUND_FINAL_FANFARE := _getSound("finalfanfare.ogg"), SOUND_FINAL_FANFARE := _getSound('finalfanfare.ogg'),
) )
# 记录本地存储文件初始值 # 记录本地存储文件初始值
@ -718,4 +771,4 @@ INIT_USERDATA = {
} }
# 无穷大常量 # 无穷大常量
INF = float("inf") # python传递字符串性能较低故在这里对inf声明一次以后仅需调用即可虽然真正的用处是可以自动补全 INF = float('inf') # python传递字符串性能较低故在这里对inf声明一次以后仅需调用即可虽然真正的用处是可以自动补全

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +1,16 @@
import pygame as pg
import os import os
from .. import tool
import pygame as pg
from .. import constants as c from .. import constants as c
from .. import tool
class Menu(tool.State): class Menu(tool.State):
def __init__(self): def __init__(self):
tool.State.__init__(self) tool.State.__init__(self)
def startup(self, current_time, persist): def startup(self, current_time: int, persist):
self.next = c.LEVEL self.next = c.LEVEL
self.persist = persist self.persist = persist
self.game_info = persist self.game_info = persist
@ -17,7 +19,7 @@ class Menu(tool.State):
self.setupOptionMenu() self.setupOptionMenu()
self.setupSunflowerTrophy() self.setupSunflowerTrophy()
pg.mixer.music.stop() pg.mixer.music.stop()
pg.mixer.music.load(os.path.join(c.PATH_MUSIC_DIR, "intro.opus")) pg.mixer.music.load(os.path.join(c.PATH_MUSIC_DIR, 'intro.opus'))
pg.mixer.music.play(-1, 0) pg.mixer.music.play(-1, 0)
pg.display.set_caption(c.ORIGINAL_CAPTION) pg.display.set_caption(c.ORIGINAL_CAPTION)
pg.mixer.music.set_volume(self.game_info[c.SOUND_VOLUME]) pg.mixer.music.set_volume(self.game_info[c.SOUND_VOLUME])
@ -30,7 +32,9 @@ class Menu(tool.State):
# 2、双星号同上区别是x视为字典。 # 2、双星号同上区别是x视为字典。
# 3、在变量前加单星号表示将元组列表、集合拆分为单个元素。 # 3、在变量前加单星号表示将元组列表、集合拆分为单个元素。
# 4、双星号同上区别是目标为字典字典前加单星号的话可以得到“键”。 # 4、双星号同上区别是目标为字典字典前加单星号的话可以得到“键”。
self.bg_image = tool.get_image(tool.GFX[c.MAIN_MENU_IMAGE], *frame_rect) self.bg_image = tool.get_image(
tool.GFX[c.MAIN_MENU_IMAGE], *frame_rect
)
self.bg_rect = self.bg_image.get_rect() self.bg_rect = self.bg_image.get_rect()
self.bg_rect.x = 0 self.bg_rect.x = 0
self.bg_rect.y = 0 self.bg_rect.y = 0
@ -39,7 +43,12 @@ class Menu(tool.State):
# 冒险模式 # 冒险模式
frame_rect = (0, 0, 330, 144) frame_rect = (0, 0, 330, 144)
# 写成列表生成器方便IDE识别与自动补全 # 写成列表生成器方便IDE识别与自动补全
self.adventure_frames = [tool.get_image_alpha(tool.GFX[f"{c.OPTION_ADVENTURE}_{i}"], *frame_rect) for i in range(2)] self.adventure_frames = [
tool.get_image_alpha(
tool.GFX[f'{c.OPTION_ADVENTURE}_{i}'], *frame_rect
)
for i in range(2)
]
self.adventure_image = self.adventure_frames[0] self.adventure_image = self.adventure_frames[0]
self.adventure_rect = self.adventure_image.get_rect() self.adventure_rect = self.adventure_image.get_rect()
self.adventure_rect.x = 400 self.adventure_rect.x = 400
@ -48,7 +57,12 @@ class Menu(tool.State):
# 小游戏 # 小游戏
littleGame_frame_rect = (0, 7, 317, 135) littleGame_frame_rect = (0, 7, 317, 135)
self.littleGame_frames = [tool.get_image_alpha(tool.GFX[f"{c.LITTLEGAME_BUTTON}_{i}"], *littleGame_frame_rect) for i in range(2)] self.littleGame_frames = [
tool.get_image_alpha(
tool.GFX[f'{c.LITTLEGAME_BUTTON}_{i}'], *littleGame_frame_rect
)
for i in range(2)
]
self.littleGame_image = self.littleGame_frames[0] self.littleGame_image = self.littleGame_frames[0]
self.littleGame_rect = self.littleGame_image.get_rect() self.littleGame_rect = self.littleGame_image.get_rect()
self.littleGame_rect.x = 397 self.littleGame_rect.x = 397
@ -57,7 +71,12 @@ class Menu(tool.State):
# 退出按钮 # 退出按钮
exit_frame_rect = (0, 0, 47, 27) exit_frame_rect = (0, 0, 47, 27)
self.exit_frames = [tool.get_image_alpha(tool.GFX[f"{c.EXIT}_{i}"], *exit_frame_rect, scale=1.1) for i in range(2)] self.exit_frames = [
tool.get_image_alpha(
tool.GFX[f'{c.EXIT}_{i}'], *exit_frame_rect, scale=1.1
)
for i in range(2)
]
self.exit_image = self.exit_frames[0] self.exit_image = self.exit_frames[0]
self.exit_rect = self.exit_image.get_rect() self.exit_rect = self.exit_image.get_rect()
self.exit_rect.x = 730 self.exit_rect.x = 730
@ -66,7 +85,12 @@ class Menu(tool.State):
# 选项按钮 # 选项按钮
option_button_frame_rect = (0, 0, 81, 31) option_button_frame_rect = (0, 0, 81, 31)
self.option_button_frames = [tool.get_image_alpha(tool.GFX[f"{c.OPTION_BUTTON}_{i}"], *option_button_frame_rect) for i in range(2)] self.option_button_frames = [
tool.get_image_alpha(
tool.GFX[f'{c.OPTION_BUTTON}_{i}'], *option_button_frame_rect
)
for i in range(2)
]
self.option_button_image = self.option_button_frames[0] self.option_button_image = self.option_button_frames[0]
self.option_button_rect = self.option_button_image.get_rect() self.option_button_rect = self.option_button_image.get_rect()
self.option_button_rect.x = 560 self.option_button_rect.x = 560
@ -75,7 +99,10 @@ class Menu(tool.State):
# 帮助菜单 # 帮助菜单
help_frame_rect = (0, 0, 48, 22) help_frame_rect = (0, 0, 48, 22)
self.help_frames = [tool.get_image_alpha(tool.GFX[f"{c.HELP}_{i}"], *help_frame_rect) for i in range(2)] self.help_frames = [
tool.get_image_alpha(tool.GFX[f'{c.HELP}_{i}'], *help_frame_rect)
for i in range(2)
]
self.help_image = self.help_frames[0] self.help_image = self.help_frames[0]
self.help_rect = self.help_image.get_rect() self.help_rect = self.help_image.get_rect()
self.help_rect.x = 653 self.help_rect.x = 653
@ -88,7 +115,7 @@ class Menu(tool.State):
self.adventure_clicked = False self.adventure_clicked = False
self.option_button_clicked = False self.option_button_clicked = False
def checkHilight(self, x, y): def checkHilight(self, x: int, y: int):
# 高亮冒险模式按钮 # 高亮冒险模式按钮
if self.inArea(self.adventure_rect, x, y): if self.inArea(self.adventure_rect, x, y):
self.adventure_highlight_time = self.current_time self.adventure_highlight_time = self.current_time
@ -106,13 +133,23 @@ class Menu(tool.State):
self.help_hilight_time = self.current_time self.help_hilight_time = self.current_time
# 处理按钮高亮情况 # 处理按钮高亮情况
self.adventure_image = self.chooseHilightImage(self.adventure_highlight_time, self.adventure_frames) self.adventure_image = self.chooseHilightImage(
self.exit_image = self.chooseHilightImage(self.exit_highlight_time, self.exit_frames) self.adventure_highlight_time, self.adventure_frames
self.option_button_image = self.chooseHilightImage(self.option_button_highlight_time, self.option_button_frames) )
self.littleGame_image = self.chooseHilightImage(self.littleGame_highlight_time, self.littleGame_frames) self.exit_image = self.chooseHilightImage(
self.help_image = self.chooseHilightImage(self.help_hilight_time, self.help_frames) self.exit_highlight_time, self.exit_frames
)
self.option_button_image = self.chooseHilightImage(
self.option_button_highlight_time, self.option_button_frames
)
self.littleGame_image = self.chooseHilightImage(
self.littleGame_highlight_time, self.littleGame_frames
)
self.help_image = self.chooseHilightImage(
self.help_hilight_time, self.help_frames
)
def chooseHilightImage(self, hilightTime, frames): def chooseHilightImage(self, hilightTime: int, frames):
if (self.current_time - hilightTime) < 80: if (self.current_time - hilightTime) < 80:
index = 1 index = 1
else: else:
@ -148,7 +185,9 @@ class Menu(tool.State):
def setupOptionMenu(self): def setupOptionMenu(self):
# 选项菜单框 # 选项菜单框
frame_rect = (0, 0, 500, 500) frame_rect = (0, 0, 500, 500)
self.big_menu = tool.get_image_alpha(tool.GFX[c.BIG_MENU], *frame_rect, c.BLACK, 1.1) self.big_menu = tool.get_image_alpha(
tool.GFX[c.BIG_MENU], *frame_rect, c.BLACK, 1.1
)
self.big_menu_rect = self.big_menu.get_rect() self.big_menu_rect = self.big_menu.get_rect()
self.big_menu_rect.x = 150 self.big_menu_rect.x = 150
self.big_menu_rect.y = 0 self.big_menu_rect.y = 0
@ -162,7 +201,7 @@ class Menu(tool.State):
self.return_button_rect.y = 440 self.return_button_rect.y = 440
font = pg.font.Font(c.FONT_PATH, 40) font = pg.font.Font(c.FONT_PATH, 40)
font.bold = True font.bold = True
text = font.render("返回游戏", True, c.YELLOWGREEN) text = font.render('返回游戏', True, c.YELLOWGREEN)
text_rect = text.get_rect() text_rect = text.get_rect()
text_rect.x = 105 text_rect.x = 105
text_rect.y = 18 text_rect.y = 18
@ -173,50 +212,71 @@ class Menu(tool.State):
font = pg.font.Font(c.FONT_PATH, 35) font = pg.font.Font(c.FONT_PATH, 35)
font.bold = True font.bold = True
# 音量+ # 音量+
self.sound_volume_plus_button = tool.get_image_alpha(tool.GFX[c.SOUND_VOLUME_BUTTON], *frame_rect, c.BLACK) self.sound_volume_plus_button = tool.get_image_alpha(
sign = font.render("+", True, c.YELLOWGREEN) tool.GFX[c.SOUND_VOLUME_BUTTON], *frame_rect, c.BLACK
)
sign = font.render('+', True, c.YELLOWGREEN)
sign_rect = sign.get_rect() sign_rect = sign.get_rect()
sign_rect.x = 8 sign_rect.x = 8
sign_rect.y = -4 sign_rect.y = -4
self.sound_volume_plus_button.blit(sign, sign_rect) self.sound_volume_plus_button.blit(sign, sign_rect)
self.sound_volume_plus_button_rect = self.sound_volume_plus_button.get_rect() self.sound_volume_plus_button_rect = (
self.sound_volume_plus_button.get_rect()
)
self.sound_volume_plus_button_rect.x = 500 self.sound_volume_plus_button_rect.x = 500
# 音量- # 音量-
self.sound_volume_minus_button = tool.get_image_alpha(tool.GFX[c.SOUND_VOLUME_BUTTON], *frame_rect, c.BLACK) self.sound_volume_minus_button = tool.get_image_alpha(
sign = font.render("-", True, c.YELLOWGREEN) tool.GFX[c.SOUND_VOLUME_BUTTON], *frame_rect, c.BLACK
)
sign = font.render('-', True, c.YELLOWGREEN)
sign_rect = sign.get_rect() sign_rect = sign.get_rect()
sign_rect.x = 12 sign_rect.x = 12
sign_rect.y = -6 sign_rect.y = -6
self.sound_volume_minus_button.blit(sign, sign_rect) self.sound_volume_minus_button.blit(sign, sign_rect)
self.sound_volume_minus_button_rect = self.sound_volume_minus_button.get_rect() self.sound_volume_minus_button_rect = (
self.sound_volume_minus_button.get_rect()
)
self.sound_volume_minus_button_rect.x = 450 self.sound_volume_minus_button_rect.x = 450
# 音量+、-应当处于同一高度 # 音量+、-应当处于同一高度
self.sound_volume_minus_button_rect.y = self.sound_volume_plus_button_rect.y = 250 self.sound_volume_minus_button_rect.y = (
self.sound_volume_plus_button_rect.y
) = 250
def setupSunflowerTrophy(self): def setupSunflowerTrophy(self):
# 设置金银向日葵图片信息 # 设置金银向日葵图片信息
if (self.game_info[c.LEVEL_COMPLETIONS] or self.game_info[c.LITTLEGAME_COMPLETIONS]): if (
if (self.game_info[c.LEVEL_COMPLETIONS] and self.game_info[c.LITTLEGAME_COMPLETIONS]): self.game_info[c.LEVEL_COMPLETIONS]
or self.game_info[c.LITTLEGAME_COMPLETIONS]
):
if (
self.game_info[c.LEVEL_COMPLETIONS]
and self.game_info[c.LITTLEGAME_COMPLETIONS]
):
frame_rect = (157, 0, 157, 269) frame_rect = (157, 0, 157, 269)
else: else:
frame_rect = (0, 0, 157, 269) frame_rect = (0, 0, 157, 269)
self.sunflower_trophy = tool.get_image_alpha(tool.GFX[c.TROPHY_SUNFLOWER], *frame_rect, c.BLACK) self.sunflower_trophy = tool.get_image_alpha(
tool.GFX[c.TROPHY_SUNFLOWER], *frame_rect, c.BLACK
)
self.sunflower_trophy_rect = self.sunflower_trophy.get_rect() self.sunflower_trophy_rect = self.sunflower_trophy.get_rect()
self.sunflower_trophy_rect.x = 0 self.sunflower_trophy_rect.x = 0
self.sunflower_trophy_rect.y = 280 self.sunflower_trophy_rect.y = 280
self.sunflower_trophy_show_info_time = 0 self.sunflower_trophy_show_info_time = 0
def checkSunflowerTrophyInfo(self, surface, x, y): def checkSunflowerTrophyInfo(self, surface: pg.Surface, x: int, y: int):
if self.inArea(self.sunflower_trophy_rect, x, y): if self.inArea(self.sunflower_trophy_rect, x, y):
self.sunflower_trophy_show_info_time = self.current_time self.sunflower_trophy_show_info_time = self.current_time
if (self.current_time - self.sunflower_trophy_show_info_time) < 80: if (self.current_time - self.sunflower_trophy_show_info_time) < 80:
font = pg.font.Font(c.FONT_PATH, 14) font = pg.font.Font(c.FONT_PATH, 14)
if (self.game_info[c.LEVEL_COMPLETIONS] and self.game_info[c.LITTLEGAME_COMPLETIONS]): if (
infoText = f"目前您一共完成了:冒险模式{self.game_info[c.LEVEL_COMPLETIONS]}轮,玩玩小游戏{self.game_info[c.LITTLEGAME_COMPLETIONS]}" self.game_info[c.LEVEL_COMPLETIONS]
and self.game_info[c.LITTLEGAME_COMPLETIONS]
):
infoText = f'目前您一共完成了:冒险模式{self.game_info[c.LEVEL_COMPLETIONS]}轮,玩玩小游戏{self.game_info[c.LITTLEGAME_COMPLETIONS]}'
elif self.game_info[c.LEVEL_COMPLETIONS]: elif self.game_info[c.LEVEL_COMPLETIONS]:
infoText = f"目前您一共完成了:冒险模式{self.game_info[c.LEVEL_COMPLETIONS]}轮;完成其他所有游戏模式以获得金向日葵奖杯!" infoText = f'目前您一共完成了:冒险模式{self.game_info[c.LEVEL_COMPLETIONS]}轮;完成其他所有游戏模式以获得金向日葵奖杯!'
else: else:
infoText = f"目前您一共完成了:玩玩小游戏{self.game_info[c.LITTLEGAME_COMPLETIONS]}轮;完成其他所有游戏模式以获得金向日葵奖杯!" infoText = f'目前您一共完成了:玩玩小游戏{self.game_info[c.LITTLEGAME_COMPLETIONS]}轮;完成其他所有游戏模式以获得金向日葵奖杯!'
infoImg = font.render(infoText, True, c.BLACK, c.LIGHTYELLOW) infoImg = font.render(infoText, True, c.BLACK, c.LIGHTYELLOW)
infoImg_rect = infoImg.get_rect() infoImg_rect = infoImg.get_rect()
infoImg_rect.x = self.sunflower_trophy_rect.x infoImg_rect.x = self.sunflower_trophy_rect.x
@ -228,16 +288,26 @@ class Menu(tool.State):
# 播放点击音效 # 播放点击音效
c.SOUND_BUTTON_CLICK.play() c.SOUND_BUTTON_CLICK.play()
def showCurrentVolumeImage(self, surface): def showCurrentVolumeImage(self, surface: pg.Surface):
# 由于音量可变,因此这一内容不能在一开始就结束加载,而应当不断刷新不断显示 # 由于音量可变,因此这一内容不能在一开始就结束加载,而应当不断刷新不断显示
font = pg.font.Font(c.FONT_PATH, 30) font = pg.font.Font(c.FONT_PATH, 30)
volume_tips = font.render(f"音量:{round(self.game_info[c.SOUND_VOLUME]*100):3}%", True, c.LIGHTGRAY) volume_tips = font.render(
f'音量:{round(self.game_info[c.SOUND_VOLUME]*100):3}%',
True,
c.LIGHTGRAY,
)
volume_tips_rect = volume_tips.get_rect() volume_tips_rect = volume_tips.get_rect()
volume_tips_rect.x = 275 volume_tips_rect.x = 275
volume_tips_rect.y = 247 volume_tips_rect.y = 247
surface.blit(volume_tips, volume_tips_rect) surface.blit(volume_tips, volume_tips_rect)
def update(self, surface, current_time, mouse_pos, mouse_click): def update(
self,
surface: pg.Surface,
current_time: int,
mouse_pos: list,
mouse_click,
):
self.current_time = self.game_info[c.CURRENT_TIME] = current_time self.current_time = self.game_info[c.CURRENT_TIME] = current_time
surface.blit(self.bg_image, self.bg_rect) surface.blit(self.bg_image, self.bg_rect)
@ -246,7 +316,10 @@ class Menu(tool.State):
surface.blit(self.exit_image, self.exit_rect) surface.blit(self.exit_image, self.exit_rect)
surface.blit(self.option_button_image, self.option_button_rect) surface.blit(self.option_button_image, self.option_button_rect)
surface.blit(self.help_image, self.help_rect) surface.blit(self.help_image, self.help_rect)
if self.game_info[c.LEVEL_COMPLETIONS] or self.game_info[c.LITTLEGAME_COMPLETIONS]: if (
self.game_info[c.LEVEL_COMPLETIONS]
or self.game_info[c.LITTLEGAME_COMPLETIONS]
):
surface.blit(self.sunflower_trophy, self.sunflower_trophy_rect) surface.blit(self.sunflower_trophy, self.sunflower_trophy_rect)
# 点到冒险模式后播放动画 # 点到冒险模式后播放动画
@ -262,8 +335,14 @@ class Menu(tool.State):
elif self.option_button_clicked: elif self.option_button_clicked:
surface.blit(self.big_menu, self.big_menu_rect) surface.blit(self.big_menu, self.big_menu_rect)
surface.blit(self.return_button, self.return_button_rect) surface.blit(self.return_button, self.return_button_rect)
surface.blit(self.sound_volume_plus_button, self.sound_volume_plus_button_rect) surface.blit(
surface.blit(self.sound_volume_minus_button, self.sound_volume_minus_button_rect) self.sound_volume_plus_button,
self.sound_volume_plus_button_rect,
)
surface.blit(
self.sound_volume_minus_button,
self.sound_volume_minus_button_rect,
)
self.showCurrentVolumeImage(surface) self.showCurrentVolumeImage(surface)
if mouse_pos: if mouse_pos:
# 返回 # 返回
@ -271,8 +350,12 @@ class Menu(tool.State):
self.option_button_clicked = False self.option_button_clicked = False
c.SOUND_BUTTON_CLICK.play() c.SOUND_BUTTON_CLICK.play()
# 音量+ # 音量+
elif self.inArea(self.sound_volume_plus_button_rect, *mouse_pos): elif self.inArea(
self.game_info[c.SOUND_VOLUME] = round(min(self.game_info[c.SOUND_VOLUME] + 0.05, 1), 2) self.sound_volume_plus_button_rect, *mouse_pos
):
self.game_info[c.SOUND_VOLUME] = round(
min(self.game_info[c.SOUND_VOLUME] + 0.05, 1), 2
)
# 一般不会有人想把音乐和音效分开设置故pg.mixer.Sound.set_volume()和pg.mixer.music.set_volume()需要一起用 # 一般不会有人想把音乐和音效分开设置故pg.mixer.Sound.set_volume()和pg.mixer.music.set_volume()需要一起用
pg.mixer.music.set_volume(self.game_info[c.SOUND_VOLUME]) pg.mixer.music.set_volume(self.game_info[c.SOUND_VOLUME])
for i in c.SOUNDS: for i in c.SOUNDS:
@ -280,8 +363,12 @@ class Menu(tool.State):
c.SOUND_BUTTON_CLICK.play() c.SOUND_BUTTON_CLICK.play()
self.saveUserData() self.saveUserData()
# 音量- # 音量-
elif self.inArea(self.sound_volume_minus_button_rect, *mouse_pos): elif self.inArea(
self.game_info[c.SOUND_VOLUME] = round(max(self.game_info[c.SOUND_VOLUME] - 0.05, 0), 2) self.sound_volume_minus_button_rect, *mouse_pos
):
self.game_info[c.SOUND_VOLUME] = round(
max(self.game_info[c.SOUND_VOLUME] - 0.05, 0), 2
)
# 一般不会有人想把音乐和音效分开设置故pg.mixer.Sound.set_volume()和pg.mixer.music.set_volume()需要一起用 # 一般不会有人想把音乐和音效分开设置故pg.mixer.Sound.set_volume()和pg.mixer.music.set_volume()需要一起用
pg.mixer.music.set_volume(self.game_info[c.SOUND_VOLUME]) pg.mixer.music.set_volume(self.game_info[c.SOUND_VOLUME])
for i in c.SOUNDS: for i in c.SOUNDS:
@ -293,7 +380,10 @@ class Menu(tool.State):
# 先检查选项高亮预览 # 先检查选项高亮预览
x, y = pg.mouse.get_pos() x, y = pg.mouse.get_pos()
self.checkHilight(x, y) self.checkHilight(x, y)
if (self.game_info[c.LEVEL_COMPLETIONS] or self.game_info[c.LITTLEGAME_COMPLETIONS]): if (
self.game_info[c.LEVEL_COMPLETIONS]
or self.game_info[c.LITTLEGAME_COMPLETIONS]
):
self.checkSunflowerTrophyInfo(surface, x, y) self.checkSunflowerTrophyInfo(surface, x, y)
if mouse_pos: if mouse_pos:
if self.inArea(self.adventure_rect, *mouse_pos): if self.inArea(self.adventure_rect, *mouse_pos):

View File

@ -1,8 +1,11 @@
import os import os
import pygame as pg
from abc import abstractmethod from abc import abstractmethod
from .. import tool
import pygame as pg
from .. import constants as c from .. import constants as c
from .. import tool
class Screen(tool.State): class Screen(tool.State):
def __init__(self): def __init__(self):
@ -14,7 +17,9 @@ class Screen(tool.State):
def setupImage(self, name, frame_rect=(0, 0, 800, 600), color_key=c.BLACK): def setupImage(self, name, frame_rect=(0, 0, 800, 600), color_key=c.BLACK):
# 背景图本身 # 背景图本身
self.image = tool.get_image(tool.GFX[name], *frame_rect, colorkey=color_key) self.image = tool.get_image(
tool.GFX[name], *frame_rect, colorkey=color_key
)
self.rect = self.image.get_rect() self.rect = self.image.get_rect()
self.rect.x = 0 self.rect.x = 0
self.rect.y = 0 self.rect.y = 0
@ -22,33 +27,45 @@ class Screen(tool.State):
# 按钮 # 按钮
frame_rect = (0, 0, 111, 26) frame_rect = (0, 0, 111, 26)
## 主菜单按钮 ## 主菜单按钮
self.main_menu_button_image = tool.get_image_alpha(tool.GFX[c.UNIVERSAL_BUTTON], *frame_rect) self.main_menu_button_image = tool.get_image_alpha(
self.main_menu_button_image_rect = self.main_menu_button_image.get_rect() tool.GFX[c.UNIVERSAL_BUTTON], *frame_rect
)
self.main_menu_button_image_rect = (
self.main_menu_button_image.get_rect()
)
self.main_menu_button_image_rect.x = 620 self.main_menu_button_image_rect.x = 620
### 主菜单按钮上的文字 ### 主菜单按钮上的文字
font = pg.font.Font(c.FONT_PATH, 18) font = pg.font.Font(c.FONT_PATH, 18)
main_menu_text = font.render("主菜单", True, c.NAVYBLUE) main_menu_text = font.render('主菜单', True, c.NAVYBLUE)
main_menu_text_rect = main_menu_text.get_rect() main_menu_text_rect = main_menu_text.get_rect()
main_menu_text_rect.x = 29 main_menu_text_rect.x = 29
## 继续按钮 ## 继续按钮
self.next_button_image = tool.get_image_alpha(tool.GFX[c.UNIVERSAL_BUTTON], *frame_rect) self.next_button_image = tool.get_image_alpha(
tool.GFX[c.UNIVERSAL_BUTTON], *frame_rect
)
self.next_button_image_rect = self.next_button_image.get_rect() self.next_button_image_rect = self.next_button_image.get_rect()
self.next_button_image_rect.x = 70 self.next_button_image_rect.x = 70
### 继续按钮上的文字 ### 继续按钮上的文字
if name == c.GAME_VICTORY_IMAGE: if name == c.GAME_VICTORY_IMAGE:
next_text = font.render("下一关", True, c.NAVYBLUE) next_text = font.render('下一关', True, c.NAVYBLUE)
next_text_rect = next_text.get_rect() next_text_rect = next_text.get_rect()
next_text_rect.x = 29 next_text_rect.x = 29
self.next_button_image_rect.y = self.main_menu_button_image_rect.y = 555 self.next_button_image_rect.y = (
self.main_menu_button_image_rect.y
) = 555
else: else:
next_text = font.render("重新开始", True, c.NAVYBLUE) next_text = font.render('重新开始', True, c.NAVYBLUE)
next_text_rect = next_text.get_rect() next_text_rect = next_text.get_rect()
next_text_rect.x = 21 next_text_rect.x = 21
self.next_button_image_rect.y = self.main_menu_button_image_rect.y = 530 self.next_button_image_rect.y = (
self.main_menu_button_image_rect.y
) = 530
self.next_button_image.blit(next_text, next_text_rect) self.next_button_image.blit(next_text, next_text_rect)
self.main_menu_button_image.blit(main_menu_text, main_menu_text_rect) self.main_menu_button_image.blit(main_menu_text, main_menu_text_rect)
self.image.blit(self.next_button_image, self.next_button_image_rect) self.image.blit(self.next_button_image, self.next_button_image_rect)
self.image.blit(self.main_menu_button_image, self.main_menu_button_image_rect) self.image.blit(
self.main_menu_button_image, self.main_menu_button_image_rect
)
def update(self, surface, current_time, mouse_pos, mouse_click): def update(self, surface, current_time, mouse_pos, mouse_click):
surface.fill(c.WHITE) surface.fill(c.WHITE)
@ -63,6 +80,7 @@ class Screen(tool.State):
self.next = c.MAIN_MENU self.next = c.MAIN_MENU
self.done = True self.done = True
class GameVictoryScreen(Screen): class GameVictoryScreen(Screen):
def __init__(self): def __init__(self):
Screen.__init__(self) Screen.__init__(self)
@ -73,12 +91,13 @@ class GameVictoryScreen(Screen):
self.persist = persist self.persist = persist
self.game_info = persist self.game_info = persist
self.setupImage(self.image_name) self.setupImage(self.image_name)
pg.display.set_caption("pypvz: 战斗胜利!") pg.display.set_caption('pypvz: 战斗胜利!')
pg.mixer.music.stop() pg.mixer.music.stop()
pg.mixer.music.load(os.path.join(c.PATH_MUSIC_DIR, "zenGarden.opus")) pg.mixer.music.load(os.path.join(c.PATH_MUSIC_DIR, 'zenGarden.opus'))
pg.mixer.music.play(-1, 0) pg.mixer.music.play(-1, 0)
pg.mixer.music.set_volume(self.game_info[c.SOUND_VOLUME]) pg.mixer.music.set_volume(self.game_info[c.SOUND_VOLUME])
class GameLoseScreen(Screen): class GameLoseScreen(Screen):
def __init__(self): def __init__(self):
Screen.__init__(self) Screen.__init__(self)
@ -89,10 +108,11 @@ class GameLoseScreen(Screen):
self.persist = persist self.persist = persist
self.game_info = persist self.game_info = persist
self.setupImage(self.image_name, (-118, -40, 800, 600), c.WHITE) self.setupImage(self.image_name, (-118, -40, 800, 600), c.WHITE)
pg.display.set_caption("pypvz: 战斗失败!") pg.display.set_caption('pypvz: 战斗失败!')
# 停止播放原来关卡中的音乐 # 停止播放原来关卡中的音乐
pg.mixer.music.stop() pg.mixer.music.stop()
class AwardScreen(tool.State): class AwardScreen(tool.State):
def __init__(self): def __init__(self):
tool.State.__init__(self) tool.State.__init__(self)
@ -100,7 +120,9 @@ class AwardScreen(tool.State):
def setupImage(self): def setupImage(self):
# 主体 # 主体
frame_rect = (0, 0, 800, 600) frame_rect = (0, 0, 800, 600)
self.image = tool.get_image(tool.GFX[c.AWARD_SCREEN_IMAGE], *frame_rect) self.image = tool.get_image(
tool.GFX[c.AWARD_SCREEN_IMAGE], *frame_rect
)
self.rect = self.image.get_rect() self.rect = self.image.get_rect()
self.rect.x = 0 self.rect.x = 0
self.rect.y = 0 self.rect.y = 0
@ -108,7 +130,7 @@ class AwardScreen(tool.State):
# 文字 # 文字
# 标题处文字 # 标题处文字
font = pg.font.Font(c.FONT_PATH, 37) font = pg.font.Font(c.FONT_PATH, 37)
title_text = font.render("您获得了新的战利品!", True, c.PARCHMENT_YELLOW) title_text = font.render('您获得了新的战利品!', True, c.PARCHMENT_YELLOW)
title_text_rect = title_text.get_rect() title_text_rect = title_text.get_rect()
title_text_rect.x = 220 title_text_rect.x = 220
title_text_rect.y = 23 title_text_rect.y = 23
@ -118,31 +140,44 @@ class AwardScreen(tool.State):
frame_rect = (0, 0, 111, 26) frame_rect = (0, 0, 111, 26)
if self.show_only_one_option: if self.show_only_one_option:
## 主菜单按钮 ## 主菜单按钮
self.main_menu_button_image = tool.get_image_alpha(tool.GFX[c.UNIVERSAL_BUTTON], *frame_rect) self.main_menu_button_image = tool.get_image_alpha(
self.main_menu_button_image_rect = self.main_menu_button_image.get_rect() tool.GFX[c.UNIVERSAL_BUTTON], *frame_rect
)
self.main_menu_button_image_rect = (
self.main_menu_button_image.get_rect()
)
self.main_menu_button_image_rect.x = 343 self.main_menu_button_image_rect.x = 343
self.main_menu_button_image_rect.y = 520 self.main_menu_button_image_rect.y = 520
### 主菜单按钮上的文字 ### 主菜单按钮上的文字
font = pg.font.Font(c.FONT_PATH, 18) font = pg.font.Font(c.FONT_PATH, 18)
main_menu_text = font.render("主菜单", True, c.NAVYBLUE) main_menu_text = font.render('主菜单', True, c.NAVYBLUE)
main_menu_text_rect = main_menu_text.get_rect() main_menu_text_rect = main_menu_text.get_rect()
main_menu_text_rect.x = 29 main_menu_text_rect.x = 29
self.main_menu_button_image.blit(main_menu_text, main_menu_text_rect) self.main_menu_button_image.blit(
self.image.blit(self.main_menu_button_image, self.main_menu_button_image_rect) main_menu_text, main_menu_text_rect
)
self.image.blit(
self.main_menu_button_image, self.main_menu_button_image_rect
)
# 绘制向日葵奖杯 # 绘制向日葵奖杯
if (self.game_info[c.LEVEL_COMPLETIONS] and self.game_info[c.LITTLEGAME_COMPLETIONS]): if (
self.game_info[c.LEVEL_COMPLETIONS]
and self.game_info[c.LITTLEGAME_COMPLETIONS]
):
frame_rect = (157, 0, 157, 269) frame_rect = (157, 0, 157, 269)
intro_title = "金向日葵奖杯" intro_title = '金向日葵奖杯'
intro_content = "您已通过所有关卡,获得此奖励!" intro_content = '您已通过所有关卡,获得此奖励!'
else: else:
frame_rect = (0, 0, 157, 269) frame_rect = (0, 0, 157, 269)
intro_title = "银向日葵奖杯" intro_title = '银向日葵奖杯'
if self.game_info[c.LEVEL_COMPLETIONS]: if self.game_info[c.LEVEL_COMPLETIONS]:
intro_content = "您已完成冒险模式,获得此奖励!" intro_content = '您已完成冒险模式,获得此奖励!'
else: else:
intro_content = "您已完成玩玩小游戏,获得此奖励!" intro_content = '您已完成玩玩小游戏,获得此奖励!'
sunflower_trophy_image = tool.get_image_alpha(tool.GFX[c.TROPHY_SUNFLOWER], *frame_rect, scale=0.7) sunflower_trophy_image = tool.get_image_alpha(
tool.GFX[c.TROPHY_SUNFLOWER], *frame_rect, scale=0.7
)
sunflower_trophy_rect = sunflower_trophy_image.get_rect() sunflower_trophy_rect = sunflower_trophy_image.get_rect()
sunflower_trophy_rect.x = 348 sunflower_trophy_rect.x = 348
sunflower_trophy_rect.y = 108 sunflower_trophy_rect.y = 108
@ -150,7 +185,9 @@ class AwardScreen(tool.State):
# 绘制介绍标题 # 绘制介绍标题
font = pg.font.Font(c.FONT_PATH, 22) font = pg.font.Font(c.FONT_PATH, 22)
intro_title_img = font.render(intro_title, True, c.PARCHMENT_YELLOW) intro_title_img = font.render(
intro_title, True, c.PARCHMENT_YELLOW
)
intro_title_rect = intro_title_img.get_rect() intro_title_rect = intro_title_img.get_rect()
intro_title_rect.x = 333 intro_title_rect.x = 333
intro_title_rect.y = 305 intro_title_rect.y = 305
@ -165,40 +202,56 @@ class AwardScreen(tool.State):
self.image.blit(intro_content_img, intro_content_rect) self.image.blit(intro_content_img, intro_content_rect)
else: else:
## 继续按钮 ## 继续按钮
self.next_button_image = tool.get_image_alpha(tool.GFX[c.UNIVERSAL_BUTTON], *frame_rect) self.next_button_image = tool.get_image_alpha(
tool.GFX[c.UNIVERSAL_BUTTON], *frame_rect
)
self.next_button_image_rect = self.next_button_image.get_rect() self.next_button_image_rect = self.next_button_image.get_rect()
self.next_button_image_rect.x = 70 self.next_button_image_rect.x = 70
### 继续按钮上的文字 ### 继续按钮上的文字
font = pg.font.Font(c.FONT_PATH, 18) font = pg.font.Font(c.FONT_PATH, 18)
next_text = font.render("继续", True, c.NAVYBLUE) next_text = font.render('继续', True, c.NAVYBLUE)
next_text_rect = next_text.get_rect() next_text_rect = next_text.get_rect()
next_text_rect.x = 37 next_text_rect.x = 37
## 主菜单按钮 ## 主菜单按钮
self.main_menu_button_image = tool.get_image_alpha(tool.GFX[c.UNIVERSAL_BUTTON], *frame_rect) self.main_menu_button_image = tool.get_image_alpha(
self.main_menu_button_image_rect = self.main_menu_button_image.get_rect() tool.GFX[c.UNIVERSAL_BUTTON], *frame_rect
)
self.main_menu_button_image_rect = (
self.main_menu_button_image.get_rect()
)
self.main_menu_button_image_rect.x = 620 self.main_menu_button_image_rect.x = 620
self.next_button_image_rect.y = self.main_menu_button_image_rect.y = 540 self.next_button_image_rect.y = (
self.main_menu_button_image_rect.y
) = 540
### 主菜单按钮上的文字 ### 主菜单按钮上的文字
main_menu_text = font.render("主菜单", True, c.NAVYBLUE) main_menu_text = font.render('主菜单', True, c.NAVYBLUE)
main_menu_text_rect = main_menu_text.get_rect() main_menu_text_rect = main_menu_text.get_rect()
main_menu_text_rect.x = 29 main_menu_text_rect.x = 29
self.next_button_image.blit(next_text, next_text_rect) self.next_button_image.blit(next_text, next_text_rect)
self.main_menu_button_image.blit(main_menu_text, main_menu_text_rect) self.main_menu_button_image.blit(
self.image.blit(self.next_button_image, self.next_button_image_rect) main_menu_text, main_menu_text_rect
self.image.blit(self.main_menu_button_image, self.main_menu_button_image_rect) )
self.image.blit(
self.next_button_image, self.next_button_image_rect
)
self.image.blit(
self.main_menu_button_image, self.main_menu_button_image_rect
)
def startup(self, current_time, persist): def startup(self, current_time, persist):
self.start_time = current_time self.start_time = current_time
self.persist = persist self.persist = persist
self.game_info = persist self.game_info = persist
if (c.PASSED_ALL in self.game_info) and (not self.game_info[c.PASSED_ALL]): if (c.PASSED_ALL in self.game_info) and (
not self.game_info[c.PASSED_ALL]
):
self.show_only_one_option = False self.show_only_one_option = False
else: else:
self.show_only_one_option = True self.show_only_one_option = True
self.setupImage() self.setupImage()
pg.display.set_caption("pypvz: 您获得了新的战利品!") pg.display.set_caption('pypvz: 您获得了新的战利品!')
pg.mixer.music.stop() pg.mixer.music.stop()
pg.mixer.music.load(os.path.join(c.PATH_MUSIC_DIR, "zenGarden.opus")) pg.mixer.music.load(os.path.join(c.PATH_MUSIC_DIR, 'zenGarden.opus'))
pg.mixer.music.play(-1, 0) pg.mixer.music.play(-1, 0)
pg.mixer.music.set_volume(self.game_info[c.SOUND_VOLUME]) pg.mixer.music.set_volume(self.game_info[c.SOUND_VOLUME])
@ -214,6 +267,7 @@ class AwardScreen(tool.State):
self.next = c.LEVEL self.next = c.LEVEL
self.done = True self.done = True
class HelpScreen(tool.State): class HelpScreen(tool.State):
def __init__(self): def __init__(self):
tool.State.__init__(self) tool.State.__init__(self)
@ -223,31 +277,39 @@ class HelpScreen(tool.State):
self.persist = persist self.persist = persist
self.game_info = persist self.game_info = persist
self.setupImage() self.setupImage()
pg.display.set_caption("pypvz: 帮助") pg.display.set_caption('pypvz: 帮助')
pg.mixer.music.stop() pg.mixer.music.stop()
c.SOUND_HELP_SCREEN.play() c.SOUND_HELP_SCREEN.play()
def setupImage(self): def setupImage(self):
# 主体 # 主体
frame_rect = (-100, -50, 800, 600) frame_rect = (-100, -50, 800, 600)
self.image = tool.get_image(tool.GFX[c.HELP_SCREEN_IMAGE], *frame_rect, colorkey=(0, 255, 255)) self.image = tool.get_image(
tool.GFX[c.HELP_SCREEN_IMAGE], *frame_rect, colorkey=(0, 255, 255)
)
self.rect = self.image.get_rect() self.rect = self.image.get_rect()
self.rect.x = 0 self.rect.x = 0
self.rect.y = 0 self.rect.y = 0
# 主菜单按钮 # 主菜单按钮
frame_rect = (0, 0, 111, 26) frame_rect = (0, 0, 111, 26)
self.main_menu_button_image = tool.get_image_alpha(tool.GFX[c.UNIVERSAL_BUTTON], *frame_rect) self.main_menu_button_image = tool.get_image_alpha(
self.main_menu_button_image_rect = self.main_menu_button_image.get_rect() tool.GFX[c.UNIVERSAL_BUTTON], *frame_rect
)
self.main_menu_button_image_rect = (
self.main_menu_button_image.get_rect()
)
self.main_menu_button_image_rect.x = 343 self.main_menu_button_image_rect.x = 343
self.main_menu_button_image_rect.y = 500 self.main_menu_button_image_rect.y = 500
### 主菜单按钮上的文字 ### 主菜单按钮上的文字
font = pg.font.Font(c.FONT_PATH, 18) font = pg.font.Font(c.FONT_PATH, 18)
main_menu_text = font.render("主菜单", True, c.NAVYBLUE) main_menu_text = font.render('主菜单', True, c.NAVYBLUE)
main_menu_text_rect = main_menu_text.get_rect() main_menu_text_rect = main_menu_text.get_rect()
main_menu_text_rect.x = 29 main_menu_text_rect.x = 29
self.main_menu_button_image.blit(main_menu_text, main_menu_text_rect) self.main_menu_button_image.blit(main_menu_text, main_menu_text_rect)
self.image.blit(self.main_menu_button_image, self.main_menu_button_image_rect) self.image.blit(
self.main_menu_button_image, self.main_menu_button_image_rect
)
def update(self, surface, current_time, mouse_pos, mouse_click): def update(self, surface, current_time, mouse_pos, mouse_click):
surface.fill(c.BLACK) surface.fill(c.BLACK)

View File

@ -1,14 +1,17 @@
import json
import logging import logging
import os import os
import json
from abc import abstractmethod from abc import abstractmethod
import pygame as pg import pygame as pg
from pygame.locals import * from pygame.locals import *
from . import constants as c from . import constants as c
logger = logging.getLogger("main")
logger = logging.getLogger('main')
# 状态机 抽象基类 # 状态机 抽象基类
class State(): class State:
def __init__(self): def __init__(self):
self.start_time = 0 self.start_time = 0
self.current_time = 0 self.current_time = 0
@ -18,30 +21,31 @@ class State():
# 当从其他状态进入这个状态时,需要进行的初始化操作 # 当从其他状态进入这个状态时,需要进行的初始化操作
@abstractmethod @abstractmethod
def startup(self, current_time, persist): def startup(self, current_time: int, persist: dict):
# 前面加了@abstractmethod表示抽象基类中必须要重新定义的methodmethod是对象和函数的结合 # 前面加了@abstractmethod表示抽象基类中必须要重新定义的methodmethod是对象和函数的结合
pass pass
# 当从这个状态退出时,需要进行的清除操作 # 当从这个状态退出时,需要进行的清除操作
def cleanup(self): def cleanup(self):
self.done = False self.done = False
return self.persist return self.persist
# 在这个状态运行时进行的更新操作 # 在这个状态运行时进行的更新操作
@abstractmethod @abstractmethod
def update(self, surface, keys, current_time): def update(self, surface: pg.Surface, keys, current_time: int):
# 前面加了@abstractmethod表示抽象基类中必须要重新定义的method # 前面加了@abstractmethod表示抽象基类中必须要重新定义的method
pass pass
# 工具:范围判断函数,用于判断点击 # 工具:范围判断函数,用于判断点击
def inArea(self, rect, x, y): def inArea(self, rect: pg.Rect, x: int, y: int):
if (rect.x <= x <= rect.right and if rect.x <= x <= rect.right and rect.y <= y <= rect.bottom:
rect.y <= y <= rect.bottom):
return True return True
else: else:
return False return False
# 工具:用户数据保存函数 # 工具:用户数据保存函数
def saveUserData(self): def saveUserData(self):
with open(c.USERDATA_PATH, "w", encoding="utf-8") as f: with open(c.USERDATA_PATH, 'w', encoding='utf-8') as f:
userdata = {} userdata = {}
for i in self.game_info: for i in self.game_info:
if i in c.INIT_USERDATA: if i in c.INIT_USERDATA:
@ -49,15 +53,19 @@ class State():
data_to_save = json.dumps(userdata, sort_keys=True, indent=4) data_to_save = json.dumps(userdata, sort_keys=True, indent=4)
f.write(data_to_save) f.write(data_to_save)
# 进行游戏控制 循环 事件响应 # 进行游戏控制 循环 事件响应
class Control(): class Control:
def __init__(self): def __init__(self):
self.screen = pg.display.get_surface() self.screen = pg.display.get_surface()
self.done = False self.done = False
self.clock = pg.time.Clock() # 创建一个对象来帮助跟踪时间 self.clock = pg.time.Clock() # 创建一个对象来帮助跟踪时间
self.keys = pg.key.get_pressed() self.keys = pg.key.get_pressed()
self.mouse_pos = None self.mouse_pos = None
self.mouse_click = [False, False] # value:[left mouse click, right mouse click] self.mouse_click = [
False,
False,
] # value:[left mouse click, right mouse click]
self.current_time = 0.0 self.current_time = 0.0
self.state_dict = {} self.state_dict = {}
self.state_name = None self.state_name = None
@ -66,12 +74,12 @@ class Control():
# 存在存档即导入 # 存在存档即导入
# 先自动修复读写权限(Python权限规则和Unix不一样420表示unix的644Windows自动忽略不支持项) # 先自动修复读写权限(Python权限规则和Unix不一样420表示unix的644Windows自动忽略不支持项)
os.chmod(c.USERDATA_PATH, 420) os.chmod(c.USERDATA_PATH, 420)
with open(c.USERDATA_PATH, encoding="utf-8") as f: with open(c.USERDATA_PATH, encoding='utf-8') as f:
userdata = json.load(f) userdata = json.load(f)
except FileNotFoundError: except FileNotFoundError:
self.setupUserData() self.setupUserData()
except json.JSONDecodeError: except json.JSONDecodeError:
logger.warning("用户存档解码错误!程序将新建初始存档!\n") logger.warning('用户存档解码错误!程序将新建初始存档!\n')
self.setupUserData() self.setupUserData()
else: # 没有引发异常才执行 else: # 没有引发异常才执行
self.game_info = {} self.game_info = {}
@ -84,8 +92,10 @@ class Control():
self.game_info[key] = c.INIT_USERDATA[key] self.game_info[key] = c.INIT_USERDATA[key]
need_to_rewrite = True need_to_rewrite = True
if need_to_rewrite: if need_to_rewrite:
with open(c.USERDATA_PATH, "w", encoding="utf-8") as f: with open(c.USERDATA_PATH, 'w', encoding='utf-8') as f:
savedata = json.dumps(self.game_info, sort_keys=True, indent=4) savedata = json.dumps(
self.game_info, sort_keys=True, indent=4
)
f.write(savedata) f.write(savedata)
# 存档内不包含即时游戏时间信息,需要新建 # 存档内不包含即时游戏时间信息,需要新建
self.game_info[c.CURRENT_TIME] = 0 self.game_info[c.CURRENT_TIME] = 0
@ -96,12 +106,12 @@ class Control():
def setupUserData(self): def setupUserData(self):
if not os.path.exists(os.path.dirname(c.USERDATA_PATH)): if not os.path.exists(os.path.dirname(c.USERDATA_PATH)):
os.makedirs(os.path.dirname(c.USERDATA_PATH)) os.makedirs(os.path.dirname(c.USERDATA_PATH))
with open(c.USERDATA_PATH, "w", encoding="utf-8") as f: with open(c.USERDATA_PATH, 'w', encoding='utf-8') as f:
savedata = json.dumps(c.INIT_USERDATA, sort_keys=True, indent=4) savedata = json.dumps(c.INIT_USERDATA, sort_keys=True, indent=4)
f.write(savedata) f.write(savedata)
self.game_info = c.INIT_USERDATA.copy() # 内部全是不可变对象,浅拷贝即可 self.game_info = c.INIT_USERDATA.copy() # 内部全是不可变对象,浅拷贝即可
def setup_states(self, state_dict, start_state): def setup_states(self, state_dict: dict, start_state):
self.state_dict = state_dict self.state_dict = state_dict
self.state_name = start_state self.state_name = start_state
self.state = self.state_dict[self.state_name] self.state = self.state_dict[self.state_name]
@ -114,7 +124,9 @@ class Control():
if self.state.done: if self.state.done:
self.flip_state() self.flip_state()
self.state.update(self.screen, self.current_time, self.mouse_pos, self.mouse_click) self.state.update(
self.screen, self.current_time, self.mouse_pos, self.mouse_click
)
self.mouse_pos = None self.mouse_pos = None
self.mouse_click[0] = False self.mouse_click[0] = False
self.mouse_click[1] = False self.mouse_click[1] = False
@ -136,17 +148,24 @@ class Control():
elif event.type == pg.KEYDOWN: elif event.type == pg.KEYDOWN:
self.keys = pg.key.get_pressed() self.keys = pg.key.get_pressed()
if event.key == pg.K_f: if event.key == pg.K_f:
pg.display.set_mode(c.SCREEN_SIZE, pg.HWSURFACE|pg.FULLSCREEN) pg.display.set_mode(
c.SCREEN_SIZE, pg.HWSURFACE | pg.FULLSCREEN
)
elif event.key == pg.K_u: elif event.key == pg.K_u:
pg.display.set_mode(c.SCREEN_SIZE) pg.display.set_mode(c.SCREEN_SIZE)
elif event.type == pg.KEYUP: elif event.type == pg.KEYUP:
self.keys = pg.key.get_pressed() self.keys = pg.key.get_pressed()
elif event.type == pg.MOUSEBUTTONDOWN: elif event.type == pg.MOUSEBUTTONDOWN:
self.mouse_pos = pg.mouse.get_pos() self.mouse_pos = pg.mouse.get_pos()
self.mouse_click[0], _, self.mouse_click[1] = pg.mouse.get_pressed() (
self.mouse_click[0],
_,
self.mouse_click[1],
) = pg.mouse.get_pressed()
# self.mouse_click[0]表示左键self.mouse_click[1]表示右键 # self.mouse_click[0]表示左键self.mouse_click[1]表示右键
print(f"点击位置: ({self.mouse_pos[0]:3}, {self.mouse_pos[1]:3}) 左右键点击情况: {self.mouse_click}") print(
f'点击位置: ({self.mouse_pos[0]:3}, {self.mouse_pos[1]:3}) 左右键点击情况: {self.mouse_click}'
)
def run(self): def run(self):
while not self.done: while not self.done:
@ -155,7 +174,16 @@ class Control():
pg.display.update() pg.display.update()
self.clock.tick(self.fps) self.clock.tick(self.fps)
def get_image(sheet, x, y, width, height, colorkey=c.BLACK, scale=1):
def get_image(
sheet: pg.Surface,
x: int,
y: int,
width: int,
height: int,
colorkey: tuple[int] = c.BLACK,
scale: int = 1,
) -> pg.Surface:
# 不保留alpha通道的图片导入 # 不保留alpha通道的图片导入
image = pg.Surface([width, height]) image = pg.Surface([width, height])
rect = image.get_rect() rect = image.get_rect()
@ -163,24 +191,36 @@ def get_image(sheet, x, y, width, height, colorkey=c.BLACK, scale=1):
image.blit(sheet, (0, 0), (x, y, width, height)) image.blit(sheet, (0, 0), (x, y, width, height))
if colorkey: if colorkey:
image.set_colorkey(colorkey) image.set_colorkey(colorkey)
image = pg.transform.scale(image, image = pg.transform.scale(
(int(rect.width*scale), image, (int(rect.width * scale), int(rect.height * scale))
int(rect.height*scale))) )
return image return image
def get_image_alpha(sheet, x, y, width, height, colorkey=c.BLACK, scale=1):
def get_image_alpha(
sheet: pg.Surface,
x: int,
y: int,
width: int,
height: int,
colorkey: tuple[int] = c.BLACK,
scale: int = 1,
) -> pg.Surface:
# 保留alpha通道的图片导入 # 保留alpha通道的图片导入
image = pg.Surface([width, height], SRCALPHA) image = pg.Surface([width, height], SRCALPHA)
rect = image.get_rect() rect = image.get_rect()
image.blit(sheet, (0, 0), (x, y, width, height)) image.blit(sheet, (0, 0), (x, y, width, height))
image.set_colorkey(colorkey) image.set_colorkey(colorkey)
image = pg.transform.scale(image, image = pg.transform.scale(
(int(rect.width*scale), image, (int(rect.width * scale), int(rect.height * scale))
int(rect.height*scale))) )
return image return image
def load_image_frames(directory, image_name, colorkey, accept):
def load_image_frames(
directory: str, image_name: str, colorkey: tuple[int], accept: tuple[str]
) -> list[pg.Surface]:
frame_list = [] frame_list = []
tmp = {} tmp = {}
# image_name is "Peashooter", pic name is "Peashooter_1", get the index 1 # image_name is "Peashooter", pic name is "Peashooter_1", get the index 1
@ -203,8 +243,13 @@ def load_image_frames(directory, image_name, colorkey, accept):
frame_list.append(tmp[i]) frame_list.append(tmp[i])
return frame_list return frame_list
# colorkeys 是设置图像中的某个颜色值为透明,这里用来消除白边 # colorkeys 是设置图像中的某个颜色值为透明,这里用来消除白边
def load_all_gfx(directory, colorkey=c.WHITE, accept=(".png", ".jpg", ".bmp", ".gif", ".webp")): def load_all_gfx(
directory: str,
colorkey: tuple[int] = c.WHITE,
accept: tuple[str] = ('.png', '.jpg', '.bmp', '.gif', '.webp'),
) -> dict[str : pg.Surface]:
graphics = {} graphics = {}
for name1 in os.listdir(directory): for name1 in os.listdir(directory):
# subfolders under the folder resources\graphics # subfolders under the folder resources\graphics
@ -220,11 +265,15 @@ def load_all_gfx(directory, colorkey=c.WHITE, accept=(".png", ".jpg", ".bmp", ".
if os.path.isdir(dir3): if os.path.isdir(dir3):
# e.g. it"s the folder resources\graphics\Zombies\ConeheadZombie\ConeheadZombieAttack # e.g. it"s the folder resources\graphics\Zombies\ConeheadZombie\ConeheadZombieAttack
image_name, _ = os.path.splitext(name3) image_name, _ = os.path.splitext(name3)
graphics[image_name] = load_image_frames(dir3, image_name, colorkey, accept) graphics[image_name] = load_image_frames(
dir3, image_name, colorkey, accept
)
else: else:
# e.g. pics under the folder resources\graphics\Plants\Peashooter # e.g. pics under the folder resources\graphics\Plants\Peashooter
image_name, _ = os.path.splitext(name2) image_name, _ = os.path.splitext(name2)
graphics[image_name] = load_image_frames(dir2, image_name, colorkey, accept) graphics[image_name] = load_image_frames(
dir2, image_name, colorkey, accept
)
break break
else: else:
# e.g. pics under the folder resources\graphics\Screen # e.g. pics under the folder resources\graphics\Screen
@ -239,10 +288,13 @@ def load_all_gfx(directory, colorkey=c.WHITE, accept=(".png", ".jpg", ".bmp", ".
graphics[name] = img graphics[name] = img
return graphics return graphics
pg.display.set_caption(c.ORIGINAL_CAPTION) # 设置标题 pg.display.set_caption(c.ORIGINAL_CAPTION) # 设置标题
SCREEN = pg.display.set_mode(c.SCREEN_SIZE) # 设置初始屏幕 SCREEN = pg.display.set_mode(c.SCREEN_SIZE, pg.SCALED) # 设置初始屏幕
pg.mixer.set_num_channels(255) # 设置可以同时播放的音频数量默认为8经常不够用 pg.mixer.set_num_channels(255) # 设置可以同时播放的音频数量默认为8经常不够用
if os.path.exists(c.ORIGINAL_LOGO): # 设置窗口图标仅对非Nuitka时生效Nuitka不需要包括额外的图标文件自动跳过这一过程即可 if os.path.exists(
c.ORIGINAL_LOGO
): # 设置窗口图标仅对非Nuitka时生效Nuitka不需要包括额外的图标文件自动跳过这一过程即可
pg.display.set_icon(pg.image.load(c.ORIGINAL_LOGO)) pg.display.set_icon(pg.image.load(c.ORIGINAL_LOGO))
GFX = load_all_gfx(c.PATH_IMG_DIR) GFX = load_all_gfx(c.PATH_IMG_DIR)

209
uv.lock generated Normal file
View File

@ -0,0 +1,209 @@
version = 1
revision = 2
requires-python = ">=3.12"
[[package]]
name = "black"
version = "22.1.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "click" },
{ name = "mypy-extensions" },
{ name = "pathspec" },
{ name = "platformdirs" },
{ name = "tomli" },
]
sdist = { url = "https://files.pythonhosted.org/packages/42/58/8a3443a5034685152270f9012a9d196c9f165791ed3f2777307708b15f6c/black-22.1.0.tar.gz", hash = "sha256:a7c0192d35635f6fc1174be575cb7915e92e5dd629ee79fdaf0dcfa41a80afb5", size = 559521, upload-time = "2022-01-29T20:38:08.749Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a5/59/bd6d44da2b364fd2bd7a0b2ce2edfe200b79faad1cde14ce5ef13d504393/black-22.1.0-py3-none-any.whl", hash = "sha256:3524739d76b6b3ed1132422bf9d82123cd1705086723bc3e235ca39fd21c667d", size = 160408, upload-time = "2022-01-29T20:38:07.336Z" },
]
[[package]]
name = "blue"
version = "0.9.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "black" },
{ name = "flake8" },
]
sdist = { url = "https://files.pythonhosted.org/packages/68/5a/ef15e7accbe49dbeeb8e7c01e13b8459006c6fed4db8fe833f2ae4924fd7/blue-0.9.1.tar.gz", hash = "sha256:76b4f26884a8425042356601d80773db6e0e14bebaa7a59d7c54bf8cef2e2af5", size = 10594, upload-time = "2022-08-01T16:38:53.798Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c5/7b/e6bfbe69b8b22abbfbdd993142bcabd1b2ce237a42c250181c5152052feb/blue-0.9.1-py3-none-any.whl", hash = "sha256:037742c072c58a2ff024f59fb9164257b907f97f8f862008db3b013d1f27cc22", size = 10590, upload-time = "2022-08-01T16:38:51.556Z" },
]
[[package]]
name = "click"
version = "8.2.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" },
]
[[package]]
name = "colorama"
version = "0.4.6"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
]
[[package]]
name = "flake8"
version = "4.0.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "mccabe" },
{ name = "pycodestyle" },
{ name = "pyflakes" },
]
sdist = { url = "https://files.pythonhosted.org/packages/e6/84/d8db922289195c435779b4ca3a3f583f263f87e67954f7b2e83c8da21f48/flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d", size = 154905, upload-time = "2021-10-11T12:42:48.941Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/34/39/cde2c8a227abb4f9ce62fe55586b920f438f1d2903a1a22514d0b982c333/flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d", size = 64091, upload-time = "2021-10-11T12:42:47.401Z" },
]
[[package]]
name = "mccabe"
version = "0.6.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/06/18/fa675aa501e11d6d6ca0ae73a101b2f3571a565e0f7d38e062eec18a91ee/mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f", size = 8612, upload-time = "2017-01-26T22:13:15.699Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/87/89/479dc97e18549e21354893e4ee4ef36db1d237534982482c3681ee6e7b57/mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", size = 8556, upload-time = "2017-01-26T22:13:14.36Z" },
]
[[package]]
name = "mypy-extensions"
version = "1.1.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" },
]
[[package]]
name = "pathspec"
version = "0.12.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" },
]
[[package]]
name = "platformdirs"
version = "4.3.8"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362, upload-time = "2025-05-07T22:47:42.121Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567, upload-time = "2025-05-07T22:47:40.376Z" },
]
[[package]]
name = "pycodestyle"
version = "2.8.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/08/dc/b29daf0a202b03f57c19e7295b60d1d5e1281c45a6f5f573e41830819918/pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f", size = 102299, upload-time = "2021-10-11T00:56:27.496Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/15/94/bc43a2efb7b8615e38acde2b6624cae8c9ec86faf718ff5676c5179a7714/pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20", size = 42112, upload-time = "2021-10-11T00:56:25.599Z" },
]
[[package]]
name = "pyflakes"
version = "2.4.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/15/60/c577e54518086e98470e9088278247f4af1d39cb43bcbd731e2c307acd6a/pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c", size = 69101, upload-time = "2021-10-06T20:39:50.936Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/43/fb/38848eb494af7df9aeb2d7673ace8b213313eb7e391691a79dbaeb6a838f/pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e", size = 69704, upload-time = "2021-10-06T20:39:49.185Z" },
]
[[package]]
name = "pygame"
version = "2.6.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/49/cc/08bba60f00541f62aaa252ce0cfbd60aebd04616c0b9574f755b583e45ae/pygame-2.6.1.tar.gz", hash = "sha256:56fb02ead529cee00d415c3e007f75e0780c655909aaa8e8bf616ee09c9feb1f", size = 14808125, upload-time = "2024-09-29T13:41:34.698Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/92/16/2c602c332f45ff9526d61f6bd764db5096ff9035433e2172e2d2cadae8db/pygame-2.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4ee7f2771f588c966fa2fa8b829be26698c9b4836f82ede5e4edc1a68594942e", size = 13118279, upload-time = "2024-09-29T14:26:30.427Z" },
{ url = "https://files.pythonhosted.org/packages/cd/53/77ccbc384b251c6e34bfd2e734c638233922449a7844e3c7a11ef91cee39/pygame-2.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c8040ea2ab18c6b255af706ec01355c8a6b08dc48d77fd4ee783f8fc46a843bf", size = 12384524, upload-time = "2024-09-29T14:26:49.996Z" },
{ url = "https://files.pythonhosted.org/packages/06/be/3ed337583f010696c3b3435e89a74fb29d0c74d0931e8f33c0a4246307a9/pygame-2.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47a6938de93fa610accd4969e638c2aebcb29b2fca518a84c3a39d91ab47116", size = 13587123, upload-time = "2024-09-29T11:10:50.072Z" },
{ url = "https://files.pythonhosted.org/packages/fd/ca/b015586a450db59313535662991b34d24c1f0c0dc149cc5f496573900f4e/pygame-2.6.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33006f784e1c7d7e466fcb61d5489da59cc5f7eb098712f792a225df1d4e229d", size = 14275532, upload-time = "2024-09-29T11:39:59.356Z" },
{ url = "https://files.pythonhosted.org/packages/b9/f2/d31e6ad42d657af07be2ffd779190353f759a07b51232b9e1d724f2cda46/pygame-2.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1206125f14cae22c44565c9d333607f1d9f59487b1f1432945dfc809aeaa3e88", size = 13952653, upload-time = "2024-09-29T11:40:01.781Z" },
{ url = "https://files.pythonhosted.org/packages/f3/42/8ea2a6979e6fa971702fece1747e862e2256d4a8558fe0da6364dd946c53/pygame-2.6.1-cp312-cp312-win32.whl", hash = "sha256:84fc4054e25262140d09d39e094f6880d730199710829902f0d8ceae0213379e", size = 10252421, upload-time = "2024-09-29T11:14:26.877Z" },
{ url = "https://files.pythonhosted.org/packages/5f/90/7d766d54bb95939725e9a9361f9c06b0cfbe3fe100aa35400f0a461a278a/pygame-2.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:3a9e7396be0d9633831c3f8d5d82dd63ba373ad65599628294b7a4f8a5a01a65", size = 10624591, upload-time = "2024-09-29T11:52:54.489Z" },
{ url = "https://files.pythonhosted.org/packages/e1/91/718acf3e2a9d08a6ddcc96bd02a6f63c99ee7ba14afeaff2a51c987df0b9/pygame-2.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ae6039f3a55d800db80e8010f387557b528d34d534435e0871326804df2a62f2", size = 13090765, upload-time = "2024-09-29T14:27:02.377Z" },
{ url = "https://files.pythonhosted.org/packages/0e/c6/9cb315de851a7682d9c7568a41ea042ee98d668cb8deadc1dafcab6116f0/pygame-2.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2a3a1288e2e9b1e5834e425bedd5ba01a3cd4902b5c2bff8ed4a740ccfe98171", size = 12381704, upload-time = "2024-09-29T14:27:10.228Z" },
{ url = "https://files.pythonhosted.org/packages/9f/8f/617a1196e31ae3b46be6949fbaa95b8c93ce15e0544266198c2266cc1b4d/pygame-2.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27eb17e3dc9640e4b4683074f1890e2e879827447770470c2aba9f125f74510b", size = 13581091, upload-time = "2024-09-29T11:30:27.653Z" },
{ url = "https://files.pythonhosted.org/packages/3b/87/2851a564e40a2dad353f1c6e143465d445dab18a95281f9ea458b94f3608/pygame-2.6.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c1623180e70a03c4a734deb9bac50fc9c82942ae84a3a220779062128e75f3b", size = 14273844, upload-time = "2024-09-29T11:40:04.138Z" },
{ url = "https://files.pythonhosted.org/packages/85/b5/aa23aa2e70bcba42c989c02e7228273c30f3b44b9b264abb93eaeff43ad7/pygame-2.6.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef07c0103d79492c21fced9ad68c11c32efa6801ca1920ebfd0f15fb46c78b1c", size = 13951197, upload-time = "2024-09-29T11:40:06.785Z" },
{ url = "https://files.pythonhosted.org/packages/a6/06/29e939b34d3f1354738c7d201c51c250ad7abefefaf6f8332d962ff67c4b/pygame-2.6.1-cp313-cp313-win32.whl", hash = "sha256:3acd8c009317190c2bfd81db681ecef47d5eb108c2151d09596d9c7ea9df5c0e", size = 10249309, upload-time = "2024-09-29T11:10:23.329Z" },
{ url = "https://files.pythonhosted.org/packages/7e/11/17f7f319ca91824b86557e9303e3b7a71991ef17fd45286bf47d7f0a38e6/pygame-2.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:813af4fba5d0b2cb8e58f5d95f7910295c34067dcc290d34f1be59c48bd1ea6a", size = 10620084, upload-time = "2024-09-29T11:48:51.587Z" },
]
[[package]]
name = "pypvz"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "blue" },
{ name = "pygame" },
{ name = "setuptools" },
{ name = "wheel" },
]
[package.metadata]
requires-dist = [
{ name = "blue", specifier = ">=0.9.1" },
{ name = "pygame", specifier = ">=2.6.1" },
{ name = "setuptools", specifier = ">=80.9.0" },
{ name = "wheel", specifier = ">=0.45.1" },
]
[[package]]
name = "setuptools"
version = "80.9.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" },
]
[[package]]
name = "tomli"
version = "2.2.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" },
{ url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" },
{ url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" },
{ url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" },
{ url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" },
{ url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" },
{ url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" },
{ url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" },
{ url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" },
{ url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" },
{ url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" },
{ url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" },
{ url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" },
{ url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" },
{ url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" },
{ url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" },
{ url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" },
{ url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" },
{ url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" },
{ url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" },
{ url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" },
]
[[package]]
name = "wheel"
version = "0.45.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/8a/98/2d9906746cdc6a6ef809ae6338005b3f21bb568bea3165cfc6a243fdc25c/wheel-0.45.1.tar.gz", hash = "sha256:661e1abd9198507b1409a20c02106d9670b2576e916d58f520316666abca6729", size = 107545, upload-time = "2024-11-23T00:18:23.513Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0b/2c/87f3254fd8ffd29e4c02732eee68a83a1d3c346ae39bc6822dcbcb697f2b/wheel-0.45.1-py3-none-any.whl", hash = "sha256:708e7481cc80179af0e556bbf0cc00b8444c7321e2700b8d8580231d13017248", size = 72494, upload-time = "2024-11-23T00:18:21.207Z" },
]