Simple Rock Paper Scissors in C++
Here are some things that may help you improve your code.

Simplify the code

This seems to be rather a lot of lines of code for a very simple game.  For example, the ask_guess and play_again both do essentially the same thing.  They show a prompt, wait for input and then check to assure the input is one of the valid choices.  Since it's done for two different things and has both associated data and functionality, that suggests an object.

Simplify winner detection

Each player has only three choices, and if we take them in the order of "rock, paper, scissors" then for any choice n, the choice n+1 wins, choice n-1 loses, and n means a tie (obviously this is all mod 3).  This allows for very simple mathematics to determine the winner.

Make all strings named constants

If you ever wanted to make, say, a German version of the game, it would be simple if all of the strings were in one location.  If they're all const, then it doesn't have much impact on the runtime speed and there really aren't that many strings.

A proposed alternative

Here's an alternative that incorporates all of these suggestions:

#include &lt;iostream&gt;
#include &lt;string&gt;
#include &lt;vector&gt;
#include &lt;algorithm&gt;
#include &lt;random&gt;
#include &lt;cctype&gt;

class Menu {
private:
    const std::string prompt; 
    const std::vector&lt;std::string&gt; choices;
    const std::string error_msg; 
public:
    Menu(const std::string &amp;prompt_string, const std::vector&lt;std::string&gt;&amp; items, const std::string &amp;err_msg)
    : prompt{prompt_string},
      choices{items},
      error_msg{err_msg}
    {}
    size_t size() const { return choices.size(); }
    const std::string&amp; operator[](int i) const { return choices[i]; }
    size_t get_choice(std::istream&amp; in=std::cin, std::ostream&amp; out=std::cout) const {
        bool first = true;
        auto it = choices.end();
        do {
            if (!first) {
                out &lt;&lt; error_msg;
            }
            out &lt;&lt; prompt;
            std::string result;
            std::getline(in, result);
            std::transform(result.begin(), result.end(), result.begin(), [](unsigned char ch){ return std::tolower(ch);});
            it = std::find(choices.begin(), choices.end(), result);
            first = false;
            in.clear();
            in.ignore(in.rdbuf()-&gt;in_avail());
        } while (it == choices.end());
        return std::distance(choices.begin(), it);
    }
};

int main() {
    const Menu rps{"Enter your choice, rock, paper, or scissors: ", 
        { "rock", "paper", "scissors"}, "Not valid input.\n"};
    const Menu again{"Do you want to play again? [y/n]: ", 
        {"y", "n"}, "Not valid input.\n"};
    const std::vector&lt;std::string&gt; results{
        "It's a tie!", "You win!", "You lose!"};
    const std::string title{"\n\n\tRock Paper Scissors Game\n\n"};
    const std::string youchose{"\n\nYou entered: "};
    const std::string computerchose{"\nComputer chose: "};
    std::mt19937 engine{ std::random_device()() };
    auto dist = std::uniform_int_distribution&lt;&gt;(0, rps.size()-1);
     // Game loop
    do {
        std::cout &lt;&lt; title;
        const auto user = rps.get_choice();
        const auto computer = dist(engine);
        std::cout &lt;&lt; youchose &lt;&lt; rps[user] 
            &lt;&lt; computerchose &lt;&lt; rps[computer] &lt;&lt; '\n'
            &lt;&lt; results[(user + rps.size() - computer) % rps.size()] 
            &lt;&lt; "\n\n";
    }
    while(!again.get_choice());
}