Haskellでハトクラを表現する

この記事はHaskell Advent Calendar 17日目の記事です。

What is ハトクラ

ハトクラとは、ドミニオンの変種みたいなカードゲームです。
ただし、カードの連結が続く限りカードをプレイできるというルールが加わっています。

f:id:haru2036:20141202231210p:plain

こんな感じ
……
ん……?連結……?

これってモナドになりませんか?(これってトリビアになりませんか?的ノリ)

そこで、今回はハトクラの一部(アクションフェーズ, またはメインフェーズ)を表現するモナドを作ってみようと思います。

やってみる

これはおそらくStateモナドの変種みたいな感じで行けるのではと思いました。ただ、一番後ろのカードに連結マークがない(=もう連結できない)状態をどうにかして表現しないといけません。
まずはなんかと合成できないかと考えてみたりしてたのですが、何と合成しようかなあと考えてました。


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って全体的にただ使うのはらくらくだけど理解するのは大変ですね……

#ssmjp 2014/10に参加してきました

ssmjp 2014/10に参加してきました。 第一部では運用とセキュリティ関連のディスカッションで、そうそうたる人たちがパネラーとして参加されていました(あんまり良く知らない界隈なのに名前知ってる時点でそうそうたる人たちだなあと) まあ、セキュリティ関連のかなり戦い的な話だったのですが、AWSの話もあり。AWSってアレちゃんと責任の所在までしっかりされているんですな。
使えるようになっとくべきかもと思いました。
第二部ではLTだったのですが、僕も唯一の本物の普通の大学生としてLTさせていただきました。
他の自称普通の大学生たちはバケモノばかりで恐ろしかった……
内容的にはあんまりうまくいかなかったので微妙だったけど、今度は係り受け解析とかも含めたいなと。

スライド。

参加された方、主催のみなさま、会場提供してくださったGMOの皆様、本当にありがとうございました。
ConoHaのクーポンで何をしようか考えている

OSXでText.RegexにUTF8な文字列使うとエラーでるっぽい

regex - Matching specific unicode char in haskell regexp - Stack Overflow

みたいです。実はこれと同じような状態で、Localeだけja_jp.UTF-8だけどUTF-8担ってるからいいのかなあと思ってたけどダメだった。 今回は特定の文字で文字列を分解するためだけに使ってたので諦めてsplitパッケージを使いました。 これから使うような状況になったら困るから解決法を調べたい

OSXでWord2Vec

OSXでWord2Vecを使おうとするとおかしなことになったのでメモ。 まずmalloc.hがないと言われるのでmalloc.hをstdlib.hに置換。 そいでmakeしてdemo-analogyを実行するとなんかターミナルがおかしくなります。 なんでだろうと思って色々やっていたらダウンロードしたコーパスgzipを展開するところで起こっているみたいだったので別の方法で展開して直接word2vecを実行したら普通に動きました。 とってもしょーもない落ちでした……

OSXでHaskellとMeCab

ハマったのでメモ。(割りとどうでも良い落ちだったのでアレ)

困った

環境: OSX10.9.4 + GHC7.8.2 + MeCab0.996(HomeBrewでインストール) + cabal1.20.0.2 (問題のプロジェクトはCabal sandboxにインストールしています。)
発生した問題: mecabを使ったプロジェクトでcabal install するとリンク中にエラーが発生する

Linking dist/dist-sandbox-e0def0a9/build/command-line/command-line ...
Undefined symbols for architecture x86_64:
  "_mecab_get_all_morphs", referenced from:
      _cfSP_info in libHSmecab-0.4.0.a(MeCab.o)
  "_mecab_get_lattice_level", referenced from:
      _cfFl_info in libHSmecab-0.4.0.a(MeCab.o)
  "_mecab_get_partial", referenced from:
      _cfW9_info in libHSmecab-0.4.0.a(MeCab.o)
  "_mecab_get_theta", referenced from:
      _cfCP_info in libHSmecab-0.4.0.a(MeCab.o)
  "_mecab_set_all_morphs", referenced from:
      _cgo1_info in libHSmecab-0.4.0.a(MeCab.o)
  "_mecab_set_lattice_level", referenced from:
      _cfXT_info in libHSmecab-0.4.0.a(MeCab.o)
  "_mecab_set_partial", referenced from:
      _cfU5_info in libHSmecab-0.4.0.a(MeCab.o)
  "_mecab_set_theta", referenced from:
      _cfEc_info in libHSmecab-0.4.0.a(MeCab.o)
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
cabal: Error: some packages failed to install:

このエラーがいまいちよくわかってないのでなんとも言えないのですが、いろいろやっているうちにHaskellMeCabパッケージにあるversionを使ってみると0.97が帰ってきて、意図的にインストールした0.996とは違うのでどこかに別のMeCabがあるのではないかと思っています

なおった

cabal installするときに、使いたいMeCabのあるディレクトリを渡してあげないとダメなようです。とりあえずこれで解決しました。

cabal install mecab --extra-include-dirs=/usr/local/Cellar/mecab/0.996/include/ --extra-lib-dirs=/usr/local/Cellar/mecab/0.996/lib/

これで動作するようになってからversionを見たら0.996となっていたので、どこかに前なんかやった時のゴミが残っててそれを見に行ってしまってたようですが、そのごみがどこにあるのかわからないので気持ち悪いです。 ひとまず解決。よかった。

追記:

実際謎なので気持ち悪さが残りまくる。
追記2:まとめ

まとめ

FFIを使って他の言語のコードをリンクするようなプロジェクトの時はできるだけextra-lib-dirsとか渡したほうが面倒は少ないかもね(なげやり)

Yesodで部員管理用Webサービス作った

作ったのは結構前のことですが、書くのがいまさらになったのでメモ。
Yesodはじめてみました - haru2036のブログ
でYesodが意外としっくりきたことを書きましたが、そのままYesodで行ってみました。
私の部活はソフトウェア研究部とかいう名前のくせに部員の管理はExcelでやるとかいうとってもしょぼい感じだったので、それよりはマシかと……部員の登録から所属に合わせたメールの一括送信までを今のところサポートしています。
リポジトリはこちら:
haru2036/Member-Manager · GitHub