packages feed

hanabi-dealer 0.9.1.0 → 0.10.1.0

raw patch · 10 files changed

+299/−164 lines, 10 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Game.Hanabi: [discarded] :: PublicInfo -> IntMap Int
- Game.Hanabi: [number] :: Card -> Number
- Game.Hanabi: [played] :: PublicInfo -> IntMap Number
- Game.Hanabi: currentScore :: PublicInfo -> Int
- Game.Hanabi: data Number
- Game.Hanabi: instance GHC.Classes.Eq Game.Hanabi.Number
- Game.Hanabi: instance GHC.Classes.Ord Game.Hanabi.Number
- Game.Hanabi: instance GHC.Enum.Bounded Game.Hanabi.Number
- Game.Hanabi: instance GHC.Enum.Enum Game.Hanabi.Number
- Game.Hanabi: instance GHC.Generics.Generic Game.Hanabi.Number
- Game.Hanabi: instance GHC.Read.Read Game.Hanabi.Number
- Game.Hanabi: instance GHC.Show.Show Game.Hanabi.Number
+ Game.Hanabi: [currentScore] :: PublicInfo -> Int
+ Game.Hanabi: [kept] :: PublicInfo -> CardTo4
+ Game.Hanabi: [nextToPlay] :: PublicInfo -> CardTo4
+ Game.Hanabi: [rank] :: Card -> Rank
+ Game.Hanabi: cardBag :: Rule -> [Card]
+ Game.Hanabi: data Rank
+ Game.Hanabi: discarded :: PublicInfo -> Card -> Int
+ Game.Hanabi: instance GHC.Classes.Eq Game.Hanabi.Rank
+ Game.Hanabi: instance GHC.Classes.Ord Game.Hanabi.Rank
+ Game.Hanabi: instance GHC.Enum.Bounded Game.Hanabi.Rank
+ Game.Hanabi: instance GHC.Enum.Enum Game.Hanabi.Rank
+ Game.Hanabi: instance GHC.Generics.Generic Game.Hanabi.Rank
+ Game.Hanabi: instance GHC.Read.Read Game.Hanabi.Rank
+ Game.Hanabi: instance GHC.Show.Show Game.Hanabi.Rank
+ Game.Hanabi: rankToBitPos :: Rank -> Int
+ Game.Hanabi: readsRankChar :: ReadS Rank
+ Game.Hanabi: showRankPossibilities :: Int -> String
+ Game.Hanabi: type Number = Rank
- Game.Hanabi: C :: Color -> Number -> Card
+ Game.Hanabi: C :: Color -> Rank -> Card
- Game.Hanabi: Empty :: Number
+ Game.Hanabi: Empty :: Rank
- Game.Hanabi: Hint :: Int -> Either Color Number -> Move
+ Game.Hanabi: Hint :: Int -> Either Color Rank -> Move
- Game.Hanabi: K1 :: Number
+ Game.Hanabi: K1 :: Rank
- Game.Hanabi: K2 :: Number
+ Game.Hanabi: K2 :: Rank
- Game.Hanabi: K3 :: Number
+ Game.Hanabi: K3 :: Rank
- Game.Hanabi: K4 :: Number
+ Game.Hanabi: K4 :: Rank
- Game.Hanabi: K5 :: Number
+ Game.Hanabi: K5 :: Rank
- Game.Hanabi: PI :: GameSpec -> Int -> IntMap Number -> IntMap Int -> IntMap Int -> Int -> Int -> Int -> Maybe Int -> [[Annotation]] -> Result -> PublicInfo
+ Game.Hanabi: PI :: GameSpec -> Int -> Int -> CardTo4 -> CardTo4 -> CardTo4 -> Int -> Int -> Int -> Maybe Int -> [[Annotation]] -> Result -> PublicInfo
- Game.Hanabi: PV :: PublicInfo -> [[Card]] -> IntMap Int -> PrivateView
+ Game.Hanabi: PV :: PublicInfo -> [[Card]] -> CardTo4 -> PrivateView
- Game.Hanabi: [invisibleBag] :: PrivateView -> IntMap Int
+ Game.Hanabi: [invisibleBag] :: PrivateView -> CardTo4
- Game.Hanabi: [nonPublic] :: PublicInfo -> IntMap Int
+ Game.Hanabi: [nonPublic] :: PublicInfo -> CardTo4
- Game.Hanabi: achievedRank :: PublicInfo -> Color -> Number
+ Game.Hanabi: achievedRank :: PublicInfo -> Color -> Rank
- Game.Hanabi: bestPossibleRank :: PublicInfo -> Color -> Number
+ Game.Hanabi: bestPossibleRank :: PublicInfo -> Color -> Rank
- Game.Hanabi: numberToBitPos :: Number -> Int
+ Game.Hanabi: numberToBitPos :: Rank -> Int
- Game.Hanabi: readsNumberChar :: ReadS Number
+ Game.Hanabi: readsNumberChar :: ReadS Rank
- Game.Hanabi: type Marks = (Maybe Color, Maybe Number)
+ Game.Hanabi: type Marks = (Maybe Color, Maybe Rank)

Files

ChangeLog.md view
@@ -1,5 +1,18 @@ 	# Revision history for hanabi-dealer +	## 0.10.1.0 -- 2020-09-08++	* slightly improve the GUI++	## 0.10.0.0 -- 2020-05-26++	* improve the efficiency by using Data.Bits instead of IntMap+	  This changes PublicInfo and PrivateView.++	* add the preset-deck functionality to the GUI++	* show both definiteChopss and obviousChopss of the player's hand+ 	## 0.9.1.0 -- 2020-05-05  	* add efficiency-improved end-game search agents
Game/Hanabi.hs view
@@ -11,21 +11,20 @@               -- ** The Game Specification               GameSpec(..), defaultGS, Rule(..), defaultRule, isRuleValid, makeRuleValid, colors, handSize, setHandSize,               -- ** The Game State and Interaction History-              Move(..), Index, State(..), PrivateView(..), PublicInfo(..), Result(..), EndGame(..),+              Move(..), Index, State(..), PrivateView(..), PublicInfo(..), Result(..), EndGame(..), discarded,               -- ** The Cards and Annotations-              Card(..), Color(..), Number(..), Marks, Possibilities, Annotation(..), cardToInt, intToCard, readsColorChar, readsNumberChar, colorToBitPos, numberToBitPos,+              Card(..), Color(..), Rank(..), Marks, Possibilities, Annotation(..), cardToInt, intToCard, readsColorChar, readsRankChar, colorToBitPos, rankToBitPos,               -- * Utilities               -- ** Hints-              isCritical, isUseless, bestPossibleRank, achievedRank, isPlayable, isHinted, currentScore, seeminglyAchievableScore, moreStrictlyAchievableScore, achievableScore,+              isCritical, isUseless, bestPossibleRank, achievedRank, isPlayable, isHinted, seeminglyAchievableScore, moreStrictlyAchievableScore, achievableScore,               definitely, obviously,               isMoreObviouslyUseless, isObviouslyUseless, isDefinitelyUseless, isDefinitelyUncritical, isDefinitelyCritical, isMoreObviouslyPlayable, isObviouslyPlayable, isDefinitelyPlayable, isObviouslyUnplayable, isDefinitelyUnplayable, obviousChopss, definiteChopss, isDoubleDrop, possibleCards, endGameMove, EndGameStrategy(..), EndGameMirrorStrategy(..), egms, EndGameLite(..), egl, EndGameMirrorLite(..), egml,               tryMove, (|||), ifA,-              -- ** Legacy functions-              givenHints, possibilities_until_Ver0720,+              -- ** Legacy functions and types+              givenHints, possibilities_until_Ver0720, Number, readsNumberChar, numberToBitPos, showNumberPossibilities,               -- ** Minor ones-              what'sUp, what'sUp1, ithPlayer, recentEvents, prettyPI, prettySt, ithPlayerFromTheLast, view, replaceNth, shuffle, showPossibilities, showColorPossibilities, showNumberPossibilities, showTrial, showDeck) where+              what'sUp, what'sUp1, ithPlayer, recentEvents, prettyPI, prettySt, ithPlayerFromTheLast, view, replaceNth, shuffle, showPossibilities, showColorPossibilities, showRankPossibilities, showTrial, showDeck, cardBag) where -- module Hanabi where-import qualified Data.IntMap as IM import qualified Data.Map as M import System.Random import Control.Applicative((<*>))@@ -38,12 +37,15 @@ import System.IO import Data.Dynamic import Data.Bits hiding (rotate)+import Data.Int(Int64)  import GHC.Generics hiding (K1) -data Number  = Empty | K1 | K2 | K3 | K4 | K5 deriving (Eq, Ord, Show, Read, Enum, Bounded, Generic)+data Rank  = Empty | K1 | K2 | K3 | K4 | K5 deriving (Eq, Ord, Show, Read, Enum, Bounded, Generic) data Color = White | Yellow | Red | Green | Blue | Multicolor   deriving (Eq, Ord, Show, Read, Enum, Bounded, Generic)+type Number = Rank+ readsColorChar :: ReadS Color readsColorChar (c:str)   | isSpace c = readsColorChar str@@ -51,26 +53,30 @@                            Nothing -> []                            Just i  -> [(i, str)] readsColorChar [] = []-readsNumberChar :: ReadS Number-readsNumberChar xs = [ (toEnum d, rest) | (d, rest) <- reads xs, d<=5 ]+readsRankChar :: ReadS Rank+readsRankChar xs = [ (toEnum d, rest) | (d, rest) <- reads xs, d<=5 ]+readsNumberChar = readsRankChar -data Card = C {color :: Color, number :: Number} deriving (Eq, Ord, Generic)+data Card = C {color :: Color, rank :: Rank} deriving (Eq, Ord, Generic) instance Show Card where   showsPrec _ (C color number) = (head (show color) :) . (show (fromEnum number) ++)   showList = foldr (.) id . map shows instance Read Card where-  readsPrec _ str = [(C i k, rest) | (i, xs) <- readsColorChar str, (k, rest) <- readsNumberChar xs]+  readsPrec _ str = [(C i k, rest) | (i, xs) <- readsColorChar str, (k, rest) <- readsRankChar xs]   readList xs = case reads xs of []       -> [([],xs)]                                  [(c,ys)] -> [ (c:cs, zs) | (cs,zs) <- readList ys ] cardToInt :: Card -> Int-cardToInt c = fromEnum (color c) * (succ $ fromEnum (maxBound::Number)) + fromEnum (number c)+cardToInt c = fromEnum (color c) * succ (fromEnum (maxBound::Rank)) + fromEnum (rank c)+-- cardToQitPos is a variant of cardToInt whose value is less than 32. Since the Empty rank is never used for a card, there is no reason to use cardToInt, but it exists mainly for backward compatibility.+-- cardToQitPos c = fromEnum (color c) * fromEnum (maxBound::Rank) + pred (fromEnum $ rank c)+cardToQitPos c = pred (fromEnum $ rank c) * (succ $ fromEnum (maxBound :: Color)) + fromEnum (color c) -- This is preferred over the above for efficient implementation of bestPossibleRank. intToCard :: Int -> Card-intToCard i = case i `divMod` (succ $ fromEnum (maxBound::Number)) of (c,k) -> C (toEnum c) (toEnum k)+intToCard i = case i `divMod` (succ $ fromEnum (maxBound::Rank)) of (c,k) -> C (toEnum c) (toEnum k) type Index = Int -- starts from 0  data Move = Drop {index::Index}            -- ^ drop the card (0-origin)           | Play {index::Index}            -- ^ play the card (0-origin)-          | Hint Int (Either Color Number) -- ^ give hint to the ith next player+          | Hint Int (Either Color Rank) -- ^ give hint to the ith next player             deriving (Eq, Ord, Generic) instance Show Move where     showsPrec _ (Drop i) = ("Drop"++) . shows i@@ -92,7 +98,7 @@                  where parseHint xs = [(Hint i eith, rest) | let (istr, ys) = splitAt 1 $ dropWhile isSpace xs -- These two lines is similar to @(i, ys) <- reads xs@,                                                            , (i, _) <- reads istr                              -- but additionally accepts something like "hint12".                                                            , let ys' = dropWhile isSpace ys-                                                           , (eith, rest) <- [ (Left c, zs) | (c,zs) <- readsColorChar ys' ] ++ [ (Right c, zs) | (c,zs) <- readsNumberChar ys' ] ]+                                                           , (eith, rest) <- [ (Left c, zs) | (c,zs) <- readsColorChar ys' ] ++ [ (Right c, zs) | (c,zs) <- readsRankChar ys' ] ]  -- | The help text. help :: String@@ -165,19 +171,34 @@                                     --   In the current implementation (arguably), this represents [current player's hand, next player's hand, second next player's hand, ...]                                     --   and this is rotated every turn.                 } deriving (Read, Show, Eq, Generic)+type ColorToRank = Int+-- | CardTo4 is the type synonym for Int64, representing Card -> {0,1,2,3}+type CardTo4     = Int64+lookupCardTo4 :: CardTo4 -> Card -> Int+lookupCardTo4 ct4 c = fromIntegral ((ct4 `shiftR` (cardToQitPos c * 2)) .&. 3)+deleteACard :: Card -> CardTo4 -> CardTo4+deleteACard c ct4 = ct4 - bit (cardToQitPos c * 2)+insertACard :: Card -> CardTo4 -> CardTo4+insertACard c ct4 = ct4 + bit (cardToQitPos c * 2)+-- something like @fmap abs@+absCardTo4 :: CardTo4 -> CardTo4+absCardTo4 ct4 = (ct4 .&. 0x5555555555555555) .|. ((ct4.&. 0x2AAAAAAAAAAAAAAA ) `shiftR` 1)+cardTo4ToList :: CardTo4 -> [Card]+cardTo4ToList ct4 = ctl ct4 [ C i k | k <- [K1 .. K5], i <- [White .. Multicolor] ]+ctl 0 _      = []+ctl n (c:cs) = replicate (fromIntegral n .&. 3) c ++ ctl (n `shiftR` 2) cs  -- | PublicInfo is the info that is available to all players. data PublicInfo = PI { gameSpec  :: GameSpec                      , pileNum   :: Int               -- ^ The number of cards at the pile.-                     , played    :: IM.IntMap Number  -- ^ @'Color' -> 'Number'@. The maximum number of successfully played cards of etch number.--                                                      -- Just a list with length 5 or 6 could do the job.-                     , discarded :: IM.IntMap Int     -- ^ @'Card' -> Int@. The multiset of discarded cards.+                     , currentScore :: Int+                     , nextToPlay :: CardTo4          -- ^ 3(4) or 11(2) if the card is playable (ignoring whether it is extinct or not), and 0(4) or 00(2) otherwise.+                     , kept     :: CardTo4           -- ^ The multiset of not-discarded cards, packed into Int64. -                     , nonPublic :: IM.IntMap Int     -- ^ @'Card' -> Int@. The multiset of Cards that have not been revealed to the public.-                                                      --   This does not include cards whose Color and Number are both revealed.+                     , nonPublic :: CardTo4     -- ^ The multiset of Cards that have not been revealed to the public.+                                                      --   This does not include cards whose Color and Rank are both revealed.                                                       ---                                                      --   This is redundant information that can be computed from 'played' and 'discarded'.+                                                      --   This is redundant information that can be computed from 'achieved' and 'discarded'.                      , turn      :: Int               -- ^ How many turns have been completed since the game started. This can be computed from 'pileNum', 'deadline', and @map length 'annotations'@.                      , lives      :: Int              -- ^ The number of black tokens. decreases at each failure                      , hintTokens :: Int              -- ^ The number of remaining hint tokens.@@ -194,18 +215,19 @@ possibilities_until_Ver0720 = map (map possibilities) . annotations  -- | 'Marks' is the type synonym representing the hint trace of a card.-type Marks = (Maybe Color, Maybe Number)+type Marks = (Maybe Color, Maybe Rank) --- | A 'Possibilities' is a pair of data that are instances of Bits. The first represents which colors are possible, and the second is for numbers.+-- | A 'Possibilities' is a pair of data that are instances of Bits. The first represents which colors are possible, and the second is for ranks. type Possibilities = (Int, Int)  colorToBitPos  :: Color -> Int colorToBitPos  i = 5 - fromEnum i-numberToBitPos :: Number -> Int-numberToBitPos k = 5 - fromEnum k+rankToBitPos :: Rank -> Int+rankToBitPos k = 5 - fromEnum k+numberToBitPos = rankToBitPos  data Annotation = Ann {ixDeck :: Int                   -- ^ Index in the initial deck-                      , marks :: Marks                 -- ^ The Number and Color hints given to the card.+                      , marks :: Marks                 -- ^ The Rank and Color hints given to the card.                       , possibilities :: Possibilities}   deriving (Eq, Generic) instance Show Annotation where@@ -214,24 +236,50 @@   readsPrec p str = [ (Ann i ms ps, rest) | ((i,ms,ps), rest) <- readsPrec p str ]    -- | the best achievable rank for each color.-bestPossibleRank :: PublicInfo -> Color -> Number+bestPossibleRank :: PublicInfo -> Color -> Rank+bestPossibleRank pub iro = toEnum $ (countTrailingZeros    ((((kept pub `shiftR` (fromEnum iro * 2)) .&. 0x3003003003003) - 0x1001001001001) .&. 0x4004004004004)   ) `div` 12+{- bestPossibleRank pub iro = toEnum $ length $ takeWhile (/=0) $ zipWith subtract (numEachCard (gameSpec pub) iro)-                                                                                (map ((discarded pub IM.!) . cardToInt . C iro) [K1 .. K5])+                                                                                [ discarded pub (C iro k) | k <- [K1 .. K5] ]+-}++-- | @discarded pub c@ represents the number of discarded @c@. Please rewrite old @discarded pub Data.IntMap.! cardToInt c@ into @discarded pub c@.+--   Implementation of @discarded@ is not very fast, and 'kept'/'keptCards' should be used instead for efficient implementation of your algorithms.+discarded :: PublicInfo -> Card -> Int+discarded pub c = (numEachCard (gameSpec pub) (color c) !! pred (fromEnum $ rank c)) - keptCards pub c+keptCards, nonPublicCards :: PublicInfo -> Card -> Int+keptCards pub = lookupCardTo4 (kept pub)+nonPublicCards = lookupCardTo4 . nonPublic+invisibleBagCards :: PrivateView -> Card -> Int+invisibleBagCards = lookupCardTo4 . invisibleBag numEachCard :: GameSpec -> Color -> [Int] numEachCard gs iro = if iro==Multicolor then numMulticolors $ rule gs else [3,2,2,2,1]+{-+packedNumEachCard :: GameSpec -> CardTo4+packedNumEachCard gs = 0x1AB6ADAB6ADAB .|. pack4 (numMulticolors $ rule gs) `shiftL` 50 -- <reverse numMulticolors>1222312223122231222312223 in base 4+pack4 :: [Int] -> CardTo4+pack4 [] = 0+pack4 (x:xs) = fromIntegral x .|. (pack4 xs `shiftL` 2) +-}+packedNumEachCard :: GameSpec -> CardTo4+packedNumEachCard gs = 0x1552AA2AA2AA3FF .|. (pack4 (numMulticolors $ rule gs) `shiftL` 10) -- <reverse numMulticolors> .|. 11111022222022222022222033333 in base 4+pack4 :: [Int] -> CardTo4+pack4 [] = 0+pack4 (x:xs) = fromIntegral x .|. (pack4 xs `shiftL` 12)+ -- | isUseless pi card means either the card is already played or it is above the bestPossibleRank. isUseless :: PublicInfo -> Card -> Bool-isUseless pub card =  number card <= achievedRank pub (color card) -- the card is already played-                   || number card > bestPossibleRank pub (color card)+isUseless pub card =  rank card <= achievedRank pub (color card) -- the card is already played+                   || rank card > bestPossibleRank pub (color card) -- | A critical card is a useful card and the last card that has not been dropped. -- --   Unmarked critical card on the chop should be marked. isCritical :: PublicInfo -> Card -> Bool isCritical pub card = not (isUseless pub card)-                      && succ (discarded pub IM.! cardToInt card) == (numEachCard (gameSpec pub) (color card) !! (pred $ fromEnum $ number card))+                      && keptCards pub card == 1  isPlayable :: PublicInfo -> Card -> Bool-isPlayable pub card = pred (number card) == achievedRank pub (color card)+isPlayable pub card = pred (rank card) == achievedRank pub (color card)  isHinted :: Marks -> Bool isHinted = not . (==(Nothing, Nothing))@@ -247,29 +295,38 @@ isMoreObviouslyPlayable pub = iOP (nonPublic pub) pub  obviously :: (PublicInfo -> Card -> Bool) -> PublicInfo -> Possibilities -> Bool-obviously predicate pub (pc,pn) = all (\card -> (nonPublic pub IM.! cardToInt card) == 0 || predicate pub card)-                                      [ C color number | color <- colorPossibilities pc, number <- numberPossibilities pn ]+obviously predicate pub pos@(pc,pn) = all (predicate pub) $ cardTo4ToList $ absCardTo4 $ possibilitiesQits pos .&. nonPublic pub+{-+obviously predicate pub (pc,pn) = all (\card -> (nonPublicCards pub card) == 0 || predicate pub card)+                                      [ C color number | color <- colorPossibilities pc, number <- rankPossibilities pn ]+-}+obviouslyQits :: (PublicInfo -> CardTo4) -> PublicInfo -> Possibilities -> Bool+obviouslyQits predicate pub pos = (possibilitiesQits pos .&. nonPublic pub .&. complement (predicate pub)) == 0 --- | In addition to 'isMoreObviouslyPlayable', 'isObviouslyPlayable' also looks into the color/number possibilities of the card and decides if the card is surely playable.+-- | In addition to 'isMoreObviouslyPlayable', 'isObviouslyPlayable' also looks into the color/rank possibilities of the card and decides if the card is surely playable. isObviouslyPlayable :: PublicInfo -> Possibilities -> Bool-isObviouslyPlayable = obviously isPlayable+isObviouslyPlayable = obviouslyQits nextToPlay  isObviouslyUnplayable :: PublicInfo -> Possibilities -> Bool-isObviouslyUnplayable = obviously (\pub -> not . isPlayable pub)+isObviouslyUnplayable = obviouslyQits (complement . nextToPlay)  definitely :: (PrivateView -> Card -> Bool) -> PrivateView -> Annotation -> Bool definitely predicate pv ann = all (predicate pv) $ possibleCards pv ann+definitelyQits :: (PrivateView -> CardTo4) -> PrivateView -> Annotation -> Bool+definitelyQits predicate pv Ann{marks = (Just c, Just n)} = predicate pv `testBit` (cardToQitPos (C c n) * 2) -- The condition is indispensable, because now invisibleBag considers fully-marked cards visible.+definitelyQits predicate pv Ann{possibilities = pos@(pc,pn)} = (possibilitiesQits pos .&. invisibleBag pv .&. complement (predicate pv)) == 0 + -- | 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 -> Annotation -> Bool-isDefinitelyPlayable = definitely (isPlayable . publicView)+isDefinitelyPlayable = definitelyQits (nextToPlay . publicView)  isDefinitelyUnplayable :: PrivateView -> Annotation -> Bool-isDefinitelyUnplayable = definitely (\pv -> not . isPlayable (publicView pv))+isDefinitelyUnplayable = definitelyQits (complement . nextToPlay . publicView)  -- | Unlike 'isDefinitelyUseless', 'isDefinitelyUnciritical' does not care whether the card is the last one or not. 'isDefinitelyUncritical' is, in other words, safe to drop. isDefinitelyUncritical :: PrivateView -> Annotation -> Bool@@ -282,12 +339,12 @@  possibleCards :: PrivateView -> Annotation -> [Card] possibleCards pv Ann{marks = (Just c, Just n)} = [C c n] -- The condition is indispensable, because now invisibleBag considers fully-marked cards visible.-possibleCards pv Ann{possibilities = (pc,pn)}  = [ card | color <- colorPossibilities pc, number <- numberPossibilities pn, let card = C color number, (invisibleBag pv IM.! cardToInt card) /= 0 ]+possibleCards pv Ann{possibilities = pos@(pc,pn)}  = cardTo4ToList $ absCardTo4 $ possibilitiesQits pos .&. invisibleBag pv                             where pub = publicView pv -iOP :: IM.IntMap Int -> PublicInfo -> (Maybe Color, Maybe Number) -> Bool+iOP :: CardTo4 -> PublicInfo -> (Maybe Color, Maybe Rank) -> Bool iOP _   pub (Just c, Just n) = isPlayable pub $ C c n-iOP bag pub (Nothing,Just n) = all (\card -> (bag IM.! cardToInt card) == 0 || isPlayable pub card) [ C color n | color <- colors pub ]+iOP bag pub (Nothing,Just n) = all (\card -> (lookupCardTo4 bag 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.@@ -306,9 +363,9 @@ {- 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+isDefinitelyUseless pv (Just c,  Nothing) = all ((==0) . (invisibleBagCards pv) . 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 || (invisibleBagCards pv (C c n)) == 0 ) $ colors $ publicView pv+isDefinitelyUseless pv (Nothing, Nothing) = all (\c -> all ((==0) . (invisibleBagCards pv) . 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). @@ -329,29 +386,20 @@ chops pub anns = concat $ map reverse $ obviousChopss pub anns  isDoubleDrop :: PrivateView -> Result -> [Index] -> Annotation -> Bool-isDoubleDrop pv@PV{publicView=pub} (Discard c@C{..}) [_i] Ann{possibilities=(pc,pn)} = not (any ((==(Just color, Just number)).marks) myAnns) &&  -- This pattern captures: the last player discards B1; I have a card which is hinted as B and 1; I don't know where the third B1 is.+isDoubleDrop pv@PV{publicView=pub} (Discard c@C{..}) [_i] Ann{possibilities=(pc,pn)} = not (any ((==(Just color, Just rank)).marks) myAnns) &&  -- This pattern captures: the last player discards B1; I have a card which is hinted as B and 1; I don't know where the third B1 is.                                                                                                                         -- This can be improved to check whether any card other than the chop is obviously the just-dropped card or not, by looking at the Possibilities.                                                                     isCritical pub c &&                                                                     color  `elem` colorPossibilities pc &&-                                                                    number `elem` numberPossibilities pn &&-                                                                    (invisibleBag pv IM.! cardToInt c) > 0+                                                                    rank   `elem` rankPossibilities pn &&+                                                                    invisibleBagCards pv c > 0                                          where myAnns = head $ annotations pub isDoubleDrop _pv _lastresult _chopset _anns = False  colors :: PublicInfo -> [Color] colors pub = take (numColors $ rule $ gameSpec pub) [minBound .. maxBound] -achievedRank :: PublicInfo -> Color -> Number-achievedRank pub k = case IM.lookup (fromEnum k) (played pub) of-                            Just n  -> n-#ifdef DEBUG-                            Nothing | numColors (rule $ gameSpec pub) <= k -> error "requesting invalid color."-                                    | otherwise                            -> error "PublicInfo is not initialized."-#else-                            Nothing -> Empty-#endif-currentScore :: PublicInfo -> Int-currentScore pub = sum [ fromEnum $ achievedRank pub k | k <- colors pub ]+achievedRank :: PublicInfo -> Color -> Rank+achievedRank pub k = toEnum  $  countTrailingZeros ((nextToPlay pub `shiftR` (2 * fromEnum k)) .&. 0x3003003003003)  `div`  12  -- | achievable score based on the info of extinct cards. seeminglyAchievableScore :: PublicInfo -> Int@@ -387,7 +435,7 @@                       , 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).-                      , 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).+                      , invisibleBag :: CardTo4       -- ^ '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)@@ -451,11 +499,11 @@  validMoves :: PrivateView -> [Move] validMoves pv@PV{publicView=pub@PI{gameSpec=gs,hintTokens=hints},handsPV=tlHands}-  = map Play [0 .. pred myHandSize] ++ (if hints > 0 then ([ Hint hintedpl eck | hintedpl <- [1 .. numPlayers gs - 1], eck <- map Left (colors pub) ++ map Right [K1 .. K5], not (null $ filter (either (\c -> (==c).color) (\k -> (==k).number) eck) (tlHands !! pred hintedpl)) ] ++) else id) (if hints < 8 then (map Drop [0 .. pred myHandSize]) else [])+  = map Play [0 .. pred myHandSize] ++ (if hints > 0 then ([ Hint hintedpl eck | hintedpl <- [1 .. numPlayers gs - 1], eck <- map Left (colors pub) ++ map Right [K1 .. K5], not (null $ filter (either (\c -> (==c).color) (\k -> (==k).rank) eck) (tlHands !! pred hintedpl)) ] ++) else id) (if hints < 8 then (map Drop [0 .. pred myHandSize]) else [])                   where myHandSize = length (head $ annotations pub)  evalMove :: (Monad m, Strategies ps m) => [(State, Int)] -> [PublicInfo] -> [Move] -> ps -> Move -> m Int-evalMove states pubs@(pub:_) mvs ps mv = fmap (sum . map (\(((eg,_,_),_),n) -> n * egToInt pub eg)) $ mapM (\(st,n) -> fmap (\a->(a,n)) $ tryAMove (stateToStateHistory pubs mvs st) mvs ps mv) states+evalMove states pubs@(pub:_) mvs ps mv = fmap (sum . map (\(((eg,st:_,_),_),n) -> n * egToInt st eg)) $ mapM (\(st,n) -> fmap (\a->(a,n)) $ tryAMove (stateToStateHistory pubs mvs st) mvs ps mv) states  -- | 'tryAMove' tries a 'Move' and then simulate the game to the end, using given 'Strategies'. Running this with empty history, such as @tryAMove [st] [] strs m@ is possible, but that assumes other strategies does not depend on the history. tryAMove :: (Monad m, Strategies ps m) => [State] -> [Move] -> ps -> Move -> m ((EndGame, [State], [Move]),ps)@@ -517,7 +565,7 @@ evalMoveLite statess@((st:_,_,_):_) mvs p mov = do                                     roundResults <- mapM (\sts ->tryAMoveARound sts mvs mov) statess                                     let pub = publicState st-                                        instantScore = sum [ egToInt pub eg * n | ((Just eg, _, _), _, n) <- roundResults ]+                                        instantScore = sum [ egToInt s eg * n | ((Just eg, s:_, _), _, n) <- roundResults ]                                         roundResultMap = groupARound pub roundResults                                     if M.null roundResultMap then return (roundResultMap, instantScore) else do                                        let roundResults = M.elems roundResultMap@@ -561,15 +609,15 @@ possiblePermutations :: PrivateView -> [([Card],[Card])] possiblePermutations pv@PV{publicView=PI{annotations=anns:_}} = possiblePerms anns (invisibleCards pv) invisibleCards :: PrivateView -> [Card]-invisibleCards PV{publicView=PI{annotations=anns}, invisibleBag=inv} = [ c | (k,v) <- IM.toList inv, c <- replicate v $ intToCard k ] -- x ++ [ C i k | (Just i, Just k) <- map marks anns ]+invisibleCards PV{publicView=PI{annotations=anns}, invisibleBag=inv} = cardTo4ToList inv -- x ++ [ C i k | (Just i, Just k) <- map marks anns ] possiblePerms :: [Annotation] -> [Card] -> [([Card],[Card])] possiblePerms [] cards = [([],cards)] possiblePerms (Ann{marks = (Just i, Just k)} : anns) cards = [ (C i k : hand, deck) | (hand, deck) <- possiblePerms anns cards ]-possiblePerms (Ann{possibilities = (pi, pk)} : anns) cards = [ (card : hand, deck) | card@(C i k) <- cards, (pi .&. bit (colorToBitPos i)) * (pk .&. bit (numberToBitPos k)) /= 0, (hand, deck) <- possiblePerms anns $ delete card cards ]+possiblePerms (Ann{possibilities = (pi, pk)} : anns) cards = [ (card : hand, deck) | card@(C i k) <- cards, (pi .&. bit (colorToBitPos i)) * (pk .&. bit (rankToBitPos k)) /= 0, (hand, deck) <- possiblePerms anns $ delete card cards ]  -- | 'mkPV' is the constructor of PrivateView. mkPV :: PublicInfo -> [[Card]] -> PrivateView-mkPV pub hs = PV pub hs $ foldr (IM.update (Just . pred)) (nonPublic pub) $ map cardToInt $ concat $ [ C c n | Ann{marks=(Just c, Just n)} <- head $ annotations pub ] : hs+mkPV pub hs = PV pub hs $ foldr deleteACard (nonPublic pub) $ concat $ [ C c n | Ann{marks=(Just c, Just n)} <- head $ annotations pub ] : hs  prettyPV :: Verbosity -> PrivateView -> String prettyPV v pv@PV{publicView=pub} = prettyPI pub ++ "\nYour hand:\n"@@ -624,11 +672,13 @@ 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]+               ++ concat [ showRankPossibilities ns ++" " | (_,ns) <- ps] -showColorPossibilities, showNumberPossibilities :: Int -> String+showColorPossibilities, showRankPossibilities :: Int -> String showColorPossibilities  = reverse . showPossibilities ' ' colorkeys-showNumberPossibilities = reverse . showPossibilities ' ' "54321 "+showRankPossibilities = reverse . showPossibilities ' ' "54321 "+showNumberPossibilities = showRankPossibilities+ colorkeys :: String 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 :: a -> [a] -> Int -> [a]@@ -637,10 +687,33 @@  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])-+rankPossibilities :: Int -> [Rank] -- The result is in the reverse order but I do not care.+rankPossibilities = concat . showPossibilities [] (map (:[]) [K5,K4 .. K1]) +possibilitiesQits :: (Int,Int) -> CardTo4+possibilitiesQits (c,r) = colorPossibilitiesQits c .&. rankPossibilitiesQits r+colorPossibilitiesQits :: Int -> CardTo4+colorPossibilitiesQits i = let bs = cpq i+                               qs = fromIntegral $ bs .|. (bs `shiftL` 1)+                               qs2 = qs .|. (qs `shiftL` 12) +                               qs4 = qs2 .|. (qs2 `shiftL` 24)+                               qs5 = qs .|. (qs4 `shiftL` 12)+                           in qs5+cpq :: Int -> Int+cpq i = (i .&. 32 .|. (i .&. 16) `shiftL` 3 .|. (i .&. 8) `shiftL` 6 .|. (i .&. 4) `shiftL` 9 .|. (i .&. 2) `shiftL` 12 .|. (i .&. 1) `shiftL` 15) `shiftR` 5+rankPossibilitiesQits :: Int -> CardTo4+rankPossibilitiesQits i = let r1 = rpq i+                              r2 = r1 .|. (r1 `shiftR` 1)+                              r4 = r2 .|. (r2 `shiftR` 2)+                              r8 = r4 .|. (r4 `shiftR` 4)+                              r12 = r4 .|. (r8 `shiftR` 4)+                          in r12+rpq :: Int -> CardTo4+rpq i = let i1 = fromIntegral $ i `shiftL` 7+            i2 = i1 .|. (i1 `shiftL` 13)+            i4 = i2 .|. (i2 `shiftL` 26)+            i5 = i1 .|. (i4 `shiftL` 13)+        in i5 .&. 0x800800800800800  -- | '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.)@@ -678,7 +751,7 @@                                    , let playedMax = achievedRank pub c                                          possible  = fromEnum $ bestPossibleRank pub c                                    ]-            ++ "\ndropped: " ++ concat [ '|' : concat (replicate n $ show $ intToCard ci) | (ci,n) <- IM.toList $ discarded pub ] ++"|\n"+            ++ "\ndropped: " ++ concat [ '|' : concat (replicate (discarded pub c) $ show c) | i <- colors pub, k <- [K1 .. maxBound], let c = C i k ] ++"|\n"  view :: State -> PrivateView view st = mkPV (publicState st) (tail $ hands st)@@ -927,13 +1000,14 @@ -- | 'createGameFromCards' deals cards and creates the initial state. createGameFromCards :: GameSpec -> [Card] -> State createGameFromCards gs cards = splitCons (numPlayers gs) [] [ (c, initAnn gs i) | (c,i) <- zip cards [0..] ]-           where splitCons 0 hnds stack-                   = St {publicState = PI {gameSpec   = gs,+           where pNEC = packedNumEachCard gs+                 splitCons 0 hnds stack+                    = St {publicState = PI {gameSpec   = gs,                                             pileNum    = initialPileNum gs,-                                            played     = IM.fromAscList [ (i,            Empty) | i <- [0 .. pred $ numColors $ rule gs] ],-                                            discarded  = IM.fromList    [ (cardToInt $ C i k, 0) | i <- take (numColors $ rule gs) [White .. Multicolor],-                                                                                                   k <- [K1 ..K5] ],-                                            nonPublic  = cardMap $ rule gs,+                                            currentScore = 0,+                                            nextToPlay = 0xFFF,+                                            kept       = pNEC,+                                            nonPublic  = pNEC,                                             turn       = 0,                                             lives      = numBlackTokens $ rule gs,                                             hintTokens = 8,@@ -953,15 +1027,13 @@ createDeck r gen = shuffle (cardBag r) gen  -numAssoc :: [(Number, Int)]+numAssoc :: [(Rank, Int)] numAssoc = zip [K1 ..K5] [3,2,2,2,1] cardAssoc :: Rule -> [(Card,Int)] cardAssoc rule = take (5 * numColors rule) $                [ (C i k, n) | i <- [White .. pred Multicolor], (k,n) <- numAssoc ] ++ [ (C Multicolor k, n) | (k, n) <- zip [K1 ..K5] (numMulticolors rule) ] cardBag :: Rule -> [Card] cardBag rule = concat         [ replicate n c | (c,n) <- cardAssoc rule ]-cardMap :: Rule -> IM.IntMap Int-cardMap rule = IM.fromList [ (cardToInt c, n) | (c,n) <- cardAssoc rule ] unknown :: GameSpec -> Possibilities unknown gs = (64 - bit (6 - numColors (rule gs)),  31) @@ -982,7 +1054,7 @@                                            hintedpl > 0 && hintedpl < numPlayers (gameSpec pub) &&    -- existing player other than the current                                            not (null $ filter willBeHinted (tlHands !! pred hintedpl))     where willBeHinted :: Card -> Bool-          willBeHinted = either (\c -> (==c).color) (\k -> (==k).number) eck+          willBeHinted = either (\c -> (==c).color) (\k -> (==k).rank) eck pickNth :: Int -> [a] -> (a, [a]) pickNth    n   xs = case splitAt n xs of (tk,nth:dr) -> (nth,tk++dr) replaceNth :: Int -> a -> [a] -> (a, [a])@@ -1007,8 +1079,8 @@   prc (Drop _) = st{pile = nextPile,                     hands = nextHands,                     publicState = pub{pileNum = nextPileNum,-                                      discarded = IM.update (Just . succ) (cardToInt nth) $ discarded pub,-                                      nonPublic = IM.update (Just . pred) (cardToInt nth) $ nonPublic pub,+                                      kept = deleteACard nth $ kept pub,+                                      nonPublic = deleteACard nth $ nonPublic pub,                                       turn       = succ $ turn pub,                                       hintTokens = succ $ hintTokens pub,                                       annotations = nextAnns,@@ -1018,14 +1090,16 @@                | otherwise = st{pile = nextPile,                                 hands = nextHands,                                 publicState = pub{pileNum = nextPileNum,-                                                  played = IM.update (Just . succ) (fromEnum $ color nth) (played pub),-                                                  nonPublic = IM.update (Just . pred) (cardToInt nth) $ nonPublic pub,+                                                  currentScore = succ $ currentScore pub,+                                                  nextToPlay = ((nextToPlay pub .&. complement mask)  .|.  ((nextToPlay pub .&. mask) `shiftL` 12)) .&. 0xFFFFFFFFFFFFFFF,+                                                  nonPublic = deleteACard nth $ nonPublic pub,                                                   turn       = succ $ turn pub,-                                                  hintTokens = if hintTokens pub < 8 && number nth == K5 then succ $ hintTokens pub else hintTokens pub,+                                                  hintTokens = if hintTokens pub < 8 && rank nth == K5 then succ $ hintTokens pub else hintTokens pub,                                                   annotations = nextAnns,                                                   deadline   = nextDeadline,                                                   result = Success nth}}     where failure = not $ isPlayable pub nth+          mask = 0x3003003003003 `shiftL` (fromEnum (color nth) * 2)   prc (Hint hintedpl eik) = st{publicState = pub{hintTokens = pred $ hintTokens pub,                                                  turn       = succ $ turn pub,                                                  annotations = snd $ updateNth hintedpl newAnns (annotations pub),@@ -1039,7 +1113,7 @@                             where ibit = colorToBitPos i                           Right k | k == ka -> ann{marks=(mi, Just k), possibilities = (c, bit kbit)}                                   |otherwise-> ann{possibilities = (c, clearBit n kbit)}-                            where kbit = numberToBitPos k+                            where kbit = rankToBitPos k   -- | @'rotate' num@ rotates the first person by @num@ (modulo the number of players).@@ -1053,14 +1127,15 @@ data EndGame = Failure | Soso Int | Perfect deriving (Show,Read,Eq,Generic)  egToInt _   Failure  = 0-egToInt _   (Soso n) = n-egToInt pub Perfect  = 5 * numColors (rule $ gameSpec pub)+egToInt st  _        = currentScore $ publicState st  checkEndGame :: PublicInfo -> Maybe EndGame checkEndGame pub | lives pub == 0                                      = Just Failure-                 | all (==K5) [ achievedRank pub k | k <- colors pub ] = Just Perfect+                 | current == numColors r * 5                          = Just Perfect                  | deadline pub == Just 0 ||-                   (earlyQuit (rule $ gameSpec pub) && currentScore pub == seeminglyAchievableScore pub)-                                                                       = Just $ Soso $ IM.foldr (+) 0 $ fmap fromEnum $ played pub+                   (earlyQuit r && current == seeminglyAchievableScore pub)+                                                                       = Just $ Soso current                  | hintTokens pub == 0 && null (head $ annotations pub) = Just Failure -- No valid play is possible for the next player. This can happen when prolong==True.                  | otherwise                                           = Nothing+  where current = currentScore pub+        r = rule $ gameSpec pub
Game/Hanabi/Backend.lhs view
@@ -26,7 +26,7 @@ import System.IO.Error(isEOFError) import Control.Exception import Data.Char(isSpace)-+import Data.List(sort) import Data.Time  import System.Console.GetOpt@@ -215,10 +215,13 @@ interpret mbVerb inp params   = let sender :: String -> IO ()         sender = sendTextData (conn params) . endecodeX mbVerb . Str-        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 =+        createR observe creater args = case reads args of [(rule,rest)] | isRuleValid rule -> createD observe creater rule rest+                                                          _             -> createD observe creater defaultRule args+        createD observe from rule args = case reads args of [(deck,';':rest)] | sort (cardBag rule) == sort deck -> create observe from rule deck args+                                                                              | otherwise                        -> sender $ "Invalid deck!\ndeck = " ++ show deck+                                                            _             -> create observe from rule [] args+        create :: Bool -> Maybe Int -> Rule -> [Card] -> String -> IO ()+        create observe from rule deck args =                               case wordsBy (==',') args of                                 is | numAllies > 0 -> if numAllies >= 9                                                       then sender "Too many teammates!\n"@@ -242,7 +245,8 @@                                                                let playerList | observe = ixSs                                                                               |otherwise= mkDS "via WebSocket" (VWS (conn params) mbVerb sendFullHistoryInFact) : ixSs                                                                    (playOrder,g) = orderPlayers from (gen params) playerList-                                                                   (shuffled, _) = createDeck rule g+                                                                   shuffled | null deck = fst $ createDeck rule g+                                                                            | otherwise = deck                                                                eithFinalSituation <- try $ if observe then sender ("The initial deck is " ++ show shuffled) >>                                                                                                            startFromCards (GS numAllies rule)        [watch (conn params) mbVerb] playOrder shuffled                                                                                                       else startFromCards (GS (succ numAllies) rule) []                           playOrder shuffled
Game/Hanabi/Client.hs view
@@ -16,7 +16,7 @@ import qualified Data.Map as M import qualified Data.IntMap as IM import Data.Maybe(fromJust, isNothing)-import Data.List(intersperse, transpose)+import Data.List(sort, intersperse, transpose)  import           Miso         hiding (Fail) import           Miso.String  (MisoString)@@ -31,6 +31,8 @@  import Control.Concurrent +import Game.Hanabi.FFI+ #ifdef ghcjs_HOST_OS client :: Game.Hanabi.Msg.Options -> IO () client options = clientJSM options@@ -82,10 +84,10 @@                            defStr = maybe "via WebSocket" id $ lookup "strategy" query                        mvStr <- liftIO newMVarStrategy                        startApp App{-    model  = Model{tboxval = Message "available", players = [S.pack defStr], from = Just 0, rule = defaultRule{numMulticolors=replicate 5 1}, received = [CreateGame], fullHistory = False, showVerbosity = False, verbosity = verbose, play = True, localStrategy = mvStr, local = False},+    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, play = True, localStrategy = mvStr, local = False, initialDeck = [], lastMoves = [], preset = False},     update = updateModel options,     view   = appView strNames $ version options,-    subs   = [ websocketSub wsURI protocols HandleWebSocket ],+    subs   = [ websocketSub wsURI protocols HandleWebSocket, windowBottomSub ViewMore Id ],     events = defaultEvents,     initialAction = Id,     -- initialAction = SendMessage $ Message "available", -- Seemingly sending as initialAction does not work, even if connect is executed before send.@@ -123,7 +125,7 @@ updateModel :: Game.Hanabi.Msg.Options -> Action -> Model -> Effect Action Model updateModel _ (HandleWebSocket (WebSocketMessage (Message m))) model   = noEff model{ received = {- take lenHistory $ -} suppressCG $ decodeMsg m : received model }-updateModel _ (SendMessage msg@(Message str)) model = model{fullHistory=False, showVerbosity=False} <# -- connect uri protocols >>+updateModel _ (SendMessage msg@(Message str)) model = model{shownHistory=defaultShownHistory, showVerbosity=False} <# -- connect uri protocols >>                                                 if local model                                                 then case reads $ S.unpack str of                                                        [(m,str)] -> liftIO $ do@@ -132,7 +134,7 @@                                                          return $ ProcMsg msg                                                        _         -> return $ ProcMsg $ Str "Could not parse as a Move."                                                 else send msg >> return Id-updateModel _ (SendMove mov)    model = model{fullHistory=False, showVerbosity=False} <# -- connect uri protocols >>+updateModel _ (SendMove mov)    model = model{shownHistory=defaultShownHistory, showVerbosity=False} <# -- connect uri protocols >>                                                 if local model                                                 then liftIO $ do                                                          putMVar (mvMov $ localStrategy model) mov@@ -150,16 +152,20 @@ 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 _ Toggle            model = noEff model{fullHistory = not $ fullHistory model}+updateModel _ (UpdateDeck ds)   model = noEff $ case reads $ S.unpack ds of [(d, rs)] | all isSpace rs -> model{initialDeck = d}+                                                                            _         -> model+updateModel _ ViewMore          model = noEff model{shownHistory = shownHistory model + historyUnit} updateModel _ ToggleVerbosity   model = noEff model{showVerbosity = not $ showVerbosity model} 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 opt ObserveLocally model = model <# liftIO (do                                                    let constructor algIx = fromJust $ lookup algIx $ strategies opt                                                    playerList <- mapM (constructor . S.unpack) $ reverse $ players model                                                    gen <- newGen                                                    let (playOrder,g) = orderPlayers (from model) gen playerList-                                                       (shuffled, _) = createDeck (rule model) g+                                                       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,_) <- startFromCards (GS (length playerList) (rule model)) [] playOrder shuffled                                                    return $ WriteLocalResult shuffled fs                                                 )@@ -171,13 +177,14 @@                                                    let thePlayerList = mkDS "local strategy" (localStrategy model) : playerList                                                    gen <- newGen                                                    let (playOrder,g) = orderPlayers (from model) gen thePlayerList-                                                       (shuffled, _) = createDeck (rule model) g+                                                       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 $ do (fs,_) <- startFromCards (GS (length thePlayerList) (rule model)) [] playOrder shuffled                                                                putMVar (mvMsg $ localStrategy model) $ PrettyEndGame shuffled $ Just fs                                                    msg <- takeMVar (mvMsg $ localStrategy model)                                                    return $ ProcMsg msg                                                 )-updateModel _ (ProcMsg msg@(PrettyEndGame shuffled (Just fs))) model = model{local=False, received = CreateGame : msg : received model} <# return (SendMessage $ Message "available")+updateModel _ (ProcMsg msg@(PrettyEndGame shuffled (Just fs@(_,_,mvs)))) model = model{local=False, received = CreateGame : msg : received model, initialDeck = shuffled, lastMoves = mvs} <# return (SendMessage $ Message "available") 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)@@ -205,13 +212,15 @@   | UpdatePlayer Int MisoString   | UpdateRule Rule   | UpdateVerbosity Verbosity-  | Toggle+  | ViewMore   | ToggleVerbosity   | TogglePlay   | ObserveLocally   | WriteLocalResult [Card] (EndGame, [State], [Move])   | PlayLocally   | ProcMsg Msg+  | TogglePreset+  | UpdateDeck MisoString   | Id  data Model = Model {@@ -220,16 +229,20 @@   , from :: Maybe Int   , rule :: Rule   , received :: [Msg]-  , fullHistory :: Bool+  , shownHistory :: Int   , showVerbosity :: Bool   , verbosity :: Verbosity   , play :: Bool   , localStrategy :: MVarStrategy   , local :: Bool+  , initialDeck :: [Card]+  , lastMoves :: [Move]+  , preset :: Bool   } deriving (Show, Eq) -lenShownHistory :: Int-lenShownHistory = 10+defaultShownHistory, historyUnit :: Int+defaultShownHistory = 10+historyUnit = 10 appView :: [MisoString] -> String -> Model -> View Action appView strategies versionInfo mdl@Model{..} = div_ [] [    input_  [ type_ "text", placeholder_ "You can also use your keyboard.", size_ "25", onInput UpdateTBoxVal, onEnter (SendMessage tboxval) ]@@ -244,11 +257,11 @@   , 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 verbosity mdl) received)- , div_ [] $ if null $ drop lenShownHistory received then [] else [+ , div_ [style_ $ M.fromList [("clear","both")]] $ take shownHistory $ map (renderMsg strategies verbosity mdl) received+ , div_ [] $ if null $ drop shownHistory received then [] else [      hr_ []-   , input_ [ type_ "checkbox", id_ "showhist", onClick Toggle, checked_ fullHistory]-   , label_ [for_ "showhist"] [text "Show full history"]+   , input_ [ type_ "checkbox", id_ "showhist", onClick ViewMore, checked_ False] -- This should be replaced with a button.+   , label_ [for_ "showhist"] [text "Show more history"]    ]  ] renderVerbosity :: Verbosity -> View Action@@ -304,7 +317,10 @@ renderMsg _ _    _ (Str xs)             = div_ [] [hr_ [], pre_ [] [ text $ S.pack xs ]] renderMsg _ verb _ (WhatsUp name ps ms) = renderWhatsUp  verb name ps ms renderMsg _ verb _ (WhatsUp1     p  m)  = renderWhatsUp1 verb p m-renderMsg _ _    _ (PrettyEndGame initDeck Nothing)     = pre_ [] [ text $ S.pack $ prettyMbEndGame Nothing ++ "By the way, the initial deck was " ++ shows initDeck ".\n"]+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 _ verb _ (Watch st [])           = div_ [style_ $ M.fromList [("font-size", "2vmin")]] [          hr_ [],@@ -325,13 +341,13 @@            caption_ [] [text $ S.pack "Available games", button_ [onClick $ SendMessage $ Message "available"] [text $ S.pack "refresh"]] :            tr_ [] [ th_ [solid] [text $ S.pack str] | str <- ["Game ID", "available", "total"] ] :            map renderAvailable games,-      div_ [] $ renderCreateGame True strategies mdl,+      renderCreateGame True strategies mdl,       hr_ []     ]-renderMsg strategies _ mdl CreateGame = div_ [] $ renderCreateGame False strategies mdl-renderCreateGame :: Bool -> [MisoString] -> Model -> [View Action]+renderMsg strategies _ mdl CreateGame = renderCreateGame False strategies mdl+renderCreateGame :: Bool -> [MisoString] -> Model -> View Action renderCreateGame online strategies mdl-    = [+    = div_ [style_ $ M.fromList [{- ("border-style","solid"), -} ("display","inline-block")]] $ [            table_ [solid] $ caption_ [textProp "text-align" "left", textProp "margin-left" "auto"] [ -- Seemingly these styles do not work.              text "Players",              button_ [onClick IncreasePlayers] [text $ S.pack "+"],@@ -366,7 +382,7 @@                  gs = GS{numPlayers = length (players mdl) + if play mdl then 1 else 0,                          Game.Hanabi.rule = rule mdl}              in-               table_ [solid] $ [+               table_ [style_ $ M.fromList [("margin","1%"),("border-style","solid")]] $ [ -- Use "1% auto" instead in order to centralize.                    caption_ [] [text "Rules"],                    mkTRd "Number of lives"                               (\n -> (rule mdl){numBlackTokens=n}) numBlackTokens [1 .. 9],                    mkTRd "Number of colors"                              (\n -> (rule mdl){numColors=n})      numColors      [1 .. 6],@@ -379,13 +395,18 @@                    mkTR [list_ "numMulticolors"] "Numbers of M1 .. M5" (\n -> (rule mdl){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_ [ type_ "checkbox", id_ "preset", onClick TogglePreset, checked_ $ preset mdl ] :+                      label_ [for_ "preset"] [text "preset deck"] : -- [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, (if preset mdl then value_ else placeholder_) $ S.pack $ show $ initialDeck mdl]] else [],              button_ [onClick $                         if not online && 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 (rule mdl) ++ concat (intersperse "," $ map S.unpack $ reverse $ players mdl)+                                                                                           | otherwise -> "observe ") ++ show (rule mdl) ++ (if preset mdl then shows (initialDeck mdl) . (';':) else id) (concat (intersperse "," $ map S.unpack $ reverse $ players mdl)),+                      style_ $ M.fromList [("float","right")]                      ] [text $ S.pack "create a game"]       ]     where you =  span_[][@@ -440,12 +461,21 @@          hr_ [],          renderSt verb ithPlayerFromTheLast st,          hr_ [],-         text $ S.pack $ "By the way, the initial deck was " ++ shows initDeck "\n and the move histories are" ++ tail (foldr showsMoves "." histories),-         hr_ []+         span_ [style_ $ M.fromList [("font-size", "2.5vmin")]] [+             text $ S.pack $ "By the way, the initial deck was ",+             div_ [style_ $ M.fromList [("font-family", "monospace"), ("font-size", "1.5vw")]] [+                 text $ S.pack $ show initDeck+                 ],+             span_ [] (if length histories > 9 then [text " and the move histories are ",+                                                     div_ [style_ $ M.fromList [("font-family", "monospace"), ("font-size", "1.5vw")]] [ text $ S.pack $ tail (foldr showsMoves "." $ transpose histories) ]+                                                    ]+                                               else []),+             hr_ []+           ]          ]-    where histories = transpose $ chopEvery (numPlayers $ gameSpec $ publicState st) $ reverse mvs+    where histories = chopEvery (numPlayers $ gameSpec $ publicState st) $ reverse mvs           showsMoves :: [Move] -> ShowS-          showsMoves mvs rest = ", " ++ filter (\c -> not (isLower c || c == 'H')) (foldr shows rest mvs)+          showsMoves mvs rest = ",\n" ++ filter (\c -> not (isLower c || c == 'H')) (foldr shows rest mvs) chopEvery n xs = case splitAt n xs of ([], _) -> []                                       (tk,dr) -> tk : chopEvery n dr @@ -492,32 +522,30 @@               [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_ [] [ span_ [] $ text (S.pack "|") : renderCardsInline verb pub c [K1 .. playedMax] : map (text . S.pack) (replicate (possible - fromEnum playedMax) "_" ++ replicate (5 - possible) "X")+        span_ [style_ $ M.fromList [("font-family", "monospace"),("font-size", "3vmin")]] $ [ 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-                                                            ],-        text $ S.pack "|"+                                                            ] ++ [text $ S.pack "|"]       ],        div_ [] [         text $ S.pack $ "dropped: ", --        span_ [] [ span_ [] $ text (S.pack "|") : (replicate n $ renderCardInline verb pub $ intToCard ci) | (ci, n) <- IM.toList $ discarded pub ],-        span_ [] [ span_ [] [text (S.pack "|"), renderCardsInline verb pub c (replicate n r)] | (ci, n) <- IM.toList $ discarded pub, n>0, let C c r = intToCard ci ],-        text $ S.pack "|"+        span_ [style_ $ M.fromList [("font-family", "monospace"),("font-size", "3vmin")]] $ [ 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 ] ++ [text $ S.pack "|"]       ]      ]     where current    = currentScore pub           achievable = seeminglyAchievableScore pub  -- renderCardsInline is a compact version of renderCardInline that prints the color letter only once.-renderCardsInline :: Verbosity -> PublicInfo -> Color -> [Number] -> View Action-renderCardsInline v pub c ns = span_ [style_ $ M.fromList [("font-family", "monospace"),("font-size", "4vmin"),("color", colorStr $ Just c),("background-color","#000000")]] $ span_ [] [text $ S.pack $ take 1 $ show c] : map (numberStrInline v pub c) ns+renderCardsInline :: Verbosity -> PublicInfo -> Color -> [Rank] -> View Action+renderCardsInline v pub c ns = span_ [style_ $ M.fromList [("color", colorStr $ Just c),("background-color","#000000")]] $ span_ [] [text $ S.pack $ take 1 $ show c] : map (rankStrInline v pub c) ns -numberStrInline v pub c n = cardStrInline v pub (C c n) $ show $ fromEnum n+rankStrInline v pub c n = cardStrInline v pub (C c n) $ show $ fromEnum n  renderCardInline :: Verbosity -> PublicInfo -> Card -> View Action-renderCardInline v pub c = span_ [style_ $ M.fromList [("font-family", "monospace"),("font-size", "4vmin"),("color", colorStr $ Just $ color c),("background-color","#000000")]] [cardStrInline v pub c $ head (show $ color c) : show (fromEnum $ number c)]+renderCardInline v pub c = span_ [style_ $ M.fromList [("font-family", "monospace"),("font-size", "4vmin"),("color", colorStr $ Just $ color c),("background-color","#000000")]] [cardStrInline v pub c $ head (show $ color c) : show (fromEnum $ rank c)]  cardStrInline v pub c xs = (if useless then s_ else span_) [style] [ --                                    text $ S.pack $ show c@@ -573,7 +601,8 @@      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 "play") ]) (const $ span_[][]) mbc,-     div_ [style_ $ M.fromList [{- ("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")]] [+     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 v pub pli mbc tup      ],      (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 '_' (head . show) mc : ' ' : [maybe '_' (head . show . fromEnum) mk] else "_ _" ],@@ -582,17 +611,11 @@                                                                                                   )] ] [ text (S.pack "drop") ]) (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 pc, -- br_[],-                                                                                                       text $ S.pack $ showNumberPossibilities pn] else span_[][]+                                                                                                       text $ S.pack $ showRankPossibilities pn] else span_[][]   ] where           pub = publicView pv           cardWidth = 90 / fromIntegral (handSize $ gameSpec pub)-          (useless,myStyle) | isNothing mbc = (markObviouslyUseless v && isDefinitelyUseless pv ann,-                                                        [ ("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"),-                                                          ("font-weight", if useless then "100" else "normal"),-                                                          ("font-style",  if markObviouslyPlayable v && isDefinitelyPlayable pv ann then "oblique" else "normal")]-                                              )-                            | otherwise     = (markObviouslyUseless v && isObviouslyUseless pub ptup,+          (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")]@@ -617,7 +640,7 @@      ]   ] -}-cardStr :: Verbosity -> PublicInfo -> Int -> Maybe Card -> (Maybe Color, Maybe Number) -> View Action+cardStr :: Verbosity -> PublicInfo -> Int -> Maybe Card -> (Maybe Color, Maybe Rank) -> View Action -- Not sure which style is better. #ifdef BUTTONSONCARDS cardStr v pub pli mbc tup = case mbc of@@ -625,7 +648,7 @@           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 $ number c), style][text $ S.pack $ show $ fromEnum $ number 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"),@@ -641,7 +664,7 @@                                               onClick (SendMessage $ Message $ S.pack $ shows pli $ take 1 $ show $ color c)] [text $ S.pack $ take 1 $ show $ color c],                                     text " ",                                     span_ [ -- style_ $ M.fromList [("font-size","1.3em")],-                                              onClick (SendMessage $ Message $ S.pack $ shows pli $ show $ fromEnum $ number c)][text $ S.pack $ show $ fromEnum $ number c]+                                              onClick (SendMessage $ Message $ S.pack $ shows pli $ show $ fromEnum $ rank c)][text $ S.pack $ show $ fromEnum $ rank c]                              ]                      where style = style_ $ M.fromList [-- ("width","30px"),                               ("font-family", if critical then "sans-serif" else "serif"),
+ Game/Hanabi/FFI.hs view
@@ -0,0 +1,19 @@+{-# LANGUAGE OverloadedStrings #-}+module Game.Hanabi.FFI where++import Miso+import Control.Monad+import Control.Monad.IO.Class(liftIO)++foreign import javascript unsafe "var html = window.document.documentElement; $r = html.scrollHeight - html.clientHeight - (window.document.body.scrollTop || html.scrollTop);"+   getBottom :: IO Int++atTheBottom :: IO Bool+atTheBottom =  (<=0) <$> getBottom++windowBottomSub :: action    -- ^ ActionToBeTaken+                   -> action -- ^ NoAction+                   -> Sub action+windowBottomSub action noAction sink = windowAddEventListener "scroll" $ \_e -> do+  b <- atTheBottom+  liftIO $ sink $ if b then action else noAction
Game/Hanabi/Msg.hs view
@@ -27,8 +27,8 @@ instance FromJSON Card instance ToJSON Color instance FromJSON Color-instance ToJSON Number-instance FromJSON Number+instance ToJSON Rank+instance FromJSON Rank instance ToJSON GameSpec instance FromJSON GameSpec instance ToJSON Rule
Game/Hanabi/Strategies/SimpleStrategy.hs view
@@ -21,26 +21,26 @@                            numHand = length myAnns                            isColorMarkable col = isPlayable pub (head [ c | (j,c,Ann{marks=(_,Nothing)}) <- nextPlayer, color c == col ])                                                || any (isPlayable pub) [ c | (j,c,Ann{marks=(Nothing,Just num)}) <- nextPlayer, color c == col ]-                           isNewestOfColor i d = null [ () | (j,c,Ann{marks=(_,Nothing)}) <- nextPlayer, color c == color d, j < i ] -- True if there is no newer number-unmarked card of the same color in nextPlayer.+                           isNewestOfColor i d = null [ () | (j,c,Ann{marks=(_,Nothing)}) <- nextPlayer, color c == color d, j < i ] -- True if there is no newer rank-unmarked card of the same color in nextPlayer.                            markCandidates = filter (\(_,card,ann) -> isPlayable pub card && not (isObviouslyPlayable pub $ possibilities ann)) $ reverse nextPlayer  -- Playable cards that are not enough hinted, old to new.-                           markUnhintedCritical = take 1 [ Hint 1 (if isColorMarkable (color te) then Left $ color te else Right $ number te) | (_ix, te, ann) <- reverse nextPlayer, not (isHinted $ marks ann), isCritical pub te ]+                           markUnhintedCritical = take 1 [ Hint 1 (if isColorMarkable (color te) then Left $ color te else Right $ rank te) | (_ix, te, ann) <- reverse nextPlayer, not (isHinted $ marks ann), isCritical pub te ]                            keep2 = take 1 [ Hint 1 $ Right $ K2 | (_ix, te@(C _ K2), ann) <- reverse nextPlayer, not (isHinted $ marks ann), not $ isUseless pub te ]                            unhintedNon2 = [ t | t@(_, C c n, ann) <- nextPlayer, n/=K2, not $ isHinted $ marks ann ]                            colorMarkUnmarkedPlayable = take 1 [ Hint 1 $ Left $ color d | (i,d,Ann{marks=(Nothing, Nothing)}) <- markCandidates,  -- Mark the color if a (not obviously) playable card is not marked                                                                                           isNewestOfColor i d, -- but be cautious not to color-mark newer cards.                                                                                           not $ havePlayableCardWithTheSameColor $ color d ] -- refrain marking if I have a playable card with the same color-                           colorMarkNumberMarkedPlayable = take 1 [ Hint 1 $ Left $ color d | (_,d,Ann{marks=(Nothing, Just _)}) <- markCandidates,  -- Mark the color if a (not obviously) playable card is only number-marked.+                           colorMarkNumberMarkedPlayable = take 1 [ Hint 1 $ Left $ color d | (_,d,Ann{marks=(Nothing, Just _)}) <- markCandidates,  -- Mark the color if a (not obviously) playable card is only rank-marked.                                                                                               not $ havePlayableCardWithTheSameColor $ color d -- refrain marking if I have a playable card with the same color                                                                                             ]-                           numberMarkPlayable = take 1 [ Hint 1 $ Right $ number d | (_,d,Ann{marks=(_, Nothing)}) <- markCandidates, -- Mark the number if a (not obviously) playable card is not number-marked.+                           numberMarkPlayable = take 1 [ Hint 1 $ Right $ rank d | (_,d,Ann{marks=(_, Nothing)}) <- markCandidates, -- Mark the rank if a (not obviously) playable card is not rank-marked.                                                                                      not $ havePlayableCardWithTheSameColor $ color d -- refrain marking if I have a playable card with the same color                                                                                    ]                            havePlayableCardWithTheSameColor c = or [ isDefinitelyPlayable pv ann | (_,ann@Ann{marks=(Just d,_)}) <- myHand, c==d ]-                           numberMarkUselessIfInformative = take 1 [ Hint 1 $ Right $ number d | (_,d,Ann{possibilities=p@(pc, _)}) <- nextPlayer, not $ isObviouslyUseless pub p, isObviouslyUseless pub (pc, bit $ numberToBitPos (number d)) ]+                           numberMarkUselessIfInformative = take 1 [ Hint 1 $ Right $ rank d | (_,d,Ann{possibilities=p@(pc, _)}) <- nextPlayer, not $ isObviouslyUseless pub p, isObviouslyUseless pub (pc, bit $ rankToBitPos (rank d)) ]                            colorMarkUselessIfInformative = take 1 [ Hint 1 $ Left $ color d | (i,d,Ann{possibilities=p@(_, pn)}) <- reverse nextPlayer, not $ isObviouslyUseless pub p, isObviouslyUseless pub (bit $ colorToBitPos (color d), pn),                                                                                               isNewestOfColor i d ] -- but be cautious not to color-mark newer cards.-                           numberMarkUnmarked       = take 1 [ Hint 1 $ Right $ number d | (_,d,Ann{marks=(Nothing, Nothing),possibilities=p}) <- nextPlayer, not $ isObviouslyUseless pub p ]-                           numberMarkNumberUnmarked = take 1 [ Hint 1 $ Right $ number d | (_,d,Ann{marks=(Just _, Nothing),possibilities=p}) <- nextPlayer, not $ isObviouslyUseless pub p ]+                           numberMarkUnmarked       = take 1 [ Hint 1 $ Right $ rank d | (_,d,Ann{marks=(Nothing, Nothing),possibilities=p}) <- nextPlayer, not $ isObviouslyUseless pub p ]+                           numberMarkNumberUnmarked = take 1 [ Hint 1 $ Right $ rank d | (_,d,Ann{marks=(Just _, Nothing),possibilities=p}) <- nextPlayer, not $ isObviouslyUseless pub p ]                            colorMarkColorUnmarked   = take 1 [ Hint 1 $ Left  $ color d  | (i,d,Ann{marks=(Nothing, Just _),possibilities=p}) <- reverse nextPlayer, not $ isObviouslyUseless pub p,                                                                                            isNewestOfColor i d ] -- but be cautious not to color-mark newer cards.                            playPlayable5 = take 1 [ Play i | (i,ann@Ann{marks=(_,Just K5)}) <- myHand, isDefinitelyPlayable pv ann ]
Game/Hanabi/Strategies/StatefulStrategy.hs view
@@ -32,7 +32,7 @@                                                 | c <- hintedColors,                                                   let i   = length $ takeWhile ((/=Just c) . fst . marks) myAnns                                                       ann = myAnns !! i-                                                      newNumberPos = bit $ numberToBitPos (succ $ achievedRank pub c), -- This is undefined when achievedRank pub c == K5, but then isDefinitelyUnplayable pv ann should be True.+                                                      newNumberPos = bit $ rankToBitPos (succ $ achievedRank pub c), -- This is undefined when achievedRank pub c == K5, but then isDefinitelyUnplayable pv ann should be True.                                                   not $ isDefinitelyUnplayable pv ann,                                                   newNumberPos .&. snd (possibilities ann) /= 0 ]                            pub = publicView pv
Game/Hanabi/Strategies/Stateless.hs view
@@ -29,7 +29,7 @@                                         , not $ or [ isObviouslyPlayable pubInQ m | Ann{marks=(Just i, _),possibilities=m} <- myAnnsInQ, c==i ] -- Exclude if there is a playable card with the color.                                          , ann <- take 1 [ ann | ann@Ann{marks=(Just i,_)} <- myAnnsInQ, c==i ]-                                        , let newNumberPos = bit $ numberToBitPos (succ $ achievedRank pubInQ c) -- This is undefined when achievedRank pub c == K5, but then isDefinitelyUnplayable pv ann should be True.+                                        , let newNumberPos = bit $ rankToBitPos (succ $ achievedRank pubInQ c) -- This is undefined when achievedRank pub c == K5, but then isDefinitelyUnplayable pv ann should be True.                                         , ixDeck ann == ix                                          , not $ isDefinitelyUnplayable pvInQ ann
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.9.1.0+version:             0.10.1.0  -- A short (one-line) description of the package. synopsis:            Hanabi card game@@ -131,6 +131,7 @@   if impl(ghcjs) --                || flag(jsaddle)       exposed-modules:     Game.Hanabi.Client+      other-modules:       Game.Hanabi.FFI       ghcjs-options:      -dedupe -O       build-depends:      base >=4.12 && <4.13, jsaddle-warp >=0.9, aeson, miso, time >=1.6, network-uri >=2.6       if flag(official)@@ -140,7 +141,7 @@  executable hanabiq   main-is: all.hs-  other-modules: Game.Hanabi, Game.Hanabi.Msg, Game.Hanabi.Backend, Game.Hanabi.Client, Game.Hanabi.Strategies.SimpleStrategy, Game.Hanabi.Strategies.StatefulStrategy+  other-modules: Game.Hanabi, Game.Hanabi.Msg, Game.Hanabi.Backend, Game.Hanabi.Client, Game.Hanabi.FFI, Game.Hanabi.Strategies.SimpleStrategy, Game.Hanabi.Strategies.StatefulStrategy --  if !flag(all)   if !flag(jsaddle)     buildable: False