Gas room

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: jin, get_sibling, get_child, get_parent, insert_obj and remove_obj.

Code for this episode can be found at https://github.com/ericlippert/flathead/tree/blog17

The 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_sibling and 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!

3 thoughts on “Gas room

Leave a comment