Sudoku - Java/Swing (Design Emergente) - sem uso de IA
- #Java
A maioria dos tutoriais de programação utiliza como esquema pedgógico a arquitetura pronta: "crie a classe X com estes atributos, o método Y com esta assinatura". O aluno recebe o resultado de um raciocínio de design que já pré-concebido, escondido, em algum lugar antes da primeira linha de código aparecer. O método ensina sintaxe, mas não ensina a projetar.
Este artigo exemplifica o CAMINHO INVERSO: nenhuma classe foi decidida antes de existir uma razão concreta para ela existir. Nenhuma IA desenhou a arquitetura do projeto com antecedência. O papel de qualquer assistente aqui, quando usado, foi o de escrever incrementos pontuais sob decisão humana já tomada, nunca o de arquiteto. Chamo isso, sem meias palavras, de método ANTI-VIBE CODING: o oposto de descrever um objetivo em linguagem natural e receber um sistema inteiro de volta. Cada classe deste Sudoku nasceu de uma dificuldade sentida rodando o programa, reconhecida através de um catálogo de heurísticas, não de um plano.
O ponto de partida: um ESBOÇO CONCEITUAL
Antes de abrir o IntelliJ, o projeto começou assim:

Três elementos nesse esboço definem todo o percurso que este artigo relata:
- Ponto de partida (rascunho inicial): uma janela vazia e o
Main.javamais simples possível:JFrame,JPanel, três linhas de configuração do framework Java/Swing. Nenhuma regra de Sudoku, nenhuma classe de domínio. - Direcionamento (emergente/orgânico): a seta que liga o rascunho ao objetivo não é uma lista de passos numerados, é uma intenção de crescimento, sem prescrever o caminho.
- Objetivo final (painel completo): o tabuleiro 9x9 com os blocos demarcados e os três botões: "Verificar jogo", "Reiniciar jogo", "Concluir". O desenhado aparece aqui como destino, não como especificação técnica.
Esse esboço foi a única ideia que existiu antes do código. Tudo o que vem a seguir, cada classe, cada refatoração, foi decidido rodando o programa, nunca desenhado no papel com antecedência.
Fluxograma de evolução do projeto

Catálogo de Gatilhos (são heurísticas, não um roteiro)
Em vez de um plano de arquitetura, o guia usado ao longo de todo o desenvolvimento foi um catálogo de gatilhos: sintomas de código que sinalizam quando vale a pena parar e extrair algo. A lógica é cíclica, não sequencial: escreva o mais simples possível, rode, sinta a dificuldade (se houver), consulte o catálogo, refatore o mínimo necessário, siga em frente. Nenhuma abstração é criada por antecipação. A REGRA DE OURO é o princípio YAGNI (You Aren't Gonna Need It): não construa hoje o que só um consumidor hipotético do futuro usaria.
Ao longo das dez etapas a seguir, cada refatoração relevante é creditada explicitamente ao gatilho do catálogo que a motivou.

A evolução do projeto, parte por parte
Antes de qualquer código, apenas duas decisões: o desenvolvimento partiria da interface gráfica — prioridade em ver algo na tela o quanto antes — e as regras do jogo entrariam apenas quando se tornassem necessárias, nunca antes. Ambiente: Java 21, IntelliJ IDEA, sem dependências externas.
Parte 0 - Exibição Inicial da Janela ("Kickoff")
O primeiro código escrito não continha nenhuma classe de domínio — apenas o suficiente para validar que o ambiente Swing funcionava: um JFrame, um JPanel vazio, setVisible(true). A ausência de qualquer separação de responsabilidades nesta etapa foi deliberada: não havia, ainda, informação suficiente sobre o problema para justificar camadas.
Resultado: janela em branco, vazia, 600x600 pixels, título "Sudoku", centralizada na tela.

Parte 1 - Construção da Grade 9x9
Com a janela validada, o passo seguinte foi puramente visual: um GridLayout(9, 9) e um laço duplo criando 81 JTextField vazios. Nenhuma referência aos campos foi guardada. A variável do laço era descartada a cada iteração, uma limitação conscientemente adiada para quando se tornasse um problema real.
Resultado: janela preenchida com 81 campos de texto vazios, organizados em grade uniforme.

Parte 2 - Preenchimento Manual dos Valores Fixos
Para ver números reais na tela, o caminho mais direto foi guardar uma matriz de referências aos campos e chamar setText(...) manualmente, célula por célula, para cada dica do Sudoku. A solução funcionou e expôs, de imediato, duas fragilidades: dezenas de linhas quase idênticas, e nenhuma restrição impedindo o jogador de apagar uma dica.
Resultado: dicas exibidas corretamente, porém totalmente editáveis (nada impediria sua remoção). Note que os números foram expostos nos respectivos campos, sem qualquer formação.

Parte 3 - Arrays Paralelos para Valor Esperado e Status Fixo
Resolver as duas fragilidades exigiu saber, para qualquer célula, qual o valor correto esperado e se ela era fixa (não apenas para as dicas memorizadas manualmente). A solução adotada, ainda sem criar nenhuma classe, foi um segundo e um terceiro array, viajando lado a lado com o array de campos, usados para preencher o texto e bloquear a edição via setEditable(false).
Resultado: dicas exibidas, legíveis e bloqueadas; demais células vazias e editáveis.

Parte 4 - Extração da Classe Space
🟩 Gatilho do catálogo: "Duas ou mais variáveis que sempre viajam juntas, na mesma coordenada --> extrair classe."
Três arrays precisando permanecer sincronizados pela mesma coordenada, sem qualquer garantia do compilador contra um índice trocado, é exatamente o sintoma que o catálogo associa à extração de uma classe. Nasceu a classe Space, agrupando valor atual, valor esperado e status de fixação em um único objeto: a primeira classe de domínio do projeto, motivada por fragilidade observada, não por planejamento prévio.
Resultado: comportamento visual idêntico ao da etapa anterior. A mudança foi inteiramente interna, na organização do código.

Parte 5 - Restrição de Digitação e Sincronização com o Space
Dois problemas novos surgiram ao testar: nada impedia digitar letras ou múltiplos caracteres em uma célula livre, e o Space nunca seria atualizado quando o jogador digitasse. As classes NumberTextLimit (restringindo o Document do campo) e NumberText (unindo aparência, restrição e sincronização em um único componente) resolveram as duas de uma vez. Adicionalmente, um ajuste de estilo deu às células fixas fundo cinza e negrito, tornando-as visualmente inconfundíveis das editáveis.
Resultado: digitação restrita a um dígito de 1 a 9 por célula; células fixas com alta legibilidade e clara diferenciação visual.

Parte 6 - Organização Visual em Blocos 3x3 (classe SudokuSector)
A grade uniforme 9x9, embora funcional, não parecia um Sudoku de verdade: faltavam as bordas grossas separando os nove blocos 3x3. A classe SudokuSector resolveu isso, e o laço de montagem do tabuleiro precisou crescer de dois para quatro níveis de profundidade, navegando simultaneamente entre coordenadas de bloco e coordenadas de célula dentro do bloco.
Resultado: tabuleiro com os nove blocos claramente demarcados por bordas pretas grossas, reproduzindo a aparência padrão de um Sudoku.

Parte 7 - Botão de Verificação do Jogo
O primeiro retorno de estado ao jogador foi implementado da forma mais direta possível: um botão cujo ActionListener contém, ele mesmo, o laço completo de varredura das 81 células. Nenhuma preocupação de organização interna nesta etapa — o objetivo era só ter feedback funcionando. Dois ajustes de estilo (fonte do botão via setFont, fonte da caixa de diálogo via UIManager) completaram a etapa.
Resultado: clique no botão exibe corretamente se o jogo está completo/incompleto e se contém erros, em interface com tipografia consistente.

Parte 8 - Extração de Board e GameStatusEnum
🟩Gatilhos do catálogo: "Bloco de código prestes a ser copiado pela segunda vez --> extrair método/classe" e "Duas variáveis booleanas representando um conceito com mais de dois estados --> extrair enum."
O laço de verificação dentro do botão, prestes a precisar ser duplicado nos próximos botões (reiniciar, concluir), disparou o primeiro gatilho. Nasceu Board, concentrando getStatus() e hasErrors(). As duas variáveis boolean que tentavam representar o estado do jogo (completo, temErro) dispararam o segundo gatilho, dando origem ao GameStatusEnum. Nenhuma dessas duas classes foi antecipada nas etapas anteriores (as duas só passaram a fazer sentido quando a duplicação ficou visível).
Resultado: comportamento idêntico ao da etapa anterior. Extração puramente interna, sem impacto visual.

Parte 9 - Botões de Reiniciar e Concluir
Com Board já extraído, completar o conjunto de ações do jogo custou apenas dois métodos novos (reset(), gameIsFinished()) e dois botões que os consomem (nenhum laço manual precisou ser reescrito). Um bom exemplo de como uma extração bem motivada na etapa anterior reduz o custo de tudo que vem depois.
Resultado: os três botões lado a lado, com confirmação antes de reiniciar e mensagens corretas de pendência/conclusão.

Problema verificado nesta etapa
O reinício limpa os dados internamente, mas os campos visuais ainda não refletem a mudança. Esse comportamento é esperado neste ponto do projeto e será tratado na Parte 10.

Parte 10 - Sincronização entre Tela e Domínio (Padrão Observer)
🟩 Gatilho do catálogo: "Um dado muda, mas algo que dependia dele não é avisado --> introduzir um mecanismo de notificação (padrão Observer)."
A divergência observada ao final da etapa anterior é o exemplo mais claro, em todo o projeto, de um gatilho do catálogo disparando exatamente como previsto: Space é limpo, mas nenhum NumberText é avisado (a interface fica desatualizada silenciosamente). A solução foi um pequeno sistema de inscrição e notificação (EventEnum, EventListener, NotifierService), com NumberText passando a implementar EventListener para escutar o evento de limpeza.

Resultado: execução de Main concluída com sucesso: ao preencher células livres e clicar em "Reiniciar jogo", confirmando no diálogo "Deseja realmente reiniciar o jogo?", o tabuleiro foi corretamente limpo: as células editáveis voltaram ao estado vazio, enquanto as células fixas permaneceram intactas, em negrito sobre fundo cinza. A divergência entre estado interno e exibição visual, observada ao final da Parte 9, foi eliminada.

Conclusão: objetivo do esboço alcançado, sem arquitetura prévia ✅
Comparando o resultado desta última etapa com o "Objetivo final" desenhado no esboço inicial (o tabuleiro com blocos demarcados e os três botões) os dois coincidem. A diferença não está no destino: está em como se chegou até ele. Nenhuma dessas dez classes (Space, Board, GameStatusEnum, NumberTextLimit, NumberText, SudokuSector, EventEnum, EventListener, NotifierService, mais o próprio Main) foi decidida antes de uma dificuldade concreta a justificar. Cada uma pode ser rastreada a um sintoma específico do catálogo de heurísticas, sentido rodando o programa (não a um documento de design escrito antecipadamente, seja por um humano, seja por uma IA).
É esse o argumento central deste artigo: um catálogo de heurísticas bem escolhido (não um assistente de IA desenhando a arquitetura por antecipação) é instrumento suficiente para conduzir um projeto real da folha em branco a um sistema funcional, com decisões de design que o próprio desenvolvedor consegue justificar, uma a uma, porque foi ele quem as concebeu.
Referências Bibliográficas:
- BECK, Kent. Extreme Programming Explained: Embrace Change. Reading, MA: Addison-Wesley, 2000.
- FOWLER, Martin. Refactoring: Improving the Design of Existing Code. Reading, MA: Addison-Wesley, 1999.
- GAMMA, Erich; HELM, Richard; JOHNSON, Ralph; VLISSIDES, John. Design Patterns: Elements of Reusable Object-Oriented Software. Reading, MA: Addison-Wesley, 1994.
- MARTIN, Robert C. Clean Code: A Handbook of Agile Software Craftsmanship. Upper Saddle River, NJ: Prentice Hall, 2008.




Olá...
Isso sim é uma verdadeira aula.
Espero ver mais conteudos assim aqui na plataforma.
Obrigado por compartilhar.