raylu 1 settimana fa
parent
commit
46b8437907
1 ha cambiato i file con 104 aggiunte e 10 eliminazioni
  1. 104 10
      ts/gov.ts

+ 104 - 10
ts/gov.ts

@@ -41,7 +41,15 @@ async function render(planetName: string, pop: Pop) {
 	const loader = document.querySelector('#loader') as HTMLElement;
 	loader.style.display = 'block';
 	renderTarget.innerHTML = '';
+	try {
+		await _render(planetName, pop);
+	} catch (e) {
+		renderTarget.textContent = (e as Error).message;
+	}
+	loader.style.display = 'none';
+}
 
+async function _render(planetName: string, pop: Pop) {
 	const encodedPlanetName = encodeURIComponent(planetName);
 	const [planet, siteCounts, infras]: [Planet, SiteCount[], Infrastructure[]] = await Promise.all([
 		cachedFetchJSON(`https://api.fnar.net/planet/${encodedPlanetName}?include_population_reports=true`),
@@ -71,6 +79,15 @@ async function render(planetName: string, pop: Pop) {
 		'education': calcTotalNeeds(lastPOPR, 'education'),
 	};
 
+	const currentPOPIFilled: Map<POPIBuilding, number> = new Map();
+	for (const infra of infras) {
+		const filled = calcPOPIFilled(infra);
+		if (filled !== null)
+			currentPOPIFilled.set(infra.Type, filled);
+	}
+
+	// gen
+
 	renderTarget.innerHTML = `last POPR: ${lastPOPR.ReportTimestamp} (${lastPOPRts})
 	<br>next POPR: ${new Date(nextPOPRts * 1000).toISOString()} (${nextPOPRts})
 
@@ -133,12 +150,30 @@ async function render(planetName: string, pop: Pop) {
 	<h2>POPI</h2>
 	<table>
 		<tr>
-			${infras.map(infra => popiFilled(infra)).join('')}
+			${infras.map((infra) => {
+				if (infra.CurrentLevel === 0)
+					return '';
+				const filled = currentPOPIFilled.get(infra.Type)!;
+				return `<tr>
+					<td>${infra.Type} T${infra.CurrentLevel}</td>
+					<td>${filled}/${infra.Upkeeps.length}</td>
+				</tr>`;
+			}).join('')}
 		</tr>
 	</table>
-	`;
 
-	loader.style.display = 'none';
+	<h2>options</h2>
+	current ${pop} happiness: ${formatPct(projectedHappiness(pop, totalNeeds, siteCount, currentPOPIFilled))}
+	<table>
+	${[...popiFillCombinations(currentPOPIFilled)].map((combination) => {
+		const happiness = projectedHappiness(pop, totalNeeds, siteCount, combination);
+		return `<tr>
+			<td>${[...combination.entries()].map(([building, filled]) => `${building}: ${filled}`).join(', ')}</td>
+			<td>${formatPct(happiness)}</td>
+		</tr>`;
+	}).join('')}
+	</table>
+	`;
 }
 
 const formatNum = new Intl.NumberFormat(undefined, {maximumFractionDigits: 2}).format;
@@ -177,9 +212,9 @@ function calcTotalNeeds(popReport: POPR, need: Need): number {
 		+ popReport.NextPopulationScientist * NEEDS.sci[need];
 }
 
-function popiFilled(infra: Infrastructure): string {
+function calcPOPIFilled(infra: Infrastructure): number | null {
 	if (infra.CurrentLevel === 0)
-		return '';
+		return null;
 
 	let filled = 0;
 	for (const upkeep of infra.Upkeeps) {
@@ -187,10 +222,50 @@ function popiFilled(infra: Infrastructure): string {
 		if (upkeep.Stored >= nextConsumptionAmount)
 			filled++;
 	}
-	return `<tr>
-		<td>${infra.Type} T${infra.CurrentLevel}</td>
-		<td>${filled}/${infra.Upkeeps.length}</td>
-	</tr>`;
+	return filled;
+}
+
+function* popiFillCombinations(currentPOPIFilled: Map<POPIBuilding, number>): Generator<Map<POPIBuilding, number>> {
+	const entries = [...currentPOPIFilled.keys()];
+	function* helper(index: number, current: Map<POPIBuilding, number>): Generator<Map<POPIBuilding, number>> {
+		if (index === entries.length) {
+			yield new Map(current);
+			return;
+		}
+		const building = entries[index];
+		for (let i = 0; i <= POPI[building].max; i++) {
+			current.set(building, i);
+			yield* helper(index + 1, current);
+		}
+	}
+	yield* helper(0, new Map());
+}
+
+function projectedHappiness(pop: Pop, totalNeeds: Record<Need, number>, siteCount: number,
+		popiFilled: Map<POPIBuilding, number>): number {
+	let totalProvided: Record<Need, number> = {'safety': 50 * siteCount, 'health': 50 * siteCount,
+		'comfort': 0, 'culture': 0, 'education': 0};
+	for (const [building, filled] of popiFilled.entries()) {
+		const {max, needs} = POPI[building];
+		for (const {need, satisfaction} of needs) {
+			const provided = filled / max * satisfaction;
+			totalProvided[need] += provided;
+		}
+	}
+	for (const _need in totalProvided) {
+		const need = _need as Need;
+		if (totalProvided[need] > totalNeeds[need])
+			totalProvided[need] = totalNeeds[need];
+	}
+
+	const weights = NEEDS[pop];
+	let happiness = 1 - Object.values(weights).reduce((sum, weight) => sum + weight, 0); // assume 100% life support
+	// TODO: caps
+	for (const [_need, provided] of Object.entries(totalProvided)) {
+		const need = _need as Need;
+		happiness += weights[need] * provided / totalNeeds[need];
+	}
+	return happiness;
 }
 
 const NEEDS: Record<Pop, Record<Need, number>> = {
@@ -200,9 +275,28 @@ const NEEDS: Record<Pop, Record<Need, number>> = {
 	'eng': {'safety': 0.10, 'health': 0.15, 'comfort': 0.35, 'culture': 0.20, 'education': 0.10},
 	'sci': {'safety': 0.10, 'health': 0.10, 'comfort': 0.20, 'culture': 0.25, 'education': 0.30},
 }
+const POPI: Record<POPIBuilding, {max: number, needs: {need: Need, satisfaction: number}[]}> = {
+	'SAFETY_STATION': {max: 3, needs: [{need: 'safety', satisfaction: 2500}]},
+	'SECURITY_DRONE_POST': {max: 4, needs: [{need: 'safety', satisfaction: 5000}]},
+	'EMERGENCY_CENTER': {max: 5, needs: [{need: 'safety', satisfaction: 1000}, {need: 'health', satisfaction: 1000}]},
+	'INFIRMARY': {max: 3, needs: [{need: 'health', satisfaction: 2500}]},
+	'HOSPITAL': {max: 6, needs: [{need: 'health', satisfaction: 5000}]},
+	'WELLNESS_CENTER': {max: 6, needs: [{need: 'health', satisfaction: 1000}, {need: 'comfort', satisfaction: 1000}]},
+	'WILDLIFE_PARK': {max: 5, needs: [{need: 'comfort', satisfaction: 2500}]},
+	'ARCADES': {max: 6, needs: [{need: 'comfort', satisfaction: 5000}]},
+	'ART_CAFE': {max: 6, needs: [{need: 'comfort', satisfaction: 1000}, {need: 'culture', satisfaction: 1000}]},
+	'ART_GALLERY': {max: 4, needs: [{need: 'culture', satisfaction: 2500}]},
+	'THEATER': {max: 6, needs: [{need: 'culture', satisfaction: 5000}]},
+	'PLANETARY_BROADCASTING_HUB': {max: 6, needs: [{need: 'culture', satisfaction: 1000}, {need: 'education', satisfaction: 1000}]},
+	'LIBRARY': {max: 5, needs: [{need: 'education', satisfaction: 2500}]},
+	'UNIVERSITY': {max: 6, needs: [{need: 'education', satisfaction: 5000}]},
+};
 
 type Pop = 'pio' | 'set' | 'tec' | 'eng' | 'sci';
 type Need = 'safety' | 'health' | 'comfort' | 'culture' | 'education';
+type POPIBuilding = 'SAFETY_STATION' | 'SECURITY_DRONE_POST' | 'EMERGENCY_CENTER' | 'INFIRMARY' | 'HOSPITAL' |
+	'WELLNESS_CENTER' | 'WILDLIFE_PARK' | 'ARCADES' | 'ART_CAFE' | 'ART_GALLERY' | 'THEATER' |
+	'PLANETARY_BROADCASTING_HUB' | 'LIBRARY' | 'UNIVERSITY';
 
 interface Planet {
 	PopulationReports: POPR[]
@@ -243,7 +337,7 @@ interface SiteCount {
 }
 
 interface Infrastructure {
-	Type: string;
+	Type: POPIBuilding;
 	CurrentLevel: number;
 	Upkeeps: {Stored: number, StoreCapacity: number, 'Duration': number}[];
 }