What we will do:
In this part we will implement a method for game board printing. We will also add a class to represent a single ship as well as method checking if ship can be placed in a given "place".
We will learn new things:
- extension,
guard
,- instance methods and type methods,
- optional,
- string interpolation,
switch
statement,- ternary conditional operator,
- tuple.
In this part we cover the following topics
- Game board printing method
- Type methods
guard
statement- String interpolation
- Extensions
switch
statement- Create
Ship
class - Mathod checking if ship can be placed
- Tuple
- More details about optionals
- Last step
Source code for this chapter.
In the
Board
class an empty printBoard()
method was created
1 2 |
func printBoard() { } |
In this chapter we will implement this method. Before we do this, some printing rules should be explained.
As we know, our game board is divided on rows and columns. Both number are not limited so in consequence it would be hard to use as coordinates both letters and digits -- we will use only digits.
1 2 3 4 5 6 |
12345 1..... 2..... 3..... 4..... 5..... |
As it was stated in the previous chapter, to simplify our further considerations we will add around our game board a one-cell width border of type notAllowed
. Other cells are filled with empty
value. This border is not a part of a normal game board so we don't index any of its cells.
1 2 3 4 5 6 7 |
12345 ++++++ <--- border 1+..... 2+..... 3+..... 4+..... 5+..... |
Because there may be a lot of rows, we have to know in advance how many digits are needed to print the last index of it. If for example we decide to have 1234
rows, then we need 4 digits to print it. It means that all of our row indexes should be printed on 4 characters right shifted and left-padded with spaces if needed. Rows number 5
, 17
, 324
should be printed as
1 2 3 4 |
5 17 324 1234 |
Because there may be a lot of columns, we have to know in advance how many digits are needed to print the last index of it. To simplify a little bit our code, let's assume that we will use less than 100 columns. So numbers used as indexes have at most two digits. We will print every digit in separate row. Column number 5
should be printed as
1 |
5 |
while number 17
should be printed as
1 2 |
1 7 |
Putting all the rules together, our game board, in case of 12 rows and 15 columns, should be printed as
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
111111 123456789012345 ++++++++++++++++ 1+............... 2+............... 3+............... 4+............... 5+............... 6+............... 7+............... 8+............... 9+............... 10+............... 11+............... 12+............... |
To make it, we need a method determining the number of digits needed to print the largest row number. Such a method is not directly connected with a board -- it is rather universal method which may be used by many other classes or methods. This is why we will put its code in separate class where we will "collect" all helper or useful method. Create the
EngineGameBattleshipUtils
class with the frame of our method
1 2 3 4 5 |
class EngineGameBattleshipUtils { class func determineNumberOfdigits(number: Int) -> Int { } } |
This code looks almost familiar except class
keyword precedings function. All the functions implemented so far were an examples of instance methods. Intance because to use them an instance of a class is needed. We have to create an object and than call an (instance) method on this object. Other words, this kind of methods need object because they operate directly on the objects. With class
keyword precedings function we define type methods that is a method which "belongs" to rather a whole type of objects than particular object. In consequence, no object of this type is needed to use this method. The need to determine the number of digits may occur in many different types and is not something typical for Board
, Ship
or any other batleship game class but rather for integer numbers, no mather where they are used. That is why separate code of this method in a versatile class collecting different utility methods seems to be resonable.
Body of this method may looks like below
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class func determineNumberOfdigits(number: Int) -> Int { var value = 10 guard number > 0 else {return 0} for digits in 1...10 { if (value > number) { return digits } value *= 10 } return 0 } |
We require in our implementation that number
argument is a positive integer (as coordinates in our case are always positive integers). To verify this requirement the code like below is typicaly used
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class func determineNumberOfdigits(number: Int) -> Int { var value = 10 if number > 0 { for digits in 1...10 { if (value > number) { return digits } value *= 10 } } return 0 } |
As we can see, the whole block of code
1 2 3 4 5 6 |
for digits in 1...10 { if (value > number) { return digits } value *= 10 } |
is embraced by if
only to prevent them beeing executed in case of required conditions are not fulfilled. In this short snippet it looks acceptable but for longer code, maybe with more nested conditions of this type, we will get few level of code indentation and set of closing curly brackets which may make the code less readable. The main idea of such an if
is to check if some required conditions are fulfilled. If not, in most cases further code execution doesn't make any sense. That is why in Swift we gave guards which check all necessary conditions. With guard
code looks more natural and let us keep the code that handles a violated requirement next to the requirement.
guard
statementA
guard
statement, like an if
statement, executes statements depending on the logic value of an expression. Unlike an if
statement, a guard statement always has an else
clause. If the guard
statement’s condition is met, code execution continues after the guard statement’s closing brace. Any variables or constants that were assigned values using an optional binding as part of the condition are available for the rest of the code block that the guard
statement appears in. If that condition is not met, the code inside the else
branch is executed. That branch must transfer control to exit the code block in which the guard
statement appears. Other words, if guard
statement’s condition is false then execution of the current block code must be aborted.
Equiped with determineNumberOfdigits(number:)
function, we may start implement printBoard()
method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
func printBoard() { let leadingPadding = EngineGameBattleshipUtils.determineNumberOfdigits(number: rows) var leadingPaddingString = "" var line = "" var digit = 0 for _ in 1...leadingPadding { leadingPaddingString += " " } line = leadingPaddingString + " " for c in 1...cols { digit = c/10 if digit == 0 { line += " " } else { line += "\(digit)" } } print(line) } |
For a game board with 12 rows and 15 columns this should print
1 |
ss#*********111111 |
where instead of characters s
, #
and *
spaces are printed -- above this characters are used to clarify how this line is created. Character
s
is a part ofleadingPaddingString
-- in this case this variable is a two-space length string and is created by
123for _ in 1...leadingPadding {leadingPaddingString += " "}#
is printed in place of border sorrounding game board and is created by
1line = leadingPaddingString + " "*
is printed in one-digit column numbers as for them there is no tens digit; this part is created by
12345678for c in 1...cols {digit = c/10if digit == 0 {line += " "} else {line += "\(digit)"}}
Notice underscore _
usage in place of a variable name
1 2 3 |
for _ in 1...leadingPadding { leadingPaddingString += " " } |
We do this every time the value from the set we iterate over is not important for us. In this case we simply want to concatenate space leadingPadding
times. Underscore _
is the way we say: I don't care about this value.
The
"\(digit)"
is how we create "interpretable" strings in Swift, that is strings whose contents depends on some values evaluated during execution. String interpolation is a way to construct such 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
1 2 3 |
let age = 12 let message = "Age \(age): is \(age < 30 ? "young" : "middle-aged")" print(message) |
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 use above 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:
1 2 3 4 5 |
if question { answer1 } else { answer2 } |
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
1 |
Age 12: is young |
Read Strings and Characters for complete information about strings in Swift.
Very similar code prints second line with unity digits
1 2 3 4 5 6 7 |
line = leadingPaddingString + " " for c in 1...cols { digit = c%10 line += "\(digit)" } print(line) |
For a game board with 12 rows and 15 columns this should print
1 |
ss#123456789012345 |
so at this moment we should have
1 2 |
ss#*********111111 ss#123456789012345 |
Remember that characters s
, #
and *
are not realy visible -- here they are used in places of spaces to clarify how both lines are created.
Using type method like
determineNumberOfdigits(number:)
to separate common code which "belongs" to rather a whole type of objects than particular object is one possible option how we can solve this issue. If we pay a lot more attention to it, we discover that in this example determining a number of digits is something we do on particular integer object. Saying the truth we made it as a type method for didactic reasons to describe what a type method is. Now we will show how this type on problems could be acomplish in more swifty style with extensions.
Extensions add new functionality to an existing class, structure, enumeration, or protocol type. What is very important, this includes the ability to extend types for which we do not have access to the original source code. Extensions are declared with the extension
keyword:
1 2 3 |
extension TypeWeExtend { // New functionality to add to TypeWeExtend goes here } |
Let's use this concept to implement another helpful method. This method should enlarge specified string to a given length left padding it with spaces by default or any other character if specified. For example, if string 12
should be transformed into four-character string, this method should return ..12
where dots .
are used in a place of spaces to make it visible. Create the EngineGameBattleshipExtensions.swift
file and put inside the following code
1 2 3 4 5 6 7 8 9 |
extension String { func leftPadding(toLength: Int, withPad: String = " ") -> String { guard toLength > self.count else {return self} let padding = String(repeating: withPad, count: toLength - self.count) return padding + self } } |
Notice how padding
string is created. In determineNumberOfdigits(number:)
function a variable leadingPaddingString
is created as leadingPadding
spaces concatenated together in for
loop
1 2 3 |
for _ in 1...leadingPadding { leadingPaddingString += " " } |
More swifty way is to use initializator with repeating
argument. We have seen this in previous chapter in array subsection where for example one-dimensional array containing 5 empty strings was created
1 |
var mutableArray3 = [String](repeating: "", count: 5) |
or in Board
initializer where two-dimensional array was created
1 |
board = Array(repeating: Array(repeating: .none, count: cols+2), count: rows+2) |
Because we extend functionality of String
class, self
refers to a given string object. So self.count
is about the number of characters in it, while padding + self
is a concatenation of sequence of spaces (or other character specified as withPad
argument) and string itself (padding
and self
part respectively).
Now we are ready to complete last part of a game board printing method. The rest of the printBoard()
code shouldn't be difficult to follow
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
for r in 0...rows+1 { line = "" if r == 0 || r == rows+1 { line += leadingPaddingString } else { line += String(r).leftPadding(toLength: leadingPadding) } for c in 0...cols+1 { switch board[r][c] { case .empty: line += "." case .hit: line += "!" case .ship: line += "X" case .shot: line += "*" case .none: line += "?" case .notAllowed: line += "+" case .rescue: line += "O" } } print(line) } |
This code should print board in the form similar to given below
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
ss#*********111111 ss#123456789012345 ss++++++++++++++++ s1+..............+ s2+..............+ s3+..............+ s4+..............+ s5+..............+ s6+..............+ s7+..............+ s8+..............+ s9+..............+ 10+..............+ 11+..............+ 12+..............+ ss++++++++++++++++ |
switch
statementFor those who are familiar with C-like programming languages it may appear to be strange that all
case
s in the last part of a printBoard()
method are break
-less. In C break
is used to break execution after completion matching case. In Swift this is a default "mode" as in most cases this is what we expect to happend in switch
statement. There is no need of break
usage in switch
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// No implicit fall through let text = "one" switch text { // Can't use "empty" case //case "one": //case "One": // print("Case 1") //Note: //There is no need to use break statement case "one", "One": print("Case ONE") case "two", "Two": print("Case TWO") default: print("Default") } |
Case ONE
If we want to use C-style fall through bahaviour a
fallthrought
keyword must be used. The fallthrough
keyword causes code execution to move to the next case or default
block on a case-by-case basis. Other words, this is not "global" behaviour for all case
s within a given switch
but concers only the case
inside which fallthrought
is used.
1 2 3 4 5 6 7 8 9 |
switch text { case "one", "One": print("Case ONE") fallthrough case "two", "Two": print("Case TWO") default: print("Default") } |
Case ONE
Case TWO
Remember that doing that, the
fallthrought
does not check the case condition for the switch
case that it causes execution to fall into. The fallthrough
keyword simply causes code execution to move directly to the statements inside the next case (or default
case) block, as in C’s standard switch
statement behavior.
1 2 3 4 5 6 7 8 9 10 |
var number = 2 switch number { case 1, 2: print("1 or 2") fallthrough case 3, 4: print("3 or 4") default: print("all other options") } |
1 or 2
3 or 4
Another improvement in Swift is an ability of
switch
's cases to check if their values are included in an interval.
1 2 3 4 5 6 7 8 9 10 11 12 |
// Inteval matching let number = 12 switch number { case 1...10: print("Range one") case 11..<15: print("Range two") case 15: print("Range three") default: print("Out of range") } |
Range two
Also tuples can be tested by case statement which can be very handy and allows to simplify our code. Tuples are described later in this chapter but this example is given here for completion. Please go back here when you read about tuples.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
var point2D: (Double, Double) point2D = (2.5, 2) switch point2D { case (0, 0): print("Origin") case (_, 0): print("Point is on the OX axis") case (0, _): print("Point is on the OY axis") case (1..<2, 1..<2), (2...3, 2...3): print("Point is inside the restricted area") default: print("Free 2D point") } |
Point is inside the restricted area
Other things which may be useful is the ability to bind the value a
switch
matches to temporary constants or variables, to be used in the body of the case.
1 2 3 4 5 6 7 8 9 10 11 |
point2D = (0, 5) switch point2D { case (0, 0): print("Origin") case (let x, 0): print("Point (\(x),0) is on the OX axis") case (0, let y): print("Point (0, \(y)) is on the OY axis") case let (x, y): print("Free 2D point (\(x), \(y))") } |
Point (0, 5.0) is on the OY axis
1 2 3 4 5 6 7 8 9 10 11 12 13 |
point2D = (2, 3) switch point2D { case (0, 0): print("Origin") case (let x, 0): print("Point (\(x),0) is on the OX axis") case (0, let y): print("Point (0, \(y)) is on the OY axis") case let (x, y) where x > y: print("Point (\(x), \(y)) from a 2D subspace") default: print("Eeee...") } |
Eeee...
Ship
classCreate a new class, as we did it before, and name it
Ship
. This class at this moment will have only one component: Direction
enumeration used to uniquely identify or position a ship on a game board. Three elements are needed to place a ship
- size so we know how many successive cells the ship occupies;
- coordinates of the first element so we know when it starts;
- direction so we know one of the four possible final location option.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
start column | | ......... ....u.... ....u.... ..llXrr..---start row ....d.... ....d.... ......... Ship of the size 3 X - first element; it has (start row, start column) coordinates u - successive cells the ship if it is directed up d - successive cells the ship if it is directed down l - successive cells the ship if it is directed left r - successive cells the ship if it is directed right |
1 2 3 4 5 |
class Ship { enum Direction { case up, down, left, right } } |
As it was explained above, to place a ship on a game board three elements are needed: its size, coordinates of the first element which we will name anchor and direction. So, the header of a mathod checking if ship can be placed may look as follw (put this code in
Board
class)
1 2 |
func mayPlaceShip(size: Int, anchor: (row: Int, col: Int), direction: Ship.Direction) -> Bool { } |
Let's stop for a while and explain what is
anchor: (row: Int, col: Int)
. This is an example of tuple. Tuples is a well known concept from script programming languages and something I always want to have in C. Of course we can live without it and mimic with for example arrays or dictionaries but tuples are more natural. Tuples group multiple values into a single compound value. The values within a tuple can be of any type and do not have to be of the same type as each other.
1 2 3 4 5 |
// Tuple of type (Int, String) let warning = (123, "This is a critical warning") var (currentMessageCode, currentMessageText) = warning print ("Message text " + currentMessageText) print ("Message text \(currentMessageText)") |
If we don't care about the first element of a tuple, we can write it as
1 |
(_, currentMessageText) = warning |
We can also use index numbers starting at zero to get tuple's element
1 |
print ("Message text " + warning.1) |
To make our code more readable, we can name the individual elements in a tuple when the tuple is defined
1 2 3 4 |
let alert = (messageCode: 456, messageText: "This is an alert") let info = (messageCode: 456, messageText: "This is an information you can simply ignore") print ("Message text " + alert.messageText) |
Tuples are great for temporary usage. They are not suited to being use as a complex data structure persisting for a long time. In such a case structures and classes are better choice.
Going back to our method, and trying to implement mayPlaceShip(size:anchor:direction)
it, we will probably write
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
func mayPlaceShip(size: Int, anchor: (row: Int, col: Int), direction: Ship.Direction) -> Bool { var r: Int! var c: Int! if direction == .up { for i in 0...size-1 { r = anchor.row + i*(-1) c = anchor.col + i*(0) guard r>0, r<rows+1 else {return false} guard c>0, c<cols+1 else {return false} if board[r][c] != .empty { return false } } } return true } |
This code allows to check if ship may be directed up
. To chek down
direction we will copy existing code, change a little and add to the body of our function
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
func mayPlaceShip(size: Int, anchor: (row: Int, col: Int), direction: Ship.Direction) -> Bool { ... else if direction == .down { for i in 0...size-1 { r = anchor.row + i*(+1) c = anchor.col + i*(0) guard r>0, r<rows+1 else {return false} guard c>0, c<cols+1 else {return false} if board[r][c] != .empty { return false } } } } |
To check all the direction we need such a spaghetti repetitive code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
func mayPlaceShip(size: Int, anchor: (row: Int, col: Int), direction: Ship.Direction) -> Bool { var r: Int! var c: Int! if direction == .up { for i in 0...size-1 { r = anchor.row + i*(-1) c = anchor.col + i*(0) guard r>0, r<rows+1 else {return false} guard c>0, c<cols+1 else {return false} if board[r][c] != .empty { return false } } } else if direction == .down { for i in 0...size-1 { r = anchor.row + i*(+1) c = anchor.col + i*(0) guard r>0, r<rows+1 else {return false} guard c>0, c<cols+1 else {return false} if board[r][c] != .empty { return false } } } else if direction == .left { for i in 0...size-1 { r = anchor.row + i*(0) c = anchor.col + i*(-1) guard r>0, r<rows+1 else {return false} guard c>0, c<cols+1 else {return false} if board[r][c] != .empty { return false } } } else if direction == .right { for i in 0...size-1 { r = anchor.row + i*(0) c = anchor.col + i*(+1) guard r>0, r<rows+1 else {return false} guard c>0, c<cols+1 else {return false} if board[r][c] != .empty { return false } } } return true } |
Notice, thet the only thing changed in each repeated part is row/column "modficator" -- sometimes it is equal +1 if coordinate should be increased, sometimes it is equal -1 if coordinates should be decreased and sometimes it is equal 0 is coordinate should stay unchanged. So let's make this element variable and keep the rest of the code constant
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
func mayPlaceShip(size: Int, anchor: (row: Int, col: Int), direction: Ship.Direction) -> Bool { var modifier: (forRow: Int, forCol: Int)! var r: Int! var c: Int! switch direction { case .up: modifier = (forRow: -1, forCol: 0) case .down: modifier = (forRow: +1, forCol: 0) case .left: modifier = (forRow: 0, forCol: -1) case .right: modifier = (forRow: 0, forCol: +1) } for i in 0...size-1 { r = anchor.row + i*modifier.forRow c = anchor.col + i*modifier.forCol guard r>0, r<rows+1 else {return false} guard c>0, c<cols+1 else {return false} if board[r][c] != .empty { return false } } return true } |
Variable modifier
allows us correctly change coordinate depending on the direction.
There is one more thing needed explanation -- mysterious exclamation mark !
just after variable declaration.
As we know from previous chapter, every object must have a value or must be explicitely defined as
optional
which is a way to indicate that this object may not have a value. We set an optional variable to a valueless state by assigning it the special value nil
. In Swift, nil
is used in case of the absence of a value of a certain type. We may say, that nil
is a value which tell us, that there is no correct value. If it sounds a little bit crazy, recal NaN
"number" which is a sequence of bits interpreted not as a number but as incorrect (nonexisting) numeric values. Optionals of any type we want can be set to nil
, not just object types. Conversely, nil
cannot be used with nonoptionals. If a variable (rather constant) in our code needs to take no value, we have to declare it as an optional value of the appropriate type. Optional is denoted by question mark ?
sufixes type name: Int?
, Ship?
etc. The key queston is: How we can use optionals? And the answer is not so obvious. We have few options
- Simple guess and try approach
123456var thisMayBeEmpty: Int?print(thisMayBeEmpty)thisMayBeEmpty = 1print(thisMayBeEmpty)thisMayBeEmpty = nilprint(thisMayBeEmpty)
doesn't work - we haveExpression implicitly coerced from Int? to Any
Swift compile warning in all of threeprint
s and results are not such as we want (Optional(1)
instead of1
)
nil
Optional(1)
nil
- If we are sure (but we have to be sure if we don't want to crash our application) that optional contains a non-
nil
value we can force unwrap its value with an exclamation mark added at the end of the optional's name
1234567print(thisMayBeEmpty!)if (thisMayBeEmpty != nil){print("Have some nonempty value: " + String(thisMayBeEmpty!))} else {print("Nothing to print")} - When working with optionals,
if-else
is something we must use. There is a special syntax we can use in this context. Optional binding is used to find out whether an optional contains a value, and if so, to make that value available as a temporary constant or variable.
12345if let nonempty = thisMayBeEmpty {print("Have some nonempty value: " + String(nonempty))} else {print("Nothing to print")}
Notice that fornonempty
we don't have to use an exclamation mark.We can do even more: we can chain together multiple optional bindings, and the entire chain fails gracefully if any link in the chain is
nil
.1234567891011let number1: Int?let number2: Int?number1 = 2number2 = nilif let value1 = number1, let value2 = number2 {print(value1, value2)} else {print("One of values is nil")} - When we are sure that optional has some value every time we will use it, we can "remove" the need to check and unwrap the optional's value every time it is accessed thanks we can assume to have a value all of the time we need it. These kinds of optionals are defined as implicitly unwrapped optionals and we write it by placing an exclamation mark instead of question mark after the type we want to make optional. For example, this code
12var implicitOptional: Int!print(implicitOptional)
in contrast to
12var thisMayBeEmpty: Int?print(thisMayBeEmpty)doesn't generate any warning or error and we can use
implicitOptional
variable without need of any explicit unwraping with an exclamation mark12implicitOptional = 1print(implicitOptional) - Another handy tool we can use is the nil-coalescing operator
??
. When used, for example in expression(a ?? b)
, it unwraps an optionala
if it contains a value, or return a default valueb
ifa
isnil
. The expressionb
must match the type that is stored insidea
.12thisMayBeEmpty = nilprint(thisMayBeEmpty ?? 3)The nil-coalescing operator is a shorthand for
1a != nil ? a! : b
In the last step we should add
test()
method to the EngineGameBattleship
class and call it in the main
file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
func test() { var x = boardPlayer.mayPlaceShip(size: 4, anchor: (row: 2, col: 2), direction: .down) print(x ? "empty" : "occupied") x = boardPlayer.mayPlaceShip(size: 4, anchor: (row: 9, col: 2), direction: .down) print(x ? "empty" : "occupied") x = boardPlayer.mayPlaceShip(size: 4, anchor: (row: 5, col: 7), direction: .right) print(x ? "empty" : "occupied") x = boardPlayer.mayPlaceShip(size: 4, anchor: (row: 5, col: 8), direction: .right) print(x ? "empty" : "occupied") } |
In test()
method we simply do four tests of our mayPlaceShip(size:anchor:direction)
method. To call it, change the main
file conrents to the form
1 2 3 4 5 |
import Foundation let game = EngineGameBattleship() game.printBoards() game.test() |
Now we can run our code. Just after both boads printed, result of a test()
method should be displayed
1 2 3 4 |
empty occupied empty occupied |