packages feed

hanabi-dealer 0.5.0.0 → 0.6.0.0

raw patch · 8 files changed

+200/−61 lines, 8 filesdep ~basedep ~timenew-component:exe:hanabibnew-component:exe:hanabifnew-component:exe:hanabiqPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: base, time

API changes (from Hackage documentation)

+ Game.Hanabi: peek :: Peeker IO
+ Game.Hanabi: prettySt :: (Int -> Int -> String) -> State -> [Char]
+ Game.Hanabi: type Peeker m = State -> m ()
- Game.Hanabi: run :: (Monad m, Strategies ps m) => [State] -> [Move] -> ps -> m ((EndGame, [State], [Move]), ps)
+ Game.Hanabi: run :: (Monad m, Strategies ps m) => [Peeker m] -> [State] -> [Move] -> ps -> m ((EndGame, [State], [Move]), ps)
- Game.Hanabi: start :: (RandomGen g, Monad m, Strategies ps m) => GameSpec -> ps -> g -> m (((EndGame, [State], [Move]), ps), g)
+ Game.Hanabi: start :: (RandomGen g, Monad m, Strategies ps m) => GameSpec -> [Peeker m] -> ps -> g -> m (((EndGame, [State], [Move]), ps), g)

Files

ChangeLog.md view
@@ -1,5 +1,13 @@ # Revision history for hanabi-dealer +	## 0.6.0.0 -- 2020-02-08++	* introduce the observation (or audience) mode++	* rename the command names from server and client to hanabib and hanabif respectively.++	* Flag jsaddle builds hanabiq that is quickly-built executable including the functionalities of both hanabib and hanabif (but it is not stable.)+ 	## 0.5.0.0 -- 2020-02-05  	* implement possibility marks which represent what colors and what numbers are possible for each card
Game/Hanabi.hs view
@@ -6,6 +6,8 @@               -- * Datatypes               -- ** The Class of Strategies               Strategies, Strategy(..), StrategyDict(..), mkSD, DynamicStrategy, mkDS, mkDS', Verbose(..), STDIO, stdio, Blind, ViaHandles(..), Verbosity(..), verbose,+              -- ** Audience+              Peeker, peek,               -- ** The Game Specification               GameSpec(..), defaultGS, Rule(..), defaultRule, isRuleValid, colors,               -- ** The Game State and Interaction History@@ -17,7 +19,7 @@               isCritical, isUseless, bestPossibleRank, achievedRank, isPlayable, isHinted,               isMoreObviouslyUseless, isObviouslyUseless, isDefinitelyUseless, isMoreObviouslyPlayable, isObviouslyPlayable, isDefinitelyPlayable, obviousChopss, definiteChopss, isDoubleDrop,               -- ** Minor ones-              what'sUp, what'sUp1, ithPlayer, recentEvents, prettyPI, ithPlayerFromTheLast, view, replaceNth, shuffle, showPossibilities, showColorPossibilities, showNumberPossibilities) where+              what'sUp, what'sUp1, ithPlayer, recentEvents, prettyPI, prettySt, ithPlayerFromTheLast, view, replaceNth, shuffle, showPossibilities, showColorPossibilities, showNumberPossibilities) where -- module Hanabi where import qualified Data.IntMap as IM import System.Random@@ -414,7 +416,7 @@ --  (where 1<n<10) starts selfplay with youselves:D selfplay gs      = do g <- newStdGen-          ((finalSituation,_),_) <- start gs [stdio] g+          ((finalSituation,_),_) <- start gs [] [stdio] g           putStrLn $ prettyEndGame finalSituation  -- | 'prettyEndGame' can be used to pretty print the final situation.@@ -430,15 +432,20 @@                      len2 =len `div` 2                  in reverse (drop len2 ys) ++ xs ++ drop (len - len2) ys +type Peeker m = State -> m ()+peek :: Peeker IO+peek = putStrLn . prettySt ithPlayerFromTheLast+ -- | 'start' creates and runs a game. This is just the composition of 'createGame' and 'run'. start :: (RandomGen g, Monad m, Strategies ps m) =>-       GameSpec -> ps -> g -> m (((EndGame, [State], [Move]), ps), g)-start gs players gen = let+       GameSpec -> [Peeker m] -> ps -> g -> m (((EndGame, [State], [Move]), ps), g)+start gs audience players gen = let                          (st, g) = createGame gs gen-                       in fmap (\e -> (e,g)) $ run [st] [] players-run :: (Monad m, Strategies ps m) => [State] -> [Move] -> ps -> m ((EndGame, [State], [Move]), ps)-run states moves players = do ((mbeg, sts, mvs), ps) <- runARound (\sts@(st:_) mvs -> let myOffset = turn (publicState st) in broadcast (zipWith rotate [-myOffset, 1-myOffset ..] sts) mvs players (myOffset `mod` numPlayers (gameSpec $ publicState st)) >> return ()) states moves players-                              case mbeg of Nothing -> run sts mvs ps+                       in fmap (\e -> (e,g)) $ run audience [st] [] players+run :: (Monad m, Strategies ps m) => [Peeker m] -> [State] -> [Move] -> ps -> m ((EndGame, [State], [Move]), ps)+run audience states moves players = do+                              ((mbeg, sts, mvs), ps) <- runARound (\sts@(st:_) mvs -> let myOffset = turn (publicState st) in mapM_ ($ st) audience >> broadcast (zipWith rotate [-myOffset, 1-myOffset ..] sts) mvs players (myOffset `mod` numPlayers (gameSpec $ publicState st)) >> return ()) states moves players+                              case mbeg of Nothing -> run audience sts mvs ps                                            Just eg -> return ((eg, sts, mvs), ps)  -- | The 'Strategy' class is exactly the interface that
Game/Hanabi/Backend.lhs view
@@ -4,7 +4,11 @@ --  -- x #define SNAP-module Game.Hanabi.Backend(server, Options(..), defaultOptions, module Game.Hanabi) where+module Game.Hanabi.Backend(server,+#ifdef WARP+                           hanabiApp,+#endif+                           Options(..), defaultOptions, module Game.Hanabi) where  import Game.Hanabi hiding (main) import Game.Hanabi.Msg@@ -50,7 +54,7 @@ # ifdef WARP import Network.Wai.Handler.WebSockets import Network.Wai.Handler.Warp as Warp-import Network.Wai(responseLBS)+import Network.Wai(Application, responseLBS) import Network.HTTP.Types(status400) # endif #endif@@ -95,24 +99,25 @@     hPutStrLn stderr $ "hanabi-dealer server " ++ version options     beginCT <- getCurrentTime     hPutStrLn stderr ("started at " ++ show beginCT)-#ifdef TFRANDOM-    g <- newTFGen+#ifdef WARP+    app <- hanabiApp options $ \_ resp -> resp $ responseLBS status400 [] "Not a WebSocket request."+    Warp.run (port options) app #else-    g <- newStdGen-#endif--    mv <- newMVar (IntMap.empty::IntMap.IntMap ([MVar ViaWebSocket], MVar (Maybe (EndGame,[State],[Move]))))-    let params = Params{gen = g, mvTIDToMVH = mv, versionString = version options, gsConstructorMap = Map.fromList $ strategies options, conn = error "Game.Hanabi.Backend.server: should not happen"}-#ifdef SNAP+    params <- mkParams options+# ifdef SNAP     httpServe (setPort (port options) defaultConfig) $ runWebSocketsSnap $ loop params-#else-# ifdef WARP-    Warp.run (port options) $ websocketsOr defaultConnectionOptions (loop params) $ \_ resp -> resp $ responseLBS status400 [] "Not a WebSocket request." # else     runServer "127.0.0.1" (port options) $ loop params # endif #endif +#ifdef WARP+hanabiApp :: Options -> Application -> IO Application+hanabiApp options fallbackApp = do+    params <- mkParams options+    return $ websocketsOr defaultConnectionOptions (loop params) fallbackApp+#endif+ data Params g = Params{gen :: g,                        mvTIDToMVH :: MVar (IntMap.IntMap ([MVar ViaWebSocket], MVar (Maybe (EndGame,[State],[Move])))),                        conn :: Connection,@@ -120,6 +125,18 @@                        gsConstructorMap :: Map.Map String (IO (DynamicStrategy IO))                       } +#ifdef TFRANDOM+mkParams :: Options -> IO (Params TFGen)+mkParams options = do+    g <- newTFGen+#else+mkParams :: Options -> IO (Params StdGen)+mkParams options = do+    g <- newStdGen+#endif+    mv <- newMVar (IntMap.empty::IntMap.IntMap ([MVar ViaWebSocket], MVar (Maybe (EndGame,[State],[Move]))))+    return Params{gen = g, mvTIDToMVH = mv, versionString = version options, gsConstructorMap = Map.fromList $ strategies options, conn = error "Game.Hanabi.Backend.server: should not happen"}+ -- Well, this is not a loop any longer.... loop :: RandomGen g =>         Params g -> PendingConnection -> IO ()@@ -136,7 +153,7 @@ # endif                  let p = params{gen = g, conn = c} #endif-                 withPingThread c 30 (return ()) $ fmap (const ()) $ answerHIO p+                 withPingThread c 25 (return ()) $ fmap (const ()) $ answerHIO p   endecodeX Nothing     = endecode@@ -172,6 +189,8 @@     observe (v:_) (m:_) vh = liftIO $ sendTextData (connection vh) $ endecodeX (verbVWS vh) $ WhatsUp1 v m  +watch conn mbVerb st = sendTextData conn $ endecodeX mbVerb $ Watch st+ answerHIO :: RandomGen g => Params g -> IO () answerHIO params = do   available Nothing (mvTIDToMVH params) (conn params)@@ -200,13 +219,13 @@ interpret mbVerb inp params   = let sender :: String -> IO ()         sender = sendTextData (conn params) . endecodeX mbVerb . Str-        createR creater args = case reads args of [(rule,rest)] | isRuleValid rule -> create creater rule rest-                                                  _             -> create creater defaultRule args-        create :: Maybe Int -> Rule -> String -> IO ()-        create from rule args =+        createR observe creater args = case reads args of [(rule,rest)] | isRuleValid rule -> create observe creater rule rest+                                                          _             -> create observe creater defaultRule args+        create :: Bool -> Maybe Int -> Rule -> String -> IO ()+        create observe from rule args =                               case wordsBy (==',') args of                                 is | numAllies > 0 -> if numAllies >= 9-                                                      then sender "Too many teemmates!\n"+                                                      then sender "Too many teammates!\n"                                                       else case dropWhile (\s -> isWS s || s `Map.member` gsConstructorMap params) is of                                                              alg:_ -> sender $ "Algorithm " ++ shows (maximum is) " not implemented yet.\n"                                                              []    -> do@@ -224,11 +243,13 @@                                                                    constructor _     algIx = fromJust $ Map.lookup algIx $ gsConstructorMap params                                                                ixSs <- sequence $ zipWith constructor [0..] is                                                                sender "starting the game\n"-                                                               let playerList = mkDS "via WebSocket" (VWS (conn params) mbVerb sendFullHistoryInFact) : ixSs+                                                               let playerList | observe = ixSs+                                                                              |otherwise= mkDS "via WebSocket" (VWS (conn params) mbVerb sendFullHistoryInFact) : ixSs                                                                    (playOrder,g) = case from of Just n  -> (dr++tk, gen params)                                                                                                    where (tk,dr) = splitAt n playerList                                                                                                 Nothing -> shuffle playerList $ gen params-                                                               eithFinalSituation <- try $ start (GS (succ numAllies) rule) playOrder g+                                                               eithFinalSituation <- try $ if observe then start (GS numAllies rule)        [watch (conn params) mbVerb] playOrder g+                                                                                                      else start (GS (succ numAllies) rule) []                           playOrder g                                                                finalSituation <- case eithFinalSituation of                                                                                    Left e -> do hPutStrLn stderr $ displayException (e::SomeException)                                                                                                 return Nothing@@ -240,10 +261,12 @@                                 _  -> sender "The arguments of the `create' command could not be parsed."     in case lex inp of         [("version", _)]   -> sender $ "hanabi-dealer server " ++ versionString params-        [("create", args)] -> createR (Just 0) args-        [("from",   args)] -> case reads args of [(n, ars)] -> createR (Just n) ars+        [("create", args)] -> createR False (Just 0) args+        [("from",   args)] -> case reads args of [(n, ars)] -> createR False (Just n) ars                                                  _          -> sender "Parse error. The player number expected."-        [("shuffle",args)] -> createR Nothing args+        [("observe",args)] -> case reads args of [(n, ars)] -> createR True (Just n) ars+                                                 _          -> createR True Nothing args+        [("shuffle",args)] -> createR False Nothing args         [("available", s)] | all isSpace s -> when (isJust mbVerb) $ available mbVerb (mvTIDToMVH params) (conn params)         [("attend", arg)] -> case reads arg of                                [(gid, s)] | all isSpace s -> do tidToMVH <- readMVar $ mvTIDToMVH params
Game/Hanabi/Client.hs view
@@ -3,7 +3,11 @@ {-# LANGUAGE OverloadedStrings          #-} {-# LANGUAGE ExtendedDefaultRules       #-} {-# LANGUAGE CPP                        #-}-module Game.Hanabi.Client(client, Game.Hanabi.Msg.Options(..), Game.Hanabi.Msg.defaultOptions, mkDS) where+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)@@ -22,24 +26,45 @@ import           Miso.String  (MisoString) import qualified Miso.String  as S -#ifdef IOS+#ifdef ghcjs_HOST_OS+client :: Game.Hanabi.Msg.Options -> IO ()+client options = clientJSM options+#else+# ifdef IOS import Language.Javascript.JSaddle.WKWebView as JSaddle -runApp :: JSM () -> IO ()-runApp = JSaddle.run-#else-import Language.Javascript.JSaddle.Warp 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) -runApp :: JSM () -> IO ()-runApp = JSaddle.run 8080 -- 8720-#endif+{- 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 -client :: Game.Hanabi.Msg.Options -> IO ()-client options = runApp $ startApp App{-    model  = Model{tboxval = Message "available", players = ["via WebSocket"], from = Just 0, rule = defaultRule, received = [], fullHistory = False, showVerbosity = False, verbosity = verbose},+clientJSM :: Game.Hanabi.Msg.Options -> JSM ()+clientJSM options = startApp App{+    model  = Model{tboxval = Message "available", players = ["via WebSocket"], from = Just 0, rule = defaultRule, received = [], fullHistory = False, showVerbosity = False, verbosity = verbose, play = True},     update = updateModel,     view   = appView strNames $ version options,     subs   = [ websocketSub uri protocols HandleWebSocket ],@@ -77,6 +102,8 @@ updateModel (UpdateVerbosity v) model = noEff model{verbosity = v} updateModel Toggle            model = noEff model{fullHistory = not $ fullHistory model} updateModel ToggleVerbosity   model = noEff model{showVerbosity = not $ showVerbosity model}+updateModel TogglePlay        model | play model && length (players model) < 2 = noEff model{play = False, players = "via WebSocket" : players model}+                                    | otherwise                                = noEff model{play = not $ play model} #ifdef DEBUG updateModel (HandleWebSocket act) model = noEff model{received = Str (show act) : received model } #endif@@ -100,6 +127,7 @@   | UpdateVerbosity Verbosity   | Toggle   | ToggleVerbosity+  | TogglePlay   | Id  data Model = Model {@@ -111,6 +139,7 @@   , fullHistory :: Bool   , showVerbosity :: Bool   , verbosity :: Verbosity+  , play :: Bool   } deriving (Show, Eq)  lenShownHistory = 10@@ -187,6 +216,7 @@ renderMsg _ verb _ (WhatsUp1     p  m)  = renderWhatsUp1 verb p m renderMsg _ _    _ (PrettyEndGame Nothing)     = pre_ [] [ text $ S.pack $ prettyMbEndGame Nothing] renderMsg _ verb _ (PrettyEndGame (Just tup))  = renderEndGame verb tup -- pre_ [] [ text $ S.pack $ prettyEndGame tup]+renderMsg _ verb _ (Watch st)           = renderSt verb ithPlayerFromTheLast st renderMsg strategies _    mdl (PrettyAvailable games)   = div_ [style_ $ M.fromList [("overflow","auto")]] [       hr_ [],@@ -203,9 +233,10 @@              label_ [for_ "shuffle"] [text "shuffle on startup"]                                   ] : --             table_ [solid] $-                         [ tr_ [] [td_ [solid] [x], td_ [] [input_ [type_ "radio", id_ iD, onClick (From $ Just n), checked_ $ from mdl == Just n],-                                                            label_ [for_ iD] [text "Turn 0"]]]-                         | (n,x) <- zip [0..] $ text "You" : reverse (zipWith (renderPlayer strategies) [0..] (players mdl))+                         [ tr_ [] [td_ [solid] [x], td_ [] (if n>=0 then [input_ [type_ "radio", id_ iD, onClick (From $ Just n), checked_ $ from mdl == Just n],+                                                                          label_ [for_ iD] [text "Turn 0"]]+                                                                    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"]]]]@@ -234,10 +265,16 @@                    mkTR "funPlayerHand"                                 (\n -> (rule mdl){funPlayerHand=n})  funPlayerHand                  ], -             button_ [onClick $ SendMessage $ Message $ S.pack $ (case from mdl of Just n -> "from " ++ shows n " "-                                                                                   Nothing-> "shuffle ") ++ show (rule mdl) ++ concat (intersperse "," $ map S.unpack $ reverse $ players mdl)] [text $ S.pack "create a game"]+             button_ [onClick $ 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 (rule mdl) ++ concat (intersperse "," $ map S.unpack $ reverse $ players mdl)] [text $ S.pack "create a game"]       ]    ]+    where you =  span_[][+              input_ [ type_ "checkbox", id_ "you", onClick TogglePlay, checked_ $ play mdl]+            , label_ [for_ "you"] [if play mdl then text "You" else s_ [] [text "You"]]+            ] renderPlayer :: [MisoString] -> Int -> MisoString -> View Action --renderPlayer _ i p = text p renderPlayer strategies i p = dropdown strategies (UpdatePlayer i) p
Game/Hanabi/Msg.hs view
@@ -33,12 +33,13 @@ #endif  -- WhatsUp and WhatsUp1 should be minimized after better clients are implemented. 効率上はminimizeすべきだが、テスト目的ではとりあえずこのままの方がやりやすい。-data Msg = Str String | WhatsUp String [PrivateView] [Move] | WhatsUp1 PrivateView Move | PrettyEndGame (Maybe (EndGame, [State], [Move])) | PrettyAvailable [(Int, (Int, Int))] deriving (Show, Read, Eq, Generic)+data Msg = Str String | WhatsUp String [PrivateView] [Move] | WhatsUp1 PrivateView Move | PrettyEndGame (Maybe (EndGame, [State], [Move])) | Watch State | PrettyAvailable [(Int, (Int, Int))] deriving (Show, Read, Eq, Generic) prettyMsg :: Verbosity -> Msg -> String prettyMsg _    (Str xs)        = xs prettyMsg verb (WhatsUp name ps ms) = what'sUp  verb name ps ms prettyMsg verb (WhatsUp1     p  m)  = what'sUp1 verb      p  m prettyMsg _    (PrettyEndGame tup)  = prettyMbEndGame tup+prettyMsg _    (Watch st)           = prettySt ithPlayerFromTheLast st prettyMsg _    (PrettyAvailable available) = unlines $ map prettyAvailableGame available prettyMbEndGame Nothing    = "Game ended abnormally, possibly by connection failure.\n" prettyMbEndGame (Just tup) = prettyEndGame tup
Game/Hanabi/Strategies/SimpleStrategy.hs view
@@ -30,6 +30,6 @@ sndOf3 (_,b,_) = b  main = do g <- newStdGen---          ((eg,_),_) <- start defaultGS ([S],[stdio]) g -- Play it with standard I/O (human player).-          ((eg,_),_) <- start defaultGS [S,S] g -- Play it with itself.+--          ((eg,_),_) <- start defaultGS [] ([S],[stdio]) g -- Play it with standard I/O (human player).+          ((eg,_),_) <- start defaultGS [peek] [S,S] g -- Play it with itself.           putStrLn $ prettyEndGame eg
+ all.hs view
@@ -0,0 +1,34 @@+{-# LANGUAGE OverloadedStrings, CPP #-}+import Network.Wai+import Network.HTTP.Types+import Network.Wai.Handler.Warp (run)++import Game.Hanabi.Client(clientApp)+import Game.Hanabi.Backend(hanabiApp, mkDS)+import Game.Hanabi.Msg(defaultOptions, Options(..))++import Game.Hanabi.Strategies.SimpleStrategy hiding (main)++main = do putStrLn "localhost:8080"  -- Seemingly other port than 8080 does not work.+          let opt = defaultOptions{version="hanabi-dealer quickbuilt server", strategies=strs}+          capp <- clientApp opt+          bapp <- hanabiApp opt $ \_ resp -> resp $ responseLBS status400 [("Content-Type", "text/plain")] "400 Not a WebSocket request."+          run 8080 $ route capp bapp+route :: Application -> Application -> Application+route capp bapp request respond = case rawPathInfo request of+    "/ws/" -> bapp request respond+    _      -> capp request respond+--    _      -> respond $ responseLBS status404 [("Content-Type", "text/plain")] "404 Not found"++{-+import Game.Hanabi.Client(client)++-- main = client defaultOptions{strategies=strs, version="foo"}+main = do capp <- clientApp defaultOptions{strategies=strs, version="foo"}+          run 8080 capp+-}+-- strs :: [(String, IO (DynamicStrategy IO))]+strs = [+     ("Stupid example strategy", return $ mkDS "Stupid example strategy" $ S)+ -- x , ("Sontakki", return $ mkDS "Sontakki" (Sontakki emptyDefault))+  ]
hanabi-dealer.cabal view
@@ -10,7 +10,7 @@ -- PVP summary:      +-+------- breaking API changes --                   | | +----- non-breaking API additions --                   | | | +--- code changes with no API change-version:             0.5.0.0+version:             0.6.0.0  -- A short (one-line) description of the package. synopsis:            Hanabi card game@@ -73,11 +73,18 @@   Description: Use template-haskell just for obtaining the compilation time.   Default:     True Flag jsaddle-  Description: Build the client with JSaddle even when compiling with GHC.+--   Description: Build the client with JSaddle even when compiling with GHC.+--                (Package miso must have been built with --flags="jsaddle".)+--   Default:     False+-- Flag all+  Description: Quickbuild all-in-one server, using JSaddle.                (Package miso must have been built with --flags="jsaddle".)   Default:     False  library+ if flag(jsaddle)+  buildable: False+ else   -- Modules exported by the library.   exposed-modules:     Game.Hanabi @@ -97,7 +104,8 @@   -- Base language which the package is written in.   default-language:    Haskell2010 -  if impl(ghcjs) || flag(jsaddle) || flag(server)+  if impl(ghcjs) || flag(server)+--                       || flag(jsaddle)     exposed-modules:     Game.Hanabi.VersionInfo     other-modules:       Game.Hanabi.Msg     -- cpp-options:      -DCABAL@@ -120,16 +128,34 @@       if flag(WARP)         build-depends:       wai-websockets, warp, wai, http-types         cpp-options:         -DWARP-  if impl(ghcjs) || flag(jsaddle)+  if impl(ghcjs)+--                || flag(jsaddle)       exposed-modules:     Game.Hanabi.Client       ghcjs-options:      -dedupe -O       build-depends:      base >=4.12 && <4.13, jsaddle-warp >=0.9, aeson, miso, time >=1.6       if flag(official)         cpp-options:  -DURI="ws://133.54.228.39:8720"+--  if flag(jsaddle)+--      build-depends:      warp, wai, websockets -executable server+executable hanabiq+  main-is: all.hs+  other-modules: Game.Hanabi, Game.Hanabi.Msg, Game.Hanabi.Backend, Game.Hanabi.Client, Game.Hanabi.Strategies.SimpleStrategy+--  if !flag(all)+  if !flag(jsaddle)+    buildable: False+  else+    ghc-options: -O0 -rtsopts+    cpp-options: -DURI="ws://localhost:8080/ws/" -DWARP+    build-depends: base >=4.12 && <4.13, containers >=0.5, random >=1.1, jsaddle-warp >=0.9, aeson, miso, websockets >=0.12 && <0.13, network >=2.6 && <3.2, hashable >=1.3, time >=1.6 && <1.9, text >=1.2, utf8-string, wai-websockets, warp, wai, http-types+    if flag(TFRANDOM)+      build-depends: tf-random+      cpp-options: -DTFRANDOM+    default-language: Haskell2010++executable hanabib   main-is:       server.hs-  other-modules: Game.Hanabi.Backend, Game.Hanabi.Msg, Game.Hanabi.VersionInfo, Game.Hanabi.Strategies.SimpleStrategy+  other-modules: Game.Hanabi.Strategies.SimpleStrategy   if impl(ghcjs) || !flag(server)     buildable: False   else@@ -151,10 +177,11 @@         build-depends:       wai-websockets, warp, wai, http-types         cpp-options:         -DWARP -executable client+executable hanabif   main-is:       client.hs-  other-modules: Game.Hanabi.Msg, Game.Hanabi.Client, Game.Hanabi.VersionInfo, Game.Hanabi.Strategies.SimpleStrategy-  if !impl(ghcjs) && !flag(jsaddle)+  other-modules: Game.Hanabi.Strategies.SimpleStrategy+  if !impl(ghcjs)+--                 && !flag(jsaddle)     buildable: False   else     ghcjs-options:      -dedupe -O@@ -165,3 +192,5 @@       cpp-options:  -DURI="ws://133.54.228.39:8720"     if flag(TH)       build-depends: template-haskell+--    if flag(jsaddle)+--      build-depends:      warp, wai, websockets