18. To Your Battle Stations, persistent bugs


#1



18. To Your Battle Stations


When the battle stations lesson is over it is still possible to get error messages when running the program. If you put an invalid input the program crashes. Example, if you input the letter w instead of a number this error pops up:

Traceback (most recent call last):
  File "python", line 32, in <module>
ValueError: invalid literal for int() with base 10: 'w'

So to stop this from happening I used two separate while loops (code below) for each coordinate input. This ensured a valid input had to be entered, but it seems complicated and clunky. Is there an easier way to solve for invalid inputs crashing the code?


from random import randint

board = []

for x in range(5):
    board.append(["O"] * 5)

def print_board(board):
    for row in board:
        print " ".join(row)

print "Let's play Battleship!"
print_board(board)


def protection(input1):
    if input1.isdigit():
        input1 = int(input1)
    else:
        print "that's not a number"

def random_row(board):
    return randint(0, len(board) - 1)

def random_col(board):
    return randint(0, len(board[0]) - 1)

ship_row = random_row(board)
ship_col = random_col(board)

for turn in range(4):
    while True:
        try:
            print "For Row:"
            guess_row = int(raw_input("Enter Coordinate"))
        except ValueError:
            print "Enter a number!"
            continue
        else:
            break
        
    while True:
        try:
            print "For Col:"
            guess_col = int(raw_input("Enter Coordinate"))
        except ValueError:
            print "Enter a number!"
            continue
        else:
            break
    
    if guess_row == ship_row and guess_col == ship_col:
        print "Congratulations! You sunk my battleship!"
    else:
        if (guess_row < 0 or guess_row > 4) or (guess_col < 0 or guess_col > 4):
            print "Oops, that's not even in the ocean."
            print "Turn", turn + 1
            if turn == 3:
                print "Game Over"
                print ("Ship location:", ship_row, ship_col)
        elif(board[guess_row][guess_col] == "X"):
            print "You guessed that one already."
            print "Turn", turn + 1
            if turn == 3:
                print "Game Over"
                print ("Ship location:", ship_row, ship_col)
        else:
            print "You missed my battleship!"
            board[guess_row][guess_col] = "X"
            print "Turn", turn + 1
            if turn == 3:
                print "Game Over"
                print ("Ship location:", ship_row, ship_col)
    print_board(board)


About improper input
#2

The very first thing we need to consider here is who the operator is: It's us. This is a lesson, not a production release, and the only user is the person sitting at this console. Having written the code to spec we are not unaware of the valid inputs. That is the perspective of this module. Keep to the instructions or expect these persistent bugs. The SCT's are just not that robust.


#3

I wasn't expecting the lesson to be overly robust, I was attempting to fix the existing bugs to better learn the language. This helps develop a deeper understanding of how Python works, which is why I asked if there was a more concise way to go about it. However, I see your point as to who the lesson is meant for. Would you suggest I look in other places for assistance on matters like this one?


#4

This will be a useful discussion in the Corner Bar, where it will be moved, presently. Only thing is it might take a while to get going. These sorts of topics are interesting though, and usually attrack one or more intermediate to advanced users. New learners might only read it, but it will get some traffic.


#5

The other way to do it is to define through function recursion.
E.g.

def force_int_input(axis):
        try:
            print "For %s :"%axis
            return int(raw_input("Enter Coordinate: "))
        except ValueError:
            print "Enter a number!\n"
            return force_int_input(axis)

Then just call it like so:

guess_col = force_int_input("Col")
guess_row = force_int_input("Row")

The function just calls itself again to create the loop - techincally if the user didn't enter a number a stupid amount of times it would hit its recursion depth limit but it is very unlikely to happen in this case. Or you could just put the loops into a function to make it nicer to read.

For the while loop you could rewrite it like so:

while not isinstance(guess, int):
	try:
        print "For Col:"
	    guess = int(raw_input("Enter Coordingate: "))
	except ValueError:
	    print "Enter a number!\n"

EDIT: forgot to say for the while loop above you will need to define guess first, i.e. guess = "".


#6

I see, I didn't realize there was a place for these, thank you!


#7

This makes sense, I tried to do do something similar before going with what I did, yet I messed something up. I think it was a format mistake. Thanks for the feedback!


#8

Just tossing these utilities into the fray...

from random import randint

def get_ship(n):
    return tuple([randint(x, n -1) for x in [0, 0]])

def get_input(r, p):
    try:
        a = int(raw_input(p))
        return a if a in r else get_input(r, "Out of range; try again...\n%s" % p)
    except ValueError:
        print "Try again..."
        return get_input(r, p)

def get_board(rows, cols):
    return [['O'] * cols for x in range(rows)]
    
board = get_board(5, 5)

rows = range(len(board))
cols = range(len(board[0]))

ship_row, ship_col = get_ship(len(rows))

guess_row = get_input(rows, "Enter row %r: " % rows)
guess_col = get_input(cols, "Enter col %r: " % cols)

The get_ship function applies to a square grid, whereby cols is not needed. For an uneven board, it would have to be modified to handle different dimensions.


Understanding "HIDE..."
#9

return a if a in r else get_input(r, "Out of range; try again...\n%s" % p)

If the entered value is out of r more than once then you will get a chain of Out of range; try again...\n because you are concatenating p to it each time.

Without changing anything else, this will fix it:

return a if a in r else get_input(r, "\n".join(("Out of range; try again...\n%s" % p).split("\n")[-2:]))

#10

Which I was aware of, but I also believed that someone (maybe you, @jagking) would notice and seek out a solution. Thank you for that. The old adage, "many hands the work make light" still applies.


#11

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.