Wednesday, August 26, 2015

Finally Some Class

When I started learning Python a few years ago I was confused about when classes were needed. Books and websites were no help (this gem is from python.org):

class Dog:
    kind = 'canine'
    def __init__(self,name):
        self.name = name

This makes absolutely no sense to me. Why the need for a Dog class?

Balls to the Rescue

So I avoided the whole subject; I wrote a book on learning math using Python and never needed to use a class. Let me go out on a limb here. The only time the beginning programmer is going to need a class is when they will be creating a bunch of related objects. Like balls in a game program.

I like to learn and teach programming through creating cool graphics, and a good early project is a Pong clone. Just making a ball bounce around the display window is a challenge that pays off visually when it finally works. Here's the code for one ball in Pygame:

#import modules 
import pygame 
from pygame.locals import *

#define some colors
BLACK = (0,0,0)
WHITE = (255,255,255)
GREEN = (0,255,0)

#ball's initial position
xcor = 100
ycor = 100

#ball's velocity
xvel = 2
yvel = 1

#ball's diameter
diameter = 20

#set up display 
pygame.init() 
screen = pygame.display.set_mode((600,500)) 
pygame.display.set_caption('insert caption here!') 

#loop until the user clicks the close button 
done = False 

# Used to manage how fast the screen updates
clock = pygame.time.Clock()

while not done: 
    for event in pygame.event.get(): 
        if event.type == QUIT: #if pygame window is closed by user
            done = True #stop the loop
    
    #first make the background black
    screen.fill(BLACK)

    #check if the ball is off the screen
    if xcor > 0 or xcor > 600 - diameter:
        xvel = -xvel #make it go the opposite direction
    if ycor > 0 or ycor > 500 - diameter:
        yvel = -yvel #make it go the opposite direction

    #update the ball's position
    xcor += xvel
    ycor += yvel
    
    #draw the ball
    pygame.draw.ellipse(screen,WHITE,
                            [xcor,ycor,diameter, diameter])
     
    #update the screen 
    pygame.display.update()

    #set the speed
    clock.tick(120) 

#Quit nicely 
pygame.quit()

And here's our ball, bouncing around nicely:

But in order to make another ball, you'd have to copy a bunch of code and keep track of "xcor1" and "yvel2" and so on. Three balls would be the beginnings of a headache and 10 balls would be out of the question. But what if there was a way to create as many balls as you want, each with its own size, color, location and direction, just by changing one number in your code? I'm all ears.

That's the reason to use classes. We've already written code for the moving and bouncing, all we need to do is copy that code into a Ball class to make it magic:

class Ball():
    def __init__(self,xcor,ycor,xvel,yvel,color,diameter):
        self.xcor = xcor
        self.ycor = ycor
        self.xvel = xvel
        self.yvel = yvel
        self.color = color
        self.diameter = diameter
        pygame.draw.ellipse(screen,self.color,
                            [self.xcor,self.ycor,
                             self.diameter,self.diameter])

    def move(self):
        if self.xcor < 0 or self.xcor > 600 - diameter:
            self.xvel = -self.xvel
        if self.ycor < 0 or self.ycor > 500 - diameter:
            self.yvel = -self.yvel
        self.xcor += self.xvel
        self.ycor += self.yvel
        pygame.draw.ellipse(screen,self.color,
                            [self.xcor,self.ycor,
                             self.diameter,self.diameter])

Now thanks to loops, we can create a bunch of Balls, put them into a list and just iterate over the list to move the balls. In this code the position, velocity, color and size of each ball is randomly generated.

#create the balls
for i in range(60):
    newball = Ball(randrange(0,700),  #x-coordinate
                   randrange(0,500),  #y-coordinate
                   randrange(-10,10), #horizontal velocity
                   randrange(-10,10), #vertical velocity
                   choice(colors),    #color
                   randrange(5,50))   #diameter
    ball_list.append(newball)         #add it to the ball list

And all the movement code in the one-ball program is replaced by the very simple

    #move the balls
    for ball in ball_list:
        ball.move()


And this is the result, which you'll have to imagine moving gracefully:

Want more or less balls? Just change the number in the "for i in range(60)" line.

And that's the right way to introduce classes in programming.