rat.ts 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. import {cachedFetchJSON} from './cache';
  2. render();
  3. async function render(): Promise<void> {
  4. const loader = document.querySelector('#loader') as HTMLElement;
  5. loader.style.display = 'block';
  6. try {
  7. await _render();
  8. } catch (e) {
  9. console.error(e);
  10. }
  11. loader.style.display = 'none';
  12. }
  13. async function _render(): Promise<void> {
  14. const allRecipes: readonly RecipeFull[] = await cachedFetchJSON('https://api.prunplanner.org/data/recipes/');
  15. const cols = ratRecipes(allRecipes);
  16. const frm = buildingOutputs(allRecipes, 'FRM');
  17. const hyf = buildingOutputs(allRecipes, 'HYF');
  18. document.querySelector('#cols')!.append(...cols.map((col) => {
  19. const colEl = document.createElement('div');
  20. colEl.className = 'col';
  21. colEl.append(...Array.from(col).map((input) => {
  22. const matEl = document.createElement('div');
  23. matEl.className = 'mat';
  24. if (frm.has(input))
  25. matEl.classList.add('frm');
  26. if (hyf.has(input))
  27. matEl.classList.add('hyf');
  28. matEl.textContent = input;
  29. return matEl;
  30. }));
  31. return colEl;
  32. }));
  33. }
  34. function ratRecipes(allRecipes: readonly RecipeFull[]): Set<string>[] {
  35. const ratRecipes = allRecipes.filter((recipe) =>
  36. recipe.building_ticker === 'FP' && recipe.outputs.length === 1 && recipe.outputs[0].material_ticker === 'RAT');
  37. const recipes: Recipe[] = ratRecipes.map((recipe) => {
  38. return {
  39. inputs: recipe.inputs.map((input) => input.material_ticker),
  40. catalogued: false,
  41. };
  42. });
  43. const cols: Set<string>[] = [new Set(), new Set(), new Set()];
  44. const seenInputs = new Set<string>();
  45. recipes[0].inputs.forEach((input, index) => {
  46. cols[index].add(input);
  47. seenInputs.add(input);
  48. });
  49. recipes[0].catalogued = true;
  50. let allCatalogued;
  51. do {
  52. allCatalogued = true;
  53. for (const recipe of recipes) {
  54. if (recipe.catalogued) continue;
  55. const inputs = new Set(recipe.inputs);
  56. const newInputs = inputs.difference(seenInputs);
  57. if (newInputs.size === 1) {
  58. const newInput = newInputs.keys().next().value!;
  59. const seenCols = Array.from(inputs.difference(newInputs)).map((oldInput) =>
  60. cols.findIndex((col) => col.has(oldInput)));
  61. const newCol = new Set([0, 1, 2]).difference(new Set(seenCols)).keys().next().value!;
  62. cols[newCol].add(newInput);
  63. seenInputs.add(newInput);
  64. recipe.catalogued = true;
  65. } else if (newInputs.size === 0)
  66. recipe.catalogued = true;
  67. else
  68. allCatalogued = false;
  69. }
  70. } while (!allCatalogued);
  71. return cols;
  72. }
  73. function buildingOutputs(allRecipes: readonly RecipeFull[], building: string): Set<string> {
  74. const outputs = new Set<string>();
  75. for (const recipe of allRecipes)
  76. if (recipe.building_ticker === building)
  77. for (const output of recipe.outputs)
  78. outputs.add(output.material_ticker);
  79. return outputs;
  80. }
  81. interface RecipeFull {
  82. building_ticker: string;
  83. inputs: Array<{material_ticker: string}>
  84. outputs: Array<{material_ticker: string}>
  85. }
  86. interface Recipe {
  87. readonly inputs: Array<string>
  88. catalogued: boolean;
  89. }