Let's suppose there is a method of an Inputter
class that queries many questions. The Human
inputter would have to answer all those questions, but an AI
inputter will do so without the questions being displayed to the screen. Some goes for command classes that contains the Inputter*
as data member. We don't want separate methods for the two different types of inputters just because the AI
will decide its own answers and does not need the questions displayed on the screen. So I've decided to use the Visitor Pattern to handle dealing with the two types of inputters.
#include <iostream>
#include <vector>
#include <ctime>
class Visitor; class Human; class AI;
class Inputter {
protected:
std::string name;
public:
Inputter (const std::string& n) : name(n) { }
virtual ~Inputter() = default;
std::string getName() const { return name; }
void dine();
virtual void accept (Visitor&) = 0;
};
class Visitor {
public:
virtual void visit (Human*) = 0;
virtual void visit (AI*) = 0;
};
class Human : public Inputter {
using Inputter::Inputter;
void accept (Visitor& visitor) override { visitor.visit(this); }
};
class AI : public Inputter {
using Inputter::Inputter;
void accept (Visitor& visitor) override { visitor.visit(this); }
};
// Use separate Visitor classes for each query to follow the open-closed principle.
class MealVisitor : public Visitor {
const std::vector<std::string> mealOptions;
std::string mealChosen;
public:
MealVisitor (const std::vector<std::string>& m) : mealOptions(m) { }
std::string getMealChosen() const { return mealChosen; }
private:
void visit (Human*) override;
void visit (AI*) override;
};
class TipVisitor : public Visitor {
int tipGiven;
public:
int getTipGiven() const { return tipGiven; }
private:
void visit (Human*) override;
void visit (AI*) override;
};
class Command {
protected:
Inputter* inputter;
public:
Command (Inputter* i) : inputter(i) { }
virtual void execute() const = 0;
};
class PlayCommand : public Command {
struct PlayCommandVisitor : Visitor {
bool yes;
};
struct BreakVisitor : PlayCommandVisitor {
void visit (Human*) override;
void visit (AI*) override;
};
struct ResumeVisitor : PlayCommandVisitor {
void visit (Human*) override;
void visit (AI*) override;
};
using Command::Command;
public:
void execute() const override;
private:
template <typename VisitorType> bool getYesOrNo() const;
};
void Inputter::dine() {
std::cout << name << " enters the restaurant.\n";
MealVisitor mealVisitor({"steak", "spaghetti", "burger"});
accept(mealVisitor);
const std::string meal = mealVisitor.getMealChosen();
std::cout << "20 minutes later, " << name << " has eaten the " << meal << ".\n";
TipVisitor tipVisitor;
accept(tipVisitor);
const int tipGiven = tipVisitor.getTipGiven();
std::cout << name << " gives " << "$" << tipGiven << " tip.\n\n";
}
void MealVisitor::visit (Human*) {
int choiceNumber = 0;
std::cout << "Choose a meal:\n";
for (const std::string& meal : mealOptions)
std::cout << ++choiceNumber << ". " << meal << '\n';
while (true) {
std::cin >> choiceNumber;
if (1 <= choiceNumber && choiceNumber <= (int)mealOptions.size())
break;
std::cout << "Please choose one of the above options.\n";
}
mealChosen = mealOptions[choiceNumber - 1];
}
void MealVisitor::visit (AI*) {
// No presentation with std::cout shall be given.
mealChosen = mealOptions[std::rand() & mealOptions.size() - 1];
}
void TipVisitor::visit (Human* human) {
std::cout << "How much will " << human->getName() << " tip? \n";
std::cin >> tipGiven;
}
void TipVisitor::visit (AI*) {
// Do not query the AI. Just let the AI do its part.
tipGiven = std::rand() % 10 + 5;
}
void PlayCommand::execute() const {
std::cout << inputter->getName() << " has been playing for 30 minutes.\n";
const bool stop = getYesOrNo<BreakVisitor>();
if (stop) {
std::cout << inputter->getName() << " has decided to take a break from playing.\n";
std::cout << inputter->getName() << " has taken a break for 10 minutes.\n";
const bool resume = getYesOrNo<ResumeVisitor>();
if (resume)
std::cout << inputter->getName() << " resumes playing.\n";
else
std::cout << inputter->getName() << " does not want to play anymore.\n";
}
else
std::cout << inputter->getName() << " continues to play.\n";
}
template <typename VisitorType>
bool PlayCommand::getYesOrNo() const {
VisitorType visitor;
inputter->accept(visitor);
return visitor.yes;
}
bool ask_yn (const std::string& question) {
while (true) {
std::cout << question << " (y/n) ";
char yesNo;
if (std::cin >> yesNo) {
switch (yesNo) {
case 'y': case 'Y': return true;
case 'n': case 'N': return false;
}
}
else { // Input failed. Assume no.
std::cout << '\n';
return false;
}
}
}
void PlayCommand::BreakVisitor::visit (Human* human) {
yes = ask_yn("Does " + human->getName() + " want to stop take a break?");
}
void PlayCommand::BreakVisitor::visit (AI*) {
yes = std::rand() % 5;
}
void PlayCommand::ResumeVisitor::visit (Human* human) {
yes = ask_yn("Does " + human->getName() + " want to resume playing?");
}
void PlayCommand::ResumeVisitor::visit (AI*) {
yes = std::rand() % 2;
}
int main() {
std::srand(std::time(nullptr));
Human human("Bob");
AI ai("Max");
human.dine();
ai.dine();
const PlayCommand playCommand1(&human), playCommand2(&ai);
playCommand1.execute();
playCommand2.execute();
}
Sample output:
Bob enters the restaurant.
Choose a meal:
1. steak
2. spaghetti
3. burger
2
20 minutes later, Bob has eaten the spaghetti.
How much will Bob tip?
10
Bob gives $10 tip.
Max enters the restaurant.
20 minutes later, Max has eaten the steak.
Max gives $13 tip.
Bob has been playing for 30 minutes.
Does Bob want to stop take a break? (y/n) y
Bob has decided to take a break from playing.
Bob has taken a break for 10 minutes.
Does Bob want to resume playing? (y/n) n
Bob does not want to play anymore.
Max has been playing for 30 minutes.
Max has decided to take a break from playing.
Max has taken a break for 10 minutes.
Max resumes playing.