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()
Jamal
35.2k13 gold badges134 silver badges238 bronze badges
asked Jan 15, 2017 at 16:52
\$\endgroup\$

3 Answers 3

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.)

answered Jan 15, 2017 at 17:27
\$\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().

answered Jan 15, 2017 at 19:49
\$\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.

answered Jan 15, 2017 at 19:10
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.