パラボラアンテナと星の日記

あることないこと

麻雀のあがり判定をゼロから書いて、天和コマンドを作ってみた

【車輪】麻雀のあがり判定をゼロから書いて、天和コマンドを作ってみた感想【再発明】

プログラミングの話ですが、麻雀の話です。

$ tenho ってコマンドを打つと天和がアガれたら面白いんじゃないかと思って、作った

うごくやつができた

こんなん

f:id:hoppie:20151231170016g:plain

なぜそんなことを

ruby -e "puts '🀀'.next"
=> "🀁"

みたいに文字が使えるし、CLIでも見栄えが良いかなと思ってた。

休日の車輪の再発明にはちょうど良さそう。

ロジックの方はおみくじ感覚でかんたんにできるかなと思ってた。

が、あがり判定っていうのはわりと難易度高かった。。

ロジックとか感想とか

以降、だらだらと感想とか書く。

$ tenhoの動き自体は、

  1. (34 * 4)のカード(牌)の束からランダムに14枚を取り出す。
  2. それがアガっていれば終了。上がっていなければもう一度14枚取り出す

という感じ。

ランダムに14枚取り出すのはArray#sample的なものが使った(あ、rubyっす)。

やはりあがり判定がキーになる。

適当に書いた感じでも、1秒間に1500回以上のサンプリングとあがり判定ができたっぽい。意外と速くて満足してしまった : )。

それでも感覚的には天和上がるには200万回ぐらい必要(本当の確率は調べてない。。)なので、数分必要なのだが。。

自分が書く上でポイントだと思ったこと3つ。

3つのあがり系

周知の通り麻雀には3つのあがり形があって(すくなくとも天和の場合はこの3つでいいと思う)

というかんじになる。ここに気づくのがポイント。

スート

スート(トランプでいうところのハート・ダイヤ・クラブ・スペード)が4種類(萬・筒・索・字)。

字牌だけは、順子が作れないという特徴がある。

別のスート同士で順子や刻子や対子は構成できないので、必然的にカードをスート毎にグルーピングする必要が出てくる。

テスト書きながら書くと楽

こういうときのテストは書いてて楽しい

書きながら思ったこと

七対子だるいwwwww

七対子をどこで判定するかがだるい。

たとえば索子が8枚あるということがわかっているとき、それは(3+3+2)形でなかったとしても(2+2+2+2)形かもしれない。

七対子の判定の順序は、最初のほうにしたほうがパフォーマンスが良いかもしれない。

順子と刻子

順子と刻子の判定については難しいかなと思ってたら、そうでもなかった。

三連刻のかたち(444555666とか)の判定は、役を解釈しないかぎり(天和だけを考慮する場合)は気にしなくて良いので。

同様に役を解釈しないかぎりにおいては、七対子二盃口か、みたいな判定も要らない。

今後書きたいこと

書いてると無性にオプション作りたくなる。いま思いついたのは以下。センス☓。

  • --[no-]normal ノーマル型(33332型)を除外
  • --[no-]chi-toitsu 七対子を除外
  • --[no-]kokushi 国士無双を除外
  • --limit [arg] リミットを指定
  • --sanma サンマモード

あと他の言語で書いても面白いかもしれないと思ってる

Docker実践LTでDocker swarmを触ってみた話をしてきました #dockerlt

connpass.com

自分のLT

gist

デモした

swarmのstrategy + このエントリで書いたようなことについてしゃべりました

f:id:hoppie:20151015232627p:plain

伝わんなかったと思いますが、「binpackよさ気じゃない?」てことを言いたかった。

Docker実践LT

短い時間でどんどんdocker発表されてく場って、

東京でも少ないな〜と思ってたので(主催の方もそう考えて企画したと言っていた)、

自分にとってはすごく良い時間でした!

とりとめないメモ

  • 資料へのリンク
  • swarm使ってる人少なかった
    • たぶん1人2人ぐらいだったような…
  • windowsでがんばってdocker動かすやつ
    • わかりやすかった
  • CIで2000個/日 コンテナ動かすとバグ踏むって話
    • SideCI
    • docker触るの怖い
  • dockerはプロトコル
  • さくらクラウドの方からクーポン券いただいた
    • ありがとうございます!
    • 使ったらがんばってなんか書きます!!
  • なんにせよLTの時間短いので、内容濃かった気がする
  • CoreOS、わりと人気だった
    • なんか嬉しかった

今後について

また機会あれば参加したいです!

EC2で複数ホストでdocker swarm します、swarm managerも冗長化するよ

f:id:hoppie:20150928031455p:plain

この記事でやること

こういう感じのクラスタをつくります

f:id:hoppie:20150929064649p:plain

ポイントとしては、

  • EC2インスタンスを2台動かし、両方で
    • swarm manager を動かす
    • swarm agent を動かす ということをやります。

agentを複数ホストでやるやつは、ググれば結構出てきますが、

今回はmanagerも複数ホストでやってみます。

今回利用した各バージョンとかは以下のとおり。


  • AMI: ami-f2338ff2 CoreOS-stable-723.3.0-hvm
    • CoreOS: 723.3.0
      • docker: 1.6.2
      • etcd: 2.0.12
  • docker swarm: 0.4.0

作ろう

以下、cloud-config を作っていきます

あ、先に断っておきますが、 Docker Swarm によると現在まだ

Note: Swarm is currently in BETA, so things are likely to change. We don’t recommend you use it in production yet.

な感じなので、将来的にこのコマンドで動くかどうかはわかりません。

etcd

サービスディスカバリーの部分です。

本来、swarm create でdocker hub のトークンを得るのが楽で良いのですが、

複数マネージャーの立て方を紹介したページ High availability in Docker Swarm によると、複数managerを動かす場合は token:// が使えず、現在のところ etcd://zk://consul:// の3つでしか動かないとのことです。

今回は、CoreOSなので、etcd を使ってみました。

以下のサンプルを元にやりました。

(サンプルに合った不要な記述を少し削除しましたが)簡単に動きました。

Customize with Cloud-Config

こんな感じ。

#cloud-config

coreos:
  etcd2:
    # <https://discovery.etcd.io/new?size=Xでトークンを得る。今回はX=1>
    discovery: https://discovery.etcd.io/tokenxxxxxxxxxxxxxxxxxxxxxxxxxxx
    advertise-client-urls: http://$private_ipv4:2379
    initial-advertise-peer-urls: http://$private_ipv4:2380
    listen-client-urls: http://0.0.0.0:2379
    listen-peer-urls: http://$private_ipv4:2380
  units:
    - name: etcd2.service
      command: start

Docker Remote APIを、TCP経由で使えるように準備

Create a swarm for development によると、

swarm agentは、Docker daemonに対して外からTCP経由での接続が必要なようです。

ここで大事なこととして、

Caution : Only use this set up if your network environment is secured by a firewall or other measures.

とありますので、注意が必要です(私はセキュリティグループで 該当ポートはVPC内での接続しか受け付けないようにしました)。

今回はCoreOSのこちらのページ を参考に、DockerのRemote APIを、TCP 2375番で晒しました。

coreos:
  units:
    - name: docker-tcp.socket
      command: start
      enable: true
      content: |
        [Unit]
        Description=Docker Socket for the API

        [Socket]
        ListenStream=2375
        BindIPv6Only=both
        Service=docker.service

        [Install]
        WantedBy=sockets.target

ここで、もしうまくいっていれば、以下の様に docker ps コマンドが、(エラーにならずに)実行できるはずです

# docker daemonが2375ポートで動いている場合
$ docker -H :2375 ps; echo $?
CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS              PORTS               NAMES
0

# docker daemonが2375ポートで動いていない場合(うまくいっていない場合)
$ docker -H :2375 ps; echo $?
FATA[0000] Cannot connect to the Docker daemon. Is 'docker -d' running on this host?
1

swarm agentを動かす

Create a swarm for development を参考に。

コマンドの形は docker run [runのoption] swarm(=イメージ名) join [swarm-joinのoption] argment みたいな感じです。

最後の引数は、サービスディスカバリーの情報です。さっき作った自ホストのetcdを指定します。

シェルからならこんな感じ。

$ docker run -d --name=swarm-join \
    swarm join \
      --advertise=ホストのプライベートIPアドレス:2375 \
      etcd://ホストのプライベートIPアドレス::2379/swarm # ここは0.0.0.0でもイケたかも、試してない

cloud-configにした場合、こんな感じ。$private_ipv4 という、変数的なものが使えます。

coreos:
  units:
    - name: swarm-join.service
      command: start
      content: |
        [Unit]
        Description=swarm-join Container
        After=docker.service
        Requires=docker.service

        [Service]
        TimeoutStartSec=0
        Restart=always
        ExecStartPre=-/usr/bin/docker stop swarm-join
        ExecStartPre=-/usr/bin/docker rm swarm-join
        ExecStartPre=-/usr/bin/docker pull swarm
        ExecStart=/usr/bin/docker run --name=swarm-join swarm join --advertise=$private_ipv4:2375 etcd://$private_ipv4:2379/swarm

        [Install]
        WantedBy=multi-user.target

swarm managerを動かす

https://docs.docker.com/swarm/multi-manager-setup/#create-the-primary-manager を参考にやります。

シェルからならこんな感じ。

$ docker run -d --net=host --name=swarm-manage \
    swarm manage \
      -H :4000 \
      --replication \
      --strategy=binpack \
      --advertise 自ホストのローカルIPアドレス:4000 \
      etcd://自ホストのローカルIPアドレス:2379/swarm

cloud-configにするとこんな形になりました。

coreos:
  units:
    - name: swarm-manage.service
      command: start
      content: |
        [Unit]
        Description=swarm-manage Container
        After=docker.service
        Requires=docker.service

        [Service]
        TimeoutStartSec=0
        Restart=always
        ExecStartPre=-/usr/bin/docker stop swarm-manage
        ExecStartPre=-/usr/bin/docker rm swarm-manage
        ExecStartPre=-/usr/bin/docker pull swarm
        ExecStart=/usr/bin/docker run --net=host --name=swarm-manage swarm manage -H :4000 --replication --strategy=binpack --advertise $private_ipv4:4000 etcd://$private_ipv4:2379/swarm

        [Install]
        WantedBy=multi-user.target

ポイントとしては、他のmanagerから来る問い合わせに使うポートを、-H 4000--advertise $private_ipv4:4000 で、指定しています。

このあたり、--net=host としないとうまく動かず… うまい書き方があったら教えてほしいところです。

準備完了っす!

ぜんぶくっつけた全体像はこんな感じ。

#cloud-config

hostname: node1
coreos:
  etcd2:
    # <https://discovery.etcd.io/new?size=1でトークンを得る>
    discovery: https://discovery.etcd.io/tokenxxxxxxxxxxxxxxxxxxxxxxxxxxx
    advertise-client-urls: http://$private_ipv4:2379
    initial-advertise-peer-urls: http://$private_ipv4:2380
    listen-client-urls: http://0.0.0.0:2379
    listen-peer-urls: http://$private_ipv4:2380
  units:
    - name: etcd2.service
      command: start
    - name: docker-tcp.socket
      command: start
      enable: true
      content: |
        [Unit]
        Description=Docker Socket for the API

        [Socket]
        ListenStream=2375
        BindIPv6Only=both
        Service=docker.service

        [Install]
        WantedBy=sockets.target

    - name: swarm-join.service
      command: start
      content: |
        [Unit]
        Description=swarm-join Container
        After=docker.service
        Requires=docker.service

        [Service]
        TimeoutStartSec=0
        Restart=always
        ExecStartPre=-/usr/bin/docker stop swarm-join
        ExecStartPre=-/usr/bin/docker rm swarm-join
        ExecStartPre=-/usr/bin/docker pull swarm
        ExecStart=/usr/bin/docker run --name=swarm-join swarm join --advertise=$private_ipv4:2375 etcd://$private_ipv4:2379/swarm

        [Install]
        WantedBy=multi-user.target

    - name: swarm-manage.service
      command: start
      content: |
        [Unit]
        Description=swarm-manage Container
        After=docker.service
        Requires=docker.service

        [Service]
        TimeoutStartSec=0
        Restart=always
        ExecStartPre=-/usr/bin/docker stop swarm-manage
        ExecStartPre=-/usr/bin/docker rm swarm-manage
        ExecStartPre=-/usr/bin/docker pull swarm
        ExecStart=/usr/bin/docker run --net=host --name=swarm-manage swarm manage -H :4000 --replication --strategy=binpack --advertise $private_ipv4:4000 etcd://$private_ipv4:2379/swarm

        [Install]
        WantedBy=multi-user.target

以上のyamlをユーザーデータとして利用し、CoreOSのAMIを選択し、最初からEC2を起動します(ユーザーデータについては説明割愛します)。

2台目を起動するときは、今回は hostname: node1 の部分を hostname: node2 に書き換えて起動します(ここだけ超手動)

動いた!

EC2が起動したら node1でコマンド発行してみます。

# 普通にdocker ps。これも一応動く。managerとagentの2つが動いています。
core@node1 ~ $ docker ps
CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS              PORTS               NAMES
1153b3bc311d        swarm:latest        "/swarm manage -H :4   5 minutes ago       Up 5 minutes                            swarm-manage        
bb475b187a5e        swarm:latest        "/swarm join --adver   5 minutes ago       Up 5 minutes        2375/tcp            swarm-join    

# 自ノードのポート指定してdocker ps。 ポートが利用されていることが確認できる。
core@node1 ~ $ docker -H:2375 ps
CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS              PORTS               NAMES
1153b3bc311d        swarm:latest        "/swarm manage -H :4   5 minutes ago       Up 5 minutes                            swarm-manage        
bb475b187a5e        swarm:latest        "/swarm join --adver   5 minutes ago       Up 5 minutes        2375/tcp            swarm-join    

# node2のポート指定してdocker ps。node2.innerという名前は、別途手で、2台めのIPに対して付与しています。。
# CONTAINER IDがnode1のものと違うので、別のコンテナであることがわかります
core@node1 ~ $ docker -H node2.inner:2375 ps
CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS              PORTS               NAMES
3c6a0a41d3e6        swarm:latest        "/swarm manage -H :4   5 minutes ago       Up 5 minutes                            swarm-manage        
1f340aef1237        swarm:latest        "/swarm join --adver   5 minutes ago       Up 5 minutes        2375/tcp            swarm-join  
core@node1 ~ $

よさ気です。つぎに、 manager である4000番ポートも調べてみます。

# 普通にdocker psするとなにも表示されない。
core@node1 ~ $ docker -H :4000 ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

# -aオプションつけると、managerやagentも表示される。NAMES列でホスト名が確認できる。
core@node1 ~ $ docker -H :4000 ps -a
CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS              PORTS               NAMES
3c6a0a41d3e6        swarm:latest        "/swarm manage -H :4   6 minutes ago       Up 6 minutes                            node2/swarm-manage
1f340aef1237        swarm:latest        "/swarm join --adver   6 minutes ago       Up 6 minutes        2375/tcp            node2/swarm-join
1153b3bc311d        swarm:latest        "/swarm manage -H :4   6 minutes ago       Up 6 minutes                            node1/swarm-manage
bb475b187a5e        swarm:latest        "/swarm join --adver   6 minutes ago       Up 6 minutes        2375/tcp            node1/swarm-join

# node2のポートも見られることを確認。
core@node1 ~ $ docker -H node2.inner:4000 ps -a
CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS              PORTS               NAMES
3c6a0a41d3e6        swarm:latest        "/swarm manage -H :4   6 minutes ago       Up 6 minutes                            node2/swarm-manage
1f340aef1237        swarm:latest        "/swarm join --adver   6 minutes ago       Up 6 minutes        2375/tcp            node2/swarm-join
1153b3bc311d        swarm:latest        "/swarm manage -H :4   7 minutes ago       Up 7 minutes                            node1/swarm-manage
bb475b187a5e        swarm:latest        "/swarm join --adver   7 minutes ago       Up 7 minutes        2375/tcp            node1/swarm-join

以上のようにmanagerに対してdocker psすると、4個のコンテナ((manageer + agent) * 2台)がrunしていることがわかります。

managerの動きを調べる

つぎに、managerに対して docker info してみます。

core@node1 ~ $ docker -H :4000 info
Containers: 4
Images: 4
Storage Driver:
Role: primary
Strategy: binpack
Filters: affinity, health, constraint, port, dependency
Nodes: 2
 node1: 172.31.14.196:2375
  └ Containers: 2
  └ Reserved CPUs: 0 / 1
  └ Reserved Memory: 0 B / 1.022 GiB
  └ Labels: executiondriver=native-0.2, kernelversion=4.0.5, operatingsystem=CoreOS 723.3.0, storagedriver=overlay
 node2: 172.31.25.198:2375
  └ Containers: 2
  └ Reserved CPUs: 0 / 1
  └ Reserved Memory: 0 B / 1.022 GiB
  └ Labels: executiondriver=native-0.2, kernelversion=4.0.5, operatingsystem=CoreOS 723.3.0, storagedriver=overlay
Execution Driver:
Kernel Version:
Operating System:
CPUs: 2
Total Memory: 2.043 GiB
Name: node1
ID:
Http Proxy:
Https Proxy:
No Proxy:

Nodeを2つ認識しており、RolePrimaryであることがわかります。

対して、node2 に対して実行すると

core@node1 ~ $ docker -H node2.inner:4000 info
Containers: 4
Images: 4
Storage Driver:
Role: replica
Primary: 172.31.14.196:4000
Strategy: binpack
…以下同じなので省略…

Rolereplicaであることがわかります。

今回はたまたま、node1(172.31.14.196)がPrimaryに選出されたようですね。

選挙の様子は、それぞれのmanagerに対してdocker logsを実行すれば見られます。

core@node1 ~ $ docker -H :4000 logs node1/swarm-manage
time="2015-09-29T21:45:30Z" level=info msg="Listening for HTTP" addr=":4000" proto=tcp
time="2015-09-29T21:45:30Z" level=info msg="Leader Election: Cluster leadership lost"
time="2015-09-29T21:45:30Z" level=error msg="Leader Election: watch leader channel closed, the store may be unavailable..."
time="2015-09-29T21:45:30Z" level=info msg="Leader Election: Cluster leadership acquired"
time="2015-09-29T21:45:30Z" level=info msg="Registered Engine node1 at 172.31.14.196:2375"
time="2015-09-29T21:45:49Z" level=info msg="Registered Engine node2 at 172.31.25.198:2375"
core@node1 ~ $ docker -H :4000 logs node2/swarm-manage
time="2015-09-29T21:45:50Z" level=info msg="Listening for HTTP" addr=":4000" proto=tcp
time="2015-09-29T21:45:50Z" level=info msg="Leader Election: Cluster leadership lost"
time="2015-09-29T21:45:50Z" level=info msg="New leader elected: 172.31.14.196:4000"
time="2015-09-29T21:45:50Z" level=info msg="Registered Engine node2 at 172.31.25.198:2375"
time="2015-09-29T21:45:51Z" level=info msg="Registered Engine node1 at 172.31.14.196:2375"

managerのうち、1台を止めてみる

止めてみます。

sudo systemctl disable swarm-manage
sudo systemctl stop swarm-manage

このあとnode2のmanagerのlogを見てみると、自分がNew leader であると主張しています!

$ docker -H node2.inner:4000 logs swarm-manage 
…()
time="2015-09-30T10:28:41Z" level=info msg="Leader Election: Cluster leadership acquired"

docker infoして確認しても、今度はnode2のほうがprimaryだと主張していることがわかります。

core@node1 ~ $ docker -H node2.jnner:4000 info
Containers: 4
Images: 4
Storage Driver:
Role: primary
...()

フェイルオーバーっぽいことができていることがわかります。

まぁ、docker -H $(etcdctl get /swarm/docker/swarm/leader) ps 的なことが、1台壊れるぐらいなら大丈夫な感じみたいな。感じ感じ

あと、etcdは安定的に動かすには3台以上無いとダメっぽいです

まとめ

  • managerの冗長化も、etcd使ってなんとかなる
    • しかも割と簡単
  • docker-machineとかdocker-composeとかvagrantとか使わんでも、なんとかなる!
  • EC2のオートスケーリングとかと組み合わせれば夢が広がりそう

次回(いつか)は、このクラスタでswarmのbinpackストラテジーを体験できることを書きたいと思います。