Skip to content

Chapter 3

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 methods to put ship on a game board. We also add a class to keep together all the information about our ships.

We will learn new things:

  • computable property,
  • dictionary,
  • tuple,
  • string interpolation,
  • structure,
  • how to iterate over an array,
  • how to iterate over a dictionary,

Table of contents

Source code for this chapter.


Add properties to Ship class

Extend previously created Ship class with new enumerated type

and properties

  • Status.damage and Status.destroyed used to mark one ship's segment as damaged or destroyed. We will differentiate both states to allow future improvements in game logic. We may say that .damage state is reversible -- we may implement method to recover it's state to fully operational Status.ready. .destroyed state will be used in case of crytical not recoverable damages.
  • size is a size of a ship in terms of cells occuied by this ship (we allow only "stright" ships where all ship's segments are in one line; bends are not allowed).
  • position is an array of tuples describing each ship's segment: its location (row and column) and condition (status).
  • readyLevel it describe combat readiness and as for now will expressed the number of ship's .ready segments.
  • isDestroyed is true if the ship is destroyed and may not be longer take part in the fight. This variable is an example of computed property.

Added fields require to implement an initializer


Computed property

Classes, structures and enumerations can define computed properties. This type of properties do not actually store a value. Instead, they provide a getter (get) and an optional setter (set) to retrieve and set other properties and values indirectly. Sometimes we don't need to store explicitly (permanently) some value - for example it may be to expensive (taking into consideration memory usage) or this value may be computed based on other values.

To define computed property we use get and set keyword. In case we want to have a read only computed property we can skip the get keyword (and of course there is no set because this is read only property). The following snippet shows an example of computed properties usage

and results of its execution

Notice that in this example struct was used instead of a class -- see next section for an explanations.


Structures

Structures are very similar to classes. Both are general-purpose, flexible constructs that become the building blocks of our program’s code. As we have sen so far, we define properties and methods to add functionality to our classes using the same syntax we use to define constants, variables, and functions; the same we do in case of structures. With structures and classes in Swift can

  • define properties to store values;
  • define methods to provide functionality;
  • define initializesr to set up their initial state;
  • conform to protocols to provide standard functionality of a certain kind;
  • define subscripts to provide access to their values using subscript syntax;
  • be extended to expand their functionality beyond a defaut implementation.

Moreover, classes have some additional capapbilities that structures do not

  • inheritance enabling one class to inherit the characteristics of another;
  • we can check and interprete the type of a class instance at runtime;
  • reference counting allows more than one reference to a class instance;
  • deinitializers enable an instance of a class to free up any resorces it has assigned.

The additional capabilities that classes support come at the cost of increased complexity. As a general guideline, prefer structures because they’re easier to reason about, and use classes when they’re appropriate or necessary. In practice, this means most of the custom data types you define will be structures and enumerations.

Another worth mention difference is that structures ale always copied when they are passed around in the code and to not use reference counting. Structure instances are always passed by value, and class instances are always passed by reference.

Objective-C vs. Swift difference
In Onjective-C's Foundation NSArray, NSDictionary, NSString are implemented as classes, not structures. This means that arrays, dictionaries and strings are always passed as a references, rather than as a copy.

In Swift Array, Dictionary and String are implemented as structures. This means that data of this type when are assigned to a new constant or variable, or when they are passed to a function or method, are copied.


Add methods to Ship class

Add two methods

In the body of hitAt(row: Int, col: Int) method we can see an example of for loop that allows us to iterate over an array and have both index and element

When index is not needed, we can use simpler syntax as it is showned in the isLocatedAt(row: Int, col: Int) method


Add Ships class

Ships class will collect informations about all ships belonging to one of the players. To do so, we create a class with the needed fields

Variable ships is defined as a dictionary. A dictionary stores associations between keys (all of the same type) and values (all of the same but possible different than keys type) with no defined order. As a dictionary key we may use any hashable (basic types like booleans, integers, and strings are hashable so they can be used as the keys for).

Let's see some examples and resulting code


[:]
[:]
["digit0": "zero", "digit2": "two", "digit1": "one"]
["digit0": "zero", "digit2": "TwO", "digit1": "one"]


["digit0": "zero", "digit2": "TWO", "digit1": "one"]
Old value: TwO (Optional("TwO"))
["digit0": "zero", "digit2": "TWO", "digit1": "one", "digit3": "three"]
key: digit0 value: zero
key: digit2 value: TWO
key: digit1 value: one
key: digit3 value: three
key: digit0
key: digit2
key: digit1
key: digit3
value: zero
value: TWO
value: one
value: three

Swift 4 brings a number of improvements to make dictionaries more powerful, useful and usable.

Sequence-based initializer.
We can now create dictionaries from a sequence of key-value pairs.


[2: "two", 3: "three", 1: "one", 4: "four"]
["three": 3, "four": 4, "one": 1, "two": 2]

Merging
Dictionary now includes an initializer that allows us to merge two dictionaries together. If we want to merge one dictionary into another, Swift also provides a merge(_:uniquingKeysWith:) method. Both allow us to specify a closure to resolve merge conflicts caused by duplicate keys.


["b": 2, "a": 1, "c": 4]
[("a", 1), ("b", 2), ("a", 3), ("c", 4)]

More details can be found for example in How to work with Dictionaries in Swift 4.

In the created Ships class notice the part dedicated to iterate over a dictionary

ships is a dictionary, so iterating over this set returns a tupple (in our case) of the form

In consequence, we may write the iteration code either in the form

or, as we did, in the form

Because in our case we know that value is a Ship object, so we use name ship instead to make code more readable. The first tupple's element, key is not needed in our code, what is signalled by Xcode with the message Immutable value 'key' was never used; consider replacing with '_' or removing it. To silent this warning, key is finally replaced by underscore character _ which is th way wy say to Swift: Swift, I know that there is something here, but I don't need it and so I don't care about it..


Board class modification

Based on previously created method mayPlaceShip(size: Int, anchor: (row: Int, col: Int), direction: Ship.Direction) add a new method

This code doesn't introduce any new elements. Variable position consist of iterarively added ship's parts which is done in

This method assumes that ship placement is possible so every time a mayPlaceShip(size: Int, anchor: (row: Int, col: Int), direction: Ship.Direction) should be called before.

There is one simplification, still also present in mayPlaceShip(size: Int, anchor: (row: Int, col: Int), direction: Ship.Direction) method: we don't care about cells sorounding the ship. Thus, as for now, two ships may be placed so they will "touch" -- this will be fixed in next chapter.


EngineGameBattleship class modification

Add two new fields to track each player's fleet

new enumeration type

to distinguish players. On of them is called player and the other is called his opponent. In practices, player can be identified with human, while oppenent with a computer. In accordance with these changes, also initializer should be modified

Add also two "boilerplate" methods which main purpose is to call previously created in Board class methods with correct arguments (that is board and ships dedicated to given player).

Both methods assumes existance of a method getWhoBoardAndShips(who: Who) returning board and ships variable dedicated to a given player who

Finally the method test() should be removed.

Before we go to the next step let's stop on the line

As we know, ships is a dictionary with keys of the String type and Ship as a value type. Key should uniquely identify every ship. Combining each ship anchor coordinates (its first cell's row and column coordinate) we get a unique ship because no more than one ship can start in a given cell (anchor). So the goal is to create such a string and ma be simpply accomplish with

This is an example of string interpolation. String interpolation is a way to construct a new string value from a mix of constants, variables, literals, and expressions by including their values inside a string literal. Each item inserted into the string literal and wrapped in a pair of parentheses, prefixed by a backslash (\ ITEM ) is interpreted and result of interpretation substitutes its call place

In the example above, the value of age (number 12) is inserted into a string literal in place of \(age). The value of age is also part of a compound expression later in the string where ternary conditional operator is used.

The ternary conditional operator is a special operator with three parts, which takes the form question ? answer1 : answer2. It’s a shortcut for evaluating one of two expressions based on whether question is true or false. If question is true, it evaluates answer1 and returns its value; otherwise, it evaluates answer2 and returns its value. The ternary conditional operator is shorthand for the code below:


Expression used in message string returns either young or middle-aged string depending on age variable value. Finally this short three-line code shoud print

Strings and Characters
https://docs.swift.org/swift-book/LanguageGuide/StringsAndCharacters.html

Going back to our code "\(anchor)" results with the following string (of course, numbers 3 and 4 may be different depending on the actual row and column values)

What is important, because anchor is a tuple, also parentheses as well as names of tuple's elements are included in this string.


Last step

In the last step we should modify the main file.

First, at the begining of the file, add the following method

This method simply calls the mayPlaceShip(size: Int, anchor: (row: Int, col: Int), direction: Ship.Direction) metod from Board class. If there may beplaced a ship of the size size starting at row row and column col and directed to direction direction then we phisicaly put it on the board with the method placeShip(size: Int, anchor: (row: Int, col: Int), direction: Ship.Direction) from Board class call. Having this method, we may implement a loop to make sequence of tests.

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