packages feed

UISF-0.1.0.0: FRP/UISF/Examples/Pinochle.hs

-- Author: Daniel Winograd-Cort
-- Date Created:        unknown
-- Date Last Modified:  12/12/2013

-- This is a pinochle assistant.  The user enters his hand at the GUI
-- and selects his preferred trump suit, and his meld is displayed.
-- If the user chooses a kitty size, he can calculate his potential 
-- meld from the kitty.

-- The kitty meld currently displays the mean expected meld and the 
-- max in the form: 
--  "# of kitties that produce this much meld"x"meld value":[best possible kitties]

-- This module requires the array package.

-- make sure to use "ghc --make -O2 pinochle.hs" for pest performance

{-# LANGUAGE Arrows, BangPatterns #-}
module FRP.UISF.Examples.Pinochle where
import FRP.UISF hiding (accum)

import Data.List (delete, foldl', group)
import GHC.Arr (Ix(..), indexError)
import Data.Array
import Data.List (transpose)


main = runUI (800,600) "Pinochole Assistant" pinochleSF

data Card = Card Suit Number
    deriving (Eq, Ord)

instance Enum Card where
    toEnum i = let (q,r) = quotRem i 6 in Card (toEnum q) (toEnum r)
    fromEnum (Card s n) = (6 * fromEnum s) + fromEnum n

instance Show Card where
    show (Card suit number) = show number ++ " of " ++ show suit

instance Ix Card where
    range (c1,c2) = [c1..c2]
    unsafeIndex (c1,c2) c = fromEnum c - fromEnum c1
    index b i | inRange b i = unsafeIndex b i | otherwise = indexError b i "Card"
    inRange (m,n) i = m <= i && i <= n

data Suit = Spades | Hearts | Diamonds | Clubs
    deriving (Show, Eq, Enum, Ord)
data Number = Nine | Jack | Queen | King | Ten | Ace
    deriving (Show, Eq, Enum, Ord)

allSuits = [Spades, Hearts, Diamonds, Clubs]
--nums = [Nine, Nine, Jack, Jack, Queen, Queen, King, King, Ten, Ten, Ace, Ace]
nums = [Ace, Ace, Ten, Ten, King, King, Queen, Queen, Jack, Jack, Nine, Nine]

type Hand = Array Card Int

deckArray = listArray (Card Spades Nine, Card Clubs Ace) (repeat 2)

emptyHand :: Hand
emptyHand = listArray (Card Spades Nine, Card Clubs Ace) (repeat 0)

addToHand :: Hand -> [Card] -> Hand
addToHand h cs = accum (+) h $ zip cs (repeat 1)

removeFromHand :: Hand -> [Card] -> Hand
removeFromHand h cs = accum (+) h $ zip cs (repeat (-1))

complementHand :: Hand -> Hand
complementHand = fmap (2-)

handLength :: Hand -> Int
handLength = sum . elems

class ShortShow a where
    shortShow :: a -> String

instance ShortShow Suit where
    shortShow = take 1 . show

instance ShortShow Number where
    shortShow = take 1 . show

instance ShortShow Card where
    shortShow (Card suit number) = shortShow number ++ " of " ++ shortShow suit

instance ShortShow a => ShortShow [a] where
    shortShow = show . map shortShow


pinochleSF :: UISF () ()
pinochleSF = proc _ -> do
    spadeB   <- title "Spades"   $ leftRight $ mapA $ map (stickyButton . show) nums -< repeat ()
    heartB   <- title "Hearts"   $ leftRight $ mapA $ map (stickyButton . show) nums -< repeat ()
    diamondB <- title "Diamonds" $ leftRight $ mapA $ map (stickyButton . show) nums -< repeat ()
    clubB    <- title "Clubs"    $ leftRight $ mapA $ map (stickyButton . show) nums -< repeat ()
    trump    <- leftRight $ label "Choose Trump:" >>> radio (map show allSuits) 0 >>> arr toEnum -< ()
    let spades   = [n | (b,n) <- zip spadeB   nums, b]
        hearts   = [n | (b,n) <- zip heartB   nums, b]
        diamonds = [n | (b,n) <- zip diamondB nums, b]
        clubs    = [n | (b,n) <- zip clubB    nums, b]
        hand = addToHand emptyHand $ map (Card Spades) spades ++ map (Card Hearts) hearts ++ map (Card Diamonds) diamonds ++ map (Card Clubs) clubs 
    (trump',hand') <- delay (Spades,emptyHand) -< (trump,hand)
    rec meld <- delay [] -< if hand == hand' && trump == trump' then meld else getMeld trump hand
    --display -< shortShow hand
    leftRight $ label "Number of cards:" >>> setSize (40,22) display -< handLength hand
    leftRight $ label "Total meld =" >>> displayStr -< show (sum (map snd3 meld)) ++ ": " ++ show (map fst3 meld)
    kittenSizeStr <- leftRight $ label "Kitty size =" >>> setSize (40,22) (textboxE "0") -< case (hand == hand', handLength hand) of
            (False, 11) -> Just $ show 4
            (False, 15) -> Just $ show 3
            _ -> Nothing
    restr <- checkbox "Restrict trump suit?" False -< ()
    b <- edge <<< button "Calculate meld from kitty" -< ()
    --let kre = Nothing
    kre <- (asyncUISF $ toAutomaton $ uncurry $ uncurry kittyResult) -< 
            fmap (const ((hand, kittenSizeStr), if restr then Just trump else Nothing)) b
    k <- hold [] -< maybe (fmap (const ["Calculating ..."]) b) Just kre
    displayStrList -< k
    returnA -< ()

kittyResult :: Hand -> String -> Maybe Suit -> [String]
kittyResult _ s _ | null (reads s :: [(Int,String)]) = ["Unable to parse kitty size"]
kittyResult hand s _ | handLength hand + fst (head (reads s :: [(Int,String)])) > handLength deckArray = 
    ["Kitty size + hand size > deck size"]
kittyResult hand s trump = ("Mean = " ++ show meanMeld ++ ", Max = " 
                     ++ show (fst4 $ head maxMeld) ++ " with " ++ show (sum $ map thd4 maxMeld) ++ " options:"):
                     map (\m -> show (thd4 m) ++ " of " ++ show (snd4 m) ++ " with " ++ show (fth4 m) ++ " as trump") maxMeld
  where
    kittySize = fst (head (reads s :: [(Int,String)]))
    restOfDeck = complementHand hand
    kitties = possibleKitties kittySize restOfDeck
    getSuitMelds s = map (calc s hand) kitties
    allMelds :: [(Int, [Card], Int, Suit)]
    allMelds = maybe allMelds' getSuitMelds trump
    allMelds' = concatMap (fst . meldStats) $ transpose $ map getSuitMelds [Spades, Hearts, Diamonds, Clubs]
    -- meldStats returns the pair (list of all best melds, (sum of all melds, count of all melds))
    meldStats = foldl' (\(a@((v,_,_,_):_),(s,c)) b@(v2,_,r,_) -> seq s $ seq c ((case compare v v2 of
                            LT -> [b]
                            EQ -> b:a
                            GT -> a),(s+r*v2,c+r)))  ([(-1,[],0,Spades)], (0,0))
    (maxMeld, meanMeld) = let (m,(s,c)) = meldStats allMelds in (m, fromIntegral s / fromIntegral c)
    --stddevMeld = stddev . map (fromIntegral . fst) . expand
    calc suit h (k,n) = (sum $ map snd3 $ getMeld suit (addToHand h k),k,n,suit)
    expand :: [(Int, [Card], Int)] -> [(Int, [Card])]
    expand [] = []
    expand ((v,c,r):lst) = replicate r (v,c) ++ expand lst
    

possibleKitties :: Int -> Hand -> [([Card],Int)]
possibleKitties i hand = map (head &&& length) $ group $ ncr (assocs hand) i

-- this only works for the ints in the list between 0 and 2 inclusive.
ncr :: [(a, Int)] -> Int -> [[a]]
ncr _ r | r < 0  = error "Zero or more elements should be extracted."
ncr _ 0          = [[]]
ncr [] _         = []
ncr ((x,0):xs) r = ncr xs r
ncr ((x,1):xs) r = map (x:) (ncr xs (r-1)) ++ ncr xs r
ncr ((x,2):xs) 1 = [[x],[x]] ++ ncr xs 1
ncr ((x,2):xs) r = map ([x,x]++) (ncr xs (r-2)) ++ concatMap (\l -> [x:l,x:l]) (ncr xs (r-1)) ++ ncr xs r

mean :: Floating a => [a] -> a
mean x = fst $ foldl' (\(!m, !n) x -> (m+(x-m)/(n+1),n+1)) (0,0) x

-- |Standard deviation of sample
stddev :: (Floating a) => [a] -> a
stddev xs = sqrt $ var xs

-- |Sample variance
var xs = var' 0 0 0 xs / fromIntegral (length xs - 1)
    where
      var' _ _ s [] = s
      var' m n s (x:xs) = var' nm (n + 1) (s + delta * (x - nm)) xs
         where
           delta = x - m
           nm = m + delta/fromIntegral (n + 1)

-- | Takes a hand and a set of meld data to potentially return meld.
-- The meld data is a list of meld names, a list of meld points, and 
-- a list of meld cards.  First, it checks to see if (length meld-points) 
-- copies of the cards are in the hand (checking for 2x, 3x, etc. copies 
-- of the given meld cards).  If so, it halts with the first String and 
-- point count, and if not, it recurs.  If the cards are never found, 
-- the empty list is returned.
-- This always returns either a one-element list or an empty list!
checkMeld :: Hand -> ([String], [Int], [Card]) -> [(String, Int, [Card])]
checkMeld hand (strs,ints,m) = 
    let n = containsCount hand m in if n == 0 then [] else [(strs!!(n-1), ints!!(n-1), m)]

-- | Will return the highest value among the indexes in the list
containsCount :: Ix i => Array i Int -> [i] -> Int
containsCount a [] = maxBound
containsCount a (i:is) = let v = a!i in if v == 0 then 0 else min v $ containsCount a is

-- Meld helpers
roundhouse = concatMap (\s -> [Card s King, Card s Queen]) allSuits
straight trump = [Card trump Ace, Card trump Ten, Card trump King, Card trump Queen, Card trump Jack]

-- | getMeld
-- Given a trump suit and hand, returns all of the meld for this hand.
getMeld :: Suit -> Hand -> [(String, Int, [Card])]
getMeld trump hand = 
    concatMap (checkMeld hand) meld2 ++ 
    case containsCount hand roundhouse of
      2 -> ("2xRoundhouse",48,concat $ replicate 2 roundhouse):
            checkMeld hand (["Straight","2xStraight"],[15,30],straight trump)
      1 -> ("Roundhouse",24,roundhouse):
            case containsCount hand (straight trump) of 
              2 -> ("2xStraight",30,concat $ replicate 2 $ straight trump):concatMap (checkMeld $ removeFromHand hand roundhouse) rhMeld
              1 -> ("Straight",15,straight trump):concatMap (checkMeld $ removeFromHand hand roundhouse) (rKQs:rhMeld)
              0 -> concatMap (checkMeld $ removeFromHand hand roundhouse) (rKQs:rhMeld)
      0 -> case containsCount hand (straight trump) of
              2 -> ("2xStraight",30,concat $ replicate 2 $ straight trump):concatMap (checkMeld hand) rhMeld
              1 -> ("Straight",15,straight trump):
                checkMeld (removeFromHand hand [Card trump King, Card trump Queen]) rKQstraight ++
                concatMap (checkMeld hand) rhMeld
              0 -> concatMap (checkMeld hand) (rKQs:rhMeld)
  where
    rhMeld = map (\s -> (["KQ of "++shortShow s,"2xKQ of "++shortShow s], [2,4], [Card s King, Card s Queen])) (delete trump allSuits) ++
            [(["Eighty Kings","All the Kings"], [8,16], map (flip Card King) allSuits),
             (["Sixty Queens","All the Queens"], [6,12], map (flip Card Queen) allSuits)]
--    rhMeld1 = map (\s -> (["KQ of "++shortShow s], [2], [Card s King, Card s Queen])) (delete trump allSuits) ++
--            [(["Eighty Kings"], [8], map (flip Card King) allSuits),
--             (["Sixty Queens"], [6], map (flip Card Queen) allSuits)]
    rKQs = (["Royal Marriage","2xRoyal Marriage"], [4,8], [Card trump King, Card trump Queen])
    rKQstraight = (["Bonus Royal Marriage"], [4], [Card trump King, Card trump Queen])
    meld2 = [(["Hundred Aces","Thousand Aces"], [10,20], map (flip Card Ace) allSuits),
             (["Forty Jacks","All the Jacks"], [4,8], map (flip Card Jack) allSuits),
             (["Pinochle","Double Pinochle"], [4,30], [Card Diamonds Jack, Card Spades Queen]),
             (["9 of Trump","2x9s of Trump"], [1,2], [Card trump Nine])]

        
     



mapA :: Arrow a => [a b c] -> a [b] [c]
mapA [] = arr $ const []
mapA (sf:sfs) = proc (b:bs) -> do
    c <- sf -< b
    cs <- mapA sfs -< bs
    returnA -< (c:cs)


fst3 (a,b,c) = a
snd3 (a,b,c) = b
thd3 (a,b,c) = c

fst4 (a,b,c,d) = a
snd4 (a,b,c,d) = b
thd4 (a,b,c,d) = c
fth4 (a,b,c,d) = d


displayStrList :: UISF [String] ()
displayStrList = proc strs -> 
    if null strs then returnA -< () else (arr snd <<< (displayStr *** displayStrList) -< (head strs, tail strs))