packages feed

hanabi-dealer 0.4.0.1 → 0.5.0.0

raw patch · 5 files changed

+302/−110 lines, 5 filesdep +http-typesdep +waidep +wai-websocketsdep ~timePVP ok

version bump matches the API change (PVP)

Dependencies added: http-types, wai, wai-websockets, warp

Dependency ranges changed: time

API changes (from Hackage documentation)

- Game.Hanabi: chops :: PublicInfo -> [Marks] -> [(Index, Marks)]
- Game.Hanabi: chopss :: PublicInfo -> [Marks] -> [[(Index, Marks)]]
- Game.Hanabi: invisibleBag :: PrivateView -> IntMap Int
+ Game.Hanabi: [invisibleBag] :: PrivateView -> IntMap Int
+ Game.Hanabi: [markPossibilities] :: Verbosity -> Bool
+ Game.Hanabi: [possibilities] :: PublicInfo -> [[Possibilities]]
+ Game.Hanabi: [warnDoubleDrop] :: Verbosity -> Bool
+ Game.Hanabi: definiteChopss :: PrivateView -> [Marks] -> [Possibilities] -> [[Index]]
+ Game.Hanabi: isDefinitelyPlayable :: PrivateView -> Marks -> Possibilities -> Bool
+ Game.Hanabi: isDefinitelyUseless :: PrivateView -> Marks -> Possibilities -> Bool
+ Game.Hanabi: isDoubleDrop :: () => PrivateView -> Result -> [a] -> (Int, Int) -> Bool
+ Game.Hanabi: isMoreObviouslyUseless :: PublicInfo -> Marks -> Bool
+ Game.Hanabi: obviousChopss :: PublicInfo -> [Marks] -> [Possibilities] -> [[Index]]
+ Game.Hanabi: showColorPossibilities :: Int -> [Char]
+ Game.Hanabi: showNumberPossibilities :: Int -> [Char]
+ Game.Hanabi: showPossibilities :: Integral t => a -> [a] -> t -> [a]
+ Game.Hanabi: type Possibilities = (Int, Int)
- Game.Hanabi: PI :: GameSpec -> Int -> IntMap Number -> IntMap Int -> IntMap Int -> Int -> Int -> Int -> Maybe Int -> [[Marks]] -> Result -> PublicInfo
+ Game.Hanabi: PI :: GameSpec -> Int -> IntMap Number -> IntMap Int -> IntMap Int -> Int -> Int -> Int -> Maybe Int -> [[Marks]] -> [[Possibilities]] -> Result -> PublicInfo
- Game.Hanabi: PV :: PublicInfo -> [[Card]] -> PrivateView
+ Game.Hanabi: PV :: PublicInfo -> [[Card]] -> IntMap Int -> PrivateView
- Game.Hanabi: V :: Bool -> Bool -> Bool -> Bool -> Bool -> Bool -> Bool -> Verbosity
+ Game.Hanabi: V :: Bool -> Bool -> Bool -> Bool -> Bool -> Bool -> Bool -> Bool -> Bool -> Verbosity
- Game.Hanabi: isObviouslyPlayable :: PrivateView -> Marks -> Bool
+ Game.Hanabi: isObviouslyPlayable :: PublicInfo -> Possibilities -> Bool
- Game.Hanabi: isObviouslyUseless :: PublicInfo -> Marks -> Bool
+ Game.Hanabi: isObviouslyUseless :: PublicInfo -> Possibilities -> Bool

Files

ChangeLog.md view
@@ -1,5 +1,19 @@ # Revision history for hanabi-dealer +	## 0.5.0.0 -- 2020-02-05++	* implement possibility marks which represent what colors and what numbers are possible for each card++	* introduce utility functions using the information of possibilities, and redesign the set of utilities++	* implement isDoubleDrop and warnDoubleDrop++	* enable customizing verbosity from the client++	* support Warp as the WebSocket server++	* add the jsaddle flag+ 	## 0.4.0.1 -- 2020-01-29  	* fix the bug of the endgame behavior that crept in during the switch to 0.4.0.0
Game/Hanabi.hs view
@@ -11,12 +11,13 @@               -- ** The Game State and Interaction History               Move(..), Index, State(..), PrivateView(..), PublicInfo(..), Result(..), EndGame(..),               -- ** The Cards-              Card(..), Color(..), Number(..), Marks, cardToInt, intToCard, readsColorChar, readsNumberChar,+              Card(..), Color(..), Number(..), Marks, Possibilities, cardToInt, intToCard, readsColorChar, readsNumberChar,               -- * Utilities               -- ** Hints-              isCritical, isUseless, bestPossibleRank, achievedRank, isPlayable, isHinted, isObviouslyUseless, isMoreObviouslyPlayable, isObviouslyPlayable, invisibleBag, chops, chopss,+              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) where+              what'sUp, what'sUp1, ithPlayer, recentEvents, prettyPI, ithPlayerFromTheLast, view, replaceNth, shuffle, showPossibilities, showColorPossibilities, showNumberPossibilities) where -- module Hanabi where import qualified Data.IntMap as IM import System.Random@@ -24,9 +25,11 @@ import Control.Monad.IO.Class(MonadIO, liftIO) import Data.Char(isSpace, isAlpha, isAlphaNum, toLower, toUpper) import Data.Maybe(fromJust)-import Data.List(isPrefixOf, group)+import Data.List(isPrefixOf, group, zipWith4) import System.IO import Data.Dynamic+import Data.Bits hiding (rotate)+import Numeric(showIntAtBase, showOct)  import GHC.Generics hiding (K1) @@ -155,12 +158,14 @@                                                        -- Negative hints should also be implemented, but they should be kept separate from givenHints,                                                       -- in order to guess the behavior of algorithms that do not use such information.+                     , possibilities :: [[Possibilities]]                      , result :: Result               -- ^ The result of the last move. This info may be separated from 'PublicInfo' in future.                      } deriving (Read, Show, Eq, Generic)  -- | 'Marks' is the type synonym representing the hint trace of a card. type Marks = (Maybe Color, Maybe Number) +type Possibilities = (Int, Int)  -- | the best achievable rank for each color. bestPossibleRank :: PublicInfo -> Color -> Number@@ -195,33 +200,70 @@ isMoreObviouslyPlayable :: PublicInfo -> Marks -> Bool isMoreObviouslyPlayable pub = iOP (nonPublic pub) pub --- | In addition to 'isMoreObviouslyPlayable', 'isObviouslyPlayable' also looks at other players' hand and decides if the card is surely playable.-isObviouslyPlayable :: PrivateView -> Marks -> Bool-isObviouslyPlayable pv = iOP (invisibleBag pv) (publicView pv)+-- | In addition to 'isMoreObviouslyPlayable', 'isObviouslyPlayable' also looks into the color/number possibilities of the card and decides if the card is surely playable.+isObviouslyPlayable :: PublicInfo -> Possibilities -> Bool+isObviouslyPlayable pub (pc,pn) = all (\card -> (nonPublic pub IM.! cardToInt card) == 0 || isPlayable pub card)+                                      [ C color number | color <- colorPossibilities pc, number <- numberPossibilities pn ] +-- | In addition to 'isObviouslyPlayable', 'isDefinitelyPlayable' also looks at other players' hand and decides if the card is surely playable.+{- This is a weaker version not looking into the possibilities.+isDefinitelyPlayable :: PrivateView -> Marks -> Bool+isDefinitelyPlayable pv = iOP (invisibleBag pv) (publicView pv)+-}+isDefinitelyPlayable :: PrivateView -> Marks -> Possibilities -> Bool+isDefinitelyPlayable pv (Just c, Just n) _       = isPlayable (publicView pv) $ C c n -- The condition is indispensable, because now invisibleBag considers fully-marked cards visible.+isDefinitelyPlayable pv _                (pc,pn) = all (\card -> (invisibleBag pv IM.! cardToInt card) == 0 || isPlayable (publicView pv) card)+                                                       [ C color number | color <- colorPossibilities pc, number <- numberPossibilities pn ]++ iOP bag pub (Just c, Just n) = isPlayable pub $ C c n-iOP bag pub (Nothing,Just n) = all (isPlayable pub) [ card | color <- colors pub, let card = C color n, (bag IM.! cardToInt card) > 0 ]+iOP bag pub (Nothing,Just n) = all (\card -> (bag IM.! cardToInt card) == 0 || isPlayable pub card) [ C color n | color <- colors pub ] iOP _   _   _                = False  -- | 'isMoreObviouslyUseless' looks at the publicly available current info and decides if the card is surely useless.-isObviouslyUseless :: PublicInfo -> Marks -> Bool-isObviouslyUseless pub (Just c,  Just n)  = isUseless pub $ C c n-isObviouslyUseless pub (Just c,  Nothing) = bestPossibleRank pub c == achievedRank pub c-isObviouslyUseless pub (Nothing, Just n)  = all (\c -> n <= achievedRank pub c || bestPossibleRank pub c < n) $ colors pub-isObviouslyUseless pub (Nothing, Nothing) = False+isMoreObviouslyUseless :: PublicInfo -> Marks -> Bool+isMoreObviouslyUseless pub (Just c,  Just n)  = isUseless pub $ C c n+isMoreObviouslyUseless pub (Just c,  Nothing) = bestPossibleRank pub c == achievedRank pub c+isMoreObviouslyUseless pub (Nothing, Just n)  = all (\c -> n <= achievedRank pub c || bestPossibleRank pub c < n) $ colors pub+isMoreObviouslyUseless pub (Nothing, Nothing) = False +isObviouslyUseless :: PublicInfo -> Possibilities -> Bool+isObviouslyUseless pub (pc,pn) = all (\card@(C c n) -> n <= achievedRank pub c || bestPossibleRank pub c < n || (nonPublic pub IM.! cardToInt (C c n)) == 0 )+                                     [ C color number | color <- colorPossibilities pc, number <- numberPossibilities pn ]++isDefinitelyUseless :: PrivateView -> Marks -> Possibilities -> Bool+isDefinitelyUseless pv (Just c, Just n) _       = isUseless (publicView pv) $ C c n -- The condition is indispensable, because now invisibleBag considers fully-marked cards visible.+isDefinitelyUseless pv _                (pc,pn) = all (\card@(C c n) -> n <= achievedRank pub c || bestPossibleRank pub c < n || (invisibleBag pv IM.! cardToInt card) == 0 )+                                                      [ C color number | color <- colorPossibilities pc, number <- numberPossibilities pn ]+                            where pub = publicView pv++{- This is a weaker version not looking into the possibilities.+isDefinitelyUseless :: PrivateView -> Marks -> Bool+isDefinitelyUseless pv (Just c,  Just n)  = isUseless (publicView pv) $ C c n+isDefinitelyUseless pv (Just c,  Nothing) = all ((==0) . (invisibleBag pv IM.!) . cardToInt . C c . toEnum) [ succ $ fromEnum $ achievedRank (publicView pv) c .. fromEnum $ bestPossibleRank (publicView pv) c ] +isDefinitelyUseless pv (Nothing, Just n)  = all (\c -> n <= achievedRank (publicView pv) c || bestPossibleRank (publicView pv) c < n || (invisibleBag pv IM.! cardToInt (C c n)) == 0 ) $ colors $ publicView pv+isDefinitelyUseless pv (Nothing, Nothing) = all (\c -> all ((==0) . (invisibleBag pv IM.!) . cardToInt . C c . toEnum) [ succ $ fromEnum $ achievedRank (publicView pv) c .. fromEnum $ bestPossibleRank (publicView pv) c ]) $ colors $ publicView pv+-}+-- In fact, invisibleBag should be included in PrivateView for efficiency of isDefinitelyUseless, etc., but should not be sent via WebSocket. This is the matter of Read and Show (or ToJSON and FromJSON).++ -- | @'choppiri' marks@ = [unmarked last, unmarked second last, ...]. This is "beginner players' idea of chops".  choppiri :: [Marks] -> [(Index, Marks)] choppiri = reverse . filter (not . isHinted . snd) . zip [0..] --- | In addition to 'choppiri', 'chopss' considers 'isObviouslyUseless'. Since "from which card to drop among obviously-useless cards" depends on conventions, cards with the same uselessness are wrapped in a list within the ordered list.-chopss :: PublicInfo -> [Marks] -> [[(Index, Marks)]]-chopss pub hls = (if null useless then id else (useless :)) $ map (:[]) (choppiri hls)-   where useless = filter (isObviouslyUseless pub . snd) (zip [0..] hls)--- | 'chops' is the flattened version of 'chopss'-chops :: PublicInfo -> [Marks] -> [(Index, Marks)]-chops pub hls = concat $ map reverse $ chopss pub hls+-- | In addition to 'choppiri', 'definiteChopss' and 'obviousChopss' consider 'isDefinitelyUseless' and 'isObviouslyUseless' respectively. Since "from which card to drop among obviously-useless cards" depends on conventions, cards with the same uselessness are wrapped in a list within the ordered list.+definiteChopss :: PrivateView -> [Marks] -> [Possibilities] -> [[Index]]+definiteChopss pv hls ps = (if null useless then id else (useless :)) $ map ((:[]) . fst) (choppiri hls)+   where useless = map fst $ filter (uncurry (isDefinitelyUseless pv) . snd) (zip [0..] $ zip hls ps)+obviousChopss :: PublicInfo -> [Marks] -> [Possibilities] -> [[Index]]+obviousChopss pub hls ps = (if null useless then id else (useless :)) $ map ((:[]) . fst) (choppiri hls)+   where useless = map fst $ filter (isObviouslyUseless pub . snd) (zip [0..] ps)+-- | 'chops' is the flattened version of 'obviousChopss'+chops :: PublicInfo -> [Marks] -> [Possibilities] -> [Index]+chops pub hls ps = concat $ map reverse $ obviousChopss pub hls ps +isDoubleDrop pv (Discard c) [i] (pc,pn) | isCritical (publicView pv) c = color c `elem` colorPossibilities pc && number c `elem` numberPossibilities pn && (invisibleBag pv IM.! cardToInt c) > 0+isDoubleDrop _pv _lastresult _chopset _ps = False  colors :: PublicInfo -> [Color] colors pub = take (numColors $ rule $ gameSpec pub) [minBound .. maxBound]@@ -250,56 +292,95 @@                       , handsPV :: [[Card]]           -- ^ Other players' hands. [next player's hand, second next player's hand, ...]                                                       --   This is based on the viewer's viewpoint (unlike 'hands' which is based on the current player's viewpoint),                                                       --   and the view history @[PrivateView]@ must be from the same player's viewpoint (as the matter of course).-                      } deriving (Read, Show, Eq, Generic)--- | 'invisibleBag' returns the bag of unknown cards (which are either in the pile or in the player's hand and not fully hinted).-invisibleBag :: PrivateView-                -> IM.IntMap Int -- ^ @'Card' -> Int@-invisibleBag pv = foldr (IM.update (Just . pred)) (nonPublic $ publicView pv) $ map cardToInt $ concat $ [ C c n | (Just c, Just n) <- head $ givenHints $ publicView pv ] : handsPV pv+                      , invisibleBag :: IM.IntMap Int -- ^ @'Card' -> Int@. 'invisibleBag' is the bag of unknown cards (which are either in the pile or in the player's hand and not fully hinted).+                      } deriving (Generic) -- ToDo: Instance for Generic should also be specialized for efficiency.+instance Show PrivateView where+  showsPrec p (PV pub h _) = showsPrec p (pub,h)+instance Read PrivateView where+  readsPrec p str = [ (mkPV pub hs, rest) | ((pub,hs), rest) <- readsPrec p str ]+instance Eq PrivateView where+  PV pub1 hs1 _ == PV pub2 hs2 _ = (pub1,hs1) == (pub2,hs2) +-- | 'mkPV' is the constructor of PrivateView.+mkPV pub hs = PV pub hs $ foldr (IM.update (Just . pred)) (nonPublic pub) $ map cardToInt $ concat $ [ C c n | (Just c, Just n) <- head $ givenHints pub ] : hs+ prettyPV :: Verbosity -> PrivateView -> String prettyPV v pv@PV{publicView=pub} = prettyPI pub ++ "\nMy hand:\n"-                                              ++ concat (replicate (length myHand) " __") ++ "\n"---                                              ++ concat [ if markObviouslyPlayable v && isObviouslyPlayable pv h then " _^" else " __" | h <- myHand ] ++"\n"-                                              ++ concat (replicate (length myHand) "|**") ++ "|\n"-                                              ++ (if markHints v then showHintLine myHand else "")+                                              ++ concat (replicate (length myHand) $ wrap "+--+") ++ "\n"+--                                              ++ concat [ if markObviouslyPlayable v && isDefinitelyPlayable pv h then " _^" else " __" | h <- myHand ] ++"\n"+                                              ++ concat (replicate (length myHand) $ wrap "|**|") ++ "\n"+                                              ++ (if markHints v then showHintLine wrap myHand else "")                                      -- x         ++ concat (replicate (length myHand) " ~~") ++ "\n" -- x                                             ++ concat [ '+':shows d "-" | d <- [0 .. pred $ length myHand] ]-                                              ++ concat [ '+':(if markChops v && d `elem` map fst (concat $ take 1 $ chopss pub myHand) then ('X':) else shows d)-                                                                      (if markObviouslyUseless  v && isObviouslyUseless pub h then "." else-                                                                       if markObviouslyPlayable v && isObviouslyPlayable pv h then "^" else "-") | (d,h) <- zip [0..] myHand ]+                                              ++ concat [ wrap $+                                                          '+':(if warnDoubleDrop v && isDoubleDrop pv (result pub) chopSet p && d `elem` chopSet then ('D':) else+                                                               if markChops v && d `elem` chopSet then ('X':) else shows d)+                                                                      (if markObviouslyUseless  v && isDefinitelyUseless pv h p then ".+" else+                                                                       if markObviouslyPlayable v && isDefinitelyPlayable pv h p then "^+" else "-+") | (d,h,p) <- zip3 [0..] myHand myPos ] -                                              ++ concat (zipWith3 (prettyHand v pub (ithPlayer $ numPlayers $ gameSpec pub)) [1..] (handsPV pv) $ tail $ givenHints pub)+                                              ++"\n"+                                              ++ (if markPossibilities v then showPosLines $ head $ possibilities pub else "")+                                              ++ concat (zipWith4 (prettyHand v pub (ithPlayer $ numPlayers $ gameSpec pub)) [1..] (handsPV pv) (tail $ givenHints pub) (tail $ possibilities pub))++"\n"   where myHand = head (givenHints pub)-prettySt ithP st@St{publicState=pub} = prettyPI pub ++ concat (zipWith3 (prettyHand verbose pub (ithP $ numPlayers $ gameSpec pub)) [0..] (hands st) $ givenHints pub)-verbose = V{warnCritical=True,markUseless=True,markPlayable=True,markObviouslyUseless=True,markObviouslyPlayable=True,markHints=True,markChops=True}-prettyHand :: Verbosity -> PublicInfo -> (Int->String) -> Int -> [Card] -> [Marks] -> String-prettyHand v pub ithPnumP i cards hl = "\n\n" ++ ithPnumP i ++ " hand:\n"+        myPos  = head (possibilities pub)+        wrap xs | markPossibilities v = "  "++xs++" "+                | otherwise           = xs+        chopSet = concat $ take 1 $ definiteChopss pv myHand myPos+prettySt ithP st@St{publicState=pub} = prettyPI pub ++ concat (zipWith4 (prettyHand verbose pub (ithP $ numPlayers $ gameSpec pub)) [0..] (hands st) (givenHints pub) (possibilities pub))+verbose = V{warnCritical=True,markUseless=True,markPlayable=True,markObviouslyUseless=True,markObviouslyPlayable=True,markHints=True,markPossibilities=True,markChops=True,warnDoubleDrop=True}+prettyHand :: Verbosity -> PublicInfo -> (Int->String) -> Int -> [Card] -> [Marks] -> [Possibilities] -> String+prettyHand v pub ithPnumP i cards hl ps = "\n\n" ++ ithPnumP i ++ " hand:\n" --                          ++ concat (replicate (length cards) " __") ++ " \n"-                          ++ concat [ if markUseless v && isUseless pub card then " .."+                          ++ concat [ wrap $+                                      if markUseless v && isUseless pub card then "+..+"                                       else case (warnCritical v && tup==(Nothing,Nothing) && isCritical pub card, markPlayable v && isPlayable pub card) of-                                             (True, True)  -> " !^"-                                             (True, False) -> " !!"-                                             (False,True)  -> " _^"-                                             (False,False) -> " __"+                                             (True, True)  -> "+!^+"+                                             (True, False) -> "+!!+"+                                             (False,True)  -> "+-^+"+                                             (False,False) -> "+--+"                                     | (card, tup) <- zip cards hl ] ++"\n"-                          ++ concat [ '|':show card | card <- cards ] ++"|\n"-                          ++ (if markHints v then showHintLine hl else "")+                          ++ concat [ wrap $ '|':shows card "|" | card <- cards ] ++"\n"+                          ++ (if markHints v then showHintLine wrap hl else "") -- x                          ++ concat (replicate (length cards) "+--")-                          ++ concat [ '+':(if markChops v && map fst (take 1 $ chops pub hl) == [d] then ('X':) else ('-':))-                                             (if markObviouslyUseless  v && isObviouslyUseless      pub h then "." else-                                              if markObviouslyPlayable v && isMoreObviouslyPlayable pub h then "^" else "-") | (d,h) <- zip [0..] hl ]+                          ++ concat [ wrap $+                                      '+':(if markChops v && d `elem` (concat $ take 1 $ obviousChopss pub hl ps) then ('X':) else ('-':))+                                             (if markObviouslyUseless  v && isObviouslyUseless  pub h then ".+" else+                                              if markObviouslyPlayable v && isObviouslyPlayable pub h then "^+" else "-+") | (d,h) <- zip [0..] ps ]++"\n"+                          ++ (if markPossibilities v then showPosLines ps else "")+                    where wrap xs | markPossibilities v = "  "++xs++" "+                                  | otherwise           = {- take 3 -} xs -showHintLine :: [Marks] -> String-showHintLine hl = '|' : concat [ maybe ' ' (head . show) mc : maybe ' ' (head . show . fromEnum) mk : "|" | (mc,mk) <- hl] ++ "\n" +showHintLine :: (String -> String) -> [Marks] -> String+showHintLine wrapper hl = concat [ wrapper $ '|' : maybe ' ' (head . show) mc : maybe ' ' (head . show . fromEnum) mk : "|" | (mc,mk) <- hl] ++ "\n"+showPosLines :: [Possibilities] -> String+showPosLines ps = concat [ ' ' : showColorPossibilities  cs | (cs,_) <- ps] ++ "\n"+               ++ concat [ showNumberPossibilities ns ++" " | (_,ns) <- ps]++showColorPossibilities  = reverse . showPossibilities ' ' colorkeys+showNumberPossibilities = reverse . showPossibilities ' ' "54321 "+colorkeys = map (head . show) [maxBound, pred maxBound .. minBound::Color] -- colorkeys == "MBGRYW", but I just prefer to make this robust to changes in the order.+showPossibilities _     []     pos = []+showPossibilities blank (x:xs) pos = (if odd pos then x else blank) : showPossibilities blank xs (pos `div` 2)++colorPossibilities  :: Int -> [Color] -- The result is in the reverse order but I do not care.+colorPossibilities  = concat . showPossibilities [] (map (:[]) [maxBound, pred maxBound .. minBound])+numberPossibilities :: Int -> [Number] -- The result is in the reverse order but I do not care.+numberPossibilities = concat . showPossibilities [] (map (:[]) [K5,K4 .. K1])+++ -- | 'Verbosity' is the set of options used by verbose 'Strategy's data Verbosity = V { warnCritical :: Bool -- ^ mark unhinted critical cards with "!!" ("!^" if it is playable and markPlayable==True.)-                   , markUseless  :: Bool -- ^ mark useless  cards with ".."+                   , markUseless  :: Bool -- ^ mark useless  cards with "..".                    , markPlayable :: Bool -- ^ mark playable cards with "_^". ("!^" if it is unhinted critical and warnCritical==True.)                    , markObviouslyUseless  :: Bool -- ^ mark useless  cards with "_." based on the hint marks.                    , markObviouslyPlayable :: Bool -- ^ mark playable cards with "_^" based on the hint marks.                    , markChops     :: Bool -- ^ mark the chop card(s) with "X". All obviously-useless cards will be marked, if any.-                   , markHints    :: Bool -- ^ mark hints.+                   , warnDoubleDrop:: Bool -- ^ mark the chop card with "D" when dropping it is double-dropping.+                   , markHints     :: Bool -- ^ mark hints.+                   , markPossibilities :: Bool -- ^ mark more detailed hints based on the positive/negative hint history.+                                               --   markPossibilities == True && markHints == False is a reasonable choice, though markHints should still be informative for guessing other players' behavior.                    } deriving (Read, Show, Eq, Generic)  @@ -320,8 +401,7 @@             ++ "\ndropped: " ++ concat [ '|' : concat (replicate n $ show $ intToCard ci) | (ci,n) <- IM.toList $ discarded pub ] ++"|\n"  view :: State -> PrivateView-view st = PV {publicView = publicState st,-              handsPV    = tail $ hands st}+view st = mkPV (publicState st) (tail $ hands st)  main = selfplay defaultGS @@ -536,6 +616,7 @@                                             hintTokens = 8,                                             deadline   = Nothing,                                             givenHints = replicate (numPlayers gs) $ replicate (numPlayerHand gs) (Nothing, Nothing),+                                            possibilities = replicate (numPlayers gs) $ replicate (numPlayerHand gs) (unknown gs),                                             result     = None                                            },                            pile  = stack,@@ -549,6 +630,8 @@                [ (C i k, n) | i <- [White .. pred Multicolor], (k,n) <- numAssoc ] ++ [ (C Multicolor k, n) | (k, n) <- zip [K1 ..K5] (numMulticolors rule) ] cardBag rule = concat         [ replicate n c | (c,n) <- cardAssoc rule ] cardMap rule = IM.fromList [ (cardToInt c, n) | (c,n) <- cardAssoc rule ]+unknown :: GameSpec -> Possibilities+unknown gs = (64 - bit (6 - numColors (rule gs)),  31)  shuffle :: RandomGen g => [c] -> g -> ([c], g) shuffle xs = shuf [] xs $ length xs@@ -578,10 +661,12 @@   -- only used by Drop and Play   (nth, droppedHand) = pickNth (index mv) playersHand where playersHand = head $ hands st   (_  , droppedHint) = pickNth (index mv) playersHint where playersHint = head $ givenHints pub-  (nextHand,nextHint,nextPile, nextPileNum) = case pile st of []   -> (droppedHand,   droppedHint,                   [], 0)-                                                              d:ps -> (d:droppedHand, (Nothing,Nothing):droppedHint, ps, pred $ pileNum pub)+  (_  , droppedPos)  = pickNth (index mv) playersPos  where playersPos  = head $ possibilities pub+  (nextHand,nextHint,nextPos,nextPile, nextPileNum) = case pile st of []   -> (droppedHand,   droppedHint,                   droppedPos,              [], 0)+                                                                      d:ps -> (d:droppedHand, (Nothing,Nothing):droppedHint, unknown gS : droppedPos, ps, pred $ pileNum pub)   nextHands = nextHand : tail (hands st)   nextHints = nextHint : tail (givenHints pub)+  nextPoss  = nextPos  : tail (possibilities pub)   nextDeadline = case deadline pub of Nothing | nextPileNum==0 && not (prolong $ rule $ gameSpec pub) -> Just $ numPlayers gS                                               | otherwise                                             -> Nothing                                       Just i  -> Just $ pred i@@ -593,6 +678,7 @@                                       turn       = succ $ turn pub,                                       hintTokens = succ $ hintTokens pub,                                       givenHints = nextHints,+                                      possibilities = nextPoss,                                       deadline   = nextDeadline,                                       result     = Discard nth}}   prc (Play i) | failure   = let newst@St{publicState=newpub} = prc (Drop i) in newst{publicState=newpub{hintTokens = hintTokens pub, lives = pred $ lives pub, result = Fail nth}}@@ -604,12 +690,14 @@                                                   turn       = succ $ turn pub,                                                   hintTokens = if hintTokens pub < 8 && number nth == K5 then succ $ hintTokens pub else hintTokens pub,                                                   givenHints = nextHints,+                                                  possibilities = nextPoss,                                                   deadline   = nextDeadline,                                                   result = Success nth}}     where failure = not $ isPlayable pub nth   prc (Hint hintedpl eik) = st{publicState = pub{hintTokens = pred $ hintTokens pub,                                                  turn       = succ $ turn pub,                                                  givenHints = snd $ updateNth hintedpl newHints (givenHints pub),+                                                 possibilities = snd $ updateNth hintedpl newPoss (possibilities pub),                                                  deadline   = case deadline pub of Nothing -> Nothing                                                                                    Just i  -> Just $ pred i,                                                  result     = None}}@@ -617,11 +705,19 @@           zipper (C ir ka) (mi,mk) = case eik of Left  i | i==ir -> (Just i, mk)                                                  Right k | k==ka -> (mi, Just k)                                                  _               -> (mi,     mk)+          newPoss  hs = zipWith zipperPos (hands st !! hintedpl) hs+          zipperPos (C ir ka) (c,n) = case eik of Left i  | i == ir -> (bit ibit,        n)+                                                          |otherwise-> (clearBit c ibit, n)+                                                       where ibit = 5 - fromEnum i+                                                  Right k | k == ka -> (c, bit kbit)+                                                          |otherwise-> (c, clearBit n kbit)+                                                       where kbit = 5 - fromEnum k + -- | @'rotate' num@ rotates the first person by @num@ (modulo the number of players). rotate :: Int -> State -> State rotate num st@(St{publicState=pub@PI{gameSpec=gS}}) = st{hands       = rotateList $ hands st,-                                                         publicState = pub{givenHints = rotateList $ givenHints pub}}+                                                         publicState = pub{givenHints = rotateList $ givenHints pub, possibilities = rotateList $ possibilities pub}}     where rotateList xs = case splitAt (num `mod` numPlayers gS) xs of (tk,dr) -> dr++tk  -- | 'EndGame' represents the game score, along with the info of how the game ended.
Game/Hanabi/Backend.lhs view
@@ -1,5 +1,5 @@ \begin{code}-{-# LANGUAGE CPP, TupleSections, MultiParamTypeClasses, FlexibleInstances #-}+{-# LANGUAGE CPP, TupleSections, MultiParamTypeClasses, FlexibleInstances, OverloadedStrings #-} -- Do not forget -threaded! -- @@ -46,6 +46,13 @@ import Snap.Http.Server.Config import Snap.Http.Server import Data.ByteString.UTF8(fromString)+#else+# ifdef WARP+import Network.Wai.Handler.WebSockets+import Network.Wai.Handler.Warp as Warp+import Network.Wai(responseLBS)+import Network.HTTP.Types(status400)+# endif #endif  #ifdef AESON@@ -97,11 +104,14 @@     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-    httpServe (setPort (port options) defaultConfig) $ runWebSocketsSnap+    httpServe (setPort (port options) defaultConfig) $ runWebSocketsSnap $ loop params #else-    runServer "127.0.0.1" (port options)+# 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-           $ loop params  data Params g = Params{gen :: g,                        mvTIDToMVH :: MVar (IntMap.IntMap ([MVar ViaWebSocket], MVar (Maybe (EndGame,[State],[Move])))),@@ -184,6 +194,7 @@ wordsBy pred xs = case break pred xs of (tk,  []) -> [tk]                                         (tk,_:dr) -> tk : wordsBy pred dr isWS = (`elem` ["0", "WS", "via WebSocket"])+ interpret :: RandomGen g =>              Maybe Verbosity -> String -> Params g -> IO () interpret mbVerb inp params
Game/Hanabi/Client.hs view
@@ -12,10 +12,11 @@ import           Data.Aeson   hiding (Success) import           GHC.Generics hiding (K1, from) import           Data.Bool+import           Data.Char(isSpace) import qualified Data.Map as M import qualified Data.IntMap as IM import Data.Maybe(fromJust, isNothing)-import Data.List(intersperse)+import Data.List(intersperse, zipWith4)  import           Miso         hiding (Fail) import           Miso.String  (MisoString)@@ -38,7 +39,7 @@  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},+    model  = Model{tboxval = Message "available", players = ["via WebSocket"], from = Just 0, rule = defaultRule, received = [], fullHistory = False, showVerbosity = False, verbosity = verbose},     update = updateModel,     view   = appView strNames $ version options,     subs   = [ websocketSub uri protocols HandleWebSocket ],@@ -65,7 +66,7 @@ updateModel :: Action -> Model -> Effect Action Model updateModel (HandleWebSocket (WebSocketMessage (Message m))) model   = noEff model{ received = {- take lenHistory $ -} decodeMsg m : received model }-updateModel (SendMessage msg) model = model{fullHistory=False} <# (-- connect uri protocols >>+updateModel (SendMessage msg) model = model{fullHistory=False, showVerbosity=False} <# (-- connect uri protocols >>                                                 send msg >> return Id) updateModel (UpdateTBoxVal m) model = noEff model{ tboxval = Message m } updateModel (From mbn)        model = noEff model{from = mbn}@@ -73,7 +74,9 @@ updateModel DecreasePlayers   model = noEff model{players = case players model of {n:ns@(_:_) -> ns; ns -> ns}} updateModel (UpdatePlayer ix pl) model = noEff model{players = snd $ replaceNth ix pl $ players model} updateModel (UpdateRule r)    model | isRuleValid r = noEff model{rule=r}+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} #ifdef DEBUG updateModel (HandleWebSocket act) model = noEff model{received = Str (show act) : received model } #endif@@ -94,7 +97,9 @@   | DecreasePlayers   | UpdatePlayer Int MisoString   | UpdateRule Rule+  | UpdateVerbosity Verbosity   | Toggle+  | ToggleVerbosity   | Id  data Model = Model {@@ -104,6 +109,8 @@   , rule :: Rule   , received :: [Msg]   , fullHistory :: Bool+  , showVerbosity :: Bool+  , verbosity :: Verbosity   } deriving (Show, Eq)  lenShownHistory = 10@@ -112,16 +119,42 @@    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.)"]- , span_ [style_ $ M.fromList [("font-size","10px"), ("float","right")]] [text $ S.pack $ "hanabi-dealer client "++versionInfo]++ , span_ [style_ $ M.fromList [("float","right")]] [+     button_ [ onClick ToggleVerbosity, id_ "verbutton" ] [ text $ if showVerbosity then "^" else "v" ]+   , label_ [for_ "verbutton"] [text "verbosity options" ]+   ]+ , if showVerbosity then span_ [style_ $ M.fromList [("clear","both"), ("float","right")]] [renderVerbosity verbosity] else span_[][]++ , span_ [style_ $ M.fromList [("clear","both"), ("font-size","10px"), ("float","right")]] [text $ S.pack $ "hanabi-dealer client "++versionInfo] -- , hr_ []- , div_ [style_ $ M.fromList [("clear","both")]] ((if fullHistory then id else take lenShownHistory) $ map (renderMsg strategies verbose mdl) received)+ , div_ [style_ $ M.fromList [("clear","both")]] ((if fullHistory then id else take lenShownHistory) $ map (renderMsg strategies verbosity mdl) received)  , div_ [] $ if null $ drop lenShownHistory received then [] else [      hr_ []    , input_ [ type_ "checkbox", id_ "showhist", onClick Toggle, checked_ fullHistory]    , label_ [for_ "showhist"] [text "Show full history"]    ]  ]+renderVerbosity :: Verbosity -> View Action+renderVerbosity v = div_ [] [+    mkChx v "mark unhinted critical cards" (\b -> v{warnCritical=b}) warnCritical,+    mkChx v "mark useless cards"           (\b -> v{markUseless=b})  markUseless,+    mkChx v "mark playable cards"          (\b -> v{markPlayable=b}) markPlayable,+    mkChx v "mark useless cards without looking at the cards"  (\b -> v{markObviouslyUseless=b}) markObviouslyUseless,  +    mkChx v "mark playable cards without looking at the cards" (\b -> v{markObviouslyPlayable=b}) markObviouslyPlayable,+    mkChx v "shade the chop card(s)"       (\b -> v{markChops=b})    markChops,+    mkChx v "warn possible double-dropping"(\b -> v{warnDoubleDrop=b}) warnDoubleDrop,+    mkChx v "mark hints"                   (\b -> v{markHints=b})    markHints,+    mkChx v "mark possibilities"           (\b -> v{markPossibilities=b}) markPossibilities+  ] ++mkChx verb label update access = div_ [] [+    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+ onEnter :: Action -> Attribute Action onEnter action = onKeyDown $ bool Id action . (== KeyCode 13) {-@@ -153,7 +186,7 @@ renderMsg _ verb _ (WhatsUp name ps ms) = renderWhatsUp  verb name ps ms renderMsg _ verb _ (WhatsUp1     p  m)  = renderWhatsUp1 verb p m renderMsg _ _    _ (PrettyEndGame Nothing)     = pre_ [] [ text $ S.pack $ prettyMbEndGame Nothing]-renderMsg _ _    _ (PrettyEndGame (Just tup))  = renderEndGame tup -- pre_ [] [ text $ S.pack $ prettyEndGame tup]+renderMsg _ verb _ (PrettyEndGame (Just tup))  = renderEndGame verb tup -- pre_ [] [ text $ S.pack $ prettyEndGame tup] renderMsg strategies _    mdl (PrettyAvailable games)   = div_ [style_ $ M.fromList [("overflow","auto")]] [       hr_ [],@@ -177,17 +210,26 @@ -- 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 . (++[rule mdl]) . map fst . reads . S.unpack), value_ $ S.pack $ show $ rule mdl, style_ $ M.fromList [("width","70%")]]],+              let mkTR label updater access = tr_ [] [                           td_ [] [text label],                           td_ [] [                              input_ [type_ "text", onInput (UpdateRule . updater . head . (++[access $ rule mdl]) . map fst . reads . S.unpack), value_ $ S.pack $ show $ access $ rule mdl]                             ]+                        ]+                 mkTRd label updater access options = tr_ [] [+                          td_ [] [text label],+                          td_ [] [+                             dropdownLiteral options (UpdateRule . updater) (access $ rule mdl)+                            ]                         ] in                table_ [solid] [                    caption_ [] [text "Rules"],-                   mkTR "Number of lives"                               (\n -> (rule mdl){numBlackTokens=n}) numBlackTokens,-                   mkTR "Number of colors"                              (\n -> (rule mdl){numColors=n})      numColors,-                   mkTR "Continue the game after the pile is exhausted" (\n -> (rule mdl){prolong=n})        prolong,+                   mkTRd "Number of lives"                               (\n -> (rule mdl){numBlackTokens=n}) numBlackTokens [1 .. 9],+                   mkTRd "Number of colors"                              (\n -> (rule mdl){numColors=n})      numColors      [1 .. 6],+                   mkTRd "Continue the game after the pile is exhausted" (\n -> (rule mdl){prolong=n})        prolong        [False,True],                    mkTR "numMulticolors"                                (\n -> (rule mdl){numMulticolors=n}) numMulticolors,                    mkTR "funPlayerHand"                                 (\n -> (rule mdl){funPlayerHand=n})  funPlayerHand                  ],@@ -198,9 +240,17 @@    ] renderPlayer :: [MisoString] -> Int -> MisoString -> View Action --renderPlayer _ i p = text p-renderPlayer strategies i p = select_ [onChange $ UpdatePlayer i] [ option_ [value_ p', selected_ $ p==p'] [text p'] | p' <- strategies ]+renderPlayer strategies i p = dropdown strategies (UpdatePlayer i) p +dropdown :: [MisoString] -> (MisoString->Action) -> MisoString -> View Action+dropdown options action selected = select_ [onChange action] [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 = dropdownLiteral [False,True]+-- but in most cases booleans should be selected by check boxes.+ 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 = style_ $ M.fromList [("border-style","solid")]@@ -209,7 +259,7 @@   hr_ [],   text $ S.pack "Your turn.",   hr_ [],-  renderRecentEvents (publicView v) ithPlayer views moves,+  renderRecentEvents verb (publicView v) ithPlayer views moves,   hr_ [],   text $ S.pack $ "Algorithm: " ++ name,   renderPV verb v@@ -217,25 +267,25 @@ renderWhatsUp1 :: Verbosity -> PrivateView -> Move -> View Action renderWhatsUp1 verb v m = div_ [style_ $ M.fromList [("background-color","#555555"),("color","#000000")]] [   hr_ [],-  renderTrial (publicView v) (const "") undefined v m,+  renderTrial verb (publicView v) (const "") undefined v m,   hr_ [],   renderPV verb v  ] -renderEndGame :: (EndGame, [State], [Move]) -> View Action-renderEndGame (eg,sts@(st:_),mvs)+renderEndGame :: Verbosity -> (EndGame, [State], [Move]) -> View Action+renderEndGame verb (eg,sts@(st:_),mvs)   = div_ [] [          hr_ [],-         renderRecentEvents (publicState st) ithPlayerFromTheLast (map Game.Hanabi.view sts) mvs,+         renderRecentEvents verb (publicState st) ithPlayerFromTheLast (map Game.Hanabi.view sts) mvs,          hr_ [],          h1_ [style_ $ M.fromList [("background-color","#FF0000"),("color","#000000")]] [text $ S.pack $ show eg],          hr_ [],-         renderSt ithPlayerFromTheLast st,+         renderSt verb ithPlayerFromTheLast st,          hr_ []          ] -renderTrial :: PublicInfo -> (Int -> String) -> Int -> PrivateView -> Move -> View Action-renderTrial pub ithP i v m =  div_ [] [+renderTrial :: Verbosity -> PublicInfo -> (Int -> String) -> Int -> PrivateView -> Move -> View Action+renderTrial verb pub ithP i v m =  div_ [] [   text $ S.pack $ ithP i ++ " move: " ++ {- replicate (length (ithP 2) - length (ithP i)) ' ' ++ -} show m   , case result $ publicView v of Discard c -> showResults c ", which revealed "                                   Success c -> showResults c ", which succeeded revealing "@@ -245,10 +295,10 @@   where showResults c xs = span_ [] [             xs --          , renderHand' verbose pub [Just c] [(Nothing,Nothing)]-          , renderCardInline verbose pub c+          , renderCardInline verb pub c           , text $ S.pack "."           ]-renderPI pub+renderPI verb pub {- This was too verbose   = let       showDeck 0 = "no card at the deck (the game will end in " ++ shows (fromJust $ deadline pub) " turn(s)), "@@ -265,64 +315,70 @@        div_ [] [         text $ S.pack $ "played:",-        span_ [] [ span_ [] $ text (S.pack "|") : [ renderCardInline verbose pub $ C c k | k <- [K1 .. playedMax]] | c <- [White .. Multicolor], Just playedMax <- [IM.lookup (fromEnum c) (played pub)] ],+        span_ [] [ span_ [] $ text (S.pack "|") : [ renderCardInline verb pub $ C c k | k <- [K1 .. playedMax]] | c <- [White .. Multicolor], Just playedMax <- [IM.lookup (fromEnum c) (played pub)] ],         text $ S.pack "|"       ],        div_ [] [         text $ S.pack $ "dropped: ",-        span_ [] [ span_ [] $ text (S.pack "|") : (replicate n $ renderCardInline verbose pub $ intToCard ci) | (ci,n) <- IM.toList $ discarded pub ],+        span_ [] [ span_ [] $ text (S.pack "|") : (replicate n $ renderCardInline verb pub $ intToCard ci) | (ci,n) <- IM.toList $ discarded pub ],         text $ S.pack "|"       ]      ]-renderCardInline v pub c = span_ [style_ $ M.fromList [("width","30px"),("color", colorStr $ Just $ color c),("background-color","#000000")]] [cardStr verbose pub 0 (Just c) (Nothing,Nothing)]+renderCardInline v pub c = span_ [style_ $ M.fromList [("width","30px"),("color", colorStr $ Just $ color c),("background-color","#000000")]] [cardStr v pub 0 (Just c) (Nothing,Nothing)] -renderRecentEvents :: PublicInfo -> (Int -> Int -> String) -> [PrivateView] -> [Move] -> View Action-renderRecentEvents pub ithP vs@(v:_) ms = div_ [] $ reverse $ zipWith3 (renderTrial pub $ ithP nump) [pred nump, nump-2..0] vs ms+renderRecentEvents :: Verbosity -> PublicInfo -> (Int -> Int -> String) -> [PrivateView] -> [Move] -> View Action+renderRecentEvents verb pub ithP vs@(v:_) ms = div_ [] $ reverse $ zipWith3 (renderTrial verb pub $ ithP nump) [pred nump, nump-2..0] vs ms    where nump = numPlayers $ gameSpec $ publicView v   renderPV :: Verbosity -> PrivateView -> View Action renderPV v pv@PV{publicView=pub} = div_ [] [-  renderPI pub,+  renderPI v pub,   div_ [] ( --      div_ [] [text $ S.pack $ "My hand:"] : --      renderCards v pub [ Nothing | _ <- myHand] myHand :-      renderHand v pv (const "My") 0 [ Nothing | _ <- myHand] myHand :+      renderHand v pv (const "My") 0 [ Nothing | _ <- myHand] myHand (head $ possibilities pub) : --                                              ++ concat [ '+':shows d "-" | d <- [0 .. pred $ length myHand] ]-      (zipWith3 (renderHand v pv (ithPlayer $ numPlayers $ gameSpec pub)) [1..] (map (map Just) $ handsPV pv) $ tail $ givenHints pub)+      (zipWith4 (renderHand v pv (ithPlayer $ numPlayers $ gameSpec pub)) [1..] (map (map Just) $ handsPV pv) (tail $ givenHints pub) (tail $ possibilities pub))     )   ]   where myHand = head (givenHints pub) -renderSt ithP st@St{publicState=pub} = div_ [] $-  renderPI pub :-  zipWith3 (renderHand verbose (Game.Hanabi.view st) (ithP $ numPlayers $ gameSpec pub)) [0..] (map (map Just) $ hands st) (givenHints pub)+renderSt verb ithP st@St{publicState=pub} = div_ [] $+  renderPI verb pub :+  zipWith4 (renderHand verb (Game.Hanabi.view st) (ithP $ numPlayers $ gameSpec pub)) [0..] (map (map Just) $ hands st) (givenHints pub) (possibilities pub) -renderHand :: Verbosity -> PrivateView -> (Int->String) -> Int -> [Maybe Card] -> [Marks] -> View Action-renderHand v pv ithPnumP i mbcards hl = div_ [] [+renderHand :: Verbosity -> PrivateView -> (Int->String) -> Int -> [Maybe Card] -> [Marks] -> [Possibilities] -> View Action+renderHand v pv ithPnumP i mbcards hl ps = div_ [] [   div_ [] [text $ S.pack $ ithPnumP i ++ " hand:"],-   renderHand' v pv i mbcards hl+   renderHand' v pv i mbcards hl ps --  renderCards v pub (map Just cards) hl   ]-renderHand' :: Verbosity -> PrivateView -> Int -> [Maybe Card] -> [Marks] -> View Action-renderHand' v pv pli mbcards hl = -   table_ [style_ $ M.fromList [("border-color","#FFFFFF"), ("border-width","medium")]] [tr_ [style_ $ M.fromList [("background-color","#000000"), ("height","48px")]] (zipWith3 (renderCard v pv pli hl) [0..] mbcards hl)]+renderHand' :: Verbosity -> PrivateView -> Int -> [Maybe Card] -> [Marks] -> [Possibilities] -> View Action+renderHand' v pv pli mbcards hl ps = +   table_ [style_ $ M.fromList [("border-color","#FFFFFF"), ("border-width","medium")]] [tr_ [style_ $ M.fromList [("background-color","#000000"), ("height","48px")]] (zipWith4 (renderCard v pv pli hl ps) [0..] mbcards hl ps)] -renderCard :: Verbosity -> PrivateView -> Int -> [Marks] -> Index -> Maybe Card -> Marks -> View Action-renderCard v pv pli hl i mbc tup@(mc,mk) = td_ [style_ $ M.fromList [("width","36px"),-                                                                   ("color", colorStr $ fmap color mbc)]] [+renderCard :: Verbosity -> PrivateView -> Int -> [Marks] -> [Possibilities] -> Index -> Maybe Card -> Marks -> Possibilities -> View Action+renderCard v pv pli hl ps i mbc tup@(mc,mk) ptup@(pc,pn) = td_ [style_ $ M.fromList [("width","4em"),+                                                                                     ("color", colorStr $ fmap color mbc)]] [      maybe (button_ [ onClick (SendMessage $ Message $ S.pack $ 'p':show i) ] [ text (S.pack "play") ]) (const $ span_[][]) mbc,      div_ [] [         cardStr v pub pli mbc tup      ],      div_ [myStyle] [text $ S.pack $ if markHints v then maybe '_' (head . show) mc : [maybe '_' (head . show . fromEnum) mk] else "__" ],-     maybe (button_ [ onClick (SendMessage $ Message $ S.pack $ 'd':show i) ] [ text (S.pack "drop") ]) (const $ span_[][]) mbc+     maybe (button_ [ onClick (SendMessage $ Message $ S.pack $ 'd':show i) ] [ text (S.pack "drop") ]) (const $ span_[][]) mbc,+     if markPossibilities v then div_ [style_ $ M.fromList [("font-size","0.8em")]] [text $ S.pack $ showColorPossibilities pc, br_[], text $ S.pack $ showNumberPossibilities pn] else span_[][]   ] where           pub = publicView pv-          myStyle = style_ $ M.fromList [ ("background-color",if markChops v && i `elem` map fst (concat $ take 1 $ chopss pub hl) then "#888888" else "#000000"),-                                          ("font-weight", if markObviouslyUseless v && isObviouslyUseless pub tup then "100" else "normal"),-                                          ("font-style",  if markObviouslyPlayable v && (if isNothing mbc then isObviouslyPlayable pv else isMoreObviouslyPlayable pub) tup then "oblique" else "normal")]+          myStyle | isNothing mbc = style_ $ M.fromList [ ("background-color", if warnDoubleDrop v && isDoubleDrop pv (result pub) chopSet ptup && i `elem` chopSet then "#880000" else+                                                                               if markChops v && i `elem` chopSet then "#888888" else "#000000"),+                                                          ("font-weight", if markObviouslyUseless v && isDefinitelyUseless pv tup ptup then "100" else "normal"),+                                                          ("font-style",  if markObviouslyPlayable v && isDefinitelyPlayable pv tup ptup then "oblique" else "normal")]+                  | otherwise     = style_ $ M.fromList [ ("background-color",if markChops v && i `elem` concat (take 1 $ obviousChopss pub hl ps) then "#888888" else "#000000"),+                                                          ("font-weight", if markObviouslyUseless v && isObviouslyUseless pub ptup then "100" else "normal"),+                                                          ("font-style",  if markObviouslyPlayable v && isObviouslyPlayable pub ptup then "oblique" else "normal")]+          chopSet = concat $ take 1 $ definiteChopss pv hl ps {- renderCards :: Verbosity -> PublicInfo -> [Maybe Card] -> [Marks] -> View Action renderCards v pub mbcs tups = table_ [style_ $ M.fromList [("border-color","#FFFFFF"),("border-width","medium")]] [
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.4.0.1+version:             0.5.0.0  -- A short (one-line) description of the package. synopsis:            Hanabi card game@@ -58,6 +58,9 @@   Default:     False Flag SNAP   Description: Use Snap instead of runServer.+  Default:     False+Flag WARP+  Description: Use Warp instead of runServer.   Default:     True FLAG TFRANDOM   Description: Use tf-random instead of random.@@ -69,6 +72,10 @@ FLAG TH   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.+               (Package miso must have been built with --flags="jsaddle".)+  Default:     False  library   -- Modules exported by the library.@@ -90,7 +97,7 @@   -- Base language which the package is written in.   default-language:    Haskell2010 -  if impl(ghcjs) || flag(server)+  if impl(ghcjs) || flag(jsaddle) || flag(server)     exposed-modules:     Game.Hanabi.VersionInfo     other-modules:       Game.Hanabi.Msg     -- cpp-options:      -DCABAL@@ -109,10 +116,14 @@     if flag(SNAP)       build-depends:       unix >=2.7 && <2.8, websockets-snap >=0.10 && <0.11, snap-server >=1.1 && <1.2, abstract-par >=0.3 && <0.4, monad-par >=0.3 && <0.4       cpp-options:         -DSNAP-  if impl(ghcjs)+    else+      if flag(WARP)+        build-depends:       wai-websockets, warp, wai, http-types+        cpp-options:         -DWARP+  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+      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" @@ -135,16 +146,20 @@     if flag(SNAP)       build-depends:       unix >=2.7 && <2.8, websockets-snap >=0.10 && <0.11, snap-server >=1.1 && <1.2, abstract-par >=0.3 && <0.4, monad-par >=0.3 && <0.4       cpp-options:         -DSNAP+    else+      if flag(WARP)+        build-depends:       wai-websockets, warp, wai, http-types+        cpp-options:         -DWARP  executable client   main-is:       client.hs   other-modules: Game.Hanabi.Msg, Game.Hanabi.Client, Game.Hanabi.VersionInfo, Game.Hanabi.Strategies.SimpleStrategy-  if !impl(ghcjs)+  if !impl(ghcjs) && !flag(jsaddle)     buildable: False   else     ghcjs-options:      -dedupe -O     cpp-options:        -DCABAL-    build-depends:      base >=4.12 && <4.13, containers >=0.5, random >=1.1, jsaddle-warp >=0.9, aeson, miso, time, hanabi-dealer+    build-depends:      base >=4.12 && <4.13, containers >=0.5, random >=1.1, jsaddle-warp >=0.9, aeson, miso, time >=1.6, hanabi-dealer     default-language:   Haskell2010     if flag(official)       cpp-options:  -DURI="ws://133.54.228.39:8720"