Lesson 3 - RollingDie in Kotlin - Constructors and random numbers

Kotlin OOP RollingDie in Kotlin - Constructors and random numbers

In the previous lesson, First object-oriented app in Kotlin - Hello object world, we programmed our first object-oriented application in Kotlin. We are already able to create new classes, insert fields, methods with parameters and return values into them. 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 deduct health points from the 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 "die" to our game so as to add a bit of randomness to the game. We're also going to learn to define custom constructors.

We'll start by creating a new project and naming it ArenaFight. We'll also add all necessary components such as a package and Main.kt containing our main() method. 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 to generate random numbers. We can do so using range and the shuffled() method. Our class will now have one field only:

  • sidesCount of the Int type

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 Kotlin treats it like it doesn't exist. When we design a class we usually set everything to private and then we make public only those class members we really need to expose. In Kotlin, when the modifier is public, we usually just omit it, since public is the default one. Our class should now look something like this:

class RollingDie {
        /** Number of sides that the die has */
        private val sidesCount: Int
}

Our code will now become underlined in red and it won't be able to compile it. This is because Kotlin doesn't like we declared a new field without any value. We'll solve this by adding a constructor and initialize the sidesCount field properly.

–°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 performs any necessary initializations. We'd create a die in the Main.kt now like this:

fun main(args: Array<String>) {
        val rollingDie = RollingDie() // at this moment the constructor is called
}

The RollingDie() method is the constructor. Since our class doesn't have a constructor, Kotlin automatically generated an empty method. However, we will now add a constructor to the RollingDie class. We declare the body of the constructor using the init keyword. In the constructor, we'll set a number of sides to a fixed value. The constructor will look like this:

init {
        sidesCount = 6
}

We'll be able to compile our application now.

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. To be able to read the value from outside, we have to set the access modifier of the sidesCount field to public, i.e. remove the access modifier. We've just broken the encapsulation rule, you say? Don't worry. The field can't be changed (it's immutable), thanks to the val keyword. By making it public we basically achieved the variable to be read-only (the field is visible, we're able to read it, however, we can't change its value). Of course, this principle works only if we initialize the field by ourselves in the constructor first. Kotlin offers some more constructs for these purposes, but let's not bother with them now. The field declaration will now look like this:

val sidesCount: Int

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

fun main(args: Array<String>) {
        val rollingDie = RollingDie() // at this moment the constructor is called
        println(rollingDie.sidesCount)
}

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. In Kotlin, we specify constructor parameters in the parentheses after the class name, similarly to parameters of a function or method:

class RollingDie(aSidesCount: Int) {

        val sidesCount: Int

        init {
                sidesCount = aSidesCount
        }
}

Notice that we prefixed the parameter name with a, otherwise it would have the same name as the field and cause an error. Let's go back to Main.kt and add a value for this parameter in the constructor:

fun main(args: Array<String>) {
        val rollingDie = RollingDie(10) // at this moment the constructor is called
        println(rollingDie.sidesCount)
}

The output:

10

Everything works as expected. Kotlin will not generate an empty, aka parameterless, constructor now, so we can't create a die without passing the parameter(s) it needs. We can make it possible by adding another constructor, the parameterless one this time. We'll call our parametric constructor in it and pass the number 6 to it since the user probably expects this value as default:

constructor(): this(6)

We use the this keyword to call other constructors, we'll explain it more later.

Let's create 2 dice instances now, one using a parameter and one without it (in Main.kt):

fun main(args: Array<String>) {
        val sixSided = RollingDie()
        val tenSided = RollingDie(10)
        println(sixSided.sidesCount)
        println(tenSided.sidesCount)
}

The output:

6
10

Kotlin doesn't mind us having two constructors as long as their parameters are different. We say that the constructor is overloaded. This works for all methods with same names and different parameters, not just constructors. IntelliJ offers overloaded versions of constructors or methods when we're typing them. We can scroll through the method variants with the arrow keys. We can see our two constructor overloads on the list:

The same behavior could be achieved using default arguments, but we'll talk about that later.

Hints for overloaded constructors

Many methods in Kotlin libraries have several overloads. Take a look at the trim() method on the String class. 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:

class RollingDie(sidesCount: Init) {
        init {
                sidesCount = sidesCount
        }
}

Kotlin can't tell the two apart. In this case, the parameter would be assigned to the parameter again. We can refer to the current instance inside a class because it's stored in the this variable. We would use this e.g. if our die had a giveToPlayer(player: Player) method, calling player.pickUpDie(this) inside. We'd essentially be passing itself, the concrete die instance with which we're working, to the other player by means of the this 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:

class RollingDie(sidesCount: Init) {
        init {
                this.sidesCount = sidesCount
        }
}

Using this, we specified that the left variable, sidesCount, belongs to the instance. The right one is treated as a parameter by Kotlin. Now we have two constructors that allow us to create different dice.

Since it might be a bit lengthy to write a class with multiple fields and then set these fields using constructor parameters, Kotlin offers a short constructor syntax. We can declare a class field through a constructor parameter by adding the val or var keyword before it. Kotlin then knows that we want to create a class field of the same name as the constructor parameter. In our case, the code would look like this:

class RollingDie(val sidesCount: Int) { // We declared our field in the constructor parameter
        constructor() : this(6)
}

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 public, 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 by generating a range of elements from 1 to the value of the sidesCount field, shuffling it and getting the first element.

We can get a shuffled range (or a shuffled array) using the shuffled() method. We'll create the range the same way as we're used to from for loops:

fun roll(): Int {
        return (1..sidesCount).shuffled().first()
}

Overriding the toString() method

Our RollingDie class is almost ready, let's add one more method before we wrap up this class. The toString() method that we've briefly gone over in previous lessons is contained in every object, including our die. The method is designed to return a textual representation of the instance. Which is handy in all cases where we need to print the instance or work with it as with text. Even numerical datatypes have this method.

We already know that Kotlin performs implicit conversions. When we want to print an object to the console, Kotlin calls the toString() method and prints its return value. When creating a class, we should consider whether toString() 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 Kotlin. 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 override toString() in our RollingDie class anyway. It'll say that it's a die and print how many sides it has. First, let's try to print a RollingDie instance directly to the console:

fun main(args: Array<String>) {
        val sixSided = RollingDie()
        val tenSided = RollingDie(10)
        println(sixSided)
}

Only the path to our class is printed to the console, which is social.ict.RollingDie and it's followed by the instance's hash.

We can't just declare a custom toString() method since it's already there (we'll find out why in upcoming tutorials). We must override it. We won't go too far in detail for now, but I want you to know what the proper usage of toString() is. We use the override keyword to mark overriden methods:

In Kotlin, there is also a so-called data class which can generate toString() and another 2 methods. We'll talk about them in the next lessons.

override fun toString(): String {
        return "A rolling die with $sidesCount sides"
}

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

The output:

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:

fun main(args: Array<String>) {
        // create instances
        val sixSided = RollingDie()
        val tenSided = RollingDie(10)

        // Rolls the 6-sided die
        println(sixSided)
        for (i in 0..9) {
                print(sixSided.roll().toString() + " ")
        }

        // Rolls the 10-sided die
        println("\n\n" + tenSided)
        for (i in 0..9) {
                print(tenSided.roll().toString() + " ")
        }
}

The output should look something like this:

Rolling die with 6 sides
3 6 6 1 6 3 6 2 6 3

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 will 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 data types in Kotlin, 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 Samuel Kodytek
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!