0

I need to pass a various amount of ships into the code but still be able to use their name when I am inside the hit_or_miss function. Is there a way to pass an arbitrary amount of parameters (as objects) but still access them specifically by name?

hit_or_miss(ship_1, ship_2, ship_3)

def hit_or_miss(*args):    
# Everything from here on should go in your for loop!
# Be sure to indent four spaces!
    ships_sunk = 0
    for turn in range(4):
        print "Turn", turn + 1
        guess_row  = int(raw_input("Guess Row:"))
        guess_col  = int(raw_input("Guess Col:"))
        guess_loc  = ((guess_row,guess_col))
        # A correct guess congratulates and exits the game
        if guess_loc in ship_1.location or \
           guess_loc in ship_2.location or \
           guess_loc in ship_3.location:
            print "Congratulations! You sunk a battleship!"
            ships_sunk += 1
            board[guess_row - 1][guess_col - 1] = "H"
Brodie
  • 11
  • 1
  • 1
  • 7
  • 1
    Possible dup here http://stackoverflow.com/questions/1419046/python-normal-arguments-vs-keyword-arguments – en_Knight Jun 27 '16 at 17:39
  • Tip: **avoid** using continuation using the backslash. If you add a single space after it it breaks. Simply put those conditions inside a parenthesis: `if (guess_loc in ship_1.location or guess_loc in ship_2.location ... )`. (Also binary operators are more readable if at the beginning of the new line.) – Bakuriu Jun 27 '16 at 17:49

5 Answers5

1

You could try to replace your if statement with a loop over your args.

hit_or_miss(ship_1, ship_2, ship_3)

def hit_or_miss(*args):    
    ships_sunk = 0
    for turn in range(4):
        print "Turn", turn + 1
        guess_row  = int(raw_input("Guess Row:"))
        guess_col  = int(raw_input("Guess Col:"))
        guess_loc  = ((guess_row,guess_col))
        # A correct guess congratulates and exits the game

        # Try something like this
        for ship in args:
            if guess_loc in ship.location:
                print("Congratulations! You sunk a {}".format(ship.ship_type))
                ships_sunk +=1
                board[guess_row - 1][guess_col - 1] = "H"

I'm assuming args are classes that you're passing in that you could add a ship_type attribute. (I didn't test this, so you may have to tweak it.)

Note that inside of your function here, *args is now the iterable args. The name args is arbitrary, as long as you include the * in the function definition. You could use def hit_or_miss(*args) and then loop over ships. This allows you to loop through everything you pass into your function.

You might also be interested in **kwargs, but I don't think that's necessary for your function.

rwhitt2049
  • 380
  • 4
  • 8
  • I need to test guess_loc against every ship not just one. – Brodie Jun 27 '16 at 17:59
  • Can you hit more than one ship with the same guess though? Regardless, this will check every ship you pass to `hit_or_miss`, it will just do them one at a time. – rwhitt2049 Jun 27 '16 at 18:03
  • That codes changes the object from ship_1.location to ship_2.location and so on and so forth? – Brodie Jun 27 '16 at 18:07
  • Yes, I see the source of your confusion. I had `ship_2.location` instead of `ship.location`. The second `for` loop will change the objects. I've made a note about `*args` to clarify what's going on there. – rwhitt2049 Jun 27 '16 at 18:10
  • Tried your code and it still said ship_1 is not defined. – Brodie Jun 27 '16 at 18:13
  • Ok i did not see your fix until just now that seems to be working. – Brodie Jun 27 '16 at 18:19
0

You could use kwargs. They basically translate your key-value arguments to a dictionary that you can access within your function:

def hit_or_miss(**kwargs): 
    print kwargs

hit_or_miss(ship1=ship1, ship2=ship2)
>>> {'ship2': *yourshipobject*, 'ship1': *yourshipobject*}
jusx
  • 1,099
  • 9
  • 14
  • ship1 and ship2 are objects. If i pass them that way will i be able to access the location attribute of the class? – Brodie Jun 27 '16 at 17:49
  • ok but how exactly do i access the location attribute in your example, maybe i am just doing that part wrong. Currently I am trying this: 'if guess_loc in ship1.location or \ guess_loc in ship2.location or \ guess_loc in ship3.location:' – Brodie Jun 27 '16 at 17:55
0

There is no way of doing it without inspecting the upper stack frame, and inspecting the stack frame is generally a bad idea.

A better approach would be to inject an attribute, perhaps ship._name, to the class, and later on map it:

ships = {ship._name: ship for ship in args}

Then you may access it as a normal dict:

ships["ship1"].location

Or iterate over them:

for ship in ships.values():
    print(ship.location)

Using **kwargs would still require you to pass the name along and I believe it won't be as modular as injection in this case.

Bharel
  • 23,672
  • 5
  • 40
  • 80
  • Ok your answer helped me figure it out. All i had to do was access the kwargs dictionary. I was unaware of how to do that. 'code' kwargs["ship1"].location worked perfectly – Brodie Jun 27 '16 at 18:03
0

1) Why can't you use a list of ships?

def hit_or_miss(ships): 
  for ship in ships: pass

hit_or_miss([ship1, ship2, ship3]) 

2) Or declare hit_or_miss with a variable number of arguments?

def hit_or_miss(*ships): 
  for ship in ships: pass

hit_or_miss(ship1, ship2, ship3) 

3) If you need named arguments, pass a dictionary to the function

def hit_or_miss(**ships): 
  for ship in ships: pass
  # ships are accessed with ships['ship1'] etc. 
hit_or_miss(ship1=Ship(...), ship2=Ship(...))

4) DO NOT DO THIS, but you can get the desired behaviour by dynamically evaluating new variables. This makes your code difficult to read and debug, and possibly exposes security vulnerabilities. Unless you have an extremely good reason for doing this, use one of the first three.

def hit_or_miss(ships): 
  for i in range(len(ships)): 
    exec(('ship{} = ships[{}]').format(i, i))
  # we can access `ship1` here now. 

hit_or_miss([ship1, ship2, ship3])
0

I think you misunderstand the word "arbitrarily". This means that there is a variable amount of ships passed to the function, not just 3.

If your function should take exactly 3 ships that you refer to by name then the function should take 3 perameters:

def hit_or_miss(ship1, ship2, ship3):

This would not allow for variable number of ships but would work better then your current version!

As you have the function written you are using the global variables ship_1, ship_2, ship_3 so if I called your function like:

hit_or_miss(a,b,c)

Your function would look for globally defined variables called ship_1, ship_2, ship_3 and completely ignore the arguments!

Similarly how do you expect to refer to a variable number of ships by name? what If you call the function like:

hit_or_miss(ship_a, ship_b) #only 2 arguments, no ship_3

hit_or_miss(s1, s2, s3, s4) #4 arguments, how do you name the fourth?

So no there is no way to take an arbitrary number of arguments and refer to all of them by name. However you only use the ships in this one condition:

    if guess_loc in ship_1.location or \
       guess_loc in ship_2.location or \
       guess_loc in ship_3.location:

Which looks a lot like you want to check if any of the conditions are true, so the equivelent with a variable number of ships would be:

if any((guess_loc in ship.location) for ship in args):

This will check if the condition is met for any of the ships passed in the arguments.

Tadhg McDonald-Jensen
  • 20,699
  • 5
  • 35
  • 59
  • I did not misunderstand abritrarily. Currently i am only working with 3 ships but i want it to be able to handle any amount of ships at any time. It is based on a user chosen difficulty, that determines how many ships are in play. – Brodie Jun 27 '16 at 18:19
  • Your last line of code is what i was looking for. A line of concise code to check all the ships no matter what number of ships was passed in. – Brodie Jun 27 '16 at 18:26
  • I realize now that my intro was kind of harsh, but if the number of arguments is arbitrary then you can't refer to them by name, that is what I was trying to get at in my answer. – Tadhg McDonald-Jensen Jun 27 '16 at 18:28
  • 1
    Yeah i understand that now. And that is fine i just did not quite know how to achieve what you did in your last line of code. That was really the answer I was looking for ultimately. – Brodie Jun 27 '16 at 18:37