waterdeep/src/ConsoleUI.hs

153 lines
5.0 KiB
Haskell
Executable File

#! /usr/bin/runhaskell
{-# LANGUAGE GADTs #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TemplateHaskell #-}
module Main (main) where
import Control.Applicative
import Control.Monad
import Control.Monad.Prompt
import Control.Monad.Random
import Control.Monad.State
import Data.IORef
import Data.List
import Data.Monoid
import Data.Traversable (traverse)
import Lens.Family2
import Lens.Family2.Stock
import Lens.Family2.State
import Lens.Family2.TH
import System.IO
import System.Random
import System.Random.Shuffle
import Text.Printf
import Waterdeep.Actions
import Waterdeep.Buildings
import Waterdeep.Factions
import Waterdeep.Intrigues
import Waterdeep.Logic
import Waterdeep.Lords
import Waterdeep.Quests
import Waterdeep.Types
import Waterdeep.Util
import qualified Data.IntMap as IM
import qualified Data.Map as M
import qualified Data.MultiSet as MS
data DisplayState =
DisplayState
{ _gameState :: WaterdeepState
, _gameMessages :: [String]
}
makeLenses ''DisplayState
clearScreen :: IO ()
clearScreen = putStr "\o033[H\o033[2J" >> hFlush stdout
showTavern :: MS.MultiSet Resource -> String
showTavern = intercalate ", " . map showResource . MS.toAscOccurList
where showResource (r, n) = show n ++ " " ++ show r
printWaterdeep :: WaterdeepState -> IO ()
printWaterdeep w = do
let playerStates = map snd $ IM.toAscList $ w ^. gamePlayerStates
clearScreen
printf "Round %d; First Player: %s; Current Player: %s\n"
(w ^. gameCurrentRound)
(w ^. gamePlayerState (w ^. gameFirstPlayer) . playerName)
(w ^. gamePlayerState (w ^. gameCurrentPlayer) . playerName)
putStrLn ""
putStrLn "Cliffwatch Inn:"
forM_ (w ^. gameCliffwatchInn) $ \q -> do
putStrLn (" " ++ (q ^. questTitle))
putStrLn ""
putStrLn "Builder's Hall:"
forM_ (w ^.. gameBuildersHall . traverse) $ \b -> do
printf " %s (%d gold -> %d points)\n"
(b ^. building . buildingTitle)
(b ^. building . buildingCost)
(b ^. buildingAccumulation)
putStrLn ""
forM_ playerStates $ \p -> do
printf "%s's State (%s; %d points; %d agents):\n" (p ^. playerName)
(show (p ^. playerFaction . factionColor))
(p ^. playerScore)
(p ^. playerAgentsInPool)
putStrLn (" Tavern: " ++ (p ^. playerTavern . to showTavern))
putStrLn (" Incomplete: " ++ showItems (p ^.. playerIncompleteQuests . traverse . questTitle))
putStrLn (" Complete: " ++ showItems (p ^.. playerCompletedQuests . traverse . questTitle))
putStrLn (" Plot Quests: " ++ showItems (p ^.. playerPlotQuests . traverse . questTitle))
putStrLn (" Intrigues: " ++ showItems (p ^.. playerIntrigueCards . traverse . intrigueTitle))
putStrLn ""
showItems :: [String] -> String
showItems ss = intercalate ", " groups
where
groups = map withCount $ group $ sort ss
withCount [x] = x
withCount xs@(x:_) = x ++ " (x" ++ show (length xs) ++ ")"
drawState :: IORef DisplayState -> IO ()
drawState ref = do
ds <- readIORef ref
printWaterdeep (ds ^. gameState)
mapM_ putStrLn $ take 4 $ ds ^. gameMessages
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:))
redraw
menuPrompt ref redraw prm@(SolicitChoice p t cs) = do
let menuSize = length cs
let blankLine = putStrLn ""
w <- view gameState <$> readIORef ref
fix $ \doMenu -> do
putStrLn t
printMenu $ zipWith (\i c -> show i ++ ") " ++ fst c) [1..] cs
putStr (w ^. gamePlayerState p . playerName)
putStr "> " >> hFlush stdout
response <- getLine
--response <- show <$> getRandomR (1, menuSize)
case reads response of
(ix,""):_ | ix >= 1 && ix <= menuSize ->
blankLine >> return (snd $ cs !! (ix-1))
_ -> do
redraw
putStrLn ("ERROR: Expected a number between 1 and " ++ show menuSize ++ ".")
blankLine
doMenu
printMenu :: [String] -> IO ()
printMenu cs = do
let n = length cs
let h = (n `div` 3) `max` 5
let cw = (maximum $ map length cs)
let cs' = map (take cw . (++ (repeat ' '))) cs
let (c1, cs'') = splitAt h cs'
let (c2, c3) = splitAt h cs''
let rows = zipWith3 (\a b c -> a ++ " " ++ b ++ " " ++ c)
c1 (c2 ++ repeat "") (c3 ++ repeat "")
mapM_ putStrLn rows
main :: IO ()
main = do
players <- zip3 <$> pure ["Ludd", "Nudd"]
<*> shuffleM defaultFactions
<*> shuffleM defaultLords
rndgen <- getSplit
let w0 = newGame players defaultQuestDeck defaultIntrigueDeck defaultBuildingDeck rndgen
ref <- newIORef (DisplayState { _gameState = w0, _gameMessages = [] })
runWaterdeepM (menuPrompt ref (drawState ref)) waterdeepGame w0
putStrLn "--- GAME LOG ---"
mapM_ putStrLn . reverse . view gameMessages =<< readIORef ref
return ()