Come introdurre la programmazione orientata agli oggetti?

Alcuni concetti dalla lezione precedente

  • Fare breccia sugli studenti è difficile
  • La scelta di un linguaggio che permette di non dover fare salti di fede aiuta ad andare dritti al punto
  • È opportuno scegliere problemi
    • in contesti alla portata degli studenti
    • di cui è chiaro il valore della soluzione
    • e che non si prestano a essere risolti "a mano"

Ma soprattutto...

...stringere un patto formativo, nel nostro caso...

...un semplice videogioco

Ma prima...

Mettiamo alla prova la nostra conoscenza di python su alcuni problemi di Project Euler

https://projecteuler.net/problems

Problema 1

Se elenchiamo tutti i numeri naturali minori di dieci che sono multipli di 3 o di 5, otteniamo 3, 5, 6 e 9. La somma di tali multipli è 23.

Trovare la somma di tutti i multipli di 3 o di 5 minori di 1000.

La slide successiva contiene una possibile soluzione. Non sbirciate!

Una soluzione del problema 1


sum([i for i in range(1000) if i % 3 == 0 or i % 5 == 0])
            

Ma andava bene anche...


n = 0
for i in range(1, 1000):
  if i % 5 == 0 or i % 3 == 0:
    n = n + i

print n
            

Valore del problema

Facile da formulare e da comprendere, non affrontabile senza PC

Ma è difficile convincere gli studenti del fatto che la soluzione sia quella corretta

Problema 2

Ogni nuovo elemento nella sequenza di Fibonacci è generato calcolando la somma dei due termini precedenti. Iniziando con 1 e 2, i primi 10 elementi saranno:

1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ...

Considerando gli elementi nella sequenza di Fibonacci il cui valore non supera 4 milioni, trovare la somma di tutti gli elementi pari.

Una soluzione del problema 2


f = 1
f_prev = 1
f_prev_prev = 0

sum = 0

while(f<4000000):
  if f % 2 == 0:
    sum += f
  f_prev_prev = f_prev
  f_prev = f
  f = f_prev + f_prev_prev

print sum
            

Valore del problema

Come per il problema 1

Problema 3

I fattori primi di 13195 sono 5, 7, 13 e 29.

Qual è il più grande fattore primo del numero 600851475143 ?

Una soluzione del problema 3


import math

n = 600851475143
i = 2

while(i < math.sqrt(n)):
  while(n % i == 0):
    n = n / i

  i += 1

print n
            

Valore del problema

È facile convincere gli studenti del fatto che la soluzione non è sbagliata (basta verificare che l'output fornito sia primo e divisore del numero dato)

Posso sfidare gli studenti a chi trova il divisore primo più alto

Problema 4

Un numero palindromo non cambia se viene letto al contrario. Il più alto numero palindromo ottenibile come prodotto di numeri di due cifre è 9009 = 91 × 99.

Trovare il più alto numero palindromo ottenibile come prodotto di numeri di tre cifre

Una soluzione del problema 4


def reverse(s):
  return int(str(s)[::-1])

print max([i*j for i in range(100, 1000) for j in range(100, 1000)
	if i*j == reverse(i*j)])
            

Valore del problema

Come per il problema 3

Il problema è percepibile più come enigmistico che come matematico

Problema 5

2520 è il più piccolo numero che può essere diviso per tutti i numeri da 1 a 10 senza ottenere resto.

Qual è il più piccolo numero positivo divisibile per tutti i numeri da 1 a 20 senza ottenere resto?

Una soluzione del problema 5


def mcd(a, b):
  return b and mcd(b, a%b) or a

def mcm(a, b):
  return a * b / mcd(a, b)

n = 1
for i in range(1, 21):
  n *= mcm(n, i)

print n
            

Valore del problema

Come per il problema 4

La soluzione offre uno spunto interdisciplinare con la matematica

Problema 6

La somma dei quadrati dei primi dieci numeri naturali è

12 + 22 + ... + 102 = 385

Il quadrato della somma dei primi dieci numeri naturali è

(1 + 2 + ... + 10)2 = 552 = 3025

Dunque la differenza tra il quadratto della somma dei primi dieci numeri naturali e la somma dei quadrati degli stessi numeri è 3025 − 385 = 2640.

Trovare la differenza tra il quadrato della somma dei primi cento numeri naturali e la somma dei quadrati degli stessi numeri.

Una soluzione del problema 4


def sumsq(n):
  return n * (n+1) * (2*n+1) / 6

def sqsum(n):
  return (n * (n+1) / 2) ** 2

print sqsum(100) - sumsq(100)
            

Valore del problema

Come per il problema 5

Definizione di classi in Python


class Employee:
   'Common base class for all employees'
   empCount = 0

   def __init__(self, name, salary):
      self.name = name
      self.salary = salary
      Employee.empCount += 1

   def displayCount(self):
     print "Total Employee %d" % Employee.empCount

   def displayEmployee(self):
      print "Name : ", self.name,  ", Salary: ", self.salary
          
  • Docstring
  • Variabili di classe
  • Variabili di istanza
  • Costruttori
  • Metodi

Uso di classi in Python


>>> emp1 = Employee('Zara', 2000)
>>> emp2 = Employee('Manni', 5000)
>>> emp1.displayEmployee()
Name :  Zara , Salary:  2000
>>> emp2.displayEmployee()
Name :  Manni , Salary:  5000
>>> print 'Total Employee %d' % Employee.empCount
Total Employee 2
>>>
          
  • Docstring
  • Variabili di classe
  • Variabili di istanza
  • Costruttori
  • Metodi

Uso di classi in Python (2)


>>> emp1.age = 7  # Aggiunge una variable di istanza
>>> emp1.age = 8  # Modifica una variable di istanza
>>> del emp1.age  # Cancella una variable di istanza

>>> hasattr(emp2, 'age')
False
>>> setattr(emp2, 'age', 8)
>>> hasattr(emp2, 'age')
True
>>> getattr(emp2, 'age')
8
>>> delattr(emp2, 'age')
>>> hasattr(emp2, 'age')
False

          

Variabili private



class JustCounter:
   __secretCount = 0

   def count(self):
      self.__secretCount += 1
      print self.__secretCount

>>> counter = JustCounter()
>>> counter.count()
1
>>> counter.count()
2
>>> print counter.__secretCount
...
AttributeError: JustCounter instance has no attribute '__secretCount'
>>> print counter._JustCounter__secretCount
2
          

C'è altro

Ereditarietà, overriding, overloading...

Ma torniamo al nostro patto

Tanto per cominciare


# game-1.py
import pygame
screen = pygame.display.set_mode((1024, 768))
pygame.init()
car = pygame.image.load('Car.png')
screen.blit(car, (50, 100))
while 1:
  for i in range(100):
    screen.fill((0, 0, 0))
    screen.blit(car, (i, 0))
    pygame.display.flip()
          

Facciamo di meglio


# game-2.py
# INTIALISATION
import pygame, math, sys
from pygame.locals import *
screen = pygame.display.set_mode((1024, 768))
car = pygame.image.load('Car.png')
clock = pygame.time.Clock()
k_up = k_down = k_left = k_right = 0
speed = direction = 0
position = (100, 100)
MAX_FORWARD_SPEED = 10
MAX_REVERSE_SPEED = 5
BLACK = (0, 0, 0)

while 1:
    # USER INPUT
    clock.tick(30)
    for event in pygame.event.get():
        if not hasattr(event, 'key'): continue
        down = event.type == KEYDOWN     # key down or up?
        if event.key == K_RIGHT: k_right = down * -5
        elif event.key == K_LEFT: k_left = down * 5
        elif event.key == K_UP: k_up = down * 2
        elif event.key == K_DOWN: k_down = down * -2
        elif event.key == K_ESCAPE: sys.exit(0)     # quit the game
    screen.fill(BLACK)
    # SIMULATION
    # .. new speed and direction based on acceleration and turn
    speed += (k_up + k_down)
    if speed > MAX_FORWARD_SPEED: speed = MAX_FORWARD_SPEED
    if speed < MAX_REVERSE_SPEED: speed = MAX_REVERSE_SPEED
    direction += (k_right + k_left)
    # .. new position based on current position, speed and direction
    x, y = position
    rad = direction * math.pi / 180
    x += -speed*math.sin(rad)
    y += -speed*math.cos(rad)
    position = (x, y)
    # RENDERING
    # .. rotate the car image for direction
    rotated = pygame.transform.rotate(car, direction)
    # .. position the car on screen
    rect = rotated.get_rect()
    rect.center = position
    # .. render the car to screen
    screen.blit(rotated, rect)
    pygame.display.flip()
          

In versione a oggetti


# game-3.py
import pygame, math, sys
from pygame.locals import *
screen = pygame.display.set_mode((1024, 768))
clock = pygame.time.Clock()

class CarSprite(pygame.sprite.Sprite):
  MAX_FORWARD_SPEED = 10
  MAX_REVERSE_SPEED = 10
  ACCELERATION = 2
  TURN_SPEED = 5

  def __init__(self, image, position):
    pygame.sprite.Sprite.__init__(self)
    self.src_image = pygame.image.load(image)
    self.position = position
    self.speed = self.direction = 0
    self.k_left = self.k_right = self.k_down = self.k_up = 0

  def update(self):
    self.speed += self.k_up + self.k_down
    if self.speed > self.MAX_FORWARD_SPEED:
      self.speed = self.MAX_FORWARD_SPEED
    if self.speed < -self.MAX_FORWARD_SPEED:
      self.speed = - self.MAX_FORWARD_SPEED
    self.direction += self.k_right + self.k_left
    x, y = self.position
    rad = self.direction * math.pi / 180
    x += -self.speed * math.sin(rad)
    y += -self.speed * math.cos(rad)
    self.position = (x, y)
    self.image = pygame.transform.rotate(self.src_image, self.direction)
    self.rect = self.image.get_rect()
    self.rect.center = self.position

rect = screen.get_rect()
car = CarSprite('car.png', rect.center)
car_group = pygame.sprite.RenderPlain(car)
while 1:
  deltat = clock.tick(30)
  for event in pygame.event.get():
    if not hasattr(event, 'key'): continue
    down = event.type == KEYDOWN
    if event.key == K_RIGHT: car.k_right = down *-5
    elif event.key == K_LEFT: car.k_left = down * 5
    elif event.key == K_UP: car.k_up = down * 2
    elif event.key == K_DOWN: car.k_down = down * -2
    elif event.key == K_ESCAPE: sys.exit(0)

  screen.fill((0, 0, 0))
  car_group.update()
  car_group.draw(screen)
  pygame.display.flip()

          

Aggiungendo funzionalità


# game-4.py
import pygame, math, sys
from pygame.locals import *
screen = pygame.display.set_mode((1024, 768))
clock = pygame.time.Clock()

class CarSprite(pygame.sprite.Sprite):
  MAX_FORWARD_SPEED = 10
  MAX_REVERSE_SPEED = 10
  ACCELERATION = 2
  TURN_SPEED = 5

  def __init__(self, image, position):
    pygame.sprite.Sprite.__init__(self)
    self.src_image = pygame.image.load(image)
    self.position = position
    self.speed = self.direction = 0
    self.k_left = self.k_right = self.k_down = self.k_up = 0

  def update(self):
    self.speed += self.k_up + self.k_down
    if self.speed > self.MAX_FORWARD_SPEED:
      self.speed = self.MAX_FORWARD_SPEED
    if self.speed < -self.MAX_FORWARD_SPEED:
      self.speed = - self.MAX_FORWARD_SPEED
    self.direction += self.k_right + self.k_left
    x, y = self.position
    rad = self.direction * math.pi / 180
    x += -self.speed * math.sin(rad)
    y += -self.speed * math.cos(rad)
    self.position = (x, y)
    self.image = pygame.transform.rotate(self.src_image, self.direction)
    self.rect = self.image.get_rect()
    self.rect.center = self.position

class PadSprite(pygame.sprite.Sprite):
  normal = pygame.image.load('pad_normal.png')
  hit = pygame.image.load('pad_hit.png')

  def __init__(self, position):
    pygame.sprite.Sprite.__init__(self)
    self.rect = pygame.Rect(self.normal.get_rect())
    self.rect.center = position

  def update(self, hit_list):
    if self in hit_list:
      self.image = self.hit
    else:
      self.image = self.normal

pads = [
  PadSprite((200, 200)),
  PadSprite((800, 200)),
  PadSprite((200, 600)),
  PadSprite((800, 600)),
]

pad_group = pygame.sprite.RenderPlain(*pads)

rect = screen.get_rect()
car = CarSprite('Car.png', rect.center)
car_group = pygame.sprite.RenderPlain(car)
while 1:
  deltat = clock.tick(30)
  for event in pygame.event.get():
    if not hasattr(event, 'key'): continue
    down = event.type == KEYDOWN
    if event.key == K_RIGHT: car.k_right = down *-5
    elif event.key == K_LEFT: car.k_left = down * 5
    elif event.key == K_UP: car.k_up = down * 2
    elif event.key == K_DOWN: car.k_down = down * -2
    elif event.key == K_ESCAPE: sys.exit(0)

  screen.fill((0, 0, 0))
  car_group.update()
  collisions = pygame.sprite.spritecollide(car, pad_group, False)
  pad_group.update(collisions)
  pad_group.draw(screen)
  car_group.draw(screen)
  pygame.display.flip()

          

Aggiungendo funzionalità (2)


# game-5.py
import pygame, math, sys
from pygame.locals import *
screen = pygame.display.set_mode((1024, 768))
clock = pygame.time.Clock()

class CarSprite(pygame.sprite.Sprite):
  MAX_FORWARD_SPEED = 10
  MAX_REVERSE_SPEED = 10
  ACCELERATION = 2
  TURN_SPEED = 5

  def __init__(self, image, position):
    pygame.sprite.Sprite.__init__(self)
    self.src_image = pygame.image.load(image)
    self.position = position
    self.speed = self.direction = 0
    self.k_left = self.k_right = self.k_down = self.k_up = 0

  def update(self):
    self.speed += self.k_up + self.k_down
    if self.speed > self.MAX_FORWARD_SPEED:
      self.speed = self.MAX_FORWARD_SPEED
    if self.speed < -self.MAX_FORWARD_SPEED:
      self.speed = - self.MAX_FORWARD_SPEED
    self.direction += self.k_right + self.k_left
    x, y = self.position
    rad = self.direction * math.pi / 180
    x += -self.speed * math.sin(rad)
    y += -self.speed * math.cos(rad)
    self.position = (x, y)
    self.image = pygame.transform.rotate(self.src_image, self.direction)
    self.rect = self.image.get_rect()
    self.rect.center = self.position

class PadSprite(pygame.sprite.Sprite):
  normal = pygame.image.load('pad_normal.png')
  hit = pygame.image.load('pad_hit.png')

  def __init__(self, number, position):
    pygame.sprite.Sprite.__init__(self)
    self.number = number
    self.rect = pygame.Rect(self.normal.get_rect())
    self.rect.center = position
    self.image = self.normal

  def update(self, hit_list):
    if self in hit_list:
      self.image = self.hit
    else:
      self.image = self.normal

pads = [
  PadSprite(1, (200, 200)),
  PadSprite(2, (800, 200)),
  PadSprite(3, (200, 600)),
  PadSprite(4, (800, 600)),
]
current_pad_number = 0

pad_group = pygame.sprite.RenderPlain(*pads)

rect = screen.get_rect()
car = CarSprite('Car.png', rect.center)
car_group = pygame.sprite.RenderPlain(car)
while 1:
  deltat = clock.tick(30)
  for event in pygame.event.get():
    if not hasattr(event, 'key'): continue
    down = event.type == KEYDOWN
    if event.key == K_RIGHT: car.k_right = down *-5
    elif event.key == K_LEFT: car.k_left = down * 5
    elif event.key == K_UP: car.k_up = down * 2
    elif event.key == K_DOWN: car.k_down = down * -2
    elif event.key == K_ESCAPE: sys.exit(0)

  screen.fill((0, 0, 0))
  car_group.update()
  pads = pygame.sprite.spritecollide(car, pad_group, False)
  if pads:
    pad = pads[0]
    if pad.number == current_pad_number + 1:
      pad.image = pad.hit
      current_pad_number += 1
  elif current_pad_number == 4:
    for pad in pad_group.sprites():
      pad.image = pad.normal
      current_pad_number = 0

  pad_group.draw(screen)
  car_group.draw(screen)
  pygame.display.flip()
          

Aggiungendo funzionalità (3)


# game-7.py
import pygame, math, sys
from pygame.locals import *
WIDTH = 1024
HEIGHT = 768
screen = pygame.display.set_mode((WIDTH, HEIGHT))
clock = pygame.time.Clock()

class CarSprite(pygame.sprite.Sprite):
  MAX_FORWARD_SPEED = 10
  MAX_REVERSE_SPEED = 10
  ACCELERATION = 2
  TURN_SPEED = 5

  def __init__(self, image, position):
    pygame.sprite.Sprite.__init__(self)
    self.src_image = pygame.image.load(image)
    self.position = position
    self.speed = self.direction = 0
    self.k_left = self.k_right = self.k_down = self.k_up = 0

  def update(self):
    self.speed += self.k_up + self.k_down
    if self.speed > self.MAX_FORWARD_SPEED:
      self.speed = self.MAX_FORWARD_SPEED
    if self.speed < -self.MAX_FORWARD_SPEED:
      self.speed = - self.MAX_FORWARD_SPEED
    self.direction += self.k_right + self.k_left
    x, y = self.position
    rad = self.direction * math.pi / 180
    x += -self.speed * math.sin(rad)
    y += -self.speed * math.cos(rad)
    self.position = (x, y)
    self.image = pygame.transform.rotate(self.src_image, self.direction)
    self.rect = self.image.get_rect()
    self.rect.center = self.position

class PadSprite(pygame.sprite.Sprite):
  normal = pygame.image.load('pad_normal.png')
  hit = pygame.image.load('pad_hit.png')

  def __init__(self, number, position):
    pygame.sprite.Sprite.__init__(self)
    self.number = number
    self.rect = pygame.Rect(self.normal.get_rect())
    self.rect.center = position
    self.image = self.normal

  def update(self, hit_list):
    if self in hit_list:
      self.image = self.hit
    else:
      self.image = self.normal

pad_rho = 300
curr_pad_theta = 0


pads = [PadSprite(i, (WIDTH/2+pad_rho * math.cos(curr_pad_theta + (i-1)*math.pi/2),
                    HEIGHT/2+pad_rho * math.sin(curr_pad_theta + (i-1)*math.pi/2)))
        for i in range(1, 5)]

current_pad_number = 0

pad_group = pygame.sprite.RenderPlain(*pads)

rect = screen.get_rect()
car = CarSprite('Car.png', rect.center)
car_group = pygame.sprite.RenderPlain(car)

background = pygame.image.load('track.png')
screen.blit(background, (0, 0))
while 1:
  deltat = clock.tick(30)
  for event in pygame.event.get():
    if not hasattr(event, 'key'): continue
    down = event.type == KEYDOWN
    if event.key == K_RIGHT: car.k_right = down *-5
    elif event.key == K_LEFT: car.k_left = down * 5
    elif event.key == K_UP: car.k_up = down * 2
    elif event.key == K_DOWN: car.k_down = down * -2
    elif event.key == K_ESCAPE: sys.exit(0)

  screen.fill((0, 0, 0))
  car_group.update()
  hit_pads = pygame.sprite.spritecollide(car, pad_group, False)
  if hit_pads:
    hit_pad = hit_pads[0]
    if hit_pad.number == current_pad_number + 1:
      hit_pad.image = hit_pad.hit
      current_pad_number += 1
  elif current_pad_number == 4:
    for pad in pad_group.sprites():
      pad.image = pad.normal
      current_pad_number = 0
  curr_pad_theta += .03
  for i in range(4):
    pads[i].rect.center = \
    (WIDTH/2+pad_rho * math.cos(curr_pad_theta + (i)*math.pi/2),
                    HEIGHT/2+pad_rho * math.sin(curr_pad_theta + (i)*math.pi/2))

  pad_group.clear(screen, background)
  car_group.clear(screen, background)

  pad_group.draw(screen)
  car_group.draw(screen)
  pygame.display.flip()
          

Aggiungendo funzionalità (4)


# game-8.py
import pygame, math, sys
from pygame.locals import *
WIDTH = 1024
HEIGHT = 768
screen = pygame.display.set_mode((WIDTH, HEIGHT))
clock = pygame.time.Clock()

pygame.init()

def texts(score):
   font = pygame.font.Font(None,30)
   scoretext = font.render("Score:" + str(score), 1, (255, 255, 255))
   screen.blit(scoretext, (50, 50))

class CarSprite(pygame.sprite.Sprite):
  MAX_FORWARD_SPEED = 10
  MAX_REVERSE_SPEED = 10
  ACCELERATION = 2
  TURN_SPEED = 5

  def __init__(self, image, position):
    pygame.sprite.Sprite.__init__(self)
    self.src_image = pygame.image.load(image)
    self.position = position
    self.speed = self.direction = 0
    self.k_left = self.k_right = self.k_down = self.k_up = 0

  def update(self):
    self.speed += self.k_up + self.k_down
    if self.speed > self.MAX_FORWARD_SPEED:
      self.speed = self.MAX_FORWARD_SPEED
    if self.speed < -self.MAX_FORWARD_SPEED:
      self.speed = - self.MAX_FORWARD_SPEED
    self.direction += self.k_right + self.k_left
    x, y = self.position
    rad = self.direction * math.pi / 180
    x += -self.speed * math.sin(rad)
    y += -self.speed * math.cos(rad)
    self.position = (x, y)
    self.image = pygame.transform.rotate(self.src_image, self.direction)
    self.rect = self.image.get_rect()
    self.rect.center = self.position

class PadSprite(pygame.sprite.Sprite):
  normal = pygame.image.load('pad_normal.png')
  hit = pygame.image.load('pad_hit.png')

  def __init__(self, number, position):
    pygame.sprite.Sprite.__init__(self)
    self.number = number
    self.rect = pygame.Rect(self.normal.get_rect())
    self.rect.center = position
    self.image = self.normal

  def update(self, hit_list):
    if self in hit_list:
      self.image = self.hit
    else:
      self.image = self.normal

pad_rho = 300
curr_pad_theta = 0


pads = [PadSprite(i, (WIDTH/2+pad_rho * math.cos(curr_pad_theta + (i-1)*math.pi/2),
                    HEIGHT/2+pad_rho * math.sin(curr_pad_theta + (i-1)*math.pi/2)))
        for i in range(1, 5)]

current_pad_number = 0

pad_group = pygame.sprite.RenderPlain(*pads)

rect = screen.get_rect()
car = CarSprite('Car.png', rect.center)
car_group = pygame.sprite.RenderPlain(car)

score = 0

while 1:
  deltat = clock.tick(30)
  for event in pygame.event.get():
    if not hasattr(event, 'key'): continue
    down = event.type == KEYDOWN
    if event.key == K_RIGHT: car.k_right = down *-5
    elif event.key == K_LEFT: car.k_left = down * 5
    elif event.key == K_UP: car.k_up = down * 2
    elif event.key == K_DOWN: car.k_down = down * -2
    elif event.key == K_ESCAPE: sys.exit(0)

  screen.fill((0, 0, 0))
  car_group.update()
  hit_pads = pygame.sprite.spritecollide(car, pad_group, False)
  if hit_pads:
    hit_pad = hit_pads[0]
    if hit_pad.number == current_pad_number + 1:
      hit_pad.image = hit_pad.hit
      current_pad_number += 1
      score += 1
    else:
      score -= 1
  elif current_pad_number == 4:
    for pad in pad_group.sprites():
      pad.image = pad.normal
      current_pad_number = 0
      score += 10
  curr_pad_theta += .03
  for i in range(4):
    pads[i].rect.center = \
    (WIDTH/2+pad_rho * math.cos(curr_pad_theta + (i)*math.pi/2),
                    HEIGHT/2+pad_rho * math.sin(curr_pad_theta + (i)*math.pi/2))


  pad_group.draw(screen)
  car_group.draw(screen)
  texts(score)
  pygame.display.flip()
          

Semplificazione del codice


# game-9.py
import pygame, math, sys
from pygame.locals import *

WIDTH = 1024
HEIGHT = 768
screen = pygame.display.set_mode((WIDTH, HEIGHT))
clock = pygame.time.Clock()

pygame.init()

def texts(score):
   font = pygame.font.Font(None,30)
   scoretext = font.render("Score:" + str(score), 1, (255, 255, 255))
   screen.blit(scoretext, (50, 50))

class CarSprite(pygame.sprite.Sprite):
  MAX_FORWARD_SPEED = 10
  MAX_REVERSE_SPEED = 10
  ACCELERATION = 2
  TURN_SPEED = 5

  def __init__(self, image, position):
    pygame.sprite.Sprite.__init__(self)
    self.src_image = pygame.image.load(image)
    self.position = position
    self.speed = self.direction = 0
    self.k_left = self.k_right = self.k_down = self.k_up = 0

  def update(self):
    self.speed += self.k_up + self.k_down
    if self.speed > self.MAX_FORWARD_SPEED:
      self.speed = self.MAX_FORWARD_SPEED
    if self.speed < -self.MAX_FORWARD_SPEED:
      self.speed = - self.MAX_FORWARD_SPEED
    self.direction += self.k_right + self.k_left
    x, y = self.position
    rad = self.direction * math.pi / 180
    x += -self.speed * math.sin(rad)
    y += -self.speed * math.cos(rad)
    self.position = (x, y)
    self.image = pygame.transform.rotate(self.src_image, self.direction)
    self.rect = self.image.get_rect()
    self.rect.center = self.position

class PadSprite(pygame.sprite.Sprite):
  normal = pygame.image.load('pad_normal.png')
  hit = pygame.image.load('pad_hit.png')

  def __init__(self, number, position):
    pygame.sprite.Sprite.__init__(self)
    self.number = number
    self.rect = pygame.Rect(self.normal.get_rect())
    self.rect.center = position
    self.image = self.normal

  def update(self, hit_list):
    if self in hit_list:
      self.image = self.hit
    else:
      self.image = self.normal

class PadGroup:
  def __init__(self, n):
    self.n = n
    self.pad_rho = 300
    self.curr_pad_theta = 0
    self.pads = [PadSprite(i,
          (WIDTH/2+self.pad_rho * math.cos(self.curr_pad_theta + (i-1)*2*math.pi/self.n),
          HEIGHT/2+self.pad_rho * math.sin(self.curr_pad_theta + (i-1)*2*math.pi/self.n)))
          for i in range(1, self.n+1)]
    self.pad_group = pygame.sprite.RenderPlain(*(self.pads))

  def handle_collisions(self, car, current_pad_number):
    hit_pads = pygame.sprite.spritecollide(car, self.pad_group, False)
    score = 0
    if hit_pads:
      hit_pad = hit_pads[0]
      if hit_pad.number == current_pad_number + 1:
        hit_pad.image = hit_pad.hit
        current_pad_number += 1
        score += 1
      else:
        score -= 1
    elif current_pad_number == 4:
      for pad in pad_group.sprites():
        pad.image = pad.normal
        current_pad_number = 0
        score += 10
    return score

  def draw(self, screen):
    self.pad_group.draw(screen)

  def handle_move(self):
    self.curr_pad_theta += .03
    for i in range(self.n):
      self.pads[i].rect.center = \
        (WIDTH/2+self.pad_rho * math.cos(self.curr_pad_theta + i*2*math.pi/self.n), \
        HEIGHT/2+self.pad_rho * math.sin(self.curr_pad_theta + i*2*math.pi/self.n))


class UserInput:
  def handle_input(self, car):
    for event in pygame.event.get():
      if not hasattr(event, 'key'): continue
      down = event.type == KEYDOWN
      if event.key == K_RIGHT:
        car.k_right = down *-5
      elif event.key == K_LEFT:
        car.k_left = down * 5
      elif event.key == K_UP:
        car.k_up = down * 2
      elif event.key == K_DOWN:
        car.k_down = down * -2
      elif event.key == K_ESCAPE:
        sys.exit(0)


current_pad_number = 0

rect = screen.get_rect()
car = CarSprite('Car.png', rect.center)
car_group = pygame.sprite.RenderPlain(car)

score = 0

input = UserInput()
pad_group = PadGroup(4)

while 1:
  deltat = clock.tick(30)
  input.handle_input(car)

  screen.fill((0, 0, 0))
  car_group.update()

  score += pad_group.handle_collisions(car, current_pad_number)

  pad_group.handle_move()

  pad_group.draw(screen)
  car_group.draw(screen)
  texts(score)
  pygame.display.flip()