I am learning how to program and during the Python course on Codecademy I was encouraged to rewrite it all with new features (2 players, various ship sizes, etc.).
In summary this works as follows:
- Board configuration is set
- Players and their deployment is initialized
- Deployment info is exchanged between players
- Each player takes a turn to look at the state of his boards and to take a shot. When all ship tiles are shot, the opposite player wins.
My question is (if anyone got patience to dig through it), how can I improve my coding skills, style and simplify this? Is it good idea to trash it and rewrite with ships and boards as classes?
from textwrap import dedent
from copy import copy
class Player():
def alternate_pixel(self, index, mode):
"""This method, after receiving a call with pixel index
will change its graphical representation. Depending on mode ofc.
Also, this function does not mess with active_indexes."""
# print('alternating %d' % index)
if mode == 'deploy': # . - empty, free space
self.own_board[index] = '$' # $ - healthy ship part
elif mode == 'get miss':
self.own_board[index] = ' ' # - hit in empty space
elif mode == 'get hit':
self.own_board[index] = 'X' # X - destroyed ship part
elif mode == 'make miss':
if self.shooting_board[index] == 'X': # do not override X
pass
self.shooting_board[index] = ' '
elif mode == 'make hit':
self.shooting_board[index] = 'X'
@staticmethod
def show_board(board):
"""Processes list of pixels into graph, without messing with them"""
temp = copy(board)
c = -1 # compensate for inserted '\n\t' units
for i in range(board_size):
c += 1
temp.insert((i+1)*board_size + c, '\n\t')
print('\t ' + ' '.join(temp))
@staticmethod
def are_indexes_allowed(occupied, proposed):
"""Periodicity (being on two ends), out of board deploys and
stacking on tile is disallowed."""
for i in range(1, board_size):
if i*board_size - 1 in proposed and i*board_size in proposed:
return False
for index in proposed:
if index not in range(board_size**2) or index in occupied:
return False
return True
def ship_settler(self, ship_size):
"""The back end to deploy_fleet(). This will put given ship size on
board(horizontaly or vertically)."""
while True:
nose_index = int(ask_for_coordinates())
if nose_index not in self.active_indexes: break # just faster
if ship_size == 1: # mono ships do not need to face the incoming
self.active_indexes.append(nose_index)
Player.alternate_pixel(self, nose_index, 'deploy'); return
proposed_indexes = []
direction = input('NSWE?\n')
for i in range(ship_size): # for each hull segment
if direction == 'N' or direction == 'n':
proposed_indexes.append(nose_index - i*board_size)
if direction == 'S' or direction == 's':
proposed_indexes.append(nose_index + i*board_size)
if direction == 'W' or direction == 'w':
proposed_indexes.append(nose_index - i)
if direction == 'E' or direction == 'e':
proposed_indexes.append(nose_index + i)
if Player.are_indexes_allowed(self.active_indexes, proposed_indexes):
for index in proposed_indexes: # run the updates
self.active_indexes.append(index)
Player.alternate_pixel(self, index, 'deploy')
else:
print('Invalid, try again.') # if not met - rerun
del proposed_indexes [:] # if not emptied it will stay filled
Player.ship_settler(self, ship_size)
def deploy_fleet(self):
"""The front end function that fills active_indexes"""
print(dedent('\
------------------------------\n\
Deployment phase of %s\n\
------------------------------\n' % (self.name)))
ship_size = 0 # going form smallest to largest
for ship_amount in ship_list:
ship_size += 1
for i in range(ship_amount): # for each ship of size
print('Deploying %d sized ship %d/%d\n'
% (ship_size, i+1, ship_amount))
Player.show_board(self.own_board)
Player.ship_settler(self, ship_size)
Player.show_board(self.own_board) # refresh board at the end
input('Your deployment phase has finished.')
def __init__(self, name):
"""Each player has 2 boards, first shows own ships and tiles shot by
the opponent, while second shows the same about him, but active ships
are hidden. Note that list of active_indexes (which marks non destroyed
ship parts) has no effect on graphics (in the shooting phase)"""
self.name = name
self.active_indexes = [] # filled by deploy(), emptied by get_shot()
self.own_board = ['.'] * board_size**2
self.shooting_records = []
self.shooting_board = copy(self.own_board)
Player.deploy_fleet(self) # deployment is part of initialization
print(chr(27) + "[2J") # clear terminal screen
def send_deployment_info(self):
"""Some method of trasfering info whether player hit somthing must be
in place. This seems suboptimal and maybe it could be resolved
diffrently, but perhaps rebuilding is nesscesary to do so."""
active_indexes = copy(self.active_indexes)
return active_indexes
def receive_deployment_info(self, opponent_indexes):
self.opponent_indexes = opponent_indexes
def make_shot(self):
"""Essentially handles players turn"""
print(dedent('\
--------------------------------------\n\
\tFiring phase of %s\n\
--------------------------------------\n' % (self.name)))
Player.show_board(self.shooting_board)
while True: # cannot strike same tile twice
index = ask_for_coordinates()
if index not in self.shooting_records:
self.shooting_records.append(index)
break
if index in self.opponent_indexes: # if guessed right
print('You got him!')
self.alternate_pixel(index, 'make hit')
else: # if guessed wrong
self.alternate_pixel(index, 'make miss')
print('Bad luck!')
Player.show_board(self.shooting_board) # refresh the board
input('Your turn has finished')
print(chr(27) + "[2J") # clears the terminal window
return index # pass shot coordinate
def get_shot(self, index):
"""This has nothing to do with input, it only displays result
of opponents turn."""
print(dedent('\
--------------------------------------\n\
\tRaport phase phase of %s\n\
--------------------------------------\n' % (self.name)))
if index in self.active_indexes: # if got hit
self.alternate_pixel(index, 'get hit')
print('The opponent got your ship hit!\n')
Player.show_board(self.own_board)
self.active_indexes.remove(index) # for finishing the game
else: # if evaded
self.alternate_pixel(index, 'get miss')
print('You have evaded the shot!\n')
Player.show_board(self.own_board)
def configure_board_size():
print(
'What size of board would you like to play on?\n\
Expected range: 4 to 20')
size = input()
if size.isdigit():
if 4 <= int(size) <= 20:
return int(size)
print('Invalid board size')
return configure_board_size()
def ask_for_ship_type_amount(ship_size, space_avaible):
"""Makes sure that there wont be much problems with fitting ship
coordinates in deployment phase. It is ensured bu restricting
the amount of ships size based on left free space."""
if ship_size > space_avaible: # speeds up prompting
return 0
value = input('How much ships sized %d are to be placed?\n' % ship_size)
if value.isdigit():
if int(value)*ship_size <= space_avaible:
return int(value)
print('Invalid amount')
return ask_for_ship_type_amount(ship_size, space_avaible)
def configure_ships(board_size): # gets called second
"""Gets called second and with help of ask_for_ship_type_amount
generates list of ships to be placed in deployment phases.
This ship_list stores amount of each next (in size) ship."""
ship_list = []
space_avaible = ((board_size)**2) // 2.2 # preserve arbitrary 60% freespace
print('Generating ships, ')
for i in range(1, board_size):
value = ask_for_ship_type_amount(i, space_avaible)
space_avaible -= i*value # each next placed ship takes space
ship_list.append(value) # this also stores board_size indirectly
return ship_list
def ask_for_coordinates():
"""Asks for row and column and outputs the index"""
coords = str(input('Select tile\n'))
if coords.isdigit() and len(coords) == 2:
if 0 < (int(coords[0]) and int(coords[1])) <= board_size:
index = int(coords[0]) + board_size * (int(coords[1])-1)
return index - 1 # the above doesnt account for 0th item
print('Invalid coordinates')
return ask_for_coordinates()
def main():
global board_size; global ship_list
board_size = configure_board_size()
ship_list = configure_ships(board_size)
#ship_list = [0, 2, 0]
#board_size = len(ship_list) + 1
a = Player('A')
b = Player('B')
a.receive_deployment_info(b.send_deployment_info())
b.receive_deployment_info(a.send_deployment_info()) # deployment info exch.
#print(a.active_indexes, b.active_indexes)
while True:
a.get_shot(b.make_shot()) # Player B turn
if not a.active_indexes:
print('B wins!'); break
b.get_shot(a.make_shot()) # Player A turn
if not b.active_indexes:
print('A wins!'); break
main()
if/elif/elif/...
whereas in ship_settler() infor i in range(ship_size):
you useif
without elif (the conditions are mutually exclusive in both cases). Consider dropping theelif
from alternate_pixel() or usecase/switch
in both cases. \$\endgroup\$