Wordle guesser with NodeJS
Tags: javascript, node, wordle, puppeteer
Published: 2022-03-16 18:50:37

### (English version bellow...)
Últimamente se ha puesto de moda jugar a [Wordle](https://es.wikipedia.org/wiki/Wordle_(videojuego)) y como no soy una excepción, yo también me puse jugar. Me di cuenta que al refrescar la página no se pierden las jugadas y además guarda las estadísticas de días anteriores. Entonces me entro curiosidad como estaba desarrollado:

Me puse a investigar y desde la consola de Google Chrome descubrí cosas interesantes en el 'local storage', vamos a ver:
![Local storage de Wordle](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/thj8oayj8d9fykagaz2c.png)
Hay varias keys interesantes: 
* Statistics: contiene un objeto 'guesses', el cual es un array que indica el número de jugadas que hemos necesitado para adivinar la palabra en días anteriores.
* Solution: parece la solución pasada por una [función hash](https://es.wikipedia.org/wiki/Funci%C3%B3n_hash)
* Board: aquí guarda las palabras que hemos usado para intentar adivinar la palabra de hoy. 
Esto es lo más interesante, ya que puedo editarlo, poner las palabras que yo quiera y Wordle lo valida, **incluso si la palabra no existe**:
![Panel de Wordle](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u22r351pzxp74fl5q4qc.png)

Aprovechando este error de validación, se me ocurrió usar la librería [Puppeteer](https://www.npmjs.com/package/puppeteer) de [NodeJS](https://es.wikipedia.org/wiki/Node.js) para automatizar el proceso y probar todas las letras del abecedario de una en una hasta encontrar la solución. Vamos a verlo:
```
async function tryCombinations(page){
    let lengthWord= getLengthWord();
    //let alphabet = 'abcdefghijklmnñopqrstuvwxyz'.split('');
    let alphabet = 'eaosrnidlctumpbgvyqhfzjñxkw'.split('');
    let word='';
    let correctWords=[];
    let isWordComplete=false;
    for(let i=0;i!=alphabet.length && !isWordComplete;i++){
        for(let j=0;j!=lengthWord;j++){
            word=word+alphabet[i];
        }
        await tryWord(page, word);
        correctWords=await getHits(page,correctWords);
        if(correctWords.length==lengthWord){
            isWordComplete=true;
        }
        word='';
    }
    return correctWords;
}
```
Esta función recorre cada una de las letras del alfabeto generando una 'palabra' de 5 letras (AAAAA, BBBBB, ...). Para optimizar la búsqueda, puse las letras del alfabeto ordenadas por su frecuencia de uso, según [este artículo](https://es.wikipedia.org/wiki/Frecuencia_de_aparici%C3%B3n_de_letras#:~:text=Ordenadas%20de%20mayor%20a%20menor,alrededor%20del%2045%25%20del%20texto.) de Wikipedia y de además deja buscar una vez tenemos las 5 letras de la palabra. Una vez generada la 'palabra', la guardo en el local storage de la página mediante la función tryWord:
```
async function tryWord(page, word){
    await page.evaluate((word) => {
        window.localStorage.setItem('board', `[\"${word}\"]`);
    }, word);
    await page.reload({ waitUntil: ["networkidle0", "domcontentloaded"] });
}
```
Una vez ha sido validada por Wordle, ejecuto la función getHits para analizar el HTML y obtener los aciertos (si los hay)
```
async function getHits(page, correctWords){
    await page.waitForSelector('.grid-cols-5');
    let bodyHTML = await page.evaluate(() => document.body.innerHTML);
    bodyHTML=bodyHTML.substring(bodyHTML.indexOf('<main'), bodyHTML.indexOf('</main'));
    let firstRow= bodyHTML.split('<div class="grid grid-cols-5 gap-[5px] w-full">')[1];
    let words = firstRow.split('<div class="w-full h-full max-w-[62px] max-h-[62px] inline-flex justify-center items-center text-lg uppercase font-bold select-none text-white');
    let count=0;
    let numStr=getNumStr(getLengthWord());
    for(let i=0;i!=words.length;i++){

        if(words[i].indexOf('bg-correct')!=-1){ //Contains bg-correct
            correctWords.push(getCorrectWord(words[i],count));
            printCorrectWords(correctWords,numStr);
            count++;
        }else if(words[i].indexOf('bg-absent')!=-1 ||
            words[i].indexOf('bg-present')!=-1){
                count++;
        }

    }
    return correctWords;
}
```
A medida que va encontrando letras correctas en la palabra, las imprime por consola y cuando la palabra esta completa, imprime el resultado final y el tiempo que ha tardado en la búsqueda. En el peor de los casos (la palabra contenga la letra 'W'), tarda alrededor de un minuto en encontrar la palabra.
![Resultado](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ek6lygvlqcsdo69zcafn.gif)

Por último, escribe la palabra encontrada como solución en Wordle y toma una captura de pantalla del resultado:
![Captura de pantalla del resultado](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3iwoevvsnqvqpvgfchfi.png)
 




### English version:
Lately everyone is playing [Wordle](https://en.wikipedia.org/wiki/Wordle) ([the spanish version](https://wordle.danielfrg.com/)) and since I'm not an exception, I started playing too. Then, I realized that by refreshing the page the plays are not lost and it also saves the statistics of previous days. Then I got curious how it was developed:
 
I began to investigate and from the Google Chrome console I discovered interesting things in the 'local storage', let's see:
![Wordle local storage](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/thj8oayj8d9fykagaz2c.png)
There are several interesting keys:
* Statistics: contains a 'guesses' object, which is an array that indicates the number of attempts we have needed to guess the word in previous days.
* Solution: looks like the solution passed through a [hash function](https://en.wikipedia.org/wiki/Hash_function)
* Board: here it saves the words that we have used to try to guess today's word.
This is the most interesting, since I can edit it, put the words I want and Wordle validates it, **even if the word does not exist**:
![Panel de Wordle](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u22r351pzxp74fl5q4qc.png)

Taking advantage of this validation error, I had the idea of using the [Puppeteer](https://www.npmjs.com/package/puppeteer) library from [NodeJS](https://en.wikipedia.org/wiki/Node.js) to automate the process and try all the letters of the alphabet one at a time until the solution is found. Let's see it:
```
async function tryCombinations(page){
    let lengthWord= getLengthWord();
    //let alphabet = 'abcdefghijklmnñopqrstuvwxyz'.split('');
    let alphabet = 'eaosrnidlctumpbgvyqhfzjñxkw'.split('');
    let word='';
    let correctWords=[];
    let isWordComplete=false;
    for(let i=0;i!=alphabet.length && !isWordComplete;i++){
        for(let j=0;j!=lengthWord;j++){
            word=word+alphabet[i];
        }
        await tryWord(page, word);
        correctWords=await getHits(page,correctWords);
        if(correctWords.length==lengthWord){
            isWordComplete=true;
        }
        word='';
    }
    return correctWords;
}
```
This function iterates through each of the letters of the alphabet generating a 5-letter 'word' (AAAAA, BBBBB, ...). To optimize the search, I put the letters of the Spanish alphabet ordered by their frequency of use, according to [this article](https://es.wikipedia.org/wiki/Frecuencia_de_aparici%C3%B3n_de_letras#:~:text=Ordenadas%20de%20mayor%20a%20menor,alrededor%20del%2045%25%20del%20texto.) from Wikipedia and also lets search once we have the 5 letters of the word. Once the 'word' is generated, I store it in the page's local storage using the tryWord function:
```
async function tryWord(page, word){
    await page.evaluate((word) => {
        window.localStorage.setItem('board', `[\"${word}\"]`);
    }, word);
    await page.reload({ waitUntil: ["networkidle0", "domcontentloaded"] });
}
```
Once it has been validated by Wordle, I run the getHits function to parse the HTML and get the hits (if any)
```
async function getHits(page, correctWords){
    await page.waitForSelector('.grid-cols-5');
    let bodyHTML = await page.evaluate(() => document.body.innerHTML);
    bodyHTML=bodyHTML.substring(bodyHTML.indexOf('<main'), bodyHTML.indexOf('</main'));
    let firstRow= bodyHTML.split('<div class="grid grid-cols-5 gap-[5px] w-full">')[1];
    let words = firstRow.split('<div class="w-full h-full max-w-[62px] max-h-[62px] inline-flex justify-center items-center text-lg uppercase font-bold select-none text-white');
    let count=0;
    let numStr=getNumStr(getLengthWord());
    for(let i=0;i!=words.length;i++){

        if(words[i].indexOf('bg-correct')!=-1){ //Contains bg-correct
            correctWords.push(getCorrectWord(words[i],count));
            printCorrectWords(correctWords,numStr);
            count++;
        }else if(words[i].indexOf('bg-absent')!=-1 ||
            words[i].indexOf('bg-present')!=-1){
                count++;
        }

    }
    return correctWords;
}
```
As it finds correct letters in the word, it prints them on the console and when the word is complete, it prints the final result and the time it took to search. In the worst case (the word contains the letter 'W'), it takes about a minute to find the word.
![Result](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ek6lygvlqcsdo69zcafn.gif)
Finally, write the found word as a solution in Wordle and take a screenshot of the result:
![Screenshot of the result](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3iwoevvsnqvqpvgfchfi.png)