Compare commits

...

4 Commits

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

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

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

Signed-off-by: Zhou Qiankang <wszqkzqk@qq.com>
2025-06-12 23:34:52 +08:00
17 changed files with 3603 additions and 1728 deletions

View File

@ -14,7 +14,7 @@ jobs:
fail-fast: false
matrix:
python_version:
- "3.11"
- "3.12"
name: Windows Python ${{ matrix.python_version }}
steps:
- uses: actions/checkout@v2
@ -46,6 +46,7 @@ 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
@ -77,6 +78,7 @@ jobs:
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
with:
allowUpdates: true
@ -91,7 +93,7 @@ jobs:
fail-fast: false
matrix:
python_version:
- "3.11"
- "3.12"
name: Ubuntu Python ${{ matrix.python_version }}
steps:
- name: 🛎️ Checkout
@ -131,6 +133,7 @@ jobs:
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
with:
allowUpdates: true

View File

@ -16,7 +16,7 @@ jobs:
fail-fast: false
matrix:
python_version:
- "3.11"
- "3.12"
name: Windows Python ${{ matrix.python_version }}
steps:
- uses: actions/checkout@v2
@ -93,7 +93,7 @@ jobs:
fail-fast: false
matrix:
python_version:
- "3.11"
- "3.12"
name: Ubuntu Python ${{ matrix.python_version }}
steps:
- name: 🛎️ Checkout

4
.gitignore vendored
View File

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

1
.python-version Normal file
View File

@ -0,0 +1 @@
3.12

View File

@ -38,7 +38,17 @@
* 光标移动到向日葵奖杯上是显示当前各个模式通关次数
* 含有游戏帮助界面 QwQ
## 环境要求
## 环境安装
建议使用 [uv](https://docs.astral.sh/uv/) 安装依赖:
```bash
git clone https://github.com/wszqkzqk/pypvz.git
cd pypvz
uv sync
```
或者参考:
* `Python3` (建议 >= 3.10,最好使用最新版)
* `Python-Pygame` (建议 >= 2.0,最好使用最新版)

12
pyproject.toml Normal file
View File

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

View File

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

View File

@ -1,8 +1,10 @@
import random
from .. import constants as c
# 记录植物种植情况的地图管理工具
class Map():
class Map:
def __init__(self, background_type: int):
self.background_type = background_type
# 注意从0开始编号
@ -10,7 +12,10 @@ class Map():
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
self.map = [
[
self.initMapGrid(c.MAP_WATER)
if 2 <= y <= 3
else self.initMapGrid(c.MAP_GRASS)
for x in range(self.width)
]
@ -20,16 +25,18 @@ class Map():
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)
]
self.map = [
[self.initMapGrid(c.MAP_TILE) for x in range(self.width)]
for y in range(self.height)
]
elif self.background_type == c.BACKGROUND_SINGLE:
self.width = c.GRID_X_LEN
self.height = c.GRID_Y_LEN
self.grid_height_size = c.GRID_Y_SIZE
self.map = [ [self.initMapGrid(c.MAP_GRASS) if y ==2
self.map = [
[
self.initMapGrid(c.MAP_GRASS)
if y == 2
else self.initMapGrid(c.MAP_UNAVAILABLE)
for x in range(self.width)
]
@ -39,7 +46,10 @@ class Map():
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
self.map = [
[
self.initMapGrid(c.MAP_GRASS)
if 1 <= y <= 3
else self.initMapGrid(c.MAP_UNAVAILABLE)
for x in range(self.width)
]
@ -49,15 +59,13 @@ class Map():
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)
]
self.map = [
[self.initMapGrid(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)):
if (0 <= map_x < self.width) and (0 <= map_y < self.height):
return True
return False
@ -65,7 +73,11 @@ class Map():
# 注意是可变对象,不能直接引用
# 由于同一格显然不可能种两个相同的植物,所以用集合
def initMapGrid(self, plot_type: str) -> set:
return {c.MAP_PLANT:set(), c.MAP_SLEEP:False, c.MAP_PLOT_TYPE:plot_type}
return {
c.MAP_PLANT: set(),
c.MAP_SLEEP: False,
c.MAP_PLOT_TYPE: plot_type,
}
# 判断位置是否可用
# 暂时没有写紫卡植物的判断方法
@ -73,30 +85,40 @@ class Map():
def isAvailable(self, map_x: int, map_y: int, plant_name: str) -> bool:
# 咖啡豆和墓碑吞噬者的判别最为特殊
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 self.map[map_y][map_x][c.MAP_SLEEP] and (
plant_name not in self.map[map_y][map_x][c.MAP_PLANT]
):
return True
else:
return False
if 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 c.GRAVE in self.map[map_y][map_x][c.MAP_PLANT] and (
plant_name not in self.map[map_y][map_x][c.MAP_PLANT]
):
return True
else:
return False
# 被非植物障碍占据的格子对于一般植物不可种植
if any((i in c.NON_PLANT_OBJECTS) for i in self.map[map_y][map_x][c.MAP_PLANT]):
if any(
(i in c.NON_PLANT_OBJECTS)
for i in self.map[map_y][map_x][c.MAP_PLANT]
):
return False
if self.map[map_y][map_x][c.MAP_PLOT_TYPE] == c.MAP_GRASS: # 草地
# 首先需要判断植物是否是水生植物,水生植物不能种植在陆地上
if plant_name not in c.WATER_PLANTS:
if not self.map[map_y][map_x][c.MAP_PLANT]: # 没有植物肯定可以种植
return True
elif (all((i in {"花盆(未实现)", c.PUMPKINHEAD}) for i in self.map[map_y][map_x][c.MAP_PLANT])
and (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 (
plant_name 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 (plant_name == c.PUMPKINHEAD) and (
c.PUMPKINHEAD not in self.map[map_y][map_x][c.MAP_PLANT]
): # 没有南瓜头就能种南瓜头
return True
else:
return False
@ -105,19 +127,25 @@ class Map():
elif self.map[map_y][map_x][c.MAP_PLOT_TYPE] == c.MAP_TILE: # 屋顶
# 首先需要判断植物是否是水生植物,水生植物不能种植在陆地上
if plant_name not in c.WATER_PLANTS:
if "花盆(未实现)" in self.map[map_y][map_x][c.MAP_PLANT]:
if (all((i in {"花盆(未实现)", c.PUMPKINHEAD}) for i in self.map[map_y][map_x][c.MAP_PLANT])
and (plant_name not in self.map[map_y][map_x][c.MAP_PLANT])): # 例外植物:集合中填花盆和南瓜头,只要这里没有这种植物就能种植
if '花盆(未实现)' in self.map[map_y][map_x][c.MAP_PLANT]:
if all(
(i in {'花盆(未实现)', c.PUMPKINHEAD})
for i in self.map[map_y][map_x][c.MAP_PLANT]
) and (
plant_name not in self.map[map_y][map_x][c.MAP_PLANT]
): # 例外植物:集合中填花盆和南瓜头,只要这里没有这种植物就能种植
if plant_name in {c.SPIKEWEED}: # 不能在花盆上种植的植物
return False
else:
return True
elif ((plant_name == c.PUMPKINHEAD)
and (c.PUMPKINHEAD not in self.map[map_y][map_x][c.MAP_PLANT])): # 有花盆且没有南瓜头就能种南瓜头
elif (plant_name == c.PUMPKINHEAD) and (
c.PUMPKINHEAD
not in self.map[map_y][map_x][c.MAP_PLANT]
): # 有花盆且没有南瓜头就能种南瓜头
return True
else:
return False
elif plant_name == "花盆(未实现)": # 这一格本来没有花盆而且新来的植物是花盆,可以种
elif plant_name == '花盆(未实现)': # 这一格本来没有花盆而且新来的植物是花盆,可以种
return True
else:
return False
@ -125,20 +153,32 @@ class Map():
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 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 (
plant_name not in self.map[map_y][map_x][c.MAP_PLANT]
):
if plant_name in {
c.SPIKEWEED,
c.POTATOMINE,
'花盆(未实现)',
}: # 不能在睡莲上种植的植物
return False
else:
return True
elif ((plant_name == c.PUMPKINHEAD)
and (c.PUMPKINHEAD not in self.map[map_y][map_x][c.MAP_PLANT])): # 在睡莲上且没有南瓜头就能种南瓜头
elif (plant_name == c.PUMPKINHEAD) and (
c.PUMPKINHEAD
not in self.map[map_y][map_x][c.MAP_PLANT]
): # 在睡莲上且没有南瓜头就能种南瓜头
return True
else:
return False
@ -168,19 +208,38 @@ class Map():
def getMapGridPos(self, map_x: int, map_y: int) -> tuple[int, int]:
if self.background_type in c.POOL_EQUIPPED_BACKGROUNDS:
return (map_x * c.GRID_POOL_X_SIZE + c.GRID_POOL_X_SIZE//2 + c.MAP_POOL_OFFSET_X,
map_y * c.GRID_POOL_Y_SIZE + c.GRID_POOL_Y_SIZE//5 * 3 + c.MAP_POOL_OFFSET_Y)
return (
map_x * c.GRID_POOL_X_SIZE
+ c.GRID_POOL_X_SIZE // 2
+ c.MAP_POOL_OFFSET_X,
map_y * c.GRID_POOL_Y_SIZE
+ c.GRID_POOL_Y_SIZE // 5 * 3
+ c.MAP_POOL_OFFSET_Y,
)
elif self.background_type in c.ON_ROOF_BACKGROUNDS:
return (map_x * c.GRID_ROOF_X_SIZE + c.GRID_ROOF_X_SIZE//2 + c.MAP_ROOF_OFFSET_X,
map_y * c.GRID_ROOF_Y_SIZE + 20 * max(0, (6 - map_y)) + c.GRID_ROOF_Y_SIZE//5 * 3 + c.MAP_POOL_OFFSET_Y)
return (
map_x * c.GRID_ROOF_X_SIZE
+ c.GRID_ROOF_X_SIZE // 2
+ c.MAP_ROOF_OFFSET_X,
map_y * c.GRID_ROOF_Y_SIZE
+ 20 * max(0, (6 - map_y))
+ c.GRID_ROOF_Y_SIZE // 5 * 3
+ c.MAP_POOL_OFFSET_Y,
)
else:
return (map_x * c.GRID_X_SIZE + c.GRID_X_SIZE//2 + c.MAP_OFFSET_X,
map_y * c.GRID_Y_SIZE + c.GRID_Y_SIZE//5 * 3 + c.MAP_OFFSET_Y)
return (
map_x * c.GRID_X_SIZE + c.GRID_X_SIZE // 2 + c.MAP_OFFSET_X,
map_y * c.GRID_Y_SIZE
+ c.GRID_Y_SIZE // 5 * 3
+ c.MAP_OFFSET_Y,
)
def setMapGridType(self, map_x: int, map_y: int, plot_type: str):
self.map[map_y][map_x][c.MAP_PLOT_TYPE] = plot_type
def addMapPlant(self, map_x:int, map_y:int, plant_name:int, sleep:bool=False):
def addMapPlant(
self, map_x: int, map_y: int, plant_name: int, sleep: bool = False
):
self.map[map_y][map_x][c.MAP_PLANT].add(plant_name)
self.map[map_y][map_x][c.MAP_SLEEP] = sleep
@ -192,125 +251,144 @@ class Map():
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: int, y: int, plant_name: str
) -> tuple[int, int]:
pos = None
map_x, map_y = self.getMapIndex(x, y)
if self.isValid(map_x, map_y) and self.isAvailable(map_x, map_y, plant_name):
if self.isValid(map_x, map_y) and self.isAvailable(
map_x, map_y, plant_name
):
pos = self.getMapGridPos(map_x, map_y)
return pos
# 保存具体关卡地图信息常数
# 冒险模式地图
LEVEL_MAP_DATA = (
# 第0关测试模式地图
{
c.BACKGROUND_TYPE: 2,
c.GAME_TITLE: "隐藏测试关卡",
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"}
)
{'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.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
c.NUM_FLAGS: 1,
},
# 第2关三行草皮
{
c.BACKGROUND_TYPE: 8,
c.GAME_TITLE: "白天 1-2",
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
c.NUM_FLAGS: 1,
},
# 第3关
{
c.BACKGROUND_TYPE: 0,
c.GAME_TITLE: "白天 1-3",
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
c.NUM_FLAGS: 2,
},
# 第4关
{
c.BACKGROUND_TYPE: 0,
c.GAME_TITLE: "白天 1-4",
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
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.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
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.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
c.INCLUDED_ZOMBIES: (c.NORMAL_ZOMBIE, c.NEWSPAPER_ZOMBIE),
c.NUM_FLAGS: 2,
},
# 第7关
{
c.BACKGROUND_TYPE: 1,
c.GAME_TITLE: "黑夜 2-2",
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.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.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.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,),
@ -323,171 +401,224 @@ LEVEL_MAP_DATA = (
# 第9关 目前为泳池模式第一关
{
c.BACKGROUND_TYPE: 2,
c.GAME_TITLE: "泳池 3-1",
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
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.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.INCLUDED_ZOMBIES: (
c.NORMAL_ZOMBIE,
c.BUCKETHEAD_ZOMBIE,
c.CONEHEAD_ZOMBIE,
c.SNORKELZOMBIE,
),
c.INEVITABLE_ZOMBIE_DICT: {30: (c.SNORKELZOMBIE,)},
c.NUM_FLAGS:3
c.NUM_FLAGS: 3,
},
# 第11关
{
c.BACKGROUND_TYPE: 2,
c.GAME_TITLE: "泳池 3-3",
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
c.NUM_FLAGS: 3,
},
# 第12关 目前为泳池最后一关
{
c.BACKGROUND_TYPE: 2,
c.GAME_TITLE: "泳池 3-4",
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.INCLUDED_ZOMBIES: (
c.NORMAL_ZOMBIE,
c.ZOMBONI,
c.BUCKETHEAD_ZOMBIE,
c.CONEHEAD_ZOMBIE, c.SNORKELZOMBIE),
c.CONEHEAD_ZOMBIE,
c.SNORKELZOMBIE,
),
c.INEVITABLE_ZOMBIE_DICT: {40: (c.ZOMBONI,)},
c.NUM_FLAGS:4
c.NUM_FLAGS: 4,
},
# 第13关 目前为浓雾第一关 尚未完善
{
c.BACKGROUND_TYPE: 3,
c.GAME_TITLE: "浓雾 4-1",
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
c.INCLUDED_ZOMBIES: (
c.NORMAL_ZOMBIE,
c.NEWSPAPER_ZOMBIE,
c.ZOMBONI,
c.FOOTBALL_ZOMBIE,
c.CONEHEAD_ZOMBIE,
c.BUCKETHEAD_ZOMBIE,
),
c.NUM_FLAGS: 4,
},
)
# 玩玩小游戏地图
LITTLE_GAME_MAP_DATA = (
# 第0关 测试
{
c.BACKGROUND_TYPE: 3,
c.GAME_TITLE: "隐藏测试关卡",
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.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.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.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.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,}
c.CARD_POOL: {
c.WALLNUTBOWLING: 300,
c.REDWALLNUTBOWLING: 100,
},
},
# 第2关 白天 大决战
{
c.BACKGROUND_TYPE: 0,
c.GAME_TITLE: "大决战(白天)",
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.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.CARD_POOL: {
c.PEASHOOTER: 200,
c.SNOWPEASHOOTER: 100,
c.WALLNUT: 100,
c.CHERRYBOMB: 100,
c.REPEATERPEA: 200,
c.CHOMPER: 100,
c.POTATOMINE: 100,}
c.POTATOMINE: 100,
},
},
# 第3关 夜晚 大决战
{
c.BACKGROUND_TYPE: 1,
c.GAME_TITLE: "大决战(黑夜)",
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.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.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
c.FUMESHROOM: 200,
},
c.GRADE_GRAVES: 3,
},
# 第4关 泳池 大决战
{
c.BACKGROUND_TYPE: 2,
c.GAME_TITLE: "大决战(泳池)",
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.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.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,}
c.THREEPEASHOOTER: 400,
},
},
# 第5关 坚果保龄球2
{
c.BACKGROUND_TYPE: 6,
c.GAME_TITLE: "坚果保龄球(II)",
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.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.CARD_POOL: {
c.WALLNUTBOWLING: 500,
c.REDWALLNUTBOWLING: 100,
c.GIANTWALLNUT:100,}
c.GIANTWALLNUT: 100,
},
},
)

View File

@ -1,7 +1,9 @@
import random
import pygame as pg
from .. import tool
from .. import constants as c
from .. import tool
def getSunValueImage(sun_value):
@ -21,13 +23,19 @@ 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 = {
c.PLANT_CARD_INFO[c.PLANT_CARD_INDEX[card_name]]: data[card_name]
for card_name in data
}
return card_pool
class Card():
def __init__(self, x:int, y:int, index:int, scale:float=0.5, not_recommend=0):
class Card:
def __init__(
self, x: int, y: int, index: int, scale: float = 0.5, not_recommend=0
):
self.info = c.PLANT_CARD_INFO[index]
self.loadFrame(self.info[c.CARD_INDEX], scale)
self.rect = self.orig_image.get_rect()
@ -35,11 +43,20 @@ class Card():
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(self.info[c.SUN_INDEX]), True, c.BLACK
)
self.sun_cost_img_rect = self.sun_cost_img.get_rect()
sun_cost_img_x = 32 - self.sun_cost_img_rect.w
self.orig_image.blit(self.sun_cost_img,
(sun_cost_img_x, 52, self.sun_cost_img_rect.w, self.sun_cost_img_rect.h))
self.orig_image.blit(
self.sun_cost_img,
(
sun_cost_img_x,
52,
self.sun_cost_img_rect.w,
self.sun_cost_img_rect.h,
),
)
self.index = index
self.sun_cost = self.info[c.SUN_INDEX]
@ -52,7 +69,9 @@ class Card():
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))
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)
@ -62,18 +81,25 @@ class Card():
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 (
self.rect.x <= x <= self.rect.right
and self.rect.y <= y <= self.rect.bottom
):
return True
return False
def canClick(self, sun_value, current_time):
if self.sun_cost <= sun_value and (current_time - self.frozen_timer) > self.frozen_time:
if (
self.sun_cost <= sun_value
and (current_time - self.frozen_timer) > self.frozen_time
):
return True
return False
@ -86,14 +112,18 @@ class Card():
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))
self.image.blit(
self.orig_image, (0, 0), (0, 0, self.rect.w, self.rect.h)
)
else:
self.image = self.orig_image
self.image.set_alpha(255)
else:
self.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.blit(
self.orig_image, (0, 0), (0, 0, self.rect.w, self.rect.h)
)
def setFrozenTime(self, current_time):
self.frozen_timer = current_time
@ -105,16 +135,25 @@ class Card():
image = pg.Surface((self.rect.w, self.rect.h)) # 黑底
frozen_image = self.orig_image
frozen_image.set_alpha(128)
frozen_height = ((self.frozen_time - time)/self.frozen_time) * self.rect.h
frozen_height = (
(self.frozen_time - time) / self.frozen_time
) * self.rect.h
image.blit(frozen_image, (0,0), (0, 0, self.rect.w, frozen_height))
image.blit(
frozen_image, (0, 0), (0, 0, self.rect.w, frozen_height)
)
self.orig_image.set_alpha(192)
image.blit(self.orig_image, (0,frozen_height),
(0, frozen_height, self.rect.w, self.rect.h - 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 = 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))
image.blit(
self.orig_image, (0, 0), (0, 0, self.rect.w, self.rect.h)
)
elif self.clicked:
image = pg.Surface((self.rect.w, self.rect.h)) # 黑底
chosen_image = self.orig_image
@ -134,8 +173,9 @@ 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()
@ -187,7 +227,10 @@ 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()
@ -196,8 +239,10 @@ class MenuBar():
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 (
self.rect.x <= x <= self.rect.right
and self.rect.y <= y <= self.rect.bottom
):
return True
return False
@ -229,8 +274,9 @@ class MenuBar():
for card in self.card_list:
card.draw(surface)
# 关卡模式选植物的界面
class Panel():
class Panel:
def __init__(self, card_list, sun_value, background_type=c.BACKGROUND_DAY):
self.loadImages(sun_value)
self.selected_cards = []
@ -256,7 +302,6 @@ 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
@ -277,17 +322,25 @@ class Panel():
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):
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):
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):
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):
elif (
plant_name == c.COFFEEBEAN
and self.background_type not in c.DAYTIME_BACKGROUNDS
):
not_recommend = c.REASON_OTHER
# 还有屋顶场景,以及其他植物没有实现的植物没有写进来
else:
@ -314,7 +367,9 @@ class Panel():
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))
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
@ -328,7 +383,9 @@ class Panel():
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.not_recommend = (
c.REASON_SLEEP_BUT_COFFEE_BEAN
)
i.image = i.orig_image
i.image.set_alpha(255)
break
@ -348,8 +405,10 @@ 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):
if (
self.button_rect.x <= x <= self.button_rect.right
and self.button_rect.y <= y <= self.button_rect.bottom
):
return True
return False
@ -371,8 +430,9 @@ class Panel():
if self.selected_num >= c.CARD_LIST_NUM:
surface.blit(self.button_image, self.button_rect)
# 传送带模式的卡片
class MoveCard():
class MoveCard:
def __init__(self, x, y, card_name, plant_name, scale=0.5):
self.loadFrame(card_name, scale)
self.rect = self.orig_image.get_rect()
@ -392,14 +452,18 @@ 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 (
self.rect.x <= x <= self.rect.right
and self.rect.y <= y <= self.rect.bottom
):
return True
return False
@ -411,14 +475,18 @@ class MoveCard():
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))
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
@ -436,8 +504,9 @@ 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()
@ -460,12 +529,24 @@ class MoveBar():
self.image = tool.get_image(tool.GFX[name], *frame_rect, c.WHITE, 1)
def createCard(self):
if len(self.card_list) > 0 and self.card_list[-1].rect.right > self.card_end_x:
if (
len(self.card_list) > 0
and self.card_list[-1].rect.right > self.card_end_x
):
return False
x = self.card_end_x
y = 6
selected_card = random.choices(self.card_pool_name, self.card_pool_weight)[0]
self.card_list.append(MoveCard(x, y, selected_card[c.CARD_INDEX], selected_card[c.PLANT_NAME_INDEX]))
selected_card = random.choices(
self.card_pool_name, self.card_pool_weight
)[0]
self.card_list.append(
MoveCard(
x,
y,
selected_card[c.CARD_INDEX],
selected_card[c.PLANT_NAME_INDEX],
)
)
return True
def update(self, current_time):
@ -489,8 +570,10 @@ class MoveBar():
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 (
self.rect.x <= x <= self.rect.right
and self.rect.y <= 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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

209
uv.lock generated Normal file
View File

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