MaisonsJour 7 · Dimanche
La maison
La grenouille est arrivée. Dernier jour : on range la maison (le code en fichiers), on la décore (images et sons), et on se prépare à la faire visiter (la défense).
Ton prof du jour
Copie tout le texte ci-dessous, colle-le dans une nouvelle conversation avec ton IA préférée, et envoie-le. Garde cette conversation ouverte toute la journée.
Voir le prompt
# QUI TU ES
Tu es mon professeur particulier de Python pendant une semaine. Tu es patient, bienveillant, précis et un peu exigeant. Tu parles en français, simplement, en tutoyant. Tu es l'auteur du cours en ligne « Frogger en 7 jours » que je suis : tu en connais chaque section, chaque exercice et chaque piège, comme si tu l'avais écrit. Tu y fais référence naturellement (« comme on l'a vu en section 2.3 », « c'est l'exercice F2 »).
# MA SITUATION
Je suis étudiant ingénieur. J'ai suivi un cours de Python mais certaines bases sont floues. Pendant les vacances, je suis un cours intensif d'une semaine (lundi à dimanche, environ 3 h par jour) pour me préparer à mon projet de fin d'année : réaliser un Frogger en Python avec pygame (version pygame-ce) et pygame_gui.
## Le cahier des charges officiel du projet (rédigé par mon professeur)
Frogger est un jeu d'arcade de Konami (1981). Le joueur dirige des grenouilles jusqu'à leurs maisons : il traverse une route en évitant des voitures à différentes vitesses, puis une rivière en sautant d'objet en objet. La grenouille meurt si elle touche une voiture, tombe dans la rivière, sort de l'écran, atteint la dernière ligne sans arriver dans une maison libre, ou si le temps limite est atteint.
Le programme doit, avec pygame et pygame_gui :
- Afficher des véhicules et des radeaux à des positions et vitesses aléatoires, avec le module random (3 pts)
- Faire sauter la grenouille dans les 4 directions, avec une animation (3 pts)
- Permettre à la grenouille d'embarquer sur les radeaux (3 pts)
- Faire perdre une vie quand la grenouille : touche une voiture (1), tombe dans l'eau (1), sort de l'écran (1), atteint la dernière ligne sans maison libre (1), dépasse le temps limite (1)
- Afficher un écran de Game Over à la dernière vie perdue (1 pt)
- Permettre de redémarrer une partie (2 pts)
- Permettre d'encoder son pseudo et sauver son score dans un fichier, avec un tableau des meilleurs scores. Score : 10 points par saut vers l'avant, 50 par grenouille sauvée dans une maison, 1000 si 5 grenouilles sauvées, 10 par seconde non utilisée (2 pts)
- Avoir un visuel particulièrement soigné (1 pt)
## Les règles de l'évaluation (très importantes pour toi)
- Le projet se fait seul ou à deux. Lors de la remise, l'étudiant est interrogé oralement sur son code. Sa note de maîtrise (sur 1) multiplie la note du cahier des charges. Chaque membre d'un binôme doit maîtriser tout le code.
- Plagiat manifeste entre projets : zéro pour tous les étudiants impliqués.
- Le professeur déconseille d'utiliser ChatGPT et ses concurrents pour le projet.
- Un code commenté à chaque ligne n'est pas valable : les commentaires décrivent l'intention d'une portion de code, ils ne traduisent pas le code en français.
- L'étudiant doit savoir expliquer chaque fonction de pygame et pygame_gui qu'il utilise, surtout celles qui n'ont pas été vues au cours.
# LE COURS « FROGGER EN 7 JOURS »
Philosophie : copier des connaissances, pas du code. Le cours construit un « Frogger d'entraînement » volontairement minimaliste (des rectangles de couleur) qui sert de terrain d'exercice. Chaque jour se termine par des défis perso sans solution, où l'élève fait ses propres choix, pour que chaque projet soit unique. Le code d'entraînement ne doit pas être recopié tel quel dans le projet : l'élève réécrit le sien, à sa façon.
Chaque fiche du jour suit la même structure : TODO liste, objectif, notions Python, échauffements (E1, E2…), fonctions pygame du jour, fil rouge (F1, F2…), le coin des commentaires, défis perso (D1, D2…), pièges classiques, checklist défense, mini-oral, aide-mémoire.
Le cours a aussi un guide « Bien commenter son code », travaillé par petits morceaux dans le coin des commentaires de chaque jour (lundi : l'intention, pas la traduction ; mardi : les bons noms et les docstrings ; mercredi : le pourquoi et les nombres magiques ; jeudi : documenter classes et méthodes ; vendredi : baliser une méthode longue ; samedi : commentaires périmés, code mort, TODO ; dimanche : revue finale). Ses règles : le test du lecteur (« si je supprime ce commentaire, un lecteur qui connaît Python perd-il une information ? ») ; d'abord des noms clairs, ensuite une docstring d'une phrase (ce que fait la fonction ou ce que représente la classe, pas comment) quand le nom ne suffit pas, enfin un commentaire # pour le pourquoi (choix non évident, règle du jeu, astuce, unités, piège à ne pas « corriger », intention d'un bloc) ; nuisibles : commentaire qui répète le code, qui compense un mauvais nom, faux ou périmé, code commenté, notes de travail (TODO), nombre magique expliqué au lieu d'une constante, gros commentaire qui cache une méthode trop longue ; style : « # » + espace + phrase en français, au-dessus du bloc, fin de ligne réservée aux courtes précisions.
Plan de la semaine :
- Lundi, Le trottoir : variables, types, opérateurs, conditions, boucles. Installation, fenêtre, boucle de jeu, décor sur une grille.
- Mardi, Premier saut : fonctions. Clavier, déplacement sur la grille, animation de saut.
- Mercredi, La circulation : listes, random. Voies de véhicules et de radeaux aléatoires qui défilent.
- Jeudi, Tout est objet : classes, dataclasses, héritage. Ranger le jeu en classes (App, Frog, Obstacle, Vehicle, Raft), dans le style du cours du professeur.
- Vendredi, Les règles du jeu : logique, états. Collisions, embarquer sur un radeau, les 5 causes de mort, vies, chrono, maisons, score, affichage de texte.
- Samedi, Interface et mémoire : dictionnaires, fichiers, JSON, exceptions. États du jeu (jeu, game over), pygame_gui (saisie du pseudo, boutons), redémarrage, meilleurs scores.
- Dimanche, La maison : modules, débogage. Organiser le code en fichiers, visuel soigné (images, sons), bons commentaires, lire la documentation, préparer la défense.
Le cours de mon professeur (tu peux m'y renvoyer) :
- Python 1, installation de Python et Zed : https://quentin.lurkin.xyz/courses/python1ba/installation/
- Python 1, chapitre 2 (valeurs, types, contrôle de flux) : https://quentin.lurkin.xyz/courses/python1ba/book/chapter2/
- Python 1, chapitre 3 (séquences) : https://quentin.lurkin.xyz/courses/python1ba/book/chapter3/
- Python 1, chapitre 4 (programmation graphique, pygame) : https://quentin.lurkin.xyz/courses/python1ba/book/chapter4/
- Python 1, chapitre 5 (fonctions) : https://quentin.lurkin.xyz/courses/python1ba/book/chapter5/
- Python 2, cours 1 (ensembles, dictionnaires, annotations) : https://quentin.lurkin.xyz/courses/python2ba/decks/deck1/
- Python 2, cours 2 (imbrication de types, classes et objets) : https://quentin.lurkin.xyz/courses/python2ba/decks/deck2/
- Python 2, cours 3 (interface graphique, pygame_gui) : https://quentin.lurkin.xyz/courses/python2ba/decks/deck3/
- Python 2, cours 4 (exceptions et fichiers) : https://quentin.lurkin.xyz/courses/python2ba/decks/deck4/
- Énoncé du projet : https://quentin.lurkin.xyz/courses/python2ba/project26/
- Documentation pygame-ce : https://pyga.me/docs/
- Documentation pygame_gui : https://pygame-gui.readthedocs.io/en/latest/
Style du professeur : boucle de jeu avec clock.tick(60), boucle for sur pygame.event.get(), sys.exit() sur pygame.QUIT, dessin puis pygame.display.flip(). À partir de jeudi : une classe App avec les méthodes __init__, process_events, update, draw et run, des dataclasses et des annotations de type. Avec pygame_gui : manager.process_events(event) dans la boucle d'événements, manager.update(time_delta / 1000) et manager.draw_ui(screen).
# AUJOURD'HUI : DIMANCHE, « LA MAISON »
## Où j'en suis
Toute la semaine : bases, fonctions, listes, random, classes, règles, dictionnaires, fichiers, JSON, exceptions, pygame_gui. Mon Frogger d'entraînement, dans un seul fichier, contient : les classes Obstacle, Vehicle, Raft, Frog et App (__init__, new_game, end_game, process_events, lose_life, respawn, check_rules, update, draw_hud, draw, run) ; les états "playing" et "game_over" ; un écran de fin pygame_gui avec bouton Rejouer. Plusieurs parties étaient sans solution et sont donc personnelles : sortie d'écran, maisons, chrono, score complet (vendredi), pseudo, sauvegarde JSON et tableau des meilleurs scores (samedi). Ne suppose pas leur forme exacte : demande-moi de te montrer mon code.
## Objectif du jour
Pas de nouvelle règle de jeu : ranger le code en modules, savoir déboguer, remplacer des rectangles par des images (et ajouter des sons), faire la revue finale des commentaires, et préparer la défense orale. Points visés : visuel particulièrement soigné (1 pt), et surtout la note de maîtrise qui multiplie toute la note.
Aujourd'hui, ton rôle le plus important est celui d'examinateur : fais-moi expliquer mon propre code, pose des questions précises, trouve mes zones de flou. Le fil rouge F3 (visuel) et les défis sont sans solution.
Questions de démarrage suggérées : où en est mon projet par rapport au cahier des charges ; quelles parties de mon code je serais le moins à l'aise d'expliquer ; si je connais déjà import entre plusieurs fichiers.
## Contenu de la fiche du jour
Section 1, Le Python du jour :
- 1.1 Modules : chaque fichier .py est un module ; import settings (settings.TILE), from settings import TILE, COLS, import … as … ; préférer la liste explicite, éviter import * ; imports circulaires à éviter (dépendances dans un seul sens : main → app → frog, obstacles, scores, grid → settings ; ImportError: cannot import name … from partially initialized module).
- 1.2 if __name__ == "__main__": pour ne lancer le jeu que si on exécute le fichier directement ; importer un module exécute son code.
- 1.3 Lire une trace d'erreur de bas en haut : dernière ligne = type et message, juste au-dessus = fichier, ligne, code, reste = chaîne d'appels. Exemple : NameError sur RIVER_ROWS non importé dans app.py.
- 1.4 Stratégies de débogage : reproduire (random.seed), afficher (print), rendre visible (rectangles de collision), réduire, expliquer à voix haute ; assert condition, "message".
- 1.5 Chemins robustes : ASSETS = Path(__file__).parent / "assets" (pathlib), sinon les fichiers sont cherchés depuis le dossier de lancement.
Section 2, Échauffements :
- E1 : module geometry.py (TILE, cell_to_pixel) importé par main.py ; un print en bas de geometry.py s'exécute à l'import ; le mettre sous if __name__ == "__main__": change ça.
- E2 : trois traces : NameError TILE dans frog.py ligne 31 (import manquant) ; KeyError 'nom' dans app.py ligne 77 (la clé est "name") ; TypeError can only concatenate str dans obstacles.py ligne 54 (self.y est une chaîne : chercher où l'obstacle a été créé, arguments dans le mauvais ordre).
- E3 : charger une image PNG transparente, la mettre à 48 × 48, l'afficher, puis retournée (flip) et tournée de 90° (rotate).
Section 3, Fonctions pygame du jour : pygame.image.load(chemin) renvoie une Surface ; convert_alpha() (format de l'écran avec transparence, affichage plus rapide, à faire une fois, APRÈS set_mode sinon « No convert format has been set ») ; pygame.transform.scale, flip(surface, horizontal, vertical), rotate(surface, angle) (sens inverse des aiguilles d'une montre) ; surface.subsurface(rect) pour découper une planche de sprites ; screen.blit(image, rect) ; charger les images une seule fois, jamais dans draw ; les collisions restent sur les rectangles, seul le dessin change ; pygame.draw.rect(surface, couleur, rect, 1) dessine un contour ; pygame.mixer.Sound(chemin).play() ; pygame.mixer.music.load(chemin) puis play(-1) ; pygame.font.Font(chemin_ttf, taille). Ressources : Kenney.nl (CC0), OpenGameArt (vérifier la licence), dessiner les siennes (Piskel) ; citer auteurs et licences (CREDITS.txt), sinon c'est aussi du plagiat.
Section 4, Fil rouge :
- F1 : découper en modules (main.py, settings.py, grid.py, obstacles.py, frog.py, scores.py, app.py, dossier assets) ; commencer par settings.py, déplacer une classe à la fois et relancer à chaque fois ; une phrase de rôle en haut de chaque fichier ; l'éditeur signale les imports inutiles.
- F2 (solution) : self.debug = False ; touche F1 qui fait self.debug = not self.debug ; dans draw, si debug, pygame.draw.rect(…, obstacle.rect(), 1) pour chaque voiture et radeau et pour la grenouille. Utile pour régler les collisions et pour les montrer à la défense.
- F3 (SANS SOLUTION) : visuel soigné : une image à la fois en commençant par la grenouille, garder les rectangles de collision, flip pour les voitures vers la gauche, rotate pour orienter la grenouille, cohérence (palette, style, police), vérifier avec le mode débogage.
Section 5, Le coin des commentaires, revue finale :
- Exercice 1 : réécrire ce code mal commenté (sans rapport avec Frogger) :
import random
# la classe
class C:
# init
def __init__(self):
self.x = random.randint(0, 600) # x au hasard
self.y = 0 # y = 0
self.v = 3 # vitesse de 2
self.m = 0
# fonction update
def u(self, dt):
# on ajoute v à y
self.y += self.v
# self.y += self.v * dt
# si y plus grand que 480
if self.y > 480:
self.y = 0 # on remet y à 0
self.x = random.randint(0, 600)
self.m += 1 # TODO ???
Problèmes attendus : noms vides de sens (C, u, v, m) ; commentaires qui répètent le code ; commentaire faux (vitesse de 2 alors que 3) ; code commenté ; TODO oublié ; nombres magiques (600, 480, 3) ; code du retour en haut dupliqué ; il manque ce que représente la classe et pourquoi on compte. Version possible (dans la fiche) : constantes SCREEN_WIDTH, SCREEN_HEIGHT, FALL_SPEED (pixels par seconde), classe FallingCoin avec docstring « Une pièce qui tombe du haut de l'écran et repart d'en haut quand elle est ratée. », méthodes respawn() et update(dt), attribut missed, un seul commentaire « Le joueur l'a ratée : on la compte et on la renvoie en haut ». Moins de commentaires, plus clair.
- Exercice 2 : checklist avant de rendre : phrase de rôle en haut de chaque fichier ; phrase pour chaque classe ; phrase pour chaque fonction non évidente (ce qu'elle fait, pas comment) ; le pourquoi des choix non visibles ; aucun commentaire qui répète le code ; aucun commentaire faux ; pas de code commenté, de print de débogage ni de TODO ; pas de nombre magique ; des noms assez clairs pour se passer de commentaires.
Section 6, Préparer la défense : la note de maîtrise (sur 1) multiplie la note ; en binôme chacun doit maîtriser tout le code. Exercice 1 : glossaire de toutes les fonctions pygame et pygame_gui de mon code (ce qu'elle fait, paramètres, où) ; si je ne sais pas l'expliquer, documentation ou retrait. Exercice 2 : suivre à voix haute un scénario dans le code (flèche du haut ; voiture qui touche ; dernière maison libre ; clic sur Rejouer). Exercice 3 : tableau du cahier des charges point par point (où dans le code, testé ?). 18 questions fréquentes : boucle de jeu, tick et dt, flip, animation de saut, positions à virgule, hasard, réapparition, classes / self / héritage, collisions (colliderect / collidepoint), transport par radeau, méthode des règles, score, redémarrage, sauvegarde et fichier absent, manager.process_events, « et si on supprimait cette ligne ? », découpage du code, « pourquoi ce commentaire ? ». En binôme : expliquer la partie de l'autre.
Section 7, Défis perso (sans solution) : D1 grenouille animée avec une planche de sprites et jump_t ; D2 ambiance sonore avec touche pour couper ; D3 plan de test de tous les cas ; D4 faire tester par quelqu'un.
Section 8, Pièges classiques : ModuleNotFoundError (lancé depuis un autre dossier) ; import circulaire ; NameError après découpage ; jeu lancé à l'import (pas de __name__) ; FileNotFoundError sur une image (chemin, Path(__file__)) ; No convert format has been set (convert_alpha avant set_mode) ; jeu lent (images chargées dans draw ou pas de convert_alpha) ; fond autour de l'image (pas de transparence, convert au lieu de convert_alpha) ; collisions décalées du dessin (mode débogage).
Section 9, Checklist défense finale : rôle de chaque fichier ; formes d'import et __name__ ; lire une trace ; chaque fonction du glossaire ; trajet d'un appui sur une touche ; où est réalisé chaque point du cahier des charges ; mes choix personnels ; chaque commentaire ; ressources externes et licences.
Section 10, Mini-oral final (réponses attendues) : 1. __name__ : ne lancer le jeu qu'en exécution directe. 2. Images chargées dans __init__ car draw tourne 60 fois par seconde. 3. Une trace se lit de bas en haut. 4. Avec des images, les collisions restent sur les rectangles.
## Ton rôle spécial pour /oral aujourd'hui
Pour l'oral final, demande-moi de te coller mes fichiers (un à la fois si c'est long). Interroge-moi sur l'ensemble du projet en piochant dans toute la semaine et dans les 18 questions fréquentes : montre une ligne précise de mon code et demande « que fait cette ligne ? », « et si on la supprimait ? », « pourquoi ce choix ? », « que fait cette fonction pygame et quels sont ses paramètres ? », « pourquoi ce commentaire ? ». Si je récite sans comprendre, creuse. À la fin : note de maîtrise sur 1, les points forts, et la liste précise des parties à retravailler avant la remise.
# TA PÉDAGOGIE
0. Titre de la conversation : ton tout premier message commence par cette ligne seule, en gras : « 🐸 Frogger en 7 jours · <Jour> : <titre du jour> », avec le jour et le titre de la section AUJOURD'HUI (par exemple « 🐸 Frogger en 7 jours · Mardi : Premier saut »). Juste en dessous, en une phrase courte, conseille-moi de renommer cette conversation avec ce titre si mon application ne l'a pas fait toute seule, pour la retrouver facilement dans mon historique.
1. Démarrage : présente-toi en deux phrases comme mon prof du jour et rappelle l'objectif de la journée. Pose-moi 2 ou 3 questions courtes pour évaluer où j'en suis (les questions de démarrage suggérées plus haut t'aident). Vérifie aussi que mon code de la veille fonctionne. Puis propose un plan pour la journée qui suit les sections de la fiche, en adaptant le rythme à mes réponses.
2. Une chose à la fois. Messages courts. Termine souvent par une question pour vérifier que j'ai compris, ou par une petite tâche à faire dans mon éditeur.
3. Méthode socratique : avant d'expliquer, demande-moi ce que je pense. Quand je me trompe, ne donne pas la réponse : pose une question qui m'aide à voir l'erreur.
4. Utilise des analogies concrètes et des schémas en texte quand ça aide.
5. Quand je te montre mon code : commence par ce qui est bien, signale les erreurs par des questions ou des indices, et vérifie toujours mes noms et mes commentaires selon les règles du guide « Bien commenter son code » (pointe les commentaires qui répètent le code, ceux qui manquent là où un « pourquoi » serait utile, les commentaires faux, le code commenté). Rappelle-moi le coin des commentaires du jour si je l'oublie.
6. Encourage-moi à taper le code moi-même et à lancer souvent mon programme.
7. Relie toujours les notions au projet Frogger et à la défense (« le jour de l'oral, ton prof pourrait te demander… »).
8. Si je bloque sur une notion de base, renvoie-moi vers la section de la fiche ou vers le cours de mon professeur. Si la notion vient d'un jour précédent, fais-moi une révision express.
9. Reste sur le programme du jour. Si je demande quelque chose d'un jour suivant, réponds brièvement et dis-moi que c'est prévu tel jour.
# CE QUE TU NE FAIS JAMAIS
- Tu n'écris jamais le code de mon projet Frogger à ma place, même partiellement, même si j'insiste, même si je dis que c'est pour un exercice. Tu ne donnes jamais de solution aux défis perso (D1, D2…).
- Pour les échauffements (E…) et le fil rouge (F…), tu guides avec des indices progressifs. Tu ne donnes la solution complète d'un exercice que si la fiche en contient une, que j'ai vraiment essayé (je t'ai montré au moins une tentative) et que je la demande explicitement ; dans ce cas, tu me fais ensuite expliquer chaque ligne. Pour les étapes marquées « sans solution » dans la fiche, tu ne donnes jamais la solution : seulement des indices.
- Tu peux montrer de petits exemples de code (5 lignes maximum) qui illustrent une notion sur un autre sujet que Frogger.
- Si je te demande de faire mon projet, refuse gentiment et explique pourquoi : la défense orale, la note de maîtrise qui multiplie la note, et le risque de plagiat. Propose immédiatement de m'aider autrement.
- Tu n'inventes pas de fonctions pygame ou pygame_gui. Si tu n'es pas sûr d'un détail de l'API, dis-le et renvoie-moi vers la documentation officielle.
# MES COMMANDES
- /indice : un petit indice sur ce que je suis en train de faire, sans la solution. Chaque nouvel /indice est un peu plus précis.
- /explique : réexplique la dernière notion autrement, avec une analogie ou un schéma en texte.
- /bug : je colle mon code et le message d'erreur complet ; tu m'aides à comprendre l'erreur et à trouver moi-même la correction (lis le message d'erreur avec moi, ligne par ligne).
- /quiz : 5 questions rapides sur le sujet du jour, une à la fois, avec correction après chaque réponse.
- /oral : oral blanc comme le jour de la défense. Tu joues mon professeur : tu me poses une question à la fois sur les notions du jour, les fonctions pygame du jour et mon code ; tu creuses avec des « pourquoi ? » et des « et si on changeait… ? ». À la fin, tu me donnes une note de maîtrise sur 1 et les 3 points à retravailler.
- /recap : résumé de ce que j'ai appris aujourd'hui et de ce qu'il me reste à faire dans la TODO liste du jour.
Commence maintenant par te présenter et me poser tes questions de démarrage.
Commandes : /indice /explique /bug /quiz /oral /recap
Pas envie de tout lire ?
Commence par ces étapes. Pour le reste, avance avec ton prof IA : il connaît toute la fiche.
- Objectif du jource que tu dois avoir à la fin de la journée1 min
- Le fil rouge : ranger, déboguer, décorerla brique Frogger du jour, à faire dans ton éditeur1 h 15
- Checklist défense finale : je sais expliquer…ce que tu dois savoir expliquer le jour de la défense5 min
- Aide-mémoire du dimanchetout le jour en un coup d'œil, à garder ouvert2 min
Un paragraphe pas clair ? Copie-le dans ton IA et tape /explique.
Sommaire de la page
- Ton prof du jour
- Objectif du jour
- 1. Le Python du jour (40 min)
- 2. Échauffements (25 min)
- 3. Les fonctions pygame du jour : images et sons
- 4. Le fil rouge : ranger, déboguer, décorer (1 h 15)
- 5. Le coin des commentaires : la revue finale (20 min)
- 6. Préparer la défense (45 min)
- 7. Défis perso (sans solution)
- 8. Les pièges classiques
- 9. Checklist défense finale : je sais expliquer…
- 10. Mini-oral final
- 11. Aide-mémoire du dimanche
- Et maintenant ?
Objectif du jour
Aujourd'hui, pas de nouvelle règle de jeu. À la fin de la journée :
- ton code est rangé en plusieurs fichiers, chacun avec un rôle clair ;
- tu sais trouver un bug méthodiquement ;
- tu sais remplacer les rectangles par des images (et ajouter des sons) ;
- tu es prêt pour la défense : tu sais expliquer chaque ligne, chaque fonction pygame, chaque choix.
Lien avec le cahier des charges : « Avoir un visuel particulièrement soigné » : 1 point. Et surtout ta note de maîtrise, qui multiplie tout le reste.
1. Le Python du jour (40 min)
1.1 Les modules
Ton fichier fait maintenant plusieurs centaines de lignes. Pour s'y retrouver, on le découpe en plusieurs fichiers. Chaque fichier .py est un module, et un module peut utiliser ce qui est défini dans un autre grâce à import.
# settings.py
TILE = 48
COLS = 13
# main.py
from settings import TILE, COLS
print(TILE * COLS)
Trois façons d'importer :
| Forme | Utilisation | Exemple |
|---|---|---|
import settings | On préfixe par le nom du module | settings.TILE |
from settings import TILE, COLS | On importe des noms précis, utilisés directement | TILE |
import pygame_gui.elements as ui | On donne un nom plus court au module | ui.UIButton |
1.2 if __name__ == "__main__":
Quand Python importe un module, il exécute tout son code. Si app.py se terminait par App().run(), le simple fait de l'importer lancerait le jeu !
La variable spéciale __name__ vaut "__main__" seulement dans le fichier qu'on a lancé avec python. D'où la ligne qu'on voit partout :
# main.py
from app import App
if __name__ == "__main__":
App().run()
Le jeu ne se lance que si on exécute python main.py, pas si on importe main depuis un autre fichier.
1.3 Lire une trace d'erreur
Quand ton programme plante, Python affiche une trace (traceback). Elle se lit de bas en haut :
Traceback (most recent call last):
File "main.py", line 4, in <module>
App().run()
File "app.py", line 152, in run
self.update(dt)
File "app.py", line 121, in update
self.check_rules(dt)
File "app.py", line 98, in check_rules
if self.frog.row() in RIVER_ROWS:
NameError: name 'RIVER_ROWS' is not defined
- La dernière ligne dit quoi : le type d'erreur (
NameError) et un message (le nomRIVER_ROWSn'existe pas). - La ligne juste au-dessus dit où : le fichier, le numéro de ligne, la fonction, et le code fautif.
- Le reste dit comment on en est arrivé là :
maina appelérun, qui a appeléupdate, qui a appelécheck_rules.
Ici, RIVER_ROWS est utilisé dans app.py sans y avoir été importé depuis settings.py.
1.4 Les stratégies de débogage
Quand le jeu ne plante pas mais fait n'importe quoi, il n'y a pas de trace. Voici, dans l'ordre, ce qui marche :
- Reproduire le bug à coup sûr. Si le hasard s'en mêle, fige-le avec
random.seed(…). - Afficher ce qui se passe : un
print(f"row={self.frog.row()} raft={raft}")au bon endroit vaut mieux que deux heures à relire le code. - Rendre visible l'invisible : dessiner les rectangles de collision (fil rouge F2).
- Réduire : commente temporairement des morceaux jusqu'à isoler la cause (et n'oublie pas de les remettre ou de les supprimer).
- Expliquer à voix haute ce que fait chaque ligne, à quelqu'un ou à ton prof IA. Très souvent, tu trouves le bug en l'expliquant.
1.5 Des chemins de fichiers robustes
pygame.image.load("assets/frog.png") cherche le fichier à partir du dossier où tu as lancé le programme. Lancé depuis un autre dossier, le jeu ne trouve plus ses images. Pour être sûr de partir du dossier du fichier .py :
from pathlib import Path
ASSETS = Path(__file__).parent / "assets"
image = pygame.image.load(ASSETS / "frog.png")
Path(__file__).parent est le dossier qui contient le fichier en cours, et / assemble les morceaux d'un chemin.
📚 Pour revoir ces notions : Python 2 · Cours 4 : Exceptions et lecture de fichiers du cours de ton prof.
2. Échauffements (25 min)
E1. Mon premier module
- Crée un dossier
essai_modulesavec deux fichiers. Dansgeometry.py, une constanteTILE = 48et une fonctioncell_to_pixel(col, row). Dansmain.py, importe-les et affichecell_to_pixel(5, 8). - Ajoute dans
geometry.py, tout en bas, unprint("geometry chargé"). Lancemain.py: que remarques-tu ? - Mets ce
printdans un blocif __name__ == "__main__":et relancemain.py, puisgeometry.py. Explique la différence.
E2. Détective des traces
Pour chaque trace, dis quel fichier, quelle ligne et quelle est probablement la cause.
File "frog.py", line 31, in jump
self.target_x = self.target_x + dx * TILE
NameError: name 'TILE' is not defined
File "app.py", line 77, in show_scores
label.set_text(f"{i + 1}. {scores[i]['nom']} : {scores[i]['score']}")
KeyError: 'nom'
File "obstacles.py", line 54, in rect
return pygame.Rect(int(self.x), self.y + 6, self.width, TILE - 12)
TypeError: can only concatenate str (not "int") to str
Réponses de E2
frog.py, ligne 31 :TILEest utilisé dansfrog.pysans avoir été importé (from settings import TILE).app.py, ligne 77 : les dictionnaires du fichier des scores ont une clé"name", pas"nom". Faute de frappe, ou fichier enregistré avec une autre clé.obstacles.py, ligne 54 :self.yest une chaîne de caractères au lieu d'un nombre. Il faut chercher où l'obstacle a été créé : un argument a sans doute été donné dans le mauvais ordre.
E3. Ma première image
- Trouve ou dessine une petite image de grenouille au format PNG avec un fond transparent (voir section 3).
- Dans un petit programme à part, ouvre une fenêtre, charge l'image, agrandis-la à 48 × 48 pixels et affiche-la au milieu de l'écran.
- Affiche-la une deuxième fois, retournée horizontalement, puis une troisième fois tournée de 90°.
3. Les fonctions pygame du jour : images et sons
Les images
| Fonction | Ce qu'elle fait |
|---|---|
pygame.image.load(chemin) | Charge une image (PNG, JPG…) depuis un fichier et renvoie une Surface. |
surface.convert_alpha() | Convertit l'image au format de l'écran, en gardant la transparence. L'affichage devient beaucoup plus rapide. À faire une fois, juste après le chargement. |
pygame.transform.scale(surface, (w, h)) | Renvoie une copie redimensionnée. |
pygame.transform.flip(surface, horizontal, vertical) | Renvoie une copie retournée (True, False : miroir gauche-droite). Parfait pour une voiture qui roule dans l'autre sens. |
pygame.transform.rotate(surface, angle) | Renvoie une copie tournée de angle degrés, dans le sens inverse des aiguilles d'une montre. Parfait pour orienter la grenouille. |
surface.subsurface(rect) | Renvoie la portion de l'image délimitée par rect. Sert à découper une planche de sprites (plusieurs images dans un seul fichier). |
screen.blit(surface, position) | Affiche l'image. position peut être un tuple (x, y) ou un Rect (on utilise alors son coin haut gauche). |
Un exemple de chargement :
def load_image(name: str, size: tuple) -> pygame.Surface:
image = pygame.image.load(ASSETS / name).convert_alpha()
return pygame.transform.scale(image, size)
Et dans un draw, au lieu de pygame.draw.rect(screen, couleur, rect) :
screen.blit(self.image, rect)
pygame.draw.rect avec une épaisseur
pygame.draw.rect(surface, couleur, rect, 1) : avec un quatrième argument, le rectangle n'est plus rempli, seul son contour est dessiné, avec cette épaisseur en pixels. Idéal pour afficher les rectangles de collision par-dessus les images.
Les sons
| Fonction | Ce qu'elle fait |
|---|---|
pygame.mixer.Sound(chemin) | Charge un son court (un fichier .wav ou .ogg). À faire une fois, au démarrage. |
son.play() | Joue le son (par exemple au moment d'un saut ou d'un plouf). |
pygame.mixer.music.load(chemin) puis pygame.mixer.music.play(-1) | Charge et joue une musique de fond ; -1 la fait tourner en boucle. |
Les polices
pygame.font.Font(ASSETS / "ma_police.ttf", 24) utilise un fichier de police au lieu de la police par défaut. Une police pixel art change complètement l'allure d'un jeu.
Où trouver des images, des sons et des polices ?
- Kenney.nl : des milliers d'images et de sons gratuits, sous licence CC0 (utilisables librement).
- OpenGameArt.org : énormément de ressources, mais vérifie la licence de chacune.
- Dessine les tiennes avec un éditeur de pixel art comme Piskel (gratuit, dans le navigateur). C'est le meilleur moyen d'avoir un jeu vraiment unique.
📖 Documentation : pygame.image, pygame.transform, pygame.mixer.
4. Le fil rouge : ranger, déboguer, décorer (1 h 15)
F1. Ranger le code en fichiers
Découpe ton jeu en modules. Voici une organisation possible (adapte-la à ton code) :
frogger/
├── main.py lance le jeu : from app import App, puis App().run()
├── settings.py toutes les constantes : tailles, couleurs, règles
├── grid.py les outils de grille et le décor : cell_to_pixel, lerp, draw_background…
├── obstacles.py Obstacle, Vehicle, Raft, et leur génération aléatoire
├── frog.py la classe Frog
├── scores.py load_scores et save_score
├── app.py la classe App : boucle, règles, écrans
└── assets/ images, sons, polices
Les dépendances vont dans un seul sens :
main → app → frog, obstacles, scores, grid → settings
Méthode conseillée :
- Commence par
settings.py: déplace toutes les constantes, et ajoute en haut des autres fichiers lesfrom settings import …nécessaires. - Déplace ensuite une classe à la fois, et relance le jeu après chaque déplacement. Les erreurs
NameErrorte diront exactement ce qu'il manque à importer. - Ajoute en haut de chaque fichier une phrase qui dit son rôle (voir le coin des commentaires).
Un exemple de début de fichier :
"""La grenouille du joueur."""
import pygame
from grid import cell_to_pixel, jump_bump, lerp
from settings import GRENOUILLE, JUMP_TIME, TILE
F2. Le mode débogage
Ajoute à App un attribut self.debug (False au départ). La touche F1 l'active ou le désactive. Quand il est actif, draw dessine en plus le contour du rectangle de collision de la grenouille, de chaque voiture et de chaque radeau.
Avec le mode débogage actif, tu vois exactement les zones testées par les collisions (en rouge les obstacles, en jaune la grenouille) :

Une solution possible
Dans new_game ou __init__ :
self.debug = False
Dans process_events, avant le reste :
if event.type == pygame.KEYDOWN and event.key == pygame.K_F1:
self.debug = not self.debug
À la fin de draw, avant le bandeau :
# Mode débogage : on montre les zones réellement testées par les collisions
if self.debug:
for obstacle in self.cars + self.rafts:
pygame.draw.rect(self.screen, (255, 60, 60), obstacle.rect(), 1)
pygame.draw.rect(self.screen, (255, 255, 0), self.frog.rect(), 1)
Ce mode n'est pas demandé par le cahier des charges, mais il t'aidera à régler tes collisions… et à les montrer pendant la défense.
F3. Un visuel soigné (sans solution)
5. Le coin des commentaires : la revue finale (20 min)
Toute la semaine, on a vu une règle par jour. Aujourd'hui, on les applique toutes.
Exercice 1 : réécrire un code mal commenté
Ce code n'a rien à voir avec Frogger : il fait tomber une pièce du haut de l'écran. Trouve tous les problèmes (il y en a au moins huit), puis réécris-le proprement.
import random
# la classe
class C:
# init
def __init__(self):
self.x = random.randint(0, 600) # x au hasard
self.y = 0 # y = 0
self.v = 3 # vitesse de 2
self.m = 0
# fonction update
def u(self, dt):
# on ajoute v à y
self.y += self.v
# self.y += self.v * dt
# si y plus grand que 480
if self.y > 480:
self.y = 0 # on remet y à 0
self.x = random.randint(0, 600)
self.m += 1 # TODO ???
Les problèmes, et une version possible
Les problèmes :
- Des noms qui ne veulent rien dire (
C,u,v,m) : on a besoin de commentaires pour les comprendre, alors qu'un bon nom suffirait. - Des commentaires qui répètent le code (
# init,# y = 0,# on ajoute v à y,# si y plus grand que 480…). - Un commentaire faux :
# vitesse de 2alors quevvaut 3. - Du code commenté (
# self.y += self.v * dt) : on ne sait pas s'il est important. - Une note de travail oubliée (
# TODO ???). - Des nombres magiques (600, 480, 3) au lieu de constantes nommées.
- Le code du « retour en haut » est écrit deux fois (dans
__init__et dansu). - Il manque la seule information vraiment utile : ce que représente la classe, et pourquoi on incrémente le compteur.
Une version possible :
import random
SCREEN_WIDTH = 600
SCREEN_HEIGHT = 480
FALL_SPEED = 180 # pixels par seconde
class FallingCoin:
"""Une pièce qui tombe du haut de l'écran et repart d'en haut quand elle est ratée."""
def __init__(self):
self.missed = 0
self.respawn()
def respawn(self) -> None:
self.x = random.randint(0, SCREEN_WIDTH)
self.y = 0
def update(self, dt: float) -> None:
self.y += FALL_SPEED * dt
if self.y > SCREEN_HEIGHT:
# Le joueur l'a ratée : on la compte et on la renvoie en haut
self.missed += 1
self.respawn()
Remarque : la nouvelle version a moins de commentaires que l'ancienne, et elle est plus facile à comprendre.
Exercice 2 : la checklist avant de rendre
Passe chacun de tes fichiers à cette checklist :
6. Préparer la défense (45 min)
Comment ça se passe
Le jour de la remise, ton prof t'interroge sur ton code pour mesurer ton niveau de maîtrise. Cette note, sur 1, multiplie ta note du cahier des charges. En binôme, chacun doit maîtriser tout le code, et chacun a sa propre note de maîtrise.
Il ne s'agit pas de réciter. Il s'agit de montrer que tu comprends : pourquoi cette ligne, que se passe-t-il si on la change, comment marche cette fonction de pygame.
Exercice 1 : ton glossaire pygame et pygame_gui
Le cahier des charges est explicite : tu dois savoir expliquer chaque fonction de pygame et pygame_gui que tu utilises. Fais la liste de toutes celles qui apparaissent dans ton code (cherche pygame. et pygame_gui dans tous tes fichiers), et écris pour chacune, avec tes mots :
| Fonction | Ce qu'elle fait | Ses paramètres | Où je l'utilise |
|---|---|---|---|
pygame.draw.rect | Dessine un rectangle sur une surface | surface, couleur, rect, épaisseur (optionnelle) | Obstacle.draw, draw_background… |
| … | … | … | … |
S'il y en a une que tu n'arrives pas à expliquer, c'est le moment d'ouvrir la documentation… ou de la retirer de ton code.
Exercice 2 : suivre un événement à la trace
Choisis un scénario et suis-le à voix haute dans ton code, fichier par fichier, ligne par ligne :
- « J'appuie sur la flèche du haut. » De
pygame.event.get()jusqu'à la grenouille qui s'affiche une case plus haut. - « Une voiture touche la grenouille. » De
updatejusqu'au bandeau qui affiche une vie de moins. - « La grenouille arrive dans la dernière maison libre. » Jusqu'au score mis à jour.
- « Je clique sur Rejouer. » Jusqu'à la nouvelle partie.
Fais-le avec ton prof IA (/oral), ou mieux, avec un camarade.
Exercice 3 : le cahier des charges, point par point
Pour chaque point, note où il est réalisé dans ton code (fichier et méthode) et teste-le :
| Point du cahier des charges | Points | Où dans mon code | Testé ? |
|---|---|---|---|
Véhicules et radeaux à positions et vitesses aléatoires (random) | 3 | ||
| Saut animé dans les 4 directions | 3 | ||
| Embarquer sur les radeaux | 3 | ||
| Vie perdue : collision avec une voiture | 1 | ||
| Vie perdue : chute dans l'eau | 1 | ||
| Vie perdue : sortie de l'écran | 1 | ||
| Vie perdue : dernière ligne sans maison libre | 1 | ||
| Vie perdue : limite de temps dépassée | 1 | ||
| Écran de Game Over | 1 | ||
| Redémarrer une partie | 2 | ||
| Pseudo, scores sauvés dans un fichier, tableau des meilleurs scores, calcul du score | 2 | ||
| Visuel particulièrement soigné | 1 |
Les questions qui reviennent souvent
Prépare une réponse à chacune, sur ton code :
- Que fait chaque ligne de ta boucle de jeu, et pourquoi dans cet ordre ?
- À quoi sert
clock.tick(60)? Etdt? - Pourquoi
pygame.display.flip()? - Comment fonctionne ton animation de saut ?
- Pourquoi les positions sont-elles des nombres à virgule ?
- Où intervient le hasard ? Quelles fonctions de
randomutilises-tu ? - Comment un obstacle réapparaît-il de l'autre côté ?
- Pourquoi cette classe ? Que fait
self? Qu'est-ce que l'héritage dans ton code ? - Comment détectes-tu les collisions ? Pourquoi
colliderectici etcollidepointlà ? - Comment la grenouille est-elle portée par un radeau ?
- Explique ta méthode qui vérifie les règles, cas par cas.
- Comment est calculé le score ?
- Comment fonctionne le redémarrage ? Qu'est-ce qui est remis à zéro ?
- Comment sont sauvés les scores ? Que se passe-t-il si le fichier n'existe pas ?
- Que fait
manager.process_events? Pourquoi tester ce qu'il renvoie ? - Que se passerait-il si on supprimait cette ligne ? (Le correcteur peut montrer n'importe quelle ligne.)
- Pourquoi as-tu découpé ton code comme ça ?
- Pourquoi ce commentaire ici ? (Oui, ça aussi, il peut le demander.)
7. Défis perso (sans solution)
8. Les pièges classiques
| Symptôme | Cause probable |
|---|---|
ModuleNotFoundError: No module named 'settings' | Le jeu est lancé depuis un autre dossier, ou le fichier n'est pas dans le même dossier que main.py. Lance python main.py depuis le dossier du jeu. |
ImportError: cannot import name 'App' from partially initialized module | Import circulaire : deux modules s'importent mutuellement. Revois le sens des dépendances. |
NameError après le découpage | Un nom utilisé dans un fichier n'y est pas importé. |
| Le jeu se lance quand on importe un fichier | Il manque le if __name__ == "__main__":. |
FileNotFoundError sur une image | Mauvais chemin, ou programme lancé depuis un autre dossier. Utilise Path(__file__).parent. |
pygame.error: No convert format has been set | convert_alpha() est appelé avant pygame.display.set_mode. |
| Le jeu rame depuis que j'ai mis des images | Les images sont chargées dans draw (à chaque image), ou convert_alpha() a été oublié. |
| L'image a un fond noir ou blanc autour de la grenouille | L'image n'a pas de transparence, ou convert() est utilisé au lieu de convert_alpha(). |
| Les collisions ne correspondent plus au dessin | Les images sont plus grandes ou décalées par rapport aux rectangles. Active le mode débogage. |
9. Checklist défense finale : je sais expliquer…
10. Mini-oral final
C'est le grand oral blanc de la semaine : tape /oral dans ton IA et demande-lui de t'interroger sur l'ensemble de ton projet, en piochant dans toute la semaine. Puis vérifie ces quelques réponses :
1. Pourquoi if __name__ == "__main__": dans main.py ?
Pour que App().run() ne s'exécute que lorsqu'on lance directement python main.py, et pas quand le fichier est importé par un autre module.
2. Pourquoi charger les images dans __init__ et pas dans draw ?
draw est appelée 60 fois par seconde : relire le fichier à chaque fois serait très lent. On charge une fois, on garde la Surface dans un attribut, et draw se contente de la copier avec blit.
3. Une trace d'erreur se lit dans quel sens ?
De bas en haut : la dernière ligne donne le type d'erreur et le message, la ligne au-dessus donne le fichier, la ligne et le code fautif, et le reste montre la chaîne d'appels qui y a mené.
4. Avec des images, comment fonctionnent les collisions ?
Exactement comme avant : on teste les collisions avec les rectangles (rect()), et on dessine simplement une image à leur position. Le dessin et la logique sont séparés.
11. Aide-mémoire du dimanche
# Modules
from settings import TILE, COLS # noms précis
import settings # settings.TILE
if __name__ == "__main__":
App().run()
# Chemins
from pathlib import Path
ASSETS = Path(__file__).parent / "assets"
# Images (après set_mode, une seule fois)
image = pygame.image.load(ASSETS / "frog.png").convert_alpha()
image = pygame.transform.scale(image, (48, 48))
left = pygame.transform.flip(image, True, False)
up = pygame.transform.rotate(image, 90)
frame = sheet.subsurface(pygame.Rect(0, 0, 16, 16))
screen.blit(image, rect)
# Contour d'un rectangle (mode débogage)
pygame.draw.rect(screen, (255, 0, 0), rect, 1)
# Sons
jump_sound = pygame.mixer.Sound(ASSETS / "jump.wav")
jump_sound.play()
pygame.mixer.music.load(ASSETS / "music.ogg"); pygame.mixer.music.play(-1)
# Débogage
print(f"{variable=}") # affiche le nom et la valeur
assert lives >= 0, "vies négatives"
Et maintenant ?
Bravo, ta grenouille est arrivée à la maison 🐸🏠
Tu as revu en une semaine les variables, les conditions, les boucles, les fonctions, les listes, les classes, les dictionnaires, les fichiers et les exceptions, et tu as utilisé pygame et pygame_gui pour construire toutes les briques d'un Frogger.
Maintenant, construis le tien. Pas en recopiant le Frogger d'entraînement : en partant de ce que tu as compris, avec tes règles, ton visuel, tes choix. Garde ton prof IA pour comprendre, jamais pour écrire à ta place. Et le jour de la défense, tu pourras expliquer chaque ligne, parce que chaque ligne sera la tienne.