add a new prompt to send informational broadcasts to all players

This commit is contained in:
Jesse D. McDonald 2014-04-13 22:39:32 -05:00
parent 2810c0ea4b
commit 4a23b5f419
3 changed files with 158 additions and 92 deletions

View File

@ -1,5 +1,6 @@
{-# LANGUAGE GADTs #-} {-# LANGUAGE GADTs #-}
{-# LANGUAGE RankNTypes #-} {-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TemplateHaskell #-}
import Waterdeep.Types import Waterdeep.Types
import Waterdeep.Monad import Waterdeep.Monad
@ -11,6 +12,7 @@ import Control.Monad
import Control.Monad.Prompt import Control.Monad.Prompt
import Control.Monad.Random import Control.Monad.Random
import Control.Monad.State import Control.Monad.State
import Data.IORef
import Data.List import Data.List
import Data.Monoid import Data.Monoid
import Text.Printf import Text.Printf
@ -21,6 +23,13 @@ import System.Random.Shuffle
import qualified Data.IntMap as IM import qualified Data.IntMap as IM
import qualified Data.Map as M import qualified Data.Map as M
data DisplayState =
DisplayState
{ _gameState :: WaterdeepState
, _gameMessages :: [String]
}
makeLenses ''DisplayState
f1 = Faction "Jesters" Blue f1 = Faction "Jesters" Blue
l1 = Lord "Prince Henry XXX" "" (QuestBonus [Skullduggery, Commerce] 4) l1 = Lord "Prince Henry XXX" "" (QuestBonus [Skullduggery, Commerce] 4)
p1 = ("Harry", f1, l1) p1 = ("Harry", f1, l1)
@ -99,10 +108,11 @@ printWaterdeep w = do
clearScreen clearScreen
putStrLn ("Players: " ++ intercalate ", " (map (^. playerName) ps)) putStrLn ("Players: " ++ intercalate ", " (map (^. playerName) ps))
putStrLn ("First Player: " ++ putStrLn ("First Player: " ++
(w ^. gamePlayers . singular (ix (w ^. gameFirstPlayer)) . playerName)) (w ^. gamePlayer (w ^. gameFirstPlayer) . playerName))
putStrLn ("Current Round: " ++ show (w ^. gameCurrentRound)) putStrLn ("Current Round: " ++ show (w ^. gameCurrentRound))
putStrLn ("Current Player: " ++ let p = w ^. gameCurrentPlayer
(w ^. gamePlayers . singular (ix (w ^. gameCurrentPlayer)) . playerName)) when (p /= noPlayerID) $
putStrLn ("Current Player: " ++ (w ^. gamePlayer p . playerName))
putStrLn "" putStrLn ""
putStrLn "Cliffwatch Inn:" putStrLn "Cliffwatch Inn:"
forM_ (w ^. gameCliffwatchInn) $ \q -> do forM_ (w ^. gameCliffwatchInn) $ \q -> do
@ -116,30 +126,42 @@ printWaterdeep w = do
(b ^. buildingAccumulation) (b ^. buildingAccumulation)
putStrLn "" putStrLn ""
forM_ ps $ \p -> do forM_ ps $ \p -> do
putStrLn ((p ^. playerName) ++ "'s State:") putStrLn ((p ^. playerName) ++ "'s State (" ++
show (p ^. playerFaction . factionColor) ++ "):")
putStrLn (" Score: " ++ show (p ^. playerScore)) putStrLn (" Score: " ++ show (p ^. playerScore))
putStrLn (" Tavern: " ++ (p ^. playerTavern . to showTavern)) putStrLn (" Tavern: " ++ (p ^. playerTavern . to showTavern))
putStrLn (" Color: " ++ show (p ^. playerFaction . factionColor))
putStrLn (" Lord: " ++ (p ^. playerLord . lordName))
putStrLn (" Faction: " ++ (p ^. playerFaction . factionName))
putStrLn (" Incomplete: " ++ show (p ^.. playerIncompleteQuests . traverse . questTitle)) putStrLn (" Incomplete: " ++ show (p ^.. playerIncompleteQuests . traverse . questTitle))
putStrLn (" Complete: " ++ show (p ^.. playerCompletedQuests . traverse . questTitle)) putStrLn (" Complete: " ++ show (p ^.. playerCompletedQuests . traverse . questTitle))
putStrLn (" Intrigues: " ++ show (p ^.. playerIntrigueCards . traverse . intrigueTitle)) putStrLn (" Intrigues: " ++ show (p ^.. playerIntrigueCards . traverse . intrigueTitle))
putStrLn (" Agent Pool: " ++ show (p ^. playerAgentsInPool)) putStrLn (" Agent Pool: " ++ show (p ^. playerAgentsInPool))
putStrLn "" putStrLn ""
menuPrompt :: WaterdeepPrompt a -> IO a drawState :: IORef DisplayState -> IO ()
menuPrompt prm@(NotifyState w) = printWaterdeep w drawState ref = do
menuPrompt prm@(SolicitChoice w p t cs) = do ds <- readIORef ref
printWaterdeep (ds ^. gameState)
forM_ (ds ^. gameMessages) putStrLn
unless (null (ds ^. gameMessages)) $ putStrLn ""
menuPrompt :: IORef DisplayState -> IO () -> WaterdeepPrompt a -> IO a
menuPrompt ref redraw (NotifyState w) = do
modifyIORef ref (gameState .~ w)
redraw
menuPrompt ref redraw (Broadcast s) = do
modifyIORef ref (gameMessages %~ ((s:) . take 2))
redraw
menuPrompt ref redraw prm@(SolicitChoice p t cs) = do
let menuSize = length cs let menuSize = length cs
let menuLine n t = putStrLn (show n ++ ") " ++ t) let menuLine n t = putStrLn (show n ++ ") " ++ t)
let blankLine = putStrLn "" let blankLine = putStrLn ""
w <- view gameState <$> readIORef ref
fix $ \doMenu -> do
let redo = do let redo = do
printWaterdeep w redraw
putStrLn ("ERROR: Expected a number between 1 and " ++ show menuSize ++ ".") putStrLn ("ERROR: Expected a number between 1 and " ++ show menuSize ++ ".")
blankLine blankLine
menuPrompt prm doMenu
putStrLn t putStrLn t
forM_ (zip [1..] cs) $ \(i, (c, _)) -> menuLine i c forM_ (zip [1..] cs) $ \(i, (c, _)) -> menuLine i c
@ -155,8 +177,8 @@ menuPrompt prm@(SolicitChoice w p t cs) = do
main :: IO () main :: IO ()
main = do main = do
g <- getSplit w0 <- newGame [p1, p2] [q1, q2] [] [b1, b2, b3, b4, b5] <$> getSplit
let w = newGame [p1, p2] [q1, q2] [] [b1, b2, b3, b4, b5] g ref <- newIORef (DisplayState { _gameState = w0, _gameMessages = [] })
let gamePlayerName n = w ^. gamePlayer n . playerName let gamePlayerName n = w0 ^. gamePlayer n . playerName
(winners, w') <- runWaterdeepM menuPrompt waterdeepGame w (winners, w') <- runWaterdeepM (menuPrompt ref (drawState ref)) waterdeepGame w0
putStrLn ("Winner(s): " ++ intercalate ", " (winners ^.. traverse . to gamePlayerName)) putStrLn ("Winner(s): " ++ intercalate ", " (winners ^.. traverse . to gamePlayerName))

View File

@ -18,6 +18,7 @@ import Data.List
import Data.Maybe import Data.Maybe
import Data.Monoid import Data.Monoid
import System.Random.Shuffle import System.Random.Shuffle
import Text.Printf
import Waterdeep.Types import Waterdeep.Types
import Waterdeep.Monad import Waterdeep.Monad
@ -115,7 +116,7 @@ waterdeepGame = do
ReturnResource [Wizard] ReturnResource [Wizard]
<> ReturnResource [Cleric, Fighter, Gold] <> ReturnResource [Cleric, Fighter, Gold]
<> ReturnResource [Cleric, Fighter] <> ReturnResource [Cleric, Fighter]
<> ReturnResource [Cleric] <> (ReturnResource [Cleric] `OrElse` TakeResource [Cleric])
-- performAction 2 $ ChooseQuest -- performAction 2 $ ChooseQuest
scoreFinalPoints scoreFinalPoints
notifyState notifyState
@ -151,34 +152,46 @@ data ContWD a where
-- If result is False, state remains unchanged. -- If result is False, state remains unchanged.
filterChoices :: Waterdeep Bool -> Waterdeep Bool filterChoices :: Waterdeep Bool -> Waterdeep Bool
filterChoices m = do filterChoices m = do
mw' <- filteredChoice =<< (runWaterdeepC Done Cont m <$> get) mw' <- filteredChoice [] =<< (runWaterdeepC Done Cont m <$> get)
case mw' of case mw' of
Just w' -> put w' >> return True Just (ps,w') -> forM_ ps forwardPrompt >> put w' >> return True
Nothing -> return False Nothing -> return False
where where
tryChoice :: ContWD Bool -> Bool tryChoice :: ContWD Bool -> Bool
tryChoice (Done a) = fst a tryChoice (Done a) = fst a
tryChoice (Cont (NotifyState w) cont) = tryChoice $ cont () tryChoice (Cont (NotifyState w) cont) = tryChoice $ cont ()
tryChoice (Cont (SolicitChoice w p t cs) cont) = tryChoice (Cont (Broadcast s) cont) = tryChoice $ cont ()
tryChoice (Cont (SolicitChoice p t cs) cont) =
or $ map (tryChoice . cont . snd) cs or $ map (tryChoice . cont . snd) cs
filteredChoice :: ContWD Bool -> Waterdeep (Maybe WaterdeepState) filteredChoice :: [WaterdeepPrompt ()] -> ContWD Bool
filteredChoice (Done (a,w)) = if a then return (Just w) else return Nothing -> Waterdeep (Maybe ([WaterdeepPrompt ()], WaterdeepState))
filteredChoice (Cont (NotifyState w) cont) = filteredChoice $ cont () filteredChoice ps (Done (a,w)) = if a then return (Just (ps,w)) else return Nothing
filteredChoice (Cont (SolicitChoice w p t cs) cont) = do filteredChoice ps (Cont p@(NotifyState w) cont) = filteredChoice (ps++[p]) $ cont ()
filteredChoice ps (Cont p@(Broadcast s) cont) = filteredChoice (ps++[p]) $ cont ()
filteredChoice ps (Cont (SolicitChoice p t cs) cont) = do
let cs' = filter (tryChoice . cont . snd) cs let cs' = filter (tryChoice . cont . snd) cs
if null cs' if null cs'
then return Nothing then return Nothing
else filteredChoice =<< (cont <$> (put w >> solicitChoice p t cs')) else do
forM_ ps forwardPrompt
filteredChoice [] =<< (cont <$> solicitChoice p t cs')
forwardPrompt :: WaterdeepPrompt () -> Waterdeep ()
forwardPrompt (NotifyState w) = get >>= \w0 -> put w >> notifyState >> put w0
forwardPrompt (Broadcast s) = broadcast' s
performAction :: PlayerID -> GameAction -> Waterdeep Bool performAction :: PlayerID -> GameAction -> Waterdeep Bool
performAction p a = case a of performAction p a = do
name <- use $ gamePlayer p . playerName
case a of
NoAction -> return True NoAction -> return True
ScorePoints n -> do ScorePoints n -> do
gamePlayer p . playerScore += n gamePlayer p . playerScore += n
broadcast $ name ++ " scored " ++ show n ++ "points."
return True return True
TakeResource rs -> do TakeResource rs -> do
r <- solicitChoice p "Take one item:" $ map (show &&& id) rs r <- solicitChoice p "Take one item:" $ map (show &&& id) rs
gamePlayer p . playerTavern %= flip (M.unionWith (+)) (M.fromList [(r, 1)]) gamePlayer p . playerTavern %= flip (M.unionWith (+)) (M.fromList [(r, 1)])
broadcast $ name ++ " received one " ++ show r ++ "."
return True return True
ReturnResource rs -> do ReturnResource rs -> do
tavern <- use $ gamePlayer p . playerTavern tavern <- use $ gamePlayer p . playerTavern
@ -189,6 +202,7 @@ performAction p a = case a of
r <- solicitChoice p "Return one item:" $ map (show &&& id) rs' r <- solicitChoice p "Return one item:" $ map (show &&& id) rs'
let removeOne x = if x > 1 then Just (x-1) else Nothing let removeOne x = if x > 1 then Just (x-1) else Nothing
gamePlayer p . playerTavern %= M.update removeOne r gamePlayer p . playerTavern %= M.update removeOne r
broadcast $ name ++ " returned one " ++ show r ++ " to the supply."
return True return True
ChooseQuest -> do ChooseQuest -> do
qs <- use gameCliffwatchInn qs <- use gameCliffwatchInn
@ -199,6 +213,7 @@ performAction p a = case a of
q <- solicitChoice p "Please choose a quest:" $ zip names qs q <- solicitChoice p "Please choose a quest:" $ zip names qs
gameCliffwatchInn %= (\\ [q]) gameCliffwatchInn %= (\\ [q])
gamePlayer p . playerIncompleteQuests %= (++ [q]) gamePlayer p . playerIncompleteQuests %= (++ [q])
broadcast $ name ++ " chose " ++ (q ^. questTitle) ++ " from Cliffwatch Inn."
restockCliffwatchInn restockCliffwatchInn
return True return True
DrawQuest -> do DrawQuest -> do
@ -207,6 +222,7 @@ performAction p a = case a of
Nothing -> return False Nothing -> return False
Just q -> do Just q -> do
gamePlayer p . playerIncompleteQuests %= (++[q]) gamePlayer p . playerIncompleteQuests %= (++[q])
broadcast $ name ++ " drew " ++ (q ^. questTitle) ++ " from the quest deck."
return True return True
DrawIntrigue -> do DrawIntrigue -> do
mi <- drawIntrigue mi <- drawIntrigue
@ -217,6 +233,11 @@ performAction p a = case a of
return True return True
a1 `Then` a2 -> do a1 `Then` a2 -> do
(&&) <$> performAction p a1 <*> performAction p a2 (&&) <$> performAction p a1 <*> performAction p a2
a1 `OrElse` a2 -> do
r1 <- filterChoices (performAction p a1)
case r1 of
True -> return True
False -> performAction p a2
_ -> return False _ -> return False
scoreFinalPoints :: Waterdeep () scoreFinalPoints :: Waterdeep ()
@ -227,14 +248,23 @@ scoreFinalPoints = do
forM_ rs $ \(r, n) -> do forM_ rs $ \(r, n) -> do
let pts = case r of { Gold -> n `div` 2; _ -> n; } let pts = case r of { Gold -> n `div` 2; _ -> n; }
gamePlayer (p ^. playerNumber) . playerScore += pts gamePlayer (p ^. playerNumber) . playerScore += pts
broadcast $ printf "%s scored %d points for having %d %ss."
(p ^. playerName) pts n (show r)
case p ^. playerLord . lordBonus of case p ^. playerLord . lordBonus of
QuestBonus types pts -> do QuestBonus types pts -> do
let (cqs, apqs, upqs) = (p ^. playerCompletedQuests, p ^. playerActivePlotQuests, p ^. playerUsedPlotQuests) let (cqs, apqs, upqs) = (p ^. playerCompletedQuests, p ^. playerActivePlotQuests, p ^. playerUsedPlotQuests)
let matches = length $ filter ((`elem` types) . (view questType)) (cqs ++ apqs ++ upqs) let matches = length $ filter ((`elem` types) . (view questType)) (cqs ++ apqs ++ upqs)
gamePlayer (p ^. playerNumber) . playerScore += matches * pts gamePlayer (p ^. playerNumber) . playerScore += matches * pts
when (matches > 0) $
broadcast $ printf "%s scored %d points for completing %d %s and/or %s quests."
(p ^. playerName) (matches * pts) matches
(show (types !! 0)) (show (types !! 1))
BuildingBonus pts -> do BuildingBonus pts -> do
ownedBuildings <- length <$> filter ((== (p ^. playerNumber)) . (view buildingOwner)) <$> use gameAdvancedBuildings ownedBuildings <- length <$> filter ((== (p ^. playerNumber)) . (view buildingOwner)) <$> use gameAdvancedBuildings
gamePlayer (p ^. playerNumber) . playerScore += ownedBuildings * pts gamePlayer (p ^. playerNumber) . playerScore += ownedBuildings * pts
when (ownedBuildings > 0) $
broadcast $ printf "%s scored %d points for controlling %d buildings."
(p ^. playerName) (ownedBuildings * pts) ownedBuildings
determineWinners :: Waterdeep [PlayerID] determineWinners :: Waterdeep [PlayerID]
determineWinners = do determineWinners = do
@ -279,6 +309,7 @@ restockCliffwatchInn = do
Nothing -> return () Nothing -> return ()
Just q -> do Just q -> do
gameCliffwatchInn %= (++ [q]) gameCliffwatchInn %= (++ [q])
broadcast $ "Added " ++ (q ^. questTitle) ++ " to Cliffwatch Inn."
restockCliffwatchInn restockCliffwatchInn
restockBuildersHall :: Waterdeep () restockBuildersHall :: Waterdeep ()
@ -290,4 +321,5 @@ restockBuildersHall = do
Nothing -> return () Nothing -> return ()
Just b -> do Just b -> do
gameBuildersHall %= (++ [newBuildingState noPlayerID b]) gameBuildersHall %= (++ [newBuildingState noPlayerID b])
broadcast $ "Added " ++ (b ^. buildingTitle) ++ " to Builder's Hall."
restockBuildersHall restockBuildersHall

View File

@ -7,7 +7,10 @@ module Waterdeep.Monad
( WaterdeepPrompt(..) ( WaterdeepPrompt(..)
, Waterdeep , Waterdeep
, notifyState , notifyState
, broadcast
, broadcast'
, solicitChoice , solicitChoice
, solicitChoice'
, runWaterdeepC , runWaterdeepC
, runWaterdeep , runWaterdeep
, runWaterdeepM , runWaterdeepM
@ -27,7 +30,8 @@ import System.Random as R
data WaterdeepPrompt a where data WaterdeepPrompt a where
NotifyState :: WaterdeepState -> WaterdeepPrompt () NotifyState :: WaterdeepState -> WaterdeepPrompt ()
SolicitChoice :: WaterdeepState -> PlayerID -> String -> [(String, a)] -> WaterdeepPrompt a Broadcast :: String -> WaterdeepPrompt ()
SolicitChoice :: PlayerID -> String -> [(String, a)] -> WaterdeepPrompt a
newtype Waterdeep a = Waterdeep { runWaterdeep' :: StateT WaterdeepState (Prompt WaterdeepPrompt) a } newtype Waterdeep a = Waterdeep { runWaterdeep' :: StateT WaterdeepState (Prompt WaterdeepPrompt) a }
@ -57,13 +61,21 @@ instance MonadSplit StdGen Waterdeep where
notifyState :: Waterdeep () notifyState :: Waterdeep ()
notifyState = get >>= Waterdeep . lift . prompt . NotifyState notifyState = get >>= Waterdeep . lift . prompt . NotifyState
broadcast' :: String -> Waterdeep ()
broadcast' s = Waterdeep (lift (prompt (Broadcast s)))
broadcast :: String -> Waterdeep ()
broadcast s = notifyState >> broadcast' s
solicitChoice' :: PlayerID -> String -> [(String, a)] -> Waterdeep a
solicitChoice' _ _ [] = error "There must be at least one option to choose."
solicitChoice' _ _ [c] = return (snd c) -- only one option, don't bother asking
solicitChoice' p t cs = Waterdeep (lift (prompt (SolicitChoice p t cs)))
solicitChoice :: PlayerID -> String -> [(String, a)] -> Waterdeep a solicitChoice :: PlayerID -> String -> [(String, a)] -> Waterdeep a
solicitChoice _ _ [] = error "There must be at least one option to choose." solicitChoice _ _ [] = error "There must be at least one option to choose."
solicitChoice _ _ [c] = return (snd c) -- only one option, don't bother asking solicitChoice _ _ [c] = return (snd c) -- only one option, don't bother asking
solicitChoice p t cs = do solicitChoice p t cs = notifyState >> Waterdeep (lift (prompt (SolicitChoice p t cs)))
notifyState
w <- get
Waterdeep $ lift $ prompt (SolicitChoice w p t cs)
runWaterdeepC :: ((r, WaterdeepState) -> b) -> (forall a. WaterdeepPrompt a -> (a -> b) -> b) -> Waterdeep r -> WaterdeepState -> b runWaterdeepC :: ((r, WaterdeepState) -> b) -> (forall a. WaterdeepPrompt a -> (a -> b) -> b) -> Waterdeep r -> WaterdeepState -> b
runWaterdeepC r p (Waterdeep m) s = runPromptC r p $ runStateT m s runWaterdeepC r p (Waterdeep m) s = runPromptC r p $ runStateT m s