Skip to content

Instantly share code, notes, and snippets.

@EliseAv
Last active January 1, 2018 23:46
Show Gist options
  • Save EliseAv/e58cbd75fc7c549d8ea9a7eef2ec8c74 to your computer and use it in GitHub Desktop.
Save EliseAv/e58cbd75fc7c549d8ea9a7eef2ec8c74 to your computer and use it in GitHub Desktop.
Factorio Rates
from collections import namedtuple
from fractions import Fraction
from functools import partial
from itertools import chain
from math import log, floor
class FractionDict(dict):
zero = Fraction()
def __init__(self, contents: dict):
super().__init__(contents)
def __getitem__(self, item):
return self.get(item, self.zero)
def __setitem__(self, key, value):
if value:
if not isinstance(value, Fraction):
value = Fraction(value)
super().__setitem__(key, value)
elif key in self:
del self[key]
def __str__(self):
return ' + '.join(str(v) + ' ' + k for k, v in self.items() if v)
class Machine(namedtuple('Machine', ('time', 'inputs', 'outputs'))):
def __str__(self):
return f'{self.time} s + {FractionDict(self.inputs)} = {FractionDict(self.outputs)}'
def make(s, crafting_speed=1, **kwargs):
try:
return Machine(Fraction(s) / crafting_speed,
tuple((k, -Fraction(v)) for k, v in kwargs.items() if v < 0),
tuple((k, Fraction(v)) for k, v in kwargs.items() if v > 0))
except TypeError:
print((s, crafting_speed))
raise
am1 = partial(make, crafting_speed=Fraction(0.5))
am2 = partial(make, crafting_speed=Fraction(0.75))
am3 = partial(make, crafting_speed=Fraction(1.25))
chemical_plant = am3
centrifuge = am2
cokery = partial(make, crafting_speed=2)
class BlueprintPlan:
def __init__(self, machine: Machine, *more_machines):
self.machines = FractionDict({machine: Fraction(1)})
self.inputs = FractionDict({k: v / machine.time for k, v in machine.inputs})
self.partways = FractionDict({})
self.outputs = FractionDict({k: v / machine.time for k, v in machine.outputs})
for other in more_machines:
self.add(other)
def add(self, machine: Machine, pivot: str = None):
if not pivot:
pivot = next(chain((p for p, _ in machine.inputs if p in self.outputs),
(p for p, _ in machine.outputs if p in self.inputs)), pivot)
# Calculate number of machines required to keep full use of pivot
if pivot in self.outputs:
machine_pivot_input = next(quantity for item, quantity in machine.inputs if item == pivot)
number_of_machines = self.outputs[pivot] * machine.time / machine_pivot_input
elif pivot in self.inputs:
machine_pivot_output = next(quantity for item, quantity in machine.outputs if item == pivot)
number_of_machines = self.inputs[pivot] * machine.time / machine_pivot_output
else:
raise ValueError("Cannot find pivot: " + str(pivot))
# Find the rate of consumption relative to me
self.machines[machine] += number_of_machines
for item, quantity in machine.inputs:
self.inputs[item] += quantity * number_of_machines / machine.time
for item, quantity in machine.outputs:
self.outputs[item] += quantity * number_of_machines / machine.time
# Cancel out inputs and outputs
for item in set(i for i in self.outputs if i in self.inputs):
cancelled = min(self.inputs[item], self.outputs[item])
self.inputs[item] -= cancelled
self.outputs[item] -= cancelled
self.partways[item] += cancelled
# Normalize number of machines
factor = number_of_machines.denominator
if factor != 1:
for k, v in self.machines.items():
self.machines[k] = v * factor
for k, v in self.inputs.items():
self.inputs[k] = v * factor
for k, v in self.partways.items():
self.partways[k] = v * factor
for k, v in self.outputs.items():
self.outputs[k] = v * factor
def report(self):
print('')
print(f'Have {len(self.machines)} machine types, totaling {sum(self.machines.values())} machines:')
for machine, quantity in self.machines.items():
print(f'- {quantity} times: {machine}')
pairs = (('Inputs', self.inputs),
('Partways', self.partways),
('Outputs', self.outputs))
for kind, dictionary in pairs:
print('')
print(f'{kind} ({len(dictionary)} types, total {fmtrate(sum(dictionary.values()))} per second):')
for item, rate in dictionary.items():
print(f'- {fmtrate(rate)} {item} per second')
def fmtrate(value):
human_powers = ('', 'k', 'M')
if not isinstance(value, float):
value = float(value)
sign = suffix = ''
if value < 0:
sign = '-'
value = abs(value)
power = floor(log(value, 10))
for current_suffix in human_powers:
suffix = current_suffix
if value < 1000:
break
else:
value /= 1000
power -= 3
rounded = round(value, 2 - power)
integer = int(rounded)
return sign + str(integer if integer == rounded else rounded) + suffix
from calc1 import BlueprintPlan
from mods import BioIndustries, Base
def main():
plastics_and_stuff()
fertilizer()
def nuclear_reactor():
BlueprintPlan(Base.nuclear_reactor,
Base.fuel_reprocessing).report()
def circuits():
BlueprintPlan(Base.bluec,
Base.redc,
Base.greenc,
Base.wire,
Base.acid,
Base.sulfur).report()
def wood_to_coal():
BlueprintPlan(BioIndustries.char2coal,
BioIndustries.pulp2char,
BioIndustries.wood2pulp).report()
def fertilizer():
BlueprintPlan(BioIndustries.fert2,
BioIndustries.fert1,
BioIndustries.nitrogen,
BioIndustries.pulp2ash,
BioIndustries.wood2pulp).report()
def plastics_and_stuff():
plan = BlueprintPlan(BioIndustries.sulfur,
BioIndustries.acid)
plan.add(BioIndustries.cellulose, 'cellulose')
plan.report()
def uranium_ammo():
BlueprintPlan(Base.ammo1,
Base.ammo2,
Base.ammo3).report()
if __name__ == '__main__':
main()
from calc1 import make, chemical_plant, cokery, am3, centrifuge
class Base:
wire = am3(0.5, copper=-1, wire=2)
greenc = am3(0.5, iron=-1, wire=-3, greenc=1)
redc = am3(6, wire=-4, greenc=-2, plastic=-2, redc=1)
bluec = am3(10, redc=-2, greenc=-20, acid=-5, bluec=1)
ammo1 = am3(1, iron=-4, ammo1=1)
ammo2 = am3(3, copper=-5, ammo1=-1, steel=-1, ammo2=1)
ammo3 = am3(10, ammo2=-1, uranium=-1, ammo3=1)
coal_liquefaction = make(5, coal=-10, steam=-50, heavy=10, light=15, gas=20)
offshore_pump = make(1, water=1200)
nuclear_reactor = make(200, cell=-1, depleted=1)
fuel_reprocessing = centrifuge(50, depleted=-5, uranium=3)
sulfur = chemical_plant(1, water=-30, gas=-30, sulfur=2)
acid = chemical_plant(1, sulfur=-5, iron=-1, water=-100, acid=50)
class BioIndustries:
wood2pulp = cokery(5, wood=-2, pulp=6)
pulp2char = cokery(12.5, pulp=-40, charcoal=18)
char2coal = cokery(18, charcoal=-12, coal=10)
pulp2ash = cokery(5, pulp=-10, ash=10)
cellulose = chemical_plant(20, pulp=-10, acid=-10, cellulose=10)
cellulose_steam = chemical_plant(5, pulp=-10, steam=-10, acid=-20, cellulose=10)
gas = make(5, biomass=-10, water=-10, gas=20)
light = make(5, biomass=-100, water=-10, light=80, cellulose=2)
plastic1 = chemical_plant(1, wood=-10, steam=-10, light=-20, plastic=2)
plastic2 = chemical_plant(1, cellulose=-1, gas=-10, plastic=2)
fert1 = chemical_plant(5, sulfur=-1, ash=-10, nitrogen=-10, fert1=5)
fert2 = chemical_plant(50, fert1=-20, pulp=-10, biomass=-10, fert2=20)
nitrogen = chemical_plant(10, air=-20, nitrogen=20)
air = chemical_plant(1, air=10)
biomass2 = make(10, ash=-10, water=-90, air=-10, biomass=90)
sulfur = chemical_plant(10, ash=-10, acid=-10, sulfur=10)
acid = chemical_plant(10, cellulose=-5, water=-90, biomass=-10, acid=50)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment