Compare commits
101 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c88a018209 | ||
|
|
11c3129ea0 | ||
|
|
72915de297 | ||
|
|
268b32bba5 | ||
|
|
47b4ba1783 | ||
|
|
9e63c5bf57 | ||
|
|
78ba0efbcd | ||
|
|
70ffd9489d | ||
|
|
daee48eb6e | ||
|
|
5267003c6f | ||
|
|
431d4ae662 | ||
|
|
7efd1fcadf | ||
|
|
1bcef4d141 | ||
|
|
2d368f8bf5 | ||
|
|
388b3175e0 | ||
|
|
ffb48381a5 | ||
|
|
6ac010321a | ||
|
|
d07983dbbd | ||
|
|
fc5029fad3 | ||
|
|
b18e442f66 | ||
|
|
fd9253d410 | ||
|
|
17de44a773 | ||
|
|
ac392595b0 | ||
|
|
926f40caf9 | ||
|
|
63e36df930 | ||
|
|
8ae6b88d8a | ||
|
|
3ef4570334 | ||
|
|
1ec9c19f41 | ||
|
|
65b7c38078 | ||
|
|
05770cf502 | ||
|
|
6c2595d35c | ||
|
|
3f22446a85 | ||
|
|
5baf11a3f1 | ||
|
|
f496e576fd | ||
|
|
60911bebdd | ||
|
|
37d8b29f60 | ||
|
|
2235f687dc | ||
|
|
533820deda | ||
|
|
8ce2ccf734 | ||
|
|
57501a8f22 | ||
|
|
cf20f9733f | ||
|
|
f46585264d | ||
|
|
f38e415add | ||
|
|
a0c8201d26 | ||
|
|
ebf68c0393 | ||
|
|
23824233df | ||
|
|
5f83d8c805 | ||
|
|
da1eb13946 | ||
|
|
777e937d98 | ||
|
|
08ec7e2d00 | ||
|
|
b7b69cec61 | ||
|
|
9e01c018e4 | ||
|
|
544560c49e | ||
|
|
baeb003f5f | ||
|
|
2376922a77 | ||
|
|
f37f05b726 | ||
|
|
10711a5509 | ||
|
|
1c52ea88f4 | ||
|
|
44ccabd74d | ||
|
|
f3b602a30e | ||
|
|
7e93949f91 | ||
|
|
6edaf34d11 | ||
|
|
1c96110e28 | ||
|
|
98463e524b | ||
|
|
e010251b00 | ||
|
|
506900d21c | ||
|
|
d07810afce | ||
|
|
139b5d3ef8 | ||
|
|
8067508eb3 | ||
|
|
d93acfe9e3 | ||
|
|
0493ec70d5 | ||
|
|
95b4d789e5 | ||
|
|
6ae7007bb9 | ||
|
|
bf3fdc08b9 | ||
|
|
9801133724 | ||
|
|
fde1661ec5 | ||
|
|
7240c61765 | ||
|
|
8cd22a1140 | ||
|
|
ce62e9b35c | ||
|
|
9fa8fbb847 | ||
|
|
72cad4a3fd | ||
|
|
80021191ff | ||
|
|
205ec949e1 | ||
|
|
0f59007e82 | ||
|
|
99deb7bbe8 | ||
|
|
2862aa28b5 | ||
|
|
ac3f7f56d7 | ||
|
|
172b736e2f | ||
|
|
7c2b72152d | ||
|
|
93259a2380 | ||
|
|
177e426ed5 | ||
|
|
a1b7c389a0 | ||
|
|
cb94f49cf9 | ||
|
|
afda2a02c8 | ||
|
|
76533796a3 | ||
|
|
6c65b27df6 | ||
|
|
428b4df8e7 | ||
|
|
a87233c920 | ||
|
|
3117a85c88 | ||
|
|
e00a640d76 | ||
|
|
8aea3e7c36 |
120
.github/workflows/build-pr.yml
vendored
@ -14,7 +14,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python_version:
|
||||
- "3.10"
|
||||
- "3.12"
|
||||
name: Windows Python ${{ matrix.python_version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
@ -46,10 +46,11 @@ jobs:
|
||||
-i ./pypvz.ico
|
||||
|
||||
- name: Release the version built by pyinstaller
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
allowUpdates: true
|
||||
tag: Current.Version.Built.with.Pyinstaller
|
||||
tag: Dev.Version.Built.with.Pyinstaller
|
||||
artifacts: ./out/*pyinstaller*.exe
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@ -67,74 +68,75 @@ jobs:
|
||||
--show-memory `
|
||||
--output-dir=out `
|
||||
--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\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\libvorbisfile-3.dll=libvorbisfile-3.dll `
|
||||
--include-data-file=c:\hostedtoolcache\windows\python\${{ matrix.python_version }}*\x64\lib\site-packages\pygame\libvorbis-0.dll=libvorbis-0.dll `
|
||||
--include-data-file=c:\hostedtoolcache\windows\python\${{ matrix.python_version }}*\x64\lib\site-packages\pygame\libjpeg-9.dll=libjpeg-9.dll `
|
||||
--include-data-dir=resources=resources `
|
||||
--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
|
||||
|
||||
# artifact压缩包处上传包含exe和运行环境的文件夹
|
||||
- name: "Upload binaries"
|
||||
uses: actions/upload-artifact@v2
|
||||
- name: Release the version built by nuitka
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
name: artifact-windows-python-${{ matrix.python_version }}
|
||||
path: ./out/*.dist
|
||||
allowUpdates: true
|
||||
tag: Dev
|
||||
artifacts: ./out/*nuitka*.exe
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
|
||||
#linux:
|
||||
# runs-on: ubuntu-latest
|
||||
# strategy:
|
||||
# fail-fast: false
|
||||
# matrix:
|
||||
# python_version:
|
||||
# - "3.10"
|
||||
# name: Ubuntu Python ${{ matrix.python_version }}
|
||||
# steps:
|
||||
# - name: 🛎️ Checkout
|
||||
# uses: actions/checkout@v2
|
||||
# with:
|
||||
# fetch-depth: 0
|
||||
linux:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python_version:
|
||||
- "3.12"
|
||||
name: Ubuntu Python ${{ matrix.python_version }}
|
||||
steps:
|
||||
- name: 🛎️ Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
# - name: 🐍 Use Python ${{ matrix.python_version }}
|
||||
# uses: actions/setup-python@v2
|
||||
# with:
|
||||
# python-version: ${{ matrix.python_version }}
|
||||
- name: 🐍 Use Python ${{ matrix.python_version }}
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: ${{ matrix.python_version }}
|
||||
|
||||
# - name: 🧳 Install dependencies
|
||||
# run: |
|
||||
# sudo apt-get update
|
||||
# sudo apt-get install patchelf gdb ccache libfuse2
|
||||
# python -m pip install --no-python-version-warning --disable-pip-version-check zstandard appdirs ordered-set tqdm Jinja2
|
||||
# python -m pip install --no-python-version-warning --disable-pip-version-check nuitka
|
||||
# python -m pip install --no-python-version-warning --disable-pip-version-check pygame
|
||||
- name: 🧳 Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install patchelf gdb ccache libfuse2 zstd tar
|
||||
python -m pip install --no-python-version-warning --disable-pip-version-check zstandard appdirs ordered-set tqdm Jinja2
|
||||
python -m pip install --no-python-version-warning --disable-pip-version-check nuitka
|
||||
python -m pip install --no-python-version-warning --disable-pip-version-check pygame
|
||||
|
||||
# # 使用Nuitka构建
|
||||
# - name: Show nuitka version
|
||||
# run: |
|
||||
# env
|
||||
# python -m nuitka --version
|
||||
# 使用Nuitka构建
|
||||
- name: Show nuitka version
|
||||
run: |
|
||||
env
|
||||
python -m nuitka --version
|
||||
|
||||
# - name: Build pypvz with Nuitka
|
||||
# run: |
|
||||
# yes | python -m nuitka \
|
||||
# --show-progress \
|
||||
# --follow-imports \
|
||||
# --show-memory \
|
||||
# --output-dir=out \
|
||||
# --linux-onefile-icon=pypvz.ico \
|
||||
# -o ./out/pypvz \
|
||||
# --remove-output \
|
||||
# main.py
|
||||
# cp -r ./resources ./out/resources
|
||||
# cd out
|
||||
# tar -cvpaf ../pypvz-with-python${{ matrix.python_version }}-nuitka-linux-x64.tar.zst .
|
||||
- name: Build pypvz with Nuitka
|
||||
run: |
|
||||
yes | python -m nuitka \
|
||||
--onefile \
|
||||
--standalone \
|
||||
--include-data-dir=resources=resources \
|
||||
--linux-onefile-icon=pypvz.png \
|
||||
--static-libpython=no \
|
||||
--remove-output \
|
||||
-o pypvz-with-python${{ matrix.python_version }}-linux-x86_64.bin \
|
||||
pypvz.py
|
||||
|
||||
# - name: "Upload binaries"
|
||||
# uses: actions/upload-artifact@v2
|
||||
# with:
|
||||
# name: artifact-windows-python-${{ matrix.python_version }}
|
||||
# path: ./pypvz-with-python${{ matrix.python_version }}-nuitka-linux-x64.tar.zst
|
||||
- name: Release the version built by nuitka
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
allowUpdates: true
|
||||
tag: Dev
|
||||
artifacts: ./pypvz*-x86_64.*
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
112
.github/workflows/build.yml
vendored
@ -16,7 +16,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python_version:
|
||||
- "3.10"
|
||||
- "3.12"
|
||||
name: Windows Python ${{ matrix.python_version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
@ -69,22 +69,15 @@ jobs:
|
||||
--show-memory `
|
||||
--output-dir=out `
|
||||
--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\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\libvorbisfile-3.dll=libvorbisfile-3.dll `
|
||||
--include-data-file=c:\hostedtoolcache\windows\python\${{ matrix.python_version }}*\x64\lib\site-packages\pygame\libvorbis-0.dll=libvorbis-0.dll `
|
||||
--include-data-file=c:\hostedtoolcache\windows\python\${{ matrix.python_version }}*\x64\lib\site-packages\pygame\libjpeg-9.dll=libjpeg-9.dll `
|
||||
--include-data-dir=resources=resources `
|
||||
--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
|
||||
|
||||
# artifact压缩包处上传包含exe和运行环境的文件夹
|
||||
- name: "Upload binaries"
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: artifact-windows-python-${{ matrix.python_version }}
|
||||
path: ./out/*.dist
|
||||
|
||||
- name: Release the version built by nuitka
|
||||
uses: ncipollo/release-action@v1
|
||||
@ -94,58 +87,55 @@ jobs:
|
||||
artifacts: ./out/*nuitka*.exe
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
#linux:
|
||||
# runs-on: ubuntu-latest
|
||||
# strategy:
|
||||
# fail-fast: false
|
||||
# matrix:
|
||||
# python_version:
|
||||
# - "3.10"
|
||||
# name: Ubuntu Python ${{ matrix.python_version }}
|
||||
# steps:
|
||||
# - name: 🛎️ Checkout
|
||||
# uses: actions/checkout@v2
|
||||
# with:
|
||||
# fetch-depth: 0
|
||||
linux:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python_version:
|
||||
- "3.12"
|
||||
name: Ubuntu Python ${{ matrix.python_version }}
|
||||
steps:
|
||||
- name: 🛎️ Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
# - name: 🐍 Use Python ${{ matrix.python_version }}
|
||||
# uses: actions/setup-python@v2
|
||||
# with:
|
||||
# python-version: ${{ matrix.python_version }}
|
||||
- name: 🐍 Use Python ${{ matrix.python_version }}
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: ${{ matrix.python_version }}
|
||||
|
||||
# - name: 🧳 Install dependencies
|
||||
# run: |
|
||||
# sudo apt-get update
|
||||
# sudo apt-get install patchelf gdb ccache libfuse2 zstd tar
|
||||
# python -m pip install --no-python-version-warning --disable-pip-version-check zstandard appdirs ordered-set tqdm Jinja2
|
||||
# python -m pip install --no-python-version-warning --disable-pip-version-check nuitka
|
||||
# python -m pip install --no-python-version-warning --disable-pip-version-check pygame
|
||||
- name: 🧳 Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install patchelf gdb ccache libfuse2 zstd tar
|
||||
python -m pip install --no-python-version-warning --disable-pip-version-check zstandard appdirs ordered-set tqdm Jinja2
|
||||
python -m pip install --no-python-version-warning --disable-pip-version-check nuitka
|
||||
python -m pip install --no-python-version-warning --disable-pip-version-check pygame
|
||||
|
||||
# # 使用Nuitka构建
|
||||
# - name: Show nuitka version
|
||||
# run: |
|
||||
# env
|
||||
# python -m nuitka --version
|
||||
# 使用Nuitka构建
|
||||
- name: Show nuitka version
|
||||
run: |
|
||||
env
|
||||
python -m nuitka --version
|
||||
|
||||
# - name: Build pypvz with Nuitka
|
||||
# run: |
|
||||
# yes | python -m nuitka \
|
||||
# --show-progress \
|
||||
# --follow-imports \
|
||||
# --show-memory \
|
||||
# --output-dir=out \
|
||||
# --linux-onefile-icon=pypvz.ico \
|
||||
# -o ./out/pypvz \
|
||||
# --remove-output \
|
||||
# main.py
|
||||
# cp -r ./resources ./out/resources
|
||||
# cd out
|
||||
# tar -cvpaf ../pypvz-with-python${{ matrix.python_version }}-nuitka-linux-x64.tar.zst .
|
||||
- name: Build pypvz with Nuitka
|
||||
run: |
|
||||
yes | python -m nuitka \
|
||||
--onefile \
|
||||
--standalone \
|
||||
--include-data-dir=resources=resources \
|
||||
--linux-onefile-icon=pypvz.png \
|
||||
--static-libpython=no \
|
||||
--remove-output \
|
||||
-o pypvz-with-python${{ matrix.python_version }}-linux-x86_64.bin \
|
||||
pypvz.py
|
||||
|
||||
# - name: Release the version built by nuitka
|
||||
# uses: ncipollo/release-action@v1
|
||||
# with:
|
||||
# allowUpdates: true
|
||||
# tag: Latest
|
||||
# artifacts: ./*.tar.zst
|
||||
# token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Release the version built by nuitka
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
allowUpdates: true
|
||||
tag: Latest
|
||||
artifacts: ./pypvz*-x86_64.*
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
6
.gitignore
vendored
@ -4,7 +4,11 @@ test-build/
|
||||
release/
|
||||
# 忽略调试内容
|
||||
.vscode/
|
||||
# 忽略 Pycharm 项目文件
|
||||
.idea/
|
||||
__pycache__/
|
||||
*/__pycache__/
|
||||
# 忽略测试文件
|
||||
test.py
|
||||
test*.py
|
||||
# uv 管理的虚拟环境
|
||||
.venv
|
||||
|
||||
1
.python-version
Normal file
@ -0,0 +1 @@
|
||||
3.12
|
||||
87
README.md
@ -22,14 +22,14 @@
|
||||
* 夜晚模式支持墓碑以及从墓碑生成僵尸
|
||||
* 含有泳池的模式支持在最后一波时从泳池中自动冒出僵尸
|
||||
* 支持保存进度
|
||||
* Windows下默认进度文件的保存路径为`~\AppData\Roaming\wszqkzqk.dev\pypvz\userdata.json`
|
||||
* 其他操作系统为`~/.config/wszqkzqk.dev/pypvz/userdata.json`
|
||||
* Windows下默认进度文件的保存路径为`~\AppData\Roaming\pypvz\userdata.json`
|
||||
* 其他操作系统为`~/.config/pypvz/userdata.json`
|
||||
* 存档为JSON文件,如果出现因存档损坏而造成程序无法启动,可以手动编辑修复或者删除该文件重试
|
||||
* 0.8.12.0版本后理论上不可能因为存档损坏而无法启动,如果有,请在[issues](https://github.com/wszqkzqk/pypvz/issues)中报告bug
|
||||
* 仍然有可能因为升级后变量名不同而丢失存档的进度信息,这种情况手动编辑恢复即可
|
||||
* 支持错误日志记录
|
||||
* Windows下默认日志文件的保存路径为`~\AppData\Roaming\wszqkzqk.dev\pypvz\run.log`
|
||||
* 其他操作系统为`~/.config/wszqkzqk.dev/pypvz/run.log`
|
||||
* Windows下默认日志文件的保存路径为`~\AppData\Roaming\pypvz\run.log`
|
||||
* 其他操作系统为`~/.config/pypvz/run.log`
|
||||
* 支持自定义游戏速度倍率
|
||||
* 保存在游戏存档文件中,可以通过修改`game rate`值更改速度倍率
|
||||
* 游戏完成成就显示
|
||||
@ -38,7 +38,17 @@
|
||||
* 光标移动到向日葵奖杯上是显示当前各个模式通关次数
|
||||
* 含有游戏帮助界面 QwQ
|
||||
|
||||
## 环境要求
|
||||
## 环境安装
|
||||
|
||||
建议使用 [uv](https://docs.astral.sh/uv/) 安装依赖:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/wszqkzqk/pypvz.git
|
||||
cd pypvz
|
||||
uv sync
|
||||
```
|
||||
|
||||
或者参考:
|
||||
|
||||
* `Python3` (建议 >= 3.10,最好使用最新版)
|
||||
* `Python-Pygame` (建议 >= 2.0,最好使用最新版)
|
||||
@ -61,24 +71,27 @@ python pypvz.py
|
||||
- 可以在仓库的[`Releases`](https://github.com/wszqkzqk/pypvz/releases)页面中[下载最新版(点击跳转)](https://github.com/wszqkzqk/pypvz/releases/latest)(推荐):
|
||||
- 使用GCC编译
|
||||
- 程序包含名称、版本等信息
|
||||
- 得到的验证最多(相对)
|
||||
- 得到的验证最多
|
||||
- 并非每次提交都会更新,更新可能不及时
|
||||
- 也可以直接下载GitHub Workflow[自动利用Nuitka构建的版本(点击跳转)](https://github.com/wszqkzqk/pypvz/releases/tag/Latest)(推荐):
|
||||
- 使用MSVC编译
|
||||
- 每次提交均会更新,保证更新及时
|
||||
- 未进行任何测试,存在bug的概率高于前者
|
||||
- 可看作本软件的测试版
|
||||
- 每次合并提交到主分支时更新
|
||||
- 得到的验证较多
|
||||
- 服务器构建,编译环境更纯粹,冗余更少,体积更小
|
||||
- 还可以下载GitHub Workflow[自动利用Pyinstaller构建的版本(点击跳转)](https://github.com/wszqkzqk/pypvz/releases/tag/Current.Version.Built.with.Pyinstaller):
|
||||
- 在程序闪退时有报错窗口弹出
|
||||
- 程序性能较差,不推荐
|
||||
- 其他特性同GitHub Workflow自动利用Nuitka构建的版本
|
||||
- 均仅支持64位操作系统
|
||||
- 不依赖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
|
||||
* 白昼模式——三行草皮:2
|
||||
@ -87,10 +100,11 @@ python pypvz.py
|
||||
* 泳池模式:9~11
|
||||
* 浓雾模式(暂时没有雾):12
|
||||
* 小游戏模式:
|
||||
* 坚果保龄球模式:1
|
||||
* 坚果保龄球:1
|
||||
* 传送带模式(白天):2
|
||||
* 传送带模式(黑夜):3
|
||||
* 传送带模式(泳池):4
|
||||
* 坚果保龄球(II):5
|
||||
* 目前暂时按照以上设定,未与原版相符
|
||||
* 可以通过修改存档JSON文件中的`game rate`值来调节游戏速度倍率
|
||||
|
||||
@ -199,7 +213,6 @@ pyinstaller -F pypvz.py `
|
||||
* 魅惑的僵尸未用红色滤镜标识
|
||||
* 这个可能会作为一种“特性”
|
||||
* 南瓜头显示不正常
|
||||
* 对于部分“长得比较长”的植物甚至可以在南瓜头存在的情况下优先被啃食
|
||||
* 墓碑吞噬者吞噬墓碑过程中被吞噬的墓碑顶端不会消失
|
||||
|
||||
**欢迎提供[Pull requests](https://github.com/wszqkzqk/pypvz/pulls)或修复方法建议,也欢迎在这里反馈新的bug()**
|
||||
@ -292,32 +305,34 @@ pyinstaller -F pypvz.py `
|
||||
|
||||
## 截屏
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
## 关于日志与反馈
|
||||
|
||||
对于闪退情况,Linux用户与Windows下的python源代码运行用户可以直接在终端中复制出崩溃日志进行反馈。
|
||||
|
||||
Windows单文件封装版本无法通过终端显示日志,需要在日志文件中寻找崩溃原因
|
||||
* Windows默认日志文件路径为`~\AppData\Roaming\wszqkzqk.dev\pypvz\run.log`
|
||||
* 其他操作系统为`~/.config/wszqkzqk.dev/pypvz/run.log`,但一般可以在终端中显示时用终端中的输出即可
|
||||
* Windows默认日志文件路径为`~\AppData\Roaming\pypvz\run.log`
|
||||
* 其他操作系统为`~/.config/pypvz/run.log`,但一般可以在终端中显示时用终端中的输出即可
|
||||
|
||||
12
pyproject.toml
Normal 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",
|
||||
]
|
||||
44
pypvz.py
@ -1,23 +1,32 @@
|
||||
#!/usr/bin/env python
|
||||
import logging
|
||||
import traceback
|
||||
import os
|
||||
import pygame as pg
|
||||
import traceback
|
||||
from logging.handlers import RotatingFileHandler
|
||||
|
||||
import pygame as pg
|
||||
|
||||
# 由于在后续本地模块中存在对pygame的调用,在此处必须完成pygame的初始化
|
||||
os.environ[
|
||||
'SDL_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR'
|
||||
] = '0' # 设置临时环境变量以避免Linux下禁用x11合成器
|
||||
pg.init()
|
||||
|
||||
from source import tool
|
||||
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)):
|
||||
os.makedirs(os.path.dirname(c.USERLOG_PATH))
|
||||
logger = logging.getLogger("main")
|
||||
formatter = logging.Formatter("%(asctime)s - %(levelname)s: %(message)s")
|
||||
fileHandler = RotatingFileHandler(c.USERLOG_PATH, "a", 1024*1024, 0, "utf-8")
|
||||
logger = logging.getLogger('main')
|
||||
formatter = logging.Formatter('%(asctime)s - %(levelname)s: %(message)s')
|
||||
fileHandler = RotatingFileHandler(
|
||||
c.USERLOG_PATH, 'a', 1_000_000, 0, 'utf-8'
|
||||
)
|
||||
# 设置日志文件权限,Unix为644,Windows为可读写;Python的os.chmod与Unix chmod相同,但要显式说明8进制
|
||||
os.chmod(c.USERLOG_PATH, 0o644)
|
||||
fileHandler.setFormatter(formatter)
|
||||
streamHandler = logging.StreamHandler()
|
||||
streamHandler.setFormatter(formatter)
|
||||
@ -27,15 +36,16 @@ if __name__=="__main__":
|
||||
try:
|
||||
# 控制状态机运行
|
||||
game = tool.Control()
|
||||
state_dict = { c.MAIN_MENU: mainmenu.Menu(),
|
||||
c.GAME_VICTORY: screen.GameVictoryScreen(),
|
||||
c.GAME_LOSE: screen.GameLoseScreen(),
|
||||
c.LEVEL: level.Level(),
|
||||
c.AWARD_SCREEN: screen.AwardScreen(),
|
||||
c.HELP_SCREEN: screen.HelpScreen(),
|
||||
}
|
||||
state_dict = {
|
||||
c.MAIN_MENU: mainmenu.Menu(),
|
||||
c.GAME_VICTORY: screen.GameVictoryScreen(),
|
||||
c.GAME_LOSE: screen.GameLoseScreen(),
|
||||
c.LEVEL: level.Level(),
|
||||
c.AWARD_SCREEN: screen.AwardScreen(),
|
||||
c.HELP_SCREEN: screen.HelpScreen(),
|
||||
}
|
||||
game.setup_states(state_dict, c.MAIN_MENU)
|
||||
game.run()
|
||||
except:
|
||||
print() # 将日志输出与上文内容分隔开,增加可读性
|
||||
logger.error(f"\n{traceback.format_exc()}")
|
||||
print() # 将日志输出与上文内容分隔开,增加可读性
|
||||
logger.error(f'\n{traceback.format_exc()}')
|
||||
|
||||
BIN
resources/graphics/Cards/card_giantwallnut.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 18 KiB |
BIN
resources/sound/helpScreen.ogg
Executable file
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 124 KiB |
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 116 KiB |
|
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 87 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
BIN
screenshots/screenshot-22.webp
Normal file
|
After Width: | Height: | Size: 63 KiB |
BIN
screenshots/screenshot-23.webp
Normal file
|
After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 125 KiB After Width: | Height: | Size: 125 KiB |
|
Before Width: | Height: | Size: 143 KiB After Width: | Height: | Size: 143 KiB |
|
Before Width: | Height: | Size: 164 KiB After Width: | Height: | Size: 164 KiB |
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 112 KiB |
|
Before Width: | Height: | Size: 119 KiB After Width: | Height: | Size: 119 KiB |
@ -1,116 +1,184 @@
|
||||
import random
|
||||
|
||||
from .. import constants as c
|
||||
|
||||
|
||||
# 记录植物种植情况的地图管理工具
|
||||
class Map():
|
||||
def __init__(self, background_type):
|
||||
class Map:
|
||||
def __init__(self, background_type: int):
|
||||
self.background_type = background_type
|
||||
# 注意:从0开始编号
|
||||
if self.background_type in c.POOL_EQUIPPED_BACKGROUNDS:
|
||||
self.width = c.GRID_POOL_X_LEN
|
||||
self.height = c.GRID_POOL_Y_LEN
|
||||
self.gridHeightSize = c.GRID_POOL_Y_SIZE
|
||||
self.map = [[(self.initMapGrid(c.MAP_GRASS), self.initMapGrid(c.MAP_WATER))[y in {2, 3}] for x in range(self.width)] for y in range(self.height)]
|
||||
self.grid_height_size = c.GRID_POOL_Y_SIZE
|
||||
self.map = [
|
||||
[
|
||||
self.initMapGrid(c.MAP_WATER)
|
||||
if 2 <= y <= 3
|
||||
else self.initMapGrid(c.MAP_GRASS)
|
||||
for x in range(self.width)
|
||||
]
|
||||
for y in range(self.height)
|
||||
]
|
||||
elif self.background_type in c.ON_ROOF_BACKGROUNDS:
|
||||
self.width = c.GRID_ROOF_X_LEN
|
||||
self.height = c.GRID_ROOF_Y_LEN
|
||||
self.gridHeightSize = c.GRID_ROOF_Y_SIZE
|
||||
self.map = [[self.initMapGrid(c.MAP_TILE) for x in range(self.width)] for y in range(self.height)]
|
||||
self.grid_height_size = c.GRID_ROOF_Y_SIZE
|
||||
self.map = [
|
||||
[self.initMapGrid(c.MAP_TILE) for x in range(self.width)]
|
||||
for y in range(self.height)
|
||||
]
|
||||
elif self.background_type == c.BACKGROUND_SINGLE:
|
||||
self.width = c.GRID_X_LEN
|
||||
self.height = c.GRID_Y_LEN
|
||||
self.gridHeightSize = c.GRID_Y_SIZE
|
||||
self.map = [[(self.initMapGrid(c.MAP_UNAVAILABLE), self.initMapGrid(c.MAP_GRASS))[y == 2] for x in range(self.width)] for y in range(self.height)]
|
||||
self.grid_height_size = c.GRID_Y_SIZE
|
||||
self.map = [
|
||||
[
|
||||
self.initMapGrid(c.MAP_GRASS)
|
||||
if y == 2
|
||||
else self.initMapGrid(c.MAP_UNAVAILABLE)
|
||||
for x in range(self.width)
|
||||
]
|
||||
for y in range(self.height)
|
||||
]
|
||||
elif self.background_type == c.BACKGROUND_TRIPLE:
|
||||
self.width = c.GRID_X_LEN
|
||||
self.height = c.GRID_Y_LEN
|
||||
self.gridHeightSize = c.GRID_Y_SIZE
|
||||
self.map = [[(self.initMapGrid(c.MAP_UNAVAILABLE), self.initMapGrid(c.MAP_GRASS))[y in {1, 2, 3}] for x in range(self.width)] for y in range(self.height)]
|
||||
self.grid_height_size = c.GRID_Y_SIZE
|
||||
self.map = [
|
||||
[
|
||||
self.initMapGrid(c.MAP_GRASS)
|
||||
if 1 <= y <= 3
|
||||
else self.initMapGrid(c.MAP_UNAVAILABLE)
|
||||
for x in range(self.width)
|
||||
]
|
||||
for y in range(self.height)
|
||||
]
|
||||
else:
|
||||
self.width = c.GRID_X_LEN
|
||||
self.height = c.GRID_Y_LEN
|
||||
self.gridHeightSize = c.GRID_Y_SIZE
|
||||
self.map = [[self.initMapGrid(c.MAP_GRASS) for x in range(self.width)] for y in range(self.height)]
|
||||
self.grid_height_size = c.GRID_Y_SIZE
|
||||
self.map = [
|
||||
[self.initMapGrid(c.MAP_GRASS) for x in range(self.width)]
|
||||
for y in range(self.height)
|
||||
]
|
||||
|
||||
def isValid(self, map_x, map_y):
|
||||
if (map_x < 0 or map_x >= self.width or
|
||||
map_y < 0 or map_y >= self.height):
|
||||
return False
|
||||
return True
|
||||
def isValid(self, map_x: int, map_y: int) -> bool:
|
||||
if (0 <= map_x < self.width) and (0 <= map_y < self.height):
|
||||
return True
|
||||
return False
|
||||
|
||||
# 地图单元格状态
|
||||
# 注意是可变对象,不能直接引用
|
||||
# 由于同一格显然不可能种两个相同的植物,所以用集合
|
||||
def initMapGrid(self, plot_type):
|
||||
return {c.MAP_PLANT:set(), c.MAP_SLEEP:False, c.MAP_PLOT_TYPE:plot_type}
|
||||
def initMapGrid(self, plot_type: str) -> set:
|
||||
return {
|
||||
c.MAP_PLANT: set(),
|
||||
c.MAP_SLEEP: False,
|
||||
c.MAP_PLOT_TYPE: plot_type,
|
||||
}
|
||||
|
||||
# 判断位置是否可用
|
||||
# 暂时没有写紫卡植物的判断方法
|
||||
# 由于紫卡植物需要移除以前的植物,所以可用另外定义一个函数
|
||||
def isAvailable(self, map_x, map_y, plantName):
|
||||
def isAvailable(self, map_x: int, map_y: int, plant_name: str) -> bool:
|
||||
# 咖啡豆和墓碑吞噬者的判别最为特殊
|
||||
if plantName == c.COFFEEBEAN:
|
||||
if self.map[map_y][map_x][c.MAP_SLEEP] and (plantName not in self.map[map_y][map_x][c.MAP_PLANT]):
|
||||
if plant_name == c.COFFEEBEAN:
|
||||
if self.map[map_y][map_x][c.MAP_SLEEP] and (
|
||||
plant_name not in self.map[map_y][map_x][c.MAP_PLANT]
|
||||
):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
if plantName == c.GRAVEBUSTER:
|
||||
if (c.GRAVE in self.map[map_y][map_x][c.MAP_PLANT]):
|
||||
if plant_name == c.GRAVEBUSTER:
|
||||
if c.GRAVE 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
|
||||
else:
|
||||
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
|
||||
if self.map[map_y][map_x][c.MAP_PLOT_TYPE] == c.MAP_GRASS: # 草地
|
||||
# 首先需要判断植物是否是水生植物,水生植物不能种植在陆地上
|
||||
if plantName not in c.WATER_PLANTS:
|
||||
if not self.map[map_y][map_x][c.MAP_PLANT]: # 没有植物肯定可以种植
|
||||
if plant_name not in c.WATER_PLANTS:
|
||||
if not self.map[map_y][map_x][c.MAP_PLANT]: # 没有植物肯定可以种植
|
||||
return True
|
||||
elif (all((i in {"花盆(未实现)", c.PUMPKINHEAD}) for i in self.map[map_y][map_x][c.MAP_PLANT])
|
||||
and (plantName not in self.map[map_y][map_x][c.MAP_PLANT])): # 例外植物:集合中填花盆和南瓜头,只要这里没有这种植物就能种植
|
||||
elif all(
|
||||
(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
|
||||
elif (plantName == c.PUMPKINHEAD) and (c.PUMPKINHEAD not in self.map[map_y][map_x][c.MAP_PLANT]): # 没有南瓜头就能种南瓜头
|
||||
elif (plant_name == c.PUMPKINHEAD) and (
|
||||
c.PUMPKINHEAD not in self.map[map_y][map_x][c.MAP_PLANT]
|
||||
): # 没有南瓜头就能种南瓜头
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
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 plantName not in c.WATER_PLANTS:
|
||||
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])
|
||||
and (plantName not in self.map[map_y][map_x][c.MAP_PLANT])): # 例外植物:集合中填花盆和南瓜头,只要这里没有这种植物就能种植
|
||||
if plantName in {c.SPIKEWEED}: # 不能在花盆上种植的植物
|
||||
if plant_name not in c.WATER_PLANTS:
|
||||
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]
|
||||
) and (
|
||||
plant_name not in self.map[map_y][map_x][c.MAP_PLANT]
|
||||
): # 例外植物:集合中填花盆和南瓜头,只要这里没有这种植物就能种植
|
||||
if plant_name in {c.SPIKEWEED}: # 不能在花盆上种植的植物
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
elif (plantName == c.PUMPKINHEAD) and (c.PUMPKINHEAD not in self.map[map_y][map_x][c.MAP_PLANT]): # 有花盆且没有南瓜头就能种南瓜头
|
||||
elif (plant_name == c.PUMPKINHEAD) and (
|
||||
c.PUMPKINHEAD
|
||||
not in self.map[map_y][map_x][c.MAP_PLANT]
|
||||
): # 有花盆且没有南瓜头就能种南瓜头
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
elif plantName == "花盆(未实现)": # 这一格本来没有花盆而且新来的植物是花盆,可以种
|
||||
elif plant_name == '花盆(未实现)': # 这一格本来没有花盆而且新来的植物是花盆,可以种
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
elif self.map[map_y][map_x][c.MAP_PLOT_TYPE] == c.MAP_WATER: # 水里
|
||||
if plantName in c.WATER_PLANTS: # 是水生植物
|
||||
if not self.map[map_y][map_x][c.MAP_PLANT]: # 只有无植物时才能在水里种植水生植物
|
||||
if plant_name in c.WATER_PLANTS: # 是水生植物
|
||||
if not self.map[map_y][map_x][
|
||||
c.MAP_PLANT
|
||||
]: # 只有无植物时才能在水里种植水生植物
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
else: # 非水生植物,依赖睡莲
|
||||
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])
|
||||
and (plantName not in self.map[map_y][map_x][c.MAP_PLANT])):
|
||||
if plantName in {c.SPIKEWEED, c.POTATOMINE, "花盆(未实现)"}: # 不能在睡莲上种植的植物
|
||||
if all(
|
||||
(i in {c.LILYPAD, 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,
|
||||
c.POTATOMINE,
|
||||
'花盆(未实现)',
|
||||
}: # 不能在睡莲上种植的植物
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
elif (plantName == c.PUMPKINHEAD) and (c.PUMPKINHEAD not in self.map[map_y][map_x][c.MAP_PLANT]): # 在睡莲上且没有南瓜头就能种南瓜头
|
||||
elif (plant_name == c.PUMPKINHEAD) and (
|
||||
c.PUMPKINHEAD
|
||||
not in self.map[map_y][map_x][c.MAP_PLANT]
|
||||
): # 在睡莲上且没有南瓜头就能种南瓜头
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
@ -118,8 +186,8 @@ class Map():
|
||||
return False
|
||||
else: # 不可种植区域
|
||||
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:
|
||||
x -= c.MAP_POOL_OFFSET_X
|
||||
y -= c.MAP_POOL_OFFSET_Y
|
||||
@ -127,293 +195,431 @@ class Map():
|
||||
elif self.background_type in c.ON_ROOF_BACKGROUNDS:
|
||||
x -= c.MAP_ROOF_OFFSET_X
|
||||
y -= c.MAP_ROOF_OFFSET_X
|
||||
gridX = x // c.GRID_ROOF_X_SIZE
|
||||
if gridX >= 5:
|
||||
gridY = y // c.GRID_ROOF_Y_SIZE
|
||||
grid_x = x // c.GRID_ROOF_X_SIZE
|
||||
if grid_x >= 5:
|
||||
grid_y = y // c.GRID_ROOF_Y_SIZE
|
||||
else:
|
||||
gridY = (y - 20*(6 - gridX)) // 85
|
||||
return (gridX, gridY)
|
||||
grid_y = (y - 20 * (6 - grid_x)) // 85
|
||||
return (grid_x, grid_y)
|
||||
else:
|
||||
x -= c.MAP_OFFSET_X
|
||||
y -= c.MAP_OFFSET_Y
|
||||
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:
|
||||
return (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)
|
||||
return (
|
||||
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:
|
||||
return (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)
|
||||
return (
|
||||
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:
|
||||
return (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):
|
||||
return (
|
||||
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: int, map_y: int, plot_type: str):
|
||||
self.map[map_y][map_x][c.MAP_PLOT_TYPE] = plot_type
|
||||
|
||||
def addMapPlant(self, map_x, map_y, plantName, sleep=False):
|
||||
self.map[map_y][map_x][c.MAP_PLANT].add(plantName)
|
||||
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_SLEEP] = sleep
|
||||
|
||||
def removeMapPlant(self, map_x, map_y, plantName):
|
||||
self.map[map_y][map_x][c.MAP_PLANT].discard(plantName)
|
||||
|
||||
def getRandomMapIndex(self):
|
||||
map_x = random.randint(0, self.width-1)
|
||||
map_y = random.randint(0, self.height-1)
|
||||
def removeMapPlant(self, map_x: int, map_y: int, plant_name: str):
|
||||
self.map[map_y][map_x][c.MAP_PLANT].discard(plant_name)
|
||||
|
||||
def getRandomMapIndex(self) -> tuple[int, int]:
|
||||
map_x = random.randint(0, self.width - 1)
|
||||
map_y = random.randint(0, self.height - 1)
|
||||
return (map_x, map_y)
|
||||
|
||||
def checkPlantToSeed(self, x, y, plantName):
|
||||
def checkPlantToSeed(
|
||||
self, x: int, y: int, plant_name: str
|
||||
) -> tuple[int, int]:
|
||||
pos = None
|
||||
map_x, map_y = self.getMapIndex(x, y)
|
||||
if self.isValid(map_x, map_y) and self.isAvailable(map_x, map_y, plantName):
|
||||
if self.isValid(map_x, map_y) and self.isAvailable(
|
||||
map_x, map_y, plant_name
|
||||
):
|
||||
pos = self.getMapGridPos(map_x, map_y)
|
||||
return pos
|
||||
|
||||
|
||||
|
||||
# 保存具体关卡地图信息常数
|
||||
# 冒险模式地图
|
||||
LEVEL_MAP_DATA = (
|
||||
# 第0关:测试模式地图
|
||||
{
|
||||
c.BACKGROUND_TYPE: 2,
|
||||
c.INIT_SUN_NAME: 5000,
|
||||
c.SHOVEL: 1,
|
||||
c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_LIST,
|
||||
c.ZOMBIE_LIST:[
|
||||
{"time":0, "map_y":5, "name":"Zomboni"},
|
||||
{"time":1000, "map_y":4, "name":"ScreenDoorZombie"},
|
||||
{"time":2000, "map_y":4, "name":"ScreenDoorZombie"},
|
||||
{"time":3100, "map_y":4, "name":"ScreenDoorZombie"},
|
||||
{"time":4500, "map_y":4, "name":"ScreenDoorZombie"},
|
||||
{"time":5000, "map_y":4, "name":"ScreenDoorZombie"},
|
||||
{"time":6000, "map_y":4, "name":"ScreenDoorZombie"},
|
||||
{"time":7000, "map_y":4, "name":"ScreenDoorZombie"},
|
||||
{"time":8000, "map_y":4, "name":"ScreenDoorZombie"},
|
||||
{"time":0, "map_y":1, "name":"NewspaperZombie"},
|
||||
{"time":0, "map_y":0, "name":"PoleVaultingZombie"},
|
||||
{"time":6000, "map_y":0, "name":"FootballZombie"},
|
||||
{"time":0, "map_y":3, "name":"ConeheadDuckyTubeZombie"},
|
||||
{"time":0, "map_y":2, "name":"SnorkelZombie"},
|
||||
{"time":90000, "map_y":2, "name":"ConeheadDuckyTubeZombie"}
|
||||
]
|
||||
},
|
||||
# 第1关:单行草皮
|
||||
{
|
||||
c.BACKGROUND_TYPE: 7,
|
||||
c.INIT_SUN_NAME: 150,
|
||||
c.SHOVEL: 1,
|
||||
c.SPAWN_ZOMBIES:c.SPAWN_ZOMBIES_AUTO,
|
||||
c.INCLUDED_ZOMBIES:(c.NORMAL_ZOMBIE,),
|
||||
c.NUM_FLAGS:1
|
||||
},
|
||||
# 第2关:三行草皮
|
||||
{
|
||||
c.BACKGROUND_TYPE: 8,
|
||||
c.INIT_SUN_NAME: 150,
|
||||
c.SHOVEL: 1,
|
||||
c.SPAWN_ZOMBIES:c.SPAWN_ZOMBIES_AUTO,
|
||||
c.INCLUDED_ZOMBIES:(c.NORMAL_ZOMBIE,),
|
||||
c.NUM_FLAGS:1
|
||||
},
|
||||
# 第3关
|
||||
{
|
||||
c.BACKGROUND_TYPE: 0,
|
||||
c.INIT_SUN_NAME: 50,
|
||||
c.SHOVEL: 1,
|
||||
c.SPAWN_ZOMBIES:c.SPAWN_ZOMBIES_AUTO,
|
||||
c.INCLUDED_ZOMBIES:(c.NORMAL_ZOMBIE,),
|
||||
c.NUM_FLAGS:2
|
||||
},
|
||||
# 第4关
|
||||
{
|
||||
c.BACKGROUND_TYPE: 0,
|
||||
c.INIT_SUN_NAME: 50,
|
||||
c.SHOVEL: 1,
|
||||
c.SPAWN_ZOMBIES:c.SPAWN_ZOMBIES_AUTO,
|
||||
c.INCLUDED_ZOMBIES: (c.NORMAL_ZOMBIE, c.CONEHEAD_ZOMBIE, c.POLE_VAULTING_ZOMBIE),
|
||||
c.NUM_FLAGS:2
|
||||
},
|
||||
# 第5关 目前白天最后一关
|
||||
{
|
||||
c.BACKGROUND_TYPE: 0,
|
||||
c.INIT_SUN_NAME: 50,
|
||||
c.SHOVEL: 1,
|
||||
c.SPAWN_ZOMBIES:c.SPAWN_ZOMBIES_AUTO,
|
||||
c.INCLUDED_ZOMBIES: (c.NORMAL_ZOMBIE, c.CONEHEAD_ZOMBIE, c.POLE_VAULTING_ZOMBIE, c.BUCKETHEAD_ZOMBIE),
|
||||
c.NUM_FLAGS:3
|
||||
},
|
||||
# 第6关 目前夜晚第一关
|
||||
{
|
||||
c.BACKGROUND_TYPE: 1,
|
||||
c.INIT_SUN_NAME: 50,
|
||||
c.SHOVEL: 1,
|
||||
c.SPAWN_ZOMBIES:c.SPAWN_ZOMBIES_AUTO,
|
||||
c.INCLUDED_ZOMBIES: ( c.NORMAL_ZOMBIE,
|
||||
c.NEWSPAPER_ZOMBIE),
|
||||
c.NUM_FLAGS:2
|
||||
},
|
||||
# 第7关
|
||||
{
|
||||
c.BACKGROUND_TYPE: 1,
|
||||
c.INIT_SUN_NAME: 50,
|
||||
c.SHOVEL: 1,
|
||||
c.SPAWN_ZOMBIES:c.SPAWN_ZOMBIES_AUTO,
|
||||
c.INCLUDED_ZOMBIES: ( c.NORMAL_ZOMBIE,
|
||||
c.SCREEN_DOOR_ZOMBIE,),
|
||||
c.NUM_FLAGS: 2,
|
||||
c.GRADE_GRAVES: 2,
|
||||
},
|
||||
# 第8关 目前为夜晚最后一关
|
||||
{
|
||||
c.BACKGROUND_TYPE: 1,
|
||||
c.INIT_SUN_NAME: 50,
|
||||
c.SHOVEL: 1,
|
||||
c.SPAWN_ZOMBIES:c.SPAWN_ZOMBIES_AUTO,
|
||||
c.INCLUDED_ZOMBIES: ( c.NORMAL_ZOMBIE, c.NEWSPAPER_ZOMBIE,
|
||||
c.CONEHEAD_ZOMBIE, c.BUCKETHEAD_ZOMBIE,
|
||||
c.SCREEN_DOOR_ZOMBIE, c.FOOTBALL_ZOMBIE),
|
||||
c.INEVITABLE_ZOMBIE_DICT: { # 这里改用python实现了以后,键不再用字符串,改用数字
|
||||
# 仍然要注意字典值是元组
|
||||
10: (c.NEWSPAPER_ZOMBIE,),
|
||||
20: (c.SCREEN_DOOR_ZOMBIE,),
|
||||
30: (c.FOOTBALL_ZOMBIE,),
|
||||
},
|
||||
c.NUM_FLAGS: 3,
|
||||
c.GRADE_GRAVES: 3,
|
||||
},
|
||||
# 第9关 目前为泳池模式第一关
|
||||
{
|
||||
c.BACKGROUND_TYPE: 2,
|
||||
c.INIT_SUN_NAME: 50,
|
||||
c.SHOVEL: 1,
|
||||
c.SPAWN_ZOMBIES:c.SPAWN_ZOMBIES_AUTO,
|
||||
c.INCLUDED_ZOMBIES: ( c.NORMAL_ZOMBIE, c.BUCKETHEAD_ZOMBIE,
|
||||
c.CONEHEAD_ZOMBIE,),
|
||||
c.NUM_FLAGS:2
|
||||
},
|
||||
# 第10关
|
||||
{
|
||||
c.BACKGROUND_TYPE: 2,
|
||||
c.INIT_SUN_NAME: 50,
|
||||
c.SHOVEL: 1,
|
||||
c.SPAWN_ZOMBIES:c.SPAWN_ZOMBIES_AUTO,
|
||||
c.INCLUDED_ZOMBIES: ( c.NORMAL_ZOMBIE, c.BUCKETHEAD_ZOMBIE,
|
||||
c.CONEHEAD_ZOMBIE, c.SNORKELZOMBIE),
|
||||
c.INEVITABLE_ZOMBIE_DICT: {30: (c.SNORKELZOMBIE,)},
|
||||
c.NUM_FLAGS:3
|
||||
},
|
||||
# 第11关
|
||||
{
|
||||
c.BACKGROUND_TYPE: 2,
|
||||
c.INIT_SUN_NAME: 50,
|
||||
c.SHOVEL: 1,
|
||||
c.SPAWN_ZOMBIES:c.SPAWN_ZOMBIES_AUTO,
|
||||
c.INCLUDED_ZOMBIES: (c.NORMAL_ZOMBIE, c.ZOMBONI),
|
||||
c.INEVITABLE_ZOMBIE_DICT: {30: (c.ZOMBONI,)},
|
||||
c.NUM_FLAGS:3
|
||||
},
|
||||
# 第12关 目前为泳池最后一关
|
||||
{
|
||||
c.BACKGROUND_TYPE: 2,
|
||||
c.INIT_SUN_NAME: 50,
|
||||
c.SHOVEL: 1,
|
||||
c.SPAWN_ZOMBIES:c.SPAWN_ZOMBIES_AUTO,
|
||||
c.INCLUDED_ZOMBIES: ( c.NORMAL_ZOMBIE, c.ZOMBONI,
|
||||
c.BUCKETHEAD_ZOMBIE,
|
||||
c.CONEHEAD_ZOMBIE, c.SNORKELZOMBIE),
|
||||
c.INEVITABLE_ZOMBIE_DICT: {40: (c.ZOMBONI,)},
|
||||
c.NUM_FLAGS:4
|
||||
},
|
||||
# 第13关 目前为浓雾第一关 尚未完善
|
||||
{
|
||||
c.BACKGROUND_TYPE: 2,
|
||||
c.INIT_SUN_NAME: 50,
|
||||
c.SHOVEL: 1,
|
||||
c.SPAWN_ZOMBIES:c.SPAWN_ZOMBIES_AUTO,
|
||||
c.INCLUDED_ZOMBIES: ( c.NORMAL_ZOMBIE, c.NEWSPAPER_ZOMBIE,
|
||||
c.ZOMBONI, c.FOOTBALL_ZOMBIE,
|
||||
c.CONEHEAD_ZOMBIE, c.BUCKETHEAD_HEALTH),
|
||||
c.NUM_FLAGS:4
|
||||
},
|
||||
# 第0关:测试模式地图
|
||||
{
|
||||
c.BACKGROUND_TYPE: 2,
|
||||
c.GAME_TITLE: '隐藏测试关卡',
|
||||
c.INIT_SUN_NAME: 5000,
|
||||
c.SHOVEL: 1,
|
||||
c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_LIST,
|
||||
c.ZOMBIE_LIST: (
|
||||
{'time': 0, 'map_y': 5, 'name': 'Zomboni'},
|
||||
{'time': 1000, 'map_y': 4, 'name': 'ScreenDoorZombie'},
|
||||
{'time': 2000, 'map_y': 4, 'name': 'ScreenDoorZombie'},
|
||||
{'time': 3100, 'map_y': 4, 'name': 'ScreenDoorZombie'},
|
||||
{'time': 4500, 'map_y': 4, 'name': 'ScreenDoorZombie'},
|
||||
{'time': 5000, 'map_y': 4, 'name': 'ScreenDoorZombie'},
|
||||
{'time': 6000, 'map_y': 4, 'name': 'ScreenDoorZombie'},
|
||||
{'time': 7000, 'map_y': 4, 'name': 'ScreenDoorZombie'},
|
||||
{'time': 8000, 'map_y': 4, 'name': 'ScreenDoorZombie'},
|
||||
{'time': 0, 'map_y': 1, 'name': 'NewspaperZombie'},
|
||||
{'time': 0, 'map_y': 0, 'name': 'PoleVaultingZombie'},
|
||||
{'time': 6000, 'map_y': 0, 'name': 'FootballZombie'},
|
||||
{'time': 0, 'map_y': 3, 'name': 'ConeheadDuckyTubeZombie'},
|
||||
{'time': 0, 'map_y': 2, 'name': 'SnorkelZombie'},
|
||||
{'time': 90000, 'map_y': 2, 'name': 'ConeheadDuckyTubeZombie'},
|
||||
),
|
||||
},
|
||||
# 第1关:单行草皮
|
||||
{
|
||||
c.BACKGROUND_TYPE: 7,
|
||||
c.GAME_TITLE: '白天 1-1',
|
||||
c.INIT_SUN_NAME: 150,
|
||||
c.SHOVEL: 1,
|
||||
c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO,
|
||||
c.INCLUDED_ZOMBIES: (c.NORMAL_ZOMBIE,),
|
||||
c.NUM_FLAGS: 1,
|
||||
},
|
||||
# 第2关:三行草皮
|
||||
{
|
||||
c.BACKGROUND_TYPE: 8,
|
||||
c.GAME_TITLE: '白天 1-2',
|
||||
c.INIT_SUN_NAME: 50,
|
||||
c.SHOVEL: 1,
|
||||
c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO,
|
||||
c.INCLUDED_ZOMBIES: (c.NORMAL_ZOMBIE,),
|
||||
c.NUM_FLAGS: 1,
|
||||
},
|
||||
# 第3关
|
||||
{
|
||||
c.BACKGROUND_TYPE: 0,
|
||||
c.GAME_TITLE: '白天 1-3',
|
||||
c.INIT_SUN_NAME: 50,
|
||||
c.SHOVEL: 1,
|
||||
c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO,
|
||||
c.INCLUDED_ZOMBIES: (c.NORMAL_ZOMBIE,),
|
||||
c.NUM_FLAGS: 2,
|
||||
},
|
||||
# 第4关
|
||||
{
|
||||
c.BACKGROUND_TYPE: 0,
|
||||
c.GAME_TITLE: '白天 1-4',
|
||||
c.INIT_SUN_NAME: 50,
|
||||
c.SHOVEL: 1,
|
||||
c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO,
|
||||
c.INCLUDED_ZOMBIES: (
|
||||
c.NORMAL_ZOMBIE,
|
||||
c.CONEHEAD_ZOMBIE,
|
||||
c.POLE_VAULTING_ZOMBIE,
|
||||
),
|
||||
c.NUM_FLAGS: 2,
|
||||
},
|
||||
# 第5关 目前白天最后一关
|
||||
{
|
||||
c.BACKGROUND_TYPE: 0,
|
||||
c.GAME_TITLE: '白天 1-5',
|
||||
c.INIT_SUN_NAME: 50,
|
||||
c.SHOVEL: 1,
|
||||
c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO,
|
||||
c.INCLUDED_ZOMBIES: (
|
||||
c.NORMAL_ZOMBIE,
|
||||
c.CONEHEAD_ZOMBIE,
|
||||
c.POLE_VAULTING_ZOMBIE,
|
||||
c.BUCKETHEAD_ZOMBIE,
|
||||
),
|
||||
c.NUM_FLAGS: 3,
|
||||
},
|
||||
# 第6关 目前夜晚第一关
|
||||
{
|
||||
c.BACKGROUND_TYPE: 1,
|
||||
c.GAME_TITLE: '黑夜 2-1',
|
||||
c.INIT_SUN_NAME: 50,
|
||||
c.SHOVEL: 1,
|
||||
c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO,
|
||||
c.INCLUDED_ZOMBIES: (c.NORMAL_ZOMBIE, c.NEWSPAPER_ZOMBIE),
|
||||
c.NUM_FLAGS: 2,
|
||||
},
|
||||
# 第7关
|
||||
{
|
||||
c.BACKGROUND_TYPE: 1,
|
||||
c.GAME_TITLE: '黑夜 2-2',
|
||||
c.INIT_SUN_NAME: 50,
|
||||
c.SHOVEL: 1,
|
||||
c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO,
|
||||
c.INCLUDED_ZOMBIES: (
|
||||
c.NORMAL_ZOMBIE,
|
||||
c.SCREEN_DOOR_ZOMBIE,
|
||||
),
|
||||
c.NUM_FLAGS: 2,
|
||||
c.GRADE_GRAVES: 2,
|
||||
},
|
||||
# 第8关 目前为夜晚最后一关
|
||||
{
|
||||
c.BACKGROUND_TYPE: 1,
|
||||
c.GAME_TITLE: '黑夜 2-3',
|
||||
c.INIT_SUN_NAME: 50,
|
||||
c.SHOVEL: 1,
|
||||
c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO,
|
||||
c.INCLUDED_ZOMBIES: (
|
||||
c.NORMAL_ZOMBIE,
|
||||
c.NEWSPAPER_ZOMBIE,
|
||||
c.CONEHEAD_ZOMBIE,
|
||||
c.BUCKETHEAD_ZOMBIE,
|
||||
c.SCREEN_DOOR_ZOMBIE,
|
||||
c.FOOTBALL_ZOMBIE,
|
||||
),
|
||||
c.INEVITABLE_ZOMBIE_DICT: { # 这里改用python实现了以后,键不再用字符串,改用数字
|
||||
# 仍然要注意字典值是元组
|
||||
10: (c.NEWSPAPER_ZOMBIE,),
|
||||
20: (c.SCREEN_DOOR_ZOMBIE,),
|
||||
30: (c.FOOTBALL_ZOMBIE,),
|
||||
},
|
||||
c.NUM_FLAGS: 3,
|
||||
c.GRADE_GRAVES: 3,
|
||||
},
|
||||
# 第9关 目前为泳池模式第一关
|
||||
{
|
||||
c.BACKGROUND_TYPE: 2,
|
||||
c.GAME_TITLE: '泳池 3-1',
|
||||
c.INIT_SUN_NAME: 50,
|
||||
c.SHOVEL: 1,
|
||||
c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO,
|
||||
c.INCLUDED_ZOMBIES: (
|
||||
c.NORMAL_ZOMBIE,
|
||||
c.BUCKETHEAD_ZOMBIE,
|
||||
c.CONEHEAD_ZOMBIE,
|
||||
),
|
||||
c.NUM_FLAGS: 2,
|
||||
},
|
||||
# 第10关
|
||||
{
|
||||
c.BACKGROUND_TYPE: 2,
|
||||
c.GAME_TITLE: '泳池 3-2',
|
||||
c.INIT_SUN_NAME: 50,
|
||||
c.SHOVEL: 1,
|
||||
c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO,
|
||||
c.INCLUDED_ZOMBIES: (
|
||||
c.NORMAL_ZOMBIE,
|
||||
c.BUCKETHEAD_ZOMBIE,
|
||||
c.CONEHEAD_ZOMBIE,
|
||||
c.SNORKELZOMBIE,
|
||||
),
|
||||
c.INEVITABLE_ZOMBIE_DICT: {30: (c.SNORKELZOMBIE,)},
|
||||
c.NUM_FLAGS: 3,
|
||||
},
|
||||
# 第11关
|
||||
{
|
||||
c.BACKGROUND_TYPE: 2,
|
||||
c.GAME_TITLE: '泳池 3-3',
|
||||
c.INIT_SUN_NAME: 50,
|
||||
c.SHOVEL: 1,
|
||||
c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO,
|
||||
c.INCLUDED_ZOMBIES: (c.NORMAL_ZOMBIE, c.ZOMBONI),
|
||||
c.INEVITABLE_ZOMBIE_DICT: {30: (c.ZOMBONI,)},
|
||||
c.NUM_FLAGS: 3,
|
||||
},
|
||||
# 第12关 目前为泳池最后一关
|
||||
{
|
||||
c.BACKGROUND_TYPE: 2,
|
||||
c.GAME_TITLE: '泳池 3-4',
|
||||
c.INIT_SUN_NAME: 50,
|
||||
c.SHOVEL: 1,
|
||||
c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO,
|
||||
c.INCLUDED_ZOMBIES: (
|
||||
c.NORMAL_ZOMBIE,
|
||||
c.ZOMBONI,
|
||||
c.BUCKETHEAD_ZOMBIE,
|
||||
c.CONEHEAD_ZOMBIE,
|
||||
c.SNORKELZOMBIE,
|
||||
),
|
||||
c.INEVITABLE_ZOMBIE_DICT: {40: (c.ZOMBONI,)},
|
||||
c.NUM_FLAGS: 4,
|
||||
},
|
||||
# 第13关 目前为浓雾第一关 尚未完善
|
||||
{
|
||||
c.BACKGROUND_TYPE: 3,
|
||||
c.GAME_TITLE: '浓雾 4-1',
|
||||
c.INIT_SUN_NAME: 50,
|
||||
c.SHOVEL: 1,
|
||||
c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO,
|
||||
c.INCLUDED_ZOMBIES: (
|
||||
c.NORMAL_ZOMBIE,
|
||||
c.NEWSPAPER_ZOMBIE,
|
||||
c.ZOMBONI,
|
||||
c.FOOTBALL_ZOMBIE,
|
||||
c.CONEHEAD_ZOMBIE,
|
||||
c.BUCKETHEAD_ZOMBIE,
|
||||
),
|
||||
c.NUM_FLAGS: 4,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
|
||||
# 玩玩小游戏地图
|
||||
LITTLE_GAME_MAP_DATA = (
|
||||
# 第0关 测试 目前空缺
|
||||
{},
|
||||
# 第1关 坚果保龄球
|
||||
{
|
||||
c.BACKGROUND_TYPE: 6,
|
||||
c.CHOOSEBAR_TYPE: c.CHOOSEBAR_BOWLING,
|
||||
c.SHOVEL: 0,
|
||||
c.SPAWN_ZOMBIES:c.SPAWN_ZOMBIES_AUTO,
|
||||
c.INCLUDED_ZOMBIES: ( c.NORMAL_ZOMBIE, c.CONEHEAD_ZOMBIE,
|
||||
c.POLE_VAULTING_ZOMBIE, c.BUCKETHEAD_ZOMBIE,
|
||||
c.NEWSPAPER_ZOMBIE),
|
||||
c.NUM_FLAGS:3,
|
||||
c.CARD_POOL: { c.WALLNUTBOWLING: 300,
|
||||
c.REDWALLNUTBOWLING: 100,}
|
||||
},
|
||||
# 第2关 白天 大决战
|
||||
{
|
||||
c.BACKGROUND_TYPE: 0,
|
||||
c.CHOOSEBAR_TYPE: c.CHOOSEBAR_MOVE,
|
||||
c.SHOVEL: 1,
|
||||
c.SPAWN_ZOMBIES:c.SPAWN_ZOMBIES_AUTO,
|
||||
c.INCLUDED_ZOMBIES: ( c.NORMAL_ZOMBIE, c.CONEHEAD_ZOMBIE,
|
||||
c.POLE_VAULTING_ZOMBIE, c.BUCKETHEAD_ZOMBIE,),
|
||||
c.NUM_FLAGS:3,
|
||||
c.CARD_POOL: { c.PEASHOOTER: 200,
|
||||
c.SNOWPEASHOOTER: 100,
|
||||
c.WALLNUT: 100,
|
||||
c.CHERRYBOMB: 100,
|
||||
c.REPEATERPEA: 200,
|
||||
c.CHOMPER: 100,
|
||||
c.POTATOMINE: 100,}
|
||||
},
|
||||
# 第3关 夜晚 大决战
|
||||
{
|
||||
c.BACKGROUND_TYPE: 1,
|
||||
c.CHOOSEBAR_TYPE: c.CHOOSEBAR_MOVE,
|
||||
c.SHOVEL: 1,
|
||||
c.SPAWN_ZOMBIES:c.SPAWN_ZOMBIES_AUTO,
|
||||
c.INCLUDED_ZOMBIES: ( c.NORMAL_ZOMBIE, c.CONEHEAD_ZOMBIE,
|
||||
c.FOOTBALL_ZOMBIE, c.BUCKETHEAD_ZOMBIE,
|
||||
c.NEWSPAPER_ZOMBIE, c.SCREEN_DOOR_ZOMBIE),
|
||||
c.NUM_FLAGS:3,
|
||||
c.CARD_POOL: { c.PUFFSHROOM: 100,
|
||||
c.SCAREDYSHROOM: 100,
|
||||
c.ICESHROOM: 100,
|
||||
c.HYPNOSHROOM: 100,
|
||||
c.DOOMSHROOM: 100,
|
||||
c.GRAVEBUSTER: 100,
|
||||
c.FUMESHROOM: 200},
|
||||
c.GRADE_GRAVES:3
|
||||
},
|
||||
# 第4关 泳池 大决战
|
||||
{
|
||||
c.BACKGROUND_TYPE: 2,
|
||||
c.CHOOSEBAR_TYPE: c.CHOOSEBAR_MOVE,
|
||||
c.SHOVEL: 1,
|
||||
c.SPAWN_ZOMBIES:c.SPAWN_ZOMBIES_AUTO,
|
||||
c.INCLUDED_ZOMBIES: ( c.NORMAL_ZOMBIE, c.CONEHEAD_ZOMBIE,
|
||||
c.SNORKELZOMBIE, c.BUCKETHEAD_ZOMBIE,
|
||||
c.ZOMBONI,),
|
||||
c.NUM_FLAGS:4,
|
||||
c.CARD_POOL: { c.LILYPAD: 300,
|
||||
c.TORCHWOOD: 100,
|
||||
c.TALLNUT: 100,
|
||||
c.TANGLEKLEP: 100,
|
||||
c.SPIKEWEED: 100,
|
||||
c.SQUASH: 100,
|
||||
c.JALAPENO: 50,
|
||||
c.THREEPEASHOOTER: 400,}
|
||||
},
|
||||
# 第0关 测试
|
||||
{
|
||||
c.BACKGROUND_TYPE: 3,
|
||||
c.GAME_TITLE: '隐藏测试关卡',
|
||||
c.CHOOSEBAR_TYPE: c.CHOOSEBAR_MOVE,
|
||||
c.SHOVEL: 1,
|
||||
c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO,
|
||||
c.INCLUDED_ZOMBIES: (
|
||||
c.NORMAL_ZOMBIE,
|
||||
c.NEWSPAPER_ZOMBIE,
|
||||
c.ZOMBONI,
|
||||
c.FOOTBALL_ZOMBIE,
|
||||
c.CONEHEAD_ZOMBIE,
|
||||
c.BUCKETHEAD_ZOMBIE,
|
||||
),
|
||||
c.NUM_FLAGS: 4,
|
||||
c.CARD_POOL: {
|
||||
c.LILYPAD: 300,
|
||||
c.STARFRUIT: 400,
|
||||
c.PUMPKINHEAD: 100,
|
||||
c.SEASHROOM: 100,
|
||||
c.SPIKEWEED: 100,
|
||||
},
|
||||
},
|
||||
# 第1关 坚果保龄球
|
||||
{
|
||||
c.BACKGROUND_TYPE: 6,
|
||||
c.GAME_TITLE: '坚果保龄球',
|
||||
c.CHOOSEBAR_TYPE: c.CHOOSEBAR_BOWLING,
|
||||
c.SHOVEL: 0,
|
||||
c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO,
|
||||
c.INCLUDED_ZOMBIES: (
|
||||
c.NORMAL_ZOMBIE,
|
||||
c.CONEHEAD_ZOMBIE,
|
||||
c.POLE_VAULTING_ZOMBIE,
|
||||
c.BUCKETHEAD_ZOMBIE,
|
||||
),
|
||||
c.NUM_FLAGS: 2,
|
||||
c.CARD_POOL: {
|
||||
c.WALLNUTBOWLING: 300,
|
||||
c.REDWALLNUTBOWLING: 100,
|
||||
},
|
||||
},
|
||||
# 第2关 白天 大决战
|
||||
{
|
||||
c.BACKGROUND_TYPE: 0,
|
||||
c.GAME_TITLE: '大决战(白天)',
|
||||
c.CHOOSEBAR_TYPE: c.CHOOSEBAR_MOVE,
|
||||
c.SHOVEL: 1,
|
||||
c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO,
|
||||
c.INCLUDED_ZOMBIES: (
|
||||
c.NORMAL_ZOMBIE,
|
||||
c.CONEHEAD_ZOMBIE,
|
||||
c.POLE_VAULTING_ZOMBIE,
|
||||
c.BUCKETHEAD_ZOMBIE,
|
||||
),
|
||||
c.NUM_FLAGS: 3,
|
||||
c.CARD_POOL: {
|
||||
c.PEASHOOTER: 200,
|
||||
c.SNOWPEASHOOTER: 100,
|
||||
c.WALLNUT: 100,
|
||||
c.CHERRYBOMB: 100,
|
||||
c.REPEATERPEA: 200,
|
||||
c.CHOMPER: 100,
|
||||
c.POTATOMINE: 100,
|
||||
},
|
||||
},
|
||||
# 第3关 夜晚 大决战
|
||||
{
|
||||
c.BACKGROUND_TYPE: 1,
|
||||
c.GAME_TITLE: '大决战(黑夜)',
|
||||
c.CHOOSEBAR_TYPE: c.CHOOSEBAR_MOVE,
|
||||
c.SHOVEL: 1,
|
||||
c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO,
|
||||
c.INCLUDED_ZOMBIES: (
|
||||
c.NORMAL_ZOMBIE,
|
||||
c.CONEHEAD_ZOMBIE,
|
||||
c.FOOTBALL_ZOMBIE,
|
||||
c.BUCKETHEAD_ZOMBIE,
|
||||
c.NEWSPAPER_ZOMBIE,
|
||||
c.SCREEN_DOOR_ZOMBIE,
|
||||
),
|
||||
c.NUM_FLAGS: 3,
|
||||
c.CARD_POOL: {
|
||||
c.PUFFSHROOM: 100,
|
||||
c.SCAREDYSHROOM: 100,
|
||||
c.ICESHROOM: 70,
|
||||
c.HYPNOSHROOM: 100,
|
||||
c.DOOMSHROOM: 50,
|
||||
c.GRAVEBUSTER: 100,
|
||||
c.FUMESHROOM: 200,
|
||||
},
|
||||
c.GRADE_GRAVES: 3,
|
||||
},
|
||||
# 第4关 泳池 大决战
|
||||
{
|
||||
c.BACKGROUND_TYPE: 2,
|
||||
c.GAME_TITLE: '大决战(泳池)',
|
||||
c.CHOOSEBAR_TYPE: c.CHOOSEBAR_MOVE,
|
||||
c.SHOVEL: 1,
|
||||
c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO,
|
||||
c.INCLUDED_ZOMBIES: (
|
||||
c.NORMAL_ZOMBIE,
|
||||
c.CONEHEAD_ZOMBIE,
|
||||
c.SNORKELZOMBIE,
|
||||
c.BUCKETHEAD_ZOMBIE,
|
||||
c.ZOMBONI,
|
||||
),
|
||||
c.NUM_FLAGS: 4,
|
||||
c.CARD_POOL: {
|
||||
c.LILYPAD: 300,
|
||||
c.TORCHWOOD: 100,
|
||||
c.TALLNUT: 100,
|
||||
c.TANGLEKLEP: 100,
|
||||
c.SPIKEWEED: 100,
|
||||
c.SQUASH: 100,
|
||||
c.JALAPENO: 50,
|
||||
c.THREEPEASHOOTER: 400,
|
||||
},
|
||||
},
|
||||
# 第5关 坚果保龄球2
|
||||
{
|
||||
c.BACKGROUND_TYPE: 6,
|
||||
c.GAME_TITLE: '坚果保龄球(II)',
|
||||
c.CHOOSEBAR_TYPE: c.CHOOSEBAR_BOWLING,
|
||||
c.SHOVEL: 0,
|
||||
c.SPAWN_ZOMBIES: c.SPAWN_ZOMBIES_AUTO,
|
||||
c.INCLUDED_ZOMBIES: (
|
||||
c.NORMAL_ZOMBIE,
|
||||
c.CONEHEAD_ZOMBIE,
|
||||
c.POLE_VAULTING_ZOMBIE,
|
||||
c.BUCKETHEAD_ZOMBIE,
|
||||
c.NEWSPAPER_ZOMBIE,
|
||||
c.SCREEN_DOOR_ZOMBIE,
|
||||
),
|
||||
c.NUM_FLAGS: 3,
|
||||
c.CARD_POOL: {
|
||||
c.WALLNUTBOWLING: 500,
|
||||
c.REDWALLNUTBOWLING: 100,
|
||||
c.GIANTWALLNUT: 100,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
# 总关卡数
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import random
|
||||
|
||||
import pygame as pg
|
||||
from .. import tool
|
||||
|
||||
from .. import constants as c
|
||||
from .. import tool
|
||||
|
||||
|
||||
def getSunValueImage(sun_value):
|
||||
@ -21,50 +23,83 @@ def getSunValueImage(sun_value):
|
||||
image.set_colorkey(c.BLACK)
|
||||
return image
|
||||
|
||||
|
||||
def getCardPool(data):
|
||||
card_pool = {}
|
||||
for cardName in data:
|
||||
card_pool[c.PLANT_CARD_INFO[c.PLANT_CARD_INDEX[cardName]]] = data[cardName]
|
||||
card_pool = {
|
||||
c.PLANT_CARD_INFO[c.PLANT_CARD_INDEX[card_name]]: data[card_name]
|
||||
for card_name in data
|
||||
}
|
||||
return card_pool
|
||||
|
||||
class Card():
|
||||
def __init__(self, x, y, index, scale=0.5):
|
||||
self.loadFrame(c.PLANT_CARD_INFO[index][c.CARD_INDEX], scale)
|
||||
|
||||
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.loadFrame(self.info[c.CARD_INDEX], scale)
|
||||
self.rect = self.orig_image.get_rect()
|
||||
self.rect.x = x
|
||||
self.rect.y = y
|
||||
# 绘制植物阳光消耗大小
|
||||
font = pg.font.Font(c.FONT_PATH, 12)
|
||||
self.sun_cost_img = font.render(str(c.PLANT_CARD_INFO[index][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()
|
||||
sun_cost_img_x = 32 - self.sun_cost_img_rect.w
|
||||
self.orig_image.blit(self.sun_cost_img, (sun_cost_img_x, 52, self.sun_cost_img_rect.w, self.sun_cost_img_rect.h))
|
||||
|
||||
self.orig_image.blit(
|
||||
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.sun_cost = c.PLANT_CARD_INFO[index][c.SUN_INDEX]
|
||||
self.frozen_time = c.PLANT_CARD_INFO[index][c.FROZEN_TIME_INDEX]
|
||||
self.sun_cost = self.info[c.SUN_INDEX]
|
||||
self.frozen_time = self.info[c.FROZEN_TIME_INDEX]
|
||||
self.frozen_timer = -self.frozen_time
|
||||
self.refresh_timer = 0
|
||||
self.select = True
|
||||
self.clicked = False
|
||||
self.not_recommend = not_recommend
|
||||
if self.not_recommend:
|
||||
self.orig_image.set_alpha(128)
|
||||
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)
|
||||
)
|
||||
else:
|
||||
self.image = self.orig_image
|
||||
self.image.set_alpha(255)
|
||||
|
||||
def loadFrame(self, name, scale):
|
||||
frame = tool.GFX[name]
|
||||
rect = frame.get_rect()
|
||||
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
|
||||
|
||||
def checkMouseClick(self, mouse_pos):
|
||||
x, y = mouse_pos
|
||||
if(x >= self.rect.x and x <= self.rect.right and
|
||||
y >= self.rect.y and y <= self.rect.bottom):
|
||||
if (
|
||||
self.rect.x <= x <= self.rect.right
|
||||
and self.rect.y <= y <= self.rect.bottom
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
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 False
|
||||
|
||||
@ -74,9 +109,21 @@ class Card():
|
||||
def setSelect(self, can_select):
|
||||
self.select = can_select
|
||||
if can_select:
|
||||
self.image.set_alpha(255)
|
||||
if self.not_recommend % 2:
|
||||
self.orig_image.set_alpha(128)
|
||||
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)
|
||||
)
|
||||
else:
|
||||
self.image = self.orig_image
|
||||
self.image.set_alpha(255)
|
||||
else:
|
||||
self.image.set_alpha(128)
|
||||
self.orig_image.set_alpha(64)
|
||||
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)
|
||||
)
|
||||
|
||||
def setFrozenTime(self, current_time):
|
||||
self.frozen_timer = current_time
|
||||
@ -84,23 +131,38 @@ class Card():
|
||||
def createShowImage(self, sun_value, current_time):
|
||||
# 有关是否满足冷却与阳光条件的图片形式
|
||||
time = current_time - self.frozen_timer
|
||||
if time < self.frozen_time: #cool down status
|
||||
image = pg.Surface([self.rect.w, self.rect.h])
|
||||
frozen_image = self.orig_image.copy()
|
||||
if time < self.frozen_time: # cool down status
|
||||
image = pg.Surface((self.rect.w, self.rect.h)) # 黑底
|
||||
frozen_image = self.orig_image
|
||||
frozen_image.set_alpha(128)
|
||||
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(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
|
||||
image = self.orig_image.copy()
|
||||
image.set_alpha(192)
|
||||
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)
|
||||
)
|
||||
self.orig_image.set_alpha(192)
|
||||
image.blit(
|
||||
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
|
||||
image = pg.Surface((self.rect.w, self.rect.h)) # 黑底
|
||||
self.orig_image.set_alpha(192)
|
||||
image.blit(
|
||||
self.orig_image, (0, 0), (0, 0, self.rect.w, self.rect.h)
|
||||
)
|
||||
elif self.clicked:
|
||||
image = self.orig_image.copy()
|
||||
image.set_alpha(128)
|
||||
image = pg.Surface((self.rect.w, self.rect.h)) # 黑底
|
||||
chosen_image = self.orig_image
|
||||
chosen_image.set_alpha(128)
|
||||
|
||||
image.blit(chosen_image, (0, 0), (0, 0, self.rect.w, self.rect.h))
|
||||
else:
|
||||
image = self.orig_image
|
||||
image.set_alpha(255)
|
||||
return image
|
||||
|
||||
def update(self, sun_value, current_time):
|
||||
@ -111,14 +173,15 @@ class Card():
|
||||
def draw(self, surface):
|
||||
surface.blit(self.image, self.rect)
|
||||
|
||||
|
||||
# 植物栏
|
||||
class MenuBar():
|
||||
class MenuBar:
|
||||
def __init__(self, card_list, sun_value):
|
||||
self.loadFrame(c.MENUBAR_BACKGROUND)
|
||||
self.rect = self.image.get_rect()
|
||||
self.rect.x = 0
|
||||
self.rect.y = 0
|
||||
|
||||
|
||||
self.sun_value = sun_value
|
||||
self.card_offset_x = 26
|
||||
self.setupCards(card_list)
|
||||
@ -148,9 +211,9 @@ class MenuBar():
|
||||
self.rect.y = y
|
||||
for i in range(num):
|
||||
x = i * width
|
||||
self.image.blit(img, (x,0))
|
||||
self.image.blit(img, (x, 0))
|
||||
self.image.set_colorkey(c.BLACK)
|
||||
|
||||
|
||||
def setupCards(self, card_list):
|
||||
self.card_list = []
|
||||
x = self.card_offset_x
|
||||
@ -164,17 +227,22 @@ class MenuBar():
|
||||
for card in self.card_list:
|
||||
if card.checkMouseClick(mouse_pos):
|
||||
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:
|
||||
# 播放无法使用该卡片的警告音
|
||||
c.SOUND_CANNOT_CHOOSE_WARNING.play()
|
||||
break
|
||||
return result
|
||||
|
||||
|
||||
def checkMenuBarClick(self, mouse_pos):
|
||||
x, y = mouse_pos
|
||||
if (x >= self.rect.x and x <= self.rect.right and
|
||||
y >= self.rect.y and y <= self.rect.bottom):
|
||||
if (
|
||||
self.rect.x <= x <= self.rect.right
|
||||
and self.rect.y <= y <= self.rect.bottom
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
@ -197,7 +265,7 @@ class MenuBar():
|
||||
self.value_rect = self.value_image.get_rect()
|
||||
self.value_rect.x = 21
|
||||
self.value_rect.y = self.rect.bottom - 24
|
||||
|
||||
|
||||
self.image.blit(self.value_image, self.value_rect)
|
||||
|
||||
def draw(self, surface):
|
||||
@ -206,12 +274,14 @@ class MenuBar():
|
||||
for card in self.card_list:
|
||||
card.draw(surface)
|
||||
|
||||
|
||||
# 关卡模式选植物的界面
|
||||
class Panel():
|
||||
def __init__(self, card_list, sun_value):
|
||||
class Panel:
|
||||
def __init__(self, card_list, sun_value, background_type=c.BACKGROUND_DAY):
|
||||
self.loadImages(sun_value)
|
||||
self.selected_cards = []
|
||||
self.selected_num = 0
|
||||
self.background_type = background_type
|
||||
self.setupCards(card_list)
|
||||
|
||||
def loadFrame(self, name):
|
||||
@ -232,13 +302,12 @@ class Panel():
|
||||
self.panel_rect.x = 0
|
||||
self.panel_rect.y = c.PANEL_Y_START
|
||||
|
||||
|
||||
self.value_image = getSunValueImage(sun_value)
|
||||
self.value_rect = self.value_image.get_rect()
|
||||
self.value_rect.x = 21
|
||||
self.value_rect.y = self.menu_rect.bottom - 24
|
||||
|
||||
self.button_image = self.loadFrame(c.START_BUTTON)
|
||||
self.button_image = self.loadFrame(c.START_BUTTON)
|
||||
self.button_rect = self.button_image.get_rect()
|
||||
self.button_rect.x = 155
|
||||
self.button_rect.y = 547
|
||||
@ -252,12 +321,36 @@ class Panel():
|
||||
x = c.PANEL_X_START - c.PANEL_X_INTERNAL
|
||||
y += c.PANEL_Y_INTERNAL
|
||||
x += c.PANEL_X_INTERNAL
|
||||
self.card_list.append(Card(x, y, index, 0.5))
|
||||
plant_name = c.PLANT_CARD_INFO[index][c.PLANT_NAME_INDEX]
|
||||
if (
|
||||
plant_name in c.WATER_PLANTS
|
||||
and self.background_type not in c.POOL_EQUIPPED_BACKGROUNDS
|
||||
):
|
||||
not_recommend = c.REASON_OTHER
|
||||
elif (
|
||||
plant_name == c.GRAVEBUSTER
|
||||
and self.background_type != c.BACKGROUND_NIGHT
|
||||
):
|
||||
not_recommend = c.REASON_OTHER
|
||||
elif (
|
||||
plant_name in c.CAN_SLEEP_PLANTS
|
||||
and self.background_type in c.DAYTIME_BACKGROUNDS
|
||||
):
|
||||
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:
|
||||
not_recommend = 0
|
||||
self.card_list.append(Card(x, y, index, 0.5, not_recommend))
|
||||
|
||||
def checkCardClick(self, mouse_pos):
|
||||
delete_card = None
|
||||
for card in self.selected_cards:
|
||||
if delete_card: # when delete a card, move right cards to left
|
||||
if delete_card: # when delete a card, move right cards to left
|
||||
card.rect.x -= c.BAR_CARD_X_INTERNAL
|
||||
elif card.checkMouseClick(mouse_pos):
|
||||
self.deleteCard(card.index)
|
||||
@ -268,6 +361,15 @@ class Panel():
|
||||
self.selected_num -= 1
|
||||
# 播放点击音效
|
||||
c.SOUND_TAPPING_CARD.play()
|
||||
if delete_card.info[c.PLANT_NAME_INDEX] == c.COFFEEBEAN:
|
||||
for i in self.card_list:
|
||||
if i.not_recommend == c.REASON_SLEEP_BUT_COFFEE_BEAN:
|
||||
i.not_recommend = c.REASON_WILL_SLEEP
|
||||
i.orig_image.set_alpha(128)
|
||||
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)
|
||||
)
|
||||
|
||||
if self.selected_num >= c.CARD_MAX_NUM:
|
||||
return
|
||||
@ -278,9 +380,17 @@ class Panel():
|
||||
self.addCard(card)
|
||||
# 播放点击音效
|
||||
c.SOUND_TAPPING_CARD.play()
|
||||
if card.info[c.PLANT_NAME_INDEX] == c.COFFEEBEAN:
|
||||
for i in self.card_list:
|
||||
if i.not_recommend == c.REASON_WILL_SLEEP:
|
||||
i.not_recommend = (
|
||||
c.REASON_SLEEP_BUT_COFFEE_BEAN
|
||||
)
|
||||
i.image = i.orig_image
|
||||
i.image.set_alpha(255)
|
||||
break
|
||||
|
||||
def addCard(self, card):
|
||||
def addCard(self, card: Card):
|
||||
card.setSelect(False)
|
||||
y = 8
|
||||
x = 77 + self.selected_num * c.BAR_CARD_X_INTERNAL
|
||||
@ -295,9 +405,11 @@ class Panel():
|
||||
return False
|
||||
|
||||
x, y = mouse_pos
|
||||
if (x >= self.button_rect.x and x <= self.button_rect.right and
|
||||
y >= self.button_rect.y and y <= self.button_rect.bottom):
|
||||
return True
|
||||
if (
|
||||
self.button_rect.x <= x <= self.button_rect.right
|
||||
and self.button_rect.y <= y <= self.button_rect.bottom
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
def getSelectedCards(self):
|
||||
@ -318,8 +430,9 @@ class Panel():
|
||||
if self.selected_num >= c.CARD_LIST_NUM:
|
||||
surface.blit(self.button_image, self.button_rect)
|
||||
|
||||
|
||||
# 传送带模式的卡片
|
||||
class MoveCard():
|
||||
class MoveCard:
|
||||
def __init__(self, x, y, card_name, plant_name, scale=0.5):
|
||||
self.loadFrame(card_name, scale)
|
||||
self.rect = self.orig_image.get_rect()
|
||||
@ -339,29 +452,44 @@ class MoveCard():
|
||||
rect = frame.get_rect()
|
||||
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.image = self.orig_image
|
||||
|
||||
def checkMouseClick(self, mouse_pos):
|
||||
x, y = mouse_pos
|
||||
if (x >= self.rect.x and x <= self.rect.right and
|
||||
y >= self.rect.y and y <= self.rect.bottom):
|
||||
if (
|
||||
self.rect.x <= x <= self.rect.right
|
||||
and self.rect.y <= y <= self.rect.bottom
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
def createShowImage(self):
|
||||
# 新增卡片时显示图片
|
||||
if self.rect.w < self.orig_rect.w: #create a part card image
|
||||
if self.rect.w < self.orig_rect.w: # create a part card image
|
||||
image = pg.Surface([self.rect.w, self.rect.h])
|
||||
image.blit(self.orig_image, (0, 0), (0, 0, self.rect.w, self.rect.h))
|
||||
if self.clicked:
|
||||
self.orig_image.set_alpha(128)
|
||||
else:
|
||||
self.orig_image.set_alpha(255)
|
||||
image.blit(
|
||||
self.orig_image, (0, 0), (0, 0, self.rect.w, self.rect.h)
|
||||
)
|
||||
self.rect.w += 1
|
||||
else:
|
||||
image = self.orig_image
|
||||
if self.clicked:
|
||||
image.set_alpha(192)
|
||||
else:
|
||||
image.set_alpha(255)
|
||||
if self.clicked:
|
||||
image = pg.Surface([self.rect.w, self.rect.h]) # 黑底
|
||||
self.orig_image.set_alpha(128)
|
||||
|
||||
image.blit(
|
||||
self.orig_image, (0, 0), (0, 0, self.rect.w, self.rect.h)
|
||||
)
|
||||
else:
|
||||
self.orig_image.set_alpha(255)
|
||||
image = self.orig_image
|
||||
return image
|
||||
|
||||
def update(self, left_x, current_time):
|
||||
@ -376,14 +504,15 @@ class MoveCard():
|
||||
def draw(self, surface):
|
||||
surface.blit(self.image, self.rect)
|
||||
|
||||
|
||||
# 传送带
|
||||
class MoveBar():
|
||||
class MoveBar:
|
||||
def __init__(self, card_pool):
|
||||
self.loadFrame(c.MOVEBAR_BACKGROUND)
|
||||
self.rect = self.image.get_rect()
|
||||
self.rect.x = 20
|
||||
self.rect.y = 0
|
||||
|
||||
|
||||
self.card_start_x = self.rect.x + 8
|
||||
self.card_end_x = self.rect.right - 5
|
||||
self.card_pool = card_pool
|
||||
@ -400,12 +529,24 @@ class MoveBar():
|
||||
self.image = tool.get_image(tool.GFX[name], *frame_rect, c.WHITE, 1)
|
||||
|
||||
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
|
||||
x = self.card_end_x
|
||||
y = 6
|
||||
selected_card = random.choices(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]))
|
||||
selected_card = random.choices(
|
||||
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
|
||||
|
||||
def update(self, current_time):
|
||||
@ -415,7 +556,7 @@ class MoveBar():
|
||||
card.update(left_x, self.current_time)
|
||||
left_x = card.rect.right + 1
|
||||
|
||||
if(self.current_time - self.create_timer) > c.MOVEBAR_CARD_FRESH_TIME:
|
||||
if (self.current_time - self.create_timer) > c.MOVEBAR_CARD_FRESH_TIME:
|
||||
if self.createCard():
|
||||
self.create_timer = self.current_time
|
||||
|
||||
@ -426,11 +567,13 @@ class MoveBar():
|
||||
result = (card.plant_name, card)
|
||||
break
|
||||
return result
|
||||
|
||||
|
||||
def checkMenuBarClick(self, mouse_pos):
|
||||
x, y = mouse_pos
|
||||
if (x >= self.rect.x and x <= self.rect.right and
|
||||
y >= self.rect.y and y <= self.rect.bottom):
|
||||
if (
|
||||
self.rect.x <= x <= self.rect.right
|
||||
and self.rect.y <= y <= self.rect.bottom
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
@ -1,14 +1,16 @@
|
||||
import pygame as pg
|
||||
import os
|
||||
from .. import tool
|
||||
|
||||
import pygame as pg
|
||||
|
||||
from .. import constants as c
|
||||
from .. import tool
|
||||
|
||||
|
||||
class Menu(tool.State):
|
||||
|
||||
def __init__(self):
|
||||
tool.State.__init__(self)
|
||||
|
||||
def startup(self, current_time, persist):
|
||||
|
||||
def startup(self, current_time: int, persist):
|
||||
self.next = c.LEVEL
|
||||
self.persist = persist
|
||||
self.game_info = persist
|
||||
@ -17,7 +19,7 @@ class Menu(tool.State):
|
||||
self.setupOptionMenu()
|
||||
self.setupSunflowerTrophy()
|
||||
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.display.set_caption(c.ORIGINAL_CAPTION)
|
||||
pg.mixer.music.set_volume(self.game_info[c.SOUND_VOLUME])
|
||||
@ -30,7 +32,9 @@ class Menu(tool.State):
|
||||
# 2、双星号同上,区别是x视为字典。
|
||||
# 3、在变量前加单星号表示将元组(列表、集合)拆分为单个元素。
|
||||
# 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.x = 0
|
||||
self.bg_rect.y = 0
|
||||
@ -39,7 +43,12 @@ class Menu(tool.State):
|
||||
# 冒险模式
|
||||
frame_rect = (0, 0, 330, 144)
|
||||
# 写成列表生成器方便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_rect = self.adventure_image.get_rect()
|
||||
self.adventure_rect.x = 400
|
||||
@ -48,7 +57,12 @@ class Menu(tool.State):
|
||||
|
||||
# 小游戏
|
||||
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_rect = self.littleGame_image.get_rect()
|
||||
self.littleGame_rect.x = 397
|
||||
@ -57,7 +71,12 @@ class Menu(tool.State):
|
||||
|
||||
# 退出按钮
|
||||
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_rect = self.exit_image.get_rect()
|
||||
self.exit_rect.x = 730
|
||||
@ -66,7 +85,12 @@ class Menu(tool.State):
|
||||
|
||||
# 选项按钮
|
||||
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_rect = self.option_button_image.get_rect()
|
||||
self.option_button_rect.x = 560
|
||||
@ -75,20 +99,23 @@ class Menu(tool.State):
|
||||
|
||||
# 帮助菜单
|
||||
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_rect = self.help_image.get_rect()
|
||||
self.help_rect.x = 653
|
||||
self.help_rect.y = 520
|
||||
self.help_hilight_time = 0
|
||||
|
||||
|
||||
# 计时器与点击信号记录器
|
||||
self.adventure_start = 0
|
||||
self.adventure_timer = 0
|
||||
self.adventure_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):
|
||||
self.adventure_highlight_time = self.current_time
|
||||
@ -106,15 +133,25 @@ class Menu(tool.State):
|
||||
self.help_hilight_time = self.current_time
|
||||
|
||||
# 处理按钮高亮情况
|
||||
self.adventure_image = self.chooseHilightImage(self.adventure_highlight_time, self.adventure_frames)
|
||||
self.exit_image = self.chooseHilightImage(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)
|
||||
self.adventure_image = self.chooseHilightImage(
|
||||
self.adventure_highlight_time, self.adventure_frames
|
||||
)
|
||||
self.exit_image = self.chooseHilightImage(
|
||||
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:
|
||||
index= 1
|
||||
index = 1
|
||||
else:
|
||||
index = 0
|
||||
return frames[index]
|
||||
@ -124,6 +161,7 @@ class Menu(tool.State):
|
||||
self.adventure_timer = self.adventure_start = self.current_time
|
||||
self.persist[c.GAME_MODE] = c.MODE_ADVENTURE
|
||||
# 播放进入音效
|
||||
pg.mixer.music.stop()
|
||||
c.SOUND_EVILLAUGH.play()
|
||||
c.SOUND_LOSE.play()
|
||||
|
||||
@ -147,7 +185,9 @@ class Menu(tool.State):
|
||||
def setupOptionMenu(self):
|
||||
# 选项菜单框
|
||||
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.x = 150
|
||||
self.big_menu_rect.y = 0
|
||||
@ -161,7 +201,7 @@ class Menu(tool.State):
|
||||
self.return_button_rect.y = 440
|
||||
font = pg.font.Font(c.FONT_PATH, 40)
|
||||
font.bold = True
|
||||
text = font.render("返回游戏", True, c.YELLOWGREEN)
|
||||
text = font.render('返回游戏', True, c.YELLOWGREEN)
|
||||
text_rect = text.get_rect()
|
||||
text_rect.x = 105
|
||||
text_rect.y = 18
|
||||
@ -172,54 +212,75 @@ class Menu(tool.State):
|
||||
font = pg.font.Font(c.FONT_PATH, 35)
|
||||
font.bold = True
|
||||
# 音量+
|
||||
self.sound_volume_plus_button = tool.get_image_alpha(tool.GFX[c.SOUND_VOLUME_BUTTON], *frame_rect, c.BLACK)
|
||||
sign = font.render("+", True, c.YELLOWGREEN)
|
||||
self.sound_volume_plus_button = tool.get_image_alpha(
|
||||
tool.GFX[c.SOUND_VOLUME_BUTTON], *frame_rect, c.BLACK
|
||||
)
|
||||
sign = font.render('+', True, c.YELLOWGREEN)
|
||||
sign_rect = sign.get_rect()
|
||||
sign_rect.x = 8
|
||||
sign_rect.y = -4
|
||||
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_minus_button = tool.get_image_alpha(tool.GFX[c.SOUND_VOLUME_BUTTON], *frame_rect, c.BLACK)
|
||||
sign = font.render("-", True, c.YELLOWGREEN)
|
||||
self.sound_volume_minus_button = tool.get_image_alpha(
|
||||
tool.GFX[c.SOUND_VOLUME_BUTTON], *frame_rect, c.BLACK
|
||||
)
|
||||
sign = font.render('-', True, c.YELLOWGREEN)
|
||||
sign_rect = sign.get_rect()
|
||||
sign_rect.x = 12
|
||||
sign_rect.y = -6
|
||||
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.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):
|
||||
# 设置金银向日葵图片信息
|
||||
if (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]):
|
||||
if (
|
||||
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)
|
||||
else:
|
||||
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.x = 0
|
||||
self.sunflower_trophy_rect.y = 280
|
||||
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):
|
||||
self.sunflower_trophy_show_info_time = self.current_time
|
||||
if (self.current_time - self.sunflower_trophy_show_info_time) < 80:
|
||||
font = pg.font.Font(c.FONT_PATH, 14)
|
||||
if (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]}轮"
|
||||
if (
|
||||
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]:
|
||||
infoText = f"目前您一共完成了:冒险模式{self.game_info[c.LEVEL_COMPLETIONS]}轮;完成其他所有游戏模式以获得金向日葵奖杯!"
|
||||
infoText = f'目前您一共完成了:冒险模式{self.game_info[c.LEVEL_COMPLETIONS]}轮;完成其他所有游戏模式以获得金向日葵奖杯!'
|
||||
else:
|
||||
infoText = f"目前您一共完成了:玩玩小游戏{self.game_info[c.LITTLEGAME_COMPLETIONS]}轮;完成其他所有游戏模式以获得金向日葵奖杯!"
|
||||
infoImg = font.render(infoText , True, c.BLACK, c.LIGHTYELLOW)
|
||||
infoText = f'目前您一共完成了:玩玩小游戏{self.game_info[c.LITTLEGAME_COMPLETIONS]}轮;完成其他所有游戏模式以获得金向日葵奖杯!'
|
||||
infoImg = font.render(infoText, True, c.BLACK, c.LIGHTYELLOW)
|
||||
infoImg_rect = infoImg.get_rect()
|
||||
infoImg_rect.x = x
|
||||
infoImg_rect.y = y
|
||||
infoImg_rect.x = self.sunflower_trophy_rect.x
|
||||
infoImg_rect.y = self.sunflower_trophy_rect.bottom - 14
|
||||
surface.blit(infoImg, infoImg_rect)
|
||||
|
||||
def respondOptionButtonClick(self):
|
||||
@ -227,25 +288,38 @@ class Menu(tool.State):
|
||||
# 播放点击音效
|
||||
c.SOUND_BUTTON_CLICK.play()
|
||||
|
||||
def showCurrentVolumeImage(self, surface):
|
||||
def showCurrentVolumeImage(self, surface: pg.Surface):
|
||||
# 由于音量可变,因此这一内容不能在一开始就结束加载,而应当不断刷新不断显示
|
||||
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.x = 275
|
||||
volume_tips_rect.y = 247
|
||||
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
|
||||
|
||||
|
||||
surface.blit(self.bg_image, self.bg_rect)
|
||||
surface.blit(self.adventure_image, self.adventure_rect)
|
||||
surface.blit(self.littleGame_image, self.littleGame_rect)
|
||||
surface.blit(self.exit_image, self.exit_rect)
|
||||
surface.blit(self.option_button_image, self.option_button_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)
|
||||
|
||||
# 点到冒险模式后播放动画
|
||||
@ -261,8 +335,14 @@ class Menu(tool.State):
|
||||
elif self.option_button_clicked:
|
||||
surface.blit(self.big_menu, self.big_menu_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(self.sound_volume_minus_button, self.sound_volume_minus_button_rect)
|
||||
surface.blit(
|
||||
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)
|
||||
if mouse_pos:
|
||||
# 返回
|
||||
@ -270,8 +350,12 @@ class Menu(tool.State):
|
||||
self.option_button_clicked = False
|
||||
c.SOUND_BUTTON_CLICK.play()
|
||||
# 音量+
|
||||
elif self.inArea(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)
|
||||
elif self.inArea(
|
||||
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.music.set_volume(self.game_info[c.SOUND_VOLUME])
|
||||
for i in c.SOUNDS:
|
||||
@ -279,8 +363,12 @@ class Menu(tool.State):
|
||||
c.SOUND_BUTTON_CLICK.play()
|
||||
self.saveUserData()
|
||||
# 音量-
|
||||
elif self.inArea(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)
|
||||
elif self.inArea(
|
||||
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.music.set_volume(self.game_info[c.SOUND_VOLUME])
|
||||
for i in c.SOUNDS:
|
||||
@ -292,7 +380,10 @@ class Menu(tool.State):
|
||||
# 先检查选项高亮预览
|
||||
x, y = pg.mouse.get_pos()
|
||||
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)
|
||||
if mouse_pos:
|
||||
if self.inArea(self.adventure_rect, *mouse_pos):
|
||||
|
||||
@ -1,18 +1,25 @@
|
||||
import os
|
||||
from abc import abstractmethod
|
||||
|
||||
import pygame as pg
|
||||
from .. import tool
|
||||
|
||||
from .. import constants as c
|
||||
from .. import tool
|
||||
|
||||
|
||||
class Screen(tool.State):
|
||||
def __init__(self):
|
||||
tool.State.__init__(self)
|
||||
|
||||
@abstractmethod
|
||||
def startup(self, current_time, persist):
|
||||
pass
|
||||
|
||||
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.x = 0
|
||||
self.rect.y = 0
|
||||
@ -20,33 +27,45 @@ class Screen(tool.State):
|
||||
# 按钮
|
||||
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_rect = self.main_menu_button_image.get_rect()
|
||||
self.main_menu_button_image = tool.get_image_alpha(
|
||||
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
|
||||
### 主菜单按钮上的文字
|
||||
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.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.x = 70
|
||||
### 继续按钮上的文字
|
||||
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.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:
|
||||
next_text = font.render("重新开始", True, c.NAVYBLUE)
|
||||
next_text = font.render('重新开始', True, c.NAVYBLUE)
|
||||
next_text_rect = next_text.get_rect()
|
||||
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.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.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):
|
||||
surface.fill(c.WHITE)
|
||||
@ -61,36 +80,39 @@ class Screen(tool.State):
|
||||
self.next = c.MAIN_MENU
|
||||
self.done = True
|
||||
|
||||
|
||||
class GameVictoryScreen(Screen):
|
||||
def __init__(self):
|
||||
Screen.__init__(self)
|
||||
self.image_name = c.GAME_VICTORY_IMAGE
|
||||
|
||||
|
||||
def startup(self, current_time, persist):
|
||||
self.start_time = current_time
|
||||
self.persist = persist
|
||||
self.game_info = persist
|
||||
self.setupImage(self.image_name)
|
||||
pg.display.set_caption("pypvz: 战斗胜利!")
|
||||
pg.display.set_caption('pypvz: 战斗胜利!')
|
||||
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.set_volume(self.game_info[c.SOUND_VOLUME])
|
||||
|
||||
|
||||
class GameLoseScreen(Screen):
|
||||
def __init__(self):
|
||||
Screen.__init__(self)
|
||||
self.image_name = c.GAME_LOSE_IMAGE
|
||||
|
||||
|
||||
def startup(self, current_time, persist):
|
||||
self.start_time = current_time
|
||||
self.persist = persist
|
||||
self.game_info = persist
|
||||
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()
|
||||
|
||||
|
||||
class AwardScreen(tool.State):
|
||||
def __init__(self):
|
||||
tool.State.__init__(self)
|
||||
@ -98,7 +120,9 @@ class AwardScreen(tool.State):
|
||||
def setupImage(self):
|
||||
# 主体
|
||||
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.x = 0
|
||||
self.rect.y = 0
|
||||
@ -106,41 +130,54 @@ class AwardScreen(tool.State):
|
||||
# 文字
|
||||
# 标题处文字
|
||||
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.x = 220
|
||||
title_text_rect.y = 23
|
||||
self.image.blit(title_text, title_text_rect)
|
||||
|
||||
|
||||
# 按钮
|
||||
frame_rect = (0, 0, 111, 26)
|
||||
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_rect = self.main_menu_button_image.get_rect()
|
||||
self.main_menu_button_image = tool.get_image_alpha(
|
||||
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.y = 520
|
||||
### 主菜单按钮上的文字
|
||||
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.x = 29
|
||||
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.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
|
||||
)
|
||||
|
||||
# 绘制向日葵奖杯
|
||||
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)
|
||||
intro_title = "金向日葵奖杯"
|
||||
intro_content = "您已通过所有关卡,获得此奖励!"
|
||||
intro_title = '金向日葵奖杯'
|
||||
intro_content = '您已通过所有关卡,获得此奖励!'
|
||||
else:
|
||||
frame_rect = (0, 0, 157, 269)
|
||||
intro_title = "银向日葵奖杯"
|
||||
intro_title = '银向日葵奖杯'
|
||||
if self.game_info[c.LEVEL_COMPLETIONS]:
|
||||
intro_content = "您已完成冒险模式,获得此奖励!"
|
||||
intro_content = '您已完成冒险模式,获得此奖励!'
|
||||
else:
|
||||
intro_content = "您已完成玩玩小游戏,获得此奖励!"
|
||||
sunflower_trophy_image = tool.get_image_alpha(tool.GFX[c.TROPHY_SUNFLOWER], *frame_rect, scale=0.7)
|
||||
intro_content = '您已完成玩玩小游戏,获得此奖励!'
|
||||
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.x = 348
|
||||
sunflower_trophy_rect.y = 108
|
||||
@ -148,7 +185,9 @@ class AwardScreen(tool.State):
|
||||
|
||||
# 绘制介绍标题
|
||||
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.x = 333
|
||||
intro_title_rect.y = 305
|
||||
@ -163,40 +202,56 @@ class AwardScreen(tool.State):
|
||||
self.image.blit(intro_content_img, intro_content_rect)
|
||||
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.x = 70
|
||||
### 继续按钮上的文字
|
||||
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.x = 37
|
||||
## 主菜单按钮
|
||||
self.main_menu_button_image = tool.get_image_alpha(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 = tool.get_image_alpha(
|
||||
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.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.x = 29
|
||||
self.next_button_image.blit(next_text, next_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.main_menu_button_image, self.main_menu_button_image_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.main_menu_button_image, self.main_menu_button_image_rect
|
||||
)
|
||||
|
||||
def startup(self, current_time, persist):
|
||||
self.start_time = current_time
|
||||
self.persist = 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
|
||||
else:
|
||||
self.show_only_one_option = True
|
||||
self.setupImage()
|
||||
pg.display.set_caption("pypvz: 您获得了新的战利品!")
|
||||
pg.display.set_caption('pypvz: 您获得了新的战利品!')
|
||||
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.set_volume(self.game_info[c.SOUND_VOLUME])
|
||||
|
||||
@ -212,6 +267,7 @@ class AwardScreen(tool.State):
|
||||
self.next = c.LEVEL
|
||||
self.done = True
|
||||
|
||||
|
||||
class HelpScreen(tool.State):
|
||||
def __init__(self):
|
||||
tool.State.__init__(self)
|
||||
@ -221,29 +277,39 @@ class HelpScreen(tool.State):
|
||||
self.persist = persist
|
||||
self.game_info = persist
|
||||
self.setupImage()
|
||||
pg.display.set_caption('pypvz: 帮助')
|
||||
pg.mixer.music.stop()
|
||||
c.SOUND_HELP_SCREEN.play()
|
||||
|
||||
def setupImage(self):
|
||||
# 主体
|
||||
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.x = 0
|
||||
self.rect.y = 0
|
||||
|
||||
|
||||
# 主菜单按钮
|
||||
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_rect = self.main_menu_button_image.get_rect()
|
||||
self.main_menu_button_image = tool.get_image_alpha(
|
||||
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.y = 500
|
||||
### 主菜单按钮上的文字
|
||||
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.x = 29
|
||||
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):
|
||||
surface.fill(c.BLACK)
|
||||
|
||||
185
source/tool.py
@ -1,12 +1,17 @@
|
||||
import os
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
from abc import abstractmethod
|
||||
|
||||
import pygame as pg
|
||||
from pygame.locals import *
|
||||
|
||||
from . import constants as c
|
||||
|
||||
logger = logging.getLogger('main')
|
||||
|
||||
# 状态机 抽象基类
|
||||
class State():
|
||||
class State:
|
||||
def __init__(self):
|
||||
self.start_time = 0
|
||||
self.current_time = 0
|
||||
@ -16,30 +21,31 @@ class State():
|
||||
|
||||
# 当从其他状态进入这个状态时,需要进行的初始化操作
|
||||
@abstractmethod
|
||||
def startup(self, current_time, persist):
|
||||
def startup(self, current_time: int, persist: dict):
|
||||
# 前面加了@abstractmethod表示抽象基类中必须要重新定义的method(method是对象和函数的结合)
|
||||
pass
|
||||
|
||||
# 当从这个状态退出时,需要进行的清除操作
|
||||
def cleanup(self):
|
||||
self.done = False
|
||||
return self.persist
|
||||
|
||||
# 在这个状态运行时进行的更新操作
|
||||
@abstractmethod
|
||||
def update(self, surface, keys, current_time):
|
||||
def update(self, surface: pg.Surface, keys, current_time: int):
|
||||
# 前面加了@abstractmethod表示抽象基类中必须要重新定义的method
|
||||
pass
|
||||
|
||||
# 工具:范围判断函数,用于判断点击
|
||||
def inArea(self, rect, x, y):
|
||||
if (x >= rect.x and x <= rect.right and
|
||||
y >= rect.y and y <= rect.bottom):
|
||||
def inArea(self, rect: pg.Rect, x: int, y: int):
|
||||
if rect.x <= x <= rect.right and rect.y <= y <= rect.bottom:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
# 工具:用户数据保存函数
|
||||
def saveUserData(self):
|
||||
with open(c.USERDATA_PATH, "w") as f:
|
||||
with open(c.USERDATA_PATH, 'w', encoding='utf-8') as f:
|
||||
userdata = {}
|
||||
for i in self.game_info:
|
||||
if i in c.INIT_USERDATA:
|
||||
@ -47,25 +53,35 @@ class State():
|
||||
data_to_save = json.dumps(userdata, sort_keys=True, indent=4)
|
||||
f.write(data_to_save)
|
||||
|
||||
|
||||
# 进行游戏控制 循环 事件响应
|
||||
class Control():
|
||||
class Control:
|
||||
def __init__(self):
|
||||
self.screen = pg.display.get_surface()
|
||||
self.done = False
|
||||
self.clock = pg.time.Clock() # 创建一个对象来帮助跟踪时间
|
||||
self.keys = pg.key.get_pressed()
|
||||
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.state_dict = {}
|
||||
self.state_name = None
|
||||
self.state = None
|
||||
# 这里需要考虑多种情况,如文件不存在、文件不可读、文件不符合JSON语法要求,这些情况目前暂定统一进行新建文件操作
|
||||
# 因此仍然采用try-except实现而非if-else实现
|
||||
try:
|
||||
# 存在存档即导入
|
||||
with open(c.USERDATA_PATH) as f:
|
||||
# 先自动修复读写权限(Python权限规则和Unix不一样,420表示unix的644,Windows自动忽略不支持项)
|
||||
os.chmod(c.USERDATA_PATH, 420)
|
||||
with open(c.USERDATA_PATH, encoding='utf-8') as f:
|
||||
userdata = json.load(f)
|
||||
except FileNotFoundError:
|
||||
self.setupUserData()
|
||||
except json.JSONDecodeError:
|
||||
logger.warning('用户存档解码错误!程序将新建初始存档!\n')
|
||||
self.setupUserData()
|
||||
else: # 没有引发异常才执行
|
||||
self.game_info = {}
|
||||
# 导入数据,保证了可运行性,但是放弃了数据向后兼容性,即假如某些变量在以后改名,在导入时可能会被重置
|
||||
need_to_rewrite = False
|
||||
@ -76,24 +92,26 @@ class Control():
|
||||
self.game_info[key] = c.INIT_USERDATA[key]
|
||||
need_to_rewrite = True
|
||||
if need_to_rewrite:
|
||||
with open(c.USERDATA_PATH, "w") as f:
|
||||
savedata = json.dumps(self.game_info, sort_keys=True, indent=4)
|
||||
with open(c.USERDATA_PATH, 'w', encoding='utf-8') as f:
|
||||
savedata = json.dumps(
|
||||
self.game_info, sort_keys=True, indent=4
|
||||
)
|
||||
f.write(savedata)
|
||||
except:
|
||||
if not os.path.exists(os.path.dirname(c.USERDATA_PATH)):
|
||||
os.makedirs(os.path.dirname(c.USERDATA_PATH))
|
||||
with open(c.USERDATA_PATH, "w") as f:
|
||||
savedata = json.dumps(c.INIT_USERDATA, sort_keys=True, indent=4)
|
||||
f.write(savedata)
|
||||
self.game_info = c.INIT_USERDATA.copy() # 内部全是不可变对象,浅拷贝即可
|
||||
# 存档内不包含即时游戏时间信息,需要新建
|
||||
self.game_info[c.CURRENT_TIME] = 0
|
||||
|
||||
# 50为目前的基础帧率,乘以倍率即是游戏帧率
|
||||
self.fps = 50 * self.game_info[c.GAME_RATE]
|
||||
|
||||
|
||||
def setup_states(self, state_dict, start_state):
|
||||
def setupUserData(self):
|
||||
if not os.path.exists(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:
|
||||
savedata = json.dumps(c.INIT_USERDATA, sort_keys=True, indent=4)
|
||||
f.write(savedata)
|
||||
self.game_info = c.INIT_USERDATA.copy() # 内部全是不可变对象,浅拷贝即可
|
||||
|
||||
def setup_states(self, state_dict: dict, start_state):
|
||||
self.state_dict = state_dict
|
||||
self.state_name = start_state
|
||||
self.state = self.state_dict[self.state_name]
|
||||
@ -105,8 +123,10 @@ class Control():
|
||||
|
||||
if self.state.done:
|
||||
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_click[0] = False
|
||||
self.mouse_click[1] = False
|
||||
@ -128,18 +148,24 @@ class Control():
|
||||
elif event.type == pg.KEYDOWN:
|
||||
self.keys = pg.key.get_pressed()
|
||||
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:
|
||||
pg.display.set_mode(c.SCREEN_SIZE)
|
||||
elif event.type == pg.KEYUP:
|
||||
self.keys = pg.key.get_pressed()
|
||||
elif event.type == pg.MOUSEBUTTONDOWN:
|
||||
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]表示右键
|
||||
print( f"点击位置: ({self.mouse_pos[0]:3}, {self.mouse_pos[1]:3})",
|
||||
f"左右键点击情况: {self.mouse_click}")
|
||||
|
||||
print(
|
||||
f'点击位置: ({self.mouse_pos[0]:3}, {self.mouse_pos[1]:3}) 左右键点击情况: {self.mouse_click}'
|
||||
)
|
||||
|
||||
def run(self):
|
||||
while not self.done:
|
||||
@ -148,36 +174,57 @@ class Control():
|
||||
pg.display.update()
|
||||
self.clock.tick(self.fps)
|
||||
|
||||
def get_image(sheet, x, y, width, height, colorkey=c.BLACK, scale=1):
|
||||
# 不保留alpha通道的图片导入
|
||||
image = pg.Surface([width, height])
|
||||
rect = image.get_rect()
|
||||
|
||||
image.blit(sheet, (0, 0), (x, y, width, height))
|
||||
if colorkey:
|
||||
image.set_colorkey(colorkey)
|
||||
image = pg.transform.scale(image,
|
||||
(int(rect.width*scale),
|
||||
int(rect.height*scale)))
|
||||
return image
|
||||
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通道的图片导入
|
||||
image = pg.Surface([width, height])
|
||||
rect = image.get_rect()
|
||||
|
||||
def get_image_alpha(sheet, x, y, width, height, colorkey=c.BLACK, scale=1):
|
||||
# 保留alpha通道的图片导入
|
||||
image = pg.Surface([width, height], SRCALPHA)
|
||||
rect = image.get_rect()
|
||||
|
||||
image.blit(sheet, (0, 0), (x, y, width, height))
|
||||
image.blit(sheet, (0, 0), (x, y, width, height))
|
||||
if colorkey:
|
||||
image.set_colorkey(colorkey)
|
||||
image = pg.transform.scale(image,
|
||||
(int(rect.width*scale),
|
||||
int(rect.height*scale)))
|
||||
return image
|
||||
|
||||
def load_image_frames(directory, image_name, colorkey, accept):
|
||||
image = pg.transform.scale(
|
||||
image, (int(rect.width * scale), int(rect.height * scale))
|
||||
)
|
||||
return image
|
||||
|
||||
|
||||
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通道的图片导入
|
||||
image = pg.Surface([width, height], SRCALPHA)
|
||||
rect = image.get_rect()
|
||||
|
||||
image.blit(sheet, (0, 0), (x, y, width, height))
|
||||
image.set_colorkey(colorkey)
|
||||
image = pg.transform.scale(
|
||||
image, (int(rect.width * scale), int(rect.height * scale))
|
||||
)
|
||||
return image
|
||||
|
||||
|
||||
def load_image_frames(
|
||||
directory: str, image_name: str, colorkey: tuple[int], accept: tuple[str]
|
||||
) -> list[pg.Surface]:
|
||||
frame_list = []
|
||||
tmp = {}
|
||||
# image_name is "Peashooter", pic name is "Peashooter_1", get the index 1
|
||||
index_start = len(image_name) + 1
|
||||
index_start = len(image_name) + 1
|
||||
frame_num = 0
|
||||
for pic in os.listdir(directory):
|
||||
name, ext = os.path.splitext(pic)
|
||||
@ -189,15 +236,20 @@ def load_image_frames(directory, image_name, colorkey, accept):
|
||||
else:
|
||||
img = img.convert()
|
||||
img.set_colorkey(colorkey)
|
||||
tmp[index]= img
|
||||
tmp[index] = img
|
||||
frame_num += 1
|
||||
|
||||
for i in range(frame_num): # 这里注意编号必须连续,否则会出错
|
||||
frame_list.append(tmp[i])
|
||||
return frame_list
|
||||
|
||||
|
||||
# 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 = {}
|
||||
for name1 in os.listdir(directory):
|
||||
# subfolders under the folder resources\graphics
|
||||
@ -206,21 +258,25 @@ def load_all_gfx(directory, colorkey=c.WHITE, accept=(".png", ".jpg", ".bmp", ".
|
||||
for name2 in os.listdir(dir1):
|
||||
dir2 = os.path.join(dir1, name2)
|
||||
if os.path.isdir(dir2):
|
||||
# e.g. subfolders under the folder resources\graphics\Zombies
|
||||
# e.g. subfolders under the folder resources\graphics\Zombies
|
||||
for name3 in os.listdir(dir2):
|
||||
dir3 = os.path.join(dir2, name3)
|
||||
# e.g. subfolders or pics under the folder resources\graphics\Zombies\ConeheadZombie
|
||||
if os.path.isdir(dir3):
|
||||
# e.g. it"s the folder resources\graphics\Zombies\ConeheadZombie\ConeheadZombieAttack
|
||||
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:
|
||||
# e.g. pics under the folder resources\graphics\Plants\Peashooter
|
||||
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
|
||||
else:
|
||||
# e.g. pics under the folder resources\graphics\Screen
|
||||
# e.g. pics under the folder resources\graphics\Screen
|
||||
name, ext = os.path.splitext(name2)
|
||||
if ext.lower() in accept:
|
||||
img = pg.image.load(dir2)
|
||||
@ -232,10 +288,13 @@ def load_all_gfx(directory, colorkey=c.WHITE, accept=(".png", ".jpg", ".bmp", ".
|
||||
graphics[name] = img
|
||||
return graphics
|
||||
|
||||
|
||||
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,经常不够用
|
||||
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))
|
||||
|
||||
GFX = load_all_gfx(c.PATH_IMG_DIR)
|
||||
|
||||
209
uv.lock
generated
Normal 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" },
|
||||
]
|
||||