首页 > 解决方案 > 我如何使用 MVars 在我的 pingpong haskell 游戏中移动球拍?

问题描述

我已经有一个在haskell的乒乓球比赛中移动2个球拍的功能。我想改变,所以它现在使用 MVars。

我知道我需要将 wHeld、sHeld、downHeld 和 upHeld 更改为 MVar,但是关于如何更改 movePaddle 以处理 MVar 的任何想法?

此外,当我声明 wHeld 一个 MVars 时,它在派生节目时显示错误(非实例(显示 MVar Bool))

data PongGame = Game
  { ballLoc :: (Float, Float)  -- ^ Pong ball (x, y) location.
  , ballVel :: (Float, Float)  -- ^ Pong ball (x, y) velocity. 
  , player1 :: Float           -- ^ Left player paddle height.
                               -- Zero is the middle of the screen. 
  , player2 :: Float           -- ^ Right player paddle height.
  , playerRPos :: Float -- posicao do player right
  , playerLPos :: Float --- posicao do player left
  , ghci :: Bool -- pausar
  , showMenu :: Bool -- mostrar o menu
  , wHeld :: MVar Bool -- segura o w
  , sHeld :: Bool -- segura os
  , downHeld :: Bool -- segura down
  , upHeld :: Bool -- segura para cima
  , playerLScore :: Int -- score do jogador left
  , playerRScore :: Int -- score do jogador right
  , paused :: Bool
  } deriving Show 
movePaddle :: PongGame -> PongGame
movePaddle = moveLeftPaddle . moveRightPaddle 
moveLeftPaddle game
          | (wHeld game) = game {playerLPos = paddleUp (playerLPos game)}
          | (sHeld game) = game {playerLPos = paddleDn (playerLPos game)}
          | otherwise = game
moveRightPaddle game
          | (upHeld   game) = game {playerRPos = paddleUp (playerRPos game)}
          | (downHeld game) = game {playerRPos = paddleDn (playerRPos game)}
          | otherwise = game

paddleUp pos = min (pos + 10) paddleMax
paddleDn pos = max (pos - 10) paddleMin

标签: multithreadinghaskellconcurrencyio-monadprogram-transformation

解决方案


MVar 的工作方式是 type 的值MVar Bool是一个不透明的“令牌”,它引用Bool. 您创建这样的令牌并使用 IO 操作读取和修改其关联存储位置的内容。

默认情况下,令牌本身(MVar Bool值)没有Show实例。出于调试目的,您可以添加一个:

instance Show (MVar a) where show _ = "<MVar>"

这将允许您派生Show实例而PongGame不会收到错误消息。但是,如果没有一些严重的 IO 滥用,实例MVars无法显示存储在 中的值Show,因此您可能需要考虑编写一个函数dumpGame :: PongGame -> IO (),使用所有 MVar 值漂亮地打印当前游戏状态,让您Show完全跳过实例.

无论如何,要重写您的程序以在关键字PongGame段中使用 MVar:

data PongGame = Game
  {
  ...
  , wHeld :: MVar Bool -- segura o w
  , sHeld :: MVar Bool -- segura os
  , downHeld :: MVar Bool -- segura down
  , upHeld :: MVar Bool -- segura para cima
  ...
  }

您需要重写您的movePaddle及其子函数以在 IO monad 中运行:

movePaddle :: PongGame -> IO PongGame
moveLeftPaddle :: PongGame -> IO PongGame
moveRightPaddle :: PongGame -> IO PongGame

movePaddle中,您可以将.运算符替换为<=<from Control.Monad,它是函数组合的一元等效项:

movePaddle = moveLeftPaddle <=< moveRightPaddle

这基本上是以下的简写:

movePaddle game = do
    game1 <- moveRightPaddle game
    game2 <- moveLeftPaddle game1
    return game2

然后,您需要通过执行 IO 操作来重写moveLeftPaddlemoveRightPaddle访问 MVar 的内容:

moveLeftPaddle game
  = do up <- readMVar (wHeld game)
       dn <- readMVar (sHeld game)
       case (up, dn) of
         (True, False) -> return $ game {playerLPos = paddleUp (playerLPos game)}
         (False, True) -> return $ game {playerLPos = paddleDn (playerLPos game)}
         _ -> return game

moveRightPaddle定义类似。

需要明确的是,函数调用wHeld game是一个简单的纯函数调用,它检索与字段关联的类型的标记。然后我们执行 IO 操作以实际检索值,然后我们可以在语句中对其进行操作以更新游戏状态。MVar BoolwHeldreadMVar <this_token>Boolcase

在您的程序的其他地方,您将需要一个setup创建这些 MVar 的例程:

initGame :: IO PongGame
initGame = do
  ...
  wHeld <- newMVar False
  sHeld <- newMVar False
  ...
  return $ Game ... wHeld sHeld ...

并且您可能会有一些线程正在运行以下函数:

processKeyEvent :: KeyEvent -> PongGame -> IO ()
processKeyEvent event game = do
  ...
  case event of
     ...
     KeyDown 'W' -> void $ swapMVar (wHeld game) True
     KeyUp   'W' -> void $ swapMVar (wHeld game) False
     ...

推荐阅读