DEV Community ๐Ÿ‘ฉโ€๐Ÿ’ป๐Ÿ‘จโ€๐Ÿ’ป

CBanks901
CBanks901

Posted on

Library Simulation

Iโ€™d like to talk about a library simulation that I created using C++. Basically, it simulates an environment of people going into a library and checking out books, as well as outputting information about books such as their shelves, pages, etc. There are a few things to know beforehand:

1. This program uses quite a few classes and structs that are linked together in a fashion to display data i.e. (Bookshelf contains the books, books contain history, and the people are linked to active books etc.)

2. There are no external libraries like boost included in this project

3. The data is predetermined but also randomized from a hardcoded set of values meaning the output wonโ€™t always be exactly the same but will be similar every time

4. Each book only has three copies, so when that limit is reached, the book cannot be checked out again

With that out of the way Iโ€™d like to break down all of the functions that I used here:

โ€ข struct DualReturnType โ€“ a custom struct I made that allows me to store pointer data as well as its size in one return value as a means of easy access

โ€ข LibraryGuests int const - a constant integer value that holds the maximum number of people I wanted in the simulation

โ€ข ID โ€“ a static counter that increases when the guests are added

โ€ข Class Person โ€“ class that holds all information about an individual such as their names and the books that they have currently

โ€ข class BookHistory โ€“ A custom class that specifically holds the history of a book, primarily being who checked it out with information about the name in a way that can be easily printed out

โ€ข class Book โ€“ a book class that holds information about a book including its name, the number of pages, copies available, and a reference to a BookHistory instance

โ€ข class BookShelf โ€“ Acts as a real-world bookshelf so it contains a list of only 13 books per shelf (instance) and is something that is referenced when looking for books. Shelves correspond to the letter A-Z

โ€ข class BookGroup โ€“ The database of this program. It references almost all other classes such as Books and shelves and handles has references to them. When a request is made, it is done through this class.

โ€ข void SetUpBookNames function โ€“ Takes care of the task of seting up the BookGroup main reference with data such as the names and pages that are used in the program

โ€ข void setUpBookNames function โ€“ Same as the SetUpBookNames but with the library guests only.

โ€ข void Main โ€“ Sets up the program with required instancing of the guests, book names and book group instance in order to run. Made to loop until the user terminates the program

โ€ข class UserInputHelperClass โ€“ a class that handles the input directions that the user takes, whether that is searching for a person, book, or terminating the program

Now Iโ€™d like to give a synopsis of each of these variables and functions in more detail.

The first being the DualReturn Type struct. The DualReturnType struct is a struct that takes in a pointer and its size as references in its constructor. The only purpose of this is to make it easier for me reference pointers in other spaces of the program, particularly in the BookShelf class.

Here is the code for that:

// Helper struct that returns pointers and their sizes for use in other functions
struct DualReturnType
{
    // The default constructor. Parameters are the array and size.
    DualReturnType(int *Ref, int Size)
    {
        // checker to make sure the parameter value isn't equal to NULL
        if (Ref != NULL)
        {
            int_Array = Ref;
            int_size = Size;
        }
        // If the incoming pointer is NULL, output an error message
        else
            cout << "Error: failed to initialize integer dual type" << endl;
    }
Enter fullscreen mode Exit fullscreen mode
// The default constructor for this struct. Makes everything NULL
DualReturnType()
{
    int_Array = NULL;
    int_size = 0;
}

// An integer array pointer, and a integer size variable
int *int_Array, int_size;

// Same as the specialized constructor but as a void function
void Initialize_Int_Type(int *Ref, int Size)
{
    if (Ref != NULL)
    {
        int_Array = Ref;
        int_size = Size;
    }
    else
        cout << "Error: Failed to initialize integer dual type" << endl;
}
Enter fullscreen mode Exit fullscreen mode

};

The next class is the person class. The person class in its constructor initializes some default values including the name, the two books (array) they are allowing to check (โ€œโ€ or empty) as well as the book counter (hash key) which increases when a book is added to this class. The class also has a ID similar to a library card that is specifically used to identify people even if you donโ€™t know their name and it can be easily tracked. The only three functions of this class are to update the books array inside it (to take away or add books) and to display their info in the special function when desired.

Here is the code for that below:

// The Person/Guest for this simulation. Has parameters attached to it such as ID, booklimits, name, etc...
class Person
{
public:
    //Default constructor which initializes all values to basic functions.
    Person()
    {
        name = "Empty";
        BookLimit = 2;
        behavior = "Browsing";
        GuestID = 0;
        books[0] = "";
        books[1] = "";
        bookcounter = 0;
    }

    // Copy constructor
    Person(const Person &copy)
    {
        name = copy.name;
        BookLimit = copy.BookLimit;
        behavior = copy.behavior;
        GuestID = copy.GuestID;
        books[0] = "";
        books[1] = "";
        bookcounter = 0;
    }

    // Initialize name if the incoming string is valid
    Person(string Name)
    {
        if (!Name.empty())
            this->name = Name;
        else
        {
            cout << "This person doesn't have a valid name." << endl;
            name = "Empty";
        }
        BookLimit = 2;
        behavior = "Browsing";

        books[0] = "";
        books[1] = "";
        bookcounter = 0;
    }
Enter fullscreen mode Exit fullscreen mode
//Default destructor
~Person()
{

}

// Adds a book to the person's book array
void AddBook(string BookName)
{
    if (bookcounter <= 1)
    {
        books[bookcounter] = BookName;
        bookcounter++;
    }
    else
        cout << "Person()::AddBook cannot add more books\n";
}

// Prints the books that a person added
void PrintBooks()
{
    if (bookcounter > 0)
    {
        cout << name << " current books:              ";
        for (int i = 0; i < bookcounter; ++i)
        {
            if (books[i] != "")
                cout << i+1 << ": " << books[i] << "      ";
        }

        cout << "\n";
    }
    else
        cout << name << " has no history yet.\n";
}

// Prints the general info about this person
void PrintGeneralInfo()
{
    cout << "Name: " << name << "\tBooks in possession: " << bookcounter << "\tGuestID: " << GuestID << endl;
}

string name, behavior, books[2];                        // Name of the person, and arrayof booknames
int BookLimit, GuestID, bookcounter;                    // BookLimit to limit the person inside the library to a certain amount, and that persons GuestID, bookcounter to access current book   
Enter fullscreen mode Exit fullscreen mode

};

The next class is BookHistory. It has a public BookName menu, a private counter variable, Person pointer called people, a integer variable called currentlimit and a string order variable. There are only two functions inside this class which are AddHistory and PrintHistory. AddHistory takes an incoming Person object and adds it to the people reference of this class, and the other one simply prints out the data contained in this class along with the bookname.

Here is what all that looks like:

// Records the history of a book which can be called at any time later
class BookHistory
{
public: 
    string bookName;                            // Name of the book that is recorded
Enter fullscreen mode Exit fullscreen mode
// Default constructor which initializes all variables
BookHistory()
{
    people = new Person[3];
    currentLimit = 3;
    currentCounter = 0;
    order[0] = "first.";
    order[1] = "second.";
    order[2] = "third.";
}

// Copy constructor which initializes based on another bookHistory reference
BookHistory(const BookHistory &otherBookHistory)
{
    currentLimit = otherBookHistory.currentLimit;
    people = new Person[currentLimit];
    currentCounter = otherBookHistory.currentCounter;
    order[0] = "first.";
    order[1] = "second.";
    order[2] = "third.";

    for (int i = 0; i < currentCounter; i++)
        people[i] = otherBookHistory.people[i];
}

// Destructor which only destroys our people pointer
~BookHistory()
{
    if (people != NULL)
        delete [] people;
}

// How people are added to this books history. 
void AddHistory(Person person)
{
    // If the currentcounter hasn't yet reached the limit, then set the people pointer inside our history to the incoming person. Basically adds it inside the array
    if (currentCounter < currentLimit)
    {
        people[currentCounter] = person;
        currentCounter++;                   // finally increase the currentCounter
    }
    else
        cout << "Cannot update this books history at this time" << endl;
}

// Prints the histoy of this book based on the currentCounter which increases when people check out this book
void PrintHistory()
{
    if (currentCounter >= 1)
    {
        // Will print the persons Info assuming the people pointer isn't NULL and it's name isn't == Empty
        for (int i = 0; i < currentCounter; i++)
        {
            if (people != NULL)
                if (people[i].name != "Empty")
                    cout << people[i].name << " ID: " << people[i].GuestID << " checked out '" << bookName << "' " << order[i] << "\n\n";
                else
                    cout << "No names setup for this book\n";
        }
    }
    else
        cout << bookName << " has no check out history yet.\n\n";       // This should print out if the current counter isn't at least equal to 1. Book has no history.
}
Enter fullscreen mode Exit fullscreen mode
private:
    Person *people;                         // people pointer which holds references to anyone who checks out this book
    int currentLimit;                       // A max limit variable which is initialized to 3 for this simulation
    int currentCounter;                     // A counter that goes up until it reaches the currentLimit
    string order[3];                        // Simply a string helper which has strings based on the order of an index. So 0 = first, 1 = seconds, 2 = third. 
};
Enter fullscreen mode Exit fullscreen mode

The next class Iโ€™d like to touch on is the Book class. The book class has all public members including the number of pages in a book, its copies, availability, name and a BookHistory class member. Its functions include checking for empty books and removing copies of the book if the number of copies equals 0, which decreases when checked out. The constructor initializes most data with arguments passed in or without them.

Have a look here:

// Book class which is used to house all information about a book including its name, number of pages, whether it's avaiable or not, its history, and number of copies
class Book
{
public:
    // Default constructor for this class. Intializes all values. Name of the book is blank (set elsewhere), pages is also reset elsewhere, copies is set to 3,
    // avaialable is set to true, and the the historys bookname is set to empty
    Book()
    {
        name = "Empty";
        pages = 0;
        copies = 3;
        available = true;
        thisBookHistory.bookName = "Empty";
    }

Enter fullscreen mode Exit fullscreen mode
// Book with all parameters setup to copy into this variables
Book(string BookName, int numPages, int copies, BookHistory book)
{
    name = BookName;
    pages = numPages;
    thisBookHistory = book;
    available = true;
    this->copies = copies;
}

// Destructor of the book class
~Book()
{

}

// If the book is a dummy or a real book
bool emptyBook()
{
    if (name == "" || name == "Empty")
        return true;
    else
        return false;
}

// Removes a copy of the book from the list. Once a copy reaches zero, no one may check out this book
void RemoveCopy()
{
    // Simply decrease the number of copies by 1 and return
    if (copies >= 2)
    {
        copies -= 1;
    }
    // If the copy equals 1, then this is the last copy of the book and available should be set to false for other functions. Copies is also decremented by 1
    else if (copies == 1)
    {
        available = false;
        //cout << "This is the last copy of " << name << endl;
        copies -= 1;
    }
    // This shouldn't occur but is set just in case this function is called by accident
    else
        cout << "Sorry. " << "No more copies of: " << name << " are avaialable.\n";
}

string name;                    // Name of the book
int pages, copies;              // The number of pages of the book and its copies
bool available;                 // Whether the book is available or not. Used in the bookshelf class
BookHistory thisBookHistory;    //History variable of the book which can be called by the shelf
Enter fullscreen mode Exit fullscreen mode

};

The next one is the Bookshelf class. This class is akin to a library bookshelf and its intended design is to have more than one that corresponds to groups of two letters. So in total there are only thirteen of them they are in order by integer. So bookshelf[0] corresponds to books starting with A and B, bookshelf[1] C and D and so on. It has all public members which include a twelve array of Books (matching the shelves), a string which holds the shelfID, a filledboolean for easy access, a fullindex array of size twelve also matching the shelf, and a stockcounter that holds the books available for use. Its functions include a AddBook function which Adds a book and its pages to the class, a PrintAllBooksOnShelf class which prints all the books currently available on this shelf, a printbyindex function which does the same thing but in a limited fashion by first checking if the incoming index is valid, two printbookhistory functions which do the same thing as printindex and printall, a locateBook Boolean helper which returns true or false if a book is found, a getbookID function which tries to find a book by itโ€™s ID instead of name using the locateBook function, a Boolean helper called AllBooksTaken which searches through all books to find one that isnโ€™t false, and finally DualReturnType helper function called ReferenceArrayHelper which uses the StockCounter integer value to manually create a integer pointer reference that can be used in other places, such as the BookGroup which will be covered next.

Hereโ€™s what the Bookshelf looks like:

// Bookshelf class that holds all data about a bookshelf instance. 
class Bookshelf
{
public:
    // Default constructor. Sets stockcounter (current books) to 0, then set fullindex incidies to the value in the loop
    Bookshelf()
    {
        StockCounter = 0;
        for (int i = 0; i <= 11; ++i)
            fullindex[i] = i;
    }
Enter fullscreen mode Exit fullscreen mode
// Destructor
~Bookshelf()
{

}

// Add a book to this shelf. Parameters include the name of the book and its pages.
void AddBook(string BookName, int pages)
{   
    // If current book couner isn't at its max (12), then proceed
    if (StockCounter <= 11)
    {
        //  If the current name of the book is empty, prepare to replace it with the parameters.
        if (books[StockCounter].name == "Empty")
        {
            books[StockCounter].name = BookName;
            books[StockCounter].pages = pages;
            books[StockCounter].thisBookHistory.bookName = BookName;
            StockCounter++;                                         // Increase the number of books on this instance by 1       
        }
        // if we tried to add a name that wasn't valid. However this won't trigger because of the stockCounter variable after the last add
        else
            cout << "Somehow, this spot is already taken\n";
    }
    else
    {
        cout << "No more room on this shelf to add new books\n";        //  Prints if we tried to add a book but the current value exceeds 11
    }
}

// Prints all books data that are currently on the shelf
void PrintAllBooksOnShelf()
{
    string available = "";
    for (int i = 0; i < StockCounter; ++i)
    {
        books[i].available ? available = "Yes" : available = "No";

        cout << "Shelf ID: " << shelfID << " \tBook number: " << i+1 << "      Book name: " << books[i].name 
            << " \tPages: " << books[i].pages << " \tAvailabile: " << available << endl;
    }
}

// Print the books general data based on a index parameter assuming it is in bounds
void PrintBookByIndex(int index)
{
    // Temp string to hold the avialability string to print out to the user
    string available = " ";

    // If the book we want is available, set the available string to Yes, otherwise it is set to no
    books[index].available ? available = "Yes" : available = "No";

    // Print the information that we want about this book
    if (index >= 0 && index <= 11)
        cout << "Shelf ID: " << shelfID << ". Book number: " << index+1 << ". Book name: " << books[index].name
        << ". Pages: " << books[index].pages << ". Available: " << available << endl;
}

// Prints the usage history of a specific book assuming its index is in bounds
void PrintBookHistory(int bookID)
{
    if (bookID != -1 && bookID <= 11)
        books[bookID].thisBookHistory.PrintHistory();
}

// Prints all of the history of all current books on this shelf only
void PrintAllBookHistory()
{
    for (int i = 0; i < StockCounter; ++i)
    {
        books[i].thisBookHistory.PrintHistory();
    }
}

// Attempts to find a single book based on the name.
bool LocateBook(string BookName)
{
    //cout << endl;
    // Loop through the elements and if the parameter name matches any of the elements name, Simply return true to indicate success.
    for (int i = 0; i <= StockCounter - 1; ++i)
    {
        if (BookName == books[i].name)
            return true;
    }

    // This occurs if we went through all elements and there is no match. In this case print a message and return false to indicate failure.
    cout << BookName << " does not exist on shelf " << shelfID << "." << endl;
    return false;
}

// Attempts to grab a booksID based on that books name.
int GetBookID(string BookName)
{
    int bookID = -1;
    if (LocateBook(BookName))
    {
        for (int i = 0; i < StockCounter; ++i)
        {
            // If a match was found, then break the loop and set the id to i.
            if (BookName == books[i].name)
            {
                bookID = i;
                break;
            }
        }
    }
    return bookID;
}

// Boolean return type based on the filled boolean variable. If ANY book is still available, this will return false. Otherwise it will return true.
bool AllBooksTaken()
{
    for (int i = 0; i < 12; ++i)
    {
        // If a book is available, set filled to false and return immediately
        if (books[i].available)
        {
            filled = false;
            return filled;
        }
        // If a book isn't available, then set the fullindex based on that space to -1, used for ReferenceArrayHelper().
        else
            fullindex[i] = -1;
    }

    // If we got here, this shelf really is full and return true.
    filled = true;
    return filled;
}

// DualReturnType reference funcion that will return a pointer and its size together.
DualReturnType ReferenceArrayHelper()
{
    int SizeCounter(0);                         // SizeCounter reference to determine valid books inside the shelf

    // Loop through the active books on the shelf and if the fullindex based on the loop value i is equal to -1, increase the sizecounter variable.
    // This is to intialize another variable later so that it only contains valid indicies from the shelf.
    for (int i = 0; i < StockCounter; ++i)
    {
        if (fullindex[i] != -1)
            SizeCounter += 1;
    }

    // Assuming that at least one of the elements in the above was valid (SizeCounter >=1), proceed.
    if (SizeCounter >= 1)
    {
        int *indexArray = new int[SizeCounter];             // Create a pointer array that is based on the SizeCounter index;
        int indexCounter = 0;                               // new variable reference that tracks the indices of the indexArray. This is so we can set them as we proceed
                                                            // through the loop based on its own counter

        // Loop through the StockCounter again and if the sizecounter is greater than or equal to 1, proceed with the following
        for (int i = 0; i < StockCounter; ++i)
        {
            // If the fullindex element isn't -1, the proceed.
            if (fullindex[i] != -1)
            {
                if (SizeCounter >= 1)
                {
                    indexArray[indexCounter] = i;           // Set the indexArray element based on the current counter equal to i in the loop to indicate a goood value
                    indexCounter++;                         // Increase the index counter by 1 to move to the next element
                    SizeCounter--;                          // Decrease the counter since we hit a successfull element
                }
                else
                {
                    break;                                  // If the sizecounter has gone below 1, stop since there are no other valid elements
                }
            }
        }

        // Finally return this newly created array, along with the index counter, which is indexArray's size
        return DualReturnType(indexArray, indexCounter);
    }
    else
        return DualReturnType();        // IF we got here, return a default or blank DualReturnType
}

Book books[12];                                 // Array of books in that shelf
string shelfID;                                 // String that containts the name of each shelfID
int StockCounter;                               // The number of books that are actually avialable to use, increases when books are added
bool filled;                                    // If the entire structure is taken
int fullindex[12];                              // Useful when checking out a book. This is so this same book isn't looked at again. (Removed from the pool)
Enter fullscreen mode Exit fullscreen mode

};

The BookGroup class houses all information containing to books and shelves. After the main setup functions and input class, this is the class that is referencing when adding, looking, finding or removing information pertaining to books, the people, and history. It has one private member which is the bookshelf array which is done to prevent data pertaining to it to be easily modified simply from creating an instance of this class alone. Changing data around it requires using the member functions which calls data and functions inside the class pertaining to the objective required.
Itโ€™s members include a constructor, a FindBook Boolean function, a void AddBook function, a void AddBooks function, a getbookshelfID index based on the book name, a GetbookshelfID string function, three printbook variants which prints all of them, one by index, and one by the shelfID, a void CheckoutBook function which takes a person reference and their desired book, a randomBookSelection string helper function which grabs a book without needing to input the name, two bookhistory helper functions which prints book history by shelves, a GetBook Book return type which tries to find a book by its shelfID and spot, and finally a GetBook string function which directly gives the user the name of a book at a certain spot. This one is very long so I wonโ€™t be explaining it here.

I wrote a lot of notes on it though here if you want to go through it yourself:

// The main class that controls everything. Operating as the library's database, this class houses all bookshelves and the bookshelves house the books.
class BookGroup
{
public:
    // Default constructor that sets up the bookshelves
    BookGroup()
    {
        // Create a string that has all of the letters of the alphabet
        string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

Enter fullscreen mode Exit fullscreen mode
    int stringcounter = 0;          // Counter variable that will be used to pull letters out of the alpabet variable

    // Loop based on the total number of shelves (12 in total).
    for (int i = 0; i < 13; ++i)
    {
        // Make the shelf ID a group of two from the alphabet. So shelf 1 = AB, 2 = CD, 3 = EF etc...
        // Uses the stringcounter as a index from the alphabet in groups of 2. So 0 first then 0 + 1 next. Then simply increase the string counter by 2
        // so when we get here again it references the next set of letters. 
        bookshelf[i].shelfID.append(1, alphabet[stringcounter]);
        bookshelf[i].shelfID.append(1, alphabet[stringcounter + 1]);

        stringcounter += 2;
    }

}

// Default destructor which does nothing
~BookGroup()
{

}

// Find a book based on its name. 
bool FindBook(string book)
{
    // holder boolean which is initialized to false
    bool returnvalue = false;

    // Shelf holder that will be changed based on the first letter of the book (parameter)
    int Shelf = 0;

    // Create a single character variable which references the first character inside the book parameter
    char bookcopy = book[0];

    // Capatitalize this letter to avoid errors and to match the switch case below
    bookcopy = toupper(bookcopy);

    // Switches based on the bookcopy variable
    switch (bookcopy)
    {
    case 'A':
        //cout << "Letter A \n";
        break;
    case 'B':
        //cout << "Letter B \n";
        break;
    case 'C':
        //cout << "Letter C \n";
        Shelf = 1;
        break;
    case 'D':
        //cout << "Letter D \n";
        Shelf = 1;
        break;
    case 'E':
        //cout << "Letter E \n";
        Shelf = 2;
        break;
    case 'F':
        //cout << "Letter F \n";
        Shelf = 2;
        break;
    case 'G':
        //cout << "Letter G \n";
        Shelf = 3;
        break;
    case 'H':
        //cout << "Letter H \n";
        Shelf = 3;
        break;
    case 'I':
        //cout << "Letter I \n";
        Shelf = 4;
        break;
    case 'J':
        //cout << "Letter J \n";
        Shelf = 4;
        break;
    case 'K':
        //cout << "Letter K \n";
        Shelf = 5;
        break;
    case 'L':
        //cout << "Letter L \n";
        Shelf = 5;
        break;
    case 'M':
        //cout << "Letter M \n";
        Shelf = 6;
        break;
    case 'N':
        //cout << "Letter N \n";
        Shelf = 6;
        break;
    case 'O':
        //cout << "Letter O \n";
        Shelf = 7;
        break;
    case 'P':
        //cout << "Letter P \n";
        Shelf = 7;
        break;
    case 'Q':
        //cout << "Letter Q \n";
        Shelf = 8;
        break;
    case 'R':
        //cout << "Letter R \n";
        Shelf = 8;
        break;
    case 'S':
        //cout << "Letter S \n";
        Shelf = 9;
        break;
    case 'T':
        //cout << "Letter T \n";
        Shelf = 9;
        break;
    case 'U':
        //cout << "Letter U \n";
        Shelf = 10;
        break;
    case 'V':
        //cout << "Letter V \n";
        Shelf = 10;
        break;
    case 'W':
        //cout << "Letter W \n";
        Shelf = 11;
        break;
    case 'X':
        //cout << "Letter X \n";
        Shelf = 11;
        break;
    case 'Y':
        //cout << "Letter Y \n";
        Shelf = 12;
        break;
    case 'Z':
        //cout << "Letter Z \n";
        Shelf = 12;
        break;
    default:
        cout << "First letter is not A - Z. Error.\n";
        break;
    }

    // Now that we have our shelf index, then attempt to find the book based on that shelf
    bool FoundBook = bookshelf[Shelf].LocateBook(book);

    // If the FoundBook boolean is equal to true, set the returnvalue to true and the same if its the opposite
    FoundBook ? returnvalue = true: returnvalue = false;
    return returnvalue;
}

// Adds a single book based on a int array value. 
void AddBook(string BookName, int *PagesArray, int PageIndex)
{
    int Shelf = GetBookShelfID_Index(BookName);             // Get the bookID based on the bookName

    // If the shelfID isn't equal to negative one proceed normally, otherwise output a error message
    if (Shelf != -1)
        bookshelf[Shelf].AddBook(BookName, PagesArray[PageIndex]);
    else
        cout << "Error in AddBook: Impossible to add " << BookName << " to the list";
}

// Adds a array of strings to the shelf based size of the array
void AddBooks(string *Books, int BookSize, int *PageArray)
{
    for (int Shelf, i = 0; i < BookSize; ++i)
    {
        Shelf = GetBookShelfID_Index(Books[i]);
        if (Shelf != -1)
        {
            bookshelf[Shelf].AddBook(Books[i], PageArray[i]);
        }
        else
            break;
    }
}

// Get the shelf index based on the first letter of the incoming string. Return that index. If the later cannot be found, return -1
int GetBookShelfID_Index(string Name)
{

    Name[0] = toupper(Name[0]);
    if (Name[0] == 'A' || Name[0] == 'B')
        return 0;
    else if (Name[0] == 'C' || Name[0] == 'D')
        return 1;
    else if (Name[0] == 'E' || Name[0] == 'F')
        return 2;
    else if (Name[0] == 'G' || Name[0] == 'H')
        return 3;
    else if (Name[0] == 'I' || Name[0] == 'J')
        return 4;
    else if (Name[0] == 'K' || Name[0] == 'L')
        return 5;
    else if (Name[0] == 'M' || Name[0] == 'N')
        return 6;
    else if (Name[0] == 'O' || Name[0] == 'P')
        return 7;
    else if (Name[0] == 'Q' || Name[0] == 'R')
        return 8;
    else if (Name[0] == 'S' || Name[0] == 'T')
        return 9;
    else if (Name[0] == 'U' || Name[0] == 'V')
        return 10;
    else if (Name[0] == 'W' || Name[0] == 'X')
        return 11;
    else if (Name[0] == 'Y' || Name[0] == 'Z')
        return 12;
    else
    {
        cout << "Error in the bookshelfID grabber function" << endl;
        cout << Name << endl;
        return -1;
    }
}

// string return of the bookID based on the Name of a book
string GetBookShelfID(string Name)
{
    // Get and Set the index variable to the index based off the name
    int ShelfID_Index = GetBookShelfID_Index(Name);

    // If index is within bounds then return the ID, otherwise return NULL
    if (ShelfID_Index >= 0 && ShelfID_Index <= 12)
        return bookshelf[ShelfID_Index].shelfID;
    else
        return "NULL";
}

// Simply prints the books in order by shelves. Calls the shelf variables PrintAllBooksOnShelf 
void PrintAllBooks()
{
    int AllShelves = sizeof(bookshelf) / sizeof(bookshelf[0]);      // Size that we'll loop through
    for (int i = 0; i < AllShelves; ++i)
    {
        bookshelf[i].PrintAllBooksOnShelf();                    // Prints all books per shelf in the array
    }
}

// Print all the books based on a desired shelf
void PrintBooksByShelf(int shelf)
{
    if (shelf >= 0 && shelf <= 12)
        bookshelf[shelf].PrintAllBooksOnShelf();            // Prints all books on the shelf that we want
}

// Print a specific book with its shelf and slot index
void PrintBookByShelfAndID(int shelf, int bookID)
{
    bookshelf[shelf].PrintBookByIndex(bookID);
}

// The actual function that checks out books from the shelevs. Requires a guest parameter, and that guests desired book to be checked out. 
// This function will continue until a successful book has been removed. The library has been adjusted to accomodate this.
void CheckoutBook(Person &guest, string desiredBook)
{
    // Guest name cannot be blank
    if (guest.name != "Empty")
    {
        // Guest must be able to at least check out one book
        if (guest.BookLimit >= 1)
        {
            // attempts to find the guests desired book
            bool FoundBook = FindBook(desiredBook);

            // If it is found proceed.
            if (FoundBook)
            {
                // Gathers the shelfID and the bookID based on the desiredbook
                int Shelf = GetBookShelfID_Index(desiredBook);
                int bookID = bookshelf[Shelf].GetBookID(desiredBook);

                // If the shelf and bookIDs are -1(failure) then proceed.
                if (Shelf != -1 && bookID != -1)
                {
                    // Checker to detrmine if all the books on the desired shelf are empty or not
                    bool AllbooksTaken = bookshelf[Shelf].AllBooksTaken();

                    // If its NOT TRUE that all the books are gone, proceed
                    if (!AllbooksTaken)
                    {
                        // If the book that we want is still available, proceed
                        if (bookshelf[Shelf].books[bookID].available)
                        {
                            // Add this person to book they wants history. Reduce this persons booklimit by 1 Add the book to their reference. Finally 
                            // reduce the number of the copies of the book by calling the books removecopy function
                            bookshelf[Shelf].books[bookID].thisBookHistory.AddHistory(guest);
                            guest.BookLimit--;
                            guest.AddBook(desiredBook);
                            bookshelf[Shelf].books[bookID].RemoveCopy();
                        }
                        // This shouldn't execute but is set just in case. Simply recalls this same function again but sets SelectRandomBook() as the second
                        // argument (desiredBook) and keeps the guest reference the same. So the guest is simply searching for another book
                        else
                        {
                            cout << "Sorry, " << desiredBook << " is not currently available" << endl;
                            return CheckoutBook(guest, SelectRandomBook() );
                        }
                    }
                    else
                        cout << "Sorry all books have been taken on this shelf. \n";
                }
                else
                    cout << "Program error. Shelf and or bookID were not initialized properly.\nShelfID: " << 
                    Shelf << ". BookID: " << bookID << ". Desired book: " << desiredBook << endl; // Either there was a failure or something went wrong
            }
            else
                cout << "Sorry. " << desiredBook << " doesn't exist in this library!" << endl;
        }
        else
            cout << "Sorry, you can't check out any more books" << endl;
    }
    else
        cout << "Error, guest must have a valid name" << endl;
}

// Attempts to return a random book name on one of the shelves. This function will repeat until a reliable name is found.
string SelectRandomBook()
{
    string bookName = "Empty";                      // Empty bookName holder

    int shelfID(rand() % 13),                       // shelfID picks a random shelf between 0 and 13
    bookID;                                         // bookID variable that will be used for later

    if (shelfID != 13)
    {
        bool TakenBooks = bookshelf[shelfID].AllBooksTaken();       // create a bool to hold whether or not all books on the desired shelf are taken or not
        // If Taken isn't true (or isn't full capacity) then proceed to this branch
        if (!TakenBooks)
        {
            DualReturnType intType = bookshelf[shelfID].ReferenceArrayHelper();     // Uses DualReturnType structure to hold a reference based on the Reference
            // Array Helper function with the shelf id and bookshelf.

            // If the array pointer inside our dualtype variable (intType) isn't NULL, proceedd
            if (intType.int_Array != NULL)
            {
                // Initialize this variable based on size we pased into our intType variable
                bookID = rand() % intType.int_size;

                // Important because we created our own array specifically with its own size and values. We get the value inside at the point of the
                // randomly generated bookID
                int originalBookID = intType.int_Array[bookID];

                // If the bookshelf we want and the book inside it that we want are available, proceed.
                if (bookshelf[shelfID].books[originalBookID].available)
                {
                    bookName = GetBookName(shelfID, originalBookID);
                }
                else
                {
                    // If we get here, delete the array reference we set and call this function again(recursion). This will happen until we get a book that isn't taken.
                    // Note: the TakenBooks variable prevents us from needlessly repeating the search
                    delete intType.int_Array;
                    return SelectRandomBook();
                }
                // This continues if the book we want is avaialable
                delete intType.int_Array;
            }
            // This shouldn't happen but is here just in case. Simply recalls this function again
            else
            {
                cout << "Retrying book selection. " << endl;
                return SelectRandomBook();
            }
        }
    }
    return bookName;        // If we got here that means the selection was a success. The function ends normally
}

// Simply outputs the history of a desiredbook if it exists on any of the shelves. Different from PrintBooks() which only prints all books on the shelves
void PrintBookHistoryByBook(string Book)
{
    if (Book != "")
    {
        bool Found = FindBook(Book);                                // Bool reference to hold whether or not the book exists on any of the shelves
        int BookSlot(-1);                                           // Slot reference initialized to -1 error

        // If the book was found continue, if not ouput a error message
        if (Found)
        {
            int ShelfID = GetBookShelfID_Index(Book);               // ShelfID variable class the GetShelfID index function based on the book
            BookSlot = bookshelf[ShelfID].GetBookID(Book);          // BookSlot variable class which uses the bookshelf variable based on shelfID to find a book. 
                                                                    // Basically the shelf is used to quicken search time

            // If the bookslot value isn't negative 1 then, then call PrintBookHistory based on the slot we just received
            if (BookSlot != -1)
                bookshelf[ShelfID].PrintBookHistory(BookSlot);
        }
        else
            cout << Book << " can't be found. (PrintBookHistoryByBook)\n";
    }
    else
        cout << "Sorry, you must insert a valid name into PrintBookHistoryByBook().\n";
}

// Prints the history of the books in order from the first shelf to the last
void PrintAllBookHistory()
{
    for (int i = 0; i <= 12; ++i)
    {
        bookshelf[i].PrintAllBookHistory();
    }
}

// Gets a book based on a shelfID parameter and on spot parameter.
Book GetBook(int ShelfID, int spot)
{
    if (ShelfID >= 0 && ShelfID <= 12 && spot >= 0 && spot <= 11)
    {
        return bookshelf[ShelfID].books[spot];
    }
    // If the parameters are out of bounds, then simply return a empty book
    else
    {
        cout << "Spot or shelfId inside GetBook() is out of bounds\n";
        return Book();
    }
}

// Does the same as the above but simply gives the name directly instead of the entire book
string GetBookName(int shelfID, int bookspot)
{
    // Simply ensures that the shelfID and bookspot variables are within reasonable bounds (0 - 12) & (0 - 11) respectively
    if (shelfID >= 0 && shelfID <= 12 && bookspot >= 0 && bookspot <= 11)
    {
        return bookshelf[shelfID].books[bookspot].name;
    }
    else
    {
        cout << "ShelfID or bookspot inside GetBookName() is out of bounds";
        return "NULL";
    }
}
Enter fullscreen mode Exit fullscreen mode
private:
    Bookshelf bookshelf[13];                                    // array of shelves A-Z AB, CD, EF, etc. Is private so the user can't do things directly
};
Enter fullscreen mode Exit fullscreen mode

Next up is the UserInputHelperClass. This class only has two functions, which are the HandlePrintBooks and the HandlePrintBooksHistory functions. The HandlePrintBooks function goes in one of three directions: it will print all books by shelves, allow the user to search for a book using the name of the book as well as its shelfID, and finally will terminate on request. It takes in the BookGroup as a parameter. As for the HandlePrintHistory function, it allows you to see the history of all books or a specific one assuming it exists and also allows the user to go back to the main menu upon request. The function allows this to repeat over and over using a loop until a certain key is pressed.

Here is the code for this here:

class UserInputHelperClass
{
public:
    // Handles the print case 1 from the center. This function will do 1 of 3 actions: 1.) Terminate upon user request 2.) Find a specific book by asking for a
    // shelfID and a bookID 3.) PrintAll books on all shelves
    void HanldePrintingAllBooks(BookGroup mainRef)
    {
        cout << "Would you like to print all books or just one? A for all, 1 for specific, N to return\n";
        char input(' ');

Enter fullscreen mode Exit fullscreen mode
    // First ask the user for one of three keys, 1, A or N. If the user doesn't do this this will repeat until then
    while (input != '1' && input != 'A' && input != 'N')
    {
        // Asks for only a single key only
        input = _getch();

        // Digit checkers so digits won't proceed through. If the key that the user has entered is a lowercase letter, then simply raise it to uppercase
        if (!isdigit(input) )
            if (islower(input))
                input = toupper(input);

        // Simply repeats the same string as the above but with a error message
        if (input != 'A' && input != '1' && input != 'N')
            cout << "Sorry please try again. A for All books, 1 for specific, N for termination\n";
    }

    // Assuming one of the 3 keys above was given, then proceed to execute based on their action.
    switch (input)
    {
        // The first case if the user wants to manually select a book based on index
        case '1':
        {
            cout << "Please enter a shelfID between 1 to 13 or N to terminate" << endl;
            int indexShelf = 0;             // initialized bookshelf ID
            int indexBook = 0;                  // initialized book ID
            bool continueLoop = false;          // Boolean to determine whether or not we need to keep asking the user for information
            string holdervalue("");             // will hold the input of our user

            // This will continue on until the user either quits, or they enter a correct value
            while (!continueLoop)
            {
                // ask for user input
                cin >> holdervalue;

                // If the value the user just entered is less than or equal to 2 in length, i.e a number (0 - 99) or two letters, proceed
                if (holdervalue.size() <= 2)
                {
                    // The case for if the size is only one character long
                    if (holdervalue.size() == 1)
                    {
                        // If the only character is a digit, then set our indexShelfID to what that digit is, if it isn't a digit, then captialize the letter inside
                        if (isdigit(holdervalue[0]))
                        {
                            indexShelf = holdervalue[0] - '0';
                        }
                        else
                            holdervalue[0] = toupper(holdervalue[0]);
                    }

                    // The case for if the size is 2, not 1 and not 0
                    else
                    {
                        // If the first character in the input string is a digit, then set the indexShelf variable to this value. Remove the null character to convert
                        // it to a digit. If we got here then if the second character in the string is also a digit, then mutliply the value that's in the indexShelf
                        // already by ten, and then add this second value to the string. This is ensure that the number is what was originally typed. I.e, 77 Index = 7.
                        // 7 * 10 = 70 + 7 = 77
                        if (isdigit(holdervalue[0]))
                        {
                            indexShelf = holdervalue[0] - '0';

                            if (isdigit(holdervalue[1]))
                            {
                                indexShelf *= 10;
                                indexShelf += holdervalue[1] - '0';
                            }
                        }

                    }

                    // Start with the null termination first to avoid early cancellation by some other value, and include certain types. If this is true, exit the function
                    if (holdervalue == "N" || holdervalue == "NN" || holdervalue == "nn")
                    {
                        cout << "Terminating" << endl;
                        return;
                    }
                    // If the indexShelf is greater than or equal to 14, then it is too high. Print a string and continue
                    else if (indexShelf >= 14)
                        cout << "Number too high. Please enter a shelfID between 1 to 13 or N to terminate" << endl;
                    // If the indexShelf value is less than or equal to 0, then this value is too low. Print a string and continue
                    else if (indexShelf <= 0)
                        cout << "Number too low. Please enter a shelfID between 1 to 13 or N to terminate" << endl;
                    // If all above conditions were not triggered, then the user entered a valid value. Set the continueLoop to true (!false) and stop the loop.
                    // Finally decrease by 1, this is to ensure that the values aren't out of bounds
                    else
                    {
                        cout << "Valid input." << endl;
                        continueLoop = !(continueLoop);
                        indexShelf -= 1;
                    }
                }
                // Then the size is too long. Simply print a string and continue like the above conditions
                else
                    cout << "Value too long. Please enter a valid shelfID between 1 to 13 or N to terminate" << endl;
            }

            // Reset the basic values again for the bookID
            continueLoop = false;
            holdervalue = " ";

            cout << "Please enter a bookID between 1 to 12 or N to terminate" << endl;

            // Repeats the same above while loop but it does this for a bookID, and the max limit is decreased by 1 to match our bookID limit and prevent out of bounds errors
            while (!continueLoop)
            {
                cin >> holdervalue;

                // If the value the user just entered is less than or equal to 2 in length, i.e a number (0 - 99) or two letters, proceed
                if (holdervalue.size() <= 2)
                {
                    // The case for if the size is only one character long
                    if (holdervalue.size() == 1)
                    {
                        // If the only character is a digit, then set our indexBook to what that digit is, if it isn't a digit, then captialize the letter inside
                        if (isdigit(holdervalue[0]))
                        {
                            indexBook = holdervalue[0] - '0';
                        }
                        else
                            holdervalue[0] = toupper(holdervalue[0]);
                    }

                    // The case for if the size is 2, not 1 and not 0
                    else
                    {
                        // If the first character in the input string is a digit, then set the indexBook variable to this value. Remove the null character to convert
                        // it to a digit. If we got here then if the second character in the string is also a digit, then mutliply the value that's in the indexBook
                        // already by ten, and then add this second value to the string. This is ensure that the number is what was originally typed. I.e, 77 Index = 7.
                        // 7 * 10 = 70 + 7 = 77
                        if (isdigit(holdervalue[0]))
                        {
                            indexBook = holdervalue[0] - '0';

                            if (isdigit(holdervalue[1]))
                            {
                                indexBook *= 10;
                                indexBook += holdervalue[1] - '0';
                            }
                        }

                    }

                    // Start with the null termination first to avoid early cancellation by some other value, and include certain types. If this is true, exit the function
                    if (holdervalue == "N" || holdervalue == "NN" || holdervalue == "nn")
                    {
                        cout << "Terminating" << endl;
                        return;
                    }
                    // If the indexBook is greater than or equal to 14, then it is too high. Print a string and continue
                    else if (indexBook >= 13)
                        cout << "BookID too high. Please enter a shelfID between 1 to 13 or N to terminate" << endl;
                    // If the indexBook value is less than or equal to 0, then this value is too low. Print a string and continue
                    else if (indexBook <= 0)
                        cout << "BookID too low. Please enter a shelfID between 1 to 13 or N to terminate" << endl;
                    // If all above conditions were not triggered, then the user entered a valid value. Set the continueLoop to true (!false) and stop the loop. Then 
                    // indexBook is decremented by 1 to fit the loop.
                    else
                    {
                        cout << "Valid bookID." << endl;
                        continueLoop = !(continueLoop);
                        indexBook -= 1;
                    }
                }
            }

            // This line is for confirmation only
            cout << "IndexShelf: " << indexShelf << " BookID: " << indexBook << endl;

            // Call the PrintBookByShelfAndID helper function to take care of the printing of the book we want
            mainRef.PrintBookByShelfAndID(indexShelf, indexBook);
            break;
        }
        case 'A':
        {
            mainRef.PrintAllBooks();                // In this case simply print all the books in the entire library
            cout << "Returning to center\n\n";
            break;
        }
        case 'N':
            cout << "Returning." << endl;
            break;
    }
}

// Function that prints the books history of either all books or a desired book
void HandlePrintingBookHistory(BookGroup &mainRef)
{
    cout << "Would you like to enter the name of a book or the ID? 1 for bookID, A for all books, F to search manually , N to return. \n";

    // Initializes basic variables that we will use for this function
    bool continueLoop = false;
    bool innerloop = false;
    string bookName;
    char input = ' ';

    // Initial placement loop to determine the outcome based on user value. If it is valid we will either terminate or proceed deeper into the function
    while (!continueLoop)
    {
        // Asks for only a single key press
        input = _getch();

        // If the input isn't a number that we want, then captialize it. Note: This works because we only gather a single key press, so it is impossible for the user to enter 
        // a number that is greater than 9 or less than 0. Negatives can't be counted as the '-' is a special character
        if (input <= 0 || input >= 10)
            input = toupper(input);

        // Switch statement based on that input value the user just entered
        switch (input)
        {
        // Easiest case scenario. Simply print the history of all books and then return
        case 'A':
        {
            mainRef.PrintAllBookHistory();
            cout << "\n";
            continueLoop = !continueLoop;
            cout << "Returning.\n\n";
            return;
        }
        // If this key is pressed, simply proceed forward as this is a main case
        case '1':
            continueLoop = !continueLoop;
            break;
        // This key is also a special case we will proceed again
        case 'F':
            continueLoop = !continueLoop;
            break;
        // This key is a termination key but unlike 'A' we don't print anything and just exist
        case 'N':
            cout << "Returning. \n\n";
            continueLoop = !continueLoop;
            return;
            break;
        // User didn't print a valid key. Will print a error message and then go back to the top which repeats this entire procees until a valid key is given
        default:
            cout << "Sorry, you didn't enter a valid character. Please enter 1 to use bookID, A for all books F to search for the bookname, or N to return. \n";
            break;
        }
    }

    continueLoop = !continueLoop;
    // Placeholder values that hold shelfID and bookID values
    int shelfID(-1);
    int bookID(-1);
    // A string that we will use to hold the users input from now on
    string bookdetector("");

    // The main loop or outer loop that will continue to ask for input until user termination or search failure
    while (!continueLoop)
    {
        // Switch statement based on input
        switch (input)
        {
        case '1':
        {
            cout << "Please enter a shelfID first between 1 to 13.\n";
            cin >> bookdetector;                                        // ask for user input

            innerloop = false;
            // Our loop inside of the main loop. This innerloop is attempting to ask the user for a valid shelfID between 1 to 13 or for N to terminate
            while (!innerloop)
            {
                // Checker for the N key. If pressed then it will break the entire function and return.
                if (bookdetector == "N" || bookdetector == "n")
                {
                    cout << "Returning.\n\n";
                    innerloop = true;
                    return;
                }

                // If the value the user entered is 2 keys or less continue
                if (bookdetector.size() <= 2)
                {
                    // If the user only entered 1 key
                    if (bookdetector.size() == 1)
                    {
                        // If that key is a digit, then set the shelfID to this value. If it isn't a digit, then set the users input to E for error handling
                        if (isdigit(bookdetector[0]))
                        {
                            shelfID = bookdetector[0] - '0';
                        }
                        else
                            bookdetector = "E";
                    }
                    // The user has entered a size that is equal to 2. 
                    else
                    {
                        // If the first key is a digit, then set shelfID to that digit
                        if (isdigit(bookdetector[0]))
                        {
                            shelfID = bookdetector[0] - '0';

                            // If the second key is a digit, then multiply the shelfID by 10, and then add the second key to the shelfID
                            if (isdigit(bookdetector[1]))
                            {
                                shelfID *= 10;
                                shelfID += bookdetector[1] - '0';
                            }
                            // If the second key isn't a digit, then set the users value to E for error handling
                            else
                                bookdetector = "E";
                        }
                        // If the first key isn't a digit, then set the users value to E for error handling
                        else
                            bookdetector = "E";
                    }

                    // Termination key
                    if (bookdetector == "N" || bookdetector == "n")
                    {
                        cout << "Returning.\n\n";
                        return;
                    }
                    // Error handling key. Will simply print a error message and let the flow proceed normally thus resulting in a repeat
                    else if (bookdetector == "E")
                    {
                        cout << "Invalid value. ShelfID must be between 1 and 13. Retry or press N to terminate.\n";
                    }

                    // Another error case. This happens if the user entered a value that doesn't corresspond to the program. Prints 2 sepearte message depending on the case
                    // and lets the program flow normally resulting in a repeat
                    else if (shelfID <= 0 || shelfID >= 14)
                    {
                        if (shelfID == 0)
                            cout << "Error, shelfID is too low. Please enter a value between 1 to 13 or N to terminate.\n";
                        else
                            cout << "Error, shelfID is too high. Please enter a value between 1 to 13 or N to terminate\n";
                    }

                    // If the user got here they successflly entered a valid shelfID. Decrease it by 1 to fit our loop, since value must be between 1 to 13,
                    // set the innerloop to true now, and then break the flow of the innerLoop which stops repetition
                    else
                    {
                        cout << "Success!! " << shelfID << endl;
                        shelfID -= 1;
                        innerloop = !innerloop;
                        break;
                    }
                }
                // User entered a value of 3 or above. These are not accepted
                else
                {
                    cout << "Value entered is too long.\nPlease enter a shelfID between 1 to 13 or N to trminate.\n";
                }

                // Asks the user for input again at the end if this loop isn't broken
                cin >> bookdetector;
            }

            // reset the innerloop so we will use it again
            innerloop = false;
            cout << "Please print a bookID between 1 to 12 or press N to terminate\n";
            // Exactly the same as the above innerloop but with the book condition instead. So values are between 1 to 12
            while (!innerloop)
            {
                cin >> bookdetector;

                if (bookdetector == "N" || bookdetector == "n")
                {
                    cout << "Returning.\n\n";
                    innerloop,continueLoop = true;
                    return;
                }

                if (bookdetector.size() <= 2)
                {
                    if (bookdetector.size() == 1)
                    {
                        if (isdigit(bookdetector[0]))
                        {
                            bookID = bookdetector[0] - '0';
                        }
                        else
                            bookdetector = "E";
                    }
                    else
                    {
                        if (isdigit(bookdetector[0]))
                        {
                            bookID = bookdetector[0] - '0';

                            if (isdigit(bookdetector[1]))
                            {
                                bookID *= 10;
                                bookID += bookdetector[1] - '0';
                            }
                            else
                                bookdetector = "E";
                        }
                        else
                            bookdetector = "E";
                    }

                    if (bookdetector == "N" || bookdetector == "n")
                    {
                        cout << "Returning.\n\n";
                        innerloop = true;
                        continueLoop = true;
                        return;
                    }

                    else if (bookdetector == "E")
                    {
                        cout << "Invalid value. bookID must be between 1 and 12. Retry or press N to terminate.\n";
                    }

                    else if (bookID <= 0 || bookID >= 13)
                    {
                        if (bookID == 0)
                            cout << "Error, bookID is too low. Please enter a value between 1 to 12 or N to terminate.\n";
                        else
                            cout << "Error, bookID is too high. Please enter a value between 1 to 12 or N to terminate\n";
                    }

                    else
                    {
                        cout << "Success!! " << shelfID << endl;
                        bookID -= 1;
                        innerloop = !innerloop;
                        break;
                    }
                }
                else
                {
                    cout << "Value entered is too long.\nPlease enter a bookID between 1 to 12 or N to trminate.\n";
                }

            }

            // Create a book reference so we can check if the shelfID and bookID found a valid book
            Book myBook = mainRef.GetBook(shelfID, bookID);

            // If the book we just created isn't empty, then print that books history, otherwise print a error message. Regardless of outcome, terminate this function
            if (!myBook.emptyBook() )
                mainRef.PrintBookHistoryByBook(myBook.name);
            else
                cout << "Sorry, but that book doesn't exist. Returning.\n\n";

            continueLoop = true;
            break;
        }
        // The case when the user presses the F key. Allows them to find the book by its name
        case 'F':
        {
            cout << "Please enter the name of the desired book.\n";
            std::getline(std::cin, bookName);

            // If the bookName the user entered exists inside the main structre, then print that books history and terminate this loop
            if (mainRef.FindBook(bookName))
            {
                mainRef.PrintBookHistoryByBook(bookName);
                continueLoop = !continueLoop;
            }
            // If the bookname isn't valid, basically ask them if they'd like to try again. If the input they entered isn't a number between the values we want, captialize it.
            else
            {
                cout << "Would you like to try again (F) or terminate(N)?\n";
                input = _getch();
                if (input < 0 || input >= 10)
                    input = toupper(input);
            }

            break;
        }
        case 'N':
            cout << "Returning.\n\n";
            continueLoop = !continueLoop; 
            return;
        default:
        {
            cout << "Sorry, invalid character entered. Please type 1 to search by 1, F to search by name, or N to terminate.\n";
            input = _getch();
            if (input < 0 || input >= 10)
                input = toupper(input);
        }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

};

The next function is the setUpBookNames function. This is a really quick and simple one. It takes a parameter reference to an active BookGroup instance, and adds a list of random video game names as book names and a random set of pages by calling the AddBooks function inside the BookGroup parameter.

It looks like this:

ref.AddBooks(Alphabet, books_Ref, Pages);
Enter fullscreen mode Exit fullscreen mode

The Alphabet variable a string array of names like โ€œCrash Bandicootโ€, โ€œJack and Daxterโ€, โ€œHorizon Zero Dawnโ€ and the books_Ref being an array of integers that have random values between 20 and 1020. Here is one index of that, although all of them look exactly the same:

rand() % 1000 + 20
Enter fullscreen mode Exit fullscreen mode

Next up is the InitializeLibraryGuests function which takes a Person pointer type as its only argument. I use this space to set up two array strings, both being considerable sizes; one of only first names, the other only as last names. At the end of this function, I combine them into one name by passing them both together as a argument in the Person pointer argument and also increasing the static integer ID by one for every successful name and passing that in as well. The FirstNames and LastNames string arrays are based on the LibaryGuestSize variable which remember is a global one. By generating a random index between that range, I can easily get a index which I used to directly plug into both respective string arrays.

It looks a bit like this:

// Go through a loop based on the guest size and setup the guests values
    for (int i = 0; i < LibraryGuestSize; ++i)
    {
        randomFirstIndex = rand() % LibraryGuestSize;       // Generate a random number between 0 and LibraryGuestSize(156)
        randomLastIndex = rand() % LibraryGuestSize;        // Same as the above but inside lastindex

Enter fullscreen mode Exit fullscreen mode
    // Make the guests name a full value based on random selections between the first name and last names. The indicies we made are used as selectors
    Guests[i].name = FirstNames[randomFirstIndex] + " " + LastNames[randomLastIndex];

    // Increase the static integer value by 1 so every guests has a incremeting ID value
    ID += 1;
    Guests[i].GuestID = ID;         // Set the guests' ID to the static ID.
}
Enter fullscreen mode Exit fullscreen mode

}

The final piece that needs to be discussed is the one that puts everything together and that is the main function. The main function first creates the array of individuals for the simulation:

Person *LibraryGuests = new Person[LibraryGuestSize];
Enter fullscreen mode Exit fullscreen mode

Then it setups them up for use by calling the InitializeLibraryGuest function like so:

InitializeLibraryGuests(LibraryGuests);
Enter fullscreen mode Exit fullscreen mode

And then creating a BookGroup reference and calling the setUpBookNames using that handler.

Following this, the LibraryGuests variable that was just made is added to the BookGroup handler along with a random book in a for loop for easy access. That code looks like this:

string myBook;
myBook = mainHandle.SelectRandomBook();                     // Selects a random book from somewhere on the shelf and returns its value
            mainHandle.CheckoutBook(LibraryGuests[i], myBook);          // Makes it so that every user checks out a book
Enter fullscreen mode Exit fullscreen mode

Finally, a while loop is used for different selection cases of numbers by checking input with a character holder variable and the UserInputHelperClass which handles all additional cases and returns here when completed or desired.

Postmortem
The biggest thing that I would do differently if doing this again is change the way that data is pulled out in terms of books. While its fine the way that it is now, I think things would have been better if Iโ€™d just used a hash table for keys and indices which I somewhat did but not in the way I liked. I chose not to use the LinkedList that I added because it didnโ€™t make sense to me at the time, constantly deleting and removing nodes since they are always static anyway. However, I could have simply cleared the data inside the node until it was active. Not disappointed in what I did, just wondering if I could have done this differently. Originally, I wanted to take the books printed by the BookHistory and add them to a file that could be easily seen. Unfortunately, I didnโ€™t end up doing this and I wish I would have. Would have been cool to see because the results arenโ€™t static. Another thing I wanted to do was make this so that the data is a bit more flexible. By flexible I simply mean that users can add books and or names to the database instead of it being hard coded like it is now. Despite these drawbacks, Iโ€™m still satisfied with the way it turned out and it can definitely be improved upon.
This was quite a long-detailed explanation and I hope that it wasnโ€™t too bad in terms of length. You can visit my GitHub page if youโ€™d like to try this program out for yourself here.

Thank you for reading if you made it this far and feel free to check out some of my other coding work.

Sincerely,

Christian Banks

Top comments (0)

๐ŸŒš Browsing with dark mode makes you a better developer.

It's a scientific fact.