State Monad 在游戏中保存棋盘

State Monad to save board in game

我有一个模块 Game 定义了这样一个方法 play play :: Board -> Move - > Board

我想在另一个名为 Playing 的模块中使用 State Monad,该模块导入了 Game 模块,这样我就可以从那里循环调用 play 直到 Board 达到一定状态

我想用从 State Monad 获得的 Board 调用方法,然后用 play 返回的 Board 更新 State Monad 值。

因此,在循环进行时,我想接收移动以应用于 play 方法和我的当前状态。

但是我完全不知道如何以模块 Game 不知道我正在使用 State Monad 的方式实现这一点。

我看了很多教程和例子(比如 this, this, this 等),我觉得我理解 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 发送到 playGame 模块中的其他方法,如 gameEnded :: Board -> BoolshowBoard :: Board -> String 和 'getMove :: String -> Move'.

欢迎任何帮助

如果将参数顺序交换为 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 要么 tail-calls 自己继续玩,要么 returns.

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

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