13 Commits
20 changed files with 761 additions and 228 deletions
+10 -9
View File
@@ -12,6 +12,7 @@ import Skat.Card
import Skat.Operations
import Skat.Player
import Skat.Pile
import Skat.Bidding
import Skat.AI.Stupid
import Skat.AI.Online
@@ -37,20 +38,20 @@ runAI = do
env <- shuffledEnv
let ps = piles env
cs = handCards Hand3 ps
trs = filter (isTrump Spades) cs
trs = filter (isTrump $ TrumpColour Spades) cs
if length trs >= 5 && any ((==32) . getID) cs
then do
pts <- fst <$> evalStateT turn env
pts <- fst <$> evalSkat turn env
-- if pts > 60 then return 1 else return 0
return pts
else runAI
env :: SkatEnv
env = SkatEnv piles Nothing Spades playersExamp Hand1
env = SkatEnv piles Nothing (Colour Spades Einfach) playersExamp Hand1 Hand3
where piles = distribute allCards
envStupid :: SkatEnv
envStupid = SkatEnv piles Nothing Spades pls2 Hand1
envStupid = SkatEnv piles Nothing (Colour Spades Einfach) pls2 Hand1 Hand3
where piles = distribute allCards
playersExamp :: Players
@@ -68,22 +69,22 @@ pls2 = Players
shuffledEnv :: IO SkatEnv
shuffledEnv = do
cards <- shuffleCards
return $ SkatEnv (distribute cards) Nothing Spades playersExamp Hand1
return $ SkatEnv (distribute cards) Nothing (Colour Spades Einfach) playersExamp Hand1 Hand3
shuffledEnv2 :: IO SkatEnv
shuffledEnv2 = do
cards <- shuffleCards
return $ SkatEnv (distribute cards) Nothing Spades pls2 Hand1
return $ SkatEnv (distribute cards) Nothing (Colour Spades Einfach) pls2 Hand1 Hand3
env2 :: SkatEnv
env2 = SkatEnv piles Nothing Hearts playersExamp Hand2
env2 = SkatEnv piles Nothing (Colour Hearts Einfach) playersExamp Hand2 Hand3
where hand1 = [Card Eight Hearts, Card Queen Hearts, Card Ace Clubs, Card Queen Diamonds]
hand2 = [Card Seven Hearts, Card King Hearts, Card Ten Hearts, Card Queen Spades]
hand3 = [Card Seven Spades, Card King Spades, Card Ace Spades, Card Queen Clubs]
piles = emptyPiles hand1 hand2 hand3 []
env3 :: SkatEnv
env3 = SkatEnv piles Nothing Diamonds pls2 Hand3
env3 = SkatEnv piles Nothing (Colour Diamonds Einfach) pls2 Hand3 Hand3
where hand1 = [ Card Jack Diamonds, Card Jack Clubs, Card Nine Spades, Card King Spades
, Card Seven Diamonds, Card Nine Diamonds, Card Seven Clubs, Card Eight Clubs
, Card Ten Clubs, Card Eight Hearts ]
@@ -108,4 +109,4 @@ application pending = do
putStrLn $ BS.unpack msg
playSkat :: IO ()
playSkat = void $ (flip runStateT) env3 playCLI
playSkat = void $ (flip runSkat) env3 playCLI
+3 -2
View File
@@ -5,6 +5,7 @@ import Skat.Card
import Skat.Pile
import Skat.Player
import Skat.AI.Stupid
import Skat.Bidding
pls2 :: Players
pls2 = Players
@@ -13,7 +14,7 @@ pls2 = Players
(PL $ Stupid Single Hand3)
env3 :: SkatEnv
env3 = SkatEnv piles Nothing Diamonds pls2 Hand3
env3 = SkatEnv piles Nothing (Colour Diamonds Einfach) pls2 Hand3 Hand3
where hand1 = [ Card Jack Diamonds, Card Jack Clubs, Card Nine Spades, Card King Spades
, Card Seven Diamonds, Card Nine Diamonds, Card Seven Clubs, Card Eight Clubs
, Card Ten Clubs, Card Eight Hearts ]
@@ -28,4 +29,4 @@ env3 = SkatEnv piles Nothing Diamonds pls2 Hand3
shuffledEnv2 :: IO SkatEnv
shuffledEnv2 = do
cards <- shuffleCards
return $ SkatEnv (distribute cards) Nothing Spades pls2 Hand1
return $ SkatEnv (distribute cards) Nothing (Colour Spades Einfach) pls2 Hand1 Hand3
+3 -1
View File
@@ -1,5 +1,5 @@
name: skat
version: 0.1.0.1
version: 0.1.0.8
github: "githubuser/skat"
license: BSD3
author: "flavis"
@@ -34,6 +34,8 @@ dependencies:
- containers
- case-insensitive
- vector
- transformers
- exceptions
library:
source-dirs: src
+8 -2
View File
@@ -4,10 +4,10 @@ cabal-version: 1.12
--
-- see: https://github.com/sol/hpack
--
-- hash: 0b9b42e767fdfcdc821bfc31f5c002e1f6752ba6af032ff402339ef667f60209
-- hash: a2e08e04140990ba90e6d7b70c6bc70b99d073ba723efa9d5e35708995da45e1
name: skat
version: 0.1.0.1
version: 0.1.0.8
description: Please see the README on Gitea at <https://git.flavigny.de/christian/skat>
homepage: https://github.com/githubuser/skat#readme
bug-reports: https://github.com/githubuser/skat/issues
@@ -56,12 +56,14 @@ library
, case-insensitive
, containers
, deepseq
, exceptions
, mtl
, network
, parallel
, random
, split
, text
, transformers
, vector
, websockets
default-language: Haskell2010
@@ -81,6 +83,7 @@ executable skat-exe
, case-insensitive
, containers
, deepseq
, exceptions
, mtl
, network
, parallel
@@ -88,6 +91,7 @@ executable skat-exe
, skat
, split
, text
, transformers
, vector
, websockets
default-language: Haskell2010
@@ -107,6 +111,7 @@ test-suite skat-test
, case-insensitive
, containers
, deepseq
, exceptions
, mtl
, network
, parallel
@@ -114,6 +119,7 @@ test-suite skat-test
, skat
, split
, text
, transformers
, vector
, websockets
default-language: Haskell2010
+25 -9
View File
@@ -5,30 +5,46 @@
module Skat where
import Control.Monad.State
import Control.Monad.Writer
import Control.Monad.Reader
import Data.List
import Data.Vector (Vector)
import Skat.Card
import Skat.Bidding
import Skat.Pile
import Skat.Player (Players)
import qualified Skat.Player as P
data SkatEnv = SkatEnv { piles :: Piles
, turnColour :: Maybe Colour
, trumpColour :: Colour
, turnColour :: Maybe TurnColour
, skatGame :: Game
, players :: Players
, currentHand :: Hand }
, currentHand :: Hand
, skatSinglePlayer :: Hand }
deriving Show
type Skat = StateT SkatEnv IO
type Skat = StateT SkatEnv (WriterT [Trick] IO)
runSkat :: Skat a -> SkatEnv -> IO (a, SkatEnv, [Trick])
runSkat action env = do
((val, env'), tricks) <- runWriterT $ runStateT action env
return (val, env', tricks)
evalSkat :: Skat a -> SkatEnv -> IO a
evalSkat action = (fmap fst) . runWriterT . evalStateT action
execSkat :: Skat a -> SkatEnv -> IO SkatEnv
execSkat action = (fmap fst) . runWriterT . execStateT action
instance P.MonadPlayer Skat where
trumpColour = gets trumpColour
trump = getTrump <$> P.game
turnColour = gets turnColour
showSkat p = case P.team p of
Single -> fmap (Just . skatCards) $ gets piles
Team -> return Nothing
singlePlayer = gets skatSinglePlayer
game = gets skatGame
instance P.MonadPlayerOpen Skat where
showPiles = gets piles
@@ -44,19 +60,19 @@ modifyPlayers :: (Players -> Players) -> Skat ()
modifyPlayers f = modify g
where g env@(SkatEnv {players}) = env { players = f players }
setTurnColour :: Maybe Colour -> SkatEnv -> SkatEnv
setTurnColour :: Maybe TurnColour -> SkatEnv -> SkatEnv
setTurnColour col sk = sk { turnColour = col }
setCurrentHand :: Hand -> SkatEnv -> SkatEnv
setCurrentHand hand sk = sk { currentHand = hand }
mkSkatEnv :: Piles -> Maybe Colour -> Colour -> Players -> Hand -> SkatEnv
mkSkatEnv :: Piles -> Maybe TurnColour -> Game -> Players -> Hand -> Hand -> SkatEnv
mkSkatEnv = SkatEnv
allowedCards :: Skat [CardS Owner]
allowedCards = do
curHand <- gets currentHand
pls <- gets players
turnCol <- gets turnColour
trumpCol <- gets trumpColour
turnCol <- P.turnColour
trumpCol <- P.trump
getp $ allowed curHand trumpCol turnCol
+2 -2
View File
@@ -15,8 +15,8 @@ data Human = Human { getTeam :: Team
instance Player Human where
team = getTeam
hand = getHand
chooseCard p table _ hand = do
trumpCol <- trumpColour
chooseCard p table _ _ hand = do
trumpCol <- trump
turnCol <- turnColour
let possible = filter (isAllowed trumpCol turnCol hand) hand
c <- liftIO $ askIO (map getCard table) (map toCard possible) (map toCard hand)
+55 -32
View File
@@ -6,7 +6,7 @@ module Skat.AI.Online where
import Control.Monad.Reader
import Control.Concurrent.Chan
import Data.Aeson
import Data.Aeson hiding (Result)
import Data.Maybe
import qualified Data.ByteString.Lazy.Char8 as BS
@@ -47,10 +47,8 @@ instance Show (PrepOnline c) where
instance Communicator c => Player (OnlineEnv c) where
team = getTeam
hand = getHand
chooseCard p table _ hand = runReaderT (choose table hand) p >>= \c -> return (c, p)
chooseCard p table _ mayOuvert hand = runReaderT (choose table mayOuvert hand) p >>= \c -> return (c, p)
onCardPlayed p c = runReaderT (cardPlayed c) p >> return p
onGameResults p res = runReaderT (onResults res) p
onGameStart p singlePlayer = runReaderT (onStartOnline singlePlayer) p
instance Communicator c => Bidder (PrepOnline c) where
hand = prepHand
@@ -86,9 +84,19 @@ instance Communicator c => Bidder (PrepOnline c) where
Just (ChosenCards cards) -> return cards
Nothing -> askSkat p bid cards
toPlayer p tm = PL $ OnlineEnv tm (prepHand p) (prepConnection p)
onBid p mayBid reizer gereizter =
liftIO $ send (prepConnection p) (BS.unpack $ encode $ BidEvent mayBid reizer gereizter)
onResponse p response reizer gereizter =
liftIO $ send (prepConnection p) (BS.unpack $ encode $ ResponseEvent response reizer gereizter)
onStart p = do
let cards = prepCards p
let cards = sortRender Jacks $ prepCards p
liftIO $ send (prepConnection p) (BS.unpack $ encode $ CardsQuery cards)
onResult p res =
liftIO $ send (prepConnection p) (BS.unpack $ encode $ GameResultsQuery res)
onGame p game sglPlayer = do
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
@@ -101,45 +109,43 @@ instance (Communicator c, MonadIO m) => MonadClient (Online c m) where
liftIO $ receive conn
instance MonadPlayer m => MonadPlayer (Online a m) where
trumpColour = lift $ trumpColour
trump = lift $ trump
turnColour = lift $ turnColour
showSkat = lift . showSkat
singlePlayer = lift singlePlayer
game = lift game
choose :: HasCard a => (Communicator c, MonadPlayer m) => [CardS Played] -> [a] -> Online c m Card
choose table hand' = do
let hand = map toCard hand'
query (BS.unpack $ encode $ ChooseQuery hand table)
choose :: (HasCard b, HasCard a) => (Communicator c, MonadPlayer m) => [CardS Played] -> Maybe [b] -> [a] -> Online c m Card
choose table mayOuvert hand' = do
gm <- game
let hand = sortRender (getTrump gm) $ map toCard hand'
ouvertCards = fmap (sortRender (getTrump gm) . map toCard) mayOuvert
query (BS.unpack $ encode $ ChooseQuery hand table ouvertCards)
r <- response
case decode (BS.pack r) of
Just (ChosenResponse card) -> do
allowed <- P.isAllowed hand card
if card `elem` hand && allowed then return card else choose table hand'
Nothing -> choose table hand'
if card `elem` hand && allowed then return card else choose table mayOuvert hand'
Nothing -> choose table mayOuvert hand'
cardPlayed :: (Communicator c, MonadPlayer m) => CardS Played -> Online c m ()
cardPlayed card = query (BS.unpack $ encode $ CardPlayedQuery card)
onResults :: (Communicator c, MonadIO m) => (Int, Int) -> Online c m ()
onResults (sgl, tm) = query (BS.unpack $ encode $ GameResultsQuery sgl tm)
onStartOnline :: (Communicator c, MonadPlayer m) => Hand -> Online c m ()
onStartOnline singlePlayer = do
trCol <- trumpColour
ownHand <- asks getHand
query (BS.unpack $ encode $ GameStartQuery trCol ownHand singlePlayer)
-- | QUERIES AND RESPONSES
data Query = ChooseQuery [Card] [CardS Played]
data Query = ChooseQuery [Card] [CardS Played] (Maybe [Card])
| CardPlayedQuery (CardS Played)
| GameResultsQuery Int Int
| GameStartQuery Colour Hand Hand
| GameResultsQuery Result
| GameStartQuery HideGame Hand
| BidQuery Hand Bid
| BidResponseQuery Hand Bid
| AskGameQuery Bid
| AskHandQuery
| AskSkatQuery [Card] Bid
| CardsQuery [Card]
| BidEvent (Maybe Bid) Hand Hand
| ResponseEvent Bool Hand Hand
| NoGameQuery
newtype ChosenResponse = ChosenResponse Card
newtype BidResponse = BidResponse Int
@@ -149,19 +155,21 @@ newtype GameResponse = GameResponse Game
newtype ChosenCards = ChosenCards [Card]
instance ToJSON Query where
toJSON (ChooseQuery hand table) =
object ["query" .= ("choose_card" :: String), "hand" .= hand, "table" .= table]
toJSON (ChooseQuery hand table mayOuvert) =
object [ "query" .= ("choose_card" :: String), "hand" .= hand, "table" .= table
, "single_hand" .= mayOuvert]
toJSON (CardPlayedQuery card) =
object ["query" .= ("card_played" :: String), "card" .= card]
toJSON (GameResultsQuery sgl tm) =
object ["query" .= ("results" :: String), "single" .= sgl, "team" .= tm]
toJSON (GameStartQuery trumps handNo sglPlayer) =
object ["query" .= ("start_game" :: String), "trumps" .= show trumps,
"hand" .= toInt handNo, "single" .= toInt sglPlayer ]
toJSON (GameResultsQuery result) =
object ["query" .= ("results" :: String), "result" .= result]
toJSON (GameStartQuery game sglPlayer) =
object [ "query" .= ("start_game" :: String)
, "game" .= game
, "single" .= show sglPlayer ]
toJSON (BidQuery hand bid) =
object ["query" .= ("bid" :: String), "whom" .= show hand, "current" .= bid]
toJSON (BidResponseQuery hand bid) =
object ["query" .= ("bid_response" :: String), "from" .= show hand ]
object ["query" .= ("bid_response" :: String), "from" .= show hand, "bid" .= bid ]
toJSON (AskHandQuery) =
object ["query" .= ("play_hand" :: String)]
toJSON (AskSkatQuery cards bid) =
@@ -170,6 +178,21 @@ instance ToJSON Query where
object ["query" .= ("cards" :: String), "cards" .= cards ]
toJSON (AskGameQuery bid) =
object ["query" .= ("ask_game" :: String), "bid" .= bid]
toJSON (BidEvent (Just bid) reizer gereizter) =
object ["query" .= ("bid_event" :: String), "bid" .= bid, "reizer" .= show reizer,
"gereizter" .= show gereizter ]
toJSON (BidEvent Nothing reizer gereizter) =
object [ "query" .= ("bid_event" :: String)
, "bid" .= ("weg" :: String)
, "reizer" .= show reizer
, "gereizter" .= show gereizter ]
toJSON (ResponseEvent response reizer gereizter) =
object [ "query" .= ("response_event" :: String)
, "response" .= response
, "reizer" .= show reizer
, "gereizter" .= show gereizter ]
toJSON NoGameQuery =
object [ "query" .= ("no_game" :: String) ]
instance FromJSON ChosenResponse where
parseJSON = withObject "ChosenResponse" $ \v -> ChosenResponse
+23 -20
View File
@@ -22,10 +22,11 @@ import qualified Skat.Player.Utils as P
import Skat.Pile hiding (isSkat)
import Skat.Card
import Skat.Utils
import Skat (Skat, modifyp, mkSkatEnv)
import Skat (Skat, modifyp, mkSkatEnv, evalSkat)
import Skat.Operations
import qualified Skat.AI.Minmax as Minmax
import qualified Skat.AI.Stupid as Stupid (Stupid(..))
import Skat.Bidding
data AIEnv = AIEnv { getTeam :: Team
, getHand :: Hand
@@ -55,8 +56,8 @@ modifyg f = modify g
type AI m = StateT AIEnv m
instance MonadPlayer m => MonadPlayer (AI m) where
trumpColour = lift $ trumpColour
turnColour = lift $ turnColour
trump = lift trump
turnColour = lift turnColour
showSkat = lift . showSkat
instance MonadPlayerOpen m => MonadPlayerOpen (AI m) where
@@ -65,7 +66,7 @@ instance MonadPlayerOpen m => MonadPlayerOpen (AI m) where
type Simulator m = ReaderT Piles (AI m)
instance MonadPlayer m => MonadPlayer (Simulator m) where
trumpColour = lift $ trumpColour
trump = lift trump
turnColour = lift $ turnColour
showSkat = lift . showSkat
@@ -79,7 +80,7 @@ runWithPiles ps sim = runReaderT sim ps
instance Player AIEnv where
team = getTeam
hand = getHand
chooseCard p table fallen hand = runStateT (do
chooseCard p table fallen _ hand = runStateT (do
modify $ setTable table
modify $ setHand (map toCard hand)
modify $ setFallen fallen
@@ -112,15 +113,15 @@ has hand cs = M.mapWithKey f
| card `elem` cs = [H hand]
| otherwise = hands
hasNoLonger :: MonadPlayer m => Hand -> Colour -> AI m ()
hasNoLonger :: MonadPlayer m => Hand -> TurnColour -> AI m ()
hasNoLonger hand colour = do
trCol <- trumpColour
trCol <- trump
modifyg $ hasNoLonger_ trCol hand colour
hasNoLonger_ :: Colour -> Hand -> Colour -> Guess -> Guess
hasNoLonger_ trColour hand effCol = M.mapWithKey f
hasNoLonger_ :: Trump -> Hand -> TurnColour -> Guess -> Guess
hasNoLonger_ trump hand effCol = M.mapWithKey f
where f card hands
| effectiveColour trColour card == effCol && (H hand) `elem` hands = filter (/=H hand) hands
| effectiveColour trump card == effCol && (H hand) `elem` hands = filter (/=H hand) hands
| otherwise = hands
isSkat :: [Card] -> Guess -> Guess
@@ -136,7 +137,7 @@ analyzeTurn (c1, c2, c3) = do
modifyg (getCard c1 `hasBeenPlayed`)
modifyg (getCard c2 `hasBeenPlayed`)
modifyg (getCard c3 `hasBeenPlayed`)
trCol <- trumpColour
trCol <- trump
let turnCol = getColour $ getCard c1
demanded = effectiveColour trCol (getCard c1)
col2 = effectiveColour trCol (getCard c2)
@@ -218,7 +219,7 @@ onPlayed :: MonadPlayer m => CardS Played -> AI m ()
onPlayed c = do
liftIO $ print c
modifyg (getCard c `hasBeenPlayed`)
trCol <- trumpColour
trCol <- trump
turnCol <- turnColour
let col = effectiveColour trCol (getCard c)
case turnCol of
@@ -308,14 +309,15 @@ chooseSimulating :: (MonadState AIEnv m, MonadPlayerOpen m)
chooseSimulating = do
piles <- showPiles
turnCol <- turnColour
trumpCol <- trumpColour
trumpCol <- trump
myHand <- gets getHand
depth <- gets simulationDepth
let ps = Players (PL $ Stupid.Stupid Team Hand1)
(PL $ Stupid.Stupid Team Hand2)
(PL $ Stupid.Stupid Single Hand3)
env = mkSkatEnv piles turnCol trumpCol ps myHand
liftIO $ evalStateT (toCard <$> (Minmax.choose depth :: Skat (CardS Owner))) env
-- TODO: fix
env = mkSkatEnv piles turnCol undefined ps myHand undefined
liftIO $ evalSkat (toCard <$> (Minmax.choose depth :: Skat (CardS Owner))) env
simulate :: (MonadState AIEnv m, MonadPlayerOpen m)
=> Card -> m Int
@@ -323,7 +325,7 @@ simulate card = do
-- retrieve all relevant info
piles <- showPiles
turnCol <- turnColour
trumpCol <- trumpColour
trumpCol <- trump
myTeam <- gets getTeam
myHand <- gets getHand
depth <- gets simulationDepth
@@ -334,9 +336,10 @@ simulate card = do
(PL $ mkAIEnv Team Hand1 newDepth)
(PL $ mkAIEnv Team Hand2 newDepth)
(PL $ mkAIEnv Single Hand3 newDepth)
env = mkSkatEnv piles turnCol trumpCol ps (next myHand)
-- TODO: fix
env = mkSkatEnv piles turnCol undefined ps (next myHand) undefined
-- simulate the game after playing the given card
(sgl, tm) <- liftIO $ evalStateT (do
(sgl, tm) <- liftIO $ evalSkat (do
modifyp $ playCard myHand card
turnGeneric playOpen depth) env
let v = if myTeam == Single then (sgl, tm) else (tm, sgl)
@@ -357,7 +360,7 @@ predictValue (own, others) = do
potential :: (MonadState AIEnv m, MonadPlayerOpen m, HasCard c)
=> [c] -> m Int
potential cs = do
tr <- trumpColour
tr <- trump
let trs = filter (isTrump tr) cs
value = count . map toCard $ cs
positions <- filter (==0) <$> mapM (position . toCard) cs
@@ -366,7 +369,7 @@ potential cs = do
position :: (MonadState AIEnv m, MonadPlayer m)
=> Card -> m Int
position card = do
tr <- trumpColour
tr <- trump
guess <- gets guess
let effCol = effectiveColour tr card
l = M.toList guess
+6 -1
View File
@@ -48,13 +48,18 @@ initServer :: Net.PortNumber -> Buffering -> OnReceive -> IO ServerEnv
initServer port buffermode handler = do
sock <- Net.socket Net.AF_INET Net.Stream 0
Net.setSocketOption sock Net.ReuseAddr 1
Net.bind sock (Net.SockAddrInet port Net.iNADDR_ANY)
addr <- Net.addrAddress <$> resolve
Net.bind sock addr
Net.listen sock 5
chan <- newChan
forkIO $ forever $ do
msg <- readChan chan -- clearing the main channel
return ()
return (ServerEnv buffermode sock chan handler)
where resolve = do
let hints = Net.defaultHints { Net.addrSocketType = Net.Stream }
addrs <- Net.getAddrInfo (Just hints) (Just "127.0.0.1") (Just $ show port)
return $ head addrs
close :: ServerEnv -> IO ()
close = Net.close . socket
+11 -6
View File
@@ -1,9 +1,13 @@
module Skat.AI.Stupid where
import Control.Concurrent
import Control.Monad.State
import Skat.Player
import Skat.Pile
import Skat.Card
import Skat.Preperation
import Skat.Bidding
data Stupid = Stupid { getTeam :: Team
, getHand :: Hand }
@@ -12,9 +16,10 @@ data Stupid = Stupid { getTeam :: Team
instance Player Stupid where
team = getTeam
hand = getHand
chooseCard p _ _ hand = do
trumpCol <- trumpColour
chooseCard p _ _ _ hand = do
trumpCol <- trump
turnCol <- turnColour
liftIO $ threadDelay 1000000
let possible = filter (isAllowed trumpCol turnCol hand) hand
return (toCard $ head possible, p)
@@ -24,10 +29,10 @@ newtype NoBidder = NoBidder Hand
-- | no bidding from that player
instance Bidder NoBidder where
hand (NoBidder h) = h
askBid _ _ _ = return Nothing
askResponse _ _ _ = return False
askGame _ _ = undefined -- never called
askHand _ _ = return False -- never called
askBid _ _ bid = return Nothing
askResponse _ _ bid = if bid < 24 then return True else return False
askGame _ _ = return $ Grand Hand
askHand _ _ = return True
askSkat _ _ _ = undefined -- never called
toPlayer (NoBidder h) team = PL $ Stupid team h
onStart _ = return ()
+175 -10
View File
@@ -1,15 +1,19 @@
{-# LANGUAGE OverloadedStrings #-}
module Skat.Bidding (
biddingScore, Game(..), Modifier(..)
biddingScore, Game(..), Modifier(..), isHand, getTrump, Result(..),
getResults, isOuvert, isSchwarz, Bid, checkGame, HideGame(..)
) where
import Data.Aeson hiding (Null)
import Data.Aeson hiding (Null, Result)
import Skat.Card
import Data.List (sortOn)
import Data.Ord (Down(..))
import Control.Monad
import Skat.Pile
type Bid = Int
-- | different game types
data Game = Colour Colour Modifier
@@ -20,6 +24,26 @@ data Game = Colour Colour Modifier
| NullOuvertHand
deriving (Show, Eq)
newtype HideGame = HideGame Game
deriving (Show, Eq)
instance ToJSON Game where
toJSON (Grand mod) =
object ["game" .= ("grand" :: String), "modifier" .= show mod]
toJSON (Colour col mod) =
object ["game" .= ("colour" :: String), "modifier" .= show mod, "colour" .= show col]
toJSON Null = object ["game" .= ("null" :: String)]
toJSON NullHand = object ["game" .= ("nullhand" :: String)]
toJSON NullOuvert = object ["game" .= ("nullouvert" :: String)]
toJSON NullOuvertHand = object ["game" .= ("nullouverthand" :: String)]
instance ToJSON HideGame where
toJSON (HideGame (Grand mod)) =
object ["game" .= ("grand" :: String), "modifier" .= prettyShow mod]
toJSON (HideGame (Colour col mod)) =
object ["game" .= ("colour" :: String), "modifier" .= prettyShow mod, "colour" .= show col]
toJSON (HideGame game) = toJSON game
instance FromJSON Game where
parseJSON = withObject "Game" $ \v -> do
gamekind <- v .: "game"
@@ -44,7 +68,7 @@ data Modifier = Einfach
| Hand
| HandSchneider
| HandSchneiderAngesagt
| HandSchneiderSchwarz
| HandSchwarz
| HandSchneiderAngesagtSchwarz
| HandSchwarzAngesagt
| Ouvert
@@ -64,6 +88,45 @@ instance FromJSON Modifier where
_ -> return Hand
else return Einfach
prettyShow :: Modifier -> String
prettyShow Schneider = show Einfach
prettyShow Schwarz = show Einfach
prettyShow HandSchneider = show Hand
prettyShow HandSchwarz = show Hand
prettyShow HandSchneiderAngesagtSchwarz = show HandSchneiderAngesagt
prettyShow mod = show mod
isHand :: Game -> Bool
isHand NullHand = True
isHand NullOuvertHand = True
isHand (Colour _ mod) = modIsHand mod
isHand (Grand mod) = modIsHand mod
isHand _ = False
modIsHand :: Modifier -> Bool
modIsHand Einfach = False
modIsHand Schneider = False
modIsHand Schwarz = False
modIsHand _ = True
isOuvert :: Game -> Bool
isOuvert NullOuvert = True
isOuvert NullOuvertHand = True
isOuvert (Grand Ouvert) = True
isOuvert (Colour _ Ouvert) = True
isOuvert _ = False
baseFactor :: Game -> Int
baseFactor (Grand _) = 24
baseFactor (Colour Clubs _) = 12
baseFactor (Colour Spades _) = 11
baseFactor (Colour Hearts _) = 10
baseFactor (Colour Diamonds _) = 9
baseFactor Null = 23
baseFactor NullHand = 35
baseFactor NullOuvert = 46
baseFactor NullOuvertHand = 59
-- | calculate the value of a game with given cards
biddingScore :: HasCard c => Game -> [c] -> Int
biddingScore game@(Grand mod) cards = (spitzen game cards + modifierFactor mod) * 24
@@ -71,10 +134,7 @@ biddingScore game@(Colour Clubs mod) cards = (spitzen game cards + modifierFa
biddingScore game@(Colour Spades mod) cards = (spitzen game cards + modifierFactor mod) * 11
biddingScore game@(Colour Hearts mod) cards = (spitzen game cards + modifierFactor mod) * 10
biddingScore game@(Colour Diamonds mod) cards = (spitzen game cards + modifierFactor mod) * 9
biddingScore Null _ = 23
biddingScore NullHand _ = 35
biddingScore NullOuvert _ = 46
biddingScore NullOuvertHand _ = 59
biddingScore game _ = baseFactor game
-- | calculate the modifier based on the game kind
modifierFactor :: Modifier -> Int
@@ -84,7 +144,7 @@ modifierFactor Schwarz = 3
modifierFactor Hand = 2
modifierFactor HandSchneider = 3
modifierFactor HandSchneiderAngesagt = 4
modifierFactor HandSchneiderSchwarz = 4
modifierFactor HandSchwarz = 4
modifierFactor HandSchneiderAngesagtSchwarz = 5
modifierFactor HandSchwarzAngesagt = 6
modifierFactor Ouvert = 7
@@ -112,6 +172,111 @@ spitzen game cards
-- | get all trumps for a given game out of a hand of cards
getTrumps :: HasCard c => Game -> [c] -> [Card]
getTrumps (Grand _) cards = sortOn Down $ filter ((==Jack) . getType) $ map toCard cards
getTrumps (Colour col _) cards = sortOn Down $ filter (isTrump col) $ map toCard cards
getTrumps (Grand _) cards = sortOn Down $ filter (isTrump Jacks) $ map toCard cards
getTrumps (Colour col _) cards = sortOn Down $ filter (isTrump $ TrumpColour col) $ map toCard cards
getTrumps _ _ = []
-- | get trump for a given game
getTrump :: Game -> Trump
getTrump (Colour col _) = TrumpColour col
getTrump (Grand _) = Jacks
getTrump _ = None
data Result = Result { resultGame :: Game
, resultScore :: Int
, resultSinglePoints :: Int
, resultTeamPoints :: Int }
deriving (Show, Eq)
instance ToJSON Result where
toJSON (Result game points sgl tm) =
object ["game" .= game, "points" .= points, "single" .= sgl, "team" .= tm]
isSchwarz :: Team -> Piles -> Bool
isSchwarz tm = null . wonCards tm
hasWon :: Game -> Piles -> (Bool, Game)
hasWon Null ps = (Single `isSchwarz` ps, Null)
hasWon NullHand ps = (Single `isSchwarz` ps, NullHand)
hasWon NullOuvert ps = (Single `isSchwarz` ps, NullOuvert)
hasWon NullOuvertHand ps = (Single `isSchwarz` ps, NullOuvertHand)
hasWon (Colour col mod) ps = let (b, mod') = meetsCall mod ps
in (b, Colour col mod')
hasWon (Grand mod) ps = let (b, mod') = meetsCall mod ps
in (b, Grand mod')
meetsCall :: Modifier -> Piles -> (Bool, Modifier)
meetsCall Hand ps = case wonByPoints ps of
(b, Schneider) -> (b, HandSchneider)
(b, Schwarz) -> (b, HandSchwarz)
(b, Einfach) -> (b, Hand)
meetsCall Schneider ps = case wonByPoints ps of
(b, Schneider) -> (b, Schneider)
(b, Schwarz) -> (b, Schwarz)
(b, Einfach) -> (False, Schneider)
meetsCall Schwarz ps = case wonByPoints ps of
(b, Schneider) -> (False, Schwarz)
(b, Schwarz) -> (b, Schwarz)
(b, Einfach) -> (False, Schwarz)
meetsCall HandSchneider ps = case wonByPoints ps of
(b, Schneider) -> (b, HandSchneider)
(b, Schwarz) -> (b, HandSchwarz)
(b, Einfach) -> (False, HandSchneider)
meetsCall HandSchneiderAngesagt ps = case wonByPoints ps of
(b, Schneider) -> (b, HandSchneiderAngesagt)
(b, Schwarz) -> (b, HandSchneiderAngesagtSchwarz)
(b, Einfach) -> (False, HandSchneiderAngesagt)
meetsCall HandSchwarz ps = case wonByPoints ps of
(b, Schneider) -> (False, HandSchwarz)
(b, Schwarz) -> (b, HandSchwarz)
(b, Einfach) -> (False, HandSchwarz)
meetsCall HandSchwarzAngesagt ps = case wonByPoints ps of
(b, Schneider) -> (False, HandSchwarzAngesagt)
(b, Schwarz) -> (b, HandSchwarzAngesagt)
(b, Einfach) -> (False, HandSchwarzAngesagt)
meetsCall Ouvert ps = case wonByPoints ps of
(b, Schneider) -> (False, Ouvert)
(b, Schwarz) -> (b, Ouvert)
(b, Einfach) -> (False, Ouvert)
meetsCall _ ps = wonByPoints ps
wonByPoints :: Piles -> (Bool, Modifier)
wonByPoints ps
| Team `isSchwarz` ps = (True, Schwarz)
| sgl >= 90 = (True, Schneider)
| Single `isSchwarz` ps = (False, Schwarz)
| sgl <= 30 = (False, Schneider)
| otherwise = (sgl > 60, Einfach)
where (sgl, _) = count ps :: (Int, Int)
-- | get result of game
getResults :: Game -> Bid -> Hand -> Piles -> Piles -> Result
getResults game bid sglPlayer before after = case checkGame bid hand game of
Just game' -> let (won, afterGame) = hasWon game' after
gameScore = biddingScore afterGame hand
score = if won then gameScore else (-2) * gameScore
in Result afterGame score sglPoints teamPoints
Nothing -> let gameScore = baseFactor game * ceiling (fromIntegral bid / fromIntegral (baseFactor game))
score = (-2) * gameScore
in Result game score sglPoints teamPoints
where hand = skatCards before ++ (map toCard $ handCards sglPlayer before)
(sglPoints, teamPoints) = count after
checkGame :: HasCard c => Bid -> [c] -> Game -> Maybe Game
checkGame bid cards game@(Colour col mod)
| biddingScore game cards >= bid = Just game
| otherwise = upgrade mod >>= \mod' -> checkGame bid cards (Colour col mod')
checkGame bid cards game@(Grand mod)
| biddingScore game cards >= bid = Just game
| otherwise = upgrade mod >>= \mod' -> checkGame bid cards (Grand mod')
checkGame bid cards game
| biddingScore game cards >= bid = Just game
| otherwise = Nothing
upgrade :: Modifier -> Maybe Modifier
upgrade Einfach = Just Schneider
upgrade Schneider = Just Schwarz
upgrade Hand = Just HandSchneider
upgrade HandSchneider = Just HandSchwarz
upgrade HandSchneiderAngesagt = Just HandSchneiderAngesagtSchwarz
upgrade _ = Nothing
+97 -27
View File
@@ -29,7 +29,17 @@ data Type = Seven
| Ten
| Ace
| Jack
deriving (Eq, Ord, Show, Enum, Read)
deriving (Eq, Ord, Show, Enum, Read, Bounded)
data NullType = NSeven
| NEight
| NNine
| NTen
| NJack
| NQueen
| NKing
| NAce
deriving (Eq, Ord, Show, Enum, Read, Bounded)
instance Countable Type Int where
count Ace = 11
@@ -43,10 +53,19 @@ data Colour = Diamonds
| Hearts
| Spades
| Clubs
deriving (Eq, Ord, Show, Enum, Read)
deriving (Eq, Ord, Show, Enum, Read, Bounded)
data Trump = TrumpColour Colour
| Jacks
| None
deriving (Show, Eq)
data TurnColour = TurnColour Colour
| Trump
deriving (Show, Eq)
data Card = Card Type Colour
deriving (Eq, Show, Ord, Read)
deriving (Eq, Show, Ord, Read, Bounded)
getType :: Card -> Type
getType (Card t _) = t
@@ -98,50 +117,101 @@ instance Countable (S.Set Card) Int where
instance NFData Card where
rnf (Card t c) = t `seq` c `seq` ()
equals :: Colour -> Maybe Colour -> Bool
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 col (Just x) = col == x
equals col Nothing = True
isTrump :: HasCard c => Colour -> c -> Bool
isTrump trumpCol crd
isTrump :: HasCard c => Trump -> c -> Bool
isTrump None crd = False
isTrump Jacks crd = getType (toCard crd) == Jack
isTrump (TrumpColour trumpCol) crd
| getType (toCard crd) == Jack = True
| otherwise = getColour (toCard crd) == trumpCol
effectiveColour :: HasCard c => Colour -> c -> Colour
effectiveColour trumpCol crd = if trump then trumpCol else getColour (toCard crd)
where trump = isTrump trumpCol crd
effectiveColour :: HasCard c => Trump -> c -> TurnColour
effectiveColour trump card
| isTrump trump card = Trump
| otherwise = TurnColour $ getColour (toCard card)
isAllowed :: (Foldable t, HasCard c1, HasCard c2) => Colour -> Maybe Colour -> t c1 -> c2 -> Bool
isAllowed trumpCol turnCol cs crd =
isAllowed :: (Foldable t, HasCard c1, HasCard c2) => Trump -> Maybe TurnColour -> t c1 -> c2 -> Bool
isAllowed trump turnCol cs crd =
if col `equals` turnCol
then True
else not $ F.any (\ca -> effectiveColour trumpCol ca `equals` turnCol && toCard ca /= toCard crd) cs
where col = effectiveColour trumpCol (toCard crd)
else not $ F.any (\ca -> effectiveColour trump ca `equals` turnCol && toCard ca /= toCard crd) cs
where col = effectiveColour trump (toCard crd)
compareCards :: Colour
-> Maybe Colour
compareCards :: Trump
-> Maybe TurnColour
-> Card
-> Card
-> Ordering
compareCards _ _ (Card Jack col1) (Card Jack col2) = compare col1 col2
compareCards trumpCol turnCol c1@(Card tp1 col1) c2@(Card tp2 col2) =
compareCards trump turnCol c1@(Card tp1 col1) c2@(Card tp2 col2) =
case (trp1, trp2) of
(True, True) -> compare tp1 tp2
(False, False) -> case compare (col1 `equals` turnCol)
(col2 `equals` turnCol) of
EQ -> compare tp1 tp2
(False, False) -> case ( effectiveColour trump c1 `equals` turnCol
, effectiveColour trump c2 `equals` turnCol ) of
(True, True) -> compareTypes trump tp1 tp2
(True, False) -> GT
(False, True) -> LT
_ -> EQ
_ -> compare trp1 trp2
where trp1 = isTrump trump c1
trp2 = isTrump trump c2
compareRender :: Trump -> Card -> Card -> Ordering
compareRender trump c1@(Card tp1 col1) c2@(Card tp2 col2) =
case (trp1, trp2) of
(True, True) -> case compare tp1 tp2 of
EQ -> compare col1 col2
v -> v
(False, False) -> case compare col1 col2 of
EQ -> compareTypes trump tp1 tp2
v -> v
_ -> compare trp1 trp2
where trp1 = isTrump trumpCol c1
trp2 = isTrump trumpCol c2
where trp1 = isTrump trump c1
trp2 = isTrump trump c2
sortCards :: HasCard c => Colour -> Maybe Colour -> [c] -> [c]
sortCards trumpCol turnCol cs = sortBy f cs
where f c1 c2 = compareCards trumpCol turnCol (toCard c1) (toCard c2)
compareTypes :: Trump
-> Type
-> Type
-> Ordering
compareTypes None tp1 tp2 = compare (toNullType tp1) (toNullType tp2)
where toNullType Seven = NSeven
toNullType Eight = NEight
toNullType Nine = NNine
toNullType Ten = NTen
toNullType Jack = NJack
toNullType Queen = NQueen
toNullType King = NKing
toNullType Ace = NAce
compareTypes _ tp1 tp2 = compare tp1 tp2
highestCard :: HasCard c => Colour -> Maybe Colour -> [c] -> c
highestCard trumpCol turnCol cs = maximumBy f cs
where f c1 c2 = compareCards trumpCol turnCol (toCard c1) (toCard c2)
-- | ascending sort of cards, depending on turn colour
sortCards :: HasCard c => Trump -> Maybe TurnColour -> [c] -> [c]
sortCards trump turnCol cs = sortBy f cs
where f c1 c2 = compareCards trump turnCol (toCard c1) (toCard c2)
-- | descending sort of cards, independent of turn colour
sortRender :: HasCard c => Trump -> [c] -> [c]
sortRender trump cs = sortBy f cs
-- note: reversed order of c1 and c2 to get a descending sort
where f c1 c2 = compareRender trump (toCard c2) (toCard c1)
highestCard :: HasCard c => Trump -> Maybe TurnColour -> [c] -> c
highestCard trump turnCol cs = maximumBy f cs
where f c1 c2 = compareCards trump turnCol (toCard c1) (toCard c2)
shuffleCards :: IO [Card]
shuffleCards = do
+99 -27
View File
@@ -1,22 +1,92 @@
module Skat.Matches (
singleVsBots, pvp, pvpWithBidding, singleWithBidding
singleVsBots, pvp, singleWithBidding, Match(..), Unfinished(..), continue,
Table(..), twoWithBidding, H(..), randomPositions
) where
import Control.Monad.State
import Control.Monad.Reader
import System.Random (mkStdGen)
import System.Random (mkStdGen, newStdGen)
import Skat
import Skat.Operations
import Skat.Player
import Skat.Player as P
import Skat.Pile
import Skat.Card
import Skat.Preperation
import Skat.Bidding
import Skat.Utils (shuffle)
import Skat.AI.Rulebased
import Skat.AI.Online
import Skat.AI.Stupid
data Table = Unfinished Unfinished
| Finished Match
| Pass { tablePiles :: Piles }
deriving Show
data Match = Match { matchPiles :: Piles
, matchResult :: Result
, matchTricks :: [Trick]
, matchSingle :: Hand }
deriving Show
data Unfinished = UnfinishedGame { unfinishedGame :: SkatEnv
, unfinishedPrep :: PrepEnv
, unfinishedTricks :: [Trick] }
| UnfinishedPrep { unfinishedPrep :: PrepEnv }
deriving Show
continue :: Communicator c => Unfinished -> c -> c -> c -> IO Table
continue (UnfinishedGame skatEnv prepEnv tricks) comm1 comm2 comm3 = do
let ps = players skatEnv
ps' = Players
(PL $ OnlineEnv (P.team $ player ps Hand1) (P.hand $ player ps Hand1) comm1)
(PL $ OnlineEnv (P.team $ player ps Hand2) (P.hand $ player ps Hand2) comm2)
(PL $ OnlineEnv (P.team $ player ps Hand3) (P.hand $ player ps Hand3) comm3)
bs = bidders prepEnv
bs' = Bidders
(BD $ PrepOnline (Skat.Preperation.hand $ bidder bs Hand1) comm1 [])
(BD $ PrepOnline (Skat.Preperation.hand $ bidder bs Hand2) comm2 [])
(BD $ PrepOnline (Skat.Preperation.hand $ bidder bs Hand3) comm3 [])
skatEnv' = skatEnv { players = ps' }
prepEnv' = prepEnv { bidders = bs' }
runGame prepEnv' skatEnv'
match :: PrepEnv -> IO Table
match prepEnv = do
(maySkatEnv, prepEnv') <- runStateT runPreperation prepEnv
case maySkatEnv of
Just skatEnv -> runGame prepEnv' skatEnv
Nothing -> do
putStrLn "no one wanted to play"
return $ Pass $ Skat.Preperation.piles prepEnv'
runGame :: PrepEnv -> SkatEnv -> IO Table
runGame prepEnv skatEnv = do
(isFinished, finalEnv, tricks) <- (flip runSkat) skatEnv $ do
-- send current table cards to clients
-- only relevant if this is a continued game
-- otherwise table is empty
table <- getp tableCards
ps <- playersToList <$> gets players
mapM_ (\card -> mapM_ (\p -> onCardPlayed p card) ps) (reverse table)
-- run game
turn
-- return if game has finished
gameOver
if isFinished then do
let res = getResults
(skatGame skatEnv)
(Skat.Preperation.current prepEnv)
(skatSinglePlayer skatEnv)
(Skat.Preperation.piles prepEnv)
(Skat.piles finalEnv)
publishGameResults res (bidders prepEnv)
return $ Finished $ Match (Skat.Preperation.piles prepEnv) res tricks (skatSinglePlayer skatEnv)
else do -- if not finished an error has occured, thus returning unfinished game state
return $ Unfinished $ UnfinishedGame finalEnv prepEnv tricks
-- | predefined card distribution for testing purposes
cardDistr :: Piles
cardDistr = emptyPiles hand1 hand2 hand3 skt
@@ -38,8 +108,8 @@ singleVsBots comm = do
(PL $ OnlineEnv Team Hand1 comm)
(PL $ Stupid Team Hand2)
(PL $ mkAIEnv Single Hand3 10)
env = SkatEnv (distribute cards) Nothing Spades ps Hand1
liftIO $ evalStateT (publishGameStart >> turn >>= publishGameResults) env
env = SkatEnv (distribute cards) Nothing (Colour Spades Einfach) ps Hand1 Hand3
void $ evalSkat turn env
singleWithBidding :: Communicator c => c -> IO ()
singleWithBidding comm = do
@@ -50,25 +120,31 @@ singleWithBidding comm = do
(BD $ PrepOnline Hand1 comm h1)
(BD $ NoBidder Hand2)
(BD $ NoBidder Hand3)
env = PrepEnv ps bs
maySkatEnv <- liftIO $ runReaderT runPreperation env
case maySkatEnv of
Just skatEnv ->
liftIO $ evalStateT (publishGameStart >> turn >>= publishGameResults) skatEnv
Nothing -> putStrLn "No one wanted to play."
env = makePrep ps bs
void $ match env
pvp :: Communicator c => c -> c -> c -> IO ()
pvp comm1 comm2 comm3 = do
--- helper object for twoWithBidding
data H = P1 | P2 | AI
randomPositions :: IO [H]
randomPositions = do
gen <- newStdGen
return $ shuffle gen [P1, P2, AI]
twoWithBidding :: Communicator c => [H] -> c -> c -> IO ()
twoWithBidding positions comm1 comm2 = do
cards <- shuffleCards
let ps = Players
(PL $ OnlineEnv Team Hand1 comm1)
(PL $ OnlineEnv Team Hand2 comm2)
(PL $ OnlineEnv Team Hand3 comm3)
env = SkatEnv (distribute cards) Nothing Spades ps Hand1
liftIO $ evalStateT (publishGameStart >> turn >>= publishGameResults) env
let bds = zipWith mkBidder [Hand1, Hand2, Hand3] positions
ps = distribute cards
mkBidder hand P1 = BD $ PrepOnline hand comm1 (map toCard $ handCards hand ps)
mkBidder hand P2 = BD $ PrepOnline hand comm2 (map toCard $ handCards hand ps)
mkBidder hand AI = BD $ NoBidder hand
bs = Bidders (bds !! 0) (bds !! 1) (bds !! 2)
env = makePrep ps bs
void $ match env
pvpWithBidding :: Communicator c => c -> c -> c -> IO ()
pvpWithBidding comm1 comm2 comm3 = do
pvp :: Communicator c => c -> c -> c -> IO Table
pvp comm1 comm2 comm3 = do
cards <- shuffleCards
let ps = distribute cards
h1 = map toCard $ handCards Hand1 ps
@@ -78,9 +154,5 @@ pvpWithBidding comm1 comm2 comm3 = do
(BD $ PrepOnline Hand1 comm1 $ h1)
(BD $ PrepOnline Hand2 comm2 $ h2)
(BD $ PrepOnline Hand3 comm3 $ h3)
env = PrepEnv ps bs
maySkatEnv <- liftIO $ runReaderT runPreperation env
case maySkatEnv of
Just skatEnv ->
liftIO $ evalStateT (publishGameStart >> turn >>= publishGameResults) skatEnv
Nothing -> putStrLn "No one wanted to play."
env = makePrep ps bs
match env
+46 -31
View File
@@ -1,9 +1,12 @@
module Skat.Operations (
turn, turnGeneric, play, playOpen, publishGameResults,
publishGameStart, play_, sortRender, undo_
turn, turnGeneric, play, playOpen,
play_, sortRender, undo_, gameOver
) where
import Control.Monad.State
import Control.Monad.Catch
import Control.Exception hiding (catch, bracketOnError)
import Control.Monad.Writer (tell)
import System.Random (newStdGen, randoms)
import Data.List
import Data.Ord
@@ -13,21 +16,15 @@ import Skat
import Skat.Card
import Skat.Pile
import Skat.Player (chooseCard, Players(..), Player(..), PL(..),
updatePlayer, playersToList, player, MonadPlayer, getSinglePlayer)
updatePlayer, playersToList, player, MonadPlayer, getSinglePlayer, trump, game,
singlePlayer)
import Skat.Utils (shuffle)
compareRender :: Card -> Card -> Ordering
compareRender (Card t1 c1) (Card t2 c2) = case compare c1 c2 of
EQ -> compare t1 t2
v -> v
sortRender :: [Card] -> [Card]
sortRender = sortBy compareRender
import Skat.Bidding
play_ :: HasCard c => c -> Skat ()
play_ card = do
hand <- gets currentHand
trCol <- gets trumpColour
trCol <- trump
modifyp $ playCard hand card
table <- getp tableCards
case length table of
@@ -36,7 +33,7 @@ play_ card = do
3 -> evaluateTable >>= modify . setCurrentHand
_ -> modify (setCurrentHand $ next hand)
undo_ :: HasCard c => c -> Hand -> Maybe Colour -> Team -> Skat ()
undo_ :: HasCard c => c -> Hand -> Maybe TurnColour -> Team -> Skat ()
undo_ card oldCurrent oldTurnCol oldWinner = do
modify $ setCurrentHand oldCurrent
modify $ setTurnColour oldTurnCol
@@ -50,19 +47,34 @@ turnGeneric playFunc depth = do
table <- getp tableCards
ps <- gets players
let p = player ps n
over <- getp $ handEmpty n
trCol <- gets trumpColour
trCol <- trump
case length table of
0 -> playFunc p >> modify (setCurrentHand $ next n) >> turnGeneric playFunc depth
0 -> do
catchAll
(do
playFunc p
modify (setCurrentHand $ next n)
turnGeneric playFunc depth)
(\_ -> countGame)
1 -> do
modify $ setTurnColour
(Just $ effectiveColour trCol $ head table)
catchAll
(do
playFunc p
modify (setCurrentHand $ next n)
turnGeneric playFunc depth
2 -> playFunc p >> modify (setCurrentHand $ next n) >> turnGeneric playFunc depth
turnGeneric playFunc depth)
(\_ -> countGame)
2 -> do
catchAll
(do
playFunc p
modify (setCurrentHand $ next n)
turnGeneric playFunc depth)
(\_ -> countGame)
3 -> do
w <- evaluateTable
over <- gameOver
if depth <= 1 || over
then countGame
else modify (setCurrentHand w) >> turnGeneric playFunc (depth - 1)
@@ -72,7 +84,7 @@ turn = turnGeneric play 10
evaluateTable :: Skat Hand
evaluateTable = do
trumpCol <- gets trumpColour
trumpCol <- trump
turnCol <- gets turnColour
table <- getp tableCards
ps <- gets players
@@ -80,6 +92,7 @@ evaluateTable = do
winner = player ps winnerHand
modifyp $ cleanTable (team winner)
modify $ setTurnColour Nothing
tell [(table !! 2, table !! 1, table !! 0)]
return $ hand winner
countGame :: Skat (Int, Int)
@@ -89,10 +102,13 @@ play :: (Show p, Player p) => p -> Skat Card
play p = do
table <- getp tableCards
turnCol <- gets turnColour
trump <- gets trumpColour
trump <- trump
cards <- getp $ handCards (hand p)
fallen <- getp played
(card, p') <- chooseCard p table fallen cards
ouvert <- isOuvert <$> game
mayOuvert <- if ouvert then Just <$> (singlePlayer >>= getp . handCards)
else return Nothing
(card, p') <- chooseCard p table fallen mayOuvert cards
modifyPlayers $ updatePlayer p'
modifyp $ playCard (hand p) card
ps <- fmap playersToList $ gets players
@@ -108,13 +124,12 @@ playOpen p = do
modifyp $ playCard (hand p) card
return card
publishGameResults :: (Int, Int) -> Skat ()
publishGameResults res = do
pls <- gets players
mapM_ (\p -> onGameResults p res) (playersToList pls)
publishGameStart :: Skat ()
publishGameStart = do
pls <- gets players
let sglPlayer = getSinglePlayer pls
mapM_ (\p -> onGameStart p sglPlayer) (playersToList pls)
gameOver :: Skat Bool
gameOver = do
tr <- trump
case tr of
None -> do
singleLost <- gets piles >>= return . not . (Single `isSchwarz`)
if singleLost then return True
else gets currentHand >>= getp . handCards >>= return . null
_ -> gets currentHand >>= getp . handCards >>= return . null
+55 -4
View File
@@ -6,6 +6,9 @@
module Skat.Pile where
import Control.Monad.State
import Control.Monad.Trans.Maybe
import Prelude hiding (lookup)
import qualified Data.Map.Strict as M
import qualified Data.Vector as V
@@ -15,6 +18,8 @@ import Data.Maybe
import Data.Aeson
import Control.Exception
import Data.List (delete)
import Text.Read (readMaybe)
import Debug.Trace
import Skat.Card
import Skat.Utils
@@ -41,7 +46,7 @@ instance ToJSON p => ToJSON (CardS p) where
object ["card" .= card, "pile" .= pile]
data Hand = Hand1 | Hand2 | Hand3
deriving (Show, Eq, Ord, Read)
deriving (Show, Eq, Ord, Read, Enum, Bounded)
toInt :: Hand -> Int
toInt Hand1 = 1
@@ -61,12 +66,33 @@ prev Hand3 = Hand2
data Owner = P Hand | S
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
toJSON (P hand) = object ["owner" .= show hand]
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 Trick = (CardS Owner, CardS Owner, CardS Owner)
data Piles = Piles { _hand1 :: [CardS Owner]
, _hand2 :: [CardS Owner]
, _hand3 :: [CardS Owner]
@@ -153,12 +179,12 @@ handCards Hand1 = _hand1
handCards Hand2 = _hand2
handCards Hand3 = _hand3
allowed :: Hand -> Colour -> Maybe Colour -> Piles -> [CardS Owner]
allowed hand trCol turnCol ps
allowed :: Hand -> Trump -> Maybe TurnColour -> Piles -> [CardS Owner]
allowed hand trump turnCol ps
| null sameColour = cards
| otherwise = sameColour
where cards = handCards hand ps
sameColour = filter (\ca -> effectiveColour trCol ca `equals` turnCol) cards
sameColour = filter (\ca -> effectiveColour trump ca `equals` turnCol) cards
skatCards :: Piles -> [Card]
skatCards = map getCard . _skat
@@ -185,3 +211,28 @@ distribute cards = emptyPiles hand1 hand2 hand3 skt
hand1 = concatMap (!! 0) [round1, round2, round3]
hand2 = concatMap (!! 1) [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)
+13 -18
View File
@@ -6,11 +6,14 @@ import Control.Monad.IO.Class
import Skat.Card
import Skat.Pile
import Skat.Bidding
class (Monad m, MonadIO m) => MonadPlayer m where
trumpColour :: m Colour
turnColour :: m (Maybe Colour)
trump :: m Trump
turnColour :: m (Maybe TurnColour)
showSkat :: Player p => p -> m (Maybe [Card])
singlePlayer :: m Hand
game :: m Game
class (Monad m, MonadIO m, MonadPlayer m) => MonadPlayerOpen m where
showPiles :: m (Piles)
@@ -18,10 +21,11 @@ class (Monad m, MonadIO m, MonadPlayer m) => MonadPlayerOpen m where
class Player p where
team :: p -> Team
hand :: p -> Hand
chooseCard :: (HasCard c, MonadPlayer m)
chooseCard :: (HasCard d, HasCard c, MonadPlayer m)
=> p
-> [CardS Played]
-> [CardS Played]
-> Maybe [d]
-> [c]
-> m (Card, p)
onCardPlayed :: MonadPlayer m
@@ -37,17 +41,10 @@ class Player p where
let table = tableCards piles
fallen = played piles
myCards = handCards (hand p) piles
fst <$> chooseCard p table fallen myCards
onGameResults :: MonadIO m
=> p
-> (Int, Int)
-> m ()
onGameResults _ _ = return ()
onGameStart :: MonadPlayer m
=> p
-> Hand
-> m ()
onGameStart _ _ = return ()
ouvert <- isOuvert <$> game
mayOuvert <- if ouvert then Just <$> (singlePlayer >>= \hnd -> return $ handCards hnd piles)
else return Nothing
fst <$> chooseCard p table fallen mayOuvert myCards
data PL = forall p. (Show p, Player p) => PL p
@@ -57,15 +54,13 @@ instance Show PL where
instance Player PL where
team (PL p) = team p
hand (PL p) = hand p
chooseCard (PL p) table fallen hand = do
(v, a) <- chooseCard p table fallen hand
chooseCard (PL p) table fallen mayOuvert hand = do
(v, a) <- chooseCard p table fallen mayOuvert hand
return $ (v, PL a)
onCardPlayed (PL p) card = do
v <- onCardPlayed p card
return $ PL v
chooseCardOpen (PL p) = chooseCardOpen p
onGameResults (PL p) res = onGameResults p res
onGameStart (PL p) singlePlayer = onGameStart p singlePlayer
data Players = Players PL PL PL
deriving Show
+4 -4
View File
@@ -8,11 +8,11 @@ import Skat.Card (Card, HasCard(..))
isAllowed :: (HasCard c, MonadPlayer m) => [c] -> c -> m Bool
isAllowed hand card = do
trCol <- trumpColour
tr <- trump
turnCol <- turnColour
return $ C.isAllowed trCol turnCol hand card
return $ C.isAllowed tr turnCol hand card
isTrump :: MonadPlayer m => Card -> m Bool
isTrump card = do
trCol <- trumpColour
return $ C.isTrump trCol card
tr <- trump
return $ C.isTrump tr card
+80 -15
View File
@@ -1,11 +1,13 @@
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE TupleSections #-}
module Skat.Preperation (
Bidder(..), Bid, BD(..), Bidders(..), PrepEnv(..), runPreperation
Bidder(..), Bid, BD(..), Bidders(..), PrepEnv(..), runPreperation,
publishGameResults, bidder, makePrep
) where
import Control.Monad.IO.Class
import Control.Monad.Reader
import Control.Monad.State
import Skat.Pile
import Skat.Card
@@ -13,13 +15,15 @@ import Skat.Player (PL, Players(..))
import Skat.Bidding
import Skat (SkatEnv, mkSkatEnv)
type Bid = Int
data PrepEnv = PrepEnv { piles :: Piles
, bidders :: Bidders }
, bidders :: Bidders
, current :: Bid }
deriving Show
type Preperation = ReaderT PrepEnv IO
makePrep :: Piles -> Bidders -> PrepEnv
makePrep ps bd = PrepEnv ps bd 0
type Preperation = StateT PrepEnv IO
class Bidder a where
hand :: a -> Hand
@@ -30,6 +34,16 @@ class Bidder a where
askHand :: MonadIO m => a -> Bid -> m Bool
askSkat :: MonadIO m => a -> Bid -> [Card] -> m [Card]
toPlayer :: a -> Team -> PL
onBid :: MonadIO m => a -> Maybe Bid -> Hand -> Hand -> m ()
onBid _ _ _ _ = return ()
onResponse :: MonadIO m => a -> Bool -> Hand -> Hand -> m ()
onResponse _ _ _ _ = return ()
onGame :: MonadIO m => a -> HideGame -> Hand -> m ()
onGame _ _ _ = return ()
onResult :: MonadIO m => a -> Result -> m ()
onResult _ _ = return ()
onNoGame :: MonadIO m => a -> m ()
onNoGame _ = return ()
-- | trick to allow heterogenous bidder list
data BD = forall b. (Show b, Bidder b) => BD b
@@ -46,6 +60,11 @@ instance Bidder BD where
askResponse (BD b) = askResponse b
toPlayer (BD b) = toPlayer b
onStart (BD b) = onStart b
onGame (BD b) = onGame b
onResult (BD b) = onResult b
onBid (BD b) = onBid b
onResponse (BD b) = onResponse b
onNoGame (BD b) = onNoGame b
data Bidders = Bidders BD BD BD
deriving Show
@@ -63,46 +82,92 @@ toPlayers single (Bidders b1 b2 b3) =
runPreperation :: Preperation (Maybe SkatEnv)
runPreperation = do
bds <- asks bidders
bds <- gets bidders
onStart (bidder bds Hand1)
onStart (bidder bds Hand2)
onStart (bidder bds Hand3)
(winner, bid) <- runBidding 0 (bidder bds Hand2) (bidder bds Hand1)
(finalWinner, finalBid) <- runBidding 0 (bidder bds Hand3) (bidder bds winner)
(finalWinner, finalBid) <- runBidding bid (bidder bds Hand3) (bidder bds winner)
if finalBid == 0 then do
bid <- askBid (bidder bds finalWinner) finalWinner 0
publishBid bid finalWinner finalWinner
case bid of
Just val -> Just <$> initGame finalWinner val
Nothing -> return Nothing
Nothing -> publishNoGame >> return Nothing
else Just <$> initGame finalWinner finalBid
runBidding :: Bid -> BD -> BD -> Preperation (Hand, Bid)
runBidding startingBid reizer gereizter = do
first <- askBid reizer (hand gereizter) startingBid
case first of
Just val -> do
Just val
| val > startingBid -> do
publishBid first (hand reizer) (hand gereizter)
modify $ \env -> env { current = val }
response <- askResponse gereizter (hand reizer) val
publishResponse response (hand reizer) (hand gereizter)
if response then runBidding val reizer gereizter
else return (hand reizer, val)
Nothing -> return (hand gereizter, startingBid)
| otherwise -> do
publishBid Nothing (hand reizer) (hand gereizter)
return (hand gereizter, startingBid)
Nothing -> do
publishBid Nothing (hand reizer) (hand gereizter)
return (hand gereizter, startingBid)
initGame :: Hand -> Bid -> Preperation SkatEnv
initGame single bid = do
ps <- asks piles
bds <- asks bidders
ps <- gets piles
bds <- gets bidders
-- ask if player wants to play hand
noSkat <- askHand (bidder bds single) bid
-- either return piles or ask for skat cards and modify piles
ps' <- if noSkat then return ps else handleSkat (bidder bds single) bid ps
-- ask for game kind
(Colour col _) <- askGame (bidder bds single) bid
game <- handleGame (bidder bds single) bid noSkat
-- publish game start
publishGameStart game single
-- construct skat env
return $ mkSkatEnv ps Nothing col (toPlayers single bds) Hand1
return $ mkSkatEnv ps' Nothing game (toPlayers single bds) Hand1 single
handleGame :: BD -> Bid -> Bool -> Preperation Game
handleGame bd bid noSkat = do
cards <- (\ps -> map toCard (handCards (hand bd) ps) ++ skatCards ps) <$> gets piles
-- ask bidder for game
proposal <- askGame bd bid
-- check if proposal is allowed
if isHand proposal == noSkat then return proposal else handleGame bd bid noSkat
handleSkat :: BD -> Bid -> Piles -> Preperation Piles
handleSkat bd bid ps = do
let skat = skatCards ps
skat' <- askSkat bd bid skat
liftIO $ putStrLn $ "received skat " ++ show skat'
case moveToSkat (hand bd) skat' ps of
Just correct -> return correct
Nothing -> handleSkat bd bid ps
publishGameResults :: MonadIO m => Result -> Bidders -> m ()
publishGameResults res bidders = do
onResult (bidder bidders Hand1) res
onResult (bidder bidders Hand2) res
onResult (bidder bidders Hand3) res
publishGameStart :: Game -> Hand -> Preperation ()
publishGameStart game sglPlayer = mapBidders (\b -> onGame b (HideGame game) sglPlayer)
publishBid :: Maybe Bid -> Hand -> Hand -> Preperation ()
publishBid bid reizer gereizter = mapBidders (\b -> onBid b bid reizer gereizter)
publishResponse :: Bool -> Hand -> Hand -> Preperation ()
publishResponse response reizer gereizter = mapBidders (\b -> onResponse b response reizer gereizter)
publishNoGame :: Preperation ()
publishNoGame = mapBidders onNoGame
mapBidders :: (BD -> Preperation ()) -> Preperation ()
mapBidders f = do
bds <- gets bidders
f (bidder bds Hand1)
f (bidder bds Hand2)
f (bidder bds Hand3)
+39 -1
View File
@@ -1,7 +1,12 @@
{-# LANGUAGE ScopedTypeVariables #-}
module Skat.Utils where
import Control.Monad.State
import Control.Monad.Trans.Maybe
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.Text as T (Text, unpack, pack)
import Data.List (foldl')
@@ -57,3 +62,36 @@ instance Stringy B.ByteString where
instance Stringy T.Text where
toString = T.unpack
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)
+1 -1
View File
@@ -17,7 +17,7 @@
#
# resolver: ./custom-snapshot.yaml
# resolver: https://example.com/snapshots/2018-01-01.yaml
resolver: lts-14.3
resolver: lts-18.18
# User packages to be built.
# Various formats can be used as shown in the example below.