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