← Voltar pros projetos

Agente de truco que aprende a blefar

Motor de Truco Gaúcho em C#, um segundo motor em Python, e um agente treinado por self-play que precisa blefar pra jogar bem, servido pra um cliente em Unity.

funcionalFunciona; uso próprio ou demo. Ainda sem dado real de cliente.

O problema

Truco é interessante pra aprendizado por reforço por um motivo específico: boa parte da informação é escondida, e mentir sobre ela é parte da regra. Um agente que só calcula a força da mão joga mal. Ele precisa aprender que pedir truco com carta ruim funciona às vezes, e que funciona porque o outro não vê a carta.

Isso muda o problema. Não é otimizar contra um tabuleiro visível; é aprender uma política num jogo onde o mesmo estado observável pode ter sido gerado por mãos completamente diferentes.

Decisões técnicas

Ações ilegais são mascaradas, não punidas

Contra o quê: deixar o agente escolher qualquer ação e devolver recompensa negativa quando a ação é inválida.

O treino usa PPO com máscara de ação: a cada estado, o motor informa quais jogadas são legais e o agente só escolhe dentro delas.

O que custou: o motor de regras deixou de poder ser só executável e passou a ter que ser consultável: para todo estado, ele precisa saber responder “quais ações valem agora” antes de qualquer jogada acontecer. As máquinas de estado de truco, envido e flor tiveram que ser escritas com essa pergunta em mente desde o começo, o que é mais trabalho do que só validar no momento do lance.

Duas implementações das mesmas regras, de propósito

Contra o quê: um motor só, com o treino conversando com o C# através da fronteira do Unity.

O jogo tem um motor em C# puro (com testes em xUnit); o treino tem um segundo motor em Python (com testes em pytest). Treinar através da fronteira de processo tornaria cada episódio caro demais, e são milhões de episódios.

O que custou: duas implementações das mesmas regras podem divergir, e divergência silenciosa é o pior tipo. Por isso os dois lados têm suíte de teste própria sobre as mesmas regras de hierarquia de carta e resolução de mão. É custo permanente de manutenção assumido em troca de treino viável.

O modelo é servido por HTTP, não embutido no jogo

Contra o quê: exportar o modelo e rodar dentro do Unity.

O agente treinado fica atrás de um serviço FastAPI, e o cliente Unity pede a jogada.

O que custou: o jogo deixou de ser autônomo: sem o serviço no ar, não tem adversário. Em troca, o modelo pode ser retreinado e trocado sem tocar no build do jogo, e dá pra jogar contra versões diferentes do agente pra comparar.

O que ficou de fora

Os modelos treinados não são versionados no repositório. E o agente hoje joga 1v1; as regras de 2x2 e 3x3 existem no motor em C#, mas o ambiente de treino ainda não cobre partida em dupla, onde a comunicação implícita entre parceiros muda o jogo inteiro.


  • Python
  • Reinforcement Learning
  • Gymnasium
  • FastAPI
  • C#
  • Unity