La librairie Pygame ne fonctionne pas présente dans capytale.
IL faut donc utiliser thonny.
Pygame, c’est une bibliothèque Python qui sert à créer facilement des jeux 2D et des animations interactives.
Elle fournit des “briques” prêtes à l’emploi pour :
- Fenêtre + boucle de jeu : créer une fenêtre, la rafraîchir à une certaine vitesse (FPS).
- Événements : lire le clavier, la souris, fermer la fenêtre (
pygame.event.get()). - Affichage 2D : dessiner des formes (rectangles, cercles…), afficher des images (sprites), gérer la transparence.
- Collision : grâce aux rectangles (
pygame.Rect) et àcolliderect. - Texte : afficher du texte avec des polices (
pygame.font). - Temps : mesurer le temps et rendre le mouvement fluide avec
Clocketdt. - Sons et musique : jouer des effets sonores (
pygame.mixer.Sound) et de la musique (pygame.mixer.music).
Le principe central : la “boucle de jeu”
Presque tous les programmes Pygame suivent ce schéma :
- Lire les événements (clavier, quitter…)
- Mettre à jour la position des objets (joueur, ennemis…)
- Dessiner la scène (fond, murs, sprites, score…)
- Afficher (
pygame.display.flip()) - Limiter les FPS (
clock.tick(60))
Pré-requis
- Installer
pygame:pip install pygame - Avoir un dossier
assetsdans le même répertoire que le code python
1) Création et fermeture d’une fenêtre
code python 1
import pygame
pygame.init()
LARGEUR, HAUTEUR = 800, 500
ecran = pygame.display.set_mode((LARGEUR, HAUTEUR))
pygame.display.set_caption('1) Fenêtre Pygame')
horloge = pygame.time.Clock()
en_cours = True
while en_cours:
for event in pygame.event.get():
if event.type == pygame.QUIT:
en_cours = False
if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
en_cours = False
ecran.fill((0, 0, 0))
pygame.display.flip()
horloge.tick(60)
pygame.quit()
2) Modification des couleurs
code python 2
import pygame
pygame.init()
LARGEUR, HAUTEUR = 800, 500
ecran = pygame.display.set_mode((LARGEUR, HAUTEUR))
pygame.display.set_caption('2) Couleurs')
horloge = pygame.time.Clock()
couleur_fond = (20, 20, 30)
en_cours = True
while en_cours:
for event in pygame.event.get():
if event.type == pygame.QUIT:
en_cours = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
en_cours = False
elif event.key == pygame.K_1:
couleur_fond = (30, 30, 40)
elif event.key == pygame.K_2:
couleur_fond = (60, 20, 20)
elif event.key == pygame.K_3:
couleur_fond = (20, 60, 30)
ecran.fill(couleur_fond)
pygame.display.flip()
horloge.tick(60)
pygame.quit()
3) Création d’un rectangle
code python 3
import pygame
pygame.init()
LARGEUR, HAUTEUR = 800, 500
ecran = pygame.display.set_mode((LARGEUR, HAUTEUR))
pygame.display.set_caption('3) Rectangle')
horloge = pygame.time.Clock()
rectangle = pygame.Rect(0, 0, 80, 60)
rectangle.center = (LARGEUR//2, HAUTEUR//2)
en_cours = True
while en_cours:
for event in pygame.event.get():
if event.type == pygame.QUIT:
en_cours = False
if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
en_cours = False
ecran.fill((20, 20, 30))
pygame.draw.rect(ecran, (80, 140, 255), rectangle, border_radius=10)
pygame.display.flip()
horloge.tick(60)
pygame.quit()
4) Déplacement du rectangle avec le clavier (flèches)
code python 4
import pygame
pygame.init()
LARGEUR, HAUTEUR = 800, 500
ecran = pygame.display.set_mode((LARGEUR, HAUTEUR))
pygame.display.set_caption('4) Rectangle au clavier')
horloge = pygame.time.Clock()
rectangle = pygame.Rect(0, 0, 80, 60)
rectangle.center = (LARGEUR//2, HAUTEUR//2)
vitesse = 280.0 # pixels par seconde
en_cours = True
while en_cours:
dt = horloge.tick(60) / 1000.0
for event in pygame.event.get():
if event.type == pygame.QUIT:
en_cours = False
if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
en_cours = False
touches = pygame.key.get_pressed()
dx, dy = 0, 0
if touches[pygame.K_LEFT]: dx -= 1
if touches[pygame.K_RIGHT]: dx += 1
if touches[pygame.K_UP]: dy -= 1
if touches[pygame.K_DOWN]: dy += 1
rectangle.x += int(dx * vitesse * dt)
rectangle.y += int(dy * vitesse * dt)
# Empêcher de sortir de la fenêtre
rectangle.clamp_ip(pygame.Rect(0, 0, LARGEUR, HAUTEUR))
ecran.fill((20, 20, 30))
pygame.draw.rect(ecran, (80, 140, 255), rectangle, border_radius=10)
pygame.display.flip()
pygame.quit()
5) Remplacer le rectangle par Pac‑Man (images préenregistrées) + déplacement au clavier
code python 5
import pygame
from pathlib import Path
pygame.init()
LARGEUR, HAUTEUR = 800, 500
ecran = pygame.display.set_mode((LARGEUR, HAUTEUR))
pygame.display.set_caption('5) Pac-Man au clavier')
horloge = pygame.time.Clock()
# Charger les images
dossier_assets = Path('assets')
fichiers = ['pacman_0.png', 'pacman_1.png', 'pacman_2.png']
frames = []
for nom in fichiers:
img = pygame.image.load(str(dossier_assets / nom)).convert_alpha()
img = pygame.transform.smoothscale(img, (64, 64))
frames.append(img)
def frames_orientees(direction):
dx, dy = direction
if (dx, dy) == (1, 0):
return frames
if (dx, dy) == (-1, 0):
return [pygame.transform.flip(f, True, False) for f in frames]
if (dx, dy) == (0, -1):
return [pygame.transform.rotate(f, 90) for f in frames]
if (dx, dy) == (0, 1):
return [pygame.transform.rotate(f, -90) for f in frames]
return frames
# Pac-Man (dictionnaire)
pacman = {
'x': LARGEUR//2,
'y': HAUTEUR//2,
'vitesse': 240.0,
'direction_aff': (1, 0),
'frame': 0,
'temps_anim': 0.0,
}
en_cours = True
while en_cours:
dt = horloge.tick(60) / 1000.0
for event in pygame.event.get():
if event.type == pygame.QUIT:
en_cours = False
if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
en_cours = False
touches = pygame.key.get_pressed()
dx, dy = 0, 0
if touches[pygame.K_LEFT]: dx -= 1
if touches[pygame.K_RIGHT]: dx += 1
if touches[pygame.K_UP]: dy -= 1
if touches[pygame.K_DOWN]: dy += 1
# Déplacement
pacman['x'] += dx * pacman['vitesse'] * dt
pacman['y'] += dy * pacman['vitesse'] * dt
# Limites fenêtre (pour éviter de sortir)
pacman['x'] = max(32, min(LARGEUR-32, pacman['x']))
pacman['y'] = max(32, min(HAUTEUR-32, pacman['y']))
bouge = (dx != 0 or dy != 0)
if bouge:
pacman['direction_aff'] = (dx, dy)
pacman['temps_anim'] += dt
if pacman['temps_anim'] >= 1.0/12:
pacman['temps_anim'] = 0.0
pacman['frame'] = (pacman['frame'] + 1) % len(frames)
else:
pacman['frame'] = 0
pacman['temps_anim'] = 0.0
ecran.fill((20, 20, 30))
fr = frames_orientees(pacman['direction_aff'])
sprite = fr[pacman['frame']]
rect = sprite.get_rect(center=(int(pacman['x']), int(pacman['y'])))
ecran.blit(sprite, rect.topleft)
pygame.display.flip()
pygame.quit()
6) Dessiner un labyrinthe très simple (1 seul niveau)
code python 6
import pygame
pygame.init()
LABYRINTHE = [
'####################',
'#........#.........#',
'#.######.#.#####...#',
'#.#....#.#.....#...#',
'#.#.##.#.#####.#...#',
'#...##.#.....#.#...#',
'###.##.#####.#.###.#',
'#......#.....#.....#',
'####################',
]
grille = [list(l) for l in LABYRINTHE]
H = len(grille)
W = len(grille[0])
LARGEUR, HAUTEUR = 900, 520
ecran = pygame.display.set_mode((LARGEUR, HAUTEUR))
pygame.display.set_caption('6) Labyrinthe')
horloge = pygame.time.Clock()
marge = 30
taille_tuile = min((LARGEUR - 2*marge)//W, (HAUTEUR - 2*marge)//H)
taille_tuile = max(16, int(taille_tuile))
ox = (LARGEUR - W*taille_tuile)//2
oy = (HAUTEUR - H*taille_tuile)//2
en_cours = True
while en_cours:
for event in pygame.event.get():
if event.type == pygame.QUIT:
en_cours = False
if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
en_cours = False
ecran.fill((15, 15, 20))
for y in range(H):
for x in range(W):
r = pygame.Rect(ox + x*taille_tuile, oy + y*taille_tuile, taille_tuile, taille_tuile)
if grille[y][x] == '#':
pygame.draw.rect(ecran, (70, 70, 85), r)
else:
pygame.draw.rect(ecran, (25, 25, 32), r)
pygame.display.flip()
horloge.tick(60)
pygame.quit()
7) Pac‑Man déplaçable dans le labyrinthe (au clavier)
code python 7
import pygame
from pathlib import Path
pygame.init()
LABYRINTHE = [
'####################',
'#........#.........#',
'#.######.#.#####...#',
'#.#....#.#.....#...#',
'#.#.##.#.#####.#...#',
'#...##.#.....#.#...#',
'###.##.#####.#.###.#',
'#......#.....#.....#',
'####################',
]
grille = [list(l) for l in LABYRINTHE]
H = len(grille)
W = len(grille[0])
LARGEUR, HAUTEUR = 900, 520
ecran = pygame.display.set_mode((LARGEUR, HAUTEUR))
pygame.display.set_caption('7) Pac-Man dans un labyrinthe (clavier)')
horloge = pygame.time.Clock()
marge = 30
taille_tuile = min((LARGEUR - 2*marge)//W, (HAUTEUR - 2*marge)//H)
taille_tuile = max(16, int(taille_tuile))
ox = (LARGEUR - W*taille_tuile)//2
oy = (HAUTEUR - H*taille_tuile)//2
def rect_tuile(x_case, y_case):
return pygame.Rect(ox + x_case*taille_tuile, oy + y_case*taille_tuile, taille_tuile, taille_tuile)
# Préparer la liste des murs (Rect)
murs = []
for y in range(H):
for x in range(W):
if grille[y][x] == '#':
murs.append(rect_tuile(x, y))
# Charger Pac-Man
dossier_assets = Path('assets')
fichiers = ['pacman_0.png', 'pacman_1.png', 'pacman_2.png']
frames_base = []
for nom in fichiers:
img = pygame.image.load(str(dossier_assets / nom)).convert_alpha()
img = pygame.transform.smoothscale(img, (int(taille_tuile*0.95), int(taille_tuile*0.95)))
frames_base.append(img)
def frames_orientees(direction):
dx, dy = direction
if (dx, dy) == (1, 0):
return frames_base
if (dx, dy) == (-1, 0):
return [pygame.transform.flip(f, True, False) for f in frames_base]
if (dx, dy) == (0, -1):
return [pygame.transform.rotate(f, 90) for f in frames_base]
if (dx, dy) == (0, 1):
return [pygame.transform.rotate(f, -90) for f in frames_base]
return frames_base
def rect_pacman(px, py):
# hitbox un peu plus petite que la tuile
s = int(taille_tuile*0.78)
return pygame.Rect(int(px - s/2), int(py - s/2), s, s)
def collision_mur(r):
return any(r.colliderect(m) for m in murs)
def dessiner_labyrinthe():
for y in range(H):
for x in range(W):
r = rect_tuile(x, y)
if grille[y][x] == '#':
pygame.draw.rect(ecran, (70, 70, 85), r)
else:
pygame.draw.rect(ecran, (25, 25, 32), r)
# Pac-Man (dictionnaire)
pacman = {
'x': ox + 1.5*taille_tuile,
'y': oy + 1.5*taille_tuile,
'vitesse': 220.0,
'direction_aff': (1, 0),
'frame': 0,
'temps_anim': 0.0,
}
en_cours = True
while en_cours:
dt = horloge.tick(60) / 1000.0
for event in pygame.event.get():
if event.type == pygame.QUIT:
en_cours = False
if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
en_cours = False
touches = pygame.key.get_pressed()
dx, dy = 0, 0
if touches[pygame.K_LEFT]: dx -= 1
if touches[pygame.K_RIGHT]: dx += 1
if touches[pygame.K_UP]: dy -= 1
if touches[pygame.K_DOWN]: dy += 1
vx = dx * pacman['vitesse'] * dt
vy = dy * pacman['vitesse'] * dt
if dx != 0 or dy != 0:
pacman['direction_aff'] = (dx, dy)
# Déplacement séparé X puis Y (plus simple pour gérer les collisions)
nx = pacman['x'] + vx
if not collision_mur(rect_pacman(nx, pacman['y'])):
pacman['x'] = nx
ny = pacman['y'] + vy
if not collision_mur(rect_pacman(pacman['x'], ny)):
pacman['y'] = ny
# Animation
bouge = (dx != 0 or dy != 0)
if bouge:
pacman['temps_anim'] += dt
if pacman['temps_anim'] >= 1.0/12:
pacman['temps_anim'] = 0.0
pacman['frame'] = (pacman['frame'] + 1) % len(frames_base)
else:
pacman['frame'] = 0
pacman['temps_anim'] = 0.0
ecran.fill((15, 15, 20))
dessiner_labyrinthe()
fr = frames_orientees(pacman['direction_aff'])
sprite = fr[pacman['frame']]
rect_sprite = sprite.get_rect(center=(int(pacman['x']), int(pacman['y'])))
ecran.blit(sprite, rect_sprite.topleft)
pygame.display.flip()
pygame.quit()
8) Pastilles à collecter + score
code python 8
import pygame
from pathlib import Path
pygame.init()
LABYRINTHE = [
"####################",
"#P.......#.........#",
"#.######.#.#####...#",
"#.#....#.#.....#...#",
"#.#.##.#.#####.#...#",
"#...##.#.....#.#...#",
"###.##.#####.#.###.#",
"#......#.....#.....#",
"##################D#",
]
grille = [list(l) for l in LABYRINTHE]
H = len(grille)
W = len(grille[0])
LARGEUR, HAUTEUR = 900, 520
ecran = pygame.display.set_mode((LARGEUR, HAUTEUR))
pygame.display.set_caption("8) Pastilles + score")
horloge = pygame.time.Clock()
police = pygame.font.SysFont(None, 28)
marge = 30
taille_tuile = min((LARGEUR - 2*marge)//W, (HAUTEUR - 2*marge)//H)
taille_tuile = max(16, int(taille_tuile))
ox = (LARGEUR - W*taille_tuile)//2
oy = (HAUTEUR - H*taille_tuile)//2
def rect_tuile(x_case, y_case):
return pygame.Rect(ox + x_case*taille_tuile, oy + y_case*taille_tuile, taille_tuile, taille_tuile)
# Murs + positions spéciales
murs = []
case_depart = None
case_porte = None
for y in range(H):
for x in range(W):
c = grille[y][x]
if c == "#":
murs.append(rect_tuile(x, y))
elif c == "P":
case_depart = (x, y)
grille[y][x] = "."
elif c == "D":
case_porte = (x, y)
grille[y][x] = "."
# Pastilles (toutes les cases de sol sauf départ/porte)
pastilles = set()
for y in range(H):
for x in range(W):
if grille[y][x] == "." and (x, y) not in (case_depart, case_porte):
pastilles.add((x, y))
# Charger Pac-Man
dossier_assets = Path("assets")
fichiers = ["pacman_0.png", "pacman_1.png", "pacman_2.png"]
frames_base = []
for nom in fichiers:
img = pygame.image.load(str(dossier_assets / nom)).convert_alpha()
img = pygame.transform.smoothscale(img, (int(taille_tuile*0.95), int(taille_tuile*0.95)))
frames_base.append(img)
def frames_orientees(direction):
dx, dy = direction
if (dx, dy) == (1, 0):
return frames_base
if (dx, dy) == (-1, 0):
return [pygame.transform.flip(f, True, False) for f in frames_base]
if (dx, dy) == (0, -1):
return [pygame.transform.rotate(f, 90) for f in frames_base]
if (dx, dy) == (0, 1):
return [pygame.transform.rotate(f, -90) for f in frames_base]
return frames_base
def rect_pacman(px, py):
s = int(taille_tuile*0.78)
return pygame.Rect(int(px - s/2), int(py - s/2), s, s)
def collision_mur(r):
return any(r.colliderect(m) for m in murs)
def case_depuis_position(px, py):
return (int((px - ox)//taille_tuile), int((py - oy)//taille_tuile))
# Pac-Man
pacman = {
"x": ox + (case_depart[0] + 0.5)*taille_tuile,
"y": oy + (case_depart[1] + 0.5)*taille_tuile,
"vitesse": 220.0,
"direction_aff": (1, 0),
"frame": 0,
"temps_anim": 0.0,
}
score = 0
def dessiner_labyrinthe():
for y in range(H):
for x in range(W):
r = rect_tuile(x, y)
if grille[y][x] == "#":
pygame.draw.rect(ecran, (70, 70, 85), r)
else:
pygame.draw.rect(ecran, (25, 25, 32), r)
# Porte (orange)
r = rect_tuile(case_porte[0], case_porte[1]).inflate(-10, -10)
pygame.draw.rect(ecran, (220, 120, 60), r, border_radius=10)
def dessiner_pastilles():
rayon = max(2, taille_tuile//8)
for (x, y) in pastilles:
cx = ox + x*taille_tuile + taille_tuile//2
cy = oy + y*taille_tuile + taille_tuile//2
pygame.draw.circle(ecran, (240, 200, 60), (cx, cy), rayon)
en_cours = True
while en_cours:
dt = horloge.tick(60) / 1000.0
for event in pygame.event.get():
if event.type == pygame.QUIT:
en_cours = False
if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
en_cours = False
touches = pygame.key.get_pressed()
dx, dy = 0, 0
if touches[pygame.K_LEFT]: dx -= 1
if touches[pygame.K_RIGHT]: dx += 1
if touches[pygame.K_UP]: dy -= 1
if touches[pygame.K_DOWN]: dy += 1
vx = dx * pacman["vitesse"] * dt
vy = dy * pacman["vitesse"] * dt
if dx != 0 or dy != 0:
pacman["direction_aff"] = (dx, dy)
nx = pacman["x"] + vx
if not collision_mur(rect_pacman(nx, pacman["y"])):
pacman["x"] = nx
ny = pacman["y"] + vy
if not collision_mur(rect_pacman(pacman["x"], ny)):
pacman["y"] = ny
# Collecte
case_p = case_depuis_position(pacman["x"], pacman["y"])
if case_p in pastilles:
pastilles.remove(case_p)
score += 1
# Animation
bouge = (dx != 0 or dy != 0)
if bouge:
pacman["temps_anim"] += dt
if pacman["temps_anim"] >= 1.0/12:
pacman["temps_anim"] = 0.0
pacman["frame"] = (pacman["frame"] + 1) % len(frames_base)
else:
pacman["frame"] = 0
pacman["temps_anim"] = 0.0
# Dessin
ecran.fill((15, 15, 20))
dessiner_labyrinthe()
dessiner_pastilles()
fr = frames_orientees(pacman["direction_aff"])
sprite = fr[pacman["frame"]]
ecran.blit(sprite, sprite.get_rect(center=(int(pacman["x"]), int(pacman["y"]))))
ecran.blit(police.render(f"Score : {score}", True, (235,235,245)), (18, 14))
pygame.display.flip()
pygame.quit()
9) Sons : chomp + victoire
code python 9
import pygame
from pathlib import Path
pygame.init()
try:
pygame.mixer.init()
except Exception:
pass
LABYRINTHE = [
"####################",
"#P.................#",
"#.######.#.#####...#",
"#.#....#.#.....#...#",
"#.#.##.#.#####.#...#",
"#...##.#.....#.#...#",
"###.##.#####.#.###.#",
"#......#.....#.....#",
"##################D#",
]
grille = [list(l) for l in LABYRINTHE]
H = len(grille)
W = len(grille[0])
LARGEUR, HAUTEUR = 900, 520
ecran = pygame.display.set_mode((LARGEUR, HAUTEUR))
pygame.display.set_caption("9) Sons : chomp + victoire")
horloge = pygame.time.Clock()
police = pygame.font.SysFont(None, 28)
marge = 30
taille_tuile = min((LARGEUR - 2*marge)//W, (HAUTEUR - 2*marge)//H)
taille_tuile = max(16, int(taille_tuile))
ox = (LARGEUR - W*taille_tuile)//2
oy = (HAUTEUR - H*taille_tuile)//2
def rect_tuile(x_case, y_case):
return pygame.Rect(ox + x_case*taille_tuile, oy + y_case*taille_tuile, taille_tuile, taille_tuile)
murs = []
case_depart = None
case_porte = None
for y in range(H):
for x in range(W):
c = grille[y][x]
if c == "#":
murs.append(rect_tuile(x, y))
elif c == "P":
case_depart = (x, y); grille[y][x] = "."
elif c == "D":
case_porte = (x, y); grille[y][x] = "."
pastilles = set((x,y) for y in range(H) for x in range(W) if grille[y][x]=="." and (x,y) not in (case_depart, case_porte))
dossier_assets = Path("assets")
def charger_son(nom, volume=0.6):
try:
if pygame.mixer.get_init() and (dossier_assets / nom).exists():
s = pygame.mixer.Sound(str(dossier_assets / nom))
s.set_volume(volume)
return s
except Exception:
pass
return None
son_chomp = charger_son("chomp.wav", 0.6)
son_victoire = charger_son("win.wav", 0.8)
# Sprites Pac-Man
fichiers = ["pacman_0.png", "pacman_1.png", "pacman_2.png"]
frames_base = []
for nom in fichiers:
img = pygame.image.load(str(dossier_assets / nom)).convert_alpha()
img = pygame.transform.smoothscale(img, (int(taille_tuile*0.95), int(taille_tuile*0.95)))
frames_base.append(img)
def frames_orientees(direction):
dx, dy = direction
if (dx, dy) == (1, 0): return frames_base
if (dx, dy) == (-1, 0): return [pygame.transform.flip(f, True, False) for f in frames_base]
if (dx, dy) == (0, -1): return [pygame.transform.rotate(f, 90) for f in frames_base]
if (dx, dy) == (0, 1): return [pygame.transform.rotate(f, -90) for f in frames_base]
return frames_base
def rect_pacman(px, py):
s = int(taille_tuile*0.78)
return pygame.Rect(int(px - s/2), int(py - s/2), s, s)
def collision_mur(r):
return any(r.colliderect(m) for m in murs)
def case_depuis_position(px, py):
return (int((px - ox)//taille_tuile), int((py - oy)//taille_tuile))
pacman = {
"x": ox + (case_depart[0] + 0.5)*taille_tuile,
"y": oy + (case_depart[1] + 0.5)*taille_tuile,
"vitesse": 220.0,
"direction_aff": (1, 0),
"frame": 0,
"temps_anim": 0.0,
}
score = 0
message = ""
victoire_jouee = False
def dessiner_labyrinthe():
for y in range(H):
for x in range(W):
r = rect_tuile(x, y)
pygame.draw.rect(ecran, (70,70,85) if grille[y][x]=="#" else (25,25,32), r)
r = rect_tuile(case_porte[0], case_porte[1]).inflate(-10, -10)
pygame.draw.rect(ecran, (220,120,60), r, border_radius=10)
def dessiner_pastilles():
rayon = max(2, taille_tuile//8)
for (x, y) in pastilles:
cx = ox + x*taille_tuile + taille_tuile//2
cy = oy + y*taille_tuile + taille_tuile//2
pygame.draw.circle(ecran, (240,200,60), (cx, cy), rayon)
en_cours = True
while en_cours:
dt = horloge.tick(60) / 1000.0
for event in pygame.event.get():
if event.type == pygame.QUIT:
en_cours = False
if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
en_cours = False
touches = pygame.key.get_pressed()
dx, dy = 0, 0
if touches[pygame.K_LEFT]: dx -= 1
if touches[pygame.K_RIGHT]: dx += 1
if touches[pygame.K_UP]: dy -= 1
if touches[pygame.K_DOWN]: dy += 1
vx = dx * pacman["vitesse"] * dt
vy = dy * pacman["vitesse"] * dt
if dx != 0 or dy != 0:
pacman["direction_aff"] = (dx, dy)
nx = pacman["x"] + vx
if not collision_mur(rect_pacman(nx, pacman["y"])):
pacman["x"] = nx
ny = pacman["y"] + vy
if not collision_mur(rect_pacman(pacman["x"], ny)):
pacman["y"] = ny
case_p = case_depuis_position(pacman["x"], pacman["y"])
if case_p in pastilles:
pastilles.remove(case_p)
score += 1
if son_chomp: son_chomp.play()
if (len(pastilles) == 0) and (not victoire_jouee):
victoire_jouee = True
message = "Bravo ! Toutes les pastilles sont mangées."
if son_victoire: son_victoire.play()
bouge = (dx != 0 or dy != 0)
if bouge:
pacman["temps_anim"] += dt
if pacman["temps_anim"] >= 1.0/12:
pacman["temps_anim"] = 0.0
pacman["frame"] = (pacman["frame"] + 1) % len(frames_base)
else:
pacman["frame"] = 0
pacman["temps_anim"] = 0.0
ecran.fill((15,15,20))
dessiner_labyrinthe()
dessiner_pastilles()
fr = frames_orientees(pacman["direction_aff"])
sprite = fr[pacman["frame"]]
ecran.blit(sprite, sprite.get_rect(center=(int(pacman["x"]), int(pacman["y"]))))
ecran.blit(police.render(f"Score : {score}", True, (235,235,245)), (18,14))
if message:
ecran.blit(police.render(message, True, (245,245,245)), (18,40))
pygame.display.flip()
pygame.quit()
10) Ennemi + collision
code python 10
import pygame
import random
from pathlib import Path
pygame.init()
try:
pygame.mixer.init()
except Exception:
pass
LABYRINTHE = [
"####################",
"#P.......#.........#",
"#.######.#.#####...#",
"#.#....#.#.....#...#",
"#.#.##.#.#####.#...#",
"#...##.#.....#.#...#",
"###.##.#####.#.###.#",
"#......#.....#..E..#",
"##################D#",
]
grille = [list(l) for l in LABYRINTHE]
H = len(grille)
W = len(grille[0])
LARGEUR, HAUTEUR = 900, 520
ecran = pygame.display.set_mode((LARGEUR, HAUTEUR))
pygame.display.set_caption("10) Ennemi + collision")
horloge = pygame.time.Clock()
police = pygame.font.SysFont(None, 28)
marge = 30
taille_tuile = min((LARGEUR - 2*marge)//W, (HAUTEUR - 2*marge)//H)
taille_tuile = max(16, int(taille_tuile))
ox = (LARGEUR - W*taille_tuile)//2
oy = (HAUTEUR - H*taille_tuile)//2
def rect_tuile(x_case, y_case):
return pygame.Rect(ox + x_case*taille_tuile, oy + y_case*taille_tuile, taille_tuile, taille_tuile)
def centre_case(x_case, y_case):
return (ox + (x_case + 0.5)*taille_tuile, oy + (y_case + 0.5)*taille_tuile)
# Parse
murs = []
case_depart = None
case_porte = None
case_ennemi = None
for y in range(H):
for x in range(W):
c = grille[y][x]
if c == "#":
murs.append(rect_tuile(x, y))
elif c == "P":
case_depart = (x, y); grille[y][x] = "."
elif c == "D":
case_porte = (x, y); grille[y][x] = "."
elif c == "E":
case_ennemi = (x, y); grille[y][x] = "."
def est_mur_case(x, y):
return grille[y][x] == "#"
pastilles = set((x,y) for y in range(H) for x in range(W) if grille[y][x]=="." and (x,y) not in (case_depart, case_porte))
# Sons
dossier_assets = Path("assets")
def charger_son(nom, volume=0.6):
try:
if pygame.mixer.get_init() and (dossier_assets / nom).exists():
s = pygame.mixer.Sound(str(dossier_assets / nom))
s.set_volume(volume)
return s
except Exception:
pass
return None
son_chomp = charger_son("chomp.wav", 0.6)
son_mort = charger_son("death.wav", 0.7)
# Pac-Man sprites
fichiers = ["pacman_0.png", "pacman_1.png", "pacman_2.png"]
frames_base = []
for nom in fichiers:
img = pygame.image.load(str(dossier_assets / nom)).convert_alpha()
img = pygame.transform.smoothscale(img, (int(taille_tuile*0.95), int(taille_tuile*0.95)))
frames_base.append(img)
def frames_orientees(direction):
dx, dy = direction
if (dx, dy) == (1, 0): return frames_base
if (dx, dy) == (-1, 0): return [pygame.transform.flip(f, True, False) for f in frames_base]
if (dx, dy) == (0, -1): return [pygame.transform.rotate(f, 90) for f in frames_base]
if (dx, dy) == (0, 1): return [pygame.transform.rotate(f, -90) for f in frames_base]
return frames_base
def rect_pacman(px, py):
s = int(taille_tuile*0.78)
return pygame.Rect(int(px - s/2), int(py - s/2), s, s)
def collision_mur(r):
return any(r.colliderect(m) for m in murs)
def case_depuis_position(px, py):
return (int((px - ox)//taille_tuile), int((py - oy)//taille_tuile))
# Pac-Man
px0, py0 = centre_case(*case_depart)
pacman = {"x": px0, "y": py0, "vitesse": 220.0, "direction_aff": (1,0), "frame": 0, "temps_anim": 0.0}
score = 0
# Ennemi (déplacement sur la grille)
DIRECTIONS = [(1,0), (-1,0), (0,1), (0,-1)]
OPPOSE = {(1,0):(-1,0), (-1,0):(1,0), (0,1):(0,-1), (0,-1):(0,1)}
ennemi = {"case": case_ennemi, "direction": random.choice(DIRECTIONS), "progression": 0.0, "vitesse_cases": 4.5}
def direction_possible(case, direction):
x, y = case
dx, dy = direction
nx, ny = x + dx, y + dy
if nx < 0 or nx >= W or ny < 0 or ny >= H:
return False
return not est_mur_case(nx, ny)
def mettre_a_jour_ennemi(dt):
distance = ennemi["vitesse_cases"] * dt
eps = 1e-6
while distance > 0:
if ennemi["progression"] <= eps:
if not direction_possible(ennemi["case"], ennemi["direction"]):
choix = DIRECTIONS[:]
random.shuffle(choix)
opp = OPPOSE.get(ennemi["direction"])
if opp in choix:
choix.remove(opp)
choix.append(opp)
for d in choix:
if direction_possible(ennemi["case"], d):
ennemi["direction"] = d
break
restant = 1.0 - ennemi["progression"]
pas = min(distance, restant)
ennemi["progression"] += pas
distance -= pas
if ennemi["progression"] >= 1.0 - eps:
x, y = ennemi["case"]
dx, dy = ennemi["direction"]
ennemi["case"] = (x+dx, y+dy)
ennemi["progression"] = 0.0
def position_ennemi_pixels():
x, y = ennemi["case"]
dx, dy = ennemi["direction"]
p = ennemi["progression"]
return (ox + (x + 0.5 + dx*p)*taille_tuile, oy + (y + 0.5 + dy*p)*taille_tuile)
def dessiner_labyrinthe():
for y in range(H):
for x in range(W):
r = rect_tuile(x, y)
pygame.draw.rect(ecran, (70,70,85) if grille[y][x]=="#" else (25,25,32), r)
r = rect_tuile(case_porte[0], case_porte[1]).inflate(-10, -10)
pygame.draw.rect(ecran, (220,120,60), r, border_radius=10)
def dessiner_pastilles():
rayon = max(2, taille_tuile//8)
for (x, y) in pastilles:
cx = ox + x*taille_tuile + taille_tuile//2
cy = oy + y*taille_tuile + taille_tuile//2
pygame.draw.circle(ecran, (240,200,60), (cx, cy), rayon)
etat = "jeu"
message = ""
en_cours = True
while en_cours:
dt = horloge.tick(60) / 1000.0
for event in pygame.event.get():
if event.type == pygame.QUIT:
en_cours = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
en_cours = False
if event.key == pygame.K_r and etat != "jeu":
# reset
px0, py0 = centre_case(*case_depart)
pacman.update({"x": px0, "y": py0, "direction_aff": (1,0), "frame": 0, "temps_anim": 0.0})
score = 0
pastilles = set((x,y) for y in range(H) for x in range(W) if grille[y][x]=="." and (x,y) not in (case_depart, case_porte))
ennemi.update({"case": case_ennemi, "direction": random.choice(DIRECTIONS), "progression": 0.0})
etat = "jeu"
message = ""
if etat == "jeu":
touches = pygame.key.get_pressed()
dx, dy = 0, 0
if touches[pygame.K_LEFT]: dx -= 1
if touches[pygame.K_RIGHT]: dx += 1
if touches[pygame.K_UP]: dy -= 1
if touches[pygame.K_DOWN]: dy += 1
vx = dx * pacman["vitesse"] * dt
vy = dy * pacman["vitesse"] * dt
if dx != 0 or dy != 0:
pacman["direction_aff"] = (dx, dy)
nx = pacman["x"] + vx
if not collision_mur(rect_pacman(nx, pacman["y"])):
pacman["x"] = nx
ny = pacman["y"] + vy
if not collision_mur(rect_pacman(pacman["x"], ny)):
pacman["y"] = ny
case_p = case_depuis_position(pacman["x"], pacman["y"])
if case_p in pastilles:
pastilles.remove(case_p)
score += 1
if son_chomp: son_chomp.play()
bouge = (dx != 0 or dy != 0)
if bouge:
pacman["temps_anim"] += dt
if pacman["temps_anim"] >= 1.0/12:
pacman["temps_anim"] = 0.0
pacman["frame"] = (pacman["frame"] + 1) % len(frames_base)
else:
pacman["frame"] = 0
pacman["temps_anim"] = 0.0
# Ennemi + collision
mettre_a_jour_ennemi(dt)
ex, ey = position_ennemi_pixels()
dxp = pacman["x"] - ex
dyp = pacman["y"] - ey
if (dxp*dxp + dyp*dyp) < (0.45*taille_tuile)**2:
etat = "perdu"
message = "Perdu ! (ennemi) — R pour recommencer"
if son_mort: son_mort.play()
# Dessin
ecran.fill((15,15,20))
dessiner_labyrinthe()
dessiner_pastilles()
ex, ey = position_ennemi_pixels()
pygame.draw.circle(ecran, (220,60,60), (int(ex), int(ey)), max(5, taille_tuile//3))
fr = frames_orientees(pacman["direction_aff"])
sprite = fr[pacman["frame"]]
ecran.blit(sprite, sprite.get_rect(center=(int(pacman["x"]), int(pacman["y"]))))
ecran.blit(police.render(f"Score : {score}", True, (235,235,245)), (18,14))
if message:
ecran.blit(police.render(message, True, (245,245,245)), (18,40))
pygame.display.flip()
pygame.quit()
11) Clé + porte + victoire (avec sons)
code python 11
import pygame
import random
from pathlib import Path
pygame.init()
try:
pygame.mixer.init()
except Exception:
pass
LABYRINTHE = [
"####################",
"#P.......#.....K...#",
"#.######.#.#####...#",
"#.#....#.#.....#...#",
"#.#.##.#.#####.#...#",
"#...##.#.....#.#...#",
"###.##.#####.#.###.#",
"#......#.....#..E..#",
"##################D#",
]
grille = [list(l) for l in LABYRINTHE]
H = len(grille)
W = len(grille[0])
LARGEUR, HAUTEUR = 900, 520
ecran = pygame.display.set_mode((LARGEUR, HAUTEUR))
pygame.display.set_caption("12) Jeu complet Pac-Man (1 niveau)")
horloge = pygame.time.Clock()
police = pygame.font.SysFont(None, 28)
marge = 30
taille_tuile = min((LARGEUR - 2*marge)//W, (HAUTEUR - 2*marge)//H)
taille_tuile = max(16, int(taille_tuile))
ox = (LARGEUR - W*taille_tuile)//2
oy = (HAUTEUR - H*taille_tuile)//2
def rect_tuile(x_case, y_case):
return pygame.Rect(ox + x_case*taille_tuile, oy + y_case*taille_tuile, taille_tuile, taille_tuile)
def centre_case(x_case, y_case):
return (ox + (x_case + 0.5)*taille_tuile, oy + (y_case + 0.5)*taille_tuile)
# Parse
murs = []
case_depart = None
case_porte = None
case_ennemi = None
case_cle = None
for y in range(H):
for x in range(W):
c = grille[y][x]
if c == "#":
murs.append(rect_tuile(x, y))
elif c == "P":
case_depart = (x, y); grille[y][x] = "."
elif c == "D":
case_porte = (x, y); grille[y][x] = "."
elif c == "E":
case_ennemi = (x, y); grille[y][x] = "."
elif c == "K":
case_cle = (x, y); grille[y][x] = "."
def est_mur_case(x, y):
return grille[y][x] == "#"
def creer_pastilles():
return set((x,y) for y in range(H) for x in range(W) if grille[y][x]=="." and (x,y) not in (case_depart, case_porte, case_cle))
# Sons + musique
dossier_assets = Path("assets")
def charger_son(nom, volume=0.6):
try:
if pygame.mixer.get_init() and (dossier_assets / nom).exists():
s = pygame.mixer.Sound(str(dossier_assets / nom))
s.set_volume(volume)
return s
except Exception:
pass
return None
son_chomp = charger_son("chomp.wav", 0.6)
son_cle = charger_son("key.wav", 0.7)
son_porte = charger_son("door.wav", 0.7)
son_mort = charger_son("death.wav", 0.7)
son_victoire = charger_son("win.wav", 0.8)
musique = dossier_assets / "music.ogg"
if pygame.mixer.get_init() and musique.exists():
try:
pygame.mixer.music.load(str(musique))
pygame.mixer.music.set_volume(0.25)
pygame.mixer.music.play(-1)
except Exception:
pass
# Sprites Pac-Man
fichiers = ["pacman_0.png", "pacman_1.png", "pacman_2.png"]
frames_base = []
for nom in fichiers:
img = pygame.image.load(str(dossier_assets / nom)).convert_alpha()
img = pygame.transform.smoothscale(img, (int(taille_tuile*0.95), int(taille_tuile*0.95)))
frames_base.append(img)
def frames_orientees(direction):
dx, dy = direction
if (dx, dy) == (1, 0): return frames_base
if (dx, dy) == (-1, 0): return [pygame.transform.flip(f, True, False) for f in frames_base]
if (dx, dy) == (0, -1): return [pygame.transform.rotate(f, 90) for f in frames_base]
if (dx, dy) == (0, 1): return [pygame.transform.rotate(f, -90) for f in frames_base]
return frames_base
def rect_pacman(px, py):
s = int(taille_tuile*0.78)
return pygame.Rect(int(px - s/2), int(py - s/2), s, s)
def collision_mur(r):
return any(r.colliderect(m) for m in murs)
def case_depuis_position(px, py):
return (int((px - ox)//taille_tuile), int((py - oy)//taille_tuile))
# Ennemi (grid)
DIRECTIONS = [(1,0), (-1,0), (0,1), (0,-1)]
OPPOSE = {(1,0):(-1,0), (-1,0):(1,0), (0,1):(0,-1), (0,-1):(0,1)}
def direction_possible(case, direction):
x, y = case
dx, dy = direction
nx, ny = x + dx, y + dy
if nx < 0 or nx >= W or ny < 0 or ny >= H:
return False
return not est_mur_case(nx, ny)
def mettre_a_jour_ennemi(ennemi, dt):
distance = ennemi["vitesse_cases"] * dt
eps = 1e-6
while distance > 0:
if ennemi["progression"] <= eps:
if not direction_possible(ennemi["case"], ennemi["direction"]):
choix = DIRECTIONS[:]
random.shuffle(choix)
opp = OPPOSE.get(ennemi["direction"])
if opp in choix:
choix.remove(opp); choix.append(opp)
for d in choix:
if direction_possible(ennemi["case"], d):
ennemi["direction"] = d
break
restant = 1.0 - ennemi["progression"]
pas = min(distance, restant)
ennemi["progression"] += pas
distance -= pas
if ennemi["progression"] >= 1.0 - eps:
x, y = ennemi["case"]
dx, dy = ennemi["direction"]
ennemi["case"] = (x+dx, y+dy)
ennemi["progression"] = 0.0
def position_ennemi_pixels(ennemi):
x, y = ennemi["case"]
dx, dy = ennemi["direction"]
p = ennemi["progression"]
return (ox + (x + 0.5 + dx*p)*taille_tuile, oy + (y + 0.5 + dy*p)*taille_tuile)
def dessiner_labyrinthe(porte_ouverte):
for y in range(H):
for x in range(W):
r = rect_tuile(x, y)
pygame.draw.rect(ecran, (70,70,85) if grille[y][x]=="#" else (25,25,32), r)
r = rect_tuile(case_porte[0], case_porte[1]).inflate(-10, -10)
couleur = (80,220,140) if porte_ouverte else (220,120,60)
pygame.draw.rect(ecran, couleur, r, border_radius=10)
def dessiner_pastilles(pastilles):
rayon = max(2, taille_tuile//8)
for (x, y) in pastilles:
cx = ox + x*taille_tuile + taille_tuile//2
cy = oy + y*taille_tuile + taille_tuile//2
pygame.draw.circle(ecran, (240,200,60), (cx, cy), rayon)
def dessiner_cle(inventaire):
if inventaire["cle"] == 0:
cx = ox + case_cle[0]*taille_tuile + taille_tuile//2
cy = oy + case_cle[1]*taille_tuile + taille_tuile//2
pygame.draw.rect(ecran, (80,220,140), pygame.Rect(cx-6, cy-6, 12, 12), border_radius=3)
def reinitialiser():
px0, py0 = centre_case(*case_depart)
pac = {"x": px0, "y": py0, "vitesse": 220.0, "direction_aff": (1,0), "frame": 0, "temps_anim": 0.0}
inv = {"cle": 0}
pts = creer_pastilles()
sc = 0
ene = {"case": case_ennemi, "direction": random.choice(DIRECTIONS), "progression": 0.0, "vitesse_cases": 4.5}
return pac, inv, pts, sc, ene
pacman, inventaire, pastilles, score, ennemi = reinitialiser()
etat = "jeu"
message = ""
en_cours = True
while en_cours:
dt = horloge.tick(60) / 1000.0
for event in pygame.event.get():
if event.type == pygame.QUIT:
en_cours = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
en_cours = False
if event.key == pygame.K_r and etat != "jeu":
pacman, inventaire, pastilles, score, ennemi = reinitialiser()
etat = "jeu"
message = ""
porte_ouverte = (inventaire["cle"] > 0) and (len(pastilles) == 0)
if etat == "jeu":
touches = pygame.key.get_pressed()
dx, dy = 0, 0
if touches[pygame.K_LEFT]: dx -= 1
if touches[pygame.K_RIGHT]: dx += 1
if touches[pygame.K_UP]: dy -= 1
if touches[pygame.K_DOWN]: dy += 1
vx = dx * pacman["vitesse"] * dt
vy = dy * pacman["vitesse"] * dt
if dx != 0 or dy != 0:
pacman["direction_aff"] = (dx, dy)
nx = pacman["x"] + vx
if not collision_mur(rect_pacman(nx, pacman["y"])):
pacman["x"] = nx
ny = pacman["y"] + vy
if not collision_mur(rect_pacman(pacman["x"], ny)):
pacman["y"] = ny
case_p = case_depuis_position(pacman["x"], pacman["y"])
if case_p in pastilles:
pastilles.remove(case_p)
score += 1
if son_chomp: son_chomp.play()
if inventaire["cle"] == 0 and case_p == case_cle:
inventaire["cle"] = 1
if son_cle: son_cle.play()
porte_ouverte = (inventaire["cle"] > 0) and (len(pastilles) == 0)
if porte_ouverte and case_p == case_porte:
etat = "gagne"
message = "Victoire ! Appuie sur R pour rejouer"
if son_porte: son_porte.play()
if son_victoire: son_victoire.play()
# Animation
bouge = (dx != 0 or dy != 0)
if bouge:
pacman["temps_anim"] += dt
if pacman["temps_anim"] >= 1.0/12:
pacman["temps_anim"] = 0.0
pacman["frame"] = (pacman["frame"] + 1) % len(frames_base)
else:
pacman["frame"] = 0
pacman["temps_anim"] = 0.0
# Ennemi + collision
mettre_a_jour_ennemi(ennemi, dt)
ex, ey = position_ennemi_pixels(ennemi)
dxp = pacman["x"] - ex
dyp = pacman["y"] - ey
if (dxp*dxp + dyp*dyp) < (0.45*taille_tuile)**2:
etat = "perdu"
message = "Perdu ! Appuie sur R pour recommencer"
if son_mort: son_mort.play()
# Dessin
ecran.fill((15,15,20))
dessiner_labyrinthe(porte_ouverte)
dessiner_pastilles(pastilles)
dessiner_cle(inventaire)
ex, ey = position_ennemi_pixels(ennemi)
pygame.draw.circle(ecran, (220,60,60), (int(ex), int(ey)), max(5, taille_tuile//3))
fr = frames_orientees(pacman["direction_aff"])
sprite = fr[pacman["frame"]]
ecran.blit(sprite, sprite.get_rect(center=(int(pacman["x"]), int(pacman["y"]))))
hud = f"Score : {score} Clé : {inventaire['cle']} Pastilles : {len(pastilles)}"
ecran.blit(police.render(hud, True, (235,235,245)), (18,14))
if message:
ecran.blit(police.render(message, True, (245,245,245)), (18,40))
pygame.display.flip()
pygame.quit()
12) Jeu complet + musique (optionnelle)
code python 12
import pygame
import random
from pathlib import Path
# ============================================================
# 1) INITIALISATION DE PYGAME
# ============================================================
pygame.init()
try:
pygame.mixer.init() # audio (peut échouer sur certaines machines)
except Exception:
pass
# ============================================================
# 2) DESCRIPTION DU NIVEAU (LABYRINTHE)
# # : mur
# . : sol (où on peut marcher)
# P : position de départ du joueur
# K : clé
# E : ennemi
# D : porte de sortie
#
# CORRECTION DU BUG "LABYRINTHE FERMÉ"
# - Dans la version d'origine, le labyrinthe était séparé en 2 zones non connectées :
# Pac-Man démarrait dans une zone, mais la clé + la porte étaient dans l'autre,
# donc impossible de gagner.
# - Ici, on a créé un passage en remplaçant un seul '#' par '.' (ligne 4).
# ============================================================
LABYRINTHE = [
"####################",
"#P.......#.....K...#",
"#.######.#.#####...#",
"#.#....#.......#...#", # <-- ouverture (à la place du # en colonne 10)
"#.#.##.#.#####.#...#",
"#...##.#.....#.#...#",
"###.##.#####.#.###.#",
"#......#.....#..E..#",
"##################D#",
]
# On transforme chaque ligne (str) en liste de caractères modifiables
grille = [list(ligne) for ligne in LABYRINTHE]
H = len(grille) # hauteur en cases
W = len(grille[0]) # largeur en cases
# ============================================================
# 3) FENÊTRE / AFFICHAGE
# ============================================================
LARGEUR, HAUTEUR = 900, 520
ecran = pygame.display.set_mode((LARGEUR, HAUTEUR))
pygame.display.set_caption("12) Jeu complet Pac-Man (1 niveau)")
horloge = pygame.time.Clock()
police = pygame.font.SysFont(None, 28)
# Marges et taille des tuiles (cases) en pixels
marge = 30
taille_tuile = min((LARGEUR - 2 * marge) // W, (HAUTEUR - 2 * marge) // H)
taille_tuile = max(16, int(taille_tuile)) # sécurité : jamais trop petit
# Décalage pour centrer le labyrinthe dans la fenêtre
ox = (LARGEUR - W * taille_tuile) // 2
oy = (HAUTEUR - H * taille_tuile) // 2
def rect_tuile(x_case: int, y_case: int) -> pygame.Rect:
"""Rectangle en pixels correspondant à la case (x_case, y_case)."""
return pygame.Rect(
ox + x_case * taille_tuile,
oy + y_case * taille_tuile,
taille_tuile,
taille_tuile
)
def centre_case(x_case: int, y_case: int) -> tuple[float, float]:
"""Coordonnées (pixels) du centre de la case (x_case, y_case)."""
return (
ox + (x_case + 0.5) * taille_tuile,
oy + (y_case + 0.5) * taille_tuile
)
# ============================================================
# 4) PARSING DU LABYRINTHE : on repère murs, départ, porte, ennemi, clé
# ============================================================
murs: list[pygame.Rect] = []
case_depart = None
case_porte = None
case_ennemi = None
case_cle = None
for y in range(H):
for x in range(W):
c = grille[y][x]
if c == "#":
murs.append(rect_tuile(x, y)) # mur : on stocke son rectangle pour les collisions
elif c == "P":
case_depart = (x, y)
grille[y][x] = "." # on remet du sol à la place de 'P'
elif c == "D":
case_porte = (x, y)
grille[y][x] = "."
elif c == "E":
case_ennemi = (x, y)
grille[y][x] = "."
elif c == "K":
case_cle = (x, y)
grille[y][x] = "."
def est_mur_case(x: int, y: int) -> bool:
"""Vrai si la case (x, y) est un mur."""
return grille[y][x] == "#"
# ============================================================
# 5) PASTILLES (PIÈCES) À RAMASSER
#
# CORRECTION "TROP DE PIÈCES"
# - Avant : une pastille sur TOUTES les cases '.' (donc énormément).
# - Maintenant : on met une pastille sur ~1 case sur 2 en damier (moins long,
# mais on conserve l'idée d'exploration).
#
# IMPORTANT : on ne met des pastilles que sur les cases réellement accessibles
# depuis le départ, pour éviter une situation où il resterait des pastilles
# dans une zone isolée (ce qui bloquerait la victoire).
# ============================================================
DIRECTIONS = [(1, 0), (-1, 0), (0, 1), (0, -1)]
def cases_accessibles_depuis(depart: tuple[int, int]) -> set[tuple[int, int]]:
"""Retourne l'ensemble des cases atteignables depuis 'depart' (BFS)."""
a_visiter = [depart]
vues = {depart}
while a_visiter:
x, y = a_visiter.pop(0)
for dx, dy in DIRECTIONS:
nx, ny = x + dx, y + dy
if 0 <= nx < W and 0 <= ny < H and not est_mur_case(nx, ny):
if (nx, ny) not in vues:
vues.add((nx, ny))
a_visiter.append((nx, ny))
return vues
def creer_pastilles() -> set[tuple[int, int]]:
"""Crée l'ensemble des positions de pastilles à ramasser."""
accessibles = cases_accessibles_depuis(case_depart)
pastilles = set()
for y in range(H):
for x in range(W):
# Une pastille peut exister uniquement sur du sol ('.')
if grille[y][x] != ".":
continue
# On évite les cases spéciales
if (x, y) in (case_depart, case_porte, case_cle):
continue
# Sécurité : uniquement dans la zone atteignable
if (x, y) not in accessibles:
continue
# Réduction : "damier" => environ 50% des cases seulement
if (x + y) % 2 == 0:
pastilles.add((x, y))
return pastilles
# ============================================================
# 6) SONS + MUSIQUE
# ============================================================
dossier_assets = Path("assets")
def charger_son(nom: str, volume: float = 0.6):
"""Charge un son si possible, sinon renvoie None."""
try:
if pygame.mixer.get_init() and (dossier_assets / nom).exists():
s = pygame.mixer.Sound(str(dossier_assets / nom))
s.set_volume(volume)
return s
except Exception:
pass
return None
son_chomp = charger_son("chomp.wav", 0.6)
son_cle = charger_son("key.wav", 0.7)
son_porte = charger_son("door.wav", 0.7)
son_mort = charger_son("death.wav", 0.7)
son_victoire = charger_son("win.wav", 0.8)
musique = dossier_assets / "music.ogg"
if pygame.mixer.get_init() and musique.exists():
try:
pygame.mixer.music.load(str(musique))
pygame.mixer.music.set_volume(0.25)
pygame.mixer.music.play(-1) # boucle infinie
except Exception:
pass
# ============================================================
# 7) SPRITES PAC-MAN (images)
# ============================================================
fichiers = ["pacman_0.png", "pacman_1.png", "pacman_2.png"]
frames_base = []
for nom in fichiers:
img = pygame.image.load(str(dossier_assets / nom)).convert_alpha()
img = pygame.transform.smoothscale(
img,
(int(taille_tuile * 0.95), int(taille_tuile * 0.95))
)
frames_base.append(img)
def frames_orientees(direction: tuple[int, int]) -> list[pygame.Surface]:
"""Retourne la liste d'images tournée selon la direction affichée."""
dx, dy = direction
if (dx, dy) == (1, 0):
return frames_base
if (dx, dy) == (-1, 0):
return [pygame.transform.flip(f, True, False) for f in frames_base]
if (dx, dy) == (0, -1):
return [pygame.transform.rotate(f, 90) for f in frames_base]
if (dx, dy) == (0, 1):
return [pygame.transform.rotate(f, -90) for f in frames_base]
return frames_base
def rect_pacman(px: float, py: float) -> pygame.Rect:
"""Rectangle de collision du Pac-Man (plus petit que la case)."""
s = int(taille_tuile * 0.78)
return pygame.Rect(int(px - s / 2), int(py - s / 2), s, s)
def collision_mur(r: pygame.Rect) -> bool:
"""Vrai si le rectangle r touche au moins un mur."""
return any(r.colliderect(m) for m in murs)
def case_depuis_position(px: float, py: float) -> tuple[int, int]:
"""Transforme des coordonnées pixels en coordonnées de case (x, y)."""
return (int((px - ox) // taille_tuile), int((py - oy) // taille_tuile))
# ============================================================
# 8) ENNEMI (MOUVEMENT SUR LA GRILLE)
# ============================================================
OPPOSE = {(1, 0): (-1, 0), (-1, 0): (1, 0), (0, 1): (0, -1), (0, -1): (0, 1)}
def direction_possible(case: tuple[int, int], direction: tuple[int, int]) -> bool:
"""Vrai si on peut aller de 'case' dans 'direction' sans sortir ni toucher un mur."""
x, y = case
dx, dy = direction
nx, ny = x + dx, y + dy
if nx < 0 or nx >= W or ny < 0 or ny >= H:
return False
return not est_mur_case(nx, ny)
def mettre_a_jour_ennemi(ennemi: dict, dt: float) -> None:
"""
Met à jour la position de l'ennemi.
- ennemi["case"] : la case actuelle (grille)
- ennemi["direction"] : direction actuelle (dx, dy)
- ennemi["progression"] : avancement (0.0 -> 1.0) pour passer à la case suivante
- ennemi["vitesse_cases"] : nombre de cases par seconde
"""
distance = ennemi["vitesse_cases"] * dt # en "cases"
eps = 1e-6
while distance > 0:
# Si l'ennemi est pile au centre d'une case, on peut décider de tourner
if ennemi["progression"] <= eps:
if not direction_possible(ennemi["case"], ennemi["direction"]):
# Mur devant : on choisit une direction possible
choix = DIRECTIONS[:]
random.shuffle(choix)
# On évite de faire demi-tour si possible (mais on le garde en dernier recours)
opp = OPPOSE.get(ennemi["direction"])
if opp in choix:
choix.remove(opp)
choix.append(opp)
for d in choix:
if direction_possible(ennemi["case"], d):
ennemi["direction"] = d
break
# Avancer vers la case suivante
restant = 1.0 - ennemi["progression"] # ce qu'il reste à parcourir avant la prochaine case
pas = min(distance, restant) # on avance au max jusqu'à la prochaine case
ennemi["progression"] += pas
distance -= pas
# Si on a atteint (ou dépassé) la case suivante, on "valide" le déplacement
if ennemi["progression"] >= 1.0 - eps:
x, y = ennemi["case"]
dx, dy = ennemi["direction"]
ennemi["case"] = (x + dx, y + dy)
ennemi["progression"] = 0.0
def position_ennemi_pixels(ennemi: dict) -> tuple[float, float]:
"""Calcule la position en pixels de l'ennemi (interpolée entre 2 cases)."""
x, y = ennemi["case"]
dx, dy = ennemi["direction"]
p = ennemi["progression"]
return (
ox + (x + 0.5 + dx * p) * taille_tuile,
oy + (y + 0.5 + dy * p) * taille_tuile
)
# ============================================================
# 9) DESSIN (LABYRINTHE, PASTILLES, CLÉ)
# ============================================================
def dessiner_labyrinthe(porte_ouverte: bool) -> None:
"""Dessine les murs et le sol + la porte (ouverte/fermée)."""
for y in range(H):
for x in range(W):
r = rect_tuile(x, y)
pygame.draw.rect(
ecran,
(70, 70, 85) if grille[y][x] == "#" else (25, 25, 32),
r
)
# La porte est un rectangle plus petit dans sa case
r = rect_tuile(case_porte[0], case_porte[1]).inflate(-10, -10)
couleur = (80, 220, 140) if porte_ouverte else (220, 120, 60)
pygame.draw.rect(ecran, couleur, r, border_radius=10)
def dessiner_pastilles(pastilles: set[tuple[int, int]]) -> None:
"""Dessine toutes les pastilles encore présentes."""
rayon = max(2, taille_tuile // 8)
for (x, y) in pastilles:
cx = ox + x * taille_tuile + taille_tuile // 2
cy = oy + y * taille_tuile + taille_tuile // 2
pygame.draw.circle(ecran, (240, 200, 60), (cx, cy), rayon)
def dessiner_cle(inventaire: dict) -> None:
"""Dessine la clé si elle n'a pas encore été ramassée."""
if inventaire["cle"] == 0:
cx = ox + case_cle[0] * taille_tuile + taille_tuile // 2
cy = oy + case_cle[1] * taille_tuile + taille_tuile // 2
pygame.draw.rect(
ecran,
(80, 220, 140),
pygame.Rect(cx - 6, cy - 6, 12, 12),
border_radius=3
)
# ============================================================
# 10) RÉINITIALISATION (RECOMMENCER UNE PARTIE)
# ============================================================
def reinitialiser():
"""Remet le jeu dans son état de départ."""
px0, py0 = centre_case(*case_depart)
pac = {
"x": px0,
"y": py0,
"vitesse": 220.0, # pixels par seconde
"direction_aff": (1, 0), # direction affichée (sprite)
"frame": 0, # image actuelle (animation)
"temps_anim": 0.0, # accumulateur de temps pour l'animation
}
inv = {"cle": 0}
pts = creer_pastilles()
sc = 0
ene = {
"case": case_ennemi,
"direction": random.choice(DIRECTIONS),
"progression": 0.0,
"vitesse_cases": 4.5, # cases par seconde
}
return pac, inv, pts, sc, ene
pacman, inventaire, pastilles, score, ennemi = reinitialiser()
etat = "jeu" # "jeu" / "gagne" / "perdu"
message = ""
# ============================================================
# 11) BOUCLE PRINCIPALE DU JEU
# ============================================================
en_cours = True
while en_cours:
dt = horloge.tick(60) / 1000.0 # temps écoulé depuis la frame précédente (en secondes)
# -----------------------------
# 11a) GESTION DES ÉVÉNEMENTS
# -----------------------------
for event in pygame.event.get():
if event.type == pygame.QUIT:
en_cours = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
en_cours = False
if event.key == pygame.K_r and etat != "jeu":
pacman, inventaire, pastilles, score, ennemi = reinitialiser()
etat = "jeu"
message = ""
# La porte s'ouvre quand on a la clé ET qu'il ne reste plus de pastilles
porte_ouverte = (inventaire["cle"] > 0) and (len(pastilles) == 0)
# -----------------------------
# 11b) LOGIQUE DE JEU (si on joue)
# -----------------------------
if etat == "jeu":
touches = pygame.key.get_pressed()
dx, dy = 0, 0
if touches[pygame.K_LEFT]:
dx -= 1
if touches[pygame.K_RIGHT]:
dx += 1
if touches[pygame.K_UP]:
dy -= 1
if touches[pygame.K_DOWN]:
dy += 1
# Vitesse * dt => déplacement en pixels
vx = dx * pacman["vitesse"] * dt
vy = dy * pacman["vitesse"] * dt
# Direction affichée du sprite
if dx != 0 or dy != 0:
pacman["direction_aff"] = (dx, dy)
# Déplacement + collisions : on teste séparément X puis Y
nx = pacman["x"] + vx
if not collision_mur(rect_pacman(nx, pacman["y"])):
pacman["x"] = nx
ny = pacman["y"] + vy
if not collision_mur(rect_pacman(pacman["x"], ny)):
pacman["y"] = ny
# On récupère la case où se trouve le joueur
case_p = case_depuis_position(pacman["x"], pacman["y"])
# Ramassage d'une pastille
if case_p in pastilles:
pastilles.remove(case_p)
score += 1
if son_chomp:
son_chomp.play()
# Ramassage de la clé
if inventaire["cle"] == 0 and case_p == case_cle:
inventaire["cle"] = 1
if son_cle:
son_cle.play()
# Victoire si la porte est ouverte et qu'on marche dessus
porte_ouverte = (inventaire["cle"] > 0) and (len(pastilles) == 0)
if porte_ouverte and case_p == case_porte:
etat = "gagne"
message = "Victoire ! Appuie sur R pour rejouer"
if son_porte:
son_porte.play()
if son_victoire:
son_victoire.play()
# Animation du Pac-Man (change d'image quand il bouge)
bouge = (dx != 0 or dy != 0)
if bouge:
pacman["temps_anim"] += dt
if pacman["temps_anim"] >= 1.0 / 12: # 12 images par seconde
pacman["temps_anim"] = 0.0
pacman["frame"] = (pacman["frame"] + 1) % len(frames_base)
else:
pacman["frame"] = 0
pacman["temps_anim"] = 0.0
# Ennemi + collision avec le joueur
mettre_a_jour_ennemi(ennemi, dt)
ex, ey = position_ennemi_pixels(ennemi)
dxp = pacman["x"] - ex
dyp = pacman["y"] - ey
if (dxp * dxp + dyp * dyp) < (0.45 * taille_tuile) ** 2:
etat = "perdu"
message = "Perdu ! Appuie sur R pour recommencer"
if son_mort:
son_mort.play()
# -----------------------------
# 11c) DESSIN
# -----------------------------
ecran.fill((15, 15, 20))
dessiner_labyrinthe(porte_ouverte)
dessiner_pastilles(pastilles)
dessiner_cle(inventaire)
# Ennemi (cercle rouge)
ex, ey = position_ennemi_pixels(ennemi)
pygame.draw.circle(
ecran,
(220, 60, 60),
(int(ex), int(ey)),
max(5, taille_tuile // 3)
)
# Pac-Man (sprite orienté)
fr = frames_orientees(pacman["direction_aff"])
sprite = fr[pacman["frame"]]
ecran.blit(sprite, sprite.get_rect(center=(int(pacman["x"]), int(pacman["y"]))))
# HUD (infos en haut)
hud = f"Score : {score} Clé : {inventaire['cle']} Pastilles : {len(pastilles)}"
ecran.blit(police.render(hud, True, (235, 235, 245)), (18, 14))
if message:
ecran.blit(police.render(message, True, (245, 245, 245)), (18, 40))
pygame.display.flip()
pygame.quit()