Questo file contiene le soluzioni di molti degli esercizi proposti, divise per lezione. Per eseguire i programmi basterà copiare ed incollare la parte che ci interessa nell'editor di Python. Per gli esempi in IDLE bisognerà digitare solo quello che è scritto dopo il prompt >>> (il testo in blu rappresenta la risposta di Python).

LEZIONE 1

ESERCIZIO 1.1 Queste sono le chiamate di funzione richieste. Ho riportato anche le risposte di Python in blu, quelle che otterrete voi potrebbero essere diverse.


>>> import pygame
pygame 2.1.2 (SDL 2.0.18, Python 3.10.0)
Hello from the pygame community. https://www.pygame.org/contribute.html

>>> pygame.init()
(5, 0)

>>> pygame.mouse.get_pos()
(0, 0)

>>> pygame.mixer.get_num_channels()
8

>>> pygame.event.wait()
<Event(4352-AudioDeviceAdded {'which': 0, 'iscapture': 0})>

>>> pygame.time.wait(1000)
1001

LEZIONE 3

ESERCIZI 3.1, 3.2, 3.3 Ecco il risultato di alcuni degli esperimenti suggeriti. Ho inserito dei commenti per rendere più chiaro il codice.


import pygame

pygame.init()
screen = pygame.display.set_mode((1000, 800))    # dimensioni della finestra cambiate
pygame.display.set_caption("Finestra di pygame") # titolo cambiato
screen.fill("aquamarine")                        # uso dei named colors
pygame.display.flip()

ESERCIZIO 3.4 Ricordando che in una tuple RGB il primo byte indica il rosso, il secondo il verde ed il terzo il blu, dobbiamo scrivere, nell'istruzione che colora lo shermo:


screen.fill((0, 255, 0))             # verde
screen.fill((255, 0, 0))             # rosso
screen.fill((150, 0, 0))             # un numero piu' piccolo indica un colore piu' scuro
screen.fill((150, 0, 150))           # viola
screen.fill((255, 100, 0))           # arancione

Il viola si ottiene da rosso + blu (con numeri abbastanza piccoli per renderlo più scuro), l'arancione da rosso + verde in quantità minore. Naturalmente potreste aver scritto numeri diversi ottenendo altre sfumature di colore.

ESERCIZIO 3.5


import pygame

s = input("Immetti il titolo della finestra: ")
pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption(s)
screen.fill("white")
pygame.display.flip()

L'esercizio chiedeva di usare la input() prima di aprire la finestra di pygame (con la #5). Invece l'ordine delle righe #3 e #4 è indifferente.

ESERCIZIO 3.6


import pygame

r = int(input("Immetti il valore per il rosso: "))
g = int(input("Immetti il valore per il verde: "))
b = int(input("Immetti il valore per il blu: "))
pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Il mio primo programma")
screen.fill((r, g, b))
pygame.display.flip()

Stesse considerazioni dell'esercizio precedente.

LEZIONE 4

ESERCIZIO 4.2 Questi sono esempi di liste di punti che potete indicare nel programma prova_surface.py per ottenere i poligoni richiesti. Le immagini che aggiungo di sotto (dove ho aggiunto le coordinate dei punti usando delle istruzioni che impareremo nella Lezione 17) dovrebbero aiutarvi. Ovviamente potreste aver usato liste diverse.


lista_punti = ((200, 100), (200, 400), (400, 400))                # triangolo rettangolo
lista_punti = ((200, 100), (200, 400), (500, 400), (500, 100))    # quadrato
lista_punti = ((300, 100), (200, 300), (300, 500), (400, 300))    # rombo
lista_punti = ((300, 100), (200, 400), (600, 400), (500, 100))    # trapezio
Triangolo rettangolo Triangolo rettangolo
Triangolo rettangolo Triangolo rettangolo

ESERCIZIO 4.4 Per soddisfare le richieste dovete sostituire la riga #12 del programma prova_blit.py con queste (una per volta, ovviamente!):


screen.blit(surf, (0, 0))          # angolo in alto a sinistra
screen.blit(surf, (600, 0))        # angolo in alto a destra
screen.blit(surf, (0, 400))        # angolo in basso a sinistra
screen.blit(surf, (600, 400))      # angolo in basso a destra
screen.blit(surf, (300, 200))      # centro

Questi numeri vanno calcolati tenendo conto che essi rappresentano l'angolo in alto a sinistra topleft del quadrato rosso e del fatto che la nostra finestra è un rettangolo di lato 800x600 pixel ed il quadrato ha lato 200 pixel. Se non avete ben chiaro il sistema di coordinate ripassatelo qui.

LEZIONE 5

ESERCIZIO 5.2 Queste sono le istruzioni in IDLE che soddisfano le richieste con le risposte di Python in blu. L'istruzione r.y += 10 potrebbe essere sostituita da r.top += 10, r.centery += 10 o altre.


>>> import pygame
pygame 2.1.2 (SDL 2.0.18, Python 3.10.0)
Hello from the pygame community. https://www.pygame.org/contribute.html

>>> r = pygame.Rect(0, 0, 100, 50)
>>> r.center
(50, 25)

>>> r.collidepoint(20, 20)
True

>>> r.collidepoint(200, 300)
False

>>> r.y += 10
>>> r.topleft
(0, 10)

>>> r.bottomleft
(0, 60)

>>> r.topright
(100, 10)

>>> r.bottomright
(100, 60)

ESERCIZIO 5.3 Ecco la versione di due_quadrati.py con le modifiche proposte:


import pygame

pygame.init()
screen = pygame.display.set_mode((800, 600))

# creiamo la Surface surf1, la coloriamo di rosso e la posizioniamo sullo schermo
surf1 = pygame.Surface((200, 200))
surf1.fill("red")
rect1 = surf1.get_rect()
rect1.topleft = (50, 50)
screen.blit(surf1, rect1)

# stessa cosa con surf2 (blu)
surf2 = pygame.Surface((100, 100))
surf2.fill("blue")
rect2 = surf2.get_rect()
rect2.topleft = rect1.topright
screen.blit(surf2, rect2)

# aggiorniamo lo schermo
pygame.display.flip()

# terminiamo correttamente il programma
input("Premi INVIO per terminare il programma")
pygame.quit()

ESERCIZIO 5.4 Per ottenere le altre posizioni del quadrato blu dovete sostituire la riga #17 dell'esercizio precedente con queste, nell'ordine:


rect2.topleft = rect1.bottomleft
rect2.topleft = rect1.bottomright
rect2.bottomright = rect1.bottomright
rect2.center = rect1.center
rect2.midtop = rect1.midbottom

In particolare, nella terza istruzione potreste avere avuto idee diverse, come rect2.topleft = rect1.center, ma queste hanno il difetto di basarsi sulle dimensioni date, e di non funzionare se cambiamo le dimensioni dei due quadrati (come richiesto dall'esercizio 5.5).

LEZIONE 6

ESERCIZIO 6.1 Ecco il programma prova_eventi1.py con le modifiche proposte:


import pygame
from pygame.locals import *

# inizializzazione
pygame.init()
pygame.display.set_mode((800, 600))

# ciclo principale (si esce quando done diventa True)
done = False
while not done:
    # riceve tutti gli eventi arrivati dal sistema
    for ev in pygame.event.get():
        # se l'evento e' la fine del programma pone done = True
        if ev.type == QUIT:
            done = True
        elif ev.type == MOUSEBUTTONDOWN:
            print("Hai fatto click")
        elif ev.type == KEYDOWN:
            print("Hai premuto un tasto")
        elif ev.type == MOUSEMOTION:
            print("Hai mosso il mouse")

# fine del ciclo e termine del programma
pygame.quit()

ESERCIZIO 6.2 Basta aggiungere al precedente questa riga:


.   .   .        
		elif ev.type == KEYDOWN:
            print("Hai premuto un tasto")
            done = True
        elif ev.type == MOUSEMOTION:
.   .   .

ESERCIZIO 6.3 Programma click_colori.py:


import pygame, random
from pygame.locals import *

# inizializzazione
pygame.init()
screen = pygame.display.set_mode((800, 600))

# ciclo principale (si esce quando done diventa True)
done = False
while not done:
    # riceve tutti gli eventi arrivati dal sistema
    for ev in pygame.event.get():
        # se l'evento e' la fine del programma pone done = True
        if ev.type == QUIT:
            done = True
        elif ev.type == MOUSEBUTTONDOWN:
            r = random.randrange(256)
            g = random.randrange(256)
            b = random.randrange(256)
            screen.fill((r, g, b))
            pygame.display.flip()

# fine del ciclo e termine del programma
pygame.quit()

LEZIONE 7

ESERCIZIO 7.1 Ecco il programma prova_clic.py con le modifiche proposte. Notate l'indentazione delle righe da #19 a #28 che vanno eseguite solo se l'evento è un clic del mouse.


import pygame
from pygame.locals import *

# inizializzazione
pygame.init()
screen = pygame.display.set_mode((800, 600))

# ciclo principale (si esce quando done diventa True)
done = False
while not done:
    # riceve tutti gli eventi arrivati da Windows
    for ev in pygame.event.get():
        if ev.type == QUIT:                  # chiusura del programma
            done = True
        elif ev.type == MOUSEBUTTONDOWN:     # click del mouse
            mouse_x = ev.pos[0]
            mouse_y = ev.pos[1]
            print("Hai fatto click in", mouse_x, ",", mouse_y)
            if ev.button == BUTTON_LEFT:
                print("click sinistro")
            elif ev.button == BUTTON_MIDDLE:
                print("click centrale")
            elif ev.button == BUTTON_RIGHT:
                print("click destro")
            elif ev.button == BUTTON_WHEELDOWN:
                print("rotella giu'")
            elif ev.button == BUTTON_WHEELUP:
                print("rotella su")

# fine del ciclo e termine del programma
pygame.quit()

ESERCIZIO 7.2 Seconda versione di prova_clic.py (vi mostro solo il ciclo principale). Ho eliminato le variabili intermedie mouse_x e mouse_y mettendo le loro espressioni direttamente nei parametri della print():


.   .   .
while not done:
    # riceve tutti gli eventi arrivati da Windows
    for ev in pygame.event.get():
        if ev.type == QUIT:                  # chiusura del programma
            done = True
        elif ev.type == MOUSEBUTTONDOWN:     # click del mouse
            print("Hai premuto un bottone in", ev.pos[0], ",", ev.pos[1])
        elif ev.type == MOUSEBUTTONUP:    
            print("Hai rilasciato un bottone in", ev.pos[0], ",", ev.pos[1])
.   .   .

ESERCIZIO 7.3 Il programma prova_surface.py modificato con il ciclo degli eventi. Notate che ho dovuto aggiungere la riga #2 che non c'era nell'originale (in alternativa avrei potuto scrivere if ev.type == pygame.QUIT nella #18).


import pygame
from pygame.locals import *

# inizializza pygame: chiamare sempre per prima
pygame.init()

# finestra principale (prende come parametro una tupla con le dimensioni)
screen = pygame.display.set_mode((800, 600))

pygame.draw.circle(screen, "red", (200, 200), 120)

# aggiorna lo schermo dopo che abbiamo disegnato su screen
pygame.display.flip()

done = False
while not done:
    for ev in pygame.event.get():
        if ev.type == QUIT:
            done = True
            
pygame.quit()

Le modifiche per il programma prova_blit.py sono simili.

ESERCIZIO 7.4 Per posizionare i due quadrati uno sopra l'altro basta usare una delle istruzioni viste nell'Esercizio 5.4, ad esempio:


rect2.center = rect1.center

Questa parte non produce il risultato previsto se il quadrato blu è sopra quello rosso:


.   .   .
            if rect1.collidepoint(click):
                print("Hai cliccato il rettangolo rosso")
            elif rect2.collidepoint(click):
                print("Hai cliccato il rettangolo blu")
            else:
                print ("Hai cliccato fuori dalle superfici")
.   .   .

infatti cliccando sul quadrato blu il programma stampa "Hai cliccato il rettangolo rosso". Il motivo è semplice: l'area blu è dentro quella rossa, quindi cliccando sul blu l'if della riga #32 (che controlla se abbiamo cliccato sul rosso) risulterà vero, il programma eseguirà la #33 e poi salterà l'elif e l'else.

Il modo più semplice di correggere la cosa è di invertire i due test: prima verifichiamo l'area blu, e poi, se il mouse non è in essa, verifichiamo quella rossa.


.   .   .
            if rect2.collidepoint(click):
                print("Hai cliccato il rettangolo blu")
            elif rect1.collidepoint(click):
                print("Hai cliccato il rettangolo rosso")
.   .   .

Un'altra idea che potreste aver avuto è quella di aggiungere nel primo test la condizione che il mouse non sia nell'area blu:


.   .   .
            if rect1.collidepoint(click) and not rect2.collidepoint(click):
                print("Hai cliccato il rettangolo rosso")
            elif rect2.collidepoint(click):
                print("Hai cliccato il rettangolo blu")
.   .   .

ESERCIZIO 7.5 Ecco il programma evidenzia.py nella prima versione. Ho aggiunto una print() nella #33 per evidenziare che il quadrato viene ridisegnato ogni volta che muoviamo il mouse (è allo stesso livello di indentazione della blit(), quindi viene sempre eseguita insieme ad essa).


import pygame
from pygame.locals import *

pygame.init()
screen = pygame.display.set_mode((800, 600))

# creiamo la Surface surf1, la coloriamo di rosso e la posizioniamo sullo schermo
surf1 = pygame.Surface((200, 200))
surf1.fill("red")
rect1 = surf1.get_rect()
rect1.topleft = (50, 50)
screen.blit(surf1, rect1)

# aggiorniamo lo schermo
pygame.display.flip()

# ciclo principale
done = False
while not done:
    for ev in pygame.event.get():
        if ev.type == QUIT:                  # chiusura della finestra
            done = True
        elif ev.type == MOUSEMOTION:         # movimento del mouse
            click = ev.pos
            # il mouse e' dentro il quadrato
            if rect1.collidepoint(click):
                surf1.fill((255, 100, 100))
            # il mouse e' fuori
            else:
                surf1.fill("red")
            # ridisegniamo il quadrato
            screen.blit(surf1, rect1)
            print("Ho ridisegnato il quadrato")
            pygame.display.flip()

# fine del ciclo e termine del programma
pygame.quit()

ESERCIZIO 7.6 Le modifiche al programma evidenzia.py:


.   .   .
light = False
# ciclo principale
done = False
while not done:
    for ev in pygame.event.get():
        if ev.type == QUIT:                  # chiusura della finestra
            done = True
        elif ev.type == MOUSEMOTION:         # movimento del mouse
            click = ev.pos
            # entriamo nel quadrato
            if rect1.collidepoint(click) and light == False:
                surf1.fill((255, 100, 100))
                screen.blit(surf1, rect1)
                print("Ho ridisegnato il quadrato")
                pygame.display.flip()
                light = True
            # usciamo dal quadrato
            elif not rect1.collidepoint(click) and light == True:
                surf1.fill("red")
                screen.blit(surf1, rect1)
                print("Ho ridisegnato il quadrato")
                pygame.display.flip()
                light = False
.   .   .

Potete verificare che ora il quadrato viene ridisegnato solo quando entriamo o usciamo da esso. Però ho dovuto duplicare le istruzioni che lo disegnano (questo si può evitare, complicando ulteriormente il programma con altre variabili).

LEZIONE 8

ESERCIZIO 8.2 Nel programma quadrato_mobile.py per far partire il quadrato da altre posizioni dovete assegnare una posizione iniziale al suo Rect. Per il centro dello schermo potete scrivere:


.   .   .
# otteniamo dalla Surface square il corrispondente Rect
rect_square = surf_square.get_rect()
rect_square.center = (400, 300)

# questa e' la velocita' del quadrato: una lista di due elementi
# (cioe' l'incremento delle sue coordinate x e y ad ogni ciclo)
vel_square = [1, 1]
.   .   .

e analogamente potete sostituire la #13 con:


rect_square.midleft = (0, 300)        # meta' del lato sinistro
rect_square.midtop = (400, 0)         # meta' del lato alto 

ESERCIZIO 8.3 Ecco le modifiche da fare per ottenere i comportamenti richiesti. Nella prima colonna c'è la riga da aggiungere dopo la #12, la seconda va sostituita alla #16

rect_square.midleft = (0, 300) vel_square = [1, 0]
rect_square.topright = (800, 600) vel_square = [-1, -1]
rect_square.midbottom = (400, 600) vel_square = [0, -1]

La vel_square indicata nella riga 1 muove il quadrato verso destra (la coordinata x del quadrato aumenta e la y rimane invariata). Quella della riga 3 lo muove verso l'alto (la x rimane invariata e la y diminuisce).

ESERCIZIO 8.4 Queste sono le modifiche proposte al programma quadrato_mobile.py:


.   .   .
    # controlliamo se il quadrato sta per uscire a sinistra o destra
    if rect_square.left < 0 or rect_square.right > screen.get_width():
        # se si' invertiamo la componente x (moltiplicandola per -1)
        vel_square[0] *= -1

    # controlliamo se il quadrato sta per uscire in alto o in basso
    if rect_square.top < 0 or rect_square.bottom > screen.get_height():
        # se si' invertiamo la componente y (moltiplicandola per -1)
        vel_square[1] *= -1
.   .   .

ESERCIZIO 8.5 Conviene ricavare il Rect del nostro schermo subito dopo aver definito la variabile screen:


import pygame
from pygame.locals import *

pygame.init()
screen = pygame.display.set_mode((800, 600))
rect_screen = screen.get_rect()
.   .   .

A questo punto possiamo posizionare il quadrato giallo usando una delle seguenti istruzioni:


rect_square.center = rect_screen.center      # centro dello schermo
rect_square.midleft = rect_screen.midleft    # meta' del lato sinistro
rect_square.midtop = rect_screen.midtop      # meta' del lato alto
rect_square.topright = rect_screen.topright  # angolo in alto a destra

LEZIONE 9

ESERCIZIO 9.1 Ecco le modifiche al programma quadrato_mobile1.py. Se vogliamo vedere il quadrato fermo dobbiamo disegnarlo prima della wait(), altrimenti vedremmo solo lo schermo nero.


import pygame
from pygame.locals import *

pygame.init()
screen = pygame.display.set_mode((800, 600))

surf_square = pygame.Surface((40, 40))
surf_square.fill("yellow")
rect_square = surf_square.get_rect()
vel_square = [1, 1]

# disegniamo il quadrato e aspettiamo 3 secondi
screen.fill("green4")
screen.blit(surf_square, rect_square)
pygame.display.flip()
pygame.time.wait(3000)

# ciclo principale
done = False
while not done:
.   .   .

ESERCIZIO 9.5 Il programma quadrato_mobile2.py. Dopo 20 secondi il timer genererà un evento QUIT che sarà intercettato dal ciclo principale e provocherà la chiusura del programma.


import pygame
from pygame.locals import *

pygame.init()
screen = pygame.display.set_mode((800, 600))

surf_square = pygame.Surface((40, 40))
surf_square.fill("yellow")
rect_square = surf_square.get_rect()
vel_square = [2, 2]

# il timer genera un evento QUIT dopo 20 sec
pygame.time.set_timer(QUIT, 20000)

# ciclo principale
done = False
while not done:
.   .   .

ESERCIZIO 9.6, 9.7 Il programma quadrato_mobile3.py. Senza la riga #28 il timer sarebbe caricato con un intervallo di tempo casuale, ma poi continuerebbe a scattare sempre con quell'intervallo.


import pygame, random
from pygame.locals import *

pygame.init()
screen = pygame.display.set_mode((800, 600))

# creiamo un oggetto Clock
clk = pygame.time.Clock()

surf_square = pygame.Surface((40, 40))
surf_square.fill("yellow")
rect_square = surf_square.get_rect()
vel_square = [2, 2]

# inneschiamo un timer con un intervallo casuale
TIMERSHOT = pygame.event.custom_type()
pygame.time.set_timer(TIMERSHOT, random.randrange(1000, 5001))

# ciclo principale
done = False
while not done:
    # sottociclo degli eventi
    for ev in pygame.event.get():
        if ev.type == QUIT:
            done = True
        elif ev.type == TIMERSHOT:
            # nuovo intervallo casuale
            pygame.time.set_timer(TIMERSHOT, random.randrange(1000, 5001))
            r = random.randrange(256)
            g = random.randrange(256)
            b = random.randrange(256)
            surf_square.fill((r, g, b))

    # muoviamo il quadrato
    if rect_square.left < 0 or rect_square.right > screen.get_width():
        vel_square[0] *= -1
    if rect_square.top < 0 or rect_square.bottom > screen.get_height():
        vel_square[1] *= -1

    rect_square.x += vel_square[0]
    rect_square.y += vel_square[1]

    screen.fill("green3")
    screen.blit(surf_square, rect_square)
    pygame.display.flip()

    #temporizziamo il ciclo a 30 FPS
    clk.tick(30)

pygame.quit()

LEZIONE 10

ESERCIZIO 10.2 Ecco il programma panda_sfondo1.py. In questa prima versione la funzione tile_surface() viene chiamata 30 volte al secondo ad ogni ripetizione del ciclo principale (nella #44), impegnando inutilmente il processore.


import pygame
from pygame.locals import *
from os.path import join

def tile_surface(surf_little, surf_big):       # surf_little e' la mattonella, surf_big l'area da ricoprire
    w_little = surf_little.get_width()         # larghezza della mattonella
    h_little = surf_little.get_height()        # altezza della mattonella
    w_big = surf_big.get_width()               # larghezza da ricoprire
    h_big = surf_big.get_height()              # altezza da ricoprire
    for y in range(0, h_big, h_little):        # incremento della y
        for x in range(0, w_big, w_little):    # incremento della x
            surf_big.blit(surf_little, (x, y)) # posiziona la mattonella

pygame.init()
screen = pygame.display.set_mode((800, 600))
clk = pygame.time.Clock()

# carica l'immagine del panda
surf_panda = pygame.image.load(join("Sprites", "panda.png"))
rect_panda = surf_panda.get_rect()
surf_tile = pygame.image.load(join("Backgrounds", "concrete01.jpg"))

# velocita' del panda
vel_panda = [2, 2]

# ciclo principale
done = False
while not done:
    # sottociclo degli eventi
    for ev in pygame.event.get():
        if ev.type == QUIT:
            done = True

    # movimento
    if rect_panda.left < 0 or rect_panda.right > screen.get_width():
        vel_panda[0] *= -1
    if rect_panda.top < 0 or rect_panda.bottom > screen.get_height():
        vel_panda[1] *= -1

    rect_panda.x += vel_panda[0]
    rect_panda.y += vel_panda[1]

    # aggiornamento dello schermo
    tile_surface(surf_tile, surf_back)
    screen.blit(surf_panda, rect_panda)
    pygame.display.flip()

    clk.tick(30)

pygame.quit()

ESERCIZIO 10.3 Ecco le modifiche al programma precedente. In questo modo la funzione tile_surface() viene eseguita solo una volta, alla riga #23.


.   .   .
# carica l'immagine del panda
surf_panda = pygame.image.load(join("Sprites", "panda.png"))
rect_panda = surf_panda.get_rect()
surf_tile = pygame.image.load(join("Backgrounds", "concrete01.jpg"))
surf_back = screen.copy()
tile_surface(surf_tile, surf_back)

# velocita' del panda
vel_panda = [2, 2]
.   .   .

.   .   .
    # aggiornamento dello schermo
    screen.blit(surf_back, (0, 0))
    screen.blit(surf_panda, rect_panda)
    pygame.display.flip()

    clk.tick(30)

pygame.quit()

ESERCIZIO 10.5 Ecco le ultime modifiche al programma panda_sfondo1.py. Usando la convert() al posto della convert_alpha() nella riga #19 l'immagine dovrebbe essere circondata da un rettangolino nero, perchè non verrebbe riconosciuta la trasparenza.


.   .   .
pygame.init()
screen = pygame.display.set_mode((800, 600))
clk = pygame.time.Clock()

# carica l'immagine del panda
surf_panda = pygame.image.load(join("Sprites", "panda.png")).convert_alpha()
rect_panda = surf_panda.get_rect()
surf_tile = pygame.image.load(join("Backgrounds", "concrete01.jpg")).convert()
.   .   .

LEZIONE 11

ESERCIZIO 11.1, 11.2 Ecco le modifiche al programma panda.py


import pygame
from pygame.locals import *
from os.path import join

pygame.init()
screen = pygame.display.set_mode((800, 600))
clk = pygame.time.Clock()

# carica l'immagine del panda
surf_panda = pygame.image.load(join("Sprites", "panda.png"))
rect_panda = surf_panda.get_rect()
surf_back = pygame.image.load(join("Backgrounds", "desert_02.jpg")).convert()
surf_back = pygame.transform.smoothscale(surf_back, screen.get_size())

# velocita' del panda
vel_panda = [2, 2]
.   .   .

ESERCIZIO 11.3 Ecco gli if piuttosto complessi che risolvono il nostro problema:


.   .   .
    # movimento
    if ((rect_panda.left < 0 and vel_panda[0] < 0) or
        (rect_panda.right > screen.get_width() and vel_panda[0] > 0)):
        vel_panda[0] *= -1
    if ((rect_panda.top < 0 and vel_panda[1] < 0) or
        (rect_panda.bottom > screen.get_height() and vel_panda[1] > 0)):
        vel_panda[1] *= -1
.   .   .

ESERCIZIO 11.4 Il programma effetti_grafici.py:


import pygame
from pygame.locals import *
from os.path import join

# definizione di funzioni
def flash_surface(surf_little, center, surf_big):
    rect = surf_little.get_rect()
    rect.center = center
    surf_big_old = surf_big.copy()
    for i in range(10):
        surf_big.blit(surf_big_old, (0, 0))
        pygame.display.flip()
        pygame.time.wait(80)        
        surf_big.blit(surf_little, rect)
        pygame.display.flip()
        pygame.time.wait(80)

def rotate_surface(surf_little, center, surf_big):
    surf_big_old = surf_big.copy()
    for i in range(55):
        surf = pygame.transform.rotate(surf_little, i * 20)
        rect = surf.get_rect()
        rect.center = center
        surf_big.blit(surf_big_old, (0, 0))
        surf_big.blit(surf, rect)
        pygame.display.flip()
        pygame.time.wait(20)
        
def resize_surface(surf_little, center, surf_big):
    w_min = surf_little.get_width() / 10
    h_min = surf_little.get_height() / 10
    w_step = (surf_little.get_width() - w_min) / 20
    h_step = (surf_little.get_height() - h_min) / 20
    surf_big_old = surf_big.copy()
    for i in range(21):
        w = int(w_min + i * w_step)
        h = int(h_min + i * h_step)
        surf = pygame.transform.smoothscale(surf_little, (w, h))
        rect = surf.get_rect()
        rect.center = center
        surf_big.blit(surf_big_old, (0, 0))
        surf_big.blit(surf, rect)
        pygame.display.flip()
        pygame.time.wait(20)


# inizializzazione
pygame.init()
screen = pygame.display.set_mode((800, 600))
clk = pygame.time.Clock()

surf_text = pygame.image.load(join("Sprites", "gameover3.gif")).convert_alpha()
surf_text.set_colorkey(surf_text.get_at((0, 0)))
center = screen.get_rect().center

screen.fill("green4")
resize_surface(surf_text, center, screen)
pygame.time.wait(2000)

screen.fill("green4")
flash_surface(surf_text, center, screen)
pygame.time.wait(2000)

screen.fill("green4")
rotate_surface(surf_text, center, screen)

# ciclo principale
done = False
while not done:
    # sottociclo degli eventi
    for ev in pygame.event.get():
        if ev.type == QUIT:
            done = True
             
    pygame.display.flip()
    clk.tick(30)

pygame.quit()

ESERCIZIO 11.6 Ecco degli esempi di parametrizzazione in due delle funzioni proposte. Nella flash_surface() è possibile scegliere il numero di flash (con il parametro times) e la velocità del lampeggio (speed), nella rotate_surface() possiamo scegliere il numero di giri della scritta (turns) e la velocità (speed). Nella seconda funzione il numero di passi del ciclo è calcolato così: numero di giri * 360 gradi / 20 gradi per step + 1.


def flash_surface(surf_little, center, surf_big, times, speed):
    rect = surf_little.get_rect()
    rect.center = center
    surf_big_old = surf_big.copy()
    for i in range(times):
        surf_big.blit(surf_big_old, (0, 0))
        pygame.display.flip()
        pygame.time.wait(speed)        
        surf_big.blit(surf_little, rect)
        pygame.display.flip()
        pygame.time.wait(speed)

def rotate_surface(surf_little, center, surf_big, turns, speed):
    surf_big_old = surf_big.copy()
    for i in range(turns * 18 + 1):
        surf = pygame.transform.rotate(surf_little, i * 20)
        rect = surf.get_rect()
        rect.center = center
        surf_big.blit(surf_big_old, (0, 0))
        surf_big.blit(surf, rect)
        pygame.display.flip()
        pygame.time.wait(speed)

In Python quando una funzione ha bisogno di molti parametri è d'uso comune impostarne alcuni come parametri di default, in modo da semplificare la chiamata della funzione. Ad esempio avremmo potuto dichiarare le due funzioni come:


def flash_surface(surf_little, center, surf_big, times=4, speed=80):
.   .   .
def rotate_surface(surf_little, center, surf_big, turns=4, speed=20):
.   .   .

LEZIONE 12

ESERCIZIO 12.1, 12.2 Ecco il programma panda_suono.py completo di effetti sonori e colonna sonora:


import pygame
from pygame.locals import *
from os.path import join

pygame.init()
screen = pygame.display.set_mode((800, 600))
clk = pygame.time.Clock()

# carica l'immagine del panda
surf_panda = pygame.image.load(join("Sprites", "panda.png"))
rect_panda = surf_panda.get_rect()

# carica i suoni
sound_bounce = pygame.mixer.Sound(join("Effects", "squeak2.wav"))
pygame.mixer.music.load(join("Music", "Rock On.mid"))

# velocita' del panda
vel_panda = [2, 2]

#colonna sonora
pygame.mixer.music.play(-1)

# ciclo principale
done = False
while not done:
    # sottociclo degli eventi
    for ev in pygame.event.get():
        if ev.type == QUIT:
            done = True

    # movimento
    if rect_panda.left < 0 or rect_panda.right > screen.get_width():
        vel_panda[0] *= -1
        sound_bounce.play()
    if rect_panda.top < 0 or rect_panda.bottom > screen.get_height():
        vel_panda[1] *= -1
        sound_bounce.play()

    rect_panda.x += vel_panda[0]
    rect_panda.y += vel_panda[1]

    # aggiornamento dello schermo
    screen.fill("green4")
    screen.blit(surf_panda, rect_panda)
    pygame.display.flip()

    clk.tick(30)

pygame.quit()

LEZIONE 13

ESERCIZIO 13.1 - 13.11 Ecco la versione finale di pong.py:


import pygame
from pygame.locals import *
from os.path import join

# --- Inizializzazione
pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Pong")
clk = pygame.time.Clock()

# --- Risorse grafiche
surf_ball = pygame.image.load(join("Sprites", "Pong", "ball_blue.png")).convert_alpha()
surf_ball = pygame.transform.scale2x(surf_ball)
rect_ball = surf_ball.get_rect()
rect_ball.topleft = (300, 200)
surf_bar = pygame.image.load(join("Sprites", "Pong", "bar2_blue.png")).convert_alpha()
rect_bar = surf_bar.get_rect()
rect_bar.topleft = (300, 560)

# --- Risorse sonore
sound_bounce = pygame.mixer.Sound(join("Effects", "SFX", "beep_8.wav"))
sound_hit = pygame.mixer.Sound(join("Effects", "SFX", "hit_1.wav"))

# --- Altre variabili
vel_ball = [3, 3]
pressed = None

# --- Ciclo principale
done = False
while not done:

    # --- Ciclo degli eventi
    for ev in pygame.event.get():
        if ev.type == pygame.QUIT:
            done = True
        elif ev.type == KEYDOWN and ev.key in (K_LEFT, K_RIGHT):
            pressed = ev.key
        elif ev.type == KEYUP and ev.key == pressed:
            pressed = None

    # --- Logica del gioco
    if rect_ball.left < 0 or rect_ball.right > screen.get_width():
        vel_ball[0] *= -1
        sound_bounce.play()
    if rect_ball.top < 0:
        vel_ball[1] *= -1
        sound_bounce.play()
    if rect_ball.bottom > screen.get_height():
        done = True
    if rect_bar.colliderect(rect_ball) and vel_ball[1] > 0:
        vel_ball[1] *= -1
        sound_hit.play()
    rect_ball.x += vel_ball[0]
    rect_ball.y += vel_ball[1]
    if pressed == K_LEFT and rect_bar.left > 0:
        rect_bar.x -= 4
    elif pressed == K_RIGHT and rect_bar.right < screen.get_width():
        rect_bar.x += 4

    # --- Aggiornamento dello schermo
    screen.fill((60, 160, 60))
    screen.blit(surf_ball, rect_ball)
    screen.blit(surf_bar, rect_bar)
    pygame.display.flip()

    clk.tick(30)

# --- Uscita
pygame.quit()

LEZIONE 14

ESERCIZIO 14.1 - 14.3 Queste sono le modifiche al ciclo principale del programma fantasmi.py:


.   .   .
# ciclo principale
done = False
while not done:
    for ev in pygame.event.get():
        if ev.type == QUIT:
            done = True
        # se l'evento e' il timer ...
        elif ev.type == TIMERSHOT:
            pygame.time.set_timer(TIMERSHOT, random.randrange(200, 2501))
            ghost = pygame.sprite.Sprite()
            ghost.image = ghost_images[random.randrange(4)]
            ghost.rect = ghost.image.get_rect()
            maxy = screen.get_height() - ghost.rect.height - 9
            ghost.rect.topright = (-1, random.randrange(10, maxy))
            if pygame.sprite.spritecollideany(ghost, all_ghosts):
                ghost.kill()
            else:
                all_ghosts.add(ghost)

    for ghost in all_ghosts:
        ghost.rect.x += 4
        if ghost.rect.left > screen.get_width():
            ghost.kill()
.   .   .

Per l'OSSERVAZIONE 2 fatta nell'ESERCIZIO 14.3 le righe #39-#42 possono essere sostituite da queste:


            if not pygame.sprite.spritecollideany(ghost, all_ghosts):
                all_ghosts.add(ghost)

LEZIONE 16

ESERCIZIO 16.1 - 16.8 La versione finale di aliens.py


import pygame, random
from pygame.locals import *
from os.path import join

# --- Inizializzazione
pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Aliens")
clk = pygame.time.Clock()

# --- Risorse grafiche
surf_back = pygame.image.load(join("Backgrounds", "Space.png")).convert()
surf_ship = pygame.image.load(join("Sprites", "SpaceArt", "player.png")).convert_alpha()
surf_alien = pygame.image.load(join("Sprites", "SpaceArt", "enemyShip.png")).convert_alpha()
surf_bullet = pygame.image.load(join("Sprites", "SpaceArt", "laserGreen.png")).convert_alpha()

# --- Risorse sonore
sound_laser = pygame.mixer.Sound(join("Effects","smc-wwvi", "synthetic_laser.ogg"))
sound_expl = pygame.mixer.Sound(join("Effects", "smc-wwvi", "synthetic_gunshot_2.ogg"))
sound_end = pygame.mixer.Sound(join("Effects", "smc-wwvi", "big_explosion.ogg"))
pygame.mixer.music.load(join("Music", "SoundImage", "Techno-Gameplay_Looping.ogg"))
sound_laser.set_volume(0.5)
sound_expl.set_volume(0.5)
sound_end.set_volume(0.5)

# --- Altre variabili
pressed = None
all_sprites = pygame.sprite.Group()
bullets = pygame.sprite.Group()
aliens = pygame.sprite.Group()
spr_ship = pygame.sprite.Sprite(all_sprites)
spr_ship.image = surf_ship
spr_ship.rect = surf_ship.get_rect()
spr_ship.rect.topleft = (320, 500)

# --- Inizio del gioco
pygame.mixer.music.play(-1)
TIMERSHOT = pygame.event.custom_type()
pygame.time.set_timer(TIMERSHOT, random.randrange(400, 1501))

# --- Ciclo principale
done = False
while not done:
    # --- Ciclo degli eventi
    for ev in pygame.event.get():
        if ev.type == pygame.QUIT:
            done = True
        elif ev.type == KEYDOWN and ev.key in (K_LEFT, K_RIGHT):
            pressed = ev.key
        elif ev.type == KEYUP and ev.key == pressed:
            pressed = None
        elif ev.type == KEYDOWN and ev.key == K_SPACE and len(bullets) < 8:
            bullet = pygame.sprite.Sprite(all_sprites, bullets)
            bullet.image = surf_bullet
            bullet.rect = surf_bullet.get_rect()
            bullet.rect.midbottom = spr_ship.rect.midtop
            sound_laser.play()
        elif ev.type == TIMERSHOT:
            pygame.time.set_timer(TIMERSHOT, random.randrange(400, 1501))
            alien = pygame.sprite.Sprite(all_sprites, aliens)
            alien.image = surf_alien
            alien.rect = surf_alien.get_rect()
            alien.rect.bottomleft = (random.randrange(0, screen.get_width() - alien.rect.width + 1), -1)

    # --- Logica del gioco
    if pressed == K_LEFT and spr_ship.rect.left > 0:
        spr_ship.rect.x -= 5
    elif pressed == K_RIGHT and spr_ship.rect.right < screen.get_width():
        spr_ship.rect.x += 5
    for bullet in bullets:
        bullet.rect.y -= 4
        if bullet.rect.bottom < 0:
            bullet.kill()
    for alien in aliens:
        alien.rect.y += 2
        if alien.rect.top > screen.get_height():
            sound_end.play()
            done = True
    if pygame.sprite.groupcollide(bullets, aliens, True, True):
        sound_expl.play()
    if pygame.sprite.spritecollide(spr_ship, aliens, True):
            sound_end.play()
            done = True       

    # --- Aggiornamento dello schermo
    screen.blit(surf_back, (0, 0))
    all_sprites.draw(screen)
    pygame.display.flip()

    clk.tick(30)

# --- Uscita
pygame.mixer.music.stop()
while pygame.mixer.get_busy():
    clk.tick(30)
pygame.quit()

LEZIONE 17

ESERCIZIO 17.2 Ecco le modifiche al programma testo1.py. Notate che nella #14 ho ricavato direttamente il centro della Surface screen con una sola istruzione ed evitando di usare una variabile.


import pygame
from pygame.locals import *

pygame.init()
screen = pygame.display.set_mode((800, 600))
screen.fill((0, 0, 160))

# carichiamo il font e lo assegniamo alla variabile fnt
fnt = pygame.font.SysFont("Times New Roman", 24)

# scriviamo "Hello world!" e otteniamo la Surface surf_text
surf_text = fnt.render("Hello world!", True, "yellow")
rect_text = surf_text.get_rect()
rect_text.center = screen.get_rect().center

# disegniamo surf_text sullo schermo
screen.blit(surf_text, rect_text)
pygame.display.flip()

# ciclo principale
.   .   .

ESERCIZIO 17.3 Questa è la prima parte del programma scritta_mobile.py. Il resto è uguale a quadrato_mobile.py (qui).


import pygame
from pygame.locals import *

pygame.init()
screen = pygame.display.set_mode((800, 600))

# creiamo un oggetto Clock
clk = pygame.time.Clock()

fnt = pygame.font.SysFont("Arial", 96)
surf_square = fnt.render("Ciao!", True, "yellow")
rect_square = surf_square.get_rect()
vel_square = [1, 1]

# ciclo principale
.   .   .

ESERCIZIO 17.4 Queste sono le nostre scritte. Notate come ho usato i vari Rect per allinearle (voi potreste aver scritto qualcosa di diverso).


import pygame
from pygame.locals import *

pygame.init()
screen = pygame.display.set_mode((800, 600))
screen.fill((0, 0, 160))

# carichiamo i font
fnt1 = pygame.font.SysFont("Times New Roman", 96)
fnt2 = pygame.font.SysFont("Arial", 48)

# scriviamo "Hello world!" e "All is OK"
surf_text = fnt1.render("Hello world!", True, "yellow")
surf_ok = fnt2.render("All is OK", True, "white")

# allineiamo i testi
rect_text = surf_text.get_rect()
rect_text.center = screen.get_rect().center
rect_ok = surf_ok.get_rect()
rect_ok.topleft = rect_text.bottomleft

# disegniamo le Surface sullo schermo
screen.blit(surf_text, rect_text)
screen.blit(surf_ok, rect_ok)
pygame.display.flip()

# ciclo principale
done = False
while not done:
    for ev in pygame.event.get():
        if ev.type == QUIT:
            done = True
pygame.quit()

ESERCIZIO 17.5 Modificate così il programma testo2.py:


.   .   .
    # scriviamo s e otteniamo la Surface surf_text
    surf_text = fnt.render(s, True, "yellow", "white")
    rect_text = surf_text.get_rect()
    rect_text.center = screen.get_rect().center

    # disegniamo surf_text sullo schermo
    screen.fill((0, 0, 160))
    screen.blit(surf_text, rect_text)
    pygame.display.flip()
.   .   .

La lettera stampata potrebbe sembrarvi troppo in basso, ma questo è dovuto allo spazio lasciato per le lettere "alte" (come b, d, l): potete evidenziarlo stampando la lettera con un colore di sfondo diverso dal blu.

ESERCIZIO 17.6 - 17.8 Ulteriori modifiche al programma. Nella #22 ho riunito con un and due condizioni in una sola riga:


.   .   .
# ciclo principale
done = False
while not done:
    # sottociclo degli eventi
    for ev in pygame.event.get():
        if ev.type == QUIT:
            done = True
        elif ev.type == KEYDOWN and ev.unicode.isprintable():
            s += ev.unicode

    # scriviamo s e otteniamo la Surface surf_text
    surf_text = fnt.render(s, True, "yellow")
    rect_text = surf_text.get_rect()
    rect_text.center = screen.get_rect().center

    # disegniamo surf_text sullo schermo
    screen.fill((0, 0, 160))
    screen.blit(surf_text, rect_text)
    pygame.display.flip()
.   .   .

Per far accettare al programma solo numeri, lettere o lettere maiuscole dobbiamo sostituire la riga #22 rispettivamente con:


        elif ev.type == KEYDOWN and ev.unicode.isdigit():
        elif ev.type == KEYDOWN and ev.unicode.isalpha():
        elif ev.type == KEYDOWN and ev.unicode.isupper():

ESERCIZIO 17.10 L'ultima versione di testo2.py, con l'implementazione del BackSpace ed il testo in diversi colori:


import pygame
from pygame.locals import *

pygame.init()
screen = pygame.display.set_mode((800, 600))
clk = pygame.time.Clock()

text_color = "yellow"

# carichiamo il font e lo assegniamo alla variabile fnt
fnt = pygame.font.SysFont("Times New Roman", 120)

# inizializziamo una string vuota
s = ""

# ciclo principale
done = False
while not done:
    # sottociclo degli eventi
    for ev in pygame.event.get():
        if ev.type == QUIT:
            done = True
        elif ev.type == KEYDOWN:
            if ev.key == K_BACKSPACE:
                s = s[:-1]
            elif ev.key == K_F1:
                text_color = "green"
            elif ev.key == K_F2:
                text_color = "red"
            elif ev.key == K_F3:
                text_color = "yellow"
            elif ev.key == K_F4:
                text_color = "white"
            elif ev.unicode.isprintable():
                s += ev.unicode

    # scriviamo s e otteniamo la Surface surf_text
    surf_text = fnt.render(s, True, text_color)
    rect_text = surf_text.get_rect()
    rect_text.center = screen.get_rect().center
    
    # disegniamo surf_text sullo schermo
    screen.fill((0, 0, 160))
    screen.blit(surf_text, rect_text)
    pygame.display.flip()

    clk.tick(30)

pygame.quit()

LEZIONE 18

ESERCIZIO 18.2 Il programma fantasmi2.py con i fantasmini fluttuanti:


.   .   .
# ciclo principale
done = False
while not done:
    for ev in pygame.event.get():
        if ev.type == QUIT:
            done = True
        # se l'evento e' il timer ...
        elif ev.type == TIMERSHOT:
            pygame.time.set_timer(TIMERSHOT, random.randrange(200, 2501))
            ghost = pygame.sprite.Sprite(all_ghosts)
            ghost.image = ghost_images[random.randrange(4)]
            ghost.rect = ghost.image.get_rect()
            maxy = screen.get_height() - ghost.rect.height - 9
            ghost.rect.topright = (-1, random.randrange(10, maxy))
            ghost.xvel = random.randrange(1, 6)
            ghost.yvel = 1
            ghost.count = 0

    for ghost in all_ghosts:
        ghost.rect.x += ghost.xvel
        ghost.rect.y += ghost.yvel
        ghost.count += 1
        if ghost.count == 4:
            ghost.yvel *= -1
            ghost.count = 0
        if ghost.rect.left > screen.get_width():
            ghost.kill()

    screen.fill((160, 160, 255))
    all_ghosts.draw(screen)
    pygame.display.flip()
    clk.tick(30)

pygame.quit()

ESERCIZIO 18.4 Lo stesso programma ristrutturato con la creazione dello Sprite eseguita da una funzione:


import pygame, random
from pygame.locals import *
from os.path import join

# definizione di funzioni
def new_ghost():
    ghost = pygame.sprite.Sprite(all_ghosts)
    ghost.image = ghost_images[random.randrange(4)]
    ghost.rect = ghost.image.get_rect()
    maxy = screen.get_height() - ghost.rect.height - 9
    ghost.rect.topright = (-1, random.randrange(10, maxy))
    ghost.xvel = random.randrange(1, 6)
    ghost.yvel = 1
    ghost.count = 0

# inizializzione
pygame.init()
screen = pygame.display.set_mode((800, 600))
clk = pygame.time.Clock()

# risorse e variabili
ghost_images = []
fname = join("Sprites", "pac-classic", "ghost-red-right.png")
ghost_images.append(pygame.image.load(fname).convert_alpha())
fname = join("Sprites", "pac-classic", "ghost-lblue-right.png")
ghost_images.append(pygame.image.load(fname).convert_alpha())
fname = join("Sprites", "pac-classic", "ghost-orange-right.png")
ghost_images.append(pygame.image.load(fname).convert_alpha())
fname = join("Sprites", "pac-classic", "ghost-pink-right.png")
ghost_images.append(pygame.image.load(fname).convert_alpha())

all_ghosts = pygame.sprite.Group()
TIMERSHOT = pygame.event.custom_type()
pygame.time.set_timer(TIMERSHOT, random.randrange(200, 2501))

# ciclo principale
done = False
while not done:
    for ev in pygame.event.get():
        if ev.type == QUIT:
            done = True
        # se l'evento e' il timer ...
        elif ev.type == TIMERSHOT:
            pygame.time.set_timer(TIMERSHOT, random.randrange(200, 2501))
            new_ghost()

    for ghost in all_ghosts:
        ghost.rect.x += ghost.xvel
        ghost.rect.y += ghost.yvel
        ghost.count += 1
        if ghost.count == 4:
            ghost.yvel *= -1
            ghost.count = 0
        if ghost.rect.left > screen.get_width():
            ghost.kill()

    screen.fill((160, 160, 255))
    all_ghosts.draw(screen)
    pygame.display.flip()
    clk.tick(30)

pygame.quit()

LEZIONE 20

ESERCIZIO 20.2 - 20.3 Potete copiare questo programma scrolling.py, dove sono definite le due funzioni vertical_scroll() e horizontal_scroll(). Notate il trucchetto in #7 e #16 per fare lo scrolling di una quantità negativa (verso sinistra o verso l'alto): per scrollare a sinistra di dy (numero negativo) basta scrollare a destra di width + dy. Voi potreste aver avuto idee diverse, ad esempio un if all'inizio con due procedimenti diversi.


import pygame, random
from pygame.locals import *
from os.path import join

# --- Definizione di funzioni
def vertical_scroll(surf, dy):
    if dy < 0:
        dy += surf.get_height()
    rect1 = pygame.Rect(0, 0, surf.get_width(), surf.get_height() - dy)
    rect2 = pygame.Rect(0, surf.get_height() - dy, surf.get_width(), dy)
    surf_temp = surf.copy()
    surf.blit(surf_temp, (0, 0), rect2)
    surf.blit(surf_temp, (0, dy), rect1)

def horizontal_scroll(surf, dx):
    if dx < 0:
        dx += surf.get_width()
    rect1 = pygame.Rect(0, 0, surf.get_width() - dx, surf.get_height())
    rect2 = pygame.Rect(surf.get_width() - dx, 0, dx, surf.get_height())
    surf_temp = surf.copy()
    surf.blit(surf_temp, (0, 0), rect2)
    surf.blit(surf_temp, (dx, 0), rect1)

# --- Inizializzazione
pygame.init()
screen = pygame.display.set_mode()
clk = pygame.time.Clock()

# --- Risorse grafiche
surf_back = pygame.image.load(join("Backgrounds", "Space.png")).convert()

# --- Altre variabili
hor_vel = 0
ver_vel = 0

# --- Inizio del gioco
screen.fill((10, 180, 10))

# --- Ciclo principale
done = False
while not done:
    # --- Ciclo degli eventi
    for ev in pygame.event.get():
        if ev.type == pygame.QUIT:
            done = True
        elif ev.type == KEYDOWN:
            if ev.key == K_LEFT:
                hor_vel -= 1
                #ver_vel = 0
            elif ev.key == K_RIGHT:
                hor_vel += 1
                #ver_vel = 0
            elif ev.key == K_UP:
                #hor_vel = 0
                ver_vel -= 1
            elif ev.key == K_DOWN:
                #hor_vel = 0
                ver_vel += 1

    # --- Aggiornamento dello schermo
    if ver_vel != 0:
        vertical_scroll(surf_back, ver_vel)
    if hor_vel != 0:
        horizontal_scroll(surf_back, hor_vel)
    screen.blit(surf_back, (50, 50))
    pygame.display.flip()

    clk.tick(30)

# --- Uscita
pygame.quit()

ESERCIZIO 20.4 - 20.10 Questa è la versione finale di aliens2.py con lo scrolling dell'area di gioco e le esplosioni:


import pygame, random, animimage
from pygame.locals import *
from os.path import join

# --- Definizione di funzioni
def vertical_scroll(surf, dy):
    rect1 = pygame.Rect(0, 0, surf.get_width(), surf.get_height() - dy)
    rect2 = pygame.Rect(0, surf.get_height() - dy, surf.get_width(), dy)
    surf_temp = surf.copy()
    surf.blit(surf_temp, (0, 0), rect2)
    surf.blit(surf_temp, (0, dy), rect1)

# --- Inizializzazione
pygame.init()
screen = pygame.display.set_mode()
pygame.display.set_caption("Aliens")
clk = pygame.time.Clock()

# --- Risorse grafiche
surf_back = pygame.image.load(join("Backgrounds", "Space.png")).convert()
surf_ship = pygame.image.load(join("Sprites", "SpaceArt", "player.png")).convert_alpha()
surf_alien = pygame.image.load(join("Sprites", "SpaceArt", "enemyShip.png")).convert_alpha()
surf_bullet = pygame.image.load(join("Sprites", "SpaceArt", "laserGreen.png")).convert_alpha()
surf_title = pygame.image.load(join("Sprites", "Aliens_text.png")).convert_alpha()
surf_play = pygame.Surface(surf_back.get_size())
surf_info = pygame.Surface((250, surf_play.get_height()))
anim_expl = []
for i in range(32):
    filename = join("Sprites", "Animations", "expl_06_{:0>4}.png".format(i))
    img = pygame.image.load(filename).convert_alpha()
    anim_expl.append(pygame.transform.scale2x(img))
anim_big_expl = []
for i in range(32):
    filename = join("Sprites", "Animations", "expl_10_{:0>4}.png".format(i))
    img = pygame.image.load(filename).convert_alpha()
    anim_big_expl.append(pygame.transform.rotozoom(img, 0, 4.0))
fnt = pygame.font.SysFont("Arial", 48)

# --- Risorse sonore
sound_laser = pygame.mixer.Sound(join("Effects","smc-wwvi", "synthetic_laser.ogg"))
sound_expl = pygame.mixer.Sound(join("Effects", "smc-wwvi", "synthetic_gunshot_2.ogg"))
sound_end = pygame.mixer.Sound(join("Effects", "smc-wwvi", "big_explosion.ogg"))
pygame.mixer.music.load(join("Music", "SoundImage", "Techno-Gameplay_Looping.ogg"))

# --- Costanti
TIMERSHOT = pygame.event.custom_type()
BACKCOLOR = (10, 160, 10)
TEXTCOLOR = (255, 255, 40)
SHIP_TOPLEFT = (320, 500)
PLAY_TOPLEFT = (20, 40 + surf_title.get_height())
INFO_TOPLEFT = (40 + surf_play.get_width(), 40 + surf_title.get_height())

# --- Altre variabili
pressed = None
all_sprites = pygame.sprite.Group()
bullets = pygame.sprite.Group()
aliens = pygame.sprite.Group()
animations = pygame.sprite.Group()
spr_ship = pygame.sprite.Sprite(all_sprites)
spr_ship.image = surf_ship
spr_ship.rect = surf_ship.get_rect()
spr_ship.rect.topleft = SHIP_TOPLEFT
score = 0

# --- Inizio del gioco
pygame.mixer.music.play(-1)
pygame.time.set_timer(TIMERSHOT, random.randrange(400, 1501))
screen.fill(BACKCOLOR)
rect_title = surf_title.get_rect()
rect_title.midtop = (surf_play.get_width() // 2 + 20, 20)
screen.blit(surf_title, rect_title)

# --- Ciclo principale
done = False
while not done:
    # --- Ciclo degli eventi
    for ev in pygame.event.get():
        if ev.type == pygame.QUIT:
            done = True
        elif ev.type == KEYDOWN and ev.key in (K_LEFT, K_RIGHT):
            pressed = ev.key
        elif ev.type == KEYUP and ev.key == pressed:
            pressed = None
        elif ev.type == KEYDOWN and ev.key == K_SPACE and len(bullets) < 8:
            bullet = pygame.sprite.Sprite(all_sprites, bullets)
            bullet.image = surf_bullet
            bullet.rect = surf_bullet.get_rect()
            bullet.rect.midbottom = spr_ship.rect.midtop
            sound_laser.play()
        elif ev.type == TIMERSHOT:
            pygame.time.set_timer(TIMERSHOT, random.randrange(400, 1501))
            alien = pygame.sprite.Sprite(all_sprites, aliens)
            alien.image = surf_alien
            alien.rect = surf_alien.get_rect()
            alien.rect.bottomleft = (random.randrange(0, surf_play.get_width() - alien.rect.width + 1), -1)

    # --- Logica del gioco
    if pressed == K_LEFT and spr_ship.rect.left > 0:
        spr_ship.rect.x -= 5
    elif pressed == K_RIGHT and spr_ship.rect.right < surf_play.get_width():
        spr_ship.rect.x += 5
    for bullet in bullets:
        bullet.rect.y -= 4
        if bullet.rect.bottom < 0:
            bullet.kill()
    for alien in aliens:
        alien.rect.y += 2
        if alien.rect.top > surf_play.get_height():
            sound_end.play()
            done = True
            spr_ship.kill()
            expl = animimage.AnimSprite(animations, all_sprites)
            expl.set_images(anim_big_expl)
            expl.rect.center = alien.rect.midtop
            expl.set_rate(3.5)
    collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
    for i in collisions.keys():
        for alien in collisions[i]:
            score += 10
            sound_expl.play()
            expl = animimage.AnimSprite(animations, all_sprites)
            expl.set_images(anim_expl)
            expl.rect.center = alien.rect.midbottom
    if pygame.sprite.spritecollide(spr_ship, aliens, True):
        sound_end.play()
        done = True
        spr_ship.kill()
        expl = animimage.AnimSprite(animations, all_sprites)
        expl.set_images(anim_big_expl)
        expl.rect.center = spr_ship.rect.midtop
        expl.set_rate(3.5)

    # --- Aggiornamento dello schermo
    animations.update()
    vertical_scroll(surf_back, 1)
    surf_play.blit(surf_back, (0, 0))
    all_sprites.draw(surf_play)
    screen.blit(surf_play, PLAY_TOPLEFT)
    surf_info.fill(BACKCOLOR)
    surf_info.blit(fnt.render("Score: " + str(score), True, TEXTCOLOR), (0, 0))
    screen.blit(surf_info, INFO_TOPLEFT)
    pygame.display.flip()

    clk.tick(30)

# --- Uscita
pygame.mixer.music.stop()
while pygame.mixer.get_busy():
    animations.update()
    surf_play.blit(surf_back, (0, 0))
    all_sprites.draw(surf_play)
    screen.blit(surf_play, PLAY_TOPLEFT)
    pygame.display.flip()
    clk.tick(30)
pygame.quit()