Skip to content

Chapter 4

Informations presented on this page may be incomplete or outdated. You can find the most up-to-date version reading my book Learn Swift by examples. Beginner level

What we will do:
In this part we will implement shooting functionality to check if shot is possible, to make a shot and to do some actions after shot. We will also implement code responsible for making ship layout -- automatically put them in legal places.

We will learn new things:

  • set data structure.

Table of contents

Source code for this chapter.

Board class modification

So far both ships and boards are controlled by EngineGameBattleship

With this there is no relation between board and ships on that board -- we still have to remember that shipsPlayer are ships related to boardPlayer. This could lead to problems in a future, so let's chane it. Remove from EngineGameBattleship lines

and add to Board class the code

At this moment a list of errors both in EngineGameBattleship as well as in Board should appear. To fix them, in EngineGameBattleship:

  • Remove two lines

    from initializer.
  • Method getWhoBoardAndShips(who: Who) -> (board: Board, ships: Ships)

    should be simplified to the form
  • Line

    in mayPlaceShip(who: Who, size: Int, anchorRow: Int, anchorCol: Int, direction: Ship.Direction) -> Bool should be simplified to the form
  • Method placeShip(who: Who, size: Int, anchorRow: Int, anchorCol: Int, direction: Ship.Direction)

    should take the form

In Board class:

  • add

    line in the initializer body.

When ship is totaly destroyed (sunken) all its sorrounding cells shoud be marked as it is showned at the figure below

We can do this becaus of our assumption that no ship can "touch" other ship. Add to Board class method implemmenting this

We marked it as a private because this is a helper method called only from the body of its class. Although we have a nice and correct if statement, it is a little bit clumsy. Me may turn it into something more readable with teh help of set data structure.

A set stores distinc values of the same type in a collection with no defined ordering. We can use a set instead of an array when the order of elements is not relevant, or when we need to ensure that an element only appears once. A type of objects stored in a set must be hashable.

["one", "two"]
["one", "two"]
["one", "two"]
["one", "three"]
[2, 3, 1, 4]
[5, 6, 3, 4]
[5, 6, 2, 3, 1, 4]
[3, 4]
[2, 1]
[5, 6, 2, 1]

To test set membership or equality we can use

  • To test if two sets contain exactly the same values, we use == operator.
  • To test if all of the values of a set are contained in the specified set, we use isSubset(of:) method. We use isStrictSubset(of:) if we want to exclude equality of both sets.
  • To test if a set contains all of the values from a specified set, we use isSuperset(of:) method. We use isStrictSuperset(of:) if we want to exclude equality of both sets.
  • To test if both sets have no common values, we use isDisjoint(with:) method.

Going back to our mathod, we can replace

with dictionary

Just created method markWhenShipDestroyed(ship: Ship) is called by

This method performs some actions every time a ship gets hit. What is important, methods doesn't check if hit was possible or not -- it assumes that it was feasible. So first we should implement mayShot(row: Int, col: Int) -> Bool to check if shot is possible. Next we should implement func shot(row: Int, col: Int) method to make a shot. And finally we will call afterHitAction(row: Int, col: Int). Both methods may be implemented as it is showned below.

Next method we will implement is a method intended to automaticaly position all ships of a defined sizes.

For every number size defined in shipSize we try at most maxTriesPerShip times to place ship of that size on a board. For every try we randomly select its anchor's row r and column c as well as shipDirection. If it is possible to put the ship of the size size, started at row r and column c and directed to shipDirection direction (this is checked with mayPlaceShip method call) we place it (with placeShip method call) we increase the total number of all positioned ships (variable positioned) and set success variable to true. Variable success equals to true informs that ship was successfuly placed on a board. If it is false when for loop ends for a given size it means that we tried without success maxTriesPerShip times to position this ship and it doesn't make any sense to try more times or for other ships. As a result we return the number of ships which were successfuly positioned on the board. If this number is equal to shipSize array it means that all ships were positioned successfully. To increase the chances of success it is important to put bigger ships as first in shipSize array and smaller at the end.

Two things should be fixed

  • Warning Result of call to 'placeShip(size:anchor:direction:)' is unused because we don't use result returned by placeShip(size:anchor:direction:) method. We will fix this in a moment.
  • Error Type 'EngineGameBattleshipUtils' has no member 'getRandomInt'. We will fix it adding getRandomInt method to the EngineGameBattleshipUtils class.

Let's fix placeShip(size:anchor:direction:) method

Compared to existing code two new blocks of code started at // BEGIN: To add border along ship and // BEGIN: To add borders at the ends of ship are added and final return (as well as return typ from function header) is removed. Both blocks are used to mark all cells directly sorrounding ship as notAllowed to prevent orther ships to be placed too close.

Add methods to EngineGameBattleshipUtils class

To be able to position ships we need a method returning an integer from given range (including both range ends) and such that is not contained in excluding array to prevent from selecting occupied cells. Mathod returns nil in case of failure or integer if it has been found.

Last step

Finally it's time to add test code to main file. To make test possible, remove for a while private in front of boardPlayer declaration in EngineGameBattleship class

Change main file contents to the following form

Now we can run our code. As a result, we should see

As we can se first shot hits the ship at row 3, column 4 which is marked with ! character. Second shot hit the ship at row 2 and column 4. This hit sunk the ship so it is marked with O characters.

Change again main file contents to the form

After code execution, we should see a result similar to presented below

We tried to put 10 ships: one of size 4, two of size 3, three of size 2 and four of size 1. All of them were placed successfuly.

Finally add back private in front of boardPlayer declaration in EngineGameBattleship class