koba::blog

小林聡: プログラマです

牌画入力ツール

麻雀の牌姿を入力する「牌画入力ツール」はネット上にいろいろある*1 *2が、どれも画面上で牌をクリックして作成する仕様なので大変面倒くさい。そこで簡単な記法を牌画に変換するツールを作ってみた。

このような記述ができます。

{s067z1 z1}(ツモ) {p2-13} {z66=6-6} {_z77_} {  }(ドラ){m1}

s0s6s7z1 z1(ツモ) p2-p1p3 z6z6=z6-z6 _z7z7_ (ドラ)m1

記法

  • { と } ではさまれた部分を牌姿とする。
  • m: 萬子、p: 筒子、s: 索子、z: 字牌m1m1z6z6
  • 赤牌は 0 で表す。p0p0
  • 同じ色が連続するときは mpsz を省略できる。m1m2m3p4p5p6s7s8s9m123p456s789 としてもよい。
  • 横向きの牌には - を付加する。s1-s1-
  • 加槓のように上に重ねる牌には = を付加する。m5m5m5=m0-m555=0-
  • 裏向きの牌は _ で表す。_z2z2__z22_
  • { と } の間の空白文字は連続しても1つにまとめられないので幅の調整に利用できる。

CSS

横向きの牌は横向きの画像を使っている*3ので基本的には画像を並べているだけであるが、2点工夫している。

まず、牌姿全体を <span style="white-space:pre;"> 囲んでいる。これにより記法中の空白文字の幅が保存されるし、牌姿の途中で改行されてしまうこともない。

次に、重ねる牌だが <span style="display:inline-block;width:34px;"> とした固定幅のボックスの中に入れることで実現している。また vertical-align のデフォルト値は baseline なので、これを bottom に変えることで隙間が出ないようにした。*4

JavaScript

まず以下の正規表現で記法部分を切り出す。

    function parse(text) {
        var html = text.replace(/{(.*?)}/g, markup);
        return html;
    }

? は最短マッチを指定するので、これで { と } が正しく対応するようになる。マッチした部分は関数 markup() に渡される。第1パラメータはマッチした全体、第2パラメータ以下は丸括弧で指定された部分文字列になる。

    function markup(match, mark) {
  
        if (! mark.length) return '';    // '{}' とされたら無視

        /* 牌姿全体のHTMLの開始 */
        var html = '<span style="white-space:pre;">', url, v = 0;
                                                      // 重ね中は v = 1 とする

        /* 表向き牌、空白、裏向き牌、その他に分ける */
        for (var pai of mark.match(/[mpsz](?:\d+[\-\=]?)+|[ _]|.+/g)) {

            if (pai == ' ') {                                // 空白
                html += ' ';
            }
            else if (pai == '_') {                           // 裏向き牌
                url = imgbase + img._;
                html += '<img src="'+url+'" width="'+w+'" height="'+h+'"'
                        + ' alt="'+pai+'">';
            }
            else if (pai.match(/^[mpsz](?:\d+[\-\=]?)+/)) {  // 表向き牌

                /* 牌を s: mpsz, n: 0〜9、d: -= に分けて処理する */
                var s = pai[0];
                for (var n of pai.match(/\d[\-\=]?/g)) {
                    var d = n[1] || ''; n = n[0];
                    if (d == '=' && ! v) {        // 重ねの開始
                        html += '<span style="display:inline-block;width:'
                                    +h+'px;">';
                        v = 1;
                    }
                    else if (! d) {               // 重ねの終了
                        if (v) html += '</span>';
                        v = 0;
                    }

                    if (d == '-' || d == '=') {   // 横向きの牌
                        url = imgbase + img[s+n+'_'];
                        html += '<img src="'+url+'" width="'+h+'" height="'+w+'"'
                                + (d == '='
                                        ? ' style="vertical-align:bottom;"'
                                        : '')
                                + ' alt="'+(s+n+d)+'">';
                    }
                    else {                        // 普通の牌
                        url = imgbase + img[s+n];
                        html += '<img src="'+url+'" width="'+w+'" height="'+h+'"'
                                + ' alt="'+(s+n)+'">';
                    }
                }
            }
            else {                                // その他
                html += '<span style="color:red;">' + pai + '</span>'
            }
        }
        if (v) html += '</span>';  // 不正に重ねを抜けたときも忘れずに閉じる
        html += '</span>';         // 全体の span を閉じる
  
        return html;
    }

記法部分を、

表向き牌
/[mpsz](?:\d+[\-\=]?)+/
裏向き牌
/_/
空白
/ /
その他

に分けて処理している。「その他」の場合は記法に誤りがあったときなので、赤字で表示した。

2017-07-22 追記

加槓の牌画がFirefoxで正しく表示されないバグがあったので修正しました。v0.8.7以降は修正済みです。