1. Introdução

Jogos de cartas colecionáveis (TCGs - Trading Card Games) criam um ecossistema econômico e competitivo interessante. Para manter a base de jogadores engajada com o produto ao longo dos anos, é comum que se adote uma prática de game design chamada “Power Creep”, onde novos produtos se tornam progressivamente melhores/mais poderosos e frequentemente mais complexos.

O problema central que essa análise verifica é a progressão do power creep, a complexidade textual das cartas e como isso afeta o jogo na franquia Yu-Gi-Oh!. Existe correlação entre quantidade de texto e poder percebido? Além disso, que tipos de efeitos (mecânicas) são mais problemáticos e, consequentemente, banidos de uso competitivo por serem poderosos demais?

Para responder a essas perguntas, estarei utilizando a base de dados com todas as cartas lançadas no jogo (numerando 14.653 no dia 07/11/2026), aplicando técnicas de manipulação de strings e extração de características desejáveis através de expressões regulares para converter o texto das cartas em dados estruturados. Estas informações são valiosas tanto para os designers de jogos (que têm de compreender os limites de complexidade dos seus sistemas) como para os jogadores já investidos no jogo, que lidam com um mini mercado especulativo dessas cartas.

Uma das cartas mais simples, antigas e populares do jogo
Um exemplo de design de uma carta recente

2. Pacotes Requeridos

Para garantir a reprodutibilidade e o sucesso da análise, os seguintes pacotes foram carregados antecipadamente:

  • readr, dplyr, tidyr e lubridate: Fundamentais para a importação, limpeza, estruturação e manipulação temporal do conjunto de dados.
  • stringr: Essencial para a mineração de texto, permitindo a contagem de palavras e identificação de mecânicas via Expressões Regulares (Regex).
  • ggplot2, plotly e scales: Utilizados em conjunto para a criação de visualizações estáticas e para transformá-las em gráficos interativos para exploração dinâmica.
  • DT: Responsável por renderizar tabelas interativas de auditoria no relatório final.

3. Preparação, Limpeza e Mineração de Texto

O dataset foi obtido via a API do YGOPRODECK.com (uma database de cartas, baralhos e artigos) e armazenado de forma estática no repositório do Kaggle - Yu-Gi-Oh! Cards Dataset para garantir reprodutibilidade. Os dados brutos incluem características das cartas (Tipo de carta, Ataque, Defesa, Nível) além de outros metadados relevantes (texto de efeito, data de lançamento, etc.).

A limpeza dos dados exigiu uma abordagem adaptada à mecânica do jogo: as cartas de Magia e Armadilha não possuem estatísticas de combate (Ataque/Defesa). No conjunto de dados brutos, estas poderiam ser interpretadas erroneamente como zeros numéricos pelos algoritmos, então converti para NA.

Além disso, introduzimos uma etapa fundamental de mineração de texto. A partir de 2011, o jogo adotou o “Problem-Solving Card Text” (PSCT), padronizando a forma como os efeitos são descritos. Isso facilitou o mapeamento de termos-chave do jogo (tais como destruir, adicionar carta do baralho para a mão e negações de efeito) utilizando Regex, criando indicadores booleanos para estruturar a função mecânica de cada carta.

cards_raw <- read_csv("cards.csv",
                      locale = locale(encoding = "UTF-8"),
                      guess_max = 20000,
                      show_col_types = FALSE)
cards <- cards_raw %>%
  mutate(
    tcg_date_parsed  = suppressWarnings(ymd(tcg_date)),
    year_lancamento  = year(tcg_date_parsed),
    tipo_categoria   = case_when(
      str_detect(type, regex("Spell",   ignore_case = TRUE)) ~ "Spell",
      str_detect(type, regex("Trap",    ignore_case = TRUE)) ~ "Trap",
      str_detect(type, regex("Monster", ignore_case = TRUE)) ~ "Monster",
      TRUE ~ "Other"
    ),
    # Tratamento estrutural de NAs para status de combate
    atk = ifelse(tipo_categoria %in% c("Spell", "Trap"), NA, atk),
    def = ifelse(tipo_categoria %in% c("Spell", "Trap"), NA, def),
    level = ifelse(tipo_categoria %in% c("Spell", "Trap"), NA, level),
    
    # Engenharia de Features: Texto e Flags
    tamanho_texto = nchar(desc, type = "chars"),
    qtd_palavras  = str_count(desc, "\\S+"),
    is_banned     = !is.na(ban_tcg) & ban_tcg != "",
    is_staple     = !is.na(staple) & str_to_lower(staple) == "yes",
    
    # Mineração de Texto (Mecânicas) via Regex
    eff_quick     = str_detect(desc, regex("\\(Quick Effect\\)|During either player's turn", ignore_case = TRUE)),
    eff_special   = str_detect(desc, regex("Special Summon", ignore_case = TRUE)),
    eff_destroy   = str_detect(desc, regex("destroy", ignore_case = TRUE)),
    eff_search    = str_detect(desc, regex("add .* to your hand|add .* from your Deck", ignore_case = TRUE)),
    eff_negate    = str_detect(desc, regex("negate", ignore_case = TRUE)),
    eff_draw      = str_detect(desc, regex("draw", ignore_case = TRUE))
  ) %>%
  filter(!is.na(year_lancamento),
         year_lancamento >= 1999,
         year_lancamento <= year(Sys.Date()) + 1)

Resumo do Dataset Limpo: Após filtragem, o dataset contém 14.135 cartas válidas. Constando com 252 cartas atualmente banidas, enquanto 64 são classificadas como “Staples” (cartas com aplicabilidade ampla em diversos tipos de baralho). A média do texto de uma carta é de 56 palavras.


4. Análise do crescimento do power creep

4.1 Aumento do tamanho dos textos

O primeiro passo para realizar essa análise foi agrupar as cartas por seu ano de lançamento e calcular a média anual do tamanho dos efeitos, mostrando assim a progressão dessa métrica ao longo dos anos.

yearly <- cards %>%
  group_by(year_lancamento) %>%
  summarise(n_cards          = n(),
            media_palavras   = mean(qtd_palavras,  na.rm = TRUE),
            media_chars      = mean(tamanho_texto, na.rm = TRUE),
            mediana_palavras = median(qtd_palavras, na.rm = TRUE),
            .groups = "drop")
p1 <- ggplot(yearly, aes(year_lancamento, media_palavras)) +
  geom_line(color = "#2c7fb8", linewidth = 1) +
  geom_point(size = 2) +
  geom_smooth(method = "lm", se = TRUE, linetype = "dashed", color = "firebrick") +
  labs(x = "Ano de Lançamento (TCG)",
       y = "Média de Palavras",
       title = "Power Creep: Complexidade Textual dos Efeitos") +
  theme_minimal(base_size = 12)
ggplotly(p1)
p2 <- ggplot(yearly, aes(year_lancamento, media_chars)) +
  geom_line(color = "#31a354", linewidth = 1) +
  geom_point(size = 2) +
  geom_smooth(method = "lm", se = TRUE, linetype = "dashed", color = "firebrick") +
  labs(x = "Ano de Lançamento (TCG)",
       y = "Média de Caracteres",
       title = "Power Creep: Comprimento do Texto de Efeito") +
  theme_minimal(base_size = 12)
ggplotly(p2)
modelo <- lm(qtd_palavras ~ year_lancamento, data = cards)
coefs <- coef(modelo)

A regressão linear indica um crescimento médio de 2.88 palavras para cada ano, reforçando a ideia do crescimento do power creep na franquia.

4.2 Cartas banidas vs “Staples”

# Calculando a média de palavras para Banidas
banned_summary <- cards %>%
  filter(!is.na(is_banned)) %>%
  mutate(Status = ifelse(is_banned, "Banida", "Permitida")) %>%
  group_by(Status) %>%
  summarise(media_palavras = mean(qtd_palavras, na.rm = TRUE))

p6 <- ggplot(banned_summary, aes(x = Status, y = media_palavras, fill = Status)) +
  geom_col(show.legend = FALSE) +
  scale_fill_manual(values = c("Banida" = "firebrick", "Permitida" = "gray70")) +
  labs(x = "", y = "Média de Palavras", title = "Banidas vs Permitidas") +
  theme_minimal(base_size = 12)

# Calculando a média de palavras para Staples
staples_summary <- cards %>%
  filter(!is.na(is_staple)) %>%
  mutate(Status = ifelse(is_staple, "Staple", "Comum")) %>%
  group_by(Status) %>%
  summarise(media_palavras = mean(qtd_palavras, na.rm = TRUE))

p7 <- ggplot(staples_summary, aes(x = Status, y = media_palavras, fill = Status)) +
  geom_col(show.legend = FALSE) +
  scale_fill_manual(values = c("Staple" = "#e7da00", "Comum" = "gray70")) +
  labs(x = "", y = "Média de Palavras", title = "Staples vs Comuns") +
  theme_minimal(base_size = 12)

# Juntando os dois gráficos
subplot(ggplotly(p6), ggplotly(p7), nrows = 1, margin = 0.05) %>%
  layout(title = "Média de Palavras por Impacto no Jogo", showlegend = FALSE)
# Densidade para Banidas
p_dens_ban <- ggplot(cards %>% filter(!is.na(is_banned)), 
                     aes(x = qtd_palavras, fill = ifelse(is_banned, "Banida", "Permitida"))) +
  geom_density(alpha = 0.6, color = NA) +
  scale_fill_manual(values = c("Banida" = "firebrick", "Permitida" = "gray70")) +
  labs(x = "Volume de Palavras", y = "Concentração (Densidade)", fill = "Status") +
  theme_minimal(base_size = 12)

# Densidade para Staples
p_dens_staple <- ggplot(cards %>% filter(!is.na(is_staple)), 
                        aes(x = qtd_palavras, fill = ifelse(is_staple, "Staple", "Comum"))) +
  geom_density(alpha = 0.6, color = NA) +
  scale_fill_manual(values = c("Staple" = "#e7da00", "Comum" = "gray70")) +
  labs(x = "Volume de Palavras", y = "Concentração (Densidade)", fill = "Status") +
  theme_minimal(base_size = 12)

# Plotando lado a lado
subplot(ggplotly(p_dens_ban), ggplotly(p_dens_staple), nrows = 1, margin = 0.05) %>%
  layout(title = "Onde as cartas se concentram? (Densidade)", 
         showlegend = FALSE)

As “staples” atingem o seu pico de massa na faixa inferior (textos mais curtos). Isto indica que a eficiência para uso geral decorre de um design elegante e simples. Em contraste, as cartas banidas apresentam uma montanha de densidade significativamente deslocada para a direita, o que mais uma vez sugere que as cartas sobrecarregadas com mais texto e múltiplos efeitos perdem o seu equilíbrio mecânico com muito mais facilidade.

4.3 Aceleração do jogo

A jogabilidade moderna do jogo é definida por interatividade constante entre os 2 jogadores, mesmo no turno do oponente. Algumas cartas possuem efeitos considerados “rápidos” (Quick Effect), que permitem seu uso mesmo no turno do oponente. Usando a flag eff_quick extraída via mineração de texto, podemos criar uma análise da progressão do número de cartas com essa poderosa habilidade.

# Isolando os monstros lançados a partir de 2010 para observar o pico da mecânica
quick_effects_tbl <- cards %>%
  filter(tipo_categoria == "Monster", year_lancamento >= 2010) %>%
  group_by(year_lancamento) %>%
  summarise(n_monsters = n(),
            n_quick = sum(eff_quick, na.rm = TRUE),
            pct_quick = n_quick / n_monsters,
            .groups = "drop")

p_quick <- ggplot(quick_effects_tbl, aes(x = year_lancamento, y = pct_quick)) +
  geom_line(color = "#9b59b6", linewidth = 1.2) +
  geom_point(color = "black", size = 2) +
  scale_y_continuous(labels = percent_format(accuracy = 1)) +
  labs(x = "Ano de Lançamento (TCG)",
       y = "% de Monstros Lançados",
       title = "Evolução: Monstros com Efeitos Rápidos (Interação)") +
  theme_minimal(base_size = 12)

ggplotly(p_quick)

4.4 O “DNA” da lista de cartas banidas: que tipos de efeito são mais propensos a serem problemáticos?

# 1. Gráfico da Proporção de Banidas por Ano
banned_tbl <- cards %>%
  group_by(year_lancamento) %>%
  summarise(n_total  = n(),
            n_banned = sum(is_banned, na.rm = TRUE),
            pct      = n_banned / n_total,
            .groups = "drop")

p4 <- ggplot(banned_tbl, aes(year_lancamento, pct)) +
  geom_col(fill = "firebrick") +
  scale_y_continuous(labels = percent_format(accuracy = 0.1)) +
  labs(x = "Ano de Lançamento (TCG)",
       y = "% Cartas Banidas (TCG)",
       title = "Proporção de Cartas Banidas por Ano") +
  theme_minimal(base_size = 12)
ggplotly(p4)
# 2. Distribuição das Mecânicas dentro da Banlist
banned_mechanics <- cards %>%
  filter(is_banned == TRUE) %>%
  summarise(
    `Invocação Especial` = sum(eff_special, na.rm = TRUE) / n(),
    `Destruição`         = sum(eff_destroy, na.rm = TRUE) / n(),
    `Busca no Deck`      = sum(eff_search, na.rm = TRUE) / n(),
    `Negação`            = sum(eff_negate, na.rm = TRUE) / n(),
    `Comprar Cartas`     = sum(eff_draw, na.rm = TRUE) / n()
  ) %>%
  pivot_longer(cols = everything(), names_to = "Mecanica", values_to = "pct") %>%
  arrange(desc(pct))

p_mec <- ggplot(banned_mechanics, aes(x = reorder(Mecanica, pct), y = pct, fill = Mecanica)) +
  geom_col() +
  coord_flip() +
  scale_y_continuous(labels = percent_format(accuracy = 1)) +
  scale_fill_brewer(palette = "Set1") +
  labs(x = "Mecânica (Efeito)",
       y = "Presença na Banlist (%)",
       title = "O que faz uma carta ser banida?") +
  theme_minimal(base_size = 12) +
  theme(legend.position = "none")

ggplotly(p_mec)
# 3. Distribuição de Staples (Avaliando a universalidade saudável)
staples_tbl <- cards %>%
  filter(is_staple) %>%
  count(tipo_categoria, sort = TRUE) %>%
  mutate(pct = n / sum(n))

p5 <- ggplot(staples_tbl, aes(x = reorder(tipo_categoria, -pct), y = pct, fill = tipo_categoria)) +
  geom_col(show.legend = FALSE) +
  scale_y_continuous(labels = percent_format(accuracy = 1)) +
  scale_fill_manual(values = c("Spell" = "#1b9e77", "Trap" = "#d95f02", "Monster" = "#7570b3")) +
  labs(x = "Categoria da Carta",
       y = "Proporção entre as Staples",
       title = "Distribuição de Staples por Categoria") +
  theme_minimal(base_size = 12)

ggplotly(p5)

Os dados comprovam que mecânicas de aceleração de recursos (“Invocação Especial” e “Destruição”) são muito mais prevalentes na lista de banimento do que o simples bloqueio de jogadas adversárias (“Negação”).

4.5 Tabela de Auditoria (Anomalias Banidas)

Abaixo, isolamos em uma tabela interativa exclusivamente as anomalias de design banidas que ultrapassam as 100 palavras de texto.

cards %>%
  filter(is_banned, qtd_palavras > 100) %>%
  mutate(desc = str_trunc(desc, 120, ellipsis = "…")) %>%
  select(name, type, tipo_categoria, year_lancamento,
         qtd_palavras, tamanho_texto, desc) %>%
  datatable(caption = "Anomalias Banidas (>100 palavras)",
            filter = "top", rownames = FALSE,
            options = list(pageLength = 5, scrollX = TRUE))

5. Conclusões e Limitações

Através da combinação de metadados e processamento avançado de texto, pudemos confirmar que o Power Creep no Yu-Gi-Oh! não é meramente uma percepção empírica da comunidade, mas sim um fenômeno matematicamente comprovável. O volume de texto nos efeitos das cartas e a inclusão de interações durante o turno do adversário (Efeitos Rápidos) têm vindo a aumentar progressivamente ao longo do tempo.

A análise sugere algumas diferenças entre os grupos de cartas. As classificadas como staples tendem a ter textos mais curtos, enquanto as cartas banidas apresentam, em média, efeitos mais longos. Também aparecem com maior frequência entre as banidas mecânicas como Invocação Especial e destruição. Esses resultados mostram uma associação entre essas características, mas não permitem afirmar que o tamanho do texto ou uma mecânica específica, por si só, cause o banimento de uma carta.

Uma limitação desta análise é que ela considera todas as cartas do conjunto de dados, incluindo cartas com pouca presença no jogo competitivo. Como o objetivo foi observar tendências gerais ao longo do tempo, a média de complexidade pode não representar bem as cartas mais usadas no metajogo. A ideia inicial era reduzir esse problema analisando a frequência de uso das cartas em baralhos de torneios. Porém, a API do YGOPRODeck não disponibiliza esses dados. Como possibilidade para trabalhos futuros, seria interessante combinar esta base com dados de listas de torneios obtidos de outras fontes.

Implicações Práticas:
Para os game designers e a editora do jogo, esses resultados funcionam como um diagnóstico: o gráfico de densidade evidencia que desenhar cartas demasiadamente carregadas de texto resulta, quase invariavelmente, num colapso do equilíbrio competitivo, culminando em banimento. Já para os jogadores e agentes do mercado especulativo que compram e revendem essas cartas, a análise sinaliza risco imediato: investimentos em cartas recém-lançadas que apresentam as mecânicas identificadas no “DNA da Banlist” (alta complexidade, Invocação Especial e Destruição) carregam altíssimas chances de depreciação total, enquanto o investimento seguro de longo prazo reside em Staples com mecânicas enxutas.