Greasy Fork

Greasy Fork is available in English.

天鳳牌理好形表示

天鳳牌理で一向聴の好形率を表示する

当前为 2021-11-07 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         天鳳牌理好形表示
// @namespace    http://tanimodori.com/
// @version      0.0.4
// @description  天鳳牌理で一向聴の好形率を表示する
// @author       Tanimodori
// @match        https://tenhou.net/2/*
// @grant        none
// @license      MIT
// ==/UserScript==

const S_P_QUERY = "一般形(七対国士を含まない)の計算結果 / 標準形";
const S_Q_QUERY = "標準形(七対国士を含む)の計算結果 / 一般形";
const S_YIISHANTEN = "1向聴";
const S_YIISHANTEN_ALL = "標準形1向聴";
const MJ_RE = /([0-9]+[mpsz])+/gm;

let mjtiles = (input) => {
    let result = [];
    let stk = "";
    for(let tile_i of input) {
        if("0" <= tile_i && tile_i <= "9") {
            stk += tile_i;
        } else {
            for(let tile_j of stk) {
                result.push(tile_j + tile_i);
            }
            stk = "";
        }
    }
    return result;
}

let mjcomp = (a, b) => {
    let [a_n, a_t] = a;
    let [b_n, b_t] = b;
    if(a_n === "0") a_n = 5.5;
    if(b_n === "0") b_n = 5.5;
    return a_t !== b_t ? (a_t < b_t ? -1 : 1) : Number(a_n) - Number(b_n);
}

const MJ_13ORPHAN_TILES = mjtiles("19m19s19p1234567z");
const MJ_TILES = mjtiles("123456789m123456789s123456789p1234567z");

let global_show_all_result = false;

let mjaka = (tile) => {
    if(tile[0] === "0" || tile[0] === "5") {
        return String(5-Number(tile[0])) + tile[1];
    }
    return tile;
}

let mjsub = (mjarr, ...tiles) => {
    if(mjarr.mjfail) return;
    for(let tile of tiles) {
        let index = mjarr.indexOf(tile);
        if(index != -1) {
            mjarr.splice(index, 1);
            continue;
        }
        index = mjarr.indexOf(mjaka(tile));
        if(index != -1) {
            mjarr.splice(index, 1);
            continue;
        }
        mjarr.mjfail = true;
        return mjarr
    }
    return mjarr
}

let mj7toi = (mjarr) => {
    if(mjarr.length != 14) {
        return false;
    }
    mjarr.sort(mjcomp);
    for(let i = 0; i <= 12; i += 2) {
        if(mjarr[i] !== mjarr[i+1] && mjarr[i] !== mjaka(mjarr[i+1])) {
            return false;
        }
    }
    return true;
}

let mj13orphan = (mjarr) => {
    if(mjarr.length != 14) {
        return false;
    }
    mjsub(mjarr,MJ_13ORPHAN_TILES);
    if(!mjarr.mjfail && MJ_13ORPHAN_TILES.indexOf(mjarr[0]) !== -1) {
         return true;
    }
    return false;
}

let mjagaricache = {};

let mjagari = (mjarr) => {
    if(mjarr.mjfail || mjarr.length % 3 === 1) return false;
    if(mjarr.length == 0) {
        return true;
    }
    if(global_show_all_result) {
        if(mj7toi([...mjarr])) return true;
        if(mj13orphan([...mjarr])) return true;
    }
    let joined_result = mjarr.sort(mjcomp).join("").replace("0","5");
    if(joined_result in mjagaricache) {
        return mjagaricache[joined_result];
    }
    let result = mjagari_raw(mjarr);
    mjagaricache[joined_result] = result;
    return result;
}

let mjagari_raw = (mjarr) => {
    let tile = mjarr[0];
    let [tile_num, tile_type] = tile;
    // toitsu
    if(mjarr.length % 3 === 2) {
        if(mjagari(mjsub([...mjarr],tile,tile),false)) return true;
    }
    // kootsu
    if(mjagari(mjsub([...mjarr],tile,tile,tile),false)) return true;
    // shuntsu
    if(tile_type !== "z") {
        let tile_number = Number(tile_num);
        if(tile_number === 0) tile_number = 5;
        let shuntsu_tiles_groups = [tile_number-2, tile_number-1, tile_number].filter(x=>x>=1&&x<=7).map(x=>[x+tile_type,x+1+tile_type,x+2+tile_type]);
        for(let s_tiles of shuntsu_tiles_groups) {
            if(mjagari(mjsub([...mjarr],...s_tiles),false)) return true;
        }
    }
    return false;
}

let mjnokori = (mjarr, tile) => {
    return 4 - mjarr.filter(x => x===tile||x===mjaka(tile)).length;
}

let mjmachi = (mjarr) => {
    if(mjarr.length % 3 !== 1) return [];
    return MJ_TILES.filter(tile => {
        if(mjnokori(mjarr,tile) === 0) return false; // ikasama
        return mjagari([...mjarr, tile]);
    });
}

let mjtenpaikei = (mjarr) => {
    if(mjarr.length % 3 === 1) return {};
    let result = {}
    result.nokori_max = 0;
    let unique = (value, index, self) => self.indexOf(value) === index;
    for(let tile of mjarr.filter(unique)) {
        let machi = mjmachi(mjsub([...mjarr],tile));
        if (machi.length > 0) {
            result[tile] = {};
            result[tile].nokori = 0;
            for(let machihai of machi) {
                let nokori = mjnokori([...mjarr,tile], machihai);
                result[tile][machihai] = nokori;
                result[tile].nokori += nokori;
            }
            result.nokori_max = Math.max(result.nokori_max, result[tile].nokori);
        }
    }
    return result;
}


let inject_css = () => {
    let styles = `
    .D {
      position: relative;
    }
    .D .popup {
      visibility: hidden;
      width: 300px;
      background-color: #ddd;
      color: #fff;
      text-align: center;
      border-radius: 6px;
      padding: 8px 0;
      position: absolute;
      z-index: 1;
      top: 125%;
      left: 50%;
      margin-left: -150px;
    }
    .D .popup::before {
      content: "";
      position: absolute;
      top: calc(0% - 10px);
      left: 50%;
      margin-left: -5px;
      border-width: 5px;
      border-style: solid;
      border-color: transparent transparent #ddd transparent;
    }
    .D .popup.show {
      visibility: visible;
    }
    .D .popup table {
        text-align: initial;
        margin-left: auto;
        margin-right: auto;
    }
`
    let styleSheet = document.createElement("style");
    styleSheet.type = "text/css";
    styleSheet.innerText = styles;
    document.head.appendChild(styleSheet);
}

let create_node_tile_img = (tile) => {
    let img_node = document.createElement("img");
    img_node.setAttribute("src", "https://cdn.tenhou.net/2/a/"+tile+".gif");
    img_node.setAttribute("border", "0");
    return img_node
}

let create_node_tile = (tile, link) => {
    let a_node = document.createElement("a");
    if(link) a_node.setAttribute("href", link);
    a_node.setAttribute("class", "D");
    a_node.appendChild(create_node_tile_img(tile));
    return a_node;
}

let create_node_td = (...children) => {
    let td = document.createElement("td");
    for(let child of children) {
        td.appendChild(child);
    }
    return td;
}

let mouse_over_node = (node, info) => {
    let popups = node.getElementsByClassName("popup");
    let popup;
    if(popups.length === 0) {
        popup = document.createElement("div");
        let tiles = mjtiles(info.link.substring(3));
        for(let tile of tiles){
            popup.appendChild(create_node_tile_img(tile));
        }
        popup.appendChild(document.createElement("br"));
        let table = document.createElement("table");
        table.setAttribute("cellpadding", 2);
        table.setAttribute("cellspacing", 0);
        let tbody = document.createElement("tbody");
        table.appendChild(tbody);
        let keys_valid = Object.keys(info).filter(x => MJ_TILES.indexOf(x.replace("0","5")) !== -1).sort((x,y) => {
            let nokori_diff = info[y].nokori-info[x].nokori;
            return nokori_diff === 0 ? mjcomp(x,y) : nokori_diff;
        });
        for(let key of keys_valid) {
            let tr = document.createElement("tr");
            let info_local = info[key];
            tr.appendChild(create_node_td(document.createTextNode("打")));
            tr.appendChild(create_node_td(create_node_tile_img(key)));
            tr.appendChild(create_node_td(document.createTextNode("待ち[")));
            let machis = []
            for(let key_local of Object.keys(info_local)) {
                if(MJ_TILES.indexOf(key_local) === -1) continue;
                machis.push(create_node_tile_img(key_local));
            }
            tr.appendChild(create_node_td(...machis));
            tr.appendChild(create_node_td(document.createTextNode(info_local.nokori + "枚")));
            tr.appendChild(create_node_td(document.createTextNode(" ]")));
            tbody.appendChild(tr);
        }
        popup.appendChild(table);
        popup.classList.add("popup");
        node.appendChild(popup);
    } else {
        popup = popups[0];
    }
    popup.classList.toggle("show");
}

let mouse_out_node = (node) => {
    let popup = node.getElementsByClassName('popup')[0];
    popup.classList.toggle("show");
}


let run = () => {
    // check
    let tehai = document.getElementById("tehai");
    let m2 = document.getElementById("m2");
    if(!tehai) return;
    if(!m2) return;

    if(m2.textContent.startsWith(S_P_QUERY)){
        global_show_all_result = false;
    } else if (m2.textContent.startsWith(S_Q_QUERY)){
        global_show_all_result = true;
    } else {
        return;
    }
    if(!tehai.textContent.startsWith(S_YIISHANTEN)) {
        if(!global_show_all_result || !tehai.textContent.startsWith(S_YIISHANTEN_ALL)) {
           return;
        }
    }

    // parse hands
    let info = m2.getElementsByTagName("textarea")[0].textContent;
    let matches = info.match(MJ_RE).map(mjtiles);

    // calculate tenpaikei
    let hands = matches[0].sort(mjcomp);
    let tenpaikeis = {};
    for(let i = 1; i < matches.length; i += 2) {
        let sutehai = matches[i][0];
        let tsumohais = matches[i+1];
        let tenpaikeis_local = {};
        tenpaikeis_local.koukei = 0;
        tenpaikeis_local.gukei = 0;
        tenpaikeis_local.koukeihais = [];
        tenpaikeis_local.gukeihais = [];
        for(let tsumohai of tsumohais) {
            let hands_local = [...mjsub([...hands], sutehai), tsumohai];
            let tenpaikei_local = mjtenpaikei(hands_local);
            tenpaikei_local.link = "?" + (global_show_all_result ? "q" : "p" ) + "=" + hands_local.join("");
            tenpaikei_local.nokori_self = mjnokori(hands, tsumohai);
            if(tenpaikei_local.nokori_max > 4) {
                tenpaikeis_local.koukei += tenpaikei_local.nokori_self;
                tenpaikeis_local.koukeihais.push(tsumohai);
            } else {
                tenpaikeis_local.gukei += tenpaikei_local.nokori_self;
                tenpaikeis_local.gukeihais.push(tsumohai);
            }
            tenpaikeis_local[tsumohai] = tenpaikei_local;
        }
        tenpaikeis[sutehai] = tenpaikeis_local;
    }

    // display tenpaikei
    inject_css();
    let trs = m2.getElementsByTagName("tr");
    let sutehais = Object.keys(tenpaikeis);
    for(let i = 0; i < sutehais.length; ++i) {
        let tr = trs[i];
        let tds = tr.children;
        let tenpaikeis_local = tenpaikeis[sutehais[i]];
        tr.removeChild(tds[3]);
        tr.removeChild(tds[3]);
        let td_anchor = tds[3];
        let nokori_all = tenpaikeis_local.koukei+tenpaikeis_local.gukei;
        // modify
        // koukei
        if(tenpaikeis_local.koukei > 0) {
            let td_node = document.createElement("td");
            for(let koukeihai of tenpaikeis_local.koukeihais) {
                td_node.appendChild(create_node_tile(koukeihai, tenpaikeis_local[koukeihai].link));
            }
            tr.insertBefore(td_node, td_anchor);
            for(let i = 0; i < tenpaikeis_local.koukeihais.length; ++i) {
                let a_node = td_node.children[i];
                let koukeihai = tenpaikeis_local.koukeihais[i];
                a_node.addEventListener("mouseover", e => mouse_over_node(a_node, tenpaikeis_local[koukeihai]));
                a_node.addEventListener("mouseout", e => mouse_out_node(a_node));
            }
            let td_node2 = document.createElement("td");
            td_node2.appendChild(document.createTextNode("好形"+tenpaikeis_local.koukei+"枚"));
            tr.insertBefore(td_node2, td_anchor);
        } else {
            tr.insertBefore(document.createElement("td"), td_anchor);
            tr.insertBefore(document.createElement("td"), td_anchor);
        }
        // koukei - gukei
        if(tenpaikeis_local.koukei > 0 && tenpaikeis_local.gukei > 0) {
            let td_node3 = document.createElement("td");
            td_node3.appendChild(document.createTextNode("+"));
            tr.insertBefore(td_node3, td_anchor);
        } else {
            tr.insertBefore(document.createElement("td"), td_anchor);
        }
        // gukei
        if(tenpaikeis_local.gukei > 0) {
            let td_node = document.createElement("td");
            for(let gukeihai of tenpaikeis_local.gukeihais) {
                td_node.appendChild(create_node_tile(gukeihai, tenpaikeis_local[gukeihai].link));
            }
            tr.insertBefore(td_node, td_anchor);
            for(let i = 0; i < tenpaikeis_local.gukeihais.length; ++i) {
                let a_node = td_node.children[i];
                let gukeihai = tenpaikeis_local.gukeihais[i];
                a_node.addEventListener("mouseover", e => mouse_over_node(a_node, tenpaikeis_local[gukeihai]));
                a_node.addEventListener("mouseout", e => mouse_out_node(a_node));
            }
            let td_node2 = document.createElement("td");
            td_node2.appendChild(document.createTextNode("愚形"+tenpaikeis_local.gukei+"枚"));
            tr.insertBefore(td_node2, td_anchor);
        } else {
            tr.insertBefore(document.createElement("td"), td_anchor);
            tr.insertBefore(document.createElement("td"), td_anchor);
        }
        // all
        let td_node_all = document.createElement("td");
        td_node_all.appendChild(document.createTextNode("="+(nokori_all)+"枚 (" + Math.round(100 * tenpaikeis_local.koukei / nokori_all) + "%)"));
        tr.insertBefore(td_node_all, td_anchor);
    }
}

run();