-
Notifications
You must be signed in to change notification settings - Fork 40
MM2 tutorial: Reachability P2
By Anneline Daggelinckx
Reachability is the task of determining which elements can be reached from an initial set by repeatedly applying a given relation. In this tutorial, we will explore various types of reachability tasks and demonstrate how to encode them in MM2.
Each example covers the goal and desired result, introduces the data, builds code stepwise, and walks through an execution trace showing how each statement affects the space.
At the end of each part, you’ll also find a few exercises to try on your own.
If any of the code is unclear, it can be helpful to start from the execution trace: seeing how the space evolves often makes the intention of the rules much easier to understand.
In the first part of this tutorial, we explored reachability through fixed relationships.
For example, when finding the ancestors of a group of people, we repeatedly used the parent relation to reach parents, grandparents, great-grandparents, and so on.
In this second part, we shift to finding a reachable set through patterns rather than fixed binary relations.
To illustrate this, we will use Minecraft crafting recipes as an example.
In Minecraft, many items can be crafted from other items.
A recipe specifies which ingredients are required to produce a new item.
For example:
- A log can be turned into four planks,
- Two planks can be turned into four sticks,
- A stick and charcoal can be combined to make a torch.
Given an initial inventory, we can therefore ask: Which items are reachable through crafting?
In this part of the tutorial, we will encode exactly that process.
Note: For simplicity, we will ignore quantities and treat each item in our inventory as if it were available in unlimited supply.
Handling resource consumption and costs will be covered in Part 3.
To keep the example easy to follow, we will work with a small set of recipes: planks, sticks, and torches.
They depend on each other in the following way:
log -> planks -> stick -> torch
^
|
charcoal
All Minecraft recipes are accessible as JSON files, either inside the game directory data/minecraft/recipe or in various
GitHub repositories.
We can convert these JSON files into MM2 statements using a
JSON → MeTTa converter.
Note: depending on the use case, the converter may need small adjustments. For this tutorial, we added the recipe name explicitly to each recipe, which required only a minor tweak to the Python code.
After conversion, the recipes for planks, sticks, and torches look like this:
(recipe oak_planks (type "minecraft:crafting_shapeless"))
(recipe oak_planks (category "building"))
(recipe oak_planks (group "planks"))
(recipe oak_planks (ingredients 0 "#minecraft:oak_logs"))
(recipe oak_planks (result (count 4)))
(recipe oak_planks (result (id "minecraft:oak_planks")))
(recipe torch (type "minecraft:crafting_shaped"))
(recipe torch (category "misc"))
(recipe torch (key ("#" "minecraft:stick")))
(recipe torch (key ("X" 0 "minecraft:coal")))
(recipe torch (key ("X" 1 "minecraft:charcoal")))
(recipe torch (pattern 0 "X"))
(recipe torch (pattern 1 "#"))
(recipe torch (result (count 4)))
(recipe torch (result (id "minecraft:torch")))
(recipe stick (type "minecraft:crafting_shaped"))
(recipe stick (category "misc"))
(recipe stick (group "sticks"))
(recipe stick (key ("#" "#minecraft:planks")))
(recipe stick (pattern 0 "#"))
(recipe stick (pattern 1 "#"))
(recipe stick (result (count 4)))
(recipe stick (result (id "minecraft:stick")))
Let’s look more closely at the structure of this data and what we actually need.
Each recipe specifies:
- the resulting item, and
- how many items are produced (which we will ignore in this part).
The oak planks recipe lists its ingredients directly in statements labeled by ingredients.
The torch and stick recipes use patterns combined with keys.
In Minecraft, to use some recipes, items must be placed in a specific shape on a crafting grid.
The pattern describes this shape using symbolic tokens (like "X" or "#"), and the key tells us which items those tokens refer to.
In this tutorial, we use only the keys to identify possible ingredients, not the patterns themselves.
For example:
- In the torch recipe,
"X"may match either"minecraft:coal"or"minecraft:charcoal". - In the stick recipe,
"#"matches the tag"#minecraft:planks". In this last example, the#prefix in"#minecraft:planks"marks a group, meaning any type of plank can be used.
To handle these groups, we also load the relevant tag data. The tags are also stored in JSON and can be converted to MM2, similar to the recipes.
(tags "#minecraft:planks" "minecraft:oak_planks")
(tags "#minecraft:planks" "minecraft:spruce_planks")
(tags "#minecraft:planks" "minecraft:birch_planks")
(tags "#minecraft:planks" "minecraft:jungle_planks")
(tags "#minecraft:oak_logs" "minecraft:oak_log")
(tags "#minecraft:oak_logs" "minecraft:oak_wood")
(tags "#minecraft:oak_logs" "minecraft:stripped_oak_log")
(tags "#minecraft:oak_logs" "minecraft:stripped_oak_wood")
Note: Throughout the execution trace later in the tutorial, we will omit statements that are not relevant for understanding the reachability process, to keep the output readable.
We now ask: from the items currently in our inventory, which new items can we create using Minecraft’s crafting recipes? In the next section, we will construct the MM2 code for this step-by-step.
We start by specifying which items are in our initial inventory:
(inventory "minecraft:oak_log")
(inventory "minecraft:charcoal")
In the recipe data, some ingredients are encoded inconsistently.
For example, the torch recipe allows multiple alternatives for the token "X" (coal or charcoal).
The JSON → MM2 converter encodes these as:
(recipe torch (key ("X" 0 "minecraft:coal")))(recipe torch (key ("X" 1 "minecraft:charcoal")))
where the extra index (0, 1) reflects the original array positions from the JSON file.
Other keys, such as (recipe torch (key ("#" "minecraft:stick"))), do not include such indices.
Since these indices are irrelevant for our purposes and only introduce unnecessary variation, we remove them.
The following execution rule normalizes the data by stripping away the index:
(exec (0 0) (, (recipe $r (pattern $i $p))
(recipe $r (key ($p $_ $ingr))))
(O (+ (recipe $r (key ($p $ingr))))
(- (recipe $r (key ($p $_ $ingr))))))
Note: Removing the original statement is optional; the algorithm would still work if we simply added the cleaned version.
Here, we delete the original indexed key for clarity in the walkthrough.
To determine whether a recipe can be crafted, we need to check whether all of its required ingredients are present in our inventory.
A convenient first step is therefore to count the number of ingredients each recipe requires.
Because the data represents ingredients in two different formats, direct ingredient lists (as in the oak_planks recipe)
and pattern-based ingredients (as in the torch and stick recipes), we use two execution statements, one for each format.
Recall the built-in
countoperator available in MM2. It is structured(count O K X)withOthe statements to output,Kthe variable in which the count is stored (that can be used inO), andXthe variable one wants to count.
(exec (0 1) (, (recipe $product (ingredients $i $x)))
(O (count (recipe $product (numIngredients $k)) $k $i)))
(exec (0 1) (, (recipe $product (pattern $i $p)))
(O (count (recipe $product (numIngredients $k)) $k $i)))
We saw in the recipes of oak_planks and stick, that sometimes an ingredient is actually a group of ingredients, where any item from this group can be used.
To work uniformly with such recipes, we expand each grouped ingredient into all of its valid concrete instances, using the available tag data.
This ensures that later matching steps can treat all ingredients as explicit items.
(exec (0 3) (, (recipe $product (key ($p $group))) (tags $group $ingr))
(, (recipe $product (key ($p $ingr)))))
(exec (0 3) (, (recipe $p (ingredients $i $group)) (tags $group $ingr))
(, (recipe $p (ingredients $i $ingr))))
For each recipe, we want to add its resulting product to the inventory whenever all of its ingredients are available.
We will build up the code for this in three stages:
- First, we write the execution statements for a single iteration: given the current inventory, add any product whose ingredients are all in the inventory.
- Then, we make this process iterative, so newly crafted items can themselves be used as ingredients.
- Finally, we add a stop criterion, such that the process always terminates and cannot run forever.
Note: As mentioned before, we ignore quantities: if an element is in the inventory, we treat it as if it were available in unlimited amounts.
First, we will write execution statements to add a product to the inventory if all its ingredients are present.
First, consider recipes like oak_planks, where ingredients are listed directly (i.e., not through a pattern).
If all the required ingredients appear in the inventory, we can add the resulting item.
In MM2, we express this by writing one execution statement per possible number of ingredients.
Below, we show the rules for recipes that require either one or two ingredients:
(exec (1 0)
(, (recipe $product (numIngredients 1))
(recipe $product (ingredients 0 $xitem))
(inventory $xitem)
(recipe $product (result (id $productname))))
(, (inventory $productname))
)
(exec (1 1)
(, (recipe $product (numIngredients 2))
(recipe $product (ingredients 0 $xitem))
(recipe $product (ingredients 1 $yitem))
(inventory $xitem)
(inventory $yitem)
(recipe $product (result (id $productname))))
(, (inventory $productname))
)
Next, we define the execution statements for recipes that use patterns instead of listing ingredients directly.
For these recipes, we first extract all pattern tokens (e.g., "X", "#"), and then check whether for each token at least one ingredient associated with it appears in the inventory.
As before, we must write out one rule per possible number of ingredients.
Below, we illustrate the cases for recipes with one and two required ingredients:
(exec (1 2)
(, (recipe $product (numIngredients 1))
(recipe $product (result (id $productname)))
(recipe $product (pattern 0 $x))
(recipe $product (key ($x $xitem)))
(inventory $xitem))
(, (inventory $productname))
)
(exec (1 3)
(, (recipe $product (numIngredients 2))
(recipe $product (result (id $productname)))
(recipe $product (pattern 0 $x)) (recipe $product3 (key ($x $xitem)))
(recipe $product (pattern 1 $y)) (recipe $product3 (key ($y $yitem)))
(inventory $xitem)
(inventory $yitem))
(, (inventory $productname))
)
It may seem inconvenient to define a rule per ingredient count, however:
- MM2 is optimized for performance, not for minimal syntax. Explicit patterns allow the engine to run much faster.
- Many query languages (e.g., SQL) also require explicitly structured queries and do not provide generalization mechanisms for variable-length patterns, so MM2 is not unusual in this regard.
- MM2 code is easy to auto-generate, which means verbosity is not a practical problem; larger rule sets can be produced automatically.
Next, we want the previous execution statements to run iteratively. If a new product is added to the inventory, it might serve as an ingredient for another recipe.
Any iterative process needs a stop criterion to avoid running infinitely.
Common options include:
- stopping after a fixed number of iterations
- stopping when the space reaches a fixed point (no new items are added; see Part 1)
- stopping when a particular goal state is reached. In our case, when a specific item appears in the inventory
For illustration, we will use the third option: We iterate until a chosen “target item” appears in the inventory. We indicate this target with a simple statement:
(wanted "minecraft:torch")
It is possible to list multiple wanted items. In that case, the iteration stops once all of them are present in the inventory.
Similar to the iterative process in Part 1, we introduce an execution statement that repeatedly re-adds the rules responsible for a single iteration, including itself.
This is the role of exec (2 2), which is shown below.
Let’s walk through the structure of this rule.
In its patterns, exec (2 2) matches on two things:
- itself, to make it possible to add itself back into the space, and
- the
wantedstatement, so the rule only fires as long as a target item still needs to be reached.
In its template, exec (2 2) adds the rules exec (1 0) through exec (1 3) back into the space. These are precisely the execution statements we defined earlier for performing one iteration of the crafting process.
We also define exec (2 1), which checks whether the wanted item has appeared in the inventory.
If it has, exec (2 1) removes the corresponding wanted statement.
Once the wanted statement is gone, exec (2 2) can no longer match and therefore stops executing, ending the iterative process.
(exec (2 2) (, (exec (2 2) $ps $ts) (wanted $goal))
(,
(exec (1 0)
(, (recipe $product (numIngredients 1))
(recipe $product (ingredients 0 $xitem))
(inventory $xitem)
(recipe $product (result (id $productname))))
(, (inventory $productname))
)
(exec (1 1)
(, (recipe $product (numIngredients 2))
(recipe $product (ingredients 0 $xitem))
(recipe $product (ingredients 1 $yitem))
(inventory $xitem)
(inventory $yitem)
(recipe $product (result (id $productname))))
(, (inventory $productname))
)
(exec (1 2)
(, (recipe $product (numIngredients 1))
(recipe $product (result (id $productname)))
(recipe $product (pattern 0 $x))
(recipe $product (key ($x $xitem)))
(inventory $xitem))
(, (inventory $productname))
)
(exec (1 3)
(, (recipe $product (numIngredients 2))
(recipe $product (result (id $productname)))
(recipe $product (pattern 0 $x3)) (recipe $product (key ($x $xitem)))
(recipe $product (pattern 1 $y3)) (recipe $product (key ($y $yitem)))
(inventory $xitem)
(inventory $yitem))
(, (inventory $productname))
)
(exec (2 1) (, (wanted $subgoal) (inventory $subgoal))
(O (- (wanted $subgoal)))
)
(exec (2 2) $ps $ts)
)
)
Note that if the wanted item cannot be produced from the available ingredients, the iterative process would never terminate.
We will fix that in the next step.
Note that if the wanted item cannot be produced from the available ingredients, the iterative process would never terminate.
We will fix that in the next step.
What if it is impossible to craft the desired item from the items currently in our inventory?
In that case, the wanted statement would never be removed, the iterative rule exec (2 2) would continue to fire indefinitely, and the program would never halt.
To avoid this, we introduce a backup stop criterion. We choose to add a maximum number of iterations that the process is allowed to perform.
We represent this limit using Peano numbers. (These were introduced in Part 1, but here is a quick reminder.)
Peano numbers encode natural numbers as repeated applications of the successor operator
Sto zeroZ:(peano 0 Z) (peano 1 (S Z)) (peano 2 (S (S Z))) (peano 3 (S (S (S Z))))
We add the maximum number of iterations to the space. Let's say we only allow four iterations. Then we write:
(maxiter (S(S(S(S(Z))))))
Next, we extend our iterative rule to also match on the maxiter statement.
On every iteration, the rule consumes the current maxiter (S $i) value and replaces it with a decremented version maxiter $i.
Once the counter reaches Z, no matching maxiter (S ...) remains, so the iterative rule can no longer be executed, and the process stops automatically.
(exec (2 2) (, (exec (2 2) $ps $ts) (wanted $goal) (maxiter (S $i))) ; <-- add max iteration in the matches
(O
(+ (exec (1 0)
(, (recipe $product (numIngredients 1))
(recipe $product (ingredients 0 $xitem))
(inventory $xitem)
(recipe $product (result (id $productname))))
(, (inventory $productname))
)
)
(+ (exec (1 1)
(, (recipe $product (numIngredients 2))
(recipe $product (ingredients 0 $xitem))
(recipe $product (ingredients 1 $yitem))
(inventory $xitem)
(inventory $yitem)
(recipe $product (result (id $productname))))
(, (inventory $productname))
)
)
(+ (exec (1 2)
(, (recipe $product (numIngredients 1))
(recipe $product (result (id $productname)))
(recipe $product (pattern 0 $x))
(recipe $product (key ($x $xitem)))
(inventory $xitem))
(, (inventory $productname))
)
)
(+ (exec (1 3)
(, (recipe $product (numIngredients 2))
(recipe $product (result (id $productname)))
(recipe $product (pattern 0 $x3)) (recipe $product (key ($x $xitem)))
(recipe $product (pattern 1 $y3)) (recipe $product (key ($y $yitem)))
(inventory $xitem)
(inventory $yitem))
(, (inventory $productname))
))
(+ (exec (2 1)
(, (wanted $subgoal) (inventory $subgoal))
(O (- (wanted $subgoal))))
)
(+ (exec (1 2)
(, (recipe $product3 (numIngredients 2))
(recipe $product3 (result (id $productname3)))
(recipe $product3 (pattern 0 $x3)) (recipe $product3 (key ($x3 $xitem3)))
(recipe $product3 (pattern 1 $y3)) (recipe $product3 (key ($y3 $yitem3)))
(inventory $xitem3)
(inventory $yitem3))
(, (inventory $productname3))
)
)
(+ (exec (2 2) $ps $ts))
(+ (maxiter $i)) ; <-- add max iteration statement with one reduced
(- (maxiter (S $i))) ; <-- remove the current max iteration statement
)
)
We will now show how the execution statements transform the space.
When running MORK from the terminal, you can limit the number of execution steps using the --steps flag to reproduce these results.
We begin by inspecting the space exactly as it appears before the first execution step.
(wanted "minecraft:torch")
(maxiter (S(S(S(S(Z))))))
(inventory "minecraft:oak_log")
(inventory "minecraft:charcoal")
(tags "#minecraft:planks" "minecraft:oak_planks")
(tags "#minecraft:planks" "minecraft:spruce_planks")
(tags "#minecraft:planks" "minecraft:birch_planks")
(tags "#minecraft:planks" "minecraft:jungle_planks")
(tags "#minecraft:oak_logs" "minecraft:oak_log")
(tags "#minecraft:oak_logs" "minecraft:oak_wood")
(tags "#minecraft:oak_logs" "minecraft:stripped_oak_log")
(tags "#minecraft:oak_logs" "minecraft:stripped_oak_wood")
(recipe oak_planks (ingredients 0 "#minecraft:oak_logs"))
(recipe oak_planks (result (id "minecraft:oak_planks")))
(recipe torch (key ("#" "minecraft:stick")))
(recipe torch (key ("X" 0 "minecraft:coal")))
(recipe torch (key ("X" 1 "minecraft:charcoal")))
(recipe torch (pattern 0 "X"))
(recipe torch (pattern 1 "#"))
(recipe torch (result (id "minecraft:torch")))
(recipe stick (key ("#" "#minecraft:planks")))
(recipe stick (pattern 0 "#"))
(recipe stick (pattern 1 "#"))
(recipe stick (result (id "minecraft:stick")))
(exec (0 0) (, (recipe $r (pattern $i $p)) (recipe $r (key ($p $_ $ingr)))) ;<--- the first execution step that will be ran
(O (+ (recipe $r (key ($p $ingr)))) (- (recipe $r (key ($p $_ $ingr))))))
(exec (0 1) (, (recipe $product (ingredients $i $x)))
(O (count (recipe $product (numIngredients $k)) $k $i)))
(exec (0 1) (, (recipe $product (pattern $i $p)))
(O (count (recipe $product (numIngredients $k)) $k $i)))
(exec (0 3) (, (recipe $product (key ($p $group))) (tags $group $ingr))
(, (recipe $product (key ($p $ingr)))))
(exec (0 3) (, (recipe $p (ingredients $i $group)) (tags $group $ingr))
(, (recipe $p (ingredients $i $ingr))))
(exec (2 2) (, (exec (2 2) $ps $ts) (wanted $goal) (maxiter (S $i)))
(O
(+
(exec (1 0)
(, (recipe $product (numIngredients 1))
(recipe $product (ingredients 0 $xitem))
(inventory $xitem)
(recipe $product (result (id $productname))))
(, (inventory $productname))
)
)
(+ (exec (1 1)
(, (recipe $product (numIngredients 2))
(recipe $product (ingredients 0 $xitem))
(recipe $product (ingredients 1 $yitem))
(inventory $xitem)
(inventory $yitem)
(recipe $product (result (id $productname))))
(, (inventory $productname))
)
)
(+ (exec (1 2)
(, (recipe $product (numIngredients 1))
(recipe $product (result (id $productname)))
(recipe $product (pattern 0 $x))
(recipe $product (key ($x $xitem)))
(inventory $xitem))
(, (inventory $productname))
)
)
(+
(exec (1 3)
(, (recipe $product3 (numIngredients 2))
(recipe $product3 (result (id $productname3)))
(recipe $product3 (pattern 0 $x3)) (recipe $product3 (key ($x3 $xitem3)))
(recipe $product3 (pattern 1 $y3)) (recipe $product3 (key ($y3 $yitem3)))
(inventory $xitem3)
(inventory $yitem3))
(, (inventory $productname3))
))
(+
(exec (2 1) (, (wanted $subgoal) (inventory $subgoal))
(O (- (wanted $subgoal))))
)
(+ (exec (2 2) $ps $ts))
(+ (maxiter $i))
(- (maxiter (S $i)))
)
)
After exec (0 0) is applied, the indexed key statements in the torch recipe are rewritten into their cleaner, index-free form.
(wanted "minecraft:torch")
(maxiter (S (S (S (S (Z))))))
(inventory "minecraft:oak_log")
(inventory "minecraft:charcoal")
(tags "#minecraft:planks" "minecraft:oak_planks")
(tags "#minecraft:planks" "minecraft:birch_planks")
(tags "#minecraft:planks" "minecraft:jungle_planks")
(tags "#minecraft:planks" "minecraft:spruce_planks")
(tags "#minecraft:oak_logs" "minecraft:oak_log")
(tags "#minecraft:oak_logs" "minecraft:oak_wood")
(tags "#minecraft:oak_logs" "minecraft:stripped_oak_log")
(tags "#minecraft:oak_logs" "minecraft:stripped_oak_wood")
(recipe stick (key ("#" "#minecraft:planks")))
(recipe stick (result (id "minecraft:stick")))
(recipe stick (pattern 0 "#"))
(recipe stick (pattern 1 "#"))
(recipe torch (key ("#" "minecraft:stick")))
(recipe torch (key ("X" "minecraft:coal"))) ; <--- index removed
(recipe torch (key ("X" "minecraft:charcoal"))) ; <--- index removed
(recipe torch (result (id "minecraft:torch")))
(recipe torch (pattern 0 "X"))
(recipe torch (pattern 1 "#"))
(recipe oak_planks (result (id "minecraft:oak_planks")))
(recipe oak_planks (ingredients 0 "#minecraft:oak_logs"))
(exec (0 1) (, (recipe $a (pattern $b $c))) (O (count (recipe $a (numIngredients $d)) $d $b)))
(exec (0 1) (, (recipe $a (ingredients $b $c))) (O (count (recipe $a (numIngredients $d)) $d $b)))
(exec (0 3) (, (recipe $a (key ($b $c))) (tags $c $d)) (, (recipe $a (key ($b $d)))))
(exec (0 3) (, (recipe $a (ingredients $b $c)) (tags $c $d)) (, (recipe $a (ingredients $b $d))))
(exec (2 2) (, (exec (2 2) $a $b) (wanted $c) (maxiter (S $d))) (O (+ (exec (1 0) (, (recipe $e (numIngredients 1)) (recipe $e (ingredients 0 $f)) (inventory $f) (recipe $e (result (id $g)))) (, (inventory $g)))) (+ (exec (1 1) (, (recipe $e (numIngredients 2)) (recipe $e (ingredients 0 $f)) (recipe $e (ingredients 1 $h)) (inventory $f) (inventory $h) (recipe $e (result (id $g)))) (, (inventory $g)))) (+ (exec (1 2) (, (recipe $e (numIngredients 1)) (recipe $e (result (id $g))) (recipe $e (pattern 0 $i)) (recipe $e (key ($i $f))) (inventory $f)) (, (inventory $g)))) (+ (exec (1 3) (, (recipe $j (numIngredients 2)) (recipe $j (result (id $x10))) (recipe $j (pattern 0 $x11)) (recipe $j (key ($x11 $x12))) (recipe $j (pattern 1 $x13)) (recipe $j (key ($x13 $x14))) (inventory $x12) (inventory $x14)) (, (inventory $x10)))) (+ (exec (2 1) (, (wanted $x15) (inventory $x15)) (O (- (wanted $x15))))) (+ (exec (2 2) $a $b)) (+ (maxiter $d)) (- (maxiter (S $d)))))
After exec (0 1) and exec (0 3) are applied, each recipe now includes a statement indicating the number of ingredients it requires.
Additionally, tokens that previously referred to a group (such as "#" standing for all items in #minecraft:planks in the stick recipe) have been expanded into separate key statements for every element belonging to that tag.
(wanted "minecraft:torch")
(maxiter (S (S (S (S (Z))))))
(inventory "minecraft:oak_log")
(inventory "minecraft:charcoal")
(tags "#minecraft:planks" "minecraft:oak_planks")
(tags "#minecraft:planks" "minecraft:birch_planks")
(tags "#minecraft:planks" "minecraft:jungle_planks")
(tags "#minecraft:planks" "minecraft:spruce_planks")
(tags "#minecraft:oak_logs" "minecraft:oak_log")
(tags "#minecraft:oak_logs" "minecraft:oak_wood")
(tags "#minecraft:oak_logs" "minecraft:stripped_oak_log")
(tags "#minecraft:oak_logs" "minecraft:stripped_oak_wood")
(recipe stick (key ("#" "#minecraft:planks")))
(recipe stick (key ("#" "minecraft:oak_planks"))) ; <--- added by (0 3)
(recipe stick (key ("#" "minecraft:birch_planks"))) ; <--- added by (0 3)
(recipe stick (key ("#" "minecraft:jungle_planks"))) ; <--- added by (0 3)
(recipe stick (key ("#" "minecraft:spruce_planks"))) ; <--- added by (0 3)
(recipe stick (result (id "minecraft:stick")))
(recipe stick (numIngredients 2)) ; <--- added by (0 1)
(recipe stick (pattern 0 "#"))
(recipe stick (pattern 1 "#"))
(recipe torch (key ("#" "minecraft:stick")))
(recipe torch (key ("X" "minecraft:coal")))
(recipe torch (key ("X" "minecraft:charcoal")))
(recipe torch (result (id "minecraft:torch")))
(recipe torch (numIngredients 2)) ; <--- added by (0 1)
(recipe torch (pattern 0 "X"))
(recipe torch (pattern 1 "#"))
(recipe oak_planks (result (id "minecraft:oak_planks")))
(recipe oak_planks (numIngredients 1)) ; <--- added by (0 1)
(recipe oak_planks (ingredients 0 "minecraft:oak_log"))
(recipe oak_planks (ingredients 0 "minecraft:oak_wood"))
(recipe oak_planks (ingredients 0 "#minecraft:oak_logs"))
(recipe oak_planks (ingredients 0 "minecraft:stripped_oak_log"))
(recipe oak_planks (ingredients 0 "minecraft:stripped_oak_wood"))
(exec (2 2) (, (exec (2 2) $a $b) (wanted $c) (maxiter (S $d))) (O (+ (exec (1 0) (, (recipe $e (numIngredients 1)) (recipe $e (ingredients 0 $f)) (inventory $f) (recipe $e (result (id $g)))) (, (inventory $g)))) (+ (exec (1 1) (, (recipe $e (numIngredients 2)) (recipe $e (ingredients 0 $f)) (recipe $e (ingredients 1 $h)) (inventory $f) (inventory $h) (recipe $e (result (id $g)))) (, (inventory $g)))) (+ (exec (1 2) (, (recipe $e (numIngredients 1)) (recipe $e (result (id $g))) (recipe $e (pattern 0 $i)) (recipe $e (key ($i $f))) (inventory $f)) (, (inventory $g)))) (+ (exec (1 3) (, (recipe $j (numIngredients 2)) (recipe $j (result (id $x10))) (recipe $j (pattern 0 $x11)) (recipe $j (key ($x11 $x12))) (recipe $j (pattern 1 $x13)) (recipe $j (key ($x13 $x14))) (inventory $x12) (inventory $x14)) (, (inventory $x10)))) (+ (exec (2 1) (, (wanted $x15) (inventory $x15)) (O (- (wanted $x15))))) (+ (exec (2 2) $a $b)) (+ (maxiter $d)) (- (maxiter (S $d)))))
First, we see that exec (2 2) has added the execution statements responsible for one crafting step back into the space, including itself.
We also observe that the maxiter statement has been decremented by one.
(wanted "minecraft:torch")
(maxiter (S (S (S (Z))))) ;<--- maxiter is 3 instead of 4
(inventory "minecraft:oak_log")
(inventory "minecraft:charcoal")
(tags "#minecraft:planks" "minecraft:oak_planks")
(tags "#minecraft:planks" "minecraft:birch_planks")
(tags "#minecraft:planks" "minecraft:jungle_planks")
(tags "#minecraft:planks" "minecraft:spruce_planks")
(tags "#minecraft:oak_logs" "minecraft:oak_log")
(tags "#minecraft:oak_logs" "minecraft:oak_wood")
(tags "#minecraft:oak_logs" "minecraft:stripped_oak_log")
(tags "#minecraft:oak_logs" "minecraft:stripped_oak_wood")
(recipe stick (key ("#" "#minecraft:planks")))
(recipe stick (key ("#" "minecraft:oak_planks")))
(recipe stick (key ("#" "minecraft:birch_planks")))
(recipe stick (key ("#" "minecraft:jungle_planks")))
(recipe stick (key ("#" "minecraft:spruce_planks")))
(recipe stick (result (id "minecraft:stick")))
(recipe stick (numIngredients 2))
(recipe stick (pattern 0 "#"))
(recipe stick (pattern 1 "#"))
(recipe torch (key ("#" "minecraft:stick")))
(recipe torch (key ("X" "minecraft:coal")))
(recipe torch (key ("X" "minecraft:charcoal")))
(recipe torch (result (id "minecraft:torch")))
(recipe torch (numIngredients 2))
(recipe torch (pattern 0 "X"))
(recipe torch (pattern 1 "#"))
(recipe oak_planks (result (id "minecraft:oak_planks")))
(recipe oak_planks (numIngredients 1))
(recipe oak_planks (ingredients 0 "minecraft:oak_log"))
(recipe oak_planks (ingredients 0 "minecraft:oak_wood"))
(recipe oak_planks (ingredients 0 "#minecraft:oak_logs"))
(recipe oak_planks (ingredients 0 "minecraft:stripped_oak_log"))
(recipe oak_planks (ingredients 0 "minecraft:stripped_oak_wood"))
; 6 newly added execution statements
(exec (1 0) (, (recipe $a (numIngredients 1)) (recipe $a (ingredients 0 $b)) (inventory $b) (recipe $a (result (id $c)))) (, (inventory $c)))
(exec (1 1) (, (recipe $a (numIngredients 2)) (recipe $a (ingredients 0 $b)) (recipe $a (ingredients 1 $c)) (inventory $b) (inventory $c) (recipe $a (result (id $d)))) (, (inventory $d)))
(exec (1 2) (, (recipe $a (numIngredients 1)) (recipe $a (result (id $b))) (recipe $a (pattern 0 $c)) (recipe $a (key ($c $d))) (inventory $d)) (, (inventory $b)))
(exec (1 3) (, (recipe $a (numIngredients 2)) (recipe $a (result (id $b))) (recipe $a (pattern 0 $c)) (recipe $a (key ($c $d))) (recipe $a (pattern 1 $e)) (recipe $a (key ($e $f))) (inventory $d) (inventory $f)) (, (inventory $b)))
(exec (2 1) (, (wanted $a) (inventory $a)) (O (- (wanted $a))))
(exec (2 2) (, (exec (2 2) $a $b) (wanted $c) (maxiter (S $d))) (O (+ (exec (1 0) (, (recipe $e (numIngredients 1)) (recipe $e (ingredients 0 $f)) (inventory $f) (recipe $e (result (id $g)))) (, (inventory $g)))) (+ (exec (1 1) (, (recipe $e (numIngredients 2)) (recipe $e (ingredients 0 $f)) (recipe $e (ingredients 1 $h)) (inventory $f) (inventory $h) (recipe $e (result (id $g)))) (, (inventory $g)))) (+ (exec (1 2) (, (recipe $e (numIngredients 1)) (recipe $e (result (id $g))) (recipe $e (pattern 0 $i)) (recipe $e (key ($i $f))) (inventory $f)) (, (inventory $g)))) (+ (exec (1 3) (, (recipe $j (numIngredients 2)) (recipe $j (result (id $x10))) (recipe $j (pattern 0 $x11)) (recipe $j (key ($x11 $x12))) (recipe $j (pattern 1 $x13)) (recipe $j (key ($x13 $x14))) (inventory $x12) (inventory $x14)) (, (inventory $x10)))) (+ (exec (2 1) (, (wanted $x15) (inventory $x15)) (O (- (wanted $x15))))) (+ (exec (2 2) $a $b)) (+ (maxiter $d)) (- (maxiter (S $d)))))
If we run one more step, we see that exec (1 0) adds "minecraft:oak_planks" to our inventory.
This happens because the recipe for oak planks requires only an oak log, which we already had in our inventory.
(wanted "minecraft:torch")
(maxiter (S (S (S (Z)))))
(inventory "minecraft:oak_log")
(inventory "minecraft:charcoal")
(inventory "minecraft:oak_planks") ;<--- added
(tags "#minecraft:planks" "minecraft:oak_planks")
(tags "#minecraft:planks" "minecraft:birch_planks")
(tags "#minecraft:planks" "minecraft:jungle_planks")
(tags "#minecraft:planks" "minecraft:spruce_planks")
(tags "#minecraft:oak_logs" "minecraft:oak_log")
(tags "#minecraft:oak_logs" "minecraft:oak_wood")
(tags "#minecraft:oak_logs" "minecraft:stripped_oak_log")
(tags "#minecraft:oak_logs" "minecraft:stripped_oak_wood")
(recipe stick (key ("#" "#minecraft:planks")))
(recipe stick (key ("#" "minecraft:oak_planks")))
(recipe stick (key ("#" "minecraft:birch_planks")))
(recipe stick (key ("#" "minecraft:jungle_planks")))
(recipe stick (key ("#" "minecraft:spruce_planks")))
(recipe stick (result (id "minecraft:stick")))
(recipe stick (numIngredients 2))
(recipe stick (pattern 0 "#"))
(recipe stick (pattern 1 "#"))
(recipe torch (key ("#" "minecraft:stick")))
(recipe torch (key ("X" "minecraft:coal")))
(recipe torch (key ("X" "minecraft:charcoal")))
(recipe torch (result (id "minecraft:torch")))
(recipe torch (numIngredients 2))
(recipe torch (pattern 0 "X"))
(recipe torch (pattern 1 "#"))
(recipe oak_planks (result (id "minecraft:oak_planks")))
(recipe oak_planks (numIngredients 1))
(recipe oak_planks (ingredients 0 "minecraft:oak_log"))
(recipe oak_planks (ingredients 0 "minecraft:oak_wood"))
(recipe oak_planks (ingredients 0 "#minecraft:oak_logs"))
(recipe oak_planks (ingredients 0 "minecraft:stripped_oak_log"))
(recipe oak_planks (ingredients 0 "minecraft:stripped_oak_wood"))
; exec (1 0) is executed
(exec (1 1) (, (recipe $a (numIngredients 2)) (recipe $a (ingredients 0 $b)) (recipe $a (ingredients 1 $c)) (inventory $b) (inventory $c) (recipe $a (result (id $d)))) (, (inventory $d)))
(exec (1 2) (, (recipe $a (numIngredients 1)) (recipe $a (result (id $b))) (recipe $a (pattern 0 $c)) (recipe $a (key ($c $d))) (inventory $d)) (, (inventory $b)))
(exec (1 3) (, (recipe $a (numIngredients 2)) (recipe $a (result (id $b))) (recipe $a (pattern 0 $c)) (recipe $a (key ($c $d))) (recipe $a (pattern 1 $e)) (recipe $a (key ($e $f))) (inventory $d) (inventory $f)) (, (inventory $b)))
(exec (2 1) (, (wanted $a) (inventory $a)) (O (- (wanted $a))))
(exec (2 2) (, (exec (2 2) $a $b) (wanted $c) (maxiter (S $d))) (O (+ (exec (1 0) (, (recipe $e (numIngredients 1)) (recipe $e (ingredients 0 $f)) (inventory $f) (recipe $e (result (id $g)))) (, (inventory $g)))) (+ (exec (1 1) (, (recipe $e (numIngredients 2)) (recipe $e (ingredients 0 $f)) (recipe $e (ingredients 1 $h)) (inventory $f) (inventory $h) (recipe $e (result (id $g)))) (, (inventory $g)))) (+ (exec (1 2) (, (recipe $e (numIngredients 1)) (recipe $e (result (id $g))) (recipe $e (pattern 0 $i)) (recipe $e (key ($i $f))) (inventory $f)) (, (inventory $g)))) (+ (exec (1 3) (, (recipe $j (numIngredients 2)) (recipe $j (result (id $x10))) (recipe $j (pattern 0 $x11)) (recipe $j (key ($x11 $x12))) (recipe $j (pattern 1 $x13)) (recipe $j (key ($x13 $x14))) (inventory $x12) (inventory $x14)) (, (inventory $x10)))) (+ (exec (2 1) (, (wanted $x15) (inventory $x15)) (O (- (wanted $x15))))) (+ (exec (2 2) $a $b)) (+ (maxiter $d)) (- (maxiter (S $d)))))
If we execute three more steps (for a total of 9), we see that exec (1 3) adds "minecraft:stick" to our inventory. This occurs because the stick recipe requires only planks, which we now have available.
(wanted "minecraft:torch")
(maxiter (S (S (S (Z)))))
(inventory "minecraft:stick") ; <--- stick
(inventory "minecraft:oak_log")
(inventory "minecraft:charcoal")
(inventory "minecraft:oak_planks")
(tags "#minecraft:planks" "minecraft:oak_planks")
(tags "#minecraft:planks" "minecraft:birch_planks")
(tags "#minecraft:planks" "minecraft:jungle_planks")
(tags "#minecraft:planks" "minecraft:spruce_planks")
(tags "#minecraft:oak_logs" "minecraft:oak_log")
(tags "#minecraft:oak_logs" "minecraft:oak_wood")
(tags "#minecraft:oak_logs" "minecraft:stripped_oak_log")
(tags "#minecraft:oak_logs" "minecraft:stripped_oak_wood")
(recipe stick (key ("#" "#minecraft:planks")))
(recipe stick (key ("#" "minecraft:oak_planks")))
(recipe stick (key ("#" "minecraft:birch_planks")))
(recipe stick (key ("#" "minecraft:jungle_planks")))
(recipe stick (key ("#" "minecraft:spruce_planks")))
(recipe stick (result (id "minecraft:stick")))
(recipe stick (numIngredients 2))
(recipe stick (pattern 0 "#"))
(recipe stick (pattern 1 "#"))
(recipe torch (key ("#" "minecraft:stick")))
(recipe torch (key ("X" "minecraft:coal")))
(recipe torch (key ("X" "minecraft:charcoal")))
(recipe torch (result (id "minecraft:torch")))
(recipe torch (numIngredients 2))
(recipe torch (pattern 0 "X"))
(recipe torch (pattern 1 "#"))
(recipe oak_planks (result (id "minecraft:oak_planks")))
(recipe oak_planks (numIngredients 1))
(recipe oak_planks (ingredients 0 "minecraft:oak_log"))
(recipe oak_planks (ingredients 0 "minecraft:oak_wood"))
(recipe oak_planks (ingredients 0 "#minecraft:oak_logs"))
(recipe oak_planks (ingredients 0 "minecraft:stripped_oak_log"))
(recipe oak_planks (ingredients 0 "minecraft:stripped_oak_wood"))
(exec (2 1) (, (wanted $a) (inventory $a)) (O (- (wanted $a))))
(exec (2 2) (, (exec (2 2) $a $b) (wanted $c) (maxiter (S $d))) (O (+ (exec (1 0) (, (recipe $e (numIngredients 1)) (recipe $e (ingredients 0 $f)) (inventory $f) (recipe $e (result (id $g)))) (, (inventory $g)))) (+ (exec (1 1) (, (recipe $e (numIngredients 2)) (recipe $e (ingredients 0 $f)) (recipe $e (ingredients 1 $h)) (inventory $f) (inventory $h) (recipe $e (result (id $g)))) (, (inventory $g)))) (+ (exec (1 2) (, (recipe $e (numIngredients 1)) (recipe $e (result (id $g))) (recipe $e (pattern 0 $i)) (recipe $e (key ($i $f))) (inventory $f)) (, (inventory $g)))) (+ (exec (1 3) (, (recipe $j (numIngredients 2)) (recipe $j (result (id $x10))) (recipe $j (pattern 0 $x11)) (recipe $j (key ($x11 $x12))) (recipe $j (pattern 1 $x13)) (recipe $j (key ($x13 $x14))) (inventory $x12) (inventory $x14)) (, (inventory $x10)))) (+ (exec (2 1) (, (wanted $x15) (inventory $x15)) (O (- (wanted $x15))))) (+ (exec (2 2) $a $b)) (+ (maxiter $d)) (- (maxiter (S $d)))))
exec (2 1) does not affect the space yet, because the wanted item is not in the inventory.
For the same reason, and because we still have remaining iterations, exec (2 2) adds all iterative execution statements (including itself) back into the space and decrements maxiter once more.
(After 11 steps)
(wanted "minecraft:torch")
(maxiter (S (S (Z)))) ;<--- reduced by one
(inventory "minecraft:stick")
(inventory "minecraft:oak_log")
(inventory "minecraft:charcoal")
(inventory "minecraft:oak_planks")
(tags "#minecraft:planks" "minecraft:oak_planks")
(tags "#minecraft:planks" "minecraft:birch_planks")
(tags "#minecraft:planks" "minecraft:jungle_planks")
(tags "#minecraft:planks" "minecraft:spruce_planks")
(tags "#minecraft:oak_logs" "minecraft:oak_log")
(tags "#minecraft:oak_logs" "minecraft:oak_wood")
(tags "#minecraft:oak_logs" "minecraft:stripped_oak_log")
(tags "#minecraft:oak_logs" "minecraft:stripped_oak_wood")
(recipe stick (key ("#" "#minecraft:planks")))
(recipe stick (key ("#" "minecraft:oak_planks")))
(recipe stick (key ("#" "minecraft:birch_planks")))
(recipe stick (key ("#" "minecraft:jungle_planks")))
(recipe stick (key ("#" "minecraft:spruce_planks")))
(recipe stick (result (id "minecraft:stick")))
(recipe stick (numIngredients 2))
(recipe stick (pattern 0 "#"))
(recipe stick (pattern 1 "#"))
(recipe torch (key ("#" "minecraft:stick")))
(recipe torch (key ("X" "minecraft:coal")))
(recipe torch (key ("X" "minecraft:charcoal")))
(recipe torch (result (id "minecraft:torch")))
(recipe torch (numIngredients 2))
(recipe torch (pattern 0 "X"))
(recipe torch (pattern 1 "#"))
(recipe oak_planks (result (id "minecraft:oak_planks")))
(recipe oak_planks (numIngredients 1))
(recipe oak_planks (ingredients 0 "minecraft:oak_log"))
(recipe oak_planks (ingredients 0 "minecraft:oak_wood"))
(recipe oak_planks (ingredients 0 "#minecraft:oak_logs"))
(recipe oak_planks (ingredients 0 "minecraft:stripped_oak_log"))
(recipe oak_planks (ingredients 0 "minecraft:stripped_oak_wood"))
; all the exec statements are added to the space again
(exec (1 0) (, (recipe $a (numIngredients 1)) (recipe $a (ingredients 0 $b)) (inventory $b) (recipe $a (result (id $c)))) (, (inventory $c)))
(exec (1 1) (, (recipe $a (numIngredients 2)) (recipe $a (ingredients 0 $b)) (recipe $a (ingredients 1 $c)) (inventory $b) (inventory $c) (recipe $a (result (id $d)))) (, (inventory $d)))
(exec (1 2) (, (recipe $a (numIngredients 1)) (recipe $a (result (id $b))) (recipe $a (pattern 0 $c)) (recipe $a (key ($c $d))) (inventory $d)) (, (inventory $b)))
(exec (1 3) (, (recipe $a (numIngredients 2)) (recipe $a (result (id $b))) (recipe $a (pattern 0 $c)) (recipe $a (key ($c $d))) (recipe $a (pattern 1 $e)) (recipe $a (key ($e $f))) (inventory $d) (inventory $f)) (, (inventory $b)))
(exec (2 1) (, (wanted $a) (inventory $a)) (O (- (wanted $a))))
(exec (2 2) (, (exec (2 2) $a $b) (wanted $c) (maxiter (S $d))) (O (+ (exec (1 0) (, (recipe $e (numIngredients 1)) (recipe $e (ingredients 0 $f)) (inventory $f) (recipe $e (result (id $g)))) (, (inventory $g)))) (+ (exec (1 1) (, (recipe $e (numIngredients 2)) (recipe $e (ingredients 0 $f)) (recipe $e (ingredients 1 $h)) (inventory $f) (inventory $h) (recipe $e (result (id $g)))) (, (inventory $g)))) (+ (exec (1 2) (, (recipe $e (numIngredients 1)) (recipe $e (result (id $g))) (recipe $e (pattern 0 $i)) (recipe $e (key ($i $f))) (inventory $f)) (, (inventory $g)))) (+ (exec (1 3) (, (recipe $j (numIngredients 2)) (recipe $j (result (id $x10))) (recipe $j (pattern 0 $x11)) (recipe $j (key ($x11 $x12))) (recipe $j (pattern 1 $x13)) (recipe $j (key ($x13 $x14))) (inventory $x12) (inventory $x14)) (, (inventory $x10)))) (+ (exec (2 1) (, (wanted $x15) (inventory $x15)) (O (- (wanted $x15))))) (+ (exec (2 2) $a $b)) (+ (maxiter $d)) (- (maxiter (S $d)))))
After exec (1 0) through exec (1 3) run again, "minecraft:stick" is added to our inventory.
(After 15 steps)
(wanted "minecraft:torch")
(maxiter (S (S (Z))))
(inventory "minecraft:stick") ; <--- Added
(inventory "minecraft:torch")
(inventory "minecraft:oak_log")
(inventory "minecraft:charcoal")
(inventory "minecraft:oak_planks")
(tags "#minecraft:planks" "minecraft:oak_planks")
(tags "#minecraft:planks" "minecraft:birch_planks")
(tags "#minecraft:planks" "minecraft:jungle_planks")
(tags "#minecraft:planks" "minecraft:spruce_planks")
(tags "#minecraft:oak_logs" "minecraft:oak_log")
(tags "#minecraft:oak_logs" "minecraft:oak_wood")
(tags "#minecraft:oak_logs" "minecraft:stripped_oak_log")
(tags "#minecraft:oak_logs" "minecraft:stripped_oak_wood")
(recipe stick (key ("#" "#minecraft:planks")))
(recipe stick (key ("#" "minecraft:oak_planks")))
(recipe stick (key ("#" "minecraft:birch_planks")))
(recipe stick (key ("#" "minecraft:jungle_planks")))
(recipe stick (key ("#" "minecraft:spruce_planks")))
(recipe stick (result (id "minecraft:stick")))
(recipe stick (numIngredients 2))
(recipe stick (pattern 0 "#"))
(recipe stick (pattern 1 "#"))
(recipe torch (key ("#" "minecraft:stick")))
(recipe torch (key ("X" "minecraft:coal")))
(recipe torch (key ("X" "minecraft:charcoal")))
(recipe torch (result (id "minecraft:torch")))
(recipe torch (numIngredients 2))
(recipe torch (pattern 0 "X"))
(recipe torch (pattern 1 "#"))
(recipe oak_planks (result (id "minecraft:oak_planks")))
(recipe oak_planks (numIngredients 1))
(recipe oak_planks (ingredients 0 "minecraft:oak_log"))
(recipe oak_planks (ingredients 0 "minecraft:oak_wood"))
(recipe oak_planks (ingredients 0 "#minecraft:oak_logs"))
(recipe oak_planks (ingredients 0 "minecraft:stripped_oak_log"))
(recipe oak_planks (ingredients 0 "minecraft:stripped_oak_wood"))
(exec (2 1) (, (wanted $a) (inventory $a)) (O (- (wanted $a))))
(exec (2 2) (, (exec (2 2) $a $b) (wanted $c) (maxiter (S $d))) (O (+ (exec (1 0) (, (recipe $e (numIngredients 1)) (recipe $e (ingredients 0 $f)) (inventory $f) (recipe $e (result (id $g)))) (, (inventory $g)))) (+ (exec (1 1) (, (recipe $e (numIngredients 2)) (recipe $e (ingredients 0 $f)) (recipe $e (ingredients 1 $h)) (inventory $f) (inventory $h) (recipe $e (result (id $g)))) (, (inventory $g)))) (+ (exec (1 2) (, (recipe $e (numIngredients 1)) (recipe $e (result (id $g))) (recipe $e (pattern 0 $i)) (recipe $e (key ($i $f))) (inventory $f)) (, (inventory $g)))) (+ (exec (1 3) (, (recipe $j (numIngredients 2)) (recipe $j (result (id $x10))) (recipe $j (pattern 0 $x11)) (recipe $j (key ($x11 $x12))) (recipe $j (pattern 1 $x13)) (recipe $j (key ($x13 $x14))) (inventory $x12) (inventory $x14)) (, (inventory $x10)))) (+ (exec (2 1) (, (wanted $x15) (inventory $x15)) (O (- (wanted $x15))))) (+ (exec (2 2) $a $b)) (+ (maxiter $d)) (- (maxiter (S $d)))))
Since our wanted item is now in the inventory, exec (2 1) removes the corresponding wanted statement from the space.
(maxiter (S (S (Z))))
; --- wanted statement is removed ---
(inventory "minecraft:stick")
(inventory "minecraft:torch")
(inventory "minecraft:oak_log")
(inventory "minecraft:charcoal")
(inventory "minecraft:oak_planks")
(tags "#minecraft:planks" "minecraft:oak_planks")
(tags "#minecraft:planks" "minecraft:birch_planks")
(tags "#minecraft:planks" "minecraft:jungle_planks")
(tags "#minecraft:planks" "minecraft:spruce_planks")
(tags "#minecraft:oak_logs" "minecraft:oak_log")
(tags "#minecraft:oak_logs" "minecraft:oak_wood")
(tags "#minecraft:oak_logs" "minecraft:stripped_oak_log")
(tags "#minecraft:oak_logs" "minecraft:stripped_oak_wood")
(recipe stick (key ("#" "#minecraft:planks")))
(recipe stick (key ("#" "minecraft:oak_planks")))
(recipe stick (key ("#" "minecraft:birch_planks")))
(recipe stick (key ("#" "minecraft:jungle_planks")))
(recipe stick (key ("#" "minecraft:spruce_planks")))
(recipe stick (result (id "minecraft:stick")))
(recipe stick (numIngredients 2))
(recipe stick (pattern 0 "#"))
(recipe stick (pattern 1 "#"))
(recipe torch (key ("#" "minecraft:stick")))
(recipe torch (key ("X" "minecraft:coal")))
(recipe torch (key ("X" "minecraft:charcoal")))
(recipe torch (result (id "minecraft:torch")))
(recipe torch (numIngredients 2))
(recipe torch (pattern 0 "X"))
(recipe torch (pattern 1 "#"))
(recipe oak_planks (result (id "minecraft:oak_planks")))
(recipe oak_planks (numIngredients 1))
(recipe oak_planks (ingredients 0 "minecraft:oak_log"))
(recipe oak_planks (ingredients 0 "minecraft:oak_wood"))
(recipe oak_planks (ingredients 0 "#minecraft:oak_logs"))
(recipe oak_planks (ingredients 0 "minecraft:stripped_oak_log"))
(recipe oak_planks (ingredients 0 "minecraft:stripped_oak_wood"))
(exec (2 2) (, (exec (2 2) $a $b) (wanted $c) (maxiter (S $d))) (O (+ (exec (1 0) (, (recipe $e (numIngredients 1)) (recipe $e (ingredients 0 $f)) (inventory $f) (recipe $e (result (id $g)))) (, (inventory $g)))) (+ (exec (1 1) (, (recipe $e (numIngredients 2)) (recipe $e (ingredients 0 $f)) (recipe $e (ingredients 1 $h)) (inventory $f) (inventory $h) (recipe $e (result (id $g)))) (, (inventory $g)))) (+ (exec (1 2) (, (recipe $e (numIngredients 1)) (recipe $e (result (id $g))) (recipe $e (pattern 0 $i)) (recipe $e (key ($i $f))) (inventory $f)) (, (inventory $g)))) (+ (exec (1 3) (, (recipe $j (numIngredients 2)) (recipe $j (result (id $x10))) (recipe $j (pattern 0 $x11)) (recipe $j (key ($x11 $x12))) (recipe $j (pattern 1 $x13)) (recipe $j (key ($x13 $x14))) (inventory $x12) (inventory $x14)) (, (inventory $x10)))) (+ (exec (2 1) (, (wanted $x15) (inventory $x15)) (O (- (wanted $x15))))) (+ (exec (2 2) $a $b)) (+ (maxiter $d)) (- (maxiter (S $d)))))
Now that no wanted statement remains, exec (2 2) no longer matches and therefore does not run.
As a result, it is not re-added to the space, and once all remaining execution statements have completed, no further rules are available to fire.
This naturally ends the iterative process.
(maxiter (S (S (Z))))
(inventory "minecraft:stick")
(inventory "minecraft:torch")
(inventory "minecraft:oak_log")
(inventory "minecraft:charcoal")
(inventory "minecraft:oak_planks")
(tags "#minecraft:planks" "minecraft:oak_planks")
(tags "#minecraft:planks" "minecraft:birch_planks")
(tags "#minecraft:planks" "minecraft:jungle_planks")
(tags "#minecraft:planks" "minecraft:spruce_planks")
(tags "#minecraft:oak_logs" "minecraft:oak_log")
(tags "#minecraft:oak_logs" "minecraft:oak_wood")
(tags "#minecraft:oak_logs" "minecraft:stripped_oak_log")
(tags "#minecraft:oak_logs" "minecraft:stripped_oak_wood")
(recipe stick (key ("#" "#minecraft:planks")))
(recipe stick (key ("#" "minecraft:oak_planks")))
(recipe stick (key ("#" "minecraft:birch_planks")))
(recipe stick (key ("#" "minecraft:jungle_planks")))
(recipe stick (key ("#" "minecraft:spruce_planks")))
(recipe stick (result (id "minecraft:stick")))
(recipe stick (numIngredients 2))
(recipe stick (pattern 0 "#"))
(recipe stick (pattern 1 "#"))
(recipe torch (key ("#" "minecraft:stick")))
(recipe torch (key ("X" "minecraft:coal")))
(recipe torch (key ("X" "minecraft:charcoal")))
(recipe torch (result (id "minecraft:torch")))
(recipe torch (numIngredients 2))
(recipe torch (pattern 0 "X"))
(recipe torch (pattern 1 "#"))
(recipe oak_planks (result (id "minecraft:oak_planks")))
(recipe oak_planks (numIngredients 1))
(recipe oak_planks (ingredients 0 "minecraft:oak_log"))
(recipe oak_planks (ingredients 0 "minecraft:oak_wood"))
(recipe oak_planks (ingredients 0 "#minecraft:oak_logs"))
(recipe oak_planks (ingredients 0 "minecraft:stripped_oak_log"))
(recipe oak_planks (ingredients 0 "minecraft:stripped_oak_wood"))
To illustrate a case where the process stops because the maximum number of iterations is reached, we remove charcoal from the initial inventory. Without charcoal (or coal), a torch can never be crafted, so the wanted item will remain unreachable, and the iteration limit will eventually halt the process.
(wanted "minecraft:torch")
(maxiter (S(S(S(S(Z))))))
(inventory "minecraft:oak_log")
(tags "#minecraft:planks" "minecraft:oak_planks")
(tags "#minecraft:planks" "minecraft:spruce_planks")
(tags "#minecraft:planks" "minecraft:birch_planks")
(tags "#minecraft:planks" "minecraft:jungle_planks")
(tags "#minecraft:oak_logs" "minecraft:oak_log")
(tags "#minecraft:oak_logs" "minecraft:oak_wood")
(tags "#minecraft:oak_logs" "minecraft:stripped_oak_log")
(tags "#minecraft:oak_logs" "minecraft:stripped_oak_wood")
(recipe oak_planks (ingredients 0 "#minecraft:oak_logs"))
(recipe oak_planks (result (id "minecraft:oak_planks")))
(recipe torch (key ("#" "minecraft:stick")))
(recipe torch (key ("X" 0 "minecraft:coal")))
(recipe torch (key ("X" 1 "minecraft:charcoal")))
(recipe torch (pattern 0 "X"))
(recipe torch (pattern 1 "#"))
(recipe torch (result (id "minecraft:torch")))
(recipe stick (key ("#" "#minecraft:planks")))
(recipe stick (pattern 0 "#"))
(recipe stick (pattern 1 "#"))
(recipe stick (result (id "minecraft:stick")))
(exec (0 0) (, (recipe $r (pattern $i $p)) (recipe $r (key ($p $_ $ingr))))
(O (+ (recipe $r (key ($p $ingr)))) (- (recipe $r (key ($p $_ $ingr))))))
(exec (0 1) (, (recipe $product (ingredients $i $x)))
(O (count (recipe $product (numIngredients $k)) $k $i)))
(exec (0 1) (, (recipe $product (pattern $i $p)))
(O (count (recipe $product (numIngredients $k)) $k $i)))
(exec (0 3) (, (recipe $product (key ($p $group))) (tags $group $ingr))
(, (recipe $product (key ($p $ingr)))))
(exec (0 3) (, (recipe $p (ingredients $i $group)) (tags $group $ingr))
(, (recipe $p (ingredients $i $ingr))))
(exec (2 2) (, (exec (2 2) $ps $ts) (wanted $goal) (maxiter (S $i)))
(O
(+
(exec (1 0)
(, (recipe $product (numIngredients 1))
(recipe $product (ingredients 0 $xitem))
(inventory $xitem)
(recipe $product (result (id $productname))))
(, (inventory $productname))
)
)
(+ (exec (1 1)
(, (recipe $product (numIngredients 2))
(recipe $product (ingredients 0 $xitem))
(recipe $product (ingredients 1 $yitem))
(inventory $xitem)
(inventory $yitem)
(recipe $product (result (id $productname))))
(, (inventory $productname))
)
)
(+ (exec (1 2)
(, (recipe $product (numIngredients 1))
(recipe $product (result (id $productname)))
(recipe $product (pattern 0 $x))
(recipe $product (key ($x $xitem)))
(inventory $xitem))
(, (inventory $productname))
)
)
(+
(exec (1 3)
(, (recipe $product3 (numIngredients 2))
(recipe $product3 (result (id $productname3)))
(recipe $product3 (pattern 0 $x3)) (recipe $product3 (key ($x3 $xitem3)))
(recipe $product3 (pattern 1 $y3)) (recipe $product3 (key ($y3 $yitem3)))
(inventory $xitem3)
(inventory $yitem3))
(, (inventory $productname3))
))
(+
(exec (2 1) (, (wanted $subgoal) (inventory $subgoal))
(O (- (wanted $subgoal))))
)
(+ (exec (2 2) $ps $ts))
(+ (maxiter $i))
(- (maxiter (S $i)))
)
)
When we run the program to completion, it terminates with the following result:
(wanted "minecraft:torch") ; <--- we still have a wanted statement
(maxiter (Z)) ; <--- the iteration count reached zero
(inventory "minecraft:stick")
(inventory "minecraft:oak_log")
(inventory "minecraft:oak_planks")
(tags "#minecraft:planks" "minecraft:oak_planks")
(tags "#minecraft:planks" "minecraft:birch_planks")
(tags "#minecraft:planks" "minecraft:jungle_planks")
(tags "#minecraft:planks" "minecraft:spruce_planks")
(tags "#minecraft:oak_logs" "minecraft:oak_log")
(tags "#minecraft:oak_logs" "minecraft:oak_wood")
(tags "#minecraft:oak_logs" "minecraft:stripped_oak_log")
(tags "#minecraft:oak_logs" "minecraft:stripped_oak_wood")
(recipe stick (key ("#" "#minecraft:planks")))
(recipe stick (key ("#" "minecraft:oak_planks")))
(recipe stick (key ("#" "minecraft:birch_planks")))
(recipe stick (key ("#" "minecraft:jungle_planks")))
(recipe stick (key ("#" "minecraft:spruce_planks")))
(recipe stick (result (id "minecraft:stick")))
(recipe stick (numIngredients 2))
(recipe stick (pattern 0 "#"))
(recipe stick (pattern 1 "#"))
(recipe torch (key ("#" "minecraft:stick")))
(recipe torch (key ("X" "minecraft:coal")))
(recipe torch (key ("X" "minecraft:charcoal")))
(recipe torch (result (id "minecraft:torch")))
(recipe torch (numIngredients 2))
(recipe torch (pattern 0 "X"))
(recipe torch (pattern 1 "#"))
(recipe oak_planks (result (id "minecraft:oak_planks")))
(recipe oak_planks (numIngredients 1))
(recipe oak_planks (ingredients 0 "minecraft:oak_log"))
(recipe oak_planks (ingredients 0 "minecraft:oak_wood"))
(recipe oak_planks (ingredients 0 "#minecraft:oak_logs"))
(recipe oak_planks (ingredients 0 "minecraft:stripped_oak_log"))
(recipe oak_planks (ingredients 0 "minecraft:stripped_oak_wood"))
We see that a torch is still marked as a wanted item, but the maximum iteration count has reached zero.
Because of this, exec (2 2) no longer matches and does not add any further execution statements to the space.
- Rewrite the algorithm so that the iterative process stops as soon as any one of a set of wanted elements is reached.
- Rewrite the algorithm so that it halts once the space no longer changes (i.e., a fixed point is reached).
Hint: use the same techniques as in the first part of the reachability tutorial.