Object Oriented Programming

In this lesson, you will take an object oriented approach to programming.


It is time to build a more substantial program. In the next several lessons, you will build a Space Game using Turtle.

As programs become more complex, Objected Oriented Programming, or OOP, is often useful in keeping the project managable. OOP includes many aspects including abstraction, polymorphism, inheritance, and encapsulation. In this lesson, you will be learning just the most fundamental OOP aspects of classes and objects.

Preview

The Space Game will allow two players to use keyboard and mouse inputs to fly through space and fire bullets at each other. The only question is: Who will survive?

Here is a demo of the completed game:



Your goal in completing these lessons should not be to learn how to make a game or blindly follow steps without understanding the underlying code. Instead, focus on learning the core OOP concepts used.

OOP concepts are not unique to games development. There are many programming languages that support OOP and this approach is used for a wide range of applications, including Machine Learning and AI.


Setup

Download spaceGame_v1.py > right-click on the link and select Save Link As.. to choose the location on your computer you would like to save to.

Open the file in IDLE and take a look through the code.

The first line of code imports the turtle module, giving you access to the turtle class. You will know what a class is by the end of this lesson!

import turtle

The next line of code instantiates a turtle object by calling the Turtle constructor. You will learn about instantiation, objects, and constructors in this lesson too! For now, just know that this code creates a turtle on the screen and stores it in the varable t so that it may be used later in the code.

t = turtle.Turtle()

Typically, you would want to avoid variable names such as t which may be ambiguous. In this case, t is a reference to the turtle, but could just as easily be used to store the time, temperature, or any other value in which t would provide some meaning. An exception is being made to this guideline here because the turtle is used so often in the code that using just the single letter t for the variable name makes the code easier to write, and might actually improve readability.

Additionally, the more meaningful identifier of turtle is already being used in the first line of the code as the identifier of the class. In this program, t refers to the turtle object that is on the screen and used to draw while turtle refers to the turtle class that we gained access to by importing the turtle module.

Similarly, the next line of code creates a Screen object and stores it in a variable named screen.

screen = turtle.Screen()

The code in lines 6 to 44 defines a function named drawShip that has one parameter to allow for a color of the ship to be specified.

When the function is called (from line 50), it draws a space ship at the same location and with the same heading (rotation) as the turtle.

Notice that many of the commands in the program have been abbreviated.
pu : penup
pd : pendown
fd : forward
bk : back
rt : right
lt : left
ht : hideTurtle

This is possible because the turle class includes functions with different names that have the same behavior.

When drawing the ship, it is important to leave the turtle in the same position and heading as it started. This is because the turtle will determine the players position and heading throughout the game.

To achieve this, the starting position and heading of the turtle are stored into variables before the turtle is moved.

    startPos = t.pos()
    startHeading = t.heading()

After the drawing is finished, the turtle is returned with the pen up to the starting position and heading.

    # RESET TURTLE
    t.pu()
    t.setpos(startPos)
    t.setheading(startHeading)

The last lines of the code after the drawShip function are not within a function so are executed when the program first begins.

First, the color of the screen is set to black.

screen.bgcolor("black")

Then the turtle is hidden.

t.ht()

Then the drawing speed of the turtle is set to 0, which causes it to have no animation and immediately appear fully drawn.

t.speed(0)

Finally, the drawShip function is called, passing the a string value to be used as the color of the ship.

drawShip("blue")

To see the turtle draw the ship slowly, comment out the line that hides the turtle and set the speed of the drawing animation to 10.

screen.bgcolor("black")
#t.ht()
t.speed(10)
drawShip("blue")

Run the program now to see how the drawFunction draws the ship.

When done, comment the line that hides the turtle back in and set the speed of the turtle back to 0. It is important that the drawing of the ship is instantaneous before we begin animating it across the screen!


Knowing the API

You might wonder how you could discover all of the objects (such as turtle and screen) and the functions and properties of those objects that you have at your disposal.

One method is through your IDEs autocomplete feature. Autocomplete makes suggestions as you type, based in part on what modules you have imported. For IDLE, the autocomplete feature has a slight delay when typing and only works if the Shell/console windown is open.



Another way to learn about the pre-existing objects, properties, and functions that are included in with a language or with an external module is to locate the API.

An API, or Application Programming Interface, is the documentation that describes the contents of a programming language or external library of code (such as python modules).

The API for the Python language provides detail about all the commands, keywords, objects, functions, properties, operators, and so forth that are part of the core language.

One Python API: www.w3schools.com/python

Another Python API: docs.python.org/3.3/

APIs for a modules are also normally available online.

An API for Turtle can be found at docs.python.org/3.3/library/turtle.html

A large part of learning to program in a certain language is learning the API. This takes time and tenacity! In this course, you will use only a small fraction of what is provided by the core language and the modules you import.


Code Redundancy

To create another player you might be tempted to start copying and pasting code to create a drawShip2 function. Do not do this!

If you duplicate blocks of code, your code will quickly become unwieldy and unmanageable. Besides making the program much longer, modifying redundant code is more work. If a block of code is repeated 10 times, any change to that code needs to be made in 10 places. Besides being tedious to maintain, redundant code leads to errors when a change is made in one part of the code and not another.

There are many ways to avoid redundant code. In fact, placing the code in a function is one such way. A function can be called multiple times from various places in the program instead of re-typing all the code within the function over and over throughout the program.

In this case, the drawShip function by itself is not sufficient because we do not want to keep drawing the same ship over and over. This function uses the only turtle we have, and we will need another turtle for the second player. Yes you can have multiple turtles!

While there are ways to modify this function to make it more reusable by different players (such as by passing the turtle as a parameter), a more elegant and useful solution to avoid code redundancy is to use classes.


Player Class

A class is what objects are created from. If you want to have Player objects, you must have a Player class that defines what a Player is.

Classes are made up of properties (variables) and methods (functions). The properties define the attributes of objects created from the class, and the methods define the behaviors of the objects.

Properties are used to store data about the object. A Player might have properties such as name, rating, score, strength, speed, experience points (xp) and so on. Methods are used to define what the object can do. A Player might have methods such as move, shoot, explode, levelUp, takeDamage, and so on.

Above the drawShip function, define a Player class with a single property to store the player's name.

t = turtle.Turtle()
screen = turtle.Screen()

class Player:
    name = "Mario"

def drawShip(color):

Just like other structures in Python, everything indented under the class definition belongs to the class.

Now you have a Player class with one property!


Player Objects

Using the Player class just defined, create some Player objects.

screen.bgcolor("black")

p1 = Player()
p2 = Player()
p2.name = "Luigi"

print(p1)
print(p1.name)
print(p2)
print(p2.name)
print(Player.name)

t.ht()
t.speed(0)
drawShip("blue")

Here, the Player() function is being called twice to create two Players. This function does not need to be defined. For every class, a function with the same name is automatically generated. This function is known as the constructor because it is used to construct new objects.

When called, the constructor fuction returns the object it creates. The Player objects returned by the Player() constructor are being stored in the p1 and p2 variables.

The act of creating an object from a class is called instantiation. By calling the constructor, an instance of that class if being created.

Dot notation can be used on objects to access or modify the object's properties; or to call the object's methods. Dot notation is used here on p2 to change the name property to Luigi.

The subsequent print statements result in:

Cannot load image

Printing p1 and p2 directly, you see that they are both storing a Player object at different locations in memory. The memory addresses are displayed in hexadecimal format.

Printing p1.name and p2.name demonstates that each object has its own separate name property. Changing the name of one player does not change the name of other players.

Finally, notice the class itself has a name property. Printing the value of this variable could be done even if a Player object was never created.


__init__

In Python, the built-in __init__ function is automatically called by the constructor to perform any initalization needed. You can override this method to set initial values for the object being created.

Delete the name property from the Player class.

Then define the __init__ function with two parameters named self and color.

class Player:
    def __init__(self, color):
        self.color = color

You should never call the __init__ method directly. Remember, it is automatically called by the constructor every time you create a Player object.

The first parameter is used as a reference to the object being created. You can name the variable anything you like, but in Python it is common to use self since it is a reference to itself (the object being created).

The statement inside the __init__ function stores the colorvalue passed to the function into the color property of the object being created.

Because the color variable is defined within the __init__ method, it only exists within that method. Variables defined within a block of code are known as local variables and are said to have limited scope. They cannot be accessed from outside of the block of code where they are defined.

Variables that are defined outside of any block of code are called global variables because they exist for the duration of the program and can be accessed from anywhere in the code.

The parameters of a function, such as self and color, are always local variables that are limited in scope to only the function they belong to. Because color is a local variable, it only exists within this function while the class variable self.color will exist for each Player object created, even outside of the __init__ function and the Player class.

Delete the p2 object and remove the print statements.

Then, update the initialization of p1 by passing a string value for the color that will be used for the player's ship.

screen.bgcolor("black")

p1 = Player("blue")

t.ht()
t.speed(0)
drawShip("blue")

When calling the constructor to instantiate an object, the first parameter of the constructor will always be the object being created (the self). This is automatic, so no argument needs to be added for the first parameter when calling the constructor. The string blue will be sent as the second argument.

The color of the ship is not the only property that needs to be initialized when creating a ship, but it is the only one for now that will be set through the constructor.

What else does every Player need?

Each player needs to have their own separate Turtle that will be used to store their ship's location and heading, and to draw their ship. Also, each player will need to have their own drawShip method that draws just their own ship using their own turtle.

Start by highlighting all of the code that makes up the drawShip method and press tab.



By tabbing over the drawShip function, it is now a method of the Player class; and each Player that is created will have its own drawShip method.

Delete the original turtle that was stored in the t variable and instead create a turtle object for each Player created.

import turtle

screen = turtle.Screen()

class Player:
    def __init__(self, color):
        self.color = color
        self.turtle = turtle.Turtle()

The turtle created for a player when it is created is now stored in the object's turtle property.

Next, hide the turtle and set its animation speed to 0.

class Player:
    def __init__(self, color):
        self.color = color
        self.turtle = turtle.Turtle()
        self.turtle.ht()
        self.turtle.speed(0)

Change the code at the bottom of the script to match the changes made to the Player class.

The original turtle saved to t had been deleted, so delete the code that was used to hide the turtle and sets its speed to 0. Also, use dot notation to call the drawShip method of the p1 Player.

screen.bgcolor("black")

p1 = Player("blue")
p1.drawShip()

Notice the string representing a color was also deleted from the drawShip function call. Because each drawShip now belongs to a specific player, the drawShip will have access to the player's color property that was initialized through the Player constructor. There is no longer a need to pass the color to the drawShip method.

The last change needed to finish converting our program to an Object Oriented approach is to update the drawShip method.

The methods we add for the Player should all have a parameter of self so it can access the properties of the specific object the method belongs to. This includes the drawShip method.

Also, as mentioned above, the drawShip method no longer needs a parameter for color.

Change the parameter for drawShip from color to self.

  def drawShip(self):
      startPos = t.pos()
      startHeading = t.heading()

The global variable t no longer exists (you deleted it from the top of the code). In the drawShip method, you now need to use the turtle of the Player that this method belongs to for drawing the ship.

You could change every instance of t to self.turtle. For example, the first command under the
# DRAW BODY
comment would be
self.turtle.fillcolor(color).
Making all of these changes might be a bit tedious.

Instead, create a new t as a local variable inside of the drawShip method and set it equal to self.turtle.

  def drawShip(self):
      t = self.turtle
      startPos = t.pos()
      startHeading = t.heading()

Now, both t and self.turtle both refer to the the Player's turtle.

There is also no longer a color variable since it was removed from the paramter list. Use the Player's color property to set the color for the ship.

      # DRAW BODY
      t.fillcolor(self.color)
      t.pencolor(self.color);

Run the program to ensure the ship is still being drawn to the screen just as it was prior to this lesson.

While the functionality of the program has not changed, adapting the code to an object oriented approach will greatly simplify the coding of the rest of the game that is covered in the next lessons.