Compare commits

..

5 Commits

Author SHA1 Message Date
wszqkzqk
9bfb9688b2
Merge branch 'master' into 面向对象化 2023-06-01 00:49:56 +08:00
wszqkzqk
e718d329d9 wip 2023-01-07 10:44:16 +08:00
wszqkzqk
4dfe1e3f18 添加类型注解 2022-12-31 14:00:28 +08:00
wszqkzqk
6de8fc10fb 地图面向对象 2022-12-25 20:59:30 +08:00
wszqkzqk
99c1645c39 避免重复构建 2022-12-25 20:58:25 +08:00
26 changed files with 1935 additions and 3615 deletions

View File

@ -14,7 +14,7 @@ jobs:
fail-fast: false
matrix:
python_version:
- "3.12"
- "3.11"
name: Windows Python ${{ matrix.python_version }}
steps:
- uses: actions/checkout@v2
@ -46,7 +46,6 @@ 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
@ -68,22 +67,22 @@ jobs:
--show-memory `
--output-dir=out `
--windows-icon-from-ico=pypvz.ico `
--include-data-dir=resources=resources `
--include-data-file=c:\hostedtoolcache\windows\python\${{ matrix.python_version }}*\x64\lib\site-packages\pygame\libogg-0.dll=libogg-0.dll `
--include-data-file=c:\hostedtoolcache\windows\python\${{ matrix.python_version }}*\x64\lib\site-packages\pygame\libopus-0.dll=libopus-0.dll `
--include-data-file=c:\hostedtoolcache\windows\python\${{ matrix.python_version }}*\x64\lib\site-packages\pygame\libopusfile-0.dll=libopusfile-0.dll `
--include-data-file=c:\hostedtoolcache\windows\python\${{ matrix.python_version }}*\x64\lib\site-packages\pygame\libjpeg-9.dll=libjpeg-9.dll `
--include-data-dir=resources=resources `
--include-data-file=c:\hostedtoolcache\windows\python\${{ matrix.python_version }}*\x64\lib\site-packages\pygame\libvorbisfile-3.dll=libvorbisfile-3.dll `
--include-data-file=c:\hostedtoolcache\windows\python\${{ matrix.python_version }}*\x64\lib\site-packages\pygame\libvorbis-0.dll=libvorbis-0.dll `
--windows-disable-console `
-o pypvz-with-python${{ matrix.python_version }}-nuitka-windows-x64.exe `
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
tag: Dev
artifacts: ./out/*nuitka*.exe
artifacts: ./*nuitka*.exe
token: ${{ secrets.GITHUB_TOKEN }}
@ -93,7 +92,7 @@ jobs:
fail-fast: false
matrix:
python_version:
- "3.12"
- "3.11"
name: Ubuntu Python ${{ matrix.python_version }}
steps:
- name: 🛎️ Checkout
@ -133,7 +132,6 @@ 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.12"
- "3.11"
name: Windows Python ${{ matrix.python_version }}
steps:
- uses: actions/checkout@v2
@ -69,11 +69,12 @@ jobs:
--show-memory `
--output-dir=out `
--windows-icon-from-ico=pypvz.ico `
--include-data-dir=resources=resources `
--include-data-file=c:\hostedtoolcache\windows\python\${{ matrix.python_version }}*\x64\lib\site-packages\pygame\libogg-0.dll=libogg-0.dll `
--include-data-file=c:\hostedtoolcache\windows\python\${{ matrix.python_version }}*\x64\lib\site-packages\pygame\libopus-0.dll=libopus-0.dll `
--include-data-file=c:\hostedtoolcache\windows\python\${{ matrix.python_version }}*\x64\lib\site-packages\pygame\libopusfile-0.dll=libopusfile-0.dll `
--include-data-file=c:\hostedtoolcache\windows\python\${{ matrix.python_version }}*\x64\lib\site-packages\pygame\libjpeg-9.dll=libjpeg-9.dll `
--include-data-dir=resources=resources `
--include-data-file=c:\hostedtoolcache\windows\python\${{ matrix.python_version }}*\x64\lib\site-packages\pygame\libvorbisfile-3.dll=libvorbisfile-3.dll `
--include-data-file=c:\hostedtoolcache\windows\python\${{ matrix.python_version }}*\x64\lib\site-packages\pygame\libvorbis-0.dll=libvorbis-0.dll `
--windows-disable-console `
-o pypvz-with-python${{ matrix.python_version }}-nuitka-windows-x64.exe `
pypvz.py
@ -84,7 +85,7 @@ jobs:
with:
allowUpdates: true
tag: Latest
artifacts: ./out/*nuitka*.exe
artifacts: ./*nuitka*.exe
token: ${{ secrets.GITHUB_TOKEN }}
linux:
@ -93,7 +94,7 @@ jobs:
fail-fast: false
matrix:
python_version:
- "3.12"
- "3.11"
name: Ubuntu Python ${{ matrix.python_version }}
steps:
- name: 🛎️ Checkout

4
.gitignore vendored
View File

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

View File

@ -1 +0,0 @@
3.12

View File

@ -38,17 +38,7 @@
* 光标移动到向日葵奖杯上是显示当前各个模式通关次数
* 含有游戏帮助界面 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,最好使用最新版)

View File

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

View File

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,7 @@
import random
import pygame as pg
from .. import constants as c
from .. import tool
from .. import constants as c
def getSunValueImage(sun_value):
@ -23,19 +21,13 @@ 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()
@ -43,21 +35,12 @@ 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]
self.frozen_time = self.info[c.FROZEN_TIME_INDEX]
@ -69,9 +52,7 @@ 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)
@ -81,25 +62,18 @@ 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
@ -112,18 +86,14 @@ 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
@ -131,35 +101,26 @@ class Card:
def createShowImage(self, sun_value, current_time):
# 有关是否满足冷却与阳光条件的图片形式
time = current_time - self.frozen_timer
if time < self.frozen_time: # cool down status
if time < self.frozen_time: #cool down status
image = pg.Surface((self.rect.w, self.rect.h)) # 黑底
frozen_image = self.orig_image
frozen_image.set_alpha(128)
frozen_height = (
(self.frozen_time - time) / self.frozen_time
) * self.rect.h
image.blit(
frozen_image, (0, 0), (0, 0, self.rect.w, frozen_height)
)
frozen_height = ((self.frozen_time - time)/self.frozen_time) * self.rect.h
image.blit(frozen_image, (0,0), (0, 0, self.rect.w, frozen_height))
self.orig_image.set_alpha(192)
image.blit(
self.orig_image,
(0, frozen_height),
(0, frozen_height, self.rect.w, self.rect.h - frozen_height),
)
elif self.sun_cost > sun_value: # disable status
image.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
chosen_image.set_alpha(128)
image.blit(chosen_image, (0, 0), (0, 0, self.rect.w, self.rect.h))
image.blit(chosen_image, (0,0), (0, 0, self.rect.w, self.rect.h))
else:
image = self.orig_image
image.set_alpha(255)
@ -173,15 +134,14 @@ class Card:
def draw(self, surface):
surface.blit(self.image, self.rect)
# 植物栏
class MenuBar:
class MenuBar():
def __init__(self, card_list, sun_value):
self.loadFrame(c.MENUBAR_BACKGROUND)
self.rect = self.image.get_rect()
self.rect.x = 0
self.rect.y = 0
self.sun_value = sun_value
self.card_offset_x = 26
self.setupCards(card_list)
@ -211,9 +171,9 @@ class MenuBar:
self.rect.y = y
for i in range(num):
x = i * width
self.image.blit(img, (x, 0))
self.image.blit(img, (x,0))
self.image.set_colorkey(c.BLACK)
def setupCards(self, card_list):
self.card_list = []
x = self.card_offset_x
@ -227,22 +187,17 @@ class MenuBar:
for card in self.card_list:
if card.checkMouseClick(mouse_pos):
if card.canClick(self.sun_value, self.current_time):
result = (
c.PLANT_CARD_INFO[card.index][c.PLANT_NAME_INDEX],
card,
)
result = (c.PLANT_CARD_INFO[card.index][c.PLANT_NAME_INDEX], card)
else:
# 播放无法使用该卡片的警告音
c.SOUND_CANNOT_CHOOSE_WARNING.play()
break
return result
def checkMenuBarClick(self, mouse_pos):
x, y = mouse_pos
if (
self.rect.x <= x <= self.rect.right
and self.rect.y <= y <= self.rect.bottom
):
if (self.rect.x <= x <= self.rect.right and
self.rect.y <= y <= self.rect.bottom):
return True
return False
@ -265,7 +220,7 @@ class MenuBar:
self.value_rect = self.value_image.get_rect()
self.value_rect.x = 21
self.value_rect.y = self.rect.bottom - 24
self.image.blit(self.value_image, self.value_rect)
def draw(self, surface):
@ -274,9 +229,8 @@ 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 = []
@ -302,12 +256,13 @@ class Panel:
self.panel_rect.x = 0
self.panel_rect.y = c.PANEL_Y_START
self.value_image = getSunValueImage(sun_value)
self.value_rect = self.value_image.get_rect()
self.value_rect.x = 21
self.value_rect.y = self.menu_rect.bottom - 24
self.button_image = self.loadFrame(c.START_BUTTON)
self.button_image = self.loadFrame(c.START_BUTTON)
self.button_rect = self.button_image.get_rect()
self.button_rect.x = 155
self.button_rect.y = 547
@ -322,26 +277,15 @@ 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
):
not_recommend = c.REASON_OTHER
# 还有屋顶场景,以及其他植物没有实现的植物没有写进来
else:
not_recommend = 0
@ -350,7 +294,7 @@ class Panel:
def checkCardClick(self, mouse_pos):
delete_card = None
for card in self.selected_cards:
if delete_card: # when delete a card, move right cards to left
if delete_card: # when delete a card, move right cards to left
card.rect.x -= c.BAR_CARD_X_INTERNAL
elif card.checkMouseClick(mouse_pos):
self.deleteCard(card.index)
@ -367,9 +311,7 @@ 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
@ -383,14 +325,12 @@ 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
def addCard(self, card: Card):
def addCard(self, card:Card):
card.setSelect(False)
y = 8
x = 77 + self.selected_num * c.BAR_CARD_X_INTERNAL
@ -405,11 +345,9 @@ class Panel:
return False
x, y = mouse_pos
if (
self.button_rect.x <= x <= self.button_rect.right
and self.button_rect.y <= y <= self.button_rect.bottom
):
return True
if (self.button_rect.x <= x <= self.button_rect.right and
self.button_rect.y <= y <= self.button_rect.bottom):
return True
return False
def getSelectedCards(self):
@ -430,9 +368,8 @@ class Panel:
if self.selected_num >= c.CARD_LIST_NUM:
surface.blit(self.button_image, self.button_rect)
# 传送带模式的卡片
class MoveCard:
class MoveCard():
def __init__(self, x, y, card_name, plant_name, scale=0.5):
self.loadFrame(card_name, scale)
self.rect = self.orig_image.get_rect()
@ -452,41 +389,33 @@ 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
def createShowImage(self):
# 新增卡片时显示图片
if self.rect.w < self.orig_rect.w: # create a part card image
if self.rect.w < self.orig_rect.w: #create a part card image
image = pg.Surface([self.rect.w, self.rect.h])
if self.clicked:
self.orig_image.set_alpha(128)
else:
self.orig_image.set_alpha(255)
image.blit(
self.orig_image, (0, 0), (0, 0, self.rect.w, self.rect.h)
)
image.blit(self.orig_image, (0, 0), (0, 0, self.rect.w, self.rect.h))
self.rect.w += 1
else:
if self.clicked:
image = pg.Surface([self.rect.w, self.rect.h]) # 黑底
self.orig_image.set_alpha(128)
image.blit(
self.orig_image, (0, 0), (0, 0, self.rect.w, self.rect.h)
)
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
@ -504,15 +433,14 @@ class MoveCard:
def draw(self, surface):
surface.blit(self.image, self.rect)
# 传送带
class MoveBar:
class MoveBar():
def __init__(self, card_pool):
self.loadFrame(c.MOVEBAR_BACKGROUND)
self.rect = self.image.get_rect()
self.rect.x = 20
self.rect.y = 0
self.card_start_x = self.rect.x + 8
self.card_end_x = self.rect.right - 5
self.card_pool = card_pool
@ -529,24 +457,12 @@ class MoveBar:
self.image = tool.get_image(tool.GFX[name], *frame_rect, c.WHITE, 1)
def createCard(self):
if (
len(self.card_list) > 0
and self.card_list[-1].rect.right > self.card_end_x
):
if len(self.card_list) > 0 and self.card_list[-1].rect.right > self.card_end_x:
return False
x = self.card_end_x
y = 6
selected_card = random.choices(
self.card_pool_name, self.card_pool_weight
)[0]
self.card_list.append(
MoveCard(
x,
y,
selected_card[c.CARD_INDEX],
selected_card[c.PLANT_NAME_INDEX],
)
)
selected_card = random.choices(self.card_pool_name, self.card_pool_weight)[0]
self.card_list.append(MoveCard(x, y, selected_card[c.CARD_INDEX], selected_card[c.PLANT_NAME_INDEX]))
return True
def update(self, current_time):
@ -567,13 +483,11 @@ class MoveBar:
result = (card.plant_name, card)
break
return result
def checkMenuBarClick(self, mouse_pos):
x, y = mouse_pos
if (
self.rect.x <= x <= self.rect.right
and self.rect.y <= y <= self.rect.bottom
):
if (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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,16 +1,14 @@
import os
import pygame as pg
from .. import constants as c
import os
from .. import tool
from .. import constants as c
class Menu(tool.State):
def __init__(self):
tool.State.__init__(self)
def startup(self, current_time: int, persist):
def startup(self, current_time:int, persist):
self.next = c.LEVEL
self.persist = persist
self.game_info = persist
@ -19,7 +17,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])
@ -32,9 +30,7 @@ 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
@ -43,12 +39,7 @@ 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
@ -57,12 +48,7 @@ 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
@ -71,12 +57,7 @@ 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
@ -85,12 +66,7 @@ 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
@ -99,23 +75,20 @@ class Menu(tool.State):
# 帮助菜单
help_frame_rect = (0, 0, 48, 22)
self.help_frames = [
tool.get_image_alpha(tool.GFX[f'{c.HELP}_{i}'], *help_frame_rect)
for i in range(2)
]
self.help_frames = [tool.get_image_alpha(tool.GFX[f"{c.HELP}_{i}"], *help_frame_rect) for i in range(2)]
self.help_image = self.help_frames[0]
self.help_rect = self.help_image.get_rect()
self.help_rect.x = 653
self.help_rect.y = 520
self.help_hilight_time = 0
# 计时器与点击信号记录器
self.adventure_start = 0
self.adventure_timer = 0
self.adventure_clicked = False
self.option_button_clicked = False
def checkHilight(self, x: int, y: int):
def checkHilight(self, x:int, y:int):
# 高亮冒险模式按钮
if self.inArea(self.adventure_rect, x, y):
self.adventure_highlight_time = self.current_time
@ -133,25 +106,15 @@ 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):
def chooseHilightImage(self, hilightTime:int, frames):
if (self.current_time - hilightTime) < 80:
index = 1
index= 1
else:
index = 0
return frames[index]
@ -185,9 +148,7 @@ 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
@ -201,7 +162,7 @@ class Menu(tool.State):
self.return_button_rect.y = 440
font = pg.font.Font(c.FONT_PATH, 40)
font.bold = True
text = font.render('返回游戏', True, c.YELLOWGREEN)
text = font.render("返回游戏", True, c.YELLOWGREEN)
text_rect = text.get_rect()
text_rect.x = 105
text_rect.y = 18
@ -212,72 +173,51 @@ class Menu(tool.State):
font = pg.font.Font(c.FONT_PATH, 35)
font.bold = True
# 音量+
self.sound_volume_plus_button = tool.get_image_alpha(
tool.GFX[c.SOUND_VOLUME_BUTTON], *frame_rect, c.BLACK
)
sign = font.render('+', True, c.YELLOWGREEN)
self.sound_volume_plus_button = tool.get_image_alpha(tool.GFX[c.SOUND_VOLUME_BUTTON], *frame_rect, c.BLACK)
sign = font.render("+", True, c.YELLOWGREEN)
sign_rect = sign.get_rect()
sign_rect.x = 8
sign_rect.y = -4
self.sound_volume_plus_button.blit(sign, sign_rect)
self.sound_volume_plus_button_rect = (
self.sound_volume_plus_button.get_rect()
)
self.sound_volume_plus_button_rect = self.sound_volume_plus_button.get_rect()
self.sound_volume_plus_button_rect.x = 500
# 音量-
self.sound_volume_minus_button = tool.get_image_alpha(
tool.GFX[c.SOUND_VOLUME_BUTTON], *frame_rect, c.BLACK
)
sign = font.render('-', True, c.YELLOWGREEN)
self.sound_volume_minus_button = tool.get_image_alpha(tool.GFX[c.SOUND_VOLUME_BUTTON], *frame_rect, c.BLACK)
sign = font.render("-", True, c.YELLOWGREEN)
sign_rect = sign.get_rect()
sign_rect.x = 12
sign_rect.y = -6
self.sound_volume_minus_button.blit(sign, sign_rect)
self.sound_volume_minus_button_rect = (
self.sound_volume_minus_button.get_rect()
)
self.sound_volume_minus_button_rect = self.sound_volume_minus_button.get_rect()
self.sound_volume_minus_button_rect.x = 450
# 音量+、-应当处于同一高度
self.sound_volume_minus_button_rect.y = (
self.sound_volume_plus_button_rect.y
) = 250
self.sound_volume_minus_button_rect.y = self.sound_volume_plus_button_rect.y = 250
def setupSunflowerTrophy(self):
# 设置金银向日葵图片信息
if (
self.game_info[c.LEVEL_COMPLETIONS]
or self.game_info[c.LITTLEGAME_COMPLETIONS]
):
if (
self.game_info[c.LEVEL_COMPLETIONS]
and self.game_info[c.LITTLEGAME_COMPLETIONS]
):
if (self.game_info[c.LEVEL_COMPLETIONS] or self.game_info[c.LITTLEGAME_COMPLETIONS]):
if (self.game_info[c.LEVEL_COMPLETIONS] and self.game_info[c.LITTLEGAME_COMPLETIONS]):
frame_rect = (157, 0, 157, 269)
else:
frame_rect = (0, 0, 157, 269)
self.sunflower_trophy = tool.get_image_alpha(
tool.GFX[c.TROPHY_SUNFLOWER], *frame_rect, c.BLACK
)
self.sunflower_trophy = tool.get_image_alpha(tool.GFX[c.TROPHY_SUNFLOWER], *frame_rect, c.BLACK)
self.sunflower_trophy_rect = self.sunflower_trophy.get_rect()
self.sunflower_trophy_rect.x = 0
self.sunflower_trophy_rect.y = 280
self.sunflower_trophy_show_info_time = 0
def checkSunflowerTrophyInfo(self, surface: pg.Surface, x: int, y: int):
def checkSunflowerTrophyInfo(self, surface:pg.Surface, x:int, y:int):
if self.inArea(self.sunflower_trophy_rect, x, y):
self.sunflower_trophy_show_info_time = self.current_time
if (self.current_time - self.sunflower_trophy_show_info_time) < 80:
font = pg.font.Font(c.FONT_PATH, 14)
if (
self.game_info[c.LEVEL_COMPLETIONS]
and self.game_info[c.LITTLEGAME_COMPLETIONS]
):
infoText = f'目前您一共完成了:冒险模式{self.game_info[c.LEVEL_COMPLETIONS]}轮,玩玩小游戏{self.game_info[c.LITTLEGAME_COMPLETIONS]}'
if (self.game_info[c.LEVEL_COMPLETIONS] and self.game_info[c.LITTLEGAME_COMPLETIONS]):
infoText = f"目前您一共完成了:冒险模式{self.game_info[c.LEVEL_COMPLETIONS]}轮,玩玩小游戏{self.game_info[c.LITTLEGAME_COMPLETIONS]}"
elif self.game_info[c.LEVEL_COMPLETIONS]:
infoText = f'目前您一共完成了:冒险模式{self.game_info[c.LEVEL_COMPLETIONS]}轮;完成其他所有游戏模式以获得金向日葵奖杯!'
infoText = f"目前您一共完成了:冒险模式{self.game_info[c.LEVEL_COMPLETIONS]}轮;完成其他所有游戏模式以获得金向日葵奖杯!"
else:
infoText = f'目前您一共完成了:玩玩小游戏{self.game_info[c.LITTLEGAME_COMPLETIONS]}轮;完成其他所有游戏模式以获得金向日葵奖杯!'
infoImg = font.render(infoText, True, c.BLACK, c.LIGHTYELLOW)
infoText = f"目前您一共完成了:玩玩小游戏{self.game_info[c.LITTLEGAME_COMPLETIONS]}轮;完成其他所有游戏模式以获得金向日葵奖杯!"
infoImg = font.render(infoText , True, c.BLACK, c.LIGHTYELLOW)
infoImg_rect = infoImg.get_rect()
infoImg_rect.x = self.sunflower_trophy_rect.x
infoImg_rect.y = self.sunflower_trophy_rect.bottom - 14
@ -288,38 +228,25 @@ class Menu(tool.State):
# 播放点击音效
c.SOUND_BUTTON_CLICK.play()
def showCurrentVolumeImage(self, surface: pg.Surface):
def showCurrentVolumeImage(self, surface:pg.Surface):
# 由于音量可变,因此这一内容不能在一开始就结束加载,而应当不断刷新不断显示
font = pg.font.Font(c.FONT_PATH, 30)
volume_tips = font.render(
f'音量:{round(self.game_info[c.SOUND_VOLUME]*100):3}%',
True,
c.LIGHTGRAY,
)
volume_tips = font.render(f"音量:{round(self.game_info[c.SOUND_VOLUME]*100):3}%", True, c.LIGHTGRAY)
volume_tips_rect = volume_tips.get_rect()
volume_tips_rect.x = 275
volume_tips_rect.y = 247
surface.blit(volume_tips, volume_tips_rect)
def update(
self,
surface: 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)
surface.blit(self.adventure_image, self.adventure_rect)
surface.blit(self.littleGame_image, self.littleGame_rect)
surface.blit(self.exit_image, self.exit_rect)
surface.blit(self.option_button_image, self.option_button_rect)
surface.blit(self.help_image, self.help_rect)
if (
self.game_info[c.LEVEL_COMPLETIONS]
or self.game_info[c.LITTLEGAME_COMPLETIONS]
):
if self.game_info[c.LEVEL_COMPLETIONS] or self.game_info[c.LITTLEGAME_COMPLETIONS]:
surface.blit(self.sunflower_trophy, self.sunflower_trophy_rect)
# 点到冒险模式后播放动画
@ -335,14 +262,8 @@ class Menu(tool.State):
elif self.option_button_clicked:
surface.blit(self.big_menu, self.big_menu_rect)
surface.blit(self.return_button, self.return_button_rect)
surface.blit(
self.sound_volume_plus_button,
self.sound_volume_plus_button_rect,
)
surface.blit(
self.sound_volume_minus_button,
self.sound_volume_minus_button_rect,
)
surface.blit(self.sound_volume_plus_button, self.sound_volume_plus_button_rect)
surface.blit(self.sound_volume_minus_button, self.sound_volume_minus_button_rect)
self.showCurrentVolumeImage(surface)
if mouse_pos:
# 返回
@ -350,12 +271,8 @@ class Menu(tool.State):
self.option_button_clicked = False
c.SOUND_BUTTON_CLICK.play()
# 音量+
elif self.inArea(
self.sound_volume_plus_button_rect, *mouse_pos
):
self.game_info[c.SOUND_VOLUME] = round(
min(self.game_info[c.SOUND_VOLUME] + 0.05, 1), 2
)
elif self.inArea(self.sound_volume_plus_button_rect, *mouse_pos):
self.game_info[c.SOUND_VOLUME] = round(min(self.game_info[c.SOUND_VOLUME] + 0.05, 1), 2)
# 一般不会有人想把音乐和音效分开设置故pg.mixer.Sound.set_volume()和pg.mixer.music.set_volume()需要一起用
pg.mixer.music.set_volume(self.game_info[c.SOUND_VOLUME])
for i in c.SOUNDS:
@ -363,12 +280,8 @@ class Menu(tool.State):
c.SOUND_BUTTON_CLICK.play()
self.saveUserData()
# 音量-
elif self.inArea(
self.sound_volume_minus_button_rect, *mouse_pos
):
self.game_info[c.SOUND_VOLUME] = round(
max(self.game_info[c.SOUND_VOLUME] - 0.05, 0), 2
)
elif self.inArea(self.sound_volume_minus_button_rect, *mouse_pos):
self.game_info[c.SOUND_VOLUME] = round(max(self.game_info[c.SOUND_VOLUME] - 0.05, 0), 2)
# 一般不会有人想把音乐和音效分开设置故pg.mixer.Sound.set_volume()和pg.mixer.music.set_volume()需要一起用
pg.mixer.music.set_volume(self.game_info[c.SOUND_VOLUME])
for i in c.SOUNDS:
@ -380,10 +293,7 @@ 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,11 +1,8 @@
import os
from abc import abstractmethod
import pygame as pg
from .. import constants as c
from abc import abstractmethod
from .. import tool
from .. import constants as c
class Screen(tool.State):
def __init__(self):
@ -17,9 +14,7 @@ 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
@ -27,45 +22,33 @@ 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)
@ -80,39 +63,36 @@ class Screen(tool.State):
self.next = c.MAIN_MENU
self.done = True
class GameVictoryScreen(Screen):
def __init__(self):
Screen.__init__(self)
self.image_name = c.GAME_VICTORY_IMAGE
def startup(self, current_time, persist):
self.start_time = current_time
self.persist = persist
self.game_info = persist
self.setupImage(self.image_name)
pg.display.set_caption('pypvz: 战斗胜利!')
pg.display.set_caption("pypvz: 战斗胜利!")
pg.mixer.music.stop()
pg.mixer.music.load(os.path.join(c.PATH_MUSIC_DIR, 'zenGarden.opus'))
pg.mixer.music.load(os.path.join(c.PATH_MUSIC_DIR, "zenGarden.opus"))
pg.mixer.music.play(-1, 0)
pg.mixer.music.set_volume(self.game_info[c.SOUND_VOLUME])
class GameLoseScreen(Screen):
def __init__(self):
Screen.__init__(self)
self.image_name = c.GAME_LOSE_IMAGE
def startup(self, current_time, persist):
self.start_time = current_time
self.persist = persist
self.game_info = persist
self.setupImage(self.image_name, (-118, -40, 800, 600), c.WHITE)
pg.display.set_caption('pypvz: 战斗失败!')
pg.display.set_caption("pypvz: 战斗失败!")
# 停止播放原来关卡中的音乐
pg.mixer.music.stop()
class AwardScreen(tool.State):
def __init__(self):
tool.State.__init__(self)
@ -120,9 +100,7 @@ class AwardScreen(tool.State):
def setupImage(self):
# 主体
frame_rect = (0, 0, 800, 600)
self.image = tool.get_image(
tool.GFX[c.AWARD_SCREEN_IMAGE], *frame_rect
)
self.image = tool.get_image(tool.GFX[c.AWARD_SCREEN_IMAGE], *frame_rect)
self.rect = self.image.get_rect()
self.rect.x = 0
self.rect.y = 0
@ -130,54 +108,41 @@ class AwardScreen(tool.State):
# 文字
# 标题处文字
font = pg.font.Font(c.FONT_PATH, 37)
title_text = font.render('您获得了新的战利品!', True, c.PARCHMENT_YELLOW)
title_text = font.render("您获得了新的战利品!", True, c.PARCHMENT_YELLOW)
title_text_rect = title_text.get_rect()
title_text_rect.x = 220
title_text_rect.y = 23
self.image.blit(title_text, title_text_rect)
# 按钮
frame_rect = (0, 0, 111, 26)
if self.show_only_one_option:
## 主菜单按钮
self.main_menu_button_image = tool.get_image_alpha(
tool.GFX[c.UNIVERSAL_BUTTON], *frame_rect
)
self.main_menu_button_image_rect = (
self.main_menu_button_image.get_rect()
)
self.main_menu_button_image = tool.get_image_alpha(tool.GFX[c.UNIVERSAL_BUTTON], *frame_rect)
self.main_menu_button_image_rect = self.main_menu_button_image.get_rect()
self.main_menu_button_image_rect.x = 343
self.main_menu_button_image_rect.y = 520
### 主菜单按钮上的文字
font = pg.font.Font(c.FONT_PATH, 18)
main_menu_text = font.render('主菜单', True, c.NAVYBLUE)
main_menu_text = font.render("主菜单", True, c.NAVYBLUE)
main_menu_text_rect = main_menu_text.get_rect()
main_menu_text_rect.x = 29
self.main_menu_button_image.blit(
main_menu_text, main_menu_text_rect
)
self.image.blit(
self.main_menu_button_image, self.main_menu_button_image_rect
)
self.main_menu_button_image.blit(main_menu_text, main_menu_text_rect)
self.image.blit(self.main_menu_button_image, self.main_menu_button_image_rect)
# 绘制向日葵奖杯
if (
self.game_info[c.LEVEL_COMPLETIONS]
and self.game_info[c.LITTLEGAME_COMPLETIONS]
):
if (self.game_info[c.LEVEL_COMPLETIONS] and self.game_info[c.LITTLEGAME_COMPLETIONS]):
frame_rect = (157, 0, 157, 269)
intro_title = '金向日葵奖杯'
intro_content = '您已通过所有关卡,获得此奖励!'
intro_title = "金向日葵奖杯"
intro_content = "您已通过所有关卡,获得此奖励!"
else:
frame_rect = (0, 0, 157, 269)
intro_title = '银向日葵奖杯'
intro_title = "银向日葵奖杯"
if self.game_info[c.LEVEL_COMPLETIONS]:
intro_content = '您已完成冒险模式,获得此奖励!'
intro_content = "您已完成冒险模式,获得此奖励!"
else:
intro_content = '您已完成玩玩小游戏,获得此奖励!'
sunflower_trophy_image = tool.get_image_alpha(
tool.GFX[c.TROPHY_SUNFLOWER], *frame_rect, scale=0.7
)
intro_content = "您已完成玩玩小游戏,获得此奖励!"
sunflower_trophy_image = tool.get_image_alpha(tool.GFX[c.TROPHY_SUNFLOWER], *frame_rect, scale=0.7)
sunflower_trophy_rect = sunflower_trophy_image.get_rect()
sunflower_trophy_rect.x = 348
sunflower_trophy_rect.y = 108
@ -185,9 +150,7 @@ class AwardScreen(tool.State):
# 绘制介绍标题
font = pg.font.Font(c.FONT_PATH, 22)
intro_title_img = font.render(
intro_title, True, c.PARCHMENT_YELLOW
)
intro_title_img = font.render(intro_title, True, c.PARCHMENT_YELLOW)
intro_title_rect = intro_title_img.get_rect()
intro_title_rect.x = 333
intro_title_rect.y = 305
@ -202,56 +165,40 @@ 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])
@ -267,7 +214,6 @@ class AwardScreen(tool.State):
self.next = c.LEVEL
self.done = True
class HelpScreen(tool.State):
def __init__(self):
tool.State.__init__(self)
@ -277,39 +223,31 @@ 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,17 +1,14 @@
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,31 +18,30 @@ class State:
# 当从其他状态进入这个状态时,需要进行的初始化操作
@abstractmethod
def startup(self, current_time: int, persist: dict):
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):
def update(self, surface:pg.Surface, keys, current_time:int):
# 前面加了@abstractmethod表示抽象基类中必须要重新定义的method
pass
# 工具:范围判断函数,用于判断点击
def inArea(self, rect: pg.Rect, x: int, y: int):
if rect.x <= x <= rect.right and rect.y <= y <= rect.bottom:
def inArea(self, rect:pg.Rect, x:int, y:int):
if (rect.x <= x <= rect.right and
rect.y <= y <= rect.bottom):
return True
else:
return False
# 工具:用户数据保存函数
def saveUserData(self):
with open(c.USERDATA_PATH, 'w', 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:
@ -53,19 +49,15 @@ 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
@ -74,12 +66,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 = {}
@ -92,10 +84,8 @@ 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
@ -106,12 +96,12 @@ 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() # 内部全是不可变对象,浅拷贝即可
self.game_info = c.INIT_USERDATA.copy() # 内部全是不可变对象,浅拷贝即可
def setup_states(self, state_dict: dict, start_state):
def setup_states(self, state_dict:dict, start_state):
self.state_dict = state_dict
self.state_name = start_state
self.state = self.state_dict[self.state_name]
@ -123,10 +113,8 @@ 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
@ -148,24 +136,17 @@ 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:
@ -174,57 +155,39 @@ 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:
# 不保留alpha通道的图片导入
image = pg.Surface([width, height])
rect = image.get_rect()
def get_image(
sheet: pg.Surface,
x: int,
y: int,
width: int,
height: int,
colorkey: tuple[int] = c.BLACK,
scale: int = 1,
) -> pg.Surface:
# 不保留alpha通道的图片导入
image = pg.Surface([width, height])
rect = image.get_rect()
image.blit(sheet, (0, 0), (x, y, width, height))
if colorkey:
image.set_colorkey(colorkey)
image = pg.transform.scale(image,
(int(rect.width*scale),
int(rect.height*scale)))
return image
image.blit(sheet, (0, 0), (x, y, width, height))
if colorkey:
image.set_colorkey(colorkey)
image = pg.transform.scale(
image, (int(rect.width * scale), int(rect.height * scale))
)
return image
def get_image_alpha(
sheet: pg.Surface,
x: int,
y: int,
width: int,
height: int,
colorkey: tuple[int] = c.BLACK,
scale: int = 1,
) -> pg.Surface:
def get_image_alpha(sheet:pg.Surface, x:int, y:int, width:int, height:int,
colorkey:tuple[int]=c.BLACK, scale:int=1) -> pg.Surface:
# 保留alpha通道的图片导入
image = pg.Surface([width, height], SRCALPHA)
rect = image.get_rect()
image.blit(sheet, (0, 0), (x, y, width, height))
image.set_colorkey(colorkey)
image = pg.transform.scale(
image, (int(rect.width * scale), int(rect.height * scale))
)
return image
def load_image_frames(
directory: str, image_name: str, colorkey: tuple[int], accept: tuple[str]
) -> list[pg.Surface]:
image = pg.transform.scale(image,
(int(rect.width*scale),
int(rect.height*scale)))
return image
def load_image_frames( directory:str, image_name:str,
colorkey:tuple[int], accept:tuple[str]) -> list[pg.Surface]:
frame_list = []
tmp = {}
# image_name is "Peashooter", pic name is "Peashooter_1", get the index 1
index_start = len(image_name) + 1
index_start = len(image_name) + 1
frame_num = 0
for pic in os.listdir(directory):
name, ext = os.path.splitext(pic)
@ -236,20 +199,16 @@ def load_image_frames(
else:
img = img.convert()
img.set_colorkey(colorkey)
tmp[index] = img
tmp[index]= img
frame_num += 1
for i in range(frame_num): # 这里注意编号必须连续,否则会出错
frame_list.append(tmp[i])
return frame_list
# colorkeys 是设置图像中的某个颜色值为透明,这里用来消除白边
def load_all_gfx(
directory: str,
colorkey: tuple[int] = c.WHITE,
accept: tuple[str] = ('.png', '.jpg', '.bmp', '.gif', '.webp'),
) -> dict[str : pg.Surface]:
def load_all_gfx( directory: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
@ -258,25 +217,21 @@ def load_all_gfx(
for name2 in os.listdir(dir1):
dir2 = os.path.join(dir1, name2)
if os.path.isdir(dir2):
# e.g. subfolders under the folder resources\graphics\Zombies
# e.g. subfolders under the folder resources\graphics\Zombies
for name3 in os.listdir(dir2):
dir3 = os.path.join(dir2, name3)
# e.g. subfolders or pics under the folder resources\graphics\Zombies\ConeheadZombie
if os.path.isdir(dir3):
# e.g. it"s the folder resources\graphics\Zombies\ConeheadZombie\ConeheadZombieAttack
image_name, _ = os.path.splitext(name3)
graphics[image_name] = load_image_frames(
dir3, image_name, colorkey, accept
)
graphics[image_name] = load_image_frames(dir3, image_name, colorkey, accept)
else:
# e.g. pics under the folder resources\graphics\Plants\Peashooter
image_name, _ = os.path.splitext(name2)
graphics[image_name] = load_image_frames(
dir2, image_name, colorkey, accept
)
graphics[image_name] = load_image_frames(dir2, image_name, colorkey, accept)
break
else:
# e.g. pics under the folder resources\graphics\Screen
# e.g. pics under the folder resources\graphics\Screen
name, ext = os.path.splitext(name2)
if ext.lower() in accept:
img = pg.image.load(dir2)
@ -288,13 +243,10 @@ def load_all_gfx(
graphics[name] = img
return graphics
pg.display.set_caption(c.ORIGINAL_CAPTION) # 设置标题
SCREEN = pg.display.set_mode(c.SCREEN_SIZE, pg.SCALED) # 设置初始屏幕
SCREEN = pg.display.set_mode(c.SCREEN_SIZE) # 设置初始屏幕
pg.mixer.set_num_channels(255) # 设置可以同时播放的音频数量默认为8经常不够用
if os.path.exists(
c.ORIGINAL_LOGO
): # 设置窗口图标仅对非Nuitka时生效Nuitka不需要包括额外的图标文件自动跳过这一过程即可
if os.path.exists(c.ORIGINAL_LOGO): # 设置窗口图标仅对非Nuitka时生效Nuitka不需要包括额外的图标文件自动跳过这一过程即可
pg.display.set_icon(pg.image.load(c.ORIGINAL_LOGO))
GFX = load_all_gfx(c.PATH_IMG_DIR)

209
uv.lock generated
View File

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