麻雀のあがり判定をゼロから書いて、天和コマンドを作ってみた
【車輪】麻雀のあがり判定をゼロから書いて、天和コマンドを作ってみた感想【再発明】
プログラミングの話ですが、麻雀の話です。
$ tenho
ってコマンドを打つと天和がアガれたら面白いんじゃないかと思って、作った
うごくやつができた
こんなん
なぜそんなことを
ruby -e "puts '🀀'.next" => "🀁"
みたいに文字が使えるし、CLIでも見栄えが良いかなと思ってた。
休日の車輪の再発明にはちょうど良さそう。
ロジックの方はおみくじ感覚でかんたんにできるかなと思ってた。
が、あがり判定っていうのはわりと難易度高かった。。
ロジックとか感想とか
以降、だらだらと感想とか書く。
$ tenho
の動き自体は、
- (34 * 4)のカード(牌)の束からランダムに14枚を取り出す。
- それがアガっていれば終了。上がっていなければもう一度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
自分のLT
デモした
swarmのstrategy + このエントリで書いたようなことについてしゃべりました
伝わんなかったと思いますが、「binpackよさ気じゃない?」てことを言いたかった。
Docker実践LT
短い時間でどんどんdocker発表されてく場って、
東京でも少ないな〜と思ってたので(主催の方もそう考えて企画したと言っていた)、
自分にとってはすごく良い時間でした!
とりとめないメモ
- 資料へのリンク
- swarm使ってる人少なかった
- たぶん1人2人ぐらいだったような…
- windowsでがんばってdocker動かすやつ
- わかりやすかった
- CIで2000個/日 コンテナ動かすとバグ踏むって話
- SideCI
docker触るの怖い
dockerはプロトコル
- さくらクラウドの方からクーポン券いただいた
- ありがとうございます!
- 使ったらがんばってなんか書きます!!
- なんにせよLTの時間短いので、内容濃かった気がする
- CoreOS、わりと人気だった
- なんか嬉しかった
今後について
また機会あれば参加したいです!
EC2で複数ホストでdocker swarm します、swarm managerも冗長化するよ
この記事でやること
こういう感じのクラスタをつくります
ポイントとしては、
- 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
- CoreOS: 723.3.0
- 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
を使ってみました。
以下のサンプルを元にやりました。
(サンプルに合った不要な記述を少し削除しましたが)簡単に動きました。
こんな感じ。
#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つ認識しており、Role
はPrimary
であることがわかります。
対して、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 …以下同じなので省略…
Role
はreplica
であることがわかります。
今回はたまたま、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ストラテジーを体験できることを書きたいと思います。