2012-08-08 13 views
7

Ho cercato di imparare Haskell in modo indipendente nelle ultime settimane. Attualmente, sto cercando di implementare un piccolo gioco indovinato in cui il computer sceglie un numero casuale e l'utente prova a indovinarlo. Se l'utente ha torto, il programma dice all'utente che la risposta è più alta o più bassa e consente all'utente di indovinare fino a quando non lo indovinano correttamente. Ho funzionato, ma vorrei aggiungere la possibilità di tenere traccia del numero di ipotesi che l'utente fa ogni gioco e riportare quel numero all'utente una volta che lo indovino correttamente.Come tenere traccia del numero di ipotesi in un semplice gioco di ipotesi (Haskell)

Provenendo da uno sfondo imperativo, la cosa naturale da fare sarebbe avere un contatore che viene incrementato ogni volta che l'utente fa un'ipotesi, ma non è possibile farlo in Haskell (almeno sembra l'apolidia e l'immutabilità di tutto lo impedirebbe).

Mi sono divertito con l'idea di rendere le funzioni getGuess e giveHints un parametro aggiuntivo che rappresenta il numero di ipotesi finora (chiamiamolo numGuesses) e, ad ogni chiamata a quei metodi, pass (numGuesses + 1) . Ma non riuscivo a farlo funzionare (per non parlare che non so nemmeno se funzionasse).

Il mio codice è di sotto. Qualsiasi suggerimento sarebbe molto apprezzato. Sono principalmente alla ricerca di idee, ma sentitevi liberi di pubblicare anche il codice attuale. Inoltre, non esitate a farmi sapere se il mio codice fa schifo e come potrei migliorare se si nota qualcosa di atroce

import System.Random 
    import System.IO 
    import Control.Monad 

    main = do 
     gen <- getStdGen 
     let (ans,_) = randomR (1,100) gen :: (Int,StdGen) 
     putStrLn $ "I'm thinking of a number between 1 and 100..." 
     getGuess ans 
     putStrLn "You guessed it in __ guesses!" 
     putStr "Play again? " 
     hFlush stdout 
     desire <- getLine 
     when ((desire !! 0) `elem` ['y','Y']) $ do 
      putStrLn "" 
      newStdGen 
      main 

    getGuess ans = do 
     putStr "Your guess? " 
     hFlush stdout 
     guessStr <- getLine 
     giveHints ans (read guessStr) 

    giveHints ans guess = do 
     when (ans /= guess) $ do 
      if ans > guess 
       then putStrLn "It's higher." 
       else putStrLn "It's lower." 
      getGuess ans 

Nota (ho solo la programmazione funzionale per un paio di settimane!): Io sto usando hFlush stdout perché sto usando il buffering di riga e, senza di esso, l'ordine di alcune interazioni non è quello che ci si aspetterebbe.

+3

La tua idea (aggiungendo un parametro che conta quanti errori sono già passati) è perfetta. Cosa è andato storto quando lo hai provato? –

+4

Mi piacerebbe indirizzare un bit strano in questo codice che non è correlato alla domanda reale (motivo per cui questo è un commento separato). Se stai usando un seed esplicito, dovresti rimettere il nuovo seme restituito da 'randomR' usando' setStdGen' invece di eseguire nuovamente il seeding con 'newStdGen'. (In effetti, in un mondo ideale, non avresti mai fatto un seeding, invece salvando il seed attraverso diverse sequenze del programma. Chiedimi perché se sei interessato.) In alternativa, puoi saltare il 'getStdGen/randomR/setStdGen 'sequenza usando' randomRIO', che incapsula questo pattern. –

+1

Sono interessato. Perché? ;-) –

risposta

8

È possibile infatti implementare il metodo di conteggio a cui si stava pensando, ma è comunque necessario passare lo stato in modo esplicito. Ma in questo caso, non è affatto una seccatura. In realtà, questo è uno schema che si vede abbastanza spesso per le funzioni di supporto, dove in realtà l'uso della monade State sarebbe eccessivo.

Il modello a cui mi riferisco si presenta spesso come questo:

doStuff xs' = go xs' 0 
    where 
    go (x:xs) n = .. etc .. 

Ecco il codice.

import System.Random  (randomRIO) 
import Control.Applicative ((<$>)) 
import Control.Monad  (when) 
import Text.Printf   (printf) 

playGame :: Int -> Int -> IO() 
playGame answer curGuesses = do 
    putStrLn "What is your guess?" 
    putStr ">" 
    guess <- getGuessFromUser 
    when (guess /= answer) $ do 
     giveHints answer guess 
     playGame answer (curGuesses + 1) 
    when (guess == answer) $ do 
     putStrLn "You guessed it!" 
     printf "You guessed %d times!\n" (curGuesses + 1) 

giveHints :: Int -> Int -> IO() 
giveHints answer guess 
    | answer > guess = putStrLn "It's higher!" 
    | otherwise  = putStrLn "It's lower!" 

getGuessFromUser :: IO Int 
getGuessFromUser = do 
    read <$> getLine 

main :: IO() 
main = do 
    answer <- randomRIO (1, 100) 
    putStrLn "I'm thinking of a number between 1 and 100." 
    playGame answer 0 

Note

  • <$> è fmap
  • ho usato randomRIO come Daniel accennato, dal momento che siamo già in monade IO.
  • Non ho dovuto utilizzare hSetBuffering o hFlush utilizzando il prompt dei comandi su Windows per ottenere l'output corretto. YMMV, comunque.
+0

Si potrebbe anche usare 'if guess == answer then do ... else do ...' invece di avere 2 istruzioni 'when' simili. – huon

+0

Sì, lo so, ma mi piace stare lontano da if-then-else se possibile. Solo una questione di preferenza. – identity

+1

@identity Grazie per la risposta dettagliata. Ora capisco che il mio difetto principale erano le funzioni giveHints e getGuesses reciprocamente ricorsive, il che era piuttosto disgustoso per me. La tua organizzazione ha molto senso. – mkfarrow

3

L'aggiunta di un parametro aggiuntivo per il numero di tentativi è esattamente come si esegue questo genere di cose dal punto di vista funzionale.

Il modo di pensare funzionale di base è che se si ha una funzione che deve comportarsi in modo diverso a seconda dei diversi valori di "qualcosa", allora quel qualcosa è un parametro della funzione. Questa è una semplice conseguenza della purezza; una funzione deve sempre restituire la stessa cosa per gli stessi input.

Quando si arriva a tecniche più avanzate ci sono vari modi per "nascondere" i parametri extra per liberare il sistema dal doverli scrivere/passarli esplicitamente; questo è fondamentalmente esattamente ciò che fa il monad State, e un modo di pensare sulla monade IO è che sta facendo qualcosa di simile. Ma mentre sei nuovo nella programmazione funzionale è probabilmente più utile abituarsi a questa modalità di pensiero; comunichi le informazioni a una funzione che stai chiamando attraverso i suoi argomenti e ricevi le informazioni attraverso i suoi argomenti. Non puoi ricorrere all'imperativo trucco di lasciare le informazioni in qualche luogo esterno (ad esempio il valore di un contatore) in cui sai che la funzione che chiamerai la cercherà (o addirittura la modificherà).

Problemi correlati