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.

284 lines
11KB

  1. {-# LANGUAGE OverloadedStrings #-}
  2. module Skat.Bidding (
  3. biddingScore, Game(..), Modifier(..), isHand, getTrump, Result(..),
  4. getResults, isOuvert, isSchwarz, Bid, checkGame, HideGame(..)
  5. ) where
  6. import Data.Aeson hiding (Null, Result)
  7. import Skat.Card
  8. import Data.List (sortOn)
  9. import Data.Ord (Down(..))
  10. import Control.Monad
  11. import Skat.Pile
  12. type Bid = Int
  13. -- | different game types
  14. data Game = Colour Colour Modifier
  15. | Grand Modifier
  16. | Null
  17. | NullHand
  18. | NullOuvert
  19. | NullOuvertHand
  20. deriving (Show, Eq)
  21. newtype HideGame = HideGame Game
  22. deriving (Show, Eq)
  23. instance ToJSON Game where
  24. toJSON (Grand mod) =
  25. object ["game" .= ("grand" :: String), "modifier" .= show mod]
  26. toJSON (Colour col mod) =
  27. object ["game" .= ("colour" :: String), "modifier" .= show mod, "colour" .= show col]
  28. toJSON Null = object ["game" .= ("null" :: String)]
  29. toJSON NullHand = object ["game" .= ("nullhand" :: String)]
  30. toJSON NullOuvert = object ["game" .= ("nullouvert" :: String)]
  31. toJSON NullOuvertHand = object ["game" .= ("nullouverthand" :: String)]
  32. instance ToJSON HideGame where
  33. toJSON (HideGame (Grand mod)) =
  34. object ["game" .= ("grand" :: String), "modifier" .= prettyShow mod]
  35. toJSON (HideGame (Colour col mod)) =
  36. object ["game" .= ("colour" :: String), "modifier" .= prettyShow mod, "colour" .= show col]
  37. toJSON (HideGame game) = toJSON game
  38. instance FromJSON Game where
  39. parseJSON = withObject "Game" $ \v -> do
  40. gamekind <- v .: "game"
  41. case (gamekind :: String) of
  42. "colour" -> do
  43. col <- v .: "colour"
  44. mod <- v .: "modifier"
  45. return $ Colour (read col) mod
  46. "grand" -> do
  47. mod <- v .: "modifier"
  48. return $ Grand mod
  49. "null" -> return Null
  50. "nullhand" -> return NullHand
  51. "nullouvert" -> return NullOuvert
  52. "nullouverthand" -> return NullOuvertHand
  53. _ -> mzero
  54. -- | modifiers for grand and colour games
  55. data Modifier = Einfach
  56. | Schneider
  57. | Schwarz
  58. | Hand
  59. | HandSchneider
  60. | HandSchneiderAngesagt
  61. | HandSchwarz
  62. | HandSchneiderAngesagtSchwarz
  63. | HandSchwarzAngesagt
  64. | Ouvert
  65. deriving (Show, Eq)
  66. instance FromJSON Modifier where
  67. parseJSON = withObject "Modifier" $ \v -> do
  68. hnd <- v .: "hand"
  69. if hnd then do
  70. schneider <- v .:? "schneider" .!= False
  71. schwarz <- v .:? "schwarz" .!= False
  72. ouvert <- v .:? "ouvert" .!= False
  73. case (schneider, schwarz, ouvert) of
  74. (_, _, True) -> return Ouvert
  75. (True, False, _) -> return HandSchneiderAngesagt
  76. (_, True, _) -> return HandSchwarzAngesagt
  77. _ -> return Hand
  78. else return Einfach
  79. prettyShow :: Modifier -> String
  80. prettyShow Schneider = show Einfach
  81. prettyShow Schwarz = show Einfach
  82. prettyShow HandSchneider = show Hand
  83. prettyShow HandSchwarz = show Hand
  84. prettyShow HandSchneiderAngesagtSchwarz = show HandSchneiderAngesagt
  85. prettyShow mod = show mod
  86. isHand :: Game -> Bool
  87. isHand NullHand = True
  88. isHand NullOuvertHand = True
  89. isHand (Colour _ mod) = modIsHand mod
  90. isHand (Grand mod) = modIsHand mod
  91. isHand _ = False
  92. modIsHand :: Modifier -> Bool
  93. modIsHand Einfach = False
  94. modIsHand Schneider = False
  95. modIsHand Schwarz = False
  96. modIsHand _ = True
  97. isOuvert :: Game -> Bool
  98. isOuvert NullOuvert = True
  99. isOuvert NullOuvertHand = True
  100. isOuvert (Grand Ouvert) = True
  101. isOuvert (Colour _ Ouvert) = True
  102. isOuvert _ = False
  103. baseFactor :: Game -> Int
  104. baseFactor (Grand _) = 24
  105. baseFactor (Colour Clubs _) = 12
  106. baseFactor (Colour Spades _) = 11
  107. baseFactor (Colour Hearts _) = 10
  108. baseFactor (Colour Diamonds _) = 9
  109. baseFactor Null = 23
  110. baseFactor NullHand = 35
  111. baseFactor NullOuvert = 46
  112. baseFactor NullOuvertHand = 59
  113. -- | calculate the value of a game with given cards
  114. biddingScore :: HasCard c => Game -> [c] -> Int
  115. biddingScore game@(Grand mod) cards = (spitzen game cards + modifierFactor mod) * 24
  116. biddingScore game@(Colour Clubs mod) cards = (spitzen game cards + modifierFactor mod) * 12
  117. biddingScore game@(Colour Spades mod) cards = (spitzen game cards + modifierFactor mod) * 11
  118. biddingScore game@(Colour Hearts mod) cards = (spitzen game cards + modifierFactor mod) * 10
  119. biddingScore game@(Colour Diamonds mod) cards = (spitzen game cards + modifierFactor mod) * 9
  120. biddingScore game _ = baseFactor game
  121. -- | calculate the modifier based on the game kind
  122. modifierFactor :: Modifier -> Int
  123. modifierFactor Einfach = 1
  124. modifierFactor Schneider = 2
  125. modifierFactor Schwarz = 3
  126. modifierFactor Hand = 2
  127. modifierFactor HandSchneider = 3
  128. modifierFactor HandSchneiderAngesagt = 4
  129. modifierFactor HandSchwarz = 4
  130. modifierFactor HandSchneiderAngesagtSchwarz = 5
  131. modifierFactor HandSchwarzAngesagt = 6
  132. modifierFactor Ouvert = 7
  133. -- | get all available trumps for a given game
  134. allTrumps :: Game -> [Card]
  135. allTrumps (Grand _) = jacks
  136. allTrumps (Colour col _) = jacks ++ [Card t col | t <- [Ace,Ten .. Seven] ]
  137. allTrumps _ = []
  138. jacks :: [Card]
  139. jacks = [ Card Jack Clubs, Card Jack Spades, Card Jack Hearts, Card Jack Diamonds ]
  140. -- | calculate the spitzen count
  141. spitzen :: HasCard c => Game -> [c] -> Int
  142. spitzen game cards
  143. | null trumps = length $ allTrumps game
  144. | mit = foldl (\val (a, o) -> if a == o then val + 1 else val) 0 zipped
  145. | otherwise = findOhne (allTrumps game) 0
  146. where trumps = getTrumps game cards
  147. zipped = zip (allTrumps game) trumps
  148. mit = Card Jack Clubs == head trumps
  149. findOhne [] acc = acc
  150. findOhne (c:cs) acc = if c /= highest then findOhne cs (acc+1) else acc
  151. highest = head trumps
  152. -- | get all trumps for a given game out of a hand of cards
  153. getTrumps :: HasCard c => Game -> [c] -> [Card]
  154. getTrumps (Grand _) cards = sortOn Down $ filter (isTrump Jacks) $ map toCard cards
  155. getTrumps (Colour col _) cards = sortOn Down $ filter (isTrump $ TrumpColour col) $ map toCard cards
  156. getTrumps _ _ = []
  157. -- | get trump for a given game
  158. getTrump :: Game -> Trump
  159. getTrump (Colour col _) = TrumpColour col
  160. getTrump (Grand _) = Jacks
  161. getTrump _ = None
  162. data Result = Result { resultGame :: Game
  163. , resultScore :: Int
  164. , resultSinglePoints :: Int
  165. , resultTeamPoints :: Int }
  166. deriving (Show, Eq)
  167. instance ToJSON Result where
  168. toJSON (Result game points sgl tm) =
  169. object ["game" .= game, "points" .= points, "single" .= sgl, "team" .= tm]
  170. isSchwarz :: Team -> Piles -> Bool
  171. isSchwarz tm = null . wonCards tm
  172. hasWon :: Game -> Piles -> (Bool, Game)
  173. hasWon Null ps = (Single `isSchwarz` ps, Null)
  174. hasWon NullHand ps = (Single `isSchwarz` ps, NullHand)
  175. hasWon NullOuvert ps = (Single `isSchwarz` ps, NullOuvert)
  176. hasWon NullOuvertHand ps = (Single `isSchwarz` ps, NullOuvertHand)
  177. hasWon (Colour col mod) ps = let (b, mod') = meetsCall mod ps
  178. in (b, Colour col mod')
  179. hasWon (Grand mod) ps = let (b, mod') = meetsCall mod ps
  180. in (b, Grand mod')
  181. meetsCall :: Modifier -> Piles -> (Bool, Modifier)
  182. meetsCall Hand ps = case wonByPoints ps of
  183. (b, Schneider) -> (b, HandSchneider)
  184. (b, Schwarz) -> (b, HandSchwarz)
  185. (b, Einfach) -> (b, Hand)
  186. meetsCall Schneider ps = case wonByPoints ps of
  187. (b, Schneider) -> (b, Schneider)
  188. (b, Schwarz) -> (b, Schwarz)
  189. (b, Einfach) -> (False, Schneider)
  190. meetsCall Schwarz ps = case wonByPoints ps of
  191. (b, Schneider) -> (False, Schwarz)
  192. (b, Schwarz) -> (b, Schwarz)
  193. (b, Einfach) -> (False, Schwarz)
  194. meetsCall HandSchneider ps = case wonByPoints ps of
  195. (b, Schneider) -> (b, HandSchneider)
  196. (b, Schwarz) -> (b, HandSchwarz)
  197. (b, Einfach) -> (False, HandSchneider)
  198. meetsCall HandSchneiderAngesagt ps = case wonByPoints ps of
  199. (b, Schneider) -> (b, HandSchneiderAngesagt)
  200. (b, Schwarz) -> (b, HandSchneiderAngesagtSchwarz)
  201. (b, Einfach) -> (False, HandSchneiderAngesagt)
  202. meetsCall HandSchwarz ps = case wonByPoints ps of
  203. (b, Schneider) -> (False, HandSchwarz)
  204. (b, Schwarz) -> (b, HandSchwarz)
  205. (b, Einfach) -> (False, HandSchwarz)
  206. meetsCall HandSchwarzAngesagt ps = case wonByPoints ps of
  207. (b, Schneider) -> (False, HandSchwarzAngesagt)
  208. (b, Schwarz) -> (b, HandSchwarzAngesagt)
  209. (b, Einfach) -> (False, HandSchwarzAngesagt)
  210. meetsCall Ouvert ps = case wonByPoints ps of
  211. (b, Schneider) -> (False, Ouvert)
  212. (b, Schwarz) -> (b, Ouvert)
  213. (b, Einfach) -> (False, Ouvert)
  214. meetsCall _ ps = wonByPoints ps
  215. wonByPoints :: Piles -> (Bool, Modifier)
  216. wonByPoints ps
  217. | Team `isSchwarz` ps = (True, Schwarz)
  218. | sgl >= 90 = (True, Schneider)
  219. | Single `isSchwarz` ps = (False, Schwarz)
  220. | sgl <= 30 = (False, Schneider)
  221. | otherwise = (sgl > 60, Einfach)
  222. where (sgl, _) = count ps :: (Int, Int)
  223. -- | get result of game
  224. getResults :: Game -> Bid -> Hand -> Piles -> Piles -> Result
  225. getResults game bid sglPlayer before after = case checkGame bid hand game of
  226. Just game' -> let (won, afterGame) = hasWon game' after
  227. gameScore = biddingScore afterGame hand
  228. score = if won then gameScore else (-2) * gameScore
  229. in Result afterGame score sglPoints teamPoints
  230. Nothing -> let gameScore = baseFactor game * ceiling (fromIntegral bid / fromIntegral (baseFactor game))
  231. score = (-2) * gameScore
  232. in Result game score sglPoints teamPoints
  233. where hand = skatCards before ++ (map toCard $ handCards sglPlayer before)
  234. (sglPoints, teamPoints) = count after
  235. checkGame :: HasCard c => Bid -> [c] -> Game -> Maybe Game
  236. checkGame bid cards game@(Colour col mod)
  237. | biddingScore game cards >= bid = Just game
  238. | otherwise = upgrade mod >>= \mod' -> checkGame bid cards (Colour col mod')
  239. checkGame bid cards game@(Grand mod)
  240. | biddingScore game cards >= bid = Just game
  241. | otherwise = upgrade mod >>= \mod' -> checkGame bid cards (Grand mod')
  242. checkGame bid cards game
  243. | biddingScore game cards >= bid = Just game
  244. | otherwise = Nothing
  245. upgrade :: Modifier -> Maybe Modifier
  246. upgrade Einfach = Just Schneider
  247. upgrade Schneider = Just Schwarz
  248. upgrade Hand = Just HandSchneider
  249. upgrade HandSchneider = Just HandSchwarz
  250. upgrade HandSchneiderAngesagt = Just HandSchneiderAngesagtSchwarz
  251. upgrade _ = Nothing