Nomyx-Web-0.7.2: src/Nomyx/Web/Game.hs
{-# LANGUAGE GADTs #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ExtendedDefaultRules #-}
{-# LANGUAGE DoAndIfThenElse #-}
module Nomyx.Web.Game where
import Prelude hiding (div)
import qualified Prelude
import Control.Monad
import Control.Monad.State
import Control.Concurrent.STM
import Control.Applicative
import Data.Monoid
import Data.Maybe
import Data.String
import Data.List
import Data.List.Split
import Data.Text (Text)
import Data.Time
import Text.Printf
import System.Locale
import Language.Nomyx
import Text.Blaze.Html5 (Html, div, (!), p, table, thead, td, tr, h2, h3, h4, h5, pre, toValue, br, toHtml, a, img)
import Text.Blaze.Html5.Attributes as A (src, title, width, style, id, onclick, disabled, placeholder, class_, href)
import Text.Reform.Blaze.String (label, textarea, inputSubmit, inputCheckboxes, inputHidden)
import qualified Text.Reform.Blaze.String as RB
import Text.Reform.Happstack (environment)
import Text.Reform ((<++), (++>), viewForm, eitherForm)
import Text.Reform.Blaze.Common (setAttr)
import Happstack.Server (Response, Method(..), seeOther, toResponse, methodM, ok)
import Web.Routes.RouteT (showURL, liftRouteT)
import Safe
import qualified Nomyx.Web.Help as Help
import Nomyx.Web.Common as NWC
import Nomyx.Core.Types as T
import Nomyx.Core.Mail
import Nomyx.Core.Engine
import Nomyx.Core.Session as S
import Nomyx.Core.Profile as Profile
default (Integer, Double, Data.Text.Text)
viewGameInfo :: GameInfo -> (Maybe PlayerNumber) -> Maybe LastRule -> Bool -> RoutedNomyxServer Html
viewGameInfo gi mpn mlr isAdmin = do
let g = getGame gi
(pi, isGameAdmin, playAs, pn) <- case mpn of
Just pn -> do
let pi = Profile.getPlayerInfo g pn
let isGameAdmin = isAdmin || maybe False (== pn) (_ownedBy gi)
let playAs = maybe Nothing _playAs pi
return (pi, isGameAdmin, playAs, pn)
Nothing -> return (Nothing, False, Nothing, 0)
rf <- viewRuleForm mlr (isJust pi) isAdmin (_gameName g)
vios <- viewIOs (fromMaybe pn playAs) g
vgd <- viewGameDesc g playAs isGameAdmin
ok $ table $ do
tr $ td $ div ! A.id "gameDesc" $ vgd
tr $ td $ div ! A.id "rules" $ viewAllRules g
tr $ td $ div ! A.id "ios" $ vios
tr $ td $ div ! A.id "newRule" $ rf
tr $ td $ div ! A.id "details" $ viewDetails pn g
viewGameDesc :: Game -> Maybe PlayerNumber -> Bool -> RoutedNomyxServer Html
viewGameDesc g playAs gameAdmin = do
vp <- viewPlayers (_players g) (_gameName g) gameAdmin
ok $ do
p $ do
h3 $ fromString $ "Viewing game: " ++ _gameName g
when (isJust playAs) $ h4 $ fromString $ "You are playing as player " ++ (show $ fromJust playAs)
p $ pre $ fromString (_desc $ _gameDesc g)
p $ h4 $ "This game is discussed in the " >> a "Agora" ! (A.href $ toValue (_agora $ _gameDesc g)) >> "."
p $ h4 "Players in game:"
when gameAdmin "(click on a player name to \"play as\" this player)"
vp
p $ viewVictory g
viewPlayers :: [PlayerInfo] -> GameName -> Bool -> RoutedNomyxServer Html
viewPlayers pis gn gameAdmin = do
let plChunks = transpose $ chunksOf 15 (sort pis)
vp <- mapM mkRow plChunks
ok $ table $ mconcat vp
where mkRow :: [PlayerInfo] -> RoutedNomyxServer Html
mkRow row = do
r <- mapM (viewPlayer gn gameAdmin) row
ok $ tr $ mconcat r
viewPlayer :: GameName -> Bool -> PlayerInfo -> RoutedNomyxServer Html
viewPlayer gn gameAdmin (PlayerInfo pn name _) = do
pad <- playAsDiv pn gn
let inf = fromString name
ok $ do
pad
td $ div ! A.id "playerNumber" $ fromString $ show pn
td $ div ! A.id "playerName" $ if gameAdmin
then a inf ! (href $ toValue $ "#openModalPlayAs" ++ show pn)
else inf
playAsDiv :: PlayerNumber -> GameName -> RoutedNomyxServer Html
playAsDiv pn gn = do
submitPlayAs <- showURL $ SubmitPlayAs gn
main <- showURL MainPage
paf <- lift $ viewForm "user" $ playAsForm $ Just pn
ok $ do
let cancel = a "Cancel" ! (href $ toValue main) ! A.class_ "modalButton"
div ! A.id (toValue $ "openModalPlayAs" ++ show pn) ! A.class_ "modalWindow" $ do
div $ do
h2 $ fromString $ "When you are in a private game, you can play instead of any players. This allows you to test " ++
"the result of their actions."
blazeForm (h2 (fromString $ "Play as player " ++ show pn ++ "? ") >> paf) submitPlayAs
br
cancel
playAsForm :: Maybe PlayerNumber -> NomyxForm String
playAsForm pn = inputHidden (show pn)
viewVictory :: Game -> Html
viewVictory g = do
let vs = _playerName <$> mapMaybe (Profile.getPlayerInfo g) (getVictorious g)
case vs of
[] -> br
a:[] -> h3 $ fromString $ "Player " ++ show a ++ " won the game!"
a:bs -> h3 $ fromString $ "Players " ++ intercalate ", " bs ++ " and " ++ a ++ " won the game!"
viewAllRules :: Game -> Html
viewAllRules g = do
titleWithHelpIcon (h3 "Rules") Help.rules
viewRules (activeRules g) "Active rules" True g >> br
viewRules (pendingRules g) "Pending rules" True g >> br
viewRules (rejectedRules g) "Suppressed rules" False g >> br
viewRules :: [RuleInfo] -> String -> Bool -> Game -> Html
viewRules nrs title visible g = showHideTitle title visible (null nrs) (h4 $ toHtml (title ++ ":") ) $ table ! class_ "table" $ do
thead $ do
td ! class_ "td" $ "#"
td ! class_ "td" $ "Name"
td ! class_ "td" $ "Description"
td ! class_ "td" $ "Code of the rule"
forM_ nrs (viewRule g)
viewRule :: Game -> RuleInfo -> Html
viewRule g ri = tr $ do
let pl = fromMaybe ("Player " ++ (show $ _rProposedBy ri)) (_playerName <$> (Profile.getPlayerInfo g $ _rProposedBy ri))
td ! class_ "td" $ p (fromString . show $ _rNumber ri) ! A.id "ruleNumber"
td ! class_ "td" $ do
div ! A.id "ruleName" $ (fromString $ _rName ri)
br
div ! A.id "proposedBy" $ (fromString $ "by " ++ (if _rProposedBy ri == 0 then "System" else pl))
td ! class_ "td" $ fromString $ _rDescription ri
td ! class_ "td" $ viewRuleFunc ri
viewRuleFunc :: RuleInfo -> Html
viewRuleFunc ri = do
let code = lines $ _rRuleCode ri
let codeCutLines = 7
let ref = "openModalCode" ++ (show $ _rNumber ri)
let assessedBy = case _rAssessedBy ri of
Nothing -> "not assessed"
Just 0 -> "the system"
Just a -> "rule " ++ show a
div ! A.id "showCodeLink" $ a ! (href $ toValue $ "#" ++ ref) $ "show more..." >> br
div ! A.id "codeDiv" $ displayCode $ unlines $ take codeCutLines code
div $ when (length code >= codeCutLines) $ fromString "(...)"
div ! A.id (toValue ref) ! class_ "modalDialog" $ do
div $ do
p "Code of the rule:"
a ! href "#close" ! title "Close" ! class_ "close" $ "X"
div ! A.id "modalCode" $ do
displayCode $ unlines code
br
case _rStatus ri of
Active -> (fromString $ "This rule was activated by " ++ assessedBy ++ ".") ! A.id "assessedBy"
Reject -> (fromString $ "This rule was deleted by " ++ assessedBy ++ ".") ! A.id "assessedBy"
Pending -> return ()
viewDetails :: PlayerNumber -> Game -> Html
viewDetails pn g = showHideTitle "Details" False False (h3 "Details") $ do
p $ titleWithHelpIcon (h4 "Variables:") Help.variables
viewVars (_variables g)
p $ titleWithHelpIcon (h4 "Events:") Help.events
viewEvents g
p $ h4 "Log:"
viewLogs (_logs g) pn
viewEvents :: Game -> Html
viewEvents g = table ! class_ "table" $ do
thead $ do
td ! class_ "td" $ "Event Number"
td ! class_ "td" $ "By Rule"
td ! class_ "td" $ "Event"
mapM_ (viewEvent g) (sort $ _events g)
viewEvent :: Game -> EventInfo -> Html
viewEvent g ei@(EventInfo eventNumber ruleNumber _ _ status _) = if status == SActive then disp else disp ! style "background:gray;" where
disp = tr $ do
td ! class_ "td" $ fromString . show $ eventNumber
td ! class_ "td" $ fromString . show $ ruleNumber
td ! class_ "td" $ fromString . show $ getEventFields ei g
viewIOs :: PlayerNumber -> Game -> RoutedNomyxServer Html
viewIOs pn g = do
vios <- mapM (viewIORule pn g) (sort $ _rules g)
ok $ do
titleWithHelpIcon (h3 "Inputs/Ouputs") Help.inputsOutputs
a "" ! A.id (toValue inputAnchor)
mconcat vios
viewIORule :: PlayerNumber -> Game -> RuleInfo -> RoutedNomyxServer Html
viewIORule pn g r = do
vior <- viewIORuleM pn (_rNumber r) g
ok $ when (isJust vior) $ div ! A.id "IORule" $ do
div ! A.id "IORuleTitle" $ h4 $ fromString $ "Rule #" ++ (show $ _rNumber r) ++ " \"" ++ _rName r ++ "\": "
fromJust vior
viewIORuleM :: PlayerNumber -> RuleNumber -> Game -> RoutedNomyxServer (Maybe Html)
viewIORuleM pn rn g = do
vir <- viewInputsRule pn rn (_events g) g
let vor = viewOutputsRule pn rn g
return $ if isJust vir || isJust vor then Just $ do
when (isJust vir) $ fromJust vir
when (isJust vor) $ fromJust vor
else Nothing
viewInputsRule :: PlayerNumber -> RuleNumber -> [EventInfo] -> Game -> RoutedNomyxServer (Maybe Html)
viewInputsRule pn rn ehs g = do
let filtered = filter (\e -> _ruleNumber e == rn) ehs
mis <- mapM (viewInput pn g) $ sort filtered
let is = catMaybes mis
case is of
[] -> return Nothing
i -> return $ Just $ table $ mconcat i
viewOutputsRule :: PlayerNumber -> RuleNumber -> Game -> Maybe Html
viewOutputsRule pn rn g = do
let filtered = filter (\o -> _oRuleNumber o == rn && _oStatus o == SActive) (_outputs g)
let myos = filter (isPn pn) (sort filtered)
case myos of
[] -> Nothing
os -> Just $ sequence_ $ mapMaybe (viewOutput g) os
isPn pn (Output _ _ (Just mypn) _ SActive) = mypn == pn
isPn _ (Output _ _ Nothing _ SActive) = True
isPn _ _ = False
viewInput :: PlayerNumber -> Game -> EventInfo -> RoutedNomyxServer (Maybe Html)
viewInput me g ei@(EventInfo en _ _ _ SActive _) = do
ds <- mapMaybeM (viewInput' me (_gameName g) en) (getEventFields ei g)
return $ if null ds
then Nothing
else Just $ sequence_ ds
viewInput _ _ _ = return Nothing
viewInput' :: PlayerNumber -> GameName -> EventNumber -> (FieldAddress, SomeField) -> RoutedNomyxServer (Maybe Html)
viewInput' me gn en (fa, ev@(SomeField (Input pn title _))) | me == pn = do
lf <- lift $ viewForm "user" $ inputForm ev
link <- showURL (DoInput en fa (fromJust $ getFormField ev) gn)
return $ Just $ tr $ td $ do
fromString title
fromString " "
blazeForm lf link ! A.id "InputForm"
viewInput' _ _ _ _ = return Nothing
viewOutput :: Game -> Output -> Maybe Html
viewOutput g o = if (s /= "") then Just (pre $ fromString s >> br) else Nothing where
s = (evalOutput g o)
viewVars :: [Var] -> Html
viewVars vs = table ! class_ "table" $ do
thead $ do
td ! class_ "td" $ "Rule number"
td ! class_ "td" $ "Name"
td ! class_ "td" $ "Value"
mapM_ viewVar vs
viewVar :: Var -> Html
viewVar (Var vRuleNumber vName vData) = tr $ do
td ! class_ "td" $ fromString . show $ vRuleNumber
td ! class_ "td" $ fromString . show $ vName
td ! class_ "td" $ fromString . show $ vData
newRuleForm :: Maybe SubmitRule -> Bool -> NomyxForm (SubmitRule, Maybe String, Maybe String)
newRuleForm (Just sr) isAdmin = newRuleForm' sr isAdmin
newRuleForm Nothing isAdmin = newRuleForm' (SubmitRule "" "" "") isAdmin
newRuleForm' :: SubmitRule -> Bool -> NomyxForm (SubmitRule, Maybe String, Maybe String)
newRuleForm' (SubmitRule name desc code) isAdmin =
(,,) <$> (SubmitRule <$> label "Name: " ++> RB.inputText name `setAttr` class_ "ruleName"
<*> (label " Short description: " ++> (RB.inputText desc `setAttr` class_ "ruleDescr") <++ RB.br)
<*> label " Code: " ++> textarea 80 15 code `setAttr` class_ "ruleCode" `setAttr` placeholder "Enter here your rule")
<*> inputSubmit "Check"
<*> if isAdmin then inputSubmit "Admin submit" else pure Nothing
viewRuleForm :: Maybe LastRule -> Bool -> Bool -> GameName -> RoutedNomyxServer Html
viewRuleForm mlr inGame isAdmin gn = do
link <- showURL (NewRule gn)
lf <- lift $ viewForm "user" (newRuleForm (fst <$> mlr) isAdmin)
ok $ do
a "" ! A.id (toValue ruleFormAnchor)
titleWithHelpIcon (h3 "Propose a new rule:") Help.code
if inGame then do
blazeForm lf link
let msg = snd <$> mlr
when (isJust msg) $ pre $ fromString $ fromJust msg
else lf ! disabled ""
newRule :: GameName -> TVar Session -> RoutedNomyxServer Response
newRule gn ts = toResponse <$> do
methodM POST
s@(T.Session sh _ _) <- liftIO $ readTVarIO ts
admin <- getIsAdmin ts
r <- liftRouteT $ eitherForm environment "user" (newRuleForm Nothing admin)
link <- showURL MainPage
pn <- fromJust <$> getPlayerNumber ts
case r of
Right (sr, Nothing, Nothing) -> do
webCommand ts $ submitRule sr pn gn sh
liftIO $ do
s' <- readTVarIO ts --TODO clean this
gn <- getPlayersGame pn s
gn' <- getPlayersGame pn s'
let rs = _rules $ _game $ _loggedGame $ fromJustNote "newRule" gn
let rs' = _rules $ _game $ _loggedGame $ fromJustNote "newRule" gn'
when (length rs' > length rs) $ sendMailsNewRule s' sr pn
Right (sr, Just _, Nothing) -> webCommand ts $ checkRule sr pn sh
Right (sr, Nothing, Just _) -> webCommand ts $ adminSubmitRule sr pn gn sh
Right (_, Just _, Just _) -> error "Impossible new rule form result"
(Left _) -> liftIO $ putStrLn "cannot retrieve form data"
seeOther (link `appendAnchor` ruleFormAnchor) $ "Redirecting..."
viewLogs :: [Log] -> PlayerNumber -> Html
viewLogs log pn = do
let ls = filter (\o -> (_lPlayerNumber o == Just pn) || (isNothing $ _lPlayerNumber o)) log
table $ mapM_ viewLog (reverse ls)
viewLog :: Log -> Html
viewLog (Log _ t s) = tr $ do
td $ fromString $ formatTime defaultTimeLocale "%Y/%m/%d_%H:%M" t
td $ p $ fromString s
-- | a form result has been sent
newInput :: EventNumber -> FieldAddress -> FormField -> GameName -> TVar Session -> RoutedNomyxServer Response
newInput en fa ft gn ts = toResponse <$> do
pn <- fromJust <$> getPlayerNumber ts
link <- showURL MainPage
methodM POST
r <- liftRouteT $ eitherForm environment "user" (inputForm' ft)
case r of
(Right c) -> webCommand ts $ S.inputResult pn en fa ft c gn
(Left _) -> liftIO $ putStrLn "cannot retrieve form data"
seeOther (link `appendAnchor` inputAnchor) "Redirecting..."
newPlayAs :: GameName -> TVar Session -> RoutedNomyxServer Response
newPlayAs gn ts = toResponse <$> do
methodM POST
p <- liftRouteT $ eitherForm environment "user" $ playAsForm Nothing
pn <- fromJust <$> getPlayerNumber ts
case p of
Right playAs -> do
webCommand ts $ S.playAs (read playAs) pn gn
link <- showURL MainPage
seeOther link "Redirecting..."
(Left errorForm) -> do
settingsLink <- showURL $ SubmitPlayAs gn
mainPage "Admin settings" "Admin settings" (blazeForm errorForm settingsLink) False True
-- TODO: merge SomeField and FormField...
inputForm :: SomeField -> NomyxForm InputData
inputForm (SomeField (Input _ _ (Radio choices))) = RadioData <$> NWC.inputRadio' (zip [0..] (snd <$> choices)) (== 0) <++ label (" " :: String)
inputForm (SomeField (Input _ _ Text)) = TextData <$> RB.inputText "" <++ label (" " :: String)
inputForm (SomeField (Input _ _ TextArea)) = TextAreaData <$> textarea 50 5 "" <++ label (" " :: String)
inputForm (SomeField (Input _ _ Button)) = pure ButtonData
inputForm (SomeField (Input _ _ (Checkbox choices))) = CheckboxData <$> inputCheckboxes (zip [0..] (snd <$> choices)) (const False) <++ label (" " :: String)
inputForm _ = error "Not an input form"
inputForm' :: FormField -> NomyxForm InputData
inputForm' (RadioField _ _ choices) = RadioData <$> NWC.inputRadio' choices (== 0) <++ label (" " :: String)
inputForm' (TextField _ _) = TextData <$> RB.inputText "" <++ label (" " :: String)
inputForm' (TextAreaField _ _) = TextAreaData <$> textarea 50 5 "" <++ label (" " :: String)
inputForm' (ButtonField _ _) = pure ButtonData
inputForm' (CheckboxField _ _ choices) = CheckboxData <$> inputCheckboxes choices (const False) <++ label (" " :: String)
showHideTitle :: String -> Bool -> Bool -> Html -> Html -> Html
showHideTitle id visible empty title rest = do
div ! onclick (fromString $ printf "toggle_visibility('%sBody', '%sShow')" id id) $ table ! width "100%" $ tr $ do
td $ title ! width "80%"
td ! style "text-align:right;" $ h5 (if visible then "[Click to hide]" else "[Click to show]") ! A.id (fromString $ printf "%sShow" id) ! width "20%"
div ! A.id (fromString $ printf "%sBody" id) ! style (fromString $ "display:" ++ (if visible then "block;" else "none;")) $
if empty then toHtml $ "No " ++ id else rest
joinGame :: GameName -> TVar Session -> RoutedNomyxServer Response
joinGame gn ts = do
pn <- fromJust <$> getPlayerNumber ts
webCommand ts (S.joinGame gn pn)
link <- showURL MainPage
seeOther link $ toResponse "Redirecting..."
leaveGame :: GameName -> TVar Session -> RoutedNomyxServer Response
leaveGame gn ts = do
pn <- fromJust <$> getPlayerNumber ts
webCommand ts (S.leaveGame gn pn)
link <- showURL MainPage
seeOther link $ toResponse "Redirecting..."
delGame :: GameName -> TVar Session -> RoutedNomyxServer Response
delGame gn ts = do
webCommand ts (S.delGame gn)
link <- showURL MainPage
seeOther link $ toResponse "Redirecting..."
forkGame :: GameName -> TVar Session -> RoutedNomyxServer Response
forkGame gn ts = do
pn <- fromJust <$> getPlayerNumber ts
webCommand ts $ S.forkGame gn pn
link <- showURL MainPage
seeOther link $ toResponse "Redirecting..."
viewGamePlayer :: GameName -> TVar Session -> RoutedNomyxServer Response
viewGamePlayer gn ts = do
pn <- fromJust <$> getPlayerNumber ts
webCommand ts (S.viewGamePlayer gn pn)
link <- showURL MainPage
seeOther link $ toResponse "Redirecting..."
titleWithHelpIcon :: Html -> String -> Html
titleWithHelpIcon myTitle help = table ! width "100%" $ tr $ do
td ! style "text-align:left;" $ myTitle
td ! style "text-align:right;" $ img ! src "/static/pictures/help.jpg" ! title (toValue help)