packages feed

werewolf 1.2.0.3 → 1.3.0.0

raw patch · 58 files changed

+545/−165 lines, 58 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ Game.Werewolf.Game: NoRoleReveal :: Variant
+ Game.Werewolf.Game: _NoRoleReveal :: Prism' Variant ()
+ Game.Werewolf.Game: activity :: Contravariant f => (Activity -> f Activity) -> Stage -> f Stage
+ Game.Werewolf.Game: getMarks :: Game -> [Player]
+ Game.Werewolf.Game: hasDullahanWon :: Game -> Bool
+ Game.Werewolf.Game: marks :: Lens' Game [Text]
+ Game.Werewolf.Player: dullahan :: Traversal' Player ()
+ Game.Werewolf.Player: dullahans :: Traversable t => Traversal' (t Player) Player
+ Game.Werewolf.Role: Diurnal :: Activity
+ Game.Werewolf.Role: Nocturnal :: Activity
+ Game.Werewolf.Role: _Diurnal :: Prism' Activity ()
+ Game.Werewolf.Role: _Nocturnal :: Prism' Activity ()
+ Game.Werewolf.Role: activity :: Lens' Role Activity
+ Game.Werewolf.Role: data Activity
+ Game.Werewolf.Role: dullahanRole :: Role
+ Game.Werewolf.Role: instance Data.String.Humanise.Humanise Game.Werewolf.Role.Activity
+ Game.Werewolf.Role: instance GHC.Classes.Eq Game.Werewolf.Role.Activity
+ Game.Werewolf.Role: instance GHC.Read.Read Game.Werewolf.Role.Activity
+ Game.Werewolf.Role: instance GHC.Show.Show Game.Werewolf.Role.Activity

Files

CHANGELOG.md view
@@ -7,6 +7,26 @@  ### Upcoming +### v1.3.0.0++*Major*++* Added activity field to Role. ([#231](https://github.com/hjwylde/werewolf/issues/231))+* Added the Dullahan role. ([#202](https://github.com/hjwylde/werewolf/issues/202))++*Minor*++* Added player roles message when the game is force ended. ([#225](https://github.com/hjwylde/werewolf/issues/225))+* Added the no-role-reveal variant. ([#181](https://github.com/hjwylde/werewolf/issues/181))+* Added a public start-of-game message for the Spiteful Ghost. ([#220](https://github.com/hjwylde/werewolf/issues/220))+* Removed the public start-of-game message for the Fallen Angel. ([#229](https://github.com/hjwylde/werewolf/issues/229))+* Updated role balances based on Simple Werewolf being -5. ([#233](https://github.com/hjwylde/werewolf/issues/233))++*Revisions*++* Fixed current stage command to declare diurnal turns. ([#231](https://github.com/hjwylde/werewolf/issues/231))+* Fixed ping command to declare diurnal roles. ([#231](https://github.com/hjwylde/werewolf/issues/231))+ ### v1.2.0.3  *Revisions*
README.md view
@@ -48,6 +48,7 @@  The Loners must complete their own objective. +* Dullahan * Fallen Angel * Spiteful Ghost 
app/Game/Werewolf/Command/Global.hs view
@@ -17,6 +17,7 @@ import Control.Lens import Control.Monad.Except import Control.Monad.Extra+import Control.Monad.State import Control.Monad.Writer  import qualified Data.Map   as Map@@ -50,6 +51,6 @@      caller <- findPlayerBy_ name callerName -    tell [playerQuitMessage caller]+    tell . (:[]) . playerQuitMessage caller =<< get      removePlayer callerName
app/Game/Werewolf/Command/Hunter.hs view
@@ -17,6 +17,7 @@ import Control.Lens import Control.Monad.Except import Control.Monad.Extra+import Control.Monad.State import Control.Monad.Writer  import Data.Text (Text)@@ -37,7 +38,7 @@      target <- findPlayerBy_ name targetName -    tell [playerShotMessage target]+    tell . (:[]) . playerShotMessage target =<< get     killPlayer targetName      hunterRetaliated .= True
app/Game/Werewolf/Command/Protector.hs view
@@ -14,7 +14,6 @@     protectCommand, ) where -import Control.Lens import Control.Lens.Extra import Control.Monad.Except import Control.Monad.Extra
app/Game/Werewolf/Command/Status.hs view
@@ -25,11 +25,12 @@ import Data.Text (Text)  -- TODO (hjw): remove Message.Engine-import Game.Werewolf                 hiding (getPendingVoters)-import Game.Werewolf.Command-import Game.Werewolf.Message.Command-import Game.Werewolf.Message.Engine-import Game.Werewolf.Util+import           Game.Werewolf                 hiding (getPendingVoters)+import           Game.Werewolf.Command+import           Game.Werewolf.Message.Command+import           Game.Werewolf.Message.Engine+import qualified Game.Werewolf.Role            as Role+import           Game.Werewolf.Util  circleCommand :: Text -> Bool -> Command circleCommand callerName includeDead = Command $ do@@ -60,8 +61,12 @@ pingRole role' = do     player <- findPlayerBy_ role role' -    tell . (:[]) . pingRoleMessage role' =<< get+    tell . (:[]) . pingRoleMessage =<< get     tell [pingPlayerMessage $ player ^. name]+    where+        pingRoleMessage game+            | has (Role.activity . _Diurnal) role'   = pingDiurnalRoleMessage role'+            | otherwise                         = pingNocturnalRoleMessage role' game  pingVillagers :: (MonadState Game m, MonadWriter [Message] m) => m () pingVillagers = do@@ -82,7 +87,12 @@ statusCommand callerName = Command $ do     game <- get -    tell [ currentStageMessage callerName game+    tell [ currentStageMessage game         , rolesInGameMessage (Just callerName) game         , playersInGameMessage callerName game         ]+    where+        currentStageMessage game+            | has (stage . _GameOver) game              = gameIsOverMessage callerName+            | has (stage . activity . _Diurnal) game    = currentDiurnalTurnMessage callerName game+            | otherwise                                 = currentNocturnalTurnMessage callerName game
app/Game/Werewolf/Command/Witch.hs view
@@ -17,7 +17,6 @@     healCommand, passCommand, poisonCommand, ) where -import Control.Lens import Control.Lens.Extra import Control.Monad.Except import Control.Monad.Extra
app/Game/Werewolf/Engine.hs view
@@ -17,7 +17,6 @@     checkStage, checkGameOver, ) where -import Control.Lens         hiding (cons, isn't) import Control.Lens.Extra import Control.Monad.Except import Control.Monad.Extra@@ -30,8 +29,7 @@ import           Data.Maybe  -- TODO (hjw): remove Message.Command-import Game.Werewolf.Game            hiding (getAllowedVoters, getPendingVoters, hasAnyoneWon,-                                      hasFallenAngelWon, hasVillagersWon, hasWerewolvesWon)+import Game.Werewolf.Game            hiding (getAllowedVoters, getPendingVoters, hasAnyoneWon) import Game.Werewolf.Message.Command import Game.Werewolf.Message.Engine import Game.Werewolf.Player@@ -57,7 +55,7 @@     bootees     <- mapM (findPlayerBy_ name) booteeNames      forM_ (filter (is alive) bootees) $ \bootee -> do-        tell [playerBootedMessage bootee]+        tell . (:[]) . playerBootedMessage bootee =<< get          removePlayer (bootee ^. name) @@ -124,7 +122,7 @@             target <- findPlayerBy_ name targetName              killPlayer targetName-            tell [playerPoisonedMessage target]+            tell . (:[]) . playerPoisonedMessage target =<< get          whenJustM (preuse $ players . seers . alive) $ \seer -> do             target <- use see >>= findPlayerBy_ name . fromJust@@ -197,10 +195,13 @@     | is fallenAngel votee  = do         fallenAngelLynched .= True -        tell [playerLynchedMessage votee]+        tell . (:[]) . playerLynchedMessage votee =<< get+    | is werewolf votee     = do+        killPlayer (votee ^. name)+        tell . (:[]) . werewolfLynchedMessage votee =<< get     | otherwise             = do         killPlayer (votee ^. name)-        tell [playerLynchedMessage votee]+        tell . (:[]) . playerLynchedMessage votee =<< get lynchVotee _            = preuse (players . scapegoats . alive) >>= \mScapegoat -> case mScapegoat of     Just scapegoat  -> do         scapegoatBlamed .= True@@ -213,11 +214,11 @@ devourVotee Nothing         = tell [noPlayerDevouredMessage] devourVotee (Just votee)    = do     killPlayer (votee ^. name)-    tell [playerDevouredMessage votee]+    tell . (:[]) . playerDevouredMessage votee =<< get      when (is medusa votee) . whenJustM (getFirstAdjacentAliveWerewolf $ votee ^. name) $ \werewolf -> do         killPlayer (werewolf ^. name)-        tell [playerTurnedToStoneMessage werewolf]+        tell . (:[]) . playerTurnedToStoneMessage werewolf =<< get  advanceStage :: (MonadState Game m, MonadWriter [Message] m) => m () advanceStage = do
app/Game/Werewolf/Message.hs view
@@ -23,7 +23,6 @@     humanisePlayerWithState, article, conjugateToBe, pluralise, ) where -import Control.Lens       hiding (isn't) import Control.Lens.Extra  import           Data.String.Humanise
app/Game/Werewolf/Message/Command.hs view
@@ -33,10 +33,11 @@     standardCommandsMessage, statusCommandsMessage, witchCommandsMessage,      -- * Ping-    pingPlayerMessage, pingRoleMessage, pingVillageMessage, pingWerewolvesMessage,+    pingDiurnalRoleMessage, pingNocturnalRoleMessage, pingPlayerMessage, pingVillageMessage,+    pingWerewolvesMessage,      -- * Status-    currentStageMessage, gameIsOverMessage, playersInGameMessage,+    currentDiurnalTurnMessage, currentNocturnalTurnMessage, gameIsOverMessage, playersInGameMessage,      -- * Unvote     playerRescindedVoteMessage,@@ -59,10 +60,13 @@ import Game.Werewolf.Response import Game.Werewolf.Role import Game.Werewolf.Variant.NoRoleKnowledge.Command as NoRoleKnowledge+import Game.Werewolf.Variant.NoRoleReveal.Command    as NoRoleReveal import Game.Werewolf.Variant.Standard.Command        as Standard -playerQuitMessage :: Player -> Message-playerQuitMessage = publicMessage . callerQuitText+playerQuitMessage :: Player -> Game -> Message+playerQuitMessage player game+    | has (variant . _NoRoleReveal) game    = publicMessage $ NoRoleReveal.callerQuitText player+    | otherwise                             = publicMessage $ Standard.callerQuitText player  playerVotedToBootMessage :: Player -> Player -> Message playerVotedToBootMessage caller = publicMessage . callerVotedBootText caller@@ -70,8 +74,10 @@ circleMessage :: Text -> [Player] -> Message circleMessage to = privateMessage to . gameCircleText -playerShotMessage :: Player -> Message-playerShotMessage = publicMessage . playerShotText+playerShotMessage :: Player -> Game -> Message+playerShotMessage player game+    | has (variant . _NoRoleReveal) game    = publicMessage $ NoRoleReveal.playerShotText player+    | otherwise                             = publicMessage $ Standard.playerShotText player  gameEndedMessage :: Text -> Message gameEndedMessage = publicMessage . gameEndedText@@ -148,28 +154,36 @@ witchCommandsMessage :: Text -> Message witchCommandsMessage to = privateMessage to witchCommandsText +pingDiurnalRoleMessage :: Role -> Message+pingDiurnalRoleMessage role = publicMessage $ diurnalRolePingedText role++pingNocturnalRoleMessage :: Role -> Game -> Message+pingNocturnalRoleMessage role game+    | has (variant . _NoRoleKnowledge) game = publicMessage $ NoRoleKnowledge.nocturnalRolePingedText role+    | has (variant . _NoRoleReveal) game    = publicMessage $ NoRoleReveal.nocturnalRolePingedText role+    | otherwise                             = publicMessage $ Standard.nocturnalRolePingedText role+ pingPlayerMessage :: Text -> Message pingPlayerMessage to = privateMessage to playerPingedText -pingRoleMessage :: Role -> Game -> Message-pingRoleMessage role game-    | has (variant . _NoRoleKnowledge) game = publicMessage $ NoRoleKnowledge.rolePingedText role-    | otherwise                             = publicMessage $ Standard.rolePingedText role- pingVillageMessage :: Message pingVillageMessage = publicMessage villagePingedText  pingWerewolvesMessage :: Game -> Message pingWerewolvesMessage game     | has (variant . _NoRoleKnowledge) game = publicMessage NoRoleKnowledge.werewolvesPingedText+    | has (variant . _NoRoleReveal) game    = publicMessage NoRoleReveal.werewolvesPingedText     | otherwise                             = publicMessage Standard.werewolvesPingedText -currentStageMessage :: Text -> Game -> Message-currentStageMessage to game-    | has (stage . _GameOver) game          = gameIsOverMessage to-    | has (variant . _NoRoleKnowledge) game = privateMessage to $ NoRoleKnowledge.currentTurnText game-    | otherwise                             = privateMessage to $ Standard.currentTurnText game+currentDiurnalTurnMessage :: Text -> Game -> Message+currentDiurnalTurnMessage to game = privateMessage to $ Standard.currentDiurnalTurnText game +currentNocturnalTurnMessage :: Text -> Game -> Message+currentNocturnalTurnMessage to game+    | has (variant . _NoRoleKnowledge) game = privateMessage to $ NoRoleKnowledge.currentNocturnalTurnText game+    | has (variant . _NoRoleReveal) game    = privateMessage to $ NoRoleReveal.currentNocturnalTurnText game+    | otherwise                             = privateMessage to $ Standard.currentNocturnalTurnText game+ gameIsOverMessage :: Text -> Message gameIsOverMessage to = privateMessage to gameOverText @@ -178,8 +192,9 @@     where         alivePlayersText' = alivePlayersText game         deadPlayersText'-            | has (players . traverse . dead) game  = deadPlayersText game-            | otherwise                             = T.empty+            | hasn't (players . traverse . dead) game   = T.empty+            | has (variant . _NoRoleReveal) game        = NoRoleReveal.deadPlayersText game+            | otherwise                                 = Standard.deadPlayersText game  playerRescindedVoteMessage :: Text -> Player -> Message playerRescindedVoteMessage to = privateMessage to . callerRescindedVoteText
app/Game/Werewolf/Message/Engine.hs view
@@ -12,7 +12,7 @@  -- TODO (hjw): sort this file module Game.Werewolf.Message.Engine (-    playerBootedMessage, villageDrunkJoinedPackMessages, villageDrunkJoinedVillageMessage,+    playerRolesMessage, playerBootedMessage, villageDrunkJoinedPackMessages, villageDrunkJoinedVillageMessage,     playerTurnedToStoneMessage, playerSeenMessage, playerDivinedMessage, playerDevouredMessage,     noPlayerDevouredMessage, scapegoatChoseAllowedVotersMessage, playerPoisonedMessage,     scapegoatLynchedMessage, playerLynchedMessage, noPlayerLynchedMessage, jesterLynchedMessage,@@ -20,13 +20,12 @@     playerContributedMessage, playerWonMessage, witchsTurnMessages, firstWerewolvesTurnMessages,     villagesTurnMessage, nightFallsMessage, sunriseMessage, villageDrunksTurnMessages,     seersTurnMessages, scapegoatsTurnMessage, protectorsTurnMessages, orphansTurnMessages,-    oraclesTurnMessages, huntersTurnMessages, stageMessages, fallenAngelMessage,-    trueVillagerMessage, spitefulGhostMessage, beholderMessage, newPlayerMessage,-    rolesInGameMessage, newPlayersInGameMessage, newGameMessages, gameOverMessages,-    fallenAngelWonMessage,+    oraclesTurnMessages, huntersTurnMessages, stageMessages,+    trueVillagerMessage, beholderMessage, newPlayerMessage, rolesInGameMessage,+    newPlayersInGameMessage, newGameMessages, gameOverMessages, fallenAngelWonMessage,+    werewolfLynchedMessage, ) where -import Control.Lens import Control.Lens.Extra  import Data.List.Extra@@ -37,13 +36,22 @@ import Game.Werewolf.Response import Game.Werewolf.Role                           hiding (name) import Game.Werewolf.Variant.NoRoleKnowledge.Engine as NoRoleKnowledge+import Game.Werewolf.Variant.NoRoleReveal.Engine    as NoRoleReveal import Game.Werewolf.Variant.Standard.Engine        as Standard -playerBootedMessage :: Player -> Message-playerBootedMessage = publicMessage . playerBootedText+playerBootedMessage :: Player -> Game -> Message+playerBootedMessage player game+    | has (variant . _NoRoleReveal) game    = publicMessage $ NoRoleReveal.playerBootedText player+    | otherwise                             = publicMessage $ Standard.playerBootedText player  gameOverMessages :: Game -> [Message] gameOverMessages game+    | hasDullahanWon game    = concat+        [ [dullahanWonMessage game]+        , [playerRolesMessage game]+        , [playerWonMessage dullahansName]+        , map playerLostMessage (game ^.. players . names \\ [dullahansName])+        ]     | hasFallenAngelWon game    = concat         [ [fallenAngelWonMessage]         , [playerRolesMessage game]@@ -70,8 +78,12 @@         playerContributedMessages   = map playerContributedMessage (winningPlayers ^.. traverse . dead . name)         playerLostMessages          = map playerLostMessage (losingPlayers ^.. names) -        fallenAngelsName = game ^?! players . fallenAngels . name+        dullahansName       = game ^?! players . dullahans . name+        fallenAngelsName    = game ^?! players . fallenAngels . name +dullahanWonMessage :: Game -> Message+dullahanWonMessage = publicMessage . dullahanWonText+ fallenAngelWonMessage :: Message fallenAngelWonMessage = publicMessage fallenAngelWonText @@ -87,23 +99,33 @@     , [rolesInGameMessage Nothing game]     , map newPlayerMessage players'     , beholderMessages-    , spitefulGhostMessages+    , dullahanMessages+    , spitefulGhostMessages'     , trueVillagerMessages-    , fallenAngelMessages     , stageMessages game     ]     where         players'                = game ^. players-        beholderMessages        = case (,) <$> players' ^? beholders <*> players' ^? seers of-            Just (beholder, _)  -> [beholderMessage (beholder ^. name) game]-            _                   -> []-        spitefulGhostMessages   = case players' ^? spitefulGhosts of-            Just spitefulGhost  -> [spitefulGhostMessage (spitefulGhost ^. name) game]-            _                   -> []-        trueVillagerMessages    = [trueVillagerMessage game | has trueVillagers players']-        fallenAngelMessages     = if has fallenAngels players'-            then [fallenAngelMessage]-            else []+        beholderMessages        =+            [ beholderMessage beholderName game+            | has beholders players'+            , has seers players'+            , beholderName <- players' ^.. beholders . name+            ]+        dullahanMessages        =+            [ dullahanMessage dullahanName game+            | has dullahans players'+            , dullahanName <- players' ^.. dullahans . name+            ]+        spitefulGhostMessages'+            | has spitefulGhosts players'   = spitefulGhostMessages spitefulGhostName game+            | otherwise                     = []+            where+                spitefulGhostName = players' ^?! spitefulGhosts . name+        trueVillagerMessages    =+            [ trueVillagerMessage game+            | has trueVillagers players'+            ]  newPlayersInGameMessage :: Game -> Message newPlayersInGameMessage = publicMessage . playersInGameText@@ -119,15 +141,18 @@ beholderMessage :: Text -> Game -> Message beholderMessage to = privateMessage to . beholderText -spitefulGhostMessage :: Text -> Game -> Message-spitefulGhostMessage to = privateMessage to . spitefulGhostText+dullahanMessage :: Text -> Game -> Message+dullahanMessage to = privateMessage to . dullahanText +spitefulGhostMessages :: Text -> Game -> [Message]+spitefulGhostMessages to game =+    [ privateMessage to $ spitefulGhostPrivateText game+    , publicMessage $ spitefulGhostPublicText game+    ]+ trueVillagerMessage :: Game -> Message trueVillagerMessage = publicMessage . trueVillagerText -fallenAngelMessage :: Message-fallenAngelMessage = publicMessage fallenAngelText- stageMessages :: Game -> [Message] stageMessages game = case game ^. stage of     DruidsTurn          -> []@@ -168,6 +193,8 @@ oraclesTurnMessages to game     | has (variant . _NoRoleKnowledge) game =         [ privateMessage to oraclesTurnPrivateText ]+    | has (variant . _NoRoleReveal) game    =+        [ privateMessage to oraclesTurnPrivateText ]     | otherwise                             =         [ publicMessage oraclesTurnPublicText         , privateMessage to oraclesTurnPrivateText@@ -177,6 +204,8 @@ orphansTurnMessages to game     | has (variant . _NoRoleKnowledge) game =         [ privateMessage to orphansTurnPrivateText ]+    | has (variant . _NoRoleReveal) game    =+        [ privateMessage to orphansTurnPrivateText ]     | otherwise                             =         [ publicMessage orphansTurnPublicText         , privateMessage to orphansTurnPrivateText@@ -186,6 +215,8 @@ protectorsTurnMessages to game     | has (variant . _NoRoleKnowledge) game =         [ privateMessage to protectorsTurnPrivateText ]+    | has (variant . _NoRoleReveal) game    =+        [ privateMessage to protectorsTurnPrivateText ]     | otherwise                             =         [ publicMessage protectorsTurnPublicText         , privateMessage to protectorsTurnPrivateText@@ -198,6 +229,8 @@ seersTurnMessages to game     | has (variant . _NoRoleKnowledge) game =         [ privateMessage to seersTurnPrivateText ]+    | has (variant . _NoRoleReveal) game    =+        [ privateMessage to seersTurnPrivateText ]     | otherwise                             =         [ publicMessage seersTurnPublicText         , privateMessage to seersTurnPrivateText@@ -206,6 +239,7 @@ villageDrunksTurnMessages :: Game -> [Message] villageDrunksTurnMessages game     | has (variant . _NoRoleKnowledge) game = []+    | has (variant . _NoRoleReveal) game    = []     | otherwise                             = [publicMessage villageDrunksTurnText]  sunriseMessage :: Message@@ -229,6 +263,8 @@ werewolvesTurnMessages tos game     | has (variant . _NoRoleKnowledge) game =         groupMessages tos werewolvesTurnPrivateText+    | has (variant . _NoRoleReveal) game    =+        groupMessages tos werewolvesTurnPrivateText     | otherwise                             =         publicMessage werewolvesTurnPublicText         : groupMessages tos werewolvesTurnPrivateText@@ -244,6 +280,7 @@         to              = game ^?! players . witches . name         wakeUpMessages             | has (variant . _NoRoleKnowledge) game = []+            | has (variant . _NoRoleReveal) game    = []             | otherwise                             = [publicMessage witchsTurnText]         passMessage     = privateMessage to passText         healMessages@@ -283,12 +320,16 @@ noPlayerLynchedMessage :: Message noPlayerLynchedMessage = publicMessage noPlayerLynchedText -playerLynchedMessage :: Player -> Message-playerLynchedMessage player-    | is simpleWerewolf player-        || is alphaWolf player  = publicMessage $ werewolfLynchedText player-    | otherwise                 = publicMessage $ playerLynchedText player+werewolfLynchedMessage :: Player -> Game -> Message+werewolfLynchedMessage player game+    | has (variant . _NoRoleReveal) game    = publicMessage $ NoRoleReveal.playerLynchedText player+    | otherwise                             = publicMessage $ Standard.werewolfLynchedText player +playerLynchedMessage :: Player -> Game -> Message+playerLynchedMessage player game+    | has (variant . _NoRoleReveal) game    = publicMessage $ NoRoleReveal.playerLynchedText player+    | otherwise                             = publicMessage $ Standard.playerLynchedText player+ scapegoatLynchedMessage :: Game -> Message scapegoatLynchedMessage = publicMessage . scapegoatLynchedText @@ -298,14 +339,18 @@ noPlayerDevouredMessage :: Message noPlayerDevouredMessage = publicMessage noPlayerDevouredText -playerDevouredMessage :: Player -> Message-playerDevouredMessage = publicMessage . playerDevouredText+playerDevouredMessage :: Player -> Game -> Message+playerDevouredMessage player game+    | has (variant . _NoRoleReveal) game    = publicMessage $ NoRoleReveal.playerDevouredText player+    | otherwise                             = publicMessage $ Standard.playerDevouredText player  playerDivinedMessage :: Text -> Player -> Message playerDivinedMessage to = privateMessage to . playerDivinedText -playerPoisonedMessage :: Player -> Message-playerPoisonedMessage = publicMessage . playerPoisonedText+playerPoisonedMessage :: Player -> Game -> Message+playerPoisonedMessage player game+    | has (variant . _NoRoleReveal) game    = publicMessage $ NoRoleReveal.playerPoisonedText player+    | otherwise                             = publicMessage $ Standard.playerPoisonedText player  playerSeenMessage :: Text -> Player -> Message playerSeenMessage to player@@ -313,8 +358,10 @@     | is lycan player       = privateMessage to $ lycanSeenText player     | otherwise             = privateMessage to $ playerSeenText player -playerTurnedToStoneMessage :: Player -> Message-playerTurnedToStoneMessage = publicMessage . playerTurnedToStoneText+playerTurnedToStoneMessage :: Player -> Game -> Message+playerTurnedToStoneMessage player game+    | has (variant . _NoRoleReveal) game    = publicMessage $ NoRoleReveal.playerTurnedToStoneText player+    | otherwise                             = publicMessage $ Standard.playerTurnedToStoneText player  villageDrunkJoinedVillageMessage :: Text -> Message villageDrunkJoinedVillageMessage to = privateMessage to villageDrunkJoinedVillageText
app/Game/Werewolf/Util.hs view
@@ -26,7 +26,7 @@     -- ** Queries     isGameOver, isHuntersTurn, isOraclesTurn, isOrphansTurn, isProtectorsTurn, isScapegoatsTurn,     isSeersTurn, isSunrise, isVillagesTurn, isWerewolvesTurn, isWitchsTurn,-    hasAnyoneWon, hasFallenAngelWon, hasVillagersWon, hasWerewolvesWon,+    hasAnyoneWon, hasVillagersWon, hasWerewolvesWon,      -- * Player @@ -38,7 +38,6 @@     isPlayerAlive, isPlayerDead, ) where -import Control.Lens         hiding (isn't) import Control.Lens.Extra import Control.Monad.Extra import Control.Monad.Random@@ -51,8 +50,7 @@ import           Data.Text  (Text)  import           Game.Werewolf.Game           hiding (getAllowedVoters, getPendingVoters,-                                               hasAnyoneWon, hasFallenAngelWon, hasVillagersWon,-                                               hasWerewolvesWon)+                                               hasAnyoneWon, hasVillagersWon, hasWerewolvesWon) import qualified Game.Werewolf.Game           as Game import           Game.Werewolf.Message.Engine import           Game.Werewolf.Player@@ -162,9 +160,6 @@  hasAnyoneWon :: MonadState Game m => m Bool hasAnyoneWon = gets Game.hasAnyoneWon--hasFallenAngelWon :: MonadState Game m => m Bool-hasFallenAngelWon = gets Game.hasFallenAngelWon  hasVillagersWon :: MonadState Game m => m Bool hasVillagersWon = gets Game.hasVillagersWon
app/Game/Werewolf/Variant/NoRoleKnowledge/Command.hs view
@@ -15,10 +15,10 @@  module Game.Werewolf.Variant.NoRoleKnowledge.Command (     -- * Ping-    rolePingedText, werewolvesPingedText,+    nocturnalRolePingedText, werewolvesPingedText,      -- * Status-    currentTurnText,+    currentNocturnalTurnText, ) where  import Data.String.Interpolate.Extra@@ -26,11 +26,11 @@  import Game.Werewolf -rolePingedText :: Role -> Text-rolePingedText _ = [iFile|variant/no-role-knowledge/command/ping/role-pinged.txt|]+nocturnalRolePingedText :: Role -> Text+nocturnalRolePingedText _ = [iFile|variant/no-role-knowledge/command/ping/nocturnal-role-pinged.txt|]  werewolvesPingedText :: Text werewolvesPingedText = [iFile|variant/no-role-knowledge/command/ping/werewolves-pinged.txt|] -currentTurnText :: Game -> Text-currentTurnText _ = [iFile|variant/no-role-knowledge/command/status/current-turn.txt|]+currentNocturnalTurnText :: Game -> Text+currentNocturnalTurnText _ = [iFile|variant/no-role-knowledge/command/status/current-nocturnal-turn.txt|]
+ app/Game/Werewolf/Variant/NoRoleReveal/Command.hs view
@@ -0,0 +1,55 @@+{-|+Module      : Game.Werewolf.Variant.NoRoleReveal.Command+Description : Suite of command messages used throughout the game.++Copyright   : (c) Henry J. Wylde, 2016+License     : BSD3+Maintainer  : public@hjwylde.com++A 'Message' is used to relay information back to either all players or a single player. This module+defines suite of command messages used throughout the werewolf game for the 'NoRoleReveal' variant.+-}++{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes       #-}++module Game.Werewolf.Variant.NoRoleReveal.Command (+    -- * Choose+    playerShotText,++    -- * Ping+    nocturnalRolePingedText, werewolvesPingedText,++    -- * Quit+    callerQuitText,++    -- * Status+    currentNocturnalTurnText, deadPlayersText,+) where++import Control.Lens++import Data.String.Humanise+import Data.String.Interpolate.Extra+import Data.Text                     (Text)++import Game.Werewolf+import Game.Werewolf.Message++playerShotText :: Player -> Text+playerShotText player = [iFile|variant/no-role-reveal/command/choose/player-shot.txt|]++nocturnalRolePingedText :: Role -> Text+nocturnalRolePingedText _ = [iFile|variant/no-role-reveal/command/ping/nocturnal-role-pinged.txt|]++werewolvesPingedText :: Text+werewolvesPingedText = [iFile|variant/no-role-reveal/command/ping/werewolves-pinged.txt|]++callerQuitText :: Player -> Text+callerQuitText caller = [iFile|variant/no-role-reveal/command/quit/caller-quit.txt|]++currentNocturnalTurnText :: Game -> Text+currentNocturnalTurnText _ = [iFile|variant/no-role-reveal/command/status/current-nocturnal-turn.txt|]++deadPlayersText :: Game -> Text+deadPlayersText game = [iFile|variant/no-role-reveal/command/status/dead-players.txt|]
+ app/Game/Werewolf/Variant/NoRoleReveal/Engine.hs view
@@ -0,0 +1,48 @@+{-|+Module      : Game.Werewolf.Variant.NoRoleReveal.Engine+Description : Suite of engine messages used throughout the game.++Copyright   : (c) Henry J. Wylde, 2016+License     : BSD3+Maintainer  : public@hjwylde.com++A 'Message' is used to relay information back to either all players or a single player. This module+defines suite of engine messages used throughout the werewolf game for the 'NoRoleReveal' variant.+-}++{-# LANGUAGE QuasiQuotes #-}++module Game.Werewolf.Variant.NoRoleReveal.Engine (+    -- * General+    playerBootedText,++    -- * Lynching+    playerLynchedText, werewolfLynchedText,++    -- * Sunrise+    playerDevouredText, playerPoisonedText, playerTurnedToStoneText,+) where++import Data.String.Humanise+import Data.String.Interpolate.Extra+import Data.Text                     (Text)++import Game.Werewolf++playerBootedText :: Player -> Text+playerBootedText player = [iFile|variant/no-role-reveal/engine/general/player-booted.txt|]++playerLynchedText :: Player -> Text+playerLynchedText player = [iFile|variant/no-role-reveal/engine/lynching/player-lynched.txt|]++werewolfLynchedText :: Player -> Text+werewolfLynchedText werewolf = [iFile|variant/no-role-reveal/engine/lynching/werewolf-lynched.txt|]++playerDevouredText :: Player -> Text+playerDevouredText player = [iFile|variant/no-role-reveal/engine/sunrise/player-devoured.txt|]++playerPoisonedText :: Player -> Text+playerPoisonedText player = [iFile|variant/no-role-reveal/engine/sunrise/player-poisoned.txt|]++playerTurnedToStoneText :: Player -> Text+playerTurnedToStoneText player = [iFile|variant/no-role-reveal/engine/sunrise/player-turned-to-stone.txt|]
app/Game/Werewolf/Variant/Standard/Command.hs view
@@ -36,13 +36,15 @@     witchCommandsText, witchsTurnText,      -- * Ping-    playerPingedText, rolePingedText, villagePingedText, werewolvesPingedText,+    diurnalRolePingedText, nocturnalRolePingedText, playerPingedText, villagePingedText,+    werewolvesPingedText,      -- * Quit     callerQuitText,      -- * Status-    alivePlayersText, currentTurnText, deadPlayersText, gameOverText,+    alivePlayersText, currentDiurnalTurnText, currentNocturnalTurnText, deadPlayersText,+    gameOverText,      -- * Unvote     callerRescindedVoteText,@@ -166,12 +168,15 @@ witchsTurnText :: Text witchsTurnText = [iFile|variant/standard/command/help/witchs-turn.txt|] +diurnalRolePingedText :: Role -> Text+diurnalRolePingedText role = [iFile|variant/standard/command/ping/diurnal-role-pinged.txt|]++nocturnalRolePingedText :: Role -> Text+nocturnalRolePingedText role = [iFile|variant/standard/command/ping/nocturnal-role-pinged.txt|]+ playerPingedText :: Text playerPingedText = [iFile|variant/standard/command/ping/player-pinged.txt|] -rolePingedText :: Role -> Text-rolePingedText role = [iFile|variant/standard/command/ping/role-pinged.txt|]- villagePingedText :: Text villagePingedText = [iFile|variant/standard/command/ping/village-pinged.txt|] @@ -184,8 +189,11 @@ alivePlayersText :: Game -> Text alivePlayersText game = [iFile|variant/standard/command/status/alive-players.txt|] -currentTurnText :: Game -> Text-currentTurnText game = [iFile|variant/standard/command/status/current-turn.txt|]+currentDiurnalTurnText :: Game -> Text+currentDiurnalTurnText game = [iFile|variant/standard/command/status/current-diurnal-turn.txt|]++currentNocturnalTurnText :: Game -> Text+currentNocturnalTurnText game = [iFile|variant/standard/command/status/current-nocturnal-turn.txt|]  deadPlayersText :: Game -> Text deadPlayersText game = [iFile|variant/standard/command/status/dead-players.txt|]
app/Game/Werewolf/Variant/Standard/Engine.hs view
@@ -21,8 +21,8 @@     playerBootedText, playerKilledText,      -- * Game over-    allegianceWonText, fallenAngelWonText, playerContributedText, playerLostText, playerRolesText,-    playerWonText,+    allegianceWonText, dullahanWonText, fallenAngelWonText, playerContributedText, playerLostText,+    playerRolesText, playerWonText,      -- * Hunter's turn     huntersTurnPrivateText, huntersTurnPublicText,@@ -32,8 +32,8 @@     werewolfLynchedText,      -- * New game-    beholderText, fallenAngelText, newPlayerText, playersInGameText, rolesInGameText,-    spitefulGhostText, trueVillagerText,+    beholderText, dullahanText, newPlayerText, playersInGameText, rolesInGameText,+    spitefulGhostPrivateText, spitefulGhostPublicText, trueVillagerText,      -- * Oracle's turn     oraclesTurnPrivateText, oraclesTurnPublicText,@@ -73,7 +73,6 @@ ) where  import Control.Arrow-import Control.Lens       hiding (isn't) import Control.Lens.Extra  import           Data.List.Extra@@ -97,6 +96,9 @@ allegianceWonText :: Allegiance -> Text allegianceWonText allegiance = [iFile|variant/standard/engine/game-over/allegiance-won.txt|] +dullahanWonText :: Game -> Text+dullahanWonText game = [iFile|variant/standard/engine/game-over/dullahan-won.txt|]+ fallenAngelWonText :: Text fallenAngelWonText = [iFile|variant/standard/engine/game-over/fallen-angel-won.txt|] @@ -144,8 +146,8 @@     where         seer = game ^?! players . seers -fallenAngelText :: Text-fallenAngelText = [iFile|variant/standard/engine/new-game/fallen-angel.txt|]+dullahanText :: Game -> Text+dullahanText game = [iFile|variant/standard/engine/new-game/dullahan.txt|]  newPlayerText :: Player -> Text newPlayerText player = [iFile|variant/standard/engine/new-game/new-player.txt|]@@ -160,8 +162,13 @@         roleCounts      = map (head &&& length) (groupSortOn humanise roles)         totalBalance    = sumOf (traverse . balance) roles -spitefulGhostText :: Game -> Text-spitefulGhostText game = [iFile|variant/standard/engine/new-game/spiteful-ghost.txt|]+spitefulGhostPrivateText :: Game -> Text+spitefulGhostPrivateText game = [iFile|variant/standard/engine/new-game/spiteful-ghost-private.txt|]++spitefulGhostPublicText :: Game -> Text+spitefulGhostPublicText game = [iFile|variant/standard/engine/new-game/spiteful-ghost-public.txt|]+    where+        spitefulGhost = game ^?! players . spitefulGhosts  trueVillagerText :: Game -> Text trueVillagerText game = [iFile|variant/standard/engine/new-game/true-villager.txt|]
app/Werewolf/Command/End.hs view
@@ -20,8 +20,10 @@  import Data.Text (Text) +-- TODO (hjw): remove Message.Engine import Game.Werewolf import Game.Werewolf.Message.Command+import Game.Werewolf.Message.Engine import Game.Werewolf.Message.Error  import Werewolf.System@@ -37,4 +39,7 @@      deleteGame tag -    exitWith success { messages = [gameEndedMessage callerName] }+    exitWith success { messages =+        [ gameEndedMessage callerName+        , playerRolesMessage game+        ] }
app/Werewolf/Command/Start.hs view
@@ -20,7 +20,6 @@     handle, ) where -import Control.Lens import Control.Lens.Extra import Control.Monad.Except import Control.Monad.Extra@@ -95,7 +94,7 @@         simpleWerewolfBalance   = simpleWerewolfRole ^. balance          -- Little magic here to calculate how many Werewolves and Villagers we want.-        -- This tries to ensure that the balance of the game is between -2 and 2.+        -- This tries to ensure that the balance of the game is between -3 and 2.         simpleWerewolvesCount   = (goal - m - startingBalance) `div` (simpleWerewolfBalance - 1) + 1         simpleVillagersCount    = m - simpleWerewolvesCount 
app/Werewolf/Options.hs view
@@ -186,6 +186,7 @@         variantOption = option $ readerAsk >>= \opt -> case opt of             "standard"          -> return Standard             "no-role-knowledge" -> return NoRoleKnowledge+            "no-role-reveal"    -> return NoRoleReveal             _                   -> readerError $ "unrecognised variant `" ++ opt ++ "'"          extraRolesOption = fmap (Start.Use . filter (/= T.empty) . T.splitOn "," . T.pack) (strOption $ mconcat
app/Werewolf/System.hs view
@@ -22,8 +22,9 @@     filePath, readGame, writeGame, deleteGame, writeOrDeleteGame, doesGameExist, ) where -import Control.Lens         hiding (cons)+import Control.Lens.Extra   hiding (cons) import Control.Monad.Except+import Control.Monad.Random import Control.Monad.Writer  import           Data.List@@ -38,22 +39,32 @@  import System.Directory import System.FilePath+import System.Random.Shuffle -startGame :: (MonadError [Message] m, MonadWriter [Message] m) => Text -> Variant -> [Player] -> m Game-startGame callerName variant players = do+startGame :: (MonadError [Message] m, MonadRandom m, MonadWriter [Message] m) => Text -> Variant -> [Player] -> m Game+startGame callerName variant players' = do     when (playerNames /= nub playerNames)   $ throwError [playerNamesMustBeUniqueMessage callerName]-    when (length players < 7)               $ throwError [mustHaveAtLeast7PlayersMessage callerName]+    when (length players' < 7)              $ throwError [mustHaveAtLeast7PlayersMessage callerName]     forM_ restrictedRoles $ \role ->-        when (length (players ^.. roles . only role) > 1) $+        when (length (players' ^.. roles . only role) > 1) $             throwError [roleCountRestrictedMessage callerName role] -    let game = newGame variant players+    let game    = newGame variant players'+    game'       <- (\marks' -> game & marks .~ marks' ^.. names) <$> randomMarks game -    tell $ newGameMessages game+    tell $ newGameMessages game' -    return game+    return game'     where-        playerNames = players ^.. names+        playerNames = players' ^.. names++randomMarks :: MonadRandom m => Game -> m [Player]+randomMarks game = do+    let count = length potentialMarks `div` 3 + 1++    take count <$> shuffleM potentialMarks+    where+        potentialMarks = game ^.. players . traverse . filtered (isn't dullahan)  filePath :: MonadIO m => Text -> m FilePath filePath tag = (</> ".werewolf" </> T.unpack tag) <$> liftIO getHomeDirectory
src/Control/Lens/Extra.hs view
@@ -12,6 +12,8 @@ {-# LANGUAGE Rank2Types #-}  module Control.Lens.Extra (+    module Control.Lens,+     -- * Folds     is, isn't, hasuse, hasn'tuse, 
src/Game/Werewolf.hs view
@@ -24,4 +24,4 @@ import Game.Werewolf.Game     as Werewolf import Game.Werewolf.Player   as Werewolf import Game.Werewolf.Response as Werewolf-import Game.Werewolf.Role     as Werewolf hiding (name)+import Game.Werewolf.Role     as Werewolf hiding (activity, name)
src/Game/Werewolf/Game.hs view
@@ -10,6 +10,7 @@ structure and any fields required to keep track of the current state. -} +{-# LANGUAGE CPP               #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE Rank2Types        #-} {-# LANGUAGE TemplateHaskell   #-}@@ -18,16 +19,17 @@     -- * Game     Game,     variant, stage, round, players, boots, allowedVoters, divine, fallenAngelLynched, healUsed,-    hunterRetaliated, jesterRevealed, passed, poison, poisonUsed, priorProtect, protect, roleModel,-    scapegoatBlamed, see, votes,+    hunterRetaliated, jesterRevealed, marks, passed, poison, poisonUsed, priorProtect, protect,+    roleModel, scapegoatBlamed, see, votes,      Variant(..),-    _Standard, _NoRoleKnowledge,+    _Standard, _NoRoleKnowledge, _NoRoleReveal,      Stage(..),     _DruidsTurn, _GameOver, _HuntersTurn1, _HuntersTurn2, _Lynching, _OraclesTurn, _OrphansTurn,     _ProtectorsTurn, _ScapegoatsTurn, _SeersTurn, _Sunrise, _Sunset, _VillageDrunksTurn,     _VillagesTurn, _WerewolvesTurn, _WitchsTurn,+    activity,      allStages,     stageCycle, stageAvailable,@@ -41,13 +43,12 @@     firstRound, secondRound, thirdRound,      -- ** Searches-    getAllowedVoters, getPendingVoters,+    getAllowedVoters, getPendingVoters, getMarks,      -- ** Queries-    hasAnyoneWon, hasFallenAngelWon, hasVillagersWon, hasWerewolvesWon,+    hasAnyoneWon, hasDullahanWon, hasFallenAngelWon, hasVillagersWon, hasWerewolvesWon, ) where -import Control.Lens       hiding (isn't) import Control.Lens.Extra  import           Data.List.Extra@@ -58,6 +59,7 @@ import           Data.Text            (Text)  import Game.Werewolf.Player+import Game.Werewolf.Role   hiding (activity, name)  import Prelude hiding (round) @@ -82,6 +84,7 @@     , _healUsed           :: Bool             -- ^ Witch     , _hunterRetaliated   :: Bool             -- ^ Hunter     , _jesterRevealed     :: Bool             -- ^ Jester+    , _marks              :: [Text]           -- ^ Dullahan     , _passed             :: Bool             -- ^ Witch     , _poison             :: Maybe Text       -- ^ Witch     , _poisonUsed         :: Bool             -- ^ Witch@@ -93,12 +96,13 @@     , _votes              :: Map Text Text    -- ^ Villagers and Werewolves     } deriving (Eq, Read, Show) -data Variant = Standard | NoRoleKnowledge+data Variant = Standard | NoRoleKnowledge | NoRoleReveal     deriving (Eq, Read, Show)  instance Humanise Variant where     humanise Standard           = "standard"     humanise NoRoleKnowledge    = "no role knowledge"+    humanise NoRoleReveal       = "no role reveal"  -- | Most of these are fairly self-explainable (the turn stages). 'Sunrise' and 'Sunset' are --   provided as meaningful breaks between the day and night as, for example, a 'VillagesTurn' may@@ -135,6 +139,30 @@  makePrisms ''Stage +#if __GLASGOW_HASKELL__ >= 800+activity :: Contravariant f => (Activity -> f Activity) -> Stage -> f Stage+#else+activity :: Getter Stage Activity+#endif+activity = to getter+    where+        getter DruidsTurn           = Diurnal+        getter GameOver             = Diurnal+        getter HuntersTurn1         = Diurnal+        getter HuntersTurn2         = Diurnal+        getter Lynching             = Diurnal+        getter OraclesTurn          = Nocturnal+        getter OrphansTurn          = Nocturnal+        getter ProtectorsTurn       = Nocturnal+        getter ScapegoatsTurn       = Diurnal+        getter SeersTurn            = Nocturnal+        getter Sunrise              = Diurnal+        getter Sunset               = Diurnal+        getter VillageDrunksTurn    = Nocturnal+        getter VillagesTurn         = Diurnal+        getter WerewolvesTurn       = Nocturnal+        getter WitchsTurn           = Nocturnal+ -- | All of the 'Stage's in the order that they should occur. allStages :: [Stage] allStages =@@ -209,6 +237,7 @@     , _healUsed             = False     , _hunterRetaliated     = False     , _jesterRevealed       = False+    , _marks                = []     , _poison               = Nothing     , _poisonUsed           = False     , _priorProtect         = Nothing@@ -260,10 +289,24 @@     where         votes' = game ^. votes +-- | Gets all the 'marks' in a game (which is names only) and maps them to their player.+getMarks :: Game -> [Player]+getMarks game = map (\name -> game ^?! players . traverse . named name) (game ^. marks)+ -- | Queries whether anyone has won. hasAnyoneWon :: Game -> Bool-hasAnyoneWon game = any ($ game) [hasFallenAngelWon, hasVillagersWon, hasWerewolvesWon]+hasAnyoneWon game = any ($ game)+    [ hasDullahanWon+    , hasFallenAngelWon+    , hasVillagersWon+    , hasWerewolvesWon+    ] +-- | Queries whether the Dullahan has won. The Dullahan wins if they manage to eliminate all their+--   marks.+hasDullahanWon :: Game -> Bool+hasDullahanWon game = has (players . dullahans . alive) game && all (is dead) (getMarks game)+ -- | Queries whether the Fallen Angel has won. The Fallen Angel wins if they manage to get --   themselves lynched by the Villagers. hasFallenAngelWon :: Game -> Bool@@ -272,9 +315,11 @@ -- | Queries whether the 'Villagers' have won. The 'Villagers' win if they are the only players --   surviving. -----   N.B., the Fallen Angel is not considered when determining whether the 'Villagers' have won.+--   N.B., the Dullahan and Fallen Angel are not considered when determining whether the 'Villagers'+--   have won. hasVillagersWon :: Game -> Bool-hasVillagersWon = allOf (players . traverse . alive) (\player -> is villager player || is fallenAngel player)+hasVillagersWon = allOf (players . traverse . alive)+    (\player -> any ($ player) [is villager, is dullahan, is fallenAngel])  -- | Queries whether the 'Werewolves' have won. The 'Werewolves' win if they are the only players --   surviving.
src/Game/Werewolf/Player.hs view
@@ -24,9 +24,9 @@     newPlayer,      -- ** Traversals-    alphaWolf, beholder, crookedSenator, druid, fallenAngel, hunter, jester, lycan, medusa, oracle,-    orphan, protector, scapegoat, seer, simpleVillager, simpleWerewolf, spitefulGhost, trueVillager,-    villageDrunk, witch,+    alphaWolf, beholder, crookedSenator, druid, dullahan, fallenAngel, hunter, jester, lycan,+    medusa, oracle, orphan, protector, scapegoat, seer, simpleVillager, simpleWerewolf,+    spitefulGhost, trueVillager, villageDrunk, witch,     loner, villager, werewolf,      -- | The following traversals are provided just as a bit of sugar to avoid continually writing@@ -35,14 +35,13 @@      -- | N.B., the following traversals are not legal for the same reason 'filtered' isn't!     named,-    alphaWolves, beholders, crookedSenators, druids, fallenAngels, hunters, jesters, lycans,-    medusas, oracles, orphans, protectors, scapegoats, seers, simpleVillagers, simpleWerewolves,-    spitefulGhosts, trueVillagers, villageDrunks, witches,+    alphaWolves, beholders, crookedSenators, druids, dullahans, fallenAngels, hunters, jesters,+    lycans, medusas, oracles, orphans, protectors, scapegoats, seers, simpleVillagers,+    simpleWerewolves, spitefulGhosts, trueVillagers, villageDrunks, witches,     loners, villagers, werewolves,     alive, dead, ) where -import Control.Lens       hiding (isn't) import Control.Lens.Extra  import Data.Function@@ -105,6 +104,14 @@ crookedSenator :: Traversal' Player () crookedSenator = role . only crookedSenatorRole +-- | The traversal of 'Player's with a 'dullahanRole'.+--+-- @+-- 'dullahan' = 'role' . 'only' 'dullahanRole'+-- @+dullahan :: Traversal' Player ()+dullahan = role . only dullahanRole+ -- | The traversal of 'Player's with a 'druidRole'. -- -- @@@ -320,6 +327,14 @@ -- @ crookedSenators :: Traversable t => Traversal' (t Player) Player crookedSenators = traverse . filtered (is crookedSenator)++-- | This 'Traversal' provides the traversal of 'dullahan' 'Player's.+--+-- @+-- 'dullahans' = 'traverse' . 'filtered' ('is' 'dullahan')+-- @+dullahans :: Traversable t => Traversal' (t Player) Player+dullahans = traverse . filtered (is dullahan)  -- | This 'Traversal' provides the traversal of 'druid' 'Player's. --
src/Game/Werewolf/Role.hs view
@@ -21,11 +21,14 @@ module Game.Werewolf.Role (     -- * Role     Role,-    tag, name, allegiance, balance, description, rules,+    tag, name, allegiance, balance, activity, description, rules,      Allegiance(..),     _NoOne, _Villagers, _Werewolves, +    Activity(..),+    _Diurnal, _Nocturnal,+     -- ** Instances     allRoles, restrictedRoles, @@ -39,7 +42,7 @@     -- | The Loners look out for themselves and themselves alone.      --   The Loners must complete their own objective.-    fallenAngelRole, spitefulGhostRole,+    dullahanRole, fallenAngelRole, spitefulGhostRole,      -- *** The Villagers     -- | Fraught with fear of the unseen enemy, the Villagers must work together to determine the@@ -79,6 +82,7 @@     , _name        :: Text     , _allegiance  :: Allegiance     , _balance     :: Int+    , _activity    :: Activity     , _description :: Text     , _rules       :: Text     } deriving (Read, Show)@@ -93,6 +97,19 @@     humanise Villagers  = "Villagers"     humanise Werewolves = "Werewolves" +-- | Defines whether a role is diurnal or nocturnal. I.e., if the role's turn occurs during the day+--   or night.+data Activity = Diurnal | Nocturnal+    deriving (Eq, Read, Show)++instance Humanise Activity where+    humanise Diurnal    = "diurnal"+    humanise Nocturnal  = "nocturnal"++makePrisms ''Allegiance++makePrisms ''Activity+ makeLenses ''Role  instance Eq Role where@@ -101,8 +118,6 @@ instance Humanise Role where     humanise = view name -makePrisms ''Allegiance- -- | A list containing all the roles defined in this file. allRoles :: [Role] allRoles =@@ -110,6 +125,7 @@     , beholderRole     , crookedSenatorRole     , druidRole+    , dullahanRole     , fallenAngelRole     , hunterRole     , jesterRole@@ -151,7 +167,8 @@     { _tag          = "orphan"     , _name         = T.strip [iFile|variant/standard/role/orphan/name.txt|]     , _allegiance   = Villagers-    , _balance      = -1+    , _balance      = -3+    , _activity     = Nocturnal     , _description  = T.strip [iFile|variant/standard/role/orphan/description.txt|]     , _rules        = T.strip [iFile|variant/standard/role/orphan/rules.txt|]     }@@ -171,11 +188,31 @@     { _tag          = "village-drunk"     , _name         = T.strip [iFile|variant/standard/role/village-drunk/name.txt|]     , _allegiance   = Villagers-    , _balance      = -1+    , _balance      = -3+    , _activity     = Diurnal     , _description  = T.strip [iFile|variant/standard/role/village-drunk/description.txt|]     , _rules        = T.strip [iFile|variant/standard/role/village-drunk/rules.txt|]     } +-- | /Normally the Dullahan carries their head under one arm, however while amongst the Villagers,/+--   /they ere on the side of caution and rest it in a more traditional place. The Dullahan rides a/+--   /black horse as dark as night and hunts down travellers in the countryside. Beware if the/+--   /Dullahan knows your name, for you are then marked for death and you should avoid them at all/+--   /costs.+--+--   The Dullahan is given a list of player names at the start of the game. They must eliminate all+--   of them before the end of the game.+dullahanRole :: Role+dullahanRole = Role+    { _tag          = "dullahan"+    , _name         = T.strip [iFile|variant/standard/role/dullahan/name.txt|]+    , _allegiance   = NoOne+    , _balance      = 0+    , _activity     = Diurnal+    , _description  = T.strip [iFile|variant/standard/role/dullahan/description.txt|]+    , _rules        = T.strip [iFile|variant/standard/role/dullahan/rules.txt|]+    }+ -- | /Long ago during the War in Heaven, angels fell from the sky as one by one those that followed/ --   /Lucifer were defeated. For centuries they lived amongst mortal Villagers as punishment for/ --   /their sins and wrongdoings. The Fallen Angel was one such being and is now one of the few/@@ -190,6 +227,7 @@     , _name         = T.strip [iFile|variant/standard/role/fallen-angel/name.txt|]     , _allegiance   = NoOne     , _balance      = 0+    , _activity     = Diurnal     , _description  = T.strip [iFile|variant/standard/role/fallen-angel/description.txt|]     , _rules        = T.strip [iFile|variant/standard/role/fallen-angel/rules.txt|]     }@@ -207,6 +245,7 @@     , _name         = T.strip [iFile|variant/standard/role/spiteful-ghost/name.txt|]     , _allegiance   = NoOne     , _balance      = 0+    , _activity     = Diurnal     , _description  = T.strip [iFile|variant/standard/role/spiteful-ghost/description.txt|]     , _rules        = T.strip [iFile|variant/standard/role/spiteful-ghost/rules.txt|]     }@@ -222,7 +261,8 @@     { _tag          = "beholder"     , _name         = T.strip [iFile|variant/standard/role/beholder/name.txt|]     , _allegiance   = Villagers-    , _balance      = 1+    , _balance      = 2+    , _activity     = Diurnal     , _description  = T.strip [iFile|variant/standard/role/beholder/description.txt|]     , _rules        = T.strip [iFile|variant/standard/role/beholder/rules.txt|]     }@@ -239,6 +279,7 @@     , _name         = T.strip [iFile|variant/standard/role/crooked-senator/name.txt|]     , _allegiance   = Villagers     , _balance      = 2+    , _activity     = Diurnal     , _description  = T.strip [iFile|variant/standard/role/crooked-senator/description.txt|]     , _rules        = T.strip [iFile|variant/standard/role/crooked-senator/rules.txt|]     }@@ -256,7 +297,8 @@     { _tag          = "druid"     , _name         = T.strip [iFile|variant/standard/role/druid/name.txt|]     , _allegiance   = Villagers-    , _balance      = 3+    , _balance      = 5+    , _activity     = Diurnal     , _description  = T.strip [iFile|variant/standard/role/druid/description.txt|]     , _rules        = T.strip [iFile|variant/standard/role/druid/rules.txt|]     }@@ -272,7 +314,8 @@     { _tag          = "hunter"     , _name         = T.strip [iFile|variant/standard/role/hunter/name.txt|]     , _allegiance   = Villagers-    , _balance      = 2+    , _balance      = 3+    , _activity     = Diurnal     , _description  = T.strip [iFile|variant/standard/role/hunter/description.txt|]     , _rules        = T.strip [iFile|variant/standard/role/hunter/rules.txt|]     }@@ -290,7 +333,8 @@     { _tag          = "jester"     , _name         = T.strip [iFile|variant/standard/role/jester/name.txt|]     , _allegiance   = Villagers-    , _balance      = 0+    , _balance      = 1+    , _activity     = Diurnal     , _description  = T.strip [iFile|variant/standard/role/jester/description.txt|]     , _rules        = T.strip [iFile|variant/standard/role/jester/rules.txt|]     }@@ -307,7 +351,8 @@     { _tag          = "lycan"     , _name         = T.strip [iFile|variant/standard/role/lycan/name.txt|]     , _allegiance   = Villagers-    , _balance      = 0+    , _balance      = -1+    , _activity     = Diurnal     , _description  = T.strip [iFile|variant/standard/role/lycan/description.txt|]     , _rules        = T.strip [iFile|variant/standard/role/lycan/rules.txt|]     }@@ -326,7 +371,8 @@     { _tag          = "medusa"     , _name         = T.strip [iFile|variant/standard/role/medusa/name.txt|]     , _allegiance   = Villagers-    , _balance      = 3+    , _balance      = 4+    , _activity     = Diurnal     , _description  = T.strip [iFile|variant/standard/role/medusa/description.txt|]     , _rules        = T.strip [iFile|variant/standard/role/medusa/rules.txt|]     }@@ -342,7 +388,8 @@     { _tag          = "oracle"     , _name         = T.strip [iFile|variant/standard/role/oracle/name.txt|]     , _allegiance   = Villagers-    , _balance      = 2+    , _balance      = 4+    , _activity     = Nocturnal     , _description  = T.strip [iFile|variant/standard/role/oracle/description.txt|]     , _rules        = T.strip [iFile|variant/standard/role/oracle/rules.txt|]     }@@ -360,7 +407,8 @@     { _tag          = "protector"     , _name         = T.strip [iFile|variant/standard/role/protector/name.txt|]     , _allegiance   = Villagers-    , _balance      = 2+    , _balance      = 3+    , _activity     = Nocturnal     , _description  = T.strip [iFile|variant/standard/role/protector/description.txt|]     , _rules        = T.strip [iFile|variant/standard/role/protector/rules.txt|]     }@@ -379,6 +427,7 @@     , _name         = T.strip [iFile|variant/standard/role/scapegoat/name.txt|]     , _allegiance   = Villagers     , _balance      = 0+    , _activity     = Diurnal     , _description  = T.strip [iFile|variant/standard/role/scapegoat/description.txt|]     , _rules        = T.strip [iFile|variant/standard/role/scapegoat/rules.txt|]     }@@ -393,7 +442,8 @@     { _tag          = "seer"     , _name         = T.strip [iFile|variant/standard/role/seer/name.txt|]     , _allegiance   = Villagers-    , _balance      = 2+    , _balance      = 4+    , _activity     = Nocturnal     , _description  = T.strip [iFile|variant/standard/role/seer/description.txt|]     , _rules        = T.strip [iFile|variant/standard/role/seer/rules.txt|]     }@@ -410,6 +460,7 @@     , _name         = T.strip [iFile|variant/standard/role/simple-villager/name.txt|]     , _allegiance   = Villagers     , _balance      = 1+    , _activity     = Diurnal     , _description  = T.strip [iFile|variant/standard/role/simple-villager/description.txt|]     , _rules        = T.strip [iFile|variant/standard/role/simple-villager/rules.txt|]     }@@ -425,6 +476,7 @@     , _name         = T.strip [iFile|variant/standard/role/true-villager/name.txt|]     , _allegiance   = Villagers     , _balance      = 2+    , _activity     = Diurnal     , _description  = T.strip [iFile|variant/standard/role/true-villager/description.txt|]     , _rules        = T.strip [iFile|variant/standard/role/true-villager/rules.txt|]     }@@ -442,6 +494,7 @@     , _name         = T.strip [iFile|variant/standard/role/witch/name.txt|]     , _allegiance   = Villagers     , _balance      = 3+    , _activity     = Nocturnal     , _description  = T.strip [iFile|variant/standard/role/witch/description.txt|]     , _rules        = T.strip [iFile|variant/standard/role/witch/rules.txt|]     }@@ -457,7 +510,8 @@     { _tag          = "alpha-wolf"     , _name         = T.strip [iFile|variant/standard/role/alpha-wolf/name.txt|]     , _allegiance   = Werewolves-    , _balance      = -5+    , _balance      = -7+    , _activity     = Nocturnal     , _description  = T.strip [iFile|variant/standard/role/alpha-wolf/description.txt|]     , _rules        = T.strip [iFile|variant/standard/role/alpha-wolf/rules.txt|]     }@@ -473,7 +527,8 @@     { _tag          = "simple-werewolf"     , _name         = T.strip [iFile|variant/standard/role/simple-werewolf/name.txt|]     , _allegiance   = Werewolves-    , _balance      = -4+    , _balance      = -5+    , _activity     = Nocturnal     , _description  = T.strip [iFile|variant/standard/role/simple-werewolf/description.txt|]     , _rules        = T.strip [iFile|variant/standard/role/simple-werewolf/rules.txt|]     }
+ variant/no-role-knowledge/command/ping/nocturnal-role-pinged.txt view
@@ -0,0 +1,1 @@+Waiting on the nocturnal players...
− variant/no-role-knowledge/command/ping/role-pinged.txt
@@ -1,1 +0,0 @@-Waiting on the nocturnal players...
+ variant/no-role-knowledge/command/status/current-nocturnal-turn.txt view
@@ -0,0 +1,1 @@+It's currently night.
− variant/no-role-knowledge/command/status/current-turn.txt
@@ -1,1 +0,0 @@-It's currently night.
+ variant/no-role-reveal/command/choose/player-shot.txt view
@@ -0,0 +1,1 @@+#{humanise player} slumps down to the ground, hands clutching at their chest while blood slips between their fingers and pools around them.
+ variant/no-role-reveal/command/ping/nocturnal-role-pinged.txt view
@@ -0,0 +1,1 @@+Waiting on the nocturnal players...
+ variant/no-role-reveal/command/ping/werewolves-pinged.txt view
@@ -0,0 +1,1 @@+Waiting on the nocturnal players...
+ variant/no-role-reveal/command/quit/caller-quit.txt view
@@ -0,0 +1,1 @@+#{humanise caller} has quit!
+ variant/no-role-reveal/command/status/current-nocturnal-turn.txt view
@@ -0,0 +1,1 @@+It's currently night.
+ variant/no-role-reveal/command/status/dead-players.txt view
@@ -0,0 +1,1 @@+The following players are dead: #{humanise $ map humanisePlayerWithRoleIfTrueVillager (game ^..  players . traverse . dead)}.
+ variant/no-role-reveal/engine/general/player-booted.txt view
@@ -0,0 +1,1 @@+#{humanise player} has been booted from the game!
+ variant/no-role-reveal/engine/lynching/player-lynched.txt view
@@ -0,0 +1,1 @@+#{humanise player} is tied up to a pyre and set alight. Eventually the screams start to die and nothing is left but their charred corpse.
+ variant/no-role-reveal/engine/lynching/werewolf-lynched.txt view
@@ -0,0 +1,1 @@+#{humanise werewolf} is tied up to a pyre and set alight. Eventually the screams start to die and nothing is left but their charred corpse.
+ variant/no-role-reveal/engine/sunrise/player-devoured.txt view
@@ -0,0 +1,1 @@+As you open them you notice a door broken down and #{humanise player}'s guts half devoured and spilling out over the cobblestones.
+ variant/no-role-reveal/engine/sunrise/player-poisoned.txt view
@@ -0,0 +1,1 @@+Upon further discovery, it looks like the #{humanise witchRole} struck in the night. #{humanise player} is hanging over the side of their bed, poisoned.
+ variant/no-role-reveal/engine/sunrise/player-turned-to-stone.txt view
@@ -0,0 +1,1 @@+Next to them you see a stone statue, cold to the touch. #{humanise player} must have looked into the eyes of the #{humanise medusaRole} at the very end.
+ variant/standard/command/ping/diurnal-role-pinged.txt view
@@ -0,0 +1,1 @@+Waiting on the #{humanise role}...
+ variant/standard/command/ping/nocturnal-role-pinged.txt view
@@ -0,0 +1,1 @@+Waiting on the #{humanise role}...
− variant/standard/command/ping/role-pinged.txt
@@ -1,1 +0,0 @@-Waiting on the #{humanise role}...
+ variant/standard/command/status/current-diurnal-turn.txt view
@@ -0,0 +1,1 @@+It's currently the #{humanise $ game ^. stage}.
+ variant/standard/command/status/current-nocturnal-turn.txt view
@@ -0,0 +1,1 @@+It's currently the #{humanise $ game ^. stage}.
− variant/standard/command/status/current-turn.txt
@@ -1,1 +0,0 @@-It's currently the #{humanise $ game ^. stage}.
+ variant/standard/engine/game-over/dullahan-won.txt view
@@ -0,0 +1,2 @@+The roads are owned by the #{humanise dullahanRole}. Foolishly #{humanise $ game ^. marks} tried to cross them, but to no avail.+The game is over! The #{humanise dullahanRole} has won.
+ variant/standard/engine/new-game/dullahan.txt view
@@ -0,0 +1,1 @@+Word has come in the night: #{humanise $ game ^. marks} are marked for death. Fulfil your purpose; ride hard and run them down.
− variant/standard/engine/new-game/fallen-angel.txt
@@ -1,1 +0,0 @@-Alas, again I regrettably yield advice: an angelic menace walks among you. Do not cast your votes lightly, for they will relish in this opportunity to be free from their terrible nightmare.
+ variant/standard/engine/new-game/spiteful-ghost-private.txt view
@@ -0,0 +1,1 @@+Being ethereal seldom has it's benefits. Perhaps however this knowledge of the townsfolks' natures will bring you some joy in the afterlife: #{humanisePlayersWithRoles $ game ^.. players . traverse . filtered (isn't spitefulGhost)}.
+ variant/standard/engine/new-game/spiteful-ghost-public.txt view
@@ -0,0 +1,1 @@+In a rash decision to rid the scourge of Werewolves, the Villagers of Fougères lynched an innocent. A foolish move, as now #{humanise spitefulGhost} is just one further horror that plagues Fougères; a ghost that knows each and every one of the Villagers and their darkest secrets.
− variant/standard/engine/new-game/spiteful-ghost.txt
@@ -1,1 +0,0 @@-Being ethereal seldom has it's benefits. Perhaps however this knowledge of the townsfolks' natures will bring you some joy in the afterlife: #{humanisePlayersWithRoles $ game ^.. players . traverse . filtered (isn't spitefulGhost)}.
+ variant/standard/role/dullahan/description.txt view
@@ -0,0 +1,1 @@+Normally the Dullahan carries their head under one arm, however while amongst the Villagers, they ere on the side of caution and rest it in a more traditional place. The Dullahan rides a black horse as dark as night and hunts down travellers in the countryside. Beware if the Dullahan knows your name, for you are then marked for death and you should avoid them at all costs.
+ variant/standard/role/dullahan/name.txt view
@@ -0,0 +1,1 @@+Dullahan
+ variant/standard/role/dullahan/rules.txt view
@@ -0,0 +1,1 @@+The Dullahan is given a list of player names at the start of the game. They must eliminate all of them before the end of the game.
werewolf.cabal view
@@ -1,5 +1,5 @@ name:           werewolf-version:        1.2.0.3+version:        1.3.0.0  author:         Henry J. Wylde maintainer:     public@hjwylde.com@@ -21,10 +21,22 @@ extra-source-files:     CHANGELOG.md     README.md-    variant/no-role-knowledge/command/ping/role-pinged.txt+    variant/no-role-knowledge/command/ping/nocturnal-role-pinged.txt     variant/no-role-knowledge/command/ping/werewolves-pinged.txt-    variant/no-role-knowledge/command/status/current-turn.txt+    variant/no-role-knowledge/command/status/current-nocturnal-turn.txt     variant/no-role-knowledge/engine/new-game/roles-in-game.txt+    variant/no-role-reveal/command/choose/player-shot.txt+    variant/no-role-reveal/command/ping/nocturnal-role-pinged.txt+    variant/no-role-reveal/command/ping/werewolves-pinged.txt+    variant/no-role-reveal/command/quit/caller-quit.txt+    variant/no-role-reveal/command/status/current-nocturnal-turn.txt+    variant/no-role-reveal/command/status/dead-players.txt+    variant/no-role-reveal/engine/general/player-booted.txt+    variant/no-role-reveal/engine/lynching/player-lynched.txt+    variant/no-role-reveal/engine/lynching/werewolf-lynched.txt+    variant/no-role-reveal/engine/sunrise/player-devoured.txt+    variant/no-role-reveal/engine/sunrise/player-poisoned.txt+    variant/no-role-reveal/engine/sunrise/player-turned-to-stone.txt     variant/standard/command/boot/player-voted-boot.txt     variant/standard/command/choose/player-shot.txt     variant/standard/command/circle/game-circle.txt@@ -58,13 +70,15 @@     variant/standard/command/help/win-condition.txt     variant/standard/command/help/witch-commands.txt     variant/standard/command/help/witchs-turn.txt+    variant/standard/command/ping/diurnal-role-pinged.txt+    variant/standard/command/ping/nocturnal-role-pinged.txt     variant/standard/command/ping/player-pinged.txt-    variant/standard/command/ping/role-pinged.txt     variant/standard/command/ping/village-pinged.txt     variant/standard/command/ping/werewolves-pinged.txt     variant/standard/command/quit/caller-quit.txt     variant/standard/command/status/alive-players.txt-    variant/standard/command/status/current-turn.txt+    variant/standard/command/status/current-diurnal-turn.txt+    variant/standard/command/status/current-nocturnal-turn.txt     variant/standard/command/status/dead-players.txt     variant/standard/command/status/game-over.txt     variant/standard/command/unvote/player-rescinded-vote.txt@@ -73,6 +87,7 @@     variant/standard/command/vote/player-made-lynch-vote.txt     variant/standard/engine/druids-turn/start.txt     variant/standard/engine/game-over/allegiance-won.txt+    variant/standard/engine/game-over/dullahan-won.txt     variant/standard/engine/game-over/fallen-angel-won.txt     variant/standard/engine/game-over/player-contributed.txt     variant/standard/engine/game-over/player-lost.txt@@ -88,11 +103,12 @@     variant/standard/engine/lynching/scapegoat-lynched.txt     variant/standard/engine/lynching/werewolf-lynched.txt     variant/standard/engine/new-game/beholder.txt-    variant/standard/engine/new-game/fallen-angel.txt+    variant/standard/engine/new-game/dullahan.txt     variant/standard/engine/new-game/new-player.txt     variant/standard/engine/new-game/players-in-game.txt     variant/standard/engine/new-game/roles-in-game.txt-    variant/standard/engine/new-game/spiteful-ghost.txt+    variant/standard/engine/new-game/spiteful-ghost-private.txt+    variant/standard/engine/new-game/spiteful-ghost-public.txt     variant/standard/engine/new-game/true-villager.txt     variant/standard/engine/oracles-turn/start-private.txt     variant/standard/engine/oracles-turn/start-public.txt@@ -161,6 +177,9 @@     variant/standard/role/druid/description.txt     variant/standard/role/druid/name.txt     variant/standard/role/druid/rules.txt+    variant/standard/role/dullahan/description.txt+    variant/standard/role/dullahan/name.txt+    variant/standard/role/dullahan/rules.txt     variant/standard/role/fallen-angel/description.txt     variant/standard/role/fallen-angel/name.txt     variant/standard/role/fallen-angel/rules.txt@@ -239,6 +258,8 @@         Game.Werewolf.Util         Game.Werewolf.Variant.NoRoleKnowledge.Command         Game.Werewolf.Variant.NoRoleKnowledge.Engine+        Game.Werewolf.Variant.NoRoleReveal.Command+        Game.Werewolf.Variant.NoRoleReveal.Engine         Game.Werewolf.Variant.Standard.Command         Game.Werewolf.Variant.Standard.Engine         Game.Werewolf.Variant.Standard.Error