diff --git a/README.md b/README.md deleted file mode 100644 index af0309a9e..000000000 --- a/README.md +++ /dev/null @@ -1,26 +0,0 @@ -# Duke project template - -This is a project template for a greenfield Java project. It's named after the Java mascot _Duke_. Given below are instructions on how to use it. - -## Setting up in Intellij - -Prerequisites: JDK 17, update Intellij to the most recent version. - -1. Open Intellij (if you are not in the welcome screen, click `File` > `Close Project` to close the existing project first) -1. Open the project into Intellij as follows: - 1. Click `Open`. - 1. Select the project directory, and click `OK`. - 1. If there are any further prompts, accept the defaults. -1. Configure the project to use **JDK 17** (not other versions) as explained in [here](https://www.jetbrains.com/help/idea/sdk.html#set-up-jdk).
- In the same dialog, set the **Project language level** field to the `SDK default` option. -1. After that, locate the `src/main/java/Duke.java` file, right-click it, and choose `Run Duke.main()` (if the code editor is showing compile errors, try restarting the IDE). If the setup is correct, you should see something like the below as the output: - ``` - Hello from - ____ _ - | _ \ _ _| | _____ - | | | | | | | |/ / _ \ - | |_| | |_| | < __/ - |____/ \__,_|_|\_\___| - ``` - -**Warning:** Keep the `src\main\java` folder as the root folder for Java files (i.e., don't rename those folders or move Java files to another folder outside of this folder path), as this is the default location some tools (e.g., Gradle) expect to find Java files. diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 47b9f984f..000000000 --- a/docs/README.md +++ /dev/null @@ -1,30 +0,0 @@ -# Duke User Guide - -// Update the title above to match the actual product name - -// Product screenshot goes here - -// Product intro goes here - -## Adding deadlines - -// Describe the action and its outcome. - -// Give examples of usage - -Example: `keyword (optional arguments)` - -// A description of the expected outcome goes here - -``` -expected output -``` - -## Feature ABC - -// Feature details - - -## Feature XYZ - -// Feature details \ No newline at end of file diff --git a/.gitignore b/ip/.gitignore similarity index 100% rename from .gitignore rename to ip/.gitignore diff --git a/ip/BuddyBot.jar b/ip/BuddyBot.jar new file mode 100644 index 000000000..c22978f09 Binary files /dev/null and b/ip/BuddyBot.jar differ diff --git a/CONTRIBUTORS.md b/ip/CONTRIBUTORS.md similarity index 100% rename from CONTRIBUTORS.md rename to ip/CONTRIBUTORS.md diff --git a/ip/README.md b/ip/README.md new file mode 100644 index 000000000..018da305d --- /dev/null +++ b/ip/README.md @@ -0,0 +1,88 @@ +# BuddyBot project template + +BuddyBot is a simple task management chatbot that helps you keep track of your to-dos, deadlines, and events. + +## Getting Started + +1. Run BuddyBot using the command: + ``` + java -jar BuddyBot.jar + ``` +If you are using IntelliJ, you can also run it by clicking the Run button. + +2. Use commands to add, view, and manage your tasks + +3. Tasks are automatically saved between sessions + +## Available Commands + +### Adding Tasks + +| Command | Format | Example | Description | +|---------|--------|---------|-------------| +| `todo` | `todo TASK_DESCRIPTION` | `todo read book` | Adds a simple to-do task | +| `deadline` | `deadline TASK_DESCRIPTION /by DEADLINE` | `deadline return book /by June 6th` | Adds a task with a deadline | +| `event` | `event TASK_DESCRIPTION /from START_TIME /to END_TIME` | `event team meeting /from Monday 2pm /to Monday 4pm` | Adds an event with start and end times | + +### Managing Tasks + +| Command | Format | Example | Description | +|---------|--------|---------|-------------| +| `list` | `list` | `list` | Shows all tasks | +| `mark` | `mark TASK_NUMBER` | `mark 3` | Marks a task as done | +| `unmark` | `unmark TASK_NUMBER` | `unmark 2` | Marks a task as not done | +| `delete` | `delete TASK_NUMBER` | `delete 1` | Removes a task | +| `find` | `find KEYWORD` | `find book` | Finds tasks containing the keyword | +| `bye` | `bye` | `bye` | Exits the program | + +## Task Formats + +Tasks are displayed in the following format: + +- **To-Do**: `[T][✓] read book` (✓ means done, ✗ means not done) +- **Deadline**: `[D][✓] return book (by: June 6th)` +- **Event**: `[E][✓] team meeting (from: Monday 2pm to: Monday 4pm)` + +## Examples + +### Adding Different Types of Tasks + +``` +todo finish homework +deadline complete project /by Friday +event doctor appointment /from Tuesday 10am /to Tuesday 11am +``` + +### Managing Your Tasks + +``` +list +mark 2 +unmark 3 +delete 1 +``` + +### Finding Tasks + +``` +find project + ____________________________________________________________ + Here are the matching tasks in your list: + 1.[T][✗] start new project + 2.[D][✓] complete project (by: Friday) +``` + +## Tips + +- Task numbers start from 1 (not 0) +- All commands are case-insensitive (`TODO` works the same as `todo`) +- Your tasks are automatically saved to a file for your next session +- Use specific keywords when searching to find relevant tasks more easily + +## Error Messages + +If you see an error message, check that you're using the correct command format: + +- For deadlines, use `/by` to specify the due date +- For events, use both `/from` and `/to` to specify start and end times +- Task numbers must be valid (can't mark or delete task #5 if you only have 3 tasks) diff --git a/ip/data/Duke.txt b/ip/data/Duke.txt new file mode 100644 index 000000000..7eae87048 --- /dev/null +++ b/ip/data/Duke.txt @@ -0,0 +1 @@ +0 | Finish project diff --git a/ip/docs/README.md b/ip/docs/README.md new file mode 100644 index 000000000..da4f2cc68 --- /dev/null +++ b/ip/docs/README.md @@ -0,0 +1,87 @@ +# BuddyBot User Guide + +Introduction +BuddyBot is an intelligent chatbot designed to help users manage their tasks efficiently. Whether you need to keep track of deadlines, events, or simple to-dos, BuddyBot makes it easy with an intuitive command-based interface. + + +## Adding deadlines + +Description: Adds a task with a deadline. + +Command: /deadline /by + +Example: /deadline Finish CS2103T project /by 2025-02-28 + +Expected Output: Task added: Finish CS2103T project (by: 28 Feb 2025) + +## Adding Events + +Description: Adds an event with a specific date and time. + +Command : /event /at + +Example: /event NUS Hackathon /at 2025-03-05 14:00 + +Expected Output: Event added: NUS Hackathon (at: 5 Mar 2025, 2:00 PM) + +## Adding To-Dos + +Description: Adds a task without a deadline. + +Command : /todo + +Example: /todo Buy groceries + +Expected Output: Task added: Buy groceries + + +## Viewing Tasks + +Description: Displays all stored tasks. + +Command: /list + +Expected Output: +1. [T] Buy groceries +2. [D] Finish CS2103T project (by: 28 Feb 2025) +3. [E] NUS Hackathon (at: 5 Mar 2025, 2:00 PM) + +## Marking Tasks as Done + +Description: Marks a specific task as completed. + +Command: /done + +Example: /done 2 + +Expected Output: Task marked as done: Finish CS2103T project + + +## Deleting Tasks + +Description: Removes a task from the list. + +Command : /delete + +Example: /delete 1 + +Expected Output: Task deleted: Buy groceries + + +## Finding Tasks + +Description: Searches for tasks containing a specific keyword. + +Command: /find + +Example: /find project + +Expected Output: 1. [D] Finish CS2103T project (by: 28 Feb 2025) + +## Exiting the Program + +Description: Exits BuddyBot. + +Command: /bye + +Expected Output: Goodbye! See you soon. diff --git a/ip/src/main/java/BuddyBot.java b/ip/src/main/java/BuddyBot.java new file mode 100644 index 000000000..54f236327 --- /dev/null +++ b/ip/src/main/java/BuddyBot.java @@ -0,0 +1,49 @@ +import java.util.ArrayList; + +/** + * A chatbot program that tracks different types of tasks. + */ + + +public class BuddyBot { + private Storage storage; + private TaskList tasks; + private Ui ui; + private Parser parser; + + + public BuddyBot(String filePath) { + ui = new Ui(); + storage = new Storage(filePath); + try { + tasks = new TaskList(storage.load()); + } catch (BuddyException e) { + ui.showLoadingError(); + tasks = new TaskList(); + } + parser = new Parser(); + } + + public void run() { + ui.showWelcome(); + boolean isExit = false; + + while (!isExit) { + try { + String fullCommand = ui.readCommand(); + ui.showLine(); + Command command = parser.parse(fullCommand); + command.execute(tasks, ui, storage); + isExit = command.isExit(); + } catch (BuddyException e) { + ui.showError(e.getMessage()); + } finally { + ui.showLine(); + } + } + } + + public static void main(String[] args) { + new BuddyBot("data/duke.txt").run(); + } +} diff --git a/ip/src/main/java/BuddyException.java b/ip/src/main/java/BuddyException.java new file mode 100644 index 000000000..d3d277341 --- /dev/null +++ b/ip/src/main/java/BuddyException.java @@ -0,0 +1,9 @@ +/** + * Custom exception for handling specific errors in the application. + */ + +public class BuddyException extends Exception { + public BuddyException(String message) { + super(message); + } +} diff --git a/ip/src/main/java/Command.java b/ip/src/main/java/Command.java new file mode 100644 index 000000000..0e76a3d96 --- /dev/null +++ b/ip/src/main/java/Command.java @@ -0,0 +1,23 @@ +/** + * Abstract class for all commands in Buddybot. + */ +public abstract class Command { + /** + * Executes the command. + * + * @param tasks The task list to operate on. + * @param ui The UI to display messages. + * @param storage The storage to save tasks. + * @throws BuddyException If there's an error executing the command. + */ + public abstract void execute(TaskList tasks, Ui ui, Storage storage) throws BuddyException; + + /** + * Checks if this command is an exit command. + * + * @return True if this is an exit command, false otherwise. + */ + public boolean isExit() { + return false; + } +} diff --git a/ip/src/main/java/Deadline.java b/ip/src/main/java/Deadline.java new file mode 100644 index 000000000..8dfa7b481 --- /dev/null +++ b/ip/src/main/java/Deadline.java @@ -0,0 +1,38 @@ + +public class Deadline extends Task { + protected String by; + + /** + * Constructs a Deadline task. + * + * @param description Description of the task. + * @param by Deadline date/time. + */ + public Deadline(String description, String by) { + super(description); + this.by = by; + } + + /** + * Constructs a Deadline task with a completion status. + * Used when loading from a file. + * + * @param description Description of the task. + * @param by Deadline date/time. + * @param isDone Whether the task is completed. + */ + public Deadline(String description, String by, boolean isDone) { + super(description, isDone); + this.by = by; + } + + @Override + public String toString() { + return "[D]" + super.toString() + " (by: " + by + ")"; + } + + @Override + public String toFileFormat() { + return "D | " + (isDone ? "1" : "0") + " | " + description + " | " + by; + } +} diff --git a/ip/src/main/java/DeadlineCommand.java b/ip/src/main/java/DeadlineCommand.java new file mode 100644 index 000000000..a5efc3fd5 --- /dev/null +++ b/ip/src/main/java/DeadlineCommand.java @@ -0,0 +1,27 @@ +/** + * Command to add a Deadline task. + */ + +public class DeadlineCommand extends Command { + private String description; + private String by; + + /** + * Creates a DeadlineCommand with the specified description and deadline. + * + * @param description Description of the Deadline task. + * @param by Deadline date/time. + */ + public DeadlineCommand(String description, String by) { + this.description = description; + this.by = by; + } + + @Override + public void execute(TaskList tasks, Ui ui, Storage storage) throws BuddyException { + Task task = new Deadline(description, by); + tasks.addTask(task); + ui.showTaskAdded(task, tasks.size()); + storage.save(tasks.getTasks()); + } +} diff --git a/ip/src/main/java/DeleteCommand.java b/ip/src/main/java/DeleteCommand.java new file mode 100644 index 000000000..de6e056a7 --- /dev/null +++ b/ip/src/main/java/DeleteCommand.java @@ -0,0 +1,23 @@ +/** + * Command to delete a task. + */ + +public class DeleteCommand extends Command { + private int taskIndex; + + /** + * Creates a DeleteCommand for the specified task index. + * + * @param taskIndex Index of the task to delete. + */ + public DeleteCommand(int taskIndex) { + this.taskIndex = taskIndex; + } + + @Override + public void execute(TaskList tasks, Ui ui, Storage storage) throws BuddyException { + Task deletedTask = tasks.deleteTask(taskIndex); + ui.showTaskDeleted(deletedTask, tasks.size()); + storage.save(tasks.getTasks()); + } +} diff --git a/ip/src/main/java/Event.java b/ip/src/main/java/Event.java new file mode 100644 index 000000000..1d3be2632 --- /dev/null +++ b/ip/src/main/java/Event.java @@ -0,0 +1,43 @@ + +public class Event extends Task { + protected String startTime; + protected String endTime; + + /** + * Constructs an Event task. + * + * @param description Description of the event. + * @param startTime Event start time. + * @param endTime Event end time. + */ + public Event(String description, String startTime, String endTime) { + super(description); + this.startTime = startTime; + this.endTime = endTime; + } + + /** + * Constructs an Event task with a completion status. + * Used when loading from a file. + * + * @param description Description of the event. + * @param timeInfo Time information string (format: "start/end"). + * @param isDone Whether the task is completed. + */ + public Event(String description, String timeInfo, boolean isDone) { + super(description, isDone); + String[] times = timeInfo.split("/"); + this.startTime = times[0]; + this.endTime = times.length > 1 ? times[1] : ""; + } + + @Override + public String toString() { + return "[E]" + super.toString() + " (from: " + startTime + " to: " + endTime + ")"; + } + + @Override + public String toFileFormat() { + return "E | " + (isDone ? "1" : "0") + " | " + description + " | " + startTime + "/" + endTime; + } +} diff --git a/ip/src/main/java/EventCommand.java b/ip/src/main/java/EventCommand.java new file mode 100644 index 000000000..8d3ed7082 --- /dev/null +++ b/ip/src/main/java/EventCommand.java @@ -0,0 +1,30 @@ +/** + * Command to add an Event task. + */ + +public class EventCommand extends Command { + private String description; + private String startTime; + private String endTime; + + /** + * Creates an EventCommand with the specified description, start time, and end time. + * + * @param description Description of the Event task. + * @param startTime Event start time. + * @param endTime Event end time. + */ + public EventCommand(String description, String startTime, String endTime) { + this.description = description; + this.startTime = startTime; + this.endTime = endTime; + } + + @Override + public void execute(TaskList tasks, Ui ui, Storage storage) throws BuddyException { + Task task = new Event(description, startTime, endTime); + tasks.addTask(task); + ui.showTaskAdded(task, tasks.size()); + storage.save(tasks.getTasks()); + } +} diff --git a/ip/src/main/java/ExitCommand.java b/ip/src/main/java/ExitCommand.java new file mode 100644 index 000000000..81ad2d32b --- /dev/null +++ b/ip/src/main/java/ExitCommand.java @@ -0,0 +1,15 @@ +/** + * Command to exit the application. + */ + +public class ExitCommand extends Command { + @Override + public void execute(TaskList tasks, Ui ui, Storage storage) { + ui.showGoodbye(); + } + + @Override + public boolean isExit() { + return true; + } +} diff --git a/ip/src/main/java/FindCommand.java b/ip/src/main/java/FindCommand.java new file mode 100644 index 000000000..ad8ddc225 --- /dev/null +++ b/ip/src/main/java/FindCommand.java @@ -0,0 +1,20 @@ +/** + * Command to find tasks by keyword. + */ +public class FindCommand extends Command { + private String keyword; + + /** + * Creates a FindCommand with the specified keyword. + * + * @param keyword Keyword to search for in task descriptions. + */ + public FindCommand(String keyword) { + this.keyword = keyword; + } + + @Override + public void execute(TaskList tasks, Ui ui, Storage storage) { + ui.showMatchingTasks(tasks, keyword); + } +} diff --git a/ip/src/main/java/ListCommand.java b/ip/src/main/java/ListCommand.java new file mode 100644 index 000000000..4f84fb21f --- /dev/null +++ b/ip/src/main/java/ListCommand.java @@ -0,0 +1,10 @@ +/** + * Command to list all tasks. + */ + +public class ListCommand extends Command { + @Override + public void execute(TaskList tasks, Ui ui, Storage storage) { + ui.showTasks(tasks); + } +} diff --git a/ip/src/main/java/MarkCommand.java b/ip/src/main/java/MarkCommand.java new file mode 100644 index 000000000..c0dae95d1 --- /dev/null +++ b/ip/src/main/java/MarkCommand.java @@ -0,0 +1,23 @@ +/** + * Command to mark a task as done. + */ + +public class MarkCommand extends Command { + private int taskIndex; + + /** + * Creates a MarkCommand for the specified task index. + * + * @param taskIndex Index of the task to mark. + */ + public MarkCommand(int taskIndex) { + this.taskIndex = taskIndex; + } + + @Override + public void execute(TaskList tasks, Ui ui, Storage storage) throws BuddyException { + Task task = tasks.markTaskDone(taskIndex); + ui.showTaskMarked(task); + storage.save(tasks.getTasks()); + } +} diff --git a/ip/src/main/java/Parser.java b/ip/src/main/java/Parser.java new file mode 100644 index 000000000..3731b0271 --- /dev/null +++ b/ip/src/main/java/Parser.java @@ -0,0 +1,135 @@ +/** + * Parses user input into commands. + */ +public class Parser { + /** + * Parses a user command string into a Command object. + * + * @param input The user input string. + * @return A Command object. + * @throws BuddyException If the command is invalid. + */ + public Command parse(String input) throws BuddyException { + String trimmedInput = input.trim(); + String[] parts = trimmedInput.split("\\s+", 2); + String commandType = parts[0].toLowerCase(); + + switch (commandType) { + case "bye": + return new ExitCommand(); + case "list": + return new ListCommand(); + case "mark": + return parseMarkCommand(parts); + case "unmark": + return parseUnmarkCommand(parts); + case "todo": + return parseToDoCommand(parts); + case "deadline": + return parseDeadlineCommand(parts); + case "event": + return parseEventCommand(parts); + case "delete": + return parseDeleteCommand(parts); + case "find": + return parseFindCommand(parts); + default: + throw new BuddyException("Invalid command! Try 'todo', 'deadline', 'event', 'list', 'mark', 'unmark', 'delete', or 'find'."); + } + } + + private MarkCommand parseMarkCommand(String[] parts) throws BuddyException { + if (parts.length < 2) { + throw new BuddyException("Please enter a task number to mark."); + } + try { + int taskNumber = Integer.parseInt(parts[1]) - 1; + return new MarkCommand(taskNumber); + } catch (NumberFormatException e) { + throw new BuddyException("Please enter a valid task number to mark."); + } + } + + private UnmarkCommand parseUnmarkCommand(String[] parts) throws BuddyException { + if (parts.length < 2) { + throw new BuddyException("Please enter a task number to unmark."); + } + try { + int taskNumber = Integer.parseInt(parts[1]) - 1; + return new UnmarkCommand(taskNumber); + } catch (NumberFormatException e) { + throw new BuddyException("Please enter a valid task number to unmark."); + } + } + + private ToDoCommand parseToDoCommand(String[] parts) throws BuddyException { + if (parts.length < 2 || parts[1].trim().isEmpty()) { + throw new BuddyException("Invalid ToDo format! Use: todo [task description]"); + } + return new ToDoCommand(parts[1].trim()); + } + + private DeadlineCommand parseDeadlineCommand(String[] parts) throws BuddyException { + if (parts.length < 2 || !parts[1].contains("/by")) { + throw new BuddyException("Invalid Deadline format! Use: deadline [task] /by [time]"); + } + + String[] components = parts[1].split(" /by ", 2); + if (components.length < 2 || components[0].trim().isEmpty() || components[1].trim().isEmpty()) { + throw new BuddyException("Invalid Deadline format! Use: deadline [task] /by [time]"); + } + + return new DeadlineCommand(components[0].trim(), components[1].trim()); + } + + private EventCommand parseEventCommand(String[] parts) throws BuddyException { + if (parts.length < 2 || !parts[1].contains("/from") || !parts[1].contains("/to")) { + throw new BuddyException("Invalid Event format! Use: event [task] /from [start] /to [end]"); + } + + try { + // Extract description (everything before "/from") + int fromIndex = parts[1].indexOf("/from"); + String description = parts[1].substring(0, fromIndex).trim(); + + // Extract the part after "/from" + String afterFrom = parts[1].substring(fromIndex + 5).trim(); + + // Extract start time (everything before "/to") + int toIndex = afterFrom.indexOf("/to"); + if (toIndex == -1) { + throw new BuddyException("Invalid Event format! Use: event [task] /from [start] /to [end]"); + } + + String startTime = afterFrom.substring(0, toIndex).trim(); + String endTime = afterFrom.substring(toIndex + 3).trim(); + + if (description.isEmpty() || startTime.isEmpty() || endTime.isEmpty()) { + throw new BuddyException("Invalid Event format! Use: event [task] /from [start] /to [end]"); + } + + return new EventCommand(description, startTime, endTime); + } catch (Exception e) { + throw new BuddyException("Invalid Event format! Use: event [task] /from [start] /to [end]"); + } + } + + private DeleteCommand parseDeleteCommand(String[] parts) throws BuddyException { + if (parts.length < 2) { + throw new BuddyException("Please enter a task number to delete."); + } + try { + int taskNumber = Integer.parseInt(parts[1]) - 1; + return new DeleteCommand(taskNumber); + } catch (NumberFormatException e) { + throw new BuddyException("Please enter a valid task number to delete."); + } + } + + private FindCommand parseFindCommand(String[] parts) throws BuddyException { + if (parts.length < 2 || parts[1].trim().isEmpty()) { + throw new BuddyException("Please enter a keyword to search for tasks."); + } + return new FindCommand(parts[1].trim()); + } +} diff --git a/ip/src/main/java/Storage.java b/ip/src/main/java/Storage.java new file mode 100644 index 000000000..86875a401 --- /dev/null +++ b/ip/src/main/java/Storage.java @@ -0,0 +1,72 @@ +import java.io.*; +import java.util.ArrayList; +import java.util.Scanner; + +/** + * Handles saving and loading tasks from file storage. + */ + +public class Storage { + private String filePath; + + /** + * Creates a Storage object with the specified file path. + * + * @param filePath Path to the storage file. + */ + public Storage(String filePath) { + this.filePath = filePath; + } + + /** + * Loads tasks from the storage file. + * + * @return ArrayList of Task objects. + * @throws BuddyException If there's an error loading the file. + */ + public ArrayList load() throws BuddyException { + ArrayList tasks = new ArrayList<>(); + File file = new File(filePath); + + if (!file.exists()) { + return tasks; + } + + try { + Scanner fileScanner = new Scanner(file); + while (fileScanner.hasNextLine()) { + String line = fileScanner.nextLine(); + Task task = Task.fromFileFormat(line); + if (task != null) { + tasks.add(task); + } + } + fileScanner.close(); + return tasks; + } catch (FileNotFoundException e) { + throw new BuddyException("Error loading tasks from file: " + e.getMessage()); + } + } + + /** + * Saves tasks to the storage file. + * + * @param tasks ArrayList of Task objects to save. + * @throws BuddyException If there's an error saving to the file. + */ + public void save(ArrayList tasks) throws BuddyException { + try { + File file = new File(filePath); + file.getParentFile().mkdirs(); // Ensure the directory exists + + BufferedWriter writer = new BufferedWriter(new FileWriter(filePath)); + for (Task task : tasks) { + writer.write(task.toFileFormat()); + writer.newLine(); + } + writer.close(); + } catch (IOException e) { + throw new BuddyException("Error saving tasks to file: " + e.getMessage()); + } + } +} diff --git a/ip/src/main/java/Task.java b/ip/src/main/java/Task.java new file mode 100644 index 000000000..fe05af818 --- /dev/null +++ b/ip/src/main/java/Task.java @@ -0,0 +1,117 @@ +/** + * Represents a task with a description and completion status. + */ +public class Task { + protected String description; + protected boolean isDone; + + /** + * Constructs a Task with the specified description. + * + * @param description The task description. + */ + public Task(String description) { + this.description = description; + this.isDone = false; + } + + /** + * Constructs a Task with a description and completion status. + * Used when loading from a file. + * + * @param description The task description. + * @param isDone Whether the task is completed. + */ + public Task(String description, boolean isDone) { + this.description = description; + this.isDone = isDone; + } + + /** + * Returns the status icon of the task. + * + * @return "X" if the task is done, otherwise a space. + */ + public String getStatusIcon() { + return isDone ? "X" : " "; + } + + /** + * Marks the task as done. + */ + public void markAsDone() { + isDone = true; + } + + /** + * Marks the task as not done. + */ + public void markAsNotDone() { + isDone = false; + } + /** + * Returns the description of the task. + * + * @return The task description. + */ + public String getDescription() { + return description; + } + /** + * Returns a string representation of the task. + * + * @return A string in the format "[statusIcon] description". + */ + @Override + public String toString() { + return "[" + getStatusIcon() + "] " + description; + } + + /** + * Converts the task to a savable file format. + * @return String representation of the task. + */ + public String toFileFormat() { + return (this instanceof ToDo ? "T" : + (this instanceof Deadline ? "D" : "E")) + " | " + + (isDone ? "1" : "0") + " | " + description; + } + + /** + * Creates a Task object from a formatted file line. + * @param line A line from the save file. + * @return A Task object. + */ + public static Task fromFileFormat(String line) { + String[] parts = line.split(" \\| "); + if (parts.length < 3) { + return null; // Skip invalid lines + } + + String type = parts[0]; + boolean isDone = parts[1].equals("1"); + String description = parts[2]; + + try { + switch (type) { + case "T": + return new ToDo(description, isDone); + case "D": + if (parts.length > 3) { + return new Deadline(description, parts[3], isDone); + } + return null; // Invalid deadline format + case "E": + if (parts.length > 3) { + return new Event(description, parts[3], isDone); + } + return null; // Invalid event format + default: + return null; // Skip unknown task types + } + } catch (Exception e) { + return null; // Skip any tasks that cause errors + } + } + +} diff --git a/ip/src/main/java/TaskList.java b/ip/src/main/java/TaskList.java new file mode 100644 index 000000000..8391a382e --- /dev/null +++ b/ip/src/main/java/TaskList.java @@ -0,0 +1,125 @@ +import java.util.ArrayList; + +/** + * Represents a list of tasks with operations to manage them. + */ + +public class TaskList { + private ArrayList tasks; + + /** + * Creates a new empty task list. + */ + public TaskList() { + tasks = new ArrayList<>(); + } + + /** + * Creates a task list with existing tasks. + * + * @param tasks ArrayList of Task objects. + */ + public TaskList(ArrayList tasks) { + this.tasks = tasks; + } + + /** + * Adds a task to the list. + * + * @param task Task to add. + */ + public void addTask(Task task) { + tasks.add(task); + } + + /** + * Deletes a task from the list. + * + * @param index Index of the task to delete. + * @return The deleted Task. + * @throws BuddyException If the index is invalid. + */ + public Task deleteTask(int index) throws BuddyException { + if (index < 0 || index >= tasks.size()) { + throw new BuddyException("Task " + (index + 1) + " does not exist."); + } + return tasks.remove(index); + } + + /** + * Gets a task at the specified index. + * + * @param index Index of the task to retrieve. + * @return The Task at the specified index. + * @throws BuddyException If the index is invalid. + */ + public Task getTask(int index) throws BuddyException { + if (index < 0 || index >= tasks.size()) { + throw new BuddyException("Task " + (index + 1) + " does not exist."); + } + return tasks.get(index); + } + + /** + * Marks a task as done. + * + * @param index Index of the task to mark. + * @return The marked Task. + * @throws BuddyException If the index is invalid. + */ + public Task markTaskDone(int index) throws BuddyException { + Task task = getTask(index); + task.markAsDone(); + return task; + } + + /** + * Marks a task as not done. + * + * @param index Index of the task to unmark. + * @return The unmarked Task. + * @throws BuddyException If the index is invalid. + */ + public Task markTaskNotDone(int index) throws BuddyException { + Task task = getTask(index); + task.markAsNotDone(); + return task; + } + + /** + * Checks if the task list is empty. + * + * @return True if the list is empty, false otherwise. + */ + public boolean isEmpty() { + return tasks.isEmpty(); + } + + /** + * Gets the number of tasks in the list. + * + * @return The number of tasks. + */ + public int size() { + return tasks.size(); + } + + /** + * Gets a task at the specified index. + * + * @param index Index of the task to retrieve. + * @return The Task at the specified index. + */ + public Task get(int index) { + return tasks.get(index); + } + + /** + * Gets all tasks in the list. + * + * @return ArrayList of Task objects. + */ + public ArrayList getTasks() { + return tasks; + } +} diff --git a/ip/src/main/java/ToDoCommand.java b/ip/src/main/java/ToDoCommand.java new file mode 100644 index 000000000..98c888904 --- /dev/null +++ b/ip/src/main/java/ToDoCommand.java @@ -0,0 +1,24 @@ +/** + * Command to add a ToDo task. + */ + +public class ToDoCommand extends Command { + private String description; + + /** + * Creates a ToDoCommand with the specified description. + * + * @param description Description of the ToDo task. + */ + public ToDoCommand(String description) { + this.description = description; + } + + @Override + public void execute(TaskList tasks, Ui ui, Storage storage) throws BuddyException { + Task task = new ToDo(description); + tasks.addTask(task); + ui.showTaskAdded(task, tasks.size()); + storage.save(tasks.getTasks()); + } +} diff --git a/ip/src/main/java/Todo.java b/ip/src/main/java/Todo.java new file mode 100644 index 000000000..afd0f56e0 --- /dev/null +++ b/ip/src/main/java/Todo.java @@ -0,0 +1,35 @@ +/** + * Represents a ToDo task. + */ +public class ToDo extends Task { + + /** + * Constructs a new ToDo task. + * + * @param description Description of the task. + */ + public ToDo(String description) { + super(description); + } + + /** + * Constructs a new ToDo task with a completion status. + * Used when loading from a file. + * + * @param description Description of the task. + * @param isDone Whether the task is completed. + */ + public ToDo(String description, boolean isDone) { + super(description, isDone); + } + + @Override + public String toString() { + return "[T]" + super.toString(); + } + + @Override + public String toFileFormat() { + return "T | " + (isDone ? "1" : "0") + " | " + description; + } +} diff --git a/ip/src/main/java/Ui.java b/ip/src/main/java/Ui.java new file mode 100644 index 000000000..05fe0f37a --- /dev/null +++ b/ip/src/main/java/Ui.java @@ -0,0 +1,150 @@ +import java.util.Scanner; + +/** + * Handles user interface interactions for BuddyBot chatbot. + */ + +public class Ui { + private static final String DIVIDER = "________________________________________________"; + private Scanner scanner; + + /** + * Creates a new UI object for BuddyBot. + */ + public Ui() { + scanner = new Scanner(System.in); + } + + /** + * Displays welcome message. + */ + public void showWelcome() { + System.out.println("Welcome to BuddyBot!"); + System.out.println("What can I assist you with today?"); + showLine(); + } + + /** + * Displays a horizontal line. + */ + public void showLine() { + System.out.println(DIVIDER); + } + + /** + * Reads user input. + * + * @return The user's command. + */ + public String readCommand() { + return scanner.nextLine().trim(); + } + + /** + * Displays an error message. + * + * @param message The error message to display. + */ + public void showError(String message) { + System.out.println("ERROR: " + message); + } + + /** + * Displays a message about tasks not being loaded properly. + */ + public void showLoadingError() { + System.out.println("Could not load tasks from file. Starting with an empty task list."); + } + + /** + * Displays a goodbye message. + */ + public void showGoodbye() { + System.out.println("Goodbye from BuddyBot!"); + } + + /** + * Displays all tasks in the list. + * + * @param tasks The task list to display. + */ + public void showTasks(TaskList tasks) { + if (tasks.isEmpty()) { + System.out.println("Your task list is empty. Add a task using 'todo', 'deadline', or 'event'."); + } else { + System.out.println("Here are the tasks in your list:"); + for (int i = 0; i < tasks.size(); i++) { + System.out.println((i + 1) + ". " + tasks.get(i)); + } + } + } + + /** + * Displays a message that a task has been marked as done. + * + * @param task The task that was marked. + */ + public void showTaskMarked(Task task) { + System.out.println("Great! I've marked this task as done:\n" + task); + } + + /** + * Displays a message that a task has been unmarked. + * + * @param task The task that was unmarked. + */ + public void showTaskUnmarked(Task task) { + System.out.println("Alright, I've marked this task as not done yet:\n" + task); + } + + /** + * Displays a message that a task has been added. + * + * @param task The task that was added. + * @param size The new size of the task list. + */ + public void showTaskAdded(Task task, int size) { + System.out.println("Got it! I've added this task:\n " + task); + System.out.println("Now you have " + size + " tasks in your list."); + } + + /** + * Displays a message that a task has been deleted. + * + * @param task The task that was deleted. + * @param size The new size of the task list. + */ + public void showTaskDeleted(Task task, int size) { + System.out.println("Noted. I've removed this task:\n " + task); + System.out.println("Now you have " + size + " tasks in your list."); + } + + /** + * Displays tasks that match a keyword. + * + * @param tasks The task list to search through. + * @param keyword The keyword to search for. + */ + public void showMatchingTasks(TaskList tasks, String keyword) { + System.out.println(" ____________________________________________________________"); + System.out.println(" Here are the matching tasks in your list:"); + + boolean hasMatches = false; + int count = 1; + + for (int i = 0; i < tasks.size(); i++) { + Task task = tasks.get(i); + if (task.getDescription().toLowerCase().contains(keyword.toLowerCase())) { + System.out.println(" " + count + "." + task); + count++; + hasMatches = true; + } + } + + if (!hasMatches) { + System.out.println(" No matching tasks found."); + } + + System.out.println(" ____________________________________________________________"); + } +} diff --git a/ip/src/main/java/UnmarkCommand.java b/ip/src/main/java/UnmarkCommand.java new file mode 100644 index 000000000..30daa22d8 --- /dev/null +++ b/ip/src/main/java/UnmarkCommand.java @@ -0,0 +1,23 @@ +/** + * Command to mark a task as not done. + */ + +public class UnmarkCommand extends Command { + private int taskIndex; + + /** + * Creates an UnmarkCommand for the specified task index. + * + * @param taskIndex Index of the task to unmark. + */ + public UnmarkCommand(int taskIndex) { + this.taskIndex = taskIndex; + } + + @Override + public void execute(TaskList tasks, Ui ui, Storage storage) throws BuddyException { + Task task = tasks.markTaskNotDone(taskIndex); + ui.showTaskUnmarked(task); + storage.save(tasks.getTasks()); + } +} diff --git a/text-ui-test/EXPECTED.TXT b/ip/text-ui-test/EXPECTED.TXT similarity index 100% rename from text-ui-test/EXPECTED.TXT rename to ip/text-ui-test/EXPECTED.TXT diff --git a/text-ui-test/input.txt b/ip/text-ui-test/input.txt similarity index 100% rename from text-ui-test/input.txt rename to ip/text-ui-test/input.txt diff --git a/text-ui-test/runtest.bat b/ip/text-ui-test/runtest.bat similarity index 90% rename from text-ui-test/runtest.bat rename to ip/text-ui-test/runtest.bat index 087374464..99f72b1e2 100644 --- a/text-ui-test/runtest.bat +++ b/ip/text-ui-test/runtest.bat @@ -15,7 +15,7 @@ IF ERRORLEVEL 1 ( REM no error here, errorlevel == 0 REM run the program, feed commands from input.txt file and redirect the output to the ACTUAL.TXT -java -classpath ..\bin Duke < input.txt > ACTUAL.TXT +java -classpath ..\bin BuddyBot < input.txt > ACTUAL.TXT REM compare the output to the expected output FC ACTUAL.TXT EXPECTED.TXT diff --git a/text-ui-test/runtest.sh b/ip/text-ui-test/runtest.sh similarity index 100% rename from text-ui-test/runtest.sh rename to ip/text-ui-test/runtest.sh diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java deleted file mode 100644 index 5d313334c..000000000 --- a/src/main/java/Duke.java +++ /dev/null @@ -1,10 +0,0 @@ -public class Duke { - public static void main(String[] args) { - String logo = " ____ _ \n" - + "| _ \\ _ _| | _____ \n" - + "| | | | | | | |/ / _ \\\n" - + "| |_| | |_| | < __/\n" - + "|____/ \\__,_|_|\\_\\___|\n"; - System.out.println("Hello from\n" + logo); - } -} diff --git a/tutorial-05 b/tutorial-05 new file mode 160000 index 000000000..2c7df1733 --- /dev/null +++ b/tutorial-05 @@ -0,0 +1 @@ +Subproject commit 2c7df17336080c3d2360c02fd9554768c1b71ed8