packages feed

nomyx-core-1.0.0: src/Nomyx/Core/Profile.hs

{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE QuasiQuotes        #-}
{-# LANGUAGE RecordWildCards    #-}
{-# LANGUAGE TemplateHaskell    #-}
{-# LANGUAGE TypeFamilies       #-}

module Nomyx.Core.Profile where

import           Control.Monad
import           Control.Monad.Catch         (bracket)
import           Control.Monad.Reader.Class  (MonadReader (..))
import           Control.Monad.State
import           Control.Lens
import           Data.Acid                   (Query, Update, makeAcidic,
                                              openLocalStateFrom)
import           Data.Acid                   (AcidState (..))
import           Data.Acid.Advanced          (query', update')
import           Data.Acid.Local             (createCheckpointAndClose)
import           Data.IxSet                  (toList, (@=))
import qualified Data.IxSet                  as IxSet
import           Data.List
import           Data.Maybe
import           Safe
import           Nomyx.Language
import           Nomyx.Core.Engine
import           Nomyx.Core.Quotes
import           Nomyx.Core.Types
import           System.FilePath             ((</>))

-- | set 'ProfileData' for UserId
setProfileData :: ProfileData -> Update ProfileDataState ProfileData
setProfileData profileData =
    do pds@(ProfileDataState {..}) <- get
       put $ pds { profilesData = IxSet.updateIx (_pPlayerNumber profileData) profileData profilesData }
       return profileData

-- | get 'ProfileData' associated with 'UserId'
askProfileData :: PlayerNumber -> Query ProfileDataState (Maybe ProfileData)
askProfileData uid = do
   ProfileDataState{..} <- ask
   let pfs = toList profilesData
   let filtered = filter (\a -> _pPlayerNumber a == uid) pfs
   return $ headMay filtered

initialProfileData :: PlayerNumber -> PlayerSettings -> Library -> ProfileData
initialProfileData uid ps lib = ProfileData uid ps (Just (exampleRule, "")) NoUpload False lib

exampleRule :: RuleTemplate
exampleRule = RuleTemplate "" "" [cr|
--This is an example new rule that you can enter.
--If you submit this rule it will have to be voted on by other players (as described by rule 1).
--A lot of other examples can be found in the left menu bar.
do
   --get your own player number
   me <- getProposerNumber_
   --create an output for me only
   let displayMsg _ = void $ newOutput_ (Just me) "Bravo!"
   --create a button for me, which will display the output when clicked
   void $ onInputButton_ "Click here:" displayMsg me
|]
   ""
   Nothing
   []
   []


-- | create the profile data, but only if it is missing
newProfileData :: PlayerNumber -> PlayerSettings -> Library -> Update ProfileDataState ProfileData
newProfileData uid ps lib =
    do pds@(ProfileDataState {..}) <- get
       case IxSet.getOne (profilesData @= uid) of
         Nothing -> do
            let pd = initialProfileData uid ps lib
            put $ pds { profilesData = IxSet.updateIx uid pd profilesData }
            return pd
         Just profileData -> return profileData

-- | get number of
askProfileDataNumber :: Query ProfileDataState Int
askProfileDataNumber =
    do pds <- ask
       return $ IxSet.size $ profilesData pds

-- | get all profiles
askProfilesData :: Query ProfileDataState [ProfileData]
askProfilesData =
    do pds <- ask
       return $ toList $ profilesData pds

$(makeAcidic ''ProfileDataState
                [ 'setProfileData
                , 'askProfileData
                , 'newProfileData
                , 'askProfileDataNumber
                , 'askProfilesData
                ]
 )

initialProfileDataState :: ProfileDataState
initialProfileDataState = ProfileDataState { profilesData = IxSet.empty }

defaultPlayerSettings :: PlayerSettings
defaultPlayerSettings = PlayerSettings "" Nothing False False False False


modifyProfile :: PlayerNumber -> (ProfileData -> ProfileData) -> StateT Session IO ()
modifyProfile pn mod = do
   s <- get
   pfd <- query' (_acidProfiles s) (AskProfileData pn)
   when (isJust pfd) $ void $ update' (_acidProfiles s) (SetProfileData (mod $ fromJust pfd))

getProfile :: MonadIO m => Session -> PlayerNumber -> m (Maybe ProfileData)
getProfile s pn = query' (_acidProfiles s) (AskProfileData pn)

getPlayerName :: PlayerNumber -> Session -> IO PlayerName
getPlayerName pn s = do
   pfd <- query' (_acidProfiles s) (AskProfileData pn)
   return $ _pPlayerName $ _pPlayerSettings $ fromJustNote ("getPlayersName: no profile for pn=" ++ show pn) pfd

getPlayerNumber' :: PlayerName -> Session -> IO (Maybe PlayerNumber)
getPlayerNumber' pname s = do
   pds <- query' (_acidProfiles s) AskProfilesData
   return $ headMay $ [ _pPlayerNumber pd | pd <- pds, _pPlayerName  (_pPlayerSettings pd) == pname]

getPlayerInGameName :: Game -> PlayerNumber -> PlayerName
getPlayerInGameName g pn = case find ((==pn) . view playerNumber) (_players g) of
   Nothing -> error "getPlayersName': No player by that number in that game"
   Just pm -> _playerName pm

getAllProfiles :: Session -> IO [ProfileData]
getAllProfiles s = query' (_acidProfiles s) AskProfilesData

getPlayerInfo :: Game -> PlayerNumber -> Maybe PlayerInfo
getPlayerInfo g pn = find ((==pn) . view playerNumber) (_players g)

withAcid :: Maybe FilePath -- ^ state directory
         -> (AcidState ProfileDataState -> IO a) -- ^ action
         -> IO a
withAcid mBasePath action = do
    let basePath = fromMaybe "_state" mBasePath
    bracket (openLocalStateFrom (basePath </> "profileData") initialProfileDataState) createCheckpointAndClose updateProfile where
       updateProfile profileData = action (profileData)