10. Lingo

portada

El juego del Lingo consiste en adivinar palabras de 5 letras, en un máximo de 5 intentos. El juego se inicia con la letra inicial. Cada vez que el jugador dice una palabra, se le indica si alguna de las letras utilizadas forma parte de la palabra oculta. Si la letra esta en el sitio exacto, se colorea de verde, y si la letra esta en la palabra, pero no en el sitio exacto, se colorea de rojo.

La mecánica te sonará del popular Wordle, pero la inspiración de este juego es muy anterior y procede de una emisión de la televisión española de los años 90 presentado por Ramoncín el rey del pollo frito.

See also

Puedes encontrar más información sobre el programa aquí. Y en la web con el archivo de Radio Televisión Española puedes ver alguna de sus emisiones.

10.1. Impresión en colores

Vas a empezar creando tres funciones para imprimir texto en diferentes colores. Estas funciones las utilizaremos para colorear las letras del panel, por este motivo las funciones no deben incluir un salto de línea.

Las funciones se llaman:

  • print_red(text)

  • print_green(text)

Y sólo reciben como argumento de entrada una variable tipo STRING con el carácter o la secuencia de caracteres que deben colorear. Las tres funciones no devuelven ningún valor, se limitan a sacar por pantalla el contenido de la variable text en el color correspondiente.

Tip

Se puede cambiar el color de una cadena de caracteres añadiendo, al principio y al final del texto a colorear, unos caraceters ANSI especiales:

print('\033[92m' +"This text is printed in green"+'\033[92m')

This text is printed in green

Este stackoverlfow contiene una explicación bastante completa, aunque para el ejercicio que aquí te proponemos sólo necesitarás estos tres colores:

  • Red: ‘\033[91m’

  • Green: ‘\033[92m’

  • Black: ‘\033[0m’

Recuerda también que para eliminar el salto de línea que introduce el comando print hay que utilizar el argumento end:

print("Text without line break",end="")

Solución:

def print_red(text):
    red =  '\033[91m'
    black =  '\033[0m' 
    print(red+text+red, end = '')
    print(black+black, end = '')
    
def print_green(text):
    green =  '\033[92m'
    black =  '\033[0m' 
    print(green+text+green, end = '')
    print(black+black, end = '')

Puedes comprobar su funcionamiento como:

print_red("RED")
print()
print_green("GREEN")

RED

GREEN

10.2. Elegir palabra aleatoria

El siguiente paso es construir una función get_word(filename,length) que reciba dos argumentos de entrada:

  • filename variable STRING con el nombre del archivo words.txt

  • length valriable INT con la longitud de la palabra con la que queremos jugar

La función tiene que devolver una palabra aleatoria que contenga el número de letras indicado.

Tip

Para simplificar el juego, podemos eliminar las palabras que contengan algún tipo de acento.

Solución:

Esta función puede resolverse de muchísimas formas diferentes. Nosotros vamos a utilizar una aproximación muy sencilla aunque puede que no sea la más óptima. Empezaremos abriendo el fichero, después recorremos todas las palabras seleccionando sólo aquellas que cumplan el requisito de la longitud, por último, utilizamos la función sample de la librería random para elegir una de entre todas las que compartan la longitud indicada.

import random

def get_word(filename, length):
    with open(filename, 'r',encoding="utf8") as file:
        words = file.read().splitlines()
        
    candidates=[]
    accents='áéíóúü'
    for w in words:
        if len(w)==length:
            if not any([True if letter in accents else False for letter in w]):
                candidates.append(w)
    
    if len(candidates)!=0:
        word=random.sample(candidates,1)[0]
    else:
        word=""

    return word
get_word("words.txt", 15)
'atestiguamiento'

Note

La manera de chequear si una palabra contiene algún acento la resolvemos con la línea:

if not any([True if letter in accents else False for letter in w]):
    candidates.append(w)

Este código es una manera condensada utilizando comprehension lists de hacer esto:

any_accent=[]
for letter in w:   
    if letter in accents:
        any_accent.append(True)
    else:
        any_accent.append(False)

if any(any_accent) == True:
    pass
else:
    candidates.append(w)

Lo que hacemos en la versión extendida es ir iterando letra a letra e ir chequeando si esa letra es una vocal acentuada. El resultado de este chequeo lo almacenamos en la lista any_accent. La manera más sencilla de chequear si alguna de las variables booleanas almacenadas en la lista está puesta a True es utilizar la función any. Puedes aceder a su documentación aquí.

Recuerda también que la función random.sample siempre devuelve listas, incluso si sólo le pedimos un elemento aleatorio. Por lo que para poder trabajar con la palabra seleccionada, tenemos que extraerla de la lista indexando el único elemento que contiene, es decir, el cero:

word=random.sample(candidates,1)[0]

10.3. Inicio del juego

Para iniciar el juego, vamos a programar una función start_game(word) que reciba como argumento una palabra en la variable word y que saca por pantalla la primera letra de la palabra ocultando el resto de caracteres con un guión bajo:

start_game("automovile")
a _ _ _ _ _ _ _ _ _ 

Solución:

def start_game(word):
    for i in range(len(word)):
        if i != 0:
            print('_', end = ' ')
        else:
            print(word[0], end = ' ')
    print('\n')

10.4. Comprobar aciertos

Para comprobar el número de aciertos del usuario vamos a escribir una función check_letters(secret, candidate) que reciba dos argumentos:

  • secret contiene la palabra secreta en formato STRING

  • candidate contiene la palabra proporcionada por el jugador en formato STRING

La función debe sacar por pantalla la palabra proporcionada por el usuario coloreando cada letra según estas reglas:

  • si la letra en cuestión existe en la palabra secreta y se encuentra en la misma posición, la imprimirá en verde

  • si la letra en cuestión existe en la palabra secreta pero se encuentra en otra posición, la imprimirá en rojo

  • si la letra en cuestión no exite en la palabra secreta, la imprimirá en el color normal

Solución:

def check_letters(secret, candidate): 
    for i in range(len(candidate)):
        if candidate[i] in secret: 
            if candidate[i] == secret[i]: 
                print_green(candidate[i])
            else:
                print_red(candidate[i])
        else:
            print(candidate[i], end= '')
    print('\n')

Vamos a validar su funcionamiento probando un caso que contemple todas las reglas. Por ejemplo:

check_letters(secret="arroz",candidate="astro")

astro

En el caso anterior, vemos que la palabra candidata contiene la A, la R y la O que son letras de la palabra secret. También contiene la S y la T que no están en la palabra secret. Según las reglas que hemos definido, la S y la T las colorea de negro por no estar contenidas en secret. Por el contrario, la R y la O las colorea de rojo, ya que ninguna de esas letras coincide en posición con las de la palabra secret. Finalmente, la letra A la colorea de verde porque hemos acertado con su posición.

10.5. Fin de juego

La última función que necesitas para poder implementar el juego completo se llama is_a_winner(secret, candidate) y recibe los mismos dos argumentos que la función check_letters. La función chequea si ambas palabras coinciden y devuelve una variable booleana de valor True en caso de coincidencia o de valor False en caso contrario.

Solución:

def is_a_winner(secret, candidate):
    if secret == candidate:
        return True
    else:
        return False

Aunque su implementación es trivial, vamos a validarla:

is_a_winner(secret="arroz",candidate="astro")
False
is_a_winner(secret="arroz",candidate="arroz")
True

10.6. Implementar el juego

Ya puedes programar el juego utilizando todas las funciones anteriores. Los pasos podrían esquematizarse así:

  • Preguntar al jugador por el nivel de dificultad del juego

  • Obtener una palabra secreta

  • Mostrar la palabra con la letra inicial y ocultando el resto

  • Construir un proceso que en un máximo de 5 iteracciones le de la oportunidad al jugador de adivinar la palabra

length = 0

while length > 23 or length < 1:
    length = int(input('Select the word length (between 1 and 23): '))
    
secret = get_word('words.txt', length)
start_game(secret)

win = False
counter=0
while counter < 5 and win == False:
    candidate = input('Make your choice: ')
    while len(candidate)!=len(secret):
        candidate = input('Make your choice ({} letters): '.format(length))
    
    check_letters(secret, candidate)
    win = is_a_winner(secret, candidate)
    counter = counter+1
    
if win:
    print(f'"Congrats!! You only needed {counter} attempts!')
else:
    print(f"So sad!! The secret word was {secret}")

Select the word length (between 1 and 23): 6

l _ _ _ _ _

Make your choice: lambda
lambda

Make your choice: lather
lather

Make your choice: layout
layout

Make your choice: laptop
laptop

“Congrats!! You only needed 4 attempts!

10.7. Extensiones del Juego

Como ves, hemos implementado una versión muy sencilla del Lingo. Puedes complicarla añadiendo nuevas reglas o funcionalidades, por ejemplo:

  • Puedes utilizar diferentes colores para indicar si una letra está mal colocada, pero aparece una o dos veces

  • Puedes inventarte un sistema de puntuación en función del número de intentos, de la longitud de la palabra o del tiempo que le lleve al jugador resolverla

  • Puedes mejorar el interfaz gráfico construyendo un entorno más parecido al del juego Wordle. Aunque para ello puede que necesites primero completar algunos juegos más avanzados de este libro para conocer algunos recursos nuevos