Strangely Consistent

Theory, practice, and languages, braided together

July 1 2012 — Hanoi as a black box

So, here's a fun challenge. We're gonna write the Hanoi game first, despite it being in the middle of the adventure game. Then we start writing the adventure game, and when the time comes, we'll plug Hanoi into the adventure game. If we do it right, it should be fairly easy. If we do it wrong, it will hurt immensely.

Think of it as an exercise in design, extensibility, and requirements prediction.

Now, I've done this before. I wrote the Hanoi game basically as a class in the adventure game. This time around it'll be an independent part. The Hanoi game will know nothing about adventurers or inventories or quests. It will be a brain in a vat who happens to be really good at the rules of the one-player game Towers of Hanoi.

Here are our design primitives:

The player interacts with the game by moving disks between three rods which we call 'left', 'middle', and 'right'. We label the disks by size, and refer to them as the tiny, small, medium, large, and huge disk, respectively. For aesthetic reasons, a larger disk may never be positioned on top of a smaller disk. The disks all start out on the left rod; you win by moving all of them to the right rod.

This starts us down the road of understanding the language and the bounded context we're dealing with. There are still many details that need sorting out, though.

Specifically, the adventure game will influence and distort the way the Hanoi game works:

We'll pick an approach to designing the Hanoi game that may be new to some readers. We will not talk about data or algorithms at all. Instead we will consider the game a black box, and only focus on behaviors (methods), successful responses (events), and unsuccessful responses (exceptions). These will completely occupy us for the rest of this post.

Here are the public methods we know we need:

Again, note that a "regular" Hanoi game only deals with the first one, but we know we'll have the adventure game as a client, and that client will need to remove the tiny disk.

Here are the events that represent successful responses:

We see that we have one event each for each of the three public methods. That's quite normal. And then we also have events for emergent change of state, when we "win" and "un-win" the game. The rule of thumb for what we need events for in this style of designing things is that the black Hanoi box emits one event per change it itself needs to keep track of, so if we would lose our black box, we could theoretically get a new one, feed it a list of past events, and it would be able to pick up the work from there. So, one event per "important" change.

Now, those are the successful return values from the black box. Let's also look at the exceptional return values, those that don't change the state and just report back why it won't do what you told it to:

These fall into approximate categories: first, four "out of bounds" type responses, where the input doesn't make sense because there is nothing there.

The next one (X::Hanoi::DiskAlreadyOnARod) is the opposite; trying to introduce duplicate objects into the game. (The adventure game won't make that possible unless hacked; but it feels prudent to prevent it anyway.)

The next one after that (X::Hanoi::ForbiddenDiskRemoval) prevents the Hanoi game from becoming trivial when embedded in an adventure game where you can just take stuff and put them back in any order. I don't know what to call that category. Anti-corruption countermeasure? The "remove a disk" thing is already a concession for the surrounding adventure game, but we box it in and prevent it from going too far.

And the final two are basically upholding the rules of the game. They are "illegal operation" exceptions, while the initial two/three are "illegal argument" excpetions, and the rest are somewhere in the murky hinterland between those endpoints.

Ok, so we have our constituent parts. Tomorrow we'll be writing tests using the behaviors/events/exceptions introduced today.

If you're curious about where we're heading, I already pushed what I'll talk about tomorrow. It's the file in the crypt repo. But I'll be going through the process tomorrow commit by commit. This is a very nice way to write code.