packages feed

werewolf-1.5.0.0: app/Werewolf/Command/Start.hs

{-|
Module      : Werewolf.Command.Start
Description : Options and handler for the start subcommand.

Copyright   : (c) Henry J. Wylde, 2016
License     : BSD3
Maintainer  : public@hjwylde.com

Options and handler for the start subcommand.
-}

{-# LANGUAGE FlexibleContexts      #-}
{-# LANGUAGE MultiParamTypeClasses #-}

module Werewolf.Command.Start (
    -- * Options
    Options(..), ExtraRoles(..),

    -- * Handle
    handle,
) 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.Text (Text)

import           Game.Werewolf
import           Game.Werewolf.Engine
import           Game.Werewolf.Message.Error
import qualified Game.Werewolf.Variant       as Variant

import System.Random.Shuffle

import Werewolf.System

data Options = Options
    { optExtraRoles :: ExtraRoles
    , optVariant    :: Text
    , 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 variant playerNames) = do
    whenM (doesGameExist tag &&^ (hasn't (stage . _GameOver) <$> readGame tag)) $ exitWith failure
        { messages = [gameAlreadyRunningMessage callerName]
        }

    result <- runExceptT $ do
        extraRoles' <- case extraRoles of
            None            -> return []
            Random          -> randomExtraRoles $ length playerNames
            Use roleNames   -> useExtraRoles callerName roleNames

        variant' <- useVariant callerName variant

        let defaultVillagerRole = if is spitefulVillage variant' then spitefulVillagerRole else simpleVillagerRole
        let roles = padRoles extraRoles' (length playerNames + 1) defaultVillagerRole

        players <- createPlayers (callerName:playerNames) <$> shuffleM roles

        runWriterT $ startGame callerName variant' players >>= execStateT checkStage

    case result of
        Left errorMessages      -> exitWith failure { messages = errorMessages }
        Right (game, messages)  -> writeOrDeleteGame tag game >> exitWith success { messages = messages }

randomExtraRoles :: MonadRandom m => Int -> m [Role]
randomExtraRoles n = do
    let minimum = n `div` 4 + 1
    let maximum = n `div` 3 + 1

    count <- getRandomR (minimum, maximum)

    take count <$> shuffleM restrictedRoles

useExtraRoles :: MonadError [Message] m => Text -> [Text] -> m [Role]
useExtraRoles callerName roleNames = forM roleNames $ \roleName -> case findRoleByTag roleName of
    Just role   -> return role
    Nothing     -> throwError [roleDoesNotExistMessage callerName roleName]

findRoleByTag :: Text -> Maybe Role
findRoleByTag tag' = restrictedRoles ^? traverse . filteredBy tag tag'

useVariant :: MonadError [Message] m => Text -> Text -> m Variant
useVariant callerName variantName = case findVariantByTag variantName of
    Just variant   -> return variant
    Nothing         -> throwError [variantDoesNotExistMessage callerName variantName]

findVariantByTag :: Text -> Maybe Variant
findVariantByTag tag' = allVariants ^? traverse . filteredBy Variant.tag tag'

padRoles :: [Role] -> Int -> Role -> [Role]
padRoles roles n defaultVillagerRole = roles ++ villagerRoles ++ simpleWerewolfRoles
    where
        goal                    = 3
        m                       = max (n - length roles) 0
        startingBalance         = sumOf (traverse . balance) roles
        simpleWerewolfBalance   = simpleWerewolfRole ^. balance

        -- Little magic here to calculate how many Werewolves and Villagers we want.
        -- This tries to ensure that the balance of the game is between -3 and 2.
        simpleWerewolvesCount   = (goal - m - startingBalance) `div` (simpleWerewolfBalance - 1) + 1
        villagersCount          = m - simpleWerewolvesCount

        -- N.B., if roles is quite unbalanced then one list will be empty.
        villagerRoles       = replicate villagersCount defaultVillagerRole
        simpleWerewolfRoles = replicate simpleWerewolvesCount simpleWerewolfRole

createPlayers :: [Text] -> [Role] -> [Player]
createPlayers = zipWith newPlayer