diff options
author | Juan J. Martinez <jjm@usebox.net> | 2023-03-02 12:54:47 +0000 |
---|---|---|
committer | Juan J. Martinez <jjm@usebox.net> | 2023-03-02 12:54:47 +0000 |
commit | e5cbf917ad8b12b48932bc4fa13a044bc8159a74 (patch) | |
tree | 41511ca9f1447643ac921b2294f5dab8a62731e2 /src/Game | |
parent | 5dd9180606e935ee5a7c1637773fdfc3277677ca (diff) | |
download | space-plat-hs-e5cbf917ad8b12b48932bc4fa13a044bc8159a74.tar.gz space-plat-hs-e5cbf917ad8b12b48932bc4fa13a044bc8159a74.zip |
Even less IORef
Also reviewed comments and made the action to add effects more general.
Diffstat (limited to 'src/Game')
-rw-r--r-- | src/Game/Entities.hs | 86 | ||||
-rw-r--r-- | src/Game/Entities/Player.hs | 2 | ||||
-rw-r--r-- | src/Game/Entities/Types.hs | 7 | ||||
-rw-r--r-- | src/Game/Hud.hs | 22 |
4 files changed, 55 insertions, 62 deletions
diff --git a/src/Game/Entities.hs b/src/Game/Entities.hs index 0c1a4f3..2810fcf 100644 --- a/src/Game/Entities.hs +++ b/src/Game/Entities.hs @@ -17,18 +17,17 @@ import Game.Entities.Types import qualified Game.Map as M import qualified Game.Sprites as S import qualified Game.State as GS -import SDL (($~)) import qualified SDL -mkEntities :: S.SpriteSheet -> M.Map -> IORef C.Controls -> IORef GS.State -> IO Entities -mkEntities sprites m controls stateRef = do +mkEntities :: S.SpriteSheet -> M.Map -> IORef C.Controls -> IO Entities +mkEntities sprites m controls = do player <- case find M.isPlayer (M.objects m) of Just (M.PlayerEntity x y) -> mkPlayer sprites x y controls (M.isBlocked m) _ -> error "No player entity in map" playerRef <- newIORef player entities <- traverse (toEntity playerRef) $ sort $ filter (not . M.isPlayer) (M.objects m) -- the entities list has always player first - pure $ Entities sprites playerRef stateRef (player : entities) + pure $ Entities sprites playerRef (player : entities) where toEntity :: IORef Entity -> M.Object -> IO Entity toEntity playerRef (M.SlimeEntity x y) = mkSlime sprites x y (collision playerRef 16) (M.isBlocked m) @@ -38,55 +37,54 @@ mkEntities sprites m controls stateRef = do -- | Return the player's entity position (x, y). playerPosition :: Entities -> (Int, Int) -playerPosition (Entities _ _ _ entities) = +playerPosition (Entities _ _ entities) = (player.x, player.y) where player = head entities -updateAll :: Entities -> IO Entities -updateAll es = do +updateAll :: Entities -> GS.State -> IO (Entities, GS.State) +updateAll es state = do -- update the player first (including the reference) updatedPlayer <- player.update player void $ writeIORef es.player updatedPlayer - state <- readIORef stateRef - -- update hit delay if the player was hit - let playerWasHit = state.hitDelay > 0 - when playerWasHit (writeIORef stateRef state {GS.hitDelay = state.hitDelay - 1}) -- then the other entities - updated <- (updatedPlayer :) <$> traverse (updateFilter playerWasHit) others + updated <- (updatedPlayer :) <$> traverse (updateFilter $ state.hitDelay > 0) others -- process actions - updated' <- processActions updated (concatMap (\e -> e.actions) updated) - -- clear actions, filter out destroyed entities, and add the new ones - pure es {entities = map (\e -> e {actions = []}) (filter (\e -> not e.destroy) updated')} + (state', updated') <- processActions (updateState state) updated (concatMap (\e -> e.actions) updated) + -- clear actions and filter out destroyed entities + pure (es {entities = map (\e -> e {actions = []}) (filter (\e -> not e.destroy) updated')}, state') where - stateRef = es.state player = head es.entities others = tail es.entities - -- the actions can add new entities of modify existing ones - processActions :: [Entity] -> [Action] -> IO [Entity] - processActions ents (a : t) = + -- update state counters + updateState :: GS.State -> GS.State + updateState s = if s.hitDelay > 0 then s {GS.hitDelay = s.hitDelay - 1} else s + + -- the actions can change the game state, add new entities, and modify existing ones + processActions :: GS.State -> [Entity] -> [Action] -> IO (GS.State, [Entity]) + processActions s ents (a : t) = case a of - ActionAddDustEffect x y -> do - effect <- mkEffect es.sprites x y "dust" - processActions (ents ++ [effect]) t - ActionAddBattery -> do - stateRef $~ (\s -> s {GS.batteries = s.batteries + 1}) - processActions ents t + ActionAddEffect x y name -> do + effect <- mkEffect es.sprites x y name + processActions s (ents ++ [effect]) t + ActionAddBattery -> + processActions s {GS.batteries = s.batteries + 1} ents t ActionHitPlayer -> do - s <- readIORef stateRef - ents' <- - if s.lives == 1 - then do - writeIORef stateRef s {GS.lives = 0, GS.gameOverDelay = gameOverDelay} - pure $ (head ents) {dir = Dying, gravity = gravityUp, frame = 0} : tail ents - else do - writeIORef stateRef s {GS.lives = s.lives - 1, GS.hitDelay = hitDelay} - pure ents - processActions ents' t - processActions ents [] = pure ents + let (s', ents') = + if s.lives == 1 + then + ( s {GS.lives = 0, GS.gameOverDelay = gameOverDelay}, + (head ents) {dir = Dying, gravity = gravityUp, frame = 0} : tail ents + ) + else + ( s {GS.lives = s.lives - 1, GS.hitDelay = hitDelay}, + ents + ) + processActions s' ents' t + processActions s ents [] = pure (s, ents) - -- Update entities skipping enemies if the player was hit. + -- Update entities skipping enemies if the player was hit updateFilter :: Bool -> Entity -> IO Entity updateFilter False e = e.update e updateFilter True e @@ -98,10 +96,9 @@ updateAll es = do TypeEnemy -> False _ -> True --- | Render only visible entities according to the provided viewport. -renderVisible :: SDL.Renderer -> Entities -> M.Viewport -> IO () -renderVisible renderer (Entities sprites player state entities) v = - render renderer (Entities sprites player state visible) +-- | Render only visible entities according to the provided viewport and state. +renderVisible :: SDL.Renderer -> Entities -> M.Viewport -> GS.State -> IO () +renderVisible renderer (Entities sprites player entities) v = render renderer (Entities sprites player visible) where -- FIXME: entities should have size so we can be exact here and -- avoid the hardcoded size @@ -110,9 +107,10 @@ renderVisible renderer (Entities sprites player state entities) v = isVisible (M.Viewport vx vy vw vh) x y w h = x < vx + vw && vx < x + w && y < vy + vh && vy < y + h -render :: SDL.Renderer -> Entities -> IO () -render renderer es = do - state <- readIORef es.state +-- | Render all entities according to the provided state. +-- Use renderVisible to only render the entities that are in the viewport area. +render :: SDL.Renderer -> Entities -> GS.State -> IO () +render renderer es state = do -- if the player was hit, make the enemies wiggle before unfreezing if state.hitDelay == 0 || state.hitDelay > hitDelay `div` 3 then traverse_ renderOne others diff --git a/src/Game/Entities/Player.hs b/src/Game/Entities/Player.hs index 87e8a7a..a69799b 100644 --- a/src/Game/Entities/Player.hs +++ b/src/Game/Entities/Player.hs @@ -48,7 +48,7 @@ updateVertical isBlocked jump down e -- make jumping easier with "Coyote time" || (e.gravity /= gravityOff && (e.gravity < gravityDown || e.gravity > jumpLimit)) = e - | not down = e {jumping = True, gravity = gravityUp, frame = jumpFrame, actions = [ActionAddDustEffect e.x (e.y + 8)]} + | not down = e {jumping = True, gravity = gravityUp, frame = jumpFrame, actions = [ActionAddEffect e.x (e.y + 8) "dust"]} -- go down a 8 pixel tall platform; not ideal to have these values hardcoded here -- but to be fair, the player height/width is hardcoded as well | down diff --git a/src/Game/Entities/Types.hs b/src/Game/Entities/Types.hs index e22032b..dce4ffa 100644 --- a/src/Game/Entities/Types.hs +++ b/src/Game/Entities/Types.hs @@ -11,7 +11,6 @@ where import Data.IORef import qualified Game.Sprites as S -import qualified Game.State as GS data Dir = DirRight | DirLeft | Dying deriving (Eq) @@ -24,11 +23,13 @@ type IsBlocked = Int -> Int -> Bool data Entities = Entities { sprites :: S.SpriteSheet, player :: IORef Entity, - state :: IORef GS.State, entities :: [Entity] } -data Action = ActionAddDustEffect Int Int | ActionAddBattery | ActionHitPlayer deriving (Show) +-- | The effect name must match the sprite name in the spritesheet. +type EffectName = String + +data Action = ActionAddEffect Int Int EffectName | ActionAddBattery | ActionHitPlayer data Entity = Entity { typ :: Type, diff --git a/src/Game/Hud.hs b/src/Game/Hud.hs index 880de50..a5d3e22 100644 --- a/src/Game/Hud.hs +++ b/src/Game/Hud.hs @@ -1,6 +1,5 @@ module Game.Hud (Hud, mkHud, render, height) where -import Data.IORef import qualified Game.Sprites as S import qualified Game.State as GS import qualified SDL @@ -8,21 +7,16 @@ import qualified SDL height :: Int height = 16 -data Hud = Hud - { sprite :: S.Sprite, - stateRef :: IORef GS.State - } +newtype Hud = Hud S.Sprite -mkHud :: S.SpriteSheet -> IORef GS.State -> IO Hud -mkHud sprites stateRef = do - sprite <- S.get sprites "hud" - pure Hud {sprite = sprite, stateRef = stateRef} +mkHud :: S.SpriteSheet -> IO Hud +mkHud sprites = do + Hud <$> S.get sprites "hud" -render :: SDL.Renderer -> Hud -> IO () -render renderer hud = do - state <- readIORef hud.stateRef +render :: SDL.Renderer -> Hud -> GS.State -> IO () +render renderer (Hud sprite) state = do let xs = [0 .. state.totalBatteries - 1] - in mapM_ (\x -> S.render renderer hud.sprite (4 + x * 8) 4 0 (if state.batteries <= x then 0 else 1)) xs + in mapM_ (\x -> S.render renderer sprite (4 + x * 8) 4 0 (if state.batteries <= x then 0 else 1)) xs let xs = [0 .. state.totalLives - 1] in -- magic numbers - mapM_ (\x -> S.render renderer hud.sprite (320 - 4 - state.totalLives * 8 + x * 8) 4 0 (if state.lives <= x then 2 else 3)) xs + mapM_ (\x -> S.render renderer sprite (320 - 4 - state.totalLives * 8 + x * 8) 4 0 (if state.lives <= x then 2 else 3)) xs |