Compare commits

..

No commits in common. "master" and "0.8.20.0" have entirely different histories.

57 changed files with 2392 additions and 4548 deletions

View File

@ -14,7 +14,7 @@ jobs:
fail-fast: false
matrix:
python_version:
- "3.12"
- '3.10'
name: Windows Python ${{ matrix.python_version }}
steps:
- uses: actions/checkout@v2
@ -46,11 +46,10 @@ 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: Dev.Version.Built.with.Pyinstaller
tag: Current.Version.Built.with.Pyinstaller
artifacts: ./out/*pyinstaller*.exe
token: ${{ secrets.GITHUB_TOKEN }}
@ -68,75 +67,74 @@ 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\libjpeg-9.dll=libjpeg-9.dll `
--include-data-dir=resources=resources `
--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 `
--windows-disable-console `
-o pypvz-with-python${{ matrix.python_version }}-nuitka-windows-x64.exe `
-o ./out/pypvz-with-python${{ matrix.python_version }}-nuitka-windows-x64.exe `
pypvz.py
- name: Release the version built by nuitka
if: github.event.pull_request.head.repo.full_name == github.repository
uses: ncipollo/release-action@v1
# artifact压缩包处上传包含exe和运行环境的文件夹
- name: "Upload binaries"
uses: actions/upload-artifact@v2
with:
allowUpdates: true
tag: Dev
artifacts: ./out/*nuitka*.exe
token: ${{ secrets.GITHUB_TOKEN }}
name: artifact-windows-python-${{ matrix.python_version }}
path: ./out/*.dist
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
#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
- 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
# 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 \
--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: 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: 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 }}
# - 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

View File

@ -16,7 +16,7 @@ jobs:
fail-fast: false
matrix:
python_version:
- "3.12"
- '3.10'
name: Windows Python ${{ matrix.python_version }}
steps:
- uses: actions/checkout@v2
@ -69,15 +69,22 @@ 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\libjpeg-9.dll=libjpeg-9.dll `
--include-data-dir=resources=resources `
--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 `
--windows-disable-console `
-o pypvz-with-python${{ matrix.python_version }}-nuitka-windows-x64.exe `
-o ./out/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
@ -87,55 +94,58 @@ jobs:
artifacts: ./out/*nuitka*.exe
token: ${{ secrets.GITHUB_TOKEN }}
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
#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
- 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 \
--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: 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: Release the version built by nuitka
uses: ncipollo/release-action@v1
with:
allowUpdates: true
tag: Latest
artifacts: ./pypvz*-x86_64.*
token: ${{ secrets.GITHUB_TOKEN }}
# - name: Release the version built by nuitka
# uses: ncipollo/release-action@v1
# with:
# allowUpdates: true
# tag: Latest
# artifacts: ./*.tar.zst
# token: ${{ secrets.GITHUB_TOKEN }}

6
.gitignore vendored
View File

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

View File

@ -1 +0,0 @@
3.12

View File

@ -22,33 +22,22 @@
* 夜晚模式支持墓碑以及从墓碑生成僵尸
* 含有泳池的模式支持在最后一波时从泳池中自动冒出僵尸
* 支持保存进度
* Windows下默认进度文件的保存路径为`~\AppData\Roaming\pypvz\userdata.json`
* 其他操作系统为`~/.config/pypvz/userdata.json`
* Windows下默认进度文件的保存路径为`~\AppData\Roaming\wszqkzqk.dev\pypvz\userdata.json`
* 其他操作系统为`~/.config/wszqkzqk.dev/pypvz/userdata.json`
* 存档为JSON文件如果出现因存档损坏而造成程序无法启动可以手动编辑修复或者删除该文件重试
* 0.8.12.0版本后理论上不可能因为存档损坏而无法启动,如果有,请在[issues](https://github.com/wszqkzqk/pypvz/issues)中报告bug
* 仍然有可能因为升级后变量名不同而丢失存档的进度信息,这种情况手动编辑恢复即可
* 支持错误日志记录
* Windows下默认日志文件的保存路径为`~\AppData\Roaming\pypvz\run.log`
* 其他操作系统为`~/.config/pypvz/run.log`
* Windows下默认日志文件的保存路径为`~\AppData\Roaming\wszqkzqk.dev\pypvz\run.log`
* 其他操作系统为`~/.config/wszqkzqk.dev/pypvz/run.log`
* 支持自定义游戏速度倍率
* 保存在游戏存档文件中,可以通过修改`game rate`值更改速度倍率
* 游戏完成成就显示
* 任意一游戏模式全部完成显示银向日葵奖杯
* 冒险模式全部完成显示银向日葵奖杯
* 所有模式全部完成显示金向日葵奖杯
* 光标移动到向日葵奖杯上是显示当前各个模式通关次数
* 含有游戏帮助界面 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,最好使用最新版)
@ -71,27 +60,24 @@ 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\pypvz\userdata.json`Windows`~/.config/pypvz/userdata.json`(其他操作系统)中修改当前关卡:
* 对于已经存在存档的用户,可以在`~\AppData\Roaming\wszqkzqk.dev\pypvz\userdata.json`Windows`~/.config/wszqkzqk.dev/pypvz/userdata.json`(其他操作系统)中修改当前关卡:
* 冒险模式:
* 白昼模式——单行草皮1
* 白昼模式——三行草皮2
@ -100,11 +86,10 @@ python pypvz.py
* 泳池模式9~11
* 浓雾模式暂时没有雾12
* 小游戏模式:
* 坚果保龄球1
* 坚果保龄球模式1
* 传送带模式白天2
* 传送带模式黑夜3
* 传送带模式泳池4
* 坚果保龄球(II)5
* 目前暂时按照以上设定,未与原版相符
* 可以通过修改存档JSON文件中的`game rate`值来调节游戏速度倍率
@ -213,6 +198,7 @@ pyinstaller -F pypvz.py `
* 魅惑的僵尸未用红色滤镜标识
* 这个可能会作为一种“特性”
* 南瓜头显示不正常
* 对于部分“长得比较长”的植物甚至可以在南瓜头存在的情况下优先被啃食
* 墓碑吞噬者吞噬墓碑过程中被吞噬的墓碑顶端不会消失
**欢迎提供[Pull requests](https://github.com/wszqkzqk/pypvz/pulls)或修复方法建议也欢迎在这里反馈新的bug()**
@ -305,34 +291,32 @@ pyinstaller -F pypvz.py `
## 截屏
![截屏1](/screenshots/screenshot-1.webp)
![截屏2](/screenshots/screenshot-2.webp)
![截屏3](/screenshots/screenshot-3.webp)
![截屏4](/screenshots/screenshot-4.webp)
![截屏5](/screenshots/screenshot-5.webp)
![截屏6](/screenshots/screenshot-6.webp)
![截屏7](/screenshots/screenshot-7.webp)
![截屏8](/screenshots/screenshot-8.webp)
![截屏9](/screenshots/screenshot-9.webp)
![截屏10](/screenshots/screenshot-10.webp)
![截屏11](/screenshots/screenshot-11.webp)
![截屏12](/screenshots/screenshot-12.webp)
![截屏13](/screenshots/screenshot-13.webp)
![截屏14](/screenshots/screenshot-14.webp)
![截屏15](/screenshots/screenshot-15.webp)
![截屏16](/screenshots/screenshot-16.webp)
![截屏17](/screenshots/screenshot-17.webp)
![截屏18](/screenshots/screenshot-18.webp)
![截屏19](/screenshots/screenshot-19.webp)
![截屏20](/screenshots/screenshot-20.webp)
![截屏21](/screenshots/screenshot-21.webp)
![截屏22](/screenshots/screenshot-22.webp)
![截屏23](/screenshots/screenshot-23.webp)
![截屏1](/demo/demo1.webp)
![截屏2](/demo/demo2.webp)
![截屏3](/demo/demo3.webp)
![截屏4](/demo/demo4.webp)
![截屏5](/demo/demo5.webp)
![截屏6](/demo/demo6.webp)
![截屏7](/demo/demo7.webp)
![截屏8](/demo/demo8.webp)
![截屏9](/demo/demo9.webp)
![截屏10](/demo/demo10.webp)
![截屏11](/demo/demo11.webp)
![截屏12](/demo/demo12.webp)
![截屏13](/demo/demo13.webp)
![截屏14](/demo/demo14.webp)
![截屏15](/demo/demo15.webp)
![截屏16](/demo/demo16.webp)
![截屏17](/demo/demo17.webp)
![截屏18](/demo/demo18.webp)
![截屏19](/demo/demo19.webp)
![截屏20](/demo/demo20.webp)
![截屏21](/demo/demo21.webp)
## 关于日志与反馈
对于闪退情况Linux用户与Windows下的python源代码运行用户可以直接在终端中复制出崩溃日志进行反馈。
Windows单文件封装版本无法通过终端显示日志需要在日志文件中寻找崩溃原因
* Windows默认日志文件路径为`~\AppData\Roaming\pypvz\run.log`
* 其他操作系统为`~/.config/pypvz/run.log`,但一般可以在终端中显示时用终端中的输出即可
* Windows默认日志文件路径为`~\AppData\Roaming\wszqkzqk.dev\pypvz\run.log`
* 其他操作系统为`~/.config/wszqkzqk.dev/pypvz/run.log`,但一般可以在终端中显示时用终端中的输出即可

View File

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

View File

Before

Width:  |  Height:  |  Size: 124 KiB

After

Width:  |  Height:  |  Size: 124 KiB

View File

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 95 KiB

View File

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 116 KiB

View File

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 87 KiB

View File

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 69 KiB

View File

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 78 KiB

View File

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

View File

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

View File

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

View File

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 75 KiB

View File

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 80 KiB

View File

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 65 KiB

View File

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

View File

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 81 KiB

View File

Before

Width:  |  Height:  |  Size: 125 KiB

After

Width:  |  Height:  |  Size: 125 KiB

View File

Before

Width:  |  Height:  |  Size: 143 KiB

After

Width:  |  Height:  |  Size: 143 KiB

View File

Before

Width:  |  Height:  |  Size: 164 KiB

After

Width:  |  Height:  |  Size: 164 KiB

View File

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 96 KiB

View File

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 112 KiB

View File

Before

Width:  |  Height:  |  Size: 119 KiB

After

Width:  |  Height:  |  Size: 119 KiB

3
git-pull.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/bash
cd `dirname $0`; pwd
git pull git@github.com:wszqkzqk/pypvz.git

View File

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

View File

@ -1,32 +1,23 @@
#!/usr/bin/env python
import logging
import os
import traceback
from logging.handlers import RotatingFileHandler
import os
import pygame as pg
from logging.handlers import RotatingFileHandler
# 由于在后续本地模块中存在对pygame的调用在此处必须完成pygame的初始化
os.environ[
'SDL_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR'
] = '0' # 设置临时环境变量以避免Linux下禁用x11合成器
pg.init()
from source import constants as c
from source import tool
from source.state import level, mainmenu, screen
from source import constants as c
from source.state import mainmenu, screen, level
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', 1_000_000, 0, 'utf-8'
)
# 设置日志文件权限Unix为644Windows为可读写Python的os.chmod与Unix chmod相同但要显式说明8进制
os.chmod(c.USERLOG_PATH, 0o644)
logger = logging.getLogger("main")
formatter = logging.Formatter("%(asctime)s - %(levelname)s: %(message)s")
fileHandler = RotatingFileHandler(c.USERLOG_PATH, "a", 1024*1024, 0, "utf-8")
fileHandler.setFormatter(formatter)
streamHandler = logging.StreamHandler()
streamHandler.setFormatter(formatter)
@ -36,16 +27,14 @@ 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(),
}
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()}")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

View File

@ -1,184 +1,110 @@
import random
from .. import constants as c
# 记录植物种植情况的地图管理工具
class Map:
def __init__(self, background_type: int):
class Map():
def __init__(self, background_type):
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.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)
]
self.gridHeightSize = c.GRID_POOL_Y_SIZE
self.map = [[(c.INIT_MAP_GRID(c.MAP_GRASS), c.INIT_MAP_GRID(c.MAP_WATER))[y in {2, 3}] 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.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)
]
self.gridHeightSize = c.GRID_ROOF_Y_SIZE
self.map = [[c.INIT_MAP_GRID(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.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)
]
self.gridHeightSize = c.GRID_Y_SIZE
self.map = [[(c.INIT_MAP_GRID(c.MAP_UNAVAILABLE), c.INIT_MAP_GRID(c.MAP_GRASS))[y == 2] 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.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)
]
self.gridHeightSize = c.GRID_Y_SIZE
self.map = [[(c.INIT_MAP_GRID(c.MAP_UNAVAILABLE), c.INIT_MAP_GRID(c.MAP_GRASS))[y in {1, 2, 3}] 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.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)
]
self.gridHeightSize = c.GRID_Y_SIZE
self.map = [[c.INIT_MAP_GRID(c.MAP_GRASS) for x in range(self.width)] for y in range(self.height)]
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: str) -> set:
return {
c.MAP_PLANT: set(),
c.MAP_SLEEP: False,
c.MAP_PLOT_TYPE: plot_type,
}
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 isAvailable(self, map_x: int, map_y: int, plant_name: str) -> bool:
def isAvailable(self, map_x, map_y, plantName):
# 咖啡豆和墓碑吞噬者的判别最为特殊
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]
):
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]):
return True
else:
return False
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]
):
if plantName == c.GRAVEBUSTER:
if (c.GRAVE 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 plant_name not in c.WATER_PLANTS:
if not self.map[map_y][map_x][c.MAP_PLANT]: # 没有植物肯定可以种植
if plantName 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 (
plant_name 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 (plantName not in self.map[map_y][map_x][c.MAP_PLANT])): # 例外植物:集合中填花盆和南瓜头,只要这里没有这种植物就能种植
return True
elif (plant_name == c.PUMPKINHEAD) and (
c.PUMPKINHEAD not in self.map[map_y][map_x][c.MAP_PLANT]
): # 没有南瓜头就能种南瓜头
elif (plantName == 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 plant_name not in c.WATER_PLANTS:
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 (
plant_name not in self.map[map_y][map_x][c.MAP_PLANT]
): # 例外植物:集合中填花盆和南瓜头,只要这里没有这种植物就能种植
if plant_name in {c.SPIKEWEED}: # 不能在花盆上种植的植物
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}: # 不能在花盆上种植的植物
return False
else:
return True
elif (plant_name == c.PUMPKINHEAD) and (
c.PUMPKINHEAD
not in self.map[map_y][map_x][c.MAP_PLANT]
): # 有花盆且没有南瓜头就能种南瓜头
elif (plantName == c.PUMPKINHEAD) and (c.PUMPKINHEAD not in self.map[map_y][map_x][c.MAP_PLANT]): # 有花盆且没有南瓜头就能种南瓜头
return True
else:
return False
elif plant_name == '花盆(未实现)': # 这一格本来没有花盆而且新来的植物是花盆,可以种
elif plantName == '花盆(未实现)': # 这一格本来没有花盆而且新来的植物是花盆,可以种
return True
else:
return False
else:
return False
elif self.map[map_y][map_x][c.MAP_PLOT_TYPE] == c.MAP_WATER: # 水里
if plant_name in c.WATER_PLANTS: # 是水生植物
if not self.map[map_y][map_x][
c.MAP_PLANT
]: # 只有无植物时才能在水里种植水生植物
if plantName 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 (
plant_name not in self.map[map_y][map_x][c.MAP_PLANT]
):
if plant_name 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 (plantName not in self.map[map_y][map_x][c.MAP_PLANT])):
if plantName in {c.SPIKEWEED, c.POTATOMINE, '花盆(未实现)'}: # 不能在睡莲上种植的植物
return False
else:
return True
elif (plant_name == c.PUMPKINHEAD) and (
c.PUMPKINHEAD
not in self.map[map_y][map_x][c.MAP_PLANT]
): # 在睡莲上且没有南瓜头就能种南瓜头
elif (plantName == c.PUMPKINHEAD) and (c.PUMPKINHEAD not in self.map[map_y][map_x][c.MAP_PLANT]): # 在睡莲上且没有南瓜头就能种南瓜头
return True
else:
return False
@ -186,8 +112,8 @@ class Map:
return False
else: # 不可种植区域
return False
def getMapIndex(self, x: int, y: int) -> tuple[int, int]:
def getMapIndex(self, x, y):
if self.background_type in c.POOL_EQUIPPED_BACKGROUNDS:
x -= c.MAP_POOL_OFFSET_X
y -= c.MAP_POOL_OFFSET_Y
@ -195,431 +121,293 @@ class Map:
elif self.background_type in c.ON_ROOF_BACKGROUNDS:
x -= c.MAP_ROOF_OFFSET_X
y -= c.MAP_ROOF_OFFSET_X
grid_x = x // c.GRID_ROOF_X_SIZE
if grid_x >= 5:
grid_y = y // c.GRID_ROOF_Y_SIZE
gridX = x // c.GRID_ROOF_X_SIZE
if gridX >= 5:
gridY = y // c.GRID_ROOF_Y_SIZE
else:
grid_y = (y - 20 * (6 - grid_x)) // 85
return (grid_x, grid_y)
gridY = (y - 20*(6 - gridX)) // 85
return (gridX, gridY)
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: int, map_y: int) -> tuple[int, int]:
def getMapGridPos(self, map_x, map_y):
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: int, map_y: int, plot_type: str):
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):
self.map[map_y][map_x][c.MAP_PLOT_TYPE] = plot_type
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)
def addMapPlant(self, map_x, map_y, plantName, sleep=False):
self.map[map_y][map_x][c.MAP_PLANT].add(plantName)
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 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)
def getRandomMapIndex(self):
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: int, y: int, plant_name: str
) -> tuple[int, int]:
def checkPlantToSeed(self, x, y, plantName):
pos = None
map_x, map_y = self.getMapIndex(x, y)
if self.isValid(map_x, map_y) and self.isAvailable(
map_x, map_y, plant_name
):
if self.isValid(map_x, map_y) and self.isAvailable(map_x, map_y, plantName):
pos = self.getMapGridPos(map_x, map_y)
return pos
# 保存具体关卡地图信息常数
# 冒险模式地图
LEVEL_MAP_DATA = (
# 第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,
},
# 第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
},
)
# 玩玩小游戏地图
LITTLE_GAME_MAP_DATA = (
# 第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,
},
},
# 第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,}
},
)
# 总关卡数

View File

@ -1,9 +1,7 @@
import random
import pygame as pg
from .. import constants as c
from .. import tool
from .. import constants as c
def getSunValueImage(sun_value):
@ -23,83 +21,50 @@ def getSunValueImage(sun_value):
image.set_colorkey(c.BLACK)
return image
def getCardPool(data):
card_pool = {
c.PLANT_CARD_INFO[c.PLANT_CARD_INDEX[card_name]]: data[card_name]
for card_name in data
}
card_pool = {}
for cardName in data:
card_pool[c.PLANT_CARD_INFO[c.PLANT_CARD_INDEX[cardName]]] = data[cardName]
return card_pool
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)
class Card():
def __init__(self, x, y, index, scale=0.5):
self.loadFrame(c.PLANT_CARD_INFO[index][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(self.info[c.SUN_INDEX]), True, c.BLACK
)
self.sun_cost_img = font.render(str(c.PLANT_CARD_INFO[index][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 = self.info[c.SUN_INDEX]
self.frozen_time = self.info[c.FROZEN_TIME_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.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 (
self.rect.x <= x <= self.rect.right
and self.rect.y <= y <= self.rect.bottom
):
if(x >= self.rect.x and x <= self.rect.right and
y >= self.rect.y and 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
@ -109,21 +74,9 @@ class Card:
def setSelect(self, can_select):
self.select = can_select
if can_select:
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)
self.image.set_alpha(255)
else:
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)
)
self.image.set_alpha(128)
def setFrozenTime(self, current_time):
self.frozen_timer = current_time
@ -131,38 +84,23 @@ 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
if time < self.frozen_time: #cool down status
image = pg.Surface([self.rect.w, self.rect.h])
frozen_image = self.orig_image.copy()
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)
)
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)
)
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)
elif self.clicked:
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))
image = self.orig_image.copy()
image.set_alpha(128)
else:
image = self.orig_image
image.set_alpha(255)
return image
def update(self, sun_value, current_time):
@ -173,15 +111,14 @@ 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)
@ -211,9 +148,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
@ -227,22 +164,17 @@ 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 (
self.rect.x <= x <= self.rect.right
and self.rect.y <= y <= self.rect.bottom
):
if (x >= self.rect.x and x <= self.rect.right and
y >= self.rect.y and y <= self.rect.bottom):
return True
return False
@ -265,7 +197,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):
@ -274,14 +206,12 @@ class MenuBar:
for card in self.card_list:
card.draw(surface)
# 关卡模式选植物的界面
class Panel:
def __init__(self, card_list, sun_value, background_type=c.BACKGROUND_DAY):
class Panel():
def __init__(self, card_list, sun_value):
self.loadImages(sun_value)
self.selected_cards = []
self.selected_num = 0
self.background_type = background_type
self.setupCards(card_list)
def loadFrame(self, name):
@ -302,12 +232,13 @@ 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
@ -321,36 +252,12 @@ class Panel:
x = c.PANEL_X_START - c.PANEL_X_INTERNAL
y += c.PANEL_Y_INTERNAL
x += c.PANEL_X_INTERNAL
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))
self.card_list.append(Card(x, y, index, 0.5))
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)
@ -361,15 +268,6 @@ 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
@ -380,17 +278,9 @@ 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: Card):
def addCard(self, card):
card.setSelect(False)
y = 8
x = 77 + self.selected_num * c.BAR_CARD_X_INTERNAL
@ -405,11 +295,9 @@ class Panel:
return False
x, y = mouse_pos
if (
self.button_rect.x <= x <= self.button_rect.right
and self.button_rect.y <= y <= self.button_rect.bottom
):
return True
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
return False
def getSelectedCards(self):
@ -430,9 +318,8 @@ 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()
@ -452,44 +339,29 @@ 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 (
self.rect.x <= x <= self.rect.right
and self.rect.y <= y <= self.rect.bottom
):
if (x >= self.rect.x and x <= self.rect.right and
y >= self.rect.y and 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])
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)
)
image.blit(self.orig_image, (0, 0), (0, 0, self.rect.w, self.rect.h))
self.rect.w += 1
else:
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
image = self.orig_image
if self.clicked:
image.set_alpha(192)
else:
image.set_alpha(255)
return image
def update(self, left_x, current_time):
@ -504,15 +376,14 @@ 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
@ -529,24 +400,12 @@ 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):
@ -556,7 +415,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
@ -567,13 +426,11 @@ class MoveBar:
result = (card.plant_name, card)
break
return result
def checkMenuBarClick(self, mouse_pos):
x, y = mouse_pos
if (
self.rect.x <= x <= self.rect.right
and self.rect.y <= y <= self.rect.bottom
):
if (x >= self.rect.x and x <= self.rect.right and
y >= self.rect.y and y <= self.rect.bottom):
return True
return False

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,16 +1,15 @@
import os
import pygame as pg
from .. import constants as c
import os
import json
from .. import tool
from .. import constants as c
class Menu(tool.State):
def __init__(self):
tool.State.__init__(self)
def startup(self, current_time: int, persist):
def startup(self, current_time, persist):
self.next = c.LEVEL
self.persist = persist
self.game_info = persist
@ -19,64 +18,52 @@ 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])
for i in c.SOUNDS:
i.set_volume(self.game_info[c.SOUND_VOLUME])
def saveUserData(self):
with open(c.USERDATA_PATH, "w") as f:
userdata = {}
for i in self.game_info:
if i in c.INIT_USERDATA:
userdata[i] = self.game_info[i]
dataToSave = json.dumps(userdata, sort_keys=True, indent=4)
f.write(dataToSave)
def setupBackground(self):
frame_rect = (80, 0, 800, 600)
# 1、形参中加单星号即f(*x)则表示x为元组所有对x的操作都应将x视为元组类型进行。
# 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
def setupOptions(self):
# 冒险模式
self.adventure_frames = []
frame_names = (f'{c.OPTION_ADVENTURE}_0', f'{c.OPTION_ADVENTURE}_1')
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)
]
for name in frame_names:
self.adventure_frames.append(tool.get_image_menu(tool.GFX[name], *frame_rect, c.BLACK, 1))
self.adventure_image = self.adventure_frames[0]
self.adventure_rect = self.adventure_image.get_rect()
self.adventure_rect.x = 400
self.adventure_rect.y = 60
self.adventure_highlight_time = 0
# 小游戏
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_image = self.littleGame_frames[0]
self.littleGame_rect = self.littleGame_image.get_rect()
self.littleGame_rect.x = 397
self.littleGame_rect.y = 175
self.littleGame_highlight_time = 0
# 退出按钮
self.exit_frames = []
exit_frame_names = (f'{c.EXIT}_0', f'{c.EXIT}_1')
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)
]
for name in exit_frame_names:
self.exit_frames.append(tool.get_image_menu(tool.GFX[name], *exit_frame_rect, c.BLACK, 1.1))
self.exit_image = self.exit_frames[0]
self.exit_rect = self.exit_image.get_rect()
self.exit_rect.x = 730
@ -84,110 +71,98 @@ class Menu(tool.State):
self.exit_highlight_time = 0
# 选项按钮
self.option_button_frames = []
option_button_frame_names = (f'{c.OPTION_BUTTON}_0', f'{c.OPTION_BUTTON}_1')
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)
]
for name in option_button_frame_names:
self.option_button_frames.append(tool.get_image_menu(tool.GFX[name], *option_button_frame_rect, c.BLACK))
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
self.option_button_rect.y = 490
self.option_button_highlight_time = 0
self.option_button_hightlight_time = 0
# 帮助菜单
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_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.littleGame_frames = []
littleGame_frame_names = (c.LITTLEGAME_BUTTON + '_0', c.LITTLEGAME_BUTTON + '_1')
littleGame_frame_rect = (0, 7, 317, 135)
for name in littleGame_frame_names:
self.littleGame_frames.append(tool.get_image_menu(tool.GFX[name], *littleGame_frame_rect, c.BLACK, 1))
self.littleGame_image = self.littleGame_frames[0]
self.littleGame_rect = self.littleGame_image.get_rect()
self.littleGame_rect.x = 397
self.littleGame_rect.y = 175
self.littleGame_highlight_time = 0
# 计时器与点击信号记录器
self.adventure_start = 0
self.adventure_timer = 0
self.adventure_clicked = False
self.option_button_clicked = False
def checkHilight(self, x: int, y: int):
def inArea(self, rect, x, y):
if (x >= rect.x and x <= rect.right and
y >= rect.y and y <= rect.bottom):
return True
else:
return False
def checkHilight(self, x, y):
# 高亮冒险模式按钮
if self.inArea(self.adventure_rect, x, y):
self.adventure_highlight_time = self.current_time
# 高亮小游戏按钮
elif self.inArea(self.littleGame_rect, x, y):
self.littleGame_highlight_time = self.current_time
# 高亮退出按钮
elif self.inArea(self.exit_rect, x, y):
self.exit_highlight_time = self.current_time
# 高亮选项按钮
elif self.inArea(self.option_button_rect, x, y):
self.option_button_highlight_time = self.current_time
# 高亮帮助按钮
elif self.inArea(self.help_rect, x, y):
self.help_hilight_time = self.current_time
self.option_button_hightlight_time = self.current_time
# 高亮小游戏按钮
elif self.inArea(self.littleGame_rect, x, y):
self.littleGame_highlight_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_hightlight_time, self.option_button_frames)
self.littleGame_image = self.chooseHilightImage(self.littleGame_highlight_time, self.littleGame_frames)
def chooseHilightImage(self, hilightTime: int, frames):
def chooseHilightImage(self, hilightTime, frames):
if (self.current_time - hilightTime) < 80:
index = 1
index= 1
else:
index = 0
return frames[index]
def respondAdventureClick(self):
self.adventure_clicked = True
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()
def checkAdventureClick(self, mouse_pos):
x, y = mouse_pos
if self.inArea(self.adventure_rect, x, y):
self.adventure_clicked = True
self.adventure_timer = self.adventure_start = self.current_time
self.persist[c.GAME_MODE] = c.MODE_ADVENTURE
# 播放进入音效
c.SOUND_EVILLAUGH.play()
c.SOUND_LOSE.play()
# 点击到按钮修改转态的done属性
def checkExitClick(self, mouse_pos):
x, y = mouse_pos
if self.inArea(self.exit_rect, x, y):
self.done = True
self.next = c.EXIT
# 按到小游戏
def respondLittleGameClick(self):
self.done = True
self.persist[c.GAME_MODE] = c.MODE_LITTLEGAME
# 播放点击音效
c.SOUND_BUTTON_CLICK.play()
# 点击到退出按钮修改转态的done属性
def respondExitClick(self):
self.done = True
self.next = c.EXIT
# 帮助按钮点击
def respondHelpClick(self):
self.done = True
self.next = c.HELP_SCREEN
# 检查有没有按到小游戏
def checkLittleGameClick(self, mouse_pos):
x, y = mouse_pos
if self.inArea(self.littleGame_rect, x, y):
self.done = True
self.persist[c.GAME_MODE] = c.MODE_LITTLEGAME
# 播放点击音效
c.SOUND_BUTTON_CLICK.play()
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_menu(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
@ -201,7 +176,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
@ -212,114 +187,81 @@ 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_menu(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_menu(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_menu(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: pg.Surface, x: int, y: int):
def checkSunflowerTrophyInfo(self, surface, x, y):
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 = self.sunflower_trophy_rect.x
infoImg_rect.y = self.sunflower_trophy_rect.bottom - 14
infoImg_rect.x = x
infoImg_rect.y = y
surface.blit(infoImg, infoImg_rect)
def respondOptionButtonClick(self):
self.option_button_clicked = True
# 播放点击音效
c.SOUND_BUTTON_CLICK.play()
def checkOptionButtonClick(self, mouse_pos):
x, y = mouse_pos
if self.inArea(self.option_button_rect, x, y):
self.option_button_clicked = True
# 播放点击音效
c.SOUND_BUTTON_CLICK.play()
def showCurrentVolumeImage(self, surface: pg.Surface):
def showCurrentVolumeImage(self, 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: pg.Surface,
current_time: int,
mouse_pos: list,
mouse_click,
):
def update(self, surface, current_time, mouse_pos, 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]
):
surface.blit(self.littleGame_image, self.littleGame_rect)
if self.game_info[c.LEVEL_COMPLETIONS] or self.game_info[c.LITTLEGAME_COMPLETIONS]:
surface.blit(self.sunflower_trophy, self.sunflower_trophy_rect)
# 点到冒险模式后播放动画
@ -335,14 +277,8 @@ 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:
# 返回
@ -350,12 +286,8 @@ 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:
@ -363,12 +295,8 @@ 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:
@ -380,19 +308,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):
self.respondAdventureClick()
elif self.inArea(self.littleGame_rect, *mouse_pos):
self.respondLittleGameClick()
elif self.inArea(self.option_button_rect, *mouse_pos):
self.respondOptionButtonClick()
elif self.inArea(self.exit_rect, *mouse_pos):
self.respondExitClick()
elif self.inArea(self.help_rect, *mouse_pos):
self.respondHelpClick()
self.checkExitClick(mouse_pos)
self.checkOptionButtonClick(mouse_pos)
self.checkLittleGameClick(mouse_pos)
self.checkAdventureClick(mouse_pos)

View File

@ -1,118 +1,79 @@
import os
from abc import abstractmethod
import pygame as pg
from .. import constants as c
from .. import tool
from .. import constants as c
class Screen(tool.State):
def __init__(self):
tool.State.__init__(self)
self.end_time = 3000
@abstractmethod
def startup(self, current_time, persist):
pass
def getImageName(self):
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
)
def set_next_state(self):
pass
def setupImage(self, name, frame_rect=(0, 0, 800, 600)):
self.image = tool.get_image(tool.GFX[name], *frame_rect)
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_rect.x = 620
### 主菜单按钮上的文字
font = pg.font.Font(c.FONT_PATH, 18)
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_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_rect = next_text.get_rect()
next_text_rect.x = 29
self.next_button_image_rect.y = (
self.main_menu_button_image_rect.y
) = 555
else:
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.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
)
def update(self, surface, current_time, mouse_pos, mouse_click):
surface.fill(c.WHITE)
surface.blit(self.image, self.rect)
if mouse_pos:
# 点到继续
if self.inArea(self.next_button_image_rect, *mouse_pos):
self.next = c.LEVEL
self.done = True
# 点到主菜单
elif self.inArea(self.main_menu_button_image_rect, *mouse_pos):
self.next = c.MAIN_MENU
self.done = True
if (current_time - self.start_time) < self.end_time:
surface.fill(c.WHITE)
surface.blit(self.image, self.rect)
else:
self.done = True
class GameVictoryScreen(Screen):
def __init__(self):
Screen.__init__(self)
self.image_name = c.GAME_VICTORY_IMAGE
def getImageName(self):
return c.GAME_VICTORY_IMAGE
def set_next_state(self):
return c.LEVEL
def startup(self, current_time, persist):
self.start_time = current_time
self.next = c.LEVEL
self.persist = persist
self.game_info = persist
self.setupImage(self.image_name)
pg.display.set_caption('pypvz: 战斗胜利!')
name = self.getImageName()
self.setupImage(name)
self.next = self.set_next_state()
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.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 getImageName(self):
return c.GAME_LOSE_IMAGE
def set_next_state(self):
return c.LEVEL
def startup(self, current_time, persist):
self.start_time = current_time
self.next = c.LEVEL
self.persist = persist
self.game_info = persist
self.setupImage(self.image_name, (-118, -40, 800, 600), c.WHITE)
pg.display.set_caption('pypvz: 战斗失败!')
name = self.getImageName()
self.setupImage(name, (-15, 0, 800, 600))
self.next = self.set_next_state()
pg.display.set_caption("pypvz: 战斗失败!")
# 停止播放原来关卡中的音乐
pg.mixer.music.stop()
class AwardScreen(tool.State):
def __init__(self):
tool.State.__init__(self)
@ -120,9 +81,7 @@ 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
@ -130,54 +89,66 @@ 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_menu(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)
else:
## 继续按钮
self.next_button_image = tool.get_image_menu(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_rect = next_text.get_rect()
next_text_rect.x = 37
## 主菜单按钮
self.main_menu_button_image = tool.get_image_menu(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
### 主菜单按钮上的文字
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)
# 显示向日葵奖杯的情况
if self.show_only_one_option:
# 绘制向日葵奖杯
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_menu(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
@ -185,9 +156,7 @@ 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
@ -200,62 +169,25 @@ class AwardScreen(tool.State):
intro_content_rect.x = 290
intro_content_rect.y = 370
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_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_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_rect.x = 620
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_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
)
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])
def update(self, surface, current_time, mouse_pos, mouse_click):
surface.fill(c.WHITE)
surface.blit(self.image, self.rect)
if mouse_pos:
# 检查主菜单点击
@ -267,55 +199,9 @@ class AwardScreen(tool.State):
self.next = c.LEVEL
self.done = True
class HelpScreen(tool.State):
def __init__(self):
tool.State.__init__(self)
def startup(self, current_time, persist):
self.start_time = current_time
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.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_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_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
)
def update(self, surface, current_time, mouse_pos, mouse_click):
surface.fill(c.BLACK)
surface.blit(self.image, self.rect)
if mouse_pos:
# 检查主菜单点击
if self.inArea(self.main_menu_button_image_rect, *mouse_pos):
self.next = c.MAIN_MENU
self.done = True
def inArea(self, rect, x, y):
if (x >= rect.x and x <= rect.right and
y >= rect.y and y <= rect.bottom):
return True
else:
return False

View File

@ -1,17 +1,12 @@
import json
import logging
import os
import json
from abc import abstractmethod
import pygame as pg
from pygame.locals import *
from . import constants as c
logger = logging.getLogger('main')
# 状态机 抽象基类
class State:
# an abstract class, one state of automata
class State():
def __init__(self):
self.start_time = 0
self.current_time = 0
@ -21,67 +16,36 @@ class State:
# 当从其他状态进入这个状态时,需要进行的初始化操作
@abstractmethod
def startup(self, current_time: int, persist: dict):
# 前面加了@abstractmethod表示抽象基类中必须要重新定义的methodmethod是对象和函数的结合
def startup(self, current_time, persist):
# abstract method
pass
# 当从这个状态退出时,需要进行的清除操作
def cleanup(self):
self.done = False
return self.persist
# 在这个状态运行时进行的更新操作
@abstractmethod
def update(self, surface: pg.Surface, keys, current_time: int):
# 前面加了@abstractmethod表示抽象基类中必须要重新定义的method
def update(self, surface, keys, current_time):
# abstract method
pass
# 工具:范围判断函数,用于判断点击
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', encoding='utf-8') as f:
userdata = {}
for i in self.game_info:
if i in c.INIT_USERDATA:
userdata[i] = self.game_info[i]
data_to_save = json.dumps(userdata, sort_keys=True, indent=4)
f.write(data_to_save)
# 进行游戏控制 循环 事件响应
class Control:
# control this game. do event loops
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
try:
# 存在存档即导入
# 先自动修复读写权限(Python权限规则和Unix不一样420表示unix的644Windows自动忽略不支持项)
os.chmod(c.USERDATA_PATH, 420)
with open(c.USERDATA_PATH, encoding='utf-8') as f:
with open(c.USERDATA_PATH) 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
@ -92,41 +56,37 @@ class Control:
self.game_info[key] = c.INIT_USERDATA[key]
need_to_rewrite = True
if need_to_rewrite:
with open(c.USERDATA_PATH, 'w', encoding='utf-8') as f:
savedata = json.dumps(
self.game_info, sort_keys=True, indent=4
)
with open(c.USERDATA_PATH, "w") as f:
savedata = json.dumps(self.game_info, sort_keys=True, indent=4)
f.write(savedata)
except: # 这里需要考虑多种情况如文件不存在、文件不可读、文件不符合JSON语法要求这些情况目前暂定统一进行新建文件操作
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 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):
def setup_states(self, state_dict, start_state):
self.state_dict = state_dict
self.state_name = start_state
self.state = self.state_dict[self.state_name]
self.state.startup(self.current_time, self.game_info)
def update(self):
# 自 pygame_init() 调用以来的毫秒数 * 游戏速度倍率,即游戏时间
# 返回自 pygame_init() 调用以来的毫秒数 * 游戏速度倍率
self.current_time = pg.time.get_ticks() * self.game_info[c.GAME_RATE]
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
@ -136,6 +96,7 @@ class Control:
if self.state.next == c.EXIT:
pg.quit()
os._exit(0)
# previous, self.state_name = self.state_name, self.state.next
self.state_name = self.state.next
persist = self.state.cleanup()
self.state = self.state_dict[self.state_name]
@ -148,24 +109,18 @@ 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}) 左右键点击情况: {self.mouse_click}'
)
print( f"点击位置: ({self.mouse_pos[0]:3}, {self.mouse_pos[1]:3})",
f"左右键点击情况: {self.mouse_click}")
def run(self):
while not self.done:
@ -174,57 +129,36 @@ 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()
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()
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
image.blit(sheet, (0, 0), (x, y, width, height))
if colorkey:
def get_image_menu(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.set_colorkey(colorkey)
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]:
image = pg.transform.scale(image,
(int(rect.width*scale),
int(rect.height*scale)))
return image
def load_image_frames(directory, image_name, colorkey, accept):
frame_list = []
tmp = {}
# image_name is "Peashooter", pic name is "Peashooter_1", get the index 1
index_start = len(image_name) + 1
# image_name is "Peashooter", pic name is 'Peashooter_1', get the index 1
index_start = len(image_name) + 1
frame_num = 0
for pic in os.listdir(directory):
name, ext = os.path.splitext(pic)
@ -236,20 +170,15 @@ def load_image_frames(
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: str,
colorkey: tuple[int] = c.WHITE,
accept: tuple[str] = ('.png', '.jpg', '.bmp', '.gif', '.webp'),
) -> dict[str : pg.Surface]:
def load_all_gfx(directory, colorkey=c.WHITE, accept=('.png', '.jpg', '.bmp', '.gif', 'webp')):
graphics = {}
for name1 in os.listdir(directory):
# subfolders under the folder resources\graphics
@ -258,25 +187,21 @@ def load_all_gfx(
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
# 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)
@ -288,13 +213,10 @@ def load_all_gfx(
graphics[name] = img
return graphics
pg.display.set_caption(c.ORIGINAL_CAPTION) # 设置标题
SCREEN = pg.display.set_mode(c.SCREEN_SIZE, pg.SCALED) # 设置初始屏幕
SCREEN = pg.display.set_mode(c.SCREEN_SIZE) # 设置初始屏幕
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)

7
update.sh Executable file
View File

@ -0,0 +1,7 @@
#!/bin/bash
cd `dirname $0`; pwd
git add *
git add .gitignore
git commit -m 'Updated by update.sh'
git push -u git@github.com:wszqkzqk/pypvz.git

209
uv.lock generated
View File

@ -1,209 +0,0 @@
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" },
]