Cómo Hacer el Juego del Ahorcado en Python

Cómo hacer el juego del ahorcado en Python

Como no todo tienen que ser cosas serias en programación, en este artículo vamos a hacer el clásico juego del ahorcado, en donde, como ya sabrás, hay que descubrir una palabra secreta letra a letra sin cometer demasiados fallos. Eso sí, vamos a hacerlo en Python y cambiando al ahorcado por un pez. ¿Cómo se hace? ¡Vamos a verlo!

Para hacer el juego del ahorcado en Python se desarrollan funciones que pidan letras al usuario, lleven la cuenta de letras acertadas y falladas y muestren el ahorcado. Después se escribe un bucle de juego con estas funciones que termina al acertar la palabra o al cometer varios errores.

Aunque en esencia este juego es muy sencillo, programarlo en Python es un gran ejercicio porque se tocan distintos aspectos de la programación. En este sentido, nos va a valer de recorrido por un montón de conceptos que te van a servir para afianzar e, incluso, aprender algunas cosas nuevas.¿Te quedas conmigo hasta el final? Merecerá la pena.

Pensando antes de programar

Una fase importante en la programación, con independencia del lenguaje que se utilice, es la que tiene lugar antes de empezar a programar. Es muy importante pensar en nuestro problema y estudiar la forma en que vamos a solucionarlo. Esta fase de análisis y diseño de software es lo que vamos a hacer en esta sección.

Vamos a empezar por pensar un poco cómo discurre el transcurso de este juego en la vida real para luego trasladarlo a la programación de forma que nuestro programa quede lo más «natural posible».

Para ello veremos cuál es la serie de eventos que suceden durante el juego. Después escribiremos un programa que concuerde con esa serie de eventos y, probablemente, cada uno de esos eventos quede reflejado como una función, lo que nos permitirá tener un programa con las funcionalidades bien repartidas y delimitadas, que será más fácil de escribir, de leer y de mantener.

La mecánica del juego

En el juego intervienen dos jugadores, el que conoce la palabra secreta y el que tiene que adivinarla. Vamos a llamarlos, respectivamente, Guardián y Jugador. Así, Guardián es el que conoce la palabra secreta y el que va dibujando las letras y el ahorcado y Jugador el que intenta averiguarla.

Los eventos que van sucediendo en el juego son los siguientes:

  1. Comienza el juego y Guardián piensa o elije una palabra secreta.
  2. Guardián dibuja la horca.
  3. Guardián dibuja tantas líneas como letras tiene la palabra secreta.
  4. Jugador dice una letra.
  5. Guardián comprueba la letra.
  6. Si la letra está en la palabra, Guardián escribe la letra sobre las líneas correspondientes.
  7. Si la letra no está en la palabra, Guardián la escribe como letra utilizada y dibuja una parte del ahorcado en la horca.
  8. Si se ha completado la palabra, el juego termina y Jugador gana.
  9. Si se ha completado el dibujo del ahorcado, el juego termina y Jugador pierde.
  10. Si no se han completado ni el dibujo ni la palabra se vuelve al paso 4.

Y ya está, esto en realidad es el algoritmo del discurrir del juego, en el que se perciben las reglas del mismo. Vamos ahora a convertir estos pasos en un programa.

El bucle principal del juego

La manera de trasladar los pasos que hemos visto al programa, y como dije antes, ha de ser lo más natural posible.

Existen dos actores que son Guardián y Jugador. Guardián será el ordenador, de forma que nuestro programa tiene que encargarse de todo lo que hace Guardián. Jugador será el usuario y por tanto el que juegue a adivinar la palabra. Como ves, lo único que tiene que hacer es decir letras. Haremos que el programa solicite al usuario una letra cuando sea el momento.

Hay que ser conscientes de algunas pequeñas limitaciones que tendremos, pues queremos que nuestro programa sea sencillo y sin demasiados alardes. La principal de ellas es a la hora de dibujar.

Cuando jugamos al juego en la vida real, el guardián va dibujando las nuevas partes del ahorcado sobre un dibujo ya hecho. Primero la cabeza, luego el cuerpo, luego un brazo, etc. Lo mismo sucede con las letras acertadas, que se van escribiendo sobre las líneas destinadas a ellas.

Nuestro programa va a mostrar la salida en modo texto, en la consola, con lo que no podremos dibujar o escribir sobre un dibujo o gráfico ya hecho, por tanto, tendremos que volver a dibujar las líneas, las letras y el dibujo del ahorcado una y otra vez. Pero como ves, tampoco es una gran limitación.

Para resolver esto vamos a alterar un poco los pasos de algoritmo que vimos arriba. Si te fijas, en el paso 10 se vuelve al paso 4 hasta que el usuario cometa el máximo de errores permitidos o hasta que adivine todas las letras.

Esta vuelta al paso 4, efectivamente, es un bucle, es el bucle principal del juego. Pues dentro de este bucle tendremos que dibujar, en cada vuelta, todas las líneas de las letras, las letras ya adivinadas sobre las líneas correspondientes y el dibujo de la horca con tantas partes del ahorcado como errores haya cometido.

De esta forma, los pasos 2 y 3 también estarán dentro del bucle y, además, en ellos también se realizan los pasos 6 y 7, pues precisamente son el de escribir las letras acertadas y el de dibujar las partes del ahorcado.

Así, nuestro algoritmo quedará de la siguiente manera:

  1. Comienza el juego y Guardián piensa o elije una palabra secreta.
  2. Guardián dibuja la horca con tantos fallos como se hayan cometido hasta el momento.
  3. Guardián dibuja tantas líneas como letras tiene la palabra secreta y escribe sobre ellas las letras acertadas hasta el momento.
  4. Jugador dice una letra.
  5. Guardián comprueba la letra.
  6. Si se ha completado la palabra, el juego termina y Jugador gana.
  7. Si se ha completado el dibujo del ahorcado, el juego termina y Jugador pierde.
  8. Si no se han completado ni el dibujo ni la palabra se vuelve al paso 2.

Ahora ya estamos en disposición de escribir nuestro código, que incluye ese bucle principal del juego.

Vamos a hacer ahora el ejercicio de escribir dicho código haciendo que cada paso complejo sea una función y que cada paso simple sean dos o tres instrucciones sencillas. Además, vamos a imaginar que las funciones que necesitamos ya existen, pues nos encargaremos luego de implementarlas:

diccionario = ['casa', 'pescado', 'calamar', 'monigote', 'codigopiton']

tablero, palabra, letras_erroneas = inicializar_juego(diccionario) # paso 1
while len(letras_erroneas) < len(simbolos): # pasos 7 y 8
    mostrar_escenario(len(letras_erroneas)) # paso 2
    mostrar_tablero(tablero, letras_erroneas) # paso 3
    letra = pedir_letra(tablero, letras_erroneas) # paso 4
    procesar_letra(letra, palabra, tablero, letras_erroneas) #paso 5
    if comprobar_palabra(tablero): # paso 6
        print('¡Enhorabuena, lo has logrado!')
        break
else:
    print(f'¡Lo siento! ¡Has perdido! La palabra a adivinar era {palabra}.')
    mostrar_escenario(len(letras_erroneas)) #paso 7

mostrar_tablero(tablero, letras_erroneas)

Si te fijas, he etiquetado cada paso con un comentario para que tengas claro qué es lo que se resuelve en cada línea del código.

Como te habrás dado cuenta, los pasos 7 y 8 están un poco desordenados. Se ha hecho así por comodidad. El paso 8 implica repetir el bucle y es la condición del propio bucle la que se encarga. Además, si la condición del bucle no se cumple, termina el juego. Así resolvemos dos pasos en una sola línea, o al menos un paso y parte del otro.

Vamos a utilizar tan solo seis variables:

  • Variable tablero. Será una lista ordenada que representa las líneas de cada letra por adivinar. Además, aquellas letras que están acertadas también estarán incluidas en dicha lista. Por ejemplo, si la palabra a divinar es casa el contenido del tablero podrá ser _ a _ a si se ha acertado la letra a. Nos servirá también para evitar contabilizar como fallo o acierto una letra repetida por el usuario que ya esté adivinada.
  • Variable diccionario. Es una lista que contiene el conjunto de palabras del cual se seleccionará al azar la palabra secreta.
  • Variable palabra. Contiene la palabra secreta a adivinar. Es fundamental para poder hacer comprobaciones de si una letra se acierta o se falla.
  • Variable letras_erroneas. Es una lista que contiene todas las letras que ha dicho el jugador pero que no están en la palabra adivinar. Necesitamos esta variable para dos cosas. Por un lado, para evitar sumar fallos si el usuario repite una letra. El segundo uso será la contabilización de errores pues esta lista tendrá tantas letras como fallos cometidos. Con solo mirar la longitud de la lista sabremos cuántos errores lleva el usuario.
  • Variable letra. Será la variable en la que guardemos la letra introducida por el usuario por teclado.
  • Variable simbolos. Es una lista que contiene los símbolos que se utilizarán para el dibujo del ahorcado. La longitud de esta lista indicará el número de errores que puede cometer el usuario. Por eso se utiliza en la condición del bucle. Si te fijas no está inicializa en ningún sitio en este código, pero nos ocuparemos de eso más tarde.

Como puedes observar, y una vez aclaradas las variables utilizadas, la codificación queda bastante directa y natural y cada uno de los pasos se resuelve con una función o con unas pocas instrucciones. Vamos a a ver cada una de estos pasos de manera individual para terminar de entender el bucle principal. Además, veremos la implementación de cada una de estas funciones.

Paso 1: inicializar el juego

El primer paso consiste en crear las variables necesarias con los valores adecuados para fijar el estado inicial del juego. Además se seleccionará del diccionario la palabra secreta a adivinar.

El paso 1, que se encuentra justo antes del bucle principal del programa, pues solo se ejecuta una vez, tiene el siguiente aspecto:

tablero, palabra, letras_erroneas = inicializar_juego(diccionario) # paso 1      

Esta función devuelve tres valores que se guardan en tres de las seis variables utilizadas. Además, recibe una cuarta variable que es una lista con muchas palabras para seleccionar una al azar.

Fíjate en que previamente hemos creado una variable diccionario con un conjunto de palabras cualesquiera. Puedes poner aquí las palabras que prefieras. También es posible cargar estas palabras de un fichero de texto, por ejemplo. Esto queda a tu elección.

Veamos la implementación de dicha función:

import random

# paso 1
def inicializar_juego(diccionario):
    palabra = random.choice(diccionario).lower()
    tablero = ['_'] * len(palabra)
    return tablero, palabra, []

Lo primero que hacemos es seleccionar al azar una palabra del diccionario. Lo puedes hacer utilizando la función choice del módulo random que toma un valor cualquier de una secuencia no vacía cualquiera. Además utilizo la función lower para convertir a minúsculas todas las letras mayúsculas que pueda tener la palabra seleccionada. Esto último facilitará las comparaciones de letras más adelante.

Después generamos el tablero, que en su estado inicial será una lista con tantas líneas _ como letras tenga la palabra secreta.

Finalmente se devuelven el tablero generado, la palabra secreta y una lista vacía que será la lista de letras erróneas y se guardan, respectivamente, en las variables tablero, palabra y letras_erroneas.

Paso 2: se dibuja el escenario del ahorcado

Este segundo paso queda resuelto también mediante una función, que se aprecia en la siguiente línea de código:

mostrar_escenario(len(letras_erroneas)) # paso 2

Esta función recibe el número de errores cometido hasta el momento por el jugador. Como las letras erróneas las tenemos almacenadas en una lista contenida en la variable letras_erroneas basta con pasar la longitud de esta lista a la función. ¡Ojo! Para que esto funcione tenemos que asegurarnos de no introducir letras repetidas en esta lista, pero de esto nos ocuparemos en el paso 5.

Antes de ver el código de la función mostrar_escenario, es necesario que definamos el escenario a dibujar, así como cada uno de los símbolos que irán mostrándose sucesivamente en dicho escenario cada vez que el jugador cometa un error.

Como dibujar una horca y un ahorcado me parecía un poco macabro, he decido reemplazar el escenario y dibujar un fondo marino en el que se vean el sedal y el anzuelo de un pescador. Cada vez que cometamos un error irá apareciendo una parte del cuerpo de un pez, si aparece el pez completo será pescado por el pescador y el jugador habrá perdido.

¿Qué mecanismo he utilizado para dibujar el escenario? Muy sencillo. Primero vamos a definir una plantilla del escenario. Esta plantilla contendrá, representado por caracteres, pues será una cadena de texto, los elementos fijos del escenario. Después, con una serie de números situados en una posición estratégica definiré los lugares en los que irá apareciendo cada uno de los símbolos que forman el pez que representan los errores del jugador.

De esta manera, nuestro escenario quedará como sigue:

~~~~~~~~~|~
         |
 0123456 J    
~~~~~~~~~~~  

No es una gran obra de arte ASCII pero puedes darte cuenta de que la J representa el anzuelo y el ~ representa el agua, por ejemplo. La ubicación del pez lo marcan los números 0 al 6. La numeración es importante porque el primer símbolo en salir, en cuanto el usuario cometa el primer error estará en la posición 0, el segundo en la 1 y así hasta llegar hasta el 6. Así en cuanto el usuario cometa 7 errores el juego habrá terminado.

El segundo paso es definir los símbolos, que evidentemente han de ser 7, pues hay 7 números (del 0 al 6) en nuestra plantilla de escenario. Los símbolos han de representarse como un string y en el orden que que van a ir saliendo en la plantilla. Como en este caso, la plantilla es muy sencilla y los números van de 0 a 6 en perfecto orden, el propio string que almacena los símbolos ya representa al pescado en sí, que será:

><(((º>

¿A que parece un pez bien simpático?

El último paso es ver cómo vamos a ir mostrando los errores. La idea es muy sencilla. Si no hay errores, se toma la plantilla y se sustituyen todos los caracteres del 0 al 6 por un espacio, así, el dibujo final no mostrará los números ni mostrará ningún símbolo.

Si se ha cometido un error, se sustituirá el número 0 por el símbolo que ocupe la posición 0 en el string de símbolos y el resto de números por un espacio. Si se han cometido dos errores, se sustituirá el 0 por el símbolo en la posición 0, el 1 por el símbolo en la posición 1 y el resto de números se sustituyen por espacios.

Por ejemplo, si el usuario hubiese cometido cuatro errores, el escenario mostrado sería el siguiente:

~~~~~~~~~|~
         |
 ><((    J    
~~~~~~~~~~~ 

Suponiendo que el escenario y los símbolos no van a cambiar, he optado por definirlos como variables globales, pero podrías hacer que fueran variables locales si quisieras. Lee más sobre variables locales y globales en Python.

Así, nuestro código queda como sigue, teniendo en cuenta que en Python podemos definir una cadena de texto con líneas múltiples delimitándola con comillas triples ''':

escenario = \
    '''   
~~~~~~~~~|~
         |
 0123456 J    
~~~~~~~~~~~   
'''

simbolos = '><(((º>'


# paso 2
def mostrar_escenario(errores):
    escena = escenario
    for i in range(0, len(simbolos)):
        simbolo = simbolos[i] if i < errores else ' '
        escena = escena.replace(str(i), simbolo)
    print(escena)

El código es bastante sencillo pero tiene un par de conceptos interesantes. Por un lado nos encontramos el operador ternario en la línea 16 para decidir si el símbolo que tenemos que sustituir en la plantilla de escenario es un espacio o uno de los símbolos del pez. Para entender esto te cuento en otro artículo cómo utilizar el operador ternario en Python y mucho más sobre condicionales.

Por otro lado tenemos la sustitución de un carácter por otro en la plantilla, cosa que podemos hacer con la función replace como puedes ver en la línea 17, y que nos permite reemplazar, no un carácter, sino cualquier subcadena o substring por otra. Te explico cómo trabajar con subcadenas en otro artículo y cómo hacer en Python substring.

Este sistema para dibujar el escenario lo he diseñado así porque te va a permitir hacer tu propio escenario de manera muy fácil. Solo tienes que preocuparte de hacer la plantilla, ubicar números donde quieras que aparezcan los símbolos y definir el string de símbolos. Esto también permite cambiar la dificultad del juego, pues al aumentar el número de símbolos disminuyes la dificultad, pues se pueden cometer más errores.

¿Te atreves a hacer tu propio escenario?

Paso 3: se dibuja el tablero

Después de mostrar el escenario tenemos que encargarnos de mostrar el tablero. En este paso vamos aprovechar para mostrar dos cosas por pantalla. La primera de ellas es el propio tablero, es decir, las líneas que representan cada letra o, de ser el caso, las letras que se han adivinado en su posición correcta. La otra es la lista de letras erróneas para informar al usuario de qué letras han sido utilizadas ya y favorecer que no las repita.

La línea que resuelve el paso 3 en el bucle principal del programa es la siguiente:

mostrar_tablero(tablero, letras_erroneas) # paso 3

Como ves, recibe dos variables que son precisamente el tablero, con las líneas y las letras adivinadas ya ubicadas y la lista de letras erróneas. En el paso 5 veremos cómo se van rellenando tanto el tablero como dicha lista.

Podemos resolver esta función de la siguiente manera:

# paso 3
def mostrar_tablero(tablero, letras_erroneas):
    for casilla in tablero:
        print(casilla, end=' ')
    print()
    print()
    if len(letras_erroneas) > 0:
        print('Letras erróneas:', *letras_erroneas)
        print()

Fíjate que, para que sea más cómodo de leer y el tablero tenga más entidad, además de hacerlo más parecido al juego real, vamos a dejar un espacio entre cada una de las líneas y letras, lo que en el código llamo casillas. Para eso utilizo el parámetro end de la función print que sustituye el salto de línea del final por el string que quieras, en este caso, un espacio.

Después dejo una línea en blanco para favorecer la legibilidad y muestro todas las letras erróneas tras el letrero «Letras erróneas:». En esta ocasión utilizo el operador asterisco * que en este caso sirve para desempaquetar todas la letras de la lista y pasárserlas a la función print como parámetros individualmente. Te explico más sobre el desempaquetado en este artículo sobre el uso del asterisco y del doble asterisco en Python.

Tras dibujar el escenario y el tablero toca pedirle una letra al usuario.

Paso 4: se pide una letra al jugador

A la hora de pedir un valor al usuario, en este caso una letra, siempre tenemos que asegurarnos de que el valor introducido es válido y tiene sentido. En nuestro juego esta validación consiste en comprobar que el carácter introducido por el usuario es una sola letra y no, por ejemplo, un número o una cadena de texto de dos letras o más.

Es por este motivo que necesitamos realizar un bucle para obtener la letra del usuario, para solicitársela una y otra vez hasta que introduce una válida.

Además, en nuestro caso debemos hacer otra comprobación, ya que necesitamos que el usuario indique una letra que no haya indicado con anterioridad. Así, si la letra es repetida también tenemos que volver a solicitar una letra al usuario.

Puedes profundizar sobre este tema en otro artículo donde te cuento cómo pedir un valor al usuario hasta que sea válido en Python.

Así, el código del bucle principal que resuelve la solicitud de una letra al jugador es el siguiente:

letra = pedir_letra(tablero, letras_erroneas) # paso 4

Esta función necesita el tablero y la lista de letras erróneas para hacer las comprobaciones pertinentes y nos devuelve la letra introducida por el usuario, ya con la seguridad de que es una letra y de que además no está repetida. El código de dicha función será el siguiente:

# paso 4
def pedir_letra(tablero, letras_erroneas):
    valida = False
    while not valida:
        letra = input('Introduce una letra (a-z): ').lower()
        valida = 'a' <= letra <= 'z' and len(letra) == 1 # es una letra
        if not valida:
            print('Error, la letra tiene que estar entre a y z.')
        else:
            valida = letra not in tablero + letras_erroneas
            if not valida:
                print('Letra repetida, prueba con otra.')

    return letra

Cómo puedes ver, se solicita una letra al usuario mediante la función input. Después se convierte a minúsculas (recuerda que cuando seleccionamos la palabra secreta del diccionario al inicializar el juego convertimos todas sus letras a minúsculas).

Una vez tenemos el valor que ha introducido el usuario comprobamos si solo es un carácter y si además se encuentra entre la a y la z, cosa que nos asegura que es una letra. Si no se cumple alguna de esas condiciones volvemos a pedir la letra al usuario.

Si se cumplen esas condiciones es cuando pasamos a comprobar que la letra no está repetida. Es tan sencillo como mirar que dicha letra no está ya en el tablero ni en la lista de letras erróneas.

Para hacer esta comprobación puedes utilizar el operador not in que sirve para comprobar si un elemento no se encuentra en una lista. Consulta este y otros operadores útiles en este artículo sobre condicionales en Python.

Fíjate en que utilizo el operador + que en este caso sirve para crear una lista con todos los elementos de tablero y de letras_erroneas de forma que así solo hago una comprobación y no dos.

Si la validación es correcta, devolvemos la letra introducida.

Paso 5: se comprueba la letra introducida

Cuando el jugador ya ha indicado su letra, como guardián nos toca hacer comprobaciones y actualizar el tablero si la letra es correcta o actualizar la lista de letras erróneas si la letra es incorrecta.

En el bucle principal del programa este paso se resuelve en la siguiente línea:

procesar_letra(letra, palabra, tablero, letras_erroneas) #paso 5

Llamo a la función procesar_letra y no comprobar_letra porque en realidad se procesa la letra tras la comprobación para actualizar el tablero o la lista de letras erróneas. La función necesita la letra introducida por el jugador, la palabra secreta, el tablero y la lista de letras erróneas.

Tras la ejecución de esta función el tablero o la lista habrán cambiado. Veamos el código:

# paso 5
def procesar_letra(letra, palabra, tablero, letras_erroneas):
    if letra in palabra:
        print('¡Genial! Has acertado una letra.')
        actualizar_tablero(letra, palabra, tablero)
    else:
        print('¡Oh! Has fallado.')
        letras_erroneas.append(letra)


# paso 5 (auxiliar)
def actualizar_tablero(letra, palabra, tablero):
    for indice, letra_palabra in enumerate(palabra):
        if letra == letra_palabra:
            tablero[indice] = letra

El primer paso aquí es comprobar si la letra introducida está dentro de la palabra clave. Recuerda que en el paso 4 nos hemos asegurado de que la letra no es repetida, así que sabemos que no está en el tablero.

Si la letra está en la palabra clave mostramos un mensaje de éxito y actualizamos el tablero. Aquí, por claridad del código, he creado una función auxiliar que me permite actualizar el tablero. Dicha función recibe la letra, la palabra y el tablero y lo que hace es buscar las posiciones que ocupa dicha letra en la palabra y actualizar dichas posiciones en el tablero, sustituyendo la línea correspondiente por la letra introducida por el jugador.

Para buscar dichas posiciones utilizo la función enumerate, que devuelve un elemento y su posición en cada vuelta del bucle, y sobre la que te hablo en un artículo donde te explico cómo recorrer dos listas a la vez en Python, en donde una de las maneras es utilizar dicha función.

Si la letra introducida por el usuario no está en el tablero, simplemente la añadimos a la lista de letras erróneas.

Paso 6: se comprueba si la palabra ha sido adivinada

Tras una posible actualización del tablero, cosa que sucede si el usuario acierta una letra, debemos comprobar si se ha completado la palabra en el tablero. En caso de suceder eso, se muestra un mensaje de enhorabuena y se detiene el bucle con un break (recuerda que la condición de nuestro bucle lo detiene solo en caso de que se hayan cometido el número máximo de errores), pues el juego termina.

El código encargado de realizar esto en el bucle principal es el siguiente:

if comprobar_palabra(tablero): # paso 6
    print('¡Enhorabuena, lo has logrado!')
    break

La comprobación de que la palabra ha sido adivinada es sumamente fácil, pues basta con comprobar que en el tablero no queda ninguna línea, lo que quiere decir que todos los huecos ya están cubiertos con letras. El código es como sigue:

def comprobar_palabra(tablero):
    return '_' not in tablero

Nuevamente puedes utilizar el operador not in como ya hemos visto más arriba. La comprobación es tan fácil que en realidad no haría falta dicha función y podríamos dejar el código del bucle principal como sigue, pero por estructura del código yo prefiero utilizar la función, sobre todo por si en el futuro se contemplan mejoras en el juego y la comprobación no resulta tan sencilla:

if '_' not in tablero: # paso 6
    print('¡Enhorabuena, lo has logrado!')
    break

Pasos 7 y 8: se comprueba si se ha cometido el número máximo de errores

En el caso de que el usuario haya cometido demasiados errores, tantos como para completar el dibujo del ahorcado o, en nuestro caso, del pez pescado, el juego también termina.

Como te dije en la explicación paso 6, la condición utilizada en el bucle controla si el usuario ya ha cometido el máximo de errores, en cuyo caso el bucle termina. Como te cuento en este artículo sobre condicionales en Python, los bucles pueden incluir un bloque else que se ejecuta cuando el bucle termina cuando la condición se hace falsa y no se ha ejecutado ningún break.

Es el caso de nuestro programa cuando se ha llegado al máximo de errores, con lo que el código que resuelve el paso 7 se encuentra dividido entre la propia condición del bucle y su rama else:

while len(letras_erroneas) < len(simbolos): # pasos 7 y 8
    # aquí resto del código del bucle principal
else:
    print(f'¡Lo siento! ¡Has perdido! La palabra a adivinar era {palabra}.')
    mostrar_escenario(len(letras_erroneas)) #paso 7

Así, cuando ya no quedan más errores por cometer, se ejecuta la rama else donde se muestra el mensaje de juego perdido y el escenario con el dibujo completado del pez pescado (o del ahorcado).

Pero también es posible que el jugador todavía no haya cometido el máximo de errores con lo que la condición es True y el cuerpo del bucle vuelve a ejecutarse, volviendo de nuevo al paso 2. Esta es la manera en la que se resuelve el último de nuestros pasos, el paso 8.

Y con esto ya tenemos el juego resuelto. Antes de probarlo vamos a ver un par de detalles más.

Paso extra: se muestra el tablero al finalizar el juego

Cuando termina el juego, ya sea porque el jugador ha averiguado la palabra secreta o bien porque ha cometido demasiados errores, es interesante volver a mostrar el estado del tablero y las letras erróneas, para que el usuario pueda observar el estado final de su tablero. Esto se hace volviendo a llamar nuevamente a la función mostrar_tablero tras la finalización del bucle.

Una pequeña mejora

Aunque el juego está completo, vamos a hacerle unas pequeñas mejoras. Por un lado, daremos una bienvenida al juego con un letrero llamativo.

Por otro lado, también vamos a crear un mensaje de despedida, con otro bonito letrero.

Además, me gustaría que al terminar del juego, el programa le preguntara al usuario si quiere volver a jugar. En caso de que el usuario conteste que sí, basta con volver a ejecutar de nuevo todo el bucle principal del juego, empezando con la inicialización, que elige una nueva palabra secreta, genera el tablero y crea una lista de letras erróneas vacía.

Para los mensajes de bienvenida y despedida creo dos nuevas funciones. Además, todo el código del bucle principal del juego lo encapsulo dentro de una función jugar_al_ahorcado que recibe el diccionario con las palabras (para que se pueda hacer la selección de la palabra secreta).

Finalmente, creo un bucle que se repite hasta que el usuario decide no jugar más. En el cuerpo de dicho bucle coloco una llamada a la función jugar_al_ahorcado, otra llamada a una función que le pregunta al usuario si quiere jugar otra vez y un if que ejecuta un break en caso de que el usuario responda que no.

Así, estas pequeñas pero interesantes modificaciones de código quedan como sigue:

def bienvenida():
    print('*' * 68)
    print('* Te doy la bienvenida al juego del ahorcado de codigopiton.com :) *')
    print('*' * 68)


def jugar_al_ahorcado(diccionario):

    tablero, palabra, letras_erroneas = inicializar_juego(diccionario)  # paso 1
    while len(letras_erroneas) < len(simbolos):  # pasos 7 y 8
        mostrar_escenario(len(letras_erroneas))  # paso 2
        mostrar_tablero(tablero, letras_erroneas)  # paso 3
        letra = pedir_letra(tablero, letras_erroneas)  # paso 4
        procesar_letra(letra, palabra, tablero, letras_erroneas)  # paso 5
        if comprobar_palabra(tablero):  # paso 6
            print('¡Enhorabuena, lo has logrado!')
            break
    else:
        print(f'¡Lo siento! ¡Has perdido! La palabra a adivinar era {palabra}.')
        mostrar_escenario(len(letras_erroneas))  # paso 7

    mostrar_tablero(tablero, letras_erroneas)


def jugar_otra_vez():
    return input('Deseas jugar otra vez (introduce s para sí o cualquier otra cosa para no): ')


def despedida():
    print('*' * 68)
    print('* Gracias por jugar al ahorcado de codigopiton.com. ¡Hasta pronto! *')
    print('*' * 68)


if __name__ == '__main__':

    diccionario = ['casa', 'pescado', 'calamar', 'monigote', 'codigopiton']

    bienvenida()
    while True:
        jugar_al_ahorcado(diccionario)
        if jugar_otra_vez() != 's': break
    despedida()

Como ves, el código queda modular y sencillo.

Otra pequeña mejora sería colocar el código que ejecuta la bienvenida, el bucle del juego y la despedida dentro de un if para que se ejecute solo en caso de que nuestro módulo sea el programa principal.

Y con esto ya está todo.

Código completo y prueba de ejecución

Bueno, hemos llegado al momento más esperado. Te pongo aquí todo el código del programa realizado para que lo tengas todo junto y puedas experimentar con él. Después, a continuación, te muestro una ejecución del programa donde juego un par de partidas con él.

import random

escenario = \
    '''   
~~~~~~~~~|~
         |
 0123456 J    
~~~~~~~~~~~   
'''

simbolos = '><(((º>'


def bienvenida():
    print('*' * 68)
    print('* Te doy la bienvenida al juego del ahorcado de codigopiton.com :) *')
    print('*' * 68)


# paso 1
def inicializar_juego(diccionario):
    palabra = random.choice(diccionario).lower()
    tablero = ['_'] * len(palabra)
    return tablero, palabra, []


# paso 2
def mostrar_escenario(errores):
    escena = escenario
    for i in range(0, len(simbolos)):
        simbolo = simbolos[i] if i < errores else ' '
        escena = escena.replace(str(i), simbolo)
    print(escena)


# paso 3
def mostrar_tablero(tablero, letras_erroneas):
    for casilla in tablero:
        print(casilla, end=' ')
    print()
    print()
    if len(letras_erroneas) > 0:
        print('Letras erróneas:', *letras_erroneas)
        print()


# paso 4
def pedir_letra(tablero, letras_erroneas):
    valida = False
    while not valida:
        letra = input('Introduce una letra (a-z): ').lower()
        valida = 'a' <= letra <= 'z' and len(letra) == 1 # es una letra
        if not valida:
            print('Error, la letra tiene que estar entre a y z.')
        else:
            valida = letra not in tablero + letras_erroneas
            if not valida:
                print('Letra repetida, prueba con otra.')

    return letra


# paso 5
def procesar_letra(letra, palabra, tablero, letras_erroneas):
    if letra in palabra:
        print('¡Genial! Has acertado una letra.')
        actualizar_tablero(letra, palabra, tablero)
    else:
        print('¡Oh! Has fallado.')
        letras_erroneas.append(letra)


# paso 5 (auxiliar)
def actualizar_tablero(letra, palabra, tablero):
    for indice, letra_palabra in enumerate(palabra):
        if letra == letra_palabra:
            tablero[indice] = letra


# paso 6
def comprobar_palabra(tablero):
    return '_' not in tablero


# bucle principal de juego
def jugar_al_ahorcado(diccionario):

    tablero, palabra, letras_erroneas = inicializar_juego(diccionario)  # paso 1
    while len(letras_erroneas) < len(simbolos):  # pasos 7 y 8
        mostrar_escenario(len(letras_erroneas))  # paso 2
        mostrar_tablero(tablero, letras_erroneas)  # paso 3
        letra = pedir_letra(tablero, letras_erroneas)  # paso 4
        procesar_letra(letra, palabra, tablero, letras_erroneas)  # paso 5
        if comprobar_palabra(tablero):  # paso 6
            print('¡Enhorabuena, lo has logrado!')
            break
    else:
        print(f'¡Lo siento! ¡Has perdido! La palabra a adivinar era {palabra}.')
        mostrar_escenario(len(letras_erroneas))  # paso 7

    mostrar_tablero(tablero, letras_erroneas)


def jugar_otra_vez():
    return input('Deseas jugar otra vez (introduce s para sí o cualquier otra cosa para no): ')


def despedida():
    print('*' * 68)
    print('* Gracias por jugar al ahorcado de codigopiton.com. ¡Hasta pronto! *')
    print('*' * 68)


if __name__ == '__main__':

    diccionario = ['casa', 'pescado', 'calamar', 'monigote', 'codigopiton']

    bienvenida()
    while True:
        jugar_al_ahorcado(diccionario)
        if jugar_otra_vez() != 's': break
    despedida()

Veamos ahora una ejecución del juego. Como verás, he jugado dos partidas. La primera la gano. Cuando termina la primera, el programa me pregunta si quiero volver a jugar. Le digo que sí y juego otra partida que pierdo. Al perder me enfado y ya no quiero jugar más. Le digo que no sigo jugando y finaliza la ejecución.

Fíjate en los mensajes de bienvenida y de despedida, en qué pasa si introduzco una letra repetida o un valor no válido como letra. También en cómo se va actualizando el tablero cuando acierto una letra y en cómo va creciendo el dibujo cuando la fallo así como la lista de letras erróneas.

********************************************************************
* Te doy la bienvenida al juego del ahorcado de codigopiton.com :) *
********************************************************************
   
~~~~~~~~~|~
         |
         J    
~~~~~~~~~~~   

_ _ _ _ _ _ _ 

Introduce una letra (a-z): 3
Error, la letra tiene que estar entre a y z.
Introduce una letra (a-z): a
¡Genial! Has acertado una letra.
   
~~~~~~~~~|~
         |
         J    
~~~~~~~~~~~   

_ _ _ _ a _ _ 

Introduce una letra (a-z): a
Letra repetida, prueba con otra.
Introduce una letra (a-z): queso
Error, la letra tiene que estar entre a y z.
Introduce una letra (a-z): s
¡Genial! Has acertado una letra.
   
~~~~~~~~~|~
         |
         J    
~~~~~~~~~~~   

_ _ s _ a _ _ 

Introduce una letra (a-z): q
¡Oh! Has fallado.
   
~~~~~~~~~|~
         |
 >       J    
~~~~~~~~~~~   

_ _ s _ a _ _ 

Letras erróneas: q

Introduce una letra (a-z): j
¡Oh! Has fallado.
   
~~~~~~~~~|~
         |
 ><      J    
~~~~~~~~~~~   

_ _ s _ a _ _ 

Letras erróneas: q j

Introduce una letra (a-z): o
¡Genial! Has acertado una letra.
   
~~~~~~~~~|~
         |
 ><      J    
~~~~~~~~~~~   

_ _ s _ a _ o 

Letras erróneas: q j

Introduce una letra (a-z): d
¡Genial! Has acertado una letra.
   
~~~~~~~~~|~
         |
 ><      J    
~~~~~~~~~~~   

_ _ s _ a d o 

Letras erróneas: q j

Introduce una letra (a-z): e
¡Genial! Has acertado una letra.
   
~~~~~~~~~|~
         |
 ><      J    
~~~~~~~~~~~   

_ e s _ a d o 

Letras erróneas: q j

Introduce una letra (a-z): r
¡Oh! Has fallado.
   
~~~~~~~~~|~
         |
 ><(     J    
~~~~~~~~~~~   

_ e s _ a d o 

Letras erróneas: q j r

Introduce una letra (a-z): t
¡Oh! Has fallado.
   
~~~~~~~~~|~
         |
 ><((    J    
~~~~~~~~~~~   

_ e s _ a d o 

Letras erróneas: q j r t

Introduce una letra (a-z): p
¡Genial! Has acertado una letra.
   
~~~~~~~~~|~
         |
 ><((    J    
~~~~~~~~~~~   

p e s _ a d o 

Letras erróneas: q j r t

Introduce una letra (a-z): c
¡Genial! Has acertado una letra.
¡Enhorabuena, lo has logrado!
p e s c a d o 

Letras erróneas: q j r t

Deseas jugar otra vez (introduce s para sí o cualquier otra cosa para no): s
   
~~~~~~~~~|~
         |
         J    
~~~~~~~~~~~   

_ _ _ _ _ _ _ _ 

Introduce una letra (a-z): a
¡Oh! Has fallado.
   
~~~~~~~~~|~
         |
 >       J    
~~~~~~~~~~~   

_ _ _ _ _ _ _ _ 

Letras erróneas: a

Introduce una letra (a-z): b
¡Oh! Has fallado.
   
~~~~~~~~~|~
         |
 ><      J    
~~~~~~~~~~~   

_ _ _ _ _ _ _ _ 

Letras erróneas: a b

Introduce una letra (a-z): o
¡Genial! Has acertado una letra.
   
~~~~~~~~~|~
         |
 ><      J    
~~~~~~~~~~~   

_ o _ _ _ o _ _ 

Letras erróneas: a b

Introduce una letra (a-z): j
¡Oh! Has fallado.
   
~~~~~~~~~|~
         |
 ><(     J    
~~~~~~~~~~~   

_ o _ _ _ o _ _ 

Letras erróneas: a b j

Introduce una letra (a-z): i
¡Genial! Has acertado una letra.
   
~~~~~~~~~|~
         |
 ><(     J    
~~~~~~~~~~~   

_ o _ i _ o _ _ 

Letras erróneas: a b j

Introduce una letra (a-z): u
¡Oh! Has fallado.
   
~~~~~~~~~|~
         |
 ><((    J    
~~~~~~~~~~~   

_ o _ i _ o _ _ 

Letras erróneas: a b j u

Introduce una letra (a-z): r
¡Oh! Has fallado.
   
~~~~~~~~~|~
         |
 ><(((   J    
~~~~~~~~~~~   

_ o _ i _ o _ _ 

Letras erróneas: a b j u r

Introduce una letra (a-z): m
¡Genial! Has acertado una letra.
   
~~~~~~~~~|~
         |
 ><(((   J    
~~~~~~~~~~~   

m o _ i _ o _ _ 

Letras erróneas: a b j u r

Introduce una letra (a-z): v
¡Oh! Has fallado.
   
~~~~~~~~~|~
         |
 ><(((º  J    
~~~~~~~~~~~   

m o _ i _ o _ _ 

Letras erróneas: a b j u r v

Introduce una letra (a-z): c
¡Oh! Has fallado.
¡Lo siento! ¡Has perdido! La palabra a adivinar era monigote.
   
~~~~~~~~~|~
         |
 ><(((º> J    
~~~~~~~~~~~   

m o _ i _ o _ _ 

Letras erróneas: a b j u r v c

Deseas jugar otra vez (introduce s para sí o cualquier otra cosa para no): n
********************************************************************
* Gracias por jugar al ahorcado de codigopiton.com. ¡Hasta pronto! *
********************************************************************

Conclusión

Bueno, escribir este artículo ha sido divertido para mí. Espero que leerlo también lo haya sido para ti.

Como te decía en la introducción, este programa parece sencillo pero trabaja con muchos conceptos y temas interesantes de la programación: análisis previo y diseño, entrada por teclado, salida por pantalla, cadenas de texto, condicionales, bucles, listas, operadores in y not in, funciones enumerate y choice, replicación de listas y cadenas de texto con el operador *, suma de listas con el operador +, desempaquetado de listas con el operador *, descomposición del código en funciones, etc.

Como ves, hemos practicado cosas que probablemente ya sabías y hemos aprendido otras cuantas nuevas. Espero que este artículo te haya resultado de mucha utilidad. Siéntete libre de estudiar el código que aquí te brindo y de modificar aquello que consideres y, por supuesto, de mejorarlo. Y… ¡gracias por leerme!

La Hoja de Referencia de Python – ¡Gratis!

La Hoja de Referencia de Python - Código Pitón
Consigue trucos, consejos y actualizaciones y, por supuesto, la Hoja de Referencia de Python gratis.



Antes de suscribirte consulta aquí la Información Básica sobre Protección de Datos. Responsable de los datos: Laura Otero Moreira. Finalidad de la recogida y tratamiento de los datos personales: enviarte boletín informativo de Python y comunicaciones comerciales. Legitimación: tu consentimiento. Destinatarios: no se ceden a terceros. Los datos se almacenan en los servidores de marketing (MailRelay). Derechos: podrás ejercer tus derechos de acceso, rectificación, limitación y supresión de datos en info @ codigopiton.com así como presentar una reclamación ante una autoridad de control. Más información en nuestra política de privacidad, encontrarás información adicional sobre la recopilación y el uso de tu información personal, incluida información sobre acceso, conservación, rectificación, eliminación, seguridad y otros temas.