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/Entities.hs | |
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/Entities.hs')
-rw-r--r-- | src/Game/Entities.hs | 86 |
1 files changed, 42 insertions, 44 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 |