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()
3 Answers 3
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.)
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 theplay_turn()
function is not efficient because it will need to change theif P1_turn:
andelif 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()
.
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.