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; + } +}