R/decklist_handling.R

Defines functions parse_decklist gen_int_decklist df_to_simple_decklist simple_decklist_to_df

Documented in df_to_simple_decklist gen_int_decklist parse_decklist simple_decklist_to_df

#' parse simple decklist format into dataframe
#'
#' Intended for compatibility of the original decklist format and the more R native use
#' of dataframes
#'
#' @param decklist character vector of decklist with format '\{count\}:\{cardcode\}'
#' @returns dataframe with character column \emph{cardcode} and numeric column \emph{count}
#' @examples
#' lordecks:::simple_decklist_to_df(c("3:01PZ008", "3:01PZ040"))
#' # returns  :
#' #   count cardcode
#' # 1     3  01PZ008
#' # 2     3  01PZ040
simple_decklist_to_df <- function(decklist) {
  has_colon <- stringr::str_detect(decklist, ":")
  if(sum(has_colon) < length(decklist)) {
    stop(glue::glue("Ill-formated entries: {glue::glue_collapse(decklist[!has_colon], sep = ', ')}"))
  }
  purrr::map_dfr(decklist, function(x) {
    count_card <- stringr::str_split(x, ":")[[1]]
    data.frame("count" = as.numeric(count_card[1]),
               "cardcode" = as.character(count_card[2]))
  })
}

#' parse dataframe decklist into simple format
#'
#' Intended for compatibility of the original decklist format and the more R native use
#' of dataframes
#'
#' @param decklist_df dataframe with character column \emph{cardcode} and numeric column \emph{count}
#' @returns character vector of decklist with format '\{count\}:\{cardcode\}'
df_to_simple_decklist <- function(decklist_df) {
  as.character(glue::glue("{decklist_df$count}:{decklist_df$cardcode}"))
}




#' translate decklist of card ID and count into LoR custom encoding
#'
#' LoR uses a custom encoding scheme (\href{https://github.com/RiotGames/LoRDeckCodes}{laid out here})
#' to store information about the cards (and their count) in any given deck as an
#' integer vector. This function takes care of this.
#'
#' @param decklist dataframe with character column \emph{cardcode} adn numeric column \emph{count}
#' @returns integer representation of deck code
#' @importFrom rlang .data
#' @examples
#' #minimalist example
#' min_deck <- data.frame(
#'   "cardcode" = c("01PZ008", "01PZ040"),
#'   "count" = c(3, 3)
#' )
#'
#' lordecks:::gen_int_decklist(min_deck)
#'
#' # returns  : "17" "1" "2" "1" "4" "8" "40" "0" "0"
#' # version/format 17 ("00010001"), last 4 bits are version (=1)
#' # 1 group of 3 copies
#' #    2 cards for combination of
#' #  1 set 1   and
#' #  4 faction 4 (PZ)
#' #      8   card code 01PZ008
#' #     40   card code 01PZ040
#' # 0 group of 2 copies
#' # 0 group if 1 copy
gen_int_decklist <- function(decklist) {

  faction_lut <- get_faction_lut()

  if(!is.data.frame(decklist) |
     !("cardcode" %in% colnames(decklist)) |
     !("count" %in% colnames(decklist))) {
    stop("decklist needs be dataframe containing columns `cardcode`and `count`")
  }

  if(sum(stringr::str_length(decklist$cardcode) == 7) != length(decklist$cardcode)) {
    stop("invalid cardcode(s): string length")
  }

  if(any(decklist$count < 1)) {
    stop("invalid card count: no listed cards may occur less than once")
  }

  #ensure all count levels are present
  if(sum(decklist$count == 1) == 0) {
    decklist <- decklist %>% dplyr::bind_rows(
      data.frame(count = 1, cardcode = "")
    )
  }
  if(sum(decklist$count == 2) == 0) {
    decklist <- decklist %>% dplyr::bind_rows(
      data.frame(count = 2, cardcode = "")
    )
  }
  if(sum(decklist$count == 3) == 0) {
    decklist <- decklist %>% dplyr::bind_rows(
      data.frame(count = 3, cardcode = "")
    )
  }


  decklist <- decklist %>%
    dplyr::mutate(
      faction = stringr::str_match(.data$cardcode, "[A-Z]{2}")[, 1],
      set = as.integer(stringr::str_match(.data$cardcode, "^[0-9]{2}")),
      card_number = as.integer(stringr::str_match(.data$cardcode, "[0-9]{3}$"))
    )

  decklist <- decklist %>%
    dplyr::left_join(faction_lut %>%
                       dplyr::select(.data$Integer_Identifier, .data$Faction_Identifier, .data$Version),
              by = c("faction" = "Faction_Identifier"))

  max_version <- max(decklist$Version, na.rm = TRUE)

  #first byte
  # encode the minimal supported library version
  version_format <- dplyr::case_when(
    max_version == 1 ~ 17, #i.e. 00010001 for version 1
    max_version == 2 ~ 18, #i.e. 00010010 for version 2
    max_version == 3 ~ 19, #i.e. 00010011 for version 3
    max_version == 4 ~ 20, #i.e. 00010100 for version 4
    max_version == 5 ~ 21, #i.e. 00010101 for version 5
    TRUE ~ 21 #fallback to latest version
  )

  int_out <- c(version_format)

  # minimalist example : "17" "1" "2" "1" "4" "8" "40" "0" "0"
  # version/format 17 ("00010001"), last 4 bits are version (1)
  # 1 group of 3 copies
  #   2 cards for combination of
  #   1 set 1   and
  #   4 faction 4 (PZ)
  #      8   card code 01PZ008
  #     40   card code 01PZ040
  # 0 group of 2 copies
  # 0 group if 1 copy

  # for 1-3 copies
  #-----------
  grouped_sorted_deck <- decklist %>%
    dplyr::filter(.data$count < 4) %>%
    dplyr::group_by(.data$count, .data$set, .data$faction) %>%
    dplyr::mutate(group_n = dplyr::n()) %>%
    # The sorting convention of this encoding scheme is
    # First by the number of set/faction combinations in each top-level list
    # Second by the alphanumeric order of the card codes within those lists.
    dplyr::arrange(dplyr::desc(.data$count), .data$group_n, .data$cardcode)

  output_by_count <- grouped_sorted_deck %>%
    dplyr::transmute(
      group_n = .data$group_n,
      Integer_Identifier = .data$Integer_Identifier,
      set = .data$set,
      cardid_ints = glue::glue_collapse(.data$card_number, sep = ", ")
    ) %>%
    unique() %>%
    dplyr::mutate(group_ints = glue::glue("{group_n}, {set}, {Integer_Identifier}, {cardid_ints}")) %>%
    dplyr::group_by(.data$count) %>%
    dplyr::summarize(
      toplevel_n = dplyr::n(),
      toplevel_ints = glue::glue_collapse(.data$group_ints, sep = ", "),
      .groups = "keep"
    ) %>%
    dplyr::transmute(top_ints = dplyr::case_when(stringr::str_detect(.data$toplevel_ints, "NA") ~ "0",
                                   # NA's should should only ever show up here
                                   # when we added invalid cardcodes for missing count levels
                                   TRUE ~ as.character(glue::glue("{toplevel_n}, {toplevel_ints}"))))

  int_out_3 <- (output_by_count %>% dplyr::filter(.data$count == 3) %>% dplyr::pull(.data$top_ints) %>%
                  stringr::str_split(pattern = ", "))[[1]]
  int_out_2 <- (output_by_count %>% dplyr::filter(.data$count == 2) %>% dplyr::pull(.data$top_ints) %>%
                  stringr::str_split(pattern = ", "))[[1]]
  int_out_1 <- (output_by_count %>% dplyr::filter(.data$count == 1) %>% dplyr::pull(.data$top_ints) %>%
                  stringr::str_split(pattern = ", "))[[1]]

  int_out <- c(int_out, int_out_3, int_out_2, int_out_1)

  # append for N > 3 cases
  # this will only happen in Limited and special game modes.
  # the encoding is simply [count] [cardcode]
  # simply sorted by card code
  if(max(decklist$count, na.rm = TRUE) > 3) {
    special_cases <- decklist %>%
      dplyr::filter(.data$count > 3) %>%
      dplyr::arrange(.data$cardcode) %>%
      dplyr::mutate(card_ints = glue::glue("{count}, {set}, {Integer_Identifier}, {card_number}"))

    for(c in special_cases$card_ints) {
      int_out <- c(int_out, stringr::str_split(c, pattern = ", ")[[1]])
    }

  }

  strtoi(int_out)

}


#' Parse integer representation of decklist
#' returns
#'
#' LoR uses a custom encoding scheme (\href{https://github.com/RiotGames/LoRDeckCodes}{laid out here})
#' to store information about the cards (and their count) in any given deck as an
#' integer vector. This function takes the integer vector and translates it
#' into a readable deck list with actual card codes and factions.
#'
#' @param cardinfo integer vector representing the deck list (without version info)
#' @returns dataframe with cardcode, count, faction, set and card number
#' @importFrom rlang .data
#' @examples
#' #minimalist example
#'
#' lordecks:::parse_decklist(c(1, 2, 1, 4, 8, 40, 0, 0))
#'
#' # returns  :
#' #   cardcode count faction set card_number
#' # 1  01PZ008     3      PZ   1         008
#' # 2  01PZ040     3      PZ   1         040
#' #
#' #
#' # 1 group of 3 copies
#' #    2 cards for combination of
#' #  1 set 1   and
#' #  4 faction 4 (PZ)
#' #      8   card code 01PZ008
#' #     40   card code 01PZ040
#' # 0 group of 2 copies
#' # 0 group if 1 copy
parse_decklist <- function(cardinfo) {

  faction_lut <- get_faction_lut()

  cardinfo <- as.integer(cardinfo)

  # minimalist example : "1" "2" "1" "4" "8" "40" "0" "0"
  # 1 group of 3 copies
  #   2 cards for combination of
  #   1 set 1   and
  #   4 faction 4 (PZ)
  #      8   card code 01PZ008
  #     40   card code 01PZ040
  # 0 group of 2 copies
  # 0 group if 1 copy

  if(any(is.na(cardinfo)|is.null(cardinfo))) {
    stop("at least one missing value in decoded decklist")
  }

  decklist <- data.frame("cardcode" = NULL,
                         "count" = NULL,
                         "faction" = NULL,
                         "set" = NULL,
                         "card_number" = NULL)

  for(i in 3:1) {

    n_of_groups <- cardinfo[1]
    cardinfo <- cardinfo[-1]

    if(n_of_groups > 0) {

      for(j in 1:n_of_groups) {

        n_in_group <- cardinfo[1]
        cur_set <- cardinfo[2]
        cur_faction <- faction_lut[faction_lut$Integer_Identifier==cardinfo[3], "Faction_Identifier"]

        for(k in 4:(n_in_group + 3)) {
          card_id <- sprintf('%03d', cardinfo[k])
          cardcode <- glue::glue("{sprintf('%02d', cur_set)}{cur_faction}{card_id}")

          decklist <- decklist %>%
            dplyr::bind_rows(data.frame("cardcode" = cardcode,
                                        "count" = i,
                                        "faction" = cur_faction,
                                        "set" = cur_set,
                                        "card_number" = card_id))
        }

        cardinfo <- cardinfo[-1:(-(n_in_group + 3))]
      }

    }

  }



  while(length(cardinfo) > 0) {
    #the remainder of the deck code is comprised of entries for cards with counts >= 4
    #this will only happen in Limited and special game modes.
    #the encoding is simply [count] [cardcode]

    count4plus <- cardinfo[1]
    cur_set <- cardinfo[2]
    cur_faction <- faction_lut[faction_lut$Integer_Identifier==cardinfo[3], "Faction_Identifier"]
    card_id <- sprintf('%03d', cardinfo[4])
    cardcode <- glue::glue("{sprintf('%02d', cur_set)}{cur_faction}{card_id}")

    decklist <- decklist %>%
      dplyr::bind_rows(data.frame("cardcode" = cardcode,
                                  "count" = count4plus,
                                  "faction" = cur_faction,
                                  "set" = cur_set,
                                  "card_number" = card_id))

    cardinfo <- cardinfo[-1:-4]
  }

  decklist  <- decklist %>% dplyr::mutate(cardcode = as.character(cardcode))

  decklist

}
pholzmgit/lordecks documentation built on May 23, 2022, 11:03 a.m.