I7 - Menus table

So I’m new to Inform and writing IF in general, and I’ve run into what looks to be a fairly newbie-ish problem, so bear with me. :slight_smile:

What I want in this IF is to include 4 separate short stories. Emily Shorts “Menus” seems to be the functionality that I’m looking for, but I’m having trouble with the tables not compiling. Probably a syntax error somewhere (I’ve run into plenty in I7. Ironic that I’m more likely to run into syntax errors when the code is made to resemble/is easily confused with actual English :stuck_out_tongue: ), but the error is a genric “I don’t understand this at all” one, so I’m not sure what I’ve done wrong.

Include Menus by Emily Short.
Include Player Experience Upgrade by Aaron Reed.
Include Custom Library Messages by Ron Newcomb.

Book 1 - In the Beginning

Chapter 1 - At the beginning of play

Use American dialect and serial comma. 

Table of Stories
title			subtable			description			toggle
"Story of Oma"	--					--					Start Play 1.
"Story of Bob"	--					--					Start Play 3.

When play begins:
	say "Which story would you like to read?[paragraph break]";
	now the current menu is the Table of Stories. 

To start play (story - a number):
	clear the screen.

The immediate problem is that you have punctuation at the end of table rows. You should remove the full stops from the line ends.

After fixing that the code still won’t compile, because “Start Play X” is not a rule but a phrase. Only rules can be used in the toggle column. Change the phrases to rules and it should work:

This is the start play 1 rule:
clear the screen.

This is the start play 3 rule:
clear the screen.

Table of Stories
title subtable description toggle
“Story of Oma” – -- start play 1 rule
“Story of Bob” – -- start play 3 rule[/code]

Ah, that makes sense, thanks. I did try using that syntax at one point, but I had the period so I still got the error. A related question, can you not pass parameters in this situation?


This is the start play (x - a number) rule:

Not that I know of, but then again that extension is not really designed for this kind of thing. As the documentation says:

I was about to go look up the “Questions” extension to see if it would be more appropriate to this situation, but it looks like inform7.com/ is down right now.

<class ‘django.core.exceptions.ImproperlyConfigured’> Python 2.5: /home/informer7/local/bin/python
Tue Oct 18 10:56:58 2011

A problem occurred in a Python script. Here is the sequence of function calls leading up to the error, in the order they occurred.
/home/informer7/inform7.com/fcgi.py in run(self=<fcgi.Request object at 0x958c50>)
578 “”“Runs the handler, flushes the streams, and ends the request.”""
579 try:
580 protocolStatus, appStatus = self.server.handler(self)
581 except:
582 traceback.print_exc(file=self.stderr)
protocolStatus undefined, appStatus undefined, self = <fcgi.Request object at 0x958c50>, self.server = <fcgi.WSGIServer object at 0x958b50>, self.server.handler = <bound method WSGIServer.handler of <fcgi.WSGIServer object at 0x958b50>>
/home/informer7/inform7.com/fcgi.py in handler(self=<fcgi.WSGIServer object at 0x958b50>, req=<fcgi.Request object at 0x958c50>)
1264 try:
1265 try:
1266 result = self.application(environ, start_response)
1267 try:
1268 for data in result:
result = None, self = <fcgi.WSGIServer object at 0x958b50>, self.application = <django.core.handlers.wsgi.WSGIHandler object at 0x958790>, environ = {‘DH_USER’: ‘informer7’, ‘DOCUMENT_ROOT’: ‘/home/informer7/inform7.com’, ‘GATEWAY_INTERFACE’: ‘CGI/1.1’, ‘HTTP_ACCEPT’: ‘text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8’, ‘HTTP_ACCEPT_CHARSET’: ‘ISO-8859-1,utf-8;q=0.7,;q=0.7’, ‘HTTP_ACCEPT_ENCODING’: ‘gzip,deflate’, ‘HTTP_ACCEPT_LANGUAGE’: ‘en-us,en;q=0.5’, ‘HTTP_CACHE_CONTROL’: ‘max-age=0’, ‘HTTP_CONNECTION’: ‘close’, ‘HTTP_HOST’: ‘inform7.com’, …}, start_response = <function start_response at 0x2aae5d1e848>
/home/informer7/local/lib/python2.5/site-packages/Django-1.0.2_final-py2.5.egg/django/core/handlers/wsgi.py in call(self=<django.core.handlers.wsgi.WSGIHandler object at 0x958790>, environ={‘DH_USER’: ‘informer7’, ‘DOCUMENT_ROOT’: ‘/home/informer7/inform7.com’, ‘GATEWAY_INTERFACE’: ‘CGI/1.1’, ‘HTTP_ACCEPT’: 'text/html,application/xhtml+xml,application/xml;q=0.9,
/;q=0.8’, ‘HTTP_ACCEPT_CHARSET’: 'ISO-8859-1,utf-8;q=0.7,;q=0.7’, ‘HTTP_ACCEPT_ENCODING’: ‘gzip,deflate’, ‘HTTP_ACCEPT_LANGUAGE’: ‘en-us,en;q=0.5’, ‘HTTP_CACHE_CONTROL’: ‘max-age=0’, ‘HTTP_CONNECTION’: ‘close’, ‘HTTP_HOST’: ‘inform7.com’, …}, start_response=<function start_response at 0x2aae5d1e848>)
237 response = http.HttpResponseBadRequest()
238 else:
239 response = self.get_response(request)
241 # Apply response middleware
response undefined, self = <django.core.handlers.wsgi.WSGIHandler object at 0x958790>, self.get_response = <bound method WSGIHandler.get_response of <djang…re.handlers.wsgi.WSGIHandler object at 0x958790>>, request = <WSGIRequest GET:<QueryDict: {}>, POST:<QueryDic…gi.url_scheme’: ‘http’, ‘wsgi.version’: (1, 0)}>
/home/informer7/local/lib/python2.5/site-packages/Django-1.0.2_final-py2.5.egg/django/core/handlers/base.py in get_response(self=<django.core.handlers.wsgi.WSGIHandler object at 0x958790>, request=<WSGIRequest GET:<QueryDict: {}>, POST:<QueryDic…gi.url_scheme’: ‘http’, ‘wsgi.version’: (1, 0)}>)
65 # Apply request middleware
66 for middleware_method in self._request_middleware:
67 response = middleware_method(request)
68 if response:
69 return response
response = None, middleware_method = <bound method SessionMiddleware.process_request …middleware.SessionMiddleware object at 0xa4c710>>, request = <WSGIRequest GET:<QueryDict: {}>, POST:<QueryDic…gi.url_scheme’: ‘http’, ‘wsgi.version’: (1, 0)}>
/home/informer7/local/lib/python2.5/site-packages/Django-1.0.2_final-py2.5.egg/django/contrib/sessions/middleware.py in process_request(self=<django.contrib.sessions.middleware.SessionMiddleware object at 0xa4c710>, request=<WSGIRequest GET:<QueryDict: {}>, POST:<QueryDic…gi.url_scheme’: ‘http’, ‘wsgi.version’: (1, 0)}>)
7 class SessionMiddleware(object):
8 def process_request(self, request):
9 engine = import(settings.SESSION_ENGINE, {}, {}, [’’])
10 session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME, None)
11 request.session = engine.SessionStore(session_key)
engine undefined, builtin import = , global settings = <django.conf.LazySettings object at 0x2aae2980810>, settings.SESSION_ENGINE = ‘django.contrib.sessions.backends.db’
/home/informer7/local/lib/python2.5/site-packages/Django-1.0.2_final-py2.5.egg/django/contrib/sessions/backends/db.py in ()
2 from django.contrib.sessions.models import Session
3 from django.contrib.sessions.backends.base import SessionBase, CreateError
4 from django.core.exceptions import SuspiciousOperation
5 from django.db import IntegrityError, transaction
6 from django.utils.encoding import force_unicode
django undefined, Session undefined
/home/informer7/local/lib/python2.5/site-packages/Django-1.0.2_final-py2.5.egg/django/contrib/sessions/models.py in ()
2 import cPickle as pickle
4 from django.db import models
5 from django.utils.translation import ugettext_lazy as _
6 from django.conf import settings
django undefined, models undefined
/home/informer7/local/lib/python2.5/site-packages/Django-1.0.2_final-py2.5.egg/django/db/init.py in ()
14 # backends that ships with Django, so look there first.
15 _import_path = ‘django.db.backends.’
16 backend = import(’%s%s.base’ % (_import_path, settings.DATABASE_ENGINE), {}, {}, [’’])
17 except ImportError, e:
18 # If the import failed, we might be looking for a database backend
backend undefined, builtin import = , _import_path = None, settings = None, settings.DATABASE_ENGINE undefined
/home/informer7/local/lib/python2.5/site-packages/Django-1.0.2_final-py2.5.egg/django/db/backends/mysql/base.py in ()
11 except ImportError, e:
12 from django.core.exceptions import ImproperlyConfigured
13 raise ImproperlyConfigured(“Error loading MySQLdb module: %s” % e)
15 # We want version (1, 2, 1, ‘final’, 2) or later. We can’t just use
ImproperlyConfigured = None, e = None

<class ‘django.core.exceptions.ImproperlyConfigured’>: Error loading MySQLdb module: libmysqlclient_r.so.15: cannot open shared object file: No such file or directory
args = (‘Error loading MySQLdb module: libmysqlclient_r.s…pen shared object file: No such file or directory’,)
message = ‘Error loading MySQLdb module: libmysqlclient_r.s…pen shared object file: No such file or directory’ [/code][/spoiler]

Keeping in mind Juhana’s point that Menus isn’t really designed for this sort of thing, you could probably hack your original code to work by using a say-phrase under the description column instead of a toggle rule. Something like this:

[code]Include Menus by Emily Short.
Include Player Experience Upgrade by Aaron Reed.
Include Custom Library Messages by Ron Newcomb.

Book 1 - In the Beginning

Chapter 1 - At the beginning of play

Use American dialect and serial comma.

Table of Stories
title subtable description toggle
“Story of Oma” – “[start play 1]” –
“Story of Bob” – “[start play 3]” –

When play begins:
say “Which story would you like to read?[paragraph break]”;
now the current menu is the Table of Stories.

To say start play (story - a number):
clear the screen.[/code]

[Untested, and because of the way I entered this in you probably can’t copy-paste from this post even using the “hit reply and copy from the text box” trick.]

Well I’m wanting to use Menus rather than Questions because, if I understand it correctly, Menus allow you to select the option with arrow keys. I think that might be slightly less intimidating for novices?

Matt, your solution compiles, woo! Appended a “carry out the displaying activity.” to the end of When play begins. Now, however, the program will carry out the function and then loop back to the menu. Is this intended? I’m looking at the docs and I don’t see any way to explicitly close the menu.

Personally, I’d say that simple keystrokes, rather than menus, would be easier. Personally, I’ve always found menus fairly awkward, and of course they feel a bit retrograde today. (They also flicker abominably in Zoom on OS X.)

Note that when I say keystrokes, I mean single keystrokes (“Press A, B, or C”), not standard parser input. Emily Short’s Basic Screen Effects includes support for keystroke input, as does Flexible Windows if you think you might to divide the screen into multiple “windows”.

OT, keystroke input is underused I think. In addition to menus and similar applications, there are other avenues: I put up a demo awhile back for parser-based IF using only keystroke input, which you can see here. Better, Jon Ingold has recreated the parser using keystroke input, which allows for autocomplete and various other niceties. The latest post about that is here, I think.


You know, for the case of a small number of choices like I’ve got, you may be right about that. I’ll see if I can’t implement it.

AaronP, I haven’t actually used Menus myself except to twiddle with other people’s code, but IIRC you can close the menu with a “decrease the menu depth” command that decreases it to zero. Not sure how that would work in this situation (maybe you would need to do it before you started the story?).

Anyway, I think Erik is probably right that this is a good case for keystroke input.

Ok, I’ve come back to this using Erik Temples Glulx Input Loops. Can anyone tell me what’s wrong with this syntax?

To start main menu loop: wait for any key; if keystroke is: -- "1": start play 1; -- "2": start play 2; -- "3": start play 3; -- "4": start play 4; -- otherwise: start main menu loop.

The first error message is as follows, but I’m not sure what it implies. Can you not evaluate indexed text like this?

I think that’s a bug. The keystroke is an indexed text variable, and it’s legal to say

if the keystroke is “1”: …

You can avoid this by checking keystroke-code instead. That’s a number, containing the ASCII code:

	if keystroke-code is:
		-- 49: start play 1;
		-- 50: start play 2;
		-- 51: start play 3;
		-- 52: start play 4;
		-- otherwise: start main menu loop.

Also, don’t call “start main menu loop” in a recursive loop like this. The VM doesn’t have unlimited stack space. Instead, say:

	while 1 is 1:
		if keystroke-code is:
			-- 49: start play 1;
			-- 50: start play 2;
			-- 51: start play 3;
			-- 52: start play 4;
			-- otherwise: next;

Actually, it may be just a poor error message, not a bug. The “is – case, – case” syntax isn’t supposed to be as powerful as a general “is”.

Hmm. This works, except that using “next” seems to put it in an infinite loop if I give it an invalid input. Or I think that’s what’s happening; the effect is that the game freezes. Using a recursive function works as intended. How likely am I to run into memory issues in Glulx like this?

Sorry – I left out the “wait for any key”. That needs to be inside the loop.

Yeesh, no worries. I should have noticed that one myself. :slight_smile: Well I’ve finally got this working. Thanks for the help!

It’s disappointing that indexed text can’t be used in switch-style statements. Does anyone know a way to allow the author the convenience of referring to keystroke input, which is stored internally as ASCII/Unicode character codes, using text strings as in the OP’s code above–but without using indexed text?

[spoiler]For reference, here is how Glulx Input Loops converts character codes to indexed text:

[code]To decide which indexed text is (N - a number) resolved to an/-- indexed text:
if (N > 31 and N < 127) or (N > 160 and N < 384):[i.e., we have a standard keystroke and not an unprintable one]
decide on “[char-code (N)]”;
decide on “”.

To say char-code (N - a number):
(- print (char) {N}; -)[/code][/spoiler]


Hmm, could you not just have a table of valid ASCII codes?

Table of ASCII code char 32 " " 33 "!" 34 "'"

And then something like

Now keystroke is "[the char corresponding to the code N in the Table of ASCII]"

Or some such?

Yes, you could do it that way, but I’m looking for a non-manual solution. Inform already knows which codes correspond to which characters, so it seems wasteful to have to include a few hundred rows of table just to get a conversion at the I7 level.

There probably is no way to do this w/o indexed text–at least not yet–but it’s cheap to ask, and there are some folks on this board who have done a lot of poking into I7’s innards…


I have an I6 function that’ll pluck the first letter off of an I7 string and return it as the ZSCII number (or as a KOV, as you prefer), but you still wouldn’t be able to use it in a switch statement.