PokéRogue Random Team Selector in R

R
pokerogue
functions
project
Automate Your Pokémon Team Creation with R.
Author

Carlos Fernández

Published

June 22, 2024

Introduction

Welcome to the PokéRogue Random Team Selector project!

PokéRogue is a fan-made roguelike game where players can select a team of up to 6 Starter Pokémon at the beginning of any run. Players have a total budget of either 10 or 15 points, depending on the game mode, to spend on these initial Starters. The more powerful the Pokémon, the more points they cost. Additionally, three random Starter Pokémon receive Pokérus each day, a condition that allows for faster experience gain and leveling up. Shiny Pokémon variants improve the player’s luck in obtaining better items. You can play PokéRogue for free here.

PokéRogue Starter selection screen

In this project, we will use R to create functions that randomly generate Pokémon teams based on the criteria mentioned above. We’ll start by loading the necessary libraries and data, then proceed to define various helper functions and main team creation functions. Finally, we’ll demonstrate how to create different teams using these functions.

Loading Libraries

We start by loading the necessary libraries using pacman::p_load. This ensures that all required packages are installed and loaded.

pacman::p_load(
  tidyverse,
  here,
  rio
)

Loading Data

Next, we load the dataset containing the list of available Pokémon. The data is imported from an Excel file using the rio::import function. This dataset includes information on whether the Pokémon has been caught, if it has a Shiny version, or if it has Pokérus.

data_raw <- import(here("posts", "pokerogue_team", "Pokerogue starters list.xlsx"))

data_catched <- data_raw |>
  filter(Catched == "Yes") |>
  select(Starter, Gen, Dex, Cost, Shiny, Pokerus)

Let’s take a quick look at the imported data using glimpse.

glimpse(data_catched)
Rows: 459
Columns: 6
$ Starter <chr> "Bulbasaur", "Charmander", "Squirtle", "Caterpie", "Weedle", "…
$ Gen     <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,…
$ Dex     <dbl> 1, 4, 7, 10, 13, 16, 19, 21, 23, 25, 27, 29, 32, 37, 39, 41, 4…
$ Cost    <dbl> 3, 3, 3, 1, 1, 2, 1, 1, 2, 4, 2, 3, 3, 3, 4, 2, 2, 1, 2, 3, 4,…
$ Shiny   <dbl> NA, NA, NA, NA, NA, NA, 1, 1, NA, NA, NA, NA, NA, NA, NA, NA, …
$ Pokerus <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…

Creating Functions

We define several helper functions to handle different aspects of the team selection process.

Select Random Pokémon

The select_random_pokemon function is designed to select a Pokémon from the available dataset based on specific criteria such as budget points, Pokérus status, cost, or exact cost.

select_random_pokemon <- function(df, points, pokerus = FALSE, expensive = FALSE, exact = FALSE) {

  # Selects an affordable Pokémon with Pokérus
  if (pokerus == TRUE){
      data_filtered <- df |> filter(Cost <= points, Pokerus == "Yes")
  } 
  
  # Selects an expensive Pókemon (all available points)
  else if (expensive == TRUE) {
      data_filtered <- df |> filter(Cost == min(points, max(Cost)))
  }

  # Selects a Pokémon with an exact cost
  else if (exact == TRUE) {
    data_filtered <- df |> filter(Cost == points)
  }
  
  else {
    data_filtered <- df |> filter(Cost <= points)
  }
  
  pokemon_selected <- slice_sample(data_filtered)
  pokemon_selected
}

Count Shiny Pokémon

The count_shiny_pokemon function counts the number of available shiny Pokémon in the dataset.

count_shiny_pokemon <- function(df) {
  data_filtered <- df |> filter(!is.na(Shiny))
  n_shiny <- nrow(data_filtered)
  n_shiny
}

Update Available Pokémon

The update_available_pokemon function updates the list of available Pokémon by removing the ones that have already been selected.

update_available_pokemon <- function(data_available, list_pokemon, already_chosen, current_pokemon) {
  new_pokemon_name <- list_pokemon[[current_pokemon]]$Starter
  already_chosen <- c(already_chosen, new_pokemon_name)
  data_available <- data_available |> filter(!Starter %in% already_chosen)
  data_available
}

Update Budget

The update_budget function updates the remaining budget after selecting a Pokémon.

update_budget <- function(budget_points, list_pokemon, current_pokemon) {
  points_spent <- sum(list_pokemon[[current_pokemon]]$Cost)
  budget_points <- budget_points - points_spent
}

Select Shiny Pokémon

The select_shiny_pokemon function selects a shiny Pokémon based on the budget. It can prioritize rarer shiny Pokémon if specified.

select_shiny_pokemon <- function(df, points, best_shiny = TRUE) {
  data_filtered <- df |> filter(Cost <= points, !is.na(Shiny))

  if (best_shiny == TRUE) {
    data_filtered <- data_filtered |>
      mutate(random_number = rnorm(nrow(data_filtered))) |>
      arrange(desc(Shiny), Cost, random_number) |>
      select(-random_number)

    pokemon_selected <- data_filtered |> slice_head(n = 1)
  } else {
    pokemon_selected <- data_filtered |> slice_sample(n = 1)
  }
}

Distribute Points

The distribute_points function distributes the available points among the team members.

distribute_points <- function(points, n_pokemon = 6) {
  distribution <- rep(1, n_pokemon)
  points_left <- points - n_pokemon

  if (points_left > 0) {
    for (i in 1:points_left) {
      pokemon_slot <- sample(1:n_pokemon, 1)
      distribution[pokemon_slot] <- distribution[pokemon_slot] + 1
    }
  }

  distribution <- sort(distribution, decreasing = TRUE)
  distribution
}

Creating Teams

With the helper functions defined, we now create the main team selection functions.

Create Team

The create_team function creates a team based on the specified budget and criteria such as Pokérus and shiny Pokémon.

create_team <- function(points, pokerus = FALSE, shiny = 0, best_shiny = TRUE, arrange = FALSE) {
  # Set the starting budget
  budget_points <- points

  # Initialize empty lists and parameters
  list_pokemon <- list()
  current_pokemon <- 1
  already_chosen <- c()
  data_available <- data_catched

  # First Pókemon, that may or may not be forced to be a Pokérus Pokémon
  list_pokemon[[current_pokemon]] <- select_random_pokemon(data_available, budget_points, pokerus = pokerus)

  # Update already chosen pokemon
  data_available <- update_available_pokemon(data_available, list_pokemon, already_chosen, current_pokemon)

  # Update disposible budget
  budget_points <- update_budget(budget_points, list_pokemon, current_pokemon)

  # Update current pókemon slot
  current_pokemon <- current_pokemon + 1

  # Find shiny Pókemon
  if (shiny > 0) {
    # Force a maximum of 6 Pókemon in the team, and calculate the number of shiny Pókemon to add
    total_shiny_available <- count_shiny_pokemon(data_available)
    number_of_shinies <- min(shiny, total_shiny_available)
    end_loop <- min(6, number_of_shinies + current_pokemon - 1)

    # Fill team with shiny Pókemon
    for (i in current_pokemon:end_loop) {
      list_pokemon[[i]] <- select_shiny_pokemon(data_available, budget_points, best_shiny)

      # Update already chosen pokemon
      data_available <- update_available_pokemon(data_available, list_pokemon, already_chosen, current_pokemon)

      # Update disposible budget
      budget_points <- update_budget(budget_points, list_pokemon, current_pokemon)

      current_pokemon <- current_pokemon + 1
    }

    # Update current pókemon slot
    team_after_shiny <- list_rbind(list_pokemon)
    current_pokemon <- nrow(team_after_shiny) + 1
  }

  # Check if there are still places to fill in the team (until the 5th place)
  if (current_pokemon <= 5 & budget_points > 0) {
    # Loop for rest of max 6 Pókemon
    for (i in current_pokemon:5) {
      list_pokemon[[i]] <- select_random_pokemon(data_available, budget_points)

      # Update already chosen pokemon
      data_available <- update_available_pokemon(data_available, list_pokemon, already_chosen, current_pokemon)

      # Update disposible budget
      budget_points <- update_budget(budget_points, list_pokemon, current_pokemon)
      current_pokemon <- current_pokemon + 1
    }
  }

  # Ensure the last Pókemon spend all available points
  if (current_pokemon == 6 & budget_points > 0) {
    list_pokemon[[current_pokemon]] <- select_random_pokemon(data_available, budget_points, expensive = TRUE)
  }

  # Bind all Pókemon, in order of Generation and Dex number
  final_team <- list_rbind(list_pokemon)

  if (arrange == TRUE) {
    final_team |>
      arrange(Gen, Dex)
  } else {
    final_team
  }
}

Create Expensive Team

The create_expensive_team function creates a team that maximizes the use of the available budget by selecting the most expensive Pokémon possible.

create_expensive_team <- function(points, arrange = FALSE) {
  # Set the starting budget
  budget_points <- points

  # Initialize empty lists and parameters
  list_pokemon <- list()
  current_pokemon <- 1
  already_chosen <- c()
  data_available <- data_catched

  # Loop for rest of max 6 Pókemon
  for (i in current_pokemon:6) {
    list_pokemon[[i]] <- select_random_pokemon(data_available, budget_points, expensive = TRUE)

    # Update already chosen pokemon
    data_available <- update_available_pokemon(data_available, list_pokemon, already_chosen, current_pokemon)

    # Update disposible budget
    budget_points <- update_budget(budget_points, list_pokemon, current_pokemon)

    current_pokemon <- current_pokemon + 1
  }

  # Bind all Pókemon, in order of Generation and Dex number

  final_team <- list_rbind(list_pokemon)

  if (arrange == TRUE) {
    final_team |>
      arrange(Gen, Dex)
  } else {
    final_team
  }
}

Create Random 6 Team

The create_random_6_team function creates a team with a random distribution of points among the six Pokémon, optionally including a Pokérus Pokémon.

create_random_6_team <- function(points, arrange = TRUE, pokerus = FALSE) {

  # Set the starting budget
  budget_points <- points
  
  # Initialize empty lists and parameters
  list_pokemon <- list()
  current_pokemon <- 1
  already_chosen <- c()
  data_available <- data_catched
  
  # Create a random distribution of points
  distribution <- distribute_points(points, n_pokemon = 6)

  # Coerce a Pokérus Pókemon
  if (pokerus == TRUE){

    pokerus_pokemon <-  data_available |> 
      filter(Pokerus == "Yes") |> 
      slice_sample(n = 1)
    
    list_pokemon[[current_pokemon]] <- pokerus_pokemon

    # Update available
    data_available <- update_available_pokemon(data_available, list_pokemon, already_chosen, current_pokemon)
    budget_points <- update_budget(budget_points, list_pokemon, current_pokemon)
    current_pokemon <- current_pokemon + 1

    # Generate another distribution of points among the remaining 5 Pókemon
    distribution <- c("NA", distribute_points(budget_points, n_pokemon = 5))
  }
  
  for (i in current_pokemon:6) {
    # Find random pókemon according to the point distribution
    list_pokemon[[i]] <- select_random_pokemon(data_available, distribution[i], exact = TRUE)
    
    # Update
    data_available <- update_available_pokemon(data_available, list_pokemon, already_chosen, current_pokemon)
    budget_points <- update_budget(budget_points, list_pokemon, current_pokemon)
    current_pokemon <- current_pokemon + 1
  }
  
  # Bind all Pókemon, in order of Generation and Dex number
  final_team <- list_rbind(list_pokemon)
  
  if (arrange == TRUE) {
    final_team |> arrange(Gen, Dex)
  } else {
    final_team
  }
}

Random Team Examples

Now that we have our functions defined, let’s see them in action. Here are some examples of creating random Pokémon teams using different criteria.

Example 1: Basic Team with a Budget of 10 Points

This example creates a team with a budget of 10 points, without considering Pokérus or shiny variants, and arranges the team by Generation and Dex number.

create_team(10, pokerus = FALSE, arrange = TRUE)
   Starter Gen Dex Cost Shiny Pokerus
1 Hoothoot   2 163    1    NA    <NA>
2   Hoppip   2 187    1    NA    <NA>
3  Yveltal   6 717    8    NA    <NA>

Example 2: Enhanced Team with a Budget of 15 Points

In this example, we create a team with a budget of 15 points, including Pokémon with Pokérus and up to 4 shiny Pokémon, prioritizing the best shiny variants. The team is also arranged by Generation and Dex number.

create_team(15, pokerus = TRUE, shiny = 4, best_shiny = TRUE, arrange = TRUE)
  Starter Gen Dex Cost Shiny Pokerus
1 Drowzee   1  96    3    NA     Yes
2   Unown   2 201    1     1    <NA>
3  Bidoof   4 399    1     2    <NA>
4   Burmy   4 412    1     1    <NA>
5    Uxie   4 480    7    NA    <NA>
6 Petilil   5 548    2     2    <NA>

Example 3: Most Expensive Team with a Budget of 15 Points

Here, we create a team that maximizes the use of the budget by selecting the most expensive Pokémon available, with a total budget of 15 points.

create_expensive_team(15)
     Starter Gen  Dex Cost Shiny Pokerus
1   Giratina   4  487    8    NA    <NA>
2 Iron Crown   9 1023    7    NA    <NA>

Example 4: Random Team with a Budget of 10 Points

This example creates a random team with a budget of 10 points, including at least one Pokémon with Pokérus, and distributes the points randomly among the six Pokémon.

create_random_6_team(10, pokerus = TRUE)
    Starter Gen Dex Cost Shiny Pokerus
1    Weedle   1  13    1    NA    <NA>
2   Spearow   1  21    1     1    <NA>
3     Zubat   1  41    2    NA    <NA>
4   Drowzee   1  96    3    NA     Yes
5 Poochyena   3 261    2    NA    <NA>
6    Bidoof   4 399    1     2    <NA>

These examples demonstrate the flexibility and power of our team creation functions, allowing you to generate a variety of Pokémon teams tailored to your preferences and game mode. Whether you want a balanced team, a team with specific conditions, or the most powerful team possible within your budget, these functions can help you achieve that.

Conclusion

With these functions, you can generate a variety of Pokémon teams for your PokéRogue adventures. Whether you want a balanced team, a team with the most expensive Pokémon, or a team with specific conditions like Pokérus or shiny variants, these functions have you covered.

Happy PokéRoguing!

References