civ/utils.js

103 lines
2.8 KiB
JavaScript
Raw Normal View History

2026-05-14 20:15:23 +09:00
function clamp(v, min, max) {
return Math.max(min, Math.min(max, v));
}
function bestBy(items, score) {
let best = null;
let bestScore = -Infinity;
for (const item of items) {
const value = score(item);
if (value > bestScore) {
best = item;
bestScore = value;
}
}
return best;
}
function smoothstep(t) {
return t * t * (3 - 2 * t);
}
const traitFields = [
["mobility", traits => traits.mobility, 0.02, 1, 1, 1],
["resourceAttraction", traits => traits.resourceAttraction, 0.05, 1.2, 1, 1],
["assimilation", traits => traits.assimilation, 0, 0.75, 1, 1],
["ethnocentrism", traits => traits.ethnocentrism, 0, 1.2, 1, 1],
["reproductionThreshold", traits => traits.reproductionThreshold, 12, 48, 16, 1 / 48],
["sedentary", traits => traits.sedentary, 0, 1, 1, 1]
];
function mutateTraits(traits, rng, amount) {
return Object.fromEntries(traitFields.map(([key, get, min, max, mutationScale]) => [
key,
clamp(get(traits) + rng.range(-amount * mutationScale, amount * mutationScale), min, max)
]));
}
function compositionTotal(composition) {
2026-09-16 13:51:43 +09:00
let sum = 0;
for (const value of composition.values()) sum += value;
return sum;
2026-05-14 20:15:23 +09:00
}
function addBirthsToComposition(composition, births, rng = null) {
const entries = [...composition.entries()];
const total = entries.reduce((sum, [, value]) => sum + value, 0);
if (!total || births <= 0) return;
for (let n = 0; n < births; n++) {
let r = (rng ? rng.next() : Math.random()) * total;
for (const [id, count] of entries) {
r -= count;
if (r <= 0) {
composition.set(id, (composition.get(id) || 0) + 1);
break;
}
}
}
}
function removeFromComposition(composition, loss) {
let remaining = loss;
const total = compositionTotal(composition);
if (!total) return;
for (const [id, count] of [...composition]) {
const removed = Math.min(count, Math.ceil(loss * (count / total)));
composition.set(id, Math.max(0, count - removed));
remaining -= removed;
if (composition.get(id) <= 0) composition.delete(id);
if (remaining <= 0) break;
}
}
function dominantComposition(composition) {
let bestId = null;
let bestCount = 0;
for (const [id, count] of composition) {
if (count > bestCount) {
bestId = id;
bestCount = count;
}
}
return bestId;
}
function hslToRgb(h, s, l) {
const hue = (p, q, t) => {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1 / 6) return p + (q - p) * 6 * t;
if (t < 1 / 2) return q;
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
return p;
};
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
return [
Math.round(hue(p, q, h + 1 / 3) * 255),
Math.round(hue(p, q, h) * 255),
Math.round(hue(p, q, h - 1 / 3) * 255)
];
}
function mix(a, b, t) {
return [
Math.round(lerp(a[0], b[0], t)),
Math.round(lerp(a[1], b[1], t)),
Math.round(lerp(a[2], b[2], t))
];
}
function lerp(a, b, t) {
return a + (b - a) * t;
}