1 #1

Merged
hnu202304060319 merged 106 commits from develop into main 5 months ago

@ -1,2 +0,0 @@
# Software-Introduction-Personal-Project

@ -0,0 +1,298 @@
# 试卷生成系统类设计说明文档
本说明文档详细描述系统中各个核心类的设计目的、职责、关键属性及方法实现逻辑。
---
## 1. User 类
### 功能概述
User 是用户信息的封装类,用于存储单个用户的登录凭证和所属学段类型。
### 属性
- `username`:字符串类型,表示用户名。
- `password`:字符串类型,表示用户密码。
- `userType`:字符串类型,表示用户类型,取值为“小学”、“初中”或“高中”。
### 方法
- **构造方法** `User(String username, String password, String userType)`
初始化用户对象,设置用户名、密码和用户类型。
- `getUsername()` / `setUsername(String username)`
获取或设置用户名。
- `getPassword()` / `setPassword(String password)`
获取或设置密码。
- `getUserType()` / `setUserType(String userType)`
获取或设置用户类型。
- `validateCredentials(String inputUsername, String inputPassword)`
验证输入的用户名和密码是否与当前用户匹配。返回布尔值,用于登录判断。
---
## 2. UserManager 类
### 功能概述
UserManager 负责管理所有用户账户、处理登录逻辑,并维护当前登录用户状态。
### 属性
- `users``Map<String, User>` 类型,以用户名为键存储所有预设用户。
- `currentUser``User` 类型,表示当前已登录的用户。
- `currentType``String` 类型,缓存当前出题类型。
### 方法
- **构造方法** `UserManager()`
调用 `initializeUsers()` 初始化9个预设用户3小学 + 3初中 + 3高中
- `initializeUsers()`
向 users 映射中添加预设用户对象用户名如“张三1”密码均为“123”类型按学段分配。
- `login(String username, String password)`
实现登录逻辑:
- 若用户名不存在,提示“该账号不存在”;
- 若密码错误,提示“密码错误”;
- 验证成功,设置 `currentUser``currentType`,返回 true。
- `logout()`
清空当前用户状态(设为 null
- `switchUserType(String newType)`
允许在已登录状态下切换用户类型(实际项目中此方法未被使用,主流程通过重新获取 QuestionSetting 实现切换)。
- `isLoggedIn()`
判断是否有用户登录(检查 `currentUser != null`)。
- `getCurrentUser()`
返回当前登录的 `User` 对象,供其他模块获取用户信息。
---
## 3. QuestionSetting 接口
### 功能概述
定义题目生成器的统一契约,确保所有学段题目生成器具有相同调用方式。
### 方法
- `Expression setQuestion(int count)`
根据指定的操作数数量 `count`,生成一个符合当前学段要求的数学表达式对象。返回 `Expression` 实例。
---
## 4. AbstractQuestionSetting 抽象类
### 功能概述
作为所有题目生成器的基类,提供通用工具方法,减少代码重复。
### 属性
- `RANDOM`:静态 Random 实例,用于生成随机数,保证线程安全且全局一致。
### 方法
- `getRandomOperator()`
`{"+", "-", "*", "/"}` 中随机返回一个四则运算符。
- `getRandomNumber()`
生成 1 到 99 的随机整数含1和99并返回其字符串形式。
- `getPriority(String operator)`
返回运算符的优先级数值,用于括号判断:
- 三角函数、平方、开方:优先级 3
- 乘、除:优先级 2
- 加、减:优先级 1
- 其他或 null返回 0 或 -1
- `addParenthesesIfNeeded(Expression child, String parentOperator, boolean isRightChild)`
根据运算优先级决定是否为子表达式添加括号:
- 子表达式无主运算符(即为纯数字),不加括号
- 子优先级 < 父优先级,加括号
- 若为右子表达式,且父运算符为 - 或 /,即使优先级相等也加括号(如 a - (b - c) 而非 a - b - c
---
## 5. PrimaryQuestionSetting 类(小学)
### 功能概述
实现小学阶段的题目生成逻辑,仅包含四则运算,并保证题目合理性。
### 方法
- `setQuestion(int count)`
递归生成表达式:
- 若 count == 1返回一个随机数字
- 否则将操作数拆分为左右两部分,递归生成左右子表达式;
- 随机选择运算符;
- 若为除法且右值为0重新生成右子表达式
- 若为减法且左值 < 右值,交换左右子表达式以保证结果非负;
- 使用 `addParenthesesIfNeeded` 处理括号;
- 返回最终表达式对象。
小学题目不包含任何一元运算符(如平方、三角函数),也不强制包含特定符号。
---
## 6. MiddleQuestionSetting 类(初中)
### 功能概述
在小学基础上增加平方(²)和平方根(√)运算,并确保每道题至少包含其中一种。
### 新增方法
- `applyUnaryOperator(Expression child, String operator)`
对子表达式应用一元运算符:
- 平方(²):若子表达式为纯数字,则直接拼接“²”;否则加括号后拼接,如 `(a + b)²`
- 平方根(√):若子值为负,替换为新随机正数;若为纯数字,前缀“√”;否则写为 `√(表达式)`
- `applyProbability(Expression result)`
以 30% 概率对表达式应用平方或平方根运算,并更新主运算符。
- `generateFirstQuestion(int count)`
与小学类似,但每次生成后调用 `applyProbability` 尝试添加一元运算。
- `setQuestion(int count)`
循环调用 `generateFirstQuestion`,直到生成的表达式包含“²”或“√”,确保题目符合初中要求。
---
## 7. HighQuestionSetting 类(高中)
### 功能概述
在初中基础上增加三角函数sin、cos、tan并确保每道题至少包含一个三角函数。
### 新增方法
- `applyUnaryOperator(Expression child, String operator)`
扩展支持三角函数:
- `sin`/`cos`:直接包裹为 `sin(表达式)``cos(表达式)`,值为 `Math.round(Math.sin(Math.toRadians(value)))`
- `tan`:若输入角度为 90 + 180k无定义点则替换为新随机数避免异常
- `applyProbability(Expression result)`
以 30% 概率从 `{"²", "√", "sin", "cos", "tan"}` 中随机选择一元运算符应用。
- `generateFirstQuestion(int count)`
逻辑同初中,但支持更多运算符。
- `setQuestion(int count)`
循环生成,直到表达式包含 `sin`、`cos` 或 `tan` 中的任意一个。
⚠️ 三角函数结果被强制转换为整数,可能损失精度,但简化了题目形式。
---
## 8. Expression 类
### 功能概述
封装一个数学表达式的文本形式、计算值及其主运算符,用于递归构建和值传递。
### 属性
- `expression`:表达式的字符串表示(如 `"5 + 3"``"√(16)"`
- `value`:表达式计算结果的整数值
- `mainOperator`:表达式的主运算符(如 `"+"`、`"²"`、`"sin"`),若为纯数字则为 null
### 方法
- 构造方法 `Expression(String expression, int value, String mainOperator)`
初始化表达式对象
- `getExpression()` / `setExpression(String expression)`
获取或设置表达式字符串
- `getValue()` / `setValue(int value)`
获取或设置计算值
- `getMainOperator()` / `setMainOperator(String mainOperator)`
获取或设置主运算符
> 该类是表达式树的节点,支持在生成过程中传递结构与值。
---
## 9. QuestionSettingFactory 类
### 功能概述
工厂类,根据用户类型字符串动态创建对应的题目生成器实例。
### 方法
- `getQuestionSetting(String type)`
使用 switch 表达式:
- `"小学"` → 返回 `new PrimaryQuestionSetting()`
- `"初中"` → 返回 `new MiddleQuestionSetting()`
- `"高中"` → 返回 `new HighQuestionSetting()`
- 其他 → 打印“类型错误”,返回 `null`
> 该类解耦了用户类型与具体实现,便于扩展新学段。
---
## 10. FileManager 类
### 功能概述
负责试卷的本地存储与题目重复性检查。
### 常量
- `ROOT_PATH`:根目录路径,值为 `"./试卷/"`
### 方法
- `savePaper(User user, List<String> questions)`
- 创建用户专属目录(如 `./试卷/张三1/`
- 生成时间戳文件名(格式:`yyyy-MM-dd-HH-mm-ss.txt`
- 以 UTF-8 编码写入题目,格式为 `1. 表达式\n\n2. 表达式\n\n...`
- `checkQuestion(String question, User user)`
- 若用户目录不存在,返回 true无需去重
- 遍历该用户所有历史试卷文件
- 对每行去除题号,与历史文件进行匹配,如果相同则重新生成(查重功能)
> 去重基于字符串精确匹配,不进行数学等价判断。
---
## 11. AllSystem 类
### 功能概述
系统的主控制器,协调用户交互、流程跳转和模块调用。
### 属性
- `scanner``Scanner` 实例,用于读取用户输入
- `userManager`:用户管理器
- `fileManager`:文件管理器
- `questionSettingFactory`:题目生成器工厂
- `questionSetting`:当前使用的题目生成器
- `currentMode`:当前学段类型(如“小学”)
- `isFirstLogin`:标志是否首次登录,用于提示信息
- `tipMessage`:临时提示信息(如“切换成功”)
### 核心方法
- `clearScreen()`
调用系统命令 `cls`Windows清屏失败时打印堆栈不影响主流程
- `initialize()`
初始化所有组件和状态变量
- `showHomeMenu()`
显示欢迎界面,循环处理登录输入,成功后进入 `setPaper()`
- `setPaper()`
提示用户输入题目数量10~30调用 `handleFirstInput`
- `handleFirstInput(String input)`
- 输入 `-1`:返回登录界面
- 输入 10~30循环生成题目调用 `checkQuestion` 去重,显示并保存
- 其他输入:提示重试
- `showChoiceMenu()`
显示主菜单,循环处理用户选择
- `handleSecondInput(String input)`
- 输入 `-1`:返回登录
- 输入 `1`:重新生成试卷
- 输入 `切换为XX`:解析类型,调用工厂切换 `questionSetting`,进入生成流程
- 其他输入:提示重试
---
## 12. Main 类
### 功能概述
程序入口类,启动整个系统。
### 方法
- `main(String[] args)`
创建 `AllSystem` 实例,调用 `showHomeMenu()` 启动交互流程
捕获 `IOException` 并转为运行时异常抛出
---

@ -0,0 +1,209 @@
# 试卷生成系统说明文档
## 目录
- [1. 项目概述](#1-项目概述)
- [2. 功能特性](#2-功能特性)
- [2.1 多学段支持](#21-多学段支持)
- [2.2 智能题目生成](#22-智能题目生成)
- [2.3 题目去重机制](#23-题目去重机制)
- [2.4 用户认证与管理](#24-用户认证与管理)
- [2.5 试卷保存](#25-试卷保存)
- [2.6 交互式菜单](#26-交互式菜单)
- [3. 系统架构](#3-系统架构)
- [3.1 核心模块](#31-核心模块)
- [3.2 数据流](#32-数据流)
- [3.3 扩展性](#33-扩展性)
- [4. 使用说明](#4-使用说明)
- [4.1 启动系统](#41-启动系统)
- [4.2 登录](#42-登录)
- [4.3 生成试卷](#43-生成试卷)
- [4.4 主菜单操作](#44-主菜单操作)
- [4.5 退出](#45-退出)
- [5. 文件存储结构](#5-文件存储结构)
- [5.1 目录结构](#51-目录结构)
- [5.2 文件内容格式](#52-文件内容格式)
- [5.3 去重机制](#53-去重机制)
- [6. 技术栈](#6-技术栈)
- [7. 注意事项](#7-注意事项)
- [7.1 数学精度问题](#71-数学精度问题)
- [7.2 题目去重局限性](#72-题目去重局限性)
- [7.3 系统环境要求](#73-系统环境要求)
- [7.4 安全性](#74-安全性)
- [7.5 扩展建议](#75-扩展建议)
---
## 1. 项目概述
本项目是一个面向小学、初中和高中三个学段的数学试卷自动生成系统,采用 Java 语言开发,运行于命令行环境。系统根据用户所属学段,智能生成符合该学段知识体系的数学题目,支持题目去重、试卷保存、用户登录及学段切换等功能。
系统内置三类题目生成策略:
- **小学**:仅包含加、减、乘、除四则运算,确保减法结果非负、除法无零除。
- **初中**:在小学基础上引入平方(²)与平方根(√)运算,题目中至少包含一项。
- **高中**进一步引入三角函数sin、cos、tan题目中至少包含一个三角函数表达式。
所有题目均以表达式形式呈现,系统自动处理运算优先级与括号逻辑,确保表达式语义清晰、格式规范。生成的试卷按用户独立存储,避免题目重复,便于教师或学生长期使用。
---
## 2. 功能特性
### 2.1 多学段支持
- 支持小学、初中、高中三种用户类型。
- 每种类型对应不同的题目生成规则与运算符集合。
- 用户可在登录后动态切换学段(如从小学切换为高中)。
### 2.2 智能题目生成
- 题目基于递归表达式树构建,结构灵活。
- 自动处理运算优先级,合理添加括号(如 `a + b * c` 不加括号,`a * (b + c)` 加括号)。
- 小学题目保证减法非负、除法无零除。
- 初中/高中题目强制包含特定运算符(²/√ 或 sin/cos/tan确保题目符合学段要求。
### 2.3 题目去重机制
- 系统在生成每道题目前,会扫描该用户历史所有试卷。
- 若发现完全相同的表达式字符串,则重新生成,确保试卷内及跨试卷无重复题目。
### 2.4 用户认证与管理
- 内置 9 个预设用户3 小学 + 3 初中 + 3 高中),用户名与密码固定。
- 登录时验证账号密码,失败可重试。
- 支持退出当前用户并重新登录。
### 2.5 试卷保存
- 试卷以纯文本格式保存至本地文件系统。
- 文件按用户隔离,按生成时间命名(`yyyy-MM-dd-HH-mm-ss.txt`)。
- 每题编号并空行分隔,格式清晰易读。
### 2.6 交互式菜单
提供清晰的命令行菜单,支持:
- 重新生成试卷
- 切换学段(输入“切换为初中”等)
- 退出当前用户
- 输入校验严格,非法输入会提示重试。
---
## 3. 系统架构
系统采用分层 + 工厂模式设计,结构清晰,易于扩展。
### 3.1 核心模块
| 模块 | 类/接口 | 职责 |
|------------|---------|------|
| 用户管理 | User, UserManager | 管理用户信息、登录验证、当前用户状态 |
| 题目生成 | QuestionSetting接口<br>PrimaryQuestionSetting<br>MiddleQuestionSetting<br>HighQuestionSetting | 定义题目生成规范,三类实现分别对应小学、初中、高中 |
| 表达式模型 | Expression | 封装表达式字符串、计算值、主运算符 |
| 公共逻辑 | AbstractQuestionSetting | 提供随机数、运算符、优先级、括号处理等通用方法 |
| 工厂模式 | QuestionSettingFactory | 根据用户类型创建对应题目生成器 |
| 文件管理 | FileManager | 负责试卷保存与题目去重检查 |
| 主控流程 | AllSystem | 控制登录、菜单、生成、保存等全流程 |
| 程序入口 | Main | 启动系统 |
### 3.2 数据流
用户登录 → UserManager 验证 → 设置 currentUser
→ AllSystem 调用 QuestionSettingFactory 获取对应 QuestionSetting
→ 循环调用 `setQuestion()` 生成表达式
→ 每题调用 `FileManager.checkQuestion()` 去重
→ 题目集齐后调用 `FileManager.savePaper()` 保存
### 3.3 扩展性
- 新增学段:实现 QuestionSetting 接口,注册到工厂类。
- 新增运算符:扩展 `getPriority()``applyUnaryOperator()`
- 修改题目结构:调整 `generateFirstQuestion()` 递归逻辑。
---
## 4. 使用说明
### 4.1 启动系统
编译并运行 `Main.java`,系统自动进入登录界面。
### 4.2 登录
输入格式:`用户名 密码`(中间一个空格)
预设账号:
- 小学张三1、张三2、张三3密码均为 `123`
- 初中李四1、李四2、李四3密码均为 `123`
- 高中王五1、王五2、王五3密码均为 `123`
输入 `exit` 可直接退出程序(仅在登录界面有效)。
### 4.3 生成试卷
- 登录成功后,系统提示输入题目数量。
- 输入 10 到 30 之间的整数(如 `15`)。
- 系统逐题生成并显示,完成后提示“试卷生成完成”。
### 4.4 主菜单操作
生成完成后进入主菜单,可执行以下操作:
- 输入 **1**:重新生成试卷(使用当前学段)
- 输入 **切换为小学 / 切换为初中 / 切换为高中**:切换学段并立即进入生成流程
- 输入 **-1**:退出当前用户,返回登录界面
⚠️ 注意切换学段命令必须完整输入“切换为XX”XX只能是“小学”“初中”“高中”之一。
### 4.5 退出
- 在主菜单输入 `-1` 可重新登录。
- 在登录界面输入 `exit` 可彻底退出程序。
---
## 5. 文件存储结构
所有试卷保存在项目根目录下的 `./试卷/` 文件夹中,按用户隔离存储。
### 5.1 目录结构
./试卷/
├── 张三1/
│ ├── 2025-09-29-14-30-22.txt
│ └── 2025-09-29-15-01-45.txt
├── 李四1/
│ └── 2025-09-29-14-35-10.txt
└── 王五1/
└── 2025-09-29-14-40-33.txt
### 5.2 文件内容格式
每份试卷为纯文本文件,内容格式如下:
45 + 23
√(16) * 5
sin(30) + 10
- 每题以 `序号. 表达式` 开头
- 每题后空一行,便于阅读
- 编码为 UTF-8支持中文与数学符号
### 5.3 去重机制
- 系统在保存前遍历该用户所有历史试卷文件。
- 对每一行去除题号前缀(如 `1. `)后与新题目比对。
- 完全相同的表达式将被跳过,重新生成新题。
---
## 6. 技术栈
| 类别 | 技术/工具 |
|--------|-----------|
| 编程语言 | Java 17或兼容版本 |
| 开发范式 | 面向对象编程OOP |
| 设计模式 | 工厂模式、抽象类、接口 |
| 核心特性 | 递归表达式生成、运算符优先级处理、文件 I/O |
| 依赖库 | 仅使用 Java 标准库(`java.util`, `java.io`, `java.nio`, `java.time` |
| 运行环境 | 支持 Java 的命令行终端Windows/Linux/macOS |
| 构建方式 | 无需构建工具,直接编译 `.java` 文件即可运行 |
项目无第三方依赖,可直接通过 `javac``java` 命令编译运行。
---
## 7. 注意事项
### 7.1 数学精度问题
- 三角函数结果被四舍五入为整数(如 `sin(30°) ≈ 0.5 → 1`),这在数学上不严谨,但便于题目简洁。实际教学中建议保留小数或使用符号形式。
- 平方根仅对非负整数开方,负数会自动替换为新随机正数。
- 除法为整数除法(`/` 运算符),结果向下取整(如 `7 / 3 = 2`)。
### 7.2 题目去重局限性
- 去重基于字符串完全匹配,无法识别数学等价但形式不同的表达式(如 `2 + 3``3 + 2` 被视为不同题目)。
- 若需更智能去重,需引入表达式解析与标准化模块。
### 7.3 系统环境要求
- 需要 **写权限** 创建 `./试卷/` 目录及子文件。
- 在 Windows 上使用 `cls` 清屏Linux/macOS 用户可能看到清屏失败(可忽略或修改 `clearScreen()` 方法)。

@ -0,0 +1,55 @@
// AbstractQuestionSetting.java
import java.util.Random;
public abstract class AbstractQuestionSetting implements QuestionSetting {
protected static final Random RANDOM = new Random();
public String getRandomOperator() {
String[] operators = {"+", "-", "*", "/"};
int index = RANDOM.nextInt(4);
return operators[index];
}
public String getRandomNumber() {
int number = RANDOM.nextInt(1, 100);
return String.valueOf(number);
}
public int getPriority(String operator) {
if (operator == null) {
return -1;
}
if (operator.equals("²") || operator.equals("√")
|| operator.equals("sin") || operator.equals("cos") || operator.equals("tan")) {
return 3;
}
if (operator.equals("+") || operator.equals("-")) {
return 1;
}
if (operator.equals("*") || operator.equals("/")) {
return 2;
}
return 0;
}
public String addParenthesesIfNeeded(Expression child, String parentOperator, boolean isRightChild) {
if (child.getMainOperator() == null) {
return child.getExpression();
}
int parentPriority = getPriority(parentOperator);
int childPriority = getPriority(child.getMainOperator());
if (childPriority < parentPriority) {
return "(" + child.getExpression() + ")";
}
if (isRightChild && (parentOperator.equals("-") || parentOperator.equals("/"))) {
if (parentPriority == childPriority) {
return "(" + child.getExpression() + ")";
}
}
return child.getExpression();
}
}

@ -0,0 +1,174 @@
// AllSystem.java
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class AllSystem {
private static final String EXIT_COMMAND = "exit";
private static final String SWITCH_PREFIX = "切换为";
private Scanner scanner;
private UserManager userManager;
private FileManager fileManager;
private QuestionSettingFactory questionSettingFactory;
private QuestionSetting questionSetting;
private String tipMessage;
private String currentMode;
private boolean isFirstLogin;
public static void clearScreen() {
try {
new ProcessBuilder("cmd", "/c", "cls").inheritIO().start().waitFor();
} catch (IOException | InterruptedException exception) {
exception.printStackTrace();
}
}
public void initialize() {
tipMessage = "";
currentMode = "";
isFirstLogin = true;
this.scanner = new Scanner(System.in);
userManager = new UserManager();
fileManager = new FileManager();
questionSettingFactory = new QuestionSettingFactory();
questionSetting = null;
}
public void showHomeMenu() throws IOException {
initialize();
clearScreen();
System.out.println("**************欢迎来到试卷生成系统**************");
System.out.println("————————————————————————————————————————————————");
System.out.println("请输入账号和密码中间用空格分开输入exit退出:");
boolean loginSuccessful = false;
while (!loginSuccessful) {
String input = scanner.nextLine();
if (input.isEmpty()) {
System.out.println("您的输入不符合要求,请重试");
continue;
}
if (input.equals(EXIT_COMMAND)) {
System.exit(0);
}
String[] inputs = input.split(" ");
if (inputs.length != 2) {
System.out.println("您的输入不符合要求,请重试");
continue;
}
if (userManager.login(inputs[0], inputs[1])) {
loginSuccessful = true;
}
}
currentMode = userManager.getCurrentUser().getUserType();
this.questionSetting = questionSettingFactory.getQuestionSetting(
userManager.getCurrentUser().getUserType());
setPaper();
}
public void setPaper() throws IOException {
clearScreen();
if (isFirstLogin) {
System.out.println("准备生成 " + currentMode + " 数学题目,请输入生成题目数量(输入-1将退出当前用户重新登录");
isFirstLogin = false;
} else {
System.out.println("准备生成 " + currentMode + " 数学题目,请输入生成题目数量");
}
String input = scanner.nextLine();
handleFirstInput(input);
}
public void handleFirstInput(String input) throws IOException {
boolean continueProcessing = true;
while (continueProcessing) {
try {
int questionCount = Integer.parseInt(input);
if (questionCount == -1) {
continueProcessing = false;
showHomeMenu();
} else if (10 <= questionCount && questionCount <= 30) {
List<String> questions = new ArrayList<>();
int remainingQuestions = questionCount;
while (remainingQuestions > 0) {
String question = questionSetting.setQuestion(getRandomNumber()).getExpression();
if (fileManager.checkQuestion(question, userManager.getCurrentUser())) {
questions.add(question);
System.out.println(questionCount - remainingQuestions + 1 + ". " + question);
System.out.println();
remainingQuestions--;
}
}
fileManager.savePaper(userManager.getCurrentUser(), questions);
tipMessage = "试卷生成成功,";
continueProcessing = false;
System.out.println("试卷生成完成!按回车键返回主菜单...");
scanner.nextLine();
showChoiceMenu();
} else {
System.out.println("您的输入不符合要求,请重试");
input = scanner.nextLine();
}
} catch (NumberFormatException exception) {
System.out.println("您的输入不符合要求,请重试");
input = scanner.nextLine();
}
}
}
public void showChoiceMenu() throws IOException {
boolean running = true;
while (running) {
clearScreen();
System.out.println("****************欢迎光临," + userManager.getCurrentUser().getUsername()
+ "****************");
System.out.println("————————————————————————————————————————————————");
System.out.println("当前模式是: " + currentMode);
System.out.println("-1. 退出(输入-1)");
System.out.println("1. 生成试卷(输入1)");
System.out.println("2. 切换类型(输入切换为XX)(XX为\"初中\"\"小学\"\"高中\")");
System.out.println(tipMessage + "请选择接下来操作:");
String input = scanner.nextLine();
running = handleSecondInput(input);
}
}
public boolean handleSecondInput(String input) throws IOException {
if (input.equals("-1")) {
clearScreen();
showHomeMenu();
return false;
} else if (input.equals("1")) {
clearScreen();
setPaper();
return true;
} else if (input.length() >= 4 && input.startsWith(SWITCH_PREFIX)) {
String type = input.substring(3);
tipMessage = "切换成功,";
if (type.equals("小学") || type.equals("初中") || type.equals("高中")) {
questionSetting = questionSettingFactory.getQuestionSetting(type);
currentMode = type;
setPaper();
return true;
} else {
System.out.println("请输入小学、初中和高中三个选项中的一个,请按回车重试");
scanner.nextLine();
return true;
}
} else {
System.out.println("输入不符合要求,请按回车重试");
scanner.nextLine();
return true;
}
}
public int getRandomNumber() {
int count = (int) (Math.random() * 5) + 1;
if (currentMode.equals("小学")) {
if (count == 1) {
count = (int) (Math.random() * 4) + 2;
}
}
return count;
}
}

@ -0,0 +1,36 @@
// Expression.java
public class Expression {
private String expression;
private int value;
private String mainOperator;
public Expression(String expression, int value, String mainOperator) {
this.expression = expression;
this.value = value;
this.mainOperator = mainOperator;
}
public String getExpression() {
return expression;
}
public void setExpression(String expression) {
this.expression = expression;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public String getMainOperator() {
return mainOperator;
}
public void setMainOperator(String mainOperator) {
this.mainOperator = mainOperator;
}
}

@ -0,0 +1,57 @@
// FileManager.java
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
public class FileManager {
public static final String ROOT_PATH = "./试卷/";
public void savePaper(User user, List<String> questions) throws IOException {
String userDirectory = ROOT_PATH + user.getUsername();
Path userPath = Paths.get(userDirectory);
if (!Files.exists(userPath)) {
Files.createDirectories(userPath);
}
String fileName = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss")) + ".txt";
Path filePath = userPath.resolve(fileName);
try (PrintWriter writer = new PrintWriter(new FileWriter(filePath.toFile(), StandardCharsets.UTF_8))) {
for (int index = 0; index < questions.size(); index++) {
writer.printf("%d. %s%n%n", index + 1, questions.get(index));
}
}
}
public boolean checkQuestion(String question, User user) throws IOException {
String userDirectory = ROOT_PATH + user.getUsername();
Path userPath = Paths.get(userDirectory);
if (!Files.exists(userPath)) {
return true;
} else {
File directory = new File("./试卷/" + user.getUsername());
File[] files = directory.listFiles();
if (files != null) {
for (File file : files) {
for (String line : Files.readAllLines(file.toPath())) {
if (!line.isEmpty()) {
line = line.replaceFirst("^\\d+\\.\\s*", "");
if (line.equals(question)) {
System.out.println("有道一样的题");
return false;
}
}
}
}
}
}
return true;
}
}

@ -0,0 +1,123 @@
// HighQuestionSetting.java
public class HighQuestionSetting extends AbstractQuestionSetting {
@Override
public String addParenthesesIfNeeded(Expression child, String parentOperator, boolean isRightChild) {
if (child.getMainOperator() == null
|| child.getMainOperator().equals("²") || child.getMainOperator().equals("√")
|| child.getMainOperator().equals("sin") || child.getMainOperator().equals("cos")
|| child.getMainOperator().equals("tan")) {
return child.getExpression();
}
int parentPriority = getPriority(parentOperator);
int childPriority = getPriority(child.getMainOperator());
if (childPriority < parentPriority) {
return "(" + child.getExpression() + ")";
}
if (isRightChild && (parentOperator.equals("-") || parentOperator.equals("/"))) {
if (parentPriority == childPriority) {
return "(" + child.getExpression() + ")";
}
}
return child.getExpression();
}
public Expression applyUnaryOperator(Expression child, String operator) {
switch (operator) {
case "²":
if (child.getMainOperator() == null) {
return new Expression(child.getExpression() + "²", child.getValue() * child.getValue(), "²");
}
return new Expression("(" + child.getExpression() + ")²", child.getValue() * child.getValue(), "²");
case "√":
if (child.getValue() < 0) {
String numberString = getRandomNumber();
child = new Expression(numberString, Integer.parseInt(numberString), null);
}
if (child.getMainOperator() == null) {
return new Expression("√" + child.getExpression(), (int) Math.sqrt(child.getValue()), "√");
}
return new Expression("√(" + child.getExpression() + ")", (int) Math.sqrt(child.getValue()), "√");
case "sin":
return new Expression("sin(" + child.getExpression() + ")",
(int) Math.round(Math.sin(Math.toRadians(child.getValue()))), "sin");
case "cos":
return new Expression("cos(" + child.getExpression() + ")",
(int) Math.round(Math.cos(Math.toRadians(child.getValue()))), "cos");
case "tan":
while (child.getValue() % 180 == 90) {
String numberString = getRandomNumber();
child = new Expression(numberString, Integer.parseInt(numberString), null);
}
return new Expression("tan(" + child.getExpression() + ")",
(int) Math.round(Math.tan(Math.toRadians(child.getValue()))), "tan");
default:
return child;
}
}
@Override
public Expression setQuestion(int count) {
Expression result = generateFirstQuestion(count);
while (!result.getExpression().contains("sin") && !result.getExpression().contains("cos")
&& !result.getExpression().contains("tan")) {
result = generateFirstQuestion(count);
}
return result;
}
public Expression applyProbability(Expression result) {
if (RANDOM.nextDouble() < 0.3) {
String[] unaryOperators = {"²", "√", "sin", "cos", "tan"};
String unaryOperator = unaryOperators[RANDOM.nextInt(unaryOperators.length)];
result = applyUnaryOperator(result, unaryOperator);
result.setMainOperator(unaryOperator);
}
return result;
}
public Expression generateFirstQuestion(int count) {
if (count == 1) {
String numberString = getRandomNumber();
int number = Integer.parseInt(numberString);
Expression expression = new Expression(numberString, number, null);
expression = applyProbability(expression);
return expression;
}
int leftCount = 1 + RANDOM.nextInt(count - 1);
int rightCount = count - leftCount;
Expression left = generateFirstQuestion(leftCount);
Expression right = generateFirstQuestion(rightCount);
String operator = getRandomOperator();
int value = 0;
switch (operator) {
case "+":
value = left.getValue() + right.getValue();
break;
case "-":
value = left.getValue() - right.getValue();
break;
case "*":
value = left.getValue() * right.getValue();
break;
case "/":
if (right.getValue() == 0) {
return generateFirstQuestion(rightCount);
}
value = left.getValue() / right.getValue();
break;
}
String leftExpression = addParenthesesIfNeeded(left, operator, false);
String rightExpression = addParenthesesIfNeeded(right, operator, true);
Expression result = new Expression(leftExpression + " " + operator + " " + rightExpression, (int) value, operator);
result = applyProbability(result);
return result;
}
}

@ -0,0 +1,13 @@
import java.io.IOException;
public class Main {
public static void main(String[] args) {
AllSystem allSystem = new AllSystem();
try {
allSystem.showHomeMenu();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

@ -0,0 +1,106 @@
// MiddleQuestionSetting.java
public class MiddleQuestionSetting extends AbstractQuestionSetting {
public Expression applyUnaryOperator(Expression child, String operator) {
switch (operator) {
case "²":
if (child.getMainOperator() == null) {
return new Expression(child.getExpression() + "²", child.getValue() * child.getValue(), "²");
}
return new Expression("(" + child.getExpression() + ")²", child.getValue() * child.getValue(), "²");
case "√":
if (child.getValue() < 0) {
String numberString = getRandomNumber();
child = new Expression(numberString, Integer.parseInt(numberString), null);
}
if (child.getMainOperator() == null) {
return new Expression("√" + child.getExpression(), child.getValue() * child.getValue(), "√");
}
return new Expression("√(" + child.getExpression() + ")", (int) Math.sqrt(child.getValue()), "√");
default:
return child;
}
}
@Override
public String addParenthesesIfNeeded(Expression child, String parentOperator, boolean isRightChild) {
if (child.getMainOperator() == null || child.getMainOperator().equals("²") || child.getMainOperator().equals("√")) {
return child.getExpression();
}
int parentPriority = getPriority(parentOperator);
int childPriority = getPriority(child.getMainOperator());
if (childPriority < parentPriority) {
return "(" + child.getExpression() + ")";
}
if (isRightChild && (parentOperator.equals("-") || parentOperator.equals("/"))) {
if (parentPriority == childPriority) {
return "(" + child.getExpression() + ")";
}
}
return child.getExpression();
}
@Override
public Expression setQuestion(int count) {
Expression result = generateFirstQuestion(count);
while (!result.getExpression().contains("²") && !result.getExpression().contains("√")) {
result = generateFirstQuestion(count);
}
return result;
}
public Expression generateFirstQuestion(int count) {
if (count == 1) {
String numberString = getRandomNumber();
int number = Integer.parseInt(numberString);
Expression expression = new Expression(numberString, number, null);
expression = applyProbability(expression);
return expression;
}
int leftCount = 1 + RANDOM.nextInt(count - 1);
int rightCount = count - leftCount;
Expression left = generateFirstQuestion(leftCount);
Expression right = generateFirstQuestion(rightCount);
String operator = getRandomOperator();
int value = 0;
switch (operator) {
case "+":
value = left.getValue() + right.getValue();
break;
case "-":
value = left.getValue() - right.getValue();
break;
case "*":
value = left.getValue() * right.getValue();
break;
case "/":
while (right.getValue() == 0) {
right = generateFirstQuestion(rightCount);
}
value = left.getValue() / right.getValue();
break;
}
String leftExpression = addParenthesesIfNeeded(left, operator, false);
String rightExpression = addParenthesesIfNeeded(right, operator, true);
Expression result = new Expression(leftExpression + " " + operator + " " + rightExpression, value, operator);
result = applyProbability(result);
return result;
}
public Expression applyProbability(Expression result) {
if (RANDOM.nextDouble() < 0.3) {
String[] unaryOperators = {"²", "√"};
String unaryOperator = unaryOperators[RANDOM.nextInt(unaryOperators.length)];
result = applyUnaryOperator(result, unaryOperator);
result.setMainOperator(unaryOperator);
}
return result;
}
}

@ -0,0 +1,37 @@
// PrimaryQuestionSetting.java
public class PrimaryQuestionSetting extends AbstractQuestionSetting {
@Override
public Expression setQuestion(int count) {
if (count == 1) {
String expression = getRandomNumber();
return new Expression(expression, Integer.parseInt(expression), null);
}
int leftCount = 1 + RANDOM.nextInt(count - 1);
int rightCount = count - leftCount;
Expression left = setQuestion(leftCount);
Expression right = setQuestion(rightCount);
String operator = getRandomOperator();
while (operator.equals("/") && right.getValue() == 0) {
right = setQuestion(rightCount);
}
if (operator.equals("-") && left.getValue() < right.getValue()) {
Expression temporary = left;
left = right;
right = temporary;
}
String leftExpression = addParenthesesIfNeeded(left, operator, false);
String rightExpression = addParenthesesIfNeeded(right, operator, true);
int value = switch (operator) {
case "+" -> left.getValue() + right.getValue();
case "-" -> left.getValue() - right.getValue();
case "*" -> left.getValue() * right.getValue();
case "/" -> left.getValue() / right.getValue();
default -> 0;
};
return new Expression(leftExpression + " " + operator + " " + rightExpression, value, operator);
}
}

@ -0,0 +1,4 @@
public interface QuestionSetting {
Expression setQuestion(int count);
}

@ -0,0 +1,20 @@
public class QuestionSettingFactory {
public QuestionSetting getQuestionSetting(String type) {
switch (type) {
case "小学" -> {
return new PrimaryQuestionSetting();
}
case "初中" -> {
return new MiddleQuestionSetting();
}
case "高中" -> {
return new HighQuestionSetting();
}
default -> {
System.out.println("类型错误");
return null;
}
}
}
}

@ -0,0 +1,41 @@
public class User {
private String username; // 用户名
private String password; // 密码
private String userType; //(小学/初中/高中)
public User(String username, String password, String userType) {
this.username = username;
this.password = password;
this.userType = userType;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getUserType() {
return userType;
}
public void setUserType(String userType) {
this.userType = userType;
}
public boolean validateCredentials(String inputUsername, String inputPassword) {
// 验证用户名和密码是否匹配
return this.username.equals(inputUsername) && this.password.equals(inputPassword);
}
}

@ -0,0 +1,63 @@
// UserManager.java
import java.util.HashMap;
import java.util.Map;
public class UserManager {
private Map<String, User> users = new HashMap<>();
private User currentUser;
private String currentType;
public UserManager() {
initializeUsers();
}
public boolean login(String username, String password) {
if (users.containsKey(username)) {
User user = users.get(username);
if (user.validateCredentials(username, password)) {
currentUser = user;
currentType = user.getUserType();
System.out.println("登录成功");
return true;
} else {
System.out.println("密码错误,请重试");
}
} else {
System.out.println("该账号不存在,请重试");
}
return false;
}
public void logout() {
currentUser = null;
currentType = null;
}
public boolean switchUserType(String newType) {
if (currentUser != null) {
currentType = newType;
return true;
}
return false;
}
public boolean isLoggedIn() {
return currentUser != null;
}
public User getCurrentUser() {
return currentUser;
}
private void initializeUsers() {
users.put("张三1", new User("张三1", "123", "小学"));
users.put("张三2", new User("张三2", "123", "小学"));
users.put("张三3", new User("张三3", "123", "小学"));
users.put("李四1", new User("李四1", "123", "初中"));
users.put("李四2", new User("李四2", "123", "初中"));
users.put("李四3", new User("李四3", "123", "初中"));
users.put("王五1", new User("王五1", "123", "高中"));
users.put("王五2", new User("王五2", "123", "高中"));
users.put("王五3", new User("王五3", "123", "高中"));
}
}
Loading…
Cancel
Save