Jump to content
  • Advertisement
Sign in to follow this  
load_bitmap_file

Haskell Pong

This topic is 3622 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I've been learning some Haskell this summer so I decided to write Pong as an exercise. The source code/binaries are here: HPong (Windows) | HPong (Linux) These are some thoughts/problems that occured to me in the process in the hope that some of the Haskell people lurking around here will see this thread and address: 1. Callbacks require modifying state in some manner in order to let the normal program flow know something has happened. For Pong I used IORefs to modify a "KeysState" structure for when the user presses a key. It seems like we'd want to minimize the amount of state changing we have to do so this seemed like the best route rather than try and modify the world directly. 2. In the HPong source in the gameMain function I have three functions in a row that need to update the ball velocity variable (if the ball went out of bounds, if we hit a wall/paddle, if it needs to be reset). If I were using a "normal" language that allowed variable modification I would just update the ballVelocity variable in each function, but in Haskell each time I return a new ball velocity variable I have to give it a new variable name, e.g. ballVel, ballVel', ballVel'', etc. Is there a better, more functional perhaps way of doing this? 3. Dealing with random variables seems kind of annoying (particularly for games) since it violates referential transparency. This means any time I need a random number for the result to be useful the return value has to propogate upwards through do blocks until it reaches its destination. On second thought we could probably store the IO result and retain purity until we actually need to evaluate the result. I haven't fully thought this through yet. 4. The approach I took for the "gameMain" function seems fairly straightforward in that we can call whatever operations need to be done and pass around whatever pieces of the world state we need, then encapsulate all the game/world state pieces in a more-or-less monolithic data type, which gets passed back into gameMain when we tail recurse into the next iteration of the gameMain loop. Then in the new iteration we can extract whatever pieces we need from the monolithic "GameState" structure and repeat. At this highest level gameMain function though, it is basically imperative since I spend the entire time sitting in a do block (for the HPong source at least). The functions I'm calling are almost all pure themselves however. I'm wondering whether or not a game main function has to be imperative like this inherently, since the order is rather important (e.g. first check player inputs, then check collisions, then update A.I., finally draw everything). So, hopefully that's not too long winded. There's still a lot I need to learn (in particular monads), but I'd like to see what this discussion brings up.

Share this post


Link to post
Share on other sites
Advertisement
OK, I've only skimmed the code but here are my thoughts:

#1: The code in GameInput.hs is a lot more verbose and repetitive than it needs to be. You can use record updates to take away a lot of the tedium:

keyBindings :: Key -> Bool -> (KeysState -> KeysState)
keyBindings (Char 'w') state keys = keys { leftPlayerUp = state }
keyBindings (Char 's') state keys = keys { leftPlayerDown = state }
keyBindings (SpecialKey KeyUp) state keys = keys { rightPlayerUp = state }
keyBindings (SpecialKey KeyDown) state keys = keys { rightPlayerDown = state }
keyBindings _ _ keys = keys

keyboardAct :: IORef KeysState -> Key -> KeyState -> IO ()

keyboardAct _ (Char '\ESC') Down = throwIO (ExitException ExitSuccess)
keyboardAct keysState key Down = modifyIORef keysState (keyBindings key True)
keyboardAct keysState key Up = modifyIORef keysState (keyBindings key False)


#2/#3: Check out the State monad, and in particular the StateT monad transformer. In the same way that your current code implicitly modifies the state of the internal random number generator, you can make the modifications to the game state implicit as well. You can also keep a RandomGen instance in your game state to avoid depending on the IO monad for random numbers.

If you haven't already, be sure to read "All About Monads".

#4: Don't worry; despite what you may have heard, "imperative" is not a dirty word in Haskell!

Anyway, good luck, and happy hacking!

Share this post


Link to post
Share on other sites
Quote:
Original post by dwahler
#1: The code in GameInput.hs is a lot more verbose and repetitive than it needs to be. You can use record updates to take away a lot of the tedium:

*** Source Snippet Removed ***


Looks very useful! Wasn't aware that existed.

Quote:
#2/#3: Check out the State monad, and in particular the StateT monad transformer. In the same way that your current code implicitly modifies the state of the internal random number generator, you can make the modifications to the game state implicit as well. You can also keep a RandomGen instance in your game state to avoid depending on the IO monad for random numbers.

If you haven't already, be sure to read "All About Monads".


Thanks for the link; the name sounds familiar but I don't believe I ever actually read it. Makes me wish I had done so sooner, since it's much more clearly written than the other monad tutorials I've found. I'm still reading over it, but I can already see some situations where monads would have been useful. There are definitely places I could have used Maybe, liftM_, and StateT, and no doubt several others I haven't seen yet. I probably shouldn't be surprised monads are turning out to be so useful since dealing with side effects is the underlying question here.

Quote:
#4: Don't worry; despite what you may have heard, "imperative" is not a dirty word in Haskell!


I've heard something along the lines of Haskell programmers considering it their favorite imperative language. Didn't make any sense to me at first, but I'm beginning to see why this might make sense in the context of monads.

Quote:
Anyway, good luck, and happy hacking!


Thanks for the great feedback and nudging me in the right direction! [smile]

Share this post


Link to post
Share on other sites
Hi, I have played the game on Windows and I have noted some errors in how the collisions with the rackets are handled. Sometimes, the ball is reflected even if it hit the wall (but always near the racket).

Share this post


Link to post
Share on other sites
Quote:
Original post by apatriarca
Hi, I have played the game on Windows and I have noted some errors in how the collisions with the rackets are handled. Sometimes, the ball is reflected even if it hit the wall (but always near the racket).


I changed what part of the paddle its (x, y) position indicated part way through making this but never updated the collision checking accordingly, so the "bounding box" is offset a bit from the paddle. The ball angle is also sometimes way too close to vertical. I'm aware of these problems but I decided to be lazy and not fix them since Pong itself wasn't really the point of this. Thanks for bringing it up though.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!