import io
with redirect(stdout=io.StringIO()):
import tdoc.pygame
setup_canvas()
import pygame
import math
import pathlib
from random import randint, choice
width, height = 600, 600
window = pygame.display.set_mode((width, height))
pygame.init()
async def refresh(color):
await animation_frame()
pygame.display.flip()
window.fill(color)
class Actor(pygame.sprite.Sprite):
def __init__(self, image_path, cx, cy):
super().__init__()
self.original_image = pygame.image.load(image_path).convert_alpha()
self.image = self.original_image.copy()
self.rect = self.image.get_rect(center=(cx, cy))
self.collision_margin = 0.05 # 5% de marge
self.angle = 0
@property
def collision_rect(self):
"""Crée le rect de collision à la volée basé sur le rect actuel"""
return self.rect.inflate(
-self.rect.width * self.collision_margin,
-self.rect.height * self.collision_margin
)
def draw(self):
global window
window.blit(self.image, self.rect)
def get_x(self):
return self.rect.centerx
def get_y(self):
return self.rect.centery
def move(self, dx, dy):
self.rect.move_ip(dx, dy)
def set_position(self, x, y):
self.rect.centerx = x
self.rect.centery = y
def collide(self, other_actor):
other_rect = getattr(other_actor, 'collision_rect', other_actor.rect)
return self.collision_rect.colliderect(other_rect)
def flip(self, horizontal=True, vertical=False):
self.image = pygame.transform.flip(self.image, horizontal, vertical)
self.rect = self.image.get_rect(center=self.rect.center)
def scale(self, factor):
new_size = (
int(self.original_image.get_width() * factor),
int(self.original_image.get_height() * factor)
)
self.image = pygame.transform.scale(self.original_image, new_size)
self.rect = self.image.get_rect(center=self.rect.center)
def kill(self):
self.image = pygame.Surface((0, 0))
self.original_image = pygame.Surface((0, 0))
self.rect = self.image.get_rect()
def set_angle(self, angle):
self.angle = angle
self.image = pygame.transform.rotate(self.original_image, angle)
class Text(pygame.sprite.Sprite):
def __init__(self, text, cx, cy, *args):
super().__init__()
self.font = pygame.font.Font(None, 36)
self.image = self.font.render(text, *args)
self.rect = self.image.get_rect()
self.rect.centerx, self.rect.centery = cx, cy
def draw(self):
global window
window.blit(self.image, self.rect)
def kill(self):
self.image = pygame.Surface((0,0))
self.rect = self.image.get_rect()
class Timer(pygame.time.Clock):
def __init__(self, time):
super().__init__()
self.time = time * 1000
self.started = False
def start(self):
self.started = True
self.start_time = animation_time()
self.end_time = self.start_time + self.time
def is_finished(self):
return animation_time() >= self.end_time
def __str__(self):
if not self.started:
return "Not started"
elif self.is_finished():
return "0.00s"
else:
return f"{(self.end_time - animation_time()) / 1000:.2f}s"
def get_pressed_keys():
pressed_list = []
keys = pygame.key.get_pressed()
# 1. La boucle pour les touches "standards" (lettres, chiffres)
# On itère sur les scancodes standards
for scancode in range(len(keys)):
if keys[scancode]:
try:
name = pygame.key.name(scancode).upper()
# On filtre un peu pour éviter d'avoir des trucs bizarres ou vides
if name and name != "UNKNOWN":
pressed_list.append(name)
except ValueError:
pass
# 2. Vérification MANUELLE des touches spéciales (Flèches, Ctrl, etc.)
# C'est nécessaire car elles échappent souvent à la boucle simple ci-dessus
if keys[pygame.K_UP]:
pressed_list.append("UP")
if keys[pygame.K_DOWN]:
pressed_list.append("DOWN")
if keys[pygame.K_LEFT]:
pressed_list.append("LEFT")
if keys[pygame.K_RIGHT]:
pressed_list.append("RIGHT")
# Tu peux aussi ajouter d'autres touches spéciales souvent manquées par la boucle :
if keys[pygame.K_LSHIFT] or keys[pygame.K_RSHIFT]:
pressed_list.append("SHIFT")
if keys[pygame.K_SPACE]:
# Parfois "SPACE" ressort comme " " (vide) dans le name(), donc utile de le forcer
if " " in pressed_list: pressed_list.remove(" ") # Nettoyage optionnel
if "SPACE" not in pressed_list: pressed_list.append("SPACE")
# 3. Souris (Ton code original)
mouse_buttons = pygame.mouse.get_pressed()
if mouse_buttons[0]: pressed_list.append("MOUSE_1")
if mouse_buttons[1]: pressed_list.append("MOUSE_2")
if mouse_buttons[2]: pressed_list.append("MOUSE_3")
return pressed_list
try:
await main()
finally:
pygame.quit()
Projet Casse-Briques#
Partie 1 : fonctionnalités de base#
Créez et dessinez l'acteur principal raquette en bas au centre de l'écran avec l'image paddle.png. Rappel : l'écran fait 600x600 pixels.
La raquette est un peu petite. Agrandissez-la !
Juste après avoir créé la raquette (étape 1), utilisez la méthode
scale()pour la rendre 2 fois plus grande.
La raquette devrait maintenant être plus large et plus facile à utiliser.
Permettez à la raquette de se déplacer latéralement.
Dans la boucle de jeu, récupérez les touches pressées avec
get_pressed_keys().Si la touche A est pressée, déplacez la raquette vers la gauche avec
move().Si la touche D est pressée, déplacez la raquette vers la droite avec
move().Ajustez la vitesse de déplacement pour que la maniabilité soit agréable.
Créons maintenant la balle qui va rebondir !
Avant la boucle
while True, créez un acteurballeavec l'imageball.png. Placez-la juste au-dessus de la raquette.Créez deux variables
vitesse_balle_x = 3etvitesse_balle_y = -3pour contrôler la vitesse de la balle. Ces vitesses initiales signifient que la balle se dirigera au début du jeu en diagonal vers le haut/droite.Dans la boucle de jeu, déplacez la balle avec
balle.move(vitesse_balle_x, vitesse_balle_y).N'oubliez pas de dessiner la balle avec
draw().
Vous devriez voir la balle se déplacer en diagonale vers le haut à droite et disparaître hors de l'écran.
La balle doit rebondir sur les murs gauche, droit et haut de l'écran.
Dans la boucle de jeu, après avoir déplacé la balle, vérifiez sa position :
Si la position horizontale (
balle.get_x()) est inférieure à 0 OU supérieure à 600, inversez la vitesse horizontale :vitesse_balle_x = -vitesse_balle_xSi la position verticale (
balle.get_y()) est inférieure à 0, inversez la vitesse verticale.
La balle devrait maintenant rebondir sur les trois côtés de l'écran.
La balle doit également rebondir sur la raquette.
Dans la boucle de jeu, vérifiez si la balle entre en collision avec la raquette :
if balle.collide(raquette):Si c'est le cas, inversez la vitesse verticale de la balle.
Afin que le joueur ait également un contrôle sur le rebond de la balle, sa vitesse horizontale doit être modifiée en fonction de l'endroit où elle percute la raquette. Ajoutez pour cela la ligne suivante en cas de collision :
vitesse_balle_x = (balle.get_x() - raquette.get_x()) / 10.
Vous pouvez maintenant contrôler la raquette pour renvoyer la balle !
Créons maintenant les briques à détruire
Avant la boucle de jeu, créez une liste vide
briques = [].Puis, copiez/collez le code suivant. Vous ne devez pas forcément comprendre à 100% ce qu'il se passe, mais devrez légèrement le modifier après.
for ligne in range(3):
for colonne in range(5):
brique = Actor("brick.png", 120 + colonne * 80, 80 + ligne * 40)
briques.append(brique)
Dans la boucle de jeu, utilisez une boucle
forpour dessiner toutes les briques. Pour chaque brique dans la liste des briques : draw cette brique.
Vous devriez voir 3 rangées de 5 briques alignées en haut de l'écran.
La balle doit maintenant détruire les briques en les touchant.
Dans la boucle de jeu, parcourez la liste des briques avec une boucle
for.Pour chaque brique, vérifiez si la balle est en collision avec elle :
if balle.collide(brique):Si c'est le cas :
Supprimez la brique de la liste avec
remove().Inversez la vitesse verticale de la balle :
vitesse_balle_y = -vitesse_balle_y
Les briques devraient maintenant se détruire lorsque la balle les touche !
Gérons maintenant la défaite du joueur lorsque la balle tombe en bas de l'écran.
Avant la boucle de jeu, créez une variable
gameover = False.Dans la boucle de jeu, vérifiez si la position verticale de la balle (
balle.get_y()) est supérieure à 620.Si c'est le cas,
gameoverdevientTrue.Modifiez votre code de sorte que tous les mouvements (raquette, balle) et les collisions ne se produisent plus que si
gameoverestFalse.
Le jeu devrait se figer lorsque la balle tombe en bas de l'écran.
Ajoutons un message de fin de jeu.
Avant la boucle
while True, créez deux textes :txt_gameover = Text("PERDU !", 300, 300, True, (255, 0, 0), (255, 255, 255))pour la défaitetxt_victoire = Text("VICTOIRE !", 300, 300, True, (0, 255, 0), (255, 255, 255))pour la victoire
Si
gameoverestTrue, dessineztxt_gameover.Si la liste
briquesest vide (toutes les briques détruites), dessineztxt_victoireet arrêtez les mouvements.
Vous avez maintenant un jeu complet avec conditions de victoire et de défaite !
async def main():
while True:
await refresh((40, 40, 60))
Partie 2 : fonctionnalités intermédiaires#
Les fonctionnalités de la partie 2 valent au maximum 10 pts. Vous pouvez donc choisir des éléments de la liste suivante à implémenter et s'additionnant à 10 pts.
(2 pts) Ajoutez un score au jeu. Chaque brique détruite augmente le score de 10 points. Affichez le score en temps réel en haut de l'écran avec un
Text.(2 pts) Ajoutez des couleurs différentes aux briques selon leur rangée. Vous pouvez utiliser différentes images ou la méthode
scale()pour varier l'apparence.(3 pts) Faites accélérer progressivement la balle. À chaque fois qu'une brique est détruite, augmentez légèrement
vitesse_balle_xetvitesse_balle_y(en conservant leur signe) pour rendre le jeu plus difficile.(3 pts) Ajoutez des vies au joueur. Le joueur commence avec 3 vies. Lorsque la balle tombe, une vie est perdue et la balle est replacée au centre. Le gameover n'arrive que lorsque toutes les vies sont perdues.
(4 pts) Ajoutez des bonus qui tombent aléatoirement lorsqu'une brique est détruite (par exemple : raquette plus large, balle plus lente, vie supplémentaire). Le joueur doit attraper ces bonus avec sa raquette.
Partie 3 : fonctionnalités avancées#
(5 pts) Ajoutez plusieurs niveaux. Lorsque toutes les briques sont détruites, créez un nouveau niveau avec plus de rangées ou des configurations différentes.
(5 pts) Ajoutez un effet de "spin" : la direction horizontale de la balle après rebond sur la raquette dépend de l'endroit où elle touche (centre = tout droit, bords = angles prononcés).
(6 pts) Créez des briques spéciales qui nécessitent plusieurs coups pour être détruites (changement d'image à chaque coup).
(7 pts) Ajoutez plusieurs balles simultanément (bonus "multi-ball"). Gérez une liste de balles comme pour les briques.