11

Haskell で Magic The Gathering (MTG) ゲーム エンジンを作成しています。

MTGに馴染みのない方のために説明すると、カードが白(W)、青(U)、黒(B)、赤(R)、緑(G)の5色まで使用できるカードゲームです。

{-# LANGUAGE ViewPatterns #-}
import Data.Set

data Color = W | U | B | R | G
    deriving (Show, Eq, Ord)

data Card = Card (Set Color) -- simplified Card type with only its colors

viewColors :: Card -> [Color]
viewColors (Card colors) = toList colors

私がやりたいのは、次のような色のパターンマッチです:

foo :: Card -> String
foo (viewColors -> [W, B]) = "card is white and black"
foo _ = "whatever"

ここまでは順調ですね。しかし、ここで 1 つの問題があります。次のように、ビュー パターンで色の順序を間違って入力できます。

bar :: Card -> String
bar (viewColors -> [B, W]) = "this will never get hit"
bar _ = "whatever"

もちろん、viewColorsこの問題を直接解決する方法で書くこともできました。または、ガードを使用することもできますが、使用したくありません。これを行うにはいくつかの方法があります

viewColors :: Card -> (Bool, Bool, Bool, Bool, Bool)
viewColors (Card colors) = let m = (`member` colors)
    in (m W, m U, m B, m R, m G)

このソリューションは、型の同形を使用しているBoolが、より短い (および/または意味のある) 識別子を使用している場合でも、パターン マッチング中は非常に冗長です。グリーンカードのマッチングは次のようになります

baz :: Card -> String
baz (viewColors -> (False, False, False, False, True)) = "it's green"

data ColorView = W | WU | WUB | ... all combos here

viewColors :: Card -> ColorView
viewColors (Card colors) = extract correct Colorview from colors

このソリューションには組み合わせ爆発があります。実装するのは非常に悪いようですが、特にcolorViewToList :: ColorView -> [Color]パターンマッチ後にプログラムによる抽出を許可する場合は、使用すると便利です。


以下が Haskell で近似できるかどうかはわかりませんが、以下が理想的です。

fuz :: Card -> String
fuz (viewColors -> (W :* ())) = "it's white"
fuz (viewColors -> (W :* U :* ())) = "it's white and blue"
fuz (viewColors -> (W :* B :* ())) = "it's white and black"

この種のコードを許可するために、DataKinds、PolyKinds、TypeFamilies、MultiParamTypeClasses、GADT などの高度な言語拡張を喜んで使用します。

このようなことは可能ですか?他に提案されたアプローチはありますか?

4

5 に答える 5

3

レコード ソリューションが好きですが、型クラスを使用すると簡単に実行できます

{-# LANGUAGE ViewPatterns, ScopedTypeVariables #-}

import qualified Data.Set as Set

data Color = W' | U' | B' | R' | G' deriving (Show, Eq, Ord)
data Card = Card (Set.Set Color) 

newtype W a = W a
newtype U a = U a
newtype B a = B a
newtype R a = R a
newtype G a = G a

class ToColors x where
  toColors :: x -> [Color]
  reify :: x

instance ToColors () where
  toColors _ = []
  reify = ()

instance ToColors a => ToColors (W a) where
  toColors (W a) = W':toColors a
  reify = W reify

--other instances

members :: Set.Set Color -> [Color] -> Bool
members s = foldl (\b e -> b && (Set.member e s)) True

viewColors :: forall a. ToColors a => Card -> Maybe a
viewColors (Card s) = let a = reify :: a in 
  if members s (toColors a) then (Just a) else Nothing

foo :: Card -> String
foo (viewColors -> Just (W (B ()))) = "card is white and black"
foo _ = "whatever"

これは、他の構文を取得するために簡単に作り直すことができます。同様に、色をパラメーターを取らない型として定義してから、中置異種リスト コンストラクターを使用することもできます。いずれにせよ、順序は気にしません。

編集:簡単な正確なセットを一致させたい場合は、members関数を次のように置き換えるだけです

viewColors :: forall a. ToColors a => Card -> Maybe a
viewColors (Card s) = let a = reify :: a in 
  if s == (Set.fromList . toColors $ a) then (Just a) else Nothing
于 2013-09-24T22:23:24.320 に答える
2

編集: さらにテストすると、このソリューションは実際には機能しないことが示されています。


実際にはこれ以上の拡張機能は必要ありません。私はあなたが望むことを行うソリューションを思いつきましたが、おそらくそれを最適化し、いくつかの名前を変更し、少し見苦しくないようにしたいと思うでしょう. 新しいデータ型をEq作成して自分で実装し、演算子を使用させるだけですinfixr:

{-# LANGUAGE ViewPatterns #-}
import Data.Set

data Color = W | U | B | R | G
    deriving (Show, Eq, Ord)

data Card = Card (Set Color) -- simplified Card type with only its colors

-- you may need to fiddle with the precedence here
infixr 0 :*
data MyList a = END | a :* (MyList a) deriving (Show)

myFromList :: [a] -> MyList a
myFromList [] = END
myFromList (x:xs) = x :* myFromList xs

instance Eq a => Eq (MyList a) where
    END == END = True
    END == _   = False
    _   == END = False
    l1  == l2  = allElem l1 l2 && allElem l2 l1
        where
            -- optimize this, otherwise it'll just be really slow
            -- I was just too lazy to write it correctly
            elemMyList :: Eq a => a -> MyList a -> Bool
            elemMyList a ml = case ml of
                END -> False
                (h :* rest) -> if a == h then True else elemMyList a rest
            allElem :: Eq a => MyList a -> MyList a -> Bool
            allElem END l = True
            allElem (h :* rest) l = h `elemMyList` l && allElem rest l

viewColors :: Card -> MyList Color
viewColors (Card colors) = myFromList $ toList colors

fuz :: Card -> String
fuz (viewColors -> (W :* END)) = "it's white"
fuz (viewColors -> (W :* U :* END)) = "it's white and blue"
fuz (viewColors -> (W :* B :* END)) = "it's white and black"
fuz (viewColors -> (W :* B :* R :* END)) = "it's white, black, and red"
fuz (viewColors -> (W :* U :* B :* R :* G :* END)) = "it's all colors"
fuz _ = "I don't know all my colors"

main = do
    putStrLn $ fuz $ Card $ fromList [W, B]
    putStrLn $ fuz $ Card $ fromList [B, W]

編集:コードを少し修正しました

于 2013-09-24T19:48:24.243 に答える
0

最初にカードの色がどうなるかを正確に表現することに集中し、その後で物事を簡潔にするなどの他の懸念事項について心配する必要があると思います。あなたのBoolタプル ソリューションはほぼ完璧に思えますが、カードには 1 つの色が必要だと思いますよね?

その場合、次のようなものが機能する可能性があり、パターン マッチが非常に簡単です。

data CardColors = W' BlackBool GreenBool ...
                | B' WhiteBool GreenBool ...
                | G' BlackBool WhiteBool ...
                ....

data BlackBool = B 
               | NotB
-- etc.

順序が定義された異質なリストをかなり簡単に作成できますが、その種のポリモーフィズムはここでは役立たないと思います。

于 2013-09-24T20:16:02.650 に答える
0

(あなたの質問への回答ではありませんが、問題の解決策になることを願っています!)

私はおそらくうまくいく可能性のある最もばかげたものを使います:

is :: Card -> Color -> Bool
is card col = col `elem` (viewColors card) -- can be optimized to use the proper elem!

その後

foo :: Card -> String
foo c
    | c `is` B && c `is` W = "card is black and white"
    | c `is` R || c `is` G = "card is red or green"
    | otherwise = "whatever"

カードが 5 色すべてを持っているかどうかを確認するためにリスト全体を綴るのは長すぎる場合は、次のような追加のコンビネータを定義できます。

hasColors :: Card -> [Color] -> Bool
hasColors card = all (`elem` (viewColors card))

これが受け入れられない理由はありますか?

于 2013-09-25T11:40:47.347 に答える