Candy Crush Saga es un famoso juego para celulares desarrollado por King (parte de Activision|Blizzard), que es jugado por millones de personas alrededor del mundo. El juego está estructurado en una serie de niveles donde los jugadores deben emparejar los dulces similares entre ellos para completar un nivel y seguir progresando en el mapa.
Candy Crush tiene mas de 3000 niveles, y nuevos niveles son añadidos cada semana. Y con tantos niveles, es importante ajustar el nivel de dificultad de manera acorde, si es muy fácil el juego se vuelve aburrido, y si es muy difícil los jugadores pueden frustrarse y dejar de jugar.
En este proyecto, veremos como podemos colectar data de los jugadores para estimar el nivel de dificultad. Empecemos cargando los paquetes que necesitaremos,
# Ajustamos el tamaño de los gráficos
options(repr.plot.width = 5, repr.plot.height = 4)
# Cargamos los paquetes
library(dplyr)
library(ggplot2)
library(RCurl)
El conjunto de datos que usaremos contiene 1 semana de datos de una muestra de jugadores que jugaron Candy Crush en 2014. La muestra también contiene un sólo episodio, es decir, 15 niveles. Tiene las siguientes columnas:
La base de nuestro conjunto de datos es: jugador, fecha y nivel. Esto quiere decir que hay una fila para cada jugador, día y nivel registrando el número total de intentos y cuántos de esos intentos resultaron en una victoria para el jugador.
Importemos la base de datos y explorémosla.
file <- getURL("https://raw.githubusercontent.com/ryanschaub/Level-Difficulty-in-Candy-Crush-Saga/master/candy_crush.csv")
data <- read.csv(text= file)
head(data)
## player_id dt level num_attempts
## 1 6dd5af4c7228fa353d505767143f5815 2014-01-04 4 3
## 2 c7ec97c39349ab7e4d39b4f74062ec13 2014-01-01 8 4
## 3 c7ec97c39349ab7e4d39b4f74062ec13 2014-01-05 12 6
## 4 a32c5e9700ed356dc8dd5bb3230c5227 2014-01-03 11 1
## 5 a32c5e9700ed356dc8dd5bb3230c5227 2014-01-07 15 6
## 6 b94d403ac4edf639442f93eeffdc7d92 2014-01-01 8 8
## num_success
## 1 1
## 2 1
## 3 0
## 4 1
## 5 0
## 6 1
Ahora que importamos nuestros datos, contemos cuántos jugadores hay en la muestra y cuántos días de datos tenemos:
#Número de jugadores
length(unique(data$player_id))
## [1] 6814
#Período de días del que tenemos data
range(data$dt)
## [1] "2014-01-01" "2014-01-07"
En cada episodio de Candy Crush, hay una mezcla de niveles fáciles y difíciles. La suerte y la habilidad individual determinan el número de intentos requeridos para completar un nivel. La hipótesis es que los niveles difíciles requieren más intentos en promedio que los fáciles, en pocas palabras, mientras más difícil un nivel, más baja es la probabilidad de completarlo en un solo intento.
Un enfoque simple para modelar esta probabilidad es con un proceso de Bernoulli. Como un evento binario (o ganas, o pierdes), caracterizado por un solo parametro, pvic: la probabilidad de ganar el nivel en un solo intento. Esta probabilidad puede ser estimada para cada nivel de la siguiente forma:
\(pvic = \frac{\sum_{i=1}^n victorias_i} {\sum_{i=1}^n intentos_i}\)
Por ejemplo, digamos que un nivel cualquiera ha sido jugado 10 veces y 2 de esos intentos terminaron en una victoria, la probabilidad de ganar en un solo intento sería \(p_{vic}\) = 2 / 10 = 20%
Ahora, calculemos \(p_{vic}\) para cada uno de los 15 niveles.
# Calculando el nivel de dificultad
difficulty <- data %>%
group_by(level) %>%
summarise(intentos = sum(num_attempts), victorias = sum(num_success)) %>%
mutate(p_vic = victorias/intentos)
# Mostramos el nivel de dificultad
difficulty
## # A tibble: 15 x 4
## level intentos victorias p_vic
## <int> <int> <int> <dbl>
## 1 1 1322 818 0.619
## 2 2 1285 666 0.518
## 3 3 1546 662 0.428
## 4 4 1893 705 0.372
## 5 5 6937 634 0.0914
## 6 6 1591 668 0.420
## 7 7 4526 614 0.136
## 8 8 15816 641 0.0405
## 9 9 8241 670 0.0813
## 10 10 3282 617 0.188
## 11 11 5575 603 0.108
## 12 12 6868 659 0.0960
## 13 13 1327 686 0.517
## 14 14 2772 777 0.280
## 15 15 30374 1157 0.0381
Ahora que tenemos el nivel de dificultad para los 15 niveles de este episodio teniendo en cuenta que si la probabilidad de completar un nivel en un solo intento es pequeña, implica que el nivel de dificultad del mismo es alto.
Hagamos un gráfico de líneas con los niveles en el eje de las X y la dificultad (\(p_{vic}\)) en el eje de las Y. Llamaremos a este gráfico el perfil de dificultad del episodio.
# Graficando el nivel de dificultad
ggplot(difficulty, aes(level, p_vic)) +
geom_line() +
scale_x_continuous(breaks = 1:15) +
scale_y_continuous(label = scales::percent) ##usando el paquete scales
Lo que constituye un nivel difícil es subjetivo. De todas formas, podemos definir un valor límite de dificultad, por ejemplo, un 10%, y mediría los niveles de la forma: \(p_{vic}\) < 10% como difíciles. Es relativamente fácil detectar estos niveles en el gráfico, pero podemos hacer el gráfico más amigable remarcando explícitamente los niveles difíciles.
# Agregando puntos y una línea de puntos
ggplot(difficulty, aes(level, p_vic)) +
geom_line() +
scale_x_continuous(breaks = 1:15) +
scale_y_continuous(label = scales::percent) +
geom_point() +
geom_hline(yintercept = 0.1, linetype = 'dashed')
Al dar estadísticas, siempre debemos informar sobre un margen de error que puedan tener los resultados. Existen muchas formas de medir el margen de error que pueda tener un resultado, pero en este caso usaremos el error estándar para medirlo:
\(\sigma_{error} = \frac{\sigma_{muestra}}{\sqrt{n}}\)
Siendo n el tamaño de la muestra y \(\sigma_{muestra}\) es la desviación estándar de la muestra. Para un proceso de Bernulli la desviación estándar se calcula de la siguiente forma:
\(\sigma_{muestra} = \sqrt{p_{vic}(1-p_{vic})}\)
Entonces, podemos calcular el error estándar mediante:
\(\sigma_{error} = \sqrt{\frac{{p_{vic}(1-p_{vic})}}{n}}\)
Ya poseemos todos los datos necesarios para calcular el error estándar en el subconjunto de datos difficulty que creamos:
# Calculando el error estándar para cada nivel
difficulty <- difficulty %>%
mutate(error = sqrt(p_vic * (1 - p_vic) / intentos))
Ahora que tenemos una estimación del margen de error para la dificultad de cada nivel, usemos barras de errores para mostrarlos en el gráfico. Ajustaremos las barras de error a un solo error estándar. El límite superior y el límite inferior deberían ser \(p_{vic} + \sigma_{error}\) y \(p_{vic} - \sigma_{error}\) respectivamente.
# Añadiendo las barras de error y puntos
ggplot(difficulty, aes(level, p_vic)) +
geom_line() +
scale_x_continuous(breaks = 1:15) +
scale_y_continuous(label = scales::percent) +
geom_point() +
geom_hline(yintercept = 0.1, linetype = 'dashed') +
geom_errorbar(aes(ymin = p_vic - error, ymax = p_vic + error))
Parece ser que nuestra estimación del nivel de dificultad es bastante precisa. Usando este gráfico un diseñador de niveles, por ejemplo, puede detectar fácilmente cuáles son los niveles más difíciles y también ver si existen muchos niveles difíciles que puedan tornar el juego frustrante en el episodio.
Una pregunta que se haría un diseñador de niveles es: “¿Qué tan probable es que un jugador complete el episodio sin haber perdido ni una sola vez?” Calculemos esta probabilidad usando la dificultad estimada de los niveles.
# Probabilidad de completar un episodio sin perder en ningún nivel
p <- prod(difficulty$p_vic)
# Mostramos la probabilidad
p
## [1] 9.447141e-12
La probabilidad es bastante baja, así que el diseñador de niveles no debería preocuparse porque el juego sea fácil y aburra a los jugadores.