aboutsummaryrefslogtreecommitdiff
path: root/src/Game/Entities.hs
diff options
context:
space:
mode:
authorJuan J. Martinez <jjm@usebox.net>2023-03-02 12:54:47 +0000
committerJuan J. Martinez <jjm@usebox.net>2023-03-02 12:54:47 +0000
commite5cbf917ad8b12b48932bc4fa13a044bc8159a74 (patch)
tree41511ca9f1447643ac921b2294f5dab8a62731e2 /src/Game/Entities.hs
parent5dd9180606e935ee5a7c1637773fdfc3277677ca (diff)
downloadspace-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.hs86
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