07 9 / 2012
Interactive Fiction in Perl 6
Plagiarism in the Name of Education
I enjoyed following Carl Mäsak’s adventure in blogging as he built an interactive fiction game in Perl 6. He did it two years in a row, and the implementation (and associated blogs) are quite different in the two versions.
It made me want to try my hand at implementing the game, titled Crypt, to play around with Perl 6 and some ideas about the game engine. I started writing it, and shared the code on the Perl 6 IRC channel (#perl6 on freenode.net), and Carl encouraged me to blog about my experience. I can’t say no, since I’m leeching off his idea in the first place.
So there’s a chunk of code already there to talk about. The code can be found at my p6-if repository on Github. Feel free to fork it there and play along.
I especially liked Mäsak’s idea of an event-driven game, so that’s where I started my implementation. The
IF::Events class provides a central event stream that every other part of the system shares. I use it as a singleton, but don’t bother to enforce that anywhere. Events are created by the
.emit() method on the stream. The
.listen() method registers event listeners.
I think most all of this is pretty straightforward Perl. I’m not using any meta-object programming, introspection, etc. Perl has had first-class subs for a long time, and they’re quite familiar here. But I love how consistent Perl 6 blocks are. An
if block can take arguments, either as a pointy sub (our version of the functional lambda), or implicitly with the
I first implemented
emit()to just call the listeners for the event directly. But that doesn’t work really well when listeners themselves emit events—the new event hijacks the currently running event. To fix this, I use the @.log attribute that tracks all events that have been emitted anyways; keep a pointer to the currently firing event, and when that one’s done, move on to the events later in the queue.
Along with this is a trick to fire a fake EOF event when the event queue is empty. In the interactive fiction context, hooks on EOF can prompt for the next command, run
tick listeners for actions that happen before every prompt, etc.
I’m pretty happy with the basic event system. It’s the first time I’ve written code like this, and it is fun to kick off the whole game by just emitting a ‘begin’ event, and the rest of the whole game is just reacting to that.
Things that I Learned the Hard Way
Working with objects is a real pleasure in Perl 6. Classes can be
my (lexical scope) if I want a little somethin’ even just inside an
if block, for example. There are a few gotchas that I didn’t easily understand, though. First was just getting my mind around
bless, and how those interact during inheritance. I’ll have to write a little post on that, but I think understanding the following sentence from Synopsis 12 is the best way to clear it up:
Other than the candidate object and any autovivifying type objects, all arguments to
blessmust be named arguments, not positional. Hence, the main purpose of custom constructors is to turn positional arguments into named arguments for
Usually it’s best to just write a
submethod BUILD and use the default
new inherited from
Mu. Public attributes will automatically be initialized from matching named parameters to BUILD. Private attributes (with the
! twigil) will not be, you’ll have to write your own
BUILD submethod to do that.
Implicit Named-parameter Slurpy on All Methods
Another gotcha for me was learning that methods always allow any named parameters, with no warning for unused parameters. So if you misspell a named parameter to a method, it’ll likely go unnoticed and be treated as if you hadn’t specified it at all. The reason for this is to enable
callnext and so forth to work along the inheritance chain. That’s how I understand it, anyways. A common example is for submethod BUILD, which is called for any roles and classes that an object composes, all from a single constructor call. One BUILD method may care about a
:foo param, and another may not. Each one binds whatever named parameters it wants. Everything else goes into the implicit
*%_ named slurpy parameter.
I can inspect the contents of
%_, and die with an exception if I don’t like what I see there. But the language doesn’t provide any checks or tools for doing that consistently. It’s too easy to misspell a parameter name and not notice it, and that makes me sad.
Pair Literals versus Named Parameters
A final related gotcha is the syntax and behavior for passing named parameters versus pair literals. In list context, such as assignment to an array or hash variable, all of the following are equivalent, and specify three Pair literals:
When passed into a method or routine, though, the first one is not like the others. The quoted key and fat arrow syntax makes it a Pair literal, even in the Capture of the routine call. But the other two forms are named-parameters. So:
I’ve done my best to write tests first, although it’s very hard. I have probably over-engineered this already, and in some places it feels too heavy and overwrought. I am pretty happy with the tests, though, and they are providing a parallel use case for all the code, so that once the tests are written, it is trivial to get the actual Crypt game working with the new functionality.
I think the testing code deserves its own blog post, so I’ll tie this one up here. So far, I’m pleased in following Mäsak’s lead on using events as a core feature of the design.