Day 2

R
tidyverse
|>
Author

Josef Fruehwald

Published

December 2, 2022

Part 1

Setup

source: https://adventofcode.com/2022/day/2

Rock paper scissors setup

sessionInfo()
R version 4.2.1 (2022-06-23)
Platform: aarch64-apple-darwin20 (64-bit)
Running under: macOS Monterey 12.3

Matrix products: default
BLAS:   /Library/Frameworks/R.framework/Versions/4.2-arm64/Resources/lib/libRblas.0.dylib
LAPACK: /Library/Frameworks/R.framework/Versions/4.2-arm64/Resources/lib/libRlapack.dylib

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

attached base packages:
[1] stats     graphics  grDevices datasets  utils     methods   base     

loaded via a namespace (and not attached):
 [1] digest_0.6.30     jsonlite_1.8.3    magrittr_2.0.3    evaluate_0.17    
 [5] rlang_1.0.6       stringi_1.7.8     cli_3.4.1         renv_0.15.5      
 [9] rstudioapi_0.14   rmarkdown_2.17    tools_4.2.1       stringr_1.4.1    
[13] htmlwidgets_1.5.4 xfun_0.34         yaml_2.3.6        fastmap_1.1.0    
[17] compiler_4.2.1    htmltools_0.5.3   knitr_1.40       
library(tidyverse)
── Attaching packages ─────────────────────────────────────── tidyverse 1.3.2 ──
✔ ggplot2 3.4.0      ✔ purrr   0.3.5 
✔ tibble  3.1.8      ✔ dplyr   1.0.10
✔ tidyr   1.2.1      ✔ stringr 1.4.1 
✔ readr   2.1.3      ✔ forcats 0.5.2 
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
rock_paper_scissors_outcome <- function(player1, player2){
  ## Draw score
  if(player1 == player2){
    return(3)
  }
  ## p2 win conditions
  else if(player1 == "rock" & player2 == "paper"){
    return(6)
  }else if(player1 == "paper" & player2 == "scissors"){
    return(6)
  }else if(player1 == "scissors" & player2 == "rock"){
    return(6)
  }
  ## else lose
  else{
    return(0)
  }
}
# testing the function
tribble(~p1, ~p2,
        "rock", "rock",
        "rock", "paper",
        "scissors", "paper") |>
  mutate(
    outcome = map2(
      p1, 
      p2, 
      rock_paper_scissors_outcome
      ) |>
        simplify()
    )
# A tibble: 3 × 3
  p1       p2    outcome
  <chr>    <chr>   <dbl>
1 rock     rock        3
2 rock     paper       6
3 scissors paper       0

I’ll use a few different joins to map the first column and the second column to "rock", "paper" and "scissors", and also to map player 2’s move to a score.

p1_map <- 
  tribble(
    ~X1, ~p1,
    "A", "rock",
    "B", "paper",
    "C", "scissors"
  )

p2_map <-
  tribble(
    ~X2, ~p2,
    "X", "rock",
    "Y", "paper",
    "Z", "scissors"
  )

score_map <-
  tribble(
    ~score, ~p2,
    1, "rock",
    2, "paper",
    3, "scissors"
  )

Reading in the data.

strategy <- read_delim(
  "2022-12-2_assets/input.txt", 
  col_names = FALSE,
  delim = " "
  )
Rows: 2500 Columns: 2
── Column specification ────────────────────────────────────────────────────────
Delimiter: " "
chr (2): X1, X2

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.

It’s just a few joins, then rowwise mapping of the rock_paper_scissors_outcome() function to get the score components. Adding the columns together and summarizing finishes it off.

play_score <- 
  strategy |>
    left_join(p1_map) |>
    left_join(p2_map) |>
    left_join(score_map) |>
    mutate(win = map2(
        p1,
        p2,
        rock_paper_scissors_outcome
      ) |>
        simplify(),
      total_score = score + win
    )
Joining, by = "X1"
Joining, by = "X2"
Joining, by = "p2"
play_score |>
  summarise(total_score = sum(total_score))
# A tibble: 1 × 1
  total_score
        <dbl>
1       10624

Part 2

Need a new mapping of X2 to outcome

outcome_map <-
  tribble(~X2, ~outcome,
          "X", "lose",
          "Y", "draw",
          "Z", "win")

To get the move player two should make, I could do a lot of logical conjunctions in case_when(), like this.

```{r}
#| eval: false

case_when(outcome == "win" & p1 == "rock" ~ "paper",
          outcome == "win" & p2 == "scissors" ~ "rock",
          ...)
```

Instead, I’ll just use a named vector, and use player 1’s move to index either the winning move vector or the losing move vector.

win_vec = c(
  "rock" = "paper", 
  "paper" = "scissors",
  "scissors" = "rock"
)

lose_vec = c(
  "paper" = "rock", 
  "scissors" = "paper",
  "rock" = "scissors"
)

Setting up one big map. The unique combination of player 1’s move, the intended outcome, and player 2’s move, to be merged onto the data.

strategy_map <- 
  expand_grid(
    p1 = c("rock", "paper", "scissors"),
    outcome = c("lose", "draw", "win"),
  ) |>
    mutate(p2 = case_when(
        outcome == "draw" ~ p1,
        outcome == "win" ~ win_vec[p1],
        outcome == "lose" ~ lose_vec[p1]
      )
    )
strategy |>
  # reusing the score map from part 1
  left_join(p1_map) |>
  # now column 2 gets mapped to outcome
  left_join(outcome_map) |>
  # given the scores and outcomes, get player 2 moves
  left_join(strategy_map) |>
  # score player 2 moves
  left_join(score_map) |>
  # score plays
  mutate(outcome_score = map2(
      p1, 
      p2, 
      rock_paper_scissors_outcome
    ) |>
      simplify(),
    total_score = score + outcome_score
  ) |>
  # total score
  summarise(total_score = sum(total_score))
Joining, by = "X1"
Joining, by = "X2"
Joining, by = c("p1", "outcome")
Joining, by = "p2"
# A tibble: 1 × 1
  total_score
        <dbl>
1       14060

Just for fun

library(ggdark)
library(khroma)
library(showtext)
Loading required package: sysfonts
Loading required package: showtextdb
library(scales)

Attaching package: 'scales'
The following object is masked from 'package:purrr':

    discard
The following object is masked from 'package:readr':

    col_factor
font_add_google(name = "Mountains of Christmas", family = "christmas")
showtext_auto()
both_score <- 
  strategy |>
    mutate(turn = 1:n()) |>
    left_join(p1_map) |>
    left_join(outcome_map) |>
    left_join(strategy_map) |>
    left_join(score_map) |>
    left_join(score_map |> 
                transmute(p1 = p2,
                          p1_score = score)) |>
    mutate(outcome_score = map2(
        p1, 
        p2, 
        rock_paper_scissors_outcome
      ) |>
        simplify(),
      p1_outcome_score = abs(outcome_score-6)
    ) |>
    arrange(turn) |>
    mutate(
      p1_cumulative = cumsum(score + outcome_score),
      p2_cumulative = cumsum(p1_score + p1_outcome_score)
    ) |>
    select(turn, p1_cumulative, p2_cumulative) |>
    pivot_longer(
      p1_cumulative:p2_cumulative, 
      names_to = "player", 
      values_to = "score"
    )
Joining, by = "X1"
Joining, by = "X2"
Joining, by = c("p1", "outcome")
Joining, by = "p2"
Joining, by = "p1"
both_score |>
  ggplot(aes(turn, score, color = player))+
    geom_line(linewidth = 1) +
    geom_point(data = both_score |> filter(turn == max(turn)),
               size = 3) +
    scale_color_light(labels = c("elf", "me"))+
    scale_x_continuous(labels = label_comma())+
    scale_y_continuous(labels = label_comma())+
    dark_theme_grey()+
    labs(
      title = "Rock-Paper-Scissors outcome",
      subtitle = "the dang elves set me up!"
    )+
    theme(title = element_text(family = "christmas", size = 20),
          plot.subtitle = element_text(family = "sans", size = 10),
          legend.position = c(0.1, 0.75))
Inverted geom defaults of fill and color/colour.
To change them back, use invert_geom_defaults().

Figure 1: Score over turns