Created
November 4, 2023 18:48
-
-
Save cjeon/6d8b834a2ea53652670d37a960e4de8a to your computer and use it in GitHub Desktop.
Bad move Korean
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// テンプレートリテラル用 | |
const getTemplate = raw => { | |
const rawObj = { raw: raw }; | |
return (...prm) => String.raw(rawObj, ...prm); | |
} | |
// 打牌に関わる文字列を成形する | |
const getDahaiStr = (playerSelectHtmlStr) => { | |
return playerSelectHtmlStr.innerHTML | |
.replaceAll('<span class="role">プレイヤー: </span>', '') | |
.replaceAll('<span class="role">Player: </span>', '') | |
.replaceAll('<span class="role">작사: </span>', '') | |
.replaceAll('<svg class="tile">', '') | |
.replaceAll('<use class="face" href="', '') | |
.replaceAll('"></use>', '') | |
.replaceAll('</svg>', '') | |
.replaceAll(' ', '') | |
} | |
// モータルの選択一覧を作成する | |
const getMortalSelects = (mortalSelectElements) => { | |
const mortalSelects = []; | |
mortalSelectElements.forEach((mortalSelect) => { | |
const recommendInt = mortalSelect.querySelectorAll('td')[2].querySelectorAll('span')[0].innerHTML; | |
const recommendFrac = mortalSelect.querySelectorAll('td')[2].querySelectorAll('span')[1].innerHTML; | |
mortalSelects.push({ | |
dahai: getDahaiStr(mortalSelect.querySelectorAll('td')[0]), | |
recommendation: parseFloat(recommendInt + recommendFrac) | |
}); | |
}); | |
return mortalSelects | |
} | |
// 手牌の情報を整理して返却する。 | |
const getTehaiState = (paiElements) => { | |
let tehai = []; | |
let draw = { kind: 'after-fu-ro', from: null, pai: null }; | |
paiElements.forEach(pai => { | |
const from = pai.getAttribute('before') | |
if (from == '自摸 ' || from == 'Draw ' || from == '쯔모 ') { | |
draw = { | |
kind: 'tsumo', | |
from: 'jicha', | |
pai: pai.querySelector('svg > use').getAttribute('href') | |
} | |
} else if (from == '上家打 ' || from == 'Kamicha👈 cuts ' || from == '상가👈 타 ') { | |
draw = { | |
kind: 'fu-ro', | |
from: 'kamicha', | |
pai: pai.querySelector('svg > use').getAttribute('href') | |
} | |
} else if (from == '対面打 ' || from == 'Toimen👆 cuts ' || from == '대면👆 타 ') { | |
draw = { | |
kind: 'fu-ro', | |
from: 'toimen', | |
pai: pai.querySelector('svg > use').getAttribute('href') | |
} | |
} else if (from == '下家打 ' || from == 'Shimocha👉 cuts ' || from == '하가👉 타 ') { | |
draw = { | |
kind: 'fu-ro', | |
from: 'shimocha', | |
pai: pai.querySelector('svg > use').getAttribute('href') | |
} | |
} else { | |
tehai.push(pai.querySelector('svg > use').getAttribute('href')) | |
}; | |
}); | |
return { | |
tehai: tehai, | |
draw: draw | |
} | |
} | |
// ページ全体 | |
const pageBody = document.querySelector('body') | |
// 半荘全体のデータを格納 | |
const hanchan = {} | |
// メタデータを取得 | |
hanchan.url = location.href | |
const rounds = pageBody.querySelectorAll('section') | |
const paifuJsonStr = rounds[0].querySelector('div > details > iframe').getAttribute('src') | |
.replace(/https:\/\/tenhou\.net\/.*json=/, ''); | |
const daniData = JSON.parse(paifuJsonStr) | |
hanchan.daniData = { | |
dan: daniData.dan, | |
name: daniData.name, | |
rate: daniData.rate, | |
sx: daniData.sx | |
} | |
hanchan.playerId = pageBody.querySelectorAll('details > dl > dd')[4].innerHTML | |
hanchan.rounds = [] | |
rounds.forEach(round => { | |
const turns = [] | |
const turnDetails = Object.values(round.querySelectorAll('div > details')).slice(3) | |
turnDetails.forEach(turnDetail => { | |
// 手牌情報取得用に、手牌のHTMLElemenstsを取得する | |
const paiElements = turnDetail.querySelectorAll('ul > li') | |
// プレイヤーの打牌のHTMLElementsを取得 | |
const dahaiElement = Object.values(turnDetail.querySelectorAll('span')).filter(e => e.className == 'role')[0].parentElement | |
// mortalの選択一覧のテーブルのtrタグをすべて取得 | |
const mortalSelectElements = turnDetail.querySelectorAll('details > table > tbody > tr') | |
const textFragment = turnDetail | |
.querySelector('summary').innerHTML | |
.replace('<span class="turn-info">', '') | |
.replaceAll(' ', ' ') | |
.replace('<span class="order-loss">', '') | |
.replaceAll('</span>', '') | |
// 集めた情報をターンの情報として格納していく | |
turns.push( | |
{ | |
summury: turnDetail.querySelector('summary').innerHTML.replace(/<span.*/, ''), | |
url: hanchan.url + '#:~:text=' + encodeURI(textFragment), | |
linkName: textFragment.replaceAll(' ', ' '), | |
tehaiState: getTehaiState(paiElements), | |
dahai: getDahaiStr(dahaiElement), | |
mortalSelects: getMortalSelects(mortalSelectElements) | |
} | |
) | |
}) | |
// 集めた局のデータを半荘データとして格納していく | |
hanchan.rounds.push({ | |
id: round.querySelector('h1 > div > a').getAttribute('href'), | |
name: round.querySelector('h1 > div > a').innerHTML, | |
turns: turns | |
}) | |
}); | |
let dahaiCount = 0 | |
let missCount = 0 | |
let furoCount = 0 | |
const missList = pageBody.querySelectorAll('fieldset > label')[4] | |
if (document.querySelector("body > h1").innerHTML == 'AI 복기') { | |
missList.insertAdjacentHTML('beforeend', '<br><span style="font-weight:700">악수:</span>') | |
} else if (document.querySelector("body > h1").innerHTML == 'Replay Examination') { | |
missList.insertAdjacentHTML('beforeend', '<br><span style="font-weight:700">Bad move:</span>') | |
} else { | |
missList.insertAdjacentHTML('beforeend', '<br><span style="font-weight:700">悪手リスト:</span>') | |
} | |
hanchan.rounds.forEach(round => { | |
round.turns.forEach(turn => { | |
// 推奨度の取得 | |
let recommendation = 0; | |
turn.mortalSelects.forEach(mortalSelect => { | |
if (turn.dahai == mortalSelect.dahai) { | |
recommendation = mortalSelect.recommendation | |
} | |
}) | |
dahaiCount++ | |
// 副露判定 | |
if (turn.tehaiState.draw.kind != "fu-ro") { | |
if (recommendation <= 5) { | |
missCount++ | |
console.debug(`【悪手】局:${round.name}, 巡目:${turn.summury}, 打牌:${turn.dahai}, 推奨度:${recommendation}, ${round.id}:~:text=${turn.summury}`) | |
missList.insertAdjacentHTML('beforeend', `<br><a href="${turn.url}">${round.name}${turn.linkName}</a>`); | |
} else { | |
console.debug(`局:${round.name}, 巡目:${turn.summury}, 打牌:${turn.dahai}, 推奨度:${recommendation}`) | |
} | |
} else { | |
furoCount++ | |
} | |
}) | |
}) | |
const missRate = new Intl.NumberFormat('ja', { style: 'percent', minimumFractionDigits: 2 }).format(missCount / (dahaiCount - furoCount)); | |
const missRateWithfuro = new Intl.NumberFormat('ja', { style: 'percent', minimumFractionDigits: 2 }).format(missCount / (dahaiCount)); | |
console.debug(`打牌選択:${dahaiCount}, 副露数:${furoCount}, 悪手率:${missCount}, 悪手率:${missRate}`) | |
pageBody.querySelectorAll('details > dl > dt').forEach(metadata => { | |
if (metadata.innerHTML == 'mjai-reviewerバージョン') { | |
metadata.insertAdjacentHTML('beforebegin', `<dt>悪手率</dt><dd>${missRate}</dd>`); | |
} else if (metadata.innerHTML == 'mjai-reviewer version') { | |
metadata.insertAdjacentHTML('beforebegin', `<dt>Bad move rate</dt><dd>${missRate}</dd>`); | |
} else if (metadata.innerHTML == 'mjai-reviewer 버전') { | |
metadata.insertAdjacentHTML('beforebegin', `<dt>악수율</dt><dd>${missRate}</dd>`); | |
} | |
}) | |
console.debug(hanchan) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment