En S3, votre Snake devient un bot : une fonction qui reçoit l'état du plateau et renvoie un coup.
Le Snake d'aujourd'hui est donc la première brique : soignez-le.
python-homework, la veille de la séance suivante à 23 h 59.Aujourd'hui : pas de quiz, et le TP T1 se fait en séance, sans rendu.
L'IA est tolérée, comme un outil pour comprendre : vous expliquer une erreur, une notion, un bout de doc, ou vous proposer une piste quand vous êtes bloqués.
Une seule règle, pour tout le cours :
On ne copie jamais le code de l'IA. On le retape, ligne par ligne, et on ne retape que ce qu'on a compris.
Pendant le TP, je reprends la main à quelques points d'arrêt, et je fais passer les checkoffs. La clôture : ce qu'il faut retenir, et la présentation du TP maison.
Avant chaque séance : une courte préparation, indiquée sur la page de la séance, et deux questions à me poser depuis le site.
uv : un outil, un projet, un environnement
Le projet A est un vieux code, écrit pour numpy 1 ; le projet B, plus récent, a besoin de numpy 2. Installons tout dans le même Python, comme le fait pip install tout court :
pip install "numpy<2" && python projet_a.py # projet A, numpy 1.26.4 : 3.14
pip install "numpy>=2" && python projet_b.py # projet B, numpy 2.5.4 : SNAKE
python projet_a.py # ?
Pouvez-vous deviner ce qu'affiche la dernière ligne ?
AttributeError: `np.float_` was removed in the NumPy 2.0 release.
Il n'y a qu'un numpy dans un environnement : en installant celui du projet B, nous avons cassé le projet A, sans même y toucher.
(base)) ;.venv), installe même la bonne version de Python, en quelques secondes. Si conda vous gêne encore : https://python.mines.prasquier.dev/setup/
uv init --bare --python 3.12 a && cd a # numpy 1 n'existe pas pour les Python récents
uv add "numpy<2" && uv run projet_a.py # projet A, numpy 1.26.4 : 3.14
cd .. && uv init --bare b && cd b
uv add "numpy>=2" && uv run projet_b.py # projet B, numpy 2.5.4 : SNAKE
cd ../a && uv run projet_a.py # projet A, numpy 1.26.4 : 3.14
Cette fois, les deux projets cohabitent : chacun a son .venv, et son pyproject.toml garde la trace de ce dont il a besoin.
uv init demo # crée un projet : pyproject.toml, main.py
cd demo
uv add rich # ajoute une bibliothèque au projet
uv run main.py # lance le programme dans l'environnement du projet
uv add met à jour pyproject.toml et uv.lock, et installe dans .venv/ ;uv run vérifie d'abord que .venv/ est à jour, puis lance ;| Fichier | Rôle | Dans git ? |
|---|---|---|
pyproject.toml |
ce dont le projet a besoin (dependencies) |
oui |
uv.lock |
les versions exactes installées | oui |
.venv/ |
l'environnement lui-même | non : uv sync le recrée |
Les outils du cours sont aussi des dépendances du projet : pytest (les tests) et ruff (le style). On les lance avec uv run pytest et uv run ruff check .
python-homeworkUn seul dépôt GitHub pour tout le cours, un dossier par TP :
python-homework/
├── T1/ ← le zip du TP, dézippé ici
├── T2/
└── ...
La procédure complète : https://python.mines.prasquier.dev/rendre/
Point d'arrêt : uv --version répond, et votre dépôt est cloné.
cd python-homework/T1
uv add pygame-ce
uv run snake.py
uv run pytest
On installe
pygame-ce, mais on écrit import pygame.
Point d'arrêt : une fenêtre qui change de couleur, et 1 test vert.
pyproject.toml et son environnement .venv.uv add pour ajouter une bibliothèque, uv run pour lancer : rien à activer.pyproject.toml et uv.lock, jamais .venv.la boucle de jeu
Un jeu affiche plusieurs images par seconde, et réagit au clavier à tout moment.
Pourtant, un programme Python s'exécute ligne après ligne, de haut en bas, puis s'arrête.
Comment un programme qui s'exécute de haut en bas peut-il « tourner » en continu ?
while running:
clock.tick(6)
for event in pg.event.get():
...
move(snake, direction, fruit)
draw(screen, snake, fruit)
pg.display.update()
Chaque tour de boucle produit une image : tout le jeu tient dans ces trois temps, plus une attente.
Les fonctions de dessin travaillent sur une image en mémoire : rien n'apparaît à l'écran tant que pg.display.update() ne l'y a pas envoyée. Comme c'est lent, on l'appelle une fois par tour.
Et clock.tick(6) ? Il attend juste ce qu'il faut pour que le tour dure 1/6 de seconde. C'est lui qui fixe la vitesse du jeu.
pygame range l'événement dans une file d'attente ; votre code ne le voit qu'au tour suivant, quand il appelle pg.event.get().
Le serpent va vers la droite. Vous tapez très vite ↑ puis ←, entre deux tours.
Au tour suivant, pg.event.get() renvoie les deux événements, et notre boucle appelle turn pour chacun, avant de bouger.
Le jeu interdit pourtant le demi-tour. Que se passe-t-il ?
Chaque appel à turn est correct : ↑ est permis quand on va à droite, ← est permis quand on va en haut. Mais le serpent n'a pas bougé entre les deux, et le voilà qui repart sur son propre corps.
Le souci ne vient pas d'une ligne fausse, mais du moment où la direction change. Pouvez-vous le corriger ? (bonus du T1)
# snake.py
print("A")
def main():
print("B")
print("C")
if __name__ == "__main__":
main()
uv run snake.py ?import snake, depuis un autre fichier (les tests, par ex.) ?# snake.py
print("A") # à l'import
def main(): # à l'import : on DÉFINIT
print("B") # seulement à l'APPEL
print("C") # à l'import
if __name__ == "__main__":
main()
| Lancement | Affiche |
|---|---|
uv run snake.py |
A, C, B |
import snake |
A, C |
Python exécute tout le fichier, de haut en bas, au moment de l'import. Un def décrit, il n'exécute pas. C'est pourquoi la boucle de jeu vit dans main().
Aujourd'hui, c'est notre boucle while qui décide de tout. Jeudi, avec arcade, c'est arcade qui aura la boucle, et qui appellera nos fonctions.
update().def décrit, l'appel exécute.Ces trois points reviendront dans le quiz Q3.
un Snake jouable, écrit par vous
| Étape | Quoi |
|---|---|
| 1 | lire la boucle (questions) |
| 2 | quitter avec q ou la croix |
| 3 | le damier |
| 4 | un serpent immobile |
| 5 | il avance : in_scope, next_head, move |
| 6 | les flèches : turn |
| 7 | le fruit : random_fruit |
| 8 | fin de partie, score |




Un commit par étape qui marche. Les tests passent au vert au fur et à mesure.
Laquelle des deux est la plus facile à tester ? Et laquelle est la plus facile à utiliser dans un bot qui veut « essayer » plusieurs coups avant de choisir ?
On y revient en S3.
uv run pytest # tout vert
uv run ruff check . # aucune remarque
git add T1 && git commit -m "T1 : étape 8" && git push
Pas de TP maison ni de quiz pour jeudi. Si vous n'avez pas fini, les étapes 7 et 8 se font tranquillement chez vous, sans obligation.
Jeudi 29 octobre, 9 h, salle L107 : les objets, avec une nuée d'oiseaux. Le premier quiz, Q3, ouvrira la séance 3 et portera sur S1 et S2.
argparse, plateau sans bord, meilleur score ;auto(snake, fruit) qui renvoie une direction vers le fruit, sans se mordre.Ce dernier point vous donnera une longueur d'avance pour le tournoi…