|
|
@@ -0,0 +1,96 @@
|
|
|
+import {cachedFetchJSON} from './cache';
|
|
|
+
|
|
|
+render();
|
|
|
+
|
|
|
+async function render(): Promise<void> {
|
|
|
+ const loader = document.querySelector('#loader') as HTMLElement;
|
|
|
+ loader.style.display = 'block';
|
|
|
+ try {
|
|
|
+ await _render();
|
|
|
+ } catch (e) {
|
|
|
+ console.error(e);
|
|
|
+ }
|
|
|
+ loader.style.display = 'none';
|
|
|
+}
|
|
|
+
|
|
|
+async function _render(): Promise<void> {
|
|
|
+ const allRecipes: readonly RecipeFull[] = await cachedFetchJSON('https://api.prunplanner.org/data/recipes/');
|
|
|
+ const cols = ratRecipes(allRecipes);
|
|
|
+ const frm = buildingOutputs(allRecipes, 'FRM');
|
|
|
+ const hyf = buildingOutputs(allRecipes, 'HYF');
|
|
|
+ document.querySelector('#cols')!.append(...cols.map((col) => {
|
|
|
+ const colEl = document.createElement('div');
|
|
|
+ colEl.className = 'col';
|
|
|
+ colEl.append(...Array.from(col).map((input) => {
|
|
|
+ const matEl = document.createElement('div');
|
|
|
+ matEl.className = 'mat';
|
|
|
+ if (frm.has(input))
|
|
|
+ matEl.classList.add('frm');
|
|
|
+ if (hyf.has(input))
|
|
|
+ matEl.classList.add('hyf');
|
|
|
+ matEl.textContent = input;
|
|
|
+ return matEl;
|
|
|
+ }));
|
|
|
+ return colEl;
|
|
|
+ }));
|
|
|
+}
|
|
|
+
|
|
|
+function ratRecipes(allRecipes: readonly RecipeFull[]): Set<string>[] {
|
|
|
+ const ratRecipes = allRecipes.filter((recipe) =>
|
|
|
+ recipe.building_ticker === 'FP' && recipe.outputs.length === 1 && recipe.outputs[0].material_ticker === 'RAT');
|
|
|
+ const recipes: Recipe[] = ratRecipes.map((recipe) => {
|
|
|
+ return {
|
|
|
+ inputs: recipe.inputs.map((input) => input.material_ticker),
|
|
|
+ catalogued: false,
|
|
|
+ };
|
|
|
+ });
|
|
|
+
|
|
|
+ const cols: Set<string>[] = [new Set(), new Set(), new Set()];
|
|
|
+ const seenInputs = new Set<string>();
|
|
|
+ recipes[0].inputs.forEach((input, index) => {
|
|
|
+ cols[index].add(input);
|
|
|
+ seenInputs.add(input);
|
|
|
+ });
|
|
|
+ recipes[0].catalogued = true;
|
|
|
+ let allCatalogued;
|
|
|
+ do {
|
|
|
+ allCatalogued = true;
|
|
|
+ for (const recipe of recipes) {
|
|
|
+ if (recipe.catalogued) continue;
|
|
|
+ const inputs = new Set(recipe.inputs);
|
|
|
+ const newInputs = inputs.difference(seenInputs);
|
|
|
+ if (newInputs.size === 1) {
|
|
|
+ const newInput = newInputs.keys().next().value!;
|
|
|
+ const seenCols = Array.from(inputs.difference(newInputs)).map((oldInput) =>
|
|
|
+ cols.findIndex((col) => col.has(oldInput)));
|
|
|
+ const newCol = new Set([0, 1, 2]).difference(new Set(seenCols)).keys().next().value!;
|
|
|
+ cols[newCol].add(newInput);
|
|
|
+ seenInputs.add(newInput);
|
|
|
+ recipe.catalogued = true;
|
|
|
+ } else if (newInputs.size === 0)
|
|
|
+ recipe.catalogued = true;
|
|
|
+ else
|
|
|
+ allCatalogued = false;
|
|
|
+ }
|
|
|
+ } while (!allCatalogued);
|
|
|
+ return cols;
|
|
|
+}
|
|
|
+
|
|
|
+function buildingOutputs(allRecipes: readonly RecipeFull[], building: string): Set<string> {
|
|
|
+ const outputs = new Set<string>();
|
|
|
+ for (const recipe of allRecipes)
|
|
|
+ if (recipe.building_ticker === building)
|
|
|
+ for (const output of recipe.outputs)
|
|
|
+ outputs.add(output.material_ticker);
|
|
|
+ return outputs;
|
|
|
+}
|
|
|
+
|
|
|
+interface RecipeFull {
|
|
|
+ building_ticker: string;
|
|
|
+ inputs: Array<{material_ticker: string}>
|
|
|
+ outputs: Array<{material_ticker: string}>
|
|
|
+}
|
|
|
+interface Recipe {
|
|
|
+ readonly inputs: Array<string>
|
|
|
+ catalogued: boolean;
|
|
|
+}
|