2012-08-08 11 views
7

私はここ数週間、Haskellを独立して学習しようとしています。現在、私はコンピュータが乱数を選択し、ユーザーがそれを推測しようとする、ちょっとした推測ゲームを実装しようとしています。ユーザーが間違っている場合、プログラムはユーザーに答えが高いか低いかを伝え、正しく推測するまでユーザーに推測させます。私はそれが働いているが、私は、ユーザーがすべてのゲームを作ったと推測の数を追跡し、彼らが正しく推測すると、その番号をユーザーに報告する機能を追加したいと思います。簡単な推測ゲーム(Has​​kell)の推測数を追跡する方法

命令的なバックグラウンドから来る自然なことは、ユーザーが推測するたびに増分されるカウンタを持つことですが、実際にはハスケルではできません(少なくとも無国籍のようですすべての不変性がそれを妨げるだろう)。

getGuess関数とgiveHints関数に、これまでの推測数を表す追加パラメータ(numGuessesと呼ぶ)を渡して、それらのメソッドを呼び出すたびにpass(numGuesses + 1) 。しかし、私はそれを働かせることができませんでした(私はそれが動作するかどうかわからないことは言うまでもありません)。

私のコードは以下の通りです。どんな提案も本当に感謝しています。私は主にアイデアを探していますが、実際のコードを投稿することも自由です。また、私のコードを吸うなら、私に知らせて自由に感じる、あなたが凶悪な何かが気づけば、私はそれを改善できるか

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 

注意を(私は数週間のために機能的にプログラミングをしてきた!):私はhFlushを使用しています私はラインバッファリングを使用しているので、stdoutと、それなしでは、いくつかの相互作用の順序は期待するものではありません。

+3

あなたのアイデア(すでに推測された推測の数を数えるパラメータを追加する)は完璧です。あなたはそれを試して何が間違っていたのですか –

+4

私は、実際の質問とは無関係なこのコードの奇妙な部分に取り組んでいます(これが別のコメントです)。明示的なシードを使用している場合、 'newStdGen'で完全に再シードするのではなく、' setStdGen'を使って 'randomR'によって返された新しいシードを戻すべきです。 (実際、理想的な世界では、プログラムを別のプログラムに渡ってシードを保存するのではなく、全くシームレスにシードする必要があります。なぜか興味があるのか​​どうか私に尋ねてください)。代わりに、 'getStdGen/randomR/setStdGen 'シーケンスを' randomRIO'を使用して作成します。これはこのパターンをカプセル化します。 –

+1

私は興味があります。どうして? ;-) –

答えて

8

あなたは考えていた計数方法を実際に実装することができますが、依然として明示的に状態を渡す必要があります。しかし、この場合は、まったく面倒なことではありません。実際には、これは実際にStateモナドを使用しているところがヘルパー関数のためにかなり頻繁に見えるパターンです。

私はしばしばに言及していたパターンは次のようになります。

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

ここでコードがあります。

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 

ノート

  • <$>ダニエルが述べたように、我々はIOモナドですでにしているので、私は、randomRIOを使用fmap
  • です。
  • 正しい出力を得るために、Windowsでコマンドプロンプトを使用してhSetBufferingまたはhFlushを使用する必要はありませんでした。しかし、YMMV。
+0

同様の2つの 'when'ステートメントを持つ代わりに' if guess == answer then do ... else do ... 'を使うこともできます。 – huon

+0

はい、私は知っていますが、可能ならばif-then-elseから遠ざかりたいです。好みの問題。 – identity

+1

@identity詳細な対応をありがとうございます。今私の主な欠陥は、相互に再帰的なgiveHints関数とgetGuesses関数であり、これはかなりわかりやすいものです。あなたの組織は多くの意味を持ちます。 – mkfarrow

3

推測の数に余分なパラメータを追加することは、この種の機能を機能させる方法とまったく同じです。

思考の基本的な機能モードは、「何か」の異なる値によって動作が異なる必要がある機能を持つ場合、その機能に対するパラメータです。これは純度の単純な結果です。関数は常に同じ入力に対して同じものを返す必要があります。

もっと高度な技術になると、余分なパラメータを「隠して」明示的に書き込み/渡す必要がなくなるさまざまな方法があります。これは基本的に正確にはStateのモナドが行うものであり、IOモナドに関する考え方の1つは、それが類似したことをすることです。しかし、あなたが関数型プログラミングに慣れていなくても、このような考え方に慣れたほうが役に立つでしょう。引数を使って呼び出す関数に情報を伝え、引数を使って情報を返すことができます。あなたが呼び出す関数がそれを探す(あるいはそれを修正する)ことを知っている外部の場所(例えば、カウンタの値)に情報を残すという命令的な手段に頼ることはできません。