#!/usr/bin/env python3 import math import collections from fractions import Fraction from typing import Dict, List import satisfactory def basic_rate(recipe: Dict) -> Fraction: """ Calculate the rate at which the item is crafted with the default recipe. :param recipe: :return: """ for output, count in recipe['output'].items(): return Fraction( count, recipe['crafting_time'] ) def calculate_rates( recipes: satisfactory.Cookbook, remain: List[Dict[str, Fraction]] ) -> Dict[str, Fraction]: """ Calculate the total production rates needed to produce items at the rates listed in 'remain' using the provided recipes. :param recipes: The source Cookbook :param remain: A mapping from requested items to their desired rate. :return: A mapping from all required items to their required output rates. """ # Iteratively accumulate a mapping from items to required rates required_items = collections.defaultdict(Fraction) while remain: for dst_name, dst_rate in remain.pop().items(): # Append the requested item required_items[dst_name] += dst_rate # We can't craft resources, so just ignore them. if not recipes.is_component(dst_name): assert recipes.is_resource(dst_name) continue # Assume we're using the default recipe dst_recipe = recipes[dst_name]['recipes'][0] # Calculate the fraction of the base rate we need to satisfy, and # append our (scaled) inputs to the request queue. normal_rate = basic_rate(dst_recipe) scale = dst_rate / normal_rate for src_name, src_count in dst_recipe['input'].items(): src_rate = Fraction( src_count, dst_recipe['crafting_time'] ) * scale remain.append({src_name: src_rate}) return required_items # The number of items per minute that each tier of conveyor can carry conveyor_rates = [0, 60, 120, 270, 480, 780, 900] def main(): recipes = satisfactory.Cookbook('data/recipes') # Create a list of items we want to make # targets = [ 'supercomputer' ] targets = recipes.components() # Create an initial name:rate request for all of the target items, then # create a plan for their creation. remain = [{n: basic_rate(recipes[n]['recipes'][0]) for n in targets}] required_items = calculate_rates(recipes, remain) # Note if any particular item is (in aggregate) going to exceed the # highest conveyor belt capacity. for name, rate in required_items.items(): print(name, rate, float(rate * 60)) if rate * 60 > 780: print("Rate exceeds mk5 conveyor") # Calculate the number of machines required to build each item at the # calculated rates. def numberdict(): return collections.defaultdict(int) required_machines = collections.defaultdict(numberdict) for name, requested_rate in required_items.items(): if recipes.is_resource(name): continue descriptor = recipes[name] normal_rate = Fraction( descriptor['recipes'][0]['output'][name], descriptor['recipes'][0]['crafting_time'] ) machine = descriptor['machine'] required_machines[machine][name] += requested_rate / normal_rate # Calculate the power requirements for all the machines required_power = 0 for machine, buckets in required_machines.items(): for result, rate in buckets.items(): count = int(math.ceil(rate)) required_power += count * recipes[machine]['power_usage'] print(machine, result, math.ceil(rate)) print(required_power) print(math.ceil(required_power / 150), "fuel generators") if __name__ == '__main__': main()