{-| Module : Game.Werewolf.Game Description : Simplistic game data structure with lenses. Copyright : (c) Henry J. Wylde, 2016 License : BSD3 Maintainer : public@hjwylde.com A game is not quite as simple as players! Roughly speaking though, this engine is /stateful/. The game state only changes when a /command/ is issued. Thus, this module defines the 'Game' data structure and any fields required to keep track of the current state. -} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE Rank2Types #-} {-# LANGUAGE TemplateHaskell #-} module Game.Werewolf.Game ( -- * Game Game, variant, stage, round, players, boots, allowedVoters, divine, fallenAngelLynched, healUsed, hunterRetaliated, jesterRevealed, passed, poison, poisonUsed, priorProtect, protect, roleModel, scapegoatBlamed, see, votes, Variant(..), _Standard, _NoRoleKnowledge, Stage(..), _DruidsTurn, _GameOver, _HuntersTurn1, _HuntersTurn2, _Lynching, _OraclesTurn, _OrphansTurn, _ProtectorsTurn, _ScapegoatsTurn, _SeersTurn, _Sunrise, _Sunset, _VillageDrunksTurn, _VillagesTurn, _WerewolvesTurn, _WitchsTurn, allStages, stageCycle, stageAvailable, newGame, -- ** Folds votee, -- ** Prisms firstRound, secondRound, thirdRound, -- ** Searches getAllowedVoters, getPendingVoters, -- ** Queries hasAnyoneWon, hasFallenAngelWon, hasVillagersWon, hasWerewolvesWon, ) where import Control.Lens hiding (isn't) import Control.Lens.Extra import Data.List.Extra import Data.Map (Map) import qualified Data.Map as Map import Data.Maybe import Data.String.Humanise import Data.Text (Text) import Game.Werewolf.Player import Prelude hiding (round) -- | There are a few key pieces of information that a game always needs to hold. These are: -- -- * the 'stage', -- * the 'round' number and -- * the 'players'. -- -- Any further fields on the game are specific to one or more roles (and their respective turns!). -- 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 { _variant :: Variant , _stage :: Stage , _round :: Int , _players :: [Player] , _boots :: Map Text [Text] , _allowedVoters :: [Text] -- ^ Jester, Scapegoat , _divine :: Maybe Text -- ^ Oracle , _fallenAngelLynched :: Bool -- ^ Fallen Angel , _healUsed :: Bool -- ^ Witch , _hunterRetaliated :: Bool -- ^ Hunter , _jesterRevealed :: Bool -- ^ Jester , _passed :: Bool -- ^ Witch , _poison :: Maybe Text -- ^ Witch , _poisonUsed :: Bool -- ^ Witch , _priorProtect :: Maybe Text -- ^ Protector , _protect :: Maybe Text -- ^ Protector , _roleModel :: Maybe Text -- ^ Orphan , _scapegoatBlamed :: Bool -- ^ Scapegoat , _see :: Maybe Text -- ^ Seer , _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 = 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 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. allStages :: [Stage] allStages = [ Sunset , OrphansTurn , VillageDrunksTurn , SeersTurn , OraclesTurn , ProtectorsTurn , WerewolvesTurn , WitchsTurn , Sunrise , HuntersTurn2 , DruidsTurn , VillagesTurn , Lynching , HuntersTurn1 , ScapegoatsTurn , GameOver ] -- | An infinite cycle of all 'Stage's in the order that they should occur. stageCycle :: [Stage] stageCycle = cycle allStages -- | Checks whether the stage is available for the given 'Game'. Most often this just involves -- checking if there is an applicable role alive, but sometimes it is more complex. -- -- 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 DruidsTurn = has (players . druids . alive) game stageAvailable _ GameOver = False stageAvailable game HuntersTurn1 = has (players . hunters . dead) game && not (game ^. hunterRetaliated) stageAvailable game HuntersTurn2 = has (players . hunters . dead) game && not (game ^. hunterRetaliated) stageAvailable game Lynching = Map.size (game ^. votes) > 0 stageAvailable game OraclesTurn = has (players . oracles . alive) game stageAvailable game OrphansTurn = has (players . orphans . alive) game && isNothing (game ^. roleModel) stageAvailable game ProtectorsTurn = has (players . protectors . alive) game stageAvailable game ScapegoatsTurn = game ^. scapegoatBlamed stageAvailable game SeersTurn = has (players . seers . alive) game stageAvailable _ Sunrise = True stageAvailable _ Sunset = True stageAvailable game VillageDrunksTurn = has (players . villageDrunks . alive) game && is thirdRound game stageAvailable game VillagesTurn = any (is alive) (getAllowedVoters game) stageAvailable game WerewolvesTurn = has (players . werewolves . alive) game stageAvailable game WitchsTurn = has (players . witches . alive) game && (not (game ^. healUsed) || not (game ^. poisonUsed)) -- | Creates a new 'Game' with the given players. No validations are performed here, those are left -- to the binary. newGame :: Variant -> [Player] -> Game newGame variant players = Game { _variant = variant , _stage = head stageCycle , _round = 0 , _players = players , _boots = Map.empty , _passed = False , _allowedVoters = players ^.. names , _divine = Nothing , _fallenAngelLynched = False , _healUsed = False , _hunterRetaliated = False , _jesterRevealed = False , _poison = Nothing , _poisonUsed = False , _priorProtect = Nothing , _protect = Nothing , _roleModel = Nothing , _scapegoatBlamed = False , _see = Nothing , _votes = Map.empty } -- | The traversal of the 'votes' victim's name. This is the player, if they exist, that received -- the majority of the votes. This could be an empty list depending on whether the votes were in -- conflict. votee :: Fold Game Player votee = folding getVotee -- | Gets the 'votes' victim's name. This is the player, if they exist, that received the majority -- of the votes. This could be an empty list depending on whether the votes were in conflict. getVotee :: Game -> [Player] getVotee game | Map.null (game ^. votes) = [] | length result /= 1 = [] | otherwise = game ^.. players . traverse . named (head result) where votees = Map.elems $ game ^. votes result = last $ groupSortOn (length . (`elemIndices` votees)) (nub votees) -- | The traversal of 'Game's on the first round. firstRound :: Prism' Game Game firstRound = prism (set round 0) $ \game -> (if game ^. round == 0 then Right else Left) game -- | The traversal of 'Game's on the second round. secondRound :: Prism' Game Game secondRound = prism (set round 1) $ \game -> (if game ^. round == 1 then Right else Left) game -- | The traversal of 'Game's on the third round. thirdRound :: Prism' Game Game thirdRound = prism (set round 2) $ \game -> (if game ^. round == 2 then Right else Left) game -- | Gets all the 'allowedVoters' in a game (which is names only) and maps them to their player. getAllowedVoters :: Game -> [Player] getAllowedVoters game = map (\name -> game ^?! players . traverse . named name) (game ^. allowedVoters) -- | Gets all 'Alive' players that have yet to vote. getPendingVoters :: Game -> [Player] getPendingVoters game = game ^.. players . traverse . alive . filtered ((`Map.notMember` votes') . view name) where votes' = game ^. votes -- | Queries whether anyone has won. hasAnyoneWon :: Game -> Bool hasAnyoneWon game = any ($ game) [hasFallenAngelWon, hasVillagersWon, hasWerewolvesWon] -- | Queries whether the Fallen Angel has won. The Fallen Angel wins if they manage to get -- themselves lynched by the Villagers. hasFallenAngelWon :: Game -> Bool hasFallenAngelWon game = game ^. fallenAngelLynched -- | Queries whether the 'Villagers' have won. The 'Villagers' win if they are the only players -- surviving. -- -- N.B., the Fallen Angel is not considered when determining whether the 'Villagers' have won. hasVillagersWon :: Game -> Bool hasVillagersWon = allOf (players . traverse . alive) (\player -> is villager player || is fallenAngel player) -- | Queries whether the 'Werewolves' have won. The 'Werewolves' win if they are the only players -- surviving. hasWerewolvesWon :: Game -> Bool hasWerewolvesWon = allOf (players . traverse . alive) (is werewolf)