Alcides Oliveira

Engenheiro de software full-stack

Todos os projetos

CineGacha

Projeto pessoal · mar–abr 2026 · Abrir site · Código-fonte

Um jogo de cartas para quem gosta de cinema. Você abre pacotes de cinco cartas feitas de filmes, atores e diretores do TMDB e monta uma coleção.

Fiz e publiquei sozinho. Os 98 commits do repositório são meus.

CineGacha, captura de tela 1
Um pacote fechado esperando o toque para abrir.

Num jogo de gacha, tudo depende de o jogador confiar nas chances, então o sorteio não podia rodar no navegador, onde qualquer um consegue mexer. Eu também queria que a pessoa jogasse já na primeira visita, sem conta, e não perdesse as cartas se criasse uma depois.

Regras do jogo no banco

Escrevi a lógica do sorteio em PL/pgSQL em vez de TypeScript. É mais difícil de testar e de ler, mas o sorteio, a contagem de pacotes e a atualização do inventário acontecem numa transação só, com a linha bloqueada. Duas abas abertas ou uma requisição adulterada não conseguem abrir o mesmo pacote duas vezes nem mudar as chances.

TMDB em cache, raridade congelada

As cartas são copiadas do TMDB para o Supabase com antecedência, e cada uma guarda a raridade que recebeu ao entrar no acervo; uma atualização acrescenta cartas novas e não mexe nas que já existem. Abrir um pacote não depende de o TMDB estar no ar, e uma carta que alguém já tem não perde raridade quando a popularidade de um ator cai. Em troca, o acervo só cresce quando eu disparo uma atualização.

Anônimo primeiro, conta depois

Todo visitante sem sessão recebe um usuário anônimo do Supabase, então o primeiro pacote está a um clique. O login tenta vincular o GitHub a esse mesmo usuário, e uma função de mesclagem cobre o caso em que a conta do GitHub já existe. Esse segundo caminho deu mais código, mas ninguém perde a coleção ao fazer login.

  • O motor de gacha como funções do PostgreSQL: bloqueio de linha contra aberturas duplicadas, regeneração de pacotes pelo tempo passado, sorteio ponderado entre sete raridades, uma regra de garantia (depois de dez pacotes seguidos sem uma Super Rara ou melhor, todas as cartas do pacote seguinte saem nesse nível), estrelas para repetidas com limite máximo e recuo para a raridade abaixo quando uma faixa está vazia.
  • Um pipeline em TypeScript que chama o TMDB por uma fila com limite de requisições feita com p-queue (35 por segundo, com novas tentativas que respeitam o Retry-After), pontua filmes com uma fórmula e atores e diretores com outra, e define a raridade por percentil dentro de cada tipo de carta, para todos os tipos terem a mesma distribuição entre as faixas.
  • Jogo sem conta com sessões anônimas do Supabase e, depois, login com GitHub que tenta se vincular ao mesmo usuário. Quando a conta do GitHub já pertence a outro usuário, o callback do OAuth chama uma função de migração que junta as cartas, os pacotes e o progresso da garantia do visitante nessa conta.
  • A tela de abertura em React com Motion: um pacote que você toca para rasgar, cinco cartas reveladas em sequência e reflexos de foil que ficam mais fortes conforme a raridade, com uma versão parada para quem ativa a redução de movimento.
  • Uma página de coleção com filtro por tipo e raridade, ordenação, contagem de progresso, um modal de detalhes e uma imagem da carta para baixar em JPEG, gerada com html-to-image. As imagens do TMDB passam por uma rota de proxy que só aceita o domínio de imagens do TMDB, para o CORS não bloquear o canvas da exportação.
  • Um workflow no GitHub Actions que roda lint e build de produção a cada push e pull request na main.

O Next.js (App Router) renderiza as páginas e roda as server actions. O Supabase cuida do login, e o banco PostgreSQL dele guarda o estado do jogo: o perfil de cada jogador com pacotes e progresso da garantia, o acervo de cartas, as cartas de cada jogador e os álbuns por gênero. As políticas de segurança por linha (RLS) deixam cada jogador ler só o próprio perfil e as próprias cartas. A tabela de cartas não tem política de escrita; toda gravação passa por funções do banco.

Abrir um pacote funciona assim. O navegador chama uma server action, que lê o usuário logado no Supabase e chama a função open_pack, escrita em PL/pgSQL. Essa função bloqueia a linha do perfil, soma os pacotes regenerados desde a última visita (um a cada dois minutos, até dez), sorteia com pesos a raridade de cada uma das cinco cartas, escolhe uma carta aleatória dessa raridade, transforma repetidas em estrelas e devolve o resultado. A animação de rasgar o pacote roda enquanto a requisição está em andamento; o navegador só mostra o que recebe.

O TMDB fica fora desse caminho. Um construtor separado, atrás de rotas de admin que exigem um token secreto, busca filmes e créditos no TMDB, valida as respostas com Zod, confere se cada imagem carrega, pontua e ordena cada item e grava as cartas no acervo em lotes de 100. Uma atualização acrescenta só as cartas que ainda não estão no acervo.

CineGacha, captura de tela 2
As cinco cartas de um pacote, cada uma com tipo, raridade e atributos de ATK/DEF.
CineGacha, captura de tela 3
A página de coleção com contagem de progresso, filtros por tipo e raridade, ordenação e um convite para fazer login e salvar.
CineGacha, captura de tela 4
O detalhe de uma carta, com atributos, a data em que foi obtida e o botão de compartilhar.
Front-end
React 19, Next.js 16 (App Router), TypeScript, Tailwind CSS v4, Motion, html-to-image
Back-end
Server actions e route handlers do Next.js, Supabase Auth (sessões anônimas, OAuth do GitHub), Zod, p-queue
Dados
PostgreSQL do Supabase, funções PL/pgSQL, segurança por linha (RLS), API do TMDB
Infraestrutura e ferramentas
Vercel, GitHub Actions, ESLint, Prettier, React Compiler