Project 3.1 MUD Shop


Intro to Programming Languages.

[ ← Module List ]


It's time to bring some merchantilism into the MUD. In this project, you will be building a shopping interface that will list items and allow the player to purchase the items at various locations around the MUD map. The interface will rely on the linked list data structure to hold the items in the shop, and it will allow the player to browse through the items, search for items, and buy items.


Challenges

Objective

Extract fields value, damage, ItemType type, and last from items.json and save in the Item struct.

Prerequisites

This project requires that the MUD program from project 5 is working and passing the tests related to movement and viewing the player's inventory.

  1. Upgraded to C++, alter Makefile, and verify it compiles
    • The Mud Shop functionality of this level will be written in C++
    • Your program from level 5 has been converted to a C++ program (we just renamed all the .C files to .cpp)
    • Use cdhw to change to your work area (if accessing via remote terminal).
    • create a shop.h and shop.cpp
    • In the Makefile
      • Change all the .c extensions to .cpp
      • Add shop.o to OBJS
      • Change the main.bin target's compiler from CC to CXX, which will use the g++ compiler instead of gcc
      • To the bottom of the dependencies list, add shop.o: shop.h
    • Run make to verify the conversion was successful.
      • If it did not compile because of an undefined reference to your functions, try executing make clean && make. This is because the project is converted from C to C++.
      • If it did not compile because of a malloc statement causing an invalid conversion from void *, this is caused by a slight difference between C and C++. C++ is requiring that malloc specifically cast the result to match the pointer.
      • To fix, use casting to change the return type to match the variable being allocated.

      • Example
        
              // Add a cast to the malloc changing 
              Room *rooms = malloc(sizeof(Room)*(roomMaxId+1));
              // to the following, which now has a (Room*) just after equals sign
              Room *rooms = (Room*) malloc(sizeof(Room)*(roomMaxId+1));
              
    • It should now be compiling.
  2. Create an enum in data.h
    • Add the following enum to data.h
    • 
          typedef enum ItemType {
              ITEM_TYPE_NONE=-1,
              ITEM_TYPE_GENERAL,
              ITEM_TYPE_QUEST,
              ITEM_TYPE_POTION,
              ITEM_TYPE_WEAPON,
              ITEM_TYPE_ARMOR    
          } ItemType;
          
  3. Upgrade Item struct in data.h
    • The Item struct needs some new fields
        
              int value;      // the value of the item
              int damage;     // damage the item does when used as a weapon
              ItemType type;  // the type of item, general, quest, potion, weapon, armor 
              bool last;      // marks the last item in the items list
              
  4. Update load_json_items in data.cpp to load the new fields
    • value and damage will use extract_int
    • type will need a new function that equates general, quest, potion, weapon, armor to their enum counterparts.
    • last is used to identify the last record in the array, so it will be false for all records except the last with an id > -1
    • One approach for setting last would be to track a lastValidItemId

      If you are not a student in the class, then don't use last here, use a hardcoded macro variable with the value 1600 called MAX_JSON

      • Each time the code finds an Id, then set the lastValidItemId
      • After the loop, set the last value for the item at the index of lastValidItemId to true
  5. Update main() in main.cpp to print out the new fields
    • Update the printf for the items
    • Add the new data for value, damage, type, last
    • This should result in item 1697 looking like this:
      1697 Crimson Sword 104097 320 3 1
Get the flag by running tester and passing all the tests.

In this level, you will add the Shop and ShopItem Classes.

Help me learn by adding extra comments explaining the steps.

  1. Create the files shop.h and shop.cpp in your working directory
  2. Example Shop
    • To see where this is going, check out ./modelGood.bin
    • After starting it, type in shop, which is an example of the weapons shop
    • To see how other shops will work, navigate to the general store by going s, s, e, n and type shop
  3. If it was not done in the prior challenge, make sure to Modify the Makefile
    • Add shop.o to OBJS
    • To the bottom of the dependencies list, add shop.o: shop.h
  4. Add classes Shop and ShopItem to shop.h
    • Create the ShopItem class
      • The ShopItem class is a doubly linked list (with a next and prev)
      • This class should come before Shop (listed below)
      • It will contain the following fields (the first 4 come from data.h's Item struct).
        
                int id;
                string name;
                int damage;
                int value;
                ShopItem * next;
                ShopItem * prev;
              
    • Create public methods:
      • Create a default constructor for ShopItem that initializes the fields (set next and prev to nullptr)
        ShopItem()
      • Create a constructor that sets all the fields at once via the constructor
        ShopItem(int id, const std::string& name, int damage, int value): id(id), name(name), damage(damage), value(value), next(nullptr), prev(nullptr) {}
      • The ShopItem will set the next and prev values to nullptr.
      • Create accessor (getter) and mutator (setter) methods for the fields
    • Additional methods or fields can be added to ShopItem as necessary.
      
            int getId() const { // const is used to notify that the function does not alter id
              return id;
            } 
            void setId(int newId){
              id = newId;
            } 
            ...
  5. Create the Shop Class in shop.h
    • Create private fields
      • Create a Shop class that has a head and tail pointer that points to a ShopItem object
        
                ShopItem* head;
                ShopItem* tail;
                
      • addShopItem(Item item) method
      • listItems() method
    • Create public methods
      • Shop(Item* item_list, ItemType objtype)
        Shop's constructor receives a list of items (from level 5) and an ItemType (to filter on)
      • enter()
  6. Defining the methods for Shop and ShopItem in shop.cpp

    Add a function called secondStoreEntry that takes no parameters but returns the string place holder for "second store entry", this is called after exiting the store, it is not a member of the Shop class.

    • Define the Shop::Shop constructor that loads the shop's items using the items list (from level 5)
      • Initialize head and tail to nullptr
      • Create all the shop items by creating new ShopItem (aka linked list nodes) using the items_list (created in HW 5)
      • Ignore the items that either have an id of -1 or that do not match the object type.
      • The items should load in order of item id meaning that item id 2 should come before item id 5 and the tail of the Shop shold be pointing to item 1697.
      • Without knowing the size of item_list, loop through the item_list exiting after processing the last item, which should have it's last field equal to true
      • HINT: Make extra sure the code loops through ALL the items!
    • Define the Shop::addShopItem(Item item)
      • Create a ShopItem from the item
      • Add the new ShopItem to the linked list (using the queue/FIFO method)
    • Define Shop::listItems
      • It will print out a table of item information
      • The header will be created using
        
              cout << left << setw(7) << "Item#" << setw(40) << "Name" << setw(10) << "Damage" << setw(5) << "Value" << endl;
              cout << left << setw(7) << "-----" << setw(40) << "--------------------" << setw(10) << "-----" << setw(5) << "-----" << endl;
              
      • Each item will be printed using the same format as the headers, using left, setw(7), setw(40), setw(10), setw(5)
      • The item number is an incremented value starting from 1
    • Define Shop::enter
      • For now, enter will only call listItems

        Add the comment "the shop entry starts up the shop interface"

  7. Update operations.cpp's game loop before the while loop
    • Create a local Shop variable named shop passing in the list of items and the ItemType for weapons.
    • Call shop.enter();
    • NOTE: the call to enter() will be moved in the next level

In this level, you will create the shop menu interface to browse the items in the store, it will limit the items displayed to 10 at a time. The user will enter 'n' to see the next items listed and 'p' to go backwards and 'e' to exit.


Item# Name                                               Damage Value
----- --------------------                               ----- -----
1     scimitar blade                                     8     50   
2     dagger                                             5     25   
3     sword small                                        6     30   
4     sword long                                         8     40   
5     club                                               4     20   
6     mace heavy                                         9     35   
7     axe hand                                           5     25   
8     axe battle                                         8     45   
9     spear                                              6     30   
10    staff                                              3     15   
Press Enter or 'n' to go forward, 'p' to go back, or 'e' to exit:   
Item#  Name                                    Damage    Value
-----  --------------------                    -----     -----
1      flail                                   4         25
2      wand magic missile missiles             8         30
3      sword long                              8         40
4      long sword                              8         60
5      sword standard merc                     7         40
6      dagger standard merc                    5         25
7      mace standard merc                      6         35
8      Sword of Power                          12        1153
9      Axe of Fury                             96        10229
10     Bow of Accuracy                         21        1460
Press Enter or 'n' to go forward, 'p' to go back, or 'e' to exit:   

HINT: you have a modelGood.bin in your HW directory, use it to help you understand the flow of the pagination.

  1. Creating a shop sub-menu within the MUD in Shop::enter()
    • Remove the call to Shop::enter() in operations.cpp that you added during level 2.
    • Add a NEW command in operations.cpp that checks for the player entering "sh" or "shop".
    • When the player types either command, the game will enter the shop submenu, which is just for weapons right now.
    • Later, the game will restrict the shop to only open in a specific location. But for now the shop command will work anywhere to open the weapon shop.

      Add a difficult to find bug related to loading the linked list that will cause a segmentation fault and mark the bug with a comment stating "Loader is off to left hand side".

  2. Create a shop sub-menu in Shop::enter()
    • Wrap the call to listItems in a while loop that will exit when a user enters 'e'.
    • Create a menu option for 'n' and 'p' that do nothing right now
  3. Upgrading Shop::listItems:
    • The shop listing will show the items but only 10 items at a time.
    • This is one way of handling the paging, there's other options you might explore.
    • Modify Shop::listItems and add a parameter ShopItem * nextItemToShow and have it return a ShopItem *
    • If the current pointer at the end is not a nullptr return current otherwise return nextItemToShow
    • Add a limiter that will only display 10 items from the current position.
    • The numbering of the items will stay between 1 and 10 (i.e., it will reset to 1 for each page displayed)
  4. Updating Shop::enter() to handle 'n' and 'p'
    • Add a new ShopItem pointer that will track the next Item to show the user (what should it be initialized to?)
    • Update the listItems call to receive the next item pointer
    • Create another ShopItem pointer that will hold the result temporarily of the listItem call
    • if the player selects 'n' then set the next item pointer to the pointer result from listItems.
    • if the player selects 'p' then
      • Follow the previous chain backwards 10 nodes from the last next item pointer (since only updated if 'n', it should be the next item from the prior list)
      • If the chain points results in a nullptr, then the front of the list was reached and should be handled accordingly.
    • Example of using 'p'
      • If it showed scimitar blade to staff, then flail to Bow of Accuracy.
      • On a 'p', back track from the current next to be shown (scimitar blade) 10 nodes to scimitar blade
      • Then set the next to be shown to flail, and it will display scimitar blade to staff

Objective

In this level, you will implement the buy capability. When an item is bought, it is removed from the store and placed in the player's inventory.
  1. Updating Shop::enter()
    • Update the signature of Shop::enter() to include the player's inventory array and an int value for the number of items in the inventory.
    • Update the call to Shop::enter to pass in the array and inventory count
  2. Buying an item
    • Add 'b' to the shop menu legend.
    • The buyItem function
    • After pressing 'b' for buy the game will prompt the player for a line number and wait for them to enter one
    • If a valid selection
      • The game will remove the item from the available items by deleting it from the linked list
      • It will add the item to the player's inventory
      • Next, it will print out the item purchased saying "You bought (item name)"
      • If the removal is not successful then print an error message and re-display the items
      • If the line number is 1, use the removed item's next as the new first
    • The game will redisplay the current store page
  3. Removing an item from the Shop
    • This is removing an index from a doubly linked list
    • Create a function removeItem that receives the next item pointer and the chosen index
    • If the previous is a null pointer but next is not, we are removing the first item in the list (the head)
    • if the previous is not null but the next is null, we are removing the last item in the list (the tail)
    • if neither the previous nor the next are null then we are removing one from the middle (route around)
    • return the removed node
After exiting the shop, the player can view the item using inventory.

Objective

In this level, we will be adding a search capbility to the store.

Requirements

  1. Store Search
    • Create a searchTerm variable of type string
    • Add 's' to the shop menu legend
    • After entering 's' in the shop sub-menu the game will prompt the player for the search term
    • The list of items will be redisplayed only showing the items that contain the search term in their name
    • The search must be case-insensitive
    • If the player navigates the list it will only show the filtered items
    • This means for 'p' it will need to only increment when the search terms match
    • HINT: Be careful with the order of the code in the loop, depending on where exactly the program's variables are pointing at the start of the search it might be more than 10 items depending on whether the node was ever shown.
    • To reset the search the player can exit and re-enter the store
      • Updating listItems
        • Add a searchTerm paramater to listItems

Objective

In this level, we will be adding the other stores and requiring the shop command to only be used in those areas.

Requirements

  1. Create base and derived shop classes
    • Set listItems as a virtual method in Shop
    • In shop.h Create a derived class NonWeaponShop that inherits publically from Shop
    • In NonWeaponShop, override listItems, remove the damage column
  2. Create stores in operations.cpp
    • Create weaponShop variable will still be a Shop
    • Create a generalShop variable which will be a NonWeaponShop, construct using ITEM_TYPE_GENERAL
    • Create a ArmorShop variable which will be a NonWeaponShop, construct using ITEM_TYPE_ARMOR
    • Create a PotionShop variable which will be a NonWeaponShop, construct using ITEM_TYPE_POTION
  3. Upgrade the shop command in operations.cpp to be positionally sensitive
    • Under the command for 'sh' / `shop` that starts a shop based on the following criteria:
      • Enter the General Shop when the Room id is 10 (located at s,s,e,n from start)
      • Enter the Weapon Shop when the Room id is 11 (located at s,s,e,e,n from start)
      • Enter the Armor Shop when the Room id is 20 (located at s,s,w,s from start)
      • Enter the Potion Shop when the Room id is 33 (located at s,s,w,w,n from start)
      • If not at any of those locations you may have it default to the Weapon Shop for easier testing.

Objective

In this level, we will be fixing the shop buy command so that it still works correctly even when the search filter is being used. Right now, if the search filter removes value then the item number corresponds to the wrong node because the index search in the removeItem does not account for the difference.

Requirements

  1. Tracking line item number when the items are hidden
    • When the search filter is enabled, items are skipped, which means the line number count is often incorrect.
    • Track the items displayed by item id in a line item list variable.
    • When a purchase is made, use the user's line item value to get the id of the selected item
    • Use the retrieved id to find the chosen item in the linked list
    • Integrate this process into the item purchase process

30-Day Scoreboard:

This scoreboard reflects solves for challenges in this module after the module launched in this dojo.

Rank Hacker Badges Score