nomyx-core-1.0.0: src/Nomyx/Core/Engine/GameEvents.hs
{-# LANGUAGE GADTs #-}
{-# LANGUAGE TemplateHaskell #-}
-- | This module implements the events that can affect a game.
module Nomyx.Core.Engine.GameEvents where
import Prelude hiding (log)
import Data.List
import Data.Time
import Data.Maybe
import Control.Monad.State
import Control.Lens
import Control.Category ((>>>))
import Control.Exception as E
import System.Log.Logger
import Nomyx.Language.Types
import Nomyx.Core.Engine.Evaluation
import Nomyx.Core.Engine.Utils
import Nomyx.Core.Engine.Types
import Nomyx.Core.Engine.Interpret
import Imprevu.Evaluation hiding (runEvalError)
-- | perform a game event
enactEvent :: GameEvent -> Maybe Rule -> StateT Game IO ()
enactEvent (JoinGame pn name) _ = joinGame name pn
enactEvent (LeaveGame pn) _ = leaveGame pn
enactEvent (ProposeRuleEv Propose pn rt ms) mr = proposeRule rt ms pn mr
enactEvent (ProposeRuleEv SystemAdd _ rt ms) mr = systemAddRule rt ms mr
enactEvent (ProposeRuleEv Check _ _ _) _ = return ()
enactEvent (ProposeRuleEv _ _ _ _) Nothing = error "ProposeRuleEv: interpreter function needed"
enactEvent (InputResult pn en is id) _ = inputResult pn en is id
enactEvent (GLog mpn s) _ = logGame s mpn
enactEvent (TimeEvent t) _ = timeEvent t
enactTimedEvent :: Maybe Rule -> TimedEvent -> StateT Game IO ()
enactTimedEvent mr (TimedEvent t ge) = flip stateCatch updateError $ do
currentTime .= t
enactEvent ge mr
lg <- get
lift $ evaluate lg
return ()
updateError :: SomeException -> StateT Game IO ()
updateError e = do
warn 0 $ "IO error: " ++ show e
logGame ("IO error: " ++ show e) Nothing
execGameEvent :: GameEvent -> StateT LoggedGame IO ()
execGameEvent = execGameEvent' Nothing
execGameEvent' :: Maybe Rule -> GameEvent -> StateT LoggedGame IO ()
execGameEvent' inter ge = do
t <- use (game . currentTime)
let te = TimedEvent t ge
gameLog %= \gl -> gl ++ [te]
zoom game $ enactTimedEvent inter te
getLoggedGame :: Game -> [TimedEvent] -> IO LoggedGame
getLoggedGame g tes = do
let a = mapM_ (enactTimedEvent Nothing) tes
g' <- execStateT a g
return $ LoggedGame g' tes
-- | join the game.
joinGame :: PlayerName -> PlayerNumber -> StateT Game IO ()
joinGame name pn = do
g <- get
case find ((== pn) . view playerNumber) (_players g) of
Just _ -> return ()
Nothing -> do
let player = PlayerInfo { _playerNumber = pn, _playerName = name, _playingAs = Nothing}
players %= (player : )
void $ mapStateIO $ runSystemEval pn $ triggerEvent (Signal Arrive) player
-- | leave the game.
leaveGame :: PlayerNumber -> StateT Game IO ()
leaveGame pn = void $ mapStateIO $ runSystemEval pn $ void $ evDelPlayer pn
-- | insert a rule in pending rules.
proposeRule :: RuleTemplate -> [ModuleInfo] -> PlayerNumber -> Maybe Rule -> StateT Game IO ()
proposeRule rt ms pn mr = do
rule <- createRule rt ms pn mr
r <- mapStateIO $ runEvalError (_rNumber rule) (Just pn) (evProposeRule rule) --do --bug here
case r of
Just _ -> info pn "Your rule has been added to pending rules."
Nothing -> warn pn "Error: Rule could not be proposed"
-- | add a rule forcefully (no votes etc.)
systemAddRule :: RuleTemplate -> [ModuleInfo] -> Maybe Rule -> StateT Game IO ()
systemAddRule rt ms mr = do
rule <- createRule rt ms 0 mr
let sysRule = (rStatus .~ Active) >>> (rAssessedBy .~ Just 0)
rules %= (sysRule rule : )
void $ mapStateIO $ runEvalError (_rNumber rule) Nothing $ evalNomex (_rRule rule)
-- | insert a log message.
logGame :: String -> Maybe PlayerNumber -> StateT Game IO ()
logGame s mpn = do
time <- use currentTime
void $ logs %= (Log mpn time s : )
-- | the user has provided an input result
inputResult :: PlayerNumber -> EventNumber -> Input -> InputData -> StateT Game IO ()
inputResult pn en is id = do
debug pn $ "input result: input " ++ (show is) ++ " input data " ++ (show id)
evs <- gets _events
let rn = _erRuleNumber $ fromJust $ find (\(RuleEventInfo rn (EventInfo en' _ _ _ _)) -> en' == en) evs
void $ mapStateIO $ runEvalError rn (Just pn) $ triggerInput is id
timeEvent :: UTCTime -> StateT Game IO ()
timeEvent t = void $ mapStateIO $ runSystemEval' $ evTriggerTime t
-- | A helper function to run the game state.
-- It additionally sets the current time
execWithGame :: UTCTime -> State LoggedGame () -> LoggedGame -> LoggedGame
execWithGame t gs g = execState gs $ set (game . currentTime) t g
execWithGame' :: UTCTime -> StateT LoggedGame IO () -> LoggedGame -> IO LoggedGame
execWithGame' t gs g = execStateT gs $ set (game . currentTime) t g
activeRules :: Game -> [RuleInfo]
activeRules = sort . filter ((==Active) . view rStatus) . _rules
pendingRules :: Game -> [RuleInfo]
pendingRules = sort . filter ((==Pending) . view rStatus) . _rules
rejectedRules :: Game -> [RuleInfo]
rejectedRules = sort . filter ((==Reject) . view rStatus) . _rules
createRule :: RuleTemplate -> [ModuleInfo] -> PlayerNumber -> Maybe Rule -> StateT Game IO RuleInfo
createRule (RuleTemplate name des code author _ _ decls) ms pn mr = do
rs <- use rules
let rn = head $ [1..] \\ (map _rNumber rs)
g <- get
r <- case mr of
-- If the rule has already been interpreted, use it
Just r -> return r
-- re-interpret the rule (in the case of loading game from logs)
Nothing -> lift $ interpretRule' code ms
info pn $ "Creating rule n=" ++ show rn ++ " code=" ++ code
let ruleTemplate = RuleTemplate {_rName = name,
_rDescription = des,
_rRuleCode = code,
_rAuthor = author,
_rPicture = Nothing,
_rCategory = [],
_rDeclarations = []} --Declarations are transfered in the RuleInfo
return RuleInfo {_rNumber = rn,
_rProposedBy = pn,
_rRule = r,
_rStatus = Pending,
_rAssessedBy = Nothing,
_rRuleTemplate = ruleTemplate,
_rModules = ms}
stateCatch :: Exception e => StateT Game IO a -> (e -> StateT Game IO a) -> StateT Game IO a
stateCatch m h = StateT $ \s -> runStateT m s `E.catch` \e -> runStateT (h e) s
getEventInfo :: EventNumber -> LoggedGame -> EventInfo
getEventInfo en g = _erEventInfo $ fromJust $ find ((== en) . view (erEventInfo . eventNumber)) (_events $ _game g)
warn, info :: (MonadIO m) => Int -> String -> m ()
info pn s = liftIO $ infoM "Nomyx.Core.GameEvent" ("Player " ++ (show pn) ++ " " ++ s)
warn pn s = liftIO $ warningM "Nomyx.Core.GameEvent" ("Player " ++ (show pn) ++ " " ++ s)
debug pn s = liftIO $ debugM "Nomyx.Core.GameEvent" ("Player " ++ (show pn) ++ " " ++ s)