Translate

domingo, 10 de mayo de 2020

Juego de adivinanzas en Haskell

Vamos a ser un juego de adivinanzas. Va a haber una palabra que el jugador debe adivinar. Para cada turno del juego, el jugador adivina una sola letra. Si esa letra es correcta, las letras adivinadas se muestran en los lugares correctos de la palabra. Si esa letra es incorrecta, el usuario pierde una estrella. Una vez que el usuario no tiene estrellas, ha perdido el juego. Sin embargo, si el usuario adivina todas las letras de la palabra, ha ganado el juego, como el ahorcado.

Comience creando un archivo de texto vacío llamado ahorcado.hs: la extensión hs es para indicar que este archivo contiene el código fuente de Haskell.


El corazón del juego consiste en verificar las conjeturas del jugador. Queremos saber si la suposición fue correcta. Este resultado es un valor Bool, ya sea verdadero o falso. Necesitamos actualizar la palabra mostrada, si la suposición fue correcta, reemplazando los guiones apropiados en la palabra mostrada con el carácter correctamente adivinado. Por lo tanto, el tipo de resultado de la función es un par (Bool, String). El primer elemento del par es el resultado de la suposición. El segundo elemento es la Cadena que se mostrará al usuario para la próxima ronda.

Ahora, la función de verificación necesita saber:
  • La palabra secreta, una cadena
  • La palabra que se muestra en pantalla o real, también una cadena
  • El carácter elegido por el jugador.
Estas son las entradas a la función de verificación. Entonces ahora podemos establecer el tipo de la función:

check :: String -> String -> Char -> (Bool, String)

Siempre es útil determinar primero el tipo de función. Esto enfoca su atención en lo que se supone que debe calcular la función, y qué datos necesita para hacerlo. Los buenos ingenieros de software hacen las especificaciones antes de la implementación.

¿Cómo será el cuerpo de la función de verificación? La suposición del jugador es correcta si y solo si el carácter elegido c está en la palabra secreta. Entonces la suposición es correcta si :

c palabra `elem`

La nueva palabra que se mostrará será:

[(if x==c then c else y) | (x,y) <- zip word display]

Esta es una lista de comprensión, donde seleccionamos cada letra de la palabra real o de la pantalla anterior. La palabra es texto sin formato, mientras que la pantalla comienza con todos los caracteres discontinuos.

check :: String -> String -> Char -> (Bool, String)
check word display c
  = (c `elem` word, [if x==c
          then c
          else y | (x,y) <- zip word display])

La siguiente función que definiremos es la función de giro. Esta es la función que se llamará cada vez que sea el turno del jugador para ingresar un caracter. Primero debemos comprobar cuántas conjeturas le quedan al jugador:

if n == 0

Si quedan algunas conjeturas, entonces necesitamos ver si el jugador completo la palabra o no:

if word == display

Entonces tendremos dos verificaciones if, cada una seguida de mensajes de estado putStrLn y el final de la secuencia de llamada a la función (ya que es el final del juego). Sin embargo, si ninguna de las condiciones es verdadera, entonces el jugador puede tomar un turno, por lo que llamamos a otra función para obtener otro personaje de la entrada del usuario.

turn :: String -> String -> Int -> IO ()
turn word display n =
  do if n==0
       then putStrLn "You lose"
       else if word==display
              then putStrLn "You win!"
              else mkguess word display n

Tenga en cuenta que hay una forma más ordenada de escribir la función de giro, utilizando Haskell guards, pero no vamos a utilizarlas para simplificar el programa.

mkguess word display n =
  do putStrLn (display ++ "  " ++ take n (repeat '*'))
     putStr "  Enter your guess: "
     q <- getLine
     let (correct, display') = check word display (q!!0)
     let n' = if correct then n else n-1
     turn word display' n'

¿Cuál es el tipo de mkguess? ¿Puedes resolverlo y agregarlo antes de la definición de la función? Tomamos una línea de entrada del usuario, pero solo usamos el primer carácter para la suposición. Esto fallará si el usuario simplemente presiona ENTER sin escribir ningún carácter, ya que q será una cadena vacía.

Bien, ahora solo necesitamos una función de nivel superior:

starman :: String -> Int -> IO ()
starman word n = turn word ['-' | x <- word] n

Esta función toma dos argumentos, el primero es la palabra a adivinar y el segundo es el número de conjeturas incorrectas que el jugador tiene permitido.

Pongamos todas estas cuatro funciones en un archivo de texto, llamado starman.hs

Guarde el archivo, luego inicie ghci quizás escribiendo ghci en un símbolo del sistema de DOS, ejecutando WinGHCi o escribiendo ghci en una ventana de terminal (macOS o Linux).

Si está en el directorio correcto, es decir, en el que guardó starman.hs, debería poder escribir

:l starman.hs

y el programa debería cargarse. Dirá algo como:

Prelude> :l ahorcado.hs
[1 of 1] Compiling Main             ( ahorcado.hs, interpreted )
Ok, one module loaded.

Starman "funcionalmente" 5

y comienza a jugar el juego! Volverá a la solicitud de GHCi cuando se complete la función starman.

Y Listo!!