HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppModerate

Rock-Paper-Scissors-Lizard-Spock challenge in C++

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
scissorspaperchallengespockrocklizard

Problem

Here's my take at the Rock-Paper-Scissors-Lizard-Spock challenge. The outcomes are as follows:



  • Scissors cuts paper



  • paper covers rock



  • rock crushes lizard



  • lizard poisons Spock



  • Spock smashes scissors



  • scissors decapitate lizard



  • lizard eats paper



  • paper disproves Spock



  • Spock vaporizes rock



  • rock crushes scissors




I've decided to implement this in procedural form, partly because it's just one player against another. The program doesn't output any of the above outcomes, just generic ones, but I may try to figure out the former at another time. Points are awarded to the winner each turn with the total displayed upon termination. I've also used std::rand() instead of something "better" (such as std::mt19937 and std::uniform_int_distribution) since this is a simple program.

My main concerns:

  • I feel that there can be a good substitute to possibleMoves; it looks quite large.



  • I couldn't find a good STL function to do the searching in determineOutcome(), although what I have now may suffice.



  • Not enough/not good enough validation to avoid breaking the game.



Other than that, nothing else sticks out to me. Please do criticize anything you may find.

```
#include
#include
#include
#include
#include
#include
#include

enum Weapon { ROCK=1, PAPER, SCISSORS, LIZARD, SPOCK };
enum Outcome { WIN, LOSE, DRAW };

typedef std::pair PlayerMoves;

const std::array possibleMoves =
{ std::make_pair(SCISSORS, PAPER),
std::make_pair(PAPER, ROCK),
std::make_pair(ROCK, LIZARD),
std::make_pair(LIZARD, SPOCK),
std::make_pair(SPOCK, SCISSORS),
std::make_pair(SCISSORS, LIZARD),
std::make_pair(LIZARD, PAPER),
std::make_pair(PAPER, SPOCK),
std::make_pair(SPOCK, ROCK),
std::make_pair(ROCK, SCISSORS)
};

std::istream& operator>>(std::istream& in, Weapon& weapon)
{
int val;

if (in >> val)
weapon = static_cast(val);
else
throw std::l

Solution

The array does not seem like the logical choice for container.

You are going to spend most of the time looking things up. This suggests that you need some form of map. Because of its size I would just use std::map. But you only have the true values stored so we only need to check for existence so we can use std::set instead.

const std::set possibleMoves =
    { std::make_pair(SCISSORS, PAPER),
      std::make_pair(PAPER, ROCK),
      std::make_pair(ROCK, LIZARD),
      std::make_pair(LIZARD, SPOCK),
      std::make_pair(SPOCK, SCISSORS),
      std::make_pair(SCISSORS, LIZARD),
      std::make_pair(LIZARD, PAPER),
      std::make_pair(PAPER, SPOCK),
      std::make_pair(SPOCK, ROCK),
      std::make_pair(ROCK, SCISSORS)
    };


Your test is now simplified too:

Outcome determineOutcome(PlayerMoves const& playerMoves)
{
    if (playerMoves.first == playerMoves.second)
        return DRAW;

    return possibleMoves.find(playerMoves) == possibleMoves.end()
                  ?  LOSE
                  :  WIN;
}


I have a template to handle enum (so I don't need to re-write the same code all the time).

#include 
#include 
#include 
#include 

namespace Serializer
{
    template
    struct SerializeTraits
    {};

    template
    struct EnumSerializer
    {
        T value;
        EnumSerializer(T const& v) : value(v) {}
    };
    template
    struct EnumDeSerializer
    {
        T& value;
        EnumDeSerializer(T& v) : value(v) {}
    };

    template
    EnumSerializer makeEnumSerializer(T const& v) {return EnumSerializer(v);}
    template
    EnumDeSerializer makeEnumDeSerializer(T& v) {return EnumDeSerializer(v);}

    template
    std::ostream& operator const& val)
    {
        return stream ::names[static_cast(val.value)];
    }
    template
    std::istream& operator>>(std::istream& stream, EnumDeSerializer const& val)
    {
        typedef std::vector::const_iterator    Iter;

        std::string value;
        stream >> value;

        Iter find   = std::find(std::begin(SerializeTraits::names), std::end(SerializeTraits::names), value);
        if (find == std::end(SerializeTraits::names))
        { 
            stream.setstate(std::ios::failbit);
        }
        else
        {
            val.value   = static_cast(std::distance(std::begin(SerializeTraits::names), find));
        }

        return stream;
    }
}


To use it you just need to declare a SerializeTraits<> for the enum type. In your case it looks like this:

enum Weapon {ROCK=1, PAPER, SCISSORS, LIZARD, SPOCK};
namespace  Serializer
{
    template<>
    struct SerializeTraits
    {
        static const std::vector    names;
    };
}
const std::vector   Serializer::SerializeTraits::names = {"", "ROCK", "PAPER", "SCISSORS", "LIZARD", "SPOCK"};


Then reading and writting enum's become the same everywhere (and its handeled for you).

int main()
{
    using namespace Serializer;
    std::cout > makeEnumDeSerializer(x);

    std::cout << makeEnumSerializer(x);
}


Also has the advantage that as you update the Weapons (and the associated name string) you do not need to modify code it will always stay in sync.

Answers to comments:


Unfortunately, I cannot std::set or std::vector with initialization I still don't have full access to C++11.

Alternative to Initialization list

class MySetWithInit: public std::list
{
    public: MySetWithInit()
    {
      insert(std::make_pair(SCISSORS, PAPER));
      insert(std::make_pair(PAPER, ROCK));
      insert(std::make_pair(ROCK, LIZARD));
      insert(std::make_pair(LIZARD, SPOCK));
      insert(std::make_pair(SPOCK, SCISSORS));
      insert(std::make_pair(SCISSORS, LIZARD));
      insert(std::make_pair(LIZARD, PAPER));
      insert(std::make_pair(PAPER, SPOCK));
      insert(std::make_pair(SPOCK, ROCK));
      insert(std::make_pair(ROCK, SCISSORS));
    }
} possibleMoves; // Note variable declaration here.
                 // This indicates type is being declared for this usage
                 // only.


Note: normally you should not inherit from standard containers. This is a case where it is OK as there are no plans to re-use the type and it is not being deleted via a pointer to the base class. This should be placed in *.cpp file not a header.


Until then, I'll either have to stay with std::array or just retrieve the set from a function (which I know is not efficient).

Is initialization from a function less effecient.

No. Not really. All modern C++ compilers have RVO and NRVO optimization as standard. This means if a function returns by value the compiler will more than likely elide the copy of the value out of the function, ie it effectively builds the value in place and no intermediate copies are used (this works through multiple levels of copying).


I don't quite know what all this does, but I'll still try to learn.

Good.

But really there are just a couple of common tricks.

-
Template traits class.

The templatized version holds no

Code Snippets

const std::set<PlayerMove> possibleMoves =
    { std::make_pair(SCISSORS, PAPER),
      std::make_pair(PAPER, ROCK),
      std::make_pair(ROCK, LIZARD),
      std::make_pair(LIZARD, SPOCK),
      std::make_pair(SPOCK, SCISSORS),
      std::make_pair(SCISSORS, LIZARD),
      std::make_pair(LIZARD, PAPER),
      std::make_pair(PAPER, SPOCK),
      std::make_pair(SPOCK, ROCK),
      std::make_pair(ROCK, SCISSORS)
    };
Outcome determineOutcome(PlayerMoves const& playerMoves)
{
    if (playerMoves.first == playerMoves.second)
        return DRAW;

    return possibleMoves.find(playerMoves) == possibleMoves.end()
                  ?  LOSE
                  :  WIN;
}
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>

namespace Serializer
{
    template<typename T>
    struct SerializeTraits
    {};

    template<typename T>
    struct EnumSerializer
    {
        T value;
        EnumSerializer(T const& v) : value(v) {}
    };
    template<typename T>
    struct EnumDeSerializer
    {
        T& value;
        EnumDeSerializer(T& v) : value(v) {}
    };

    template<typename T>
    EnumSerializer<T> makeEnumSerializer(T const& v) {return EnumSerializer<T>(v);}
    template<typename T>
    EnumDeSerializer<T> makeEnumDeSerializer(T& v) {return EnumDeSerializer<T>(v);}

    template<typename T>
    std::ostream& operator<<(std::ostream& stream, EnumSerializer<T> const& val)
    {
        return stream << SerializeTraits<T>::names[static_cast<int>(val.value)];
    }
    template<typename T>
    std::istream& operator>>(std::istream& stream, EnumDeSerializer<T> const& val)
    {
        typedef std::vector<std::string>::const_iterator    Iter;

        std::string value;
        stream >> value;

        Iter find   = std::find(std::begin(SerializeTraits<T>::names), std::end(SerializeTraits<T>::names), value);
        if (find == std::end(SerializeTraits<T>::names))
        { 
            stream.setstate(std::ios::failbit);
        }
        else
        {
            val.value   = static_cast<T>(std::distance(std::begin(SerializeTraits<T>::names), find));
        }

        return stream;
    }
}
enum Weapon {ROCK=1, PAPER, SCISSORS, LIZARD, SPOCK};
namespace  Serializer
{
    template<>
    struct SerializeTraits<Weapon>
    {
        static const std::vector<std::string>    names;
    };
}
const std::vector<std::string>   Serializer::SerializeTraits<Weapon>::names = {"", "ROCK", "PAPER", "SCISSORS", "LIZARD", "SPOCK"};
int main()
{
    using namespace Serializer;
    std::cout << makeEnumSerializer(ROCK);

    Weapon  x;
    std::cin >> makeEnumDeSerializer(x);

    std::cout << makeEnumSerializer(x);
}

Context

StackExchange Code Review Q#36435, answer score: 12

Revisions (0)

No revisions yet.