This room smells strongly of coal gas. Narrow tunnels lead east and south.
There is a sapphire-encrusted bracelet here.
> go east
This room of course explodes if the burning torch is brought into it. I note that this game feature is unrealistic. The characteristic gas smell that you get from a natural gas leak is man-made. Coal seam gas, unless it happens to have sulfides in it, has no smell. Then again, walking on solid rainbows is also unrealistic, so perhaps this is a small sin at best.
As we know, game objects form a tree where the parent-child relationship is containment. Game objects also have attributes, which are a collection of Boolean flags, and properties, which can vary in size and have a default value. There are fifteen instructions that deal with objects; today we’ll implement the instructions that deal only with the object tree:
Code for this episode can be found at https://github.com/ericlippert/flathead/tree/blog17
jin instruction is a conditional branch. We have two object numbers and wish to branch if one is the parent of the other.
This is an unnecessary instruction, as it could be assembled as pushing the parent on the stack, and then doing an equality comparison. But this is a common operantion.
let handle_jin obj1 obj2 interpreter = let obj1 = Object obj1 in let obj2 = Object obj2 in let parent = Object.parent interpreter.story obj1 in if parent = obj2 then 1 else 0
The getters are pretty straightforward. I note that
get_child are both conditional branch and store instructions. That is, they both store the value and conditionally branch if it is non-zero. The
get_parent instruction does not branch as it is rare for an object to have no parent. But in both cases all our helper must do is simply return the value; the caller takes care of both storing the result and branching appropriately.
let handle_get_sibling obj interpreter = let obj = Object obj in let (Object sibling) = Object.sibling interpreter.story obj in sibling
I won’t bother to show the corresponding parent and child helpers.
For the remaining operations we’re going to need to set the parent, sibling and child; in our previous episode on dealing with objects we only created getters for those. Easily done. Recall that object numbers are bytes in v3 and lower, words otherwise.
let set_sibling story obj (Object new_sibling) = let (Object_address addr) = address story obj in if Story.v3_or_lower (Story.version story) then Story.write_byte story (Byte_address (addr + 5)) new_sibling else Story.write_word story (Word_address (addr + 8)) new_sibling
Again I won’t bother to show the corresponding parent and child functions.
Now let’s talk a bit about
insert_obj. This instruction takes two object numbers and it creates a story in which the object tree has one of the objects as the first child of the other. For example, suppose we have a room R containing the player P and objects B and C. The player is holding an object A. So the world looks like this:
R has child P P has parent R, sibling B and child A A has parent P, no sibling B has parent R, sibling C C has parent R, no sibling R | P--B--C | A
If the player picks up B then B is inserted into P; the new state is:
R has child P P has parent R, sibling C, and child B A has parent P, no sibling B has parent P, sibling A C has parent R, no sibling R | P--C | B--A
There are numerous cases to consider here. But first we need a helper method. Notice how in the example given above we need to detect that B is the sibling of P so that we can make C the sibling of P:
let find_previous_sibling story obj = let rec aux current = let next_sibling = sibling story current in if next_sibling = obj then current else aux next_sibling in let parent = parent story obj in let first_child = child story parent in aux first_child
The easiest way I know of to solve this problem is to first make B parentless, get the tree consistent, and then insert B as the first child of P. Here’s code for the first part:
let remove story obj = let original_parent = parent story obj in if original_parent = invalid_object then story (* Already detatched *) else
If the child is the parent’s first child then make the next sibling the new first child. If the child is not the first child then the previous sibling needs to point to the next sibling.
let edit1 = let sibling = sibling story obj in if obj = child story original_parent then set_child story original_parent sibling else let prev_sibling = find_previous_sibling story obj in set_sibling story prev_sibling sibling in set_parent edit1 obj invalid_object
And now we can insert the parentless object:
let insert story new_child new_parent = let edit1 = remove story new_child in let edit2 = set_parent edit1 new_child new_parent in let edit3 = set_sibling edit2 new_child (child edit2 new_parent) in set_child edit3 new_parent new_child
And now the rest is just boring code to call these helper methods. When we run our program it now gets as far as…
... 37f7: store g00 2e 37fa: store g7a a7 37fd: store g26 01 3800: store g73 1e 3803: insert_obj g73 g00 3806: call 5862 ->sp 5865: print MINI-ZORK I:
Holy goodness, we are actually getting as far as output!
Next time on FAIC: output!