Nomyx-Core-0.6.0: src/Nomyx/Core/Engine/Evaluation.hs
{-# LANGUAGE GADTs #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE StandaloneDeriving #-}
module Nomyx.Core.Engine.Evaluation where
import Prelude hiding ((.), log)
import Control.Monad
import Control.Monad.State
import Control.Monad.Reader
import Data.List
import Data.Typeable
import Data.Function hiding ((.))
import Data.Time
import Data.Lens
import Data.Maybe
import Control.Category hiding (id)
import Control.Applicative
import Control.Monad.Error (ErrorT(..))
import Control.Monad.Error.Class (MonadError(..))
import Language.Nomyx.Expression
import Nomyx.Core.Engine.Game
import Nomyx.Core.Engine.Utils
import Safe
type Evaluate a = ErrorT String (State Game) a
-- data sent back by the forms
data InputData = RadioData Int
| CheckboxData [Int]
| TextData String
| TextAreaData String
| ButtonData
deriving (Show, Read, Eq, Ord)
-- | evaluate an expression.
-- The rule number passed is the number of the rule containing the expression.
evalNomex :: Nomex a -> RuleNumber -> Evaluate a
evalNomex (NewVar name def) rn = do
vars <- access variables
case find ((== name) . getL vName) vars of
Nothing -> do
variables %= (Var rn name def : )
return $ Just (V name)
Just _ -> return Nothing
evalNomex (DelVar (V name)) _ = do
vars <- access variables
case find ((== name) . getL vName) vars of
Nothing -> return False
Just _ -> do
variables %= filter ((/= name) . getL vName)
return True
evalNomex (WriteVar (V name) val) _ = do
vars <- access variables
case find (\(Var _ myName _) -> myName == name) vars of
Nothing -> return False
Just (Var rn myName _) -> do
variables %= replaceWith ((== name) . getL vName) (Var rn myName val)
return True
evalNomex (OnEvent event handler) rn = do
evs <- access events
let en = getFreeNumber (map _eventNumber evs)
events %= (EventInfo en rn (indexInputs event) handler SActive [] : )
return en
evalNomex (DelEvent en) _ = evDelEvent en
evalNomex (SendMessage (Msg id) myData) _ = triggerEvent (Message (Msg id)) myData
evalNomex (NewOutput pn s) rn = evNewOutput pn rn s
evalNomex (UpdateOutput on s) _ = evUpdateOutput on s
evalNomex (DelOutput on) _ = evDelOutput on
evalNomex (ProposeRule rule) _ = evProposeRule rule
evalNomex (ActivateRule rule) rn = evActivateRule rule rn
evalNomex (RejectRule rule) rn = evRejectRule rule rn
evalNomex (AddRule rule) _ = evAddRule rule
evalNomex (ModifyRule mod rule) _ = evModifyRule mod rule
evalNomex (SetPlayerName pn n) _ = evChangeName pn n
evalNomex (DelPlayer pn) _ = evDelPlayer pn
evalNomex (LiftEffect e) pn = liftEval $ evalNomexNE e pn
evalNomex (ThrowError s) _ = throwError s
evalNomex (CatchError n h) rn = catchError (evalNomex n rn) (\a -> evalNomex (h a) rn)
evalNomex (SetVictory ps) rn = do
void $ victory ~= (Just $ VictoryCond rn ps)
triggerEvent Victory (VictoryCond rn ps)
evalNomex (Return a) _ = return a
evalNomex (Bind exp f) rn = do
e <- evalNomex exp rn
evalNomex (f e) rn
liftEval :: Reader Game a -> Evaluate a
liftEval r = runReader r <$> get
evalNomexNE :: NomexNE a -> RuleNumber -> Reader Game a
evalNomexNE (ReadVar (V name)) _ = do
vars <- asks _variables
let var = find ((== name) . getL vName) vars
case var of
Nothing -> return Nothing
Just (Var _ _ val) -> case cast val of
Just v -> return $ Just v
Nothing -> return Nothing
evalNomexNE (GetOutput on) _ = evGetOutput on
evalNomexNE GetRules _ = asks _rules
evalNomexNE GetPlayers _ = asks _players
evalNomexNE SelfRuleNumber rn = return rn
evalNomexNE (CurrentTime) _ = asks _currentTime
evalNomexNE (Return a) _ = return a
evalNomexNE (Bind exp f) rn = do
e <- evalNomexNE exp rn
evalNomexNE (f e) rn
evalNomexNE (Simu sim ev) rn = do
let s = runEvalError Nothing (evalNomex sim rn)
g <- ask
let g' = execState s g
return $ runReader (evalNomexNE ev rn) g'
getVictorious :: Game -> [PlayerNumber]
getVictorious g = case _victory g of
Nothing -> []
Just (VictoryCond rn v) -> runReader (evalNomexNE v rn) g
evalOutput :: Game -> Output -> String
evalOutput g (Output _ rn _ o _) = runReader (evalNomexNE o rn) g
allOutputs :: Game -> [String]
allOutputs g = map (evalOutput g) (_outputs g)
triggerEvent :: (Typeable e, Show e) => Field e -> e -> Evaluate ()
triggerEvent e dat = do
evs <- access events
triggerEvent' e dat evs
-- receive a field data
triggerEvent' :: (Typeable e, Show e) => Field e -> e -> [EventInfo] -> Evaluate ()
triggerEvent' e dat evs = do
let evs' = map (updateEventInfo e dat) evs
events %= union (map fst evs')
mapM triggerIfComplete evs'
return ()
updateEventInfo :: (Typeable a, Show a) => Field a -> a -> EventInfo -> (EventInfo, Maybe SomeData)
updateEventInfo field dat ei@(EventInfo _ _ ev _ _ envi) =
if (SomeField field) `elem` (getEventFields ev envi) -- if the field if found among the remaining fields of the event
then case getEventResult ev (eventRes : envi) of -- then check the event with that field result included
BE (Left _) -> (env ^= (eventRes : envi) $ ei, Nothing) -- some fields are left to complete: add ours in the environment
BE (Right a) -> (env ^= [] $ ei, Just $ SomeData a) -- the event is now complete: empty the environment and set the handler to be triggered
else (ei, Nothing) -- field not found: do nothing
where eventRes = EventEnv field dat
data SomeData = forall e. (Typeable e, Show e) => SomeData e
deriving instance Show SomeData
-- in the case an event is complete, trigger its handler
triggerIfComplete :: (EventInfo, Maybe SomeData) -> Evaluate ()
triggerIfComplete (EventInfo en rn _ h SActive _, Just (SomeData val)) = do
case (cast val) of
Just a -> do
let exp = h (en, a)
(evalNomex exp rn) `catchError` (errorHandler rn en)
Nothing -> error "Bad trigger data type"
triggerIfComplete _ = return ()
-- compute the result of an event given an environment.
-- in the case the event cannot be computed because some fields results are pending, return that list instead.
getEventResult :: Event a -> [EventEnv] -> BEither [SomeField] a
getEventResult (PureEvent a) _ = BE (Right a)
getEventResult EmptyEvent _ = BE (Left [])
getEventResult (SumEvent a b) ers = (getEventResult a ers) <|> (getEventResult b ers)
getEventResult (AppEvent f b) ers = (getEventResult f ers) <*> (getEventResult b ers)
getEventResult (BaseEvent a) ers = case lookupField a ers of
Just r -> BE (Right r)
Nothing -> BE (Left [SomeField a])
-- find a field result in an environment
lookupField :: Typeable a => Field a -> [EventEnv] -> Maybe a
lookupField _ [] = Nothing
lookupField be (EventEnv a r : ers) = case (cast (a,r)) of
Just (a',r') -> if (a' == be) then Just r' else lookupField be ers
Nothing -> lookupField be ers
--get the fields lefft to be completed in an event
getEventFields :: Event a -> [EventEnv] -> [SomeField]
getEventFields e er = case (getEventResult e er) of
BE (Right _) -> []
BE (Left a) -> a
errorHandler :: RuleNumber -> EventNumber -> String -> Evaluate ()
errorHandler rn en s = logAll $ "Error in rule " ++ show rn ++ " (triggered by event " ++ show en ++ "): " ++ s
-- trigger the input event with the input data
triggerInput :: EventNumber -> InputNumber -> InputData -> Evaluate ()
triggerInput en inn ir = do
evs <- access events
let mei = find ((== en) . getL eventNumber) evs
when (isJust mei) $ execInputHandler ir inn (fromJust mei)
execInputHandler :: InputData -> InputNumber -> EventInfo -> Evaluate ()
execInputHandler ir inn ei = do
case (getInput ei inn) of
Just sf -> execInputHandler' ir sf ei
Nothing -> error "Input not found"
getInput :: EventInfo -> InputNumber -> Maybe SomeField
getInput (EventInfo _ _ ev _ _ env) inn = find isInput (getEventFields ev env) where
isInput (SomeField (Input (Just n) _ _ _)) | n == inn = True
isInput _ = False
-- execute the event handler using the data received from user
execInputHandler' :: InputData -> SomeField -> EventInfo -> Evaluate ()
execInputHandler' (TextData s) (SomeField e@(Input _ _ _ (Text))) ei = triggerEvent' e s [ei]
execInputHandler' (TextAreaData s) (SomeField e@(Input _ _ _ (TextArea))) ei = triggerEvent' e s [ei]
execInputHandler' (ButtonData) (SomeField e@(Input _ _ _ (Button))) ei = triggerEvent' e () [ei]
execInputHandler' (RadioData i) (SomeField e@(Input _ _ _ (Radio cs))) ei = triggerEvent' e (fst $ cs!!i) [ei]
execInputHandler' (CheckboxData is) (SomeField e@(Input _ _ _ (Checkbox cs))) ei = triggerEvent' e (fst <$> cs `sel` is) [ei]
execInputHandler' _ _ _ = return ()
evProposeRule :: RuleInfo -> Evaluate Bool
evProposeRule rule = do
rs <- access rules
case find ((== (rNumber ^$ rule)) . getL rNumber) rs of
Nothing -> do
rules %= (rule:)
triggerEvent (RuleEv Proposed) rule
return True
Just _ -> return False
--Sets the rule status to Active and execute it if possible
evActivateRule :: RuleNumber -> RuleNumber -> Evaluate Bool
evActivateRule rn by = do
rs <- access rules
case find (\r -> _rNumber r == rn && _rStatus r /= Active) rs of
Nothing -> return False
Just r -> do
let newrules = replaceWith ((== rn) . getL rNumber) r{_rStatus = Active, _rAssessedBy = Just by} rs
rules ~= newrules
--execute the rule
evalNomex (_rRule r) rn
triggerEvent (RuleEv Activated) r
return True
evRejectRule :: RuleNumber -> RuleNumber -> Evaluate Bool
evRejectRule rn by = do
rs <- access rules
case find (\r -> _rNumber r == rn && _rStatus r /= Reject) rs of
Nothing -> return False
Just r -> do
let newrules = replaceWith ((== rn) . getL rNumber) r{_rStatus = Reject, _rAssessedBy = Just by} rs
rules ~= newrules
triggerEvent (RuleEv Rejected) r
delVarsRule rn
delEventsRule rn
delOutputsRule rn
return True
evAddRule :: RuleInfo -> Evaluate Bool
evAddRule rule = do
rs <- access rules
case find ((== (rNumber ^$ rule)) . getL rNumber) rs of
Nothing -> do
rules %= (rule:)
triggerEvent (RuleEv Added) rule
return True
Just _ -> return False
--TODO: clean and execute new rule
evModifyRule :: RuleNumber -> RuleInfo -> Evaluate Bool
evModifyRule mod rule = do
rs <- access rules
let newRules = replaceWith ((== mod) . getL rNumber) rule rs
case find ((== mod) . getL rNumber) rs of
Nothing -> return False
Just r -> do
rules ~= newRules
triggerEvent (RuleEv Modified) r
return True
addPlayer :: PlayerInfo -> Evaluate Bool
addPlayer pi = do
pls <- access players
let exists = any (((==) `on` _playerNumber) pi) pls
unless exists $ do
players %= (pi:)
triggerEvent (Player Arrive) pi
return $ not exists
evDelPlayer :: PlayerNumber -> Evaluate Bool
evDelPlayer pn = do
g <- get
case find ((== pn) . getL playerNumber) (_players g) of
Nothing -> do
tracePN pn "not in game!"
return False
Just pi -> do
players %= filter ((/= pn) . getL playerNumber)
triggerEvent (Player Leave) pi
tracePN pn $ "leaving the game: " ++ _gameName g
return True
evChangeName :: PlayerNumber -> PlayerName -> Evaluate Bool
evChangeName pn name = do
pls <- access players
case find ((== pn) . getL playerNumber) pls of
Nothing -> return False
Just pi -> do
players ~= replaceWith ((== pn) . getL playerNumber) (pi {_playerName = name}) pls
return True
evDelEvent :: EventNumber -> Evaluate Bool
evDelEvent en = do
evs <- access events
case find ((== en) . getL eventNumber) evs of
Nothing -> return False
Just eh -> case _evStatus eh of
SActive -> do
let newEvents = replaceWith ((== en) . getL eventNumber) eh{_evStatus = SDeleted} evs
events ~= newEvents
return True
SDeleted -> return False
evTriggerTime :: UTCTime -> Evaluate ()
evTriggerTime t = triggerEvent (Time t) t
--delete all variables of a rule
delVarsRule :: RuleNumber -> Evaluate ()
delVarsRule rn = void $ variables %= filter ((/= rn) . getL vRuleNumber)
--delete all events of a rule
delEventsRule :: RuleNumber -> Evaluate ()
delEventsRule rn = do
evs <- access events
let toDelete = filter ((== rn) . getL ruleNumber) evs
mapM_ (evDelEvent . _eventNumber) toDelete
--delete all outputs of a rule
delOutputsRule :: RuleNumber -> Evaluate ()
delOutputsRule rn = do
os <- access outputs
let toDelete = filter ((== rn) . getL oRuleNumber) os
mapM_ (evDelOutput . _outputNumber) toDelete
evNewOutput :: Maybe PlayerNumber -> RuleNumber -> NomexNE String -> Evaluate OutputNumber
evNewOutput pn rn s = do
ops <- access outputs
let on = getFreeNumber (map _outputNumber ops)
outputs %= (Output on rn pn s SActive : )
return on
evGetOutput :: OutputNumber -> Reader Game (Maybe String)
evGetOutput on = do
ops <- asks _outputs
case find (\(Output myOn _ _ _ s) -> myOn == on && s == SActive) ops of
Nothing -> return Nothing
Just (Output _ rn _ o _) -> do
out <- evalNomexNE o rn
return $ Just out
evUpdateOutput :: OutputNumber -> NomexNE String -> Evaluate Bool
evUpdateOutput on s = do
ops <- access outputs
case find (\(Output myOn _ _ _ s) -> myOn == on && s == SActive) ops of
Nothing -> return False
Just (Output _ rn pn _ _) -> do
outputs %= replaceWith ((== on) . getL outputNumber) (Output on rn pn s SActive)
return True
evDelOutput :: OutputNumber -> Evaluate Bool
evDelOutput on = do
ops <- access outputs
case find ((== on) . getL outputNumber) ops of
Nothing -> return False
Just o -> case _oStatus o of
SActive -> do
let newOutputs = replaceWith ((== on) . getL outputNumber) o{_oStatus = SDeleted} ops
outputs ~= newOutputs
return True
SDeleted -> return False
logPlayer :: PlayerNumber -> String -> Evaluate ()
logPlayer pn = log (Just pn)
logAll :: String -> Evaluate ()
logAll = log Nothing
log :: Maybe PlayerNumber -> String -> Evaluate ()
log mpn s = do
time <- access currentTime
void $ logs %= (Log mpn time s : )
--remove the ErrorT layer from the Evaluate monad stack.
runEvalError :: Maybe PlayerNumber -> Evaluate a -> State Game ()
runEvalError pn egs = do
e <- runErrorT egs
case e of
Right _ -> return ()
Left e -> do
tracePN (fromMaybe 0 pn) $ "Error: " ++ e
void $ runErrorT $ log pn "Error: "
-- Put an index on every input fields
indexInputs :: Event a -> Event a
indexInputs e = fst $ indexInputs' 0 e
indexInputs' :: Int -> Event a -> (Event a, Int)
indexInputs' n (BaseEvent (Input _ pn s ifo)) = (BaseEvent (Input (Just n) pn s ifo), n+1)
indexInputs' n (BaseEvent a) = (BaseEvent a, n)
indexInputs' n (PureEvent a) = (PureEvent a, n)
indexInputs' n EmptyEvent = (EmptyEvent, n)
indexInputs' n (SumEvent a b) = (SumEvent e1 e2, n2) where
(e1, n1) = indexInputs' n a
(e2, n2) = indexInputs' n1 b
indexInputs' n (AppEvent a b) = (AppEvent e1 e2, n2) where
(e1, n1) = indexInputs' n a
(e2, n2) = indexInputs' n1 b
newtype BEither a b = BE (Either a b) deriving (Show, Eq, Typeable)
bLeft = BE . Left
bRight = BE . Right
instance Alternative (BEither [a]) where
empty = bLeft []
BE (Left a) <|> BE (Left b) = bLeft $ a ++ b
BE (Left _) <|> n = n
m <|> _ = m
instance Applicative (BEither [a]) where
pure = BE . Right
BE (Left a) <*> BE (Left b) = BE (Left (a ++ b))
BE (Left a) <*> _ = BE (Left a)
BE (Right f) <*> r = fmap f r
instance Functor (BEither a) where
fmap _ (BE (Left x)) = BE (Left x)
fmap f (BE (Right y)) = BE (Right (f y))
-- | Show instance for Game
-- showing a game involves evaluating some parts (such as victory and outputs)
instance Show Game where
show g@(Game { _gameName, _rules, _players, _variables, _events, _victory, _currentTime}) =
"Game Name = " ++ show _gameName ++
"\n Rules = " ++ (intercalate "\n " $ map show _rules) ++
"\n Players = " ++ show _players ++
"\n Variables = " ++ show _variables ++
"\n Events = " ++ (intercalate "\n " $ map show _events) ++
"\n Outputs = " ++ show (allOutputs g) ++
"\n Victory = " ++ show (getVictorious g) ++
"\n currentTime = " ++ show _currentTime ++ "\n"
instance Show EventInfo where
show (EventInfo en rn e _ s env) =
"event num: " ++ (show en) ++
", rule num: " ++ (show rn) ++
", event fields: " ++ (show $ getEventFields e env) ++
", envs: " ++ (show env) ++
", status: " ++ (show s)
instance Eq SomeField where
(SomeField e1) == (SomeField e2) = e1 === e2