Trouble with Python Text Game - python

So, this may, or may not get complicated. Hopefully not.
In any case, I've been writing an fairly ambitious Python text game in my spare time, just to see if I can get it done. I realize there's tons of interactive fiction engines, parsers, etc out there, but Im doing it from scratch. Its the way I learn - the hard way, I guess.
So here's how it breaks down:
You have the main() function in the engine.py module which pretty much does grabs a room-object & displays the room description. Then it waits for user input and sends it to the parser.
The parser in the parser.py module runs through the user input and constructs a Sentence object (made up of a verb & the object - which can be a noun or direction). The Sentence object also has a output function which calls the xxxCommand class from inside the command.py module.
Example: You input "go north" & the parser accepts it as a suitable sentence. So the parser output function will search for the GoCommand class.
Now here's where I'm having trouble. Before I continue and for the sake of clarity, I'll paste my Engine & Sentence class:
class Engine(object):
def __init__(self, start_room):
self.L = lexi.Lexicon() #Imported earlier
self.P = parser.Parser() #Imported earlier
self.room = start_room
def main(self):
while True:
self.room.describe() #Describes the current room.
# Ask for user prompt
input = raw_input(self.room.prompt)
cmd = input.lower()
# Scans user input & creates tokens from Lexicon table
# For example: [('verb', 'go'), ('direction', 'north')]
tokenizer = self.L.scan(cmd)
# Runs through tokens and creates a sentence object
# With noun & object attributes
parsed_sentence = self.P.parse_sentence(tokenizer)
# See below
parsed_sentence.output(self.room)
class Sentence(object):
def __init__(self, verb, noun):
self.verb = verb[1]
self.obj = noun[1]
# There's a command module with different xxxCommand classes with an execute method
# as seen on last line. So pretend the command module has already been imported.
def output(self, current_room):
verb = self.verb.capitalize()
obj = self.obj
command = getattr(commands, '%sCommand' % verb)(obj)
command.execute(current_room)
Ok, after that long winded setup, I have my GoCommand class like so:
# Subclassed from Command parent class. Has one method called execute. Does nothing else.
class GoCommand(Command):
def __init__(self, direction):
self.direction = direction
def execute(self, current_room):
# 'E' is the instantiation of the Engine class, declared within engine.py
from engine import E
# self.direction is 'north' in our example
# current_room.map is a dict within any Room class named 'map'.
# For example: Intro.map = {'north': char_intro }
if self.direction in current_room.map:
print "You head %s\n" % self.direction # Pretty explanatory
# HERE'S WHERE I HAVE TROUBLE
E.room = current_room.map[self.direction]
else:
print "You can't go that way."
So what I was hoping to achieve was that when the loop finishes, E.room would equal a room class called char_intro and as the loop runs through again, it displays char_intro's description, essentially starting over again.
This is not whats happening. It just stays in the first room. Although the GoCommand.execute() is being run, E.room doesnt change. Anyone know why?
Oh dear god, I realize this is long but I hope someone knows what im talking about & can help me out. How should I fix this so that when the user says go north & there is a path set for North, it changes the room class??

To answer my own question, the loop in Engine.main() works just as its supposed to, but I took self.room.describe() out of the while statement:
def main(self):
self.room.describe() #Describes the current room.
while True:
# Ask for user prompt
input = raw_input(self.room.prompt)
cmd = input.lower()
...etc, etc, etc...
Then I changed the GoCommand to:
class GoCommand(Command):
def __init__(self, direction):
self.direction = direction
def execute(self):
# 'E' is the instantiation of the Engine class, declared within engine.py
from engine import E
# self.direction is 'north' in our example
if self.direction in E.room.map.keys():
print "You head %s\n" % self.direction # Pretty explanatory
E.room = E.room.map[self.direction]
print E.room.describe()
else:
print "You can't go that way."
And everything works as its supposed to.

So it looks like the game state (current room, inventory) is being kept on the Engine. Just for comparison's sake, look over the text adventure game I wrote as a command-parsing exercise at http://www.ptmcg.com/geo/python/confs/adventureEngine.py.txt. In that code, I kept the current room and inventory on a Player instance. The main game loop then looks like:
parser = Parser()
p = Player("Joe")
p.moveTo( startRoom )
while not p.gameOver:
cmdstr = raw_input(">> ")
cmd = parser.parseCmd(cmdstr)
if cmd is not None:
cmd.command( p )
parseCmd parses the input string, and if valid, returns a Command object that implements command(p) where p is the Player. Through the player, the command has access to the current room, current inventory, and any special player state or skills (for instance, a player with heightened vision might have better odds at detecting a trap when entering a particular room). This also makes it easier to test your commands with a mock Player, instead of having to mock any globals or attributes in the Engine itself.

Related

calling methods as command line arguments in the cmd

I have been dealing with this question for quite a time, so I decided to ask directly here for explanations/ what and where to read from.
From university we are doing a project where we use python and the cmd class to call the methods as arguments to the command line.
The lecturer has provided us with some example on how it could work and so on. However he didn't fully explained some details that I still miss about the arguments and how in general you can call the function as an argument in the command line prompt.
Here I will provide a shortened version of the code which he has written, as the general methods are the only thing included here in order to explain the situation in simpler manner.
import cmd
class Multiplayer(cmd.Cmd):
def __init__(self):
"""Init the object."""
super().__init__()
self.dice = dice.Dice() # dice object to call the roll() later
self.player1 = player.Player(1) # score = 0, index = 1
self.player2 = player.Player(2) # score = 0, index = 2
self.playerTurn = player.Player(1) # score = 0, index = 1
def do_start(self, _): #here is the part i dont fully understand
"""start a new game"""
print("You have started a new game.\n")
print("""
Welcome to the game. Type help or ? to list commands.\n
To set both players names, type: 'set_names' \n""")
self.__init__() #calling constructor so the new game starts
def do_exit(self, _):
"""Leave the game."""
print("press 'l' to leave game, press 's' to start a new one")
comand = input()
if comand == 'l':
return True
elif comand == 's':
self.do_start(self)
else:
print("unknow syntax")
self.do_exit(self)
def do_EOF(self, arg):
"""Leave the game."""
return self.do_exit(arg)
When I type "start" this function is called byt the cmd and executed. In general does the "do_" part infront of the function name has to do something with the command line itself? Why do we need this second argument (the underscore dash)? And what is it for?
(I have tried to remove it but then i typed in "-help", couldn't see the command.) I have checked other's classmates codes and they have used other words instead of underscore dash.
If you provide me with some sources/explanations so I can get a better grasp of this concept, it would be really appreciated.
Thanks in advance for the replies. Cheers!

How can I write to a text file in Python while a user is running a script?

I created a basic game as part of a learning exercise and I'd like to expand it as I learn more Python. The game is a pretty basic, text-based adventure game and some rooms let users pick up items.
I want to write those items to a text file as the user plays and then give the user an option to check his/her 'inventory' during the game. I can't get the syntax right that will enable the script to do the following:
Create a new text file when the user starts a game;
Write defined items to the text file as the user reaches that part of the game;
Create an option to view the inventory (I have an idea how to do this).
Here is an example of part of the script with my attempt at the code for this commented out:
def room1_creep():
print "You slowly enter the room, and look around. In the opposite corner of the room, you see a ferocious looking bear. It doesn't seem to have seen you yet."
print "You make your way to the chest at the end of the room, checking to see whether the bear has seen you yet. So far, so good."
print "Just as you reach the treasure chest, you hear a roar from the bear that seems to have seen you. What do you do? Rush 'back' to the door or go for the 'treasure'?"
creep_choice = raw_input("You have two choices: 'back' or 'treasure'. > ")
if creep_choice == "back":
print "You make a run for it. You feel the bear's hot breath on the back of your neck but you reach the door before it catches you."
print "You slam the door closed behind you and you find yourself back in the passage."
return entrance()
elif creep_choice == "treasure":
print "You manage to grab a few handfuls of gold coins before the bear stabs its claws into you and sprint for the exit."
# inv = open("ex36_game_txt.txt", 'w')
# line3 = raw_input("10 gold coins")
# inv.write(line3)
# inv.write("\n")
# inv.close()
# I also want to add "gold coins" to a text file inventory that the script will add to.
print "You manage to slam the door closed just as the bear reaches it. It howls in frustration and hunger."
return middle()
else:
room1_indecision()
My script is on GitHub if the full script would be useful. I ran a couple searches here and the question that comes closest to what I think I need is this one. I can't work out how to implement this effectively
One of my main challenges is working out how to get the script to create a new text file dynamically and then populate that text file with the items from the inventory.
If you need to write to files in python use with open(...):
...
elif creep_choice == "treasure":
print "You manage to grab a few handfuls of gold coins before the bear stabs its claws into you and sprint for the exit."
with open("ex36_game_txt.txt", 'w') as inv:
line3 = "10 gold coins"
inv.write(line3)
# I also want to add "gold coins" to a text file inventory that the script will add to.
print "You manage to slam the door closed just as the bear reaches it. It howls in frustration and hunger."
return middle()
...
with open will automatically handle exceptions and close file when you finish writing to it.
If you need to a list of defined items you can initialize a dictionary and keep it in the memory like this:
list_of_items = {item0: "...", item1: "...", ...}
Define it in a separate module and import it when you need. Then you can access its values by key and write them to the inventory during the game.
I am not sure what exactly you mean by creating an option to view and inventory. Why not use raw_input() as you already do and check for word inventory?
options = ('1. Inventory.\n'
'2. Save.\n'
'3. Exit.\n')
option = raw_input("Choose an option: {}".format(options))
if option == "Inventory":
with open("ex36_game_txt.txt", "r") as inv:
for item in inv:
print(inv)
It will print out the contents of your inventory file.
Also note that if you plan to run your game in python3, then don't use raw_input() and use input() instead.
You do not need to write to a text file if you use the singleton design pattern. It guarantees that a class always returns one unique instance of itself. Thus you can create a class called "PlayerInventory", and once it has been instanciated at least once, whenever and wherever in your code you try to instantiate your inventory class, it will always return the same instance.
If you wish to make your inventory persistent, so that a player can save a game and get his inventory back after closing the program, use the module called "pickle" to serialize your inventory object directly when exiting.
Example:
class PlayerInventory(object):
_instance = None
def __new__(class_, *args, **kwargs):
if not isinstance(class_._instance, class_):
class_._instance = object.__new__(class_, *args, **kwargs)
# you need to initialize your attributes here otherwise they will be erased everytime you get your singleton
class_._instance.gold_coins = 0
class_._instance.magic_items = []
# etc... whatever stuff you need to store !
return class_._instance
You can write this class in a separate file and import it whenever you need to access your inventory. Example usecase (assuming your wrote this class in a file called "inventory.py", which is contained in your main package called "mygame"):
from mygame.inventory import PlayerInventory
# Adding coins to inventory
if has_won_some_gold:
PlayerInventory().gold_coins += 10
Somewhere else in you code, you might want to check if the player has enough gold to perform a certain action:
from mygame.inventory import PlayerInventory
if PlayerInventory().gold_coins < 50:
print "Unfortunately, you do not possess enough wealth for this action..."
else:
# Whatever you wish ...
Appending something to a list of items:
from mygame.inventory import PlayerInventory
if player_picked_up_sword:
print "Got: 1 bastard sword"
PlayerInventory().magic_items.append("bastard sword")
Note that the import line is only necessary once per file if you put it at the very beggining !

Example of a Sufficient MUD-Style and working Text Parser for Python

I am in the middle of programming a MUD like game, more for the experience and practice. I am trying to find an algorithm that will effectively prase text commands. If you have played MUD type games before you know what I am talking about. If not and example would be if I typed in the command: 'search' it would execute the 'search' whether you type in s, se, sea, sear, searc, search, etc...
Now I do have a algorithm already established, but the more I think about it the more problems see to arise. In code it goeslike this:
def text_parser(string, command):
string_list = []
command_list = []
for x in string:
string_list.append(x)
for x in command:
command_list.append(x)
if len(string_list) == 0: # checks to see if user have entered anything
return False
if ((string_list > command_list) - (string_list < command_list)) is 1: # returns false if string has more items than command
return False
else:
if string_list[:len(string_list)] == command_list[:len(string_list)]:
return True
else:
return False
And you can call this function by:
if text_parser('hel', 'hello') is True:
print('This returns True')
if text_parser('hel', 'Foo') is True:
print('This returns False')
Now this code works perfectly.. exactly what I need for it to do. If I type in 'se' and its other end-members for the command' search' it will always be true... but now comes my biggest problem.. Say i have two commands:
'quit' and 'quaff' and the user only inputs 'qu'
According to my algorithm it will execute both the 'quit' and the 'quaff', because my code is set up as:
if (text parser check):
blah
if (text parser check):
blach
etc.... # The only way out of this is to do nested if's and elif's.. which will look messy..
Which is not what I want to do at all. As you can see many more problems can arise the more commands you set up with the game.
What will be a good algorithm set up for text parsing? Or do I just need to change a few things in my already existing code to address the bugs that can pop up with this set up...? Thanks
I think your approach can be simplified by having a list of all available commands. And then define a function that will parse your input string and look for a command match like this:
all_commands = ['search', 'quit', 'quaff']
def find_command(string, allowed_commands=None):
if allowed_commands is None:
# if there is no restrictions, look for all commands
allowed_commands = all_commands
matching_commands = []
for command in commands:
if command.startswith(string):
matching_commands.append(command)
if len(matching_commands) == 1:
# found a match
return matching_commands[0]
# found either no match or more than one
return None
Now, the function find_command will find matches for the input string and it will either match all commands (all_commands) or a given subset (allowed_commands), in case you only want to allow certain commands in that situation.
For example:
print find_command('se') # returns search
print find_command('search') # returns search
print find_command('qu') # returns None
print find_command('quaf') # returns quaff
print find_command('qu', ['search', 'quit']) # returns quit
Did you try pyparsing? It looks good for such tasks. Check examples: http://pyparsing.wikispaces.com/Examples
You might want to consider the cmd module, which lets you define commands as methods on a class. It then provides a prompt where you can type the commands and see the results. The parsing is fairly rudimentary, but probably sufficient for you to get started.
cmd was featured on Python Module of the Week a while ago, and it has a very nice tutorial. Here's just a very brief taste:
import cmd
class HelloWorld(cmd.Cmd):
"""Simple command processor example."""
def do_greet(self, person):
"""greet [person]
Greet the named person"""
if person:
print "hi,", person
else:
print 'hi'
def do_EOF(self, line):
return True
def postloop(self):
print
if __name__ == '__main__':
HelloWorld().cmdloop()
When you run the above code you'll get a prompt where you can type commands. For example:
(Cmd) greet
hi
(Cmd) greet bob
hi, bob
(Cmd)
The module provides the command loop, and it also supports autocompletion, live help, a customizable prompt, and several other features.
Perhaps, put your commands in a list, and do
def parse(userInput):
returnList = []
for cmd in commandList:
if userInput in cmd:
cmdList.append(cmd)
return returnList
Then you have matching commands in your returned list.

Text adventure in Python issue

I am working on a text adventure in Python. I'm very new to the concept of object oriented programming (and programming in general), so I'm not entirely sure what went wrong. Well, what I've done so far is made two methods; one that handles what the user types in, and one that dictates what will happen in the game, using other methods defining rooms that I will create in the future. One problem I have, though, is that I can't test the program by running it! When I run it, there is no prompt for user input - the program just ends without returning any error. There's not much code so maybe you could help me in determining the problem! I'm sure there's something really obvious in there I've forgotten...
class Main(object):
def handle_events(self, userinput, cmd):
self.userinput = userinput
self.cmd = cmd
userinput = raw_input('> ')
cmd = {'use' : use,'quit' : quit, 'help' : help}
if userinput[0] not in cmd:
print "Invalid command. Check [help] for assistance."
def main(self, handle_events):
print '''You are in a dark room filled with
strange and ominous objects.
After some feeling around, you think
you can make out a light switch.'''
self.userinput
if userinput[1] == 'switch':
print "You flicked the switch!"
Main().main
You are not calling your method. Main().main is just a reference to the method. To call it you need another set of parentheses: Main().main().

Return to code after function runs

I'm making a text-based game and need an application-wide command line that's able to be pulled up at any text entry. My plan was to have the commands contained within a module, and execute command(), which would then allow the user to enter a command (or get a list) that is then run (contained within the same module). This didn't work because I need a way to return to where the user was. Is there anyway to return to where the user was before they entered command mode, or is there a better way to do this?
This was my idea:
import commands
def something():
print "a question"
action = raw_input("> ")
if action == "command":
commands.commands()
elif "something else" in action:
do something
else:
error.error(1)
something()
Which then went to commands.py:
def commands():
print "Enter a command, help for a list, blah blah blah."
command = raw_input("$ ")
if command == "bag":
bag()
elif command == "other":
other()
def bag():
print "Action listing things in bag"
The problem is returning where the user left off.
What you need is a main game loop:
while game.is_running:
command = get_user_input()
user_do(command)
update_world()
This will repeat the three lines of code inside the while loop for as long as game.is_running is True. First, you get the user input. Next, you act on it. Finally, you perform any other updates your game needs, like moving or spawning monsters. At this point, it loops back and asks the user for another command.
Update: here is a working example:
# In commands.py:
def bag():
print 'bag'
def other():
print 'other'
def unrecognized():
print 'unknown command'
# In main.py:
import commands
def user_input():
print 'a question'
return raw_input('>')
def user_do(command):
# get the matching command out of commands, or pass back
# the unrecognized function if it's not found
action = getattr(commands, command, commands.unrecognized)
action()
is_running = True
while is_running:
command = user_input()
if command == 'quit':
is_running = False
else:
user_do(command)
In this example, I've cheated and am relying on the user input commands being identical to the name of the functions to be called. In user_do, the getattr call compares the string the user has input with the contents of the command module, returning the function of the same name if one exists, or the fallback function unrecognized if it doesn't. action will now either hold the command function or unrecognized.
If you don't want to have your user commands so tightly bound to the actual functions themselves, you can use a dict as a branching construct (or dispatch) instead of having a lot of if / elif / else statements:
# Modified main.py
import commands
COMMAND_DISPATCH = {
'bag': commands.bag,
'sack': commands.bag,
'other': commands.other,
# ...
}
# ...
def user_do(command):
action = COMMAND_DISPATCH.get(command, commands.unrecognized)
action()
In this example, rather than look up the functions in the commands module, we look them up in COMMAND_DISPATCH instead.
One more bit of advice: pretty soon you'll want to look at parsing the user input into more than just a single command. For this example, let's assume you want to be able to accept input of the form "command ... ". You can extend the user_input function to take care of this:
def user_input():
print 'a question'
user_input = raw_input('>').split(' ')
command = user_input[0]
arguments = user_input[1:]
return command, arguments
So if you enter 'foo bar baz', this would return the tuple ('foo', ['bar', 'baz']). Next we update the main loop to deal with the arguments.
while is_running:
# use tuple unpacking to split into command, argument pairs
command, arguments = user_input()
if command == 'quit':
is_running = False
else:
user_do(command, arguments)
Then make sure we pass them to the command:
def user_do(command, arguments):
action = COMMAND_DISPATCH.get(command, commands.unrecognized)
action(arguments)
And finally, we modify the commands to accept and handle the arguments:
def bag(arguments):
for argument in arguments:
print 'bagged ', argument
For a text adventure, you'll want a more substantial parser, something that deals with command object, command object preposition subject, and possibly even command adjective object ....
You should research "python finite state machine". It is pretty much exactly what you want.
Charming Python: Using state machines
Finite-state machine
wiki.python.org/moin/FiniteStateMachine
What is a state machine?
An overly accurate description of a state machine is that it is a
directed graph, consisting of a set of nodes and a corresponding set
of transition functions. The machine "runs" by responding to a series
of events. Each event is in the domain of the transition function
belonging to the "current" node, where the function's range is a
subset of the nodes. The function returns the "next" (perhaps the
same) node. At least one of these nodes must be an end-state. When an
end-state is reached, the machine stops.
When to use a state machine ...
Start in an initial state.
Read a line of input.
Depending on the input and the current state, either transition to a new state or process the line as appropriate for the current state.
Similar to what #MatthewTrevor suggested in his answer, you would have a main loop, and pass a "state" context to the first entry point call (start or intro or whatever). That call can change the state context to point at something else. When control reaches back to the main loop again and it checks the state, it will run the new state.

Categories