commit 8a93dc36f07695490ea66ad3fc8eeee86dd81988 Author: Valerie Date: Thu Nov 23 01:11:19 2023 -0500 she put her bidge to the bidge. diff --git a/data.py b/data.py new file mode 100644 index 0000000..dbaa03b --- /dev/null +++ b/data.py @@ -0,0 +1,50 @@ +from enum import Enum + +from engine import Recipe + +class Item(Enum): + IronBar = "Iron Bar" + CopperBar = "Copper Bar" + SteelBar = "Steel Bar" + IronPlate = "Iron Plate" + CopperPlate = "Copper Plate" + IronBeam = "Iron Beam" + CopperBeam = "Copper Beam" + SteelBeam = "Steel Beam" + IronMechanicalParts = "Iron Mechanical Parts" + CopperMechanicalParts = "Copper Mechanical Parts" + SteelMechanicalParts = "Steel Mechanical Parts" + CrushedIron = "Crushed Iron" + CrushedCoalCoke = "Crushed Coal Coke" + SteelBlend = "Steel Blend" + CoalCoke = "Coal Coke" + SteamEngine = "Steam Engine" + SteamAssembler = "Steam Assembler" + 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") + SteamAssembler = Recipe([ + Item.Pipe, + Item.CopperMechanicalParts, + Item.SteamEngine, + Item.CopperBeam + ], [Item.SteamAssembler], "Steam Assembler") + SteamCrusher = Recipe([ + Item.Pipe, + Item.IronMechanicalParts, + Item.SteamEngine, + Item.CopperBeam + ], [Item.SteamCrusher], "Steam Crusher") + + diff --git a/engine.py b/engine.py new file mode 100644 index 0000000..9edcbac --- /dev/null +++ b/engine.py @@ -0,0 +1,176 @@ +from typing import List, Set, Tuple +import matplotlib.pyplot as plt +from matplotlib.patches import Rectangle +from typing import List, Any + +class Item: + def __init__(self, name: str) -> None: + self.name = name + +class Recipe: + def __init__(self, inputs: List[Item], outputs: List[Item], name: str) -> None: + self.inputs = inputs + self.outputs = outputs + self.name = name + +class World: + def __init__( + self, + recipes: Set[Recipe], + items: Set[Item], + bus: Set[Item], + size: int + ) -> None: + self.recipes = recipes + self.items = items + self.bus = bus + self.size = size + + +def find_valid_recipes(output: Item, world: World) -> List[Recipe]: + valid_recipes = [] + for recipe in world.recipes: + if output in recipe.value.outputs: + valid_recipes.append(recipe.value) + return valid_recipes + + +class Assembler: + def __init__(self, recipe: Recipe, world: World) -> None: + self.recipe = recipe + self.input_links = [] # List[Assembler] + self.world = world + + def link_input(self, assembler: "Assembler") -> None: + self.input_links.append(assembler) + + def get_unlinked_inputs(self) -> List[Item]: + return [ + item + for item in self.recipe.inputs + if item not in [ + assembler.recipe.outputs + for assembler in self.input_links + ].flatten() + and item not in self.world.bus + ] + + def is_solved(self) -> bool: + return len(self.get_unlinked_inputs()) == 0 + +class Graph: + def __init__(self, world: World) -> None: + self.grid: List[List["Assembler"]] = [ + [ + None for _ in range(world.size) + ] for _ in range(world.size) + ] + self.world = world + + def create_graph(world: World, recipe: Recipe) -> "Graph": + graph = Graph(world) + graph.grid[1][world.size // 2] = recipe + return graph + + def get_coordinates_of_assembler(self, assembler: Assembler) -> (int, int): + for i in range(len(self.grid)): + for j in range(len(self.grid)): + if self.grid[i][j] == assembler: + return (i, j) + return None + + def clone(self) -> "Graph": + new_graph = Graph(len(self.grid)) + for i in range(len(self.grid)): + for j in range(len(self.grid)): + if self.grid[i][j] is not None: + assembler = self.grid[i][j] + new_assembler = Assembler(assembler.recipe) + + # re-link assemblers in cloned graph + for i in range(len(self.grid)): + for j in range(len(self.grid)): + if self.grid[i][j] is not None: + assembler = self.grid[i][j] + new_assembler = Assembler(assembler.recipe) + for link in assembler.input_links: + coords = self.get_coordinates_of_assembler(link) + new_assembler.link_input(new_graph.grid[coords[0]][coords[1]]) + + return new_graph + + def add_assembler(self, recipe: Assembler, x: int, y: int) -> "Graph": + graph = self.clone() + graph.grid[x][y] = recipe + return graph + + def is_solved_at(self, x: int, y: int) -> bool: + if self.grid[x][y] is None: + return False + return self.grid[x][y].is_solved() + + def is_solved(self) -> bool: + for i in range(len(self.grid)): + for j in range(len(self.grid)): + if not self.is_solved_at(i, j): + return False + return True + + def get_adjacent_coordinates(self, x: int, y: int) -> List[Tuple[int, int]]: + return [ + (x + 1, y) if x < len(self.grid) - 1 else None, + (x - 1, y) if x > 0 else None, + (x, y + 1) if y < len(self.grid) - 1 else None, + (x, y - 1) if y > 0 else None, + (x + 1, y + 1) if x < len(self.grid) - 1 and y < len(self.grid) - 1 and y % 2 == 0 else None, + (x + 1, y - 1) if x < len(self.grid) - 1 and y > 0 and y % 2 == 0 else None, + (x - 1, y + 1) if x > 0 and y < len(self.grid) - 1 and y % 2 == 1 else None, + (x - 1, y - 1) if x > 0 and y > 0 and y % 2 == 1 else None, + ].filter(lambda x: x is not None) + + def are_coordinates_adjacent(self, a: (int, int), b: (int, int)) -> bool: + adjacent_coordinates = self.get_adjacent_coordinates(a[0], a[1]) + return b in adjacent_coordinates + + def draw(self) -> None: + plt.figure() + + def convert_coords(p: (int, int)) -> (int, int): + (x, y) = p + return (x + 0.5 if y % 2 == 1 else x, y * 0.86602540378) + + + for row in self.grid: + for assembler in row: + if assembler is not None: + (x, y) = convert_coords(self.get_coordinates_of_assembler(assembler)) + plt.gca().add_patch(Rectangle((x - 0.5, y - 0.5), 1, 1, fill=True)) + plt.annotate("thing", (x, y), textcoords="offset points", xytext=(0,5), ha='center') + + # # Adding edges (arrows) between nodes + # plt.arrow(0, 0, 0.9, 0.9, head_width=0.05, head_length=0.1, fc='k', ec='k') + # plt.arrow(1, 1, 0.9, -0.9, head_width=0.05, head_length=0.1, fc='k', ec='k') + + # Setting the plot limits + plt.xlim(-1, 11) + plt.ylim(-1, 11) + + # Show the plot + plt.show() + + def get_solutions(self) -> List["Graph"]: + if self.is_solved(): + return [] + + graphs: List["Graph"] = [] + + for x in range(len(self.grid)): + for y in range(len(self.grid)): + assembler = self.grid[x][y] + if assembler == None: + continue + print(assembler) + # for item in assembler.get_unlinked_inputs(): + # 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 new file mode 100644 index 0000000..1ef5751 --- /dev/null +++ b/main.py @@ -0,0 +1,18 @@ +from typing import List +from data import Item, Recipes +from engine import Graph + +bus_resources = [ + Item.IronBar, + Item.CopperBar, + Item.SteelBar, +] + + + + + + +solutions = Graph.create_graph(10, Recipes.IronPlate).get_solutions() +for solution in solutions: + solution.draw()