Nomyx-Language 0.6.1 → 0.6.2
raw patch · 7 files changed
+148/−311 lines, 7 filesPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
API changes (from Hackage documentation)
- Language.Nomyx.Examples: gameMasterElections :: Rule
- Language.Nomyx.Examples: referendum :: String -> Nomex () -> Rule
- Language.Nomyx.Examples: referendumOnKickPlayer :: Rule
- Language.Nomyx.Rules: activateOrReject :: RuleInfo -> Bool -> Nomex ()
- Language.Nomyx.Vote: Election :: String -> Election
- Language.Nomyx.Vote: ExclusiveVote :: (Maybe (Alts a)) -> VoteType a
- Language.Nomyx.Vote: NonExclusiveVote :: [Alts a] -> VoteType a
- Language.Nomyx.Vote: Referendum :: String -> Referendum
- Language.Nomyx.Vote: VoteData :: Msg [Alts a] -> ArrayVar PlayerNumber (Alts a) -> [EventNumber] -> VoteResult a -> Maybe UTCTime -> VoteData a
- Language.Nomyx.Vote: alts :: Votable a => [Alts a]
- Language.Nomyx.Vote: assessFunction :: VoteData a -> VoteResult a
- Language.Nomyx.Vote: assessOnEveryVote :: Votable a => Assessor a
- Language.Nomyx.Vote: assessOnTimeDelay :: Votable a => NominalDiffTime -> Assessor a
- Language.Nomyx.Vote: assessOnTimeLimit :: Votable a => UTCTime -> Assessor a
- Language.Nomyx.Vote: assessWhenEverybodyVoted :: Votable a => Assessor a
- Language.Nomyx.Vote: class (Eq (Alts a), Show (Alts a), Ord (Alts a), Typeable a) => Votable a where data family Alts a exclusiveWinner _ = Nothing
- Language.Nomyx.Vote: cleanVote :: Votable a => VoteData a -> Nomex EventNumber
- Language.Nomyx.Vote: data Election
- Language.Nomyx.Vote: data Referendum
- Language.Nomyx.Vote: data VoteData a
- Language.Nomyx.Vote: data VoteType a
- Language.Nomyx.Vote: displayVoteResult :: Votable a => String -> VoteData a -> Nomex OutputNumber
- Language.Nomyx.Vote: displayVoteVar :: Votable a => (Maybe PlayerNumber) -> String -> ArrayVar PlayerNumber (Alts a) -> Nomex EventNumber
- Language.Nomyx.Vote: elections :: String -> [PlayerInfo] -> (PlayerNumber -> Nomex ()) -> Nomex ()
- Language.Nomyx.Vote: exclusiveVoteQuota :: Votable a => Int -> VoteStats a -> (Alts a, Alts a) -> Maybe (Alts a)
- Language.Nomyx.Vote: exclusiveWinner :: Votable a => a -> Maybe (Alts a, Alts a)
- Language.Nomyx.Vote: firstBest :: Votable a => VoteStats a -> [Alts a]
- Language.Nomyx.Vote: firstXBest :: Votable a => Int -> VoteStats a -> [Alts a]
- Language.Nomyx.Vote: inputNumbers :: VoteData a -> [EventNumber]
- Language.Nomyx.Vote: instance Bounded (Alts Referendum)
- Language.Nomyx.Vote: instance Bounded (Alts RuleInfo)
- Language.Nomyx.Vote: instance Enum (Alts Referendum)
- Language.Nomyx.Vote: instance Enum (Alts RuleInfo)
- Language.Nomyx.Vote: instance Eq (Alts Election)
- Language.Nomyx.Vote: instance Eq (Alts Referendum)
- Language.Nomyx.Vote: instance Eq (Alts RuleInfo)
- Language.Nomyx.Vote: instance Ord (Alts Election)
- Language.Nomyx.Vote: instance Ord (Alts Referendum)
- Language.Nomyx.Vote: instance Ord (Alts RuleInfo)
- Language.Nomyx.Vote: instance Read (Alts Referendum)
- Language.Nomyx.Vote: instance Read (Alts RuleInfo)
- Language.Nomyx.Vote: instance Show (Alts Election)
- Language.Nomyx.Vote: instance Show (Alts Referendum)
- Language.Nomyx.Vote: instance Show (Alts RuleInfo)
- Language.Nomyx.Vote: instance Typeable Election
- Language.Nomyx.Vote: instance Typeable Referendum
- Language.Nomyx.Vote: instance Typeable1 Alts
- Language.Nomyx.Vote: instance Votable Election
- Language.Nomyx.Vote: instance Votable Referendum
- Language.Nomyx.Vote: instance Votable RuleInfo
- Language.Nomyx.Vote: msgEnd :: VoteData a -> Msg [Alts a]
- Language.Nomyx.Vote: name :: Votable a => a -> String
- Language.Nomyx.Vote: onRuleProposed :: (RuleInfo -> Nomex (Msg [ForAgainst])) -> Rule
- Language.Nomyx.Vote: quorum :: Votable a => Int -> VoteStats a -> Bool
- Language.Nomyx.Vote: quota :: Votable a => Alts a -> Int -> Int -> Int
- Language.Nomyx.Vote: referendum :: String -> Nomex () -> Rule
- Language.Nomyx.Vote: showChoice :: Votable a => Maybe (Alts a) -> String
- Language.Nomyx.Vote: showChoices :: Votable a => [Alts a] -> String
- Language.Nomyx.Vote: showFinishedVote :: Votable a => [(PlayerNumber, Maybe (Alts a))] -> NomexNE String
- Language.Nomyx.Vote: showOnGoingVote :: Votable a => String -> [(PlayerNumber, Maybe (Alts a))] -> NomexNE String
- Language.Nomyx.Vote: showVote :: Votable a => (PlayerNumber, Maybe (Alts a)) -> NomexNE (String, String)
- Language.Nomyx.Vote: sortWith :: Ord b => (a -> b) -> [a] -> [a]
- Language.Nomyx.Vote: takeInGroups :: Int -> [[a]] -> [a]
- Language.Nomyx.Vote: timeLimit :: VoteData a -> Maybe UTCTime
- Language.Nomyx.Vote: type Assessor a = StateT (VoteData a) Nomex ()
- Language.Nomyx.Vote: type ForAgainst = Alts RuleInfo
- Language.Nomyx.Vote: type Vote a = (PlayerNumber, Maybe (Alts a))
- Language.Nomyx.Vote: type VoteResult a = VoteStats a -> [Alts a]
- Language.Nomyx.Vote: unanimity' :: [Maybe Bool] -> Bool
- Language.Nomyx.Vote: vote :: UTCTime -> [PlayerNumber] -> Event Bool
- Language.Nomyx.Vote: voteEvent :: UTCTime -> [PlayerNumber] -> Event ([Maybe Bool])
- Language.Nomyx.Vote: voteVar :: VoteData a -> ArrayVar PlayerNumber (Alts a)
- Language.Nomyx.Vote: voteWith_ :: Votable a => VoteResult a -> Assessor a -> a -> Nomex (Msg [Alts a])
+ Language.Nomyx.Events: shortcutEvents :: [Event a] -> ([a] -> Maybe b) -> Event b
+ Language.Nomyx.Expression: EventEnv :: (Field e) -> e -> EventEnv
+ Language.Nomyx.Expression: EventInfo :: EventNumber -> RuleNumber -> Event e -> EventHandler e -> Status -> [EventEnv] -> EventInfo
+ Language.Nomyx.Expression: GetEvents :: NomexNE [EventInfo]
+ Language.Nomyx.Expression: SActive :: Status
+ Language.Nomyx.Expression: SDeleted :: Status
+ Language.Nomyx.Expression: ShortcutEvents :: [Event a] -> ([a] -> Maybe b) -> Event b
+ Language.Nomyx.Expression: _env :: EventInfo -> [EventEnv]
+ Language.Nomyx.Expression: _evStatus :: EventInfo -> Status
+ Language.Nomyx.Expression: _eventNumber :: EventInfo -> EventNumber
+ Language.Nomyx.Expression: _ruleNumber :: EventInfo -> RuleNumber
+ Language.Nomyx.Expression: data EventEnv
+ Language.Nomyx.Expression: data EventInfo
+ Language.Nomyx.Expression: data Status
+ Language.Nomyx.Expression: env :: Lens EventInfo [EventEnv]
+ Language.Nomyx.Expression: evStatus :: Lens EventInfo Status
+ Language.Nomyx.Expression: event :: EventInfo -> Event e
+ Language.Nomyx.Expression: eventNumber :: Lens EventInfo EventNumber
+ Language.Nomyx.Expression: handler :: EventInfo -> EventHandler e
+ Language.Nomyx.Expression: instance Eq EventInfo
+ Language.Nomyx.Expression: instance Eq Status
+ Language.Nomyx.Expression: instance Ord EventInfo
+ Language.Nomyx.Expression: instance Show EventEnv
+ Language.Nomyx.Expression: instance Show Status
+ Language.Nomyx.Expression: ruleNumber :: Lens EventInfo RuleNumber
+ Language.Nomyx.Expression: type EventHandler e = (EventNumber, e) -> Nomex ()
+ Language.Nomyx.Rules: activateOrRejectRule :: RuleInfo -> Bool -> Nomex ()
+ Language.Nomyx.Rules: onRuleProposed :: (RuleInfo -> Nomex ()) -> Nomex ()
+ Language.Nomyx.Vote: callVoteRule :: AssessFunction -> NominalDiffTime -> RuleInfo -> Nomex ()
+ Language.Nomyx.Vote: callVoteRule' :: AssessFunction -> UTCTime -> RuleInfo -> Nomex ()
+ Language.Nomyx.Vote: instance Show VoteStats
+ Language.Nomyx.Vote: nbParticipants :: VoteStats -> Int
+ Language.Nomyx.Vote: type AssessFunction = VoteStats -> Maybe Bool
+ Language.Nomyx.Vote: unanimityVote :: Nomex ()
+ Language.Nomyx.Vote: voteEvents :: UTCTime -> String -> [PlayerNumber] -> [Event (Maybe Bool)]
- Language.Nomyx.Inputs: inputRadio :: (Eq c, Show c, Typeable c) => PlayerNumber -> String -> [c] -> c -> Event c
+ Language.Nomyx.Inputs: inputRadio :: (Eq c, Show c, Typeable c) => PlayerNumber -> String -> [(c, String)] -> Event c
- Language.Nomyx.Vote: VoteStats :: Map (Maybe (Alts a)) Int -> Bool -> VoteStats a
+ Language.Nomyx.Vote: VoteStats :: Map (Maybe Bool) Int -> Int -> Bool -> VoteStats
- Language.Nomyx.Vote: callVote :: UTCTime -> Nomex ()
+ Language.Nomyx.Vote: callVote :: AssessFunction -> UTCTime -> String -> (Bool -> Nomex ()) -> Nomex ()
- Language.Nomyx.Vote: data VoteStats a
+ Language.Nomyx.Vote: data VoteStats
- Language.Nomyx.Vote: getVoteStats :: Votable a => [Vote a] -> Bool -> VoteStats a
+ Language.Nomyx.Vote: getVoteStats :: Int -> [Maybe Bool] -> VoteStats
- Language.Nomyx.Vote: majority :: Votable a => VoteStats a -> [Alts a]
+ Language.Nomyx.Vote: majority :: AssessFunction
- Language.Nomyx.Vote: majorityWith :: Votable a => Int -> VoteStats a -> [Alts a]
+ Language.Nomyx.Vote: majorityWith :: Int -> AssessFunction
- Language.Nomyx.Vote: nbVoters :: Votable a => VoteStats a -> Int
+ Language.Nomyx.Vote: nbVoters :: VoteStats -> Int
- Language.Nomyx.Vote: notVoted :: Votable a => VoteStats a -> Int
+ Language.Nomyx.Vote: notVoted :: VoteStats -> Int
- Language.Nomyx.Vote: numberVotes :: Votable a => Int -> VoteStats a -> [Alts a]
+ Language.Nomyx.Vote: numberVotes :: Int -> AssessFunction
- Language.Nomyx.Vote: singleVote :: UTCTime -> PlayerNumber -> Event (Maybe Bool)
+ Language.Nomyx.Vote: singleVote :: UTCTime -> String -> PlayerNumber -> Event (Maybe Bool)
- Language.Nomyx.Vote: totalVoters :: Votable a => VoteStats a -> Int
+ Language.Nomyx.Vote: totalVoters :: VoteStats -> Int
- Language.Nomyx.Vote: unanimity :: Votable a => VoteStats a -> [Alts a]
+ Language.Nomyx.Vote: unanimity :: AssessFunction
- Language.Nomyx.Vote: voteCounts :: VoteStats a -> Map (Maybe (Alts a)) Int
+ Language.Nomyx.Vote: voteCounts :: VoteStats -> Map (Maybe Bool) Int
- Language.Nomyx.Vote: voteFinished :: VoteStats a -> Bool
+ Language.Nomyx.Vote: voteFinished :: VoteStats -> Bool
- Language.Nomyx.Vote: voteQuota :: Votable a => Int -> VoteStats a -> [Alts a]
+ Language.Nomyx.Vote: voteQuota :: Int -> VoteStats -> Maybe Bool
- Language.Nomyx.Vote: voteWith :: Votable a => VoteResult a -> Assessor a -> a -> [Alts a] -> Nomex (Msg [Alts a])
+ Language.Nomyx.Vote: voteWith :: UTCTime -> [PlayerNumber] -> AssessFunction -> String -> Event Bool
- Language.Nomyx.Vote: voted :: Votable a => VoteStats a -> Int
+ Language.Nomyx.Vote: voted :: VoteStats -> Int
- Language.Nomyx.Vote: withQuorum :: Votable a => VoteResult a -> Int -> VoteResult a
+ Language.Nomyx.Vote: withQuorum :: AssessFunction -> Int -> AssessFunction
Files
- Nomyx-Language.cabal +2/−2
- src/Language/Nomyx/Events.hs +12/−1
- src/Language/Nomyx/Examples.hs +13/−13
- src/Language/Nomyx/Expression.hs +34/−7
- src/Language/Nomyx/Inputs.hs +8/−8
- src/Language/Nomyx/Rules.hs +7/−4
- src/Language/Nomyx/Vote.hs +72/−276
Nomyx-Language.cabal view
@@ -1,5 +1,5 @@ name: Nomyx-Language-version: 0.6.1+version: 0.6.2 cabal-version: >=1.6 ---Warning: to use "cabal sdist", this patch is needed in cabal: https://github.com/haskell/cabal/pull/1110 ---This allows cabal to work with exported generated modules (Paths_Nomyx_Language)@@ -10,7 +10,7 @@ maintainer: Corentin Dupont stability: Experimental synopsis: Language to express rules for Nomic-description: Provide a DSL to express rules for a Nomic game, with evaluation engine. See package Nomyx for a full game implementation.+description: Provide a DSL to express rules in a Nomic game. See package Nomyx for a full game implementation. category: Language Homepage: http://www.nomyx.net author: Corentin Dupont
src/Language/Nomyx/Events.hs view
@@ -10,7 +10,8 @@ getCurrentTime, oneWeek, oneDay, oneHour, oneMinute, timeEvent, messageEvent, victoryEvent, playerEvent, ruleEvent,- baseEvent, baseInputEvent+ baseEvent, baseInputEvent,+ shortcutEvents ) where import Language.Nomyx.Expression@@ -110,13 +111,23 @@ timeEvent :: UTCTime -> Event UTCTime timeEvent = BaseEvent . Time+ messageEvent :: (Typeable a) => Msg a -> Event a messageEvent = BaseEvent . Message++victoryEvent :: Event VictoryCond victoryEvent = BaseEvent Victory+ playerEvent :: Player -> Event PlayerInfo playerEvent = BaseEvent . Player+ ruleEvent :: RuleEvent -> Event RuleInfo ruleEvent re = BaseEvent $ RuleEv re baseInputEvent :: (Typeable a) => PlayerNumber -> String -> (InputForm a) -> Field a baseInputEvent pn s iform = Input Nothing pn s iform++shortcutEvents :: [Event a] -> ([a] -> Maybe b) -> Event b+shortcutEvents = ShortcutEvents++
src/Language/Nomyx/Examples.hs view
@@ -25,9 +25,9 @@ victoryXEcu, --noGroupVictory, banPlayer,- referendum,- referendumOnKickPlayer,- gameMasterElections,+-- referendum,+-- referendumOnKickPlayer,+-- gameMasterElections, gameMaster, bravoButton, enterHaiku,@@ -112,7 +112,7 @@ monarchy :: Rule monarchy = void $ onEvent_ (ruleEvent Proposed) $ \rule -> do k <- readMsgVar_ king- void $ onInputRadioOnce ("Your Royal Highness, do you accept rule " ++ (show $ _rNumber rule) ++ "?") [True, False] (activateOrReject rule) k+ void $ onInputRadioOnce ("Your Royal Highness, do you accept rule " ++ (show $ _rNumber rule) ++ "?") [True, False] (activateOrRejectRule rule) k -- | Revolution! Hail to the king! -- This rule suppresses the democracy (usually rules 1 and 2), installs the king and activates monarchy.@@ -161,7 +161,7 @@ -- the vote is assessed after every vote in case the winner is already known -- the vote will finish anyway after one day voteWithMajority :: Rule-voteWithMajority = onRuleProposed $ voteWith_ (majority `withQuorum` 2) $ assessOnEveryVote >> assessOnTimeDelay oneDay+voteWithMajority = onRuleProposed $ callVoteRule (majority `withQuorum` 2) oneDay -- | Change current system (the rules passed in parameter) to absolute majority (half participants plus one) returnToDemocracy :: [RuleNumber] -> Rule@@ -180,14 +180,14 @@ -- * Referendum & elections -- | triggers a referendum, if the outcome is yes player 2 will be kicked-referendumOnKickPlayer :: Rule-referendumOnKickPlayer = referendum " kick player 2" (void $ delPlayer 2)---- | triggers elections (all players are candidates), the winner becomes game master-gameMasterElections :: Rule-gameMasterElections = do- pls <- liftEffect getPlayers- elections "Game Master" pls makeGM+--referendumOnKickPlayer :: Rule+--referendumOnKickPlayer = referendum " kick player 2" (void $ delPlayer 2)+--+---- | triggers elections (all players are candidates), the winner becomes game master+--gameMasterElections :: Rule+--gameMasterElections = do+-- pls <- liftEffect getPlayers+-- elections "Game Master" pls makeGM makeGM :: PlayerNumber -> Nomex () makeGM pn = do
src/Language/Nomyx/Expression.hs view
@@ -54,7 +54,8 @@ DelVar :: (V a) -> Nomex Bool --Events management OnEvent :: (Typeable e, Show e) => Event e -> ((EventNumber, e) -> Nomex ()) -> Nomex EventNumber - DelEvent :: EventNumber -> Nomex Bool + DelEvent :: EventNumber -> Nomex Bool+ GetEvents :: NomexNE [EventInfo] SendMessage :: (Typeable a, Show a) => Msg a -> a -> Nomex () --Rules management ProposeRule :: RuleInfo -> Nomex Bool @@ -129,11 +130,12 @@ -- | Composable events data Event a where- SumEvent :: Event a -> Event a -> Event a -- The first event to fire will be returned- AppEvent :: Event (a -> b) -> Event a -> Event b -- Both events should fire, and then the result is returned- PureEvent :: a -> Event a -- Create a fake event. The result is useable with no delay.- EmptyEvent :: Event a -- An event that is never fired.- BaseEvent :: (Typeable a) => Field a -> Event a -- Embed a base event+ SumEvent :: Event a -> Event a -> Event a -- The first event to fire will be returned+ AppEvent :: Event (a -> b) -> Event a -> Event b -- Both events should fire, and then the result is returned+ PureEvent :: a -> Event a -- Create a fake event. The result is useable with no delay.+ EmptyEvent :: Event a -- An event that is never fired.+ ShortcutEvents :: [Event a] -> ([a] -> Maybe b) -> Event b -- a result is returned as soon as it can be computed, dismissing the events that hasn't fired yet+ BaseEvent :: (Typeable a) => Field a -> Event a -- Embed a base event deriving Typeable -- | Base events@@ -181,6 +183,31 @@ (<|>) = SumEvent empty = EmptyEvent +-- EventInfo++data EventInfo = forall e. (Typeable e, Show e) =>+ EventInfo {_eventNumber :: EventNumber,+ _ruleNumber :: RuleNumber,+ event :: Event e,+ handler :: EventHandler e,+ _evStatus :: Status,+ _env :: [EventEnv]}++data EventEnv = forall e. (Typeable e, Show e) => EventEnv (Field e) e++type EventHandler e = (EventNumber, e) -> Nomex ()++deriving instance Show EventEnv++data Status = SActive | SDeleted deriving (Eq, Show)++instance Eq EventInfo where+ (EventInfo {_eventNumber=e1}) == (EventInfo {_eventNumber=e2}) = e1 == e2++instance Ord EventInfo where+ (EventInfo {_eventNumber=e1}) <= (EventInfo {_eventNumber=e2}) = e1 <= e2++ -- * Rule -- | Type of a rule function.@@ -234,5 +261,5 @@ concatMapM :: (Monad m) => (a -> m [b]) -> [a] -> m [b] concatMapM f xs = liftM concat (mapM f xs) -$( makeLenses [''RuleInfo, ''PlayerInfo] ) +$( makeLenses [''RuleInfo, ''PlayerInfo, ''EventInfo] )
src/Language/Nomyx/Inputs.hs view
@@ -28,21 +28,21 @@ -- | triggers a choice input to the user. The result will be sent to the callback onInputRadio :: (Typeable a, Eq a, Show a) => String -> [a] -> (EventNumber -> a -> Nomex ()) -> PlayerNumber -> Nomex EventNumber -onInputRadio title choices handler pn = onEvent (inputRadioHead pn title choices) (\(en, a) -> handler en a) +onInputRadio title choices handler pn = onEvent (inputRadio' pn title choices) (\(en, a) -> handler en a) -- | the same, disregard the event number onInputRadio_ :: (Typeable a, Eq a, Show a) => String -> [a] -> (a -> Nomex ()) -> PlayerNumber -> Nomex EventNumber -onInputRadio_ title choices handler pn = onEvent_ (inputRadioHead pn title choices) handler +onInputRadio_ title choices handler pn = onEvent_ (inputRadio' pn title choices) handler -- | the same, suppress the event after first trigger onInputRadioOnce :: (Typeable a, Eq a, Show a) => String -> [a] -> (a -> Nomex ()) -> PlayerNumber -> Nomex EventNumber -onInputRadioOnce title choices handler pn = onEventOnce (inputRadioHead pn title choices) handler - -inputRadioHead :: (Eq c, Show c, Typeable c) => PlayerNumber -> String -> [c] -> Event c -inputRadioHead pn title choices = inputRadio pn title choices (head choices) +onInputRadioOnce title choices handler pn = onEventOnce (inputRadio' pn title choices) handler -inputRadio :: (Eq c, Show c, Typeable c) => PlayerNumber -> String -> [c] -> c -> Event c -inputRadio pn title cs _ = baseEvent $ baseInputRadio pn title (zip cs (show <$> cs))+inputRadio :: (Eq c, Show c, Typeable c) => PlayerNumber -> String -> [(c, String)] -> Event c +inputRadio pn title cs = baseEvent $ baseInputRadio pn title cs++inputRadio' :: (Eq c, Show c, Typeable c) => PlayerNumber -> String -> [c] -> Event c +inputRadio' pn title cs = inputRadio pn title (zip cs (show <$> cs)) baseInputRadio :: (Eq c, Show c, Typeable c) => PlayerNumber -> String -> [(c, String)] -> Field c baseInputRadio pn title cs = baseInputEvent pn title (Radio cs)
src/Language/Nomyx/Rules.hs view
@@ -21,13 +21,14 @@ suppressRule, suppressRule_, suppressAllRules, modifyRule, autoActivate,- activateOrReject,+ activateOrRejectRule, simulate, metaruleVar, createMetaruleVar, addMetarule, testWithMetaRules, displayMetarules, legal, illegal, noPlayPlayer, immutableRule, autoDelete, eraseAllRules, getSelfRuleNumber, getSelfRule,+ onRuleProposed, showRule ) where @@ -124,8 +125,8 @@ return rs -- | activate or reject a rule-activateOrReject :: RuleInfo -> Bool -> Nomex ()-activateOrReject r b = if b then activateRule_ (_rNumber r) else rejectRule_ (_rNumber r)+activateOrRejectRule :: RuleInfo -> Bool -> Nomex ()+activateOrRejectRule r b = if b then activateRule_ (_rNumber r) else rejectRule_ (_rNumber r) -- | a rule can autodelete itself (generaly after having performed some actions) autoDelete :: Nomex ()@@ -197,7 +198,9 @@ simulate :: Nomex a -> NomexNE Bool -> NomexNE Bool simulate sim test = Simu sim test -+-- | sets a callback called for each rule proposed+onRuleProposed :: (RuleInfo -> Nomex ()) -> Nomex () +onRuleProposed f = void $ onEvent_ (ruleEvent Proposed) f -- | a default rule defaultRule = RuleInfo {
src/Language/Nomyx/Vote.hs view
@@ -11,315 +11,111 @@ import Prelude hiding (foldr) import Language.Nomyx.Expression import Language.Nomyx.Events-import Language.Nomyx.Variables-import Language.Nomyx.Outputs import Language.Nomyx.Inputs import Language.Nomyx.Players import Language.Nomyx.Rules -import Data.Typeable import Control.Monad.State hiding (forM_) import Data.Maybe import Data.Time hiding (getCurrentTime)-import Data.Traversable hiding (mapM) import Control.Arrow import Control.Applicative import Data.List-import Data.Function import qualified Data.Map as M-import Control.Monad.Error (MonadError(..)) -+-- | a vote assessing function (such as unanimity, majority...)+type AssessFunction = VoteStats -> Maybe Bool -data VoteType a = ExclusiveVote (Maybe (Alts a))- | NonExclusiveVote [Alts a]+-- | the vote statistics, including the number of votes per choice (Nothing means abstention),+-- the number of persons called to vote, and if the vote if finished (timeout or everybody voted)+data VoteStats = VoteStats { voteCounts :: M.Map (Maybe Bool) Int,+ nbParticipants :: Int,+ voteFinished :: Bool}+ deriving (Show) -class (Eq (Alts a), Show (Alts a), Ord (Alts a), Typeable a) => Votable a where- data Alts a- alts :: [Alts a] -- The alternatives- quota :: Alts a -> Int -> Int -> Int -- "quota alts q total" gives the quota to reach for each alternatives- name :: a -> String- exclusiveWinner :: a -> Maybe (Alts a, Alts a) --The Votable as only two alternatives, one excluding the other- exclusiveWinner _ = Nothing+-- | vote at unanimity every incoming rule+unanimityVote :: Nomex ()+unanimityVote = onRuleProposed $ callVoteRule unanimity oneDay -type ForAgainst = Alts RuleInfo -instance Votable RuleInfo where- data Alts RuleInfo = For | Against deriving (Typeable, Enum, Show, Eq, Bounded, Read, Ord)- alts = [For, Against]- quota For q _ = q- quota Against q vs = vs - q + 1- name r = "rule " ++ (show $ _rNumber r)- exclusiveWinner _ = Just (For, Against)+-- | call a vote on a rule for every players, with an assessing function and a delay+callVoteRule :: AssessFunction -> NominalDiffTime -> RuleInfo -> Nomex ()+callVoteRule assess delay ri = do+ endTime <- addUTCTime delay <$> liftEffect getCurrentTime+ callVoteRule' assess endTime ri - -type Vote a = (PlayerNumber, Maybe (Alts a))-type VoteResult a = VoteStats a -> [Alts a] -data VoteStats a = VoteStats { voteCounts :: M.Map (Maybe (Alts a)) Int,- voteFinished :: Bool}+callVoteRule' :: AssessFunction -> UTCTime -> RuleInfo -> Nomex ()+callVoteRule' assess endTime ri = do+ let title = "Vote for rule: \"" ++ (_rName ri) ++ "\" (#" ++ (show $ _rNumber ri) ++ "):"+ callVote assess endTime title (activateOrRejectRule ri) -data VoteData a = VoteData { msgEnd :: Msg [Alts a], -- message sent when the vote is finished with the winners - voteVar :: ArrayVar PlayerNumber (Alts a),-- variable containing the votes - inputNumbers :: [EventNumber], -- numbers of the toggle inputs of the players (to delete them at the end of vote) - assessFunction :: VoteResult a, -- function used to count the votes- timeLimit :: Maybe UTCTime} -- end of the vote- -type Assessor a = StateT (VoteData a) Nomex () +-- | call a vote for every players, with an assessing function, a delay and a function to run on the result+callVote :: AssessFunction -> UTCTime -> String -> (Bool -> Nomex ()) -> Nomex ()+callVote assess endTime title payload = do+ pns <- liftEffect getAllPlayerNumbers+ void $ onEventOnce (voteWith endTime pns assess title) payload ---TODO: decorelate effects and non effects--- | Perform a vote.-voteWith :: (Votable a) => VoteResult a -- ^ the function used to count the votes.- -> Assessor a -- ^ assessors: when and how to perform the vote assessment (several assessors can be chained).- -> a -- ^ toVote: the matter to be voted.- -> [Alts a] -- ^ the vote alternatives.- -> Nomex (Msg [Alts a]) -- ^ return value: a message containing the result of the vote. -voteWith countVotes assessors toVote als = do - pns <- liftEffect getAllPlayerNumbers - let toVoteName = name toVote - let msgEnd = Msg ("Result of votes for " ++ toVoteName) :: Msg [Alts a] - --create an array variable to store the votes - (voteVar :: ArrayVar PlayerNumber (Alts a)) <- newArrayVar_ ("Votes for " ++ toVoteName) pns - --create the voting buttons- let askPlayer pn = onInputRadioOnce ("Vote for " ++ toVoteName ++ ":") als (putArrayVar_ voteVar pn) pn - inputs <- mapM askPlayer pns - let voteData = VoteData msgEnd voteVar inputs countVotes Nothing - --set the assessors- evalStateT assessors voteData- --display the vote- displayVoteVar Nothing ("On-going vote for " ++ toVoteName ++ ":") voteVar- displayVoteResult toVoteName voteData- --clean the vote at the end- cleanVote voteData- return msgEnd+-- vote with a function able to assess the ongoing votes.+-- the vote can be concluded as soon as the result is known.+voteWith :: UTCTime -> [PlayerNumber] -> AssessFunction -> String -> Event Bool+voteWith timeLimit pns assess title = shortcutEvents (voteEvents timeLimit title pns) (assess . getVoteStats (length pns)) --- | Performs a vote, all the possible alternatives are selected.-voteWith_ :: (Votable a) => VoteResult a -> Assessor a -> a -> Nomex (Msg [Alts a]) -voteWith_ assessFunction assessors toVote = voteWith assessFunction assessors toVote alts +-- list of vote events+voteEvents :: UTCTime -> String -> [PlayerNumber] -> [Event (Maybe Bool)]+voteEvents time title pns = map (singleVote time title) pns - --- | assess the vote on every new vote with the assess function, and as soon as the vote has an issue (positive of negative), sends a signal -assessOnEveryVote :: (Votable a) => Assessor a -assessOnEveryVote = do - (VoteData msgEnd voteVar _ assess _) <- get - lift $ void $ onMsgVarEvent voteVar $ f assess msgEnd where- f assess msgEnd (VUpdated votes) = do- let res = assess $ getVoteStats votes False- unless (null res) $ sendMessage msgEnd res- f _ _ _ = return () - - --- | assess the vote with the assess function when time is reached, and sends a signal with the issue (positive of negative) -assessOnTimeLimit :: (Votable a) => UTCTime -> Assessor a -assessOnTimeLimit time = do - (VoteData msgEnd voteVar i assess _) <- get- put (VoteData msgEnd voteVar i assess (Just time)) - lift $ void $ onEvent_ (timeEvent time) $ \_ -> do - votes <- getMsgVarData_ voteVar - sendMessage msgEnd (assess $ getVoteStats votes True) +-- trigger the display of a radio button choice on the player screen, yelding either Just True or Just False.+-- after the time limit, the value sent back is Nothing.+singleVote :: UTCTime -> String -> PlayerNumber -> Event (Maybe Bool)+singleVote timeLimit title pn = (Just <$> inputRadio pn title [(True, "For"), (False, "Against")]) <|> (Nothing <$ timeEvent timeLimit) - --- | assess the vote with the assess function when time is elapsed, and sends a signal with the issue (positive of negative) -assessOnTimeDelay :: (Votable a) => NominalDiffTime -> Assessor a -assessOnTimeDelay delay = do - t <- addUTCTime delay <$> lift (liftEffect getCurrentTime) - assessOnTimeLimit t - --- | assess the vote only when every body voted. An error is generated if the assessing function returns Nothing. -assessWhenEverybodyVoted :: (Votable a) => Assessor a -assessWhenEverybodyVoted = do - (VoteData msgEnd voteVar _ assess _) <- get - lift $ void $ onMsgVarEvent voteVar $ f assess msgEnd where- f assess msgEnd (VUpdated votes) = when (all isJust (map snd votes)) $ sendMessage msgEnd $ assess $ getVoteStats votes True - f _ _ _ = return () - --- | clean events and variables necessary for the vote -cleanVote :: (Votable a) => VoteData a -> Nomex EventNumber -cleanVote (VoteData msgEnd voteVar inputsNumber _ _) = onMessageOnce msgEnd $ \_ -> do - delMsgVar voteVar - mapM_ delEvent inputsNumber - --- | a quorum is the neccessary number of voters for the validity of the vote -quorum :: (Votable a) => Int -> VoteStats a -> Bool -quorum q vs = (voted vs) >= q - --- | adds a quorum to an assessing function -withQuorum :: (Votable a) => VoteResult a -> Int -> VoteResult a -withQuorum assess q vs = if quorum q vs then assess vs else [] +-- | assess the vote results according to a unanimity+unanimity :: AssessFunction+unanimity voteStats = voteQuota (nbVoters voteStats) voteStats --- | assess the vote results according to a unanimity (everybody votes for) -unanimity :: (Votable a) => VoteStats a -> [Alts a] -unanimity vs = voteQuota (nbVoters vs) vs - --- | assess the vote results according to an absolute majority (half voters plus one, no quorum is needed) -majority :: (Votable a) => VoteStats a -> [Alts a] -majority vs = voteQuota ((nbVoters vs) `div` 2 + 1) vs - +-- | assess the vote results according to an absolute majority (half voters plus one) +majority :: AssessFunction +majority voteStats = voteQuota ((nbVoters voteStats) `div` 2 + 1) voteStats+ -- | assess the vote results according to a majority of x (in %) -majorityWith :: (Votable a) => Int -> VoteStats a -> [Alts a] -majorityWith x vs = voteQuota ((nbVoters vs) * x `div` 100 + 1) vs - --- | assess the vote results according to a necessary number of positive votes -numberVotes :: (Votable a) => Int -> VoteStats a -> [Alts a] -numberVotes = voteQuota ---- | the winners are the x vote alternatives with the more votes -firstXBest :: forall a. (Votable a) => Int -> VoteStats a -> [Alts a] -firstXBest x votes = map fst $ takeInGroups x sortedVotesWithExAequoGroups where- voted (Just a, n) = Just (a, n)- voted (Nothing, _) = Nothing- sortedVotesWithExAequoGroups :: [[(Alts a, Int)]]- sortedVotesWithExAequoGroups = groupBy ((==) `on` snd) $ sortWith snd $ mapMaybe voted $ M.assocs (voteCounts votes)+majorityWith :: Int -> AssessFunction +majorityWith x voteStats = voteQuota ((nbVoters voteStats) * x `div` 100 + 1) voteStats ---take n elements from the first lists, but do not break groups-takeInGroups :: Int -> [[a]] -> [a]-takeInGroups 0 _ = []-takeInGroups n grps = if grpsSize <= n then head grps ++ takeInGroups (n - grpsSize) (tail grps) else head grps where- grpsSize = length $ head grps+-- | assess the vote results according to a fixed number of positive votes+numberVotes :: Int -> AssessFunction+numberVotes x voteStats = voteQuota x voteStats+ +-- | adds a quorum to an assessing function +withQuorum :: AssessFunction -> Int -> AssessFunction +withQuorum f minNbVotes = \voteStats -> if (voted voteStats) >= minNbVotes+ then f voteStats+ else if voteFinished voteStats then Just False else Nothing --- | the winner is the vote alternative with the more votes -firstBest :: (Votable a) => VoteStats a -> [Alts a] -firstBest = firstXBest 1+getVoteStats :: Int -> [Maybe Bool] -> VoteStats+getVoteStats npPlayers votes = VoteStats+ {voteCounts = M.fromList $ counts votes,+ nbParticipants = npPlayers,+ voteFinished = length votes == npPlayers} -sortWith :: Ord b => (a -> b) -> [a] -> [a]-sortWith f = sortBy (\x y -> compare (f x) (f y))- --- | return the vote alternatives that are above threshold -voteQuota :: forall a. (Votable a) => Int -> VoteStats a -> [Alts a] -voteQuota q votes = case exclusiveWinner (undefined :: a) of- Nothing -> catMaybes $ M.keys $ M.filter (>= q) (voteCounts votes)- Just a -> maybeToList $ exclusiveVoteQuota q votes a+counts :: (Eq a, Ord a) => [a] -> [(a, Int)]+counts as = map (head &&& length) (group $ sort as) --- | in case of exclusive winner-exclusiveVoteQuota :: (Votable a) => Int -> VoteStats a -> (Alts a, Alts a) -> Maybe (Alts a)-exclusiveVoteQuota q votes (for, against)- | M.findWithDefault 0 (Just for) vs >= q = Just for- | M.findWithDefault 0 (Just against) vs > (nbVoters votes) - q = Just against+-- | Compute a result based on a quota of positive votes.+-- the result can be positive if the quota if reached, negative if the quota cannot be reached anymore at that point, or still pending.+voteQuota :: Int -> VoteStats -> Maybe Bool+voteQuota q voteStats+ | M.findWithDefault 0 (Just True) vs >= q = Just True+ | M.findWithDefault 0 (Just False) vs > (nbVoters voteStats) - q = Just False | otherwise = Nothing- where vs = voteCounts votes+ where vs = voteCounts voteStats -- | number of people that voted if the voting is finished, -- total number of people that should vote otherwise-nbVoters :: (Votable a) => VoteStats a -> Int+nbVoters :: VoteStats -> Int nbVoters vs- | voteFinished vs = (totalVoters vs) - (notVoted vs)- | otherwise = totalVoters vs+ | voteFinished vs = (nbParticipants vs) - (notVoted vs)+ | otherwise = nbParticipants vs -totalVoters, voted, notVoted :: (Votable a) => VoteStats a -> Int+totalVoters, voted, notVoted :: VoteStats -> Int totalVoters vs = M.foldr (+) 0 (voteCounts vs) notVoted vs = fromMaybe 0 $ M.lookup Nothing (voteCounts vs) voted vs = (totalVoters vs) - (notVoted vs)---getVoteStats :: (Votable a) => [Vote a] -> Bool -> VoteStats a-getVoteStats vs voteFinished = VoteStats- {voteCounts = M.fromList $ counts (snd <$> vs),- voteFinished = voteFinished}--counts :: (Eq a, Ord a) => [a] -> [(a, Int)]-counts as = map (head &&& length) (group $ sort as)---displayVoteVar :: (Votable a) => (Maybe PlayerNumber) -> String -> ArrayVar PlayerNumber (Alts a) -> Nomex EventNumber-displayVoteVar mpn title mv = displayVar' mpn mv (showOnGoingVote title)--showChoice :: (Votable a) => Maybe (Alts a) -> String-showChoice (Just a) = show a-showChoice Nothing = "Not Voted"--showChoices :: (Votable a) => [Alts a] -> String-showChoices [] = "no result"-showChoices cs = intercalate ", " $ map show cs--showOnGoingVote :: (Votable a) => String -> [(PlayerNumber, Maybe (Alts a))] -> NomexNE String-showOnGoingVote title listVotes = do- list <- mapM showVote listVotes- return $ title ++ "\n" ++ concatMap (\(name, vote) -> name ++ "\t" ++ vote ++ "\n") list--showFinishedVote :: (Votable a) => [(PlayerNumber, Maybe (Alts a))] -> NomexNE String-showFinishedVote l = do- let voted = filter (\(_, r) -> isJust r) l- votes <- mapM showVote voted- return $ intercalate ", " $ map (\(name, vote) -> name ++ ": " ++ vote) votes--showVote :: (Votable a) => (PlayerNumber, Maybe (Alts a)) -> NomexNE (String, String)-showVote (pn, v) = do- name <- showPlayer pn- return (name, showChoice v)- -displayVoteResult :: (Votable a) => String -> VoteData a -> Nomex OutputNumber-displayVoteResult toVoteName (VoteData msgEnd voteVar _ _ _) = onMessage msgEnd $ \result -> do - vs <- getMsgVarData_ voteVar- votes <- liftEffect $ showFinishedVote vs- void $ outputAll_ $ "Vote result for " ++ toVoteName ++ ": " ++ showChoices result ++ " (" ++ votes ++ ")"---- | any new rule will be activate if the rule in parameter returns For -onRuleProposed :: (RuleInfo -> Nomex (Msg [ForAgainst]) ) -> Rule -onRuleProposed f = void $ onEvent_ (ruleEvent Proposed) $ \rule -> do - resp <- f rule- void $ onMessageOnce resp $ (activateOrReject rule) . (== [For])---- * Referendum & elections--data Referendum = Referendum String deriving (Typeable)-instance Votable Referendum where- data Alts Referendum = Yes | No deriving (Enum, Show, Eq, Bounded, Read, Ord)- alts = [Yes, No]- quota Yes q _ = q- quota No q vs = vs - q + 1- name (Referendum n) = "referendum on " ++ n--referendum :: String -> Nomex () -> Rule-referendum name action = do- msg <- voteWith_ (majority `withQuorum` 2) (assessOnEveryVote >> assessOnTimeDelay oneDay) (Referendum name)- void $ onMessageOnce msg resolution where- resolution [Yes] = do- outputAll_ "Positive result of referendum"- action- resolution [No] = void $ outputAll_ "Negative result of referendum"- resolution [] = void $ outputAll_ "No result for referendum"- resolution _ = throwError "Impossible result for referendum"---data Election = Election String deriving (Typeable)-instance Votable Election where- data Alts Election = Candidate {candidate :: PlayerInfo}- alts = map (\n -> Candidate (PlayerInfo n "" Nothing)) [1..]- quota _ q _ = q- name (Election n) = "elections on " ++ n--instance Show (Alts Election) where- show (Candidate (PlayerInfo _ name _)) = name--instance Eq (Alts Election) where- (Candidate (PlayerInfo pn1 _ _)) == (Candidate (PlayerInfo pn2 _ _)) = pn1 == pn2--instance Ord (Alts Election) where- compare (Candidate (PlayerInfo pn1 _ _)) (Candidate (PlayerInfo pn2 _ _)) = compare pn1 pn2--elections :: String -> [PlayerInfo] -> (PlayerNumber -> Nomex ()) -> Nomex ()-elections name pns action = do- msg <- voteWith majority (assessOnTimeDelay oneDay) (Election name) (Candidate <$> pns)- void $ onMessageOnce msg resolution where- resolution [Candidate pi] = do- outputAll_ $ "Result of elections: player(s) " ++ (show $ _playerName pi) ++ " won!"- action $ _playerNumber pi- resolution [] = void $ outputAll_ "Result of elections: nobody won!"- resolution _ = throwError "Impossible result for elections"--voteEvent :: UTCTime -> [PlayerNumber] -> Event ([Maybe Bool])-voteEvent time pns = sequenceA $ map (singleVote time) pns--singleVote :: UTCTime -> PlayerNumber -> Event (Maybe Bool)-singleVote timeLimit pn = (Just <$> inputRadio pn "Vote for "[True, False] True) <|> (Nothing <$ timeEvent timeLimit)--vote :: UTCTime -> [PlayerNumber] -> Event Bool-vote timeLimit pns = unanimity' <$> (voteEvent timeLimit pns)--unanimity' :: [Maybe Bool] -> Bool-unanimity' = all (== Just True)--callVote :: UTCTime -> Nomex ()-callVote t = do- pns <- liftEffect getAllPlayerNumbers- void $ onEventOnce (vote t pns) (outputAll_ . show)--