- Home
- iThink
- Programmazione per Ragazzi
- Pong in Python con Pygame: il gioco completo
Pong in Python con Pygame: il gioco completo
Divento Programmatore — Capitolo 8: Pong: Due giocatori, un solo vincitore
Obiettivi di Apprendimento
Al termine di questo capitolo avrai costruito un gioco completo di Arcade Pong e imparerai a:
- Comprendere il paradigma della Programmazione Orientata agli Oggetti (OOP)
- Creare Classi in Python per definire modelli di oggetti di gioco
- Istanziare Oggetti (creare copie reali partendo da una classe)
- Utilizzare il costruttore
__init__per inizializzare lo stato interno degli oggetti - Comprendere l'uso del parametro
selfper fare riferimento all'oggetto corrente - Applicare metodi all'interno delle classi per gestirne il movimento e il disegno
Termini Fondamentali
| Termine | Simbolo | Significato | | --------- | --------- | ------------- | | Programmazione Orientata agli Oggetti (OOP) | 📦 | Stile di programmazione in cui il codice è organizzato attorno a "oggetti" reali che contengono sia i dati (attributi) che le azioni (metodi) | | Classe (class) | 📐 | Lo stampo o il progetto che descrive le caratteristiche e i comportamenti di un tipo di oggetto | | Oggetto / Istanza (object / instance) | 🍪 | Il singolo elemento creato fisicamente in memoria partendo da una classe | | Costruttore (__init__) | 🏗️ | Metodo speciale di Python eseguito in automatico non appena un nuovo oggetto viene creato, usato per impostarne le variabili iniziali | | Metodo (method) | ⚙️ | Una funzione definita all'interno di una classe che descrive un'azione compibile dall'oggetto (es. muovi() o disegna()) | | self | 👤 | Parola chiave speciale che rappresenta l'oggetto specifico su cui Python sta eseguendo il metodo in quel momento |
La Storia: La nascita dell'industria dei videogiochi
Nel 1972, una piccolissima e sconosciuta azienda californiana chiamata Atari, fondata da Nolan Bushnell e Ted Dabney, decise di creare un gioco elettronico da installare nei locali pubblici (i bar e le sale giochi dell'epoca). Bushnell assegnò l'incarico a un giovane ingegnere elettronico appena assunto, Allan Alcorn, come esercizio di prova per fargli fare pratica.
Alcorn progettò un semplicissimo simulatore di ping-pong in cui due barrette bianche potevano muoversi in verticale per intercettare un quadratino bianco che rimbalzava sullo schermo, emettendo un caratteristico suono elettronico "bip" a ogni tocco. Installarono il prototipo del cabinato (chiamato semplicemente Pong) in una taverna di Sunnyvale, in California, inserendovi una scatola di latte vuota per raccogliere le monete.
Dopo poche ore, il gestore del locale chiamò Atari infuriato dicendo che la macchina si era rotta. Quando Alcorn andò a controllare, scoprì che il gioco non era rotto, ma semplicemente intasato di monete da 25 centesimi che impedivano il funzionamento del meccanismo! Pong fu il primo videogioco a riscuotere un successo commerciale travolgente, segnando la nascita ufficiale dell'industria mondiale dell'intrattenimento interattivo. Oggi, userai la programmazione a oggetti per ricreare questa storica pietra miliare!
Prima di tutto: cosa sono le Classi?
Prima di immergerti nel codice di Pong, un momento di chiarezza. Fino a ora hai usato funzioni per organizzare il tuo codice. Le classi sono il passo successivo: una classe è come uno stampino per biscotti — definisce la forma una volta sola, e puoi usarlo quante volte vuoi per creare oggetti tutti uguali ma indipendenti.
# Esempio semplicissimo: una Classe base
class Persona:
def __init__(self, nome, eta):
self.nome = nome # ogni Persona ha il suo nome
self.eta = eta # e la sua età
# Creare due oggetti dalla stessa Classe
mario = Persona("Mario", 12)
luigi = Persona("Luigi", 14)
print(mario.nome) # Output: Mario
print(luigi.eta) # Output: 14
self è il modo in cui la classe si riferisce a se stessa: self.nome significa "il nome di questo oggetto specifico". Ora sei pronto per le classi Racchetta e Pallina di Pong!
Concept 1: Lo stampino per biscotti (Classi e Oggetti)
La Programmazione Orientata agli Oggetti può sembrare difficile all'inizio, ma si basa su un'analogia molto semplice del mondo reale: lo stampino per biscotti.
┌───────────────────────┐
│ CLASSE (Stampino) │ --> Definisce solo la forma geometrica del biscotto.
└───────────┬───────────┘
│
▼
┌───────────────────────┐
│ OGGETTI (Biscotti) │ --> Sono i biscotti reali fatti di pasta sfoglia
└───────────────────────┘ (ognuno posizionato in un punto diverso).
- La Classe è lo stampino. Non è un biscotto vero: definisce solo la forma, l'altezza, la larghezza e le regole del biscotto. Nel codice, scriverai
class Racchetta:per spiegare a Python com'è fatta una racchetta. - L'Oggetto (o istanza) è il biscotto reale sfornato dallo stampino. Scrivendo
racchetta_sinistra = Racchetta(...)crei un oggetto reale posizionato a sinistra dello schermo, e scrivendoracchetta_destra = Racchetta(...)ne crei un secondo a destra.
Entrambe le racchette usano lo stesso identico codice (la stessa classe), ma sono due oggetti distinti con posizioni e tasti di controllo diversi!
Concept 2: Cos'è quel self nel codice?
All'interno delle definizioni delle classi Python, noterai che il primo argomento di ogni funzione è sempre la parolina magica self (es: def muovi(self):).
Quando scrivi una classe, non sai ancora quale oggetto reale userà quel codice. self serve a dire a Python: "esegui questa azione sull'oggetto specifico che ti ha chiamato in questo momento".
Ad esempio, se scriviamo:
class Racchetta:
def __init__(self, y):
self.y = y # Imposta la coordinata Y di QUESTO specifico oggetto
Quando Python esegue racchetta_sinistra = Racchetta(250), la variabile self punterà a racchetta_sinistra, modificando la sua Y. Quando esegue racchetta_destra = Racchetta(100), self punterà a racchetta_destra, lasciando inalterata la racchetta sinistra!
Costruiamo il Gioco: Versione Base
Ecco la versione base strutturata in modo procedurale, con la pallina che rimbalza e le due racchette che possono essere spostate in verticale:
import pygame
import sys
pygame.init()
schermo = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Pong Base")
orologio = pygame.time.Clock()
# Coordinate e velocità degli elementi
palla_x, palla_y = 400, 300
palla_vel_x, palla_vel_y = 5, 5
racc_sx_y = 250
racc_dx_y = 250
while True:
tasti = pygame.key.get_pressed()
for evento in pygame.event.get():
if evento.type == pygame.QUIT:
pygame.quit()
sys.exit()
# Spostamento racchette
if tasti[pygame.K_w] and racc_sx_y > 0:
racc_sx_y -= 5
if tasti[pygame.K_s] and racc_sx_y < 500:
racc_sx_y += 5
if tasti[pygame.K_UP] and racc_dx_y > 0:
racc_dx_y -= 5
if tasti[pygame.K_DOWN] and racc_dx_y < 500:
racc_dx_y += 5
# Spostamento pallina
palla_x += palla_vel_x
palla_y += palla_vel_y
# Rimbalzo alto e basso
if palla_y <= 0 or palla_y >= 600:
palla_vel_y = -palla_vel_y
# Reset se va fuori
if palla_x < 0 or palla_x > 800:
palla_x, palla_y = 400, 300
# Rendering
schermo.fill((0, 0, 0))
pygame.draw.rect(schermo, (255, 255, 255), pygame.Rect(30, racc_sx_y, 20, 100))
pygame.draw.rect(schermo, (255, 255, 255), pygame.Rect(750, racc_dx_y, 20, 100))
pygame.draw.circle(schermo, (255, 255, 255), (palla_x, palla_y), 10)
pygame.display.flip()
orologio.tick(60)
Sfide: Rendi il tuo Pong un vero capolavoro OOP
Sfida 1 — Spostare le Racchette in Classe
Riscrivi il codice delle racchette incapsulandolo all'interno di una classe Racchetta, definendo un costruttore __init__, un metodo muovi(self, tasti) e un metodo disegna(self, schermo).
Sfida 2 — Velocità Pallina
Fai in modo che a ogni collisione riuscita con una racchetta, la pallina incrementi la propria velocità del 10%. In questo modo, lo scambio diventerà sempre più veloce e stimolante!
Sfida 3 — Punteggio Grafico
Utilizza il modulo pygame.font per caricare un font di sistema (es: Consolas o Arial) e stampare a schermo il punteggio dei due giocatori al centro della metà campo.
Sfida 4 — L'avversario AI
Aggiungi un parametro booleano is_ai alla classe Racchetta. Se impostato su True, la racchetta non leggerà l'input della tastiera ma seguirà in automatico la coordinata Y della pallina, permettendo al giocatore di sfidare il computer!
Il Codice Completo con Tutte le Sfide
Salva questo codice in VS Code come pong_oop.py e avvialo per sfidare l'intelligenza artificiale!
import pygame
import sys
import random
# Dimensioni finestra
LARGHEZZA = 800
ALTEZZA = 600
# Colori
NERO = (0, 0, 0)
BIANCO = (255, 255, 255)
class Racchetta:
"""Classe che rappresenta la racchetta di gioco."""
def __init__(self, x, y, larghezza, altezza, velocita, tasto_su, tasto_giu, is_ai=False):
self.x = x
self.y = y
self.larghezza = larghezza
self.altezza = altezza
self.velocita = velocita
self.tasto_su = tasto_su
self.tasto_giu = tasto_giu
self.is_ai = is_ai
def muovi(self, tasti, palla_y):
"""Sposta la racchetta in base ai tasti premuti o all'algoritmo AI."""
if self.is_ai:
# Algoritmo AI molto semplice: segue la pallina con un leggero ritardo
if self.y + self.altezza/2 < palla_y:
self.y += self.velocita
elif self.y + self.altezza/2 > palla_y:
self.y -= self.velocita
else:
# Movimento umano
if tasti[self.tasto_su]:
self.y -= self.velocita
if tasti[self.tasto_giu]:
self.y += self.velocita
# Limiti dello schermo per evitare di sparire
if self.y < 0:
self.y = 0
elif self.y > ALTEZZA - self.altezza:
self.y = ALTEZZA - self.altezza
def disegna(self, schermo):
"""Disegna la racchetta a schermo."""
pygame.draw.rect(schermo, BIANCO, pygame.Rect(self.x, self.y, self.larghezza, self.altezza))
class Pallina:
"""Classe che rappresenta la pallina di gioco."""
def __init__(self, x, y, raggio, vel_x, vel_y):
self.x = x
self.y = y
self.raggio = raggio
self.vel_x = vel_x
self.vel_y = vel_y
self.vel_iniziale_x = vel_x
self.vel_iniziale_y = vel_y
def reset(self):
"""Riposiziona la pallina al centro e inverte la direzione di lancio."""
self.x = LARGHEZZA // 2
self.y = ALTEZZA // 2
self.vel_x = self.vel_iniziale_x * random.choice([1, -1])
self.vel_y = self.vel_iniziale_y * random.choice([1, -1])
def muovi(self):
"""Aggiorna la posizione e gestisce i rimbalzi sui bordi orizzontali."""
self.x += self.vel_x
self.y += self.vel_y
# Rimbalzo su soffitto e pavimento
if self.y - self.raggio <= 0 or self.y + self.raggio >= ALTEZZA:
self.vel_y = -self.vel_y
def disegna(self, schermo):
"""Disegna la pallina a schermo."""
pygame.draw.circle(schermo, BIANCO, (int(self.x), int(self.y)), self.raggio)
def controlla_collisione(self, racchetta):
"""Gestisce le collisioni elastiche con le racchette."""
rettangolo_racchetta = pygame.Rect(racchetta.x, racchetta.y, racchetta.larghezza, racchetta.altezza)
# Troviamo il punto più vicino sul rettangolo alla circonferenza
punto_vicino_x = max(racchetta.x, min(self.x, racchetta.x + racchetta.larghezza))
punto_vicino_y = max(racchetta.y, min(self.y, racchetta.y + racchetta.altezza))
# Calcoliamo la distanza
dist_x = self.x - punto_vicino_x
dist_y = self.y - punto_vicino_y
distanza_quadrata = dist_x**2 + dist_y**2
if distanza_quadrata < self.raggio**2:
# Collisione rilevata! Invertiamo la direzione orizzontale
self.vel_x = -self.vel_x
# Aumentiamo la velocità del 10% per rendere il gioco dinamico (Sfida 2)
self.vel_x *= 1.1
self.vel_y *= 1.1
# Allontaniamo leggermente la pallina per evitare collisioni multiple nello stesso frame
if self.x < LARGHEZZA / 2:
self.x = racchetta.x + racchetta.larghezza + self.raggio
else:
self.x = racchetta.x - self.raggio
def main():
pygame.init()
schermo = pygame.display.set_mode((LARGHEZZA, ALTEZZA))
pygame.display.set_caption("🏓 Arcade Pong OOP 🎮")
orologio = pygame.time.Clock()
# Creiamo gli oggetti di gioco (Istanze delle Classi)
# Sfida 4: la racchetta di destra viene controllata dall'AI
racchetta_sinistra = Racchetta(30, 250, 20, 100, 7, pygame.K_w, pygame.K_s)
racchetta_destra = Racchetta(750, 250, 20, 100, 7, pygame.K_UP, pygame.K_DOWN, is_ai=True)
pallina = Pallina(400, 300, 12, 5, 5)
punteggio_sinistro = 0
punteggio_destro = 0
try:
font = pygame.font.SysFont("Consolas", 60)
except Exception:
font = None
while True:
# 1. Rilevamento Input
tasti = pygame.key.get_pressed()
for evento in pygame.event.get():
if evento.type == pygame.QUIT:
pygame.quit()
sys.exit()
# 2. Aggiornamento dello Stato
racchetta_sinistra.muovi(tasti, pallina.y)
racchetta_destra.muovi(tasti, pallina.y)
pallina.muovi()
# Gestiamo le collisioni con le racchette
pallina.controlla_collisione(racchetta_sinistra)
pallina.controlla_collisione(racchetta_destra)
# Punti segnati
if pallina.x < 0:
punteggio_destro += 1
pallina.reset()
elif pallina.x > LARGHEZZA:
punteggio_sinistro += 1
pallina.reset()
# 3. Disegniamo lo schermo (Rendering)
schermo.fill(NERO)
# Disegniamo la linea di mezzeria tratteggiata
for y in range(0, ALTEZZA, 30):
pygame.draw.rect(schermo, BIANCO, pygame.Rect(LARGHEZZA // 2 - 2, y, 4, 15))
# Disegniamo gli oggetti
racchetta_sinistra.disegna(schermo)
racchetta_destra.disegna(schermo)
pallina.disegna(schermo)
# Disegniamo i punteggi (Sfida 3)
if font:
testo_sx = font.render(str(punteggio_sinistro), True, BIANCO)
testo_dx = font.render(str(punteggio_destro), True, BIANCO)
schermo.blit(testo_sx, (LARGHEZZA // 4 - testo_sx.get_width() // 2, 20))
schermo.blit(testo_dx, (3 * LARGHEZZA // 4 - testo_dx.get_width() // 2, 20))
pygame.display.flip()
orologio.tick(60)
if __name__ == "__main__":
main()
Analisi del Codice Completo
Analizziamo come la programmazione orientata agli oggetti ha strutturato il nostro codice:
1. Metodi specifici per comportamento
Le azioni come muovi() e disegna() non sono più sparpagliate all'interno del ciclo di gioco principale. Esse appartengono direttamente a Racchetta e Pallina. Il ciclo di gioco principale si limita a chiamare pallina.muovi() e racchetta_sinistra.muovi(tasti), rendendo il codice leggibile ed estensibile.
2. Calcolo della collisione circolare-rettangolare
Nel metodo controlla_collisione della classe Pallina, usiamo la matematica geometrica: troviamo il punto del rettangolo della racchetta più vicino alla sfera, ne calcoliamo la distanza e, se inferiore al raggio della sfera, applichiamo un rimbalzo invertendo vel_x.
3. Il costruttore __init__ per la flessibilità
Grazie ai parametri del costruttore, possiamo istanziare due racchette identiche nel comportamento ma posizionate in lati opposti e comandate da tasti diversi semplicemente passando argomenti differenti alla chiamata (es: pygame.K_w vs pygame.K_UP).
Curiosità: Home Pong
Lo sapevi che Pong ha ispirato la nascita della prima console per videogiochi domestica di successo? Si chiamava Home Pong (1975), si collegava direttamente alla televisione e aveva due manopole rotanti integrate nella scocca di plastica per controllare le racchette!
Provaci tu! — Pallina impazzita
Modifica il metodo reset() della classe Pallina in modo che a ogni punto segnato la pallina venga sparata a caso con una inclinazione e velocità verticale leggermente diversa, rendendo ogni inizio di partita imprevedibile!
Il Piccolo Glossario
| Termine | Definizione | | --------- | ------------- | | OOP (Object-Oriented Programming) | Paradigma di programmazione basato sull'uso di classi e oggetti per strutturare il codice. | | Class / Classe | Il modello teorico di base (lo stampo) che definisce come sarà strutturato un oggetto. | | Instance / Istanza | Sinonimo di oggetto reale, creato partendo da una classe. | | __init__ | Il costruttore standard di Python, usato per definire le proprietà iniziali degli oggetti. | | Method / Metodo | Una funzione dichiarata dentro una classe che opera direttamente sulle proprietà dell'oggetto. | | self | La parola chiave che Python usa per fare riferimento a se stesso e all'oggetto corrente. |
Prossimo capitolo
Appendice A: Setup Thonny e VS Code
Installazione passo-passo Windows/Mac/Linux, Thonny per principianti (Cap 1-6), VS Code per pygame (Cap 7+), estensioni consigliate, troubleshooting comune (PATH, permissions, antivirus).
Questo post è un estratto dal libro "Divento Programmatore: Python e i Giochi" (Volume 2 della collana "Cervello Elettronico") di Rocco Balzamà. Target: 10-14 anni · ~180 pagine · Thonny + VS Code.
Per approfondire
- Progetto finale in Python: dall'idea al programma
- Snake in Python con Pygame, passo per passo
- Python per principianti: cheat sheet e glossario
- Corsi e percorsi di programmazione