This commit is contained in:
zhangyuheng 2024-10-18 20:24:15 +08:00
commit 18d0cefe0b
13 changed files with 19613 additions and 0 deletions

162
.gitignore vendored Normal file
View File

@ -0,0 +1,162 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

8
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

14
.idea/deployment.xml Normal file
View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PublishConfigData" remoteFilesAllowedToDisappearOnAutoupload="false">
<serverData>
<paths name="zhangyuheng@192.168.161.152:22 password">
<serverdata>
<mappings>
<mapping local="$PROJECT_DIR$" web="/" />
</mappings>
</serverdata>
</paths>
</serverData>
</component>
</project>

View File

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

7
.idea/misc.xml Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="ChinaHamRadioQuest" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="ChinaHamRadioQuest" project-jdk-type="Python SDK" />
</project>

8
.idea/modules.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/ChinaHamRadioQuest.iml" filepath="$PROJECT_DIR$/.idea/ChinaHamRadioQuest.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

214
Quest.py Normal file
View File

@ -0,0 +1,214 @@
"""
Quest.py
"""
import os
import tkinter as tk
import random
import json
import time
# 配置考卷
quest_config = {
'A': {'num': 30, 'time': 40, 'pass': 25},
'B': {'num': 50, 'time': 60, 'pass': 40},
'C': {'num': 80, 'time': 90, 'pass': 60}
}
font = ('Microsoft YaHei', 13)
# 模拟加载题目
def generate_quests(type_str):
# 模拟从JSON文件加载题目
with open(f'quests/quests_{type_str}.json', 'r', encoding='utf-8') as f:
json_content = json.load(f)
list_quest = []
for obj in json_content:
list_quest.append(Quest(obj))
return random.sample(list_quest, quest_config[type_str]['num'])
class Quest:
def __init__(self, json_obj):
self.index = json_obj['index']
self.question = json_obj['Q']
self.code = json_obj['I']
self.correct = json_obj['A']
self.options = [json_obj['A'], json_obj['B'], json_obj['C'], json_obj['D']]
self.random_options = random.sample(self.options, len(self.options))
class ExamApp:
def __init__(self, root):
self.next_btn = None
self.prev_btn = None
self.submit_restart_btn = None
self.ques_nav_frame = None
self.timer_score_label = None
self.correct_answer_label = None
self.option_buttons = None
self.options_var = None
self.question_label = None
self.root = root
self.root.title("模拟考试")
self.level = None
self.time_left = 0
self.current_quest_index = 0
self.quests = []
self.selected_answers = {}
self.is_submitted = False
# 初始化主界面
self.init_main_menu()
def init_main_menu(self):
self.clear_screen()
tk.Label(self.root, text="选择考试级别").pack(pady=20)
for level in ['A', 'B', 'C']:
tk.Button(self.root, text=f"级别 {level}", command=lambda l=level: self.start_exam(l)).pack(pady=10)
def start_exam(self, level):
self.level = level
self.time_left = quest_config[level]['time'] * 60 # 转换为秒
self.quests = generate_quests(level)
self.current_quest_index = 0
self.selected_answers = {i: None for i in range(len(self.quests))}
self.is_submitted = False
self.init_exam_screen()
def init_exam_screen(self):
self.clear_screen()
# 左边显示考题
left_frame = tk.Frame(self.root, borderwidth=2, relief=tk.GROOVE, width=450, height=600)
left_frame.pack(side=tk.LEFT, padx=20, pady=20)
left_frame.pack_propagate(False)
self.question_label = tk.Label(left_frame, text="", width=400, wraplength=400, anchor=tk.W
, justify=tk.LEFT, font=font
, borderwidth=2, relief=tk.GROOVE)
self.question_label.pack(pady=20, padx=20)
self.question_label.pack_propagate(False)
self.options_var = tk.StringVar(value="")
self.option_buttons = []
for i in range(4):
btn = tk.Radiobutton(left_frame, text="", variable=self.options_var, value="", command=self.save_answer
, anchor=tk.W, font=font
, justify=tk.LEFT, wraplength=380)
btn.pack(anchor=tk.W, padx=20)
self.option_buttons.append(btn)
self.correct_answer_label = tk.Label(left_frame, text="", width=400, wraplength=400, anchor=tk.W, font=font
, justify=tk.LEFT)
self.correct_answer_label.pack(pady=20, padx=20)
self.correct_answer_label.pack_propagate(False)
# 上一题、下一题按钮
self.prev_btn = tk.Button(left_frame, text="上一题", command=lambda: self.jump_to_question(self.current_quest_index - 1))
self.prev_btn.pack(side=tk.LEFT, padx=20, pady=20, anchor=tk.NW)
self.next_btn = tk.Button(left_frame, text="下一题", command=lambda: self.jump_to_question(self.current_quest_index + 1))
self.next_btn.pack(side=tk.RIGHT, padx=20, pady=20, anchor=tk.NE)
# 右边显示考试状态
right_frame = tk.Frame(self.root, borderwidth=2, relief=tk.GROOVE, width=300, height=600)
right_frame.pack(side=tk.RIGHT, padx=20, pady=20)
right_frame.pack_propagate(False)
self.timer_score_label = tk.Label(right_frame, text="剩余时间00:00")
self.timer_score_label.pack(pady=10)
self.ques_nav_frame = tk.Frame(right_frame)
self.ques_nav_frame.pack(pady=10)
self.update_timer()
self.update_question()
self.submit_restart_btn = tk.Button(right_frame, text="交卷", command=self.submit_exam)
self.submit_restart_btn.pack(pady=10)
def update_question(self):
quest = self.quests[self.current_quest_index]
self.question_label.config(text=f"Q{self.current_quest_index + 1} - ({quest.code})\n"
f"{quest.question}\n")
self.options_var.set(self.selected_answers[self.current_quest_index])
for i, opt in enumerate(quest.random_options):
self.option_buttons[i].config(text=opt, value=opt)
if self.options_var.get() == opt:
self.option_buttons[i].select()
else:
self.option_buttons[i].deselect()
if self.is_submitted:
self.option_buttons[i].config(state=tk.DISABLED)
if not self.is_submitted:
self.update_nav_buttons()
else:
# 显示正确答案
self.correct_answer_label.config(text=f"正确答案: \n{quest.correct}")
if self.current_quest_index == 0:
self.prev_btn.config(state=tk.DISABLED)
else:
self.prev_btn.config(state=tk.NORMAL)
if self.current_quest_index == len(self.quests) - 1:
self.next_btn.config(state=tk.DISABLED)
else:
self.next_btn.config(state=tk.NORMAL)
def update_nav_buttons(self):
for widget in self.ques_nav_frame.winfo_children():
widget.destroy()
if not self.is_submitted:
for i in range(len(self.quests)):
color = "yellow" if self.selected_answers[i] else "white"
btn = tk.Button(self.ques_nav_frame, text=f"{i + 1}", bg=color,
command=lambda idx=i: self.jump_to_question(idx))
btn.grid(row=i // 8, column=i % 8, sticky="nsew")
else:
for i in range(len(self.quests)):
quest = self.quests[i]
color = "green" if self.selected_answers[i] == quest.correct else "red"
btn = tk.Button(self.ques_nav_frame, text=f"{i + 1}", bg=color,
command=lambda idx=i: self.jump_to_question(idx))
btn.grid(row=i // 8, column=i % 8, sticky="nsew")
def save_answer(self):
self.selected_answers[self.current_quest_index] = self.options_var.get()
self.update_nav_buttons()
def jump_to_question(self, index):
self.current_quest_index = index
self.update_question()
def submit_exam(self):
self.is_submitted = True
self.update_question()
self.update_nav_buttons()
correct_count = sum(1 for i, quest in enumerate(self.quests) if self.selected_answers[i] == quest.correct)
pass_score = quest_config[self.level]['pass']
result_text = "通过" if correct_count >= pass_score else "未通过"
self.timer_score_label.config(text=f"得分: {correct_count}/{len(self.quests)} - {result_text}")
self.submit_restart_btn.config(text="重新开始", command=self.init_main_menu)
def update_timer(self):
if self.time_left > 0 and not self.is_submitted:
self.time_left -= 1
mins, secs = divmod(self.time_left, 60)
self.timer_score_label.config(text=f"剩余时间:{mins:02}:{secs:02}")
self.root.after(1000, self.update_timer)
def clear_screen(self):
for widget in self.root.winfo_children():
widget.destroy()
if __name__ == '__main__':
os.path.abspath(os.path.dirname(__file__))
root = tk.Tk()
app = ExamApp(root)
root.geometry("800x600")
root.resizable(False, False)
root.mainloop()

3278
quests/quests_A.json Normal file

File diff suppressed because it is too large Load Diff

6212
quests/quests_B.json Normal file

File diff suppressed because it is too large Load Diff

9640
quests/quests_C.json Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,50 @@
"""
updateQuestLibrary.py
"""
import os
import json
import requests
baseURL = 'https://www.cqid.cn/api/getAll/'
def post_quest_library(type_str):
"""
postQuestLibrary function
method POST
content-type: application/x-www-form-urlencoded
body: type=type_str
"""
headers = {
'content-type': 'application/x-www-form-urlencoded'
}
body = {
'type': type_str
}
response = requests.post(baseURL, headers=headers, data=body)
return response
def remove_answer_list(json_content):
for i in range(len(json_content)):
if 'answer' in json_content[i]:
json_content[i].pop('answer')
return json_content
def main():
os.path.abspath(os.path.dirname(__file__))
typeList = ['A', 'B', 'C']
for type_str in typeList:
response = post_quest_library(type_str).text
print(f"Get quests of type {type_str}.")
json_content = remove_answer_list(json.loads(response))
# write to file ../quests/quests_x.json
with open(f'../quests/quests_{type_str}.json', 'w', encoding='utf-8') as f:
json.dump(json_content, f, ensure_ascii=False, indent=4)
print(f"Write quests of type {type_str} to file.")
if __name__ == '__main__':
main()