Creando un Wordle para consola

Diego Herrera
8 min readFeb 5, 2022

Que Wordle se ha convertido en un fenómeno del entretenimiento diario, es algo que no creo que escape a nadie. Raro es quien, en las primeras horas de la mañana, no ve entre sus grupos de WhatsApp o su timeline de Twitter la ya icónica matriz de cuadrados negros, amarillos y, con suerte, verdes, muchos verdes. En concreto, si las neuronas del jugador han hecho bien su trabajo y la suerte no le ha sido esquiva, cinco cuadrados verdes dispuestos en horizontal que muestran la palabra semilla que debía adivinarse. Algo así, más o menos:

Captura de pantalla de una partida cualquiera de Wordle
Captura de pantalla de una partida cualquiera de Wordle

El propósito del juego es sencillo: adivinar una palabra de cinco letras en seis intentos o menos. El juego te ayuda visualmente en cada intento:

  • En negro ➔ Letra incorrecta
  • En amarillo ➔ Letra correcta, posición incorrecta
  • En verde ➔ Letra y posición correctas

Existen ya un buen puñado de clones con distintos añadidos, como la posibilidad de elegir el idioma en el que jugar, o también la de elegir una palabra para adivinar de distinta longitud. Pero no me había topado aún con una opción decente programada por y para amantes de la consola de comandos -que no quiere decir que no las haya-.

Una consola o terminal de comandos puede ser poco atractiva a primera vista, pero cuanto más la usas y más dominio tienes de los comandos, más te enamora y menos quieres hacer en tu sistema operativo a base de clic de ratón. Y esta es la verdadera razón por la que dediqué un par de ratos a programar palabros, un clon -más- de Wordle, esta vez para consola de comandos.

TL;DR

Soy de prosa fácil, sigue leyendo bajo tu responsabilidad.

Cómo lo pensé técnicamente

De haberme dejado llevar por modas, habría planteado una arquitectura muy loca, con las últimos patrones de diseño, las librerías más chic de la comunidad, y el último grito en buenas prácticas. ¿La realidad? Que he preferido pecar de práctico y hacer que, simplemente, funcione sin alardes.

Quería que fuera una aplicación standalone que no dependiera de integración con otros servicios. Exponer una API desde un servicio serverless con alta disponibilidad, elástico y con autenticación JWT, era una tentación difícil de esquivar, pero complicaba la solución. Tendría que codificar dos aplicaciones, cliente y servidor, cada una de ellas con su ciclo de vida, y asegurar la integración con otros servicios -base de datos, por ejemplo-. Matar moscas a cañonazos, vamos.

So, monolito time!

Los monolitos están algo denostados estos días, pero yo creo que aún hay espacio para ellos. Hay que evaluar en profundidad cada reto técnico al que nos enfrentamos, tirar de sensatez y elegir la mejor solución posible; lo de complicarse la vida gratuitamente está sobrevalorado.

Para palabros lo único que necesitaba era:

  • Un diccionario de palabras de cinco letras
  • Un sitio donde almacenar las jugadas y sus resultados
  • Una implementación simple que haga que todo funcione

Recordemos que no quería complicarme en exceso; buscaba una aplicación sencilla, rápida de codificar y fácil de mantener. Hice unos garabatos en una libreta, dediqué unos minutos a probar unas librerías, y la combinación ganadora que me salió fue esta:

  • Python como lenguaje de programación; versión 3.8 solo porque era la que tenía configurada globalmente en el sistema -bendito pyenv-
  • Poetry como gestor de paquetes; soy muy fan de tener toda la especificación del proyecto en un solo documento
  • SQLite como base de datos; recordemos que no quería depender de motores y servicios externos
  • Rich para pintar los resultados de las jugadas en la consola; puedes hacer cosas muy molonas en la consola con esta librería
  • Typer para exponer el juego como una CLI; porque funciona fetén y, además, admiro a por todas las joyas que está aportando a la comunidad Python

Esto es, grosso modo, palabros. Vamos a profundizar en un par de temas que considero clave: el trabajo con los datos y el método mágico sobre el que gira toda la aplicación.

Hablemos de datos

Si esperáis leer sobre el procesamiento en streaming de petabytes de datos en tiempo real en un modelo Data Mesh, este no es el artículo correcto. El modelo de datos que necesita palabros es tan ridículo que hasta pensé mantenerlo en un documento CSV. Aposté finalmente por SQLite por varios motivos:

  • Se puede delegar en SQL el consumo de los datos, que es muy cómodo
  • Se pueden hacer scripts de migración de esquema y datos para futuras versiones que requieran cambios en el modelo
  • Para este caso concreto, me parecía más elegante que CSV

El modelo se resume en la siguiente imagen:

Esquema de base de datos

Desde mi casa puedo escuchar a los expertos en datos llorar con fuerza al ver la imagen. Pero recordemos que la idea no era hacer la aplicación más óptima del mundo, sino una sencilla que, simplemente, funcione como se espera.

Sobre las tablas:

  • words es una tabla maestra que almacena el diccionario de palabras a adivinar. Las palabras son únicas, por lo que podemos usar este campo como clave primaria -aunque no mole mucho eso de tener un campo de texto como clave primaria-.
  • games mantiene una relación 1:1 con la tabla words, y se encarga de almacenar los juegos diarios. Es decir, palabra y fecha, poco más. La palabra se replica desde words para evitar joins en ciertas consultas -normalización para qué-.
  • attempts almacena los intentos del usuario de adivinar cada palabra. En este caso, la relación es 1:N con games. Se guarda la palabra introducida por el jugador y un flag que indica si acertó o no.

Planteado el modelo, ya pude desarrollar toda la capa de consumo de datos, que se resume a unas pocas consultas. De ellas os comparto la siguiente, que me parece la más interesante; se encarga de obtener una palabra aleatoria para un nuevo juego, procurando no repetir ninguna de las previamente jugadas:

Consulta SQL que selecciona una palabra aleatoria para un nuevo juego

Parafraseando el eslogan de una conocida cadena de comida rápida, el secreto está en el LEFT OUTER JOIN.

Representación gráfica de un LEFT OUTER JOIN

Al hacer una unión LEFT OUTER no solo prevalecen los registros resultantes de la intersección de ambas tablas, sino también aquellos de la tabla que está a la izquierda de la unión, aunque no tengan correspondencia al otro lado. En la consulta SQL de más arriba, tomamos por válidos todos aquellos registros de words que no tienen correspondencia en games, y de ellos elegimos uno al azar. Esa es la palabra que el jugador debe adivinar en el nuevo juego.

Más simple, imposible.

El método que hizo llorar a Steven Spielberg

La aplicación se reduce a 26 líneas de código; no es coña. Todo lo demás no son más que remates aquí y allá para hacer que todo funcione: que las jugadas se escriban en base de datos, que el resultado se pinte de forma elegante en la consola o que al arrancar un nuevo día se pueda jugar de nuevo.

De verdad, 26 líneas de código. En Python, eso sí; en Java quizá habrían sido unas 371, línea arriba, línea abajo -chiste manido, pero estaba a huevo-. Es lo primero que escribí de la aplicación, ya que es sobre lo que gira todo el juego; una vez resuelto el reto principal, me puse con lo demás.

Las 26 líneas, ya famosas, son las siguientes:

El código mágico que ha enganchado a miles de personas

Vamos a repasar paso a paso el método inspect_word:

  • Se hace un conteo de ocurrencias de cada letra de la palabra semilla; esto permite, por ejemplo, evitar falsos positivos al comparar las palabras
  • Se comparan, letra por letra, la palabra semilla -pongámosle A- y la palabra introducida por el jugador -digamos B-
  • Si una letra de B existe en A, aún quedan ocurrencias de esta letra en A y las posiciones de esta letra en A y B coinciden, entonces la letra es válida
  • Si se cumplen solo las dos primeras premisas del punto anterior, entonces la letra es válida, pero está en una posición incorrecta
  • Si se da cualquier otro caso no cubierto en las premisas previas, entonces la letra no es válida

Veamos estos puntos en acción con el siguiente ejemplo:

Ejemplo práctico de cómo se evalúa una jugada

El jugador debe adivinar la palabra casto y ha probado suerte con corte. Si evaluamos el resultado obtenemos lo siguiente:

  • La letra C es válida ➔ hay que pintarla en verde
  • La letra O es válida, pero su posición no ➔ hay que pintarla en amarillo
  • La letra R no es válida ➔ hay que pintarla en negro
  • La letra T es válida ➔ hay que pintarla en verde
  • La letra E no es válida ➔ hay que pintarla en negro

Y esto, chicuelas y chicuelos, es palabros -y también Wordle y los otros clones, claro-. Una pequeña pieza de código que, usada sabiamente, ha generado un auténtico movimiento en las redes sociales y tiene a miles de personas enganchadas intentado adivinar palabras. A veces, las ideas más sencillas son las que más potencial tienen.

Lecciones aprendidas

Más que lecciones son obviedades que a veces se olvidan. He caído en ellas en el tiempo que he dedicado a desarrollar palabros, por eso he querido dejar algunas aquí plasmadas.

  • Tendemos a sobredimensionar los problemas e intentamos darle solución en consecuencia. Y no está mal pensar en grande, pero creo que es mejor analizar el problema en profundidad y poner en práctica soluciones que cumplan correctamente sin complicar otros aspectos, como el coste del mantenimiento y la operación.
  • Es muy probable que los problemas que intentamos solucionar ya estén resueltos. Hay que hacer research antes de lanzarnos a escribir código, apostar por tecnologías que encajen con el problema a resolver y apoyarse en librerías que nos liberen de tareas básicas -logging, seguridad, etc-.
  • No subestimemos el valor de hacer aplicaciones sencillas como esta. Quizá lo son tanto que no reparamos en el poder que tienen. Nos obligan a poner en práctica nuestras habilidades en distintos ámbitos: tenemos que analizar las necesidades, estimar y priorizar esfuerzos, diseñar la arquitectura, implementar los casos de uso, velar por la calidad del código, asegurar la correcta integración y despliegue, monitorizar logs y métricas, etc. Estas aplicaciones sencillas son proyectos reales a pequeña escala que podemos acometer individualmente para medirnos a nosotros mismos y detectar áreas de mejora. ¿A que ya no parecen tan sencillas?
  • Perdamos el miedo a aprender luchando contra la inmediatez. Aprender y dominar requiere tiempo, esfuerzo, dedicación y disciplina; no hay fórmulas mágicas. Es una carrera de fondo, no queramos esprintar desde la salida.

One more thing!

Por si no ha quedado claro hasta ahora, palabros no es ninguna genialidad, ni es la mejor aplicación en nada; lo que importa es verla nacer y crecer, y aprender por el camino.

Es tu turno ahora: clona el repositorio, modifica el código, rompe cosas sin querer y arréglalas, añade nueva funcionalidad, rompe cosas queriendo y disfrútalo, aprende y diviértete.

¡Ah, por cierto! Hay un segundo artículo en camino donde hablaré de la parte más Ops de palabros, y así complemento este, que es más Dev.

EDITO: El segundo artículo está ya publicado; el enlace está más arriba, al comienzo de este.

--

--

Diego Herrera

Hago software de vez en cuando. Mientras tanto, juego a videojuegos, toco la guitarra y veo cine asiático.