chinko
This commit is contained in:
parent
0359bb2445
commit
b17be0e0d2
21 changed files with 8034 additions and 2737 deletions
200
mapAdminSeedLifecycle.js
Normal file
200
mapAdminSeedLifecycle.js
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
import { INF, clamp, indexOf, inside } from "./mapUtils.js";
|
||||
import { applyCompartmentOwners, dominantCompartmentOwners, municipalityAreaById } from "./mapAdminShared.js";
|
||||
|
||||
export function absorbSeedCompartments(adminId, compartments, seedLifecycle) {
|
||||
const owner = dominantCompartmentOwners(compartments, adminId);
|
||||
const absorbed = new Set(seedLifecycle.filter((seed) => seed.state === "absorbed").map((seed) => seed.id));
|
||||
let changed = 0;
|
||||
for (const unit of compartments) {
|
||||
if (!unit || unit.area === 0 || !absorbed.has(owner[unit.id])) continue;
|
||||
let bestId = -1, bestScore = -INF;
|
||||
for (const [neighborId, edge] of unit.adjacent) {
|
||||
const candidate = owner[neighborId];
|
||||
if (candidate < 0 || absorbed.has(candidate)) continue;
|
||||
const neighbor = compartments[neighborId];
|
||||
const score = edge.count * 2 - (edge.target / Math.max(1, edge.count)) * 2 + (neighbor?.area || 0) * 0.002;
|
||||
if (score > bestScore) { bestScore = score; bestId = candidate; }
|
||||
}
|
||||
if (bestId < 0) continue;
|
||||
owner[unit.id] = bestId;
|
||||
changed += unit.area;
|
||||
}
|
||||
applyCompartmentOwners(adminId, compartments, owner);
|
||||
return changed;
|
||||
}
|
||||
|
||||
export function splitOversizedLowlandsWithPendingSeeds(adminId, prefectureMask, sea, fields, compartments, adminCenters, seedLifecycle, settlements = []) {
|
||||
const { plain, agriculture, basinField, coastalLowland, valleyField, ridgeField, slope, elevation } = fields;
|
||||
const areaById = municipalityAreaById(adminId, prefectureMask, sea);
|
||||
const areas = [...areaById.values()].sort((a, b) => a - b);
|
||||
const median = areas.length ? areas[Math.floor(areas.length / 2)] : 0;
|
||||
if (!median) return { changedCells: 0, splitMunicipalities: 0, pendingSeedsUsed: 0 };
|
||||
const owner = dominantCompartmentOwners(compartments, adminId);
|
||||
const unitsByOwner = new Map();
|
||||
for (const unit of compartments) {
|
||||
if (!unit || unit.area === 0 || owner[unit.id] < 0) continue;
|
||||
if (!unitsByOwner.has(owner[unit.id])) unitsByOwner.set(owner[unit.id], []);
|
||||
unitsByOwner.get(owner[unit.id]).push(unit);
|
||||
}
|
||||
const pending = seedLifecycle.filter((seed) => seed.state === "pending" && !seed.protected);
|
||||
let changedCells = 0;
|
||||
let splitMunicipalities = 0;
|
||||
let pendingSeedsUsed = 0;
|
||||
for (const [id, units] of unitsByOwner) {
|
||||
const area = areaById.get(id) || 0;
|
||||
if (area < Math.max(260, median * 1.45) || units.length < 6) continue;
|
||||
let lowland = 0, rough = 0;
|
||||
for (const unit of units) {
|
||||
for (const i of unit.cells) {
|
||||
lowland += (plain[i] || 0) * 0.38 + (agriculture[i] || 0) * 0.20 + basinField[i] * 0.22 + coastalLowland[i] * 0.22 + valleyField[i] * 0.10;
|
||||
rough += ridgeField[i] * 0.48 + slope[i] * 0.34 + Math.max(0, elevation[i] - 0.58) * 0.30;
|
||||
}
|
||||
}
|
||||
if (lowland / area < 0.26 || rough / area > 0.48) continue;
|
||||
const localPending = pending.filter((seed) => {
|
||||
const center = adminCenters[seed.id];
|
||||
if (!center || !inside(center.x, center.y)) return false;
|
||||
const centerOwner = adminId[indexOf(center.x, center.y)];
|
||||
return centerOwner === id || Math.hypot(center.x - (adminCenters[id]?.x || center.x), center.y - (adminCenters[id]?.y || center.y)) < 28;
|
||||
});
|
||||
const localSettlements = settlements.filter((p) => p && inside(p.x, p.y) && adminId[indexOf(p.x, p.y)] === id);
|
||||
if (localPending.length === 0 || localPending.length + localSettlements.length < 2) continue;
|
||||
let municipalitySplit = false;
|
||||
for (const seed of localPending.slice(0, 3)) {
|
||||
const center = adminCenters[seed.id];
|
||||
if (!center) continue;
|
||||
const targetArea = clamp(95 + (center.score || 0.5) * 60, 90, 180);
|
||||
let claimed = 0;
|
||||
const candidates = units
|
||||
.filter((unit) => owner[unit.id] === id && unit.classId !== 8 && unit.classId !== 9)
|
||||
.map((unit) => ({
|
||||
unit,
|
||||
score: Math.hypot(unit.x - center.x, unit.y - center.y) - (unit.lowlandFitness || 0) * 8 - (unit.urbanWeight || 0) * 3,
|
||||
}))
|
||||
.sort((a, b) => a.score - b.score);
|
||||
if (candidates.length < 2) continue;
|
||||
for (const { unit } of candidates) {
|
||||
if (claimed >= targetArea && claimed >= 2) break;
|
||||
owner[unit.id] = seed.id;
|
||||
claimed += unit.area;
|
||||
changedCells += unit.area;
|
||||
}
|
||||
if (claimed >= 45) {
|
||||
seed.state = "survived";
|
||||
seed.area = claimed;
|
||||
pendingSeedsUsed++;
|
||||
municipalitySplit = true;
|
||||
}
|
||||
}
|
||||
if (municipalitySplit) splitMunicipalities++;
|
||||
}
|
||||
applyCompartmentOwners(adminId, compartments, owner);
|
||||
return { changedCells, splitMunicipalities, pendingSeedsUsed };
|
||||
}
|
||||
|
||||
export function promotePendingSeedsForMunicipalityCount(adminId, prefectureMask, sea, fields, compartments, adminCenters, seedLifecycle, targetMinCount = 20) {
|
||||
const { plain, agriculture, basinField, coastalLowland, valleyField, ridgeField, slope, elevation } = fields;
|
||||
let areaById = municipalityAreaById(adminId, prefectureMask, sea);
|
||||
let currentCount = areaById.size;
|
||||
if (currentCount >= targetMinCount) return { changedCells: 0, promotedSeeds: 0 };
|
||||
const owner = dominantCompartmentOwners(compartments, adminId);
|
||||
let changedCells = 0;
|
||||
let promotedSeeds = 0;
|
||||
const pending = seedLifecycle
|
||||
.filter((seed) => seed.state === "pending" && !seed.protected)
|
||||
.sort((a, b) => (adminCenters[b.id]?.score || 0) - (adminCenters[a.id]?.score || 0));
|
||||
for (const seed of pending) {
|
||||
if (currentCount >= targetMinCount) break;
|
||||
const center = adminCenters[seed.id];
|
||||
if (!center || !inside(center.x, center.y)) continue;
|
||||
const existingArea = areaById.get(seed.id) || 0;
|
||||
if (existingArea >= 12) {
|
||||
seed.state = "survived";
|
||||
seed.area = existingArea;
|
||||
promotedSeeds++;
|
||||
continue;
|
||||
}
|
||||
const candidates = compartments
|
||||
.filter((unit) => {
|
||||
if (!unit || unit.area === 0) return false;
|
||||
const currentOwner = owner[unit.id];
|
||||
if (currentOwner < 0 || currentOwner === seed.id) return false;
|
||||
const ownerArea = areaById.get(currentOwner) || 0;
|
||||
if (ownerArea < 90) return false;
|
||||
const lowlandFit = (unit.lowlandFitness || 0) + (unit.basinIdentity || 0) * 0.15 + (unit.coastalExposure || 0) * 0.15;
|
||||
if (lowlandFit < 0.26) return false;
|
||||
return Math.hypot(unit.x - center.x, unit.y - center.y) < 36;
|
||||
})
|
||||
.map((unit) => ({
|
||||
unit,
|
||||
score: Math.hypot(unit.x - center.x, unit.y - center.y) - (unit.lowlandFitness || 0) * 6 - (unit.urbanWeight || 0) * 2,
|
||||
}))
|
||||
.sort((a, b) => a.score - b.score);
|
||||
if (candidates.length === 0) continue;
|
||||
let claimed = 0;
|
||||
for (const { unit } of candidates) {
|
||||
const currentOwner = owner[unit.id];
|
||||
if ((areaById.get(currentOwner) || 0) - unit.area < 70) continue;
|
||||
owner[unit.id] = seed.id;
|
||||
areaById.set(currentOwner, (areaById.get(currentOwner) || 0) - unit.area);
|
||||
areaById.set(seed.id, (areaById.get(seed.id) || 0) + unit.area);
|
||||
claimed += unit.area;
|
||||
changedCells += unit.area;
|
||||
if (claimed >= 55) break;
|
||||
}
|
||||
if (claimed >= 25) {
|
||||
seed.state = "survived";
|
||||
seed.area = areaById.get(seed.id) || claimed;
|
||||
promotedSeeds++;
|
||||
currentCount++;
|
||||
}
|
||||
}
|
||||
applyCompartmentOwners(adminId, compartments, owner);
|
||||
return { changedCells, promotedSeeds };
|
||||
}
|
||||
|
||||
export function restoreSurvivedSeedsByCompartment(adminId, prefectureMask, sea, compartments, adminCenters, seedLifecycle, targetMinCount = 20) {
|
||||
const areaById = municipalityAreaById(adminId, prefectureMask, sea);
|
||||
let currentCount = areaById.size;
|
||||
if (currentCount >= targetMinCount) return { changedCells: 0, restoredSeeds: 0 };
|
||||
const owner = dominantCompartmentOwners(compartments, adminId);
|
||||
let changedCells = 0;
|
||||
let restoredSeeds = 0;
|
||||
const missing = seedLifecycle
|
||||
.filter((seed) => (seed.state === "survived" || seed.protected) && (areaById.get(seed.id) || 0) === 0)
|
||||
.sort((a, b) => (b.protected ? 1 : 0) - (a.protected ? 1 : 0));
|
||||
for (const seed of missing) {
|
||||
if (currentCount >= targetMinCount) break;
|
||||
const center = adminCenters[seed.id];
|
||||
if (!center || !inside(center.x, center.y)) continue;
|
||||
const candidates = compartments
|
||||
.filter((unit) => {
|
||||
if (!unit || unit.area === 0 || unit.classId === 8 || unit.classId === 9) return false;
|
||||
const currentOwner = owner[unit.id];
|
||||
if (currentOwner < 0 || currentOwner === seed.id) return false;
|
||||
if ((areaById.get(currentOwner) || 0) - unit.area < 25) return false;
|
||||
return Math.hypot(unit.x - center.x, unit.y - center.y) < 90 && ((unit.lowlandFitness || 0) > 0.08 || seed.protected);
|
||||
})
|
||||
.map((unit) => ({
|
||||
unit,
|
||||
score: Math.hypot(unit.x - center.x, unit.y - center.y) - (unit.lowlandFitness || 0) * 6 - (unit.urbanWeight || 0) * 2 + (unit.classId === 8 || unit.classId === 9 ? 20 : 0),
|
||||
}))
|
||||
.sort((a, b) => a.score - b.score);
|
||||
if (candidates.length === 0) continue;
|
||||
const unit = candidates[0].unit;
|
||||
const oldOwner = owner[unit.id];
|
||||
if ((areaById.get(oldOwner) || 0) - unit.area < 25) continue;
|
||||
owner[unit.id] = seed.id;
|
||||
const claimed = unit.area;
|
||||
areaById.set(oldOwner, (areaById.get(oldOwner) || 0) - claimed);
|
||||
changedCells += claimed;
|
||||
areaById.set(seed.id, claimed);
|
||||
seed.area = claimed;
|
||||
restoredSeeds++;
|
||||
currentCount++;
|
||||
}
|
||||
applyCompartmentOwners(adminId, compartments, owner);
|
||||
return { changedCells, restoredSeeds };
|
||||
}
|
||||
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue