From c4583ef694fe866f75c892ac4aac02db9b15a5bd Mon Sep 17 00:00:00 2001 From: ThomasCorreia <48772223+Midnight-Envy@users.noreply.github.com> Date: Mon, 27 Jul 2026 11:32:03 -0400 Subject: [PATCH 01/18] Add user stories Added user stories and project overview for QuestForge application, detailing features for managing adventurers and quests. --- user-stories.md | 377 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 377 insertions(+) create mode 100644 user-stories.md diff --git a/user-stories.md b/user-stories.md new file mode 100644 index 0000000..67c1dac --- /dev/null +++ b/user-stories.md @@ -0,0 +1,377 @@ +# QuestForge + +## Overview + +QuestForge is a Ruby command-line application designed for users who want to manage an RPG-inspired adventurer and quest log. + +Users can create an adventurer, accept quests, update quest information, complete quests, earn experience points, and remove quests they no longer want to pursue. + +The goal is to create a simple but engaging CLI experience that demonstrates CRUD operations, Active Record associations, database management, validations, and object-oriented Ruby design. + +--- + +# User Stories + +## Feature 1 - Create and Manage Adventurers + +**User Story:** As a user, I want to create and manage an adventurer so that I have a character who can accept quests and earn experience. + +### Details + +* Create a new player +* Enter a player name +* Assign a starting level of 1 +* Assign starting experience of 0 +* Save the player to the database +* View all saved players +* View an individual player's information +* Update a player's name +* Delete a player +* Display validation errors when player information is invalid +* Confirm before deleting a player + +--- + +## Feature 2 - Create and View Quests + +**User Story:** As a user, I want to create quests for an adventurer so that I can give the player objectives to complete. + +### Details + +* Select the player receiving the quest +* Enter a quest title +* Enter a quest description +* Select a quest difficulty +* Assign an experience-point reward +* Save the quest to the database +* Display the newly created quest +* View all quests +* View quests belonging to a specific player +* View active quests +* View completed quests +* Display the player associated with each quest +* Display validation errors when quest information is invalid + +--- + +## Feature 3 - Update Quest Information + +**User Story:** As a user, I want to edit an existing quest so that I can correct mistakes or change the quest details. + +### Details + +* Select a quest to update +* Display the current quest title before requesting a new value +* Display the current description before requesting a new value +* Display the current difficulty before requesting a new value +* Display the current experience reward before requesting a new value +* Allow the user to keep an existing value by leaving the input blank +* Save updated quest information to the database +* Display confirmation after the quest is updated +* Display validation errors when updated information is invalid + +--- + +## Feature 4 - Complete Quests and Earn Experience + +**User Story:** As a user, I want to complete quests and earn experience so that my adventurer can progress. + +### Details + +* Select an active quest +* Mark the quest as completed +* Add the quest's experience reward to the associated player +* Save the updated quest +* Save the player's updated experience +* Display the amount of experience earned +* Prevent a completed quest from awarding experience more than once +* Display confirmation after completing a quest +* Display the player's updated level and experience + +--- + +## Feature 5 - Player Level Progression + +**User Story:** As a user, I want my adventurer to level up after earning enough experience so that completing quests feels rewarding. + +### Details + +* Calculate a player's level using accumulated experience +* Check for a level increase after completing a quest +* Update the player's level when the required experience is reached +* Display a level-up message +* Display the player's current level +* Display the player's current experience +* Keep the level calculation inside the Player model + +--- + +## Feature 6 - Delete and Abandon Quests + +**User Story:** As a user, I want to delete quests so that I can remove objectives I no longer want to pursue. + +### Details + +* Select a quest to delete +* Display the selected quest before deletion +* Ask the user to confirm the deletion +* Delete the quest from the database +* Return to the quest menu after deletion +* Display confirmation after the quest is deleted +* Handle an invalid quest selection without crashing the application + +--- + +## Feature 7 - CLI Navigation and User Feedback + +**User Story:** As a user, I want a clear menu-driven interface so that I can navigate the application and understand the result of each action. + +### Details + +* Display a main menu +* Display numbered menu choices +* Keep the application running inside a loop +* Allow the user to return to the previous menu +* Allow the user to exit the application +* Handle invalid menu choices +* Display clear success messages +* Display clear error messages +* Format player and quest information for readability +* Separate CLI responsibilities into at least two Ruby classes +* Use reusable helper methods to avoid repeated code + +--- + +# MVP Features + +The initial project scope will include: + +* Create players +* View players +* Update players +* Delete players +* Create quests +* View all quests +* View quests belonging to a player +* Update quests +* Complete quests +* Delete quests +* Award experience for completed quests +* Level up players +* Display validation errors +* Menu-driven CLI navigation + +--- + +# CLI Menus + +## Main Menu + +```txt +1. Manage Players +2. Manage Quests +3. View Quest Log +4. Exit +``` + +## Player Menu + +```txt +1. Create Player +2. View All Players +3. View Player Details +4. Update Player +5. Delete Player +6. Return to Main Menu +``` + +## Quest Menu + +```txt +1. Create Quest +2. View All Quests +3. View Active Quests +4. View Completed Quests +5. Update Quest +6. Complete Quest +7. Delete Quest +8. Return to Main Menu +``` + +--- + +# Planned Classes + +* Player +* Quest +* QuestForgeCLI +* PlayerMenu +* QuestMenu + +The exact CLI class structure may be adjusted during development, but the application will contain at least two Ruby classes responsible for CLI behavior. + +--- + +# Database Structure + +## Players + +```txt +players +------- +id +name +level +current_xp +created_at +updated_at +``` + +## Quests + +```txt +quests +------ +id +title +description +difficulty +xp_reward +completed +player_id +created_at +updated_at +``` + +--- + +# Model Relationships + +A Player has many Quests. + +A Quest belongs to one Player. + +```txt +Player + | + | has many + | + v +Quest +``` + +```ruby +class Player < ActiveRecord::Base + has_many :quests, dependent: :destroy +end +``` + +```ruby +class Quest < ActiveRecord::Base + belongs_to :player +end +``` + +--- + +# Validations + +## Player Validations + +* Name must be present +* Name must be unique +* Level must be greater than 0 +* Current experience must be 0 or greater + +## Quest Validations + +* Title must be present +* Difficulty must be present +* Experience reward must be greater than 0 +* Completed must be either true or false +* Player must exist + +--- + +# Technical Challenges + +## Managing Active Record Associations + +Creating quests that correctly belong to a player and displaying the associated records. + +## Updating Existing Records + +Showing the current database value before asking the user to enter an updated value. + +## Experience and Level Progression + +Awarding experience when a quest is completed while preventing the same quest from awarding experience multiple times. + +## User Input Validation + +Handling invalid menu choices, missing records, blank values, and failed Active Record validations without crashing the application. + +## CLI Organization + +Separating menu navigation, user input, and database interactions into organized and reusable Ruby methods and classes. + +--- + +# Meeting Project Requirements + +## Active Record + +All database interactions will use Active Record. + +## Models + +The application will contain at least two Active Record models: + +* Player +* Quest + +## One-to-Many Relationship + +* Player has many Quests +* Quest belongs to Player + +## CRUD Operations + +The application will allow users to create, read, update, and delete player and quest records. + +## Validations + +At least one model will contain Active Record validations. + +## CLI Classes + +The application will contain at least two Ruby classes responsible for menu navigation and CLI behavior. + +## Menu Loop + +The application will use a loop-based menu that continues running until the user chooses to exit. + +## Update Prompts + +Update prompts will display the current value before asking the user to enter a replacement. + +--- + +# Stretch Goals + +These features are not required for the MVP: + +* Search quests by title +* Sort quests by difficulty +* Randomly generate quests +* Add gold rewards +* Add character classes +* Add quest categories +* Add deadlines to quests +* Display player statistics +* Display completion percentages +* Add achievements +* Add an inventory system +* Add items as a third model +* Add multiple experience progression systems +* Add ASCII art and enhanced CLI styling From d42e48869c5b1c2ec1cc13d4744363cc4796e67e Mon Sep 17 00:00:00 2001 From: ThomasCorreia <48772223+Midnight-Envy@users.noreply.github.com> Date: Mon, 27 Jul 2026 12:39:07 -0400 Subject: [PATCH 02/18] Revise user stories for quests and player management --- user-stories.md | 278 ++++++++++++++++++++++++++---------------------- 1 file changed, 153 insertions(+), 125 deletions(-) diff --git a/user-stories.md b/user-stories.md index 67c1dac..dea65e2 100644 --- a/user-stories.md +++ b/user-stories.md @@ -4,7 +4,7 @@ QuestForge is a Ruby command-line application designed for users who want to manage an RPG-inspired adventurer and quest log. -Users can create an adventurer, accept quests, update quest information, complete quests, earn experience points, and remove quests they no longer want to pursue. +Users can create an adventurer, accept quests, update quest information, complete quests, earn experience points, level up, and abandon quests they no longer wish to pursue. The goal is to create a simple but engaging CLI experience that demonstrates CRUD operations, Active Record associations, database management, validations, and object-oriented Ruby design. @@ -18,39 +18,41 @@ The goal is to create a simple but engaging CLI experience that demonstrates CRU ### Details -* Create a new player -* Enter a player name -* Assign a starting level of 1 -* Assign starting experience of 0 -* Save the player to the database -* View all saved players -* View an individual player's information -* Update a player's name -* Delete a player -* Display validation errors when player information is invalid -* Confirm before deleting a player +- Create a new player +- Enter a player name +- Assign a starting level of 1 +- Assign starting experience of 0 +- Save the player to the database +- View all saved players +- View an individual player's information +- Update a player's name +- Delete a player +- Display validation errors when player information is invalid +- Confirm before deleting a player --- -## Feature 2 - Create and View Quests +## Feature 2 - Accept and View Quests -**User Story:** As a user, I want to create quests for an adventurer so that I can give the player objectives to complete. +**User Story:** As a user, I want my adventurer to accept quests so that they can begin working toward objectives and earning experience. ### Details -* Select the player receiving the quest -* Enter a quest title -* Enter a quest description -* Select a quest difficulty -* Assign an experience-point reward -* Save the quest to the database -* Display the newly created quest -* View all quests -* View quests belonging to a specific player -* View active quests -* View completed quests -* Display the player associated with each quest -* Display validation errors when quest information is invalid +- Select the player receiving the quest +- Enter a quest title +- Enter a quest description +- Select a quest difficulty +- Assign an experience-point reward +- Save the quest to the database +- Automatically mark newly accepted quests as active +- Display a "Quest Accepted" confirmation message +- Display the newly accepted quest +- View all quests +- View quests belonging to a specific player +- View active quests +- View completed quests +- Display the player associated with each quest +- Display validation errors when quest information is invalid --- @@ -60,33 +62,35 @@ The goal is to create a simple but engaging CLI experience that demonstrates CRU ### Details -* Select a quest to update -* Display the current quest title before requesting a new value -* Display the current description before requesting a new value -* Display the current difficulty before requesting a new value -* Display the current experience reward before requesting a new value -* Allow the user to keep an existing value by leaving the input blank -* Save updated quest information to the database -* Display confirmation after the quest is updated -* Display validation errors when updated information is invalid +- Select a quest to update +- Display the current quest title before requesting a new value +- Display the current description before requesting a new value +- Display the current difficulty before requesting a new value +- Display the current experience reward before requesting a new value +- Allow the user to keep an existing value by leaving the input blank +- Save updated quest information to the database +- Display confirmation after the quest is updated +- Display validation errors when updated information is invalid --- ## Feature 4 - Complete Quests and Earn Experience -**User Story:** As a user, I want to complete quests and earn experience so that my adventurer can progress. +**User Story:** As a user, I want my adventurer to complete accepted quests so that they earn experience and grow stronger. ### Details -* Select an active quest -* Mark the quest as completed -* Add the quest's experience reward to the associated player -* Save the updated quest -* Save the player's updated experience -* Display the amount of experience earned -* Prevent a completed quest from awarding experience more than once -* Display confirmation after completing a quest -* Display the player's updated level and experience +- Select an active quest +- Confirm quest completion +- Mark the quest as completed +- Add the quest's experience reward to the associated player +- Save the updated quest +- Save the player's updated experience +- Display a "Quest Complete" confirmation message +- Display the amount of experience earned +- Display the player's updated level and experience +- Prevent a completed quest from being completed twice +- Prevent a completed quest from awarding experience more than once --- @@ -96,29 +100,29 @@ The goal is to create a simple but engaging CLI experience that demonstrates CRU ### Details -* Calculate a player's level using accumulated experience -* Check for a level increase after completing a quest -* Update the player's level when the required experience is reached -* Display a level-up message -* Display the player's current level -* Display the player's current experience -* Keep the level calculation inside the Player model +- Calculate a player's level using accumulated experience +- Check for a level increase after completing a quest +- Update the player's level when the required experience is reached +- Display a level-up message +- Display the player's current level +- Display the player's current experience +- Keep the level calculation inside the Player model --- -## Feature 6 - Delete and Abandon Quests +## Feature 6 - Abandon Quests -**User Story:** As a user, I want to delete quests so that I can remove objectives I no longer want to pursue. +**User Story:** As a user, I want to abandon quests so that I can remove objectives I no longer want to pursue. ### Details -* Select a quest to delete -* Display the selected quest before deletion -* Ask the user to confirm the deletion -* Delete the quest from the database -* Return to the quest menu after deletion -* Display confirmation after the quest is deleted -* Handle an invalid quest selection without crashing the application +- Select a quest to abandon +- Display the selected quest before deletion +- Ask the user to confirm the deletion +- Delete the quest from the database +- Return to the quest menu after deletion +- Display confirmation after the quest is abandoned +- Handle an invalid quest selection without crashing the application --- @@ -128,17 +132,42 @@ The goal is to create a simple but engaging CLI experience that demonstrates CRU ### Details -* Display a main menu -* Display numbered menu choices -* Keep the application running inside a loop -* Allow the user to return to the previous menu -* Allow the user to exit the application -* Handle invalid menu choices -* Display clear success messages -* Display clear error messages -* Format player and quest information for readability -* Separate CLI responsibilities into at least two Ruby classes -* Use reusable helper methods to avoid repeated code +- Display a main menu +- Display numbered menu choices +- Keep the application running inside a loop +- Allow the user to return to the previous menu +- Allow the user to exit the application +- Handle invalid menu choices +- Display clear success messages +- Display clear error messages +- Format player and quest information for readability +- Separate CLI responsibilities into at least two Ruby classes +- Use reusable helper methods to avoid repeated code + +--- + +# Quest Lifecycle + +```text +Quest Accepted + │ + ▼ +Active Quest + │ + ▼ +Quest Updated (optional) + │ + ▼ +Quest Completed + │ + ▼ +Experience Awarded + │ + ▼ +Player Levels Up (if enough XP) +``` + +A quest may also be abandoned before it is completed. --- @@ -146,20 +175,21 @@ The goal is to create a simple but engaging CLI experience that demonstrates CRU The initial project scope will include: -* Create players -* View players -* Update players -* Delete players -* Create quests -* View all quests -* View quests belonging to a player -* Update quests -* Complete quests -* Delete quests -* Award experience for completed quests -* Level up players -* Display validation errors -* Menu-driven CLI navigation +- Create players +- View players +- Update players +- Delete players +- Accept quests +- View all quests +- View quests belonging to a player +- Update quests +- Complete quests +- Abandon quests +- Award experience for completed quests +- Prevent completed quests from awarding experience twice +- Level up players +- Display validation errors +- Menu-driven CLI navigation --- @@ -188,13 +218,13 @@ The initial project scope will include: ## Quest Menu ```txt -1. Create Quest +1. Accept Quest 2. View All Quests 3. View Active Quests 4. View Completed Quests 5. Update Quest 6. Complete Quest -7. Delete Quest +7. Abandon Quest 8. Return to Main Menu ``` @@ -202,11 +232,11 @@ The initial project scope will include: # Planned Classes -* Player -* Quest -* QuestForgeCLI -* PlayerMenu -* QuestMenu +- Player +- Quest +- QuestForgeCLI +- PlayerMenu +- QuestMenu The exact CLI class structure may be adjusted during development, but the application will contain at least two Ruby classes responsible for CLI behavior. @@ -251,12 +281,12 @@ A Player has many Quests. A Quest belongs to one Player. -```txt +```text Player | | has many | - v + ▼ Quest ``` @@ -264,9 +294,7 @@ Quest class Player < ActiveRecord::Base has_many :quests, dependent: :destroy end -``` -```ruby class Quest < ActiveRecord::Base belongs_to :player end @@ -278,18 +306,18 @@ end ## Player Validations -* Name must be present -* Name must be unique -* Level must be greater than 0 -* Current experience must be 0 or greater +- Name must be present +- Name must be unique +- Level must be greater than 0 +- Current experience must be 0 or greater ## Quest Validations -* Title must be present -* Difficulty must be present -* Experience reward must be greater than 0 -* Completed must be either true or false -* Player must exist +- Title must be present +- Difficulty must be present +- Experience reward must be greater than 0 +- Completed must be either true or false +- Player must exist --- @@ -327,13 +355,13 @@ All database interactions will use Active Record. The application will contain at least two Active Record models: -* Player -* Quest +- Player +- Quest ## One-to-Many Relationship -* Player has many Quests -* Quest belongs to Player +- Player has many Quests +- Quest belongs to Player ## CRUD Operations @@ -361,17 +389,17 @@ Update prompts will display the current value before asking the user to enter a These features are not required for the MVP: -* Search quests by title -* Sort quests by difficulty -* Randomly generate quests -* Add gold rewards -* Add character classes -* Add quest categories -* Add deadlines to quests -* Display player statistics -* Display completion percentages -* Add achievements -* Add an inventory system -* Add items as a third model -* Add multiple experience progression systems -* Add ASCII art and enhanced CLI styling +- Search quests by title +- Sort quests by difficulty +- Randomly generate quests +- Add gold rewards +- Add character classes +- Add quest categories +- Add deadlines to quests +- Display player statistics +- Display completion percentages +- Add achievements +- Add an inventory system +- Add items as a third model +- Add multiple experience progression systems +- Add ASCII art and enhanced CLI styling From 938e937c3eb8c148c5fc4b3a312b4ea87a1fbbb6 Mon Sep 17 00:00:00 2001 From: ThomasCorreia <48772223+Midnight-Envy@users.noreply.github.com> Date: Mon, 27 Jul 2026 13:33:30 -0400 Subject: [PATCH 03/18] Revised Readme Updated project name and enhanced project details in README. --- README.md | 321 +++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 223 insertions(+), 98 deletions(-) diff --git a/README.md b/README.md index d6d0893..b63f043 100644 --- a/README.md +++ b/README.md @@ -1,165 +1,290 @@ -# Phase 3 Project — Single-Script Active Record CLI +# QuestCLI -## Learning Goals +QuestCLI is a Ruby command-line application built with **Active Record** and **SQLite3** that allows players to create an adventurer, accept quests, complete objectives, earn experience, and level up. -- Build a database-backed Ruby application using Active Record -- Design and interact with data models using object-oriented Ruby -- Implement a multi-class CLI frontend -- Practice working with migrations, associations, and validations +Designed as an RPG-inspired quest manager, QuestCLI demonstrates object-oriented programming principles, database design, Active Record associations, validations, and full CRUD functionality through an interactive command-line interface. -## Introduction +--- -Congrats on getting through all the material for Phase 3! You've learned how to work with databases, design models with Active Record, and write object-oriented Ruby. Now it's time to bring those skills together into a full project. +# Features -This project will focus on building a Ruby command-line application that reads from and writes to a local SQLite3 database using Active Record — no web server required. +## Adventurer Management -By the end of the project, you'll have a functioning CLI that lets users interact with your data by creating, viewing, updating, and deleting records from the terminal. +- Create adventurers +- View all adventurers +- View individual adventurer details +- Update adventurer information +- Delete adventurers -## Requirements +## Quest Management -### Models +- Accept new quests +- View all quests +- View active quests +- View completed quests +- Update quest information +- Complete quests +- Abandon quests -- At least two model classes using `ActiveRecord::Base` -- A one-to-many relationship (`has_many` / `belongs_to`) -- At least one model with validations -- Display associated data where appropriate (e.g. listing a parent record's associated children) +## Progression System -### CLI +- Earn experience by completing quests +- Automatic level progression +- Prevent duplicate quest rewards +- Track active and completed quests -- At least two Ruby classes (e.g. a `Menu` class and a model-specific helper) -- A loop or menu interface -- Ability to create, view, update, and delete records -- Update prompts should display the current value before asking for a new one +--- -## Planning +# Domain Model -- Plan out your features -- Develop user stories - - "As [ a user ], I want [ to perform this action ] so that [ I can accomplish this goal ]." - - Features should not need you there to explain them to users - - Create a `user-stories.md` file and add your user stories there +QuestCLI contains two Active Record models with a one-to-many relationship. -## Project Pitches +- A **Player** has many **Quests** +- A **Quest** belongs to one **Player** -Before you start working on your project, you'll pitch your project idea to your instructors for approval and feedback. +```text +Player +------ +id +name +level +current_xp -For your project pitch, you should include: + 1 + │ + │ has many + ▼ -- The basic story of your application -- The core features of your MVP -- The data you plan to persist and how you will structure it -- Challenges you expect to face -- How you are meeting the requirements of the project +Quest +------ +id +title +description +difficulty +xp_reward +completed +player_id +``` -**MVP ASAP** — Focus on getting your minimum viable product working first! +--- -## Example Project Domains +# Technologies -You could build a **Book Tracker** app: +- Ruby +- Active Record +- SQLite3 +- Rake -- `Author` has many `Books` -- Users can: - - Create a new book - - List all books - - Update book details - - Delete a book - - View books by a specific author +--- -Or a **Workout Log**: +# Installation -- `WorkoutSession` has many `Exercises` -- Users can: - - Log a new workout - - Add exercises - - Update reps/weights - - View or delete past workouts +Clone the repository. -## Getting Started +```bash +git clone +``` -**Fork and clone** this repository to get started. +Navigate into the project. -Install dependencies: +```bash +cd questcli +``` + +Install dependencies. ```bash bundle install ``` -Create and migrate the database: +Create the database. ```bash bundle exec rake db:create +``` + +Run the migrations. + +```bash bundle exec rake db:migrate ``` -Optionally seed the database with starter data: +(Optional) Seed the database. ```bash bundle exec rake seed ``` -Run your CLI application: +Start the application. ```bash ruby cli/main.rb ``` -## Other Useful Commands +--- -Open a Pry console with your models loaded: +# Example Gameplay -```bash -bundle exec rake console +```text +======================== + QUESTCLI +======================== + +1. Manage Players +2. Manage Quests +3. View Quest Log +4. Exit ``` -Generate a new migration: +Accepting a quest: -```bash -bundle exec rake db:create_migration NAME=create_books -``` +```text +⚔ Quest Accepted! -## Project Structure +Quest: +Recover the Ancient Relic +Difficulty: +Medium + +Reward: +250 XP + +Status: +Active ``` -├── app/ -│ └── models/ # Your Active Record model classes go here -├── cli/ -│ └── main.rb # Entry point — your CLI menu lives here -├── config/ -│ └── environment.rb # Loads gems, DB connection, and models -├── db/ -│ ├── config.yml # Database connection settings -│ ├── migrate/ # Migration files -│ └── seeds.rb # Seed data -└── spec/ # RSpec tests (optional) + +Completing a quest: + +```text +🏆 Quest Complete! + ++250 XP + +Current XP: 950 + +LEVEL UP! + +You are now Level 4. ``` -## Project Tips +--- -- Sketch your domain model first using [dbdiagram.io](https://dbdiagram.io/) -- Use `bundle exec rake console` to test your models before building the CLI -- Use `binding.pry` for debugging -- Use `puts` and `pp` or gems like `tty-table` for formatted CLI output +# CRUD Functionality -## Sample Project +## Players -A complete implementation is available on the `sample-project` branch. It demonstrates: +### Create -- **Pet Tracker** domain with Owners and Pets -- Full CRUD with Active Record and a clean menu-driven CLI -- Object-oriented design with user-friendly output -- All required features including current value prompts for updates +Create a new adventurer. -To view the sample: +### Read -```bash -git checkout sample-project +View all adventurers or inspect an individual adventurer. + +### Update + +Modify an adventurer's information. + +### Delete + +Delete an adventurer and their associated quests. + +--- + +## Quests + +### Create + +Accept a new quest. + +### Read + +View all quests, active quests, completed quests, or quests belonging to a specific player. + +### Update + +Modify quest details. + +### Delete + +Abandon a quest. + +--- + +# Validations + +## Player + +- Name is required +- Name must be unique +- Level must be greater than zero +- Current experience cannot be negative + +## Quest + +- Title is required +- Difficulty is required +- Experience reward must be greater than zero +- Every quest must belong to a player + +--- + +# Project Structure + +```text +. +├── app +│ └── models +│ ├── player.rb +│ └── quest.rb +├── cli +│ └── main.rb +├── config +│ ├── database.yml +│ └── environment.rb +├── db +│ ├── migrate +│ └── seeds.rb +└── README.md ``` -See `SAMPLE_PROJECT_README.md` for detailed documentation. +--- + +# Future Improvements + +Future versions of QuestCLI may include: + +- Character classes +- Gold and economy system +- Inventory management +- Equipment +- NPC quest givers +- Random quest generation +- Quest categories +- Achievements +- Player statistics dashboard +- Multiple save files +- ASCII art and enhanced terminal styling + +--- + +# Learning Objectives + +This project demonstrates: + +- Object-Oriented Programming +- Active Record Associations +- Active Record Validations +- Database Migrations +- SQLite3 +- CRUD Operations +- Menu-Driven CLI Design +- Ruby Classes and Modules +- Separation of Concerns + +--- -## Resources +# Author -- [dbdiagram.io](https://dbdiagram.io/) -- [Active Record Basics](https://guides.rubyonrails.org/active_record_basics.html) -- [Active Record Associations](https://guides.rubyonrails.org/association_basics.html) -- [Active Record Validations](https://guides.rubyonrails.org/active_record_validations.html) +Thomas Correia From a1b6a14f9898ef94678224f84c39862b53aeaffe Mon Sep 17 00:00:00 2001 From: ThomasCorreia <48772223+Midnight-Envy@users.noreply.github.com> Date: Mon, 27 Jul 2026 13:36:01 -0400 Subject: [PATCH 04/18] Rename QuestForge to QuestCLI in documentation --- user-stories.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/user-stories.md b/user-stories.md index dea65e2..43ae638 100644 --- a/user-stories.md +++ b/user-stories.md @@ -1,8 +1,8 @@ -# QuestForge +# QuestCLI ## Overview -QuestForge is a Ruby command-line application designed for users who want to manage an RPG-inspired adventurer and quest log. +QuestCLI is a Ruby command-line application designed for users who want to manage an RPG-inspired adventurer and quest log. Users can create an adventurer, accept quests, update quest information, complete quests, earn experience points, level up, and abandon quests they no longer wish to pursue. @@ -234,7 +234,7 @@ The initial project scope will include: - Player - Quest -- QuestForgeCLI +- QuestCLI - PlayerMenu - QuestMenu From 2b959885b9935e2d23464fd46f38fd23a376e40a Mon Sep 17 00:00:00 2001 From: Thomas Correia Date: Mon, 27 Jul 2026 15:59:11 -0400 Subject: [PATCH 05/18] Add adventurer management feature --- Rakefile | 6 +- app/models/player.rb | 15 ++ cli/main.rb | 17 +- cli/player_menu.rb | 173 ++++++++++++++++++++ cli/quest_cli.rb | 60 +++++++ config/environment.rb | 10 +- db/migrate/20260727192309_create_players.rb | 11 ++ db/schema.rb | 21 +++ 8 files changed, 293 insertions(+), 20 deletions(-) create mode 100644 app/models/player.rb create mode 100644 cli/player_menu.rb create mode 100644 cli/quest_cli.rb create mode 100644 db/migrate/20260727192309_create_players.rb create mode 100644 db/schema.rb diff --git a/Rakefile b/Rakefile index 0b7ef85..18ec232 100644 --- a/Rakefile +++ b/Rakefile @@ -4,11 +4,11 @@ require "standalone_migrations" StandaloneMigrations::Tasks.load_tasks desc "Start the console" -task :console => :environment do +task console: :environment do Pry.start end desc "Seed the database" -task :seed => :environment do +task seed: :environment do load "db/seeds.rb" -end \ No newline at end of file +end diff --git a/app/models/player.rb b/app/models/player.rb new file mode 100644 index 0000000..2eac1fd --- /dev/null +++ b/app/models/player.rb @@ -0,0 +1,15 @@ +class Player < ActiveRecord::Base + validates :name, presence: true, uniqueness: true + + validates :level, + numericality: { + only_integer: true, + greater_than: 0, + } + + validates :current_xp, + numericality: { + only_integer: true, + greater_than_or_equal_to: 0, + } +end diff --git a/cli/main.rb b/cli/main.rb index 33e117f..43bbb86 100755 --- a/cli/main.rb +++ b/cli/main.rb @@ -2,19 +2,4 @@ require_relative "../config/environment" -# TODO: Build your CLI application here! -# -# Requirements: -# - Be object-oriented (at least two classes) -# - Query and update the database directly via Active Record models -# - Parse and display records in a readable format -# - Accept user input and use it to create, update, and delete records -# - Use a loop or menu interface -# - Include current value prompts for updates (e.g. show the current value -# before asking the user what they want to change it to) - -puts "Welcome to your CLI Application!" -puts - -# TODO: Implement your CLI functionality here -puts "TODO: Build your CLI application" +QuestCLI.new.run diff --git a/cli/player_menu.rb b/cli/player_menu.rb new file mode 100644 index 0000000..1d08541 --- /dev/null +++ b/cli/player_menu.rb @@ -0,0 +1,173 @@ +class PlayerMenu + MENU_ACTIONS = { + "1" => :create_player, + "2" => :view_all_players, + "3" => :view_player_details, + "4" => :update_player, + "5" => :delete_player, + }.freeze + + def run + loop do + display_menu + choice = gets.chomp + + break if return_to_main_menu?(choice) + + perform_action(choice) + end + end + + private + + def display_menu + puts "\n========================" + puts " ADVENTURER MENU" + puts "========================" + puts "1. Create Adventurer" + puts "2. View All Adventurers" + puts "3. View Adventurer Details" + puts "4. Update Adventurer" + puts "5. Delete Adventurer" + puts "6. Return to Main Menu" + print "\nChoose an option: " + end + + def return_to_main_menu?(choice) + return false unless choice == "6" + + puts "\nReturning to main menu..." + true + end + + def perform_action(choice) + action = MENU_ACTIONS[choice] + + if action + send(action) + else + puts "\nInvalid choice. Please select an option from 1 to 6." + end + end + + def create_player + print "\nEnter adventurer name: " + name = gets.chomp + + player = Player.new(name: name) + + if player.save + puts "\nAdventurer created successfully!" + display_player(player) + else + display_errors(player) + end + end + + def view_all_players + players = Player.all + + if players.empty? + puts "\nNo adventurers found." + return + end + + puts "\n========================" + puts " ADVENTURERS" + puts "========================" + + players.each do |player| + display_player(player) + end + end + + def view_player_details + player = select_player + + return unless player + + puts "\n========================" + puts " ADVENTURER DETAILS" + puts "========================" + + display_player(player) + end + + def update_player + player = select_player + + return unless player + + puts "\nCurrent name: #{player.name}" + print "Enter a new name, or press Enter to keep the current name: " + + new_name = gets.chomp + new_name = player.name if new_name.empty? + + if player.update(name: new_name) + puts "\nAdventurer updated successfully!" + display_player(player) + else + display_errors(player) + end + end + + def delete_player + player = select_player + + return unless player + + puts "\nYou selected:" + display_player(player) + + print "\nAre you sure you want to delete this adventurer? (y/n): " + confirmation = gets.chomp.downcase + + if confirmation == "y" + player.destroy + puts "\nAdventurer deleted successfully." + else + puts "\nDeletion canceled." + end + end + + def select_player + players = Player.all + + if players.empty? + puts "\nNo adventurers found." + return nil + end + + puts "\nSelect an adventurer:" + + players.each do |player| + puts "#{player.id}. #{player.name}" + end + + print "\nEnter adventurer ID: " + player_id = gets.chomp + + player = Player.find_by(id: player_id) + + puts "\nAdventurer not found." unless player + + player + end + + def display_player(player) + puts "\nID: #{player.id}" + puts "Name: #{player.name}" + puts "Level: #{player.level}" + puts "Current XP: #{player.current_xp}" + puts "------------------------" + end + + def display_errors(record) + puts "\nUnable to save adventurer:" + + record.errors.full_messages.each do |message| + puts "- #{message}" + end + end +end diff --git a/cli/quest_cli.rb b/cli/quest_cli.rb new file mode 100644 index 0000000..135dbf1 --- /dev/null +++ b/cli/quest_cli.rb @@ -0,0 +1,60 @@ +class QuestCLI + MENU_ACTIONS = { + "1" => :manage_adventurers, + "2" => :manage_quests, + "3" => :view_quest_log, + }.freeze + + def run + loop do + display_main_menu + choice = gets.chomp + + break if exit_selected?(choice) + + perform_action(choice) + end + end + + private + + def display_main_menu + puts "\n========================" + puts " QUESTCLI" + puts "========================" + puts "1. Manage Adventurers" + puts "2. Manage Quests" + puts "3. View Quest Log" + puts "4. Exit" + print "\nChoose an option: " + end + + def exit_selected?(choice) + return false unless choice == "4" + + puts "\nThanks for using QuestCLI!" + true + end + + def perform_action(choice) + action = MENU_ACTIONS[choice] + + if action + send(action) + else + puts "\nInvalid choice. Please select an option from 1 to 4." + end + end + + def manage_adventurers + PlayerMenu.new.run + end + + def manage_quests + puts "\nQuest management will be added in a future feature." + end + + def view_quest_log + puts "\nQuest log will be added in a future feature." + end +end diff --git a/config/environment.rb b/config/environment.rb index f7f8fa8..95ac6d0 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -9,8 +9,16 @@ # Set up database connection directly via Active Record require "yaml" + db_config = YAML.load_file(File.join(__dir__, "database.yml")) -ActiveRecord::Base.establish_connection(db_config[ENV.fetch("RACK_ENV", "development")]) + +ActiveRecord::Base.establish_connection( + db_config[ENV.fetch("RACK_ENV", "development")] +) # Require in all model files require_all "app/models" + +# Require reusable CLI classes +require_relative "../cli/player_menu" +require_relative "../cli/quest_cli" diff --git a/db/migrate/20260727192309_create_players.rb b/db/migrate/20260727192309_create_players.rb new file mode 100644 index 0000000..5a36f3c --- /dev/null +++ b/db/migrate/20260727192309_create_players.rb @@ -0,0 +1,11 @@ +class CreatePlayers < ActiveRecord::Migration[8.0] + def change + create_table :players do |t| + t.string :name, null: false + t.integer :level, null: false, default: 1 + t.integer :current_xp, null: false, default: 0 + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb new file mode 100644 index 0000000..49cb158 --- /dev/null +++ b/db/schema.rb @@ -0,0 +1,21 @@ +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# This file is the source Rails uses to define your schema when running `bin/rails +# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to +# be faster and is potentially less error prone than running all of your +# migrations from scratch. Old migrations may fail to apply correctly if those +# migrations use external dependencies or application code. +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema[7.2].define(version: 2026_07_27_192309) do + create_table "players", force: :cascade do |t| + t.string "name", null: false + t.integer "level", default: 1, null: false + t.integer "current_xp", default: 0, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end +end From dc85e6d4f6cf4f839af5d7ba4ef1e1e09d439800 Mon Sep 17 00:00:00 2001 From: Thomas Correia Date: Tue, 28 Jul 2026 10:48:43 -0400 Subject: [PATCH 06/18] Feature 2 Complete --- app/models/player.rb | 2 + app/models/quest.rb | 13 ++ cli/quest_cli.rb | 2 +- cli/quest_menu.rb | 142 ++++++++++++++++++++ config/environment.rb | 1 + db/migrate/20260727192309_create_players.rb | 2 +- db/migrate/20260728140853_create_quests.rb | 14 ++ db/schema.rb | 16 ++- 8 files changed, 189 insertions(+), 3 deletions(-) create mode 100644 app/models/quest.rb create mode 100644 cli/quest_menu.rb create mode 100644 db/migrate/20260728140853_create_quests.rb diff --git a/app/models/player.rb b/app/models/player.rb index 2eac1fd..538a474 100644 --- a/app/models/player.rb +++ b/app/models/player.rb @@ -1,4 +1,6 @@ class Player < ActiveRecord::Base + has_many :quests, dependent: :destroy + validates :name, presence: true, uniqueness: true validates :level, diff --git a/app/models/quest.rb b/app/models/quest.rb new file mode 100644 index 0000000..4d341ea --- /dev/null +++ b/app/models/quest.rb @@ -0,0 +1,13 @@ +class Quest < ActiveRecord::Base + DIFFICULTIES = %w[Easy Medium Hard].freeze + + belongs_to :player + + validates :title, presence: true + validates :difficulty, presence: true, inclusion: { in: DIFFICULTIES } + validates :xp_reward, + numericality: { + only_integer: true, + greater_than: 0, + } +end diff --git a/cli/quest_cli.rb b/cli/quest_cli.rb index 135dbf1..3a8ade6 100644 --- a/cli/quest_cli.rb +++ b/cli/quest_cli.rb @@ -51,7 +51,7 @@ def manage_adventurers end def manage_quests - puts "\nQuest management will be added in a future feature." + QuestMenu.new.run end def view_quest_log diff --git a/cli/quest_menu.rb b/cli/quest_menu.rb new file mode 100644 index 0000000..b919c2c --- /dev/null +++ b/cli/quest_menu.rb @@ -0,0 +1,142 @@ +class QuestMenu + MENU_ACTIONS = { + "1" => :accept_quest, + "2" => :view_all_quests, + "3" => :view_player_quests, + "4" => :view_active_quests, + "5" => :view_completed_quests, + }.freeze + + def run + loop do + display_menu + choice = gets.chomp + + break if return_to_main_menu?(choice) + + perform_action(choice) + end + end + + private + + def display_menu + puts "\nQUEST MENU" + puts "1. Accept Quest" + puts "2. View All Quests" + puts "3. View Adventurer's Quests" + puts "4. View Active Quests" + puts "5. View Completed Quests" + puts "6. Return to Main Menu" + print "Choose an option: " + end + + def return_to_main_menu?(choice) + choice == "6" + end + + def perform_action(choice) + action = MENU_ACTIONS[choice] + + if action + send(action) + else + puts "Invalid selection." + end + end + + def accept_quest + player = select_player + return unless player + + quest = player.quests.build(quest_attributes) + + if quest.save + puts "#{quest.title} accepted by #{player.name}." + else + display_errors(quest) + end + end + + def quest_attributes + print "Quest title: " + title = gets.chomp + + print "Description: " + description = gets.chomp + + print "Difficulty (Easy, Medium, or Hard): " + difficulty = gets.chomp.capitalize + + print "XP reward: " + xp_reward = gets.chomp + + { + title: title, + description: description, + difficulty: difficulty, + xp_reward: xp_reward, + } + end + + def view_all_quests + display_quests(Quest.all) + end + + def view_player_quests + player = select_player + return unless player + + display_quests(player.quests) + end + + def view_active_quests + display_quests(Quest.where(completed: false)) + end + + def view_completed_quests + display_quests(Quest.where(completed: true)) + end + + def select_player + players = Player.all + + if players.empty? + puts "No adventurers found." + return + end + + players.each do |player| + puts "#{player.id}. #{player.name}" + end + + print "Enter adventurer ID: " + Player.find_by(id: gets.chomp) + end + + def display_quests(quests) + if quests.empty? + puts "No quests found." + return + end + + quests.each { |quest| display_quest(quest) } + end + + def display_quest(quest) + status = quest.completed? ? "Completed" : "Active" + + puts "\n#{quest.title}" + puts "Adventurer: #{quest.player.name}" + puts "Description: #{quest.description}" + puts "Difficulty: #{quest.difficulty}" + puts "XP Reward: #{quest.xp_reward}" + puts "Status: #{status}" + end + + def display_errors(record) + record.errors.full_messages.each do |message| + puts "Error: #{message}" + end + end +end diff --git a/config/environment.rb b/config/environment.rb index 95ac6d0..81bf943 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -21,4 +21,5 @@ # Require reusable CLI classes require_relative "../cli/player_menu" +require_relative "../cli/quest_menu" require_relative "../cli/quest_cli" diff --git a/db/migrate/20260727192309_create_players.rb b/db/migrate/20260727192309_create_players.rb index 5a36f3c..c0c506e 100644 --- a/db/migrate/20260727192309_create_players.rb +++ b/db/migrate/20260727192309_create_players.rb @@ -1,4 +1,4 @@ -class CreatePlayers < ActiveRecord::Migration[8.0] +class CreatePlayers < ActiveRecord::Migration[7.2] def change create_table :players do |t| t.string :name, null: false diff --git a/db/migrate/20260728140853_create_quests.rb b/db/migrate/20260728140853_create_quests.rb new file mode 100644 index 0000000..8d7b04a --- /dev/null +++ b/db/migrate/20260728140853_create_quests.rb @@ -0,0 +1,14 @@ +class CreateQuests < ActiveRecord::Migration[7.2] + def change + create_table :quests do |t| + t.string :title, null: false + t.text :description + t.string :difficulty, null: false + t.integer :xp_reward, null: false + t.boolean :completed, null: false, default: false + t.references :player, null: false, foreign_key: true + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 49cb158..e37bd52 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.2].define(version: 2026_07_27_192309) do +ActiveRecord::Schema[7.2].define(version: 2026_07_28_140853) do create_table "players", force: :cascade do |t| t.string "name", null: false t.integer "level", default: 1, null: false @@ -18,4 +18,18 @@ t.datetime "created_at", null: false t.datetime "updated_at", null: false end + + create_table "quests", force: :cascade do |t| + t.string "title", null: false + t.text "description" + t.string "difficulty", null: false + t.integer "xp_reward", null: false + t.boolean "completed", default: false, null: false + t.integer "player_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["player_id"], name: "index_quests_on_player_id" + end + + add_foreign_key "quests", "players" end From 19bc62f89f97e890348a56c6063f3a93b4e9ae77 Mon Sep 17 00:00:00 2001 From: Thomas Correia Date: Tue, 28 Jul 2026 11:45:48 -0400 Subject: [PATCH 07/18] Feature 3 Complete --- cli/quest_menu.rb | 67 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 63 insertions(+), 4 deletions(-) diff --git a/cli/quest_menu.rb b/cli/quest_menu.rb index b919c2c..1e94bfb 100644 --- a/cli/quest_menu.rb +++ b/cli/quest_menu.rb @@ -5,6 +5,7 @@ class QuestMenu "3" => :view_player_quests, "4" => :view_active_quests, "5" => :view_completed_quests, + "6" => :update_quest, }.freeze def run @@ -27,12 +28,13 @@ def display_menu puts "3. View Adventurer's Quests" puts "4. View Active Quests" puts "5. View Completed Quests" - puts "6. Return to Main Menu" + puts "6. Update Quest" + puts "7. Return to Main Menu" print "Choose an option: " end def return_to_main_menu?(choice) - choice == "6" + choice == "7" end def perform_action(choice) @@ -52,7 +54,8 @@ def accept_quest quest = player.quests.build(quest_attributes) if quest.save - puts "#{quest.title} accepted by #{player.name}." + puts "Quest Accepted!" + display_quest(quest) else display_errors(quest) end @@ -98,6 +101,40 @@ def view_completed_quests display_quests(Quest.where(completed: true)) end + def update_quest + quest = select_quest + return unless quest + + if quest.update(updated_quest_attributes(quest)) + puts "Quest updated successfully." + display_quest(quest) + else + display_errors(quest) + end + end + + def updated_quest_attributes(quest) + { + title: updated_value("Title", quest.title), + description: updated_value("Description", quest.description), + difficulty: updated_difficulty(quest), + xp_reward: updated_value("XP reward", quest.xp_reward), + } + end + + def updated_difficulty(quest) + value = updated_value("Difficulty", quest.difficulty) + + value == quest.difficulty ? value : value.capitalize + end + + def updated_value(label, current_value) + print "#{label} [#{current_value}]: " + input = gets.chomp + + input.empty? ? current_value : input + end + def select_player players = Player.all @@ -111,7 +148,29 @@ def select_player end print "Enter adventurer ID: " - Player.find_by(id: gets.chomp) + player = Player.find_by(id: gets.chomp) + + puts "Adventurer not found." unless player + player + end + + def select_quest + quests = Quest.all + + if quests.empty? + puts "No quests found." + return + end + + quests.each do |quest| + puts "#{quest.id}. #{quest.title}" + end + + print "Enter quest ID: " + quest = Quest.find_by(id: gets.chomp) + + puts "Quest not found." unless quest + quest end def display_quests(quests) From 44a13339317ab956210c9f763279e6e036eaa3f7 Mon Sep 17 00:00:00 2001 From: Thomas Correia Date: Tue, 28 Jul 2026 13:41:50 -0400 Subject: [PATCH 08/18] Feature 4 Completed --- cli/quest_menu.rb | 68 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 63 insertions(+), 5 deletions(-) diff --git a/cli/quest_menu.rb b/cli/quest_menu.rb index 1e94bfb..d2fc590 100644 --- a/cli/quest_menu.rb +++ b/cli/quest_menu.rb @@ -6,6 +6,7 @@ class QuestMenu "4" => :view_active_quests, "5" => :view_completed_quests, "6" => :update_quest, + "7" => :complete_quest, }.freeze def run @@ -29,12 +30,13 @@ def display_menu puts "4. View Active Quests" puts "5. View Completed Quests" puts "6. Update Quest" - puts "7. Return to Main Menu" + puts "7. Complete Quest" + puts "8. Return to Main Menu" print "Choose an option: " end def return_to_main_menu?(choice) - choice == "7" + choice == "8" end def perform_action(choice) @@ -135,6 +137,41 @@ def updated_value(label, current_value) input.empty? ? current_value : input end + def complete_quest + quest = select_active_quest + return unless quest + + display_quest(quest) + return unless confirm_completion? + + complete_quest_and_award_xp(quest) + end + + def confirm_completion? + print "Complete this quest? (y/n): " + gets.chomp.downcase == "y" + end + + def complete_quest_and_award_xp(quest) + player = quest.player + + Quest.transaction do + quest.update!(completed: true) + player.update!(current_xp: player.current_xp + quest.xp_reward) + end + + display_completion_message(quest, player) + rescue ActiveRecord::RecordInvalid => e + display_errors(e.record) + end + + def display_completion_message(quest, player) + puts "\nQuest Complete!" + puts "#{player.name} earned #{quest.xp_reward} XP." + puts "Level: #{player.level}" + puts "Current XP: #{player.current_xp}" + end + def select_player players = Player.all @@ -162,9 +199,7 @@ def select_quest return end - quests.each do |quest| - puts "#{quest.id}. #{quest.title}" - end + display_quest_choices(quests) print "Enter quest ID: " quest = Quest.find_by(id: gets.chomp) @@ -173,6 +208,29 @@ def select_quest quest end + def select_active_quest + active_quests = Quest.where(completed: false) + + if active_quests.empty? + puts "No active quests found." + return + end + + display_quest_choices(active_quests) + + print "Enter active quest ID: " + quest = active_quests.find_by(id: gets.chomp) + + puts "Active quest not found." unless quest + quest + end + + def display_quest_choices(quests) + quests.each do |quest| + puts "#{quest.id}. #{quest.title}" + end + end + def display_quests(quests) if quests.empty? puts "No quests found." From 5d3ab52b39cad8b5eb7601615ef7e795741be7f0 Mon Sep 17 00:00:00 2001 From: Thomas Correia Date: Tue, 28 Jul 2026 14:11:26 -0400 Subject: [PATCH 09/18] Feature 5 Complete --- app/models/player.rb | 30 +++++++++++ cli/quest_display.rb | 65 ++++++++++++++++++++++++ cli/quest_menu.rb | 118 +++++++++++-------------------------------- 3 files changed, 125 insertions(+), 88 deletions(-) create mode 100644 cli/quest_display.rb diff --git a/app/models/player.rb b/app/models/player.rb index 538a474..a807b48 100644 --- a/app/models/player.rb +++ b/app/models/player.rb @@ -1,4 +1,6 @@ class Player < ActiveRecord::Base + BASE_XP_REQUIREMENT = 100 + has_many :quests, dependent: :destroy validates :name, presence: true, uniqueness: true @@ -14,4 +16,32 @@ class Player < ActiveRecord::Base only_integer: true, greater_than_or_equal_to: 0, } + + def add_experience!(amount) + new_xp = current_xp + amount + new_level = calculate_level(new_xp) + + update!( + current_xp: new_xp, + level: new_level + ) + end + + private + + def calculate_level(experience) + calculated_level = 1 + remaining_xp = experience + + while remaining_xp >= xp_required_for_next_level(calculated_level) + remaining_xp -= xp_required_for_next_level(calculated_level) + calculated_level += 1 + end + + calculated_level + end + + def xp_required_for_next_level(current_level) + current_level * BASE_XP_REQUIREMENT + end end diff --git a/cli/quest_display.rb b/cli/quest_display.rb new file mode 100644 index 0000000..59ca220 --- /dev/null +++ b/cli/quest_display.rb @@ -0,0 +1,65 @@ +class QuestDisplay + def menu + puts "\nQUEST MENU" + puts "1. Accept Quest" + puts "2. View All Quests" + puts "3. View Adventurer's Quests" + puts "4. View Active Quests" + puts "5. View Completed Quests" + puts "6. Update Quest" + puts "7. Complete Quest" + puts "8. Return to Main Menu" + print "Choose an option: " + end + + def quests(quests) + if quests.empty? + puts "No quests found." + return + end + + quests.each { |quest| quest_details(quest) } + end + + def quest_choices(quests) + quests.each do |quest| + puts "#{quest.id}. #{quest.title}" + end + end + + def quest_details(quest) + status = quest.completed? ? "Completed" : "Active" + + puts "\n#{quest.title}" + puts "Adventurer: #{quest.player.name}" + puts "Description: #{quest.description}" + puts "Difficulty: #{quest.difficulty}" + puts "XP Reward: #{quest.xp_reward}" + puts "Status: #{status}" + end + + def completion(quest, player, previous_level) + puts "\nQuest Complete!" + puts "#{player.name} earned #{quest.xp_reward} XP." + + level_up(player, previous_level) + + puts "Level: #{player.level}" + puts "Current XP: #{player.current_xp}" + end + + def errors(record) + record.errors.full_messages.each do |message| + puts "Error: #{message}" + end + end + + private + + def level_up(player, previous_level) + return unless player.level > previous_level + + puts "#{player.name} leveled up!" + puts "Level #{previous_level} -> Level #{player.level}" + end +end diff --git a/cli/quest_menu.rb b/cli/quest_menu.rb index d2fc590..9876cd7 100644 --- a/cli/quest_menu.rb +++ b/cli/quest_menu.rb @@ -1,3 +1,5 @@ +require_relative "quest_display" + class QuestMenu MENU_ACTIONS = { "1" => :accept_quest, @@ -9,12 +11,16 @@ class QuestMenu "7" => :complete_quest, }.freeze + def initialize + @display = QuestDisplay.new + end + def run loop do - display_menu + @display.menu choice = gets.chomp - break if return_to_main_menu?(choice) + break if choice == "8" perform_action(choice) end @@ -22,23 +28,6 @@ def run private - def display_menu - puts "\nQUEST MENU" - puts "1. Accept Quest" - puts "2. View All Quests" - puts "3. View Adventurer's Quests" - puts "4. View Active Quests" - puts "5. View Completed Quests" - puts "6. Update Quest" - puts "7. Complete Quest" - puts "8. Return to Main Menu" - print "Choose an option: " - end - - def return_to_main_menu?(choice) - choice == "8" - end - def perform_action(choice) action = MENU_ACTIONS[choice] @@ -57,9 +46,9 @@ def accept_quest if quest.save puts "Quest Accepted!" - display_quest(quest) + @display.quest_details(quest) else - display_errors(quest) + @display.errors(quest) end end @@ -85,22 +74,22 @@ def quest_attributes end def view_all_quests - display_quests(Quest.all) + @display.quests(Quest.all) end def view_player_quests player = select_player return unless player - display_quests(player.quests) + @display.quests(player.quests) end def view_active_quests - display_quests(Quest.where(completed: false)) + @display.quests(Quest.where(completed: false)) end def view_completed_quests - display_quests(Quest.where(completed: true)) + @display.quests(Quest.where(completed: true)) end def update_quest @@ -109,9 +98,9 @@ def update_quest if quest.update(updated_quest_attributes(quest)) puts "Quest updated successfully." - display_quest(quest) + @display.quest_details(quest) else - display_errors(quest) + @display.errors(quest) end end @@ -141,7 +130,7 @@ def complete_quest quest = select_active_quest return unless quest - display_quest(quest) + @display.quest_details(quest) return unless confirm_completion? complete_quest_and_award_xp(quest) @@ -154,22 +143,16 @@ def confirm_completion? def complete_quest_and_award_xp(quest) player = quest.player + previous_level = player.level Quest.transaction do quest.update!(completed: true) - player.update!(current_xp: player.current_xp + quest.xp_reward) + player.add_experience!(quest.xp_reward) end - display_completion_message(quest, player) + @display.completion(quest, player, previous_level) rescue ActiveRecord::RecordInvalid => e - display_errors(e.record) - end - - def display_completion_message(quest, player) - puts "\nQuest Complete!" - puts "#{player.name} earned #{quest.xp_reward} XP." - puts "Level: #{player.level}" - puts "Current XP: #{player.current_xp}" + @display.errors(e.record) end def select_player @@ -193,67 +176,26 @@ def select_player def select_quest quests = Quest.all - - if quests.empty? - puts "No quests found." - return - end - - display_quest_choices(quests) - - print "Enter quest ID: " - quest = Quest.find_by(id: gets.chomp) - - puts "Quest not found." unless quest - quest + select_quest_from(quests, "quest") end def select_active_quest active_quests = Quest.where(completed: false) - - if active_quests.empty? - puts "No active quests found." - return - end - - display_quest_choices(active_quests) - - print "Enter active quest ID: " - quest = active_quests.find_by(id: gets.chomp) - - puts "Active quest not found." unless quest - quest + select_quest_from(active_quests, "active quest") end - def display_quest_choices(quests) - quests.each do |quest| - puts "#{quest.id}. #{quest.title}" - end - end - - def display_quests(quests) + def select_quest_from(quests, quest_type) if quests.empty? - puts "No quests found." + puts "No #{quest_type}s found." return end - quests.each { |quest| display_quest(quest) } - end - - def display_quest(quest) - status = quest.completed? ? "Completed" : "Active" + @display.quest_choices(quests) - puts "\n#{quest.title}" - puts "Adventurer: #{quest.player.name}" - puts "Description: #{quest.description}" - puts "Difficulty: #{quest.difficulty}" - puts "XP Reward: #{quest.xp_reward}" - puts "Status: #{status}" - end + print "Enter #{quest_type} ID: " + quest = quests.find_by(id: gets.chomp) - def display_errors(record) - record.errors.full_messages.each do |message| - puts "Error: #{message}" - end + puts "#{quest_type.capitalize} not found." unless quest + quest end end From 896833d2f3d00f390161e85e805fa6177985919a Mon Sep 17 00:00:00 2001 From: Thomas Correia Date: Tue, 28 Jul 2026 15:03:45 -0400 Subject: [PATCH 10/18] Feature 6 Completed --- cli/quest_display.rb | 9 ++++++++- cli/quest_menu.rb | 39 ++++++++++++++++++++++++++++----------- 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/cli/quest_display.rb b/cli/quest_display.rb index 59ca220..ebb92a2 100644 --- a/cli/quest_display.rb +++ b/cli/quest_display.rb @@ -8,7 +8,8 @@ def menu puts "5. View Completed Quests" puts "6. Update Quest" puts "7. Complete Quest" - puts "8. Return to Main Menu" + puts "8. Abandon Quest" + puts "9. Return to Main Menu" print "Choose an option: " end @@ -48,6 +49,12 @@ def completion(quest, player, previous_level) puts "Current XP: #{player.current_xp}" end + def abandonment(quest) + puts "\nQuest Abandoned." + puts "#{quest.title} has been removed." + puts "No XP was awarded." + end + def errors(record) record.errors.full_messages.each do |message| puts "Error: #{message}" diff --git a/cli/quest_menu.rb b/cli/quest_menu.rb index 9876cd7..3a26f24 100644 --- a/cli/quest_menu.rb +++ b/cli/quest_menu.rb @@ -9,6 +9,7 @@ class QuestMenu "5" => :view_completed_quests, "6" => :update_quest, "7" => :complete_quest, + "8" => :abandon_quest, }.freeze def initialize @@ -20,7 +21,7 @@ def run @display.menu choice = gets.chomp - break if choice == "8" + break if choice == "9" perform_action(choice) end @@ -131,16 +132,11 @@ def complete_quest return unless quest @display.quest_details(quest) - return unless confirm_completion? + return unless confirm_action?("Complete this quest?") complete_quest_and_award_xp(quest) end - def confirm_completion? - print "Complete this quest? (y/n): " - gets.chomp.downcase == "y" - end - def complete_quest_and_award_xp(quest) player = quest.player previous_level = player.level @@ -155,6 +151,29 @@ def complete_quest_and_award_xp(quest) @display.errors(e.record) end + def abandon_quest + quest = select_active_quest + return unless quest + + @display.quest_details(quest) + return unless confirm_action?("Abandon this quest?") + + destroy_quest(quest) + end + + def destroy_quest(quest) + if quest.destroy + @display.abandonment(quest) + else + @display.errors(quest) + end + end + + def confirm_action?(message) + print "#{message} (y/n): " + gets.chomp.downcase == "y" + end + def select_player players = Player.all @@ -175,13 +194,11 @@ def select_player end def select_quest - quests = Quest.all - select_quest_from(quests, "quest") + select_quest_from(Quest.all, "quest") end def select_active_quest - active_quests = Quest.where(completed: false) - select_quest_from(active_quests, "active quest") + select_quest_from(Quest.where(completed: false), "active quest") end def select_quest_from(quests, quest_type) From 2e823502f3d8852767d0e5918e8a5e87ee6944ef Mon Sep 17 00:00:00 2001 From: Thomas Correia Date: Wed, 29 Jul 2026 09:00:52 -0400 Subject: [PATCH 11/18] Feature 7 Complete --- Gemfile | 2 ++ Gemfile.lock | 2 ++ cli/player_menu.rb | 78 +++++++++++++++++++++++++------------------- cli/quest_cli.rb | 20 ++++++++++-- cli/quest_display.rb | 73 ++++++++++++++++++++++++++++++++++------- cli/quest_menu.rb | 54 ++++++++++++++++++++---------- 6 files changed, 164 insertions(+), 65 deletions(-) diff --git a/Gemfile b/Gemfile index e682c9b..0f5a327 100644 --- a/Gemfile +++ b/Gemfile @@ -32,3 +32,5 @@ group :test do gem "database_cleaner", "~> 2.0" gem "rspec", "~> 3.10" end + +gem "ostruct" diff --git a/Gemfile.lock b/Gemfile.lock index bbe6a3d..15eac80 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -91,6 +91,7 @@ GEM racc (~> 1.4) nokogiri (1.19.4-x86_64-linux-gnu) racc (~> 1.4) + ostruct (0.6.3) parallel (2.1.0) parser (3.3.11.1) ast (~> 2.4.1) @@ -206,6 +207,7 @@ PLATFORMS DEPENDENCIES activerecord (~> 7.1) database_cleaner (~> 2.0) + ostruct pry (~> 0.14.1) rake (~> 13.0) require_all (~> 3.0) diff --git a/cli/player_menu.rb b/cli/player_menu.rb index 1d08541..bb4261c 100644 --- a/cli/player_menu.rb +++ b/cli/player_menu.rb @@ -22,7 +22,7 @@ def run def display_menu puts "\n========================" - puts " ADVENTURER MENU" + puts " ADVENTURER MENU" puts "========================" puts "1. Create Adventurer" puts "2. View All Adventurers" @@ -36,7 +36,7 @@ def display_menu def return_to_main_menu?(choice) return false unless choice == "6" - puts "\nReturning to main menu..." + puts "\nReturning to the main menu..." true end @@ -52,9 +52,7 @@ def perform_action(choice) def create_player print "\nEnter adventurer name: " - name = gets.chomp - - player = Player.new(name: name) + player = Player.new(name: gets.chomp) if player.save puts "\nAdventurer created successfully!" @@ -68,39 +66,28 @@ def view_all_players players = Player.all if players.empty? - puts "\nNo adventurers found." + puts "\nNo adventurers have been created yet." return end - puts "\n========================" - puts " ADVENTURERS" - puts "========================" - - players.each do |player| - display_player(player) - end + display_heading("ADVENTURERS") + players.each { |player| display_player(player) } end def view_player_details player = select_player - return unless player - puts "\n========================" - puts " ADVENTURER DETAILS" - puts "========================" - + display_heading("ADVENTURER DETAILS") display_player(player) + display_quest_summary(player) end def update_player player = select_player - return unless player - puts "\nCurrent name: #{player.name}" - print "Enter a new name, or press Enter to keep the current name: " - + print "\nName [#{player.name}]: " new_name = gets.chomp new_name = player.name if new_name.empty? @@ -114,29 +101,40 @@ def update_player def delete_player player = select_player - return unless player puts "\nYou selected:" display_player(player) - print "\nAre you sure you want to delete this adventurer? (y/n): " - confirmation = gets.chomp.downcase + unless confirm_action?( + "Delete this adventurer and all of their quests?" + ) + puts "\nDeletion canceled." + return + end + + destroy_player(player) + end - if confirmation == "y" - player.destroy + def destroy_player(player) + if player.destroy puts "\nAdventurer deleted successfully." else - puts "\nDeletion canceled." + display_errors(player) end end + def confirm_action?(message) + print "\n#{message} (y/n): " + gets.chomp.downcase == "y" + end + def select_player players = Player.all if players.empty? - puts "\nNo adventurers found." - return nil + puts "\nNo adventurers have been created yet." + return end puts "\nSelect an adventurer:" @@ -146,15 +144,18 @@ def select_player end print "\nEnter adventurer ID: " - player_id = gets.chomp - - player = Player.find_by(id: player_id) + player = players.find_by(id: gets.chomp) puts "\nAdventurer not found." unless player - player end + def display_heading(title) + puts "\n========================" + puts " #{title}" + puts "========================" + end + def display_player(player) puts "\nID: #{player.id}" puts "Name: #{player.name}" @@ -163,6 +164,15 @@ def display_player(player) puts "------------------------" end + def display_quest_summary(player) + active_count = player.quests.where(completed: false).count + completed_count = player.quests.where(completed: true).count + + puts "Active Quests: #{active_count}" + puts "Completed Quests: #{completed_count}" + puts "------------------------" + end + def display_errors(record) puts "\nUnable to save adventurer:" diff --git a/cli/quest_cli.rb b/cli/quest_cli.rb index 3a8ade6..3174930 100644 --- a/cli/quest_cli.rb +++ b/cli/quest_cli.rb @@ -6,6 +6,8 @@ class QuestCLI }.freeze def run + display_welcome + loop do display_main_menu choice = gets.chomp @@ -18,9 +20,16 @@ def run private + def display_welcome + puts "\n========================" + puts " WELCOME TO QUESTCLI" + puts "========================" + puts "Create adventurers, accept quests, and earn experience!" + end + def display_main_menu puts "\n========================" - puts " QUESTCLI" + puts " MAIN MENU" puts "========================" puts "1. Manage Adventurers" puts "2. Manage Quests" @@ -32,7 +41,10 @@ def display_main_menu def exit_selected?(choice) return false unless choice == "4" - puts "\nThanks for using QuestCLI!" + puts "\n========================" + puts " THANKS FOR PLAYING" + puts "========================" + puts "Your adventures have been saved." true end @@ -55,6 +67,8 @@ def manage_quests end def view_quest_log - puts "\nQuest log will be added in a future feature." + players = Player.includes(:quests) + + QuestDisplay.new.quest_log(players) end end diff --git a/cli/quest_display.rb b/cli/quest_display.rb index ebb92a2..20a8e6e 100644 --- a/cli/quest_display.rb +++ b/cli/quest_display.rb @@ -1,6 +1,8 @@ class QuestDisplay def menu - puts "\nQUEST MENU" + puts "\n========================" + puts " QUEST MENU" + puts "========================" puts "1. Accept Quest" puts "2. View All Quests" puts "3. View Adventurer's Quests" @@ -10,12 +12,12 @@ def menu puts "7. Complete Quest" puts "8. Abandon Quest" puts "9. Return to Main Menu" - print "Choose an option: " + print "\nChoose an option: " end def quests(quests) if quests.empty? - puts "No quests found." + puts "\nNo quests found." return end @@ -23,50 +25,99 @@ def quests(quests) end def quest_choices(quests) + puts + quests.each do |quest| - puts "#{quest.id}. #{quest.title}" + puts "#{quest.id}. #{quest.title} - #{quest.player.name}" end end def quest_details(quest) status = quest.completed? ? "Completed" : "Active" - puts "\n#{quest.title}" + puts "\n------------------------" + puts "Quest: #{quest.title}" puts "Adventurer: #{quest.player.name}" puts "Description: #{quest.description}" puts "Difficulty: #{quest.difficulty}" puts "XP Reward: #{quest.xp_reward}" puts "Status: #{status}" + puts "------------------------" end def completion(quest, player, previous_level) - puts "\nQuest Complete!" + puts "\n========================" + puts " QUEST COMPLETE!" + puts "========================" puts "#{player.name} earned #{quest.xp_reward} XP." level_up(player, previous_level) - puts "Level: #{player.level}" - puts "Current XP: #{player.current_xp}" + puts "Current Level: #{player.level}" + puts "Total XP: #{player.current_xp}" end def abandonment(quest) - puts "\nQuest Abandoned." + puts "\nQuest abandoned successfully." puts "#{quest.title} has been removed." puts "No XP was awarded." end + def quest_log(players) + if players.empty? + puts "\nNo adventurers have been created yet." + return + end + + puts "\n========================" + puts " QUEST LOG" + puts "========================" + + players.each { |player| player_quest_log(player) } + end + def errors(record) + puts "\nUnable to save quest:" + record.errors.full_messages.each do |message| - puts "Error: #{message}" + puts "- #{message}" end end private + def player_quest_log(player) + quests = player.quests + completed_quests, active_quests = quests.partition(&:completed?) + + puts "\n#{player.name}" + puts "Level: #{player.level} | XP: #{player.current_xp}" + puts "Active: #{active_quests.count}" + puts "Completed: #{completed_quests.count}" + + display_log_section("ACTIVE QUESTS", active_quests) + display_log_section("COMPLETED QUESTS", completed_quests) + + puts "========================" + end + + def display_log_section(title, quests) + puts "\n#{title}" + + if quests.empty? + puts "None" + return + end + + quests.each do |quest| + puts "- #{quest.title} | #{quest.difficulty} | #{quest.xp_reward} XP" + end + end + def level_up(player, previous_level) return unless player.level > previous_level - puts "#{player.name} leveled up!" + puts "\n#{player.name} leveled up!" puts "Level #{previous_level} -> Level #{player.level}" end end diff --git a/cli/quest_menu.rb b/cli/quest_menu.rb index 3a26f24..9329a3f 100644 --- a/cli/quest_menu.rb +++ b/cli/quest_menu.rb @@ -21,7 +21,7 @@ def run @display.menu choice = gets.chomp - break if choice == "9" + break if return_to_main_menu?(choice) perform_action(choice) end @@ -29,13 +29,20 @@ def run private + def return_to_main_menu?(choice) + return false unless choice == "9" + + puts "\nReturning to the main menu..." + true + end + def perform_action(choice) action = MENU_ACTIONS[choice] if action send(action) else - puts "Invalid selection." + puts "\nInvalid choice. Please select an option from 1 to 9." end end @@ -46,7 +53,7 @@ def accept_quest quest = player.quests.build(quest_attributes) if quest.save - puts "Quest Accepted!" + puts "\nQuest accepted successfully!" @display.quest_details(quest) else @display.errors(quest) @@ -54,7 +61,7 @@ def accept_quest end def quest_attributes - print "Quest title: " + print "\nQuest title: " title = gets.chomp print "Description: " @@ -98,7 +105,7 @@ def update_quest return unless quest if quest.update(updated_quest_attributes(quest)) - puts "Quest updated successfully." + puts "\nQuest updated successfully!" @display.quest_details(quest) else @display.errors(quest) @@ -121,7 +128,7 @@ def updated_difficulty(quest) end def updated_value(label, current_value) - print "#{label} [#{current_value}]: " + print "\n#{label} [#{current_value}]: " input = gets.chomp input.empty? ? current_value : input @@ -132,7 +139,11 @@ def complete_quest return unless quest @display.quest_details(quest) - return unless confirm_action?("Complete this quest?") + + unless confirm_action?("Complete this quest?") + puts "\nQuest completion canceled." + return + end complete_quest_and_award_xp(quest) end @@ -156,7 +167,11 @@ def abandon_quest return unless quest @display.quest_details(quest) - return unless confirm_action?("Abandon this quest?") + + unless confirm_action?("Abandon this quest?") + puts "\nQuest abandonment canceled." + return + end destroy_quest(quest) end @@ -170,7 +185,7 @@ def destroy_quest(quest) end def confirm_action?(message) - print "#{message} (y/n): " + print "\n#{message} (y/n): " gets.chomp.downcase == "y" end @@ -178,18 +193,20 @@ def select_player players = Player.all if players.empty? - puts "No adventurers found." + puts "\nNo adventurers have been created yet." return end + puts "\nSelect an adventurer:" + players.each do |player| puts "#{player.id}. #{player.name}" end - print "Enter adventurer ID: " - player = Player.find_by(id: gets.chomp) + print "\nEnter adventurer ID: " + player = players.find_by(id: gets.chomp) - puts "Adventurer not found." unless player + puts "\nAdventurer not found." unless player player end @@ -198,21 +215,24 @@ def select_quest end def select_active_quest - select_quest_from(Quest.where(completed: false), "active quest") + select_quest_from( + Quest.where(completed: false), + "active quest" + ) end def select_quest_from(quests, quest_type) if quests.empty? - puts "No #{quest_type}s found." + puts "\nNo #{quest_type}s found." return end @display.quest_choices(quests) - print "Enter #{quest_type} ID: " + print "\nEnter #{quest_type} ID: " quest = quests.find_by(id: gets.chomp) - puts "#{quest_type.capitalize} not found." unless quest + puts "\n#{quest_type.capitalize} not found." unless quest quest end end From 77e826e4008a691b3b26b62b8e55f62caad6f9cd Mon Sep 17 00:00:00 2001 From: ThomasCorreia <48772223+Midnight-Envy@users.noreply.github.com> Date: Wed, 29 Jul 2026 11:24:52 -0400 Subject: [PATCH 12/18] Add user story for color-coded CLI feedback Added user story for color-coded CLI feedback to enhance player experience. --- user-stories.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/user-stories.md b/user-stories.md index 43ae638..02716af 100644 --- a/user-stories.md +++ b/user-stories.md @@ -146,6 +146,25 @@ The goal is to create a simple but engaging CLI experience that demonstrates CRU --- +## Feature 8 - Color-Coded CLI Feedback + +**User Story:** As a player, I want important information in the CLI to use consistent colors so that I can quickly understand quest difficulty, status, rewards, errors, and successful actions. + +### Details + +- Menu headings and decorative separators remain white. +- Successful actions and completed quests display in green. +- Errors and invalid selections display in red. +- Prompts, warnings, confirmations, and active quests display in yellow. +- XP rewards and level-up information display in magenta. +- Easy difficulty displays in green. +- Medium difficulty displays in yellow. +- Hard difficulty displays in red. +- Color formatting is managed through a reusable `Colors` module. +- Existing player and quest functionality continues to work. + +--- + # Quest Lifecycle ```text From b3dc7b8e359c1c2a2a844db4136dc4bcceacf0e7 Mon Sep 17 00:00:00 2001 From: Thomas Correia Date: Wed, 29 Jul 2026 11:30:37 -0400 Subject: [PATCH 13/18] Completed Feature 8 --- cli/colors.rb | 103 +++++++++++++++++++++++++++++++++++++++++++ cli/player_menu.rb | 56 ++++++++++++----------- cli/quest_cli.rb | 25 ++++++----- cli/quest_display.rb | 79 +++++++++++++++++++-------------- cli/quest_menu.rb | 28 ++++++------ 5 files changed, 208 insertions(+), 83 deletions(-) create mode 100644 cli/colors.rb diff --git a/cli/colors.rb b/cli/colors.rb new file mode 100644 index 0000000..c1b552f --- /dev/null +++ b/cli/colors.rb @@ -0,0 +1,103 @@ +module Colors + RESET = "\e[0m".freeze + BOLD = "\e[1m".freeze + + RED = "\e[31m".freeze + GREEN = "\e[32m".freeze + YELLOW = "\e[33m".freeze + BLUE = "\e[34m".freeze + MAGENTA = "\e[35m".freeze + CYAN = "\e[36m".freeze + + BRIGHT_GREEN = "\e[92m".freeze + BRIGHT_YELLOW = "\e[93m".freeze + BRIGHT_MAGENTA = "\e[95m".freeze + BRIGHT_CYAN = "\e[96m".freeze + + module_function + + def red(text) + colorize(text, RED) + end + + def green(text) + colorize(text, GREEN) + end + + def yellow(text) + colorize(text, YELLOW) + end + + def blue(text) + colorize(text, BLUE) + end + + def magenta(text) + colorize(text, MAGENTA) + end + + def cyan(text) + colorize(text, CYAN) + end + + def bright_green(text) + colorize(text, BRIGHT_GREEN) + end + + def bright_yellow(text) + colorize(text, BRIGHT_YELLOW) + end + + def bright_magenta(text) + colorize(text, BRIGHT_MAGENTA) + end + + def bright_cyan(text) + colorize(text, BRIGHT_CYAN) + end + + def bold(text) + colorize(text, BOLD) + end + + def heading(text) + bold(text) + end + + def success(text) + bright_green(text) + end + + def warning(text) + bright_yellow(text) + end + + def error(text) + red(text) + end + + def xp(text) + bright_magenta(text) + end + + def difficulty(value) + case value + when "Easy" + green(value) + when "Medium" + yellow(value) + when "Hard" + red(value) + else + value + end + end + + def status(completed) + completed ? bright_green("Completed") : yellow("Active") + end + + def colorize(text, color) + "#{color}#{text}#{RESET}" + end +end diff --git a/cli/player_menu.rb b/cli/player_menu.rb index bb4261c..4e9791b 100644 --- a/cli/player_menu.rb +++ b/cli/player_menu.rb @@ -1,3 +1,5 @@ +require_relative "colors" + class PlayerMenu MENU_ACTIONS = { "1" => :create_player, @@ -21,8 +23,9 @@ def run private def display_menu - puts "\n========================" - puts " ADVENTURER MENU" + puts + puts "========================" + puts Colors.heading(" ADVENTURER MENU") puts "========================" puts "1. Create Adventurer" puts "2. View All Adventurers" @@ -30,13 +33,13 @@ def display_menu puts "4. Update Adventurer" puts "5. Delete Adventurer" puts "6. Return to Main Menu" - print "\nChoose an option: " + print "\n#{Colors.yellow('Choose an option: ')}" end def return_to_main_menu?(choice) return false unless choice == "6" - puts "\nReturning to the main menu..." + puts "\n#{Colors.warning('Returning to the main menu...')}" true end @@ -46,7 +49,7 @@ def perform_action(choice) if action send(action) else - puts "\nInvalid choice. Please select an option from 1 to 6." + puts "\n#{Colors.error('Invalid choice. Please select an option from 1 to 6.')}" end end @@ -55,7 +58,7 @@ def create_player player = Player.new(name: gets.chomp) if player.save - puts "\nAdventurer created successfully!" + puts "\n#{Colors.success('Adventurer created successfully!')}" display_player(player) else display_errors(player) @@ -66,7 +69,7 @@ def view_all_players players = Player.all if players.empty? - puts "\nNo adventurers have been created yet." + puts "\n#{Colors.warning('No adventurers have been created yet.')}" return end @@ -92,7 +95,7 @@ def update_player new_name = player.name if new_name.empty? if player.update(name: new_name) - puts "\nAdventurer updated successfully!" + puts "\n#{Colors.success('Adventurer updated successfully!')}" display_player(player) else display_errors(player) @@ -103,13 +106,13 @@ def delete_player player = select_player return unless player - puts "\nYou selected:" + puts "\n#{Colors.warning('You selected:')}" display_player(player) unless confirm_action?( "Delete this adventurer and all of their quests?" ) - puts "\nDeletion canceled." + puts "\n#{Colors.warning('Deletion canceled.')}" return end @@ -118,14 +121,14 @@ def delete_player def destroy_player(player) if player.destroy - puts "\nAdventurer deleted successfully." + puts "\n#{Colors.success('Adventurer deleted successfully.')}" else display_errors(player) end end def confirm_action?(message) - print "\n#{message} (y/n): " + print "\n#{Colors.warning("#{message} (y/n): ")}" gets.chomp.downcase == "y" end @@ -133,34 +136,35 @@ def select_player players = Player.all if players.empty? - puts "\nNo adventurers have been created yet." + puts "\n#{Colors.warning('No adventurers have been created yet.')}" return end - puts "\nSelect an adventurer:" + puts "\n#{Colors.heading('Select an adventurer:')}" players.each do |player| - puts "#{player.id}. #{player.name}" + puts "#{Colors.cyan(player.id)}. #{player.name}" end print "\nEnter adventurer ID: " player = players.find_by(id: gets.chomp) - puts "\nAdventurer not found." unless player + puts "\n#{Colors.error('Adventurer not found.')}" unless player player end def display_heading(title) - puts "\n========================" - puts " #{title}" + puts + puts "========================" + puts Colors.heading(title.center(24)) puts "========================" end def display_player(player) - puts "\nID: #{player.id}" - puts "Name: #{player.name}" - puts "Level: #{player.level}" - puts "Current XP: #{player.current_xp}" + puts "\n#{Colors.bold('ID:')} #{player.id}" + puts "#{Colors.bold('Name:')} #{player.name}" + puts "#{Colors.bold('Level:')} #{Colors.xp(player.level)}" + puts "#{Colors.bold('Current XP:')} #{Colors.xp(player.current_xp)}" puts "------------------------" end @@ -168,16 +172,16 @@ def display_quest_summary(player) active_count = player.quests.where(completed: false).count completed_count = player.quests.where(completed: true).count - puts "Active Quests: #{active_count}" - puts "Completed Quests: #{completed_count}" + puts "#{Colors.yellow('Active Quests:')} #{active_count}" + puts "#{Colors.green('Completed Quests:')} #{completed_count}" puts "------------------------" end def display_errors(record) - puts "\nUnable to save adventurer:" + puts "\n#{Colors.error('Unable to save adventurer:')}" record.errors.full_messages.each do |message| - puts "- #{message}" + puts Colors.red("- #{message}") end end end diff --git a/cli/quest_cli.rb b/cli/quest_cli.rb index 3174930..223cbbe 100644 --- a/cli/quest_cli.rb +++ b/cli/quest_cli.rb @@ -1,3 +1,5 @@ +require_relative "colors" + class QuestCLI MENU_ACTIONS = { "1" => :manage_adventurers, @@ -21,30 +23,33 @@ def run private def display_welcome - puts "\n========================" - puts " WELCOME TO QUESTCLI" - puts "========================" + puts + puts("========================") + puts Colors.heading(" QUESTCLI") + puts("========================") puts "Create adventurers, accept quests, and earn experience!" end def display_main_menu - puts "\n========================" - puts " MAIN MENU" + puts + puts "========================" + puts Colors.heading(" MAIN MENU") puts "========================" puts "1. Manage Adventurers" puts "2. Manage Quests" puts "3. View Quest Log" puts "4. Exit" - print "\nChoose an option: " + print "\n#{Colors.yellow('Choose an option: ')}" end def exit_selected?(choice) return false unless choice == "4" - puts "\n========================" - puts " THANKS FOR PLAYING" + puts + puts "========================" + puts Colors.heading(" THANKS FOR PLAYING") puts "========================" - puts "Your adventures have been saved." + puts Colors.success("Your adventures have been saved.") true end @@ -54,7 +59,7 @@ def perform_action(choice) if action send(action) else - puts "\nInvalid choice. Please select an option from 1 to 4." + puts "\n#{Colors.error('Invalid choice. Please select an option from 1 to 4.')}" end end diff --git a/cli/quest_display.rb b/cli/quest_display.rb index 20a8e6e..b040a8a 100644 --- a/cli/quest_display.rb +++ b/cli/quest_display.rb @@ -1,7 +1,10 @@ +require_relative "colors" + class QuestDisplay def menu - puts "\n========================" - puts " QUEST MENU" + puts + puts "========================" + puts Colors.heading(" QUEST MENU") puts "========================" puts "1. Accept Quest" puts "2. View All Quests" @@ -12,12 +15,12 @@ def menu puts "7. Complete Quest" puts "8. Abandon Quest" puts "9. Return to Main Menu" - print "\nChoose an option: " + print "\n#{Colors.yellow('Choose an option: ')}" end def quests(quests) if quests.empty? - puts "\nNo quests found." + puts "\n#{Colors.warning('No quests found.')}" return end @@ -28,59 +31,60 @@ def quest_choices(quests) puts quests.each do |quest| - puts "#{quest.id}. #{quest.title} - #{quest.player.name}" + puts "#{Colors.cyan(quest.id)}. #{quest.title} - #{quest.player.name}" end end def quest_details(quest) - status = quest.completed? ? "Completed" : "Active" - puts "\n------------------------" - puts "Quest: #{quest.title}" - puts "Adventurer: #{quest.player.name}" - puts "Description: #{quest.description}" - puts "Difficulty: #{quest.difficulty}" - puts "XP Reward: #{quest.xp_reward}" - puts "Status: #{status}" + puts "#{Colors.bold('Quest:')} #{Colors.heading(quest.title)}" + puts "#{Colors.bold('Adventurer:')} #{quest.player.name}" + puts "#{Colors.bold('Description:')} #{quest.description}" + puts "#{Colors.bold('Difficulty:')} #{Colors.difficulty(quest.difficulty)}" + puts "#{Colors.bold('XP Reward:')} #{Colors.xp("#{quest.xp_reward} XP")}" + puts "#{Colors.bold('Status:')} #{Colors.status(quest.completed?)}" puts "------------------------" end def completion(quest, player, previous_level) - puts "\n========================" - puts " QUEST COMPLETE!" - puts "========================" - puts "#{player.name} earned #{quest.xp_reward} XP." + puts + puts Colors.bright_green("========================") + puts Colors.bold(Colors.bright_green(" QUEST COMPLETE!")) + puts Colors.bright_green("========================") + puts Colors.success(quest.title) + puts "#{player.name} earned #{Colors.xp("+#{quest.xp_reward} XP")}." level_up(player, previous_level) - puts "Current Level: #{player.level}" - puts "Total XP: #{player.current_xp}" + puts "#{Colors.bold('Current Level:')} #{Colors.xp(player.level)}" + puts "#{Colors.bold('Total XP:')} #{Colors.xp(player.current_xp)}" end def abandonment(quest) - puts "\nQuest abandoned successfully." + puts "\n#{Colors.warning('Quest abandoned successfully.')}" puts "#{quest.title} has been removed." - puts "No XP was awarded." + puts Colors.yellow("No XP was awarded.") end def quest_log(players) if players.empty? - puts "\nNo adventurers have been created yet." + puts "\n#{Colors.warning('No adventurers have been created yet.')}" return end - puts "\n========================" - puts " QUEST LOG" + puts + puts "========================" + puts Colors.heading(" QUEST LOG") puts "========================" players.each { |player| player_quest_log(player) } end def errors(record) - puts "\nUnable to save quest:" + puts "\n#{Colors.error('Unable to save quest:')}" record.errors.full_messages.each do |message| - puts "- #{message}" + puts Colors.red("- #{message}") end end @@ -90,10 +94,11 @@ def player_quest_log(player) quests = player.quests completed_quests, active_quests = quests.partition(&:completed?) - puts "\n#{player.name}" - puts "Level: #{player.level} | XP: #{player.current_xp}" - puts "Active: #{active_quests.count}" - puts "Completed: #{completed_quests.count}" + puts "\n#{Colors.heading(player.name)}" + puts "#{Colors.bold('Level:')} #{Colors.xp(player.level)} | " \ + "#{Colors.bold('XP:')} #{Colors.xp(player.current_xp)}" + puts "#{Colors.yellow('Active:')} #{active_quests.count}" + puts "#{Colors.green('Completed:')} #{completed_quests.count}" display_log_section("ACTIVE QUESTS", active_quests) display_log_section("COMPLETED QUESTS", completed_quests) @@ -102,22 +107,28 @@ def player_quest_log(player) end def display_log_section(title, quests) - puts "\n#{title}" + puts "\n#{Colors.heading(title)}" if quests.empty? - puts "None" + puts Colors.warning("None") return end quests.each do |quest| - puts "- #{quest.title} | #{quest.difficulty} | #{quest.xp_reward} XP" + difficulty = Colors.difficulty(quest.difficulty) + reward = Colors.xp("#{quest.xp_reward} XP") + + puts "- #{quest.title} | #{difficulty} | #{reward}" end end def level_up(player, previous_level) return unless player.level > previous_level - puts "\n#{player.name} leveled up!" + puts + puts Colors.bold(Colors.bright_magenta("★ LEVEL UP! ★")) + puts "#{player.name} reached " \ + "#{Colors.xp("Level #{player.level}")}!" puts "Level #{previous_level} -> Level #{player.level}" end end diff --git a/cli/quest_menu.rb b/cli/quest_menu.rb index 9329a3f..fc7755b 100644 --- a/cli/quest_menu.rb +++ b/cli/quest_menu.rb @@ -1,3 +1,4 @@ +require_relative "colors" require_relative "quest_display" class QuestMenu @@ -32,7 +33,7 @@ def run def return_to_main_menu?(choice) return false unless choice == "9" - puts "\nReturning to the main menu..." + puts "\n#{Colors.warning('Returning to the main menu...')}" true end @@ -42,7 +43,7 @@ def perform_action(choice) if action send(action) else - puts "\nInvalid choice. Please select an option from 1 to 9." + puts "\n#{Colors.error('Invalid choice. Please select an option from 1 to 9.')}" end end @@ -53,7 +54,7 @@ def accept_quest quest = player.quests.build(quest_attributes) if quest.save - puts "\nQuest accepted successfully!" + puts "\n#{Colors.success('Quest accepted successfully!')}" @display.quest_details(quest) else @display.errors(quest) @@ -105,7 +106,7 @@ def update_quest return unless quest if quest.update(updated_quest_attributes(quest)) - puts "\nQuest updated successfully!" + puts "\n#{Colors.success('Quest updated successfully!')}" @display.quest_details(quest) else @display.errors(quest) @@ -141,7 +142,7 @@ def complete_quest @display.quest_details(quest) unless confirm_action?("Complete this quest?") - puts "\nQuest completion canceled." + puts "\n#{Colors.warning('Quest completion canceled.')}" return end @@ -169,7 +170,7 @@ def abandon_quest @display.quest_details(quest) unless confirm_action?("Abandon this quest?") - puts "\nQuest abandonment canceled." + puts "\n#{Colors.warning('Quest abandonment canceled.')}" return end @@ -185,7 +186,7 @@ def destroy_quest(quest) end def confirm_action?(message) - print "\n#{message} (y/n): " + print "\n#{Colors.warning("#{message} (y/n): ")}" gets.chomp.downcase == "y" end @@ -193,20 +194,20 @@ def select_player players = Player.all if players.empty? - puts "\nNo adventurers have been created yet." + puts "\n#{Colors.warning('No adventurers have been created yet.')}" return end - puts "\nSelect an adventurer:" + puts "\n#{Colors.heading('Select an adventurer:')}" players.each do |player| - puts "#{player.id}. #{player.name}" + puts "#{Colors.cyan(player.id)}. #{player.name}" end print "\nEnter adventurer ID: " player = players.find_by(id: gets.chomp) - puts "\nAdventurer not found." unless player + puts "\n#{Colors.error('Adventurer not found.')}" unless player player end @@ -223,7 +224,7 @@ def select_active_quest def select_quest_from(quests, quest_type) if quests.empty? - puts "\nNo #{quest_type}s found." + puts "\n#{Colors.warning("No #{quest_type}s found.")}" return end @@ -232,7 +233,8 @@ def select_quest_from(quests, quest_type) print "\nEnter #{quest_type} ID: " quest = quests.find_by(id: gets.chomp) - puts "\n#{quest_type.capitalize} not found." unless quest + puts "\n#{Colors.error("#{quest_type.capitalize} not found.")}" unless quest + quest end end From e0ec8eda5144f641d61ee64576c8cb48babe430a Mon Sep 17 00:00:00 2001 From: ThomasCorreia <48772223+Midnight-Envy@users.noreply.github.com> Date: Wed, 29 Jul 2026 12:33:40 -0400 Subject: [PATCH 14/18] Expand stretch goals with detailed user stories --- user-stories.md | 148 +++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 134 insertions(+), 14 deletions(-) diff --git a/user-stories.md b/user-stories.md index 02716af..8eebc2c 100644 --- a/user-stories.md +++ b/user-stories.md @@ -165,6 +165,8 @@ The goal is to create a simple but engaging CLI experience that demonstrates CRU --- + + # Quest Lifecycle ```text @@ -408,17 +410,135 @@ Update prompts will display the current value before asking the user to enter a These features are not required for the MVP: -- Search quests by title -- Sort quests by difficulty -- Randomly generate quests -- Add gold rewards -- Add character classes -- Add quest categories -- Add deadlines to quests -- Display player statistics -- Display completion percentages -- Add achievements -- Add an inventory system -- Add items as a third model -- Add multiple experience progression systems -- Add ASCII art and enhanced CLI styling +-Character classes +-Gold rewards +-Items +-Adventurer inventory +-Quest prerequisites +-Store +-Quest steps and interactions +-Full seed data +-Final balancing and CLI polish + +# Stretch Goal User Stories + +These user stories describe optional features that expand QuestCLI into a more complete playable RPG experience. + +## Feature 9 - Character Classes + +**User Story:** As a player, I want my adventurer to specialize into a character class so that the adventurer has a distinct identity and progression path. + +### Details + +- An eligible adventurer can choose from available character classes. +- The selected class is saved to the adventurer. +- An adventurer cannot select an invalid class. +- An adventurer cannot repeatedly change classes unless the game explicitly allows it. +- The adventurer's class appears in their details. +- Existing adventurers remain valid before selecting a class. + +## Feature 10 - Gold Rewards + +**User Story:** As a player, I want quests to reward gold so that I can purchase useful items. + +### Details + +- Players have a gold balance. +- Quests may define a gold reward. +- Completing a quest adds its gold reward to the player. +- Gold is awarded only once per quest. +- The player's current gold appears in their details. + +## Feature 11 - Quest Item Rewards + +**User Story:** As a player, I want quests to reward items so that completing adventures can unlock tools and valuable objects. + +### Details + +- Quests may reward one or more items. +- Quest rewards are added to the player's inventory. +- Rewards are granted only after successful quest completion. +- The CLI displays the items earned. +- The same reward is not granted more than once. + +## Feature 12 - Adventurer Inventory + +**User Story:** As a player, I want my adventurer to have an inventory so that I can collect, view, and use items. + +### Details + +- A player can own multiple items. +- Inventory tracks the quantity of each item. +- The player can view their inventory. +- Purchased and rewarded items are added to inventory. +- Empty inventories display a clear message. + +## Feature 13 - Quest Prerequisites + +**User Story:** As a player, I want quests to have prerequisites so that progression feels meaningful and advanced quests must be earned. + +### Details + +- A quest may require a minimum level. +- A quest may require a character class. +- A quest may require one or more inventory items. +- A quest may require another quest to be completed. +- The player cannot begin an unavailable quest. +- The CLI explains every unmet prerequisite. + +## Feature 14 - Item Store + +**User Story:** As a player, I want to purchase items from a store so that I can prepare for future quests. + +### Details + +- The store displays available items. +- Each item displays a name, description, and gold price. +- Players can purchase items they can afford. +- Purchased items are added to inventory. +- The cost is deducted from the player's gold. +- Players cannot purchase items they cannot afford. +- Invalid purchases display a clear error. + +## Feature 15 - Interactive Quest Steps + +**User Story:** As a player, I want quests to contain interactive steps so that completing a quest feels like playing an adventure instead of selecting a single menu option. + +### Details + +- A quest contains one or more ordered steps. +- The number and complexity of steps can vary by difficulty. +- Steps may include riddles, combat, choices, obstacles, dialogue, and item use. +- Required steps must be completed in order. +- Failed or invalid input does not automatically complete the quest. +- The quest completes only after all required steps are finished. +- Rewards are granted after the final step. + +## Feature 16 - Difficulty-Based Quest Depth + +**User Story:** As a player, I want harder quests to contain more challenging and longer interactions so that difficulty affects gameplay rather than only rewards. + +### Details + +- Easy quests contain a small number of simple steps. +- Medium quests contain multiple interactions. +- Hard quests contain longer sequences and more demanding prerequisites. +- Difficulty affects XP, gold, and possible item rewards. +- The CLI clearly displays quest difficulty. + +## Feature 17 - Seeded Adventure + +**User Story:** As a new player, I want the game to include a complete seeded adventure so that I can create an adventurer and begin playing immediately. + +### Details + +- The seed file creates reusable items. +- The seed file creates store inventory. +- The seed file creates quests of every difficulty. +- Seeded quests include XP and gold rewards. +- Some quests include item rewards. +- Some quests include prerequisites. +- Seeded quests contain interactive steps. +- Quests form a logical progression. +- Running the seed task does not create uncontrolled duplicate data. +- A new user can play without manually creating every quest. From c6011469b75dccef60e29391c39ffbd6fa0b6511 Mon Sep 17 00:00:00 2001 From: ThomasCorreia <48772223+Midnight-Envy@users.noreply.github.com> Date: Wed, 29 Jul 2026 12:35:08 -0400 Subject: [PATCH 15/18] Format stretch goals list for clarity --- user-stories.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/user-stories.md b/user-stories.md index 8eebc2c..3184426 100644 --- a/user-stories.md +++ b/user-stories.md @@ -410,15 +410,16 @@ Update prompts will display the current value before asking the user to enter a These features are not required for the MVP: --Character classes --Gold rewards --Items --Adventurer inventory --Quest prerequisites --Store --Quest steps and interactions --Full seed data --Final balancing and CLI polish +- Character classes +- Gold rewards +- Items +- Adventurer inventory +- Quest prerequisites +- Store +- Quest steps and interactions +- Full seed data +- Final balancing and CLI polish + # Stretch Goal User Stories From 6ca0897ed48088abce81517c8ebfeb684ecfdaf5 Mon Sep 17 00:00:00 2001 From: ThomasCorreia <48772223+Midnight-Envy@users.noreply.github.com> Date: Thu, 30 Jul 2026 08:37:28 -0400 Subject: [PATCH 16/18] Update project directory name in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b63f043..99c5ec1 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ git clone Navigate into the project. ```bash -cd questcli +cd phase-3-ruby-project ``` Install dependencies. From b30fca0236d9344f3a6b3c595b178b784397adae Mon Sep 17 00:00:00 2001 From: ThomasCorreia <48772223+Midnight-Envy@users.noreply.github.com> Date: Thu, 30 Jul 2026 09:19:29 -0400 Subject: [PATCH 17/18] Add user stories for new features and update numbering --- user-stories.md | 49 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/user-stories.md b/user-stories.md index 3184426..dffb8f5 100644 --- a/user-stories.md +++ b/user-stories.md @@ -165,7 +165,38 @@ The goal is to create a simple but engaging CLI experience that demonstrates CRU --- +## Feature 9 - Index-Based Record Selection +**User Story:** As a user, I want players and quests displayed as consecutive numbered choices so that record selection is clear even when database records have been deleted. + +### Details + +- Display players using consecutive list numbers beginning at 1. +- Display quests using consecutive list numbers beginning at 1. +- Allow the user to select a player by its displayed list number. +- Allow the user to select a quest by its displayed list number. +- Keep database IDs hidden from normal CLI selection. +- Continue using Active Record records internally. +- Handle selections outside the displayed range without crashing. +- Apply index-based selection consistently throughout the player and quest menus. + +--- + +## Feature 10 - Seeded Starter Adventure + +**User Story:** As a reviewer, I want the database to contain starter content so that I can immediately explore the application without manually creating every record. + +### Details + +- Seed at least one sample adventurer. +- Seed multiple quests belonging to the sample adventurer. +- Include easy, medium, and hard quests. +- Give every seeded quest a title, description, difficulty, XP reward, and active status. +- Ensure seeded records satisfy all model validations. +- Allow the reviewer to view, update, complete, and abandon seeded quests. +- Prevent repeated seeding from creating uncontrolled duplicate records. + +--- # Quest Lifecycle @@ -425,7 +456,7 @@ These features are not required for the MVP: These user stories describe optional features that expand QuestCLI into a more complete playable RPG experience. -## Feature 9 - Character Classes +## Feature 11 - Character Classes **User Story:** As a player, I want my adventurer to specialize into a character class so that the adventurer has a distinct identity and progression path. @@ -438,7 +469,7 @@ These user stories describe optional features that expand QuestCLI into a more c - The adventurer's class appears in their details. - Existing adventurers remain valid before selecting a class. -## Feature 10 - Gold Rewards +## Feature 12 - Gold Rewards **User Story:** As a player, I want quests to reward gold so that I can purchase useful items. @@ -450,7 +481,7 @@ These user stories describe optional features that expand QuestCLI into a more c - Gold is awarded only once per quest. - The player's current gold appears in their details. -## Feature 11 - Quest Item Rewards +## Feature 13 - Quest Item Rewards **User Story:** As a player, I want quests to reward items so that completing adventures can unlock tools and valuable objects. @@ -462,7 +493,7 @@ These user stories describe optional features that expand QuestCLI into a more c - The CLI displays the items earned. - The same reward is not granted more than once. -## Feature 12 - Adventurer Inventory +## Feature 14 - Adventurer Inventory **User Story:** As a player, I want my adventurer to have an inventory so that I can collect, view, and use items. @@ -474,7 +505,7 @@ These user stories describe optional features that expand QuestCLI into a more c - Purchased and rewarded items are added to inventory. - Empty inventories display a clear message. -## Feature 13 - Quest Prerequisites +## Feature 15 - Quest Prerequisites **User Story:** As a player, I want quests to have prerequisites so that progression feels meaningful and advanced quests must be earned. @@ -487,7 +518,7 @@ These user stories describe optional features that expand QuestCLI into a more c - The player cannot begin an unavailable quest. - The CLI explains every unmet prerequisite. -## Feature 14 - Item Store +## Feature 16 - Item Store **User Story:** As a player, I want to purchase items from a store so that I can prepare for future quests. @@ -501,7 +532,7 @@ These user stories describe optional features that expand QuestCLI into a more c - Players cannot purchase items they cannot afford. - Invalid purchases display a clear error. -## Feature 15 - Interactive Quest Steps +## Feature 17 - Interactive Quest Steps **User Story:** As a player, I want quests to contain interactive steps so that completing a quest feels like playing an adventure instead of selecting a single menu option. @@ -515,7 +546,7 @@ These user stories describe optional features that expand QuestCLI into a more c - The quest completes only after all required steps are finished. - Rewards are granted after the final step. -## Feature 16 - Difficulty-Based Quest Depth +## Feature 18 - Difficulty-Based Quest Depth **User Story:** As a player, I want harder quests to contain more challenging and longer interactions so that difficulty affects gameplay rather than only rewards. @@ -527,7 +558,7 @@ These user stories describe optional features that expand QuestCLI into a more c - Difficulty affects XP, gold, and possible item rewards. - The CLI clearly displays quest difficulty. -## Feature 17 - Seeded Adventure +## Feature 19 - Seeded Adventure **User Story:** As a new player, I want the game to include a complete seeded adventure so that I can create an adventurer and begin playing immediately. From 3a31dfd7508fd58927722c911d68d34d5af6120c Mon Sep 17 00:00:00 2001 From: Thomas Correia Date: Thu, 30 Jul 2026 14:54:18 -0400 Subject: [PATCH 18/18] Completed Features 9 and 10 --- cli/player_menu.rb | 37 +++++++++++++++++++++++----------- cli/quest_display.rb | 21 +++++++++++++------ cli/quest_menu.rb | 34 +++++++++++++++++++++---------- db/seeds.rb | 48 ++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 109 insertions(+), 31 deletions(-) diff --git a/cli/player_menu.rb b/cli/player_menu.rb index 4e9791b..d612407 100644 --- a/cli/player_menu.rb +++ b/cli/player_menu.rb @@ -66,7 +66,7 @@ def create_player end def view_all_players - players = Player.all + players = Player.all.to_a if players.empty? puts "\n#{Colors.warning('No adventurers have been created yet.')}" @@ -74,7 +74,11 @@ def view_all_players end display_heading("ADVENTURERS") - players.each { |player| display_player(player) } + + players.each_with_index do |player, index| + puts "\n#{Colors.heading("#{index + 1}. #{player.name}")}" + display_player(player, show_name: false) + end end def view_player_details @@ -133,7 +137,7 @@ def confirm_action?(message) end def select_player - players = Player.all + players = Player.all.to_a if players.empty? puts "\n#{Colors.warning('No adventurers have been created yet.')}" @@ -142,15 +146,25 @@ def select_player puts "\n#{Colors.heading('Select an adventurer:')}" - players.each do |player| - puts "#{Colors.cyan(player.id)}. #{player.name}" + players.each_with_index do |player, index| + puts "#{Colors.cyan(index + 1)}. #{player.name}" end - print "\nEnter adventurer ID: " - player = players.find_by(id: gets.chomp) + print "\nEnter adventurer number: " + selected_player = record_at_index(players, gets.chomp) + + puts "\n#{Colors.error('Adventurer not found.')}" unless selected_player + + selected_player + end + + def record_at_index(records, input) + index = input.to_i - 1 + + return if index.negative? + return if index >= records.length - puts "\n#{Colors.error('Adventurer not found.')}" unless player - player + records[index] end def display_heading(title) @@ -160,9 +174,8 @@ def display_heading(title) puts "========================" end - def display_player(player) - puts "\n#{Colors.bold('ID:')} #{player.id}" - puts "#{Colors.bold('Name:')} #{player.name}" + def display_player(player, show_name: true) + puts "\n#{Colors.bold('Name:')} #{player.name}" if show_name puts "#{Colors.bold('Level:')} #{Colors.xp(player.level)}" puts "#{Colors.bold('Current XP:')} #{Colors.xp(player.current_xp)}" puts "------------------------" diff --git a/cli/quest_display.rb b/cli/quest_display.rb index b040a8a..b77c0b8 100644 --- a/cli/quest_display.rb +++ b/cli/quest_display.rb @@ -19,24 +19,33 @@ def menu end def quests(quests) + quests = quests.to_a + if quests.empty? puts "\n#{Colors.warning('No quests found.')}" return end - quests.each { |quest| quest_details(quest) } + quests.each_with_index do |quest, index| + quest_details(quest, index + 1) + end end def quest_choices(quests) puts - quests.each do |quest| - puts "#{Colors.cyan(quest.id)}. #{quest.title} - #{quest.player.name}" + quests.each_with_index do |quest, index| + number = Colors.cyan(index + 1) + + puts "#{number}. #{quest.title} - #{quest.player.name}" end end - def quest_details(quest) + def quest_details(quest, number = nil) puts "\n------------------------" + + puts "#{Colors.bold('Quest Number:')} #{number}" if number + puts "#{Colors.bold('Quest:')} #{Colors.heading(quest.title)}" puts "#{Colors.bold('Adventurer:')} #{quest.player.name}" puts "#{Colors.bold('Description:')} #{quest.description}" @@ -114,11 +123,11 @@ def display_log_section(title, quests) return end - quests.each do |quest| + quests.each_with_index do |quest, index| difficulty = Colors.difficulty(quest.difficulty) reward = Colors.xp("#{quest.xp_reward} XP") - puts "- #{quest.title} | #{difficulty} | #{reward}" + puts "#{index + 1}. #{quest.title} | #{difficulty} | #{reward}" end end diff --git a/cli/quest_menu.rb b/cli/quest_menu.rb index fc7755b..ba3d850 100644 --- a/cli/quest_menu.rb +++ b/cli/quest_menu.rb @@ -191,7 +191,7 @@ def confirm_action?(message) end def select_player - players = Player.all + players = Player.all.to_a if players.empty? puts "\n#{Colors.warning('No adventurers have been created yet.')}" @@ -200,15 +200,16 @@ def select_player puts "\n#{Colors.heading('Select an adventurer:')}" - players.each do |player| - puts "#{Colors.cyan(player.id)}. #{player.name}" + players.each_with_index do |player, index| + puts "#{Colors.cyan(index + 1)}. #{player.name}" end - print "\nEnter adventurer ID: " - player = players.find_by(id: gets.chomp) + print "\nEnter adventurer number: " + selected_player = record_at_index(players, gets.chomp) - puts "\n#{Colors.error('Adventurer not found.')}" unless player - player + puts "\n#{Colors.error('Adventurer not found.')}" unless selected_player + + selected_player end def select_quest @@ -223,6 +224,8 @@ def select_active_quest end def select_quest_from(quests, quest_type) + quests = quests.to_a + if quests.empty? puts "\n#{Colors.warning("No #{quest_type}s found.")}" return @@ -230,11 +233,20 @@ def select_quest_from(quests, quest_type) @display.quest_choices(quests) - print "\nEnter #{quest_type} ID: " - quest = quests.find_by(id: gets.chomp) + print "\nEnter #{quest_type} number: " + selected_quest = record_at_index(quests, gets.chomp) + + puts "\n#{Colors.error("#{quest_type.capitalize} not found.")}" unless selected_quest + + selected_quest + end + + def record_at_index(records, input) + index = input.to_i - 1 - puts "\n#{Colors.error("#{quest_type.capitalize} not found.")}" unless quest + return if index.negative? + return if index >= records.length - quest + records[index] end end diff --git a/db/seeds.rb b/db/seeds.rb index 437ef89..1fb53ba 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -1,7 +1,51 @@ # frozen_string_literal: true -puts "🌱 Seeding spices..." +puts "🌱 Seeding QuestCLI..." -# Seed your database here +adventurer = Player.find_or_create_by!(name: "Neha") do |player| + player.level = 1 + player.current_xp = 0 +end +quests = [ + { + title: "Gather Healing Herbs", + description: "Collect medicinal herbs from the edge of the Whispering Woods.", + difficulty: "Easy", + xp_reward: 50, + }, + { + title: "Recover the Lost Satchel", + description: "Find the merchant's missing satchel along the old forest road.", + difficulty: "Easy", + xp_reward: 75, + }, + { + title: "Defeat the Cave Goblin", + description: "Enter the northern cave and defeat the goblin troubling nearby travelers.", + difficulty: "Medium", + xp_reward: 150, + }, + { + title: "Recover the Moonlit Amulet", + description: "Search the ruined temple and recover the ancient Moonlit Amulet.", + difficulty: "Hard", + xp_reward: 300, + }, +] + +quests.each do |attributes| + Quest.find_or_create_by!( + player: adventurer, + title: attributes[:title] + ) do |quest| + quest.description = attributes[:description] + quest.difficulty = attributes[:difficulty] + quest.xp_reward = attributes[:xp_reward] + quest.completed = false + end +end + +puts "✅ Seeded #{Player.count} adventurer(s)." +puts "✅ Seeded #{Quest.count} quest(s)." puts "✅ Done seeding!"