2021/12/31(金)コミックマーケット99にサークル参加しました

2021/12/31 20:02 同人技術::Android技術::Python
一昨年の コミックマーケット97 以来、本当に久しぶりのサークル参加となりました。

コミックマーケット99スペース風景


想像していたよりも人通りが少なく、印刷部数を控えめにしてもなお余りまくるという状況でしたが、この場所に戻ってこられたことが本当に嬉しいです。スペースに来てくださったみなさま、ありがとうございました。

同人誌即売会にとって厳しいご時世はまだまだ続きそうですが、新型コロナに奪われた大切なものを取り返すための第一歩として、とても良いイベントになったと思います。これからも着実に前へ進んでいきましょう。

2021/12/30(木)コミックマーケット99にサークル参加します

2021/12/30 21:55 同人技術::Android技術::Python
コミックマーケット99 が始まりました。Thunder Clawは明日の2日目にサークル参加します。スペースは 東ト-28a です。今回は以下の3冊をご用意してお待ちしていますので、お時間のある方は遊びに来ていただければ幸いです。

コミックマーケット99お品書き


『端末何台持てる?』 は、本来であれば昨年の 技術書典8コミックマーケット98 で頒布する予定だった本です。AndroidエンジニアやQAエンジニア向けに、OSバージョンごとの挙動の変更点と、その変更点を実機テストで確認する際のポイントをまとめました。

この『端末何台持てる?』は 技術書典応援祭エアコミケ にて電子版を頒布しています。今回印刷するにあたって一部加筆修正をしてはいますが、すでに電子版をお持ちの方が改めてお求めいただくほどの変更点はありません。ご注意ください。

『Androidを 実機で テストしろ!!』 は、『端末何台持てる?』よりも広い範囲の方々を対象とした実機テストのガイドブックです。具体的には、何らかの形でAndroidアプリの開発や運用に関わっており、そのアプリの品質を高めたいと思っているすべての方を対象としています。

この『Androidを 実機で テストしろ!!』は将来的に100ページを超える本になる予定ですが、今回はロケテスト版として一部の内容を抜粋しました。なお、 例のゲーム をご存じの方は、そのリズムと勢いでタイトルを読んでください。

『PythonにAndroidを求めるのは間違っているのだろうか』 は、 コミックマーケット97 で頒布した既刊です。まぁ、間違ってるよね。

2021/10/23(土)雀魂の牌譜をNAGAに解析させる

2021/10/23 19:12 ゲーム::雀魂技術::Python
(2021-12-12追記) 本エントリの内容は現在でも有効なものですが、より簡単に実践できる新しい仕組みを作りましたので、今後はその手順で解析することをお勧めします。詳しくは 雀魂の牌譜をNAGAに解析させる-完全版- をご覧ください。

0. はじめに

10月20日のアップデートにより、麻雀AIの NAGA にオリジナルの牌譜を解析させる機能が追加されました。これまでは 天鳳 の牌譜を対象としたサービスでしたが、これからはどんな牌譜でも解析させられるのです。

私もさっそく 雀魂 の牌譜を解析させようと思ったものの、かといって 天鳳牌譜エディタ にポチポチ入力するのは現実的でありません。というわけで、もっと簡単に解析対象のデータを作成する仕組みを考えてみました。

1. 牌譜データをダウンロードする

前提として、 NAGAで牌譜の解析をさせるには、天鳳牌譜エディタ形式のURLが必要です 。牌譜エディタのURLは https://tenhou.net/6/#json=牌譜データ という形式なので、この 牌譜データ の部分に雀魂の牌譜を突っ込むことが最終目標になります。

とはいえ、この牌譜データを手で作るのはあまりにも大変です。というより、それならば素直に牌譜エディタを使うべきです。では、ほかに何かいい方法はないでしょうか。

少し話は変わりますが、麻雀AIの Akochan に天鳳や雀魂の牌譜を解析させる Akochan Reviewer というシステムがあります。そして、Akochan Reviewerで雀魂の牌譜を解析させるには、事前に牌譜データをダウンロードしなければいけません。

そうです。 牌譜データをダウンロードしなければいけない のです。そして、ダウンロードするための仕組みはすでに用意されています。せっかくですから、ありがたく乗っからせてもらいましょう。以下の手順でダウンロードの準備をしてください。
このとき、 downloadlogs.jsNAMEPREF の値を 0 に設定しておきましょう。ダウンロードする牌譜データ内の役名が日本語になります。

さて、以上でダウンロードの準備が完了しました。雀魂にログインし、対象の牌譜画面でキーボードの S キーを押してください。これまでの手順に誤りがなければ、牌譜データのダウンロードが始まるはずです。

(謝辞) 本文中にもあるとおり、本節の手順はAkochan Reviewerの仕組みを流用させていただきました。Akochan Reviewerを開発されている Equimさん に感謝いたします。ありがとうございました。

2. 牌譜データをNAGAが解析できる形式に変換する

前節の手順で牌譜データをダウンロードできるようになりましたが、残念ながらこのままではNAGAに読み込ませることができません。2点ほど手を加える必要があります。

2-1. 局ごとのデータに分割する

先ほどダウンロードした牌譜データは、JSONの中に半荘すべてのデータが含まれています。
// ダウンロードした牌譜データ
{
    "title": [...],
    "name": [...],
    "rule": [...],
    "log": [
        [東1局の牌譜],
        [東2局の牌譜],
        ...
    ],
    ...
}
NAGAでは局単位の牌譜データを読み込むため、以下のように局数分のJSONに分割してあげる必要があります。
// 東1局
{
    "title": [...],
    "name": [...],
    "rule": [...],
    "log": [
        [東1局の牌譜]
    ]
}

// 東2局
{
    "title": [...],
    "name": [...],
    "rule": [...],
    "log": [
        [東2局の牌譜]
    ]
}

...
titlenamerulelog 以外の項目は削除しても問題ありません。

2-2. 役名を変換する

和了役に風牌が含まれる場合、そのままではNAGAに読み込ませることができません。雀魂では「場風牌」「自風牌」という表記であるのに対し、NAGA(というよりも天鳳)では「場風 東」「自風 南」のように牌の種類まで含まれているからです。役の名前は牌譜データの中に直接文字列で埋め込まれているので、天鳳の形式に書き換えてしまいましょう。

(2021-10-29追記) ダブルリーチについても、雀魂では「ダブル立直」、NAGAでは「両立直」と表記が異なります。こちらもNAGAの形式に書き換えましょう。

2-3. 退屈なことはPythonにやらせよう(2021-10-29更新)

以上の対応で https://tenhou.net/6/#json="title":[...],"name":[...],"rule":[...],"log":[[...]] のようなURLを組み立てられるようになりました。これでNAGAに牌譜を解析させることができますね。お疲れ様でした!

……で、終わりにしてしまうのも芸がないので、ダウンロードした牌譜データを編集するPythonスクリプトを作成しました。このスクリプトは牌譜データを標準入力から受け取り、牌譜エディタのURLを標準出力に出力します。
import json
import sys

def create_viewer_urls(soul_json):
    """
    雀魂の牌譜JSONを牌譜エディタURL群に変換する。
    
    Args:
        soul_json (str): 雀魂の牌譜JSONを指定する。
    Returns:
        list[str]: 牌譜エディタURL群を返す。
    """
    # 雀魂の牌譜JSONを辞書に変換する。
    soul_paifu = json.loads(soul_json)
    
    # title内の卓名を雀魂っぽく変換する。
    # 
    # 変換前のtitle:
    #     "title": [ "玉の間南喰赤", "2021/10/20 20:48:01" ]
    # 変換後のtitle:
    #     "title": [ "玉の間四人南", "2021/10/20 20:48:01" ]
    title = soul_paifu['title'].copy()
    title[0] = to_soul_table(title[0])
    
    # rule内の卓名を雀魂っぽく変換する。
    # 
    # 変換前のrule:
    #     "rule": { "disp": "玉の間南喰赤", "aka53": 1, "aka52": 1, "aka51": 1 }
    # 変換後のrule:
    #     "rule": { "disp": "玉の間四人南", "aka53": 1, "aka52": 1, "aka51": 1 }
    rule = soul_paifu['rule'].copy()
    rule['disp'] = to_soul_table(rule['disp'])
    
    # logを局ごとのデータに分割し、牌譜エディタURL群として返す。
    return ['https://tenhou.net/6/#json=' + json.dumps({
        'title': title,
        'name': soul_paifu['name'],
        'rule': rule,
        'log': [to_naga_log(log)],
    }, ensure_ascii=False, separators=(',', ':')) for log in soul_paifu['log']]

def to_soul_table(tenhou_table):
    """
    卓名を雀魂っぽく変換する。
    
    Args:
        tenhou_table (str): 天鳳っぽい卓名を指定する。
    Returns:
        str: 雀魂っぽい卓名を返す。
    """
    # 表記の好みの問題なので、必ずしも必要となる処理ではない。
    return tenhou_table.replace('南喰赤', '四人南')

def to_naga_log(soul_log):
    """
    局データをNAGAが解析可能な形式に変換する。
    
    Args:
        soul_log (list[list]): 雀魂形式のlogを指定する。
    Returns:
        list[list]: NAGAで解析可能な形式のlogを返す。
    """
    # 流局時のデータは変換の必要がない。
    if len(soul_log[16]) < 3:
        return soul_log
    naga_log = soul_log.copy()
    
    # 当該局の場風を設定する。
    # 
    # 局を表す数字と意味:
    #     0 => 東1局, 1 => 東2局, ...
    prevalent = ['東', '南', '西', '北'][naga_log[0][0] // 4]
    
    # 当該局の和了者の自風を設定する。
    # 
    # 算出方法:
    #     (和了者のプレイヤー番号 - 親の位置) % 4
    seat = ['東', '南', '西', '北'][
        (max(enumerate(naga_log[16][1]), key=lambda x: x[1])[0] - (naga_log[0][0] % 4)) % 4
    ]
    
    # 役名をNAGAが解析可能な表記に変換する。
    naga_log[16][2][4:] = [to_naga_hand(hand, prevalent, seat) for hand in naga_log[16][2][4:]]
    
    # 変換後のlogを返す。
    return naga_log

def to_naga_hand(hand, prevalent, seat):
    """
    役名をNAGAが解析可能な表記に変換する。
    
    Args:
        hand (str): 和了役を指定する。
        prevalent (str): 場風を指定する。
        seat (str): 和了者の自風を指定する。
    Returns:
        str: NAGAで解析可能な表記の役名を返す。
    """
    # 対応が必要な役が判明次第、随時追加する。
    if hand == '役牌:場風牌(1飜)':
        return f"場風 {prevalent}(1飜)"
    elif hand == '役牌:自風牌(1飜)':
        return f"自風 {seat}(1飜)"
    elif hand == 'ダブル立直(2飜)':
        return '両立直(2飜)'
    else:
        return hand

if __name__ == '__main__':
    for url in create_viewer_urls(''.join(sys.stdin.readlines())):
        print(url)

2-4. プログラミングはわからにゃいけどNAGAを使いたいにゃ!(2021-10-24追記)

Pythonを動かすための環境を用意できない場合は、Web上でプログラムを実行するサービスを利用しても良いでしょう。

まず、先ほどのスクリプトを少し変更します。最後の3行を以下のように書き換えた上で、 ※ここに牌譜データを貼り付けるにゃ! の部分に牌譜データを埋め込んでください。
if __name__ == '__main__':
    soul_json = """
    ※ここに牌譜データを貼り付けるにゃ!
    """
    for url in create_viewer_urls(soul_json):
        print(url)
続いて、 Paiza.io にアクセスします。入力欄(背景色が黒い領域)に変更後のスクリプトを貼り付け、実行ボタンを押してください。スクリプトに誤りがなければ、画面下部のコンソールに牌譜エディタのURLが表示されるはずです。

また、 雀魂の牌譜をNAGA解析する方法|アトリエ@凛凛、凛世|note ではGoogleスプレッドシートを使う仕組みが紹介されています。本エントリの内容はすっぱり忘れて、そちらの手順で進めるのもひとつの方法でしょう。

3. 遊びのはずなのに仕事のような障害報告をしている件(2022-01-21更新)

何度か解析を行う中で、NAGA側の不具合と思われる事象にも遭遇しました。以下の問題はすべてサポートに連絡しています。

自分で言うのもアレですが、連絡に際してはかなり質の高いレポートを送っているつもりです。 一応プロなので
  • アガリ時の点数申告画面で赤ドラと裏ドラの表記が逆になっている
    • 2021年10月20日に報告
    • 2021年10月22日に解消の報告を受領
  • カンが含まれる牌譜を読み込めない
    • 2021年10月22日に報告
    • 2021年10月25日に解消の報告を受領
  • 同一プレイヤーが2巡続けて同一の牌を切り、その両方を別々のプレイヤーが鳴いた局の牌譜を読み込めない
    • 2022年1月5日に報告
    • 2022年1月6日に原因の報告を受領
    • 2022年1月21日に解消の報告を受領
いずれも迅速にご対応いただけました。ありがとうございました。

3つ目の事象は 牌譜ビューア で見たほうがわかりやすいかもしれません。Dさんのリーチ宣言牌と次巡の捨て牌がいずれも七萬で、宣言牌はBさんがポン、次の牌はAさんがチーしています。このようなケースで読み込みに失敗するという事象でした。

4. おわりに

すべての牌譜をNAGAに読み込ませてしまうと、九種九牌や四風連打の局でも20ポイント消費することになるので、対象の局はしっかりと取捨選択しましょう。