This commit is contained in:
2025-11-07 17:07:52 +00:00
parent 58c83f6140
commit bdd0d26acb
4 changed files with 281 additions and 74 deletions

131
main.py
View File

@@ -5,10 +5,13 @@ from typing import Dict, List, Tuple, Optional, Any
import networkx as nx import networkx as nx
from flask import Flask, render_template, request from flask import Flask, render_template, request
from pydantic import BaseModel, Field 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 from rich import print
app = Flask(__name__) app = Flask(__name__)
# Helpers to map item names to safe query parameter keys # Helpers to map item names to safe query parameter keys
def _slugify(name: str) -> str: def _slugify(name: str) -> str:
s = ''.join(ch.lower() if ch.isalnum() else '_' for ch in name) s = ''.join(ch.lower() if ch.isalnum() else '_' for ch in name)
@@ -29,53 +32,88 @@ class Production(BaseModel):
recipe: Recipe recipe: Recipe
quantity: float 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]: def get_recipe(self, item: Items, recipe_map: Dict[Items, list[Recipes]]) -> Optional[Recipe]:
return recipe_map.get(item, (None,))[0] # TODO: make logic for priority selection of recipes
return recipe_map.get(item, (None,))[0]
def compute_chain2(targets: Dict[Items, float]) -> Any: def calculate_excess(self, production: Dict[Items, float], demand: Dict[Items, float], raw_resources: Dict[Items, float]):
if not targets: excess = defaultdict(float)
return {} for item, quantity in demand.items():
recipe_map: Dict[Items, list[Recipes]] = defaultdict(list) total = production[item] - demand[item]
for r in Recipes: if total != 0:
for o in r.value.outputs: if item in RawResources:
recipe_map[o].append(r.value) raw_resources[item] -= production[item]
# 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)
else: else:
levels.append(0) excess[item] = total
production_level = max(levels) if max(levels) > 0 else 0 return excess
production_chain[recipe.name] = production_level
for out, quantity in recipe.outputs.items(): def calculate_byproduct(self, production: Dict[Items, float], demand: Dict[Items, float], raw_resources: Dict[Items, float]):
production[out] += production_level * quantity byproduct = defaultdict(float)
for inp, quantity in recipe.inputs.items(): for item, quantity in production.items():
queue.append(inp) if item not in demand:
demand[inp] += production_level * quantity byproduct[item] = quantity
print(demand, production, raw_resources, production_chain) return byproduct
return production
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]]: 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__": if __name__ == "__main__":
# For local dev: python main.py # For local dev: python main.py
# app.run(host="0.0.0.0", port=5000, debug=True) # app.run(host="0.0.0.0", port=5000, debug=True)
compute_chain2({Items.ModularFrame: 45.0}) prod_chain = ProductionChain()
prod_chain.compute_chain({Items.FusedModularFrame: 1.5})

19
plus.py
View File

@@ -1,15 +1,9 @@
from enum import Enum from enum import Enum
from typing import Dict from typing import Dict
from shared import Machine, Item
from pydantic import BaseModel, Field, ConfigDict from pydantic import BaseModel, Field, ConfigDict
class Item(BaseModel):
name: str
class Machine(BaseModel):
name: str
class Machines(Enum): class Machines(Enum):
Miner = Machine(name="Miner") Miner = Machine(name="Miner")
Smelter = Machine(name="Smelter") Smelter = Machine(name="Smelter")
@@ -80,12 +74,11 @@ class Items(Enum):
class Recipe(BaseModel): class Recipe(BaseModel):
model_config = ConfigDict(frozen=True) model_config = ConfigDict(frozen=True)
name: str # Human-friendly name name: str
building: Machines # e.g., "Smelter", "Constructor" building: Machines
outputs: Dict[Items, float] # Produced item name outputs: Dict[Items, float]
inputs: Dict[Items, float] = Field(default_factory=dict) inputs: Dict[Items, float] = Field(default_factory=dict)
class Recipes(Enum): class Recipes(Enum):
# Crusher # Crusher
# - Crushing Ores # - Crushing Ores
@@ -476,4 +469,6 @@ class Recipes(Enum):
Items.CateriumPlate: 24.0, Items.CateriumPlate: 24.0,
Items.TinnedWire: 18.0, Items.TinnedWire: 18.0,
}, },
) )
Recipe.model_rebuild()

12
shared.py Normal file
View File

@@ -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

View File

@@ -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): 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 # Smelting
IronIngot = Recipe( IronIngot = Recipe(
name="Iron Ingot", name="Iron Ingot",
@@ -76,4 +124,117 @@ class Recipes(Enum):
Items.IronPlate: 30.0, Items.IronPlate: 30.0,
Items.Screw: 60.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}
) )