Browse Source

gov: government programs

raylu 1 tuần trước cách đây
mục cha
commit
99bce73b32
1 tập tin đã thay đổi với 85 bổ sung30 xóa
  1. 85 30
      ts/gov.ts

+ 85 - 30
ts/gov.ts

@@ -100,6 +100,8 @@ async function _render(planetName: string, pop: Pop) {
 		'culture': calcTotalNeeds(lastPOPR, 'culture'),
 		'education': calcTotalNeeds(lastPOPR, 'education'),
 	};
+	const totalPop = lastPOPR.NextPopulationPioneer + lastPOPR.NextPopulationSettler +
+			lastPOPR.NextPopulationTechnician + lastPOPR.NextPopulationEngineer + lastPOPR.NextPopulationScientist;
 
 	const currentPOPIFilled: POPIFill = new Map();
 	for (const infra of infras) {
@@ -189,23 +191,32 @@ async function _render(planetName: string, pop: Pop) {
 		</tr>
 	</table>
 	current projected ${pop.toUpperCase()} happiness:
-	${formatPct(projectedHappiness(pop, totalNeeds, siteCount, currentPOPIFilled))}
+	${formatPct(projectedHappiness(pop, totalNeeds, siteCount, currentPOPIFilled, null))}
 
 	<h2>options</h2>
 	<table class="options">
 		<tr>
 			<th>config</th>
 			<th>projected happiness</th>
-			<th>projected migration</th>
+			<th>projected change</th>
 			<th>cost/day</th>
 			<th>unit cost</th>
 		</tr>
-		${paretoFront(pop, totalNeeds, siteCount, currentPOPIFilled, currentPop, lowerPop, prices).map((result) => {
+		${paretoFront(pop, totalNeeds, siteCount, currentPOPIFilled, currentPop, lowerPop, totalPop, prices).map((result) => {
 			let unitCost = '';
 			if (result.cost > 0)
 				unitCost = formatNum(result.cost / result.change);
 			return `<tr>
-				<td>${[...result.config.entries()].map(([building, fill]) => `${building}: ${fill.numMats}`).join('<br>')}</td>
+				<td>
+					${[...result.config.entries()].map(([building, fill]) => {
+						let currentBuildingFill = currentPOPIFilled.get(building)!.numMats;
+						let className = '';
+						if (fill.numMats > currentBuildingFill) className = 'positive';
+						else if (fill.numMats < currentBuildingFill) className = 'negative';
+						return `${building}: <span class="${className}">${fill.numMats}</span>`;
+					}).join('<br>')}
+					${result.govProgram !== null ? `<br>${result.govProgram}` : ''}
+				</td>
 				<td>${formatPct(result.happiness)}</td>
 				<td>${formatDelta(result.change)}</td>
 				<td>${formatNum(result.cost)}</td>
@@ -266,8 +277,9 @@ function calcPOPIFilled(infra: Infrastructure): number | null {
 }
 
 function paretoFront(pop: Pop, totalNeeds: Record<Need, number>, siteCount: number, currentPOPIFilled: POPIFill,
-		currentPop: number, lowerPop: number, prices: Map<string, number>): {config: POPIFill, happiness: number, change: number, cost: number}[] {
-	const results: {config: POPIFill, happiness: number, change: number, cost: number}[] = [];
+		currentPop: number, lowerPop: number, totalPop: number, prices: Map<string, number>):
+		{config: POPIFill, govProgram: GovProgram | null, happiness: number, change: number, cost: number}[] {
+	const results: {config: POPIFill, govProgram: GovProgram | null, happiness: number, change: number, cost: number}[] = [];
 	let education = 0;
 	if (pop !== 'pio') {
 		education = EDUCATION[pop];
@@ -276,29 +288,49 @@ function paretoFront(pop: Pop, totalNeeds: Record<Need, number>, siteCount: numb
 		education += (currentPOPIFilled.get('UNIVERSITY')?.tier ?? 0) * 0.004;
 	}
 
+	const govPrograms: (GovProgram | null)[] = [null];
+	govPrograms.push(...(Object.keys(GOV_PROGRAMS) as GovProgram[]));
+
 	for (const config of popiFillCombinations(currentPOPIFilled)) {
-		const happiness = projectedHappiness(pop, totalNeeds, siteCount, config);
-		let change = 0;
-		if (happiness > 0.7 && ['pio', 'set', 'tec'].includes(pop))
-			change = currentPop * (happiness - 0.7);
-		else if (happiness < 0.5)
-			change = 0.8 * currentPop * (happiness - 0.5);
-
-		if (pop !== 'pio')
-			change += lowerPop * education * happiness;
-		// TODO: education out
-
-		let cost = calcCost(config, prices);
-		// is any result better than this one?
-		if (results.some((result) => (result.change >= change && result.cost < cost) ||
-				(result.change > change && result.cost <= cost)))
-			continue;
-		// are any results worse than this one?
-		for (let i = results.length - 1; i >= 0; i--)
-			if ((results[i].change <= change && results[i].cost > cost) ||
-					(results[i].change < change && results[i].cost >= cost))
-				results.splice(i, 1);
-		results.push({config, happiness, change, cost});
+		for (const govProgram of govPrograms) {
+			const happiness = projectedHappiness(pop, totalNeeds, siteCount, config, govProgram);
+			let change = 0;
+			if (happiness > 0.7 && ['pio', 'set', 'tec'].includes(pop))
+				change = currentPop * (happiness - 0.7);
+			else if (happiness < 0.5)
+				change = 0.8 * currentPop * (happiness - 0.5);
+			if (govProgram === 'pio immigration' && pop === 'pio') change += 500;
+			else if (govProgram === 'set immigration' && pop === 'set') change += 200;
+			else if (govProgram === 'tec immigration' && pop === 'tec') change += 100;
+			else if (govProgram === 'eng immigration' && pop === 'eng') change += 50;
+			else if (govProgram === 'sci immigration' && pop === 'sci') change += 25;
+
+			if (pop !== 'pio') {
+				let eduIn = lowerPop * education * happiness;
+				if (govProgram === 'education I') eduIn *= 1.5;
+				else if (govProgram === 'education II') eduIn *= 1.75;
+				else if (govProgram === 'education III') eduIn *= 2;
+				change += eduIn;
+			}
+			// TODO: education out
+
+			let cost = calcCost(config, prices);
+			if (govProgram !== null) {
+				const program = GOV_PROGRAMS[govProgram];
+				cost += (program.baseCost + program.popCost * totalPop) / 7;
+			}
+
+			// is any result better than this one?
+			if (results.some((result) => (result.change >= change && result.cost < cost) ||
+					(result.change > change && result.cost <= cost)))
+				continue;
+			// are any results worse than this one?
+			for (let i = results.length - 1; i >= 0; i--)
+				if ((results[i].change <= change && results[i].cost > cost) ||
+						(results[i].change < change && results[i].cost >= cost))
+					results.splice(i, 1);
+			results.push({config, govProgram, happiness, change, cost});
+		}
 	}
 	return results;
 }
@@ -321,7 +353,8 @@ function* popiFillCombinations(currentPOPIFilled: POPIFill): Generator<POPIFill>
 	yield* helper(0, new Map());
 }
 
-function projectedHappiness(pop: Pop, totalNeeds: Record<Need, number>, siteCount: number, popiFilled: POPIFill): number {
+function projectedHappiness(pop: Pop, totalNeeds: Record<Need, number>, siteCount: number, popiFilled: POPIFill,
+		govProgram: GovProgram | null): number {
 	let totalProvided: Record<Need, number> = {'safety': 50 * siteCount, 'health': 50 * siteCount,
 		'comfort': 0, 'culture': 0, 'education': 0};
 	for (const [building, fill] of popiFilled.entries()) {
@@ -351,7 +384,14 @@ function projectedHappiness(pop: Pop, totalNeeds: Record<Need, number>, siteCoun
 	let happiness = 1 - Object.values(weights).reduce((sum, weight) => sum + weight, 0); // assume 100% life support
 	for (const [need, s] of satisfaction.entries())
 		happiness += weights[need] * s;
-	return happiness;
+
+	if (govProgram === 'festivities I')
+		happiness += 0.05;
+	else if (govProgram === 'festivities II')
+		happiness += 0.1;
+	else if (govProgram === 'festivities III')
+		happiness += 0.2;
+	return Math.min(happiness, 1);
 }
 
 function calcCost(config: POPIFill, prices: Map<string, number>): number {
@@ -444,6 +484,19 @@ const EDUCATION: Record<Exclude<Pop, 'pio'>, number> = {
 	'eng': 0.0125,
 	'sci': 0.0075,
 }
+const GOV_PROGRAMS: Record<GovProgram, {baseCost: number, popCost: number}> = {
+	'festivities I': {baseCost: 5000, popCost: 0.125},
+	'festivities II': {baseCost: 10000, popCost: 0.25},
+	'festivities III': {baseCost: 20000, popCost: 0.5},
+	'education I': {baseCost: 5000, popCost: 0.15},
+	'education II': {baseCost: 10000, popCost: 0.25},
+	'education III': {baseCost: 20000, popCost: 0.4},
+	'pio immigration': {baseCost: 10000, popCost: 0},
+	'set immigration': {baseCost: 25000, popCost: 0},
+	'tec immigration': {baseCost: 50000, popCost: 0},
+	'eng immigration': {baseCost: 80000, popCost: 0},
+	'sci immigration': {baseCost: 125000, popCost: 0},
+}
 
 type Pop = 'pio' | 'set' | 'tec' | 'eng' | 'sci';
 type Need = 'safety' | 'health' | 'comfort' | 'culture' | 'education';
@@ -451,6 +504,8 @@ type POPIBuilding = 'SAFETY_STATION' | 'SECURITY_DRONE_POST' | 'EMERGENCY_CENTER
 	'WELLNESS_CENTER' | 'WILDLIFE_PARK' | 'ARCADES' | 'ART_CAFE' | 'ART_GALLERY' | 'THEATER' |
 	'PLANETARY_BROADCASTING_HUB' | 'LIBRARY' | 'UNIVERSITY';
 type POPIFill = Map<POPIBuilding, {tier: number, numMats: number}>;
+type GovProgram = 'festivities I' | 'festivities II' | 'festivities III' | 'education I' | 'education II' | 'education III' |
+	'pio immigration' | 'set immigration' | 'tec immigration' | 'eng immigration' | 'sci immigration';
 
 interface Planet {
 	PopulationReports: POPR[]