patternMinor
BrickBreaker Spinoff in Haskell
Viewed 0 times
spinoffbrickbreakerhaskell
Problem
I have an implementation of a BrickBreaker-like game where instead of pieces being just removed from the ceiling, each impact results in a new ball being released, gradually building up to a pretty chaotic game.
The SDL related code that actually draws the game is not included here.
This is my second "big" project using Haskell, so I'd appreciate some critique, specifically concerning the
``
data Particle = Particle { partX, partY, partDX, partDY :: !Int }
data Paddle = Paddle { paddleX, paddleW, paddleH :: !Int }
data GameState = GS ![Particle] !Block
data CollisionResult = Miss | Hit Particle Block
type Block = M.Map Pos Particle
type Pos = (Int, Int)
getPos, getSpeed :: Particle -> Pos
getPos pt = (partX pt, partY pt)
getSpeed pt = (partDX pt, partDY pt)
genBlock :: Block
genBlock = mkMap [Particle w h 0 0 | w (getPos pt, pt))
approach :: Particle -> Block -> Maybe Particle
approach pt bs = msum $ zipWith ((flip M.lookup bs .) . addSpeed) (enum dx) (enum dy)
where (x, y) = getPos pt; (dx, dy) = getSpeed pt
addSpeed dx dy = (x + dx, y + dy)
enum 0 = repeat 0
enum n = let i = if n Block -> CollisionResult
collisionBlock pt bs
| dy > 0 && y > blockH = Miss
| dy blockH - dy = Miss
| otherwise =
case approach pt bs of
The SDL related code that actually draws the game is not included here.
This is my second "big" project using Haskell, so I'd appreciate some critique, specifically concerning the
approach and collisionBlock functions. First in approach I zip Data.Map.lookup over a list of keys, and then use msum to get the first successful lookup. Then in collisionBlock I use Data.Map.updateLookupWithKey again on the key I already know is successful and had already performed a lookup with. I'd like to be able to eliminate this extra lookup or otherwise improve the approach function.``
{-# LANGUAGE BangPatterns #-}
module BrickBreaker where
import Control.Monad
import Data.List
import qualified Data.Map as M
import System.Random
width = 640 :: Int
height = 420 :: Int
blockW = width
blockH = height quot` 3data Particle = Particle { partX, partY, partDX, partDY :: !Int }
data Paddle = Paddle { paddleX, paddleW, paddleH :: !Int }
data GameState = GS ![Particle] !Block
data CollisionResult = Miss | Hit Particle Block
type Block = M.Map Pos Particle
type Pos = (Int, Int)
getPos, getSpeed :: Particle -> Pos
getPos pt = (partX pt, partY pt)
getSpeed pt = (partDX pt, partDY pt)
genBlock :: Block
genBlock = mkMap [Particle w h 0 0 | w (getPos pt, pt))
approach :: Particle -> Block -> Maybe Particle
approach pt bs = msum $ zipWith ((flip M.lookup bs .) . addSpeed) (enum dx) (enum dy)
where (x, y) = getPos pt; (dx, dy) = getSpeed pt
addSpeed dx dy = (x + dx, y + dy)
enum 0 = repeat 0
enum n = let i = if n Block -> CollisionResult
collisionBlock pt bs
| dy > 0 && y > blockH = Miss
| dy blockH - dy = Miss
| otherwise =
case approach pt bs of
Solution
One comment on your modelling...
Consider making the paddle position part of your
One of the events could be "move the paddle left" or "move the paddle right" which would affect the position of the paddle.
Also, don't forget to put a random number generator into your
Update: It's a good idea to think in alternative use cases when defining the roles and responsibilities of your functions.
For instance, one possible definition of
Consider making the paddle position part of your
GameState. Regardless of how you are going to control the paddle, conceptually it is part of the state. In particular, it is required in order to draw the game screen. Your game loop will look something like:gameLoop :: GameState -> IO ()
gameLoop s = if stillPlaying s
then do drawScreen s
e IO ()
...
getEvent :: IO Event
...stillPlaying has the signature GameState -> Bool and returns false when the game is over.nextState has the signature GameState -> Event -> GameState and creates the next state by applying the effects of an event.One of the events could be "move the paddle left" or "move the paddle right" which would affect the position of the paddle.
Also, don't forget to put a random number generator into your
GameState - I'm sure you will want to have some randomness in your game eventually.Update: It's a good idea to think in alternative use cases when defining the roles and responsibilities of your functions.
For instance, one possible definition of
drawScreen is to simply putStrLn $ show s - assuming that you've derived a Show instance for GameState. And getEvent could simply read a number from stdin and create the Event value. Then you can test your game code without using SDL.Code Snippets
gameLoop :: GameState -> IO ()
gameLoop s = if stillPlaying s
then do drawScreen s
e <- getEvent
gameLoop $ nextState s e
else return ()
drawScreen :: GameState -> IO ()
...
getEvent :: IO Event
...Context
StackExchange Code Review Q#18268, answer score: 2
Revisions (0)
No revisions yet.