4
\$\begingroup\$

I have a 2-player game of Battleship written in Python, but I've reused a lot of code for the second player's decisions (particularly in the play_game function). I'm not sure if my question is specific enough for this website, but if so would appreciate some guidance on how this redundancy could be minimised.

__author__ = 'admin'
import sys

def initialise_board(): #create a 10x10 board
    game_board = []
    opponent_board = []
    letters = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"]
    for x in range(len(letters)):
        game_board.append([])
        opponent_board.append([])
        for y in range(1, 11):
            game_board[x].append(str(letters[x])+str(y))
            opponent_board[x].append(str(letters[x])+str(y))

    choose_ships(game_board, opponent_board) #call function to choose ships

def choose_ships(game_board, opponent_board):
    Ships = {'Carrier': 5, 'Battleship': 4, 'Cruiser': 3, 'Submarine': 3, 'Destroyer': 2} #size of each ship
    P1_Ships = [['Carrier', 1], ['Battleship', 1], ['Cruiser', 1], ['Submarine', 1], ['Destroyer', 1]] #number of ships to place
    P2_Ships = [['Carrier', 1], ['Battleship', 1], ['Cruiser', 1], ['Submarine', 1], ['Destroyer', 1]]

    for x in P1_Ships: #place ships
        r = 0

        while x[1] > 0: #check there's ships available
            r += 1
            type = x[0]
            ship_size = Ships[x[0]]
            position = input("Player 1, enter start position of {0}: ".format(x[0])) #choose position (i.e, A1)
            check = place_ship(game_board, ship_size, position, type)
            if check is True:
                x[1] -= 1
            else:
                print("Can't place ship here.")

    for z in P2_Ships: #place ships
        r = 0

        while z[1] > 0: #check there's ships available
            r += 1
            type = z[0]
            ship_size = Ships[z[0]]
            position = input("Player 2, enter start position of {0}: ".format(z[0])) #choose position (i.e, A1)
            check = place_ship(opponent_board, ship_size, position, type)
            if check is True:
                z[1] -= 1
            else:
                print("Can't place ship here.")
    play_game(game_board, opponent_board)

def check_availability(game_board, ship_size, col, row, direction): #check that ship can be placed
    check_ships = ["Carrier", "Battleship", "Cruiser", "Submarine", "Destroyer"]
    if direction == 'up':
        if row - int(ship_size) >= 0: #check ship within boundaries
            for i in range(0, ship_size):
                if game_board[int(row-i)][int(col)-1] not in check_ships: #check for collision
                    pass
                else:
                    return False
            return True
        else:
            return False
    elif direction == 'down':
        space = row + int(ship_size)
        if space <= 11: #check ship within boundaries
            for i in range(0, ship_size):
                rawr = game_board[int(row+i)][int(col)-1]
                if rawr not in check_ships: #check for collision
                    pass
                else:
                    return False
            return True
        else:
            return False
    elif direction == 'right':
        if col + int(ship_size) <= 11: #check ship within boundaries
            for i in range(0, ship_size):
                if game_board[int(row)][int(col)+i-1] not in check_ships: #check for collision
                    pass
                else:
                    return False
            return True
        else:
            return False
    elif direction == 'left':
        if col - int(ship_size) >= 0: #check ship within boundaries
            for i in range(0, ship_size+1):
                if game_board[int(row)][int(col-1)-i] not in check_ships: #check for collision
                    pass
                else:
                    return False
            return True
        else:
            return False

def place_ship(game_board, ship_size, position, ship_type):
    row = position[0]
    row = ord(row)-65
    if len(position) > 2:
        col = (position[1]+position[2])
    else:
        col = position[1]
    row = int(row)
    col = int(col)



    orientation = int(input("Place ship: \n"
                            "1. Vertically \n"
                            "2. Horizontally")) #horizontal or vertical
    if orientation == 1:
        direction = int(input("Choose direction: \n"
                          "1. Up \n"
                          "2. Down")) #up or down
        if direction == 1:
            result = check_availability(game_board, ship_size, col, row, 'up')
            if result is True:
                for i in range(0, ship_size):
                    game_board[int(row-i)][int(col)-1] = str(ship_type)

            print(game_board)
        else:
            result = check_availability(game_board, ship_size, col, row, 'down')
            if result is True:
                for i in range(0, ship_size):
                    game_board[int(row+i)][int(col)-1] = str(ship_type)

            print(game_board)


    else:
        direction = int(input("Choose direction: \n"
                          "1. Right\n"
                          "2. Left"))
        if direction == 1:
            result = check_availability(game_board, ship_size, col, row, 'right')
            if result is True:
                for i in range(0, ship_size):
                    game_board[int(row)][int(col)+i-1] = str(ship_type)
            print(game_board)

        else:
            result = check_availability(game_board, ship_size, col, row, 'left')
            if result is True:
                for i in range(0, ship_size):
                    game_board[int(row)][int(col)-i-1] = str(ship_type)
            print(game_board)

    return result

def play_game(game_board, opponnent_board):
    print("Player 1 starts.")
    P1_turn = True
    P2_turn = False
    check_ships = ["Carrier", "Battleship", "Cruiser", "Submarine", "Destroyer"]
    victory = False

    while True:
        if P1_turn:
            while not victory:
                pos = input("Player 1, enter target: ")
                row = pos[0]
                row = ord(row)-65
                if len(pos) > 2:
                    col = (pos[1]+pos[2])
                else:
                    col = pos[1]
                row = int(row)
                col = int(col) - 1
                target = opponnent_board[row][col]
                ship_hit = target
                target = str(ship_hit)
                if target in check_ships:
                    print("Hit")
                else:
                    print("Miss")
                opponnent_board[row][col] = "X"



                destroyed = True
                for y in opponnent_board:
                    if destroyed:
                        for x in y:
                            if x == ship_hit:
                                destroyed = False
                                break
                    else:
                        break

                if destroyed:
                    if target in check_ships:
                        print("{0} DESTROYED.".format(target))

                victory = True
                for y in opponnent_board:
                    if victory:
                        for j in y:
                            if j not in check_ships:
                                victory = True
                            else:
                                victory = False
                                break
                if victory:
                    print("PLAYER 1 WINS")
                    sys.exit()
                P1_turn = False
                P2_turn = True
                break

        elif P2_turn:
            while not victory:
                pos = input("Player 2, enter target: ")
                row = pos[0]
                row = ord(row)-65
                if len(pos) > 2:
                    col = (pos[1]+pos[2])
                else:
                    col = pos[1]
                row = int(row)
                col = int(col) - 1
                target = game_board[row][col]
                ship_hit = target
                target = str(ship_hit)
                if target in check_ships:
                    print("Hit")
                else:
                    print("Miss")
                game_board[row][col] = "X"



                destroyed = True
                for y in game_board:
                    if destroyed:
                        for x in y:
                            if x == ship_hit:
                                destroyed = False
                                break
                    else:
                        break

                if destroyed:
                    if target in check_ships:
                        print("{0} DESTROYED.".format(target))

                victory = True
                for y in game_board:
                    if victory:
                        for j in y:
                            if j not in check_ships:
                                victory = True
                            else:
                                victory = False
                                break
                if victory:
                    print("PLAYER 2 WINS")
                    sys.exit()
                P1_turn = True
                P2_turn = False
                break


initialise_board()
\$\endgroup\$
4
\$\begingroup\$

You can use loops to reduce repetition between players, here is an example:

P1_Ships = [['Carrier', 1], ['Battleship', 1], ['Cruiser', 1], ['Submarine', 1], ['Destroyer', 1]] #number of ships to place
P2_Ships = [['Carrier', 1], ['Battleship', 1], ['Cruiser', 1], ['Submarine', 1], ['Destroyer', 1]]

for x in P1_Ships: #place ships
    r = 0

    while x[1] > 0: #check there's ships available
        r += 1
        type = x[0]
        ship_size = Ships[x[0]]
        position = input("Player 1, enter start position of {0}: ".format(x[0])) #choose position (i.e, A1)
        check = place_ship(game_board, ship_size, position, type)
        if check is True:
            x[1] -= 1
        else:
            print("Can't place ship here.")

for z in P2_Ships: #place ships
    r = 0

    while z[1] > 0: #check there's ships available
        r += 1
        type = z[0]
        ship_size = Ships[z[0]]
        position = input("Player 2, enter start position of {0}: ".format(z[0])) #choose position (i.e, A1)
        check = place_ship(opponent_board, ship_size, position, type)
        if check is True:
            z[1] -= 1
        else:
            print("Can't place ship here.")

Becomes:

for player, board, ships in ( ("1", game_board, p1_ships), ("2", opponent_board, p2_ships) ):
    # Exercise for the reader

(Please note that the ships are the same for each player so you can just set one equal to the copy of the other.)

\$\endgroup\$
3
\$\begingroup\$

When checking the function play_game(), codes for P1_turn and for P2_turn are identical at algorithm level and only different for player identity 1 or 2 and its associated board opponnent_board or game_board.

Minimize redundancy - PART 1

Step 1 - define a function play_turn() to manage the update the board of the player.

The function returns victory to be checked by the exit-condition.

def play_turn(player_id, player_board):
    pos = input("Player {0}, enter target: ".format(player_id))
    row = pos[0]
    row = ord(row)-65
    if len(pos) > 2:
        col = (pos[1]+pos[2])
    else:
        col = pos[1]
    row = int(row)
    col = int(col) - 1
    target = player_board[row][col]
    ship_hit = target
    target = str(ship_hit)
    if target in check_ships:
        print("Hit")
    else:
        print("Miss")
    player_board[row][col] = "X"
    destroyed = True
    for y in player_board:
        if destroyed:
            for x in y:
                if x == ship_hit:
                    destroyed = False
                    break
        else:
            break
    if destroyed:
        if target in check_ships:
            print("{0} DESTROYED.".format(target))
    victory = True
    for y in player_board:
        if victory:
            for j in y:
                if j not in check_ships:
                    victory = True
                else:
                    victory = False
                    break
    return victory

Step 2 - keep only the while-loop mechanism with the exit condition.

Adding the exit-condition and the P1_turn / P2_turn switching in the play_turn() function is not efficient because it will need to change the if P1_turn: and elif P2_turn: mechanism.

while True:
    if P1_turn:
        while not victory:
            # P1_turn = player 1 and opponnent_board
            victory = play_turn(1,opponnent_board)
            # exit condition player 1
            if victory:
                print("PLAYER 1 WINS")
                sys.exit()
            P1_turn = False
            P2_turn = True
            break

    elif P2_turn:
        while not victory:
            # P2_turn = player 2 and game_board
            victory = play_turn(2,game_board)
            # exit condition player 2
            if victory:
                print("PLAYER 2 WINS")
                sys.exit()
            P1_turn = True
            P2_turn = False
            break

Minimize redundancy - PART 2

It is your turn... Check in the function place_ship() what algorithm is identical and what variable are different.

The function could be called move_ship().

\$\endgroup\$
2
\$\begingroup\$

Here is a technique for reducing common code. It uses the facts that 0 * x = 0, 1 * x = x and -1 * x = -x to reduce the tests that seem to be different for the four directions to a single set of tests. This example reduces check_availability to this:

def check_availability(game_board, ship_size, col, row,
                       direction):  # check that ship can be placed
    check_ships = ["Carrier", "Battleship", "Cruiser", "Submarine",
                   "Destroyer"]

    row_sign, col_sign = dict(
        up=(-1, 0),
        down=(1, 0),
        right=(0, 1),
        left=(0, -1),
    )[direction]

    # check ship within boundaries
    bound = row * abs(row_sign) + col * abs(col_sign) + (
        row_sign + col_sign) * int(ship_size)
    if 0 <= bound <= 11:
        for i in range(ship_size):
            # check for collision
            if game_board[int(row) + i * row_sign][
                    int(col) + i * col_sign] in check_ships:
                return False
        return True
    else:
        return False

Note, I did not test this, so it may have a sign error or such. Hopefully this is illustrative.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.