Skat Engine und AI auf Haskell Basis
Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

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