Nomyx-0.1.0: src/Multi.hs
{-# LANGUAGE FlexibleContexts, GeneralizedNewtypeDeriving, ScopedTypeVariables,
MultiParamTypeClasses, TemplateHaskell, TypeFamilies, TypeOperators,
TypeSynonymInstances, FlexibleInstances, GADTs, NamedFieldPuns, DoAndIfThenElse #-}
-- | This module manages multi-player games and commands.
module Multi where
--Multi, PlayerPassword, GetMulti(..), FindPlayer(..), PlayerMulti(..), GetNewPlayerNumber(..), NewPlayerU(..),
-- listGame, newGame, joinGame, leaveGame, subscribeGame, unsubscribeGame, showSubscribtion, showSubGame, newPlayer,
-- submitRule, myCatch, submitRuleI, showConstitution, showAllRules, listPlayers, amendConstitution, showPendingActions,
-- showMyPendingActions, doActionsI, doAction', showCompletedActions, quit, getPendingActions, doAction, games, getPlayersGame,
-- getPlayersName) where
import Prelude hiding (catch)
import Data.List
import Control.Monad.State
import Game
import Utils
import Interpret
import Debug.Trace.Helpers()
import Language.Nomyx.Expression
import Data.Time
import Language.Haskell.Interpreter.Server (ServerHandle)
import Language.Nomyx.Evaluation
import Data.Maybe
import Types
import Control.Applicative
import Control.Exception
import Debug.Trace.Helpers
-- | helper function to change a player's ingame status.
mayJoinGame :: Maybe GameName -> PlayerNumber -> [PlayerMulti] -> [PlayerMulti]
mayJoinGame maybename pn pl = case find (\(PlayerMulti mypn _ _ _ _ _) -> mypn == pn) pl of
Just o -> replace o o{ inGame = maybename} pl
Nothing -> pl
newPlayerU :: PlayerMulti -> State Multi ()
newPlayerU pm = do
pms <- gets mPlayers
modify (\multi -> multi { mPlayers = pm : pms})
getNewPlayerNumber :: State Multi PlayerNumber
getNewPlayerNumber = do
ps <- gets mPlayers
return $ length ps + 1
addNewGame :: Game -> State Multi ()
addNewGame new = modify (\multi@Multi {games=gs} -> multi {games = new:gs})
getGameByName :: GameName -> State Multi (Maybe Game)
getGameByName gn = fmap (find (\(Game {gameName = n}) -> n==gn)) (gets games)
joinGamePlayer :: PlayerNumber -> GameName -> State Multi ()
joinGamePlayer pn game = modify (\multi -> multi {mPlayers = mayJoinGame (Just game) pn (mPlayers multi)})
leaveGameU :: PlayerNumber -> State Multi ()
leaveGameU pn = modify (\multi -> multi {mPlayers = mayJoinGame Nothing pn (mPlayers multi)})
-- | list the active games
listGame :: PlayerNumber -> State Multi ()
listGame _ = do
gs <- gets games
case length gs of
0 -> traceM "No active games"
_ -> do
traceM "Active games:"
traceM $ concatMap (\g -> gameName g ++ "\n") gs
-- | starts a new game
newGame :: GameName -> String -> PlayerNumber -> State Multi ()
newGame name desc _ = do
gs <- gets games
case null $ filter (\p -> gameName p == name) gs of
True -> do
traceM $ "Creating a new game of name: " ++ name
t <- gets mCurrentTime
-- create a game with zero players
modify (\m -> m {games = (initialGame name desc t):gs})
False -> traceM $ "this name is already used"
uniqueGame :: String -> [Game] -> Bool
uniqueGame s gs = null $ filter (\p -> gameName p == s) gs
-- | join a game.
joinGame :: GameName -> PlayerNumber -> State Multi ()
joinGame game pn = do
mg <- getGameByName game
case mg of
Nothing -> traceM $ "No game by that name"
Just g -> do
traceM "subscribing first."
subscribeGame (gameName g) pn
traceM $ "Joining game: " ++ game
joinGamePlayer pn game
-- | leave a game (you remain subscribed).
leaveGame :: PlayerNumber -> State Multi ()
leaveGame pn = do
leaveGameU pn
traceM "You left the game (you remain subscribed)."
-- | subcribe to a game.
subscribeGame :: GameName -> PlayerNumber -> State Multi ()
subscribeGame game pn = do
m <- get
inGameDo game $ do
g <- get
case find (\(PlayerInfo { playerNumber=mypn}) -> mypn == pn ) (players g) of
Just _ -> traceM "Already subscribed!"
Nothing -> do
traceM $ "Subscribing to game: " ++ game
let player = PlayerInfo { playerNumber = pn, playerName = getPlayersName pn m}
put g {players = player : (players g)}
triggerEvent (Player Arrive) (PlayerData player)
-- | subcribe to a game.
unsubscribeGame :: GameName -> PlayerNumber -> State Multi ()
unsubscribeGame game pn = inGameDo game $ do
g <- get
case find (\(PlayerInfo { playerNumber=mypn}) -> mypn == pn ) (players g) of
Nothing -> traceM "Not subscribed!"
Just _ -> do
traceM $ "Unsubscribing to game: " ++ game
let player = PlayerInfo { playerNumber = pn, playerName = getPlayersName' g pn}
put g {players = filter (\PlayerInfo { playerNumber = mypn} -> mypn /= pn) (players g)}
triggerEvent (Player Leave) (PlayerData player)
showSubGame :: GameName -> PlayerNumber -> State Multi ()
showSubGame g _ = inGameDo g $ do
ps <- gets players
traceM $ concatMap show ps
showSubscribtion :: PlayerNumber -> State Multi ()
showSubscribtion pn = inPlayersGameDo_ pn $ do
ps <- gets players
traceM $ concatMap show ps
-- | insert a rule in pending rules.
submitRule :: SubmitRule -> PlayerNumber -> ServerHandle -> StateT Multi IO ()
submitRule sr pn sh = do
mnr <- enterRule sr pn sh
case mnr of
Just nr -> do
inPlayersGameDo' pn $ do
r <- evProposeRule nr
if r == True then traceM $ "Your rule has been added to pending rules."
else traceM $ "Error: Rule could not be proposed"
liftT $ updateLastRule Nothing pn
Nothing -> liftT $ updateLastRule (Just sr) pn
-- | reads a rule.
enterRule :: SubmitRule -> PlayerNumber -> ServerHandle -> StateT Multi IO (Maybe Rule)
enterRule (SubmitRule name desc code) pn sh = do
mrr <- lift $ interpretRule code sh
join <$> (inPlayersGameDo' pn $ do
rs <- gets rules
let rn = getFreeNumber $ map rNumber rs
case mrr of
Right ruleFunc -> return $ Just Rule {rNumber = rn,
rName = name,
rDescription = desc,
rProposedBy = pn,
rRuleCode = code,
rRuleFunc = ruleFunc,
rStatus = Pending,
rAssessedBy = Nothing}
Left e -> do
output ("Compiler error: " ++ show e ++ "\n") pn
return Nothing)
updateLastRule :: Maybe SubmitRule -> PlayerNumber -> State Multi ()
updateLastRule msr pn = do
pm <- fromJust <$> findPlayer' pn
pls <- gets mPlayers
let pls' = replace pm (pm {lastRule = msr}) pls
modify (\m -> m{mPlayers = pls'})
--cpuTimeLimitSoft = ResourceLimit 4
--cpuTimeLimitHard = ResourceLimit 5
--limits :: [(Resource, ResourceLimits)]
--limits = [ (ResourceCPUTime, ResourceLimits cpuTimeLimitSoft cpuTimeLimitHard)]
inputChoiceResult :: EventNumber -> Int -> PlayerNumber -> State Multi ()
inputChoiceResult eventNumber choiceIndex pn = inPlayersGameDo_ pn $ triggerChoice eventNumber choiceIndex
-- TODO maybe homogeneise both inputs event
inputStringResult :: Event InputString -> String -> PlayerNumber -> State Multi ()
inputStringResult event input pn = inPlayersGameDo_ pn $ triggerEvent event (InputStringData input)
inputUpload :: PlayerNumber -> FilePath -> String -> ServerHandle -> StateT Multi IO ()
inputUpload pn dir mod sh = do
m <- lift $ loadModule dir mod sh
case m of
Right _ -> do
inPlayersGameDo'_ pn $ output ("File loaded: " ++ show dir ++ " Module " ++ show mod ++"\n") pn
return ()
Left e -> do
inPlayersGameDo'_ pn $ output ("Compiler error: " ++ show e ++ "\n") pn
return ()
mailSettings :: MailSettings -> PlayerNumber -> State Multi ()
mailSettings mailSettings pn = do
mps <- gets mPlayers
case find (\(PlayerMulti {mPlayerNumber}) -> pn==mPlayerNumber) mps of
Nothing -> traceM "settings not modified!"
Just pm -> do
let newmps = replace pm pm{mMail=mailSettings} mps
modify (\m -> m{mPlayers = newmps})
-- | show the constitution.
showConstitution :: PlayerNumber -> State Multi ()
showConstitution pn = inPlayersGameDo_ pn $ get >>= (traceM . show . activeRules)
-- | show every rules (including pendings and deleted)
showAllRules :: PlayerNumber -> State Multi ()
showAllRules pn = inPlayersGameDo_ pn $ get >>= (traceM . show . rules)
displayPlayer :: PlayerMulti -> String
displayPlayer (PlayerMulti pn name _ _ (Just game) _) = show pn ++ ": " ++ name ++ " in game: " ++ game ++ "\n"
displayPlayer (PlayerMulti pn name _ _ Nothing _) = show pn ++ ": " ++ name ++ "\n"
-- | Utility functions
-- | replace the player's name in the list
setName :: String -> PlayerNumber -> [PlayerMulti] -> [PlayerMulti]
setName name pn pl = case find (\(PlayerMulti h _ _ _ _ _) -> h == pn) pl of
Just o -> replace o o{ mPlayerName = name} pl
Nothing -> pl
-- | this function apply the given game actions to the game the player is in.
inPlayersGameDo :: PlayerNumber -> State Game a -> State Multi (Maybe a)
inPlayersGameDo pn action = do
multi <- get
t <- gets mCurrentTime
let mg = getPlayersGame pn multi
case mg of
Nothing -> traceM "You must be in a game" >> return Nothing
Just g -> do
(a, myg) <- lift $ runStateT action (g { currentTime = t})
modifyGame myg
return (Just a)
inPlayersGameDo_ :: PlayerNumber -> State Game a -> State Multi ()
inPlayersGameDo_ pn action = inPlayersGameDo pn action >> return ()
inPlayersGameDo' :: PlayerNumber -> State Game a -> StateT Multi IO (Maybe a)
inPlayersGameDo' pn gs = do
m <- get
let (a, m') = runState (inPlayersGameDo pn gs) m
put m'
return a
inPlayersGameDo'_ :: PlayerNumber -> State Game a -> StateT Multi IO ()
inPlayersGameDo'_ pn action = inPlayersGameDo' pn action >> return ()
inGameDo :: GameName -> State Game () -> State Multi ()
inGameDo game action = do
gs <- gets games
case find (\(Game {gameName =n}) -> n==game) gs of
Nothing -> traceM "No game by that name"
Just g -> do
t <- gets mCurrentTime
let myg = execWithGame t action g
modifyGame myg
triggerTimeEvent :: UTCTime -> StateT Multi IO ()
triggerTimeEvent t = do
gs <- gets games
gs' <- lift $ mapM (\g -> trig t g `catch` timeExceptionHandler t g) gs
modify(\m -> m{games = gs'})
trig :: UTCTime -> Game -> IO Game
trig t g = do
let g' = execWithGame t (evTriggerTime t) g
evaluate g'
return g'
timeExceptionHandler :: UTCTime -> Game -> ErrorCall -> IO Game
timeExceptionHandler t g e = do
putStrLn $ "Error in triggerTimeEvent: " ++ (show e)
return $ execWithGame t (outputAll $ "Error while triggering a time event: " ++ (show e) ++
"\nThe event have been canceled. Please remove/fix the faulty rule.") g