2015-07-16 02:33:19 +05:30
|
|
|
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
|
|
|
{-# LANGUAGE RecordWildCards #-}
|
2015-07-08 20:41:37 +05:30
|
|
|
module Hastron.Game.Engine where
|
|
|
|
|
2015-07-16 02:33:19 +05:30
|
|
|
import Control.Applicative (Applicative)
|
|
|
|
import Control.Monad.Identity (Identity, runIdentity)
|
|
|
|
import Control.Monad.Reader (MonadReader, ask)
|
|
|
|
import Control.Monad.RWS (MonadRWS, RWST, execRWST)
|
2015-07-16 10:25:21 +05:30
|
|
|
import Control.Monad.State (MonadState, get, put, modify)
|
2015-07-16 02:33:19 +05:30
|
|
|
import Control.Monad.Writer (MonadWriter, tell)
|
2015-07-16 10:25:21 +05:30
|
|
|
import Data.Bifunctor (second)
|
2015-07-16 02:33:19 +05:30
|
|
|
import Data.DList (DList)
|
|
|
|
import qualified Data.DList as DList
|
|
|
|
import qualified Data.HashMap.Strict as Map
|
|
|
|
import qualified Data.HashSet as Set
|
|
|
|
import Data.List (foldl')
|
2015-07-10 00:56:37 +05:30
|
|
|
import Hastron.Game.Types
|
2015-07-16 02:33:19 +05:30
|
|
|
import Prelude hiding (Left, Right)
|
2015-07-08 20:41:37 +05:30
|
|
|
|
2015-07-16 02:33:19 +05:30
|
|
|
newtype GameEngine a =
|
|
|
|
GameEngine { _runGameEngine :: RWST GameSettings (DList OutEvent) (GameMap, Player) Identity a }
|
|
|
|
deriving ( Functor
|
|
|
|
, Applicative
|
|
|
|
, Monad
|
|
|
|
, MonadReader GameSettings
|
|
|
|
, MonadWriter (DList OutEvent)
|
|
|
|
, MonadState (GameMap, Player)
|
|
|
|
, MonadRWS GameSettings (DList OutEvent) (GameMap, Player))
|
|
|
|
|
|
|
|
stepGameEngine :: GameSettings -> GameMap -> Player -> GameEngine () -> (GameMap, Player, [OutEvent])
|
|
|
|
stepGameEngine gameSettings gameMap player =
|
|
|
|
(\((gameMap', player'), outEvents) -> (gameMap', player', DList.toList outEvents))
|
|
|
|
. runIdentity
|
|
|
|
. (\rwst -> execRWST rwst gameSettings (gameMap, player))
|
|
|
|
. _runGameEngine
|
|
|
|
|
2015-07-22 19:44:01 +05:30
|
|
|
move :: TimeInterval -> GameEngine ()
|
2015-07-16 02:33:19 +05:30
|
|
|
move timeElapsed = do
|
|
|
|
state <- get
|
|
|
|
settings <- ask
|
|
|
|
go settings state
|
2015-07-14 09:37:20 +05:30
|
|
|
where
|
2015-07-16 10:25:21 +05:30
|
|
|
go GameSettings{ .. } (gameMap@GameMap{ .. }, player@Player{ .. })
|
2015-07-16 02:33:19 +05:30
|
|
|
| playerState /= PlayerAlive || timeElapsed < 0 = return ()
|
2015-07-16 10:25:21 +05:30
|
|
|
| not boostActive || boostFuel >= timeElapsed = move'
|
2015-07-16 02:33:19 +05:30
|
|
|
| otherwise =
|
|
|
|
move boostFuel >> move (timeElapsed - boostFuel)
|
|
|
|
where
|
|
|
|
(x, y) = playerPosition
|
|
|
|
(Velocity speed dir) = playerVelocity
|
2015-07-22 19:44:01 +05:30
|
|
|
PlayerBoost{ .. } = playerBoost
|
2015-07-16 02:33:19 +05:30
|
|
|
dist =
|
|
|
|
timeElapsed * speed * (if boostActive && boostFuel > 0 then gameBoostFactor else 1)
|
|
|
|
|
2015-07-16 10:25:21 +05:30
|
|
|
makeTrail Left = tail [(x', y) | x' <- [x, x - 1 .. x - dist]]
|
|
|
|
makeTrail Right = tail [(x', y) | x' <- [x .. x + dist]]
|
|
|
|
makeTrail Up = tail [(x, y') | y' <- [y, y - 1 .. y - dist]]
|
|
|
|
makeTrail Down = tail [(x, y') | y' <- [y .. y + dist]]
|
2015-07-16 02:33:19 +05:30
|
|
|
|
|
|
|
checkTrail trail =
|
|
|
|
let trail' = takeWhile (not . flip Set.member gameMapBlockedPoints) trail
|
|
|
|
in if trail' == trail
|
|
|
|
then (trail, PlayerAlive)
|
|
|
|
else (trail', PlayerDead)
|
|
|
|
|
2015-07-16 10:25:21 +05:30
|
|
|
move' = do
|
2015-07-16 02:33:19 +05:30
|
|
|
let
|
2015-07-16 10:25:21 +05:30
|
|
|
revTrail = makeTrail dir
|
2015-07-16 02:33:19 +05:30
|
|
|
(checkedTrail, playerState') = checkTrail revTrail
|
|
|
|
trail = reverse checkedTrail
|
|
|
|
pos' = if null trail then playerPosition else head trail
|
|
|
|
boostFuel' = if boostActive then boostFuel - timeElapsed else boostFuel
|
|
|
|
|
|
|
|
player' = player { playerState = playerState'
|
|
|
|
, playerPosition = pos'
|
|
|
|
, playerTrail = trail ++ playerTrail
|
|
|
|
, playerBoost = playerBoost { boostFuel = boostFuel' }
|
|
|
|
}
|
|
|
|
gameMap' = gameMap { gameMapBlockedPoints =
|
|
|
|
foldl' (flip Set.insert) gameMapBlockedPoints trail }
|
|
|
|
outEvents = [OutPlayerStateChange playerId playerState' | playerState /= playerState']
|
2015-07-14 09:37:20 +05:30
|
|
|
|
2015-07-16 02:33:19 +05:30
|
|
|
put (gameMap', player')
|
|
|
|
tell $ DList.fromList outEvents
|
2015-07-10 00:56:37 +05:30
|
|
|
|
2015-07-16 10:25:21 +05:30
|
|
|
leftTurn :: Direction -> Direction
|
|
|
|
leftTurn Left = Down
|
|
|
|
leftTurn Right = Up
|
|
|
|
leftTurn Up = Left
|
|
|
|
leftTurn Down = Right
|
|
|
|
|
|
|
|
rightTurn :: Direction -> Direction
|
|
|
|
rightTurn Left = Up
|
|
|
|
rightTurn Right = Down
|
|
|
|
rightTurn Up = Right
|
|
|
|
rightTurn Down = Left
|
|
|
|
|
2015-07-16 02:33:19 +05:30
|
|
|
turn :: (Direction -> Direction) -> GameEngine ()
|
2015-07-16 10:25:21 +05:30
|
|
|
turn turnFn = modify . second $ \player@Player{ playerVelocity = Velocity speed dir } ->
|
|
|
|
player { playerVelocity = Velocity speed $ turnFn dir }
|
2015-07-15 18:24:43 +05:30
|
|
|
|
2015-07-16 02:33:19 +05:30
|
|
|
changeBoost :: Bool -> GameEngine ()
|
2015-07-16 10:25:21 +05:30
|
|
|
changeBoost boostActive = modify . second $ \player@Player{ .. } ->
|
|
|
|
player { playerBoost = playerBoost { boostActive = boostActive } }
|
2015-07-10 00:56:37 +05:30
|
|
|
|
2015-07-22 19:44:01 +05:30
|
|
|
refillBoost :: Timestamp -> GameEngine ()
|
2015-07-16 02:33:19 +05:30
|
|
|
refillBoost timeElapsed = do
|
2015-07-16 10:25:21 +05:30
|
|
|
(gameMap, player@Player{ .. }) <- get
|
|
|
|
GameSettings{ .. } <- ask
|
|
|
|
let boostFuel' = floor (fromIntegral timeElapsed * gameBoostRefillFactor)
|
2015-07-22 19:44:01 +05:30
|
|
|
playerBoost' = playerBoost { boostFuel = boostFuel playerBoost + boostFuel' }
|
2015-07-16 10:25:21 +05:30
|
|
|
put (gameMap, player { playerBoost = playerBoost' })
|
|
|
|
tell $ DList.fromList [OutPlayerBoostChange playerId playerBoost' | playerBoost /= playerBoost']
|
2015-07-10 00:56:37 +05:30
|
|
|
|
2015-07-22 19:44:01 +05:30
|
|
|
stepGame :: Game -> Timestamp -> InEvent -> (Game, [OutEvent])
|
2015-07-16 10:25:21 +05:30
|
|
|
stepGame game@Game{ gameMap = gameMap@GameMap{ .. }, .. } time = stepGame'
|
2015-07-10 00:56:37 +05:30
|
|
|
where
|
2015-07-16 02:33:19 +05:30
|
|
|
stepGame' (InPlayerTurnLeft playerId) = stepEvent playerId $ turn leftTurn
|
|
|
|
stepGame' (InPlayerTurnRight playerId) = stepEvent playerId $ turn rightTurn
|
2015-07-22 19:44:01 +05:30
|
|
|
stepGame' (InPlayerIdle playerId) = stepEvent playerId $ return ()
|
2015-07-16 02:33:19 +05:30
|
|
|
stepGame' (InPlayerBoostChange playerId boostActive) = stepEvent playerId $ changeBoost boostActive
|
|
|
|
|
|
|
|
stepEvent pId step =
|
2015-07-16 10:25:21 +05:30
|
|
|
flip (maybe (game, [])) (Map.lookup pId gamePlayers) $ \player@Player{ .. } ->
|
2015-07-10 00:56:37 +05:30
|
|
|
if playerState /= PlayerAlive
|
|
|
|
then (game, [])
|
|
|
|
else let
|
2015-07-16 10:25:21 +05:30
|
|
|
timeElapsed = time - playerLastEventTime
|
2015-07-16 02:33:19 +05:30
|
|
|
fullStep = move timeElapsed >> step >> refillBoost timeElapsed
|
|
|
|
|
|
|
|
(gameMap', player'@Player{ playerPosition = pos'
|
|
|
|
, playerVelocity = Velocity _ dir' }, outEvents) =
|
|
|
|
stepGameEngine gameSettings gameMap player fullStep
|
|
|
|
|
|
|
|
player'' = player' { playerScore = playerScore + score playerPosition pos'
|
|
|
|
, playerLastEventTime = time }
|
|
|
|
game' = game { gamePlayers = Map.insert playerId player'' gamePlayers
|
|
|
|
, gameMap = gameMap' }
|
|
|
|
outEvents' = outEvents ++ [OutPlayerPosition playerId pos' dir']
|
|
|
|
in (game', outEvents')
|
2015-07-10 00:56:37 +05:30
|
|
|
|
|
|
|
score (x1, y1) (x2, y2) = abs (x1 - x2) + abs (y1 - y2)
|
|
|
|
|
2015-07-22 19:44:01 +05:30
|
|
|
runGame :: Game -> [(Timestamp, InEvent)] -> (Game, [OutEvent])
|
2015-07-16 02:33:19 +05:30
|
|
|
runGame initialGame =
|
2015-07-15 18:24:43 +05:30
|
|
|
foldl (\(game, outEvents) (time, inEvent) ->
|
|
|
|
fmap (outEvents ++) $ stepGame game time inEvent)
|
2015-07-16 02:33:19 +05:30
|
|
|
(initialGame, [])
|