電脳麻将 では以下の2つのクラスのインスタンスが互いに通信することで局を進行させている。
その他に人間用のUIを備えたMajiang.UI
があるが、これはMajiang.Player
のサブクラスなので、通信部分のコードは共有している。
現在は同一ページ内に両クラスが存在するので、通信方式は関数呼出しである。麻雀サーバを作るのであれば、Majiang.Game
をサーバ側に、Majiang.Player
をクライアント側に配置し、WebSocketなどで通信することになるであろう*1。
通信プロトコルは以下の通り。
Gameからの通知 | Playerの応答 | 補足 | |
---|---|---|---|
開局 (kaiju) | (なし) | 半荘の開始 | |
配牌 (qipai) | (なし) | 一局の開始 | |
自摸 (zimo) | 手番 | 打牌 (dapai) | |
槓 (gang) | 暗槓・加槓 | ||
和了 (hule) | ツモ和了 | ||
流局 (pingju) | 九種九牌 | ||
手番以外 | − | ||
打牌 (dapai) | 手番 | − | |
手番以外 | − | ||
和了 (hule) | ロン和了 | ||
副露 (fulou) | チー・ポン・大明槓 | ||
副露 (fulou) | 手番 | 打牌 (dapai) | |
− | 大明槓の後 | ||
手番以外 | − | ||
槓 (gang) | 手番 | − | |
手番以外 | − | ||
和了 (hule) | 槍槓 | ||
槓自摸 (gangzimo) | 手番 | 打牌 (dapai) | |
槓 (gang) | 槓が連続する場合 | ||
和了 (hule) | 嶺上開花 | ||
手番以外 | − | ||
開槓 (kaigang) | (なし) | ||
流局 (pingju) | − | ||
和了 (hule) | − | ||
終局 (jieju) | − | 半荘の終了 |
Gameからの通知にはPlayerの応答を待つもの(同期メッセージ)と待たないもの(非同期メッセージ)がある。同期メッセージは4人分の応答を待って次の処理へ進む。配牌、開槓は次の動作が続けて起こるため非同期メッセージとしている。開局、流局、和了、終局は非同期メッセージであるべきなのだが、ゲームの進行上人間の応答を待つところでは同期メッセージとしている*2。
各メッセージの送受信データは 電脳麻将の牌譜形式 - koba::blog の形式を踏襲している。
Gameからの通知メッセージ
基本形式
メッセージは type
と data
からなる。type
は上記の開局、配牌などの種別を表し、data
は type
ごとに形式が異なる。
開局 (kaiju)
type = 'kaiju'; data = { player: ['私','下家','対面','上家'], // 対局者情報 qijia: 2, // 起家 hongpai: { m: 1, p: 1, s: 1 } // 赤牌の数 };
配牌 (qipai)
type = 'qipai'; data = { zhuangfeng: 0, // 場風 jushu: 0, // 局数 changbang: 0, // 積み棒の数 lizhibang: 0, // 立直棒の数 defen: [ 25000, 25000, 25000, 25000 ], // 局開始時の持ち点 baopai: 's7', // ドラ表示牌 shoupai: [ '', '', 'm299p1158s288z356', '' ] // 配牌 };
牌譜の形式と同じだが、他家分の情報はマスクして通知する。上の例は西家への通知データ。
自摸 (zimo)
type = 'zimo'; data = { l: 0 /* 手番 */, p: 'm1' /* 自摸牌 */ };
牌譜の形式と同じだが、手番以外のPlayerには p
は空文字にマスクして通知する。
打牌 (dapai)
type = 'dapai'; data = { l: 0 /* 手番 */, p: 'z6' /* 打牌 */ };
牌譜の形式と同じ。ツモ切り、および立直宣言の表し方も牌譜の形式にしたがう。
副露 (fulou)
type = 'fulou'; data = { l: 2 /* 手番 */, m: 'z666=' /* 副露面子 */ };
牌譜の形式と同じ。上記の例は西家が対面(東家)の發をポン。
槓 (gang)
type = 'gang'; data = { l: 2 /* 手番 */, m: 'z666=6' /* 副露面子 */ };
牌譜の形式と同じ。上記の例は西家が副露済みの發に加槓。暗槓の場合は、
type = 'gang'; data = { l: 3 /* 手番 */, m: 'z1111' /* 副露面子 */ };
となる。
槓自摸 (gangzimo)
type = 'gangzimo'; data = { l: 0 /* 手番 */, p: 'p0' /* 自摸牌 */ };
自摸と形式は同じだが type
のみが異なる。Playerは gangzimo であれば嶺上開花で和了できると判断できる。
開槓 (kaigang)
type = 'kaigang'; data = { baopai: 'p0' /* 槓ドラ表示牌 */ };
牌譜の形式と同じだが実装上の都合で発生順序が異なる。
「明槓のドラ後乗り」のルールの場合、
- 暗槓
- gang → kaigang → gamgzimo → dapai (kaigang と gangzimo の順序が逆)
- 大明槓
- fulou → gangzimo → kaigang → dapai (kaigang と dapai の順序が逆)
- 加槓
- gang → gangzimo → kaigang → dapai (kaigang と dapai の順序が逆)
という順序になる。
流局 (pingju)
type = 'pingju'; data = { name: '荒牌平局', // 流局理由 shoupai: [ 'm678p66s12,s6-78,s555=', // 流局時の手牌 'm1177p456s340,z444=', '', 'm44p12340,z2222=,p999=' ], fenpei: [ 1000, 1000, -3000, 1000 ] // 局収支 };
牌譜の形式と同じ。上記の例は西家のみがノー聴でノー聴罰符を支払う。
和了 (hule)
type = 'hule'; data = { l: 3, // 和了者 shoupai: 'm78p405667s34577m9', // 手牌 baojia: null, // 放銃者 fubaopai: ['s2','m4'], // 裏ドラ表示牌 fu: 20, // 符 fanshu: 5, // 翻数 defen: 8000, // 供託をのぞいた和了点 hupai: [ // 和了役名と翻数のリスト { name: '立直', fanshu: 1 }, { name: '門前清自摸和', fanshu: 1 }, { name: '平和', fanshu: 1 }, { name: '赤ドラ', fanshu: 1 }, { name: '裏ドラ', fanshu: 1 }, ], fenpei: [ -4200, -2200, -2200, 9600 ] // 局収支 };
牌譜の形式と同じ。
終局 (jieju)
type = 'jieju'; data = { defen: [ 36700, 25300, 19700, 18300 ], // 半荘終了時の得点 rank: [ 1, 2, 3, 4 ], // 順位 point: [ 47, 5, -20, -32 ] // オカ・ウマを含めたポイント数 };
defen
- 牌譜のそれと同じ。
rank
- 牌譜のそれと同じ。
point
- 牌譜のそれと同じ。電脳麻将のウマは +20, +10 -10, -20。
Playerからの応答メッセージ
基本形式
Gameからの通知メッセージ同様 type
と data
からなるが、data
は構造を持たないただの文字列である。また、Player側の動作がない場合(表中の「−」のパターン)は
type = ''; data = null;
で応答する。
副露 (fulou)
type = 'fulou'; data = 'p78-9';
副露面子を「面子」の形式で返す。上記の例は という副露。
槓 (gang)
type = 'gang'; data = 's505+5';
暗槓または加槓した面子を「面子」の形式で返す。上記の例は に対して を加槓。
流局 (pingju)
type = 'pingju'; data = null;
流局する意思だけ示す。Playerの意思で流局できるケースは九種九牌のみ。
次回は、Game、Player 間通信の実際の実装方法を説明する予定。