Show HN: Advenjure – Interactive fiction on the terminal and in the browser
facundoolano.github.io
facundoolano.github.io
Here's a quick link to the main code file defining the rooms and actions for the example game: https://github.com/facundoolano/advenjure-example/blob/maste.... I doubt I'm the only one that likes looking at this type of example first when looking at an interactive fiction engine, and it may help some people confused by the game.
The history of interactive fiction and Lisp is surprisingly intertwined. To the best of my knowledge, all of the original Infocom games were written in ZIL [0], an idiosyncratic Lisp variant that was meant to be a more portable version of the programming language used to write the original mainframe Zork, MDL—which was apparently designed by some of the same people who worked on Scheme and Common Lisp [1].
There appears to be an active but still incomplete open source compiler for ZIL written in C# [2], and a few Common Lisp experiments, but not many mature projects combining interactive fiction and Lisp.
I hope projects like this one catch on more. I've had trouble grasping other interactive fiction engines, which I often feel are trying to please programmers and writers but hitting an awkward space in between. Lisp's "code is data" idea seems like it could be a more graceful way of hitting that balance.
[0] http://www.ifwiki.org/index.php/ZIL
[1] https://en.wikipedia.org/wiki/MDL_(programming_language)
I still find Inform 7 a deeply fascinating example simply for trying a "Prose is code" approach that tries to favor writers first, somewhat at the expense of sometimes making programmers frustrated.
There was a small table there. The small table contained:
A bottle. The bottle contained a amount of water
@Bedroom [0] > get all
I didn't see that.
@Bedroom [1] > get bottle
I couldn't take that.
@Bedroom [2] > take bottle
I couldn't take that.
@Bedroom [3] > drink water
I didn't know how to do that.
@Bedroom [4] > open bottle
I couldn't open that.
I just wanted a drink. :(I made it so it's fairly simple to extend the supported verbs (you can even add your own verbs as a lib user, without modifying the lib code). It's just that it hadn't been used much before today, so a lot of somewhat obvious verbs are probably missing.
(full disclosure: I had little experience with infocom games myself, I'm more used to graphic adventures, but this was a far more interesting programming project to learn Clojure).
> take all
bottle: taken
table: that's nailed to the floor
bed: that is too heavy to take
wallet: taken
rug: you can't take that!
This (a) automatically lets me pick up anything that I can (and drop the ones that I don't immediately need), but (b) it also tells me that there is no point in trying anything with the table & rug, but the BED might still be an actionable item because of the response. Perhaps I could 'move' instead of 'take' the bed?
In advenjure you can get a similar effect with the autocomplete. If you enter "take " and hit tab you will get suggestions for all the items available in the room.
Either case still requires the game scripter to try to guess what the players will attempt and write specific responses for that, otherwise they'll get the frustrating generic responses.
If you're serious about making a viable interactive fiction engine, you should get really familiar with the Infocom games, the Z-machine engine and its various ports, and the Inform compiler that a latter-day fan made for the Z-machine (and the fan-made games that use it). That's the standard by which your work will be judged.
If you're just fooling around, then do whatever you like. :) Some of your ideas are pretty neat, like tab completion.
Apologies if I sound harshly critical - I am not, and I applaud your efforts here. I am simply framing my comments from many years of playing Infocom games over 30 years ago. I still have Frotz on my iPad these days so I can continue to enjoy bootleg copies of Infocom and other games. Would continue to use Advenjure too if the parsing quirks are worked out so that the game flowed naturally, rather than being a constant fight with the interpreter to try and achieve what I want to do.
> talk to wallet
ME —Hi, wallet.
WALLET —Tsup?
ME —Any cash I can use?
WALLET —Sorry.Good work though on keeping this very simple.
Also, what is the harm in making it so that the data for the game is Clojure source rather than some other format? "Code is data" allows for data being code. It saves having to make a state machine interpreter which you have to extend with exceptions for particular games which will eventually become a poorly implemented lisp in Clojure in the JVM.
It's not a parser based game. It's a state based game. When you have to track complex interactions with massive sets of states, nodes, and interconnections you are writing a state machine.
To truely understand that this is the case you have to see an example. As such I recommend looking at the Fallout 1/2/3/New Vegas quest flow charts that have been generated. Here are a few:
Example of describing story with a state machine: http://i.imgur.com/mAENC.jpg
Example of describing spacial directions with state machine: http://i.imgur.com/qVnEH.png
A Fallout NV mod as another example of story-as-state machine: http://www.thumbsticks.com/wp-content/uploads/2015/02/Fallou...
These should all demonstrate what I mean. All that would need to happen is for the state machine to interface with a player "inventory" and check the states of that before actions are completed. It turns most of the game logic into walking a tree and checking conditions.
> but parser-based games tend to model a virtual world of objects which your character interacts with.
That's not what I'm talking about. I'm talking about modeling the game as a set of actions that can be taken but are blocked by sets of preconditions.
> Some complications are in expressing indirect objects for verbs and in rules which only apply to certain instances of objects in certain situations -- there is always a game which will complicate your simple world model
I'm not saying to create the human interface using a state machine. I'm saying to model the story as a state machine.
> Also, what is the harm in making it so that the data for the game is Clojure source rather than some other format? "Code is data" allows for data being code. It saves having to make a state machine interpreter which you have to extend with exceptions for particular games which will eventually become a poorly implemented lisp in Clojure in the JVM.
There are many reasons to do this. For one, it allows people who aren't programmers to create stories for your engine. To take a look at the interesting side effects of this I'd recommend watching this video: https://www.youtube.com/watch?v=FnFKEWCzVCo
It's again from one of the people involved with the fallout series but he goes into how the scripting side of things enabled story writers to create an entirely different experience. When you give non-programmers a safety-scissors equivalent of a programming environment they can create cool things without bothering you.
Another great side effect is that you've abstracted your problem. You could pull your state machine code out of your game, turn it into a module and interface with it there. Any other code that you need to use a state machine will be able to now interface with it.
This is, I'd say, the most fundamental idea possible in computer science. The idea of abstracting concepts to make the project being created simpler.
Finally, there is the problem of reusability. This game is pretty much locked to it's engine. It can't be reimplemented or updated by people who don't have knowledge of the code base.
On a side note this cannot be configured to run a story safely if it is clojure code that is executed.
Basically, you're removing reusability of this engine, you're not creating a solution to the root problem, and you're making it so the user cannot play other stories in this engine: it's entirely unsafe to redistribute alternative stories since the stories are executed at the same privileged level as the rest of the game.
I agree that there is an underlying state machine, but that's true of any program. I was saying that modeling the state transitions for these sorts of games tends to work out better for authors when using an OO-style model, since the game is simulating a player object interacting with other game objects. Making a system to help organize running certain logic on certain conditions is exactly one of the goals of blah-orientation, where "blah" is object, functional, state machine, or whatever.
I've seen state transition diagrams like the ones you mention for the design of interactive fiction, but only for high-level design. The interactions between objects in these games tends to be too complicated to completely describe that way. For instance, does the player character see anything in a room if they put their only light source inside an opaque closable container? What if the player is inside that container? Puzzles in IF quite often use emergent behaviors of simulated environments, and there are large standard libraries for them in, for instance, Inform 7 or TADS.
Ok, so what I'm gathering is that you have identified the root problem of these sorts of games as being organizing the story as a state machine, making it so that non-engine authors are able to make trusted content, and making it so the engine can be reused.
But I think an adventure game engine is more along the lines of the underlying game engine for Fallout 4 than the code inside Fallout 4 which helps drive the story. For instance, where is the crafting system implemented? Is it really thought of as a giant state transition diagram? I find that difficult to believe.
Again, I believe you are thinking choose-your-own-adventure rather than this genre of parser-based games. CYOA games tend to just disallow disallowed actions, but parser-based games with a modeled world tend to attempt reasonable responses to any (reasonable) action. Whether you agree games should support these sorts of interactions is a different question from whether these games should be implemented as state transition diagrams.
And sure, with this Clojure-based engine arbitrary code can be executed. There are a couple differences between this and Fallout 4. The first is that the cost of making interactive fiction is much less than a game like that, so one or two people can easily produce one, reducing the need to abstract game logic to a separate language. The second is that the engine can be compiled into the game for distribution, just like most other games.
Also, it's conceivable that the game could run in a locked-down virtual machine. The old Infocom games ran in a bytecode interpreter that their in-house Lisp-like language targeted. In the modern day, people still distribute interactive fiction in the same (but somewhat modernized) format.
> This is, I'd say, the most fundamental idea possible in computer science. The idea of abstracting concepts to make the project being created simpler.
I agree that abstraction is powerful and can make things simpler. But it also requires the correct abstraction, and I'm just not convinced that modeling interactive fiction games using state machine diagrams is actually simpler. I found the discussion of ontology problems in the Inform 7 white paper interesting: http://inform7.com/learn/documents/WhitePaper.pdf
http://mentalfloss.com/article/29885/eaten-grue-brief-histor...
Earlier this year I was playing around with recompilation (reading the Emscripten whitepaper) of z-code. The Infocom games used a virtual machine to get their games onto lots of contemporary computers, and also to reasonably implement a virtual memory system to fit the games into the small memory of those computers.
Anyway, it isn't too hard to make a z-machine interpreter (at least version 5 of z-code), so I made one which recompiles the code into javascript: http://tmp.esoteri.casa/zmach/zmach.html
The way z-code works makes it so you can't tell what is code and what is data ahead of time, so it decompiles as you play. "Dump routines" gives you the the code so far (as javascript). Part of the fun is making code which can pause execution to wait for player input. (Turns out the technique is already in some paper from some scheme workshop.)
https://github.com/kmill/zmach is the project.