Added persistent statistics for the duck Module.
This commit is contained in:
parent
81fb522dd4
commit
1995f4ddb8
2 changed files with 87 additions and 18 deletions
|
@ -1,3 +1,4 @@
|
||||||
|
{-# LANGUAGE TypeFamilies, DeriveDataTypeable, TemplateHaskell #-}
|
||||||
-- | This module is an IRC plugin that generates and kills ducks
|
-- | This module is an IRC plugin that generates and kills ducks
|
||||||
module Hsbot.Plugin.Duck
|
module Hsbot.Plugin.Duck
|
||||||
( duck
|
( duck
|
||||||
|
@ -5,16 +6,65 @@ module Hsbot.Plugin.Duck
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Control.Concurrent
|
import Control.Concurrent
|
||||||
import qualified Data.List as L
|
|
||||||
import Control.Monad.Reader
|
import Control.Monad.Reader
|
||||||
|
import Control.Monad.State
|
||||||
|
import Data.Acid
|
||||||
|
import qualified Data.List as L
|
||||||
|
import qualified Data.Map as M
|
||||||
|
import Data.Maybe
|
||||||
|
import Data.SafeCopy
|
||||||
|
import Data.Typeable
|
||||||
import qualified Network.IRC as IRC
|
import qualified Network.IRC as IRC
|
||||||
import Prelude hiding (catch)
|
import Prelude hiding (catch)
|
||||||
|
import System.Environment.XDG.BaseDir
|
||||||
import System.Random
|
import System.Random
|
||||||
|
|
||||||
import Hsbot.Message
|
import Hsbot.Message
|
||||||
import Hsbot.Types
|
import Hsbot.Types
|
||||||
import Hsbot.Utils
|
import Hsbot.Utils
|
||||||
|
|
||||||
|
-- | A user statistic
|
||||||
|
data Stat = Stat
|
||||||
|
{ statRounds :: Int
|
||||||
|
, statShot :: Int
|
||||||
|
, statKilled :: Int
|
||||||
|
} deriving (Show, Typeable)
|
||||||
|
|
||||||
|
-- | Default values for new stat
|
||||||
|
emptyStat :: Stat
|
||||||
|
emptyStat = Stat { statRounds = 0
|
||||||
|
, statShot = 0
|
||||||
|
, statKilled = 0 }
|
||||||
|
|
||||||
|
-- The statistics database
|
||||||
|
data StatDB = StatDB
|
||||||
|
{ nickStats :: M.Map String Stat
|
||||||
|
} deriving (Show, Typeable)
|
||||||
|
|
||||||
|
-- | Statistics database initial state
|
||||||
|
emptyStatDB :: StatDB
|
||||||
|
emptyStatDB = StatDB { nickStats = M.empty }
|
||||||
|
|
||||||
|
$(deriveSafeCopy 0 'base ''Stat)
|
||||||
|
$(deriveSafeCopy 0 'base ''StatDB)
|
||||||
|
|
||||||
|
-- | Statistics database transactions
|
||||||
|
scoreAction :: String -> Int -> Int -> Int -> Update StatDB ()
|
||||||
|
scoreAction sender rounds shots kills = do
|
||||||
|
statDB <- get
|
||||||
|
let stats = nickStats statDB
|
||||||
|
stat = fromMaybe emptyStat $ M.lookup sender stats
|
||||||
|
stat' = stat { statRounds = rounds + statRounds stat
|
||||||
|
, statShot = shots + statShot stat
|
||||||
|
, statKilled = kills + statKilled stat }
|
||||||
|
put statDB { nickStats = M.insert sender stat' stats }
|
||||||
|
|
||||||
|
getDuckStats :: Query StatDB StatDB
|
||||||
|
getDuckStats = ask
|
||||||
|
|
||||||
|
-- This will define @ViewMessage@ and @AddMessage@ for us.
|
||||||
|
$(makeAcidic ''StatDB ['getDuckStats, 'scoreAction])
|
||||||
|
|
||||||
-- | The duck plugin identity
|
-- | The duck plugin identity
|
||||||
duck :: PluginId
|
duck :: PluginId
|
||||||
duck = PluginId
|
duck = PluginId
|
||||||
|
@ -24,35 +74,39 @@ duck = PluginId
|
||||||
-- | An IRC plugin that generates and kills ducks
|
-- | An IRC plugin that generates and kills ducks
|
||||||
theDuck :: String -> Int -> Plugin (Env IO) ()
|
theDuck :: String -> Int -> Plugin (Env IO) ()
|
||||||
theDuck channel seconds = do
|
theDuck channel seconds = do
|
||||||
|
baseDir <- liftIO $ System.Environment.XDG.BaseDir.getUserDataDir "hsbot"
|
||||||
|
statDB <- liftIO $ openAcidStateFrom (baseDir ++ "/duckDB/") emptyStatDB
|
||||||
ducksMVar <- liftIO newEmptyMVar
|
ducksMVar <- liftIO newEmptyMVar
|
||||||
duckSpawner channel seconds ducksMVar
|
duckSpawner channel seconds ducksMVar
|
||||||
forever $ readMsg >>= eval ducksMVar
|
forever $ readMsg >>= eval statDB ducksMVar
|
||||||
where
|
where
|
||||||
eval :: MVar Int -> Message -> Plugin (Env IO) ()
|
eval :: AcidState StatDB -> MVar Int -> Message -> Plugin (Env IO) ()
|
||||||
eval ducksMVar (IncomingMsg msg)
|
eval statDB ducksMVar (IncomingMsg msg)
|
||||||
| IRC.msg_command msg == "PRIVMSG" = do
|
| IRC.msg_command msg == "PRIVMSG" = do
|
||||||
-- First we kill the ducks that we find in the message
|
-- First we kill the ducks that we find in the message
|
||||||
let kills = howManyDucksInThere . concat $ IRC.msg_params msg
|
let kills = howManyDucksInThere . concat $ IRC.msg_params msg
|
||||||
when (kills /= "") $ answerMsg msg kills
|
when (kills /= "") $ answerMsg msg kills
|
||||||
-- Then we check if someone shot some duck
|
-- Then we check if someone shot some duck
|
||||||
let shots = howManyBulletFiredInThere . concat $ IRC.msg_params msg
|
let shots = howManyBulletFiredInThere . concat $ IRC.msg_params msg
|
||||||
noDucksToShot <- liftIO $ isEmptyMVar ducksMVar
|
noDucksToShoot <- liftIO $ isEmptyMVar ducksMVar
|
||||||
when (and [getDestination msg == channel, not noDucksToShot, shots > 0]) $ do
|
when (shots > 0) $ do
|
||||||
-- TODO: score dead ducks (with a min (ducksWaitingForDeath, shots))
|
_ <- update' statDB (ScoreAction (getSender msg) 0 shots 0)
|
||||||
|
when (and [getDestination msg == channel, not noDucksToShoot]) $ do
|
||||||
ducksWaitingForDeath <- liftIO $ modifyMVar ducksMVar (\x -> return (x - shots, x))
|
ducksWaitingForDeath <- liftIO $ modifyMVar ducksMVar (\x -> return (x - shots, x))
|
||||||
|
_ <- update' statDB (ScoreAction (getSender msg) 0 0 (min ducksWaitingForDeath shots))
|
||||||
when (shots >= ducksWaitingForDeath) $ do
|
when (shots >= ducksWaitingForDeath) $ do
|
||||||
_ <- liftIO $ takeMVar ducksMVar
|
_ <- liftIO $ takeMVar ducksMVar
|
||||||
duckSpawner channel seconds ducksMVar
|
duckSpawner channel seconds ducksMVar
|
||||||
-- TODO : score lost bullets and round
|
_ <- update' statDB (ScoreAction (getSender msg) 1 0 0)
|
||||||
return ()
|
return ()
|
||||||
-- Finally we check if we received some command
|
-- Finally we check if we received some command
|
||||||
cmdArgs <- lift $ getCommand msg
|
cmdArgs <- lift $ getCommand msg
|
||||||
case cmdArgs of
|
case cmdArgs of
|
||||||
--"ducks":"stats":args -> TODO
|
"ducks":"stats":_ -> query' statDB GetDuckStats >>= printDuckStats channel
|
||||||
"ducks":_ -> answerMsg msg "Invalid duck command."
|
"ducks":_ -> answerMsg msg "Invalid duck command."
|
||||||
_ -> return ()
|
_ -> return ()
|
||||||
| otherwise = return ()
|
| otherwise = return ()
|
||||||
eval _ _ = return ()
|
eval _ _ _ = return ()
|
||||||
|
|
||||||
-- | Regularly spawns ducks on a channel, just waiting to be shot
|
-- | Regularly spawns ducks on a channel, just waiting to be shot
|
||||||
duckSpawner :: String -> Int -> MVar Int -> Plugin (Env IO) ()
|
duckSpawner :: String -> Int -> MVar Int -> Plugin (Env IO) ()
|
||||||
|
@ -98,3 +152,15 @@ ducks = [ x : y : z | x <- nose, y <- face, z <- ["__/", "_/", "/"] ]
|
||||||
bangs :: [String]
|
bangs :: [String]
|
||||||
bangs = [ "PAN", "PAN!" ]
|
bangs = [ "PAN", "PAN!" ]
|
||||||
|
|
||||||
|
-- | Nicely prints duck statistics
|
||||||
|
printDuckStats :: String -> StatDB -> Plugin (Env IO) ()
|
||||||
|
printDuckStats channel statDB = do
|
||||||
|
sendLine "Duck slaughter simulator - Hall of Fame"
|
||||||
|
mapM_ (sendLine . buildLine) $ M.toList (nickStats statDB)
|
||||||
|
where
|
||||||
|
buildLine :: (String, Stat) -> String
|
||||||
|
buildLine (nick, stat) = concat [ nick, ": ", show $ statRounds stat, " rounds won, ", show $ statShot stat
|
||||||
|
, " shots fired, ", show $ statKilled stat, " ducks killed" ]
|
||||||
|
sendLine :: String -> Plugin (Env IO) ()
|
||||||
|
sendLine msg = writeMsg . OutgoingMsg $ IRC.Message Nothing "PRIVMSG" [channel, msg]
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
Name: hsbot
|
Name: hsbot
|
||||||
Version: 0.4.14
|
Version: 0.4.15
|
||||||
Cabal-version: >=1.2
|
Cabal-version: >=1.2
|
||||||
Synopsis: A multipurposes IRC bot
|
Synopsis: A multipurposes IRC bot
|
||||||
Description:
|
Description:
|
||||||
|
@ -28,7 +28,8 @@ Library
|
||||||
Hsbot.Types
|
Hsbot.Types
|
||||||
Hsbot.Utils
|
Hsbot.Utils
|
||||||
Extensions: DeriveDataTypeable FlexibleContexts ScopedTypeVariables
|
Extensions: DeriveDataTypeable FlexibleContexts ScopedTypeVariables
|
||||||
Build-depends: base >= 4.1 && < 5,
|
Build-depends: acid-state,
|
||||||
|
base >= 4.1 && < 5,
|
||||||
bytestring,
|
bytestring,
|
||||||
certificate,
|
certificate,
|
||||||
containers,
|
containers,
|
||||||
|
@ -40,8 +41,10 @@ Library
|
||||||
mtl,
|
mtl,
|
||||||
network,
|
network,
|
||||||
random,
|
random,
|
||||||
|
safecopy,
|
||||||
tls >= 0.6.1,
|
tls >= 0.6.1,
|
||||||
tls-extra >= 0.2.0
|
tls-extra >= 0.2.0,
|
||||||
|
xdg-basedir
|
||||||
|
|
||||||
|
|
||||||
Executable hsbot
|
Executable hsbot
|
||||||
|
|
Reference in a new issue