Reputation: 23
I wasn't entirely sure how to word this question, so I'll stick with explaining my specific goal.
I'm trying to implement an 'eat' option to a user_input function for a text adventure. I want it to check if the verb is 'eat', check if the item is in the player's inventory, check if the item is consumable or not, and then - my goal - show that the player eats it (and only show it once, see below). The 'eat' option has no functionality as of yet, I'm only testing print statements.
I'm totally aware that the code below is not the most efficient way to handle user input (I'm still tweaking ways to handle input that is unexpected), so I'm 100% open to criticism. This was just my initial way of handling it and I'd like to try to make it work. I have a few other verbs ('go', 'look', 'take', etc.) that work, I'm just having a lot of trouble with 'eat'.
def user_input(self):
player = Player()
while True:
user_input = input("> ").lower()
words = user_input.split(" ")
wordlist = list(words)
verb = wordlist[0]
if len(wordlist) == 2:
noun = wordlist[1]
if noun not in items:
print("Somehow '{}' isn't the right word.".format(noun))
continue
else:
pass
# The above works fine because I don't have
# an issue with any of the other verbs regarding
# that bit of code.
# There's more code between these blocks to:
# handle if the user enters a noun that is > 1 word,
# define a few more verbs,
# then,
if verb == "eat":
for item in player.inventory:
if item.name == noun:
if isinstance(item, Consumable):
print("You eat the {}.".format(noun))
break
else:
print("You can't eat that!")
break
else:
print("You don't have '{}'.".format(noun))
I had to use the for loop (at least, I think I had to) because I'm iterating over a list that has objects in them, not strings, so I couldn't just use
if noun in player.inventory:
(I still tried it a million times though, it took forever to come up with a solution for that problem). Here's my specific example for the code above:
class Fists(Weapons):
def __init__(self):
self.name = "fists"
# more instance variables for Fists()
class LeatherWallet(Classy):
def __init__(self):
self.name = "leather wallet"
# more ...
class Pizza(Consumable):
def __init__(self):
self.name = "pizza"
# more ...
class Player():
def __init__(self):
self.inventory = [Fists(), LeatherWallet(), Pizza()]
# other stuff
*eat fists
You can't eat that!
*eat leather wallet
You don't have 'leather wallet'.
You can't eat that!
*eat pizza
You don't have 'pizza'.
You don't have 'pizza'.
You eat the pizza.
From the looks of it, it has got to be a simple fix, because it's clear what's happening as it iterates over the list. I just don't know how to (or if you can) wrangle a for loop to make it check conditions first then print later. At the risk of sounding dumb, I turn to you all for help!
Thank you, and please let me know if I can make this question/my goal any clearer.
Edit: Tried to make the goal a little clearer in opening paragraphs.
Upvotes: 1
Views: 59
Reputation: 774
So, another approach is to just make this work, right?
if noun in player.inventory:
...
You have (at least) two ways to do it.
The first and easy one is using a list comprehension:
inventory_items = [item.name for name in player.inventory]
if noun in inventory_items:
...
That's it.
The second one is creating an Inventory
class inheriting from list
and overriding the __contains__
method so you can compare them however you want.
Something like:
class Inventory(list):
def __contains__(self, item):
"""
Override `list.__contains__`.
"""
# First, check whether `item` is in the inventory.
#
# This is what a normal `list` would do.
if list.__contains__(self, item):
return True
# If we couldn't find the item in the list searching
# the "normal" way, then try comparing comparing it
# against the `name` attribute of the items in our
# inventory.
for inventory_item in self:
# Ensure we can do `inventory_item.name` so we don't get
# an AttributeError.
if 'name' not in dir(inventory_item):
continue
if inventory_item.name == item:
return True
return False
Then you instantiate your inventory like:
self.inventory = Inventory([Fists(), LeatherWallet(), Pizza()])
Remember that Inventory
inherits from list
, so if you can do list(x)
then you should also be able to do Inventory(x)
and get a very similar result (depending on how much you override in your class).
You could also get creative and make all items inherit from an Item
class where you define the __eq__
method and make Pizza() == 'pizza'
to simplify the Inventory
comparisons.
Upvotes: 0
Reputation: 77407
for
loops have else
clauses that are run if the loop doesn't exit with a break. The idea is that the loop is looking for an item, will break when it finds it, and the else
clause handles the default case where you don't find what you're looking for. The variables used inside the loop are still valid after the loop ends so the found (or defaulted) variable can be used in the next bit of code.
In your case, if you move that final else clause out one level, it will only run if the item is not found. In your example output, the two "you don't have 'pizza1" lines will no longer be printed.
for item in player.inventory:
if item.name == noun:
if isinstance(item, Consumable):
print("You eat the {}.".format(noun))
break
else:
print("You can't eat that!")
break
else:
print("You don't have '{}'.".format(noun))
item = None # <-- you can give a default value if you want to
# use the variable later
Looping through the list is okay until the list gets large. Then you are better off indexing the list with a dict
for faster lookup.
self.inventory = dict((item.name, item)
for item in (Fists(), LeatherWallet(), Pizza()))
Then the for loop is replaced with
try:
item = player.inventory[noun]
if isinstance(item, Consumable):
print("You eat the {}.".format(noun))
else:
print("You can't eat that!")
except KeyError:
print("You don't have '{}'.".format(noun))
Upvotes: 1
Reputation: 4714
You're not saying what you want the output to be, but my best guess is you don't want to say "you don't have X" more than once.
One way to do this is to use a flag, initialized to false, set to true when you find the item. When you get out of the loop the flag will tell you if you found it.
Something like this:
if verb == "eat":
found = false
for item in player.inventory:
if item.name == noun:
found = true
if isinstance(item, Consumable):
print("You eat the {}.".format(noun))
break
else:
print("You can't eat that!")
break
if !found:
print("You don't have '{}'.".format(noun))
Upvotes: 1