Haskellでハトクラを表現する
この記事はHaskell Advent Calendar 17日目の記事です。
What is ハトクラ
ハトクラとは、ドミニオンの変種みたいなカードゲームです。
ただし、カードの連結が続く限りカードをプレイできるというルールが加わっています。
こんな感じ
……
ん……?連結……?
これってモナドになりませんか?(これってトリビアになりませんか?的ノリ)
そこで、今回はハトクラの一部(アクションフェーズ, またはメインフェーズ)を表現するモナドを作ってみようと思います。
やってみる
これはおそらくStateモナドの変種みたいな感じで行けるのではと思いました。ただ、一番後ろのカードに連結マークがない(=もう連結できない)状態をどうにかして表現しないといけません。
まずはなんかと合成できないかと考えてみたりしてたのですが、何と合成しようかなあと考えてました。
@haru2036 残りアクションのカウンタがマイナスになった時点で残りの計算をしなくなるような仕組みが良いんじゃないかなぁ、EitherとStateを変換子で合成するとか。
— ちゅーん (@its_out_of_tune) November 17, 2014
EitherとStateならLeftでも値が持てるけどそのあとの計算がされなくなるのでピッタリですね。ありがとうございます! 一般的には失敗した時の情報とかを持たせるEitherですが、そんな使い方もあるとは。
というわけで、アクションフェーズの型を考えてみました。
data Field = Field { inheritanceRights :: Int, hand :: Cards, deck :: Cards, trash :: Cards } type Cards = [Card] type Card = Coins -> ActionPhase type Coins = Int type ActionPhase = StateT Field (Either (Coins, Field)) Coins
StateTとEitherを合成しました。Fieldはゲーム内の自分に関する大域的な状態で、Coinsは利用できるコインの枚数です。
ただ、なんかこれだと定義がごちゃごちゃしてる気がしてちょっとイケてないなと思ったので、StateとEitherTを使ったバージョンも書いてみたのですが、あんまりどっちもイケてなかったので使いやすそうなこっちのバージョンを採用しました。
とか全然間違ってた事を考えてたのですが、ツッコミをもらえて助かりました。
理解が遅くて申し訳ない、ありがとうございます。
それも含めた版がこちら:
type ActionPhase = EitherT Coins (State Field) Coins
一部のカードを実装してみる
さて、幾つか実際のゲームに登場するカードを実装して試してみましょう。
まずはこれがなきゃ始まらない農園とか都市とか。
farm :: Card farm c = do return (c + 1)
順当 and 単純。当たり前やがなッて感じです。
あとは、コレ単体では存在しませんがよく出てくる次にカードが繋げない終端のカードです。
stop :: Card stop c = do fld <- (lift get) left c
EitherTの機能を使ってその時点での状態をLeftにくるんで返すだけです。 単純です。
他には個人的に好きなカード、錬金術士とかもよいですね。
alchemist :: Card alchemist c = do fld <- lift get lift $ put $ drawCards 2 fld return c
addHands :: Field -> Cards -> Field addHands f cds = setHands f ((f ^. hand) ++ cds) setHands :: Field -> Cards -> Field setHands f cds = f & hand .~ cds drawCard :: Field -> Field drawCard f = let card = head (f^.deck) deckTail = tail (f^.deck) newHand = card : (f^.hand) in (f&hand .~ newHand)&deck .~ deckTail drawCards :: Int -> Field -> Field drawCards count f | count > 0 = drawCard $ drawCards (count - 1) f | count == 0 = f | count < 0 = error "count should be > 0"
2枚山札から手札に引く事ができるカードです。 これまでFieldを触る処理がなかったので全然書きませんでしたが、Lensは更新を簡単に出来てよいですね、なお、今回の処理に使うには本家はでかすぎるのでちゅーんさんのLensを使わせていただきました。
Haskellまだまだ修行の身な上時間が少なくやっつけ気味になってしまいましたが、とりあえずこんなかんじでハトクラをモナドにできそう、という感じはもてました。
ただし、二股になるのとかは考慮していないので完全にはまだまだ程遠いですね…… ちなみに、githubにリポジトリあります。需要全くなさそうだし適当ですが。
haru2036/heart-of-crown-monad · GitHub
余談: Stateモナドの仕組みを理解してから、それを使うためには仕組みまで理解することがないのを知りました。 普通に楽しかったし興味深い仕組みにより実現されてることを知れたのでよいですが、Haskellって全体的にただ使うのはらくらくだけど理解するのは大変ですね……