nyx-game-0.1.0.0: app/GameState.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TemplateHaskell #-}
module GameState where
import qualified SDL
import qualified SDL.Font as SDLF
import qualified SDL.Mixer as Mix
import qualified Play.Engine.MySDL.MySDL as MySDL
import Play.Engine
import Control.Monad
import Control.Monad.Except
import Control.Lens
import Data.Maybe
import Data.Foldable
import System.Random
import qualified Play.Engine.Load as Load
import qualified Control.Monad.State as SM
import qualified Data.DList as DL
import qualified Data.Map as M
import qualified Script
import Bullet hiding (update, render)
import qualified Bullet
import qualified ShootingBox as SB
import qualified Enemy as Enemy
import qualified DecorationObject as DO
import qualified Play.Engine.ScrollingBackground as SBG
import qualified Play.Engine.Sprite as Spr
data State
= State
{ _bg :: SBG.SBG
, _bga :: Spr.Sprite
, _mc :: SB.MainChar
, _enemies :: [Enemy.Enemy]
, _mcBullets :: DL.DList Bullet
, _enemyBullets :: DL.DList Bullet
, _decObjs :: DL.DList DO.DecorationObject
, _resources :: MySDL.Resources
, _script :: Script.Script
, _camera :: Int
, _restart :: Scene
, _isPause :: !Bool
, _pauseChanged :: !Bool
, _isMute :: !Bool
, _hudFont :: SDLF.Font
, _exit :: !Bool
}
makeFieldsNoPrefix ''State
wantedAssets :: [(String, MySDL.ResourceType FilePath)]
wantedAssets =
[ ("bg", MySDL.Texture "background.png")
, ("unispace", MySDL.Font "unispace/unispace.ttf")
--, ("bga", MySDL.Texture "bga.png")
]
++ SB.wantedAssets
mkGameState :: Script.ScriptData -> Scene
mkGameState sd = Load.mkState 30 (wantedAssets ++ Script.assets sd) (mkState sd)
mkState
:: Script.ScriptData
-> MySDL.Resources -> Result Scene
mkState sd rs = do
state <- initState sd (Script.script sd rs) rs
pure $ mkScene
state
update
render
initState :: Script.ScriptData -> Script.Script -> MySDL.Resources -> Result State
initState sd scrpt rs = do
case (,) <$> M.lookup "bg" (MySDL.textures rs) <*> M.lookup "unispace" (MySDL.fonts rs) of
Nothing ->
throwError ["Texture or font not found: bg / unispace"]
Just (bgt, font) -> do
mc' <- (SB.mkMainChar $ MySDL.textures rs)
pure $ State
(SBG.mkSBG bgt 1 (Point 800 1000) (Point 0 0))
( maybe undefined id $ Spr.make $ Spr.MakeArgs
{ mkActionmap = ["normal"]
, mkAction = "normal"
, mkTexture = bgt
, mkSize = Point 800 1000
, mkMaxPos = 4
, mkSpeed = 2
}
)
mc'
[]
(DL.fromList [])
(DL.fromList [])
(DL.fromList [])
rs
scrpt
0
(Script.restart sd)
False
False
False
font
False
initEnemyTimer :: Int
initEnemyTimer = 60
update :: Input -> State -> Result (StackCommand, State)
update input state = do
wSize <- _windowSize <$> SM.get
ismute <- _muteMusic <$> SM.get
(acts, script') <- Script.update input (SB.get (state ^. mc) pos) (state ^. enemies) (state ^. script)
(mc', addMCBullets) <-
SB.update
(maybe input (dirToInput . dirToPlace (state ^. mc . pos)) (Script.moveMC acts))
(state ^. mc)
(enemies', addEnemiesBullets, newDecObjs) <-
(\(a, b, c) ->
( mconcat a
, foldr (.) id b
, foldr (\curr acc -> DL.fromList curr `DL.append` acc) DL.empty c
)
) . unzip3
<$> traverse (Enemy.update input) (state ^. enemies)
let
(mcBullets', _enemiesHit) =
updateListWith
M.empty
(const $ const M.empty)
(Bullet.update wSize isTouchingCircleRect (state ^. enemies))
(state ^. mcBullets)
(enemyBullets', (concat . map snd . M.toList) -> mcHits) =
updateListWith
M.empty
M.union
(Bullet.update wSize isTouchingCircleRect $ maybe [] (:[]) $ SB.get (state ^. mc) id)
(state ^. enemyBullets)
updatedDecObjs <- updateDecObjs input (state ^. decObjs)
let
newState =
state'
& set pauseChanged False
& set isMute ismute
& set script script'
& over bg SBG.updateSBG
& over camera
(\c ->
if
| c <= 0 && Script.shake acts -> 60
| c <= 0 -> 0
| otherwise -> c - 1
)
& over bga
( case Script.changeSprite acts of
Nothing -> Spr.update Nothing False
Just sp -> const sp
)
where
state' =
if Script.stopTheWorld acts
then
state
& set mcBullets mcBullets'
& set enemyBullets enemyBullets'
else
state
& set mc (SB.checkHit mcHits mc')
& set enemies enemies'
& over enemies ((++) (Script.spawn acts) . map (Enemy.checkHit mcBullets'))
& set mcBullets (addMCBullets mcBullets')
& set enemyBullets (addEnemiesBullets enemyBullets')
& set decObjs (updatedDecObjs `DL.append` newDecObjs)
if
| keyReleased KeyC input -> do
pure (Replace $ state ^. restart, state)
| keyReleased KeyP input && state ^. isPause -> do
pure (None, set pauseChanged True $ set isPause False state)
| keyReleased KeyP input && not (state ^. isPause) -> do
pure (None, set pauseChanged True $ set isPause True state)
| state ^. isPause -> do
pure (None, state)
| keyReleased KeyQuit input -> do
pure (None, set exit True state)
| state ^. exit -> do
pure (Done, state)
| otherwise ->
pure (Script.command acts, newState)
updateDecObjs :: Input -> DL.DList DO.DecorationObject -> Result (DL.DList DO.DecorationObject)
updateDecObjs input =
foldrM
( \(DO.DecObj obj@(DO.DecObj'{..})) acc -> do
(r, objs) <- _update input _state
pure $ DL.append
(DL.fromList $ maybeToList (fmap (DO.DecObj . flip (set DO.state) obj) r) ++ objs)
acc
)
DL.empty
flipEnemyDir :: Either () () -> Either () ()
flipEnemyDir = \case
Right () -> Left ()
Left () -> Right ()
render :: SDL.Renderer -> State -> IO ()
render renderer state = do
cam' <- Point <$> randomRIO (-1, 1) <*> randomRIO (-1, 1) :: IO FPoint
let cam = addPoint $ fmap (floor . (*) (fromIntegral $ state ^. camera `div` 3)) cam'
SBG.render renderer cam (state ^. bg)
--Spr.render renderer cam (Point 0 0) (state ^. bga . size) (state ^. bga)
SB.render renderer cam (state ^. mc)
void $ traverse (Enemy.render renderer cam) (state ^. enemies)
forM_ (state ^. decObjs) (\(DO.DecObj (DO.DecObj'{..})) -> _render renderer cam _state)
forM_ (state ^. mcBullets) (Bullet.render renderer cam)
forM_ (state ^. enemyBullets) (Bullet.render renderer cam)
when (state ^. isMute) $
renderText renderer (state ^. hudFont) (Point 40 30) "MUTED"
Script.render renderer cam (state ^. script)
when (state ^. isPause)
$ renderText renderer (state ^. hudFont) (Point 370 380) "PAUSE"
when (state ^. pauseChanged) $
if (state ^. isPause)
then
Mix.pauseMusic
else
Mix.resumeMusic
when (state ^. exit) $
Mix.haltMusic
dirToInput :: IPoint -> Input
dirToInput dir =
Input ks []
where
ks = M.fromList $ map (,Click)
$ (if dir ^. x > 0 then [KeyRight] else if dir ^. x < 0 then [KeyLeft] else [])
++ (if dir ^. y > 0 then [KeyUp] else if dir ^. y < 0 then [KeyDown] else [])