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