Já pensou em ter um advisor para chess.com? Imagine um assistente pessoal que te ajuda a evitar aqueles vacilos que te impedem de subir no ranking. Um desenvolvedor criou uma ferramenta que faz exatamente isso! Neste artigo, vamos explorar como essa pequena aplicação para Windows, escrita em C# e WPF, pode te dar aquela força extra nas suas partidas de xadrez online. E o melhor de tudo: é grátis e de código aberto!
Como tudo começou
O criador do software adora jogar xadrez online, mas sempre caía na armadilha do “olho de túnel”, fixando-se em um único lance e perdendo oportunidades cruciais. Sua solução? Desenvolver um advisor que o alertasse sobre os erros. A ideia era integrar o Stockfish, um motor de xadrez de código aberto, ao chess.com. Mas como fazer essa mágica?
Inicialmente, ele pensou em usar visão computacional para identificar as peças e o tabuleiro. Mas o chess.com oferece inúmeras opções de tabuleiros e peças, o que inviabilizaria essa abordagem. A solução veio ao incorporar o navegador diretamente no aplicativo. Assim, ele poderia ler a página da web sem problemas. Se você se interessa por novidades do mundo da tecnologia, aproveite para saber mais sobre os planos da Xiaomi para novos dispositivos em 2025.
O resultado final é um aplicativo simples em WPF, com uma única janela e um controle web, que executa o Stockfish como um processo filho e interage com ele através de fluxos de entrada e saída do console. Mas nem tudo foram flores no início. Os primeiros testes com o controle WebBrowser em WPF resultaram em erros de JavaScript e incompatibilidade com as páginas modernas.
Para quem busca opções de entretenimento, confira três filmes de ficção científica no Amazon Prime para assistir em março de 2025.
O navegador embutido
O antigo controle WebBrowser do WPF ainda usa as bibliotecas do Internet Explorer, que não são compatíveis com as páginas da web atuais. Era preciso algo mais moderno, como o Chromium ou o Edge. Para resolver isso, o desenvolvedor instalou o componente Microsoft.Web.WebView2 via NuGet.
Com o novo componente, tudo começou a funcionar como esperado. As sessões e cookies eram suportados, mas a forma de interagir com o DOM precisou ser adaptada. Em WebView2, não era possível acessar o HtmlDocument e o DOM da mesma forma que antes. O mundo mudou, e as páginas são dinâmicas, exigindo uma abordagem diferente.
O código abaixo mostra como extrair o HTML da página:
const string script = "document.documentElement.outerHTML";
var result = await WebBrowser.CoreWebView2.ExecuteScriptAsync(script);
var decodedHtml = Regex.Unescape(result.Trim('"'));
O próximo desafio era descobrir como extrair as informações do tabuleiro e das peças a partir do código HTML.
Analisando o tabuleiro de xadrez
Na implementação atual, o tabuleiro do chess.com é codificado com um conjunto de divs:
<div class="piece bb square-55" style=""></div>
<div class="piece square-78 bk" style=""></div>
<div class="piece square-68 br" style=""></div>
...
<div class="piece square-61 wk" style=""></div>
<div class="element-pool" style=""></div>
<div class="piece wq square-41" style=""></div>
<div class="element-pool" style=""></div>
A classe de uma peça sempre começa com “piece”, o primeiro caractere da classe de duas letras é “w” ou “b” (peça branca ou preta), e o segundo indica a peça (“r” para torre, “p” para peão, “q” para rainha, etc.). “square-XY” indica a casa. No entanto, o tabuleiro pode ser invertido se o jogador estiver jogando com as peças pretas. Isso é determinado por outro elemento:
<wc-chess-board class="board flipped">
A presença da classe flipped
indica que o tabuleiro está invertido. A posição de todas as peças pode ser extraída com uma expressão regular simples. O desafio seguinte foi transmitir essas informações para o Stockfish.
Interagindo com o Stockfish
A versão binária stockfish-windows-x86-64-avx2.exe foi baixada. Ela não exige instalação e pode ser colocada em qualquer pasta. Ao ser executada, exibe uma janela de console vazia.
O diálogo sempre começa com o comando “uci“. O motor fornece informações sobre si e finaliza a saída com a palavra “uciok“. Em seguida, é recomendável especificar o número de threads para melhorar o desempenho (o padrão é um thread) com o comando “setoption name Threads value XXXX“. Após finalizar as configurações, o comando “ucinewgame” indica um novo jogo. Depois, é preciso especificar a posição para análise com “position fen XXXX“, onde XXXX é a posição codificada. Por fim, o comando “isready” solicita a aceitação. Se tudo estiver correto, o motor responderá com “readyok“. Para iniciar a análise, usa-se o comando go com parâmetros, como “go movetime XXXX“, onde XXXX é o tempo em milissegundos para思考。
Após isso, o motor começa a avaliar as opções e exibe informações interessantes no console, como a avaliação da posição atual. A mensagem final é “bestmove XXXX” (o melhor lance encontrado), que será exibida na barra de status. Depois, o fluxo e o processo filho podem ser fechados. Mas o que é essa tal string codificada em FEN?
"rnbqkbnr/.../RNBQKBNR w KQkq - 0 1"
???
Uma posição FEN consiste em uma descrição de oito fileiras, separadas por barras. Por exemplo, a terceira fileira pode ser “2P2N2”. O “2” significa dois espaços vazios, “P” é um peão branco, “N” é um cavalo branco. As peças pretas são escritas em minúsculas.
Em seguida, vem um bloco de informações com seis campos. O primeiro campo, “w“, indica que é a vez das brancas, ou “b” para as pretas. Depois, listam-se os possíveis roques (“KQkq“, dois para cada lado) ou “–” se o roque não estiver disponível. O próximo campo indica se há capturas en passant. O penúltimo campo é o contador de meio lance, usado para determinar um empate se não houver movimentos de peões ou capturas por um longo tempo. O último campo indica o número de lances completos. Para mais detalhes sobre FEN, consulte a descrição FEN.
Com as peças lidas da página e a posição codificada em FEN, o Stockfish recebe a informação e retorna “bestmove XXXX“, que é exibido na barra de status.
Mas será que o problema foi resolvido? Ainda não.
Stockfish joga MUITO bem
Ao fazer apenas os melhores lances, o jogador ganharia de todos, inclusive do campeão mundial, e seria banido da plataforma. O objetivo é ter um advisor que proteja de lances ruins, sem sugerir jogadas muito acima ou abaixo do nível do oponente.
Para isso, foi necessário explorar os comandos UCI. O Stockfish tem uma configuração para ajustar a força do jogo (níveis de 1 a 20 ou rating Elo de 1320 a 3190), mas o algoritmo dessa função é bastante direto e pode escolher lances aleatórios e sem sentido. Para quem tiver interesse, aqui está o início dessa função:
Skill(int skill_level, int uci_elo) {
if (uci_elo) {
double e = double(uci_elo - LowestElo) / (HighestElo - LowestElo);
level = std::clamp((((37.2473 * e - 40.8525) * e + 22.2943) * e - 0.311438), 0.0, 19.0);
}
else {
level = double(skill_level);
}
...
A solução encontrada foi usar a configuração “set option name MultiPV value N“, que instrui o Stockfish a retornar os N melhores lances, ordenados de forma decrescente. Além disso, para cada lance, é possível retornar as estatísticas WDL (“win-draw-loss”) com “setoption name UCI_ShowWDL value true“. Esses números indicam a probabilidade de vitória, empate ou derrota. Para começar, foram testados os três melhores lances.
No entanto, apenas três opções de lances não eram suficientes.
Eu quero ver todos os lances
Ter três opções de diferentes forças é melhor que ter apenas uma. Mas se já temos a avaliação de cada lance, por que não especificar o nível desejado nas configurações, como “+3.00” (vantagem de um bispo extra) ou “-1.00” (deixar o oponente ter um peão extra)? E, ao mesmo tempo, colorir os lances em diferentes tons, do vermelho ao verde? Em vez de mostrar os três melhores lances, mostrar todos!
Para evitar lances de abertura ridículos, foi compilado um livro de aberturas a partir de várias fontes em um único arquivo .csv (cerca de 3000 posições). Se um lance estiver presente na teoria, seu nome pode ser exibido.
Por que só chess.com?
Após mostrar o projeto em uma plataforma, um comentarista mencionou que o chess.com já tem uma opção de dica ao jogar contra um bot, e que os profissionais estão no lichess.org, que não oferece essa opção. Para adicionar o suporte ao lichess.org, foi preciso descobrir como o tabuleiro é codificado lá.
<cg-board>
<piece class="black rook" style="transform: translate(0px, 0px);"></piece>
<piece class="black bishop" style="transform: translate(174px, 0px);"></piece>
<piece class="black queen" style="transform: translate(174px, 87px);"></piece>
<piece class="black king" style="transform: translate(522px, 0px);"></piece>
A lógica permaneceu a mesma. Agora é possível escolher entre os dois sites a qualquer momento. Falando em diferentes opções, que tal aproveitar a temporada de impostos para economizar em planos de telefonia?
Eu preciso de um advisor, não de um bot de xadrez
Apesar dos avanços, a ferramenta ainda não estava funcionando como o esperado. A busca por um lance específico nas faixas multicoloridas era exaustiva. A solução foi agrupar os lances por peças.
var groups = _moves
.Values
.OrderByDescending(move => move.Score)
.GroupBy(move => move.FirstMove[..2])
.Select(group => group.ToArray())
.OrderByDescending(list => list.First().Score)
.ToArray();
Encontrar um lance dessa forma é mais rápido, mas o design ficou sobrecarregado, com muitos símbolos repetidos. A solução foi descartá-los, já que a primeira metade do lance para a peça é sempre a mesma. Assim, chegou-se à interface atual.
Os lances mais próximos da pontuação desejada são mostrados por completo, enquanto os outros são representados por barras. A intensidade do jogo pode ser ajustada selecionando a pontuação com os botões à direita do indicador. Também é possível clicar em um lance interessante para ajustar a intensidade.
Nessa interface, os lances mais promissores ganham destaque, facilitando a identificação de oportunidades e a correção de erros. E por falar em melhorias, que tal conferir as novidades em tecnologia: MacBook Air, Mac Studio e iPads?
Em situações de desvantagem, por exemplo, ao definir o nível para “-1.50”, quase todos os lances são ruins, representados pela cor vermelha. No entanto, pode haver um lance “verde” que oferece a oportunidade de um xeque-mate em quatro movimentos. Mas ler os lances nas faixas e traduzir “h5f7” no tabuleiro ainda é cansativo. Seria ideal desenhar o lance diretamente no tabuleiro…
Desenhando um lance
A ideia inicial era colocar um Canvas na frente do elemento WebBrowser, obter o deslocamento do tabuleiro em relação ao canto superior esquerdo com uma função JS e desenhar linhas ou texto no tabuleiro. Mas isso não funcionou. O WebView2 usa aceleração de hardware e DirectComposition para renderizar seu conteúdo, o que dificulta a integração com o sistema de renderização WPF tradicional. A solução foi implementar os elementos no tabuleiro da mesma forma que o chess.com, desenhando uma seta como um polígono e inserindo-a diretamente no código da página usando JS.
var x1 = (src[0] - 'a') * 12.5 + 6.25;
var x2 = (dst[0] - 'a') * 12.5 + 6.25;
var y1 = ('8' - src[1]) * 12.5 + 6.25;
var y2 = ('8' - dst[1]) * 12.5 + 6.25;
if (!isWhite) {
x1 = 100.0 - x1;
x2 = 100.0 - x2;
y1 = 100.0 - y1;
y2 = 100.0 - y2;
}
var dx = x2 - x1;
var dy = y1 - y2;
var angle = Math.Round(Math.Atan2(dx, dy) * (180.0 / Math.PI), 2);
var length = Math.Round(Math.Sqrt(dx * dx + dy * dy), 2);
const double headRadius = 1.5;
var point1X = x1 + headRadius;
var point2Y = y1 - length + headRadius * 2;
var point3X = x1 + headRadius * 2;
var point4Y = y1 - length;
var point5X = x1 - headRadius * 2;
var point6X = x1 - headRadius;
var points = $"{point1X},{y1} {point1X},{point2Y} {point3X},{point2Y} {x1},{point4Y} {point5X},{point2Y} {point6X},{point2Y} {point6X},{y1}";
var svgElement = $"<svg viewBox='0 0 100 100'><polygon transform='rotate({angle} {x1} {y1})' points='{points}' style='fill: rgb(255, 255, 0); opacity: 0.7;' /></svg>";
O resultado foi satisfatório, mas a seta não desaparece automaticamente quando o oponente faz um lance. Para resolver isso, foi implementado um MutationObserver, que remove a seta sempre que há mudanças no tabuleiro.
window._chessBoardObserver = new MutationObserver(function(mutations){{
if(window._disableArrowObserver){{
return;
}}
mutations.forEach(function(mutation){{
if(mutation.type === 'childList' || mutation.type === 'attributes'){{
removeArrow();
}}
}});
}});
Dificuldades inesperadas
A posição FEN estava imprecisa na parte final, sempre definida como “KQkq – 0 1“. A falta de informações sobre a possibilidade de capturas en passant, a perda do direito ao roque e o número de lances sem movimento de peões resultava em análises incorretas do Stockfish. A tarefa de corrigir essa imprecisão se mostrou mais complexa do que o esperado.
A princípio, a ideia era encontrar o FEN da posição atual no código da página do chess.com. Essa informação está disponível ao jogar contra um bot, mas não contra um humano, provavelmente para dificultar a análise da posição por aplicativos de terceiros.
No entanto, a página registra todos os lances anteriores, tanto no chess.com quanto no lichess.org, embora em formatos diferentes. A partir dessas informações, seria possível partir da posição inicial e repetir todos os lances para chegar à posição atual, construindo o FEN correto.
<i5z>8</i5z><kwdb class="">Nf3</kwdb><kwdb class="">Nc6</kwdb>
<i5z>9</i5z><kwdb class="">Be2</kwdb><kwdb class="">a6</kwdb>
<i5z>10</i5z><kwdb class="">Nbxd4</kwdb><kwdb class="">Bc5</kwdb>
<i5z>11</i5z><kwdb class="">c3</kwdb><kwdb class="">O-O</kwdb>
<i5z>12</i5z><kwdb class="">Bd3</kwdb><kwdb class="">Ne7</kwdb>
O desafio é que a notação SAN não informa a casa de onde a peça se move. Traduzir um lance de “Bd3” (SAN) para “c1d3” (UCI) não é trivial, especialmente considerando as ambiguidades. Como o Stockfish não traduz SAN para UCI, era necessário escrever um motor de xadrez ou usar uma biblioteca externa.
A biblioteca Geras1mleo foi a escolhida. Apesar de excelente, 80% do código era desnecessário para o projeto, e não havia a opção de configurar as peças manualmente. Após adaptar a biblioteca e escrever testes unitários, o cálculo do FEN exato no navegador levou sete noites de codificação e depuração.
O FEN na barra de status pode ser copiado para análise em outros programas. O trecho “kq – 2 12” indica que apenas o jogador preto pode fazer ambos os roques (“kq”), não há capturas en passant (“-“), dois meio-lances foram feitos sem movimento de peões e 12 lances completos foram feitos desde o início do jogo. Agora, a análise da posição pelo Stockfish é 100% precisa.
Se você se interessa por temas relacionados, não deixe de ler sobre a gestão de dados em grandes eventos: um diferencial estratégico.
A interface atual do software oferece uma experiência completa e precisa para análise de partidas de xadrez.
Conclusão
O objetivo deste projeto foi criar uma ferramenta para auxiliar jogadores de xadrez a evitarem erros e melhorarem seu desempenho. É importante lembrar que trapacear é injusto, mas o advisor pode ser usado para equilibrar partidas entre jogadores de diferentes níveis, tornando os jogos mais interessantes. Se o oponente tem um rating de 1800, definir a força para “+0.50” permitirá jogar em um nível de 1900-2000, com o consentimento do oponente, para fins de treinamento, por exemplo.
Este conteúdo foi auxiliado por Inteligência Artificiado, mas escrito e revisado por um humano.