From 27743d9e6d79d61761749860a054be6334911138 Mon Sep 17 00:00:00 2001 From: Danny Robson Date: Sun, 14 Jul 2019 13:17:28 +1000 Subject: [PATCH] Initial conversion to NamedTuple representation --- data/recipes/items/components/ai_limiter.json | 2 +- .../machine/production/constructor.json | 2 +- .../recipes/machine/production/miner_mk1.json | 2 +- .../recipes/machine/production/miner_mk2.json | 2 +- .../machine/production/oil_refinery.json | 4 +- data/recipes/machine/production/smelter.json | 4 +- graph.py | 2 +- requirements.py | 21 ++++----- satisfactory/__init__.py | 43 ++++++++++++++++--- 9 files changed, 57 insertions(+), 25 deletions(-) diff --git a/data/recipes/items/components/ai_limiter.json b/data/recipes/items/components/ai_limiter.json index f2d44b0..e50202e 100644 --- a/data/recipes/items/components/ai_limiter.json +++ b/data/recipes/items/components/ai_limiter.json @@ -3,6 +3,7 @@ "component" ], "machine": "assembler", + "stack_size": 100, "recipes": [ { "input": { @@ -14,7 +15,6 @@ }, "crafting_time": 12, "clicks": 3, - "stack_size": 100, "requires": "caterium_electronics" } ] diff --git a/data/recipes/machine/production/constructor.json b/data/recipes/machine/production/constructor.json index 907cf76..0ac0aab 100644 --- a/data/recipes/machine/production/constructor.json +++ b/data/recipes/machine/production/constructor.json @@ -21,4 +21,4 @@ } } ] -} +} \ No newline at end of file diff --git a/data/recipes/machine/production/miner_mk1.json b/data/recipes/machine/production/miner_mk1.json index fb1a08d..0cda27a 100644 --- a/data/recipes/machine/production/miner_mk1.json +++ b/data/recipes/machine/production/miner_mk1.json @@ -2,7 +2,7 @@ "type": [ "machine" ], - "is": "miner", + "alias": "miner", "machine": "_craft_bench", "power_usage": 5, "size": [ diff --git a/data/recipes/machine/production/miner_mk2.json b/data/recipes/machine/production/miner_mk2.json index d969cdb..267dbfb 100644 --- a/data/recipes/machine/production/miner_mk2.json +++ b/data/recipes/machine/production/miner_mk2.json @@ -2,7 +2,7 @@ "type": [ "machine" ], - "is": "miner", + "alias": "miner", "machine": "_craft_bench", "power_usage": 12, "size": [ diff --git a/data/recipes/machine/production/oil_refinery.json b/data/recipes/machine/production/oil_refinery.json index 97b792a..57ffee6 100644 --- a/data/recipes/machine/production/oil_refinery.json +++ b/data/recipes/machine/production/oil_refinery.json @@ -3,6 +3,7 @@ "machine" ], "machine": "_craft_bench", + "size": [ 6, 10, 10 ], "power_usage": 50, "recipes": [ { @@ -12,8 +13,7 @@ }, "output": { "smelter": 1 - }, - "size": [ 6, 10, 10 ] + } } ] } diff --git a/data/recipes/machine/production/smelter.json b/data/recipes/machine/production/smelter.json index 387dad8..cc1ed13 100644 --- a/data/recipes/machine/production/smelter.json +++ b/data/recipes/machine/production/smelter.json @@ -3,6 +3,7 @@ "machine" ], "machine": "_craft_bench", + "size": [ 6, 10, 10 ], "power_usage": 4, "recipes": [ { @@ -12,8 +13,7 @@ }, "output": { "smelter": 1 - }, - "size": [ 6, 10, 10 ] + } } ] } diff --git a/graph.py b/graph.py index c9490ea..e78693f 100755 --- a/graph.py +++ b/graph.py @@ -16,7 +16,7 @@ def graph(cookbook: satisfactory.Cookbook, targets: Iterable[str]): while remain: output = remain.pop() - for need in cookbook.recipes(output)[0]['input']: + for need in cookbook.recipes(output)[0].input: print(f"{need} -> {output}") if need not in seen: remain.add(need) diff --git a/requirements.py b/requirements.py index cd3d761..5ee675d 100755 --- a/requirements.py +++ b/requirements.py @@ -15,9 +15,9 @@ def basic_rate(recipe: Dict) -> Fraction: :param recipe: :return: """ - for output, count in recipe['output'].items(): + for output, count in recipe.output.items(): return Fraction( - count, recipe['crafting_time'] + count, recipe.crafting_time ) @@ -48,17 +48,17 @@ def required_rates( continue # Assume we're using the default recipe - dst_recipe = recipes[dst_name]['recipes'][0] + 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(): + for src_name, src_count in dst_recipe.input.items(): src_rate = Fraction( src_count, - dst_recipe['crafting_time'] + dst_recipe.crafting_time ) * scale remain.append({src_name: src_rate}) @@ -90,11 +90,11 @@ def required_machines(recipes: satisfactory.Cookbook, rates: Dict[str, Fraction] descriptor = recipes[name] normal_rate = Fraction( - descriptor['recipes'][0]['output'][name], - descriptor['recipes'][0]['crafting_time'] + descriptor.recipes[0].output[name], + descriptor.recipes[0].crafting_time ) - machine = descriptor['machine'] + machine = descriptor.machine required_machines[machine][name] += requested_rate / normal_rate return required_machines @@ -115,7 +115,8 @@ def required_power(recipes: satisfactory.Cookbook, machines: Dict[str, int]) -> for machine, buckets in machines.items(): for result, rate in buckets.items(): count = int(math.ceil(rate)) - total += count * recipes[machine]['power_usage'] + source = recipes[machine] + total += count * source.power_usage print(machine, result, math.ceil(rate)) return total @@ -164,7 +165,7 @@ def main(): # 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 args.item}] + request = [{n: basic_rate(recipes[n].recipes[0]) for n in args.item}] plan(recipes, request) diff --git a/satisfactory/__init__.py b/satisfactory/__init__.py index d7aa1cd..a7482ed 100644 --- a/satisfactory/__init__.py +++ b/satisfactory/__init__.py @@ -1,11 +1,34 @@ import os import json +import logging -from typing import Dict, Generator, Iterable +from typing import Dict, Generator, Iterable, NamedTuple, Set, List, Optional + + +class Recipe(NamedTuple): + input: Dict[str, int] + output: Dict[str, int] + + crafting_time: Optional[int] = None + clicks: Optional[int] = None + stage: Optional[str] = None + requires: Optional[str] = None + + +class Item(NamedTuple): + type: Set[str] + machine: str + recipes: List[Recipe] + + alias: Optional[str] = None + stack_size: Optional[int] = None + power_usage: Optional[int] = None + size: Optional[List[int]] = None + requires: Optional[str] = None class Cookbook(object): - __recipes: Dict[str, Dict] + __recipes: Dict[str, Item] def __init__(self, root: str): self.__recipes = dict() @@ -14,8 +37,16 @@ class Cookbook(object): for f in files: path = os.path.join(dirname, f) name, _ = os.path.splitext(f) + with open(path, 'r') as src: - self.__recipes[name] = json.load(src) + j = json.load(src) + + try: + recipes = list(Recipe(**r) for r in j['recipes']) + j['recipes'] = recipes + self.__recipes[name] = Item(**j) + except Exception as ex: + logging.error(f"Could not load {name}: {ex}") def __getitem__(self, item: str) -> Dict[str, Dict]: return self.__recipes[item] @@ -31,14 +62,14 @@ class Cookbook(object): :param name: The target item name :return: A sequence of possible recipes for the target item """ - return self.__recipes[name]['recipes'] + return self.__recipes[name].recipes def is_component(self, name: str) -> bool: """ :param name: The name of an item :return: Whether the item is a component; ie, craftable. """ - return 'component' in self.__recipes[name]['type'] + return 'component' in self.__recipes[name].type def components(self) -> Generator[str, None, None]: """ @@ -51,7 +82,7 @@ class Cookbook(object): :param name: The name of an item :return: Whether the item must have harvested (rather than crafted) """ - return 'resource' in self.__recipes[name]['type'] + return 'resource' in self.__recipes[name].type def resources(self) -> Generator[str, None, None]: """