diff --git a/pom.xml b/pom.xml
index 800a271..4d69a1a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -9,54 +9,46 @@
1.0.0
-
17
17
UTF-8
-
-
21.0.8
-
com.google.code.gson
gson
2.10.1
-
org.openjfx
javafx-controls
${javafx.version}
-
org.openjfx
javafx-fxml
${javafx.version}
-
-
- org.openjfx
- javafx-graphics
- ${javafx.version}
-
-
org.apache.commons
commons-email
1.5
+
+
+ org.mozilla
+ rhino-engine
+ 1.7.14
+
-
org.apache.maven.plugins
maven-compiler-plugin
@@ -67,24 +59,19 @@
-
-
+ org.apache.maven.plugins
maven-assembly-plugin
3.6.0
-
- com.mathgenerator.Application
+ com.mathgenerator.MainApplication
jar-with-dependencies
- mathgenerator
- false
- single
diff --git a/src/main/java/com/mathgenerator/generator/JuniorHighSchoolGenerator.java b/src/main/java/com/mathgenerator/generator/JuniorHighSchoolGenerator.java
index 7922dc4..add01a1 100644
--- a/src/main/java/com/mathgenerator/generator/JuniorHighSchoolGenerator.java
+++ b/src/main/java/com/mathgenerator/generator/JuniorHighSchoolGenerator.java
@@ -4,75 +4,63 @@ import com.mathgenerator.model.ChoiceQuestion;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
/**
- * 初中选择题生成器 (已修正)。
- * 通过构造而非随机的方式,确保题目包含平方或开根号,且最终解为整数。
+ * 初中选择题生成器 (最终版 - 采用结构化插入)。
+ * 通过直接在表达式结构中插入运算项,确保语法正确性和高性能。
*/
public class JuniorHighSchoolGenerator extends PrimarySchoolGenerator {
- private static final Pattern NUMBER_PATTERN = Pattern.compile("\\d+");
- private static final int[] PERFECT_SQUARES = {4, 9, 16, 25, 36, 49, 64, 81, 100};
+ private static final int[] PERFECT_SQUARES = {1, 4, 9, 16, 25, 36, 49, 64, 81, 100};
@Override
public ChoiceQuestion generateSingleQuestion() {
- String questionText;
- int correctAnswer;
+ ThreadLocalRandom random = ThreadLocalRandom.current();
+ int operandCount = random.nextInt(2, 5); // 2到4个操作数
- // 循环直到生成一个有效的、有整数解的题目
- while (true) {
- // 1. 获取一个基础的小学题目字符串
- String basicQuestionText = super.generateBasicQuestionText();
-
- // 2. 随机选择在题目中加入平方还是开根号
- boolean useSquare = ThreadLocalRandom.current().nextBoolean();
-
- if (useSquare) {
- // --- 平方策略 ---
- // 随机找到一个数字,给它加上平方
- Matcher matcher = NUMBER_PATTERN.matcher(basicQuestionText);
- List numbers = new ArrayList<>();
- while (matcher.find()) { numbers.add(matcher.group()); }
- if (numbers.isEmpty()) continue;
+ // 1. 生成基础的表达式组件列表
+ List parts = new ArrayList<>();
+ // 使用 getOperand() 和 getRandomOperator() 这些继承自父类的方法
+ parts.add(String.valueOf(getOperand()));
+ for (int i = 1; i < operandCount; i++) {
+ parts.add(getRandomOperator());
+ parts.add(String.valueOf(getOperand()));
+ }
- String numToSquare = numbers.get(ThreadLocalRandom.current().nextInt(numbers.size()));
- questionText = basicQuestionText.replaceFirst(Pattern.quote(numToSquare), numToSquare + "²");
+ // 2. 结构化地插入初中特色运算
+ int modificationIndex = random.nextInt(operandCount) * 2; // 随机选择一个操作数的位置
+ boolean useSquare = random.nextBoolean();
- } else {
- // --- 开根号策略 (保证整数解) ---
- // a. 随机找到一个数字
- Matcher matcher = NUMBER_PATTERN.matcher(basicQuestionText);
- List numbers = new ArrayList<>();
- while (matcher.find()) { numbers.add(matcher.group()); }
- if (numbers.isEmpty()) continue;
- String numToReplace = numbers.get(ThreadLocalRandom.current().nextInt(numbers.size()));
+ if (useSquare) {
+ // 平方策略:直接在数字后附加平方符号
+ parts.set(modificationIndex, parts.get(modificationIndex) + "²");
+ } else {
+ // 开根号策略:用一个完美的开根号表达式替换整个数字
+ int perfectSquare = PERFECT_SQUARES[random.nextInt(PERFECT_SQUARES.length)];
+ parts.set(modificationIndex, "√" + perfectSquare);
+ }
- // b. 随机选一个完美的平方数
- int perfectSquare = PERFECT_SQUARES[ThreadLocalRandom.current().nextInt(PERFECT_SQUARES.length)];
+ // 3. (可选)为增强后的表达式添加括号
+ if (operandCount > 2 && random.nextBoolean()) {
+ super.addParentheses(parts); // 调用父类的protected方法
+ }
- // c. 用 "√(平方数)" 替换掉原数字
- String sqrtExpression = "√" + perfectSquare;
- questionText = basicQuestionText.replaceFirst(Pattern.quote(numToReplace), sqrtExpression);
- }
+ String finalQuestionText = String.join(" ", parts);
- // 3. 验证修改后的题目是否有整数解
- try {
- Object result = evaluateExpression(questionText);
- if (result instanceof Number && ((Number) result).doubleValue() == ((Number) result).intValue()) {
- correctAnswer = ((Number) result).intValue();
- break; // 成功!得到整数解,跳出循环
- }
- } catch (Exception e) {
- // 如果表达式计算出错(例如语法错误),则忽略并重试
- }
+ // 4. 计算答案
+ double finalCorrectAnswer;
+ try {
+ Object result = evaluateExpression(finalQuestionText);
+ finalCorrectAnswer = ((Number) result).doubleValue();
+ } catch (Exception e) {
+ // 发生意外,安全返回一个小学题
+ return super.generateSingleQuestion();
}
- // 4. 为最终的题目生成选项
- List options = generateOptions(correctAnswer);
- int correctIndex = options.indexOf(String.valueOf(correctAnswer));
+ // 5. 生成选项
+ List options = generateDecimalOptions(finalCorrectAnswer);
+ int correctIndex = options.indexOf(formatNumber(finalCorrectAnswer));
- return new ChoiceQuestion(questionText, options, correctIndex);
+ return new ChoiceQuestion(finalQuestionText, options, correctIndex);
}
}
\ No newline at end of file
diff --git a/src/main/java/com/mathgenerator/generator/PrimarySchoolGenerator.java b/src/main/java/com/mathgenerator/generator/PrimarySchoolGenerator.java
index ff357b0..9ed1061 100644
--- a/src/main/java/com/mathgenerator/generator/PrimarySchoolGenerator.java
+++ b/src/main/java/com/mathgenerator/generator/PrimarySchoolGenerator.java
@@ -10,6 +10,7 @@ import java.util.concurrent.ThreadLocalRandom;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
+import java.text.DecimalFormat;
/**
* 小学选择题生成器。
@@ -87,30 +88,81 @@ public class PrimarySchoolGenerator implements QuestionGenerator {
}
/**
- * 使用JVM的脚本引擎计算字符串表达式的值。
+ * 使用JVM的脚本引擎计算字符串表达式的值 (已优化兼容性)。
* @param expression 数学表达式字符串
- * @return 计算结果
+ * @return 计算结果 (可能是Integer或Double)
* @throws ScriptException 如果表达式有语法错误
*/
protected Object evaluateExpression(String expression) throws ScriptException {
ScriptEngineManager manager = new ScriptEngineManager();
- ScriptEngine engine = manager.getEngineByName("JavaScript");
- // 为支持初高中计算,替换特殊符号
- // 注意√的正则表达式,确保只匹配数字
- expression = expression.replaceAll("²", "**2")
- .replaceAll("√(\\d+)", "Math.sqrt($1)");
- return engine.eval(expression);
+ // --- 核心修改在这里 ---
+ // 使用 "rhino" 作为引擎名称,这是Rhino引擎的官方名称
+ ScriptEngine engine = manager.getEngineByName("rhino");
+
+ if (engine == null) {
+ // 增加一个健壮性检查,如果引擎还是没找到,就给出清晰的错误提示
+ throw new IllegalStateException("错误:找不到Rhino JavaScript引擎。请检查pom.xml中是否已添加rhino-engine的依赖。");
+ }
+
+ // Rhino不需要预定义函数,可以直接计算
+ String script = expression.replaceAll("(\\d+(\\.\\d+)?)²", "Math.pow($1, 2)")
+ .replaceAll("√(\\d+(\\.\\d+)?)", "Math.sqrt($1)")
+ .replaceAll("(\\d+)°", " * (Math.PI / 180)"); // Rhino对角度计算的语法要求更严格
+
+ // 为了让sin/cos/tan能正确计算,需要特殊处理
+ script = script.replaceAll("sin\\(", "Math.sin(")
+ .replaceAll("cos\\(", "Math.cos(")
+ .replaceAll("tan\\(", "Math.tan(");
+
+ return engine.eval(script);
+ }
+
+ /**
+ * 格式化数字,最多保留两位小数。
+ * @param number 待格式化的数字
+ * @return 格式化后的字符串
+ */
+ protected String formatNumber(double number) {
+ if (number == (long) number) {
+ return String.format("%d", (long) number); // 如果是整数,不显示小数位
+ } else {
+ // 使用DecimalFormat来去除末尾多余的0
+ DecimalFormat df = new DecimalFormat("#.##");
+ return df.format(number);
+ }
+ }
+
+ /**
+ * 为小数答案生成四个选项。
+ * @param correctAnswer 正确答案
+ * @return 包含四个选项的随机排序列表
+ */
+ protected List generateDecimalOptions(double correctAnswer) {
+ ThreadLocalRandom random = ThreadLocalRandom.current();
+ Set options = new HashSet<>();
+ options.add(formatNumber(correctAnswer));
+
+ while (options.size() < 4) {
+ double delta = random.nextDouble(1, 11); // 答案加减1-10之间的随机小数
+ // 随机决定是加还是减
+ double distractor = random.nextBoolean() ? correctAnswer + delta : correctAnswer - delta;
+ options.add(formatNumber(distractor));
+ }
+
+ List sortedOptions = new ArrayList<>(options);
+ Collections.shuffle(sortedOptions);
+ return sortedOptions;
}
- private int getOperand() {
+ protected int getOperand() {
return ThreadLocalRandom.current().nextInt(1, 101);
}
- private String getRandomOperator() {
+ protected String getRandomOperator() {
return OPERATORS[ThreadLocalRandom.current().nextInt(OPERATORS.length)];
}
- private void addParentheses(List parts) {
+ protected void addParentheses(List parts) {
ThreadLocalRandom random = ThreadLocalRandom.current();
int startOperandIndex = random.nextInt(parts.size() / 2);
int endOperandIndex = random.nextInt(startOperandIndex + 1, parts.size() / 2 + 1);
diff --git a/src/main/java/com/mathgenerator/generator/SeniorHighSchoolGenerator.java b/src/main/java/com/mathgenerator/generator/SeniorHighSchoolGenerator.java
index 43ecb30..4dc6be4 100644
--- a/src/main/java/com/mathgenerator/generator/SeniorHighSchoolGenerator.java
+++ b/src/main/java/com/mathgenerator/generator/SeniorHighSchoolGenerator.java
@@ -6,35 +6,40 @@ import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;
/**
- * 高中选择题生成器 (已修正)。
- * 通过在初中题目的基础上添加值为整数的三角函数项,确保最终解为整数。
+ * 高中选择题生成器 (最终版 - 采用构造式添加)。
+ * 通过在已生成的初中题基础上添加预设的三角函数项来构造题目。
*/
public class SeniorHighSchoolGenerator extends JuniorHighSchoolGenerator {
- // 只使用值为整数的三角函数
- private static final Map TRIG_TERMS = Map.of(
- "sin(90°)", 1,
- "cos(0°)", 1,
- "tan(45°)", 1
+ // 预设计算结果简单的三角函数项及其对应的值
+ private static final Map TRIG_TERMS = Map.of(
+ "sin(0°)", 0.0,
+ "sin(30°)", 0.5,
+ "sin(90°)", 1.0,
+ "cos(0°)", 1.0,
+ "cos(60°)", 0.5,
+ "cos(90°)", 0.0,
+ "tan(45°)", 1.0
);
+ // 将Map的键转换为数组,方便随机选取
private static final String[] TRIG_KEYS = TRIG_TERMS.keySet().toArray(new String[0]);
@Override
public ChoiceQuestion generateSingleQuestion() {
- // 1. 先生成一个保证有整数解的初中选择题
+ // 1. 先生成一个保证可计算的、高性能的初中选择题,作为基础
ChoiceQuestion juniorHighQuestion = super.generateSingleQuestion();
String juniorQuestionText = juniorHighQuestion.questionText();
- int juniorCorrectAnswer = Integer.parseInt(juniorHighQuestion.options().get(juniorHighQuestion.correctOptionIndex()));
+ // 直接从初中题的答案反推其数值
+ double juniorCorrectAnswer = Double.parseDouble(juniorHighQuestion.options().get(juniorHighQuestion.correctOptionIndex()));
- // 2. 随机选择一个值为整数的三角函数项
+ // 2. 随机选择一个预设的三角函数项
String trigKey = TRIG_KEYS[ThreadLocalRandom.current().nextInt(TRIG_KEYS.length)];
- int trigValue = TRIG_TERMS.get(trigKey);
-
- // 3. 随机决定是加还是减
- boolean useAddition = ThreadLocalRandom.current().nextBoolean();
+ double trigValue = TRIG_TERMS.get(trigKey);
+ // 3. 构造最终的高中题目
String finalQuestionText;
- int finalCorrectAnswer;
+ double finalCorrectAnswer;
+ boolean useAddition = ThreadLocalRandom.current().nextBoolean(); // 随机决定是加还是减
if (useAddition) {
finalQuestionText = "(" + juniorQuestionText + ") + " + trigKey;
@@ -45,8 +50,8 @@ public class SeniorHighSchoolGenerator extends JuniorHighSchoolGenerator {
}
// 4. 为最终的题目生成选项
- List options = generateOptions(finalCorrectAnswer);
- int correctIndex = options.indexOf(String.valueOf(finalCorrectAnswer));
+ List options = generateDecimalOptions(finalCorrectAnswer);
+ int correctIndex = options.indexOf(formatNumber(finalCorrectAnswer));
return new ChoiceQuestion(finalQuestionText, options, correctIndex);
}