How can i use MVars to move paddles on my pingpong haskell game?

I already have a function that moves 2 paddles in a ping pong game in haskell. I want to change so it uses MVars now.

I know that i need to change wHeld, sHeld, downHeld and upHeld to MVars but any ideas on how to change movePaddle to deal with MVars?

Also when i declare wHeld an MVars it shows a error on deriving show (Non instance for (Show 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

1 answer

  • answered 2019-06-24 23:10 K. A. Buhr

    The way MVars work is that a value of type MVar Bool is an opaque "token" referencing a storage location for a Bool. You create such a token and read and modify the contents of its associated storage location using IO actions.

    The token itself (the MVar Bool value) has no Show instance by default. For debugging purposes, you could add one:

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

    This will allow you to derive a Show instance for PongGame without getting an error message. However, the values stored in the MVars can't be displayed by the Show instance without some egregious IO abuse, so you might want to consider writing a function dumpGame :: PongGame -> IO () that pretty-prints the current game state with all the MVar values, letting you skip the Show instance entirely.

    Anyway, to rewrite your program to use MVars in key PongGame fields:

    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
      ...
      }
    

    you'll want to rewrite your movePaddle and its subfunctions to run in the IO monad:

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

    In movePaddle, you can replace the . operator with <=< from Control.Monad, which is the monadic equivalent of function composition:

    movePaddle = moveLeftPaddle <=< moveRightPaddle
    

    This is basically shorthand for:

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

    Then, you'll need to rewrite moveLeftPaddle and moveRightPaddle to access the contents of the MVars by executing IO actions:

    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
    

    with moveRightPaddle defined similarly.

    To be clear, the function call wHeld game is a simple, pure function call that retrieves the token of type MVar Bool associated with the wHeld field. We then execute the IO action readMVar <this_token> to actually retrieve the Bool value, which we can then act on in a case statement to update the game state.

    Elsewhere in your program, you'll need a setup routine that creates these MVars:

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

    and you'll presumably have some thread that's running a function like:

    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
         ...