Black Friday Black Friday
Black Friday ultimate sale! Get up to 80 % extra free points! More information here

Lesson 3 - RollingDie in Swift - Constructors and Random numbers

Swift OOP RollingDie in Swift - Constructors and Random numbers

In the previous lesson, First object-oriented app in Swift, we programmed our first object-oriented application in Swift. We are already able to create new classes with fields and parameterized methods with return values. Today, we're going to start working on an arena in which two warriors will battle against each other. The battle will be turn-based and one warrior will decrease the health points of other one based on his attack and on the other warrior's defense. Essentially, we'll be simulating a board game, so we'll add a rolling "die" to our game so as to add a bit of randomness. We're also going to learn to declare custom constructors.

We'll start by creating a new Command Line Tool application and naming it ArenaFight. We'll add a new class to our project and name it RollingDie. Let's think about the fields we'll have to add to our die in order for it to function according to our current needs. We should be able to choose the number of sides. Typically 6 or 10 sides as is standard in this type of game. Our die will also need a random number generator. We are provided with this generator by Swift which provides a special function for these purposes. So our class will contain only the sidesCount field for now which we'll set to 6 by default. That way we won't need to deal with an Optional or write a constructor.

Last time, for simplicity's sake, we set all the fields of our class as publicly accessible; however, in most cases, we don't want our fields to be modified externally. In this case, we will use the private modifier. A field is from then on only accessible from the inside of the class and from the outside Swift treats it like it doesn't exist. When we design a class we usually set everything to private and then we make internal (just remove private to make it visible for the whole module) only those class members we really need to expose. Our class should now look something like this:

class RollingDie {

        private var sidesCount = 6
}

Сonstructors

We would now only be able to set the value of the public fields from outside of the class since the private fields are not visible from the outside. We've already discussed constructors a bit. A constructor is a method that is called while an object instance is being created. We use it to set the internal state of the object and perform any necessary initializations. We'd create a die in the main.swift now like this:

let rollingDie = RollingDie()

The RollingDie() method is the constructor. Since our class doesn't have a constructor, Swift automatically generated an empty method. However, we'll now add a constructor to the RollingDie class. We declare it as a method, but it doesn't have a return type and it must be named init(). In the constructor, we'll set the number of sides to a fixed value. This way, we don't have to specify it directly in the declaration, but we need to assign a data type. The RollingDie class will look like this:

private var sidesCount : Int

init() {
        sidesCount = 6
}

If we create the die now, it'll have the sidesCount field set to 6. We'll print the number of sides to the console, so as to visually confirm that the value is there. It's not a good idea to set the field as public/internal since we don't want somebody to be able to change the number of sides once the die is created.

We'll add a new getSidesCount() method to the class which returns the value of the sidesCount field. That way we basically made the field ready-only (the field isn't visible and the only way to ready it is through this method; it can't be changed from outside the class). Swift offers some more constructs for these purposes, but let's not bother with them now. The new method will look like this:

func getSidesCount() -> Int {
        return sidesCount
}

Let's move on to main.swift so we could create our first die and print the number of its sides to the console:

let rollingDie = RollingDie() // at this moment the constructor is called
print(rollingDie.getSidesCount())
}

The output:

6

Now we have visual confirmation that the constructor had been called. However, we'd like to be able to specify how many sides our die will have. Let's add a parameter to our constructor.

init(aSidesCount: Int) {
        sidesCount = aSidesCount
}

Notice that we prefixed the parameter name with aotherwise it would have the same name as the field and cause an error. Let's go back to main.swift and pass a value for this parameter in the constructor:

let rollingDie = RollingDie(aSidesCount: 10)
print(rollingDie.getSidesCount())

The output:

10

Everything works as expected. Swift will not generate an empty, aka parameterless, constructor now, so we can't create a die without passing the parameter it needs. We can make it possible by adding another constructor, the parameterless one this time. We'll set the number of sides to 6 in it since the user probably expects this value as default:

init() {
        sidesCount = 6
}

Let's create 2 dice instances now, one with a parameter and one without (in main.swift):

let sixSided = RollingDie()
let tenSided = RollingDie(aSidesCount: 10)
print(sixSided.getSidesCount())
print(tenSided.getSidesCount())

The output:

6
10

Swift doesn't mind us having two methods of the same name as long as their parameters are different. We say that the init() method (the constructor) is overloaded. This works for all methods with same names and different parameters, not just constructors. Xcode offers overloaded versions of methods when we type a left bracket. We can scroll through the method variants with the arrow keys. We can see our two constructor overloads on the list:

Hints for overloaded constructors

Many methods in Swift libraries have several overloads. Take a look at the append() method on a String. It's good to look through method overloads, so you don't end up programming something what has already been done for you.

Now, let's go over how to name constructor parameters better than aSidesCount in our case. If we named them the same as the fields they initialize, we would cause an error:

init(sidesCount: Int)
        sidesCount = sidesCount
}

Swift can't tell the two apart. In this case, the parameter would be assigned to the parameter again. Xcode will even alert us of this fact. We can refer to the current instance inside a class because it's stored in the self variable. We would use self e.g. if our die had a giveToPlayer(player: Player) method, calling player.pickUpDie(self) inside. We'd essentially be passing itself, the concrete die instance with which we're working, to the other player by means of the self keyword. We won't deal with any of the "player" stuff for now, but we'll set things up to reference the current instance while setting the field:

init(sidesCount: Int) {
        self.sidesCount = sidesCount
}

Using self, we specified that the left variable, sidesCount, belongs to the instance. The right one is treated as a parameter by Swift. Now we have two constructors that allow us to create different dice. Let's continue.

Random numbers

Next, we'll declare a roll() method in the RollingDie class, which will return a random number from 1 to the number of sides. The method will be internal, meaning that it can be called from outside the class, and will have no parameters. The return value will be of the Int type. We can obtain a random number using not exactly a pretty function, arc4random_uniform(), which takes the exclusive upper bound of the generated number. That means it'll generate a random number from 0 to the specified value - 1. Since the data type of the parameter is UInt32, we have to convert it. We'll also add one to the result to get the 1-6 interval, because the lower bound is zero and we can't roll a zero on a die :-)

The roll() method will look like this:

func roll() -> Int {
        return Int(arc4random_uniform(UInt32(sidesCount))) + 1
}

Printing information about the RollingDie class instance

Our RollingDie class is almost ready, let's add one more field before we wrap up this class. Swift uses the description field to return a textual representation of the instance. It's handy in all cases where we need to print the instance or work with it as with text. Even numerical data types have this field and Swift goes for this field when we use the variable in an interpolated String or in the print() method. When creating a class, we should consider whether description will come in handy. We should never make our own method, such as print(), when there is already a way to handle string conversions in Swift. It makes no sense to use it in our die, but when implementing warriors it could return their names. Still, for education's sake, we'll add the description field to our RollingDie class anyway. It'll say that it's a die and contain how many sides it has. First, let's try to print a RollingDie instance directly to the console:

let sixSided = RollingDie()
let tenSided = RollingDie(sidesCount: 10)
print(sixSided)

Only the path to our class is printed to the console, which is ArenaFight.RollingDie. Now let's have a look at how to declare the description field. The code is quite complex for us now so don't think about it much, everything will be explained in the following lessons :-)

class RollingDie : CustomStringConvertible {

        var description: String {
                return "A rolling die with \(sidesCount) sides"
        }

        // ... the rest of the RollingDie class
}

let sixSided = RollingDie()
let tenSided = RollingDie(sidesCount: 10)
print(sixSided)

We implemented description as a special read-only property. In the class definition, we can see : CustomStringConvertible. That's a protocol specifying what the class can do. We'll explain protocols in one of the following lessons.

Let's print out the die instance to the console again.

The output:

A rolling die with 6 sides

Let's test our rolling dice now. We'll roll them in loops and see if they work as expected:

let sixSided = RollingDie()
let tenSided = RollingDie(sidesCount: 10)

// rolls the 6-sided die
print(sixSided)
for _ in 1...10 {
        print(sixSided.roll(), terminator: " ")
}

// rolls the 10-sided die
print("\n\n \(tenSided)")
for _ in 1...10 {
        print(tenSided.roll(), terminator: " ")
}

The output should look something like this:

A rolling die with 6 sides
3 6 6 1 6 3 6 2 6 3

A rolling die with 10 sides
5 9 9 2 10 4 9 3 10 5

We've created a nice, customizable class that represents a rolling die. It'll come in handy when we get to the arena part of the application, but you can use it whenever you'd like. Hopefully, you now understand how OOP allows you to reuse components. In the next lesson, Reference and value data types in Swift, we'll talk about the differences between reference data types (objects) and value data types (e.g. Int). :)


 

 

Article has been written for you by Filip Němeček
Avatar
Do you like this article?
No one has rated this quite yet, be the first one!
Activities (3)

 

 

Comments

To maintain the quality of discussion, we only allow registered members to comment. Sign in. If you're new, Sign up, it's free.

No one has commented yet - be the first!