Hey guys, just dropped in to tell you about the free, open-source plugin I created to allow you to play IF with a chat bot right inside Discourse-based forums (of which this is a great example).
I make no excuses. Sure, it might not be the perfect medium in which to play a game, but great for creating a record of your whole journey, a showcase, demo or walkthrough? Combined with Discourse hotkeys (hit ?), interaction can be surprisingly efficient too.
Naturally, this leverages Frotz (Dumb Frotz in fact). Full credits on the target page.
Enjoy and thanks for all the prior work to make this possible, especially the awesome Frotz!
I did see you announce this on the Discourse forum, and it looks pretty cool!
If you do any more work on it you could consider using RemGlk which would make it easier to extract formatting out of the games and to support more formats than just the Z-Machine.
The implementation is basically a chat bot. Discourse is restricted more or less to rendering Markdown and at best BBCode for posts (a sensible restriction to keep things neat and secure).
The interaction between you and a bot with simple plain-text passages was enough to tackle initially. Adding mulitvarious support for a myriad of different response types with different formatting regimes and converting responses to Markdown or even BBCode, was, well, deliberately avoided!
Another reason, was, well, it turns out you can make dumb-frotz in a vanilla build of the Discourse docker container with one line of code. This is critical for the packaging in a Discourse plugin. The install instructions are complex enough without having to worry about installing loads of dependencies.
All that said, it is a nice idea for a future update.
PRās and/or Distinct Forks always welcome of course!
RemGlk doesnāt get you full Z-machine formatting, but it can transmit italic/bold/fixed-width, keystroke input, timed input, and the status window as a character grid.
You then have to think about how to represent all of this in Discourse. This is possible. (Weāve done it for a telnet-style MUD. E.g., accepting ā/key spaceā for a space keystroke, and ā/statusā to redisplay the status window.) Itās extra work, but at that point you have made 95% of games 100% playable.
So youāve already written a RemGlk response to Markdown/BBCode converter? (always helpful to link your code when stating that youāve achieved something btw )
Iāve written a Discord bot in Python that uses RemGlk.
The part that associates RemGlk styles with Markdown formatting is in formatting.py, but of course itās subjective. (And not all styles are used by the Z-machine.).
I guess the source is more complex than if you use Frotz though, because you have to take account of multiple windows and inputs.
(I donāt know how Discourse plugins work, but it may well be possible to just take my bot and modify the file bot.py to communicate with Discourse instead of Discord.)
To manage expectations this is unlikely to be high on my priorities for some time.
However I like the fact that the data is in a structured format. That makes it straightforward to process.
Discourse posts are not structured and have little to no layout. There is but one āwindowā to work with. But some formatting would be good to add like headings and bold text etc.
Andrew, digging into this now as was far too curious. And itās a weekend.
To port this to RemGlk Iāve got to consider how Iām going to handle multiple simultaneous games going on across Discourse and how Iām going to persist state for each individual player and game.
The way I handled this with Frotz was pretty straightforward:
At every step in the game I would check to see if a save game existed using a crude naming convention that involved the Discourse User Id, e.g. gamefilename_userid.sav
I would load it, apply the user input, e.g. āgo northā, update state, send the update to the UI and save the game state to disk.
Iām starting to learn the framework here, but have you any suggestions how i might handle persistent state?
Remember I have a few considerations and constraints:
Multiple people will be playing at the āsame timeā using the same executable and game files.
The processes must be able to completely disengage between game moves, so state has to be persisted somehow before the executable is run again.
Zarf has developed an autosave system, but it requires cooperation between the VMs and the Glk library. Currently Remglk doesnāt support it. GlkOte (the JS version) does however. So you could run it in Node to allow for autosaves. But the console GlkOte port would have to be altered to output the JSON protocol directly. (I should maybe do this myself, Iāve been thinking about it for a while.) Itās all quite messy sorry.
I donāt need autosave. In fact Iād prefer to have control. So long as I can reliably instruct Remglk to save, thatās fine. I can simply not present the prompt to the user and deal with that internally.
If I can avoid the segmentation fault, too, that would be great :).
Remember, Iām having to run everything as a system call to the terminal from Ruby and handling the io stream. And all of that within a time bound server process that has a short lifespan (because its running within a web worker that will be subject to a sensible timeout)
So autosave may not work for me because I need to be able to save to a specific filename so I can pick up where I left off for a specific user/game combination, as per my working, reliable Frotz implementation (which itself copies the approach of restful-Frotz project).
With a discrete http call there is no continuity in process. Its send a command, process it and respond with result. Then its all over. The state saving gives the illusion of continuity.
The server needs to handle multiple requests from different users at random moments.
You can send in that SAVE input and then (immediately) send a specialinput containing the filename. However, this will never be reliable. You donāt know how the game will respond to the input text SAVE. The game could have disabled SAVE, it could be in Russian, it could be in the middle of keystroke input.
āThe VM initiates saveā means a setup where the interpreter runs a turn, gets back to the point of awaiting input (glk_select), and ā as part of that code path ā saves its state somewhere. This can be made to work reliably, but it requires extra integration work between the interpreter and VM, as Dannii said.
(I apologize for leading you down a bit of a garden path by recommending RemGlk! Autosave really is a big headache.)
The concept here is great. I love how the responses are in a useful format that can be processed easily without a load of rubbish string processing.
It has the potential to vastly improve upon the Dumb Frotz implementation.
However, until thereās a reliable way to remotely load and save state quickly and easily at the beginning and end of a move whilst having discretion over the filename I think this will be too limited for this use case.
What is the technical blocker for implementing a simple single command to explicitly save or load state to a specified path and filename in Remglk in one step? ie include the filepath with the save or load command in the json passed to execute this? Why do we need more than a single command to achieve this?
Is it simply that the work to implement that hasnāt been done yet or that there is some architectural impediment that prevents that feature being added?
Iād love to be able to spend the time to learn the library and PR to facilitate that but I donāt think I will have the bandwidth to do that for some time. It would be enough to cover the Discourse plugin side which will be significant in itself.
For now, I suspect, the Frotz implementation will have to suffice. Delighted to revisit if the situation changes.
The problem is that RemGlk doesnāt know that passing āSAVEā in as line input will trigger a glk_fileref_create_by_prompt() call. Ultimately, only the game knows what player input will start the save process.
You need a completely separate path to trigger an out-of-band save. It shouldnāt be triggered by an input at all ā you want it to run every turn, so it should be automatic! However, for technical reasons, it has to do extra work beyond the currently-implemented interpreter save mechanism.
So itās easier in some ways and harder in other ways.
Iāve set this up in Quixe and in the iOS Glk library, because those are the interpreters that Iāve reworked seriously since 2010-ish. RemGlk is behind the times.
Since weāre all stuck at home for a while, I might have time to look at RemGlk this week.
I started a Rust port of RemGlk, figuring it would be easier to add autosaving etc because of its built in JSON serialising functions. But I didnāt get very far, learning new languages is tricky! If you do get to add it to RemGlk Zarf that would be great though.
I will also try to add it to my glkote-term. That will be much less work because most of it is already done, I just have to expose it.
Iād be glad to entertain additions to Dumb Frotz to help with what youāre trying to do. Would a save after ever move do? The saves already are named numerically and increase after every save. This coupled with the fact you can restrict an instance of Frotz to a specific directory you can therefore handle multiple players at once. Each player can have a directory where saves, transcripts, and so on are stored and then deleted when the desire or need arises. When someone makes a move, start Dumb Frotz in restricted mode on that userās directory, restore the latest save, and youāre good. There are probably some minor nits to this, like removing the ability of the player to name their saves and shove this onto the bot wrapper, but on the whole, I think this is doable without much hassle.
plus an option to turn on autosave, whatever that turns out to be. The -R option restricts all reads and writes from the Z-machine to that directory. The -L option tells the Z-machine to immediately load the provided save file after starting the game.
Hi David! State management is already good in Frotz and suitable for the way you need to handle move ātransactionsā in a single call and response.
The only challenge is that it prints to the same console so you have to ignore those characters in the response and hide them from the user as this housekeeping is irrelevant to the users experience and supposed to be seamless. They donāt need to know the system is loading or saving.
If you could have a mode where you could load a particular file and with a switch request it was saved at the end of the move to the same file whilst hiding all the housekeeping text that might suffice for improving the state persistence.
Remember we are starting cold on every move:
We want to:
Load the latest state from gamefilename_userid.sav (Which maintains a unique file per game and userid)
Send the move
Save the state.
Print the response to the Discourse bots post without any housekeeping info
Step 1 and 4 currently involves masking by ignoring a set number of lines for the status updates of loading and saving. It would be good to have a mode to defeat those outputs (useful for many circumstances but not when you want to suspend disbelief and certainly not when you are repeating both of those steps on every move)
The other way in which things might be improved is in formatting of the story output.
Having a āJason Frotzā (Dumbās Bro?) that returns the response in formatted data in Json format similar to Remglk or
āMark Frotzā that sends it in Markdown or
āBob Frotzā that uses BBCode
would be interesting options. A version could send the story in BBCode would be the ultimate for a Discourse implementation. Not sure BBCode has a baseline standard though unlike Markdown?
I would propose āHenrietta (HTML) Frotzā but HTML may be going further than you need and might introduce security concerns.
Perhaps there are more options already available to handle formatting that Iām not aware of?
It would be good to have an option that like Remglk focussed on a computer end point rather than a human but dealing with inline control chars is also an option. Perhaps Dumb Frotz already has the ability to push out formatting codes?
I donāt mind doing some of the formatting work at my end so long as I can rely on consistency of input regime across all games as much as possible.