diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 5fe5705..0000000 --- a/.gitignore +++ /dev/null @@ -1,28 +0,0 @@ -# 编译生成的可执行文件 -*.exe -*.out -*.o -*.obj - -# 临时文件 -*.tmp -*.temp -*~ - -# 系统文件 -.DS_Store -Thumbs.db - -# IDE文件 -.vscode/ -.idea/ -*.swp -*.swo - -# 生成的题目文件夹(用户数据) -xiaoxue*/ -chuzhong*/ -gaozhong*/ - -# 日志文件 -*.log \ No newline at end of file diff --git a/doc/Git使用说明.md b/doc/Git使用说明.md deleted file mode 100644 index 0852a92..0000000 --- a/doc/Git使用说明.md +++ /dev/null @@ -1,172 +0,0 @@ -# Git版本控制使用说明 - -## 项目Git结构 - -根据头歌平台要求,本项目采用以下分支结构: -- **master分支**:稳定版本,用于发布 -- **develop分支**:开发版本,用于日常开发 - -## 快速设置(推荐) - -### 方法一:使用自动化脚本 -1. 双击项目根目录下的 `git_setup.bat` 文件 -2. 按照提示输入Git用户名和邮箱 -3. 脚本会自动完成仓库初始化和分支创建 - -### 方法二:手动设置 -如果您熟悉Git命令,可以按照以下步骤手动设置: - -## 详细设置步骤 - -### 1. 检查Git安装 -```bash -git --version -``` -如果提示"git不是内部或外部命令",请先安装Git: -- 访问:https://git-scm.com/download/win -- 下载并安装Git for Windows - -### 2. 初始化仓库 -```bash -# 进入项目目录 -cd "e:\学习通\download\软件项目管理\软件2301班_沈永佳_个人项目" - -# 初始化Git仓库 -git init - -# 配置用户信息 -git config user.name "您的姓名" -git config user.email "您的邮箱" -``` - -### 3. 创建分支结构 -```bash -# 创建并切换到develop分支 -git checkout -b develop - -# 添加所有文件 -git add . - -# 提交初始版本 -git commit -m "初始提交:中小学数学卷子自动生成程序" - -# 创建master分支 -git checkout -b master - -# 合并develop分支到master -git merge develop --no-ff -m "合并develop分支到master(稳定版本)" - -# 切换回develop分支进行日常开发 -git checkout develop -``` - -## 连接头歌平台 - -### 1. 在头歌平台创建项目 -1. 登录头歌平台 -2. 创建新项目:`软件2301班_沈永佳_个人项目` -3. 复制项目的Git仓库地址 - -### 2. 添加远程仓库 -```bash -# 添加远程仓库(替换为实际的仓库地址) -git remote add origin https://git.educoder.net/your-username/软件2301班_沈永佳_个人项目.git - -# 推送master分支 -git push -u origin master - -# 推送develop分支 -git push -u origin develop -``` - -## 日常开发流程 - -### 开发新功能 -```bash -# 确保在develop分支 -git checkout develop - -# 拉取最新代码 -git pull origin develop - -# 修改代码后提交 -git add . -git commit -m "描述您的修改内容" - -# 推送到远程develop分支 -git push origin develop -``` - -### 发布稳定版本 -```bash -# 切换到master分支 -git checkout master - -# 合并develop分支的稳定功能 -git merge develop --no-ff -m "发布版本v1.x" - -# 推送到远程master分支 -git push origin master - -# 切换回develop分支继续开发 -git checkout develop -``` - -## 常用Git命令 - -| 命令 | 说明 | -|------|------| -| `git status` | 查看文件状态 | -| `git log --oneline` | 查看提交历史 | -| `git branch` | 查看分支列表 | -| `git checkout [分支名]` | 切换分支 | -| `git add .` | 添加所有修改的文件 | -| `git commit -m "消息"` | 提交修改 | -| `git push origin [分支名]` | 推送到远程分支 | -| `git pull origin [分支名]` | 拉取远程分支 | - -## 文件结构说明 - -``` -项目根目录/ -├── .git/ # Git版本控制目录 -├── .gitignore # Git忽略文件配置 -├── src/ # 源代码目录 -│ ├── main.cpp # 主程序文件 -│ ├── Makefile # 编译配置 -│ ├── run.bat # 运行脚本 -│ └── run_utf8.bat # UTF-8编码运行脚本 -├── doc/ # 文档目录 -│ ├── README.md # 项目说明 -│ └── Git使用说明.md # 本文档 -└── git_setup.bat # Git自动设置脚本 -``` - -## 注意事项 - -1. **编码问题**:确保使用UTF-8编码,避免中文乱码 -2. **分支管理**: - - develop分支用于日常开发 - - master分支只用于稳定版本发布 -3. **提交信息**:使用清晰的中文描述提交内容 -4. **文件忽略**:`.gitignore`已配置忽略编译文件和用户数据 - -## 故障排除 - -### 问题1:Git命令不识别 -**解决方案**:安装Git for Windows并重启命令行 - -### 问题2:中文文件名乱码 -**解决方案**: -```bash -git config --global core.quotepath false -``` - -### 问题3:推送失败 -**解决方案**:检查网络连接和仓库地址是否正确 - -## 联系方式 - -如有问题,请联系: -- 姓名:沈永佳 -- 班级:软件2301班 \ No newline at end of file diff --git a/doc/README.md b/doc/README.md index 553b8eb..3e00ed9 100644 --- a/doc/README.md +++ b/doc/README.md @@ -12,9 +12,9 @@ - 根据账户类型自动识别出题难度 ### 2. 智能题目生成 -- **小学题目**:2-3个操作数,数值范围1-50,支持加减法 -- **初中题目**:3-4个操作数,数值范围1-100,支持加减乘法 -- **高中题目**:4-5个操作数,数值范围1-100,支持加减乘除法 +- **小学题目**:2-3个操作数,数值范围1-50,支持加减乘除和括号运算 +- **初中题目**:3-4个操作数,数值范围1-100,支持加减乘除和括号运算,并能计算平方和开根号 +- **高中题目**:4-5个操作数,数值范围1-100,支持加减乘除和括号运算,并能计算平方和开根号,而且题目中至少有一个sin,cos或tan运算符 ### 3. 题目管理 - 自动避免同一教师生成重复题目 @@ -29,15 +29,15 @@ | 学段 | 用户名 | 密码 | |------|--------|------| -| 小学 | xiaoxue1 | 123456 | -| 小学 | xiaoxue2 | 123456 | -| 小学 | xiaoxue3 | 123456 | -| 初中 | chuzhong1 | 123456 | -| 初中 | chuzhong2 | 123456 | -| 初中 | chuzhong3 | 123456 | -| 高中 | gaozhong1 | 123456 | -| 高中 | gaozhong2 | 123456 | -| 高中 | gaozhong3 | 123456 | +| 小学 | 张三1 | 123 | +| 小学 | 张三2 | 123 | +| 小学 | 张三3 | 123 | +| 初中 | 李四1 | 123 | +| 初中 | 李四2 | 123 | +| 初中 | 李四3 | 123 | +| 高中 | 王五1 | 123 | +| 高中 | 王五2 | 123 | +| 高中 | 王五3 | 123 | ## 编译和运行 @@ -60,24 +60,9 @@ g++ -std=c++17 -Wall -Wextra -O2 -o math_exam_generator main.cpp ``` ### 运行程序 - -#### 方法一:使用UTF-8编码脚本(推荐,解决乱码问题) -```bash -cd src -run_utf8.bat -``` - -#### 方法二:手动设置编码后运行 ```bash cd src -chcp 65001 -math_exam_generator.exe -``` - -#### 方法三:直接运行(可能出现中文乱码) -```bash -cd src -math_exam_generator.exe +./math_exam_generator ``` ## 使用说明 @@ -85,7 +70,7 @@ math_exam_generator.exe ### 1. 登录 启动程序后,输入用户名和密码,用空格隔开: ``` -请输入用户名和密码(用空格隔开): xiaoxue1 123456 +请输入用户名和密码(用空格隔开): 张三1 123 ``` ### 2. 生成试卷 @@ -110,13 +95,28 @@ math_exam_generator.exe ``` 项目根目录/ -├── src/ # 源代码目录 -│ ├── main.cpp # 主程序文件 -│ └── Makefile # 编译配置文件 -├── doc/ # 文档目录 -│ └── README.md # 项目说明文档 -└── [用户名]/ # 自动生成的用户文件夹 - └── 年-月-日-时-分-秒.txt # 生成的试卷文件 +├── doc/ # 文档目录 +│ ├── README.md # 本项目的详细说明 +│ └── README_root.md # 根目录的README +└── src/ # 源代码目录 + ├── Makefile # 编译配置文件 + ├── app.cpp # 应用逻辑 + ├── auth.cpp # 用户认证模块 + ├── exam.cpp # 试卷生成模块 + ├── include/ # 头文件目录 + ├── login.cpp # 登录逻辑 + ├── main.cpp # 主程序入口 + ├── math_exam_generator.exe # 编译后的可执行文件 + └── utils.cpp # 工具函数 + +``` +*注:程序运行时,会在`src`目录下自动生成`paper`文件夹,用于存放用户生成的试卷。* + +``` +src/ +└── paper/ + └── [用户名]/ # 自动生成的用户文件夹 + └── 年-月-日-时-分-秒.txt # 生成的试卷文件 ``` ## 试卷格式示例 @@ -127,6 +127,18 @@ math_exam_generator.exe 2. 42 - 15 + 23 = ? 3. 8 + 17 - 9 + 14 = ? + +4. 36 * 4 - 20 = ? + +5. 16^2 - 4 = ? + +6. 2^3 - 1 = ? + +7. sin(30°) = ? + +8. cos(60°) = ? + +9. tan(45°) +2= ? ``` ## 技术特点 @@ -146,7 +158,7 @@ math_exam_generator.exe ## 版本控制 本项目使用Git进行版本控制: -- `master` 分支:稳定版本 +- `main` 分支:稳定版本 - `develop` 分支:开发版本 ## 作者 diff --git a/README.md b/doc/README_root.md similarity index 100% rename from README.md rename to doc/README_root.md diff --git a/git_setup.bat b/git_setup.bat deleted file mode 100644 index 787f3c2..0000000 --- a/git_setup.bat +++ /dev/null @@ -1,63 +0,0 @@ -@echo off -chcp 65001 >nul -echo ======================================== -echo 中小学数学卷子生成程序 Git 设置 -echo ======================================== -echo. - -echo 正在检查Git是否已安装... -git --version >nul 2>&1 -if %errorlevel% neq 0 ( - echo [错误] 未检测到Git,请先安装Git: - echo 1. 访问 https://git-scm.com/download/win - echo 2. 下载并安装Git for Windows - echo 3. 重新运行此脚本 - pause - exit /b 1 -) - -echo [成功] Git已安装 -echo. - -echo 正在初始化Git仓库... -git init -if %errorlevel% neq 0 ( - echo [错误] Git初始化失败 - pause - exit /b 1 -) - -echo 正在配置Git用户信息... -set /p username="请输入您的Git用户名: " -set /p email="请输入您的Git邮箱: " - -git config user.name "%username%" -git config user.email "%email%" - -echo. -echo 正在创建分支结构... -git checkout -b develop -git add . -git commit -m "初始提交:中小学数学卷子自动生成程序" - -echo 正在创建master分支... -git checkout -b master -git merge develop --no-ff -m "合并develop分支到master(稳定版本)" - -echo. -echo ======================================== -echo Git仓库设置完成! -echo. -echo 分支结构: -echo - master: 稳定版本 -echo - develop: 开发版本(当前分支) -echo. -echo 下一步: -echo 1. 在头歌平台创建项目仓库 -echo 2. 复制仓库地址 -echo 3. 运行以下命令添加远程仓库: -echo git remote add origin [仓库地址] -echo git push -u origin master -echo git push -u origin develop -echo ======================================== -pause \ No newline at end of file diff --git a/src/Makefile b/src/Makefile index 68ba534..e961a40 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,22 +1,28 @@ -# Makefile for Math Exam Generator -CXX = g++ -CXXFLAGS = -std=c++17 -Wall -Wextra -O2 -TARGET = math_exam_generator -SOURCE = main.cpp +# Simple Makefile for Math Exam Generator -# Default target -all: $(TARGET) +CXX ?= g++ +CXXFLAGS ?= -std=c++17 -Wall -Wextra -O2 -# Build target -$(TARGET): $(SOURCE) - $(CXX) $(CXXFLAGS) -o $(TARGET) $(SOURCE) +# Cross-platform remove command +ifeq ($(OS),Windows_NT) + RM := cmd /C del /Q +else + RM := rm -f +endif -# Clean target -clean: - rm -f $(TARGET) $(TARGET).exe +TARGET := math_exam_generator +SRCS := main.cpp app.cpp auth.cpp exam.cpp login.cpp utils.cpp +OBJS := $(SRCS:.cpp=.o) + +all: $(TARGET) + +$(TARGET): $(OBJS) + $(CXX) $(CXXFLAGS) -o $@ $^ + -$(RM) $(OBJS) -# Run target -run: $(TARGET) - ./$(TARGET) +%.o: %.cpp + $(CXX) $(CXXFLAGS) -c $< -o $@ -.PHONY: all clean run \ No newline at end of file +.PHONY: clean +clean: + -$(RM) $(OBJS) $(TARGET) $(TARGET).exe \ No newline at end of file diff --git a/src/app.cpp b/src/app.cpp new file mode 100644 index 0000000..258b77d --- /dev/null +++ b/src/app.cpp @@ -0,0 +1,92 @@ +#include "include/app.hpp" +#include "include/utils.hpp" + +#include +#include +#include + +namespace fs = std::filesystem; + +namespace meg { + +App::App() {} + +void App::handle_logged_in() { + if (!current_user_) return; + current_level_ = current_user_->level; + + while (true) { + std::wcout << L"准备生成" << level_to_chinese(current_level_) << L"数学题目,请输入生成题目数量(输入-1将退出当前用户,重新登录):"; + std::wcout.flush(); + std::wstring line; + if (!std::getline(std::wcin, line)) return; // EOF + line = trim(line); + if (line.empty()) continue; + + if (line == L"-1") { + std::wcout << L"已退出登录。" << std::endl; + current_user_.reset(); + return; + } + + // 支持命令:切换为小学/初中/高中 + if (starts_with(line, L"切换为")) { + std::wstring t = line.substr(3); + t = trim(t); + if (t == L"小学") current_level_ = Level::Primary; + else if (t == L"初中") current_level_ = Level::Middle; + else if (t == L"高中") current_level_ = Level::High; + else { + std::wcout << L"不支持的类型:" << t << std::endl; + continue; + } + std::wcout << L"已切换为" << level_to_chinese(current_level_) << L"。" << std::endl; + continue; + } + + // 尝试解析数量 + try { + int n = std::stoi(std::string(line.begin(), line.end())); + if (n < 10 || n > 30) { + std::wcout << L"请输入 10-30 之间的数字。" << std::endl; + continue; + } + + fs::path base = fs::path("paper") / current_user_->username; + fs::create_directories(base); + auto history_path = base / ".history.txt"; + auto history = exam_.load_history(history_path); + auto questions = exam_.generate_unique(current_level_, n, history); + // 保存试卷 + std::string ts = now_timestamp_str(); + fs::path outfile = base / (ts + ".txt"); + std::ofstream fout(outfile); + for (size_t i = 0; i < questions.size(); ++i) { + fout << (i + 1) << ". " << questions[i] << "\n\n"; + } + fout.close(); + exam_.append_history(history_path, questions); + + std::wcout << L"已生成试卷:" << outfile.wstring() << std::endl; + } catch (...) { + std::wcout << L"请输入正确的数字,或使用指令如:切换为初中。" << std::endl; + } + } +} + +void App::run() { + init_console_locale(); + std::wcout << L"中小学数学卷子自动生成程序" << std::endl; + while (true) { + if (!current_user_) { + auto u = login_.prompt_login(); + if (!u) return; // EOF 退出 + current_user_ = u; + current_level_ = current_user_->level; + } else { + handle_logged_in(); + } + } +} + +} // namespace meg \ No newline at end of file diff --git a/src/auth.cpp b/src/auth.cpp new file mode 100644 index 0000000..02774e9 --- /dev/null +++ b/src/auth.cpp @@ -0,0 +1,29 @@ +#include "include/auth.hpp" + +namespace meg { + +Auth::Auth() { + // 小学 张三1..3 + users_[L"张三1"] = {L"123", Level::Primary}; + users_[L"张三2"] = {L"123", Level::Primary}; + users_[L"张三3"] = {L"123", Level::Primary}; + + // 初中 李四1..3 + users_[L"李四1"] = {L"123", Level::Middle}; + users_[L"李四2"] = {L"123", Level::Middle}; + users_[L"李四3"] = {L"123", Level::Middle}; + + // 高中 王五1..3 + users_[L"王五1"] = {L"123", Level::High}; + users_[L"王五2"] = {L"123", Level::High}; + users_[L"王五3"] = {L"123", Level::High}; +} + +std::optional Auth::authenticate(const std::wstring& username, const std::wstring& password) const { + auto it = users_.find(username); + if (it == users_.end()) return std::nullopt; + if (it->second.first != password) return std::nullopt; + return User{username, password, it->second.second}; +} + +} // namespace meg \ No newline at end of file diff --git a/src/exam.cpp b/src/exam.cpp new file mode 100644 index 0000000..d22a57f --- /dev/null +++ b/src/exam.cpp @@ -0,0 +1,152 @@ +#include "include/exam.hpp" + +#include +#include +#include + +namespace meg { + +static int rand_int(std::mt19937& rng, int lo, int hi) { + std::uniform_int_distribution dist(lo, hi); + return dist(rng); +} + +ExamGenerator::ExamGenerator() { + std::random_device rd; + rng_ = std::mt19937(rd()); +} + +std::unordered_set ExamGenerator::load_history(const std::filesystem::path& history_path) { + std::unordered_set s; + std::ifstream fin(history_path); + std::string line; + while (std::getline(fin, line)) { + if (!line.empty()) s.insert(line); + } + return s; +} + +void ExamGenerator::append_history(const std::filesystem::path& history_path, const std::vector& qs) { + std::ofstream fout(history_path, std::ios::app); + for (auto& q : qs) { + fout << q << '\n'; + } +} + +std::vector ExamGenerator::generate_unique(Level level, int count, std::unordered_set& history) { + std::vector out; + int attempts = 0; + while ((int)out.size() < count && attempts < count * 50) { + std::string q; + if (level == Level::Primary) q = gen_primary(); + else if (level == Level::Middle) q = gen_middle(); + else q = gen_high(); + + if (history.insert(q).second) { + out.push_back(q); + } + ++attempts; + } + return out; +} + +static std::string join_ops(const std::vector& nums, const std::vector& ops) { + std::string s; + for (size_t i = 0; i < nums.size(); ++i) { + s += std::to_string(nums[i]); + if (i < ops.size()) { + s += ' '; + s += ops[i]; + s += ' '; + } + } + return s; +} + +std::string ExamGenerator::gen_primary() { + int n_ops = rand_int(rng_, 1, 2); // 2-3个操作数 => 1-2个操作符 + std::vector nums; + for (int i = 0; i < n_ops + 1; ++i) nums.push_back(rand_int(rng_, 1, 50)); + static const char all_ops[] = {'+', '-', '*', '/'}; + std::vector ops; + for (int i = 0; i < n_ops; ++i) ops.push_back(all_ops[rand_int(rng_, 0, 3)]); + std::string expr = join_ops(nums, ops); + // 适度加括号 + if (nums.size() >= 3 && rand_int(rng_, 0, 1)) { + size_t pos = expr.find(' '); + if (pos != std::string::npos) { + expr = "(" + expr.substr(0, pos) + ")" + expr.substr(pos); + } + } + return expr + " = ?"; +} + +std::string ExamGenerator::gen_middle() { + int n_ops = rand_int(rng_, 2, 3); // 3-4 操作数 + std::vector nums; + for (int i = 0; i < n_ops + 1; ++i) nums.push_back(rand_int(rng_, 1, 100)); + static const char all_ops[] = {'+', '-', '*', '/'}; + std::vector ops; + for (int i = 0; i < n_ops; ++i) ops.push_back(all_ops[rand_int(rng_, 0, 3)]); + + // 随机把一个数变成平方或开方 + int idx = rand_int(rng_, 0, (int)nums.size() - 1); + bool use_sqrt = rand_int(rng_, 0, 1) == 1; + std::string expr = join_ops(nums, ops); + + // 找到替换位置(简单方式:直接重新拼接) + std::string s; + for (size_t i = 0; i < nums.size(); ++i) { + std::string term = std::to_string(nums[i]); + if ((int)i == idx) { + if (use_sqrt) term = "sqrt(" + term + ")"; // 开根号 + else term = term + "^2"; // 平方 + } + s += term; + if (i < ops.size()) { + s += ' '; s += ops[i]; s += ' '; + } + } + return s + " = ?"; +} + +std::string ExamGenerator::gen_high() { + int n_ops = rand_int(rng_, 3, 4); // 4-5 操作数 + std::vector nums; + for (int i = 0; i < n_ops + 1; ++i) nums.push_back(rand_int(rng_, 1, 100)); + static const char all_ops[] = {'+', '-', '*', '/'}; + std::vector ops; + for (int i = 0; i < n_ops; ++i) ops.push_back(all_ops[rand_int(rng_, 0, 3)]); + + // 先基础表达式 + std::string base; + for (size_t i = 0; i < nums.size(); ++i) { + base += std::to_string(nums[i]); + if (i < ops.size()) { base += ' '; base += ops[i]; base += ' '; } + } + + // 至少一个三角函数,角度用 30/45/60/90 之一,使用 "deg" 标识角度 + static const char* funcs[] = {"sin", "cos", "tan"}; + static const int angles[] = {30, 45, 60, 90}; + std::string tri = std::string(funcs[rand_int(rng_, 0, 2)]) + "(" + std::to_string(angles[rand_int(rng_, 0, 3)]) + "deg)"; + + // 随机把一个数替换为三角项 + size_t replace_pos = rand_int(rng_, 0, (int)nums.size() - 1); + // 重建字符串并替换 + std::string s; + for (size_t i = 0; i < nums.size(); ++i) { + std::string term = (i == replace_pos) ? tri : std::to_string(nums[i]); + s += term; + if (i < ops.size()) { s += ' '; s += ops[i]; s += ' '; } + } + + // 同时可能再加平方或开方 + if (rand_int(rng_, 0, 1)) { + s = "(" + s + ")^2"; + } else if (rand_int(rng_, 0, 1)) { + s = "sqrt(" + s + ")"; + } + return s + " = ?"; +} + +} // namespace meg \ No newline at end of file diff --git a/src/include/app.hpp b/src/include/app.hpp new file mode 100644 index 0000000..4aca3dd --- /dev/null +++ b/src/include/app.hpp @@ -0,0 +1,30 @@ +#ifndef MEG_APP_HPP +#define MEG_APP_HPP + +#include +#include +#include "auth.hpp" +#include "login.hpp" +#include "exam.hpp" + +namespace meg { + +class App { +public: + App(); + void run(); + +private: + Auth auth_; + LoginManager login_{auth_}; + ExamGenerator exam_; + + std::optional current_user_; + Level current_level_ = Level::Primary; + + void handle_logged_in(); +}; + +} // namespace meg + +#endif // MEG_APP_HPP \ No newline at end of file diff --git a/src/include/auth.hpp b/src/include/auth.hpp new file mode 100644 index 0000000..3a2f4a9 --- /dev/null +++ b/src/include/auth.hpp @@ -0,0 +1,27 @@ +#ifndef MEG_AUTH_HPP +#define MEG_AUTH_HPP + +#include +#include +#include +#include "utils.hpp" + +namespace meg { + +struct User { + std::wstring username; + std::wstring password; + Level level; +}; + +class Auth { +public: + Auth(); + std::optional authenticate(const std::wstring& username, const std::wstring& password) const; +private: + std::map> users_; +}; + +} // namespace meg + +#endif // MEG_AUTH_HPP \ No newline at end of file diff --git a/src/include/exam.hpp b/src/include/exam.hpp new file mode 100644 index 0000000..1e1cb60 --- /dev/null +++ b/src/include/exam.hpp @@ -0,0 +1,33 @@ +#ifndef MEG_EXAM_HPP +#define MEG_EXAM_HPP + +#include +#include +#include +#include +#include +#include "utils.hpp" + +namespace meg { + +class ExamGenerator { +public: + ExamGenerator(); + // 加载/保存历史,防止同教师重复题目 + std::unordered_set load_history(const std::filesystem::path& history_path); + void append_history(const std::filesystem::path& history_path, const std::vector& qs); + + // 生成不重复题目 + std::vector generate_unique(Level level, int count, std::unordered_set& history); + +private: + std::mt19937 rng_; + + std::string gen_primary(); + std::string gen_middle(); + std::string gen_high(); +}; + +} // namespace meg + +#endif // MEG_EXAM_HPP \ No newline at end of file diff --git a/src/include/login.hpp b/src/include/login.hpp new file mode 100644 index 0000000..e6551e6 --- /dev/null +++ b/src/include/login.hpp @@ -0,0 +1,20 @@ +#ifndef MEG_LOGIN_HPP +#define MEG_LOGIN_HPP + +#include +#include +#include "auth.hpp" + +namespace meg { + +class LoginManager { +public: + explicit LoginManager(const Auth& auth) : auth_(auth) {} + std::optional prompt_login(); +private: + const Auth& auth_; +}; + +} // namespace meg + +#endif // MEG_LOGIN_HPP \ No newline at end of file diff --git a/src/include/utils.hpp b/src/include/utils.hpp new file mode 100644 index 0000000..48269e0 --- /dev/null +++ b/src/include/utils.hpp @@ -0,0 +1,25 @@ +#ifndef MEG_UTILS_HPP +#define MEG_UTILS_HPP + +#include +#include + +namespace meg { + +enum class Level { Primary, Middle, High }; + +std::wstring level_to_chinese(Level level); + +// 初始化控制台本地化,解决中文输入/输出问题 +void init_console_locale(); + +// 生成时间戳:YYYY-MM-DD-HH-MM-SS +std::string now_timestamp_str(); + +// 字符串工具 +bool starts_with(const std::wstring& s, const std::wstring& prefix); +std::wstring trim(const std::wstring& s); + +} // namespace meg + +#endif // MEG_UTILS_HPP \ No newline at end of file diff --git a/src/login.cpp b/src/login.cpp new file mode 100644 index 0000000..729632e --- /dev/null +++ b/src/login.cpp @@ -0,0 +1,38 @@ +#include "include/login.hpp" +#include "include/utils.hpp" + +#include +#include + +namespace meg { + +std::optional LoginManager::prompt_login() { + while (true) { + std::wcout << L"请输入用户名和密码(用空格隔开): "; + std::wcout.flush(); + std::wstring line; + if (!std::getline(std::wcin, line)) { + return std::nullopt; + } + line = trim(line); + if (line.empty()) continue; + + // 拆分为两段:用户名 密码 + std::wistringstream iss(line); + std::wstring u, p; + iss >> u >> p; + if (u.empty() || p.empty()) { + std::wcout << L"输入格式不正确,请重新输入。" << std::endl; + continue; + } + auto user = auth_.authenticate(u, p); + if (user) { + std::wcout << L"登录成功,欢迎您," << u << L"!" << std::endl; + return user; + } else { + std::wcout << L"用户名或密码错误,请重试。" << std::endl; + } + } +} + +} // namespace meg \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 9443107..2bd9c39 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,410 +1,7 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef _WIN32 -#include -#include -#include -#endif - -using namespace std; -namespace fs = std::filesystem; - -// 用户账号结构 -struct User { - string username; - string password; - string type; // "小学", "初中", "高中" -}; - -// 数学题目结构 -struct MathQuestion { - string question; - int answer; - - bool operator<(const MathQuestion& other) const { - return question < other.question; - } -}; - -class MathExamGenerator { -private: - vector users; - User currentUser; - bool isLoggedIn; - string currentType; - random_device rd; - mt19937 gen; - - // 存储已生成的题目,避免重复 - map> generatedQuestions; - -public: - MathExamGenerator() : gen(rd()), isLoggedIn(false) { - initializeUsers(); - loadExistingQuestions(); - } - - // 初始化预设账号 - void initializeUsers() { - // 小学账号 - users.push_back({"xiaoxue1", "123456", "小学"}); - users.push_back({"xiaoxue2", "123456", "小学"}); - users.push_back({"xiaoxue3", "123456", "小学"}); - - // 初中账号 - users.push_back({"chuzhong1", "123456", "初中"}); - users.push_back({"chuzhong2", "123456", "初中"}); - users.push_back({"chuzhong3", "123456", "初中"}); - - // 高中账号 - users.push_back({"gaozhong1", "123456", "高中"}); - users.push_back({"gaozhong2", "123456", "高中"}); - users.push_back({"gaozhong3", "123456", "高中"}); - } - - // 加载已存在的题目 - void loadExistingQuestions() { - for (const auto& user : users) { - string folderPath = user.username; - if (fs::exists(folderPath)) { - for (const auto& entry : fs::directory_iterator(folderPath)) { - if (entry.is_regular_file() && entry.path().extension() == ".txt") { - loadQuestionsFromFile(entry.path().string(), user.username); - } - } - } - } - } - - // 从文件加载题目 - void loadQuestionsFromFile(const string& filename, const string& username) { - ifstream file(filename); - string line; - while (getline(file, line)) { - if (!line.empty() && line.find('.') != string::npos) { - // 提取题目部分(去掉题号) - size_t dotPos = line.find('.'); - if (dotPos != string::npos && dotPos + 1 < line.length()) { - string question = line.substr(dotPos + 1); - // 去掉前后空格 - question.erase(0, question.find_first_not_of(" \t")); - question.erase(question.find_last_not_of(" \t") + 1); - - MathQuestion mq; - mq.question = question; - mq.answer = 0; // 这里简化处理 - generatedQuestions[username].insert(mq); - } - } - } - file.close(); - } - - // 用户登录 - bool login(const string& username, const string& password) { - for (const auto& user : users) { - if (user.username == username && user.password == password) { - currentUser = user; - currentType = user.type; - isLoggedIn = true; - cout << "当前选择为" << currentType << "出题" << endl; - return true; - } - } - return false; - } - - // 生成小学数学题 - MathQuestion generatePrimaryQuestion() { - uniform_int_distribution<> opCount(2, 3); // 操作数2-3个 - uniform_int_distribution<> numDist(1, 50); // 数值范围1-50 - uniform_int_distribution<> opDist(0, 1); // 0:加法, 1:减法 - - int operandCount = opCount(gen); - vector numbers; - vector operators; - - for (int i = 0; i < operandCount; i++) { - numbers.push_back(numDist(gen)); - } - - for (int i = 0; i < operandCount - 1; i++) { - operators.push_back(opDist(gen) == 0 ? '+' : '-'); - } - - // 构建题目字符串 - stringstream ss; - ss << numbers[0]; - int result = numbers[0]; - - for (int i = 0; i < operators.size(); i++) { - ss << " " << operators[i] << " " << numbers[i + 1]; - if (operators[i] == '+') { - result += numbers[i + 1]; - } else { - result -= numbers[i + 1]; - } - } - - // 确保结果为正数 - if (result < 0) { - return generatePrimaryQuestion(); - } - - ss << " = ?"; - - MathQuestion question; - question.question = ss.str(); - question.answer = result; - - return question; - } - - // 生成初中数学题 - MathQuestion generateMiddleQuestion() { - uniform_int_distribution<> opCount(3, 4); // 操作数3-4个 - uniform_int_distribution<> numDist(1, 100); // 数值范围1-100 - uniform_int_distribution<> opDist(0, 2); // 0:加法, 1:减法, 2:乘法 - - int operandCount = opCount(gen); - vector numbers; - vector operators; - - for (int i = 0; i < operandCount; i++) { - numbers.push_back(numDist(gen)); - } - - for (int i = 0; i < operandCount - 1; i++) { - int op = opDist(gen); - if (op == 0) operators.push_back('+'); - else if (op == 1) operators.push_back('-'); - else operators.push_back('*'); - } - - // 构建题目字符串 - stringstream ss; - ss << numbers[0]; - int result = numbers[0]; - - for (int i = 0; i < operators.size(); i++) { - ss << " " << operators[i] << " " << numbers[i + 1]; - if (operators[i] == '+') { - result += numbers[i + 1]; - } else if (operators[i] == '-') { - result -= numbers[i + 1]; - } else { - result *= numbers[i + 1]; - } - } - - ss << " = ?"; - - MathQuestion question; - question.question = ss.str(); - question.answer = result; - - return question; - } - - // 生成高中数学题 - MathQuestion generateHighQuestion() { - uniform_int_distribution<> opCount(4, 5); // 操作数4-5个 - uniform_int_distribution<> numDist(1, 100); // 数值范围1-100 - uniform_int_distribution<> opDist(0, 3); // 0:加法, 1:减法, 2:乘法, 3:除法 - - int operandCount = opCount(gen); - vector numbers; - vector operators; - - for (int i = 0; i < operandCount; i++) { - numbers.push_back(numDist(gen)); - } - - for (int i = 0; i < operandCount - 1; i++) { - int op = opDist(gen); - if (op == 0) operators.push_back('+'); - else if (op == 1) operators.push_back('-'); - else if (op == 2) operators.push_back('*'); - else { - operators.push_back('/'); - // 确保除法结果为整数 - numbers[i + 1] = numbers[i] % numbers[i + 1] == 0 ? numbers[i + 1] : - (numbers[i] / numbers[i + 1] + 1); - } - } - - // 构建题目字符串 - stringstream ss; - ss << numbers[0]; - double result = numbers[0]; - - for (int i = 0; i < operators.size(); i++) { - ss << " " << operators[i] << " " << numbers[i + 1]; - if (operators[i] == '+') { - result += numbers[i + 1]; - } else if (operators[i] == '-') { - result -= numbers[i + 1]; - } else if (operators[i] == '*') { - result *= numbers[i + 1]; - } else { - if (numbers[i + 1] != 0) { - result /= numbers[i + 1]; - } - } - } - - ss << " = ?"; - - MathQuestion question; - question.question = ss.str(); - question.answer = (int)result; - - return question; - } - - // 根据类型生成题目 - MathQuestion generateQuestion(const string& type) { - if (type == "小学") { - return generatePrimaryQuestion(); - } else if (type == "初中") { - return generateMiddleQuestion(); - } else { - return generateHighQuestion(); - } - } - - // 检查题目是否重复 - bool isQuestionDuplicate(const MathQuestion& question) { - return generatedQuestions[currentUser.username].count(question) > 0; - } - - // 生成试卷 - void generateExam(int questionCount) { - vector questions; - - // 生成不重复的题目 - while (questions.size() < questionCount) { - MathQuestion question = generateQuestion(currentType); - if (!isQuestionDuplicate(question)) { - questions.push_back(question); - generatedQuestions[currentUser.username].insert(question); - } - } - - // 创建用户文件夹 - string folderPath = currentUser.username; - if (!fs::exists(folderPath)) { - fs::create_directory(folderPath); - } - - // 生成文件名(年-月-日-时-分-秒.txt) - auto now = chrono::system_clock::now(); - auto time_t = chrono::system_clock::to_time_t(now); - auto tm = *localtime(&time_t); - - stringstream filename; - filename << folderPath << "/" - << put_time(&tm, "%Y-%m-%d-%H-%M-%S") << ".txt"; - - // 写入文件 - ofstream file(filename.str()); - for (int i = 0; i < questions.size(); i++) { - file << (i + 1) << ". " << questions[i].question << endl; - if (i < questions.size() - 1) { - file << endl; // 题目之间空一行 - } - } - file.close(); - - cout << "试卷已生成并保存到: " << filename.str() << endl; - } - - // 切换类型 - bool switchType(const string& newType) { - if (newType == "小学" || newType == "初中" || newType == "高中") { - currentType = newType; - return true; - } - return false; - } - - // 主运行循环 - void run() { - cout << "=== 中小学数学卷子自动生成程序 ===" << endl; - - while (true) { - if (!isLoggedIn) { - cout << "请输入用户名和密码(用空格隔开): "; - string username, password; - cin >> username >> password; - - if (login(username, password)) { - continue; - } else { - cout << "请输入正确的用户名、密码" << endl; - continue; - } - } - - cout << "准备生成" << currentType << "数学题目,请输入生成题目数量(输入-1将退出当前用户,重新登录):"; - string input; - cin >> input; - - // 检查是否是切换命令 - if (input.find("切换为") == 0) { - string newType = input.substr(3); // 去掉"切换为" - if (switchType(newType)) { - cout << "准备生成" << currentType << "数学题目,请输入生成题目数量:"; - continue; - } else { - cout << "请输入小学、初中和高中三个选项中的一个" << endl; - continue; - } - } - - try { - int count = stoi(input); - - if (count == -1) { - isLoggedIn = false; - cout << "已退出登录" << endl; - continue; - } - - if (count >= 10 && count <= 30) { - generateExam(count); - } else { - cout << "题目数量必须在10-30之间" << endl; - } - } catch (const exception& e) { - cout << "请输入有效的数字" << endl; - } - } - } -}; - -int main() { -#ifdef _WIN32 - // 设置控制台编码为UTF-8,解决中文乱码问题 - SetConsoleOutputCP(CP_UTF8); - SetConsoleCP(CP_UTF8); - - // 设置locale为中文 - setlocale(LC_ALL, "zh_CN.UTF-8"); -#endif - - MathExamGenerator generator; - generator.run(); - return 0; +#include "include/app.hpp" + +int main() { + meg::App app; + app.run(); + return 0; } \ No newline at end of file diff --git a/src/math_exam_generator.exe b/src/math_exam_generator.exe new file mode 100644 index 0000000..e86f2d6 Binary files /dev/null and b/src/math_exam_generator.exe differ diff --git a/src/paper/张三1/.history.txt b/src/paper/张三1/.history.txt new file mode 100644 index 0000000..f83fd24 --- /dev/null +++ b/src/paper/张三1/.history.txt @@ -0,0 +1,30 @@ +40 + 31 * 38 = ? +46 * 6 = ? +(8) - 32 / 31 = ? +36 + 20 + 17 = ? +(7) + 14 / 39 = ? +(19) - 24 * 20 = ? +(13) - 27 * 43 = ? +43 / 17 - 39 = ? +17 - 43 = ? +9 / 30 = ? +48 / 31 = ? +17 - 24 = ? +(3) - 23 * 30 = ? +26 / 38 = ? +(48) / 46 * 33 = ? +11 * 7 = ? +(47) / 17 * 31 = ? +36 * 10 / 14 = ? +47 * 23 / 32 = ? +(29) - 14 * 33 = ? +33 * 46 + 38 = ? +(4) * 11 * 9 = ? +(39) / 36 - 36 = ? +25 - 4 = ? +12 - 8 = ? +39 - 32 = ? +13 / 25 = ? +40 - 43 = ? +(48) + 44 + 28 = ? +(12) / 9 + 44 = ? diff --git a/src/paper/张三1/2025-09-27-16-21-44.txt b/src/paper/张三1/2025-09-27-16-21-44.txt new file mode 100644 index 0000000..48699e3 --- /dev/null +++ b/src/paper/张三1/2025-09-27-16-21-44.txt @@ -0,0 +1,60 @@ +1. 40 + 31 * 38 = ? + +2. 46 * 6 = ? + +3. (8) - 32 / 31 = ? + +4. 36 + 20 + 17 = ? + +5. (7) + 14 / 39 = ? + +6. (19) - 24 * 20 = ? + +7. (13) - 27 * 43 = ? + +8. 43 / 17 - 39 = ? + +9. 17 - 43 = ? + +10. 9 / 30 = ? + +11. 48 / 31 = ? + +12. 17 - 24 = ? + +13. (3) - 23 * 30 = ? + +14. 26 / 38 = ? + +15. (48) / 46 * 33 = ? + +16. 11 * 7 = ? + +17. (47) / 17 * 31 = ? + +18. 36 * 10 / 14 = ? + +19. 47 * 23 / 32 = ? + +20. (29) - 14 * 33 = ? + +21. 33 * 46 + 38 = ? + +22. (4) * 11 * 9 = ? + +23. (39) / 36 - 36 = ? + +24. 25 - 4 = ? + +25. 12 - 8 = ? + +26. 39 - 32 = ? + +27. 13 / 25 = ? + +28. 40 - 43 = ? + +29. (48) + 44 + 28 = ? + +30. (12) / 9 + 44 = ? + diff --git a/src/run.bat b/src/run.bat deleted file mode 100644 index df72c47..0000000 --- a/src/run.bat +++ /dev/null @@ -1,11 +0,0 @@ -@echo off -echo 正在编译程序... -g++ -std=c++17 -Wall -Wextra -O2 -o math_exam_generator.exe main.cpp -if %errorlevel% equ 0 ( - echo 编译成功!正在启动程序... - echo. - math_exam_generator.exe -) else ( - echo 编译失败! - pause -) \ No newline at end of file diff --git a/src/run_utf8.bat b/src/run_utf8.bat deleted file mode 100644 index 5699628..0000000 --- a/src/run_utf8.bat +++ /dev/null @@ -1,12 +0,0 @@ -@echo off -chcp 65001 >nul -echo 正在编译程序... -g++ -std=c++17 -Wall -Wextra -O2 -o math_exam_generator.exe main.cpp -if %errorlevel% equ 0 ( - echo 编译成功!正在启动程序... - echo. - math_exam_generator.exe -) else ( - echo 编译失败! - pause -) \ No newline at end of file diff --git a/src/utils.cpp b/src/utils.cpp new file mode 100644 index 0000000..fd10f3b --- /dev/null +++ b/src/utils.cpp @@ -0,0 +1,74 @@ +#include "include/utils.hpp" + +#include +#include +#include +#include +#include + +#ifdef _WIN32 +# include +# include +#endif + +namespace meg { + +std::wstring level_to_chinese(Level level) { + switch (level) { + case Level::Primary: return L"小学"; + case Level::Middle: return L"初中"; + case Level::High: return L"高中"; + } + return L"小学"; +} + +void init_console_locale() { +#ifdef _WIN32 + _setmode(_fileno(stdout), _O_U16TEXT); + _setmode(_fileno(stdin), _O_U16TEXT); + _setmode(_fileno(stderr), _O_U16TEXT); +#else + try { + std::locale loc(""); + std::locale::global(loc); + std::wcout.imbue(loc); + std::wcin.imbue(loc); + std::wcerr.imbue(loc); + } catch (...) { + // 忽略本地化设置失败 + } +#endif +} + +std::string now_timestamp_str() { + using namespace std::chrono; + auto now = system_clock::now(); + std::time_t t = system_clock::to_time_t(now); + std::tm tm{}; +#ifdef _WIN32 + localtime_s(&tm, &t); +#else + localtime_r(&t, &tm); +#endif + std::ostringstream oss; + oss << std::put_time(&tm, "%Y-%m-%d-%H-%M-%S"); + return oss.str(); +} + +bool starts_with(const std::wstring& s, const std::wstring& prefix) { + if (prefix.size() > s.size()) return false; + return std::equal(prefix.begin(), prefix.end(), s.begin()); +} + +static inline bool is_space(wchar_t c) { + return c == L' ' || c == L'\t' || c == L'\n' || c == L'\r' || c == L'\f' || c == L'\v'; +} + +std::wstring trim(const std::wstring& s) { + size_t b = 0, e = s.size(); + while (b < e && is_space(s[b])) ++b; + while (e > b && is_space(s[e-1])) --e; + return s.substr(b, e - b); +} + +} // namespace meg \ No newline at end of file