` (optionally with an empty attempt).
+
+Then, the correct answer is displayed, and our 'advanced' algorithm scores the user's attempt against the correct
+ answer. Finally, the user is given the option to re-attempt the question later. See below for example gamplay.
+
+##### Examples
+
+* Start Game Mode (from within Deck Mode)
+
+```java
+[Deck - Micro-Economics]
+ > list
+// ------------------------------------------------------------
+// You are now viewing deck: Micro-Economics
+// 1. Question: What is the Law of demand?
+//
+// 2. Question: What is the Law of supply?
+//
+// 3. Question: What is price elasticity of demand?
+//
+// 4. Question: What is price elasticity of supply?
+// ------------------------------------------------------------
+[Deck - Micro-Economics]
+ > start
+```
+
+### Contributions to the DG
+
+Mainly [Commands section](https://ay2021s1-cs2113-t14-2.github.io/tp/DeveloperGuide.html#commands) and
+[Exceptions section](https://ay2021s1-cs2113-t14-2.github.io/tp/DeveloperGuide.html#exceptions) under Design,
+and [Game Mode section](https://ay2021s1-cs2113-t14-2.github.io/tp/DeveloperGuide.html#game-mode) under Features.
+
+Relevant Pull Requests for DG:
+
+* Commands [v2.0](https://github.com/AY2021S1-CS2113-T14-2/tp/pull/89), [v2.1](https://github.com/AY2021S1-CS2113-T14-2/tp/pull/166)
+* [Exceptions](https://github.com/AY2021S1-CS2113-T14-2/tp/pull/166)
+* Game Mode [v2.0](https://github.com/AY2021S1-CS2113-T14-2/tp/pull/104), [v2.1](https://github.com/AY2021S1-CS2113-T14-2/tp/pull/166)
+* [Trivial](https://github.com/AY2021S1-CS2113-T14-2/tp/pull/105)
+
+### DG Extracts
+
+#### Design - Exceptions section
+
+#### Feature Implementation - Game Mode
+
+#### General Architecture
+
+[DG-Implementation-Features-Game-Mode-Architecture](../images-dg/DG-Game-Mode-Architecture-Overview.png?raw=true
+ "Game Mode
+ Architecture Overview")
+
+[DG-Implementation-Features-Game-Storage-Game-Engine](../images-dg/DG-Game-Storage-Game-Engine.png?raw=true "Game
+ Mode Game Storage Game Engine")
+
+#### Example Use Case
+
+For a more contextual use case, consider the following scenario of Econs Wiz attempting the Game Mode for the first
+ deck, `Demand-Supply`, in his deck list.
+
+> Note: Focus on the biggest box in the diagram!
+
+
+
+**API**: [seedu/ecardnomics/game](https://github.com/AY2021S1-CS2113-T14-2/tp/tree/master/src/main/java/seedu/ecardnomics/game)
+
+Since there are quite a few things going on in this diagram, here are the key takeaways (the last of which arguably
+ the most important):
+- the retest question pool (`retestStore`) is updated upon the user's attempt at each question and response to the
+ prompt to include or exclude the flash card to be displayed again—if `y` then the flash card is added to the
+ `retestStore`
+- whenever the current question pool (`deque`) is emptied, i.e. all flash cards have been popped off and displayed to
+ the user, the retest question pool (`retestStore`) is consulted from which to create a new question pool (`deque`)
+- after a specific question is displayed, it does not appear again (even if the user has chosen to re-encounter the
+ question) until after all other questions in the current question pool (`deque`) have been exhausted
+
+#### Sequential Flow
+
+For a more formal sequential flow of the inner workings of Game Mode, the following elaborates the execution
+ sequence of Game Mode, from after a `start` command has been parsed in Normal Mode:
+
+
+
+ In the above diagram the key takeaway is the existence of an *intermediary* `game:Game` object that holds
+ `GameEngine` and `GameStorage` together. In fact, this is the sole purpose of the `Game` class: to hold the
+ current game instance, in a Single-Responsibility-Principle (SRP) and Object-Oriented Programming (OOP) manner.
+ This intermediary role of the `Game` class is also illustrated in the upper part of the earlier [use case
+ diagram](#example-use-case). Note how it is from this `game` object that the main game loop, run and managed by
+ `GameEngine`, is started.
+
+[DG-Implementation-Features-Game-Mode-Sequence-run-game-loop-sd](../images-dg/DG-run-game-loop-sd.png?raw=true "Game
+ Mode UML Sequence Diagram run game loop sd")
+
+The main game loop. As with all simple games, this flash card game mode is fundamentally built on the concept of a
+ possibly never-ending game loop with specific end conditions. In this case, the main end condition is explicitly
+ that the `command` object that is parsed and returned upon the `getAttempt()` call (that prompts the user for an
+ answer attempt) is either a `DoneGameCommand` or `ExitCommand`.
+
+> This is not the *only* end condition, though, because the other important but implicit end scenario is when the
+> question pool is exhausted (i.e., `storage.deque` is empty) **and** the retest question pool (`storage.retestStore
+>`) is empty.
+
+Naturally, the other sequence of special note here is the whole `update()` sequence, and even more specifically the
+ `updateDeque()` call within the `update()` sequence. Notice how `updateDeque()` calls `createRandomisedStack(storage
+ .retestStore)` with the `retestStore` as argument. This essentially creates a new randomised question pool from the
+ retest question pool.
+
+Notice how this `updateDeque()` sequence is only called exactly when the `storage.deque` is empty (i.e., when all
+ questions have been popped off the question pool). This is important because it ensures that the user encounters all
+ available questions in the `deque` at least once before the retest questions are later displayed. Cross-check this
+ with the detailed descriptions of the inner workings of the game loop implementation shown in the earlier
+ [architecture](#general-architecture) and [use case](#example-use-case) diagrams.
+
+Lastly, notice how `refreshRetestStore()` is called at the end of `updateDeque()` to, as its name suggests, clear the
+ retest question pool to get ready to store the next wave of retest questions. This is also covered in the bottom few
+ lines of the `GameEngine` portion of the use case diagram.
diff --git a/docs/team/zhixiangteoh.png b/docs/team/zhixiangteoh.png
new file mode 100644
index 0000000000..30f71d03d0
Binary files /dev/null and b/docs/team/zhixiangteoh.png differ
diff --git a/pptx/Micro-Economics.pptx b/pptx/Micro-Economics.pptx
new file mode 100644
index 0000000000..509bfdc915
Binary files /dev/null and b/pptx/Micro-Economics.pptx differ
diff --git a/src/main/java/seedu/duke/Duke.java b/src/main/java/seedu/duke/Duke.java
deleted file mode 100644
index 5c74e68d59..0000000000
--- a/src/main/java/seedu/duke/Duke.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package seedu.duke;
-
-import java.util.Scanner;
-
-public class Duke {
- /**
- * Main entry-point for the java.duke.Duke application.
- */
- public static void main(String[] args) {
- String logo = " ____ _ \n"
- + "| _ \\ _ _| | _____ \n"
- + "| | | | | | | |/ / _ \\\n"
- + "| |_| | |_| | < __/\n"
- + "|____/ \\__,_|_|\\_\\___|\n";
- System.out.println("Hello from\n" + logo);
- System.out.println("What is your name?");
-
- Scanner in = new Scanner(System.in);
- System.out.println("Hello " + in.nextLine());
- }
-}
diff --git a/src/main/java/seedu/ecardnomics/Main.java b/src/main/java/seedu/ecardnomics/Main.java
new file mode 100644
index 0000000000..eafce5bc45
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/Main.java
@@ -0,0 +1,137 @@
+package seedu.ecardnomics;
+
+import seedu.ecardnomics.command.Command;
+import seedu.ecardnomics.command.deck.DoneEditCommand;
+import seedu.ecardnomics.command.game.DoneGameCommand;
+import seedu.ecardnomics.command.game.GameStartCommand;
+import seedu.ecardnomics.command.normal.EditCommand;
+import seedu.ecardnomics.command.ExitCommand;
+import seedu.ecardnomics.command.normal.StartCommand;
+import seedu.ecardnomics.deck.Deck;
+import seedu.ecardnomics.deck.DeckList;
+import seedu.ecardnomics.game.Game;
+import seedu.ecardnomics.parser.DeckParser;
+import seedu.ecardnomics.parser.NormalParser;
+import seedu.ecardnomics.storage.LogStorage;
+import seedu.ecardnomics.storage.Storage;
+
+import java.io.IOException;
+
+/**
+ * Main Class for eCardnomics - Flash Card Manager Command Line Program.
+ */
+public class Main {
+
+ public static final double VERSION_NUMBER = 2.1;
+ public static DeckList deckList = new DeckList();
+ public static NormalParser normalParser = new NormalParser(deckList);
+ public static Storage storage = new Storage();
+
+ /**
+ * Executes the command.
+ *
+ * @param command command from parser
+ */
+ private static void executeCommand(Command command) {
+ command.execute();
+ }
+
+ /**
+ * Runs Deck Mode to edit a deck.
+ *
+ * @param deck Deck to edit
+ * @return Command used to exit Deck Mode (either done
or exit
)
+ */
+ public static Command runDeckMode(Deck deck) {
+ DeckParser deckParser = new DeckParser(deckList, deck);
+
+ String userInput;
+ Command command;
+
+ do {
+ Ui.printDeckPrompt(deck);
+ userInput = Ui.readUserInput();
+
+ command = deckParser.parse(userInput);
+
+ executeCommand(command);
+
+ } while (!DoneEditCommand.isDoneEdit(command)
+ && !ExitCommand.isExit(command)
+ && !StartCommand.isStart(command));
+
+ return command;
+ }
+
+ /**
+ * Runs Game Mode for specified deck.
+ *
+ * @param deck to run Game Mode on
+ * @return Command used to exit Game Mode (either done
or exit
)
+ */
+ public static Command runGameMode(Deck deck) {
+ GameStartCommand gameStartCommand = new GameStartCommand(deck);
+ executeCommand(gameStartCommand);
+
+ Game game = gameStartCommand.getGameInstance();
+ return game.run();
+ }
+
+ /**
+ * Runs Normal Mode in a loop until exit
in input.
+ * Enters Deck Mode when edit
is input.
+ */
+ public static void runNormalMode() {
+ Ui.printGreeting();
+
+ String userInput;
+ Command command;
+
+ do {
+ Ui.printNormalPrompt();
+ userInput = Ui.readUserInput();
+
+ command = normalParser.parse(userInput);
+
+ executeCommand(command);
+
+ if (EditCommand.isEdit(command)) {
+ EditCommand editCommand = (EditCommand) command;
+ command = runDeckMode(editCommand.getDeck());
+
+ if (command instanceof DoneEditCommand) {
+ Ui.printNormalWelcome();
+ }
+ }
+
+ if (StartCommand.isStart(command)) {
+ StartCommand startCommand = (StartCommand) command;
+ command = runGameMode(startCommand.getDeck());
+
+ if (command instanceof DoneGameCommand) {
+ Ui.printNormalWelcome();
+ }
+ }
+
+ } while (!ExitCommand.isExit(command));
+
+ Ui.printExitLine();
+ }
+
+ /**
+ * Main method.
+ *
+ * @param args Arguments from command line when user starts the program
+ */
+ public static void main(String[] args) {
+ deckList = storage.load(deckList);
+
+ runNormalMode();
+
+ try {
+ storage.write(Main.deckList);
+ } catch (IOException e) {
+ Ui.printMessage("Unable to write file...");
+ }
+ }
+}
diff --git a/src/main/java/seedu/ecardnomics/Mode.java b/src/main/java/seedu/ecardnomics/Mode.java
new file mode 100644
index 0000000000..2826cf675a
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/Mode.java
@@ -0,0 +1,17 @@
+package seedu.ecardnomics;
+
+public enum Mode {
+ NORMAL("Normal"), DECK("Deck");
+
+ private String name;
+
+ private Mode(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+
+}
diff --git a/src/main/java/seedu/ecardnomics/Ui.java b/src/main/java/seedu/ecardnomics/Ui.java
new file mode 100644
index 0000000000..4120c0c9b2
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/Ui.java
@@ -0,0 +1,709 @@
+package seedu.ecardnomics;
+
+import org.apache.commons.math3.analysis.function.Log;
+import seedu.ecardnomics.deck.Deck;
+import seedu.ecardnomics.deck.DeckList;
+import seedu.ecardnomics.deck.FlashCard;
+import seedu.ecardnomics.game.Game;
+import seedu.ecardnomics.powerpoint.ColorOption;
+import seedu.ecardnomics.storage.LogStorage;
+
+import java.util.ArrayList;
+import java.util.Scanner;
+import java.util.logging.Level;
+
+import static seedu.ecardnomics.Main.VERSION_NUMBER;
+
+public class Ui {
+
+ // Output lines to users
+ public static final String GREETING_LINES =
+ "Hello! Welcome to eCardnomics,\n"
+ + "your one stop Flash Cards solution";
+ public static final String NORMAL_WELCOME_LINE =
+ "You are back in Normal Mode";
+ public static final String DECK_WELCOME_LINE =
+ "You are now in Deck Mode, editing: ";
+ public static final String BYE_LINE =
+ "Bye. Hope to see you again soon!";
+ public static final String NOT_RECOGNISED_LINE =
+ "Command not recognised";
+ public static final String YN_LINE = "[y/n]";
+ public static final String ADD_FLASHCARD_LINE =
+ "You are now adding a FlashCard to: ";
+ public static final String ENTER_QUESTION_LINE =
+ "Enter question:";
+ public static final String ENTER_ANSWER_LINE =
+ "Enter answer:";
+ public static final String FLASHCARD_ADDED_LINE =
+ "FlashCard successfully added!";
+ public static final String LIST_FLASHCARDS_LINE =
+ "You are now viewing deck: ";
+ public static final String DELETE_FLASHCARD_LINE =
+ "Do you want to delete the following flash card? [y/n] ";
+ public static final String FLASHCARD_DELETED_LINE =
+ "The following flash card has been deleted:\n '";
+ public static final String NEW_DECK_CREATED_LINE =
+ "New deck created: ";
+ public static final String DECKS_AVAILABLE_LINE =
+ "The following decks are available:\n";
+ public static final String DELETED_DECK_QUESTION_LINE =
+ "Do you want to delete %1$s deck? [y/n] ";
+ public static final String DELETED_DECK_LINE =
+ "%1$s has been deleted.";
+ public static final String PPTX_DECK_QUESTION_LINE =
+ "Do you want to print %1$s deck to PowerPoint? [y/n] ";
+ public static final String PPTX_DECK_DEFAULT_LINE =
+ "%s has been created as PowerPoint with default,\n black background and white text.";
+ public static final String PPTX_DECK_CS_LINE =
+ "%s has been created as PowerPoint using Color Scheme,\n with %s background and %s text.";
+ public static final String PPTX_DECK_OC_LINE =
+ "%s has been created as PowerPoint using Original Colors,\n with %s background and %s text.";
+ public static final String INVALID_YN_RESPONSE_LINE =
+ "Response should be 'y' or 'n'\n > ";
+ private static final String EMPTY_DECK_LINE =
+ "Deck is currently empty!";
+ private static final String NEW_TAGS_LINE =
+ "The deck %1$s has been tagged as: %2$s";
+ private static final String REMOVED_TAGS_QUESTION_LINE =
+ "Do you want to remove the tag(s) %1$s from %2$s? [y/n] ";
+ private static final String REMOVED_TAGS_LINE =
+ "The tag(s) %1$s have been removed from the deck %2$s.";
+ private static final String NEW_QUESTION_LINE =
+ "New Question: ";
+ private static final String NEW_ANSWER_LINE =
+ "New Answer: ";
+ private static final String QUESTION_UPDATED_LINE =
+ "Question updated.";
+ private static final String ANSWER_UPDATED_LINE =
+ "Answer updated.";
+ private static final String QNA_UPDATED_LINE =
+ "Question and answer updated.";
+ private static final String NO_UPDATE_LINE =
+ "Original question and answer retained";
+ private static final String INCLUDE_EXCLUDE_LINE =
+ "Do you want to re-attempt this question later? [y/n] ";
+ private static final String ATTEMPT_FEEDBACK_LINE =
+ "The score for your answer is:";
+ private static final String ENTER_ATTEMPT_LINE =
+ " Enter your attempt below (or `done`, `exit`, `help`):";
+ private static final String DONE_GAME_LINE =
+ "You have completed all the flash cards in this deck!\n"
+ + "Returning to Normal Mode...";
+ private static final String GAME_EMPTY_DECK_LINE =
+ EMPTY_DECK_LINE + " Please add some flash cards first.";
+ private static final String INVALID_TAGS_LINE =
+ "Tag %s is not in the deck already!";
+ private static final String DECKS_HAVING_TAGS_LINE =
+ "The decks having tags you are searching for:";
+ private static final String NO_DECKS_WITH_TAGS_LINE =
+ "There is no decks having the tags you are looking for.";
+
+ public static final String QUESTION = "Question ";
+ public static final String ANSWER = "Answer";
+ public static final String CREATE_NEW_FILE_ERROR_LINE = "Error creating new file - ";
+ public static final String WRITE_TO_FILE_ERROR_LINE = "Error writing for file";
+ public static final String FILE_NOT_FOUND_ERROR_LINE = " not found or file currently open";
+
+ // Commands
+ public static final String EXIT = "exit";
+ public static final String EDIT = "edit";
+ public static final String START = "start";
+ public static final String DONE = "done";
+ public static final String ADD = "add";
+ public static final String CREATE = "create";
+ public static final String LIST = "list";
+ public static final String DECKS = "decks";
+ public static final String DELETE = "delete";
+ public static final String UPDATE = "update";
+ public static final String HELP = "help";
+ public static final String TAG = "tag";
+ public static final String UNTAG = "untag";
+ public static final String PPTX = "pptx";
+ public static final String SEARCH = "search";
+
+ public static final String VERSION_CMD = "--version";
+
+ // Options
+ public static final String FORCE_YES_OPT = "-y";
+ public static final String COLOR_SCHEME_OPT = "-cs";
+ public static final String ORIGINAL_COLOR_OPT = "-oc";
+
+ //Regex
+ public static final String DIGITS_REGEX = "\\d+";
+ public static final String PUNC_REGEX = "\\p{Punct}";
+ public static final String ORIGINAL_COLOR_REGEX = "-oc\\s+(\\w+)\\s+(\\w+)\\s*(-|$)";
+ public static final String COLOR_SCHEME_REGEX = "-cs\\s(\\S+)\\s*(-|$)";
+
+ public static final String Y = "y";
+ public static final String N = "n";
+ public static final String DASH_LINES = "------------------------------------------------------------"
+ + "--------------------";
+ public static final String MIDDLE_SEPARATOR = " ---------------- "
+ + " ";
+
+
+ public static final String LOGO1 = " ___ _ _";
+ public static final String LOGO2 = " ___ / __\\__ _ _ __ __| |_ __ ___ _ __ ___ (_) ___ ___";
+ public static final String LOGO3 = " / _ \\/ / / _` | '__/ _` | '_ \\ / _ \\| '_ ` _ \\| |/ __/ __|";
+ public static final String LOGO4 = "| __/ /__| (_| | | | (_| | | | | (_) | | | | | | | (__\\__ \\";
+ public static final String LOGO5 = " \\___\\____/\\__,_|_| \\__,_|_| |_|\\___/|_| |_| |_|_|\\___|___/";
+
+ public static Scanner in = new Scanner(System.in);
+ private static LogStorage logger = new LogStorage("UiLogger");
+
+ /**
+ * Reads user input from command line.
+ *
+ * @return String containing userInput
+ */
+ public static String readUserInput() {
+ return in.nextLine();
+ }
+
+ /**
+ * Displays dash line.
+ */
+ public static void printDashLines() {
+ System.out.println(DASH_LINES);
+ }
+
+ public static void printMiddleSeparator() {
+ System.out.println(MIDDLE_SEPARATOR);
+ }
+
+ /**
+ * Displays the message given.
+ *
+ * @param message message to be displayed to user
+ */
+ public static void printMessage(String message) {
+ printDashLines();
+ System.out.println(message);
+ printDashLines();
+ }
+
+ /**
+ * Prints logo when user opens the program.
+ */
+ public static void printLogo() {
+ System.out.println(LOGO1);
+ System.out.println(LOGO2);
+ System.out.println(LOGO3);
+ System.out.println(LOGO4);
+ System.out.println(LOGO5);
+ System.out.println("");
+ }
+
+ /**
+ * Displays the prompt for user input in Normal Mode.
+ */
+ public static void printNormalPrompt() {
+ System.out.println("[Normal]");
+ System.out.print(" > ");
+ }
+
+ /**
+ * Displays the prompt for user input in Deck Mode.
+ */
+ public static void printDeckPrompt(Deck deck) {
+ System.out.println("[Deck - " + deck.getName() + "]");
+ System.out.print(" > ");
+ }
+
+ /**
+ * Displays the prompt for user input in Game Mode.
+ */
+ public static void printGamePrompt(Deck deck) {
+ System.out.println("[Game - " + deck.getName() + "]");
+ System.out.print(" > ");
+ }
+
+ /**
+ * Displays the prompt for user input without specifying current mode.
+ */
+ public static void printPrompt() {
+ System.out.print(" > ");
+ }
+
+ /**
+ * Displays the welcome message from Deck Mode to Normal Mode.
+ */
+ public static void printNormalWelcome() {
+ printMessage(NORMAL_WELCOME_LINE);
+ }
+
+ /**
+ * Displays the welcome message from Normal Mode to Deck Mode.
+ */
+ public static void printDeckWelcome(int index, Deck deck) {
+ printMessage(DECK_WELCOME_LINE + "[" + index + "] " + deck.getName());
+ }
+
+ /**
+ * Displays the welcome message for Game Mode.
+ */
+ public static void printGameWelcome(int index, Deck deck) {
+ printMessage(Game.WELCOME_MESSAGE + "[" + index + "] " + deck.getName());
+ }
+
+ /**
+ * Displays the greeting message.
+ */
+ public static void printGreeting() {
+ printDashLines();
+ printLogo();
+ System.out.println(GREETING_LINES);
+ printDashLines();
+ }
+
+ /**
+ * Displays the exit message.
+ */
+ public static void printExitLine() {
+ printMessage(BYE_LINE);
+ }
+
+ /**
+ * Displays the add FlashCard line.
+ *
+ * @param deck current deck
+ */
+ public static void printAddFlashCardLine(Deck deck) {
+ printMessage(ADD_FLASHCARD_LINE + deck.getName());
+ }
+
+ /**
+ * Displays the enter question line.
+ */
+ public static void printEnterQuestionLine() {
+ System.out.println(ENTER_QUESTION_LINE);
+ printPrompt();
+ }
+
+ /**
+ * Displays the enter answer line.
+ */
+ public static void printEnterAnswerLine() {
+ System.out.println(ENTER_ANSWER_LINE);
+ printPrompt();
+ }
+
+ /**
+ * Displays the FlashCard added line.
+ */
+ public static void printFlashCardAddedLine() {
+ System.out.println(FLASHCARD_ADDED_LINE);
+ }
+
+ /**
+ * Displays an index list of FlashCards in the deck.
+ *
+ * @param deck deck to display
+ * @param isQuestionOnly print question only if true, question and answer otherwise
+ */
+ public static void printDeck(Deck deck, boolean isQuestionOnly) {
+ String deckMessage = "";
+ if (deck.toString(isQuestionOnly).trim().equals("")) {
+ deckMessage += EMPTY_DECK_LINE;
+ } else {
+ deckMessage += LIST_FLASHCARDS_LINE + deck.getName() + System.lineSeparator()
+ + deck.toString(isQuestionOnly);
+ }
+ printMessage(deckMessage);
+ }
+
+ /**
+ * Displays the delete FlashCard line.
+ *
+ * @param question The question of the FlashCard to delete
+ */
+ public static void printDeleteFlashCardLine(String question) {
+ System.out.print(DELETE_FLASHCARD_LINE + "?\n '" + question + "' ");
+ }
+
+ /**
+ * Displays the FlashCard deleted line.
+ *
+ * @param question The question of the FlashCard to delete
+ */
+ public static void printFlashCardDeletedLine(String question) {
+ System.out.println(FLASHCARD_DELETED_LINE + question + "'");
+ }
+
+ /**
+ * Displays the help page of commands.
+ *
+ * Displays a different help page for Normal Mode and Deck Mode.
+ *
+ * @param helpDisplay help text to display
+ */
+ public static void printHelp(String helpDisplay) {
+ printMessage(helpDisplay);
+ }
+
+ /**
+ * Prints new deck added in the Normal Mode.
+ *
+ * @param deck in new Deck added
+ */
+ public static void printNewDeck(Deck deck) {
+ String tagsOfNewDeck = deck.getTagString();
+ if (!tagsOfNewDeck.isEmpty()) {
+ tagsOfNewDeck = " #Tag: " + tagsOfNewDeck;
+ }
+ printMessage(NEW_DECK_CREATED_LINE + deck.getName() + tagsOfNewDeck);
+ }
+
+ /**
+ * Prints all decks available in the Normal Mode.
+ *
+ * @param decks all decks in the list
+ */
+ public static void printDeckList(DeckList decks) {
+ printMessage(DECKS_AVAILABLE_LINE + decks.toString());
+ }
+
+ /**
+ * Confirms the deck the user wants to delete.
+ *
+ * @param deletedDeckName name of the deleted deck
+ */
+ public static void printDeletedDeckQuestion(String deletedDeckName) {
+ System.out.print(String.format(DELETED_DECK_QUESTION_LINE, deletedDeckName));
+ }
+
+ /**
+ * Prints the name of the deleted deck.
+ *
+ * @param deletedDeckName name of the deleted deck
+ */
+ public static void printDeckDeletedLine(String deletedDeckName) {
+ System.out.println(String.format(DELETED_DECK_LINE, deletedDeckName));
+ }
+
+ /**
+ * Confirms the deck the user wants to print to PowerPoint.
+ *
+ * @param pptxDeckName name of the pptx deck
+ */
+ public static void printPptxDeckQuestion(String pptxDeckName) {
+ System.out.print(String.format(PPTX_DECK_QUESTION_LINE, pptxDeckName));
+ }
+
+ /**
+ * Prints the name of the deck to be printed to PowerPoint.
+ *
+ * @param pptxDeckName name of the pptx deck
+ */
+ public static void printDeckPptxLine(String pptxDeckName, String bgColor, String txtColor, ColorOption colorOpt) {
+ switch (colorOpt) {
+ case DEFAULT:
+ printMessage(String.format(PPTX_DECK_DEFAULT_LINE, pptxDeckName));
+ break;
+ case COLOR_SCHEME:
+ printMessage(String.format(PPTX_DECK_CS_LINE, pptxDeckName, bgColor, txtColor));
+ break;
+ case ORGINAL_COLOR:
+ printMessage(String.format(PPTX_DECK_OC_LINE, pptxDeckName, bgColor, txtColor));
+ break;
+ default:
+ //
+ break;
+ }
+ }
+
+ /**
+ * Prints the update question lines for a flashcard.
+ *
+ * @param flashCard for which the question should be updated.
+ */
+ public static void printUpdateQuestionLine(FlashCard flashCard) {
+ // No offset since printing from start of line.
+ System.out.println(flashCard.toString(true, 0));
+ System.out.println(NEW_QUESTION_LINE);
+ printPrompt();
+ }
+
+ /**
+ * Prints the update answer lines for a flashcard.
+ *
+ * @param flashCard for which the answer should be updated.
+ */
+ public static void printUpdateAnswerLine(FlashCard flashCard) {
+ // No offset since printing from start of line.
+ System.out.println(System.lineSeparator() + flashCard.toString(false, 0));
+ System.out.println(NEW_ANSWER_LINE);
+ printPrompt();
+ }
+
+ /**
+ * Prints line informing updated flashcards.
+ *
+ * @param hasNewQ boolean parameter indicating new Question provided
+ * @param hasNewA boolean parameter indicating new Answer providid
+ */
+ public static void printFlashCardUpdatedLine(boolean hasNewQ, boolean hasNewA) {
+ if (hasNewQ && hasNewA) {
+ System.out.println(QNA_UPDATED_LINE);
+ } else if (hasNewQ) {
+ // !hasNewA
+ System.out.println(QUESTION_UPDATED_LINE);
+ } else if (hasNewA) {
+ // !hasNewQ
+ System.out.println(ANSWER_UPDATED_LINE);
+ } else {
+ System.out.println(NO_UPDATE_LINE);
+ }
+ }
+
+ /**
+ * Prints a line prompting user to enter only 'y' or 'n'.
+ */
+ public static void printInvalidYorNResponse() {
+ System.out.print(INVALID_YN_RESPONSE_LINE);
+ }
+
+ public static void printVersionNumber() {
+ printMessage("Version: " + VERSION_NUMBER);
+ }
+
+ public static void printIncludeExcludeLine() {
+ System.out.print(INCLUDE_EXCLUDE_LINE);
+ }
+
+ public static void printAttemptFeedback(double matchPercentage) {
+ System.out.println(String.format("%s %.2f", ATTEMPT_FEEDBACK_LINE, matchPercentage));
+ }
+
+ public static boolean getInclExclConfirmation() {
+ logger.log(Level.INFO, "Logging method getInclExclConfirmation() in Ui.");
+ Ui.printIncludeExcludeLine();
+ return checkYorNResponse();
+ }
+
+ /**
+ * Displays question in the flashcard.
+ *
+ * @param question String representing the question
+ */
+ public static void printGameQuestion(String question) {
+ final String label = "Q: ";
+ System.out.println(label + prettyPrintFormatter(question, label.length()));
+ System.out.println(ENTER_ATTEMPT_LINE);
+ printPrompt();
+ }
+
+ public static void printGameEmptyDeckLine() {
+ System.out.println(GAME_EMPTY_DECK_LINE);
+ }
+
+ public static void printAnswerGameMode(String answer) {
+ final String label = "A: ";
+ System.out.println(label + prettyPrintFormatter(answer, label.length()));
+ }
+
+ public static void printDoneGameMessage() {
+ System.out.println(DONE_GAME_LINE);
+ }
+
+ /**
+ * Prints the new tags added to the deck.
+ *
+ * @param name the name of the deck
+ * @param newTags the tag(s) will be added to the deck
+ */
+ public static void printNewTags(String name, ArrayList newTags) {
+ String tagString = formStringOfTags(newTags);
+ printMessage(String.format(NEW_TAGS_LINE, name, tagString));
+ }
+
+ /**
+ * Prints out warning about invalid tags provided.
+ */
+ public static void printInvalidTagsLine(String tags) {
+ logger.log(Level.WARNING, "User did not supply valid tags.");
+ printMessage(String.format((INVALID_TAGS_LINE), tags));
+ }
+
+ /**
+ * Prints confirmation question before tag removal.
+ *
+ * @param deckName the name of the deck having tags being removed
+ * @param tags the tags will be removed
+ */
+ public static void printRemovedTagsQuestion(String deckName, ArrayList tags) {
+ String removedTags = formStringOfTags(tags);
+ System.out.print(String.format(REMOVED_TAGS_QUESTION_LINE, removedTags, deckName));
+ }
+
+ /**
+ * Prints the tags that are removed from the specified deck.
+ *
+ * @param deckName the name of the deck having removed tags
+ * @param tags tags were removed
+ */
+ public static void printTagsRemovedLine(String deckName, ArrayList tags) {
+ String removedTags = formStringOfTags(tags);
+ System.out.println(String.format(REMOVED_TAGS_LINE, removedTags, deckName));
+ }
+
+ /**
+ * Forms a String of provided tags.
+ *
+ * @param tags tags to be formed to String
+ * @return a String of tags
+ */
+ public static String formStringOfTags(ArrayList tags) {
+ String stringOfTags = "";
+
+ for (int i = 0; i < tags.size(); i++) {
+ stringOfTags += tags.get(i);
+ if (i < tags.size() - 1) {
+ stringOfTags += " | ";
+ }
+ }
+ return stringOfTags;
+ }
+
+ /**
+ * Checks y or n response from user.
+ *
+ * @return true if user enters confirms, otherwise false
+ */
+ private static boolean checkYorNResponse() {
+ logger.log(Level.INFO, "Logging method checkYorNResponse() in Ui.");
+ String response;
+ do {
+ response = readUserInput();
+ assert response != null : "response should not be null";
+ switch (response.trim()) {
+ case Y:
+ response = Y;
+ break;
+ case N:
+ response = N;
+ break;
+ default:
+ logger.log(Level.INFO, "User entered response other than 'y' or 'n'");
+ logger.log(Level.INFO, "Re-prompting...");
+ printInvalidYorNResponse();
+ }
+ } while (!response.trim().equals(Y) && !response.trim().equals(N));
+ assert (response.equals(Y) || response.equals(N)) : "Response should be y/n";
+ return response.equals(Y);
+ }
+
+ /**
+ * Get confirmation for deleting a deck.
+ *
+ * @param deckName String representing the index of the deck in deckList
+ * @return true if delete is confirmed, otherwise false
+ */
+ public static boolean getDeletedDeckConfirmation(String deckName) {
+ logger.log(Level.INFO, "Logging method getDeletedDeckConfirmation() in Ui.");
+ printDashLines();
+ printDeletedDeckQuestion(deckName);
+
+ return checkYorNResponse();
+ }
+
+ public static boolean getDeletedFlashCardConfirmation(String question) {
+ logger.log(Level.INFO, "Logging method getDeletedFlashCardConfirmation() in Ui.");
+ printDashLines();
+ Ui.printDeleteFlashCardLine(question);
+
+ return checkYorNResponse();
+ }
+
+ /**
+ * Checks whether the user want to remove the tags specified
+ * from the deck specified.
+ *
+ * @param tags String[] representing the tags be removed
+ * @param deckName String representing the index of the deck of the tags
+ * @return true if the removal is confirmed, otherwise false
+ */
+ public static boolean getRemovedTagsConfirmation(ArrayList tags, String deckName) {
+ printDashLines();
+ printRemovedTagsQuestion(deckName, tags);
+ boolean isConfirmed = checkYorNResponse();
+
+ if (isConfirmed) {
+ printTagsRemovedLine(deckName, tags);
+ printDashLines();
+ }
+
+ return isConfirmed;
+ }
+
+ public static void printSearchDecksLine(String decksHavingTags) {
+ if (decksHavingTags.isEmpty()) {
+ printMessage(NO_DECKS_WITH_TAGS_LINE);
+ } else {
+ printMessage(DECKS_HAVING_TAGS_LINE + decksHavingTags);
+ }
+ }
+
+ /**
+ * Get confirmation from user on whether to print deck to PowerPoint.
+ *
+ * @param deckName name of deck to be printed to PowerPoint
+ * @return true if delete is confirmed, otherwise false
+ */
+ public static boolean getPptxDeckConfirmation(String deckName) {
+ logger.log(Level.INFO, "Logging method getPptxDeckConfirmation() in Ui.");
+ Ui.printPptxDeckQuestion(deckName);
+ return checkYorNResponse();
+ }
+
+ /**
+ * Format the target string to properly wrap around the end
+ * of each line. The response will occupy the area between offset and
+ * Ui.DASH_LINES.length().
+ *
+ * @param target String to be formatted
+ * @param offset Number of characters from the start of the line
+ * @return String that stores the formatted question or answer
+ */
+ public static String prettyPrintFormatter(String target, int offset) {
+ String result = "";
+ String[] words = target.split(" ");
+ int lineLength = Ui.DASH_LINES.length();
+ int usableLength = lineLength - offset;
+ assert usableLength > 0 : "Otherwise we cannot print anything.";
+
+ int currentLength = 0;
+ for (String word : words) {
+ // Handle the case where a word is too long to print on one line
+ if (word.length() > usableLength) {
+ // Find number of characters that can be printed on current line
+ int remainLength = usableLength - currentLength;
+ result += word.substring(0, remainLength);
+ String leftover = word.substring(remainLength);
+ // Separate the word into parts that can fit in a line
+ while (leftover.length() > usableLength) {
+ result += System.lineSeparator() + " ".repeat(offset)
+ + leftover.substring(0, usableLength);
+ leftover = leftover.substring(usableLength);
+ }
+ // Place remainder of word into line and continue
+ result += System.lineSeparator() + " ".repeat(offset) + leftover + " ";
+ currentLength = leftover.length() + 1;
+ continue;
+ }
+ currentLength += word.length();
+ if (currentLength > usableLength) {
+ // Repeat enough spaces so that text is aligned to usable area.
+ result += System.lineSeparator() + " ".repeat(offset) + word;
+ currentLength = word.length();
+ } else {
+ result += word;
+ }
+ result += " ";
+ // Account for the " " after the word.
+ ++currentLength;
+ }
+ return result.trim();
+ }
+}
diff --git a/src/main/java/seedu/ecardnomics/command/Command.java b/src/main/java/seedu/ecardnomics/command/Command.java
new file mode 100644
index 0000000000..1b8e342721
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/command/Command.java
@@ -0,0 +1,5 @@
+package seedu.ecardnomics.command;
+
+public abstract class Command {
+ public abstract void execute();
+}
diff --git a/src/main/java/seedu/ecardnomics/command/ExitCommand.java b/src/main/java/seedu/ecardnomics/command/ExitCommand.java
new file mode 100644
index 0000000000..0c0fc10751
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/command/ExitCommand.java
@@ -0,0 +1,15 @@
+package seedu.ecardnomics.command;
+
+public class ExitCommand extends Command {
+ public ExitCommand() {
+ }
+
+ @Override
+ public void execute() {
+ }
+
+ /** Returns if command given is an instance of ExitCommand
. */
+ public static boolean isExit(Command command) {
+ return command instanceof ExitCommand;
+ }
+}
diff --git a/src/main/java/seedu/ecardnomics/command/VersionCommand.java b/src/main/java/seedu/ecardnomics/command/VersionCommand.java
new file mode 100644
index 0000000000..0f49171379
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/command/VersionCommand.java
@@ -0,0 +1,14 @@
+package seedu.ecardnomics.command;
+
+import seedu.ecardnomics.Ui;
+
+public class VersionCommand extends Command {
+
+ public VersionCommand() {
+ }
+
+ @Override
+ public void execute() {
+ Ui.printVersionNumber();
+ }
+}
diff --git a/src/main/java/seedu/ecardnomics/command/VoidCommand.java b/src/main/java/seedu/ecardnomics/command/VoidCommand.java
new file mode 100644
index 0000000000..549c05901f
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/command/VoidCommand.java
@@ -0,0 +1,23 @@
+package seedu.ecardnomics.command;
+
+import seedu.ecardnomics.Ui;
+
+public class VoidCommand extends Command {
+ private String errorMessage;
+
+ /** Constructor without errorMessage
. */
+ public VoidCommand() {
+ errorMessage = Ui.NOT_RECOGNISED_LINE;
+ }
+
+ /** Constructor with errorMessage
. */
+ public VoidCommand(String errorMessage) {
+ assert (errorMessage != null && !errorMessage.isEmpty()) : "Must specify error message";
+ this.errorMessage = errorMessage;
+ }
+
+ @Override
+ public void execute() {
+ Ui.printMessage(errorMessage);
+ }
+}
diff --git a/src/main/java/seedu/ecardnomics/command/deck/AddCommand.java b/src/main/java/seedu/ecardnomics/command/deck/AddCommand.java
new file mode 100644
index 0000000000..d5ed60da2b
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/command/deck/AddCommand.java
@@ -0,0 +1,23 @@
+package seedu.ecardnomics.command.deck;
+
+import seedu.ecardnomics.deck.Deck;
+import seedu.ecardnomics.deck.FlashCard;
+
+public class AddCommand extends DeckCommand {
+ private final String question;
+ private final String answer;
+
+ /** Constructor. */
+ public AddCommand(Deck deck, String question, String answer) {
+ super(deck);
+ assert (question != null && !question.isEmpty()) : "Flashcard must have one question.";
+ this.question = question;
+ assert (answer != null && !answer.isEmpty()) : "Flashcard must have one question.";
+ this.answer = answer;
+ }
+
+ @Override
+ public void execute() {
+ this.currentDeck.add(new FlashCard(this.question, this.answer));
+ }
+}
diff --git a/src/main/java/seedu/ecardnomics/command/deck/DeckCommand.java b/src/main/java/seedu/ecardnomics/command/deck/DeckCommand.java
new file mode 100644
index 0000000000..64ffd36fb9
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/command/deck/DeckCommand.java
@@ -0,0 +1,19 @@
+package seedu.ecardnomics.command.deck;
+
+import seedu.ecardnomics.command.Command;
+import seedu.ecardnomics.deck.Deck;
+
+public abstract class DeckCommand extends Command {
+ protected Deck currentDeck;
+
+ public DeckCommand() {
+ currentDeck = null;
+ }
+
+ public DeckCommand(Deck currentDeck) {
+ assert currentDeck != null : "Command must operate on a deck.";
+ this.currentDeck = currentDeck;
+ }
+
+ public abstract void execute();
+}
diff --git a/src/main/java/seedu/ecardnomics/command/deck/DeleteCommand.java b/src/main/java/seedu/ecardnomics/command/deck/DeleteCommand.java
new file mode 100644
index 0000000000..6a054c3a2c
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/command/deck/DeleteCommand.java
@@ -0,0 +1,23 @@
+package seedu.ecardnomics.command.deck;
+
+import seedu.ecardnomics.Ui;
+import seedu.ecardnomics.deck.Deck;
+
+public class DeleteCommand extends DeckCommand {
+ private int flashCardID;
+ private boolean isFlashCardDeleted;
+
+ public DeleteCommand(Deck deck, int flashCardID, boolean isFlashCardDeleted) {
+ super(deck);
+ assert (flashCardID >= 0 && flashCardID < deck.size()) : "Index must be within range.";
+ this.flashCardID = flashCardID;
+ this.isFlashCardDeleted = isFlashCardDeleted;
+ }
+
+ @Override
+ public void execute() {
+ if (isFlashCardDeleted) {
+ currentDeck.delete(flashCardID);
+ }
+ }
+}
diff --git a/src/main/java/seedu/ecardnomics/command/deck/DoneEditCommand.java b/src/main/java/seedu/ecardnomics/command/deck/DoneEditCommand.java
new file mode 100644
index 0000000000..69fbf55f63
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/command/deck/DoneEditCommand.java
@@ -0,0 +1,21 @@
+package seedu.ecardnomics.command.deck;
+
+import seedu.ecardnomics.command.Command;
+import seedu.ecardnomics.deck.Deck;
+
+public class DoneEditCommand extends DeckCommand {
+
+ /** Constructor. */
+ public DoneEditCommand(Deck deck) {
+ super(deck);
+ }
+
+ @Override
+ public void execute() {
+ }
+
+ /** Returns if command given is an instance of DoneEditCommand
. */
+ public static boolean isDoneEdit(Command command) {
+ return command instanceof DoneEditCommand;
+ }
+}
diff --git a/src/main/java/seedu/ecardnomics/command/deck/HelpCommand.java b/src/main/java/seedu/ecardnomics/command/deck/HelpCommand.java
new file mode 100644
index 0000000000..e3320647f0
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/command/deck/HelpCommand.java
@@ -0,0 +1,40 @@
+package seedu.ecardnomics.command.deck;
+
+import seedu.ecardnomics.Ui;
+
+public class HelpCommand extends DeckCommand {
+ public static final String DECK_HELP = ""
+ // --------------------------------------------------------------------------------
+ + "eCardnomics.\n"
+ + "Deck Mode.\n"
+ + "\n"
+ + "Usage:\n"
+ + " add [ /ans ] Adds a new flash card to the current \n"
+ + " deck. \n"
+ + " list [/ans] Lists all flash cards in the current \n"
+ + " deck, optionally with answers. \n"
+ + " delete [-y] Deletes the flash card at list index \n"
+ + " update Updates the flash card at list index \n"
+ + " from the current deck. \n"
+ + " pptx [-y] [-cs | -oc Creates a PowerPoint slides based on \n"
+ + " ] current deck. \n"
+ + " start Enter Game Mode for this deck! Do your\n"
+ + " best! \n"
+ + " done Exits from Deck Mode and returns to \n"
+ + " Normal Mode. \n"
+ + " exit Exits the program. \n"
+ + " help Show this output. \n"
+ + "\n"
+ + "Options:\n"
+ + " --version Show version.";
+ // --------------------------------------------------------------------------------
+
+ public HelpCommand() {
+ super();
+ }
+
+ @Override
+ public void execute() {
+ Ui.printHelp(DECK_HELP);
+ }
+}
diff --git a/src/main/java/seedu/ecardnomics/command/deck/ListCommand.java b/src/main/java/seedu/ecardnomics/command/deck/ListCommand.java
new file mode 100644
index 0000000000..3c249e3bb0
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/command/deck/ListCommand.java
@@ -0,0 +1,20 @@
+package seedu.ecardnomics.command.deck;
+
+import seedu.ecardnomics.Ui;
+import seedu.ecardnomics.deck.Deck;
+
+public class ListCommand extends DeckCommand {
+ private final String arguments;
+
+ /** Constructor. */
+ public ListCommand(Deck deck, String arguments) {
+ super(deck);
+ assert arguments != null : "Arguments cannot be a null String.";
+ this.arguments = arguments;
+ }
+
+ @Override
+ public void execute() {
+ Ui.printDeck(currentDeck, !arguments.contains("/ans"));
+ }
+}
diff --git a/src/main/java/seedu/ecardnomics/command/deck/UpdateCommand.java b/src/main/java/seedu/ecardnomics/command/deck/UpdateCommand.java
new file mode 100644
index 0000000000..795b81a573
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/command/deck/UpdateCommand.java
@@ -0,0 +1,24 @@
+package seedu.ecardnomics.command.deck;
+
+import seedu.ecardnomics.deck.Deck;
+
+public class UpdateCommand extends DeckCommand {
+ private int flashCardID;
+ private String newQuestion;
+ private String newAnswer;
+
+ public UpdateCommand(Deck deck, int flashCardID, String question, String answer) {
+ super(deck);
+ this.flashCardID = flashCardID;
+ newQuestion = question;
+ newAnswer = answer;
+ }
+
+ @Override
+ public void execute() {
+ // Update question
+ currentDeck.get(flashCardID).setQuestion(newQuestion);
+ // Update answer
+ currentDeck.get(flashCardID).setAnswer(newAnswer);
+ }
+}
diff --git a/src/main/java/seedu/ecardnomics/command/game/DoneGameCommand.java b/src/main/java/seedu/ecardnomics/command/game/DoneGameCommand.java
new file mode 100644
index 0000000000..640d42de12
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/command/game/DoneGameCommand.java
@@ -0,0 +1,20 @@
+package seedu.ecardnomics.command.game;
+
+import seedu.ecardnomics.command.Command;
+import seedu.ecardnomics.deck.Deck;
+
+public class DoneGameCommand extends GameCommand {
+ /** Constructor. */
+ public DoneGameCommand(Deck deck) {
+ super(deck);
+ }
+
+ @Override
+ public void execute() {
+ }
+
+ /** Returns if command given is an instance of DoneEditCommand
. */
+ public static boolean isDoneGame(Command command) {
+ return command instanceof DoneGameCommand;
+ }
+}
diff --git a/src/main/java/seedu/ecardnomics/command/game/GameCommand.java b/src/main/java/seedu/ecardnomics/command/game/GameCommand.java
new file mode 100644
index 0000000000..bf0b47f5f9
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/command/game/GameCommand.java
@@ -0,0 +1,19 @@
+package seedu.ecardnomics.command.game;
+
+import seedu.ecardnomics.command.Command;
+import seedu.ecardnomics.deck.Deck;
+
+public abstract class GameCommand extends Command {
+ protected Deck currentDeck;
+
+ public GameCommand() {
+ currentDeck = null;
+ }
+
+ public GameCommand(Deck currentDeck) {
+ assert currentDeck != null : "Command must operate on a deck.";
+ this.currentDeck = currentDeck;
+ }
+
+ public abstract void execute();
+}
diff --git a/src/main/java/seedu/ecardnomics/command/game/GameResponseCommand.java b/src/main/java/seedu/ecardnomics/command/game/GameResponseCommand.java
new file mode 100644
index 0000000000..4d6bb93d04
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/command/game/GameResponseCommand.java
@@ -0,0 +1,19 @@
+package seedu.ecardnomics.command.game;
+
+public class GameResponseCommand extends GameCommand {
+ String attempt;
+
+ public GameResponseCommand(String userInput) {
+ super();
+ attempt = userInput;
+ }
+
+ @Override
+ public void execute() {
+ //
+ }
+
+ public String getAttempt() {
+ return attempt;
+ }
+}
diff --git a/src/main/java/seedu/ecardnomics/command/game/GameStartCommand.java b/src/main/java/seedu/ecardnomics/command/game/GameStartCommand.java
new file mode 100644
index 0000000000..23dc2896eb
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/command/game/GameStartCommand.java
@@ -0,0 +1,22 @@
+package seedu.ecardnomics.command.game;
+
+import seedu.ecardnomics.deck.Deck;
+import seedu.ecardnomics.game.Game;
+
+public class GameStartCommand extends GameCommand {
+ Game game;
+
+ /** Constructor. */
+ public GameStartCommand(Deck deck) {
+ super(deck);
+ }
+
+ @Override
+ public void execute() {
+ game = new Game(currentDeck);
+ }
+
+ public Game getGameInstance() {
+ return game;
+ }
+}
diff --git a/src/main/java/seedu/ecardnomics/command/game/HelpCommand.java b/src/main/java/seedu/ecardnomics/command/game/HelpCommand.java
new file mode 100644
index 0000000000..4a7135cf8a
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/command/game/HelpCommand.java
@@ -0,0 +1,32 @@
+package seedu.ecardnomics.command.game;
+
+import seedu.ecardnomics.Ui;
+import seedu.ecardnomics.game.Game;
+
+public class HelpCommand extends GameCommand {
+ public static final String GAME_HELP = ""
+ // --------------------------------------------------------------------------------
+ + "eCardnomics.\n"
+ + "Game Mode.\n"
+ + "\n"
+ + "Usage:\n"
+ + " done Exits from Game Mode and returns to Normal Mode.\n"
+ + " exit Exits the program.\n"
+ + " help Show this output.\n"
+ + "\n"
+ + "Options:\n"
+ + " --version Show version.\n"
+ + "\n"
+ + "Gameplay:\n"
+ + Game.GAMEPLAY_MESSAGE;
+ // --------------------------------------------------------------------------------
+
+ public HelpCommand() {
+ super();
+ }
+
+ @Override
+ public void execute() {
+ Ui.printHelp(GAME_HELP);
+ }
+}
diff --git a/src/main/java/seedu/ecardnomics/command/normal/CreateCommand.java b/src/main/java/seedu/ecardnomics/command/normal/CreateCommand.java
new file mode 100644
index 0000000000..9990b3a40c
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/command/normal/CreateCommand.java
@@ -0,0 +1,30 @@
+package seedu.ecardnomics.command.normal;
+
+import seedu.ecardnomics.Ui;
+import seedu.ecardnomics.deck.Deck;
+import seedu.ecardnomics.deck.DeckList;
+
+/**
+ * Creates a deck adds it to the existing deck list.
+ */
+public class CreateCommand extends NormalCommand {
+ Deck newDeck;
+
+ /**
+ * Constructor.
+ *
+ * @param deckList reference to the existing list of decks
+ * @param deck the new deck created
+ */
+ public CreateCommand(DeckList deckList, Deck deck) {
+ super(deckList);
+ assert deck != null : "Do not add null objects to the list.";
+ newDeck = deck;
+ }
+
+ @Override
+ public void execute() {
+ deckList.addDeck(newDeck);
+ Ui.printNewDeck(newDeck);
+ }
+}
diff --git a/src/main/java/seedu/ecardnomics/command/normal/DecksCommand.java b/src/main/java/seedu/ecardnomics/command/normal/DecksCommand.java
new file mode 100644
index 0000000000..3c22dd54df
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/command/normal/DecksCommand.java
@@ -0,0 +1,24 @@
+package seedu.ecardnomics.command.normal;
+
+import seedu.ecardnomics.Ui;
+import seedu.ecardnomics.deck.DeckList;
+
+/**
+ * Lists all the existing deck.
+ */
+public class DecksCommand extends NormalCommand {
+
+ /** Constructor. */
+ public DecksCommand(DeckList deckList) {
+ super(deckList);
+ }
+
+ @Override
+ public void execute() {
+ if (deckList.size() == 0) {
+ Ui.printMessage("There are no decks to list.");
+ } else {
+ Ui.printDeckList(deckList);
+ }
+ }
+}
diff --git a/src/main/java/seedu/ecardnomics/command/normal/DeleteDeckCommand.java b/src/main/java/seedu/ecardnomics/command/normal/DeleteDeckCommand.java
new file mode 100644
index 0000000000..d8d0c6a7a4
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/command/normal/DeleteDeckCommand.java
@@ -0,0 +1,22 @@
+package seedu.ecardnomics.command.normal;
+
+import seedu.ecardnomics.deck.DeckList;
+
+public class DeleteDeckCommand extends NormalCommand {
+ private int index;
+ private boolean isDeckDeleted;
+
+ public DeleteDeckCommand(DeckList decks, int index, boolean isDeckDeleted) {
+ super(decks);
+ assert (index >= 0 && index < decks.size()) : "Index must be within range.";
+ this.index = index;
+ this.isDeckDeleted = isDeckDeleted;
+ }
+
+ @Override
+ public void execute() {
+ if (isDeckDeleted) {
+ deckList.removeDeck(index);
+ }
+ }
+}
diff --git a/src/main/java/seedu/ecardnomics/command/normal/EditCommand.java b/src/main/java/seedu/ecardnomics/command/normal/EditCommand.java
new file mode 100644
index 0000000000..9486b3df66
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/command/normal/EditCommand.java
@@ -0,0 +1,32 @@
+package seedu.ecardnomics.command.normal;
+
+import seedu.ecardnomics.Ui;
+import seedu.ecardnomics.command.Command;
+import seedu.ecardnomics.deck.Deck;
+import seedu.ecardnomics.deck.DeckList;
+
+public class EditCommand extends NormalCommand {
+ Deck deck;
+
+ /** Constructor. */
+ public EditCommand(DeckList deckList, Deck deck) {
+ super(deckList);
+ assert deck != null : "Do not operate on a null reference.";
+ this.deck = deck;
+ }
+
+ @Override
+ public void execute() {
+ Ui.printDeckWelcome(deckList.getIndexOf(deck) + 1, deck);
+ }
+
+ /** Returns Deck which is to be edited. */
+ public Deck getDeck() {
+ return deck;
+ }
+
+ /** Returns if command given is an instance of EditCommand
. */
+ public static boolean isEdit(Command command) {
+ return command instanceof EditCommand;
+ }
+}
diff --git a/src/main/java/seedu/ecardnomics/command/normal/HelpCommand.java b/src/main/java/seedu/ecardnomics/command/normal/HelpCommand.java
new file mode 100644
index 0000000000..0315db2a61
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/command/normal/HelpCommand.java
@@ -0,0 +1,45 @@
+package seedu.ecardnomics.command.normal;
+
+import seedu.ecardnomics.Ui;
+import seedu.ecardnomics.deck.DeckList;
+
+public class HelpCommand extends NormalCommand {
+ public static final String NORMAL_HELP = ""
+ // --------------------------------------------------------------------------------
+ + "eCardnomics.\n"
+ + "Normal Mode.\n"
+ + "\n"
+ + "Usage:\n"
+ + " create [/tag [ ...]] Creates a new deck of flash cards\n"
+ + " named . \n"
+ + " decks Lists all available decks. \n"
+ + " edit Enter Deck Mode for editing the \n"
+ + " deck at list index . \n"
+ + " start Enter Game Mode for deck at list \n"
+ + " index ! Do your best! \n"
+ + " delete [-y] Deletes the deck at list index \n"
+ + " from list of decks. \n"
+ + " pptx [-y] [-cs | -oc Creates a PowerPoint slides based\n"
+ + " ] on the deck at list index .\n"
+ + " tag /tag [ ...] Tags the deck at list index \n"
+ + " with 1 or more tags. \n"
+ + " untag /tag [ ...] Untags specified s of the \n"
+ + " deck at list index . \n"
+ + " search [ ...] Search deck list for decks tagged\n"
+ + " with specified s. \n"
+ + " exit Exits the program.\n"
+ + " help Show this output.\n"
+ + "\n"
+ + "Options:\n"
+ + " --version Show version.";
+ // --------------------------------------------------------------------------------
+
+ public HelpCommand() {
+ super();
+ }
+
+ @Override
+ public void execute() {
+ Ui.printHelp(NORMAL_HELP);
+ }
+}
diff --git a/src/main/java/seedu/ecardnomics/command/normal/NormalCommand.java b/src/main/java/seedu/ecardnomics/command/normal/NormalCommand.java
new file mode 100644
index 0000000000..43ddf26ef0
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/command/normal/NormalCommand.java
@@ -0,0 +1,22 @@
+package seedu.ecardnomics.command.normal;
+
+import seedu.ecardnomics.command.Command;
+import seedu.ecardnomics.deck.DeckList;
+
+/**
+ * Parent Command in Normal Mode.
+ */
+public abstract class NormalCommand extends Command {
+ protected DeckList deckList;
+
+ public NormalCommand() {
+ deckList = null;
+ }
+
+ public NormalCommand(DeckList deckList) {
+ assert deckList != null : "Command must operate on a deck list";
+ this.deckList = deckList;
+ }
+
+ public abstract void execute();
+}
diff --git a/src/main/java/seedu/ecardnomics/command/normal/PowerPointCommand.java b/src/main/java/seedu/ecardnomics/command/normal/PowerPointCommand.java
new file mode 100644
index 0000000000..5990b4c5f5
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/command/normal/PowerPointCommand.java
@@ -0,0 +1,48 @@
+package seedu.ecardnomics.command.normal;
+
+import seedu.ecardnomics.deck.Deck;
+import seedu.ecardnomics.deck.DeckList;
+import seedu.ecardnomics.powerpoint.PowerPoint;
+
+import java.awt.Color;
+
+public class PowerPointCommand extends NormalCommand {
+ Deck deck;
+ PowerPoint pptx;
+ boolean isPpptxCreated;
+
+ /** Constructor for default printing. */
+ public PowerPointCommand(DeckList deckList, Deck deck, boolean isPpptxCreated) {
+ super(deckList);
+ assert deck != null : "Do not operate on a null reference.";
+ this.deck = deck;
+ pptx = new PowerPoint(deck);
+ this.isPpptxCreated = isPpptxCreated;
+ }
+
+ /** Constructor for -cs option. */
+ public PowerPointCommand(DeckList deckList, Deck deck, boolean isPpptxCreated, int csIndex) {
+ super(deckList);
+ assert deck != null : "Do not operate on a null reference.";
+ this.deck = deck;
+ pptx = new PowerPoint(deck, csIndex);
+ this.isPpptxCreated = isPpptxCreated;
+ }
+
+ /** Constructor for -oc option. */
+ public PowerPointCommand(DeckList deckList, Deck deck, boolean isPpptxCreated, String bgColorString,
+ String txtColorString, Color bgColor, Color txtColor) {
+ super(deckList);
+ assert deck != null : "Do not operate on a null reference.";
+ this.deck = deck;
+ pptx = new PowerPoint(deck, bgColorString, txtColorString, bgColor, txtColor);
+ this.isPpptxCreated = isPpptxCreated;
+ }
+
+ @Override
+ public void execute() {
+ if (isPpptxCreated) {
+ pptx.createNewPowerPoint();
+ }
+ }
+}
diff --git a/src/main/java/seedu/ecardnomics/command/normal/SearchCommand.java b/src/main/java/seedu/ecardnomics/command/normal/SearchCommand.java
new file mode 100644
index 0000000000..a571d290f7
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/command/normal/SearchCommand.java
@@ -0,0 +1,39 @@
+package seedu.ecardnomics.command.normal;
+
+import seedu.ecardnomics.Ui;
+import seedu.ecardnomics.deck.Deck;
+import seedu.ecardnomics.deck.DeckList;
+
+import java.util.ArrayList;
+
+public class SearchCommand extends NormalCommand {
+ private String[] relevantTags;
+
+ /** Constructor. */
+ public SearchCommand(DeckList decks, String[] relevantTags) {
+ super(decks);
+ assert (relevantTags.length != 0) : "Relevant tags must be provided.";
+ this.relevantTags = relevantTags;
+ }
+
+ @Override
+ public void execute() {
+ String decksHavingTags = "";
+ ArrayList uniqueTagList = new ArrayList<>();
+ for (String tag: relevantTags) {
+ String lowerCaseTrimmedTag = tag.toLowerCase();
+ if (!uniqueTagList.contains(lowerCaseTrimmedTag) & !lowerCaseTrimmedTag.isEmpty()) {
+ uniqueTagList.add(tag);
+ }
+ }
+ for (String tag: uniqueTagList) {
+ for (int i = 0; i < deckList.size(); i++) {
+ String tagLowerCase = deckList.getDeck(i).getTagString().toLowerCase();
+ if (tagLowerCase.contains(tag.toLowerCase())) {
+ decksHavingTags += "\n" + deckList.getNameWithTags(i);
+ }
+ }
+ }
+ Ui.printSearchDecksLine(decksHavingTags);
+ }
+}
diff --git a/src/main/java/seedu/ecardnomics/command/normal/StartCommand.java b/src/main/java/seedu/ecardnomics/command/normal/StartCommand.java
new file mode 100644
index 0000000000..9a6aecfaea
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/command/normal/StartCommand.java
@@ -0,0 +1,34 @@
+package seedu.ecardnomics.command.normal;
+
+import seedu.ecardnomics.Ui;
+import seedu.ecardnomics.command.Command;
+import seedu.ecardnomics.command.game.GameStartCommand;
+import seedu.ecardnomics.deck.Deck;
+import seedu.ecardnomics.deck.DeckList;
+
+public class StartCommand extends NormalCommand {
+ Deck deck;
+
+ /** Constructor. */
+ public StartCommand(DeckList deckList, Deck deck) {
+ super(deckList);
+ assert deck != null : "Do not operate on a null reference.";
+ this.deck = deck;
+ }
+
+ @Override
+ public void execute() {
+ Ui.printGameWelcome(deckList.getIndexOf(deck) + 1, deck);
+ new GameStartCommand(deck);
+ }
+
+ /** Returns Deck whose Game Mode is in progress. */
+ public Deck getDeck() {
+ return deck;
+ }
+
+ /** Returns if command given is an instance of StartCommand
. */
+ public static boolean isStart(Command command) {
+ return command instanceof StartCommand;
+ }
+}
diff --git a/src/main/java/seedu/ecardnomics/command/normal/TagCommand.java b/src/main/java/seedu/ecardnomics/command/normal/TagCommand.java
new file mode 100644
index 0000000000..149ee2ed3d
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/command/normal/TagCommand.java
@@ -0,0 +1,29 @@
+package seedu.ecardnomics.command.normal;
+
+import seedu.ecardnomics.Ui;
+import seedu.ecardnomics.deck.DeckList;
+
+import java.util.ArrayList;
+
+/**
+ * Adds tags to existing deck.
+ */
+public class TagCommand extends NormalCommand {
+ private int index;
+ private ArrayList newTags;
+
+ /** Constructor. */
+ public TagCommand(DeckList decks, int index, ArrayList newTags) {
+ super(decks);
+ assert (index >= 0 && index < decks.size()) : "Index must be within range.";
+ this.index = index;
+ assert (newTags.size() != 0) : "Tags must be provided.";
+ this.newTags = newTags;
+ }
+
+ @Override
+ public void execute() {
+ deckList.getDeck(index).addTag(newTags);
+ Ui.printNewTags(deckList.getDeck(index).getName(), newTags);
+ }
+}
diff --git a/src/main/java/seedu/ecardnomics/command/normal/UntagCommand.java b/src/main/java/seedu/ecardnomics/command/normal/UntagCommand.java
new file mode 100644
index 0000000000..7d0f99af32
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/command/normal/UntagCommand.java
@@ -0,0 +1,64 @@
+package seedu.ecardnomics.command.normal;
+
+import seedu.ecardnomics.Ui;
+import seedu.ecardnomics.deck.DeckList;
+
+import java.util.ArrayList;
+
+/**
+ * Removes tags from the existing deck.
+ */
+public class UntagCommand extends NormalCommand {
+ private int index;
+ private ArrayList removedTags;
+ private boolean isYes = false;
+
+ /** Constructor. */
+ public UntagCommand(DeckList decks, int index, ArrayList removedTags, boolean isYes) {
+ super(decks);
+ assert (index >= 0 && index < decks.size()) : "Index must be within range.";
+ this.index = index;
+ assert (removedTags.size() != 0) : "Remove tags must be provided.";
+ this.removedTags = removedTags;
+ this.isYes = isYes;
+ }
+
+ @Override
+ public void execute() {
+ String deckName = deckList.getDeck(index).getName();
+ boolean isTagsValid = checkTagsExist(removedTags);
+
+ if (isTagsValid) {
+ if (isYes) {
+ deckList.getDeck(index).removeTag(removedTags);
+ Ui.printTagsRemovedLine(deckName, removedTags);
+ } else if (!isYes) {
+ boolean isTagsRemoved = Ui.getRemovedTagsConfirmation(removedTags, deckName);
+ if (isTagsRemoved) {
+ deckList.getDeck(index).removeTag(removedTags);
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks if the deck specified contains the removed tags.
+ *
+ * @param removedTags String[] list tags to be removed
+ * @return a boolean value indicating if all the tags exist
+ */
+ public boolean checkTagsExist(ArrayList removedTags) {
+ boolean isExist = true;
+ ArrayList availableTagList = deckList.getDeck(index).getTag();
+ outerLoop:
+ for (String removedTag : removedTags) {
+ if (!availableTagList.contains(removedTag)) {
+ isExist = false;
+ Ui.printInvalidTagsLine(removedTag);
+ break outerLoop;
+ }
+ }
+ return isExist;
+ }
+
+}
diff --git a/src/main/java/seedu/ecardnomics/deck/Deck.java b/src/main/java/seedu/ecardnomics/deck/Deck.java
new file mode 100644
index 0000000000..154ee1d8a0
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/deck/Deck.java
@@ -0,0 +1,164 @@
+package seedu.ecardnomics.deck;
+
+import java.util.ArrayList;
+
+/**
+ * Deck of flashcards.
+ */
+public class Deck {
+ private String name;
+ private ArrayList tags;
+ private ArrayList deck;
+
+ /** Constructor. */
+ public Deck(String name) {
+ assert (name != null && !name.isEmpty()) : "A deck requires a name.";
+ this.name = name;
+ deck = new ArrayList<>();
+ tags = new ArrayList<>();
+ }
+
+ /**
+ * Constructors with tags parameter.
+ *
+ * @param name the name of the new deck
+ * @param tags tags of the new deck
+ */
+ public Deck(String name, ArrayList tags) {
+ assert (name != null && !name.isEmpty()) : "A deck requires a name.";
+ this.name = name;
+ deck = new ArrayList<>();
+ this.tags = new ArrayList<>();
+ this.tags = tags;
+ }
+
+ /**
+ * Gets the name of the deck.
+ *
+ * @return name of deck
+ */
+ public String getName() {
+ return name;
+ }
+
+ public String getTagString() {
+ String tagString = "";
+ for (int j = 0; j < tags.size(); j++) {
+ tagString += tags.get(j);
+ if (j < tags.size() - 1) {
+ tagString += " | ";
+ }
+ }
+ return tagString;
+ }
+
+ public ArrayList getTag() {
+ return tags;
+ }
+
+ /**
+ * Adds tag to the deck.
+ *
+ * @param newTags an unique ArraList to be added to the deck
+ */
+ public void addTag(ArrayList newTags) {
+ for (String tag: newTags) {
+ if (!tags.contains(tag)) {
+ tags.add(tag);
+ }
+ }
+ }
+
+ public void removeTag(ArrayList deletedTags) {
+ for (String tag: deletedTags) {
+ tags.remove(tag);
+ }
+ }
+
+ /**
+ * Sets the name of the deck.
+ *
+ * @param name new name of the deck
+ */
+ public void setName(String name) {
+ assert (name != null && !name.isEmpty()) : "A deck requires a name.";
+ this.name = name;
+ }
+
+ /**
+ * Retrieves the inherent ArrayList data structure of the deck.
+ *
+ * @return ArrayList of FlashCards
+ */
+ public ArrayList getDeck() {
+ return deck;
+ }
+
+ /**
+ * Retrieves the flashcard at specified index.
+ *
+ * @param index Index of flashcard to be found
+ * @return FlashCard at index
+ */
+ public FlashCard get(int index) {
+ assert (index >= 0 && index < deck.size()) : "Index should be within range";
+ return deck.get(index);
+ }
+
+ /**
+ * Adds a flashcard to the deck.
+ *
+ * @param flashCard Reference to the flashcard to be added
+ */
+ public void add(FlashCard flashCard) {
+ assert flashCard != null : "Do not add null objects into deck";
+ deck.add(flashCard);
+ }
+
+ /**
+ * Returns size of current deck.
+ *
+ * @return size of current deck
+ */
+ public int size() {
+ return deck.size();
+ }
+
+ /**
+ * Deletes the flashcard at specified index from the deck.
+ *
+ * @param index Index of flashcard to be deleted
+ */
+ public void delete(int index) {
+ assert (index >= 0 && index < deck.size()) : "Index should be within range";
+ deck.remove(index);
+ }
+
+ @Override
+ public String toString() {
+ String output = name + ":" + System.lineSeparator();
+ output += this.toString(false);
+ return output;
+ }
+
+ public String toString(boolean isQuestionOnly) {
+ String output = "";
+ if (this.deck.size() == 0) {
+ return output;
+ }
+
+ for (int i = 0; i < deck.size(); i++) {
+ String serialNumber = (i + 1) + ". ";
+ // Always print the question.
+ output += serialNumber + deck.get(i).toString(true, serialNumber.length());
+ if (!isQuestionOnly) {
+ // Add in the answer
+ output += System.lineSeparator() + deck.get(i).toString(false, serialNumber.length());
+ }
+ if (i != deck.size() - 1) {
+ output += "\n\n";
+ }
+ }
+ return output;
+ }
+}
diff --git a/src/main/java/seedu/ecardnomics/deck/DeckList.java b/src/main/java/seedu/ecardnomics/deck/DeckList.java
new file mode 100644
index 0000000000..cd7da3926a
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/deck/DeckList.java
@@ -0,0 +1,135 @@
+package seedu.ecardnomics.deck;
+
+import java.util.ArrayList;
+
+/**
+ * List for storing decks.
+ */
+public class DeckList {
+ private ArrayList deckList;
+
+ /** Constructor. */
+ public DeckList() {
+ deckList = new ArrayList<>();
+ }
+
+ /**
+ * Adds a deck to the list.
+ *
+ * @param deck reference to Deck to be added
+ */
+ public void addDeck(Deck deck) {
+ assert deck != null : "Do not add null object to list.";
+ assert !deckList.contains(deck.getName()) : "The deck list has duplicate decks.";
+ deckList.add(deck);
+ }
+
+ /**
+ * Removes a deck from the list.
+ *
+ * @param index int representing index of deck to to be removed
+ */
+ public void removeDeck(int index) {
+ assert (index >= 0 && index < deckList.size()) : "Index should be within range.";
+ deckList.remove(index);
+ }
+
+ /**
+ * Checks whether a deck with the same deck name is already in the deck.
+ * @param deck reference to deck to be checked
+ * @return boolean
+ */
+ public boolean contains(Deck deck) {
+ for (Deck thisDeck : deckList) {
+ if (thisDeck.getName().equals(deck.getName())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns size of current list.
+ *
+ * @return size of list
+ */
+ public int size() {
+ return deckList.size();
+ }
+
+ /**
+ * Returns a deck from deck list based on given index.
+ *
+ * @param index Index of deck to be found
+ * @return Deck found at index
+ */
+ public Deck getDeck(int index) {
+ assert (index >= 0 && index < deckList.size()) : "Index should be within range.";
+ return deckList.get(index);
+ }
+
+ /**
+ * Returns the names of all decks in the deck list in ArrayList.
+ *
+ * @return ArrayList
+ */
+ public ArrayList getAllNames() {
+ ArrayList deckNameOfDecks = new ArrayList<>();
+ for (Deck deck: deckList) {
+ deckNameOfDecks.add(deck.getName());
+ }
+
+ return deckNameOfDecks;
+ }
+
+ /**
+ * Returns index of deck in deck list.
+ *
+ * @param deck Deck which index is to be found
+ * @return Index found of deck
+ */
+ public int getIndexOf(Deck deck) {
+ assert deck != null : "Do not try to get index of a null reference.";
+ return deckList.indexOf(deck);
+ }
+
+ /**
+ * Returns a deck name with its tag.
+ *
+ * @param index the index of that deck
+ * @return a String including of the deck name and tags.
+ */
+ public String getNameWithTags(int index) {
+ String nameWithTags = "";
+ String offset = "";
+ while (offset.length() <= (index / 10 + 2)) {
+ offset += " ";
+ }
+ nameWithTags += (index + 1) + ". " + deckList.get(index).getName();
+ if (deckList.get(index).getTag().size() != 0) {
+ nameWithTags += "\n";
+ nameWithTags += offset + "Tags: " + deckList.get(index).getTagString();
+ }
+
+ return nameWithTags;
+ }
+
+ /**
+ * Returns a string including index and name, tag of all the deck available.
+ *
+ * @return A String representing index, name and tag
+ */
+ @Override
+ public String toString() {
+ String output = "";
+ for (int i = 0; i < deckList.size(); i++) {
+ output += getNameWithTags(i);
+ if (i != deckList.size() - 1) {
+ output += "\n";
+ }
+ }
+
+ return output;
+ }
+
+}
diff --git a/src/main/java/seedu/ecardnomics/deck/FlashCard.java b/src/main/java/seedu/ecardnomics/deck/FlashCard.java
new file mode 100644
index 0000000000..be40a8afd5
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/deck/FlashCard.java
@@ -0,0 +1,85 @@
+package seedu.ecardnomics.deck;
+
+import seedu.ecardnomics.Ui;
+
+/**
+ * Flashcards that contain a question and asnwer.
+ */
+public class FlashCard {
+ private String question;
+ private String answer;
+
+ private static final String QN_LABEL = "Question: ";
+ private static final String ANS_LABEL = "Answer: ";
+
+ /** Constructor. */
+ public FlashCard(String question, String answer) {
+ assert (question != null && !question.isEmpty()) : "Flashcard must have one question.";
+ this.question = question;
+ assert (answer != null && !answer.isEmpty()) : "Flashcard must have one answer";
+ this.answer = answer;
+ }
+
+ /**
+ * Get the answer of the flashcard.
+ *
+ * @return String representing the answer on the flashcard
+ */
+ public String getAnswer() {
+ return answer;
+ }
+
+ /**
+ * Get the question of the flashcard.
+ *
+ * @return String representing the question on the flashcard
+ */
+ public String getQuestion() {
+ return question;
+ }
+
+ /**
+ * Set the answer of the flashcard.
+ *
+ * @param answer new answer on the flashcard
+ */
+ public void setAnswer(String answer) {
+ assert (answer != null && !answer.isEmpty()) : "Flashcard must have one answer";
+ this.answer = answer;
+ }
+
+ /**
+ * Set the question of the flashcard.
+ *
+ * @param question new answer on the flashcard
+ */
+ public void setQuestion(String question) {
+ assert (question != null && !question.isEmpty()) : "Flashcard must have one question.";
+ this.question = question;
+ }
+
+ /* Would not recommend using this method unless there is no offset to be printed before QN_LABEL */
+ public String toString() {
+ return QN_LABEL + Ui.prettyPrintFormatter(question, QN_LABEL.length()) + System.lineSeparator()
+ + ANS_LABEL + Ui.prettyPrintFormatter(answer, ANS_LABEL.length());
+ }
+
+ /**
+ * Returns a String that contains the requested details (question or answer)
+ * for the flashcard for printing. The String is formatted such that the
+ * details will fit between offset characters from the start of the line and
+ * Ui.DASH_LINES.length().
+ *
+ * @param isQuestion indicates whether the required detail is question or answer
+ * @param offset Number of characters from the start of the line
+ * @return String for displaying question if isQuestion, answer otherwise
+ */
+ public String toString(boolean isQuestion, int offset) {
+ if (isQuestion) {
+ return QN_LABEL + Ui.prettyPrintFormatter(question, offset + QN_LABEL.length());
+ } else {
+ return " ".repeat(offset) + ANS_LABEL
+ + Ui.prettyPrintFormatter(answer, offset + ANS_LABEL.length());
+ }
+ }
+}
diff --git a/src/main/java/seedu/ecardnomics/exceptions/BothOcAndCsException.java b/src/main/java/seedu/ecardnomics/exceptions/BothOcAndCsException.java
new file mode 100644
index 0000000000..997459b311
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/exceptions/BothOcAndCsException.java
@@ -0,0 +1,11 @@
+package seedu.ecardnomics.exceptions;
+
+public class BothOcAndCsException extends Exception {
+ public static final String BOTH_OC_AND_CS_LINE =
+ "Input should not have both -oc and -cs option.";
+
+ @Override
+ public String getMessage() {
+ return BOTH_OC_AND_CS_LINE;
+ }
+}
diff --git a/src/main/java/seedu/ecardnomics/exceptions/ColorsNotAvailException.java b/src/main/java/seedu/ecardnomics/exceptions/ColorsNotAvailException.java
new file mode 100644
index 0000000000..932aca127e
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/exceptions/ColorsNotAvailException.java
@@ -0,0 +1,11 @@
+package seedu.ecardnomics.exceptions;
+
+public class ColorsNotAvailException extends Exception {
+ public static final String COLORS_NOT_AVAIL_LINE =
+ "Colors option is not available";
+
+ @Override
+ public String getMessage() {
+ return COLORS_NOT_AVAIL_LINE;
+ }
+}
diff --git a/src/main/java/seedu/ecardnomics/exceptions/CsIndexFormatException.java b/src/main/java/seedu/ecardnomics/exceptions/CsIndexFormatException.java
new file mode 100644
index 0000000000..d0ec24f2fa
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/exceptions/CsIndexFormatException.java
@@ -0,0 +1,12 @@
+package seedu.ecardnomics.exceptions;
+
+public class CsIndexFormatException extends Exception {
+ public static final String CS_INDEX_FORMAT_LINE =
+ "-cs needs to be followed by a integer between 1 and 10 (inclusive)";
+
+ @Override
+ public String getMessage() {
+ return CS_INDEX_FORMAT_LINE;
+
+ }
+}
diff --git a/src/main/java/seedu/ecardnomics/exceptions/CsIndexRangeException.java b/src/main/java/seedu/ecardnomics/exceptions/CsIndexRangeException.java
new file mode 100644
index 0000000000..fd3c342c28
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/exceptions/CsIndexRangeException.java
@@ -0,0 +1,11 @@
+package seedu.ecardnomics.exceptions;
+
+public class CsIndexRangeException extends Exception {
+ public static final String CS_INDEX_RANGE_LINE =
+ "-cs index not within the correct range, [1,10]";
+
+ @Override
+ public String getMessage() {
+ return CS_INDEX_RANGE_LINE;
+ }
+}
diff --git a/src/main/java/seedu/ecardnomics/exceptions/DeckRangeException.java b/src/main/java/seedu/ecardnomics/exceptions/DeckRangeException.java
new file mode 100644
index 0000000000..f43b8b440f
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/exceptions/DeckRangeException.java
@@ -0,0 +1,11 @@
+package seedu.ecardnomics.exceptions;
+
+public class DeckRangeException extends Exception {
+ public static final String DECK_RANGE_LINE =
+ "Index should be within range of number of decks!";
+
+ @Override
+ public String getMessage() {
+ return DECK_RANGE_LINE;
+ }
+}
diff --git a/src/main/java/seedu/ecardnomics/exceptions/DuplicateDeckException.java b/src/main/java/seedu/ecardnomics/exceptions/DuplicateDeckException.java
new file mode 100644
index 0000000000..c19fff7457
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/exceptions/DuplicateDeckException.java
@@ -0,0 +1,11 @@
+package seedu.ecardnomics.exceptions;
+
+public class DuplicateDeckException extends Exception {
+ public static final String DUPLICATE_DECK_LINE =
+ "This deck name has already existed, please choose another name.";
+
+ @Override
+ public String getMessage() {
+ return DUPLICATE_DECK_LINE;
+ }
+}
diff --git a/src/main/java/seedu/ecardnomics/exceptions/EmptyInputException.java b/src/main/java/seedu/ecardnomics/exceptions/EmptyInputException.java
new file mode 100644
index 0000000000..acf3b0fd95
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/exceptions/EmptyInputException.java
@@ -0,0 +1,11 @@
+package seedu.ecardnomics.exceptions;
+
+public class EmptyInputException extends Exception {
+ public static final String EMPTY_INPUT_LINE =
+ "Input shouldn't be empty! Returning...";
+
+ @Override
+ public String getMessage() {
+ return EMPTY_INPUT_LINE;
+ }
+}
diff --git a/src/main/java/seedu/ecardnomics/exceptions/EmptyQnAException.java b/src/main/java/seedu/ecardnomics/exceptions/EmptyQnAException.java
new file mode 100644
index 0000000000..0f6d856283
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/exceptions/EmptyQnAException.java
@@ -0,0 +1,11 @@
+package seedu.ecardnomics.exceptions;
+
+public class EmptyQnAException extends Exception {
+ public static final String EMPTY_QNA_LINE =
+ "A flash card must have a question and answer.";
+
+ @Override
+ public String getMessage() {
+ return EMPTY_QNA_LINE;
+ }
+}
diff --git a/src/main/java/seedu/ecardnomics/exceptions/FlashCardRangeException.java b/src/main/java/seedu/ecardnomics/exceptions/FlashCardRangeException.java
new file mode 100644
index 0000000000..4043359182
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/exceptions/FlashCardRangeException.java
@@ -0,0 +1,11 @@
+package seedu.ecardnomics.exceptions;
+
+public class FlashCardRangeException extends Exception {
+ public static final String FLASHCARD_RANGE_LINE =
+ "Index should be within range of number of flash cards!";
+
+ @Override
+ public String getMessage() {
+ return FLASHCARD_RANGE_LINE;
+ }
+}
diff --git a/src/main/java/seedu/ecardnomics/exceptions/IndexFormatException.java b/src/main/java/seedu/ecardnomics/exceptions/IndexFormatException.java
new file mode 100644
index 0000000000..0b5c86d648
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/exceptions/IndexFormatException.java
@@ -0,0 +1,11 @@
+package seedu.ecardnomics.exceptions;
+
+public class IndexFormatException extends Exception {
+ public static final String INDEX_FORMAT_LINE =
+ "Command should be followed by a valid positive !";
+
+ @Override
+ public String getMessage() {
+ return INDEX_FORMAT_LINE;
+ }
+}
diff --git a/src/main/java/seedu/ecardnomics/exceptions/InvalidListCommandException.java b/src/main/java/seedu/ecardnomics/exceptions/InvalidListCommandException.java
new file mode 100644
index 0000000000..ebaa3822a5
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/exceptions/InvalidListCommandException.java
@@ -0,0 +1,11 @@
+package seedu.ecardnomics.exceptions;
+
+public class InvalidListCommandException extends Exception {
+ public static final String INVALID_LIST_COMMAND_LINE =
+ "Invalid argument for list command";
+
+ @Override
+ public String getMessage() {
+ return INVALID_LIST_COMMAND_LINE;
+ }
+}
diff --git a/src/main/java/seedu/ecardnomics/exceptions/InvalidOptionsException.java b/src/main/java/seedu/ecardnomics/exceptions/InvalidOptionsException.java
new file mode 100644
index 0000000000..9834b6e3ac
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/exceptions/InvalidOptionsException.java
@@ -0,0 +1,11 @@
+package seedu.ecardnomics.exceptions;
+
+public class InvalidOptionsException extends Exception {
+ public static final String INVALID_OPTIONS_LINE =
+ "Invalid options in command";
+
+ @Override
+ public String getMessage() {
+ return INVALID_OPTIONS_LINE;
+ }
+}
diff --git a/src/main/java/seedu/ecardnomics/exceptions/InvalidPptxArgumentException.java b/src/main/java/seedu/ecardnomics/exceptions/InvalidPptxArgumentException.java
new file mode 100644
index 0000000000..07feba7875
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/exceptions/InvalidPptxArgumentException.java
@@ -0,0 +1,11 @@
+package seedu.ecardnomics.exceptions;
+
+public class InvalidPptxArgumentException extends Exception {
+ public static final String INVALID_PPTX_ARGUMENT_LINE =
+ "Invalid arguments for PPTX command";
+
+ @Override
+ public String getMessage() {
+ return INVALID_PPTX_ARGUMENT_LINE;
+ }
+}
diff --git a/src/main/java/seedu/ecardnomics/exceptions/NoAlphaNumericInputException.java b/src/main/java/seedu/ecardnomics/exceptions/NoAlphaNumericInputException.java
new file mode 100644
index 0000000000..54b0f33f10
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/exceptions/NoAlphaNumericInputException.java
@@ -0,0 +1,11 @@
+package seedu.ecardnomics.exceptions;
+
+public class NoAlphaNumericInputException extends Exception {
+ public static final String NO_ALPHANUMERIC_LINE =
+ "Input should contain at least one alphanumeric character! Returning...";
+
+ @Override
+ public String getMessage() {
+ return NO_ALPHANUMERIC_LINE;
+ }
+}
diff --git a/src/main/java/seedu/ecardnomics/exceptions/NoSeparatorException.java b/src/main/java/seedu/ecardnomics/exceptions/NoSeparatorException.java
new file mode 100644
index 0000000000..f911488c2f
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/exceptions/NoSeparatorException.java
@@ -0,0 +1,11 @@
+package seedu.ecardnomics.exceptions;
+
+public class NoSeparatorException extends Exception {
+ public static final String NO_SEPARATOR_LINE =
+ " separator should be added before tags!";
+
+ @Override
+ public String getMessage() {
+ return NO_SEPARATOR_LINE;
+ }
+}
diff --git a/src/main/java/seedu/ecardnomics/exceptions/NumberTooBigException.java b/src/main/java/seedu/ecardnomics/exceptions/NumberTooBigException.java
new file mode 100644
index 0000000000..74568da418
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/exceptions/NumberTooBigException.java
@@ -0,0 +1,10 @@
+package seedu.ecardnomics.exceptions;
+
+public class NumberTooBigException extends Exception {
+ public static final String NUMBER_TOO_BIG_LINE = "Index is too large.";
+
+ @Override
+ public String getMessage() {
+ return NUMBER_TOO_BIG_LINE;
+ }
+}
diff --git a/src/main/java/seedu/ecardnomics/game/Game.java b/src/main/java/seedu/ecardnomics/game/Game.java
new file mode 100644
index 0000000000..940cedad4d
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/game/Game.java
@@ -0,0 +1,41 @@
+package seedu.ecardnomics.game;
+
+import seedu.ecardnomics.command.Command;
+import seedu.ecardnomics.deck.Deck;
+
+public class Game {
+ GameStorage storage;
+ GameEngine engine;
+ public static final String GAMEPLAY_MESSAGE = ""
+ + "Questions will be displayed in a randomised order. At each question, you can\n"
+ + " 1. Try to attempt an answer at the question, by typing at the prompt\n"
+ + " 2. Press (with an empty attempt if you want to do it in your head)\n"
+ + System.lineSeparator()
+ + "Then, our 'advanced' algorithms will check your answer and score your answer (if\n"
+ + "any), and display the correct answer for you to check your answer against.\n"
+ + "Finally, we will ask if you think you got it right. If you did not, the question\n"
+ + "will be inserted back into the question pool, and you will get a chance to\n"
+ + "attempt it again!";
+ public static final String WELCOME_MESSAGE = ""
+ + "Welcome to Game Mode!"
+ + "\n"
+ + System.lineSeparator()
+ + "In this mode, you test your knowledge against the flash cards in the deck.\n"
+ + System.lineSeparator()
+ + GAMEPLAY_MESSAGE
+ + "\n"
+ + System.lineSeparator()
+ + "Type `done` to return to Normal Mode, `exit` to exit application.\n"
+ + "Type `help` to see a list of commands. Have fun!\n"
+ + System.lineSeparator()
+ + "Game Mode is started for: ";
+
+ public Game(Deck deck) {
+ storage = new GameStorage(deck);
+ engine = new GameEngine(storage);
+ }
+
+ public Command run() {
+ return engine.runGameLoop();
+ }
+}
diff --git a/src/main/java/seedu/ecardnomics/game/GameEngine.java b/src/main/java/seedu/ecardnomics/game/GameEngine.java
new file mode 100644
index 0000000000..ee9605b288
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/game/GameEngine.java
@@ -0,0 +1,158 @@
+package seedu.ecardnomics.game;
+
+import seedu.ecardnomics.Ui;
+import seedu.ecardnomics.command.Command;
+import seedu.ecardnomics.command.ExitCommand;
+import seedu.ecardnomics.command.VersionCommand;
+import seedu.ecardnomics.command.game.DoneGameCommand;
+import seedu.ecardnomics.command.game.GameResponseCommand;
+import seedu.ecardnomics.command.game.HelpCommand;
+import seedu.ecardnomics.deck.FlashCard;
+import seedu.ecardnomics.parser.GameParser;
+
+public class GameEngine {
+ GameStorage storage;
+ GameParser gameParser;
+
+ GameEngine(GameStorage storage) {
+ gameParser = new GameParser(storage.originalDeck);
+ this.storage = storage;
+ }
+
+ Command runGameLoop() {
+ Command command;
+
+ do {
+ if (isDeckInitiallyEmpty()) {
+ Ui.printGameEmptyDeckLine();
+ return forceDoneGame();
+ }
+ FlashCard flashCard = getQuestion();
+ do {
+ poseQuestion(flashCard);
+ command = getAttempt();
+ if (isHelpCommand(command) || isVersionCommand(command)) {
+ command.execute();
+ }
+ } while (isHelpCommand(command) || isVersionCommand(command));
+ if (isTerminate(command)) {
+ break;
+ }
+ Ui.printAnswerGameMode(flashCard.getAnswer());
+ try {
+ Ui.printAttemptFeedback(checkAttempt(command, flashCard));
+ } catch (Exception e) {
+ e.getMessage();
+ }
+ command = update(Ui.getInclExclConfirmation(), flashCard);
+
+ } while (!isTerminate(command) && !isNoMoreCards());
+
+ return command;
+ }
+
+ Command update(boolean isResponseY, FlashCard flashCard) {
+ updateRetestStore(isResponseY, flashCard);
+
+ if (storage.deque.isEmpty()) {
+ if (storage.retestStore.isEmpty()) {
+ Ui.printMiddleSeparator();
+ Ui.printDoneGameMessage();
+ return forceDoneGame();
+ } else {
+ updateDeque();
+ }
+ }
+
+ Ui.printMiddleSeparator();
+ return null;
+ }
+
+ void updateDeque() {
+ storage.deque = storage.createRandomisedStack(storage.retestStore);
+ storage.refreshRetestStore();
+ }
+
+ void updateRetestStore(boolean response, FlashCard flashCard) {
+ if (response) {
+ storage.retestStore.add(flashCard);
+ }
+ }
+
+ FlashCard getQuestion() {
+ return storage.deque.pop();
+ }
+
+ void poseQuestion(FlashCard flashCard) {
+ Ui.printGameQuestion(flashCard.getQuestion());
+ }
+
+ Command getAttempt() {
+ String userInput = Ui.readUserInput();
+ Command command = gameParser.parse(userInput);
+ return command;
+ }
+
+ double checkAttempt(Command command, FlashCard flashCard) {
+ assert command instanceof GameResponseCommand : "command not instance of GameResponseCommand!";
+
+ String attempt = ((GameResponseCommand) command).getAttempt();
+ String answer = flashCard.getAnswer();
+
+ return getMatchPercentage(attempt, answer);
+ }
+
+ double getMatchPercentage(String attempt, String answer) {
+ String[] attemptArray = attempt.replaceAll(Ui.PUNC_REGEX,"").split(" ");
+ String[] answerArray = answer.replaceAll(Ui.PUNC_REGEX,"").split(" ");
+ int answerLength = answerArray.length;
+ if (answerLength <= 1 && !isValidAnswer(answerArray)) {
+ return 0;
+ }
+ int matchCount = 0;
+ for (String word1 : answerArray) {
+ for (String word2: attemptArray) {
+ if (word2.trim().equalsIgnoreCase(word1.trim())) {
+ matchCount++;
+ break;
+ }
+ }
+ }
+
+ assert matchCount <= answerLength : "matchCount > answerLength!";
+ return (double) matchCount / answerLength * 100;
+ }
+
+ boolean isValidAnswer(String[] answerArray) {
+ for (String word : answerArray) {
+ if (!word.isBlank()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ Command forceDoneGame() {
+ return gameParser.parse("done");
+ }
+
+ boolean isDeckInitiallyEmpty() {
+ return storage.originalDeck.size() == 0;
+ }
+
+ boolean isTerminate(Command command) {
+ return DoneGameCommand.isDoneGame(command) || ExitCommand.isExit(command);
+ }
+
+ boolean isNoMoreCards() {
+ return storage.deque.isEmpty() && storage.retestStore.isEmpty();
+ }
+
+ boolean isHelpCommand(Command command) {
+ return command instanceof HelpCommand;
+ }
+
+ boolean isVersionCommand(Command command) {
+ return command instanceof VersionCommand;
+ }
+}
diff --git a/src/main/java/seedu/ecardnomics/game/GameStorage.java b/src/main/java/seedu/ecardnomics/game/GameStorage.java
new file mode 100644
index 0000000000..1fb26d942b
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/game/GameStorage.java
@@ -0,0 +1,35 @@
+package seedu.ecardnomics.game;
+
+import seedu.ecardnomics.deck.Deck;
+import seedu.ecardnomics.deck.FlashCard;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collections;
+
+public class GameStorage {
+ Deck originalDeck;
+ ArrayDeque deque;
+ ArrayList retestStore;
+
+ GameStorage(Deck deck) {
+ originalDeck = deck;
+ deque = createRandomisedStack(deck);
+ retestStore = new ArrayList<>();
+ }
+
+ ArrayDeque createRandomisedStack(Deck deck) {
+ ArrayList randomisedDeck = new ArrayList<>(deck.getDeck());
+ Collections.shuffle(randomisedDeck);
+ return new ArrayDeque<>(randomisedDeck);
+ }
+
+ ArrayDeque createRandomisedStack(ArrayList retestStore) {
+ Collections.shuffle(retestStore);
+ return new ArrayDeque<>(retestStore);
+ }
+
+ void refreshRetestStore() {
+ retestStore = new ArrayList<>();
+ }
+}
diff --git a/src/main/java/seedu/ecardnomics/parser/DeckParser.java b/src/main/java/seedu/ecardnomics/parser/DeckParser.java
new file mode 100644
index 0000000000..9786378512
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/parser/DeckParser.java
@@ -0,0 +1,320 @@
+package seedu.ecardnomics.parser;
+
+import seedu.ecardnomics.Main;
+import seedu.ecardnomics.Ui;
+import seedu.ecardnomics.command.Command;
+import seedu.ecardnomics.command.VersionCommand;
+import seedu.ecardnomics.command.deck.AddCommand;
+import seedu.ecardnomics.command.deck.DeleteCommand;
+import seedu.ecardnomics.command.deck.DoneEditCommand;
+import seedu.ecardnomics.command.deck.HelpCommand;
+import seedu.ecardnomics.command.deck.ListCommand;
+import seedu.ecardnomics.command.deck.UpdateCommand;
+import seedu.ecardnomics.command.ExitCommand;
+import seedu.ecardnomics.command.VoidCommand;
+import seedu.ecardnomics.deck.Deck;
+import seedu.ecardnomics.deck.DeckList;
+import seedu.ecardnomics.exceptions.EmptyQnAException;
+import seedu.ecardnomics.exceptions.FlashCardRangeException;
+import seedu.ecardnomics.exceptions.IndexFormatException;
+import seedu.ecardnomics.exceptions.InvalidListCommandException;
+import seedu.ecardnomics.exceptions.InvalidOptionsException;
+import seedu.ecardnomics.exceptions.InvalidPptxArgumentException;
+import seedu.ecardnomics.exceptions.NoAlphaNumericInputException;
+import seedu.ecardnomics.exceptions.NumberTooBigException;
+import seedu.ecardnomics.storage.LogStorage;
+
+import java.util.logging.Level;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Parser for commands supplied in Deck Mode.
+ */
+public class DeckParser extends Parser {
+ public Deck deck;
+ public DeckList deckList;
+ private static LogStorage logger = new LogStorage("DeckParserLogger");
+
+ /**
+ * Constructor.
+ */
+ public DeckParser(DeckList deckList, Deck deck) {
+ this.deckList = deckList;
+ this.deck = deck;
+ }
+
+ protected Command prepareDeletedFlashCard(String arguments) throws Exception {
+ int flashCardID;
+ boolean isFlashCardDeleted;
+
+ if (arguments.contains("-y")) {
+ arguments = arguments.replaceAll(Ui.FORCE_YES_OPT, "");
+ flashCardID = getIndex(arguments);
+ isFlashCardDeleted = true;
+ } else {
+ flashCardID = getIndex(arguments);
+ isFlashCardDeleted = Ui.getDeletedFlashCardConfirmation(deck.get(flashCardID).getQuestion());
+ }
+ logger.log(Level.INFO, "returning DeleteCommand object");
+
+ if (isFlashCardDeleted) {
+ Ui.printFlashCardDeletedLine(deck.get(flashCardID).getQuestion());
+ }
+
+ Ui.printDashLines();
+
+ return new DeleteCommand(deck, flashCardID, isFlashCardDeleted);
+ }
+
+ public static boolean containsNoAlphaNumerics(String field) {
+ String puncRemovedField = field.replaceAll(Ui.PUNC_REGEX, "");
+ return puncRemovedField.isBlank();
+ }
+
+ /**
+ * Verify that a String is contains meaningful contents.
+ *
+ * @param field String to be verified
+ * @throws EmptyQnAException if string is empty after trim
+ */
+ private void verifyStringField(String field) throws EmptyQnAException, NoAlphaNumericInputException {
+ if (field.trim().length() == 0) {
+ logger.log(Level.WARNING, "User entered nothing or a series of blank spaces for field");
+ throw new EmptyQnAException();
+ }
+ if (containsNoAlphaNumerics(field.trim())) {
+ logger.log(Level.WARNING, "User entered no alphanumeric characters as field!");
+ throw new NoAlphaNumericInputException();
+ }
+ }
+
+ protected Command prepareFlashCard(String arguments) throws EmptyQnAException, NoAlphaNumericInputException {
+ String[] questionAndAnswer = new String[2];
+
+ if (arguments.isEmpty()) {
+ // Ask for both question and answer
+ Ui.printAddFlashCardLine(deck);
+ Ui.printEnterQuestionLine();
+ questionAndAnswer[0] = Ui.readUserInput();
+ logger.log(Level.INFO, "Reading user input for question");
+ verifyStringField(questionAndAnswer[0]);
+
+ Ui.printEnterAnswerLine();
+ questionAndAnswer[1] = Ui.readUserInput();
+ logger.log(Level.INFO, "Reading user input for answer");
+ } else if (arguments.contains(" /ans ")) {
+ // Split by the first /ans only
+ questionAndAnswer = arguments.split("/ans", 2);
+ // Expect a qn and ans
+ if (questionAndAnswer.length != 2) {
+ throw new EmptyQnAException();
+ }
+ verifyStringField(questionAndAnswer[0]);
+ } else if (arguments.trim().equals("/ans")) {
+ throw new EmptyQnAException();
+ } else {
+ questionAndAnswer[0] = arguments;
+ // Valid question provided but not answer
+ Ui.printAddFlashCardLine(deck);
+ Ui.printEnterAnswerLine();
+ questionAndAnswer[1] = Ui.readUserInput();
+ logger.log(Level.INFO, "Reading user input for answer");
+ }
+ verifyStringField(questionAndAnswer[1]);
+
+ assert questionAndAnswer[0].length() > 0 : "question field empty!";
+ assert questionAndAnswer[1].length() > 0 : "answer field empty!";
+ assert !containsNoAlphaNumerics(questionAndAnswer[0]) : "question field nonsensical!";
+ assert !containsNoAlphaNumerics(questionAndAnswer[1]) : "question field nonsensical!";
+
+ Ui.printDashLines();
+ Ui.printFlashCardAddedLine();
+ Ui.printDashLines();
+
+ logger.log(Level.INFO, "returning AddCommand object");
+ return new AddCommand(deck, questionAndAnswer[0].trim(), questionAndAnswer[1].trim());
+ }
+
+ protected Command prepareUpdate(int flashCardID) throws EmptyQnAException, NoAlphaNumericInputException {
+ String[] newQnA = new String[2];
+ Ui.printUpdateQuestionLine(deck.get(flashCardID));
+ newQnA[0] = Ui.readUserInput();
+ logger.log(Level.INFO, "Reading user input for question");
+ boolean hasNewQ = true;
+ boolean hasNewA = true;
+ if (newQnA[0].trim().length() == 0) {
+ logger.log(Level.INFO, "User entered nothing or a series of blank spaces for question. "
+ + "Keep current question.");
+ newQnA[0] = deck.get(flashCardID).getQuestion();
+ hasNewQ = false;
+ }
+ Ui.printUpdateAnswerLine(deck.get(flashCardID));
+ newQnA[1] = Ui.readUserInput();
+ logger.log(Level.INFO, "Reading user input for answer");
+ if (newQnA[1].trim().length() == 0) {
+ logger.log(Level.INFO, "User entered nothing or a series of blank spaces for answer. "
+ + "Keep current answer.");
+ newQnA[1] = deck.get(flashCardID).getAnswer();
+ hasNewA = false;
+ }
+ verifyStringField(newQnA[0]);
+ verifyStringField(newQnA[1]);
+
+ assert newQnA[0].length() > 0 : "question field empty!";
+ assert newQnA[1].length() > 0 : "answer field empty!";
+ Ui.printDashLines();
+ Ui.printFlashCardUpdatedLine(hasNewQ, hasNewA);
+ Ui.printDashLines();
+ return new UpdateCommand(deck, flashCardID, newQnA[0], newQnA[1]);
+ }
+
+ protected Command prepareListCommand(String arguments) throws InvalidListCommandException {
+ if (arguments.trim().equals("/ans") || arguments.trim().isBlank()) {
+ return new ListCommand(deck, arguments);
+ }
+ throw new InvalidListCommandException();
+ }
+
+ protected void checkForValidPptxCommand(String arguments) throws Exception {
+ String validArg = arguments;
+
+ String dashOrEnd = "";
+ if (validArg.contains(Ui.ORIGINAL_COLOR_OPT)) {
+ Pattern p1 = Pattern.compile(Ui.ORIGINAL_COLOR_REGEX);
+ Matcher m1 = p1.matcher(arguments);
+ if (m1.find()) {
+ dashOrEnd = m1.group(3);
+ }
+ validArg = validArg.replaceAll(Ui.ORIGINAL_COLOR_REGEX, dashOrEnd);
+ }
+
+ if (validArg.contains(Ui.COLOR_SCHEME_OPT)) {
+ Pattern p2 = Pattern.compile(Ui.COLOR_SCHEME_REGEX);
+ Matcher m2 = p2.matcher(arguments);
+ if (m2.find()) {
+ dashOrEnd = m2.group(2);
+ }
+ validArg = validArg.replaceAll(Ui.COLOR_SCHEME_REGEX, dashOrEnd);
+ }
+
+ if (validArg.contains(Ui.FORCE_YES_OPT)) {
+ validArg = validArg.replaceAll(Ui.FORCE_YES_OPT, "");
+ }
+
+ Pattern p = Pattern.compile("-\\w+");
+ Matcher m = p.matcher(validArg);
+ if (m.find()) {
+ throw new InvalidOptionsException();
+ }
+
+ System.out.println(validArg);
+
+ if (!validArg.isBlank()) {
+ throw new InvalidPptxArgumentException();
+ }
+ }
+
+ @Override
+ protected int getIndex(String arguments) throws IndexFormatException,
+ FlashCardRangeException, NumberTooBigException {
+
+ arguments = arguments.trim();
+
+ if (!arguments.matches(Ui.DIGITS_REGEX)) {
+ logger.log(Level.WARNING, "User did not enter valid integer");
+ throw new IndexFormatException();
+ }
+
+ assert arguments.length() > 0 : "arguments empty!";
+
+ int index;
+ try {
+ index = Integer.parseInt(arguments) - INDEX_OFFSET;
+ } catch (NumberFormatException e) {
+ throw new NumberTooBigException();
+ }
+
+ if (index >= deck.size() || index < LOWEST_POSSIBLE_INDEX) {
+ logger.log(Level.WARNING, "Flash card index larger than total number of cards");
+ throw new FlashCardRangeException();
+ }
+ return index;
+ }
+
+ @Override
+ protected Command parseCommand(String commandWord, String arguments)
+ throws Exception {
+
+ switch (commandWord) {
+ // Version
+ case Ui.VERSION_CMD:
+ return new VersionCommand();
+ // Exit
+ case Ui.EXIT:
+ logger.log(Level.INFO, "returning ExitCommand object");
+ return new ExitCommand();
+ // Done with Edit Mode
+ case Ui.DONE:
+ logger.log(Level.INFO, "returning DoneEditCommand object");
+ return new DoneEditCommand(deck);
+ // Help
+ case Ui.HELP:
+ logger.log(Level.INFO, "returning HelpCommand object");
+ return new HelpCommand();
+ // Start
+ case Ui.START:
+ logger.log(Level.INFO, "Starting Game Mode");
+ return new NormalParser(Main.deckList).parseCommand(commandWord,
+ String.valueOf(deckList.getIndexOf(deck) + 1));
+ // Add a FlashCard
+ case Ui.ADD:
+ logger.log(Level.INFO, "Preparing FlashCard to add");
+ return prepareFlashCard(arguments);
+ // List all FlashCards
+ case Ui.LIST:
+ logger.log(Level.INFO, "returning ListCommand object");
+ return prepareListCommand(arguments);
+ // Delete a FlashCard
+ case Ui.DELETE:
+ logger.log(Level.INFO, "Preparing FlashCard to delete");
+ return prepareDeletedFlashCard(arguments);
+ // Update a FlashCard
+ case Ui.UPDATE:
+ logger.log(Level.INFO, "Preparing FlashCard to update");
+ int flashCardID = getIndex(arguments);
+ assert flashCardID >= LOWEST_POSSIBLE_INDEX : "flash card ID less than lowest possible flash card index!";
+ return prepareUpdate(flashCardID);
+ // Create PowerPoint
+ case Ui.PPTX:
+ logger.log(Level.INFO, "Printing to PowerPoint");
+ checkForValidPptxCommand(arguments);
+ return new NormalParser(Main.deckList).parseCommand(commandWord,
+ (deckList.getIndexOf(deck) + 1) + " " + arguments);
+ default:
+ logger.log(Level.WARNING, "returning VoidCommand object");
+ return new VoidCommand();
+ }
+ }
+
+ @Override
+ public Command parse(String userInput) {
+ logger.log(Level.INFO, "Parsing user input string");
+ String[] splitString = userInput.split(" ", 2);
+ String commandWord = splitString[0];
+ logger.log(Level.INFO, "Parsed commandWord");
+ String arguments = "";
+ boolean argumentsExist = splitString.length > 1;
+ if (argumentsExist) {
+ arguments = splitString[1];
+ logger.log(Level.INFO, "Parsed arguments");
+ }
+ try {
+ logger.log(Level.INFO, "Parsing command");
+ return parseCommand(commandWord, arguments);
+ } catch (Exception e) {
+ logger.log(Level.WARNING, "Parsed void or invalid command");
+ return new VoidCommand(e.getMessage());
+ }
+ }
+}
diff --git a/src/main/java/seedu/ecardnomics/parser/GameParser.java b/src/main/java/seedu/ecardnomics/parser/GameParser.java
new file mode 100644
index 0000000000..6bf4b98294
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/parser/GameParser.java
@@ -0,0 +1,89 @@
+package seedu.ecardnomics.parser;
+
+import seedu.ecardnomics.Ui;
+import seedu.ecardnomics.command.Command;
+import seedu.ecardnomics.command.ExitCommand;
+import seedu.ecardnomics.command.VersionCommand;
+import seedu.ecardnomics.command.VoidCommand;
+import seedu.ecardnomics.command.game.HelpCommand;
+import seedu.ecardnomics.command.game.DoneGameCommand;
+import seedu.ecardnomics.command.game.GameResponseCommand;
+import seedu.ecardnomics.deck.Deck;
+import seedu.ecardnomics.exceptions.FlashCardRangeException;
+import seedu.ecardnomics.exceptions.IndexFormatException;
+import seedu.ecardnomics.exceptions.NumberTooBigException;
+import seedu.ecardnomics.storage.LogStorage;
+
+import java.util.logging.Level;
+
+public class GameParser extends Parser {
+ public Deck deck;
+ private static LogStorage logger = new LogStorage("GameParserLogger");
+
+ /**
+ * Constructor.
+ */
+ public GameParser(Deck deck) {
+ this.deck = deck;
+ }
+
+ @Override
+ protected int getIndex(String arguments) throws IndexFormatException,
+ NumberTooBigException, FlashCardRangeException {
+ arguments = arguments.trim();
+
+ if (!arguments.matches(Ui.DIGITS_REGEX)) {
+ logger.log(Level.WARNING, "User did not enter valid integer");
+ throw new IndexFormatException();
+ }
+
+ assert arguments.length() > 0 : "arguments empty!";
+
+ int index;
+ try {
+ index = Integer.parseInt(arguments) - INDEX_OFFSET;
+ } catch (NumberFormatException e) {
+ throw new NumberTooBigException();
+ }
+
+ if (index >= deck.size() || index < LOWEST_POSSIBLE_INDEX) {
+ logger.log(Level.WARNING, "Flash card index larger than total number of cards");
+ throw new FlashCardRangeException();
+ }
+ return index;
+ }
+
+ @Override
+ protected Command parseCommand(String commandWord, String arguments) {
+
+ switch (commandWord) {
+ // Version
+ case Ui.VERSION_CMD:
+ return new VersionCommand();
+ // Exit
+ case Ui.EXIT:
+ logger.log(Level.INFO, "returning ExitCommand object");
+ return new ExitCommand();
+ // Done with Game Mode
+ case Ui.DONE:
+ logger.log(Level.INFO, "returning DoneGameCommand object");
+ return new DoneGameCommand(deck);
+ // Help
+ case Ui.HELP:
+ logger.log(Level.INFO, "returning HelpCommand object");
+ return new HelpCommand();
+ default:
+ return new VoidCommand();
+ }
+ }
+
+ @Override
+ public Command parse(String userInput) {
+ Command command = parseCommand(userInput.trim(), "");
+ if (command instanceof VoidCommand) {
+ return new GameResponseCommand(userInput.trim());
+ }
+
+ return command;
+ }
+}
diff --git a/src/main/java/seedu/ecardnomics/parser/NormalParser.java b/src/main/java/seedu/ecardnomics/parser/NormalParser.java
new file mode 100644
index 0000000000..0453443bb5
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/parser/NormalParser.java
@@ -0,0 +1,546 @@
+package seedu.ecardnomics.parser;
+
+import seedu.ecardnomics.Ui;
+import seedu.ecardnomics.command.Command;
+import seedu.ecardnomics.command.ExitCommand;
+import seedu.ecardnomics.command.VersionCommand;
+import seedu.ecardnomics.command.VoidCommand;
+import seedu.ecardnomics.command.normal.CreateCommand;
+import seedu.ecardnomics.command.normal.DecksCommand;
+import seedu.ecardnomics.command.normal.DeleteDeckCommand;
+import seedu.ecardnomics.command.normal.EditCommand;
+import seedu.ecardnomics.command.normal.HelpCommand;
+import seedu.ecardnomics.command.normal.PowerPointCommand;
+import seedu.ecardnomics.command.normal.StartCommand;
+import seedu.ecardnomics.command.normal.TagCommand;
+import seedu.ecardnomics.command.normal.UntagCommand;
+import seedu.ecardnomics.command.normal.SearchCommand;
+import seedu.ecardnomics.deck.Deck;
+import seedu.ecardnomics.deck.DeckList;
+import seedu.ecardnomics.exceptions.BothOcAndCsException;
+import seedu.ecardnomics.exceptions.ColorsNotAvailException;
+import seedu.ecardnomics.exceptions.CsIndexFormatException;
+import seedu.ecardnomics.exceptions.CsIndexRangeException;
+import seedu.ecardnomics.exceptions.DeckRangeException;
+import seedu.ecardnomics.exceptions.EmptyInputException;
+import seedu.ecardnomics.exceptions.IndexFormatException;
+import seedu.ecardnomics.exceptions.InvalidOptionsException;
+import seedu.ecardnomics.exceptions.NoSeparatorException;
+import seedu.ecardnomics.exceptions.NumberTooBigException;
+import seedu.ecardnomics.powerpoint.ColorOption;
+import seedu.ecardnomics.exceptions.DuplicateDeckException;
+import seedu.ecardnomics.storage.LogStorage;
+
+import java.util.ArrayList;
+import java.util.logging.Level;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import java.awt.Color;
+import org.beryx.awt.color.ColorFactory;
+
+import static seedu.ecardnomics.Ui.FORCE_YES_OPT;
+
+/**
+ * Parser for commands supplied in Normal Mode.
+ */
+public class NormalParser extends Parser {
+ DeckList deckList;
+ private static LogStorage logger = new LogStorage("NormalParserLogger");
+ public static final int HIGHEST_CS_INDEX = 9;
+
+ /** Constructor. */
+ public NormalParser(DeckList deckList) {
+ this.deckList = deckList;
+ }
+
+ /**
+ * Check whether the index input from users is valid.
+ *
+ * @param arguments Argument from user input which is index
+ * @return the index from user if it is valid
+ * @throws IndexFormatException if index input is not a number
+ * @throws DeckRangeException if index is out of range
+ * @throws NumberTooBigException if the index is too big
+ */
+ @Override
+ protected int getIndex(String arguments) throws IndexFormatException,
+ DeckRangeException, NumberTooBigException {
+
+ arguments = arguments.trim();
+
+ logger.log(Level.INFO, "Logging method getIndex() in NormalParser.");
+
+ if (!arguments.matches(Ui.DIGITS_REGEX)) {
+ logger.log(Level.WARNING, "User did not enter a valid integer index. string = " + arguments);
+ throw new IndexFormatException();
+ }
+
+ int index;
+ try {
+ index = Integer.parseInt(arguments) - INDEX_OFFSET;
+ } catch (NumberFormatException e) {
+ throw new NumberTooBigException();
+ }
+
+ if ((index >= deckList.size()) || (index < LOWEST_POSSIBLE_INDEX)) {
+ logger.log(Level.WARNING, "User did not enter an index in the valid range.");
+ throw new DeckRangeException();
+ }
+
+ return index;
+ }
+
+ /**
+ * Retrieves deck at index specified in arguments from deckList.
+ * getIndex() is used to convert arguments from String to an int index.
+ *
+ * @param arguments String that contains the ID number of the deck requested
+ * @return Reference to requested deck
+ * @throws IndexFormatException if arguments is not a digit
+ * @throws DeckRangeException if index obtained from arguments is not in range
+ */
+ private Deck prepareDeck(String arguments) throws Exception {
+ return deckList.getDeck(getIndex(arguments));
+ }
+
+ /**
+ * Prepares an instance of DeleteDeckCommand from given arguments.
+ *
+ * @param arguments String that contains the user arguments for the delete command
+ * @return DeleteDeckCommand that can be executed to delete the deck.
+ * @throws Exception if index is invalid or no index is supplied.
+ */
+ private Command prepareDeleteDeck(String arguments) throws Exception {
+ boolean isDeckDeleted;
+ int deckID;
+ if (arguments.contains(FORCE_YES_OPT)) {
+ arguments = arguments.replaceAll(FORCE_YES_OPT, "");
+ deckID = getIndex(arguments);
+ isDeckDeleted = true;
+ } else {
+ deckID = getIndex(arguments);
+ isDeckDeleted = Ui.getDeletedDeckConfirmation(deckList.getDeck(deckID).getName());
+ }
+
+ if (isDeckDeleted) {
+ Ui.printDeckDeletedLine(deckList.getDeck(deckID).getName());
+ }
+
+ Ui.printDashLines();
+
+ return new DeleteDeckCommand(deckList, deckID, isDeckDeleted);
+ }
+
+ /**
+ * Prepares new Tag Command from given arguments.
+ *
+ * @param arguments arguments input from user
+ * @return a Tag Command
+ * @throws Exception if index is invalid or empty arguments
+ */
+ private Command prepareTagCommand(String arguments) throws Exception {
+ String argumentsWithSpace = arguments + " ";
+ String[] idAndNewTags = argumentsWithSpace.split("/tag", 2);
+
+ if (idAndNewTags.length < 2) {
+ logger.log(Level.WARNING, "User did not provide /tag when adding tag.");
+ throw new NoSeparatorException();
+ }
+ assert (arguments.contains("/tag")) :
+ "Tags to be added are after /tag label.";
+ int deckID = getIndex(idAndNewTags[0]);
+
+ if (idAndNewTags[1].trim().isEmpty()) {
+ logger.log(Level.WARNING, "User did not supply tags when adding tag.");
+ throw new EmptyInputException();
+ }
+
+ String[] newTags = idAndNewTags[1].trim().split(" ");
+
+ return new TagCommand(deckList, deckID, getUniqueValues(newTags));
+ }
+
+ /**
+ * Prepares new Tag Command from given arguments.
+ *
+ * @param arguments arguments input from user
+ * @return a Tag Command
+ * @throws Exception if index is invalid or empty arguments
+ */
+ private Command prepareUntagCommand(String arguments) throws Exception {
+ String argumentsWithSpace = arguments + " ";
+ String[] idAndRemovedTags = argumentsWithSpace.split("/tag ", 2);
+ boolean isYes = false;
+ if (idAndRemovedTags.length < 2) {
+ logger.log(Level.WARNING, "User did not provide /tag when removing tags.");
+ throw new NoSeparatorException();
+ }
+ assert (arguments.contains("/tag")) :
+ "tags to be removed are after /tag label";
+
+ if (idAndRemovedTags[1].trim().isEmpty()) {
+ logger.log(Level.WARNING, "User did not supply tags when removing tags.");
+ throw new EmptyInputException();
+ }
+ String[] removedTags = idAndRemovedTags[1].trim().split(" ");
+ ArrayList uniqueTags = getUniqueValues(removedTags);
+ if (uniqueTags.get(uniqueTags.size() - 1).equals(FORCE_YES_OPT)) {
+ isYes = true;
+ uniqueTags.remove(uniqueTags.size() - 1);
+ }
+
+ if (uniqueTags.isEmpty()) {
+ logger.log(Level.WARNING, "User did not supply tags when removing tags.");
+ throw new EmptyInputException();
+ }
+
+ int deckID = getIndex(idAndRemovedTags[0]);
+ return new UntagCommand(deckList, deckID, uniqueTags, isYes);
+ }
+
+ /**
+ * Returns an array list of unique values.
+ *
+ * @param tags a String array of tags
+ * @return a unique array list
+ */
+ private ArrayList getUniqueValues(String[] tags) {
+ ArrayList uniqueTags = new ArrayList<>();
+ for (String tag: tags) {
+ String trimmedTag = tag.trim();
+ if (!uniqueTags.contains(trimmedTag) & !trimmedTag.isEmpty()) {
+ uniqueTags.add(tag.trim());
+ }
+ }
+ return uniqueTags;
+ }
+
+ /**
+ * Creates a new deck for adding to deckList.
+ *
+ * @param arguments String that represents the name of deck to be created
+ * @return Reference to the deck created
+ * @throws EmptyInputException if no name is supplied for the deck
+ * @throws DuplicateDeckException if duplicated name is entered
+ */
+ private Deck prepareNewDeck(String arguments) throws Exception {
+ logger.log(Level.INFO, "Logging method prepareNewDeck() in NormalParser.");
+ if (arguments.trim().isEmpty()) {
+ logger.log(Level.WARNING, "User did not supply name when creating a new deck.");
+ throw new EmptyInputException();
+ }
+
+ if (deckList.getAllNames().contains(arguments.trim())) {
+ throw new DuplicateDeckException();
+ }
+
+ String argumentsWithSpace = arguments + " ";
+ if (argumentsWithSpace.contains("/tag ")) {
+ return prepareNewDeckWithTags(argumentsWithSpace);
+ } else {
+ return new Deck(arguments);
+ }
+ }
+
+ /**
+ * Prepare a CreateCommand with tag argument.
+ *
+ * @param arguments input from user after command word
+ * @return a create command
+ * @throws Exception if input is empty or if duplicate name presents
+ */
+ private Deck prepareNewDeckWithTags(String arguments) throws Exception {
+ assert (arguments.contains("/tag ")) : "User did enter tag label.";
+ String[] nameAndTags = arguments.split("/tag ", 2);
+
+ if (nameAndTags.length != 2) {
+ throw new EmptyInputException();
+ }
+
+ assert (nameAndTags.length == 2) : "Input should contain name and tags.";
+
+ String name = nameAndTags[0].trim();
+ if (name.isEmpty()) {
+ throw new EmptyInputException();
+ }
+ if (deckList.getAllNames().contains(name)) {
+ throw new DuplicateDeckException();
+ }
+ if (nameAndTags[1].trim().isEmpty()) {
+ throw new EmptyInputException();
+ }
+
+ String[] tags = nameAndTags[1].trim().split(" ");
+ ArrayList tagsList = getUniqueValues(tags);
+ return new Deck(name, tagsList);
+ }
+
+ /**
+ * Gets the index for the -cs option in pptx command.
+ * @param arguments String representing the user input for the index
+ * @return int representing the index for the the color scheme chosen
+ * @throws CsIndexFormatException when format of index is not an integer
+ * @throws CsIndexRangeException when the index integer is not within the range [1,10], number of cs available
+ */
+ protected int getCsIndex(String arguments) throws CsIndexFormatException,
+ CsIndexRangeException {
+
+ arguments = arguments.trim();
+
+ logger.log(Level.INFO, "Logging method getIndex() in NormalParser.");
+
+ if (!arguments.matches(Ui.DIGITS_REGEX)) {
+ logger.log(Level.WARNING, "User did not enter a valid integer index. string = " + arguments);
+ throw new CsIndexFormatException();
+ }
+
+ int index;
+ try {
+ index = Integer.parseInt(arguments) - INDEX_OFFSET;
+ } catch (NumberFormatException e) {
+ throw new CsIndexFormatException();
+ }
+
+ if (index > HIGHEST_CS_INDEX || index < LOWEST_POSSIBLE_INDEX) {
+ logger.log(Level.WARNING, "User did not enter an index in the valid range for -cs.");
+ throw new CsIndexRangeException();
+ }
+
+ return index;
+ }
+
+ /**
+ * Prepares the PowerPoint command when the user input is pptx.
+ * @param arguments String representing the arguments of user input
+ * @return PowerPointCommand to be executed in the Main
+ * @throws Exception when arguments (index and options) are not valid
+ */
+ protected PowerPointCommand preparePptxCommand(String arguments) throws Exception {
+ Color bgColor = null;
+ Color txtColor = null;
+ String bgColorString = "";
+ String txtColorString = "";
+ ColorOption colorOpt = ColorOption.DEFAULT;
+ int colorSchemeIndex = 0;
+
+ boolean isPptxCreated = false;
+ boolean bothOCandCS = false;
+ boolean colorInvalid = false;
+ boolean colorSchemeInvalid = false;
+
+ Exception csException = null;
+
+ if (arguments.contains(FORCE_YES_OPT)) {
+ arguments = arguments.replaceAll(FORCE_YES_OPT, "").trim();
+ isPptxCreated = true;
+ }
+
+ if (arguments.contains(Ui.ORIGINAL_COLOR_OPT) && arguments.contains(Ui.COLOR_SCHEME_OPT)) {
+ bothOCandCS = true;
+ }
+
+ if (arguments.contains(Ui.ORIGINAL_COLOR_OPT)) {
+ String dashOrEnd = "";
+
+ Pattern pattern = Pattern.compile(Ui.ORIGINAL_COLOR_REGEX);
+ Matcher matcher = pattern.matcher(arguments);
+
+
+ if (matcher.find()) {
+ bgColorString = matcher.group(1);
+ txtColorString = matcher.group(2);
+ dashOrEnd = matcher.group(3);
+
+ try {
+ bgColor = ColorFactory.valueOf(bgColorString);
+ txtColor = ColorFactory.valueOf(txtColorString);
+
+ } catch (IllegalArgumentException e) {
+ colorInvalid = true;
+ }
+ }
+
+ colorOpt = ColorOption.ORGINAL_COLOR;
+ arguments = arguments.replaceAll(Ui.ORIGINAL_COLOR_REGEX, dashOrEnd).trim();
+ }
+
+ if (arguments.contains(Ui.COLOR_SCHEME_OPT)) {
+ String dashOrEnd = "";
+ Pattern pattern = Pattern.compile(Ui.COLOR_SCHEME_REGEX);
+ Matcher matcher = pattern.matcher(arguments);
+ if (matcher.find()) {
+ String numArg = matcher.group(1);
+ dashOrEnd = matcher.group(2);
+
+
+ try {
+ colorSchemeIndex = getCsIndex(numArg);
+ } catch (Exception e) {
+ csException = e;
+ colorSchemeInvalid = true;
+ }
+
+ colorOpt = ColorOption.COLOR_SCHEME;
+ arguments = arguments.replaceAll(Ui.COLOR_SCHEME_REGEX, dashOrEnd).trim();
+ }
+ }
+
+ if (arguments.contains("-")) {
+ throw new InvalidOptionsException();
+ }
+
+ if (bothOCandCS) {
+ throw new BothOcAndCsException();
+ }
+
+ if (colorInvalid) {
+ throw new ColorsNotAvailException();
+ }
+
+ if (colorSchemeInvalid) {
+ throw csException;
+ }
+
+ int deckID = getIndex(arguments);
+ Deck deck = deckList.getDeck(deckID);
+
+ if (!isPptxCreated) {
+ isPptxCreated = Ui.getPptxDeckConfirmation(deck.getName());
+ }
+
+ if (colorOpt == ColorOption.ORGINAL_COLOR) {
+ return new PowerPointCommand(deckList, deck, isPptxCreated, bgColorString, txtColorString,
+ bgColor, txtColor);
+ }
+
+ if (colorOpt == ColorOption.COLOR_SCHEME) {
+ return new PowerPointCommand(deckList, deck, isPptxCreated, colorSchemeIndex);
+ }
+
+ return new PowerPointCommand(deckList, deck, isPptxCreated);
+ }
+
+ /**
+ * Gets input from users and prepares a search command.
+ *
+ * @param arguments input from users after command word
+ * @return a search command
+ * @throws EmptyInputException if input is empty
+ */
+ private Command prepareSearchCommand(String arguments) throws EmptyInputException {
+ logger.log(Level.INFO, "Logging method prepareSearchCommand() in NormalParser.");
+
+ if (arguments.trim().isEmpty()) {
+ logger.log(Level.WARNING, "User did not supply tags when searching for decks.");
+ throw new EmptyInputException();
+ }
+
+ String[] relevantTags = arguments.trim().split(" ");
+ return new SearchCommand(deckList, relevantTags);
+ }
+
+ /**
+ * Prepare Command for execution in Main.
+ *
+ * @param commandWord String that corresponds to a command
+ * @param arguments String that lists the arguments for the command
+ * @return respective Command object
+ * @throws Exception when something wrong with the argument
+ */
+ @Override
+ protected Command parseCommand(String commandWord, String arguments)
+ throws Exception {
+
+ assert (commandWord != null && arguments != null) :
+ "commandWord and arguments should not be null";
+
+ logger.log(Level.INFO, "Logging method parseCommand() in NormalParser.");
+
+ switch (commandWord) {
+ // Version
+ case Ui.VERSION_CMD:
+ return new VersionCommand();
+ // Exit
+ case Ui.EXIT:
+ logger.log(Level.INFO, "User issued command to terminate program.");
+ return new ExitCommand();
+ // Help
+ case Ui.HELP:
+ logger.log(Level.INFO, "User issued command to view help.");
+ return new HelpCommand();
+ // Edit
+ case Ui.EDIT:
+ Deck deck = prepareDeck(arguments);
+ logger.log(Level.INFO, "User issued command to edit deck " + deck.getName() + ".");
+ return new EditCommand(deckList, deck);
+ // Start
+ case Ui.START:
+ Deck startDeck = prepareDeck(arguments);
+ logger.log(Level.INFO, "User issued command to start deck " + startDeck.getName() + ".");
+ return new StartCommand(deckList, startDeck);
+ // Create
+ case Ui.CREATE:
+ Deck newDeck = prepareNewDeck(arguments);
+ logger.log(Level.INFO, "User issued command to create deck " + newDeck.getName() + ".");
+ return new CreateCommand(deckList, newDeck);
+ // Decks
+ case Ui.DECKS:
+ logger.log(Level.INFO, "User issued command to list decks.");
+ return new DecksCommand(deckList);
+ // Delete
+ case Ui.DELETE:
+ logger.log(Level.INFO, "User issued command to delete deck");
+ return prepareDeleteDeck(arguments);
+ // Tag
+ case Ui.TAG:
+ logger.log(Level.INFO, "User issued command to tag a deck.");
+ return prepareTagCommand(arguments);
+ // Untag
+ case Ui.UNTAG:
+ logger.log(Level.INFO, "User issued command to untag a deck.");
+ return prepareUntagCommand(arguments);
+ // Create new PowerPoint
+ case Ui.PPTX:
+ logger.log(Level.INFO, "User issued command to create a PowerPoint.");
+ return preparePptxCommand(arguments);
+ // Search
+ case Ui.SEARCH:
+ logger.log(Level.INFO, "User issued command to search for decks.");
+ return prepareSearchCommand(arguments);
+ default:
+ logger.log(Level.INFO, "User issued an invalid command.");
+ return new VoidCommand();
+ }
+ }
+
+ /**
+ * Parses User Input from Main.
+ *
+ * @param userInput Input from user, passed through Main
+ * @return Command to be executed
+ */
+ @Override
+ public Command parse(String userInput) {
+ logger.log(Level.INFO, "Logging method parse() in NormalParser.");
+ String[] splitString = userInput.split(" ", 2);
+ String commandWord = splitString[0];
+ logger.log(Level.INFO, "Parsed commandWord");
+ boolean argumentsExist = splitString.length > 1;
+ String arguments = "";
+
+ if (argumentsExist) {
+ arguments = splitString[1];
+ logger.log(Level.INFO, "Parsed arguments");
+ }
+
+ try {
+ logger.log(Level.INFO, "Parsing command");
+ return parseCommand(commandWord, arguments);
+ } catch (Exception e) {
+ logger.log(Level.WARNING, "Parsed void or invalid command");
+ return new VoidCommand(e.getMessage());
+ }
+ }
+
+}
diff --git a/src/main/java/seedu/ecardnomics/parser/Parser.java b/src/main/java/seedu/ecardnomics/parser/Parser.java
new file mode 100644
index 0000000000..95bbe3d289
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/parser/Parser.java
@@ -0,0 +1,39 @@
+package seedu.ecardnomics.parser;
+
+import seedu.ecardnomics.command.Command;
+
+public abstract class Parser {
+
+ public static final int LOWEST_POSSIBLE_INDEX = 0;
+ public static final int INDEX_OFFSET = 1;
+
+ /**
+ * Checks whether argument from user is a number and whether the index given is a within the correct range of
+ * decks/ flash cards.
+ *
+ * @param arguments Argument from user input
+ * @return int representing the index from the argument given
+ */
+ protected abstract int getIndex(String arguments)
+ throws Exception;
+
+ /**
+ * Determines the Command subclass to return based on the commandWord
+ * and arguments provided.
+ *
+ * @param commandWord String that corresponds to a command
+ * @param arguments String that lists the arguments for the command
+ * @return Command representing the command to be executed
+ * @throws Exception if arguments are inappropriate
+ */
+ protected abstract Command parseCommand(String commandWord, String arguments)
+ throws Exception;
+
+ /**
+ * Parse user input to determine which Command
to output.
+ *
+ * @param userInput Input from user
+ * @return Command representing command to be executed
+ */
+ public abstract Command parse(String userInput);
+}
diff --git a/src/main/java/seedu/ecardnomics/powerpoint/ColorOption.java b/src/main/java/seedu/ecardnomics/powerpoint/ColorOption.java
new file mode 100644
index 0000000000..100c41bb83
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/powerpoint/ColorOption.java
@@ -0,0 +1,7 @@
+package seedu.ecardnomics.powerpoint;
+
+public enum ColorOption {
+ DEFAULT,
+ ORGINAL_COLOR,
+ COLOR_SCHEME
+}
diff --git a/src/main/java/seedu/ecardnomics/powerpoint/PowerPoint.java b/src/main/java/seedu/ecardnomics/powerpoint/PowerPoint.java
new file mode 100644
index 0000000000..5d5c5fc642
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/powerpoint/PowerPoint.java
@@ -0,0 +1,176 @@
+package seedu.ecardnomics.powerpoint;
+
+import org.apache.poi.xslf.usermodel.SlideLayout;
+import org.apache.poi.xslf.usermodel.XMLSlideShow;
+import org.apache.poi.xslf.usermodel.XSLFSlide;
+import org.apache.poi.xslf.usermodel.XSLFSlideLayout;
+import org.apache.poi.xslf.usermodel.XSLFSlideMaster;
+import org.apache.poi.xslf.usermodel.XSLFTextParagraph;
+import org.apache.poi.xslf.usermodel.XSLFTextRun;
+import org.apache.poi.xslf.usermodel.XSLFTextShape;
+import org.beryx.awt.color.ColorFactory;
+import seedu.ecardnomics.Ui;
+import seedu.ecardnomics.deck.Deck;
+import seedu.ecardnomics.deck.FlashCard;
+
+import java.awt.Color;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+public class PowerPoint {
+ private Deck deck;
+ private XMLSlideShow pptx = new XMLSlideShow();
+ private XSLFSlideMaster defaultMaster = pptx.getSlideMasters().get(0);
+ private String bgColorString;
+ private String txtColorString;
+ private Color bgColor;
+ private Color txtColor;
+ private ColorOption colorOpt;
+
+ public static final String[][] COLOR_SCHEMES = {
+ {"steelblue", "silver"},
+ {"goldenrod", "crimson"},
+ {"navy", "turquoise"},
+ {"black", "orange"},
+ {"black", "deeppink"},
+ {"grey", "pink"},
+ {"pink", "navy"},
+ {"lightyellow", "seagreen"},
+ {"maroon", "peachpuff"},
+ {"azure", "darkgreen"}
+ };
+
+ public static final String PPTX_DIR = "pptx/";
+ public static final String PPTX_EXT = ".pptx";
+
+ /** Constructor for default printing. */
+ public PowerPoint(Deck deck) {
+ this.deck = deck;
+ bgColorString = "white";
+ txtColorString = "black";
+ bgColor = ColorFactory.valueOf("white");
+ txtColor = ColorFactory.valueOf("black");
+ colorOpt = ColorOption.DEFAULT;
+ }
+
+ /** Constructor for -cs option. */
+ public PowerPoint(Deck deck, int colorScheme) {
+ this.deck = deck;
+ bgColorString = COLOR_SCHEMES[colorScheme][0];
+ txtColorString = COLOR_SCHEMES[colorScheme][1];
+ bgColor = ColorFactory.valueOf(COLOR_SCHEMES[colorScheme][0]);
+ txtColor = ColorFactory.valueOf(COLOR_SCHEMES[colorScheme][1]);
+ colorOpt = ColorOption.COLOR_SCHEME;
+ }
+
+ /** Constructor for -oc option. */
+ public PowerPoint(Deck deck, String bgColorString, String txtColorSting,
+ Color bgColor, Color txtColor) {
+ this.deck = deck;
+ this.bgColorString = bgColorString;
+ this.txtColorString = txtColorSting;
+ this.bgColor = bgColor;
+ this.txtColor = txtColor;
+ colorOpt = ColorOption.ORGINAL_COLOR;
+ }
+
+ private void newIntroSlide() {
+ XSLFSlideLayout layout = defaultMaster.getLayout(SlideLayout.TITLE);
+ XSLFSlide slide = pptx.createSlide(layout);
+
+ slide.getBackground().setFillColor(bgColor);
+
+ XSLFTextShape title = slide.getPlaceholder(0);
+ XSLFTextShape subtitle = slide.getPlaceholder(1);
+ slide.removeShape(subtitle);
+
+ title.clearText();
+
+ XSLFTextParagraph p = title.addNewTextParagraph();
+ XSLFTextRun r = p.addNewTextRun();
+
+ r.setText(deck.getName());
+ r.setFontColor(txtColor);
+ r.setFontSize(60.);
+ }
+
+ private void newSlide(String titleText, String text) {
+ XSLFSlideLayout layout = defaultMaster.getLayout(SlideLayout.TITLE_AND_CONTENT);
+ XSLFSlide slide = pptx.createSlide(layout);
+
+ XSLFTextShape title = slide.getPlaceholder(0);
+ XSLFTextShape object = slide.getPlaceholder(1);
+
+ title.clearText();
+ object.clearText();
+
+ XSLFTextParagraph p = title.addNewTextParagraph();
+ XSLFTextRun r = p.addNewTextRun();
+
+ r.setText(titleText);
+ r.setFontColor(txtColor);
+ r.setFontSize(50.);
+
+ p = object.addNewTextParagraph();
+ r = p.addNewTextRun();
+
+ r.setText(text);
+ r.setFontColor(txtColor);
+ r.setFontSize(40.);
+ }
+
+ private void createPowerPoint() {
+ newIntroSlide();
+
+ for (int i = 0; i < deck.size(); ++i) {
+ FlashCard card = deck.get(i);
+ newSlide(Ui.QUESTION + (i + 1), card.getQuestion());
+ newSlide(Ui.ANSWER, card.getAnswer());
+ }
+ }
+
+ private void exportPowerPoint() {
+ String fileName = PPTX_DIR + deck.getName() + PPTX_EXT;
+
+ File folder = new File(PPTX_DIR);
+ if (!folder.exists()) {
+ folder.mkdir();
+ }
+
+ assert (folder.exists());
+
+ File file = new File(fileName);
+ try {
+ file.createNewFile();
+ } catch (Exception e) {
+ Ui.printMessage(Ui.CREATE_NEW_FILE_ERROR_LINE + fileName);
+ }
+
+ assert (file.exists());
+
+ try {
+ FileOutputStream out = new FileOutputStream(fileName);
+ try {
+ pptx.write(out);
+ out.close();
+ pptx.close();
+ Ui.printDeckPptxLine(deck.getName(), bgColorString, txtColorString, colorOpt);
+ } catch (IOException e) {
+ Ui.printMessage(Ui.WRITE_TO_FILE_ERROR_LINE);
+ }
+ } catch (FileNotFoundException e) {
+ Ui.printMessage(fileName + Ui.FILE_NOT_FOUND_ERROR_LINE);
+ }
+ }
+
+ /**
+ * Creates a new PowerPoint Slide based on the current deck
+ * and saves it as pptx/deckName.pptx
+ */
+ public void createNewPowerPoint() {
+ createPowerPoint();
+ exportPowerPoint();
+ }
+}
diff --git a/src/main/java/seedu/ecardnomics/storage/LogStorage.java b/src/main/java/seedu/ecardnomics/storage/LogStorage.java
new file mode 100644
index 0000000000..68d30dd30f
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/storage/LogStorage.java
@@ -0,0 +1,64 @@
+package seedu.ecardnomics.storage;
+
+import seedu.ecardnomics.Ui;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.logging.FileHandler;
+import java.util.logging.Logger;
+import java.util.logging.Level;
+import java.util.logging.SimpleFormatter;
+
+/**
+ * Class for instantiating loggers that log to files instead of stdout.
+ */
+public class LogStorage {
+ private boolean willLog;
+ private String name;
+ private String filePath;
+ private Logger logger;
+ private FileHandler handler;
+ private static final String FOLDER_PATH = "./log/";
+ private static final File DIRECTORY = new File(FOLDER_PATH);
+
+ /**
+ * Constructor to initialize a logger for logging to file.
+ *
+ * @param name of class being logged by logger
+ */
+ public LogStorage(String name) {
+ this.name = name;
+ willLog = true;
+ filePath = FOLDER_PATH + this.name + ".log";
+ logger = Logger.getLogger(name);
+ logger.setUseParentHandlers(false);
+
+ if (!DIRECTORY.exists()) {
+ DIRECTORY.mkdir();
+ }
+
+ try {
+ handler = new FileHandler(filePath);
+ logger.addHandler(handler);
+ handler.setFormatter(new SimpleFormatter());
+ } catch (SecurityException se) {
+ Ui.printMessage("No permission to create file, log file will not be created.");
+ willLog = false;
+ } catch (IOException ioe) {
+ Ui.printMessage("Unable to open log file. Will not be logging.");
+ willLog = false;
+ }
+ }
+
+ /**
+ * Create a log entry in the log file.
+ *
+ * @param level java.util.logging.Level object to indicate log level
+ * @param message to be logged
+ */
+ public void log(Level level, String message) {
+ if (willLog) {
+ logger.log(level, message);
+ }
+ }
+}
diff --git a/src/main/java/seedu/ecardnomics/storage/Storage.java b/src/main/java/seedu/ecardnomics/storage/Storage.java
new file mode 100644
index 0000000000..ab966f11a5
--- /dev/null
+++ b/src/main/java/seedu/ecardnomics/storage/Storage.java
@@ -0,0 +1,126 @@
+package seedu.ecardnomics.storage;
+
+import seedu.ecardnomics.deck.Deck;
+import seedu.ecardnomics.deck.DeckList;
+import seedu.ecardnomics.deck.FlashCard;
+import seedu.ecardnomics.parser.DeckParser;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Scanner;
+
+public class Storage {
+ public static final String FILE_PATH = "./data/deckList.txt";
+ public static final String FOLDER_PATH = "./data";
+ public static final String DIVIDER =
+ "================================================================================";
+ public static final String DECK_NAME_REGEX = "deck\\s\\|\\s.+";
+ public static final String TAGS_REGEX = "tags\\s\\|.+";
+ public static final String NO_TAGS_REGEX = "tags\\s\\|";
+ public static final String QUESTION_REGEX = "Q\\s\\|\\s.+";
+ public static final String EMPTY_QUESTION_REGEX = "Q\\s\\|\\s+";
+ public static final String ANSWER_REGEX = "A\\s\\|\\s.+";
+ public static final String EMPTY_ANSWER_REGEX = "A\\s\\|\\s+";
+ public static final String DIVIDER_REGEX = "={10,}";
+
+ public DeckList load(DeckList deckList) {
+ File folder = new File(FOLDER_PATH);
+ if (!folder.exists()) {
+ folder.mkdir();
+ }
+
+ File file = new File(FILE_PATH);
+ Scanner scanner = null;
+ try {
+ scanner = new Scanner(file);
+ } catch (FileNotFoundException e) {
+ return deckList;
+ }
+
+ String line;
+
+ while (scanner.hasNext()) {
+ line = scanner.nextLine();
+ String deckName = null;
+ Deck deck = null;
+ if (line.matches(DECK_NAME_REGEX)) {
+ deckName = line.substring(7);
+ deck = new Deck(deckName);
+ } else {
+ continue;
+ }
+ line = scanner.nextLine();
+ if (line.matches(TAGS_REGEX)) {
+ line = line.substring(line.indexOf("|") + 2).trim();
+ ArrayList arrayOfTags = new ArrayList<>();
+ if (!line.isBlank()) {
+ String[] tags = line.split("\\|");
+ for (String tag: tags) {
+ arrayOfTags.add(tag.trim());
+ }
+ deck.addTag(arrayOfTags);
+ }
+ } else if (!line.matches(NO_TAGS_REGEX)) {
+ continue;
+ }
+ while (scanner.hasNext()) {
+ line = scanner.nextLine();
+ String question = null;
+ if (line.matches(QUESTION_REGEX) && !line.matches(EMPTY_QUESTION_REGEX)) {
+ question = line.substring(4).trim();
+ if (DeckParser.containsNoAlphaNumerics(question)) {
+ continue;
+ }
+ } else if (line.matches(DIVIDER_REGEX)) {
+ break;
+ } else {
+ continue;
+ }
+ line = scanner.nextLine();
+ String answer = null;
+ if (line.matches(ANSWER_REGEX) && !line.matches(EMPTY_ANSWER_REGEX)) {
+ answer = line.substring(4).trim();
+ if (DeckParser.containsNoAlphaNumerics(answer)) {
+ continue;
+ }
+ } else if (line.matches(DIVIDER_REGEX)) {
+ break;
+ } else {
+ continue;
+ }
+ FlashCard flashCard = new FlashCard(question, answer);
+ deck.add(flashCard);
+ }
+ if (!deckList.contains(deck)) {
+ deckList.addDeck(deck);
+ }
+ }
+ return deckList;
+ }
+
+ public void write(DeckList deckList) throws IOException {
+ FileWriter fw = new FileWriter(FILE_PATH);
+ fw.write(DIVIDER);
+ fw.write(System.lineSeparator());
+
+ for (int i = 0; i < deckList.size(); i++) {
+ Deck deck = deckList.getDeck(i);
+ fw.write("deck | " + deck.getName());
+ fw.write(System.lineSeparator());
+ fw.write("tags | " + deck.getTagString());
+ fw.write(System.lineSeparator());
+ for (int j = 0; j < deck.size(); j++) {
+ fw.write("Q | " + deck.get(j).getQuestion());
+ fw.write(System.lineSeparator());
+ fw.write("A | " + deck.get(j).getAnswer());
+ fw.write(System.lineSeparator());
+ }
+ fw.write(DIVIDER);
+ fw.write(System.lineSeparator());
+ }
+ fw.close();
+ }
+}
diff --git a/src/test/java/seedu/duke/DukeTest.java b/src/test/java/seedu/ecardnomics/MainTest.java
similarity index 79%
rename from src/test/java/seedu/duke/DukeTest.java
rename to src/test/java/seedu/ecardnomics/MainTest.java
index 2dda5fd651..1c64425e71 100644
--- a/src/test/java/seedu/duke/DukeTest.java
+++ b/src/test/java/seedu/ecardnomics/MainTest.java
@@ -1,10 +1,10 @@
-package seedu.duke;
+package seedu.ecardnomics;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
-class DukeTest {
+class MainTest {
@Test
public void sampleTest() {
assertTrue(true);
diff --git a/src/test/java/seedu/ecardnomics/UiTest.java b/src/test/java/seedu/ecardnomics/UiTest.java
new file mode 100644
index 0000000000..3aa2728ffa
--- /dev/null
+++ b/src/test/java/seedu/ecardnomics/UiTest.java
@@ -0,0 +1,250 @@
+package seedu.ecardnomics;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import seedu.ecardnomics.deck.Deck;
+import seedu.ecardnomics.deck.FlashCard;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.util.ArrayList;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static seedu.ecardnomics.Ui.printTagsRemovedLine;
+import static seedu.ecardnomics.Ui.printNewDeck;
+import static seedu.ecardnomics.Ui.printNewTags;
+import static seedu.ecardnomics.Ui.printDeletedDeckQuestion;
+import static seedu.ecardnomics.Ui.printDeckDeletedLine;
+import static seedu.ecardnomics.Ui.printInvalidTagsLine;
+import static seedu.ecardnomics.Ui.printRemovedTagsQuestion;
+import static seedu.ecardnomics.Ui.printUpdateAnswerLine;
+import static seedu.ecardnomics.Ui.printUpdateQuestionLine;
+import static seedu.ecardnomics.Ui.printSearchDecksLine;
+import static seedu.ecardnomics.Ui.formStringOfTags;
+
+
+
+public class UiTest {
+ private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
+ private final ByteArrayOutputStream errContent = new ByteArrayOutputStream();
+ private final PrintStream originalOut = System.out;
+ private final PrintStream originalErr = System.err;
+
+ @BeforeEach
+ public void setUpStreams() {
+ System.setOut(new PrintStream(outContent));
+ System.setErr(new PrintStream(errContent));
+ }
+
+ @AfterEach
+ public void restoreStreams() {
+ System.setOut(originalOut);
+ System.setErr(originalErr);
+ }
+
+ @Test
+ public void printMessage_singleLine_goodFormat() {
+ String singleLine = "Hello! Welcome to eCardnomics!\t\t\tFlashCards Application.";
+ String message = Ui.DASH_LINES + System.lineSeparator() + singleLine + System.lineSeparator()
+ + Ui.DASH_LINES + System.lineSeparator();
+ Ui.printMessage(singleLine);
+ assertEquals(message, outContent.toString());
+ }
+
+ @Test
+ void printNormalPrompt() {
+ }
+
+ @Test
+ void printDeckPrompt() {
+ }
+
+ @Test
+ void printPrompt() {
+ }
+
+ @Test
+ void printNormalWelcome() {
+ }
+
+ @Test
+ void printDeckWelcome() {
+ }
+
+ @Test
+ void printGreeting() {
+ }
+
+ @Test
+ void printExitLine() {
+ }
+
+ @Test
+ void printNotRecognisedLine() {
+ }
+
+ @Test
+ void printAddFlashCardLine() {
+ }
+
+ @Test
+ void printEnterQuestionLine() {
+ }
+
+ @Test
+ void printEnterAnswerLine() {
+ }
+
+ @Test
+ void printFlashCardAddedLine() {
+ }
+
+ @Test
+ void printDeck() {
+ }
+
+ @Test
+ void printDeleteFlashCardLine() {
+ }
+
+ @Test
+ void printFlashCardDeletedLine() {
+ }
+
+ @Test
+ void printHelp() {
+ }
+
+ @Test
+ void printNewDeck_newDeckName_deckName() {
+
+ String expectedOutput = "------------------------------------------------------------"
+ + "--------------------" + System.lineSeparator()
+ + "New deck created: Pokemon" + System.lineSeparator() + "--------------------"
+ + "------------------------------------------------------------" + System.lineSeparator();
+ printNewDeck(new Deck("Pokemon"));
+ assertEquals(expectedOutput, outContent.toString());
+ }
+
+ @Test
+ void printDeckList() {
+ }
+
+ @Test
+ void printDeletedDeckQuestion_deletedDeckName_questionLine() {
+ String expectedOutput = "Do you want to delete Pokemon deck? [y/n] ";
+ Ui.printDeletedDeckQuestion("Pokemon");
+ assertEquals(expectedOutput, outContent.toString());
+ }
+
+ @Test
+ void printDeletedDeck_deletedDeckName_confirmation() {
+ String expectedOutput = "Pokemon has been deleted." + System.lineSeparator();
+ printDeckDeletedLine("Pokemon");
+ assertEquals(expectedOutput, outContent.toString());
+ }
+
+ @Test
+ void printNewTags_none_newTagsLine() {
+ ArrayList newTagsArray = new ArrayList<>();
+ newTagsArray.add("Anime");
+ newTagsArray.add("Unreal");
+ printNewTags("Pokemon", newTagsArray);
+ String expectedOutput = "------------------------------------------------------------"
+ + "--------------------" + System.lineSeparator()
+ + "The deck Pokemon has been tagged as: Anime | Unreal" + System.lineSeparator()
+ + "--------------------------------------------------------------------------------"
+ + System.lineSeparator();
+ assertEquals(expectedOutput, outContent.toString());
+ }
+
+ @Test
+ void printInvalidTagsLine_none_warning() {
+ String expectedOutput = "------------------------------------------------------------"
+ + "--------------------" + System.lineSeparator()
+ + "Tag Cons is not in the deck already!" + System.lineSeparator() + "--------------------"
+ + "------------------------------------------------------------" + System.lineSeparator();
+ printInvalidTagsLine("Cons");
+ assertEquals(expectedOutput, outContent.toString());
+ }
+
+ @Test
+ void printRemovedTagsQuestion_StringArrayOfTagsAndDeckName_question() {
+ ArrayList removedTagsArray = new ArrayList<>();
+ removedTagsArray.add("Beginner");
+ removedTagsArray.add("Year2");
+ printRemovedTagsQuestion("Micro-Economics", removedTagsArray);
+ String expectedOutput = "Do you want to remove the tag(s) Beginner | Year2 from Micro-Economics? [y/n] ";
+ assertEquals(expectedOutput, outContent.toString());
+ }
+
+ @Test
+ void printTagsRemovedLine_removedTagsAndDeckName_confirmation() {
+ ArrayList removedTagsArray = new ArrayList<>();
+ removedTagsArray.add("Beginner");
+ removedTagsArray.add("Year2");
+ printTagsRemovedLine("Micro-Economics", removedTagsArray);
+ String expectedOutput = "The tag(s) Beginner | Year2 have been removed from the deck Micro-Economics."
+ + System.lineSeparator();
+ assertEquals(expectedOutput, outContent.toString());
+ }
+
+ @Test
+ void formStringOfTags_StringArrayOfTags_aStringOfTags() {
+ ArrayList arrayStringOfTags = new ArrayList<>();
+ arrayStringOfTags.add("Advanced");
+ arrayStringOfTags.add("Year4");
+ arrayStringOfTags.add("Finance");
+ String expectedOutput = "Advanced | Year4 | Finance";
+
+ String actualOutput = formStringOfTags(arrayStringOfTags);
+ assertEquals(expectedOutput, actualOutput);
+ }
+
+ @Test
+ void printUpdateQuestionLine_existingFlashCard_updateQuestionLine() {
+ String question = "Old question";
+ String answer = "Old answer";
+ FlashCard existingCard = new FlashCard(question, answer);
+ String expectedOutput = existingCard.toString(true, 0) + System.lineSeparator()
+ + "New Question: " + System.lineSeparator() + " > ";
+ printUpdateQuestionLine(existingCard);
+ assertEquals(expectedOutput, outContent.toString());
+ }
+
+ @Test
+ void printUpdateAnswerLine_existingFlashCard_updateAnswerLine() {
+ String question = "Old question";
+ String answer = "Old answer";
+ FlashCard existingCard = new FlashCard(question, answer);
+ String expectedOutput = System.lineSeparator() + existingCard.toString(false, 0)
+ + System.lineSeparator() + "New Answer: " + System.lineSeparator() + " > ";
+ printUpdateAnswerLine(existingCard);
+ assertEquals(expectedOutput, outContent.toString());
+ }
+
+ @Test
+ void printSearchDecksLine_emptyString_NoDecksLine() {
+ String expectedOutput = "------------------------------------------------------------"
+ + "--------------------" + System.lineSeparator()
+ + "There is no decks having the tags you are looking for."
+ + System.lineSeparator() + "--------------------"
+ + "------------------------------------------------------------" + System.lineSeparator();
+ printSearchDecksLine("");
+ assertEquals(expectedOutput, outContent.toString());
+ }
+
+ @Test
+ void printSearchDecksLine_stringOfMatchedDecks_decksListings() {
+ String expectedOutput = "------------------------------------------------------------"
+ + "--------------------" + System.lineSeparator()
+ + "The decks having tags you are searching for:"
+ + "\n1. Micro-Economics"
+ + "\n3. Object-oriented Programming" + System.lineSeparator() + "--------------------"
+ + "------------------------------------------------------------" + System.lineSeparator();
+ String input = "\n1. Micro-Economics\n3. Object-oriented Programming";
+ printSearchDecksLine(input);
+ assertEquals(expectedOutput, outContent.toString());
+ }
+}
diff --git a/src/test/java/seedu/ecardnomics/command/deck/AddCommandTest.java b/src/test/java/seedu/ecardnomics/command/deck/AddCommandTest.java
new file mode 100644
index 0000000000..b2c312b726
--- /dev/null
+++ b/src/test/java/seedu/ecardnomics/command/deck/AddCommandTest.java
@@ -0,0 +1,17 @@
+package seedu.ecardnomics.command.deck;
+
+import org.junit.jupiter.api.Test;
+import seedu.ecardnomics.deck.Deck;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class AddCommandTest {
+
+ @Test
+ void execute_addNewFlashCard() {
+ Deck deck = new Deck("Test");
+ AddCommand cmd = new AddCommand(deck, "Test Question", "Test Amswer");
+ cmd.execute();
+ assertEquals(1, deck.size());
+ }
+}
diff --git a/src/test/java/seedu/ecardnomics/command/deck/ListCommandTest.java b/src/test/java/seedu/ecardnomics/command/deck/ListCommandTest.java
new file mode 100644
index 0000000000..296401ac4c
--- /dev/null
+++ b/src/test/java/seedu/ecardnomics/command/deck/ListCommandTest.java
@@ -0,0 +1,79 @@
+package seedu.ecardnomics.command.deck;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import seedu.ecardnomics.deck.Deck;
+import seedu.ecardnomics.deck.FlashCard;
+import seedu.ecardnomics.storage.LogStorage;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class ListCommandTest {
+ private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
+ private final ByteArrayOutputStream errContent = new ByteArrayOutputStream();
+ private final PrintStream originalOut = System.out;
+ private final PrintStream originalErr = System.err;
+
+ @BeforeEach
+ public void setUpStreams() {
+ System.setOut(new PrintStream(outContent));
+ System.setErr(new PrintStream(errContent));
+ }
+
+ @AfterEach
+ public void restoreStreams() {
+ System.setOut(originalOut);
+ System.setErr(originalErr);
+ }
+
+ @Test
+ void execute_emptyDeck_listCommand() {
+ String expectedOutput =
+ "------------------------------------------------------------"
+ + "--------------------" + System.lineSeparator()
+ + "Deck is currently empty!" + System.lineSeparator() + "--------------------"
+ + "------------------------------------------------------------" + System.lineSeparator();
+
+ Deck deck = new Deck("Test");
+ ListCommand cmd = new ListCommand(deck, "/ans");
+ cmd.execute();
+ assertEquals(expectedOutput, outContent.toString());
+ }
+
+ @Test
+ void execute_nonEmptyDeck_ansArgument_listCommand() {
+ String expectedOutput =
+ "------------------------------------------------------------"
+ + "--------------------" + System.lineSeparator()
+ + "You are now viewing deck: Test" + System.lineSeparator()
+ + "1. Question: Test Question" + System.lineSeparator()
+ + " Answer: Test Answer" + System.lineSeparator() + "--------------------"
+ + "------------------------------------------------------------" + System.lineSeparator();
+
+ Deck deck = new Deck("Test");
+ deck.add(new FlashCard("Test Question", "Test Answer"));
+ ListCommand cmd = new ListCommand(deck, "/ans");
+ cmd.execute();
+ assertEquals(expectedOutput, outContent.toString());
+ }
+
+ @Test
+ void execute_nonEmptyDeck_noAnsArgument_listCommand() {
+ String expectedOutput =
+ "------------------------------------------------------------"
+ + "--------------------" + System.lineSeparator()
+ + "You are now viewing deck: Test" + System.lineSeparator()
+ + "1. Question: Test Question" + System.lineSeparator() + "--------------------"
+ + "------------------------------------------------------------" + System.lineSeparator();
+
+ Deck deck = new Deck("Test");
+ deck.add(new FlashCard("Test Question", "Test Answer"));
+ ListCommand cmd = new ListCommand(deck, "");
+ cmd.execute();
+ assertEquals(expectedOutput, outContent.toString());
+ }
+}
diff --git a/src/test/java/seedu/ecardnomics/command/deck/UpdateCommandTest.java b/src/test/java/seedu/ecardnomics/command/deck/UpdateCommandTest.java
new file mode 100644
index 0000000000..01021ae669
--- /dev/null
+++ b/src/test/java/seedu/ecardnomics/command/deck/UpdateCommandTest.java
@@ -0,0 +1,32 @@
+package seedu.ecardnomics.command.deck;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import seedu.ecardnomics.deck.Deck;
+import seedu.ecardnomics.deck.FlashCard;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class UpdateCommandTest {
+ Deck currentDeck;
+ int flashCardID;
+ static final String OLD_Q = "Knock knock";
+ static final String OLD_A = "No joke";
+
+ @Test
+ void execute_update() {
+ String newQ = "newQ";
+ String newA = "newA";
+ UpdateCommand cmd = new UpdateCommand(currentDeck, flashCardID, newQ, newA);
+ cmd.execute();
+ assertEquals(newQ, currentDeck.get(flashCardID).getQuestion());
+ assertEquals(newA, currentDeck.get(flashCardID).getAnswer());
+ }
+
+ @BeforeEach
+ void initializeDeck() {
+ currentDeck = new Deck("test deck");
+ currentDeck.add(new FlashCard(OLD_Q, OLD_A));
+ flashCardID = 0;
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/seedu/ecardnomics/command/normal/CreateCommandTest.java b/src/test/java/seedu/ecardnomics/command/normal/CreateCommandTest.java
new file mode 100644
index 0000000000..c91393c0e6
--- /dev/null
+++ b/src/test/java/seedu/ecardnomics/command/normal/CreateCommandTest.java
@@ -0,0 +1,23 @@
+package seedu.ecardnomics.command.normal;
+
+import org.junit.jupiter.api.Test;
+import seedu.ecardnomics.deck.Deck;
+import seedu.ecardnomics.deck.DeckList;
+
+import java.util.ArrayList;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class CreateCommandTest {
+
+ @Test
+ void execute_createNewDeck() {
+ DeckList deckList = new DeckList();
+ assertEquals(0, deckList.size());
+ CreateCommand cmd = new CreateCommand(deckList, new Deck("Test"));
+ cmd.execute();
+ assertEquals(1, deckList.size());
+ assertEquals("Test", deckList.getDeck(0).getName());
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/seedu/ecardnomics/command/normal/DeleteDeckCommandTest.java b/src/test/java/seedu/ecardnomics/command/normal/DeleteDeckCommandTest.java
new file mode 100644
index 0000000000..dc58553906
--- /dev/null
+++ b/src/test/java/seedu/ecardnomics/command/normal/DeleteDeckCommandTest.java
@@ -0,0 +1,30 @@
+package seedu.ecardnomics.command.normal;
+
+import org.junit.jupiter.api.Test;
+import seedu.ecardnomics.deck.Deck;
+import seedu.ecardnomics.deck.DeckList;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class DeleteDeckCommandTest {
+
+ @Test
+ void execute_confirmDeleteDeck() {
+ DeckList deckList = new DeckList();
+ deckList.addDeck(new Deck("Test"));
+ assertEquals(1, deckList.size());
+ DeleteDeckCommand cmd = new DeleteDeckCommand(deckList, 0, true);
+ cmd.execute();
+ assertEquals(0, deckList.size());
+ }
+
+ @Test
+ void execute_notConfirmedDeleteDeck() {
+ DeckList deckList = new DeckList();
+ deckList.addDeck(new Deck("Test"));
+ assertEquals(1, deckList.size());
+ DeleteDeckCommand cmd = new DeleteDeckCommand(deckList, 0, false);
+ cmd.execute();
+ assertEquals(1, deckList.size());
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/seedu/ecardnomics/command/normal/TagCommandTest.java b/src/test/java/seedu/ecardnomics/command/normal/TagCommandTest.java
new file mode 100644
index 0000000000..eed662a338
--- /dev/null
+++ b/src/test/java/seedu/ecardnomics/command/normal/TagCommandTest.java
@@ -0,0 +1,25 @@
+package seedu.ecardnomics.command.normal;
+
+import org.junit.jupiter.api.Test;
+import seedu.ecardnomics.deck.Deck;
+import seedu.ecardnomics.deck.DeckList;
+
+import java.util.ArrayList;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class TagCommandTest {
+
+ @Test
+ void execute_addNewTagsToExistingDeck() {
+ DeckList deckList = new DeckList();
+ Deck newDeck = new Deck("Pokemon");
+ deckList.addDeck(newDeck);
+ ArrayList newTags = new ArrayList<>();
+ newTags.add("anime");
+ newTags.add("unreal");
+ TagCommand cmd = new TagCommand(deckList, 0, newTags);
+ cmd.execute();
+ assertEquals(2, deckList.getDeck(0).getTag().size());
+ }
+}
diff --git a/src/test/java/seedu/ecardnomics/command/normal/UntagCommandTest.java b/src/test/java/seedu/ecardnomics/command/normal/UntagCommandTest.java
new file mode 100644
index 0000000000..c7d700135a
--- /dev/null
+++ b/src/test/java/seedu/ecardnomics/command/normal/UntagCommandTest.java
@@ -0,0 +1,27 @@
+package seedu.ecardnomics.command.normal;
+
+import org.junit.jupiter.api.Test;
+import seedu.ecardnomics.deck.Deck;
+import seedu.ecardnomics.deck.DeckList;
+
+import java.util.ArrayList;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class UntagCommandTest {
+
+ @Test
+ void execute_removeTagsFromDeck() {
+ DeckList deckList = new DeckList();
+ ArrayList tags = new ArrayList<>();
+ tags.add("anime");
+ tags.add("unreal");
+ Deck newDeck = new Deck("Pokemon", tags);
+ deckList.addDeck(newDeck);
+ ArrayList removedTags = new ArrayList<>();
+ removedTags.add("unreal");
+ UntagCommand cmd = new UntagCommand(deckList, 0, removedTags, true);
+ cmd.execute();
+ assertEquals(1, deckList.getDeck(0).getTag().size());
+ }
+}
diff --git a/src/test/java/seedu/ecardnomics/deck/DeckListTest.java b/src/test/java/seedu/ecardnomics/deck/DeckListTest.java
new file mode 100644
index 0000000000..3450d28afd
--- /dev/null
+++ b/src/test/java/seedu/ecardnomics/deck/DeckListTest.java
@@ -0,0 +1,35 @@
+package seedu.ecardnomics.deck;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class DeckListTest {
+ @Test
+ void size() {
+ DeckList deckList = initialiseDeckList(2);
+ assertEquals(2, deckList.size());
+ deckList.removeDeck(1);
+ assertEquals(1, deckList.size());
+ deckList.removeDeck(0);
+ assertEquals(0, deckList.size());
+ deckList.addDeck(new Deck("deck 1"));
+ assertEquals(1, deckList.size());
+ }
+
+ @Test
+ void testToString() {
+ DeckList deckList = initialiseDeckList(2);
+ String deckListString = "1. deck 1\n2. deck 2";
+ assertEquals(deckListString, deckList.toString());
+ }
+
+ DeckList initialiseDeckList(int size) {
+ DeckList deckList = new DeckList();
+ for (int i = 1; i <= size; i++) {
+ Deck deck = new Deck(String.format("deck %d", i));
+ deckList.addDeck(deck);
+ }
+ return deckList;
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/seedu/ecardnomics/deck/DeckTest.java b/src/test/java/seedu/ecardnomics/deck/DeckTest.java
new file mode 100644
index 0000000000..d8f3086c7a
--- /dev/null
+++ b/src/test/java/seedu/ecardnomics/deck/DeckTest.java
@@ -0,0 +1,176 @@
+package seedu.ecardnomics.deck;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class DeckTest {
+ @Test
+ void getName() {
+ Deck deck = new Deck("Pokemon");
+ assertEquals("Pokemon", deck.getName());
+ }
+
+ @Test
+ void setName() {
+ Deck deck = new Deck("Pokemon");
+ deck.setName("Pokemon Go");
+ assertEquals("Pokemon Go", deck.getName());
+ }
+
+ // @Test
+ // void get_emptyDeck_exceptionThrown() {
+ // Deck deck = new Deck("Pokemon");
+ // try {
+ // assertEquals(0, deck.get(0));
+ // fail();
+ // } catch (Exception e) {
+ // NullPointerException npe = new NullPointerException();
+ // assertEquals(npe.getMessage(), e.getMessage());
+ // }
+ // }
+
+ // @Test
+ // void get_invalidIndex_exceptionThrown() {
+ // Deck deck = initialiseDeck(2);
+ // try {
+ // assertEquals(0, deck.get(-1));
+ // assertEquals(0, deck.get(2));
+ // assertEquals(0, deck.get(3));
+ // fail(); // test should not reach this line
+ // } catch (Exception e) {
+ // NullPointerException npe = new NullPointerException();
+ // assertEquals(npe.getMessage(), e.getMessage());
+ // }
+ // }
+
+ @Test
+ void get_validIndex_success() {
+ Deck deck = new Deck("Pokemon");
+ FlashCard q1 = new FlashCard("q 1", "a 1");
+ FlashCard q2 = new FlashCard("q 2", "a 2");
+ deck.add(q1);
+ deck.add(q2);
+ assertEquals(q1, deck.get(0));
+ assertEquals(q2, deck.get(1));
+ }
+
+ @Test
+ void size() {
+ Deck deck = initialiseDeck(2, new ArrayList<>());
+ assertEquals(2, deck.size());
+ deck.delete(1);
+ assertEquals(1, deck.size());
+ deck.delete(0);
+ assertEquals(0, deck.size());
+ deck.add(new FlashCard("q 1", "a 1"));
+ assertEquals(1, deck.size());
+ }
+
+ @Test
+ void constructor_newDeckWithTags() {
+ ArrayList tags = new ArrayList<>();
+ tags.add("anime");
+ tags.add("unreal");
+ Deck deck = initialiseDeck(2, tags);
+ assertEquals(2, deck.getTag().size());
+ }
+
+ @Test
+ void getTagString_StringOfTags() {
+ ArrayList tags = new ArrayList<>();
+ tags.add("anime");
+ tags.add("unreal");
+ Deck deck = initialiseDeck(2, tags);
+ String expectedOutput = "anime | unreal";
+ assertEquals(expectedOutput, deck.getTagString());
+ }
+
+ @Test
+ void addTag_newTags_void() {
+ ArrayList tags = new ArrayList<>();
+ tags.add("anime");
+ tags.add("unreal");
+ Deck deck = initialiseDeck(2, tags);
+ ArrayList newTags = new ArrayList<>();
+ newTags.add("for-kids");
+ deck.addTag(newTags);
+ assertEquals(3, deck.getTag().size());
+ }
+
+ @Test
+ void removeTag_newTags_void() {
+ ArrayList tags = new ArrayList<>();
+ tags.add("anime");
+ tags.add("unreal");
+ tags.add("for-kids");
+ Deck deck = initialiseDeck(2, tags);
+ ArrayList removedTags = new ArrayList<>();
+ removedTags.add("for-kids");
+ deck.removeTag(removedTags);
+ assertEquals(2, deck.getTag().size());
+ }
+
+ // @Test
+ // void delete_emptyDeck_exceptionThrown() {
+ // Deck deck = initialiseDeck(0);
+ // try {
+ // deck.delete(0);
+ // fail(); // test should not reach this line
+ // } catch (Exception e) {
+ // NullPointerException npe = new NullPointerException();
+ // assertEquals(npe.getMessage(), e.getMessage());
+ // }
+ // }
+
+ // @Test
+ // void delete_invalidIndex_exceptionThrown() {
+ // Deck deck = initialiseDeck(2);
+ // try {
+ // deck.delete(-1);
+ // deck.delete(2);
+ // deck.delete(3);
+ // fail(); // test should not reach this line
+ // } catch (Exception e) {
+ // NullPointerException npe = new NullPointerException();
+ // assertEquals(npe.getMessage(), e.getMessage());
+ // }
+ // }
+
+ @Test
+ void delete_validIndex_success() {
+ Deck deck = initialiseDeck(2, new ArrayList<>());
+ deck.delete(1);
+ assertEquals(1, deck.size());
+ }
+
+ @Test
+ void testToString_default_goodFormat() {
+ Deck deck = initialiseDeck(2, new ArrayList<>());
+ String deckString = "Pokemon:" + System.lineSeparator() + "1. Question: q 1"
+ + System.lineSeparator() + " Answer: a 1\n\n2. Question: q 2"
+ + System.lineSeparator() + " Answer: a 2";
+ assertEquals(deckString, deck.toString());
+ }
+
+ @Test
+ void testToString_withType_goodFormat() {
+ Deck deck = initialiseDeck(2, new ArrayList<>());
+ String deckString = "1. Question: q 1\n\n" + "2. Question: q 2";
+ String deckStringAns = "1. Question: q 1" + System.lineSeparator() + " Answer: a 1\n\n"
+ + "2. Question: q 2" + System.lineSeparator() + " Answer: a 2";
+ assertEquals(deckString, deck.toString(true));
+ assertEquals(deckStringAns, deck.toString(false));
+ }
+
+ Deck initialiseDeck(int size, ArrayList tags) {
+ Deck deck = new Deck("Pokemon", tags);
+ for (int i = 1; i <= size; i++) {
+ FlashCard flashCard = new FlashCard(String.format("q %d", i), String.format("a %d", i));
+ deck.add(flashCard);
+ }
+ return deck;
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/seedu/ecardnomics/deck/FlashCardTest.java b/src/test/java/seedu/ecardnomics/deck/FlashCardTest.java
new file mode 100644
index 0000000000..f7595c636a
--- /dev/null
+++ b/src/test/java/seedu/ecardnomics/deck/FlashCardTest.java
@@ -0,0 +1,108 @@
+package seedu.ecardnomics.deck;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class FlashCardTest {
+ static FlashCard flashCard;
+ static FlashCard longFlashCard;
+ static FlashCard longWordFlashCard;
+ static final String LABEL_PAD = " ".repeat("Question: ".length());
+
+ @Test
+ void testToString_default_goodFormat() {
+ String flashCardString = "Question: Who's that Pokemon?" + System.lineSeparator()
+ + "Answer: It's Pikachu!";
+ assertEquals(flashCardString, flashCard.toString());
+ }
+
+ @Test
+ void testToString_default_wrappedLine() {
+ String flashCardString = "Question: "
+ + "Ok, long question let's goooooooo! Ahhhhhhhhhhhhh. Make this question " + System.lineSeparator()
+ + LABEL_PAD + "veryyyyyy longggg!" + System.lineSeparator()
+ + "Answer: " + "Ahhhhhhhhhhhhhhhhhhhhhhhh!!!!!!!!!!!!!! Get an even longer answer. How "
+ + System.lineSeparator() + LABEL_PAD
+ + "many lines will this answer span? I do not know. Maybe we can get it "
+ + System.lineSeparator() + LABEL_PAD
+ + "to three lines? Perhaps. Anyway span reminds me of linear algebra.";
+ assertEquals(flashCardString, longFlashCard.toString());
+ }
+
+ @Test
+ void testToString_default_wrapLongWord() {
+ String flashCardString = "Question: Do you have Pneumonoultramicroscopicsilicovolcanoconiosis?"
+ + System.lineSeparator() + "Answer: Try to enter the loop so create an answer with "
+ + "more than 2 lines: Aaaa" + System.lineSeparator() + LABEL_PAD
+ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaahhhhhhhhhhhhhhhhhhh"
+ + System.lineSeparator() + LABEL_PAD + "hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh"
+ + "hhhhhhhhhhhhh" + System.lineSeparator() + LABEL_PAD
+ + "hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhSaveMeeeeeeeeeeeeeee"
+ + System.lineSeparator() + LABEL_PAD + "e";
+ assertEquals(flashCardString, longWordFlashCard.toString());
+ }
+
+ @Test
+ void testToString_withType_goodFormat() {
+ String flashCardQn = "Question: Who's that Pokemon?";
+ String flashCardAns = "Answer: It's Pikachu!";
+ assertEquals(flashCardQn, flashCard.toString(true, 0));
+ assertEquals(flashCardAns, flashCard.toString(false, 0));
+ }
+
+ @Test
+ void testToString_withType_wrappedLine() {
+ String flashCardQn = "Question: "
+ + "Ok, long question let's goooooooo! Ahhhhhhhhhhhhh. Make this question " + System.lineSeparator()
+ + LABEL_PAD + "veryyyyyy longggg!";
+ String flashCardAns = "Answer: "
+ + "Ahhhhhhhhhhhhhhhhhhhhhhhh!!!!!!!!!!!!!! Get an even longer answer. How " + System.lineSeparator()
+ + LABEL_PAD + "many lines will this answer span? I do not know. Maybe we can get it "
+ + System.lineSeparator()
+ + LABEL_PAD + "to three lines? Perhaps. Anyway span reminds me of linear algebra.";
+ assertEquals(flashCardQn, longFlashCard.toString(true, 0));
+ assertEquals(flashCardAns, longFlashCard.toString(false, 0));
+ }
+
+ @Test
+ void testToString_withType_wrapLongWord() {
+ String largeSerialNumber = Integer.MAX_VALUE + ". ";
+ String indexPad = " ".repeat(largeSerialNumber.length());
+ String flashCardQn = largeSerialNumber + "Question: Do you have "
+ + "Pneumonoultramicroscopicsilicovolcanoconiosis?";
+ String flashCardAns = indexPad + "Answer: Try to enter the loop so create an answer with more than 2 "
+ + System.lineSeparator() + indexPad + LABEL_PAD + "lines: Aaaaaaa"
+ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ + System.lineSeparator() + indexPad + LABEL_PAD + "aaaahhhhhhhhhhhhhhhhhhhhhhhhhhhhh"
+ + "hhhhhhhhhhhhhhhhhhhhhhhhh"
+ + System.lineSeparator() + indexPad + LABEL_PAD + "hhhhhhhhhhhhhhhhhhhhhhhhhh"
+ + "hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh"
+ + System.lineSeparator() + indexPad + LABEL_PAD + "hhhhhhhhhhhhhhhhhhhhhhhhhhhSav"
+ + "eMeeeeeeeeeeeeeeee";
+ assertEquals(flashCardQn, largeSerialNumber
+ + longWordFlashCard.toString(true, largeSerialNumber.length()));
+ assertEquals(flashCardAns, longWordFlashCard.toString(false, largeSerialNumber.length()));
+ }
+
+ @BeforeAll
+ public static void createFlashCard() {
+ flashCard = new FlashCard("Who's that Pokemon?", "It's Pikachu!");
+
+ String longQuestion = "Ok, long question let's goooooooo! Ahhhhhhhhhhhhh."
+ + " Make this question veryyyyyy longggg!";
+ String longAnswer = "Ahhhhhhhhhhhhhhhhhhhhhhhh!!!!!!!!!!!!!! "
+ + "Get an even longer answer. How many lines will this answer span? "
+ + "I do not know. Maybe we can get it to three lines? Perhaps. "
+ + "Anyway span reminds me of linear algebra.";
+ longFlashCard = new FlashCard(longQuestion, longAnswer);
+
+ String longWordQn = "Do you have Pneumonoultramicroscopicsilicovolcanoconiosis?";
+ String longWordAns = "Try to enter the loop so create an answer with more than 2 lines: "
+ + "Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaahhhhhhhhhhhhhhhhhhhhhhhhhh"
+ + "hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh"
+ + "hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhSaveMeeeeeeeeeeeeeeee";
+ longWordFlashCard = new FlashCard(longWordQn, longWordAns);
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/seedu/ecardnomics/game/GameEngineTest.java b/src/test/java/seedu/ecardnomics/game/GameEngineTest.java
new file mode 100644
index 0000000000..e36201141b
--- /dev/null
+++ b/src/test/java/seedu/ecardnomics/game/GameEngineTest.java
@@ -0,0 +1,166 @@
+package seedu.ecardnomics.game;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import seedu.ecardnomics.command.Command;
+import seedu.ecardnomics.command.game.DoneGameCommand;
+import seedu.ecardnomics.deck.Deck;
+import seedu.ecardnomics.deck.DeckList;
+import seedu.ecardnomics.deck.FlashCard;
+import seedu.ecardnomics.parser.GameParser;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class GameEngineTest {
+ Deck deck;
+ DeckList deckList;
+ GameStorage storage;
+ GameParser gameParser;
+ GameEngine engine;
+
+ FlashCard[] createfcStubs(int number) {
+ FlashCard[] fcStubs = new FlashCard[number];
+ for (int i = 0; i < number; i++) {
+ fcStubs[i] = new FlashCard(String.format("ques-stub%d", i), String.format("ans-stub%d", i));
+ }
+ return fcStubs;
+ }
+
+ /**
+ * Checks if storage.deque is randomised enough by sequentially popping and ensuring there is some degree of
+ * randomness.
+ *
+ * @return true if random "enough", otherwise false
+ */
+ boolean checkRandomnessOfDequeFromReference(ArrayDeque dequeFromRetestStubs,
+ ArrayList referenceList) {
+ int mismatchCount = 0;
+ while (!dequeFromRetestStubs.isEmpty()) {
+ // flash cards follow strict format
+ int index = 0;
+ FlashCard fc = dequeFromRetestStubs.pop();
+ if (!fc.getQuestion().equals(referenceList.get(index))) {
+ mismatchCount++;
+ }
+ index++;
+ }
+
+ double mismatchRatio = (double) referenceList.size() / mismatchCount;
+ double threshold = 0.25;
+ if (mismatchRatio >= threshold) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Test
+ void update_storageDequeAndRetestStoreEmptyUserEntersN_returnsDoneGameCommand() {
+ storage.deque.clear();
+ storage.retestStore.clear();
+ FlashCard fcStub = createfcStubs(1)[0];
+ assertTrue(engine.update(false, fcStub) instanceof DoneGameCommand);
+ }
+
+ @Test
+ void update_storageDequeNotEmpty_returnsNull() {
+ FlashCard fcStub = createfcStubs(1)[0];
+ assertTrue(engine.update(true, fcStub) == null);
+ assertTrue(engine.update(false, fcStub) == null);
+
+ }
+
+ @Test
+ void updateDeque_storageDequeEmptyAndUpdateFromRetestStore_success() {
+ storage.deque.clear();
+ FlashCard[] fcStubs = createfcStubs(5);
+ for (FlashCard fc : fcStubs) {
+ storage.retestStore.add(fc);
+ }
+ ArrayList retestStoreRef = storage.retestStore;
+ storage.createRandomisedStack(storage.retestStore);
+ engine.updateDeque();
+ assertTrue(checkRandomnessOfDequeFromReference(storage.deque, retestStoreRef));
+ assertEquals(0, storage.retestStore.size());
+ }
+
+ @Test
+ void updateRetestStore_responseY_addsFlashCard() {
+ storage.retestStore.clear();
+ FlashCard fcStub = createfcStubs(1)[0];
+ engine.updateRetestStore(true, fcStub);
+ assertTrue(storage.retestStore.contains(fcStub));
+ }
+
+ @Test
+ void updateRetestStore_responseN_doNothing() {
+ storage.retestStore.clear();
+ FlashCard fcStub = createfcStubs(1)[0];
+ engine.updateRetestStore(false, fcStub);
+ assertFalse(storage.retestStore.contains(fcStub));
+ }
+
+ @Test
+ void checkAttempt_validCommand_success() {
+ Command attempt = gameParser.parse("ans-stub0");
+ FlashCard fcStub = createfcStubs(1)[0];
+ assertEquals(100.0, engine.checkAttempt(attempt, fcStub));
+ }
+
+ @Test
+ void getMatchPercentage_completeMatchSpacePunctuationPadded_success() {
+ double match = engine.getMatchPercentage("this is an answer.!` -_+=)(*&^%$#@!", "this is an answer .");
+ assertEquals(100.0, match);
+ }
+
+ @Test
+ void getMatchPercentage_completeMatchSpacePaddedMatchedWords_success() {
+ double match = engine.getMatchPercentage("this \t\t is an answer.", "this is an answer");
+ assertEquals(100.0, match);
+ }
+
+ @Test
+ void getMatchPercentage_zeroMatchSpacePunctuationPadded_success() {
+ double match = engine.getMatchPercentage("completely wrong.!` -_+=)(*&^%$#@!", "this is an answer .");
+ assertEquals(0.0, match);
+ }
+
+ @Test
+ void getMatchPercentage_punctuationAnswerZeroMatch_success() {
+ double match = engine.getMatchPercentage("!@#//... . %^*()", "!@#//... . %^*()");
+ assertEquals(0.0, match);
+ }
+
+ @BeforeEach
+ void preliminaries() {
+ deckList = initialiseDeckList(2);
+ deck = initialiseDeck(deckList, 2);
+ storage = new GameStorage(deck);
+ gameParser = new GameParser(deck);
+ engine = new GameEngine(storage);
+ }
+
+ DeckList initialiseDeckList(int size) {
+ DeckList deckList = new DeckList();
+ for (int i = 1; i <= size; i++) {
+ Deck deck = new Deck(String.format("deck %d", i));
+ deckList.addDeck(deck);
+ }
+ return deckList;
+ }
+
+ Deck initialiseDeck(DeckList deckList, int size) {
+ deckList.addDeck(new Deck("Pokemon"));
+ Deck deck = deckList.getDeck(2);
+ for (int i = 1; i <= size; i++) {
+ FlashCard flashCard = new FlashCard(String.format("q %d", i), String.format("a %d", i));
+ deck.add(flashCard);
+ }
+ return deck;
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/seedu/ecardnomics/game/GameStorageTest.java b/src/test/java/seedu/ecardnomics/game/GameStorageTest.java
new file mode 100644
index 0000000000..d745e087c7
--- /dev/null
+++ b/src/test/java/seedu/ecardnomics/game/GameStorageTest.java
@@ -0,0 +1,66 @@
+package seedu.ecardnomics.game;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import seedu.ecardnomics.deck.Deck;
+import seedu.ecardnomics.deck.DeckList;
+import seedu.ecardnomics.deck.FlashCard;
+
+class GameStorageTest {
+ GameStorage storage;
+ Deck originalDeck;
+
+ @Test
+ void createRandomisedStack_takesDeck_originalDeckUnchanged() throws Exception {
+ Deck deckCopy = new Deck("Pokemon");
+ copyDeck(originalDeck, deckCopy);
+ storage.createRandomisedStack(originalDeck);
+ checkDeckEquality(originalDeck, deckCopy);
+ }
+
+ void checkDeckEquality(Deck original, Deck copy) throws Exception {
+ boolean isEqual = true;
+ for (int i = 0; i < original.size(); i++) {
+ if (!original.get(i).equals(copy.get(i))) {
+ isEqual = false;
+ break;
+ }
+ }
+
+ if (!isEqual) {
+ throw new Exception("Decks not equal!");
+ }
+ }
+
+ void copyDeck(Deck originalDeck, Deck deckCopy) {
+ for (FlashCard fc : originalDeck.getDeck()) {
+ deckCopy.add(fc);
+ }
+ }
+
+ @BeforeEach
+ void preliminaries() {
+ DeckList deckList = initialiseDeckList(2);
+ originalDeck = initialiseDeck(deckList, 2);
+ storage = new GameStorage(originalDeck);
+ }
+
+ DeckList initialiseDeckList(int size) {
+ DeckList deckList = new DeckList();
+ for (int i = 1; i <= size; i++) {
+ Deck deck = new Deck(String.format("deck %d", i));
+ deckList.addDeck(deck);
+ }
+ return deckList;
+ }
+
+ Deck initialiseDeck(DeckList deckList, int size) {
+ deckList.addDeck(new Deck("Pokemon"));
+ Deck deck = deckList.getDeck(2);
+ for (int i = 1; i <= size; i++) {
+ FlashCard flashCard = new FlashCard(String.format("q %d", i), String.format("a %d", i));
+ deck.add(flashCard);
+ }
+ return deck;
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/seedu/ecardnomics/parser/DeckParserTest.java b/src/test/java/seedu/ecardnomics/parser/DeckParserTest.java
new file mode 100644
index 0000000000..d9e82c8400
--- /dev/null
+++ b/src/test/java/seedu/ecardnomics/parser/DeckParserTest.java
@@ -0,0 +1,401 @@
+package seedu.ecardnomics.parser;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import seedu.ecardnomics.command.VersionCommand;
+import seedu.ecardnomics.command.VoidCommand;
+import seedu.ecardnomics.command.deck.DoneEditCommand;
+import seedu.ecardnomics.command.deck.AddCommand;
+import seedu.ecardnomics.command.deck.DeleteCommand;
+import seedu.ecardnomics.command.deck.ListCommand;
+import seedu.ecardnomics.command.deck.HelpCommand;
+import seedu.ecardnomics.command.ExitCommand;
+import seedu.ecardnomics.command.normal.PowerPointCommand;
+import seedu.ecardnomics.command.normal.StartCommand;
+import seedu.ecardnomics.deck.Deck;
+import seedu.ecardnomics.deck.DeckList;
+import seedu.ecardnomics.deck.FlashCard;
+import seedu.ecardnomics.exceptions.BothOcAndCsException;
+import seedu.ecardnomics.exceptions.ColorsNotAvailException;
+import seedu.ecardnomics.exceptions.CsIndexFormatException;
+import seedu.ecardnomics.exceptions.CsIndexRangeException;
+import seedu.ecardnomics.exceptions.EmptyQnAException;
+import seedu.ecardnomics.exceptions.FlashCardRangeException;
+import seedu.ecardnomics.exceptions.IndexFormatException;
+import seedu.ecardnomics.exceptions.InvalidOptionsException;
+import seedu.ecardnomics.exceptions.InvalidPptxArgumentException;
+import seedu.ecardnomics.exceptions.NoAlphaNumericInputException;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+class DeckParserTest {
+ DeckParser deckParser;
+
+ @Test
+ void getIndex_validIndex_success() throws Exception {
+ assertEquals(0, deckParser.getIndex("1"));
+ assertEquals(1, deckParser.getIndex("2"));
+ }
+
+ @Test
+ void getIndex_validIndexSpacePadded_success() throws Exception {
+ assertEquals(0, deckParser.getIndex(" 1"));
+ assertEquals(0, deckParser.getIndex("\t1"));
+ assertEquals(1, deckParser.getIndex("2\t"));
+ assertEquals(1, deckParser.getIndex(" 2 "));
+ }
+
+ @Test
+ void getIndex_outOfRangeIndex_exceptionThrown() {
+ try {
+ assertEquals(1, deckParser.getIndex("3"));
+ assertEquals(1, deckParser.getIndex("0"));
+ fail();
+ } catch (Exception e) {
+ assertEquals((new FlashCardRangeException()).getMessage(), e.getMessage());
+ }
+ }
+
+ @Test
+ void getIndex_noIndex_exceptionThrown() {
+ try {
+ assertEquals(1, deckParser.getIndex(""));
+ assertEquals(1, deckParser.getIndex(" "));
+ assertEquals(1, deckParser.getIndex("something"));
+ assertEquals(1, deckParser.getIndex(" something"));
+ fail();
+ } catch (Exception e) {
+ assertEquals((new IndexFormatException()).getMessage(), e.getMessage());
+ }
+ }
+
+ @Test
+ void prepareFlashCard_oneLinerEmptyInputQuestion_EmptyQnAExceptionThrown() {
+ try {
+ assertEquals(1, deckParser.prepareFlashCard(" /ans some answer"));
+ // question input checked first
+ assertEquals(1, deckParser.prepareFlashCard(" /ans !@#"));
+ } catch (Exception e) {
+ assertEquals(new EmptyQnAException().getMessage(), e.getMessage());
+ }
+ }
+
+ @Test
+ void prepareFlashCard_oneLinerEmptyInputAnswerValidQuestion_EmptyQnAExceptionThrown() {
+ try {
+ // question input checked first
+ assertEquals(1, deckParser.prepareFlashCard("some question /ans "));
+ } catch (Exception e) {
+ assertEquals(new EmptyQnAException().getMessage(), e.getMessage());
+ }
+ }
+
+ @Test
+ void prepareFlashCard_oneLinerNoAlphaNumericQuestion_NoAlphaNumericExceptionThrown() {
+ try {
+ assertEquals(1, deckParser.prepareFlashCard("!@#.. /ans some answer"));
+ // question input checked first
+ assertEquals(1, deckParser.prepareFlashCard("!@#.. / "));
+ } catch (Exception e) {
+ assertEquals(new NoAlphaNumericInputException().getMessage(), e.getMessage());
+ }
+ }
+
+ @Test
+ void prepareFlashCard_oneLinerNoAlphaNumericAnswerValidQuestion_NoAlphaNumericExceptionThrown() {
+ try {
+ // question input checked first
+ assertEquals(1, deckParser.prepareFlashCard("some question /ans !@#.."));
+ } catch (Exception e) {
+ assertEquals(new NoAlphaNumericInputException().getMessage(), e.getMessage());
+ }
+ }
+
+ @Test
+ void prepareFlashCard_oneLinerInValidAnswerInvalidQuestion_questionDependentExceptionsThrown() {
+ try {
+ // question input checked first
+ // throws EmptyInputException
+ assertEquals(1, deckParser.prepareFlashCard(" /ans !@#.."));
+ // throws NoAlphaNumericException
+ assertEquals(1, deckParser.prepareFlashCard("!@#. /ans "));
+ } catch (EmptyQnAException eie) {
+ assertEquals(new EmptyQnAException().getMessage(), eie.getMessage());
+ } catch (NoAlphaNumericInputException nae) {
+ assertEquals(new NoAlphaNumericInputException().getMessage(), nae.getMessage());
+ }
+ }
+
+ @Test
+ void prepareFlashCard_oneLinerValidQuestionContainsAns() throws EmptyQnAException, NoAlphaNumericInputException {
+ assertTrue(deckParser.prepareFlashCard("question with/ans /ans answer") instanceof AddCommand);
+ }
+
+ @Test
+ void prepareFlashCard_oneLinerValidAnswerContainsAns() throws EmptyQnAException, NoAlphaNumericInputException {
+ assertTrue(deckParser.prepareFlashCard("question /ans answer with/ans") instanceof AddCommand);
+ assertTrue(deckParser.prepareFlashCard("question /ans answer /ans") instanceof AddCommand);
+ }
+
+ @Test
+ void prepareFlashCard_oneLinerEmptyQuestionEmptyAnswer() {
+ try {
+ assertTrue(deckParser.prepareFlashCard("/ans") instanceof AddCommand);
+ } catch (EmptyQnAException ee) {
+ assertEquals(new EmptyQnAException().getMessage(), ee.getMessage());
+ } catch (NoAlphaNumericInputException nae) {
+ assertTrue(!NoAlphaNumericInputException.NO_ALPHANUMERIC_LINE.equals(nae.getMessage()));
+ }
+ try {
+ assertTrue(deckParser.prepareFlashCard(" /ans ") instanceof AddCommand);
+ } catch (EmptyQnAException ee) {
+ assertEquals(new EmptyQnAException().getMessage(), ee.getMessage());
+ } catch (NoAlphaNumericInputException nae) {
+ assertTrue(!NoAlphaNumericInputException.NO_ALPHANUMERIC_LINE.equals(nae.getMessage()));
+ }
+ }
+
+ @Test
+ void parseCommand_ExitCommand_success() throws Exception {
+ assertTrue(deckParser.parseCommand("exit", "") instanceof ExitCommand);
+ }
+
+ @Test
+ void parseCommand_DoneEditCommand_success() throws Exception {
+ assertTrue(deckParser.parseCommand("done", "") instanceof DoneEditCommand);
+ }
+
+ @Test
+ void parseCommand_AddCommand_oneLine_success() throws Exception {
+ assertTrue(deckParser.parseCommand("add", "qn /ans ans") instanceof AddCommand);
+ }
+
+ @Test
+ void parseCommand_ListCommand_success() throws Exception {
+ assertTrue(deckParser.parseCommand("list", "") instanceof ListCommand);
+ }
+
+ @Test
+ void parseCommand_DeleteCommand_success() throws Exception {
+ assertTrue(deckParser.parseCommand("delete", "1 -y") instanceof DeleteCommand);
+ }
+
+ @Test
+ void parseCommand_DeleteCommandNoIndex_exceptionThrown() {
+ try {
+ deckParser.parseCommand("delete", "");
+ } catch (Exception e) {
+ assertTrue(e instanceof IndexFormatException);
+ }
+ }
+
+ @Test
+ void parseCommand_DeleteCommandOutOfRangeIndex_exceptionThrown() {
+ try {
+ deckParser.parseCommand("delete", "3");
+ } catch (Exception e) {
+ assertTrue(e instanceof FlashCardRangeException);
+ }
+ }
+
+ @Test
+ void parseCommand_UpdateCommandNoIndex_exceptionThrown() {
+ try {
+ deckParser.parseCommand("update", "");
+ } catch (Exception e) {
+ assertTrue(e instanceof IndexFormatException);
+ }
+ }
+
+ @Test
+ void parseCommand_UpdateCommandOutOfRangeIndex_exceptionThrown() {
+ try {
+ deckParser.parseCommand("update", "3");
+ } catch (Exception e) {
+ assertTrue(e instanceof FlashCardRangeException);
+ }
+ }
+
+ @Test
+ void parseCommand_PptxCommandForceYes_success() {
+ try {
+ assertTrue(deckParser.parseCommand("pptx", "-y") instanceof PowerPointCommand);
+ } catch (Exception e) {
+ System.out.println(" error");
+ }
+ }
+
+ @Test
+ void parseCommand_PptxCommandForceYesOriginalColor_success() {
+ try {
+ assertTrue(deckParser.parseCommand("pptx", "-oc blue green -y") instanceof PowerPointCommand);
+ assertTrue(deckParser.parseCommand("pptx", "-y -oc blue green") instanceof PowerPointCommand);
+ } catch (Exception e) {
+ System.out.println(" error");
+ }
+ }
+
+ @Test
+ void parseCommand_PptxCommandForceYesColorScheme_success() {
+ try {
+ assertTrue(deckParser.parseCommand("pptx", "-cs 1 -y") instanceof PowerPointCommand);
+ assertTrue(deckParser.parseCommand("pptx", "-y -cs 1") instanceof PowerPointCommand);
+ } catch (Exception e) {
+ System.out.println(" error");
+ }
+ }
+
+ @Test
+ void parseCommand_PptxCommandExtraArguments_exceptionThrown() {
+ try {
+ deckParser.parseCommand("pptx", "1");
+ deckParser.parseCommand("pptx", "1 -y");
+ deckParser.parseCommand("pptx", "-y 1");
+ fail();
+ } catch (Exception e) {
+ assertTrue(e instanceof InvalidPptxArgumentException);
+ }
+ }
+
+ @Test
+ void parseCommand_PptxCommandBothOcAndCs_exceptionThrown() {
+ try {
+ deckParser.parseCommand("pptx", "-y -oc green black -cs 1");
+ deckParser.parseCommand("pptx", "-y -cs 1 -oc black green");
+ fail();
+ } catch (Exception e) {
+ System.out.println(e.getMessage());
+ assertTrue(e instanceof BothOcAndCsException);
+ }
+ }
+
+ @Test
+ void parseCommand_PptxCommandOriginalColorUnavailableColor_exceptionThrown() {
+ try {
+ deckParser.parseCommand("pptx", "-y -oc green block");
+ deckParser.parseCommand("pptx", "-y -oc block green");
+ fail();
+ } catch (Exception e) {
+ assertTrue(e instanceof ColorsNotAvailException);
+ }
+ }
+
+ @Test
+ void parseCommand_PptxCommandInvalidOptions_exceptionThrown() {
+ try {
+ deckParser.parseCommand("pptx", "-y -cc green black");
+ deckParser.parseCommand("pptx", "-y -ss 1");
+ fail();
+ } catch (Exception e) {
+ System.out.println(e.getMessage());
+ assertTrue(e instanceof InvalidOptionsException);
+ }
+ }
+
+ @Test
+ void parseCommand_PptxCommandCsIndexOutOfRange_exceptionThrown() {
+ try {
+ deckParser.parseCommand("pptx", "-y -cs 0");
+ deckParser.parseCommand("pptx", "-y -cs 11");
+ fail();
+ } catch (Exception e) {
+ assertTrue(e instanceof CsIndexRangeException);
+ }
+ }
+
+ @Test
+ void parseCommand_PptxCommandCsIndexWrongFormat_exceptionThrown() {
+ try {
+ deckParser.parseCommand("pptx", "-y -cs -1");
+ deckParser.parseCommand("pptx", "-y -cs blue red");
+ fail();
+ } catch (Exception e) {
+ assertTrue(e instanceof CsIndexFormatException);
+ }
+ }
+
+ @Test
+ void parseCommand_HelpCommand_success() throws Exception {
+ assertTrue(deckParser.parseCommand("help", "") instanceof HelpCommand);
+ }
+
+ @Test
+ void parseCommand_StartCommandArgumentsSpacePadded_success() {
+ try {
+ assertTrue(deckParser.parseCommand("start", "") instanceof StartCommand);
+ assertTrue(deckParser.parseCommand("start", "\t") instanceof StartCommand);
+ assertTrue(deckParser.parseCommand("start", "\n") instanceof StartCommand);
+ } catch (Exception e) {
+ System.out.println(" error");
+ }
+ }
+
+ @Test
+ void parseCommand_StartCommandRandomArguments_success() {
+ try {
+ assertTrue(deckParser.parseCommand("start", "1") instanceof StartCommand);
+ assertTrue(deckParser.parseCommand("start", "\t1") instanceof StartCommand);
+ assertTrue(deckParser.parseCommand("start", "\n1") instanceof StartCommand);
+ } catch (Exception e) {
+ System.out.println(" error");
+ }
+ }
+
+ @Test
+ void parseCommand_VersionCommandAnyArguments_success() {
+ try {
+ assertTrue(deckParser.parseCommand("--version", "1") instanceof VersionCommand);
+ assertTrue(deckParser.parseCommand("--version", "\t1") instanceof VersionCommand);
+ assertTrue(deckParser.parseCommand("--version", "\n hdkljfashdfs\t\t") instanceof VersionCommand);
+ } catch (Exception e) {
+ System.out.println(" error");
+ }
+ }
+
+ @Test
+ void parse_invalidCommand_returnsVoidCommand() {
+ assertTrue(deckParser.parse("something random") instanceof VoidCommand);
+ assertTrue(deckParser.parse("") instanceof VoidCommand);
+ assertTrue(deckParser.parse(" ") instanceof VoidCommand);
+ assertTrue(deckParser.parse("\t") instanceof VoidCommand);
+ assertTrue(deckParser.parse("blah") instanceof VoidCommand);
+ }
+
+ //@BeforeAll
+ //public static void addUserInput() {
+ //String userInputs = "q1" + System.getProperty("line.separator") + "a1" + System.getProperty("line.separator")
+ //+ "y" + System.getProperty("line.separator") + "y";
+ //ByteArrayInputStream input = new ByteArrayInputStream(userInputs.getBytes());
+ //System.setIn(input);
+ //}
+
+ @BeforeEach
+ void initialiseDeckParser() {
+ DeckList deckList = initialiseDeckList(2);
+ Deck deck = initialiseDeck(deckList, 2);
+ deckParser = new DeckParser(deckList, deck);
+ }
+
+ DeckList initialiseDeckList(int size) {
+ DeckList deckList = new DeckList();
+ for (int i = 1; i <= size; i++) {
+ Deck deck = new Deck(String.format("deck %d", i));
+ deckList.addDeck(deck);
+ }
+ return deckList;
+ }
+
+ Deck initialiseDeck(DeckList deckList, int size) {
+ deckList.addDeck(new Deck("Pokemon"));
+ Deck deck = deckList.getDeck(2);
+ for (int i = 1; i <= size; i++) {
+ FlashCard flashCard = new FlashCard(String.format("q %d", i), String.format("a %d", i));
+ deck.add(flashCard);
+ }
+ return deck;
+ }
+
+
+}
\ No newline at end of file
diff --git a/src/test/java/seedu/ecardnomics/parser/GameParserTest.java b/src/test/java/seedu/ecardnomics/parser/GameParserTest.java
new file mode 100644
index 0000000000..3b604134fc
--- /dev/null
+++ b/src/test/java/seedu/ecardnomics/parser/GameParserTest.java
@@ -0,0 +1,106 @@
+package seedu.ecardnomics.parser;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import seedu.ecardnomics.command.ExitCommand;
+import seedu.ecardnomics.command.VersionCommand;
+import seedu.ecardnomics.command.VoidCommand;
+import seedu.ecardnomics.command.game.DoneGameCommand;
+import seedu.ecardnomics.command.game.GameResponseCommand;
+import seedu.ecardnomics.command.game.HelpCommand;
+import seedu.ecardnomics.deck.Deck;
+import seedu.ecardnomics.deck.DeckList;
+import seedu.ecardnomics.deck.FlashCard;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class GameParserTest {
+ GameParser gameParser;
+
+ @Test
+ void parseCommand_ExitCommand_success() {
+ assertTrue(gameParser.parseCommand("exit", "") instanceof ExitCommand);
+ }
+
+ @Test
+ void parseCommand_DoneGameCommand_success() {
+ assertTrue(gameParser.parseCommand("done", "") instanceof DoneGameCommand);
+
+
+ }
+
+ @Test
+ void parseCommand_HelpCommandAnyArguments_success() {
+ assertTrue(gameParser.parseCommand("help", "") instanceof HelpCommand);
+ assertTrue(gameParser.parseCommand("help", "gibberish\t") instanceof HelpCommand);
+ assertTrue(gameParser.parseCommand("help", "\n 12gibberish\t") instanceof HelpCommand);
+ }
+
+ @Test
+ void parseCommand_VersionCommandAnyArguments_success() {
+ assertTrue(gameParser.parseCommand("--version", "") instanceof VersionCommand);
+ assertTrue(gameParser.parseCommand("--version", "\t1") instanceof VersionCommand);
+ assertTrue(gameParser.parseCommand("--version", "\n hdkljfashdfs\t\t") instanceof VersionCommand);
+ }
+
+ @Test
+ void parseCommand_AnythingElse_returnsVoidCommand() {
+ assertTrue(gameParser.parseCommand("random", "") instanceof VoidCommand);
+ assertTrue(gameParser.parseCommand("lmaoo sdalkfe we", "") instanceof VoidCommand);
+ }
+
+ @Test
+ void parse_someGameModeCommandPermissibleFormat_success() {
+ assertTrue(gameParser.parse("--version") instanceof VersionCommand);
+ assertTrue(gameParser.parse("--version ") instanceof VersionCommand);
+ assertTrue(gameParser.parse("done") instanceof DoneGameCommand);
+ assertTrue(gameParser.parse("done ") instanceof DoneGameCommand);
+ assertTrue(gameParser.parse("exit") instanceof ExitCommand);
+ assertTrue(gameParser.parse("help") instanceof HelpCommand);
+ }
+
+ @Test
+ void parse_commandWordWithOtherWords_returnsGameResponseCommand() {
+ assertTrue(gameParser.parse("--version s df") instanceof GameResponseCommand);
+ assertTrue(gameParser.parse("--version 22 ") instanceof GameResponseCommand);
+ assertTrue(gameParser.parse("done sdsd") instanceof GameResponseCommand);
+ assertTrue(gameParser.parse("done 22") instanceof GameResponseCommand);
+ assertTrue(gameParser.parse("exit sd2") instanceof GameResponseCommand);
+ assertTrue(gameParser.parse("help sde 2") instanceof GameResponseCommand);
+ }
+
+ @Test
+ void parse_AnythingElseGivesGameResponseCommand_responseRecordedTrimmed() {
+ GameResponseCommand response = (GameResponseCommand) gameParser.parse("Some answer here");
+ assertEquals(response.getAttempt(), "Some answer here");
+ response = (GameResponseCommand) gameParser.parse(" Some answer here ");
+ assertEquals(response.getAttempt(), "Some answer here");
+ }
+
+ @BeforeEach
+ void initialiseGameParser() {
+ DeckList deckList = initialiseDeckList(2);
+ Deck deck = initialiseDeck(deckList, 2);
+ gameParser = new GameParser(deck);
+ }
+
+ DeckList initialiseDeckList(int size) {
+ DeckList deckList = new DeckList();
+ for (int i = 1; i <= size; i++) {
+ Deck deck = new Deck(String.format("deck %d", i));
+ deckList.addDeck(deck);
+ }
+ return deckList;
+ }
+
+ Deck initialiseDeck(DeckList deckList, int size) {
+ deckList.addDeck(new Deck("Pokemon"));
+ Deck deck = deckList.getDeck(2);
+ for (int i = 1; i <= size; i++) {
+ FlashCard flashCard = new FlashCard(String.format("q %d", i), String.format("a %d", i));
+ deck.add(flashCard);
+ }
+ return deck;
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/seedu/ecardnomics/parser/NormalParserTest.java b/src/test/java/seedu/ecardnomics/parser/NormalParserTest.java
new file mode 100644
index 0000000000..3b081cd2d1
--- /dev/null
+++ b/src/test/java/seedu/ecardnomics/parser/NormalParserTest.java
@@ -0,0 +1,334 @@
+package seedu.ecardnomics.parser;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import seedu.ecardnomics.command.VersionCommand;
+import seedu.ecardnomics.command.VoidCommand;
+import seedu.ecardnomics.command.ExitCommand;
+import seedu.ecardnomics.command.normal.DeleteDeckCommand;
+import seedu.ecardnomics.command.normal.EditCommand;
+import seedu.ecardnomics.command.normal.CreateCommand;
+import seedu.ecardnomics.command.normal.DecksCommand;
+import seedu.ecardnomics.command.normal.PowerPointCommand;
+import seedu.ecardnomics.command.normal.HelpCommand;
+import seedu.ecardnomics.command.normal.StartCommand;
+import seedu.ecardnomics.deck.Deck;
+import seedu.ecardnomics.deck.DeckList;
+import seedu.ecardnomics.exceptions.BothOcAndCsException;
+import seedu.ecardnomics.exceptions.ColorsNotAvailException;
+import seedu.ecardnomics.exceptions.CsIndexFormatException;
+import seedu.ecardnomics.exceptions.CsIndexRangeException;
+import seedu.ecardnomics.exceptions.DeckRangeException;
+import seedu.ecardnomics.exceptions.EmptyInputException;
+import seedu.ecardnomics.exceptions.IndexFormatException;
+import seedu.ecardnomics.exceptions.InvalidOptionsException;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+class NormalParserTest {
+ NormalParser normalParser;
+
+ @Test
+ void getIndex_validIndex_success() throws Exception {
+ assertEquals(0, normalParser.getIndex("1"));
+ assertEquals(1, normalParser.getIndex("2"));
+ }
+
+ @Test
+ void getIndex_validIndexSpacePadded_success() throws Exception {
+ assertEquals(0, normalParser.getIndex(" 1"));
+ assertEquals(0, normalParser.getIndex("\t1"));
+ assertEquals(1, normalParser.getIndex("2\t"));
+ assertEquals(1, normalParser.getIndex(" 2 "));
+ }
+
+ @Test
+ void getIndex_outOfRangeIndex_exceptionThrown() {
+ try {
+ assertEquals(1, normalParser.getIndex("3"));
+ assertEquals(1, normalParser.getIndex("-1"));
+ assertEquals(1, normalParser.getIndex("0"));
+ fail();
+ } catch (Exception e) {
+ assertEquals((new DeckRangeException()).getMessage(), e.getMessage());
+ }
+ }
+
+ @Test
+ void getIndex_noIndex_exceptionThrown() {
+ try {
+ assertEquals(1, normalParser.getIndex(""));
+ assertEquals(1, normalParser.getIndex(" "));
+ assertEquals(1, normalParser.getIndex("something"));
+ assertEquals(1, normalParser.getIndex(" something"));
+ fail();
+ } catch (Exception e) {
+ assertEquals((new IndexFormatException()).getMessage(), e.getMessage());
+ }
+ }
+
+ @Test
+ void getCsIndex_indexOutOfRange_exceptionThrown() {
+ try {
+ assertEquals(1, normalParser.getCsIndex("11"));
+ assertEquals(1, normalParser.getCsIndex("0"));
+ fail();
+ } catch (Exception e) {
+ assertTrue(e instanceof CsIndexRangeException);
+ }
+ }
+
+ @Test
+ void getCsCsIndex_indexWrongFormat_exceptionThrown() {
+ try {
+ assertEquals(1, normalParser.getCsIndex("-1"));
+ assertEquals(1, normalParser.getCsIndex("blue"));
+ assertEquals(1, normalParser.getIndex(""));
+ fail();
+ } catch (Exception e) {
+ assertTrue(e instanceof CsIndexFormatException);
+ }
+ }
+
+ @Test
+ void preparePptxCommand_forceYes_success() {
+ try {
+ assertTrue(normalParser.preparePptxCommand("1 -y") instanceof PowerPointCommand);
+ assertTrue(normalParser.preparePptxCommand("-y 1") instanceof PowerPointCommand);
+ } catch (Exception e) {
+ System.out.println(" error");
+ }
+ }
+
+ @Test
+ void preparePptxCommand_forceYesOriginalColor_success() {
+ try {
+ assertTrue(normalParser.preparePptxCommand("1 -oc blue green -y") instanceof PowerPointCommand);
+ assertTrue(normalParser.preparePptxCommand("1 -y -oc blue green") instanceof PowerPointCommand);
+ } catch (Exception e) {
+ System.out.println(" error");
+ }
+ }
+
+ @Test
+ void preparePptxCommand_forceYesColorScheme_success() {
+ try {
+ assertTrue(normalParser.preparePptxCommand("1 -cs 1 -y") instanceof PowerPointCommand);
+ assertTrue(normalParser.preparePptxCommand("1 -y -cs 1") instanceof PowerPointCommand);
+ } catch (Exception e) {
+ System.out.println(" error");
+ }
+ }
+
+ @Test
+ void preparePptxCommand_bothOcAndCs_exceptionThrown() {
+ try {
+ normalParser.preparePptxCommand("1 -y -oc green black -cs 1");
+ normalParser.preparePptxCommand("1 -y -cs 1 -oc black green");
+ fail();
+ } catch (Exception e) {
+ assertTrue(e instanceof BothOcAndCsException);
+ }
+ }
+
+ @Test
+ void preparePptxCommand_originalColorUnavailableColor_exceptionThrown() {
+ try {
+ normalParser.preparePptxCommand("1 -y -oc green block");
+ normalParser.preparePptxCommand("1 -y -oc block green");
+ fail();
+ } catch (Exception e) {
+ assertTrue(e instanceof ColorsNotAvailException);
+ }
+ }
+
+ @Test
+ void preparePptxCommand_invalidOptions_exceptionThrown() {
+ try {
+ normalParser.preparePptxCommand("1 -y -cc green black");
+ normalParser.preparePptxCommand("1 -y -ss 1");
+ fail();
+ } catch (Exception e) {
+ assertTrue(e instanceof InvalidOptionsException);
+ }
+ }
+
+ @Test
+ void parseCommand_VersionCommand_success() throws Exception {
+ assertTrue(normalParser.parseCommand("--version", "") instanceof VersionCommand);
+ }
+
+
+ @Test
+ void parseCommand_ExitCommand_success() throws Exception {
+ assertTrue(normalParser.parseCommand("exit", "") instanceof ExitCommand);
+ }
+
+ @Test
+ void parseCommand_EditCommand_success() throws Exception {
+ assertTrue(normalParser.parseCommand("edit", "1") instanceof EditCommand);
+ assertTrue(normalParser.parseCommand("edit", "2") instanceof EditCommand);
+ }
+
+ @Test
+ void parseCommand_EditCommandNoIndex_exceptionThrown() {
+ try {
+ assertTrue(normalParser.parseCommand("edit", "") instanceof EditCommand);
+ fail();
+ } catch (Exception e) {
+ assertTrue(e instanceof IndexFormatException);
+ }
+ }
+
+ @Test
+ void parseCommand_EditCommandOutOfRangeIndex_exceptionThrown() {
+ try {
+ normalParser.parseCommand("edit", "0");
+ normalParser.parseCommand("edit", "3");
+ fail();
+ } catch (Exception e) {
+ assertTrue(e instanceof DeckRangeException);
+ }
+ }
+
+ @Test
+ void parseCommand_CreateCommand_success() throws Exception {
+ assertTrue(normalParser.parseCommand("create", "Deck 3") instanceof CreateCommand);
+ }
+
+ @Test
+ void parseCommand_CreateCommandEmptyInput_exceptionThrown() throws Exception {
+ try {
+ normalParser.parseCommand("create", "");
+ fail();
+ } catch (Exception e) {
+ assertTrue(e instanceof EmptyInputException);
+ }
+ }
+
+
+ @Test
+ void parseCommand_DecksCommand_success() throws Exception {
+ assertTrue(normalParser.parseCommand("decks", "") instanceof DecksCommand);
+ }
+
+ @Test
+ void parseCommand_DeleteDeckCommand_force_success() throws Exception {
+ assertTrue(normalParser.parseCommand("delete", "1 -y") instanceof DeleteDeckCommand);
+ }
+
+ @Test
+ void parseCommand_DeleteDeckCommandNoIndex_exceptionThrown() {
+ try {
+ normalParser.parseCommand("delete", "");
+ fail();
+ } catch (Exception e) {
+ assertTrue(e instanceof IndexFormatException);
+ }
+ }
+
+ @Test
+ void parseCommand_DeleteDeckCommandOutOfRangeIndex_exceptionThrown() {
+ try {
+ normalParser.parseCommand("delete", "3");
+ fail();
+ } catch (Exception e) {
+ assertTrue(e instanceof DeckRangeException);
+ }
+ }
+
+
+
+ @Test
+ void parseCommand_PptxCommandOutOfRangeIndex_exceptionThrown() {
+ try {
+ normalParser.parseCommand("pptx", "3");
+ fail();
+ } catch (Exception e) {
+ assertTrue(e instanceof DeckRangeException);
+ }
+ }
+
+ @Test
+ void parseCommand_StartCommandValidArgumentsSpacePadded_success() {
+ try {
+ assertTrue(normalParser.parseCommand("start", "1") instanceof StartCommand);
+ assertTrue(normalParser.parseCommand("start", " 1 ") instanceof StartCommand);
+ assertTrue(normalParser.parseCommand("start", "\t1\n") instanceof StartCommand);
+ } catch (Exception e) {
+ System.out.println(" error");
+ }
+ }
+
+ @Test
+ void parseCommand_StartCommandNoIndex_exceptionThrown() {
+ try {
+ normalParser.parseCommand("start", "");
+ fail();
+ } catch (Exception e) {
+ assertTrue(e instanceof IndexFormatException);
+ }
+ }
+
+ @Test
+ void parseCommand_StartCommandTooHighRangeIndex_exceptionThrown() {
+ try {
+ normalParser.parseCommand("start", "3");
+ fail();
+ } catch (Exception e) {
+ assertTrue(e instanceof DeckRangeException);
+ }
+ }
+
+ // @Test
+ // void parseCommand_StartCommandTooLowRangeIndex_exceptionThrown() {
+ // try {
+ // normalParser.parseCommand("start", "-1");
+ // fail();
+ // } catch (Exception e) {
+ // assertTrue(e instanceof DeckRangeException);
+ // }
+ // }
+
+ @Test
+ void parseCommand_HelpCommand_success() throws Exception {
+ assertTrue(normalParser.parseCommand("help", "") instanceof HelpCommand);
+ }
+
+ @Test
+ void parseCommand_VersionCommandAnyArguments_success() {
+ try {
+ assertTrue(normalParser.parseCommand("--version", "1") instanceof VersionCommand);
+ assertTrue(normalParser.parseCommand("--version", "\t1") instanceof VersionCommand);
+ assertTrue(normalParser.parseCommand("--version", "\n hdkljfashdfs\t\t") instanceof VersionCommand);
+ } catch (Exception e) {
+ System.out.println(" error");
+ }
+ }
+
+ @Test
+ void parse_invalidCommand_returnsVoidCommand() {
+ assertTrue(normalParser.parse("something random") instanceof VoidCommand);
+ assertTrue(normalParser.parse("") instanceof VoidCommand);
+ assertTrue(normalParser.parse(" ") instanceof VoidCommand);
+ assertTrue(normalParser.parse("\t") instanceof VoidCommand);
+ assertTrue(normalParser.parse("blah") instanceof VoidCommand);
+ }
+
+ @BeforeEach
+ void initialiseNormalParser() {
+ DeckList deckList = initialiseDeckList(2);
+ normalParser = new NormalParser(deckList);
+ }
+
+ DeckList initialiseDeckList(int size) {
+ DeckList deckList = new DeckList();
+ for (int i = 1; i <= size; i++) {
+ Deck deck = new Deck(String.format("deck %d", i));
+ deckList.addDeck(deck);
+ }
+ return deckList;
+ }
+}
\ No newline at end of file
diff --git a/text-ui-test/ACTUAL.TXT b/text-ui-test/ACTUAL.TXT
new file mode 100644
index 0000000000..b651c6b210
--- /dev/null
+++ b/text-ui-test/ACTUAL.TXT
@@ -0,0 +1,14 @@
+--------------------------------------------------------------------------------
+ ___ _ _
+ ___ / __\__ _ _ __ __| |_ __ ___ _ __ ___ (_) ___ ___
+ / _ \/ / / _` | '__/ _` | '_ \ / _ \| '_ ` _ \| |/ __/ __|
+| __/ /__| (_| | | | (_| | | | | (_) | | | | | | | (__\__ \
+ \___\____/\__,_|_| \__,_|_| |_|\___/|_| |_| |_|_|\___|___/
+
+Hello! Welcome to eCardnomics,
+your one stop Flash Cards solution
+--------------------------------------------------------------------------------
+[Normal]
+ > --------------------------------------------------------------------------------
+Bye. Hope to see you again soon!
+--------------------------------------------------------------------------------
diff --git a/text-ui-test/EXPECTED-UNIX.TXT b/text-ui-test/EXPECTED-UNIX.TXT
new file mode 100644
index 0000000000..1162be95c2
--- /dev/null
+++ b/text-ui-test/EXPECTED-UNIX.TXT
@@ -0,0 +1,14 @@
+------------------------------------------------------------
+ ___ _ _
+ ___ / __\__ _ _ __ __| |_ __ ___ _ __ ___ (_) ___ ___
+ / _ \/ / / _` | '__/ _` | '_ \ / _ \| '_ ` _ \| |/ __/ __|
+| __/ /__| (_| | | | (_| | | | | (_) | | | | | | | (__\__ \
+ \___\____/\__,_|_| \__,_|_| |_|\___/|_| |_| |_|_|\___|___/
+
+Hello! Welcome to eCardnomics,
+your one stop Flash Cards solution
+------------------------------------------------------------
+[Normal]
+ > ------------------------------------------------------------
+Bye. Hope to see you again soon!
+------------------------------------------------------------
diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT
index 892cb6cae7..294a4e5490 100644
--- a/text-ui-test/EXPECTED.TXT
+++ b/text-ui-test/EXPECTED.TXT
@@ -1,9 +1,14 @@
-Hello from
- ____ _
-| _ \ _ _| | _____
-| | | | | | | |/ / _ \
-| |_| | |_| | < __/
-|____/ \__,_|_|\_\___|
+--------------------------------------------------------------------------------
+ ___ _ _
+ ___ / __\__ _ _ __ __| |_ __ ___ _ __ ___ (_) ___ ___
+ / _ \/ / / _` | '__/ _` | '_ \ / _ \| '_ ` _ \| |/ __/ __|
+| __/ /__| (_| | | | (_| | | | | (_) | | | | | | | (__\__ \
+ \___\____/\__,_|_| \__,_|_| |_|\___/|_| |_| |_|_|\___|___/
-What is your name?
-Hello James Gosling
+Hello! Welcome to eCardnomics,
+your one stop Flash Cards solution
+--------------------------------------------------------------------------------
+[Normal]
+ > --------------------------------------------------------------------------------
+Bye. Hope to see you again soon!
+--------------------------------------------------------------------------------
diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt
index f6ec2e9f95..dc1c9e520a 100644
--- a/text-ui-test/input.txt
+++ b/text-ui-test/input.txt
@@ -1 +1,2 @@
-James Gosling
\ No newline at end of file
+exit
+
diff --git a/text-ui-test/runtest.bat b/text-ui-test/runtest.bat
index 25ac7a2989..f78bf8b2f5 100644
--- a/text-ui-test/runtest.bat
+++ b/text-ui-test/runtest.bat
@@ -16,4 +16,4 @@ java -jar %jarloc% < ..\..\text-ui-test\input.txt > ..\..\text-ui-test\ACTUAL.TX
cd ..\..\text-ui-test
-FC ACTUAL.TXT EXPECTED.TXT >NUL && ECHO Test passed! || Echo Test failed!
+FC ACTUAL.TXT EXPECTED.TXT && ECHO Test passed! || Echo Test failed!