Shaft room, again

> look

In the middle this room a small shaft descends into darkness below. Above the shaft is a metal framework to which a heavy iron chain is attached. There are exits to the west and north. A foul odor can be detected from the latter direction.
From the chain is suspended a basket.
The basket contains:
A screwdriver
A torch (providing light)

> put the coal in the basket

Done.

> lower the basket

The basket is lowered to the bottom of the shaft.

> go north
> go east
> go north
> go south
> go down
> drop all

clove of garlic: Dropped.
pair of candles: Dropped.
matchbook: Dropped.
brass lantern: Dropped.

> go west


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

Recall that every object has a set of properties, that properties can have multiple bytes of value, and that they have default values if omitted. There are five instructions that manipulate properties: get_prop, get_prop_addr, get_next_prop, get_prop_len and putprop.

The get_prop_len instruction is a bit odd in that it takes the address of a property rather than an object number and property number, as all the rest do. Note that the address associated with a property is the address of the property data itself; this will cause us some small difficulties, as we’ll see.

The instruction handlers are all just straightforward little methods that call helpers in the object module, so I won’t bother to list them here.

Let’s start with the default values of properties. That’s pretty straightforward. I’m going to make yet another wrapper type, this one to represent a property number:

type property_number = Property of int

And we can fetch the default value of a particular property easily enough:

let default_property_table_base story =
  let (Object_base base)= Story.object_table_base story in
  Property_defaults_table base

let default_property_value story (Property n) =
  if n < 1 || n > (default_property_table_size story) then
    failwith "invalid index into default property table"
  else
    let (Property_defaults_table base) = default_property_table_base story in
    let addr = Word_address ((base + (n - 1) * default_property_table_entry_size)) in
    Story.read_word story addr

So if an object does not have a property, we can fetch its default value. What if it does have a property?

We already know that the last two bytes in the descriptor for an object is a pointer to a block of property data. We also know already that it begins with a length-prefixed zstring-encoded string giving the “short name” of the object. The following data block logically very straightforward; it consists of:

  • the property number
  • the length of the data for that property
  • that many bytes of data

and then another number / length / data, and so on. A zero byte terminates the table. So we can do a linear search of the table looking for the property we want. Easy, right?

Unfortunately the actual implementation is a little less straightforward.

In version 3 it’s not bad. The length and property number are encoded in a single byte. If zero, then there are no more properties. Otherwise, we treat the top three bits as a three-bit integer, add one, and that’s the length. The bottom five bits are the property number. There are only 31 possible properties, and the length is anything from 1 to 8 bytes.

In version 4 and above though things get weird. The version 4 format is:

  • if bit 7 of the first byte is clear then bit 6 indicates whether the property is one or two bytes, and the bottom six bits give the property number
  • if bit 7 is set then the bottom six bits of the following byte give the length, and if the length is 0 then it is treated as though it were 64, and the high bit of the following byte must be set. We’ll see why next time, though perhaps you can guess.

What a mess.

Once again, to ensure that the type system is helping me prevent bugs, I’m going to give a different type to each kind of integer:

type property_address = Property_address of int
type property_data_address = Property_data of int

Let’s implement some logic for the rules I’ve just laid out:

let decode_property_data story (Property_address address) =
  let b = Story.read_byte story (Byte_address address) in
  if b = 0 then
    (0, 0, invalid_property)
  else if Story.v3_or_lower (Story.version story) then
    (1, (fetch_bits bit7 size3 b) + 1, Property (fetch_bits bit4 size5 b))
  else
    let prop = Property (fetch_bits bit5 size6 b) in
    if fetch_bit bit7 b then
      let b2 = Story.read_byte story (Byte_address (address + 1)) in
      let len = fetch_bits bit5 size6 b2 in
      (2, (if len = 0 then 64 else len), prop)
    else
      (1, (if fetch_bit bit6 b then 2 else 1), prop)

This thing takes the address of a property block and returns a 3-tuple containing first, the length of the header, second, the length of the data, and third, the number of the property.

Next time on FAIC: We’ll use this information to make a list of all the properties for a given object.

Advertisements

15 thoughts on “Shaft room, again

  1. “Unfortunately the actually implementation…”

    It will always feel strange to find a typo (well, ‘mistake presumably resulting from rewriting the sentence at least twice’, but that’s hardly as snappy as ‘typo’) in a native speakers’ writing, when I’m not.

  2. Eric – not germane to this post in particular, but I wanted to let you know your blog behaves strangely: Every time I visit it, I get a first attempt that ends up in a 404 error but the second attempt succeeds. This from two different browsers (IE11 and Edge), two different OSes (Win 7 and 10), two different ISPs. Something is amiss, and I think I’ve ruled out the possibility of it being on my end.

      • It is weird! Just started maybe a month ago, too. I’ll have to find some other WordPress blogs to follow to answer the question – would be extra weird if it’s just your blog.

        • It looks like it has something to do with the redirection from the http site to the https site. If I navigate directly to the https site, it works every time.

          • HTTPS was a red herring. Turns out it’s the first time I hit any WordPress blog on a given day. Very strange.

  3. I’d just like to say thanks a lot for the very enjoyable series. I’m happily reading along in anticipation of completing the Z-machine interpreter and the surprises yet to come. Although I’ve never played Zork or any text adventure, I’m still enjoying your series very much.

  4. Hi Eric. Not germane to this particular post but there is something I’ve been meaning to ask for a while now. Throughout the code you’ve been posting the following pattern is used numerous times:

    let x = .... in
    let y = ... something unrelated to x ... in
    let z = ... something unrelated to x and y ... in
    f(x y z)

    In Haskell I would write the code the following way:

    let x = ....
    y = ... something unrelated to x ...
    z = ... something unrelated to x and y ...
    in
    f(x y z)

    which, to me, conveys better the semantics of what’s going on. Is there a reason why you don’t choose this option or is it simply because Ocaml doesn’t support let-in blocks? (I have absolutely no knowledge whatsoever of Ocaml apart from what I’ve learned from this great series and I’m just starting to learn Haskell).

    • In Haskell, I would write (as in Miranda, where I originally learned it, so probably that is why it looks prettier to me)

      f (x y z)
      where
      x = …. in
      y = … something unrelated to x …
      z = … something unrelated to x and y …

  5. Shaft room, again, again! Hopefully this thread resumes soon. I was really enjoying it!

    Maybe when this is complete we can tackle a NES emulator. 😉

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s