Discourse-Frotz

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!

8 Likes

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.

2 Likes

Thanks Dannii

When you say formatting, do you mean layout and text colours and styles?

The reason I went with Dumb Frotz was for multiple reasons.

Firstly, its a household name practically. Thereā€™s even an iOS app to promote the brand.

Itā€™s also clearly well-maintained and well-supported in the community.

Soooo much content!

The fourth, and significant reason, is probably precisely the same reason why GitHub - tlef/restful-frotz: A restful interface to interact with Frotz use it and that is: simplicity.

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!

1 Like

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.

2 Likes

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 :slight_smile: )

If not, are you aware of one?

Itā€™s not markdown. Let me look around for itā€¦

1 Like

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.)

Anyway, feel free to look at it!

2 Likes

Thanks guys!

All useful sources for inspiration and snippets.

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:

  1. Multiple people will be playing at the ā€œsame timeā€ using the same executable and game files.
  2. The processes must be able to completely disengage between game moves, so state has to be persisted somehow before the executable is run again.

Thoughts?

Is there a state save system?

Thanks for your time in advance!

OK, making some progress in reverse engineering it but might seem some help.

Is it possible to send a single command for a save without having to handle a prompt?

This is because I cannot give the user any discretion when we are dealing with a remote server and a bot.

I have got this far:

My attempt:

{ "type":"line", "gen":2, "window":16, "value":"save", "filename":"mysave.sav" }

The response:

{"type":"update", "gen":3,
 "content":[
 {"id":16, "text": [
  {"append":true, "content":[{ "style":"input", "text":"save"}]},
  {}
 ] }
 ],
 "input":[
 ],
 "specialinput":
  { "type":"fileref_prompt", "filemode":"write", "filetype":"save",
Segmentation fault (core dumped)

I guess I can handle this internally without exposing it, but the segmentation fault is something I canā€™t handle! :slight_smile:

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.

1 Like

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 :).

Weā€™ve found that itā€™s better to let the VMs initiate it, they know exactly when and how to save and restore the state.

What do you mean by that exactly?

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.

The above prototyping is using glulxe btw.

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.)

2 Likes

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.

1 Like

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.

2 Likes

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.

1 Like

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.

The invocation would look something like this:

$latest_save = get_latest_save($playerdir)
run (shell dfrotz -R $playerdir -L $latest_save $game`)
...

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.

1 Like

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:

  1. Load the latest state from gamefilename_userid.sav (Which maintains a unique file per game and userid)
  2. Send the move
  3. Save the state.
  4. 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.