packages feed

dovin-3.0: src/Dovin/Builder.hs

{-|
Functions for adding new cards to the board.

> withLocation Hand $ do
>   withAttributes ["angel", token] $ addCreature (4, 4) "Angel"
-}
module Dovin.Builder (
  -- * Builders
  -- | Each of these terminates a build chain, and will add a card with the
  -- specified type to the board.
    addCard
  , addAura
  , addArtifact
  , addCreature
  , addEnchantment
  , addInstant
  , addLand
  , addLands
  , addPlaneswalker
  , addSorcery
  -- * Fluid interface
  -- | These methods can be chained together to specify different properties of
  -- the card to be created.
  , as
  , withAttribute
  , withAttributes
  , withCMC
  , withEffect
  , withEffectWhen
  , withLocation
  , withOwner
  , withPlusOneCounters
  , withMinusOneCounters
  ) where

import Dovin.Attributes
import Dovin.Prelude
import Dovin.Types
import Dovin.Helpers (getTimestamp)
import Dovin.Matchers (matchNone)
import Dovin.Effects (resolveEffects, enabledInPlay)

import Control.Monad.Reader (local)
import Control.Lens (_1)
import qualified Data.HashMap.Strict as M
import qualified Data.Set as S

addCard :: CardName -> GameMonad ()
addCard name = do
  card <- use $ cards . at name
  now <- getTimestamp

  case card of
    Just _ -> throwError $ "Card should be removed: " <> name
    Nothing -> do
      template <- view envTemplate
      owner <- view envOwner
      modifying cards (M.insert name (BaseCard
        $ set cardName name
        . set cardTimestamp now
        . set cardOwner (maybe (view (cardLocation . _1) template) id owner)
        $ template))
      resolveEffects

addAura :: CardName -> GameMonad ()
addAura name = withAttribute aura $ addEnchantment name

addArtifact :: CardName -> GameMonad ()
addArtifact name = withAttribute artifact $ addEnchantment name

addCreature :: (Int, Int) -> CardName -> GameMonad ()
addCreature strength name = local (set (envTemplate . cardStrength) $ mkStrength strength)
  $ withAttribute creature
  $ addCard name

addPlaneswalker :: Int -> CardName -> GameMonad ()
addPlaneswalker loyalty name = local (set (envTemplate . cardLoyalty) loyalty)
  $ withAttribute planeswalker
  $ addCard name

addEnchantment :: CardName -> GameMonad ()
addEnchantment name = withAttribute enchantment $ addCard name

addInstant :: CardName -> GameMonad ()
addInstant name = withAttribute instant $ addCard name

addLand :: CardName -> GameMonad ()
addLand name = withAttribute land $ addCard name

addLands :: Int -> CardName -> GameMonad ()
addLands n name = withAttribute land $
  forM_ [1..n] $ \n -> addCard (numbered n name)

addSorcery :: CardName -> GameMonad ()
addSorcery name = withAttribute sorcery $ addCard name

-- | Perform action as the specified player.
as :: Player -> GameMonad () -> GameMonad ()
as player = local (set envActor player)

-- | Add an attribute to the created card, as identified by a string.
-- Attributes with that special meaning to Dovin built-ins (such as flying) are
-- defined in "Dovin.Attributes".
withAttribute :: String -> GameMonad () -> GameMonad ()
withAttribute attr = withAttributes [attr]

-- | Helper version of 'withAttribute' for adding multiple attributes at a
-- time.
withAttributes :: [String] -> GameMonad () -> GameMonad ()
withAttributes attrs =
  let f = S.union . S.fromList $ attrs in
  local (over (envTemplate . cardAttributes) f
       . over (envTemplate . cardDefaultAttributes) f)

-- | Add an effect to the created card. The effect will only apply when the
-- card is in play.
withEffect ::
    EffectMonad CardMatcher -- ^ The set of cards to apply the effect to
 -> [LayeredEffectPart]     -- ^ The effect to apply
 -> EffectName              -- ^ Human-readable description, cosmetic only.
 -> GameMonad ()
 -> GameMonad ()
withEffect = withEffectWhen enabledInPlay

-- | A more flexible version of 'withEffect' that allows customization of then
-- the effect should apply.
withEffectWhen ::
    EffectMonad Bool        -- ^ Effect only applies when this returns true
 -> EffectMonad CardMatcher -- ^ The set of cards to apply the effect to
 -> [LayeredEffectPart]     -- ^ The effect to apply
 -> EffectName              -- ^ Human-readable description, cosmetic only.
 -> GameMonad ()
 -> GameMonad ()
withEffectWhen enabled appliesTo effect name =
  local (over (envTemplate . cardPassiveEffects)
  (mkLayeredEffectPart combinedMatcher effect name:))

  where
    combinedMatcher :: EffectMonad CardMatcher
    combinedMatcher = do
      isEnabled <- enabled

      if isEnabled then
        appliesTo
      else
        return matchNone

-- | Set the converted mana cost of the created card.
withCMC :: Int -> GameMonad () -> GameMonad ()
withCMC n =
  local (set (envTemplate . cardCmc) n)

-- | Place the created card into a specific location.
withLocation :: Location -> GameMonad () -> GameMonad ()
withLocation loc m = do
  p <- view envActor

  local (set (envTemplate . cardLocation) (p, loc)) m

-- | Set the owner for the created card. If not specified, defaults to the
-- owner of the card location.
withOwner :: Player -> GameMonad () -> GameMonad ()
withOwner owner = local (set envOwner (Just owner))

-- | Set the number of +1/+1 counters of the created card.
withPlusOneCounters :: Int -> GameMonad () -> GameMonad ()
withPlusOneCounters = local . set (envTemplate . cardPlusOneCounters)

-- | Set the number of -1/-1 counters of the created card.
withMinusOneCounters :: Int -> GameMonad () -> GameMonad ()
withMinusOneCounters = local . set (envTemplate . cardMinusOneCounters)