我刚刚完成了大学的函数式编程课程,并继续学习Haskell,因为我发现它非常有趣!我做了一个简单的绞刑架游戏,并希望听到您的任何想法和想法,可能会出现在代码!例如,在函数式编程的意义上,怎样才能使它更加教条主义呢?
import Control.Monad
import Data.List (elemIndices, sort)
pictures = 9
main :: IO ()
main = do
word <- getWord ""
clearScreen
hang word
hang :: String -> IO ()
hang word = hang' word [] [] pictures
hang' :: String -> [Char] -> [Char] -> Int -> IO ()
hang' word _ _ lives | lives == 0 = clearScreen >> putStrLn (renderHangman 0) >> putStrLn "You lost!" >> putStrLn ("The correct word was " ++ word ++ "\n")
hang' word rights _ lives | win rights word = clearScreen >> putStrLn (renderHangman lives) >> putStrLn "You won!" >> putStrLn ("The correct word was " ++ word ++ "\n")
hang' word rights wrongs lives = do
clearScreen
putStrLn $ renderHangman lives
putStrLn $ renderWord rights word
putStrLn $ renderLives lives
putStrLn $ renderWrongs wrongs
guess <- getGuess
if guess `elem` (rights ++ wrongs)
then hang' word rights wrongs lives
else
if correctGuess guess word
then hang' word (guess : rights) wrongs lives
else hang' word rights (guess : wrongs) (lives - 1)
win :: [Char] -> String -> Bool
win guesses = all (`elem` guesses)
clearScreen :: IO ()
clearScreen = replicateM_ 100 (putStrLn "")
correctGuess :: Char -> String -> Bool
correctGuess guess word = guess `elem` word
getGuess :: IO Char
getGuess = do
putStrLn "Guess a letter!"
getChar
getWord s = do
clearScreen
putStrLn "Give a secret word!"
putStr ['*' | _ <- s]
c <- getChar
case c of
'\n' -> return s
char -> getWord (s ++ [char])
renderWord :: [Char] -> String -> String
renderWord guesses = foldr hide ""
where
hide = \x xs -> if x `elem` guesses then x : xs else '_' : xs
renderWrongs :: [Char] -> String
renderWrongs [] = ""
renderWrongs wrongs = "Wrong guesses: " ++ sort wrongs
renderHangman :: Int -> String
renderHangman = unlines . hangmanpics
renderLives :: Int -> String
renderLives lives = show lives ++ " guesses left!"
hangmanpics :: Int -> [String]
hangmanpics 9 = [" ", " ", " ", " ", " ", " ", "========="]
hangmanpics 8 = [" ", " |", " |", " |", " |", " |", "========="]
hangmanpics 7 = [" +---+", " |", " |", " |", " |", " |", "========="]
hangmanpics 6 = [" +---+", " | |", " |", " |", " |", " |", "========="]
hangmanpics 5 = [" +---+", " | |", " O |", " |", " |", " |", " ========="]
hangmanpics 4 = [" +---+", " | |", " O |", " | |", " |", " |", " ========="]
hangmanpics 3 = [" +---+", " | |", " O |", " /| |", " |", " |", " ========="]
hangmanpics 2 = [" +---+", " | |", " O |", " /|\\ |", " |", " |", " ========="]
hangmanpics 1 = [" +---+", " | |", " O |", " /|\\ |", " / |", " |", " ========="]
hangmanpics 0 = [" +---+", " | |", " O |", " /|\\ |", " / \\ |", " |", " ========="]
hangmanpics _ = [" +---+", " | |", " O |", " /|\\ |", " / \\ |", " |", " ========="]
```#qcStackCode#发布于 2022-06-21 04:58:40
看上去真不错!
以下是一些建议,按意义的大致顺序排列
word、rights、wrongs和lives )来描述,这三个值在函数之间传递。为了可读性,我建议将它们包装成一个数据类型: data GameState = GameState { word ::String,right:,lives ::Int }hang'的结束让人感到困惑。我所说的“结束”是指getGuess之后的代码。语义结构是:“更新”“游戏状态”,然后重新启动hang'。但是,用当前的代码结构很难看出这一点,因为每个分支都是对hang'的“独立”调用。将状态存储在自己的GameState类型中,我们可以将代码清晰地拆分为以下部分:(1)更新状态;(2)调用hang'。接下来,它看起来是这样的:猜测<- getGuess let state‘= if猜测elem (权利状态++错误状态),然后声明correctGuess猜测状态,如果correctGuess猜测状态,则状态{ rights =猜测: rights } lives { if =猜测:错误状态,生命=生命状态-1}if’state‘state。[Char]不是rights和wrongs的适当数据类型。在这里使用[Char]向我表明,您关心Chars在列表中的顺序,以及他们出现了多少次,但实际上您不关心。相反,我推荐Data.Set。(这也会给你更好的表现!)getWord可以更简单地实现为clearScreen >> putStrLn "Give a secret word!" >> getLinewin --这个函数的圆滑实现!不相关,我会重命名它。win听起来像一个动作,但函数实际上是一个谓词。也许是isWin?import Control.Monad这样的不合格的进口产品。如果您有多个不合格的导入,则很难知道从哪里导入函数或操作符。getWord缺少它的类型签名,pictures也是如此。这并不是什么大问题,但通常建议在(至少)所有顶级值上包含类型签名。>>链接D42操作,而在其他地方,则使用do符号。这实际上是一回事;thingOne >> thingTwo和do { thingOne; thingTwo; }是相同的代码。(我不清楚你是否已经知道了)无论如何,为了可读性,我建议使用do来表示更长的链,而使用>>或>>=来表示更短的表达式。具体而言,使用clearScreen >> putStrLn ...重写hang'的do部分,然后将getGuess重写为putStrLn "Guess a letter!" >> getChar。希望这会有所帮助:-)查看所有更改的代码(Set除外) 这里
https://codereview.stackexchange.com/questions/277500
复制相似问题