#include <algorithm>
#include <iostream>
#include <sstream>
#include <string>
#include <deque>
#include <cstdlib>
#include <ctime>

using namespace std;

enum { ROCK = 0, PAPER = 1, SCISSORS = 2 };
enum { WIN  = 0, LOSS  = 1, DRAW     = 2 };

const string    moveStrTable[3] = { "rock",     "paper",       "scissors"     };
const string outcomeStrTable[3] = { "You win!", "You lose...", "It's a draw." };

const int outcomeTable[3][3] = {
    { DRAW, LOSS, WIN },
    { WIN, DRAW, LOSS },
    { LOSS, WIN, DRAW },
};

const int winMoveTable[3] = { PAPER, SCISSORS, ROCK };

struct RPSAI
{
    const deque<int> & humanMoves;

    RPSAI(const deque<int> & hm) : humanMoves(hm) { }

    struct Guess
    {
        int    move;
        double strength;

        Guess()                                       { }
        Guess(int m, double s) : move(m), strength(s) { }
    };

    Guess bestGuessForPattern(const deque<int> & pattern, bool & matchFound)
    {
        matchFound = false;

        double ms[3] = { 0.0 }; // move strength

        for (int i = 1; i < humanMoves.size() - pattern.size() + 1; ++ i)
        {
            if (pattern == deque<int>(humanMoves.begin() + i,
                                      humanMoves.begin() + i + pattern.size()))
            {
                ms[humanMoves[i - 1]] += 10.0 / i;
                matchFound             = true;
            }
        }

        int msi[3] = { 0, 1, 2 }; // move strength indexes

        int maxi = *max_element(msi, msi + 3, [ms](int a, int b) { return ms[a] < ms[b]; });

        return Guess(winMoveTable[maxi], ms[maxi] * 10.0 * pattern.size() * pattern.size());
    }

    int nextMove()
    {
        if (humanMoves.empty()) return rand() % 3;

        deque<Guess> guessList;

        for (int i = 1; i <= 20; ++ i)
        {
            if (humanMoves.size() < i) continue;

            bool matchFound = false;

            Guess guess = bestGuessForPattern(deque<int>(humanMoves.begin(),
                                                         humanMoves.begin() + i), matchFound);

            if (matchFound) guessList.push_back(guess);
        }

        if (guessList.empty()) return rand() % 3;

        return max_element(guessList.begin(), guessList.end(),
            [](const Guess & g1, const Guess & g2) { return g1.strength < g2.strength; })->move;
    }
};

bool getInt(int & n) { string str; getline(cin, str); return stringstream(str) >> n; }

int main()
{
    srand(time(0));

    int humanMove, computerMove;
    int outcome, outcomes[3] = { 0 };

    deque<int> humanMoves;

    RPSAI ai(humanMoves);

    while (true)
    {
        cout <<   "Wins   : " << outcomes[WIN ];
        cout << "\nLosses : " << outcomes[LOSS];
        cout << "\nDraws  : " << outcomes[DRAW];

        cout << "\n\n(1) [Rock] \n(2) [Paper] \n(3) [Scissors] \n(4) [Quit] \n\n>";

        int choice;

        //while (!getInt(choice) || choice < 1 || choice > 4) cout << ">";

        cin >> choice; // for ideone demo

        if (choice == 4) break;

        humanMove = choice - 1;
        computerMove = ai.nextMove();

        cout << "\nYou chose " << moveStrTable[   humanMove] << ".\n";
        cout <<     "I chose " << moveStrTable[computerMove] << ".\n";

        outcomes[outcome = outcomeTable[humanMove][computerMove]] ++;

        cout << '\n' << outcomeStrTable[outcome] << '\n' << endl;

        humanMoves.push_front(humanMove);
        if (humanMoves.size() > 100) humanMoves.pop_back();
    }
}