👻

Terminalから遊ぶ麻雀を作ってみた。

2021/03/14に公開

Terminalから遊ぶ麻雀を作ってみた。

こんにちは。k-junです。Terminal上で動作(文字列が流れてくるだけですが)する、麻雀のゲームを作ってみました。セキュリティ的な懸念が拭いきれなかったために外部公開はしていませんが、面白がりな方は良ければ遊んで見ていただけると幸いです(ちなみにテストプレイしていません 笑)。

また、麻雀と銘打ちましたがかなり簡略化されている麻雀(ドンジャラ?)のような状態です。ドラ表示、自風、場風は実装しておらず、役はメンタンピンのみで、振聴もありません。半荘、東風という概念はなく、1局戦のみになります。

年始におふざけで始めて、ちまちま作った麻雀もどきゲームですのでこんなものかなと思っています 笑。

tl;dr

https://github.com/k-jun/cli-mahjong

Terminal上で麻雀の山、手牌、捨て牌を再現しています。正直な所以下の出力を見て笑っていただけば、この記事の目的は完了していたりします 笑 解散!

                            ┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐
                            │  ││  ││  ││  ││  ││  ││  ││  ││  ││  ││  ││  ││  │
                            └──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘
┌──┐
│  │
└──┘
│  │
└──┘
│  │
└──┘
│  │
└──┘
│  │
└──┘                                ┌──┐┌──┐┌──┐┌──┐
│  │                                │p2││p1││m9││m9│
└──┘                        ┌──┐┌──┐└──┘└──┘└──┘└──┘
│  │                        │m1││p4││m4││南││北││西│
└──┘                ┌──┐┌──┐└──┘└──┘└──┘└──┘└──┘└──┘┌──┐┌──┐                ┏━━┓
└──┘                │北││中│└──┘└──┘└──┘└──┘└──┘└──┘│s1││s8│                ┃  ┃
                    └──┘└──┘                        └──┘└──┘                ┗━━┛
                    │北││東│                        │m5││m9│                ┃  ┃
                    └──┘└──┘                        └──┘└──┘                ┗━━┛
                    │p9││東│                        │m1││南│                ┃  ┃
                    └──┘└──┘                        └──┘└──┘                ┗━━┛
                    │p1││白│                        │発││西│                ┃  ┃
                    └──┘└──┘                        └──┘└──┘                ┗━━┛
                    │p9││m1│                        │北││s9│                ┃  ┃
                    └──┘└──┘                        └──┘└──┘                ┗━━┛
                    │p9││p9│                        │東││白│                ┃  ┃
                    └──┘└──┘┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐└──┘└──┘                ┗━━┛
                    └──┘└──┘│西││中││中││発││s3││白│└──┘└──┘                ┃  ┃
┌──┐                        └──┘└──┘└──┘└──┘└──┘└──┘                        ┗━━┛
│m4│                        │南││s9││東││中││s9││s1│                        ┃  ┃
└──┘                        └──┘└──┘└──┘└──┘└──┘└──┘                        ┗━━┛
│m3│                        └──┘└──┘└──┘└──┘└──┘└──┘                        ┃  ┃
└──┘                                                                        ┗━━┛
│m2│                                                                        ┃  ┃
└──┘                                                                        ┗━━┛
│s5│                                                                        ┃  ┃
└──┘                                                                        ┗━━┛
│s4│                                                                        ┃  ┃
└──┘                                                                        ┗━━┛
│s3│                                                                        ┃  ┃
└──┘                                                                        ┗━━┛
└──┘                                                                        ┗━━┛
                                                                    ┌──┐┌──┐┌──┐
┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐    ┌──┐                    │p5││p5││p5│
│m3││m6││m8││p4││s1││s1││s2││s3││s4││s4│    │p6│                    └──┘└──┘└──┘
└──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘    └──┘                    └──┘└──┘└──┘

>>m3

遊び方

麻雀ゲームとしてプレイする人もいないと思うので、あまり真面目に解説するのもあれなのですが。
サーバーをgolangで起動した後に、netcatでアクセスします。部屋の振り分けは自動でなされ、4人のユーザーが集まると自動的にゲームがスタートします。

$ nc localhost 8080
current number of users : 1
max number of users     : 4
current number of users : 2
max number of users     : 4
current number of users : 3
max number of users     : 4
current number of users : 4
max number of users     : 4
                            ┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐
                            │  ││  ││  ││  ││  ││  ││  ││  ││  ││  ││  ││  ││  │
                            └──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘
┌──┐
│  │
└──┘
│  │
└──┘
│  │
└──┘
│  │
└──┘
│  │
└──┘
│  │
└──┘
│  │
└──┘                                                                        ┌──┐
│  │                                                                        │  │
└──┘                                                                        └──┘
│  │                                                                        │  │
└──┘                                                                        └──┘
│  │                                                                        │  │
└──┘                                                                        └──┘
│  │                                                                        │  │
└──┘                                                                        └──┘
│  │                                                                        │  │
└──┘                                                                        └──┘
│  │                                                                        │  │
└──┘                                                                        └──┘
└──┘                                                                        │  │
                                                                            └──┘
                                                                            │  │
                                                                            └──┘
                                                                            │  │
                                                                            └──┘
                                                                            │  │
                                                                            └──┘
                                                                            │  │
                                                                            └──┘
                                                                            │  │
                                                                            └──┘
                                                                            │  │
                                                                            └──┘
                                                                            └──┘

┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐    ┌──┐
│m5││m8││m8││m9││p2││p3││p7││p8││s2││s3││s6││白││西│    │北│
└──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘    └──┘

>>

この>>が自分のターンを表しており、各牌の名前を直打ちすることで打牌を行えます。

                            ┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐
                            │  ││  ││  ││  ││  ││  ││  ││  ││  ││  ││  ││  ││  │
                            └──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘
┌──┐
│  │
└──┘
│  │
└──┘
│  │
└──┘
│  │
└──┘
│  │
└──┘
│  │
└──┘                                            ┌──┐
│  │                                            │西│
└──┘                                            └──┘                        ┌──┐
│  │                                            └──┘                        │  │
└──┘                                                                        └──┘
│  │                                                                        │  │
└──┘                                                                        └──┘
│  │                                                                        │  │
└──┘                                                                        └──┘
│  │                                                                        │  │
└──┘                                                                        └──┘
│  │                                                                        │  │
└──┘                                                                        └──┘
│  │                                                                        │  │
└──┘                                                                        └──┘
└──┘                                                                        │  │
                                                                            └──┘
                                                                            │  │
                                                                            └──┘
                                                                            │  │
                                                                            └──┘
                                                                            │  │
                                                                            └──┘
                                                                            │  │
                                                                            └──┘
                                                                            │  │
                                                                            └──┘
                                                                            │  │
                                                                            └──┘
                                                                            └──┘

┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐
│m1││m8││p3││p4││p5││s2││s3││s7││s9││中││発││西││西│
└──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘

pon>> 0: (西 西)

pon>>chii>>などが表示された際には、他ユーザーの打牌に対して鳴きを行うことが出来ます。複数選択肢がある場合にはchii 2などのようにindexを追加で付与し、表現します。鳴きを行わない際にはnoと直打ちすることで鳴きをキャンセルします。自分のターンに行うriichi>>,tsumo>>なども同様です。
ちなみに、鳴きの優先順位は実装していないのでronは早めに行いましょう。他のユーザーのchiiにより自分のronが消されます 笑。

終了時には和了ったユーザーの手牌が公開され、自動で接続が切れます。

                            ┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐
                            │  ││  ││  ││  ││  ││  ││  ││  ││  ││  ││  ││  ││  │
                            └──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘
┌──┐
│  │
└──┘
│  │
└──┘
│  │
└──┘
│  │
└──┘
│  │
└──┘                        ┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐
│  │                        │南││m1││p9││南││p7││m9│
└──┘                        └──┘└──┘└──┘└──┘└──┘└──┘
│  │                        │p8││中││s9││白││北││西│
└──┘                ┌──┐┌──┐└──┘└──┘└──┘└──┘└──┘└──┘┌──┐┌──┐                ┌──┐
└──┘                │北││東│└──┘└──┘└──┘└──┘└──┘└──┘│南││白│                │  │
                    └──┘└──┘                        └──┘└──┘                └──┘
                    │北││m1│                        │中││s8│                │  │
                    └──┘└──┘                        └──┘└──┘                └──┘
                    │s9││p1│                        │発││中│                │  │
                    └──┘└──┘                        └──┘└──┘                └──┘
                    │p9││西│                        │p1││m9│                │  │
                    └──┘└──┘                        └──┘└──┘┌──┐            └──┘
                    │s3││発│                        │白││p1││p2│            │  │
                    └──┘└──┘                        └──┘└──┘└──┘            └──┘
                    │東││s1│                        │p1││北││東│            │  │
                    └──┘└──┘┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐└──┘└──┘└──┘            └──┘
                    └──┘└──┘│m1││中││発││西││m7││南│└──┘└──┘└──┘            │  │
┌──┐                        └──┘└──┘└──┘└──┘└──┘└──┘                        └──┘
│m5│                        │西││m9││p5││s9││s1││s1│                        │  │
└──┘                        └──┘└──┘└──┘└──┘└──┘└──┘                        └──┘
│m5│                        │m8││p3│└──┘└──┘└──┘└──┘                        │  │
└──┘                        └──┘└──┘                                        └──┘
│m5│                        └──┘└──┘                                        │  │
└──┘                                                                        └──┘
│m7│                                                                        │  │
└──┘                                                                        └──┘
│m6│                                                                        │  │
└──┘                                                                        └──┘
│m5│                                                                        │  │
└──┘                                                                        └──┘
└──┘                                                                        └──┘
                                                        ┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐
┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐                            │s3││s2││s4││p6││p5││p7│
│p2││p4││p5││p6││s5││s6││s7│                            └──┘└──┘└──┘└──┘└──┘└──┘
└──┘└──┘└──┘└──┘└──┘└──┘└──┘                            └──┘└──┘└──┘└──┘└──┘└──┘

ron>>
ron
                                                        ┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐
┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐                        │s3││s2││s4││p6││p5││p7│
│p2││p4││p5││p6││s5││s6││s7││p2│                        └──┘└──┘└──┘└──┘└──┘└──┘
└──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘                        └──┘└──┘└──┘└──┘└──┘└──┘
$

使用技術

サーバーはgolang、クライアントはnetcat?になります。

地味に大変だったのが、netcatの通信が切られた場合の処理でした..。WebSocketのOnCloseのようなものを実装しなければならず、中身の実装も各ユーザーが保持するchannel間やり取りになってしまい、多少手間取りました。

クライアントにnetcatを用いているので、接続中にユーザーはCtrl-Cで通信を切断できます。麻雀はターン周りで打牌を行うゲームですので、一人のユーザーがいなくなるとゲームの続行が不可能となります。このため現時点では、1人のユーザーが落ちると連鎖的に全てのユーザーの接続も同時に切っています(将来的には代走ユーザーに変わりに打牌を打たせるなどの対応が望ましいと思いますが)。この際の user -> user の伝播が煩雑でした。

鳴きの確認の実装も他と比べるとめんどくさかったですね..。各ユーザーの処理系をまとめたplayerオブジェクト、全体に関わる処理をまとめたboardオブジェクトが存在していますが、鳴きの確認はplayer(eventの発生) -> board(nakiの確認&キャンセル用のqueueを生成) -> player(入力受付) -> board(入力処理)とフローが発生し、単純な打牌と比べるとかなり複雑になりました..。

感想

やっぱり、Terminal上に表現するときが一番おもしろかったですね 笑。鳴きの実装後、Terminal上で鳴きができることに1人で馬鹿だなぁと思いながら爆笑していました 笑。

今回の発端はTCP周りの勉強だったのでnetcatにこだわって実装していますが、cli側を実装すれば手元でもっとヌルヌル動く麻雀が実装できると思うので、機会があったらやってみようと思います。

Discussion