plancraft 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- environments/__init__.py +0 -0
- environments/actions.py +218 -0
- environments/env_real.py +315 -0
- environments/env_symbolic.py +215 -0
- environments/items.py +10 -0
- environments/planner.py +109 -0
- environments/recipes.py +542 -0
- environments/sampler.py +224 -0
- models/__init__.py +21 -0
- models/act.py +184 -0
- models/base.py +152 -0
- models/bbox_model.py +492 -0
- models/dummy.py +54 -0
- models/few_shot_images/__init__.py +16 -0
- models/generators.py +483 -0
- models/oam.py +284 -0
- models/oracle.py +268 -0
- models/prompts.py +158 -0
- models/react.py +98 -0
- models/utils.py +289 -0
- plancraft-0.1.0.dist-info/LICENSE +21 -0
- plancraft-0.1.0.dist-info/METADATA +53 -0
- plancraft-0.1.0.dist-info/RECORD +26 -0
- plancraft-0.1.0.dist-info/WHEEL +5 -0
- plancraft-0.1.0.dist-info/top_level.txt +3 -0
- train/dataset.py +187 -0
environments/recipes.py
ADDED
@@ -0,0 +1,542 @@
|
|
1
|
+
import glob
|
2
|
+
import json
|
3
|
+
import os
|
4
|
+
import random
|
5
|
+
from abc import abstractmethod
|
6
|
+
from collections import defaultdict
|
7
|
+
from copy import deepcopy
|
8
|
+
from dataclasses import dataclass
|
9
|
+
|
10
|
+
import numpy as np
|
11
|
+
|
12
|
+
from plancraft.environments.items import ALL_ITEMS
|
13
|
+
|
14
|
+
|
15
|
+
def clean_item_name(item: str) -> str:
|
16
|
+
return item.replace("minecraft:", "")
|
17
|
+
|
18
|
+
|
19
|
+
# find directory of file
|
20
|
+
dir_path = os.path.dirname(os.path.realpath(__file__))
|
21
|
+
TAG_TO_ITEMS: dict[str, list[str]] = {}
|
22
|
+
for tag_file in glob.glob(f"{dir_path}/tags/*.json"):
|
23
|
+
with open(tag_file) as file:
|
24
|
+
tag = json.load(file)
|
25
|
+
tag_name = tag_file.split("/")[-1].split(".")[0]
|
26
|
+
TAG_TO_ITEMS[tag_name] = [clean_item_name(v) for v in tag["values"]]
|
27
|
+
|
28
|
+
|
29
|
+
def item_to_id(item: str) -> int:
|
30
|
+
if item is None:
|
31
|
+
return len(ALL_ITEMS)
|
32
|
+
return ALL_ITEMS.index(clean_item_name(item))
|
33
|
+
|
34
|
+
|
35
|
+
def id_to_item(item_id: int) -> str:
|
36
|
+
if item_id == len(ALL_ITEMS):
|
37
|
+
return None
|
38
|
+
return ALL_ITEMS[item_id]
|
39
|
+
|
40
|
+
|
41
|
+
def convert_ingredients_to_table(ingredients: list[str]) -> np.array:
|
42
|
+
assert len(ingredients) == 9, "Crafting table must have 9 slots"
|
43
|
+
table = np.zeros((3, 3), dtype=int)
|
44
|
+
for index, item in enumerate(ingredients):
|
45
|
+
x, y = divmod(index, 3)
|
46
|
+
table[x, y] = item_to_id(item)
|
47
|
+
return table
|
48
|
+
|
49
|
+
|
50
|
+
def get_item(item):
|
51
|
+
"""
|
52
|
+
Iterator over the possible items in a recipe object
|
53
|
+
"""
|
54
|
+
if isinstance(item, list):
|
55
|
+
for i in item:
|
56
|
+
yield from get_item(i)
|
57
|
+
if isinstance(item, str):
|
58
|
+
if item.startswith("#"):
|
59
|
+
tag_items = TAG_TO_ITEMS[clean_item_name(item.replace("#", ""))]
|
60
|
+
yield from get_item(tag_items)
|
61
|
+
else:
|
62
|
+
yield clean_item_name(item)
|
63
|
+
if isinstance(item, dict):
|
64
|
+
if "item" in item:
|
65
|
+
yield clean_item_name(item["item"])
|
66
|
+
else:
|
67
|
+
tag_items = TAG_TO_ITEMS[clean_item_name(item["tag"])]
|
68
|
+
yield from get_item(tag_items)
|
69
|
+
|
70
|
+
|
71
|
+
@dataclass
|
72
|
+
class RecipeResult:
|
73
|
+
item: str
|
74
|
+
count: int = 1
|
75
|
+
|
76
|
+
|
77
|
+
class BaseRecipe:
|
78
|
+
@abstractmethod
|
79
|
+
def craft(self, table: np.array) -> RecipeResult:
|
80
|
+
pass
|
81
|
+
|
82
|
+
@abstractmethod
|
83
|
+
def smelt(self, ingredient: str) -> RecipeResult:
|
84
|
+
pass
|
85
|
+
|
86
|
+
@abstractmethod
|
87
|
+
def sample_inputs(self) -> tuple[dict[str, int], set]:
|
88
|
+
pass
|
89
|
+
|
90
|
+
@abstractmethod
|
91
|
+
def can_craft_from_inventory(self, inventory: dict[str, int]) -> bool:
|
92
|
+
pass
|
93
|
+
|
94
|
+
@abstractmethod
|
95
|
+
def craft_from_inventory(self, inventory: dict[str, int]) -> dict[str, int]:
|
96
|
+
pass
|
97
|
+
|
98
|
+
@property
|
99
|
+
def inputs(self) -> set:
|
100
|
+
raise NotImplementedError()
|
101
|
+
|
102
|
+
@property
|
103
|
+
def recipe_type(self) -> str:
|
104
|
+
raise NotImplementedError()
|
105
|
+
|
106
|
+
@property
|
107
|
+
def num_slots(self) -> int:
|
108
|
+
return NotImplementedError()
|
109
|
+
|
110
|
+
def __repr__(self) -> str:
|
111
|
+
pass
|
112
|
+
|
113
|
+
|
114
|
+
class ShapelessRecipe(BaseRecipe):
|
115
|
+
def __init__(self, recipe):
|
116
|
+
# list of counters that represent the different valid inputs
|
117
|
+
self.ingredients: list[dict[str, int]] = []
|
118
|
+
self.add_ingredient(recipe["ingredients"], 0, {})
|
119
|
+
|
120
|
+
self.ingredients_arr = np.stack(
|
121
|
+
[self.convert_ingredients_counter_to_arr(ing) for ing in self.ingredients],
|
122
|
+
axis=0,
|
123
|
+
)
|
124
|
+
|
125
|
+
result_item_name = clean_item_name(recipe["result"]["item"])
|
126
|
+
self.result = RecipeResult(result_item_name, recipe["result"].get("count", 1))
|
127
|
+
|
128
|
+
def add_ingredient(
|
129
|
+
self, ingredients_list: list[dict], index: int, current_counter: dict[str, int]
|
130
|
+
):
|
131
|
+
# Base case: all ingredients are processed, add current variant to the list
|
132
|
+
if index == len(ingredients_list):
|
133
|
+
self.ingredients.append(current_counter)
|
134
|
+
return
|
135
|
+
|
136
|
+
ingredient_names = list(get_item(ingredients_list[index]))
|
137
|
+
if len(ingredient_names) > 1:
|
138
|
+
# If the ingredient has alternatives, recurse for each one
|
139
|
+
# This is the case for fire_charge with coal/charcoal
|
140
|
+
for item_name in ingredient_names:
|
141
|
+
new_counter = deepcopy(current_counter)
|
142
|
+
new_counter[item_name] = new_counter.get(item_name, 0) + 1
|
143
|
+
self.add_ingredient(ingredients_list, index + 1, new_counter)
|
144
|
+
# single acceptable ingredient
|
145
|
+
elif len(ingredient_names) == 1:
|
146
|
+
item_name = ingredient_names[0]
|
147
|
+
current_counter[item_name] = current_counter.get(item_name, 0) + 1
|
148
|
+
self.add_ingredient(ingredients_list, index + 1, current_counter)
|
149
|
+
else:
|
150
|
+
raise ValueError("No item found in ingredient")
|
151
|
+
|
152
|
+
@staticmethod
|
153
|
+
def convert_ingredients_counter_to_arr(
|
154
|
+
ingredients_counter: dict[str, int],
|
155
|
+
) -> np.array:
|
156
|
+
arr = np.zeros(len(ALL_ITEMS) + 1)
|
157
|
+
total_slots = 0
|
158
|
+
for item, count in ingredients_counter.items():
|
159
|
+
arr[item_to_id(item)] = count
|
160
|
+
total_slots += count
|
161
|
+
# account for empty slots
|
162
|
+
arr[len(ALL_ITEMS)] = 9 - total_slots
|
163
|
+
return arr
|
164
|
+
|
165
|
+
def craft(self, table: np.array) -> tuple[RecipeResult, list[int]]:
|
166
|
+
assert table.shape == (3, 3), "Crafting table must have 3x3 shape"
|
167
|
+
table_arr = np.bincount(table.flatten(), minlength=len(ALL_ITEMS) + 1)
|
168
|
+
if (table_arr == self.ingredients_arr).all(axis=1).any():
|
169
|
+
indexes_to_decrement = []
|
170
|
+
for idx, item_id in enumerate(table.flatten()):
|
171
|
+
if item_id != len(ALL_ITEMS):
|
172
|
+
indexes_to_decrement.append(idx)
|
173
|
+
return self.result, indexes_to_decrement
|
174
|
+
|
175
|
+
return None
|
176
|
+
|
177
|
+
def smelt(self, ingredient: str):
|
178
|
+
return None
|
179
|
+
|
180
|
+
def sample_inputs(self) -> tuple[dict[str, int], set]:
|
181
|
+
exclude_set = set()
|
182
|
+
for ingredient_counter in self.ingredients:
|
183
|
+
for ingredient in ingredient_counter.keys():
|
184
|
+
if ingredient is not None:
|
185
|
+
exclude_set.add(ingredient)
|
186
|
+
|
187
|
+
# sample a random ingredients set from the list of possible
|
188
|
+
return deepcopy(random.choice(self.ingredients)), deepcopy(exclude_set)
|
189
|
+
|
190
|
+
@property
|
191
|
+
def inputs(self) -> set:
|
192
|
+
all_inputs = set()
|
193
|
+
for ingredient_counter in self.ingredients:
|
194
|
+
for ingredient in ingredient_counter.keys():
|
195
|
+
if ingredient is not None:
|
196
|
+
all_inputs.add(ingredient)
|
197
|
+
return all_inputs
|
198
|
+
|
199
|
+
@property
|
200
|
+
def recipe_type(self) -> str:
|
201
|
+
return "shapeless"
|
202
|
+
|
203
|
+
def can_craft_from_inventory(self, inventory: dict[str, int]) -> bool:
|
204
|
+
for ingredient_counter in self.ingredients:
|
205
|
+
temp_inventory = deepcopy(inventory)
|
206
|
+
for ingredient, count in ingredient_counter.items():
|
207
|
+
if (
|
208
|
+
ingredient not in temp_inventory
|
209
|
+
or temp_inventory[ingredient] < count
|
210
|
+
):
|
211
|
+
break
|
212
|
+
temp_inventory[ingredient] -= count
|
213
|
+
else:
|
214
|
+
return True
|
215
|
+
return False
|
216
|
+
|
217
|
+
def craft_from_inventory(self, inventory: dict[str, int]) -> dict[str, int]:
|
218
|
+
assert self.can_craft_from_inventory(inventory), "Cannot craft from inventory"
|
219
|
+
for ingredient_counter in self.ingredients:
|
220
|
+
temp_inventory = deepcopy(inventory)
|
221
|
+
for ingredient, count in ingredient_counter.items():
|
222
|
+
if temp_inventory.get(ingredient, 0) < count:
|
223
|
+
break
|
224
|
+
temp_inventory[ingredient] -= count
|
225
|
+
else:
|
226
|
+
new_inventory = deepcopy(inventory)
|
227
|
+
for ingredient, count in ingredient_counter.items():
|
228
|
+
new_inventory[ingredient] -= count
|
229
|
+
if new_inventory[ingredient] == 0:
|
230
|
+
del new_inventory[ingredient]
|
231
|
+
new_inventory[self.result.item] = (
|
232
|
+
new_inventory.get(self.result.item, 0) + self.result.count
|
233
|
+
)
|
234
|
+
return new_inventory
|
235
|
+
|
236
|
+
def __repr__(self):
|
237
|
+
return f"ShapelessRecipe({self.ingredients}, {self.result})"
|
238
|
+
|
239
|
+
def __prompt_repr__(self) -> str:
|
240
|
+
# use to get a simple representation of the recipe for prompting
|
241
|
+
out = []
|
242
|
+
for ingredients in self.ingredients:
|
243
|
+
ingredients_string = ", ".join(
|
244
|
+
[f"{count} {i}" for i, count in ingredients.items()]
|
245
|
+
)
|
246
|
+
out.append(
|
247
|
+
f"{ingredients_string} -> {self.result.count} {self.result.item}"
|
248
|
+
)
|
249
|
+
return "\n".join(out)
|
250
|
+
|
251
|
+
def sample_input_crafting_grid(self) -> list[dict[str, int]]:
|
252
|
+
# sample a random ingredient crafting arrangement to craft item
|
253
|
+
ingredients = deepcopy(random.choice(self.ingredients))
|
254
|
+
|
255
|
+
num_inputs = sum(ingredients.values())
|
256
|
+
crafting_table_slots = random.sample(range(1, 10), num_inputs)
|
257
|
+
|
258
|
+
ingredients_list = []
|
259
|
+
for i, count in ingredients.items():
|
260
|
+
ingredients_list += [i] * count
|
261
|
+
|
262
|
+
crafting_table = []
|
263
|
+
for ingredient, slot in zip(ingredients_list, crafting_table_slots):
|
264
|
+
if ingredient is not None:
|
265
|
+
crafting_table.append({"type": ingredient, "slot": slot, "quantity": 1})
|
266
|
+
|
267
|
+
return crafting_table
|
268
|
+
|
269
|
+
|
270
|
+
class ShapedRecipe(BaseRecipe):
|
271
|
+
def __init__(self, recipe):
|
272
|
+
self.kernel = self.extract_kernel(recipe)
|
273
|
+
|
274
|
+
self.kernel_height = len(self.kernel)
|
275
|
+
self.kernel_width = len(self.kernel[0])
|
276
|
+
self.kernel_size = self.kernel_height * self.kernel_width
|
277
|
+
|
278
|
+
result_item_name = clean_item_name(recipe["result"]["item"])
|
279
|
+
self.result = RecipeResult(result_item_name, recipe["result"].get("count", 1))
|
280
|
+
|
281
|
+
def possible_kernel_positions(self):
|
282
|
+
return [
|
283
|
+
(row, col)
|
284
|
+
for row in range(3 - self.kernel_height + 1)
|
285
|
+
for col in range(3 - self.kernel_width + 1)
|
286
|
+
]
|
287
|
+
|
288
|
+
def extract_kernel(self, recipe) -> np.array:
|
289
|
+
patterns = recipe["pattern"]
|
290
|
+
keys = recipe["key"]
|
291
|
+
|
292
|
+
# Convert pattern symbols to corresponding items ids
|
293
|
+
def symbol_to_items(symbol):
|
294
|
+
if symbol == " ":
|
295
|
+
return set([item_to_id(None)])
|
296
|
+
return set([item_to_id(item) for item in get_item(keys[symbol])])
|
297
|
+
|
298
|
+
# Convert each row of the pattern to a list of possible item lists
|
299
|
+
kernel = [[symbol_to_items(symbol) for symbol in row] for row in patterns]
|
300
|
+
return kernel
|
301
|
+
|
302
|
+
def craft(self, table: np.array) -> tuple[RecipeResult, list[int]]:
|
303
|
+
assert table.shape == (3, 3), "Crafting table must have 3x3 shape"
|
304
|
+
|
305
|
+
count_empty = (table == len(ALL_ITEMS)).sum()
|
306
|
+
should_be_empty_count = 9 - self.kernel_size
|
307
|
+
if count_empty < should_be_empty_count:
|
308
|
+
return None
|
309
|
+
|
310
|
+
for start_row, start_col in self.possible_kernel_positions():
|
311
|
+
# count number of empty slots in kernel
|
312
|
+
count_empty_in_kernel = (
|
313
|
+
table[
|
314
|
+
start_row : start_row + self.kernel_height,
|
315
|
+
start_col : start_col + self.kernel_width,
|
316
|
+
]
|
317
|
+
== len(ALL_ITEMS)
|
318
|
+
).sum()
|
319
|
+
# count number of empty slots outside
|
320
|
+
count_empty_outside_kernel = count_empty - count_empty_in_kernel
|
321
|
+
# check if the number of empty slots outside is correct
|
322
|
+
if count_empty_outside_kernel != should_be_empty_count:
|
323
|
+
continue
|
324
|
+
|
325
|
+
# check that all items in kernel match the table
|
326
|
+
indexes_to_decrement = []
|
327
|
+
for row in range(self.kernel_height):
|
328
|
+
for col in range(self.kernel_width):
|
329
|
+
if (
|
330
|
+
table[start_row + row, start_col + col]
|
331
|
+
not in self.kernel[row][col]
|
332
|
+
):
|
333
|
+
break
|
334
|
+
# add to indexes to decrement if not empty slot
|
335
|
+
if table[start_row + row, start_col + col] != len(ALL_ITEMS):
|
336
|
+
idx = (start_row + row) * 3 + start_col + col
|
337
|
+
indexes_to_decrement.append(idx)
|
338
|
+
else:
|
339
|
+
continue
|
340
|
+
break
|
341
|
+
else:
|
342
|
+
return self.result, indexes_to_decrement
|
343
|
+
return None
|
344
|
+
|
345
|
+
def smelt(self, ingredient: str):
|
346
|
+
return None
|
347
|
+
|
348
|
+
def sample_inputs(self) -> tuple[dict[str, int], set]:
|
349
|
+
input_counter = defaultdict(int)
|
350
|
+
exclude_set = set()
|
351
|
+
for row in self.kernel:
|
352
|
+
for item_set in row:
|
353
|
+
# sample a random item from the set
|
354
|
+
if len(item_set) > 1:
|
355
|
+
item_id = random.choice(list(item_set))
|
356
|
+
# add all items to exclude set
|
357
|
+
exclude_set.update(item_set)
|
358
|
+
# single acceptable ingredient
|
359
|
+
else:
|
360
|
+
item_id = list(item_set)[0]
|
361
|
+
exclude_set.add(item_id)
|
362
|
+
|
363
|
+
# exclude empty slot
|
364
|
+
if id_to_item(item_id) is not None:
|
365
|
+
input_counter[id_to_item(item_id)] += 1
|
366
|
+
|
367
|
+
# convert exclude set to item names
|
368
|
+
exclude_set = {
|
369
|
+
id_to_item(item_id) for item_id in exclude_set if id_to_item(item_id)
|
370
|
+
}
|
371
|
+
return dict(input_counter), exclude_set
|
372
|
+
|
373
|
+
@property
|
374
|
+
def inputs(self) -> set:
|
375
|
+
all_inputs = set()
|
376
|
+
for row in self.kernel:
|
377
|
+
for item_set in row:
|
378
|
+
all_inputs.update(item_set)
|
379
|
+
all_inputs = {
|
380
|
+
id_to_item(item_id) for item_id in all_inputs if id_to_item(item_id)
|
381
|
+
}
|
382
|
+
return all_inputs
|
383
|
+
|
384
|
+
@property
|
385
|
+
def recipe_type(self) -> str:
|
386
|
+
return "shaped"
|
387
|
+
|
388
|
+
def can_craft_from_inventory(self, inventory: dict[str, int]) -> bool:
|
389
|
+
temp_inventory = deepcopy(inventory)
|
390
|
+
for row in self.kernel:
|
391
|
+
for item_set in row:
|
392
|
+
for inventory_item in item_set:
|
393
|
+
item_name = id_to_item(inventory_item)
|
394
|
+
if item_name is None:
|
395
|
+
break
|
396
|
+
elif item_name in temp_inventory and temp_inventory[item_name] > 0:
|
397
|
+
temp_inventory[item_name] -= 1
|
398
|
+
break
|
399
|
+
else:
|
400
|
+
return False
|
401
|
+
return True
|
402
|
+
|
403
|
+
def craft_from_inventory(self, inventory: dict[str, int]) -> dict[str, int]:
|
404
|
+
assert self.can_craft_from_inventory(inventory), "Cannot craft from inventory"
|
405
|
+
new_inventory = deepcopy(inventory)
|
406
|
+
for row in self.kernel:
|
407
|
+
for item_set in row:
|
408
|
+
for inventory_item in item_set:
|
409
|
+
item_name = id_to_item(inventory_item)
|
410
|
+
if (
|
411
|
+
item_name
|
412
|
+
and item_name in new_inventory
|
413
|
+
and new_inventory[item_name] > 0
|
414
|
+
):
|
415
|
+
new_inventory[item_name] -= 1
|
416
|
+
if new_inventory[item_name] == 0:
|
417
|
+
del new_inventory[item_name]
|
418
|
+
break
|
419
|
+
# add result to inventory
|
420
|
+
new_inventory[self.result.item] = (
|
421
|
+
new_inventory.get(self.result.item, 0) + self.result.count
|
422
|
+
)
|
423
|
+
return new_inventory
|
424
|
+
|
425
|
+
def __repr__(self) -> str:
|
426
|
+
return f"ShapedRecipe({self.kernel}, {self.result})"
|
427
|
+
|
428
|
+
def __prompt_repr__(self) -> str:
|
429
|
+
string_kernel = []
|
430
|
+
for row in self.kernel:
|
431
|
+
row_col = []
|
432
|
+
for col in row:
|
433
|
+
valid_items = [str(id_to_item(i)) for i in col]
|
434
|
+
if valid_items[0] == "None":
|
435
|
+
valid_items[0] = "empty"
|
436
|
+
row_col.append("|".join(valid_items))
|
437
|
+
string_kernel.append("\t".join(row_col))
|
438
|
+
result_row = len(self.kernel) // 2
|
439
|
+
string_kernel[result_row] = (
|
440
|
+
string_kernel[result_row] + f" -> {self.result.count} {self.result.item}"
|
441
|
+
)
|
442
|
+
return "\n".join(string_kernel)
|
443
|
+
|
444
|
+
def sample_input_crafting_grid(self) -> list[dict[str, int]]:
|
445
|
+
# sample a random ingredient crafting arrangement to craft item
|
446
|
+
crafting_table = []
|
447
|
+
|
448
|
+
start_row, start_col = random.choice(self.possible_kernel_positions())
|
449
|
+
for x in range(0, len(self.kernel)):
|
450
|
+
for y in range(0, len(self.kernel[0])):
|
451
|
+
i = random.choice(list(self.kernel[x][y]))
|
452
|
+
object_type = id_to_item(i)
|
453
|
+
if object_type is None:
|
454
|
+
continue
|
455
|
+
location_x = start_row + x
|
456
|
+
location_y = start_col + y
|
457
|
+
crafting_table += [
|
458
|
+
{
|
459
|
+
"type": object_type,
|
460
|
+
"slot": location_x * 3 + location_y + 1,
|
461
|
+
"quantity": 1,
|
462
|
+
}
|
463
|
+
]
|
464
|
+
return crafting_table
|
465
|
+
|
466
|
+
|
467
|
+
class SmeltingRecipe(BaseRecipe):
|
468
|
+
def __init__(self, recipe):
|
469
|
+
self.ingredient = set(get_item(recipe["ingredient"]))
|
470
|
+
result_item_name = clean_item_name(recipe["result"])
|
471
|
+
self.result = RecipeResult(result_item_name, 1)
|
472
|
+
|
473
|
+
def smelt(self, ingredient: str) -> RecipeResult:
|
474
|
+
if ingredient in self.ingredient:
|
475
|
+
return self.result
|
476
|
+
return None
|
477
|
+
|
478
|
+
def craft(self, table: np.array):
|
479
|
+
return None
|
480
|
+
|
481
|
+
def can_craft_from_inventory(self, inventory: dict[str, int]) -> bool:
|
482
|
+
return len(set(inventory.keys()).intersection(self.ingredient)) > 0
|
483
|
+
|
484
|
+
def craft_from_inventory(
|
485
|
+
self, inventory: dict[str, int], quantity=1
|
486
|
+
) -> dict[str, int]:
|
487
|
+
assert self.can_craft_from_inventory(inventory), "Cannot craft from inventory"
|
488
|
+
|
489
|
+
new_inventory = deepcopy(inventory)
|
490
|
+
for ingredient in self.ingredient:
|
491
|
+
if ingredient in new_inventory and new_inventory[ingredient] >= quantity:
|
492
|
+
new_inventory[ingredient] -= quantity
|
493
|
+
if new_inventory[ingredient] == 0:
|
494
|
+
del new_inventory[ingredient]
|
495
|
+
new_inventory[self.result.item] = (
|
496
|
+
new_inventory.get(self.result.item, 0) + quantity # count
|
497
|
+
)
|
498
|
+
return new_inventory
|
499
|
+
return None
|
500
|
+
|
501
|
+
def sample_inputs(self) -> tuple[dict[str, int], set]:
|
502
|
+
# sample a random ingredient from the list of possible
|
503
|
+
# return a dict with the ingredient and its count
|
504
|
+
# also return the set of all possible ingredients
|
505
|
+
return {random.choice(deepcopy(list(self.ingredient))): 1}, deepcopy(
|
506
|
+
self.ingredient
|
507
|
+
)
|
508
|
+
|
509
|
+
@property
|
510
|
+
def inputs(self) -> set:
|
511
|
+
return deepcopy(self.ingredient)
|
512
|
+
|
513
|
+
@property
|
514
|
+
def recipe_type(self) -> str:
|
515
|
+
return "smelting"
|
516
|
+
|
517
|
+
def __repr__(self) -> str:
|
518
|
+
return f"SmeltingRecipe({self.ingredient}, {self.result})"
|
519
|
+
|
520
|
+
def __prompt_repr__(self) -> str:
|
521
|
+
# use to get a simple representation of the recipe for prompting
|
522
|
+
out = []
|
523
|
+
for i in self.ingredient:
|
524
|
+
out.append(f"1 {i} -> {self.result.count} {self.result.item}")
|
525
|
+
return "\n".join(out)
|
526
|
+
|
527
|
+
|
528
|
+
def recipe_factory(recipe: dict) -> BaseRecipe:
|
529
|
+
if recipe["type"] == "minecraft:crafting_shapeless":
|
530
|
+
return ShapelessRecipe(recipe)
|
531
|
+
if recipe["type"] == "minecraft:crafting_shaped":
|
532
|
+
return ShapedRecipe(recipe)
|
533
|
+
if recipe["type"] == "minecraft:smelting":
|
534
|
+
return SmeltingRecipe(recipe)
|
535
|
+
|
536
|
+
|
537
|
+
RECIPES: dict[str, list[BaseRecipe]] = defaultdict(list)
|
538
|
+
for f in glob.glob(f"{dir_path}/recipes/*.json"):
|
539
|
+
with open(f) as file:
|
540
|
+
recipe = json.load(file)
|
541
|
+
if r := recipe_factory(recipe):
|
542
|
+
RECIPES[r.result.item].append(r)
|