This is a circular room with passages in all directions. Several have unfortunately been blocked by cave-ins.
> go southeast
Last time we implemented the state transition function; it takes in a zchar and a state, and produces a string fragment and new state. We can then modify our earlier byte-dumping debug method from a few episodes before to first, read the three zchars out of the current word, then use each zchar to compute the next string fragment and next state, and finally, if we’re at the end, return the accumulated string. If we’re not at the end then we bump up the word address and do a tail recursion:
let rec read story (Zstring address) = let process_zchar (Zchar zchar) state = (* Omitted; see previous episode *) let rec aux acc state1 current_address = let zchar_bit_size = size5 in let word = Story.read_word story current_address in let is_end = fetch_bit bit15 word in let zchar1 = Zchar (fetch_bits bit14 zchar_bit_size word) in let zchar2 = Zchar (fetch_bits bit9 zchar_bit_size word) in let zchar3 = Zchar (fetch_bits bit4 zchar_bit_size word) in let (text1, state2) = process_zchar zchar1 state1 in let (text2, state3) = process_zchar zchar2 state2 in let (text3, state_next) = process_zchar zchar3 state3 in let new_acc = acc ^ text1 ^ text2 ^ text3 in if is_end then new_acc else aux new_acc state_next (inc_word_addr current_address) in aux "" alphabet0 (Word_address address)
Code for this episode can be found here:
A few things to note:
- the read method is marked as recursive because the nested process_zchar method calls it. This is the easiest way to get a mutually recursive function in OCaml – that is, two functions which do not call themselves, but each calls the other. It’s easiest to have one not-marked-recursive function as a nested function inside the marked-recursive outer function. (There are ways to get “sibling” methods to be mutually recursive but I understand this scenario to be relatively rare in OCaml.)
- I made a helper method fetch_bit that returns a single bit as a Boolean rather than a number.
- Look at how elegant it is to extract the values from a tuple returned by a function. Man, I wish C# had this feature.
- Comments in OCaml are marked by (* *) and they obey parenthesis nesting rules. So if you comment out something that already contains a comment, you don’t have to worry that your comment will end prematurely.
All right, we can now try out our zstring decoder. I happen to know – and how I know will have to be in a future episode – that there is a string at address 0xb106:
let () = let story = Story.load "minizork.z3" in let zstring = Zstring 0xb106 in let text = Zstring.read story zstring in Printf.printf "%s\n" text;
and sure enough:
"Flood Control Dam #3 was constructed in 783 GUE with a grant of 37 million zorkmids from Lord Dimwit Flathead the Excessive. This impressive structure is composed of 370,000 cubic feet of concrete, is 256 feet tall and 193 feet wide. The construction of FCD#3 took 112 days from ground breaking to dedication. It required a work force of 384 slaves, 34 slave drivers, and 12 engineers, 2345 bureaucrats, and nearly one million dead trees. As you start your tour, notice the more interesting features of FCD#3. On your right..."
I think we got it right this time.
And I almost forgot to mention that now we know what those “5” characters were doing at the end of the abbreviation for “the “. That string is four real characters long but there is room for six zchars in a four-byte zstring. The last two zchars are just harmless “alphabet shift” characters that contribute no text to the string, but make up the necessary two extra zchars.
Next time on FAIC: Now that we can decode strings, we can decode the dictionary the game uses to recognize user commands.