【问题标题】:How to keep track of number of guesses in a simple guessing game (Haskell)如何在一个简单的猜谜游戏中跟踪猜测的次数(Haskell)
【发布时间】:2012-08-08 00:43:41
【问题描述】:

在过去的几周里,我一直在尝试独立学习 Haskell。目前,我正在尝试实现一个愚蠢的小猜谜游戏,其中计算机选择一个随机数,用户尝试猜测它。如果用户错了,程序会告诉用户答案更高或更低,并允许用户猜测,直到他们猜对为止。我已经让它工作了,但我想添加一个功能,以跟踪用户在每场游戏中的猜测次数,并在用户猜对后将该数字报告给用户。

来自命令式背景,很自然的做法是有一个计数器,每次用户猜测时都会增加一个计数器,但在 Haskell 中你不能真正做到这一点(至少看起来像无状态和一切的不变性会阻止这种情况发生)。

我玩弄了让 getGuess 和 giveHints 函数接受一个额外参数的想法,该参数表示到目前为止的猜测次数(我们称之为 numGuesses),并且在每次调用这些方法时,传递 (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 标准输出是因为我使用的是行缓冲,如果没有它,一些交互的顺序就不是人们所期望的。

【问题讨论】:

  • 你的想法(添加一个参数来计算已经有多少猜测)是完美的。尝试时出了什么问题?
  • 我想解决这段代码中与实际问题无关的一个奇怪的位(这就是为什么这是一个单独的注释)。如果您使用的是显式种子,则应使用setStdGen 放回randomR 返回的新种子,而不是使用newStdGen 完全重新播种。 (实际上,在理想情况下,您根本不会重新播种,而是在程序的不同运行中保存种子。如果您有兴趣,请问我为什么。)或者,您可以使用跳过 getStdGen/randomR/setStdGen 序列randomRIO,封装了这个模式。
  • @NicolasDudebout 好吧,简短的版本是 PRNG 只保证从单个种子开始的长序列;我不知道有什么可以保证不同种子产生的第一个数字的序列。在 ghci 中试试这个,例如:map (fst . random . mkStdGen) [0..10] :: [Bool],它会产生看起来不太随机的[True,True,True,True,True,True,True,True,True,True,True]
  • @DanielWagner - 仅通过查看序列,您如何确定这不是随机的? ;)
  • 我遇到的问题是,在我的理解中,块会返回一些 IO () 类型的东西。我构建函数的方式要求我需要 getGuess 和 giveHints 函数来返回 numGuesses 值(并且它们都需要传递它),因为它们是相互递归的。我尝试通过使用 return 将 numGuesses 封装为 IO 操作来解决此问题,但这似乎很棘手,我无法让它工作。

标签: haskell random state


【解决方案1】:

您实际上可以实现您正在考虑的计数方法,但您仍然必须显式地传递状态。但在这种情况下,这根本不是一件麻烦事。事实上,这是一种在辅助函数中经常看到的模式,实际上使用 State monad 会有点过头了。

我所指的模式通常是这样的:

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

备注

  • &lt;$&gt;fmap
  • 正如 Daniel 所说,我使用了 randomRIO,因为我们已经在 IO monad 中了。
  • 我不必在 Windows 上使用命令提示符使用 hSetBufferinghFlush 来获得正确的输出。然而,YMMV。

【讨论】:

  • 您也可以使用if guess == answer then do ... else do ... 而不是两个类似的when 语句。
  • 是的,我知道,但如果可能的话,我喜欢远离 if-then-else。只是偏好问题。
  • @identity 感谢您的详细回复。我现在看到我的主要缺陷是我的相互递归的 giveHints 和 getGuesses 函数,这对我来说非常恶心。你的组织很有意义。
  • @mkfarrow:相互递归还不错,只是这种方式好一点。你想看看一个使用相互递归但也传递猜测次数的版本吗?
【解决方案2】:

为猜测的数量添加一个额外的参数正是你在功能上做这种事情的方式。

基本的函数式思维模式是,如果你有一个函数需要根据“某物”的不同值表现出不同的行为,那么该某物就是该函数的参数。这是纯洁的一个简单结果;对于相同的输入,函数必须始终返回相同的内容。

当您使用更高级的技术时,有多种方法可以“隐藏”额外参数,从而使您不必显式地编写/传递它们;这基本上正是State monad 所做的事情,而对IO monad 的一种思考方式是它正在做类似的事情。但是,虽然您是函数式编程的新手,但习惯这种思维模式可能会更有帮助;您通过其参数将信息传递给您正在调用的函数,并通过其参数接收信息。您不能诉诸将信息留在某个外部位置(例如计数器的值)的必要技巧,您知道调用的函数会查找它(甚至修改它)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多