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.
Related
I was trying to make a "game" in Python where the user inputs a command. However, I do not know whether you can take that input to be a function name. This is my current effort:
def move():
print("Test.")
if __name__ == "__main__":
input("Press enter to begin.")
currentEnvironment = getNewEnvironment(environments)
currentTimeOfDay = getTime(timeTicks, timeOfDay)
print("You are standing in the {0}. It is {1}.".format(currentEnvironment, currentTimeOfDay))
command = input("> ")
command()
Here, the input was move, as I wanted to try and call that function (as a potential end user might). However, I get the following error:
Traceback (most recent call last):
File "D:\Text Adventure.py", line 64, in <module>
command()
TypeError: 'str' object is not callable
I was wondering if there was any way that I could allow a user to 'move' in the game, which the program achieves by calling the "move" function.
It looks like you're using python3.x where input returns a string. To recover the python2.x behavior, you need eval(input()). However, you shouldn't do this. It's likely to lead to a bad day.
A better idea is to put the functions into a dictionary --
def move():
#...
def jump():
#...
function_dict = {'move':move, 'jump':jump }
and then:
func = input('>') #raw_input on python2.x
function_dict[func]()
The following code works for me on python3.2.
def move():
print("Test.")
func_dict = {'move':move}
if __name__ == "__main__":
input("Press enter to begin.")
currentEnvironment = "room" #getNewEnvironment(environments)
currentTimeOfDay = "1 A.M." #getTime(timeTicks, timeOfDay)
print("You are standing in the {0}. It is {1}.".format(currentEnvironment, currentTimeOfDay))
command = input("> ")
func_dict[command]()
You can access functions by name using:
function = globals()[function_name]
if the function is in the current module, or
function = getattr(other_module, function_name)
You should also take measures to disallow calling arbitrary functions, for example, prefixing:
def cmd_move() # ok to call this
def cmd_jump() # ok to call this
def internal_func....
cmd = raw_input('>') # e.g. "move"
fun = globals()['cmd_' + cmd]
fun()
Have a look at the cmd module. See this.
It is normally used for shell style comman dlanguages, but it can also be used to create simple text style adventure games.
You can create commands by creating a new method on the Cmd subclass.
E.g.
def do_move(self, args):
if self.next_room.has_snake():
print "The next room contains a poisonous snake. It bites you and you die."
else:
print "The room is empty"
It's usually better to re-use code as Hans suggests, but if you wanted to input commands and run them manually, it would be MUCH safer to have a dictionary of valid commands than to directly execute user-provided input.
cmd = { 'move': move, 'jump': jump, 'look': look }
currently I am writing a robot operating system (ros) node in python. I want to create a while loop which accepts user input on the one hand but is able to continue if no user input is available.
The idea of the following snippet is, that this python-script offers always 'start' or 'stop'. Another node is listening what string gets published. The user should be able to type in 0 or 1 at runtime to toggle the flag.
Here is my python code:
def main():
pub = rospy.Publisher('/start_stop', String, queue_size=10)
rospy.init_node('start_stop', anonymous = True);
rate=rospy.Rate(10) # 10hz
pubStr = "Start"
while not rospy.is_shutdown():
try:
input = raw_input()
if input == "0":
pubStr = "Stop"
elif input == "1":
pubStr = "Start"
except:
rospy.sleep(0.1)
rospy.loginfo(pubStr)
pub.publish(pubStr)
rate.sleep()
if __name__ == '__main__':
main();
In case you don't find a way to do this directly in Python, an easy solution would be to move the user input to another node:
The first node (let's call it "user input node") looks basically like the code you posted, but publishes directly the value of input to a topic /user_input.
The second node ("start/stop node") publishes the "Start"/"Stop" in a loop, depending on the flag. This flag is set by a callback listening to /user_input.
This way, the start/stop node always publishes depending on the user input, without waiting for new input, while the user can always change the flag by sending a new value via the user input node.
This solution would be easy to implement but has the downside of an additional node in your setup.
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.
So, finally I'm getting to the end of LPTHW, and I'm creating my own text adventure type of game.
I want to incorporate a save function to the game (probably by using file write). Also, the game can give you hints based on your location in the game. What I basically need is following:
There will be lots of prompts for user input (raw_input) in while loops. I want to be able to type SAVE or HINT any time to trigger a function. How do I do this so I don't have to create the same conditional every time? (for example elif action == "HINT": print "...")
Is there a way to create some global expressions so that every time they're typed in the prompt, I can act on them? I will create a module with a dictionary that will reference a certain hint when the player is present in a certain location. I just want to avoid putting the same conditionals all over the place.
If you separate the input into a function, you can pass a hint and access save easily:
def user_input(prompt, hint):
while True:
ui = raw_input(prompt)
if ui.lower() == "hint":
print hint
elif ui.lower() == "save":
save()
else:
return ui
You could also add checking here that the user stays within specific choices (an additional argument), deal with any errors and only ever return valid input.
you should probably use a dictionary
def do_save(*args,**kwargs):
print "SAVE!"
def do_hint(*args,**kwargs):
print "HINT!"
def do_quit(*args,**kwargs):
print "OK EXIT!"
global_actions = {'SAVE':do_save,
'HINT':do_hint,
'QUIT':do_quit}
def go_north(*args,**kwargs):
print "You Go North"
def go_east(*args,**kwargs):
print "you go east"
def make_choice(prompt="ENTER COMMAND:",actions={},context_info={}):
choice = raw_input(prompt)
fn = actions.get(choice.upper(),lambda *a,**kw:sys.stdout.write("UNKOWN CHOICE!"))
return fn(some_context=context_info)
local_actions = {"NORTH":go_north,"EAST":go_east}
player_actions = dict(global_actions.items() + local_actions.items())
print "From Here You Can Go [North] or [East]"
result = make_choice(actions=player_actions,
context_info={"location":"narnia","player_level":5})
I don't know about the save feature but for hint you could just have;
If raw_input == hint:
print "whatever you want here"
Or if you need the hint to be different depending on your position you could have a variable for what the hint for that room is and have it update each time you enter a new room then have:
if raw_input == "hint":
print hintvariable
If this doesn't work then sorry, I'm new.
I was trying to make a "game" in Python where the user inputs a command. However, I do not know whether you can take that input to be a function name. This is my current effort:
def move():
print("Test.")
if __name__ == "__main__":
input("Press enter to begin.")
currentEnvironment = getNewEnvironment(environments)
currentTimeOfDay = getTime(timeTicks, timeOfDay)
print("You are standing in the {0}. It is {1}.".format(currentEnvironment, currentTimeOfDay))
command = input("> ")
command()
Here, the input was move, as I wanted to try and call that function (as a potential end user might). However, I get the following error:
Traceback (most recent call last):
File "D:\Text Adventure.py", line 64, in <module>
command()
TypeError: 'str' object is not callable
I was wondering if there was any way that I could allow a user to 'move' in the game, which the program achieves by calling the "move" function.
It looks like you're using python3.x where input returns a string. To recover the python2.x behavior, you need eval(input()). However, you shouldn't do this. It's likely to lead to a bad day.
A better idea is to put the functions into a dictionary --
def move():
#...
def jump():
#...
function_dict = {'move':move, 'jump':jump }
and then:
func = input('>') #raw_input on python2.x
function_dict[func]()
The following code works for me on python3.2.
def move():
print("Test.")
func_dict = {'move':move}
if __name__ == "__main__":
input("Press enter to begin.")
currentEnvironment = "room" #getNewEnvironment(environments)
currentTimeOfDay = "1 A.M." #getTime(timeTicks, timeOfDay)
print("You are standing in the {0}. It is {1}.".format(currentEnvironment, currentTimeOfDay))
command = input("> ")
func_dict[command]()
You can access functions by name using:
function = globals()[function_name]
if the function is in the current module, or
function = getattr(other_module, function_name)
You should also take measures to disallow calling arbitrary functions, for example, prefixing:
def cmd_move() # ok to call this
def cmd_jump() # ok to call this
def internal_func....
cmd = raw_input('>') # e.g. "move"
fun = globals()['cmd_' + cmd]
fun()
Have a look at the cmd module. See this.
It is normally used for shell style comman dlanguages, but it can also be used to create simple text style adventure games.
You can create commands by creating a new method on the Cmd subclass.
E.g.
def do_move(self, args):
if self.next_room.has_snake():
print "The next room contains a poisonous snake. It bites you and you die."
else:
print "The room is empty"
It's usually better to re-use code as Hans suggests, but if you wanted to input commands and run them manually, it would be MUCH safer to have a dictionary of valid commands than to directly execute user-provided input.
cmd = { 'move': move, 'jump': jump, 'look': look }