packages feed

werewolf 1.1.1.0 → 1.2.0.0

raw patch · 53 files changed

+1813/−1298 lines, 53 filesdep +interpolatedep +template-haskell

Dependencies added: interpolate, template-haskell

Files

CHANGELOG.md view
@@ -7,6 +7,26 @@  ### Upcoming +### v1.2.0.0++*Major*++* Renamed FerinasGrunt to DruidsTurn. ([#155](https://github.com/hjwylde/werewolf/issues/155))+* Added tag field to Role. ([#155](https://github.com/hjwylde/werewolf/issues/155))+* Added variant field to Game. ([#222](https://github.com/hjwylde/werewolf/issues/222))++*Minor*++* Moved messages to external files using QuasiQuotes. ([#155](https://github.com/hjwylde/werewolf/issues/155))+* Moved role names, descriptions and rules to external files using QuasiQuotes. ([#155](https://github.com/hjwylde/werewolf/issues/155))+* Removed `--include-seer` option from `start`. ([#223](https://github.com/hjwylde/werewolf/issues/223))+* Removed `--force` option from `end`. ([#224](https://github.com/hjwylde/werewolf/issues/224))+* Added the no-role-knowledge variant. ([#182](https://github.com/hjwylde/werewolf/issues/182))++*Revisions*++* Updated Crooked Senator's balance to 2.+ ### v1.1.1.0  *Revisions*
app/Game/Werewolf/Command.hs view
@@ -24,6 +24,7 @@     validatePlayer, ) where +import Control.Lens.Extra import Control.Monad.Except import Control.Monad.Extra import Control.Monad.State@@ -31,9 +32,9 @@  import Data.Text (Text) -import Game.Werewolf.Game-import Game.Werewolf.Messages-import Game.Werewolf.Response+import Game.Werewolf+import Game.Werewolf.Message.Command+import Game.Werewolf.Message.Error import Game.Werewolf.Util  data Command = Command { apply :: forall m . (MonadError [Message] m, MonadState Game m, MonadWriter [Message] m) => m () }@@ -42,7 +43,10 @@ noopCommand = Command $ return ()  validatePlayer :: (MonadError [Message] m, MonadState Game m) => Text -> Text -> m ()-validatePlayer callerName name = do+validatePlayer callerName name' = do     whenM isGameOver                $ throwError [gameIsOverMessage callerName]-    unlessM (doesPlayerExist name)  $ throwError [playerDoesNotExistMessage callerName name]-    whenM (isPlayerDead name)       $ throwError [if callerName == name then playerIsDeadMessage callerName else targetIsDeadMessage callerName name]+    unlessM (doesPlayerExist name') $ throwError [playerDoesNotExistMessage callerName name']++    player <- findPlayerBy_ name name'++    when (is dead player) $ throwError [if callerName == name' then playerIsDeadMessage callerName else targetIsDeadMessage callerName player]
app/Game/Werewolf/Command/Global.hs view
@@ -25,19 +25,24 @@  import Game.Werewolf import Game.Werewolf.Command-import Game.Werewolf.Messages+import Game.Werewolf.Message.Command+import Game.Werewolf.Message.Error import Game.Werewolf.Util  bootCommand :: Text -> Text -> Command bootCommand callerName targetName = Command $ do     validatePlayer callerName callerName     validatePlayer callerName targetName++    caller <- findPlayerBy_ name callerName+    target <- findPlayerBy_ name targetName+     whenM (uses (boots . at targetName) $ elem callerName . fromMaybe []) $-        throwError [playerHasAlreadyVotedToBootMessage callerName targetName]+        throwError [playerHasAlreadyVotedToBootMessage callerName target]      boots %= Map.insertWith (++) targetName [callerName] -    tell [playerVotedToBootMessage callerName targetName]+    tell [playerVotedToBootMessage caller target]  quitCommand :: Text -> Command quitCommand callerName = Command $ do
app/Game/Werewolf/Command/Hunter.hs view
@@ -23,7 +23,8 @@  import Game.Werewolf import Game.Werewolf.Command-import Game.Werewolf.Messages+import Game.Werewolf.Message.Command+import Game.Werewolf.Message.Error import Game.Werewolf.Util  chooseCommand :: Text -> Text -> Command
app/Game/Werewolf/Command/Oracle.hs view
@@ -22,7 +22,7 @@  import Game.Werewolf import Game.Werewolf.Command-import Game.Werewolf.Messages+import Game.Werewolf.Message.Error import Game.Werewolf.Util  divineCommand :: Text -> Text -> Command
app/Game/Werewolf/Command/Orphan.hs view
@@ -22,7 +22,7 @@  import Game.Werewolf import Game.Werewolf.Command-import Game.Werewolf.Messages+import Game.Werewolf.Message.Error import Game.Werewolf.Util  chooseCommand :: Text -> Text -> Command
app/Game/Werewolf/Command/Protector.hs view
@@ -23,7 +23,7 @@  import Game.Werewolf import Game.Werewolf.Command-import Game.Werewolf.Messages+import Game.Werewolf.Message.Error import Game.Werewolf.Util  protectCommand :: Text -> Text -> Command
app/Game/Werewolf/Command/Scapegoat.hs view
@@ -22,7 +22,8 @@  import Game.Werewolf import Game.Werewolf.Command-import Game.Werewolf.Messages+import Game.Werewolf.Message.Command+import Game.Werewolf.Message.Error import Game.Werewolf.Util  chooseCommand :: Text -> [Text] -> Command
app/Game/Werewolf/Command/Seer.hs view
@@ -22,7 +22,7 @@  import Game.Werewolf import Game.Werewolf.Command-import Game.Werewolf.Messages+import Game.Werewolf.Message.Error import Game.Werewolf.Util  seeCommand :: Text -> Text -> Command
app/Game/Werewolf/Command/Status.hs view
@@ -11,7 +11,6 @@  {-# LANGUAGE FlexibleContexts      #-} {-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE OverloadedStrings     #-}  module Game.Werewolf.Command.Status (     -- * Commands@@ -25,11 +24,12 @@ import Data.List import Data.Text (Text) -import           Game.Werewolf          hiding (getPendingVoters)-import           Game.Werewolf.Command-import           Game.Werewolf.Messages-import qualified Game.Werewolf.Role     as Role-import           Game.Werewolf.Util+-- 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  circleCommand :: Text -> Bool -> Command circleCommand callerName includeDead = Command $ do@@ -39,7 +39,7 @@  pingCommand :: Text -> Command pingCommand callerName = Command $ use stage >>= \stage' -> case stage' of-    FerinasGrunt        -> return ()+    DruidsTurn          -> return ()     GameOver            -> tell [gameIsOverMessage callerName]     HuntersTurn1        -> pingRole hunterRole     HuntersTurn2        -> pingRole hunterRole@@ -60,7 +60,7 @@ pingRole role' = do     player <- findPlayerBy_ role role' -    tell [pingRoleMessage $ role' ^. Role.name]+    tell . (:[]) . pingRoleMessage role' =<< get     tell [pingPlayerMessage $ player ^. name]  pingVillagers :: (MonadState Game m, MonadWriter [Message] m) => m ()@@ -68,23 +68,21 @@     allowedVoterNames <- use allowedVoters     pendingVoterNames <- toListOf names <$> getPendingVoters -    tell [pingRoleMessage "village"]+    tell [pingVillageMessage]     tell $ map pingPlayerMessage (allowedVoterNames `intersect` pendingVoterNames)  pingWerewolves :: (MonadState Game m, MonadWriter [Message] m) => m () pingWerewolves = do     pendingVoters <- getPendingVoters -    tell [pingRoleMessage "Werewolves"]+    tell . (:[]) . pingWerewolvesMessage =<< get     tell $ map pingPlayerMessage (pendingVoters ^.. werewolves . name)  statusCommand :: Text -> Command-statusCommand callerName = Command $ use stage >>= \stage' -> case stage' of-    GameOver    -> tell [gameIsOverMessage callerName]-    _           -> tell . statusMessages stage' =<< use players-    where-        statusMessages stage players =-            currentStageMessages callerName stage ++-            [ rolesInGameMessage (Just callerName) (players ^.. roles)-            , playersInGameMessage callerName players-            ]+statusCommand callerName = Command $ do+    game <- get++    tell [ currentStageMessage callerName game+        , rolesInGameMessage (Just callerName) game+        , playersInGameMessage callerName game+        ]
app/Game/Werewolf/Command/Villager.hs view
@@ -29,7 +29,8 @@  import Game.Werewolf import Game.Werewolf.Command-import Game.Werewolf.Messages+import Game.Werewolf.Message.Command+import Game.Werewolf.Message.Error import Game.Werewolf.Util  unvoteCommand :: Text -> Command@@ -39,8 +40,10 @@      votes %= Map.delete callerName +    caller <- findPlayerBy_ name callerName+     whenJustM (preuse $ players . crookedSenators . alive) $ \crookedSenator ->-        tell [playerRescindedVoteMessage (crookedSenator ^. name) callerName]+        tell [playerRescindedVoteMessage (crookedSenator ^. name) caller]  voteCommand :: Text -> Text -> Command voteCommand callerName targetName = Command $ do@@ -50,8 +53,11 @@      votes %= Map.insert callerName targetName +    caller <- findPlayerBy_ name callerName+    target <- findPlayerBy_ name targetName+     whenJustM (preuse $ players . crookedSenators . alive) $ \crookedSenator ->-        tell [playerMadeLynchVoteMessage (Just $ crookedSenator ^. name) callerName targetName]+        tell [playerMadeLynchVoteMessage (Just $ crookedSenator ^. name) caller target]  validateCommand :: (MonadError [Message] m, MonadState Game m) => Text -> m () validateCommand callerName = do
app/Game/Werewolf/Command/Werewolf.hs view
@@ -33,7 +33,8 @@  import Game.Werewolf import Game.Werewolf.Command-import Game.Werewolf.Messages+import Game.Werewolf.Message.Command+import Game.Werewolf.Message.Error import Game.Werewolf.Util  unvoteCommand :: Text -> Command@@ -43,9 +44,10 @@      votes %= Map.delete callerName -    aliveWerewolfNames <- toListOf (players . werewolves . alive . name) <$> get+    aliveWerewolfNames  <- toListOf (players . werewolves . alive . name) <$> get+    caller              <- findPlayerBy_ name callerName -    tell [playerRescindedVoteMessage werewolfName callerName | werewolfName <- aliveWerewolfNames \\ [callerName]]+    tell [playerRescindedVoteMessage werewolfName caller | werewolfName <- aliveWerewolfNames \\ [callerName]]  voteCommand :: Text -> Text -> Command voteCommand callerName targetName = Command $ do@@ -56,9 +58,11 @@      votes %= Map.insert callerName targetName -    aliveWerewolfNames <- toListOf (players . werewolves . alive . name) <$> get+    aliveWerewolfNames  <- toListOf (players . werewolves . alive . name) <$> get+    caller              <- findPlayerBy_ name callerName+    target              <- findPlayerBy_ name targetName -    tell [playerMadeDevourVoteMessage werewolfName callerName targetName | werewolfName <- aliveWerewolfNames \\ [callerName]]+    tell [playerMadeDevourVoteMessage werewolfName caller target | werewolfName <- aliveWerewolfNames \\ [callerName]]  validateCommand :: (MonadError [Message] m, MonadState Game m) => Text -> m () validateCommand callerName = do
app/Game/Werewolf/Command/Witch.hs view
@@ -28,7 +28,7 @@  import Game.Werewolf import Game.Werewolf.Command-import Game.Werewolf.Messages+import Game.Werewolf.Message.Error import Game.Werewolf.Util  healCommand :: Text -> Command
app/Game/Werewolf/Engine.hs view
@@ -29,12 +29,14 @@ import qualified Data.Map        as Map import           Data.Maybe -import Game.Werewolf.Game     hiding (getAllowedVoters, getPendingVoters, hasAnyoneWon,-                               hasFallenAngelWon, hasVillagersWon, hasWerewolvesWon)-import Game.Werewolf.Messages+-- TODO (hjw): remove Message.Command+import Game.Werewolf.Game            hiding (getAllowedVoters, getPendingVoters, hasAnyoneWon,+                                      hasFallenAngelWon, hasVillagersWon, hasWerewolvesWon)+import Game.Werewolf.Message.Command+import Game.Werewolf.Message.Engine import Game.Werewolf.Player import Game.Werewolf.Response-import Game.Werewolf.Role     hiding (name)+import Game.Werewolf.Role            hiding (name) import Game.Werewolf.Util  import Prelude hiding (round)@@ -61,7 +63,7 @@  checkStage' :: (MonadRandom m, MonadState Game m, MonadWriter [Message] m) => m () checkStage' = use stage >>= \stage' -> case stage' of-    FerinasGrunt -> do+    DruidsTurn -> do         druid       <- findPlayerBy_ role druidRole         players'    <- filter (isn't alphaWolf) <$> getAdjacentAlivePlayers (druid ^. name) @@ -103,8 +105,8 @@         whenM (isJust <$> use protect) advanceStage      ScapegoatsTurn -> unlessM (use scapegoatBlamed) $ do-        allowedVoters' <- use allowedVoters-        tell [scapegoatChoseAllowedVotersMessage allowedVoters']+        game <- get+        tell [scapegoatChoseAllowedVotersMessage game]          advanceStage @@ -147,17 +149,13 @@             orphan <- findPlayerBy_ role orphanRole              whenM (isPlayerDead roleModelsName &&^ return (is alive orphan) &&^ return (is villager orphan)) $ do-                aliveWerewolfNames <- toListOf (players . werewolves . alive . name) <$> get-                 setPlayerAllegiance (orphan ^. name) Werewolves -                tell $ orphanJoinedPackMessages (orphan ^. name) aliveWerewolfNames+                tell . orphanJoinedPackMessages (orphan ^. name) =<< get          advanceStage      VillageDrunksTurn -> do-        aliveWerewolfNames <- toListOf (players . werewolves . alive . name) <$> get-         randomAllegiance <- getRandomAllegiance         players . villageDrunks . role . allegiance .= randomAllegiance @@ -165,13 +163,18 @@          if is villager villageDrunk             then tell [villageDrunkJoinedVillageMessage $ villageDrunk ^. name]-            else tell $ villageDrunkJoinedPackMessages (villageDrunk ^. name) aliveWerewolfNames+            else tell . villageDrunkJoinedPackMessages (villageDrunk ^. name) =<< get          advanceStage      VillagesTurn -> whenM (null <$> liftM2 intersect getAllowedVoters getPendingVoters) $ do-        tell . map (uncurry $ playerMadeLynchVoteMessage Nothing) =<< uses votes Map.toList+        uses votes Map.toList >>= mapM_ (\(voterName, voteeName) -> do+            voter <- findPlayerBy_ name voterName+            votee <- findPlayerBy_ name voteeName +            tell [playerMadeLynchVoteMessage Nothing voter votee]+            )+         advanceStage      WerewolvesTurn -> whenM (none (is werewolf) <$> getPendingVoters) $ do@@ -190,7 +193,7 @@     | is jester votee       = do         jesterRevealed .= True -        tell [jesterLynchedMessage $ votee ^. name]+        tell . (:[]) . jesterLynchedMessage =<< get     | is fallenAngel votee  = do         fallenAngelLynched .= True @@ -203,7 +206,7 @@         scapegoatBlamed .= True          killPlayer (scapegoat ^. name)-        tell [scapegoatLynchedMessage (scapegoat ^. name)]+        tell . (:[]) . scapegoatLynchedMessage =<< get     _               -> tell [noPlayerLynchedMessage]  devourVotee :: (MonadState Game m, MonadWriter [Message] m) => Maybe Player -> m ()
+ app/Game/Werewolf/Message.hs view
@@ -0,0 +1,65 @@+{-|+Module      : Game.Werewolf.Message+Description : Utility functions for building messages.++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 some utility functions for building messages. See "Game.Werewolf.Message.Command",+"Game.Werewolf.Message.Engine" and "Game.Werewolf.Message.Error" for actual message definitions.++@werewolf@ was designed to be ambivalent to the playing chat client. The response-message structure+reflects this by staying away from anything that could be construed as client-specific. This+includes features such as emoji support.+-}++{-# LANGUAGE OverloadedStrings #-}++module Game.Werewolf.Message (+    -- * Utility functions+    humanisePlayerWithRole, humanisePlayersWithRoles, humanisePlayerWithRoleIfTrueVillager,+    humanisePlayerWithState, article, conjugateToBe, pluralise,+) where++import Control.Lens       hiding (isn't)+import Control.Lens.Extra++import           Data.String.Humanise+import           Data.Text            (Text)+import qualified Data.Text            as T++import Game.Werewolf.Player+import Game.Werewolf.Role   hiding (name)++-- TODO (hjw): tidy up all of the messages++humanisePlayerWithRole :: Player -> Text+humanisePlayerWithRole player = T.concat [humanise player, " (", humanise $ player ^. role, ")"]++humanisePlayersWithRoles :: [Player] -> Text+humanisePlayersWithRoles = humanise . map humanisePlayerWithRole++humanisePlayerWithRoleIfTrueVillager :: Player -> Text+humanisePlayerWithRoleIfTrueVillager player+    | is trueVillager player    = humanisePlayerWithRole player+    | otherwise                 = humanise player++humanisePlayerWithState :: Player -> Text+humanisePlayerWithState player+    | is alive player   = humanise player+    | otherwise         = T.concat [humanise player, " (dead)"]++article :: Role -> Text+article role+    | role `elem` restrictedRoles   = "the"+    | otherwise                     = "a"++conjugateToBe :: Int -> Text+conjugateToBe 1 = "is"+conjugateToBe _ = "are"++pluralise :: Int -> Text -> Text+pluralise 1 word = word+pluralise _ word = T.snoc word 's'
+ app/Game/Werewolf/Message/Command.hs view
@@ -0,0 +1,194 @@+{-|+Module      : Game.Werewolf.Message.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.+-}++module Game.Werewolf.Message.Command (+    -- * Quit+    playerQuitMessage,++    -- * Boot+    playerVotedToBootMessage,++    -- * Circle+    circleMessage,++    -- * Choose+    playerShotMessage,++    -- * End+    gameEndedMessage,++    -- * Help+    gameDescriptionMessage, globalCommandsMessage, helpCommandsMessage, hunterCommandsMessage,+    oracleCommandsMessage, orphanCommandsMessage, protectorCommandsMessage, roleMessage,+    rulesMessage, scapegoatCommandsMessage, seerCommandsMessage, stagesMessage,+    standardCommandsMessage, statusCommandsMessage, witchCommandsMessage,++    -- * Ping+    pingPlayerMessage, pingRoleMessage, pingVillageMessage, pingWerewolvesMessage,++    -- * Status+    currentStageMessage, gameIsOverMessage, playersInGameMessage,++    -- * Unvote+    playerRescindedVoteMessage,++    -- * Version+    engineVersionMessage,++    -- * Vote+    playerMadeDevourVoteMessage, playerMadeLynchVoteMessage,+) where++import Control.Lens++import           Data.Maybe+import           Data.Text  (Text)+import qualified Data.Text  as T++import Game.Werewolf.Game+import Game.Werewolf.Player+import Game.Werewolf.Response+import Game.Werewolf.Role+import Game.Werewolf.Variant.NoRoleKnowledge.Command as NoRoleKnowledge+import Game.Werewolf.Variant.Standard.Command        as Standard++playerQuitMessage :: Player -> Message+playerQuitMessage = publicMessage . callerQuitText++playerVotedToBootMessage :: Player -> Player -> Message+playerVotedToBootMessage caller = publicMessage . callerVotedBootText caller++circleMessage :: Text -> [Player] -> Message+circleMessage to = privateMessage to . gameCircleText++playerShotMessage :: Player -> Message+playerShotMessage = publicMessage . playerShotText++gameEndedMessage :: Text -> Message+gameEndedMessage = publicMessage . gameEndedText++gameDescriptionMessage :: Text -> Message+gameDescriptionMessage to = privateMessage to gameDescriptionText++globalCommandsMessage :: Text -> Message+globalCommandsMessage to = privateMessage to globalCommandsText++helpCommandsMessage :: Text -> Message+helpCommandsMessage to = privateMessage to helpCommandsText++hunterCommandsMessage :: Text -> Message+hunterCommandsMessage to = privateMessage to hunterCommandsText++oracleCommandsMessage :: Text -> Message+oracleCommandsMessage to = privateMessage to oracleCommandsText++orphanCommandsMessage :: Text -> Message+orphanCommandsMessage to = privateMessage to orphanCommandsText++protectorCommandsMessage :: Text -> Message+protectorCommandsMessage to = privateMessage to protectorCommandsText++roleMessage :: Text -> Role -> Message+roleMessage to = privateMessage to . roleDescriptionText++rulesMessage :: Text -> Message+rulesMessage to = privateMessage to gameRulesText++scapegoatCommandsMessage :: Text -> Message+scapegoatCommandsMessage to = privateMessage to scapegoatCommandsText++seerCommandsMessage :: Text -> Message+seerCommandsMessage to = privateMessage to seerCommandsText++stagesMessage :: Text -> Maybe Game -> Message+stagesMessage to mGame = privateMessage to . T.concat $+    [ standardCycleText+    , sunsetText+    ] ++ [ orphansTurnText+    | isNothing mGame || has (players . orphans . named to) (fromJust mGame)+    ] ++ [ villageDrunksTurnText+    | isNothing mGame || has (players . villageDrunks . named to) (fromJust mGame)+    ] ++ [ seersTurnText+    | isNothing mGame || has (players . seers . named to) (fromJust mGame)+    ] ++ [ oraclesTurnText+    | isNothing mGame || has (players . oracles . named to) (fromJust mGame)+    ] ++ [ protectorsTurnText+    | isNothing mGame || has (players . protectors . named to) (fromJust mGame)+    ] ++ [ werewolvesTurnText+    ] ++ [ witchsTurnText+    | isNothing mGame || has (players . witches . named to) (fromJust mGame)+    ] ++ [ sunriseText+    ] ++ [ huntersTurnText+    | isNothing mGame || has (players . hunters . named to) (fromJust mGame)+    ] ++ [ druidsTurnText+    | isNothing mGame || has (players . druids . named to) (fromJust mGame)+    ] ++ [ villagesTurnText+    ] ++ [ huntersTurnText+    | isNothing mGame || has (players . hunters . named to) (fromJust mGame)+    ] ++ [ scapegoatsTurnText+    | isNothing mGame || has (players . scapegoats . named to) (fromJust mGame)+    ] ++ [ winConditionText+    ]++standardCommandsMessage :: Text -> Message+standardCommandsMessage to = privateMessage to standardCommandsText++statusCommandsMessage :: Text -> Message+statusCommandsMessage to = privateMessage to statusCommandsText++witchCommandsMessage :: Text -> Message+witchCommandsMessage to = privateMessage to witchCommandsText++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+    | 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++gameIsOverMessage :: Text -> Message+gameIsOverMessage to = privateMessage to gameOverText++playersInGameMessage :: Text -> Game -> Message+playersInGameMessage to game = privateMessage to $ T.append alivePlayersText' deadPlayersText'+    where+        alivePlayersText' = alivePlayersText game+        deadPlayersText'+            | has (players . traverse . dead) game  = deadPlayersText game+            | otherwise                             = T.empty++playerRescindedVoteMessage :: Text -> Player -> Message+playerRescindedVoteMessage to = privateMessage to . callerRescindedVoteText++engineVersionMessage :: Text -> Message+engineVersionMessage to = privateMessage to engineVersionText++playerMadeDevourVoteMessage :: Text -> Player -> Player -> Message+playerMadeDevourVoteMessage to caller = privateMessage to . callerVotedDevourText caller++playerMadeLynchVoteMessage :: Maybe Text -> Player -> Player -> Message+playerMadeLynchVoteMessage mTo caller = Message mTo . callerVotedLynchText caller
+ app/Game/Werewolf/Message/Engine.hs view
@@ -0,0 +1,328 @@+{-|+Module      : Game.Werewolf.Message.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.+-}++-- TODO (hjw): sort this file+module Game.Werewolf.Message.Engine (+    playerBootedMessage, villageDrunkJoinedPackMessages, villageDrunkJoinedVillageMessage,+    playerTurnedToStoneMessage, playerSeenMessage, playerDivinedMessage, playerDevouredMessage,+    noPlayerDevouredMessage, scapegoatChoseAllowedVotersMessage, playerPoisonedMessage,+    scapegoatLynchedMessage, playerLynchedMessage, noPlayerLynchedMessage, jesterLynchedMessage,+    ferinaGruntsMessage, orphanJoinedPackMessages, playerKilledMessage, playerLostMessage,+    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,+) where++import Control.Lens+import Control.Lens.Extra++import Data.List.Extra+import Data.Text       (Text)++import Game.Werewolf.Game+import Game.Werewolf.Player+import Game.Werewolf.Response+import Game.Werewolf.Role                           hiding (name)+import Game.Werewolf.Variant.NoRoleKnowledge.Engine as NoRoleKnowledge+import Game.Werewolf.Variant.Standard.Engine        as Standard++playerBootedMessage :: Player -> Message+playerBootedMessage = publicMessage . playerBootedText++gameOverMessages :: Game -> [Message]+gameOverMessages game+    | hasFallenAngelWon game    = concat+        [ [fallenAngelWonMessage]+        , [playerRolesMessage game]+        , [playerWonMessage fallenAngelsName]+        , map playerLostMessage (game ^.. players . names \\ [fallenAngelsName])+        ]+    | otherwise                 = concat+        [ [allegianceWonMessage winningAllegiance]+        , [playerRolesMessage game]+        , playerWonMessages+        , playerContributedMessages+        , playerLostMessages+        ]+    where+        winningAllegiance+            | hasVillagersWon game      = Villagers+            | hasWerewolvesWon game     = Werewolves+            | otherwise                 = undefined++        winningPlayers  = game ^.. players . traverse . filteredBy (role . allegiance) winningAllegiance+        losingPlayers   = game ^. players \\ winningPlayers++        playerWonMessages           = map playerWonMessage (winningPlayers ^.. traverse . alive . name)+        playerContributedMessages   = map playerContributedMessage (winningPlayers ^.. traverse . dead . name)+        playerLostMessages          = map playerLostMessage (losingPlayers ^.. names)++        fallenAngelsName = game ^?! players . fallenAngels . name++fallenAngelWonMessage :: Message+fallenAngelWonMessage = publicMessage fallenAngelWonText++allegianceWonMessage :: Allegiance -> Message+allegianceWonMessage = publicMessage . allegianceWonText++playerRolesMessage :: Game -> Message+playerRolesMessage = publicMessage . playerRolesText++newGameMessages :: Game -> [Message]+newGameMessages game = concat+    [ [newPlayersInGameMessage game]+    , [rolesInGameMessage Nothing game]+    , map newPlayerMessage players'+    , beholderMessages+    , 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 []++newPlayersInGameMessage :: Game -> Message+newPlayersInGameMessage = publicMessage . playersInGameText++rolesInGameMessage :: Maybe Text -> Game -> Message+rolesInGameMessage mTo game+    | has (variant . _NoRoleKnowledge) game = Message mTo $ NoRoleKnowledge.rolesInGameText game+    | otherwise                             = Message mTo $ Standard.rolesInGameText game++newPlayerMessage :: Player -> Message+newPlayerMessage player = privateMessage (player ^. name) $ newPlayerText player++beholderMessage :: Text -> Game -> Message+beholderMessage to = privateMessage to . beholderText++spitefulGhostMessage :: Text -> Game -> Message+spitefulGhostMessage to = privateMessage to . spitefulGhostText++trueVillagerMessage :: Game -> Message+trueVillagerMessage = publicMessage . trueVillagerText++fallenAngelMessage :: Message+fallenAngelMessage = publicMessage fallenAngelText++stageMessages :: Game -> [Message]+stageMessages game = case game ^. stage of+    DruidsTurn          -> []+    GameOver            -> []+    HuntersTurn1        -> huntersTurnMessages game+    HuntersTurn2        -> huntersTurnMessages game+    Lynching            -> []+    OraclesTurn         -> oraclesTurnMessages oraclesName game+    OrphansTurn         -> orphansTurnMessages orphansName game+    ProtectorsTurn      -> protectorsTurnMessages protectorsName game+    ScapegoatsTurn      -> [scapegoatsTurnMessage game]+    SeersTurn           -> seersTurnMessages seersName game+    Sunrise             -> [sunriseMessage]+    Sunset              -> [nightFallsMessage]+    VillageDrunksTurn   -> villageDrunksTurnMessages game+    VillagesTurn        -> [villagesTurnMessage]+    WerewolvesTurn      -> if is firstRound game+        then firstWerewolvesTurnMessages game+        else werewolvesTurnMessages aliveWerewolfNames game+    WitchsTurn          -> witchsTurnMessages game+    where+        players'            = game ^. players+        oraclesName         = players' ^?! oracles . name+        orphansName         = players' ^?! orphans . name+        protectorsName      = players' ^?! protectors . name+        seersName           = players' ^?! seers . name+        aliveWerewolfNames  = players' ^.. werewolves . alive . name++huntersTurnMessages :: Game -> [Message]+huntersTurnMessages game =+    [ publicMessage $ huntersTurnPublicText game+    , privateMessage hunterName huntersTurnPrivateText+    ]+    where+        hunterName = game ^?! players . hunters . name++oraclesTurnMessages :: Text -> Game -> [Message]+oraclesTurnMessages to game+    | has (variant . _NoRoleKnowledge) game =+        [ privateMessage to oraclesTurnPrivateText ]+    | otherwise                             =+        [ publicMessage oraclesTurnPublicText+        , privateMessage to oraclesTurnPrivateText+        ]++orphansTurnMessages :: Text -> Game -> [Message]+orphansTurnMessages to game+    | has (variant . _NoRoleKnowledge) game =+        [ privateMessage to orphansTurnPrivateText ]+    | otherwise                             =+        [ publicMessage orphansTurnPublicText+        , privateMessage to orphansTurnPrivateText+        ]++protectorsTurnMessages :: Text -> Game -> [Message]+protectorsTurnMessages to game+    | has (variant . _NoRoleKnowledge) game =+        [ privateMessage to protectorsTurnPrivateText ]+    | otherwise                             =+        [ publicMessage protectorsTurnPublicText+        , privateMessage to protectorsTurnPrivateText+        ]++scapegoatsTurnMessage :: Game -> Message+scapegoatsTurnMessage = publicMessage . scapegoatsTurnText++seersTurnMessages :: Text -> Game -> [Message]+seersTurnMessages to game+    | has (variant . _NoRoleKnowledge) game =+        [ privateMessage to seersTurnPrivateText ]+    | otherwise                             =+        [ publicMessage seersTurnPublicText+        , privateMessage to seersTurnPrivateText+        ]++villageDrunksTurnMessages :: Game -> [Message]+villageDrunksTurnMessages game+    | has (variant . _NoRoleKnowledge) game = []+    | otherwise                             = [publicMessage villageDrunksTurnText]++sunriseMessage :: Message+sunriseMessage = publicMessage sunriseText++nightFallsMessage :: Message+nightFallsMessage = publicMessage sunsetText++villagesTurnMessage :: Message+villagesTurnMessage = publicMessage villagesTurnText++firstWerewolvesTurnMessages :: Game -> [Message]+firstWerewolvesTurnMessages game =+    [privateMessage (player ^. name) (firstWerewolvesTurnText player game)+        | length werewolves > 1, player <- werewolves]+    ++ werewolvesTurnMessages (map (view name) werewolves) game+    where+        werewolves = game ^.. players . traverse . alive . filtered (is werewolf)++werewolvesTurnMessages :: [Text] -> Game -> [Message]+werewolvesTurnMessages tos game+    | has (variant . _NoRoleKnowledge) game =+        groupMessages tos werewolvesTurnPrivateText+    | otherwise                             =+        publicMessage werewolvesTurnPublicText+        : groupMessages tos werewolvesTurnPrivateText++witchsTurnMessages :: Game -> [Message]+witchsTurnMessages game = concat+    [ wakeUpMessages+    , healMessages+    , poisonMessages+    , [passMessage]+    ]+    where+        to              = game ^?! players . witches . name+        wakeUpMessages+            | has (variant . _NoRoleKnowledge) game = []+            | otherwise                             = [publicMessage witchsTurnText]+        passMessage     = privateMessage to passText+        healMessages+            | game ^. healUsed  = []+            | hasn't votee game = []+            | otherwise         = [privateMessage to $ healText game]+        poisonMessages+            | game ^. poisonUsed    = []+            | otherwise             = [privateMessage to poisonText]++playerWonMessage :: Text -> Message+playerWonMessage to = privateMessage to playerWonText++playerContributedMessage :: Text -> Message+playerContributedMessage to = privateMessage to playerContributedText++playerLostMessage :: Text -> Message+playerLostMessage to = privateMessage to playerLostText++playerKilledMessage :: Text -> Message+playerKilledMessage to = privateMessage to playerKilledText++orphanJoinedPackMessages :: Text -> Game -> [Message]+orphanJoinedPackMessages to game =+    privateMessage to (orphanJoinedWerewolvesPrivateText game)+    : groupMessages (map (view name) werewolves) (orphanJoinedWerewolvesGroupText game)+    where+        orphan      = game ^?! players . villageDrunks+        werewolves  = game ^.. players . traverse . alive . filtered (is werewolf) \\ [orphan]++ferinaGruntsMessage :: Message+ferinaGruntsMessage = publicMessage druidsTurnText++jesterLynchedMessage :: Game -> Message+jesterLynchedMessage = publicMessage . jesterLynchedText++noPlayerLynchedMessage :: Message+noPlayerLynchedMessage = publicMessage noPlayerLynchedText++playerLynchedMessage :: Player -> Message+playerLynchedMessage player+    | is simpleWerewolf player+        || is alphaWolf player  = publicMessage $ werewolfLynchedText player+    | otherwise                 = publicMessage $ playerLynchedText player++scapegoatLynchedMessage :: Game -> Message+scapegoatLynchedMessage = publicMessage . scapegoatLynchedText++scapegoatChoseAllowedVotersMessage :: Game -> Message+scapegoatChoseAllowedVotersMessage = publicMessage . scapegoatsTurnEndedText++noPlayerDevouredMessage :: Message+noPlayerDevouredMessage = publicMessage noPlayerDevouredText++playerDevouredMessage :: Player -> Message+playerDevouredMessage = publicMessage . playerDevouredText++playerDivinedMessage :: Text -> Player -> Message+playerDivinedMessage to = privateMessage to . playerDivinedText++playerPoisonedMessage :: Player -> Message+playerPoisonedMessage = publicMessage . playerPoisonedText++playerSeenMessage :: Text -> Player -> Message+playerSeenMessage to player+    | is alphaWolf player   = privateMessage to $ alphaWolfSeenText player+    | is lycan player       = privateMessage to $ lycanSeenText player+    | otherwise             = privateMessage to $ playerSeenText player++playerTurnedToStoneMessage :: Player -> Message+playerTurnedToStoneMessage = publicMessage . playerTurnedToStoneText++villageDrunkJoinedVillageMessage :: Text -> Message+villageDrunkJoinedVillageMessage to = privateMessage to villageDrunkJoinedVillageText++villageDrunkJoinedPackMessages :: Text -> Game -> [Message]+villageDrunkJoinedPackMessages to game =+    privateMessage to (villageDrunkJoinedWerewolvesPrivateText game)+    : groupMessages (map (view name) werewolves) (villageDrunkJoinedWerewolvesGroupText game)+    where+        villageDrunk    = game ^?! players . villageDrunks+        werewolves      = game ^.. players . traverse . alive . filtered (is werewolf) \\ [villageDrunk]
+ app/Game/Werewolf/Message/Error.hs view
@@ -0,0 +1,115 @@+{-|+Module      : Game.Werewolf.Message.Error+Description : Suite of error 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 error messages used throughout the werewolf game.+-}++module Game.Werewolf.Message.Error (+    -- * Command++    -- ** Boot+    playerHasAlreadyVotedToBootMessage,++    -- ** Choose+    playerCannotChooseSelfMessage, playerCannotChooseJesterMessage,+    playerMustChooseAtLeastOneTargetMessage,++    -- ** General+    noGameRunningMessage, playerCannotDoThatMessage, playerCannotDoThatRightNowMessage,+    playerIsDeadMessage, playerDoesNotExistMessage, targetIsDeadMessage,++    -- ** Heal+    playerHasAlreadyHealedMessage,++    -- ** Poison+    playerHasAlreadyPoisonedMessage,++    -- ** Protect+    playerCannotProtectSamePlayerTwiceInARowMessage,++    -- ** Start+    gameAlreadyRunningMessage, mustHaveAtLeast7PlayersMessage, playerNamesMustBeUniqueMessage,+    roleCountRestrictedMessage, roleDoesNotExistMessage,++    -- ** Unvote+    playerHasNotVotedMessage,++    -- ** Vote+    playerHasAlreadyVotedMessage, playerCannotDevourAnotherWerewolfMessage,+) where++import Data.Text (Text)++import Game.Werewolf.Player+import Game.Werewolf.Response+import Game.Werewolf.Role+import Game.Werewolf.Variant.Standard.Error++playerHasAlreadyVotedToBootMessage :: Text -> Player -> Message+playerHasAlreadyVotedToBootMessage to = privateMessage to . callerAlreadyVotedBootText++playerCannotChooseSelfMessage :: Text -> Message+playerCannotChooseSelfMessage to = privateMessage to callerCannotChooseSelfText++playerCannotChooseJesterMessage :: Text -> Message+playerCannotChooseJesterMessage to = privateMessage to callerCannotChooseJesterText++playerMustChooseAtLeastOneTargetMessage :: Text -> Message+playerMustChooseAtLeastOneTargetMessage to = privateMessage to noTargetText++noGameRunningMessage :: Text -> Message+noGameRunningMessage to = privateMessage to noGameRunningText++playerCannotDoThatMessage :: Text -> Message+playerCannotDoThatMessage to = privateMessage to callerCannotDoThatText++playerCannotDoThatRightNowMessage :: Text -> Message+playerCannotDoThatRightNowMessage to = privateMessage to callerCannotDoThatRightNowText++playerIsDeadMessage :: Text -> Message+playerIsDeadMessage to = privateMessage to callerDeadText++playerDoesNotExistMessage :: Text -> Text -> Message+playerDoesNotExistMessage to = privateMessage to . playerDoesNotExistText++targetIsDeadMessage :: Text -> Player -> Message+targetIsDeadMessage to = privateMessage to . targetDeadText++playerHasAlreadyHealedMessage :: Text -> Message+playerHasAlreadyHealedMessage to = privateMessage to callerAlreadyHealedText++playerHasAlreadyPoisonedMessage :: Text -> Message+playerHasAlreadyPoisonedMessage to = privateMessage to callerAlreadyPoisonedText++playerCannotProtectSamePlayerTwiceInARowMessage :: Text -> Message+playerCannotProtectSamePlayerTwiceInARowMessage to = privateMessage to callerCannotProtectSamePlayerText++gameAlreadyRunningMessage :: Text -> Message+gameAlreadyRunningMessage to = privateMessage to gameAlreadyRunningText++mustHaveAtLeast7PlayersMessage :: Text -> Message+mustHaveAtLeast7PlayersMessage to = privateMessage to playerCountTooLowText++playerNamesMustBeUniqueMessage :: Text -> Message+playerNamesMustBeUniqueMessage to = privateMessage to playerNamesNotUniqueText++roleCountRestrictedMessage :: Text -> Role -> Message+roleCountRestrictedMessage to = privateMessage to . roleCountRestrictedText++roleDoesNotExistMessage :: Text -> Text -> Message+roleDoesNotExistMessage to = privateMessage to . roleDoesNotExistText++playerHasNotVotedMessage :: Text -> Message+playerHasNotVotedMessage to = privateMessage to callerNotVotedText++playerHasAlreadyVotedMessage :: Text -> Message+playerHasAlreadyVotedMessage to = privateMessage to callerAlreadyVotedText++playerCannotDevourAnotherWerewolfMessage :: Text -> Message+playerCannotDevourAnotherWerewolfMessage to = privateMessage to callerCannotDevourAnotherWerewolfText
− app/Game/Werewolf/Messages.hs
@@ -1,658 +0,0 @@-{-|-Module      : Game.Werewolf.Messages-Description : Suite of 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 messages used throughout the werewolf game, including both game play messages and-binary errors.--@werewolf@ was designed to be ambivalent to the playing chat client. The response-message structure-reflects this by staying away from anything that could be construed as client-specific. This-includes features such as emoji support.--}--{-# LANGUAGE OverloadedStrings #-}--module Game.Werewolf.Messages (-    -- * Generic messages-    newGameMessages, stageMessages, gameOverMessages, playerQuitMessage, gameIsOverMessage,-    playerKilledMessage,--    -- ** Error messages-    playerDoesNotExistMessage, playerCannotDoThatMessage, playerCannotDoThatRightNowMessage,-    playerIsDeadMessage, targetIsDeadMessage,--    -- * Boot messages-    playerVotedToBootMessage, playerBootedMessage,--    -- ** Error messages-    playerHasAlreadyVotedToBootMessage,--    -- * Circle messages-    circleMessage,--    -- * Ping messages-    pingPlayerMessage, pingRoleMessage,--    -- * Status messages-    currentStageMessages, rolesInGameMessage, playersInGameMessage,--    -- * Druid's turn messages-    ferinaGruntsMessage,--    -- * Hunter's turn messages-    playerShotMessage,--    -- * Oracle's turn messages-    playerDivinedMessage,--    -- * Orphan's turn messages-    orphanJoinedPackMessages,--    -- * Protector's turn messages--    -- ** Error messages-    playerCannotProtectSamePlayerTwiceInARowMessage,--    -- * Scapegoat's turn messages-    scapegoatChoseAllowedVotersMessage,--    -- ** Error messages-    playerMustChooseAtLeastOneTargetMessage, playerCannotChooseJesterMessage,--    -- * Seer's turn messages-    playerSeenMessage,--    -- * Village Drunk's turn messages-    villageDrunkJoinedVillageMessage, villageDrunkJoinedPackMessages,--    -- * Villages' turn messages-    playerMadeLynchVoteMessage, playerRescindedVoteMessage, playerLynchedMessage,-    noPlayerLynchedMessage, jesterLynchedMessage, scapegoatLynchedMessage,--    -- ** Error messages-    playerHasAlreadyVotedMessage, playerHasNotVotedMessage,--    -- * Werewolves' turn messages-    playerMadeDevourVoteMessage, playerDevouredMessage, playerTurnedToStoneMessage,-    noPlayerDevouredMessage,--    -- ** Error messages-    playerCannotDevourAnotherWerewolfMessage,--    -- ** Error messages-    playerCannotChooseSelfMessage,--    -- * Witch's turn messages-    playerPoisonedMessage,--    -- ** Error messages-    playerHasAlreadyHealedMessage, playerHasAlreadyPoisonedMessage,-) where--import Control.Arrow-import Control.Lens-import Control.Lens.Extra--import           Data.List.Extra-import           Data.String.Humanise-import           Data.Text            (Text)-import qualified Data.Text            as T--import Game.Werewolf.Game-import Game.Werewolf.Player-import Game.Werewolf.Response-import Game.Werewolf.Role     hiding (name)--newGameMessages :: Game -> [Message]-newGameMessages game = concat-    [ [newPlayersInGameMessage $ players' ^.. names]-    , [rolesInGameMessage Nothing $ players' ^.. roles]-    , map newPlayerMessage players'-    , beholderMessages-    , spitefulGhostMessages-    , trueVillagerMessages-    , fallenAngelMessages-    , stageMessages game-    ]-    where-        players'                = game ^. players-        beholderMessages        = case (,) <$> players' ^? beholders <*> players' ^? seers of-            Just (beholder, seer)   -> [beholderMessage (beholder ^. name) (humanise seer)]-            _                       -> []-        spitefulGhostMessages   = case players' ^? spitefulGhosts of-            Just spitefulGhost  -> [spitefulGhostMessage (spitefulGhost ^. name) (players' \\ [spitefulGhost])]-            _                   -> []-        trueVillagerMessages    = case players' ^? trueVillagers of-            Just trueVillager   -> [trueVillagerMessage $ humanise trueVillager]-            _                   -> []-        fallenAngelMessages     = if has fallenAngels players'-            then [fallenAngelMessage]-            else []--newPlayersInGameMessage :: [Text] -> Message-newPlayersInGameMessage playerNames = publicMessage $ T.concat-    ["A new game of werewolf is starting with ", concatList playerNames, "!"]--newPlayerMessage :: Player -> Message-newPlayerMessage player = privateMessage (player ^. name) $ T.intercalate "\n"-    [ T.concat ["You're ", article playerRole, " ", humanise playerRole, "."]-    , playerRole ^. description-    , playerRole ^. rules-    ]-    where-        playerRole = player ^. role--beholderMessage :: Text -> Text -> Message-beholderMessage to seerName = privateMessage to $ T.concat-    [ "The Seer has always been held in high regard among the Villagers. Few are as lucky as you to"-    , " know the Seer, ", seerName, ", personally."-    ]--spitefulGhostMessage :: Text -> [Player] -> Message-spitefulGhostMessage to players = privateMessage to $ T.concat-    [ "Being ethereal seldom has it's benefits. Perhaps however this knowledge of the townsfolks' "-    , "natures will bring you some joy in the afterlife: ", playerNamesWithRoles, "."-    ]-    where-        playerNamesWithRoles = concatList $ map-            (\player -> T.concat [humanise player, " (", humanise $ player ^. role, ")"])-            players--trueVillagerMessage :: Text -> Message-trueVillagerMessage name = publicMessage $ T.unwords-    [ "Unguarded advice is seldom given, for advice is a dangerous gift, even from the wise to the"-    , "wise, and all courses may run ill. Yet as you feel like you need help, I begrudgingly leave"-    , "you with this:", name, "is the True Villager."-    ]--fallenAngelMessage :: Message-fallenAngelMessage = publicMessage $ T.unwords-    [ "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."-    ]--stageMessages :: Game -> [Message]-stageMessages game = case game ^. stage of-    FerinasGrunt        -> []-    GameOver            -> []-    HuntersTurn1        -> huntersTurnMessages huntersName-    HuntersTurn2        -> huntersTurnMessages huntersName-    Lynching            -> []-    OraclesTurn         -> oraclesTurnMessages oraclesName-    OrphansTurn         -> orphansTurnMessages orphansName-    ProtectorsTurn      -> protectorsTurnMessages protectorsName-    ScapegoatsTurn      -> scapegoatsTurnMessages scapegoatsName-    SeersTurn           -> seersTurnMessages seersName-    Sunrise             -> [sunriseMessage]-    Sunset              -> [nightFallsMessage]-    VillageDrunksTurn   -> [villageDrunksTurnMessage]-    VillagesTurn        -> villagesTurnMessages-    WerewolvesTurn      -> if is firstRound game-        then firstWerewolvesTurnMessages aliveWerewolfNames-        else werewolvesTurnMessages aliveWerewolfNames-    WitchsTurn          -> witchsTurnMessages game-    where-        players'            = game ^. players-        huntersName         = players' ^?! hunters . name-        oraclesName         = players' ^?! oracles . name-        orphansName         = players' ^?! orphans . name-        protectorsName      = players' ^?! protectors . name-        scapegoatsName      = humanise $ players' ^?! scapegoats-        seersName           = players' ^?! seers . name-        aliveWerewolfNames  = players' ^.. werewolves . alive . name--huntersTurnMessages :: Text -> [Message]-huntersTurnMessages huntersName =-    [ publicMessage $ T.unwords ["Just before", huntersName, "was murdered they let off a shot."]-    , privateMessage huntersName "Whom do you `choose` to kill with your last shot?"-    ]--oraclesTurnMessages :: Text -> [Message]-oraclesTurnMessages to =-    [ publicMessage "The Oracle wakes up."-    , privateMessage to "Whose role would you like to `divine`?"-    ]--orphansTurnMessages :: Text -> [Message]-orphansTurnMessages to =-    [ publicMessage "The Orphan wakes up."-    , privateMessage to "Whom do you `choose` to be your role model?"-    ]--protectorsTurnMessages :: Text -> [Message]-protectorsTurnMessages to =-    [ publicMessage "The Protector wakes up."-    , privateMessage to "Whom would you like to `protect`?"-    ]--scapegoatsTurnMessages :: Text -> [Message]-scapegoatsTurnMessages scapegoatsName =-    [ publicMessage "Just before the Scapegoat burns to a complete crisp, they cry out a dying wish."-    , publicMessage $ T.concat [scapegoatsName, ", which players do you `choose` to vote on the next day?"]-    ]--seersTurnMessages :: Text -> [Message]-seersTurnMessages to =-    [ publicMessage "The Seer wakes up."-    , privateMessage to "Whose allegiance would you like to `see`?"-    ]--villageDrunksTurnMessage :: Message-villageDrunksTurnMessage = publicMessage "The Village Drunk sobers up."--sunriseMessage :: Message-sunriseMessage = publicMessage "The sun rises. Everybody wakes up and opens their eyes..."--nightFallsMessage :: Message-nightFallsMessage = publicMessage "Night falls, the village is asleep."--villagesTurnMessages :: [Message]-villagesTurnMessages =-    [ publicMessage "As the village gathers in the square the Town Clerk calls for a vote."-    , publicMessage "Whom would you like to `vote` to lynch?"-    ]--firstWerewolvesTurnMessages :: [Text] -> [Message]-firstWerewolvesTurnMessages tos =-    [privateMessage to $ packMessage to | length tos > 1, to <- tos]-    ++ werewolvesTurnMessages tos-    where-        packMessage werewolfName    = T.unwords-            [ "You feel restless, like an old curse is keeping you from sleep. It seems you're not"-            , "the only one...", packNames werewolfName-            , conjugateToBe (length tos - 1), "also emerging from their"-            , tryPlural (length tos - 1) "home"-            ]-        packNames werewolfName      = concatList $ tos \\ [werewolfName]--werewolvesTurnMessages :: [Text] -> [Message]-werewolvesTurnMessages tos =-    publicMessage "The Werewolves wake up, transform and choose a new victim."-    : groupMessages tos "Whom would you like to `vote` to devour?"--witchsTurnMessages :: Game -> [Message]-witchsTurnMessages game = concat-    [ [wakeUpMessage]-    , devourMessages-    , healMessages-    , poisonMessages-    , [passMessage]-    ]-    where-        witchsName      = game ^?! players . witches . name-        wakeUpMessage   = publicMessage "The Witch wakes up."-        passMessage     = privateMessage witchsName "Type `pass` to end your turn."-        devourMessages  = case game ^? votee of-            Just votee ->-                [ privateMessage witchsName $-                    T.unwords ["You see", humanise votee, "sprawled outside bleeding uncontrollably."]-                ]-            _               -> []-        healMessages-            | game ^. healUsed  = []-            | hasn't votee game = []-            | otherwise         = [privateMessage witchsName "Would you like to `heal` them?"]-        poisonMessages-            | game ^. poisonUsed    = []-            | otherwise             = [privateMessage witchsName "Would you like to `poison` anyone?"]--gameOverMessages :: Game -> [Message]-gameOverMessages game-    | hasFallenAngelWon game    = concat-        [ [publicMessage "You should have heeded my warning, for now the Fallen Angel has been set free!"]-        , [publicMessage "The game is over! The Fallen Angel has won."]-        , [playerRolesMessage]-        , [playerWonMessage fallenAngelsName]-        , map playerLostMessage (game ^.. players . names \\ [fallenAngelsName])-        ]-    | hasVillagersWon game      = concat-        [ [publicMessage "The game is over! The Villagers have won."]-        , [playerRolesMessage]-        , playerWonMessages-        , playerContributedMessages-        , playerLostMessages-        ]-    | hasWerewolvesWon game     = concat-        [ [publicMessage "The game is over! The Werewolves have won."]-        , [playerRolesMessage]-        , playerWonMessages-        , playerContributedMessages-        , playerLostMessages-        ]-    | otherwise             = undefined-    where-        playerRolesMessage = publicMessage $ T.concat-            [ "As I know you're all wondering who lied to you, here's the role allocations: "-            , concatList $ map-                (\player -> T.concat [humanise player, " (", humanise $ player ^. role, ")"])-                (game ^. players)-            , "."-            ]--        winningAllegiance-            | hasVillagersWon game      = Villagers-            | hasWerewolvesWon game     = Werewolves-            | otherwise                 = undefined--        winningPlayers  = game ^.. players . traverse . filteredBy (role . allegiance) winningAllegiance-        losingPlayers   = game ^. players \\ winningPlayers--        playerWonMessages           = map playerWonMessage (winningPlayers ^.. traverse . alive . name)-        playerContributedMessages   = map playerContributedMessage (winningPlayers ^.. traverse . dead . name)-        playerLostMessages          = map playerLostMessage (losingPlayers ^.. names)--        fallenAngelsName = game ^?! players . fallenAngels . name--playerWonMessage :: Text -> Message-playerWonMessage to = privateMessage to "Victory! You won!"--playerContributedMessage :: Text -> Message-playerContributedMessage to = privateMessage to "Your team won, but you died. Congratulations?"--playerLostMessage :: Text -> Message-playerLostMessage to = privateMessage to "Feck, you lost this time round."--playerQuitMessage :: Player -> Message-playerQuitMessage player = publicMessage $ T.unwords [playerName, "the", playerRole, "has quit!"]-    where-        playerName = humanise player-        playerRole = humanise $ player ^. role--gameIsOverMessage :: Text -> Message-gameIsOverMessage to = privateMessage to "The game is over!"--playerKilledMessage :: Text -> Message-playerKilledMessage to = privateMessage to "Guh, you've been killed!"--playerDoesNotExistMessage :: Text -> Text -> Message-playerDoesNotExistMessage to name = privateMessage to $ T.unwords ["Player", name, "does not exist."]--playerCannotDoThatMessage :: Text -> Message-playerCannotDoThatMessage to = privateMessage to "You cannot do that!"--playerCannotDoThatRightNowMessage :: Text -> Message-playerCannotDoThatRightNowMessage to = privateMessage to "You cannot do that right now!"--playerIsDeadMessage :: Text -> Message-playerIsDeadMessage to = privateMessage to "Sshh, you're meant to be dead!"--targetIsDeadMessage :: Text -> Text -> Message-targetIsDeadMessage to targetName = privateMessage to $ T.unwords [targetName, "is already dead!"]--playerVotedToBootMessage :: Text -> Text -> Message-playerVotedToBootMessage playerName targetName = publicMessage $ T.concat-    [playerName, " voted to boot ", targetName, "!"]--playerBootedMessage :: Player -> Message-playerBootedMessage player = publicMessage $ T.unwords-    [playerName, "the", playerRole , "has been booted from the game!"]-    where-        playerName = humanise player-        playerRole = humanise $ player ^. role--playerHasAlreadyVotedToBootMessage :: Text -> Text -> Message-playerHasAlreadyVotedToBootMessage to targetName = privateMessage to $ T.concat-    ["You've already voted to boot ", targetName, "!"]--circleMessage :: Text -> [Player] -> Message-circleMessage to players = privateMessage to $ T.intercalate "\n"-    [ "The players are sitting in the following order:"-    , T.intercalate " <-> " (map playerName (players ++ [head players]))-    ]-    where-        playerName player = T.concat [humanise player, if is dead player then " (dead)" else ""]--pingPlayerMessage :: Text -> Message-pingPlayerMessage to = privateMessage to "Waiting on you..."--pingRoleMessage :: Text -> Message-pingRoleMessage roleName = publicMessage $ T.concat ["Waiting on the ", roleName, "..."]--currentStageMessages :: Text -> Stage -> [Message]-currentStageMessages _ FerinasGrunt = []-currentStageMessages to GameOver    = [gameIsOverMessage to]-currentStageMessages _ Lynching     = []-currentStageMessages _ Sunrise      = []-currentStageMessages _ Sunset       = []-currentStageMessages to turn        = [privateMessage to $ T.concat-    [ "It's currently the ", humanise turn, "."-    ]]--rolesInGameMessage :: Maybe Text -> [Role] -> Message-rolesInGameMessage mTo roles = Message mTo $ T.concat-    [ "The roles in play are "-    , concatList $ map (\(role, count) ->-        T.concat [humanise role, " (", T.pack $ show count, ")"])-        roleCounts-    , " for a total balance of ", T.pack $ show totalBalance, "."-    ]-    where-        roleCounts      = map (head &&& length) (groupSortOn (humanise :: Role -> Text) roles)-        totalBalance    = sumOf (traverse . balance) roles--playersInGameMessage :: Text -> [Player] -> Message-playersInGameMessage to players = privateMessage to . T.intercalate "\n" $-    alivePlayersText : [deadPlayersText | any (is dead) players]-    where-        alivePlayers    = players ^.. traverse . alive-        deadPlayers     = players ^.. traverse . dead--        alivePlayersText            = T.concat-            [ "The following players are still alive: "-            , concatList $ map (\player -> if is trueVillager player then playerNameWithRole player else humanise player) alivePlayers, "."-            ]-        deadPlayersText             = T.concat-            [ "The following players are dead: "-            , concatList $ map playerNameWithRole deadPlayers, "."-            ]-        playerNameWithRole player   = T.concat [humanise player, " (", humanise $ player ^. role, ")"]--ferinaGruntsMessage :: Message-ferinaGruntsMessage = publicMessage-    "Ferina wakes from her slumber, disturbed and on edge. She loudly grunts as she smells danger."--playerShotMessage :: Player -> Message-playerShotMessage target = publicMessage $ T.unwords-    [ targetName, "the", targetRole, "slumps down to the ground, hands clutching at their chest"-    , "while blood slips between their fingers and pools around them."-    ]-    where-        targetName = humanise target-        targetRole = humanise $ target ^. role--playerDivinedMessage :: Text -> Player -> Message-playerDivinedMessage to player = privateMessage to $ T.concat-    [playerName, " is ", article playerRole, " ", humanise playerRole, "."]-    where-        playerName = humanise player-        playerRole = player ^. role--orphanJoinedPackMessages :: Text -> [Text] -> [Message]-orphanJoinedPackMessages orphansName werewolfNames =-    privateMessage orphansName (T.unwords-        [ "The death of your role model is distressing. Without second thought you abandon the"-        , "Villagers and run off into the woods, towards a new home. As you arrive you see the"-        , tryPlural (length werewolfNames) "face", "of"-        , concatList werewolfNames, "waiting for you."-        ])-    : groupMessages werewolfNames (T.unwords-        [ orphansName, "the Orphan scampers off into the woods. Without their role model they have"-        , "abandoned the village and are in search of a new home. You welcome them into your pack."-        ])--playerCannotProtectSamePlayerTwiceInARowMessage :: Text -> Message-playerCannotProtectSamePlayerTwiceInARowMessage to =-    privateMessage to "You cannot protect the same player twice in a row!"--scapegoatChoseAllowedVotersMessage :: [Text] -> Message-scapegoatChoseAllowedVotersMessage allowedVoters = publicMessage $ T.unwords-    [ "On the next day only", concatList allowedVoters, "shall be allowed to vote. The Town Crier,"-    , "realising how foolish it was to kill the Scapegoat, grants them this wish."-    ]--playerMustChooseAtLeastOneTargetMessage :: Text -> Message-playerMustChooseAtLeastOneTargetMessage to =-    privateMessage to "You must choose at least 1 target!"--playerCannotChooseJesterMessage :: Text -> Message-playerCannotChooseJesterMessage to =-    privateMessage to "You cannot choose the Jester!"--playerSeenMessage :: Text -> Player -> Message-playerSeenMessage to player = privateMessage to $ T.concat-    [playerName, " is aligned with ", article, humanise allegiance', "."]-    where-        playerName  = humanise player-        allegiance'-            | is alphaWolf player   = Villagers-            | is lycan player       = Werewolves-            | otherwise             = player ^. role . allegiance-        article     = if allegiance' == NoOne then "" else "the "--villageDrunkJoinedVillageMessage :: Text -> Message-villageDrunkJoinedVillageMessage to = privateMessage to $ T.unwords-    [ "Somehow you managed to avoid getting killed while in your drunken stupor. Thank God for"-    , "that, maybe now you can actually help the village."-    ]--villageDrunkJoinedPackMessages :: Text -> [Text] -> [Message]-villageDrunkJoinedPackMessages villageDrunksName werewolfNames =-    privateMessage villageDrunksName (T.concat-        [ "As you start to feel sober for the first time in days a new thirst begins to take hold."-        , " The bloodthirst starts to bring back memories, memories of your true home with "-        , concatList werewolfNames, "."-        ])-    : groupMessages werewolfNames (T.unwords-        [ villageDrunksName-        , "the Village Drunk has finally sobered up and remembered their true home."-        ])--playerMadeLynchVoteMessage :: Maybe Text -> Text -> Text -> Message-playerMadeLynchVoteMessage mTo voterName targetName = Message mTo $ T.concat-    [ voterName, " voted to lynch ", targetName, "."-    ]--playerRescindedVoteMessage :: Text -> Text -> Message-playerRescindedVoteMessage to voterName = privateMessage to $ T.unwords-    [ voterName, "rescinded their vote."-    ]--playerLynchedMessage :: Player -> Message-playerLynchedMessage player-    | is simpleWerewolf player-        || is alphaWolf player  = publicMessage $ T.concat-        [ playerName, " is tied up to a pyre and set alight. As they scream their body starts to "-        , "contort and writhe, transforming into ", article playerRole, " "-        , humanise playerRole, ".", " Thankfully they go limp before breaking free of their "-        , "restraints."-        ]-    | otherwise                 = publicMessage $ T.concat-        [ playerName, " is tied up to a pyre and set alight. Eventually the screams start to die "-        , "and with their last breath, they reveal themselves as ", article playerRole, " "-        , humanise playerRole, "."-        ]-    where-        playerName = humanise player-        playerRole = player ^. role--noPlayerLynchedMessage :: Message-noPlayerLynchedMessage = publicMessage $ T.unwords-    [ "Daylight is wasted as the townsfolk squabble over whom to tie up. Looks like no-one is being"-    , "burned this day."-    ]--jesterLynchedMessage :: Text -> Message-jesterLynchedMessage name = publicMessage $ T.concat-    [ "Just as the townsfolk tie ", name, " up to the pyre, a voice in the crowd yells out."-    , " \"We can't burn ", name, "! He's the joke of the town!\" "-    , name, " the Jester is quickly untied and apologised to."-    ]--scapegoatLynchedMessage :: Text -> Message-scapegoatLynchedMessage name = publicMessage $ T.unwords-    [ "The townsfolk squabble over whom to tie up. Just as they are about to call it a day"-    , "they notice that", name, "has been acting awfully suspicious."-    , "Not wanting to take any chances,", name, "is promptly tied to a pyre and burned alive."-    ]--playerHasAlreadyVotedMessage :: Text -> Message-playerHasAlreadyVotedMessage to = privateMessage to "You've already voted!"--playerHasNotVotedMessage :: Text -> Message-playerHasNotVotedMessage to = privateMessage to "You haven't voted yet!"--playerMadeDevourVoteMessage :: Text -> Text -> Text -> Message-playerMadeDevourVoteMessage to voterName targetName = privateMessage to $ T.concat-    [ voterName, " voted to devour ", targetName, "."-    ]--playerDevouredMessage :: Player -> Message-playerDevouredMessage player = publicMessage $ T.concat-    [ "As you open them you notice a door broken down and "-    , playerName, "'s guts half devoured and spilling out over the cobblestones."-    , " From the look of their personal effects, you deduce they were "-    , article playerRole, " ", humanise playerRole, "."-    ]-    where-        playerName = humanise player-        playerRole = player ^. role--playerTurnedToStoneMessage :: Player -> Message-playerTurnedToStoneMessage player = publicMessage $ T.unwords-    [ "Next to them you see a stone", playerRole, "statue, cold to the touch.", playerName-    , "must have looked into the eyes of the Medusa at the very end."-    ]-    where-        playerName = humanise player-        playerRole = humanise $ player ^. role--noPlayerDevouredMessage :: Message-noPlayerDevouredMessage = publicMessage $ T.unwords-    [ "Surprisingly you see everyone present at the town square."-    , "Perhaps the Werewolves have left Fougères?"-    ]--playerCannotDevourAnotherWerewolfMessage :: Text -> Message-playerCannotDevourAnotherWerewolfMessage to = privateMessage to "You cannot devour another Werewolf!"--playerCannotChooseSelfMessage :: Text -> Message-playerCannotChooseSelfMessage to = privateMessage to "You cannot choose yourself!"--playerPoisonedMessage :: Player -> Message-playerPoisonedMessage player = publicMessage $ T.unwords-    [ "Upon further discovery, it looks like the Witch struck in the night."-    , playerName, "the", playerRole, "is hanging over the side of their bed, poisoned."-    ]-    where-        playerName = humanise player-        playerRole = humanise $ player ^. role--playerHasAlreadyHealedMessage :: Text -> Message-playerHasAlreadyHealedMessage to = privateMessage to "You've already healed someone!"--playerHasAlreadyPoisonedMessage :: Text -> Message-playerHasAlreadyPoisonedMessage to = privateMessage to "You've already poisoned someone!"--article :: Role -> Text-article role-    | role `elem` restrictedRoles   = "the"-    | otherwise                     = "a"--concatList :: [Text] -> Text-concatList []       = ""-concatList [word]   = word-concatList words    = T.unwords [T.intercalate ", " (init words), "and", last words]--conjugateToBe :: Int -> Text-conjugateToBe 1 = "is"-conjugateToBe _ = "are"--tryPlural :: Int -> Text -> Text-tryPlural 1 word = word-tryPlural _ word = T.snoc word 's'
app/Game/Werewolf/Util.hs view
@@ -50,13 +50,14 @@ import           Data.Maybe import           Data.Text  (Text) -import           Game.Werewolf.Game     hiding (getAllowedVoters, getPendingVoters, hasAnyoneWon,-                                         hasFallenAngelWon, hasVillagersWon, hasWerewolvesWon)-import qualified Game.Werewolf.Game     as Game-import           Game.Werewolf.Messages+import           Game.Werewolf.Game           hiding (getAllowedVoters, getPendingVoters,+                                               hasAnyoneWon, hasFallenAngelWon, hasVillagersWon,+                                               hasWerewolvesWon)+import qualified Game.Werewolf.Game           as Game+import           Game.Werewolf.Message.Engine import           Game.Werewolf.Player import           Game.Werewolf.Response-import           Game.Werewolf.Role     hiding (name)+import           Game.Werewolf.Role           hiding (name)  import Prelude hiding (round) 
+ app/Game/Werewolf/Variant/NoRoleKnowledge/Command.hs view
@@ -0,0 +1,36 @@+{-|+Module      : Game.Werewolf.Variant.NoRoleKnowledge.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 'NoRoleKnowledge'+variant.+-}++{-# LANGUAGE QuasiQuotes #-}++module Game.Werewolf.Variant.NoRoleKnowledge.Command (+    -- * Ping+    rolePingedText, werewolvesPingedText,++    -- * Status+    currentTurnText,+) where++import Data.String.Interpolate.Extra+import Data.Text                     (Text)++import Game.Werewolf++rolePingedText :: Role -> Text+rolePingedText _ = [iFile|variant/no-role-knowledge/command/ping/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|]
+ app/Game/Werewolf/Variant/NoRoleKnowledge/Engine.hs view
@@ -0,0 +1,32 @@+{-|+Module      : Game.Werewolf.Variant.NoRoleKnowledge.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 'NoRoleKnowledge'+variant.+-}++{-# LANGUAGE QuasiQuotes #-}++module Game.Werewolf.Variant.NoRoleKnowledge.Engine (+    -- * New game+    rolesInGameText,+) where++import Control.Lens++import           Data.String.Interpolate.Extra+import           Data.Text                     (Text)+import qualified Data.Text                     as T++import Game.Werewolf++rolesInGameText :: Game -> Text+rolesInGameText game = [iFile|variant/no-role-knowledge/engine/new-game/roles-in-game.txt|]+    where+        totalBalance = sumOf (players . roles . balance) game
+ app/Game/Werewolf/Variant/Standard/Command.hs view
@@ -0,0 +1,206 @@+{-|+Module      : Game.Werewolf.Variant.Standard.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 'Standard' variant.+-}++{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes       #-}++module Game.Werewolf.Variant.Standard.Command (+    -- * Boot+    callerVotedBootText,++    -- * Circle+    gameCircleText,++    -- * Choose+    playerShotText,++    -- * End+    gameEndedText,++    -- * Help+    druidsTurnText, gameDescriptionText, gameRulesText, globalCommandsText, helpCommandsText,+    hunterCommandsText, huntersTurnText, oracleCommandsText, oraclesTurnText, orphanCommandsText,+    orphansTurnText, protectorCommandsText, protectorsTurnText, roleDescriptionText,+    scapegoatCommandsText, scapegoatsTurnText, seerCommandsText, seersTurnText,+    standardCommandsText, standardCycleText, statusCommandsText, sunriseText, sunsetText,+    villageDrunksTurnText, villagesTurnText, werewolvesTurnText, winConditionText,+    witchCommandsText, witchsTurnText,++    -- * Ping+    playerPingedText, rolePingedText, villagePingedText, werewolvesPingedText,++    -- * Quit+    callerQuitText,++    -- * Status+    alivePlayersText, currentTurnText, deadPlayersText, gameOverText,++    -- * Unvote+    callerRescindedVoteText,++    -- * Version+    engineVersionText,++    -- * Vote+    callerVotedDevourText, callerVotedLynchText,+) where++import Control.Lens++import           Data.String.Humanise+import           Data.String.Interpolate.Extra+import           Data.Text                     (Text)+import qualified Data.Text                     as T+import           Data.Version++import Game.Werewolf+import Game.Werewolf.Message++import Werewolf.Version++callerVotedBootText :: Player -> Player -> Text+callerVotedBootText caller target = [iFile|variant/standard/command/boot/player-voted-boot.txt|]++gameCircleText :: [Player] -> Text+gameCircleText players = [iFile|variant/standard/command/circle/game-circle.txt|]++playerShotText :: Player -> Text+playerShotText player = [iFile|variant/standard/command/choose/player-shot.txt|]++gameEndedText :: Text -> Text+gameEndedText callerName = [iFile|variant/standard/command/end/game-ended.txt|]++druidsTurnText :: Text+druidsTurnText = [iFile|variant/standard/command/help/druids-turn.txt|]++gameDescriptionText :: Text+gameDescriptionText = [iFile|variant/standard/command/help/game-description.txt|]++gameRulesText :: Text+gameRulesText = [iFile|variant/standard/command/help/rules.txt|]++globalCommandsText :: Text+globalCommandsText = [iFile|variant/standard/command/help/global-commands.txt|]++helpCommandsText :: Text+helpCommandsText = [iFile|variant/standard/command/help/help-commands.txt|]++hunterCommandsText :: Text+hunterCommandsText = [iFile|variant/standard/command/help/hunter-commands.txt|]++huntersTurnText :: Text+huntersTurnText = [iFile|variant/standard/command/help/hunters-turn.txt|]++oracleCommandsText :: Text+oracleCommandsText = [iFile|variant/standard/command/help/oracle-commands.txt|]++oraclesTurnText :: Text+oraclesTurnText = [iFile|variant/standard/command/help/oracles-turn.txt|]++orphanCommandsText :: Text+orphanCommandsText = [iFile|variant/standard/command/help/orphan-commands.txt|]++orphansTurnText :: Text+orphansTurnText = [iFile|variant/standard/command/help/orphans-turn.txt|]++protectorCommandsText :: Text+protectorCommandsText = [iFile|variant/standard/command/help/protector-commands.txt|]++protectorsTurnText :: Text+protectorsTurnText = [iFile|variant/standard/command/help/protectors-turn.txt|]++roleDescriptionText :: Role -> Text+roleDescriptionText role = [iFile|variant/standard/command/help/role.txt|]++scapegoatCommandsText :: Text+scapegoatCommandsText = [iFile|variant/standard/command/help/scapegoat-commands.txt|]++scapegoatsTurnText :: Text+scapegoatsTurnText = [iFile|variant/standard/command/help/scapegoats-turn.txt|]++seerCommandsText :: Text+seerCommandsText = [iFile|variant/standard/command/help/seer-commands.txt|]++seersTurnText :: Text+seersTurnText = [iFile|variant/standard/command/help/seers-turn.txt|]++standardCommandsText :: Text+standardCommandsText = [iFile|variant/standard/command/help/standard-commands.txt|]++standardCycleText :: Text+standardCycleText = [iFile|variant/standard/command/help/standard-cycle.txt|]++statusCommandsText :: Text+statusCommandsText = [iFile|variant/standard/command/help/status-commands.txt|]++sunriseText :: Text+sunriseText = [iFile|variant/standard/command/help/sunrise.txt|]++sunsetText :: Text+sunsetText = [iFile|variant/standard/command/help/sunset.txt|]++villageDrunksTurnText :: Text+villageDrunksTurnText = [iFile|variant/standard/command/help/village-drunks-turn.txt|]++villagesTurnText :: Text+villagesTurnText = [iFile|variant/standard/command/help/villages-turn.txt|]++werewolvesTurnText :: Text+werewolvesTurnText = [iFile|variant/standard/command/help/werewolves-turn.txt|]++winConditionText :: Text+winConditionText = [iFile|variant/standard/command/help/win-condition.txt|]++witchCommandsText :: Text+witchCommandsText = [iFile|variant/standard/command/help/witch-commands.txt|]++witchsTurnText :: Text+witchsTurnText = [iFile|variant/standard/command/help/witchs-turn.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|]++werewolvesPingedText :: Text+werewolvesPingedText = [iFile|variant/standard/command/ping/werewolves-pinged.txt|]++callerQuitText :: Player -> Text+callerQuitText caller = [iFile|variant/standard/command/quit/caller-quit.txt|]++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|]++deadPlayersText :: Game -> Text+deadPlayersText game = [iFile|variant/standard/command/status/dead-players.txt|]++gameOverText :: Text+gameOverText = [iFile|variant/standard/command/status/game-over.txt|]++callerRescindedVoteText :: Player -> Text+callerRescindedVoteText caller = [iFile|variant/standard/command/unvote/player-rescinded-vote.txt|]++engineVersionText :: Text+engineVersionText = [iFile|variant/standard/command/version/engine-version.txt|]++callerVotedDevourText :: Player -> Player -> Text+callerVotedDevourText caller target = [iFile|variant/standard/command/vote/player-made-devour-vote.txt|]++callerVotedLynchText :: Player -> Player -> Text+callerVotedLynchText caller target = [iFile|variant/standard/command/vote/player-made-lynch-vote.txt|]
+ app/Game/Werewolf/Variant/Standard/Engine.hs view
@@ -0,0 +1,289 @@+{-|+Module      : Game.Werewolf.Variant.Standard.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 'Standard' variant.+-}++{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes       #-}++module Game.Werewolf.Variant.Standard.Engine (+    -- * Druid's turn+    druidsTurnText,++    -- * General+    playerBootedText, playerKilledText,++    -- * Game over+    allegianceWonText, fallenAngelWonText, playerContributedText, playerLostText, playerRolesText,+    playerWonText,++    -- * Hunter's turn+    huntersTurnPrivateText, huntersTurnPublicText,++    -- * Lynching+    jesterLynchedText, noPlayerLynchedText, playerLynchedText, scapegoatLynchedText,+    werewolfLynchedText,++    -- * New game+    beholderText, fallenAngelText, newPlayerText, playersInGameText, rolesInGameText,+    spitefulGhostText, trueVillagerText,++    -- * Oracle's turn+    oraclesTurnPrivateText, oraclesTurnPublicText,++    -- * Orphan's turn+    orphanJoinedWerewolvesGroupText, orphanJoinedWerewolvesPrivateText, orphansTurnPrivateText,+    orphansTurnPublicText,++    -- * Protector's turn+    protectorsTurnPrivateText, protectorsTurnPublicText,++    -- * Scapegoat's turn+    scapegoatsTurnText, scapegoatsTurnEndedText,++    -- * Seer's turn+    seersTurnPrivateText, seersTurnPublicText, alphaWolfSeenText, lycanSeenText,++    -- * Sunrise+    noPlayerDevouredText, playerDevouredText, playerDivinedText, playerPoisonedText, playerSeenText,+    sunriseText, playerTurnedToStoneText,++    -- * Sunset+    sunsetText,++    -- * Village Drunk's turn+    villageDrunkJoinedVillageText, villageDrunkJoinedWerewolvesGroupText,+    villageDrunkJoinedWerewolvesPrivateText, villageDrunksTurnText,++    -- * Village's turn+    villagesTurnText,++    -- * Werewolves' turn+    firstWerewolvesTurnText, werewolvesTurnPrivateText, werewolvesTurnPublicText,++    -- * Witch's turn+    healText, passText, poisonText, witchsTurnText,+) where++import Control.Arrow+import Control.Lens       hiding (isn't)+import Control.Lens.Extra++import           Data.List.Extra+import           Data.String.Humanise+import           Data.String.Interpolate.Extra+import           Data.Text                     (Text)+import qualified Data.Text                     as T++import Game.Werewolf+import Game.Werewolf.Message++druidsTurnText :: Text+druidsTurnText = [iFile|variant/standard/engine/druids-turn/start.txt|]++playerBootedText :: Player -> Text+playerBootedText player = [iFile|variant/standard/engine/general/player-booted.txt|]++playerKilledText :: Text+playerKilledText = [iFile|variant/standard/engine/general/player-killed.txt|]++allegianceWonText :: Allegiance -> Text+allegianceWonText allegiance = [iFile|variant/standard/engine/game-over/allegiance-won.txt|]++fallenAngelWonText :: Text+fallenAngelWonText = [iFile|variant/standard/engine/game-over/fallen-angel-won.txt|]++playerContributedText :: Text+playerContributedText = [iFile|variant/standard/engine/game-over/player-contributed.txt|]++playerLostText :: Text+playerLostText = [iFile|variant/standard/engine/game-over/player-lost.txt|]++playerRolesText :: Game -> Text+playerRolesText game = [iFile|variant/standard/engine/game-over/player-roles.txt|]++playerWonText :: Text+playerWonText = [iFile|variant/standard/engine/game-over/player-won.txt|]++huntersTurnPrivateText :: Text+huntersTurnPrivateText = [iFile|variant/standard/engine/hunters-turn/start-private.txt|]++huntersTurnPublicText :: Game -> Text+huntersTurnPublicText game = [iFile|variant/standard/engine/hunters-turn/start-public.txt|]+    where+        hunter = game ^?! players . hunters++jesterLynchedText :: Game -> Text+jesterLynchedText game = [iFile|variant/standard/engine/lynching/jester-lynched.txt|]+    where+        jester = game ^?! players . jesters++noPlayerLynchedText :: Text+noPlayerLynchedText = [iFile|variant/standard/engine/lynching/no-player-lynched.txt|]++playerLynchedText :: Player -> Text+playerLynchedText player = [iFile|variant/standard/engine/lynching/player-lynched.txt|]++scapegoatLynchedText :: Game -> Text+scapegoatLynchedText game = [iFile|variant/standard/engine/lynching/scapegoat-lynched.txt|]+    where+        scapegoat = game ^?! players . scapegoats++werewolfLynchedText :: Player -> Text+werewolfLynchedText werewolf = [iFile|variant/standard/engine/lynching/werewolf-lynched.txt|]++beholderText :: Game -> Text+beholderText game = [iFile|variant/standard/engine/new-game/beholder.txt|]+    where+        seer = game ^?! players . seers++fallenAngelText :: Text+fallenAngelText = [iFile|variant/standard/engine/new-game/fallen-angel.txt|]++newPlayerText :: Player -> Text+newPlayerText player = [iFile|variant/standard/engine/new-game/new-player.txt|]++playersInGameText :: Game -> Text+playersInGameText game = [iFile|variant/standard/engine/new-game/players-in-game.txt|]++rolesInGameText :: Game -> Text+rolesInGameText game = [iFile|variant/standard/engine/new-game/roles-in-game.txt|]+    where+        roles           = game ^.. players . traverse . role+        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|]++trueVillagerText :: Game -> Text+trueVillagerText game = [iFile|variant/standard/engine/new-game/true-villager.txt|]+    where+        trueVillager = game ^?! players . trueVillagers++oraclesTurnPrivateText :: Text+oraclesTurnPrivateText = [iFile|variant/standard/engine/oracles-turn/start-private.txt|]++oraclesTurnPublicText :: Text+oraclesTurnPublicText = [iFile|variant/standard/engine/oracles-turn/start-public.txt|]++orphanJoinedWerewolvesGroupText :: Game -> Text+orphanJoinedWerewolvesGroupText game = [iFile|variant/standard/engine/orphans-turn/player-joined-werewolves-group.txt|]+    where+        orphan = game ^?! players . orphans++orphanJoinedWerewolvesPrivateText :: Game -> Text+orphanJoinedWerewolvesPrivateText game = [iFile|variant/standard/engine/orphans-turn/player-joined-werewolves-private.txt|]+    where+        orphan      = game ^?! players . orphans+        werewolves  = game ^.. players . traverse . alive . filtered (is werewolf) \\ [orphan]++orphansTurnPrivateText :: Text+orphansTurnPrivateText = [iFile|variant/standard/engine/orphans-turn/start-private.txt|]++orphansTurnPublicText :: Text+orphansTurnPublicText = [iFile|variant/standard/engine/orphans-turn/start-public.txt|]++protectorsTurnPrivateText :: Text+protectorsTurnPrivateText = [iFile|variant/standard/engine/protectors-turn/start-private.txt|]++protectorsTurnPublicText :: Text+protectorsTurnPublicText = [iFile|variant/standard/engine/protectors-turn/start-public.txt|]++scapegoatsTurnText :: Game -> Text+scapegoatsTurnText game = [iFile|variant/standard/engine/scapegoats-turn/start.txt|]+    where+        scapegoat = game ^?! players . scapegoats++scapegoatsTurnEndedText :: Game -> Text+scapegoatsTurnEndedText game = [iFile|variant/standard/engine/scapegoats-turn/end.txt|]++seersTurnPrivateText :: Text+seersTurnPrivateText = [iFile|variant/standard/engine/seers-turn/start-private.txt|]++seersTurnPublicText :: Text+seersTurnPublicText = [iFile|variant/standard/engine/seers-turn/start-public.txt|]++alphaWolfSeenText :: Player -> Text+alphaWolfSeenText alphaWolf = [iFile|variant/standard/engine/sunrise/alpha-wolf-seen.txt|]++lycanSeenText :: Player -> Text+lycanSeenText lycan = [iFile|variant/standard/engine/sunrise/lycan-seen.txt|]++noPlayerDevouredText :: Text+noPlayerDevouredText = [iFile|variant/standard/engine/sunrise/no-player-devoured.txt|]++playerDevouredText :: Player -> Text+playerDevouredText player = [iFile|variant/standard/engine/sunrise/player-devoured.txt|]++playerDivinedText :: Player -> Text+playerDivinedText player = [iFile|variant/standard/engine/sunrise/player-divined.txt|]++playerPoisonedText :: Player -> Text+playerPoisonedText player = [iFile|variant/standard/engine/sunrise/player-poisoned.txt|]++playerSeenText :: Player -> Text+playerSeenText player = [iFile|variant/standard/engine/sunrise/player-seen.txt|]+    where+        article = if is loner player then "" else "the " :: Text++sunriseText :: Text+sunriseText = [iFile|variant/standard/engine/sunrise/start.txt|]++playerTurnedToStoneText :: Player -> Text+playerTurnedToStoneText player = [iFile|variant/standard/engine/sunrise/player-turned-to-stone.txt|]++sunsetText :: Text+sunsetText = [iFile|variant/standard/engine/sunset/start.txt|]++villageDrunkJoinedVillageText :: Text+villageDrunkJoinedVillageText = [iFile|variant/standard/engine/village-drunks-turn/player-joined-village.txt|]++villageDrunkJoinedWerewolvesGroupText :: Game -> Text+villageDrunkJoinedWerewolvesGroupText game = [iFile|variant/standard/engine/village-drunks-turn/player-joined-werewolves-group.txt|]+    where+        villageDrunk = game ^?! players . villageDrunks++villageDrunkJoinedWerewolvesPrivateText :: Game -> Text+villageDrunkJoinedWerewolvesPrivateText game = [iFile|variant/standard/engine/village-drunks-turn/player-joined-werewolves-private.txt|]+    where+        villageDrunk    = game ^?! players . villageDrunks+        werewolves      = game ^.. players . traverse . alive . filtered (is werewolf) \\ [villageDrunk]++villageDrunksTurnText :: Text+villageDrunksTurnText = [iFile|variant/standard/engine/village-drunks-turn/start.txt|]++villagesTurnText :: Text+villagesTurnText = [iFile|variant/standard/engine/villages-turn/start.txt|]++firstWerewolvesTurnText :: Player -> Game -> Text+firstWerewolvesTurnText player game = [iFile|variant/standard/engine/werewolves-turn/start-first-round-private.txt|]+    where+        werewolves = game ^.. players . traverse . alive . filtered (is werewolf)++werewolvesTurnPrivateText :: Text+werewolvesTurnPrivateText = [iFile|variant/standard/engine/werewolves-turn/start-private.txt|]++werewolvesTurnPublicText :: Text+werewolvesTurnPublicText = [iFile|variant/standard/engine/werewolves-turn/start-public.txt|]++healText :: Game -> Text+healText game = [iFile|variant/standard/engine/witchs-turn/heal.txt|]+    where+        victim = game ^?! votee++passText :: Text+passText = [iFile|variant/standard/engine/witchs-turn/pass.txt|]++poisonText :: Text+poisonText = [iFile|variant/standard/engine/witchs-turn/poison.txt|]++witchsTurnText :: Text+witchsTurnText = [iFile|variant/standard/engine/witchs-turn/start.txt|]
+ app/Game/Werewolf/Variant/Standard/Error.hs view
@@ -0,0 +1,115 @@+{-|+Module      : Game.Werewolf.Variant.Standard.Error+Description : Suite of error 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 error messages used throughout the werewolf game for the 'Standard' variant.+-}++{-# LANGUAGE QuasiQuotes #-}++module Game.Werewolf.Variant.Standard.Error (+    -- * Command++    -- ** Boot+    callerAlreadyVotedBootText,++    -- ** Choose+    callerCannotChooseJesterText, callerCannotChooseSelfText, noTargetText,++    -- ** General+    callerCannotDoThatText, callerCannotDoThatRightNowText, callerDeadText, noGameRunningText,+    playerDoesNotExistText, targetDeadText,++    -- ** Heal+    callerAlreadyHealedText,++    -- ** Poison+    callerAlreadyPoisonedText,++    -- ** Protect+    callerCannotProtectSamePlayerText,++    -- ** Start+    gameAlreadyRunningText, playerCountTooLowText, playerNamesNotUniqueText,+    roleCountRestrictedText, roleDoesNotExistText,++    -- ** Unvote+    callerNotVotedText,++    -- ** Vote+    callerAlreadyVotedText, callerCannotDevourAnotherWerewolfText,+) where++import Data.String.Humanise+import Data.String.Interpolate.Extra+import Data.Text                     (Text)++import Game.Werewolf++callerAlreadyVotedBootText :: Player -> Text+callerAlreadyVotedBootText target = [iFile|variant/standard/error/command/boot/caller-already-voted-boot.txt|]++callerCannotChooseJesterText :: Text+callerCannotChooseJesterText = [iFile|variant/standard/error/command/choose/caller-cannot-choose-jester.txt|]++callerCannotChooseSelfText :: Text+callerCannotChooseSelfText = [iFile|variant/standard/error/command/choose/caller-cannot-choose-self.txt|]++noTargetText :: Text+noTargetText = [iFile|variant/standard/error/command/choose/no-target.txt|]++callerCannotDoThatText :: Text+callerCannotDoThatText = [iFile|variant/standard/error/command/general/caller-cannot-do-that.txt|]++callerCannotDoThatRightNowText :: Text+callerCannotDoThatRightNowText = [iFile|variant/standard/error/command/general/caller-cannot-do-that-right-now.txt|]++callerDeadText :: Text+callerDeadText = [iFile|variant/standard/error/command/general/caller-dead.txt|]++noGameRunningText :: Text+noGameRunningText = [iFile|variant/standard/error/command/general/no-game-running.txt|]++playerDoesNotExistText :: Text -> Text+playerDoesNotExistText name = [iFile|variant/standard/error/command/general/player-does-not-exist.txt|]++targetDeadText :: Player -> Text+targetDeadText target = [iFile|variant/standard/error/command/general/target-dead.txt|]++callerAlreadyHealedText :: Text+callerAlreadyHealedText = [iFile|variant/standard/error/command/heal/caller-already-healed.txt|]++callerAlreadyPoisonedText :: Text+callerAlreadyPoisonedText = [iFile|variant/standard/error/command/poison/caller-already-poisoned.txt|]++callerCannotProtectSamePlayerText :: Text+callerCannotProtectSamePlayerText = [iFile|variant/standard/error/command/protect/caller-cannot-protect-same-player.txt|]++gameAlreadyRunningText :: Text+gameAlreadyRunningText = [iFile|variant/standard/error/command/start/game-already-running.txt|]++playerCountTooLowText :: Text+playerCountTooLowText = [iFile|variant/standard/error/command/start/player-count-too-low.txt|]++playerNamesNotUniqueText :: Text+playerNamesNotUniqueText = [iFile|variant/standard/error/command/start/player-names-not-unique.txt|]++roleCountRestrictedText :: Role -> Text+roleCountRestrictedText role = [iFile|variant/standard/error/command/start/role-count-restricted.txt|]++roleDoesNotExistText :: Text -> Text+roleDoesNotExistText roleName = [iFile|variant/standard/error/command/start/role-does-not-exist.txt|]++callerNotVotedText :: Text+callerNotVotedText = [iFile|variant/standard/error/command/unvote/caller-not-voted.txt|]++callerAlreadyVotedText :: Text+callerAlreadyVotedText = [iFile|variant/standard/error/command/vote/caller-already-voted.txt|]++callerCannotDevourAnotherWerewolfText :: Text+callerCannotDevourAnotherWerewolfText = [iFile|variant/standard/error/command/vote/caller-cannot-devour-werewolf.txt|]
app/Main.hs view
@@ -61,7 +61,7 @@     Boot options                        -> Boot.handle callerName tag options     Circle options                      -> Circle.handle callerName tag options     Divine options                      -> Divine.handle callerName tag options-    End options                         -> End.handle callerName tag options+    End                                 -> End.handle callerName tag     Heal                                -> Heal.handle callerName tag     Help options                        -> Help.handle callerName tag options     Interpret (Interpret.Options args)  -> interpret callerName tag args
app/Werewolf/Command/Boot.hs view
@@ -29,8 +29,8 @@ import Game.Werewolf.Command import Game.Werewolf.Command.Global import Game.Werewolf.Engine+import Game.Werewolf.Message.Error -import Werewolf.Messages import Werewolf.System  data Options = Options
app/Werewolf/Command/Choose.hs view
@@ -9,8 +9,6 @@ Options and handler for the choose subcommand. -} -{-# LANGUAGE OverloadedStrings #-}- module Werewolf.Command.Choose (     -- * Options     Options(..),@@ -34,9 +32,8 @@ import Game.Werewolf.Command.Orphan    as Orphan import Game.Werewolf.Command.Scapegoat as Scapegoat import Game.Werewolf.Engine-import Game.Werewolf.Messages+import Game.Werewolf.Message.Error -import Werewolf.Messages import Werewolf.System  data Options = Options
app/Werewolf/Command/Circle.hs view
@@ -27,8 +27,8 @@ import Game.Werewolf import Game.Werewolf.Command import Game.Werewolf.Command.Status+import Game.Werewolf.Message.Error -import Werewolf.Messages import Werewolf.System  data Options = Options
app/Werewolf/Command/Divine.hs view
@@ -29,8 +29,8 @@ import Game.Werewolf.Command import Game.Werewolf.Command.Oracle import Game.Werewolf.Engine+import Game.Werewolf.Message.Error -import Werewolf.Messages import Werewolf.System  data Options = Options
app/Werewolf/Command/End.hs view
@@ -1,20 +1,15 @@ {-| Module      : Werewolf.Command.End-Description : Options and handler for the end subcommand.+Description : Handler for the end subcommand.  Copyright   : (c) Henry J. Wylde, 2016 License     : BSD3 Maintainer  : public@hjwylde.com -Options and handler for the end subcommand.+Handler for the end subcommand. -} -{-# LANGUAGE OverloadedStrings #-}- module Werewolf.Command.End (-    -- * Options-    Options(..),-     -- * Handle     handle, ) where@@ -23,32 +18,23 @@ import Control.Monad.Extra import Control.Monad.IO.Class -import           Data.Text (Text)-import qualified Data.Text as T+import Data.Text (Text)  import Game.Werewolf-import Game.Werewolf.Messages+import Game.Werewolf.Message.Command+import Game.Werewolf.Message.Error -import Werewolf.Messages import Werewolf.System -data Options = Options-    { optForce :: Bool-    } deriving (Eq, Show)--handle :: MonadIO m => Text -> Text -> Options -> m ()-handle callerName tag (Options force) = do+handle :: MonadIO m => Text -> Text -> m ()+handle callerName tag = do     unlessM (doesGameExist tag) $ exitWith failure { messages = [noGameRunningMessage callerName] } -    unless force $ do-        game <- readGame tag+    game <- readGame tag -        unless (has (players . traverse . named callerName) game) $-            exitWith failure { messages = [playerCannotDoThatMessage callerName] }+    unless (has (players . traverse . named callerName) game) $+        exitWith failure { messages = [playerCannotDoThatMessage callerName] }      deleteGame tag -    exitWith success { messages = [gameEndedMessage] }-    where-        -- TODO (hjw): move this to Messages-        gameEndedMessage = publicMessage $ T.concat ["Game ended by ", callerName, "."]+    exitWith success { messages = [gameEndedMessage callerName] }
app/Werewolf/Command/Heal.hs view
@@ -9,8 +9,6 @@ Handler for the heal subcommand. -} -{-# LANGUAGE OverloadedStrings #-}- module Werewolf.Command.Heal (     -- * Handle     handle,@@ -28,8 +26,8 @@ import Game.Werewolf.Command import Game.Werewolf.Command.Witch import Game.Werewolf.Engine+import Game.Werewolf.Message.Error -import Werewolf.Messages import Werewolf.System  handle :: (MonadIO m, MonadRandom m) => Text -> Text -> m ()
app/Werewolf/Command/Help.hs view
@@ -9,8 +9,6 @@ Options and handler for the help subcommand. -} -{-# LANGUAGE OverloadedStrings #-}- module Werewolf.Command.Help (     -- * Options     Options(..), Command(..),@@ -20,16 +18,16 @@ ) where  import Control.Lens-import Control.Monad.Extra import Control.Monad.IO.Class -import           Data.Function-import           Data.List-import           Data.Text     (Text)-import qualified Data.Text     as T+import Data.Function+import Data.List+import Data.Maybe+import Data.String.Humanise+import Data.Text            (Text) -import           Game.Werewolf-import qualified Game.Werewolf.Role as Role+import Game.Werewolf+import Game.Werewolf.Message.Command  import Werewolf.System @@ -47,173 +45,69 @@  handle :: MonadIO m => Text -> Text -> Options -> m () handle callerName tag (Options (Just (Commands optAll))) = do-    mGame <- ifM (doesGameExist tag &&^ return (not optAll)) (Just <$> readGame tag) (return Nothing)+    mGame <- getGame tag optAll      exitWith success-        { messages = map (privateMessage callerName) (commandsMessages callerName mGame)+        { messages = commandsMessages callerName mGame         } handle callerName tag (Options (Just (Roles optAll))) = do-    roles <- (sortBy (compare `on` view Role.name) . nub) <$> ifM (doesGameExist tag &&^ return (not optAll))-        (toListOf (players . roles) <$> readGame tag)-        (return allRoles)+    mGame <- getGame tag optAll +    let roles' = sortBy (compare `on` humanise) . nub $ case mGame of+            Just game   -> game ^.. players . roles+            Nothing     -> allRoles+     exitWith success-        { messages = map (privateMessage callerName . roleMessage) roles+        { messages = map (roleMessage callerName) roles'         } handle callerName tag (Options (Just (Rules optAll))) = do-    mGame <- ifM (doesGameExist tag &&^ return (not optAll)) (Just <$> readGame tag) (return Nothing)+    mGame <- getGame tag optAll      exitWith success-        { messages = map (privateMessage callerName) (rulesMessages mGame)+        { messages = rulesMessages callerName mGame         } handle callerName _ (Options Nothing) = exitWith success-    { messages = map (privateMessage callerName) helpMessages+    { messages = helpMessages callerName     } -commandsMessages :: Text -> Maybe Game -> [Text]-commandsMessages callerName mGame = map (T.intercalate "\n") $ filter (/= [])-    [ [ "Global commands:"-      , "- `start ([-e|--extra-roles ROLE,...] | [-r|--random-extra-roles]) [--include-seer] PLAYER...`"-      , "- `end`"-      , "- `boot PLAYER`"-      , "- `quit`"-      , "- `version`"-      ]-    , [ "Status commands:"-      , "- `ping` ping the status of the current game publicly"-      , "- `status` get the status of the current game privately"-      , "- `circle [-a|--include-dead]` get the game circle"-      ]-    , [ "Standard commands:"-      , "- `vote PLAYER`"-      , "- `unvote`"-      ]-    , whenPlayerHasRole callerName mGame hunterRole-      [ "Hunter commands:"-      , "- `choose PLAYER`"-      ]-    , whenPlayerHasRole callerName mGame oracleRole-      [ "Oracle commands:"-      , "- `divine PLAYER`"-      ]-    , whenPlayerHasRole callerName mGame orphanRole-      [ "Orphan commands:"-      , "- `choose PLAYER`"-      ]-    , whenPlayerHasRole callerName mGame protectorRole-      [ "Protector commands:"-      , "- `protect PLAYER`"-      ]-    , whenPlayerHasRole callerName mGame scapegoatRole-      [ "Scapegoat commands:"-      , "- `choose PLAYER...`"-      ]-    , whenPlayerHasRole callerName mGame seerRole-      [ "Seer commands:"-      , "- `see PLAYER`"-      ]-    , whenPlayerHasRole callerName mGame witchRole-      [ "Witch commands:"-      , "- `heal`"-      , "- `poison PLAYER`"-      , "- `pass`"-      ]-    ]--roleMessage :: Role -> Text-roleMessage role = T.intercalate "\n"-    [ T.concat [role ^. Role.name, " (", T.pack . show $ role ^. balance, "):"]-    , role ^. description-    , role ^. rules+commandsMessages :: Text -> Maybe Game -> [Message]+commandsMessages callerName mGame =+    [ globalCommandsMessage callerName+    , statusCommandsMessage callerName+    , standardCommandsMessage callerName+    ] ++ [ hunterCommandsMessage callerName+    | isNothing mGame || has (players . hunters . named callerName) (fromJust mGame)+    ] ++ [ oracleCommandsMessage callerName+    | isNothing mGame || has (players . oracles . named callerName) (fromJust mGame)+    ] ++ [ orphanCommandsMessage callerName+    | isNothing mGame || has (players . orphans . named callerName) (fromJust mGame)+    ] ++ [ protectorCommandsMessage callerName+    | isNothing mGame || has (players . protectors . named callerName) (fromJust mGame)+    ] ++ [ scapegoatCommandsMessage callerName+    | isNothing mGame || has (players . scapegoats . named callerName) (fromJust mGame)+    ] ++ [ seerCommandsMessage callerName+    | isNothing mGame || has (players . seers . named callerName) (fromJust mGame)+    ] ++ [ witchCommandsMessage callerName+    | isNothing mGame || has (players . witches . named callerName) (fromJust mGame)     ] -rulesMessages :: Maybe Game -> [Text]-rulesMessages mGame = map (T.intercalate "\n")-    [ [ T.unwords-        [ "Each player is informed of their role (see `help roles` for a list) at the start of the"-        , "game."-        ]-      , T.unwords-        [ "Each night, the Werewolves transform and subsequently assault and devour one Villager."-        , "After feasting, their lycanthropic form subsides and they once again hide in plain"-        , "sight."-        ]-      , T.unwords-        [ "Each day, after discovering the victim, the village gathers in the town square. In a"-        , "democratic fashion they then vote for whom they believe to be a Werewolf. The votee is"-        , "immediately tied to a pyre and burned alive in an attempt to rid Fougères of all"-        , "lupines."-        ]-      ]-    , filter (/= "")-      [ "A game begins at night and follows a standard cycle."-      , "- The village falls asleep."-      , whenRoleInPlay mGame orphanRole-        "- (First round only) the Orphan wakes up and chooses a role model."-      , whenRoleInPlay mGame villageDrunkRole-        "- (Third round only) the Village Drunk sobers up and remembers their allegiance."-      , whenRoleInPlay mGame seerRole-        "- The Seer wakes up and sees someone's allegiance."-      , whenRoleInPlay mGame oracleRole-        "- The Oracle wakes up and divines someone's role."-      , whenRoleInPlay mGame protectorRole-        "- The Protector wakes up and protects someone."-      , "- The Werewolves wake up and vote to devour a victim."-      , whenRoleInPlay mGame witchRole-        "- The Witch wakes up and may heal the victim and/or poison someone."-      , "- The village wakes up and find the victim."-      , whenRoleInPlay mGame hunterRole-        "- (When the Hunter is killed) the Hunter chooses someone to shoot."-      , whenRoleInPlay mGame druidRole-        "- Ferina grunts if the Druid is next to a Werewolf."-      , "- The village votes to lynch a suspect."-      , whenRoleInPlay mGame hunterRole-        "- (When the Hunter is killed) the Hunter chooses someone to shoot."-      , whenRoleInPlay mGame scapegoatRole-        "- (When the Scapegoat is blamed) the Scapegoat chooses whom may vote on the next day."-      , T.concat-        [ "The game is over when only Villagers or Werewolves are left alive"-        , ifRoleInPlay mGame fallenAngelRole-          ", or when one of the Loners completes their own objective."-          "."-        ]-      ]+rulesMessages :: Text -> Maybe Game -> [Message]+rulesMessages callerName mGame =+    [ rulesMessage callerName+    , stagesMessage callerName mGame     ] -helpMessages :: [Text]-helpMessages = map (T.intercalate "\n")-    [ [ T.unwords-        [ "Long has the woods been home to wild creatures, both kind and cruel. Most have faces and"-        , "are known to the inhabitants of Fougères in Brittany, France; but no-one from the"-        , "village has yet to lay eyes on the merciless Werewolf."-        ]-      , T.unwords-        [ "Each night Werewolves attack the village and devour the innocent. For centuries no-one"-        , "knew how to fight this scourge, however recently a theory has taken ahold that mayhaps"-        , "the Werewolves walk among the Villagers themselves..."-        ]-      ]-    , [ "Help commands:"-      , "- `help commands [-a | --all]`"-      , "- `help roles [-a | --all]`"-      , "- `help rules [-a | --all]`"-      ]+helpMessages :: Text -> [Message]+helpMessages callerName =+    [ gameDescriptionMessage callerName+    , helpCommandsMessage callerName     ] -whenPlayerHasRole :: Monoid m => Text -> Maybe Game -> Role -> m -> m-whenPlayerHasRole _ Nothing _ m                 = m-whenPlayerHasRole callerName (Just game) role' m-    | hasn't (players . traverse . named callerName) game  = mempty-    | hasn't (role . only role') player         = mempty-    | otherwise                                 = m-     where-        player = game ^?! players . traverse . named callerName--ifRoleInPlay :: Maybe Game -> Role -> a -> a -> a-ifRoleInPlay Nothing _ true _               = true-ifRoleInPlay (Just game) role' true false-    | has (players . roles . only role') game   = true-    | otherwise                                 = false+getGame :: MonadIO m => Text -> Bool -> m (Maybe Game)+getGame _ True  = return Nothing+getGame tag _   = do+    game <- readGame tag -whenRoleInPlay :: Monoid m => Maybe Game -> Role -> m -> m-whenRoleInPlay mGame role m = ifRoleInPlay mGame role m mempty+    return $ case game ^. variant of+        NoRoleKnowledge -> Nothing+        _               -> Just game
app/Werewolf/Command/Pass.hs view
@@ -26,8 +26,8 @@ import Game.Werewolf.Command import Game.Werewolf.Command.Witch import Game.Werewolf.Engine+import Game.Werewolf.Message.Error -import Werewolf.Messages import Werewolf.System  handle :: (MonadIO m, MonadRandom m) => Text -> Text -> m ()
app/Werewolf/Command/Ping.hs view
@@ -24,8 +24,8 @@ import Game.Werewolf import Game.Werewolf.Command import Game.Werewolf.Command.Status+import Game.Werewolf.Message.Error -import Werewolf.Messages import Werewolf.System  handle :: MonadIO m => Text -> Text -> m ()
app/Werewolf/Command/Poison.hs view
@@ -29,8 +29,8 @@ import Game.Werewolf.Command import Game.Werewolf.Command.Witch import Game.Werewolf.Engine+import Game.Werewolf.Message.Error -import Werewolf.Messages import Werewolf.System  data Options = Options
app/Werewolf/Command/Protect.hs view
@@ -29,8 +29,8 @@ import Game.Werewolf.Command import Game.Werewolf.Command.Protector import Game.Werewolf.Engine+import Game.Werewolf.Message.Error -import Werewolf.Messages import Werewolf.System  data Options = Options
app/Werewolf/Command/Quit.hs view
@@ -26,8 +26,8 @@ import Game.Werewolf.Command import Game.Werewolf.Command.Global import Game.Werewolf.Engine+import Game.Werewolf.Message.Error -import Werewolf.Messages import Werewolf.System  handle :: (MonadIO m, MonadRandom m) => Text -> Text -> m ()
app/Werewolf/Command/See.hs view
@@ -29,8 +29,8 @@ import Game.Werewolf.Command import Game.Werewolf.Command.Seer import Game.Werewolf.Engine+import Game.Werewolf.Message.Error -import Werewolf.Messages import Werewolf.System  data Options = Options
app/Werewolf/Command/Start.hs view
@@ -11,7 +11,6 @@  {-# LANGUAGE FlexibleContexts      #-} {-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE OverloadedStrings     #-}  module Werewolf.Command.Start (     -- * Options@@ -22,36 +21,34 @@ ) where  import Control.Lens+import Control.Lens.Extra import Control.Monad.Except import Control.Monad.Extra import Control.Monad.Random import Control.Monad.State import Control.Monad.Writer -import           Data.List-import           Data.Text (Text)-import qualified Data.Text as T+import Data.Text (Text)  import Game.Werewolf import Game.Werewolf.Engine-import Game.Werewolf.Role   as Role+import Game.Werewolf.Message.Error  import System.Random.Shuffle -import Werewolf.Messages import Werewolf.System  data Options = Options-    { optExtraRoles  :: ExtraRoles-    , optIncludeSeer :: Bool-    , argPlayers     :: [Text]+    { optExtraRoles :: ExtraRoles+    , optVariant    :: Variant+    , argPlayers    :: [Text]     } deriving (Eq, Show)  data ExtraRoles = None | Random | Use [Text]     deriving (Eq, Show)  handle :: (MonadIO m, MonadRandom m) => Text -> Text -> Options -> m ()-handle callerName tag (Options extraRoles includeSeer playerNames) = do+handle callerName tag (Options extraRoles variant playerNames) = do     whenM (doesGameExist tag &&^ (hasn't (stage . _GameOver) <$> readGame tag)) $ exitWith failure         { messages = [gameAlreadyRunningMessage callerName]         }@@ -62,12 +59,11 @@             Random          -> randomExtraRoles $ length playerNames             Use roleNames   -> useExtraRoles callerName roleNames -        let extraRoles''    = if includeSeer then nub (seerRole:extraRoles') else extraRoles'-        let roles           = padRoles extraRoles'' (length playerNames + 1)+        let roles = padRoles extraRoles' (length playerNames + 1)          players <- createPlayers (callerName:playerNames) <$> shuffleM roles -        runWriterT $ startGame callerName players >>= execStateT checkStage+        runWriterT $ startGame callerName variant players >>= execStateT checkStage      case result of         Left errorMessages      -> exitWith failure { messages = errorMessages }@@ -83,14 +79,12 @@     take count <$> shuffleM restrictedRoles  useExtraRoles :: MonadError [Message] m => Text -> [Text] -> m [Role]-useExtraRoles callerName roleNames = forM roleNames $ \roleName -> case findByName roleName of+useExtraRoles callerName roleNames = forM roleNames $ \roleName -> case findByTag roleName of     Just role   -> return role     Nothing     -> throwError [roleDoesNotExistMessage callerName roleName] -findByName :: Text -> Maybe Role-findByName name' = restrictedRoles ^? traverse . filtered ((sanitise name' ==) . T.toLower . sanitise . view Role.name)-    where-        sanitise = T.replace " " "-"+findByTag :: Text -> Maybe Role+findByTag tag' = restrictedRoles ^? traverse . filteredBy tag tag'  padRoles :: [Role] -> Int -> [Role] padRoles roles n = roles ++ simpleVillagerRoles ++ simpleWerewolfRoles
app/Werewolf/Command/Status.hs view
@@ -24,8 +24,8 @@ import Game.Werewolf import Game.Werewolf.Command import Game.Werewolf.Command.Status+import Game.Werewolf.Message.Error -import Werewolf.Messages import Werewolf.System  handle :: MonadIO m => Text -> Text -> m ()
app/Werewolf/Command/Unvote.hs view
@@ -28,9 +28,8 @@ import Game.Werewolf.Command.Villager as Villager import Game.Werewolf.Command.Werewolf as Werewolf import Game.Werewolf.Engine-import Game.Werewolf.Messages+import Game.Werewolf.Message.Error -import Werewolf.Messages import Werewolf.System  handle :: (MonadIO m, MonadRandom m) => Text -> Text -> m ()
app/Werewolf/Command/Version.hs view
@@ -19,9 +19,7 @@ import Data.Text (Text)  import Game.Werewolf--import Werewolf.Messages-import Werewolf.Version+import Game.Werewolf.Message.Command  handle :: MonadIO m => Text -> m ()-handle callerName = exitWith success { messages = [engineVersionMessage callerName version] }+handle callerName = exitWith success { messages = [engineVersionMessage callerName] }
app/Werewolf/Command/Vote.hs view
@@ -31,9 +31,8 @@ import Game.Werewolf.Command.Villager as Villager import Game.Werewolf.Command.Werewolf as Werewolf import Game.Werewolf.Engine-import Game.Werewolf.Messages+import Game.Werewolf.Message.Error -import Werewolf.Messages import Werewolf.System  data Options = Options
− app/Werewolf/Messages.hs
@@ -1,39 +0,0 @@-{-|-Module      : Werewolf.Messages-Description : Suite of messages used when calling the binary.--Copyright   : (c) Henry J. Wylde, 2016-License     : BSD3-Maintainer  : public@hjwylde.com--Suite of messages used when calling the binary.--}--{-# LANGUAGE OverloadedStrings #-}--module Werewolf.Messages (-    -- * Binary messages-    engineVersionMessage,--    -- ** Error messages-    noGameRunningMessage, gameAlreadyRunningMessage, roleDoesNotExistMessage,-) where--import           Data.Text    (Text)-import qualified Data.Text    as T-import           Data.Version--import Game.Werewolf--engineVersionMessage :: Text -> Version -> Message-engineVersionMessage to version =-    privateMessage to $ T.unwords ["Version", T.pack $ showVersion version]--noGameRunningMessage :: Text -> Message-noGameRunningMessage to = privateMessage to "No game is running."--gameAlreadyRunningMessage :: Text -> Message-gameAlreadyRunningMessage to = privateMessage to "A game is already running."--roleDoesNotExistMessage :: Text -> Text -> Message-roleDoesNotExistMessage to name = privateMessage to $ T.unwords ["Role", name, "does not exist."]
app/Werewolf/Options.hs view
@@ -23,11 +23,15 @@ import qualified Data.Text    as T import           Data.Version (showVersion) +import Game.Werewolf (Variant (..))++import Options.Applicative+import Options.Applicative.Types+ import qualified Werewolf.Command.Boot      as Boot import qualified Werewolf.Command.Choose    as Choose import qualified Werewolf.Command.Circle    as Circle import qualified Werewolf.Command.Divine    as Divine-import qualified Werewolf.Command.End       as End import qualified Werewolf.Command.Help      as Help import qualified Werewolf.Command.Interpret as Interpret import qualified Werewolf.Command.Poison    as Poison@@ -37,8 +41,6 @@ import qualified Werewolf.Command.Vote      as Vote import qualified Werewolf.Version           as This -import Options.Applicative- data Options = Options     { optCaller  :: Text     , optTag     :: Text@@ -50,7 +52,7 @@     | Choose Choose.Options     | Circle Circle.Options     | Divine Divine.Options-    | End End.Options+    | End     | Heal     | Help Help.Options     | Interpret Interpret.Options@@ -134,11 +136,7 @@ divine = Divine . Divine.Options <$> playerArgument  end :: Parser Command-end = End . End.Options-    <$> switch (mconcat-        [ long "force", short 'f'-        , help "Force the game to end"-        ])+end = pure End  heal :: Parser Command heal = pure Heal@@ -178,12 +176,18 @@ start :: Parser Command start = fmap Start $ Start.Options     <$> (extraRolesOption <|> randomExtraRolesOption)-    <*> switch (mconcat-        [ long "include-seer"-        , help "Always include the Seer"+    <*> variantOption (mconcat [+        long "variant", short 'v', metavar "VARIANT",+        value Standard, showDefaultWith $ const "standard",+        help "Specify the game variant"         ])     <*> some (T.pack <$> strArgument (metavar "PLAYER..."))     where+        variantOption = option $ readerAsk >>= \opt -> case opt of+            "standard"          -> return Standard+            "no-role-knowledge" -> return NoRoleKnowledge+            _                   -> readerError $ "unrecognised variant `" ++ opt ++ "'"+         extraRolesOption = fmap (Start.Use . filter (/= T.empty) . T.splitOn "," . T.pack) (strOption $ mconcat             [ long "extra-roles", short 'e', metavar "ROLE,..."             , value []
app/Werewolf/System.hs view
@@ -11,7 +11,6 @@  {-# LANGUAGE FlexibleContexts      #-} {-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE OverloadedStrings     #-}  module Werewolf.System (     -- * Game@@ -24,7 +23,6 @@ ) where  import Control.Lens         hiding (cons)-import Control.Lens.Extra import Control.Monad.Except import Control.Monad.Writer @@ -33,24 +31,23 @@ import qualified Data.Text as T  import Game.Werewolf-import Game.Werewolf.Messages-import Game.Werewolf.Role     as Role+import Game.Werewolf.Message.Engine+import Game.Werewolf.Message.Error  import Prelude hiding (round)  import System.Directory import System.FilePath -startGame :: (MonadError [Message] m, MonadWriter [Message] m) => Text -> [Player] -> m Game-startGame callerName players = do-    -- TODO (hjw): move the messages to Messages-    when (playerNames /= nub playerNames)   $ throwError [privateMessage callerName "Player names must be unique."]-    when (length players < 7)               $ throwError [privateMessage callerName "Must have at least 7 players."]-    forM_ restrictedRoles $ \role' ->-        when (length (players ^.. traverse . filteredBy role role') > 1) $-            throwError [privateMessage callerName $ T.concat ["Cannot have more than 1 ", role' ^. Role.name, "."]]+startGame :: (MonadError [Message] 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]+    forM_ restrictedRoles $ \role ->+        when (length (players ^.. roles . only role) > 1) $+            throwError [roleCountRestrictedMessage callerName role] -    let game = newGame players+    let game = newGame variant players      tell $ newGameMessages game 
src/Data/String/Humanise.hs view
@@ -9,12 +9,23 @@ Humanise type class for displaying data structures to a human. -} +{-# LANGUAGE OverloadedStrings #-}+ module Data.String.Humanise (     -- * Humanise     Humanise(..), ) where -import Data.String+import           Data.Text (Text)+import qualified Data.Text as T  class Humanise a where-    humanise :: IsString b => a -> b+    humanise :: a -> Text++instance Humanise Text where+    humanise = id++instance Humanise a => Humanise [a] where+    humanise []     = ""+    humanise [word] = humanise word+    humanise words  = T.unwords [T.intercalate ", " (map humanise $ init words), "and", humanise $ last words]
+ src/Data/String/Interpolate/Extra.hs view
@@ -0,0 +1,22 @@+{-|+Module      : Data.String.Interpolate.Extra+Description : Extra utility functions for working with the interpolate Quasi Quoter.++Copyright   : (c) Henry J. Wylde, 2016+License     : BSD3+Maintainer  : public@hjwylde.com++Extra utility functions for working with the interpolate Quasi Quoter.+-}++module Data.String.Interpolate.Extra (+    -- * Quasi Quoters+    iFile,+) where++import Data.String.Interpolate.IsString++import Language.Haskell.TH.Quote++iFile :: QuasiQuoter+iFile = quoteFile i
src/Game/Werewolf/Game.hs view
@@ -10,18 +10,22 @@ structure and any fields required to keep track of the current state. -} -{-# LANGUAGE Rank2Types      #-}-{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE Rank2Types        #-}+{-# LANGUAGE TemplateHaskell   #-}  module Game.Werewolf.Game (     -- * Game     Game,-    stage, round, players, boots, allowedVoters, divine, fallenAngelLynched, healUsed,+    variant, stage, round, players, boots, allowedVoters, divine, fallenAngelLynched, healUsed,     hunterRetaliated, jesterRevealed, passed, poison, poisonUsed, priorProtect, protect, roleModel,     scapegoatBlamed, see, votes, +    Variant(..),+    _Standard, _NoRoleKnowledge,+     Stage(..),-    _FerinasGrunt, _GameOver, _HuntersTurn1, _HuntersTurn2, _Lynching, _OraclesTurn, _OrphansTurn,+    _DruidsTurn, _GameOver, _HuntersTurn1, _HuntersTurn2, _Lynching, _OraclesTurn, _OrphansTurn,     _ProtectorsTurn, _ScapegoatsTurn, _SeersTurn, _Sunrise, _Sunset, _VillageDrunksTurn,     _VillagesTurn, _WerewolvesTurn, _WitchsTurn, @@ -50,7 +54,6 @@ import           Data.Map             (Map) import qualified Data.Map             as Map import           Data.Maybe-import           Data.String import           Data.String.Humanise import           Data.Text            (Text) @@ -68,7 +71,8 @@ --   Some of the additional fields are reset each round (e.g., the Seer's 'see') while others are --   kept around for the whole game (e.g., the Orphan's 'roleModel'). data Game = Game-    { _stage              :: Stage+    { _variant            :: Variant+    , _stage              :: Stage     , _round              :: Int     , _players            :: [Player]     , _boots              :: Map Text [Text]@@ -89,37 +93,46 @@     , _votes              :: Map Text Text    -- ^ Villagers and Werewolves     } deriving (Eq, Read, Show) +data Variant = Standard | NoRoleKnowledge+    deriving (Eq, Read, Show)++instance Humanise Variant where+    humanise Standard           = "standard"+    humanise NoRoleKnowledge    = "no role knowledge"+ -- | 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 --   not always be available (curse that retched Scapegoat). -- --   Once the game reaches a turn stage, it requires a /command/ to help push it past. Often only --   certain roles and commands may be performed at any given stage.-data Stage  = FerinasGrunt | GameOver | HuntersTurn1 | HuntersTurn2 | Lynching | OraclesTurn+data Stage  = DruidsTurn | GameOver | HuntersTurn1 | HuntersTurn2 | Lynching | OraclesTurn             | OrphansTurn | ProtectorsTurn | ScapegoatsTurn | SeersTurn | Sunrise | Sunset             | VillageDrunksTurn | VillagesTurn | WerewolvesTurn | WitchsTurn     deriving (Eq, Read, Show)  instance Humanise Stage where-    humanise FerinasGrunt       = fromString "Ferina's Grunt"-    humanise GameOver           = fromString "Game over"-    humanise HuntersTurn1       = fromString "Hunter's turn"-    humanise HuntersTurn2       = fromString "Hunter's turn"-    humanise Lynching           = fromString "Lynching"-    humanise OraclesTurn        = fromString "Oracle's turn"-    humanise OrphansTurn        = fromString "Orphan's turn"-    humanise ProtectorsTurn     = fromString "Protector's turn"-    humanise ScapegoatsTurn     = fromString "Scapegoat's turn"-    humanise SeersTurn          = fromString "Seer's turn"-    humanise Sunrise            = fromString "Sunrise"-    humanise Sunset             = fromString "Sunset"-    humanise VillageDrunksTurn  = fromString "Village Drunk's turn"-    humanise VillagesTurn       = fromString "village's turn"-    humanise WerewolvesTurn     = fromString "Werewolves' turn"-    humanise WitchsTurn         = fromString "Witch's turn"+    humanise DruidsTurn         = "Druid's turn"+    humanise GameOver           = "Game over"+    humanise HuntersTurn1       = "Hunter's turn"+    humanise HuntersTurn2       = "Hunter's turn"+    humanise Lynching           = "Lynching"+    humanise OraclesTurn        = "Oracle's turn"+    humanise OrphansTurn        = "Orphan's turn"+    humanise ProtectorsTurn     = "Protector's turn"+    humanise ScapegoatsTurn     = "Scapegoat's turn"+    humanise SeersTurn          = "Seer's turn"+    humanise Sunrise            = "Sunrise"+    humanise Sunset             = "Sunset"+    humanise VillageDrunksTurn  = "Village Drunk's turn"+    humanise VillagesTurn       = "village's turn"+    humanise WerewolvesTurn     = "Werewolves' turn"+    humanise WitchsTurn         = "Witch's turn"  makeLenses ''Game +makePrisms ''Variant+ makePrisms ''Stage  -- | All of the 'Stage's in the order that they should occur.@@ -135,7 +148,7 @@     , WitchsTurn     , Sunrise     , HuntersTurn2-    , FerinasGrunt+    , DruidsTurn     , VillagesTurn     , Lynching     , HuntersTurn1@@ -153,7 +166,7 @@ --   One of the more complex checks here is for the 'VillagesTurn'. If the Fallen Angel is in play, --   then the 'VillagesTurn' is available on the first day rather than only after the first night. stageAvailable :: Game -> Stage -> Bool-stageAvailable game FerinasGrunt        = has (players . druids . alive) game+stageAvailable game DruidsTurn          = has (players . druids . alive) game stageAvailable _ GameOver               = False stageAvailable game HuntersTurn1        =     has (players . hunters . dead) game@@ -182,9 +195,10 @@  -- | Creates a new 'Game' with the given players. No validations are performed here, those are left --   to the binary.-newGame :: [Player] -> Game-newGame players = Game-    { _stage                = head stageCycle+newGame :: Variant -> [Player] -> Game+newGame variant players = Game+    { _variant              = variant+    , _stage                = head stageCycle     , _round                = 0     , _players              = players     , _boots                = Map.empty
src/Game/Werewolf/Player.hs view
@@ -27,7 +27,7 @@     alphaWolf, beholder, crookedSenator, druid, fallenAngel, hunter, jester, lycan, medusa, oracle,     orphan, protector, scapegoat, seer, simpleVillager, simpleWerewolf, spitefulGhost, trueVillager,     villageDrunk, witch,-    villager, werewolf,+    loner, villager, werewolf,      -- | The following traversals are provided just as a bit of sugar to avoid continually writing     --   @'traverse' .@.@@ -38,7 +38,7 @@     alphaWolves, beholders, crookedSenators, druids, fallenAngels, hunters, jesters, lycans,     medusas, oracles, orphans, protectors, scapegoats, seers, simpleVillagers, simpleWerewolves,     spitefulGhosts, trueVillagers, villageDrunks, witches,-    villagers, werewolves,+    loners, villagers, werewolves,     alive, dead, ) where @@ -46,7 +46,6 @@ import Control.Lens.Extra  import Data.Function-import Data.String import Data.String.Humanise import Data.Text            as T @@ -72,7 +71,7 @@     (==) = (==) `on` view name  instance Humanise Player where-    humanise player = fromString . T.unpack $ player ^. name+    humanise = view name  makePrisms ''State @@ -242,6 +241,14 @@ witch :: Traversal' Player () witch = role . only witchRole +-- | The traversal of 'Player's aligned with 'NoOne'.+--+-- @+-- 'loner' = 'role' . 'allegiance' . '_NoOne'+-- @+loner :: Traversal' Player ()+loner = role . allegiance . _NoOne+ -- | The traversal of 'Player's aligned with the 'Villagers'. -- -- @@@ -449,6 +456,14 @@ -- @ witches :: Traversable t => Traversal' (t Player) Player witches = traverse . filtered (is witch)++-- | This 'Traversal' provides the traversal of 'loner' 'Player's.+--+-- @+-- 'loners' = 'traverse' . 'filtered' . ('is' 'loner')+-- @+loners :: Traversable t => Traversal' (t Player) Player+loners = traverse . filtered (is loner)  -- | This 'Traversal' provides the traversal of 'villager' 'Player's. --
src/Game/Werewolf/Role.hs view
@@ -15,13 +15,13 @@ -}  {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE Rank2Types        #-}+{-# LANGUAGE QuasiQuotes       #-} {-# LANGUAGE TemplateHaskell   #-}  module Game.Werewolf.Role (     -- * Role     Role,-    name, allegiance, balance, description, rules,+    tag, name, allegiance, balance, description, rules,      Allegiance(..),     _NoOne, _Villagers, _Werewolves,@@ -60,12 +60,11 @@  import Control.Lens -import           Data.Function-import           Data.List-import           Data.String-import           Data.String.Humanise-import           Data.Text            (Text)-import qualified Data.Text            as T+import Data.Function+import Data.List+import Data.String.Humanise+import Data.String.Interpolate.Extra+import Data.Text                     as T  -- | Role definitions require only a few pieces of information. --@@ -73,10 +72,11 @@ --   Werewolf has a balance of -4 while the Seer has a balance of 2. A balance of 0 means it favours --   neither allegiance. -----   N.B., role equality is defined on just the 'name' as a role's 'allegiance' may change---   throughout the game.+--   N.B., role equality is defined on just the 'tag' as a role's 'allegiance' may change throughout+--   the game. data Role = Role-    { _name        :: Text+    { _tag         :: Text+    , _name        :: Text     , _allegiance  :: Allegiance     , _balance     :: Int     , _description :: Text@@ -89,17 +89,17 @@     deriving (Eq, Read, Show)  instance Humanise Allegiance where-    humanise NoOne      = fromString "no-one"-    humanise Villagers  = fromString "Villagers"-    humanise Werewolves = fromString "Werewolves"+    humanise NoOne      = "no-one"+    humanise Villagers  = "Villagers"+    humanise Werewolves = "Werewolves"  makeLenses ''Role  instance Eq Role where-    (==) = (==) `on` view name+    (==) = (==) `on` view tag  instance Humanise Role where-    humanise role = fromString . T.unpack $ role ^. name+    humanise = view name  makePrisms ''Allegiance @@ -148,23 +148,12 @@ --   Orphan becomes a Werewolf. orphanRole :: Role orphanRole = Role-    { _name         = "Orphan"+    { _tag          = "orphan"+    , _name         = T.strip [iFile|variant/standard/role/orphan/name.txt|]     , _allegiance   = Villagers     , _balance      = -1-    , _description  = T.unwords-        [ "Abandoned by their parents as a child, with no-one wanting to look after another mouth"-        , "to feed, the Orphan was left to fend for themself. No-one looked twice at the Orphan and"-        , "even fewer showed kindness towards the lonely child. One day however, one townsperson"-        , "changed all this. He offered the Orphan food, water and a roof over their head. Grateful"-        , "for his chairty and affection, the Orphan made him their role model. Pray that no ill"-        , "should befall their role model, for they are the only one conforming the Orphan as a"-        , "Villager."-        ]-    , _rules        = T.unwords-        [ "On the first night, the Orphan chooses a player to become their role model. So long as"-        , "the role model is alive, the Orphan is a Villager. If however the role model is"-        , "eliminated, then the Orphan becomes a Werewolf."-        ]+    , _description  = T.strip [iFile|variant/standard/role/orphan/description.txt|]+    , _rules        = T.strip [iFile|variant/standard/role/orphan/rules.txt|]     }  -- | /Hah, maybe not as liked as the Jester, but the Drunk sure does their fair share of stupid/@@ -179,23 +168,12 @@ --   Villagers or Werewolves. villageDrunkRole :: Role villageDrunkRole = Role-    { _name         = "Village Drunk"+    { _tag          = "village-drunk"+    , _name         = T.strip [iFile|variant/standard/role/village-drunk/name.txt|]     , _allegiance   = Villagers     , _balance      = -1-    , _description  = T.unwords-        [ "Hah, maybe not as liked as the Jester, but the Drunk sure does their fair share of"-        , "stupid things in the night! No-one knows if they even actually make it home; sometimes"-        , "people see them sleeping outside the Blacksmith's home, others say they see them"-        , "wandering towards the woods. It's pointless quizzing the Village Drunk in the morning"-        , "about their doings; they can never remember what they did!"-        ]-    , _rules        = T.intercalate "\n"-        [ "The Village Drunk is initially aligned with the Villagers."-        , T.unwords-            [ "On the third night the Village Drunk sobers up and is randomly assigned a new"-            , "alignment, either Villagers or Werewolves."-            ]-        ]+    , _description  = T.strip [iFile|variant/standard/role/village-drunk/description.txt|]+    , _rules        = T.strip [iFile|variant/standard/role/village-drunk/rules.txt|]     }  -- | /Long ago during the War in Heaven, angels fell from the sky as one by one those that followed/@@ -208,20 +186,12 @@ --   game. fallenAngelRole :: Role fallenAngelRole = Role-    { _name         = "Fallen Angel"+    { _tag          = "fallen-angel"+    , _name         = T.strip [iFile|variant/standard/role/fallen-angel/name.txt|]     , _allegiance   = NoOne     , _balance      = 0-    , _description  = T.unwords-        [ "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 angels left on Earth. Nothing is worse punishment for them, the"-        , "Fallen Angel yearns for death to once again be free!"-        ]-    , _rules        = T.unwords-        [ "The Fallen Angel wins if they manage to get lynched by the Villagers before the end of"-        , "the game."-        ]+    , _description  = T.strip [iFile|variant/standard/role/fallen-angel/description.txt|]+    , _rules        = T.strip [iFile|variant/standard/role/fallen-angel/rules.txt|]     }  -- | /In this time of turmoil, it would seem unlikely for the Villagers of Fougères to unanimously/@@ -233,20 +203,12 @@ --   may haunt the village as they wish. spitefulGhostRole :: Role spitefulGhostRole = Role-    { _name         = "Spiteful Ghost"+    { _tag          = "spiteful-ghost"+    , _name         = T.strip [iFile|variant/standard/role/spiteful-ghost/name.txt|]     , _allegiance   = NoOne     , _balance      = 0-    , _description  = T.unwords-        [ "In this time of turmoil, it would seem unlikely for the Villagers of Fougères to"-        , "unanimously agree on anything. Yet this is not so, for they all agree the village is"-        , "haunted by a ghost. The vindictive Spiteful Ghost never moved on, rather they remain"-        , "with the sole purpose of haunting the village and ensuring that the Villagers never"-        , "forget what they have done."-        ]-    , _rules        = T.unwords-        [ "The Spiteful ghost is dead and cannot win, however they know the game's role allocations"-        , "and may haunt the village as they wish."-        ]+    , _description  = T.strip [iFile|variant/standard/role/spiteful-ghost/description.txt|]+    , _rules        = T.strip [iFile|variant/standard/role/spiteful-ghost/rules.txt|]     }  -- | /Awareness comes easy to the Beholder. They listen to their senses and trust their hunches./@@ -257,16 +219,12 @@ --   At the start of the game the Beholder is informed the Seer's identity. beholderRole :: Role beholderRole = Role-    { _name         = "Beholder"+    { _tag          = "beholder"+    , _name         = T.strip [iFile|variant/standard/role/beholder/name.txt|]     , _allegiance   = Villagers     , _balance      = 1-    , _description  = T.unwords-        [ "Awareness comes easy to the Beholder. They listen to their senses and trust their"-        , "hunches. Over the years the Beholder has grown to know a certain few of the village just"-        , "by paying attention. Little cues here and there, the way someone talks, the way they"-        , "move - it all gives clues as to their true nature and role."-        ]-    , _rules        = "At the start of the game the Beholder is informed the Seer's identity."+    , _description  = T.strip [iFile|variant/standard/role/beholder/description.txt|]+    , _rules        = T.strip [iFile|variant/standard/role/beholder/rules.txt|]     }  -- | /Never trust a politician. Nor a Crooked Senator for that matter. The Crooked Senator may seem/@@ -277,16 +235,12 @@ --   The Crooked Senator looks at the village votes as they come in. crookedSenatorRole :: Role crookedSenatorRole = Role-    { _name         = "Crooked Senator"+    { _tag          = "crooked-senator"+    , _name         = T.strip [iFile|variant/standard/role/crooked-senator/name.txt|]     , _allegiance   = Villagers-    , _balance      = 1-    , _description  = T.unwords-        [ "Never trust a politician. Nor a Crooked Senator for that matter. The Crooked Senator may"-        , "seem like he has the village's best interests at heart, but let's be honest, when put in"-        , "a tough situation he looks after no-one but himself. Even when safe, the Crooked Senator"-        , "may decide to toy with the Villagers' emotions and try pit them against one another."-        ]-    , _rules        = "The Crooked Senator looks at the village votes as they come in."+    , _balance      = 2+    , _description  = T.strip [iFile|variant/standard/role/crooked-senator/description.txt|]+    , _rules        = T.strip [iFile|variant/standard/role/crooked-senator/rules.txt|]     }  -- | /How honoured we are to be in the presence of such a noble leader. The return of the Druid/@@ -299,21 +253,12 @@ --   next to a Werewolf in the player `circle` then Ferina will grunt in warning. druidRole :: Role druidRole = Role-    { _name         = "Druid"+    { _tag          = "druid"+    , _name         = T.strip [iFile|variant/standard/role/druid/name.txt|]     , _allegiance   = Villagers     , _balance      = 3-    , _description  = T.unwords-        [ "How honoured we are to be in the presence of such a noble leader. The return of the"-        , "Druid marks an exceptional time in Fougères's history! Friend of the woodland creatures,"-        , "practiced philosopher and now, with the help of Ferina their companion, a bane to the"-        , "Werewolves themselves! My does she have a nose on her, strong enough to sniff out"-        , "lycanthropes in close proximity! Listen for her grunt and heed her warning for she will"-        , "not let you down."-        ]-    , _rules        = T.unwords-        [ "Each morning when Ferina wakes from her slumber she will be alert and cautious. If the"-        , "Druid is next to a Werewolf in the player `circle` then Ferina will grunt in warning."-        ]+    , _description  = T.strip [iFile|variant/standard/role/druid/description.txt|]+    , _rules        = T.strip [iFile|variant/standard/role/druid/rules.txt|]     }  -- | /A skilled marksman with quick reflexes. In the unfortunate situation that they are jumped and/@@ -324,18 +269,12 @@ --   immediately. hunterRole :: Role hunterRole = Role-    { _name         = "Hunter"+    { _tag          = "hunter"+    , _name         = T.strip [iFile|variant/standard/role/hunter/name.txt|]     , _allegiance   = Villagers     , _balance      = 2-    , _description  = T.unwords-        [ "A skilled marksman with quick reflexes. In the unfortunate situation that they are"-        , "jumped and killed unjustly, they let off a shot at their attacker, killing them"-        , "instantly. The Hunter never misses."-        ]-    , _rules        = T.unwords-        [ "If the Hunter is killed they choose one player, believed to be an attacker, to kill"-        , "immediately."-        ]+    , _description  = T.strip [iFile|variant/standard/role/hunter/description.txt|]+    , _rules        = T.strip [iFile|variant/standard/role/hunter/rules.txt|]     }  -- | /Every village needs a Jester; they're so stupid, but provide so much entertainment! The/@@ -348,21 +287,12 @@ --   The Jester continues to play but may no longer vote as no-one can take them seriously. jesterRole :: Role jesterRole = Role-    { _name         = "Jester"+    { _tag          = "jester"+    , _name         = T.strip [iFile|variant/standard/role/jester/name.txt|]     , _allegiance   = Villagers     , _balance      = 0-    , _description  = T.unwords-        [ "Every village needs a Jester; they're so stupid, but provide so much entertainment! The"-        , "Jester may not have any special abilities, but at least no-one in the village wants to"-        , "hurt them."-        ]-    , _rules        = T.intercalate "\n"-        [ T.unwords-            [ "If the village votes to lynch the Jester, their identity is revealed. The village"-            , "realise there's no point in burning them and so they are set free."-            ]-        , "The Jester continues to play but may no longer vote as no-one can take them seriously."-        ]+    , _description  = T.strip [iFile|variant/standard/role/jester/description.txt|]+    , _rules        = T.strip [iFile|variant/standard/role/jester/rules.txt|]     }  -- | /Traditionally a Werewolf once transformed loses all memories and personality. Over years of/@@ -374,19 +304,12 @@ --   a Werewolf. lycanRole :: Role lycanRole = Role-    { _name         = "Lycan"+    { _tag          = "lycan"+    , _name         = T.strip [iFile|variant/standard/role/lycan/name.txt|]     , _allegiance   = Villagers     , _balance      = 0-    , _description  = T.unwords-        [ "Traditionally a Werewolf once transformed loses all memories and personality. Over years"-        , "of transforming, the Lycan has slowly evolved and learnt how to retain themself. Night"-        , "after night of devouring with the other Werewolves took its toll. The screams alone were"-        , "enough to turn the Lycan and make them question their true nature."-        ]-    , _rules        = T.unwords-        [ "The Lycan is aligned with the Villagers, but appears to nature-seeing roles (e.g., the"-        , "Seer) as a Werewolf."-        ]+    , _description  = T.strip [iFile|variant/standard/role/lycan/description.txt|]+    , _rules        = T.strip [iFile|variant/standard/role/lycan/rules.txt|]     }  -- | /A beautiful flirt, the Medusa is aligned with the Villagers but harbours a terrifying secret./@@ -400,22 +323,12 @@ --   instantly killing the lupine predator. medusaRole :: Role medusaRole = Role-    { _name         = "Medusa"+    { _tag          = "medusa"+    , _name         = T.strip [iFile|variant/standard/role/medusa/name.txt|]     , _allegiance   = Villagers     , _balance      = 3-    , _description  = T.unwords-        [ "A beautiful flirt, the Medusa is aligned with the Villagers but harbours a terrifying"-        , "secret. During the day they are well known in the village of Fougères for their stunning"-        , "appearance which captures the eye and love of all the townsfolk. However when their"-        , "secret takes ahold at sundown, their true self is revealed. Any who gaze upon her true"-        , "form would see live snakes for hair and the few that further look into her eyes are"-        , "turned to stone."-        ]-    , _rules        = T.unwords-        [ "If Medusa attracts the attention of a Werewolf during the night and is devoured, the"-        , "first Werewolf to their left in the player `circle` will catch their gaze and turn to"-        , "stone, instantly killing the lupine predator."-        ]+    , _description  = T.strip [iFile|variant/standard/role/medusa/description.txt|]+    , _rules        = T.strip [iFile|variant/standard/role/medusa/rules.txt|]     }  -- | /Originally rejected by the townsfolk, the Oracle's prophetic divinations has earned trust/@@ -426,19 +339,12 @@ --   the following morning. This wisdom is for the Oracle to use to ensure the future of Fougères. oracleRole :: Role oracleRole = Role-    { _name         = "Oracle"+    { _tag          = "oracle"+    , _name         = T.strip [iFile|variant/standard/role/oracle/name.txt|]     , _allegiance   = Villagers     , _balance      = 2-    , _description  = T.unwords-        [ "Originally rejected by the townsfolk, the Oracle's prophetic divinations has earned"-        , "trust within the village. With constant precognition - and concern for the future - the"-        , "Oracle knows the village will only live if they work together."-        ]-    , _rules        = T.unwords-        [ "Each night the Oracle chooses a player to divine. They are then informed of the player's"-        , "role the following morning. This wisdom is for the Oracle to use to ensure the future of"-        , "Fougères."-        ]+    , _description  = T.strip [iFile|variant/standard/role/oracle/description.txt|]+    , _rules        = T.strip [iFile|variant/standard/role/oracle/rules.txt|]     }  -- | /The Protector is one of the few pure of heart and altruistic Villagers; they are forever/@@ -451,21 +357,12 @@ --   The Protector may not protect the same player two nights in a row. protectorRole :: Role protectorRole = Role-    { _name         = "Protector"+    { _tag          = "protector"+    , _name         = T.strip [iFile|variant/standard/role/protector/name.txt|]     , _allegiance   = Villagers     , _balance      = 2-    , _description  = T.unwords-        [ "The Protector is one of the few pure of heart and altruistic Villagers; they are forever"-        , "putting others needs above their own. Each night they fight against the Werewolves with"-        , "naught but a sword and shield, potentially saving an innocents life."-        ]-    , _rules        = T.intercalate "\n"-        [ T.unwords-            [ "Each night the Protector chooses a player deemed worthy of their protection. That"-            , "player is safe for that night (and only that night) against the Werewolves."-            ]-        , "The Protector may not protect the same player two nights in a row."-        ]+    , _description  = T.strip [iFile|variant/standard/role/protector/description.txt|]+    , _rules        = T.strip [iFile|variant/standard/role/protector/rules.txt|]     }  -- | /Werewolves don't just spring up out of the ground! That's where dwarves come from. Clearly/@@ -478,25 +375,12 @@ --   to vote or not on the next day. scapegoatRole :: Role scapegoatRole = Role-    { _name         = "Scapegoat"+    { _tag          = "scapegoat"+    , _name         = T.strip [iFile|variant/standard/role/scapegoat/name.txt|]     , _allegiance   = Villagers     , _balance      = 0-    , _description  = T.unwords-        [ "Werewolves don't just spring up out of the ground! That's where dwarves come from."-        , "Clearly someone is to blame for this affliction to Fougères. Unluckily for the"-        , "Scapegoat, since no-one actually knows who brought them here, the blame is always laid"-        , "upon them!"-        ]-    , _rules        = T.intercalate "\n"-        [ T.unwords-            [ "If the village's vote ends in a tie, it's the Scapegoat who is eliminated instead of"-            , "no-one."-            ]-        , T.unwords-            [ "In this event, the Scapegoat has one last task to complete: they must choose whom is"-            , "permitted to vote or not on the next day."-            ]-        ]+    , _description  = T.strip [iFile|variant/standard/role/scapegoat/description.txt|]+    , _rules        = T.strip [iFile|variant/standard/role/scapegoat/rules.txt|]     }  -- | /The Seer has the ability to see into fellow townsfolk and determine their true nature. This/@@ -506,19 +390,12 @@ --   Each night the Seer sees the allegiance of one player of their choice. seerRole :: Role seerRole = Role-    { _name         = "Seer"+    { _tag          = "seer"+    , _name         = T.strip [iFile|variant/standard/role/seer/name.txt|]     , _allegiance   = Villagers     , _balance      = 2-    , _description  = T.unwords-        [ "The Seer has the ability to see into fellow townsfolk and determine their true nature."-        , "This ability to see is not given out lightly, for certain it is a gift! The visions will"-        , "always be true, but only for the present as not even the Seer knows what the future"-        , "holds."-        ]-    , _rules        = T.unwords-        [ "Each night the Seer chooses a player to see. They are then informed of the player's"-        , "allegiance the following morning."-        ]+    , _description  = T.strip [iFile|variant/standard/role/seer/description.txt|]+    , _rules        = T.strip [iFile|variant/standard/role/seer/rules.txt|]     }  -- | /A simple, ordinary townsperson in every way. Some may be cobblers, others bakers or even/@@ -529,18 +406,12 @@ --   them is not who they say they are. simpleVillagerRole :: Role simpleVillagerRole = Role-    { _name         = "Simple Villager"+    { _tag          = "simple-villager"+    , _name         = T.strip [iFile|variant/standard/role/simple-villager/name.txt|]     , _allegiance   = Villagers     , _balance      = 1-    , _description  = T.unwords-        [ "A simple, ordinary townsperson in every way. Some may be cobblers, others bakers or even"-        , "nobles. No matter their differences though, the plight of Werewolves in Fougères unites"-        , "all the Villagers in this unfortunate time."-        ]-    , _rules        = T.unwords-        [ "The Simple Villager has no special abilities, they must use their guile to determine"-        , "whom among them is not who they say they are."-        ]+    , _description  = T.strip [iFile|variant/standard/role/simple-villager/description.txt|]+    , _rules        = T.strip [iFile|variant/standard/role/simple-villager/rules.txt|]     }  -- | /The True Villager has a heart and soul as clear as day! Their allegiance and devotion to the/@@ -550,15 +421,12 @@ --   At the start of the game the True Villager's identity is revealed. trueVillagerRole :: Role trueVillagerRole = Role-    { _name         = "True Villager"+    { _tag          = "true-villager"+    , _name         = T.strip [iFile|variant/standard/role/true-villager/name.txt|]     , _allegiance   = Villagers     , _balance      = 2-    , _description  = T.unwords-        [ "The True Villager has a heart and soul as clear as day! Their allegiance and devotion to"-        , "the village are beyond reproach. If there is one person whom you should confide in,"-        , "listen to and trust, it is the True Villager."-        ]-    , _rules        = "At the start of the game the True Villager's identity is revealed."+    , _description  = T.strip [iFile|variant/standard/role/true-villager/description.txt|]+    , _rules        = T.strip [iFile|variant/standard/role/true-villager/rules.txt|]     }  -- | /Somehow forgotten with the coming of the Werewolves, the Witch has a chance to prove themself/@@ -570,21 +438,12 @@ --   There is no restriction on using both potions in one night or on healing themself. witchRole :: Role witchRole = Role-    { _name         = "Witch"+    { _tag          = "witch"+    , _name         = T.strip [iFile|variant/standard/role/witch/name.txt|]     , _allegiance   = Villagers     , _balance      = 3-    , _description  = T.unwords-        [ "Somehow forgotten with the coming of the Werewolves, the Witch has a chance to prove"-        , "themself valuable to the village and maybe abolish the absurd pastime of burning and"-        , "drowning their cult. The Witch is blessed (or maybe cursed) with the ability to make two"-        , "powerful potions; one of which heals a victim of the Werewolves, the other poisons a"-        , "player."-        ]-    , _rules        = T.unwords-        [ "The Witch is called after the Werewolves. They are able to heal and poison one player"-        , "per game. There is no restriction on using both potions in one night or on healing"-        , "themself."-        ]+    , _description  = T.strip [iFile|variant/standard/role/witch/description.txt|]+    , _rules        = T.strip [iFile|variant/standard/role/witch/rules.txt|]     }  -- | /The Alpha Wolf leads the Werewolves in the raids against Fougères each night and not even the/@@ -595,17 +454,12 @@ --   The Alpha Wolf appears to nature-seeing roles (e.g., the Seer) as a Villager. alphaWolfRole :: Role alphaWolfRole = Role-    { _name         = "Alpha Wolf"+    { _tag          = "alpha-wolf"+    , _name         = T.strip [iFile|variant/standard/role/alpha-wolf/name.txt|]     , _allegiance   = Werewolves     , _balance      = -5-    , _description  = T.unwords-        [ "The Alpha Wolf leads the Werewolves in the raids against Fougères each night and not"-        , "even the Seer can see them coming. If the Werewolves caused the Villagers to question"-        , "and accuse one another beforehand, the Alpha Wolf eliminates any shred of humanity left."-        , "No-one can be trusted anymore and no-one knows the truth."-        ]-    , _rules        =-        "The Alpha Wolf appears to nature-seeing roles (e.g., the Seer) as a Villager."+    , _description  = T.strip [iFile|variant/standard/role/alpha-wolf/description.txt|]+    , _rules        = T.strip [iFile|variant/standard/role/alpha-wolf/rules.txt|]     }  -- | /The Simple Werewolf is a fearsome lupine, cunning like no other creature that roams the/@@ -616,15 +470,10 @@ --   A Werewolf may never devour another Werewolf. simpleWerewolfRole :: Role simpleWerewolfRole = Role-    { _name         = "Simple Werewolf"+    { _tag          = "simple-werewolf"+    , _name         = T.strip [iFile|variant/standard/role/simple-werewolf/name.txt|]     , _allegiance   = Werewolves     , _balance      = -4-    , _description  = T.unwords-        [ "The Simple Werewolf is a fearsome lupine, cunning like no other creature that roams the"-        , "forest. Their origin is unknown, but that matters little, for they present a grave"-        , "threat to Fougères. While each day they hide in plain sight as an ordinary Villager,"-        , "each night they transform and devour an innocent. There is little hope left for the"-        , "village."-        ]-    , _rules        = "A Werewolf may never devour another Werewolf."+    , _description  = T.strip [iFile|variant/standard/role/simple-werewolf/description.txt|]+    , _rules        = T.strip [iFile|variant/standard/role/simple-werewolf/rules.txt|]     }
werewolf.cabal view
@@ -1,5 +1,5 @@ name:           werewolf-version:        1.1.1.0+version:        1.2.0.0  author:         Henry J. Wylde maintainer:     public@hjwylde.com@@ -42,8 +42,16 @@         Game.Werewolf.Command.Werewolf         Game.Werewolf.Command.Witch         Game.Werewolf.Engine-        Game.Werewolf.Messages+        Game.Werewolf.Message+        Game.Werewolf.Message.Command+        Game.Werewolf.Message.Engine+        Game.Werewolf.Message.Error         Game.Werewolf.Util+        Game.Werewolf.Variant.NoRoleKnowledge.Command+        Game.Werewolf.Variant.NoRoleKnowledge.Engine+        Game.Werewolf.Variant.Standard.Command+        Game.Werewolf.Variant.Standard.Engine+        Game.Werewolf.Variant.Standard.Error         Paths_werewolf         Werewolf.Command.Boot         Werewolf.Command.Choose@@ -64,7 +72,6 @@         Werewolf.Command.Unvote         Werewolf.Command.Version         Werewolf.Command.Vote-        Werewolf.Messages         Werewolf.Options         Werewolf.System         Werewolf.Version@@ -74,6 +81,7 @@         FlexibleContexts,         MultiParamTypeClasses,         OverloadedStrings,+        QuasiQuotes,         Rank2Types     build-depends:         aeson >= 0.8 && < 0.12,@@ -96,6 +104,7 @@     exposed-modules:         Control.Lens.Extra         Data.String.Humanise+        Data.String.Interpolate.Extra         Game.Werewolf         Game.Werewolf.Game         Game.Werewolf.Player@@ -108,6 +117,7 @@         DeriveGeneric,         FlexibleContexts,         OverloadedStrings,+        QuasiQuotes,         Rank2Types,         TemplateHaskell     build-depends:@@ -115,7 +125,9 @@         base >= 4.8 && < 5,         containers == 0.5.*,         extra == 1.4.*,+        interpolate == 0.1.*,         lens >= 4.12 && < 4.15,         mtl == 2.2.*,+        template-haskell == 2.10.*,         text == 1.2.*,         transformers == 0.4.*