initial commit
This commit is contained in:
23
CMakeLists.txt
Normal file
23
CMakeLists.txt
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.5)
|
||||||
|
project(Candland)
|
||||||
|
add_executable(candyland candyland.cpp)
|
||||||
|
set_target_properties(candyland PROPERTIES CXX_STANDARD 20)
|
||||||
|
set_target_properties(candyland PROPERTIES CXX_STANDARD_REQUIRED TRUE)
|
||||||
|
|
||||||
|
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "AppleClang")
|
||||||
|
target_compile_options(candyland PRIVATE
|
||||||
|
-Wall
|
||||||
|
-Wextra
|
||||||
|
-Wpedantic
|
||||||
|
-Wshadow
|
||||||
|
-Wnon-virtual-dtor
|
||||||
|
-Wold-style-cast
|
||||||
|
-Wcast-align
|
||||||
|
-Wunused
|
||||||
|
-Woverloaded-virtual
|
||||||
|
# -Wconversion
|
||||||
|
-Wnull-dereference
|
||||||
|
-Wdouble-promotion
|
||||||
|
-Wformat=2
|
||||||
|
)
|
||||||
|
endif()
|
||||||
443
candyland.cpp
Normal file
443
candyland.cpp
Normal file
@@ -0,0 +1,443 @@
|
|||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user