I’ve used C++ professionally for years and love to study it. Here’s what I came up for TripleX:
Code
#include <iostream>
#include <ctime>
#include <map>
#include <vector>
#include <string>
#include <functional>
#include <algorithm>
using std::cin;
using std::cout;
using std::endl;
using std::map;
using std::vector;
using std::string;
using std::function;
// making it an enum class will prevent us from doing errors like writing Play(3) or Play(false) instead of Play(EASIEST)
enum class EDifficulty {
EASIEST,
EASIER,
EASY,
STANDARD,
INTERESTING,
CHALLENGING,
HARD,
HARDER,
EXTREME,
NIGHTMARISH,
IMPOSSIBLE
};
// this order is important for the progression.
static const vector<EDifficulty> Difficulties = {
EDifficulty::EASIEST,
EDifficulty::EASIER,
EDifficulty::EASY,
EDifficulty::STANDARD,
EDifficulty::INTERESTING,
EDifficulty::CHALLENGING,
EDifficulty::HARD,
EDifficulty::HARDER,
EDifficulty::EXTREME,
EDifficulty::NIGHTMARISH,
EDifficulty::IMPOSSIBLE
};
static const map<EDifficulty, unsigned short int> DifficultyValue = {
{ EDifficulty::EASIEST, 2},
{ EDifficulty::EASIER, 3},
{ EDifficulty::EASY, 4},
{ EDifficulty::STANDARD, 5},
{ EDifficulty::INTERESTING, 6},
{ EDifficulty::CHALLENGING, 7},
{ EDifficulty::HARD, 8},
{ EDifficulty::HARDER, 9},
{ EDifficulty::EXTREME, 10},
// the latter difficulties will have additional rules to make them more difficult, let's not bump numbers any higher.
{ EDifficulty::NIGHTMARISH, 10},
{ EDifficulty::IMPOSSIBLE, 10}
};
static const map<EDifficulty, string> DifficultyDescription = {
{ EDifficulty::EASIEST, "a tiny snake"},
{ EDifficulty::EASIER, "a small flying imp"},
{ EDifficulty::EASY, "an angered imp"},
{ EDifficulty::STANDARD, "a cruel goblin"},
{ EDifficulty::INTERESTING, "a bloodthirsty orc"},
{ EDifficulty::CHALLENGING, "a dreadful dark-armored knight"},
{ EDifficulty::HARD, "a giant, intimidating spider"},
{ EDifficulty::HARDER, "a hive of killer bees, seeping with murderous intent"},
{ EDifficulty::EXTREME, "a sphinx, consumed by time"},
{ EDifficulty::NIGHTMARISH, "an abominable entity with physical features that continuously change"},
{ EDifficulty::IMPOSSIBLE, "the deepest void of the world, where no light and no sound can ever reach"}
};
enum class EAdditionalRules {
ALL_ODD,
ALL_DIFFERENT
};
static bool ValidInput(unsigned int a, unsigned int b, unsigned int c)
{
return a != 0
&& b != 0
&& c != 0;
}
typedef function<void(int&, int&, int&)> ValidityChecker;
static ValidityChecker CheckAscending = [](int& a, int& b, int& c) {
// just sort them in ascending order and reassign them.
std::vector<int> temp = { a, b, c };
std::sort(temp.begin(), temp.end());
a = temp[0]; b = temp[1]; c = temp[2];
};
static ValidityChecker CheckAllDifferent = [](int& a, int& b, int& c) {
if (a == b
|| b == c
|| a == c)
// at least one of them is equal: this selection is invalid.
a = b = c = 0;
};
static ValidityChecker CheckOdd = [](int& a, int& b, int& c) {
if ((a % 2 == 0)
|| (b % 2 == 0)
|| (c % 2 == 0))
// at least one of them is even: this selection is invalid.
a = b = c = 0;
};
static const map <EAdditionalRules, ValidityChecker> RuleImplementation =
{
{ EAdditionalRules::ALL_ODD, CheckOdd },
{ EAdditionalRules::ALL_DIFFERENT, CheckAllDifferent }
};
static const map <EAdditionalRules, string> RuleDescription =
{
{ EAdditionalRules::ALL_ODD, "The three numbers must all be odd numbers." },
{ EAdditionalRules::ALL_DIFFERENT, "The three numbers must all be different from each other." }
};
static const map<EDifficulty, vector<EAdditionalRules>> AdditionalRulesForDifficulty = {
{ EDifficulty::NIGHTMARISH, {EAdditionalRules::ALL_ODD}},
{ EDifficulty::IMPOSSIBLE, {EAdditionalRules::ALL_ODD, EAdditionalRules::ALL_DIFFERENT}}
};
static string SCENERY_DESCRIPTION(EDifficulty difficulty)
{
return string("After walking for a while, you find in front of you ") + DifficultyDescription.at(difficulty) + ". A question is posed to you: if you don't answer right, your journey will be over.";
}
static string Introduction = "You're the Nameless Hero, sent to quell an unknown evil. You've received the task with honor, and proudly started walking towards the path that the elders have indicated.",
GameOver = "You can feel the entity's malice overpowering you, and before you know anything your senses dim and your consciousness fades. Will the next Nameless Hero save the world instead of you?",
Win = "The entity shrinks and fades, terrified by your knowledge and wisdom. You can safely journey onwards.",
GameEnd = "At last, you find the unknown evil. It is... a simple lever, near a small waterfall, gushing out clean water on mud.\r\n"
"What a waste, you think. As you switch the lever, the water stops flowing.\r\n"
"Will the gods ever learn to close the water stream when they're done with the dishes?",
Question = "You will have to give three numbers: ", QuestionSum = "their sum is ", QuestionProduct = "; their product is ", QuestionEnter = "Write them now, separated by a space and followed by an 'x'.";
class InputGatherer
{
public:
InputGatherer()
{
int stop;
cin >> a >> b >> c >> stop;
CheckAscending(a, b, c);
}
bool matches(int questionA, int questionB, int questionC)
{
return a == questionA
&& b == questionB
&& c == questionC;
}
// use a RAII approach to clear buffer. When this object is destroyed, cin will be reset.
~InputGatherer()
{
cin.clear();
cin.ignore();
}
private:
int a, b, c;
};
bool PlayerWinsDifficulty(EDifficulty difficulty)
{
int difficultyLevel = DifficultyValue.at(difficulty);
// three question numbers
int questionA, questionB, questionC;
// let's retrieve our question validity checkers
vector<ValidityChecker> validators;
vector<EAdditionalRules> additionalRules;
if (AdditionalRulesForDifficulty.find(difficulty) != AdditionalRulesForDifficulty.end())
additionalRules = AdditionalRulesForDifficulty.at(difficulty);
for (auto rule : additionalRules)
validators.push_back(RuleImplementation.at(rule));
// generate different numbers until they satisfy the conditions. Unless some validator invalidate them, they will always be valid.
do {
questionA = (rand() * 100) % difficultyLevel + difficultyLevel;
questionB = (rand() * 100) % difficultyLevel + difficultyLevel;
questionC = (rand() * 100) % difficultyLevel + difficultyLevel;
for (auto validator : validators)
{
validator(questionA, questionB, questionC);
}
} while (!ValidInput(questionA, questionB, questionC));
// this is to make the player win even if he gives them in a different order.
CheckAscending(questionA, questionB, questionC);
cout << SCENERY_DESCRIPTION(difficulty) << endl;
cout << Question << QuestionSum << (questionA + questionB + questionC) << QuestionProduct << (questionA * questionB * questionC) << "." << endl;
for (auto rule : additionalRules)
cout << RuleDescription.at(rule) << endl;
cout << endl << QuestionEnter << endl;
auto input = InputGatherer();
return input.matches(questionA, questionB, questionC);
}
int main()
{
cout << Introduction << endl;
for (auto difficulty : Difficulties)
{
if (!PlayerWinsDifficulty(difficulty))
{
cout << GameOver << endl;;
return 0;
}
else
{
cout << Win << endl;
}
}
cout << GameEnd << endl;
return 0;
}
I wanted to focus on clean code practices, while keeping a fun game. I also took the occasion to fix the “you lost but you may continue” behaviour and customize the difficulties a bit.
Enjoy a standard fantasy journey as it gets darker and darker… with a final twist!