Library Simulation
CBanks901
Posted on March 8, 2022
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;
}
// 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;
}
};
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 ©)
{
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;
}
//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
};
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
// 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.
}
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.
};
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";
}
// 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
};
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;
}
// 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)
};
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";
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";
}
}
private:
Bookshelf bookshelf[13]; // array of shelves A-Z AB, CD, EF, etc. Is private so the user can't do things directly
};
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(' ');
// 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);
}
}
}
}
};
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);
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
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
// 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.
}
}
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];
Then it setups them up for use by calling the InitializeLibraryGuest function like so:
InitializeLibraryGuests(LibraryGuests);
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
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
Posted on March 8, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.