Collapsing Complexity
I’ve been pondering something lately, as to how complex designs tend to resolve into simpler forms over time. While the human mind does have an upper limit on the amount of complexity it can accept, it’s not that I’m getting stupider as I work on Seasons. It’s not that I’m getting lazier, either; it’s that looking at the code, I can see not only that some functionality just is not necessary (see here), but that what needs to be done can be done in a less convoluted fashion.
The complexity of any coding project generally follows the life-cycle of a star. As a star is born, grows into a red giant, and then collapses into a dwarf star, so a project’s complexity starts low, rises to high, and then abates to some degree or another. I think many programmers do not spend enough time on their projects, and release them before the final stage is reached. As a result bugs and worldview inconsistencies abound.
If you look at many commercial projects, you’ll find the programmers figuratively out of breath and celebrating as they cross the finish line: the code has been delivered by the deadline! Yes, the project works (mostly), the date has been hit (so management gets their bonuses), but the application is simply not as good as it could be; it is still a red giant. When you have to patch or extend the code, or support the project, you end up spending an inordinate amount of time making it work or explaining it because it is still overly complex.
In IF, the support staff is usually the author, so what happens? Either the game is a buggy junk pile and further releases must be created to fix problems, or the author abandons the game and decides not to fix the problems at all. The end result is a game that’s not as good as it could be.
The only thing that allows us to reach that final stage is the passage of time. Apparently the mind needs enough time to analyze the deeper patterns in something fashioned, or repeated exposure yields up the glaring complexities; most likely it is both. Both writing and IF design must be “edited cold” or otherwise they are not mature. I think of the stage of maturation as an aged wine; you can drink wines before their time, but you miss the experience of a good wine. Incidentally, this is why Speed IF almost always produces a horrible game.
So in short, a good motto for IF designers (and programmers as a whole) would be to release no project before its time. Let it reach full bloom and then unfold that beauty to the world.
Permalink Comments Off
Failing By Over-design
I’ve since forgotten where, but I read an article about designing electronic gadgets that plumbed Apple’s design methodology. What they concluded was profound: most electronic gadgets are over-designed. They have too many features and as a result instead of doing one thing (or a few) things well, they do many things with brilliant mediocrity. This state of woe is not exclusive to such gear, of course; for years, mid-fi has suffered from what my dad once termed “buttonitis” — a superabundance of buttons, whose nature tried to convince potential buyers of its functionality, instead of playing music well. With overdesigned products, quality suffers, complexity increases, customer satisfaction goes out the window, and customer loyalty is nonexistent.
IF is the same way. You can create a huge stinking mess of fail by over-designing your game. Take for instance, Seasons. In one of the seasons, there is a barn populated by barn cats. These cats are not critical to the plot, but are just present to add to the realism of the game. Despite that, I was determined to create a class Cat that could automatically create cats as needed, make each cat have a unique description (well, uniquely-selected from an array of descriptions), have the cats do different things every so often, move them around, and so forth. I was even working on a way to have the cats referred to as “a/an” if more than one cat was in a room at the same time; otherwise, the cat would be referred to as “the” cat. Overdesigned? Whoo yeah.
It’s more important to have minor things do their job well as minor things than to spend days of work making them nearly simulationist. Audiences will appreciate nice accents of color (which is what the cats really are supposed to be) but won’t be impressed if scenery can do interesting things when the rest of the game works half-well. When you think about it, you only have so much time to make a game, create the help, distribute it, and gin up interest in it. Do you really want your game to be years late to the market because you had to code an imp statue that could act out lines from every play of Shakespeare’s?
Permalink Comments Off
Patted On the Head Again (Inform 7 Docs)
While working on New Cat, I was curious how to do a one-time room description; in Inform 6, I used the visited attribute. How could I do it in Inform 7?
As it turns out, I’m still not sure. I searched through the documentation provided by the IDE, and the results returned by the search were completely unhelpful. The closest thing I got was a page that talked about how to set the initial descriptions of items, but which (horribly unhelpfully) noted that you can’t do that with a room because a room doesn’t have an initial attribute. And how was this noted? You guessed it — with the usual pat on the head, the “good dog” kind of insulting, demeaning language. Yes, the personality of the writer (whom we all know) is shining though. Freaking thanks. So what are the properties of a room? Are they the same as in I6? If so, how do I manipulate them in I7?
Fifteen minutes of looking didn’t turn up an answer to any of these questions. Ok, fine, whatever. I went out there to find other manuals and apparently no-one else has addressed this issue either, which I find nothing short of amazing.
I guess it’s up to me to write an Inform 6 -> Inform 7 technical guide which explains how to do the things in I7 that you used to do in I6.
Permalink Comments Off
My Very Own “I Told You So” Moment
There’s been discussion on R*IF lately on how Paul Panks didn’t get his “I Told You So” moment, where he would’ve proved all his detractors wrong. It is a shame and not many people who take on the establishment get to have theirs. Something just clicked this evening and I realized that I was one of the fortunate few.
I got mine.
Here’s the backstory.
One of the reasons why the Ifwiki blows chunks is that it’s run by a clique of little Stalinists who try to prescribe their opinion about things as fact. I was one of the early contributors to the IfWiki, long before it really took off. I contributed to various articles and even wrote one (as memory serves). One of the articles I gussied up was about the history, present, and future of commercial IF. I wrote in admittedly-glowing tones that the age of commercial IF was not dead, as 1893 and Future Boy! had shown. Apparently two commercial entities weren’t enough. For daring to go against what the little Stalinists had decreed was truth immutable, my edits were flayed, I was personally attacked, and eventually, they took out what I wrote on the basis of majority vote.
Textfyre has released a commercial game. That makes three commercial games, put out by three separate commercial companies!
I TOLD YOU SO..
You can’t bury it any longer, guys. Commercial IF is resurgent, and long live all those who will make it glorious. Finally, on a personal note, there’s an awful lot of peace in being vindicated.
Permalink Comments Off
TADS 3 Sucks Like a Black Hole
Until recently, I was in a TADS workshop. Then I actually tried to install TADS. Doing so was so inordinately frustrating that now I am a former member of the workshop.
The warning bell was going off when I visited the TADS 3 site. No IDE for Macs except for one in pre-beta? Hmm. Ok. I went and downloaded that, only to find that it required a G4 or G5, which excluded my trusty Pismo — even though I was running the correct OS (10.4). Just for grins, I tried to run it anyways. No dice.
Next, I tried scouring through the IF archive (which STILL has no search — yeah, I know), and there I found a discontinued, unsupported IDE for OS 9. Cool! I downloaded that and tried to compile the first program in the Getting Started manual. Of course I updated to the latest libraries, like any good geek would, but no amount of inserting the latest 3.0.18 adv3 libraries would work. So then I tried 3.0.8 libraries. They too failed. So then I reinstalled the IDE, hoping to get down to a level that I could compile a ten-line program. THAT didn’t work either.
The second line of the program was #charset, but there’s no matching charset in the libraries. Somehow I find out that the IDE doesn’t support charsets. Fine. I commented it out, only to be hit with a different set of 70+ errors.
Then I go to R*IF and look around. No-one has any binaries for the Mac (OS 9 or X) that they’re sharing. Even years ago, it was apparently a well-known fact that developing TADS 3 on the Mac involves compiling FrobTads from the source, which eliminates all graphics, sounds, and movies from the output. At this moment, I’m staring at the screen in irony.
The reason to code in TADS is to take advantage of its multimedia capabilities, but in order to develop a TADS 3 game on the Mac requires you to basically forgo that. And the reason why you’d use TADS 3 instead of TADS 2 is that you don’t have to hack the libraries extensively to do what you want.
TADS 3 sucks like a black hole.
Puzzles: The MRS Method
While working on Seasons, I realized that I needed a way to measure several different aspects of a puzzle to determine whether it worked or didn’t. I ended up with a new way to determine whether puzzles work for any game: the MRS method.
First, the MRS method is a mnemonic for Momentum, Resonance, and Satisfaction. Each puzzle can be measured along this tripartite axis (think of x, y, and z for the geometrically inclined). I’ve chosen to use a ten point axis for each of these measurements, but of course, there’s nothing that restricts anyone else to that.
Momentum measures how much the puzzle advances the plot. Does it open up a new area, reveal some clue to the murder, or somehow move the overall plot of the story forward? Puzzles with a high momentum rating both settle something and leave the players unsettled for the next part. That is to say, metaphorically they close one door behind them and open a new door before them.
Resonance measures how well the puzzle fits into the themes, atmosphere, and other non-plot aspects of the story. If a puzzle feels “tacked-on” or exists for the sake of giving the player points, it is missing resonance.
Satisfaction measures how satisfied the player can reasonably be assumed to be, given the effort put forth into solving the puzzle and its payoff. You can construct the most elaborate puzzle, but if it is nearly impossible to solve and the player is not rewarded for it, they will still hate the puzzle.
A good puzzle will score high on each one of these three measurements, and the more puzzles that score highly in your game, the better the game tends to be. Of course, puzzles are not the whole game, but that’s another post for another day.
Permalink Comments Off
A New Development Methodology
Maybe this is news to no-one but me, but it still represents a breakthrough in efficient development of IF. (To recap, the constant war against the independent developer consists of sloth, lack of community, lack of organization, and difficulty in measuring progress). What I’ve discovered is this: coding the puzzles without worrying about the text allows you to map out the plot of the game and to make sure that the pieces are fitting together. Doing this allows you to really develop at a quicker pace, because instead of gnashing your teeth over the perfect spot of prose, you can say, “Ok, everything WORKS, but the words aren’t there yet.” Getting the words right is usually a tougher task for me, but sheer logic, putting the pieces together — that is not as difficult.
Maybe I’m deformed in that I see no great beauty in logic, or derive any intense emotions from the way things fit together. I have a threshhold that the puzzles must pass, but that’s it. They must make sense, they must fit the level of difficulty, they must be well-clued, and they must be fair to the player. But once they’ve cleared that admittedly non-trivial hurdle, then it’s relatively easy to simply grind through all their ramifications, with occasional touchbacks to the overall plot, theme, and characters to make sure that it all works together.
The short and sweet version: code the puzzles first and worry about the prose later.
Permalink Comments Off
Types of Dialog
Not all dialog in IF follows the same structure, as each discussion does not serve the same goal, just like in real life. Some conversations are for fulfilling social needs (relieving boredom, establishing social harmony, making friends). Others are for obtaining information, or obtaining goods or services. Still others are initiated by people besides yourself to get you to do something or give something. Yet, in IF, usually conversations are one-size-fits-all and cut right to the chase. Either you need something or the NPC needs something, without even a “Hi” or “Bye” mixed in.
It’s important to keep in mind the motivations of the NPC when planning your conversations. Of course the NPC exists for a specific game purpose, but the NPC brings his or her own personality and preferences to play. Say that you have an NPC that serves largely to explain some of the strange goings-on in the mysterious mansion. He could be a historian of manses in the South with unrequited love for Vicky, a manor-owner’s daughter. When the player interacts with this NPC, he dispenses background information readily, but requires you to find out more information about Vicky before he reveals the secrets of her father’s palatial residence.
Not all NPCs should dispense whatever the player wants to know, or do whatever the player needs to have done at the drop of a hat. They must be convinced, either through ask/tell actions, or displaying of objects (say, an incriminating photo — which is an idea I got from some old RAIF posting), or by the PC befriending them, like bringing a blackberry cobbler to a hungry recluse.
This is something I’ve learned while designing the characters for Seasons — each serves a game purpose, and each has his or her own motivations, which makes fulfilling the game purpose a challenge for the player.
Permalink Comments Off
The Mortification of Conversation
If you can design you game without conversation, do it. Nothing has caused me more grief than designing a realistic method of conversation. It’s not really the concepts that I’ve had to wrestle with, but rather the implementation. It’s been one of those bad days extended over several months. You know those days, that after you’ve broken your arm, been dumped, lost your keys, and got in a wreck, it finally looks like it’s stopped raining — and then you get hit by lightning.
I’m using the ORLibrary, and it’s pretty darn amazing for what it lets you do. Conversation pools, related topics, NPCs that can talk on their own — heady stuff. I’ve spent a long while learning how to speak its language and I’ve made it to the advanced tourist level, where I’m going into restaurants outside of the major cities and ordering local cuisine.
Anyhow, I thought I’d share a few code samples to show you what a few good topics look like if you’re using the various NPC conversational modules and Inform 6.
class topic_c class ORKnowledgeWeb;
!-- For talking to James topics
topic_c pctopics_c
with
KnownBy manexp,
context cbman;
!-- James' topics talking to player
topic_c cbtopics
with
KnownBy cbman,
context manexp;
What we’ve done up above is create topic pools for all the topics the player will use to talk to James, and for all the topics that James will use to talk to the player (James is cbman; the player is manexp). This is a good thing, because if you ever need to forbid a character from talking about a topic, you can simply move it out of that character’s pool. On a related note, say that you have a topic that needs to be discussed by an NPC, but not until certain other things happen first. You keep this topic in some other location and move it into the NPC’s pool at that time.
Here’s how individual topics that the player can talk about look:
topic_c names_c "names" cbtopics
with
name 'name' 'his' 'himself' 'man',
query "~Who are you?~ you ask.",
topicinformation [;
!-- Prevents PC from talking about topic he just asked about.
if ((action == ##AskTopic) && (manexp.prevact == action))
move who_p to hldclz;
print "He looks you up and down and extends a hand.
~The name is James. Yours?~";
],
initiatable false,
RelatedTopics nam_p;
topic_c who_p "who" pctopics_c
with
name 'who',
topicinformation "~Who are you?~ you ask.",
RelatedTopics names_c;
If that looks complex, don’t worry. It is.
The way conversation works in the ORLibrary is that you can either Tell an NPC <topic>, or you can Ask an NPC <topic>. I’ve rejiggered it so that the Talk command tells the NPC a topic that the PC currently knows. This saves the player from having to think, “What do I say now?” However, that still leaves open Ask. Hence, the complexity.
First, note the multiplicity of names for the names_c topic, known by the NPC James. Having many names for a topic is critical for the asking to work well. In this example, “Ask man about his name”, “Ask man about himself” and “Ask man about man” all cause this topic to be chosen. Once this topic is chosen, its query (what the PC actually says to the NPC) is printed.
The topicinformation is the response of the NPC, the main text that you want to display. Like any other property, it can be a text string or a routine. Here, I needed to make it a routine to handle the fact that the player could get to this topic through two ways: Talking or Asking. That is to say, the player could type:
Ask man about his name
Talk to man
And this topic could display either way. The first way directly calls this topic; the second finds a topic that the PC currently knows (which could very well be who_p, and that in turn causes the NPC to respond with names_c).
I needed to make sure that if the player asks the man his name, that this topic doesn’t hang around for the player to stumble across through ordinary Talking interaction. Thus, the comparison line:
if ((action == ##AskTopic) && (manexp.prevact == action)) move who_p to hldclz;
If the PC is asking the NPC this question, then the who_p topic must be moved out of the PC’s topic pool. (Otherwise, you have the embarrassing situation where the PC asks a direct question and gets the NPC’s response, and then the PC can Talk to the NPC and the same topic displays.) Hldclz is a generic hidden room with no entrances and no exits that I’ve used to store objects no longer used by the game.
Lastly, the topic’s print statement fires no matter how the topic was reached.
Of course, this is just the beginning. Sometimes you’ll need a topic to change based on what the player has done or said previously. Say if the player has broken his arm climbing a tree, you’d want to change the response that an NPC says about climbing the tree for a better view. If you brain hasn’t exploded yet, hang around. I’ll put up a more involved example eventually that will definitely cause cranial detonation.
Permalink Comments Off