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
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
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.theNoun. If I put them in a class, this syntax would become even more convoluted and would turn into something like
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 --
Parse() function, the first thing I do now is this…
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
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
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.