packages feed

hanabi-dealer-0.15.1.1: Game/Hanabi/Client.hs

{-# LANGUAGE DeriveGeneric, RecordWildCards, OverloadedStrings, ExtendedDefaultRules, MultiParamTypeClasses, CPP #-}
module Game.Hanabi.Client(client,
#if !defined ghcjs_HOST_OS && !defined IOS
      clientApp,
#endif
      Game.Hanabi.Msg.Options(..), Game.Hanabi.Msg.defaultOptions, mkDS) where

import Game.Hanabi hiding (main, rule)
import qualified Game.Hanabi(rule)
import Game.Hanabi.Msg

import           Data.Aeson   hiding (Success)
import           GHC.Generics hiding (K1, from)
import           Data.Bool
import           Data.Char(isSpace, isLower, toLower)
import qualified Data.Map as M
import qualified Data.IntMap as IM
import Data.Maybe(fromJust, isNothing, isJust)
import Data.List(sort, sortBy, intersperse, transpose, isPrefixOf)
import Data.Function(on)

import           Miso         hiding (Fail, on)
import           Miso.String  (MisoString)
import qualified Miso.String  as S

import Miso.Subscription.History(getCurrentURI)
import Network.URI   -- maybe this can conflict #define URI

import Control.Monad.IO.Class(liftIO)
import System.IO(hPutStrLn, stderr)

import System.Random

import Control.Concurrent


#ifdef DEBUG
import Debug.Trace
#endif

#ifdef ghcjs_HOST_OS
import Game.Hanabi.FFI

client :: Game.Hanabi.Msg.Options -> IO ()
client options = clientJSM options
#else

{-
When building with GHC,
1. miso has to be built with --flags="jsaddle" option;
2. the resulting executable does not work with Konqueror---use Firefox. (Cf. https://github.com/aveltras/arohi-skeleton/issues/1)
-}

# ifdef IOS
import Language.Javascript.JSaddle.WKWebView as JSaddle

client :: Game.Hanabi.Msg.Options -> IO ()
client options = JSaddle.run $ clientJSM options
# else
import Language.Javascript.JSaddle.WebSockets
import Network.Wai.Handler.Warp
import Network.Wai
import Network.WebSockets(defaultConnectionOptions)

{- This doesn't work.
client :: Game.Hanabi.Msg.Options -> IO ()
client options = runSettings (setPort 8080 (setTimeout 3600 defaultSettings)) $ clientApp options    -- Maybe the port number should be taken from options, and it should be correctly set.
clientApp :: Game.Hanabi.Msg.Options -> Application
clientApp options request respond = do
    app <- jsmToApp $ clientJSM options
    app request respond
-}

client :: Game.Hanabi.Msg.Options -> IO ()
client options = runSettings (setPort 8080 (setTimeout 3600 defaultSettings)) =<< clientApp options    -- Maybe the port number should be taken from options, and it should be correctly set.
clientApp :: Game.Hanabi.Msg.Options -> IO Application
clientApp = jsmToApp . clientJSM

jsmToApp :: JSM () -> IO Application
jsmToApp f = jsaddleOr defaultConnectionOptions (f >> syncPoint) jsaddleApp
# endif
#endif

-- Miso's implementation of WebSockets uses global IORef.
-- https://github.com/dmjio/miso/blob/master/frontend-src/Miso/Subscription/WebSocket.hs

clientJSM :: Game.Hanabi.Msg.Options -> JSM ()
clientJSM options = do thisURI <- getCurrentURI
                       let query = parseURIQuery thisURI
                           wsScheme | uriScheme thisURI == "https:" = "wss:"
                                    | otherwise                     = "ws:"
                           wsURI = URL $ S.pack $ wsScheme ++ maybe uri (\_ -> "//localhost:8720") (lookup "localhost" query)
                           defStr = maybe "via WebSocket" id $ lookup "strategy" query
                           langStr = maybe "en" id $ lookup "lang" query
                           lang | "ja" `isPrefixOf` map toLower langStr = Ja
                                | otherwise                             = En
                       mvStr <- liftIO newMVarStrategy
                       liftIO $ hPutStrLn stderr "Preloading algorithms.... This should not take long on the client Javascript. Please contact the author if it does."
                       constrAssoc <- liftIO $ mapM sequenceA [ (name, crs $ lazy options) | (name, Just crs) <- strategies options]
                       liftIO $ hPutStrLn stderr "Done."
                       startApp App{
    model  = Model{tboxval = Message "available", players = [S.pack defStr], from = Just 0, rule = defaultRule{numMulticolors=replicate 5 1}, received = [CreateGame], shownHistory = defaultShownHistory, showVerbosity = False, verbosity = verbose,
                   language = lang, contrast = Colorful, play = True, localStrategy = mvStr, local = False, initialDeck = [], lastMoves = [], preset = False, forceLocal = False, numRepeats = 1, scores = []},
    update = updateModel constrAssoc,
    view   = appView strNames $ version options,
#ifdef ghcjs_HOST_OS
    subs   = [ websocketSub wsURI protocols HandleWebSocket, windowBottomSub ViewMore Id ],
#else
# ifdef ALL
    subs   = [ websocketSub wsURI protocols HandleWebSocket ],
# else
    subs   = [],
# endif
#endif
    events = defaultEvents,
    initialAction = Id,
    -- initialAction = SendMessage $ Message "available", -- Seemingly sending as initialAction does not work, even if connect is executed before send.
    mountPoint = Nothing}
  where strNames = "via WebSocket" : [ S.pack name | (name, _) <- strategies options ]
#ifdef WSURI
uri = WSURI
#else
-- uri = URL "//133.54.228.39:8720"
uri = "//localhost:8720"
#endif
protocols = Protocols []

parseURIQuery :: URI -> [(String,String)]
parseURIQuery = parseQ . drop 1 . uriQuery
parseQ str
  = case span (/='&') str of
      (tk,[])   -> [parseField tk]
      (tk,_:dr) -> parseField tk : parseQ dr
parseField str
  = case span (/='=') str of
      (tk,dr) -> (decodeString tk, decodeString $ drop 1 dr)
decodeString = map decoS . unEscapeString
decoS '+' = ' '
decoS c   = c


{- At last, I chose to hide the history by default.
lenHistory = 400
-- A better approach might be
-- 1. to send the history to a separate frame after the endgame
-- 2. to memoize renderMsg
-- Also, `observe' should not send to the current player.
-}
updateModel :: [(String, (IO (DynamicStrategy IO)))] -> Action -> Model -> Effect Action Model
updateModel _ (HandleWebSocket (WebSocketMessage (Message m))) model
  | not $ local model -- ignore the message while playing locally. (There might be a better option, but at least any confusion should be avoided.)
  = noEff model{ received = {- take lenHistory $ -} sortRecent $ suppressCG $ decodeMsg m : received model }
updateModel _ (SendMessage msg@(Message str)) model = model{shownHistory=defaultShownHistory, showVerbosity=False} <# -- connect uri protocols >>
                                                if forceLocal model || local model
                                                then case reads $ S.unpack str of
                                                       [(m,str)] -> liftIO $ do
                                                         putMVar (mvMov $ localStrategy model) m
                                                         msg <- takeMVar (mvMsg $ localStrategy model)
                                                         return $ ProcMsg msg
                                                       _         | str == "available" -> return Id  -- Just ignore when trying to send "available" but it is local yet.
                                                                 | otherwise -> return $ ProcMsg $ Str $ S.unpack str ++ " could not parse as a Move."
                                                else send msg >> return Id
updateModel _ (SendMove mov)    model = model{shownHistory=defaultShownHistory, showVerbosity=False} <# -- connect uri protocols >>
                                                if forceLocal model || local model
                                                then liftIO $ do
                                                         putMVar (mvMov $ localStrategy model) mov
                                                         msg <- takeMVar (mvMsg $ localStrategy model)
                                                         return $ ProcMsg msg
                                                else send (Message $ S.pack $ show mov) >> return Id
updateModel _ (UpdateTBoxVal m) model = noEff model{ tboxval = Message m }
updateModel _ (From mbn)        model = noEff model{from = mbn}
updateModel _ IncreasePlayers   model = noEff model{players = take 9 $ head (players model) : players model}
updateModel _ DecreasePlayers   model = noEff model{players = case players model of
                                                     _n:ns@(_:_:_) -> ns
                                                     _n:ns@(_:_) | play model -> ns
                                                     ns            -> ns
                                                 }
updateModel _ (UpdatePlayer ix pl) model = noEff model{players = snd $ replaceNth ix pl $ players model}
updateModel _ (UpdateRule r)    model = noEff model{rule=makeRuleValid r}
updateModel _ (UpdateVerbosity v) model = noEff model{verbosity = v}
updateModel _ (UpdateDeck ds)   model = noEff $ case reads $ S.unpack ds of [(d, rs)] | all isSpace rs -> model{initialDeck = d}
                                                                            _         -> model
updateModel _ (UpdateNumRepeats ds) model = noEff $ case reads $ S.unpack ds of [(d, rs)] | all isSpace rs -> model{numRepeats = d}
                                                                                _         -> model
updateModel _ ViewMore          model = noEff model{shownHistory = shownHistory model + historyUnit}
updateModel _ ToggleVerbosity   model = noEff model{showVerbosity = not $ showVerbosity model}
updateModel _ ToggleLanguage    model = noEff model{language = if language model == En then Ja else En}
updateModel _ ToggleContrast    model = noEff model{contrast = if contrast model == Colorful then BW else Colorful}
updateModel _ TogglePlay        model | play model && length (players model) < 2 = noEff model{play = False, players = head (players model) : players model}
                                      | otherwise                                = noEff model{play = not $ play model}
updateModel _ TogglePreset     model = noEff model{preset = not $ preset model}
updateModel _ ToggleForcePlayingLocally     model = noEff model{forceLocal = not $ forceLocal model}
updateModel cassoc ObserveLocally model = model <# liftIO (
  let mbplayers = [ (lookup uname cassoc, uname) | name <- reverse $ players model, let uname = S.unpack name ]
  in case dropWhile (isJust . fst) mbplayers of
       (_noth,uname):_ -> return $ ProcMsg $ Str $ "The strategy \"" ++ uname ++ "\" cannot be used in the local Javascript mode. Connect to the internet and try again."
       []              -> do
                                                   playerList <- mapM (fromJust . fst) mbplayers
                                                   gen <- newGen
                                                   let (playOrder,g) = orderPlayers (from model) gen playerList
                                                       shuffled | preset model && sort (cardBag $ rule model) == sort (initialDeck model) = initialDeck model -- sort (cardBag $ rule model) could be memoized if necessary.
                                                                | otherwise    = fst $ createDeck (rule model) g
                                                   (fs,_) <-
#ifdef DEBUG
                                                             trace ("The deck is "++show shuffled) $
#endif
                                                             startFromCards (GS (length playerList) (rule model)) [] playOrder shuffled
                                                   return $ WriteLocalResult shuffled fs
                                                )
updateModel _ (WriteLocalResult initDeck fs@(eg,sts,mvs)) model
  | numRepeats model > 1 = model{scores = newScores, received = Str "running the next game..." : gameresult, numRepeats = pred $ numRepeats model}
                                                                 <# return ObserveLocally
  | otherwise            = model{scores = newScores, received = CreateGame : gameresult}
                                                                 <# return (SendMessage $ Message "available")
  where gameresult = Scores newScores : PrettyEndGame initDeck (Just fs) : zipWith Watch (tail sts) (iterate (drop 1) $ drop 1 mvs) ++ received model
        newScores = case sts of []   -> scores model -- not sure if this is possible
                                st:_ -> egToInt st eg : scores model
updateModel cassoc PlayLocally model = model{local=True} <# liftIO (
  let mbplayers = [ (lookup uname cassoc, uname) | name <- reverse $ players model, let uname = S.unpack name ]
  in case dropWhile (isJust . fst) mbplayers of
       (_noth,uname):_ -> return $ ProcMsg $ Str $ "The strategy \"" ++ uname ++ "\" cannot be used in the local Javascript mode. Connect to the internet and try again."
       []              -> do
                                                   playerList <- mapM (fromJust . fst) mbplayers
                                                   let thePlayerList = mkDS "local strategy" (localStrategy model) : playerList
                                                   gen <- newGen
                                                   let (playOrder,g) = orderPlayers (from model) gen thePlayerList
                                                       shuffled | preset model && sort (cardBag $ rule model) == sort (initialDeck model) = initialDeck model -- sort (cardBag $ rule model) could be memoized if necessary.
                                                                | otherwise    = fst $ createDeck (rule model) g
                                                   forkIO $
#ifdef DEBUG
                                                            trace ("The deck is "++show shuffled) $
#endif
                                                            do (fs,_) <-
                                                                 startFromCardsToEnd (GS (length thePlayerList) (rule model)) [] playOrder shuffled
--                                                               putMVar (mvMsg $ localStrategy model) $ PrettyEndGame shuffled $ Just fs
--                                                               observeEndGame shuffled fs (localStrategy model)
                                                               return ()
                                                   msg <- takeMVar (mvMsg $ localStrategy model)
                                                   return $ ProcMsg msg
                                                )
updateModel _ (ProcMsg msg@(PrettyEndGame shuffled (Just fs@(eg,sts,mvs)))) model = model{local=False, received = CreateGame : Scores newScores : msg : received model, initialDeck = shuffled, lastMoves = mvs, scores = newScores} <# return (SendMessage $ Message "available")
  where newScores = case sts of []   -> scores model
                                st:_ -> egToInt st eg : scores model
updateModel _ (ProcMsg msg@(WhatsUp _ _ _))           model = noEff model{received = msg : received model}
updateModel _ (ProcMsg msg)                           model = model{received = msg : received model} <# liftIO (do
                                                   msg <- takeMVar (mvMsg $ localStrategy model)
                                                   return $ ProcMsg msg
                                                )
#ifdef DEBUG
updateModel _ (HandleWebSocket act) model = noEff model{received = Str (show act) : received model }
#endif
updateModel _ _ model = noEff model

-- Both CreateGame and PrettyAvailable have the "Create a Game" UI. Since CreateGame can work offline, sometimes CreateGame is printed before PrettyAvailable is printed. That looks redundant when online, so in such cases CreateGame is removed when PrettyAvailable is received.
suppressCG :: [Msg] -> [Msg]
suppressCG (pr@(PrettyAvailable _) : CreateGame : rest) = pr : rest
suppressCG (pr@(PrettyAvailable _) : PrettyAvailable _ : rest) = pr : rest -- this line makes the state less informative, but makes the UI more sophisticated
suppressCG xs = xs

-- sortRecent works around the situation where old msg comes later.
sortRecent :: [Msg] -> [Msg]
sortRecent ms = case span isWU1 ms of
                  ([],  _) -> ms
                  (wu1s,drs) -> let
                      sortedTups@((t,_):_) = sortBy (flip compare `on` fst) [ (turn $ publicView pv, wu1) | wu1@(WhatsUp1 pv _) <- wu1s ]
                      sortedWU1s = map snd sortedTups
                    in case drs of wu@(WhatsUp _ (pv:_) _):rest                  | turn (publicView pv)  >= t -> wu:sortedWU1s++rest
                                   eg@(PrettyEndGame _ (Just (_, st:_, _))):rest | turn (publicState st) >= t -> eg:sortedWU1s++rest
                                   _ -> sortedWU1s ++ drs
-- I guess there is a language extension for this....
isWU1 (WhatsUp1 _ _) = True
isWU1 _              = False


instance ToJSON Message
instance FromJSON Message

newtype Message = Message MisoString
  deriving (Eq, Show, Generic)

data Action
  = HandleWebSocket (WebSocket Message)
  | SendMessage Message
  | SendMove    Move
  | UpdateTBoxVal MisoString
  | From (Maybe Int)
  | IncreasePlayers
  | DecreasePlayers
  | UpdatePlayer Int MisoString
  | UpdateRule Rule
  | UpdateVerbosity Verbosity
  | ViewMore
  | ToggleVerbosity
  | ToggleLanguage
  | ToggleContrast
  | TogglePlay
  | ObserveLocally
  | WriteLocalResult [Card] (EndGame, [State], [Move])
  | PlayLocally
  | ProcMsg Msg
  | TogglePreset
  | ToggleForcePlayingLocally
  | UpdateDeck MisoString
  | UpdateNumRepeats MisoString
  | Id

data Model = Model {
    tboxval :: Message
  , players :: [MisoString] -- REVERSED list of players other than this client
  , from :: Maybe Int
  , rule :: Rule
  , received :: [Msg]
  , shownHistory :: Int
  , showVerbosity :: Bool
  , verbosity :: Verbosity
  , language :: Language
  , contrast :: Contrast
  , play :: Bool
  , localStrategy :: MVarStrategy
  , local :: Bool
  , initialDeck :: [Card]
  , lastMoves :: [Move]
  , preset :: Bool
  , forceLocal :: Bool
  , numRepeats :: Int
  , scores :: [Int]
  } deriving (Show, Eq)

data Language = En | Ja deriving (Show, Eq)
data Contrast = Colorful | BW deriving (Show, Eq)
type VLC = (Verbosity, Language, Contrast)

defaultShownHistory, historyUnit :: Int
defaultShownHistory = 10
historyUnit = 10
appView :: [MisoString] -> String -> Model -> View Action
appView strategies versionInfo mdl@Model{..} = div_ [] [
  span_ [style_ $ M.fromList [("font-size","2vmin")]] [
   input_  [ type_ "text", placeholder_ "You can also use your keyboard.", size_ "25", onInput UpdateTBoxVal, onEnter (SendMessage tboxval) ]
 , button_ [ onClick (SendMessage tboxval) ] [ text (S.pack "Send to the server") ]
-- x , span_ [style_ $ M.fromList [("font-size","10px")]] [text $ S.pack "(Use this line if you prefer the keyboard interface.)"]

 , case received of WhatsUp _ _ _     : _ -> checkLocal mdl $ span_ [emphasize contrast] [text $ strBan language]
                    Invalid           : _ -> checkLocal mdl $ span_ [emphasize contrast] [text $ strInvalid language]
                    PrettyAvailable _ : _ -> span_ [style_ $ M.fromList [("float","right"),("margin","auto 1ch")]] [text " Online server mode "]
                    CreateGame        : _ -> javascriptMode
                    _                     -> checkLocal mdl $ span_ [] []
 , span_ [style_ $ M.fromList [("float","right"),("font-weight", "bold")]] [
       button_ [ onClick ToggleLanguage, margin1ch ] [ text $ if language==En then "日本語" else "English" ]
     , button_ [ onClick ToggleContrast, margin1ch ] [ text $ if language==En then if contrast==Colorful then "Black/White" else "Colorful"
                                                                              else if contrast==Colorful then "白黒"        else "カラー"]
     ]
 , span_ [style_ $ M.fromList [("float","right"),("margin","auto 1ch")]] [
     button_ [ onClick ToggleVerbosity, id_ "verbutton" ] [ text $ if showVerbosity then "^" else "v" ]
   , label_ [for_ "verbutton"] [text $ strVerbOpt language ]
   ]
 , if showVerbosity then span_ [style_ $ M.fromList [("clear","both"), ("float","right")]] [renderVerbosity (verbosity,language)] else span_[][]
 ]

 , span_ [style_ $ M.fromList [("clear","both"), ("font-size","1.8vmin"), ("float","right")]] [text $ S.pack $ "hanabi-dealer client "++versionInfo]
-- , hr_ []
 , div_ [style_ $ M.fromList [("clear","both")]] $ take shownHistory $ map (renderMsg strategies (verbosity,language,contrast) mdl) received
 , div_ [] $ if null $ drop shownHistory received then [] else [
     hr_ []
   , input_ [ type_ "checkbox", id_ "showhist", onClick ViewMore, checked_ False] -- This should be replaced with a button.
   , label_ [for_ "showhist"] [text "Show more history"]
   ]
 ]
checkLocal mdl sp | local mdl = span_ [] [sp, javascriptMode]
                  | otherwise = sp
javascriptMode = span_ [style_ $ M.fromList [("float","right"),("margin","auto 1ch")]] [text " Offline Javascript mode "]
emphasize cont = style_ $ M.fromList $ maybeColor cont "#FF4444" [("text-align","center"),("font-weight", "bold")]
strVerbOpt En = "verbosity options"
strVerbOpt Ja = "表示オプション"
renderVerbosity :: (Verbosity, Language) -> View Action
renderVerbosity vl@(v,_) = div_ [] [
    mkChx span_ vl strBeginner (\b -> if b then verbose else v) (==verbose),
    mkChx span_ vl strExpert   (\b -> if b then quiet else v)   (==quiet),
    hr_ [],
    mkChx div_ vl strUnhintedCritical   (\b -> v{warnCritical=b})          warnCritical,
    mkChx div_ vl strUseless           (\b -> v{markUseless=b})           markUseless,
    mkChx div_ vl strPlayable          (\b -> v{markPlayable=b})          markPlayable,
    mkChx div_ vl strObviouslyUseless  (\b -> v{markObviouslyUseless=b})  markObviouslyUseless,  
    mkChx div_ vl strObviouslyPlayable (\b -> v{markObviouslyPlayable=b}) markObviouslyPlayable,
    mkChx div_ vl strChop              (\b -> v{markChops=b})             markChops,
    mkChx div_ vl strDD                (\b -> v{warnDoubleDrop=b})        warnDoubleDrop,
    mkChx div_ vl strHints             (\b -> v{markHints=b})             markHints,
    mkChx div_ vl strPos               (\b -> v{markPossibilities=b})     markPossibilities
  ]

strBeginner En = "beginner"
strBeginner Ja = "カモ"
strExpert En = "expert"
strExpert Ja = "鬼"
strUnhintedCritical En = "mark unhinted critical cards"
strUnhintedCritical Ja = "危険牌に印"
strUseless En = "mark useless cards"
strUseless Ja = "不要牌に印"
strPlayable En = "mark playable cards"
strPlayable Ja = "安牌に印"
strObviouslyUseless En = "mark useless cards without looking at the cards"
strObviouslyUseless Ja = "公然の不要牌に印"
strObviouslyPlayable En = "mark playable cards without looking at the cards"
strObviouslyPlayable Ja = "公然の安牌に印"
strChop En ="shade the chop card"
strChop Ja = "捨て牌候補に印"
strDD En = "warn possible double-dropping"
strDD Ja = "合わせ打ち警報"
strHints En = "mark hints. Just check this, because there is an unfixed misfeature."
strHints Ja = "めんどくさくてバグを放置しているのでここはチェック入れとけ"
strPos En = "mark possibilities"
strPos Ja = "過去のヒントに基づき可能な色と数を表示"

mkChx divspan (verb,lang) labelf update access = divspan [] [
    input_ [ type_ "checkbox", id_ idName, onClick (UpdateVerbosity $ update $ not $ access verb), checked_ $ access verb ],
    label_ [ for_ idName ] [text label]
  ]
     where idName = S.filter (not.isSpace) label
           label = labelf lang
onEnter :: Action -> Attribute Action
onEnter action = onKeyDown $ bool Id action . (== KeyCode 13)
{-
#ifdef AESON
prettyDecode :: MisoString -> MisoString
prettyDecode str = case decode $ S.fromMisoString str of Nothing  -> str
                                                         Just msg -> S.toMisoString $ encode $ prettyMsg verbose msg
#else
prettyDecode :: MisoString -> MisoString
prettyDecode str = case reads $ S.fromMisoString str of []  -> str
                                                        [(msg,_)] -> S.toMisoString $ prettyMsg verbose msg
#endif
render :: MisoString -> View Action
render str = case decode str' of Nothing  -> text $ S.fromMisoString str
                                 Just msg -> renderMsg verbose msg
   where str' = S.fromMisoString str
-}
decodeMsg :: MisoString -> Msg
#ifdef AESON
decodeMsg str = case decode $ S.fromMisoString str of Nothing  -> Str $ S.fromMisoString str
                                                      Just msg -> msg
#else
decodeMsg str = case reads $ S.fromMisoString str of []  -> Str $ S.fromMisoString str
                                                     [(msg,_)] -> msg
#endif
strInvalid En = "Invalid move!"
strInvalid Ja = "その手はムリ!"

maybeColor Colorful col = (("color", col):)
maybeColor BW       _   = id

renderMsg :: [MisoString] -> VLC -> Model -> Msg -> View Action
renderMsg _ _    _ (Str xs)             = div_ [] [hr_ [], pre_ [] [ text $ S.pack xs ]]
renderMsg _ (_,l,c) _ Invalid           = div_ [style_ $ M.fromList (maybeColor c "#FF4444" [("text-align","center"), ("font-weight", "bold")])] [hr_ [], text $ strInvalid l]
  -- Using red instead of black should be OK, but anyway it says "Black/White".
renderMsg _ verb _ (WhatsUp name ps ms) = renderWhatsUp  verb name ps ms
renderMsg _ verb _ (WhatsUp1     p  m)  = renderWhatsUp1 verb p m
renderMsg _ _    _ (PrettyEndGame initDeck Nothing)     = p_ [style_ $ M.fromList [("font-size", "2vw")]] [
    text $ S.pack $ prettyMbEndGame Nothing ++ "By the way, the initial deck was ",
    span_ [style_ $ M.fromList [("font-family", "monospace"), ("font-size", "1.5vw")]] [ text $ S.pack $ shows initDeck "."]
  ]
renderMsg _ verb _ (PrettyEndGame initDeck (Just tup))  = renderEndGame verb initDeck tup -- pre_ [] [ text $ S.pack $ prettyEndGame initDeck tup]
renderMsg _ vl@(_,l,_) _ (Watch st [])     = div_ [style_ $ M.fromList [("font-size", "2vmin")]] [
         hr_ [],
         renderSt vl (ithPlayerFromTheLastI18N l) st,
         hr_ []
         ]
renderMsg _ vl@(_,l,_) _ (Watch st (mv:_)) = div_ [style_ $ M.fromList [("font-size", "2vmin")]] [
         hr_ [],
         renderTrial vl (publicState st) (const "") undefined (Game.Hanabi.view st) mv,
         hr_ [],
         renderSt vl (ithPlayerFromTheLastI18N l) st,
         hr_ []
         ]
renderMsg strategies (_,lang,_) mdl (PrettyAvailable games)
  = div_ [style_ $ M.fromList [("overflow","auto"), ("font-size", "2vw")]] [
      hr_ [],
      table_ [style_ $ M.fromList [("border-style","solid"), ("clear","both"), ("float","right")]] $
           caption_ [] [text $ S.pack "Available games", button_ [onClick $ SendMessage $ Message "available", font2vw] [text $ S.pack "refresh"]] :
           tr_ [] [ th_ [solid] [text $ S.pack str] | str <- ["Game ID", "available", "total"] ] :
           map renderAvailable games,
      renderCreateGame True strategies lang mdl,
      hr_ []
    ]
renderMsg strategies (_,lang,_) mdl CreateGame = renderCreateGame False strategies lang mdl
renderMsg _ (_,lang,_) _ PlayAgain = div_ [] [
  text $ strAskAgain lang,
  button_ [onClick $ SendMessage $ Message "Yes"] [text $ strAgain lang],
  button_ [onClick $ SendMessage $ Message "No"]  [text $ strLeave lang]
  ]
renderMsg strategies opt mdl msg = renderMsg strategies opt mdl $ Str $ prettyMsg verbose msg

strAskAgain En = "Play with the same member again? Adaptive strategies (if any) may have learned your play style (to some extent)"
strAskAgain Ja = "同じメンツでまた打ちますか?学習するプレーヤーだと多少プレースタイルを学習しているかも?(10回位打たんとイマイチかもしれんが)"
strAgain    En = "Play again"
strAgain    Ja = "ぜひ!"
strLeave    En = "Leave and let them forget your play style"
strLeave    Ja = "もうええわ"

strShuffle En = "shuffle players on startup"
strShuffle Ja = "ランダムに場決め"
strPlayers En = "Players"
strPlayers Ja = "メンツ"
strRules   En = "Rules"
strRules   Ja = "ルール"
strPreset  En = "preset deck"
strPreset  Ja = "積み込み"
strTurn0   En = "Turn 0"
strTurn0   Ja = "親"
strYou     En = "You"
strYou     Ja = "あなた"

renderCreateGame :: Bool -> [MisoString] -> Language -> Model -> View Action
renderCreateGame online strategies lang mdl
    = div_ [style_ $ M.fromList [{- ("border-style","solid"), -} ("display","inline-block"), ("font-size", "2vw")]] $ [
           table_ [solid] $ caption_ [textProp "text-align" "left", textProp "margin-left" "auto"] [ -- Seemingly these styles do not work.
             text $ strPlayers lang,
             button_ [onClick IncreasePlayers, font2vw] [text $ S.pack "+"],
             button_ [onClick DecreasePlayers, font2vw] [text $ S.pack "-"],
             span_ [style_ $ M.fromList [("float","right")]] [
                 input_ [lem, type_ "radio", id_ "shuffle", onClick (From Nothing), checked_ $ from mdl == Nothing],
                 label_ [for_ "shuffle"] [text $ strShuffle lang]
                 ]
             ] :
--             table_ [solid] $
                         [ tr_ [] [td_ [solid] [x], td_ [] (if n>=0 then [input_ [lem, type_ "radio", id_ iD, onClick (From $ Just n), checked_ $ from mdl == Just n],
                                                                          label_ [for_ iD] [text $ strTurn0 lang]]
                                                                    else [])]
                         | (n,x) <- zip [if play mdl then 0 else -1 ..] $ you : reverse (zipWith (renderPlayer strategies) [0..] (players mdl))
                         , let iD = S.pack $ "radio"++show n ]
-- x                       ++ [tr_ [] [td_ [] [], td_ [] [input_ [type_ "radio", id_ "shuffle", onClick (From Nothing), checked_ $ from mdl == Nothing],
--                                                        label_ [for_ "shuffle"] [text "shuffle the player list before the game"]]]]
                     ,
--             div_ [] [text $ "Rules"],
--             div_ [] [input_ [type_ "text", onInput (UpdateRule . head . (++[rl]) . map fst . reads . S.unpack), value_ $ S.pack $ show rl, style_ $ M.fromList [("width","70%")]]],

             let mkTR list label updater access = tr_ [] [
                          td_ [] [text label],
                          td_ [] [
                             input_ $ list ++ [type_ "text", onInput (UpdateRule . updater . head . (++[access rl]) . map fst . reads . S.unpack), (if null list then value_ else placeholder_) $ S.pack $ show $ access rl]
                            ]
                        ]
                 mkTRd label updater access options = tr_ [] [
                          td_ [] [text label],
                          td_ [] [
                             dropdownLiteral options (UpdateRule . updater) (access rl)
                            ]
                        ]
                 gs = GS{numPlayers = length (players mdl) + if play mdl then 1 else 0,
                         Game.Hanabi.rule = rl}
             in
               table_ [style_ $ M.fromList [("margin","1%"),("border-style","solid")]] $ [ -- Use "1% auto" instead in order to centralize.
                   caption_ [] [text $ strRules lang],
                   mkTRd (strLives lang)     (\n -> rl{numBlackTokens=n}) numBlackTokens [1 .. 9],
                   mkTRd (strColors lang)    (\n -> rl{numColors=n})      numColors      [1 .. 6],
                   mkTRd (strProlong lang)   (\n -> rl{prolong=n, earlyQuit=earlyQuit rl || n})        prolong        [False,True],
                   mkTRd (strEarlyQuit lang) (\n -> rl{earlyQuit=n})      earlyQuit      [False,True],
--                   mkTR [] "funPlayerHand"                               (\n -> rl{funPlayerHand=n})  funPlayerHand
                   mkTRd (strHandSize lang)  (setHandSize gs) (const (handSize gs))         [1 .. 9]
                 ] ++
                 if numColors rl == 6 then [
                   mkTR [list_ "numMulticolors"] (strRainbow lang) (\n -> rl{numMulticolors=n}) numMulticolors
                 ] else [],
             datalist_ [id_ "numMulticolors"] [option_ [value_ "[1, 1, 1, 1, 1]"] [text "[1, 1, 1, 1, 1]"], option_ [value_ "[3, 2, 2, 2, 1]"] [text "[3, 2, 2, 2, 1]"]],
             span_ [] $
                      input_ [ lem, type_ "checkbox", id_ "preset", onClick TogglePreset, checked_ $ preset mdl ] :
                      label_ [for_ "preset"] [text $ strPreset lang] : -- [if preset mdl then text "preset deck" else s_ [] [text "preset deck"]] :
                      if preset mdl || not (null $ initialDeck mdl) then [input_ [type_ "text", onInput UpdateDeck, font2vw, (if preset mdl then value_ else placeholder_) $ S.pack $ show $ initialDeck mdl]] else [],
             span_ [margin1ch] $
                      input_ [ lem, type_ "checkbox", id_ "forceLocal", onClick ToggleForcePlayingLocally, checked_ $ forceLocal mdl ] :
                      label_ [for_ "forceLocal"] [text "Force playing locally"] :
                      if play mdl then [] else
                        text ", repeating " :
                        input_ [type_ "text", onInput UpdateNumRepeats, (if forceLocal mdl then value_ else placeholder_) $ S.pack $ show $ numRepeats mdl]:
                        [text " times"]
                     ,
             button_ [onClick $
                        if not online || forceLocal mdl -- && all (not . isWS . S.unpack) (players mdl)
                           then if play mdl then PlayLocally else ObserveLocally
                           else SendMessage $ Message $ S.pack $ (case from mdl of Just n  | play mdl  -> "from " ++ shows n " "
                                                                                           | otherwise -> "observe " ++ shows n " "
                                                                                   Nothing | play mdl  -> "shuffle "
                                                                                           | otherwise -> "observe ") ++ show rl ++ (if preset mdl then shows (initialDeck mdl) . (';':) else id) (concat (intersperse "," $ map S.unpack $ reverse $ players mdl)),
                      style_ $ M.fromList [("float","right"),("font-size","1.8vw")]
                     ] [text $ strButsu lang]
      ]
    where you =  span_[][
              input_ [ lem, type_ "checkbox", id_ "you", onClick TogglePlay, checked_ $ play mdl]
            , label_ [for_ "you"] [(if play mdl then span_ else s_) [] [text $ strYou lang]]
            ]
          rl = rule mdl
margin1ch = style_ $ M.fromList [("margin","auto 1ch")] -- 1 ch is the width of `0'.
font2vw   = style_ $ M.fromList [("font-size","1.8vw")]
lem       = style_ $ M.fromList [("width","1.5vw"),("height","1.5vw")]

strButsu     En = "create a game"
strButsu     Ja = "打つ"
strLives     En = "Number of lives"
strLives     Ja = "💓の数。チャイ"
strColors    En = "Number of colors"
strColors    Ja = "色数"
strProlong   En = "Continue the game after the pile is exhausted"
strProlong   Ja = "流局なし。白黒つくまでとことんやる"
strEarlyQuit En = "Quit the game when no more score is possible"
strEarlyQuit Ja = "それ以上の点が無理なら途中流局"
strHandSize  En = "Hand size"
strHandSize  Ja = "手札の数"
strRainbow   En = "Numbers of M1 .. M5. Note that they are not implemented as multicolors yet."
strRainbow   Ja = "虹色の数。いろんな色になるようには(まだ)実装していないので注意。(ただの1色)"

renderPlayer :: [MisoString] -> Int -> MisoString -> View Action
--renderPlayer _ i p = text p
renderPlayer strategies i p = dropdown strategies (UpdatePlayer i) p

dropdown :: [MisoString] -> (MisoString->Action) -> MisoString -> View Action
dropdown options action selected = select_ [onChange action, font2vw] [option_ [ value_ p, selected_ $ selected==p] [text p] | p <- options ]

dropdownLiteral :: (Read a, Show a) => [a] -> (a -> Action) -> a -> View Action
dropdownLiteral options action selected = dropdown (map (S.pack . show) options) (action . read . S.unpack) (S.pack $ show selected)

dropdownBool :: (Bool -> Action) -> Bool -> View Action
dropdownBool = dropdownLiteral [False,True]
-- but in most cases booleans should be selected by check boxes.

renderAvailable :: (Int, (Int, Int)) -> View Action
renderAvailable (gameid, (missing, total))
  = tr_ [onClick $ SendMessage $ Message $ S.pack $ "attend "++show gameid] [ td_ [solid] [text $ S.pack str] | str <- [show gameid, show missing, show total] ]
solid :: Attribute action
solid = style_ $ M.fromList [("border-style","solid")]

renderWhatsUp :: VLC -> String -> [PrivateView] -> [Move] -> View Action
renderWhatsUp verb@(_,lang,_) name views@(v:_) moves = div_ [style_ $ M.fromList [("font-size", "2vmin")]] [
--  hr_ [],
--  text $ S.pack $ strBan lang,
  hr_ [],
  renderRecentEvents verb (publicView v) (ithPlayerI18N lang "あなたの") views moves,
  hr_ [style_ $ M.fromList [("padding","0"),("margin","0")]],
--  text $ S.pack $ "Algorithm: " ++ name,
  renderPV verb v
 ]
strBan En = "Your turn."
strBan Ja = "あなたの手番"
ithPlayerI18N En _    _ i = ithPlayer undefined i
ithPlayerI18N Ja self _ i = iterate (++"下家の") self !! i
ithPlayerFromTheLastI18N En n j = ithPlayerFromTheLast n j
ithPlayerFromTheLastI18N Ja n j = iterate (++"上家の") "最後の手番の人の" !! (n-j-1)
strHand En = " hand:"
strHand Ja = "手札:"

showMove En (Hint n e) = "Tell "++map toLower (ithPlayer 0 n) ++ ' ' : either show (showRank En) e
showMove Ja (Hint n e) = ithPlayerI18N Ja "自分の" 0 n ++ either ((:[]) . showColor Ja) (showRank Ja) e ++ "を教える。"
showMove En (Drop i)   =                   "Drop the " ++ ith' i ++ " card"   -- " from the left (0-origin)"
showMove Ja (Drop i)   = {- "左から" ++ -} shows i "番目のカードを捨てる。"     -- "(0から数えて)"
showMove En (Play i)   =                   "Play the " ++ ith' i ++ " card"   -- " from the left (0-origin)"
showMove Ja (Play i)   = {- "左から" ++ -} shows i "番目のカードを打ち上げる。" --"(0から数えて)"
ith' 1 = "1st "
-- ith' n = ith n
ith' 2 = "2nd "
ith' 3 = "3rd "
ith' i = shows i "th "

renderWhatsUp1 :: VLC -> PrivateView -> Move -> View Action
renderWhatsUp1 verb@(_,_,c) v m = div_ [style_ $ M.fromList [("background-color", "#555555"),("color","#000000"),("font-size", "1.8vmin")]] [
  hr_ [],
  renderTrial verb (publicView v) (const "") undefined v m,
  hr_ [],
  renderPV verb v
 ]

renderEndGame :: VLC -> [Card] -> (EndGame, [State], [Move]) -> View Action
renderEndGame verb@(_,lang,c) initDeck (eg,sts@(st:_),mvs)
  = div_ [style_ $ M.fromList [("font-size", "2.5vmin")]] [
         hr_ [],
         renderRecentEvents verb (publicState st) (ithPlayerFromTheLastI18N lang) (map Game.Hanabi.view sts) mvs,
         hr_ [],
         h1_ [style_ $ M.fromList [("background-color", forceWhite c "#FF0000"),("color","#000000"),("font-size", "5vmax")]] [text $ S.pack $ strEndGame lang eg],
         hr_ [],
         renderSt verb (ithPlayerFromTheLastI18N lang) st,
         hr_ [],
         span_ [style_ $ M.fromList [("font-size", "2.5vmin")]] [
             text $ S.pack $ strInitialDeck lang,
             div_ [style_ $ M.fromList [("font-family", "monospace"), ("font-size", "1.5vw")]] [
                 text $ S.pack $ show initDeck
                 ],
             span_ [] (if length histories > 9 then [text $ strMoveHist lang,
                                                     div_ [style_ $ M.fromList [("font-family", "monospace"), ("font-size", "1.5vw")]] [ text $ S.pack $ tail (foldr showsMoves "." $ transpose histories) ]
                                                    ]
                                               else []),
             hr_ []
           ]
         ]
    where histories = chopEvery (numPlayers $ gameSpec $ publicState st) $ reverse mvs
          showsMoves :: [Move] -> ShowS
          showsMoves mvs rest = ",\n" ++ filter (\c -> not (isLower c || c == 'H')) (foldr shows rest mvs)
forceColor mono  BW       _col = mono
forceColor _mono Colorful col  = col
forceWhite = forceColor "#FFFFFF"
forceBlack = forceColor "#000000"

strEndGame En eg = show eg
strEndGame Ja Perfect  = "やったね! パーフェクト!"
strEndGame Ja (Soso n) = "流局。"++show n++"点。"
strEndGame Ja Failure  = "チョンボ!"
strInitialDeck En = "By the way, the initial deck was "
strInitialDeck Ja = "なお、最初の山は"
strMoveHist En = " and the move histories are "
strMoveHist Ja = "これに対し、以下の手が打たれました。"
chopEvery n xs = case splitAt n xs of ([], _) -> []
                                      (tk,dr) -> tk : chopEvery n dr
strMove En = " move: "
strMove Ja = "一手: "
renderTrial :: VLC -> PublicInfo -> (Int -> String) -> Int -> PrivateView -> Move -> View Action
renderTrial verb@(_,l,_) pub ithP i v m = (if l==Ja then span_ [style_ $ M.fromList [("margin", "1em")]] else div_ []) [
  text $ S.pack $ ithP i ++ strMove l ++ {- replicate (length (ithP 2) - length (ithP i)) ' ' ++ -} showMove l m ++ " ("++shows m ")"
  , span_ [] $ showResults l renderRevealed res
  ]
  where res = result $ publicView v
        renderRevealed = renderCardInline verb pub $ revealed res

showResults En rev (Discard _) = [text ", which revealed ",            rev, text ". "]
showResults En rev (Success _) = [text ", which succeeded revealing ", rev, text ". "]
showResults En rev (Fail    _) = [text ", which failed revealing ",    rev, text ". "]
showResults Ja rev (Discard _) = [span_ [style_ $ M.fromList [("margin", "1em")]] [rev, text "捨て! "]]
showResults Ja rev (Success _) = [span_ [style_ $ M.fromList [("margin", "1em")]] [rev, text "通し! "]]
showResults Ja rev (Fail    _) = [span_ [style_ $ M.fromList [("margin", "1em")]] [rev, text "通らず! "]]
showResults _  rev _           = [text ". "]

strBot En = "← Bottom deck"
strBot Ja = "← 海底"
strLasZimo En = "Be ready for the bottom deck"
strLasZimo Ja = "ラスヅモかも"
strDeck    En = "deck: "
strDeck    Ja = "山: "
strPlayed  En = "played "
strPlayed  Ja = "打上済"
strDropped En = "dropped: "
strDropped Ja = "捨て牌: "
renderPI :: VLC -> PublicInfo -> View Action
renderPI verb@(_,l,cont) pub
  = div_ [style_ $ M.fromList [("font-size", "2.5vmin"),("padding","0"),("margin","0")]] [
      text $ S.pack $ "Turn: "++ shows (turn pub) ",  " ++ showDeck pub ++ "Lives: " ++ show (lives pub),
      span_ [style_ $ M.fromList [("font-size","1.5em"),("color",forceBlack cont "#FF0000")]] [text $ S.pack (concat $ replicate (lives pub) "💓"{-"♥"-})], -- In html, heart is &hearts; or &#9829; and info is &#9432; but they show up literally when used here.
      span_ [style_ $ M.fromList [("font-size","1.1em"),("color","#000000")]] [text $ S.pack (concat $ replicate (numBlackTokens (Game.Hanabi.rule $ gameSpec pub) - lives pub) "💔")],
      span_ [] [
          text $ S.pack $ ",  Hints: " ++ show (hintTokens pub),
          span_ [style_ $ M.fromList [("font-size","1.3em"),("color",forceBlack cont "#008800")]] [text $ S.pack (concat $ replicate (hintTokens pub) "ⓘ")],
          s_ [style_ $ M.fromList [("font-size","1.1em"),("color","#000000")]] [text $ S.pack (concat $ replicate (8 - hintTokens pub) "ⓘ")]
        ],
      text $ S.pack $ ";",

      div_ [style_ $ M.fromList [("padding", "0"),("margin","0")]] [
        text $ strDeck l,
        case deadline pub of -- Nothing -> span_ [style_ $ M.fromList [("width","30px"),("color","#FFFFFF"),("background-color","#000000")]] $ map (text . S.pack) $ replicate (pileNum pub) "__|"
                             Nothing -> span_ [style_ $ M.fromList [("font-size","0.65em")]] $ map (text . S.pack) $ replicate (pileNum pub) "🂠 "
                             Just dl -> span_ [style_ $ M.fromList [("font-size","1.5em")]] [ text . S.pack $ take dl "🕛🕚🕙🕘🕗🕖🕕🕔🕓🕒🕑🕐" ],
        if prolong $ Game.Hanabi.rule $ gameSpec pub then span_ [] [] else
          case pileNum pub of 1                                         -> span_ [style_ $ M.fromList (maybeColor cont "#FF0000" [("font-weight", "bold")])] [text $ strBot l]
                              n | n>1 && n <= numPlayers (gameSpec pub) -> span_ [style_ $ M.fromList (maybeColor cont "#777700" [("font-weight", "bold")])] [text $ strLasZimo l]
                                | otherwise                             -> span_ [] []
       ],
      div_ [style_ $ M.fromList [("padding", "0"),("margin","0")]] [
        text $ strPlayed l,
        span_ (if achievable - current >= pileNum pub then [style_ $ M.fromList (maybeColor cont "#FF0000" [("font-weight", "bold")])] else [])
              [text $ S.pack $ "(" ++ shows current " / " ++ shows achievable ")"],
        text $ S.pack ": ",
--        span_ [] [ span_ [] $ text (S.pack "|") : [ renderCardInline verb pub $ C c k | k <- [K1 .. playedMax] ] ++ map (text . S.pack) (replicate (possible - fromEnum playedMax) "__" ++ replicate (5 - possible) "XX")
        span_ [style_ $ M.fromList [("font-family", "monospace"),("font-size", inlineFontSize l)]] $ [ span_ [] $ text (S.pack " ") : renderCardsInline verb pub c [K1 .. playedMax] : map (text . S.pack) (replicate (possible - fromEnum playedMax) "_" ++ replicate (5 - possible) "X")
                                                            | c <- colors pub
                                                            , let playedMax = achievedRank pub c
                                                                  possible  = fromEnum $ bestPossibleRank pub c
                                                            ]
      ],

      div_ [style_ $ M.fromList [("padding", "0"),("margin","0")]] [
        text $ strDropped l,
--        span_ [] [ span_ [] $ text (S.pack "|") : (replicate n $ renderCardInline verb pub $ intToCard ci) | (ci, n) <- IM.toList $ discarded pub ],
        span_ [style_ $ M.fromList [("font-family", "monospace"),("font-size", inlineFontSize l)]] $ [ span_ [] [text (S.pack " "), renderCardsInline verb pub c (replicate n r)] | c <- colors pub, r <- [K1 .. maxBound], let n = discarded pub $ C c r, n>0 ]
      ]
     ]
    where current    = currentScore pub
          achievable = seeminglyAchievableScore pub

-- renderCardsInline is a compact version of renderCardInline that prints the color letter only once.
renderCardsInline :: VLC -> PublicInfo -> Color -> [Rank] -> View Action
renderCardsInline vl@(_,l,cont) pub c ns = span_ [style_ $ M.fromList [("color", forceWhite cont $ colorStr $ Just c),("background-color","#000000"),("padding", "0"),("margin","0")]] $ span_ [] [text $ S.pack [showColor l c]] : map (rankStrInline vl pub c) ns

rankStrInline (v,l,_) pub c n = cardStrInline v pub (C c n) $ showRank l n

renderCardInline :: VLC -> PublicInfo -> Card -> View Action
renderCardInline (v,l,cont) pub c = span_ [style_ $ M.fromList [("font-family", "monospace"),("font-size", inlineFontSize l),("color", forceWhite cont $ colorStr $ Just $ color c),("background-color","#000000"),("padding", "0"),("margin","0")]] [cardStrInline v pub c $ showColor l (color c) : showRank l (rank c)]
inlineFontSize En = "3vmin"
inlineFontSize Ja = "4vmin"
showColor En c      = head (show c)
showColor Ja White  = '白'
showColor Ja Yellow = '黃'
showColor Ja Red    = '赤'
showColor Ja Green  = '緑'
showColor Ja Blue   = '青'
showRank En r  = show $ fromEnum r
showRank Ja K1 = "🀙"
showRank Ja K2 = "🀚"
showRank Ja K3 = "🀛"
showRank Ja K4 = "🀜"
showRank Ja K5 = "🀝"
showBack En = '_'
showBack Ja = '🀫'

cardStrInline v pub c xs = (if useless then s_ else span_) [style] [
--                                    text $ S.pack $ show c
                                    span_ [] [text $ S.pack xs]
                             ]
                     where style = style_ $ M.fromList [
                              ("font-weight", if useless then "100"
                                              else if critical then "bold" else "normal"),
                              ("font-style",  if markPlayable v && isPlayable pub c then "oblique" else "normal")]
                           critical = warnCritical v && isCritical pub c
                           useless = markUseless v && isUseless pub c


renderRecentEvents :: VLC -> PublicInfo -> (Int -> Int -> String) -> [PrivateView] -> [Move] -> View Action
renderRecentEvents verb pub ithP vs@(v:_) ms = div_ [style_ $ M.fromList [("font-size", "2.5vmin")]] $ reverse $ zipWith3 (renderTrial verb pub $ ithP nump) [pred nump, nump-2..0] vs ms
   where nump = numPlayers $ gameSpec $ publicView v


renderPV :: VLC -> PrivateView -> View Action
renderPV v@(_,l,_) pv@PV{publicView=pub} = div_ [] [
  renderPI v pub,
  div_ [] (
--      div_ [] [text $ S.pack $ "Your hand:"] :
--      renderCards v pub [ Nothing | _ <- yourHand] yourHand :
      renderHand v pv (const $ strYour l) 0 [ Nothing | _ <- yourHand] yourHand :
--                                              ++ concat [ '+':shows d "-" | d <- [0 .. pred $ length yourHand] ]
      (zipWith3 (renderHand v pv (ithPlayerI18N l "あなたの" $ numPlayers $ gameSpec pub)) [1..] (map (map Just) $ handsPV pv) (tail $ annotations pub))
    )
  ]
  where yourHand = head (annotations pub)
strYour En = "Your "
strYour Ja = "あなたの"
renderSt :: VLC -> (Int -> Int -> String) -> State -> View Action
renderSt verb ithP st@St{publicState=pub} = div_ [] $
  renderPI verb pub :
  zipWith3 (renderHand verb (Game.Hanabi.view st) (ithP $ numPlayers $ gameSpec pub)) [0..] (map (map Just) $ hands st) (annotations pub)

renderHand :: VLC -> PrivateView -> (Int->String) -> Int -> [Maybe Card] -> [Annotation] -> View Action
renderHand vl@(_,l,_) pv ithPnumP i mbcards anns = div_ [] [
  div_ [style_ $ M.fromList [("font-size", "2.5vmin")]] [text $ S.pack $ ithPnumP i ++ strHand l],
   renderHand' vl pv i mbcards anns
--  renderCards v pub (map Just cards) hl
  ]
renderHand' :: VLC -> PrivateView -> Int -> [Maybe Card] -> [Annotation] -> View Action
renderHand' v pv pli mbcards anns = 
   table_ [style_ $ M.fromList [("border-color","#FFFFFF"), ("border-width","medium")]] [tr_ [style_ $ M.fromList [("background-color","#000000"){- , ("height","48px") -}]] (zipWith3 (renderCard v pv pli anns) [0..] mbcards anns)]

renderCard :: VLC -> PrivateView -> Int -> [Annotation] -> Index -> Maybe Card -> Annotation -> View Action
renderCard vl@(v,l,cont) pv pli anns i mbc ann@Ann{marks=tup@(mc,mk), possibilities=ptup} = td_ [style_ $ M.fromList [
                                                                                     ("text-align","center"),
                                                                                     ("width", S.pack $ shows cardWidth "vmin"),
                                                                                     ("color", forceWhite cont $ colorStr $ fmap color mbc),
                                                                                     ("font-family", "monospace"),
                                                                                     ("-webkit-touch-callout", "none"),
                                                                                     ("-webkit-user-select", "none"),
                                                                                     ("-khtml-user-select", "none"),
                                                                                     ("-moz-user-select", "none"),
                                                                                     ("-ms-user-select", "none"),
                                                                                     ("user-select", "none")
                                                                                    ]] [ -- ("font-size", S.pack $ shows (cardWidth - cardWidth `div` 10) "vmin")]] [
     maybe (button_ [ onClick (SendMessage $ Message $ S.pack $ 'p':show i), style_ $ M.fromList [("width", S.pack $ shows cardWidth "vmin"),
                                                                                                  ("font-size", S.pack $ shows (cardWidth / 5) "vmin" -- "0.3em"
                                                                                                  )] ] [ text (S.pack $ strPlay l) ]) (const $ span_[][]) mbc,
     div_ [style_ $ M.fromList $ (if isNothing mbc then (("background-color", if warnDoubleDrop v && isDoubleDrop pv (result pub) chopSet ann && i `elem` chopSet then "#880000" else
                                                                              if markChops v && i `elem` chopSet then "#888888" else "#000000") :) else id) [{- ("height", S.pack $ shows (cardWidth / 2) "vmin"), -} ("font-family", "serif"), ("font-size", S.pack $ shows (if isNothing mbc then cardWidth / 3 else cardWidth * (4/9)) "vmin")]] [   --  "1.2em")]] [
        cardStr vl pv pli mbc ann
     ],
     (if useless then s_ [] . (:[]) else id) $ div_ [style_ $ M.fromList $ ("text-align","center") : ("font-size", S.pack $ shows (cardWidth / 3) "vmin") : myStyle] [text $ S.pack $ if markHints v then maybe '_' (showColor l) mc : ' ' : [maybe (showBack l) (head . showRank l) mk] else "_ "++[showBack l] ],
     maybe (button_ [ onClick (SendMessage $ Message $ S.pack $ 'd':show i), style_ $ M.fromList [("width", S.pack $ shows cardWidth "vmin"),
                                                                                                  ("font-size", S.pack $ shows (cardWidth / 5) "vmin" -- "0.3em"
                                                                                                  )] ] [ text (S.pack $ strDrop l) ]) (const $ span_[][]) mbc,
     if markPossibilities v then div_ [style_ $ M.fromList [("font-size", S.pack $ shows (cardWidth / 7) "vmin" -- "0.3em"
                                                                                                  )]] [text $ S.pack $ showColorPossibilities $ qitsToColorPossibilities ptup, -- br_[],
                                                                                                       text $ S.pack $ showRankPossibilities $ qitsToRankPossibilities ptup] else span_[][]
  ] where
          pub = publicView pv
          cardWidth = (case l of En -> 90
                                 Ja -> 80) / fromIntegral (handSize $ gameSpec pub)
          (useless,myStyle) = (markObviouslyUseless v && isObviouslyUseless pub ptup,
                                                        [ ("background-color",if markChops v && i `elem` concat (take 1 $ obviousChopss pub anns) then "#888888" else "#000000"),
                                                          ("font-weight", if useless then "100" else "normal"),
                                                          ("font-style",  if markObviouslyPlayable v && isObviouslyPlayable pub ptup then "oblique" else "normal")]
                                              )
          chopSet = concat $ take 1 $ definiteChopss pv anns
strPlay En = "play"
strPlay Ja = "勝負!"
strDrop En = "drop"
strDrop Ja = "捨て"
{-
renderCards :: Verbosity -> PrivateView -> [Maybe Card] -> [Marks] -> View Action
renderCards v pv@PV{publicView=pub} mbcs tups = table_ [style_ $ M.fromList [("border-color","#FFFFFF"),("border-width","medium")]] [
  tr_ [style_ $ M.fromList [("background-color","#000000")]]
     [
       td_ [style_ $ M.fromList [("color", colorStr $ fmap color mbc)]] [
        cardStr v pv mbc ann
        ]
     | (mbc,tup) <- zip mbcs tups
     ],
  tr_ [style_ $ M.fromList [("background-color","#000000")]]
     [
       td_ [style_ $ M.fromList [("color", colorStr $ fmap color mbc)]] [
          text $ S.pack $ if markHints v then maybe ' ' (head . show) mc : [maybe ' ' (head . show . fromEnum) mk] else ""
       ]
     | (mbc,(mc,mk)) <- zip mbcs tups
     ]
  ]
-}
strBack En = "? ?"
strBack Ja = "🀫"
cardStr :: VLC -> PrivateView -> Int -> Maybe Card -> Annotation -> View Action
-- Not sure which style is better.
#ifdef BUTTONSONCARDS
cardStr (v,l,cont) pv@PV{publicView=pub} pli mbc ann@Ann{marks=tup} = case mbc of
          Nothing -> (if useless then s_ else span_) [style] [text $ S.pack $ strBack l]
                     where style = style_ $ M.fromList [-- ("width","30px"),
                              ("text-align","center"),
                              ("font-family", if critical then "sans-serif" else "serif"),
                              ("font-weight", if useless then "100"
                                              else if critical then "bold" else "normal"),
                              ("font-style",  if markPlayable v && isDefinitelyPlayable pv ann then "oblique" else "normal")]
                           critical = warnCritical v && isDefinitelyCritical pv ann
                           useless  = markUseless  v && isDefinitelyUseless  pv ann
          Just c  -> (if useless then s_ else span_) [] [
--                                    text $ S.pack $ show c
                                    button_ [onClick (SendMessage $ Message $ S.pack $ shows pli $ take 1 $ show $ color c), style] [text $ S.pack $ take 1 $ show $ color c],
                                    button_ [onClick (SendMessage $ Message $ S.pack $ shows pli $ show $ fromEnum $ rank c), style][text $ S.pack $ show $ fromEnum $ rank c]
                             ]
                     where style = style_ $ M.fromList [
                              ("font-family", if critical then "sans-serif" else "serif"),
                              ("font-weight", if useless then "100"
                                              else if critical then "bold" else "normal"),
                              ("font-style",  if markPlayable v && isPlayable pub c then "oblique" else "normal"), ("background-color","#000000"),("color", forceWhite cont $ colorStr $ fmap color mbc)]
#else
cardStr (v,l,cont) pv@PV{publicView=pub} pli mbc ann@Ann{marks=tup} = case mbc of
          Nothing -> (if useless then s_ else span_) [style] [text $ S.pack $ strBack l]
                     where style = style_ $ M.fromList [-- ("width","30px"),
                              ("text-align","center"),
                              ("font-family", if critical then "sans-serif" else "serif"),
                              ("font-weight", if useless then "100"
                                              else if critical then "bold" else "normal"),
                              ("font-style",  if markPlayable v && isDefinitelyPlayable pv ann then "oblique" else "normal")]
                           critical = warnCritical v && isDefinitelyCritical pv ann
                           useless  = markUseless  v && isDefinitelyUseless  pv ann
          Just c  -> (if useless then s_ else span_) [style] [
--                                    text $ S.pack $ show c
                                    span_ [ -- style_ $ M.fromList [("font-size","1.3em")],
                                              onClick (SendMessage $ Message $ S.pack $ shows pli $ take 1 $ show $ color c)] [text $ S.pack [showColor l $ color c]],
                                    text " ",
                                    span_ [ -- style_ $ M.fromList [("font-size","1.3em")],
                                              onClick (SendMessage $ Message $ S.pack $ shows pli $ show $ fromEnum $ rank c)][text $ S.pack $ showRank l $ rank c]
                             ]
                     where style = style_ $ M.fromList [-- ("width","30px"),
                              ("font-family", if critical then "sans-serif" else "serif"),
                              ("font-weight", if useless then "100"
                                              else if critical then "bold" else "normal"),
                              ("font-style",  if markPlayable v && isPlayable pub c then "oblique" else "normal"), ("background-color","#000000"),("color", forceWhite cont $ colorStr $ fmap color mbc)]
#endif
                           critical = warnCritical v && tup==(Nothing,Nothing) && isCritical pub c
                           useless = markUseless v && isUseless pub c
colorStr :: Maybe Color -> MisoString
colorStr Nothing       = "#00FFFF"
colorStr (Just White)  = "#FFFFFF"
colorStr (Just Yellow) = "#FFFF00"
colorStr (Just Red)    = "#FF4444"
colorStr (Just Green)  = "#44FF44"
colorStr (Just Blue)   = "#8888FF"
colorStr (Just Multicolor) = "#FF00FF"

data MVarStrategy = MVS {mvMsg :: MVar Msg, mvMov :: MVar Move} deriving Eq
instance Strategy MVarStrategy IO where
  strategyName _ = return "local player"
  move pvs@(pv:_) mvs mvstr@(MVS mvmsg mvmov) = do
    putMVar mvmsg $ WhatsUp "local player" pvs mvs
    mov <- getMoveUntilSuccess pv mvstr
    return (mov, mvstr)
  observe (v:_) (m:_) (MVS mvmsg _) = putMVar mvmsg $ WhatsUp1 v m
  observeEndGame d eh mvstr@(MVS mvmsg _) = do
    putMVar mvmsg $ PrettyEndGame d $ Just eh
    return mvstr
getMoveUntilSuccess pv mvstr@(MVS mvmsg mvmov) = do
  m <- takeMVar mvmov
  if isMoveValid pv m then return m else do putMVar mvmsg Invalid
                                            getMoveUntilSuccess pv mvstr
newMVarStrategy = do
  mvmsg <- newEmptyMVar
  mvmov <- newEmptyMVar
  return $ MVS mvmsg mvmov

instance Show MVarStrategy where
  showsPrec p _ = ("MVarStrategy "++)