satisfactory/plan.py

164 lines
5.0 KiB
Python
Raw Normal View History

2019-04-28 17:23:21 +10:00
#!/usr/bin/env python3
2019-04-28 18:02:57 +10:00
import math
2019-04-28 17:23:21 +10:00
import collections
2019-07-14 11:36:54 +10:00
from fractions import Fraction
from typing import Dict, List
2019-07-14 11:35:09 +10:00
2019-04-28 18:02:57 +10:00
import satisfactory
2019-07-14 11:35:09 +10:00
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 required_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
2019-07-14 11:36:54 +10:00
required_items = collections.defaultdict(Fraction)
2019-04-28 17:23:21 +10:00
while remain:
for dst_name, dst_rate in remain.pop().items():
# Append the requested item
2019-04-28 18:02:57 +10:00
required_items[dst_name] += dst_rate
2019-04-28 17:23:21 +10:00
# We can't craft resources, so just ignore them.
2019-04-28 18:59:31 +10:00
if not recipes.is_component(dst_name):
assert recipes.is_resource(dst_name)
2019-04-28 17:23:21 +10:00
continue
# Assume we're using the default recipe
2019-04-28 17:23:21 +10:00
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)
2019-04-28 17:23:21 +10:00
scale = dst_rate / normal_rate
for src_name, src_count in dst_recipe['input'].items():
2019-07-14 11:36:54 +10:00
src_rate = Fraction(
2019-04-28 17:23:21 +10:00
src_count,
dst_recipe['crafting_time']
) * scale
remain.append({src_name: src_rate})
2019-04-28 18:59:31 +10:00
return required_items
2019-07-14 11:35:22 +10:00
# The number of items per minute that each tier of conveyor can carry
conveyor_rates = [0, 60, 120, 270, 480, 780, 900]
def required_machines(recipes: satisfactory.Cookbook, rates: Dict[str, Fraction]) -> Dict[str, int]:
"""
Calculate the number of machines required to build each item at the
requested rates. This will, by necessity, round up rates where they are
below the output rate of the relevant machine.
2019-04-28 18:02:57 +10:00
:param recipes: The cookbook object we're working from
:param rates: A mapping from item names to requested output rates.
:return: A mapping from machine names to counts
"""
2019-04-28 18:02:57 +10:00
def numberdict():
return collections.defaultdict(int)
required_machines = collections.defaultdict(numberdict)
for name, requested_rate in rates.items():
2019-04-28 18:02:57 +10:00
if recipes.is_resource(name):
continue
descriptor = recipes[name]
2019-07-14 11:36:54 +10:00
normal_rate = Fraction(
2019-04-28 18:02:57 +10:00
descriptor['recipes'][0]['output'][name],
descriptor['recipes'][0]['crafting_time']
)
machine = descriptor['machine']
required_machines[machine][name] += requested_rate / normal_rate
return required_machines
def required_power(recipes: satisfactory.Cookbook, machines: Dict[str, int]) -> int:
"""
Calculate the cumulative power requirements for a mapping of machine
names to counts
:param recipes: The cookbook we're working from
:param machines: A mapping from machine names to counts
:return: The total required power in MW
"""
2019-07-14 11:35:22 +10:00
# Calculate the power requirements for all the machines
total = 0
2019-04-28 18:02:57 +10:00
for machine, buckets in machines.items():
2019-04-28 18:02:57 +10:00
for result, rate in buckets.items():
count = int(math.ceil(rate))
total += count * recipes[machine]['power_usage']
2019-04-28 18:02:57 +10:00
print(machine, result, math.ceil(rate))
2019-04-28 17:23:21 +10:00
return total
def plan(recipes: satisfactory.Cookbook, required: Dict[str, Fraction]):
"""
Print the items and rates, machines and counts, and power requirements
need to produce the items named in the dict at the mapped rate.
:param recipes:
:param required:
:return:
"""
rates = required_rates(recipes, required)
# Note if any particular item is (in aggregate) going to exceed the
# highest conveyor belt capacity.
for name, rate in rates.items():
print(name, rate, float(rate * 60))
if rate * 60 > conveyor_rates[-1]:
print("Rate exceeds max conveyor")
machines = required_machines(recipes, rates)
power = required_power(recipes, machines)
print(power, "MW")
print(math.ceil(power / 150), "fuel generators")
def main():
recipes = satisfactory.Cookbook('data/recipes')
# Create a list of items we want to make
# names = [ 'supercomputer' ]
names = recipes.components()
# Create an initial name:rate request for all of the target items, then
# create a plan for their creation.
request = [{n: basic_rate(recipes[n]['recipes'][0]) for n in names}]
plan(recipes, request)
if __name__ == '__main__':
main()