Skat Engine und AI auf Haskell Basis
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

434 lignes
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)