103 lines
2.8 KiB
JavaScript
103 lines
2.8 KiB
JavaScript
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) {
|
|
let sum = 0;
|
|
for (const value of composition.values()) sum += value;
|
|
return sum;
|
|
}
|
|
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;
|
|
}
|