Lesson 3 - RollingDie in Python - Constructors and random numbers

In the previous lesson, First object-oriented app in Python - Hello object world, we programmed our first object-oriented application in Python. We are already able to create new classes with attributes 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. Let's start it easy and create this rolling die today. We're also going to learn to declare custom constructors.

We'll create a new file named Arena. We'll create a new class there and name it RollingDie. Let's think about the attributes 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. Python provides the random module, which includes the randint() method for this sort of projects. Our class will now have a sides_count attribute.

Last time, for simplicity's sake, we set all the attributes of our class as publicly accessible; however, in most cases, we don't want our attributes to be modified externally. In this case, we mark them as private. Private attributes start with two underscores __ and are only accessible from the inside of the class. When we design a class we usually set everything as private and then we make public only those class members we really need to expose. Our class should now look something like this, we're going to add the attributes in a minute:

class RollingDie:
    """
    Class representing a die for a board game
    """

–°onstructors

We'd now only be able to set values of the public attributes from outside of the class since the private attributes are not visible from the outside. This problem can be bypassed by name mangling, but it's not a good practice since it would violate encapsulation. 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 initialization. We'd create a die in the program now like this:

die = RollingDie()

The RollingDie() method is the constructor. Since our class doesn't have a constructor, Python automatically generated an empty method. However, we'll now add a constructor to the class. We declare it as a method. In Python, we can use two methods for this purpose. The __new__() method and __init__(). The first one is called when the object is being created, but we'll usually get by with the second method which is called when the object is being initialized. Our constructor method will be empty. Of course, we'll write self as the first parameter and insert the pass keyword to the method body, telling Python that it shouldn't do anything. If pass wasn't there, Python would expect a command block and report an error:

def __init__(self):
    pass

If we just created a regular variable in the __init__() method, it'd stop existing once the method ends. But we need to create a sides_count attribute. We create object attributes using the powerful self keyword. self is followed by a period . and the attribute name. So let's create a public sides_count class attribute:

def __init__(self):
    self.sides_count = 6

If we create a new die now, it'll have the sides_count attribute set to 6. Let's print the number of sides to the console, so as to visually confirm that the value is there. The full code looks like this:

class RollingDie:
    """
    Class representing a die for a board game
    """

    def __init__(self):
        self.sides_count = 6


die = RollingDie()
print(die.sides_count)
input()

It's not a good idea to set the attribute as public since we don't want somebody to be able to change the number of sides once the die is created.

We'll add a get_sides_count() method to the class returning the value of the sides_count attribute and we'll make this attribute private. We have now successfully created a read-only attribute. Meaning that, the attribute is not visible and can only be read by using the get_sides_count() method, so it can't be changed from outside the class. Python has other constructs made for these purposes, but we'll go over them later. The updated class with the method would look something like this:

class RollingDie:
    """
    Class representing a die for a board game
    """

    def __init__(self):
        self.__sides_count = 6

    def get_sides_count(self):
    """
    Returns the number of sides the die has
    """
    return self.__sides_count


die = RollingDie()
print(die.get_sides_count())
input()

The attribute became private by adding two underscores. We also changed the printing because we can now only get the value of the attribute by calling the method.

The output:

Console application
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:

def __init__(self, sides_count):
    self.__sides_count = sides_count

Notice that the attribute and parameter names are almost the same. Even if we had the sides_count as a public attribute, it won't be a problem. We use self the specify that the variable on the left side belongs to the instance and the right one is understood by Python as the value from the parameter. With a public attribute, the situation would look like this:

def __init__(self, sides_count):
    self.sides_count = sides_count

But let's go back to our original code and pass a value for this parameter to the constructor:

die = RollingDie(10) # a constructor with a par. 10 is called
print(die.get_sides_count())
input()

The output:

Console application
10

Everything works as expected. Python 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 specifying the default value for the sides_count parameter in the constructor declaration. We'll set the value to 6 since the user probably expects a die to have this value as default:

def __init__(self, sides_count = 6):
    self.__sides_count = sides_count

Let's create 2 dice instances now, one with the parameter specified and one without it:

sixSided = RollingDie()
tenSided = RollingDie(10) # or we can write RollingDie(sides_count=10)
print(sixSided.get_sides_count())
print(tenSided.get_sides_count())
input()

The output:

Console application
6
10

Thanks to the keyword argument, we don't have to specify the sides count. We can use such arguments with other methods as well, not just with constructors. Many Python functions and methods offer keyword arguments, e.g. the build-in print() function. It's a good idea to look at the methods' declaration and arguments, so you don't end up programming something what has already been done for you. Now we got a constructor which allows us to create different rolling dice. Let's continue.

Random numbers

Next, we'll define a roll() method in the RollingDie class, which will return a random number from 1 to the number of sides. The method won't have any parameters. We can obtain a random number using the random module. We'll import it internally using one underscore _. And we'll use its randint() method:

def roll(self):
    """
    Rolls a die and returns a number from 1
    to the sides count
    """
    import random as _random
    return _random.randint(1, self.__sides_count)

When importing modules, Python looks at whether the module has already been imported, so if it was previously imported, Python will not import it again. Thus, the first line in the method is performed only once.

Overriding the __str__() method

Our RollingDie class is almost ready, let's add one more method which we'll use a lot in the future. We're now talking about the __str__() method contained in every object, including our die. The method is designed 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 datatypes have this method.

Python performs implicit conversions. When we want to print a number or any other object to the console, Python calls the __str__() method and prints its return value. When creating a class, we should consider whether __str__() 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 Python. 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 __str__() 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 as it is now:

print(sixSided)

Only the path to our class is printed to the console. Although the method is already declared, we can easily declare it again. It this case, we say we override it:

def __str__(self):
    """
    Returns a textual representation of our die
    """
    return str("A rolling die with {0} sides".format(self.__sides_count))

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

The output:

Console application
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's modify the end of the file:

# Creates the dice
sixSided = RollingDie()
tenSided = RollingDie(10)

# Rolls the 6-sided die
print(sixSided)
for _ in range(10):
    print(sixSided.roll(), end=" ")

# Rolls the 10-sided die
print("\n", tenSided, sep="")
for _ in range(10):
    print(tenSided.roll(), end=" ")

input()

The output should look something like this:

Console application
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, Object References, Cloning, and Garbage Collector in Python, we'll focus on how the objects are handled in memory.


 

Download

Downloaded 0x (1.81 kB)
Application includes source codes in language Python

 

 

Article has been written for you by David Capka
Avatar
Do you like this article?
No one has rated this quite yet, be the first one!
The author is a programmer, who likes web technologies and being the lead/chief article writer at ICT.social. He shares his knowledge with the community and is always looking to improve. He believes that anyone can do what they set their mind to.
Unicorn College The author learned IT at the Unicorn College - a prestigious college providing education on IT and economics.
Previous article
First object-oriented app in Python - Hello object world
All articles in this section
Object-Oriented Programming in Python
Thumbnail
Next article
Object References, Cloning, and Garbage Collector in Python
Activities (2)

 

 

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!