Five Days with Inform 7: a retrospective review

Many years ago, I7 came out, and in those early years, I got to test it, I think mostly as part of my time with ‘IF Whispers’ games. At some point during that time, someone asked me if I found I7’s syntax intuitive, because they found it difficult. I said, “Yeah, because any time I try something that doesn’t work, I file it as a bug report, so by now, it matches my personal intuition.” I don’t know how precisely true that was, but I do definitely remember getting a bunch of better error messages in there, handling the sorts of incorrect constructs I used to throw at it all the time. Never underestimate the power of a good error message that gets you back on track.

But it’s been decades since then, and (like python) I7 seems to revel in discarding backward-compatibility at any opportunity, so even the error messages Graham crafted just for me have mostly faded into obscurity, as has my knowledge of the now-defunct syntax. It’s still recognizable, but overall a lot of the details have changed.

A couple years ago, I finally pulled it up again, and started work on a project that I think I first had the idea for around 2008. But I still felt kind of shaky with it, and when the opportunity came up to participate in ‘Iron ChIF’, I jumped at it, partly just to force myself to really grapple with the language, and see if I could find a setup that worked for me.

The environment I landed on was to set up the bulk of the content of the game as ‘Extensions’, in a separate folder. Those source code files I would create and edit in Visual Studio Code, using the “Inform 7” extension by ‘Natrium729’. In another window, I’d have the I7 Windows IDE open, and that’s where I’d edit the (minimal) story.ni file. While coding and recompiling, I would edit in VSC, switch to I7, click in story.ni, hit ‘space’ and then ‘delete’ somewhere to let it know things had changed, then hit ‘replay’.

In the meantime, I uploaded the whole shebang to github. Ideally github commits are ‘I fixed/added this one thing’. This did not happen in the slightest. The first day of coding, I would set an alarm every hour, and when it went off I’d commit what I had. I was really afraid of futzing around accomplishing nothing, and I needed the external pressure of the timer and a public commit to keep on task. As the days progressed, I needed that crutch less, but still tried to keep up at least semi-regularly with commits.

So! How did it go? Here’s my report.

Where Lucian Went Wrong, and More of Lucian’s Greatest Mistakes

By far my biggest stupid problem was misremembering some phrase with a synonym instead of the actual word. Checking if a scene ‘started’ instead of if it ‘began’. Checking if a cyoa node was ‘visited’ instead of ‘previously displayed’. Or ‘previously viewed’ instead of ‘previously displayed’. This happened with my own things as well: I had one scene I called ‘Everything Falls’ and another I called ‘Everything Is Open’. I can’t tell you how many times I checked to see if “Everything is Falling” or “Everything Opens” were happening.

I also caused myself a fair amount of difficulty using constructs I guessed might exist, and do, but don’t do what I thought they would:

  • A [if purple]purple[otherwise]invisible cloak” compiles! It prints ‘purple’! It does not actually print ‘invisible’, because it’s not checking if the thing being described is purple, it checks the concept of the ‘purple’ attribute in a Boolean context. I think.
  • Writing ‘stop;’ in a routine: it compiles! It exits the routine like I thought it should! And I’m on to the next bit. And then with 6 hours to go, I finally realize that this is the cause of umpteen bugs in my code, because it doesn’t just stop the routine I’m running; it just stops the entire structure of the Inform turn sequence!

This was definitely exacerbated by not having enough time to look everything up, but also was a problem in the first place because it’s not nearly as easy to stumble on compiles-but-wrong syntax in traditional programming languages, meaning I’ve grown to rely on ‘check if it compiles’ as a syntax checker. Also exacerbated by the fact that similar shortcuts do do what I want, and if we went back in time to 2008 and gave Graham Nelson the idea, it might well have worked like I thought it would.

This was, of course, mitigated by all the times I tried something else I thought might exist, and it did, and did what I wanted! So, overall, a win. But definitely something to watch out for: I need to be more robust about checking to make sure my guesses about What Will Happen, Do Actually Happen. I will blame the time constraints.

The only other sort of ‘big issue’ I had was that I first experimented on using Carry Out for doors, and it turns out doors have extra steps I didn’t expect. So I was right about what Carry Out does in general, but I was wrong about what they did in that specific context, so that led me to think I just generally had failed to understand the concept. Not terrible; ‘check’ still worked, and it gave me something to ask about later, but a bit of a hiccup.

I also realized that I don’t really know how to use the various debugging verbs, nor how to utilize the ‘Index’ tab. I spent a lot of time trying to figure out why ‘visited’ didn’t work, and only much later did I realize that all I had to do was switch to the Index and look at the defined attributes for cyoa node. When I asked my ‘carry out’ question, someone suggested the ‘>RULES’ command: I tried it, and was utterly unable to parse what I was reading. So I have some work to do there, but I’m not sure if it’ll happen: if I can’t parse the results of RULES, I’m not going to try it more, which means I’m not going to see those results in different contexts, which would let me figure out what was going on. There doesn’t seem to be an easy on-ramp, in other words. But we’ll see.

An Area Where I7 Shines That Surprised Me

This is something that’s totally unique to Inform 7, and is kind of a secret sauce. Almost more than anything else about I7’s ‘natural language’ motif, it allows source code to have emotive meaning. I can go to the source, read that line, and remember what it’s like to be Horatio. It’s not at all the same as it might look in another language:

Horatio.mem_flag = 0;

Now, one might say I could write ‘Horatio.remembered = 0’ just as well, but when I went to go write that bit of code, my intent was simply 'I need a flag to trigger the ‘horatio is wandering around’ scene. But Inform pushed me to write something with semantic depth, and that in turn helped me write the player-facing text that wasn’t code, just by reminding me more of what the emotional tenor of the scene is. It’s surprisingly effective!

The Setup: review

My ‘Extensions’ setup: overall a win! Just being able to use Visual Studio Code with its much better find/replace system was huge. The biggest problem: remembering to hit ‘space/delete’ in my Inform 7 window when going back to hit ‘replay’. Especially when I was somewhere deep in the game, so the game would take a while to re-run the transcript, which would fool my brain into thinking it had re-compiled when it had not. Umpteen times, I would fix/change something, hit Replay, nothing would be changed, I would be flummoxed, and then finally realize I hadn’t hit space/delete so it would Actually Recompile.

I7 repositories: A win, but wow there’s a lot of fluff in there, with the Skein and a billion Index file and I don’t know what all. There’s probably a better way to do that, maybe with a github action to compile just the source, instead of committing both the source and the compilation files to the same repository. Though this way any I7 archaeologist will be able to see the rise and fall of 'course correction’s Skein. If someone has cause to do that.

My code organization: Mixed! I divided things up how I thought they might be needed later, and for most of the game’s development, it was generally true that if I was working on a single thing, I would probably be mostly messing around in one area in a single file. However, as the game progressed, this started falling apart more and more: stuff for the finale ended up being spread out over (I think) four different files. However, I was saved here by VSC’s global search. I had that window pinned to the lower left of my screen (under the list of files) and used it constantly. With that, if there was a phrase or object I used in different bits of the code, I could jump from one file to another seamlessly. Would 100% recommend.

The takeaway

While I was excited to dive in, I worried that Inform 7’s odd syntax might overwhelm me, especially after so many years of programming in more traditional environments. And as noted, it certainly had its foibles. But overall, it absolutely worked as intended: it helped focus my ideas and translate them into a playable game. In five days!

For any of the ‘pain points’ above: does anyone have a system that helps mitigate them? Or other setups that work for you?

21 Likes

I have a rules debugging variant that improves reading a little by indenting appropriately, but it’s still a firehose. Consulting it while also looking at the detailed action-processing rules and the Standard Rules’ action rules and the Standard Rules semantics is helpful. But all this gets one to a slightly less diffiult on-ramp, not an easy one.

For git repos, you could use a .gitignore to ignore

  • project.inform/Build
  • project.inform/Index
  • project.inform/Skein.skein [but if you’ve curated it and are using it, it should be in source control]
  • project.inform/manifest.plist
  • project.inform/Release.blurb
  • project.inform/Metadata.iFiction
  • project.materials/Release

Further discussion here: Git versioning and inform 7

4 Likes

Huh? I’m confused at the issue here. The say phrase if purple does indeed check if the item described is purple, so I’m not sure what you got wrong in your example? In general, evaluating an adjective in a boolean context is equivalent to item described is X.

I do notice that there’s no [end if], so it would print either “purple” or “invisible cloak”, which I doubt was the intent.

Wait, is this really true!? I wrote in my Inform 7 reference manual that this is allowed in phrases that don’t return a value, but if what you say here is correct, it means there’s actually no supported way to return early from such a phrase?

Surely also by the difficulty of actually looking up stuff in Inform 7.

I’ve yet to put any of my Inform 7 work in a git repository, but I think if I did, I’d probably only commit the story.ni and any local extension files. Plus stuff in the Materials folder, if necessary. But I wouldn’t commit the index, and I think I probably wouldn’t commit the Skein either, though I’m not sure on that one. (It can theoretically be used to show your game’s “solution” and is a good way to notice that you accidentally changed something you shouldn’t have, but as of right now it doesn’t really do its job very well, and it also massively slows your game down just keeping it updated, so it’s probably easier to not use it most of the time.)

2 Likes

I also code in an external editor (Neovim for me), and I just use Inform’s command line interface to compile instead of the Inform IDE. VS Code has a plugin that can compile the project for you (I haven’t used it, I just made a shell script). Of course, if you like to test the game in the IDE with the Index and everything (I’ve so far had Vorple as a target so I want to test in a browser anyway), this is less useful, but I thought I’d mention it anyway.

What you’re talking about isn’t syntax, though, it’s semantics! Those problems exist in traditional languages too (like falling through a switch/case in C because you forgot to break, or mixing up = and ==), but usually modern compilers are nice enough to warn you about it.

4 Likes

It’s true in an “every turn” rule. This is surprising, yes.

4 Likes

That works in a description property, but not universally because “the item described” is not always set.

3 Likes

Thanks for the great write-up! I’m really enjoying the info being shared in here.

I’m surprised to learn this! I’ve seen behaviour similar to what Lucian is describing when I forgot to specify a noun in my own projects, and I thought that was always invalid!

Testing it now, it looks much more flexible than I expected—I think it’s even able to identify the noun targeted by an action (e.g. in Check or Carry out rules)? Very cool to know!

Though I figure it’s probably good practice to always specify a noun anyway…

Hey, I know this guy! :grin:

As mentionned above, there’s a button to compile projects directly from VS Code, and there’s the “IF Player” extension to be able to test directly in VS Code (with some features missing like the skein) but if you’re on Windows, it might not work (my limited testing says it should, but I’ve got reports saying it doesn’t…).

And some things, like underlining errors, might not work perfectly with Inform 10. Not really sure.

(Also, I did see your bug report, just didn’t have time to take a look yet.)

4 Likes

Well, I mean, yes. All of my questions I would type into Google, and find the answer on this very forum. Usually I would try to look things up in the IDE/documentation first, but it wouldn’t really help.

Now that I can go back and test things, I do see it working as I thought it would in item descriptions and printed names. So we at least knew why I thought it would work! But I know things weren’t working somewhere, and being explicit worked, so that trained me to never use the ‘if purple’ construct, especially when I didn’t have time to test everything.

1 Like

The documentation says that this use of stop is valid. It also says that using stop in an action rule is equivalent to stop the action.

(Both stop and stop the action compile to rtrue;, which in a routine compiled from a rule means to stop the rulebook.)

Woo! Thank you so much! It worked beautifully. As reported, I did have weird problems with tabs sometimes turning into spaces, but in general, it just made everything work smoothly and intuitively.

Who is this Lucian Person Anyway?

2 Likes

Well, yes, it only works when the item described is set. In practice, that means you can use it in any text property on an object, and that’s about it. I guess it could be used in certain kinds of To say phrases, if they themselves are intended to be used only in a text property on an object.

Oh, good point! I think a better way to explain it is as follows:

Using an adjective alone, without a head noun, means that the head noun is implicitly set to thing. In other words, the description purple is equivalent to a purple thing or something purple. This applies in descriptions anywhere.

When a description is used on its own as a condition, it refers to the item described. I think this works for arbitrarily complicated descriptions, but it’s most commonly seen with isolated adjectives. In this context, what you’re seeing is that if DESCRIPTION is equivalent to if item described is DESCRIPTION.

So, in fact, both rules apply in the case of if purple, which is interpreted as if the item described is a purple thing.

In short: I don’t think it’s “good practice” to just always specify a noun.

For normal adjectives that sound like an adjective, I’d normally include a noun in action preambles; but for weird adjectives, I might omit the noun. For example:

John is a person. John can be John-working, John-sleeping, or John-travelling.

Instead of doing something to John-working, say "He seems busy. Best not disturb him."

In descriptions, I’ll usually omit the noun for brevity.

You mean when using it like this, right:

Every turn:
	stop.

But not when using it like this, right?

To move along:
	stop.

Every turn:
	move along.

This seems to imply that I misinterpreted what the original poster said…

1 Like

The best use I’ve found for it is when you have a command that is getting interrupted or stopped or firing and you don’t know why. If you type >RULES ON and perform the action, you’ll get the explosion of rules, which is every rule under consideration at the moment, but occasionally you’ll see output happen right after a specific rule which can point you to the right one.

This is where it really helps if you name your rules. It might seem redundant and pointless to include things like

Check running (This is the can't run in banana-shoes rule):

But the name you set is the one you’ll see in the Rules output and the >ACTIONS output; otherwise you get a stack of potentially identical “Check running” rules.

If you at least name your new source text rules, that helps you tell them apart from the standard library rules.

6 Likes

Yup, just tried this, and the first blocks any other ‘every turn’ from running, but the second does not.

What I learned from using ‘stop’ in an ‘every turn’ rule was ‘do not use ‘stop’ in Inform 7’.

1 Like

Okay. I think the correct use in Every turn would’ve been continue or continue the action? There’s also make no decision, not sure if that makes sense here.

1 Like

“Continue the action”, “continue the activity”, and “make no decision” are exactly equivalent—they’re all defined as meaning rfalse; and have the effect of ending the current phrase or rule without ending the rulebook. They just have different names to clarify what that means in different contexts.

Similarly, “stop”, “stop the action”, and “instead” attached to another statement are exactly equivalent: they’re defined as meaning rtrue; and have the effect of ending the current phrase or rulebook (not just the rule!) without recording an outcome.

Finally, “rule fails”, “rule succeeds”, and “rule succeeds with result ___” record an outcome and then rtrue;. For most rulebooks, the outcome (success, failure, or something else) doesn’t matter, so it’s equivalent to “stop” and “stop the action”.

7 Likes

OK, things are making more sense! Thanks everyone for explaining.

Mucking about some more, it looks to me like adding ‘instead’ to the end of a line also breaks out of the routine, seemingly with an ‘rtrue’, meaning that if you put it in an ‘every turn’, you no longer run any other 'every turn’s:

Testing is a room.

scene1 is a scene.  scene1 begins when play begins.

Every turn during scene1:
	say "scene 1";
	if the hunting cloak is purple:
		now the hunting cloak is invisible instead;
		[continue the activity;]
	now the hunting cloak is purple;
	
scene2 is a scene.  Scene2 begins when play begins.

Every turn during scene2:
	say "scene 2";
	
The hunting cloak is a wearable in Testing.  
The hunting cloak can be purple or invisible.  
The hunting cloak is purple.  
The description is "A [if purple]purple[otherwise]invisible[end if] hunting cloak."  
The printed name of the hunting cloak is "[if purple]purple[otherwise]invisible[end if] hunting cloak".

gives us:

>x cloak
A invisible hunting cloak.

scene 1
scene 2
>x cloak
A purple hunting cloak.

scene 1
>x cloak
A invisible hunting cloak.

namely, scene 2 doesn’t run when the hunting cloak turns invisible.

So: things that will stop other every turns:

  • stop
  • stop the action
  • rule fails
  • rule succeeds
  • instead

Things that will not stop other every turns:

  • continue the action/activity
  • make no decision
2 Likes

Correct! I forgot that one—“instead” after a statement is equivalent to “stop”. I’ll add it to the list.