{-| 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.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 -- TODO (hjw): remove Message.Command import Game.Werewolf.Game hiding (hasAnyoneWon, hasEveryoneLost) 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.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 =<< get removePlayer (bootee ^. name) checkStage' :: (MonadRandom m, MonadState Game m, MonadWriter [Message] m) => m () checkStage' = use stage >>= \stage' -> case stage' of DruidsTurn -> 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 unlessM (Map.null <$> use votes) $ lynchVotee =<< preuse votee chosenVoters .= [] votes .= Map.empty advanceStage NecromancersTurn -> do whenM (hasuse $ players . necromancers . dead) advanceStage whenM (use deadRaised) advanceStage whenM (use passed) 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 game <- get tell [scapegoatChoseAllowedVotersMessage game] 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 =<< get 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 setPlayerAllegiance (orphan ^. name) Werewolves tell . orphanJoinedPackMessages (orphan ^. name) =<< get advanceStage VillageDrunksTurn -> do 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) =<< get advanceStage VillagesTurn -> whenM (hasn'tuse pendingVoters) $ do uses votes Map.toList >>= mapM_ (\(voterName, voteeName) -> do voter <- findPlayerBy_ name voterName votee <- findPlayerBy_ name voteeName tell [playerMadeLynchVoteMessage Nothing voter votee] ) advanceStage WerewolvesTurn -> whenM (hasn'tuse pendingVoters) $ 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 =<< get | is fallenAngel votee = do fallenAngelLynched .= True tell . (:[]) . playerLynchedMessage votee =<< get | is saint votee = do tell . (:[]) . playerLynchedMessage votee =<< get killPlayer (votee ^. name) voterNames <- uses votes (filter (/= votee ^. name) . Map.keys . Map.filter (== votee ^. name)) forM_ voterNames killPlayer voters <- mapM (findPlayerBy_ name) voterNames tell . (:[]) . saintLynchedMessage voters =<< get | is werewolf votee = do tell . (:[]) . werewolfLynchedMessage votee =<< get killPlayer (votee ^. name) | otherwise = do tell . (:[]) . playerLynchedMessage votee =<< get killPlayer (votee ^. name) lynchVotee _ = preuse (players . scapegoats . alive) >>= \mScapegoat -> case mScapegoat of Just scapegoat -> do scapegoatBlamed .= True killPlayer (scapegoat ^. name) tell . (:[]) . scapegoatLynchedMessage =<< get _ -> 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 =<< get when (is medusa votee) . whenJustM (getFirstAdjacentAliveWerewolf $ votee ^. name) $ \werewolf -> do killPlayer (werewolf ^. name) tell . (:[]) . playerTurnedToStoneMessage werewolf =<< get advanceStage :: (MonadState Game m, MonadWriter [Message] m) => m () advanceStage = do game <- get nextStage <- ifM (hasAnyoneWon ||^ hasEveryoneLost) (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 ||^ hasEveryoneLost) $ stage .= GameOver >> get >>= tell . gameOverMessages