LIBRE != BEER!

Ryan DeYong's Programming Portfolio

Home In-browser Examples Bash Scripts Chrome Userscripts The Best Calculators Physical Projects Fine Art Website Source Code

This is a calculator I made to calculate how many supporting machines you would need to support creating a particular item at the fastest possible speed.








Machine Name Item Name Machine Qty.

Check the checkbox to show the code.
class CraftingNode { constructor(p={}) { this.itemname = p.itemname, this.itemqtys = p.itemqtys, this.machinename = p.machinename, this.neededmachines = p.neededmachines, this.craftingtime = p.craftingtime, this.nextnodes = p.nextnodes } } class FactorioCalculator { constructor() { // format is: // machine name => machine recipe dict => recipe item => recipe resource requirement dict, containing items needed per second, and production per second this.recipes = { 'Stone Furnace': { 'Iron Plate': { 'Iron Ore': 1, 'Coal': 0.0225, 'ProductionSeconds': 1/0.3125 }, 'Copper Plate': { 'Copper Ore': 1, 'Coal': 0.0225, 'ProductionSeconds': 1/0.3125 }, 'Steel Plate': { 'Iron Plate': 5, 'Coal': 0.0225, 'ProductionSeconds': 1/0.0625 } }, 'Mining Drill': { 'Iron Ore': { 'ProductionSeconds': 2 }, 'Coal': { 'ProductionSeconds': 2 }, 'Copper Ore': { 'ProductionSeconds': 2 } }, 'Assembling Machine': // 50% crafting speed { 'Iron gear wheel': { 'Iron Plate': 2, 'ProductionSeconds': 0.5 }, 'Gun Turret': { 'Copper Plate': 10, 'Iron gear wheel': 10, 'Iron Plate': 20, 'ProductionSeconds': 8 }, 'Firearm magazine': { 'Iron Plate': 4, 'ProductionSeconds': 1 }, 'Piercing rounds magazine': { 'Copper Plate': 5, 'Firearm magazine': 1, 'Steel Plate': 1, 'ProductionSeconds': 3 }, 'Automation science pack': { 'Copper Plate': 1, 'Iron gear wheel': 1, 'ProductionSeconds': 5 }, 'Transport belt': { 'Iron gear wheel': 1, 'Iron Plate': 1, 'ProductionSeconds': 0.5/2 }, 'Electronic circuit': { 'Copper cable': 3, 'Iron Plate': 1, 'ProductionSeconds': 0.5 }, 'Copper cable': { 'Copper Plate': 1, 'ProductionSeconds': 0.5/2 }, 'Inserter': { 'Electronic circuit': 1, 'Iron gear wheel': 1, 'Iron Plate': 1, 'ProductionSeconds': 0.5 }, 'Logistic science pack': { 'Transport belt': 1, 'Inserter': 1, 'ProductionSeconds': 6 }, 'MachineVariantMultiplier': // for the assembling machines that sometimes craft faster or slower depending on the version { 1: 0.5, 2: 0.75 } } }; } TotalUpRecipeTreeMachines(recipetreenode) { let machinedict = {}; machinedict[recipetreenode.machinename] = {}; machinedict[recipetreenode.machinename][recipetreenode.itemname] = recipetreenode.neededmachines; for(let node of recipetreenode.nextnodes) { let res = this.TotalUpRecipeTreeMachines(node); for(let mdict_entry of Object.keys(res)) { for (let item of Object.keys(res[mdict_entry])) { if(!Object.keys(machinedict).includes(mdict_entry)) { machinedict[mdict_entry] = {}; } if(!Object.keys(machinedict[mdict_entry]).includes(item)) { machinedict[mdict_entry][item] = res[mdict_entry][item]; } else { machinedict[mdict_entry][item] += res[mdict_entry][item]; } } } } return machinedict; } BalanceRecipeTreeNode(recipetreenode) { let parent_craft_time = recipetreenode.craftingtime; for(let node of recipetreenode.nextnodes) { node.craftingtime *= recipetreenode.itemqtys.find(x => x[0] == node.itemname)[1]; for(let item of node.itemqtys) { item[1] *= recipetreenode.itemqtys.find(x => x[0] == node.itemname)[1]; } let machines_for_total_craft_time = node.craftingtime / parent_craft_time; node.neededmachines = machines_for_total_craft_time * recipetreenode.neededmachines; this.BalanceRecipeTreeNode(node) } } BuildRecipeTree(goalitem, variant_mappings, qty=1) { let recipe = ''; for(let arecipe of Object.keys(this.recipes)) { if (Object.keys(this.recipes[arecipe]).includes(goalitem)) { console.log(`Using ${arecipe} as the recipe for ${goalitem}`); recipe = arecipe; break; } } let recipecalculationnode = new CraftingNode({ itemname: goalitem, itemqtys: Object.keys(this.recipes[recipe][goalitem]).filter((x) => x != 'ProductionSeconds').map(x => [x, this.recipes[recipe][goalitem][x]]), machinename: recipe, neededmachines: qty, craftingtime: this.recipes[recipe][goalitem]['ProductionSeconds'], nextnodes: [] }); if (Object.keys(variant_mappings).includes(goalitem)) { recipecalculationnode.craftingtime /= this.recipes[recipe]['MachineVariantMultiplier'][variant_mappings[goalitem]] recipecalculationnode.machinename += ' ' + variant_mappings[goalitem]; } for(let recipeitem of Object.keys(this.recipes[recipe][goalitem])) { if (recipeitem == 'ProductionSeconds') continue; recipecalculationnode.nextnodes = recipecalculationnode.nextnodes.concat([this.BuildRecipeTree(recipeitem, variant_mappings)]); } return recipecalculationnode } } class MainApplication { constructor(itemlistbox, itemcombobox, factorioassemblymappings, base64stringbox, assemblymachinevariants, assemblyitem, assemblyitem_mapping, results_table) { this.itemlistbox = itemlistbox; this.itemcombobox = itemcombobox; this.factorioassemblymappings = factorioassemblymappings; this.base64stringbox = base64stringbox this.assemblymachinevariants = assemblymachinevariants; this.assemblyitem = assemblyitem; this.assemblyitem_mapping = assemblyitem_mapping; this.results_table = results_table; this.assemblyitem.addEventListener('change', () => { let selected_item_text = this.assemblyitem.options[this.assemblyitem.selectedIndex].value; for(let i = 0;i < this.factorioassemblymappings.options.length;i++) { let item = this.factorioassemblymappings.options[i]; if (item.value == selected_item_text) { this.factorioassemblymappings.selectedIndex = i; let selectedassemblymachine = this.assemblyitem_mapping.options[i].value; for(let j = 0;j < this.assemblymachinevariants.options.length;j++) { let variant = this.assemblymachinevariants.options[j]; if (variant.value == selectedassemblymachine) { this.assemblymachinevariants.selectedIndex = j; } } break; } } }); this.factorioassemblymappings.addEventListener('change', () => { let selected_item_text = this.factorioassemblymappings.options[this.factorioassemblymappings.selectedIndex].value; for(let i = 0;i < this.assemblyitem.options.length;i++) { let item = this.assemblyitem.options[i]; if (item.value == selected_item_text) { this.assemblyitem.focus(); this.assemblyitem.selectedIndex = i; let selectedassemblymachine = this.assemblyitem_mapping.options[i].value; for(let j = 0;j < this.assemblymachinevariants.options.length;j++) { let variant = this.assemblymachinevariants.options[j]; if (variant.value == selectedassemblymachine) { this.assemblymachinevariants.selectedIndex = j; } } break; } } }); this.factoriorecipecalc = new FactorioCalculator(); this.DoInitialPopulate(); } DoInitialPopulate() { let recipes = this.factoriorecipecalc.recipes; for (let recipe of Object.keys(recipes)) { for (let item of Object.keys(recipes[recipe])) { // checking length > 1 stops non craftables from being pulled in if (Object.keys(recipes[recipe][item]).length > 1 && item != 'MachineVariantMultiplier') { let newselectitem = document.createElement('option'); newselectitem.text = item; this.itemcombobox.appendChild(newselectitem); if (recipe == 'Assembling Machine') { this.factorioassemblymappings.appendChild(newselectitem.cloneNode(true)); this.assemblyitem.appendChild(newselectitem.cloneNode(true)); let newasm1 = document.createElement('option'); newasm1.text = 'Assembling Machine 1'; this.assemblyitem_mapping.appendChild(newasm1); } } if (item == 'MachineVariantMultiplier') { for (let variant of Object.keys(recipes[recipe][item])) { let newvariantitem = document.createElement('option'); newvariantitem.text = 'Assembling Machine ' + variant; this.assemblymachinevariants.appendChild(newvariantitem); } } } } this.assemblyitem.selectedIndex = 0; this.#CalculateBase64(); } #CalculateBase64() { let alloptions = Array.from(this.itemlistbox.options); let savearray = []; let assembly_machine_mappings = Array.from(this.assemblyitem.options).map((e, i) => { return [e.value, this.assemblyitem_mapping.options[i].value] }); savearray[0] = alloptions.map((opt) => opt.value); savearray[1] = assembly_machine_mappings; let fulloptionjson = JSON.stringify(savearray); // I compress it because why not, it makes it way smaller let compressed_json = CompressText(escape(fulloptionjson)); let base64_string = btoa(compressed_json); this.base64stringbox.value = base64_string; //console.log(`Compression Ratio is ${base64_string.length / btoa(fulloptionjson).length}`); } #CombineTwoCraftTrees(c1, c2) { for(let node2 in c2.nextnodes) { if(c1.nextnodes.find((x) => x.itemname == node2.itemname) != undefined) { this.#CombineTwoCraftTrees(c1.nextnodes[node2], c2.nextnodes[node2]); } else { c1.nextnodes.push(c2.nextnodes[node2]); } } c1.neededmachines += c2.neededmachines; } // actual calculation happens here Calculate() { // assembly machine mappings let variant_mappings = {}; for(let i = 0;i < this.assemblyitem.options.length;i++) { variant_mappings[this.assemblyitem.options[i].value] = parseInt(this.assemblyitem_mapping.options[i].value.replace('Assembling Machine ', '')); } // get list of things to craft let craft_list = Array.from(this.itemlistbox.options).map((opt) => opt.value); let rtree = undefined; for(let craftitem of craft_list) { let new_rtree = this.factoriorecipecalc.BuildRecipeTree(craftitem, variant_mappings); this.factoriorecipecalc.BalanceRecipeTreeNode(new_rtree); if (rtree != undefined) { this.#CombineTwoCraftTrees(rtree, new_rtree); } else { rtree = new_rtree; } } let totaled = this.factoriorecipecalc.TotalUpRecipeTreeMachines(rtree); for(let i of Object.keys(totaled)) { for(let j of Object.keys(totaled[i])) { totaled[i][j] = Math.ceil(totaled[i][j]) } } // now display totaled in the table this.results_table.innerHTML = ''; for(let machine of Object.keys(totaled).sort()) { for (let item of Object.keys(totaled[machine])) { let tablerow = document.createElement('tr'); let machinedata = document.createElement('td'); machinedata.textContent = machine; let itemdata = document.createElement('td'); itemdata.textContent = item; let machineqty = document.createElement('td'); machineqty.textContent = totaled[machine][item]; tablerow.appendChild(machinedata); tablerow.appendChild(itemdata); tablerow.appendChild(machineqty); this.results_table.appendChild(tablerow); } } } LoadSavedB64() { let base64_string = this.base64stringbox.value; let jsonifiedjson = undefined; try { let decompressed_json = unescape(DecompressText(atob(base64_string))); jsonifiedjson = JSON.parse(decompressed_json); } catch(e) { alert('Bad JSON String!'); return; } this.ClearList(); for(let opt of jsonifiedjson[0]) { let loadeditem = document.createElement('option'); loadeditem.text = opt; this.itemlistbox.appendChild(loadeditem); } for(let savedmapping of jsonifiedjson[1]) { for (let i = 0;i < this.assemblyitem.options.length;i++) { if (this.assemblyitem.options[i].value == savedmapping[0]) { this.assemblyitem_mapping.options[i].text = savedmapping[1]; break; } } } } UpdateMapping() { let new_assembly_variant = this.assemblymachinevariants.options[this.assemblymachinevariants.selectedIndex].value; this.assemblyitem_mapping.options[this.assemblyitem.selectedIndex].text = new_assembly_variant; this.#CalculateBase64(); } AddItemToList() { let newselectopt = document.createElement('option'); newselectopt.text = this.itemcombobox.value; this.itemlistbox.appendChild(newselectopt); this.#CalculateBase64(); } RemoveItemFromList() { for(let opt of Array.from(this.itemlistbox.selectedOptions)) { this.itemlistbox.removeChild(opt); } this.#CalculateBase64(); } ClearList() { for(let opt of Array.from(this.itemlistbox.options)) { this.itemlistbox.removeChild(opt); } this.base64stringbox.value = ''; } SelectBase64String() { this.base64stringbox.focus(); this.base64stringbox.setSelectionRange(0, this.base64stringbox.value.length, "forward"); } } var mainapp = undefined; function MainFunc(e) { let itemlistbox = document.getElementsByClassName('factorioitemview')[0]; let itemcombobox = document.getElementsByClassName('factorioselectitem')[0]; let factorioassemblymappings = document.getElementsByClassName('assemblyitems')[0]; let assemblymachinevariants = document.getElementsByClassName('assemblymachinevariants')[0] let base64stringbox = document.getElementsByClassName('base64itemliststring')[0]; let assemblyitem = document.getElementsByClassName('assemblyitem')[0]; let assemblyitem_mapping = document.getElementsByClassName('assemblyitem_mapping')[0]; let results_table = document.getElementsByClassName('factorioresultstable')[0].getElementsByTagName('tbody')[0]; mainapp = new MainApplication(itemlistbox, itemcombobox, factorioassemblymappings, base64stringbox, assemblymachinevariants, assemblyitem, assemblyitem_mapping, results_table); } MainFunc();

About me
Contact Information:
Voicemail box #: +14408478142
Email: administrator@ryancdeyong.us

All software on this website uses the MIT License.
ξ This page was generated Fri Mar 14, 2025 15:34