603 lines
24 KiB
Haskell
603 lines
24 KiB
Haskell
{-# LANGUAGE GADTs #-}
|
|
{-# LANGUAGE FlexibleContexts #-}
|
|
{-# LANGUAGE LambdaCase #-}
|
|
{-# LANGUAGE MultiWayIf #-}
|
|
{-# LANGUAGE RankNTypes #-}
|
|
{-# LANGUAGE NoMonomorphismRestriction #-}
|
|
|
|
module Waterdeep.Actions
|
|
( noAction
|
|
, scorePoints
|
|
, takeResources
|
|
, returnResources
|
|
, chooseQuest
|
|
, replaceQuests
|
|
, drawQuest
|
|
, drawNamedQuestType
|
|
, distributeQuests
|
|
, completeQuest
|
|
, chooseAndCompleteQuest
|
|
, discardUncompletedQuest
|
|
, buyBuilding
|
|
, chooseFreeBuilding
|
|
, drawFreeBuilding
|
|
, discardUnoccupiedBuilding
|
|
, drawIntrigue
|
|
, playIntrigue
|
|
, returnAgent
|
|
, returnAgentFromHarbor
|
|
, assignAmbassador
|
|
, assignAgent
|
|
, assignAgentToBuildersHall
|
|
, assignAgentToOpponentsSpace
|
|
, useOpponentsSpace
|
|
, gainLieutenant
|
|
, gainAmbassador
|
|
, assignMandatoryQuest
|
|
, becomeFirstPlayer
|
|
, enableAssignOnceToOpponentsSpace
|
|
, forOneOpponent
|
|
, forEachOpponent
|
|
, forCurrentPlayer
|
|
, forEachBuilding
|
|
, forEachControlledBuilding
|
|
, restockCliffwatchInn
|
|
, restockBuildersHall
|
|
, getOpponents
|
|
, doPlotAction
|
|
, mandatoryQuestCard
|
|
) where
|
|
|
|
import Control.Applicative
|
|
import Control.Arrow ((&&&))
|
|
import Control.Monad (when, unless, guard, join, replicateM, replicateM_, void)
|
|
import Control.Monad.Random
|
|
import Control.Monad.State (get)
|
|
import Control.Monad.Trans.Writer
|
|
import Data.Char
|
|
import Data.Function
|
|
import Data.List
|
|
import Data.Maybe
|
|
import Data.Monoid
|
|
import Data.Foldable (foldMap, forM_, mapM_)
|
|
import Data.Traversable (traverse, for, forM, mapM)
|
|
import Lens.Family2 hiding ((&))
|
|
import Lens.Family2.State
|
|
import Lens.Family2.Stock
|
|
import Prelude hiding (forM, mapM)
|
|
import System.Random.Shuffle
|
|
import Text.Printf
|
|
import Waterdeep.Types
|
|
import Waterdeep.Util
|
|
|
|
import qualified Data.Foldable as F
|
|
import qualified Data.IntMap as IM
|
|
import qualified Data.Map as M
|
|
import qualified Data.MultiSet as MS
|
|
import qualified Data.Traversable as T
|
|
|
|
noAction :: GameAction
|
|
noAction = return ()
|
|
|
|
scorePoints :: Int -> GameAction
|
|
scorePoints n = do
|
|
activePlayerState . playerScore += n
|
|
name <- use activePlayerName
|
|
broadcast $ printf "%s scored %d points." name n
|
|
|
|
takeResources :: Int -> [Resource] -> GameAction
|
|
takeResources n rs = do
|
|
received <- map (head &&& length) . group . sort <$$> replicateM n $ do
|
|
r <- solicitChoice "Take one item:" $ map (show &&& id) rs
|
|
activePlayerState . playerTavern <>= MS.singleton r
|
|
actionProvided r
|
|
return r
|
|
let items = joinStrings $ map (\(r,n) -> show n ++ " " ++ show r) received
|
|
name <- use $ activePlayerName
|
|
broadcast $ printf "%s received %s." name items
|
|
|
|
returnResources :: Int -> [Resource] -> GameAction
|
|
returnResources n rs = do
|
|
returned <- replicateM n $ do
|
|
r <- solicitChoice "Return one item:" $ map (show &&& id) rs
|
|
guard . (r `MS.member`) =<< use (activePlayerState . playerTavern)
|
|
activePlayerState . playerTavern %= (`MS.difference` MS.singleton r)
|
|
return r
|
|
let groups = map (head &&& length) $ group returned
|
|
let items = joinStrings $ map (\(r,n) -> show n ++ " " ++ show r) groups
|
|
name <- use $ activePlayerName
|
|
broadcast $ name ++ " returned " ++ items ++ " to the supply."
|
|
|
|
chooseQuest' :: Waterdeep Quest
|
|
chooseQuest' = do
|
|
choices <- zipWith (\i q -> (q ^. questTitle, (i, q))) [0..] <$> use gameCliffwatchInn
|
|
(i, quest) <- solicitChoice "Choose one quest:" $ nubOn fst choices
|
|
gameCliffwatchInn %= deleteAt i
|
|
name <- use $ activePlayerName
|
|
broadcast $ name ++ " chose " ++ (quest ^. questTitle) ++ " from Cliffwatch Inn."
|
|
restockCliffwatchInn
|
|
return quest
|
|
|
|
chooseQuest :: GameAction
|
|
chooseQuest = do
|
|
quest <- chooseQuest'
|
|
activePlayerState . playerIncompleteQuests <>= [quest]
|
|
return ()
|
|
|
|
replaceQuests :: GameAction
|
|
replaceQuests = do
|
|
quests <- use gameCliffwatchInn
|
|
gameCliffwatchInn .= []
|
|
gameQuestDiscard <>= quests
|
|
restockCliffwatchInn
|
|
|
|
drawQuest :: GameAction
|
|
drawQuest = do
|
|
Just q <- drawQuest'
|
|
name <- use $ activePlayerName
|
|
activePlayerState . playerIncompleteQuests <>= [q]
|
|
broadcast $ printf "%s drew %s from the quest deck." name (q ^. questTitle)
|
|
|
|
drawNamedQuestType :: GameAction
|
|
drawNamedQuestType = do
|
|
let qtypes = [Piety, Warfare, Skullduggery, Arcana, Commerce]
|
|
qtype <- solicitChoice "Choose a quest type:" $ map (show &&& id) qtypes
|
|
name <- use $ activePlayerName
|
|
broadcast $ name ++ " chose the " ++ (show qtype) ++ " quest type."
|
|
flip fix [] $ \loop discards -> do
|
|
mq <- drawQuest'
|
|
case mq of
|
|
Nothing -> do
|
|
broadcast $ "There were no " ++ show qtype ++ " quests available."
|
|
gameQuestDiscard <>= discards
|
|
return ()
|
|
Just q -> do
|
|
broadcast $ printf "%s drew the %s quest %s."
|
|
name (show (q ^. questType)) (q ^. questTitle)
|
|
if (q ^. questType /= qtype)
|
|
then loop (q : discards)
|
|
else do
|
|
gameQuestDiscard <>= discards
|
|
activePlayerState . playerIncompleteQuests <>= [q]
|
|
return ()
|
|
|
|
distributeQuests :: GameAction
|
|
distributeQuests = do
|
|
let distribute remQuests = unless (null remQuests) $ do
|
|
let choices = zipWith (\i q -> (q ^. questTitle, (i, q))) [0..] remQuests
|
|
(i, quest) <- solicitChoice "Choose a quest:" $ nubOn fst choices
|
|
activePlayerState . playerIncompleteQuests <>= [quest]
|
|
name <- use $ activePlayerName
|
|
broadcast $ printf "%s chose the %s quest." name (quest ^. questTitle)
|
|
p' <- nextPlayerID <$> use gameActivePlayer <*> get
|
|
withActivePlayer p' $ distribute (deleteAt i remQuests)
|
|
distribute =<< catMaybes <$> (flip replicateM drawQuest' =<< use gameNumberOfPlayers)
|
|
|
|
completeQuest :: GameAction
|
|
completeQuest = do
|
|
quests <- zip [0..] <$> use (activePlayerState . playerIncompleteQuests)
|
|
let mandatoryQuests = filter ((== Mandatory) . view questType . snd) quests
|
|
let availQuests = if null mandatoryQuests then quests else mandatoryQuests
|
|
let choices = map (\(i,q) -> (q ^. questTitle, (i, q))) availQuests
|
|
(i, quest) <- solicitChoice "Complete one quest:" $ nubOn fst choices
|
|
activePlayerState . playerIncompleteQuests %= deleteAt i
|
|
completeQuest' quest
|
|
|
|
chooseAndCompleteQuest :: GameAction -> GameAction
|
|
chooseAndCompleteQuest bonusAction = do
|
|
quest <- chooseQuest'
|
|
join $ solicitChoice "Complete this quest immediately?" $
|
|
[ ("Yes", do { incompleteQuests <- use (activePlayerState . playerIncompleteQuests)
|
|
; guard . and $ map ((/= Mandatory) . view questType) incompleteQuests
|
|
; completeQuest' quest
|
|
; bonusAction
|
|
})
|
|
, ("No", activePlayerState . playerIncompleteQuests <>= [quest])
|
|
]
|
|
|
|
-- common to both completeQuest and chooseAndCompleteQuest
|
|
completeQuest' :: Quest -> GameAction
|
|
completeQuest' quest = do
|
|
tavern <- use $ activePlayerState . playerTavern
|
|
guard $ ((quest ^. questCost) `MS.isSubsetOf` tavern)
|
|
activePlayerState . playerTavern %= (`MS.difference` (quest ^. questCost))
|
|
name <- use activePlayerName
|
|
broadcast $ name ++ " completed the \"" ++ (quest^.questTitle) ++ "\" "
|
|
++ map toLower (show (quest^.questType)) ++ " quest."
|
|
delimitAction $ do
|
|
quest ^. questReward
|
|
forM_ (filter (\(s,c,a) -> c == Immediately) (quest^.questPlotActions)) $ \(s,c,a) -> a
|
|
if (quest ^. questType == Mandatory)
|
|
then gameIntrigueDiscard <>= [mandatoryQuestCard quest]
|
|
else if null (quest ^. questPlotActions)
|
|
then activePlayerState . playerCompletedQuests <>= [quest]
|
|
else activePlayerState . playerPlotQuests . intAtNext .= Just quest
|
|
doPlotAction (== CompletesQuest (quest^.questType))
|
|
|
|
discardUncompletedQuest :: GameAction
|
|
discardUncompletedQuest = do
|
|
choices <- zipWith (\i q -> (q ^. questTitle, (i, q))) [0..] <$>
|
|
use (activePlayerState . playerIncompleteQuests)
|
|
(i, quest) <- solicitChoice "Choose a quest to discard:" $ nubOn fst choices
|
|
activePlayerState . playerIncompleteQuests %= deleteAt i
|
|
gameQuestDiscard <>= [quest]
|
|
name <- use $ activePlayerName
|
|
broadcast $ name ++ " discarded the " ++ (quest ^. questTitle) ++ " quest."
|
|
|
|
chooseFromBuildersHall :: Waterdeep (Int, BuildingState)
|
|
chooseFromBuildersHall = do
|
|
choices <- IM.foldrWithKey accumBuildings [] <$> use gameBuildersHall
|
|
(i, b) <- solicitChoice "Choose a building from Builder's Hall:" $ nubOn fst choices
|
|
gameBuildersHall . intAt i .= Nothing
|
|
return (i, b)
|
|
where
|
|
accumBuildings i b a = (label b, (i, b)) : a
|
|
label b = printf "%s (%d Gold, %d Points)"
|
|
(b ^. building . buildingTitle)
|
|
(b ^. building . buildingCost)
|
|
(b ^. buildingAccumulation)
|
|
|
|
buyBuilding :: GameAction
|
|
buyBuilding = do
|
|
(i, b) <- chooseFromBuildersHall
|
|
p <- use gameActivePlayer
|
|
name <- use activePlayerName
|
|
returnResources (b ^. building . buildingCost) [Gold]
|
|
let newState = b & buildingOwner .~ p & buildingAccumulation .~ 0
|
|
gameBuildings . intAtNext .= Just newState
|
|
broadcast $ name ++ " built the " ++ (b ^. building . buildingTitle) ++ "."
|
|
scorePoints (b ^. buildingAccumulation)
|
|
doPlotAction (== BuysBuilding)
|
|
restockBuildersHall
|
|
|
|
chooseFreeBuilding :: GameAction
|
|
chooseFreeBuilding = do
|
|
(i, b) <- chooseFromBuildersHall
|
|
p <- use gameActivePlayer
|
|
name <- use activePlayerName
|
|
let newState = b & buildingOwner .~ p & buildingAccumulation .~ 0
|
|
gameBuildings . intAtNext .= Just newState
|
|
broadcast $ name ++ " built the " ++ (b ^. building . buildingTitle) ++ " for free."
|
|
scorePoints (b ^. buildingAccumulation)
|
|
restockBuildersHall
|
|
|
|
drawFreeBuilding :: GameAction
|
|
drawFreeBuilding = do
|
|
Just b <- drawBuilding'
|
|
p <- use gameActivePlayer
|
|
gameBuildings . intAtNext .= Just (newBuildingState p b)
|
|
name <- use $ activePlayerName
|
|
broadcast $ name ++ " built the " ++ (b ^. buildingTitle) ++ " for free."
|
|
|
|
discardUnoccupiedBuilding :: GameAction
|
|
discardUnoccupiedBuilding = do
|
|
allBuildings <- IM.toAscList <$> use gameBuildings
|
|
p <- use gameActivePlayer
|
|
let ownedBuildings = filter ((== p) . view buildingOwner . snd) allBuildings
|
|
let emptyBuildings = filter (null . view buildingAgents . snd) ownedBuildings
|
|
let choices = map (\(i,b) -> (b ^. building . buildingTitle, (i, b))) emptyBuildings
|
|
(i, b) <- solicitChoice "Choose a building to discard:" $ nubOn fst choices
|
|
gameBuildings . intAt i .= Nothing
|
|
gameBuildingDiscard <>= [b ^. building]
|
|
name <- use $ activePlayerName
|
|
broadcast $ name ++ " discarded the " ++ (b ^. building . buildingTitle) ++ "."
|
|
|
|
drawIntrigue :: GameAction
|
|
drawIntrigue = do
|
|
Just ic <- drawIntrigue'
|
|
activePlayerState . playerIntrigueCards <>= [ic]
|
|
name <- use $ activePlayerName
|
|
broadcast $ name ++ " drew an Intrigue card."
|
|
|
|
playIntrigue :: GameAction
|
|
playIntrigue = do
|
|
choices <- zipWith (\i ic -> (ic ^. intrigueTitle, (i, ic))) [0..] <$>
|
|
use (activePlayerState . playerIntrigueCards)
|
|
(i, intrigue) <- solicitChoice "Play one intrigue card:" $ nubOn fst choices
|
|
activePlayerState . playerIntrigueCards %= deleteAt i
|
|
name <- use $ activePlayerName
|
|
broadcast $ name ++ " played the " ++ (intrigue ^. intrigueTitle) ++ " Intrigue card."
|
|
intrigue ^. intrigueAction
|
|
unless (intrigue ^. intrigueType == MandatoryQuest) $
|
|
gameIntrigueDiscard <>= [intrigue]
|
|
doPlotAction (== PlaysIntrigue)
|
|
return ()
|
|
|
|
returnAgent :: GameAction
|
|
returnAgent = do
|
|
w <- get
|
|
let p = w^.gameActivePlayer
|
|
let buildings = flip foldMapBuildings w $ \_ bl ->
|
|
let agents = w^.bl.buildingAgents
|
|
title = w^.bl.building.buildingTitle
|
|
in mif (p `elem` agents) [(title, bl.buildingAgents %= (\\[p]))]
|
|
join $ solicitChoice "Recall an agent from:" $ nubOn fst buildings
|
|
activePlayerState . playerAgentsInPool += 1
|
|
|
|
returnAgentFromHarbor :: GameAction
|
|
returnAgentFromHarbor = do
|
|
w <- get
|
|
let p = w ^. gameActivePlayer
|
|
let choices = flip foldMap [1,2,3] $ \i ->
|
|
let bl :: Lens' WaterdeepState BuildingState
|
|
bl = gameWaterdeepHarbor . intAt i . singular _Just
|
|
in mif (p `elem` (w^.bl.buildingAgents))
|
|
[(w^.bl.building.buildingTitle, bl.buildingAgents %= (\\[p]))]
|
|
join $ solicitChoice "Recall an agent from:" $ choices
|
|
activePlayerState . playerAgentsInPool += 1
|
|
|
|
foldMapBuildings ::
|
|
(Monoid m) => (Bool -> Lens' WaterdeepState BuildingState -> m) -> WaterdeepState -> m
|
|
foldMapBuildings f w = normal <> harbor <> hall
|
|
where
|
|
normal = flip foldMap (w ^. gameBuildings . to IM.keys) $ \i ->
|
|
f False $ gameBuildings . intAt i . singular _Just
|
|
(harborOccupied, harborUnoccupied) = flip foldMap [1,2,3] $ \i ->
|
|
let bl :: Lens' WaterdeepState BuildingState
|
|
bl = gameWaterdeepHarbor . intAt i . singular _Just
|
|
in if null (w^.bl.buildingAgents)
|
|
then (mempty, First $ Just $ f False bl)
|
|
else (f False bl, mempty)
|
|
harbor = harborOccupied <> (maybe mempty id $ getFirst harborUnoccupied)
|
|
hall = flip foldMap (w ^. gameBuildersHall . to IM.keys) $ \i ->
|
|
f True $ gameBuildersHall . intAt i . singular _Just
|
|
|
|
useBuilding :: Lens' WaterdeepState BuildingState -> Waterdeep ()
|
|
useBuilding bl = do
|
|
p <- use gameActivePlayer
|
|
owner <- use $ bl.buildingOwner
|
|
when (owner `notElem` [noPlayerID, p]) $ fst <$$> delimitAction $
|
|
withActivePlayer owner =<< use (bl.building.buildingOwnerAction)
|
|
join . use $ bl.building.buildingAction
|
|
|
|
assignAgentToBuilding :: PlayerID -> Lens' WaterdeepState BuildingState -> Waterdeep ()
|
|
assignAgentToBuilding agent bl = do
|
|
when (agent /= noPlayerID) $
|
|
gamePlayerState agent . playerAgentsInPool -= 1
|
|
bl.buildingAgents <>= [agent]
|
|
name <- use $ activePlayerName
|
|
bName <- use $ bl.building.buildingTitle
|
|
broadcast $ name ++ " assigned an agent to " ++ bName ++ "."
|
|
useBuilding bl
|
|
|
|
assignAmbassador :: GameAction
|
|
assignAmbassador = do
|
|
w <- get
|
|
let p = w^.gameActivePlayer
|
|
let opponents = (w^..gamePlayerStates.traverse.playerNumber) \\ [p]
|
|
let includesOpponent = any (`elem` opponents)
|
|
let usedOpSpace = (activePlayerState . playerCanUseOpSpace .= False)
|
|
let canUseOpSpace = w ^. activePlayerState . playerCanUseOpSpace
|
|
let buildings = flip foldMapBuildings w $ \inHall bl ->
|
|
let agents = w^.bl.buildingAgents
|
|
title = w^.bl.building.buildingTitle
|
|
in if | canUseOpSpace && includesOpponent agents ->
|
|
[(title, usedOpSpace >> assignAgentToBuilding noPlayerID bl)]
|
|
| null agents && not inHall ->
|
|
[(title, assignAgentToBuilding noPlayerID bl)]
|
|
| otherwise -> []
|
|
join $ solicitChoice "Assign an agent to:" $ nubOn fst buildings
|
|
|
|
assignAgent :: GameAction
|
|
assignAgent = do
|
|
agents <- use (activePlayerState . playerAgentsInPool)
|
|
guard (agents > 0)
|
|
w <- get
|
|
let p = w^.gameActivePlayer
|
|
let opponents = (w^..gamePlayerStates.traverse.playerNumber) \\ [p]
|
|
let includesOpponent = any (`elem` opponents)
|
|
let usedOpSpace = (activePlayerState . playerCanUseOpSpace .= False)
|
|
let canUseOpSpace = w ^. activePlayerState . playerCanUseOpSpace
|
|
let buildings = flip foldMapBuildings w $ \inHall bl ->
|
|
let agents = w^.bl.buildingAgents
|
|
title = w^.bl.building.buildingTitle
|
|
in if | canUseOpSpace && includesOpponent agents ->
|
|
[(title, usedOpSpace >> assignAgentToBuilding p bl)]
|
|
| null agents && not inHall ->
|
|
[(title, assignAgentToBuilding p bl)]
|
|
| otherwise -> []
|
|
join $ solicitChoice "Assign an agent to:" $ nubOn fst buildings
|
|
|
|
assignAgentToBuildersHall :: GameAction
|
|
assignAgentToBuildersHall = do
|
|
agents <- use (activePlayerState . playerAgentsInPool)
|
|
guard (agents > 0)
|
|
w <- get
|
|
let p = w^.gameActivePlayer
|
|
let opponents = (w^..gamePlayerStates.traverse.playerNumber) \\ [p]
|
|
let includesOpponent = any (`elem` opponents)
|
|
let usedOpSpace = (activePlayerState . playerCanUseOpSpace .= False)
|
|
let canUseOpSpace = w ^. activePlayerState . playerCanUseOpSpace
|
|
let buildings = flip foldMapBuildings w $ \inHall bl -> mif inHall $
|
|
let agents = w^.bl.buildingAgents
|
|
title = w^.bl.building.buildingTitle
|
|
in if null agents
|
|
then [(title, assignAgentToBuilding p bl)]
|
|
else if canUseOpSpace && includesOpponent agents
|
|
then [(title, usedOpSpace >> assignAgentToBuilding p bl)]
|
|
else []
|
|
join $ solicitChoice "Assign an agent to:" $ nubOn fst buildings
|
|
|
|
assignAgentToOpponentsSpace :: GameAction
|
|
assignAgentToOpponentsSpace = do
|
|
agents <- use (activePlayerState . playerAgentsInPool)
|
|
guard (agents > 0)
|
|
w <- get
|
|
let p = w^.gameActivePlayer
|
|
let opponents = (w^..gamePlayerStates.traverse.playerNumber) \\ [p]
|
|
let includesOpponent = any (`elem` opponents)
|
|
let buildings = flip foldMapBuildings w $ \inHall bl ->
|
|
mif (includesOpponent (w^.bl.buildingAgents)) $
|
|
[(w^.bl.building.buildingTitle, assignAgentToBuilding p bl)]
|
|
join $ solicitChoice "Assign an agent to:" $ nubOn fst buildings
|
|
|
|
useOpponentsSpace :: GameAction
|
|
useOpponentsSpace = do
|
|
agents <- use (activePlayerState . playerAgentsInPool)
|
|
guard (agents > 0)
|
|
w <- get
|
|
let p = w^.gameActivePlayer
|
|
let opponents = (w^..gamePlayerStates.traverse.playerNumber) \\ [p]
|
|
let includesOpponent = any (`elem` opponents)
|
|
let buildings = flip foldMapBuildings w $ \inHall bl ->
|
|
mif (includesOpponent (w^.bl.buildingAgents)) $
|
|
[(w^.bl.building.buildingTitle, useBuilding bl)]
|
|
join $ solicitChoice "Use the effect of one building:" $ nubOn fst buildings
|
|
|
|
gainLieutenant :: GameAction
|
|
gainLieutenant = do
|
|
activePlayerState . playerHasLieutenant .= True
|
|
activePlayerState . playerAgentsInPool += 1
|
|
name <- use $ activePlayerName
|
|
broadcast $ name ++ " gained the Lieutenant."
|
|
return ()
|
|
|
|
gainAmbassador :: GameAction
|
|
gainAmbassador = do
|
|
guard . not . or . toListOf (traverse . playerHasAmbassador) =<< use gamePlayerStates
|
|
gameBuildings . traverse . buildingAgents %= (\\ [noPlayerID])
|
|
gameWaterdeepHarbor . traverse . buildingAgents %= (\\ [noPlayerID])
|
|
gameBuildersHall . traverse . buildingAgents %= (\\ [noPlayerID])
|
|
activePlayerState . playerHasAmbassador .= True
|
|
name <- use $ activePlayerName
|
|
broadcast $ name ++ " gained the Ambassador."
|
|
|
|
assignMandatoryQuest :: Quest -> GameAction
|
|
assignMandatoryQuest quest = do
|
|
name <- use $ activePlayerName
|
|
opponents <- getOpponents
|
|
let choices = map (view playerName &&& (view playerNumber &&& view playerName)) opponents
|
|
(opID, opName) <- solicitChoice "Choose one opponent:" choices
|
|
gamePlayerState opID . playerIncompleteQuests <>= [quest]
|
|
broadcast $ name ++ " assigned " ++ opName ++ " a mandatory quest."
|
|
|
|
becomeFirstPlayer :: GameAction
|
|
becomeFirstPlayer = do
|
|
assign gameFirstPlayer =<< use gameActivePlayer
|
|
name <- use $ activePlayerName
|
|
broadcast $ name ++ " is now the first player."
|
|
|
|
enableAssignOnceToOpponentsSpace :: GameAction
|
|
enableAssignOnceToOpponentsSpace = do
|
|
activePlayerState . playerCanUseOpSpace .= True
|
|
|
|
forOneOpponent :: Waterdeep a -> Waterdeep a
|
|
forOneOpponent a1 = do
|
|
opponents <- getOpponents
|
|
let choices = map (view playerName &&& (view playerNumber &&& view playerName)) opponents
|
|
(opID, opName) <- solicitChoice "Choose one opponent:" choices
|
|
name <- use $ activePlayerName
|
|
broadcast $ name ++ " chose " ++ opName ++ "."
|
|
withActivePlayer opID (fst <$> delimitAction a1)
|
|
|
|
forEachOpponent :: Monoid a => Waterdeep a -> Waterdeep a
|
|
forEachOpponent a1 = do
|
|
opponents <- getOpponents
|
|
mconcat <$$> forM opponents $ \op ->
|
|
withActivePlayer (op ^. playerNumber) (fst <$> delimitAction a1)
|
|
|
|
forCurrentPlayer :: Waterdeep a -> Waterdeep (a, ResourceSet)
|
|
forCurrentPlayer a1 = do
|
|
p <- use gameCurrentPlayer
|
|
withActivePlayer p $ delimitAction a1
|
|
|
|
shufflePiles :: Lens WaterdeepState WaterdeepState [a] [a]
|
|
-> Lens WaterdeepState WaterdeepState [a] [a]
|
|
-> Waterdeep ()
|
|
shufflePiles deck discard = do
|
|
xs <- (++) <$> use discard <*> use deck
|
|
xs' <- shuffleM xs
|
|
deck .= xs'
|
|
discard .= []
|
|
return ()
|
|
|
|
shuffleQuests = shufflePiles gameQuestDeck gameQuestDiscard
|
|
shuffleIntrigues = shufflePiles gameIntrigueDeck gameIntrigueDiscard
|
|
shuffleBuildings = shufflePiles gameBuildingDeck gameBuildingDiscard
|
|
|
|
draw :: Lens WaterdeepState WaterdeepState [a] [a]
|
|
-> Lens WaterdeepState WaterdeepState [a] [a]
|
|
-> Waterdeep (Maybe a)
|
|
draw deck discard = do
|
|
out <- null <$> use deck
|
|
when out $ shufflePiles deck discard
|
|
listToMaybe <$> (deck %%= splitAt 1)
|
|
|
|
drawQuest' = draw gameQuestDeck gameQuestDiscard
|
|
drawIntrigue' = draw gameIntrigueDeck gameIntrigueDiscard
|
|
drawBuilding' = draw gameBuildingDeck gameBuildingDiscard
|
|
|
|
restockCliffwatchInn :: Waterdeep ()
|
|
restockCliffwatchInn = do
|
|
num <- length <$> use gameCliffwatchInn
|
|
when (num < 4) $ do
|
|
mq <- drawQuest'
|
|
case mq of
|
|
Nothing -> return ()
|
|
Just q -> do
|
|
gameCliffwatchInn <>= [q]
|
|
broadcast $ "Added " ++ (q ^. questTitle) ++ " to Cliffwatch Inn."
|
|
restockCliffwatchInn
|
|
|
|
restockBuildersHall :: Waterdeep ()
|
|
restockBuildersHall = do
|
|
hall <- use gameBuildersHall
|
|
let missing = getFirst $ flip F.foldMap [1,2,3] $ \i -> First $
|
|
if i `IM.member` hall then Nothing else Just i
|
|
case missing of
|
|
Nothing -> return ()
|
|
Just i -> do
|
|
mb <- drawBuilding'
|
|
case mb of
|
|
Nothing -> return ()
|
|
Just b -> do
|
|
gameBuildersHall . intAt i .= Just (newBuildingState noPlayerID b)
|
|
broadcast $ "Added " ++ (b ^. buildingTitle) ++ " to Builder's Hall."
|
|
restockBuildersHall
|
|
|
|
getOpponents :: Waterdeep [PlayerState]
|
|
getOpponents = do
|
|
p <- use gameActivePlayer
|
|
filter (\p1 -> (p1 ^. playerNumber) /= p) . toListOf traverse <$> use gamePlayerStates
|
|
|
|
forEachBuilding :: GameAction -> GameAction
|
|
forEachBuilding m = do
|
|
advancedBuildings <- countOf traverse isAdvanced <$> use gameBuildings
|
|
replicateM_ advancedBuildings m
|
|
where
|
|
isAdvanced b = b ^. buildingOwner /= noPlayerID
|
|
|
|
forEachControlledBuilding :: GameAction -> GameAction
|
|
forEachControlledBuilding m = do
|
|
p <- use gameActivePlayer
|
|
controlledBuildings <- countOf traverse (`isControlledBy` p) <$> use gameBuildings
|
|
replicateM_ controlledBuildings m
|
|
where
|
|
b `isControlledBy` p = b ^. buildingOwner == p
|
|
|
|
doPlotAction :: (PlotQualifier -> Bool) -> Waterdeep ()
|
|
doPlotAction f = void $ delimitAction $ do
|
|
walkIntMap (activePlayerState . playerPlotQuests) $ \k l -> do
|
|
-- Assumption: Plot actions within the same card do not affect each other.
|
|
actions <- use $ l . singular _Just . questPlotActions
|
|
actions' <- forM actions $ \case
|
|
(Active, cond@(Whenever qual), action) | f qual -> do
|
|
filterChoices action
|
|
return (Active,cond,action)
|
|
-- TODO: Let player choose when (or if) to use once-per-round actions
|
|
(Active, cond@(OncePerRoundWhen qual), action) | f qual -> do
|
|
filterChoices action >>= \case
|
|
Nothing -> return (Active,cond,action) -- action can't be completed now
|
|
Just _ -> return (Used,cond,action) -- action is used up for this round
|
|
x -> return x
|
|
l . singular _Just . questPlotActions .= actions'
|
|
|
|
mandatoryQuestCard :: Quest -> IntrigueCard
|
|
mandatoryQuestCard q =
|
|
IntrigueCard
|
|
{ _intrigueTitle = (q ^. questTitle)
|
|
, _intrigueType = MandatoryQuest
|
|
, _intrigueAction = assignMandatoryQuest q
|
|
, _intrigueQuote = (q ^. questQuote)
|
|
}
|