plancraft 0.1.0__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.0.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 -315
- environments/env_symbolic.py +0 -215
- 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 -483
- models/oam.py +0 -284
- models/oracle.py +0 -268
- models/prompts.py +0 -158
- models/react.py +0 -98
- models/utils.py +0 -289
- plancraft-0.1.0.dist-info/METADATA +0 -53
- plancraft-0.1.0.dist-info/RECORD +0 -26
- plancraft-0.1.0.dist-info/top_level.txt +0 -3
- train/dataset.py +0 -187
- {plancraft-0.1.0.dist-info → plancraft-0.1.2.dist-info}/LICENSE +0 -0
environments/recipes.py
DELETED
@@ -1,542 +0,0 @@
|
|
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)
|