| @@ -18,14 +18,18 @@ import Skat.AI.Stupid | |||||
| import Skat.AI.Online | import Skat.AI.Online | ||||
| import Skat.AI.Rulebased | import Skat.AI.Rulebased | ||||
| import Skat.AI.Minmax (playCLI) | import Skat.AI.Minmax (playCLI) | ||||
| import Skat.AI.Games.Skat.Guess | |||||
| import Skat.AI.Skat (playSkat) | |||||
| main :: IO () | main :: IO () | ||||
| main = testMinmax 10 | |||||
| main = playSkat 42 | |||||
| {- | |||||
| testMinmax :: Int -> IO () | testMinmax :: Int -> IO () | ||||
| testMinmax n = do | testMinmax n = do | ||||
| let acs = repeat playSkat | let acs = repeat playSkat | ||||
| sequence_ (take n acs) | sequence_ (take n acs) | ||||
| -} | |||||
| testAI :: Int -> IO () | testAI :: Int -> IO () | ||||
| testAI n = do | testAI n = do | ||||
| @@ -108,5 +112,7 @@ application pending = do | |||||
| msg <- WS.receiveData conn | msg <- WS.receiveData conn | ||||
| putStrLn $ BS.unpack msg | putStrLn $ BS.unpack msg | ||||
| {- | |||||
| playSkat :: IO () | playSkat :: IO () | ||||
| playSkat = void $ (flip runSkat) env3 playCLI | playSkat = void $ (flip runSkat) env3 playCLI | ||||
| -} | |||||
| @@ -4,7 +4,7 @@ cabal-version: 1.12 | |||||
| -- | -- | ||||
| -- see: https://github.com/sol/hpack | -- see: https://github.com/sol/hpack | ||||
| -- | -- | ||||
| -- hash: 8a975ca39edf7adfa4bbf95bd068d1b2f4f3fa9e954eb61fa3cf553f03b7dd56 | |||||
| name: skat | name: skat | ||||
| version: 0.1.0.8 | version: 0.1.0.8 | ||||
| @@ -28,13 +28,18 @@ source-repository head | |||||
| library | library | ||||
| exposed-modules: | exposed-modules: | ||||
| Skat | Skat | ||||
| Skat.AI.Base | |||||
| Skat.AI.Games.Skat.Guess | |||||
| Skat.AI.Human | Skat.AI.Human | ||||
| Skat.AI.Markov | Skat.AI.Markov | ||||
| Skat.AI.Minmax | Skat.AI.Minmax | ||||
| Skat.AI.MonteCarlo | |||||
| Skat.AI.Online | Skat.AI.Online | ||||
| Skat.AI.Rulebased | Skat.AI.Rulebased | ||||
| Skat.AI.Server | Skat.AI.Server | ||||
| Skat.AI.Skat | |||||
| Skat.AI.Stupid | Skat.AI.Stupid | ||||
| Skat.AI.TicTacToe | |||||
| Skat.Bidding | Skat.Bidding | ||||
| Skat.Card | Skat.Card | ||||
| Skat.Matches | Skat.Matches | ||||
| @@ -1,6 +1,7 @@ | |||||
| {-# LANGUAGE NamedFieldPuns #-} | {-# LANGUAGE NamedFieldPuns #-} | ||||
| {-# LANGUAGE TypeSynonymInstances #-} | {-# LANGUAGE TypeSynonymInstances #-} | ||||
| {-# LANGUAGE FlexibleInstances #-} | {-# LANGUAGE FlexibleInstances #-} | ||||
| {-# LANGUAGE FlexibleContexts #-} | |||||
| module Skat where | module Skat where | ||||
| @@ -49,14 +50,14 @@ instance P.MonadPlayer Skat where | |||||
| instance P.MonadPlayerOpen Skat where | instance P.MonadPlayerOpen Skat where | ||||
| showPiles = gets piles | showPiles = gets piles | ||||
| modifyp :: (Piles -> Piles) -> Skat () | |||||
| modifyp :: MonadState SkatEnv m => (Piles -> Piles) -> m () | |||||
| modifyp f = modify g | modifyp f = modify g | ||||
| where g env@(SkatEnv {piles}) = env { piles = f piles} | where g env@(SkatEnv {piles}) = env { piles = f piles} | ||||
| getp :: (Piles -> a) -> Skat a | |||||
| getp :: MonadState SkatEnv m => (Piles -> a) -> m a | |||||
| getp f = gets piles >>= return . f | getp f = gets piles >>= return . f | ||||
| modifyPlayers :: (Players -> Players) -> Skat () | |||||
| modifyPlayers :: MonadState SkatEnv m => (Players -> Players) -> m () | |||||
| modifyPlayers f = modify g | modifyPlayers f = modify g | ||||
| where g env@(SkatEnv {players}) = env { players = f players } | where g env@(SkatEnv {players}) = env { players = f players } | ||||
| @@ -69,7 +70,7 @@ setCurrentHand hand sk = sk { currentHand = hand } | |||||
| mkSkatEnv :: Piles -> Maybe TurnColour -> Game -> Players -> Hand -> Hand -> SkatEnv | mkSkatEnv :: Piles -> Maybe TurnColour -> Game -> Players -> Hand -> Hand -> SkatEnv | ||||
| mkSkatEnv = SkatEnv | mkSkatEnv = SkatEnv | ||||
| allowedCards :: Skat [CardS Owner] | |||||
| allowedCards :: (P.MonadPlayer m, MonadState SkatEnv m) => m [CardS Owner] | |||||
| allowedCards = do | allowedCards = do | ||||
| curHand <- gets currentHand | curHand <- gets currentHand | ||||
| pls <- gets players | pls <- gets players | ||||
| @@ -0,0 +1,48 @@ | |||||
| {-# LANGUAGE MultiParamTypeClasses #-} | |||||
| {-# LANGUAGE TypeSynonymInstances #-} | |||||
| {-# LANGUAGE FlexibleInstances #-} | |||||
| {-# LANGUAGE FlexibleContexts #-} | |||||
| {-# LANGUAGE FunctionalDependencies #-} | |||||
| {-# LANGUAGE TupleSections #-} | |||||
| module Skat.AI.Base where | |||||
| import Control.Monad.State | |||||
| import Control.Exception (assert) | |||||
| import Control.Monad.Fail | |||||
| import Data.Ord | |||||
| import Text.Read (readMaybe) | |||||
| import Data.List (maximumBy, sortBy) | |||||
| import Debug.Trace | |||||
| class (Ord v, Eq v) => Value v where | |||||
| invert :: v -> v | |||||
| win :: v | |||||
| loss :: v | |||||
| tie :: v | |||||
| tonum :: v -> Float | |||||
| tonum v | |||||
| | v == win = 1.0 | |||||
| | v == loss = 0.0 | |||||
| | v == tie = 0.5 | |||||
| class Player p where | |||||
| maxing :: p -> Bool | |||||
| 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 | |||||
| turns :: m (l t) | |||||
| play :: t -> m () | |||||
| simulate :: t -> m a -> m a | |||||
| evaluate :: m v | |||||
| over :: m Bool | |||||
| 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 () | |||||
| showBoard :: m () | |||||
| askTurn :: m (Maybe t) | |||||
| showTurn :: t -> m () | |||||
| winner :: m (Maybe p) | |||||
| class Choose t m | m -> t where | |||||
| choose :: m t | |||||
| @@ -0,0 +1,177 @@ | |||||
| {-# LANGUAGE DeriveGeneric #-} | |||||
| {-# LANGUAGE DeriveAnyClass #-} | |||||
| {-# LANGUAGE BangPatterns #-} | |||||
| module Skat.AI.Games.Skat.Guess where | |||||
| import GHC.Generics (Generic, Generic1) | |||||
| import Data.Ord | |||||
| import Data.Monoid ((<>)) | |||||
| import Data.List | |||||
| import qualified Data.Set as S | |||||
| import Control.Monad.State | |||||
| import Control.Monad.Reader | |||||
| import Data.Map.Strict (Map) | |||||
| import qualified Data.Map.Strict as M | |||||
| import Data.List (delete) | |||||
| import Data.Bits | |||||
| import Debug.Trace | |||||
| import Skat | |||||
| import Skat.Utils | |||||
| import Skat.Card | |||||
| import Skat.Pile | |||||
| import Skat.Player | |||||
| import Skat.Player | |||||
| import Control.Parallel.Strategies | |||||
| import Control.DeepSeq | |||||
| data Option = H Hand | |||||
| | Skt | |||||
| deriving (Show, Eq, Ord, Generic, NFData) | |||||
| type Guess = Map Card [Option] | |||||
| newGuess :: Guess | |||||
| newGuess = newGuessWith allCards | |||||
| newGuessWith :: [Card] -> Guess | |||||
| newGuessWith cards = M.fromList l | |||||
| where l = map (\c -> (c, [H Hand1, H Hand2, H Hand3, Skt])) cards | |||||
| hasBeenPlayed :: Card -> Guess -> Guess | |||||
| hasBeenPlayed card = M.delete card | |||||
| has :: Hand -> [Card] -> Guess -> Guess | |||||
| has hand cs = M.mapWithKey f | |||||
| where f card hands | |||||
| | card `elem` cs = [H hand] | |||||
| | otherwise = hands | |||||
| hasOnly :: Hand -> [Card] -> Guess -> Guess | |||||
| hasOnly hand cs = M.mapWithKey f | |||||
| where f card hands | |||||
| | card `elem` cs = [H hand] | |||||
| | otherwise = delete (H hand) hands | |||||
| hasOnly_ :: Option -> [Card] -> Guess -> Guess | |||||
| hasOnly_ option cs = M.mapWithKey f | |||||
| where f card hands | |||||
| | card `elem` cs = [option] | |||||
| | otherwise = h option hands | |||||
| h a b = delete a b | |||||
| hasNoLonger :: Trump -> Hand -> TurnColour -> Guess -> Guess | |||||
| hasNoLonger trump hand effCol = M.mapWithKey f | |||||
| where f card hands | |||||
| | effectiveColour trump card == effCol && (H hand) `elem` hands = filter (/=H hand) hands | |||||
| | otherwise = hands | |||||
| isSkat :: [Card] -> Guess -> Guess | |||||
| isSkat cs = M.mapWithKey f | |||||
| where f card hands | |||||
| | card `elem` cs = [Skt] | |||||
| | otherwise = if length cs == 2 then delete Skt hands else hands | |||||
| choosen1 :: Int -> [a] -> [[a]] | |||||
| choosen1 !n !cs = map f (filter ((==n) . popCount) [0..(m-1)]) | |||||
| where m = 2^(length cs) :: Int | |||||
| f !i = collect $! filter (< length cs) $! getSetBits i | |||||
| collect !idx = map (cs!!) $! idx | |||||
| getSetBits :: Int -> [Int] | |||||
| getSetBits !a = filter (\i -> 2^i .&. a /= 0) [0..a] | |||||
| {-# INLINE getSetBits #-} | |||||
| choosen2 :: Int -> [a] -> [[a]] | |||||
| choosen2 !n !cs = map f (filter ((==n) . popCount) [0..(m-1)]) | |||||
| where m = 2^(length cs) :: Int | |||||
| f !i = filterMap (g i) fst $! zip cs [0..] | |||||
| g !i (c, k) = 2^k .&. i /= 0 | |||||
| choosen = choosen2 | |||||
| smplguess :: Guess | |||||
| smplguess = Hand1 `hasOnly` [(Card Seven Diamonds)..(Card Eight Hearts)] $! newGuess | |||||
| distributions2 :: Guess -> (Int, Int, Int, Int) -> [Distribution] | |||||
| distributions2 !guess1 !(n1, n2, n3, nskt) = do | |||||
| let h1cards = M.keys $!! M.filter (H Hand1 `elem`) guess1 | |||||
| hand1 <- choosen 10 h1cards | |||||
| let guess2 = Hand1 `hasOnly` hand1 $! guess1 | |||||
| h2cards = M.keys $!! M.filter (H Hand2 `elem`) guess2 | |||||
| hand2 <- choosen 10 h2cards | |||||
| let guess3 = Hand2 `hasOnly` hand2 $! guess2 | |||||
| h3cards = M.keys $!! M.filter (H Hand3 `elem`) guess3 | |||||
| x = choosen 10 $!! h3cards | |||||
| hand3 <- x | |||||
| --let guess4 = Hand3 `hasOnly` hand3 $! guess3 | |||||
| -- sktcards = M.keys $!! M.filter (Skt `elem`) guess4 | |||||
| --skt <- choosen (2 + nskt) sktcards | |||||
| return (hand1, hand2, hand3, [])--, skt) | |||||
| carddist :: Option -> Int -> Guess -> [[Card]] | |||||
| carddist option n guess = choosen n options | |||||
| where options = M.keys $ M.filter (option `elem`) guess | |||||
| carddistS :: Option -> Int -> StateT Guess [] [Card] | |||||
| carddistS option n = do | |||||
| guess <- get | |||||
| sels <- lift $ carddist option n guess | |||||
| put $ option `hasOnly_` sels $ guess | |||||
| return sels | |||||
| distributions3 :: Guess -> (Int, Int, Int, Int) -> [Distribution] | |||||
| distributions3 guess (n1, n2, n3, n4) = (flip evalStateT) guess $ do | |||||
| hand1 <- carddistS (H Hand1) (cardsPerHand + n1) | |||||
| hand2 <- carddistS (H Hand2) (cardsPerHand + n2) | |||||
| hand3 <- carddistS (H Hand3) (cardsPerHand + n3) | |||||
| skt <- carddistS Skt (2 + n4) | |||||
| return (hand1, hand2, hand3, skt) | |||||
| where cardsPerHand = (length guess-2-n1-n2-n3) `div` 3 | |||||
| distributions1 :: Guess -> (Int, Int, Int, Int) -> [Distribution] | |||||
| distributions1 guess nos = | |||||
| helper (sortBy compareGuess $ M.toList guess) nos | |||||
| `using` parList rdeepseq | |||||
| where helper [] _ = [] | |||||
| helper ((c, hs):[]) ns = map fst (distr c hs ns) | |||||
| helper ((c, hs):gs) ns = | |||||
| let dsWithNs = distr c hs ns | |||||
| go (d, ns') = map (d <>) (helper gs ns') | |||||
| in concatMap go dsWithNs | |||||
| distr card hands (n1, n2, n3, n4) = | |||||
| let f card (H Hand1) = | |||||
| (([card], [], [], []), (n1+1, n2, n3, n4)) | |||||
| f card (H Hand2) = | |||||
| (([], [card], [], []), (n1, n2+1, n3, n4)) | |||||
| f card (H Hand3) = | |||||
| (([], [], [card], []), (n1, n2, n3+1, n4)) | |||||
| f card Skt = | |||||
| (([], [], [], [card]), (n1, n2, n3, n4+1)) | |||||
| isOk (H Hand1) = n1 < cardsPerHand | |||||
| isOk (H Hand2) = n2 < cardsPerHand | |||||
| isOk (H Hand3) = n3 < cardsPerHand | |||||
| isOk Skt = n4 < 2 | |||||
| in filterMap isOk (f card) hands | |||||
| cardsPerHand = (length guess - 2) `div` 3 | |||||
| distributions = distributions3 | |||||
| type Distribution = ([Card], [Card], [Card], [Card]) | |||||
| compareGuess :: (Card, [Option]) -> (Card, [Option]) -> Ordering | |||||
| compareGuess (c1, ops1) (c2, ops2) | |||||
| | length ops1 == 1 = LT | |||||
| | length ops2 == 1 = GT | |||||
| | c1 > c2 = LT | |||||
| | c1 < c2 = GT | |||||
| toPiles :: [CardS Played] -> Distribution -> Piles | |||||
| toPiles table (h1, h2, h3, skt) = makePiles h1 h2 h3 table skt | |||||
| updatePiles :: Distribution -> Piles -> Piles | |||||
| updatePiles (h1, h2, h3, skt) piles = piles { _hand1 = fmap (putAt $ P Hand1) h1 | |||||
| , _hand2 = fmap (putAt $ P Hand2) h2 | |||||
| , _hand3 = fmap (putAt $ P Hand3) h3 | |||||
| , _skat = fmap (putAt S) skt } | |||||
| @@ -17,13 +17,16 @@ 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 (maximumBy, sortBy) | |||||
| import Data.List (maximumBy, sortBy, delete) | |||||
| import Debug.Trace | import Debug.Trace | ||||
| import Data.Ratio | import Data.Ratio | ||||
| import Data.Set (Set) | import Data.Set (Set) | ||||
| import qualified Data.Set as Set | import qualified Data.Set as Set | ||||
| import Data.Map (Map) | import Data.Map (Map) | ||||
| import qualified Data.Map as Map | import qualified Data.Map as Map | ||||
| import Data.Bits | |||||
| import Data.Vector (Vector) | |||||
| import qualified Data.Vector as Vector | |||||
| import qualified Skat as S | import qualified Skat as S | ||||
| import qualified Skat.Card as S | import qualified Skat.Card as S | ||||
| @@ -33,22 +36,21 @@ 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) | --import TestEnvs (env3, shuffledEnv2) | ||||
| data Probability d a = Or (Set a) d | |||||
| | Probability { value :: a | |||||
| data Possibility d a = Possibility { value :: a | |||||
| , probability :: d | , probability :: d | ||||
| } | } | ||||
| newtype Distribution d a = Distribution { runDistribution :: [Probability d a] } | |||||
| newtype Distribution d a = Distribution { runDistribution :: [Possibility d a] } | |||||
| instance Num d => Monad (Distribution d) where | instance Num d => Monad (Distribution d) where | ||||
| return :: a -> Distribution d a | return :: a -> Distribution d a | ||||
| return x = Distribution [Probability x 1] | |||||
| return x = Distribution [Possibility x 1] | |||||
| (>>=) :: Distribution d a -> (a -> Distribution d b) -> Distribution d b | (>>=) :: Distribution d a -> (a -> Distribution d b) -> Distribution d b | ||||
| (Distribution ps) >>= f = Distribution $ do | (Distribution ps) >>= f = Distribution $ do | ||||
| (Probability x1 p1) <- ps | |||||
| (Possibility x1 p1) <- ps | |||||
| let (Distribution ds) = f x1 | let (Distribution ds) = f x1 | ||||
| (Probability x2 p2) <- ds | |||||
| return $ Probability x2 (p1*p2) | |||||
| (Possibility x2 p2) <- ds | |||||
| return $ Possibility x2 (p1*p2) | |||||
| instance Num d => Applicative (Distribution d) where | instance Num d => Applicative (Distribution d) where | ||||
| pure = return | pure = return | ||||
| @@ -60,23 +62,118 @@ instance Num d => Functor (Distribution d) where | |||||
| sumDist :: (Num d, Ord a) => Distribution d a -> Distribution d a | sumDist :: (Num d, Ord a) => Distribution d a -> Distribution d a | ||||
| sumDist = distFromMap . distToMap | sumDist = distFromMap . distToMap | ||||
| where distToMap (Distribution ps) = Map.fromListWith (+) $ do | where distToMap (Distribution ps) = Map.fromListWith (+) $ do | ||||
| (Probability x p) <- ps | |||||
| (Possibility x p) <- ps | |||||
| return (x, p) | return (x, p) | ||||
| distFromMap m = Distribution $ do | distFromMap m = Distribution $ do | ||||
| (x, p) <- Map.toList m | (x, p) <- Map.toList m | ||||
| return $ Probability x p | |||||
| return $ Possibility x p | |||||
| deriving instance (Show d, Show a) => Show (Probability d a) | |||||
| deriving instance (Show d, Show a) => Show (Possibility d a) | |||||
| deriving instance (Show d, Show a) => Show (Distribution d a) | deriving instance (Show d, Show a) => Show (Distribution d a) | ||||
| deriving instance (Eq d, Eq a) => Eq (Probability d a) | |||||
| deriving instance (Eq d, Eq a) => Eq (Possibility d a) | |||||
| deriving instance (Eq d, Eq a) => Eq (Distribution d a) | deriving instance (Eq d, Eq a) => Eq (Distribution d a) | ||||
| deriving instance (Ord d, Ord a) => Ord (Probability d a) | |||||
| deriving instance (Ord d, Ord a) => Ord (Possibility d a) | |||||
| deriving instance (Ord d, Ord a) => Ord (Distribution d a) | deriving instance (Ord d, Ord a) => Ord (Distribution d a) | ||||
| drawSome :: Int -> StateT (Set S.Card) (Distribution Rational) (Set S.Card) | |||||
| drawSome n = do | |||||
| s <- get | |||||
| let ds = sumDist $ runStateT (Set.fromList <$> replicateM n draw) s | |||||
| (d, s') <- lift ds | |||||
| put s' | |||||
| return d | |||||
| --put s' | |||||
| draw2 = sumDist $ (flip evalStateT) (Set.fromList $ take 22 S.allCards) do | |||||
| ss <- replicateM 5 (drawSome 2) | |||||
| return $ Set.unions ss | |||||
| basen = 22 | |||||
| taken = 10 | |||||
| allCards = Vector.fromList $ take basen S.allCards | |||||
| example = stupid taken (Set.fromList $ take basen S.allCards) | |||||
| example2 = stupid2 taken (take basen S.allCards) | |||||
| example3 = stupid3 taken (take basen S.allCards) | |||||
| example4 = smart taken (take basen S.allCards) | |||||
| example5 = stupid4 taken allCards | |||||
| example6 = stupid5 taken (take basen S.allCards) | |||||
| stupid :: Int -> Set S.Card -> Set (Set S.Card) | |||||
| stupid 0 _ = Set.singleton Set.empty | |||||
| stupid n cs | |||||
| | length cs == 0 = Set.empty | |||||
| | otherwise = xs | |||||
| where f :: S.Card -> Set (Set S.Card) | |||||
| f c = let cs' = Set.delete c cs | |||||
| distrs = stupid (n-1) cs' | |||||
| distrs' = Set.map (Set.insert c) distrs | |||||
| in distrs' | |||||
| --xs :: Set (Set (Set S.Card)) | |||||
| xs = Set.foldr (\c s -> Set.union s $ f c) Set.empty cs | |||||
| --stupid2 :: (Monoid f, Foldable f, Functor f) => Int -> f S.Card -> f (f S.Card) | |||||
| stupid2 0 _ = [mempty] | |||||
| stupid2 n cs | |||||
| | length cs == 0 = mempty | |||||
| | otherwise = xs | |||||
| where f c = let cs' = delete c cs | |||||
| distrs = stupid2 (n-1) cs' | |||||
| distrs' = fmap (c:) distrs | |||||
| in distrs' | |||||
| --xs :: Set (Set (Set S.Card)) | |||||
| xs = foldr (\c s -> s <> f c) mempty cs | |||||
| stupid3 :: Int -> [S.Card] -> [[S.Card]] | |||||
| stupid3 n cs = map (f cs) (filter ((==n) . popCount) [1..m]) | |||||
| where m = 2^(length cs) :: Int | |||||
| f cs i = collect cs $ filter (< length cs) $ getSetBits i | |||||
| collect l idx = map (l!!) idx | |||||
| getSetBits :: Int -> [Int] | |||||
| getSetBits a = filter (\i -> 2^i .&. a /= 0) [0..a] | |||||
| -- very bad suddenly | |||||
| stupid5 :: Int -> [S.Card] -> Set (Set S.Card) | |||||
| stupid5 n cs = Set.map (Set.fromList . f cs) (Set.filter ((==n) . popCount) $ Set.fromList [1..m]) | |||||
| where m = 2^(length cs) :: Int | |||||
| f cs i = collect cs $ filter (< length cs) $ getSetBits i | |||||
| collect l idx = map (\i -> l!!i) idx | |||||
| stupid4 :: Int -> Vector S.Card -> Vector [S.Card] | |||||
| stupid4 n cs = fmap f (Vector.filter ((==n) . popCount) bs) | |||||
| where bs = Vector.fromList [1..m] | |||||
| m = 2^(length cs) :: Int | |||||
| f i = collect $ filter (< length cs) $ getSetBits i | |||||
| collect idx = map (\i -> cs Vector.! (i-1)) idx | |||||
| getSetBits a = filter ((/=0) . (.&.a)) [1..a] | |||||
| {- | |||||
| getSetBits a | |||||
| | popCount a == n = filter (\k -> (k.&.a) /= 0) [1..a] | |||||
| | otherwise = [] | |||||
| -} | |||||
| smart n = map Set.fromList . stupid3 n | |||||
| carddist :: Int -> Set S.Card -> Distribution Rational (Set S.Card) | |||||
| carddist n cs = Distribution $ fmap (\x -> Possibility x (1%l)) raw | |||||
| where raw = smart n cards | |||||
| l = fromIntegral $ length raw | |||||
| cards = Set.toList cs | |||||
| carddistS :: Int -> StateT (Set S.Card) (Distribution Rational) (Set S.Card) | |||||
| carddistS n = do | |||||
| cards <- get | |||||
| sels <- lift $ carddist n cards | |||||
| put $ cards `Set.difference` sels | |||||
| return sels | |||||
| draw :: StateT (Set S.Card) (Distribution Rational) S.Card | draw :: StateT (Set S.Card) (Distribution Rational) S.Card | ||||
| draw = do | draw = do | ||||
| cards <- get | cards <- get | ||||
| card <- lift $ Distribution $ Set.toList $ Set.map (flip Probability $ (1 % (fromIntegral $ length cards))) cards | |||||
| card <- lift $ Distribution $ Set.toList $ Set.map (flip Possibility $ (1 % (fromIntegral $ length cards))) cards | |||||
| let cards' = Set.delete (card) cards | let cards' = Set.delete (card) cards | ||||
| put cards' | put cards' | ||||
| return card | return card | ||||
| @@ -86,29 +183,29 @@ draw2 :: StateT (Set S.Card) Identity (Distribution Rational S.Card) | |||||
| draw2 = do | draw2 = do | ||||
| cards <- get | cards <- get | ||||
| cards <- get | cards <- get | ||||
| card <- lift $ Distribution $ Set.toList $ Set.map (flip Probability $ (1 % (fromIntegral $ length cards))) cards | |||||
| card <- lift $ Distribution $ Set.toList $ Set.map (flip Possibility $ (1 % (fromIntegral $ length cards))) cards | |||||
| let cards' = Set.delete (card) cards | let cards' = Set.delete (card) cards | ||||
| put cards' | put cards' | ||||
| return card | return card | ||||
| -} | -} | ||||
| coprod :: (Ord a, Num d) => Probability d a -> Probability d a -> Probability d a | |||||
| coprod (Probability x p) (Probability y q) = Or (Set.fromList [x, y]) $ p + q | |||||
| skat :: Distribution Rational (Set S.Card, Set S.Card, Set S.Card, Set S.Card) | |||||
| skat = (flip evalStateT) (Set.fromList $ take 8 S.allCards) $ do | |||||
| fstHand <- replicateM 2 draw | |||||
| sndHand <- replicateM 2 draw | |||||
| trdHand <- replicateM 2 draw | |||||
| skt <- replicateM 2 draw | |||||
| return ( Set.fromList fstHand | |||||
| , Set.fromList sndHand | |||||
| , Set.fromList trdHand | |||||
| , Set.fromList skt | |||||
| {- | |||||
| coprod :: (Ord a, Num d) => Possibility d a -> Possibility d a -> Possibility d a | |||||
| coprod (Possibility x p) (Possibility y q) = Or (Set.fromList [x, y]) $ p + q | |||||
| -} | |||||
| skat :: Distribution Rational (Set S.Card, Set S.Card, Set S.Card) | |||||
| skat = (flip evalStateT) (Set.fromList $ take 22 S.allCards) $ do | |||||
| sndHand <- carddistS 10 | |||||
| trdHand <- carddistS 10 | |||||
| skt <- carddistS 2 | |||||
| return ( sndHand | |||||
| , trdHand | |||||
| , skt | |||||
| ) | ) | ||||
| coin :: Distribution Rational Bool | coin :: Distribution Rational Bool | ||||
| coin = Distribution [ Probability True (1%2), Probability False (1%2)] | |||||
| coin = Distribution [ Possibility True (1%2), Possibility False (1%2)] | |||||
| tosstwice :: Distribution Rational (Bool, Bool) | tosstwice :: Distribution Rational (Bool, Bool) | ||||
| tosstwice = do | tosstwice = do | ||||
| @@ -281,7 +378,7 @@ 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 | ||||
| toss :: Distribution Rational Coin | toss :: Distribution Rational Coin | ||||
| toss = Distribution [Probability Head (1%2), Probability Tail (1%2)] | |||||
| toss = Distribution [Possibility Head (1%2), Possibility Tail (1%2)] | |||||
| data Coin = Head | data Coin = Head | ||||
| | Tail | | Tail | ||||
| @@ -23,73 +23,15 @@ import qualified Skat.Operations as S | |||||
| import qualified Skat.Pile as S | import qualified Skat.Pile as S | ||||
| import qualified Skat.Player as S hiding (trumpColour, turnColour) | import qualified Skat.Player as S hiding (trumpColour, turnColour) | ||||
| import qualified Skat.Render as S | import qualified Skat.Render as S | ||||
| import Skat.AI.Base hiding (playCLI, Choose(..)) | |||||
| import Skat.AI.TicTacToe hiding (playCLI) | |||||
| import Skat.AI.Skat hiding (playCLI) | |||||
| --import TestEnvs (env3, shuffledEnv2) | --import TestEnvs (env3, shuffledEnv2) | ||||
| debug :: Bool | debug :: Bool | ||||
| debug = False | debug = False | ||||
| class (Ord v, Eq v) => Value v where | |||||
| invert :: v -> v | |||||
| win :: v | |||||
| loss :: v | |||||
| class Player p where | |||||
| maxing :: p -> Bool | |||||
| 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 | |||||
| turns :: m (l t) | |||||
| play :: t -> m () | |||||
| simulate :: t -> m a -> m a | |||||
| evaluate :: m v | |||||
| over :: m Bool | |||||
| 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 () | |||||
| showBoard :: m () | |||||
| askTurn :: m (Maybe t) | |||||
| showTurn :: t -> m () | |||||
| winner :: m (Maybe p) | |||||
| -- Skat implementation | -- Skat implementation | ||||
| instance Player S.PL where | |||||
| maxing p = S.team p == S.Team | |||||
| instance Value Int where | |||||
| invert = negate | |||||
| win = 120 | |||||
| loss = -120 | |||||
| instance MonadGame (S.CardS S.Owner) [] Int S.PL S.Skat where | |||||
| currentPlayer = do | |||||
| hand <- gets S.currentHand | |||||
| pls <- gets S.players | |||||
| return $! S.player pls hand | |||||
| 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_ | |||||
| simulate card action = do | |||||
| --oldCurrent <- gets S.currentHand | |||||
| --oldTurnCol <- gets S.turnColour | |||||
| backup <- get | |||||
| play card | |||||
| --oldWinner <- currentPlayer | |||||
| res <- action | |||||
| --S.undo_ card oldCurrent oldTurnCol (S.team oldWinner) | |||||
| put backup | |||||
| return $! res | |||||
| over = ((==0) . length) <$!> S.allowedCards | |||||
| evaluate = do | |||||
| player <- currentPlayer | |||||
| piles <- gets S.piles | |||||
| let (sgl, tm) = S.count piles | |||||
| return $! (if maxing player then tm - sgl else sgl - tm) | |||||
| potentialByType :: S.Type -> Int | potentialByType :: S.Type -> Int | ||||
| potentialByType S.Ace = 11 | potentialByType S.Ace = 11 | ||||
| potentialByType S.Jack = 10 | potentialByType S.Jack = 10 | ||||
| @@ -106,89 +48,6 @@ optimalSingle trCol (S.Card t1 _) (S.Card t2 _) = (comparing potentialByType) t2 | |||||
| optimalTeam :: S.Colour -> S.Card -> S.Card -> Ordering | optimalTeam :: S.Colour -> S.Card -> S.Card -> Ordering | ||||
| optimalTeam trCol (S.Card t1 _) (S.Card t2 _) = (comparing potentialByType) t2 t1 | optimalTeam trCol (S.Card t1 _) (S.Card t2 _) = (comparing potentialByType) t2 t1 | ||||
| data TicTacToe = Tic | Tac | Toe | |||||
| deriving (Eq, Ord) | |||||
| instance Show TicTacToe where | |||||
| show Tic = "O" | |||||
| show Tac = "X" | |||||
| show Toe = "_" | |||||
| data WinLossTie = Loss | Tie | Win | |||||
| deriving (Eq, Show, Ord) | |||||
| instance Value WinLossTie where | |||||
| invert Win = Loss | |||||
| invert Loss = Win | |||||
| invert Tie = Tie | |||||
| win = Win | |||||
| loss = Loss | |||||
| data GameState = GameState { getBoard :: [TicTacToe] | |||||
| , getCurrent :: Bool } | |||||
| deriving Show | |||||
| instance Player Bool where | |||||
| maxing = id | |||||
| instance Monad m => MonadGame Int [] WinLossTie Bool (StateT GameState m) where | |||||
| currentPlayer = gets getCurrent | |||||
| turns = do | |||||
| board <- gets getBoard | |||||
| let fields = zip [0..] board | |||||
| return $ map fst $ filter ((==Toe) . snd) fields | |||||
| play turn = do | |||||
| env <- get | |||||
| let value = if getCurrent env then Tic else Tac | |||||
| board' = updateAt turn (getBoard env) value | |||||
| current' = not $ getCurrent env | |||||
| put $ GameState board' current' | |||||
| simulate turn action = do | |||||
| backup <- get | |||||
| play turn | |||||
| res <- action | |||||
| put backup | |||||
| return $! res | |||||
| evaluate = do | |||||
| board <- gets getBoard | |||||
| current <- currentPlayer | |||||
| let mayWinner = ticWinner board | |||||
| case mayWinner of | |||||
| Just Tic -> return $ if current then Win else Loss | |||||
| Just Tac -> return $ if current then Loss else Win | |||||
| Just Toe -> return Tie | |||||
| Nothing -> return Tie | |||||
| over = do | |||||
| board <- gets getBoard | |||||
| case ticWinner board of | |||||
| Just _ -> return True | |||||
| _ -> return False | |||||
| ticWinner :: [TicTacToe] -> Maybe TicTacToe | |||||
| ticWinner board | |||||
| | ticWon = Just Tic | |||||
| | tacWon = Just Tac | |||||
| | over = Just Toe | |||||
| | otherwise = Nothing | |||||
| where ticWon = hasWon $ map (==Tic) board | |||||
| tacWon = hasWon $ map (==Tac) board | |||||
| hasWon (True:_:_:True:_:_:True:_:_:[]) = True | |||||
| hasWon (True:_:_:_:True:_:_:_:True:[]) = True | |||||
| hasWon (_:True:_:_:True:_:_:True:_:[]) = True | |||||
| hasWon (_:_:True:_:_:True:_:_:True:[]) = True | |||||
| hasWon (_:_:True:_:True:_:True:_:_:[]) = True | |||||
| hasWon (True:True:True:_:_:_:_:_:_:[]) = True | |||||
| hasWon (_:_:_:True:True:True:_:_:_:[]) = True | |||||
| hasWon (_:_:_:_:_:_:True:True:True:[]) = True | |||||
| hasWon _ = False | |||||
| over = (length $ filter (==Toe) board) == 0 | |||||
| updateAt :: Int -> [a] -> a -> [a] | |||||
| updateAt n xs y = map f $ zip [0..] xs | |||||
| 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 l 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 | ||||
| @@ -221,73 +80,6 @@ choose :: (MonadIO m, Show v, Show t, Show p, Value v, Eq t, Player p, MonadGame | |||||
| -> m t | -> m t | ||||
| choose depth = fst <$> minmax depth (error "choose") loss win | choose depth = fst <$> minmax depth (error "choose") loss win | ||||
| emptyBoard :: [TicTacToe] | |||||
| emptyBoard = [Toe, Toe, Toe, Toe, Toe, Toe, Toe, Toe, Toe] | |||||
| otherBoard :: [TicTacToe] | |||||
| otherBoard = [Tic, Tac, Tac, Tic, Tac, Tic, Toe, Tic, Toe] | |||||
| print9x9 :: (Int -> IO ()) -> IO () | |||||
| print9x9 pr = pr 0 >> pr 1 >> pr 2 >> putStrLn "" | |||||
| >> pr 3 >> pr 4 >> pr 5 >> putStrLn "" | |||||
| >> pr 6 >> pr 7 >> pr 8 >> putStrLn "" | |||||
| printBoard :: [TicTacToe] -> IO () | |||||
| printBoard board = print9x9 pr >> putStrLn "" | |||||
| where pr n = putStr (show $ board !! n) >> putStr " " | |||||
| printOptions :: [Int] -> IO () | |||||
| printOptions opts = print9x9 pr | |||||
| where pr n | |||||
| | n `elem` opts = putStr (show n) >> putStr " " | |||||
| | otherwise = putStr " " | |||||
| instance MonadIO m => PlayableGame Int [] WinLossTie Bool (StateT GameState m) where | |||||
| showBoard = do | |||||
| board <- gets getBoard | |||||
| liftIO $ printBoard board | |||||
| showTurns = turns >>= liftIO . printOptions | |||||
| winner = do | |||||
| board <- gets getBoard | |||||
| let win = ticWinner board | |||||
| case win of | |||||
| Just Toe -> return Nothing | |||||
| Just Tic -> return $ Just True | |||||
| Just Tac -> return $ Just False | |||||
| Nothing -> return Nothing | |||||
| askTurn = readMaybe <$> liftIO getLine | |||||
| showTurn _ = return () | |||||
| instance PlayableGame (S.CardS S.Owner) [] Int S.PL S.Skat where | |||||
| showBoard = do | |||||
| liftIO $ putStrLn "" | |||||
| table <- S.getp S.tableCards | |||||
| liftIO $ putStr "Table: " | |||||
| liftIO $ print table | |||||
| showTurns = do | |||||
| cards <- turns | |||||
| player <- currentPlayer | |||||
| liftIO $ print player | |||||
| liftIO $ S.render cards | |||||
| winner = do | |||||
| piles <- gets S.piles | |||||
| pls <- gets S.players | |||||
| let res = S.count piles :: (Int, Int) | |||||
| winnerTeam = trace (show res) $ if fst res > snd res then S.Single else S.Team | |||||
| winners = filter ((==winnerTeam) . S.team) (S.playersToList pls) | |||||
| return $ Just $ head winners | |||||
| askTurn = do | |||||
| cards <- turns | |||||
| let sorted = cards | |||||
| input <- liftIO getLine | |||||
| case readMaybe input of | |||||
| Just n -> if n >= 0 && n < length sorted then return $ Just (sorted !! n) | |||||
| else return Nothing | |||||
| Nothing -> return Nothing | |||||
| showTurn card = do | |||||
| player <- currentPlayer | |||||
| liftIO $ putStrLn $ show player ++ " plays " ++ show card | |||||
| playCLI :: (MonadFail m, Read t, PlayableGame t l v p m) => m () | playCLI :: (MonadFail m, Read t, PlayableGame t l v p m) => m () | ||||
| playCLI = do | playCLI = do | ||||
| gameOver <- over | gameOver <- over | ||||
| @@ -0,0 +1,244 @@ | |||||
| {-# LANGUAGE MultiParamTypeClasses #-} | |||||
| {-# LANGUAGE BlockArguments #-} | |||||
| {-# LANGUAGE TypeSynonymInstances #-} | |||||
| {-# LANGUAGE FlexibleInstances #-} | |||||
| {-# LANGUAGE FlexibleContexts #-} | |||||
| {-# LANGUAGE FunctionalDependencies #-} | |||||
| {-# LANGUAGE TupleSections #-} | |||||
| {-# LANGUAGE InstanceSigs #-} | |||||
| {-# LANGUAGE StandaloneDeriving #-} | |||||
| {-# LANGUAGE ImportQualifiedPost #-} | |||||
| {-# LANGUAGE UndecidableInstances #-} | |||||
| module Skat.AI.MonteCarlo where | |||||
| import Control.Monad.State | |||||
| import Control.Exception (assert) | |||||
| import Control.Monad.Fail | |||||
| import Data.Ord | |||||
| import Text.Read (readMaybe) | |||||
| import Data.List (maximumBy, minimumBy, sortBy, delete, intercalate) | |||||
| import Debug.Trace | |||||
| import Data.Ratio | |||||
| import Data.Set (Set) | |||||
| import qualified Data.Set as Set | |||||
| import Data.Map (Map) | |||||
| import qualified Data.Map as Map | |||||
| import Data.Bits | |||||
| import Data.Vector (Vector) | |||||
| import qualified Data.Vector as Vector | |||||
| import System.Random (Random) | |||||
| import qualified System.Random as Rand | |||||
| import Text.Printf | |||||
| import Data.List.Split | |||||
| import Skat.AI.Base | |||||
| import qualified Skat as S | |||||
| import qualified Skat.Card as S | |||||
| import qualified Skat.Operations as S | |||||
| import qualified Skat.Pile as S | |||||
| import qualified Skat.Player as S hiding (trumpColour, turnColour) | |||||
| import qualified Skat.Render as S | |||||
| import Skat.Utils | |||||
| --import TestEnvs (env3, shuffledEnv2) | |||||
| type WinCount = Float | |||||
| type SimCount = Int | |||||
| data Tree t s = Leaf s Bool (WinCount, SimCount) | |||||
| | Node s Bool (WinCount, SimCount) [Tree t s] | |||||
| | Pending s t | |||||
| simruns :: Tree t s -> SimCount | |||||
| simruns (Leaf _ _ d) = snd d | |||||
| simruns (Node _ _ d _) = snd d | |||||
| simruns Pending{} = 0 | |||||
| wins :: Tree t s -> WinCount | |||||
| wins (Leaf _ _ d) = fst d | |||||
| wins (Node _ _ d _) = fst d | |||||
| wins Pending{} = 0 | |||||
| childrenwins :: Tree t s -> WinCount | |||||
| childrenwins (Node _ _ _ cs) = sum $ fmap wins cs | |||||
| childrenwins _ = 0 | |||||
| treestate :: Tree t s -> s | |||||
| treestate (Leaf s _ _) = s | |||||
| treestate (Node s _ _ _) = s | |||||
| treestate (Pending s _) = s | |||||
| isterminal :: Tree t s -> Bool | |||||
| isterminal (Leaf _ b _) = b | |||||
| isterminal (Node _ b _ _) = b | |||||
| isterminal Pending{} = False | |||||
| class Draw s where | |||||
| draw :: s -> String | |||||
| instance Draw Int where | |||||
| draw = show | |||||
| indent :: Int -> String -> String | |||||
| indent n s = intercalate ("\n" ++ replicate n ' ') $ splitOn "\n" s | |||||
| visualise :: (HasGameState t p d s, Draw s, Draw t) => Tree t s -> String | |||||
| visualise (Node s _ d children) = printf "[%f/%d]: %s %s:\n%s" (fst d) (snd d) (show . maxing . current $ s) (indent 14 $ draw s) (intercalate "\n" $ fmap f children) | |||||
| where f c = printf "---%s" (indent 3 $ visualise c) | |||||
| visualise (Leaf s _ d) = printf "[%f/%d]: %s" (fst d) (snd d) (indent 9 $ draw s) | |||||
| visualise (Pending s t) = printf "[pend]: %s %s" (indent 9 $ draw s) (indent 9 $ draw t) | |||||
| emptytree :: s -> Tree t s | |||||
| emptytree s = Leaf s False (0, 0) | |||||
| valuation :: Tree t s -> (WinCount, SimCount) | |||||
| valuation (Leaf _ _ d) = d | |||||
| valuation (Node _ _ d _) = d | |||||
| valuation Pending{} = (0,0) | |||||
| 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 v | |||||
| | v == win = 1 | |||||
| | v == loss = 0 | |||||
| | v == tie = 0.5 | |||||
| -} | |||||
| restoint :: (Player p, Value v) => p -> v -> Float | |||||
| restoint p v = tonum $ if maxing p then v else invert v | |||||
| {- | |||||
| updateval :: (Player p, Value d) => p -> [d] -> (WinCount, SimCount) -> (WinCount, SimCount) | |||||
| updateval team xs d = | |||||
| let newSimCount = snd d + fromIntegral (length xs) | |||||
| newWinCount = fst d + sum (fmap (tonum . cvt) xs) | |||||
| cvt = if maxing team then id else invert | |||||
| in (newWinCount, newSimCount) | |||||
| -} | |||||
| class (Player p, Value d) => HasGameState t p d s | s -> d, s -> p, s -> t where | |||||
| moves :: s -> [t] | |||||
| execute :: t -> s -> s | |||||
| monteevaluate :: s -> d | |||||
| current :: s -> p | |||||
| montecarlo :: (Show s, Show t, Eq p, Show d, Monad m, HasGameState t p d s, MonadRandom m) | |||||
| => Tree t s | |||||
| -> m (Tree t s) | |||||
| montecarlo (Pending state turn) = do | |||||
| let currentTeam = current state | |||||
| state' = execute turn state | |||||
| -- 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 | |||||
| vs = fmap (tonum . tr) values | |||||
| n = sum vs / 1 | |||||
| --let v = if maxing (current state') then value else invert value | |||||
| let val = (n, 1) | |||||
| pure $ Leaf state' False val | |||||
| montecarlo (Leaf state terminal d) | |||||
| | terminal || length ms == 0 = pure $ Leaf state True d | |||||
| | otherwise = let children = map (Pending state) ms in pure $ Node state False d children | |||||
| where ms = moves state | |||||
| montecarlo (Node state _ d []) = pure $ Leaf state True d | |||||
| montecarlo n@(Node state True d children) = pure n | |||||
| montecarlo n@(Node state _ d children) | |||||
| | all isterminal children = | |||||
| let d' = reevaluateminmax n | |||||
| in pure $ Node state True d' children | |||||
| | otherwise = do | |||||
| let myruns = snd d | |||||
| cmp c = if isterminal c then -1 else selectcoeff myruns $ valuation c | |||||
| (idx, bestChild) = | |||||
| maximumBy (comparing $ cmp . snd) $ zipWith (,) [0..] children | |||||
| updated <- montecarlo bestChild | |||||
| let cs = updateAt idx children updated | |||||
| newSimRuns = simruns updated - simruns bestChild + snd d | |||||
| diff = wins updated - wins bestChild | |||||
| diff2 = | |||||
| if newSimRuns == snd d then 0 | |||||
| else | |||||
| if current state == current (treestate updated) | |||||
| then diff | |||||
| else 1 - diff | |||||
| newWins = diff2 + fst d | |||||
| --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 | |||||
| montesimulate :: (Monad m, MonadRandom m, HasGameState t p d s, Show d) | |||||
| => s | |||||
| -> m d | |||||
| montesimulate state = case moves state of | |||||
| [] -> pure $ monteevaluate state | |||||
| allowed -> do | |||||
| turn <- chooser allowed | |||||
| montesimulate $ execute turn state | |||||
| runmonte :: Int -> State Rand.StdGen (Tree t s) -> Tree t s | |||||
| runmonte n action = evalState action (Rand.mkStdGen n) | |||||
| {- | |||||
| bestmove :: Tree s -> s | |||||
| bestmove (Leaf s _ _) = s | |||||
| bestmove (Node s _ _ cs) = treestate $ selection (comparing $ rate . valuation) cs | |||||
| where rate (w, s) = w / fromIntegral s | |||||
| mxing = maxing . current $ s | |||||
| selection = if mxing then maximumBy else minimumBy | |||||
| -} | |||||
| bestmove :: Tree t s -> s | |||||
| bestmove (Leaf s _ _) = s | |||||
| bestmove (Node s _ _ cs) = treestate $ maximumBy (comparing $ rate . valuation) cs | |||||
| where rate (w, s) = w / fromIntegral s | |||||
| selectcoeff :: SimCount -> (WinCount, SimCount) -> Float | |||||
| selectcoeff _ (_, 0) = 10000000 | |||||
| selectcoeff t (w, s) = w / fromIntegral s + explorationParam * sqrt (log (fromIntegral t) / fromIntegral s) | |||||
| where explorationParam = sqrt 2 | |||||
| reevaluate :: Tree t s -> (WinCount, SimCount) | |||||
| reevaluate tree | |||||
| | isterminal tree = valuation tree | |||||
| | otherwise = case tree of | |||||
| (Pending{}) -> valuation tree | |||||
| (Leaf{}) -> valuation tree | |||||
| (Node _ _ _ children) -> let total = sum $ fmap simruns children | |||||
| wns = fromIntegral total - sum (fmap wins children) | |||||
| in (wns, total) | |||||
| reevaluateminmax :: HasGameState t p d s => Tree t s -> (WinCount, SimCount) | |||||
| reevaluateminmax tree | |||||
| | isterminal tree = valuation tree | |||||
| | otherwise = case tree of | |||||
| (Pending{}) -> valuation tree | |||||
| (Leaf{}) -> valuation tree | |||||
| (Node state _ _ children) -> | |||||
| let vals = fmap ((\(w, s) -> w / fromIntegral s) . valuation) children | |||||
| -- m = maxing . current $ state | |||||
| childrenMaxing = all (maxing . current . treestate) children | |||||
| selfMaxing = maxing . current $ state | |||||
| newval = if childrenMaxing /= selfMaxing then 1 - maximum vals else maximum vals | |||||
| in (newval, 1) | |||||
| --playCLI :: (MonadFail m, Read t, Choose t m, PlayableGame t l v p m) => m () | |||||
| @@ -115,7 +115,7 @@ instance MonadPlayer m => MonadPlayer (Online a m) where | |||||
| singlePlayer = lift singlePlayer | singlePlayer = lift singlePlayer | ||||
| game = lift game | game = lift game | ||||
| choose :: (HasCard b, HasCard a) => (Communicator c, MonadPlayer m) => [CardS Played] -> Maybe [b] -> [a] -> Online c m Card | |||||
| choose :: (MonadIO m, HasCard b, HasCard a) => (Communicator c, MonadPlayer m) => [CardS Played] -> Maybe [b] -> [a] -> Online c m Card | |||||
| choose table mayOuvert hand' = do | choose table mayOuvert hand' = do | ||||
| gm <- game | gm <- game | ||||
| let hand = sortRender (getTrump gm) $ map toCard hand' | let hand = sortRender (getTrump gm) $ map toCard hand' | ||||
| @@ -128,7 +128,7 @@ choose table mayOuvert hand' = do | |||||
| if card `elem` hand && allowed then return card else choose table mayOuvert hand' | if card `elem` hand && allowed then return card else choose table mayOuvert hand' | ||||
| Nothing -> choose table mayOuvert hand' | Nothing -> choose table mayOuvert hand' | ||||
| cardPlayed :: (Communicator c, MonadPlayer m) => CardS Played -> Online c m () | |||||
| cardPlayed :: (MonadIO m, 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) | ||||
| -- | QUERIES AND RESPONSES | -- | QUERIES AND RESPONSES | ||||
| @@ -70,7 +70,7 @@ instance MonadPlayer m => MonadPlayer (Simulator m) where | |||||
| turnColour = lift $ turnColour | turnColour = lift $ turnColour | ||||
| showSkat = lift . showSkat | showSkat = lift . showSkat | ||||
| instance MonadPlayer m => MonadPlayerOpen (Simulator m) where | |||||
| instance (MonadIO m, MonadPlayer m) => MonadPlayerOpen (Simulator m) where | |||||
| showPiles = ask | showPiles = ask | ||||
| runWithPiles :: MonadPlayer m | runWithPiles :: MonadPlayer m | ||||
| @@ -215,7 +215,7 @@ simplify :: Hand -> [Distribution] -> [(Distribution, Int)] | |||||
| simplify hand ds = M.elems cleaned | simplify hand ds = M.elems cleaned | ||||
| where cleaned = remove789s hand ds | where cleaned = remove789s hand ds | ||||
| onPlayed :: MonadPlayer m => CardS Played -> AI m () | |||||
| onPlayed :: (MonadIO m, MonadPlayer m) => CardS Played -> AI m () | |||||
| onPlayed c = do | onPlayed c = do | ||||
| liftIO $ print c | liftIO $ print c | ||||
| modifyg (getCard c `hasBeenPlayed`) | modifyg (getCard c `hasBeenPlayed`) | ||||
| @@ -227,10 +227,10 @@ onPlayed c = do | |||||
| then uorigin (getPile c) `hasNoLonger` demanded else return () | then uorigin (getPile c) `hasNoLonger` demanded else return () | ||||
| Nothing -> return () | Nothing -> return () | ||||
| choose :: MonadPlayer m => AI m Card | |||||
| choose :: (MonadIO m, MonadPlayer m) => AI m Card | |||||
| choose = chooseStatistic | choose = chooseStatistic | ||||
| chooseStatistic :: MonadPlayer m => AI m Card | |||||
| chooseStatistic :: (MonadIO m, MonadPlayer m) => AI m Card | |||||
| chooseStatistic = do | chooseStatistic = do | ||||
| h <- gets getHand | h <- gets getHand | ||||
| handCards <- gets myHand | handCards <- gets myHand | ||||
| @@ -284,13 +284,13 @@ foldWithLimit limit f start (x:xs) = do | |||||
| foldWithLimit limit f m xs | foldWithLimit limit f m xs | ||||
| _ -> return start | _ -> return start | ||||
| runOnPiles :: MonadPlayer m | |||||
| runOnPiles :: (MonadIO m, MonadPlayer m) | |||||
| => M.Map Card Int -> (Piles, Int) -> AI m (M.Map Card Int) | => M.Map Card Int -> (Piles, Int) -> AI m (M.Map Card Int) | ||||
| runOnPiles m (ps, n) = do | runOnPiles m (ps, n) = do | ||||
| c <- runWithPiles ps chooseOpen | c <- runWithPiles ps chooseOpen | ||||
| return $ M.insertWith (+) c n m | return $ M.insertWith (+) c n m | ||||
| chooseOpen :: (MonadState AIEnv m, MonadPlayerOpen m) => m Card | |||||
| chooseOpen :: (MonadIO m, MonadState AIEnv m, MonadPlayerOpen m) => m Card | |||||
| chooseOpen = do | chooseOpen = do | ||||
| piles <- showPiles | piles <- showPiles | ||||
| hand <- gets getHand | hand <- gets getHand | ||||
| @@ -388,7 +388,7 @@ leadPotential card = do | |||||
| 0 -> return value | 0 -> return value | ||||
| _ -> return $ -value | _ -> return $ -value | ||||
| chooseLead :: (MonadState AIEnv m, MonadPlayer m) => m Card | |||||
| chooseLead :: (MonadIO m, MonadState AIEnv m, MonadPlayer m) => m Card | |||||
| chooseLead = do | chooseLead = do | ||||
| cards <- gets myHand | cards <- gets myHand | ||||
| possible <- filterM (P.isAllowed cards) cards | possible <- filterM (P.isAllowed cards) cards | ||||
| @@ -0,0 +1,259 @@ | |||||
| {-# LANGUAGE MultiParamTypeClasses #-} | |||||
| {-# LANGUAGE TypeSynonymInstances #-} | |||||
| {-# LANGUAGE FlexibleInstances #-} | |||||
| {-# LANGUAGE FlexibleContexts #-} | |||||
| {-# LANGUAGE FunctionalDependencies #-} | |||||
| {-# LANGUAGE TupleSections #-} | |||||
| {-# LANGUAGE ScopedTypeVariables #-} | |||||
| module Skat.AI.Skat where | |||||
| import Control.Monad.State | |||||
| import Control.Exception (assert) | |||||
| import Control.Monad.Fail | |||||
| import Control.Monad.Writer | |||||
| import Data.Ord | |||||
| import Text.Read (readMaybe) | |||||
| import Data.List (maximumBy, sortBy) | |||||
| import Debug.Trace | |||||
| import Data.Map.Strict (Map) | |||||
| import qualified Data.Map.Strict as Map | |||||
| import qualified System.Random as Rand | |||||
| import System.IO.Unsafe | |||||
| import qualified Skat as S | |||||
| import qualified Skat.Card as S | |||||
| import qualified Skat.Utils as S | |||||
| import qualified Skat.AI.Stupid as S | |||||
| import qualified Skat.Operations as S | |||||
| import qualified Skat.Pile as S | |||||
| import qualified Skat.Player as P hiding (trumpColour) | |||||
| import qualified Skat.Render as S | |||||
| import qualified Skat.Bidding as S | |||||
| import Skat.AI.Base hiding (playCLI, Choose(..)) | |||||
| import Skat.AI.MonteCarlo | |||||
| import Skat.AI.Games.Skat.Guess | |||||
| instance Player P.PL where | |||||
| maxing p = P.team p == S.Team | |||||
| instance Player Bool where | |||||
| maxing = id | |||||
| instance Value Float where | |||||
| invert = (1-) | |||||
| win = undefined | |||||
| loss = undefined | |||||
| tie = undefined | |||||
| tonum = id | |||||
| instance P.MonadPlayer (StateT S.SkatEnv (Writer [S.Trick])) where | |||||
| trump = S.getTrump <$> P.game | |||||
| turnColour = gets S.turnColour | |||||
| showSkat p = case P.team p of | |||||
| S.Single -> fmap (Just . S.skatCards) $ gets S.piles | |||||
| S.Team -> return Nothing | |||||
| singlePlayer = gets S.skatSinglePlayer | |||||
| game = gets S.skatGame | |||||
| data SkatState = SkatState { skatEnv :: S.SkatEnv | |||||
| , self :: S.Hand | |||||
| , guess :: Guess | |||||
| } | |||||
| deriving Show | |||||
| instance Draw SkatState where | |||||
| draw = show . S.tableCards . S.piles . skatEnv | |||||
| instance PlayableGame (S.CardS S.Owner) [] Float P.PL S.Skat where | |||||
| showBoard = do | |||||
| liftIO $ putStrLn "" | |||||
| table <- S.getp S.tableCards | |||||
| liftIO $ putStr "Table: " | |||||
| liftIO $ print table | |||||
| showTurns = do | |||||
| cards <- turns | |||||
| player <- currentPlayer | |||||
| liftIO $ print player | |||||
| liftIO $ S.render cards | |||||
| winner = do | |||||
| piles <- gets S.piles | |||||
| pls <- gets S.players | |||||
| let res = S.count piles :: (Int, Int) | |||||
| winnerTeam = trace (show res) $ if fst res > snd res then S.Single else S.Team | |||||
| winners = filter ((==winnerTeam) . P.team) (P.playersToList pls) | |||||
| return $ Just $ head winners | |||||
| askTurn = do | |||||
| cards <- turns | |||||
| let sorted = cards | |||||
| input <- liftIO getLine | |||||
| case readMaybe input of | |||||
| Just n -> if n >= 0 && n < length sorted then return $ Just (sorted !! n) | |||||
| else return Nothing | |||||
| Nothing -> return Nothing | |||||
| showTurn card = do | |||||
| player <- currentPlayer | |||||
| liftIO $ putStrLn $ show player ++ " plays " ++ show card | |||||
| instance MonadGame (S.CardS S.Owner) [] Float P.PL S.Skat where | |||||
| currentPlayer = do | |||||
| hand <- gets S.currentHand | |||||
| pls <- gets S.players | |||||
| return $! P.player pls hand | |||||
| 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_ | |||||
| simulate card action = do | |||||
| --oldCurrent <- gets S.currentHand | |||||
| --oldTurnCol <- gets S.turnColour | |||||
| backup <- get | |||||
| play card | |||||
| --oldWinner <- currentPlayer | |||||
| res <- action | |||||
| --S.undo_ card oldCurrent oldTurnCol (P.team oldWinner) | |||||
| put backup | |||||
| return $! res | |||||
| over = ((==0) . length) <$!> S.allowedCards | |||||
| evaluate = do | |||||
| player <- currentPlayer | |||||
| piles <- gets S.piles | |||||
| let (sgl, tm) = S.count piles :: (Int, Int) | |||||
| return $! fromIntegral (if maxing player then tm - sgl else sgl - tm) | |||||
| data Turn = Turn { turnStartingEnv :: S.SkatEnv | |||||
| , turnCard :: S.Card } | |||||
| deriving Show | |||||
| instance Draw Turn where | |||||
| draw = show . turnCard | |||||
| instance HasGameState Turn Bool Float SkatState where | |||||
| current s = | |||||
| let curhand = S.currentHand $ skatEnv s | |||||
| sglhand = S.skatSinglePlayer $ skatEnv s | |||||
| in sglhand == curhand | |||||
| monteevaluate s = let (sgl, tm) = ev S.countGame (skatEnv s) | |||||
| in if sgl > tm then 1.0 else 0.0 --fromIntegral sgl / (fromIntegral $ sgl + tm) | |||||
| execute turn state = | |||||
| let newEnv = ex (S.play_ card) env | |||||
| in state { skatEnv = newEnv | |||||
| , guess = card `hasBeenPlayed` (guess state) | |||||
| } | |||||
| where env = turnStartingEnv turn | |||||
| card = turnCard turn | |||||
| moves s | |||||
| | S.currentHand env == self s = | |||||
| let options = fmap S.toCard $ ev S.allowedCards env | |||||
| in fmap (Turn env) options | |||||
| | otherwise = | |||||
| 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) | |||||
| possibleDistrs = distributions (guess s) ns | |||||
| piless = fmap ((flip updatePiles) currentPiles) possibleDistrs | |||||
| in do | |||||
| piles <- piless | |||||
| let newEnv = env { S.piles = piles } | |||||
| card <- ev S.allowedCards newEnv | |||||
| pure $ Turn newEnv (S.toCard card) | |||||
| where env = skatEnv s | |||||
| ev :: StateT S.SkatEnv (Writer [S.Trick]) a -> S.SkatEnv -> a | |||||
| ev action = fst . runWriter . evalStateT action | |||||
| ev2 = flip ev | |||||
| ex :: StateT S.SkatEnv (Writer [S.Trick]) a -> S.SkatEnv -> S.SkatEnv | |||||
| ex action = fst . runWriter . execStateT action | |||||
| ex2 = flip ex | |||||
| playCLI :: Int -> StateT SkatState S.Skat () | |||||
| playCLI n = do | |||||
| gameOver <- lift over | |||||
| if gameOver | |||||
| then lift announceWinner | |||||
| else do | |||||
| current <- (lift currentPlayer) :: StateT SkatState S.Skat (P.PL) | |||||
| self <- gets self | |||||
| --let current = False | |||||
| if P.hand current == self then do | |||||
| liftIO $ putStrLn "iterating" | |||||
| s <- get | |||||
| let tree = Leaf s False (0, 0) | |||||
| t = runmonte n (foldM (\tree _ -> montecarlo tree) tree [1..20]) | |||||
| newstate = bestmove t | |||||
| --liftIO $ putStrLn $ visualise t | |||||
| put newstate | |||||
| lift (put $ skatEnv newstate) | |||||
| else do | |||||
| liftIO $ putStrLn "new turn" | |||||
| lift $ showBoard | |||||
| t <- lift readTurn | |||||
| lift $ play t | |||||
| s <- get | |||||
| env <- lift get | |||||
| let s' = s { skatEnv = env } | |||||
| put s' | |||||
| {- | |||||
| showBoard | |||||
| liftIO $ getLine | |||||
| -} | |||||
| playCLI n | |||||
| where | |||||
| --readTurn :: (MonadFail m, Read t, PlayableGame t l v p m) => m t | |||||
| readTurn :: S.Skat (S.CardS S.Owner) | |||||
| readTurn = do | |||||
| v <- evaluate :: S.Skat Float | |||||
| options <- (turns :: S.Skat [S.CardS S.Owner]) | |||||
| showTurns | |||||
| liftIO $ putStr "> " | |||||
| mayTurn <- askTurn | |||||
| case mayTurn of | |||||
| Just val -> if val `elem` options then return val else readTurn | |||||
| Nothing -> readTurn | |||||
| announceWinner :: S.Skat () | |||||
| announceWinner = do | |||||
| showBoard | |||||
| win <- (winner :: S.Skat (Maybe P.PL)) | |||||
| liftIO $ putStrLn $ show win ++ " wins the game!" | |||||
| initSkatEnv :: Int -> S.SkatEnv | |||||
| initSkatEnv n = | |||||
| let gen = Rand.mkStdGen n | |||||
| --cards = S.shuffle gen S.allCards | |||||
| --piles = S.distribute cards | |||||
| piles = S.cardDistr6 | |||||
| players = P.Players | |||||
| (P.PL $ S.Stupid S.Single S.Hand1) | |||||
| (P.PL $ S.Stupid S.Team S.Hand2) | |||||
| (P.PL $ S.Stupid S.Team S.Hand3) | |||||
| in S.SkatEnv { S.piles = piles | |||||
| , S.turnColour = Nothing | |||||
| , S.skatGame = S.Colour S.Spades S.Einfach | |||||
| , S.players = players | |||||
| , S.currentHand = S.Hand1 | |||||
| , S.skatSinglePlayer = S.Hand1 | |||||
| } | |||||
| initSkatState :: SkatState | |||||
| initSkatState = | |||||
| let env = initSkatEnv 42 | |||||
| ownCards = S.handCards S.Hand1 $ S.piles env | |||||
| sktCards = S.skatCards $ S.piles env | |||||
| tblCards = fmap S.toCard $ S.tableCards $ S.piles env | |||||
| totalcards = fmap S.toCard $ S.fromPiles $ S.piles env | |||||
| guess = (\g -> foldr hasBeenPlayed g tblCards) . isSkat sktCards . (S.Hand1 `hasOnly` (fmap S.toCard ownCards)) $ newGuessWith totalcards | |||||
| in SkatState { skatEnv = env | |||||
| , self = S.Hand1 | |||||
| , guess = guess | |||||
| } | |||||
| playSkat :: Int -> IO () | |||||
| playSkat n = let env = skatEnv initSkatState | |||||
| in void $ S.evalSkat ( (flip runStateT) initSkatState (playCLI n) ) env | |||||
| @@ -19,7 +19,7 @@ instance Player Stupid where | |||||
| chooseCard p _ _ _ hand = do | chooseCard p _ _ _ hand = do | ||||
| trumpCol <- trump | trumpCol <- trump | ||||
| turnCol <- turnColour | turnCol <- turnColour | ||||
| liftIO $ threadDelay 1000000 | |||||
| --liftIO $ threadDelay 1000000 | |||||
| let possible = filter (isAllowed trumpCol turnCol hand) hand | let possible = filter (isAllowed trumpCol turnCol hand) hand | ||||
| return (toCard $ head possible, p) | return (toCard $ head possible, p) | ||||
| @@ -0,0 +1,242 @@ | |||||
| {-# LANGUAGE MultiParamTypeClasses #-} | |||||
| {-# LANGUAGE BlockArguments #-} | |||||
| {-# LANGUAGE TypeSynonymInstances #-} | |||||
| {-# LANGUAGE FlexibleInstances #-} | |||||
| {-# LANGUAGE FlexibleContexts #-} | |||||
| {-# LANGUAGE FunctionalDependencies #-} | |||||
| {-# LANGUAGE TupleSections #-} | |||||
| {-# LANGUAGE InstanceSigs #-} | |||||
| {-# LANGUAGE StandaloneDeriving #-} | |||||
| {-# LANGUAGE ImportQualifiedPost #-} | |||||
| {-# LANGUAGE GeneralizedNewtypeDeriving #-} | |||||
| module Skat.AI.TicTacToe where | |||||
| import Control.Monad.State | |||||
| import Control.Exception (assert) | |||||
| import Control.Monad.Fail | |||||
| import Data.Ord | |||||
| import Text.Read (readMaybe) | |||||
| import Data.List (maximumBy, sortBy) | |||||
| import Debug.Trace | |||||
| import Text.Printf | |||||
| import Data.Maybe | |||||
| import qualified System.Random as Rand | |||||
| import Skat.AI.Base | |||||
| import Skat.AI.MonteCarlo | |||||
| import Skat.Utils | |||||
| -- TIC TAC TOE implementation | |||||
| data TicTacToe = Tic | Tac | Toe | |||||
| deriving (Eq, Ord) | |||||
| instance Show TicTacToe where | |||||
| show Tic = "O" | |||||
| show Tac = "X" | |||||
| show Toe = "_" | |||||
| data WinLossTie = Loss | Tie | Win | |||||
| deriving (Eq, Show, Ord) | |||||
| instance Value WinLossTie where | |||||
| invert Win = Loss | |||||
| invert Loss = Win | |||||
| invert Tie = Tie | |||||
| win = Win | |||||
| loss = Loss | |||||
| tie = Tie | |||||
| data GameState = GameState { getBoard :: [TicTacToe] | |||||
| , getCurrent :: Bool } | |||||
| deriving Show | |||||
| instance HasGameState Int Bool WinLossTie GameState where | |||||
| execute turn state = execState (play turn) state | |||||
| moves state = evalState turns state | |||||
| monteevaluate s = let b = getBoard s | |||||
| w = fromMaybe Toe $ ticWinner b | |||||
| in case w of | |||||
| Tac -> Win | |||||
| Tic -> Loss | |||||
| Toe -> Tie | |||||
| current s = evalState currentPlayer s | |||||
| instance Player Bool where | |||||
| maxing = id | |||||
| instance Monad m => MonadGame Int [] WinLossTie Bool (StateT GameState m) where | |||||
| currentPlayer = gets getCurrent | |||||
| turns = do | |||||
| o <- over | |||||
| if o then return [] else do | |||||
| board <- gets getBoard | |||||
| let fields = zip [0..] board | |||||
| return $ map fst $ filter ((==Toe) . snd) fields | |||||
| play turn = do | |||||
| env <- get | |||||
| let value = if getCurrent env then Tic else Tac | |||||
| board' = updateAt turn (getBoard env) value | |||||
| current' = not $ getCurrent env | |||||
| put $ GameState board' current' | |||||
| simulate turn action = do | |||||
| backup <- get | |||||
| play turn | |||||
| res <- action | |||||
| put backup | |||||
| return $! res | |||||
| evaluate = do | |||||
| board <- gets getBoard | |||||
| current <- currentPlayer | |||||
| let mayWinner = ticWinner board | |||||
| case mayWinner of | |||||
| Just Tic -> return $ if current then Win else Loss | |||||
| Just Tac -> return $ if current then Loss else Win | |||||
| Just Toe -> return Tie | |||||
| Nothing -> return Tie | |||||
| over = do | |||||
| board <- gets getBoard | |||||
| case ticWinner board of | |||||
| Just _ -> return True | |||||
| _ -> return False | |||||
| ticWinner :: [TicTacToe] -> Maybe TicTacToe | |||||
| ticWinner board | |||||
| | ticWon = Just Tic | |||||
| | tacWon = Just Tac | |||||
| | over = Just Toe | |||||
| | otherwise = Nothing | |||||
| where ticWon = hasWon $ map (==Tic) board | |||||
| tacWon = hasWon $ map (==Tac) board | |||||
| hasWon (True:_:_:True:_:_:True:_:_:[]) = True | |||||
| hasWon (True:_:_:_:True:_:_:_:True:[]) = True | |||||
| hasWon (_:True:_:_:True:_:_:True:_:[]) = True | |||||
| hasWon (_:_:True:_:_:True:_:_:True:[]) = True | |||||
| hasWon (_:_:True:_:True:_:True:_:_:[]) = True | |||||
| hasWon (True:True:True:_:_:_:_:_:_:[]) = True | |||||
| hasWon (_:_:_:True:True:True:_:_:_:[]) = True | |||||
| hasWon (_:_:_:_:_:_:True:True:True:[]) = True | |||||
| hasWon _ = False | |||||
| over = (length $ filter (==Toe) board) == 0 | |||||
| -- some consts | |||||
| emptyBoard :: [TicTacToe] | |||||
| emptyBoard = [Toe, Toe, Toe, Toe, Toe, Toe, Toe, Toe, Toe] | |||||
| otherBoard2 :: [TicTacToe] | |||||
| otherBoard2 = [Tic, Tac, Toe, Tac, Tac, Tic, Tic, Toe, Toe] | |||||
| otherBoard3 :: [TicTacToe] | |||||
| otherBoard3 = [Tic, Toe, Toe, Tac, Tic, Toe, Toe, Tac, Toe] | |||||
| tree2 = emptytree (initGameState { getBoard = otherBoard2 | |||||
| , getCurrent = True }) | |||||
| tree3 = emptytree (initGameState { getBoard = otherBoard3 | |||||
| , getCurrent = False }) | |||||
| initGameState :: GameState | |||||
| initGameState = GameState { getBoard = emptyBoard | |||||
| , getCurrent = False } | |||||
| tictree :: Tree Int GameState | |||||
| tictree = emptytree initGameState | |||||
| instance Draw GameState where | |||||
| draw s = let b = getBoard s | |||||
| in printf "%s %s %s\n%s %s %s\n%s %s %s" | |||||
| (show $ b !! 0) | |||||
| (show $ b !! 1) | |||||
| (show $ b !! 2) | |||||
| (show $ b !! 3) | |||||
| (show $ b !! 4) | |||||
| (show $ b !! 5) | |||||
| (show $ b !! 6) | |||||
| (show $ b !! 7) | |||||
| (show $ b !! 8) | |||||
| otherBoard :: [TicTacToe] | |||||
| otherBoard = [Tic, Tac, Tac, Tic, Tac, Tic, Toe, Tic, Toe] | |||||
| print9x9 :: (Int -> IO ()) -> IO () | |||||
| print9x9 pr = pr 0 >> pr 1 >> pr 2 >> putStrLn "" | |||||
| >> pr 3 >> pr 4 >> pr 5 >> putStrLn "" | |||||
| >> pr 6 >> pr 7 >> pr 8 >> putStrLn "" | |||||
| printBoard :: [TicTacToe] -> IO () | |||||
| printBoard board = print9x9 pr >> putStrLn "" | |||||
| where pr n = putStr (show $ board !! n) >> putStr " " | |||||
| printOptions :: [Int] -> IO () | |||||
| printOptions opts = print9x9 pr | |||||
| where pr n | |||||
| | n `elem` opts = putStr (show n) >> putStr " " | |||||
| | otherwise = putStr " " | |||||
| instance MonadIO m => PlayableGame Int [] WinLossTie Bool (StateT GameState m) where | |||||
| showBoard = do | |||||
| board <- gets getBoard | |||||
| liftIO $ printBoard board | |||||
| showTurns = turns >>= liftIO . printOptions | |||||
| winner = do | |||||
| board <- gets getBoard | |||||
| let win = ticWinner board | |||||
| case win of | |||||
| Just Toe -> return Nothing | |||||
| Just Tic -> return $ Just True | |||||
| Just Tac -> return $ Just False | |||||
| Nothing -> return Nothing | |||||
| askTurn = readMaybe <$> liftIO getLine | |||||
| showTurn _ = return () | |||||
| playTicTacToe :: Int -> IO () | |||||
| playTicTacToe n = void $ (flip runStateT) (GameState emptyBoard False) (playCLI n) | |||||
| playoften :: Int -> IO () | |||||
| playoften n = mapM_ playTicTacToe [1..n] | |||||
| {- | |||||
| newtype TicMCTS a = TicMCTS (StateT GameState (State Rand.StdGen) a) | |||||
| deriving (Functor, Applicative, Monad, MonadState GameState) | |||||
| instance Choose Int TicMCTS where | |||||
| choose = do | |||||
| s <- get | |||||
| -} | |||||
| playCLI :: Int -> StateT GameState IO () | |||||
| playCLI n = do | |||||
| gameOver <- over | |||||
| if gameOver | |||||
| then announceWinner | |||||
| else do | |||||
| current <- currentPlayer | |||||
| --let current = False | |||||
| if not current then do | |||||
| s <- get | |||||
| let tree = Leaf s False (0, 0) | |||||
| t = bestmove $ runmonte n (foldM (\tree _ -> montecarlo tree) tree [1..5000]) | |||||
| put t | |||||
| else do | |||||
| showBoard | |||||
| t <- readTurn | |||||
| play t | |||||
| showBoard | |||||
| {- | |||||
| liftIO $ getLine | |||||
| -} | |||||
| playCLI n | |||||
| where | |||||
| readTurn :: (MonadFail m, Read t, PlayableGame t l v p m) => m t | |||||
| readTurn = do | |||||
| options <- turns | |||||
| showTurns | |||||
| liftIO $ putStr "> " | |||||
| mayTurn <- askTurn | |||||
| case mayTurn of | |||||
| Just val -> if val `elem` options then return val else readTurn | |||||
| Nothing -> readTurn | |||||
| announceWinner = do | |||||
| showBoard | |||||
| win <- winner | |||||
| liftIO $ putStrLn $ show win ++ " wins the game!" | |||||
| @@ -2,9 +2,12 @@ | |||||
| {-# LANGUAGE FlexibleInstances #-} | {-# LANGUAGE FlexibleInstances #-} | ||||
| {-# LANGUAGE FlexibleContexts #-} | {-# LANGUAGE FlexibleContexts #-} | ||||
| {-# LANGUAGE OverloadedStrings #-} | {-# LANGUAGE OverloadedStrings #-} | ||||
| {-# LANGUAGE DeriveGeneric #-} | |||||
| {-# LANGUAGE DeriveAnyClass #-} | |||||
| module Skat.Card where | module Skat.Card where | ||||
| import GHC.Generics (Generic, Generic1) | |||||
| import Data.List | import Data.List | ||||
| import Data.Foldable (Foldable) | import Data.Foldable (Foldable) | ||||
| import qualified Data.Foldable as F | import qualified Data.Foldable as F | ||||
| @@ -29,7 +32,7 @@ data Type = Seven | |||||
| | Ten | | Ten | ||||
| | Ace | | Ace | ||||
| | Jack | | Jack | ||||
| deriving (Eq, Ord, Show, Enum, Read, Bounded) | |||||
| deriving (Eq, Ord, Show, Enum, Read, Bounded, Generic, NFData) | |||||
| data NullType = NSeven | data NullType = NSeven | ||||
| | NEight | | NEight | ||||
| @@ -53,7 +56,7 @@ data Colour = Diamonds | |||||
| | Hearts | | Hearts | ||||
| | Spades | | Spades | ||||
| | Clubs | | Clubs | ||||
| deriving (Eq, Ord, Show, Enum, Read, Bounded) | |||||
| deriving (Eq, Ord, Show, Enum, Read, Bounded, Generic, NFData) | |||||
| data Trump = TrumpColour Colour | data Trump = TrumpColour Colour | ||||
| | Jacks | | Jacks | ||||
| @@ -65,7 +68,7 @@ data TurnColour = TurnColour Colour | |||||
| deriving (Show, Eq) | deriving (Show, Eq) | ||||
| data Card = Card Type Colour | data Card = Card Type Colour | ||||
| deriving (Eq, Show, Ord, Read, Bounded) | |||||
| deriving (Eq, Show, Ord, Read, Bounded, Generic) | |||||
| getType :: Card -> Type | getType :: Card -> Type | ||||
| getType (Card t _) = t | getType (Card t _) = t | ||||
| @@ -1,12 +1,15 @@ | |||||
| {-# LANGUAGE FlexibleContexts #-} | |||||
| module Skat.Operations ( | module Skat.Operations ( | ||||
| turn, turnGeneric, play, playOpen, | turn, turnGeneric, play, playOpen, | ||||
| play_, sortRender, undo_, gameOver | |||||
| play_, sortRender, undo_, gameOver, | |||||
| countGame | |||||
| ) where | ) where | ||||
| import Control.Monad.State | import Control.Monad.State | ||||
| import Control.Monad.Catch | import Control.Monad.Catch | ||||
| import Control.Exception hiding (catch, bracketOnError) | import Control.Exception hiding (catch, bracketOnError) | ||||
| import Control.Monad.Writer (tell) | |||||
| import Control.Monad.Writer | |||||
| import System.Random (newStdGen, randoms) | import System.Random (newStdGen, randoms) | ||||
| import Data.List | import Data.List | ||||
| import Data.Ord | import Data.Ord | ||||
| @@ -21,7 +24,7 @@ import Skat.Player (chooseCard, Players(..), Player(..), PL(..), | |||||
| import Skat.Utils (shuffle) | import Skat.Utils (shuffle) | ||||
| import Skat.Bidding | import Skat.Bidding | ||||
| play_ :: HasCard c => c -> Skat () | |||||
| play_ :: (MonadWriter [Trick] m, MonadPlayer m, MonadState SkatEnv m, HasCard c) => c -> m () | |||||
| play_ card = do | play_ card = do | ||||
| hand <- gets currentHand | hand <- gets currentHand | ||||
| trCol <- trump | trCol <- trump | ||||
| @@ -82,7 +85,7 @@ turnGeneric playFunc depth = do | |||||
| turn :: Skat (Int, Int) | turn :: Skat (Int, Int) | ||||
| turn = turnGeneric play 10 | turn = turnGeneric play 10 | ||||
| evaluateTable :: Skat Hand | |||||
| evaluateTable :: (MonadPlayer m, MonadState SkatEnv m, MonadWriter [Trick] m) => m Hand | |||||
| evaluateTable = do | evaluateTable = do | ||||
| trumpCol <- trump | trumpCol <- trump | ||||
| turnCol <- gets turnColour | turnCol <- gets turnColour | ||||
| @@ -95,7 +98,7 @@ evaluateTable = do | |||||
| tell [(table !! 2, table !! 1, table !! 0)] | tell [(table !! 2, table !! 1, table !! 0)] | ||||
| return $ hand winner | return $ hand winner | ||||
| countGame :: Skat (Int, Int) | |||||
| countGame :: (MonadState SkatEnv m) => m (Int, Int) | |||||
| countGame = getp count | countGame = getp count | ||||
| play :: (Show p, Player p) => p -> Skat Card | play :: (Show p, Player p) => p -> Skat Card | ||||
| @@ -124,7 +127,7 @@ playOpen p = do | |||||
| modifyp $ playCard (hand p) card | modifyp $ playCard (hand p) card | ||||
| return card | return card | ||||
| gameOver :: Skat Bool | |||||
| gameOver :: (MonadPlayer m, MonadState SkatEnv m) => m Bool | |||||
| gameOver = do | gameOver = do | ||||
| tr <- trump | tr <- trump | ||||
| case tr of | case tr of | ||||
| @@ -3,12 +3,16 @@ | |||||
| {-# LANGUAGE FlexibleContexts #-} | {-# LANGUAGE FlexibleContexts #-} | ||||
| {-# LANGUAGE OverloadedStrings #-} | {-# LANGUAGE OverloadedStrings #-} | ||||
| {-# LANGUAGE TupleSections #-} | {-# LANGUAGE TupleSections #-} | ||||
| {-# LANGUAGE DeriveGeneric #-} | |||||
| {-# LANGUAGE DeriveAnyClass #-} | |||||
| module Skat.Pile where | module Skat.Pile where | ||||
| import Control.Monad.State | import Control.Monad.State | ||||
| import Control.Monad.Trans.Maybe | import Control.Monad.Trans.Maybe | ||||
| import GHC.Generics | |||||
| import Control.DeepSeq | |||||
| import Prelude hiding (lookup) | import Prelude hiding (lookup) | ||||
| import qualified Data.Map.Strict as M | import qualified Data.Map.Strict as M | ||||
| import qualified Data.Vector as V | import qualified Data.Vector as V | ||||
| @@ -29,7 +33,10 @@ data Team = Team | Single | |||||
| data CardS p = CardS { getCard :: Card | data CardS p = CardS { getCard :: Card | ||||
| , getPile :: p } | , getPile :: p } | ||||
| deriving (Show, Eq, Ord, Read) | |||||
| deriving (Eq, Ord, Read) | |||||
| instance (Show p) => Show (CardS p) where | |||||
| show (CardS card pile) = show card ++ " from " ++ show pile | |||||
| instance HasCard (CardS p) where | instance HasCard (CardS p) where | ||||
| toCard = getCard | toCard = getCard | ||||
| @@ -46,7 +53,7 @@ instance ToJSON p => ToJSON (CardS p) where | |||||
| object ["card" .= card, "pile" .= pile] | object ["card" .= card, "pile" .= pile] | ||||
| data Hand = Hand1 | Hand2 | Hand3 | data Hand = Hand1 | Hand2 | Hand3 | ||||
| deriving (Show, Eq, Ord, Read, Enum, Bounded) | |||||
| deriving (Show, Eq, Ord, Read, Enum, Bounded, Generic, NFData) | |||||
| toInt :: Hand -> Int | toInt :: Hand -> Int | ||||
| toInt Hand1 = 1 | toInt Hand1 = 1 | ||||
| @@ -102,6 +109,9 @@ data Piles = Piles { _hand1 :: [CardS Owner] | |||||
| , _skat :: [CardS Owner] } | , _skat :: [CardS Owner] } | ||||
| deriving (Show, Eq, Ord) | deriving (Show, Eq, Ord) | ||||
| fromPiles :: Piles -> [CardS Owner] | |||||
| fromPiles ps = _hand1 ps ++ _hand2 ps ++ _hand3 ps ++ _table ps ++ _wonSingle ps ++ _wonTeam ps ++ _skat ps | |||||
| toTable :: Hand -> Card -> Piles -> Piles | toTable :: Hand -> Card -> Piles -> Piles | ||||
| toTable hand card ps = ps { _table = (CardS card (P hand)) : _table ps } | toTable hand card ps = ps { _table = (CardS card (P hand)) : _table ps } | ||||
| @@ -236,3 +246,60 @@ instance Serialize String [Trick] where | |||||
| card2 <- takeG 2 >>= MaybeT . return . deserialize | card2 <- takeG 2 >>= MaybeT . return . deserialize | ||||
| card3 <- takeG 2 >>= MaybeT . return . deserialize | card3 <- takeG 2 >>= MaybeT . return . deserialize | ||||
| go ((card1, card2, card3):acc) | go ((card1, card2, card3):acc) | ||||
| cardDistr :: Piles | |||||
| cardDistr = emptyPiles hand1 hand2 hand3 skt | |||||
| 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 Eight Clubs, Card King Clubs] | |||||
| hand1 = [Card Jack Spades, Card Jack Hearts, Card Ten Spades, Card Ace Hearts, Card Ten Hearts, | |||||
| Card Nine Hearts, Card Seven Clubs, Card Ace Clubs, Card King Diamonds, | |||||
| Card Ten 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 Nine Diamonds, Card Eight Diamonds] | |||||
| skt = [Card Nine Clubs, Card Queen Clubs] | |||||
| cardDistr2 :: Piles | |||||
| cardDistr2 = emptyPiles hand1 hand2 hand3 skt | |||||
| where hand3 = [Card Ace Spades, Card Eight Spades, Card Queen Diamonds, Card Ace Clubs] | |||||
| hand1 = [Card Jack Spades, Card Seven Spades, Card Ten Diamonds, Card Nine Spades] | |||||
| hand2 = [Card Ten Hearts, Card Eight Hearts, Card Ace Diamonds, Card King Clubs] | |||||
| skt = [Card Nine Clubs, Card Queen Clubs] | |||||
| cardDistr3 :: Piles | |||||
| cardDistr3 = emptyPiles hand1 hand2 hand3 skt | |||||
| where hand3 = [Card Ace Spades, Card Eight Spades, Card Ace Clubs] | |||||
| hand1 = [Card Jack Spades, Card Seven Spades, Card Nine Spades] | |||||
| hand2 = [Card Ten Hearts, Card Ace Hearts, Card Ten Clubs] | |||||
| skt = [Card Nine Clubs, Card Seven Clubs] | |||||
| cardDistr4 :: Piles | |||||
| cardDistr4 = makePiles hand1 hand2 hand3 tbl skt | |||||
| where hand3 = [Card Ace Spades] | |||||
| hand1 = [Card Jack Spades, Card Nine Spades] | |||||
| hand2 = [Card Eight Spades] | |||||
| skt = [Card Nine Clubs, Card Eight Clubs] | |||||
| tbl = [CardS (Card Ace Clubs) (P Hand3), CardS (Card King Clubs) (P Hand2)] | |||||
| cardDistr5 :: Piles | |||||
| cardDistr5 = makePiles hand1 hand2 hand3 tbl skt | |||||
| where hand3 = [Card Ace Spades] | |||||
| hand1 = [] | |||||
| hand2 = [] | |||||
| skt = [Card Nine Clubs, Card Queen Clubs] | |||||
| tbl = [CardS (Card Jack Spades) (P Hand1), CardS (Card Eight Spades) (P Hand2)] | |||||
| cardDistr6 :: Piles | |||||
| cardDistr6 = emptyPiles hand1 hand2 hand3 skt | |||||
| where hand3 = [Card Ace Spades, Card Jack Diamonds, Card Jack Clubs, Card King Spades, | |||||
| Card Nine Spades, Card Ace Diamonds, Card Queen Diamonds | |||||
| ] | |||||
| hand1 = [Card Jack Spades, Card Jack Hearts, Card Ten Spades, Card Ace Hearts, | |||||
| Card Ten Hearts, Card Nine Hearts, Card Seven Clubs | |||||
| ] | |||||
| hand2 = [Card Eight Spades, Card Queen Spades, Card Seven Spades, Card Seven Diamonds, | |||||
| Card Seven Hearts, Card Eight Hearts, Card Queen Hearts | |||||
| ] | |||||
| skt = [Card Nine Clubs, Card Queen Clubs] | |||||
| @@ -8,7 +8,7 @@ import Skat.Card | |||||
| import Skat.Pile | import Skat.Pile | ||||
| import Skat.Bidding | import Skat.Bidding | ||||
| class (Monad m, MonadIO m) => MonadPlayer m where | |||||
| class Monad m => MonadPlayer m where | |||||
| trump :: m Trump | trump :: m Trump | ||||
| turnColour :: m (Maybe TurnColour) | turnColour :: m (Maybe TurnColour) | ||||
| showSkat :: Player p => p -> m (Maybe [Card]) | showSkat :: Player p => p -> m (Maybe [Card]) | ||||
| @@ -21,19 +21,19 @@ 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 :: (HasCard d, HasCard c, MonadPlayer m) | |||||
| chooseCard :: (MonadIO m, HasCard d, HasCard c, MonadPlayer m) | |||||
| => p | => p | ||||
| -> [CardS Played] | -> [CardS Played] | ||||
| -> [CardS Played] | -> [CardS Played] | ||||
| -> Maybe [d] | -> Maybe [d] | ||||
| -> [c] | -> [c] | ||||
| -> m (Card, p) | -> m (Card, p) | ||||
| onCardPlayed :: MonadPlayer m | |||||
| onCardPlayed :: (MonadPlayer m, MonadIO m) | |||||
| => p | => p | ||||
| -> CardS Played | -> CardS Played | ||||
| -> m p | -> m p | ||||
| onCardPlayed p _ = return p | onCardPlayed p _ = return p | ||||
| chooseCardOpen :: MonadPlayerOpen m | |||||
| chooseCardOpen :: (MonadIO m, MonadPlayerOpen m) | |||||
| => p | => p | ||||
| -> m Card | -> m Card | ||||
| chooseCardOpen p = do | chooseCardOpen p = do | ||||
| @@ -95,3 +95,7 @@ safeToEnum n | |||||
| | otherwise = Just $ toEnum n | | otherwise = Just $ toEnum n | ||||
| where maxN = fromEnum (maxBound :: a) | where maxN = fromEnum (maxBound :: a) | ||||
| minN = fromEnum (minBound :: a) | minN = fromEnum (minBound :: a) | ||||
| updateAt :: Int -> [a] -> a -> [a] | |||||
| updateAt n xs y = map f $ zip [0..] xs | |||||
| where f (i, x) = if i == n then y else x | |||||