| @@ -34,6 +34,7 @@ dependencies: | |||||
| - containers | - containers | ||||
| - case-insensitive | - case-insensitive | ||||
| - vector | - vector | ||||
| - transformers | |||||
| library: | library: | ||||
| source-dirs: src | source-dirs: src | ||||
| @@ -4,7 +4,7 @@ cabal-version: 1.12 | |||||
| -- | -- | ||||
| -- see: https://github.com/sol/hpack | -- see: https://github.com/sol/hpack | ||||
| -- | -- | ||||
| -- hash: a181979e99bc6631140cc5973d1714c0b5aa021f74c72938c0ee7ab47b581cf2 | |||||
| name: skat | name: skat | ||||
| version: 0.1.0.7 | version: 0.1.0.7 | ||||
| @@ -62,6 +62,7 @@ library | |||||
| , random | , random | ||||
| , split | , split | ||||
| , text | , text | ||||
| , transformers | |||||
| , vector | , vector | ||||
| , websockets | , websockets | ||||
| default-language: Haskell2010 | default-language: Haskell2010 | ||||
| @@ -88,6 +89,7 @@ executable skat-exe | |||||
| , skat | , skat | ||||
| , split | , split | ||||
| , text | , text | ||||
| , transformers | |||||
| , vector | , vector | ||||
| , websockets | , websockets | ||||
| default-language: Haskell2010 | default-language: Haskell2010 | ||||
| @@ -114,6 +116,7 @@ test-suite skat-test | |||||
| , skat | , skat | ||||
| , split | , split | ||||
| , text | , text | ||||
| , transformers | |||||
| , vector | , vector | ||||
| , websockets | , websockets | ||||
| default-language: Haskell2010 | default-language: Haskell2010 | ||||
| @@ -23,7 +23,6 @@ data SkatEnv = SkatEnv { piles :: Piles | |||||
| , currentHand :: Hand } | , currentHand :: Hand } | ||||
| deriving Show | deriving Show | ||||
| type Trick = (CardS Owner, CardS Owner, CardS Owner) | |||||
| type Skat = StateT SkatEnv (WriterT [Trick] IO) | type Skat = StateT SkatEnv (WriterT [Trick] IO) | ||||
| runSkat :: Skat a -> SkatEnv -> IO (a, SkatEnv, [Trick]) | runSkat :: Skat a -> SkatEnv -> IO (a, SkatEnv, [Trick]) | ||||
| @@ -95,6 +95,8 @@ instance Communicator c => Bidder (PrepOnline c) where | |||||
| liftIO $ send (prepConnection p) (BS.unpack $ encode $ GameResultsQuery res) | liftIO $ send (prepConnection p) (BS.unpack $ encode $ GameResultsQuery res) | ||||
| onGame p game sglPlayer = do | onGame p game sglPlayer = do | ||||
| liftIO $ send (prepConnection p) (BS.unpack $ encode $ GameStartQuery game sglPlayer) | liftIO $ send (prepConnection p) (BS.unpack $ encode $ GameStartQuery game sglPlayer) | ||||
| onNoGame p = do | |||||
| liftIO $ send (prepConnection p) (BS.unpack $ encode $ NoGameQuery) | |||||
| type Online a m = ReaderT (OnlineEnv a) m | type Online a m = ReaderT (OnlineEnv a) m | ||||
| @@ -139,6 +141,7 @@ data Query = ChooseQuery [Card] [CardS Played] | |||||
| | CardsQuery [Card] | | CardsQuery [Card] | ||||
| | BidEvent (Maybe Bid) Hand Hand | | BidEvent (Maybe Bid) Hand Hand | ||||
| | ResponseEvent Bool Hand Hand | | ResponseEvent Bool Hand Hand | ||||
| | NoGameQuery | |||||
| newtype ChosenResponse = ChosenResponse Card | newtype ChosenResponse = ChosenResponse Card | ||||
| newtype BidResponse = BidResponse Int | newtype BidResponse = BidResponse Int | ||||
| @@ -183,6 +186,8 @@ instance ToJSON Query where | |||||
| , "response" .= response | , "response" .= response | ||||
| , "reizer" .= show reizer | , "reizer" .= show reizer | ||||
| , "gereizter" .= show gereizter ] | , "gereizter" .= show gereizter ] | ||||
| toJSON NoGameQuery = | |||||
| object [ "query" .= ("no_game" :: String) ] | |||||
| instance FromJSON ChosenResponse where | instance FromJSON ChosenResponse where | ||||
| parseJSON = withObject "ChosenResponse" $ \v -> ChosenResponse | parseJSON = withObject "ChosenResponse" $ \v -> ChosenResponse | ||||
| @@ -29,7 +29,7 @@ data Type = Seven | |||||
| | Ten | | Ten | ||||
| | Ace | | Ace | ||||
| | Jack | | Jack | ||||
| deriving (Eq, Ord, Show, Enum, Read) | |||||
| deriving (Eq, Ord, Show, Enum, Read, Bounded) | |||||
| data NullType = NSeven | data NullType = NSeven | ||||
| | NEight | | NEight | ||||
| @@ -39,7 +39,7 @@ data NullType = NSeven | |||||
| | NQueen | | NQueen | ||||
| | NKing | | NKing | ||||
| | NAce | | NAce | ||||
| deriving (Eq, Ord, Show, Enum, Read) | |||||
| deriving (Eq, Ord, Show, Enum, Read, Bounded) | |||||
| instance Countable Type Int where | instance Countable Type Int where | ||||
| count Ace = 11 | count Ace = 11 | ||||
| @@ -53,7 +53,7 @@ data Colour = Diamonds | |||||
| | Hearts | | Hearts | ||||
| | Spades | | Spades | ||||
| | Clubs | | Clubs | ||||
| deriving (Eq, Ord, Show, Enum, Read) | |||||
| deriving (Eq, Ord, Show, Enum, Read, Bounded) | |||||
| data Trump = TrumpColour Colour | data Trump = TrumpColour Colour | ||||
| | Jacks | | Jacks | ||||
| @@ -65,7 +65,7 @@ data TurnColour = TurnColour Colour | |||||
| deriving (Show, Eq) | deriving (Show, Eq) | ||||
| data Card = Card Type Colour | data Card = Card Type Colour | ||||
| deriving (Eq, Show, Ord, Read) | |||||
| deriving (Eq, Show, Ord, Read, Bounded) | |||||
| getType :: Card -> Type | getType :: Card -> Type | ||||
| getType (Card t _) = t | getType (Card t _) = t | ||||
| @@ -117,6 +117,17 @@ instance Countable (S.Set Card) Int where | |||||
| instance NFData Card where | instance NFData Card where | ||||
| rnf (Card t c) = t `seq` c `seq` () | rnf (Card t c) = t `seq` c `seq` () | ||||
| base64table :: [Char] | |||||
| base64table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" | |||||
| class Serialize c a where | |||||
| serialize :: a -> c | |||||
| deserialize :: c -> Maybe a | |||||
| instance Serialize Char Card where | |||||
| serialize card = base64table !! fromEnum card | |||||
| deserialize char = base64table `indexOf` char >>= safeToEnum | |||||
| equals :: TurnColour -> Maybe TurnColour -> Bool | equals :: TurnColour -> Maybe TurnColour -> Bool | ||||
| equals col (Just x) = col == x | equals col (Just x) = col == x | ||||
| equals col Nothing = True | equals col Nothing = True | ||||
| @@ -33,10 +33,10 @@ match prepEnv = do | |||||
| let res = getResults | let res = getResults | ||||
| (game skatEnv) | (game skatEnv) | ||||
| sglPlayer | sglPlayer | ||||
| (Skat.piles skatEnv) | |||||
| (Skat.Preperation.piles prepEnv) | |||||
| (Skat.piles finished) | (Skat.piles finished) | ||||
| publishGameResults res (bidders prepEnv) | publishGameResults res (bidders prepEnv) | ||||
| return $ Just $ Match (Skat.piles skatEnv) res tricks sglPlayer | |||||
| return $ Just $ Match (Skat.Preperation.piles prepEnv) res tricks sglPlayer | |||||
| Nothing -> putStrLn "no one wanted to play" >> return Nothing | Nothing -> putStrLn "no one wanted to play" >> return Nothing | ||||
| -- | predefined card distribution for testing purposes | -- | predefined card distribution for testing purposes | ||||
| @@ -6,6 +6,9 @@ | |||||
| module Skat.Pile where | module Skat.Pile where | ||||
| import Control.Monad.State | |||||
| import Control.Monad.Trans.Maybe | |||||
| import Prelude hiding (lookup) | import Prelude hiding (lookup) | ||||
| import qualified Data.Map.Strict as M | import qualified Data.Map.Strict as M | ||||
| import qualified Data.Vector as V | import qualified Data.Vector as V | ||||
| @@ -15,6 +18,8 @@ import Data.Maybe | |||||
| import Data.Aeson | import Data.Aeson | ||||
| import Control.Exception | import Control.Exception | ||||
| import Data.List (delete) | import Data.List (delete) | ||||
| import Text.Read (readMaybe) | |||||
| import Debug.Trace | |||||
| import Skat.Card | import Skat.Card | ||||
| import Skat.Utils | import Skat.Utils | ||||
| @@ -41,7 +46,7 @@ instance ToJSON p => ToJSON (CardS p) where | |||||
| object ["card" .= card, "pile" .= pile] | object ["card" .= card, "pile" .= pile] | ||||
| data Hand = Hand1 | Hand2 | Hand3 | data Hand = Hand1 | Hand2 | Hand3 | ||||
| deriving (Show, Eq, Ord, Read) | |||||
| deriving (Show, Eq, Ord, Read, Enum, Bounded) | |||||
| toInt :: Hand -> Int | toInt :: Hand -> Int | ||||
| toInt Hand1 = 1 | toInt Hand1 = 1 | ||||
| @@ -61,12 +66,33 @@ prev Hand3 = Hand2 | |||||
| data Owner = P Hand | S | data Owner = P Hand | S | ||||
| deriving (Show, Eq, Ord, Read) | deriving (Show, Eq, Ord, Read) | ||||
| instance Enum Owner where | |||||
| fromEnum (P hand) = fromEnum hand | |||||
| fromEnum S = 3 | |||||
| toEnum 0 = P Hand1 | |||||
| toEnum 1 = P Hand2 | |||||
| toEnum 2 = P Hand3 | |||||
| toEnum 3 = S | |||||
| instance Bounded Owner where | |||||
| maxBound = S | |||||
| minBound = P Hand1 | |||||
| instance ToJSON Owner where | instance ToJSON Owner where | ||||
| toJSON (P hand) = object ["owner" .= show hand] | toJSON (P hand) = object ["owner" .= show hand] | ||||
| toJSON S = object ["owner" .= ("skat" :: String) ] | toJSON S = object ["owner" .= ("skat" :: String) ] | ||||
| instance Serialize String (CardS Owner) where | |||||
| serialize (CardS card owner) = show (fromEnum owner) ++ [serialize card] | |||||
| deserialize str = (flip evalState) str $ runMaybeT $ do | |||||
| owner <- pop >>= MaybeT . return . (>>= safeToEnum) . readMaybe . (:[]) | |||||
| card <- pop >>= MaybeT . return . deserialize | |||||
| return $ CardS card owner | |||||
| type Played = Owner -- TODO: remove | type Played = Owner -- TODO: remove | ||||
| type Trick = (CardS Owner, CardS Owner, CardS Owner) | |||||
| data Piles = Piles { _hand1 :: [CardS Owner] | data Piles = Piles { _hand1 :: [CardS Owner] | ||||
| , _hand2 :: [CardS Owner] | , _hand2 :: [CardS Owner] | ||||
| , _hand3 :: [CardS Owner] | , _hand3 :: [CardS Owner] | ||||
| @@ -185,3 +211,28 @@ distribute cards = emptyPiles hand1 hand2 hand3 skt | |||||
| hand1 = concatMap (!! 0) [round1, round2, round3] | hand1 = concatMap (!! 0) [round1, round2, round3] | ||||
| hand2 = concatMap (!! 1) [round1, round2, round3] | hand2 = concatMap (!! 1) [round1, round2, round3] | ||||
| hand3 = concatMap (!! 2) [round1, round2, round3] | hand3 = concatMap (!! 2) [round1, round2, round3] | ||||
| instance Serialize String Piles where | |||||
| serialize piles = sers (_hand1 piles) ++ sers (_hand2 piles) ++ sers (_hand3 piles) | |||||
| ++ sers (_skat piles) | |||||
| where sers cards = map (serialize . toCard) cards | |||||
| deserialize str = (flip evalState) str $ runMaybeT $ do | |||||
| hand1 <- takeG 10 >>= mapM deser | |||||
| hand2 <- takeG 10 >>= mapM deser | |||||
| hand3 <- takeG 10 >>= mapM deser | |||||
| skat <- takeG 2 >>= mapM deser | |||||
| return $ emptyPiles hand1 hand2 hand3 skat | |||||
| where deser char = MaybeT $ return $ deserialize char | |||||
| instance Serialize String [Trick] where | |||||
| serialize [] = "" | |||||
| serialize ((c1, c2, c3):tricks) = serialize c1 ++ serialize c2 ++ serialize c3 | |||||
| ++ serialize tricks | |||||
| deserialize str = (flip evalState) str $ runMaybeT $ reverse <$> go [] | |||||
| where go acc = do | |||||
| empty <- isEmpty | |||||
| if empty then return acc else do | |||||
| card1 <- takeG 2 >>= MaybeT . return . deserialize | |||||
| card2 <- takeG 2 >>= MaybeT . return . deserialize | |||||
| card3 <- takeG 2 >>= MaybeT . return . deserialize | |||||
| go ((card1, card2, card3):acc) | |||||
| @@ -40,6 +40,8 @@ class Bidder a where | |||||
| onGame _ _ _ = return () | onGame _ _ _ = return () | ||||
| onResult :: MonadIO m => a -> Result -> m () | onResult :: MonadIO m => a -> Result -> m () | ||||
| onResult _ _ = return () | onResult _ _ = return () | ||||
| onNoGame :: MonadIO m => a -> m () | |||||
| onNoGame _ = return () | |||||
| -- | trick to allow heterogenous bidder list | -- | trick to allow heterogenous bidder list | ||||
| data BD = forall b. (Show b, Bidder b) => BD b | data BD = forall b. (Show b, Bidder b) => BD b | ||||
| @@ -60,6 +62,7 @@ instance Bidder BD where | |||||
| onResult (BD b) = onResult b | onResult (BD b) = onResult b | ||||
| onBid (BD b) = onBid b | onBid (BD b) = onBid b | ||||
| onResponse (BD b) = onResponse b | onResponse (BD b) = onResponse b | ||||
| onNoGame (BD b) = onNoGame b | |||||
| data Bidders = Bidders BD BD BD | data Bidders = Bidders BD BD BD | ||||
| deriving Show | deriving Show | ||||
| @@ -88,7 +91,7 @@ runPreperation = do | |||||
| publishBid bid finalWinner finalWinner | publishBid bid finalWinner finalWinner | ||||
| case bid of | case bid of | ||||
| Just val -> (Just . (finalWinner,)) <$> initGame finalWinner val | Just val -> (Just . (finalWinner,)) <$> initGame finalWinner val | ||||
| Nothing -> return Nothing | |||||
| Nothing -> publishNoGame >> return Nothing | |||||
| else (Just . (finalWinner,)) <$> initGame finalWinner finalBid | else (Just . (finalWinner,)) <$> initGame finalWinner finalBid | ||||
| runBidding :: Bid -> BD -> BD -> Preperation (Hand, Bid) | runBidding :: Bid -> BD -> BD -> Preperation (Hand, Bid) | ||||
| @@ -160,6 +163,9 @@ publishBid bid reizer gereizter = mapBidders (\b -> onBid b bid reizer gereizter | |||||
| publishResponse :: Bool -> Hand -> Hand -> Preperation () | publishResponse :: Bool -> Hand -> Hand -> Preperation () | ||||
| publishResponse response reizer gereizter = mapBidders (\b -> onResponse b response reizer gereizter) | publishResponse response reizer gereizter = mapBidders (\b -> onResponse b response reizer gereizter) | ||||
| publishNoGame :: Preperation () | |||||
| publishNoGame = mapBidders onNoGame | |||||
| mapBidders :: (BD -> Preperation ()) -> Preperation () | mapBidders :: (BD -> Preperation ()) -> Preperation () | ||||
| mapBidders f = do | mapBidders f = do | ||||
| bds <- asks bidders | bds <- asks bidders | ||||
| @@ -1,7 +1,12 @@ | |||||
| {-# LANGUAGE ScopedTypeVariables #-} | |||||
| module Skat.Utils where | module Skat.Utils where | ||||
| import Control.Monad.State | |||||
| import Control.Monad.Trans.Maybe | |||||
| import System.Random | import System.Random | ||||
| import Text.Read | |||||
| import Text.Read hiding (get, lift) | |||||
| import qualified Data.ByteString.Char8 as B (ByteString, unpack, pack) | import qualified Data.ByteString.Char8 as B (ByteString, unpack, pack) | ||||
| import qualified Data.Text as T (Text, unpack, pack) | import qualified Data.Text as T (Text, unpack, pack) | ||||
| import Data.List (foldl') | import Data.List (foldl') | ||||
| @@ -57,3 +62,36 @@ instance Stringy B.ByteString where | |||||
| instance Stringy T.Text where | instance Stringy T.Text where | ||||
| toString = T.unpack | toString = T.unpack | ||||
| fromString = T.pack | fromString = T.pack | ||||
| indexOf :: Eq a => [a] -> a -> Maybe Int | |||||
| indexOf [] _ = Nothing | |||||
| indexOf (x:xs) item | |||||
| | x == item = Just 0 | |||||
| | otherwise = (1+) <$> xs `indexOf` item | |||||
| type Generator c = MaybeT (State [c]) | |||||
| pop :: Generator c c | |||||
| pop = do | |||||
| cs <- get | |||||
| if null cs then mzero else put (tail cs) >> return (head cs) | |||||
| isEmpty :: Generator c Bool | |||||
| isEmpty = get >>= return . null | |||||
| takeG :: Int -> Generator c [c] | |||||
| takeG n = do | |||||
| cs <- lift get | |||||
| if length cs >= n | |||||
| then do | |||||
| put (drop n cs) | |||||
| return (take n cs) | |||||
| else mzero | |||||
| -- forall is needed to allow scoped type variables | |||||
| safeToEnum :: forall a. (Enum a, Bounded a) => Int -> Maybe a | |||||
| safeToEnum n | |||||
| | maxN < n || minN > n = Nothing | |||||
| | otherwise = Just $ toEnum n | |||||
| where maxN = fromEnum (maxBound :: a) | |||||
| minN = fromEnum (minBound :: a) | |||||