When working on a project, one of the first things I like to do is get something simple working that I can iterate on.
I’ll start with building the component that can accept user input and is in charge of interacting with the battleship game.
class GameCli:
'''Manages interactions between the user and the game.'''
def __init__(self, user_inputs):
self.user_inputs = user_inputs
I’ll initialize the game object with user_inputs. My plan is to use a generator that will let me interactively collect user input while essentially just looking like a list to the GameCli class. This will also make testing easier later.
The next part will be converting raw user input into valid game actions. I’ll use an enum to represent the different types of actions a user can perform.
class UserActionType(Enum):
'''Different type of actions a user can perform.'''
INVALID = 1
SHOW = 2
ATTACK = 3
Next I’ll define a simple parse function to map user inputs to the user action types.
def _parse_user_input(self, raw_user_input):
user_input = raw_user_input.lower().strip()
if user_input == self.show:
return (UserActionType.SHOW, None)
match = re.search(self.valid_attack_pattern, user_input)
if match and match.lastindex == 2:
col = match.group(1)
row = match.group(2)
return (UserActionType.ATTACK, (col, row))
return (UserActionType.INVALID, None)
Another option would be to define a class to encompass all the different user actions and the relevant data (such as the coordinates), but given the current interaction is simple, I’m just using a tuple.
The current implementation is incomplete, but it gives us something to work with.
import re
from enum import Enum
class UserActionType(Enum):
'''Different type of actions a user can perform.'''
INVALID = 1
SHOW = 2
ATTACK = 3
class GameCli:
'''Manages interactions between the user and the game.'''
# user term to display the game board
show = 'show'
# starts with a letter and ends with a number
valid_attack_pattern = r'(^[a-z]{1})([0-9]$)'
def __init__(self, user_inputs):
self.user_inputs = user_inputs
def run(self):
'''Parses and applies user provided commands to the game'''
for user_input in self.user_inputs:
(action_type, action_info) = self._parse_user_input(user_input)
if action_type == UserActionType.SHOW:
self._display_board_layout()
elif action_type == UserActionType.ATTACK:
(col, row) = action_info
self._attack(col, row)
else:
self._display_invalid_user_input_message()
def _parse_user_input(self, raw_user_input):
user_input = raw_user_input.lower().strip()
if user_input == self.show:
return (UserActionType.SHOW, None)
match = re.search(self.valid_attack_pattern, user_input)
if match and match.lastindex == 2:
col = match.group(1)
row = match.group(2)
return (UserActionType.ATTACK, (col, row))
return (UserActionType.INVALID, None)
def _display_board_layout(self):
print('TODO: display board')
def _attack(self, col, row):
print('TODO: attack')
def _display_invalid_user_input_message(self):
print('TODO: display invalid message')
Now I can define a simple main function to run the program.
from game import GameCli
def get_user_inputs():
while True:
yield input('Your Move > ')
if __name__ == '__main__':
cli = GameCli(get_user_inputs())
cli.run()
This gives me something that is now runnable.
$ python3 main.py
Your Move > a
TODO: display invalid message
Your Move > a3
TODO: attack
Your Move > show
TODO: display board
Project changeset
Leave a reply to BattleShip Project Review – Level up SE Cancel reply