mall-solver/engine.py

177 lines
5.0 KiB
Python
Raw Normal View History

2023-11-23 01:11:19 -05:00
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)