diff --git a/src/main/java/com/example/mathsystemtogether/ChangePasswordController.java b/src/main/java/com/example/mathsystemtogether/ChangePasswordController.java new file mode 100644 index 0000000..03ad1f1 --- /dev/null +++ b/src/main/java/com/example/mathsystemtogether/ChangePasswordController.java @@ -0,0 +1,164 @@ +package com.example.mathsystemtogether; + +import javafx.fxml.FXML; +import javafx.scene.control.*; +import javafx.stage.Stage; +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.List; +import java.util.regex.Pattern; + +public class ChangePasswordController { + + @FXML + private TextField usernameField; + + @FXML + private PasswordField oldPasswordField; + + @FXML + private PasswordField newPasswordField; + + @FXML + private PasswordField confirmNewPasswordField; + + @FXML + private Button changePasswordButton; + + @FXML + private Button cancelButton; + + @FXML + private Label statusLabel; + + private String currentUser; + private ExamController examController; + private static final String USER_DATA_FILE = "user_data.txt"; + + // 密码强度验证正则表达式 + private static final String PASSWORD_REGEX = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).+$"; + private static final Pattern PASSWORD_PATTERN = Pattern.compile(PASSWORD_REGEX); + + public void setCurrentUser(String username) { + this.currentUser = username; + usernameField.setText(username); + } + + public void setExamController(ExamController examController) { + this.examController = examController; + } + + @FXML + private void handleChangePassword() { + String oldPassword = oldPasswordField.getText(); + String newPassword = newPasswordField.getText(); + String confirmNewPassword = confirmNewPasswordField.getText(); + + if (oldPassword.isEmpty() || newPassword.isEmpty() || confirmNewPassword.isEmpty()) { + showStatus("请填写所有字段", true); + return; + } + + if (!examController.checkUserPassword(currentUser, oldPassword)) { + showStatus("原密码错误", true); + return; + } + + if (newPassword.length() < 6) { + showStatus("新密码至少需要6个字符", true); + return; + } + + if (!isValidPassword(newPassword)) { + showStatus("新密码必须包含大小写字母和数字", true); + return; + } + + if (!newPassword.equals(confirmNewPassword)) { + showStatus("两次输入的新密码不一致", true); + return; + } + + if (oldPassword.equals(newPassword)) { + showStatus("新密码不能与原密码相同", true); + return; + } + + if (updatePassword(currentUser, newPassword)) { + showStatus("密码修改成功!", false); + + // 延迟关闭窗口 + new java.util.Timer().schedule( + new java.util.TimerTask() { + @Override + public void run() { + javafx.application.Platform.runLater(() -> { + handleClose(); + }); + } + }, + 1500 + ); + } else { + showStatus("密码修改失败,请重试", true); + } + } + + @FXML + private void handleCancel() { + handleClose(); + } + + private void handleClose() { + Stage stage = (Stage) cancelButton.getScene().getWindow(); + stage.close(); + } + + private boolean updatePassword(String username, String newPassword) { + try { + // 读取所有用户数据 + List lines = Files.readAllLines(Paths.get(USER_DATA_FILE)); + boolean userFound = false; + + for (int i = 0; i < lines.size(); i++) { + String[] parts = lines.get(i).split("\\|"); + if (parts.length >= 2 && parts[0].equals(username)) { + // 更新密码 + parts[1] = newPassword; + lines.set(i, String.join("|", parts)); + userFound = true; + break; + } + } + + if (userFound) { + // 写回文件 + Files.write(Paths.get(USER_DATA_FILE), lines); + // 更新内存中的用户数据 + examController.updateUserPassword(username, newPassword); + return true; + } + } catch (IOException e) { + e.printStackTrace(); + } + return false; + } + + /** + * 验证密码强度 + * 密码必须包含至少一个小写字母、一个大写字母和一个数字 + */ + private boolean isValidPassword(String password) { + return PASSWORD_PATTERN.matcher(password).matches(); + } + + private void showStatus(String message, boolean isError) { + statusLabel.setText(message); + if (isError) { + statusLabel.setStyle("-fx-text-fill: #DC143C; -fx-font-weight: bold; -fx-font-size: 14;"); + } else { + statusLabel.setStyle("-fx-text-fill: #228B22; -fx-font-weight: bold; -fx-font-size: 14;"); + } + } +} diff --git a/src/main/java/com/example/mathsystemtogether/ChoiceQuestionGenerator.java b/src/main/java/com/example/mathsystemtogether/ChoiceQuestionGenerator.java index 6fce20b..299282a 100644 --- a/src/main/java/com/example/mathsystemtogether/ChoiceQuestionGenerator.java +++ b/src/main/java/com/example/mathsystemtogether/ChoiceQuestionGenerator.java @@ -9,31 +9,31 @@ public class ChoiceQuestionGenerator { private final Random random = new Random(); private final int MAX_OPERAND_VALUE = 50; // 降低数值范围,便于计算 private final int MIN_OPERAND_VALUE = 1; - + /** * 生成指定数量的选择题 */ public List generateQuestions(Level level, int count) { List questions = new ArrayList<>(); Set usedQuestions = new HashSet<>(); - + int attempts = 0; int maxAttempts = count * 100; // 最大尝试次数 - + while (questions.size() < count && attempts < maxAttempts) { Question question = generateSingleQuestion(level, questions.size() + 1); String questionKey = question.getQuestionText(); - + if (!usedQuestions.contains(questionKey)) { usedQuestions.add(questionKey); questions.add(question); } attempts++; } - + return questions; } - + /** * 生成单个选择题 */ @@ -49,98 +49,320 @@ public class ChoiceQuestionGenerator { return generatePrimaryQuestion(questionNumber); } } - + /** * 生成小学题目 */ private Question generatePrimaryQuestion(int questionNumber) { - int a = MIN_OPERAND_VALUE + random.nextInt(MAX_OPERAND_VALUE); - int b = MIN_OPERAND_VALUE + random.nextInt(MAX_OPERAND_VALUE); - - String operation = getRandomOperation("+*-"); - String questionText = String.format("%d %s %d = ?", a, operation, b); - int correctAnswer = calculate(a, b, operation); + // 随机选择运算符号数量(1-3个) + int operatorCount = 1 + random.nextInt(3); + // 生成复合运算表达式 + String[] expression = generateCompoundExpression(operatorCount); + String questionText = expression[0]; + int correctAnswer = Integer.parseInt(expression[1]); + // 生成错误选项 List options = generateWrongOptions(correctAnswer); options.add(correctAnswer); Collections.shuffle(options); - - return new Question(questionText, - String.valueOf(options.get(0)), - String.valueOf(options.get(1)), - String.valueOf(options.get(2)), - String.valueOf(options.get(3)), - String.valueOf(correctAnswer), + + return new Question(questionText, + String.valueOf(options.get(0)), + String.valueOf(options.get(1)), + String.valueOf(options.get(2)), + String.valueOf(options.get(3)), + String.valueOf(correctAnswer), questionNumber); } - + /** * 生成初中题目 */ private Question generateJuniorQuestion(int questionNumber) { - int a = MIN_OPERAND_VALUE + random.nextInt(MAX_OPERAND_VALUE); + // 随机选择运算符号数量(1-3个) + int operatorCount = 1 + random.nextInt(3); - // 50%概率生成平方或开方题目 - if (random.nextBoolean()) { - // 平方题目 - String questionText = String.format("%d² = ?", a); - int correctAnswer = a * a; + // 30%概率生成纯平方/开方题目,70%概率生成包含平方/开方的复合运算题目 + if (random.nextDouble() < 0.3) { + // 生成纯平方或开方题目 + if (random.nextBoolean()) { + // 平方题目 + int a = MIN_OPERAND_VALUE + random.nextInt(MAX_OPERAND_VALUE); + String questionText = String.format("%d² = ?", a); + int correctAnswer = a * a; + List options = generateWrongOptions(correctAnswer); + options.add(correctAnswer); + Collections.shuffle(options); + + return new Question(questionText, + String.valueOf(options.get(0)), + String.valueOf(options.get(1)), + String.valueOf(options.get(2)), + String.valueOf(options.get(3)), + String.valueOf(correctAnswer), + questionNumber); + } else { + // 开方题目 + int perfectSquare = (int) Math.pow(random.nextInt(10) + 1, 2); + String questionText = String.format("√%d = ?", perfectSquare); + int correctAnswer = (int) Math.sqrt(perfectSquare); + List options = generateWrongOptions(correctAnswer); + options.add(correctAnswer); + Collections.shuffle(options); + + return new Question(questionText, + String.valueOf(options.get(0)), + String.valueOf(options.get(1)), + String.valueOf(options.get(2)), + String.valueOf(options.get(3)), + String.valueOf(correctAnswer), + questionNumber); + } + } else { + // 生成包含平方/开方的复合运算表达式(确保包含平方或开方) + String[] expression = generateJuniorCompoundExpressionWithGuarantee(operatorCount); + String questionText = expression[0]; + int correctAnswer = Integer.parseInt(expression[1]); + + // 生成错误选项 List options = generateWrongOptions(correctAnswer); options.add(correctAnswer); Collections.shuffle(options); - - return new Question(questionText, - String.valueOf(options.get(0)), - String.valueOf(options.get(1)), - String.valueOf(options.get(2)), - String.valueOf(options.get(3)), - String.valueOf(correctAnswer), + + return new Question(questionText, + String.valueOf(options.get(0)), + String.valueOf(options.get(1)), + String.valueOf(options.get(2)), + String.valueOf(options.get(3)), + String.valueOf(correctAnswer), questionNumber); + } + } + + /** + * 生成高中题目 + */ + private Question generateSeniorQuestion(int questionNumber) { + // 随机选择运算符号数量(1-3个) + int operatorCount = 1 + random.nextInt(3); + + // 30%概率生成纯三角函数题目,70%概率生成包含三角函数的复合运算题目 + if (random.nextDouble() < 0.3) { + // 生成纯三角函数题目 + int[] commonAngles = {0, 30, 45, 60, 90, 120, 135, 150, 180, 210, 225, 270, 300, 315, 330}; + int angle = commonAngles[random.nextInt(commonAngles.length)]; + String[] functions = {"sin", "cos", "tan"}; + String function = functions[random.nextInt(functions.length)]; + + String questionText = String.format("%s(%d°) = ?", function, angle); + double result = calculateTrigFunction(function, angle); + + // 生成选项 + List options = generateTrigOptions(result); + // 使用格式化后的值作为正确答案,确保与选项一致 + String correctAnswer = formatTrigValue(result); + + // 随机打乱选项 + Collections.shuffle(options); + + return new Question(questionText, + options.get(0), + options.get(1), + options.get(2), + options.get(3), + correctAnswer, + questionNumber); } else { - // 开方题目 - int perfectSquare = (int) Math.pow(random.nextInt(10) + 1, 2); - String questionText = String.format("√%d = ?", perfectSquare); - int correctAnswer = (int) Math.sqrt(perfectSquare); + // 生成包含三角函数的复合运算表达式(确保包含三角函数) + String[] expression = generateSeniorCompoundExpressionWithGuarantee(operatorCount); + String questionText = expression[0]; + int correctAnswer = Integer.parseInt(expression[1]); + + // 生成错误选项 List options = generateWrongOptions(correctAnswer); options.add(correctAnswer); Collections.shuffle(options); - - return new Question(questionText, - String.valueOf(options.get(0)), - String.valueOf(options.get(1)), - String.valueOf(options.get(2)), - String.valueOf(options.get(3)), - String.valueOf(correctAnswer), + + return new Question(questionText, + String.valueOf(options.get(0)), + String.valueOf(options.get(1)), + String.valueOf(options.get(2)), + String.valueOf(options.get(3)), + String.valueOf(correctAnswer), questionNumber); } +} + +/** + * 生成三角函数题目的选项 + */ +private List generateTrigOptions(double correctAnswer) { + List options = new ArrayList<>(); + Set usedOptions = new HashSet<>(); + + // 格式化正确答案,保留适当的小数位数 + String formattedCorrectAnswer = formatTrigValue(correctAnswer); + options.add(formattedCorrectAnswer); + usedOptions.add(formattedCorrectAnswer); + + // 添加常见三角函数值作为错误选项 + String[] commonValues = {"0", "0.5", "0.71", "0.87", "1", "1.73", "-0.5", "-0.71", "-0.87", "-1", "-1.73", "undefined"}; + List commonValuesList = new ArrayList<>(Arrays.asList(commonValues)); + + // 打乱常见值列表 + Collections.shuffle(commonValuesList); + + // 添加错误选项 + int wrongOptionsNeeded = 3; + for (String value : commonValuesList) { + if (wrongOptionsNeeded <= 0) break; + // 确保不添加与正确答案相同的选项 + if (!usedOptions.contains(value) && !value.equals(formattedCorrectAnswer)) { + options.add(value); + usedOptions.add(value); + wrongOptionsNeeded--; + } } - - /** - * 生成高中题目 - */ - private Question generateSeniorQuestion(int questionNumber) { - int angle = random.nextInt(360); - String[] functions = {"sin", "cos", "tan"}; - String function = functions[random.nextInt(functions.length)]; - - String questionText = String.format("%s(%d°) = ?", function, angle); - double result = calculateTrigFunction(function, angle); - int correctAnswer = (int) Math.round(result * 100) / 100; // 保留两位小数 - - List options = generateWrongOptions(correctAnswer); - options.add(correctAnswer); - Collections.shuffle(options); - - return new Question(questionText, - String.valueOf(options.get(0)), - String.valueOf(options.get(1)), - String.valueOf(options.get(2)), - String.valueOf(options.get(3)), - String.valueOf(correctAnswer), - questionNumber); + + // 如果还需要更多选项,则生成接近正确答案的选项 + while (options.size() < 4) { + String wrongOption = generateNearbyValue(correctAnswer, usedOptions); + // 确保新生成的选项不重复且不与正确答案相同 + if (!usedOptions.contains(wrongOption) && !wrongOption.equals(formattedCorrectAnswer)) { + options.add(wrongOption); + usedOptions.add(wrongOption); + } } - + + return options; +} + +/** + * 格式化三角函数值 + */ +private String formatTrigValue(double value) { + // 对于特殊值进行精确表示 + if (Math.abs(value) < 1e-10) return "0"; // 接近0的值 + if (Math.abs(value - 1.0) < 1e-10) return "1"; + if (Math.abs(value + 1.0) < 1e-10) return "-1"; + if (Math.abs(value - 0.5) < 1e-10) return "0.5"; + if (Math.abs(value + 0.5) < 1e-10) return "-0.5"; + if (Math.abs(value - Math.sqrt(2)/2) < 1e-10) return "0.71"; // √2/2 ≈ 0.707 + if (Math.abs(value + Math.sqrt(2)/2) < 1e-10) return "-0.71"; + if (Math.abs(value - Math.sqrt(3)/2) < 1e-10) return "0.87"; // √3/2 ≈ 0.866 + if (Math.abs(value + Math.sqrt(3)/2) < 1e-10) return "-0.87"; + if (Math.abs(value - Math.sqrt(3)) < 1e-10) return "1.73"; // √3 ≈ 1.732 + if (Math.abs(value + Math.sqrt(3)) < 1e-10) return "-1.73"; + + // 对于tan(90°)等未定义值 + if (Double.isInfinite(value) || Double.isNaN(value)) { + return "undefined"; + } + + // 其他值保留两位小数 + return String.format("%.2f", value); +} + +/** + * 生成接近正确答案的选项 + */ +private String generateNearbyValue(double correctAnswer, Set usedOptions) { + // 对于特殊值直接返回简单值 + if (Double.isInfinite(correctAnswer) || Double.isNaN(correctAnswer)) { + return "0"; + } + + // 生成在正确答案附近的值 + double variation = (random.nextDouble() - 0.5) * 2; // -1 到 1 之间的随机数 + double wrongAnswer = correctAnswer + variation; + + // 格式化生成的值 + String formatted = formatTrigValue(wrongAnswer); + + // 确保不会生成与已使用选项相同的值,最多尝试10次 + int attempts = 0; + while (usedOptions.contains(formatted) && attempts < 10) { + variation = (random.nextDouble() - 0.5) * 2; + wrongAnswer = correctAnswer + variation; + formatted = formatTrigValue(wrongAnswer); + attempts++; + } + + return formatted; +} + +/** + * 计算三角函数 + */ +private double calculateTrigFunction(String function, int angle) { + double radians = Math.toRadians(angle); + switch (function) { + case "sin": + // 对特定角度返回精确值 + switch (angle) { + case 0: return 0; + case 30: return 0.5; + case 45: return Math.sqrt(2)/2; + case 60: return Math.sqrt(3)/2; + case 90: return 1; + case 120: return Math.sqrt(3)/2; + case 135: return Math.sqrt(2)/2; + case 150: return 0.5; + case 180: return 0; + case 210: return -0.5; + case 225: return -Math.sqrt(2)/2; + case 270: return -1; + case 300: return -Math.sqrt(3)/2; + case 315: return -Math.sqrt(2)/2; + case 330: return -0.5; + default: return Math.sin(radians); + } + case "cos": + // 对特定角度返回精确值 + switch (angle) { + case 0: return 1; + case 30: return Math.sqrt(3)/2; + case 45: return Math.sqrt(2)/2; + case 60: return 0.5; + case 90: return 0; + case 120: return -0.5; + case 135: return -Math.sqrt(2)/2; + case 150: return -Math.sqrt(3)/2; + case 180: return -1; + case 210: return -Math.sqrt(3)/2; + case 225: return -Math.sqrt(2)/2; + case 270: return 0; + case 300: return 0.5; + case 315: return Math.sqrt(2)/2; + case 330: return Math.sqrt(3)/2; + default: return Math.cos(radians); + } + case "tan": + // 对特定角度返回精确值或未定义 + switch (angle) { + case 0: return 0; + case 30: return Math.sqrt(3)/3; + case 45: return 1; + case 60: return Math.sqrt(3); + case 90: return Double.POSITIVE_INFINITY; // 未定义 + case 120: return -Math.sqrt(3); + case 135: return -1; + case 150: return -Math.sqrt(3)/3; + case 180: return 0; + case 210: return Math.sqrt(3)/3; + case 225: return 1; + case 270: return Double.POSITIVE_INFINITY; // 未定义 + case 300: return -Math.sqrt(3); + case 315: return -1; + case 330: return -Math.sqrt(3)/3; + default: return Math.tan(radians); + } + default: return 0; + } +} + + /** * 生成错误选项 */ @@ -148,7 +370,7 @@ public class ChoiceQuestionGenerator { List options = new ArrayList<>(); Set usedOptions = new HashSet<>(); usedOptions.add(correctAnswer); - + while (options.size() < 3) { int wrongAnswer; if (correctAnswer == 0) { @@ -158,16 +380,16 @@ public class ChoiceQuestionGenerator { int variation = random.nextInt(Math.max(1, Math.abs(correctAnswer) / 2)) + 1; wrongAnswer = correctAnswer + (random.nextBoolean() ? variation : -variation); } - + if (!usedOptions.contains(wrongAnswer)) { options.add(wrongAnswer); usedOptions.add(wrongAnswer); } } - + return options; } - + /** * 计算基本运算 */ @@ -180,20 +402,573 @@ public class ChoiceQuestionGenerator { default: return 0; } } + + + /** + * 生成复合运算表达式 + * @param operatorCount 运算符数量(1-3个) + * @return 返回[表达式字符串, 计算结果字符串] + */ + private String[] generateCompoundExpression(int operatorCount) { + StringBuilder expression = new StringBuilder(); + List operands = new ArrayList<>(); + List operators = new ArrayList<>(); + + // 生成操作数(运算符数量+1个操作数) + for (int i = 0; i <= operatorCount; i++) { + operands.add(MIN_OPERAND_VALUE + random.nextInt(MAX_OPERAND_VALUE)); + } + + // 生成运算符 + String[] availableOps = {"+", "-", "*"}; + for (int i = 0; i < operatorCount; i++) { + operators.add(availableOps[random.nextInt(availableOps.length)]); + } + + // 构建表达式字符串 + expression.append(operands.get(0)); + for (int i = 0; i < operatorCount; i++) { + expression.append(" ").append(operators.get(i)).append(" ").append(operands.get(i + 1)); + } + expression.append(" = ?"); + + // 计算表达式结果 + int result = calculateCompoundExpression(operands, operators); + + return new String[]{expression.toString(), String.valueOf(result)}; + } /** - * 计算三角函数 + * 生成初中复合运算表达式(确保包含平方/开方) */ - private double calculateTrigFunction(String function, int angle) { - double radians = Math.toRadians(angle); - switch (function) { - case "sin": return Math.sin(radians); - case "cos": return Math.cos(radians); - case "tan": return Math.tan(radians); - default: return 0; + private String[] generateJuniorCompoundExpressionWithGuarantee(int operatorCount) { + StringBuilder expression = new StringBuilder(); + List operands = new ArrayList<>(); + List operators = new ArrayList<>(); + + // 生成操作数(运算符数量+1个操作数) + for (int i = 0; i <= operatorCount; i++) { + operands.add(MIN_OPERAND_VALUE + random.nextInt(MAX_OPERAND_VALUE)); } + + // 确保至少有一个平方或开方运算 + boolean hasSquareOrRoot = false; + + // 生成运算符 + for (int i = 0; i < operatorCount; i++) { + String[] availableOps; + if (!hasSquareOrRoot && i == operatorCount - 1) { + // 最后一个运算符必须是平方或开方 + availableOps = new String[]{"²", "√"}; + hasSquareOrRoot = true; + } else if (!hasSquareOrRoot && random.nextDouble() < 0.4) { + // 40%概率选择平方或开方 + availableOps = new String[]{"+", "-", "*", "/", "²", "√"}; + } else { + // 其他情况选择基础运算 + availableOps = new String[]{"+", "-", "*", "/"}; + } + + String selectedOp = availableOps[random.nextInt(availableOps.length)]; + if (selectedOp.equals("²") || selectedOp.equals("√")) { + hasSquareOrRoot = true; + } + operators.add(selectedOp); + } + + // 构建表达式字符串 + expression.append(operands.get(0)); + for (int i = 0; i < operatorCount; i++) { + if (operators.get(i).equals("²")) { + expression.append("²"); + } else if (operators.get(i).equals("√")) { + // 确保开方的是完全平方数 + int perfectSquare = (int) Math.pow(random.nextInt(10) + 1, 2); + expression.append("√").append(perfectSquare); + operands.set(i + 1, (int) Math.sqrt(perfectSquare)); + } else { + expression.append(" ").append(operators.get(i)).append(" ").append(operands.get(i + 1)); + } + } + expression.append(" = ?"); + + // 计算表达式结果 + int result = calculateJuniorCompoundExpression(operands, operators); + + return new String[]{expression.toString(), String.valueOf(result)}; + } + + /** + * 生成初中复合运算表达式(包含平方/开方) + */ + private String[] generateJuniorCompoundExpression(int operatorCount) { + StringBuilder expression = new StringBuilder(); + List operands = new ArrayList<>(); + List operators = new ArrayList<>(); + + // 生成操作数(运算符数量+1个操作数) + for (int i = 0; i <= operatorCount; i++) { + operands.add(MIN_OPERAND_VALUE + random.nextInt(MAX_OPERAND_VALUE)); + } + + // 生成运算符(包含平方/开方) + String[] availableOps = {"+", "-", "*", "/", "²", "√"}; + for (int i = 0; i < operatorCount; i++) { + operators.add(availableOps[random.nextInt(availableOps.length)]); + } + + // 构建表达式字符串 + expression.append(operands.get(0)); + for (int i = 0; i < operatorCount; i++) { + if (operators.get(i).equals("²")) { + expression.append("²"); + } else if (operators.get(i).equals("√")) { + // 确保开方的是完全平方数 + int perfectSquare = (int) Math.pow(random.nextInt(10) + 1, 2); + expression.append("√").append(perfectSquare); + operands.set(i + 1, (int) Math.sqrt(perfectSquare)); + } else { + expression.append(" ").append(operators.get(i)).append(" ").append(operands.get(i + 1)); + } + } + expression.append(" = ?"); + + // 计算表达式结果 + int result = calculateJuniorCompoundExpression(operands, operators); + + return new String[]{expression.toString(), String.valueOf(result)}; + } + + /** + * 生成包含除法的复合运算表达式 + */ + private String[] generateCompoundExpressionWithDivision(int operatorCount) { + StringBuilder expression = new StringBuilder(); + List operands = new ArrayList<>(); + List operators = new ArrayList<>(); + + // 生成操作数(运算符数量+1个操作数) + for (int i = 0; i <= operatorCount; i++) { + operands.add(MIN_OPERAND_VALUE + random.nextInt(MAX_OPERAND_VALUE)); + } + + // 生成运算符(包含除法) + String[] availableOps = {"+", "-", "*", "/"}; + for (int i = 0; i < operatorCount; i++) { + operators.add(availableOps[random.nextInt(availableOps.length)]); + } + + // 构建表达式字符串 + expression.append(operands.get(0)); + for (int i = 0; i < operatorCount; i++) { + expression.append(" ").append(operators.get(i)).append(" ").append(operands.get(i + 1)); + } + expression.append(" = ?"); + + // 计算表达式结果 + int result = calculateCompoundExpressionWithDivision(operands, operators); + + return new String[]{expression.toString(), String.valueOf(result)}; + } + + /** + * 计算复合表达式 + */ + private int calculateCompoundExpression(List operands, List operators) { + // 先处理乘除法,再处理加减法 + List numbers = new ArrayList<>(operands); + List ops = new ArrayList<>(operators); + + // 第一轮:处理乘除法 + for (int i = 0; i < ops.size(); i++) { + if (ops.get(i).equals("*")) { + int result = numbers.get(i) * numbers.get(i + 1); + numbers.set(i, result); + numbers.remove(i + 1); + ops.remove(i); + i--; // 调整索引 + } + } + + // 第二轮:处理加减法 + int result = numbers.get(0); + for (int i = 0; i < ops.size(); i++) { + if (ops.get(i).equals("+")) { + result += numbers.get(i + 1); + } else if (ops.get(i).equals("-")) { + result -= numbers.get(i + 1); + } + } + + return result; + } + + /** + * 生成高中复合运算表达式(确保包含三角函数) + */ + private String[] generateSeniorCompoundExpressionWithGuarantee(int operatorCount) { + StringBuilder expression = new StringBuilder(); + List operands = new ArrayList<>(); + List operators = new ArrayList<>(); + + // 生成操作数(运算符数量+1个操作数) + for (int i = 0; i <= operatorCount; i++) { + operands.add((double)(MIN_OPERAND_VALUE + random.nextInt(MAX_OPERAND_VALUE))); + } + + // 确保至少有一个三角函数运算 + boolean hasTrigFunction = false; + + // 生成运算符 + for (int i = 0; i < operatorCount; i++) { + String[] availableOps; + if (!hasTrigFunction && i == operatorCount - 1) { + // 最后一个运算符必须是三角函数 + availableOps = new String[]{"sin", "cos", "tan"}; + hasTrigFunction = true; + } else if (!hasTrigFunction && random.nextDouble() < 0.4) { + // 40%概率选择三角函数 + availableOps = new String[]{"+", "-", "*", "/", "sin", "cos", "tan"}; + } else { + // 其他情况选择基础运算 + availableOps = new String[]{"+", "-", "*", "/"}; + } + + String selectedOp = availableOps[random.nextInt(availableOps.length)]; + if (selectedOp.equals("sin") || selectedOp.equals("cos") || selectedOp.equals("tan")) { + hasTrigFunction = true; + } + operators.add(selectedOp); + } + + // 构建表达式字符串 + expression.append(operands.get(0).intValue()); + for (int i = 0; i < operatorCount; i++) { + if (operators.get(i).equals("sin") || operators.get(i).equals("cos") || operators.get(i).equals("tan")) { + // 生成常见角度 + int[] commonAngles = {0, 30, 45, 60, 90, 120, 135, 150, 180, 210, 225, 270, 300, 315, 330}; + int angle = commonAngles[random.nextInt(commonAngles.length)]; + expression.append(" ").append(operators.get(i)).append("(").append(angle).append("°)"); + + // 计算三角函数值 + double trigValue = calculateTrigFunction(operators.get(i), angle); + operands.set(i + 1, trigValue); + } else { + expression.append(" ").append(operators.get(i)).append(" ").append(operands.get(i + 1).intValue()); + } + } + expression.append(" = ?"); + + // 计算表达式结果 + int result = calculateSeniorCompoundExpression(operands, operators); + + return new String[]{expression.toString(), String.valueOf(result)}; + } + + /** + * 生成高中复合运算表达式(包含三角函数) + */ + private String[] generateSeniorCompoundExpression(int operatorCount) { + StringBuilder expression = new StringBuilder(); + List operands = new ArrayList<>(); + List operators = new ArrayList<>(); + + // 生成操作数(运算符数量+1个操作数) + for (int i = 0; i <= operatorCount; i++) { + operands.add((double)(MIN_OPERAND_VALUE + random.nextInt(MAX_OPERAND_VALUE))); + } + + // 生成运算符(包含三角函数) + String[] availableOps = {"+", "-", "*", "/", "sin", "cos", "tan"}; + for (int i = 0; i < operatorCount; i++) { + operators.add(availableOps[random.nextInt(availableOps.length)]); + } + + // 构建表达式字符串 + expression.append(operands.get(0).intValue()); + for (int i = 0; i < operatorCount; i++) { + if (operators.get(i).equals("sin") || operators.get(i).equals("cos") || operators.get(i).equals("tan")) { + // 生成常见角度 + int[] commonAngles = {0, 30, 45, 60, 90, 120, 135, 150, 180, 210, 225, 270, 300, 315, 330}; + int angle = commonAngles[random.nextInt(commonAngles.length)]; + expression.append(" ").append(operators.get(i)).append("(").append(angle).append("°)"); + + // 计算三角函数值 + double trigValue = calculateTrigFunction(operators.get(i), angle); + operands.set(i + 1, trigValue); + } else { + expression.append(" ").append(operators.get(i)).append(" ").append(operands.get(i + 1).intValue()); + } + } + expression.append(" = ?"); + + // 计算表达式结果 + int result = calculateSeniorCompoundExpression(operands, operators); + + return new String[]{expression.toString(), String.valueOf(result)}; + } + + /** + * 生成高级复合运算表达式(包含平方运算) + */ + private String[] generateAdvancedCompoundExpression(int operatorCount) { + StringBuilder expression = new StringBuilder(); + List operands = new ArrayList<>(); + List operators = new ArrayList<>(); + + // 生成操作数(运算符数量+1个操作数) + for (int i = 0; i <= operatorCount; i++) { + operands.add(MIN_OPERAND_VALUE + random.nextInt(MAX_OPERAND_VALUE)); + } + + // 生成运算符(包含平方运算) + String[] availableOps = {"+", "-", "*", "/", "²"}; + for (int i = 0; i < operatorCount; i++) { + operators.add(availableOps[random.nextInt(availableOps.length)]); + } + + // 构建表达式字符串 + expression.append(operands.get(0)); + for (int i = 0; i < operatorCount; i++) { + if (operators.get(i).equals("²")) { + expression.append("²"); + } else { + expression.append(" ").append(operators.get(i)).append(" ").append(operands.get(i + 1)); + } + } + expression.append(" = ?"); + + // 计算表达式结果 + int result = calculateAdvancedCompoundExpression(operands, operators); + + return new String[]{expression.toString(), String.valueOf(result)}; + } + + /** + * 计算包含除法的复合表达式 + */ + private int calculateCompoundExpressionWithDivision(List operands, List operators) { + // 先处理乘除法,再处理加减法 + List numbers = new ArrayList<>(operands); + List ops = new ArrayList<>(operators); + + // 第一轮:处理乘除法 + for (int i = 0; i < ops.size(); i++) { + if (ops.get(i).equals("*")) { + int result = numbers.get(i) * numbers.get(i + 1); + numbers.set(i, result); + numbers.remove(i + 1); + ops.remove(i); + i--; // 调整索引 + } else if (ops.get(i).equals("/")) { + if (numbers.get(i + 1) != 0) { + int result = numbers.get(i) / numbers.get(i + 1); + numbers.set(i, result); + numbers.remove(i + 1); + ops.remove(i); + i--; // 调整索引 + } else { + // 避免除零错误,重新生成 + numbers.set(i + 1, 1 + random.nextInt(MAX_OPERAND_VALUE)); + int result = numbers.get(i) / numbers.get(i + 1); + numbers.set(i, result); + numbers.remove(i + 1); + ops.remove(i); + i--; // 调整索引 + } + } + } + + // 第二轮:处理加减法 + int result = numbers.get(0); + for (int i = 0; i < ops.size(); i++) { + if (ops.get(i).equals("+")) { + result += numbers.get(i + 1); + } else if (ops.get(i).equals("-")) { + result -= numbers.get(i + 1); + } + } + + return result; } + /** + * 计算初中复合表达式(包含平方/开方) + */ + private int calculateJuniorCompoundExpression(List operands, List operators) { + // 先处理平方和开方运算 + List numbers = new ArrayList<>(operands); + List ops = new ArrayList<>(operators); + + // 第一轮:处理平方和开方运算 + for (int i = 0; i < ops.size(); i++) { + if (ops.get(i).equals("²")) { + int result = numbers.get(i) * numbers.get(i); + numbers.set(i, result); + ops.remove(i); + i--; // 调整索引 + } else if (ops.get(i).equals("√")) { + // 开方运算已经在生成时处理了 + ops.remove(i); + i--; // 调整索引 + } + } + + // 第二轮:处理乘除法 + for (int i = 0; i < ops.size(); i++) { + if (ops.get(i).equals("*")) { + int result = numbers.get(i) * numbers.get(i + 1); + numbers.set(i, result); + numbers.remove(i + 1); + ops.remove(i); + i--; // 调整索引 + } else if (ops.get(i).equals("/")) { + if (numbers.get(i + 1) != 0) { + int result = numbers.get(i) / numbers.get(i + 1); + numbers.set(i, result); + numbers.remove(i + 1); + ops.remove(i); + i--; // 调整索引 + } else { + // 避免除零错误,重新生成 + numbers.set(i + 1, 1 + random.nextInt(MAX_OPERAND_VALUE)); + int result = numbers.get(i) / numbers.get(i + 1); + numbers.set(i, result); + numbers.remove(i + 1); + ops.remove(i); + i--; // 调整索引 + } + } + } + + // 第三轮:处理加减法 + int result = numbers.get(0); + for (int i = 0; i < ops.size(); i++) { + if (ops.get(i).equals("+")) { + result += numbers.get(i + 1); + } else if (ops.get(i).equals("-")) { + result -= numbers.get(i + 1); + } + } + + return result; + } + + /** + * 计算高中复合表达式(包含三角函数) + */ + private int calculateSeniorCompoundExpression(List operands, List operators) { + // 先处理三角函数运算 + List numbers = new ArrayList<>(operands); + List ops = new ArrayList<>(operators); + + // 第一轮:处理三角函数运算 + for (int i = 0; i < ops.size(); i++) { + if (ops.get(i).equals("sin") || ops.get(i).equals("cos") || ops.get(i).equals("tan")) { + // 三角函数运算已经在生成时处理了 + ops.remove(i); + i--; // 调整索引 + } + } + + // 第二轮:处理乘除法 + for (int i = 0; i < ops.size(); i++) { + if (ops.get(i).equals("*")) { + double result = numbers.get(i) * numbers.get(i + 1); + numbers.set(i, result); + numbers.remove(i + 1); + ops.remove(i); + i--; // 调整索引 + } else if (ops.get(i).equals("/")) { + if (numbers.get(i + 1) != 0) { + double result = numbers.get(i) / numbers.get(i + 1); + numbers.set(i, result); + numbers.remove(i + 1); + ops.remove(i); + i--; // 调整索引 + } else { + // 避免除零错误,重新生成 + numbers.set(i + 1, 1.0 + random.nextInt(MAX_OPERAND_VALUE)); + double result = numbers.get(i) / numbers.get(i + 1); + numbers.set(i, result); + numbers.remove(i + 1); + ops.remove(i); + i--; // 调整索引 + } + } + } + + // 第三轮:处理加减法 + double result = numbers.get(0); + for (int i = 0; i < ops.size(); i++) { + if (ops.get(i).equals("+")) { + result += numbers.get(i + 1); + } else if (ops.get(i).equals("-")) { + result -= numbers.get(i + 1); + } + } + + return (int) Math.round(result); + } + + /** + * 计算高级复合表达式(包含平方运算) + */ + private int calculateAdvancedCompoundExpression(List operands, List operators) { + // 先处理平方运算 + List numbers = new ArrayList<>(operands); + List ops = new ArrayList<>(operators); + + // 第一轮:处理平方运算 + for (int i = 0; i < ops.size(); i++) { + if (ops.get(i).equals("²")) { + int result = numbers.get(i) * numbers.get(i); + numbers.set(i, result); + ops.remove(i); + i--; // 调整索引 + } + } + + // 第二轮:处理乘除法 + for (int i = 0; i < ops.size(); i++) { + if (ops.get(i).equals("*")) { + int result = numbers.get(i) * numbers.get(i + 1); + numbers.set(i, result); + numbers.remove(i + 1); + ops.remove(i); + i--; // 调整索引 + } else if (ops.get(i).equals("/")) { + if (numbers.get(i + 1) != 0) { + int result = numbers.get(i) / numbers.get(i + 1); + numbers.set(i, result); + numbers.remove(i + 1); + ops.remove(i); + i--; // 调整索引 + } else { + // 避免除零错误,重新生成 + numbers.set(i + 1, 1 + random.nextInt(MAX_OPERAND_VALUE)); + int result = numbers.get(i) / numbers.get(i + 1); + numbers.set(i, result); + numbers.remove(i + 1); + ops.remove(i); + i--; // 调整索引 + } + } + } + + // 第三轮:处理加减法 + int result = numbers.get(0); + for (int i = 0; i < ops.size(); i++) { + if (ops.get(i).equals("+")) { + result += numbers.get(i + 1); + } else if (ops.get(i).equals("-")) { + result -= numbers.get(i + 1); + } + } + + return result; + } + /** * 获取随机运算符 */ @@ -201,7 +976,7 @@ public class ChoiceQuestionGenerator { int index = random.nextInt(operations.length()); return String.valueOf(operations.charAt(index)); } - + /** * 难度级别枚举 */ diff --git a/src/main/java/com/example/mathsystemtogether/ExamController.java b/src/main/java/com/example/mathsystemtogether/ExamController.java index cb60676..99c42c7 100644 --- a/src/main/java/com/example/mathsystemtogether/ExamController.java +++ b/src/main/java/com/example/mathsystemtogether/ExamController.java @@ -33,6 +33,7 @@ public class ExamController { @FXML private Button startExamButton; @FXML private Button logoutButton; @FXML private Label statusLabel; + @FXML private Button changePasswordButton; // 数据成员 private Account currentAccount; @@ -80,7 +81,7 @@ public class ExamController { if (!Files.exists(Paths.get(USER_DATA_FILE))) { return; } - + List lines = Files.readAllLines(Paths.get(USER_DATA_FILE)); for (String line : lines) { String[] parts = line.split("\\|"); @@ -88,14 +89,14 @@ public class ExamController { String username = parts[0]; String password = parts[1]; String levelStr = parts[3]; - + Level level; try { level = Level.valueOf(levelStr); } catch (IllegalArgumentException e) { level = Level.小学; // 默认级别 } - + userMap.put(username, new Account(username, password, level)); } } @@ -140,7 +141,7 @@ public class ExamController { registerStage.setScene(scene); registerStage.setResizable(false); registerStage.show(); - + } catch (Exception e) { loginStatusLabel.setText("打开注册界面失败:" + e.getMessage()); loginStatusLabel.setStyle("-fx-text-fill: red;"); @@ -197,6 +198,62 @@ public class ExamController { } } +@FXML +private void handleChangePassword() { + if (currentAccount == null) { + statusLabel.setText("请先登录"); + statusLabel.setStyle("-fx-text-fill: red;"); + return; + } + + try { + FXMLLoader loader = new FXMLLoader(getClass().getResource("change-password-view.fxml")); + Scene scene = new Scene(loader.load(), 600, 500); + Stage changePasswordStage = new Stage(); + changePasswordStage.setTitle("修改密码"); + changePasswordStage.setScene(scene); + changePasswordStage.setResizable(false); + + // 传递当前用户信息给密码修改控制器 + ChangePasswordController controller = loader.getController(); + controller.setCurrentUser(currentAccount.username); + controller.setExamController(this); + + changePasswordStage.show(); + } catch (Exception e) { + statusLabel.setText("打开修改密码界面失败:" + e.getMessage()); + statusLabel.setStyle("-fx-text-fill: red;"); + } +} + +// 供 ChangePasswordController 调用的方法 +public boolean checkUserPassword(String username, String password) { + Account account = userMap.get(username); + return account != null && account.password.equals(password); +} + + // 更新用户密码 + public void updateUserPassword(String username, String newPassword) { + Account account = userMap.get(username); + if (account != null) { + account.password = newPassword; + } + } + + // 为继续做题设置用户信息 + public void setUserForContinueExam(String username, String level) { + // 设置当前用户 + Account account = userMap.get(username); + if (account != null) { + currentAccount = account; + welcomeLabel.setText("欢迎," + username + "!当前级别:" + account.level); + levelComboBox.setValue(account.level.toString()); + examSetupPanel.setVisible(true); + loginStatusLabel.setText("继续做题模式"); + loginStatusLabel.setStyle("-fx-text-fill: green;"); + } + } + private void startExam() { try { // 打开专门的考试界面 @@ -240,4 +297,4 @@ public class ExamController { enum Level { 小学, 初中, 高中 } -} \ No newline at end of file +} diff --git a/src/main/java/com/example/mathsystemtogether/ExamDetailsController.java b/src/main/java/com/example/mathsystemtogether/ExamDetailsController.java new file mode 100644 index 0000000..ade72a1 --- /dev/null +++ b/src/main/java/com/example/mathsystemtogether/ExamDetailsController.java @@ -0,0 +1,175 @@ +package com.example.mathsystemtogether; + +import javafx.fxml.FXML; +import javafx.scene.control.*; +import javafx.scene.layout.VBox; +import javafx.scene.layout.HBox; +import javafx.scene.text.Font; +import javafx.scene.text.FontWeight; +import javafx.scene.paint.Color; +import javafx.stage.Stage; + +import java.util.List; + +/** + * 考试详细信息界面控制器 + */ +public class ExamDetailsController { + + @FXML private Label examInfoLabel; + @FXML private ScrollPane detailsScrollPane; + @FXML private VBox detailsContainer; + @FXML private Button scrollToTopButton; + @FXML private Button scrollToBottomButton; + @FXML private Button closeButton; + + // 数据成员 + private List examQuestions; + private List userAnswers; + private String username; + private String level; + + /** + * 设置考试详细信息 + */ + public void setExamDetails(List questions, List answers, String username, String level) { + this.examQuestions = questions; + this.userAnswers = answers; + this.username = username; + this.level = level; + + // 更新界面显示 + updateDisplay(); + } + + /** + * 更新界面显示 + */ + private void updateDisplay() { + // 更新考试信息 + examInfoLabel.setText(String.format("用户: %s | 级别: %s | 题目: %d题", username, level, examQuestions.size())); + + // 清空容器 + detailsContainer.getChildren().clear(); + + // 添加每道题的详细信息 + for (int i = 0; i < examQuestions.size(); i++) { + Question question = examQuestions.get(i); + String userAnswer = i < userAnswers.size() ? userAnswers.get(i) : "未作答"; + boolean isCorrect = question.isCorrect(userAnswer); + + // 创建题目详情卡片 + VBox questionCard = createQuestionCard(i + 1, question, userAnswer, isCorrect); + detailsContainer.getChildren().add(questionCard); + } + } + + /** + * 创建题目详情卡片 + */ + private VBox createQuestionCard(int questionNumber, Question question, String userAnswer, boolean isCorrect) { + VBox card = new VBox(); + card.setSpacing(10); + card.setStyle("-fx-background-color: " + (isCorrect ? "rgba(144,238,144,0.3)" : "rgba(255,182,193,0.3)") + + "; -fx-background-radius: 10; -fx-padding: 15; -fx-border-color: " + + (isCorrect ? "#90EE90" : "#FFB6C1") + "; -fx-border-radius: 10; -fx-border-width: 2;"); + + // 题目编号和状态 + HBox headerBox = new HBox(); + headerBox.setSpacing(10); + + Label questionNumLabel = new Label("第 " + questionNumber + " 题"); + questionNumLabel.setFont(Font.font("System", FontWeight.BOLD, 16)); + questionNumLabel.setTextFill(Color.DARKBLUE); + + Label statusLabel = new Label(isCorrect ? "✓ 正确" : "✗ 错误"); + statusLabel.setFont(Font.font("System", FontWeight.BOLD, 14)); + statusLabel.setTextFill(isCorrect ? Color.GREEN : Color.RED); + + headerBox.getChildren().addAll(questionNumLabel, statusLabel); + + // 题目内容 + Label questionLabel = new Label("题目: " + question.getQuestionText()); + questionLabel.setFont(Font.font("System", 14)); + questionLabel.setWrapText(true); + + // 选项 + VBox optionsBox = new VBox(); + optionsBox.setSpacing(5); + + Label optionALabel = new Label("A. " + question.getOptionA()); + Label optionBLabel = new Label("B. " + question.getOptionB()); + Label optionCLabel = new Label("C. " + question.getOptionC()); + Label optionDLabel = new Label("D. " + question.getOptionD()); + + // 高亮用户答案和正确答案 + highlightAnswer(optionALabel, question.getOptionA(), userAnswer, question.getCorrectAnswer()); + highlightAnswer(optionBLabel, question.getOptionB(), userAnswer, question.getCorrectAnswer()); + highlightAnswer(optionCLabel, question.getOptionC(), userAnswer, question.getCorrectAnswer()); + highlightAnswer(optionDLabel, question.getOptionD(), userAnswer, question.getCorrectAnswer()); + + optionsBox.getChildren().addAll(optionALabel, optionBLabel, optionCLabel, optionDLabel); + + // 答案信息 + HBox answerBox = new HBox(); + answerBox.setSpacing(20); + + Label userAnswerLabel = new Label("你的答案: " + userAnswer); + userAnswerLabel.setFont(Font.font("System", FontWeight.BOLD, 12)); + userAnswerLabel.setTextFill(Color.BLUE); + + Label correctAnswerLabel = new Label("正确答案: " + question.getCorrectAnswer()); + correctAnswerLabel.setFont(Font.font("System", FontWeight.BOLD, 12)); + correctAnswerLabel.setTextFill(Color.GREEN); + + answerBox.getChildren().addAll(userAnswerLabel, correctAnswerLabel); + + // 添加到卡片 + card.getChildren().addAll(headerBox, questionLabel, optionsBox, answerBox); + + return card; + } + + /** + * 高亮显示答案选项 + */ + private void highlightAnswer(Label optionLabel, String optionValue, String userAnswer, String correctAnswer) { + optionLabel.setFont(Font.font("System", 12)); + + if (optionValue.equals(correctAnswer)) { + // 正确答案 - 绿色背景 + optionLabel.setStyle("-fx-background-color: rgba(144,238,144,0.5); -fx-background-radius: 5; -fx-padding: 5; -fx-font-weight: bold;"); + } else if (optionValue.equals(userAnswer) && !userAnswer.equals(correctAnswer)) { + // 用户错误答案 - 红色背景 + optionLabel.setStyle("-fx-background-color: rgba(255,182,193,0.5); -fx-background-radius: 5; -fx-padding: 5; -fx-font-weight: bold;"); + } else { + // 其他选项 - 默认样式 + optionLabel.setStyle("-fx-padding: 5;"); + } + } + + /** + * 处理滚动到顶部按钮 + */ + @FXML + private void handleScrollToTop() { + detailsScrollPane.setVvalue(0.0); + } + + /** + * 处理滚动到底部按钮 + */ + @FXML + private void handleScrollToBottom() { + detailsScrollPane.setVvalue(1.0); + } + + /** + * 处理关闭按钮 + */ + @FXML + private void handleClose() { + Stage stage = (Stage) closeButton.getScene().getWindow(); + stage.close(); + } +} diff --git a/src/main/java/com/example/mathsystemtogether/ExamResultController.java b/src/main/java/com/example/mathsystemtogether/ExamResultController.java new file mode 100644 index 0000000..ce63036 --- /dev/null +++ b/src/main/java/com/example/mathsystemtogether/ExamResultController.java @@ -0,0 +1,206 @@ +package com.example.mathsystemtogether; + +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.Scene; +import javafx.scene.control.*; +import javafx.stage.Stage; + +import java.util.List; + +/** + * 考试结果界面控制器 + */ +public class ExamResultController { + + @FXML private Label titleLabel; + @FXML private Label subtitleLabel; + @FXML private Label scoreLabel; + @FXML private Label correctCountLabel; + @FXML private Label totalCountLabel; + @FXML private Label accuracyLabel; + @FXML private Label gradeLabel; + @FXML private Label commentLabel; + @FXML private Button continueButton; + @FXML private Button exitButton; + @FXML private Button detailsButton; + + // 数据成员 + private List examQuestions; + private List userAnswers; + private String username; + private String level; + private int score; + private int correctCount; + private int totalCount; + private double accuracy; + + /** + * 设置考试结果数据 + */ + public void setExamResult(List questions, List answers, String username, String level) { + this.examQuestions = questions; + this.userAnswers = answers; + this.username = username; + this.level = level; + this.totalCount = questions.size(); + + // 计算成绩 + calculateScore(); + + // 更新界面显示 + updateDisplay(); + } + + /** + * 计算考试成绩 + */ + private void calculateScore() { + correctCount = 0; + + for (int i = 0; i < examQuestions.size(); i++) { + Question question = examQuestions.get(i); + String userAnswer = i < userAnswers.size() ? userAnswers.get(i) : ""; + + if (question.isCorrect(userAnswer)) { + correctCount++; + } + } + + score = (int) Math.round((double) correctCount / totalCount * 100); + accuracy = (double) correctCount / totalCount * 100; + } + + /** + * 更新界面显示 + */ + private void updateDisplay() { + // 更新分数显示 + scoreLabel.setText(String.format("得分: %d分", score)); + correctCountLabel.setText(String.format("正确: %d题", correctCount)); + totalCountLabel.setText(String.format("总计: %d题", totalCount)); + accuracyLabel.setText(String.format("正确率: %.1f%%", accuracy)); + + // 更新成绩评价 + String grade = getGrade(score); + String comment = getComment(score); + + gradeLabel.setText(grade); + commentLabel.setText(comment); + + // 根据成绩调整颜色 + updateGradeColor(score); + } + + /** + * 获取成绩等级 + */ + private String getGrade(int score) { + if (score >= 90) return "优秀"; + else if (score >= 80) return "良好"; + else if (score >= 70) return "中等"; + else if (score >= 60) return "及格"; + else return "不及格"; + } + + /** + * 获取成绩评语 + */ + private String getComment(int score) { + if (score >= 90) return "表现非常出色!继续保持!"; + else if (score >= 80) return "表现良好,还有提升空间!"; + else if (score >= 70) return "表现中等,需要加强练习!"; + else if (score >= 60) return "刚刚及格,需要更多努力!"; + else return "需要认真学习,多加练习!"; + } + + /** + * 根据成绩更新颜色 + */ + private void updateGradeColor(int score) { + String color; + if (score >= 90) color = "#2E8B57"; // 绿色 + else if (score >= 80) color = "#32CD32"; // 浅绿色 + else if (score >= 70) color = "#FFD700"; // 金色 + else if (score >= 60) color = "#FF8C00"; // 橙色 + else color = "#DC143C"; // 红色 + + gradeLabel.setStyle("-fx-font-size: 24; -fx-font-weight: bold; -fx-text-fill: " + color + ";"); + } + + /** + * 处理继续做题按钮 + */ + @FXML + private void handleContinueExam() { + try { + // 关闭当前窗口 + Stage currentStage = (Stage) continueButton.getScene().getWindow(); + currentStage.close(); + + // 打开考试设置界面,让用户选择题目数量和难度 + FXMLLoader loader = new FXMLLoader(getClass().getResource("exam-view.fxml")); + Scene scene = new Scene(loader.load(), 1000, 900); + Stage stage = new Stage(); + stage.setTitle("数学考试系统"); + stage.setScene(scene); + + // 获取主界面控制器并设置用户信息 + ExamController examController = loader.getController(); + examController.setUserForContinueExam(username, level); + + stage.show(); + + } catch (Exception e) { + showAlert("错误", "打开考试设置界面时出错:" + e.getMessage()); + } + } + + /** + * 处理退出系统按钮 + */ + @FXML + private void handleExitSystem() { + // 关闭当前窗口 + Stage currentStage = (Stage) exitButton.getScene().getWindow(); + currentStage.close(); + } + + /** + * 处理查看详细信息按钮 + */ + @FXML + private void handleShowDetails() { + try { + // 打开详细信息窗口 + FXMLLoader loader = new FXMLLoader(getClass().getResource("exam-details-view.fxml")); + Scene scene = new Scene(loader.load(), 300, 700); + Stage detailsStage = new Stage(); + detailsStage.setTitle("详细答题情况"); + detailsStage.setScene(scene); + detailsStage.setResizable(true); + detailsStage.setMinWidth(800); + detailsStage.setMinHeight(600); + + // 传递数据给详细信息控制器 + ExamDetailsController controller = loader.getController(); + controller.setExamDetails(examQuestions, userAnswers, username, level); + + detailsStage.show(); + + } catch (Exception e) { + showAlert("错误", "打开详细信息时出错:" + e.getMessage()); + } + } + + /** + * 显示警告对话框 + */ + private void showAlert(String title, String message) { + Alert alert = new Alert(Alert.AlertType.INFORMATION); + alert.setTitle(title); + alert.setHeaderText(null); + alert.setContentText(message); + alert.showAndWait(); + } +} diff --git a/src/main/java/com/example/mathsystemtogether/ExamTakingController.java b/src/main/java/com/example/mathsystemtogether/ExamTakingController.java index be77e36..7b1f89a 100644 --- a/src/main/java/com/example/mathsystemtogether/ExamTakingController.java +++ b/src/main/java/com/example/mathsystemtogether/ExamTakingController.java @@ -4,10 +4,7 @@ import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.scene.Scene; import javafx.scene.control.*; -import javafx.scene.layout.VBox; import javafx.stage.Stage; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; import javafx.animation.Timeline; import javafx.animation.KeyFrame; import javafx.util.Duration; @@ -31,6 +28,7 @@ public class ExamTakingController { @FXML private ToggleGroup answerGroup; @FXML private Button submitButton; @FXML private Button nextButton; + @FXML private Button prevButton; @FXML private Button exitExamButton; @FXML private ProgressBar progressBar; @FXML private Label progressLabel; @@ -117,10 +115,57 @@ public class ExamTakingController { optionC.setText("C. " + question.getOptionC()); optionD.setText("D. " + question.getOptionD()); - // 清除之前的选择 - answerGroup.selectToggle(null); + // 恢复之前保存的答案选择 + restorePreviousAnswer(); // 更新按钮状态 + updateButtonStates(); + + updateProgress(); + } + + /** + * 恢复之前保存的答案选择 + */ + private void restorePreviousAnswer() { + String savedAnswer = userAnswers.get(currentQuestionIndex); + if (savedAnswer != null) { + // 根据保存的答案值找到对应的选项 + Question question = examQuestions.get(currentQuestionIndex); + if (savedAnswer.equals(question.getOptionA())) { + answerGroup.selectToggle(optionA); + } else if (savedAnswer.equals(question.getOptionB())) { + answerGroup.selectToggle(optionB); + } else if (savedAnswer.equals(question.getOptionC())) { + answerGroup.selectToggle(optionC); + } else if (savedAnswer.equals(question.getOptionD())) { + answerGroup.selectToggle(optionD); + } + } else { + // 没有保存的答案,清除选择 + answerGroup.selectToggle(null); + } + } + + /** + * 保存当前答案 + */ + private void saveCurrentAnswer() { + RadioButton selectedOption = (RadioButton) answerGroup.getSelectedToggle(); + if (selectedOption != null) { + String answer = getAnswerValue(selectedOption.getText().substring(0, 1)); + userAnswers.put(currentQuestionIndex, answer); + } + } + + /** + * 更新按钮状态 + */ + private void updateButtonStates() { + // 上一题按钮状态 + prevButton.setVisible(currentQuestionIndex > 0); + + // 下一题按钮状态 if (currentQuestionIndex == examQuestions.size() - 1) { submitButton.setText("✅ 提交并完成考试"); nextButton.setVisible(false); @@ -128,8 +173,6 @@ public class ExamTakingController { submitButton.setText("✅ 提交答案"); nextButton.setVisible(true); } - - updateProgress(); } private void updateProgress() { @@ -173,10 +216,22 @@ public class ExamTakingController { } @FXML private void handleNextQuestion() { + // 保存当前答案(如果有选择的话) + saveCurrentAnswer(); currentQuestionIndex++; displayCurrentQuestion(); } + @FXML + private void handlePreviousQuestion() { + if (currentQuestionIndex > 0) { + // 保存当前答案(如果有选择的话) + saveCurrentAnswer(); + currentQuestionIndex--; + displayCurrentQuestion(); + } + } + @FXML private void handleExitExam() { if (showExitConfirmation()) { @@ -206,40 +261,30 @@ public class ExamTakingController { // 停止计时器 timer.stop(); - // 计算成绩 - int correctCount = 0; - StringBuilder resultDetails = new StringBuilder(); - + // 准备用户答案列表 + List userAnswersList = new ArrayList<>(); for (int i = 0; i < examQuestions.size(); i++) { - Question question = examQuestions.get(i); - String userAnswer = userAnswers.get(i); - boolean isCorrect = question.isCorrect(userAnswer); - - if (isCorrect) { - correctCount++; - } - - resultDetails.append("第").append(i + 1).append("题: "); - resultDetails.append(question.getQuestionText()).append("\n"); - resultDetails.append("你的答案: ").append(userAnswer != null ? userAnswer : "未作答").append(" "); - resultDetails.append("正确答案: ").append(question.getCorrectAnswer()).append(" "); - resultDetails.append(isCorrect ? "✓" : "✗").append("\n\n"); + String answer = userAnswers.get(i); + userAnswersList.add(answer != null ? answer : ""); } - int score = (int) Math.round((double) correctCount / examQuestions.size() * 100); + // 关闭当前考试窗口 + Stage currentStage = (Stage) submitButton.getScene().getWindow(); + currentStage.close(); + + // 打开结果界面 + FXMLLoader loader = new FXMLLoader(getClass().getResource("exam-result-view.fxml")); + Scene scene = new Scene(loader.load(), 600, 700); + Stage resultStage = new Stage(); + resultStage.setTitle("考试结果"); + resultStage.setScene(scene); + resultStage.setResizable(false); - // 显示结果对话框 - Alert resultAlert = new Alert(Alert.AlertType.INFORMATION); - resultAlert.setTitle("考试完成"); - resultAlert.setHeaderText("🎉 考试结果"); - resultAlert.setContentText(String.format( - "得分: %d分\n正确: %d题\n总计: %d题\n正确率: %.1f%%", - score, correctCount, examQuestions.size(), (double) correctCount / examQuestions.size() * 100 - )); - resultAlert.showAndWait(); + // 传递数据给结果控制器 + ExamResultController controller = loader.getController(); + controller.setExamResult(examQuestions, userAnswersList, username, level); - // 返回主菜单 - returnToMainMenu(); + resultStage.show(); } catch (Exception e) { showAlert("错误", "显示结果时出错:" + e.getMessage()); diff --git a/src/main/java/com/example/mathsystemtogether/HelloApplication.java b/src/main/java/com/example/mathsystemtogether/HelloApplication.java index 91183f8..9cf2187 100644 --- a/src/main/java/com/example/mathsystemtogether/HelloApplication.java +++ b/src/main/java/com/example/mathsystemtogether/HelloApplication.java @@ -11,7 +11,7 @@ public class HelloApplication extends Application { @Override public void start(Stage stage) throws IOException { FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("exam-view.fxml")); - Scene scene = new Scene(fxmlLoader.load(), 900, 900); + Scene scene = new Scene(fxmlLoader.load(), 900, 800); stage.setTitle("数学考试系统"); stage.setResizable(true); stage.setScene(scene); diff --git a/src/main/java/com/example/mathsystemtogether/RegisterController.java b/src/main/java/com/example/mathsystemtogether/RegisterController.java index 9939189..b19416f 100644 --- a/src/main/java/com/example/mathsystemtogether/RegisterController.java +++ b/src/main/java/com/example/mathsystemtogether/RegisterController.java @@ -37,6 +37,10 @@ public class RegisterController { private static final String EMAIL_REGEX = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$"; private static final Pattern EMAIL_PATTERN = Pattern.compile(EMAIL_REGEX); + // 密码强度验证正则表达式 + private static final String PASSWORD_REGEX = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).+$"; + private static final Pattern PASSWORD_PATTERN = Pattern.compile(PASSWORD_REGEX); + // 用户数据文件路径 private static final String USER_DATA_FILE = "user_data.txt"; @@ -140,6 +144,11 @@ public class RegisterController { showStatus("密码至少需要6个字符", true); return; } + + if (!isValidPassword(password)) { + showStatus("密码必须包含大小写字母和数字", true); + return; + } if (!password.equals(confirmPassword)) { showStatus("两次输入的密码不一致", true); @@ -229,6 +238,14 @@ public class RegisterController { private boolean isValidEmail(String email) { return EMAIL_PATTERN.matcher(email).matches(); } + + /** + * 验证密码强度 + * 密码必须包含至少一个小写字母、一个大写字母和一个数字 + */ + private boolean isValidPassword(String password) { + return PASSWORD_PATTERN.matcher(password).matches(); + } private boolean isUserExists(String username) { try { diff --git a/src/main/resources/com/example/mathsystemtogether/change-password-view.fxml b/src/main/resources/com/example/mathsystemtogether/change-password-view.fxml new file mode 100644 index 0000000..7b7970b --- /dev/null +++ b/src/main/resources/com/example/mathsystemtogether/change-password-view.fxml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +