Skat Engine und AI auf Haskell Basis
Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

430 Zeilen
14KB

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