werewolf-1.1.0.0: app/Game/Werewolf/Engine.hs
{-|
Module : Game.Werewolf.Engine
Description : Engine functions.
Copyright : (c) Henry J. Wylde, 2016
License : BSD3
Maintainer : public@hjwylde.com
Engine functions.
-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE MultiParamTypeClasses #-}
module Game.Werewolf.Engine (
-- * Loop
checkStage, checkGameOver,
) where
import Control.Lens hiding (cons, isn't)
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.Extra
import qualified Data.Map as Map
import Data.Maybe
import Game.Werewolf.Game hiding (getAllowedVoters, getPendingVoters, hasAnyoneWon,
hasFallenAngelWon, hasVillagersWon, hasWerewolvesWon)
import Game.Werewolf.Messages
import Game.Werewolf.Player
import Game.Werewolf.Response
import Game.Werewolf.Role hiding (name)
import Game.Werewolf.Util
import Prelude hiding (round)
checkStage :: (MonadRandom m, MonadState Game m, MonadWriter [Message] m) => m ()
checkStage = do
game <- get
checkBoots >> checkStage'
game' <- get
when (game /= game') checkStage
checkBoots :: (MonadState Game m, MonadWriter [Message] m) => m ()
checkBoots = do
alivePlayerCount <- length . toListOf (players . traverse . alive) <$> get
booteeNames <- uses boots $ Map.keys . Map.filter (\voters -> length voters > alivePlayerCount `div` 2)
bootees <- mapM (findPlayerBy_ name) booteeNames
forM_ (filter (is alive) bootees) $ \bootee -> do
tell [playerBootedMessage bootee]
removePlayer (bootee ^. name)
checkStage' :: (MonadRandom m, MonadState Game m, MonadWriter [Message] m) => m ()
checkStage' = use stage >>= \stage' -> case stage' of
FerinasGrunt -> do
druid <- findPlayerBy_ role druidRole
players' <- filter (isn't alphaWolf) <$> getAdjacentAlivePlayers (druid ^. name)
when (has werewolves players' || has lycans players') $ tell [ferinaGruntsMessage]
advanceStage
GameOver -> return ()
HuntersTurn1 -> whenM (use hunterRetaliated) advanceStage
HuntersTurn2 -> whenM (use hunterRetaliated) advanceStage
Lynching -> do
lynchVotee =<< preuse votee
allVoters <- ifM (use jesterRevealed)
(uses players $ filter (isn't jester))
(use players)
allowedVoters .= allVoters ^.. traverse . alive . name
votes .= Map.empty
advanceStage
OraclesTurn -> do
whenM (hasuse $ players . oracles . dead) advanceStage
whenM (isJust <$> use divine) advanceStage
OrphansTurn -> do
whenM (hasuse $ players . orphans . dead) advanceStage
whenM (isJust <$> use roleModel) advanceStage
ProtectorsTurn -> do
whenM (hasuse $ players . protectors . dead) advanceStage
whenM (isJust <$> use protect) advanceStage
ScapegoatsTurn -> unlessM (use scapegoatBlamed) $ do
allowedVoters' <- use allowedVoters
tell [scapegoatChoseAllowedVotersMessage allowedVoters']
advanceStage
SeersTurn -> do
whenM (hasuse $ players . seers . dead) advanceStage
whenM (isJust <$> use see) advanceStage
Sunrise -> do
round += 1
devourVotee =<< preuse votee
whenJustM (use poison) $ \targetName -> do
target <- findPlayerBy_ name targetName
killPlayer targetName
tell [playerPoisonedMessage target]
whenJustM (preuse $ players . seers . alive) $ \seer -> do
target <- use see >>= findPlayerBy_ name . fromJust
when (is alive target) $ tell [playerSeenMessage (seer ^. name) target]
whenJustM (preuse $ players . oracles . alive) $ \oracle -> do
target <- use divine >>= findPlayerBy_ name . fromJust
when (is alive target) $ tell [playerDivinedMessage (oracle ^. name) target]
divine .= Nothing
poison .= Nothing
protect .= Nothing
see .= Nothing
votes .= Map.empty
advanceStage
Sunset -> do
whenJustM (use roleModel) $ \roleModelsName -> do
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
advanceStage
VillageDrunksTurn -> do
aliveWerewolfNames <- toListOf (players . werewolves . alive . name) <$> get
randomAllegiance <- getRandomAllegiance
players . villageDrunks . role . allegiance .= randomAllegiance
villageDrunk <- findPlayerBy_ role villageDrunkRole
if is villager villageDrunk
then tell [villageDrunkJoinedVillageMessage $ villageDrunk ^. name]
else tell $ villageDrunkJoinedPackMessages (villageDrunk ^. name) aliveWerewolfNames
advanceStage
VillagesTurn -> whenM (null <$> liftM2 intersect getAllowedVoters getPendingVoters) $ do
tell . map (uncurry $ playerMadeLynchVoteMessage Nothing) =<< uses votes Map.toList
advanceStage
WerewolvesTurn -> whenM (none (is werewolf) <$> getPendingVoters) $ do
whenM (liftM2 (==) (use protect) (preuses votee $ view name)) $ votes .= Map.empty
advanceStage
WitchsTurn -> do
whenM (hasuse $ players . witches . dead) advanceStage
whenM (use healUsed &&^ use poisonUsed) advanceStage
whenM (use passed) advanceStage
lynchVotee :: (MonadState Game m, MonadWriter [Message] m) => Maybe Player -> m ()
lynchVotee (Just votee)
| is jester votee = do
jesterRevealed .= True
tell [jesterLynchedMessage $ votee ^. name]
| is fallenAngel votee = do
fallenAngelLynched .= True
tell [playerLynchedMessage votee]
| otherwise = do
killPlayer (votee ^. name)
tell [playerLynchedMessage votee]
lynchVotee _ = preuse (players . scapegoats . alive) >>= \mScapegoat -> case mScapegoat of
Just scapegoat -> do
scapegoatBlamed .= True
killPlayer (scapegoat ^. name)
tell [scapegoatLynchedMessage (scapegoat ^. name)]
_ -> tell [noPlayerLynchedMessage]
devourVotee :: (MonadState Game m, MonadWriter [Message] m) => Maybe Player -> m ()
devourVotee Nothing = tell [noPlayerDevouredMessage]
devourVotee (Just votee) = do
killPlayer (votee ^. name)
tell [playerDevouredMessage votee]
when (is medusa votee) . whenJustM (getFirstAdjacentAliveWerewolf $ votee ^. name) $ \werewolf -> do
killPlayer (werewolf ^. name)
tell [playerTurnedToStoneMessage werewolf]
advanceStage :: (MonadState Game m, MonadWriter [Message] m) => m ()
advanceStage = do
game <- get
nextStage <- ifM hasAnyoneWon
(return GameOver)
(return . head $ filter (stageAvailable game) (drop1 $ dropWhile (game ^. stage /=) stageCycle))
stage .= nextStage
boots .= Map.empty
passed .= False
tell . stageMessages =<< get
checkGameOver :: (MonadState Game m, MonadWriter [Message] m) => m ()
checkGameOver = whenM hasAnyoneWon $ stage .= GameOver >> get >>= tell . gameOverMessages