Teoría de los
Lenguajes de Programación
Práctica curso 2024-2025
Enunciado
Fernando López Ostenero
Ana García Serrano
Miguel Rodríguez Artacho
1
1. Mastermind, Wordle y los piques entre compañeros.
En la empresa “Tecleamos Los Programas” (TLP) se fomenta el compañerismo, por lo que en sus
oficinas es frecuente ver a los empleados y empleadas tomando cafés, charlando y jugando en la
sala de descanso. ¿Y trabajando? Bueno, sí, a veces también.
En las últimas semanas el juego más jugado entre la gente ha sido el Mastermind, ya que alguien se
trajo de casa una versión de tablero con clavijas de colores y hasta se organizaron competiciones
amistosas… hasta que uno de los jugadores, tras no poder adivinar la combinación de colores oculta
la emprendió a golpes con el tablero. Consecuencia: tablero roto y clavijas de colores esparcidas por
todas partes. En los corrillos del café se comenta que seguirán apareciendo clavijas durante los
próximos años.
Pero la pérdida del tablero ha provocado un sentimiento de falta en las oficinas. Los cafés ya no
saben tan dulces, las pausas son más cortas, la gente apenas se relaciona con sus compañeros y
compañeras… Los jefes estarían contentos, de no ser porque este sentimiento está afectando a la
productividad y los errores lógicos en los programas empiezan a multiplicarse exponencialmente.
Ante el riesgo de afectar negativamente a la empresa, los jefes han decidido institucionalizar un
torneo de Mastermind y nos han encargado hacer una versión informática del juego. Como
conocíamos el juego Wordle, que es muy similar, propusimos hacer una versión de este juego para
mejorar el ánimo de los y las trabajadoras, ya que en lugar de clavijas podríamos usar letras. Como
nuestro lenguaje favorito es Haskell, nuestra idea será programar una versión de Wordle en este
lenguaje. La idea ha corrido como la pólvora y toda la empresa está deseando poder jugar
nuevamente.
¿Seremos capaces de salvar a la empresa haciendo una versión funcional del Wordle?
2. Enunciado de la práctica
La práctica consiste en elaborar un programa en Haskell que permita a un jugador escribir una
palabra a adivinar y luego otro jugador pueda realizar diferentes intentos de adivinar dicha palabra.
En cada intento se colorearán las letras de la palabra intento de la siguiente forma:
Si la letra de la palabra intento está en la palabra oculta y en la misma posición, se coloreará
en verde.
Si la letra de la palabra intento está en la palabra oculta, pero en otra posición, se coloreará
en amarillo.
Si la letra de la palabra intento no está en la palabra oculta, se coloreará en rojo.
Además de mostrar la lista de todos los intentos previos (coloreados), se mostrará una lista de todas
las letras válidas que se colorearán de forma similar:
Si esa letra está en la palabra oculta y se ha adivinado su posición en algún intento, se
coloreará en verde.
Si esa letra está en la palabra oculta, pero aún no se ha adivinado su posición en ningún
intento, se coloreará en amarillo.
Si esa letra no está en la palabra oculta y se ha usado en un intento se coloreará en rojo.
Si la letra aún no se ha utilizado en ningún intento, se mostrará en gris.
Cuando se consiga adivinar la palabra, el programa indicará el número de intentos que ha costado
adivinar la palabra.
Por lo tanto, la práctica consiste en programar las siguientes funciones:
2
1. validLetters: que recibirá una palabra intento y comprobará si contiene únicamente letras
válidas.
2. newTry: que recibirá una palabra intento y una palabra secreta y devolverá la palabra intento
etiquetando cada una de sus letras con la siguiente información:
◦ C → esta letra está en la palabra secreta y en esta misma posición.
◦ I → esta letra está en la palabra secreta, pero no en esta posición.
◦ N → esta letra no está en la palabra secreta.
3. initialLS: que devolverá la lista de letras admitidas etiquetando todas ellas como no
utilizadas. Para ello se utilizará el identificador U, de manera similar a los identificadores C,
I y N utilizados por newTry.
4. updateLS: que recibirá la lista de letras admitidas etiquetadas según estén o no presentes en
la palabra secreta y una palabra intento etiquetada por newTry. Esta función devolverá la
lista de letras admitidas etiquetadas, actualizando la información según la etiquetación de la
palabra intento.
2.1 Ejemplo de funcionamiento
A continuación vamos a dar varios ejemplos del funcionamiento esperado de la práctica:
Función newTry
La palabra secreta es “cierre” y el jugador hace un intento con la palabra “camisa”. Entonces:
ghci> newTry "camisa" "cierre"
[('c',C),('a',N),('m',N),('i',I),('s',N),('a',N)]
ghci>
vemos cómo la función newTry recibe la palabra intento “camisa” y la palabra secreta “cierre” y
devuelve la palabra “camisa” etiquetando cada una de las letras:
('c',C): la letra ‘c’ está en la palabra secreta y, además, en esta misma posición.
('a',N): la letra ‘a’ no está en la palabra secreta.
('m',N): la letra ‘m’ no está en la palabra secreta.
('i',I): la letra ‘i’ está en la palabra secreta, pero no en esta posición
('s',N): la letra ‘s’ no está en la palabra secreta.
('a',N): la letra ‘a’ no está en la palabra secreta.
Ahora el jugador hace un intento con la palabra “cincel”. Entonces:
ghci> newTry "cincel" "cierre"
[('c',C),('i',C),('n',N),('c',N),('e',I),('l',N)]
ghci>
En este segundo intento podemos ver que la ‘c’ y la ‘i’ se han colocado correctamente, mientras que
la ‘e’ está en la palabra secreta, pero no en la quinta posición. Sin embargo, la segunda ‘c’ de la
palabra intento “cincel” se marca como que no está presente en la palabra secreta, ya que “cierre”
solo tiene una ‘c’.
Función updateLS
Vamos a abusar de la notación y, para simplificar el ejemplo, supondremos que bajo el identificador
lu tenemos la lista de todas las letras válidas siempre actualizada. Vamos, que se trata de una
variable en el sentido que tiene en un lenguaje imperativo. Supongamos que originalmente lu tiene
todas las letras ya etiquetadas como no utilizadas. Es decir, algo así:
3
[('a',U),('b',U),('c',U),('d',U),('e',U),('f',U),('g',U),('h',U),('i',U),
('j',U),('k',U),('l',U),('m',U),('n',U),('o',U),('p',U),('q',U),('r',U),
('s',U),('t',U),('u',U),('v',U),('w',U),('x',U),('y',U),('z',U)]
y que el jugador ha realizado un intento con la palabra “camisa”. Como hemos visto antes, la
etiquetación de “camisa” devuelta por la función newTry sería:
[('c',C),('a',N),('m',N),('i',I),('s',N),('a',N)]
así que la actualización de las letras utilizadas sería:
ghci> updateLS lu [('c',C),('a',N),('m',N),('i',I),('s',N),('a',N)]
[('a',N),('b',U),('c',C),('d',U),('e',U),('f',U),('g',U),('h',U),('i',I),
('j',U),('k',U),('l',U),('m',N),('n',U),('o',U),('p',U),('q',U),('r',U),
('s',N),('t',U),('u',U),('v',U),('w',U),('x',U),('y',U),('z',U)]
Si nos fijamos, se han actualizado las etiquetas de todas las letras presentes en el intento.
Recordemos que, aunque sea un abuso de notación, a partir de ahora en para nuestro ejemplo bajo el
identificador lu tendremos este último valor obtenido.
Si ahora queremos actualizar con la etiquetación del intento “cincel”, que, por lo que vimos antes,
era:
[('c',C),('i',C),('n',N),('c',N),('e',I),('l',N)]
obtendríamos lo siguiente:
ghci> updateLS lu [('c',C),('i',C),('n',N),('c',N),('e',I),('l',N)]
[('a',N),('b',U),('c',C),('d',U),('e',I),('f',U),('g',U),('h',U),('i',C),
('j',U),('k',U),('l',N),('m',N),('n',N),('o',U),('p',U),('q',U),('r',U),
('s',N),('t',U),('u',U),('v',U),('w',U),('x',U),('y',U),('z',U)]
y se vuelven a actualizar los valores de las etiquetas de las letras utilizadas.
2.2 Estructura de módulos de la práctica
La práctica está dividida en tres módulos, cada uno en un fichero independiente cuyo nombre
coincide (incluyendo mayúsculas y minúsculas) con el nombre del módulo y cuya extensión es .hs:
1 Módulo Wordle: dentro de este módulo se programarán las funciones indicadas. Incluye las
definiciones de todos los tipos de datos utilizados en la práctica.
2 Módulo Ansi: en este módulo se encuentran las funciones que nos permiten colorear el texto
mostrado por consola. Este módulo se proporciona ya totalmente programado y no deberá
modificarse.
3 Módulo Main: este módulo contiene la función principal y las encargadas de interactuar con
el usuario. Este módulo se proporciona ya totalmente programado y no deberá modificarse.
Dado que un estudio más profundo de los módulos en Haskell está fuera del ámbito de la
asignatura, sólo indicaremos que el módulo Main importa (además de los módulos Wordle y Ansi)
algunos módulos adicionales para poder trabajar más cómodamente con la entrada/salida.
2.2.1 Módulo Main (programa principal)
El módulo Main contiene el programa principal, ya programado por el equipo docente. Hay
funciones que utilizan mónadas y están escritas utilizando la “notación do”, que es una notación
para facilitar la escritura de concatenaciones de funciones monádicas. Sin entrar en detalle sobre el
funcionamiento de las mónadas, vamos a explicar qué hace cada función:
4
showTry: esta función llama a las funciones del módulo Ansi para colorear las letras de
un intento (o de las letras utilizadas) según las etiquetas que tengan.
showAllTries: esta función se encarga de mostrar (coloreados) todos los intentos que
se han realizado por parte del segundo jugador de adivinar la palabra oculta. También
muestra las letras ya utilizadas.
addTry: esta función es la encargada de añadir un nuevo intento etiquetado a la lista
de intentos de adivinar la palabra.
inputError: esta función muestra un error de entrada, como palabras de longitud
incorrecta o uso de letras no permitidas.
inputWord: esta función le pide al segundo jugador que introduzca una palabra.
Comprobará que tenga la longitud adecuada y que solo contenga letras válidas.
Cuando así sea, devolverá ĺa palabra introducida.
guessWord: esta función es el “bucle principal” del programa. Muestra al segundo
jugador un mensaje para que introduzca una palabra de la longitud adecuada, todos
los intentos realizados hasta ese momento y las letras que se han usado. Si el usuario
acertó la palabra secreta, se indica el número de intentos que tardó. Si no la acertó, se
llama a inputWord para que introduzca una nueva palabra y se vuelve a llamar a
guessWord.
secretWord: la función pide al primer jugador que introduzca la palabra secreta. Si la
palabra contiene solo letras permitidas, la devolverá. Si no, mostrará un error y
volverá a pedirla.
main: es la función principal. Configura la entrada para que se pueda editar (si no, no tendría
efecto la tecla de retroceso), borra la consola, llama a secretWord para obtener la palabra
secreta y, por último, llama a la función guessWord.
Como ya se ha indicado, este módulo se entrega ya programado por el equipo docente. Para su
correcto funcionamiento hay que programar las funciones necesarias en el módulo Wordle.
2.2.2 Módulo Ansi
Este módulo contiene una serie de funciones que nos permiten definir el color y el efecto con el que
las letras se dibujarán en pantalla:
Color: un tipo enumerado que contiene los colores posibles.
Intensity: un tipo enumerado que indica si el color será oscuro o brillante.
ansiSetInkColor: esta función recibe una intensidad y un color y prepara la consola para
que el siguiente texto se dibuje en ese color.
ansiSetBackColor: esta función recibe una intensidad y un color y prepara la consola para
que el siguiente texto se dibuje con ese color de fondo.
ansiReset: esta función devuelve la consola a su configuración por defecto.
ansiBold: esta función prepara la pantalla para que el siguiente texto se escriba en negrita.
ansiItalic: esta función prepara la pantalla para que el siguiente texto se escriba en
cursiva.
ansiUnderline: esta función prepara la pantalla para que el siguiente texto se escriba
subrayado.
ansiBlinking: esta función prepara la pantalla para que el siguiente texto que se escriba
aparezca parpadeando.
ansiEraseLine: esta función borra la línea en la que se encuentra el cursor.
ansiClearScreen: esta función borra toda la consola.
ansiCursorUp: esta función recibe un entero n y mueve el cursor n líneas hacia arriba.
Como ya se ha indicado, este módulo se entrega ya programado por el equipo docente. Estas
funciones se utilizan desde el módulo Main, no se necesitan para las funciones que deben ser
programadas.
5
2.2.3 Módulo Wordle
En este módulo se deben programar las cuatro funciones indicadas anteriormente. También se
incluyen en él las definiciones de los tipos de datos necesarios para almacenar los intentos y las
pistas, así como la lista de letras válidas:
data Clue = C | I | N | U
deriving (Eq,Ord,Read,Show)
type Try = [(Char,Clue)]
letters :: [Char]
letters = "abcdefghijklmnopqrstuvwxyz"
Los tipos de datos son las siguientes:
1. Clue: es un tipo que almacena las pistas:
◦ Correctly placed: la letra está en la palabra secreta y en la misma posición que en la
palabra intento.
◦ Incorrectly placed: la letra está en la palabra secreta, pero no en la misma posición que
en la palabra intento.
◦ Not in secret word: la letra no está en la palabra secreta.
◦ Unused: esta letra no ha sido utilizada en ningún intento (esta pista solo se utiliza en la
lista de letras utilizadas)
Este tipo se instancia en las clases Eq, Ord, Read y Show, lo que permite compararlo,
establecer un orden, leerlo de un string y mostrarlo.
2. Try: es una lista de tuplas carácter-pista que asocia a cada letra de la palabra intento (o la
lista de letras usadas) la pista correspondiente.
Las funciones de este módulo son:
1. letters :: [Char]: es la lista de letras válidas. Ya se da programada y, para evitar
problemas de codificación, no se consideran válidas la ñ y los caracteres acentuados.
2. validLetters :: String -> Bool: esta función recibe una cadena de caracteres y
devolverá un booleano indicando si todas las letras de la cadena están en la lista de letras
válidas.
3. newTry :: String -> String -> Try: esta función recibe una cadena de caracteres con
la palabra intento y una segunda cadena de caracteres con la palabra secreta. Devuelve un
dato de tipo Try en el que se marcan todas las letras de la palabra intento con las pistas
adecuadas según estén o no (y donde) en la palabra secreta.
4. initialLS :: Try: esta función deberá devolver un dato de tipo Try en el que se marquen
todas las letras posibles como aún no utilizadas en ningún intento.
5. updateLS :: Try -> Try -> Try : esta función recibe la lista actual de letras utilizadas
(ya etiquetadas según su uso en intentos anteriores), un dato de tipo Try conteniendo las
letras de la palabra intento etiquetadas por la función newTry y devuelve la lista de letras
utilizadas actualizando las etiquetas según el nuevo intento.
Además, podrá implementar cualesquiera otras funciones auxiliares que necesite para el correcto
funcionamiento de las cuatro que ha de implementar.
3. Cuestiones sobre la práctica
La respuesta a estas preguntas es optativa. Sin embargo, si no se responde a estas preguntas, la
calificación de la práctica solo podrá llegar a 5 puntos sobre 10.
6
1 Supongamos una implementación de la práctica (usando los mismos tipos de datos aquí
presentados) en un lenguaje no declarativo (como Java, Pascal, C…). Entonces, comente
qué ventajas y desventajas tendría frente a la implementación en Haskell en relación a lo
siguiente:
a) (1 punto). Eficiencia con respecto a la programación.
b) (1 punto). Eficiencia con respecto a la ejecución.
c) (0,5 puntos). ¿Qué lenguaje sale beneficiado gracias a la semántica de sus variables?
d) (0,5 puntos). ¿Cuál sería el principal punto a favor de la implementación en un lenguaje
no declarativo?
e) (0,5 puntos). ¿Y el principal punto a favor de la implementación en Haskell?
Razone y justifique sus respuestas.
2 (1,5 puntos). Indique qué clases de constructores de tipos (ver capítulo 5 del libro de la
asignatura) se han utilizado para definir los tipos de datos presentes en el módulo Wordle.
Justifique sus respuestas.
4. Documentación a entregar
Cada estudiante deberá entregar la siguiente documentación a su tutor/a de prácticas:
Código fuente en Haskell que resuelva el problema planteado. Para ello se deberá entregar
el ficheros [Link], con las funciones descritas en este enunciado, así como todas las
funciones auxiliares que sean necesarias.
Una memoria con las respuestas a las cuestiones sobre la práctica.