Nas últimas semanas, eu e meus amigos temos nos divertido com Wordle: um jogo de browser no qual você tem 6 palpites pra adivinhar uma palavra, sendo que cada palpite te traz informações novas (se a letra está na posição certa, se está na posição errada, ou se não pertence à solução). É basicamente uma versão modificada de Mastermind/Senha, ou como eu sempre chamei, "aquele puzzle do Gustavinho e o Enigma da Esfinge que eu não conseguia passar"
Sendo um jogo complexo de regras simples, naturalmente surgem perguntas de cunho computacional: qual a melhor palavra pra se começar? qual a melhor estratégia? é possível sempre ganhar com menos de 6 palpites? Estimulado pelo meu caro amigo @BrunoOgava e inspirado por esse post do Matt Rickard, tentei responder algumas dessas perguntas para o term.ooo, a “localização” mais famosa do Wordle em pt-br.
Quando confrontadas com essa pergunta, a maioria das pessoas começa a pensar na frequência das letras: uma palavra com mais letras comuns é melhor, porque você descobre logo de cara se essas letras pertencem à solução. Essa ideia é intuitiva, mas ainda restam dúvidas: será que também devemos considerar a frequência de letras por posição? Como considerar isso? E letras repetidas, são inúteis?
Precisamos usar algum princípio, e o princípio que mais fez sentido pra mim foi o seguinte: a melhor palavra é a que reduz ao máximo o número de soluções possíveis. Para entender o que significa "soluções possíveis", vamos olhar um exemplo simples.
Suponha um Wordle simplificado com 2 letras, no qual você tenta adivinhar qual a solução dentre 4 palavras possíveis: AA
, AB
, AC
e BC
. Vamos supôr também que você está em dúvida entre dois palpites: AB
e CC
. Qual o melhor palpite dentre esses dois? De cara, AB
parece mais interessante porque contém duas letras diferentes, enquanto CC
repete a mesma letra duas vezes; mas vamos ver isso de uma forma um pouco mais detalhada.
Se você chutar o palpite AB
, existem três possíveis respostas que o jogo pode te dar:
AB
.
AA
ou AC
.
BC
.
(Todas as outras respostas que o jogo teoricamente poderia te dar (ex.: ⬛🟩, ⬛⬛, ⬛🟨, etc) não se encaixam em nenhuma palavra possível, portanto elas podem ser seguramente ignoradas.)
Em compensação, se você chutar o palpite CC
, existem duas possíveis respostas:
AA
ou AB
.
AC
ou BC
.
Olhando pra essa esquematização, é intuitivo que o palpite AB
seja melhor: há dois casos em que ele nos leva à solução final, enquanto em contrapartida o palpite CC
sempre nos faz continuar em dúvida. Porém, como conseguimos traduzir essa ideia intuitiva em um critério que possa ser programado? A chave está no cálculo da média de soluções possíveis restantes.
Se você se sente confortável chamando números de letras e falando sobre moedas enviesadas, por favor prossiga para a próxima seção. Caso contrário, pule para a parte mais legal que é a de Resultados, logo mais abaixo; assuma que eu consegui capturar a ideia de "soluções possíveis restantes" em um número, e que usei esse número pra ranquear as palavras.
Essa é a parte do texto em que eu tento fingir que aprendi alguma coisa de probabilidade e estatística.
Nosso objetivo é obter uma métrica que nos faça dizer se um palpite é melhor que outro, e pra isso vamos usar o conceito de soluções possíveis restantes: um palpite é melhor do que outro se ele possui, em média, uma quantidade menor de soluções possíveis restantes. Vale lembrar que ter mais soluções possíveis é ruim porque significa que você está em muitas dúvidas sobre qual a real solução; em contrapartida, ter menos soluções possíveis é bom porque significa que você está em dúvida entre poucas palavras (idealmente, apenas 1, que é a solução).
No exemplo anterior, o palpite AB
é melhor do que o palpite CC
porque AB
possui, em média, menos soluções possíveis restantes:
AB
, o palpite AB
nos reduz a 1 solução possível;AA
, o palpite AB
nos reduz a 2 soluções possíveis;AC
, o palpite AB
nos reduz a 2 soluções possíveis;BC
, o palpite AB
nos reduz a 1 solução possível.Assumindo que as soluções são escolhidas com probabilidade uniforme, podemos calcular a média ponderada de soluções restantes, com o valor sendo o “número de soluções possíveis” e o peso sendo a “porcentagem de palavras que produzem aquela resposta”. Para o palpite AB
, temos uma média de:
$$ \frac{1}{4} \times 1 + \frac{1}{4} \times 2 + \frac{1}{4} \times 2 + \frac{1}{4} \times 1 = 1.5 \text{ soluções possíveis restantes} $$
Analogamente, o palpite CC
nos reduz em média a:
$$ \frac{1}{4} \times 2 + \frac{1}{4} \times 2 + \frac{1}{4} \times 2 + \frac{1}{4} \times 2 = 2 \text{ soluções possíveis restantes} $$
Assim fica evidente que o palpite AB
é melhor, porque ele reduz ao máximo o número de soluções possíveis!
Portanto, para trazer isso de volta ao Wordle tradicional, basta passarmos por todos os palpites e fazer esse mesmo cálculo de “média de soluções possíveis” para cada palpite. Descrevendo esse processo algoritmicamente, temos:
Assim, o palpite com menor $s(w)$ é o melhor palpite possível!
O único detalhe que falta ser explicado é como calcular a quantidade de soluções possíveis que são eliminadas por um palpite; felizmente, isso também pode ser descrito de forma simples. Seja $w[k]$ a $k$-ésima letra do palpite $w$, e seja $r[k]$ a resposta à $k$-ésima letra do palpite. Temos:
FARTO
mas seu palpite foi FALAR
, então sua resposta vai ser 🟩🟩⬛⬛🟨 - o segundo A
vai tomar um ⬛ mesmo estando na solução, porque ele já foi confirmado anteriormente (no segundo 🟩). Portanto, é preciso considerar as outras letras confirmadas na hora de eliminar os ⬛s.Com o procedimento definido, o resto é só implementação. O repositório em que fiz meus testes pode ser acessado aqui: https://github.com/vgarciasc/termooo-solver.
(Obs.: como cada palpite tem 5 letras e cada letra pode ser 🟩, ⬛ ou 🟨, então $|R| = 3^5$)
(Obs.: o algoritmo para encontrar o melhor palpite inicial é da ordem de $O(|W|^2 |R|)$)
Ok vamos ver a parte dos gráficos legais. O term.ooo tem duas listas internas:
A ideia é que o term.ooo aceita palpites que pertençam à lista completa (toda palavra de 5 letras no dicionário), mas ele só tira soluções possíveis de uma lista menor (com palavras de 5 letras que as pessoas conhecem).
Rankeando as palavras da lista completa de acordo com a média de soluções possíveis restantes após usar aquela palavra como palpite inicial, temos:
A melhor palavra sendo “SÓRIA”, que reduz em média a quantidade de soluções possíveis a ~31. Honestamente não faço a ideia do que seja uma “SÓRIA”, mas aparentemente é um tipo de tecido português?? (Existe um motivo pra essa lista não ser a lista de soluções possíveis...)
Para o conjunto reduzido, temos:
A melhor palavra desse conjunto é “SÉRIO”, que em média reduz a quantidade de soluções possíveis a ~36 (bem próximo dos ~31 de “SÓRIA”). O que acho mais interessante aqui é que o melhor palpite não tem a letra “A”: nossa intuição nos diz que é melhor começar com letras comuns, e “A” é a mais comum, então começar com uma palavra sem “A” parece sub-ótimo - mas na verdade não é! É um bom exemplo de como nossa intuição pode falhar.
Por curiosidade, os piores palpites do conjunto reduzido são “ESSES” e “OSSOS”, palavras com muitas letras comuns repetidas. Em média, essas palavras nos fazem continuar em dúvida entre ~500 soluções! (Agora dá pra ter uma perspectiva sobre as ~36 de “SÉRIO”)
Ok, agora temos o melhor palpite inicial. Mas quem disse que precisamos parar por aí? O jogo ainda continua: qual o segundo melhor palpite, e o terceiro, e o quarto?
Felizmente, a estratégia anterior ainda se aplica: com a resposta que você recebeu do palpite inicial, o conjunto de soluções possíveis foi reduzido. Agora, basta escolher o palpite que reduz ao máximo o número de soluções possíveis dentro desse novo conjunto,
Vamos voltar ao exemplo anterior, em que o conjunto de soluções possíveis era {AA
, AB
, AC
e BC
} e estávamos comparando os palpites AB
e CC
. Vamos supôr que você chutou AB
e recebeu como resposta 🟩⬛, reduzindo assim o conjunto de soluções possíveis pra {AA
, AC
}. Qual o melhor palpite a se fazer agora, AB
ou CC
?
Se você fizer o palpite AB
, há 1 resposta possível:
AA
ou AC
. Restam 2 soluções possíveis.Se você fizer o palpite CC
, há 2 respostas possíveis:
AA
. Resta 1 solução possível (vitória!)AC
. Resta 1 solução possível (vitória!)Assim, o palpite AB
nos reduz em média a 2 soluções possíveis, e o palpite CC
nos reduz em média a 1 solução possível. O palpite CC
é melhor! (O que é óbvio: o palpite AB
já foi feito, e portanto não carrega nenhuma informação nova).
Em outras palavras, a cada passo do jogo podemos empregar o princípio guloso de escolher a palavra que em média traz a maior redução em soluções possíveis. Estou confiante de que essa é a estratégia ótima[1], mas não cheguei a provar.
Pra finalizar, implementei essa estratégia e testei ela em 1637 partidas diferentes, uma para cada solução possível. Assumindo a lista reduzida de palpites, obtive os seguintes resultados:
JATOS
e PATOS
, ambos demoram 6 turnos pra serem encontrados - provavelmente o algoritmo demora pra conseguir discerni-los de outras palavras similares como GATOS
e FATOS
(duas palavras que demoram 5 turnos pra serem encontradas, assim como MOTOS
, FOFOS
, BOBOS
, etc).Também é legal ver o algoritmo rodando em tempo real. Aqui, por exemplo, o algoritmo percebeu na segunda tentativa que a palavra era _OTAR
, mas ao invés de testar uma das quatro possíveis soluções (NOTAR
, DOTAR
, VOTAR
e BOTAR
), ele decidiu palpitar BUNDA
(uma palavra com B
, N
e D
) pra descobrir com certeza absoluta a resposta. Eu não poderia inventar isso nem se quisesse
Modo difícil: O Wordle possui um “modo difícil” em que você é obrigado a utilizar todas as dicas que recebeu até agora - portanto, se você sabe que a palavra tem um R
, então só poderá usar palpites que tem pelo menos um R
. Acredito que a estratégia acima tenha que ser modificada um pouco pra esse outro modo de jogo: um palpite deve levar em consideração não apenas o número de palavras que elimina, mas também o número de palavras que ele poderá eliminar no futuro. Por exemplo, se você chutar GATOS
e obtiver resposta ⬛🟩🟩🟩🟩, então seus palpites futuros só poderão ser RATOS
, PATOS
, FATOS
, MATOS
... É possível que você acabe perdendo o jogo! Portanto, acho que esse tipo de coisa teria de ser considerada pelo algoritmo, algum nível de lookahead.
[1]: se o seu objetivo for reduzir o número médio de turnos até a vitória