library(tidyverse)
library(readxl)
<- read_excel("candyland_data.xlsx", sheet = 1) |>
board mutate(special = replace_na(special, "none")) |>
print()
<- function(.color) {
create_one_color_card c(rep(paste0("double ", .color), 4), rep(paste0("single ", .color), 6))
}<- c("cupcake", "ice cream cone", "gummy star", "gingerbread man", "lollipop", "popsicle", "chocolate truffle")
candy_cards <- function() {
create_cards tibble(card = c(create_one_color_card("red"),
create_one_color_card("purple"),
create_one_color_card("yellow"),
create_one_color_card("blue"),
create_one_color_card("orange"),
create_one_color_card("green"),
|>
candy_cards)) rowid_to_column("card_id")
}<- create_cards() cards
Just copy over from chutes and ladders
set.seed(250304)
<- function(game_num = 0) {
simulate_game
<- create_cards()
cards
# Declare space for the full game.
<- nrow(cards)
n_cards <- tibble(turn_num = 1:(n_cards*2),
turns start = NA,
card_id = c(sample(1:n_cards, n_cards, replace = FALSE), sample(1:n_cards, n_cards, replace = FALSE)),
shortcut = NA,
end = NA) |>
# Get the card names
left_join(cards, by = "card_id")
# Loop until the game is over
<- 1
i <- TRUE
keep_playing while(keep_playing) {
# Step 1: Start at zero
if (i == 1) {
$start[[i]] <- 0
turns
# Otherwise, start where the last turn ended.
else {
} $start[[i]] <- turns$end[[i - 1]]
turns
}
# Step 2: This is where the game actually happens.
# If it's a candy card, go straight there.
if (turns$card[[i]] %in% candy_cards) {
$end[[i]] <- board[board$special == turns$card[[i]],]$tile
turns
# If it's not, find the next colors.
else {
} <- str_extract(turns$card[[i]], "\\w+\\Z")
card_color <- str_extract(turns$card[[i]], "\\A\\w+")
card_amount
# move to the next spot
<- board |>
eligible_spots filter(tile > turns$start[[i]],
== card_color) |>
color pull(tile)
<- length(eligible_spots)
n_eligible_spots
if (n_eligible_spots >= 2 & card_amount == "single") {
$end[[i]] <- eligible_spots[[1]]
turnselse if (n_eligible_spots >= 2 & card_amount == "double") {
} $end[[i]] <- eligible_spots[[2]]
turnselse if (n_eligible_spots == 1 & card_amount == "single") {
} $end[[i]] <- eligible_spots[[1]]
turnselse {
} $end[[i]] <- 133
turns
}
}
# Do the shortcuts.
if (turns$end[[i]] == 4) {
$end[[i]] <- 60
turns$shortcut[[i]] <- "peppermint pass"
turnselse if (turns$end[[i]] == 29) {
} $end[[i]] <- 41
turns$shortcut[[i]] <- "gummy pass"
turns
}
# Step 4: Check if it's game over.
# run out of cards
if (i >= c(n_cards*2)) {
<- FALSE
keep_playing # win
else if (turns$end[[i]] >= max(board$tile)) {
} <- FALSE
keep_playing else {
} <- i + 1
i
}
}
%>%
turns filter(turn_num <= i) %>%
return()
}simulate_game()
Now simulate lots of games!
# Takes 1.8 minutes for 10K simulations.
<- Sys.time()
start_time set.seed(250304)
<- tibble(game_num = 1:10000) |>
games mutate(game = map(game_num, simulate_game)) |>
unnest(cols = c(game))
Sys.time() - start_time
::beep() beepr
Results
Now that we have the simulation done, let’s take a look at the results!
<- games %>%
games_summary summarize(turns = max(turn_num),
n_candies = sum(card %in% candy_cards),
n_singles = sum(str_detect(card, "single")),
n_doubles = sum(str_detect(card, "double")),
n_shortcuts = sum(!is.na(shortcut)),
.by = game_num) |>
print()
summary(games_summary)
First, let’s look at the number of turns
ggplot(games_summary, aes(turns)) +
geom_histogram(binwidth = 1)
ggplot(games_summary, aes(n_shortcuts)) +
geom_histogram(binwidth = 1)
ggplot(games_summary, aes(n_candies)) +
geom_histogram(binwidth = 1)
ggplot(games_summary, aes(n_singles)) +
geom_histogram(binwidth = 1)
ggplot(games_summary, aes(n_doubles)) +
geom_histogram(binwidth = 1)
|>
games filter(card %in% candy_cards) |>
mutate(diff = end - start) |>
ggplot(aes(diff)) +
geom_histogram(binwidth = 1)
|>
games filter(end != 133) |>
ggplot(aes(end)) +
geom_histogram(binwidth = 1)
|>
games add_count(name = "n_turns", .by = game_num) |>
filter(n_turns == max(n_turns))
Visualize the board
<- function(df, .n, direction) {
add_turn <- df %>% filter(row_number() < .n)
before_turn <- df %>% filter(row_number() == .n)
pivot_point <- df %>% filter(row_number() > .n)
after_turn
<- pivot_point %>% pull(x)
pivot_x <- pivot_point %>% pull(y)
pivot_y
if (direction == "left_north") {
<- after_turn %>%
after_turn mutate(y = y + (x - pivot_x),
x = pivot_x)
else if (direction == "left_west") {
} <- after_turn %>%
after_turn mutate(x = pivot_x - (y - pivot_y),
y = pivot_x + 1)
else if (direction == "right_north") {
} <- after_turn %>%
after_turn mutate(y = y + (pivot_x - x),
x = pivot_x)
else if (direction == "right_east") {
} <- after_turn %>%
after_turn mutate(x = x + (y - pivot_y),
y = pivot_y)
}bind_rows(before_turn, pivot_point, after_turn)
}
<- candyland %>%
layout mutate(x = 1:nrow(.),
y = 1) %>%
add_turn(16, "left_north") %>%
# add_turn(8, "left_west") %>%
# add_turn(11, "right_north") %>%
# add_turn(14, "right_east") %>%
print()
ggplot(layout, aes(x, y, color = color)) +
# geom_point(shape = "square") +
geom_text(aes(label = tile)) +
scale_color_identity() +
coord_fixed()
Sandbox
Can I do a for loop and just exit
?
set.seed(250304)
<- function(game_num = 0) {
simulate_game
# Declare space for the full game.
<- length(cards)
n_cards <- tibble(turn_num = 1:(n_cards*2),
turns start = NA,
card = c(sample(cards, replace = FALSE), sample(cards, replace = FALSE)),
shortcut = NA,
end = NA)
# Loop until the game is over
for (i in 1:nrow(turns)) {
# Step 1: Start at zero
if (i == 1) {
$start[[i]] <- 0
turns
# Otherwise, start where the last turn ended.
else {
} $start[[i]] <- turns$end[[i - 1]]
turns
}
# Step 2: This is where the game actually happens.
# If it's a candy card, go straight there.
if (turns$card[[i]] %in% candy_cards) {
$end[[i]] <- board[board$special == turns$card[[i]],]$tile
turns
# If it's not, find the next colors.
else {
} <- str_extract(turns$card[[i]], "\\w+\\Z")
card_color <- str_extract(turns$card[[i]], "\\A\\w+")
card_amount
# move to the next spot
<- board |>
eligible_spots filter(tile > turns$start[[i]],
== card_color) |>
color pull(tile)
<- length(eligible_spots)
n_eligible_spots
if (n_eligible_spots >= 2 & card_amount == "single") {
$end[[i]] <- eligible_spots[[1]]
turnselse if (n_eligible_spots >= 2 & card_amount == "double") {
} $end[[i]] <- eligible_spots[[2]]
turnselse if (n_eligible_spots == 1 & card_amount == "single") {
} $end[[i]] <- eligible_spots[[1]]
turnselse {
} $end[[i]] <- 133
turns
}
}
# Do the shortcuts.
if (turns$end[[i]] == 4) {
$end[[i]] <- 60
turns$shortcut[[i]] <- "peppermint pass"
turnselse if (turns$end[[i]] == 29) {
} $end[[i]] <- 41
turns$shortcut[[i]] <- "gummy pass"
turns
}
cat(turns$end[[i]], " ")
# Step 4: Check if it's game over.
if (i >= c(n_cards*2)) {
<- TRUE
keep_playing else if (turns$end[[i]] >= max(board$tile)) {
} <- TRUE
keep_playing
}
}
}simulate_game() |>
print()