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 ♥ or ♥ and info is ⓘ 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 "++)