#include #include #include #include #include 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 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 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 class Set { std::vector 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 reachable_list(BoardIndex pos) { static std::map> cache; auto it = cache.find(pos); if (it != cache.end()) { return it->second; } std::vector result; Set 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 winCounts(nplayers, 0); std::vector 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::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; } }