Skip to content

Latest commit

 

History

History
657 lines (435 loc) · 29.2 KB

File metadata and controls

657 lines (435 loc) · 29.2 KB

Dukemon - Developer Guide

1. Setting up

Refer to the guide here.

2. Design

2.1. Architecture

ArchitectureDiagram
Figure 1. Dukemon Architecture Diagram

The Architecture Diagram given above explains the high-level design of the Dukemon. Given below is a quick overview of each component.

💡
The .puml files used to create diagrams in this document can be found in the diagrams folder. Refer to the Using PlantUML guide to learn how to create and edit diagrams.

Main has two classes called Main and MainApp. It is responsible for,

  • At app launch: Initializes the components in the correct sequence, and connects them up with each other.

  • At shut down: Shuts down the components and invokes cleanup method where necessary.

Commons represents a collection of classes used by multiple other components. The following class plays an important role at the architecture level:

  • LogsCenter : Used by many classes to write log messages to the App’s log file.

The rest of Dukemon contains seven componenets.

  • UI:
    The Graphical UI of Dukemon that interacts with the user.

  • AppManager:
    The buffer between the User and Dukemon’s internal components.

  • Timer:
    The internal Timer that triggers events based on time elapsed.

  • Logic:
    The main command executor and performer of operations.

  • Model:
    Holds the non-game data in-memory.

  • Game:
    Holds the data of live game sessions in-memory.

  • Storage:
    Reads data from, and writes data to, the local hard disk.

For the components UI, Logic, Model, Timer and Game:

  • Defines its API in an interface with the same name as the Component.

  • Exposes its functionality using a {Component Name}Manager class.

For example, the Logic component (see the class diagram given below) defines it’s API in the Logic.java interface and exposes its functionality using the LogicManager.java class.

LogicClassDiagram
Figure 2. Class Diagram of the Logic Component

How the architecture components interact with each other

The Sequence Diagram below shows how the components interact with each other for the scenario where the user issues the command delete 1.

ArchitectureSequenceDiagram
Figure 3. Component interactions for delete 1 command

The sections below give more details of each component.

2.2. UI component

UiClassDiagram
Figure 4. Structure of the UI Component

API : Ui.java

The UI consists of a MainWindow that is made up of parts e.g.CommandBox, ResultDisplay, PersonListPanel, StatusBarFooter etc. All these, including the MainWindow, inherit from the abstract UiPart class.

The UI component uses JavaFx UI framework. The layout of these UI parts are defined in matching .fxml files that are in the src/main/resources/view folder. For example, the layout of the MainWindow is specified in MainWindow.fxml

The UI component,

  • Executes user commands using the Logic component.

  • Listens for changes to Model data so that the UI can be updated with the modified data.

2.3. AppManager component

"Explain AppManager component"

2.4. Timer component

TimerClassDiagram
Figure 5. Structure of the Timer Component

The Timer consists of a GameTimer that will keep track of time elapsed via an internal countdown timer and notify the AppManager, who will notify the UI components.

  • Dealing with the internal countdown timer that runs during a game session.

  • Periodically triggering callbacks that will notify either the AppManager component.

  • Gets timestamps to trigger Hints via a HintTimingQueue

Due to the fact that the timer has to work closely with the UI and AppManager (without being coupled directly), it is separated from the Logic, Model and Game components.

2.5. Logic component

LogicClassDiagram
Figure 6. Structure of the Logic Component

API : Logic.java

  1. Logic uses the ParserManager class to parse the user command.

  2. This results in a Command object which is executed by the LogicManager.

  3. The command execution can affect the Model (e.g. adding a person).

  4. The result of the command execution is encapsulated as a CommandResult object which is passed back to the Ui and AppManager.

  5. In addition, the CommandResult object can also instruct the Ui to perform certain actions, such as displaying help to the user.

  6. Furthermore, through the UiLogicHelper Interface, it updates the AutoComplete suggestions for every keystroke.

Given below is the Activity Diagram that shows the workflows that change state of ParserManager that produce different outcomes for commands entered.

ParserManagerActivityDiagram
Figure 7. Activity diagram of Application flow
ℹ️
The lifeline for DeleteCommandParser should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.

2.6. Model component

ModelClassDiagram
Figure 8. Structure of the Model Component

API : Model.java

The Model,

  • stores a UserPref object that represents the user’s preferences.

  • stores the Address Book data.

  • exposes an unmodifiable ObservableList<Person> that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change.

  • does not depend on any of the other three components.

ℹ️
As a more OOP model, we can store a Tag list in Address Book, which Person can reference. This would allow Address Book to only require one Tag object per unique Tag, instead of each Person needing their own Tag object. An example of how such a model may look like is given below.

BetterModelClassDiagram

2.7. Game component

GameClassDiagram
Figure 9. Structure of the Game Component

The Game,

  • stores a shuffled List<Card> that is cloned/copied from a ReadOnlyWordBank.

  • maintains an Index to keep track of the state of the game.

  • has an associated DifficultyEnum that dictates the time allowed for each question.

  • verifies Guess that are sent by Logic (User’s guesses)

2.8. Storage component

StorageClassDiagram
Figure 10. Structure of the Storage Component

API : Storage.java

The Storage component,

  • can save UserPref objects in json format and read it back.

  • can save the Address Book data in json format and read it back.

2.9. Common classes

Classes used by multiple components are in the seedu.Dukemon.commons package.

3. Implementation

This section describes some noteworthy details on how certain features are implemented.

3.1. AutoComplete Feature (for command execution)

3.1.1. Implementation

The AutoComplete is facilitated by ParserManager. It dynamically changes parser depending on current mode the game is at. When updating the User Interface for every keystroke, it ensures only the right commands get parsed and autocompleted at each moment.

For the UI package to have access to this information, it exposes its functionality through LogicManager as UiLogicHelper interface with the following getter methods:

  • List<AutoFillAction>#getMenuItems(String text) — Gets an List of AutoFillActions to fill up AutoComplete display based on current user input given in text

  • ModeEnum#getMode() — Retrieves the application mode to display visually to the user (represented by enumeration object ModeEnum)

  • List<ModeEnum>#getModes() — Retrieves the possible modes the user can transition to from current mode

These operations are implemented in SpecificModeParser aided by ClassUtil to handle instantiation of Parser and Command objects.

Given below is an example usage scenario and how the AutoCompletion mechanism behaves at each step.

Step 1. The user launches the application. The VersionedDukemon will be initialized with the initial address book state, and the currentStatePointer pointing to that single address book state.

Replace image with state of ParserManager

Step 2. The user enters start difficult command to begin the game. The LogicManager passes string to Model#commitDukemon(), causing the modified state of the address book after the delete 5 command executes to be saved in the DukemonStateList, and the currentStatePointer is shifted to the newly inserted address book state.

Step 3. The user executes stop to abruptly end the game. The add command also calls Model#commitDukemon(), causing another modified address book state to be saved into the DukemonStateList.

Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing the undo command. The undo command will call Model#undoDukemon(), which will shift the currentStatePointer once to the left, pointing it to the previous address book state, and restores the address book to that state.

The following sequence diagram shows how the AutoComplete operation works:

AutoCompleteSequenceDiagram
ℹ️
The lifeline for UndoCommand should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.

The redo command does the opposite — it calls Model#redoDukemon(), which shifts the currentStatePointer once to the right, pointing to the previously undone state, and restores the address book to that state.

The redo command does the opposite — it calls Model#redoAddressBook(), which shifts the currentStatePointer once to the right, pointing to the previously undone state, and restores the address book to that state.

3.1.2. Design Considerations

Aspect: How parser and command objects are instantiated in SpecificModeParser
  • Alternative 1 (current choice): Use java reflections to hold a List of Classes and iterate through them to pick the matching Classes

    • Pros: Open Close Principle strictly followed. Adding a command with a parser takes only one line of code.

    • Cons: It is developer responsibility to ensure classes subclass Command object as compile time errors would not be thrown.

  • Alternative 2: Use switches in Parser to match Command Word to create Parser objects if necessary else directly create Command object.

    • Pros: Compile time error would be thrown if new command or parser does not subclass correctly.

    • Cons: Adding a new command with parser would require the developer to insert it into multiple locations as the autocomplete feature needs an iterable command list.

Aspect: Single Parser vs Parser Manager
  • Alternative 1 (current choice): Using a ParserManager to dynamically switch between Parsers based on current state

    • Pros: Commands not belonging to specific mode would not be parsed

    • Cons: More code to right for initial developer. However it is easily extnensible for new modes and parsers by the Open Close Principle

  • Alternative 2: Use HistoryManager for undo/redo

    • Pros: We do not need to maintain a separate list, and just reuse what is already in the codebase.

    • Cons: Requires dealing with commands that have already been undone: We must remember to skip these commands. Violates Single Responsibility Principle and Separation of Concerns as HistoryManager now needs to do two different things.

3.2. Settings Feature

3.2.1. Implementation

AppSettings was a class that was created to be integrated into the Model of the app. It currently contains these functionalities:

  • difficulty [EASY/MEDIUM/HARD] to change the difficulty of the game.

  • hints [ON/OFF] to turn hints on or off.

  • theme [DARK/LIGHT] to change the theme of the app. Currently only supporting dark and light themes.

This feature provides the user an interface to make their own changes to the state of the machine. The settings set by the user will also be saved to a .json file under data/appsettings.json.

The activity diagram below summarizes what happens in the execution of a settings command:

SettingsActivityDiagram
Figure 11. Activity diagram of the execution of a settings command.
ℹ️
Take note that "mode" as defined in our project is the state in which the application is able to take commands specific to that mode.

Given below is a step by step walk-through of what happens when a user executes a difficulty command while in settings mode:

  1. Let us assume that the current difficulty of the application is "EASY". The object diagram below shows the current state of AppSettings.

  2. When the user enters difficulty hard, the command gets passed into Ui first, which executes AppManager#execute(), which passes straight to LogicManager#execute() without any logic conditions to determine its execution path.

  3. At LogicManager#execute() however, the command gets passed into a parser manager which filters out the DifficultyCommand as a non-switch command and it creates a DifficultyCommand to be executed.

  4. Upon execution of the DifficultyCommand, the state of the model is changed such that the DifficultyEnum in AppSettings is now set to HARD.

  5. Since the main function of the difficulty command is accomplished and all that is left is to update the ui, the CommandResult that is produced by the execution of the command goes back to Ui without much problem.

  6. Assuming that there were no errors thrown during the execution of the difficulty command, the execution calls updateModularDisplay in UpdateUi. In here, the ModeEnum.SETTINGS is registered and it updates the settings display to properly reflect the change in difficulty.

3.2.2. Design Considerations

There were a few considerations for implementing an interface that essentially allows users to touch a lot of parts of the application through settings and some of these methods break software design principles. These are the considerations we came across:

Alternative 1

Alternative 2

Aspect 1:
Where to effect change when a setting is changed by the user

Effecting the change inside the execute() command of the settings commands:
Pros:
Since the Command is taking care of all the execution, there is no need to worry about extra implementation of the settings' effects in their classes.
Cons:
However, there are certain situations that will break software design principles, such as the Single Responsibility Principle by doing the job of already existing classes.

Effecting the change in the part of the architecture that the setting is affecting. E.g, Changing the theme inside Ui or changing the difficulty inside model
Pros:
This method practises good software engineering principles and it abides by the architecture diagram shown above as to where the changes of the settings are being effected.
Cons:
This method however requires that the reader gets familiar with the whole architecture diagram as they need to know where to implement the actual change in settings as opposed to creating a new class that performs the same functionality of an existing class.

Why did we choose Alternative 2:
We believe that software design principles exist for a reason. Furthermore, while alternative 1 may seem a lot simpler, Alternative 2 allows for extension just by adding new methods and refrains the user from having to extensively rework the structure of the application in order to add a new setting.

Aspect 2:
How to store information regarding the different settings

Storing it inside the enumerations that make up the choices for the settings
Pros:
Having the information stored inside the enum allows for immutablilty, such that no other class can change the properties of the enums. Only the developer can change the values of the enums and it will subsequently affect all the methods and functionality that relies on said enum.
Cons:
In the case that the user wants to customise certain continuous settings such as time limit, they are unable to as those settings are already defined by the developer to be discrete options.

Storing it inside the classes that implement the settings
Pros
The information is easily accessible from within the class itself and there is no need for extra import classes to handle the enums in alternative 1.
Cons
Unlike Alternative 1, the developer can create an extension to the class implementing the setting to allow the user to customise their settings even further, allowing for continuous values to be used rather than discrete values.

Why did we choose Alternative 1:
The considerations for this aspect was mainly down to how much customisability we wanted to grant our users. While having more customisability is better in some cases, in this one, we do not think the added functionality of allowing the user to extensively customise their experience with our application to be particularly impactful not necessary. Moreover, alternative 2 makes for a less organised code base and we wanted to avoid that as much as possible.

3.3. Timer Feature

TimerDGScreenshot
Figure 12. Screenshot of the Timer component in action as seen through the UI.

3.3.1. Implementation Overview

The Timer component utilizes the java.util.Timer API to simulate a stopwatch during a Game. It also relies on using Functional Interfaces as callbacks to periodically notify other components in the system. Using callbacks allows the Timer to enact changes in other components of the system without directly holding a reference to those components.

Internally, the Timer works by using the method java.util.Timer.schedule() that will schedule java.util.TimerTasks at a fixed rate.

An Observer Pattern is loosly followed between the Timer and the other components. As opposed to defining an Observable interface, the AppManager simply passes in method pointers into the Timer to callback when an event is triggered. The AppManager thus works closely with the Timer as the main hub to enact changes based on signals given by the Timer.

ℹ️
To avoid synchronization issues with the UI component, all TimerTasks (such as requesting to refresh a component of the UI) are forced to run on the JavaFX Application Thread using Platform.runLater().
TimerClassDiagramCallbacks
Figure 13. Class diagram reflecting how the callback-functions are organized in the Timer component.

The three main events that are currently triggered by the Timer component which require a callback are:

  1. Time has elapsed, callback to AppManager to update and display the new timestamp on the UI.

  2. Time has run out (reached zero), callback to AppManager to skip over to next Card.

  3. Time has reached a point where Hints are to be given to the User, callback to AppManager to retrieve a hint and display accordingly on the UI.

The call-backs for each of these events are implemented as nested Functional Interfaces within the GameTimer interface, which is concretized via the GameTimerManager.

3.3.2. Flow of Events

This section describes the sequential flow of events in the life cycle of a GameTimer object.

TimerSequenceDiagram1
Figure 14. Sequence diagram describing the flow of registering and executing callbacks between the different components

The UI component first registers callbacks with the AppManager, who then registers callbacks with the Timer component. Periodically, the Timer will notify the AppManager to perform tasks to notify the UI component. This is to provide better abstraction between the UI and Timer.

A GameTimer instance is created by the AppManager for every Card of a Game. The AppManager provides information regarding the duration in which the Timer should run for, and whether to trigger Hints at the point when a GameTimer instance is created.

3.3.3. Design Considerations

There were a few considerations for designing the Timer this way.

Alternative 1

Alternative 2

Aspect 1:
Where and How to effect changes to the Ui and other components when the Timer triggers an event.

Holding a reference to Ui and other components directly inside GameTimer itself:

Pros:
Straightforward and direct, can perform many different tasks on the dependent components.

Cons:
Poor abstraction and high potential for cyclic dependencies, resulting in high coupling.

Using Functional Interfaces as Call-backs to notify components indirectly.

Pros:
Maintains abstraction and minimal coupling between Timer and other components

Cons:
Relies on developer to register correct call-back methods with the Timer. Different actions need to be implemented as different call-backs separately. Possible overhead in performing few levels of call-backs.

Why did we choose Alternative 2:
To ensure better extendability of our code for future expansion, we felt it was important to maintain as much abstraction between components. This is also to make life easier when there comes a need to debug and resolve problems in the code.

3.4. [Proposed] Data Encryption

{Explain here how the data encryption feature will be implemented}

3.5. Logging

We are using java.util.logging package for logging. The LogsCenter class is used to manage the logging levels and logging destinations.

  • The logging level can be controlled using the logLevel setting in the configuration file (See Section 3.6, “Configuration”)

  • The Logger for a class can be obtained using LogsCenter.getLogger(Class) which will log messages according to the specified logging level

  • Currently log messages are output through: Console and to a .log file.

Logging Levels

  • SEVERE : Critical problem detected which may possibly cause the termination of the application

  • WARNING : Can continue, but with caution

  • INFO : Information showing the noteworthy actions by the App

  • FINE : Details that is not usually noteworthy but may be useful in debugging e.g. print the actual list instead of just its size

3.6. Configuration

Certain properties of the application can be controlled (e.g user prefs file location, logging level) through the configuration file (default: config.json).

4. Documentation

Refer to the guide here.

5. Testing

Refer to the guide here.

6. Dev Ops

Refer to the guide here.

Appendix A: Product Scope

Target user profile:

  • students

  • wants to learn new English words or definitions

  • can type fast

  • enjoys games

  • is reasonably comfortable using CLI apps

Value proposition: gamify learning experiences

Appendix B: User Stories

Priorities: High (must have) - * * *, Medium (nice to have) - * *, Low (unlikely to have) - *

Priority As a …​ I want to …​ So that I can…​

* * *

teacher

add, edit, and delete questions in the word banks

make corrections on what my students are supposed to learn

* * *

teacher

give customised word banks and definitions

can let my students practice specific problems.

* * *

user

list all my word banks

* * *

user

give titles to word banks

recognise them better

* * *

user

delete word banks

free up some memory when I don’t need it anymore

* * *

user

see the content of the word bank

study beforehand/make changes

* * *

young student

trivia questions to be gamified

enjoy the process

* * *

student

create my own question banks

tailor fit to my learning

* * *

computer science student

have a manual of the commands available

refer to them when I am lost

* *

frequent user

easily access my most recently attempted question sets

can quickly resume my revision

* *

studious student

set and complete goals

have something to work towards

* *

student

see my test statistics

track my progress/improvement

* *

student

choose different kinds of time constraints

can simulate exam conditions

* *

student

categorise my question sets

easily look for relevant materials

* *

student

mark question sets as important/urgent

know how to prioritise my revision

* *

module coordinator

export lessons

send to their students

* *

student

share and compare my results with my classmates

know where I stand

* *

student

partition the trivia

attempt questions that I’m comfortable with

* *

weak student

have the option to see hints

won’t get stuck all the time

* *

computer science student

practise typing bash commands into the CLI

strengthen my bash skills

* *

teacher

export statistics

can compare performance across different students

*

computer science student

customize my “terminal”

changing themes/ background/ font size/ font colour, so that I feel comfortable working on it

*

teacher

protect tests with passwords

let my students do them in lessons together when password is released

*

teacher

protect the files

doesn’t get tampered when distributing to students

*

student

have smaller sized files

have more space on my computer

{More to be added}

Appendix C: Use Cases

(For all use cases below, the System is the Dukemon and the Actor is the user, unless specified otherwise)

Use case: Delete person

MSS

  1. User requests to list persons

  2. Dukemon shows a list of persons

  3. User requests to delete a specific person in the list

  4. Dukemon deletes the person

    Use case ends.

Extensions

  • 2a. The list is empty.

    Use case ends.

  • 3a. The given index is invalid.

    • 3a1. Dukemon shows an error message.

      Use case resumes at step 2.

{More to be added}

Appendix D: Non Functional Requirements

  1. Should work on any mainstream OS as long as it has Java 11 or above installed.

  2. A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse.

  3. Users can export and import their word banks or statistics.

{More to be added}

Appendix E: Glossary

Mainstream OS

Windows, Linux, Unix, OS-X

Private contact detail

A contact detail that is not meant to be shared with others

Word Bank

A list of word-description pair that either the user can create himself or import from.

Appendix F: Product Survey

Product Name

Author: …​

Pros:

  • …​

  • …​

Cons:

  • …​

  • …​

Appendix G: Instructions for Manual Testing

Given below are instructions to test the app manually.

ℹ️
These instructions only provide a starting point for testers to work on; testers are expected to do more exploratory testing.

G.1. Launch and Shutdown

  1. Initial launch

    1. Download the jar file and copy into an empty folder

    2. Double-click the jar file
      Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum.

  2. Saving window preferences

    1. Resize the window to an optimum size. Move the window to a different location. Close the window.

    2. Re-launch the app by double-clicking the jar file.
      Expected: The most recent window size and location is retained.

{ more test cases …​ }

G.2. Deleting a person

  1. Deleting a person while all persons are listed

    1. Prerequisites: List all persons using the list command. Multiple persons in the list.

    2. Test case: delete 1
      Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated.

    3. Test case: delete 0
      Expected: No person is deleted. Error details shown in the status message. Status bar remains the same.

    4. Other incorrect delete commands to try: delete, delete x (where x is larger than the list size) {give more}
      Expected: Similar to previous.

{ more test cases …​ }

G.3. Saving data

  1. Dealing with missing/corrupted data files

    1. {explain how to simulate a missing/corrupted file and the expected behavior}

{ more test cases …​ }