id rather be famous instead

stable
Valerie (Bronwen) 2023-11-23 18:14:39 -05:00
parent 8a93dc36f0
commit 2474b474e6
5 changed files with 164 additions and 56 deletions

2
.gitignore vendored 100644
View File

@ -0,0 +1,2 @@
__pycache__
.venv

16
.vscode/launch.json vendored 100644
View File

@ -0,0 +1,16 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python: Current File",
"type": "python",
"request": "launch",
"program": "main.py",
"console": "integratedTerminal",
"justMyCode": true
}
]
}

46
data.py
View File

@ -1,8 +1,8 @@
from enum import Enum from enum import Enum
from engine import Recipe from engine import Collection, Recipe
class Item(Enum): class Items(Collection):
IronBar = "Iron Bar" IronBar = "Iron Bar"
CopperBar = "Copper Bar" CopperBar = "Copper Bar"
SteelBar = "Steel Bar" SteelBar = "Steel Bar"
@ -23,28 +23,28 @@ class Item(Enum):
SteamCrusher = "Steam Crusher" SteamCrusher = "Steam Crusher"
Pipe = "Pipe" Pipe = "Pipe"
class Recipes(Enum): class Recipes(Collection):
IronPlate = Recipe([Item.IronBar], [Item.IronPlate], "Iron Plate") IronPlate = Recipe([Items.IronBar], [Items.IronPlate], "Iron Plate")
CopperPlate = Recipe([Item.CopperBar], [Item.CopperPlate], "Copper Plate") CopperPlate = Recipe([Items.CopperBar], [Items.CopperPlate], "Copper Plate")
IronBeam = Recipe([Item.IronBar], [Item.IronBeam], "Iron Beam") IronBeam = Recipe([Items.IronBar], [Items.IronBeam], "Iron Beam")
CopperBeam = Recipe([Item.CopperBar], [Item.CopperBeam], "Copper Beam") CopperBeam = Recipe([Items.CopperBar], [Items.CopperBeam], "Copper Beam")
SteelBeam = Recipe([Item.SteelBar], [Item.SteelBeam], "Steel Beam") SteelBeam = Recipe([Items.SteelBar], [Items.SteelBeam], "Steel Beam")
IronMechanicalParts = Recipe([Item.IronBar], [Item.IronMechanicalParts], "Iron Mechanical Parts") IronMechanicalParts = Recipe([Items.IronBar], [Items.IronMechanicalParts], "Iron Mechanical Parts")
CopperMechanicalParts = Recipe([Item.CopperBar], [Item.CopperMechanicalParts], "Copper Mechanical Parts") CopperMechanicalParts = Recipe([Items.CopperBar], [Items.CopperMechanicalParts], "Copper Mechanical Parts")
SteelMechanicalParts = Recipe([Item.SteelBar], [Item.SteelMechanicalParts], "Steel Mechanical Parts") SteelMechanicalParts = Recipe([Items.SteelBar], [Items.SteelMechanicalParts], "Steel Mechanical Parts")
Pipe = Recipe([Item.IronPlate, Item.IronMechanicalParts], [Item.Pipe], "Pipe") Pipe = Recipe([Items.IronPlate, Items.IronMechanicalParts], [Items.Pipe], "Pipe")
SteamEngine = Recipe([Item.Pipe, Item.CopperMechanicalParts], [Item.SteamEngine], "Steam Engine") SteamEngine = Recipe([Items.Pipe, Items.CopperMechanicalParts], [Items.SteamEngine], "Steam Engine")
SteamAssembler = Recipe([ SteamAssembler = Recipe([
Item.Pipe, Items.Pipe,
Item.CopperMechanicalParts, Items.CopperMechanicalParts,
Item.SteamEngine, Items.SteamEngine,
Item.CopperBeam Items.CopperBeam
], [Item.SteamAssembler], "Steam Assembler") ], [Items.SteamAssembler], "Steam Assembler")
SteamCrusher = Recipe([ SteamCrusher = Recipe([
Item.Pipe, Items.Pipe,
Item.IronMechanicalParts, Items.IronMechanicalParts,
Item.SteamEngine, Items.SteamEngine,
Item.CopperBeam Items.CopperBeam
], [Item.SteamCrusher], "Steam Crusher") ], [Items.SteamCrusher], "Steam Crusher")

126
engine.py
View File

@ -1,7 +1,20 @@
from typing import List, Set, Tuple from typing import List, Set, Tuple
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle from matplotlib.patches import Rectangle
from typing import List, Any from typing import List, Generic, TypeVar, Callable, Set, Tuple, Dict, Any
T = TypeVar("T")
class Collection(Generic[T]):
@classmethod
def as_set(clazz) -> set[T]:
return {
key: value
for (key, value) in clazz.__dict__.items()
if not key.startswith("__")
and not hasattr(Collection, key)
}
class Item: class Item:
def __init__(self, name: str) -> None: def __init__(self, name: str) -> None:
@ -9,8 +22,8 @@ class Item:
class Recipe: class Recipe:
def __init__(self, inputs: List[Item], outputs: List[Item], name: str) -> None: def __init__(self, inputs: List[Item], outputs: List[Item], name: str) -> None:
self.inputs = inputs self.inputs = set(inputs)
self.outputs = outputs self.outputs = set(outputs)
self.name = name self.name = name
class World: class World:
@ -38,20 +51,23 @@ def find_valid_recipes(output: Item, world: World) -> List[Recipe]:
class Assembler: class Assembler:
def __init__(self, recipe: Recipe, world: World) -> None: def __init__(self, recipe: Recipe, world: World) -> None:
self.recipe = recipe self.recipe = recipe
self.input_links = [] # List[Assembler] self.input_links: Set[Assembler] = set() # List[Assembler]
self.world = world self.world = world
def link_input(self, assembler: "Assembler") -> None: def link_input(self, assembler: "Assembler") -> None:
self.input_links.append(assembler) self.input_links.append(assembler)
def get_unlinked_inputs(self) -> List[Item]: def get_unlinked_inputs(self) -> List[Item]:
print(type(self.recipe))
recv = [
output
for assembler in self.input_links
for output in assembler.recipe.outputs
]
return [ return [
item item
for item in self.recipe.inputs for item in self.recipe.inputs
if item not in [ if item not in recv
assembler.recipe.outputs
for assembler in self.input_links
].flatten()
and item not in self.world.bus and item not in self.world.bus
] ]
@ -67,9 +83,40 @@ class Graph:
] ]
self.world = world self.world = world
def __hash__(self) -> int:
recipes = []
for x in range(len(self.grid)):
for y in range(len(self.grid)):
assembler = self.get_assembler_at(x, y)
if assembler is not None:
recipes.append(assembler.recipe)
return hash((
tuple(self.world.bus),
self.world.size,
tuple(self.world.recipes),
tuple(self.world.items),
tuple(recipes)
))
def __eq__(self, other: "Graph") -> bool:
if self.world != other.world:
return False
for x in range(len(self.grid)):
for y in range(len(self.grid)):
if self.get_assembler_at(x, y).recipe != other.get_assembler_at(x, y).recipe:
return False
a_linked_coords = self.get_assembler_at(x, y).input_links.map(lambda assembler: self.get_coordinates_of_assembler(assembler))
b_linked_coords = other.get_assembler_at(x, y).input_links.map(lambda assembler: other.get_coordinates_of_assembler(assembler))
if a_linked_coords != b_linked_coords:
return False
return True
def create_graph(world: World, recipe: Recipe) -> "Graph": def create_graph(world: World, recipe: Recipe) -> "Graph":
graph = Graph(world) graph = Graph(world)
graph.grid[1][world.size // 2] = recipe graph.grid[1][world.size // 2] = Assembler(recipe, world)
print(recipe)
return graph return graph
def get_coordinates_of_assembler(self, assembler: Assembler) -> (int, int): def get_coordinates_of_assembler(self, assembler: Assembler) -> (int, int):
@ -132,6 +179,21 @@ class Graph:
adjacent_coordinates = self.get_adjacent_coordinates(a[0], a[1]) adjacent_coordinates = self.get_adjacent_coordinates(a[0], a[1])
return b in adjacent_coordinates return b in adjacent_coordinates
def get_assembler_at(self, x: int, y: int) -> Assembler:
if x < 0 or x >= len(self.grid) or y < 0 or y >= len(self.grid):
return None
if self.grid[x][y] is None:
return None
return self.grid[x][y]
def get_adjacent_assemblers(self, x: int, y: int) -> List[Assembler]:
assemblers = []
for (x, y) in self.get_adjacent_coordinates(x, y):
assembler = self.get_assembler_at(x, y)
if assembler is not None:
assemblers.append(assembler)
return assemblers
def draw(self) -> None: def draw(self) -> None:
plt.figure() plt.figure()
@ -156,21 +218,45 @@ class Graph:
plt.ylim(-1, 11) plt.ylim(-1, 11)
# Show the plot # Show the plot
plt.show() plt.show(block=False)
plt.pause(5)
plt.close()
def get_solutions(self) -> List["Graph"]: def solve(graph: Graph) -> Set["Graph"]:
if self.is_solved():
return []
graphs: List["Graph"] = [] if graph.is_solved():
return set([graph])
for x in range(len(self.grid)): graphs: Set["Graph"] = set([graph])
for y in range(len(self.grid)):
assembler = self.grid[x][y] def count_unsolved_graphs(graphs: Set[Graph]) -> int:
count = 0
for graph in graphs:
if not graph.is_solved():
count += 1
return count
def get_unsolved_graph(graphs: Set[Graph]) -> Graph:
print(f"grabbing next unsolved graph ({count_unsolved_graphs(graphs)})")
selected = None
for graph in graphs:
if not graph.is_solved():
selected = graph
break
if selected is not None:
graphs.remove(selected)
return selected
while count_unsolved_graphs(graphs) > 0:
graph = get_unsolved_graph(graphs)
for x in range(len(graph.grid)):
for y in range(len(graph.grid)):
assembler = graph.get_assembler_at(x, y)
if assembler == None: if assembler == None:
continue continue
print(assembler) for item in assembler.get_unlinked_inputs():
# for item in assembler.get_unlinked_inputs(): print(item)
# for recipes in find_valid_recipes(item): # for recipes in find_valid_recipes(item):
# graph = self.add_assembler(assembler, x, y) # graph = self.add_assembler(assembler, x, y)
# graphs.append(graph) # graphs.append(graph)

28
main.py
View File

@ -1,18 +1,22 @@
from typing import List from typing import List
from data import Item, Recipes from data import Items, Recipes
from engine import Graph from engine import Graph, World, solve
bus_resources = [ world = World(
Item.IronBar, bus=[
Item.CopperBar, Items.IronBar,
Item.SteelBar, Items.CopperBar,
] Items.SteelBar,
],
items=Items.as_set(),
recipes=Recipes.as_set(),
size=10
)
solutions = solve(Graph.create_graph(world, Recipes.IronPlate))
if not solutions:
print("No solutions found")
exit(1)
solutions = Graph.create_graph(10, Recipes.IronPlate).get_solutions()
for solution in solutions: for solution in solutions:
solution.draw() solution.draw()