Skip to content

refactoring of battleship_oo2 #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Battleship

A simple python implementation of the famous battleship game
Empty file added battleship/__init__.py
Empty file.
2 changes: 1 addition & 1 deletion battleship_oo1.py → battleship/battleship_oo1.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def declare_winner(self):
print('{}, you won the game!'.format(self.winner.name))

def take_shot(self, shot):
ship_hit = self.board.take_shot(shot)
ship_hit = self.board.add_shot(shot)
if ship_hit:
print('{}, your shot hit {}!'.format(self.player_takes_turn.name, ship_hit.name))
self.winner = self.player2
Expand Down
164 changes: 164 additions & 0 deletions battleship/battleship_oo2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
from typing import NamedTuple, Iterator
from enum import Enum, auto


class Game:
"""
This class controls the game.
It declares the winner and who takes turn
"""

def __init__(self, player1: 'Player', player2: 'Player'):
self._player1 = player1
self._player2 = player2
self._winner = None
self._shooter = self._player1
self._receiver = self._player2

def play_game(self):
while self._winner is None:
self._take_shot()
self._update_winner()
self._alternate_turns()
self._declare_winner()

def _alternate_turns(self):
self._shooter, self._receiver = self._receiver, self._shooter

def _declare_winner(self):
print('{}, you won the game!'.format(self._winner.get_name()))

def _take_shot(self):
shot = self._shooter.call_your_shot()
for ship in self._receiver.get_ships():
if ship.is_hit(shot):
print('{}, your shot hit {}!'.format(self._shooter.get_name(), ship.get_name()))
if ship.get_is_sunk():
print('{} sunk!'.format(ship.get_name()))
return
print('{}, you missed your shot!'.format(self._shooter.get_name()))

def _update_winner(self):
if self._player1.has_lost():
self._winner = self._player2
elif self._player2.has_lost():
self._winner = self._player1


class Player:
"""
This class contains the details of the player, the list of ships and the board seen by the player
"""

def __init__(self, name: str):
self._name = name
self._ships = None
self._board = None

def set_ships(self, iter_ships: 'Iterator[Ship]'):
self._ships = list(iter_ships)

def get_ships(self):
return self._ships

def set_board(self, board):
self._board = board

def get_name(self):
return self._name

def call_your_shot(self):
print('{}, call your shot using comma separated coordinates x, y: '.format(self._name))
while True:
try:
coordinates = Coordinates(*(int(x.strip()) for x in
input().split(',')))
break
except (TypeError, ValueError):
print("Please try again, you must input two integers separated by a comma")
shot = Shot(coordinates=coordinates)
return shot

def has_lost(self):
return all(ship.get_is_sunk() for ship in self._ships)


class Ship:
"""This class represents a ship, it keeps track if it is_sunk"""
id_counter = 0

def __init__(self, iter_coordinates: 'Iterator[Coordinates]'):
Ship.id_counter += 1
self._name = "Ship{}".format(Ship.id_counter)
self._cells = [Cell(coordinates, CellStatus.UNDISCOVERED) for coordinates in iter_coordinates]
self._is_sunk = False

def _update_is_sunk(self):
self._is_sunk = all(cell.get_status() is CellStatus.HIT for cell in self._cells)

def get_is_sunk(self):
return self._is_sunk

def get_name(self):
return self._name

def is_hit(self, shot: 'Shot'):
for cell in self._cells:
if cell.get_coordinates() == shot.coordinates:
if cell.get_status() is not CellStatus.HIT:
cell.set_status(CellStatus.HIT)
self._update_is_sunk()
return True
return False


class Board:
"""This class keeps track of the shots taken by a Player """

def __init__(self, board_len: int):
self._cells = [Cell(Coordinates(x=x, y=y)) for x in range(1, board_len) for y in range(1, board_len)]


Coordinates = NamedTuple('Coordinates', x=int, y=int)

Shot = NamedTuple('Shot', coordinates=Coordinates)


class CellStatus(Enum):
UNDISCOVERED = auto()
HIT = auto()


class Cell:
""" This class contains the coordinates of a single cell, and keeps track if it has been hit """

def __init__(self, coordinates: 'Coordinates', status=CellStatus.UNDISCOVERED):
self._coordinates = coordinates
self._status = status

def get_status(self):
return self._status

def set_status(self, status: CellStatus):
if status in CellStatus:
self._status = status
else:
raise ValueError(type(self).__name__ + " supports only CellStatus ")

def get_coordinates(self):
return self._coordinates


def mock_game():
board_len = 9
player1 = Player("Jack")
player1.set_ships([Ship((Coordinates(1, 1), Coordinates(1, 2))), Ship([Coordinates(4, 5), Coordinates(4, 6)])])
player1.set_board(Board(board_len))
player2 = Player("Jill")
player2.set_board(Board(board_len))
player2.set_ships((Ship([Coordinates(6, 5), Coordinates(6, 6)]), Ship([Coordinates(7, 7), Coordinates(8, 7)])))
Game(player1, player2).play_game()


if __name__ == "__main__":
mock_game()
File renamed without changes.
22 changes: 8 additions & 14 deletions battleship_procedural2.py → battleship/battleship_procedural2.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ def main():
winner = None
player_takes_turn = player1
while winner is None:
take_shot(call_your_shot(player_takes_turn), player_takes_turn)
take_shot(player_takes_turn)
winner = has_player_won()
player_takes_turn = alternate_turns(player_takes_turn)
declare_winner(winner)
Expand All @@ -18,23 +18,15 @@ def main():
def call_your_shot(player_takes_turn):
return tuple(int(x.strip()) for x in input('{}, call your shot using comma separated coordinates x, y: '.format(player_takes_turn)).split(','))


def take_shot(shot, player_takes_turn):
for ship in ships1:
def take_shot(player_takes_turn):
shot = call_your_shot(player_takes_turn)
for ship in ships_to_hit(player_takes_turn):
if shot in ship:
print('{}, your shot hit {}\'s ship!'.format(player_takes_turn, player1))
print('{}, your shot hit a ship!'.format(player_takes_turn))
ship.remove(shot)
if len(ship) == 0:
ships1.remove(ship)
print('{}\'s ship sunk!'.format(player1))
return
for ship in ships2:
if shot in ship:
print('{}, your shot hit {}\'s ship'.format(player_takes_turn, player2))
ship.remove(shot)
if len(ship) == 0:
ships2.remove(ship)
print('{}\'s ship sunk!'.format(player2))
print('The ship sunk!')
return
print('{}, you missed your shot!'.format(player_takes_turn))

Expand All @@ -46,6 +38,8 @@ def has_player_won():
else:
return None

def ships_to_hit(player):
return ships2 if player is player1 else ships1


def alternate_turns(player_takes_turn):
Expand Down
1 change: 0 additions & 1 deletion battleship_oo2.py

This file was deleted.

Empty file added test/__init__.py
Empty file.
71 changes: 71 additions & 0 deletions test/battleship_oo2_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import pytest
import battleship.battleship_oo2 as btl

@pytest.fixture
def a_cell_hit():
return btl.Cell(btl.Coordinates(2, 1), btl.CellStatus.HIT)


class TestCell:

def test_set_correct_status(self):
cell = a_cell_hit()
new_status = btl.CellStatus.UNDISCOVERED
cell.set_status(new_status)
assert cell.get_status() is new_status

def test_set_wrong_status(self):
cell = a_cell_hit()
new_status = "wrong_status"
with pytest.raises(ValueError):
cell.set_status(new_status)


@pytest.fixture
def a_ship_half_hit():
ship = btl.Ship((btl.Coordinates(2, 2), btl.Coordinates(2, 3)))
ship._cells[0].set_status(btl.CellStatus.HIT)
return ship


@pytest.fixture
def a_hitting_shot():
return btl.Shot(btl.Coordinates(2, 3))


@pytest.fixture
def a_missing_shot():
return btl.Shot(btl.Coordinates(4, 4))


@pytest.fixture
def ship_is_hit():
ship = a_ship_half_hit()
ship.is_hit(shot=a_hitting_shot())
return ship


@pytest.fixture
def ship_is_not_hit():
ship = a_ship_half_hit()
ship.is_hit(shot=a_missing_shot())
return ship


class TestShip(object):

def test_is_cell_set_to_hit_when_shot_is_a_hit(self):
ship = ship_is_hit()
assert ship._cells[1].get_status() is btl.CellStatus.HIT

def test_is_cell_still_undiscovered_when_shot_is_a_miss(self):
ship = ship_is_not_hit()
assert ship._cells[1].get_status() is btl.CellStatus.UNDISCOVERED

def test_is_sunk_when_shot_is_a_hit(self):
ship = ship_is_hit()
assert ship._is_sunk is True

def test_is_sunk_when_shot_is_a_miss(self):
ship = ship_is_not_hit()
assert ship._is_sunk is False