Programming lesson
Building a Tavern in C++: Recursive Realms & OOP Adventures
Learn how to implement a Tavern class using C++ inheritance, operator overloading, and ArrayBag. This tutorial guides you through modifying Character, ArrayBag, and building a Tavern with level sums, enemy counts, and reports.
Welcome to the Tavern: Your C++ OOP Quest Begins
In this algorithmic adventure, you'll build a Tavern class—a subclass of ArrayBag that holds Character objects. This project is a rite of passage for CSCI 235 students, blending object-oriented programming with data structures. Think of it as designing the backend for a fantasy RPG like Baldur's Gate 3 or World of Warcraft, where characters gather, battle, and level up. By the end, you'll have a fully functional tavern that tracks adventurers, enemies, and their stats.
Why This Matters: From School Projects to Game Dev
Understanding inheritance and operator overloading is crucial for any C++ developer. Whether you're building a game AI or a finance app, these concepts help you write reusable, clean code. For example, the viral app Character.AI uses similar OOP principles to manage thousands of conversational agents. In this tutorial, you'll apply these skills to a fantasy tavern—a perfect blend of C++ programming and game design.
Task 1: Modify the Character Class
First, you'll add operator== and operator!= to the Character class. Two characters are equal if they share the same name, race, level, and enemy status. This is like checking if two players in a League of Legends match have the same champion, rank, and role.
bool Character::operator==(const Character& rhs) const {
return (name_ == rhs.name_ &&
race_ == rhs.race_ &&
level_ == rhs.level_ &&
isEnemy_ == rhs.isEnemy_);
}
bool Character::operator!=(const Character& rhs) const {
return !(*this == rhs);
}Notice the const reference parameter and const member function. This ensures you don't accidentally modify the objects. Also add a display() method that prints character info in a formatted string.
void Character::display() const {
std::cout << name_ << " is a Level " << level_ << " " << race_ << ". Vitality: " << vitality_ << " Max Armor: " << armor_ << " ";
if (isEnemy_) std::cout << "They are an enemy.";
else std::cout << "They are not an enemy.";
std::cout << std::endl;
}Task 2: Enhance the ArrayBag Class
Next, you'll add two operators to ArrayBag: operator/= (combine without duplicates) and operator+= (combine with duplicates). This is like merging two inventory lists in Minecraft—one mode stacks identical items, the other keeps them separate.
template<typename ItemType>
void ArrayBag<ItemType>::operator/=(const ArrayBag<ItemType>& other) {
for (int i = 0; i < other.getCurrentSize(); ++i) {
if (!contains(other.items_[i])) {
add(other.items_[i]);
}
}
}
template<typename ItemType>
void ArrayBag<ItemType>::operator+=(const ArrayBag<ItemType>& other) {
for (int i = 0; i < other.getCurrentSize() && getCurrentSize() < DEFAULT_CAPACITY; ++i) {
add(other.items_[i]);
}
}These operators are essential for data structure manipulation. In a school project, you might use them to merge student records or game scores.
Task 3: Implement the Tavern Class
Now the main event: the Tavern class inherits from ArrayBag<Character>. It tracks two private members: levelSum_ and enemyCount_. These are updated every time a character enters or exits.
Private Members and Constructor
class Tavern : public ArrayBag<Character> {
private:
int levelSum_;
int enemyCount_;
public:
Tavern() : ArrayBag<Character>(), levelSum_(0), enemyCount_(0) {}
// ...
};Entering and Exiting the Tavern
The enterTavern method adds a character and updates sums. Think of it like a Twitch stream where a new viewer joins—the viewer count and average level change.
bool Tavern::enterTavern(const Character& entrant) {
if (add(entrant)) {
levelSum_ += entrant.getLevel();
if (entrant.isEnemy()) enemyCount_++;
return true;
}
return false;
}
bool Tavern::exitTavern(const Character& evictee) {
if (remove(evictee)) {
levelSum_ -= evictee.getLevel();
if (evictee.isEnemy()) enemyCount_--;
return true;
}
return false;
}Notice we use the add and remove methods inherited from ArrayBag. This is the power of inheritance in C++—you reuse tested code.
Calculating Stats: Level Sum, Average, Enemy Count
These methods are straightforward. calculateAvgLevel rounds to the nearest integer using std::round.
int Tavern::getLevelSum() const { return levelSum_; }
int Tavern::getEnemyCount() const { return enemyCount_; }
int Tavern::calculateAvgLevel() const {
if (getCurrentSize() == 0) return 0;
return static_cast<int>(std::round(static_cast<double>(levelSum_) / getCurrentSize()));
}
double Tavern::calculateEnemyPercentage() const {
if (getCurrentSize() == 0) return 0.0;
double pct = 100.0 * enemyCount_ / getCurrentSize();
return std::round(pct * 100.0) / 100.0; // round to 2 decimal places
}Race Tally and Tavern Report
The tallyRace method counts characters of a given race. Use a loop and compare strings. The tavernReport method outputs a formatted summary—perfect for debugging or displaying in a game UI.
int Tavern::tallyRace(const std::string& race) const {
int count = 0;
for (int i = 0; i < getCurrentSize(); ++i) {
if (items_[i].getRace() == race) count++;
}
return count;
}
void Tavern::tavernReport() const {
std::cout << "Humans: " << tallyRace("HUMAN") << std::endl;
std::cout << "Elves: " << tallyRace("ELF") << std::endl;
std::cout << "Dwarves: " << tallyRace("DWARF") << std::endl;
std::cout << "Lizards: " << tallyRace("LIZARD") << std::endl;
std::cout << "Undead: " << tallyRace("UNDEAD") << std::endl;
std::cout << "The average level is: " << calculateAvgLevel() << std::endl;
std::cout << std::fixed << std::setprecision(2) << calculateEnemyPercentage() << "% are enemies." << std::endl;
}Testing Incrementally: The Secret to Success
Don't write all code at once! Implement and test each method. For example, test enterTavern by adding a character and checking getLevelSum. Use a main.cpp with multiple cases:
int main() {
Tavern tavern;
Character hero("Aragorn", "HUMAN", 10, 100, 50, false);
Character villain("Sauron", "UNDEAD", 20, 200, 100, true);
tavern.enterTavern(hero);
tavern.enterTavern(villain);
std::cout << "Level sum: " << tavern.getLevelSum() << std::endl; // 30
std::cout << "Enemy count: " << tavern.getEnemyCount() << std::endl; // 1
tavern.tavernReport();
return 0;
}This incremental testing approach saves hours of debugging. It's like leveling up in a game—you gain XP one quest at a time.
Real-World Connections: From Taverns to Tech
This project mirrors real software engineering patterns. The Tavern class is a repository pattern—a common design in finance apps and AI systems. For instance, a stock portfolio might hold assets and track total value, similar to your level sum. Even social media algorithms use bags of data to recommend content. By mastering these C++ fundamentals, you're building skills for internships at FAANG or startups.
Conclusion: Your Tavern Awaits
You've now learned how to implement a Tavern class using inheritance, operator overloading, and ArrayBag. This project is a milestone in your algorithmic adventures. Remember to document your code, test often, and have fun. The recursive realms of C++ are vast—this tavern is just the beginning. Happy coding!