Files
Battle-for-Tismo/shared/text_format.js
T

91 lines
3.3 KiB
JavaScript

// Small formatting helpers shared by the UI, ported from
// scripts/classes/text_format.gd.
// Groups a number's digits in threes. The separators are non-breaking spaces,
// so a money or resource figure never wraps in the middle of a number.
export function groupDigits(value) {
const truncated = Math.trunc(value);
const digits = String(Math.abs(truncated));
let grouped = "";
let count = 0;
for (let i = digits.length - 1; i >= 0; i--) {
grouped = digits[i] + grouped;
count += 1;
if (count % 3 === 0 && i > 0) grouped = "\u00a0" + grouped;
}
return (truncated < 0 ? "-" : "") + grouped;
}
// The canonical number for a variable display: digits grouped in threes with
// non-breaking spaces and, when `signed`, an explicit plus or minus. Callers
// pass an unsigned figure for a stock; a flow passes `signed` so it reads as a
// movement. This is what every money, resource and stat figure renders through.
export function formatNumber(value, { decimals = 0, signed = false } = {}) {
const amount = Number(value);
const safe = Number.isFinite(amount) ? amount : 0;
const negative = safe < 0;
const sign = signed ? (negative ? "\u2212" : "+") : (negative ? "\u2212" : "");
const fixed = Math.abs(safe).toFixed(decimals);
const [whole, fraction] = fixed.split(".");
const grouped = groupDigits(Number(whole));
return sign + (fraction ? `${grouped}.${fraction}` : grouped);
}
// Splits a compact figure into its mantissa and power-of-ten suffix, so the
// suffix can sit apart from the number in the unified display. `-1.5M` becomes
// the number `-1.5` and the suffix `M`.
export function splitCompact(value) {
const text = compact(value);
const match = /^(-?[0-9.]+)([KMBT]?)$/.exec(text);
if (!match) return { number: text, suffix: "" };
return { number: match[1].replace("-", "\u2212"), suffix: match[2] };
}
function threeSignificant(value) {
const digits = Math.floor(Math.log(value) / Math.log(10)) + 1;
const decimals = Math.max(0, 3 - digits);
const factor = Math.pow(10, decimals);
const rounded = Math.round(value * factor) / factor;
let text = rounded.toFixed(decimals);
if (text.includes(".")) text = text.replace(/0+$/, "").replace(/\.$/, "");
return text;
}
export function compact(value) {
if (Math.abs(value) < 1e-9) return "0";
const sign = value < 0 ? "-" : "";
let amount = Math.abs(value);
const suffixes = ["", "K", "M", "B", "T"];
let index = 0;
while (amount >= 1000 && index < suffixes.length - 1) {
amount /= 1000;
index += 1;
}
let text = threeSignificant(amount);
if (text === "1000" && index < suffixes.length - 1) {
index += 1;
text = "1";
}
return sign + text + suffixes[index];
}
// A fraction as a whole-number percentage, the unit most descriptions quote:
// 0.1 reads "10%". Callers pass the raw fraction the simulation applies so a
// description can never drift from the rule it describes.
export function formatPercent(fraction, { decimals = 0 } = {}) {
const value = Number(fraction) * 100;
const fixed = value.toFixed(decimals);
const text = decimals === 0 ? fixed : fixed.replace(/\.?0+$/, "");
return `${text}%`;
}
export function improvements(list) {
if (!list || list.length === 0) return "None";
return list.join(", ");
}
export function hours(value) {
if (value < 1.0) return value.toFixed(2) + " h";
return value.toFixed(1) + " h";
}