
#include <algorithm>
#include <iostream>
#include <map>
#include <numeric>
#include <vector>

constexpr uint8_t START = 0;
constexpr uint8_t RED = 1;
constexpr uint8_t PURPLE = 2;
constexpr uint8_t YELLOW = 3;
constexpr uint8_t BLUE = 4;
constexpr uint8_t ORANGE = 5;
constexpr uint8_t GREEN = 6;
constexpr uint8_t PINK = 7;
constexpr uint8_t LICORICE = 8;
constexpr uint8_t GUMMY_BEAR = 9;
constexpr uint8_t LOLLIPOP = 10;
constexpr uint8_t MINT = 11;
constexpr uint8_t ICE_CREAM = 12;
constexpr uint8_t END = 13;

using Color = uint16_t;
using BoardIndex = uint8_t;

struct Space {
    uint8_t next[2] = {0,0}; // indices of next possible spaces (0 means none)
    Color color : 14;
    Color shortcut : 1 = false; // does this space start a shortcut
};

struct Card {
    uint8_t size; // how many colors (1 or 2)
    Color color;
};

class BagOfWonder {
    std::vector<Card> cards;
public:
    Card draw() {
        auto idx = rand() % cards.size();
        Card c = cards[idx];
        cards.erase(cards.begin() + idx);
        return c;
    }

    auto empty() const {
        return cards.empty();
    }

    void reset() {
        cards.clear();
        add(5, {1, 1 << RED});
        add(4, {2, 1 << RED});

        add(5, {1, 1 << GREEN});
        add(3, {2, 1 << GREEN});
        add(5, {1, 1 << BLUE});
        add(3, {2, 1 << BLUE});
        add(5, {1, 1 << YELLOW});
        add(3, {2, 1 << YELLOW});
        add(5, {1, 1 << ORANGE});
        add(3, {2, 1 << ORANGE});
        add(5, {1, 1 << PURPLE});
        add(3, {2, 1 << PURPLE});

        add(1, {1, 1 << LICORICE});
        add(1, {1, 1 << GUMMY_BEAR});
        add(1, {1, 1 << LOLLIPOP});
        add(1, {1, 1 << MINT});
        add(1, {1, 1 << ICE_CREAM});
    }

    void add(int n, const Card &c) {
        for (int i = 0; i < n; ++i) {
            cards.push_back(c);
        }
    }

    BagOfWonder() {
        reset();
    }
};

struct State {
    BagOfWonder bow;
    std::vector<BoardIndex> players; // player positions

    State(uint8_t nplayers) : players(nplayers, 0) {}

    uint8_t num_players() const {
        return players.size();
    }
};


const Space BOARD[83] = {
    {{1,0}, 1 << START}, // 0

    {{2,0}, 1 << RED}, // 1
    {{3,0}, 1 << PURPLE},
    {{4,0}, 1 << YELLOW},
    {{5,0}, 1 << BLUE},
    {{6,0}, 1 << ORANGE},
    {{7,0}, 1 << GREEN},

    {{8,0}, 1 << RED}, // 7
    {{9,0}, 1 << PURPLE},
    {{10,0}, 1 << YELLOW},
    {{11,0}, 1 << BLUE},
    {{12,0}, 1 << ORANGE},
    {{13,0}, 1 << GREEN},

    {{14,18}, (1 << PURPLE) | (1 << ORANGE)}, // 13
    {{15,0}, 1 << BLUE},
    {{16,0}, 1 << GREEN},
    {{17,0}, 1 << PURPLE},
    {{22,0}, 1 << BLUE},

    {{19,0}, 1 << RED}, // 18
    {{20,0}, 1 << YELLOW},
    {{21,0}, 1 << ORANGE},
    {{22,0}, 1 << RED},

    {{23,0}, 1 << GREEN | 1 << YELLOW}, // 22
    {{24,0}, (1 << LICORICE) | (1 << PINK)},
    {{25,0}, 1 << GREEN},
    {{26,0}, 1 << RED},
    {{27,0}, 1 << PURPLE},
    {{28,0}, 1 << YELLOW},
    {{29,0}, 1 << BLUE},
    {{30,0}, 1 << ORANGE},

    {{31,0}, 1 << GREEN}, // 30
    {{32,0}, 1 << RED, true /*to 39*/},
    {{33,0}, (1 << GUMMY_BEAR) | (1 << PINK)},
    {{34,0}, 1 << PURPLE},
    {{35,0}, 1 << YELLOW},
    {{36,0}, 1 << BLUE},
    {{37,0}, 1 << ORANGE},
    {{38,0}, 1 << GREEN},
    {{39,0}, 1 << RED},
    {{40,0}, 1 << PURPLE},

    {{41,0}, 1 << YELLOW}, // 40
    {{42,0}, 1 << BLUE},
    {{43,0}, 1 << ORANGE},
    {{44,0}, (1 << LOLLIPOP) | (1 << PINK)},
    {{45,0}, 1 << RED},
    {{46,0}, 1 << PURPLE},
    {{47,0}, 1 << YELLOW},
    {{48,0}, 1 << BLUE},
    {{49,0}, 1 << ORANGE},
    {{50,0}, 1 << GREEN},

    {{51,0}, 1 << RED}, // 50
    {{52,0}, (1 << ICE_CREAM) | (1 << PINK)},
    {{53,0}, 1 << PURPLE},
    {{54,0}, 1 << YELLOW},
    {{55,0}, 1 << BLUE},
    {{56,0}, 1 << ORANGE},
    {{57,0}, 1 << GREEN},
    {{58,0}, 1 << RED, true /*to 63*/},
    {{59,0}, 1 << PURPLE},
    {{60,0}, 1 << YELLOW},

    {{61,0}, 1 << BLUE}, // 60
    {{62,0}, 1 << ORANGE},
    {{63,0}, 1 << GREEN},
    {{64,0}, 1 << RED},
    {{65,0}, 1 << PURPLE},
    {{66,0}, 1 << YELLOW},
    {{67,0}, (1 << MINT) | (1 << PINK)},
    {{68,0}, 1 << BLUE},
    {{69,0}, 1 << ORANGE},
    {{70,0}, 1 << GREEN},

    {{71,0}, 1 << RED}, // 70
    {{72,0}, 1 << PURPLE},
    {{73,0}, 1 << YELLOW},
    {{74,0}, 1 << BLUE},
    {{75,0}, 1 << ORANGE},
    {{76,0}, 1 << GREEN},
    {{77,0}, 1 << RED},
    {{78,0}, 1 << PURPLE},
    {{79,0}, 1 << YELLOW},
    {{80,0}, 1 << BLUE},

    {{81,0}, 1 << ORANGE}, // 80
    {{82,0}, 1 << GREEN},
    {{0,0}, 1 << END | 1 << RED | 1 << PURPLE | 1 << YELLOW | 1 << BLUE | 1 << ORANGE | 1 << GREEN},
};

constexpr BoardIndex BOARD_SIZE = sizeof(BOARD) / sizeof(BOARD[0]);

// faster than an std::set for what we're doing
template <typename T>
class Set {
    std::vector<T> data_;

    public:

    void insert(const T &t) {
        if (std::find(data_.begin(), data_.end(), t) == data_.end()) {
            data_.push_back(t);
        }
    }

    void erase(auto it) {
        data_.erase(it);
    }

    auto begin() const {
        return data_.begin();
    }

    auto empty() const {
        return data_.empty();
    }
};

std::vector<BoardIndex> reachable_list(BoardIndex pos) {

    static std::map<BoardIndex, std::vector<BoardIndex>> cache;

    auto it = cache.find(pos);
    if (it != cache.end()) {
        return it->second;
    }

    std::vector<BoardIndex> result;
    Set<BoardIndex> stack;

    for (auto n : BOARD[pos].next) {
        if (n) {
            stack.insert(n);
        }
    }

    while(!stack.empty()) {
        const BoardIndex bi = *stack.begin();
        stack.erase(stack.begin());
        result.push_back(bi);

        for (BoardIndex n : BOARD[bi].next) {
            if (n) {
                stack.insert(n);
            }
        }
    }

    std::sort(result.begin(), result.end());

    cache[pos] = result;
    return result;
}

struct Result {
    int winner;
    int landedOnAnother = 0;
    int emptyBagOfWonder = 0;
    int shortcuts = 0;
    int backwards = 0;
    int didntMove = 0;
    int draws = 0;
};

// return the index of the next reachable, unoccupied space of color c after board index bi
int next_reachable(Result &result, const BoardIndex bi, const Color c, State &s) {

    // assemble list of reachable positions from bi
    auto reachable = reachable_list(bi);

    // find first tile with that color that's after the current position
    // and not occupied
    int landedOnAnother = 0;
    for (auto j : reachable) {
        if (BOARD[j].color & c) {
            // check if j is occupied
            bool occupied = false;
            for (uint8_t pi = 0; pi < s.num_players(); ++pi) {
                if (s.players[pi] == j) {
                    occupied = true;
                    break;
                }
            }
            if (!occupied) {
                // only tally up how many times we landed on another player if we
                // actually move
                result.landedOnAnother += landedOnAnother;

                // shotcuts go to the same color, so just advance again from a shortcut
                if (BOARD[j].shortcut) {
                    ++result.shortcuts;
                    return next_reachable(result, j, c, s);
                }
                return j;
            } else {
                ++landedOnAnother;
            }
        }
    }

    return bi;
};

Result play(State &s) {

    // std::cout << "==== new game ====\n";
    Result result;

    uint8_t curPlayer = 0;
    while (true) {
        BoardIndex &playerPos = s.players[curPlayer];
        // std::cout << "player " << int(curPlayer) << " @ " << int(playerPos) << "\n";

        // refill BoW
        if (s.bow.empty()) {
            ++result.emptyBagOfWonder;
            s.bow.reset();
        }

        // draw a card
        const Card c = s.bow.draw();
        ++result.draws;

        for (uint8_t i = 0; i < c.size; ++i) {

            // std::cout << "color " << i << ": " << c.color << "\n";

            switch (c.color) {
                // for the candies, put the player 1 step behind
                // them, and then advance to the next available pink square

                case 1 << LICORICE:
                case 1 << GUMMY_BEAR:
                case 1 << LOLLIPOP:
                case 1 << MINT:
                case 1 << ICE_CREAM:

                for (BoardIndex j = 0; j < BOARD_SIZE; ++j) {
                    if (BOARD[j].color & c.color) {
                        if (j == playerPos) {
                            result.didntMove++;
                            break;
                        }

                        const BoardIndex before = playerPos;

                        // act as if the player was moved one spot behind the target
                        // pink space, and move to the next free pink space
                        playerPos = next_reachable(result, j-1, 1 << PINK, s);
                        // std::cout << "player " << int(curPlayer) << " -> " << int(playerPos) << "\n";

                        if (playerPos < before) {
                            ++result.backwards;
                        }

                        break;
                    }
                }
                break;

                default:
                playerPos = next_reachable(result, playerPos, c.color, s);
                // std::cout << "player " << int(curPlayer) << " -> " << int(playerPos) << "\n";
                break;
            }

            if (BOARD[playerPos].color & (1 << END)) {
                result.winner = curPlayer;
                return result;
            }
        }

        curPlayer = (curPlayer + 1) % s.num_players();
    }

    return result;
}

int main(void) {

    int ngames = 1000000;
    int nplayers = 4;
    std::vector<int> winCounts(nplayers, 0);
    std::vector<int> gameLengths;

    int backwards = 0;
    int draws = 0;
    int shortcuts = 0;
    int didntMove = 0;
    int emptyBagOfWonder = 0;
    int landedOnAnother = 0;
    int mostDraws = 0;
    int leastDraws = std::numeric_limits<int>::max();
    for (int i = 0; i < ngames; ++i) {

        if (i % 10000 == 0) {
            std::cout << i << "\n";
        }

        State s(nplayers);
        Result result = play(s);
        backwards += result.backwards;
        draws += result.draws;
        shortcuts += result.shortcuts;
        didntMove += result.didntMove;
        emptyBagOfWonder += result.emptyBagOfWonder;
        landedOnAnother += result.landedOnAnother;
        mostDraws = std::max(mostDraws, result.draws);
        leastDraws = std::min(leastDraws, result.draws);

        if (size_t(result.draws) + 1 > gameLengths.size()) {
            gameLengths.resize(result.draws + 1);
        }
        gameLengths[result.draws]++;
    
        winCounts[result.winner]++;

        // if (result.draws < 7) {
        //     exit(1);
        // }
    }

    std::cout << "games played:      " << ngames << std::endl;
    for (size_t pi = 0; pi < winCounts.size(); ++pi) {
        std::cout << "player " << pi << " won:      " << winCounts[pi] << std::endl;
    }

    std::cout << "total draws:       " << draws << std::endl;
    std::cout << "backwards moves:   " << backwards << std::endl;
    std::cout << "shortcuts taken:   " << shortcuts << std::endl;
    std::cout << "didn't move:       " << didntMove << std::endl;
    std::cout << "empty BoW:         " << emptyBagOfWonder << std::endl;
    std::cout << "landed on another: " << landedOnAnother << std::endl;
    std::cout << "longest game:      " << mostDraws << " draws" << std::endl;
    std::cout << "shortest game:     " << leastDraws << " draws" << std::endl;

    for (size_t i = 0; i < gameLengths.size(); ++i) {
        std::cout << i << ": " << gameLengths[i] << std::endl;
    }
}