Let’s do it Again: Repeating commands in my text adventure

You may recall that a little while ago I posted about the way I added the use of pronouns, like It, to the parsers of my text adventure game, that allowed you to refer to objects from your previous command. After I had finished that and used it for a little, I realized that the way I did this is actually the basis how I could also implement a repeat function. I want the player to be able to simply enter Again or Repeat and the previous command would be executed once again.

Little did I know that this would lead me down a strange rabbit hole. See, right now I am hold theVerb, theNoun, etc. as global variables. If I want to make backup copies of these variables as part of my game loop, I will have to copy each one of them, including the variables theVerbString, theNounString etc. that I use to preserve the original command string the player entered. Since I am tracking a number of different things, this means I need to backup—and potentially restore—twenty individual variables.

I did not like that thought, so I thought, if I could stick them all in a class, I could do a single assignment TheBackupCommands = theCommands and be done with it. Yeah, well, nice thinking, but that too had its share of problems.

The first thing is that I hold all my global variables in a module called globals.py. This means that whenever I access them in the game I have to access them through globals.theVerb and globals.theNoun. If I put them in a class, this syntax would become even more convoluted and would turn into something like globals.Commands.theVerb or globals.Commands.theNoun. See how much longer the code just got? Just to access the same variable I’d have these lengthy names all over my program. I did not like that a bit.

So I thought if I can find a way to remove the globals part somehow and shorten Command to Cmd, perhaps it wouldn’t be so bad. I spent the better part of two days, trying all sorts of things and eventually gave up because from what I can tell, Python simply does not seem to allow for that. At least not that I could figure out.
Since I found the long globals.Commands.theVerb version completely unacceptable, I decided to go back to the original form and create a series of instructions to backup and restore the variables.

Because I hated to just look at it, I then decided to move the code into its own functions where I would collapse it and never have to look at it again.

In the end, what I needed were three functions.

def BakCommands ( self ):
	globals.theLastVerb = globals.theVerb
	globals.theLastVerbString = globals.theVerbString
	globals.theLastNoun = globals.theNoun
	globals.theLastNounString = globals.theNounString
	-- Snipped --
	
def RestoreCommands ( self ):
	globals.theVerb = globals.theLastVerb
	globals.theVerbString = globals.theLastVerbString
	globals.theNoun = globals.theLastNoun
	globals.theNounString = globals.theLastNounString
	-- Snipped --

def ResetCommands ( self ):
	globals.theVerb = None
	globals.theVerbString = None
	globals.theNoun = None
	globals.theNounString = None
	-- Snipped --

In my Parse() function, the first thing I do now is this…

	self.BakCommands()
	self.ResetCommands()

No need to explain, I suppose. All it does is make a backup of all commands and then clear all the variables. All I have to add do now is to check for the Again or Repeat command in the user input and respond to it.

In my parser, I have a function called Grammar() that I call once the parser has decoded all words. This allows me to do some processing before the commands ever reach the actual game logic. This is where I hook myself in.

def Grammar ( self ):
	""" Inspect input tokens to derive additional meaning from prepositions, adjectives, etc. """

	if Tokens.Drop == globals.theVerb:
		if Tokens.In == globals.thePrep:							# PUT IN
			globals.theVerb = Tokens.PutIn
			globals.thePrep = None

		elif Tokens.On == globals.thePrep:							# PUT ON
			globals.thePrep = None
			if not globals.theNoun2:								# without second noun
				globals.theVerb = Tokens.Wear						# i.e. Put on the jacket
			else:
				globals.theVerb = Tokens.PutOn						# i.e. Put the key on the table

	if Tokens.Repeat == globals.theVerb and not globals.theNoun:	# AGAIN
		self.RestoreCommands()

As you can see from the code, this function is also the place where I adjust certain inputs. If the player enters PUT sth IN sth I am then changing it into a special PutIn token, which can be used by containers to identify specific actions. Or, if the player enters PUT sth ON sth, it can mean to place an item on a supporter, but it can also potentially mean to wear something. The actual meaning depends on the number of nouns in the sentence (and potentially whether an item is actually wearable). But I digress…

In my current case, I am checking here if the input command was Again or Repeat and make sure there was no noun in the sentence. This essentially tells me that Again was the only command entered. In time I should probably add a variable that tracks how many words the parser has tokenized, which would make this even easier as it would allow me to check if there was only a single token and that that token is Again. For now, this will have to do.

And do, it did. The command works like a charm. I enter a command, let the game respond to it and then I enter “Again” and voilà, the exact same command is executed again.

Leave a Reply

Your email address will not be published. Required fields are marked *