hannah/src/AI.hs

99 lines
3.3 KiB
Haskell

{-# LANGUAGE NamedFieldPuns #-}
module AI (
move
) where
import Data.List (sortOn)
import Data.Map ((!), delete, findMin)
import Data.Ord (Down(..))
import Data.Set (member)
import qualified Data.Set as Set (fromList)
import Hanafuda (
Card(..), Flower(..), Pack
, cardsOf, cardsOfPack, contains, empty,flower, packOfCards, sameMonth, size, union
)
import Hanafuda.KoiKoi (Move(..), PlayerID, Step(..))
import Hanafuda.Message (PublicGame(..), PublicState(..), PublicPlayer)
import qualified Hanafuda.Message as Message (Coordinates(..))
move :: PlayerID -> PublicGame -> Move
move me (PublicGame {playerHand, public = PublicState {coordinates, step = ToPlay, players, river}}) =
case getAvailableCards hand (cardsOfPack river) of
[] -> Play $ worstFor month opponent hand
available ->
let riverCard = bestFor month (players ! me) available in
let matchingCards = cardsOfPack . sameMonth riverCard $ playerHand in
capture (bestFor month (players ! me) matchingCards) riverCard river
where
month = Message.month $ coordinates
hand = cardsOfPack playerHand
opponent = snd . findMin $ delete me players
move me (PublicGame {public = PublicState {coordinates, step = Turned card, players, river}}) =
Choose . bestFor month (players ! me) . cardsOfPack $ sameMonth card river
where
month = Message.month $ coordinates
move _ (PublicGame {playerHand, public = PublicState {step = Scored}}) =
KoiKoi $ playerHand /= empty
move _ _ = error "Nothing to play on ended game"
capture :: Card -> Card -> Pack -> Move
capture card caught river =
if size (sameMonth card river) == 1
then Play card
else Capture (card, caught)
getAvailableCards :: [Card] -> [Card] -> [Card]
getAvailableCards hand =
filter ((`member` flowersInHand) . flower)
where
flowersInHand = Set.fromList $ flower <$> hand
choose :: Ord a => (Card -> a) -> [Card] -> Card
choose sortCriterion = head . sortOn sortCriterion
bestFor :: Flower -> PublicPlayer -> [Card] -> Card
bestFor monthFlower player = choose (Down . rank monthFlower player)
worstFor :: Flower -> PublicPlayer -> [Card] -> Card
worstFor monthFlower player = choose (rank monthFlower player)
rank :: Flower -> PublicPlayer -> Card -> Int
rank monthFlower _ card
| isTrueLight card = 5
| card == RainMan = 2
| isSpecialRibbon card = 4
| isRibbon card = 3
| isSpecialAnimal card = 4
| isAnimal card = 3
| isMonthly monthFlower card = 4
| otherwise = 1
isTrueLight :: Card -> Bool
isTrueLight = (packOfCards [Crane, CampCurtain, FullMoon, Phoenix] `contains`)
isSpecialRibbon :: Card -> Bool
isSpecialRibbon = ((aotan `union` akatan) `contains`)
where
aotan = packOfCards [PeonyBlue, ChrysanthemumBlue, MapleBlue]
akatan = packOfCards [PinePoetry, PlumPoetry, CherryPoetry]
isRibbon :: Card -> Bool
isRibbon card = isSpecialRibbon card || packOfCards other `contains` card
where
other = [WisteriaRed, IrisRed, BushCloverRed, WillowRed]
isSpecialAnimal :: Card -> Bool
isSpecialAnimal = (packOfCards [Butterflies, Boar, SakeCup, Deer] `contains`)
isAnimal :: Card -> Bool
isAnimal card = (isSpecialAnimal card) || (packOfCards other `contains` card)
where
other = [BushWarbler, Cuckoo, EightPlankBridge, Geese, Swallow]
isMonthly :: Flower -> Card -> Bool
isMonthly monthFlower = (cardsOf monthFlower `contains`)