Tic Development
Tic Development
07 de julho de 2026 — Terça-feira
Eu e meu amigo Renato nos reunimos e acabamos caindo em uma conversa em que frequentemente caíamos: ideias minimamente legais de software para desenvolvermos juntos.
Provavelmente durante mais uma troca de ideias sobre desenvolvimento de jogos, a discussão se encaminhou para WebSockets. Daí veio a ideia, simples e direta: fazermos uma POC de um jogo utilizando WebSockets. Era ideal: não parecia ser tão difícil, mas nenhum de nós havia feito algo do tipo.
A linguagem escolhida acabou sendo Go, pela popularidade da linguagem em lidar muito bem com conexões de rede e, principalmente, porque gostamos de Go.
Renato então realizou uma pesquisa rápida para descobrir como normalmente se trabalha com WebSockets em Go e encontrou o pacote Gorilla WebSocket. Parecia ótimo e muito utilizado. Para o frontend, nossa ideia inicial era que o Renato fizesse uma TUI, para podermos rodar tudo diretamente no terminal.
Perfeito. Com as tecnologias definidas, bastava decidir o conceito do jogo. E qual seria o jogo mais simples possível e à prova de incompetência? Um jogo que já foi feito tantas e tantas vezes que não teria como errar?
A resposta foi óbvia e veio do Renato:
Ora, faremos um jogo da velha!
E então iniciou-se a pesquisa de como fazer um jogo da velha. O passo 1 era simples: qual algoritmo normalmente é utilizado para definir o vencedor?
Depois de pesquisarmos um pouco, encontramos uma resposta para um problema do LeetCode no Stack Overflow explicando o algoritmo. Infelizmente, não fui competente o suficiente para encontrar a postagem original.
E então estávamos prontos.
O que precisávamos agora era de um pouco de planejamento de projeto: uma forma de controlar as tarefas e um diagrama explicando o sistema por cima. A ideia era entender o que movia a parada: quais rotas existiriam, qual seria o fluxo de informação etc.
Então fomos debatendo sobre isso, e o Renato chegou neste belo desenho:
Perfeito. Tínhamos o diagrama, o algoritmo e as tecnologias.
Precisávamos agora de uma forma de controlar as tarefas. O ponto era que, sendo altamente e perigosamente rebeldes, não queríamos usar uma ferramenta apropriada para isso (viva a contracultura).
Daí decidimos usar o Dontpad, um site em que você cria notas e consegue compartilhá-las usando um link único gerado. Qualquer pessoa que, acidental ou propositalmente, acertar o link pode alterar tudo que foi escrito antes.
Sensacional.
Perfeito. As tarefas tinham sido definidas e fizemos um combinado: eu atuaria 80% no backend e 20% no frontend, e o Renato vice-versa.
Foi uma briga feia.
Minha primeira tarefa era simples: criar o repositório, iniciar um projeto Go e adicionar o Renato ao projeto. Como o homem empenhado que sou, fiz esse trabalho duro na mesma noite.
08 de julho de 2026 — Quarta-feira
No segundo dia de trabalho, eu estava ansioso para a próxima tarefa: aplicar de fato o algoritmo-base do jogo para verificar o vencedor.
Inicialmente, eu achava que tudo de que precisava era o model de um jogo e um método Move, pois o algoritmo cobria o teste para verificar se aquele último movimento feito era o movimento vencedor ou se resultava em empate.
E foi o que commitei:
Commit — implementação inicial do algoritmo
Feito isso, passei para a próxima tarefa: configurar o básico para podermos rodar o projeto com Docker:
Commit — configuração inicial do Docker
Por aquele dia era tudo. Deu um trabalho considerável aplicar o algoritmo no código, e eu tinha que dormir para trabalhar no dia seguinte.
10 de julho de 2026 — Sexta-feira
O Renato terminou de configurar o Docker e iniciou o Docker Compose para conseguirmos rodar o MongoDB em outro container.
Naquele dia também lembrei que precisávamos de um algoritmo para verificar se um movimento era válido antes de registrá-lo.
É, eu não tinha pensado nisso. Semana difícil.
Fiz então um algoritmo simples. Mesmo tendo feito isso, honestamente, algo não parecia certo. Uma slice de ints enfileirados não fazia muito sentido para a comunicação com o frontend. Precisávamos de algum mapper ou algo do tipo.
Esse foi meu desconforto inicial, que acabou evoluindo até eu perceber que estava faltando uma peça bem importante do quebra-cabeça. Mas, naquele dia, foram esses os commits mesmo.
Ia me preocupar com isso depois.
15 de julho de 2026 — Quarta-feira
Cinco dias sem mexer no projeto.
Cinco dias com o Renato enchendo o saco fervorosamente para desenvolvermos os testes unitários.
Então decidi mexer nisso naquele dia.
A ideia era ter testes para verificar automaticamente se os movimentos funcionavam, se o sistema anti-movimento-ilegal funcionava e afins.
Foi aí que o desconforto inicial chegou à sua maturidade máxima.
Não fazia sentido o mapping do board que tínhamos para o algoritmo de vencedor nos testes, porque não tínhamos exatamente a posição de cada movimento. Tínhamos apenas uma slice de nove zeros sobre a qual rodávamos um algoritmo para verificar quem ganhou.
Foi então que percebi:
O algoritmo para determinar o vencedor era somente isso: o algoritmo.
Não fazia sentido tratá-lo como o nosso Board de fato, porque faltavam informações.
Graças à insistência do Renato (parabéns, Renato) para fazermos os testes, pude finalmente concluir que meu desconforto fazia sentido: precisávamos de um Board propriamente dito para realizar a comunicação entre o backend e o frontend.
E esse foi um dos meus commits do dia.
16 de julho de 2026 — Quinta-feira
Foi um dia curto.
Configurei nosso MongoDB e o fiz rodar no Docker.
Muito massa.
22 e 23 de julho de 2026 — Quarta e quinta-feira
Aqui começou a parte clássica do backend: desenvolver as rotas de POST e GET, GET by ID, GET all dos jogos e dos usuários. Coisas bem simples e diretas, mas extremamente gostosas de fazer com Go.
Eu adoro demais desenvolver nessa linguagem.
Eu não me lembro se mexi no projeto no dia 22 e depois no dia 23 ou se virei a noite mexendo, porque estava muito animado para fazer essas partes. Mesmo que básicas, eram muito satisfatórias pela sintaxe e pela forma de testar em Go.
Porém, os dias simples estavam prestes a acabar: eu teria que iniciar meus estudos em WebSockets em Go.
Tan tan daaaan.
13 de agosto de 2026 — Quinta-feira
É. Uma pausa considerável no projeto acabou acontecendo. Coisas da vida, correria do trabalho, distrações porque eu tinha acabado de descobrir o jogo Zomboid (simplesmente incrível). Mesmo assim, eu estava pronto para estudar sobre WebSockets em Go e aplicar no projeto. Nos dias que fiquei fora do projeto, era tudo que eu pensava em fazer.
E então começou pelo básico: ler as docs do Gorilla WebSocket.
Eu já tinha mexido com WebSockets antes, em Python, a linguagem com a qual mais trabalhei. A primeira diferença entre os dois me foi bem interessante.
Em Python eu simplesmente criaria uma rota WebSocket e escutaria por mensagens. Algo mais ou menos assim:
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
while True:
message = await websocket.receive_text()
await websocket.send_text(message)
Mas, lendo as docs do Gorilla WebSocket, entendi que o fluxo básico, na verdade, consistia em iniciar com uma requisição HTTP normal e então fazer um "upgrade" para um WebSocket formalmente:
var upgrader = websocket.Upgrader{}
func websocketHandler(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
return
}
defer conn.Close()
for {
_, message, err := conn.ReadMessage()
if err != nil {
return
}
conn.WriteMessage(websocket.TextMessage, message)
}
}
Esse upgrade faz um handshake que, se rolar tudo bem, retorna a nossa *websocket.Conn, representando a conexão com o cliente. Daí sim, ao invés do request/response, ambos os lados mandam mensagens pela mesma connection.
Perfeito.
Não quero fazer aqui um resumo das docs. Caso interesse, leia! É muito boa:
https://pkg.go.dev/github.com/gorilla/websocket#section-readme
O importante é que agora eu entendia como funcionava e já tinha códigos de exemplo. Além disso, como sempre gosto de fazer ao estudar alguma tecnologia nova, assisti a um vídeo no YouTube fazendo as operações básicas com WebSocket, que me ajudou demais:
https://www.youtube.com/watch?v=qW_hGMUVVmg
Excelente. Agora era aplicar no jogo.
Não foi tão difícil: sistema de mensagens e conexão WebSocket para um player enviar para o outro a atualização do board a cada movimento, com checagem se ele venceu ou se o move estava correto.
Commits subidos.
"Pretty much done" foi um exagero, confesso.
18 de agosto de 2026 — Terça-feira
Aqui foi quando decidimos como de fato funcionaria para jogar com alguém.
Pensamos em fazer uma pool de jogadores, mas decidimos que isso seria muito complexo para uma simples POC, então fizemos um sistema de invite.
Você cria uma partida, recebe um link para enviar para alguém e, quando esse alguém clica no link, coloca o nomezinho e começa a jogar.
Simples e direto.
"Invite working" também foi um exagero.
19 de agosto de 2026 — Quarta-feira
É, este aqui foi um dia de derrota.
Nem eu nem o Renato somos muito fãs de IA no geral, principalmente para projetos que deveriam ser para diversão, mas às vezes...
Às vezes é complicado.
O Renato viu o backend e ficou muito animado para fazermos um front logo. Porém, sendo preguiçoso, não queria fazer a parte dele de rodar isso num terminal (ele ainda me promete todos os dias que ainda vai fazer).
Então mudamos de plano e decidimos que iríamos criar um frontend web simples, 100% feito com IA (porque nenhum dos dois gosta de mexer com frontend).
Qual o pior que poderia acontecer?
Nesse momento, o Renato e eu estávamos aqui em casa, promptando e codando, codando e promptando, e alguns problemas surgiram.
Primeiro que eu não havia feito um logging muito completo (pra ser justo, não tinha task mapeada pra isso, a culpa é do Renato), e também começamos a ter alguns probleminhas com nosso amigo CORS ao tentar rodar no frontend.
Foi uma madrugada estressante, mas, eventualmente, os problemas foram resolvidos, resultando em dois commits extremamente depressivos:
Hey, pelo menos o redirect problems e draw result eu fiz!
Mas, enfim, com o projeto rodando com as funções básicas, para uma POC, estava tudo praticamente certo.
Botei meu melhor homem para subir essa bomba para a AWS: o Renato.
E, depois de pouquíssimos problemas (dada sua alta perícia), o jogodavelha.site teve vida pela primeira vez!
Mas ainda tínhamos algumas coisas para resolver à medida que fomos mandando o jogo para amigos e familiares testarem.
01/02 de setembro de 2026 — Terça-feira
(Esse foi de madrugada, eu lembro.)
Bom, foi mais um tempinho até mexermos no projeto de novo. Algumas coisas relacionadas ao Tic tinham ocorrido nesse meio-tempo, mas isso é história pra já já.
O ponto era que: as pessoas jogavam uma partida, ela acabava com uma vitória ou empate, e era só isso.
Ainda não tínhamos uma feature para fazer revanches!
Essa foi minha próxima missão.
Acabou sendo um pouco difícil. Debati com o Renato sobre como seria a melhor forma de fazer e acabou sendo:
Quando termina uma partida, o jogador pode solicitar o rematch. O pedido vai para o MongoDB e é enviado por mensagens no WebSocket para os jogadores. O outro player pode aceitar e, daí, a partida é criada de uma forma vinculada à anterior, invertendo X e O (quem era X vira O e vice-versa).
Além disso, também tive que tratar a concorrência para evitar múltiplas criações e aceites de partidas e também tive que usar o WebSocket Hub para centralizar as atualizações.
Uff, bastante coisa, mas não quero complicar muito a parte técnica.
O commit tá lá!
Ah, eu também adicionei um .env.example antes mesmo do Renato me pedir 45x pra fazer isso.
03 de setembro de 2026 — Quinta-feira
Esse dia...
Não existem letras, símbolos, palavras ou organizações disso tudo, as chamadas línguas, para descrever o que ocorreu.
Porém, existem commits.
Existe a história de um bravo guerreiro, corajoso, em sua difícil jornada.
Existem as mensagens de sangue deixadas na parede.
Os restos de alma de um ser flagelado.
04 de setembro de 2026 — Sexta-feira
Sim, amigos, mesmo após tantos e tantos commits, lutando com a parte de infra (meu melhor cara de infra, inclusive), o problema ainda não havia sido resolvido.
Porém, não temam.
O verdadeiro herói dessa história deixaria o último commit no repositório, a salvação final da história árdua e dura do guerreiro Renato.
Um desfecho épico para uma história épica.
Obrigado por ler.