Compare commits

...

4 Commits
V3.0 ... main

Binary file not shown.

@ -1,2 +1,144 @@
# Partner_Project
# 小初高数学学习系统
## 项目简介
这是一个基于Java Swing开发的数学学习软件专门为小学、初中和高中学生设计。系统提供个性化的数学题目生成、答题练习和成绩记录功能支持用户注册、登录和密码管理。
## 功能特性
### 用户管理
- **用户注册**:支持邮箱、用户名、密码和年级选择注册
- **用户登录**:支持邮箱或用户名登录
- **密码修改**:安全的密码修改功能
- **数据持久化**用户数据使用JSON格式存储
### 题目生成
- **分级题目**:根据用户年级(小学、初中、高中)生成相应难度的数学题目
- **智能去重**:避免重复题目,确保练习效果
- **题目保存**:自动保存生成的题目到用户专属文件夹
### 答题系统
- **选择题形式**每题提供4个选项
- **实时评分**:答题后立即显示结果
- **成绩记录**:记录每次练习的成绩
### 年级适配
- **小学**:基础四则运算
- **初中**:包含平方、平方根运算
- **高中**:包含三角函数运算
## 技术架构
### 开发环境
- **语言**Java 21
- **构建工具**Maven
- **GUI框架**Swing
- **数据格式**JSON
### 项目结构
```
src/
├── model/ # 数据模型层
│ ├── UserManager.java # 用户管理
│ ├── QuestionMaker.java # 题目生成器
│ ├── Question.java # 题目模型
│ ├── Grade.java # 年级枚举
│ ├── PrimaryMaker.java # 小学题目生成
│ ├── MiddleMaker.java # 初中题目生成
│ └── HighMaker.java # 高中题目生成
├── ui/ # 用户界面层
│ ├── MathLearningApp.java # 主程序入口
│ ├── LoginFrame.java # 登录界面
│ ├── RegisterDialog.java # 注册对话框
│ ├── GradeSelectionFrame.java # 年级选择界面
│ ├── QuizFrame.java # 答题界面
│ ├── ResultFrame.java # 结果界面
│ ├── UserSettingsDialog.java # 用户设置
│ └── ChangePasswordDialog.java # 修改密码
├── others/ # 资源文件
└── Math.exe # 可执行文件
```
### 依赖库
- **Gson 2.10.1**JSON数据处理
- **JavaMail 1.6.2**:邮件功能支持
- **JAF 1.1.1**JavaBeans激活框架
## 安装与运行
### 平台支持
![Windows](https://img.shields.io/badge/Windows-Supported-0078D6?logo=windows)
| 操作系统 | 版本要求 | 支持状态 | 应用 |
|---------|-------------|--------|---------|
| Windows | 11 | ✅ 完全支持 | exe应用程序 |
运行前,请确保系统已安装以下环境:
**Windows:**
- **openjdk** 21.0.5(2024-10-15 LTS)
- **OpenJDK Runtime Environment** build 21.0.5+11-LTS
- **OpenJDK 64-Bit Server VM** build 21.0.5+11-LTS, mixed mode, sharing
- **Maven** 3.6或更高版本
### 直接运行
- 双击Math.exe程序
### 编译运行
```bash
# 使用Maven编译
mvn compile
# 运行程序
mvn exec:java
# 打包为可执行JAR
mvn package
```
## 使用说明
### 首次使用
1. 启动程序后进入登录界面
2. 点击"注册"按钮创建新账户
3. 填写邮箱、验证码
4. 验证完成后填写用户名、密码并选择年级
5. 注册成功后返回登录界面登录
### 练习流程
1. 登录成功后选择年级系统会根据注册信息自动推荐以及题目数量10-30
2. 进入答题界面开始练习
3. 选择答案后点击“下一题”继续
4. 完成所有题目后自动生成并显示成绩
### 用户设置
- 在登录后界面可以修改密码
- 系统会自动保存练习记录
## 数据存储
### 用户数据
- 位置:`users.json`
- 格式JSON格式存储用户信息
### 题目记录
- 位置:`questions/{邮箱}/` 目录
- 格式:按时间戳命名的文本文件
## 开发说明
### 扩展题目类型
要添加新的题目类型,需要:
1. 在相应的`*Maker.java`类中添加题目生成逻辑
2. 在`QuestionMaker.java`中更新表达式解析逻辑
3. 如果需要新的年级,更新`Grade.java`枚举
### 自定义界面
所有UI组件都使用Swing开发可以通过修改对应的`*.java`文件来自定义界面样式和布局。
### 分工
后端:张一帆
前端:赵俊杰
邮箱注册实现:张一帆,赵俊杰
调试:赵俊杰,张一帆

@ -0,0 +1,143 @@
# 小初高数学学习系统
## 项目简介
这是一个基于Java Swing开发的数学学习软件专门为小学、初中和高中学生设计。系统提供个性化的数学题目生成、答题练习和成绩记录功能支持用户注册、登录和密码管理。
## 功能特性
### 用户管理
- **用户注册**:支持邮箱、用户名、密码和年级选择注册
- **用户登录**:支持邮箱或用户名登录
- **密码修改**:安全的密码修改功能
- **数据持久化**用户数据使用JSON格式存储
### 题目生成
- **分级题目**:根据用户年级(小学、初中、高中)生成相应难度的数学题目
- **智能去重**:避免重复题目,确保练习效果
- **题目保存**:自动保存生成的题目到用户专属文件夹
### 答题系统
- **选择题形式**每题提供4个选项
- **实时评分**:答题后立即显示结果
- **成绩记录**:记录每次练习的成绩
### 年级适配
- **小学**:基础四则运算
- **初中**:包含平方、平方根运算
- **高中**:包含三角函数运算
## 技术架构
### 开发环境
- **语言**Java 21
- **构建工具**Maven
- **GUI框架**Swing
- **数据格式**JSON
### 项目结构
```
src/
├── model/ # 数据模型层
│ ├── UserManager.java # 用户管理
│ ├── QuestionMaker.java # 题目生成器
│ ├── Question.java # 题目模型
│ ├── Grade.java # 年级枚举
│ ├── PrimaryMaker.java # 小学题目生成
│ ├── MiddleMaker.java # 初中题目生成
│ └── HighMaker.java # 高中题目生成
├── ui/ # 用户界面层
│ ├── MathLearningApp.java # 主程序入口
│ ├── LoginFrame.java # 登录界面
│ ├── RegisterDialog.java # 注册对话框
│ ├── GradeSelectionFrame.java # 年级选择界面
│ ├── QuizFrame.java # 答题界面
│ ├── ResultFrame.java # 结果界面
│ ├── UserSettingsDialog.java # 用户设置
│ └── ChangePasswordDialog.java # 修改密码
├── others/ # 资源文件
└── Math.exe # 可执行文件
```
### 依赖库
- **Gson 2.10.1**JSON数据处理
- **JavaMail 1.6.2**:邮件功能支持
- **JAF 1.1.1**JavaBeans激活框架
## 安装与运行
### 平台支持
![Windows](https://img.shields.io/badge/Windows-Supported-0078D6?logo=windows)
| 操作系统 | 版本要求 | 支持状态 | 应用 |
|---------|-------------|--------|---------|
| Windows | 11 | ✅ 完全支持 | exe应用程序 |
运行前,请确保系统已安装以下环境:
**Windows:**
- **openjdk** 21.0.5(2024-10-15 LTS)
- **OpenJDK Runtime Environment** build 21.0.5+11-LTS
- **OpenJDK 64-Bit Server VM** build 21.0.5+11-LTS, mixed mode, sharing
- **Maven** 3.6或更高版本
### 直接运行
- 双击Math.exe程序
### 编译运行
```bash
# 使用Maven编译
mvn compile
# 运行程序
mvn exec:java
# 打包为可执行JAR
mvn package
```
## 使用说明
### 首次使用
1. 启动程序后进入登录界面
2. 点击"注册"按钮创建新账户
3. 填写邮箱、验证码
4. 验证完成后填写用户名、密码并选择年级
5. 注册成功后返回登录界面登录
### 练习流程
1. 登录成功后选择年级系统会根据注册信息自动推荐以及题目数量10-30
2. 进入答题界面开始练习
3. 选择答案后点击“下一题”继续
4. 完成所有题目后自动生成并显示成绩
### 用户设置
- 在登录后界面可以修改密码
- 系统会自动保存练习记录
## 数据存储
### 用户数据
- 位置:`users.json`
- 格式JSON格式存储用户信息
### 题目记录
- 位置:`questions/{邮箱}/` 目录
- 格式:按时间戳命名的文本文件
## 开发说明
### 扩展题目类型
要添加新的题目类型,需要:
1. 在相应的`*Maker.java`类中添加题目生成逻辑
2. 在`QuestionMaker.java`中更新表达式解析逻辑
3. 如果需要新的年级,更新`Grade.java`枚举
### 自定义界面
所有UI组件都使用Swing开发可以通过修改对应的`*.java`文件来自定义界面样式和布局。
### 分工
后端:张一帆
前端:赵俊杰
邮箱注册实现:张一帆,赵俊杰
调试:赵俊杰,张一帆

Binary file not shown.

@ -1,3 +0,0 @@
Manifest-Version: 1.0
Main-Class: ui.MathLearningApp

@ -0,0 +1,19 @@
1. 37 - 37
2. 48 - 8 + 15 + 99 + 48
3. 55 * 64 * 67 + 11 + 11
4. 88 * 44
5. 44 - 44
6. ( 71 - 16 ) / 64
7. 27 - 27
8. ( 33 / 51 ) * 83
9. 93 / 58
10. 99 * 52

@ -0,0 +1,19 @@
1. 77 * ( 87 / 13 + 74 )
2. 95 - 4 * 16
3. 28 / ( 51 + 88 )
4. 56 * 30
5. 67 + ( 53 - 53 )
6. ( 67 / 78 + 92 ) / 24 * 5
7. 10 + 60 / 96
8. 87 + 48 / 85
9. 55 / 79 + 72
10. ( 88 + 48 ) - 48

@ -0,0 +1,19 @@
1. √42 + √99
2. √85 - 9^2 / 4 * 89 * 49^2
3. 7^2 - 7^2 / (17^2 / √96)
4. √32 / 92 - √37 + 20
5. 75 * √93
6. 29 / (24^2 + 46 / 89)
7. √56 * 17^2 + √80
8. 95^2 - 43 - √77 * 13 * √60
9. 1^2 + 9^2
10. (56^2 - 24) * 71^2

@ -1,38 +1,40 @@
package model;
/**
*
*/
public class HighMaker extends Student {
final static String[] binaryOps = {"+", "-", "*", "/"}; // 操作符
final static String[] trigOps = {"sin", "cos", "tan"};
int operandCount;
String[] questionParts; // 操作数
boolean[] specialOpFlags; // 特殊操作符索引序列
int parenStart;
int parenEnd;
private static final String[] BINARY_OPS = {"+", "-", "*", "/"};
private static final String[] TRIG_OPS = {"sin", "cos", "tan"};
private int operandCount;
private String[] questionParts;
private boolean[] specialOpFlags;
private int parenStart;
private int parenEnd;
public HighMaker(String name, String password, String path) {
super(name, password, path);
}
// 数据预处理
/**
*
*/
public void getRandom() {
this.operandCount = random.nextInt(4) + 2;
this.questionParts = new String[this.operandCount];
// 使用基类方法随机决定特殊操作符的数量和位置
this.specialOpFlags = randomMaker(this.operandCount);
// 生成题目各部分
for (int i = 0; i < operandCount; i++) {
int operand = random.nextInt(100) + 1;
if (specialOpFlags[i]) {
String op = trigOps[random.nextInt(trigOps.length)];
String op = TRIG_OPS[random.nextInt(TRIG_OPS.length)];
questionParts[i] = op + "(" + operand + ")";
} else {
questionParts[i] = String.valueOf(operand);
}
}
// 使用基类方法生成有效括号
int[] parenPos = bracketMaker(this.operandCount);
this.parenStart = parenPos[0];
this.parenEnd = parenPos[1];
@ -53,11 +55,10 @@ public class HighMaker extends Student {
}
if (i < operandCount - 1) {
String op = binaryOps[random.nextInt(binaryOps.length)];
String op = BINARY_OPS[random.nextInt(BINARY_OPS.length)];
question.append(" ").append(op).append(" ");
}
}
return question.toString();
}
}

@ -1,31 +1,33 @@
package model;
/**
*
*/
public class MiddleMaker extends Student {
final static String[] binaryOps = {"+", "-", "*", "/"}; // 操作符
final static String[] unaryOps = {"^2", "√"};
int operandCount;
String[] questionParts; // 操作数
boolean[] specialOpFlags; // 特殊操作符索引序列
int parenStart;
int parenEnd;
private static final String[] BINARY_OPS = {"+", "-", "*", "/"};
private static final String[] UNARY_OPS = {"^2", "√"};
private int operandCount;
private String[] questionParts;
private int parenStart;
private int parenEnd;
public MiddleMaker(String name, String password, String path) {
super(name, password, path);
}
// 数据预处理
/**
*
*/
public void getRandom() {
this.operandCount = random.nextInt(4) + 2;
this.questionParts = new String[this.operandCount];
boolean[] specialOpFlags = randomMaker(this.operandCount);
// 使用基类方法随机决定特殊操作符的数量和位置
this.specialOpFlags = randomMaker(this.operandCount);
// 生成题目各部分
for (int i = 0; i < this.operandCount; i++) {
int operand = random.nextInt(100) + 1;
if (this.specialOpFlags[i]) {
String op = unaryOps[random.nextInt(unaryOps.length)];
if (specialOpFlags[i]) {
String op = UNARY_OPS[random.nextInt(UNARY_OPS.length)];
if (op.equals("√")) {
this.questionParts[i] = op + operand;
} else {
@ -36,7 +38,6 @@ public class MiddleMaker extends Student {
}
}
// 使用基类方法生成有效括号
int[] parenPos = bracketMaker(this.operandCount);
this.parenStart = parenPos[0];
this.parenEnd = parenPos[1];
@ -56,10 +57,9 @@ public class MiddleMaker extends Student {
question.append(")");
}
if (i < operandCount - 1) {
question.append(" ").append(binaryOps[random.nextInt(binaryOps.length)]).append(" ");
question.append(" ").append(BINARY_OPS[random.nextInt(BINARY_OPS.length)]).append(" ");
}
}
return question.toString();
}
}

@ -2,141 +2,185 @@ package model;
import java.util.Stack;
/**
*
*/
public class PrimaryMaker extends Student {
final static char[] operators = {'+', '-', '*', '/'}; // 操作符
int operandCount; // 操作数个数
int[] operands; // 操作数列表
int parenStart; // 左括号索引
int parenEnd; // 右括号
private static final char[] OPERATORS = {'+', '-', '*', '/'};
private static final int MIN_OPERANDS = 2;
private static final int MAX_OPERANDS = 5;
private static final int MAX_OPERAND_VALUE = 100;
private int operandCount;
private int[] operands;
private int parenStart;
private int parenEnd;
public PrimaryMaker(String name, String password, String path) {
super(name, password, path);
}
// 数据预处理
public void getRandom() {
// 随机决定操作数个数
this.operandCount = random.nextInt(4) + 2;
this.operands = new int[operandCount];
for (int j = 0; j < operandCount; j++) {
operands[j] = random.nextInt(100) + 1;
}
// 使用基类方法生成有效括号
int[] parenPos = bracketMaker(this.operandCount);
this.parenStart = parenPos[0];
this.parenEnd = parenPos[1];
}
@Override
protected String makeOneQuestion() {
String question;
while (true) {
question = generateSingleQuestion();
try {
if (isQuestionValid(question)) {
break;
}
} catch (Exception e) {
// 如果表达式求值出错,重新生成
if (isQuestionValid(question)) {
break;
}
}
return question;
}
private String generateSingleQuestion() {
getRandom();
generateRandomParameters();
StringBuilder question = new StringBuilder();
for (int j = 0; j < this.operandCount; j++) {
if (j == this.parenStart) {
question.append("( ");
}
question.append(operands[j]);
if (j == this.parenEnd) {
question.append(" )");
for (int j = 0; j < operandCount; j++) {
appendOperandWithParentheses(question, j);
if (j < operandCount - 1) {
appendOperator(question, j);
}
}
return question.toString();
}
if (j < this.operandCount - 1) {
char op = operators[random.nextInt(operators.length)];
private void generateRandomParameters() {
operandCount = random.nextInt(MAX_OPERANDS - MIN_OPERANDS + 1) + MIN_OPERANDS;
operands = new int[operandCount];
// 确保减法时被减数大于减数
if (op == '-' && operands[j] < operands[j + 1]) {
int temp = operands[j];
operands[j] = operands[j + 1];
operands[j + 1] = temp;
}
for (int j = 0; j < operandCount; j++) {
operands[j] = random.nextInt(MAX_OPERAND_VALUE) + 1;
}
if (op == '/') {
this.operands[j + 1] = this.operands[j + 1] == 0 ? 1 : this.operands[j + 1];
}
question.append(" ").append(op).append(" ");
}
int[] parenPos = bracketMaker(operandCount);
parenStart = parenPos[0];
parenEnd = parenPos[1];
}
private void appendOperandWithParentheses(StringBuilder question, int index) {
if (index == parenStart) {
question.append("( ");
}
question.append(operands[index]);
if (index == parenEnd) {
question.append(" )");
}
}
private void appendOperator(StringBuilder question, int currentIndex) {
char op = OPERATORS[random.nextInt(OPERATORS.length)];
if (op == '-') {
ensureSubtractionIsValid(currentIndex);
}
if (op == '/') {
ensureDivisionIsValid(currentIndex);
}
question.append(" ").append(op).append(" ");
}
private void ensureSubtractionIsValid(int currentIndex) {
if (operands[currentIndex] < operands[currentIndex + 1]) {
int temp = operands[currentIndex];
operands[currentIndex] = operands[currentIndex + 1];
operands[currentIndex + 1] = temp;
}
}
private void ensureDivisionIsValid(int currentIndex) {
if (operands[currentIndex + 1] == 0) {
operands[currentIndex + 1] = 1;
}
return question.toString();
}
private boolean isQuestionValid(String expression) {
// 检查中间过程和最终结果是否为负数
try {
evaluateExpression(expression, true);
return true;
} catch (ArithmeticException e) {
return false; // 捕获到负数异常
return false;
}
return true;
}
private double evaluateExpression(String expression, boolean checkNegative) throws ArithmeticException {
private double evaluateExpression(String expression, boolean checkNegative)
throws ArithmeticException {
Stack<Double> numbers = new Stack<>();
Stack<Character> ops = new Stack<>();
for (int i = 0; i < expression.length(); i++) {
char c = expression.charAt(i);
if (c == ' ') continue;
if (c == ' ') {
continue;
}
if (Character.isDigit(c)) {
StringBuilder sb = new StringBuilder();
while (i < expression.length() && (Character.isDigit(expression.charAt(i)) || expression.charAt(i) == '.')) {
sb.append(expression.charAt(i++));
}
i--;
numbers.push(Double.parseDouble(sb.toString()));
i = parseAndPushNumber(expression, i, numbers);
} else if (c == '(') {
ops.push(c);
} else if (c == ')') {
while (ops.peek() != '(') {
double result = applyOp(ops.pop(), numbers.pop(), numbers.pop());
if (checkNegative && result < 0) {
throw new ArithmeticException("Negative intermediate result");
}
numbers.push(result);
}
ops.pop();
processClosingParenthesis(numbers, ops, checkNegative);
} else if (isOperator(c)) {
while (!ops.isEmpty() && hasPrecedence(c, ops.peek())) {
double result = applyOp(ops.pop(), numbers.pop(), numbers.pop());
if (checkNegative && result < 0) {
throw new ArithmeticException("Negative intermediate result");
}
numbers.push(result);
}
ops.push(c);
processOperator(c, numbers, ops, checkNegative);
}
}
processRemainingOperators(numbers, ops, checkNegative);
double finalResult = numbers.pop();
validateResult(finalResult, checkNegative);
return finalResult;
}
private int parseAndPushNumber(String expression, int startIndex,
Stack<Double> numbers) {
StringBuilder sb = new StringBuilder();
int i = startIndex;
while (i < expression.length()
&& (Character.isDigit(expression.charAt(i)) || expression.charAt(i) == '.')) {
sb.append(expression.charAt(i++));
}
numbers.push(Double.parseDouble(sb.toString()));
return i - 1;
}
private void processClosingParenthesis(Stack<Double> numbers, Stack<Character> ops,
boolean checkNegative) {
while (ops.peek() != '(') {
double result = applyOp(ops.pop(), numbers.pop(), numbers.pop());
validateResult(result, checkNegative);
numbers.push(result);
}
ops.pop();
}
private void processOperator(char currentOp, Stack<Double> numbers,
Stack<Character> ops, boolean checkNegative) {
while (!ops.isEmpty() && hasPrecedence(currentOp, ops.peek())) {
double result = applyOp(ops.pop(), numbers.pop(), numbers.pop());
validateResult(result, checkNegative);
numbers.push(result);
}
ops.push(currentOp);
}
private void processRemainingOperators(Stack<Double> numbers, Stack<Character> ops,
boolean checkNegative) {
while (!ops.isEmpty()) {
double result = applyOp(ops.pop(), numbers.pop(), numbers.pop());
if (checkNegative && result < 0) {
throw new ArithmeticException("Negative intermediate result");
}
validateResult(result, checkNegative);
numbers.push(result);
}
}
double finalResult = numbers.pop();
if (checkNegative && finalResult < 0) {
throw new ArithmeticException("Negative final result");
private void validateResult(double result, boolean checkNegative) {
if (checkNegative && result < 0) {
throw new ArithmeticException("Negative result");
}
return finalResult;
}
private boolean isOperator(char c) {
@ -152,15 +196,22 @@ public class PrimaryMaker extends Student {
private double applyOp(char op, double b, double a) {
switch (op) {
case '+': return a + b;
case '+':
return a + b;
case '-':
if (a < b) throw new ArithmeticException("Negative result in subtraction");
if (a < b) {
throw new ArithmeticException("Negative result in subtraction");
}
return a - b;
case '*': return a * b;
case '*':
return a * b;
case '/':
if (b == 0) throw new UnsupportedOperationException("Cannot divide by zero");
if (b == 0) {
throw new ArithmeticException("Cannot divide by zero");
}
return a / b;
default:
return 0;
}
return 0;
}
}

@ -1,64 +1,57 @@
package model;
/**
*
*
*
*/
public class Question {
private final String questionText;
private final String[] options;
private final int correctAnswer;
private final String expression;
public record Question(String expression, String questionText, String[] options, int correctAnswer) {
/**
*
*
*
* @param expression
* @param questionText
* @param options (4)
* @param expression
* @param questionText
* @param options (4)
* @param correctAnswer (0-3)
*/
public Question(String expression, String questionText, String[] options,
int correctAnswer) {
this.expression = expression;
this.questionText = questionText;
this.options = options;
this.correctAnswer = correctAnswer;
public Question {
}
/**
*
*
*
* @return
*/
public String getQuestionText() {
@Override
public String questionText() {
return questionText;
}
/**
*
*
*
* @return
*/
public String[] getOptions() {
@Override
public String[] options() {
return options;
}
/**
*
*
*
* @return
*/
public int getCorrectAnswer() {
@Override
public int correctAnswer() {
return correctAnswer;
}
/**
*
*
*
* @return
*/
public String getExpression() {
@Override
public String expression() {
return expression;
}
}

@ -14,8 +14,15 @@ import java.util.List;
import java.util.Random;
import java.util.Set;
/**
*
*/
public class QuestionMaker {
private static final int max_attempts=100;
private static final int MAX_ATTEMPTS = 100;
private static final String QUESTIONS_DIR = "questions";
private static final String FILE_SEPARATOR = File.separator;
private static final int OPTION_COUNT = 4;
private Student student;
private final Random random;
@ -23,265 +30,377 @@ public class QuestionMaker {
this.random = new Random();
}
public List<Question> makeQuestions(Grade grade,
int sum, String email) {
startStudent(grade,email);
/**
*
*
* @param grade
* @param sum
* @param email
* @return
*/
public List<Question> makeQuestions(Grade grade, int sum, String email) {
initializeStudent(grade, email);
List<Question> questions = new ArrayList<>();
Set<String> exQuestions=loadQuestions(email);
Set<String> currQuestions=new HashSet<>();
int attempt=0;
while(questions.size()<sum && attempt<max_attempts){
String expression=student.makeOneQuestion();
if(!exQuestions.contains(expression) &&
!currQuestions.contains(expression)) {
try {
double result = checkExpression(expression,grade);
Question question = createQuestion(expression, result);
questions.add(question);
exQuestions.add(expression);
currQuestions.add(expression);
} catch (Exception e) {
Set<String> existingQuestions = loadQuestions(email);
Set<String> currentQuestions = new HashSet<>();
int attempt = 0;
}
while (questions.size() < sum && attempt < MAX_ATTEMPTS) {
String expression = student.makeOneQuestion();
if (isUniqueExpression(expression, existingQuestions, currentQuestions)) {
tryAddQuestion(expression, grade, questions, existingQuestions,
currentQuestions);
}
attempt++;
}
saveQuestions(email,new ArrayList<>(currQuestions));
saveQuestions(email, new ArrayList<>(currentQuestions));
return questions;
}
private void startStudent(Grade grade, String email) {
String path="questions"+File.separator+email;
private void initializeStudent(Grade grade, String email) {
String path = QUESTIONS_DIR + FILE_SEPARATOR + email;
switch(grade){
switch (grade) {
case primary:
student=new PrimaryMaker("primary","",path);
student = new PrimaryMaker("primary", "", path);
break;
case middle:
student=new MiddleMaker("middle","",path);
student = new MiddleMaker("middle", "", path);
break;
case high:
student=new HighMaker("high","",path);
student = new HighMaker("high", "", path);
break;
default:
throw new IllegalArgumentException("Unknown grade: " + grade);
}
}
private double checkExpression(String expression, Grade grade) {
String expressions=expression.replace(" ","");
private boolean isUniqueExpression(String expression,
Set<String> existingQuestions, Set<String> currentQuestions) {
return !existingQuestions.contains(expression)
&& !currentQuestions.contains(expression);
}
private void tryAddQuestion(String expression, Grade grade,
List<Question> questions, Set<String> existingQuestions,
Set<String> currentQuestions) {
try {
double result = evaluateExpression(expression, grade);
Question question = createQuestion(expression, result);
questions.add(question);
existingQuestions.add(expression);
currentQuestions.add(expression);
} catch (Exception e) {
// Skip invalid expressions
}
}
private double evaluateExpression(String expression, Grade grade) {
String processedExpression = preprocessExpression(expression, grade);
return parseAndEvaluate(processedExpression);
}
private String preprocessExpression(String expression, Grade grade) {
String result = expression.replace(" ", "");
if (grade == Grade.middle) {
// 初中:处理平方和平方根
// 先处理括号内的平方:(5)^2 -> (5*5)
expressions = expressions.replaceAll("\\((\\d+)\\)\\^2", "($1*$1)");
// 处理普通数字的平方5^2 -> (5*5)
expressions = expressions.replaceAll("(\\d+)\\^2", "($1*$1)");
// 处理平方根√16 -> Math.sqrt(16)
expressions = expressions.replaceAll("√(\\d+)", "Math.sqrt($1)");
result = preprocessMiddleSchoolExpression(result);
} else if (grade == Grade.high) {
// 高中:处理三角函数
expressions = expressions.replaceAll("sin\\((\\d+)\\)", "Math.sin(Math.toRadians($1))");
expressions = expressions.replaceAll("cos\\((\\d+)\\)", "Math.cos(Math.toRadians($1))");
expressions = expressions.replaceAll("tan\\((\\d+)\\)", "Math.tan(Math.toRadians($1))");
result = preprocessHighSchoolExpression(result);
}
return evaluation(expressions);
return result;
}
private double evaluation(String expressions) {
return new Object(){
int pos=-1;
int ch;
void nextChar() {
ch = (++pos < expressions.length()) ? expressions.charAt(pos) : -1;
}
private String preprocessMiddleSchoolExpression(String expression) {
// Handle squares: (5)^2 -> (5*5) and 5^2 -> (5*5)
String result = expression.replaceAll("\\((\\d+)\\)\\^2", "($1*$1)");
result = result.replaceAll("(\\d+)\\^2", "($1*$1)");
// Handle square roots: √16 -> Math.sqrt(16)
result = result.replaceAll("√(\\d+)", "Math.sqrt($1)");
return result;
}
boolean eat(int charToEat) {
while (ch == ' ') {
nextChar();
}
if (ch == charToEat) {
nextChar();
return true;
}
return false;
private String preprocessHighSchoolExpression(String expression) {
String result = expression.replaceAll("sin\\((\\d+)\\)",
"Math.sin(Math.toRadians($1))");
result = result.replaceAll("cos\\((\\d+)\\)",
"Math.cos(Math.toRadians($1))");
result = result.replaceAll("tan\\((\\d+)\\)",
"Math.tan(Math.toRadians($1))");
return result;
}
private double parseAndEvaluate(String expression) {
return new ExpressionParser(expression).parse();
}
private static class ExpressionParser {
private final String expression;
private int pos = -1;
private int ch;
ExpressionParser(String expression) {
this.expression = expression;
}
double parse() {
nextChar();
double result = parseExpression();
if (pos < expression.length()) {
throw new RuntimeException("Unexpected: " + (char) ch);
}
return result;
}
double parse() {
private void nextChar() {
ch = (++pos < expression.length()) ? expression.charAt(pos) : -1;
}
private boolean eat(int charToEat) {
while (ch == ' ') {
nextChar();
double x = parseExpression();
if (pos < expressions.length()) {
throw new RuntimeException("Unexpected: " + (char) ch);
}
return x;
}
if (ch == charToEat) {
nextChar();
return true;
}
return false;
}
double parseExpression() {
double x = parseTerm();
for (;;) {
if (eat('+')) {
x += parseTerm();
} else if (eat('-')) {
x -= parseTerm();
} else {
return x;
}
private double parseExpression() {
double x = parseTerm();
while (true) {
if (eat('+')) {
x += parseTerm();
} else if (eat('-')) {
x -= parseTerm();
} else {
return x;
}
}
}
double parseTerm() {
double x = parseFactor();
for (;;) {
if (eat('*')) {
x *= parseFactor();
} else if (eat('/')) {
x /= parseFactor();
} else {
return x;
}
private double parseTerm() {
double x = parseFactor();
while (true) {
if (eat('*')) {
x *= parseFactor();
} else if (eat('/')) {
x /= parseFactor();
} else {
return x;
}
}
}
double parseFactor() {
if (eat('+')) {
return parseFactor();
}
if (eat('-')) {
return -parseFactor();
}
private double parseFactor() {
if (eat('+')) {
return parseFactor();
}
if (eat('-')) {
return -parseFactor();
}
double x;
int startPos = this.pos;
if (eat('(')) {
x = parseExpression();
eat(')');
} else if ((ch >= '0' && ch <= '9') || ch == '.') {
while ((ch >= '0' && ch <= '9') || ch == '.') {
nextChar();
}
x = Double.parseDouble(expressions.substring(startPos, this.pos));
} else if (ch >= 'A' && ch <= 'Z') {
// 处理 Math.xxx 函数
while ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '.') {
nextChar();
}
String func = expressions.substring(startPos, this.pos);
eat('(');
x = parseExpression();
eat(')');
if (func.equals("Math.sqrt")) {
x = Math.sqrt(x);
} else if (func.equals("Math.sin")) {
x = Math.sin(x);
} else if (func.equals("Math.cos")) {
x = Math.cos(x);
} else if (func.equals("Math.tan")) {
x = Math.tan(x);
} else if (func.equals("Math.toRadians")) {
x = Math.toRadians(x);
} else {
throw new RuntimeException("Unknown function: " + func);
}
} else if (ch >= 'a' && ch <= 'z') {
while (ch >= 'a' && ch <= 'z') {
nextChar();
}
String function = expressions.substring(startPos, this.pos);
x = parseFactor();
if (function.equals("sqrt")) {
x = Math.sqrt(x);
} else if (function.equals("sin")) {
x = Math.sin(x);
} else if (function.equals("cos")) {
x = Math.cos(x);
} else if (function.equals("tan")) {
x = Math.tan(x);
} else {
throw new RuntimeException("Unknown function: " + function);
}
} else {
throw new RuntimeException("Unexpected: " + (char) ch);
}
int startPos = pos;
if (eat('(')) {
return parseParenthesizedExpression();
} else if (isDigit()) {
return parseNumber(startPos);
} else if (isUpperCaseLetter()) {
return parseMathFunction(startPos);
} else if (isLowerCaseLetter()) {
return parseCustomFunction(startPos);
} else {
throw new RuntimeException("Unexpected: " + (char) ch);
}
}
private boolean isDigit() {
return (ch >= '0' && ch <= '9') || ch == '.';
}
private boolean isUpperCaseLetter() {
return ch >= 'A' && ch <= 'Z';
}
return x;
private boolean isLowerCaseLetter() {
return ch >= 'a' && ch <= 'z';
}
private double parseParenthesizedExpression() {
double x = parseExpression();
eat(')');
return x;
}
private double parseNumber(int startPos) {
while (isDigit()) {
nextChar();
}
return Double.parseDouble(expression.substring(startPos, pos));
}
private double parseMathFunction(int startPos) {
while ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '.') {
nextChar();
}
String func = expression.substring(startPos, pos);
eat('(');
double x = parseExpression();
eat(')');
return applyMathFunction(func, x);
}
private double parseCustomFunction(int startPos) {
while (ch >= 'a' && ch <= 'z') {
nextChar();
}
String function = expression.substring(startPos, pos);
double x = parseFactor();
return applyCustomFunction(function, x);
}
private double applyMathFunction(String func, double value) {
switch (func) {
case "Math.sqrt":
return Math.sqrt(value);
case "Math.sin":
return Math.sin(value);
case "Math.cos":
return Math.cos(value);
case "Math.tan":
return Math.tan(value);
case "Math.toRadians":
return Math.toRadians(value);
default:
throw new RuntimeException("Unknown function: " + func);
}
}
private double applyCustomFunction(String function, double value) {
switch (function) {
case "sqrt":
return Math.sqrt(value);
case "sin":
return Math.sin(value);
case "cos":
return Math.cos(value);
case "tan":
return Math.tan(value);
default:
throw new RuntimeException("Unknown function: " + function);
}
}.parse();
}
}
private Question createQuestion(String expression, double result) {
String text="计算: "+expression+" = ? ";
double[] options=new double[4];
int currIndex=random.nextInt(4);
String text = "计算: " + expression + " = ? ";
double[] options = generateOptions(result);
int correctIndex = random.nextInt(OPTION_COUNT);
options[correctIndex] = result;
String[] optionStrings = formatOptions(options);
return new Question(expression, text, optionStrings, correctIndex);
}
options[currIndex]=result;
private double[] generateOptions(double correctAnswer) {
double[] options = new double[OPTION_COUNT];
for(int i=0;i<4;i++) {
if(i!=currIndex) {
double offset=(random.nextDouble()*20-10);
if(Math.abs(offset)<1) {
offset=random.nextBoolean() ? 5 : -5 ;
}
options[i]=offset+result;
for (int i = 0; i < OPTION_COUNT; i++) {
double offset = random.nextDouble() * 20 - 10;
if (Math.abs(offset) < 1) {
offset = random.nextBoolean() ? 5 : -5;
}
options[i] = offset + correctAnswer;
}
String[] optionStrings=new String[4];
for(int i=0;i<4;i++) {
optionStrings[i]=String.format("%c. %.2f",
return options;
}
private String[] formatOptions(double[] options) {
String[] optionStrings = new String[OPTION_COUNT];
for (int i = 0; i < OPTION_COUNT; i++) {
optionStrings[i] = String.format("%c. %.2f",
(char) ('A' + i), options[i]);
}
return new Question(expression, text, optionStrings, currIndex);
return optionStrings;
}
private Set<String> loadQuestions(String email) {
Set<String> questions=new HashSet<>();
File userDir = new File("questions" + File.separator + email);
Set<String> questions = new HashSet<>();
File userDir = getUserDirectory(email);
if(!userDir.exists()||!userDir.isDirectory()) {
if (!userDir.exists() || !userDir.isDirectory()) {
return questions;
}
File[] files = userDir.listFiles((dir, name) -> name.endsWith(".txt"));
if(files==null) {
if (files == null) {
return questions;
}
for(File file:files) {
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.matches("\\d+\\.\\s+.*")) {
String question = line.substring(line.indexOf('.') + 1).trim();
questions.add(question);
}
for (File file : files) {
loadQuestionsFromFile(file, questions);
}
return questions;
}
private void loadQuestionsFromFile(File file, Set<String> questions) {
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
String line;
while ((line = reader.readLine()) != null) {
String question = extractQuestion(line);
if (question != null) {
questions.add(question);
}
} catch (IOException e) {
System.err.println("读取文件失败: " + e.getMessage());
}
} catch (IOException e) {
System.err.println("读取文件失败: " + e.getMessage());
}
return questions;
}
private String extractQuestion(String line) {
line = line.trim();
if (line.matches("\\d+\\.\\s+.*")) {
return line.substring(line.indexOf('.') + 1).trim();
}
return null;
}
private void saveQuestions(String email, List<String> questions) {
File userDir = new File("questions" + File.separator + email);
if(!userDir.exists()) {
if(!userDir.mkdirs()) {
File userDir = getUserDirectory(email);
ensureDirectoryExists(userDir);
String fileName = generateFileName();
File file = new File(userDir, fileName);
writeQuestionsToFile(file, questions);
}
private File getUserDirectory(String email) {
return new File(QUESTIONS_DIR + FILE_SEPARATOR + email);
}
private void ensureDirectoryExists(File directory) {
if (!directory.exists()) {
if (!directory.mkdirs()) {
System.err.println("创建目录失败");
return;
}
}
}
private String generateFileName() {
LocalDateTime now = LocalDateTime.now();
String fileName = now.format(
DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss")) + ".txt";
File file = new File(userDir, fileName);
return now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss")) + ".txt";
}
private void writeQuestionsToFile(File file, List<String> questions) {
try (PrintWriter writer = new PrintWriter(new FileWriter(file))) {
for (int i = 0; i < questions.size(); i++) {
writer.println((i + 1) + ". " + questions.get(i));
@ -293,4 +412,4 @@ public class QuestionMaker {
System.err.println("保存文件失败: " + e.getMessage());
}
}
}
}

@ -145,11 +145,11 @@ public class QuizFrame extends JFrame {
progressLabel.setText(String.format("第 %d/%d 题",
currentIndex + 1, questions.size()));
questionLabel.setText("<html><body style='width: 500px'>" +
question.getQuestionText() + "</body></html>");
question.questionText() + "</body></html>");
optionGroup.clearSelection();
String[] options = question.getOptions();
String[] options = question.options();
for (int i = 0; i < 4; i++) {
optionButtons[i].setText(options[i]);
}
@ -178,7 +178,7 @@ public class QuizFrame extends JFrame {
}
Question question = questions.get(currentIndex);
if (selectedIndex == question.getCorrectAnswer()) {
if (selectedIndex == question.correctAnswer()) {
correctCount++;
}

@ -34,10 +34,10 @@ public class RegisterDialog extends JDialog {
Pattern.compile("^[A-Za-z0-9+_.-]+@qq\\.com$");
// 邮件配置 - 请根据实际情况修改
private static final String SMTP_HOST = "smtp.qq.com"; // QQ邮箱SMTP服务器
private static final String SMTP_PORT = "587"; // 或使用465(SSL)
private static final String SMTP_HOST = "smtp.qq.com"; // SMTP服务器
private static final String SMTP_PORT = "587";
private static final String FROM_EMAIL = "3551664030@qq.com"; // 发件人邮箱
private static final String EMAIL_PASSWORD = "wvazqphcxhuqdbfa"; // 授权码(非登录密码)
private static final String EMAIL_PASSWORD = "wvazqphcxhuqdbfa"; // 授权码
private final JTextField emailField;
private final JTextField codeField;
@ -180,9 +180,6 @@ public class RegisterDialog extends JDialog {
props.put("mail.smtp.port", SMTP_PORT);
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", "true"); // 启用TLS
// 如果使用SSL端口465取消下面两行注释
// props.put("mail.smtp.ssl.enable", "true");
// props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
// 创建会话
Session session = Session.getInstance(props, new Authenticator() {

@ -83,4 +83,4 @@ public class UserSettingsDialog extends JDialog {
ChangePasswordDialog dialog = new ChangePasswordDialog(parentFrame, email);
dialog.setVisible(true);
}
}
}
Loading…
Cancel
Save