From bdd0d26acbee8a0ecd06e4e4633ed3bd591a204b Mon Sep 17 00:00:00 2001 From: Ajurna Date: Fri, 7 Nov 2025 17:07:52 +0000 Subject: [PATCH] testing --- main.py | 131 +++++++++++++++++++++++------------- plus.py | 19 ++---- shared.py | 12 ++++ vanilla.py | 193 ++++++++++++++++++++++++++++++++++++++++++++++++----- 4 files changed, 281 insertions(+), 74 deletions(-) create mode 100644 shared.py diff --git a/main.py b/main.py index dc2b3d1..2b3c1e8 100644 --- a/main.py +++ b/main.py @@ -5,10 +5,13 @@ from typing import Dict, List, Tuple, Optional, Any import networkx as nx from flask import Flask, render_template, request from pydantic import BaseModel, Field -from plus import Items, Machines, Recipes, Recipe +# from plus import Items, Machines, Recipes, Recipe +from vanilla import Items, Machines, Recipes, Recipe, RawResources from rich import print app = Flask(__name__) + + # Helpers to map item names to safe query parameter keys def _slugify(name: str) -> str: s = ''.join(ch.lower() if ch.isalnum() else '_' for ch in name) @@ -29,53 +32,88 @@ class Production(BaseModel): recipe: Recipe quantity: float +class ProductionChain: + def __init__(self): + self.recipe_map: Dict[Items, list[Recipes]] = defaultdict(list) + for r in Recipes: + for o in r.value.outputs: + self.recipe_map[o].append(r.value) -def get_recipe(item: Items, recipe_map: Dict[Items, list[Recipes]]) -> Optional[Recipe]: - return recipe_map.get(item, (None,))[0] + def get_recipe(self, item: Items, recipe_map: Dict[Items, list[Recipes]]) -> Optional[Recipe]: + # TODO: make logic for priority selection of recipes + return recipe_map.get(item, (None,))[0] -def compute_chain2(targets: Dict[Items, float]) -> Any: - if not targets: - return {} - recipe_map: Dict[Items, list[Recipes]] = defaultdict(list) - for r in Recipes: - for o in r.value.outputs: - recipe_map[o].append(r.value) - # print(recipe_map) - demand = defaultdict(float) - for target in targets: - demand[target] = targets[target] - production = defaultdict(float) - # add demands to production - # find a recipe for that demand - # add inputs to demand - queue = deque(targets) - g = nx.DiGraph() - production_queue = [] - raw_resources = defaultdict(float) - production_chain = defaultdict(float) - while queue: - item = queue.popleft() - recipe = get_recipe(item, recipe_map) - if recipe is None: - raw_resources[item] += demand[item] - raw_resources[item] - continue - levels = [] - for out, quantity in recipe.outputs.items(): - if out in demand: - target_quantity = demand[out] - production[out] - if target_quantity > 0: - levels.append(target_quantity / quantity) + def calculate_excess(self, production: Dict[Items, float], demand: Dict[Items, float], raw_resources: Dict[Items, float]): + excess = defaultdict(float) + for item, quantity in demand.items(): + total = production[item] - demand[item] + if total != 0: + if item in RawResources: + raw_resources[item] -= production[item] else: - levels.append(0) - production_level = max(levels) if max(levels) > 0 else 0 - production_chain[recipe.name] = production_level - for out, quantity in recipe.outputs.items(): - production[out] += production_level * quantity - for inp, quantity in recipe.inputs.items(): - queue.append(inp) - demand[inp] += production_level * quantity - print(demand, production, raw_resources, production_chain) - return production + excess[item] = total + return excess + + def calculate_byproduct(self, production: Dict[Items, float], demand: Dict[Items, float], raw_resources: Dict[Items, float]): + byproduct = defaultdict(float) + for item, quantity in production.items(): + if item not in demand: + byproduct[item] = quantity + return byproduct + + def compute_chain(self, targets: Dict[Items, float]) -> Any: + if not targets: + return {} + demand = defaultdict(float) + for target in targets: + demand[target] = targets[target] + + queue = deque(targets) + production = defaultdict(float) + raw_resources = defaultdict(float) + production_chain = defaultdict(float) + while queue: + item = queue.popleft() + if item == Items.Silica: + print("silica") + recipe = self.get_recipe(item, self.recipe_map) + if item in RawResources: + raw_resources[item] += demand[item] - raw_resources[item] + continue + if item in production: + if production[item] == demand[item]: + continue + + levels = [] + for out, quantity in recipe.outputs.items(): + if out in demand: + target_quantity = demand[out] - production[out] + levels.append(target_quantity / quantity) + + production_level = max(levels) + production_chain[recipe.name] += production_level + for out, quantity in recipe.outputs.items(): + production[out] += production_level * quantity + for byproduct, quantity in recipe.byproducts.items(): + production[byproduct] += production_level * quantity + queue.append(byproduct) + for inp, quantity in recipe.inputs.items(): + queue.append(inp) + demand[inp] += production_level * quantity + + excess = self.calculate_excess(production, demand, raw_resources) + byproduct =self.calculate_byproduct(production, demand, raw_resources) + + + print("demand:", demand) + print("production:", production) + print("excess:", excess) + print("byproduct:", byproduct) + print("raw resources:", raw_resources) + print("production chain:", production_chain) + + + return production_chain def compute_chain(targets: Dict[Items, float], preferred_by_output: Optional[Dict[Items, str]] = None) -> Tuple[Dict[str, float], List[dict], Dict[str, float], Dict[str, float]]: @@ -388,4 +426,5 @@ def create_app(): if __name__ == "__main__": # For local dev: python main.py # app.run(host="0.0.0.0", port=5000, debug=True) - compute_chain2({Items.ModularFrame: 45.0}) \ No newline at end of file + prod_chain = ProductionChain() + prod_chain.compute_chain({Items.FusedModularFrame: 1.5}) \ No newline at end of file diff --git a/plus.py b/plus.py index f4e5831..5826655 100644 --- a/plus.py +++ b/plus.py @@ -1,15 +1,9 @@ from enum import Enum from typing import Dict - +from shared import Machine, Item from pydantic import BaseModel, Field, ConfigDict -class Item(BaseModel): - name: str - -class Machine(BaseModel): - name: str - class Machines(Enum): Miner = Machine(name="Miner") Smelter = Machine(name="Smelter") @@ -80,12 +74,11 @@ class Items(Enum): class Recipe(BaseModel): model_config = ConfigDict(frozen=True) - name: str # Human-friendly name - building: Machines # e.g., "Smelter", "Constructor" - outputs: Dict[Items, float] # Produced item name + name: str + building: Machines + outputs: Dict[Items, float] inputs: Dict[Items, float] = Field(default_factory=dict) - class Recipes(Enum): # Crusher # - Crushing Ores @@ -476,4 +469,6 @@ class Recipes(Enum): Items.CateriumPlate: 24.0, Items.TinnedWire: 18.0, }, - ) \ No newline at end of file + ) + +Recipe.model_rebuild() \ No newline at end of file diff --git a/shared.py b/shared.py new file mode 100644 index 0000000..6833630 --- /dev/null +++ b/shared.py @@ -0,0 +1,12 @@ +from enum import Enum, EnumType +from typing import Dict + +from pydantic import BaseModel, ConfigDict, Field + + +class Item(BaseModel): + name: str + +class Machine(BaseModel): + name: str + diff --git a/vanilla.py b/vanilla.py index d36c09f..87773b1 100644 --- a/vanilla.py +++ b/vanilla.py @@ -1,20 +1,68 @@ +from enum import Enum +from typing import Dict + +from pydantic import BaseModel, Field +from shared import Machine, Item + +class Machines(Enum): + Smelter = Machine(name="Smelter") + Constructor = Machine(name="Constructor") + Assembler = Machine(name="Assembler") + Foundry = Machine(name="Foundry") + Manufacturer = Machine(name="Manufacturer") + Blender = Machine(name="Blender") + Refinery = Machine(name="Refinery") + + +class Items(Enum): + IronIngot = Item(name="Iron Ingot") + CopperIngot = Item(name="Copper Ingot") + Limestone = Item(name="Limestone") + IronOre = Item(name="Iron Ore") + CopperOre = Item(name="Copper Ore") + IronPlate = Item(name="Iron Plate") + IronRod = Item(name="Iron Rod") + Wire = Item(name="Wire") + Cable = Item(name="Cable") + Concrete = Item(name="Concrete") + Screw = Item(name="Screw") + ReinforcedIronPlate = Item(name="Reinforced Iron Plate") + ModularFrame = Item(name="Modular Frame") + HeavyModularFrame = Item(name="Heavy Modular Frame") + EncasedIndustrialBeam = Item(name="Encased Industrial Beam") + SteelPipe = Item(name="Steel Pipe") + SteelBeam = Item(name="Steel Beam") + SteelIngot = Item(name="Steel Ingot") + Coal = Item(name="Coal") + FusedModularFrame = Item(name="Fused Modular Frame") + NitrogenGas = Item(name="Nitrogen Gas") + AluminumCasing = Item(name="Aluminum Casing") + AluminumIngot = Item(name="Aluminum Ingot") + Silica = Item(name="Silica") + AluminumScrap = Item(name="Aluminum Scrap") + AluminaSolution = Item(name="Alumina Solution") + Water = Item(name="Water") + RawQuartz = Item(name="Raw Quartz") + Bauxite = Item(name="Bauxite") + +class RawResources(Enum): + Water = Items.Water + Coal = Items.Coal + IronOre = Items.IronOre + CopperOre = Items.CopperOre + Limestone = Items.Limestone + NitrogenGas = Items.NitrogenGas + RawQuartz = Items.RawQuartz + Bauxite = Items.Bauxite + +class Recipe(BaseModel): + name: str + building: Machines + outputs: Dict[Items, float] + inputs: Dict[Items, float] = Field(default_factory=dict) + byproducts: Dict[Items, float] = Field(default_factory=dict) + class Recipes(Enum): - # Mining - IronOre = Recipe( - name="Iron Ore", - building=Machines.Miner, - outputs={Items.IronOre: 60.0}, - ) - CopperOre = Recipe( - name="Copper Ore", - building=Machines.Miner, - outputs={Items.CopperOre: 60.0}, - ) - Limestone = Recipe( - name="Limestone", - building=Machines.Miner, - outputs={Items.Limestone: 60.0}, - ) # Smelting IronIngot = Recipe( name="Iron Ingot", @@ -76,4 +124,117 @@ class Recipes(Enum): Items.IronPlate: 30.0, Items.Screw: 60.0, }, + ) + ModularFrame = Recipe( + name="Modular Frame", + building=Machines.Assembler, + outputs={Items.ModularFrame: 2.0}, + inputs={ + Items.IronRod: 12.0, + Items.ReinforcedIronPlate: 3.0, + }, + ) + HeavyModularFrame = Recipe( + name="Heavy Modular Frame", + building=Machines.Manufacturer, + outputs={Items.HeavyModularFrame: 2.0}, + inputs={ + Items.SteelPipe: 40.0, + Items.Screw: 240.0, + Items.EncasedIndustrialBeam: 10.0, + Items.ModularFrame: 10.0, + }, + ) + EncasedIndustrialBeam = Recipe( + name="Encased Industrial Beam", + building=Machines.Assembler, + outputs={Items.EncasedIndustrialBeam: 6.0}, + inputs={ + Items.Concrete: 36.0, + Items.SteelBeam: 18.0, + }, + ) + SteelPipe = Recipe( + name="Steel Pipe", + building=Machines.Constructor, + outputs={Items.SteelPipe: 20.0}, + inputs={ + Items.SteelIngot: 30.0 + }, + ) + SteelBeam = Recipe( + name="Steel Beam", + building=Machines.Constructor, + outputs={Items.SteelBeam: 15.0}, + inputs={ + Items.SteelIngot: 60.0 + }, + ) + SteelIngot = Recipe( + name="Steel Ingot", + building=Machines.Foundry, + outputs={Items.SteelIngot: 45.0}, + inputs={ + Items.Coal: 45.0, + Items.IronOre: 45.0, + } + ) + FusedModularFrame = Recipe( + name="Fused Modular Frame", + building=Machines.Blender, + outputs={Items.FusedModularFrame: 1.5}, + inputs={ + Items.NitrogenGas: 37.5, + Items.AluminumCasing: 75.0, + Items.HeavyModularFrame: 1.5, + }, + ) + AluminumCasing = Recipe( + name="Aluminum Casing", + building=Machines.Constructor, + outputs={Items.AluminumCasing: 60.0}, + inputs={ + Items.AluminumIngot: 90.0, + }, + ) + AluminumIngot = Recipe( + name="Aluminum Ingot", + building=Machines.Foundry, + outputs={Items.AluminumIngot: 60.0}, + inputs={ + Items.Silica: 75.0, + Items.AluminumScrap: 90.0, + } + ) + AluminumScrap = Recipe( + name="Aluminum Scrap", + building=Machines.Refinery, + outputs={ + Items.AluminumScrap: 360.0 + }, + inputs={ + Items.Coal: 120.0, + Items.AluminaSolution: 240.0, + }, + byproducts={Items.Water: 120.0} + ) + Silica = Recipe( + name="Silica", + building=Machines.Constructor, + outputs={Items.Silica: 37.5}, + inputs={ + Items.RawQuartz: 22.5, + } + ) + AluminaSolution = Recipe( + name="Alumina Solution", + building=Machines.Refinery, + outputs={ + Items.AluminaSolution: 120.0 + }, + inputs={ + Items.Water: 180.0, + Items.Bauxite: 120.0, + }, + byproducts={Items.Silica: 50.0} ) \ No newline at end of file