function parseMixedNumber(input) {
const units = { '億': 100000000, '万': 10000 };
let total = 0;
let matched = false;
for (let unit in units) {
const regex = new RegExp('(\\d+(?:\\.\\d+)?)' + unit);
const match = input.match(regex);
if (match) {
total += parseFloat(match[1]) * units[unit];
input = input.replace(match[0], '');
matched = true;
}
}
if (input.match(/[一二三四五六七八九〇十百千万億]/)) {
total += kanjiToNumber(input);
matched = true;
} else if (input.trim().length > 0) {
total += parseFloat(input);
matched = true;
}
return matched ? total : NaN;
}
function kanjiToNumber(kanji) {
const nums = { '〇': 0, '一': 1, '二': 2, '三': 3, '四': 4, '五': 5, '六': 6, '七': 7, '八': 8, '九': 9 };
const units = { '十': 10, '百': 100, '千': 1000 };
const bigUnits = { '万': 10000, '億': 100000000 };
let total = 0;
const parseSection = (str) => {
let n = 0, temp = 0;
for (let c of str) {
if (nums[c] !== undefined) {
temp = nums[c];
} else if (units[c]) {
n += (temp || 1) * units[c];
temp = 0;
}
}
return n + temp;
};
let tmp = kanji;
for (let unit in bigUnits) {
const idx = tmp.indexOf(unit);
if (idx !== -1) {
const left = tmp.slice(0, idx);
total += parseSection(left) * bigUnits[unit];
tmp = tmp.slice(idx + 1);
}
}
total += parseSection(tmp);
return total;
}
function formatNumber(num) {
if (num >= 100000000) return (num / 100000000).toFixed(2).replace(/\.00$/, '') + "億";
if (num >= 10000) return (num / 10000).toFixed(2).replace(/\.00$/, '') + "万";
return num.toLocaleString();
}
function calculateDamage(params) {
const hp = parseMixedNumber(params.hp);
let damage = parseMixedNumber(params.damage);
const originalDamage = damage;
const reductionRate = parseFloat(params.reduction) / 100 || 0;
const aPercent = parseFloat(params.aPercent) / 100 || 0;
const blockRate = parseFloat(params.blockRate) / 100 || 0;
if (isNaN(hp) || isNaN(damage)) {
return {
error: "HPまたはダメージが無効です。"
};
}
damage = damage * (1 - reductionRate);
const threshold = hp * aPercent;
const excess = Math.max(0, damage - threshold);
const blockedDamage = excess * (1 - blockRate);
const nonBlockedDamage = damage - excess;
const totalDamage = nonBlockedDamage + blockedDamage;
const resultHP = hp - totalDamage;
const attackCount = totalDamage > 0 ? (hp / totalDamage) : Infinity;
const attackCountDisplay = attackCount === Infinity ? "∞" : attackCount.toFixed(3);
const finalBlockRate = (1 - (totalDamage / originalDamage)) * 100;
const finalBlockRateDisplay = isFinite(finalBlockRate) ? finalBlockRate.toFixed(2) + "%" : "0%";
return {
result: `受けたダメージ: ${formatNumber(totalDamage)}\n残りHP: ${formatNumber(resultHP)}\n` +
`最終遮断率: ${finalBlockRateDisplay}\nこのダメージでHPを0以下にするには 約 ${attackCountDisplay} 回攻撃が必要`,
formula: `計算詳細:
最大HP: ${formatNumber(hp)}
受けるダメージ(初期): ${formatNumber(originalDamage)}
被ダメージ減少: ${(reductionRate * 100).toFixed(1)}% → ${formatNumber(damage)}
遮断発動ライン: ${formatNumber(threshold)}(${(aPercent * 100).toFixed(1)}%)
超過ダメージ: ${formatNumber(excess)}
遮断率: ${(blockRate * 100).toFixed(1)}%
遮断後の超過ダメージ: ${formatNumber(blockedDamage)}
非遮断ダメージ: ${formatNumber(nonBlockedDamage)}
合計ダメージ: ${formatNumber(totalDamage)}
最終遮断率(元のダメージと比較): ${finalBlockRateDisplay}
残りHP: ${formatNumber(hp)} - ${formatNumber(totalDamage)} = ${formatNumber(resultHP)}
HPを0以下にするのに必要な攻撃回数: 約 ${attackCountDisplay} 回`
};
}