| @@ -7,6 +7,8 @@ | |||||
| module Skat.AI.Base where | module Skat.AI.Base where | ||||
| import System.Random (Random) | |||||
| import qualified System.Random as Rand | |||||
| import Control.Monad.State | import Control.Monad.State | ||||
| import Control.Exception (assert) | import Control.Exception (assert) | ||||
| import Control.Monad.Fail | import Control.Monad.Fail | ||||
| @@ -46,3 +48,25 @@ class (MonadIO m, Show t, Show v, Show p, MonadGame t l v p m) => PlayableGame t | |||||
| class Choose t m | m -> t where | class Choose t m | m -> t where | ||||
| choose :: m t | choose :: m t | ||||
| class MonadRandom m where | |||||
| random :: Random a => m a | |||||
| chooser :: [a] -> m a | |||||
| instance MonadRandom IO where | |||||
| random = Rand.randomIO | |||||
| chooser [] = error "chooser: empty list" | |||||
| chooser os = (os!!) <$> Rand.randomRIO (0, length os -1) | |||||
| instance MonadRandom (State Rand.StdGen) where | |||||
| random = do | |||||
| gen <- get | |||||
| let (a, gen') = Rand.random gen | |||||
| put gen' | |||||
| return a | |||||
| chooser [] = error "chooser: empty list" | |||||
| chooser os = do | |||||
| gen <- get | |||||
| let (a, gen') = Rand.randomR (0, length os -1) gen | |||||
| put gen' | |||||
| return (os !! a) | |||||
| @@ -8,6 +8,7 @@ import GHC.Generics (Generic, Generic1) | |||||
| import Data.Ord | import Data.Ord | ||||
| import Data.Monoid ((<>)) | import Data.Monoid ((<>)) | ||||
| import Data.List | import Data.List | ||||
| import Data.Set (Set) | |||||
| import qualified Data.Set as S | import qualified Data.Set as S | ||||
| import Control.Monad.State | import Control.Monad.State | ||||
| import Control.Monad.Reader | import Control.Monad.Reader | ||||
| @@ -18,6 +19,7 @@ import Data.Bits | |||||
| import Debug.Trace | import Debug.Trace | ||||
| import Skat | import Skat | ||||
| import Skat.AI.Base | |||||
| import Skat.Utils | import Skat.Utils | ||||
| import Skat.Card | import Skat.Card | ||||
| import Skat.Pile | import Skat.Pile | ||||
| @@ -30,14 +32,14 @@ data Option = H Hand | |||||
| | Skt | | Skt | ||||
| deriving (Show, Eq, Ord, Generic, NFData) | deriving (Show, Eq, Ord, Generic, NFData) | ||||
| type Guess = Map Card [Option] | |||||
| type Guess = Map Card (Set Option) | |||||
| newGuess :: Guess | newGuess :: Guess | ||||
| newGuess = newGuessWith allCards | newGuess = newGuessWith allCards | ||||
| newGuessWith :: [Card] -> Guess | newGuessWith :: [Card] -> Guess | ||||
| newGuessWith cards = M.fromList l | newGuessWith cards = M.fromList l | ||||
| where l = map (\c -> (c, [H Hand1, H Hand2, H Hand3, Skt])) cards | |||||
| where l = map (\c -> (c, S.fromList [H Hand1, H Hand2, H Hand3, Skt])) cards | |||||
| hasBeenPlayed :: Card -> Guess -> Guess | hasBeenPlayed :: Card -> Guess -> Guess | ||||
| hasBeenPlayed card = M.delete card | hasBeenPlayed card = M.delete card | ||||
| @@ -45,33 +47,33 @@ hasBeenPlayed card = M.delete card | |||||
| has :: Hand -> [Card] -> Guess -> Guess | has :: Hand -> [Card] -> Guess -> Guess | ||||
| has hand cs = M.mapWithKey f | has hand cs = M.mapWithKey f | ||||
| where f card hands | where f card hands | ||||
| | card `elem` cs = [H hand] | |||||
| | card `elem` cs = S.singleton (H hand) | |||||
| | otherwise = hands | | otherwise = hands | ||||
| hasOnly :: Hand -> [Card] -> Guess -> Guess | hasOnly :: Hand -> [Card] -> Guess -> Guess | ||||
| hasOnly hand cs = M.mapWithKey f | hasOnly hand cs = M.mapWithKey f | ||||
| where f card hands | where f card hands | ||||
| | card `elem` cs = [H hand] | |||||
| | otherwise = delete (H hand) hands | |||||
| | card `elem` cs = S.singleton (H hand) | |||||
| | otherwise = S.delete (H hand) hands | |||||
| hasOnly_ :: Option -> [Card] -> Guess -> Guess | hasOnly_ :: Option -> [Card] -> Guess -> Guess | ||||
| hasOnly_ option cs = M.mapWithKey f | hasOnly_ option cs = M.mapWithKey f | ||||
| where f card hands | where f card hands | ||||
| | card `elem` cs = [option] | |||||
| | otherwise = h option hands | |||||
| h a b = delete a b | |||||
| | card `elem` cs = S.singleton option | |||||
| | otherwise = S.delete option hands | |||||
| hasNoLonger :: Trump -> Hand -> TurnColour -> Guess -> Guess | hasNoLonger :: Trump -> Hand -> TurnColour -> Guess -> Guess | ||||
| hasNoLonger trump hand effCol = M.mapWithKey f | hasNoLonger trump hand effCol = M.mapWithKey f | ||||
| where f card hands | where f card hands | ||||
| | effectiveColour trump card == effCol && (H hand) `elem` hands = filter (/=H hand) hands | |||||
| | effectiveColour trump card == effCol && (H hand) `S.member` hands = | |||||
| S.filter (/=H hand) hands | |||||
| | otherwise = hands | | otherwise = hands | ||||
| isSkat :: [Card] -> Guess -> Guess | isSkat :: [Card] -> Guess -> Guess | ||||
| isSkat cs = M.mapWithKey f | isSkat cs = M.mapWithKey f | ||||
| where f card hands | where f card hands | ||||
| | card `elem` cs = [Skt] | |||||
| | otherwise = if length cs == 2 then delete Skt hands else hands | |||||
| | card `elem` cs = S.singleton Skt | |||||
| | otherwise = if length cs == 2 then S.delete Skt hands else hands | |||||
| choosen1 :: Int -> [a] -> [[a]] | choosen1 :: Int -> [a] -> [[a]] | ||||
| choosen1 !n !cs = map f (filter ((==n) . popCount) [0..(m-1)]) | choosen1 !n !cs = map f (filter ((==n) . popCount) [0..(m-1)]) | ||||
| @@ -112,7 +114,7 @@ distributions2 !guess1 !(n1, n2, n3, nskt) = do | |||||
| carddist :: Option -> Int -> Guess -> [[Card]] | carddist :: Option -> Int -> Guess -> [[Card]] | ||||
| carddist option n guess = choosen n options | carddist option n guess = choosen n options | ||||
| where options = M.keys $ M.filter (option `elem`) guess | |||||
| where options = M.keys $ M.filter (option `S.member`) guess | |||||
| carddistS :: Option -> Int -> StateT Guess [] [Card] | carddistS :: Option -> Int -> StateT Guess [] [Card] | ||||
| carddistS option n = do | carddistS option n = do | ||||
| @@ -130,6 +132,53 @@ distributions3 guess (n1, n2, n3, n4) = (flip evalStateT) guess $ do | |||||
| return (hand1, hand2, hand3, skt) | return (hand1, hand2, hand3, skt) | ||||
| where cardsPerHand = (length guess-2-n1-n2-n3) `div` 3 | where cardsPerHand = (length guess-2-n1-n2-n3) `div` 3 | ||||
| randomChoice :: (MonadRandom m, Monad m) => Set Option -> StateT (Int, Int, Int, Int) m Option | |||||
| randomChoice options = do | |||||
| --when (null options) $ error "randomChoice: options are empty" | |||||
| (n1, n2, n3, n4) <- get | |||||
| let g (H Hand1) = n1 > 0 | |||||
| g (H Hand2) = n2 > 0 | |||||
| g (H Hand3) = n3 > 0 | |||||
| g Skt = n4 > 0 | |||||
| opts = S.toList $ S.filter g options | |||||
| --when (null opts) $ error "randomChoice: after filtering options are empty" | |||||
| option <- lift $ chooser opts | |||||
| let (n1', n2', n3', n4') = case option of | |||||
| H Hand1 -> (n1-1, n2, n3, n4) | |||||
| H Hand2 -> (n1, n2-1, n3, n4) | |||||
| H Hand3 -> (n1, n2, n3-1, n4) | |||||
| Skt -> (n1, n2, n3, n4-1) | |||||
| put (n1', n2', n3', n4') | |||||
| return option | |||||
| randomGuess :: (MonadRandom m, Monad m) => Guess -> (Int, Int, Int, Int) -> m Guess | |||||
| randomGuess guess (n1, n2, n3, n4) = (flip evalStateT) ( cardsPerHand + n1 | |||||
| , cardsPerHand + n2 | |||||
| , cardsPerHand + n3 | |||||
| , 2 + n4 | |||||
| ) $ do | |||||
| foldM helper guess (M.keys guess) | |||||
| where cardsPerHand = (length guess-2-n1-n2-n3) `div` 3 | |||||
| helper g card = do | |||||
| let opts = M.findWithDefault (error "findWithDefault") card g | |||||
| o <- randomChoice opts | |||||
| pure $ M.insert card (S.singleton o) g | |||||
| randomDistr :: (MonadRandom m, Monad m) => Guess -> (Int, Int, Int, Int) -> m Distribution | |||||
| randomDistr guess (n1, n2, n3, n4) = (flip evalStateT) ( cardsPerHand + n1 | |||||
| , cardsPerHand + n2 | |||||
| , cardsPerHand + n3 | |||||
| , 2 + n4 | |||||
| ) $ do | |||||
| randomGuess <- foldM helper guess (M.keys guess) | |||||
| let [d] = distributions randomGuess (n1, n2, n3, n4) | |||||
| pure d | |||||
| where cardsPerHand = (length guess-2-n1-n2-n3) `div` 3 | |||||
| helper g card = do | |||||
| let opts = M.findWithDefault (error "findWithDefault") card g | |||||
| o <- randomChoice opts | |||||
| pure $ M.insert card (S.singleton o) g | |||||
| {- | |||||
| distributions1 :: Guess -> (Int, Int, Int, Int) -> [Distribution] | distributions1 :: Guess -> (Int, Int, Int, Int) -> [Distribution] | ||||
| distributions1 guess nos = | distributions1 guess nos = | ||||
| helper (sortBy compareGuess $ M.toList guess) nos | helper (sortBy compareGuess $ M.toList guess) nos | ||||
| @@ -155,6 +204,7 @@ distributions1 guess nos = | |||||
| isOk Skt = n4 < 2 | isOk Skt = n4 < 2 | ||||
| in filterMap isOk (f card) hands | in filterMap isOk (f card) hands | ||||
| cardsPerHand = (length guess - 2) `div` 3 | cardsPerHand = (length guess - 2) `div` 3 | ||||
| -} | |||||
| distributions = distributions3 | distributions = distributions3 | ||||
| @@ -32,7 +32,7 @@ import qualified System.Random as Rand | |||||
| import Text.Printf | import Text.Printf | ||||
| import Data.List.Split | import Data.List.Split | ||||
| import Skat.AI.Base | |||||
| import Skat.AI.Base hiding (simulate) | |||||
| 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 | ||||
| @@ -98,26 +98,6 @@ valuation Pending{} = (0,0) | |||||
| deriving instance (Show s, Show t) => Show (Tree t s) | deriving instance (Show s, Show t) => Show (Tree t s) | ||||
| class MonadRandom m where | |||||
| random :: Random a => m a | |||||
| chooser :: [a] -> m a | |||||
| instance MonadRandom IO where | |||||
| random = Rand.randomIO | |||||
| chooser os = (os!!) <$> Rand.randomRIO (0, length os -1) | |||||
| instance MonadRandom (State Rand.StdGen) where | |||||
| random = do | |||||
| gen <- get | |||||
| let (a, gen') = Rand.random gen | |||||
| put gen' | |||||
| return a | |||||
| chooser os = do | |||||
| gen <- get | |||||
| let (a, gen') = Rand.randomR (0, length os -1) gen | |||||
| put gen' | |||||
| return (os !! a) | |||||
| {- | {- | ||||
| valuetonum :: (Fractional a, Value v) => v -> a | valuetonum :: (Fractional a, Value v) => v -> a | ||||
| valuetonum v | valuetonum v | ||||
| @@ -143,6 +123,8 @@ class (Player p, Value d) => HasGameState t p d s | s -> d, s -> p, s -> t where | |||||
| execute :: t -> s -> s | execute :: t -> s -> s | ||||
| monteevaluate :: s -> d | monteevaluate :: s -> d | ||||
| current :: s -> p | current :: s -> p | ||||
| simulate :: (Monad m, MonadRandom m) => s -> m d | |||||
| simulate = montesimulate | |||||
| montecarlo :: (Show s, Show t, Eq p, Show d, Monad m, HasGameState t p d s, MonadRandom m) | montecarlo :: (Show s, Show t, Eq p, Show d, Monad m, HasGameState t p d s, MonadRandom m) | ||||
| => Tree t s | => Tree t s | ||||
| @@ -151,12 +133,12 @@ montecarlo (Pending state turn) = do | |||||
| let currentTeam = current state | let currentTeam = current state | ||||
| state' = execute turn state | state' = execute turn state | ||||
| -- objectively get a final score of random playout (independent of perspective) | -- objectively get a final score of random playout (independent of perspective) | ||||
| values <- replicateM 1 (montesimulate state') | |||||
| let tr = if maxing (current state') then id else invert | |||||
| values <- replicateM 100 (simulate state') | |||||
| let tr = if maxing (current state) then id else invert | |||||
| vs = fmap (tonum . tr) values | vs = fmap (tonum . tr) values | ||||
| n = sum vs / 1 | |||||
| n = sum vs | |||||
| --let v = if maxing (current state') then value else invert value | --let v = if maxing (current state') then value else invert value | ||||
| let val = (n, 1) | |||||
| let val = (n, 100) | |||||
| pure $ Leaf state' False val | pure $ Leaf state' False val | ||||
| montecarlo (Leaf state terminal d) | montecarlo (Leaf state terminal d) | ||||
| | terminal || length ms == 0 = pure $ Leaf state True d | | terminal || length ms == 0 = pure $ Leaf state True d | ||||
| @@ -182,12 +164,12 @@ montecarlo n@(Node state _ d children) | |||||
| else | else | ||||
| if current state == current (treestate updated) | if current state == current (treestate updated) | ||||
| then diff | then diff | ||||
| else 1 - diff | |||||
| else fromIntegral (simruns updated) - diff | |||||
| newWins = diff2 + fst d | newWins = diff2 + fst d | ||||
| --return $ trace ("updating node " ++ show diff2 ++ "\n" ++ show updated ++ "\n" ++ show bestChild) (Node state False (newWins, newSimRuns) cs) | --return $ trace ("updating node " ++ show diff2 ++ "\n" ++ show updated ++ "\n" ++ show bestChild) (Node state False (newWins, newSimRuns) cs) | ||||
| return $ Node state False (newWins, newSimRuns) cs | return $ Node state False (newWins, newSimRuns) cs | ||||
| montesimulate :: (Monad m, MonadRandom m, HasGameState t p d s, Show d) | |||||
| montesimulate :: (Monad m, MonadRandom m, HasGameState t p d s) | |||||
| => s | => s | ||||
| -> m d | -> m d | ||||
| montesimulate state = case moves state of | montesimulate state = case moves state of | ||||
| @@ -165,6 +165,22 @@ instance HasGameState Turn Bool Float SkatState where | |||||
| card <- ev S.allowedCards newEnv | card <- ev S.allowedCards newEnv | ||||
| pure $ Turn newEnv (S.toCard card) | pure $ Turn newEnv (S.toCard card) | ||||
| where env = skatEnv s | where env = skatEnv s | ||||
| simulate s | |||||
| | Map.size (guess s) <= 2 = pure $ monteevaluate s | |||||
| | otherwise = do | |||||
| let currentPiles = ev (gets S.piles) env | |||||
| table = S.tableCards currentPiles | |||||
| n1 = length $ filter ((S.P S.Hand1==) . S.getPile) table | |||||
| n2 = length $ filter ((S.P S.Hand2==) . S.getPile) table | |||||
| n3 = length $ filter ((S.P S.Hand3==) . S.getPile) table | |||||
| ns = (-n1, -n2, -n3, 0) | |||||
| d <- randomDistr (guess s) ns | |||||
| let newEnv = env { S.piles = updatePiles d (S.piles env) } | |||||
| cards = ev S.allowedCards newEnv | |||||
| card <- chooser cards | |||||
| let newState = execute (Turn newEnv (S.toCard card)) s | |||||
| Skat.AI.MonteCarlo.simulate newState | |||||
| where env = skatEnv s | |||||
| ev :: StateT S.SkatEnv (Writer [S.Trick]) a -> S.SkatEnv -> a | ev :: StateT S.SkatEnv (Writer [S.Trick]) a -> S.SkatEnv -> a | ||||
| ev action = fst . runWriter . evalStateT action | ev action = fst . runWriter . evalStateT action | ||||
| @@ -186,9 +202,10 @@ playCLI n = do | |||||
| liftIO $ putStrLn "iterating" | liftIO $ putStrLn "iterating" | ||||
| s <- get | s <- get | ||||
| let tree = Leaf s False (0, 0) | let tree = Leaf s False (0, 0) | ||||
| t = runmonte n (foldM (\tree _ -> montecarlo tree) tree [1..20]) | |||||
| t = runmonte n (foldM (\tree _ -> montecarlo tree) tree [1..100]) | |||||
| newstate = bestmove t | newstate = bestmove t | ||||
| --liftIO $ putStrLn $ visualise t | --liftIO $ putStrLn $ visualise t | ||||
| liftIO $ print newstate | |||||
| put newstate | put newstate | ||||
| lift (put $ skatEnv newstate) | lift (put $ skatEnv newstate) | ||||
| else do | else do | ||||
| @@ -204,7 +221,7 @@ playCLI n = do | |||||
| showBoard | showBoard | ||||
| liftIO $ getLine | liftIO $ getLine | ||||
| -} | -} | ||||
| playCLI n | |||||
| --playCLI n | |||||
| where | where | ||||
| --readTurn :: (MonadFail m, Read t, PlayableGame t l v p m) => m t | --readTurn :: (MonadFail m, Read t, PlayableGame t l v p m) => m t | ||||
| readTurn :: S.Skat (S.CardS S.Owner) | readTurn :: S.Skat (S.CardS S.Owner) | ||||
| @@ -228,7 +245,7 @@ initSkatEnv n = | |||||
| let gen = Rand.mkStdGen n | let gen = Rand.mkStdGen n | ||||
| --cards = S.shuffle gen S.allCards | --cards = S.shuffle gen S.allCards | ||||
| --piles = S.distribute cards | --piles = S.distribute cards | ||||
| piles = S.cardDistr6 | |||||
| piles = S.cardDistr3 | |||||
| players = P.Players | players = P.Players | ||||
| (P.PL $ S.Stupid S.Single S.Hand1) | (P.PL $ S.Stupid S.Single S.Hand1) | ||||
| (P.PL $ S.Stupid S.Team S.Hand2) | (P.PL $ S.Stupid S.Team S.Hand2) | ||||
| @@ -257,3 +274,6 @@ initSkatState = | |||||
| playSkat :: Int -> IO () | playSkat :: Int -> IO () | ||||
| playSkat n = let env = skatEnv initSkatState | playSkat n = let env = skatEnv initSkatState | ||||
| in void $ S.evalSkat ( (flip runStateT) initSkatState (playCLI n) ) env | in void $ S.evalSkat ( (flip runStateT) initSkatState (playCLI n) ) env | ||||
| skattree :: Tree Turn SkatState | |||||
| skattree = Leaf initSkatState False (0,0) | |||||
| @@ -210,12 +210,12 @@ playCLI n = do | |||||
| if gameOver | if gameOver | ||||
| then announceWinner | then announceWinner | ||||
| else do | else do | ||||
| current <- currentPlayer | |||||
| --let current = False | |||||
| --current <- currentPlayer | |||||
| let current = False | |||||
| if not current then do | if not current then do | ||||
| s <- get | s <- get | ||||
| let tree = Leaf s False (0, 0) | let tree = Leaf s False (0, 0) | ||||
| t = bestmove $ runmonte n (foldM (\tree _ -> montecarlo tree) tree [1..5000]) | |||||
| t = bestmove $ runmonte n (foldM (\tree _ -> montecarlo tree) tree [1..1000]) | |||||
| put t | put t | ||||
| else do | else do | ||||
| showBoard | showBoard | ||||
| @@ -4,6 +4,7 @@ | |||||
| {-# LANGUAGE OverloadedStrings #-} | {-# LANGUAGE OverloadedStrings #-} | ||||
| {-# LANGUAGE DeriveGeneric #-} | {-# LANGUAGE DeriveGeneric #-} | ||||
| {-# LANGUAGE DeriveAnyClass #-} | {-# LANGUAGE DeriveAnyClass #-} | ||||
| {-# LANGUAGE BangPatterns #-} | |||||
| module Skat.Card where | module Skat.Card where | ||||
| @@ -67,7 +68,7 @@ data TurnColour = TurnColour Colour | |||||
| | Trump | | Trump | ||||
| deriving (Show, Eq) | deriving (Show, Eq) | ||||
| data Card = Card Type Colour | |||||
| data Card = Card !Type !Colour | |||||
| deriving (Eq, Show, Ord, Read, Bounded, Generic) | deriving (Eq, Show, Ord, Read, Bounded, Generic) | ||||
| getType :: Card -> Type | getType :: Card -> Type | ||||
| @@ -292,14 +292,13 @@ cardDistr5 = makePiles hand1 hand2 hand3 tbl skt | |||||
| cardDistr6 :: Piles | cardDistr6 :: Piles | ||||
| cardDistr6 = emptyPiles hand1 hand2 hand3 skt | cardDistr6 = emptyPiles hand1 hand2 hand3 skt | ||||
| where hand3 = [Card Ace Spades, Card Jack Diamonds, Card Jack Clubs, Card King Spades, | |||||
| where hand1 = [Card Jack Diamonds, Card Jack Clubs, Card King Spades, | |||||
| Card Nine Spades, Card Ace Diamonds, Card Queen Diamonds | Card Nine Spades, Card Ace Diamonds, Card Queen Diamonds | ||||
| ] | ] | ||||
| hand1 = [Card Jack Spades, Card Jack Hearts, Card Ten Spades, Card Ace Hearts, | |||||
| hand3 = [Card Jack Spades, Card Ten Spades, Card Ace Hearts, | |||||
| Card Ten Hearts, Card Nine Hearts, Card Seven Clubs | Card Ten Hearts, Card Nine Hearts, Card Seven Clubs | ||||
| ] | ] | ||||
| hand2 = [Card Eight Spades, Card Queen Spades, Card Seven Spades, Card Seven Diamonds, | |||||
| hand2 = [Card Queen Spades, Card Seven Spades, Card Seven Diamonds, | |||||
| Card Seven Hearts, Card Eight Hearts, Card Queen Hearts | Card Seven Hearts, Card Eight Hearts, Card Queen Hearts | ||||
| ] | ] | ||||
| skt = [Card Nine Clubs, Card Queen Clubs] | skt = [Card Nine Clubs, Card Queen Clubs] | ||||