你好
{
"riddles": [
{
"question": "四个'口'围小狗",
"answer": "器",
"hint": "注意'犬'字的位置",
"difficulty": 3
},
{
"question": "山上还有山",
"answer": "出",
"hint": "两个'山'字叠加",
"difficulty": 2
},
{
"question": "一加一不是二",
"answer": "王",
"hint": "注意字形",
"difficulty": 3
},
{
"question": "九十九",
"answer": "白",
"hint": "一百减一",
"difficulty": 2
},
{
"question": "夫人回娘家",
"answer": "圭",
"hint": "注意'女'字的位置",
"difficulty": 3
}
],
"xiehouyu": [
{
"first_part": "张飞审西瓜",
"second_part": "粗中有细",
"explanation": "猛将也有细心时"
},
{
"first_part": "孔夫子搬家",
"second_part": "净是书(输)",
"explanation": "谐音双关"
},
{
"first_part": "外甥打灯笼",
"second_part": "照舅(旧)",
"explanation": "谐音双关"
},
{
"first_part": "和尚打伞",
"second_part": "无法无天",
"explanation": "双关语"
},
{
"first_part": "泥菩萨过河",
"second_part": "自身难保",
"explanation": "比喻自身难保"
},
{
"first_part": "狗拿耗子",
"second_part": "多管闲事",
"explanation": "比喻多管闲事"
},
{
"first_part": "猪八戒照镜子",
"second_part": "里外不是人",
"explanation": "比喻两面不讨好"
},
{
"first_part": "老虎屁股",
"second_part": "摸不得",
"explanation": "比喻不能招惹"
},
{
"first_part": "老鼠过街",
"second_part": "人人喊打",
"explanation": "比喻人人痛恨"
},
{
"first_part": "竹篮打水",
"second_part": "一场空",
"explanation": "比喻白费力气"
},
{
"first_part": "瞎子点灯",
"second_part": "白费蜡",
"explanation": "比喻白费力气"
},
{
"first_part": "黄鼠狼给鸡拜年",
"second_part": "没安好心",
"explanation": "比喻不怀好意"
},
{
"first_part": "哑巴吃黄连",
"second_part": "有苦说不出",
"explanation": "比喻有苦难言"
},
{
"first_part": "骑驴看唱本",
"second_part": "走着瞧",
"explanation": "比喻看事情发展"
},
{
"first_part": "铁公鸡",
"second_part": "一毛不拔",
"explanation": "比喻非常吝啬"
},
{
"first_part": "鸡蛋碰石头",
"second_part": "不自量力",
"explanation": "比喻自不量力"
},
{
"first_part": "猫哭老鼠",
"second_part": "假慈悲",
"explanation": "比喻假慈悲"
},
{
"first_part": "猴子捞月亮",
"second_part": "白忙一场",
"explanation": "比喻白费力气"
},
{
"first_part": "画蛇添足",
"second_part": "多此一举",
"explanation": "比喻多余的行为"
},
{
"first_part": "对牛弹琴",
"second_part": "白费劲",
"explanation": "比喻对不懂的人说教"
}
],
"version": "1.0.0"
}
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import json
import random
from PIL import Image, ImageTk
import time
class HanziAdventure:
def __init__(self):
self.window = tk.Tk()
self.window.title("汉字秘境闯关")
self.window.geometry("800x600")
self.score_default = False # 初始化 score_default
# 添加设置按钮,放在底部正中间
self.settings_button = ttk.Button(self.window, text="⚙️ 设置", command=self.show_settings_window)
self.settings_button.place(relx=0.5, rely=1.0, anchor='s', y=-10)
# 加载题库
with open('questions.json', 'r', encoding='utf-8') as f:
self.questions = json.load(f)
self.score = 0
# 初始化默认时间为 10 分钟
self.DEFAULT_TIME_SECOND = 600 # 10 * 60
self.time_left = self.DEFAULT_TIME_SECOND
self.used_questions = {
"xiehouyu": set(),
"riddles": set()
} # 记录已使用的题目索引
self.rule_window = None # 规则窗口
self.version_window = None # 版本更新窗口 # 新增
self.current_question_type = None # 当前题目类型
self.current_question = None # 当前题目
# 创建界面
self.create_welcome_page()
def clear_window(self):
"""清除窗口中的所有内容"""
if hasattr(self, 'timer_id'): # 如果计时器存在
self.window.after_cancel(self.timer_id) # 停止计时器
for widget in self.window.winfo_children():
if widget != self.settings_button: # 保留设置按钮
widget.destroy()
# 确保设置按钮始终在底部正中间
self.settings_button.lift() # 将按钮置于最上层
self.settings_button.place(relx=0.5, rely=1.0, anchor='s', y=-10)
def create_welcome_page(self):
"""创建欢迎页面"""
self.clear_window()
# 重置已使用的题目索引
for key in self.used_questions:
self.used_questions[key] = set()
tk.Label(self.window, text="汉字秘境探险", font=("楷体", 36)).pack(pady=50)
ttk.Button(self.window, text="开始挑战", command=self.main_game_loop).pack(pady=10)
ttk.Button(self.window, text="挑战规则", command=self.show_rules).pack(pady=10)
ttk.Button(self.window, text="更新谜语版本", command=self.show_version_window).pack(pady=10) # 删除这行代码:self.settings_button.place(relx=1.0, rely=0.0, anchor='ne', x=-10, y=10)
def load_questions(self):
"""加载题库"""
try:
with open(self.questions_file, 'r', encoding='utf-8') as f:
self.questions = json.load(f)
except Exception as e:
messagebox.showerror("错误", f"加载题库失败: {str(e)}")
self.questions = {"xiehouyu": [], "riddles": []}
def show_version_window(self):
"""显示版本更新窗口"""
if self.version_window is not None and self.version_window.winfo_exists():
return # 如果窗口已经存在,则不再创建
self.version_window = tk.Toplevel(self.window)
self.version_window.title("更新谜语版本")
self.version_window.geometry("400x300")
# 显示当前版本信息
version = self.get_current_version()
tk.Label(self.version_window, text=f"当前版本: {version}", font=("楷体", 14)).pack(pady=20)
# 选择新题库文件
ttk.Button(self.version_window, text="选择新题库文件", command=self.select_new_questions_file).pack(pady=10)
# 关闭窗口时重置变量
self.version_window.protocol("WM_DELETE_WINDOW", self.close_version_window)
def get_current_version(self):
"""获取当前版本"""
try:
with open(self.questions_file, 'r', encoding='utf-8') as f:
data = json.load(f)
return data.get("version", "未知")
except:
# 如果当前打开了加载题库默认的 questions.json 文件,返回 questions.json 文件的版本号
try:
with open('questions.json', 'r', encoding='utf-8') as f:
data = json.load(f)
return data.get("version", "未知")
except:
return "未知"
def select_new_questions_file(self):
"""选择新的题库文件"""
file_path = tk.filedialog.askopenfilename(
title="选择题库文件",
filetypes=[("JSON 文件", "*.json")],
initialdir="."
)
if file_path:
if self.validate_questions_file(file_path):
self.questions_file = file_path
print(f"新的题库文件已选择: {self.questions_file}")
self.load_questions()
messagebox.showinfo("成功", "题库文件已选择!")
self.version_window.destroy()
else:
messagebox.showerror("错误", "选择的题库文件格式不正确")
def validate_questions_file(self, file_path):
"""验证题库文件格式"""
try:
with open(file_path, 'r', encoding='utf-8') as f:
data = json.load(f)
if "xiehouyu" in data and "riddles" in data:
return True
return False
except:
return False
def close_version_window(self):
"""关闭版本更新窗口"""
self.version_window.destroy()
self.version_window = None
def create_game_frame(self):
"""创建游戏主框架"""
self.top_frame = tk.Frame(self.window)
self.top_frame.pack(fill=tk.X)
self.score_label = tk.Label(self.top_frame, text=f"积分: {self.score}")
self.score_label.pack(side=tk.LEFT)
self.timer_label = tk.Label(self.top_frame, text="剩余时间: 20:00")
self.timer_label.pack(side=tk.RIGHT)
self.main_frame = tk.Frame(self.window)
self.main_frame.pack(expand=True, fill=tk.BOTH)
# 底部按钮
self.bottom_frame = tk.Frame(self.window)
self.bottom_frame.pack(fill=tk.X, side=tk.BOTTOM)
ttk.Button(self.bottom_frame, text="结束挑战", command=self.show_result).pack(side=tk.LEFT, padx=10)
ttk.Button(self.bottom_frame, text="挑战说明", command=self.show_rules).pack(side=tk.RIGHT, padx=10)
def update_timer(self):
"""更新计时器"""
if not hasattr(self, 'timer_label') or not self.timer_label.winfo_exists():
return # 如果计时器标签不存在,直接返回
mins, secs = divmod(self.time_left, 60)
self.timer_label.config(text=f"剩余时间: {mins:02d}:{secs:02d}")
if self.time_left > 0:
self.time_left -= 1
self.timer_id = self.window.after(1000, self.update_timer) # 保存计时器 ID
else:
self.show_result()
def show_xiehouyu(self):
"""显示歇后语题目"""
if len(self.used_questions["xiehouyu"]) >= len(self.questions["xiehouyu"]):
# 如果没有更多歇后语题目,检查是否还有字谜题目
if len(self.used_questions["riddles"]) < len(self.questions["riddles"]):
self.show_riddles()
else:
self.show_result() # 所有题目都用完,直接结束游戏
return
# 确保 main_frame 存在
if not hasattr(self, 'main_frame') or not self.main_frame.winfo_exists():
self.create_game_frame() # 如果 main_frame 不存在,重新创建
# 随机选择一个未使用过的题目
available_questions = [
i for i in range(len(self.questions["xiehouyu"]))
if i not in self.used_questions["xiehouyu"]
]
question_index = random.choice(available_questions)
self.current_question = self.questions["xiehouyu"][question_index]
self.current_question_type = "xiehouyu"
# 检查题目格式是否正确
if 'first_part' not in self.current_question or 'second_part' not in self.current_question:
# 记录错误日志
with open('error_log.txt', 'a', encoding='utf-8') as f:
f.write(f"歇后语题目格式错误,时间: {time.strftime('%Y-%m-%d %H:%M:%S')}\n")
f.write(f"题目内容: {str(self.current_question)}\n\n")
# 跳过该题目
self.used_questions["xiehouyu"].add(question_index)
self.show_xiehouyu()
return
self.used_questions["xiehouyu"].add(question_index)
question = f"{self.current_question['first_part']} —— ?"
# 清除主框架内容
for widget in self.main_frame.winfo_children():
widget.destroy()
# 显示题目
self.question_label = tk.Label(self.main_frame, text=question, font=("楷体", 24))
self.question_label.pack(pady=20)
# 输入框
self.answer_entry = ttk.Entry(self.main_frame, font=("楷体", 18))
self.answer_entry.pack(pady=10)
# 提交按钮
ttk.Button(self.main_frame, text="提交答案", command=self.check_answer).pack()
def show_riddles(self):
"""显示字谜题目"""
if len(self.used_questions["riddles"]) >= len(self.questions["riddles"]):
# 如果没有更多字谜题目,检查是否还有歇后语题目
if len(self.used_questions["xiehouyu"]) < len(self.questions["xiehouyu"]):
self.show_xiehouyu()
else:
self.show_result() # 所有题目都用完,直接结束游戏
return
# 确保 main_frame 存在
if not hasattr(self, 'main_frame') or not self.main_frame.winfo_exists():
self.create_game_frame() # 如果 main_frame 不存在,重新创建
# 随机选择一个未使用过的题目
available_questions = [
i for i in range(len(self.questions["riddles"]))
if i not in self.used_questions["riddles"]
]
question_index = random.choice(available_questions)
self.current_question = self.questions["riddles"][question_index]
self.current_question_type = "riddles"
# 检查题目格式是否正确
if 'question' not in self.current_question or 'answer' not in self.current_question:
# 记录错误日志
with open('error_log.txt', 'a', encoding='utf-8') as f:
f.write(f"字谜题目格式错误,时间: {time.strftime('%Y-%m-%d %H:%M:%S')}\n")
f.write(f"题目内容: {str(self.current_question)}\n\n")
# 跳过该题目
self.used_questions["riddles"].add(question_index)
self.show_riddles()
return
self.used_questions["riddles"].add(question_index)
# 动态计算答案字数
answer_length = len(self.current_question["answer"])
question = f"{self.current_question['question']}(答案字数: {answer_length})"
# 清除主框架内容
for widget in self.main_frame.winfo_children():
widget.destroy()
# 显示题目
self.question_label = tk.Label(self.main_frame, text=question, font=("楷体", 24))
self.question_label.pack(pady=20)
# 输入框
self.answer_entry = ttk.Entry(self.main_frame, font=("楷体", 18))
self.answer_entry.pack(pady=10)
# 提交按钮
ttk.Button(self.main_frame, text="提交答案", command=self.check_answer).pack()
def check_answer(self):
"""检查答案"""
user_answer = self.answer_entry.get().strip()
try:
if self.current_question_type == "xiehouyu":
correct_answer = self.current_question["second_part"]
elif self.current_question_type == "riddles":
correct_answer = self.current_question["answer"]
except KeyError:
with open('error_log.txt', 'a', encoding='utf-8') as f:
f.write(f"题目格式错误,时间: {time.strftime('%Y-%m-%d %H:%M:%S')}\n")
f.write(f"题目类型: {self.current_question_type}\n")
f.write(f"题目内容: {str(self.current_question)}\n\n")
# 更友好的提示并自动跳转到下一题
messagebox.showwarning("提示", "当前题目数据异常,将自动跳转到下一题")
self.main_game_loop()
return
if user_answer == correct_answer:
self.score += 10
self.score_label.config(text=f"积分: {self.score}")
messagebox.showinfo("正确", "回答正确!")
self.main_game_loop() # 答对后直接更新题目
else:
self.show_error_buttons() # 答错后显示按钮
def show_error_buttons(self):
"""显示答错后的按钮"""
# 隐藏输入框和提交按钮
self.answer_entry.pack_forget()
for widget in self.main_frame.winfo_children():
if isinstance(widget, ttk.Button) and widget["text"] == "提交答案":
widget.pack_forget()
# 显示按钮
ttk.Button(self.main_frame, text="重新答题", command=self.reset_question).pack(pady=5)
ttk.Button(self.main_frame, text="展示答案", command=self.show_answer).pack(pady=5)
ttk.Button(self.main_frame, text="下一题", command=self.main_game_loop).pack(pady=5)
def reset_question(self):
"""重新答题"""
# 清空输入框并重新显示输入框和提交按钮
self.answer_entry.delete(0, tk.END)
self.answer_entry.pack(pady=10)
ttk.Button(self.main_frame, text="提交答案", command=self.check_answer).pack()
# 隐藏错误按钮
for widget in self.main_frame.winfo_children():
if isinstance(widget, ttk.Button) and widget["text"] in ["重新答题", "展示答案", "下一题"]:
widget.pack_forget()
def show_answer(self):
"""展示答案"""
# 创建自定义对话框
answer_window = tk.Toplevel(self.window)
answer_window.title("正确答案")
# 设置答案和寓意文本
if self.current_question_type == "xiehouyu":
answer_text = self.current_question["second_part"]
explanation_text = self.current_question["explanation"]
elif self.current_question_type == "riddles":
answer_text = self.current_question["answer"]
explanation_text = "无寓意" # 字谜没有寓意字段
# 答案部分
tk.Label(answer_window, text="答案:", font=("楷体", 14)).pack(pady=(10, 0))
answer_box = tk.Text(answer_window, wrap=tk.WORD, height=1, width=20, font=("楷体", 14), bg=self.window.cget("bg"))
answer_box.insert(tk.END, answer_text)
answer_box.config(state=tk.DISABLED) # 设置为只读
answer_box.pack(padx=20, pady=5)
# 含义部分
tk.Label(answer_window, text="含义:", font=("楷体", 14)).pack(pady=(10, 0))
explanation_box = tk.Text(answer_window, wrap=tk.WORD, height=3, width=30, font=("楷体", 14), bg=self.window.cget("bg"))
explanation_box.insert(tk.END, explanation_text)
explanation_box.config(state=tk.DISABLED) # 设置为只读
explanation_box.pack(padx=20, pady=5)
# 复制答案按钮
def copy_answer():
self.window.clipboard_clear()
self.window.clipboard_append(answer_text)
messagebox.showinfo("成功", "答案已复制到剪贴板")
ttk.Button(answer_window, text="复制答案", command=copy_answer).pack(pady=10)
# 关闭按钮
ttk.Button(answer_window, text="关闭", command=answer_window.destroy).pack(pady=10)
def show_rules(self):
"""显示挑战说明"""
if self.rule_window is not None and self.rule_window.winfo_exists():
return # 如果规则窗口已经存在,则不再创建
self.rule_window = tk.Toplevel(self.window)
self.rule_window.title("挑战说明")
self.rule_window.geometry("500x300")
rules_text = """
游戏规则:
1. 每次回答一个题目,题目类型包括歇后语、字谜。
2. 答对一题得10分。
3. 答错后可以选择重新答题、查看答案或跳过。
4. 时间结束后或主动结束挑战时显示最终得分。
"""
#去除空格
rules_text = rules_text.replace(" ", "")
tk.Label(self.rule_window, text=rules_text, font=("楷体", 14), justify=tk.LEFT).pack(pady=20)
# 关闭规则窗口时重置变量
self.rule_window.protocol("WM_DELETE_WINDOW", self.close_rule_window)
def close_rule_window(self):
"""关闭规则窗口"""
self.rule_window.destroy()
self.rule_window = None
def show_result(self):
"""显示最终结果"""
self.clear_window() # 清除所有窗口内容
# 重置分数和时间
if self.score_default:
self.score = 0
self.time_left = self.DEFAULT_TIME_SECOND
tk.Label(self.window, text="挑战结束!", font=("楷体", 36)).pack(pady=50)
tk.Label(self.window, text=f"你的最终得分是: {self.score}", font=("楷体", 24)).pack()
ttk.Button(self.window, text="重新开始", command=self.create_welcome_page).pack()
def main_game_loop(self):
"""主游戏逻辑"""
self.clear_window() # 清除当前窗口内容,包括结束页
self.create_game_frame()
self.update_timer()
# 确保设置按钮始终可见
self.settings_button.lift()
self.settings_button.place(relx=0.5, rely=1.0, anchor='s', y=-10)
# 重置时间
self.time_left = self.DEFAULT_TIME_SECOND
# 随机选择一种题目类型
question_types = ["xiehouyu", "riddles"]
self.current_question_type = random.choice(question_types)
if self.current_question_type == "xiehouyu":
self.show_xiehouyu()
elif self.current_question_type == "riddles":
self.show_riddles()
def run(self):
"""运行程序"""
self.window.mainloop()
def show_settings_window(self):
"""显示设置窗口"""
# 如果设置窗口已经存在且未关闭,则直接返回
if hasattr(self, 'settings_window') and self.settings_window.winfo_exists():
return
self.settings_window = tk.Toplevel(self.window)
self.settings_window.title("设置")
self.settings_window.geometry("300x220")
# 添加分数重置选项
tk.Label(self.settings_window, text="分数设置", font=("楷体", 14)).pack(pady=5)
self.score_default_var = tk.BooleanVar(value=self.score_default)
ttk.Checkbutton(self.settings_window, text="每次重新开始重置分数",
variable=self.score_default_var).pack()
# 添加当前分数设置
tk.Label(self.settings_window, text="当前分数", font=("楷体", 14)).pack(pady=5)
self.score_entry = ttk.Entry(self.settings_window)
self.score_entry.insert(0, str(self.score))
self.score_entry.pack()
# 添加时间设置
tk.Label(self.settings_window, text="默认时间(秒)", font=("楷体", 14)).pack(pady=5)
self.time_entry = ttk.Entry(self.settings_window)
self.time_entry.insert(0, str(self.DEFAULT_TIME_SECOND))
self.time_entry.pack()
# 保存按钮
ttk.Button(self.settings_window, text="保存", command=self.save_settings).pack(pady=10)
# 关闭窗口时重置变量
self.settings_window.protocol("WM_DELETE_WINDOW", self.close_settings_window)
def close_settings_window(self):
"""关闭设置窗口"""
self.settings_window.destroy()
del self.settings_window
def save_settings(self):
"""保存设置"""
try:
# 更新分数重置选项
self.score_default = self.score_default_var.get()
# 更新当前分数
new_score = int(self.score_entry.get())
self.score = new_score
if hasattr(self, 'score_label'):
self.score_label.config(text=f"积分: {self.score}")
# 更新默认时间
new_time = int(self.time_entry.get())
self.DEFAULT_TIME_SECOND = new_time
self.time_left = new_time
if hasattr(self, 'timer_label'):
mins, secs = divmod(self.time_left, 60)
self.timer_label.config(text=f"剩余时间: {mins:02d}:{secs:02d}")
messagebox.showinfo("成功", "设置已保存!")
self.settings_window.destroy()
except ValueError:
messagebox.showerror("错误", "请输入有效的数字")
if __name__ == "__main__":
app = HanziAdventure()
app.run()