雀魂の部屋別の戦績を確認する

:::note warn
**スクリプトの利用は、自己責任でお願いします。**
:::

雀魂の部屋別の戦績を取得するスクリプトです。開発者ツールで実行できます。
**[牌譜屋](https://amae-koromo.sapk.ch/)にないデータ**（銅の間と銀の間での戦績や、被ツモ率、横移動率）の確認が可能です。[^dif]


```JavaScript
window.to_stats = (res, is_recent) => {
    const room_names = "銅, 銀, 金, 玉, 王, 王".split(", ");
    const rule_names = "一, 東, 南, CPU, 一".split(", ");

    const stats = [];
    const types = mjcore.E_Round_Result;
    const all_types = Object.values(types).filter(Number.isInteger);
    const rank_stat = res.detail_data.rank_statistic;
    const st_name = is_recent ? "month_statistic" : "total_statistic";

    if (is_recent) {
        stats.since = new Date(rank_stat.month_refresh_time * 1000);
    }
    rank_stat[st_name].level_data_list.map(RankLevelData =>
        RankLevelData.statistic.game_mode.map(({
            mode,
            round_end,
            fly_count,
            game_final_position,
            game_count_sum,
            ming_count_sum,
            liqi_count_sum,
            xun_count_sum,
            dadian_sum,
        }) => {
            const sum = (..._types) => round_end
                .filter(r => _types.includes(r.type))
                .map(r => r.sum)
                .reduce((p, c) => p + c, 0);
            const rounds = sum(...all_types);
            const hora_sum = sum(types.zimo, types.rong);
            const rank = RankLevelData.rank_level - 1;

            return {
                _sort_index: rank * 2 + mode,
                is3: mode >= 10,
                games: game_count_sum,
                room_name: room_names[rank] + rule_names[mode % 10],

                heichaku: game_final_position.reduce((p, c, i) => p + c * (i + 1)) / game_count_sum,
                first: game_final_position[0] / game_count_sum,
                second: game_final_position[1] / game_count_sum,
                third: game_final_position[2] / game_count_sum,
                forth: game_final_position[3] / game_count_sum || 0,

                hora: hora_sum / rounds,
                hoju: sum(types.fangchong) / rounds,
                tsumo: sum(types.zimo) / hora_sum || 0,
                furo: ming_count_sum / rounds,
                riichi: liqi_count_sum / rounds,

                tobi: fly_count / game_count_sum,
                hitsumo: sum(types.beizimo) / rounds,
                ryukyoku: sum(types.liuju) / rounds,
                yokoido: sum(types.shaoji) / rounds,
                junsu: xun_count_sum / hora_sum || 0,
                daten: dadian_sum / hora_sum || 0,
            };
        })
    ).forEach(stat => stats.push(...stat));

    return stats.sort((a, b) => a._sort_index < b._sort_index ? -1 : 1);
};


window.show_stats = (stats, is3 = false) => {
    const chart_colors = [
        "hwb(134 36% 30%)",
        "hwb(188 29% 23%)",
        "hwb(208 62% 46%)",
        "hwb(354 41% 9%)",
    ];
    const chart_length = 20;

    if (stats.since) {
        console.log("since", stats.since.toLocaleString("ja-JP"));
    }
    for (const st of stats) {
        if (st.is3 === !is3) continue;
        const stat_values = Object.entries({
            "": `${st.games}戦`.padStart(6, " "),
            和: st.hora,
            放: st.hoju,
            被: st.hitsumo,
            流: st.ryukyoku,
            横: st.yokoido,
            自: st.tsumo,
            副: st.furo,
            立: st.riichi,
            着: st.heichaku.toFixed(2),
            // 飛: st.tobi,
            // 巡: st.junsu.toFixed(1).padStart(4, " "),
            // 打: st.daten.toFixed(0).padStart(4, " "),
        }).map(([k, v]) => k + (
            typeof v === "string" ? v : (v * 100).toFixed(1).padStart(4, " ")
        ));
        const chart_ratios = st.is3
            ? [st.first, 0, st.second, st.third]
            : [st.first, st.second, st.third, st.forth];
        const chart_labels = chart_ratios.map(r =>
            `${Math.round(r * 100) || ""}`
        );
        const chart_styles = chart_ratios.map((r, i) => `
            padding: 0 ${(r * chart_length - chart_labels[i].length / 2) / 2}em;
            background-color: ${chart_colors[i]};
            color: white;
            line-height: 1.5;
        `);
        console.log(`${st.room_name}  %c${chart_labels.join("%c")}%c ${stat_values.join(" ")}`, ...chart_styles, "");
    }
};



window.get_stats = (account_id = 0, is3 = false, is_recent = false) => {
    account_id ||= GameMgr.Inst.account_id;

    const zone_icons = "Ⓒ, Ⓙ, Ⓔ".split(", ");
    const verified_icons = ", 🐾, 𝙋".split(", ");
    const rank_names = "初, 士, 傑, 豪, 聖, 魂, 魂".split(", ");
    const max_scores = [20, 80, 200, 600, 800, 1000, 1200, 1400, 2000, 2800, 3200, 3600, 4000, 6000, 9000];

    const zone = zone_icons[game.Tools.get_zone_id(account_id) - 1];
    const rule = is3 ? "三麻" : "四麻";

    app.NetAgent.sendReq2Lobby("Lobby", "fetchAccountInfo", {account_id}, (err, res) => {
        if (err || res.error) return;
        const verified = verified_icons[res.account.verified];
        const name = `${zone} ${res.account.nickname}${verified}`;

        const level = is3 ? res.account.level3 : res.account.level;
        const rank_id = `${level.id}`[2] - 1;
        const rank_lv = (level.id % 100) - 1;
        const rank = rank_names[rank_id] + (rank_lv + 1);

        const konten = rank_id === rank_names.length - 1;
        const score = level.score / (konten ? 100 : 1);
        const max_score = max_scores[rank_id * 3 + rank_lv] || 20;
        console.log(`%c${name}%c  ${rule}  ${rank} ${score}/${max_score}`, "font-size: 1.3em;", "");

        app.NetAgent.sendReq2Lobby("Lobby", "fetchAccountStatisticInfo", {account_id}, (_err, _res) => {
            if (_err || _res.error) return;
            show_stats(to_stats(_res, is_recent), is3);
        });
    });
};

get_stats();
// get_stats(0, true); // 三人麻雀の場合
```


[^dif]: ただし、流局率と平均打点が、牌譜屋の値と異なります。