plancraft 0.1.1__py3-none-any.whl → 0.1.2__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- plancraft-0.1.2.dist-info/METADATA +74 -0
- plancraft-0.1.2.dist-info/RECORD +5 -0
- {plancraft-0.1.1.dist-info → plancraft-0.1.2.dist-info}/WHEEL +1 -1
- plancraft-0.1.2.dist-info/top_level.txt +1 -0
- environments/__init__.py +0 -0
- environments/actions.py +0 -218
- environments/env_real.py +0 -316
- environments/env_symbolic.py +0 -212
- environments/items.py +0 -10
- environments/planner.py +0 -109
- environments/recipes.py +0 -542
- environments/sampler.py +0 -224
- models/__init__.py +0 -21
- models/act.py +0 -184
- models/base.py +0 -152
- models/bbox_model.py +0 -492
- models/dummy.py +0 -54
- models/few_shot_images/__init__.py +0 -16
- models/generators.py +0 -480
- models/oam.py +0 -283
- models/oracle.py +0 -265
- models/prompts.py +0 -158
- models/react.py +0 -93
- models/utils.py +0 -289
- plancraft-0.1.1.dist-info/METADATA +0 -74
- plancraft-0.1.1.dist-info/RECORD +0 -26
- plancraft-0.1.1.dist-info/top_level.txt +0 -3
- train/dataset.py +0 -187
- {plancraft-0.1.1.dist-info → plancraft-0.1.2.dist-info}/LICENSE +0 -0
environments/env_symbolic.py
DELETED
@@ -1,212 +0,0 @@
|
|
1
|
-
from typing import Optional
|
2
|
-
|
3
|
-
from plancraft.environments.actions import SymbolicAction
|
4
|
-
from plancraft.environments.recipes import (
|
5
|
-
RECIPES,
|
6
|
-
ShapedRecipe,
|
7
|
-
ShapelessRecipe,
|
8
|
-
SmeltingRecipe,
|
9
|
-
convert_ingredients_to_table,
|
10
|
-
)
|
11
|
-
from plancraft.environments.sampler import MAX_STACK_SIZE
|
12
|
-
|
13
|
-
|
14
|
-
class PseudoActionSpace:
|
15
|
-
def no_op(self):
|
16
|
-
return {
|
17
|
-
"inventory_command": (0, 0, 0),
|
18
|
-
}
|
19
|
-
|
20
|
-
|
21
|
-
class SymbolicPlancraft:
|
22
|
-
def __init__(self, inventory: list[dict] = [], recipes=RECIPES, **kwargs):
|
23
|
-
self.inventory = inventory
|
24
|
-
self.reset_state()
|
25
|
-
self.table_indexes = list(range(1, 10))
|
26
|
-
self.output_index = 0
|
27
|
-
|
28
|
-
self.action_space = PseudoActionSpace()
|
29
|
-
|
30
|
-
self.recipes = recipes
|
31
|
-
|
32
|
-
self.smelting_recipes = []
|
33
|
-
|
34
|
-
self.crafting_recipes = []
|
35
|
-
|
36
|
-
for recipe_list in recipes.values():
|
37
|
-
for recipe in recipe_list:
|
38
|
-
if isinstance(recipe, SmeltingRecipe):
|
39
|
-
self.smelting_recipes.append(recipe)
|
40
|
-
elif isinstance(recipe, (ShapelessRecipe, ShapedRecipe)):
|
41
|
-
self.crafting_recipes.append(recipe)
|
42
|
-
|
43
|
-
def reset_state(self):
|
44
|
-
self.state = {i: {"type": "air", "quantity": 0} for i in range(46)}
|
45
|
-
# initialise inventory
|
46
|
-
for item in self.inventory:
|
47
|
-
self.state[item["slot"]] = {
|
48
|
-
"type": item["type"],
|
49
|
-
"quantity": item["quantity"],
|
50
|
-
}
|
51
|
-
|
52
|
-
def step(self, action: Optional[SymbolicAction | dict]):
|
53
|
-
if action is None:
|
54
|
-
state_list = [
|
55
|
-
{"type": item["type"], "quantity": item["quantity"], "index": idx}
|
56
|
-
for idx, item in self.state.items()
|
57
|
-
]
|
58
|
-
return {"inventory": state_list}, 0, False, {}
|
59
|
-
|
60
|
-
# action_dict = action.to_action_dict()
|
61
|
-
if not isinstance(action, dict):
|
62
|
-
action = action.to_action_dict()
|
63
|
-
|
64
|
-
if "inventory_command" in action:
|
65
|
-
# do inventory command (move)
|
66
|
-
slot, slot_to, quantity = action["inventory_command"]
|
67
|
-
self.move_item(slot, slot_to, quantity)
|
68
|
-
elif "smelt" in action:
|
69
|
-
# do smelt
|
70
|
-
slot, slot_to, quantity = action["smelt"]
|
71
|
-
self.smelt_item(slot, slot_to, quantity)
|
72
|
-
else:
|
73
|
-
raise ValueError("Invalid action")
|
74
|
-
# logger.warn("Cannot parse action for Symbolic action")
|
75
|
-
|
76
|
-
self.clean_state()
|
77
|
-
|
78
|
-
# convert to list for same format as minerl
|
79
|
-
state_list = [
|
80
|
-
{"type": item["type"], "quantity": item["quantity"], "index": idx}
|
81
|
-
for idx, item in self.state.items()
|
82
|
-
]
|
83
|
-
|
84
|
-
return {"inventory": state_list}, 0, False, {}
|
85
|
-
|
86
|
-
def clean_state(self):
|
87
|
-
# reset slot type if quantity is 0
|
88
|
-
for i in self.state.keys():
|
89
|
-
if self.state[i]["quantity"] == 0:
|
90
|
-
self.state[i]["type"] = "air"
|
91
|
-
|
92
|
-
def move_item(self, slot_from: int, slot_to: int, quantity: int):
|
93
|
-
if slot_from == slot_to or quantity < 1 or slot_to == 0:
|
94
|
-
return
|
95
|
-
# slot outside of inventory
|
96
|
-
if slot_from not in self.state or slot_to not in self.state:
|
97
|
-
return
|
98
|
-
# not enough
|
99
|
-
if self.state[slot_from]["quantity"] < quantity:
|
100
|
-
return
|
101
|
-
|
102
|
-
item = self.state[slot_from]
|
103
|
-
|
104
|
-
# slot to is not empty or is the same type as item
|
105
|
-
if (self.state[slot_to]["type"] == "air") or (
|
106
|
-
self.state[slot_to]["quantity"] <= 0
|
107
|
-
):
|
108
|
-
self.state[slot_to] = {"type": item["type"], "quantity": quantity}
|
109
|
-
self.state[slot_from]["quantity"] -= quantity
|
110
|
-
elif self.state[slot_to]["type"] == item["type"] and (
|
111
|
-
MAX_STACK_SIZE[item["type"]] >= self.state[slot_to]["quantity"] + quantity
|
112
|
-
):
|
113
|
-
# check if the quantity exceeds the max stack size
|
114
|
-
self.state[slot_to]["quantity"] += quantity
|
115
|
-
self.state[slot_from]["quantity"] -= quantity
|
116
|
-
else:
|
117
|
-
return
|
118
|
-
|
119
|
-
# reset slot if quantity is 0
|
120
|
-
if self.state[slot_from]["quantity"] == 0:
|
121
|
-
self.state[slot_from] = {"type": "air", "quantity": 0}
|
122
|
-
|
123
|
-
# use up ingredients
|
124
|
-
if slot_from == 0:
|
125
|
-
self.use_ingredients()
|
126
|
-
|
127
|
-
# populate craft slot if ingredients in crafting table have changed
|
128
|
-
if slot_to < 10 or slot_from < 10:
|
129
|
-
self.populate_craft_slot_craft_item()
|
130
|
-
|
131
|
-
def smelt_item(self, slot_from: int, slot_to: int, quantity: int):
|
132
|
-
if quantity < 1 or slot_to == 0 or slot_from == slot_to or slot_from == 0:
|
133
|
-
return # skip if quantity is less than 1
|
134
|
-
|
135
|
-
if slot_from not in self.state or slot_to not in self.state:
|
136
|
-
return # handle slot out of bounds or invalid slot numbers
|
137
|
-
|
138
|
-
item = self.state[slot_from]
|
139
|
-
if item["quantity"] < quantity or item["type"] == "air":
|
140
|
-
return # skip if the slot from is empty or does not have enough items
|
141
|
-
|
142
|
-
for recipe in self.smelting_recipes:
|
143
|
-
if output := recipe.smelt(item["type"]):
|
144
|
-
output_type = output.item
|
145
|
-
# Check if the destination slot is empty or has the same type of item as the output
|
146
|
-
if self.state[slot_to]["type"] == "air":
|
147
|
-
self.state[slot_to] = {"type": output_type, "quantity": quantity}
|
148
|
-
self.state[slot_from]["quantity"] -= quantity
|
149
|
-
break
|
150
|
-
elif self.state[slot_to]["type"] == output_type and (
|
151
|
-
MAX_STACK_SIZE[output_type]
|
152
|
-
>= self.state[slot_to]["quantity"] + quantity
|
153
|
-
): # assuming max stack size is 64
|
154
|
-
self.state[slot_to]["quantity"] += quantity
|
155
|
-
self.state[slot_from]["quantity"] -= quantity
|
156
|
-
break
|
157
|
-
else:
|
158
|
-
return # No space or type mismatch in slot_to
|
159
|
-
|
160
|
-
# Clean up if the source slot is depleted
|
161
|
-
if self.state[slot_from] == 0:
|
162
|
-
self.state[slot_from] = {"type": "air", "quantity": 0}
|
163
|
-
|
164
|
-
if slot_to < 10 or slot_from < 10:
|
165
|
-
self.populate_craft_slot_craft_item()
|
166
|
-
|
167
|
-
def populate_craft_slot_craft_item(self):
|
168
|
-
# get ingredients from crafting table
|
169
|
-
ingredients = []
|
170
|
-
for i in self.table_indexes:
|
171
|
-
if self.state[i]["type"] != "air" and self.state[i]["quantity"] > 0:
|
172
|
-
ingredients.append(self.state[i]["type"])
|
173
|
-
else:
|
174
|
-
ingredients.append(None)
|
175
|
-
table = convert_ingredients_to_table(ingredients)
|
176
|
-
|
177
|
-
# check if any of the crafting recipes match the ingredients
|
178
|
-
for recipe in self.crafting_recipes:
|
179
|
-
if result := recipe.craft(table):
|
180
|
-
result, indexes = result
|
181
|
-
self.ingredients_idxs = indexes
|
182
|
-
self.state[self.output_index] = {
|
183
|
-
"type": result.item,
|
184
|
-
"quantity": result.count,
|
185
|
-
}
|
186
|
-
return
|
187
|
-
|
188
|
-
self.ingredients_idxs = []
|
189
|
-
self.state[self.output_index] = {"type": "air", "quantity": 0}
|
190
|
-
|
191
|
-
def use_ingredients(self):
|
192
|
-
# remove used ingredients from crafting table
|
193
|
-
for idx in self.ingredients_idxs:
|
194
|
-
self.state[idx + 1]["quantity"] -= 1
|
195
|
-
if self.state[idx + 1]["quantity"] <= 0:
|
196
|
-
self.state[idx + 1] = {"type": "air", "quantity": 0}
|
197
|
-
self.ingredients_idxs = []
|
198
|
-
|
199
|
-
def reset(self):
|
200
|
-
self.reset_state()
|
201
|
-
return self.state
|
202
|
-
|
203
|
-
def fast_reset(self, new_inventory: list[dict]):
|
204
|
-
self.inventory = new_inventory
|
205
|
-
self.reset_state()
|
206
|
-
return self.state
|
207
|
-
|
208
|
-
def render(self):
|
209
|
-
print(f"state: {self.state}")
|
210
|
-
|
211
|
-
def close(self):
|
212
|
-
self.reset_state()
|
environments/items.py
DELETED
environments/planner.py
DELETED
@@ -1,109 +0,0 @@
|
|
1
|
-
import time
|
2
|
-
|
3
|
-
import networkx as nx
|
4
|
-
|
5
|
-
from plancraft.environments.recipes import RECIPES, BaseRecipe
|
6
|
-
|
7
|
-
RECIPE_GRAPH = nx.DiGraph()
|
8
|
-
|
9
|
-
for item, recipes in RECIPES.items():
|
10
|
-
for recipe in recipes:
|
11
|
-
RECIPE_GRAPH.add_node(recipe.result.item)
|
12
|
-
for ingredient in recipe.inputs:
|
13
|
-
RECIPE_GRAPH.add_node(ingredient)
|
14
|
-
RECIPE_GRAPH.add_edge(ingredient, recipe.result.item)
|
15
|
-
|
16
|
-
|
17
|
-
def get_ancestors(target: str):
|
18
|
-
return list(nx.ancestors(RECIPE_GRAPH, source=target))
|
19
|
-
|
20
|
-
|
21
|
-
def optimal_planner(
|
22
|
-
target: str,
|
23
|
-
inventory: dict[str, int],
|
24
|
-
steps=[],
|
25
|
-
best_steps=None,
|
26
|
-
max_steps=40,
|
27
|
-
timeout=30,
|
28
|
-
) -> list[tuple[BaseRecipe, dict[str, int]]]:
|
29
|
-
"""
|
30
|
-
Optimal planner for crafting the target item from the given inventory.
|
31
|
-
|
32
|
-
Uses depth-first search with memoization to find the shortest path of crafting steps.
|
33
|
-
|
34
|
-
Args:
|
35
|
-
target: The target item to craft.
|
36
|
-
inventory: The current inventory.
|
37
|
-
steps: The current path of crafting steps.
|
38
|
-
best_steps: The best path of crafting steps found so far.
|
39
|
-
max_steps: The maximum number of steps to take.
|
40
|
-
timeout: The maximum time to spend searching for a solution.
|
41
|
-
|
42
|
-
Returns:
|
43
|
-
list of tuples of (recipe, inventory) for each step in the optimal path.
|
44
|
-
"""
|
45
|
-
|
46
|
-
memo = {}
|
47
|
-
# only look at recipes that are ancestors of the target
|
48
|
-
ancestors = get_ancestors(target)
|
49
|
-
# sort to put the closest ancestors first
|
50
|
-
ancestors = sorted(
|
51
|
-
ancestors,
|
52
|
-
key=lambda x: nx.shortest_path_length(RECIPE_GRAPH, source=x, target=target),
|
53
|
-
)
|
54
|
-
|
55
|
-
time_now = time.time()
|
56
|
-
|
57
|
-
def dfs(starting_inventory, steps, best_steps):
|
58
|
-
# If we have exceeded the timeout, return the best known path so far.
|
59
|
-
if time.time() - time_now > timeout:
|
60
|
-
raise TimeoutError("Timeout exceeded")
|
61
|
-
|
62
|
-
memo_key = (frozenset(starting_inventory.items()), len(steps))
|
63
|
-
if memo_key in memo:
|
64
|
-
return memo[memo_key]
|
65
|
-
|
66
|
-
if best_steps is not None and len(steps) >= len(best_steps):
|
67
|
-
# If we already have a shorter or equally short solution, do not proceed further.
|
68
|
-
return best_steps
|
69
|
-
|
70
|
-
if len(steps) > max_steps:
|
71
|
-
# If we have already exceeded the maximum number of steps, do not proceed further.
|
72
|
-
return best_steps
|
73
|
-
|
74
|
-
if target in starting_inventory and starting_inventory[target] > 0:
|
75
|
-
# If the target item is already in the inventory in the required amount, return the current path.
|
76
|
-
if best_steps is None or len(steps) < len(best_steps):
|
77
|
-
return steps
|
78
|
-
return best_steps
|
79
|
-
|
80
|
-
for recipe_name in [target] + ancestors:
|
81
|
-
# skip if already have 9 of the item
|
82
|
-
if starting_inventory.get(recipe_name, 0) >= 9:
|
83
|
-
continue
|
84
|
-
# TODO prevent looping between equivalent recipes (coal <-> coal_block)
|
85
|
-
for recipe in RECIPES[recipe_name]:
|
86
|
-
if recipe.can_craft_from_inventory(starting_inventory):
|
87
|
-
# Craft this item and update the inventory.
|
88
|
-
new_inventory = recipe.craft_from_inventory(starting_inventory)
|
89
|
-
# Add this step to the path.
|
90
|
-
new_steps = steps + [(recipe, new_inventory)]
|
91
|
-
|
92
|
-
# Recursively try to craft the target item with the updated inventory.
|
93
|
-
candidate_steps = dfs(new_inventory, new_steps, best_steps)
|
94
|
-
|
95
|
-
# Update the best known path if the candidate path is better.
|
96
|
-
if candidate_steps is not None and (
|
97
|
-
best_steps is None or len(candidate_steps) < len(best_steps)
|
98
|
-
):
|
99
|
-
best_steps = candidate_steps
|
100
|
-
|
101
|
-
memo[memo_key] = best_steps
|
102
|
-
return best_steps
|
103
|
-
|
104
|
-
try:
|
105
|
-
path = dfs(inventory, steps, best_steps)
|
106
|
-
return path
|
107
|
-
|
108
|
-
except TimeoutError:
|
109
|
-
return None
|