Allie Curtis
Allie Curtis

Reputation: 23

Using if statements to terminate a for loop

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

Answers (3)

noisypixy
noisypixy

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.

List comprehension

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.

Custom class

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

tdelaney
tdelaney

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

Jim B.
Jim B.

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

Related Questions