diff --git a/src/by/alexbiliba/quizer/Main.java b/src/by/alexbiliba/quizer/Main.java new file mode 100644 index 0000000..236b3fd --- /dev/null +++ b/src/by/alexbiliba/quizer/Main.java @@ -0,0 +1,93 @@ +package by.alexbiliba.quizer; + +import by.alexbiliba.quizer.tasks.TextTask; +import by.alexbiliba.quizer.task_generators.GroupTaskGenerator; +import by.alexbiliba.quizer.task_generators.PoolTaskGenerator; +import by.alexbiliba.quizer.tasks.math_tasks.EquationTask; +import by.alexbiliba.quizer.tasks.math_tasks.ExpressionTask; +import by.alexbiliba.quizer.tasks.math_tasks.MathTask; + +import java.util.*; +import java.util.concurrent.ThreadLocalRandom; + +public class Main { + /** + * @return тесты в {@link Map}, где + * ключ - название теста {@link String} + * значение - сам тест {@link Quiz} + */ + static Map getQuizMap() { + Map quizzes = new HashMap(); + + EnumSet enumSet1; + enumSet1 = EnumSet.of(MathTask.Operation.SUM, + MathTask.Operation.DIFFERENCE, + MathTask.Operation.MULTIPLICATION, + MathTask.Operation.DIVISION); + quizzes.put("Only expressions", new Quiz(new ExpressionTask.Generator(0, 1000, 3, enumSet1), 10)); + + EnumSet enumSet2; + enumSet2 = EnumSet.of(MathTask.Operation.SUM, + MathTask.Operation.MULTIPLICATION, + MathTask.Operation.DIVISION); + quizzes.put("Only equations", new Quiz(new EquationTask.Generator(0, 2000, 2, enumSet2), 10)); + + EnumSet enumSet3; + enumSet3 = EnumSet.of( + MathTask.Operation.MULTIPLICATION, + MathTask.Operation.DIVISION); + quizzes.put("Mixed math tasks", new Quiz(new GroupTaskGenerator(new ExpressionTask.Generator(0, 1000, 5, enumSet3), + new EquationTask.Generator(0, 1000, 6, enumSet3)), 10)); + + // Заготовка текстовых заданий + TextTask task1 = new TextTask("Do you love Java?", "Yes"); + TextTask task2 = new TextTask("Do you love C++?", "Yes"); + TextTask task3 = new TextTask("Do you love Assembly?", "No....."); + TextTask task4 = new TextTask("Do you love Python?", "Maybe"); + TextTask task5 = new TextTask("Do you love WinAPI?", "No"); + PoolTaskGenerator textTaskGen = new PoolTaskGenerator(false, task1, task2, task3, task4, task5); + + EnumSet enumSet4; + enumSet4 = EnumSet.of(MathTask.Operation.SUM, + MathTask.Operation.DIFFERENCE); + quizzes.put("Mixed math tasks", new Quiz(new GroupTaskGenerator(textTaskGen, + new ExpressionTask.Generator(0, 1000, 10, enumSet4), + new EquationTask.Generator(0, 1000, 11, enumSet4)), 10)); + + quizzes.put("Only text", new Quiz(new PoolTaskGenerator(true, task1, task2, task3, task4, task5), 10)); + + return quizzes; + } + public static void main(String[] args) { + Map quizzes = getQuizMap(); + Scanner input = new Scanner(System.in); + String quizName = null; + while (true) { + System.out.print("Enter the quiz name: "); + quizName = input.nextLine(); + if (quizzes.containsKey(quizName)) { + break; + } + System.out.println("There is no quiz with this name!"); + } + System.out.flush(); + + Quiz mainQuiz = quizzes.get(quizName); + System.out.println("Quiz started!"); + int taskIndex = 1; + while (!mainQuiz.isFinished()) { + System.out.print(String.valueOf(taskIndex) + ") "); + Task currentTask = mainQuiz.nextTask(); + System.out.println(currentTask.getText()); + String userAnswer = input.nextLine(); + mainQuiz.provideAnswer(userAnswer); + taskIndex++; + } + System.out.println("Total:"); + System.out.println("Correct answers - " + String.valueOf(mainQuiz.AC_number)); + System.out.println("Wrong answers - " + String.valueOf(mainQuiz.WA_number)); + System.out.println("Incorrect input - " + String.valueOf(mainQuiz.II_number)); + System.out.println("Your score: " + mainQuiz.getMark() * 100. + "%"); + input.close(); + } +} diff --git a/src/by/alexbiliba/quizer/Quiz.java b/src/by/alexbiliba/quizer/Quiz.java new file mode 100644 index 0000000..b957639 --- /dev/null +++ b/src/by/alexbiliba/quizer/Quiz.java @@ -0,0 +1,91 @@ +package by.alexbiliba.quizer; + +import java.util.ArrayList; + +/** + * Class, который описывает один тест + */ +class Quiz { + + ArrayList tasks = new ArrayList<>(); + int current_task_index = -1; + + int AC_number = 0; + int WA_number = 0; + int II_number = 0; + + /** + * @param generator генератор заданий + * @param taskCount количество заданий в тесте + */ + Quiz(Task.Generator generator, int taskCount) { + while (taskCount > 0) { + Task new_task = generator.generate(); + tasks.add(new_task); + taskCount--; + } + } + + /** + * @return задание, повторный вызов вернет слелующее + * @see Task + */ + Task nextTask() { + current_task_index++; + Task current_task = tasks.get(current_task_index); + return current_task; + } + + /** + * Предоставить ответ ученика. Если результат {@link Result#INCORRECT_INPUT}, то счетчик неправильных + * ответов не увеличивается, а {@link #nextTask()} в следующий раз вернет тот же самый объект {@link Task}. + */ + Result provideAnswer(String answer) { + Task current_task = tasks.get(current_task_index); + Result current_result = current_task.validate(answer); + if (current_result == Result.OK) { + AC_number++; + } else if (current_result == Result.WRONG) { + WA_number++; + } else { + II_number++; + } + return current_result; + } + + /** + * @return завершен ли тест + */ + boolean isFinished() { + return current_task_index >= tasks.size() - 1; + } + + /** + * @return количество правильных ответов + */ + int getCorrectAnswerNumber() { + return AC_number; + } + + /** + * @return количество неправильных ответов + */ + int getWrongAnswerNumber() { + return WA_number; + } + + /** + * @return количество раз, когда был предоставлен неправильный ввод + */ + int getIncorrectInputNumber() { + return II_number; + } + + /** + * @return оценка, которая является отношением количества правильных ответов к количеству всех вопросов. + * Оценка выставляется только в конце! + */ + double getMark() { + return ((double) AC_number) / ((double) tasks.size()); + } +} \ No newline at end of file diff --git a/src/by/alexbiliba/quizer/Result.java b/src/by/alexbiliba/quizer/Result.java new file mode 100644 index 0000000..c811d42 --- /dev/null +++ b/src/by/alexbiliba/quizer/Result.java @@ -0,0 +1,10 @@ +package by.alexbiliba.quizer; + +/** + * Enum, который описывает результат ответа на задание + */ +public enum Result { + OK, // Получен правильный ответ + WRONG, // Получен неправильный ответ + INCORRECT_INPUT // Некорректный ввод. Например, текст, когда ожидалось число +} \ No newline at end of file diff --git a/src/by/alexbiliba/quizer/Task.java b/src/by/alexbiliba/quizer/Task.java new file mode 100644 index 0000000..d97b204 --- /dev/null +++ b/src/by/alexbiliba/quizer/Task.java @@ -0,0 +1,30 @@ +package by.alexbiliba.quizer; + +/** + * Interface, который описывает одно задание + */ +public interface Task { + public interface Generator { + /* + * Возвращает задание. При этом новый объект может не создаваться, если класс задания иммутабельный + * + * @return задание + * @see Task + */ + Task generate(); + } + /* + @return текст задания + */ + String getText(); + String getAnswer(); + + /* + * Проверяет ответ на задание и возвращает результат + * + * @param answer ответ на задание + * @return результат ответа + * @see Result + */ + Result validate(String answer); +} \ No newline at end of file diff --git a/src/by/alexbiliba/quizer/task_generators/GroupTaskGenerator.java b/src/by/alexbiliba/quizer/task_generators/GroupTaskGenerator.java new file mode 100644 index 0000000..1a16a67 --- /dev/null +++ b/src/by/alexbiliba/quizer/task_generators/GroupTaskGenerator.java @@ -0,0 +1,47 @@ +package by.alexbiliba.quizer.task_generators; + +import by.alexbiliba.quizer.*; + +import java.util.*; + +import java.util.concurrent.ThreadLocalRandom; + +public class GroupTaskGenerator implements Task.Generator { + Collection generators = null; + + /** + * Конструктор с переменным числом аргументов + * + * @param generators генераторы, которые в конструктор передаются через запятую + */ + public GroupTaskGenerator(Task.Generator... generators) { + this.generators = new ArrayList(); + this.generators.addAll(Arrays.asList(generators)); + } + + /** + * Конструктор, который принимает коллекцию генераторов + * + * @param generators генераторы, которые передаются в конструктор в Collection (например, {@link ArrayList}) + */ + GroupTaskGenerator(Collection generators) { + this.generators = generators; + } + + /** + * @return результат метода generate() случайного генератора из списка. + * Если этот генератор выбросил исключение в методе generate(), выбирается другой. + * Если все генераторы выбрасывают исключение, то и тут выбрасывается исключение. + */ + public Task generate() { + int generatorIndex = ThreadLocalRandom.current().nextInt(0, generators.size()); + int currentGeneratorIndex = 0; + for (Task.Generator generator : generators) { + if (generatorIndex == currentGeneratorIndex) { + return generator.generate(); + } + currentGeneratorIndex++; + } + return null; + } +} \ No newline at end of file diff --git a/src/by/alexbiliba/quizer/task_generators/PoolTaskGenerator.java b/src/by/alexbiliba/quizer/task_generators/PoolTaskGenerator.java new file mode 100644 index 0000000..51d3c01 --- /dev/null +++ b/src/by/alexbiliba/quizer/task_generators/PoolTaskGenerator.java @@ -0,0 +1,79 @@ +package by.alexbiliba.quizer.task_generators; + +import by.alexbiliba.quizer.*; +import java.util.*; +import java.util.concurrent.ThreadLocalRandom; + +public class PoolTaskGenerator implements Task.Generator { + boolean allowDuplicate; + int tasksAllow; + Collection tasks = null; + ArrayList task_used = new ArrayList<>(); + + /** + * Конструктор с переменным числом аргументов + * + * @param allowDuplicate разрешить повторения + * @param tasks задания, которые в конструктор передаются через запятую + */ + public PoolTaskGenerator( + boolean allowDuplicate, + Task... tasks + ) { + tasksAllow = tasks.length; + this.allowDuplicate = allowDuplicate; + this.tasks = new ArrayList(); + this.tasks.addAll(Arrays.asList(tasks)); + for (int i = 0; i < tasks.length; i++) { + task_used.add(false); + } + } + + /** + * Конструктор, который принимает коллекцию заданий + * + * @param allowDuplicate разрешить повторения + * @param tasks задания, которые передаются в конструктор в Collection (например, {@link LinkedList}) + */ + PoolTaskGenerator( + boolean allowDuplicate, + Collection tasks + ) { + tasksAllow = tasks.size(); + this.allowDuplicate = allowDuplicate; + this.tasks = tasks; + for (int i = 0; i < tasks.size(); i++) { + task_used.add(false); + } + } + + /** + * @return случайная задача из списка + */ + public Task generate() { + Task newTask = null; + int taskIndex = 0; + if (allowDuplicate) { + taskIndex = ThreadLocalRandom.current().nextInt(0, tasks.size()); + } else { + if (tasksAllow == 0) { + return null; + } + while (task_used.get(taskIndex)) { + taskIndex = ThreadLocalRandom.current().nextInt(0, tasks.size()); + } + tasksAllow--; + task_used.set(taskIndex, true); + } + + int currentTaskIndex = 0; + for (Task task : tasks) { + if (taskIndex == currentTaskIndex) { + newTask = task; + break; + } + currentTaskIndex++; + } + return newTask; + } +} diff --git a/src/by/alexbiliba/quizer/tasks/TextTask.java b/src/by/alexbiliba/quizer/tasks/TextTask.java new file mode 100644 index 0000000..c1bced6 --- /dev/null +++ b/src/by/alexbiliba/quizer/tasks/TextTask.java @@ -0,0 +1,43 @@ +package by.alexbiliba.quizer.tasks; + +import by.alexbiliba.quizer.*; +import by.alexbiliba.quizer.task_generators.PoolTaskGenerator; + +/** + * Задание с заранее заготовленным текстом. + * Можно использовать {@link PoolTaskGenerator}, чтобы задавать задания такого типа. + */ +public class TextTask implements Task { + String text; + String answer; + + /** + * @param text текст задания + * @param answer ответ на задание + */ + public TextTask( + String text, + String answer + ) { + this.text = text; + this.answer = answer; + } + + public String getAnswer() { + return answer; + } + + @Override + public String getText() { + return text; + } + + @Override + public Result validate(String answer) { + if (answer.equals(this.answer)) { + return Result.OK; + } else { + return Result.WRONG; + } + } +} diff --git a/src/by/alexbiliba/quizer/tasks/math_tasks/AbstractMathTask.java b/src/by/alexbiliba/quizer/tasks/math_tasks/AbstractMathTask.java new file mode 100644 index 0000000..bcd75a2 --- /dev/null +++ b/src/by/alexbiliba/quizer/tasks/math_tasks/AbstractMathTask.java @@ -0,0 +1,235 @@ +package by.alexbiliba.quizer.tasks.math_tasks; + +import by.alexbiliba.quizer.*; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.EnumSet; +import java.util.concurrent.ThreadLocalRandom; + +public class AbstractMathTask implements MathTask { + + static class Generator implements MathTask.Generator { + protected double minNumber; + protected double maxNumber; + protected int precision = 0; + protected int answerPosition = 0; + private int startPosition; + private int boundaryForPosition; + protected EnumSet allowedOperations; + + /** + * @param minNumber минимальное число + * @param maxNumber максимальное число + * @param allowedOperations разрешённые операторы для генерации (+, -, *, /) + */ + public Generator( + double minNumber, + double maxNumber, + int precision, + int startPosition, + int boundaryForPosition, + EnumSet allowedOperations + ) { + this.minNumber = minNumber; + this.maxNumber = maxNumber; + this.precision = precision; + this.allowedOperations = allowedOperations; + } + + public int getAnswerPosition() { + return answerPosition; + } + + public AbstractMathTask generate() { + double leftNumber = ThreadLocalRandom.current().nextDouble(minNumber, + maxNumber); + double rightNumber = ThreadLocalRandom.current().nextDouble(minNumber, + maxNumber); + double resultNumber = ThreadLocalRandom.current().nextDouble(minNumber, + maxNumber); + int operationIndex = ThreadLocalRandom.current().nextInt(0, + allowedOperations.size()); + + int index = 0; + MathTask.Operation operation = null; + for (MathTask.Operation currentOperation : allowedOperations) { + if (index == operationIndex) { + operation = currentOperation; + break; + } + index++; + } + + answerPosition = ThreadLocalRandom.current().nextInt(startPosition, + boundaryForPosition + 1); + + if (operation == MathTask.Operation.SUM) { + if (answerPosition == 0) { + leftNumber = resultNumber - rightNumber; + } else if (answerPosition == 1) { + rightNumber = resultNumber - leftNumber; + } else { + resultNumber = leftNumber + rightNumber; + } + } else if (operation == MathTask.Operation.DIFFERENCE) { + leftNumber = resultNumber + rightNumber; + if (answerPosition == 0) { + leftNumber = resultNumber + rightNumber; + } else if (answerPosition == 1) { + rightNumber = leftNumber - resultNumber; + } else { + resultNumber = leftNumber - rightNumber; + } + } else { + while (rightNumber < 1e-10) { + rightNumber = ThreadLocalRandom.current().nextDouble(minNumber, + maxNumber); + } + if (operation == MathTask.Operation.MULTIPLICATION) { + leftNumber = resultNumber / rightNumber; + if (answerPosition == 0) { + leftNumber = resultNumber / rightNumber; + } else if (answerPosition == 1) { + rightNumber = resultNumber / leftNumber; + } else { + resultNumber = leftNumber * rightNumber; + } + } else if (operation == MathTask.Operation.DIVISION) { + leftNumber = resultNumber * rightNumber; + if (answerPosition == 0) { + leftNumber = resultNumber * rightNumber; + } else if (answerPosition == 1) { + rightNumber = leftNumber / resultNumber; + } else { + resultNumber = leftNumber / rightNumber; + } + } + } + + return new AbstractMathTask(leftNumber, rightNumber, resultNumber, operation, precision, 2); + } + + @Override + public double getMinNumber() { + return minNumber; + } + + @Override + public double getMaxNumber() { + return maxNumber; + } + } + + String text; + String answer; + + protected double leftNumber = 0; + protected double rightNumber = 0; + protected double resultNumber = 0; + protected MathTask.Operation operation = null; + protected int precision = 0; + private int answerPosition; + + public AbstractMathTask(String text, String answer) { + this.text = text; + this.answer = answer; + } + + public AbstractMathTask(double leftNumber, double rightNumber, double resultNumber, + Operation operation, int precision, int answerPosition) { + this.leftNumber = leftNumber; + this.rightNumber = rightNumber; + this.resultNumber = resultNumber; + this.operation = operation; + this.precision = precision; + this.answerPosition = answerPosition; + text = ""; + if (answerPosition == 0) { + text += 'x'; + answer = BigDecimal.valueOf(leftNumber) + .setScale(precision, RoundingMode.HALF_UP) + .toString(); + } else { + text += BigDecimal.valueOf(leftNumber) + .setScale(precision, RoundingMode.HALF_UP) + .toString(); + } + text += operationSymbol.get(operation); + + if (answerPosition == 1) { + text += 'x'; + answer = BigDecimal.valueOf(rightNumber) + .setScale(precision, RoundingMode.HALF_UP) + .toString(); + } else { + text += BigDecimal.valueOf(rightNumber) + .setScale(precision, RoundingMode.HALF_UP) + .toString(); + } + text += "="; + + if (answerPosition == 2) { + text += '?'; + answer = BigDecimal.valueOf(resultNumber) + .setScale(precision, RoundingMode.HALF_UP) + .toString(); + } else { + text += String.format("%." + String.valueOf(precision) + "f", resultNumber); + text += BigDecimal.valueOf(resultNumber) + .setScale(precision, RoundingMode.HALF_UP) + .toString(); + } + } + + public int getAnswerPosition() { + return answerPosition; + } + + @Override + public String getText() { + return text; + } + + public double getLeftNumber() { + return leftNumber; + } + + public double getRightNumber() { + return rightNumber; + } + + public double getResultNumber() { + return resultNumber; + } + + public int getPrecision() { + return precision; + } + + public MathTask.Operation getOperation() { + return operation; + } + + @Override + public Result validate(String answer) { + try { + answer = BigDecimal.valueOf(Double.parseDouble(answer)) + .setScale(precision, RoundingMode.HALF_UP) + .toString(); + double userAnswer = Double.parseDouble(answer); + double rightAnswer = Double.parseDouble(this.answer); + if (Math.abs(userAnswer - rightAnswer) < threshold) { + return Result.OK; + } else { + return Result.WRONG; + } + } catch (NumberFormatException e) { + return Result.INCORRECT_INPUT; + } + } + + public String getAnswer() { + return answer; + } +} diff --git a/src/by/alexbiliba/quizer/tasks/math_tasks/EquationTask.java b/src/by/alexbiliba/quizer/tasks/math_tasks/EquationTask.java new file mode 100644 index 0000000..fc440b6 --- /dev/null +++ b/src/by/alexbiliba/quizer/tasks/math_tasks/EquationTask.java @@ -0,0 +1,49 @@ +package by.alexbiliba.quizer.tasks.math_tasks; + +import by.alexbiliba.quizer.Result; +import by.alexbiliba.quizer.Task; + +import java.util.EnumSet; +import java.util.concurrent.ThreadLocalRandom; + +public class EquationTask extends AbstractMathTask { + public static class Generator extends AbstractMathTask.Generator { + + /** + * @param minNumber минимальное число + * @param maxNumber максимальное число + * @param allowedOperations разрешённые операторы для генерации (+, -, *, /) + */ + public Generator( + double minNumber, + double maxNumber, + int precision, + EnumSet allowedOperations + ) { + super(minNumber, maxNumber, precision, 2, 0, allowedOperations); + } + + /** + * return задание типа {@link EquationTask} + */ + public EquationTask generate() { + AbstractMathTask newTask = super.generate(); + return new EquationTask(newTask, newTask.getAnswerPosition()); + } + } + public String getAnswer() { + return answer; + } + public EquationTask(String text, String answer) { + super(text, answer); + } + + public EquationTask(AbstractMathTask taskData, int answerPosition) { + super(taskData.getLeftNumber(), + taskData.getRightNumber(), + taskData.getResultNumber(), + taskData.getOperation(), + taskData.getPrecision(), + answerPosition); + } +} diff --git a/src/by/alexbiliba/quizer/tasks/math_tasks/ExpressionTask.java b/src/by/alexbiliba/quizer/tasks/math_tasks/ExpressionTask.java new file mode 100644 index 0000000..5c1dfa8 --- /dev/null +++ b/src/by/alexbiliba/quizer/tasks/math_tasks/ExpressionTask.java @@ -0,0 +1,48 @@ +package by.alexbiliba.quizer.tasks.math_tasks; + +import by.alexbiliba.quizer.*; + +import java.util.EnumSet; + +public class ExpressionTask extends AbstractMathTask { + public static class Generator extends AbstractMathTask.Generator { + + /** + * @param minNumber минимальное число + * @param maxNumber максимальное число + * @param allowedOperations разрешённые операторы для генерации (+, -, *, /) + */ + public Generator( + double minNumber, + double maxNumber, + int precision, + EnumSet allowedOperations + ) { + super(minNumber, maxNumber, precision, 2, 0, allowedOperations); + } + + /** + * return задание типа {@link ExpressionTask} + */ + public ExpressionTask generate() { + AbstractMathTask newTask = super.generate(); + return new ExpressionTask(newTask); + } + } + + public ExpressionTask(String text, String answer) { + super(text, answer); + } + + public String getAnswer() { + return answer; + } + public ExpressionTask(AbstractMathTask taskData) { + super(taskData.getLeftNumber(), + taskData.getRightNumber(), + taskData.getResultNumber(), + taskData.getOperation(), + taskData.getPrecision(), + 2); + } +} diff --git a/src/by/alexbiliba/quizer/tasks/math_tasks/MathTask.java b/src/by/alexbiliba/quizer/tasks/math_tasks/MathTask.java new file mode 100644 index 0000000..a702e8b --- /dev/null +++ b/src/by/alexbiliba/quizer/tasks/math_tasks/MathTask.java @@ -0,0 +1,38 @@ +package by.alexbiliba.quizer.tasks.math_tasks; + +import by.alexbiliba.quizer.Task; + +import java.util.HashMap; + +public interface MathTask extends Task { + public interface Generator extends Task.Generator { + double getMinNumber(); // получить минимальное число + double getMaxNumber(); // получить максимальное число + /** + * @return разница между максимальным и минимальным возможным числом + */ + default double getDiffNumber() { + return getMaxNumber() - getMinNumber(); + } + } + + static double threshold = 1e-20; + double leftValue = 0; + double rightValue = 0; + double resultValue = 0; + + enum Operation { + SUM, + DIFFERENCE, + MULTIPLICATION, + DIVISION + } + + static HashMap operationSymbol = + new HashMap() {{ + put(Operation.SUM, '+'); + put(Operation.DIFFERENCE, '-'); + put(Operation.MULTIPLICATION, '*'); + put(Operation.DIVISION, '/'); + }}; +}