diff --git a/lab-01/ClassDiagram.png b/lab-01/ClassDiagram.png
new file mode 100644
index 0000000..19270f1
Binary files /dev/null and b/lab-01/ClassDiagram.png differ
diff --git a/lab-01/ClassDiagram.uml b/lab-01/ClassDiagram.uml
new file mode 100644
index 0000000..da4a89c
--- /dev/null
+++ b/lab-01/ClassDiagram.uml
@@ -0,0 +1,188 @@
+
+
+ JAVA
+
+
+ by.marmotikon.quizer.tasks.math_tasks.AnimalsTask
+ by.marmotikon.quizer.tasks.math_tasks.ApplesTask.ApplesTaskGenerator
+ by.marmotikon.quizer.tasks.math_tasks.MathTask
+ by.marmotikon.quizer.tasks.math_tasks.ExpressionTask
+ by.marmotikon.quizer.exceptions.EmptyTaskPoolException
+ by.marmotikon.quizer.tasks.math_tasks.EquationTask.EquationTaskGenerator
+ by.marmotikon.quizer.tasks.math_tasks.Number
+ by.marmotikon.quizer.task_generators.PoolTaskGenerator
+ by.marmotikon.quizer.Main
+ by.marmotikon.quizer.tasks.Task.TaskGenerator
+ by.marmotikon.quizer.tasks.math_tasks.AbstractMathTask.AbstractMathTaskGenerator
+ by.marmotikon.quizer.Quiz
+ by.marmotikon.quizer.tasks.TextTask
+ by.marmotikon.quizer.tasks.math_tasks.ExpressionTask.ExpressionTaskGenerator
+ by.marmotikon.quizer.task_generators.GroupTaskGenerator
+ by.marmotikon.quizer.Result
+ by.marmotikon.quizer.tasks.Task
+ by.marmotikon.quizer.tasks.math_tasks.ApplesTask
+ by.marmotikon.quizer.tasks.math_tasks.AbstractMathTask
+ by.marmotikon.quizer.exceptions.GroupTaskGeneratorException
+ by.marmotikon.quizer.tasks.math_tasks.MathTask.MathTaskGenerator
+ by.marmotikon.quizer.exceptions.QuizNotFinishedException
+ java.lang.Exception
+ by.marmotikon.quizer.tasks.math_tasks.EquationTask
+ by.marmotikon.quizer.tasks.math_tasks.MathTask.Operation
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Constructors
+ Fields
+ Inner Classes
+ Methods
+
+ All
+ private
+
+
diff --git a/lab-01/README.md b/lab-01/README.md
index ac1aae2..052ce00 100644
--- a/lab-01/README.md
+++ b/lab-01/README.md
@@ -3,7 +3,7 @@
Приложение должно функционировать следующим образом. Учитель врывается в класс и решает провести плановую проверку знаний, большими буквами на доске пишет название теста. Обрадованные прекрасной новостью, ученики, в свою очередь, охотно открывают разработанное вами приложение и вводят в _CLI_ название теста. Затем они предположительно самостоятельно отвечают на все вопросы, и в конце им выводится оценка.
-# Архитектура приложения
+# Архитектура приложения :heavy_check_mark:
>Всю работу ведите в пакете `by.<ваш ник>.quizer`
## Базовые элементы
@@ -23,21 +23,23 @@ enum Result {
### Task
```java
+import by.marmotikon.quizer.Result;
+
/**
* Interface, который описывает одно задание
*/
interface Task {
- /*
- @return текст задания
+ /**
+ * @return текст задания
*/
String getText();
-
- /*
+
+ /**
* Проверяет ответ на задание и возвращает результат
*
* @param answer ответ на задание
- * @return результат ответа
- * @see Result
+ * @return результат ответа
+ * @see Result
*/
Result validate(String answer);
}
@@ -46,11 +48,13 @@ interface Task {
### TaskGenerator
```java
+import by.marmotikon.quizer.tasks.Task;
+
/**
* Interface, который описывает один генератор заданий
*/
interface TaskGenerator {
- /*
+ /**
* Возвращает задание. При этом новый объект может не создаваться, если класс задания иммутабельный
*
* @return задание
@@ -61,7 +65,11 @@ interface TaskGenerator {
```
### Quiz
+
```java
+import by.marmotikon.quizer.Result;
+import by.marmotikon.quizer.tasks.Task;
+
/**
* Class, который описывает один тест
*/
@@ -70,10 +78,10 @@ class Quiz {
* @param generator генератор заданий
* @param taskCount количество заданий в тесте
*/
- Quiz(TaskGenerator generator, int taskCount) {
+ Quiz(TaskGenerator generator, int taskCount) {
// ...
}
-
+
/**
* @return задание, повторный вызов вернет слелующее
* @see Task
@@ -81,7 +89,7 @@ class Quiz {
Task nextTask() {
// ...
}
-
+
/**
* Предоставить ответ ученика. Если результат {@link Result#INCORRECT_INPUT}, то счетчик неправильных
* ответов не увеличивается, а {@link #nextTask()} в следующий раз вернет тот же самый объект {@link Task}.
@@ -89,35 +97,35 @@ class Quiz {
Result provideAnswer(String answer) {
// ...
}
-
+
/**
* @return завершен ли тест
*/
boolean isFinished() {
// ...
}
-
+
/**
* @return количество правильных ответов
*/
int getCorrectAnswerNumber() {
// ...
}
-
+
/**
* @return количество неправильных ответов
*/
int getWrongAnswerNumber() {
// ...
}
-
+
/**
* @return количество раз, когда был предоставлен неправильный ввод
*/
int getIncorrectInputNumber() {
// ...
}
-
+
/**
* @return оценка, которая является отношением количества правильных ответов к количеству всех вопросов.
* Оценка выставляется только в конце!
@@ -128,7 +136,7 @@ class Quiz {
}
```
-## Функция main()
+## Функция main()
### getQuizMap
Этот метод будет использоваться из `main()`, чтобы получить список доступных тестов. Создание всех тестов (`Quiz`) будет захардкожено в этом методе. После реализации разных TaskGenerator’ов (см. ниже), добавьте в этот метод несколько различных тестов.
@@ -330,7 +338,7 @@ class TextTask implements Task {
}
```
-## Добавляем абстракций
+## Добавляем абстракций :heavy_check_mark:
Для всего в этом пункте следует сделать пакет `math_tasks` в пакете `tasks` и `math_task_generators` в пакете `task_genertors`.
Сейчас `ExpressionTask` и `EquationTask` наследуются напрямую от `Task`. Введем несколько абстракций, чтобы вынести общую логику из этих двух классов.
@@ -355,13 +363,13 @@ int getMaxNumber(); // получить максимальное число
default int getDiffNumber();
```
-## ★ EnumSet
+## ★ EnumSet :heavy_check_mark:
Сейчас сигнатуры `ExpressionTaskGenerator` и `EquationTaskGenerator` выглядят не очень красиво, приходится передавать туда 4 була для каждого оператора. Сделайте `enum Operation` внутри интерфейса `MathTask` и передавайте в `ExpressionTaskGenerator` и `EquationTaskGenerator` вместо булов `EnumSet`.
>https://www.baeldung.com/java-enumset
-## Generator как nested class в Task★
+## ★ Generator как nested class в Task :heavy_check_mark:
Сейчас вся иерархия `Task` дублируется и для `Generator` в отдельном пакете, это не очень хорошая практика, т. к. за таким кодом сложно следить. Давайте сделаем все `Generator`'ы внутреннеми классами в соответствующих `Task`. Например, вместо `ExpressionTaskGenerator` будет `ExpressionTask.Generator`. При этом `GroupTaskGenerator` и `PoolTaskGenerator` остануться в отдельном пакете, т.к. они не привязаны к конкретному типу задачи.
@@ -392,17 +400,17 @@ class EquationTask extends AbstractMathTask {
}
```
-## ★ Real ExpressionTask и EquationTask
+## ★ Real ExpressionTask и EquationTask :heavy_check_mark:
Сделайте, чтобы `ExpressionTaskGenerator` и `EquationTaskGenerator` работал с double вместо int. Так же нужно изменить сигнатуру в методах `getMinNumber()`, `getMaxNumnber()`, `getDiffNumber` интерфейса `MathTask`, чтобы они возвращали double.
Добавьте в `ExpressionTaskGenerator` и `EquationTaskGenerator` конструктор, который после `maxNumber` принимает еще `int precision` - количество знаков после запятой в генерируемых числах. В других конструкторах считается, что `precision = 0`, т.e. генерируются только целые числа.
->★★ Учитывайте precision еще и в ответе
+>★★ Учитывайте precision еще и в ответе :heavy_check_mark:
-## ★★ UML
+## ★★ UML :heavy_check_mark:
В каком-нибудь онлайн UML-редакторе (если очень хочется, можно и в пеинте) сделайте схему с участием всех интерфейсов и классов. Это схему нужно приложить в AnyTask к ссылке на github.
-## Добавляем исключения
+## Добавляем исключения :heavy_check_mark:
Добавьте исключения везде, где это необходимо. Например, когда у `Quiz` вызывается метод `getMark()`, пока тест на завершен или в конструктор `*MathTask` `maxNumber` передается меньше, чем `minNumber`, в `precision` передается некорректное число и т.д. В случае некорректных `minNumber`, `maxNumber`, `precision` уместно использовать `IllegalArgumentException`, для раннего вызова `getMark()` стоит сделать свое исключение, назвать `QuizNotFinishedException`. Все свои исключения стоит создавать в пакете `exceptions`.
-## Добавляем тесты (Quiz) и проверяем
+## Добавляем тесты (Quiz) и проверяем :heavy_check_mark:
Теперь добавьте в метод `getQuizMap()` тестов (минимум 5) и тщательно протестируйте приложение. Обязательно используйте все созданные классы, постарайтесь придумать свои `TaskGenerator`. Например, который генерирует задания вида _"У **A** было X яблок, он(она) подарил(а) **B** Y яблок. Сколько яблок осталось у **A**?"_.
diff --git a/lab-01/src/by/marmotikon/quizer/Main.java b/lab-01/src/by/marmotikon/quizer/Main.java
new file mode 100644
index 0000000..7bd4904
--- /dev/null
+++ b/lab-01/src/by/marmotikon/quizer/Main.java
@@ -0,0 +1,92 @@
+package by.marmotikon.quizer;
+
+import by.marmotikon.quizer.exceptions.QuizAlreadyFinishedException;
+import by.marmotikon.quizer.tasks.Task;
+import by.marmotikon.quizer.tasks.TextTask;
+import by.marmotikon.quizer.tasks.math_tasks.ApplesTask.ApplesTaskGenerator;
+import by.marmotikon.quizer.tasks.math_tasks.EquationTask.EquationTaskGenerator;
+import by.marmotikon.quizer.tasks.math_tasks.ExpressionTask.ExpressionTaskGenerator;
+import by.marmotikon.quizer.tasks.math_tasks.MathTask;
+import by.marmotikon.quizer.task_generators.GroupTaskGenerator;
+import by.marmotikon.quizer.task_generators.PoolTaskGenerator;
+
+import java.util.*;
+
+public class Main {
+ public static void main(String[] args) {
+ Map quizMap = getQuizMap();
+ Scanner sc = new Scanner(System.in);
+ System.out.println("Введите название теста...");
+ String enteredName = sc.nextLine();
+ while (!quizMap.containsKey(enteredName)) {
+ System.out.println("Неверное название теста. Попробуйте еще раз...");
+ enteredName = sc.nextLine();
+ }
+ Quiz currentQuiz = quizMap.get(enteredName);
+ while (!currentQuiz.isFinished()) {
+ try {
+ currentQuiz.nextTask();
+ } catch (QuizAlreadyFinishedException e) {
+ throw new RuntimeException(e);
+ }
+ System.out.println(currentQuiz.getText());
+ Result result = currentQuiz.provideAnswer(sc.next());
+ switch (result) {
+ case OK -> {
+ System.out.println("Правильно!");
+ }
+ case WRONG -> {
+ System.out.println("Неправильно. Правильный ответ: " + currentQuiz.getAnswer());
+ }
+ case INCORRECT_INPUT -> {
+ System.out.println("Ввод некорректный, попробуйте еще раз:");
+ }
+ }
+ }
+ System.out.println("Тест завершен. Оценка за тест : " + currentQuiz.getMark());
+ System.out.println("Правильных ответов : " + currentQuiz.getCorrectAnswerNumber());
+ System.out.println("Неправильных ответов : " + currentQuiz.getWrongAnswerNumber());
+ System.out.println("Некорректных вводов : " + currentQuiz.getIncorrectInputNumber());
+ }
+
+ /**
+ * @return тесты в {@link Map}, где
+ * ключ - название теста {@link String}
+ * значение - сам тест {@link Quiz}
+ */
+ static Map getQuizMap() {
+ Map quizMap = new HashMap<>();
+ ExpressionTaskGenerator expressionTaskGenerator = new ExpressionTaskGenerator(
+ 0, 10, 1, EnumSet.allOf(MathTask.Operation.class));
+
+ EquationTaskGenerator equationTaskGenerator = new EquationTaskGenerator(
+ 0, 10, 0, EnumSet.allOf(MathTask.Operation.class));
+
+ ApplesTaskGenerator applesTaskGenerator = new ApplesTaskGenerator(0, 10);
+
+ List animalsTaskPool = List.of(
+ new TextTask("Сколько ног у осминога?", "2"),
+ new TextTask("Сколько щупалец у осминога?", "8"),
+ new TextTask("Сколько сердец у осминога?", "3"),
+ new TextTask("Сколько мозгов у осминога?", "1"),
+ new TextTask("Сколько глаз у осминога?", "2"),
+ new TextTask("Сколько кг весил самый большой пойманный осминог?", "180"),
+ new TextTask("Сколько существует видов пингвинов?", "18"),
+ new TextTask("На сколько сотен метров в глубину максимально может нырнуть императорский пингвин?", "5"),
+ new TextTask("Сколько метров размах крыльев у альбатроса?", "3"),
+ new TextTask("Сколько глаз у пчелы?", "5"));
+
+ PoolTaskGenerator poolTaskGenerator = new PoolTaskGenerator(false, animalsTaskPool);
+
+ GroupTaskGenerator groupTaskGenerator = new GroupTaskGenerator(
+ expressionTaskGenerator, equationTaskGenerator, poolTaskGenerator, applesTaskGenerator);
+
+ quizMap.put("Expressions", new Quiz(expressionTaskGenerator, 10));
+ quizMap.put("Equations", new Quiz(equationTaskGenerator, 10));
+ quizMap.put("DifferentTasks", new Quiz(groupTaskGenerator, 10));
+ quizMap.put("Animal facts", new Quiz(new PoolTaskGenerator(false, animalsTaskPool), 10));
+ quizMap.put("ApplesTasks", new Quiz(applesTaskGenerator, 10));
+
+ return quizMap;
+ }
+}
diff --git a/lab-01/src/by/marmotikon/quizer/Quiz.java b/lab-01/src/by/marmotikon/quizer/Quiz.java
new file mode 100644
index 0000000..5c14a19
--- /dev/null
+++ b/lab-01/src/by/marmotikon/quizer/Quiz.java
@@ -0,0 +1,119 @@
+package by.marmotikon.quizer;
+
+import by.marmotikon.quizer.exceptions.QuizAlreadyFinishedException;
+import by.marmotikon.quizer.exceptions.QuizNotFinishedException;
+import by.marmotikon.quizer.tasks.Task;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Scanner;
+
+import static by.marmotikon.quizer.Result.*;
+
+/**
+ * Class, который описывает один тест
+ */
+class Quiz {
+// private final ArrayList tasks;
+ private Task currentTask;
+ private final Map Answers;
+// private int index = -1;
+ private boolean wasIncorrectInput = false;
+
+ private final Task.TaskGenerator generator;
+ private final int taskCount;
+
+
+ /**
+ * @param generator генератор заданий
+ * @param taskCount количество заданий в тесте
+ */
+ Quiz(Task.TaskGenerator generator, int taskCount) throws IllegalArgumentException{
+ if (taskCount <= 0) {
+ throw new IllegalArgumentException("can't create quiz with less then one task");
+ }
+ Answers = new HashMap<>(3);
+ Answers.put(OK, 0);
+ Answers.put(WRONG, 0);
+ Answers.put(INCORRECT_INPUT, 0);
+ this.generator = generator;
+ this.taskCount = taskCount;
+ }
+
+ /**
+ * @return задание, повторный вызов вернет следующее
+ * @see Task
+ */
+ void nextTask() throws QuizAlreadyFinishedException{
+ if (wasIncorrectInput) {
+ return;
+// return currentTask;
+ }
+ if (isFinished()) {
+ throw new QuizAlreadyFinishedException("trying to get new task when quiz is finished");
+ }
+ try {
+ currentTask = generator.generate();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+// return currentTask;
+ }
+
+ /**
+ * Предоставить ответ ученика. Если результат {@link Result#INCORRECT_INPUT}, то счетчик неправильных
+ * ответов не увеличивается, а {@link #nextTask()} в следующий раз вернет тот же самый объект {@link Task}.
+ */
+ Result provideAnswer(String answer) throws IllegalCallerException {
+ Result result = currentTask.validate(answer);
+ Answers.put(result, Answers.get(result) + 1);
+ wasIncorrectInput = result == INCORRECT_INPUT;
+ return result;
+ }
+
+ /**
+ * @return завершен ли тест
+ */
+ boolean isFinished() {
+ return Answers.get(OK) + Answers.get(WRONG) == taskCount;
+ }
+
+ /**
+ * @return количество правильных ответов
+ */
+ int getCorrectAnswerNumber() {
+ return Answers.get(OK);
+ }
+
+ /**
+ * @return количество неправильных ответов
+ */
+ int getWrongAnswerNumber() {
+ return Answers.get(WRONG);
+ }
+
+ /**
+ * @return количество раз, когда был предоставлен неправильный ввод
+ */
+ int getIncorrectInputNumber() {
+ return Answers.get(INCORRECT_INPUT);
+ }
+
+ final String getAnswer() {
+ return currentTask.getAnswerString();
+ }
+ final String getText() {
+ return currentTask.getText();
+ }
+
+ /**
+ * @return Оценка, которая является отношением количества правильных ответов к количеству всех вопросов.
+ * Оценка выставляется только в конце!
+ */
+ double getMark() throws QuizNotFinishedException{
+ if (!isFinished()) {
+ throw new QuizNotFinishedException("trying to get mark before finishing quiz");
+ }
+ return (float) (Answers.get(OK) * 10) / taskCount;
+ }
+}
diff --git a/lab-01/src/by/marmotikon/quizer/Result.java b/lab-01/src/by/marmotikon/quizer/Result.java
new file mode 100644
index 0000000..4ed7fae
--- /dev/null
+++ b/lab-01/src/by/marmotikon/quizer/Result.java
@@ -0,0 +1,10 @@
+package by.marmotikon.quizer;
+
+/**
+ * Enum, который описывает результат ответа на задание
+ */
+public enum Result {
+ OK, // Получен правильный ответ
+ WRONG, // Получен неправильный ответ
+ INCORRECT_INPUT // Некорректный ввод. Например, текст, когда ожидалось число
+}
diff --git a/lab-01/src/by/marmotikon/quizer/exceptions/EmptyTaskPoolException.java b/lab-01/src/by/marmotikon/quizer/exceptions/EmptyTaskPoolException.java
new file mode 100644
index 0000000..ca2b0f9
--- /dev/null
+++ b/lab-01/src/by/marmotikon/quizer/exceptions/EmptyTaskPoolException.java
@@ -0,0 +1,7 @@
+package by.marmotikon.quizer.exceptions;
+
+public class EmptyTaskPoolException extends RuntimeException{
+ public EmptyTaskPoolException(String message) {
+ super(message);
+ }
+}
diff --git a/lab-01/src/by/marmotikon/quizer/exceptions/GroupTaskGeneratorException.java b/lab-01/src/by/marmotikon/quizer/exceptions/GroupTaskGeneratorException.java
new file mode 100644
index 0000000..374bc19
--- /dev/null
+++ b/lab-01/src/by/marmotikon/quizer/exceptions/GroupTaskGeneratorException.java
@@ -0,0 +1,7 @@
+package by.marmotikon.quizer.exceptions;
+
+public class GroupTaskGeneratorException extends RuntimeException{
+ public GroupTaskGeneratorException(String message) {
+ super(message);
+ }
+}
diff --git a/lab-01/src/by/marmotikon/quizer/exceptions/QuizAlreadyFinishedException.java b/lab-01/src/by/marmotikon/quizer/exceptions/QuizAlreadyFinishedException.java
new file mode 100644
index 0000000..10228d1
--- /dev/null
+++ b/lab-01/src/by/marmotikon/quizer/exceptions/QuizAlreadyFinishedException.java
@@ -0,0 +1,7 @@
+package by.marmotikon.quizer.exceptions;
+
+public class QuizAlreadyFinishedException extends Exception{
+ public QuizAlreadyFinishedException(String message) {
+ super(message);
+ }
+}
diff --git a/lab-01/src/by/marmotikon/quizer/exceptions/QuizNotFinishedException.java b/lab-01/src/by/marmotikon/quizer/exceptions/QuizNotFinishedException.java
new file mode 100644
index 0000000..b5a788c
--- /dev/null
+++ b/lab-01/src/by/marmotikon/quizer/exceptions/QuizNotFinishedException.java
@@ -0,0 +1,7 @@
+package by.marmotikon.quizer.exceptions;
+
+public class QuizNotFinishedException extends RuntimeException {
+ public QuizNotFinishedException(String message) {
+ super(message);
+ }
+}
diff --git a/lab-01/src/by/marmotikon/quizer/task_generators/GroupTaskGenerator.java b/lab-01/src/by/marmotikon/quizer/task_generators/GroupTaskGenerator.java
new file mode 100644
index 0000000..b09bb7e
--- /dev/null
+++ b/lab-01/src/by/marmotikon/quizer/task_generators/GroupTaskGenerator.java
@@ -0,0 +1,55 @@
+package by.marmotikon.quizer.task_generators;
+
+import by.marmotikon.quizer.exceptions.GroupTaskGeneratorException;
+import by.marmotikon.quizer.tasks.Task;
+
+import java.util.*;
+
+public class GroupTaskGenerator implements Task.TaskGenerator {
+ private final ArrayList generators;
+ /**
+ * Конструктор с переменным числом аргументов
+ *
+ * @param generators генераторы, которые в конструктор передаются через запятую
+ */
+ public GroupTaskGenerator(Task.TaskGenerator... generators) {
+ if (generators.length == 0) {
+ throw new IllegalArgumentException("no generators are given to GroupTaskGenerator");
+ }
+ this.generators = new ArrayList<>(List.of(generators));
+ }
+
+ /**
+ * Конструктор, который принимает коллекцию генераторов
+ *
+ * @param generators генераторы, которые передаются в конструктор в Collection (например, {@link ArrayList})
+ */
+ GroupTaskGenerator(Collection generators) {
+ if (generators.size() == 0) {
+ throw new IllegalArgumentException("no generators are given to GroupTaskGenerator");
+ }
+ this.generators = new ArrayList<>(generators);
+ }
+
+ /**
+ * @return Результат метода generate() случайного генератора из списка.
+ * Если этот генератор выбросил исключение в методе generate(), выбирается другой.
+ * Если все генераторы выбрасывают исключение, то и тут выбрасывается исключение.
+ */
+ public Task generate() throws GroupTaskGeneratorException {
+ Random random = new Random();
+ int index = random.nextInt(generators.size());
+ int index_copy = index;
+ while (true) {
+ try {
+ return generators.get(index).generate();
+ } catch (Exception e) {
+ index %= generators.size();
+ index++;
+ if (index == index_copy) {
+ throw new GroupTaskGeneratorException("All generators throw exception");
+ }
+ }
+ }
+ }
+}
diff --git a/lab-01/src/by/marmotikon/quizer/task_generators/PoolTaskGenerator.java b/lab-01/src/by/marmotikon/quizer/task_generators/PoolTaskGenerator.java
new file mode 100644
index 0000000..1b5d330
--- /dev/null
+++ b/lab-01/src/by/marmotikon/quizer/task_generators/PoolTaskGenerator.java
@@ -0,0 +1,65 @@
+package by.marmotikon.quizer.task_generators;
+
+import by.marmotikon.quizer.exceptions.EmptyTaskPoolException;
+import by.marmotikon.quizer.tasks.Task;
+import by.marmotikon.quizer.tasks.Task.TaskGenerator;
+
+import java.util.*;
+
+public class PoolTaskGenerator implements TaskGenerator {
+ private final boolean allowDuplicate;
+ private List tasks;
+ private final Random random = new Random();
+
+ /**
+ * Конструктор с переменным числом аргументов
+ *
+ * @param allowDuplicate разрешить повторения
+ * @param tasks задания, которые в конструктор передаются через запятую
+ */
+ PoolTaskGenerator(boolean allowDuplicate, Task... tasks) {
+ this(allowDuplicate, List.of(tasks));
+ }
+
+ /**
+ * Конструктор, который принимает коллекцию заданий
+ *
+ * @param allowDuplicate разрешить повторения
+ * @param tasks задания, которые передаются в конструктор в Collection (например, {@link LinkedList})
+ */
+ public PoolTaskGenerator(boolean allowDuplicate, Collection tasks) {
+ this.allowDuplicate = allowDuplicate;
+ this.tasks = new ArrayList<>(tasks.stream().toList());
+ if (!allowDuplicate) {
+ this.tasks = new ArrayList<>();
+ for(var task1 : tasks) {
+ boolean isDuplicate = false;
+ for(var task2 : this.tasks) {
+ if (task1.equals(task2)) {
+ isDuplicate = true;
+ break;
+ }
+ }
+ if (!isDuplicate) {
+ this.tasks.add(task1);
+ }
+ }
+ }
+ Collections.shuffle(this.tasks);
+ }
+
+ /**
+ * @return случайная задача из списка
+ */
+ public Task generate() {
+ if (tasks.isEmpty()) {
+ throw new EmptyTaskPoolException("trying to generate more tasks than given to PoolTaskGenerator with banned duplicates");
+ }
+ int indexOfTask = random.nextInt(tasks.size());
+ Task task = tasks.get(indexOfTask).copy();
+ if (!allowDuplicate) {
+ tasks.remove(indexOfTask);
+ }
+ return task;
+ }
+}
\ No newline at end of file
diff --git a/lab-01/src/by/marmotikon/quizer/tasks/Task.java b/lab-01/src/by/marmotikon/quizer/tasks/Task.java
new file mode 100644
index 0000000..7f88e3e
--- /dev/null
+++ b/lab-01/src/by/marmotikon/quizer/tasks/Task.java
@@ -0,0 +1,44 @@
+package by.marmotikon.quizer.tasks;
+
+import by.marmotikon.quizer.Result;
+
+/**
+ * Interface, который описывает одно задание
+ */
+public interface Task {
+ /**
+ * Interface, который описывает один генератор заданий
+ */
+ interface TaskGenerator {
+ /**
+ * Возвращает задание. При этом новый объект может не создаваться, если класс задания иммутабельный
+ *
+ * @return задание
+ * @see Task
+ */
+ Task generate() throws Exception;
+ }
+
+ /**
+ *@return текст задания
+ */
+ String getText();
+
+ /**
+ *@return ответ на задание
+ */
+ String getAnswerString();
+
+ /**
+ * Проверяет ответ на задание и возвращает результат
+ *
+ * @param answer ответ на задание
+ * @return результат ответа
+ * @see Result
+ */
+ Result validate(String answer);
+
+ boolean equals(Task other);
+
+ Task copy();
+}
diff --git a/lab-01/src/by/marmotikon/quizer/tasks/TextTask.java b/lab-01/src/by/marmotikon/quizer/tasks/TextTask.java
new file mode 100644
index 0000000..a2974ad
--- /dev/null
+++ b/lab-01/src/by/marmotikon/quizer/tasks/TextTask.java
@@ -0,0 +1,58 @@
+package by.marmotikon.quizer.tasks;
+
+import by.marmotikon.quizer.Result;
+import by.marmotikon.quizer.task_generators.PoolTaskGenerator;
+import by.marmotikon.quizer.tasks.math_tasks.AbstractMathTask;
+
+/**
+ * Задание с заранее заготовленным текстом.
+ * Можно использовать {@link PoolTaskGenerator}, чтобы задавать задания такого типа.
+ */
+public class TextTask implements Task {
+ String text;
+ String answer;
+
+ @Override
+ public String getAnswerString() {
+ return answer;
+ }
+
+ /**
+ * @param text текст задания
+ * @param answer ответ на задание
+ */
+ public TextTask(String text, String answer) {
+ this.text = text;
+ this.answer = answer;
+ }
+
+ /**
+ * @param other копируемое задание
+ */
+ public TextTask(TextTask other) {
+ this.text = other.text;
+ this.answer = other.answer;
+ }
+
+ @Override
+ public String getText() {
+ return text;
+ }
+
+ @Override
+ public Result validate(String answer) {
+ return (answer.equalsIgnoreCase(this.answer) ? Result.OK : Result.WRONG);
+ }
+
+ @Override
+ public boolean equals(Task other) {
+ if (this == other) return true;
+ if (!(other instanceof TextTask otherTextTask)) return false;
+ return text.equals(otherTextTask.getText()) && answer.equals(otherTextTask.getAnswerString());
+ }
+
+ @Override
+ public Task copy() {
+ return new TextTask(text, answer);
+ }
+}
diff --git a/lab-01/src/by/marmotikon/quizer/tasks/math_tasks/AbstractMathTask.java b/lab-01/src/by/marmotikon/quizer/tasks/math_tasks/AbstractMathTask.java
new file mode 100644
index 0000000..53f750a
--- /dev/null
+++ b/lab-01/src/by/marmotikon/quizer/tasks/math_tasks/AbstractMathTask.java
@@ -0,0 +1,104 @@
+package by.marmotikon.quizer.tasks.math_tasks;
+
+import by.marmotikon.quizer.Result;
+import by.marmotikon.quizer.tasks.Task;
+
+import java.util.EnumSet;
+import java.util.Random;
+
+public abstract class AbstractMathTask implements MathTask {
+ public abstract static class AbstractMathTaskGenerator implements MathTaskGenerator {
+ private final double minNumber;
+ private final double maxNumber;
+ protected final int precision;
+ protected final EnumSet generateOperations;
+ protected final Random random = new Random();
+
+ /**
+ * @param minNumber минимальное число
+ * @param maxNumber максимальное число
+ * @param precision количество знаков после запятой в генерируемых числах
+ * @param generateOperations разрешить генерацию с данными операторами
+ **/
+ public AbstractMathTaskGenerator(
+ double minNumber,
+ double maxNumber,
+ int precision,
+ EnumSet generateOperations
+ ) throws IllegalCallerException {
+ if (maxNumber < minNumber) {
+ throw new IllegalArgumentException("can not generate any math task with minNumber > maxNumber");
+ }
+ if (precision < 0) {
+ throw new IllegalArgumentException("can not generate any math task with invalid precision < 0");
+ }
+ this.minNumber = minNumber;
+ this.maxNumber = maxNumber;
+ this.precision = precision;
+ this.generateOperations = generateOperations;
+ }
+
+ @Override
+ public double getMinNumber() {
+ return minNumber;
+ }
+
+ @Override
+ public double getMaxNumber() {
+ return maxNumber;
+ }
+
+ protected Number generateNumber() {
+ return new Number(getDiffNumber() * random.nextDouble() + getMinNumber(), precision);
+ }
+
+ }
+
+ protected final String statement;
+ protected final Number answer;
+
+
+ /**
+ * @param statement текст задания
+ * @param answer ответ на задание
+ * @see Number
+ */
+ public AbstractMathTask(String statement, Number answer) {
+ this.statement = statement;
+ this.answer = answer;
+ }
+
+ /**
+ * @param other копируемое задание
+ */
+ public AbstractMathTask(AbstractMathTask other) {
+ this.statement = other.statement;
+ this.answer = other.answer;
+ }
+
+ @Override
+ public String getText() {
+ return statement;
+ }
+
+ @Override
+ public String getAnswerString() {
+ return String.valueOf(answer);
+ }
+
+ public Number getAnswer() {
+ return answer;
+ }
+
+ @Override
+ public Result validate(String answer) {
+ return this.answer.provideAnswer(answer);
+ }
+
+ @Override
+ public boolean equals(Task other) {
+ if (this == other) return true;
+ if (!(other instanceof AbstractMathTask otherMathTask)) return false;
+ return statement.equals(other.getText()) && answer.compareTo(otherMathTask.getAnswer()) == 0;
+ }
+}
diff --git a/lab-01/src/by/marmotikon/quizer/tasks/math_tasks/ApplesTask.java b/lab-01/src/by/marmotikon/quizer/tasks/math_tasks/ApplesTask.java
new file mode 100644
index 0000000..8ef410c
--- /dev/null
+++ b/lab-01/src/by/marmotikon/quizer/tasks/math_tasks/ApplesTask.java
@@ -0,0 +1,41 @@
+package by.marmotikon.quizer.tasks.math_tasks;
+
+import by.marmotikon.quizer.tasks.Task;
+
+public class ApplesTask extends AbstractMathTask{
+ public static class ApplesTaskGenerator extends AbstractMathTaskGenerator {
+
+ /**
+ * @param minNumber минимальное число
+ * @param maxNumber максимальное число
+ **/
+ public ApplesTaskGenerator(
+ double minNumber,
+ double maxNumber
+ ) {
+ super(minNumber, maxNumber, 0, null);
+ }
+ /**
+ * return задание типа {@link ApplesTask}
+ */
+ public ApplesTask generate() {
+ Number a = generateNumber();
+ Number b = generateNumber();
+ if (a.compareTo(b) < 0) {
+ a.swap(b);
+ }
+ return new ApplesTask("У A было " + a + " яблок, он(она) подарил(а) B "
+ + b + " яблок. Сколько яблок осталось у A?",
+ new Number(a.getValue() - b.getValue(), precision));
+ }
+
+ }
+ public ApplesTask(String statement, Number answer) {
+ super(statement, answer);
+ }
+
+ @Override
+ public Task copy() {
+ return new ApplesTask(super.statement, super.answer);
+ }
+}
diff --git a/lab-01/src/by/marmotikon/quizer/tasks/math_tasks/EquationTask.java b/lab-01/src/by/marmotikon/quizer/tasks/math_tasks/EquationTask.java
new file mode 100644
index 0000000..3396b0b
--- /dev/null
+++ b/lab-01/src/by/marmotikon/quizer/tasks/math_tasks/EquationTask.java
@@ -0,0 +1,116 @@
+package by.marmotikon.quizer.tasks.math_tasks;
+
+import by.marmotikon.quizer.tasks.Task;
+
+import java.util.EnumSet;
+
+public class EquationTask extends AbstractMathTask {
+ public static class EquationTaskGenerator extends AbstractMathTaskGenerator {
+ /**
+ * @param minNumber минимальное число
+ * @param maxNumber максимальное число
+ * @param generateOperations разрешить генерацию с данными операторами
+ **/
+ public EquationTaskGenerator (
+ double minNumber,
+ double maxNumber,
+ EnumSet generateOperations
+ ) throws IllegalArgumentException {
+ super(minNumber, maxNumber, 0, generateOperations);
+ if (generateOperations == null || generateOperations.isEmpty()) {
+ throw new IllegalArgumentException("can not generate any equation task without math operations");
+ }
+ }
+
+ /**
+ * @param minNumber минимальное число
+ * @param maxNumber максимальное число
+ * @param precision количество знаков после запятой в генерируемых числах
+ * @param generateOperations разрешить генерацию с данными операторами
+ **/
+ public EquationTaskGenerator(
+ double minNumber,
+ double maxNumber,
+ int precision,
+ EnumSet generateOperations
+ ) {
+ super(minNumber, maxNumber, precision, generateOperations);
+ }
+
+ /**
+ * return задание типа {@link EquationTask}
+ */
+ public EquationTask generate() {
+ Number a = generateNumber();
+ Number b = generateNumber();
+ boolean isLeftX = random.nextBoolean();
+ //noinspection OptionalGetWithoutIsPresent
+ MathTask.Operation chosenOperation = generateOperations
+ .stream()
+ .skip(random.nextInt(generateOperations.size()))
+ .findFirst().get();
+
+ String statement;
+ Number answer;
+ switch (chosenOperation) {
+ case SUM -> {
+ if (isLeftX) {
+ statement = "x + " + a + " = " + b;
+ } else {
+ statement = a + " + x = " + b;
+ }
+ answer = new Number(b.getValue() - a.getValue(), precision);
+ }
+ case DIFFERENCE -> {
+ if (isLeftX) {
+ statement = "x - " + a + " = " + b;
+ answer = new Number(b.getValue() + a.getValue(), precision);
+ } else {
+ statement = a + " - x = " + b;
+ answer = new Number(a.getValue() - b.getValue(), precision);
+ }
+ }
+ case MULTIPLICATION -> {
+ while (a.isZero()) {
+ a = generateNumber();
+ }
+ if (isLeftX) {
+ statement = "x * " + a.toInt() + " = " + new Number(b.getValue() * a.toInt(), precision);
+ } else {
+ statement = a.toInt() + " * x = " + new Number(b.getValue() * a.toInt(), precision);
+ }
+ answer = new Number(b);
+ }
+ case DIVISION -> {
+ while (a.isZero()) {
+ a = generateNumber();
+ }
+ if (isLeftX) {
+ statement = "x / " + a + " = " + b.toInt();
+ answer = new Number(a.getValue() * b.toInt(), precision);
+ } else {
+ while (b.isZero()) {
+ b = generateNumber();
+ }
+ statement = new Number(a.getValue() * b.toInt(), precision)
+ + " / x = " + b.toInt();
+ answer = new Number(a);
+ }
+ }
+ default -> {
+ throw new RuntimeException("wtf operation enum has invalid values");
+ }
+ }
+ return new EquationTask(statement, answer);
+ }
+ }
+
+ public EquationTask(String statement, Number answer) {
+ super(statement, answer);
+ }
+
+ @Override
+ public Task copy() {
+ return new EquationTask(super.statement, super.answer);
+ }
+}
diff --git a/lab-01/src/by/marmotikon/quizer/tasks/math_tasks/ExpressionTask.java b/lab-01/src/by/marmotikon/quizer/tasks/math_tasks/ExpressionTask.java
new file mode 100644
index 0000000..b0352ea
--- /dev/null
+++ b/lab-01/src/by/marmotikon/quizer/tasks/math_tasks/ExpressionTask.java
@@ -0,0 +1,87 @@
+package by.marmotikon.quizer.tasks.math_tasks;
+
+import by.marmotikon.quizer.tasks.Task;
+
+import java.util.EnumSet;
+
+public class ExpressionTask extends AbstractMathTask {
+ public static class ExpressionTaskGenerator extends AbstractMathTaskGenerator {
+ /**
+ * @param minNumber минимальное число
+ * @param maxNumber максимальное число
+ * @param generateOperations разрешить генерацию с данными операторами
+ */
+ public ExpressionTaskGenerator(
+ double minNumber,
+ double maxNumber,
+ EnumSet generateOperations
+ ) throws IllegalArgumentException {
+ super(minNumber, maxNumber, 0, generateOperations);
+ if (generateOperations == null || generateOperations.isEmpty()) {
+ throw new IllegalArgumentException("can not generate any expression task without math operations");
+ }
+ }
+
+ /**
+ * @param minNumber минимальное число
+ * @param maxNumber максимальное число
+ * @param precision количество знаков после запятой в генерируемых числах
+ * @param generateOperations разрешить генерацию с данными операторами
+ **/
+ public ExpressionTaskGenerator(
+ double minNumber,
+ double maxNumber,
+ int precision,
+ EnumSet generateOperations
+ ) {
+ super(minNumber, maxNumber, precision, generateOperations);
+ }
+
+ /**
+ * return задание типа {@link ExpressionTask}
+ */
+ public ExpressionTask generate() {
+ Number a = generateNumber();
+ Number b = generateNumber();
+ //noinspection OptionalGetWithoutIsPresent
+ MathTask.Operation chosenOperation = generateOperations
+ .stream()
+ .skip(random.nextInt(generateOperations.size()))
+ .findFirst().get();
+ String statement;
+ Number answer;
+ switch (chosenOperation) {
+ case SUM -> {
+ statement = a + " + " + b + " = ?";
+ answer = new Number(a.getValue() + b.getValue(), precision);
+ }
+ case DIFFERENCE -> {
+ statement = a + " - " + b + " = ?";
+ answer = new Number(a.getValue() - b.getValue(), precision);
+ }
+ case MULTIPLICATION -> {
+ statement = a.toInt() + " * " + b + " = ?";
+ answer = new Number(b.getValue() * a.toInt(), precision);
+ }
+ case DIVISION -> {
+ while (b.isZero()) {
+ b = generateNumber();
+ }
+ statement = new Number(a.getValue() * b.toInt(), precision) + " / " + b.toInt() + " = ?";
+ answer = new Number(a);
+ }
+ default -> throw new RuntimeException("wtf operation enum has invalid values");
+ }
+ return new ExpressionTask(statement, answer);
+ }
+ }
+
+ public ExpressionTask(String statement, Number answer) {
+ super(statement, answer);
+ }
+
+ @Override
+ public Task copy() {
+ return new ExpressionTask(super.statement, super.answer);
+ }
+}
diff --git a/lab-01/src/by/marmotikon/quizer/tasks/math_tasks/MathTask.java b/lab-01/src/by/marmotikon/quizer/tasks/math_tasks/MathTask.java
new file mode 100644
index 0000000..95c0671
--- /dev/null
+++ b/lab-01/src/by/marmotikon/quizer/tasks/math_tasks/MathTask.java
@@ -0,0 +1,37 @@
+package by.marmotikon.quizer.tasks.math_tasks;
+
+import by.marmotikon.quizer.tasks.Task;
+
+/**
+ * Interface, который описывает одно математическое задание
+ */
+public interface MathTask extends Task {
+ interface MathTaskGenerator extends TaskGenerator {
+ /**
+ * @return минимальное генерируемое число
+ */
+ double getMinNumber();
+
+ /**
+ * @return максимальное генерируемое число
+ */
+ double getMaxNumber();
+
+ /**
+ * @return разница между максимальным и минимальным возможным числом
+ */
+ default double getDiffNumber() {
+ return getMaxNumber() - getMinNumber();
+ }
+ }
+
+ /**
+ * Enum, который описывает математическую операцию в задании
+ */
+ enum Operation {
+ SUM,
+ DIFFERENCE,
+ MULTIPLICATION,
+ DIVISION
+ }
+}
diff --git a/lab-01/src/by/marmotikon/quizer/tasks/math_tasks/Number.java b/lab-01/src/by/marmotikon/quizer/tasks/math_tasks/Number.java
new file mode 100644
index 0000000..0df8da0
--- /dev/null
+++ b/lab-01/src/by/marmotikon/quizer/tasks/math_tasks/Number.java
@@ -0,0 +1,93 @@
+package by.marmotikon.quizer.tasks.math_tasks;
+
+import by.marmotikon.quizer.Result;
+
+import java.text.DecimalFormat;
+
+public class Number {
+ private double value;
+ private double epsilon;
+
+ public Number(double value, int precision) {
+ DecimalFormat decimalFormat = new DecimalFormat("#." + "#".repeat(precision));
+ epsilon = Math.pow(10, -(precision + 1)) * 5;
+ this.value = Double.parseDouble(decimalFormat.format(value));
+ }
+
+ public Number(Number other) {
+ this.value = other.value;
+ this.epsilon = other.epsilon;
+ }
+
+ public String toString() {
+ if (isZero()) {
+ return "0";
+ }
+ String string = Double.toString(value);
+ int pos = string.length() - 1;
+ while (string.charAt(pos) == '0') {
+ pos--;
+ }
+ if (string.charAt(pos) == '.') {
+ pos--;
+ }
+ string = string.substring(0, pos + 1);
+ return string;
+ }
+
+ public Result provideAnswer(String answer) {
+ try {
+ if (Math.abs(value - Double.parseDouble(answer)) < epsilon) {
+ return Result.OK;
+ } else {
+ return Result.WRONG;
+ }
+ } catch (NumberFormatException e) {
+ return Result.INCORRECT_INPUT;
+ }
+ }
+
+ public int compareTo(Number other) {
+ double diff = this.value - other.value;
+ if (diff < -epsilon) {
+ return -1;
+ }
+ if (diff > epsilon) {
+ return 1;
+ }
+ return 0;
+ }
+
+ public void swap(Number other) {
+ double cup = this.value;
+ this.value = other.value;
+ other.value = cup;
+ cup = this.epsilon;
+ this.epsilon = other.epsilon;
+ other.epsilon = cup;
+ }
+
+ public int toInt() {
+ return (int) value;
+ }
+
+ public double getValue() {
+ return value;
+ }
+
+ public double getEpsilon() {
+ return epsilon;
+ }
+
+ public void setValue(double value) {
+ this.value = value;
+ }
+
+ public void setEpsilon(double epsilon) {
+ this.epsilon = epsilon;
+ }
+
+ public boolean isZero() {
+ return Math.abs(value) < epsilon;
+ }
+}