Skat Engine und AI auf Haskell Basis
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

428 lines
14KB

  1. {-# LANGUAGE NamedFieldPuns #-}
  2. {-# LANGUAGE TypeSynonymInstances #-}
  3. {-# LANGUAGE FlexibleInstances #-}
  4. {-# LANGUAGE FlexibleContexts #-}
  5. module Skat.AI.Rulebased (
  6. mkAIEnv, testds, simplify
  7. ) where
  8. import Control.Parallel.Strategies
  9. import Data.Ord
  10. import Data.Monoid ((<>))
  11. import Data.List
  12. import qualified Data.Set as S
  13. import Control.Monad.State
  14. import Control.Monad.Reader
  15. import qualified Data.Map.Strict as M
  16. import Skat.Player
  17. import qualified Skat.Player.Utils as P
  18. import Skat.Pile hiding (isSkat)
  19. import Skat.Card
  20. import Skat.Utils
  21. import Skat (Skat, modifyp, mkSkatEnv, evalSkat)
  22. import Skat.Operations
  23. import qualified Skat.AI.Minmax as Minmax
  24. import qualified Skat.AI.Stupid as Stupid (Stupid(..))
  25. import Skat.Bidding
  26. data AIEnv = AIEnv { getTeam :: Team
  27. , getHand :: Hand
  28. , table :: [CardS Played]
  29. , fallen :: [CardS Played]
  30. , myHand :: [Card]
  31. , guess :: Guess
  32. , simulationDepth :: Int }
  33. deriving Show
  34. setTable :: [CardS Played] -> AIEnv -> AIEnv
  35. setTable tab env = env { table = tab }
  36. setHand :: [Card] -> AIEnv -> AIEnv
  37. setHand hand env = env { myHand = hand }
  38. setFallen :: [CardS Played] -> AIEnv -> AIEnv
  39. setFallen fallen env = env { fallen = fallen }
  40. setDepth :: Int -> AIEnv -> AIEnv
  41. setDepth depth env = env { simulationDepth = depth }
  42. modifyg :: MonadPlayer m => (Guess -> Guess) -> AI m ()
  43. modifyg f = modify g
  44. where g env@(AIEnv {guess}) = env { guess = f guess }
  45. type AI m = StateT AIEnv m
  46. instance MonadPlayer m => MonadPlayer (AI m) where
  47. trump = lift trump
  48. turnColour = lift turnColour
  49. showSkat = lift . showSkat
  50. instance MonadPlayerOpen m => MonadPlayerOpen (AI m) where
  51. showPiles = lift $ showPiles
  52. type Simulator m = ReaderT Piles (AI m)
  53. instance MonadPlayer m => MonadPlayer (Simulator m) where
  54. trump = lift trump
  55. turnColour = lift $ turnColour
  56. showSkat = lift . showSkat
  57. instance (MonadIO m, MonadPlayer m) => MonadPlayerOpen (Simulator m) where
  58. showPiles = ask
  59. runWithPiles :: MonadPlayer m
  60. => Piles -> Simulator m a -> AI m a
  61. runWithPiles ps sim = runReaderT sim ps
  62. instance Player AIEnv where
  63. team = getTeam
  64. hand = getHand
  65. chooseCard p table fallen _ hand = runStateT (do
  66. modify $ setTable table
  67. modify $ setHand (map toCard hand)
  68. modify $ setFallen fallen
  69. choose) p
  70. onCardPlayed p card = execStateT (do
  71. onPlayed card) p
  72. chooseCardOpen p = evalStateT chooseOpen p
  73. value :: Card -> Int
  74. value (Card Ace _) = 100
  75. value _ = 0
  76. data Option = H Hand
  77. | Skt
  78. deriving (Show, Eq, Ord)
  79. -- | possible card distributions
  80. type Guess = M.Map Card [Option]
  81. newGuess :: Guess
  82. newGuess = M.fromList l
  83. where l = map (\c -> (c, [H Hand1, H Hand2, H Hand3, Skt])) allCards
  84. hasBeenPlayed :: Card -> Guess -> Guess
  85. hasBeenPlayed card = M.delete card
  86. has :: Hand -> [Card] -> Guess -> Guess
  87. has hand cs = M.mapWithKey f
  88. where f card hands
  89. | card `elem` cs = [H hand]
  90. | otherwise = hands
  91. hasNoLonger :: MonadPlayer m => Hand -> TurnColour -> AI m ()
  92. hasNoLonger hand colour = do
  93. trCol <- trump
  94. modifyg $ hasNoLonger_ trCol hand colour
  95. hasNoLonger_ :: Trump -> Hand -> TurnColour -> Guess -> Guess
  96. hasNoLonger_ trump hand effCol = M.mapWithKey f
  97. where f card hands
  98. | effectiveColour trump card == effCol && (H hand) `elem` hands = filter (/=H hand) hands
  99. | otherwise = hands
  100. isSkat :: [Card] -> Guess -> Guess
  101. isSkat cs = M.mapWithKey f
  102. where f card hands
  103. | card `elem` cs = [Skt]
  104. | otherwise = hands
  105. type Turn = (CardS Played, CardS Played, CardS Played)
  106. analyzeTurn :: MonadPlayer m => Turn -> AI m ()
  107. analyzeTurn (c1, c2, c3) = do
  108. modifyg (getCard c1 `hasBeenPlayed`)
  109. modifyg (getCard c2 `hasBeenPlayed`)
  110. modifyg (getCard c3 `hasBeenPlayed`)
  111. trCol <- trump
  112. let turnCol = getColour $ getCard c1
  113. demanded = effectiveColour trCol (getCard c1)
  114. col2 = effectiveColour trCol (getCard c2)
  115. col3 = effectiveColour trCol (getCard c3)
  116. if col2 /= demanded
  117. then uorigin (getPile c2) `hasNoLonger` demanded
  118. else return ()
  119. if col3 /= demanded
  120. then uorigin (getPile c3) `hasNoLonger` demanded
  121. else return ()
  122. type Distribution = ([Card], [Card], [Card], [Card])
  123. toPiles :: [CardS Played] -> Distribution -> Piles
  124. toPiles table (h1, h2, h3, skt) = makePiles h1 h2 h3 table skt
  125. compareGuess :: (Card, [Option]) -> (Card, [Option]) -> Ordering
  126. compareGuess (c1, ops1) (c2, ops2)
  127. | length ops1 == 1 = LT
  128. | length ops2 == 1 = GT
  129. | c1 > c2 = LT
  130. | c1 < c2 = GT
  131. distributions :: Guess -> (Int, Int, Int, Int) -> [Distribution]
  132. distributions guess nos =
  133. helper (sortBy compareGuess $ M.toList guess) nos
  134. `using` parList rdeepseq
  135. where helper [] _ = []
  136. helper ((c, hs):[]) ns = map fst (distr c hs ns)
  137. helper ((c, hs):gs) ns =
  138. let dsWithNs = distr c hs ns
  139. go (d, ns') = map (d <>) (helper gs ns')
  140. in concatMap go dsWithNs
  141. distr card hands (n1, n2, n3, n4) =
  142. let f card (H Hand1) =
  143. (([card], [], [], []), (n1+1, n2, n3, n4))
  144. f card (H Hand2) =
  145. (([], [card], [], []), (n1, n2+1, n3, n4))
  146. f card (H Hand3) =
  147. (([], [], [card], []), (n1, n2, n3+1, n4))
  148. f card Skt =
  149. (([], [], [], [card]), (n1, n2, n3, n4+1))
  150. isOk (H Hand1) = n1 < cardsPerHand
  151. isOk (H Hand2) = n2 < cardsPerHand
  152. isOk (H Hand3) = n3 < cardsPerHand
  153. isOk Skt = n4 < 2
  154. in filterMap isOk (f card) hands
  155. cardsPerHand = (length guess - 2) `div` 3
  156. type Abstract = (Int, Int, Int, Int)
  157. abstract :: [Card] -> Abstract
  158. abstract cs = foldr f (0, 0, 0, 0) cs
  159. where f c (clubs, spades, hearts, diamonds) =
  160. let v = getID c in
  161. case getColour c of
  162. Diamonds -> (clubs, spades, hearts, diamonds + 1 + v*100)
  163. Hearts -> (clubs, spades, hearts + 1 + v*100, diamonds)
  164. Spades -> (clubs, spades + 1 + v*100, hearts, diamonds)
  165. Clubs -> (clubs + 1 + v*100, spades, hearts, diamonds)
  166. remove789s :: Hand
  167. -> [Distribution]
  168. -> M.Map (Abstract, Abstract) (Distribution, Int)
  169. remove789s hand ds = foldl' f M.empty ds
  170. where f cleaned d =
  171. let (c1, c2) = reduce hand d
  172. a = (abstract c1, abstract c2) in
  173. M.insertWith (\(oldD, n) _ -> (oldD, n+1)) a (d, 1) cleaned
  174. reduce Hand1 (_, h2, h3, _) = (h2, h3)
  175. reduce Hand2 (h1, _, h3, _) = (h1, h3)
  176. reduce Hand3 (h1, h2, _, _) = (h1, h2)
  177. simplify :: Hand -> [Distribution] -> [(Distribution, Int)]
  178. simplify hand ds = M.elems cleaned
  179. where cleaned = remove789s hand ds
  180. onPlayed :: (MonadIO m, MonadPlayer m) => CardS Played -> AI m ()
  181. onPlayed c = do
  182. liftIO $ print c
  183. modifyg (getCard c `hasBeenPlayed`)
  184. trCol <- trump
  185. turnCol <- turnColour
  186. let col = effectiveColour trCol (getCard c)
  187. case turnCol of
  188. Just demanded -> if col /= demanded
  189. then uorigin (getPile c) `hasNoLonger` demanded else return ()
  190. Nothing -> return ()
  191. choose :: (MonadIO m, MonadPlayer m) => AI m Card
  192. choose = chooseStatistic
  193. chooseStatistic :: (MonadIO m, MonadPlayer m) => AI m Card
  194. chooseStatistic = do
  195. h <- gets getHand
  196. handCards <- gets myHand
  197. table <- gets table
  198. let tableNo = length table
  199. left = 3 - tableNo
  200. depth = case length handCards of
  201. 10 -> 3 + tableNo
  202. 9 -> 3 + tableNo
  203. 8 -> 3 + tableNo
  204. 7 -> 6 + tableNo
  205. 6 -> 9 + tableNo
  206. 5 -> 12 + tableNo
  207. 4 -> 15 + tableNo
  208. _ -> 100
  209. modify $ setDepth depth
  210. guess__ <- gets guess
  211. self <- get
  212. maySkat <- showSkat self
  213. let guess_ = (hand self `has` handCards) guess__
  214. guess = case maySkat of
  215. Just cs -> (cs `isSkat`) guess_
  216. Nothing -> guess_
  217. let ns = case tableNo of
  218. 0 -> (0, 0, 0, 0)
  219. 1 -> (-1, 0, -1, 0)
  220. 2 -> (0, 0, -1, 0)
  221. let realDis = distributions guess ns
  222. realDisNo = length realDis
  223. reducedDis = simplify Hand3 realDis
  224. reducedDisNo = length reducedDis
  225. piless = map (\(d, n) -> (toPiles table d, n)) reducedDis
  226. limit = min 10000 $ realDisNo `div` 2
  227. liftIO $ putStrLn $ "players hand" ++ show handCards
  228. liftIO $ putStrLn $ "possible distrs without simp " ++ show realDisNo
  229. liftIO $ putStrLn $ "possible distrs " ++ show reducedDisNo
  230. vals <- M.toList <$> foldWithLimit limit runOnPiles M.empty piless
  231. liftIO $ print vals
  232. return $ fst $ maximumBy (comparing snd) vals
  233. foldWithLimit :: Monad m
  234. => Int
  235. -> (M.Map k Int -> a -> m (M.Map k Int))
  236. -> M.Map k Int
  237. -> [a]
  238. -> m (M.Map k Int)
  239. foldWithLimit _ _ start [] = return start
  240. foldWithLimit limit f start (x:xs) = do
  241. case M.size (M.filter (>=limit) start) of
  242. 0 -> do m <- f start x
  243. foldWithLimit limit f m xs
  244. _ -> return start
  245. runOnPiles :: (MonadIO m, MonadPlayer m)
  246. => M.Map Card Int -> (Piles, Int) -> AI m (M.Map Card Int)
  247. runOnPiles m (ps, n) = do
  248. c <- runWithPiles ps chooseOpen
  249. return $ M.insertWith (+) c n m
  250. chooseOpen :: (MonadIO m, MonadState AIEnv m, MonadPlayerOpen m) => m Card
  251. chooseOpen = do
  252. piles <- showPiles
  253. hand <- gets getHand
  254. let myCards = handCards hand piles
  255. possible <- filterM (P.isAllowed myCards) myCards
  256. case length possible of
  257. 0 -> do
  258. liftIO $ print hand
  259. liftIO $ print piles
  260. error "no cards left to choose from"
  261. 1 -> return $ toCard $ head possible
  262. _ -> chooseSimulating
  263. chooseSimulating :: (MonadState AIEnv m, MonadPlayerOpen m)
  264. => m Card
  265. chooseSimulating = do
  266. piles <- showPiles
  267. turnCol <- turnColour
  268. trumpCol <- trump
  269. myHand <- gets getHand
  270. depth <- gets simulationDepth
  271. let ps = Players (PL $ Stupid.Stupid Team Hand1)
  272. (PL $ Stupid.Stupid Team Hand2)
  273. (PL $ Stupid.Stupid Single Hand3)
  274. -- TODO: fix
  275. env = mkSkatEnv piles turnCol undefined ps myHand undefined
  276. liftIO $ evalSkat (toCard <$> (Minmax.choose depth :: Skat (CardS Owner))) env
  277. simulate :: (MonadState AIEnv m, MonadPlayerOpen m)
  278. => Card -> m Int
  279. simulate card = do
  280. -- retrieve all relevant info
  281. piles <- showPiles
  282. turnCol <- turnColour
  283. trumpCol <- trump
  284. myTeam <- gets getTeam
  285. myHand <- gets getHand
  286. depth <- gets simulationDepth
  287. liftIO $ putStrLn $ "simulate: " ++ show myHand ++ " plays " ++ show card
  288. let newDepth = depth - 1
  289. -- create a virtual env with 3 ai players
  290. ps = Players
  291. (PL $ mkAIEnv Team Hand1 newDepth)
  292. (PL $ mkAIEnv Team Hand2 newDepth)
  293. (PL $ mkAIEnv Single Hand3 newDepth)
  294. -- TODO: fix
  295. env = mkSkatEnv piles turnCol undefined ps (next myHand) undefined
  296. -- simulate the game after playing the given card
  297. (sgl, tm) <- liftIO $ evalSkat (do
  298. modifyp $ playCard myHand card
  299. turnGeneric playOpen depth) env
  300. let v = if myTeam == Single then (sgl, tm) else (tm, sgl)
  301. -- put the value into context for when not the whole game is
  302. -- simulated
  303. predictValue v
  304. predictValue :: (MonadState AIEnv m, MonadPlayerOpen m)
  305. => (Int, Int) -> m Int
  306. predictValue (own, others) = do
  307. hand <- gets getHand
  308. piles <- showPiles
  309. let cs = handCards hand piles
  310. pot <- potential cs
  311. --return $ own + pot
  312. return (own-others)
  313. potential :: (MonadState AIEnv m, MonadPlayerOpen m, HasCard c)
  314. => [c] -> m Int
  315. potential cs = do
  316. tr <- trump
  317. let trs = filter (isTrump tr) cs
  318. value = count . map toCard $ cs
  319. positions <- filter (==0) <$> mapM (position . toCard) cs
  320. return $ length trs * 10 + value + length positions * 5
  321. position :: (MonadState AIEnv m, MonadPlayer m)
  322. => Card -> m Int
  323. position card = do
  324. tr <- trump
  325. guess <- gets guess
  326. let effCol = effectiveColour tr card
  327. l = M.toList guess
  328. cs = filterMap ((==effCol) . effectiveColour tr . fst) fst l
  329. csInd = zip [0..] (reverse cs)
  330. Just (pos, _) = find ((== card) . snd) csInd
  331. return pos
  332. leadPotential :: (MonadState AIEnv m, MonadPlayer m)
  333. => Card -> m Int
  334. leadPotential card = do
  335. pos <- position card
  336. isTr <- P.isTrump card
  337. let value = count card
  338. case pos of
  339. 0 -> return value
  340. _ -> return $ -value
  341. chooseLead :: (MonadIO m, MonadState AIEnv m, MonadPlayer m) => m Card
  342. chooseLead = do
  343. cards <- gets myHand
  344. possible <- filterM (P.isAllowed cards) cards
  345. liftIO $ putStrLn $ "choosing lead from " ++ show possible
  346. pots <- mapM leadPotential possible
  347. let ps = zip pots possible
  348. liftIO $ putStrLn $ "lead potential of cards " ++ show ps
  349. return $ snd $ maximumBy (comparing fst) ps
  350. mkAIEnv :: Team -> Hand -> Int -> AIEnv
  351. mkAIEnv tm h depth = AIEnv tm h [] [] [] newGuess depth
  352. -- | TESTING VARS
  353. aienv :: AIEnv
  354. aienv = AIEnv Single Hand3 [] [] [] newGuess 10
  355. testguess :: Guess
  356. testguess = isSkat (take 2 $ drop 10 cs)
  357. $ Hand3 `has` (take 10 cs) $ m
  358. where l = map (\c -> (c, [H Hand1, H Hand2, H Hand3, Skt])) (take 32 cs)
  359. m = M.fromList l
  360. cs = allCards
  361. testguess2 :: Guess
  362. testguess2 = isSkat (take 2 $ drop 6 cs)
  363. $ Hand3 `has` [head cs, head $ drop 5 cs] $ m
  364. where l = map (\c -> (c, [H Hand1, H Hand2, H Hand3, Skt])) cs
  365. m = M.fromList l
  366. cs = take 8 $ drop 8 allCards
  367. testds :: [Distribution]
  368. testds = distributions testguess (0, 0, 0, 0)
  369. testds2 :: [Distribution]
  370. testds2 = distributions testguess2 (0, 0, 0, 0)