Article 1CRM0 Gas room

Gas room

by
ericlippert
from Fabulous adventures in coding on (#1CRM0)

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 2e37fa: store g7a a737fd: store g26 013800: store g73 1e3803: insert_obj g73 g003806: call 5862 ->sp5865: print MINI-ZORK I:

Holy goodness, we are actually getting as far as output!

Next time on FAIC: output!


4344 b.gif?host=ericlippert.com&blog=67759120
External Content
Source RSS or Atom Feed
Feed Location http://ericlippert.com/feed
Feed Title Fabulous adventures in coding
Feed Link https://ericlippert.com/
Reply 0 comments