Races have many distinguishing physical characteristics. But apart from appearances, different races have other distinguishing characteristcs, all of I've decided to be built using the Builder Pattern. Separate builder classes I found to very useful when many races have multiple sub-races as well. Once the races and sub-races are defined in the code below, the accompanying builder classes are
defined and then used in the constructors of the HumanoidWithRaceType
s.
#include <iostream>
#include <string>
#include <unordered_map>
#include <fstream>
const int NOT_FOUND = -1;
enum RaceType {HUMAN, ELF, DWARF, HALFLING};
enum HumanSubRaceType {CALASHITE, CHONDATHAN, DAMARAN}; // several more
enum ElfSubRaceType {HIGH, WOOD, DROW};
enum HighElfSubRaceType {GREY, VALLEY, SILVANESTI, GREYHAWK_HIGH, QUALINESTI, MOON, SUN};
enum WoodElfSubRaceType {WILD, KAGONESTI, GREYHAWK_WOOD, FORGOTTEN_REALMS_WOOD, FOREST};
class Race;
class HumanoidWithRaceType;
template <RaceType> class RaceClass;
using Human = RaceClass<HUMAN>;
using Elf = RaceClass<ELF>;
template <HumanSubRaceType> class HumanSubRace;
using Calashite = HumanSubRace<CALASHITE>;
using Chondathan = HumanSubRace<CHONDATHAN>;
using Damaran = HumanSubRace<DAMARAN>;
// etc... for the other Human subraces
template <ElfSubRaceType> class ElfSubRace;
using HighElf = ElfSubRace<HIGH>;
using WoodElf = ElfSubRace<WOOD>;
using Drow = ElfSubRace<DROW>;
template <HighElfSubRaceType> class HighElfSubRace;
using GreyElf = HighElfSubRace<GREY>;
using ValleyElf = HighElfSubRace<VALLEY>;
using SilvanestiElf = HighElfSubRace<SILVANESTI>;
using GreyhawkHighElf = HighElfSubRace<GREYHAWK_HIGH>;
using QualinestiElf = HighElfSubRace<QUALINESTI>;
using MoonElf = HighElfSubRace<MOON>;
using SunElf = HighElfSubRace<SUN>;
template <WoodElfSubRaceType> class WoodElfSubRace;
using WildElf = WoodElfSubRace<WILD>;
using KagonestiElf = WoodElfSubRace<KAGONESTI>;
using GreyhawkWoodElf = WoodElfSubRace<GREYHAWK_WOOD>;
using ForgottenRealmsWoodElf = WoodElfSubRace<FORGOTTEN_REALMS_WOOD>;
using ForestElf = WoodElfSubRace<FOREST>;
class LivingBeing {
int intelligence = 9, wisdom = 9, dexterity = 9, height = 180;
int hitPoints = 10;
public:
void changeIntelligenceBy (int change) { intelligence += change; }
void changeWisdomBy (int change) { wisdom += change; }
void changeDexterityBy (int change) { dexterity += change; }
void changeHeightBy (int change) { height += change; }
void setHitPoints (int hp) { hitPoints = hp; }
virtual bool notAffectedBySleepSpell() const { return false; } // Overriden by UndeadMonster and HumanoidWithRaceType with race == any kind of Elf.
virtual bool notAffectedByCharmSpell() const { return false; } // Overriden by UndeadMonster and HumanoidWithRaceType with race == any kind of Elf.
virtual bool hasAdvantageVsCharm() const { return false; } // Overriden by HumanoidWithRaceType with race == any kind of Elf.
virtual void print (std::ostream& os = std::cout) const {
os << "Intelligence: " << intelligence << '\n';
os << "Wisdom: " << wisdom << '\n';
os << "Dexterity: " << dexterity << '\n';
os << "Height: " << height << '\n';
os << "Hit Points: " << hitPoints << '\n';
}
virtual void save (std::ostream& os) const { os << intelligence << ' ' << wisdom << ' ' << dexterity << ' ' << height << ' ' << hitPoints << '\n'; }
virtual void load (std::istream& is) { is >> std::skipws >> intelligence >> wisdom >> dexterity >> height >> hitPoints; }
};
struct Weapon {
virtual std::string getName() const = 0;
};
struct LongSword : Weapon { std::string getName() const override { return "long sword"; } };
struct ShortSword : Weapon { std::string getName() const override { return "short sword"; } };
struct Armor {
virtual std::string getName() const = 0;
};
struct SmallChainMail : Armor { std::string getName() const override { return "small chainmail"; } };
struct LargeChainMail : Armor { std::string getName() const override { return "large chainmail"; } };
// DemiHuman is a being that has physical features of a human being and can use weapons and/or wear armor, which not only includes Humanoids (like humans-sized Goblins, Orcs, etc...) but also certain undead, giants, celestials, even deities, all of which use weapons and wear armor.
class DemiHuman : public LivingBeing {
Weapon* weaponInHand = nullptr;
Armor* armorWorn = nullptr;
int proficiencyWithMissiles = 5;
public:
Weapon* getWeapon() const { return weaponInHand; }
void setWeapon (Weapon* weapon) { weaponInHand = weapon; }
void setArmor (Armor* armor) { armorWorn = armor; }
void changeProficiencyWithMissilesBy (int change) { proficiencyWithMissiles += change; }
virtual void save (std::ostream& os) const override {
LivingBeing::save(os);
os << proficiencyWithMissiles << '\n'; // Skipping weaponInHand and armorWorn for simplicity.
if (weaponInHand) {
os << std::boolalpha << true << '\n';
os << weaponInHand->getName() << '\n';
}
else
os << std::boolalpha << false << '\n';
if (armorWorn) {
os << std::boolalpha << true << '\n';
os << armorWorn->getName() << '\n';
}
else
os << std::boolalpha << false << '\n';
}
virtual void load (std::istream& is) override {
LivingBeing::load(is);
is >> proficiencyWithMissiles; // Skipping weaponInHand and armorWorn for simplicity.
bool b;
std::string itemName;
is >> std::boolalpha >> b;
if (b) {
while (std::getline(is, itemName) && itemName.empty());
if (itemName == "short sword") // Factory std::map<std::string, std::function<Weapon*()>> construction and look-up omitted for brevity.
weaponInHand = new ShortSword;
}
is >> std::boolalpha >> b;
if (b) {
while (std::getline(is, itemName) && itemName.empty());
if (itemName == "small chainmail") // Factory std::map<std::string, std::function<Armor*()>> construction and look-up omitted for brevity.
armorWorn = new SmallChainMail;
}
}
virtual void print (std::ostream& os = std::cout) const override {
LivingBeing::print(os);
if (weaponInHand) os << "Holding " << weaponInHand->getName() << " as a weapon.\n";
else os << "No weapon in hand.\n";
if (armorWorn) os << "Wearing " << armorWorn->getName() << " for armor.\n";
else os << "No armor worn.\n";
os << "Proficiency With missiles = " << proficiencyWithMissiles << '\n';
}
};
class Humanoid : public DemiHuman { }; // Includes Goblins, Orcs, Kobolds, etc... (all human-sized)
class RaceDatabase {
static std::unordered_map<std::string, Race*> map; // Used for saving and loading DemiHumanWithRaceType instances from strings.
public:
static Race* getRaceFromString (const std::string& raceName) { return map.at(raceName); }
static void insertIntoMap (const std::string& tag, Race* race) { map.emplace(tag, race); std::cout << "{" << tag << ", " << race << "} inserted in RaceDatabase::map.\n"; }
private:
RaceDatabase() = default; RaceDatabase (const RaceDatabase&); const RaceDatabase& operator = (const RaceDatabase&); ~RaceDatabase() = default;
};
std::unordered_map<std::string, Race*> RaceDatabase::map;
class Race {
protected:
struct Exemplar {};
public:
virtual void build (HumanoidWithRaceType&) = 0;
virtual std::string tag() const = 0;
virtual int getWalkingSpeed() const = 0;
virtual bool isElf() const { return false; }
virtual int getDarkVisionDistance() const { return NOT_FOUND; }
virtual bool notAffectedBySleepSpell() const { return false; } // overridden by Elf
virtual bool hasAdvantageVsCharm() const { return false; } // overridden by Elf
// virtual void increaseNumCantripsByOne (SpellCasterCharacterClass*) const { } // overridden by HighElf only
};
template <RaceType>
class RaceCRTP : public Race {
virtual void build (HumanoidWithRaceType&) override;
};
// Human and its subraces
template <>
class RaceClass<HUMAN> : public RaceCRTP<HUMAN> { // Human
static const Human prototype;
public:
static Human* getInstance() {static Human* instance = new Human; return instance;} // Singleton, because all beings of this (or any other) race shall simply share the same Human* pointer (Flyweight Pattern).
protected: // The default constructor, copy constructor, assignment operator cannot be accessed in order to force the sharing of the same Human* pointer by all beings of race Human.
RaceClass() = default; RaceClass (const RaceClass&); const RaceClass& operator = (const RaceClass&); ~RaceClass() = default;
private:
RaceClass (Exemplar) { RaceDatabase::insertIntoMap(className(), getInstance()); } // Cannot implement this in the base class Race because getInstance() is being passed instead of 'this' (and virtual functions should not be called in a base constructor).
virtual std::string tag() const override { return className(); }
static std::string className() { return "Human"; }
virtual int getWalkingSpeed() const override { return 30; }
};
const Human Human::prototype(Exemplar{});
template <HumanSubRaceType>
class HumanCRTP : public Human {
// Whatever is needed using CRTP, other than build(HumanoidWithRaceType&).
};
template <HumanSubRaceType H>
class HumanCRTPWithBuilder : public HumanCRTP<H> {
virtual void build (HumanoidWithRaceType&) override; // No other methods defined here.
};
template <>
class HumanSubRace<CALASHITE> : public HumanCRTPWithBuilder<CALASHITE> { // Calashite
static const Calashite prototype;
public:
static Calashite* getInstance() {static Calashite* instance = new Calashite; return instance;} // Singleton
private:
HumanSubRace (Exemplar) { RaceDatabase::insertIntoMap(className(), getInstance()); } // Cannot implement this in the base class Race because getInstance() is being passed instead of 'this' (and virtual functions should not be called in a base constructor).
HumanSubRace() = default; HumanSubRace (const HumanSubRace&); const HumanSubRace& operator = (const HumanSubRace&); ~HumanSubRace() = default;
virtual std::string tag() const override { return className(); }
static std::string className() { return "Calashite Human"; }
};
const Calashite Calashite::prototype(Exemplar{});
// HumanBuilder<CHONDATHAN> is not to be defined because it won't do anything different from RacialClassBuilder<HUMAN>, i.e. being of race Chondathan Human is no different from Human from a builder point of view,
// so HumanSubRace<CHONDATHAN> will just derive from HumanCRTP<CHONDATHAN> instead of from HumanCRTPWithBuilder<CHONDATHAN>.
template <>
class HumanSubRace<CHONDATHAN> : public HumanCRTP<CHONDATHAN> { // Chondathan
static const Chondathan prototype;
public:
static Chondathan* getInstance() {static Chondathan* instance = new Chondathan; return instance;} // Singleton
private:
HumanSubRace (Exemplar) { RaceDatabase::insertIntoMap(className(), getInstance()); }
HumanSubRace() = default; HumanSubRace (const HumanSubRace&); const HumanSubRace& operator = (const HumanSubRace&); ~HumanSubRace() = default;
virtual std::string tag() const override { return className(); }
static std::string className() { return "Chondathan Human"; }
};
const Chondathan Chondathan::prototype(Exemplar{});
template <>
class HumanSubRace<DAMARAN> : public HumanCRTP<DAMARAN> { // Damaran (nothing special about it either from a builder point of view, so also derive from HumanCRTP<DAMARAN> instead of from HumanCRTPWithBuilder<DAMARAN>).
static const Damaran prototype;
public:
static Damaran* getInstance() {static Damaran* instance = new Damaran; return instance;} // Singleton
private:
HumanSubRace (Exemplar) {RaceDatabase::insertIntoMap (className(), getInstance());}
HumanSubRace() = default; HumanSubRace (const HumanSubRace&); const HumanSubRace& operator = (const HumanSubRace&); ~HumanSubRace() = default;
virtual std::string tag() const override {return className();}
static std::string className() {return "Damaran Human";}
};
const Damaran Damaran::prototype(Exemplar{});
// and several more Human subraces
// Elf and its subraces
template <>
class RaceClass<ELF> : public RaceCRTP<ELF> { // Elf
static const Elf prototype;
public:
static Elf* getInstance() {static Elf* instance = new Elf; return instance;} // Singleton
protected:
RaceClass() = default; RaceClass (const RaceClass&); const RaceClass& operator = (const RaceClass&); ~RaceClass() = default;
private:
RaceClass (Exemplar) { RaceDatabase::insertIntoMap(className(), getInstance()); }
virtual bool isElf() const override { return true; }
virtual std::string tag() const override { return className(); }
static std::string className() { return "Elf"; }
virtual int getWalkingSpeed() const override { return 32; }
virtual bool notAffectedBySleepSpell() const override { return true; }
virtual bool hasAdvantageVsCharm() const override { return true; }
};
const Elf Elf::prototype(Exemplar{});
template <ElfSubRaceType>
class ElfCRTP : public Elf {
// Whatever is needed using CRTP, other than build(HumanoidWithRaceType&).
};
template <ElfSubRaceType E>
class ElfCRTPWithBuilder : public ElfCRTP<E> {
virtual void build (HumanoidWithRaceType&) override; // No other methods defined here.
};
template <>
class ElfSubRace<HIGH> : public ElfCRTPWithBuilder<HIGH> { // HighElf
static const HighElf prototype;
public:
static HighElf* getInstance() { static HighElf* instance = new HighElf; return instance; } // Singleton
protected: // ElfSubRace<HIGH> will itself have subraces deriving from it.
ElfSubRace() = default; ElfSubRace (const ElfSubRace&); const ElfSubRace& operator = (const ElfSubRace&); ~ElfSubRace() = default;
private:
ElfSubRace (Exemplar) { RaceDatabase::insertIntoMap(className(), getInstance()); }
virtual std::string tag() const override { return className(); }
static std::string className() { return "High Elf"; }
};
const HighElf HighElf::prototype(Exemplar{});
template <HighElfSubRaceType>
class HighElfCRTP : public HighElf {
// Whatever is needed using CRTP, other than build(HumanoidWithRaceType&).
};
template <HighElfSubRaceType H>
class HighElfCRTPWithBuilder : public HighElfCRTP<H> {
virtual void build (HumanoidWithRaceType&) override; // No other methods defined here.
};
template <>
class HighElfSubRace<GREY> : public HighElfCRTPWithBuilder<GREY> { // GreyElf
static const GreyElf prototype;
public:
static GreyElf* getInstance() { static GreyElf* instance = new GreyElf; return instance; } // Singleton
private:
HighElfSubRace (Exemplar) { RaceDatabase::insertIntoMap(className(), getInstance()); }
HighElfSubRace() = default; HighElfSubRace (const HighElfSubRace&); const HighElfSubRace& operator = (const HighElfSubRace&); ~HighElfSubRace() = default;
virtual std::string tag() const override { return className(); }
static std::string className() { return "Grey Elf"; }
};
const GreyElf GreyElf::prototype(Exemplar{});
template <>
class HighElfSubRace<VALLEY> : public HighElfCRTPWithBuilder<VALLEY> { // ValleyElf
static const ValleyElf prototype;
public:
static ValleyElf* getInstance() { static ValleyElf* instance = new ValleyElf; return instance; } // Singleton
private:
HighElfSubRace (Exemplar) { RaceDatabase::insertIntoMap(className(), getInstance()); }
HighElfSubRace() = default; HighElfSubRace (const HighElfSubRace&); const HighElfSubRace& operator = (const HighElfSubRace&); ~HighElfSubRace() = default;
virtual std::string tag() const override { return className(); }
static std::string className() { return "Valley Elf"; }
};
const ValleyElf ValleyElf::prototype(Exemplar{});
// ***** The all-important derived type of Humanoid that has races.
class HumanoidWithRaceType : public Humanoid { // This class placed between CharacterClass and Humanoid to accomodate things like Goblin races, Orc races, Kobold races, etc...
Race* race; // Type Object Pattern (better choice than using multiple inheritance from Race).
public:
HumanoidWithRaceType (Race* race) { setRace(race); }
HumanoidWithRaceType() = default;
Race* getRace() const { return race; }
virtual void setRace (Race* thisRace) { race = thisRace; race->build(*this); } // ***** 'race->build(*this);' is the key line that initializes all the stats of 'this' according to what 'race' is. No need for the Visitor Pattern, and the 'build(HumanoidWithRaceType&)' overrides do actually belong in the Race subtypes (just one override for each concrete derived class of Race), and hence the Open-Closed Principle is not violated.
int getDarkVisionDistance() const { return race->getDarkVisionDistance(); }
virtual void save (std::ostream& os) const override {
DemiHuman::save(os);
os << race->tag() << '\n';
}
virtual void load (std::istream& is) override {
DemiHuman::load(is);
std::string raceName;
while (std::getline(is, raceName) && raceName.empty());
race = RaceDatabase::getRaceFromString(raceName); // RaceDatabase now being used. No need to call 'setRace', namely 'race->build(*this);' (which is only used for Factory-type production of HumanoidWithRaceType's) since all the special attributes have been saved and hence will be loaded.
}
virtual void print (std::ostream& os = std::cout) const override {
DemiHuman::print(os);
os << "Race: " << race->tag() << '\n';
}
private:
virtual bool notAffectedBySleepSpell() const override { return race->notAffectedBySleepSpell(); } // Elves resist with 100% success.
virtual bool hasAdvantageVsCharm() const override { return race->hasAdvantageVsCharm(); } // Elves have advantage saving throw vs Charm spells.
};
// Abstract builder classes
class HumanoidWithRaceTypeBuilder {
HumanoidWithRaceType& being; // Reference is used here because a HumanoidWithRaceType already built by some other builder (e.g. PrimaryStatsBuilder) is passed into here, instead of being instantiated here as a pointer (as the classic builder pattern follows).
public:
HumanoidWithRaceTypeBuilder (HumanoidWithRaceType& d) : being(d) {}
protected:
HumanoidWithRaceType& getBeing() const { return being; }
};
class RacialClassBuilderBase : protected HumanoidWithRaceTypeBuilder {
friend class RacialClassDirector;
public:
using HumanoidWithRaceTypeBuilder::HumanoidWithRaceTypeBuilder;
protected:
virtual void adjustAbilityScores() const = 0;
virtual void adjustWeaponProficiencies() const = 0;
virtual void adjustPhysicalAppearance() const = 0;
};
// The director class in the Builder Pattern.
class RacialClassDirector {
private:
const RacialClassBuilderBase& racialClassBuilderBase; // const, as setRacialClassBuilderBase(RacialClassBuilderBase*) is not needed I don't think, since this class is apparently only used the 'build' overides in Race subtypes.
public:
RacialClassDirector (const RacialClassBuilderBase& r) : racialClassBuilderBase(r) {}
void construct() const { // The key Facade Pattern used in the actual building process in the Builder Pattern.
racialClassBuilderBase.adjustAbilityScores();
racialClassBuilderBase.adjustWeaponProficiencies();
racialClassBuilderBase.adjustPhysicalAppearance();
// racialClassBuilderBase.adjustLifespan(); // And common diseases, etc...
// etc...
}
};
// The concrete race builder classes
template <RaceType> class RacialClassBuilder;
template <>
class RacialClassBuilder<HUMAN> : public RacialClassBuilderBase {
public:
using RacialClassBuilderBase::RacialClassBuilderBase;
protected:
virtual void adjustAbilityScores() const override { } // Do whatever applies for all humans.
virtual void adjustWeaponProficiencies() const override { } // Do whatever applies for all humans.
virtual void adjustPhysicalAppearance() const override { } // Do whatever applies for all humans.
};
template <HumanSubRaceType> class HumanBuilder;
template <>
class HumanBuilder<CALASHITE>: public RacialClassBuilder<HUMAN> {
public:
using RacialClassBuilder<HUMAN>::RacialClassBuilder;
protected:
virtual void adjustAbilityScores() const override {
RacialClassBuilder<HUMAN>::adjustAbilityScores();
getBeing().changeIntelligenceBy(2);
}
virtual void adjustWeaponProficiencies() const override {
RacialClassBuilder<HUMAN>::adjustWeaponProficiencies();
getBeing().changeProficiencyWithMissilesBy(1);
}
virtual void adjustPhysicalAppearance() const override {
RacialClassBuilder<HUMAN>::adjustPhysicalAppearance();
getBeing().changeHeightBy(7);
}
};
template <>
class RacialClassBuilder<ELF> : public RacialClassBuilderBase {
public:
using RacialClassBuilderBase::RacialClassBuilderBase;
protected:
virtual void adjustAbilityScores() const override { getBeing().changeDexterityBy(2); }
virtual void adjustWeaponProficiencies() const override { } // Do whatever applies for all elves.
virtual void adjustPhysicalAppearance() const override { getBeing().changeHeightBy(-30); } // Elves are much shorter than humans.
};
template <ElfSubRaceType> class ElfBuilder;
template <>
class ElfBuilder<HIGH>: public RacialClassBuilder<ELF> {
public:
using RacialClassBuilder<ELF>::RacialClassBuilder;
protected:
virtual void adjustAbilityScores() const override {
RacialClassBuilder<ELF>::adjustAbilityScores();
getBeing().changeIntelligenceBy(1);
// SpellCasterCharacterClass* spellCaster = dynamic_cast<SpellCasterCharacterClass*>(&getBeing());
// if (spellCaster)
// spellCaster->increaseNumCantripsByOne();
}
virtual void adjustWeaponProficiencies() const override {
RacialClassBuilder<ELF>::adjustWeaponProficiencies();
getBeing().changeProficiencyWithMissilesBy(2);
}
};
template <HighElfSubRaceType> class HighElfBuilder;
template <>
class HighElfBuilder<GREY>: public ElfBuilder<HIGH> { // Haughty, reclusive, believing themselves to be superior to non-elves and even other elves.
public:
using ElfBuilder<HIGH>::ElfBuilder;
protected:
virtual void adjustAbilityScores() const override {
ElfBuilder<HIGH>::adjustAbilityScores();
getBeing().changeWisdomBy(1);
}
virtual void adjustWeaponProficiencies() const override {
ElfBuilder<HIGH>::adjustWeaponProficiencies();
getBeing().changeProficiencyWithMissilesBy(1); // Grey Elves are even better with missiles than the already missile-adept Elf.
}
virtual void adjustPhysicalAppearance() const override {
ElfBuilder<HIGH>::adjustPhysicalAppearance();
getBeing().changeHeightBy(-10); // Grey Elves are 10 cm shorter than the already short Elf.
}
};
template <>
class HighElfBuilder<VALLEY>: public ElfBuilder<HIGH> {
public:
using ElfBuilder<HIGH>::ElfBuilder;
protected:
virtual void adjustAbilityScores() const override {
ElfBuilder<HIGH>::adjustAbilityScores();
// Add whatever unique to ValleyElf.
}
virtual void adjustWeaponProficiencies() const override {
ElfBuilder<HIGH>::adjustWeaponProficiencies();
// Add whatever unique to ValleyElf.
}
virtual void adjustPhysicalAppearance() const override {
ElfBuilder<HIGH>::adjustPhysicalAppearance();
// Add whatever unique to ValleyElf.
}
};
// Similarly for class HighElfBuilder<SILVANESTI>: public ElfBuilder<HIGH>, etc...
// The all-important build(HumanoidWithRaceType&) overrides of the Race class.
template <RaceType R>
void RaceCRTP<R>::build (HumanoidWithRaceType& h) {
const RacialClassBuilder<R> builder(h);
const RacialClassDirector director(builder);
director.construct();
}
template <HumanSubRaceType H>
void HumanCRTPWithBuilder<H>::build (HumanoidWithRaceType& h) {
const HumanBuilder<H> humanBuilder(h);
const RacialClassDirector director(humanBuilder);
director.construct();
}
template <ElfSubRaceType E>
void ElfCRTPWithBuilder<E>::build (HumanoidWithRaceType& h) {
const ElfBuilder<E> elfBuilder(h);
const RacialClassDirector director(elfBuilder);
director.construct();
}
template <HighElfSubRaceType H>
void HighElfCRTPWithBuilder<H>::build (HumanoidWithRaceType& h) {
const HighElfBuilder<H> highElfBuilder(h);
const RacialClassDirector director(highElfBuilder);
director.construct();
}
// Some totally different builder classes not related to race, but to be used sequentially with race builders. Defined briefly only.
class PrimaryStatsBuilderBase : protected HumanoidWithRaceTypeBuilder { // Builds based on level of the character class.
public:
using HumanoidWithRaceTypeBuilder::HumanoidWithRaceTypeBuilder;
void construct() const { // Doing without the Director class this time, since here construct() also needs to be defined only once.
establishHitPointsAndAbilityScores();
establishProficiencies();
establishSpells();
// etc...
}
private:
virtual void establishHitPointsAndAbilityScores() const = 0;
virtual void establishProficiencies() const = 0;
virtual void establishSpells() const = 0;
// etc...
};
class EquipmentBuilderBase : protected HumanoidWithRaceTypeBuilder { // Builds based on the type of character class (and to some extent the race already established).
public:
using HumanoidWithRaceTypeBuilder::HumanoidWithRaceTypeBuilder;
void construct() const { // Doing without the Director class this time, since here construct() also needs to be defined only once.
establishWeapons();
establishArmor();
// etc...
}
private:
virtual void establishWeapons() const = 0;
virtual void establishArmor() const = 0;
// etc...
};
enum CharacteClassType { FIGHTER, WIZARD, CLERIC }; // etc...
class CharacterClass : public HumanoidWithRaceType {
int level;
public:
using HumanoidWithRaceType::HumanoidWithRaceType;
void setLevel (int n) { level = n; }
virtual void print (std::ostream& os = std::cout) const override {
HumanoidWithRaceType::print(os);
os << "Level: " << level << '\n';
}
virtual void save (std::ostream& os) const override {
HumanoidWithRaceType::save(os);
os << level << '\n';
}
virtual void load (std::istream& is) override {
HumanoidWithRaceType::load(is);
is >> level;
}
};
template <CharacteClassType> class PrimaryStatsBuilder;
template <>
class PrimaryStatsBuilder<FIGHTER> : public PrimaryStatsBuilderBase {
public:
using PrimaryStatsBuilderBase::PrimaryStatsBuilderBase;
private:
virtual void establishHitPointsAndAbilityScores() const override {
const int level = 5;
dynamic_cast<CharacterClass&>(getBeing()).setLevel(level);
getBeing().setHitPoints(level*10);
// etc...
}
virtual void establishProficiencies() const override { std::cout << "\nProficiency added: can bend bars.\n"; }
virtual void establishSpells() const override { std::cout << "\nNo spells allowed for Fighters.\n"; }
};
// Similar specializations for PrimaryStatsBuilder<WIZARD>, PrimaryStatsBuilder<CLERIC>, etc...
template <CharacteClassType> class EquipmentBuilder;
template <>
class EquipmentBuilder<FIGHTER> : public EquipmentBuilderBase {
public:
using EquipmentBuilderBase::EquipmentBuilderBase;
private:
virtual void establishWeapons() const override { // These overrides are related to race due to racial sizes.
if (getBeing().getRace()->isElf())
getBeing().setWeapon(new ShortSword);
else
getBeing().setWeapon(new LongSword);
}
virtual void establishArmor() const override {
if (getBeing().getRace()->isElf())
getBeing().setArmor(new SmallChainMail);
else
getBeing().setArmor(new LargeChainMail);
}
};
// Similar specializations for EquipmentBuilder<WIZARD>, EquipmentBuilder<CLERIC>, etc...
template <CharacteClassType C>
class CharacterClassCRTP : public CharacterClass {
protected:
using CharacterClass::CharacterClass;
void intializeWithBuilders() {
const PrimaryStatsBuilder<C> primaryStatsBuilder(*this);
primaryStatsBuilder.construct();
const EquipmentBuilder<C> equipmentBuilder(*this); // This can be used because race has already been established.
equipmentBuilder.construct();
}
};
template <CharacteClassType> class Character;
template <>
class Character<FIGHTER> : public CharacterClassCRTP<FIGHTER> { // Fighter
public:
Character (Race* race) : CharacterClassCRTP<FIGHTER>(race) {
intializeWithBuilders(); // ***** All three separate builder classes are used in this constructor.
}
Character() = default;
};
template <> class Character<WIZARD> : public CharacterClassCRTP<WIZARD> { }; // Wizard (is similar to Fighter)
template <> class Character<CLERIC> : public CharacterClassCRTP<CLERIC> { }; // Cleric (is similar to Fighter)
using Fighter = Character<FIGHTER>;
int main() {
HumanoidWithRaceType humanoid1;
humanoid1.setRace(Human::getInstance());
std::cout << "\nAfter being built with assigned race Human:\n";
humanoid1.print();
humanoid1.setRace(HumanSubRace<CHONDATHAN>::getInstance());
std::cout << "\nAfter being built with assigned race Chondathan Human (sub-race of Human, but nothing special about it):\n";;
humanoid1.print();
HumanoidWithRaceType humanoid2;
humanoid2.setRace(HumanSubRace<CALASHITE>::getInstance());
std::cout << "\nAfter being built with assigned race Calashite Human (sub-race of Human):\n";
humanoid2.print();
HumanoidWithRaceType humanoid3;
humanoid3.setRace(Elf::getInstance());
std::cout << "\nAfter being built with assigned race Elf:\n";
humanoid3.print();
HumanoidWithRaceType humanoid4;
humanoid4.setRace(ElfSubRace<HIGH>::getInstance());
std::cout << "\nAfter being built with assigned race High Elf (sub-race of Elf):\n";
humanoid4.print();
std::cout << "\n\nGenerating a Fighter now:\n";
const std::string raceNameGenerated = "Grey Elf"; // From some Factory method.
Fighter fighter(RaceDatabase::getRaceFromString(raceNameGenerated)); // For Factory methods, race must be passed into the Fighter constructor to use EquipmentBuilder due to race requiring to be known first.
std::cout << "\nFighter, after being built with assigned race Grey Elf (sub-race of HighElf):\n";
fighter.print();
const std::string fileName = "SaveFile.txt";
std::ofstream outfile(fileName);
fighter.save(outfile);
std::cout << "\nGrey Elf fighter written to txt file.\n";
outfile.close();
std::ifstream infile(fileName);
Fighter loadedFighter;
loadedFighter.load(infile);
std::cout << "\nGrey Elf fighter constructed from txt file:\n";
loadedFighter.print();
}
Output:
{Human, 0x1959d0} inserted in RaceDatabase::map.
{Calashite Human, 0x195a50} inserted in RaceDatabase::map.
{Chondathan Human, 0x195ad0} inserted in RaceDatabase::map.
{Damaran Human, 0x195d40} inserted in RaceDatabase::map.
{Elf, 0x195cd0} inserted in RaceDatabase::map.
{High Elf, 0x195d50} inserted in RaceDatabase::map.
{Grey Elf, 0x195ca0} inserted in RaceDatabase::map.
{Valley Elf, 0x195be0} inserted in RaceDatabase::map.
After being built with assigned race Human:
Intelligence: 9
Wisdom: 9
Dexterity: 9
Height: 180
Hit Points: 10
No weapon in hand.
No armor worn.
Proficiency With missiles = 5
Race: Human
After being built with assigned race Chondathan Human (sub-race of Human, but nothing special about it):
Intelligence: 9
Wisdom: 9
Dexterity: 9
Height: 180
Hit Points: 10
No weapon in hand.
No armor worn.
Proficiency With missiles = 5
Race: Chondathan Human
After being built with assigned race Calashite Human (sub-race of Human):
Intelligence: 11
Wisdom: 9
Dexterity: 9
Height: 187
Hit Points: 10
No weapon in hand.
No armor worn.
Proficiency With missiles = 6
Race: Calashite Human
After being built with assigned race Elf:
Intelligence: 9
Wisdom: 9
Dexterity: 11
Height: 150
Hit Points: 10
No weapon in hand.
No armor worn.
Proficiency With missiles = 5
Race: Elf
After being built with assigned race High Elf (sub-race of Elf):
Intelligence: 10
Wisdom: 9
Dexterity: 11
Height: 150
Hit Points: 10
No weapon in hand.
No armor worn.
Proficiency With missiles = 7
Race: High Elf
Generating a Fighter now:
Proficiency added: can bend bars.
No spells allowed for Fighters.
Fighter, after being built with assigned race Grey Elf (sub-race of HighElf):
Intelligence: 10
Wisdom: 10
Dexterity: 11
Height: 140
Hit Points: 50
Holding short sword as a weapon.
Wearing small chainmail for armor.
Proficiency With missiles = 8
Race: Grey Elf
Level: 5
Grey Elf fighter written to txt file.
Grey Elf fighter constructed from txt file:
Intelligence: 10
Wisdom: 10
Dexterity: 11
Height: 140
Hit Points: 50
Holding short sword as a weapon.
Wearing small chainmail for armor.
Proficiency With missiles = 8
Race: Grey Elf
Level: 5