更新1 #6

Merged
hnu202326010111 merged 0 commits from pengyunhao_branch into develop 5 months ago

@ -0,0 +1,132 @@
软1_[彭云昊]_[王祖旺]_结对项目
中小学数学学习软件 - 结对编程项目
项目简介
本项目是一个面向中小学学生的数学学习桌面应用程序,提供个性化的数学题目练习和评估功能。系统根据学生所在学段(小学、初中、高中)生成相应难度的数学题目,通过图形化界面提供友好的学习体验。
功能特性
用户管理
• ✅ 用户注册:通过邮箱验证码完成注册
• ✅ 密码设置6-10位必须包含大小写字母和数字
• ✅ 用户登录:安全的身份验证机制
• ✅ 密码修改:支持原密码验证
• ✅ 账户注销:永久删除账户及所有数据
题目生成
• ✅ 小学题目:加减乘除四则运算,支持括号
• ✅ 初中题目:平方、开根运算
• ✅ 高中题目:三角函数计算
• ✅ 智能防重复:确保同一试卷无重复题目
学习流程
• ✅ 难度选择:小学/初中/高中三级难度
• ✅ 题目数量用户自定义题目数量10-30题
• ✅ 选择题形式每题4个选项单选作答
• ✅ 实时评分:提交后立即显示得分情况
• ✅ 学习延续:支持连续练习或退出选择
• ✅ 答题进度:支持上一题/下一题导航
技术栈
• 编程语言Python 3.x
• GUI框架Tkinter内置Python GUI库
• 数据存储JSON文件无需数据库
• 邮件服务SMTP协议QQ邮箱
• 架构模式MVC模型-视图-控制器)
项目结构
text
复制
下载
软1_[彭云昊]_[王祖旺]_结对项目/
├── main.py # 程序入口
├── main_app.py # 主应用模块,界面控制器
├── user_manager.py # 用户管理模块
├── question_bank.py # 题库管理模块
├── question_generator.py # 题目生成器模块
├── quiz.py # 测验管理模块
├── users.json # 用户数据文件(运行时生成)
└── README.md # 项目说明文档
安装与运行
环境要求
• Python 3.11.9 或更高版本
• 网络连接(用于邮箱验证码发送)
运行步骤
1. 下载项目文件到本地
2. 确保所有Python文件在同一目录下
3. 运行主程序:
bash
复制
下载
python main.py
4. 按照界面提示进行注册和登录
预设测试账号
系统支持新用户注册,也可使用以下测试账号:
• 邮箱:任意有效邮箱(接收验证码)
• 密码符合规范的密码Abc123
分支管理
本项目遵循Git分支管理规范
• main分支稳定版本存放经过测试的代码
• develop分支开发主线集成最新功能
• 个人分支每位开发者的功能分支zhangsan_branch
代码提交规则
• 源代码:必须通过个人分支 + Pull Request
• 文档直接推送到develop分支
开发规范
代码规范
• 遵循PEP 8 Python编码规范
• 使用类型注解提高代码可读性
• 模块化设计,高内聚低耦合
提交信息规范
• feat: 新功能
• fix: 修复bug
• docs: 文档更新
• style: 代码格式调整
• refactor: 代码重构
功能模块详解
1. 用户认证模块 (user_manager.py)
• 邮箱格式验证
• 密码强度校验
• 验证码发送与验证
• 用户数据持久化JSON文件
2. 题目生成模块 (question_generator.py)
• 小学题目2-5个数字的加减乘除运算确保结果非负
• 初中题目平方运算1-12、开根运算完全平方数
• 高中题目三角函数sin/cos/tan特殊角度计算
• 选项生成智能生成4个合理选项包含正确答案
3. 界面模块 (main_app.py)
• 响应式图形界面设计
• 实时输入验证
• 友好的用户交互反馈
• 多框架界面切换
数据存储
项目使用JSON文件存储用户数据文件结构如下
json
复制
下载
{
"user@example.com": {
"username": "张三",
"password_hash": "加密密码",
"registration_code": "注册码",
"is_registered": true
}
}
配置说明
在 user_manager.py 中配置以下参数:
• 邮箱服务配置SMTP服务器、端口、授权码
• 用户数据文件路径
测试用例
功能测试
• 用户注册流程测试
• 登录验证测试
• 密码修改测试
• 题目生成测试(各学段)
• 答题评分测试
边界测试
• 密码格式边界测试
• 题目数量边界测试
• 邮箱格式验证测试
已知限制
• 邮箱服务依赖QQ邮箱SMTP服务需配置正确的授权码
• 题目数量建议10-30题过多可能影响性能
• 网络要求:发送验证码需要网络连接
• 平台兼容主要支持Windows其他平台可能需调整
开发团队
• 班级软1
• 组长:[彭云昊]202326010111
• 组员:[王祖旺]202326010117

@ -0,0 +1,238 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
后端服务模块作为前端与后端之间的通信中介
"""
from typing import List, Optional, Dict, Any
from user_manager import UserManager
from question_bank import QuestionBank
from quiz import Quiz
class BackendService:
"""
后端服务类作为前端与实际业务逻辑模块之间的通信中介
"""
def __init__(self):
"""
初始化后端服务
"""
self.user_manager = UserManager()
self.question_bank = QuestionBank()
self.current_quiz: Optional[Quiz] = None
def login(self, username: str, password: str) -> bool:
"""
用户登录
@param username: 用户名
@param password: 密码
@return: 登录是否成功
"""
return self.user_manager.login(username, password)
def register_user(self, email: str, username: str) -> bool:
"""
注册新用户
@param email: 邮箱
@param username: 用户名
@return: 是否成功发送注册码
"""
return self.user_manager.register_user(email, username)
def verify_registration_code(self, email: str, code: str) -> bool:
"""
验证注册码
@param email: 邮箱
@param code: 注册码
@return: 验证是否成功
"""
return self.user_manager.verify_registration_code(email, code)
def set_password(self, email: str, password: str) -> bool:
"""
设置密码
@param email: 邮箱
@param password: 密码
@return: 是否设置成功
"""
return self.user_manager.set_password(email, password)
def change_password(self, old_password: str, new_password: str) -> bool:
"""
修改密码
@param old_password: 原密码
@param new_password: 新密码
@return: 是否修改成功
"""
return self.user_manager.change_password(old_password, new_password)
def logout(self):
"""
退出登录
"""
self.user_manager.logout()
def delete_account(self, email: str, password: str) -> bool:
"""
删除账户
@param email: 邮箱
@param password: 密码
@return: 是否删除成功
"""
return self.user_manager.delete_account(email, password)
def start_quiz(self, level: str, question_count: int) -> bool:
"""
开始测验
@param level: 题目难度等级
@param question_count: 题目数量
@return: 是否成功开始测验
"""
try:
questions = self.question_bank.generate_questions(level, question_count)
self.current_quiz = Quiz(questions)
return True
except Exception:
return False
def get_current_question(self):
"""
获取当前题目
@return: 当前题目
"""
if self.current_quiz:
return self.current_quiz.get_current_question()
return None
def get_current_options(self) -> List[str]:
"""
获取当前题目的选项
@return: 选项列表
"""
if self.current_quiz:
return self.current_quiz.get_current_options()
return []
def answer_question(self, answer: str) -> bool:
"""
回答当前题目
@param answer: 答案
@return: 是否回答成功
"""
if self.current_quiz:
try:
self.current_quiz.answer_question(answer)
return True
except Exception:
return False
return False
def next_question(self) -> bool:
"""
跳转到下一题
@return: 是否成功跳转
"""
if self.current_quiz:
return self.current_quiz.next_question()
return False
def previous_question(self) -> bool:
"""
跳转到上一题
@return: 是否成功跳转
"""
if self.current_quiz:
return self.current_quiz.previous_question()
return False
def is_quiz_finished(self) -> bool:
"""
检查测验是否已完成
@return: 测验是否已完成
"""
if self.current_quiz:
return self.current_quiz.is_finished()
return True
def calculate_score(self) -> float:
"""
计算测验得分
@return: 得分百分比
"""
if self.current_quiz:
return self.current_quiz.calculate_score()
return 0.0
def get_quiz_progress(self) -> Dict[str, Any]:
"""
获取测验进度信息
@return: 进度信息字典
"""
if self.current_quiz:
return {
"current_index": self.current_quiz.current_question_index,
"total_questions": len(self.current_quiz.questions),
"current_answer": self.current_quiz.answers[
self.current_quiz.current_question_index] if self.current_quiz.answers else None
}
return {
"current_index": 0,
"total_questions": 0,
"current_answer": None
}
def get_quiz_result_details(self) -> Dict[str, Any]:
"""
获取测验结果详情
@return: 结果详情字典
"""
if not self.current_quiz:
return {}
correct_count = 0
for q, a in zip(self.current_quiz.questions, self.current_quiz.answers):
if a is not None:
try:
# 将字符串答案转换为浮点数进行比较
if abs(q.answer - float(a)) < 1e-6:
correct_count += 1
except ValueError:
# 如果转换失败,说明答案无效,不计入正确答案
pass
total_count = len(self.current_quiz.questions)
return {
"correct_count": correct_count,
"total_count": total_count
}
def get_current_username(self) -> str:
"""
获取当前登录用户的用户名
@return: 用户名
"""
if self.user_manager.current_user:
return self.user_manager.current_user.username
return "未知用户"

File diff suppressed because it is too large Load Diff

@ -14,7 +14,7 @@ from typing import List, Set, Optional
class Expression:
"""
数学表达式类
数学表达式类辅助类
"""
def __init__(self, expression_str: str, answer: float):
@ -97,42 +97,16 @@ class ElementaryQuestionGenerator(QuestionGenerator):
"""
max_attempts = 100
for _ in range(max_attempts):
# 随机生成操作数数量2-5个
num_operands = random.randint(2, 5)
operands = [random.randint(1, 50) for _ in range(num_operands)]
operators = [random.choice(['+', '-', '*']) if random.random() < 0.8 else '/'
for _ in range(num_operands - 1)] # 减少减法和除法概率以避免负数
# 随机添加括号
expression_parts = []
for i in range(num_operands):
expression_parts.append(str(operands[i]))
if i < len(operators):
expression_parts.append(operators[i])
# 随机添加括号
if num_operands >= 3 and random.random() < 0.3:
# 在随机位置添加括号
open_pos = random.randint(0, len(expression_parts) - 3)
# 确保括号内至少有两个操作数
close_pos = min(open_pos + 2 + random.randint(1, 2) * 2, len(expression_parts))
# 确保括号位置是操作数位置
if open_pos % 2 == 0 and close_pos % 2 == 0:
expression_parts.insert(open_pos, '(')
expression_parts.insert(close_pos + 1, ')')
expression_str = ''.join(expression_parts)
expression_str = self._generate_elementary_expression()
# 验证表达式是否有效
try:
# 替换除法符号以便计算
eval_expr = expression_str.replace('/', '/').replace('*', '*')
result = eval(eval_expr)
# 确保结果是非负数且是合理的(整数或有限小数)
if isinstance(result, (int, float)) and result >= 0 and abs(result) < 1000:
# 格式化结果,保留合适的小数位数
if (isinstance(result, (int, float)) and
result >= 0 and
abs(result) < 1000):
if isinstance(result, float) and result.is_integer():
result = int(result)
@ -141,7 +115,8 @@ class ElementaryQuestionGenerator(QuestionGenerator):
if str(expr) not in self.generated_questions:
self.generated_questions.add(str(expr))
# 将表达式中的乘号和除号替换为更易读的形式
readable_expr_str = expression_str.replace('*', '×').replace('/', '÷')
readable_expr_str = expression_str.replace('*', '×')
readable_expr_str = readable_expr_str.replace('/', '÷')
readable_expr = Expression(readable_expr_str, result)
return readable_expr
except:
@ -153,6 +128,65 @@ class ElementaryQuestionGenerator(QuestionGenerator):
readable_expr = Expression("1+1", 2)
return readable_expr
def _generate_elementary_expression(self) -> str:
"""
生成小学题目表达式字符串
@return: 表达式字符串
"""
# 随机生成操作数数量2-5个
num_operands = random.randint(2, 5)
# 修改操作数范围从1-50到1-100
operands = [random.randint(1, 100) for _ in range(num_operands)]
operators = [random.choice(['+', '-', '*']) if random.random() < 0.8 else '/'
for _ in range(num_operands - 1)] # 减少减法和除法概率以避免负数
# 构建表达式部分
expression_parts = self._build_expression_parts(operands, operators)
# 随机添加括号
expression_parts = self._add_parentheses(expression_parts, num_operands)
return ''.join(expression_parts)
def _build_expression_parts(self, operands: List[int],
operators: List[str]) -> List[str]:
"""
构建表达式部分
@param operands: 操作数列表
@param operators: 操作符列表
@return: 表达式部分列表
"""
expression_parts = []
for i in range(len(operands)):
expression_parts.append(str(operands[i]))
if i < len(operators):
expression_parts.append(operators[i])
return expression_parts
def _add_parentheses(self, expression_parts: List[str],
num_operands: int) -> List[str]:
"""
随机添加括号到表达式
@param expression_parts: 表达式部分列表
@param num_operands: 操作数数量
@return: 添加括号后的表达式部分列表
"""
if num_operands >= 3 and random.random() < 0.3:
# 在随机位置添加括号
open_pos = random.randint(0, len(expression_parts) - 3)
# 确保括号内至少有两个操作数
close_pos = min(open_pos + 2 + random.randint(1, 2) * 2,
len(expression_parts))
# 确保括号位置是操作数位置
if open_pos % 2 == 0 and close_pos % 2 == 0:
expression_parts.insert(open_pos, '(')
expression_parts.insert(close_pos + 1, ')')
return expression_parts
class MiddleQuestionGenerator(QuestionGenerator):
"""
@ -167,93 +201,161 @@ class MiddleQuestionGenerator(QuestionGenerator):
"""
max_attempts = 100
for _ in range(max_attempts):
expression_parts = []
# 确保至少有一个平方或开根号
has_square_or_sqrt = True # 确保至少有一个平方或开根号
# 生成操作数数量2-5个
num_operands = random.randint(2, 5)
# 标记是否已添加特殊操作
special_added = False
for i in range(num_operands):
# 确保至少添加一个平方或开根号
if not special_added and i == num_operands - 1:
# 如果还没添加特殊操作,强制在最后一个操作数添加
choice = random.choice([0, 1]) # 提高开根号概率
if choice == 0:
# 添加平方
base = random.randint(1, 12)
expression_parts.append(f"{base}²")
else:
# 添加开根号
square = random.randint(1, 12)
value = square * square
expression_parts.append(f"{value}")
special_added = True
else:
rand_val = random.random()
# 修改这里的概率,增加开根号和平方的出现频率
if not special_added and rand_val < 0.6: # 提高特殊操作概率从0.4到0.6
# 添加特殊操作(平方或开根号)
choice = random.choice([0, 1]) if random.random() < 0.6 else 1 # 提高开根号概率
if choice == 0:
# 添加平方
base = random.randint(1, 12)
expression_parts.append(f"{base}²")
else:
# 添加开根号
square = random.randint(1, 12)
value = square * square
expression_parts.append(f"{value}")
special_added = True
else:
# 普通操作数
expression_parts.append(str(random.randint(1, 50)))
# 添加运算符(除了最后一个操作数)
if i < num_operands - 1:
expression_parts.append(random.choice(['+', '-', '*', '/']))
expression_str = ''.join(expression_parts)
# 处理可能的语法问题
expression_str = self._generate_middle_expression()
expression_str = self.fix_expression_syntax(expression_str)
# 计算结果
try:
# 替换表达式中的函数以便计算
eval_expr = expression_str.replace('²', '**2').replace('', 'math.sqrt')
eval_expr = self._prepare_middle_expression_for_eval(expression_str)
result = eval(eval_expr)
# 确保结果是合理的
if isinstance(result, (int, float)) and abs(result) < 1000 and not math.isnan(result):
if (isinstance(result, (int, float)) and
abs(result) < 1000 and
not math.isnan(result)):
# 格式化结果
if isinstance(result, float) and abs(result - round(result)) < 1e-10:
result = int(round(result))
expr = Expression(expression_str, result)
# 检查是否已生成过相同题目
if str(expr) not in self.generated_questions:
self.generated_questions.add(str(expr))
# 将表达式中的乘号和除号替换为更易读的形式
readable_expr_str = expression_str.replace('*', '×').replace('/', '÷')
readable_expr_str = expression_str.replace('*', '×')
readable_expr_str = readable_expr_str.replace('/', '÷')
readable_expr = Expression(readable_expr_str, result)
return readable_expr
except:
continue
# 如果无法生成有效题目,返回默认题目
expr = Expression("√4+2²", 6)
self.generated_questions.add(str(expr))
readable_expr = Expression("√4+2²", 6)
# 将表达式中的乘号和除号替换为更易读的形式
readable_expr_str = "√4+2²".replace('*', '×').replace('/', '÷')
readable_expr = Expression(readable_expr_str, 6)
return readable_expr
def _generate_middle_expression(self) -> str:
"""
生成初中题目表达式字符串
@return: 表达式字符串
"""
expression_parts = []
# 生成操作数数量2-5个
num_operands = random.randint(2, 5)
# 标记是否已添加特殊操作
special_added = False
# 生成表达式部分
expression_parts, special_added = self._build_middle_expression_parts(
expression_parts, num_operands, special_added)
return ''.join(expression_parts)
def _build_middle_expression_parts(self, expression_parts: List[str],
num_operands: int,
special_added: bool) -> tuple:
"""
构建初中表达式部分
@param expression_parts: 表达式部分列表
@param num_operands: 操作数数量
@param special_added: 是否已添加特殊操作标记
@return: (表达式部分列表, 是否已添加特殊操作标记)
"""
for i in range(num_operands):
# 确保至少添加一个平方或开根号
if not special_added and i == num_operands - 1:
# 如果还没添加特殊操作,强制在最后一个操作数添加
expression_parts, special_added = self._add_forced_special_operation(
expression_parts)
else:
expression_parts, special_added = self._add_middle_operand_or_operation(
expression_parts, special_added, i, num_operands)
# 添加运算符(除了最后一个操作数)
if i < num_operands - 1:
expression_parts.append(random.choice(['+', '-', '*', '/']))
return expression_parts, special_added
def _add_forced_special_operation(self, expression_parts: List[str]) -> tuple:
"""
强制添加特殊操作平方或开根号
@param expression_parts: 表达式部分列表
@return: (表达式部分列表, 是否已添加特殊操作标记)
"""
choice = random.choice([0, 1]) # 0为平方1为开根号
if choice == 0:
# 添加平方
base = random.randint(1, 100)
expression_parts.append(f"{base}²")
else:
# 添加开根号(确保至少有一个开根号)
square = random.randint(1, 10)
value = square * square
expression_parts.append(f"{value}")
return expression_parts, True
def _add_middle_operand_or_operation(self, expression_parts: List[str],
special_added: bool, i: int,
num_operands: int) -> tuple:
"""
添加初中操作数或操作
@param expression_parts: 表达式部分列表
@param special_added: 是否已添加特殊操作标记
@param i: 当前索引
@param num_operands: 操作数总数
@return: (表达式部分列表, 是否已添加特殊操作标记)
"""
rand_val = random.random()
# 修改这里的概率,增加开根号和平方的出现频率
if not special_added and rand_val < 0.6:
# 添加特殊操作(平方或开根号)
expression_parts, special_added = self._add_middle_special_operation()
else:
# 普通操作数
expression_parts.append(str(random.randint(1, 100)))
return expression_parts, special_added
return expression_parts, special_added
def _add_middle_special_operation(self) -> tuple:
"""
添加初中特殊操作平方或开根号
@return: (表达式部分列表, 是否已添加特殊操作标记)
"""
choice = random.choice([0, 1]) # 平方和开根号的概率相等
expression_parts = []
if choice == 0:
# 添加平方
base = random.randint(1, 100)
expression_parts.append(f"{base}²")
else:
# 添加开根号
square = random.randint(1, 10)
value = square * square
expression_parts.append(f"{value}")
return expression_parts, True
def _prepare_middle_expression_for_eval(self, expression_str: str) -> str:
"""
准备初中题目表达式用于eval计算
@param expression_str: 原始表达式字符串
@return: 可用于eval计算的表达式字符串
"""
# 替换表达式中的函数以便计算
eval_expr = expression_str.replace('²', '**2')
# 使用正则表达式正确处理平方根
eval_expr = re.sub(r'√(\d+)',
r'math.sqrt(\1)',
eval_expr)
return eval_expr
def fix_expression_syntax(self, expression: str) -> str:
"""
修复表达式语法问题
@ -266,8 +368,6 @@ class MiddleQuestionGenerator(QuestionGenerator):
expression = re.sub(r'(²)([√])', r'\1*\2', expression)
expression = re.sub(r'(\))(\d)', r'\1*\2', expression)
expression = re.sub(r'(\d)(\()', r'\1*\2', expression)
# 修复根号表达式显示,确保根号符号正确显示
expression = re.sub(r'√(\d+)', r'\1', expression)
return expression
@ -279,110 +379,34 @@ class HighQuestionGenerator(QuestionGenerator):
def generate_question(self) -> Expression:
"""
生成高中题目
@return: 数学表达式
"""
max_attempts = 100
for _ in range(max_attempts):
expression_parts = []
# 确保至少有一个三角函数
has_trig_function = random.random() < 0.8
# 生成操作数数量2-4个
num_operands = random.randint(2, 4)
for i in range(num_operands):
if has_trig_function and i == 0:
# 第一个操作数有较高概率是三角函数
if random.random() < 0.33:
angle = random.choice([0, 30, 45, 60, 90])
expression_parts.append(f"sin({angle}°)")
elif random.random() < 0.5:
angle = random.choice([0, 30, 45, 60, 90])
expression_parts.append(f"cos({angle}°)")
else:
angle = random.choice([0, 30, 45, 60, 90])
expression_parts.append(f"tan({angle}°)")
else:
# 其他操作数
rand_val = random.random()
if rand_val < 0.1 and has_trig_function:
# 添加三角函数
angle = random.choice([0, 30, 45, 60, 90])
expression_parts.append(f"sin({angle}°)")
elif rand_val < 0.2 and has_trig_function:
angle = random.choice([0, 30, 45, 60, 90])
expression_parts.append(f"cos({angle}°)")
elif rand_val < 0.3 and has_trig_function:
angle = random.choice([0, 30, 45, 60, 90])
expression_parts.append(f"tan({angle}°)")
elif rand_val < 0.55:
# 普通数字
expression_parts.append(str(random.randint(1, 20)))
elif rand_val < 0.7:
# 平方
base = random.randint(1, 10)
expression_parts.append(f"{base}²")
else:
# 开根号
square = random.randint(1, 10)
value = square * square
expression_parts.append(f"{value}")
# 添加运算符(除了最后一个操作数)
if i < num_operands - 1:
expression_parts.append(random.choice(['+', '-', '*', '/']))
expression_str = ''.join(expression_parts)
# 处理可能的语法问题
expression_str = self._generate_high_expression()
expression_str = self.fix_expression_syntax(expression_str)
# 计算结果
try:
# 替换表达式中的函数以便计算
eval_expr = expression_str.replace('²', '**2').replace('', 'math.sqrt')
# 修复:确保正确的替换顺序,先替换角度制三角函数
eval_expr = re.sub(r'sin\((\d+)°\)', r'math.sin(math.radians(\1))', eval_expr)
eval_expr = re.sub(r'cos\((\d+)°\)', r'math.cos(math.radians(\1))', eval_expr)
eval_expr = re.sub(r'tan\((\d+)°\)', r'math.tan(math.radians(\1))', eval_expr)
eval_expr = self._prepare_high_expression_for_eval(expression_str)
result = eval(eval_expr)
# 确保结果是合理的
if isinstance(result, (int, float)) and abs(result) < 1000 and not math.isnan(result) and not math.isinf(result):
# 格式化结果
if isinstance(result, float) and abs(result - round(result, 10)) < 1e-10:
result = int(round(result))
# 特殊处理常见的三角函数值,使其更加准确
elif isinstance(result, float):
# 对于常见的三角函数值进行舍入处理
if abs(result - 0.5) < 1e-10: # sin(30°) = 0.5
result = 0.5
elif abs(result - 0.7071067811865476) < 1e-10: # sin(45°) = cos(45°) ≈ 0.707
result = round(result, 10)
elif abs(result - 0.8660254037844386) < 1e-10: # sin(60°) ≈ 0.866
result = round(result, 10)
elif abs(result - 0.5773502691896257) < 1e-10: # tan(30°) ≈ 0.577
result = round(result, 10)
elif abs(result - 1.7320508075688772) < 1e-10: # tan(60°) ≈ 1.732
result = round(result, 10)
elif abs(result - 1.0) < 1e-10: # tan(45°) = 1, sin(90°) = 1
result = 1.0
if (isinstance(result, (int, float)) and
abs(result) < 1000 and
not math.isnan(result) and
not math.isinf(result)):
# 格式化结果,保留两位小数
result = self._format_high_result(result)
expr = Expression(expression_str, result)
# 检查是否已生成过相同题目
if str(expr) not in self.generated_questions:
self.generated_questions.add(str(expr))
# 将表达式中的乘号和除号替换为更易读的形式
readable_expr_str = expression_str.replace('*', '×').replace('/', '÷')
readable_expr_str = expression_str.replace('*', '×')
readable_expr_str = readable_expr_str.replace('/', '÷')
readable_expr = Expression(readable_expr_str, result)
return readable_expr
except:
continue
# 如果无法生成有效题目,返回默认题目
expr = Expression("sin(30°)", 0.5)
self.generated_questions.add(str(expr))
readable_expr = Expression("sin(30°)", 0.5)
@ -391,6 +415,189 @@ class HighQuestionGenerator(QuestionGenerator):
readable_expr = Expression(readable_expr_str, 0.5)
return readable_expr
def _generate_high_expression(self) -> str:
"""
生成高中题目表达式字符串
@return: 表达式字符串
"""
expression_parts = []
# 生成操作数数量2-4个
num_operands = random.randint(2, 5)
# 标记是否已添加三角函数
trig_added = False
# 生成表达式部分
expression_parts, trig_added = self._build_high_expression_parts(
expression_parts, num_operands, trig_added)
return ''.join(expression_parts)
def _build_high_expression_parts(self, expression_parts: List[str],
num_operands: int,
trig_added: bool) -> tuple:
"""
构建高中表达式部分
@param expression_parts: 表达式部分列表
@param num_operands: 操作数数量
@param trig_added: 是否已添加三角函数标记
@return: (表达式部分列表, 是否已添加三角函数标记)
"""
for i in range(num_operands):
# 确保至少添加一个三角函数
if not trig_added and i == num_operands - 1:
# 如果还没添加三角函数,强制在最后一个操作数添加
expression_parts, trig_added = self._add_forced_trig_function(
expression_parts)
else:
# 其他操作数
expression_parts, trig_added = self._add_high_operand_or_operation(
expression_parts, trig_added)
# 添加运算符(除了最后一个操作数)
if i < num_operands - 1:
expression_parts.append(random.choice(['+', '-', '*', '/']))
return expression_parts, trig_added
def _add_forced_trig_function(self, expression_parts: List[str]) -> tuple:
"""
强制添加三角函数
@param expression_parts: 表达式部分列表
@return: (表达式部分列表, 是否已添加三角函数标记)
"""
choice = random.randint(0, 2)
if choice == 0:
angle = random.choice([0, 30, 45, 60]) # 不包含90度
expression_parts.append(f"sin({angle}°)")
elif choice == 1:
angle = random.choice([0, 30, 45, 60, 90])
expression_parts.append(f"cos({angle}°)")
else:
angle = random.choice([0, 30, 45, 60]) # 不包含90度
expression_parts.append(f"tan({angle}°)")
return expression_parts, True
def _add_high_operand_or_operation(self, expression_parts: List[str],
trig_added: bool) -> tuple:
"""
添加高中操作数或操作
@param expression_parts: 表达式部分列表
@param trig_added: 是否已添加三角函数标记
@return: (表达式部分列表, 是否已添加三角函数标记)
"""
rand_val = random.random()
if not trig_added and rand_val < 0.4:
# 添加三角函数
return self._add_trig_function()
elif rand_val < 0.65:
# 普通数字
expression_parts.append(str(random.randint(1, 20)))
return expression_parts, trig_added
elif rand_val < 0.8:
# 平方
base = random.randint(1, 10)
expression_parts.append(f"{base}²")
return expression_parts, trig_added
else:
# 开根号
square = random.randint(1, 10)
value = square * square
expression_parts.append(f"{value}")
return expression_parts, trig_added
def _add_trig_function(self) -> tuple:
"""
添加三角函数
@return: (表达式部分列表, 是否已添加三角函数标记)
"""
expression_parts = []
func_choice = random.randint(0, 2)
if func_choice == 0:
angle = random.choice([0, 30, 45, 60]) # 不包含90度
expression_parts.append(f"sin({angle}°)")
elif func_choice == 1:
angle = random.choice([0, 30, 45, 60, 90])
expression_parts.append(f"cos({angle}°)")
else:
angle = random.choice([0, 30, 45, 60]) # 不包含90度
expression_parts.append(f"tan({angle}°)")
return expression_parts, True
def _prepare_high_expression_for_eval(self, expression_str: str) -> str:
"""
准备高中题目表达式用于eval计算
@param expression_str: 原始表达式字符串
@return: 可用于eval计算的表达式字符串
"""
eval_expr = self._replace_powers_and_roots(expression_str)
# 修复:确保正确的替换顺序,先替换角度制三角函数
eval_expr = self._replace_trig_functions(eval_expr)
return eval_expr
def _replace_powers_and_roots(self, expression_str: str) -> str:
"""
替换幂运算和开根号符号
@param expression_str: 原始表达式字符串
@return: 替换后的表达式字符串
"""
eval_expr = expression_str.replace('²', '**2')
eval_expr = eval_expr.replace('', 'math.sqrt')
return eval_expr
def _replace_trig_functions(self, eval_expr: str) -> str:
"""
替换三角函数
@param eval_expr: 表达式字符串
@return: 替换三角函数后的表达式字符串
"""
eval_expr = re.sub(r'sin\((\d+)°\)',
r'math.sin(math.radians(\1))',
eval_expr)
eval_expr = re.sub(r'cos\((\d+)°\)',
r'math.cos(math.radians(\1))',
eval_expr)
eval_expr = re.sub(r'tan\((\d+)°\)',
r'math.tan(math.radians(\1))',
eval_expr)
return eval_expr
def _format_high_result(self, result: float) -> float:
"""
格式化高中题目计算结果
@param result: 计算结果
@return: 格式化后的结果
"""
if isinstance(result, float):
# 特殊处理常见的三角函数值,使其更加准确
if abs(result - 0.5) < 1e-10: # sin(30°) = 0.5
result = 0.5
elif abs(result - 0.7071067811865476) < 1e-10: # sin(45°) = cos(45°) ≈ 0.707
result = round(result, 2)
elif abs(result - 0.8660254037844386) < 1e-10: # sin(60°) ≈ 0.866
result = round(result, 2)
elif abs(result - 0.5773502691896257) < 1e-10: # tan(30°) ≈ 0.577
result = round(result, 2)
elif abs(result - 1.7320508075688772) < 1e-10: # tan(60°) ≈ 1.732
result = round(result, 2)
elif abs(result - 1.0) < 1e-10: # tan(45°) = 1, sin(90°) = 1
result = 1.0
else:
# 保留两位小数
result = round(result, 2)
# 整数保持不变
return result
def fix_expression_syntax(self, expression: str) -> str:
"""
修复表达式语法问题
@ -399,10 +606,22 @@ class HighQuestionGenerator(QuestionGenerator):
@return: 修复后的表达式
"""
# 确保函数调用之间有运算符
expression = re.sub(r'(\d)([sincostan√])', r'\1*\2', expression)
expression = re.sub(r'(²)([sincostan√])', r'\1*\2', expression)
expression = re.sub(r'(sqrt\(\d+\))(\d)', r'\1*\2', expression)
expression = re.sub(r'(\))(\d)', r'\1*\2', expression)
expression = re.sub(r'(\d)(\()', r'\1*\2', expression)
expression = re.sub(r'°\)(\d)', r'°)*\1', expression)
expression = re.sub(r'(\d)([sincostan√])',
r'\1*\2',
expression)
expression = re.sub(r'(²)([sincostan√])',
r'\1*\2',
expression)
expression = re.sub(r'(sqrt\(\d+\))(\d)',
r'\1*\2',
expression)
expression = re.sub(r'(\))(\d)',
r'\1*\2',
expression)
expression = re.sub(r'(\d)(\()',
r'\1*\2',
expression)
expression = re.sub(r'°\)(\d)',
r'°)*\1',
expression)
return expression

@ -5,7 +5,7 @@
测验模块
"""
from typing import List, Optional
from typing import List, Optional, Dict, Tuple
from question_generator import Expression
@ -21,15 +21,77 @@ class Quiz:
@param questions: 题目列表
"""
self.questions = questions
self.answers: List[Optional[float]] = [None] * len(questions)
self.answers: List[Optional[str]] = [None] * len(questions) # 改为字符串类型存储
self.options: List[List[str]] = self._generate_options_for_questions()
self.current_question_index = 0
self.score = 0
def answer_question(self, answer: float) -> None:
def _generate_options_for_questions(self) -> List[List[str]]:
"""
为所有题目生成固定选项
@return: 每道题的选项列表
"""
options_list = []
for question in self.questions:
options = self._generate_options(question.answer)
options_list.append(options)
return options_list
def _generate_options(self, correct_answer) -> List[str]:
"""
为题目生成选项
@param correct_answer: 正确答案
@return: 选项列表
"""
import random
# 生成4个选项其中一个是正确答案
options = {correct_answer}
# 添加一些干扰项
if isinstance(correct_answer, int):
while len(options) < 4:
# 生成不同长度的干扰项,避免正确答案总是最长的
if random.random() < 0.5:
# 生成1-2位数的干扰项
options.add(random.randint(0, 99))
else:
# 生成2-3位数的干扰项
options.add(random.randint(10, 999))
else:
# 浮点数情况
while len(options) < 4:
# 随机生成不同长度的浮点数选项
if random.random() < 0.33:
# 生成1位小数的数
options.add(round(random.uniform(0, 100), 1))
elif random.random() < 0.66:
# 生成2位小数的数
options.add(round(random.uniform(0, 100), 2))
else:
# 生成整数
options.add(random.randint(0, 100))
# 如果选项不足4个补充一些随机数
while len(options) < 4:
if random.random() < 0.5:
options.add(random.randint(0, 100))
else:
options.add(round(random.uniform(0, 100),
random.choice([0, 1, 2])))
# 将所有选项转换为字符串
options_list = [str(opt) for opt in options]
random.shuffle(options_list)
return options_list[:4]
def answer_question(self, answer: str) -> None:
"""
回答当前题目
@param answer: 用户答案
@param answer: 用户答案字符串形式
"""
if 0 <= self.current_question_index < len(self.questions):
self.answers[self.current_question_index] = answer
@ -66,6 +128,16 @@ class Quiz:
return self.questions[self.current_question_index]
return None
def get_current_options(self) -> List[str]:
"""
获取当前题目的选项
@return: 当前题目的选项列表
"""
if 0 <= self.current_question_index < len(self.questions):
return self.options[self.current_question_index]
return []
def calculate_score(self) -> float:
"""
计算得分
@ -75,11 +147,18 @@ class Quiz:
correct_count = 0
for i, (question, answer) in enumerate(zip(self.questions, self.answers)):
if answer is not None:
# 允许一定的浮点数误差
if abs(question.answer - answer) < 1e-6:
correct_count += 1
try:
# 将字符串答案转换为浮点数进行比较
if abs(question.answer - float(answer)) < 1e-6:
correct_count += 1
except ValueError:
# 如果转换失败,说明答案无效,不计入正确答案
pass
self.score = (correct_count / len(self.questions)) * 100 if self.questions else 0
if self.questions:
self.score = (correct_count / len(self.questions)) * 100
else:
self.score = 0
return self.score
def is_finished(self) -> bool:
@ -88,4 +167,5 @@ class Quiz:
@return: 是否已完成
"""
return self.current_question_index == len(self.questions) - 1 and self.answers[self.current_question_index] is not None
return (self.current_question_index == len(self.questions) - 1 and
self.answers[self.current_question_index] is not None)

@ -11,6 +11,7 @@ import re
import random
import hashlib
import smtplib
from abc import ABC, abstractmethod
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from typing import Dict, Optional
@ -22,7 +23,8 @@ class User:
用户类存储用户信息
"""
def __init__(self, email: str, username: str = "", password_hash: str = "", registration_code: str = ""):
def __init__(self, email: str, username: str = "",
password_hash: str = "", registration_code: str = ""):
"""
初始化用户对象
@ -38,7 +40,48 @@ class User:
self.is_registered = bool(password_hash)
class UserManager:
class AbstractUserManager(ABC):
"""
抽象用户管理类定义用户管理的接口
"""
@abstractmethod
def register_user(self, email: str, username: str) -> bool:
"""注册新用户"""
pass
@abstractmethod
def verify_registration_code(self, email: str, code: str) -> bool:
"""验证注册码"""
pass
@abstractmethod
def set_password(self, email: str, password: str) -> bool:
"""设置用户密码"""
pass
@abstractmethod
def login(self, username: str, password: str) -> bool:
"""用户登录"""
pass
@abstractmethod
def change_password(self, old_password: str, new_password: str) -> bool:
"""修改用户密码"""
pass
@abstractmethod
def logout(self) -> None:
"""用户登出"""
pass
@abstractmethod
def delete_account(self, email: str, password: str) -> bool:
"""注销账户"""
pass
class UserManager(AbstractUserManager):
"""
用户管理类负责处理用户注册登录和密码管理
"""
@ -72,9 +115,11 @@ class UserManager:
email=email,
username=user_data.get('username', ''),
password_hash=user_data.get('password_hash', ''),
registration_code=user_data.get('registration_code', '')
registration_code=user_data.get(
'registration_code', '')
)
user.is_registered = user_data.get('is_registered', False)
user.is_registered = user_data.get('is_registered',
False)
self.users[email] = user
except (json.JSONDecodeError, FileNotFoundError):
self.users = {}
@ -118,9 +163,8 @@ class UserManager:
# 用户名长度应在3-20个字符之间
if not (3 <= len(username) <= 20):
return False
# 用户名只能包含字母、数字和下划线
pattern = r'^[a-zA-Z0-9_]+$'
return re.match(pattern, username) is not None
# 不再限制用户名只能包含字母、数字和下划线,允许使用各种字符包括中文
return True
def generate_registration_code(self) -> str:
"""
@ -155,20 +199,76 @@ class UserManager:
return has_lower and has_upper and has_digit
def register_user(self, email: str, username: str) -> bool:
def _create_registration_email(self, email: str, code: str
) -> MIMEMultipart:
"""
注册新用户发送注册码
创建注册邮件内容
@param email: 接收邮箱
@param code: 注册码
@return: 邮件对象
"""
message = MIMEMultipart()
message["From"] = self.sender_email
message["To"] = email
message["Subject"] = "数学练习系统注册码"
body = f"""
您好
欢迎使用数学练习系统
您的注册码是: {code}
请在注册界面输入此注册码完成注册
如果您没有请求此注册码请忽略此邮件
祝学习愉快
数学练习系统团队
"""
message.attach(MIMEText(body, "plain", "utf-8"))
return message
def send_registration_code_via_email(self, email: str, code: str) -> bool:
"""
通过电子邮件发送注册码
@param email: 接收邮箱
@param code: 注册码
@return: 是否发送成功
"""
try:
message = self._create_registration_email(email, code)
# 使用SMTP_SSL连接QQ邮箱服务器
server = smtplib.SMTP_SSL(self.smtp_server, self.smtp_port)
server.login(self.sender_email, self.sender_password)
text = message.as_string()
server.sendmail(self.sender_email, email, text)
server.quit()
return True
except Exception as e:
print(f"邮件发送失败: {str(e)}")
return False
def _check_user_registration_eligibility(self, email: str,
username: str) -> bool:
"""
检查用户是否符合注册条件
@param email: 用户邮箱
@param username: 用户名
@return: 是否成功发送注册码
@return: 是否符合注册条件
"""
if not self.is_valid_email(email):
messagebox.showerror("错误", "邮箱格式不正确")
return False
if not self.is_valid_username(username):
messagebox.showerror("错误", "用户名应为3-20位只能包含字母、数字和下划线")
messagebox.showerror("错误", "用户名长度应为3-20个字符")
return False
# 检查邮箱是否已被注册
@ -181,9 +281,23 @@ class UserManager:
if user.username == username and user.is_registered:
messagebox.showerror("错误", "该用户名已存在")
return False
return True
def register_user(self, email: str, username: str) -> bool:
"""
注册新用户发送注册码
@param email: 用户邮箱
@param username: 用户名
@return: 是否成功发送注册码
"""
if not self._check_user_registration_eligibility(email, username):
return False
registration_code = self.generate_registration_code()
user = User(email=email, username=username, registration_code=registration_code)
user = User(email=email, username=username,
registration_code=registration_code)
self.users[email] = user
# 尝试发送邮件
@ -192,65 +306,23 @@ class UserManager:
messagebox.showinfo("成功", "注册码已发送到您的邮箱,请查收")
return True
else:
messagebox.showerror("错误", "无法发送注册码到邮箱,请检查网络连接或邮箱地址")
messagebox.showerror(
"错误",
"无法发送注册码到邮箱,请检查网络连接或邮箱地址")
# 即使邮件发送失败,也保存用户信息以便重试
self.save_users()
return False
def send_registration_code_via_email(self, email: str, code: str) -> bool:
"""
通过电子邮件发送注册码
@param email: 接收邮箱
@param code: 注册码
@return: 是否发送成功
"""
try:
# 创建邮件内容
message = MIMEMultipart()
message["From"] = self.sender_email
message["To"] = email
message["Subject"] = "数学练习系统注册码"
body = f"""
您好
欢迎使用数学练习系统
您的注册码是: {code}
请在注册界面输入此注册码完成注册
如果您没有请求此注册码请忽略此邮件
祝学习愉快
数学练习系统团队
"""
message.attach(MIMEText(body, "plain", "utf-8"))
# 使用SMTP_SSL连接QQ邮箱服务器
server = smtplib.SMTP_SSL(self.smtp_server, self.smtp_port)
server.login(self.sender_email, self.sender_password)
text = message.as_string()
server.sendmail(self.sender_email, email, text)
server.quit()
return True
except Exception as e:
print(f"邮件发送失败: {str(e)}")
return False
def verify_registration_code(self, email: str, code: str) -> bool:
"""
验证注册码
@param email: 用户邮箱
@param code: 用户输入的注册码
@param code: 注册码
@return: 注册码是否正确
"""
if email not in self.users:
messagebox.showerror("错误", "请先获取注册码")
messagebox.showerror("错误", "用户不存在")
return False
user = self.users[email]
@ -269,7 +341,9 @@ class UserManager:
@return: 是否设置成功
"""
if not self.is_valid_password(password):
messagebox.showerror("错误", "密码必须为6-10位且包含大小写字母和数字")
messagebox.showerror(
"错误",
"密码必须为6-10位且包含大小写字母和数字")
return False
if email not in self.users:
@ -280,8 +354,21 @@ class UserManager:
user.password_hash = self.hash_password(password)
user.is_registered = True
self.save_users()
messagebox.showinfo("成功", "密码设置成功,请登录")
return True
def _find_user_by_username(self, username: str) -> Optional[User]:
"""
根据用户名查找用户
@param username: 用户名
@return: 用户对象或None
"""
for u in self.users.values():
if u.username == username:
return u
return None
def login(self, username: str, password: str) -> bool:
"""
用户登录
@ -290,12 +377,7 @@ class UserManager:
@param password: 用户密码
@return: 是否登录成功
"""
# 根据用户名查找用户
user = None
for u in self.users.values():
if u.username == username:
user = u
break
user = self._find_user_by_username(username)
if user is None:
messagebox.showerror("错误", "用户不存在")
@ -329,7 +411,9 @@ class UserManager:
return False
if not self.is_valid_password(new_password):
messagebox.showerror("错误", "新密码必须为6-10位且包含大小写字母和数字")
messagebox.showerror(
"错误",
"新密码必须为6-10位且包含大小写字母和数字")
return False
self.current_user.password_hash = self.hash_password(new_password)
@ -378,4 +462,4 @@ class UserManager:
self.current_user = None
self.save_users()
return True
return True

@ -1,8 +1,14 @@
{
"3154420541@qq.com": {
"username": "123",
"password_hash": "b17e1e0450dac425ea318253f6f852972d69731d6c7499c001468b695b6da219",
"registration_code": "926322",
"is_registered": true
},
"1426688201@qq.com": {
"username": "111",
"password_hash": "c4318372f98f4c46ed3a32c16ee4d7a76c832886d887631c0294b3314f34edf1",
"registration_code": "939598",
"registration_code": "769790",
"is_registered": true
}
}
Loading…
Cancel
Save