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 +20/−0
- app/Game/Werewolf/Command.hs +10/−6
- app/Game/Werewolf/Command/Global.hs +8/−3
- app/Game/Werewolf/Command/Hunter.hs +2/−1
- app/Game/Werewolf/Command/Oracle.hs +1/−1
- app/Game/Werewolf/Command/Orphan.hs +1/−1
- app/Game/Werewolf/Command/Protector.hs +1/−1
- app/Game/Werewolf/Command/Scapegoat.hs +2/−1
- app/Game/Werewolf/Command/Seer.hs +1/−1
- app/Game/Werewolf/Command/Status.hs +17/−19
- app/Game/Werewolf/Command/Villager.hs +9/−3
- app/Game/Werewolf/Command/Werewolf.hs +9/−5
- app/Game/Werewolf/Command/Witch.hs +1/−1
- app/Game/Werewolf/Engine.hs +19/−16
- app/Game/Werewolf/Message.hs +65/−0
- app/Game/Werewolf/Message/Command.hs +194/−0
- app/Game/Werewolf/Message/Engine.hs +328/−0
- app/Game/Werewolf/Message/Error.hs +115/−0
- app/Game/Werewolf/Messages.hs +0/−658
- app/Game/Werewolf/Util.hs +6/−5
- app/Game/Werewolf/Variant/NoRoleKnowledge/Command.hs +36/−0
- app/Game/Werewolf/Variant/NoRoleKnowledge/Engine.hs +32/−0
- app/Game/Werewolf/Variant/Standard/Command.hs +206/−0
- app/Game/Werewolf/Variant/Standard/Engine.hs +289/−0
- app/Game/Werewolf/Variant/Standard/Error.hs +115/−0
- app/Main.hs +1/−1
- app/Werewolf/Command/Boot.hs +1/−1
- app/Werewolf/Command/Choose.hs +1/−4
- app/Werewolf/Command/Circle.hs +1/−1
- app/Werewolf/Command/Divine.hs +1/−1
- app/Werewolf/Command/End.hs +11/−25
- app/Werewolf/Command/Heal.hs +1/−3
- app/Werewolf/Command/Help.hs +52/−158
- app/Werewolf/Command/Pass.hs +1/−1
- app/Werewolf/Command/Ping.hs +1/−1
- app/Werewolf/Command/Poison.hs +1/−1
- app/Werewolf/Command/Protect.hs +1/−1
- app/Werewolf/Command/Quit.hs +1/−1
- app/Werewolf/Command/See.hs +1/−1
- app/Werewolf/Command/Start.hs +12/−18
- app/Werewolf/Command/Status.hs +1/−1
- app/Werewolf/Command/Unvote.hs +1/−2
- app/Werewolf/Command/Version.hs +2/−4
- app/Werewolf/Command/Vote.hs +1/−2
- app/Werewolf/Messages.hs +0/−39
- app/Werewolf/Options.hs +16/−12
- app/Werewolf/System.hs +10/−13
- src/Data/String/Humanise.hs +13/−2
- src/Data/String/Interpolate/Extra.hs +22/−0
- src/Game/Werewolf/Game.hs +42/−28
- src/Game/Werewolf/Player.hs +19/−4
- src/Game/Werewolf/Role.hs +97/−248
- werewolf.cabal +15/−3
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.*