gov.ts 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. import {cachedFetchJSON} from './cache';
  2. const renderTarget = document.querySelector('#gov')!;
  3. const planetInput = document.querySelector('#planet') as HTMLInputElement;
  4. const popSelect = document.querySelector('#pop') as HTMLSelectElement;
  5. function serializeToHash(planet: string, pop: Pop): void {
  6. const params = new URLSearchParams({'planet': planet, 'pop': pop});
  7. document.location.hash = params.toString();
  8. }
  9. function deserializeFromHash(): {planet: string, pop: Pop} | null {
  10. const params = new URLSearchParams(document.location.hash.substring(1));
  11. const planet = params.get('planet');
  12. const pop = params.get('pop');
  13. if (planet && pop && ['pio', 'set', 'tec', 'eng', 'sci'].includes(pop)) {
  14. planetInput.value = planet;
  15. popSelect.value = pop;
  16. return {planet, pop: pop as Pop};
  17. } else
  18. return null;
  19. }
  20. document.querySelector('form')!.addEventListener('submit', async (event) => {
  21. event.preventDefault();
  22. const planet = planetInput.value;
  23. const pop = popSelect.value as Pop;
  24. if (planet && pop) {
  25. await render(planet, pop);
  26. serializeToHash(planet, pop);
  27. }
  28. });
  29. {
  30. const deserialized = deserializeFromHash();
  31. if (deserialized)
  32. void render(deserialized.planet, deserialized.pop);
  33. }
  34. async function render(planetName: string, pop: Pop) {
  35. const loader = document.querySelector('#loader') as HTMLElement;
  36. loader.style.display = 'block';
  37. renderTarget.innerHTML = '';
  38. const planet: Planet = await cachedFetchJSON(
  39. `https://api.fnar.net/planet/${encodeURIComponent(planetName)}?include_population_reports=true`);
  40. let lastPOPR = null;
  41. for (const report of planet.PopulationReports) {
  42. if (!lastPOPR || report.SimulationPeriod > lastPOPR.SimulationPeriod)
  43. lastPOPR = report;
  44. }
  45. if (lastPOPR === null) {
  46. renderTarget.textContent = `no POPR for ${planetName}`;
  47. return;
  48. }
  49. const lastPOPRts = Math.floor(new Date(lastPOPR.ReportTimestamp).getTime() / 1000);
  50. const nextPOPRts = lastPOPRts + 7 * 24 * 60 * 60;
  51. renderTarget.innerHTML = `last POPR: ${lastPOPR.ReportTimestamp} (${lastPOPRts})
  52. <br>next POPR: ${new Date(nextPOPRts * 1000).toISOString()} (${nextPOPRts})
  53. <table>
  54. <tr>
  55. <th></th>
  56. <th>pio</th>
  57. <th>set</th>
  58. <th>tec</th>
  59. <th>eng</th>
  60. <th>sci</th>
  61. </tr>
  62. <tr>
  63. <th>population</th>
  64. <td>${lastPOPR.NextPopulationPioneer}<br>${formatDelta(lastPOPR.PopulationDifferencePioneer)}</td>
  65. <td>${lastPOPR.NextPopulationSettler}<br>${formatDelta(lastPOPR.PopulationDifferenceSettler)}</td>
  66. <td>${lastPOPR.NextPopulationTechnician}<br>${formatDelta(lastPOPR.PopulationDifferenceTechnician)}</td>
  67. <td>${lastPOPR.NextPopulationEngineer}<br>${formatDelta(lastPOPR.PopulationDifferenceEngineer)}</td>
  68. <td>${lastPOPR.NextPopulationScientist}<br>${formatDelta(lastPOPR.PopulationDifferenceScientist)}</td>
  69. </tr>
  70. <tr>
  71. <th>unemployed</th>
  72. <td>${unemployed(lastPOPR.NextPopulationPioneer, lastPOPR.PopulationDifferencePioneer, lastPOPR.OpenJobsPioneer, lastPOPR.UnemploymentRatePioneer)}</td>
  73. <td>${unemployed(lastPOPR.NextPopulationSettler, lastPOPR.PopulationDifferenceSettler, lastPOPR.OpenJobsSettler, lastPOPR.UnemploymentRateSettler)}</td>
  74. <td>${unemployed(lastPOPR.NextPopulationTechnician, lastPOPR.PopulationDifferenceTechnician, lastPOPR.OpenJobsTechnician, lastPOPR.UnemploymentRateTechnician)}</td>
  75. <td>${unemployed(lastPOPR.NextPopulationEngineer, lastPOPR.PopulationDifferenceEngineer, lastPOPR.OpenJobsEngineer, lastPOPR.UnemploymentRateEngineer)}</td>
  76. <td>${unemployed(lastPOPR.NextPopulationScientist, lastPOPR.PopulationDifferenceScientist, lastPOPR.OpenJobsScientist, lastPOPR.UnemploymentRateScientist)}</td>
  77. </tr>
  78. </table>`;
  79. loader.style.display = 'none';
  80. }
  81. function formatDelta(n: number, withPlus: boolean = true): string {
  82. if (n > 0)
  83. return `<span class="positive">${withPlus ? '+' : ''}${n}</span>`;
  84. else if (n < 0)
  85. return `<span class="negative">${n}</span>`;
  86. else
  87. return n.toString();
  88. }
  89. function unemployed(people: number, change: number, openJobs: number, unemploymentRate: number): string {
  90. let nextUnemployed;
  91. if (openJobs <= people + change) {
  92. let prevUnemploymentRate = unemploymentRate;
  93. if (openJobs > 0)
  94. if (people - change > 0)
  95. prevUnemploymentRate = -openJobs / (people - change);
  96. else
  97. prevUnemploymentRate = -1
  98. const prevUnemployed = prevUnemploymentRate * (people - change);
  99. nextUnemployed = prevUnemployed + change;
  100. } else
  101. nextUnemployed = -openJobs + change;
  102. return formatDelta(Math.round(nextUnemployed), false);
  103. }
  104. type Pop = 'pio' | 'set' | 'tec' | 'eng' | 'sci';
  105. interface Planet {
  106. PopulationReports: POPR[]
  107. }
  108. interface POPR {
  109. SimulationPeriod: number;
  110. ReportTimestamp: string;
  111. NextPopulationPioneer: number;
  112. NextPopulationSettler: number;
  113. NextPopulationTechnician: number;
  114. NextPopulationEngineer: number;
  115. NextPopulationScientist: number;
  116. PopulationDifferencePioneer: number;
  117. PopulationDifferenceSettler: number;
  118. PopulationDifferenceTechnician: number;
  119. PopulationDifferenceEngineer: number;
  120. PopulationDifferenceScientist: number;
  121. OpenJobsPioneer: number;
  122. OpenJobsSettler: number;
  123. OpenJobsTechnician: number;
  124. OpenJobsEngineer: number;
  125. OpenJobsScientist: number;
  126. UnemploymentRatePioneer: number;
  127. UnemploymentRateSettler: number;
  128. UnemploymentRateTechnician: number;
  129. UnemploymentRateEngineer: number;
  130. UnemploymentRateScientist: number;
  131. }