import sys
import json
import os
from typing import List
from PyQt6.QtWidgets import (
QApplication,
QMainWindow,
QWidget,
QVBoxLayout,
QHBoxLayout,
QPushButton,
QLabel,
QListWidget,
QInputDialog,
QMessageBox,
QGroupBox,
QFileDialog,
QStatusBar,
)
from PyQt6.QtGui import QPixmap, QIcon
from PyQt6.QtCore import Qt
# 数据保存文件路径
DATA_FILE = "pets_data.json"
class Pet:
"""表示一只宠物的状态和行为。"""
# 记录不同物种的数量
species_counter = {}
def __init__(
self,
name: str,
animal_type: str,
color: str,
age: int,
image_path: str = "",
happiness: int = 60,
hunger: int = 40,
cleanliness: int = 70,
) -> None:
self.name = name
self.animal_type = animal_type
self.color = color
self.age = age
# 通过 property 统一限制在 0~100 之间
self._happiness = happiness
self._hunger = hunger
self._cleanliness = cleanliness
self.image_path = image_path
# 更新物种统计
Pet.species_counter[animal_type] = Pet.species_counter.get(animal_type, 0) + 1
@property
def happiness(self) -> int:
"""幸福度:0 ~ 100,越高表示越开心。"""
return self._happiness
@happiness.setter
def happiness(self, value: int) -> None:
self._happiness = max(0, min(100, value))
@property
def hunger(self) -> int:
"""饥饿值:0 ~ 100,越高表示越饿。"""
return self._hunger
@hunger.setter
def hunger(self, value: int) -> None:
self._hunger = max(0, min(100, value))
@property
def cleanliness(self) -> int:
"""清洁度:0 ~ 100,越高表示越干净。"""
return self._cleanliness
@cleanliness.setter
def cleanliness(self, value: int) -> None:
self._cleanliness = max(0, min(100, value))
def get_name(self) -> str:
return self.name
def get_animal_type(self) -> str:
return self.animal_type
def get_color(self) -> str:
return self.color
def get_age(self) -> int:
return self.age
def get_image_path(self) -> str:
return self.image_path
# -------- 行为 --------
def play(self) -> None:
"""陪宠物玩耍。"""
self.happiness += 15
self.hunger += 10
self.cleanliness -= 8
def feed(self) -> None:
"""喂食。"""
self.hunger -= 25
self.happiness += 5
self.cleanliness -= 3
def bath(self) -> None:
"""洗澡。"""
self.cleanliness += 30
self.happiness -= 3
def rest(self) -> None:
"""休息。"""
self.happiness += 5
self.hunger += 5
def __str__(self) -> str:
return (
f"{self.name} ({self.animal_type}, {self.color}, {self.age}岁, "
f"幸福度 {self.happiness}, 饥饿 {self.hunger}, 清洁度 {self.cleanliness})"
)
def to_dict(self) -> dict:
"""转换为可保存的字典。"""
return {
"name": self.name,
"animal_type": self.animal_type,
"color": self.color,
"age": self.age,
"happiness": self.happiness,
"hunger": self.hunger,
"cleanliness": self.cleanliness,
"image_path": self.image_path,
}
@staticmethod
def from_dict(data: dict) -> "Pet":
"""从字典还原为 Pet 对象。"""
return Pet(
name=data.get("name", "未命名"),
animal_type=data.get("animal_type", "未知"),
color=data.get("color", "未知"),
age=int(data.get("age", 0)),
image_path=data.get("image_path", ""),
happiness=int(data.get("happiness", 60)),
hunger=int(data.get("hunger", 40)),
cleanliness=int(data.get("cleanliness", 70)),
)
# 简单的界面样式
APP_STYLE = """
QMainWindow {
background-color: #f5f7fa;
}
QLabel#TitleLabel {
font-size: 28px;
font-weight: 700;
color: #2c3e50;
}
QLabel, QListWidget, QGroupBox {
font-size: 16px;
}
QGroupBox {
font-weight: bold;
border: 1px solid #d0d7de;
border-radius: 8px;
margin-top: 10px;
}
QGroupBox::title {
subcontrol-origin: margin;
subcontrol-position: top left;
padding: 0 8px;
}
QPushButton {
font-size: 16px;
padding: 8px 18px;
border-radius: 10px;
border: 1px solid #3498db;
background-color: #3498db;
color: white;
}
QPushButton:hover {
background-color: #2980b9;
}
QPushButton:disabled {
background-color: #bdc3c7;
border-color: #95a5a6;
}
QListWidget {
background-color: white;
border-radius: 8px;
padding: 4px;
}
"""
class PetManagerWindow(QMainWindow):
"""主窗口。"""
def __init__(self) -> None:
super().__init__()
self.pet_list: List[Pet] = []
self.selected_pet_index: int = -1
# 先加载数据,再初始化界面
self.load_pets()
self.init_ui()
def init_ui(self) -> None:
"""初始化界面。"""
self.setWindowTitle("宠物管理系统")
self.setGeometry(100, 100, 980, 720)
self.setStyleSheet(APP_STYLE)
if os.path.exists("icon.png"):
self.setWindowIcon(QIcon("icon.png"))
central_widget = QWidget()
self.setCentralWidget(central_widget)
main_layout = QVBoxLayout()
main_layout.setContentsMargins(20, 10, 20, 10)
main_layout.setSpacing(12)
# 标题
title_label = QLabel("🐾 宠物管理系统")
title_label.setObjectName("TitleLabel")
title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
main_layout.addWidget(title_label)
# 顶部按钮区域
top_layout = QHBoxLayout()
top_layout.setSpacing(10)
self.add_pet_button = QPushButton("➕ 添加新宠物")
self.add_pet_button.clicked.connect(self.add_pet)
self.delete_pet_button = QPushButton("🗑 删除选中宠物")
self.delete_pet_button.clicked.connect(self.delete_selected_pet)
self.delete_pet_button.setEnabled(False)
self.pet_count_label = QLabel(f"当前宠物数量: {len(self.pet_list)}")
self.pet_count_label.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
top_layout.addWidget(self.add_pet_button)
top_layout.addWidget(self.delete_pet_button)
top_layout.addStretch()
top_layout.addWidget(self.pet_count_label)
main_layout.addLayout(top_layout)
# 中间区域:左侧列表,右侧详情与互动
middle_layout = QHBoxLayout()
middle_layout.setSpacing(15)
# 左侧:宠物列表
left_group = QGroupBox("宠物列表")
left_layout = QVBoxLayout()
left_layout.setContentsMargins(10, 10, 10, 10)
self.pet_list_widget = QListWidget()
self.pet_list_widget.itemClicked.connect(self.select_pet)
left_layout.addWidget(self.pet_list_widget)
left_group.setLayout(left_layout)
middle_layout.addWidget(left_group, stretch=2)
# 右侧:图片 + 信息 + 按钮
right_group = QGroupBox("宠物详情")
right_layout = QVBoxLayout()
right_layout.setContentsMargins(10, 10, 10, 10)
right_layout.setSpacing(12)
# 宠物图片显示
self.pet_image_label = QLabel("暂无图片")
self.pet_image_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.pet_image_label.setStyleSheet(
"border: 1px solid #d0d7de; background: white; border-radius: 10px;"
)
self.pet_image_label.setFixedSize(340, 320)
self.pet_image_label.setScaledContents(True)
right_layout.addWidget(self.pet_image_label, alignment=Qt.AlignmentFlag.AlignCenter)
# 选中宠物信息
self.selected_pet_label = QLabel("在左侧选择一只宠物。")
self.selected_pet_label.setWordWrap(True)
self.selected_pet_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
right_layout.addWidget(self.selected_pet_label)
# 互动按钮
button_layout = QHBoxLayout()
button_layout.setSpacing(10)
self.play_button = QPushButton("陪它玩耍")
self.play_button.clicked.connect(lambda: self.interact_with_pet("play"))
self.play_button.setEnabled(False)
self.feed_button = QPushButton("喂它吃饭")
self.feed_button.clicked.connect(lambda: self.interact_with_pet("feed"))
self.feed_button.setEnabled(False)
self.bath_button = QPushButton("帮它洗澡")
self.bath_button.clicked.connect(lambda: self.interact_with_pet("bath"))
self.bath_button.setEnabled(False)
self.rest_button = QPushButton("让它休息")
self.rest_button.clicked.connect(lambda: self.interact_with_pet("rest"))
self.rest_button.setEnabled(False)
button_layout.addWidget(self.play_button)
button_layout.addWidget(self.feed_button)
button_layout.addWidget(self.bath_button)
button_layout.addWidget(self.rest_button)
right_layout.addLayout(button_layout)
right_group.setLayout(right_layout)
middle_layout.addWidget(right_group, stretch=3)
main_layout.addLayout(middle_layout)
# 底部统计信息区域
stats_group = QGroupBox("统计信息")
stats_layout = QVBoxLayout()
stats_layout.setContentsMargins(10, 8, 10, 8)
self.stats_label = QLabel("暂无宠物数据,请先添加宠物。")
self.stats_label.setWordWrap(True)
stats_layout.addWidget(self.stats_label)
stats_group.setLayout(stats_layout)
main_layout.addWidget(stats_group)
central_widget.setLayout(main_layout)
# 状态栏
status_bar = QStatusBar()
status_bar.setStyleSheet("font-size: 14px;")
self.setStatusBar(status_bar)
# 更新列表和统计信息显示
self.update_pet_list()
self.update_stats_panel()
# -------- 宠物列表相关 --------
def add_pet(self) -> None:
"""添加宠物。"""
name, ok = QInputDialog.getText(self, "添加宠物", "请输入宠物名字:")
if not ok or not name.strip():
return
animal_type, ok = QInputDialog.getText(self, "添加宠物", "请输入宠物类型 (例如: 狗、猫):")
if not ok or not animal_type.strip():
return
color, ok = QInputDialog.getText(self, "添加宠物", "请输入宠物颜色:")
if not ok or not color.strip():
return
try:
age, ok = QInputDialog.getInt(self, "添加宠物", "请输入宠物年龄:", value=1, min=0, max=30)
except Exception as e:
QMessageBox.warning(self, "输入错误", f"获取年龄失败:{e}")
return
if not ok:
return
image_path, _ = QFileDialog.getOpenFileName(
self,
"选择宠物图片(可选)",
"",
"图像文件 (*.png *.jpg *.jpeg *.bmp *.gif)",
)
pet = Pet(
name=name.strip(),
animal_type=animal_type.strip(),
color=color.strip(),
age=age,
image_path=image_path if image_path else "",
)
self.pet_list.append(pet)
self.update_pet_list()
self.update_stats_panel()
QMessageBox.information(self, "成功", f"成功添加宠物: {pet.get_name()}")
def delete_selected_pet(self) -> None:
"""删除当前选中的宠物。"""
if not (0 <= self.selected_pet_index < len(self.pet_list)):
return
pet = self.pet_list[self.selected_pet_index]
reply = QMessageBox.question(
self,
"确认删除",
f"确定要删除宠物:{pet.get_name()} 吗?",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
)
if reply == QMessageBox.StandardButton.Yes:
if pet.animal_type in Pet.species_counter:
Pet.species_counter[pet.animal_type] -= 1
if Pet.species_counter[pet.animal_type] <= 0:
del Pet.species_counter[pet.animal_type]
del self.pet_list[self.selected_pet_index]
self.selected_pet_index = -1
self.pet_image_label.setPixmap(QPixmap())
self.pet_image_label.setText("暂无图片")
self.selected_pet_label.setText("在左侧选择一只宠物。")
for btn in (self.play_button, self.feed_button, self.bath_button, self.rest_button):
btn.setEnabled(False)
self.delete_pet_button.setEnabled(False)
self.update_pet_list()
self.update_stats_panel()
def update_pet_list(self) -> None:
"""刷新左侧宠物列表。"""
self.pet_list_widget.clear()
for i, pet in enumerate(self.pet_list, 1):
pet_info = (
f"{i}. {pet.get_name()} | 类型: {pet.get_animal_type()} | 颜色: {pet.get_color()} | "
f"{pet.get_age()}岁 | 幸福度: {pet.happiness} | 饥饿: {pet.hunger} | 清洁度: {pet.cleanliness}"
)
self.pet_list_widget.addItem(pet_info)
self.pet_count_label.setText(f"当前宠物数量: {len(self.pet_list)}")
def select_pet(self, item) -> None:
"""选择宠物,更新右侧详情。"""
self.selected_pet_index = self.pet_list_widget.row(item)
self.delete_pet_button.setEnabled(True)
if 0 <= self.selected_pet_index < len(self.pet_list):
pet = self.pet_list[self.selected_pet_index]
self.selected_pet_label.setText(
f"已选择: {pet.get_name()} "
f"({pet.get_animal_type()}, {pet.get_color()}, {pet.get_age()}岁)\n"
f"当前状态:幸福度 {pet.happiness},饥饿 {pet.hunger},清洁度 {pet.cleanliness}"
)
if pet.get_image_path() and os.path.exists(pet.get_image_path()):
pixmap = QPixmap(pet.get_image_path())
if not pixmap.isNull():
self.pet_image_label.setPixmap(pixmap)
self.pet_image_label.setText("")
else:
self.pet_image_label.setPixmap(QPixmap())
self.pet_image_label.setText("图片加载失败")
else:
self.pet_image_label.setPixmap(QPixmap())
self.pet_image_label.setText(
"暂无图片" if not pet.get_image_path() else "图片文件不存在"
)
for btn in (self.play_button, self.feed_button, self.bath_button, self.rest_button):
btn.setEnabled(True)
# -------- 互动逻辑 --------
def interact_with_pet(self, interaction_type: str) -> None:
"""与宠物互动。"""
if not (0 <= self.selected_pet_index < len(self.pet_list)):
return
pet = self.pet_list[self.selected_pet_index]
actions = {
"play": (pet.play, "陪它玩耍", "幸福+15,饥饿+10,清洁-8"),
"feed": (pet.feed, "喂它吃饭", "幸福+5,饥饿-25,清洁-3"),
"bath": (pet.bath, "帮它洗澡", "幸福-3,清洁+30"),
"rest": (pet.rest, "让它休息", "幸福+5,饥饿+5"),
}
func, action_name, change = actions.get(
interaction_type, (pet.rest, "让它休息", "幸福+5,饥饿+5")
)
func()
self.update_pet_list()
self.update_stats_panel()
self.selected_pet_label.setText(
f"已选择: {pet.get_name()} "
f"({pet.get_animal_type()}, {pet.get_color()}, {pet.get_age()}岁)\n"
f"当前状态:幸福度 {pet.happiness},饥饿 {pet.hunger},清洁度 {pet.cleanliness}\n"
f"本次操作:{action_name}({change})"
)
if pet.get_image_path() and os.path.exists(pet.get_image_path()):
self.pet_image_label.setPixmap(QPixmap(pet.get_image_path()))
self.statusBar().showMessage(
f"已对 {pet.get_name()} 执行操作:{action_name}({change})", 3000
)
# -------- 统计信息 --------
def update_stats_panel(self) -> None:
"""更新底部统计信息。"""
if not self.pet_list:
self.stats_label.setText("暂无宠物数据,请先添加宠物。")
return
happiness_values = [p.happiness for p in self.pet_list]
hunger_values = [p.hunger for p in self.pet_list]
cleanliness_values = [p.cleanliness for p in self.pet_list]
avg_happiness = sum(happiness_values) / len(happiness_values)
avg_hunger = sum(hunger_values) / len(hunger_values)
avg_cleanliness = sum(cleanliness_values) / len(cleanliness_values)
happiest_pet = max(self.pet_list, key=lambda p: p.happiness)
most_hungry_pet = max(self.pet_list, key=lambda p: p.hunger)
species_set = {p.animal_type for p in self.pet_list}
species_str = "、".join(species_set)
self.stats_label.setText(
f"平均幸福度:{avg_happiness:.1f},平均饥饿值:{avg_hunger:.1f}(越低越好),"
f"平均清洁度:{avg_cleanliness:.1f};\n"
f"最幸福的宠物:{happiest_pet.name}({happiest_pet.happiness}),"
f"最饥饿的宠物:{most_hungry_pet.name}({most_hungry_pet.hunger}),"
f"当前宠物种类:{species_str}。"
)
# -------- 数据持久化 --------
def load_pets(self) -> None:
"""从本地文件加载宠物数据。"""
if not os.path.exists(DATA_FILE):
return
try:
with open(DATA_FILE, "r", encoding="utf-8") as f:
data = json.load(f)
self.pet_list = [Pet.from_dict(item) for item in data]
except Exception as e:
QMessageBox.warning(self, "加载失败", f"无法加载宠物数据:{e}\n将使用空数据启动。")
self.pet_list = []
def save_pets(self) -> None:
"""将当前宠物数据保存到本地文件。"""
try:
data = [pet.to_dict() for pet in self.pet_list]
with open(DATA_FILE, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=4)
except Exception as e:
QMessageBox.critical(self, "保存失败", f"无法保存宠物数据:{e}")
def closeEvent(self, event) -> None:
"""窗口关闭事件。"""
self.save_pets()
event.accept()
def main() -> None:
"""程序入口。"""
app = QApplication(sys.argv)
window = PetManagerWindow()
window.show()
sys.exit(app.exec())
if __name__ == "__main__":
main() 4.7 题目六:神奇魔杖魔法书
4.7.1 文件与类命名
文件:magic_wand_manager.py
数据文件:`DATA_FILE = "wands_data.json"`
实体类:class MagicWand:
主窗口类:class MagicWandManagerWindow(QMainWindow):
4.7.2 MagicWand 类设计
属性:
name: str — 魔杖名称
element: str — 元素属性(火/水/风/土/光/暗)
core: str — 材质(如龙心弦/独角兽毛等)
power: int — 魔力强度 0~100
durability: int — 耐久度 0~100
mastery: int — 熟练度 0~100
charge: int — 当前充能 0~100
image_path: str — 魔杖图片
行为方法:
cast_spell()
若 charge < 10 或 durability < 10:
可选择直接返回(由外部弹窗提示“无法施法”)
否则:
charge -= 15
durability -= 5
power += 2
mastery += 5
recharge()
charge += 25(由 @property 限制不超过 100)
mastery += 1
polish()
durability += 20
mastery += 2
study_spellbook()
mastery += 10
charge -= 5
4.7.3 主窗口命名
self.wand_list: List[MagicWand]
self.selected_wand_index: int
self.wand_list_widget: QListWidget
self.wand_image_label: QLabel
self.selected_wand_label: QLabel
按钮:
self.cast_button
self.recharge_button
self.polish_button
self.study_button
统计内容:
平均魔力 avg_power
平均熟练度 avg_mastery
施法最强(power 最大)的魔杖
元素种类集合 . 程序要求(与“宠物管理系统”对齐)
所有项目需仿照 宠物管理系统 的结构与风格来实现:
使用 PyQt6 开发图形界面:
左侧:对象列表
(植物 / 偶像 / 英雄卡牌 / 飞船 / 歌曲 / 魔杖 / 车手)
右侧:详情信息 + 多个互动按钮
(行为示例:训练、修理、播放、施法、比赛等)
底部:统计信息
(平均值、最强对象、种类集合等)
至少包含:
一个实体类(如 Plant、Idol、HeroCard 等),需包括:
属性(状态字段)
行为方法
to_dict() / from_dict() 用于 JSON 持久化
一个主窗口类(如 PlantManagerWindow),包含:
列表控件、详情标签、图片标签、多个按钮、统计标签
典型方法命名参考:
add_xxx()
delete_selected_xxx()
update_xxx_list()
select_xxx()
interact_with_xxx()
update_stats_panel()
load_xxx()
save_xxx()
数据持久化:
使用 JSON 文件 进行数据保存与加载(类似 pets_data.json)。
风格要求:
代码风格、命名风格需与 pet_manager_system.py 一致。依照上方的代码,重新输出一个魔杖魔法书系统的代码,包括如何复制到程序上,及图片如何添加,步骤详细一些,是从来没接触过这方面的小白