Looking over Rock, Paper, Scissors. C++ from a beginning programmer prompted me to think about how I'd program this simple game in C++. The RockPaperScissors
class contains the essential features of printing and evaluating each round of the game, as well as generation of randomly selected moves. I didn't encode a player input because that didn't seem too interesting; instead the sample program just plays 20 rounds against itself and prints the result of each. No statistics are gathered or stored.
One feature that does exist is that there is a #define
to select whether the classic game or the enhanced "Rock-Paper-Scissors-Lizard-Spock" version is used. (With a bit more work, which I chose not to invest, one could allow for a fairly generic \$n\$-ary relation as the basis of the game.) The other feature worth noting is that the operator<
is deliberately private
because the \$<\$ operation implemented here does not represent a partially ordered set because the operation is not transitive. In simple terms, we would normally expect that \$(a < b) \land (b < c) \implies (a < c)\$ but this is expressly not true here.
I'd be interested in comments on those or any other design decisions or implementation details.
#include <iostream>
#include <string_view>
#include <array>
#include <stdexcept>
#include <random>
// define ENHANCED to non-zero for enhanced version with Spock, Lizard
#ifndef ENHANCED
#define ENHANCED 0
#endif
class RockPaperScissors
{
public:
RockPaperScissors(std::size_t num) {
if (num >= words.size()) {
throw std::range_error("invalid choice");
}
choice = num;
}
static RockPaperScissors random();
const std::string_view& vs(const RockPaperScissors &b) const;
friend std::ostream& operator<<(std::ostream& out, const RockPaperScissors &rps);
private:
bool operator==(const RockPaperScissors &b) const;
bool operator<(const RockPaperScissors &b) const;
#if ENHANCED
static constexpr std::array<std::string_view, 5> words{ "Rock", "Paper", "Scissors", "Spock", "Lizard", };
#else
static constexpr std::array<std::string_view, 3> words{ "Rock", "Paper", "Scissors" };
#endif
static constexpr std::array<std::string_view, 3> results{"LOSE", "TIE", "WIN" };
std::size_t choice;
};
std::ostream& operator<<(std::ostream& out, const RockPaperScissors &rps) {
return out << rps.words[rps.choice];
}
RockPaperScissors RockPaperScissors::random() {
static std::random_device rd;
static std::mt19937 gen{rd()};
static std::uniform_int_distribution<> dis(0, RockPaperScissors::words.size()-1);
std::size_t which{static_cast<std::size_t>(dis(gen))};
return RockPaperScissors{which};
}
bool RockPaperScissors::operator==(const RockPaperScissors &b) const {
return choice == b.choice;
}
bool RockPaperScissors::operator<(const RockPaperScissors &b) const {
return (choice + 1) % words.size() == b.choice || (choice + 3) % words.size() == b.choice;
}
const std::string_view& RockPaperScissors::vs(const RockPaperScissors &b) const {
if (*this == b) {
return results[1];
} else if (*this < b) {
return results[0];
}
return results[2];
}
int main()
{
for (int trials = 20; trials; --trials) {
auto a = RockPaperScissors::random();
auto b = RockPaperScissors::random();
std::cout << a << " vs. " << b << ": " << a << " " << a.vs(b) << "S!\n";
}
}
int trials
...unsigned int trials
maybe? \$\endgroup\$for
loop a bit or why the somewhat unusal design? \$\endgroup\$for
loop. \$\endgroup\$