RouteJour 3 · Mercredi
La circulation
La grenouille sait sauter. Il est temps de lui compliquer la vie : la route se remplit de voitures, la rivière de radeaux, et tout ça au hasard.
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 : MERCREDI, « LA CIRCULATION »
## Où j'en suis
Lundi : variables, conditions, boucles ; fenêtre, boucle de jeu, décor sur une grille (TILE = 48, COLS = 13, ROWS = 13 ; ligne 0 maisons, 1 à 5 eau, 6 berge, 7 à 11 route, 12 trottoir).
Mardi : fonctions (paramètres, return, None, portée locale, annotations). Mon code est rangé en fonctions : zone_color, draw_background, cell_to_pixel, direction_from_key, is_on_screen, lerp, jump_bump, draw_frog. La grenouille saute d'une case avec les flèches (KEYDOWN), animation de 0.15 s par interpolation (start_x/start_y, target_x/target_y, jump_t de 0 à 1 ou None au repos, dt = clock.tick(60) / 1000), effet de bond avec math.sin, impossible de sortir de l'écran pour l'instant.
## Objectif du jour
Des voitures et des radeaux défilent sur toutes les voies, avec des positions, vitesses et sens tirés au hasard ; un obstacle sorti d'un côté réapparaît de l'autre. Pas encore de collisions (vendredi). Notions : listes, listes de listes, mutabilité, module random. Point du cahier des charges visé : véhicules et radeaux à positions et vitesses aléatoires avec random (3 pts).
Questions de démarrage suggérées : est-ce que ma grenouille saute bien ; ce que je sais des listes (indices, append) ; si je connais le module random.
## Contenu de la fiche du jour
Section 1, Le Python du jour :
- 1.1 Listes : création, indices dès 0, indices négatifs, len, modifiables (contrairement aux tuples).
- 1.2 append, remove, pop, in ; a + b et a += b ; max, min, sum.
- 1.3 Parcourir : for x in liste ; enumerate pour l'indice et la valeur.
- 1.4 Listes de listes : un obstacle = [x, y, largeur, vitesse, couleur] ; cars[1][3] = vitesse du deuxième obstacle. Remarque : car[3] est peu lisible, les classes de jeudi régleront ça (car.speed).
- 1.5 Mutabilité : une fonction qui modifie speeds[i] modifie la liste de l'appelant (même liste, pas une copie) ; réaffecter le nom dans la fonction ne change que le nom local. move_obstacles s'appuie sur ça.
- 1.6 random : randint (bornes comprises), uniform, random, choice ; sens au hasard avec random.random() < 0.5 ou random.choice([-1, 1]) ; random.seed(42) pour reproduire un bug.
Section 2, Échauffements :
- E1 : speeds = [60, -90, 45, -120, 75] ; afficher « Voie i : v px/s vers la droite/gauche » avec enumerate ; len, max, min ; voie la plus rapide quel que soit le sens sans max, avec abs : la voie 3.
- E2 : random_speed(min_speed, max_speed) avec sens aléatoire ; liste de 5 vitesses avec append ; effet de random.seed(42) (mêmes tirages à chaque lancement).
- E3 : simulation texte, voie de 100 px, cars = [[0, 20], [50, 20]] ([x, largeur]) ; +15 px par étape ; si x > 100 alors x = -largeur ; 10 étapes.
- E4 : prédire : slow_down(s) ne change pas s (80) ; slow_down_all modifie la liste ([40.0, -20.0]) ; slow_down_all_wrong avec for speed in speeds: speed = speed / 2 ne change rien ([80, -40]) car la variable de boucle n'est qu'un nom vers la valeur, la réaffecter ne touche pas la liste.
Section 3, Notions pygame : positions à virgule gardées dans les données (sinon les voitures lentes n'avancent jamais), int() seulement au dessin ; ordre de dessin : décor, radeaux, voitures, grenouille (le dernier dessiné est au-dessus) ; vitesses en pixels par seconde, x += speed * dt.
Section 4, Fil rouge (solution disponible dans la fiche) :
- F1 : une voiture [x, y, largeur, vitesse] sur la ligne 9 à 80 px/s ; draw_obstacles(screen, obstacles) avec une marge verticale ; move_obstacles(obstacles, dt) ; réapparition : vitesse > 0 et x > WIDTH → x = -largeur ; vitesse < 0 et x + largeur < 0 → x = WIDTH.
- F2 : ROAD_ROWS = [7, 8, 9, 10, 11] ; create_lane(row, count, length, speed) renvoie une liste ; create_cars() tire 2 ou 3 voitures, longueur 1 ou 2 cases, vitesse de voie avec random_speed ; anti-chevauchement : découper la voie en count parts (spacing = WIDTH / count), la voiture i commence entre i * spacing et i * spacing + spacing - longueur_px ; même vitesse pour toute une voie.
- F3 : RIVER_ROWS = [1, 2, 3, 4, 5] ; create_rafts() avec radeaux de 2 à 4 cases, plus lents ; liste séparée des voitures (vendredi : voiture tue, radeau porte) ; radeaux dessinés avant la grenouille.
- F4 : 5e élément couleur ; voitures colorées avec random.choice(CAR_COLORS), radeaux bruns.
- Remarque finale : obstacle[0], [2], [3] sont illisibles, ce qui motive les classes.
Code de référence (parties nouvelles par rapport à mardi) :
import random ; ROAD_ROWS = [7, 8, 9, 10, 11] ; RIVER_ROWS = [1, 2, 3, 4, 5] ; RADEAU = (139, 105, 72) ; CAR_COLORS = [(214, 96, 77), (232, 178, 80), (120, 160, 200), (190, 120, 170)]
def random_speed(min_speed: float, max_speed: float) -> float:
speed = random.uniform(min_speed, max_speed)
if random.random() < 0.5:
speed = -speed
return speed
def create_lane(row: int, count: int, length: int, speed: float, color: tuple) -> list:
# Chaque obstacle tire sa position au hasard dans sa propre part de la voie,
# ce qui garantit qu'aucun ne chevauche son voisin
lane = []
spacing = WIDTH / count
for i in range(count):
x = i * spacing + random.uniform(0, spacing - length * TILE)
lane.append([x, row * TILE, length * TILE, speed, color])
return lane
def create_cars() -> list:
cars = []
for row in ROAD_ROWS:
count = random.randint(2, 3)
length = random.randint(1, 2)
speed = random_speed(40, 120)
cars += create_lane(row, count, length, speed, random.choice(CAR_COLORS))
return cars
def create_rafts() -> list:
rafts = []
for row in RIVER_ROWS:
count = random.randint(2, 3)
length = random.randint(2, 4)
speed = random_speed(30, 80)
rafts += create_lane(row, count, length, speed, RADEAU)
return rafts
def move_obstacles(obstacles: list, dt: float) -> None:
for obstacle in obstacles:
obstacle[0] += obstacle[3] * dt
# Un obstacle sorti d'un côté réapparaît de l'autre
if obstacle[3] > 0 and obstacle[0] > WIDTH:
obstacle[0] = -obstacle[2]
elif obstacle[3] < 0 and obstacle[0] + obstacle[2] < 0:
obstacle[0] = WIDTH
def draw_obstacles(screen: pygame.Surface, obstacles: list) -> None:
for obstacle in obstacles:
rect = pygame.Rect(int(obstacle[0]), obstacle[1] + 6, obstacle[2], TILE - 12)
pygame.draw.rect(screen, obstacle[4], rect)
Avant la boucle : cars = create_cars() ; rafts = create_rafts(). Dans la boucle, après la mise à jour du saut : move_obstacles(cars, dt) ; move_obstacles(rafts, dt) ; puis draw_background, draw_obstacles(screen, rafts), draw_obstacles(screen, cars), puis la grenouille.
Section 5, Le coin des commentaires : commenter le pourquoi (le code dit quoi, jamais pourquoi) ; exemple du commentaire de create_lane sur le découpage anti-chevauchement, sans lequel un lecteur pourrait « simplifier » le code ; nombres magiques (random_speed(40, 120)) à remplacer par des constantes nommées CAR_SPEED_MIN = 40 (avec l'unité en commentaire de fin de ligne) plutôt que par un commentaire. Exercice (sans solution) : écrire le « pourquoi » à deux endroits de mon code (par exemple x = -largeur) ; remplacer les nombres magiques de la génération des voies par des constantes.
Section 6, Défis perso (sans solution) :
- D1 : ses propres règles de circulation (sens alternés, vitesses croissantes, voie de camions), décrites dans une liste de réglages par voie, en gardant du hasard.
- D2 : vérifier qu'une voie laisse un trou d'au moins 2 cases, sinon la régénérer (while).
- D3 : véhicules dessinés avec pygame.draw (fenêtres, roues, phares dans le sens de la marche), radeaux en troncs.
- D4 : tortues plongeuses (radeau qui disparaît un moment), idée à finir vendredi.
Section 7, Pièges classiques : IndexError ; voitures immobiles (move pas appelée) ; voitures lentes figées (int() dans les données) ; voitures qui disparaissent (pas de retour ou un seul sens géré) ; apparition brutale (x = 0 au lieu de -largeur) ; for speed in speeds: speed = … ne modifie rien ; chevauchements (vitesses différentes dans une voie, positions non découpées) ; grenouille sous les radeaux (ordre de dessin).
Section 8, Checklist défense : liste contre tuple ; indices et len ; append, remove, pop, in, a + b contre a += b ; for contre enumerate ; ma représentation d'un obstacle ; pourquoi une fonction modifie une liste mais pas un nombre ; chaque fonction random utilisée et où intervient le hasard ; génération sans chevauchement ; réapparition dans les deux sens ; positions à virgule et conversion au dessin ; ordre de dessin.
Section 9, Mini-oral (réponses attendues) :
1. cars[1][3] : vitesse du deuxième obstacle.
2. move_obstacles sans return : les listes sont mutables, la fonction modifie la même liste.
3. randint(1, 3) : entier 1, 2 ou 3 ; uniform(1, 3) : nombre à virgule entre 1 et 3.
4. x = -largeur : la voiture entre doucement au lieu d'apparaître d'un coup.
5. Même vitesse par voie : sinon les voitures se rattrapent et se superposent.
6. random.seed(n) : tirages reproductibles pour déboguer.
# 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 : la circulationla brique Frogger du jour, à faire dans ton éditeur1 h
- Checklist défense : je sais expliquer…ce que tu dois savoir expliquer le jour de la défense5 min
- Aide-mémoire du mercreditout 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 (45 min)
- 2. Échauffements (30 min)
- 3. Les notions pygame du jour
- 4. Le fil rouge : la circulation (1 h)
- 5. Le coin des commentaires (10 min)
- 6. Défis perso (30 min, sans solution)
- 7. Les pièges classiques
- 8. Checklist défense : je sais expliquer…
- 9. Mini-oral
- 10. Aide-mémoire du mercredi
- Pour aller plus loin
Objectif du jour
À la fin de la journée, des voitures et des radeaux défilent sur toutes les voies, chacun à une position, une vitesse et dans un sens tirés au hasard. Quand un obstacle sort d'un côté de l'écran, il réapparaît de l'autre.
La grenouille peut encore traverser les voitures sans rien sentir : les collisions, c'est vendredi. Aujourd'hui, on apprend à gérer beaucoup d'objets à la fois grâce aux listes.
Lien avec le cahier des charges : « Afficher des véhicules et des radeaux à des positions et des vitesses aléatoires (module random) » : 3 points.
1. Le Python du jour (45 min)
1.1 Les listes
Hier, une variable contenait une valeur. Mais une route contient plusieurs voitures. Une liste range plusieurs valeurs, dans l'ordre, sous un seul nom.
speeds = [60, -90, 45]
empty = []
- On lit un élément avec son indice, qui commence à 0 :
speeds[0]vaut60. - Les indices négatifs partent de la fin :
speeds[-1]vaut45(le dernier). len(speeds)donne le nombre d'éléments :3.- Contrairement aux tuples, une liste se modifie :
speeds[1] = -100.
1.2 Ajouter, retirer, chercher
speeds.append(75) # ajoute à la fin : [60, -100, 45, 75]
speeds.remove(45) # retire la première valeur 45
last = speeds.pop() # retire et renvoie le dernier élément
if 60 in speeds: # teste si une valeur est dans la liste
print("Il y a une voiture à 60")
On peut aussi coller deux listes : a + b crée une nouvelle liste, et a += b ajoute les éléments de b à la fin de a.
Quelques fonctions utiles qui travaillent sur des listes : max(speeds), min(speeds), sum(speeds).
1.3 Parcourir une liste
Le for de lundi marche sur n'importe quelle liste, pas seulement sur range :
for speed in speeds:
print(speed)
Si tu as besoin de l'indice et de la valeur, utilise enumerate :
for i, speed in enumerate(speeds):
print(f"Voie {i} : {speed} pixels par seconde")
1.4 Des listes dans une liste
Une voiture a plusieurs caractéristiques : sa position x, sa position y, sa largeur, sa vitesse, sa couleur. On peut les ranger dans une petite liste… et ranger toutes les voitures dans une grande liste.
car = [120.0, 384, 48, 75.0, (214, 96, 77)] # x, y, largeur, vitesse, couleur
cars = [car, [400.0, 432, 96, -60.0, (120, 160, 200)]]
print(cars[1][3]) # la vitesse de la deuxième voiture : -60.0
cars[1] est la deuxième voiture (une liste), et cars[1][3] est l'élément d'indice 3 de cette voiture : sa vitesse.
1.5 La mutabilité : le piège (et la force) des listes
Hier, une fonction ne pouvait pas modifier un nombre extérieur. Avec une liste, c'est différent :
def slow_down_all(speeds):
for i in range(len(speeds)):
speeds[i] = speeds[i] / 2
lanes = [80, -40]
slow_down_all(lanes)
print(lanes) # [40.0, -20.0] : la liste a changé !
Une liste est mutable : la fonction ne reçoit pas une copie, elle reçoit la même liste sous un autre nom. Modifier un élément de la liste dans la fonction modifie donc la liste de l'extérieur.
Ce qu'on ne peut toujours pas faire, c'est remplacer la variable entière (speeds = [...] dans la fonction ne change que le nom local). Modifier dedans (speeds[i] = ...) : oui. Réaffecter le nom : non.
1.6 Le hasard : le module random
import random
random.randint(1, 6) # entier entre 1 et 6, bornes comprises
random.uniform(40, 120) # nombre à virgule entre 40 et 120
random.random() # nombre à virgule entre 0 et 1
random.choice(["rouge", "bleu"]) # un élément au hasard dans une liste
Pour tirer un sens au hasard, on peut faire random.random() < 0.5 (vrai une fois sur deux) ou random.choice([-1, 1]).
📚 Pour revoir ces notions : Python 1 · Chapitre 3 : Séquences et Python 2 · Cours 2 : Imbrication de types du cours de ton prof.
2. Échauffements (30 min)
Du Python pur, dans un fichier echauffement_mercredi.py.
E1. Les vitesses des voies
speeds = [60, -90, 45, -120, 75]
- Avec
enumerate, affiche pour chaque voie :Voie 0 : 60 px/s vers la droite(ouvers la gauchesi la vitesse est négative). - Affiche le nombre de voies, la vitesse la plus grande et la plus petite (avec les fonctions de 1.2).
- Sans utiliser
max, trouve la voie la plus rapide, quel que soit son sens. (Indice :abs(-120)vaut120.) Réponse attendue : la voie 3.
E2. Jouer avec le hasard
- Écris
random_speed(min_speed, max_speed)qui renvoie une vitesse au hasard entre les deux bornes, avec un sens au hasard (positive ou négative). - Crée une liste vide, puis ajoute-y 5 vitesses tirées avec ta fonction. Affiche-la.
- Ajoute
random.seed(42)au début du fichier et relance plusieurs fois. Que remarques-tu ?
E3. Le défilement en texte
Une voie fait 100 pixels de large. Deux voitures de 20 pixels de large :
cars = [[0, 20], [50, 20]] # [x, largeur]
À chaque étape, chaque voiture avance de 15 pixels. Quand une voiture sort complètement à droite (x > 100), elle réapparaît à gauche, juste avant le bord : x = -largeur.
Simule 10 étapes avec une boucle, et affiche la liste à chaque étape. Vérifie que les voitures « bouclent » bien.
E4. Le piège de la mutabilité
Sans l'exécuter, prédis ce qu'affiche chaque print. Puis exécute.
def slow_down(speed):
speed = speed / 2
def slow_down_all(speeds):
for i in range(len(speeds)):
speeds[i] = speeds[i] / 2
def slow_down_all_wrong(speeds):
for speed in speeds:
speed = speed / 2
s = 80
slow_down(s)
print(s)
lanes = [80, -40]
slow_down_all(lanes)
print(lanes)
lanes = [80, -40]
slow_down_all_wrong(lanes)
print(lanes)
Le troisième résultat surprend presque tout le monde. Explique-le à ton prof IA.
3. Les notions pygame du jour
Peu de nouvelles fonctions aujourd'hui, mais deux idées importantes.
Les positions à virgule
Avec dt, une voiture avance de vitesse × dt pixels par image : par exemple 75 × 0.016 = 1.2 pixel. Sa position x devient un nombre à virgule. C'est voulu : si on arrondissait à chaque image, les voitures lentes n'avanceraient jamais !
On garde donc x en nombre à virgule dans nos données, et on convertit avec int() seulement au moment de dessiner, parce qu'un rectangle se dessine au pixel près.
L'ordre de dessin
Ce qui est dessiné en dernier apparaît par-dessus. L'ordre dans draw compte donc :
- le décor (au fond),
- les radeaux (sur l'eau),
- les voitures,
- la grenouille (toujours au-dessus, surtout quand elle est sur un radeau).
La vitesse en pixels par seconde
On exprime les vitesses en pixels par seconde, et on multiplie par dt (en secondes) : x += speed * dt. Une voiture à 100 px/s traverse les 624 pixels de l'écran en un peu plus de 6 secondes, quelle que soit la vitesse de l'ordinateur.
4. Le fil rouge : la circulation (1 h)
On repart de ton code de mardi.
F1. Une première voiture
- Représente une voiture par une liste
[x, y, largeur, vitesse]. Crée-en une sur la voie de la ligne 9, qui avance vers la droite à 80 px/s. - Écris
draw_obstacles(screen, obstacles)qui dessine chaque obstacle d'une liste avecpygame.draw.rect. Laisse un petit espace en haut et en bas pour que les voitures ne touchent pas les bords de la voie. - Écris
move_obstacles(obstacles, dt)qui fait avancer chaque obstacle devitesse × dt. - Appelle les deux dans ta boucle de jeu (la mise à jour, puis le dessin), avec une liste qui contient ta seule voiture.
La voiture traverse l'écran… puis disparaît pour toujours. Corrige move_obstacles : une voiture sortie à droite doit réapparaître à gauche, entièrement cachée (x = -largeur), comme dans E3.
Puis teste une vitesse négative : la voiture va vers la gauche, et il faut gérer l'autre bord.
F2. Des voies entières, au hasard
On veut maintenant remplir toutes les voies de la route.
- Crée une constante
ROAD_ROWS = [7, 8, 9, 10, 11], la liste des lignes de la route. - Écris
create_lane(row, count, length, speed)qui renvoie une liste decountvoitures delengthcases de long, toutes sur la lignerowet à la vitessespeed. - Écris
create_cars()qui parcourtROAD_ROWSet, pour chaque voie, tire au hasard le nombre de voitures (2 ou 3), leur longueur (1 ou 2 cases) et la vitesse de la voie (avec tonrandom_speedde E2). Elle renvoie la liste de toutes les voitures.
Le problème du chevauchement. Si tu tires la position de chaque voiture complètement au hasard, deux voitures risquent de se superposer. Une solution : couper la voie en count parts égales, et tirer la position de chaque voiture à l'intérieur de sa part.
| part 0 | part 1 | part 2 |
| [==] | [==] | [==] |
Avec spacing = WIDTH / count, la voiture i commence quelque part entre i * spacing et i * spacing + spacing - longueur_en_pixels.
F3. La rivière et ses radeaux
Fais la même chose pour la rivière :
- Une constante
RIVER_ROWS = [1, 2, 3, 4, 5]. - Une fonction
create_rafts(): des radeaux plus longs (2 à 4 cases) et plus lents que les voitures. - Garde les radeaux dans une liste séparée des voitures. Vendredi, une voiture tuera la grenouille, mais un radeau la portera : on aura besoin de les distinguer.
- Dessine les radeaux avant la grenouille.
F4. Un peu de couleur
Ajoute un 5ᵉ élément à chaque obstacle : sa couleur. Les radeaux sont tous bruns ; pour les voitures, tire une couleur au hasard dans une liste avec random.choice.
Relance le jeu plusieurs fois : chaque partie doit être différente.

Une solution possible : les parties nouvelles par rapport à mardi
En haut du fichier :
import math
import random
import sys
import pygame
# ... constantes de mardi ...
ROAD_ROWS = [7, 8, 9, 10, 11]
RIVER_ROWS = [1, 2, 3, 4, 5]
# ... couleurs de mardi ...
RADEAU = (139, 105, 72)
CAR_COLORS = [(214, 96, 77), (232, 178, 80), (120, 160, 200), (190, 120, 170)]
Les nouvelles fonctions :
def random_speed(min_speed: float, max_speed: float) -> float:
speed = random.uniform(min_speed, max_speed)
if random.random() < 0.5:
speed = -speed
return speed
def create_lane(row: int, count: int, length: int, speed: float, color: tuple) -> list:
# Chaque obstacle tire sa position au hasard dans sa propre part de la voie,
# ce qui garantit qu'aucun ne chevauche son voisin
lane = []
spacing = WIDTH / count
for i in range(count):
x = i * spacing + random.uniform(0, spacing - length * TILE)
lane.append([x, row * TILE, length * TILE, speed, color])
return lane
def create_cars() -> list:
cars = []
for row in ROAD_ROWS:
count = random.randint(2, 3)
length = random.randint(1, 2)
speed = random_speed(40, 120)
cars += create_lane(row, count, length, speed, random.choice(CAR_COLORS))
return cars
def create_rafts() -> list:
rafts = []
for row in RIVER_ROWS:
count = random.randint(2, 3)
length = random.randint(2, 4)
speed = random_speed(30, 80)
rafts += create_lane(row, count, length, speed, RADEAU)
return rafts
def move_obstacles(obstacles: list, dt: float) -> None:
for obstacle in obstacles:
obstacle[0] += obstacle[3] * dt
# Un obstacle sorti d'un côté réapparaît de l'autre
if obstacle[3] > 0 and obstacle[0] > WIDTH:
obstacle[0] = -obstacle[2]
elif obstacle[3] < 0 and obstacle[0] + obstacle[2] < 0:
obstacle[0] = WIDTH
def draw_obstacles(screen: pygame.Surface, obstacles: list) -> None:
for obstacle in obstacles:
rect = pygame.Rect(int(obstacle[0]), obstacle[1] + 6, obstacle[2], TILE - 12)
pygame.draw.rect(screen, obstacle[4], rect)
Avant la boucle de jeu :
cars = create_cars()
rafts = create_rafts()
Dans la boucle, après la mise à jour du saut :
move_obstacles(cars, dt)
move_obstacles(rafts, dt)
draw_background(screen)
draw_obstacles(screen, rafts)
draw_obstacles(screen, cars)
# ... puis la grenouille, comme mardi
Regarde move_obstacles : elle ne renvoie rien, et pourtant les voitures avancent. C'est la mutabilité des listes (section 1.5).
Regarde ton code
Relis move_obstacles et compte les obstacle[0], obstacle[2], obstacle[3]… Dans une semaine, sauras-tu encore que [3] est la vitesse ? Et ton correcteur, à l'oral ? C'est exactement le problème que les classes vont résoudre demain.
5. Le coin des commentaires (10 min)
Aujourd'hui : commenter le pourquoi
Le code dit ce qu'il fait. Il ne dit jamais pourquoi il le fait comme ça. C'est exactement ce que doit dire un bon commentaire.
# Chaque obstacle tire sa position au hasard dans sa propre part de la voie,
# ce qui garantit qu'aucun ne chevauche son voisin
spacing = WIDTH / count
Sans ce commentaire, un lecteur voit un calcul, mais il ne sait pas à quoi sert ce découpage. Et il pourrait le « simplifier »… en réintroduisant les chevauchements.
Les endroits qui méritent un « pourquoi » : un choix entre plusieurs solutions, une règle du jeu, une astuce, un piège qu'on a évité.
Chasser les nombres magiques
Un nombre magique est un nombre écrit en dur dont on ne sait pas d'où il vient.
speed = random_speed(40, 120) # 40 et 120, c'est quoi ?
Plutôt qu'un commentaire, donne-lui un nom avec une constante, en haut du fichier :
CAR_SPEED_MIN = 40 # pixels par seconde
CAR_SPEED_MAX = 120
speed = random_speed(CAR_SPEED_MIN, CAR_SPEED_MAX)
Le code se lit tout seul, et tous les réglages sont au même endroit. Le petit commentaire en fin de ligne précise l'unité, qui ne se voit pas dans le nom.
Exercice
- Dans ton code du jour, trouve deux endroits où un lecteur se demanderait « pourquoi ? », et écris le commentaire qui répond (pense au retour de l'autre côté de l'écran en
x = -largeur). - Remplace les nombres magiques de ta génération de voies (nombre d'obstacles, longueurs, vitesses) par des constantes nommées. Bonus : ça t'aide pour le défi D1.
6. Défis perso (30 min, sans solution)
7. Les pièges classiques
| Symptôme | Cause probable |
|---|---|
IndexError: list index out of range | Tu lis un indice qui n'existe pas : speeds[5] dans une liste de 5 éléments (les indices vont de 0 à 4). |
| Les voitures ne bougent pas | move_obstacles n'est pas appelée dans la boucle, ou dt vaut 0. |
| Les voitures lentes restent figées | La position est arrondie avec int() dans les données, à chaque image. N'arrondis qu'au moment de dessiner. |
| Les voitures disparaissent pour de bon | Le retour de l'autre côté de l'écran manque, ou la condition ne gère qu'un sens. |
| Les voitures « sautent » au lieu d'apparaître doucement | Elles réapparaissent en x = 0 au lieu de x = -largeur (entièrement cachées). |
Une boucle for speed in speeds: speed = … ne change rien | La variable de boucle est une copie du nom, pas la case de la liste. Modifie speeds[i], ou l'élément de la sous-liste. |
| Les obstacles se chevauchent | Chaque voiture a sa propre vitesse dans une même voie, ou les positions sont tirées sans découper la voie. |
| La grenouille passe sous les radeaux | La grenouille est dessinée avant les radeaux. L'ordre de dessin compte. |
8. Checklist défense : je sais expliquer…
9. Mini-oral
Réponds à voix haute, puis vérifie. Mieux : tape /oral dans ton IA.
1. Que vaut cars[1][3] si cars contient des obstacles [x, y, largeur, vitesse, couleur] ?
La vitesse du deuxième obstacle de la liste : cars[1] est le deuxième obstacle (indice 1), et [3] est sa quatrième case, la vitesse.
2. Pourquoi move_obstacles n'a-t-elle pas besoin de return ?
Elle modifie le contenu des listes qu'elle reçoit. Une liste est mutable : la fonction travaille sur la même liste que l'appelant, donc les modifications sont visibles à l'extérieur.
3. Quelle est la différence entre random.randint(1, 3) et random.uniform(1, 3) ?
randint renvoie un entier entre 1 et 3, bornes comprises (1, 2 ou 3). uniform renvoie un nombre à virgule quelconque entre 1 et 3, par exemple 2.718.
4. Pourquoi faire réapparaître une voiture en x = -largeur et pas en x = 0 ?
En x = 0, la voiture apparaîtrait d'un coup, entièrement visible. En x = -largeur, elle est juste derrière le bord gauche et entre doucement dans l'écran.
5. Pourquoi toutes les voitures d'une voie ont-elles la même vitesse ?
Si deux voitures d'une même voie avaient des vitesses différentes, la plus rapide finirait par rattraper l'autre et elles se superposeraient.
6. Comment rendre les tirages aléatoires identiques à chaque lancement, et pourquoi ?
Avec random.seed(un_nombre) au début du programme. C'est utile pour reproduire un bug qui n'apparaît qu'avec certains tirages.
10. Aide-mémoire du mercredi
# Listes
lst = [1, 2, 3]
lst[0]; lst[-1]; len(lst)
lst.append(4); lst.remove(2); x = lst.pop()
if 3 in lst: ...
a += b # ajoute les éléments de b à a
max(lst); min(lst); sum(lst); abs(-5)
# Parcourir
for x in lst: ...
for i, x in enumerate(lst): ...
for i in range(len(lst)):
lst[i] = ... # pour MODIFIER les éléments
# Listes de listes
obstacle = [x, y, largeur, vitesse, couleur]
obstacles[i][3] # vitesse du i-ème obstacle
# Hasard
import random
random.randint(a, b) # entier, bornes comprises
random.uniform(a, b) # à virgule
random.random() # entre 0 et 1
random.choice(lst) # un élément
random.seed(42) # tirages reproductibles
# Mouvement indépendant de la vitesse de l'ordinateur
x += speed * dt # speed en pixels par seconde
Pour aller plus loin
- 📚 Python 1 · Chapitre 3 : Séquences
- 📚 Python 2 · Cours 2 : Imbrication de types, classes et objets
- 📖 Documentation Python : le module random
Demain, jeudi : tout est objet. On range le jeu en classes, et obstacle[3] devient enfin obstacle.speed 🧱