Display resources for each factory in the Rocket Valley Tycoon I'm new here. I'm trying to teach myself programming by learning Python. I have assigned myself the following task.

There's a game I'm playing (Rocket Valley Tycoon) where the goal is to basically place an Extractor to gather resources, then use Factories to transform the resource into a good, which then usually gets processed again by another Factory into a processed good.

The point of the program I'm trying to write is to be able to see the resources needed for any Factory. So, for example, Copper plates need 10 carbon and 20 copper ore, and at the same time, the carbon needs 2 coal and 4 water, so I would like the final output to be something like:

Copper plates need 20 copper ore, 20 coal, and 40 water. I have a working prototype, that works with just this, but if I know it can be simplified somehow. (Especially in the print_total function)

Also, this only works with a Factory that has just one "child" Factory and not with n "child" Factories. This is the first tech tree: 

Can someone please point me in the right direction?

class Factory:

    def __init__(self, name, ing1, amount1, ing2=None, amount2=None):
        self.name = name
        self.ing1 = ing1
        self.amount1 = amount1
        self.ing2 = ing2
        self.amount2 = amount2
        self.total_amount1_1 = None
        self.total_amount1_2 = None
        self.total_amount2_1 = None
        self.total_amount2_2 = None
        self.total_ing1_ing1 = None
        self.total_ing1_ing2 = None
        self.total_ing2_ing1 = None
        self.total_ing2_ing2 = None

    def print_ing(self):
        print(self.name + ' needs: ')
        print(self.amount1, self.ing1.name, self.amount2, self.ing2.name)

    def print_total(self):
        if isinstance(self.ing1, Factory):
            print('Ing1 is Factory')
            self.total_ing1_ing1 = self.ing1.ing1
            self.total_ing1_ing2 = self.ing1.ing2
            self.total_amount1_1 = self.amount1 * self.ing1.amount1
            self.total_amount1_2 = self.amount1 * self.ing1.amount2
        elif isinstance(self.ing2, Factory):
            print('Ing2 is Factory')
            self.total_ing2_ing1 = self.ing2.ing1
            self.total_ing2_ing2 = self.ing2.ing2
            self.total_amount2_1 = self.amount2 * self.ing2.amount1
            self.total_amount2_2 = self.amount2 * self.ing2.amount2
        print(self.name + ' needs: ')
        print(self.amount1, self.ing1.name, self.total_amount2_1, 
              self.total_ing2_ing1.name, self.total_amount2_2,\
              self.total_ing2_ing2.name)


class Extractor:
    def __init__(self, name):
        self.name = name


coal = Extractor('Coal')
water = Extractor('Water')
copper_ore = Extractor('Copper Ore')
carbon = Factory('Carbon', coal, 2, water, 4)
copper_plates = Factory('Copper Plates', copper_ore, 20, carbon, 10)
I would turn this into one class that takes a list of ingredients and cost tuples. The resulting object can be added with another of its instance or multiplied with a number.

from collections import defaultdict

class Ingredient:

    def __init__(self, *ingredients):
        self.d = defaultdict(int)
        if len(ingredients) == 1 and isinstance(ingredients[0], str):
            # special case for base ingredients
            self.d[ingredients[0]] = 1
        else:
            for ingredient, amount in ingredients:
                self.d[ingredient] += amount

    @property
    def ingredients(self):
        return list(self.d.keys())

    @property
    def amounts(self):
        return list(self.d.values())

    def __rmul__(self, other):
         # allows doing other * self, where other is a number
        return Ingredient(*zip(self.ingredients, [other * x for x in self.amounts]))

    def __add__(self, other):
        # allows self + other, where other is another Ingredient
        return Ingredient(*zip(self.ingredients + other.ingredients,
                               self.amounts + other.amounts))

    def __str__(self):
        return str(list(self.d.items()))


Then you can define the base items and any derived item is just algebraic manipulations:

coal = Ingredient("Coal")
water = Ingredient("Water")
copper_ore = Ingredient("Copper Ore")
carbon = 2 * water + 4 * coal
copper_plates = 20 * copper_ore + 10 * carbon
print(copper_plates)
# [('Copper Ore', 20), ('Water', 20), ('Coal', 40)]