aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJuan J. Martinez <jjm@usebox.net>2023-02-18 16:15:12 +0000
committerJuan J. Martinez <jjm@usebox.net>2023-02-18 16:15:12 +0000
commitdbb4799f832ef3c1d943e1708c0b0193446e938f (patch)
tree0aa715fe3987c845c28e87d554fc7685318dd6e2
parentccd83b474cf99b1bea16316404c87b99c2de3fde (diff)
downloadspace-plat-hs-dbb4799f832ef3c1d943e1708c0b0193446e938f.tar.gz
space-plat-hs-dbb4799f832ef3c1d943e1708c0b0193446e938f.zip
Slimes hit enemies
-rw-r--r--src/Game.hs3
-rw-r--r--src/Game/Entities.hs72
-rw-r--r--src/Game/State.hs3
3 files changed, 54 insertions, 24 deletions
diff --git a/src/Game.hs b/src/Game.hs
index bc604ad..1e0245b 100644
--- a/src/Game.hs
+++ b/src/Game.hs
@@ -82,7 +82,8 @@ main = do
{ batteries = 0,
totalBatteries = M.totalBatteries map',
lives = maxLives,
- totalLives = maxLives
+ totalLives = maxLives,
+ hitDelay = 0
}
hud <- H.mkHud sprites state
entities <- newIORef =<< E.mkEntities sprites map' controls state
diff --git a/src/Game/Entities.hs b/src/Game/Entities.hs
index 8decf07..cfdfcd0 100644
--- a/src/Game/Entities.hs
+++ b/src/Game/Entities.hs
@@ -13,12 +13,15 @@ import qualified SDL
data Dir = DirRight | DirLeft deriving (Eq)
-data Type = TypePlayer | TypePickup | TypeEffect
+data Type = TypePlayer | TypePickup | TypeEffect | TypeEnemy
toSpriteSet :: Dir -> Int
toSpriteSet DirRight = 0
toSpriteSet DirLeft = 1
+hitDelay :: Int
+hitDelay = 72
+
frameDelay :: Int
frameDelay = 6
@@ -45,6 +48,7 @@ type IsBlocked = Int -> Int -> Bool
data Entities = Entities
{ sprites :: S.SpriteSheet,
player :: IORef Entity,
+ state :: IORef GS.State,
entities :: [Entity]
}
@@ -83,6 +87,19 @@ collision playerRef other = do
&& player.y + 12 < other.y + 16
&& other.y + 4 < player.y + 24
+-- | Update game state to reflect that the player was hit by an enemy.
+hitPlayer :: IORef GS.State -> IO ()
+hitPlayer stateRef =
+ stateRef $~ updatePlayerHit
+ where
+ updatePlayerHit :: GS.State -> GS.State
+ updatePlayerHit s = s {GS.lives = s.lives - 1, GS.hitDelay = hitDelay}
+
+-- | Update game state to reflect that the player picked up a battery.
+collectedBattery :: IORef GS.State -> IO ()
+collectedBattery stateRef =
+ stateRef $~ (\s -> s {GS.batteries = s.batteries + 1})
+
mkEntities :: S.SpriteSheet -> M.Map -> IORef C.Controls -> IORef GS.State -> IO Entities
mkEntities sprites m controls stateRef = do
player <- case find M.isPlayer (M.objects m) of
@@ -91,11 +108,11 @@ mkEntities sprites m controls stateRef = do
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 (player : entities)
+ pure $ Entities sprites playerRef stateRef (player : entities)
where
toEntity :: IORef Entity -> M.Object -> IO Entity
- toEntity playerRef (M.SlimeEntity x y) = mkSlime sprites x y playerRef (M.isBlocked m)
- toEntity playerRef (M.BatteryEntity x y) = mkBattery sprites x y playerRef stateRef
+ toEntity playerRef (M.SlimeEntity x y) = mkSlime sprites x y playerRef (M.isBlocked m) (hitPlayer stateRef)
+ toEntity playerRef (M.BatteryEntity x y) = mkBattery sprites x y playerRef (collectedBattery stateRef)
toEntity _ (M.PlayerEntity _ _) = error "Player already processed"
processSpawn :: S.SpriteSheet -> Spawn -> IO Entity
@@ -106,8 +123,16 @@ updateAll es = do
-- update the player first (including the reference)
updatedPlayer <- player.update player
_ <- writeIORef es.player updatedPlayer
+ state <- readIORef es.state
-- then the other entities
- updated <- (updatedPlayer :) <$> traverse (\e -> e.update e) others
+ updated <-
+ if state.hitDelay > 0
+ then do
+ -- if the player was hit, update state and don't update the enemies
+ _ <- writeIORef es.state state {GS.hitDelay = state.hitDelay - 1}
+ (updatedPlayer :) <$> traverse (\e -> if notEnemy e then e.update e else pure e) others
+ else -- otherwise update all
+ (updatedPlayer :) <$> traverse (\e -> e.update e) others
-- collect new entities
new <- traverse (processSpawn es.sprites) (concatMap (\e -> e.spawns) updated)
-- clear spawns (new entities), filter out destroyed entities, and add the new ones
@@ -116,11 +141,18 @@ updateAll es = do
player = head es.entities
others = tail es.entities
+ notEnemy :: Entity -> Bool
+ notEnemy ent = case ent.typ of
+ TypeEnemy -> False
+ _ -> True
+
render :: SDL.Renderer -> Entities -> IO ()
render renderer es = do
+ state <- readIORef es.state
-- always render player last
traverse_ renderOne others
- renderOne player
+ -- won't draw all the frames if the player was hit
+ if testBit state.hitDelay 2 then pure () else renderOne player
where
player = head es.entities
others = tail es.entities
@@ -156,8 +188,8 @@ updateEffect e
| e.frame + 1 < frameLimit e = e {delay = frameDelay, frame = e.frame + 1}
| otherwise = e {destroy = True}
-mkBattery :: S.SpriteSheet -> Int -> Int -> IORef Entity -> IORef GS.State -> IO Entity
-mkBattery sprites x y playerRef stateRef = do
+mkBattery :: S.SpriteSheet -> Int -> Int -> IORef Entity -> IO () -> IO Entity
+mkBattery sprites x y playerRef collectedBattery' = do
s <- S.get sprites "battery"
pure
Entity
@@ -170,19 +202,15 @@ mkBattery sprites x y playerRef stateRef = do
gravity = gravityOff,
dir = DirRight,
sprite = s,
- update = updateBattery (collision playerRef) collectedBattery,
+ update = updateBattery (collision playerRef) collectedBattery',
destroy = False,
spawns = []
}
- where
- collectedBattery :: IO ()
- collectedBattery = do
- stateRef $~ (\s -> s {GS.batteries = s.batteries + 1})
updateBattery :: Collision -> IO () -> Entity -> IO Entity
-updateBattery touchedPlayer collectedBattery e = do
+updateBattery touchedPlayer collectedBattery' e = do
touched <- touchedPlayer e
- if touched then e {destroy = True} <$ collectedBattery else pure updateBatteryFrame
+ if touched then e {destroy = True} <$ collectedBattery' else pure updateBatteryFrame
where
updateBatteryFrame :: Entity
updateBatteryFrame
@@ -190,12 +218,12 @@ updateBattery touchedPlayer collectedBattery e = do
| e.frame + 1 < frameLimit e = e {delay = frameDelay, frame = e.frame + 1}
| otherwise = e {delay = frameDelay, frame = 0}
-mkSlime :: S.SpriteSheet -> Int -> Int -> IORef Entity -> IsBlocked -> IO Entity
-mkSlime sprites x y playerRef isBlocked = do
+mkSlime :: S.SpriteSheet -> Int -> Int -> IORef Entity -> IsBlocked -> IO () -> IO Entity
+mkSlime sprites x y playerRef isBlocked hitPlayer' = do
s <- S.get sprites "slime"
pure
Entity
- { typ = TypePickup,
+ { typ = TypeEnemy,
x = x,
y = y,
delay = frameDelay,
@@ -204,16 +232,16 @@ mkSlime sprites x y playerRef isBlocked = do
gravity = gravityOff,
dir = DirRight,
sprite = s,
- update = updateSlime (collision playerRef) isBlocked,
+ update = updateSlime (collision playerRef) isBlocked hitPlayer',
destroy = False,
spawns = []
}
-updateSlime :: Collision -> IsBlocked -> Entity -> IO Entity
-updateSlime touchedPlayer isBlocked e = do
+updateSlime :: Collision -> IsBlocked -> IO () -> Entity -> IO Entity
+updateSlime touchedPlayer isBlocked hitPlayer' e = do
touched <- touchedPlayer e
let updated = updateSlimeFrame
- pure $ if touched then e {destroy = True} else updateMovement updated
+ if touched then fmap (const e) hitPlayer' else pure $ updateMovement updated
where
updateMovement :: Entity -> Entity
updateMovement ent
diff --git a/src/Game/State.hs b/src/Game/State.hs
index 2223434..d943ac0 100644
--- a/src/Game/State.hs
+++ b/src/Game/State.hs
@@ -4,5 +4,6 @@ data State = State
{ batteries :: Int,
totalBatteries :: Int,
lives :: Int,
- totalLives :: Int
+ totalLives :: Int,
+ hitDelay :: Int
}