From 2474b474e655794aad1b967b4f40e594d5aff914 Mon Sep 17 00:00:00 2001 From: "Valerie (Bronwen)" Date: Thu, 23 Nov 2023 18:14:39 -0500 Subject: [PATCH] id rather be famous instead --- .gitignore | 2 + .vscode/launch.json | 16 ++++++ data.py | 46 ++++++++-------- engine.py | 128 ++++++++++++++++++++++++++++++++++++-------- main.py | 28 +++++----- 5 files changed, 164 insertions(+), 56 deletions(-) create mode 100644 .gitignore create mode 100644 .vscode/launch.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b0f2192 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +__pycache__ +.venv \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..3f6586d --- /dev/null +++ b/.vscode/launch.json @@ -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 + } + ] +} \ No newline at end of file diff --git a/data.py b/data.py index dbaa03b..36cd7cf 100644 --- a/data.py +++ b/data.py @@ -1,8 +1,8 @@ from enum import Enum -from engine import Recipe +from engine import Collection, Recipe -class Item(Enum): +class Items(Collection): IronBar = "Iron Bar" CopperBar = "Copper Bar" SteelBar = "Steel Bar" @@ -23,28 +23,28 @@ class Item(Enum): SteamCrusher = "Steam Crusher" Pipe = "Pipe" -class Recipes(Enum): - IronPlate = Recipe([Item.IronBar], [Item.IronPlate], "Iron Plate") - CopperPlate = Recipe([Item.CopperBar], [Item.CopperPlate], "Copper Plate") - IronBeam = Recipe([Item.IronBar], [Item.IronBeam], "Iron Beam") - CopperBeam = Recipe([Item.CopperBar], [Item.CopperBeam], "Copper Beam") - SteelBeam = Recipe([Item.SteelBar], [Item.SteelBeam], "Steel Beam") - IronMechanicalParts = Recipe([Item.IronBar], [Item.IronMechanicalParts], "Iron Mechanical Parts") - CopperMechanicalParts = Recipe([Item.CopperBar], [Item.CopperMechanicalParts], "Copper Mechanical Parts") - SteelMechanicalParts = Recipe([Item.SteelBar], [Item.SteelMechanicalParts], "Steel Mechanical Parts") - Pipe = Recipe([Item.IronPlate, Item.IronMechanicalParts], [Item.Pipe], "Pipe") - SteamEngine = Recipe([Item.Pipe, Item.CopperMechanicalParts], [Item.SteamEngine], "Steam Engine") +class Recipes(Collection): + IronPlate = Recipe([Items.IronBar], [Items.IronPlate], "Iron Plate") + CopperPlate = Recipe([Items.CopperBar], [Items.CopperPlate], "Copper Plate") + IronBeam = Recipe([Items.IronBar], [Items.IronBeam], "Iron Beam") + CopperBeam = Recipe([Items.CopperBar], [Items.CopperBeam], "Copper Beam") + SteelBeam = Recipe([Items.SteelBar], [Items.SteelBeam], "Steel Beam") + IronMechanicalParts = Recipe([Items.IronBar], [Items.IronMechanicalParts], "Iron Mechanical Parts") + CopperMechanicalParts = Recipe([Items.CopperBar], [Items.CopperMechanicalParts], "Copper Mechanical Parts") + SteelMechanicalParts = Recipe([Items.SteelBar], [Items.SteelMechanicalParts], "Steel Mechanical Parts") + Pipe = Recipe([Items.IronPlate, Items.IronMechanicalParts], [Items.Pipe], "Pipe") + SteamEngine = Recipe([Items.Pipe, Items.CopperMechanicalParts], [Items.SteamEngine], "Steam Engine") SteamAssembler = Recipe([ - Item.Pipe, - Item.CopperMechanicalParts, - Item.SteamEngine, - Item.CopperBeam - ], [Item.SteamAssembler], "Steam Assembler") + Items.Pipe, + Items.CopperMechanicalParts, + Items.SteamEngine, + Items.CopperBeam + ], [Items.SteamAssembler], "Steam Assembler") SteamCrusher = Recipe([ - Item.Pipe, - Item.IronMechanicalParts, - Item.SteamEngine, - Item.CopperBeam - ], [Item.SteamCrusher], "Steam Crusher") + Items.Pipe, + Items.IronMechanicalParts, + Items.SteamEngine, + Items.CopperBeam + ], [Items.SteamCrusher], "Steam Crusher") diff --git a/engine.py b/engine.py index 9edcbac..9c6eda1 100644 --- a/engine.py +++ b/engine.py @@ -1,7 +1,20 @@ from typing import List, Set, Tuple import matplotlib.pyplot as plt 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: def __init__(self, name: str) -> None: @@ -9,8 +22,8 @@ class Item: class Recipe: def __init__(self, inputs: List[Item], outputs: List[Item], name: str) -> None: - self.inputs = inputs - self.outputs = outputs + self.inputs = set(inputs) + self.outputs = set(outputs) self.name = name class World: @@ -38,20 +51,23 @@ def find_valid_recipes(output: Item, world: World) -> List[Recipe]: class Assembler: def __init__(self, recipe: Recipe, world: World) -> None: self.recipe = recipe - self.input_links = [] # List[Assembler] + self.input_links: Set[Assembler] = set() # List[Assembler] self.world = world def link_input(self, assembler: "Assembler") -> None: self.input_links.append(assembler) 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 [ item for item in self.recipe.inputs - if item not in [ - assembler.recipe.outputs - for assembler in self.input_links - ].flatten() + if item not in recv and item not in self.world.bus ] @@ -67,9 +83,40 @@ class Graph: ] 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": graph = Graph(world) - graph.grid[1][world.size // 2] = recipe + graph.grid[1][world.size // 2] = Assembler(recipe, world) + print(recipe) return graph 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]) 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: plt.figure() @@ -156,21 +218,45 @@ class Graph: plt.ylim(-1, 11) # Show the plot - plt.show() + plt.show(block=False) + plt.pause(5) + plt.close() - def get_solutions(self) -> List["Graph"]: - if self.is_solved(): - return [] - - graphs: List["Graph"] = [] +def solve(graph: Graph) -> Set["Graph"]: - for x in range(len(self.grid)): - for y in range(len(self.grid)): - assembler = self.grid[x][y] + if graph.is_solved(): + return set([graph]) + + graphs: Set["Graph"] = set([graph]) + + 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: continue - print(assembler) - # for item in assembler.get_unlinked_inputs(): - # for recipes in find_valid_recipes(item): + for item in assembler.get_unlinked_inputs(): + print(item) + # for recipes in find_valid_recipes(item): # graph = self.add_assembler(assembler, x, y) # graphs.append(graph) diff --git a/main.py b/main.py index 1ef5751..fd178d8 100644 --- a/main.py +++ b/main.py @@ -1,18 +1,22 @@ from typing import List -from data import Item, Recipes -from engine import Graph +from data import Items, Recipes +from engine import Graph, World, solve -bus_resources = [ - Item.IronBar, - Item.CopperBar, - Item.SteelBar, -] +world = World( + bus=[ + Items.IronBar, + Items.CopperBar, + Items.SteelBar, + ], + items=Items.as_set(), + recipes=Recipes.as_set(), + size=10 +) +solutions = solve(Graph.create_graph(world, Recipes.IronPlate)) - - - - -solutions = Graph.create_graph(10, Recipes.IronPlate).get_solutions() +if not solutions: + print("No solutions found") + exit(1) for solution in solutions: solution.draw()