State Monad在游戏中拯救董事会

时间:2017-06-02 17:35:01

标签: haskell monads state-monad

我有一个模块Game,用于定义类似play :: Board -> Move - > Board的方法播放。

我想在另一个名为Playing的模块中使用State Monad来导入Game模块,以便我可以在循环中调用play,直到Board到达某个州。

我想通过播放我从State Monad获得的Board来调用该方法,然后使用Board返回的play更新State Monad值。

因此,当循环继续时,我希望接收应用于play方法和当前状态的移动。

但我完全迷失了,因为模块Game不知道我使用的是State Monad。

我一直在查看相当多的教程和示例(例如thisthisthis等),我觉得我理解State Monad的应用方式那里但显然不够好,不能将它抽象到这个特定的实现。

playing :: IO ()
playing = do
            putStr $ "The board looks like:"
            board <- get
            putStr $ showBoard  board 
            putStr $ "Indicate a move:"
            move <- getLine
            if validMove move then do
                newBoard <- play board (getMove move)
                if gameEnded newBoard then do
                    putStr $ "You win!" --stop the execution
                else do
                    put newBoard
            else do
               putStr $ "Invalid move"

我希望playing处于循环状态,直到获得特定Board表示游戏结束。并使用State Monad将当前Board发送到play以及Game模块中的其他方法,例如gameEnded :: Board -> BoolshowBoard :: Board -> String和'getMove ::字符串 - &gt;移动”。

欢迎任何帮助

1 个答案:

答案 0 :(得分:1)

如果您将参数的顺序交换为play,则您的函数类型为:

Move -> Board -> Board

您可以使用Move部分申请获得以下类型之一:

Board -> Board

您可以使用State将此转换为modify :: (s -> s) -> State s ()上的操作来修改电路板:

playing :: Move -> State Board ()
playing move = modify (play move)

这里的一个解决方案是monad变压器 - 声音比它更可怕。您可以使用StateT而非IOStateT来存储游戏状态,使用IO来提示用户进行移动。例如:

import Control.Monad.Trans.Class (lift)
import Control.Monad.Trans.State (evalStateT, gets, modify)

-- Get a move from the user.
getMove :: IO Move
getMove = do
  line <- getLine
  -- (Your implementation of parsing moves here.)

-- The initial state of the board.
initialBoard :: Board
initialBoard = -- ...

-- Whether the board represents a completed game.
boardDone :: Board -> Bool
boardDone board = -- ...

-- Main game loop.
gameLoop :: IO ()
gameLoop = evalStateT loop initialBoard
  where
    loop = do
      move <- lift getMove
      modify (play move)
      done <- gets boardDone
      if done then pure () else loop

您使用lift将正常IO操作转换为StateT Board IO操作,modify :: (Monad m) => (s -> s) -> StateT s m ()修改状态,使用gets :: (Monad m) => (s -> a) -> StateT s m a读取属性当前状态。 loop要么尾部调用自己继续播放,要么返回。

使用编辑过的问题中的结构和名称:

playing :: IO ()
playing = evalStateT loop initialBoard
  where

    loop :: StateT Board IO ()
    loop = do
      printBoard
      move <- lift promptMove
      modify (play move)
      ended <- gets gameEnded
      if ended
        then lift $ putStrLn "You win!"
        else loop

    printBoard :: StateT Board IO ()
    printBoard = do
      lift $ putStrLn $ "The board looks like:"
      board <- get
      lift $ putStrLn $ showBoard board

    promptMove :: IO Move
    promptMove = do
      putStr "Indicate a move: "
      move <- getLine
      if validMove move
        then pure $ getMove move
        else do
          putStrLn "Invalid move."
          promptMove