werewolf-0.3.4.0: src/Game/Werewolf/Response.hs
{-|
Module : Game.Werewolf.Response
Description : Response and message data structures.
Copyright : (c) Henry J. Wylde, 2015
License : BSD3
Maintainer : public@hjwylde.com
Response and message data structures.
-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
module Game.Werewolf.Response (
-- * Response
Response(..),
-- ** Common responses
success, failure,
-- ** Exit functions
exitWith, exitSuccess, exitFailure,
-- * Message
Message(..),
publicMessage, privateMessage,
-- ** Generic messages
newGameMessages, turnMessages, nightFallsMessage, gameOverMessages, playerQuitMessage,
-- ** Ping messages
pingSeersMessage, pingWerewolvesMessage,
-- ** Status messages
currentTurnMessage, rolesInGameMessage, playersInGameMessage, waitingOnMessage,
-- ** Seers turn messages
seersTurnMessages, playerSeenMessage,
-- ** Villagers turn messages
villagersTurnMessage, playerMadeLynchVoteMessage, playerLynchedMessage, noPlayerLynchedMessage,
scapegoatLynchedMessage,
-- ** Werewolves turn messages
werewolvesTurnMessages, playerMadeDevourVoteMessage, playerDevouredMessage,
noPlayerDevouredMessage, villagersLynchVoteMessage,
-- ** Generic error messages
gameIsOverMessage, playerDoesNotExistMessage, playerCannotDoThatMessage,
playerCannotDoThatRightNowMessage, playerIsDeadMessage, roleDoesNotExistMessage,
-- ** Seers turn error messages
playerHasAlreadySeenMessage,
-- ** Voting turn error messages
playerHasAlreadyVotedMessage, targetIsDeadMessage,
-- ** Werewolves turn error messages
playerCannotDevourAnotherWerewolf,
) where
import Control.Lens
import Control.Monad.IO.Class
import Data.Aeson
#if !MIN_VERSION_aeson(0,10,0)
import Data.Aeson.Types
#endif
import Data.List.Extra
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.Lazy.Encoding as T
import qualified Data.Text.Lazy.IO as T
import Game.Werewolf.Game
import Game.Werewolf.Player
import Game.Werewolf.Role (Role, allegiance, description, _allegiance)
import qualified Game.Werewolf.Role as Role
import GHC.Generics
import qualified System.Exit as Exit
data Response = Response
{ ok :: Bool
, messages :: [Message]
} deriving (Eq, Generic, Show)
instance FromJSON Response
instance ToJSON Response where
toJSON = genericToJSON defaultOptions
#if MIN_VERSION_aeson(0,10,0)
toEncoding = genericToEncoding defaultOptions
#endif
success :: Response
success = Response True []
failure :: Response
failure = Response False []
exitWith :: MonadIO m => Response -> m ()
exitWith response = liftIO $ T.putStrLn (T.decodeUtf8 $ encode response) >> Exit.exitSuccess
exitSuccess :: MonadIO m => m ()
exitSuccess = exitWith success
exitFailure :: MonadIO m => m ()
exitFailure = exitWith failure
data Message = Message
{ to :: Maybe [Text]
, message :: Text
} deriving (Eq, Generic, Show)
instance FromJSON Message
instance ToJSON Message where
toJSON = genericToJSON defaultOptions
#if MIN_VERSION_aeson(0,10,0)
toEncoding = genericToEncoding defaultOptions
#endif
publicMessage :: Text -> Message
publicMessage = Message Nothing
privateMessage :: [Text] -> Text -> Message
privateMessage to = Message (Just to)
newGameMessages :: Game -> [Message]
newGameMessages game = [newPlayersInGameMessage players', rolesInGameMessage Nothing $ map _role players'] ++ map (newPlayerMessage players') players' ++ turnMessages turn' players'
where
turn' = game ^. turn
players' = game ^. players
newPlayersInGameMessage :: [Player] -> Message
newPlayersInGameMessage players = publicMessage $ T.concat [
"A new game of werewolf is starting with ",
T.intercalate ", " (map _name players), "!"
]
newPlayerMessage :: [Player] -> Player -> Message
newPlayerMessage players player
| isWerewolf player = privateMessage [player ^. name] $ T.intercalate "\n" [T.concat ["You're a Werewolf", packMessage], player ^. role . description]
| otherwise = privateMessage [player ^. name] $ T.intercalate "\n" [T.concat ["You're a ", player ^. role . Role.name, "."], player ^. role . description]
where
packMessage
| length (filterWerewolves players) <= 1 = "."
| otherwise = T.concat [", along with ", T.intercalate ", " (map _name $ filterWerewolves players \\ [player]), "."]
turnMessages :: Turn -> [Player] -> [Message]
turnMessages NightFalling _ = [nightFallsMessage]
turnMessages DayBreaking _ = []
turnMessages Seers players = seersTurnMessages $ filter isSeer players
turnMessages Villagers _ = [villagersTurnMessage]
turnMessages Werewolves players = werewolvesTurnMessages $ filter isWerewolf players
turnMessages NoOne _ = []
nightFallsMessage :: Message
nightFallsMessage = publicMessage "Night falls, the townsfolk are asleep."
gameOverMessages :: Game -> [Message]
gameOverMessages game = case aliveAllegiances of
[allegiance] -> concat [
[publicMessage $ T.unwords ["The game is over! The", T.pack $ show allegiance, "have won."]],
map (playerWonMessage . _name) (filter ((allegiance ==) . _allegiance . _role) players'),
map (playerLostMessage . _name) (filter ((allegiance /=) . _allegiance . _role) players')
]
_ -> publicMessage "The game is over! Everyone died...":map (playerLostMessage . _name) players'
where
players' = game ^. players
aliveAllegiances = nub $ map (_allegiance . _role) (filterAlive players')
playerWonMessage :: Text -> Message
playerWonMessage name = privateMessage [name] "Victory! You won!"
playerLostMessage :: Text -> Message
playerLostMessage name = privateMessage [name] "Feck, you lost this time round..."
playerQuitMessage :: Player -> Message
playerQuitMessage player = publicMessage $ T.unwords [player ^. name, "the", player ^. role . Role.name, "has quit!"]
pingSeersMessage :: Message
pingSeersMessage = publicMessage "Waiting on the Seers..."
pingWerewolvesMessage :: Message
pingWerewolvesMessage = publicMessage "Waiting on the Werewolves..."
currentTurnMessage :: Text -> Turn -> Message
currentTurnMessage name NoOne = gameIsOverMessage name
currentTurnMessage name turn = privateMessage [name] $ T.concat [
"It's currently the ", T.pack $ show turn, "' turn."
]
rolesInGameMessage :: Maybe [Text] -> [Role] -> Message
rolesInGameMessage mTo roles = Message mTo $ T.concat [
"The roles in play are ",
T.intercalate ", " $ map (\(role, count) ->
T.concat [role ^. Role.name, " (", T.pack $ show count, ")"])
roleCounts,
"."
]
where
roleCounts = map (\list -> (head list, length list)) (groupSortOn Role._name roles)
playersInGameMessage :: Text -> [Player] -> Message
playersInGameMessage to players = privateMessage [to] . T.intercalate "\n" $ [
alivePlayersText
] ++ if (null $ filterDead players) then [] else [deadPlayersText]
where
alivePlayersText = T.concat [
"The following players are still alive: ",
T.intercalate ", " (map _name $ filterAlive players), "."
]
deadPlayersText = T.concat [
"The following players are dead: ",
T.intercalate ", " (map (\player -> T.concat [player ^. name, " (", player ^. role . Role.name, ")"]) $ filterDead players), "."
]
waitingOnMessage :: Maybe [Text] -> [Player] -> Message
waitingOnMessage to players = Message to $ T.concat [
"Waiting on ", T.intercalate ", " playerNames, "..."
]
where
playerNames = map _name players
seersTurnMessages :: [Player] -> [Message]
seersTurnMessages seers = [
publicMessage "The Seers wake up.",
privateMessage (map _name seers) "Whose allegiance would you like to see?"
]
playerSeenMessage :: Text -> Player -> Message
playerSeenMessage seerName target = privateMessage [seerName] $ T.concat [target ^. name, " is aligned with the ", T.pack . show $ target ^. role . allegiance, "."]
villagersTurnMessage :: Message
villagersTurnMessage = publicMessage "The sun rises. Everybody wakes up and opens their eyes..."
villagersLynchVoteMessage :: Message
villagersLynchVoteMessage = publicMessage "Whom would you like to lynch?"
playerMadeLynchVoteMessage :: Text -> Text -> Message
playerMadeLynchVoteMessage voterName targetName = publicMessage $ T.concat [voterName, " voted to lynch ", targetName, "."]
playerLynchedMessage :: Text -> Text -> Message
playerLynchedMessage name "Werewolf" = publicMessage $ T.unwords [
name, "is tied up to a pyre and set alight.",
"As they scream their body starts to contort and writhe, transforming into a Werewolf.",
"Thankfully they go limp before breaking free of their restraints."
]
playerLynchedMessage name roleName = publicMessage $ T.concat [
name, " is tied up to a pyre and set alight.",
" Eventually the screams start to die and with their last breath,",
" they reveal themselves as a ", roleName, "."
]
noPlayerLynchedMessage :: Message
noPlayerLynchedMessage = publicMessage "Daylight is wasted as the townsfolk squabble over whom to tie up. Looks like no one is being burned this day."
werewolvesTurnMessages :: [Player] -> [Message]
werewolvesTurnMessages werewolves = [
publicMessage "The Werewolves wake up, recognise one another and choose a new victim.",
privateMessage (map _name werewolves) "Whom would you like to devour?"
]
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."
]
playerMadeDevourVoteMessage :: [Text] -> Text -> Text -> Message
playerMadeDevourVoteMessage to voterName targetName = privateMessage to $ T.concat [voterName, " voted to devour ", targetName, "."]
playerDevouredMessage :: Text -> Text -> Message
playerDevouredMessage name roleName = publicMessage $ T.concat [
"As you open them you notice a door broken down and ",
name, "'s guts half devoured and spilling out over the cobblestones.",
" From the look of their personal effects, you deduce they were a ", roleName, ".",
" As the village bays for vengeance, the town clerk calls for a vote."
]
noPlayerDevouredMessage :: Message
noPlayerDevouredMessage = publicMessage $ T.unwords [
"Surprisingly you see everyone present at the town square. Perhaps the Werewolves have left Miller's Hollow?",
"Still got to keep up the tradition though, so the town clerk calls for a vote."
]
gameIsOverMessage :: Text -> Message
gameIsOverMessage name = privateMessage [name] "The game is over!"
playerDoesNotExistMessage :: Text -> Text -> Message
playerDoesNotExistMessage to name = privateMessage [to] $ T.unwords ["Player", name, "does not exist."]
playerCannotDoThatMessage :: Text -> Message
playerCannotDoThatMessage name = privateMessage [name] "You cannot do that!"
playerCannotDoThatRightNowMessage :: Text -> Message
playerCannotDoThatRightNowMessage name = privateMessage [name] "You cannot do that right now!"
playerIsDeadMessage :: Text -> Message
playerIsDeadMessage name = privateMessage [name] "Sshh, you're meant to be dead!"
roleDoesNotExistMessage :: Text -> Text -> Message
roleDoesNotExistMessage to name = privateMessage [to] $ T.unwords ["Role", name, "does not exist."]
playerHasAlreadySeenMessage :: Text -> Message
playerHasAlreadySeenMessage name = privateMessage [name] "You've already seen!"
playerHasAlreadyVotedMessage :: Text -> Message
playerHasAlreadyVotedMessage name = privateMessage [name] "You've already voted!"
targetIsDeadMessage :: Text -> Text -> Message
targetIsDeadMessage name targetName = privateMessage [name] $ T.unwords [targetName, "is already dead!"]
playerCannotDevourAnotherWerewolf :: Text -> Message
playerCannotDevourAnotherWerewolf name = privateMessage [name] "You cannot devour another Werewolf!"