99 lines
3.3 KiB
Haskell
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`)
|