| @@ -19,7 +19,12 @@ import Skat.AI.Rulebased | |||||
| import Skat.AI.Minmax (playCLI) | import Skat.AI.Minmax (playCLI) | ||||
| main :: IO () | main :: IO () | ||||
| main = testAI 10 | |||||
| main = testMinmax 10 | |||||
| testMinmax :: Int -> IO () | |||||
| testMinmax n = do | |||||
| let acs = repeat playSkat | |||||
| sequence_ (take n acs) | |||||
| testAI :: Int -> IO () | testAI :: Int -> IO () | ||||
| testAI n = do | testAI n = do | ||||
| @@ -71,14 +76,11 @@ shuffledEnv2 = do | |||||
| return $ SkatEnv (distribute cards) Nothing Spades pls2 Hand1 | return $ SkatEnv (distribute cards) Nothing Spades pls2 Hand1 | ||||
| env2 :: SkatEnv | env2 :: SkatEnv | ||||
| env2 = SkatEnv piles Nothing Spades playersExamp Hand1 | |||||
| where hand1 = [Card Seven Clubs, Card King Clubs, Card Ace Clubs, Card Queen Diamonds] | |||||
| hand2 = [Card Seven Hearts, Card King Hearts, Card Ace Hearts, Card Queen Spades] | |||||
| env2 = SkatEnv piles Nothing Hearts playersExamp Hand2 | |||||
| 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] | hand3 = [Card Seven Spades, Card King Spades, Card Ace Spades, Card Queen Clubs] | ||||
| h1 = map (putAt Hand1) hand1 | |||||
| h2 = map (putAt Hand2) hand2 | |||||
| h3 = map (putAt Hand3) hand3 | |||||
| piles = Piles (h1 ++ h2 ++ h3) [] [] | |||||
| piles = emptyPiles hand1 hand2 hand3 [] | |||||
| env3 :: SkatEnv | env3 :: SkatEnv | ||||
| env3 = SkatEnv piles Nothing Diamonds pls2 Hand3 | env3 = SkatEnv piles Nothing Diamonds pls2 Hand3 | ||||
| @@ -91,11 +93,7 @@ env3 = SkatEnv piles Nothing Diamonds pls2 Hand3 | |||||
| hand3 = [ Card Jack Hearts, Card Jack Spades, Card Ten Spades, Card Ace Spades, Card Eight Diamonds | hand3 = [ Card Jack Hearts, Card Jack Spades, Card Ten Spades, Card Ace Spades, Card Eight Diamonds | ||||
| , Card Queen Diamonds, Card Ten Diamonds, Card Ten Hearts, Card Queen Hearts, Card King Hearts ] | , Card Queen Diamonds, Card Ten Diamonds, Card Ten Hearts, Card Queen Hearts, Card King Hearts ] | ||||
| skat = [ Card Queen Clubs, Card Queen Spades] | skat = [ Card Queen Clubs, Card Queen Spades] | ||||
| h1 = map (putAt Hand1) hand1 | |||||
| h2 = map (putAt Hand2) hand2 | |||||
| h3 = map (putAt Hand3) hand3 | |||||
| skt = map (putAt SkatP) skat | |||||
| piles = Piles (h1 ++ h2 ++ h3) [] skt | |||||
| piles = emptyPiles hand1 hand2 hand3 skat | |||||
| runWebSocketServer :: IO () | runWebSocketServer :: IO () | ||||
| runWebSocketServer = do | runWebSocketServer = do | ||||
| @@ -110,5 +108,4 @@ application pending = do | |||||
| putStrLn $ BS.unpack msg | putStrLn $ BS.unpack msg | ||||
| playSkat :: IO () | playSkat :: IO () | ||||
| playSkat = do | |||||
| void $ (flip runStateT) env3 playCLI | |||||
| playSkat = void $ (flip runStateT) env3 playCLI | |||||
| @@ -0,0 +1,31 @@ | |||||
| module TestEnvs where | |||||
| import Skat | |||||
| import Skat.Card | |||||
| import Skat.Pile | |||||
| import Skat.Player | |||||
| import Skat.AI.Stupid | |||||
| pls2 :: Players | |||||
| pls2 = Players | |||||
| (PL $ Stupid Team Hand1) | |||||
| (PL $ Stupid Team Hand2) | |||||
| (PL $ Stupid Single Hand3) | |||||
| env3 :: SkatEnv | |||||
| env3 = SkatEnv piles Nothing Diamonds pls2 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 ] | |||||
| hand2 = [ Card Seven Spades, Card Eight Spades, Card Seven Hearts, Card Nine Hearts | |||||
| , Card Ace Hearts, Card King Diamonds, Card Ace Diamonds, Card Nine Clubs | |||||
| , Card King Clubs, Card Ace Clubs ] | |||||
| hand3 = [ Card Jack Hearts, Card Jack Spades, Card Ten Spades, Card Ace Spades, Card Eight Diamonds | |||||
| , Card Queen Diamonds, Card Ten Diamonds, Card Ten Hearts, Card Queen Hearts, Card King Hearts ] | |||||
| skat = [ Card Queen Clubs, Card Queen Spades] | |||||
| piles = emptyPiles hand1 hand2 hand3 skat | |||||
| shuffledEnv2 :: IO SkatEnv | |||||
| shuffledEnv2 = do | |||||
| cards <- shuffleCards | |||||
| return $ SkatEnv (distribute cards) Nothing Spades pls2 Hand1 | |||||
| @@ -33,6 +33,7 @@ dependencies: | |||||
| - parallel | - parallel | ||||
| - containers | - containers | ||||
| - case-insensitive | - case-insensitive | ||||
| - vector | |||||
| library: | library: | ||||
| source-dirs: src | source-dirs: src | ||||
| @@ -45,6 +46,7 @@ executables: | |||||
| - -threaded | - -threaded | ||||
| - -rtsopts | - -rtsopts | ||||
| - -with-rtsopts=-N | - -with-rtsopts=-N | ||||
| - -O2 | |||||
| dependencies: | dependencies: | ||||
| - skat | - skat | ||||
| @@ -56,5 +58,6 @@ tests: | |||||
| - -threaded | - -threaded | ||||
| - -rtsopts | - -rtsopts | ||||
| - -with-rtsopts=-N | - -with-rtsopts=-N | ||||
| - -O2 | |||||
| dependencies: | dependencies: | ||||
| - skat | - skat | ||||
| @@ -4,7 +4,7 @@ cabal-version: 1.12 | |||||
| -- | -- | ||||
| -- see: https://github.com/sol/hpack | -- see: https://github.com/sol/hpack | ||||
| -- | -- | ||||
| -- hash: 3f130a9bf454b63893b6f1958214229a75ad6916b19eb7bb6797a19f0f14dd3e | |||||
| name: skat | name: skat | ||||
| version: 0.1.0.1 | version: 0.1.0.1 | ||||
| @@ -60,16 +60,18 @@ library | |||||
| , random | , random | ||||
| , split | , split | ||||
| , text | , text | ||||
| , vector | |||||
| , websockets | , websockets | ||||
| default-language: Haskell2010 | default-language: Haskell2010 | ||||
| executable skat-exe | executable skat-exe | ||||
| main-is: Main.hs | main-is: Main.hs | ||||
| other-modules: | other-modules: | ||||
| TestEnvs | |||||
| Paths_skat | Paths_skat | ||||
| hs-source-dirs: | hs-source-dirs: | ||||
| app | app | ||||
| ghc-options: -threaded -rtsopts -with-rtsopts=-N | |||||
| ghc-options: -threaded -rtsopts -with-rtsopts=-N -O2 | |||||
| build-depends: | build-depends: | ||||
| aeson | aeson | ||||
| , base >=4.7 && <5 | , base >=4.7 && <5 | ||||
| @@ -84,6 +86,7 @@ executable skat-exe | |||||
| , skat | , skat | ||||
| , split | , split | ||||
| , text | , text | ||||
| , vector | |||||
| , websockets | , websockets | ||||
| default-language: Haskell2010 | default-language: Haskell2010 | ||||
| @@ -94,7 +97,7 @@ test-suite skat-test | |||||
| Paths_skat | Paths_skat | ||||
| hs-source-dirs: | hs-source-dirs: | ||||
| test | test | ||||
| ghc-options: -threaded -rtsopts -with-rtsopts=-N | |||||
| ghc-options: -threaded -rtsopts -with-rtsopts=-N -O2 | |||||
| build-depends: | build-depends: | ||||
| aeson | aeson | ||||
| , base >=4.7 && <5 | , base >=4.7 && <5 | ||||
| @@ -109,5 +112,6 @@ test-suite skat-test | |||||
| , skat | , skat | ||||
| , split | , split | ||||
| , text | , text | ||||
| , vector | |||||
| , websockets | , websockets | ||||
| default-language: Haskell2010 | default-language: Haskell2010 | ||||
| @@ -7,6 +7,7 @@ module Skat where | |||||
| import Control.Monad.State | import Control.Monad.State | ||||
| import Control.Monad.Reader | import Control.Monad.Reader | ||||
| import Data.List | import Data.List | ||||
| import Data.Vector (Vector) | |||||
| import Skat.Card | import Skat.Card | ||||
| import Skat.Pile | import Skat.Pile | ||||
| @@ -52,13 +53,10 @@ setCurrentHand hand sk = sk { currentHand = hand } | |||||
| mkSkatEnv :: Piles -> Maybe Colour -> Colour -> Players -> Hand -> SkatEnv | mkSkatEnv :: Piles -> Maybe Colour -> Colour -> Players -> Hand -> SkatEnv | ||||
| mkSkatEnv = SkatEnv | mkSkatEnv = SkatEnv | ||||
| allowedCards :: Skat [Card] | |||||
| allowedCards :: Skat [CardS Owner] | |||||
| allowedCards = do | allowedCards = do | ||||
| curHand <- gets currentHand | curHand <- gets currentHand | ||||
| pls <- gets players | pls <- gets players | ||||
| turnCol <- gets turnColour | turnCol <- gets turnColour | ||||
| trumpCol <- gets trumpColour | trumpCol <- gets trumpColour | ||||
| ps <- gets piles | |||||
| let p = P.player pls curHand | |||||
| cards = handCards curHand ps | |||||
| return $ filter (isAllowed trumpCol turnCol cards) cards | |||||
| getp $ allowed curHand trumpCol turnCol | |||||
| @@ -19,7 +19,7 @@ instance Player Human where | |||||
| trumpCol <- trumpColour | trumpCol <- trumpColour | ||||
| turnCol <- turnColour | turnCol <- turnColour | ||||
| let possible = filter (isAllowed trumpCol turnCol hand) hand | let possible = filter (isAllowed trumpCol turnCol hand) hand | ||||
| c <- liftIO $ askIO (map getCard table) possible hand | |||||
| c <- liftIO $ askIO (map getCard table) (map toCard possible) (map toCard hand) | |||||
| return $ (c, p) | return $ (c, p) | ||||
| askIO :: [Card] -> [Card] -> [Card] -> IO Card | askIO :: [Card] -> [Card] -> [Card] -> IO Card | ||||
| @@ -10,18 +10,20 @@ module Skat.AI.Minmax ( | |||||
| ) where | ) where | ||||
| import Control.Monad.State | import Control.Monad.State | ||||
| import Control.Exception (assert) | |||||
| import Control.Monad.Fail | import Control.Monad.Fail | ||||
| import Data.Ord | import Data.Ord | ||||
| import Text.Read (readMaybe) | import Text.Read (readMaybe) | ||||
| import Data.List (minimumBy, maximumBy) | |||||
| import Data.List (maximumBy, sortBy) | |||||
| import Debug.Trace | import Debug.Trace | ||||
| import qualified Skat as S | import qualified Skat as S | ||||
| import qualified Skat.Card as S | import qualified Skat.Card as S | ||||
| import qualified Skat.Operations as S | import qualified Skat.Operations as S | ||||
| import qualified Skat.Pile as S | import qualified Skat.Pile as S | ||||
| import qualified Skat.Player as S | |||||
| import qualified Skat.Player as S hiding (trumpColour, turnColour) | |||||
| import qualified Skat.Render as S | import qualified Skat.Render as S | ||||
| --import TestEnvs (env3, shuffledEnv2) | |||||
| debug :: Bool | debug :: Bool | ||||
| debug = False | debug = False | ||||
| @@ -34,15 +36,15 @@ class (Ord v, Eq v) => Value v where | |||||
| class Player p where | class Player p where | ||||
| maxing :: p -> Bool | maxing :: p -> Bool | ||||
| class (Monad m, Value v, Player p, Eq t) => MonadGame t v p m | m -> t, m -> p, m -> v where | |||||
| class (Traversable l, Monad m, Value v, Player p, Eq t) => MonadGame t l v p m | m -> t, m -> p, m -> v, m -> l where | |||||
| currentPlayer :: m p | currentPlayer :: m p | ||||
| turns :: m [t] | |||||
| turns :: m (l t) | |||||
| play :: t -> m () | play :: t -> m () | ||||
| simulate :: t -> m a -> m a | simulate :: t -> m a -> m a | ||||
| evaluate :: m v | evaluate :: m v | ||||
| over :: m Bool | over :: m Bool | ||||
| class (MonadIO m, Show t, Show v, Show p, MonadGame t v p m) => PlayableGame t v p m | m -> t, m -> p, m -> v where | |||||
| class (MonadIO m, Show t, Show v, Show p, MonadGame t l v p m) => PlayableGame t l v p m | m -> t, m -> p, m -> v where | |||||
| showTurns :: m () | showTurns :: m () | ||||
| showBoard :: m () | showBoard :: m () | ||||
| askTurn :: m (Maybe t) | askTurn :: m (Maybe t) | ||||
| @@ -59,25 +61,50 @@ instance Value Int where | |||||
| win = 120 | win = 120 | ||||
| loss = -120 | loss = -120 | ||||
| instance MonadGame S.Card Int S.PL S.Skat where | |||||
| instance MonadGame (S.CardS S.Owner) [] Int S.PL S.Skat where | |||||
| currentPlayer = do | currentPlayer = do | ||||
| hand <- gets S.currentHand | hand <- gets S.currentHand | ||||
| pls <- gets S.players | pls <- gets S.players | ||||
| return $ S.player pls hand | |||||
| return $! S.player pls hand | |||||
| turns = S.allowedCards | turns = S.allowedCards | ||||
| --player <- currentPlayer | |||||
| --trCol <- gets S.trumpColour | |||||
| --return $! if maxing player | |||||
| -- then sortBy (optimalTeam trCol) cards | |||||
| -- else sortBy (optimalSingle trCol) cards | |||||
| play = S.play_ | play = S.play_ | ||||
| simulate card action = do | simulate card action = do | ||||
| --oldCurrent <- gets S.currentHand | |||||
| --oldTurnCol <- gets S.turnColour | |||||
| backup <- get | backup <- get | ||||
| play card | play card | ||||
| --oldWinner <- currentPlayer | |||||
| res <- action | res <- action | ||||
| --S.undo_ card oldCurrent oldTurnCol (S.team oldWinner) | |||||
| put backup | put backup | ||||
| return res | |||||
| over = ((==0) . length) <$> S.allowedCards | |||||
| return $! res | |||||
| over = ((==0) . length) <$!> S.allowedCards | |||||
| evaluate = do | evaluate = do | ||||
| player <- currentPlayer | player <- currentPlayer | ||||
| piles <- gets S.piles | piles <- gets S.piles | ||||
| let (sgl, tm) = S.count piles | let (sgl, tm) = S.count piles | ||||
| return $ (if maxing player then tm - sgl else sgl - tm) | |||||
| return $! (if maxing player then tm - sgl else sgl - tm) | |||||
| potentialByType :: S.Type -> Int | |||||
| potentialByType S.Ace = 11 | |||||
| potentialByType S.Jack = 10 | |||||
| potentialByType S.Ten = 4 | |||||
| potentialByType S.Seven = 7 | |||||
| potentialByType S.Eight = 7 | |||||
| potentialByType S.Nine = 7 | |||||
| potentialByType S.Queen = 5 | |||||
| potentialByType S.King = 5 | |||||
| optimalSingle :: S.Colour -> S.Card -> S.Card -> Ordering | |||||
| optimalSingle trCol (S.Card t1 _) (S.Card t2 _) = (comparing potentialByType) t2 t1 | |||||
| optimalTeam :: S.Colour -> S.Card -> S.Card -> Ordering | |||||
| optimalTeam trCol (S.Card t1 _) (S.Card t2 _) = (comparing potentialByType) t2 t1 | |||||
| -- TIC TAC TOE implementation | -- TIC TAC TOE implementation | ||||
| @@ -106,7 +133,7 @@ data GameState = GameState { getBoard :: [TicTacToe] | |||||
| instance Player Bool where | instance Player Bool where | ||||
| maxing = id | maxing = id | ||||
| instance Monad m => MonadGame Int WinLossTie Bool (StateT GameState m) where | |||||
| instance Monad m => MonadGame Int [] WinLossTie Bool (StateT GameState m) where | |||||
| currentPlayer = gets getCurrent | currentPlayer = gets getCurrent | ||||
| turns = do | turns = do | ||||
| board <- gets getBoard | board <- gets getBoard | ||||
| @@ -123,7 +150,7 @@ instance Monad m => MonadGame Int WinLossTie Bool (StateT GameState m) where | |||||
| play turn | play turn | ||||
| res <- action | res <- action | ||||
| put backup | put backup | ||||
| return res | |||||
| return $! res | |||||
| evaluate = do | evaluate = do | ||||
| board <- gets getBoard | board <- gets getBoard | ||||
| current <- currentPlayer | current <- currentPlayer | ||||
| @@ -162,45 +189,37 @@ updateAt :: Int -> [a] -> a -> [a] | |||||
| updateAt n xs y = map f $ zip [0..] xs | updateAt n xs y = map f $ zip [0..] xs | ||||
| where f (i, x) = if i == n then y else x | where f (i, x) = if i == n then y else x | ||||
| minmax :: (MonadIO m, Show v, Show t, Show p, Value v, Eq t, Player p, MonadGame t v p m) | |||||
| minmax :: (MonadIO m, Show v, Show t, Show p, Value v, Eq t, Player p, MonadGame t l v p m) | |||||
| => Int | => Int | ||||
| -> t | -> t | ||||
| -> v | -> v | ||||
| -> v | -> v | ||||
| -> m (t, v) | -> m (t, v) | ||||
| minmax depth turn alpha beta = (flip evalStateT) (alpha, beta) $ do | |||||
| minmax depth turn_ alpha beta = (flip evalStateT) (alpha, beta) $ do | |||||
| gameOver <- lift over | gameOver <- lift over | ||||
| -- if last step or game is over then evaluate situation | -- if last step or game is over then evaluate situation | ||||
| if depth == 0 || gameOver then do | |||||
| val <- lift evaluate | |||||
| when debug $ liftIO $ putStrLn $ "evaluation: " ++ show val | |||||
| return (turn, val) | |||||
| if depth == 0 || gameOver then (turn_,) <$> lift evaluate | |||||
| else do | else do | ||||
| when debug $ liftIO $ putStrLn $ "depth " ++ show depth | |||||
| -- generate a list of possible turns | -- generate a list of possible turns | ||||
| currentlyMaxing <- maxing <$> lift currentPlayer | currentlyMaxing <- maxing <$> lift currentPlayer | ||||
| availableTurns <- lift turns | availableTurns <- lift turns | ||||
| (alpha, beta) <- get | (alpha, beta) <- get | ||||
| -- try every turn, StateT wraps current best turn and current max value | -- try every turn, StateT wraps current best turn and current max value | ||||
| (flip execStateT) (undefined, alpha) $ forM_ availableTurns $ \turn -> do | |||||
| (flip execStateT) (turn_, alpha) $ forM_ availableTurns $ \turn -> do | |||||
| currentMax <- gets snd | currentMax <- gets snd | ||||
| when debug $ liftIO $ putStrLn $ "simulating " ++ show turn ++ " with max " ++ show currentMax | |||||
| ++ " and beta " ++ show beta | |||||
| --when (currentMax >= beta && debug) $ liftIO $ putStrLn "beta cutoff" | |||||
| -- beta cutoff | -- beta cutoff | ||||
| unless (currentMax >= beta) $ do | unless (currentMax >= beta) $ do | ||||
| --unless False $ do | |||||
| value <- lift $ lift $ simulate turn $ step currentlyMaxing beta currentMax | |||||
| when debug $ liftIO $ putStrLn $ "value " ++ show value | |||||
| value <- lift $! lift $! simulate turn $! do | |||||
| nextMaxing <- maxing <$!> currentPlayer | |||||
| if nextMaxing /= currentlyMaxing | |||||
| then (invert . snd) <$!> minmax (depth-1) turn (invert beta) (invert currentMax) | |||||
| else snd <$!> minmax (depth-1) turn currentMax beta | |||||
| when (value > currentMax) (put (turn, value)) | when (value > currentMax) (put (turn, value)) | ||||
| where step currentlyMaxing beta currentMax = do | |||||
| nextMaxing <- maxing <$> currentPlayer | |||||
| if nextMaxing /= currentlyMaxing | |||||
| then (invert . snd) <$> minmax (depth-1) turn (invert beta) (invert currentMax) | |||||
| else snd <$> minmax (depth-1) turn currentMax beta | |||||
| choose :: (MonadIO m, Show v, Show t, Show p, Value v, Eq t, Player p, MonadGame t v p m) => m t | |||||
| choose = fst <$> minmax 10 undefined loss win | |||||
| choose :: (MonadIO m, Show v, Show t, Show p, Value v, Eq t, Player p, MonadGame t l v p m) | |||||
| => Int | |||||
| -> m t | |||||
| choose depth = fst <$> minmax depth (error "choose") loss win | |||||
| emptyBoard :: [TicTacToe] | emptyBoard :: [TicTacToe] | ||||
| emptyBoard = [Toe, Toe, Toe, Toe, Toe, Toe, Toe, Toe, Toe] | emptyBoard = [Toe, Toe, Toe, Toe, Toe, Toe, Toe, Toe, Toe] | ||||
| @@ -223,7 +242,7 @@ printOptions opts = print9x9 pr | |||||
| | n `elem` opts = putStr (show n) >> putStr " " | | n `elem` opts = putStr (show n) >> putStr " " | ||||
| | otherwise = putStr " " | | otherwise = putStr " " | ||||
| instance MonadIO m => PlayableGame Int WinLossTie Bool (StateT GameState m) where | |||||
| instance MonadIO m => PlayableGame Int [] WinLossTie Bool (StateT GameState m) where | |||||
| showBoard = do | showBoard = do | ||||
| board <- gets getBoard | board <- gets getBoard | ||||
| liftIO $ printBoard board | liftIO $ printBoard board | ||||
| @@ -239,7 +258,7 @@ instance MonadIO m => PlayableGame Int WinLossTie Bool (StateT GameState m) wher | |||||
| askTurn = readMaybe <$> liftIO getLine | askTurn = readMaybe <$> liftIO getLine | ||||
| showTurn _ = return () | showTurn _ = return () | ||||
| instance PlayableGame S.Card Int S.PL S.Skat where | |||||
| instance PlayableGame (S.CardS S.Owner) [] Int S.PL S.Skat where | |||||
| showBoard = do | showBoard = do | ||||
| liftIO $ putStrLn "" | liftIO $ putStrLn "" | ||||
| table <- S.getp S.tableCards | table <- S.getp S.tableCards | ||||
| @@ -249,7 +268,7 @@ instance PlayableGame S.Card Int S.PL S.Skat where | |||||
| cards <- turns | cards <- turns | ||||
| player <- currentPlayer | player <- currentPlayer | ||||
| liftIO $ print player | liftIO $ print player | ||||
| liftIO $ S.render (S.sortRender cards) | |||||
| liftIO $ S.render cards | |||||
| winner = do | winner = do | ||||
| piles <- gets S.piles | piles <- gets S.piles | ||||
| pls <- gets S.players | pls <- gets S.players | ||||
| @@ -259,7 +278,7 @@ instance PlayableGame S.Card Int S.PL S.Skat where | |||||
| return $ Just $ head winners | return $ Just $ head winners | ||||
| askTurn = do | askTurn = do | ||||
| cards <- turns | cards <- turns | ||||
| let sorted = S.sortRender cards | |||||
| let sorted = cards | |||||
| input <- liftIO getLine | input <- liftIO getLine | ||||
| case readMaybe input of | case readMaybe input of | ||||
| Just n -> if n >= 0 && n < length sorted then return $ Just (sorted !! n) | Just n -> if n >= 0 && n < length sorted then return $ Just (sorted !! n) | ||||
| @@ -269,19 +288,20 @@ instance PlayableGame S.Card Int S.PL S.Skat where | |||||
| player <- currentPlayer | player <- currentPlayer | ||||
| liftIO $ putStrLn $ show player ++ " plays " ++ show card | liftIO $ putStrLn $ show player ++ " plays " ++ show card | ||||
| playCLI :: (MonadFail m, Read t, PlayableGame t v p m) => m () | |||||
| playCLI :: (MonadFail m, Read t, PlayableGame t l v p m) => m () | |||||
| playCLI = do | playCLI = do | ||||
| gameOver <- over | gameOver <- over | ||||
| if gameOver | if gameOver | ||||
| then announceWinner | then announceWinner | ||||
| else do | else do | ||||
| showBoard | |||||
| when debug showBoard | |||||
| current <- currentPlayer | current <- currentPlayer | ||||
| turn <- if not (maxing current) then readTurn else choose | |||||
| showTurn turn | |||||
| turn <- choose 10 | |||||
| when debug $ showTurn turn | |||||
| play turn | play turn | ||||
| playCLI | playCLI | ||||
| where | where | ||||
| readTurn :: (MonadFail m, Read t, PlayableGame t l v p m) => m t | |||||
| readTurn = do | readTurn = do | ||||
| options <- turns | options <- turns | ||||
| showTurns | showTurns | ||||
| @@ -6,6 +6,7 @@ module Skat.AI.Online where | |||||
| import Control.Monad.Reader | import Control.Monad.Reader | ||||
| import Data.Aeson | import Data.Aeson | ||||
| import Data.Maybe | |||||
| import qualified Data.ByteString.Lazy.Char8 as BS | import qualified Data.ByteString.Lazy.Char8 as BS | ||||
| import Skat.Player | import Skat.Player | ||||
| @@ -52,15 +53,16 @@ instance MonadPlayer m => MonadPlayer (Online a m) where | |||||
| turnColour = lift $ turnColour | turnColour = lift $ turnColour | ||||
| showSkat = lift . showSkat | showSkat = lift . showSkat | ||||
| choose :: (Communicator c, MonadPlayer m) => [CardS Played] -> [Card] -> Online c m Card | |||||
| choose table hand = do | |||||
| 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) | query (BS.unpack $ encode $ ChooseQuery hand table) | ||||
| r <- response | r <- response | ||||
| case decode (BS.pack r) of | case decode (BS.pack r) of | ||||
| Just (ChosenResponse card) -> do | Just (ChosenResponse card) -> do | ||||
| allowed <- P.isAllowed hand card | 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 hand' | |||||
| Nothing -> choose table hand' | |||||
| cardPlayed :: (Communicator c, MonadPlayer m) => CardS Played -> Online c m () | cardPlayed :: (Communicator c, MonadPlayer m) => CardS Played -> Online c m () | ||||
| cardPlayed card = query (BS.unpack $ encode $ CardPlayedQuery card) | cardPlayed card = query (BS.unpack $ encode $ CardPlayedQuery card) | ||||
| @@ -19,7 +19,7 @@ import qualified Data.Map.Strict as M | |||||
| import Skat.Player | import Skat.Player | ||||
| import qualified Skat.Player.Utils as P | import qualified Skat.Player.Utils as P | ||||
| import Skat.Pile | |||||
| import Skat.Pile hiding (isSkat) | |||||
| import Skat.Card | import Skat.Card | ||||
| import Skat.Utils | import Skat.Utils | ||||
| import Skat (Skat, modifyp, mkSkatEnv) | import Skat (Skat, modifyp, mkSkatEnv) | ||||
| @@ -81,7 +81,7 @@ instance Player AIEnv where | |||||
| hand = getHand | hand = getHand | ||||
| chooseCard p table fallen hand = runStateT (do | chooseCard p table fallen hand = runStateT (do | ||||
| modify $ setTable table | modify $ setTable table | ||||
| modify $ setHand hand | |||||
| modify $ setHand (map toCard hand) | |||||
| modify $ setFallen fallen | modify $ setFallen fallen | ||||
| choose) p | choose) p | ||||
| onCardPlayed p card = execStateT (do | onCardPlayed p card = execStateT (do | ||||
| @@ -142,20 +142,16 @@ analyzeTurn (c1, c2, c3) = do | |||||
| col2 = effectiveColour trCol (getCard c2) | col2 = effectiveColour trCol (getCard c2) | ||||
| col3 = effectiveColour trCol (getCard c3) | col3 = effectiveColour trCol (getCard c3) | ||||
| if col2 /= demanded | if col2 /= demanded | ||||
| then origin c2 `hasNoLonger` demanded | |||||
| then uorigin (getPile c2) `hasNoLonger` demanded | |||||
| else return () | else return () | ||||
| if col3 /= demanded | if col3 /= demanded | ||||
| then origin c3 `hasNoLonger` demanded | |||||
| then uorigin (getPile c3) `hasNoLonger` demanded | |||||
| else return () | else return () | ||||
| type Distribution = ([Card], [Card], [Card], [Card]) | type Distribution = ([Card], [Card], [Card], [Card]) | ||||
| toPiles :: [CardS Played] -> Distribution -> Piles | toPiles :: [CardS Played] -> Distribution -> Piles | ||||
| toPiles table (h1, h2, h3, skt) = Piles (cs1 ++ cs2 ++ cs3) table ss | |||||
| where cs1 = map (putAt Hand1) h1 | |||||
| cs2 = map (putAt Hand2) h2 | |||||
| cs3 = map (putAt Hand3) h3 | |||||
| ss = map (putAt SkatP) skt | |||||
| toPiles table (h1, h2, h3, skt) = makePiles h1 h2 h3 table skt | |||||
| compareGuess :: (Card, [Option]) -> (Card, [Option]) -> Ordering | compareGuess :: (Card, [Option]) -> (Card, [Option]) -> Ordering | ||||
| compareGuess (c1, ops1) (c2, ops2) | compareGuess (c1, ops1) (c2, ops2) | ||||
| @@ -227,7 +223,7 @@ onPlayed c = do | |||||
| let col = effectiveColour trCol (getCard c) | let col = effectiveColour trCol (getCard c) | ||||
| case turnCol of | case turnCol of | ||||
| Just demanded -> if col /= demanded | Just demanded -> if col /= demanded | ||||
| then origin c `hasNoLonger` demanded else return () | |||||
| then uorigin (getPile c) `hasNoLonger` demanded else return () | |||||
| Nothing -> return () | Nothing -> return () | ||||
| choose :: MonadPlayer m => AI m Card | choose :: MonadPlayer m => AI m Card | ||||
| @@ -237,6 +233,19 @@ chooseStatistic :: MonadPlayer m => AI m Card | |||||
| chooseStatistic = do | chooseStatistic = do | ||||
| h <- gets getHand | h <- gets getHand | ||||
| handCards <- gets myHand | handCards <- gets myHand | ||||
| table <- gets table | |||||
| let tableNo = length table | |||||
| left = 3 - tableNo | |||||
| depth = case length handCards of | |||||
| 10 -> 3 + tableNo | |||||
| 9 -> 3 + tableNo | |||||
| 8 -> 3 + tableNo | |||||
| 7 -> 6 + tableNo | |||||
| 6 -> 9 + tableNo | |||||
| 5 -> 12 + tableNo | |||||
| 4 -> 15 + tableNo | |||||
| _ -> 100 | |||||
| modify $ setDepth depth | |||||
| guess__ <- gets guess | guess__ <- gets guess | ||||
| self <- get | self <- get | ||||
| maySkat <- showSkat self | maySkat <- showSkat self | ||||
| @@ -244,8 +253,7 @@ chooseStatistic = do | |||||
| guess = case maySkat of | guess = case maySkat of | ||||
| Just cs -> (cs `isSkat`) guess_ | Just cs -> (cs `isSkat`) guess_ | ||||
| Nothing -> guess_ | Nothing -> guess_ | ||||
| table <- gets table | |||||
| let ns = case length table of | |||||
| let ns = case tableNo of | |||||
| 0 -> (0, 0, 0, 0) | 0 -> (0, 0, 0, 0) | ||||
| 1 -> (-1, 0, -1, 0) | 1 -> (-1, 0, -1, 0) | ||||
| 2 -> (0, 0, -1, 0) | 2 -> (0, 0, -1, 0) | ||||
| @@ -286,14 +294,13 @@ chooseOpen = do | |||||
| piles <- showPiles | piles <- showPiles | ||||
| hand <- gets getHand | hand <- gets getHand | ||||
| let myCards = handCards hand piles | let myCards = handCards hand piles | ||||
| liftIO $ putStrLn $ show hand ++ " chooses from " ++ show myCards | |||||
| possible <- filterM (P.isAllowed myCards) myCards | possible <- filterM (P.isAllowed myCards) myCards | ||||
| case length possible of | case length possible of | ||||
| 0 -> do | 0 -> do | ||||
| liftIO $ print hand | liftIO $ print hand | ||||
| liftIO $ print piles | liftIO $ print piles | ||||
| error "no cards left to choose from" | error "no cards left to choose from" | ||||
| 1 -> return $ head possible | |||||
| 1 -> return $ toCard $ head possible | |||||
| _ -> chooseSimulating | _ -> chooseSimulating | ||||
| chooseSimulating :: (MonadState AIEnv m, MonadPlayerOpen m) | chooseSimulating :: (MonadState AIEnv m, MonadPlayerOpen m) | ||||
| @@ -303,11 +310,12 @@ chooseSimulating = do | |||||
| turnCol <- turnColour | turnCol <- turnColour | ||||
| trumpCol <- trumpColour | trumpCol <- trumpColour | ||||
| myHand <- gets getHand | myHand <- gets getHand | ||||
| depth <- gets simulationDepth | |||||
| let ps = Players (PL $ Stupid.Stupid Team Hand1) | let ps = Players (PL $ Stupid.Stupid Team Hand1) | ||||
| (PL $ Stupid.Stupid Team Hand2) | (PL $ Stupid.Stupid Team Hand2) | ||||
| (PL $ Stupid.Stupid Single Hand3) | (PL $ Stupid.Stupid Single Hand3) | ||||
| env = mkSkatEnv piles turnCol trumpCol ps myHand | env = mkSkatEnv piles turnCol trumpCol ps myHand | ||||
| liftIO $ evalStateT (Minmax.choose :: Skat Card) env | |||||
| liftIO $ evalStateT (toCard <$> (Minmax.choose depth :: Skat (CardS Owner))) env | |||||
| simulate :: (MonadState AIEnv m, MonadPlayerOpen m) | simulate :: (MonadState AIEnv m, MonadPlayerOpen m) | ||||
| => Card -> m Int | => Card -> m Int | ||||
| @@ -329,7 +337,7 @@ simulate card = do | |||||
| env = mkSkatEnv piles turnCol trumpCol ps (next myHand) | env = mkSkatEnv piles turnCol trumpCol ps (next myHand) | ||||
| -- simulate the game after playing the given card | -- simulate the game after playing the given card | ||||
| (sgl, tm) <- liftIO $ evalStateT (do | (sgl, tm) <- liftIO $ evalStateT (do | ||||
| modifyp $ playCard card | |||||
| modifyp $ playCard myHand card | |||||
| turnGeneric playOpen depth) env | turnGeneric playOpen depth) env | ||||
| let v = if myTeam == Single then (sgl, tm) else (tm, sgl) | let v = if myTeam == Single then (sgl, tm) else (tm, sgl) | ||||
| -- put the value into context for when not the whole game is | -- put the value into context for when not the whole game is | ||||
| @@ -346,13 +354,13 @@ predictValue (own, others) = do | |||||
| --return $ own + pot | --return $ own + pot | ||||
| return (own-others) | return (own-others) | ||||
| potential :: (MonadState AIEnv m, MonadPlayerOpen m) | |||||
| => [Card] -> m Int | |||||
| potential :: (MonadState AIEnv m, MonadPlayerOpen m, HasCard c) | |||||
| => [c] -> m Int | |||||
| potential cs = do | potential cs = do | ||||
| tr <- trumpColour | tr <- trumpColour | ||||
| let trs = filter (isTrump tr) cs | let trs = filter (isTrump tr) cs | ||||
| value = count cs | |||||
| positions <- filter (==0) <$> mapM position cs | |||||
| value = count . map toCard $ cs | |||||
| positions <- filter (==0) <$> mapM (position . toCard) cs | |||||
| return $ length trs * 10 + value + length positions * 5 | return $ length trs * 10 + value + length positions * 5 | ||||
| position :: (MonadState AIEnv m, MonadPlayer m) | position :: (MonadState AIEnv m, MonadPlayer m) | ||||
| @@ -15,4 +15,4 @@ instance Player Stupid where | |||||
| trumpCol <- trumpColour | trumpCol <- trumpColour | ||||
| turnCol <- turnColour | turnCol <- turnColour | ||||
| let possible = filter (isAllowed trumpCol turnCol hand) hand | let possible = filter (isAllowed trumpCol turnCol hand) hand | ||||
| return (head possible, p) | |||||
| return (toCard $ head possible, p) | |||||
| @@ -1,16 +1,23 @@ | |||||
| {-# LANGUAGE MultiParamTypeClasses #-} | {-# LANGUAGE MultiParamTypeClasses #-} | ||||
| {-# LANGUAGE FlexibleInstances #-} | {-# LANGUAGE FlexibleInstances #-} | ||||
| {-# LANGUAGE FlexibleContexts #-} | |||||
| {-# LANGUAGE OverloadedStrings #-} | {-# LANGUAGE OverloadedStrings #-} | ||||
| module Skat.Card where | module Skat.Card where | ||||
| import Data.List | import Data.List | ||||
| import Data.Foldable (Foldable) | |||||
| import qualified Data.Foldable as F | |||||
| import qualified Data.Set as S | |||||
| import Data.Aeson | import Data.Aeson | ||||
| import System.Random (newStdGen, StdGen) | import System.Random (newStdGen, StdGen) | ||||
| import Control.DeepSeq | import Control.DeepSeq | ||||
| import Skat.Utils | import Skat.Utils | ||||
| class HasCard c where | |||||
| toCard :: c -> Card | |||||
| class Countable a b where | class Countable a b where | ||||
| count :: a -> b | count :: a -> b | ||||
| @@ -41,6 +48,15 @@ data Colour = Diamonds | |||||
| data Card = Card Type Colour | data Card = Card Type Colour | ||||
| deriving (Eq, Show, Ord, Read) | deriving (Eq, Show, Ord, Read) | ||||
| getType :: Card -> Type | |||||
| getType (Card t _) = t | |||||
| getColour :: Card -> Colour | |||||
| getColour (Card _ c) = c | |||||
| instance HasCard Card where | |||||
| toCard = id | |||||
| instance ToJSON Card where | instance ToJSON Card where | ||||
| toJSON (Card t c) = | toJSON (Card t c) = | ||||
| object ["type" .= show t, "colour" .= show c] | object ["type" .= show t, "colour" .= show c] | ||||
| @@ -51,11 +67,8 @@ instance FromJSON Card where | |||||
| c <- v .: "colour" | c <- v .: "colour" | ||||
| return $ Card (read t) (read c) | return $ Card (read t) (read c) | ||||
| getColour :: Card -> Colour | |||||
| getColour (Card _ c) = c | |||||
| getID :: Card -> Int | |||||
| getID (Card t _) = case t of | |||||
| getID :: HasCard c => c -> Int | |||||
| getID card = let t = getType $ toCard card in case t of | |||||
| Seven -> 0 | Seven -> 0 | ||||
| Eight -> 0 | Eight -> 0 | ||||
| Nine -> 0 | Nine -> 0 | ||||
| @@ -65,11 +78,22 @@ getID (Card t _) = case t of | |||||
| Ace -> 16 | Ace -> 16 | ||||
| Jack -> 32 | Jack -> 32 | ||||
| instance Enum Card where | |||||
| fromEnum (Card tp col) = fromEnum col * 8 + fromEnum tp | |||||
| toEnum n = Card tp col | |||||
| where col = toEnum (n `div` 8) | |||||
| tp = toEnum (n `mod` 8) | |||||
| instance Countable Card Int where | instance Countable Card Int where | ||||
| count (Card t _) = count t | count (Card t _) = count t | ||||
| instance Countable [Card] Int where | |||||
| count = sum . map count | |||||
| instance Foldable t => Countable (t Card) Int where | |||||
| count = foldl' f 0 | |||||
| where f acc c = count c + acc | |||||
| instance Countable (S.Set Card) Int where | |||||
| count = S.foldl' f 0 | |||||
| where f acc card = count card + acc | |||||
| instance NFData Card where | instance NFData Card where | ||||
| rnf (Card t c) = t `seq` c `seq` () | rnf (Card t c) = t `seq` c `seq` () | ||||
| @@ -78,22 +102,21 @@ equals :: Colour -> Maybe Colour -> Bool | |||||
| equals col (Just x) = col == x | equals col (Just x) = col == x | ||||
| equals col Nothing = True | equals col Nothing = True | ||||
| isTrump :: Colour -> Card -> Bool | |||||
| isTrump trumpCol (Card tp col) | |||||
| | tp == Jack = True | |||||
| | otherwise = col == trumpCol | |||||
| isTrump :: HasCard c => Colour -> c -> Bool | |||||
| isTrump trumpCol crd | |||||
| | getType (toCard crd) == Jack = True | |||||
| | otherwise = getColour (toCard crd) == trumpCol | |||||
| effectiveColour :: Colour -> Card -> Colour | |||||
| effectiveColour trumpCol card@(Card _ col) = | |||||
| if trump then trumpCol else col | |||||
| where trump = isTrump trumpCol card | |||||
| effectiveColour :: HasCard c => Colour -> c -> Colour | |||||
| effectiveColour trumpCol crd = if trump then trumpCol else getColour (toCard crd) | |||||
| where trump = isTrump trumpCol crd | |||||
| isAllowed :: Colour -> Maybe Colour -> [Card] -> Card -> Bool | |||||
| isAllowed trumpCol turnCol cs card = | |||||
| isAllowed :: (Foldable t, HasCard c1, HasCard c2) => Colour -> Maybe Colour -> t c1 -> c2 -> Bool | |||||
| isAllowed trumpCol turnCol cs crd = | |||||
| if col `equals` turnCol | if col `equals` turnCol | ||||
| then True | then True | ||||
| else not $ any (\ca -> effectiveColour trumpCol ca `equals` turnCol && ca /= card) cs | |||||
| where col = effectiveColour trumpCol card | |||||
| else not $ F.any (\ca -> effectiveColour trumpCol ca `equals` turnCol && toCard ca /= toCard crd) cs | |||||
| where col = effectiveColour trumpCol (toCard crd) | |||||
| compareCards :: Colour | compareCards :: Colour | ||||
| -> Maybe Colour | -> Maybe Colour | ||||
| @@ -112,11 +135,13 @@ compareCards trumpCol turnCol c1@(Card tp1 col1) c2@(Card tp2 col2) = | |||||
| where trp1 = isTrump trumpCol c1 | where trp1 = isTrump trumpCol c1 | ||||
| trp2 = isTrump trumpCol c2 | trp2 = isTrump trumpCol c2 | ||||
| sortCards :: Colour -> Maybe Colour -> [Card] -> [Card] | |||||
| sortCards trumpCol turnCol cs = sortBy (compareCards trumpCol turnCol) cs | |||||
| 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) | |||||
| highestCard :: Colour -> Maybe Colour -> [Card] -> Card | |||||
| highestCard trumpCol turnCol cs = maximumBy (compareCards trumpCol turnCol) cs | |||||
| 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) | |||||
| shuffleCards :: IO [Card] | shuffleCards :: IO [Card] | ||||
| shuffleCards = do | shuffleCards = do | ||||
| @@ -17,7 +17,7 @@ import Skat.AI.Stupid | |||||
| -- | predefined card distribution for testing purposes | -- | predefined card distribution for testing purposes | ||||
| cardDistr :: Piles | cardDistr :: Piles | ||||
| cardDistr = Piles hands [] (map (putAt SkatP) skt) | |||||
| cardDistr = emptyPiles hand1 hand2 hand3 skt | |||||
| where hand3 = [Card Ace Spades, Card Jack Diamonds, Card Jack Clubs, Card King Spades, | where hand3 = [Card Ace Spades, Card Jack Diamonds, Card Jack Clubs, Card King Spades, | ||||
| Card Nine Spades, Card Ace Diamonds, Card Queen Diamonds, Card Ten Clubs, | Card Nine Spades, Card Ace Diamonds, Card Queen Diamonds, Card Ten Clubs, | ||||
| Card Eight Clubs, Card King Clubs] | Card Eight Clubs, Card King Clubs] | ||||
| @@ -27,9 +27,6 @@ cardDistr = Piles hands [] (map (putAt SkatP) skt) | |||||
| hand2 = [Card Eight Spades, Card Queen Spades, Card Seven Spades, Card Seven Diamonds, | hand2 = [Card Eight Spades, Card Queen Spades, Card Seven Spades, Card Seven Diamonds, | ||||
| Card Seven Hearts, Card Eight Hearts, Card Queen Hearts, Card King Hearts, | Card Seven Hearts, Card Eight Hearts, Card Queen Hearts, Card King Hearts, | ||||
| Card Nine Diamonds, Card Eight Diamonds] | Card Nine Diamonds, Card Eight Diamonds] | ||||
| hands = map (putAt Hand1) hand1 | |||||
| ++ map (putAt Hand2) hand2 | |||||
| ++ map (putAt Hand3) hand3 | |||||
| skt = [Card Nine Clubs, Card Queen Clubs] | skt = [Card Nine Clubs, Card Queen Clubs] | ||||
| singleVsBots :: Communicator c => c -> IO () | singleVsBots :: Communicator c => c -> IO () | ||||
| @@ -1,12 +1,13 @@ | |||||
| module Skat.Operations ( | module Skat.Operations ( | ||||
| turn, turnGeneric, play, playOpen, publishGameResults, | turn, turnGeneric, play, playOpen, publishGameResults, | ||||
| publishGameStart, play_, sortRender | |||||
| publishGameStart, play_, sortRender, undo_ | |||||
| ) where | ) where | ||||
| import Control.Monad.State | import Control.Monad.State | ||||
| import System.Random (newStdGen, randoms) | import System.Random (newStdGen, randoms) | ||||
| import Data.List | import Data.List | ||||
| import Data.Ord | import Data.Ord | ||||
| import qualified Data.Set as S | |||||
| import Skat | import Skat | ||||
| import Skat.Card | import Skat.Card | ||||
| @@ -23,11 +24,11 @@ compareRender (Card t1 c1) (Card t2 c2) = case compare c1 c2 of | |||||
| sortRender :: [Card] -> [Card] | sortRender :: [Card] -> [Card] | ||||
| sortRender = sortBy compareRender | sortRender = sortBy compareRender | ||||
| play_ :: Card -> Skat () | |||||
| play_ :: HasCard c => c -> Skat () | |||||
| play_ card = do | play_ card = do | ||||
| hand <- gets currentHand | hand <- gets currentHand | ||||
| trCol <- gets trumpColour | trCol <- gets trumpColour | ||||
| modifyp $ playCard card | |||||
| modifyp $ playCard hand card | |||||
| table <- getp tableCards | table <- getp tableCards | ||||
| case length table of | case length table of | ||||
| 1 -> do modify (setCurrentHand $ next hand) | 1 -> do modify (setCurrentHand $ next hand) | ||||
| @@ -35,6 +36,12 @@ play_ card = do | |||||
| 3 -> evaluateTable >>= modify . setCurrentHand | 3 -> evaluateTable >>= modify . setCurrentHand | ||||
| _ -> modify (setCurrentHand $ next hand) | _ -> modify (setCurrentHand $ next hand) | ||||
| undo_ :: HasCard c => c -> Hand -> Maybe Colour -> Team -> Skat () | |||||
| undo_ card oldCurrent oldTurnCol oldWinner = do | |||||
| modify $ setCurrentHand oldCurrent | |||||
| modify $ setTurnColour oldTurnCol | |||||
| modifyp $ unplayCard oldCurrent (toCard card) oldWinner | |||||
| turnGeneric :: (PL -> Skat Card) | turnGeneric :: (PL -> Skat Card) | ||||
| -> Int | -> Int | ||||
| -> Skat (Int, Int) | -> Skat (Int, Int) | ||||
| @@ -43,7 +50,7 @@ turnGeneric playFunc depth = do | |||||
| table <- getp tableCards | table <- getp tableCards | ||||
| ps <- gets players | ps <- gets players | ||||
| let p = player ps n | let p = player ps n | ||||
| hand <- getp $ handCards n | |||||
| over <- getp $ handEmpty n | |||||
| trCol <- gets trumpColour | trCol <- gets trumpColour | ||||
| case length table of | case length table of | ||||
| 0 -> playFunc p >> modify (setCurrentHand $ next n) >> turnGeneric playFunc depth | 0 -> playFunc p >> modify (setCurrentHand $ next n) >> turnGeneric playFunc depth | ||||
| @@ -56,7 +63,7 @@ turnGeneric playFunc depth = do | |||||
| 2 -> playFunc p >> modify (setCurrentHand $ next n) >> turnGeneric playFunc depth | 2 -> playFunc p >> modify (setCurrentHand $ next n) >> turnGeneric playFunc depth | ||||
| 3 -> do | 3 -> do | ||||
| w <- evaluateTable | w <- evaluateTable | ||||
| if depth <= 1 || length hand == 0 | |||||
| if depth <= 1 || over | |||||
| then countGame | then countGame | ||||
| else modify (setCurrentHand w) >> turnGeneric playFunc (depth - 1) | else modify (setCurrentHand w) >> turnGeneric playFunc (depth - 1) | ||||
| @@ -69,9 +76,8 @@ evaluateTable = do | |||||
| turnCol <- gets turnColour | turnCol <- gets turnColour | ||||
| table <- getp tableCards | table <- getp tableCards | ||||
| ps <- gets players | ps <- gets players | ||||
| let winningCard = highestCard trumpCol turnCol table | |||||
| Just winnerHand <- getp $ originOfCard winningCard | |||||
| let winner = player ps winnerHand | |||||
| let winnerHand = uorigin $ getPile $ highestCard trumpCol turnCol table | |||||
| winner = player ps winnerHand | |||||
| modifyp $ cleanTable (team winner) | modifyp $ cleanTable (team winner) | ||||
| modify $ setTurnColour Nothing | modify $ setTurnColour Nothing | ||||
| return $ hand winner | return $ hand winner | ||||
| @@ -82,25 +88,25 @@ countGame = getp count | |||||
| play :: (Show p, Player p) => p -> Skat Card | play :: (Show p, Player p) => p -> Skat Card | ||||
| play p = do | play p = do | ||||
| liftIO $ putStrLn "playing" | liftIO $ putStrLn "playing" | ||||
| table <- getp tableCardsS | |||||
| table <- getp tableCards | |||||
| turnCol <- gets turnColour | turnCol <- gets turnColour | ||||
| trump <- gets trumpColour | trump <- gets trumpColour | ||||
| hand <- getp $ handCards (hand p) | |||||
| cards <- getp $ handCards (hand p) | |||||
| fallen <- getp played | fallen <- getp played | ||||
| (card, p') <- chooseCard p table fallen hand | |||||
| (card, p') <- chooseCard p table fallen cards | |||||
| modifyPlayers $ updatePlayer p' | modifyPlayers $ updatePlayer p' | ||||
| modifyp $ playCard card | |||||
| modifyp $ playCard (hand p) card | |||||
| ps <- fmap playersToList $ gets players | ps <- fmap playersToList $ gets players | ||||
| table' <- getp tableCardsS | |||||
| table' <- getp tableCards | |||||
| ps' <- mapM (\p -> onCardPlayed p (head table')) ps | ps' <- mapM (\p -> onCardPlayed p (head table')) ps | ||||
| mapM_ (modifyPlayers . updatePlayer) ps' | mapM_ (modifyPlayers . updatePlayer) ps' | ||||
| return card | |||||
| return (toCard card) | |||||
| playOpen :: (Show p, Player p) => p -> Skat Card | playOpen :: (Show p, Player p) => p -> Skat Card | ||||
| playOpen p = do | playOpen p = do | ||||
| --liftIO $ putStrLn $ show (hand p) ++ " playing open" | --liftIO $ putStrLn $ show (hand p) ++ " playing open" | ||||
| card <- chooseCardOpen p | card <- chooseCardOpen p | ||||
| modifyp $ playCard card | |||||
| modifyp $ playCard (hand p) card | |||||
| return card | return card | ||||
| publishGameResults :: (Int, Int) -> Skat () | publishGameResults :: (Int, Int) -> Skat () | ||||
| @@ -1,32 +1,47 @@ | |||||
| {-# LANGUAGE MultiParamTypeClasses #-} | {-# LANGUAGE MultiParamTypeClasses #-} | ||||
| {-# LANGUAGE FlexibleInstances #-} | {-# LANGUAGE FlexibleInstances #-} | ||||
| {-# LANGUAGE FlexibleContexts #-} | |||||
| {-# LANGUAGE OverloadedStrings #-} | {-# LANGUAGE OverloadedStrings #-} | ||||
| {-# LANGUAGE TupleSections #-} | |||||
| module Skat.Pile where | module Skat.Pile where | ||||
| import Data.List | |||||
| import Prelude hiding (lookup) | |||||
| import qualified Data.Map.Strict as M | |||||
| import qualified Data.Vector as V | |||||
| import Data.Vector (Vector) | |||||
| import Data.Foldable (toList, foldl', Foldable) | |||||
| import Data.Maybe | |||||
| import Data.Aeson | import Data.Aeson | ||||
| import Control.Exception | import Control.Exception | ||||
| import Data.List (delete) | |||||
| import Skat.Card | import Skat.Card | ||||
| import Skat.Utils | import Skat.Utils | ||||
| data Team = Team | Single | data Team = Team | Single | ||||
| deriving (Show, Eq, Ord, Enum) | |||||
| deriving (Show, Eq, Ord, Enum, Read) | |||||
| data CardS p = CardS { getCard :: Card | data CardS p = CardS { getCard :: Card | ||||
| , getPile :: p } | , getPile :: p } | ||||
| deriving (Show, Eq, Ord) | |||||
| deriving (Show, Eq, Ord, Read) | |||||
| instance HasCard (CardS p) where | |||||
| toCard = getCard | |||||
| instance Countable (CardS p) Int where | instance Countable (CardS p) Int where | ||||
| count = count . getCard | count = count . getCard | ||||
| instance Foldable t => Countable (t (CardS p)) Int where | |||||
| count = foldl' f 0 | |||||
| where f acc c = count c + acc | |||||
| instance ToJSON p => ToJSON (CardS p) where | instance ToJSON p => ToJSON (CardS p) where | ||||
| toJSON (CardS card pile) = | toJSON (CardS card pile) = | ||||
| object ["card" .= card, "pile" .= pile] | object ["card" .= card, "pile" .= pile] | ||||
| data Hand = Hand1 | Hand2 | Hand3 | data Hand = Hand1 | Hand2 | Hand3 | ||||
| deriving (Show, Eq, Ord) | |||||
| deriving (Show, Eq, Ord, Read) | |||||
| toInt :: Hand -> Int | toInt :: Hand -> Int | ||||
| toInt Hand1 = 1 | toInt Hand1 = 1 | ||||
| @@ -43,76 +58,112 @@ prev Hand1 = Hand3 | |||||
| prev Hand2 = Hand1 | prev Hand2 = Hand1 | ||||
| prev Hand3 = Hand2 | prev Hand3 = Hand2 | ||||
| data Played = Table Hand | |||||
| | Won Hand Team | |||||
| deriving (Show, Eq, Ord) | |||||
| data Owner = P Hand | S | |||||
| deriving (Show, Eq, Ord, Read) | |||||
| instance ToJSON Played where | |||||
| toJSON (Table hand) = | |||||
| object ["state" .= ("table" :: String), "played_by" .= show hand] | |||||
| toJSON (Won hand team) = | |||||
| object ["state" .= ("won" :: String), "played_by" .= show hand, "won_by" .= show team] | |||||
| instance ToJSON Owner where | |||||
| toJSON _ = undefined -- TODO: fix | |||||
| data SkatP = SkatP | |||||
| deriving (Show, Eq, Ord) | |||||
| type Played = Owner -- TODO: remove | |||||
| data Piles = Piles { hands :: [CardS Hand] | |||||
| , played :: [CardS Played] | |||||
| , skat :: [CardS SkatP] } | |||||
| data Piles = Piles { _hand1 :: [CardS Owner] | |||||
| , _hand2 :: [CardS Owner] | |||||
| , _hand3 :: [CardS Owner] | |||||
| , _table :: [CardS Owner] | |||||
| , _wonSingle :: [CardS Owner] | |||||
| , _wonTeam :: [CardS Owner] | |||||
| , _skat :: [CardS Owner] } | |||||
| deriving (Show, Eq, Ord) | deriving (Show, Eq, Ord) | ||||
| toTable :: Hand -> Card -> Piles -> Piles | |||||
| toTable hand card ps = ps { _table = (CardS card (P hand)) : _table ps } | |||||
| instance Countable Piles (Int, Int) where | instance Countable Piles (Int, Int) where | ||||
| count ps = (sgl, tm) | count ps = (sgl, tm) | ||||
| where sgl = count (skatCards ps) + count (wonCards Single ps) | where sgl = count (skatCards ps) + count (wonCards Single ps) | ||||
| tm = count (wonCards Team ps) | tm = count (wonCards Team ps) | ||||
| origin :: CardS Played -> Hand | |||||
| origin (CardS _ (Table hand)) = hand | |||||
| origin (CardS _ (Won hand _)) = hand | |||||
| originOfCard :: Card -> Piles -> Maybe Hand | |||||
| originOfCard card (Piles _ pld _) = origin <$> find ((==card) . getCard) pld | |||||
| playCard :: Card -> Piles -> Piles | |||||
| playCard card (Piles hs pld skt) = Piles hs' (ca : pld) skt | |||||
| where (CardS _ hand, hs') = remove ((==card) . getCard) hs | |||||
| ca = CardS card (Table hand) | |||||
| winCard :: Team -> CardS Played -> CardS Played | |||||
| winCard team (CardS card (Table hand)) = CardS card (Won hand team) | |||||
| winCard team c = c | |||||
| wonCards :: Team -> Piles -> [Card] | |||||
| wonCards team (Piles _ pld _) = filterMap (f . getPile) getCard pld | |||||
| where f (Won _ tm) = tm == team | |||||
| f _ = False | |||||
| played :: Piles -> [CardS Owner] | |||||
| played ps = _wonSingle ps ++ _wonTeam ps ++ _table ps | |||||
| origin :: Owner -> Maybe Hand | |||||
| origin (P hand) = Just hand | |||||
| origin S = Nothing | |||||
| uorigin :: Owner -> Hand | |||||
| uorigin owner = case origin owner of | |||||
| Just hand -> hand | |||||
| Nothing -> error "has no origin" | |||||
| removeFromHand :: Hand -> Card -> Piles -> Piles | |||||
| removeFromHand Hand1 card ps = ps { _hand1 = delete (CardS card (P Hand1)) (_hand1 ps) } | |||||
| removeFromHand Hand2 card ps = ps { _hand2 = delete (CardS card (P Hand2)) (_hand2 ps) } | |||||
| removeFromHand Hand3 card ps = ps { _hand3 = delete (CardS card (P Hand3)) (_hand3 ps) } | |||||
| addToHand :: Hand -> Card -> Piles -> Piles | |||||
| addToHand Hand1 card ps = ps { _hand1 = (CardS card (P Hand1)) : (_hand1 ps) } | |||||
| addToHand Hand2 card ps = ps { _hand2 = (CardS card (P Hand2)) : (_hand2 ps) } | |||||
| addToHand Hand3 card ps = ps { _hand3 = (CardS card (P Hand3)) : (_hand3 ps) } | |||||
| playCard :: HasCard c => Hand -> c -> Piles -> Piles | |||||
| playCard hand card' ps = (removeFromHand hand card ps) { _table = (CardS card (P hand)) : _table ps } | |||||
| where card = toCard card' | |||||
| unplayCard :: Hand -> Card -> Team -> Piles -> Piles | |||||
| unplayCard hand card winner ps | |||||
| | null table = case winner of | |||||
| Team -> ps' { _table = tail $ take 3 (_wonTeam ps), _wonTeam = drop 3 (_wonTeam ps) } | |||||
| Single -> ps' { _table = tail $ take 3 (_wonSingle ps), _wonSingle = drop 3 (_wonSingle ps) } | |||||
| | otherwise = ps' { _table = tail (_table ps) } | |||||
| where ps' = addToHand hand card ps | |||||
| table = tableCards ps | |||||
| wonCards :: Team -> Piles -> [CardS Owner] | |||||
| wonCards Team = _wonTeam | |||||
| wonCards Single = _wonSingle | |||||
| cleanTable :: Team -> Piles -> Piles | cleanTable :: Team -> Piles -> Piles | ||||
| cleanTable winner ps@(Piles hs pld skt) = Piles hs pld' skt | |||||
| where table = tableCards ps | |||||
| pld' = map (winCard winner) pld | |||||
| cleanTable Team ps = ps { _table = [], _wonTeam = _table ps ++ _wonTeam ps } | |||||
| cleanTable Single ps = ps { _table = [], _wonSingle = _table ps ++ _wonSingle ps } | |||||
| tableCards :: Piles -> [CardS Owner] | |||||
| tableCards = _table | |||||
| tableCards :: Piles -> [Card] | |||||
| tableCards (Piles _ pld _) = filterMap (f . getPile) getCard pld | |||||
| where f (Table _) = True | |||||
| f _ = False | |||||
| handEmpty :: Hand -> Piles -> Bool | |||||
| handEmpty Hand1 = null . _hand1 | |||||
| handEmpty Hand2 = null . _hand2 | |||||
| handEmpty Hand3 = null . _hand3 | |||||
| tableCardsS :: Piles -> [CardS Played] | |||||
| tableCardsS (Piles _ pld _) = filter (f . getPile) pld | |||||
| where f (Table _) = True | |||||
| f _ = False | |||||
| handCards :: Hand -> Piles -> [CardS Owner] | |||||
| handCards Hand1 = _hand1 | |||||
| handCards Hand2 = _hand2 | |||||
| handCards Hand3 = _hand3 | |||||
| handCards :: Hand -> Piles -> [Card] | |||||
| handCards hand (Piles hs _ _) = filterMap ((==hand) . getPile) getCard hs | |||||
| allowed :: Hand -> Colour -> Maybe Colour -> Piles -> [CardS Owner] | |||||
| allowed hand trCol turnCol ps | |||||
| | null sameColour = cards | |||||
| | otherwise = sameColour | |||||
| where cards = handCards hand ps | |||||
| sameColour = filter (\ca -> effectiveColour trCol ca `equals` turnCol) cards | |||||
| skatCards :: Piles -> [Card] | skatCards :: Piles -> [Card] | ||||
| skatCards (Piles _ _ skat) = map getCard skat | |||||
| skatCards = map getCard . _skat | |||||
| emptyPiles :: [Card] -> [Card] -> [Card] -> [Card] -> Piles | |||||
| emptyPiles h1 h2 h3 skt = makePiles h1 h2 h3 [] skt | |||||
| putAt :: p -> Card -> CardS p | putAt :: p -> Card -> CardS p | ||||
| putAt = flip CardS | putAt = flip CardS | ||||
| makePiles :: [Card] -> [Card] -> [Card] -> [CardS Owner] -> [Card] -> Piles | |||||
| makePiles h1 h2 h3 table skt = Piles h1' h2' h3' table [] [] skt' | |||||
| where h1' = map (putAt $ P Hand1) h1 | |||||
| h2' = map (putAt $ P Hand2) h2 | |||||
| h3' = map (putAt $ P Hand3) h3 | |||||
| skt' = map (putAt S) skt | |||||
| distribute :: [Card] -> Piles | distribute :: [Card] -> Piles | ||||
| distribute cards = Piles hands [] (map (putAt SkatP) skt) | |||||
| distribute cards = emptyPiles hand1 hand2 hand3 skt | |||||
| where round1 = chunksOf 3 (take 9 cards) | where round1 = chunksOf 3 (take 9 cards) | ||||
| skt = take 2 $ drop 9 cards | skt = take 2 $ drop 9 cards | ||||
| round2 = chunksOf 4 (take 12 $ drop 11 cards) | round2 = chunksOf 4 (take 12 $ drop 11 cards) | ||||
| @@ -120,6 +171,3 @@ distribute cards = Piles hands [] (map (putAt SkatP) 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] | ||||
| hands = map (putAt Hand1) hand1 | |||||
| ++ map (putAt Hand2) hand2 | |||||
| ++ map (putAt Hand3) hand3 | |||||
| @@ -18,11 +18,11 @@ class (Monad m, MonadIO m, MonadPlayer m) => MonadPlayerOpen m where | |||||
| class Player p where | class Player p where | ||||
| team :: p -> Team | team :: p -> Team | ||||
| hand :: p -> Hand | hand :: p -> Hand | ||||
| chooseCard :: MonadPlayer m | |||||
| chooseCard :: (HasCard c, MonadPlayer m) | |||||
| => p | => p | ||||
| -> [CardS Played] | -> [CardS Played] | ||||
| -> [CardS Played] | -> [CardS Played] | ||||
| -> [Card] | |||||
| -> [c] | |||||
| -> m (Card, p) | -> m (Card, p) | ||||
| onCardPlayed :: MonadPlayer m | onCardPlayed :: MonadPlayer m | ||||
| => p | => p | ||||
| @@ -34,10 +34,10 @@ class Player p where | |||||
| -> m Card | -> m Card | ||||
| chooseCardOpen p = do | chooseCardOpen p = do | ||||
| piles <- showPiles | piles <- showPiles | ||||
| let table = tableCardsS piles | |||||
| let table = tableCards piles | |||||
| fallen = played piles | fallen = played piles | ||||
| myCards = handCards (hand p) piles | myCards = handCards (hand p) piles | ||||
| fmap fst $ chooseCard p table fallen myCards | |||||
| fst <$> chooseCard p table fallen myCards | |||||
| onGameResults :: MonadIO m | onGameResults :: MonadIO m | ||||
| => p | => p | ||||
| -> (Int, Int) | -> (Int, Int) | ||||
| @@ -4,9 +4,9 @@ module Skat.Player.Utils ( | |||||
| import Skat.Player | import Skat.Player | ||||
| import qualified Skat.Card as C | import qualified Skat.Card as C | ||||
| import Skat.Card (Card) | |||||
| import Skat.Card (Card, HasCard(..)) | |||||
| isAllowed :: MonadPlayer m => [Card] -> Card -> m Bool | |||||
| isAllowed :: (HasCard c, MonadPlayer m) => [c] -> c -> m Bool | |||||
| isAllowed hand card = do | isAllowed hand card = do | ||||
| trCol <- trumpColour | trCol <- trumpColour | ||||
| turnCol <- turnColour | turnCol <- turnColour | ||||
| @@ -1,8 +1,12 @@ | |||||
| module Skat.Render where | module Skat.Render where | ||||
| import Data.List | import Data.List | ||||
| import Data.Vector (Vector, toList) | |||||
| import Skat.Card | import Skat.Card | ||||
| render :: [Card] -> IO () | |||||
| render = putStrLn . intercalate "\n" . zipWith (\n c -> show n ++ ") " ++ show c) [0..] | |||||
| render :: HasCard c => [c] -> IO () | |||||
| render = putStrLn . intercalate "\n" . zipWith (\n c -> show n ++ ") " ++ show c) [0..] . map toCard | |||||
| renderVector :: Vector Card -> IO () | |||||
| renderVector = render . toList | |||||
| @@ -4,6 +4,7 @@ import System.Random | |||||
| import Text.Read | import Text.Read | ||||
| 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') | |||||
| shuffle :: StdGen -> [a] -> [a] | shuffle :: StdGen -> [a] -> [a] | ||||
| shuffle g xs = shuffle' (randoms g) xs | shuffle g xs = shuffle' (randoms g) xs | ||||
| @@ -31,7 +32,7 @@ remove pred xs = foldr f (undefined, []) xs | |||||
| filterMap :: (a -> Bool) -> (a -> b) -> [a] -> [b] | filterMap :: (a -> Bool) -> (a -> b) -> [a] -> [b] | ||||
| filterMap pred f as = foldr g [] as | filterMap pred f as = foldr g [] as | ||||
| where g a bs = if pred a then f a : bs else bs | |||||
| where g a bs = if pred a then (f $! a) : bs else bs | |||||
| --filterM :: Monad m => (a -> m Bool) -> [a] -> m [a] | --filterM :: Monad m => (a -> m Bool) -> [a] -> m [a] | ||||
| --filterM _ [] = return [] | --filterM _ [] = return [] | ||||