Uploaded by abeaucadeau

ellipse apprendre la programmation par le jeu decouvrir Pygame

advertisement
Vincent Maille
CRÉATIONS NUMÉRIQUES
Apprendre
la programmation
par le jeu
RESSOURCES
TÉLÉCHARGEABLES
Découvrir Pygame
avec de nouveaux jeux en Python
Vincent Maille
Graphismes de David Beauget
CRÉATIONS NUMÉRIQUES
Apprendre la programmation
par le jeu
Apprendre
la programmation
par le jeu
Découvrir Pygame avec de nouveaux jeux
Découvrir Pygame
avec de nouveaux jeux en Python
Vincent Maille
Graphismes de
David Beauget
Collection
CRÉ ATIONS NUMÉRIQUES
dirigée par Vincent Maille
http://creations-numeriques.fr/
Retrouvez tous les titres de la collection et des extraits
sur www.editions-ellipses.fr
ISBN 9782340-042100
© Ellipses Édition Marketing S.A., 2020
32, rue Bargue 75740 Paris cedex 15
Petite intro
Pourquoi ce livre ?
Je vais vous parler d’un temps que les moins de 40 ans ne peuvent pas
connaı̂tre (ni peut-être comprendre l’allusion à cette chanson !), mais j’ai
eu mon premier ordinateur à mon entrée en sixième (il faut dire que je travaillais au corps mes parents depuis déjà quelques années ! ! !). C’était un
MO5 de Thomson et les jeux étaient enregistrés sur des cassettes
magnétiques... Avec lui, j’ai fait mes premiers pas dans la programmation
avec mon grand frère. Notre plus grand projet de jeu fut ≪ La capture de
Joe ≫ ! Grâce au droit à l’oubli beaucoup plus facile à l’époque, il doit finir
ses jours à 6 pieds sous terre au fond d’une ancienne décharge à ciel ouvert
comme cela existait malheureusement à l’époque !
Quelques années plus tard, alors que j’étais en seconde et première,
nous avons eu un ATARI STE, le top du moment, on pouvait jouer, programmer et même faire de la MAO (Musique Assistée par Ordinateur !)
avec ce petit bijou. J’avais plus de connaissances en mathématiques pour
aborder des choses plus complexes et nous avons passé pas mal de temps
avec mon frère à coder, des heures durant, des démos pour notre groupe
de programmation ( LEGEND ). Un retour de 20 ans en arrière est proposé d’ailleurs au chapitre F du livre. A cette époque, pas d’Internet pour
apprendre, ni de forum pour poser des questions... Nous avions juste une
BAL (Boı̂te Aux Lettres) sur Minitel, que nous relevions une fois par semaine (vu les tarifs !). Heureusement, nous avions à notre disposition un
énorme livre de 400 pages : notre bible du GFA Basic, mais surtout, tous
les mois, nous attendions avec impatience la sortie du nouveau numéro
de ST Magazine. Cette revue parlait de jeux, mais aussi de programmation
avec des cours, des astuces et bien sûr les GFA Punchs, dont il fallait relever le défi de réaliser un programme – utile ou joli – en moins de 20 lignes !
(Sur la capture de la page suivante, un Tétris en 20 lignes)
1
Le livre que vous lisez en ce moment est donc un peu celui que j’aurais
aimé avoir entre les mains il y a 30 ans...
La démarche de projet
On parle souvent d’enseigner par compétence ou d’enseigner par projet pour changer les façons de faire... Pourtant la plupart des manuels ou
des ouvrages sont encore souvent très linéaires.
Ce livre propose enfin une démarche d’apprentissage par projet. Ainsi
chaque chapitre présente la réalisation d’un petit jeu ; les connaissances
nécessaires à la réalisation de celui-ci sont apportées au fur et à mesure du
projet. Dans la plupart des cas, le chapitre se termine par une résolution
automatique du niveau ou une intelligence artificielle simple servant de
deuxième joueur.
Bien entendu, dans ce livre, les chapitres sont assez guidés pour suivre
le cheminement que j’ai pu imaginer au cours de l’activité. J’aurais opté
pour une version bien plus ouverte pour un projet en classe, mais cela donnera, je l’espère en tout cas, des idées au lecteur pour chercher à
approfondir certaines notions.
Le choix de Pygame
Il n’est pas nécessaire d’avoir lu le premier tome pour entamer ce
second opus. Si vous l’avez acheté et lu, merci, cela vous aidera grandement à comprendre les notions qui sont un peu plus poussées dans cet
ouvrage. Sinon, il est quand même conseillé d’avoir des notions de base
de Python avant de s’atteler à la découverte du livre. Dans tous les cas, un
premier chapitre reprenant les types de base de Python est proposé pour
faire le point ensemble.
Pour ce second tome, j’ai choisi de vous présenter l’interface Pygame,
plus puissante que Tkinter pour réaliser des jeux, mais moins adaptée si
vous souhaitez réaliser des applications.
Si vous utilisez EduPython, c’est un bon choix et vous n’aurez rien à
installer pour utiliser Pygame ; sinon, il vous suffit d’installer Pygame avec
pip : pip install pygame et le tour est joué. Sous EduPython, lorsque
votre programme plantera (ne vous inquiétez pas, ça arrivera !), ne tentez
pas de fermer la fenêtre mais le fait de relancer le programme (avec l’erreur
corrigée) relancera la machine. Dans le pire des cas, l’appui sur CTRL +
F2 fera tout rentrer dans l’ordre.
Comme pour tous les livres de la collection créations numériques,
vous aurez la possibilité d’utiliser le complément en ligne sur le site :
http://creations-numeriques.fr/
En saisissant les CODES signalés sur fond gris ou les numéros des
exercices, vous pourrez télécharger les différents codes sources des programmes proposés ainsi que les images et autres ressources. Tous les exercices y sont corrigés.
À ce propos, n’hésitez pas à lire la correction des différents exercices
que vous aurez réalisés, même si vous les avez réussis. Les corrections
donnent souvent différentes pistes de résolution et apportent parfois des
compléments précieux pour la suite de l’aventure.
Merci, merci, merci
J’aimerais commencer par remercier
ici les élèves du lycée Louis Thuillier
d’Amiens, ceux qui, comme Guillaume,
n’ont jamais manqué un rendez-vous le
mercredi midi au club info, mes étudiants
de TSI en particulier Théo, Alexandre
et Lucas, mais aussi de MPSI, MP ou
BCPST qui nous obligent à régulièrement
faire des recherches, imaginer de nouvelles
stratégies d’enseignement et m’ont souvent inspiré par les idées qu’ils pouvaient
avoir au travers de leurs projets ou TIPE.
Merci à ceux qui ont pris le temps de relire ce livre pour en proposer
une version qui contienne moins de fautes que dans sa version originale !
Mes parents, sans qui tout cela n’aurait pu commencer (je ne parle pas
de ma naissance, mais du MO5 !). Mes collègues et amis avec qui j’ai la
chance de travailler : Guillaume Miannay et Paul Stienne, deux traqueurs
de coquilles d’une efficacité redoutable.
Enfin, un livre sur les jeux n’aurait pas le même cachet sans les
superbes graphismes de David Beauget, mon complice de toujours qui en
plus d’être un informaticien hors norme, possède comme vous le verrez
un réel talent de dessinateur !
Table des matières
Table des matières
A Révisions
1 - Variables et fonctions . . . . . . . . . .
A Révisions
2 - Tests et boucles . . . . . . . . . . . . . .
1 - Variables et fonctions . . . . . . . . . .
3 - Chaı̂nes de caractères . . . . . . . . . .
2 - Tests et boucles . . . . . . . . . . . . . .
4 - Les listes et matrices . . . . . . . . . . .
3 - Chaı̂nes de caractères . . . . . . . . . .
5 - Aide pour les exercices . . . . . . . . .
4 - Les listes et matrices . . . . . . . . . . .
6 - Solutions des exercices . . . . . . . . . .
5 - Aide pour les exercices . . . . . . . . .
6 - Solutions des exercices . . . . . . . . . .
B Jeu de Pong
1 - Interface et variables . . . . . . . . . . .
B Jeu de Pong
2 - Utilisation des dictionnaires . . . . . .
1 - Interface et variables . . . . . . . . . . .
3 - Retour à notre projet . . . . . . . . . . .
2 - Utilisation des dictionnaires . . . . . .
4 - Animation et gestion des rebonds . . .
3 - Retour à notre projet . . . . . . . . . . .
5 - Déplacement des raquettes . . . . . . .
4 - Animation et gestion des rebonds . . .
6 - Buts et scores . . . . . . . . . . . . . . .
5 - Déplacement des raquettes . . . . . . .
7 - Amélioration des rebonds . . . . . . .
6 - Buts et scores . . . . . . . . . . . . . . .
8 - Un peu d’intelligence artificielle... . . .
7 - Amélioration des rebonds . . . . . . .
9 - Aide pour les exercices . . . . . . . . .
8 - Un peu d’intelligence artificielle... . . .
10 - Solutions des exercices . . . . . . . . . .
9 - Aide pour les exercices . . . . . . . . .
10 - Solutions des exercices . . . . . . . . . .
C Minos
1 - Fenêtre et surfaces dans Pygame . . .
C Minos
2 - Dessiner un labyrinthe . . . . . . . . .
1 - Fenêtre et surfaces dans Pygame . . .
3 - Générer un labyrinthe . . . . . . . . . .
2 - Dessiner un labyrinthe . . . . . . . . .
4 - Les événements clavier, retour au jeu !
3 - Générer un labyrinthe . . . . . . . . . .
5 - Pour aller plus loin : sortir . . . . . . .
4 - Les événements clavier, retour au jeu !
6 - Aide pour les exercices . . . . . . . . .
5 - Pour aller plus loin : sortir . . . . . . .
7 - Solutions des exercices . . . . . . . . . .
6 - Aide pour les exercices . . . . . . . . .
7 - Solutions des exercices . . . . . . . . . .
D SELEM-STOM
1 - Quelques fonctions pour commencer .
2 - Remplissage et expressions régulières
3 - Dessiner la grille de jeu . . . . . . . . .
4 - Gestion de la souris . . . . . . . . . . .
5 - Une grille à thème ! . . . . . . . . . . . .
6 - Aide pour les exercices . . . . . . . . .
7 - Solutions des exercices . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
11
11
11
18
11
24
18
26
24
31
26
32
31
32
45
45
45
48
45
54
48
54
54
55
54
57
55
57
57
60
57
60
60
62
60
62
73
74
73
83
74
86
83
88
86
92
88
96
92
98
96
98
113
114
116
122
125
127
137
139
E Cupidon
157
1 - L’object Rect de Pygame . . . . . . . . . . . . . . . . . . . . . . . . . . 158
34567-
Dessiner la grille de jeu
Gestion de la souris . .
Une grille à thème ! . . .
Aide pour les exercices
Solutions des exercices .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
122
125
127
137
139
E Cupidon
1 - L’object Rect de Pygame . . . . . . . . .
2 - Mise en place de Cupidon . . . . . . . .
3 - La flèche . . . . . . . . . . . . . . . . . . .
4 - Les ennemis . . . . . . . . . . . . . . . . .
5 - Introduction à la programmation objet .
6 - Aide pour les exercices . . . . . . . . . .
7 - Solutions des exercices . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
157
158
161
163
164
168
175
176
F SELEM-STOM
Front-end
D
Manipulerfonctions
les fichiers
de commencer
configuration
11 -- Quelques
pour
. . .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ..
La bande de film
. . . . . . régulières
. . . . . . . .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ..
22 -- Remplissage
et expressions
Unlimitedlasprites
. .jeu
. . .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ..
33 -- Dessiner
grille de
3D Starfield
. . . . . .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ..
44 -- Gestion
de la souris
5
Aide
pour
les
exercices
5 - Une grille à thème
! . . . .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ..
6
Solutions
des
exercices
6 - Aide pour les exercices . .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ..
7 - Solutions des exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . .
G EHPAD-RUN
1 - Manipulation d’images . . . . . . . . . . . . . . . . . . . . . . . . . . .
E Cupidon
L’interface
du de
jeuPygame
. . . . . .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ..
12 -- L’object
Rect
Les personnages
. . . . . . .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ..
23 -- Mise
en place de Cupidon
Aide
pour .les
34 -- La
flèche
. .exercices
. . . . . . .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ..
Solutions
des .exercices
45 -- Les
ennemis
. . . . . .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ..
5 - Introduction à la programmation objet . . . . . . . . . . . . . . . . . .
H 6Les
données
- bases
Aide de
pour
les exercices . . . . . . . . . . . . . . . . . . . . . . . . . . .
1
Présentation
. . . . . .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ..
7 - Solutions des .exercices
2 - Utilité des tables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3 - Premières requêtes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
F Front-end
41 -- Compléments
sur les requêtes
. . . . . . .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ..
Manipuler les fichiers
de configuration
52 -- Les
jointures
. . . .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ..
La bande
de film
63 -- Ajouter,
modifier,
Unlimited
sprites supprimer
. . . . . . . des
. . .enregistrements
. . . . . . . . . . .. .. .. .. .. .. .. .. .. .. ..
7
Compléments
sur
les
dates
et
chaı̂nes
.. ..
4
3D
Starfield
.
.
.
.
.
.
.
.
.
.
.
.
.
. . . .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. 113
D SELEM-STOM
D SELEM-STOM
8
Les
groupements
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Aide pour
les exercices
.commencer
. . . . . . . . . . . . . . . . . . . . . . . . 114
1 - 15Quelques
- Quelques
fonctions
fonctions
pourpour
commencer
.. ..
D SELEM-STOM
D
SELEM-STOM
113
9
Aides
pour
les
exercices
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Solutionsetdes
exercices
. . . régulières
. . . . . . . . . . . . . . . . . . . . . . . 116
2 - 26Remplissage
- Remplissage
expressions
et
expressions
régulières
.. ..
10
Solutions
des
exercices
.
.
.
1 - 3Quelques
1
Quelques
fonctions
fonctions
pour
commencer
pour
commencer
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
114
.
.
.
.. ..
- 3Dessiner
- Dessiner
la grille
la grille
de jeu
de .jeu
. . . . . . . . . . . . . . . . . . . . . . . . . . . 122
G
2 - 4Remplissage
-- Remplissage
et expressions
expressions
.. .. .. 125
.. ..
- 24EHPAD-RUN
Gestion
Gestion
de
lade
souris
la et
souris
. régulières
. . . . . . régulières
. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. 116
I O-But
1
Manipulation
d’images
3 - 5A
Dessiner
3
la
Dessiner
grille
de
la
jeu
grille
.
.
de
.
.
jeu
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
122
.
.
.
. .
Révisions
- 15Une
Une
grillegrille
à thème
à thème
! . niveaux
. .! . . . . .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. 127
--- de
Chargement
des
... ...
2
L’interface
du
jeu
4 - 6Gestion
4
Gestion
la
souris
de
la
.
souris
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
125
.
.
.
- 126Aide
Aide
pourpour
lesetexercices
les
exercices
. .. .. -.. Propriétés
.. .. .. .. .. .. .. ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... 137
- -- Variables
fonctions
Déplacement
des
boules
... ..
A
Les
personnages
5 - 7A
Une
573Révisions
grille
-- Une
à thème
grille
!exercices
à. thème
. .exercices
. . ..! .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. 127
. .. .. 139
. .
- 2Révisions
Solutions
Solutions
des
des
.
et bouclesdu. personnage
. . . . . . . . -.La
. .classe
. . . .Mask
. . . . .. .. .. .. .. .. .. .. .. .. .. ... ..
3- - Tests
Déplacement
6 - Aide
- Variables
Aide
les exercices
pour
exercices
. . . . .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. 137
.. .. .. .. .
1641-pour
Variables
etetles
fonctions
fonctions
Ce n’estde
qu’un
au revoir...
. . . . . . . . . . . . . . . . . . . . . .. .. .. .. .
345- -- Chaı̂nes
caractères
7 E- Cupidon
Solutions
Solutions
des et
exercices
des
.exercices
.. ... ... ... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... .... 139
. . . ... ..
E 27Cupidon
Tests
etboucles
boucles
52- -- Tests
Aide
pour
les exercices
. . . . . . . . . . . . . . . . . . . . . . ... ... ... 157
. .
4
Les
listes
et
matrices
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1 - 3613L’object
L’object
Rect
Rect
de
Pygame
de Pygame
.. ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... .... .... .... 158
.... ..
Solutions
des
exercices
- -- Chaı̂nes
Chaı̂nes
de
de
caractères
caractères
H
bases
de
E Cupidon
- - Aide
pour
les Cupidon
exercices
2E
- 5Cupidon
2Les
Mise
Mise
en place
en données
place
de
de Cupidon
.. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. 157
.. .. .. 161
.. .
414- -- Rect
Les
Leslistes
listes
etetmatrices
matrices
. ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... 158
.. .. .. ... ..
Présentation
.
.
1 - 3L’object
L’object
de
Pygame
Rect
de
.
Pygame
.
.
- - flèche
Solutions
- 6Glossaire
3La
La flèche
. . des
. . .exercices
. . . . . .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ... ... ... 163
.. .
525-en
- Aide
Aidepour
pour
les
lesexercices
exercices
. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. 161
.. .. .. .. .
Utilité
des
tables
2 - 4Mise
place
Mise
de
en
Cupidon
place
de
Cupidon
.
.
.
.
- 4Les
- ennemis
Les ennemis
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164
. .
Premières
requêtes
6356Introduction
- -- de
Solutions
Solutions
des
exercices
. .programmation
. . .. .. .. .. ..objet
.. .. ..objet
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... 163
3 - 5B
La
La
flèch àdes
- flèche
Introduction
la
programmation
à.exercices
la
... ... ... 168
... ..
Jeu
Pong
Compléments
sur
. . . .. ..
4 - 6Les
. . les
. .. requêtes
. . . . . . . . . . . . . . . . . . . . . . . . 164
46Aide
- Les
enn
- 1enne
Aide
pour
pour
lesetexercices
les .exercices
- - Interface
variables
. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. 175
.
Les
jointures
.
.
.
.
.
.
.
.
.
.
5 - 7B
Introduction
à
la
programmation
objet
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
168
5
Introduction
à
la
programmation
objet
.
.
.. ..
B
Jeu
Jeu
de
de
Pong
Pong
- 27Solutions
- Utilisation
Solutions
des exercices
des
exercices
. . . . . . .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ... ... 176
des
dictionnaires
Ajouter,
modifier,
6 - Aide6pour
les exercices
. . supprimer
. . . . . . . des
. . .enregistrements
. . . . . . . . . . . . . . . . 175
- Aide
pour
les exercices
. . . .. .
Table des matières
Table
Table des
des matières
matières
191
113
192
114
197
116
200
122
204
125
206
127
207
137
139
215
216
157
225
158
230
161
238
163
240
164
168
261
175
261
176
262
265
191
267
192
270
197
274
200
277
204
113
282
206
114
113
283
207
116
283
114
122
215
116
125
293
216
122
11
127
294
225
125
137
11
301
11
11
230
127
139
18
304
238
137
1111
309
24
240
139
157
1818
310
26
158
311
2424
261
157
31
161
26
26
261
158
32
163
3131
262
161
164
168
45
267
175
45
270
168
45
45
176
48
274
175
Chapitre A
Révisions
Dans ce premier chapitre, nous n’allons pas programmer de jeu, mais
revoir à la vitesse d’un grand 8 quelques exercices de base de programmation en Python. Si vous manipulez déjà avec aisance les tests, les boucles
et les manipulations de listes, vous pouvez directement passer au chapitre
suivant.
1 - Variables et fonctions
La console
Dans la zone console de Python, on peut saisir
des instructions qui seront exécutées immédiatement. >>>
Cette saisie se fait après les >>> que l’on appelle des 4>>>
chevrons. Cela est très pratique pour tester en di- 6
rect les fonctions que vous aurez programmées. Voici >>>
9
un rappel de quelques opérations possibles sur les nombres :
Opérateur
+, -, *
a/b
3+1
len("Python")
(15%4) ** 2
Effet
Respectivement la somme, la différence et le produit
Quotient décimal de a par b
a // b
a%b
Quotient entier de a par b
Reste entier dans la division de a par b
a ** b
Résultat de ab ( et non ˆ)
11
C HAPITRE A
✍ Souvenez-vous que dans la console, vous pouvez utiliser les touches
↑ et ↓ pour vous déplacer dans l’historique de ce que vous avez
déjà tapé au fur et à mesure.
Python respecte les priorités mathématiques des opérations. En informatique, ce concept porte le nom de précédence des opérateurs.
Dans le cas du langage Python, les précédences sont définies ainsi
(dans l’ordre croissant) :
+, *, /, //, %
+x, -x
**
Addition et soustraction
Multiplication, division, quotient et reste
Positif, négatif
Exponentiation
Les variables
Pour programmer nos jeux, il faudra conserver un certain nombre d’informations en mémoire ; on utilise alors des variables pour stocker les valeurs. Le fait de donner un nom à un calcul, un texte ou un autre objet
s’appelle l’affectation.
En Algorithmique
a←3
On lit ...
a reçoit la valeur 3
En Python
a = 3
Dans la console Python, lorsque l’on affecte une valeur à une variable,
le contenu de celle-ci n’est pas affiché. Il faut taper le nom de la variable
pour afficher sa valeur.
Lors d’une affectation, la quantité à droite du signe
= est évaluée et stockée dans la variable de gauche.
L’écriture a = a + 1 a donc un sens en informatique. Cela signifie simplement que la variable a est
augmentée de 1.
Le symbole = joue donc un rôle d’affectation et non
d’égalité. D’ailleurs, l’égalité b = a * 2 n’est plus
vérifiée à la fin de notre exemple.
12
>>>
>>>
>>>
6
>>>
4
>>>
3
>>>
>>>
4
>>>
6
a = 3
b = a * 2
b
a + 1
a
a = a + 1
a
b
R ÉVISIONS
Voici donc un petit exercice permettant de jouer avec les nombres et les
opérations avec Python...
E X A1 Si n est un entier positif, relier les expressions Python aux expressions
françaises correspondantes :
● Le chiffre des unités de n
● n + 1
● n // 10 % 10
● Le premier nombre impair qui suit n
● n // 2 * 2 + 1
● n ** 2
● Le nombre de dizaines de n
● n // 10
● n % 10
● n * 2
● Le chiffre des dizaines de n
● Le carré de n
● Le double de n
● L’entier qui suit n
Affectations simultanées
Commençons par un premier exercice pour nous faire une idée.
E X A2 On considère la suite d’instructions ci-contre :
1. Que contiennent les variables x et y à la fin ?
2. Constate-t-on le même phénomène pour des valeurs quelconques de x et y ?
>>>
>>>
>>>
>>>
>>>
x
y
x
y
x
=
=
=
=
=
17
32
x + y
x - y
x - y
3. Écrire une suite d’instructions produisant le même effet, mais sans effectuer
de calcul (on pourra utiliser une troisième variable temporaire).
Python propose une manière simple d’affecter simultanément des
variables en utilisant une virgule : les deux premières lignes de l’exercice précédent peuvent être réduites en x, y = 17, 32. Comme
pour l’affectation classique, lorsque l’on procède à une affectation
simultanée, les quantités de droite sont évaluées dans un premier
temps, puis dans un second temps elles sont stockées dans les variables de gauche. Ainsi les 3 dernières lignes de l’exercice précédent
peuvent tout simplement être transformées en : x, y = y, x.
13
C HAPITRE A
E X A3 La carte vitale.
En France, le numéro de sécurité sociale présent sur la carte vitale est
un numéro ”signifiant” (c’est-à-dire
non aléatoire) composé de 13 chiffres
permettant d’identifier une personne,
suivi d’une clé de contrôle de 2 chiffres
pour déceler d’éventuelles erreurs de
saisie.
Image par Giesesamvitale sur Wikipédia.
Par exemple, le premier chiffre indique le sexe (1 pour un homme, 2 pour une
femme), les deux suivants l’année de naissance, suivis de deux chiffres pour indiquer le mois et ainsi de suite. Pour la carte fictive ci-dessus, le code est donc
2690549588157 et la clé 80. Cette clé correspond à la différence entre 97 et le
reste de la division du code par 97. Réaliser une suite d’instructions qui, connaissant le code N à 13 chiffres, calcule la clé de sécurité.
Les types d’objets
Il existe de nombreux types d’objets en Python,
nous apprendrons même dans ce livre à créer
nos propres types ! En voici quelques-uns que
nous allons régulièrement utiliser.
Dans la console (ou plus tard dans un programme), on peut demander de quel type est
une variable à l’aide de la fonction type comme
le montre l’exemple de droite.
>>> n = 5
>>> type(n)
<class 'int'>
>>> type(2 ** 2020)
<class 'int'>
>>> type(1/2)
<class 'float'>
>>> A=(2,3)
>>> type(A)
<class 'tuple'>
>>> n < 1
False
>>> type(n < 1)
<class 'bool'>
● Les entiers : en Python, les entiers ont la particularité de ne pas être
limités en taille (contrairement à la majorité des autres langages).
● Les flottants : pour simplifier, on peut considérer que ce sont les
nombres décimaux ou même réels. En réalité ce n’est pas si simple, comme
l’illustre le 3e exemple.
>> type(3)
<class 'int'>
>>> type(3.0)
<class 'float'>
14
Pour indiquer à Python qu’un nombre entier est un flottant, on ajoute .0 ou juste un
point.
R ÉVISIONS
>>> a = 45 / 9
>>> a
5.0
>>> type(a)
<class 'float'>
Une division décimale renvoie toujours un
flottant même si le résultat est entier.
Enfin, il faut se méfier des ”erreurs
d’arrondis” avec les nombres flottants
même simples.
Ici le résultat affiché est environ −5,6.10−17. Nous n’irons pas plus
loin dans les explications mais il faut retenir qu’il peut se produire
des erreurs d’arrondis sur les nombres flottants et qu’il est préférable
d’utiliser des entiers, dans la mesure du possible bien entendu.
>>> 1 - 0.8 - 0.2
-5.551115123125783e-17
● Les tuples : ils représentent des couples ou triplets ou n-uplets. On
peut récupérer chacune des composantes via leurs indices (ces derniers
commencent à 0) ou directement par une affectation simultanée :
>>>
>>>
>>>
>>>
4
>>>
2
A = (4, 2, 5)
x = A[0]
y = A[1]
x
ou
y
>>>
>>>
>>>
4
>>>
2
A = (4, 2, 5)
x, y, z = A
x
y
● Les booléens : c’est le dernier type dont nous
parlerons rapidement. Ces variables ne peuvent
prendre que deux valeurs : True ou False (Vrai
ou Faux pour les non-anglophones !). Vous aurez
sûrement remarqué la majuscule devant ces deux
mots, pensez-y.
>>> b = 2 < 3
>>> b
True
>>> type(b)
<class 'bool'>
Les fonctions
Python propose déjà un certain nombre de fonctions déjà programmées
(on les appelle les fonctions built-in ou fonctions natives en français).
Nous avons déjà rencontré par exemple la fonction type : elle reçoit une
variable en entrée (un objet) et renvoie son type (sa classe). Dans nos
projets, nous utiliserons très souvent des fonctions : elles permettent de
séparer une tâche à réaliser en tâches plus simples et donnent ainsi de la
clarté au code.
15
C HAPITRE A
✍ L’exemple suivant calcule et renvoie la distance entre un personnage P(xP ; yP ) et un ennemi E(x
√ E ; yE ) à l’aide de la formule issue du
théorème de Pythagore, PE = (xE − xP )2 + (yE − yP )2 :
À noter que l’on a dû ici importer en première ligne la fonction racine carrée (sqrt) depuis le module math : il s’agit d’une bibliothèque
contenant un grand nombre de fonctions mathématiques.
La déclaration d’une fonction est ainsi structurée :
● La déclaration commence par le mot clef def suivi du nom de la
fonction (ici distance), puis des paramètres (ici P et E) et enfin
deux points pour déclarer le bloc correspondant à la fonction.
● Juste en dessous, un texte placé entre des triples guillemets qui
donne la description de la fonction. Ce texte appelé docstring est
facultatif. S’il est renseigné, il s’affiche en info-bulle lorsque l’on
tape le nom de la fonction, comme sur la capture précédente dans
la console. Ceci est très pratique pour retrouver l’ordre des paramètres dès lors que l’on a créé un grand nombre de fonctions.
● L’ensemble des instructions à réaliser dans la fonction est indenté,
c’est à dire décalé vers la droite pour structurer le programme.
● Enfin, dès que le programme rencontre l’instruction return, il quitte
la fonction et renvoie la valeur indiquée.
16
R ÉVISIONS
Quelques remarques supplémentaires sur les fonctions :
● Comme dans l’exemple précédent, une fonction peut dépendre de
plusieurs paramètres, il suffit de les lister en les séparant par une
virgule.
● Si vous définissez une fonction dont le nom existe déjà, seule la
dernière définition est prise en compte en écrasant la précédente.
● Une fois le programme exécuté, les fonctions et les variables en cours
sont accessibles dans la console comme on peut le voir sur la capture
d’écran précédente. Cependant, gardez en tête que les paramètres de
la fonction sont des variables muettes : les variables P et E ne sont
connues qu’à l’intérieur même de la fonction et vous pouvez très
bien appeler distance(A, (-1,4)) si A a été défini auparavant.
● De la même manière, toutes les nouvelles variables créées à l’intérieur
de la fonction sont appelées variables locales et n’existent qu’à
l’intérieur de la fonction. En particulier, elles n’interfèrent heureusement pas avec d’autres variables qui pourraient porter le même nom
dans le reste du programme. Voici ce qui se passe si on demande la
valeur de xP : elle n’est pas connue.
● Une fonction peut aussi renvoyer plusieurs valeurs, il suffit de les
séparer par une virgule au moment du return.
● Enfin, une fonction peut à son tour en appeler une autre. L’exemple
suivant propose de détecter une collision et renvoie un booléen indiquant si la distance PE est inférieure à 10.
def touche(P,E) :
return (distance(P,E) < 10)
>>> touche((10,2),(12,1))
True
17
C HAPITRE A
E X A4 On dispose à l’écran d’une grille de 8 × 8 cases, dont chaque case mesure
10 × 10 pixels.
1. Écrire une fonction xy2lc qui reçoit deux entiers x et y chacun entre 0
et 79 (correspondant par exemple aux coordonnées d’un clic de souris) et
retourne un couple indiquant l’indice de la ligne et de la colonne correspondant.
2. Utiliser la fonction précédente pour écrire à nouveau une fonction xy2num
qui retourne cette fois-ci le numéro de la case (entre 0 et 63).
x = 32
0
1
2
3
4
5
6
7
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
0
1
y = 18
×
2
3
4
5
6
7
2 - Tests et boucles
Les tests
Jusque-là nous nous sommes contentés de réaliser des programmes
très linéaires : les instructions s’enchaı̂nent toujours à l’identique. Nous
allons donc ajouter des tests à nos programmes de manière à ne pas effectuer systématiquement toutes les instructions.
18
R ÉVISIONS
Partons d’un exemple où l’on souhaite afficher un message en fonction
de l’âge du joueur placé dans une variable age. C’est d’ailleurs l’occasion
de présenter la fonction print qui affiche un texte dans la console (celui-ci
sera placé entre guillemets).
● SI... ALORS
age = ...
if age == 0 :
print("Peux-tu être sérieux 2 minutes ?")
print("Tout est pardonné...")
print("C'est parti !")
✍ Remarquez que le test d’égalité de deux variables se réalise
avec un double égal == (le simple = étant réservé à l’affectation). Remarquez aussi que toute la partie indenté (décalée) est
exécutée lorsque le test est validé. La dernière ligne quant à elle
est exécutée dans tous les cas. Bien évidemment, remplacez les
... par différentes valeurs pour faire les tests.
● SI... ALORS...SINON :
age = ...
if age >= 18 :
print("Vous êtes majeur")
print("Bienvenue")
else :
print("Désolé, vous ne pouvez jouer à ce jeu")
print("Fin du programme")
● SI... ALORS...SINON SI ... :
Si on souhaite distinguer 3 messages différents selon que l’utilisateur est majeur, qu’il a entre 13 et 18 ans ou moins de 13 ans, on peut
imbriquer les if dans les if ainsi :
age = ...
if age >= 18 :
print("Vous êtes majeur")
print("Bienvenue")
else :
if age >= 13 :
print("Vos parents doivent valider votre inscription")
else :
print("Désolé, vous ne pouvez jouer à ce jeu")
print("Fin du programme")
19
C HAPITRE A
Cependant, il est plus efficace d’utiliser l’instruction elif (contraction de else if) pour effectuer une disjonction de cas :
age = ...
if age >= 18 :
print("Vous êtes majeur")
print("Bienvenue")
elif age >= 13 :
print("Vos parents doivent valider votre inscription")
else :
print("Désolé, vous ne pouvez jouer à ce jeu")
print("Fin du programme")
✍ On peut enchaı̂ner autant d’instructions elif que nécessaire.
L’utilisation du else final n’est pas obligatoire si les autres cas
ne nécessitent aucune action supplémentaire. Dès qu’une des
conditions est validée, les autres ne sont pas testées et le programme continue la suite des instructions.
Pour les tests, on pourra utiliser les connecteurs logiques suivants :
Opérateur
A and B
Signification
Renvoie VRAI si les conditions A et B sont vérifiées.
A or B
Renvoie VRAI si au moins l’une des conditions A
ou B est vérifiée.
AˆB
Renvoie VRAI si exactement une des conditions A
ou B est vérifiée (mais pas les deux, on dit qu’il
s’agit d’un "ou exclusif").
not A
Renvoie le contraire de la véracité de la condition A.
E X A5 Écrire une fonction qui reçoit le numéro du mois et renvoie le nombre de
jours de celui-ci.
20
ÉVISIONS
RRÉVISIONS
sur les
les tests
tests :: Python
Python ininDernière remarque sur
...
les tests
tests de
de doubles
doubles xx == ...
terprète correctement les
if 22 <=
<= xx << 44 ::
if
cas, ilil traduit
traduit A
A << BB << C
C
inégalités. Dans ce cas,
print("Oui")
print("Oui")
else ::
C). Par
Par exemple
exemple le
le propro- else
par (A < B) et (B < C).
print("Non")
print("Non")
affiche "Oui"
"Oui" si
si et
et seulement
seulement gramme suivant affiche
si x ∈ [2; 4[.
E X A6 Un jeu d’échecs contient
contient 64
64 cases
cases (8
(8××8)
8);; celles-ci
celles-ci sont
sont repérées
repérées par
pardes
des
couples d’entiers (l,c) représentant
représentant respectivement
respectivement la
la ligne
ligne etet lala colonne
colonne(de
(de00àà
7). On suppose d’autre part qu’il
qu’il n’y
n’y aa qu’une
qu’une pièce
pièce sur
sur lele plateau.
plateau.
1. Écrire une fonction tour
tour qui
qui renvoie
renvoie un
unbooléen
booléenindiquant
indiquantsisileledéplacement
déplacement
une autre
autre case
case A
Aest
est possible
possible(les
(lestours
toursse
sedéplacent
déplacent
d’une tour de la case D àà une
verticalement ou
ou horizontalement).
horizontalement).
uniquement verticalement
2. Même question pour un
un fou.
fou.
3. Même question pour un
un cavalier.
cavalier.
Les boucles
Commençons par présenter
présenter les
les boucles
boucles conditionnelles
conditionnellescar
carce
cesont
sontcelcelles qui se comprennent le plus
plus facilement
facilement en
en français
français;; ce
ce sont
sontles
lesboucles
boucles
TANT QUE .... FAIRE : ... Cette
Cette instruction
instruction sera
sera traduite
traduite par
par while
while en
en
Python.
while cond : Exécute
Exécute une
une instruction
instructionou
ouun
unbloc
blocd’instructions
d’instructionstant
tant
que la condition cond est
est vérifiée.
vérifiée.
● Comme pour le if, l’ensemble
l’ensemble des
desinstructions
instructionsààexécuter
exécuterest
estindenté.
indenté.
● La boucle peut donc ne
ne jamais
jamais être
être exécutée
exécutée si
si d’entrée
d’entréela
lacondition
condition
n’est pas remplie.
E X A7 Qu’affiche cet algorithme
algorithme?? Le
Le programmer
programmer en
en Python.
Python.
n ← 11
TANT
TANT QUE
QUE nn ⩽⩽ 100
100 FAIRE
FAIRE ::
nn ←
← 22××nn
FIN TANT
TANT QUE
QUE
AFFICHER
AFFICHER n.
n.
21
21
C HAPITRE A
E X A8 ”Un homme met un couple de lapins dans un lieu isolé de tous les côtés
par un mur. Combien de couples obtient-on en un an si chaque couple engendre
tous les mois un nouveau couple à compter du troisième mois de son existence ?”
Tel est le problème proposé par Fibonacci dans un problème récréatif publié en
1202 dans son ouvrage ”Liber Abaci”.
Image wikipédia
Pour tout l’exercice, on suppose qu’aucun lapin ne meurt.
1. Combien y a-t-il de couples le 3e et le 4e mois ?
2. Réaliser une fonction qui renvoie le nombre de couples au nième mois.
3. Écrire un programme qui trouve à partir de quel mois il y a plus de 100
couples de lapins.
Boucle inconditionnelle POUR
Parfois, on connaı̂t à l’avance combien de fois on souhaite répéter la
boucle (par exemple tirer 3 cartes). Dans ce cas, on peut utiliser la commande for pour gagner du temps.
Boucle TANT QUE :
Boucle POUR :
n = 0
while n < 5:
...instructions...
n = n + 1
22
for n in range(5):
...instructions...
R ÉVISIONS
for var in range(debut,f in,pas):
Les paramètres debut et pas sont optionnels. Cela permet de réaliser
une boucle en faisant parcourir à la variable var tous les entiers :
● de l’intervalle [0; f in[ si un seul paramètre est renseigné ;
● de l’intervalle [debut; f in[ si 2 paramètres sont renseignés ;
● dans l’intervalle [debut; f in[ mais avec un pas de pas si les
3 paramètres sont renseignés.
À noter que :
● Comme pour le while, l’instruction se termine par deux points et
les instructions à répéter doivent être décalées.
● L’intervalle parcouru est semi-ouvert : for i in range (1,5),
la variable i prend successivement les valeurs 1, 2, 3, 4.
● En cas d’impossibilité, la boucle n’est pas exécutée. Par exemple dans
for n in range (100,110,-2):
● Contrairement à la boucle TANT QUE, la variable du compteur parcourt, quoiqu’il arrive, les valeurs demandées, on ne peut pas la modifier en cours de boucle. Le programme ci-dessous affiche bien les
10 premiers entiers de 0 à 9 :
for i in range(10):
print(i)
i = i * 2
Avec la boucle POUR, on peut parcourir d’autres choses que des suites
arithmétiques de nombres :
for i in (1, 4, 5, 0, 9, 1):
print(i)
for i in ("a", "e", "i", "o", "u", "y"):
print(i)
for lettre in "Python":
print(lettre)
● Des listes de nombres
ou d’autres objets. On
les met alors entre parenthèses ou entre cro chets.
● Une à une, les lettres
d’un mot...
23
C HAPITRE A
E X A9 Code ISBN - L’International Standard Book Number (ISBN) à 13 chiffres
est un numéro international qui permet d’identifier, de manière unique, chaque
édition de chaque livre publié, que son support soit numérique ou papier. Les 3
premiers chiffres sont 978, suivis de 9 chiffres. Le dernier chiffre est une clé obtenue par le programme de calcul suivant :
● Additionner les 12 chiffres du code
● Y ajouter le double de la somme des chiffres de rang pairs (2e , 4e , 6e , 8e ,
10e et 12e )
● Prendre le dernier chiffre de cette somme
● Calculer la différence entre 10 et le résultat obtenu.
● La clé est le chiffre des unités de ce dernier résultat.
1. Vérifier sans programme la clé sur l’exemple.
2. Écrire une fonction qui vérifie qu’un code
ISBN est correct.
3 - Chaı̂nes de caractères
Même si vous n’avez pas dans l’idée de programmer un jeu de mots
croisés, vous verrez qu’il est parfois utile d’avoir quelques notions sur les
chaı̂nes de caractères. Une chaı̂ne de caractères est donc, comme son nom
peut le laisser penser, une succession de caractères. Un caractère pouvant
être une lettre, mais aussi un chiffre, <, @, *, §, ... Pour simplifier on peut
considérer qu’un caractère est un symbole. Dans une chaı̂ne de caractères,
chaque caractère est indicé, Python commence la numérotation à 0.
Une chaı̂ne de caractères
U n
caractère U
d’indice 0
e x e m p l e .
caractère espace
d’indice 2
caractère .
d’indice 10
Dans un programme, il faut donc différencier le nom de la variable qui
contient une chaı̂ne de son contenu. Pour cela en Python, pour signifier
qu’il s’agit d’une chaı̂ne de caractères, ou d’un caractère, on met le texte
entre guillemets ’ainsi’ ou "ainsi".
24
R ÉVISIONS
Fonctions sur les chaı̂nes
Il existe de très nombreuses fonctions pour travailler sur les chaı̂nes de
caractères, nous n’en donnerons que quelques-unes :
Commande
len(ch)
Effet
Renvoie la longueur de la chaı̂ne ch.
ch[i]
Renvoie le caractère situé au rang i dans la
chaı̂ne ch. Attention :
● La première lettre est au rang 0.
● On ne peut pas modifier cette lettre
(ch[2]=’A’ renvoie une erreur).
mots[debut:f in]
Renvoie la partie de chaı̂ne mots comprise
entre le caractère à la position debut
(inclus) et celui à la position f in (exclu).
mots1+mots2
Colle deux chaı̂nes mots1 et mots2. Le verbe
utilisé est ≪ concaténer ≫.
Recopie nb fois la chaı̂ne mots.
mots*nb
str(nb)
int(ch)
for l in mot :
Renvoie une chaı̂ne de caractères contenant
l’écriture décimale du nombre nb.
Transforme la chaı̂ne ch en entier.
Réalise une boucle dans laquelle la variable l
va prendre tour à tour chaque lettre de mot.
E X A10 Un palindrome est un mot qui peut se lire aussi bien à l’endroit qu’à
l’envers comme ”kayak”. Écrire une fonction qui reçoit en entrée une chaı̂ne de
caractères et renvoie un booléen affichant si le texte est un palindrome.
E X A11 Écrire une fonction qui affiche les initiales d’une personne. Par exemple :
● initiales("Vincent Maille") → ’VM’
● initiales("Jean-Jacques Goldman") → ’JJG’
● initiales("Jean-Claude Van Damme") → ’JCVD’
E X A12 Écrire une fonction qui compte le nombre de mots dans une phrase
simple (on ne tient pas compte des mots composés, des apostrophes...).
25
C HAPITRE A
E X A13 Écrire une fonction qui renvoie la lettre qui apparaı̂t le plus souvent
dans un texte donné.
E X A14 Temps passé sur un jeu...
1. Écrire une fonction str2min qui transforme une chaı̂ne de caractères type
h:mm ou hh:mm en nombre de minutes que cela représente. Par exemple
str2min(’2:12’) renvoie 132.
2. Écrire une fonction min2str ayant l’effet inverse.
3. Créer une troisième qui calcule le temps de jeu entre deux instants t1 et t2
(maximum 24H !) donnés sous forme hh:mm
4 - Les listes et matrices
Les listes
Une liste peut être vue comme un tableau dont le nombre de cases
n’est pas limité. Le terme illimité est ici exagéré dans la mesure où cela
dépend néanmoins de votre machine. Sur mon ordinateur portable, j’ai pu
entrer dans une liste tous les entiers de 1 à un million (mais pas jusqu’à un
milliard) ce qui devrait largement nous suffire ! Comme pour les chaı̂nes
de caractères, dans une liste, le premier élément a pour indice 0.
Création et manipulation d’une liste :
Instruction
Effet
L=[]
Crée une liste vide L.
L=[e1,e2,...] Crée une liste L contenant les éléments e1, e2, ...
L[i]
L[i]=elem
len(L)
Renvoie le nombre d’éléments de la liste L.
e in L
Teste si l’élément e est dans la liste L et renvoie
True ou False (VRAI ou FAUX).
for e in L
26
Renvoie l’élément d’indice i de la liste (indice 0
pour le premier).
Stocke elem dans la liste L au rang i.
Réalise une boucle dans laquelle la variable e
prend tour à tour les valeurs des éléments
de la liste L.
R ÉVISIONS
Opérations qui agissent en modifiant une liste :
Méthode
Effet
L.append(e)
Ajoute l’élément e à la fin de la liste L.
L.insert(j ,e)
L.remove(e)
L.pop(i)
Insère l’élément e au rang j de la liste L.
Supprime la première occurrence de l’élément
e dans la liste L. Attention, si e n’est pas présent dans la liste, une erreur se produit.
Pensez donc à vérifier que e est présent avec
l’instruction in.
Supprime l’élément d’indice i de la liste L.
Fonctions qui agissent sur les listes sans les modifier :
Fonction
Effet
min(L), max(L) Renvoie le plus petit (le plus grand) élément
de la liste L.
sorted(L)
Renvoie une nouvelle liste contenant les éléments ordonnés de la liste L.
choice(L)
Choisit au hasard un élément de la liste L
(nécessite le module random).
E X A15 Dire ce qu’affichent les programmes suivants (ou s’ils déclenchent une
erreur) avant de les taper pour vérifier...
1.
2.
3.
P = [2, 4, 6, 8]
print(P[2])
P.append(3)
print(P[2])
P.remove(3)
print(P)
print(len(P))
f = []
for x in range(1, 6):
f.append(3 - (x-3) ** 2)
print(min(f), max(f))
L = []
for i in range(6):
L.append(i**2)
if i in L:
L.remove(i)
print(L)
4.
L = [1, 1]
for i in range(6):
print(L[0], end=' ; ')
L.append(L[0] + L[1])
L.pop(0)
27
C HAPITRE A
E X A16 Une vache qui pisse dans un tonneau... On cherche à simuler ce jeu
d’enfant. Écrire une fonction qui reçoit une liste de prénoms, l’indice du joueur
qui commence et le nombre de syllabes de la ritournelle. Elle affiche les personnes
qui perdent une à une et renvoie la personne qui reste à la fin. Par exemple avec
les prénoms Hugo, Lily, Mathilda, Pierre et Léa. Si Pierre commence et que la
ritournelle comporte 16 syllabes, on obtient le tableau suivant où l’on a indiqué
en gras celui par lequel on commence et souligné la personne éliminée :
●
Hugo
Lily
Mathilda
Pierre
●
Hugo
Lily
Mathilda
Léa Mathilda sort
●
Hugo
Lily
Léa Léa sort
●
Hugo
Lily Lily sort
Léa Pierre sort
● Hugo a gagné !
E X A17 On cherche à simuler le choix d’une main dans un jeu de poker simplifié
en piochant successivement, et sans remise, 5 cartes dans un jeu de 52 cartes. On
veut alors déterminer si on a un full, c’est-à-dire une main comportant 3 cartes de
même valeur et deux autres cartes de même valeur (mais distinctes de la première
valeur, sinon, vous avez triché !).
1. Créer une fonction qui génère et renvoie une liste jeu dont les éléments
représentent les 52 cartes du jeu. Pour notre problème, inutile de coder l’information sur la couleur de la carte. On créera juste une liste contenant
4 cartes de chaque valeur ’As’, ’2’, ’3’, ..., ’10’, ’Valet’, ’Dame’, ’Roi’.
2. Écrire une fonction main qui reçoit une liste correspondant au jeu de cartes
et retourne une main de 5 cartes choisies au hasard.
3. Écrire une fonction full qui indique par un booléen si la main, donnée
sous forme de liste en argument, est un full ou non.
Les listes sont des alias
Les listes présentent l’avantage de pouvoir stocker un très grand nombre de données, mais cela a un prix...Lorsque l’on écrit L=[1,2,3,4], la
variable L ne contient pas le contenu de la liste comme c’est le cas pour un
nombre, mais l’emplacement mémoire où se trouve la liste. On dit que la
variable L est un pointeur ou plus exactement un alias en Python.
28
R ÉVISIONS
L’exemple qui suit illustre le problème : lorsque l’on saisit en Python
L2=L1, on ne crée pas une copie de la liste, mais une copie des adresses
mémoires, ainsi lorsque l’on modifie L2, la liste L1 est aussi modifiée,
puisqu’il s’agit d’une seule et même liste. Pour éviter ce problème, on peut
créer un double de la liste en tapant L2=L1[:] qui recopie le contenu de
la liste L1 dans L2, lui affectant ainsi une adresse mémoire différente de
L1.
L1 = ["Bleu", "Blanc", "Rouge"]
L2 = L1
L2[1] = "Vert"
print("L1=", L1)
print("L2=", L2)
L1 = ["Bleu", "Blanc", "Rouge"]
L2 = L1[:]
L2[1] = "Vert"
print("L1=", L1)
print("L2=", L2)
Affiche :
Affiche :
L1= [’Bleu’,’Vert’,’Rouge’]
L2= [’Bleu’,’Vert’,’Rouge’]
L1= [’Bleu’,’Blanc’,’Rouge’]
L2= [’Bleu’,’Vert’,’Rouge’]
Capture réalisée via le site http ://pythontutor.com/
Autre subtilité liée aux alias : si vous avez utilisé le code ci-contre dans l’exercice précédent,
bien que la liste j ne soit pas renvoyée, celle-ci
est modifiée dans le reste du programme !
def main(j) :
m = []
for i in range(5) :
m.append(choice(j))
j.remove(m[-1])
return m
E X A18 [Mastermind] Le jeu de Mastermind est un jeu dans lequel il faut trouver une combinaison de 5 pions, chaque pion pouvant être choisi parmi 8 couleurs
(ici représentées par les nombres 0 à 7). Pour chaque proposition du joueur, le
maı̂tre du jeu lui répond le nombre de jetons bien placés et le nombre de jetons
mal placés. Écrire une fonction qui reçoit 2 listes : la proposition et la réponse et
renvoie sous forme d’un couple le nombre de jetons bien placés et mal placés. Par
exemple test([1,1,2,1,3],[1,7,3,2,1]) renvoie (1,3).
29
C HAPITRE A
Matrices
Terminons ces petits rappels avec les listes de listes appelées parfois
liste2D ou matrices :
La liste M définie ci-contre comporte 3 éléments :
M=[[13,
M[0] : [13, 5, 1, 7], M[1] : [11, 9,
[11,
4, 7] et M[2] : [12, 4, 5, 7]. Chaque [12,
élément étant à son tour une liste.
5,
9,
4,
1,
4,
5,
7],
7],
7]]
On peut ainsi :
● Connaı̂tre le nombre de lignes : len(M)
● Connaı̂tre le nombre de colonnes : len(M[0])
● Accéder à l’élément de la ligne l et colonne c : M[l][c]. Ce dernier étant accessible en lecture / écriture (il peut être modifié).
Voici quelques exercices, ceux qui en veulent davantage sur ce thème
pourront se reporter au chapitre N du tome 1.
E X A19 Le but du jeu Roller Splat est de repeindre un plateau de jeu à l’aide
d’une bille de peinture.
Lorsque l’on choisit une direction, la bille avance jusqu’à toucher un mur. Dans cet exercice, on modélise le
plateau de jeu par une matrice avec la convention suivante : 0 case vide, 1 : mur, 2 : case peinte. Par exemple,
le plateau ci-contre est modélisé par la déclaration :
plateau = [[1,
[1,
[1,
[1,
[1,
[1,
[1,
1,
1,
1,
2,
2,
2,
1,
1,
1,
1,
2,
1,
1,
1,
1,
0,
0,
2,
0,
0,
1,
1,
0,
1,
2,
1,
0,
1,
1,
0,
1,
2,
0,
0,
1,
1],
1],
1],
1],
1],
1],
1]]
À noter enfin que l’on peut repasser sur une case peinte
et que le plateau de jeu est toujours entouré de murs.
1. Écrire une fonction gagne qui reçoit un plateau et renvoie un booléen indiquant si la partie est gagnée ou non.
2. Écrire une fonction haut qui reçoit un couple (l, c) indiquant la position
de la bille et le plateau et renvoie la nouvelle position de la bille, une fois que
l’on a appuyé sur le flèche du haut. Le plateau est colorié en conséquence.
30
ÉVISIONS
RRÉVISIONS
E XX A20 [Puissance 4]
Pour ce jeu bien connu, le but est d’aligner 4 pions
pions
d’une même couleur dans une des 8 directions pospossibles. Comme pour le jeu précédent, on modélise
modélise le
le
plateau par une matrice composée de 0 (la case
case est
est
vide), de 1 (pion du joueur 1) et de 2.
1. Écrire une fonction gagne dir qui reçoit
reçoit 33 paramètres
paramètres :: lele plateau,
plateau, une
une
case sous la forme d’un couple (l, c) et
et une
une direction
direction ooùù regarder
regarder sous
sous lala
forme d’un couple (Δl, Δc). Elle renvoie
renvoie 11 si
si le
le joueur
joueur 11 gagne
gagne àà partir
partir
de cette case dans cette direction, 2 si c’est
c’est le
le joueur
joueur 22 et
et 00 dans
dans les
les autres
autres
cas. Par exemple gagne dir(p, (2,6),(1,-1))
(2,6),(1,-1))va
va tester
testersisiles
lescases
cases
(2,6), (3,5), (4,4) et (5,3) sont de la même
même couleur
couleur sur
sur le
le plateau
plateau p.
p.
2. Écrire une fonction gagne qui reçoit le
le plateau
plateau et
et renvoie
renvoie qui
qui aa gagné
gagné (ou
(ou
0 si personne n’a encore gagné).
5 - Aide pour les exercices
Aide pour l’exercice A1 Petit rappel sur un
un exemple
exemple :: pour
pour le
le nombre
nombre123,
123,
le chiffre des dizaines est 2 alors que le nombre
nombre de
de dizaines
dizaines est
est 12.
12.
Aide pour l’exercice A4 Prenez des exemples
exemples et
et pensez
pensez àà la
la division
divisioneuclieuclidienne.
Aide pour l’exercice A7 Il suffit d’exécuter
d’exécuter ce
ce programme
programme àà la
la main
main pour
pour
avoir la réponse.
Aide pour l’exercice A8 Pour la deuxième
deuxième question,
question, pensez
pensezqu’il
qu’il faut
fautconconserver en mémoire le nombre de couples des
des deux
deux mois
mois précédents
précédents pour
pour
trouver le nombre de couples du mois en cours.
cours.
Aide pour l’exercice A9 On rappelle que pour
pour un
un nombre
nombre xxdonné,
donné,on
onpeut
peut
trouver le chiffre des unités par le calcul du
du reste
reste de
de la
la division
division par
par 10.
10.
Aide pour l’exercice A11 On peut parcourir
parcourir le
le mot
mot et
et garder
garder en
en mémoire
mémoire
toutes les lettres qui se trouvent au début,
début, après
après un
un trait
trait d’union
d’union ou
ou après
après
un espace.
31
31
C HAPITRE A
Aide pour l’exercice A12 Le nombre d’espaces donne une bonne information sur le nombre de mots...
Aide pour l’exercice A13 Avec l’exercice précédent, on a vu comment
compter le nombre d’espaces. On peut facilement généraliser cela à n’importe quel caractère et créer une fonction à cet effet. La difficulté ensuite est
de conserver en mémoire le caractère qui apparaı̂t le plus fréquemment. Il
faudra 2 variables, l’une pour garder en mémoire la lettre, une autre pour
garder le nombre de fois où celle-ci apparaı̂t afin de pouvoir comparer
avec les nombre d’apparitions des suivantes.
Aide pour l’exercice A16 L’idée la plus simple est d’apprendre à l’ordinateur à jouer réellement : on avance de case en case et lorsque l’on arrive au
bout de la liste, on revient au début... Attention, quand un joueur est retiré, pensez à reculer d’une case, sinon cela aura le même effet qu’avancer
de 2 cases au tour suivant...
Aide pour l’exercice A17
2. Pensez à ôter du jeu la carte tirée...
3. Pour tester si on a un full ou non, il peut être intéressant d’ordonner
la liste pour ensuite tester si elle est de la forme AAABB ou AABBB.
Aide pour l’exercice A18 On peut par exemple chercher ceux qui sont bien
placés et les supprimer des listes pour ne pas les recompter dans les mal
placés.
6 - Solutions des exercices
Retour sur l’exercice A1
● Le chiffre des unités de n : n % 10
● Le premier nombre impair qui suit n : n // 2 * 2 + 1
● Le chiffre des dizaines n : n // 10 % 10
● Le nombre de dizaines de n : n // 10
● Le carré de n : n ** 2
● Le double de n : n * 2
● L’entier qui suit n : n + 1
32
R ÉVISIONS
R ÉVISIONS
Retour sur l’exercice A2
Retour sur l’exercice A2
1. À la fin, c’est x qui vaut 32 et y 17.
1.
la fin,
c’estvaleurs
x qui vaut
17.départ, le programme inverse ces
2. À
Pour
toutes
de x32etetyyde
deux
valeurs.
En
effet,
notons
x
y0 leslevaleurs
de départ
de xces
et
0 et
2. Pour toutes valeurs de x et y de
départ,
programme
inverse
y : valeurs. En effet, notons x et y les valeurs de départ de x et
deux
Ligne
3Ligne
43
54
Instruction
x
= x + y
Instruction
y
x =
= x
x + y
y
x
=
x
y
y = x - y
0
x
x
x0 + y0
x
x00 +
+ yy00
x
x0 +
+ yy0 − x0 = y0
0
0
y
y0
y
x
y00 + y0 − y0 = x0
x
x0 + y − y = x
0
0
0
0
5
x = x - y x0 + y0 − x0 = y0 x0
3. Autre alternative pour inverser deux variables :
t = x alternative pour inverser deux variables :
3. Autre
x
t
y
x
y
=
=
=
=
y
x
t
y
t
Retour sur l’exercice A3 Il suffit de reprendre les opérations indiquées :
Retour
l’exercice A3 Il suffit de reprendre les opérations indiquées :
>>> N = sur
2690549588157
>>>
>>>
>>>
80
>>>
80
cle = 97 - N % 97
N
= 2690549588157
cle
cle = 97 - N % 97
cle
Retour sur l’exercice A4
Retour sur l’exercice A4
1. Pour commencer, on peut remarquer que l’abscisse x va déterminer
numéro
de la colonne
l’ordonnée
celui de la
ligne.
D’autre
1. le
Pour
commencer,
on peutet
remarquer
quey l’abscisse
x va
déterminer
part,
la
colonne
0
est
composée
des
abscisses
de
0
à
9,
la
colonne
1
le numéro de la colonne et l’ordonnée y celui de la ligne. D’autre
des
de 0
10est
à 19
etc. Ainsides
uneabscisses
simple division
par 10
part,abscisses
la colonne
composée
de 0 à 9,entière
la colonne
1
permet
de
répondre
à
la
question.
Voici
le
code
:
des abscisses de 10 à 19 etc. Ainsi une simple division entière par 10
permet
de répondre
à la question. Voici le code :
def xy2lc(x,
y) :
def
return y // 10, x // 10
xy2lc(x, y) :
return y // 10, x // 10
2. Pour récupérer le numéro de la case, puisqu’une ligne comporte 8
il suffit d’effectuer
le de
calcul
: ligne
× 8 + colonne
: comporte 8
2. cases,
Pour récupérer
le numéro
la case,
puisqu’une
ligne
def xy2num(x,y)
:
cases,
il suffit d’effectuer
le calcul : ligne × 8 + colonne :
l, c = xy2lc(x, y)
def xy2num(x,y)
return l*8 +:c
l, c = xy2lc(x, y)
return l*8 + c
33
33
Solutions des exercices
0
y:
C HAPITRE A
Retour sur l’exercice A5 Les mois qui comportent 31 jours sont : 1-Janvier,
3-Mars, 5-Mai, 7-Juillet, 8-Août, 10-Octobre, 12-Décembre. Voici donc une
première idée :
def nbjours(n) :
if n==1 or n==3 or n==5 or n==7 or n==8 or n==10 or n==12:
return 31
elif n == 2 :
return 28
# Oui, ça peut être bissextile, je sais...
else :
return 30
On pourrait simplifier le premier test à l’aide de l’instruction in en :
if n in (1,3,5,7,8,10,12) :
ou, si vous aimez les maths :
if (n + n//8) % 2 == 1 :
Retour sur l’exercice A6
1. Il suffit que les cases A et D se situent à la même ligne ou à la même
colonne :
def tour(D, A) :
lD, cD = D
lA, cA = A
return (lD == lA) or (cD == cA)
2. Pour le fou, c’est la même idée, il faut que le décalage de ligne (Δl)
soit égal au décalage de colonne (Δc) éventuellement avec un changement de signe. Avec la fonction valeur absolue abs, on peut réaliser
la fonction :
def fou(D,
lD, cD
lA, cA
return
A) :
= D
= A
abs(lD-lA) == abs(cD-cA)
3. Pour le cavalier, c’est un peu plus compliqué.
Il faut que le déplacement absolu en ligne soit égal à 2 et que
celui en colonne fasse 1 ou le
contraire. Commençons par calculer ces quantités :
def cavalier(D, A) :
Dl = abs(A[0]-D[0])
Dc = abs(A[1]-D[1])
34
ÉVISIONS
RRÉVISIONS
tester ::
Ensuite on peut tester
2) and
and (Dl
(Dl ==1))
==1)) or
or ((Dc
((Dc ==
== 1)
1) and
and (Dl
(Dl ==2))
==2))
return ((Dc == 2)
simplifier ce
ce test,
test, on
on peut
peututiliser
utiliserun
uncouple
couplede
decouples
couples: :
Si on souhaite simplifier
in ((2,1),
((2,1), (1,2))
(1,2))
return (Dc, Dl) in
tenté de
de tester
tester si
si la
la somme
sommeΔΔll+Δc
+Δcvaut
vaut3,3,mais
maisalors
alors
On pourrait être tenté
les déplacements
déplacementsde
de33cases
casesen
enligne
lignedroite...
droite...Une
Une
on accepterait aussi les
fonctionne, est
est de
de regarder
regarder sisiΔl
Δl22++Δc
autre idée qui, elle, fonctionne,
Δc22==55: :cette
cette
fois, pour que la somme
somme de
de deux
deux carrés
carrés d’entiers
d’entiers naturels
naturels fasse
fasse 5,5,
l’un doit faire 2 et l’autre
l’autre 11 ::
Dl**
== 55
return Dc**2 + Dl
**22 ==
Retour sur l’exercice A7 n prend
prend successivement
successivement les
les valeurs
valeurs::11--22--44--88-16 - 32 - 64 - 128 (qui est la première
première àà dépasser
dépasser 100).
100). Ainsi
Ainsile
leprogramme
programme
affiche 128. En Python, on obtient
obtient ::
n = 1
while n <= 100:
n = 2 * n
print(n)
Retour sur l’exercice A8
1.
● Le premier mois,
mois, ilil yy aa un
un couple
couple de
de jeunes
jeuneslapins.
lapins.
● Le deuxième mois,
mois, ilil yy aa un
un couple
couple reproducteur.
reproducteur.
● Le troisième mois,
mois, ilil yy aa un
un couple
couple de
dejeunes
jeuneset
etun
uncouple
couplereproreproducteur, soit 2 couples.
couples.
● Le quatrième mois,
mois, ilil yy aa 22 couples
couples reproducteurs
reproducteurset
et11couple
couplede
de
jeunes, soit 3 couples.
couples.
2. En gardant la même idée
idée que
que dans
dans la
lapremière
premièrequestion,
question,on
ons’aperçoit
s’aperçoit
qu’il faut 2 variables, la
la première
première pour
pour dénombrer
dénombrerles
lesjeunes
jeunescouples
couples
de lapins, la seconde pour
pour dénombrer
dénombrer les
les couples
couples de
de lapins
lapins en
enâge
âge
de se reproduire.
35
35
Solutions des exercices
C
C HAPITRE
HAPITRE A
A
def couples(n) :
def couples(n) :
jeunes = 1
jeunes = 1
reproducteurs = 0
reproducteurs = 0
mois = 1
mois = 1
while mois < n:
while mois < n:
jeunes, reproducteurs = reproducteurs, reproducteurs + jeunes
jeunes, reproducteurs = reproducteurs, reproducteurs + jeunes
mois = mois + 1
mois = mois + 1
return jeunes+reproducteurs
return
jeunes+reproducteurs
3.
3. Pour
Pour déterminer
déterminer le
le nombre
nombre nécessaire
nécessaire de
de mois
mois permettant
permettant de
de
dépasser
100
couples,
on
peut
utiliser
la
fonction
précédente
dépasser 100 couples, on peut utiliser la fonction précédente ::
mois = 1
mois = 1
while couples(mois) < 100 :
while couples(mois) < 100 :
mois = mois + 1
mois = mois + 1
print(mois)
print(mois)
En
En réalité
réalité il
il aurait
aurait été
été plus
plus efficace
efficace de
de modifier
modifier la
la fonction
fonction précédente
précédente
pour
ne
pas
avoir
à
recalculer
tous
les
termes
à
chaque
pour ne pas avoir à recalculer tous les termes à chaque mois
mois ::
jeunes = 1
jeunes = 1
reproducteurs = 0
reproducteurs = 0
mois = 1
mois = 1
while jeunes + reproducteurs < 100:
while jeunes + reproducteurs < 100:
jeunes, reproducteurs = reproducteurs, reproducteurs + jeunes
jeunes, reproducteurs = reproducteurs, reproducteurs + jeunes
mois = mois + 1
mois = mois + 1
print("A partir du", mois, "ième mois, il y a plus de 100 couples")
print("A
partir du", mois, "ième mois, il y a plus de 100 couples")
Retour
Retour sur
sur l’exercice
l’exercice A9
A9
1.
1. Pour
Pour l’exemple
l’exemple donné,
donné, on
on peut
peut en
en effet
effet vérifier
vérifier :: 9
9+
+7
7+
+8
8+
+2
2+
+7
7+
+2
2+
+
9+8+7+7+8+2+2×(7+2+2+8+7+2)
=
132,
le
dernier
chiffre
étant
9+8+7+7+8+2+2×(7+2+2+8+7+2) = 132, le dernier chiffre étant
2,
2, on
on calcule
calcule 10
10 −
−2
2=
=8
8 qui
qui est
est la
la clé
clé puisque
puisque le
le résultat
résultat ne
ne comporte
comporte
qu’un
chiffre.
qu’un chiffre.
2.
2. On
On peut
peut commencer
commencer par
par remarquer
remarquer qu’en
qu’en réalité,
réalité, les
les chiffres
chiffres placés
placés
àà un
rang
pair
sont
comptés
3
fois
alors
que
ceux
situés
un rang pair sont comptés 3 fois alors que ceux situés àà un
un rang
rang
impair
ne
le
sont
qu’une
fois.
On
va
donc
ajouter
les
chiffres
impair ne le sont qu’une fois. On va donc ajouter les chiffres des
des
unités
unités un
un àà un
un en
en les
les comptant
comptant une
une fois
fois ou
ou trois
trois selon
selon leur
leur place
place
dans
dans le
le code.
code. À
À chaque
chaque passage
passage dans
dans la
la boucle,
boucle, on
on divisera
divisera par
par 10
10
(quotient
entier)
le
code
de
manière
à
retirer
le
dernier
chiffre.
(quotient entier) le code de manière à retirer le dernier chiffre. Sur
Sur
notre
notre exemple,
exemple, cela
cela donnerait
donnerait ::
36
36
R ÉVISIONS
ÉVISIONS
R
rang
rang %2
%2
00
11
00
11
00
11
00
11
00
11
00
11
chiffres
chiffres
978272987782
978272987782
97827298778
97827298778
9782729877
9782729877
978272987
978272987
97827298
97827298
9782729
9782729
978272
978272
97827
97827
9782
9782
978
978
97
97
99
cle2
cle2
0
+
0+3
3×
×2
2=
=6
6
6
+
8
=
14
6 + 8 = 14
14
14 +
+3
3×
×7
7=
= 35
35
35 +
+7
7=
= 42
42
35
42
42 +
+3
3×
×8
8=
= 66
66
66
+
9
=
66 + 9 = 75
75
75 +
+3
3×
×2
2=
= 81
81
75
81
+
7
=
88
81 + 7 = 88
88 +
+3
3×
×2
2=
= 94
94
88
94
+
8
=
102
94 + 8 = 102
102
102 +
+3
3×
×7
7=
= 123
123
123 +
+9
9=
= 132
132
123
Solutions des exercices
rang
rang
12
12
11
11
10
10
99
88
77
66
55
44
33
22
11
Que
Que l’on
l’on peut
peut programmer
programmer ainsi
ainsi ::
def
def ISBN(n)
ISBN(n) :
:
""" En
En entrée
entrée le
le code
code est
est donné
donné sous
sous forme
forme d'un
d'un entier
entier """
"""
"""
chiffres
chiffres =
= n
n //
// 10
10
cle1
cle1 =
= n
n %
% 10
10
cle2 =
= 0
0
cle2
for
for rang
rang in
in range(12):
range(12):
if
if rang
rang %
% 2
2 ==
== 0:
0: #
# Rang
Rang pair
pair ?
?
cle2
=
cle2
+
3
(chiffres
*
cle2 = cle2 + 3 * (chiffres %
% 10)
10)
else:
else:
cle2 =
= cle2
cle2 +
+ chiffres
chiffres %
% 10
10
cle2
chiffres
chiffres =
= chiffres
chiffres //
// 10
10
cle2
cle2 =
= (10
(10 - (cle2
(cle2 %
% 10)
10) %
% 10)
10)
return cle1
cle1 ==
== cle2
cle2
return
Retour
Retour sur
sur l’exercice
l’exercice A10
A10 Le
Le plus
plus simple
simple est
est ssûrement
ûrement de
de parcourir
parcourir le
le mot
mot
jusqu’au milieu
milieu et
et de
de regarder
regarder si
si le
le caractère
caractère présent
présent est
est le
le même
même que
que le
le cacajusqu’au
ractère
miroir.
On
utilise
alors
à
bon
escient
le
fait
que
dès
que
l’instruction
ractère miroir. On utilise alors à bon escient le fait que dès que l’instruction
return
return est
est rencontrée,
rencontrée, on
on sort
sort de
de la
la fonction
fonction ::
def palindrome(mot)
:
def
palindrome(mot) :
L
L =
= len(mot)
len(mot)
for
for i
i in
in range(L//2)
range(L//2) :
:
if
mot[i]
if mot[i] !=
!= mot[L-1-i]
mot[L-1-i] :
:
return
return False
False
return True
True
return
Retour
Retour sur
sur l’exercice
l’exercice A11
A11 Voici
Voici une
une solution
solution qui
qui parcourt
parcourt le
le nom
nom àà partir
partir
de la
la lettre
lettre d’indice
d’indice 11 et
et regarde
regarde àà chaque
chaque fois
fois si
si le
le caractère
caractère précédent
précédent est
est
de
un
espace
ou
un
trait
d’union.
un espace ou un trait d’union.
37
37
C HAPITRE A
def initiales(nom) :
reponse = nom[0]
# Il faut au moins prendre la première lettre
for i in range(1, len(nom)):
if nom[i-1] == ' ' or nom[i-1] == '-':
reponse = reponse + nom[i]
return reponse
Retour sur l’exercice A12 Voici une solution qui compte le nombre d’espaces (+1), ce qui doit être assez proche du nombre de mots :
def nb_mots(phrase) :
nbM = 1
for lettre in phrase:
if lettre == ' ':
nbM = nbM + 1
return nbM
Retour sur l’exercice A13 En s’appuyant sur la remarque donnée en indication, on utilise une variable fois qui compte combien de fois le caractère lettre apparaı̂t dans la phrase. À chaque fois que l’on obtient un
meilleur score, on mémorise ce nombre dans foisMaxi ainsi que la lettre
en question dans lettreMaxi :
def nb_fois(phrase, car) :
nbM = 1
for lettre in phrase:
if lettre == car :
nbM = nbM + 1
return nbM
def lettre_maxi(phrase) :
foisMaxi = 0
for lettre in phrase:
if lettre != " " :
# On ne compte pas les espaces
fois = nb_fois(phrase, lettre)
if fois > foisMaxi:
lettreMaxi = lettre
foisMaxi = fois
return lettreMaxi
38
R ÉVISIONS
Retour sur l’exercice A14
1. Pour cette fonction, il n’y a pas de difficulté, il faut simplement gérer
les cas où l’heure comporte 1 ou 2 chiffres :
2. Pour le problème inverse, pas de difficulté non plus, il faut juste
faire attention à ajouter un ”0” si le nombre de minutes ne comporte
qu’un chiffre :
def min2str(minutes):
h = minutes // 60
m = minutes % 60
if m < 10:
return str(h)+":0"+str(m)
else:
return str(h)+":"+str(m)
Remarque : au lieu d’écrire en deux lignes h = minutes // 60 et
m = minutes % 60, on peut écrire en une ligne à l’aide de la fonction divmod de cette manière : h, m = divmod(minutes, 60).
3. Pour la dernière fonction, on utilise les deux fonctions précédemment créées. Si l’heure d’arrivée est inférieure à l’heure de départ, il
faut ajouter une journée :
def duree(t1, t2) :
temps = str2min(t2) - str2min(t1)
if temps <= 0:
temps = temps + 24*60
return min2str(temps)
Retour sur l’exercice A15
1.
instruction
P = [2, 4, 6, 8]
print(P[2])
P.append(3)
print(P[2])
P.remove(3)
print(P)
print(len(P))
affichage
6
6
[2,4,6,8]
4
P
[2,4,6,8]
6
[2,4, ,8]
[2,4,6,8,2]
6
[2,4, ,8,3]
[2,4,6,8]
[2,4,6,8]
[2,4,6,8]
39
Solutions des exercices
def str2min(txt):
if txt[1] == ':':
# Si c'est au format h:mm
return int(txt[0])*60 + int(txt[2]+txt[3])
else:
return int(txt[0]+txt[1])*60 + int(txt[3]+txt[4])
C HAPITRE A
C HAPITRE A
x
2.
2.
3.
3.
4.
4.
40
40
1
x
2
3
1
4
2
5
3
4
5
f
[]
[-1]
f
[-1,2]
[]
[-1,2,3]
[-1]
[-1,2,3,2]
[-1,2]
[-1,2,3,2,-1]
[-1,2,3]
[-1,2,3,2]
[-1,2,3,2,-1]
3
2
3
1
2
−1
1
−1
1
2
3
4
5
1
2
3
4
5
−2
−1
−1
Finalement, le programme affiche
-1 (pour le minimum) et 3 pour le
−2
maximum. Remarquons qu’ici ces 2 valeurs sont bien le maximum
et le minimum de f sur l’intervalle [1,5] car les extrémums sont atFinalement, le programme affiche -1 (pour le minimum) et 3 pour le
teints pour des valeurs entières ce qui n’est évidemment pas toujours
maximum. Remarquons qu’ici ces 2 valeurs sont bien le maximum
le cas ...
et le minimum de f sur l’intervalle [1,5] car les extrémums sont ati Lpour des valeurs
i ∈ L? entières
L
teints
ce qui n’est évidemment pas toujours
0
[0]
OUI
[]
le cas ...
1i [1]
OUI
L
i ∈ L? []
L
2 [4]
NON [4]
0 [0]
OUI
[]
3 [4, 9]
NON [4,9]
1 [1]
OUI
[]
4 [4, 9, 16]
OUI
[9, 16]
2 [4]
NON [4]
5 [9, 16, 25] NON [9, 16, 25]
3 [4, 9]
NON [4,9]
À4la fin
la boucle,
affiche [9,16,25].
[4, de
9, 16]
OUIle programme
[9, 16]
5 [9, 16, 25] NON [9, 16, 25]
print...
L.append...
L.pop...affiche [9,16,25].
Ài la fin
de la boucle,
le programme
1
0
1
[,1,2]
[1,2]
1
1i print...
1
[,2,3]
[2,3]
L.append...
L.pop...
2
2
2
[,3,5]
[3,5]
1
0
1
[,1,2]
[1,2]
3
3
3
[,5,8]
[5,8]
1
1
1
[,2,3]
[2,3]
5
4
5
[,8,13]
[8,13]
2
2
2
[,3,5]
[3,5]
8
5
8
[,13,21]
[13,21]
3
3
3
[,5,8]
[5,8]
Le
affiche
”1 ; 1 ; [8,13]
2 ; 3 ; 5 ; 8 ;” qui sont en réalité les pre5
4 programme
5
[,8,13]
miers
termes
de[,13,21]
8la suite de Fibonacci
5
8
[13,21] définie par u0 = u1 = 1 et pour
n ∈ N, un+2 = un+1 +un que l’on retrouve dans de nombreux problèmes
Le programme
affiche ”1 ; 1 ; 2 ; 3 ; 5 ; 8 ;” qui sont en réalité les pred’évolution (souvenez-vous des lapins !)
miers termes de la suite de Fibonacci définie par u0 = u1 = 1 et pour
n ∈ N, un+2 = un+1 +un que l’on retrouve dans de nombreux problèmes
d’évolution (souvenez-vous des lapins !)
R ÉVISIONS
Retour sur l’exercice A16 Cette fonction convient (attention aux indices !) :
def vache(L, pos, syllabe) :
pos = pos - 1
n = len(L) - 1
for i in range(n):
for avance in range(syllabe):
pos = pos + 1
if pos == len(L):
pos = 0
print(L[pos], "sort du jeu")
L.pop(pos)
pos = pos - 1
return L[0]
Pour optimiser le programme on peut remarquer que si on avance de
16 et s’il y a 5 personnes, cela revient finalement à avancer de 1. En effet
16 = 5×3+1, on a donc fait 3 tours et encore une personne. 1 n’est autre que
le reste de la division de 16 par 5. Ainsi, la fonction devient plus simple :
def vache(L, pos, syllabe) :
pos = pos - 1
n = len(L) - 1
for i in range(n):
pos = (pos + syllabe) % len(L)
print(L[pos], "sort du jeu")
L.pop(pos)
pos = pos - 1
return L[0]
Pour information, ce problème mathématique connu sous le nom de
Tragédie de Flavius Josèphe n’a pas de solution explicite connue. C’està-dire que, connaissant une collection (x1 ,...,xn ) de valeurs, un indice i0
de départ et un nombre T de syllabes, il n’existe pas à ce jour de formule
donnant l’indice du gagnant en fonction de n,i0 et T.
Retour sur l’exercice A17
1. Le plus rapide est sûrement d’utiliser *4 pour recopier 4 fois la liste.
Pour la liste initiale, on peut soit la saisir à la main, soit la saisir en
compréhension ainsi :
def jeu() :
liste = [str(i) for i in range(2,11)]+['Valet','Dame','Roi','As']
return liste*4
2. Pour tirer une main, on va répéter 5 fois : tirer une carte, l’ajouter à
la liste m, la retirer de la liste du jeu :
41
Solutions des exercices
C HAPITRE A
from random import choice
def main(j) :
m = []
for i in range(5) :
m.append(choice(j))
j.remove(m[-1])
return m
Ici vous remarquerez l’utilisation de m[-1] pour signifier le dernier
élément de m. Enfin, notons que l’on aurait pu utiliser la fonction
sample(L,k) du module random qui a le même effet : choisir k valeurs d’une liste L sans remise :
from random import *
m = sample(jeu, 5)
3. Pour tester s’il s’agit d’un full, nous allons appliquer l’idée proposée
en aide :
def full(m) :
L = sorted(m)
return L[0]==L[1] and L[3]==L[4] and (L[2]==L[1] or L[2]==L[3])
Retour sur l’exercice A18 Pour compter les pions bien placés, on va mettre
en place la solution proposée en aide. Pour cette boucle, on ne peut pas
utiliser un for puisque la taille de la liste évolue au fur et à mesure des
suppressions. Pour les pions mal placés, même principe, mais on peut
cette fois utiliser une boucle for pour parcourir la proposition et on supprime dans la réponse donnée au fur et à mesure qu’un pion a été identifié
comme mal placé pour ne pas le compter encore une fois :
def test(prop,rep) :
p, r = prop[:], rep[:] # On recopie
# les bien placés
bp, i = 0, 0
while i < len(p) :
if p[i] == r[i] :
r.pop(i)
p.pop(i)
bp= bp + 1
else :
i = i + 1
# Les mal placés :
mp = 0
for j in p :
if j in r :
r.remove(j)
mp = mp + 1
return bp, mp
42
R ÉVISIONS
Retour sur l’exercice A19
1. Pour tester si l’on a gagné, on peut parcourir le tableau et dès que
l’on trouve un 0, on renvoie False. Si à la fin du parcours, on est
encore dans la fonction, c’est qu’il n’y avait pas de 0 ; on renvoie
alors True. Voici une solution où on fait la boucle sur les indices :
une seconde, un peu plus légère, où on travaille sur les éléments :
def gagne(p) :
for ligne in p :
for case in ligne :
if case == 0 :
return False
return True
2. Pas de difficulté pour cette deuxième question, à partir de la case
donnée, on remonte jusqu’à atteindre une case marquée 1 :
def haut(case, p) :
l, c = case
while p[l-1][c] != 1 :
l = l - 1
p[l][c] = 2
return l, c
Noter qu’ici on peut regarder la case p[l-1][c] sans risque de
débordement puisque le terrain est toujours entouré de murs.
Retour sur l’exercice A20
1. Il suffit de se déplacer 3 fois dans la direction demandée, si on sort
du plateau ou si on rencontre un pion d’une autre couleur, on quitte
la fonction en renvoyant 0 :
43
Solutions des exercices
def gagne(p) :
nbl, nbc = len(p), len(p[0])
for l in range(nbl) :
for c in range(nbc) :
if p[l][c] == 0 :
return False
return True
C HAPITRE A
def gagne_dir(p, case, direction) :
l, c = case
Dl, Dc = direction
j = p[l][c]
for i in range(1,4) : # 3 cases suivantes
l, c = l + Dl, c + Dc
if l < 0 or l >= 6 or c < 0 or c >= 7 : # On déborde
return 0
if p[l][c] != j : # Si c'est une autre couleur
return 0
return j
On aurait pu regrouper les deux if en un, mais cela ne change pas
grand chose et c’est peut-être plus clair ainsi.
2. On lance un parcours ”brutal” sur toutes les cases et dans toutes
les directions. Dès que l’on rencontre une situation gagnante, on
s’arrête :
def gagne(p) :
for l in range(6) :
for c in range(7) :
for Dl in range(-1,2) :
for Dc in range(-1,2) :
if (Dl, Dc) != (0,0) : # La direction
g = gagne_dir(p, (l,c), (Dl,Dc)) #
if g != 0 : return g
return 0 # Personne n'a gagné
(0,0) n'existe
pas
Cela fait 6 × 7 × 8 = 336 appels à la fonction gagne dir ce qui est
raisonnable (pour cet exercice).
44
Chapitre B
Objectif
Programmer un jeu de pong
Programmation
Découvrir l’interface pygame
Utiliser un dictionnaire informatique
Algorithmique
Réaliser une intelligence artificielle simple
Mathématiques
Trigonométrie
Jeu de Pong
Votre première mission – si vous l’acceptez – va être de programmer
un jeu de Pong. Pour ceux, trop jeunes, qui ne connaissent pas ce jeu
sorti dans les années 70, il s’agit d’un jeu vidéo inspiré du tennis de table
développé par Ralph Baer et son équipe chez Sanders Associates. Le jeu
sera rapidement repris par la société Atari pour en faire un gros succès
populaire.
1 - Interface et variables
Pour ce premier projet, l’interface graphique du jeu a déjà été programmée avec le module pygame que nous allons découvrir tout au long
de ce livre. Vous pouvez télécharger cette interface en tapant sur le site
45
C HAPITRE B
du livre le code PONG et ainsi découvrir une vidéo du projet final. La
création de l’interface graphique et la gestion du clavier seront repris au
chapitre suivant et nous allons pour le moment nous concentrer sur la partie algorithmique.
centre.png
droite.png
gauche.png
haut.png
Pour vous aider dans vos calculs durant le projet, vous pouvez vous
appuyer sur ce plan de l’écran (disponible au format PDF pour impression
sur le site) :
0
100
130
570
600
0
46
100
900
1000
J EU
DE
P ONG
Le terrain de jeu est découpé en 4 images. Les différents éléments graphiques seront affichés dans cet ordre : haut, centre, la balle, les deux raquettes et en dernier les buts (gauche et droit). Ainsi lorsque la balle entrera sur l’un des cotés, elle ”disparaı̂tra” dans le but.
Voici un extrait du fichier pong.py que vous pouvez exécuter :
import pygame
from pygame.locals import *
def afficheScore(s1, s2):
....
def enfoncee(key):
....
def relachee(key):
....
pygame.init()
fenetre = pygame.display.set_mode((1000,600))
# Déclaration des variables globales
continuer = True
Touches = []
# Chargement des images
haut = pygame.image.load("haut.png").convert()
fenetre.blit(haut, (0,0))
gauche = pygame.image.load("gauche.png").convert()
fenetre.blit(gauche, (0,100))
terrain = pygame.image.load("centre.png").convert()
fenetre.blit(terrain, (100,100))
droite = pygame.image.load("droite.png").convert()
fenetre.blit(droite, (900,100))
balle = pygame.image.load("balle.png").convert_alpha()
P1 = pygame.image.load("R1.png").convert_alpha()
P2 = pygame.image.load("R2.png").convert_alpha()
clock = pygame.time.Clock()
while continuer:
clock.tick(100)
for event in pygame.event.get():
if event.type == QUIT :
continuer = False
elif event.type == KEYDOWN :
enfoncee(event.key)
elif event.type == KEYUP :
relachee(event.key)
# ici on réalise l'animation
# Mise à jour de l'écran
pygame.display.flip()
print('Fin')
pygame.quit()
47
C HAPITRE B
2 - Utilisation des dictionnaires
Avant de se lancer de but en blanc dans le projet, prenons le temps
de découvrir une nouvelle structure de données. Au niveau des variables,
nous avons besoin :
● pour la balle :
◇ de son abscisse, de son ordonnée
◇ de sa vitesse (composantes horizontale et verticale)
● pour le joueur 1 (et pour le joueur 2) :
◇ de la hauteur (ordonnée) de la raquette
◇ de son score
Une première idée pourrait donc être d’utiliser des variables distinctes
pour toutes ces quantités. Nous allons plutôt ici utiliser des dictionnaires
pour regrouper ces informations. Cela nous servira aussi de première approche de la programmation objet. Pour la balle, une liste [100, 50,
-2, 3] pourrait permettre de définir en une seule variable la position et
la vitesse de la balle, mais rapidement la programmation va être illisible,
il faudra se souvenir de ce que représente la variable à chaque indice.
Un dictionnaire est une collection d’objets pouvant être de natures
différentes. Il permet de repérer les éléments par des noms (on parle de
clés) plutôt que par leurs indices, ce qui permet de se repérer plus simplement. La déclaration de la balle pourra alors se faire ainsi :
balle = {'X': 100, 'Y':50, 'VX':-2, 'VY':3 }
dé
VY
pla
ce
m
en
t
VX
48
(X, Y)
16
J EU
DE
P ONG
Accès aux éléments
La déclaration d’un dictionnaire (on parle aussi en informatique de tableau associatif) ressemble donc beaucoup à celle d’une liste : on déclare
les éléments entre accolades et on précise cette fois le nom de la clé à laquelle chacun est associé. Voici un tableau donnant quelques commandes
pour manipuler les dictionnaires :
Commande
dict={}
Effet
Crée un dictionnaire vide et le nomme dict.
dict[cle]
Renvoie l’élément associé à la clé cle du
dictionnaire dict. Si cette clé n’existe pas,
une erreur est déclenchée.
dict[cle]=v
del(dict[cle])
v=d.pop(cle)
d2=d1.copy()
len(dict)
cle in dict
Modifie l’élément du dictionnaire dict associé à
la clé cle en lui assignant la valeur v. Si la clé
n’existe pas encore, elle est créée à ce moment.
Efface la clé cle (et sa valeur) du dictionnaire
dict.
Même effet que del, mais renvoie la valeur
avant de l’effacer.
Crée une copie (indépendante) du dictionnaire
d1 et la stocke dans le dictionnaire d2.
Renvoie le nombre d’enregistrements dans le
dictionnaire dict.
Renvoie un booléen indiquant si cle est une clé
du dictionnaire dict.
E X B1 Écrire une fonction lettres qui reçoit en entrée une chaı̂ne de
caractères et renvoie un dictionnaire dont les clés sont les lettres du mot et les
valeurs sont le nombre d’occurrences.
>>> lettres('Bonjour')
{'B': 1, 'j': 1, 'n': 1, 'o': 2, 'r': 1, 'u': 1}
49
C HAPITRE B
✍
● À noter qu’il n’y a pas de notion d’ordre dans un dictionnaire.
● Vous trouverez peut-être des exemples sur des sites web utilisant la
méthode has key() pour tester l’existence d’une clé. Cette
méthode n’existe plus en Python 3.
● Comme pour les listes, saisir d2 = dict ne crée pas une copie du
dictionnaire dict, mais donne simplement un nouveau nom au
même objet, comme l’illustre l’exemple ci-dessous :
>>> scores = {'Hugo':1, 'Lily':3}
>>> sc2 = scores
>>> sc2['Mathilda'] = 2
>>> sc2
{'Hugo': 1, 'Lily': 3, 'Mathilda': 2}
>>> scores
{'Hugo': 1, 'Lily': 3, 'Mathilda': 2}
>>> scores = {'Hugo':1, 'Lily':3}
>>> sc2 = scores.copy()
>>> sc2['Mathilda'] = 2
>>> sc2
{'Hugo': 1, 'Lily': 3, 'Mathilda': 2}
>>> scores
{'Hugo': 1, 'Lily': 3}
Clés d’un dictionnaire
Nous avons présenté des dictionnaires dans lesquels les clés étaient
des textes, en réalité cela peut être tout type d’objets non mutables, c’est
le cas par exemple pour des nombres, des chaı̂nes et des tuples formés de
nombres et de chaı̂nes. Par exemple :
● Des couples :
G = { ('A',4):"Porte-Avion", \
('B',4):"Porte-Avion", \
('C',4):"Porte-Avion", \
('D',4):"Porte-Avion", \
('E',4):"Porte-Avion" }
G['D',4] = 'Touché'
for k in G :
if G[k] == 'Porte-Avion' :
print('Il reste du porte-avion en',k)
50
J EU
DE
P ONG
● Des nombres (attention cependant le comportement n’est pas celui
d’une liste, si on supprime l’élément 2, les indices passent de 1 à 3) :
>>>
>>>
{0:
>>>
>>>
{0:
D = { 0 : 'zéro', 1:'un', 2 : 'deux', 3: 'trois'}
D
'zéro', 1: 'un', 2: 'deux', 3: 'trois'}
del D[2]
D
'zéro', 1: 'un', 3: 'trois'}
E X B2 En mathématiques, on a le théorème suivant :
Tout entier naturel n ⩾ 2 se décompose de manière unique sous la forme
n = p1α1 ×p2α2 ×...×pkαk où les pi sont des nombres premiers distincts classés
par ordre croissant et les αi des entiers naturels non nuls.
Par exemple 12 = 22 ×31 et 98000 = 24 ×53 ×72. Écrire une fonction decompose
qui renvoie cette décomposition sous forme d’un dictionnaire :
>>> decompose(12)
{2: 2, 3: 1}
>>> decompose(98000)
{2: 4, 5: 3, 7: 2}
Parcours d’un dictionnaire
Pour parcourir les éléments d’un dictionnaire, on dispose de trois
méthodes pour générer des itérateurs qui peuvent être ensuite appelés dans une boucle for :
Commande
d.keys()
Effet
Renvoie la liste des clés du dictionnaire d.
d.values()
Renvoie la liste des valeurs du dictionnaire d.
d.items()
Renvoie la liste des couples (clés, valeurs) de d.
Par exemple FRUITS :
decoupe = {'pasteques':3, 'ananas':2, 'oranges':4}
for fruit in decoupe.keys() :
print('Vous avez coupé des', fruit)
print('--------')
total = 0
for quantite in decoupe.values() :
total = total + quantite
print('Vous avez coupé', total, 'fruits.')
print('--------')
for (fruit,quantite) in decoupe.items() :
print('Vous avez coupé', quantite, fruit)
51
C HAPITRE B
Vous avez
Vous avez
Vous avez
-------Vous avez
-------Vous avez
Vous avez
Vous avez
coupé des oranges
coupé des ananas
coupé des pasteques
coupé 9 fruits.
coupé 4 oranges
coupé 2 ananas
coupé 3 pasteques
✍ La première boucle for peut aussi être simplement remplacée par
for fruit in decoupe :
E X B3 Renseigner un dictionnaire dont les clés sont les numéros des 12 mois
de l’année et les valeurs le nombre de jours qu’ils comportent. Réaliser alors une
fonction jour(date) qui reçoit une date sous forme de texte au format ′ j j /mm′
et renvoie le numéro du jour de l’année. Par exemple jour(’14/03’) doit
renvoyer 73.
E X B4 En tapant sur le site du livre le code de l’exercice, on peut télécharger
un début de programme contenant un dictionnaire où les clés sont des noms de
joueurs et les valeurs leurs scores à un jeu (il n’y a pas d’ex-æquo).
scores = {'Léo':2441, 'Gabriel':2991, 'Adam':3993, 'Timéo':5554, \
'Raphaël':2012, 'Lucas':4617, 'Arthur':8709, 'Nathan':3601, \
...
'Adèle':9844, 'Rose':8778, 'Mila':7709}
Écrire un programme permettant d’afficher :
1. le meilleur score,
2. le prénom du joueur ayant le meilleur score,
3. la liste ordonnée des prénoms des 5 meilleurs joueurs.
Comme une liste, un dictionnaire peut contenir un très grand nombre
d’enregistrements. La taille n’est pas limitée par Python mais seulement
par la mémoire de votre ordinateur. L’exercice qui suit en est un exemple.
52
J EU
DE
P ONG
E X B5 À partir des données sur le site https://www.data.gouv.fr,
on a récupéré dans un fichier texte (disponible sur le site
du livre) la liste de toutes les communes de France ainsi
que leurs codes postaux séparés par un point-virgule.
1. Écrire un programme qui lit le fichier texte et
stocke ces informations dans un dictionnaire. La
clé sera le nom de la ville et la valeur sera le code
postal de celle-ci.
2. Utiliser ce programme pour connaı̂tre
a) le nombre de villes en France,
b) les villes dont le code postal est 80540,
c) le nombre de villes de la Somme (département 80).
E X B6 On a représenté ci-contre une situation de jeu d’échecs où il n’y a que des
pièces noires (tour ou fou) sous forme d’un dictionnaire. Les clés sont des couples
sous la forme (ligne,colonne) et les valeurs le type de pièce sur cette case.
plateau = {(1,5):'fou', (4,2):'tour',
(4,8):'tour', (8,5):'fou'}
On rappelle que la tour ne peut se déplacer
que horizontalement ou verticalement et le fou
qu’en diagonale et d’autant de cases qu’ils le
souhaitent.
1. Écrire une fonction deplacement(c1,c2) qui reçoit deux cases sous
forme d’un couple et renvoie un booléen indiquant si ce déplacement est autorisé ou non. S’il n’y a pas de pièce sur la case départ, la fonction renverra
False, sinon, on tiendra compte du type de pièce sur la case c1. On ne
tient pas compte des autres pièces pour le moment.
>>> deplacement((1,4), (2,5))
False
>>> deplacement((4,2),(3,1))
False
>>> deplacement((4,2),(4,6))
True
2. Écrire une fonction piece(c1,c2) qui, pour un déplacement autorisé de
la case c1 vers la case c2, renvoie un booléen indiquant la présence d’une
autre pièce sur le chemin.
>>> piece((4,2),(4,7))
False
>>> piece((4,2),(4,8))
True
53
C HAPITRE B
3 - Retour à notre projet
Nous allons donc définir notre balle comme un dictionnaire contenant :
● les coordonnées de son centre (’X’, ’Y’)
● la largeur et la hauteur (’L’, ’H’)
● les coordonnées de son vecteur vitesse
(’VX’, ’VY’)
dé
VY
pla
ce
m
en
t
● l’image associée que l’on attachera à la
clé ’sprite’
VX
16
Ainsi, toutes les informations concernant la balle seront placées dans
une seule variable balle, on va donc remplacer la ligne :
balle = pygame.image.load("balle.png").convert_alpha()
par :
balle = { 'X':300, 'Y':300, 'VX':0, 'VY':0, 'L':32, 'H':32}
balle['sprite'] = pygame.image.load("balle.png").convert_alpha()
E X B7 Faire de même en initialisant deux dictionnaires J1 et
J2 pour représenter les joueurs 1 (à gauche) et 2 (à droite). On
indiquera les clés X, Y, L et H pour les coordonnées du centre,
la largeur et la hauteur ainsi que sprite pour l’image de la
raquette.
100
4 - Animation et gestion des rebonds
16
Dans le code du projet commencé, le programme réalise une boucle
à l’aide du while continuer de laquelle on sort lorsque l’on ferme la
fenêtre. L’intégralité de l’animation se fera donc au niveau de la ligne 32
entre les deux commentaires :
# ici on réalise l'animation
# Mise à jour de l'écran
pygame.display.flip()
Ces lignes réalisent donc la boucle principale du jeu, vous aller les
compléter au fur et à mesure des exercices.
54
J EU
DE
P ONG
E X B8 Écrire une fonction place(D) qui place le sprite du dictionnaire D
aux coordonnées indiquées par les clés X et Y de ce dictionnaire. Pour dessiner
une image sur le terrain, on utilisera la méthode blit qui sera présentée en détail
dans le chapitre suivant :
fenetre.blit(image, (x,y))
Cette dernière permet de déposer une image au point de coordonnées (x,y). Attention, ce point représente le coin en haut à gauche de l’image, il faudra donc
tenir compte de sa largeur et de sa hauteur pour la recentrer.
E X B9 Ajouter quelques lignes à la boucle principale pour afficher la balle et les
raquettes et pour qu’à chaque tour, les coordonnées de la balle soient augmentées
de celles du vecteur vitesse (on ne s’intéresse pas aux rebonds pour le moment).
Si vous avez réalisé l’exercice précédent et que vous étiez habitués à
Tkinter, vous serez sûrement surpris par le fait que la balle ne soit pas
déplacée à proprement parler mais laisse derrière elle une traı̂née. Il faut
donc à chaque tour de boucle que l’on dessine à nouveau l’intégralité du
fond via la ligne (on pourra améliorer par la suite)
fenetre.blit(terrain, (100,100))
E X B10 À présent, gérer les rebonds :
1. sur les bords haut et bas, la balle rebondit ;
2. lorsqu’elle frappe un but, la balle revient au centre et part dans la direction
opposée.
5 - Déplacement des raquettes
Dans le projet téléchargé, les événements clavier sont déjà surveillés
grâce aux lignes suivantes (tout ceci sera détaillé au prochain chapitre) :
for event in pygame.event.get():
if event.type == QUIT :
continuer = False
elif event.type == KEYDOWN :
enfoncee(event.key)
elif event.type == KEYUP :
relachee(event.key)
55
C HAPITRE B
Ainsi, lorsqu’une touche est enfoncée, la fonction suivante ajoute à la
liste Touches le code de la touche en question :
def enfoncee(key):
"""
Fonction qui est déclenchée lorsqu'on appuie sur
une touche. On met à jour la liste Touches
"""
if not key in Touches:
Touches.append(key)
De même, lorsque la touche est relâchée, la fonction suivante retire le
code correspondant :
def relachee(key):
"""
Fonction qui est déclenchée lorsqu'on relâche une
touche. On met à jour la liste Touches
"""
if key in Touches:
Touches.remove(key)
Ainsi, on peut tester si la touche X est enfoncée via l’instruction :
if K_x in Touches :
...
Comme les déplacements des deux joueurs vont être assez similaires
et que les noms des clés sont identiques, on peut éviter de refaire deux fois
la même chose.
E X B11 Créer deux fonctions raquetteUP(J) et raquetteDOWN(J) qui
auront pour effet de baisser ou augmenter les valeurs de la clé Y du dictionnaire
J représentant l’un des joueurs.
E X B12 Utiliser les deux fonctions de l’exercice précédent pour déplacer la ra-
quette de gauche avec les touches S et X et celle de droite avec les touches
directionnelles ↑ et ↓ . Les codes des touches X, S, haut et bas sont respectivement K x, K s, K UP et K DOWN.
Pour le moment, les raquettes peuvent sortir du terrain, mais l’intérêt
d’avoir programmé les déplacements des deux joueurs dans une fonction
est que la modification peut se faire en une seule fois.
E X B13 Modifier les fonctions raquetteUP et raquetteDOWN pour maı̂triser
les raquettes à l’intérieur du terrain.
56
J EU
DE
P ONG
6 - Buts et scores
Il nous reste encore à gérer les rebonds lorsque la balle touche une
raquette.
E X B14 Réaliser cette mission (un petit schéma sera peut-être le bienvenu).
Dans le projet commencé, vous disposez déjà d’une fonction nommée
afficheScore recevant en paramètres deux entiers représentant les
scores des deux joueurs et qui affiche ces scores à l’écran.
E X B15 Dans notre jeu, chaque but marqué contre l’adversaire rapporte 1 point.
Ajouter les lignes nécessaires pour que les scores apparaissent à l’écran.
7 - Amélioration des rebonds
À présent le projet est jouable, cependant après quelques parties, on
reste sur sa faim car il n’est pas possible d’avoir de stratégie pour gagner.
On va donc améliorer la gestion des rebonds en tenant compte de la position de l’impact sur la raquette.
Pour cette partie, nous allons avoir besoin de quelques notions de trigonométrie et du module math dont voici quelques fonctions :
fonction
sqrt(x)
Description
√
Renvoie x.
cos(a)
sin(a)
tan(a)
Représentent les 3 fonctions trigonométriques
cosinus, sinus et tangente. Attention, le nombre
a représente une mesure d’angle en radians.
degrees(x)
radians(x)
Permet de convertir en degrés un angle en radians,
et inversement.
57
C HAPITRE B
fonction
asin(x)
Description
π π
Renvoie l’unique nombre de l’intervalle [− ; ]
2 2
dont le sinus vaut x.
1
√
x = 23
−1
π
3
1
−1
Pour que ce soit plus simple, nous travaillerons en degrés, et nous
convertirons en radians pour les calculs.
Partons du cas le plus simple où les composantes VX et VY de la vitesse sont positives. Nous noterons α l’angle d’arrivée sur la normale à la
raquette et β l’angle après rebond. Jusque là nous avions α = β. L’idée va
être d’ajouter à α un angle en fonction de la position de la balle sur la raquette. Ici on choisit d’ajouter (en degrés) la distance (en pixels) séparant
le centre de la raquette et celui de la balle. Les 3 cas qui suivent illustrent
1
cette idée où la vitesse est v⃗ ( ).
2
58
J EU
cas 1
cas 2
DE
P ONG
cas 3
α
B
J2
β
α
β
J2
B
B
α
β
BY = J2Y
β = α ≈ 63○
BY = J2Y + 15
β = α + 15○ ≈ 78○
BY = J2Y − 30
β = α − 30○ ≈ 33○
E X B16 Programmer une fonction rebond(x,y,e) qui reçoit les composantes
(x, y) (positives pour le moment) du vecteur vitesse et l’écart e entre l’impact et
le centre de la raquette et renvoie le nouveau vecteur vitesse après rebond. Pour
vérification sur les 3 cas présentés :
>>> rebond(1,2,0)
(-1.0000000000000002, 2.0)
>>> rebond(1,2,15)
(-0.44828773608402667, 2.1906706976806576)
>>> rebond(1,2,-30)
(-1.8660254037844388, 1.2320508075688772)
E X B17 Adapter la fonction précédente pour qu’elle fonctionne avec une vitesse
x
v⃗ ( ) où x > 0 et y < 0 (collision avec la raquette 2). Par symétrie, on aura :
y
>>> rebond(1,-2,0)
(-1.0000000000000002, -2.0)
>>> rebond(1,-2,-15)
(-0.44828773608402667, -2.1906706976806576)
>>> rebond(1,-2,30)
(-1.8660254037844388, -1.2320508075688772)
E X B18 Réaliser une version finale où la fonction rebond fonctionne avec les
deux raquettes et l’incorporer au jeu.
59
C HAPITRE B
E X B19 Pour éviter les échanges interminables, améliorer une dernière fois le
projet, pour qu’à chaque rebond sur la raquette, la vitesse de la balle augmente de
10%.
8 - Un peu d’intelligence artificielle...
Pour ce premier projet, nous allons utiliser une intelligence artificielle
assez basique sur le joueur 2 afin de pouvoir jouer à un seul joueur :
● Lorsque la balle part vers le joueur 1, la machine replace la raquette
au milieu du terrain puisqu’elle ne sait pas où l’adversaire va la renvoyer.
● Lorsque la balle arrive vers lui, l’ordinateur tente de se rapprocher
au mieux de l’ordonnée de la balle.
E X B20 Programmer cette première solution.
Après quelques parties, on s’aperçoit que l’ordinateur est un piètre
adversaire, en particulier lorsque la balle accélère car il suit la balle et,
contrairement à nous, n’anticipe pas la position d’impact.
E X B21 Écrire une fonction arrivee(b) qui reçoit un dictionnaire représentant la balle sur le terrain et retourne l’ordonnée du point de collision prévu avec la
raquette 2. L’incorporer au projet pour programmer un adversaire plus redoutable.
Cette fois-ci on tombe dans l’excès inverse : la machine est invincible.
Une solution souvent utilisée est alors de limiter le nombre de coups anticipés, ce qui permet de régler le niveau du joueur (ici un coup représente
un pas de balle).
E X B22 Réaliser cette dernière amélioration et faire en sorte qu’à chaque point
marqué par l’humain, le nombre de coups anticipés par la machine augmente de 2
et, dans le cas inverse, diminue de 2 pour équilibrer la partie.
N’hésitez pas à aller voir sur le site le résultat final du projet.
9 - Aide pour les exercices
Aide pour l’exercice B4 Les deux premières questions ne devraient pas
poser trop de problèmes. Pour la dernière, on pourra utiliser une liste des
5 meilleurs scores et utiliser la méthode sort pour l’ordonner.
60
J EU
DE
P ONG
Aide pour l’exercice B5 Si vous avez oublié comment lire un fichier en Python, consultez le tomme 1, chapitre E.
Aide pour l’exercice B14
Xballe = X J2 − 24
Xballe = X J2 − 8
Yballe = YJ2 − 50
Parfois une figure vaut mieux qu’un long discours. On a représenté par 4 points noirs les
positions limites des centres de la balle lorsqu’elle entre en contact avec la raquette et avec
une croix le centre de la raquette. On peut donc
déterminer les encadrements à tester. Ainsi dès
que le centre de la balle entre dans la zone hachurée, elle doit rebondir.
×
100
Yballe = YJ2 + 50
16
Aide pour l’exercice B15 On peut :
● soit découper le test d’origine
if balle['X'] > 900 + 16 or balle['X'] < 100 - 16 :
en deux tests pour donner le point au joueur 1 ou 2
● soit conserver ce test et regarder le signe de l’abscisse du vecteur
vitesse de la balle pour savoir qui a marqué.
Aide pour l’exercice B21 Inutile de chercher à faire compliqué, une boucle
TANT QUE simulant l’avancée de la balle fera l’affaire.
61
C HAPITRE B
10 -
Solutions des exercices
Retour sur l’exercice B1 Pour ce premier exercice, on va créer un dictionnaire vide puis parcourir la chaı̂ne donnée lettre par lettre. La première fois
que l’on rencontre une lettre, on crée la clé correspondante et on initialise
la valeur à 1. Si on a déjà rencontré cette lettre, on augmente le compteur
de 1 :
def lettres(mot) :
reponse = {}
for l in mot :
if l in reponse :
reponse[l] += 1
else :
reponse[l] = 1
return reponse
La ligne reponse[l]+=1 est équivalente à reponse[l]=reponse[l]+1
dans notre cas. Personnellement, je préfère de loin la seconde notation
Retour sur l’exercice B2 On peut s’inspirer de l’exercice précédent. On va
commencer par diviser par 2 jusqu’à ne plus pouvoir, puis par 3, et ainsi
de suite...
def decompose(n) :
decomp = {}
diviseur = 2
while n > 1 :
if n % diviseur == 0 :
decomp[diviseur] = 1
n = n // diviseur
while n % diviseur == 0 :
n = n // diviseur
decomp[diviseur] += 1
diviseur = diviseur + 1
return decomp
Remarquons quand même que l’on teste aussi des diviseurs non premiers,
ce qui n’est pas gênant dans la mesure où ceux-ci ne peuvent plus être diviseurs (si on ne peut plus diviser par 2, ni par 3, on ne pourra pas diviser
par 6). Si vous trouvez cet exercice trop mathématiques, ne vous inquiétez
pas, nous n’en aurons pas besoin pour la suite.
62
J EU
DE
P ONG
Retour sur l’exercice B3 La solution présentée ne devrait pas poser de difficulté, d’ailleurs un dictionnaire n’est pas vraiment nécessaire ici :
mois = {1:31, 2:28, 3:31, 4:30, 5:31, 6: 30,
\
7:31, 8:31, 9:30, 10:31, 11:30, 12:31}
def jour(date) :
jj, mm = int(date[:2]), int(date[3:])
nbJours = 0
for i in range(1,mm) :
nbJours += mois[i]
return nbJours + jj
1. On va utiliser une variable best pour enregistrer le meilleur score
au fur et à mesure du parcours du dictionnaire :
best = 0
for s in scores.values() :
if s > best :
best = s
print('Le meilleur score est', best)
2. Il faut une seconde variable pour mémoriser le prénom du joueur à
chaque fois que le meilleur score est mis à jour :
best = 0
for nom, s in scores.items() :
if s > best :
best = s
bestJoueur = nom
print("C'est", bestJoueur, "qui a le meilleur score (", best, ")")
3. En utilisant une liste pour mémoriser les 5 meilleurs scores, on peut
procéder ainsi :
listeBest = [0] * 5
for s in scores.values() :
if s > min(listeBest) :
listeBest.remove(min(listeBest))
listeBest.append(s)
listeBest.sort()
print('Classement des 5 meilleurs')
for s in listeBest :
for nom in scores :
if scores[nom] == s :
print(nom, 'avec' ,s ,'points')
63
Solutions des exercices
Retour sur l’exercice B4
C HAPITRE B
On peut améliorer le programme précédent en conservant dans la
liste le couple (score,nom). Attention l’ordre du couple est important
car lors du tri de la liste, ce sont les scores qui seront comparés et à
score égal (ce qui n’arrive pas ici), les noms :
listeBest = [(0,0)] * 5
for nom, s in scores.items() :
if s > listeBest[0][0] :
listeBest.pop(0)
listeBest.append((s,nom))
listeBest.sort()
print('Classement des 5 meilleurs')
for s, nom in listeBest :
print(nom,'avec', s, 'points')
Avec la méthode proposée, on trie à nouveau la liste à chaque insertion. On pourrait profiter du fait que la liste est déjà ordonnée pour
insérer le nouveau score au bon endroit pour être plus efficace.
Retour sur l’exercice B5
1. Pour lire les informations, une première solution est de lire une à
une les lignes du fichier, de repérer la position du point-virgule et de
découper la chaı̂ne en deux :
f = open('codes_postaux.txt','r')
d = {}
for ligne in f :
i = ligne.index(';')
ville, code = ligne[:i], ligne[i+1:-1]
d[ville] = int(code)
f.close()
Remarque : si on met code=ligne[i+1:] à la place de code=
ligne[i+1:-1], il subsiste un \n de fin de chaı̂ne sous Windows
(voir page 42 du tome 1).
Une autre solution plus rapide est d’utiliser la méthode split d’une
chaı̂ne de caractères :
● ch.split(sep) : renvoie une liste dont les éléments sont
les morceaux de la chaı̂ne de caractères ch, la coupure ayant
effet sur le séparateur sep.
● sep.join(liste) : Renvoie une chaı̂ne où les éléments de
la liste (de textes) sont concaténés par le séparateur sep.
64
J EU
DE
P ONG
On obtient une solution plus lisible :
f = open('codes_postaux.txt','r')
d = {}
for ligne in f :
liste = ligne.split(';')
ville, code = liste[0], liste[1]
d[ville] = int(code[:-1])
f.close()
f = open('codes_postaux.txt','r')
d = {ligne[:ligne.index(';')]:int(ligne[ligne.index(';')+1:-1]) ⤦
� for ligne in f}
f.close()
Vous pouvez encore utiliser cette proposition de wiztricks sur le forum du site de programmation www.developpez.net :
d = { nom:int(code) for nom, code in (l.split(';') for l in f ) }
2.
a) Pour obtenir le nombre de villes :
print("le nombre de villes est", len(d))
b) Pour obtenir les villes dont le code postal est 80540 avec une
boucle :
for nom,code in d.items() :
if code == 80540 :
print(nom)
ou en une seule ligne :
print(" / ".join([n for n,c in d.items() if c == 80540]))
c) Pour connaı̂tre les villes de la Somme, on peut convertir le code
postal en chaı̂ne de caractères puis supprimer les 3 derniers caractères pour tester s’il reste "80" :
for nom,code in d.items() :
if str(code)[:-3] == '80' :
print(nom)
Où regarder si le quotient entier du code postal par 1000 est 80,
ce qui évite les conversions de types.
print(" / ".join([n for n,c in d.items() if c//1000 == 80]))
65
Solutions des exercices
Encore plus court : on réalise une déclaration du dictionnaire en
compréhension :
C HAPITRE B
Retour sur l’exercice B6
1. Voici une première solution : si c’est une tour, on s’assure que la case
de départ et celle d’arrivée ont même abscisse ou même ordonnée.
���→
Si c’est un fou, on peut calculer les coordonnées du vecteur c2c1 et
vérifier que ses deux composantes sont égales en valeur absolue :
def deplacement(c1, c2) :
if c1 not in plateau :
return False
if plateau[c1] == 'tour' :
if c1[0] == c2[0] or c1[1] == c2[1] :
return True
else :
return False
if plateau[c1] == 'fou' :
if abs(c1[0]-c2[0]) == abs(c1[1]-c2[1]) :
return True
else :
return False
On peut, comme souvent, raccourcir, puisque l’expression
c1[0] == c2[0] or c1[1] == c2[1] est déjà un booléen.
def deplacement(c1, c2) :
if c1 not in plateau :
return False
if plateau[c1] == 'tour' :
return (c1[0] == c2[0] or c1[1] == c2[1])
if plateau[c1] == 'fou' :
return (abs(c1[0]-c2[0]) == abs(c1[1]-c2[1]) )
2. La seconde question est un peu plus ardue. Il faut parcourir toutes
les cases depuis celle de départ jusqu’à celle d’arrivée et regarder si
une pièce se trouve dessus. Une idée peut être de s’appuyer sur la
constatation illustrée ci-dessous : si A et B sont deux points du plan,
��→
�→
le point M défini par AM = t AB parcourt l’intégralité du segment
[AB] lorsque t parcourt l’intervalle [0,1].
A
M
t = 0.3
B
x −x
x = xA + t (xB − xA )
xM − xA
) = t ( B A ) soit { M
yM = yA + t (yB − yA )
yM − yA
yB − yA
Par analogie, si c1 est la case de départ et c2 celle d’arrivée, on va
parcourir toutes les cases entre c1 et c2 en calculant les coordonnées
c1 + t× (c2-c1) avec t ∈ { n1 , 2n ,..., nn } et n le nombre de cases que l’on
souhaite parcourir.
Ainsi, on a (
66
J EU
DE
P ONG
def piece(c1,c2):
x1, y1 = c1
x2, y2 = c2
if plateau[c1] == 'tour' :
n = abs((x2-x1) + (y2-y1)) #l'un des deux vaut 0
else :
n = abs(x2-x1)
for i in range(1, n+1) :
if (x1 + i*(x2-x1)//n, y1 + i*(y2-y1)//n) in plateau :
return True
return False
J1 = { 'X':110, 'Y':350, 'L':16, 'H':100, 'score':0}
J1['sprite'] = pygame.image.load("R1.png").convert_alpha()
J2 = { 'X':890, 'Y':350, 'L':16, 'H':100, 'score':0}
J2['sprite'] = pygame.image.load("R2.png").convert_alpha()
Retour sur l’exercice B8 Avec les clés ainsi définies, on obtient le code :
def place(D) :
"""
Cette fonction place le 'sprite' du dictionnaire D
aux coordonnées (X,Y).
"""
fenetre.blit(D['sprite'], (D['X']-D['L']//2,D['Y']-D['H']//2))
Vous remarquerez l’intérêt d’avoir utilisé les mêmes clés pour la balle et
les raquettes : la même fonction permet de dessiner n’importe lequel des
3 sprites du jeu.
Retour sur l’exercice B9 Les deux dernières lignes permettent d’effectuer
VX
une translation de vecteur v⃗ ( ), il faut aussi afficher la balle :
VY
place(balle)
place(J1)
place(J2)
# Mise à jour des coordonnées
balle['X'] = balle['X'] + balle['VX']
balle['Y'] = balle['Y'] + balle['VY']
...évidemment, la balle ne bougera pas si sa vitesse est initialisée à 0 ...
Retour sur l’exercice B10 Il est préférable de tester les collisions sur les
bords avec des inégalités plutôt que des égalités, dans la mesure où rien
n’assure que les coordonnées resteront entières.
67
Solutions des exercices
Retour sur l’exercice B7 On peut faire ainsi :
C HAPITRE B
1. Pour les rebonds haut/bas, on peut gérer ainsi :
# rebond du haut :
if balle['Y'] < 130 + 16 : # limite du terrain + rayon de la balle
balle['VY'] = -balle['VY']
# rebond du bas :
if balle['Y'] > 570 - 16 : # limite du terrain - rayon de la balle
balle['VY'] = -balle['VY']
2. Et pour les entrées dans le but (on peut gérer les deux de la même
manière pour le moment) :
# but gauche / droit :
if balle['X'] > 900 + 16 or balle['X'] < 100 - 16:
balle['VX'] = -balle['VX']
balle['VY'] = -balle['VY']
balle['X'] = 500
balle['Y'] = 350
Retour sur l’exercice B11 On peut augmenter ou diminuer la hauteur de
la raquette de un pixel ou plus si on veut une raquette plus rapide :
def raquetteUP(J) :
J['Y'] = J['Y'] - 1
def raquetteDOWN(J) :
J['Y'] = J['Y'] + 1
Retour sur l’exercice B12 Tout est déjà programmé, il ne reste plus qu’à
assembler les morceaux :
if
if
if
if
K_s in Touches : raquetteUP(J1)
K_x in Touches : raquetteDOWN(J1)
K_UP in Touches : raquetteUP(J2)
K_DOWN in Touches : raquetteDOWN(J2)
Retour sur l’exercice B13 On ne va ajouter ou retirer 1 que lorsque les ordonnées sont dans l’intervalle correspondant au terrain :
def raquetteUP(J) :
if J['Y'] > 130 + J['H']//2 :
J['Y'] = J['Y'] - 1
# Limite haute du terrain + 1/2 de la
# hauteur de la raquette
def raquetteDOWN(J) :
if J['Y'] < 570 - J['H']//2 :
J['Y'] = J['Y'] + 1
# Limite basse du terrain - 1/2 de la
# hauteur de la raquette
68
J EU
DE
P ONG
J EU DE P ONG
Retour sur l’exercice B14 En s’appuyant sur la figure donnée
il
J EUen
DEaide,
P ONG
suffit, une fois les encadrements déterminés, de prendre l’opposé de l’absRetour sur l’exercice B14 En s’appuyant sur la figure donnée en aide, il
cisse du vecteur vitesse pour faire rebondir la balle :
suffit, une
les encadrements
déterminés,
l’opposé
l’absRetour
sur fois
l’exercice
B14 En s’appuyant
surde
la prendre
figure donnée
endeaide,
il
cisse
du
vecteur
vitesse
pour
faire
rebondir
la
balle
:
suffit,
unela
fois
les encadrements
déterminés,
de prendre l’opposé de l’abs# Lorsque
balle
touche la raquette
1 :
if J1['X']+8
<= balle['X']
<= faire
J1['X']+24
andla\ balle :
cisse
du vecteur
vitesse pour
rebondir
J1['Y']+50 :
J1['Y']+50 :
J1['Y']+50 :
J2['Y']+50 :
J2['Y']+50 :
J2['Y']+50 :
Retour sur l’exercice B15 En appliquant la deuxième idée proposée dans
l’aide :
Retour sur l’exercice B15 En appliquant la deuxième idée proposée dans
l’aide
: sur l’exercice
Retour
En appliquant la deuxième idée proposée dans
# but gauche
/ droit B15
:
if balle['X']
> 900 + 16 or balle['X'] < 100 - 16 :
l’aide
:
ifgauche
balle['VX']
> :
0 :
# but
/ droit
J1['score']
1 or balle['X'] < 100 - 16 :
if balle['X']
> 900 +=
+ 16
# but
gauche
/ droit :
else
:
if
balle['VX']
> 0 :
if balle['X']
> 900 +=
+ 16
J2['score']
1 or balle['X'] < 100 - 16 :
J1['score']
+= 1
if balle['VX']
> 0 :
balle['VX']
= -balle['VX']
else
:
J1['score']
+= 1
balle['VY']
= -balle['VY']
J2['score']
+= 1
else :
balle['X']
=
500
balle['VX'] = -balle['VX']
J2['score']
+= 1
balle['Y']
==350
balle['VY']
-balle['VY']
balle['VX']
= -balle['VX']J2['score'])
afficheScore(J1['score'],
= 500
balle['X']
balle['VY'] = -balle['VY']
balle['Y'] = 350
balle['X'] = 500
afficheScore(J1['score'], J2['score'])
balle['Y'] = 350
afficheScore(J1['score'], J2['score'])
x
Retour sur l’exercice B16 La norme de v⃗ ( ) est conservée lors du choc.
y
x√ 2 2
Retour
surthéorème
l’exercicede
B16
La norme
conservée lors du choc.
⃗v⃗ (= ) est
Grâce au
Pythagore,
on de
a ∥v∥
yx x + y et les relations trigo√
⃗
Retour sur l’exercice B16 La norme xde
v
(
)
est
conservée lors du choc.
= � cos
α
⃗ relations trigo⃗ =y √x2avec
nométriques
du triangle
donnent {
∥v∥.
Grâce
au théorème
de Pythagore,
on a ∥v∥
+ y 2�et= les
y = � sinα 2 2
⃗cos
Grâce au théorème de Pythagore, onxa=∥�v∥
= α x + y et les relations trigo⃗
nométriques du triangle donnent {
avec � = ∥v∥.
yx = � sqrt,
sinα
cos
α sin, cos
from math import asin, degrees, radians,
⃗
nométriques du triangle donnent {
avec � = ∥v∥.
y = � sinα
def
:
fromrebond(x,y,e)
math import asin,
degrees, radians, sqrt, sin, cos
l = sqrt(x**2 + y**2)
fromalpha
math =
import
asin,
degrees, radians, sqrt, sin, cos
degrees(
def rebond(x,y,e)
: asin(y/l) )
beta
=
alpha
+
e
l = sqrt(x**2 + y**2)
def return
rebond(x,y,e)
:
*cos(radians(beta)),
=-l
degrees(
asin(y/l) ) l*sin(radians(beta))
alpha
l = sqrt(x**2 + y**2)
beta = alpha
+ e
alpha = degrees( asin(y/l) )
return -l*cos(radians(beta)), l*sin(radians(beta))
beta = alpha + e
return -l*cos(radians(beta)), l*sin(radians(beta))
69
69
69
Solutions des exercices
J1['Y']-50
<= balle['Y']
<=
# Lorsque la balle touche
la raquette
1 :
balle['VX']
= -balle['VX']
if J1['X']+8
<= balle['X']
<= J1['X']+24 and \
# Lorsque la balle touche la raquette 1 :
J1['Y']-50 <= balle['Y'] <=
if
J1['X']+8
balle['X']
<=raquette
J1['X']+24
# Lorsque
la <=
balle
touche la
2 :and \
balle['VX']
= -balle['VX']
J1['Y']-50
balle['Y']
if J2['X']-24 <= balle['X']
<=<=
J2['X']-8
and<=
\
balle['VX'] = -balle['VX']
J2['Y']-50
<= balle['Y']
<=
# Lorsque la balle touche
la raquette
2 :
balle['VX']
-balle['VX']
if J2['X']-24
<==balle['X']
<= J2['X']-8 and \
# Lorsque la balle touche la raquette 2 :
J2['Y']-50 <= balle['Y'] <=
if J2['X']-24 <= balle['X'] <= J2['X']-8 and \
balle['VX'] = -balle['VX']
J2['Y']-50 <= balle['Y'] <=
balle['VX'] = -balle['VX']
C HAPITRE B
Retour sur l’exercice B17 La première idée est sûrement d’adapter le code
précédent par disjonction de cas :
def rebond(x,y,e) :
l = sqrt(x**2+y**2)
if y > 0 :
alpha = degrees(asin(y/l))
beta = alpha + e
return -l*cos(radians(beta)), l*sin(radians(beta))
else :
alpha = degrees(asin(-y/l))
beta = alpha - e
return -l*cos(radians(beta)), -l*sin(radians(beta))
Si vous êtes habitué à manipuler les angles orientés, en jouant sur la parité des fonctions trigonométriques circulaires (cos(−x) = cos x ; sin(−x) =
− sinx et arcsin(−x) = − arcsinx) vous vous apercevrez alors que la fonction
de l’exercice précédent fonctionne sans ajout de test !
Retour sur l’exercice B18 Encore une fois, il n’y a pas grand-chose à changer, puisque la valeur de x n’entre pas dans le calcul de l’angle α. Il faut
juste faire attention à ce que, cette fois-ci, la première composante de la
vitesse renvoyée soit positive. Voici une proposition où l’on a ajouté aussi
une ligne afin de limiter les grandes valeurs de β pour une meilleure jouabilité :
def rebond(x,y,e) :
l = sqrt(x**2+y**2)
alpha = degrees(asin(y/l))
beta = alpha + e
beta = min(max(-70,beta),70) # Pour ne pas avoir d'angles trop grands
if x > 0 :
return -l*cos(radians(beta)), l*sin(radians(beta))
else :
return l*cos(radians(beta)), l*sin(radians(beta))
On peut éviter ce dernier test en utilisant la fonction copysign du module
math .
copysign(A,x) : renvoie la valeur (absolue) de A précédée du signe
de x. En particulier copysign(1,x) renvoie 1 si x ⩾ 0 et −1 sinon.
def rebond(x,y,e) :
l = sqrt(x**2+y**2)
alpha = degrees(asin(y/l))
70
beta = alpha + e
beta = min(max(-70,beta),70) # Pour ne pas avoir d'angles trop grands
return -copysign(1,x)*l*cos(radians(beta)), l*sin(radians(beta))
J EU
DE
P ONG
Enfin, pour incorporer ce nouveau type de rebond, on modifiera les lignes
correspondantes dans la boucle principale. Par exemple pour la raquette
numéro 1 :
# Lorsque la balle touche la raquette 1 :
if J1['X']+8 <= balle['X'] <= J1['X']+24 and \
J1['Y']-50 <= balle['Y'] <= J1['Y']+50 :
balle['VX'], balle['VY'] = rebond(balle['VX'], balle['VY'], ⤦
� balle['Y']-J1['Y'])
balle['X'] = J1['X']+25
Retour sur l’exercice B19 Augmenter de 10% revient à multiplier la quantité par 1,1. On va donc, dans la fonction rebond, multiplier les nouvelles
composantes du vecteur vitesse avant le renvoi.
def rebond(x,y,e) :
l = sqrt(x**2+y**2)
alpha = degrees(asin(y/l))
beta = alpha + e
beta = min(max(-70,beta),70) # Pour ne pas avoir d'angles trop grands
return -copysign(1.1,x)*l*cos(radians(beta)), 1.1*l*sin(radians(beta))
Pensez aussi à ré-initialiser la vitesse lorsqu’un point est marqué !
Retour sur l’exercice B20 En suivant la proposition d’intelligence artificielle, on va calculer la hauteur cible à atteindre :
● si la balle vient vers la machine, la hauteur cible est celle de la balle ;
● 350 (milieu du terrain) si la balle repart.
On va donc remplacer les deux lignes :
if 'UP' in Touches : raquetteUP(J2)
if 'DOWN' in Touches : raquetteDOWN(J2)
par :
# Déplacement du joueur 2 par IA
if balle['VX'] > 0 : # La balle vient vers le joueur 2
cible = balle['Y']
else :
cible = 350
if J2['Y'] < cible :
raquetteDOWN(J2)
elif J2['Y'] > cible :
raquetteUP(J2)
71
Solutions des exercices
Remarquer que l’on ré-initialise ici l’abscisse de la balle au bord de la raquette pour s’assurer que la balle n’est plus en collision avec elle.
C HAPITRE B
Retour sur l’exercice B21 En s’inspirant de ce qui a précédemment été fait
pour gérer les déplacements de la balle, on peut proposer cette solution :
def arrivee(b) :
x, y, vx, vy = b['X'], b['Y'], b['VX'], b['VY']
while x < 890 - 24 :
x, y = x + vx, y+ vy
if y < 130 + 16 or y > 570 - 16 :
vy = -vy
return y
Il faut ensuite mettre le calcul de cible à jour dans l’I.A :
cible = arrivee(balle)
Retour sur l’exercice B22 D’une part, il faut adapter la fonction arrivee :
def arrivee(b, coup) :
x, y, vx, vy = b['X'], b['Y'], b['VX'], b['VY']
c = 0
while x < 890 - 24 and c < coup :
x, y = x + vx, y+ vy
c = c + 1
if y < 130 + 16 or y > 570 - 16 :
vy = -vy
return y
D’autre part, il faut créer une variable globale coup qui sera initialisée
arbitrairement à 10 et mise à jour lorsqu’un but sera marqué.
72
Chapitre C
Objectif
Programmer un jeu de labyrinthe
Programmation
Listes chaı̂nées et tuples
Principe d’une file
Algorithmique
Générer un labyrinthe parfait
Sortir d’un labyrinthe
Pygame
Gestion de la fenêtre
Gestion des surfaces
Gestion des événements clavier
Minos
Bravo ! Si vous êtes là, c’est que le jeu de Pong a été réalisé. Ce premier projet avait pour objectif de vous faire découvrir quelques fonctions
de Pygame, mais comme vous l’aurez remarqué, un certain nombre de
fonctions étaient déjà présentes pour nous simplifier la tâche. Pour autant,
vous pouvez être fier du travail accompli. Il arrive souvent en informatique que nous ayons recours à des fonctions dont nous ne connaissons
que les entrées et sorties sans pour autant connaitre en détail l’algorithme
qui se cache derrière.
À présent, nous allons réaliser nos projets à partir d’une feuille blanche !
Commençons par un jeu de labyrinthe :
MINOS
73
C HAPITRE C
1 - Fenêtre et surfaces dans Pygame
Voici le script classique et minimal pour afficher une fenêtre avec le
module Pygame :
1
2
import pygame
from pygame.locals import *
3
4
5
pygame.init()
fenetre = pygame.display.set_mode((1000,600))
6
7
8
9
10
11
12
13
continuer = True
clock = pygame.time.Clock()
while continuer:
clock.tick(100)
for event in pygame.event.get():
if event.type == QUIT :
continuer = False
14
15
pygame.display.flip()
16
17
pygame.quit()
Expliquons en détail les actions de chaque ligne :
1. On importe le module pygame. Avec ce type de syntaxe, toutes les
fonctions de ce module appelées seront précédées de pygame.
2. On importe les constantes de pygame, comme QUIT utilisé à la ligne
12 ou encore les codes clavier comme cela sera détaillé plus loin.
4. On initialise le module (obligatoire).
5. On crée une surface qui sera de dimensions 1000 × 600 pixels et
qui représentera la fenêtre. Sous Pygame, une surface peut être vue
comme une feuille blanche sur laquelle on pourra exécuter des actions.
7. La variable continuer est un booléen, c’est-à-dire une variable qui
ne peut valoir que VRAI (True) ou FAUX (False) . Ici continuer
sera la condition pour entrer dans la boucle principale du jeu et en
sortir.
8. La variable clock est un objet de type Clock , qui permet de gérer
la vitesse du jeu, en particulier pour que le jeu ne soit pas trop rapide
sur une machine très puissante. La ligne 10 indique que le jeu va se
jouer au mieux à 100 FPS, c’est à dire à 100 images par seconde (100
Frames Per Second). Si la boucle revient à la ligne 10 avant 1/100ème
de seconde, le programme attend.
74
M INOS
11. Si vous avez lu le tome 1, vous connaissez la notion d’événement :
ce sont toutes les actions extérieures qui se produisent : la souris qui
bouge, une touche pressée, la fenêtre déplacée ou fermée... Ici avec
la boucle for, on va lire tous les événements qui se sont produits
depuis le tour précédent, chaque événement est alors placé dans la
variable event pour être traité ensuite.
12. Si l’événement est de type QUIT (on cherche à fermer la fenêtre)...
13. ...on met la variable continuer à FAUX de manière à quitter la
boucle.
15. Cette ligne ne sert à rien pour le moment puisque rien n’est affiché
mais sera nécessaire par la suite. Lorsque l’on dessine sur la fenêtre,
tout l’affichage se passe sur une surface ”virtuelle”. Lors du flip ,
la surface est entièrement recopiée sur l’écran ”réel”. Cette technique
qui a pour but d’éviter de voir les étapes de dessin à l’écran ainsi
que les problèmes de scintillement s’appelle le principe de double
buffering .
17. On quitte proprement le mode Pygame, cette dernière ligne est importante, surtout pour les utilisateurs de Windows, sinon la fenêtre
se gèle et on ne peut plus reprendre la main.
✍
À propos de la fenêtre qui gèle lorsqu’une erreur est rencontrée : si
vous utilisez EduPython, inutile de chercher à la fermer. Corrigez
le problème et relancez votre programme, l’autre fenêtre disparaitra
alors. Vous pouvez aussi revenir à l’éditeur Python en appuyant sur
les touches CTRL + F2 pour tout réinitialiser.
La fenêtre
Les fonctions qui permettent d’agir sur la fenêtre se trouvent dans le
module display du package Pygame. Si vous commencez votre programme par
import pygame
il faudra faire précéder le nom de la fonction de pygame.display.,
par exemple :
pygame.display.flip()
75
C HAPITRE C
Fonction
set mode(s,opt)
Effet
Renvoie une surface qui représentera
la fenêtre présente à l’écran. La taille
(intérieure) de la fenêtre est donnée par le
couple s. On peut aussi préciser des options
opt, celles-ci sont facultatives. On peut
choisir (entre autres) parmi FULLSCREEN
(plein écran), RESIZABLE (fenêtre redimensionnable, elle ne l’est pas par défaut) ou
encore NOFRAME (fenêtre sans bordure).
set caption(txt)
Affiche le texte txt dans la barre du haut de
la fenêtre ; cette fonction ne renvoie rien.
flip()
set icon(s)
Recopie la surface virtuelle à l’écran.
Associe à la fenêtre l’icône définie par la surface s. Cette image peut être de n’importe
quelle taille et contenir de la transparence ;
souvent on utilise des images 32 × 32.
✍ ● Si
vous
utilisez
le
mode
FULLSCREEN ou NOFRAME, pensez à prévoir une sortie (par la
touche ECHAP par exemple).
● Si
vous
utilisez
le
mode
FULLSCREEN,
choisissez
de
préférence une résolution supportée par votre carte graphique,
sinon vous aurez des cadres noirs
pour compléter l’écran. Vous avez
accès à ces informations en affichant
les ”paramètres d’affichage” sous
Windows.
76
Paramètres d’affichage
sous Windows 10
M INOS
Les surfaces et les images
Le module image de Pygame, permet de
lire et enregistrer des images. Encore une
fois, il faut penser à faire précéder les noms
des fonctions de pygame.image. selon le
mode d’importation.
Les images sont composées de pixels, qui
peuvent être codés de différentes façons en
mémoire :
Fonction
load(f ich)
save(surf ,f ich)
Un dessin de Mathilda pour
l’anniversaire de son papa
Effet
Renvoie une surface représentant l’image
contenue dans le fichier f ich.
Sauvegarde la surface surf sous forme d’un
fichier image nommé f ich. Le format de
l’image est déterminé par son extension.
✍ Au niveau des formats d’images, Pygame sait lire les principaux fichiers d’images (PNG, JPG et GIF non animé). À noter que :
● Les fichiers JPG sont souvent au format 16 millions de couleurs :
chaque pixel est codé sur 3 octets (donc 3 nombres entre 0 et 255) : le
premier représente la dose de rouge, le second le vert et le dernier le
bleu. On parle d’image au format RGB (Red-Green-Blue)
● Les fichiers PNG peuvent contenir une information supplémentaire
de transparence, chaque pixel est alors codé sur 4 octets : 3 octets
pour le rouge, vert, bleu et un quatrième appelé canal alpha. Ce
nombre est compris entre 0 (pixel totalement transparent) et 255 (pixel
totalement opaque). On parlera dans ce livre d’images (ou de surface) RGBA (Red-Green-Blue-Alpha)
● Les fichiers GIF (une fois pour toute, on prononce ≪ jif ≫ selon l’inventeur même Steve Wilhite dans une interview accordée au New
York Times) ne seront pas beaucoup utilisés dans cet ouvrage car
ils ne peuvent contenir que 256 couleurs (dont éventuellement une
transparente) données sous forme de palette. Ainsi chaque pixel est
codé sur 1 octet indiquant le numéro de la couleur dans la palette.
77
C HAPITRE C
On peut alors manipuler les pixels de ces surfaces :
Méthode
surf =Surface(s,opt)
Effet
Crée et renvoie une surface de dimension
s donnée sous forme de couple. En option
opt, on peut préciser SRCALPHA si on veut
une image au format RGBA. Attention à la
majuscule (on verra d’où vient le S majuscule plus loin dans le livre).
surf .fill(coul)
Remplit la surface surf de la couleur coul.
Cette dernière peut être donnée par un triplet ou un quadruplet selon le format de
la surface.
surf .set at(p,coul)
Colorie le pixel de coordonnées p = (x,y)
de la couleur coul. Comme d’habitude le
point de coordonnées (0,0) est le coin en
haut à gauche de l’image.
surf .get at(p)
Renvoie la couleur du pixel de coordonnées p sous la forme d’un quadruplet
(R, G, B, A). Si la surface n’a pas d’information de transparence, le canal alpha est
à 255.
E X C1 Dessiner un soleil dans une image 128 × 128.
L’extérieur du soleil étant transparent et l’intérieur
jaune avec une transparence proportionnelle à la distance à son centre.
E X C2 Dans ce second exercice, nous allons tenter de
dessiner ce motif de trois couleurs sur une image de dimension 128 × 64. Nous enregistrerons le résultat sous
le nom motif.png pour le réutiliser plus tard.
78
M INOS
En pratique, il est assez rare de recopier une image pixel par pixel, on
copie le plus souvent des zones entières.
Méthode
surf .copy()
surf .blit(s1,p,r)
Effet
Renvoie une copie de la surface surf .
Copie la surface s1 sur la surface surf à
partir du point de coordonnées p. Si un
quadruplet r = (x0 ,y0 ,l,h) est précisé, la
surface s1 est limitée aux pixels de coordonnées (x,y) vérifiant : x0 ⩽ x < x0 + l
et y0 ⩽ y < y0 + h. Si r n’est pas précisé,
l’intégralité de la surface s1 est recopiée.
Voici un exemple qui illustre l’utilisation de ces fonctions (on peut obtenir le code et les images en tapant BLIT sur le site du livre) :
image : nombres.png
import pygame
fond = pygame.image.load('fond.jpg')
nombres = pygame.image.load('nombres.png')
nouveau = fond.copy()
nouveau.blit(nombres,(100,50),(57,82,50,80))
pygame.image.save(nouveau,'nouveau.jpg')
50
(100, 50)
fond.jpg
nouveau.jpg
E X C3 À partir du motif réalisé motif.png dans l’exercice précédent réaliser
un pavage à la mode q*bert de taille 640 × 640 et l’afficher dans une fenêtre dont
le titre est ”Pavages” et l’icône le motif de base.
79
C HAPITRE C
✍
À propos du chargement d’images : il existe deux méthodes sur les
surfaces permettant de les convertir dans le même format que la
fenêtre d’affichage, il s’agit de convert et convert alpha , la seconde étant utilisée pour conserver l’information RGBA lors du chargement d’une image PNG par exemple. Il est conseillé de convertir
ces images dès le chargement pour gagner du temps par la suite. Typiquement, on charge ainsi les images :
import pygame
imageRGB = pygame.image.load('fond.jpg').convert()
imageRGBA = pygame.image.load('nombres.png').convert_alpha()
...
Pour les coordonnées des points ou les codes couleur, nous utilisons
souvent des tuple. Prenons un petit moment pour faire la différence avec
les listes :
Les listes sont des structures de taille dynamique (on peut ajouter ou
supprimer des éléments) et mutables (on peut modifier des éléments).
Derrière se cache une structure de liste chaı̂née : quand on tape L =
[5, 8 10], on mémorise l’adresse du début de la liste plutôt que
son contenu, c’est-à-dire que, pour chaque valeur de la liste, la ma-
80
M INOS
chine conserve deux informations : la valeur et l’adresse de la valeur
suivante.
● Création d’une liste : L = [5,8,10]
L
5
8
10
● Suppression d’un élément par son indice : x = L.pop(1).
La valeur de la case d’indice 1 est stockée dans la variable x. La
machine garde en mémoire l’adresse de la case suivante, détruit la
cellule d’indice 1 pour libérer la mémoire et on ré-adresse la case
précédente :
L
5
8
10
● Même principe pour l’ajout ou l’insertion : L.append(3).
La machine se place à l’endroit voulu, crée une nouvelle case et
corrige les liens des maillons de la chaı̂ne :
L
5
10
3
Tout cela est totalement transparent pour le programmeur, c’est
pour cela que l’on dit que Python est un langage de haut niveau , car
on laisse à Python le soin de gérer cela. D’ailleurs la gestion des listes
dans ce langage est bien plus complexe que cela de manière à optimiser l’accès aux éléments de celles-ci. Néanmoins, garder à l’esprit le
principe de fonctionnement d’une liste en mémoire permet de comprendre certaines subtilités du langage comme le montre l’exemple
suivant :
81
C HAPITRE C
>>> France = ['Bleu', 'Blanc', 'Rouge']
>>> Roumanie = France
>>> Roumanie[1] = 'Jaune'
>>> Roumanie
['Bleu', 'Jaune', 'Rouge']
>>> France
['Bleu', 'Jaune', 'Rouge']
>>> id(France), id(Roumanie)
(109725216, 109725216)
>>> a = 10
>>> b = a
>>> b = 3
>>> b
3
>>> a
10
>>> id(a)
1881531232
>>> a = a + 1
>>> id(a)
1881531248
En effet, lorsque l’on tape Roumanie = France, on ne copie pas
le contenu de la liste, mais l’adresse (on parle d’alias ). Ainsi lorsque
l’on fait la modification sur l’une, on la fait sur l’autre puisque ce sont
les mêmes objets. Avec les nombres, on ne rencontre pas ce problème,
car les nombres en Python sont des données non mutables : si on
change sa valeur, on change d’emplacement mémoire comme le
montre l’exemple avec la fonction id qui renvoie l’adresse mémoire
de l’objet.
Les tuples ressemblent aux listes, on sait déjà qu’on les déclare
avec des parenthèses au lieu de crochets, mais ce sont des données
non mutables. On ne peut pas ajouter ou retirer des éléments, on ne
peut pas les modifier :
>>> L = (1,4,5)
>>> L[1]
4
>>> L[1] = 7
Traceback (most recent call last):
File "<string>", line 301, in runcode
File "<interactive input>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
Les tuples de longueur 2 sont appelés simplement couples et ceux
de longueur 3 sont appelés triplets. Comme pour les listes, on peut
récupérer rapidement les valeurs dans des variables distinctes :
A = (1, 2, 3)
x, y, z = A
Enfin on peut passer d’un type à l’autre à l’aide des fonctions
list et tuple
82
M INOS
list
Tuple
(1,2,3)
Liste
[1,2,3]
tuple
2 - Dessiner un labyrinthe
Dans cette partie nous allons chercher à dessiner un labyrinthe. Dans
notre jeu, il sera de dimension 20 × 20 cases.
Chaque case est entourée de 0 à 4 murs notés N, S,
E, O. À chaque direction on associe un entier, comme
l’explique le schéma ci-contre. Pour déterminer le
code d’une case, on ajoutera les valeurs des murs
présents. Par exemple, la case
sera codée par le
nombre 1 + 8 = 9.
N∶1
E∶2
O∶8
S∶4
E X C4 Pour voir si vous avez compris :
1. Quel est le code représentant la case
?
2. Dessiner la case correspondant au nombre 7.
Le labyrinthe peut alors être représenté par une matrice de nombres :
image
M
⎛13 5 1 7⎞
⎜11 9 4 7⎟
⎝12 4 5 7⎠ matrice
= [[13,
[11,
[12,
5,
9,
4,
1,
4,
5,
7],
7],
7]]
Python
83
C HAPITRE C
E X C5 Pour le moment nous prendrons l’exemple de ce labyrinthe que vous pou-
vez télécharger en tapant le code LABY :
L = [[13, 5, 5, 5, 1, 1, 5, 7,11,11, 9, 7,11,11, 9, 1, 5, 1, 3,11],
[ 9, 5, 5, 5, 6,10,11,11,10, 8, 6, 9, 4, 4, 2,14,11,14, 8, 6],
[10, 9, 7,11, 9, 2, 8, 4, 4, 4, 3,10,11,13, 4, 5, 4, 7,10,11],
[ 8, 2,13, 2,14,12, 0, 7,13, 5, 4, 4, 0, 7, 9, 7,13, 5, 6,10],
[10,12, 1, 4, 7, 9, 6,13, 5, 1, 3,13, 0, 1, 6,13, 3,11,13, 2],
[12, 3,12, 3,11,10,13, 5, 5, 6,10, 9, 6, 8, 5, 7,10,10,11,10],
[ 9, 4, 3,14,10,12, 5, 5, 5, 7,12, 4, 7, 8, 5, 5, 2,10, 8, 2],
[ 8, 7,14,13, 0, 5, 1, 5, 5, 7,13, 5, 3,10, 9, 1, 0, 4, 6,14],
[12, 5, 5, 1, 4, 3,12, 1, 7, 9, 3,13, 0, 6,14,10,12, 5, 1, 7],
[ 9, 7,11,12, 3,12, 3,14, 9, 2,14,13, 4, 7,13, 2,13, 3, 8, 7],
[ 8, 5, 6, 9, 0, 7, 8, 7,10,14,13, 3,11,13, 5, 0, 3,12, 4, 3],
[10,13, 3,14,14,11,12, 5, 2, 9, 5, 4, 0, 5, 7,10,14, 9, 3,14],
[ 8, 7,10,13, 1, 2, 9, 5, 4, 4, 5, 7,10, 9, 3, 8, 7,10,12, 7],
[ 8, 5, 0, 5, 2,14, 8, 5, 7,11,11,11,14,14,12, 4, 1, 4, 7,11],
[10, 9, 4, 7,12, 5, 0, 7,11,10,10,12, 5, 3,13, 5, 2,11,11,10],
[14,10,13, 3,11,13, 0, 7, 8, 4, 6, 9, 5, 2,11, 9, 4, 4, 0, 6],
[ 9, 6, 9, 0, 6,11,12, 5, 2, 9, 1, 0, 7,12, 4, 4, 1, 7, 8, 7],
[ 8, 1, 2, 8, 7,10,11,11,14,10,10,14,13, 1, 3,11,14,11,12, 3],
[10,10,10,12, 1, 0, 6, 8, 7,10,14, 9, 1, 6,12, 2, 9, 4, 5, 2],
[14,14,12, 7,14,12, 5, 4, 7,12, 7,14,12, 7,13, 4, 4, 7,13, 6]]
1. Représenter sur une feuille les premières
cases du labyrinthe défini par L.
2. Quelle est la liste représentant la configuration ci-contre ?
Si on regarde à présent le code d’une case en écriture binaire, on voit
alors facilement où il y a des murs :
case
code décimal
1 + 2 + 8 = 11
code binaire
2
1011
en liste
[1,1,0,1]
Noter que dans l’écriture sous forme de liste, le nombre est écrit ≪ à
l’envers ≫ pour simplifier les choses. En effet, si L est la liste représentant
un nombre, on peut retrouver ce nombre en écriture décimale ainsi :
3
L[0]+2L[1]+4L[2]+8L[3] = L[0]×20 + L[1]×21 + L[2]×22 + L[3]×23 = ∑ L[i]×2i
i=0
E X C6 Écrire une fonction mur2bin (on lit ”mur to bin”) qui reçoit un entier n
et retourne une liste de 4 éléments représentant la décomposition de n sur 4 bits.
Par exemple mur2bin(5) retourne [1,0,1,0].
84
M INOS
E X C7 Écrire une fonction mur qui possède deux paramètres c et m et qui indique
par un booléen (True ou False) si le code c d’une case indique la présence d’un
mur de type m où m ∈ {1,2,4,8}. Par exemple mur(9,1) retourne True alors
que mur(9,2) retourne False.
La présence de murs autour d’une case étant maintenant détectable,
nous pouvons dessiner le labyrinthe à l’écran.
E X C8 On dispose de 2 images de taille 644 × 644
fond.jpg
murs.jpg
Chaque case du labyrinthe est de taille 32 × 32 pixels. Nous allons dessiner des
murs de 4 pixels de large que nous allons prendre dans l’image murs.jpg. À
noter que nous ne dessinerons dans chaque case que les murs Nord et Ouest (lorsqu’ils sont présents), car les autres seront construits par les cases voisines. En
effet, l’information concernant les murs est présente deux fois : sur le schéma suivant A B représentant un labyrinthe de 2 cases, la présence du mur entre A
et B est indiquée dans la case A par le code 2 (Est) et dans la case B par le code
8 (Ouest). Inutile donc de le dessiner deux fois. C’est aussi pour cette raison que
l’image de fond est un carré de 644 = 20 × 32 + 4 où les murs Est et Sud de toutes
les cases de droite et du bas ont déjà été dessinés. À vous de jouer en écrivant une
fonction dessine qui reçoit une liste représentant le labyrinthe et renvoie une
surface contenant le fond du jeu.
85
C HAPITRE C
3 - Générer un labyrinthe
Notre plateau de jeu est opérationnel, mais quel dommage de toujours
devoir jouer sur le même niveau ! Nous allons découvrir comment générer
nos propres labyrinthes. En réalité, nous allons nous contenter de générer
des labyrinthes dits parfaits. Un labyrinthe parfait est un labyrinthe pour
lequel, quelle que soit la case d’entrée et la case de sortie, il existe un et un
unique chemin pour s’y rendre. On pourra toujours supprimer des murs
existants si l’on souhaite multiplier les possibilités de chemins ensuite ;
mais au moins nous serons sûrs qu’il en existe au moins un.
Si cette partie ne vous intéresse pas, vous pouvez la passer et y revenir
plus tard, quand vous serez fatigué de toujours jouer avec le même décor.
Pour ceux qui ont décidé de rester, nous allons construire le labyrinthe via
l’algorithme suivant :
● Au départ, toutes les cases contiennent 4 murs et on attribue à chaque
case une ”couleur” unique.
● Tant qu’il y a au moins deux couleurs présentes dans le labyrinthe :
◇ on choisit 2 cases voisines possédant des couleurs différentes ;
◇ on casse le mur entre ces deux cases ;
◇ on fusionne les couleurs des deux cases. Ainsi pour deux cases
d’une même couleur il existe toujours un chemin qui relie ces deux
cases.
86
M INOS
● À la fin, toutes les cases sont de la même couleur, on peut donc relier
n’importe quelle case du labyrinthe à n’importe quelle autre (en plus
ce chemin est unique, mais c’est une autre histoire).
Comme d’habitude, un schéma est souvent plus parlant.
En partant de cette situation initiale de 3 × 3 cases :
G
H
D
E
F
A
B
C
G
H
I
G
H
I
G
H
I
G
H
I
D
E
F
D
E
F
D
E
F
D
E
F
A
B
C
A
B
C
A
B
C
A
B
C
on fusionne D et E
on fusionne E et B
on fusionne F et I
on fusionne G et H
G
H
I
G
H
I
G
H
I
G
H
I
D
E
F
D
E
F
D
E
F
D
E
F
A
B
C
A
B
C
A
B
C
A
B
C
on fusionne E et F
on fusionne C et F
on fusionne A et B
I
on fusionne D et G
En pratique, les couleurs importent peu puisqu’elles ne sont utilisées
que dans l’algorithme de construction du labyrinthes et ne sont jamais
représentées à l’écran. Nous allons utiliser des nombres pour les coder, ce
qui sera plus simple et aussi efficace.
E X C9 On propose la fonction suivante qui renvoie une liste2D de taille n×n
remplie de 0.
1. Le programme fonctionne-t-il si on affiche A ?
2. Si on tape A[1][1] = 3 que se passe-t-il
pour A ?
3. Expliquer et corriger ce problème.
def init(n) :
ligne = [0] * n
res = []
for i in range(n) :
res.append(ligne)
return res
A = init(5)
E X C10 Écrire une fonction initCoul(n) qui retourne une liste de listes d’entiers deux à deux distincts de taille n × n.
On peut casser un mur, s’il y en a un, et si les cases situées de part et
d’autre de ce mur ont des couleurs distinctes. En réalité, on peut s’abstenir
de tester la présence de murs et ne regarder que les couleurs. En effet, si
deux cases voisines sont de couleurs différentes, il n’existe pas encore de
chemin pour aller de l’une à l’autre, il y a donc forcément un mur.
87
C HAPITRE C
E X C11 Écrire une fonction cassables telle
que cassables(couleurs,l,c) retourne la
liste des directions dans lesquelles on peut casser un mur appartenant à la case située à la
ligne l et colonne c et où couleurs est
une grille (de dimension quelconque) représentant
les couleurs des cases. Sur l’exemple ci-contre,
cassables(coul, 0, 1) renvoie [2,4,8]
et cassables(coul, 2, 0) renvoie [1,2].
Maintenant que l’on sait quels murs sont cassables, il ne nous reste
plus qu’à les casser !
E X C12 Écrire une fonction casse telle que casse(M,Coul,l,c,d) effec-
tue la suppression du mur de la case (l,c) dans la direction d ∈ {1,2,4,8}.
Cette fonction ne renvoie rien, mais les grilles M et Coul sont mises à jour. On
suppose ici que cette casse est possible.
Nous avons à présent toutes les fonctions nécessaires pour réaliser l’algorithme détaillé en début de partie.
E X C13 Terminer le programme en créant une fonction laby(n) qui crée un
labyrinthe parfait de taille n × n en retournant la liste2D générée.
4 - Les événements clavier, retour au jeu !
Faisons le point... nous savons construire et représenter le terrain de
jeu. Il va nous falloir déplacer un personnage dans le labyrinthe. Dans le
chapitre précédent, la gestion du clavier avait déjà été programmée pour
alléger la difficulté du projet. Regardons de plus près comment cela fonctionne.
À chaque fois qu’un événement se produit, celui-ci est mémorisé dans
une file qui repose sur le même principe qu’une file d’attente dans la vie
courante, c’est-à-dire que le premier événement qui y a été placé sera le
premier à être traité.
88
M INOS
Récupération
par Pygame
Arrivée des
événements
Appuie sur
Relâche
Fenêtre
la touche A
la touche CTRL
redimensionnée
QUEUE
Un exemple de file d’événements.
TETE
Principe FIFO : First In, First Out : Premier arrivé, premier servi !
Le module event de Pygame permet de gérer cette file d’événements.
On récupère alors chaque événement sous forme d’objets informatiques de type Event. Comme d’habitude, si vous avez importé Pygame de manière classique, il vous faudra faire précéder ces fonctions de pygame.event
Fonction
poll()
Effet
Renvoie (et supprime de la file) l’événement en tête
de file.
get()
Retourne la liste de tous les événements présents
dans la file et vide cette file.
clear(t)
Vide la file des événements de type t. Si t n’est pas
précisé, la file est entièrement vidée.
wait()
Attend qu’un événement se produise puis le renvoie.
Lorsqu’un événement est récupéré, on peut avoir accès à diverses informations supplémentaires en fonction du type de celui-ci. Si evt est un
événement, on récupère son type avec evt.type. En voici quelques-uns,
nous en verrons d’autres au fur et à mesure du livre :
● QUIT : cet événement se produit quand l’utilisateur tente de quitter
le programme (clique sur la croix de fermeture de fenêtre).
● KEYUP : cet événement se produit lorsqu’une touche est relâchée. On
peut alors récupérer d’autres informations :
◇ evt.key : donne le code de la touche enfoncée.
◇ evt.mod : est un entier indiquant si, en plus de la touche, une
touche spéciale est enfoncée ( CTRL , ALT ...), ces valeurs sont
89
C HAPITRE C
définies par les constantes Pygame importées lors du from
pygame.locals import *. En voici quelques-unes dont les
noms parlent d’eux-mêmes : KMOD LSHIFT, KMOD RSHIFT,
KMOD LALT, KMOD NUM, KMOD CAPS... On peut aussi ajouter
ces valeurs pour détecter des combinaisons de touches.
● KEYDOWN : cet événement se produit lorsqu’une touche est enfoncée.
On peut alors récupérer les mêmes informations qu’avec KEYUP et
en plus :
◇ evt.unicode : donne sous forme d’une chaı̂ne de caractères
la lettre réellement tapée. Contrairement à key, cet attribut
unicode tient compte des majuscules et minuscules. Pour les
touches spéciales, unicode est la chaı̂ne vide.
◇ evt.scancode : fonctionne comme key, mais le code renvoyé
est spécifique au système d’exploitation. Cela sert par exemple
pour les touches spéciales (Windows, volume, ...).
Après différents essais, voilà une technique que j’utilise souvent pour
gérer les touches. C’est la même idée que celle utilisée et présentée dans le
premier tome (chapitre M, page 169) avec Tkinter :
● On initialise une liste vide Touches.
● À chaque fois qu’une touche est enfoncée, on l’ajoute à la liste.
● À chaque fois que la touche est relâchée, on la retire de cette liste.
Ainsi, en temps réel la liste Touches contient les touches pressées. Avant
de la supprimer ou de l’ajouter, on vérifie sa présence dans la liste (imaginer si la touche est pressée ou relâchée alors que la fenêtre du jeu n’est pas
active).
Comme l’information unicode n’est pas présente au moment où l’on
relâche la touche, on va plutôt stocker la valeur evt.key. Ce qui donne
ce petit programme. Il existe une autre technique, que nous présenterons
plus tard :
...
Touches = []
...
while continuer:
for event in pygame.event.get():
if event.type == QUIT :
continuer = False
elif event.type == KEYDOWN and event.key not in Touches :
Touches.append(event.key)
elif event.type == KEYUP and event.key in Touches :
Touches.remove(event.key)
...
90
M INOS
On peut alors savoir si une touche est enfoncée en testant si son code
est présent dans la liste Touches. Pour ce faire, soit on connaı̂t les valeurs
de ces touches, soit on utilise encore une fois les valeurs des constantes
définies par Pygame. En voici quelques-unes :
K
K
K
K
K
K
K
K
K
K
BACKSPACE
RETURN
UP
DOWN
RIGHT
LEFT
SPACE
RSHIFT
RCTRL
RALT
Retour arrière
Return
Flèche haut
Flèche bas
Flèche droite
Flèche gauche
Espace
Shift droit
Control droit
ALT droit
K
K
K
K
K
K
K
K
K
K
TAB
PAUSE
ESCAPE
F1 à K F15
0 à K 9
KP0 à K KP9
a à K z
LSHIFT
LCTRL
LALT
Tabulation
Pause
Echap
F1 à F15
0 à 9
0 à 9 clavier numérique
A à Z
Shift gauche
Control gauche
ALT gauche
✍ Tout cela semble parfait... un peu trop peut-être. Le module Pygame
a été pensé et programmé sous Linux ; de ce fait certains problèmes
apparaissent sous Windows, comme la détection du type de clavier...
Si vous poussez un peu les essais, vous vous apercevrez que votre
clavier est reconnu comme un clavier QWERTY, ainsi vous risquez
d’avoir certaines surprises si vous appuyez sur A puisque c’est la
touche Q qui est détectée.
E X C14 Réaliser un programme qui pourrait servir de page de menu pour le projet. En fond sera placée l’image du jeu avec le personnage qui court de gauche
à droite. On ajoute alors l’image ”appuyer sur espace pour commencer” qui clignote. Lorsque l’on appuie sur espace, le programme s’arrête. Comme d’habitude,
on dispose des ressources nécessaires, 6 images de notre héros en pleine course :
S0.png
S1.png
S2.png
S3.png
S4.png
S5.png
91
C HAPITRE C
Ainsi qu’une image espace.png
et du fond fond.jpg
E X C15 Pour finir le jeu, il nous faudra choisir une case de départ sous la forme
(lD, cD) indiquant respectivement la ligne et la colonne de la case et une case
d’arrivée sous le forme (lA, cA). Pour que le jeu ne soit pas trop facile, nous
allons imposer que ces cases soient espacées de 10 cases à vol d’oiseau (sans tenir compte des murs). Écrire une fonction distance(l1,c1,l2,c2) qui retourne la distance entre deux cases (l1, c1) et (l2, c2). Attention, on ne
peut se déplacer que horizontalement et verticalement, il ne s’agit donc pas de la
distance euclidienne classique.
À présent, nous pouvons planter le décor et y faire se déplacer le personnage. Nous allons enfin pouvoir jouer !
E X C16 Après avoir généré un labyrinthe parfait et l’avoir affiché, placer le per-
sonnage et la case de sortie et terminer le projet pour qu’il soit jouable. Pour
simplifier, le personnage ne sera pas animé mais simplement représenté par un
pion se déplaçant de case en case. Il en sera de même pour la case d’arrivée :
perso.png
arrivee.png
Lorsque le personnage atteint la case d’arrivée, on se contentera d’afficher
”GAGNE !” dans la console et de stopper le jeu.
5 - Pour aller plus loin : sortir
Cette dernière partie peut être passée en première lecture si vous êtes
impatient de découvrir le projet suivant. Elle a pour objectif de trouver un
92
M INOS
chemin de sortie et de le dessiner pour l’indiquer au joueur. C’est l’occasion de travailler à nouveau sur la notion de file présentée précédemment
pour la gestion du clavier.
Il existe de nombreux algorithmes pour déterminer un chemin dans
un labyrinthe ; en voici un assez simple à mettre en œuvre : un labyrinthe
peut être vu comme un graphe où les cellules communicantes sont reliées
par des arcs. Sur ce nouvel exemple :
Départ
Arrivée
On peut alors effectuer un parcours en largeur de ce graphe : partant
de la case de départ, on marque tous les sommets à une distance de 1, puis
ceux à une distance de 2 qui n’ont pas encore été marqués, et ainsi de suite,
jusqu’à atteindre la case d’arrivée :
Départ
0
1
4
5
1
2
3
6
4
3
8
7
Arrivée
93
C HAPITRE C
Il suffit alors de partir cette fois de l’arrivée, et d’aller de case en case
dans le sens strictement décroissant pour obtenir le chemin.
E X C17 Écrire une fonction dist1 qui reçoit comme paramètres M, l1 , c1 , l2 et
c2 représentant respectivement les murs d’un labyrinthe, la case de départ (l1 ,c1 )
et celle d’arrivée (l2 ,c2 ) et retourne une grille avec les distances au départ comme
cela vient d’être présenté. On propose de s’appuyer sur l’algorithme suivant :
● sol est initialisée comme une liste 2D de même taille que M remplie de -1
pour indiquer que pour le moment aucune case n’a été visitée ;
● on initialise une variable pas à 0 et on met 0 dans la case de départ (l1 ,c1 ) ;
● on parcourt toutes les cases de sol. À chaque fois que la valeur pas est
trouvée, on met pas + 1 dans toutes les cases voisines qui sont accessibles
(pas de mur) et qui contiennent la valeur -1 (c’est-à-dire qu’on ne connaı̂t
pas encore la distance au départ) ;
● on incrémente pas de 1 et on recommence l’étape précédente. Évidemment,
dès que la case de sortie est atteinte, on arrête et retourne la liste2D sol.
Si vous souhaitez tester cette fonction avec l’exemple du livre, tapez le numéro
de l’exercice pour télécharger le labyrinthe. Sur cet exemple, on trouve comme
l’illustre la figure précédente :
>>> dist1(L,0,0,2,2)
[[0, 1, 4, 5], [1, 2, 3, 6], [4, 3, 8, 7]]
E X C18 En utilisant l’exercice précédent, écrire une fonction solution qui
reçoit comme paramètres M, l1 , c1 , l2 et c2 représentant respectivement les murs
d’un labyrinthe, la case de départ (l1 ,c1 ) et celle d’arrivée (l2 ,c2 ) et retourne la
liste des cases indiquant le chemin à prendre pour relier (l1 ,c1 ) à (l2 ,c2 ). Par
exemple, sur le labyrinthe du livre :
>>> solution(M,(0,0),(2,3))
[(0, 0), (1, 0), (1, 1), (1, 2), (0, 2), (0, 3), (1, 3), (2, 3), (2,2)]
E X C19 Écrire enfin une fonction represente qui reçoit un labyrinthe M et
une liste de cases donnant le chemin et qui dessine sur la fenêtre la solution à
l’aide de petits cailloux de taille 8 × 8. L’image disponible sur le site provient de
l’image du soleil que nous avons générée dans ce chapitre.
Il ne vous reste plus qu’à incorporer ces fonctions dans votre projet. En
appuyant sur la touche S , le chemin apparaı̂t.
94
M INOS
Une dernière amélioration pour ce projet serait d’optimiser la fonction
dist1 de l’exercice 17. Celle-ci est assez gourmande en temps machine,
même si cela semble instantané pour un labyrinthe deMpetite
INOS taille. En effet
pour chaque valeur de pas, 20 × 20 = 400 tests sont réalisés, ce qui peut
ralentir le jeu pour de gros labyrinthes. Au lieu de regarder les valeurs des
Une dernière
pour ce
projet
serait
d’optimiser
la fonction
400amélioration
cases pour tester
si on
peut
les affecter
à pas+1,
il suffit de regarder
ist1 de l’exercice
17.
Celle-ci
est
assez
gourmande
en
temps
machine,
”autour” des cases de valeurs pas dont nous connaissons la liste puisque
ême si cela semble
instantané
pour
un labyrinthe
petite précédente.
taille. En effet
nous venons
de les
traiter
lors de lade
boucle
Pour cela nous
our chaque valeur
de pas,une
20 file
× 20dans
= 400
tests sont
ce qui peut
allons utiliser
laquelle
nousréalisés,
mémoriserons
les cases étudiées :
lentir le jeu pour de gros labyrinthes. Au lieu de regarder les valeurs des
● Ausidébut,
la les
file affecter
est initialisée
avecillasuffit
case de
de regarder
départ.
00 cases pour tester
on peut
à pas+1,
autour” des cases de valeurs pas dont nous connaissons la liste puisque
● Tant que la case en tête de file n’est pas la case d’arrivée :
ous venons de les traiter lors de la boucle précédente. Pour cela nous
lons utiliser une file dans
laquelle
mémoriserons
: de la file),
◇ on
prend nous
la case
en tête de fileles
(etcases
on laétudiées
supprime
◇ pour
toutes
les cases
accessibles et non visitées :
● Au début, la file est
initialisée
avec
la casevoisines
de départ.
● Tant que la case en tête deon
filemet
n’est
la case
:
la pas
valeur
de lad’arrivée
case à pas+1,
on place
cette
nouvelle
case dans
file :
◇ on prend la case en tête
de file
(et on
la supprime
de lalafile),
◇ pour toutes◇lesoncases
voisines accessibles
et nouvelle
non visitées
recommence
l’étude de la
case: en tête de file.
on met la valeur de la case à pas+1,
E X C20 Programmer dans une fonction dist2 ce nouvel algorithme.
on place cette nouvelle case dans la file :
◇ on recommence
l’étude
la nouvelle
casede
ences
tête
de file.
Pour comparer
lesde
vitesses
de calcul
deux
fonctions, on a appelé
dist1 et dist2 pour se déplacer d’une case à une autre diamétralement
opposée d’un labyrinthe de taille n × n généré aléatoirement en faisant vaX C20 Programmer dans une fonction dist2 ce nouvel algorithme.
rier n. Voici les courbes des temps obtenues :
Pour comparertemps
les vitesses de calcul de ces deux fonctions, on a appelé
ist1 et dist2 pour se déplacer d’une case à une autre diamétralement
pposée d’un labyrinthe de taille n × n généré aléatoirement en faisant vadist1
er n. Voici les courbes des temps obtenues :
temps
dist2
dist1
valeurs de n
10
20
30
40
50
60
70
dist2
20
30
40
50
60
70
80
90
95
valeurs de n
10
80
90
C HAPITRE C
Pour un labyrinthe de taille 20 × 20, la
fonction dist2 est deux fois plus rapide, mais pour un de taille 70×70, elle
l’est 4 fois plus. En fait si on trace le
rapport des temps de l’appel à dist2
par rapport à dist1, on obtient une
courbe qui se rapproche de 0. On dit
que le temps de dist2 est négligeable
par rapport à dist1.
1.0
0.8
0.6
0.4
0.2
0
0
10
20
30
40
50
60
70
80
90
6 - Aide pour les exercices
Aide pour l’exercice C1 On rappelle que l’on peut calculer la distance entre
deux points A(xA ; yA) et B(xB ; yB ) avec la formule
AB =
√
(xB − xA )2 + (yB − yA )2 .
Il faudra donc sûrement importer la fonction sqrt du module math.
Aide pour l’exercice C2 Un quadrillage peut aider (chaque case mesure
16 × 16 pixels) :
Aide pour l’exercice C6 Il s’agit là d’un algorithme de décomposition en
binaire d’un nombre : prenons un nombre n s’écrivant en liste [a,b,c,d]
2
soit dcba en binaire, alors n = a+2b+4c+8d = a+2(b+2c+4d). Ainsi comme
a = 0 ou 1, a est le reste de la division de n par 2 et b + 2c + 4d le quotient.
On peut alors recommencer pour trouver b :
96
M INOS
n
a + 2b + 4c + 8d
b + 2c + 4d
c + 2d
d
quotient
b + 2c + 4d
c + 2d
d
0
reste
a
b
c
d
Avec cet algorithme, on obtient les valeurs de a,b,c et d dans l’ordre inverse de l’écriture binaire classique, mais dans l’ordre souhaité pour notre
liste.
Aide pour l’exercice C7 N’hésitez pas à réutiliser la fonction précédente.
Aide pour l’exercice C10 Inutile de chercher compliqué, il y a n2 cases dans
une grille n × n, on peut donc les remplir avec les nombres de 1 à n2 .
Aide pour l’exercice C12 Il y a 3 étapes à réaliser :
● Supprimer le mur d dans la case (l,c) ce qui est très facile puisqu’il
suffit de retirer d à la valeur de la case. En effet : 1 + 2 + 4 − 2 = 1 + 4.
● Supprimer le mur correspondant dans la case voisine. Pour cela, on
peut établir une table de correspondance : 1 ↔ 4 et 2 ↔ 8.
● Fusionner les couleurs des deux cases et de toutes les autres cases
de la même couleur.
Aide pour l’exercice C14 Pour gérer l’animation, on peut charger les six
images une à une :
perso0
perso1
perso2
perso3
perso4
perso5
=
=
=
=
=
=
pygame.image.load('S0.png').convert_alpha()
pygame.image.load('S1.png').convert_alpha()
pygame.image.load('S2.png').convert_alpha()
pygame.image.load('S3.png').convert_alpha()
pygame.image.load('S4.png').convert_alpha()
pygame.image.load('S5.png').convert_alpha()
Cependant, la gestion risque ensuite de ne pas être simple. On a tout à
gagner à utiliser une liste d’images :
perso= []
for i in range(6) :
perso.append(pygame.image.load('S'+str(i)+'.png').convert_alpha())
97
C HAPITRE C
Aide pour l’exercice C15 Pour visualiser ces deux distances,
c1
on a représenté en pointillés la distance
usuelle dE appelée distance euclidienne et
en trait plein la distance qui nous intéresse
ici dM appelée distance de Manhattan ,
car elle indique la distance effectuée par
une voiture se déplaçant dans une ville au
réseau routier en forme de quadrillage.
dE =
√
(c2 − c1 )2 + (l2 − l1 )2
et
c2
l2
l1
dM = ∣c2 − c1 ∣ + ∣l2 − l1 ∣
Aide pour l’exercice C16 Pensez à réutiliser les fonctions que vous avez
déjà programmées dans le chapitre.
Aide pour l’exercice C20 Nous devons nous-mêmes modéliser la file, le
plus simple est d’utiliser une liste :
● File vide : f = []
● Enfiler x : f.append(x)
● Défiler et stocker dans v : v = f.pop(0)
Avec cette solution, la tête de file est représentée à gauche :
>>>
>>>
>>>
>>>
>>>
f = []
f.append(7)
f.append(3)
x = f.pop(0)
f.append(4)
#
#
#
#
#
[]
[7]
[7,3]
[3]
[3,4]
Initialisation
Enfiler 7
Enfiler 3
Défiler (x vaut 7)
Enfiler 4
7 - Solutions des exercices
Retour sur l’exercice C1 Vues les dimensions de l’image souhaitée, le
Soleil a un rayon de 64, on peut le multiplier par 4 pour obtenir un nombre
jusque 256, d’où la proposition suivante (n’hésitez pas à faire une figure
pour comprendre) :
98
M INOS
import pygame
from pygame.locals import *
from math import sqrt
img = pygame.Surface((128,128),SRCALPHA)
img.fill((255,255,255,0))
for y in range(128) :
for x in range(128) :
r = int(sqrt((64-x)**2+(64-y)**2))
if r < 64 :
img.set_at((x,y),(255,255,0,4*(63-r)))
pygame.image.save(img,'soleil.png')
import pygame
img = pygame.Surface((128,64))
img.fill((254,245,216))
# Coté gauche
for y in range(32) :
for x in range(y//2,48+y//2) :
img.set_at((x,y),(160,173,192))
img.set_at((x,63-y),(124,138,163))
# Symétrie centrale :
for x in range(64) :
for y in range(64) :
img.set_at((127-x,63-y),img.get_at((x,y)))
pygame.image.save(img,'motif.png')
Retour sur l’exercice C3 On réalise le pavage à l’aide d’une boucle avec
un pas de 128 en abscisse et 64 en ordonnée :
import pygame
from pygame.locals import *
pygame.init()
fenetre = pygame.display.set_mode((640,640))
motif = pygame.image.load('motif.png')
pygame.display.set_caption('Pavage')
pygame.display.set_icon(motif)
for x in range(0, 640, 128) :
for y in range(0, 640, 64) :
fenetre.blit(motif,(x,y))
continuer = True
while continuer:
for event in pygame.event.get():
if event.type == QUIT :
continuer = False
pygame.display.flip()
pygame.quit()
99
Solutions des exercices
Retour sur l’exercice C2 Voici une solution (il y en a plein d’autres) :
C HAPITRE C
Retour sur l’exercice C4 La case de la première question correspond au
code 2+4+8 = 14. Pour la seconde question, on peut décomposer le nombre
7 en 7 = 1 + 2 + 4, il s’agit donc de la case :
Retour sur l’exercice C5 Pour la première question, voici l’intégralité du
labyrinthe :
Pour la seconde, la liste correspondant au dessin est :
[[ 9,
[10,
[12,
7, 9, 3],
9, 2, 14],
6, 12, 7]]
Retour sur l’exercice C6 En appliquant l’algorithme proposé dans l’aide,
on peut faire ainsi :
def mur2bin(n) :
"""
En entrée : un nombre entier n entre 0 et 15
En sortie : une liste de 4 bits représentant le nombre n en binaire
"""
resultat = []
for _ in range(4) :
resultat.append(n%2)
n = n // 2
return resultat
100
M INOS
À noter le nom original de la variable dans la boucle, c’est une variable
comme les autres, on l’utilise parfois pour signifier que l’on ne l’utilise pas
à l’intérieur de la boucle. Vous pouvez mettre i ou j si cela vous perturbe,
cela ne change absolument rien.
Retour sur l’exercice C7 La première idée peut être de faire un raisonnement par disjonction de cas selon les valeurs de m :
Solutions des exercices
def mur(c,m) :
"""
En entrée : c est un entier entre 0 et 15 et m = 1, 2, 4 ou 8
En sortie : Renvoie un booléen indiquant la présence du mur m
dans une case de code c
"""
b = mur2bin(c)
if m == 1 :
if b[0] == 1 :
return True
else :
return False
elif m == 2 :
if b[1] == 1 :
return True
else :
return False
elif m == 4 :
if b[2] == 1 :
return True
else :
return False
else :
if b[3] == 1 :
return True
else :
return False
À noter qu’ici les elif sont inutiles puisque dès que l’on rencontre un
return, on sort de la fonction. On peut d’ailleurs utiliser cette remarque
pour raccourcir le code en traitant d’abord tous les cas VRAI :
def mur(c,m) :
"""
En entrée : c est un entier entre 0 et 15 et m = 1, 2, 4 ou 8
En sortie : Renvoie un booléen indiquant la présence du mur m
dans une case de code c
"""
b = mur2bin(c)
if m == 1 and b[0] == 1 : return True
if m == 2 and b[1] == 1 : return True
if m == 4 and b[2] == 1 : return True
if b[3] == 1 : return True
return False
101
C HAPITRE C
Une autre technique, un peu plus courte encore et surtout plus cohérente
d’un point de vu informatique, peut être de renvoyer le résultat du test,
en effet :
if b[2] == 1 :
return True
else :
return False
peut être remplacé par :
return b[2] == 1
Enfin plutôt que d’utiliser 4 tests pour les valeurs de m, on pourrait utiliser
avantageusement un dictionnaire :
def mur(c,m) :
"""
En entrée : c est un entier entre 0 et 15 et m = 1,2,4 ou 8
En sortie : Renvoie un booléen indiquant la présence du mur m
dans une case de code c
"""
b = mur2bin(c)
indice = {1:0, 2:1, 4:2, 8:3}
return b[indice(m)] == 1
On est passé de 21 lignes au départ à 3 lignes, c’est mieux. On peut faire
encore plus court et plus rapide en une ligne et même sans passer par la
décomposition binaire en utilisant les masques binaires que nous n’aurons pas le temps d’aborder ici.
✍ Plutôt qu’un dictionnaire, les matheux auront remarqué qu’après importation du module math, on peut écrire
b = mur2bin(c)
indice = int(log2(m))
return b[indice(m)] == 1
ln 1
ln2
ln4 2 ln2
= 0, log2 (2) =
= 1, log2 (4) =
=
=2
ln 2
ln2
ln2 ln2
ln8 ln(23 ) 3 ln2
et log2 (8) =
=
=
= 3.
ln2
ln2
ln2
En effet log2 (1) =
102
M INOS
Retour sur l’exercice C8 Il suffit de regarder une à une les cases et de tester la présence d’un mur au Nord et à l’Ouest :
def dessine(L) :
fond = pygame.image.load('fond.jpg')
murs = pygame.image.load('murs.jpg')
for ligne in range(20) :
for colonne in range(20) :
if mur(L[ligne][colonne],1) : # Mur au Nord ?
x, y = colonne * 32, ligne * 32
fond.blit(murs,(x,y),(x,y,36,4))
if mur(L[ligne][colonne],8) : # Mur à Ouest ?
x, y = colonne * 32, ligne * 32
fond.blit(murs,(x,y),(x,y,4,36))
return fond
Pour les murs Nord, on copie un rectangle de taille 36 × 4 ce qui peut
paraı̂tre surprenant car cela dépasse la largeur d’une case mais vous verrez
que les virages sont assez vilains si vous vous restreignez à 32 pixels. Le
résultat en ”débordant” est plus joli.
Retour sur l’exercice C9 Le programme semble fonctionner à première
vue, mais en y regardant de plus près ...
Dès que l’on change une cellule de A, toutes les
>>> A[1][1] = 3
lignes sont modifiées. Après réflexion, on com>>> A
[[0, 3, 0, 0, 0],
prend que lorsque l’on ajoute ligne dans la fonc[0, 3, 0, 0, 0],
tion, c’est l’alias (l’adresse) de la liste qui est copié
[0, 3, 0, 0, 0],
et non son contenu. Les 5 lignes de A pointent donc
[0, 3, 0, 0, 0],
[0, 3, 0, 0, 0]]
sur le même objet et dès que l’on en modifie un, les
autres se retrouvent aussi modifiés.
Avec le code ligne = [0] * 5, cela ne def
se produit pas, car lors d’additions, de
multiplications ou d’extractions de listes,
un nouvel objet est créé. On peut utiliser
cette remarque pour corriger la fonction.
init(n) :
ligne = [0] * n
res = []
for i in range(n) :
res.append(ligne[:])
return res
103
Solutions des exercices
C HAPITRE C
Une autre solution élégante est d’utiliser les listes en compréhension :
def init(n) :
return [[0 for i in range(n)] for j in range(n)]
Ou, un peu plus court :
def init(n) :
return [[0] * n for j in range(n)]
Enfin, l’idée lumineuse suivante ne fonctionne pas non plus :
def init(n) :
return [[0] * n]*n
Car l’objet [0]*5 est créé (une fois), puis recopié n fois.
Retour sur l’exercice C10 Dans la solution proposée ci-dessous, la boucle
sur les j permet de remplir une ligne. Une fois cette ligne complétée, on
ajoute à la liste C et on vide la ligne pour commencer la suivante :
def initCoul(n) :
val = 1
C = []
for i in range(n) :
ligne = []
for j in range(n) :
ligne.append(val)
val = val + 1
C.append(ligne)
return C
104
M INOS
On peut tester :
>>> initCoul(3)
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
En remarquant qu’avec cette fonction, le nombre situé à la ligne i et la
colonne j vaut 1 + j + i × n, on peut programmer la fonction précédente en
une ligne en utilisant des déclarations de listes en compréhension comme
cela a été présenté dans l’exercice précédent :
def initCoul(n) :
return [[1+j+i*n for j in range(n)] for i in range(n)]
Retour sur l’exercice C11 Pour cette question, il n’y a pas vraiment d’astuce pour gagner du temps en factorisant le code. On compare les couleurs
des cases voisines en prenant garde aux bords :
def cassables(couleurs, l, c) :
Lmax = len(couleurs)-1
Cmax = len(couleurs[0])-1
murs = []
if l > 0 and couleurs[l][c] != couleurs[l-1][c] :
murs.append(1)
# Mur au Nord ?
if c < Cmax and couleurs[l][c] != couleurs[l][c+1] :
murs.append(2)
# Mur à l'Est ?
if l < Lmax and couleurs[l][c] != couleurs[l+1][c] :
murs.append(4)
# Mur au Sud ?
if c > 0 and couleurs[l][c] != couleurs[l][c-1] :
murs.append(8)
# Mur à l'Ouest ?
return murs
Retour sur l’exercice C12 Reprenons les 3 étapes détaillées dans l’aide :
● Pour supprimer le mur en question, rien de plus facile.
M[l][c] = M[l][c] - d
● Pour supprimer le mur correspondant, on peut le faire par disjonction de cas :
if d
elif
elif
else
== 1 : M[l-1][c] = M[l-1][c] - 4
d == 2 : M[l][c+1] = M[l][c+1] - 8
d == 4 : M[l+1][c] = M[l+1][c] - 1
: M[l][c-1] = M[l][c-1] - 2
105
Solutions des exercices
C HAPITRE C
Si on veut limiter les if, on peut utiliser un dictionnaire de correspondances :
mur
1
2
4
8
mur correspondant
4
8
1
2
décalage ligne
-1
0
1
0
décalage colonne
0
1
0
-1
corresp = {1 : (4,-1,0), 2 : (8,0,1), 4 : (1,1,0), 8 : (2,0,-1)}
new_d, Dl, Dc = corresp[d]
M[l+Dl][c+Dc] = M[l+Dl][c+Dc] - new_d
ou pour ceux qui aiment les calculs, en utilisant les nombres complexes...(testez pour les 4 valeurs de d possibles, ça fonctionne)
n = int(log2(d))
new_d = 2**((n+2) % 4)
Dl, Dc = int(((1j)**(n+3)).imag), int(((1j)**(n)).imag)
M[l+Dl][c+Dc] = M[l+Dl][c+Dc] - new_d
● Enfin, il faut fusionner les couleurs :
ancienne_couleur = Coul[l][c]
nouvelle_couleur = Coul[l+Dl][c+Dc]
for i in range(len(M)) :
for j in range(len(M[0])) :
if Coul[i][j] == ancienne_couleur :
Coul[i][j] = nouvelle_couleur
Il existe des algorithmes bien plus efficaces pour fusionner les couleurs, mais ce n’est pas le but cherché ici.
Retour sur l’exercice C13 En suivant l’algorithme donné, on initialise une
liste2D de taille n × n ne contenant que la valeur 15 pour modéliser les
murs, puis une seconde liste de couleurs. Tant qu’il reste au moins 2 couleurs dans le labyrinthe, on fusionne deux cases au hasard.
def laby(n):
M = [[15 for i in range(n)] for j in range(n)]
C = initCoul(n)
nb_coul = n**2
while nb_coul > 1 :
l, c = randint(0,n-1), randint(0,n-1)
cass = cassables(C,l,c)
if len(cass) > 0 :
d = choice(cass)
casse(M, C, l, c, d)
nb_coul = nb_coul - 1
return M
106
élément dans une liste.
✍ Dans le code précédent, on a utilisé la fonction choice du module
Ici l’exécution de la fonction est assez rapide (il y a 400 cases à notre
random.mais
Celle-ci
choisit
aléatoirement
et delamanière
équiprobable
un
labyrinthe),
selon
la taille,
il se peut que
casse des
derniers murs
élément
dans
une liste.
prenne
un peu
de temps
(on peut tirer des cases longtemps sans trouver de
murs à casser). On pourrait donc améliorer la fonction de cette manière :
si laIci
case
(l,c) n’a pas
defonction
mur cassable,
au lieu
de tirer
une
case,
l’exécution
de la
est assez
rapide
(il yàanouveau
400 cases
à notre
on
se
décale
vers
la
droite,
puis
vers
le
bas
si
on
arrive
en
bout
de
ligne,
labyrinthe), mais selon la taille, il se peut que la casse des derniers murs
jusqu’à un
en peu
trouver
une qui
: cases longtemps sans trouver de
prenne
de temps
(onconvienne
peut tirer des
murs à casser). On pourrait donc améliorer la fonction de cette manière :
l’exécution
de la
est assez
rapide
(il yàanouveau
400 cases
à notre
si laIci
case
(l,c) n’a pas
defonction
mur cassable,
au lieu
de tirer
une
case,
def
laby(n): mais selon la taille, il se peut que la casse des derniers murs
labyrinthe),
on se
décale
vers
la
droite,
puis
vers
le
bas
si
on
arrive
en
bout
de
ligne,
M = [[15 for i in range(n)] for j in range(n)]
prenne
un
peu
de temps
(onconvienne
peut tirer des
jusqu’à
trouver
une qui
: cases longtemps sans trouver de
C = en
initCoul(n)
murs
à casser).
On
nb_coul
= n**
2 pourrait donc améliorer la fonction de cette manière :
nb_coul
> 1 de
: mur cassable, au lieu de tirer à nouveau une case,
si lawhile
case (l,c)
n’a pas
l, c = randint(0,n-1), randint(0,n-1)
on se
décale
vers
la droite, puis vers le bas si on arrive en bout de ligne,
def
laby(n):
cass =
cassables(C,l,c)
M = en
[[15
for
i une
in range(n)]
j in
while
len(cass)
== 0convienne
: for
# pas
de
mur à casser ?
jusqu’à
trouver
qui
: range(n)]
C = initCoul(n)
c = c + 1
# On se décale vers la droite
nb_coul =
ifn**
c 2
== n :
# Si on arrive au bout de la ligne
while nb_coul
c >
= 1
0 :
# On se place au début
l, c = randint(0,n-1),
randint(0,n-1)
l = l + 1
#
de la ligne suivante
def laby(n):
cassif
=
cassables(C,l,c)
l i
==in
n range(n)]
:
# Sijon
en bas de la grille
M = [[15
for
for
inarrive
range(n)]
while len(cass)
==00 : # On
pasrepart
de murde
à la
casser
?
l, c = 0,
première
case
C = initCoul(n)
c
=**
c2
1
# On se décale vers la droite
cass
=+cassables(C,l,c)
nb_coul =
n
if c ==
n :
:
# Si on arrive au bout de la ligne
d nb_coul
= choice(cass)
while
> 1
cC,
= l,
0 c, d)
# On se place au début
casse(M,
l, c = randint(0,n-1),
randint(0,n-1)
l nb_coul
= l + 1 - 1
#
de la ligne suivante
nb_coul
=
cass = cassables(C,l,c)
l == n : == 0 : #
bas de
return
Miflen(cass)
while
# Si
pason
dearrive
mur à en
casser
? la grille
l,+c1= 0, 0
# On
On se
repart
de la
première
case
c = c
#
décale
vers
la droite
cass
if c =
==cassables(C,l,c)
n :
# Si on arrive au bout de la ligne
d = choice(cass)
c = 0
# On se place au début
casse(M,lC,
= l,
l +c,
1 d)
#
de la ligne suivante
nb_coul
nb_coul
- 1
if l===
n :
# Si on arrive en bas de la grille
return M
l, c = 0, 0
# On repart de la première case
cass = cassables(C,l,c)
d = choice(cass)
casse(M, C, l, c, d)
nb_coul = nb_coul - 1
Retour
sur Ml’exercice C14 Après quelques essais pour placer à bonne
return
hau
teur les éléments, voici un programme au rendu sympathique (une version
avec du son est disponible sur le site) :
107
Retour sur l’exercice C14 Après quelques essais pour placer à bonne hauteur les éléments, voici un programme au rendu sympathique (une version
avec du son est disponible sur le site) :
Retour sur l’exercice C14 Après quelques essais pour placer à bonne hau107
teur les éléments, voici un programme au rendu sympathique (une version
avec du son est disponible sur le site) :
107
Solutions des exercices
✍ Dans le code précédent, on a utilisé la fonction choice du module
M INOS
random. Celle-ci choisit aléatoirement et de manière équiprobable
un
élément dans une liste.
C HAPITRE C
import pygame
from pygame.locals import *
pygame.init()
pygame.display.set_caption('MINOS')
fenetre = pygame.display.set_mode((1280,720))
fond = pygame.image.load('fond.jpg').convert()
perso = [pygame.image.load('S'+str(i)+'.png').convert_alpha() for i in ⤦
� range(6)]
espace = pygame.image.load('espace.png').convert_alpha()
x = -250
espaceT =0
continuer = True
clock = pygame.time.Clock()
while continuer:
clock.tick(100)
for event in pygame.event.get():
if event.type == QUIT :
continuer = False
if event.type == KEYDOWN and event.key == K_SPACE :
continuer = False
fenetre.blit(fond,(0,0))
num_perso = (x//25) % 6
fenetre.blit(perso[num_perso],(x,20))
if espaceT < 20 :
fenetre.blit(espace,(440,300))
espaceT = (espaceT + 1) % 30
x = x + 5
if x > 2000 :
x = -250
pygame.display.flip()
pygame.quit()
Quelques explications : on charge les 6 images de l’animation dans une
liste perso comme cela a été suggéré dans l’aide. La variable x représente
l’abscisse du personnage qui commence à −250 (hors de l’écran à gauche).
Si on change l’image à chaque fois que le personnage avance, l’animation
est trop rapide et non réaliste. Dans notre correction, j’ai choisi de changer
d’image tous les 5 tours de boucle. On peut décider d’utiliser une variable
qui sera incrémentée à chaque fois que x est un multiple de 25 (5 tours de
5 pixels). Une solution plus élégante peut être de déterminer le numéro
de l’image par le calcul (x // 25) % 6, une explication illustrée sur les
premières valeurs de x permet de mieux comprendre :
x −250
−200
−150
−100
−50
0
x // 25 -10 -9 -8 -7 -6 -5 -4 -3 -2 -1
(x//25)%6
108
2
3
4
5
0
1
2
3
4
5
100
150
200
0
1
50
2
3
4
5
6
7
8
9
0
1
2
3
4
5
0
1
2
3
M INOS
M INOS
On procède de même avec la variable espaceT qui varie sur [0; 30[.
M INOS
Entre
et 19, l’image
est affichée.
LorsqueespaceT
cette variable
arrive
à 30,
elle
On0procède
de même
avec la variable
qui varie
sur
[0; 30[.
Entre
0 età 19,
l’image
est affichée.
Lorsque
cette variable
arrive
à 30, elle
espaceT
= (espaceT
+ 1) % 30
retourne
0 grâce
au calcul
: qui est une
On
procède
de
même
avec
la
variable
espaceT
qui
varie
sur
[0; 30[.
espaceT = (espaceT + 1) % 30
retourne
à 0 grâce
aud’écrire
calcul :: qui est une
manière
plus
simple
Entre 0 et 19, l’image est affichée. Lorsque cette variable arrive à 30, elle
manière plus simple d’écrire :
espaceT = (espaceT + 1) % 30
retourne à 0 grâce au calcul : qui est une
espaceT = espaceT + 1
if
espaceT
== simple
30 :
manière
espaceT
=plus
espaceT
+ 1 d’écrire
espaceT = 0
if
espaceT
==
30
:
espaceT = 0
espaceT = espaceT + 1
if espaceT == 30 :
espaceT = 0
:
Retour sur l’exercice C15 Avec la formule présentée dans l’aide, on obtient le sur
code
suivant C15
:
Retour
l’exercice
Avec la formule présentée dans l’aide, on obtient le code suivant :
Retour
sur l’exercice
Avec
def distance(l1,
c1, C15
l2, c2)
: la formule présentée dans l’aide, on obreturn
abs(c2-c1)+abs(l2-l1)
tient
le code suivant
def distance(l1,
c1, :l2, c2) :
return abs(c2-c1)+abs(l2-l1)
def distance(l1, c1, l2, c2) :
return
À noter
queabs(c2-c1)+abs(l2-l1)
la fonction abs est présente dans Python sans avoir à importer
le
module
À noter
quemath.
la fonction abs est présente dans Python sans avoir à importer
le module math.
À noter que la fonction abs est présente dans Python sans avoir à importer
le module math.
Retour sur l’exercice C16 On commence par importer les modules et les
fonctions
(les codes
ne sont pas
redonnés)
Retour
surnécessaires
l’exercice C16
On commence
par
importer: les modules et les
fonctions nécessaires (les codes ne sont pas redonnés) :
Retour
sur l’exercice C16 On commence par importer les modules et les
import pygame
from
pygame.locals
import
*
fonctions
nécessaires
(les codes
ne sont pas redonnés) :
import pygame
from random import randint, choice
from pygame.locals import *
from random import randint, choice
def distance(l1, c1, l2, c2) : ...
import pygame
def initCoul(n) : ...
fromdistance(l1,
pygame.locals
import
def
c1,
l2, c2) : ...
def cassables(couleurs, l,*c) : ...
from
random import
randint, choice
def initCoul(n)
: ...
def casse(M, Coul, l, c, d) : ...
def cassables(couleurs, l, c) : ...
def laby(n): ...
distance(l1,
c1,
c2)::...
...
def casse(M,
Coul,
l,l2,
c, d)
def mur2bin(n) : ...
laby(n): ...: ...
def initCoul(n)
def mur(c,m) : ...
mur2bin(n) : ...
def cassables(couleurs,
l, c) : ...
def dessine(L) : ...
mur(c,m) Coul,
: ... l, c, d) : ...
def casse(M,
def laby(n):
...
dessine(L)
: ...
def mur2bin(n) : ...
def mur(c,m) : ...
programme
principal ne devrait
def Le
dessine(L)
: ...
pas poser de problème :
Le programme principal ne devrait pas poser de problème :
Le programme principal ne devrait pas poser de problème :
109
109
109
Solutions des exercices
C HAPITRE C
pygame.init()
pygame.display.set_caption('MINOS')
fenetre = pygame.display.set_mode((644, 644))
perso = pygame.image.load('perso.png').convert_alpha()
arrivee = pygame.image.load('arrivee.png').convert_alpha()
pygame.display.set_icon(perso)
continuer = True
Touches = []
L = laby(20)
ImgL = dessine(L)
lD, cD, lA, cA = [randint(0,19) for _ in range(4)]
while distance(lD, cD, lA, cA) < 10 :
lD, cD, lA, cA = [randint(0,19) for _ in range(4)]
clock = pygame.time.Clock()
while continuer:
clock.tick(10)
for event in pygame.event.get():
if event.type == QUIT :
continuer = False
elif event.type == KEYDOWN and event.key not in Touches :
Touches.append(event.key)
elif event.type == KEYUP and event.key in Touches :
Touches.remove(event.key)
m = L[lD][cD]
if K_UP in Touches and not mur(m,1) :
lD = lD - 1
elif K_DOWN in Touches and not mur(m,4) :
lD = lD + 1
elif K_LEFT in Touches and not mur(m,8):
cD = cD - 1
elif K_RIGHT in Touches and not mur(m,2):
cD = cD + 1
fenetre.blit(ImgL,(0,0))
fenetre.blit(arrivee,(cA*32+4,lA*32+4))
fenetre.blit(perso,(cD*32+4,lD*32+4))
if distance(lD, cD, lA, cA) == 0 :
print('GAGNE !')
continuer = False
pygame.display.flip()
pygame.quit()
Quelques remarques :
●
lD, cD, lA, cA = [randint(0,19) for _ in range(4)]
Permet en une ligne de choisir 4 valeurs aléatoires en générant une
liste de 4 nombres que l’on stocke directement dans ces 4 variables.
● On a baissé le nombre de FPS à 10 pour que le jeu ne soit pas trop
rapide puisque l’on se promène de case en case.
● Dans les tests de touches, on utilise des elif au lieu de if pour
empêcher le joueur d’aller en diagonale.
110
M INOS
Retour sur l’exercice C17 Avec les fonctions précédemment définies, on
applique l’algorithme proposé :
j in range(nb_lignes)]
and sol[li-1][co] == -1 :
+ 1
and sol[li][co+1] == -1 :
+ 1
and sol[li+1][co] == -1 :
+ 1
and sol[li][co-1] == -1 :
+ 1
Retour sur l’exercice C18 On implémente la fin de la technique proposée :
dans le code qui suit, les variables l et c indiquent la case où l’on se trouve
à l’instant présent, qui sont donc initialisées à la case d’arrivée. La variable pas représente la distance qu’il reste à parcourir pour rejoindre la
case de départ. On parcourt alors le chemin à l’envers par valeurs strictement décroissantes de pas, on choisit une (seule) des 4 directions (d’où les
elif).
def solution(M, l1, c1, l2, c2) :
sol = dist1(M, l1, c1, l2, c2)
pas, l, c = sol[l2][c2], l2, c2
chemin = [(l,c)]
while pas > 0 :
if not mur(M[l][c],1) and sol[l-1][c] == pas - 1 :
# au Nord ?
l = l - 1
elif not mur(M[l][c],2) and sol[l][c+1] == pas - 1 : # à l'Est ?
c = c + 1
elif not mur(M[l][c],4) and sol[l+1][c] == pas - 1 : # au Sud ?
l = l + 1
else :
# à l'Ouest ?
c = c - 1
chemin.insert(0,(l,c))
pas = pas - 1
return chemin
111
Solutions des exercices
def dist1(M, l1, c1, l2, c2) :
nb_lignes, nb_cols = len(M), len(M[0])
sol = [[-1 for i in range(nb_cols)] for
pas = 0
sol[l1][c1] = 0
while sol[l2][c2] == -1 :
for li in range(nb_lignes) :
for co in range(nb_cols) :
if sol[li][co] == pas :
# Au Nord ?
if not mur(M[li][co],1)
sol[li-1][co] = pas
# A l'Est ?
if not mur(M[li][co],2)
sol[li][co+1] = pas
# Au Sud ?
if not mur(M[li][co],4)
sol[li+1][co] = pas
# A l'Ouest ?
if not mur(M[li][co],8)
sol[li][co-1] = pas
pas = pas + 1
return sol
C HAPITRE C
À noter la présence du insert qui permet d’insérer la nouvelle case en
tête de liste et non à la fin comme avec un append (sinon il vous faudra
retourner le chemin généré avant de le renvoyer).
Retour sur l’exercice C19 Le code de la fonction tient en 2 lignes, après
avoir chargé l’image caillou.png :
caillou = pygame.image.load('caillou.png').convert_alpha()
...
def represente(L) :
for l,c in L :
fenetre.blit(caillou,(c*32+14,l*32+14))
Remarquez avec quelle simplicité on peut réaliser une boucle sur les
couples (l,c) présents dans la liste L.
Sur le site une version mettant en place cette fonction dans le projet
final peut être téléchargée en tapant le numéro de cet exercice.
Retour sur l’exercice C20 C’est le même principe que pour dist1, mais la
double boucle for est remplacée par l’étude de la case (l, c) que l’on vient
récupérer à la tête de la file. En temps normal, il faudrait s’assurer que la
file n’est pas vide avant de se servir, mais comme ici on est en présence
d’un labyrinthe parfait, on est certain de trouver un chemin :
def dist2(M, l1, c1, l2, c2) :
nb_lignes, nb_cols = len(M), len(M[0])
sol = [[-1 for i in range(nb_cols)] for j in
sol[l1][c1] = 0
AVoir = [(l1,c1)]
l, c = (l1,c1)
while (l, c) != (l2, c2) :
l, c = AVoir.pop(0)
# Nouvelle case
pas = sol[l][c]
# En haut ?
if not mur(M[l][c],1) and sol[l-1][c] ==
sol[l-1][c] = pas + 1
AVoir.append((l-1,c))
# Future
# A droite ?
if not mur(M[l][c],2) and sol[l][c+1] ==
sol[l][c+1] = pas + 1
AVoir.append((l,c+1))
# En bas ?
if not mur(M[l][c],4) and sol[l+1][c] ==
sol[l+1][c] = pas + 1
AVoir.append((l+1,c))
# A gauche ?
if not mur(M[l][c],8) and sol[l][c-1] ==
sol[l][c-1] = pas + 1
AVoir.append((l,c-1))
return sol
112
range(nb_lignes)]
-1 :
case à visiter
-1 :
-1 :
-1 :
Chapitre D
Objectif
Programmer un jeu de mots mêlés
Programmation
Chaı̂nes et encodage des caractères
Les expressions régulières
Algorithmique
Générer une grille de jeu
python
Le package urllib
Pygame
Gestion des textes
Gestion des événements souris
Dessiner des surfaces
SELEM-STOM
Pour changer, nous allons réaliser un jeu un peu plus cérébral de motsmêlés. Pour ceux qui ne connaissent pas, il s’agit de retrouver des mots
cachés dans une grille de lettres, les mots pouvant être écrits dans huit
directions, comme l’illustre l’exemple ci-dessous représentant une partie
en cours.
MOMEL
113
C HAPITRE D
1 - Quelques fonctions pour commencer
Dans ce projet, une grille de jeu sera modélisée par une liste2D de
caractères. Lorsque l’on parlera d’une case de la grille cela signifiera un
couple sous la forme (ligne,colonne). Dans la grille G1, la case (1,3) contient
la lettre X.
T S A M
>>> G1=[['T','S','A','M'],
['Q','E','B','X'],
Q E B X
['O','C','R','E'],
['C','O','U','P']]
>>> G1[1][3]
'X'
O C R E
C O U P
E X D1 Écrire une fonction affiche qui reçoit une
grille, et affiche proprement la grille dans la console.
grille G1
>>> affiche(G1)
TSAM
QEBX
OCRE
COUP
On souhaite à présent lire un mot situé entre deux cases dans une
grille.
E X D2 Avant de lire le mot, il faut s’assurer qu’il existe un déplacement valide
pour lire d’une case à l’autre.
1. Écrire une fonction direction(K1,K2) qui reçoit deux cases et retourne le couple indiquant le déplacement à suivre sous la forme d’un couple
(Δligne ,Δcolonne ) pour se déplacer de la case K1 à la case K2 si cela est possible. La fonction renvoie False dans le cas contraire. Par exemple :
(−1, −1)
>>> direction((0,0),(2,1))
False
>>> direction((0,0),(2,2))
(1, 1)
>>> direction((0,3),(2,1))
(1, -1)
(−1, 0)
(−1, 1)
(0, −1)
(0, 1)
(1, −1)
(1, 0)
(1, 1)
2. Écrire alors la fonction lis mot qui reçoit une grille et deux cases et retourne le mot formé en lisant de la première à la deuxième case lorsque cela
est possible. Dans le cas contraire, le mot vide est renvoyé.
>>> lis_mot(G1, (2,0), (2,3))
'OCRE'
>>> lis_mot(G1, (3,0), (1,0))
'COQ'
>>> lis_mot(G1, (3,3), (0,0))
'PRET'
114
>>> lis_mot(G1, (0,1), (2,1))
'SEC'
>>> lis_mot(G1, (2,1), (0,1))
'CES'
>>> lis_mot(G1, (3,3), (0,2))
''
SELEM-STOM
Pour ne pas nous lasser trop rapidement, nous allons avoir à générer
une grille de jeu aléatoire. Pour remplir celle-ci, il faudra choisir des mots
parmi une liste de possibilités.
E X D3 Imaginer une fonction choisir, qui reçoit une liste de mots et en tire un
au hasard de manière à ce que la probabilité de tirage d’un mot soit proportionnelle
à sa longueur. Ainsi, un mot de 8 lettres aura deux fois plus de chance d’être
choisi qu’un mot de 4, ce qui permettra d’éviter les trop petits mots dans la grille.
Pour tester votre fonction, vous pouvez utiliser le code suivant qui utilise un
dictionnaire vu au premier chapitre : le mot CHAT devrait apparaı̂tre environ
deux fois moins que BALEINES.
mots = ['CHAT', 'LAPIN', 'BALEINES']
nb = {'CHAT' : 0, 'LAPIN': 0 , 'BALEINES' : 0}
for i in range(1000) :
c = choisir(mots)
nb[c] = nb[c] + 1
print(nb)
Une fois qu’un mot m aura été choisi et placé dans la grille, il est
préférable de le supprimer de la liste des mots possibles. Il faudra supprimer aussi les facteurs du mot m, c’est à dire les mots inclus dans m
ainsi que les mots contenant m. Par exemple, si on a placé BOITE, il faut
s’interdire les mots BOIT ou BOITER ou encore DEBOITER.
E X D4 Écrire une fonction supprime telle que supprime(L,m) efface dans
la liste L le mot m ainsi que ceux où l’on retrouve m. La fonction ne renvoie rien,
mais la liste L est modifiée.
En tapant le numéro de l’exercice qui suit, vous pourrez télécharger
sur le site un fichier contenant plus de 3500 mots écrits en majuscules sans
accent et d’au minimum 4 lettres. Nous allons charger ce fichier dans une
liste de mots.
E X D5 Écrire une fonction lis fichier qui reçoit une chaı̂ne de caractères
indiquant un nom de fichier texte et retourne une liste où chaque élément est
une ligne de ce fichier. Vérifier que Python vous indique bien par exemple que le
premier mot a une longueur de 8 ! Se reporter au tome 1, chapitre E si vous voulez
revoir comment lire un fichier.
115
C HAPITRE D
E X D6 Imaginer une fonction init qui reçoit un entier n et une liste L et ren-
voie un couple formé d’un mot choisi au hasard dans la liste L et d’une liste2D
de taille n×n où ce mot a été placé quelque part verticalement. Les autres cases
vides sont identifiées par le caractère (caractère underscore) . Avant de retourner la grille, on effacera de L les mots proches du mot choisi, comme on l’a fait
précédemment.
2 - Remplissage et expressions régulières
Nous voilà donc en présence d’une grille qui contient
un mot et de nombreux trous qu’il va falloir remplir
étape par étape. Imaginons que nous ayons déjà placé
les mots SABLE, SOIE et FLUTE comme sur la grille
G2 et que nous cherchions à placer un mot à la ligne
d’indice 1 de gauche à droite. On peut y placer des
mots :
E
I
O
T
U
S A B L E
F
grille G2
● de 5 lettres : BOITE, FUITE, PUITS, SUITE, ...
● de 4 lettres en collant à gauche : FAIT, HUIT, LAIT, NUIT, TOIT, ...
● de 4 lettres en collant à droite : CITE, GITE, VITE, ...
Il s’agit donc de déterminer tous les mots correspondant au motif IT
où le caractère peut être une lettre ou vide... En réalité le problème n’est
pas si simple car dans cette même grille à la ligne suivante, le motif est
O U , pour autant le symbole du milieu ne peut être un caractère vide :
il faut nécessairement une lettre entre le O et le U.
Une fois n’est pas coutume, nous allons scinder notre problème en plusieurs fonctions.
E X D7 Écrire une fonction debutOK(motif, mot) et qui retourne un
booléen indiquant si le mot respecte ou non le motif donné où seuls les symboles
de fin de motif peuvent être vides. Voici un certain nombre d’exemples que je
vous invite à tester.
116
SELEM-STOM
>>> debutOK('__IT_','BOITE')
True
>>> debutOK('__IT_','FAIT')
True
>>> debutOK('__IT_','CITE')
False
>>> debutOK('__IT_','FUITER')
False
>>> debutOK('__IT_','ICI')
True
>>> debutOK('__IT_','HA')
True
Nous pouvons alors terminer le travail demandé :
E X D8 Programmer une fonction possible(motif, mot) qui indique par
un nombre si le mot respecte ou non le motif donné. La fonction retourne -1
si le mot ne convient pas, sinon, elle retourne le nombre de cases à décaler pour
placer le mot. Par exemple :
>>> possible('__IT_','BOITE')
0
>>> possible('__IT_','FAIT')
0
>>> possible('__IT_','CITE')
1
>>> possible('__IT_','FUITER')
-1
>>> possible('__IT_','ITT')
2
>>> possible('__IT_','Z')
0
Bien compliqué tout cela... mais nous allons pouvoir simplifier (un
peu) les choses ! Dans les années 1960, Stephen Cole Kleene travaille sur
les ensembles de mots et cherche une manière simple de décrire ces ensembles. Ainsi naissent les expressions régulières : des expressions sous
forme de chaı̂nes de caractères, respectant une certaine syntaxe et
représentant un ensemble de chaı̂nes de caractères possibles.
De nos jours, tous les langages évolués permettent de travailler sur
les expressions régulières, le langage Python ne fait pas exception avec le
module re présent dans toutes les distributions.
Étudions de plus près quelques règles et les caractères spéciaux permettant de générer ces expressions régulières (on utilise parfois le terme
regex(regular expression) en abrégé). Comme d’habitude, il ne s’agit que
d’un premier tour d’horizon, les expressions régulières pouvant à elles
seules constituer un livre. Dans les exemples qui suivent, une expression
régulière est donnée (▸), suivie d’exemples de mots reconnus (✓) par cette
expression ou non (✘).
117
C HAPITRE D
Indiquer les caractères autorisés : on peut
● donner simplement les caractères
▸ PYTHON
✓ PYTHON
✘ Python
● utiliser le point, qui signifie ”n’importe quel caractère”
▸ .AGE
✓ GAGE, MAGE, RAGE
✘ AGE, NAGER, ETAGE
● utiliser des crochets pour donner un ensemble de possibilités : ici un
mot commençant par une lettre de B à F et se terminant par un T ou
un L.
▸ [B-F].UI[TL]
✓ BRUIT, DEUIL, FRUIT
✘ PLUIE, SEUIL
● utiliser des parenthèses pour faire des groupes
● utiliser le symbole ∣ pour indiquer un choix (ou exclusif)
▸ B[AEIOU](CH|LL|T)E
✓BULLE, BETE, BUCHE
✘BOTTE, BOUCHE
✍ Lorsque l’on met des crochets, on peut utiliser le symbole - pour
signifier jusqu’à. Par exemple [A-Za-z] signifie ”une lettre minuscule
ou majuscule” alors que [0-9] signifie ”un chiffre”.
Voici un petit exercice pour voir si les explications ont été claires !
E X D9 Testez le motif [CHO][AEIOU].., le motif (CHO)[AEIOU].., le motif (CH|O)[AEIOU].. et enfin [C-O][AEIOU].. sur les mots suivants :
CAFE, CHOEUR, CHAIR, OEUF, CHENE et NOIX.
118
SELEM-STOM
SELEM-STOM
Avec le module re, on peut tester des
des expressions
expressions régulières
régulières sur
sur des
des
chaı̂nes de caractères :
● fullmatch(m,ch) : teste si la chaı̂ne
chaı̂ne ch
ch est
est un
un représentant
représentantdu
dumomotif m.
● match(m,ch) : même effet que la fonction
fonction précédente,
précédente,mais
mais teste
testesisi
la chaı̂ne ch commence par un représentant
représentant du
du motif
motif m.
m.
● search(m,ch) : même effet que la fonction
fonction précédente,
précédente, mais
mais teste
teste
si la chaı̂ne ch contient un représentant
représentant du
du motif
motif m.
m.
Ces trois fonctions renvoient None (soit
(soit "Rien")
"Rien") si
si le
le test
test n’est
n’est pas
pas
concluant, sinon elles renvoient un objet
objet de
de type
type sre.SRE
sre.SRE Match
Match
sur lequel nous reviendrons un peu plus
plus loin.
loin. À
À savoir
savoir que
que le
le None
None
se comporte comme un False lors d’un
d’un test.
test. Par
Par exemple,
exemple, on
on peut
peut
tester les réponses de l’exercice précédent
précédent ainsi
ainsi (code
(code MATCH
MATCH pour
pour
télécharger l’exemple) :
import re
motifs = ['[CHO][AEIOU]..', '(CHO)[AEIOU]..',
'(CHO)[AEIOU]..',
'(CH|O)[AEIOU]..', '[C-O][AEIOU]..']
'[C-O][AEIOU]..']
chaı̂nes = ['CAFE', 'CHOEUR', 'CHAIR',
'CHAIR', 'OEUF',
'OEUF', 'CHENE',
'CHENE', 'NOIX']
'NOIX']
for m in motifs :
print("-----> Motif",m)
for ch in chaı̂nes :
if re.fullmatch(m, ch) ::
print('OUI :', ch)
else :
print('NON :', ch)
D’autres informations de recherche peuvent
peuvent être
être présentes
présentesdans
dansles
lesexexpressions régulières comme des contraintes
contraintes de
de répétitions
répétitions ::
Indiquer des répétitions :
● ? : signifie une fois ou aucune fois
● + : signifie au moins une fois
● * : signifie aucune, une ou plusieurs fois
fois
● {n} : exactement n fois
● {n1 , n2 } : entre n1 et n2 fois (inclus)
● {, n2 } : entre 0 et n2 fois
● {n1 , } : au moins n1 fois
● Si on veut répéter des groupes de symboles,
symboles, on
on utilise
utilisedes
desparenthèses.
parenthèses.
119
119
C HAPITRE D
E X D10 Écrire une expression régulière permettant de reconnaı̂tre si une chaı̂ne
de caractères représente un nombre (entier ou décimal).
E X D11 Source Wikipédia : Une adresse IP est un numéro d’identification qui est
attribué de façon permanente ou provisoire à chaque appareil connecté à un réseau
informatique utilisant l’Internet Protocol. Il existe des adresses IP de version 4
(sur 32 bits, soit 4 octets) et de version 6 (sur 128 bits, soit 16 octets). La version
4 est actuellement la plus utilisée. Elle est généralement représentée en notation
décimale avec quatre nombres compris entre 0 et 255, séparés par des points, ce
qui donne par exemple : 212.85.150.134. Les plages d’adresses IPv4 étant proches
de la saturation, les opérateurs incitent à la transition d’IPv4 vers IPv6.
Écrire une fonction qui reçoit une chaı̂ne de caractères et, à l’aide d’expressions
régulières, renvoie un booléen indiquant si cette chaı̂ne représente une adresse
IPv4 valide ou non.
✍ Si on veut détecter le caractère + ou ? ou un autre caractère utilisé
dans la syntaxe des regex, on utilise le / comme dans l’exercice qui
suit.
E X D12 Trouver une expression régulière permettant de vérifier si une chaı̂ne
de caractères peut représenter un numéro de téléphone portable. Par exemple le
numéro 07 65 43 21 00 peut être saisi de nombreuses manières : "0765432100",
"07 65 43 21 00", "07.65.43.21.00", "+33 7 65 43 21 00"...
Revenons à notre problème sur un exemple : imaginons que nous souhaitions placer un mot dans cet espace de 6 cases :
A
E
.
● Une première idée est de chercher les mots représentés par l’expression régulière "..A.E." comme le mot PLAIES.
● Mais des mots plus courts peuvent aussi convenir, on peut alors penser à rendre optionnel les places de début et de fin ".{,2}A.E.?".
Cette fois, on a en plus les mots PLAIE, RARE... mais il nous manque
encore le mot RAT, si on ne prend pas le E...
● Ajoutons alors que le mot peut se terminer par A, A , A E ou A E
avec le motif : ".{,2}(A|A.|A.E|A.E.)" ou plus court avec le
motif ".{,2}(A|A.|A.E|A.E.?)". Le mot RAT est alors reconnu !
120
SELEM-STOM
Malheureusement cette dernière solution n’est toujours pas satisfaisante.
Le mot CES utilisant le E n’est toujours pas représenté dans cette expression... Pas simple.
Je vous propose alors l’algorithme suivant illustré avec l’espace de
A
E
6 cases
.
Soit n le nombre de lettres du mot que l’on cherche à placer.
Soit N le nombre de cases disponibles.
SI n > N ALORS :
le mot ne convient pas
SINON :
on fait ≪ glisser ≫ une fenêtre de n cases sur l’espace
et l’on compare avec le mot. Par exemple si le mot
est composé de 4 lettres, on testera s’il est de la forme
A
ou
A
E ou A
E
E X D13 Réécrire la fonction possible de l’exercice 8 en utilisant l’algorithme
précédent et les expressions régulières. On refusera un mot de la forme
qui n’aurait alors aucune lettre commune avec le reste de la grille.
Pour remplir la grille, nous choisirons aléatoirement une case vide de
la grille, puis une direction.
E X D14 Écrire une fonction emplacement qui reçoit
une grille et qui :
● choisit une case vide, ici ☀(2,3) ;
● une direction, ici (-1,1) ;
● ”recule” jusqu’à un bord de la grille ;
● renvoie les coordonnées de la case en bordure ○, la
direction et le motif à chercher. S’il n’y a plus de case
vide, la fonction renvoie -1.
E
I
O R ☀
S A B L E
A
grille G3
Dans notre exemple :
>>> emplacement(G3)
[(4, 1), (-1, 1), '_B__']
En utilisant les fonctions précédemment programmées, nous pouvons
enfin réaliser la fonction qui génère une grille de jeu.
E X D15 Écrire une fonction genereGrille qui reçoit un entier n et retourne
un couple (G,L) où G est une grille de jeu de taille n × n et L la liste des mots à
trouver.
121
C HAPITRE D
3 - Dessiner la grille de jeu
Dans cette partie, nous allons nous intéresser à la représentation de
la grille, il va donc falloir apprendre à écrire du texte pour générer une
image. Dans le projet PONG, la partie qui affichait le score avait déjà été
programmée, regardons à présent le module font de plus près.
Pour écrire un texte, il faut créer un objet de type Font (Font signifie
police de caractères en français).
● Font(f ich, s) renvoie un objet représentant la police du fichier
f ich et de taille s. On peut ajouter bold = True ou italic =
True si l’on souhaite. Si f ich vaut None la police par défaut du
système est chargée.
On peut donc fournir le fichier contenant la police désirée en
même temps que le projet, ou chercher dans les polices déjà présentes
dans le système d’exploitation :
● get fonts() retourne la liste des polices installées dans le système
d’exploitation.
● match font(txt) retourne, sous forme de chaı̂ne, le chemin de la
police contenant le mot txt. On peut donner plusieurs noms en les
séparant par des virgules. Si aucune police n’est trouvée, None est
retourné (la police par défaut sera alors chargée).
>>> pygame.font.match_font('arial, courier')
'C:\\WINDOWS\\Fonts\\ARIALN.TTF'
Une fois l’objet Font créé, il n’est plus possible de modifier sa taille.
On peut alors créer une surface en générant un texte :
● f nt.render(txt,l,c) retourne une surface contenant le texte txt
écrit avec la police f nt et avec la couleur c donnée sous forme d’un
triplet (R,V,B). Le paramètre l (appelé antialias) est un booléen indiquant si le texte doit être lissé. On peut aussi préciser un fond en
ajoutant background=.... S’il n’est pas précisé, le fond est transparent. Un exemple simple :
122
SELEM-STOM
import pygame
pygame.init()
fichier = pygame.font.match_font('arial, courier')
fnt = pygame.font.Font(fichier, 40)
s = fnt.render("COUCOU", True, (255,0,0) )
pygame.image.save(s, 'demo.png')
pygame.quit()
● f nt.size(txt) retourne un couple indiquant la largeur et la hauteur en pixels du texte txt écrit avec la police f nt.
E X D16 Écrire un programme où le texte ”WINNERS
DON’T USE DRUGS” rebondit à l’écran. À chaque rebond, on incrémente de 1 la taille du texte. Ce slogan
était présent sur les bornes d’arcades américaines entre les
années 1989 et 2000.
À partir d’une grille donnée sous forme de liste2D, nous allons devoir générer une image la représentant. La taille des cases va dépendre
de la place dont on dispose à l’écran et du nombre de cases de la grille à
représenter de manière à maximiser l’espace disponible.
E X D17 Écrire une fonction calcule(G, l, h) qui retourne un triplet
(c,Δx ,Δy ) indiquant la taille intérieure c (pour côté) maximale d’une case, ainsi
que le décalage à effectuer à gauche (Δx ) et en haut (Δy ) pour placer la première
case comme l’illustre le schéma ci-dessous pour une grille de 3 lignes et 4 colonnes.
l
Δy
h
c
Δx
123
C HAPITRE D
E X D18 Imaginer une fonction taillePolice(G,c) qui détermine la plus
grande taille de police possible pour que les lettres de la grille G ne dépassent pas
c pixels.
Il n’y a plus qu’à dessiner cette fameuse grille.
E X D19 Écrire une fonction dessine(G, l, h) qui retourne une surface
représentant la grille G sur une image de taille l×h. La taille de la grille sera
calculée afin de maximiser l’espace.
Il faut encore afficher sur le côté de la grille les mots à trouver.
E X D20 Programmer une fonction dessine mots(M1, M2) qui reçoit deux
listes de mots, la première contient les mots à trouver et la seconde les mots déjà
trouvés et qui renvoie une surface de taille 300 × 700 où les mots à trouver sont
écrits d’une couleur, suivis des mots déjà trouvés d’une autre. On pourra mettre
plusieurs mots par ligne en prenant garde de ne pas dépasser l’espace alloué.
E X D21 Enfin vous pouvez assembler toutes ces fonctions et avoir un début de
jeu. Par exemple, la fenêtre mesurera 1000 × 700 pixels : une grille de 700 × 700
et la liste sur la droite.
124
SELEM-STOM
4 - Gestion de la souris
Comme pour le clavier, on peut surveiller grâce à Pygame les actions
de la souris.
3 types d’événements sont reconnus :
● MOUSEBUTTONDOWN lorsqu’un bouton de la souris est enfoncé.
● MOUSEBUTTONUP lorsqu’un bouton est relâché.
● MOUSEMOTION lorsque la souris est déplacée.
À noter que l’utilisation de la roulette de la souris est assimilée à l’utilisation d’un bouton (bouton n°4 lorsque l’on roule vers le haut et n°5 vers
le bas).
Lorsqu’un événement souris evt est capturé, deux attributs permettent
d’en savoir plus :
● evt.pos : indique sous la forme d’un couple (x,y) la position du
pointeur de la souris au moment de l’événement.
● evt.button : Indique le numéro du bouton en action (sur les
événements de type MOUSEBUTTONDOWN et MOUSEBUTTONUP).
E X D22 Nous allons créer une pastille transparente qui va suivre la souris sur
la grille.
1. Écrire une fonction pastille(r, coul) qui renvoie une surface
représentant un disque de rayon r et de couleur coul où coul est un
quadruplet indiquant une couleur RVGA.
2. Compléter votre programme pour que cette pastille (de diamètre la moitié de
la taille d’une case) suive votre souris (centre sur le pointeur de la souris).
À présent nous allons modéliser le surlignage d’un mot par un stylo :
lorsque l’on déplace la souris avec le bouton enfoncé, la pastille laisse
derrière elle une traı̂née...
E X D23 Programmer cet effet de manière à ce que le marquage disparaisse dès
que le bouton est relâché.
125
C HAPITRE D
E X D24 Pour la suite, nous allons devoir convertir les coordonnées cartésiennes
de la souris (x,y) en numéros de ligne et de colonne (l,c) correspondants.
1. Écrire une fonction XY2Case(XY, l, Dx, Dy, N) qui reçoit un
couple XY de coordonnées cartésiennes, la largeur intérieure l d’une case,
le décalage (Dx, Dy) du coin supérieur gauche de la grille de taille N × N par
rapport à la fenêtre. Cette fonction renvoie un couple (l,c) indiquant la
case présente sous le point de coordonnées XY.
2. Écrire une fonction Case2XY(C, larg, Dx, Dy) ayant l’effet
inverse : connaissant la ligne et la colonne d’une case, elle renvoie les coordonnées du point situé en haut à gauche de cette case.
Lorsque l’on relâche le bouton, il nous faut étudier le mot entre les
2 cases sélectionnées.
E X D25 Programmer l’amélioration suivante : si c’est bien un mot à trouver,
● on colorie de façon nette ce mot,
● on le déplace dans la liste des mots trouvés.
Si vous êtes arrivé au bout de l’exercice, vous aurez rapidement remarqué que le fait d’utiliser une pastille, même transparente, pour dessiner un trait de stylo, efface les lettres d’origine. On aimerait dessiner les
lettres par-dessus ces marques. Or la fonction dessine représente cette
grille sur un fond noir. Une idée pourrait consister à modifier la fonction pour qu’elle dessine sur un fond transparent. Nous allons plutôt nous
orienter vers une autre technique qui utilise des paramètres
optionnels pour notre fonction. Actuellement la fonction dessine commence ainsi (la mienne en tout cas, mais je pense que la vôtre doit être
assez semblable) :
def dessine(G, l, h) :
c, Dx, Dy = calcule(G, l, h)
surf = pygame.Surface((l,h))
surf.fill((0,0,0))
lG, cG = len(G), len(G[0])
...
126
SELEM-STOM
Les différentes lettres sont ensuite placées sur la surface surf. Nous
allons modifier la fonction ainsi :
def dessine(G, l, h, surf=None) :
c, Dx, Dy = calcule(G, l, h)
# dessine les lignes
if surf == None :
surf = pygame.Surface((l,h))
surf.fill((0,0,0))
lG, cG = len(G), len(G[0])
...
Ainsi, si le quatrième paramètre n’est pas précisé, il vaut None, et la variable surf est initialisée comme une surface vide. Dans le cas contraire,
on dessinera les lettres au-dessus de la surface surf donnée en argument.
Il suffit alors d’ajouter : imgG = dessine(G,700,700,imgG) dans
notre code pour redessiner les lettres par-dessus notre marque de stylo.
def f onc(a=val) : permet de donner une valeur par défaut au paramètre a de la fonction f onc si celui-ci n’est pas précisé.
Ainsi, le projet du jeu de mots mêlés se termine, comme les autres fois,
vous pouvez télécharger la version avec le code MOMEL .
E X D26 On peut encore réaliser quelques améliorations :
1. Changer la couleur de la pastille à chaque mot trouvé.
2. Écrire ≪ GAGNE ! ≫ à la fin du jeu.
3. Mettre un nombre aléatoire de cases.
5 - Une grille à thème !
Dans cette dernière partie, je vous propose d’étudier la possibilité de
réaliser des grilles à thèmes. Pour cela, une petite visite sur le site
https://www.rimessolides.com/ va orienter vos recherches... Cet
excellent site réalisé par Éric Desrosiers permet, entre autres, d’afficher un
ensemble de mots du même champ lexical qu’un mot donné.
127
C HAPITRE
C HAPITRE
C HAPITRE
C HAPITRE
D
D
D
D
Mot du même champ lexical que PROGRAMMATION
Mot du même champ lexical que PROGRAMMATION
Mot du même champ lexical que PROGRAMMATION
Mot du même champ lexical que PROGRAMMATION
En analysant l’adresse de la page web appelée pour afficher le résultat
de notre
requête ,l’adresse
on lit l’adresse
:
En analysant
de la page
web appelée pour afficher le résultat
En analysant l’adresse de la page web appelée pour afficher le résultat
de notre requête , on lit l’adresse :
de notre
requête ,l’adresse
on lit l’adresse
:
En analysant
de la page
web appelée pour afficher le résultat
de notre requête , on lit l’adresse :
https://www.rimessolides.com/motscles.aspx?m=programmation
https://www.rimessolides.com/motscles.aspx?m=programmation
https://www.rimessolides.com/motscles.aspx?m=programmation
https://www.rimessolides.com/motscles.aspx?m=programmation
ce qui laisse penser qu’en téléchargeant cette page, on peut récupérer les
informations
souhaitées
avec
l’aval de l’auteur
que jeon
remercie
ici pour son
ce qui laisse penser
qu’en
téléchargeant
cette page,
peut récupérer
les
ce qui laisse penser qu’en téléchargeant cette page, on peut récupérer les
autorisation.
informations souhaitées avec l’aval de l’auteur que je remercie ici pour son
informations
souhaitées
avec
l’aval de l’auteur
que jeon
remercie
ici pour son
ce qui laisse penser
qu’en
téléchargeant
cette page,
peut récupérer
les
autorisation.
autorisation.
informations souhaitées avec l’aval de l’auteur que je remercie ici pour son
autorisation.
Le module request du package urllib
permet
de request
téléchargerduune
page urllib
web ou
Le module
package
Le module request du package urllib
toute autre
disponible
surweb
le web
permet
de ressource
télécharger
une page
ou
permet
de request
téléchargerduune
page urllib
web ou
Le
module
package
dont
connaı̂t
l’adresse.
L’exemple
suitoute on
autre
ressource
disponible
sur le web
toute autre
disponible
surweb
le web
permet
de ressource
télécharger
une page
ou
vant
comment
télécharger
l’image
dont illustre
on connaı̂t
l’adresse.
L’exemple
suidont
on
connaı̂t
l’adresse.
L’exemple
suitoute
autre
ressource
disponible
sur
le
web
david.jpg
surl’image
le site
vant illustre ci-contre
comment hébergée
télécharger
vant
illustre
comment
télécharger
l’image
dont
on
connaı̂t
l’adresse.
L’exemple
suidu livre :
david.jpg
ci-contre hébergée sur le site
david.jpg
surl’image
le site
vant illustre ci-contre
comment hébergée
télécharger
du livre :
du livre :
david.jpg
ci-contre hébergée sur le site
128
du livre :
128
128
128
SELEM-STOM
SELEM-STOM
SELEM-STOM
SELEM-STOM
1
12
12
31
23
42
34
53
45
64
56
576
7
76
7
from urllib.request import urlopen
from urllib.request import urlopen
from urllib.request import urlopen
requete
= urlopen('http://creations-numeriques.fr/images/david.jpg')
from urllib.request
import urlopen
requete
contenu =
= urlopen('http://creations-numeriques.fr/images/david.jpg')
requete.read()
requete
=
urlopen('http://creations-numeriques.fr/images/david.jpg')
contenu
=
requete.read()
f
= open('david.jpg','wb')
requete
= requete.read()
urlopen('http://creations-numeriques.fr/images/david.jpg')
contenu
=
f = open('david.jpg','wb')
f.write(contenu)
contenu
= requete.read()
f = open('david.jpg','wb')
f.write(contenu)
f.close()
f
= open('david.jpg','wb')
f.write(contenu)
f.close()
f.write(contenu)
f.close()
f.close()
Quelques explications :
Quelques explications :
Quelques explications :
Quelques explications :
●
●
●
●
●
●
●
●
●
●
●
●
à la ligne 3, on crée un objet de type http.client.HTTPResponse ;
à la ligne 3, on crée un objet de type http.client.HTTPResponse ;
à la ligne 3, on crée un objet de type http.client.HTTPResponse ;
à la ligne 3, on crée un objet de type http.client.HTTPResponse ;
à la ligne 4, on charge la ressource demandée et on stocke le contenu
à la ligne 4, on charge la ressource demandée et on stocke le contenu
une 4,variable
contenu
qui est
de type Bytes,
c’est-à-dire
un
àdans
la ligne
on charge
la ressource
demandée
et on stocke
le contenu
dans
une 4,variable
contenu
qui est
de type Bytes,
c’est-à-dire
un
à la ligne
on charge
la ressource
demandée
et on stocke
le contenu
tableau
d’octets
: les
informations
lues octet
parc’est-à-dire
octet (ou par
dans
une
variable
contenu
qui estsont
de type
Bytes,
un
tableau
d’octets
: les
informations
lues octet
parc’est-à-dire
octet (ou par
dans
une
variable
contenu
qui estsont
de type
Bytes,
un
paquet
d’octets)
et
recopiées
telles
quelles
;
tableau d’octets : les informations sont lues octet par octet (ou par
paquet d’octets)
recopiées
telles quelles
; octet par octet (ou par
tableau
d’octets et
: les
informations
sont lues
paquet d’octets) et recopiées telles quelles ;
paquet d’octets) et recopiées telles quelles ;
sur les 3 dernières lignes, on enregistre le contenu qui a été lu. Le
sur les 3 dernières lignes, on enregistre le contenu qui a été lu. Le
mode
du fichier
enregistré
est important,
il qui
indique
le
sur
les’wb’
3 dernières
lignes,
on enregistre
le contenu
a été que
lu. Le
mode
du fichier
enregistré
est important,
il qui
indique
le
sur
les’wb’
3 dernières
lignes,
on enregistre
le contenu
a été que
lu. Le
fichier
est ouvert
en écriture
(w) mais
surtout en mode
binaire
mode ’wb’
du fichier
enregistré
est important,
il indique
que(b),
le
fichier
est ouvert
en écriture
(w) mais
surtout en mode
binaire
mode ’wb’
du fichier
enregistré
est important,
il indique
que(b),
le
c’est-à-dire
que chaque
octet lu
enregistré
(sans
converfichier
est ouvert
en écriture
(w)est
mais
surtouttel
enquel
mode
binaire
(b),
c’est-à-dire
que chaque
octet lu
enregistré
(sans
converfichier
est ouvert
en écriture
(w)est
mais
surtouttel
enquel
mode
binaire
(b),
sion). Ainsique
unechaque
copie conforme
l’image est
enregistrée
sur le
c’est-à-dire
octet lu estde
enregistré
tel quel
(sans conversion). Ainsique
unechaque
copie conforme
l’image est
enregistrée
sur le
c’est-à-dire
octet lu estde
enregistré
tel quel
(sans converdisqueAinsi
dur. une copie conforme de l’image est enregistrée sur le
sion).
disque
dur. une copie conforme de l’image est enregistrée sur le
sion). Ainsi
disque dur.
disque dur.
On peut ainsi télécharger n’importe quel fichier disponible sur InterOn peut ainsi télécharger n’importe quel fichier disponible sur Internet,On
ce peut
une
image, une
musique quel
ou encore
undisponible
fichier texte.
peutêtre
ainsi
télécharger
n’importe
fichier
surParfois
Internet,On
ce peut
une
image, une
musique quel
ou encore
undisponible
fichier texte.
peutêtre
ainsi
télécharger
n’importe
fichier
surParfois
Intermême
le fichier
texteimage,
peut être
dynamique.
On
parle
net,
ce peut
être une
uneretourné
musiquede
oumanière
encore un
fichier texte.
Parfois
même
le fichier
texteimage,
peut être
dynamique.
On
parle
net, ce peut
être une
uneretourné
musiquede
oumanière
encore un
fichier texte.
Parfois
alors d’API.
même
le fichier texte peut être retourné de manière dynamique. On parle
alors
mêmed’API.
le fichier texte peut être retourné de manière dynamique. On parle
alors d’API.
alors d’API.
Une API (Application Programming Interface) est donc une interUne API (Application Programming Interface) est donc une interface,
c’est (Application
à dire une manière
d’interagir
avec un est
site donc
web (contenant
Une API
Programming
Interface)
une interface,
c’est (Application
à dire une manière
d’interagir
avec un est
site donc
web (contenant
Une API
Programming
Interface)
une interici
une
base
de données)
à l’aide
de requêtes.
face,
c’est
à dire
une manière
d’interagir
avec un site web (contenant
ici
une
base
de données)
à l’aide
de requêtes.
face,
c’est
à dire
une manière
d’interagir
avec un site web (contenant
ici une base de données) à l’aide de requêtes.
ici une base de données) à l’aide de requêtes.
129
129
129
129
C HAPITRE D
La plupart des API retournent des informations au format JSON (JavaScript
Object Notation ou Notation Objet issue
de JavaScript).
Il s’agit d’un format d’échange de
données.
Les
navigateurs
actuels
savent lire ce format et l’afficher de
manière lisible. Par exemple, le site
http://api.zippopotam.us
offre
une API gratuite et sans compte à créer
(ce qui est assez rare) permettant de
retrouver des informations sur les villes
d’un code postal donné.
En
tapant
par
exemple
l’adresse
http://api.zippopotam.us/fr/80170
dans votre navigateur, on visualise le
résultat ci-contre.
Si on souhaite charger et afficher le résultat de la page en Python, il
faut commencer par lire le contenu à l’aide de la fonction read sous forme
”brute” (comme dans l’exemple précédent pour le chargement d’une
image), puis le convertir en chaı̂ne de caractères. On précise alors l’encodage de la page (le plus souvent UTF-8 de nos jours) pour que Python
applique les bonnes règles de conversion :
from urllib.request import urlopen
requete = urlopen('http://api.zippopotam.us/fr/80170')
contenu = requete.read().decode('utf-8')
print(contenu)
{"post code": "80170", "country": "France", "country abbreviation": "FR",
"places": [{"place name": "Guillaucourt", "longitude": "2.6317", "state":
"Picardie", "state abbreviation": "B6", "latitude": "49.8425"}, {"place
name": "Bayonvillers", "longitude": "2.6287", "state": "Picardie", "state
abbreviation": "B6", "latitude": "49.8614"}, {"place name": "Wiencourtl’\u00c9quip\u00e9e", "longitude": "2.6117", "state": "Picardie", "state
abbreviation": "B6", "latitude": "49.8462"},
....
{"place name":
"Rosi\u00e8res-en-Santerre", "longitude": "2.7009", "state": "Picardie",
"state abbreviation": "B6", "latitude": "49.8143"}]}
130
SELEM-STOM
On a donc une grande chaı̂ne contenant tous les résultats. Certains caractères spéciaux (les accents par exemple) sont encodés de manière peu
lisible d’ailleurs. Il ne nous reste plus qu’à extraire les résultats qui nous
intéressent. Cherchons donc toutes les villes correspondant au code postal
80170...
Comme le format JSON est très répandu, vous aurez deviné qu’un module permet de gérer les données JSON. Il s’agit du module json :
import json
from urllib.request import urlopen
requete = urlopen('http://api.zippopotam.us/fr/80170')
contenu = requete.read().decode('utf-8')
contenu = json.loads(contenu)
Dans la console :
>>> contenu.keys()
dict_keys(['country', 'country abbreviation', 'post code', 'places'])
>>> contenu['places']
[{'latitude': '49.8425',
'longitude': '2.6317',
'place name': 'Guillaucourt',
'state': 'Picardie',
'state abbreviation': 'B6'},
.....
{'latitude': '49.7977',
'longitude': '2.732',
'place name': 'Méharicourt',
'state': 'Picardie',
'state abbreviation': 'B6'},
{'latitude': '49.7936',
'longitude': '2.7541',
'place name': 'Maucourt',
'state': 'Picardie',
'state abbreviation': 'B6'}]
La fonction loads permet donc de convertir le contenu donné sous
forme de chaı̂ne de caractères en un dictionnaire. Petite cerise sur le gâteau,
les caractères spéciaux sont convertis pour être lus par un humain.
E X D27 En analysant la structure du dictionnaire, modifier le programme pour
réaliser une fonction qui reçoit en paramètre un code postal et renvoie la liste des
villes correspondant à ce code postal.
La plupart des sites proposant des API demandent au minimum une
inscription. On obtient alors une clé d’identification qui permet de faire
des requêtes que l’on place ensuite dans l’adresse pour s’identifier. Ceci
évite par exemple de faire un programme qui aspirerait totalement la base
de données du site.
131
C HAPITRE D
Revenons à notre problématique, c’est-à-dire récupérer à partir du site
www.rimessolides.com, les mots du même champ lexical que ≪ programmation ≫. Le site ne prévoit pas de sortie JSON des résultats, nous allons
donc faire sans en utilisant la page HTML directement, ce qui n’en sera
que plus intéressant. Analysons le code source de la page, vous pouvez
obtenir ce contenu avec votre navigateur en effectuant un clic-droit sur la
page puis ”Afficher la source”. Ci-dessous, on a volontairement ignoré les
lignes peu utiles de la page pour nos besoins :
<!DOCTYPE html>
<html lang="fr" xml:lang="fr" xmlns="http://www.w3.org/...
<head>
<meta http-equiv="content-type" content="text/html; charset=...
<title>Champ lexical avec programmation</title>
<meta name="keywords" content="champs lexicaux, dictionnaire...
<meta name="description" content="Champ lexical avec ...
<style type="text/css"> ... </style>
</head>
<body>
<div id="fb-root"></div> ... </div>
<div class="header"> ... </div>
<div class="container">
<div class="container2">
<div class="container-left">
<div class="contenu" style="padding-bottom:30px;">
<div id="divResultat">
<div id="divMotcle">
<h1><span class="first-char">C</span>hamp lexical...
</br>
<table class="tb-motcle"><tr>
<td ><a class="l-black"
href="motscles.aspx?m=informatique">
informatique</a>
</td>
<td ><a class="l-black"
href="motscles.aspx?m=langage">
langage</a>
</td>
...
<td ><a class="l-black"
href="motscles.aspx?m=r%c3%a9cursion">
récursion</a>
</td>
</tr></table>
</div>
<div id="divMotcleMobile">
....
</html>
132
SELEM-STOM
L’objectif n’est pas ici de faire une analyse détaillée du code HTML de
la page (n’hésitez pas à consulter l’ouvrage de la même collection sur le
thème si cela vous intéresse). On remarque néanmoins que :
● les mots apparaissent après <div id="divMotcle"> ;
● on retrouve ensuite pour chaque mot le motif suivant :
<a class="l-black" href="motscles.aspx?m=☀">⧫</a>
où ⧫ est le mot à afficher et ☀ le même mot, mais où les caractères
spéciaux ont été remplacés par des codes HTML ;
● les mêmes mots sont répétés une seconde fois avec le même format
après <div id="divMotcleMobile"> pour un affichage adapté sur
téléphone portable.
E X D28 En s’appuyant sur les considérations précédentes, écrire une fonction
qui reçoit un mot et retourne une liste de mots du même champ lexical. On pourra
utiliser la méthode find décrite ci-dessous.
txt.find(ch,deb) : renvoie la position de la première occurrence
de la chaı̂ne ch dans la chaı̂ne txt à partir de la position deb si elle
est précisée (depuis le début sinon). Si la chaı̂ne ch n’est pas présente
dans txt, la fonction renvoie -1.
Essayons de faire plus simple à l’aide des expressions régulières. Pour
cela revenons à l’objet généré lorsque l’on effectue une recherche :
L’objet renvoyé par search, match ou fullmatch est soit False
lorsque la recherche a été infructueuse, soit de type SRE Match sur
lequel on peut appliquer différentes méthodes ou lire certains attributs :
● sre.start() : renvoie la position de départ de la chaı̂ne trouvée.
● sre.end() : renvoie la position de la fin de la chaı̂ne trouvée.
● sre.string : renvoie la chaı̂ne dans laquelle s’est effectuée la recherche.
On peut alors récupérer la chaı̂ne ainsi :
sre.string[sre.start():sre.end()]
ou beaucoup plus simplement :
● sre.group(0) : renvoie la chaı̂ne trouvée.
133
C HAPITRE D
● sre.group(i) : renvoie la partie de la chaı̂ne trouvée correspondant au i ème groupe (à la i ème paire de parenthèses).
Prenons un exemple avec un extrait de ce superbe texte du slameur
Govrache : ≪ le bout de la table ≫
txt="Je me souviens du bout de la table comme d'un fragment d'éternité. "
txt+="C'est Noël. J'ai quatre ans. Je suis le plus jeune de la famille. "
txt+="A l'autre bout en face de moi mon arrière-grand-mère est figée "
txt+="Elle n'entend plus que des yeux, et semble sourire sans béquille."
>>> resultat = re.search('la (chaise|table)',txt)
>>> resultat.group(0)
'la table'
>>> resultat.group(1)
'table'
>>> resultat = re.search('la (chaise|table)(.*)(m{2})e',txt)
>>> resultat.group(0)
'la table comme'
>>> resultat.group(2)
' co'
>>> resultat.group(3)
'mm'
Jusque-là, tout va bien... mais imaginons à présent que nous voulions
récupérer les noms communs qui se trouvent après le déterminant ”la”.
Comme précédemment, on peut chercher ”la ” suivi de n’importe quoi
(notre nom commun) suivi ensuite d’un espace :
>>> resultat = re.search('la (.*) ',txt)
>>> resultat.group(1)
"table comme d'un fragment d'éternité.C'est Noël. J'ai [...]sourire sans"
Ce qui n’est pas le résultat prévu. Ceci s’explique en allant lire la
documentation officielle du package re :
The ’*’, ’+’, and ’ ?’ qualifiers are all greedy ; they match as much text as possible. Sometimes this behaviour isn’t desired. Ce qui pourrait se traduire
par : les opérateurs *, + et ? sont gourmands et vont tenter de trouver
la plus grande chaı̂ne possible. On peut y remédier en ajoutant un
second point d’interrogation : *?, +? ou ??.
>>> resultat = re.search('la (.*?) ',txt)
>>> resultat.group(1)
'table'
134
SELEM-STOM
Finissons cet aparté sur les expressions régulières en présentant une
fonction qui permet d’effectuer une recherche sur tous les motifs désirés :
findall(m, ch) : renvoie sous forme d’une liste l’ensemble des
chaı̂nes correspondant au motif m dans le texte ch.
>>> re.findall('la (.*?) ',txt)
['table', 'famille.']
S’il y a plusieurs groupes, on a une liste de tuples :
>>> re.findall('(la|des) (.*?) ',txt)
[('la', 'table'), ('la', 'famille.'), ('des', 'yeux,')]
E X D29 Tout compris ? Testons vos connaissances avec un autre chanteur à texte :
Carlos et son tirelipinpon !
import re
txt = "Tirelipimpon sur le chihuahua"
txt+="Tirelipimpon avec la tête avec les bras"
txt+="Tirelipimpon un coup en l'air un coup en bas"
txt+="Touche mes castagnettes moi je touche à tes ananas"
a
b
c
d
=
=
=
=
re.findall('T.*? ', txt)
re.search('.?[aeiuoy]{2,}',txt).group(0)
re.search('.*(.?[aeiuoy]{2,})',txt).group(1)
re.match('mes',txt)
Que contiennent les variables a, b, c et d à la fin du programme ?
Revenons à notre projet d’extraire tous les mots d’un même champ
lexical qu’un mot donné à l’aide d’une expression régulière :
E X D30 Écrire une nouvelle fonction qui reçoit un mot et retourne une liste de
mots du même champ lexical en utilisant les expressions régulières.
Nous voilà au bout de l’aventure pour ce chapitre, reste un dernier
problème à régler : mettre en majuscule les mots récupérés. Le problème
serait simple si nous étions anglais et que nous n’utilisions pas d’accents !
Une première solution peut être de lister une à une les correspondances :
é → e, ç → c,... Bien heureusement, ce problème s’est posé à d’autres personnes avant nous et un système de normalisation a vu le jour :
135
C HAPITRE D
C HAPITRE D
C HAPITRE D
La normalisation Unicode est une normalisation de texte qui transforme
La normalisation
Unicode est une normalisation de texte qui transforme
C HAPITRE
D
des
caractères
ou séquences
deest
caractères
équivalents de
entexte
représentations
fonLa normalisation
Unicode
une normalisation
qui transforme
des caractères ou séquences de caractères équivalents en représentations fondamentales
afin
celles-cide
puissent
facilement de
comparées
des
caractères
ouque
séquences
caractères
équivalents
entexte
représentations
fonLa normalisation
Unicode
est
une être
normalisation
qui(Wikipédia).
transforme
damentales afin que celles-ci puissent être facilement comparées (Wikipédia).
Nous
utiliserons
lacelles-ci
normalisation
NFKD
pour comparées
Normalization
Form
damentales
afin
puissent
être
facilement
(Wikipédia).
des caractères
ouque
séquences
de
caractères
équivalents
en représentations
fonNous
utiliserons la
normalisation
NFKD pour de
Normalization
Form
La
normalisation
Unicode
est une
normalisation
texte
qui transforme
Compatibility
Decomposition
(ne
cherchez
pas
le
K
!)
que
l’on
peut
Nous
utiliserons
la
normalisation
NFKD
pour
Normalization
Form
damentales afin que celles-ci puissent être facilement comparées (Wikipédia).
Compatibility
(ne cherchez
pas en
le représentations
K !) que l’on peut
des
caractères
ouDecomposition
séquences
de caractères
équivalents
fonobtenir
facilement
le module
: K !) que l’onForm
Compatibility
Decomposition
(neunicodedata
cherchez
pasNormalization
le
peut
Nous utiliserons
lavia
normalisation
NFKD pour
obtenir
facilement
via
le
module
unicodedata
:
damentales
afin
que
celles-ci
puissent
être
facilement
comparées
(Wikipédia).
obtenir
facilement
via le module
: K !) que l’on peut
Compatibility
Decomposition
(neunicodedata
cherchez pas le
import
unicodedata
Nous
utiliserons
la normalisation
NFKD
pour Normalization
Form
import
unicodedata
>>>
txt
=
"Je
m'appelle
Gaëtan
s'écria
le
garçon,
obtenirunicodedata
facilement via le module unicodedata : ça c'est sûr"
import
>>>
txt
=
"Je
m'appelle
Gaëtan
s'écria
le
garçon,
ça
c'est
sûr"
Compatibility
(ne
cherchez pas le K !) que l’on peut
# Etape 1 : On Decomposition
normalise la chaı̂ne
>>>
txt =
"Je
s'écria le garçon, ça c'est sûr"
# Etape
1
Onm'appelle
normaliseGaëtan
la chaı̂ne
import
>>>
txtunicodedata
= :
unicodedata.normalize('NFKD',
txt)
obtenir
facilement
via
le
module
unicodedata
:
#
Etape
1
:
On
normalise
la
chaı̂ne
>>> txt = unicodedata.normalize('NFKD', txt)
# Etape
2 :
Onm'appelle
la transforme
ens'écria
un tableau
d'octets
>>>
txt =
"Je
Gaëtan
le garçon,
ça(type
c'estBytes
sûr"
>>> txt 2
= unicodedata.normalize('NFKD',
txt)
#
la
transforme
enindiquant
un tableau
d'octets
(type Bytes
# Etape
déjà
rencontré)
en
que
les caractères
non
Etapeunicodedata
1 :
: On
On
normalise
la chaı̂ne
import
#
Etape
2
:
On
la
transforme
en
un
tableau
d'octets
(type
Bytes
#
déjà rencontré)
en supprimés
indiquanttxt)
que les caractères non
#
compatibles
seront
>>> txt
txt =
= unicodedata.normalize('NFKD',
>>>
"Je
m'appelle
Gaëtan
s'écria leque
garçon,
ça c'est sûr"
#
déjà
rencontré)
en
indiquant
les
caractères
non
#
compatibles
seront
supprimés
# Etape
Etape
2 :
: On
On
la transforme
en
un tableau d'octets (type Bytes
>>>
txt 1
=
txt.encode('ascii',
'ignore')
#
normalise
la chaı̂ne
#
compatibles
seront
supprimés
>>>
txt
=
txt.encode('ascii',
'ignore')
déjà
rencontré)
en indiquant
que
les caractères
non
#
Etape
On
réencode
la chaı̂ne
en UTF-8
(valeur
par défaut)
>>>
txt 3
= :
unicodedata.normalize('NFKD',
txt)
>>>
txt
=
txt.encode('ascii',
'ignore')
#
Etape
:
On
réencode la
chaı̂ne
en UTF-8 (valeur par défaut)
#
compatibles
seront
supprimés
>>>
txt 3
=
txt.decode()
#
Etape
2
:
On
la
transforme
en
un
tableau
d'octets
(type
Bytes
# Etape
3 :
On réencode la chaı̂ne en UTF-8 (valeur par défaut)
>>>
txt
= txt.decode()
txt.encode('ascii',
>>>
txt =
#
déjà rencontré) en'ignore')
indiquant que les caractères non
>>>
txt
= txt.decode()
>>>
txt 3
#
Etape
:
On
réencode
la
chaı̂ne
en UTF-8
(valeur
par défaut)
"Je
m'appelle
Gaetan
s'ecria
lesupprimés
garcon,
ca c'est
sur"
#
compatibles
seront
>>>
txt
"Je
m'appelle
Gaetan s'ecria le garcon, ca c'est sur"
>>>
txt
=
txt.decode()
>>>
txt
=
txt.encode('ascii',
'ignore')
"Je m'appelle Gaetan s'ecria le garcon, ca c'est sur"
>>>
txt 3 : On réencode la chaı̂ne en UTF-8 (valeur par défaut)
# Etape
"Je
Gaetan s'ecria le garcon, ca c'est sur"
>>> m'appelle
txt = txt.decode()
>>> txt
"Je m'appelle Gaetan s'ecria le garcon, ca c'est sur"
E X D31 Améliorer une dernière fois la fonction de l’exercice précédent pour qu’
E X D31 Améliorer une dernière fois la fonction de l’exercice précédent pour qu’
elle
retourne
une liste
mots en
contenant
d’autres
caEX D
31 Améliorer
unededernière
foismajuscule.
la fonctionLes
de mots
l’exercice
précédent
pour qu’
elle retourne une liste de mots en majuscule. Les mots contenant d’autres caractères
que
des
lettres
seront
ignorés.
On
pourra
utiliser
la
méthode
upper
sur
elle
retourne
une liste
mots en
contenant
d’autres
caEX D
31 Améliorer
unededernière
foismajuscule.
la fonctionLes
de mots
l’exercice
précédent
pour qu’
ractères que des lettres seront ignorés. On pourra utiliser la méthode upper sur
les
pour
mettre
automatiquement
en majuscules
:
ractères
queprésentées
des
seront
ignorés.
On pourra
la méthode
upper sur
ellechaı̂nes
retourne
unelettres
listeci-dessous
de mots
en
majuscule.
Lesutiliser
mots contenant
d’autres
cales
présentéesune
ci-dessous
mettre
automatiquement
en majuscules
:
E Xchaı̂nes
D31 Améliorer
dernièrepour
fois la
fonction
de l’exercice précédent
pour qu’
les
chaı̂nes
présentées
ci-dessous
pour
mettre
automatiquement
en
majuscules
:
ractères que des lettres seront ignorés. On pourra utiliser la méthode upper sur
elle retourne une liste de mots en majuscule. Les mots contenant d’autres cales chaı̂nes présentées ci-dessous pour mettre automatiquement en majuscules :
ractères que des lettres seront ignorés. On pourra utiliser la méthode upper sur
les chaı̂nes
présentées
pour renvoient
mettre automatiquement
en majuscules
:
ch.upper()
et ci-dessous
ch.lower()
respectivement
la chaı̂ne ch
ch.upper() et ch.lower() renvoient respectivement la chaı̂ne ch
en majusculesetetch.lower()
minuscules. renvoient respectivement la chaı̂ne ch
ch.upper()
en majuscules et minuscules.
en
majusculesetetch.lower()
minuscules. renvoient respectivement la chaı̂ne ch
ch.upper()
en majuscules et minuscules.
et plus
ch.lower()
renvoient
respectivement
la défaut
chaı̂ne par
ch
Ilch.upper()
ne vous reste
qu’à remplacer
la liste
des mots par
Ilennemajuscules
vous resteetplus
qu’à remplacer la liste des mots par défaut par
minuscules.
cette
de mots
thème.
Il liste
ne vous
resteà plus
qu’à remplacer la liste des mots par défaut par
cette liste de mots à thème.
cette
de mots
thème.
Il liste
ne vous
resteà plus
qu’à remplacer la liste des mots par défaut par
cette liste de mots à thème.
vous reste plus qu’à remplacer la liste des mots par défaut par
✍ IlSine
vous
l’analyse de pages web plus en détails, le
✍
cette
liste
de êtes
motsintéressé
à thème.par
vous
êtes
intéressé
par l’analyse de pages web plus en détails, le
✍ Si
package
Beautiful
Souppar
pourra
vousde
être
utile,web
il comporte
de nomSi vous êtes
intéressé
l’analyse
pages
plus en détails,
le
package Beautiful Soup pourra vous être utile, il comporte de nom✍ Si
breux
outils
d’analyse,
plus
simples
à
utiliser
qu’une
extraction
”arpackage
Beautiful
Soup
pourra
vous
être
utile,
il
comporte
de
nomvous êtes intéressé par l’analyse de pages web plus en détails, le
breux outils d’analyse, plus simples à utiliser qu’une extraction ”artisanale”
comme
nous
présentée.
breux
outils
d’analyse,
plus
simples
utiliser
extraction
”arpackage
Beautiful
Soupl’avons
pourra
vous àêtre
utile,qu’une
il comporte
de nom✍ tisanale”
comme
nouspar
l’avons
présentée.
Si vous êtes
intéressé
l’analyse
de pages web plus en détails, le
tisanale”
comme
nous l’avons
présentée.
breux
outils
d’analyse,
plus simples
à utiliser qu’une extraction ”arpackage Beautiful Soup pourra vous être utile, il comporte de nomtisanale” comme nous l’avons présentée.
breux outils d’analyse, plus simples à utiliser qu’une extraction ”ar136 tisanale” comme nous l’avons présentée.
136
136
136
136
SELEM-STOM
6 - Aide pour les exercices
Aide pour l’exercice D3 Une première idée pourrait être de recopier autant de fois le mot que le nombre de lettres qu’il contient, puis de choisir un mot dans cette nouvelle liste. Cependant, d’un point de vue de la
mémoire, ce n’est pas du tout optimal et on risque rapidement de dépasser
la possibilité de stockage de la liste.
Voici une autre solution bien moins gourmande : imaginons que tous
les mots soient collés.
● On calcule la longueur totale LT des mots de la liste.
● On choisit une lettre, c’est-à-dire un nombre entre 0 et LT - 1.
● On crée un compteur indice lettre, initialisé à 0 et qui augmente
de la taille du mot jusqu’à devenir supérieur ou égal à lettre. Parallèlement, on crée un autre compteur indice mot qui retient le
nombre de mots passés.
● On retourne alors le mot correspondant à la lettre tirée.
lettre
C H A T L A P
indice lettre
indice mot=0
I N B A L E
indice mot=1
I N E S
indice mot=2
Aide pour l’exercice D7 Regarder plutôt les cas où le retour est ”FAUX”,
dans les autres cas, la réponse sera ”VRAI”.
Aide pour l’exercice D12 Le numéro doit :
● commencer par 0 ou +33,
● être suivi éventuellement d’un séparateur (espace, point),
● puis être suivi d’un 6 ou d’un 7,
● et enfin, être suivi de 4 blocs constitués :
◇ d’un éventuel séparateur,
◇ d’un nombre de 2 chiffres.
Aide pour l’exercice D15 Vous avez toutes les fonctions nécessaires pour
programmer cette ultime fonction. Pensez à mettre un test d’arrêt : si au
bout de x essais il est impossible de placer un nouveau mot, on complète
la grille par des lettres au hasard.
137
C HAPITRE D
Aide pour l’exercice D19 On peut dessiner les traits de la grille pixel par
pixel.
Aide pour l’exercice D23 À l’aide d’un booléen indiquant si le bouton est
enfoncé ou non, on répond facilement à la question. Il suffit de ne pas
dessiner le fond pour que la trace reste visible.
Aide pour l’exercice D24 Il faut faire attention à deux choses :
● la ligne est en lien avec l’ordonnée alors que la colonne est en lien
avec l’abscisse ;
● la largeur de la case est sa largeur intérieure, il faut donc ajouter 1
pour connaı̂tre le nombre de pixels utiles.
On a ainsi les relations :
Δy
c
(x,y)
l
larg
⎧
x = c × (larg + 1) + Δx
⎪
⎪
⎪
⎨
⎪
⎪
⎪
⎩ y = l × (larg + 1) + Δy
Δx
Rappelez-vous aussi que les numéros de ligne et de colonne ne doivent
pas dépasser l’intervalle d’entiers [0, N[ au cas où le joueur clique hors de
la grille !
Aide pour l’exercice D25 Pour tester si le mot surligné est valide, on peut
utiliser la fonction lis mot déjà programmée. Pour le coloriage, on pourra
simplement faire glisser la pastille sur la surface représentant la grille, de
manière à ce que la marque reste visible.
138
SELEM-STOM
Aide pour l’exercice D28 Voici un algorithme qui peut répondre à notre
problème :
Remarque : le curseur deb est initialisé à la position du mot
"divMotcleMobile" plutôt que "divMotcle" pour éviter d’avoir les
mots 2 fois.
Aide pour l’exercice D29 Voici les réponses. Allez voir la correction si vous
ne comprenez pas les raisons de ces valeurs :
a : [’Tirelipimpon ’, ’Tirelipimpon ’, ’Tirelipimpon ’,
’Touche ’]
b : ’hua’
c : ’ou’
d : None
7 - Solutions des exercices
Retour sur l’exercice D1 Pas de difficulté pour se mettre en route dans ce
nouveau projet :
G1=[['T','S','A','M'],
['Q','E','B','X'],
['O','C','R','E'],
['C','O','U','P']]
def affiche(G) :
for ligne in G :
for lettre in ligne :
print(lettre, end="")
print()
Le label end="" permet d’indiquer de ne pas aller à la ligne après le
print. On peut aussi utiliser la méthode join sur les chaı̂nes de caractères :
139
Solutions des exercices
lire le fichier demandé et placer son contenu dans une chaı̂ne html
deb ← place de "divMotcleMobile"
deb ← place de "a class="l-black" href="motscles.aspx?m=" après deb
deb ← place de ">" après deb
TANT QUE deb > 0 :
f in ← place de "<" après deb
récupérer le mot entre deb et f in
deb ← position suivante
C HAPITRE D
C HAPITRE D
C HAPITRE D
ch.join(L) : Renvoie une chaı̂ne de caractères où tous les éléments
ch.join(L) : Renvoie une chaı̂ne de caractères où tous les éléments
de la liste L ont été concaténés en utilisant la chaı̂ne ch comme joinde la liste L ont
été concaténés
en utilisant
la chaı̂ne
ch comme
joinch.join(L)
: Renvoie
une chaı̂ne
de caractères
où tous
les éléments
ture. Attention, les éléments de la liste L doivent déjà être des chaı̂nes
ture.
Attention,
les
éléments
de
la
liste
L
doivent
déjà
être
des
chaı̂nes
de la liste L ont été concaténés en utilisant la chaı̂ne ch comme joinde caractères. Par exemple :
de caractères.
Par
ture.
Attention,
lesexemple
éléments: de la liste L doivent déjà être des chaı̂nes
>>> caractères.
" / ".join(['14','03',
'1976'])
de
Par
exemple
:
>>> " / ".join(['14','03', '1976'])
'14 /
/
"
/
'14
>>>
'14
03 / 1976'
03 / 1976'
/ ".join(['14','03', '1976'])
03 / 1976'
On peut alors raccourcir le code :
On peut alors raccourcir le code :
On peut
alors raccourcir
le code :
def
affiche(G)
:
def affiche(G)
: G :
for ligne in
for ligne
in G :
print("".join(ligne))
def affiche(G)
:
for ligne
print("".join(ligne))
in G :
print("".join(ligne))
Ou encore plus court :
Ou encore plus court :
Ou encore
plus:court :
def
affiche(G)
def affiche(G)
:
print("\n".join(["".join(ligne)
for ligne in G]))
print("\n".join(["".join(ligne) for ligne in G]))
affiche(G) :
print("\n".join(["".join(ligne) for ligne in G]))
def
Retour sur l’exercice D2 Voici une idée pour la première question : on calRetour sur l’exercice D2 Voici une idée pour la première question : on calcule l’écart sur les lignes et sur les colonnes. Le déplacement est possible
cule l’écart
sur les lignes
et surune
les idée
colonnes.
Lepremière
déplacement
est possible
Retour
sur l’exercice
D2 Voici
pour la
question
: on calsi l’un des deux écarts est nul (dans ce cas le produit est nul), ou s’ils sont
si l’un
des deux
écarts
estet
nul
(dans
ce cas le produit
est nul), ou
sont
cule
l’écart
sur les
lignes
sur
les colonnes.
Le déplacement
est s’ils
possible
égaux en valeur absolue. Lorsque le déplacement est valide, on calcule
égaux
en valeur
absolue.
Lorsque
calcule
si l’un des
deux écarts
est nul
(dansle
cedéplacement
cas le produitest
estvalide,
nul), ouons’ils
sont
dans une variable long le nombre de pas pour déterminer la direction à
dans une
long leLorsque
nombre le
de déplacement
pas pour déterminer
la direction
égaux
en variable
valeur absolue.
est valide,
on calculeà
prendre
prendre
dans une variable long le nombre de pas pour déterminer la direction à
prendre
def direction(K1, K2) :
def if
direction(K1,
: False
K1 == K2 : K2)
return
if K1
K2 : return False
c1==
= K1
def l1,
direction(K1,
K2) :
l1, c2
c1 =
= K2
K1
l2,
if K1 ==
K2 : return False
l2, Dc
c2 =
= l2
K2
Dl,
l1, c1 = K1 - l1, c2 - c1
Dl,
Dc
=
l2
c2==
- c1
if
== 0 l1,
or Dc
Dl or Dc == -Dl :
*Dc
l2,Dl
c2
= K2
if Dl
Dc ==
0 or Dc == Dl
or Dc == -Dl :
*
long
=
max(abs(Dl),
abs(Dc))
Dl, Dc = l2 - l1, c2 - c1
long
= max(abs(Dl),
abs(Dc))
return
Dl//long,
Dc//long
if Dl*Dc == 0 or Dc == Dl or Dc == -Dl :
Dl//long, Dc//long
elsereturn
:
long
= max(abs(Dl), abs(Dc))
elsereturn
:
False
Dl//long, Dc//long
elsereturn
return False
:
return False
La formule du calcul du nombre de pas s’explique par le fait que soit l’un
La formule du calcul du nombre de pas s’explique par le fait que soit l’un
des écarts est nul (déplacement vertical ou horizontal) soit ces écarts sont
desformule
écarts est
(déplacement
horizontal)
soit
ces
écarts
La
dunul
calcul
du nombrevertical
de pas ou
s’explique
par le
fait
que
soit sont
l’un
égaux en valeur absolue, dans ce cas, on choisit n’importe lequel des deux.
égaux
en valeur
dans cevertical
cas, on choisit
n’importe
des deux.
des écarts
est nulabsolue,
(déplacement
ou horizontal)
soitlequel
ces écarts
sont
Cette distance est appelée distance de Tchebychev en dimension 2, elle
Cette
appelée
distance
en dimension
elle
égauxdistance
en valeurest
absolue,
dans
ce cas, de
on Tchebychev
choisit n’importe
lequel des2,
deux.
complète la gamme des distances déjà présentées au chapitre précédent.
complète
la gamme
des distances
déjà
au chapitre
précédent.
Cette distance
est appelée
distance
deprésentées
Tchebychev
en dimension
2, elle
complète la gamme des distances déjà présentées au chapitre précédent.
140
140
140
SELEM-STOM
def lis_mot(G, K1, K2) :
"""
G : grille ; K1, K2 cases = (l,c)
Retourne le mot
"""
l1, c1 = K1
D = direction(K1, K2)
if D == False :
return ""
Dl, Dc = D
rep = G[l1][c1]
while (l1, c1) != K2 :
l1, c1 = l1 + Dl, c1 + Dc
rep = rep + G[l1][c1]
return rep
Retour sur l’exercice D3 En appliquant la solution proposée en aide, on
obtient le code suivant :
def choisir(L) :
LT = sum([len(mot) for mot in L])
lettre = randint(0, LT-1)
indice_lettre, indice_mot = 0, 0
while indice_lettre < lettre :
indice_lettre = indice_lettre + len(L[indice_mot])
indice_mot = indice_mot + 1
return L[indice_mot-1]
Retour sur l’exercice D4 A priori, pas de difficulté pour cette fonction si
l’on connaı̂t l’opérateur in ou la méthode find qui teste justement la
présence d’une chaı̂ne dans une autre :
ch1 in ch2 : donne un booléen indiquant si la chaı̂ne ch1 est incluse
dans chaı̂ne ch2
Attention cependant si vous utilisez une boucle for :
def supprime(L, m) :
for l in L :
if m in l or l in m :
L.remove(l)
M = ['BOITE', 'BOIT', 'CODE', 'BOTTE', 'DEBOITER', 'CARTE']
supprime(M, 'BOITE')
print(M)
141
Solutions des exercices
Pour la seconde question, pas de difficulté, il suffit de se déplacer dans
la direction calculée et de reconstruire le mot lettre à lettre :
HAPITRE D
D
C HAPITRE
Retourne:: [’BOIT’,
[’BOIT’, ’CODE’,
’CODE’, ’BOTTE’,
’BOTTE’, ’CARTE’]...
’CARTE’]...LeLemot
motBOIT
BOIT
Retourne
été supprimé
supprimé!! Et
Et oui,
oui, lala fonction
fonctionprécédente
précédenteest
estéquivalente
équivalenteauau
n’a pas été
code :
def supprime(L,
supprime(L, m)
m) ::
i == 00
while
while ii << len(L)
len(L) ::
ll == L[i]
L[i]
if
if mm in
in ll or
or ll in
in mm ::
L.remove(l)
L.remove(l)
ii == ii ++ 11
Exécutons étape
étape par
parétape
étapepour
pourcomprendre
comprendre: :
Exécutons
LL
[’BOITE’,
[’BOITE’, ’BOIT’,
’BOIT’, ’CODE’,
’CODE’, ’BOTTE’,
’BOTTE’, ’DEBOITER’,
’DEBOITER’,’CARTE’]
’CARTE’]
[’BOIT’,
[’BOIT’, ’CODE’,
’CODE’, ’BOTTE’,
’BOTTE’, ’DEBOITER’,
’DEBOITER’,’CARTE’]
’CARTE’]
[’BOIT’,
[’BOIT’, ’CODE’,
’CODE’, ’BOTTE’,
’BOTTE’, ’DEBOITER’,
’DEBOITER’,’CARTE’]
’CARTE’]
[’BOIT’,
[’BOIT’, ’CODE’,
’CODE’, ’BOTTE’,
’BOTTE’, ’DEBOITER’,
’DEBOITER’,’CARTE’]
’CARTE’]
[’BOIT’,
[’BOIT’, ’CODE’,
’CODE’, ’BOTTE’,
’BOTTE’, ’CARTE’]
’CARTE’]
inclusion
ii
ll
inclusion
??
00
BOITE
BOITE
OUI
OUI
11
CODE
CODE
NON
NON
22
BOTTE
BOTTE
NON
NON
33 DEBOITER
DEBOITER
OUI
OUI
44
FIN
FIN
Lorsqu’un
Lorsqu’un élément
élémentest
estsupprimé,
supprimé,les
lesindices
indicesdes
desmots
motsqui
quisuivent
suiventsont
sont
décalés, mais
mais comme
comme dans
dans lelemême
mêmetemps
tempson
onincrémente
incrémentelalavaleur
valeurdedei,i,
on passe
passe certains
certains mots.
mots.IlIlne
nefaut
fautdonc
doncincrémenter
incrémenterl’indice
l’indiceque
quesisil’on
l’onnene
trouve pas
pas de
de mot
mot ::
def supprime(L,
supprime(L, m)
m) ::
i == 00
while
while ii << len(L)
len(L) ::
ll == L[i]
L[i]
if
if mm in
in ll or
or ll in
in mm ::
L.remove(l)
L.remove(l)
else
else ::
ii == ii ++ 11
Retour sur
sur l’exercice
l’exercice D5
D5 Encore
Encoreune
unefois,
fois,lalamission
missionn’est
n’estpas
pastrès
trèsdifficile,
difficile,
cependant
cependant l’idée
l’idée la
la plus
plussimple
simple: :
def lis_fichier(f)
lis_fichier(f) ::
fichier
fichier == open(f,'r')
open(f,'r')
return
return fichier.readlines()
fichier.readlines()
fichier.close()
fichier.close()
L = lis_fichier('mots.txt')
lis_fichier('mots.txt')
print(L[0],
print(L[0], len(L[0]))
len(L[0]))
ne fonctionne
fonctionne pas
pas car
car le
le premier
premiermot
motABAISSER
ABAISSERest
estannoncé
annoncécomme
commeun
un
mot de 99 lettres...
lettres... Cela
Cela vient
vientdu
ducaractère
caractèrepolluant
polluant/n
/nsous
sousWindows
Windowsdéjà
déjà
évoqué dans
dans le
le tome
tome 1.
1.
142
SELEM-STOM
SELEM-STOM
Voici une nouvelle
nouvelle fonction
fonctionqui
quirègle
règleleleproblème
problème: :
def
def lis_fichier(f)
lis_fichier(f) ::
fichier = open(f,'r')
open(f,'r')
return [m.replace('\n','')
[m.replace('\n','') for
for mm in
in fichier.readlines()]
fichier.readlines()]
fichier.close()
fichier.close()
def
def init(n, L) :
G = [['_' for ii in
in range(n)]
range(n)] for
for jj in
in range(n)]
range(n)]
mot = choisir(L)
choisir(L)
while len(mot)
len(mot) >> nn ::
mot = choisir(L)
choisir(L)
...
On
On tire au hasard un
un mot
mot jusqu’à
jusqu’àen
entrouver
trouverun
unqui
quirentre
rentredans
danslalagrille...
grille...
Évidemment
Évidemment si vous
vous demandez
demandezune
unegrille
grille33××3,3,cela
celarisque
risquede
deposer
poserquelquelques
ques soucis vu qu’il
qu’il n’existe
n’existe pas
pas de
demots
motsde
demoins
moinsde
de44lettres.
lettres.On
Onpourpourrait
rait :
● Ajouter une clause
clause avec
avec un
unassert
assertjuste
justeen
endessous
dessousdu
dudef
def: :
def init(n, L)
L) ::
assert(n>3)
assert(n>3)
Si cette condition
condition n’est
n’estpas
pasrespectée,
respectée,leleprogramme
programmeplante
planteetets’arrête...
s’arrête...
Finalement ce
ce n’est
n’est pas
pas beaucoup
beaucoupmieux
mieuxque
quede
detourner
tournerà àl’infini...
l’infini...
● Commencer par
par supprimer
supprimerde
delalaliste
listeLLtous
tousles
lesmots
motstrop
tropgrands
grandsetet
si cette liste n’est
n’est pas
pas vide,
vide,tirer
tirerun
unmot.
mot.Cette
Cettesolution
solutionest
estd’ailleurs
d’ailleurs
bien meilleure,
meilleure, car
car les
les mots
mots seront
serontsupprimés
supprimésde
deLLune
unefois
foispour
pour
toutes.
Attention, la solution
solution suivante
suivantede
degauche
gauchene
nefonctionne
fonctionnequ’à
qu’àmoitié
moitié: :
les mots sont bien
bien retirés
retirés de
dela
laliste
listeavant
avantleletirage,
tirage,mais
maislalaliste
listen’est
n’est
pas modifiée dans
dans le
le reste
restedu
duprogramme.
programme.L’éditeur
L’éditeurPyScripter
PyScripterd’Edud’EduPython ne s’y trompe
trompe pas
pas d’ailleurs
d’ailleurs: :
143
143
Solutions des exercices
Retour
Retour sur l’exercice
l’exercice D6
D6 Pas
Pas de
de difficulté
difficulté spécifique
spécifiquepour
pourcette
cettefonction
fonction
puisque
puisque nous avons
avons déjà
déjà programmé
programmépas
pasmal
malde
dechoses...
choses...Comme
Commed’had’habitude
bitude avec les listes
listes cela
cela vaut
vaut la
la peine
peinede
dese
seposer
posernéanmoins
néanmoinsquelques
quelques
instants....
instants....
C HAPITRE D
Dans la fonction de gauche, une nouvelle liste est créée et stockée
dans L. Comme le paramètre d’une fonction ne peut être modifié (au
sens de prendre une nouvelle adresse), l’objet L devient une variable
locale, tout se passe comme prévu à l’intérieur de la fonction, mais
lorsque l’on sort de celle-ci, la liste donnée en argument n’a subi aucun changement. Avec la solution de droite, la méthode remove agit
directement sur le paramètre L (l’adresse mémoire). Ainsi c’est l’objet
passé en argument qui est modifié.
Une fois cette subtilité comprise, le reste n’est pas trop compliqué. On
place le mot à un emplacement aléatoire sans sortir de la grille. Si aucun
mot n’est trouvé, on renvoie le couple (False,False) pour signaler le
problème :
def init(n, L) :
G = [['_' for i in range(n)] for j in range(n)]
i = 0
while i < len(L) :
m = L[i]
if len(m) > n : L.remove(m)
else : i = i + 1
if L == [] :
return False, False
mot = choisir(L)
supprime(L, mot)
l = randint(0,n-1)
c = randint(0,n-len(mot))
for i in range(len(mot)) :
G[l][c+i] = mot[i]
return G, m
Retour sur l’exercice D7 Le mot ne convient pas si :
● il est plus long que le motif,
● une de ses lettres n’est pas la même que la lettre du motif de même
rang (si celle-ci n’est pas le symbole ).
Cela donne le code suivant :
def debutOK(motif, mot) :
if len(mot) > len(motif) :
return False
for i in range(len(mot)) :
if motif[i] != "_" and motif[i] != mot[i] :
return False
return True
144
SELEM-STOM
Retour sur l’exercice D8 Il suffit de retirer un à un les premiers caractères
du motif pour voir si celui-ci convient :
def possible(motif, mot) :
dec = 0
while motif != "" :
if debutOK(motif, mot) :
return dec
motif = motif[1:]
dec = dec + 1
return -1
Retour sur l’exercice D9 Tous les mots doivent se terminer par une voyelle
1 :
(exceptée Y) suivie de 2 lettres. Seul le début peut différer. Le motif [CHO][AEIOU].. signifie que le mot commence par un C, un H ou un
2 : (CHO)[AEIOU].. signifie le mot commence par CHO.
O. Le motif 3 : (CH|O)[AEIOU].. signifie que le mot commence par CH
Le motif 4 : [C-O][AEIOU].. signifie que le mot comou par O et enfin le motif mence par une lettre entre C et O (C, D, E, ..., N, O). On trouve ainsi les
réponses suivantes :
Motif
1
2
3
4
CAFE
✓
✘
✘
✓
CHOEUR
✘
✓
✘
✘
CHAIR
✘
✘
✓
✘
OEUF
✓
✘
✓
✓
CHENE
✘
✘
✓
✘
NOIX
✘
✘
✘
✓
Retour sur l’exercice D10 Un nombre (entier ou décimal) en écriture
française est composé :
● d’au moins un chiffre (ou plus) : [0-9]+
● suivi soit de rien (d’où le ? en fin de chaı̂ne), soit d’une virgule et
d’au moins un chiffre ensuite : (,[0-9]+)?
On obtient ainsi l’expression : [0-9]+(,[0-9]+)?
Retour sur l’exercice D11 Une première idée simple serait de faire ainsi :
import re
def IP(ch) :
expr = '[0?9]{1,3}.){3}.([0?9]{1,3}'
return re.fullmatch(expr, ch)
Mais cela n’est pas tout à fait correct, car l’adresse 999.999.999.999
serait considérée comme valide. Comment alors indiquer un nombre entre
0 et 255 ?
145
Solutions des exercices
C HAPITRE D
Voici une solution :
(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)
import re
def IP(ch) :
octet = '(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)'
expr = (octet+'.')*3+octet
return re.fullmatch(expr, ch)
Retour sur l’exercice D12 Il ne reste plus qu’à traduire les possibilités
présentées dans l’énoncé :
(0|\+33)[ \.]?[67]([ \.]?[0-9]{2}){4}
Retour sur l’exercice D13 En appliquant l’algorithme, on obtient la fonction suivante :
import re
def possible(motif, mot) :
motif = motif.replace('_','.')
l = len(mot)
for i in range(len(motif)-l+1) : # indice du début de la fenêtre
expr = motif[i:i+l]
#
glissante
if expr != '.'*l and re.fullmatch(motif[i:i+l], mot) :
return i
return -1 # Aucun motif ne correspond
Remarquons que si le motif est plus court que le mot, aucune erreur ne se
produit puisque l’on n’entre pas dans la boucle, et -1 est bien renvoyé.
Retour sur l’exercice D14 La fonction comporte les 4 parties décrites dans
l’énoncé : le première partie consiste à trouver une case vide. Cet algorithme a déjà été présenté au cours du projet MINOS. On choisit une case
au hasard : si elle n’est pas vide, on regarde à la colonne suivante, puis la
ligne suivante... Si on a échoué nbl× nbc fois, c’est qu’il n’y a plus de case
libre :
def emplacement(G) :
# Recherche d'une case vide
nbl, nbc, nbe = len(G), len(G[0]), 0
l, c = randint(0,nbl-1), randint(0, nbc-1)
while G[l][c] != '_' :
c = c + 1
nbe = nbe + 1
if nbe == nbl * nbc :# Plus de cases vides
return -1
if c == nbc :
c, l = 0, l+1
.../...
146
if l == nbl :
l = 0
# Recherche d'une direction
Dl, Dc = randint(-1,1), randint(-1,1)
while (Dl, Dc) == (0,0) :
Dl, Dc = randint(-1,1), randint(-1,1)
# On recule jusqu'à un bord
while l != 0 and c !=0 and l != nbl-1 and c != nbc-1 :
l, c = l-Dl, c-Dc
case = l,c
# Lit le motif, cette fois-ci, on avance
motif = G[l][c]
l, c = l+Dl, c+Dc
while l >= 0 and c >= 0 and l < nbl and c < nbc :
motif = motif + G[l][c]
l, c = l+Dl, c+Dc
return [case, (Dl,Dc), motif]
Retour sur l’exercice D15 Voici une proposition de solution :
def genereGrille(n) :
L = lis_fichier('mots.txt')
G, mot = init(n,L)
M = [mot]
nbe = 0
# Nombre d'essais infructueux
while nbe < 100 :
retour = emplacement(G)
if retour != -1 :
case, direction , motif = retour
possibles, positions = [], []
for m in L :
p = possible(motif, m)
if p != -1 :
possibles.append(m)
positions.append(p)
if len(possibles) == 0 :
nbe = nbe + 1
else :
nbe = 0
mot = choisir(possibles)
supprime(L, mot)
pos = positions[possibles.index(mot)]
M.append(mot)
l , c = case
Dl, Dc = direction
for lettre in mot :
G[l][c] = lettre
l, c = l + Dl, c+Dc
else:
nbe = nbe + 1
# On comble le reste par des lettres :
for i in range(n) :
for j in range(n) :
if G[i][j] == '_' :
G[i][j] = choice('ABCDEFGHIJKLMNOPQRSTUVWXYZ')
return (G, M)
147
Solutions des exercices
SELEM-STOM
C HAPITRE D
À noter l’utilisation de la fonction choice du module random :
choice(x) : renvoie aléatoirement un élément de la liste x ou une
lettre de la chaı̂ne x avec équiprobabilité.
Retour sur l’exercice D16 Après un jeu de PONG, cet exercice devrait être
une formalité ! On définit un vecteur de déplacement (vx, vy) pour gérer
les rebonds :
import pygame
from pygame.locals import *
pygame.init()
pygame.display.set_caption("WINNERS DON'T USE DRUGS")
fenetre = pygame.display.set_mode((770,770))
logo = pygame.image.load('winners.png').convert()
continuer = True
clock = pygame.time.Clock()
x, y = 0, 0
vx, vy = 2,2
taille = 10
while continuer:
clock.tick(100)
for event in pygame.event.get():
if event.type == QUIT :
continuer = False
fenetre.fill((115,140,156)) # Couleur du fond de l'image
fenetre.blit(logo,(300,300))
fnt = pygame.font.Font(None, taille)
txt = fnt.render("WINNERS DON'T USE DRUGS", True, (255,255,0))
x, y = x + vx, y + vy
l, h = fnt.size("WINNERS DON'T USE DRUGS")
if x < 0 :
vx = -vx
taille = taille + 1
x = 0
if x+l > 770 :
vx = -vx
taille = taille + 1
x = 770-l
if y < 0 :
vy = -vy
taille = taille + 1
y = 0
if y+h > 770 :
vy = -vy
taille = taille + 1
y = 770-h
fenetre.blit(txt,(x,y))
pygame.display.flip()
pygame.quit()
148
SELEM-STOM
SELEM-STOM
Retour sur l’exercice D17 Cet exercice est plus un exercice de mathématiques que
d’algorithmique.
Si exercice
on note est
lG et
cG le
et de
Retour
sur l’exercice
D17 Cet
plus
unnombre
exercicededelignes
mathémacolonnes
de
la
grille,
on
a
à
disposition
l
−
(c
+
1)
pixels
en
largeur
(il
tiques que d’algorithmique. Si on note lG et cGGle nombre de lignes et de
faut
retirer
les
traits
verticaux)
et
h
−
(l
+
1)
pixels
en
hauteur,
qu’il
faut
G l − (c + 1) pixels en largeur (il
colonnes de la grille, on a à disposition
G
diviser
par
le
nombre
de
colonnes
et
de
pouren
connaı̂tre
dimenfaut retirer les traits verticaux) et h − (lG lignes
+ 1) pixels
hauteur,les
qu’il
faut
sions
maximales
en
pixels
de
chaque
case.
Comme
nous
voulons
des
cases
diviser par le nombre de colonnes et de lignes pour connaı̂tre les dimencarrées,
on prendra
la plusde
petite
descase.
dimensions
:
sions maximales
en pixels
chaque
Commetrouvées
nous voulons
des cases
des dimensions trouvées :
Retour sur l’exercice D18 Après avoir listé les lettres à afficher, on réalise
une boucle
dans laquelle
tailleavoir
de lalisté
police
jusqu’àon
ceréalise
qu’au
Retour
sur l’exercice
D18 la
Après
lesaugmente
lettres à afficher,
moins
une
des
lettres
ne
rentre
plus
dans
la
case
:
une boucle dans laquelle la taille de la police augmente jusqu’à ce qu’au
def taillePolice(G,c)
: rentre plus dans la case :
moins
une des lettres ne
L = [] # Commençons par lister toutes les lettres
def for
taillePolice(G,c)
ligne in G : :
L = []
Commençons
par:lister toutes les lettres
for #
lettre
in ligne
for ligne
G : not in L :
ifin
lettre
for lettre
in ligne :
L.append(lettre)
lettre
in L difficile
:
taille =if
1 #
Sinonnot
ce sera
de jouer !
L.append(lettre)
maxi = 0
taillemaxi
= 1 <
# c
Sinon
ce sera difficile de jouer !
while
:
maxitaille
= 0
= taille + 1
while
maxi
< c :
fnt
= pygame.font.Font(None,
taille)
taille
= taille + 1
maxi
= 0
fnt lettre
= pygame.font.Font(None,
taille)
for
in L :
maxil,
= h
0 = fnt.size(lettre)
for if
lettre
in L :
l > maxi
l, hmaxi
= fnt.size(lettre)
= l
l > maxi :
if h
l
maxi = h
if h > maxi :
return taille-1
maxi = h
return taille-1
Retour sur l’exercice D19 Cette fonction comporte plusieurs parties :
● on
dessine
les lignes
horizontales
verticalesplusieurs
pixel parparties
pixel (nous
Retour
sur
l’exercice
D19 Cette
fonctionetcomporte
:
étudierons
plus
tard
comment
faire
cela
plus
efficacement)
;
● on dessine les lignes horizontales et verticales pixel par pixel (nous
● on
détermine
une
taille
adéquate
decela
police.
ne pas qu’elle
soit
étudierons
plus
tard
comment
faire
plusPour
efficacement)
;
grosse, j’ai
fait
le choix
ici de
80%ne
(4/5)
de la taille
● trop
on détermine
une
taille
adéquate
delimiter
police. àPour
pas qu’elle
soit
d’une
case
;
trop grosse, j’ai fait le choix ici de limiter à 80% (4/5) de la taille
● on
dessine
d’une
case ;chaque lettre en la centrant sur la case.
● on dessine chaque lettre en la centrant sur la case.
149
149
Solutions des exercices
def
calcule(G,
l, h) la
: plus petite
carrées,
on prendra
lG, cG = len(G), len(G[0])
def cx
calcule(G,
l, //
h) cG
:
= (l-cG-1)
lG,=cG
= len(G),
len(G[0])
cy
(h-lG-1)
// lG
cx==min(cx,
(l-cG-1)
// cG
c
cy)
cy = (l-c
(h-lG-1)
// lG// 2
Dx
*cG-cG-1)
c ==min(cx,
cy)
Dy
(h-c*lG-lG-1)
// 2
Dx = (l-c
// 2
return
c,*cG-cG-1)
Dx, Dy
Dy = (h-c*lG-lG-1) // 2
return c, Dx, Dy
C HAPITRE D
def dessine(G, l, h) :
c, Dx, Dy = calcule(G, l, h)
# dessine les lignes
surf = pygame.Surface((l,h))
surf.fill((0,0,0))
lG, cG = len(G), len(G[0])
for li in range(lG+1) :
for x in range(Dx,Dx+cG*(c+1)+1) :
surf.set_at((x,Dy+li*(c+1)),(255,255,255))
for co in range(cG+1) :
for y in range(Dy,Dy+lG*(c+1)+1) :
surf.set_at((Dx+co*(c+1),y),(255,255,255))
# Dessin des lettres
t = taillePolice(G, c*4//5) # On ne garde que 80% de la case
for li in range(lG) :
for co in range(cG):
fnt = pygame.font.Font(None, t)
ltxt, htxt = fnt.size(G[li][co])
xtxt = Dx+co*(c+1)+(c-ltxt)//2
ytxt = Dy+li*(c+1)+(c-htxt)//2
texte = fnt.render(G[li][co], True, (255,255,255))
surf.blit(texte, (xtxt, ytxt))
return surf
Retour sur l’exercice D20 Dans la proposition qui suit, (x,y) représente
l’emplacement du mot à écrire. On décide de garder 25 pixels de bordure
pour éviter que les mots ne soient trop collés aux bords. On vérifie pour
chaque mot que l’abscisse finale ne dépasse pas 275, sinon on va à la ligne.
Pour ne pas recommencer deux fois la même chose, on fusionne les deux
listes. On réalise une boucle sur les indices plutôt que sur les mots de
manière à surveiller le changement de couleur et à mettre un point plutôt
qu’une virgule à la fin du dernier mot :
def dessine_mots(M1, M2) :
surf = pygame.Surface((300,700))
surf.fill((0,0,0))
fnt = pygame.font.Font(None, 34)
x, y = 25, 25
l1, l2, L = len(M1), len(M2), M1+M2
for i in range(l1+l2) :
m = L[i]+'.' if i == l1+l2-1 else L[i]+', '
if i < l1 :
texte = fnt.render(m, True, (255,255,100))
else :
texte = fnt.render(m, True, (125,125,125))
ltxt, htxt = fnt.size(m)
if x + ltxt > 275 :
x, y = 25, y + htxt
surf.blit(texte, (x, y))
x = x + ltxt
return surf
150
SELEM-STOM
Retour sur l’exercice D21 Cet exercice est corrigé sur le site, mais ne vaut
pas la peine d’être présenté ici, il s’agit simplement d’un assemblage de
fonctions déjà créées.
Retour sur l’exercice D22
def pastille(r, coul) :
img = pygame.Surface((2*r,2*r),SRCALPHA)
img.fill((255,255,255,0))
for y in range(2*r) :
for x in range(2*r) :
ray = (r-x)**2+(r-y)**2
if ray < r**2 :
img.set_at((x,y),coul)
return img
2. Par rapport à l’exercice précédent, on va modifier quelque peu la
boucle principale pour surveiller la position de la souris (le code
complet est disponible sur le site) :
largeur , Dx, Dy = calcule(G, 700, 700)
xp, yp, lp = 100,100,largeur*9//10
past = pastille(largeur//2,(255,100,100,200)).convert_alpha()
while continuer :
for event in pygame.event.get():
if event.type == QUIT :
continuer = False
if event.type == MOUSEMOTION :
xp, yp = event.pos
fenetre.blit(imgG,(0,0))
fenetre.blit(past, (xp-lp//2, yp-lp//2))
fenetre.blit(dessine_mots(M1,M2),(700,0))
pygame.display.flip()
Les variables xp et yp représentent les coordonnées de la pastille et
lp son diamètre. Ces variables sont utiles pour conserver en mémoire
ces informations : si on se contente de tracer la pastille lorsque
l’événement MOUSEMOTION est déclenché, la pastille disparaı̂t dès
que la souris est immobile.
151
Solutions des exercices
1. On peut s’inspirer du premier exercice du chapitre MINOS :
C HAPITRE D
C HAPITRE D
Retour sur l’exercice D23 En appliquant l’astuce donnée en aide :
C HAPITRE D
Retour sur l’exercice D23 En appliquant l’astuce donnée en aide :
enfonce = False
while continuer :
Retour
l’exercice
D23 En appliquant l’astuce donnée en aide
for sur
event
in pygame.event.get():
enfonce
=
False
if event.type
== QUIT :
while continuer
:
continuer
= False
for =
event
in pygame.event.get():
enfonce
False
if
==
:
if event.type
event.type
== MOUSEMOTION
QUIT :
while continuer
:
xp, yp = event.pos
continuer
=
False
for event in pygame.event.get():
if
event.type
==
MOUSEBUTTONDOWN
:
if event.type
event.type ==
== QUIT
MOUSEMOTION
:
if
:
enfonce
=event.pos
True
xp,
yp
=
continuer = False
if event.type
event.type ==
== MOUSEBUTTONDOWN
MOUSEBUTTONUP : :
if
if
event.type ==
MOUSEMOTION :
enfonce =
= True
False
enfonce
xp,
yp = event.pos
if not
enfonce
: fenetre.blit(imgG,(0,0))
if event.type
event.type
== MOUSEBUTTONDOWN
MOUSEBUTTONUP : :
if
==
fenetre.blit(past,
(xp-lp//2, yp-lp//2))
enfonce
= True
False
enfonce =
if
enfonce
if not
not
enfonce :
: fenetre.blit(dessine_mots(M1,M2),(700,0))
fenetre.blit(imgG,(0,0))
if event.type
== MOUSEBUTTONUP :
pygame.display.flip()
fenetre.blit(past,
(xp-lp//2, yp-lp//2))
enfonce = False
if not enfonce
: fenetre.blit(imgG,(0,0))
fenetre.blit(dessine_mots(M1,M2),(700,0))
if not enfonce :
pygame.display.flip()
(xp-lp//2, yp-lp//2))
fenetre.blit(past,
if not enfonce : fenetre.blit(dessine_mots(M1,M2),(700,0))
pygame.display.flip()
:
Retour sur l’exercice D24 Avec les formules données dans l’aide, on obtient les deux fonctions suivantes :
Retour sur l’exercice D24 Avec les formules données dans l’aide, on obtient les deux fonctions suivantes :
def XY2Case(XY,
l, Dx,
Dy, Avec
N) : les formules données dans l’aide, on obRetour
sur l’exercice
D24
x, y = XY
tientl,lesc deux
fonctions suivantes
:
= (y-Dy)//(l+1),
(x-Dx)//(l+1)
def XY2Case(XY, l, Dx, Dy, N) :
if y
l =
< XY
0 : l = 0
x,
if c
l =
>=(y-Dy)//(l+1),
N : l = N-1
def l,
XY2Case(XY,
l, Dx, Dy,(x-Dx)//(l+1)
N) :
if l
c <
< 0
0 :
: c = 0
if
x, y = XY l = 0
if l
c >=
>= N
N :
: l
c =
= N-1
N-1
if
l, c
= (y-Dy)//(l+1),
(x-Dx)//(l+1)
return
l, cc = 0
if
if c
l <
< 0
0 :
: l = 0
if
if c
l >=
>= N
N :
: c
l =
= N-1
N-1
def return
Case2XY(C,
Dx, Dy) :
l,:clarg,
if c < 0
c = 0
l, c = C
if c >= N : c = N-1
return c*(larg+1)+Dx,
l*(larg+1)+Dy
def Case2XY(C,
:
return l, clarg, Dx, Dy)
l, c = C
c*(larg+1)+Dx,
l*(larg+1)+Dy
def return
Case2XY(C,
larg, Dx, Dy)
:
l, c = C
return c*(larg+1)+Dx, l*(larg+1)+Dy
Retour sur l’exercice D25 Les changements à réaliser sont :
● d’une part lorsque le bouton de la souris est enfoncé, on va garder en
Retour sur l’exercice D25 Les changements à réaliser sont :
mémoire la case choisie :
● d’une part lorsque le bouton de la souris est enfoncé, on va garder en
Retour sur l’exercice D25 Les changements à réaliser sont :
mémoire la case choisie :
event.type
== MOUSEBUTTONDOWN
: la souris est enfoncé, on va garder en
●ifd’une
part lorsque
le bouton de
enfonce = True
mémoire
choisie :
posDebla=case
XY2Case(event.pos,
largeur, Dx, Dy, 7)
event.type == MOUSEBUTTONDOWN :
enfonce = True
posDeb = XY2Case(event.pos,
Dx, Dy, 7)
if event.type
MOUSEBUTTONDOWNlargeur,
:
enfonce = ==
True
posDeb = XY2Case(event.pos, largeur, Dx, Dy, 7)
if
152
152
152
SELEM-STOM
if event.type == MOUSEBUTTONUP :
enfonce = False
posFin = XY2Case(event.pos, largeur, Dx, Dy, 7)
# On lit le mot situé entre les cases de début et de fin
m = lis_mot(G, posDeb, posFin)
if m in M1 :
dy, dx = direction(posDeb,posFin)
# Coordonnées de la première pastille tracée
x, y = Case2XY(posDeb, largeur, Dx, Dy)
x, y = x + largeur//4, y + largeur // 4
# Coordonnées de la dernière pastille tracée
xfin, yfin = Case2XY(posFin, largeur, Dx, Dy)
xfin, yfin = xfin + largeur//4, yfin + largeur//4
while (x,y) != (xfin, yfin) :
imgG.blit(past,(x,y))
x, y = x+dx, y + dy
# On supprime m des mots à trouver.
M1.remove(m)
M2.append(m)
La ligne x, y = x+largeur//4, y+largeur//4
se justifie par le fait que si l’on veut centrer l’image
d’un disque de diamètre l/2 dans un carré de côté l,
il faut la placer en la décalant de l/4.
l
4
l
4
l
4
Retour sur l’exercice D26 Cet exercice ne présente pas d’intérêt spécial
pour la suite, et n’est pas corrigé en détail. Vous pouvez cependant
télécharger une solution sur le site.
Retour sur l’exercice D27 Dans l’exemple précédent, contenu était un
dictionnaire et contenu[’places’] une liste dont chaque élément est
une ville représentée à son tour à l’aide d’un dictionnaire. Pour une ville
donnée, la clé place name permet de retrouver le nom de celle-ci :
import json
from urllib.request import urlopen
def villes(cp) :
liste = []
requete = urlopen('http://api.zippopotam.us/fr/'+str(cp))
contenu = requete.read().decode('utf-8')
contenu = json.loads(contenu)
for v in contenu['places'] :
liste.append(v['place name'])
return liste
153
Solutions des exercices
● d’autre part quand le bouton est relâché (c’est là qu’il va y avoir le
plus de travail) :
C HAPITRE D
Retour sur l’exercice D28 En appliquant l’algorithme proposé dans l’aide :
import urllib.request as url
def rimes1(mot) :
reponse = []
page =url.urlopen('https://www.rimessolides.com/motscles.aspx?m='+mot)
html = page.read().decode('utf-8')
deb = html.find('divMotcleMobile')
deb = html.find('<a class="l-black" href="motscles.aspx?m=', deb)
deb = html.find('>', deb)
while deb > 0 :
fin = html.find('<', deb)
reponse.append(html[deb+1:fin])
deb = html.find('<a class="l-black" href="motscles.aspx?m=', fin)
deb = html.find('>', deb)
return reponse
Retour sur l’exercice D29 Les réponses ont été présentées dans l’aide,
voici les justifications :
a : On cherche tous les textes dont le motif est T suivi d’une série de
caractères quelconques puis d’un espace.
b : On cherche le premier texte contenant un caractère suivi d’au moins
2 voyelles.
c : La recherche ressemble à la précédente mais on autorise ”n’importe
quoi” avant (le plus long possible).
d : On teste si la chaı̂ne commence par ”mes”, ce qui n’est pas le cas.
Retour sur l’exercice D30 Vous avez toutes les connaissances nécessaires.
Pensez à ne conserver qu’une partie de la page pour ne pas avoir de doublons :
def rimes2(mot) :
page = url.urlopen('https://www.rimessolides.com/motscles.aspx?m='+mot)
html = page.read().decode('utf-8')
html = html[:html.index('divMotcleMobile')]
return re.findall('<a class="l-black" ⤦
� href="motscles.aspx\?m=.*?">(.*?)</a>',html)
154
SELEM-STOM
def rimes3(mot) :
liste = []
page = url.urlopen('https://www.rimessolides.com/motscles.aspx?m='+mot)
html = page.read().decode('utf-8')
html = html[:html.index('divMotcleMobile')]
for l in re.findall('<a class="l-black" ⤦
� href="motscles.aspx\?m=.*?">(.*?)</a>',html) :
l = unicodedata.normalize('NFKD', l).encode('ascii', ⤦
� 'ignore').decode().upper()
if re.fullmatch('[A-Z]*',l) :
liste.append(l)
return liste
Certains mots comme ”C++” ou ”Visual Basic” ont bien disparu de la liste.
155
Solutions des exercices
Retour sur l’exercice D31 On récupère les mots, on retire les majuscules,
on vérifie qu’ils ne contiennent que des lettres. On les ajoute alors dans la
liste des possibilités :
Objectif
Programmer un jeu ...
Programmation
Utiliser l’objet Rect de Pygame
Introduction à la programmation objet
Mathématiques
La courbe du chien
Chapitre E
Cupidon
Jaloux de sa beauté, Jupiter jeta un sort à Cupidon le paralysant,
l’empêchant ainsi de se retourner... À l’aide de votre arc et de vos flèches,
déplacez-vous du mieux que vous pourrez et détruisez les horribles diables
qui cherchent à vous dévorer !
Ce jeu m’a été inspiré par l’un de
mes brillants étudiants Théo LEONARD
en l’aidant à trouver un algorithme de
déplacement pour un projet qu’il était en
train de réaliser au club informatique du
lycée.
157
C HAPITRE E
1 - L’object Rect de Pygame
Dans le projet de PONG, les informations (position, dimensions)
concernant la balle et les raquettes étaient consignées dans des dictionnaires. En réalité Pygame propose déjà un objet qui permet de gérer des
zones rectangulaires très facilement : l’objet Rect.
On peut définir une zone rectangulaire de différentes manières :
Commande
Rect(x,y,l,h)
ou
Rect((x,y),(l,h))
Effet
Retourne un rectangle dont le coin
supérieur gauche est situé en (x,y),
de largeur l et de hauteur h.
img.get rect()
Retourne un rectangle dont le coin
supérieur gauche se trouve en (0,0)
et dont les dimensions sont celles de
l’image img.
On peut alors récupérer une multitude d’informations :
● des valeurs entières :
left=x
centerx
top=y width=w
centery bottom 158
height=h
right
C UPIDON
● des couples d’entiers donnés sous forme de tuples.
topleft
midtop
topright
midleft center
bottomleft
midbottom
midright
bottomright
size est le couple (largeur, hauteur)
● Méthodes sur les objets de type Rect :
Sur les objets Rect, on peut appliquer différentes méthodes (c’est-àdire des fonctions), en voici quelques-unes :
Commande
Effet
R1.copy()
Retourne une copie du rectangle R1.
R1.move(x,y)
Retourne un nouveau rectangle, obtenu
comme image du rectangle R1 par
⃗
la translation de vecteur v(x,y).
R1.collidepoint((x,y))
Renvoie un booléen indiquant si P(x,y)
est à l’intérieur de R1.
Renvoie un booléen indiquant s’il y a
collision entre les rectangles R1 et R2.
Retourne l’index de la première collision
entre une liste R de Rects et R1.
Si aucune collision n’est trouvée, un
index de -1 est renvoyé.
Retourne la liste de tous les indices
des rectangles de R qui entrent
en collision avec le rectangle R1.
R1.colliderect(R2)
R1.collidelist(R)
R1.collidelistall(R)
159
C HAPITRE E
✍ Les bords ”Sud” et ”Est” ne sont pas considérés comme appartenant
au rectangle. Plus rigoureusement, le rectangle Rect(a,b,l,h) est
composé de l’ensemble des points de coordonnées (x,y) telles que :
{
a ⩽ x < a+l
b ⩽ y < b+h
On peut aussi utiliser des objets Rect en arguments avec la méthode
blit : surf .blit(source, dest, area) copie sur la surface surf ,
la surface source dans le coin Nord-Ouest du rectangle dest (sa largeur et sa longueur ne sont pas prises en compte). Si le dernier
argument area n’est pas précisé, l’intégralité de la surface est déposée,
sinon, seule la partie délimitée par le rectangle area est recopiée.
● Un exemple pour résumer :
On peut gérer la balle du pong qui rebondit sur les 4 coins d’une
fenêtre très facilement :
import pygame
pygame.init()
fenetre = pygame.display.set_mode((800,600))
continuer = True
balle = pygame.image.load("balle.png").convert_alpha()
v = (1,2) # Vitesse initiale
balleR = balle.get_rect()
clock = pygame.time.Clock()
while continuer:
clock.tick(100)
for event in pygame.event.get():
if event.type == QUIT :
continuer = False
fenetre.fill((0,0,0))
fenetre.blit(balle, balleR)
if balleR.top < 0 or balleR.bottom > 600 :
v = (v[0], -v[1])
if balleR.left < 0 or balleR.right > 800 :
v = (-v[0], v[1])
balleR.move_ip(v)
pygame.display.flip()
pygame.quit()
160
C UPIDON
2 - Mise en place de Cupidon
Pour ce jeu, on dispose d’un certain nombre d’images pour animer le
personnage principal.
A l’arrêt :
perso 0.png
perso 1.png
perso 2.png
perso 3.png
Lorsque l’on décroche une flèche :
perso 4.png
perso 5.png
perso 6.png
perso 7.png
Lorsque l’on est touché :
perso 8.png perso 9.png perso 10.png perso 11.png perso 12.png
Tous ces éléments se trouvent dans un fichier compressé téléchargeable
sur le site en tapant CUPIDON .
E X E1 Dans une fenêtre 1000 × 600, planter le décor :
1. Placer le fond ainsi que le personnage (on ne prendra que l’image perso 1
pour commencer par exemple).
2. Gérer les actions des flèches de directions pour déplacer le personnage. On
prendra soin de définir un objet Rect qui ”suivra” le personnage pour gérer
les dépassements de l’écran comme cela vient d’être présenté.
Pour réaliser l’exercice précédent, vous vous serez sûrement inspiré
du jeu MINOS déjà programmé en surveillant les événements KEYUP et
KEYDOWN et en conservant en mémoire la liste des touches enfoncées. Pygame propose une fonction qui fait le travail à notre place :
161
C HAPITRE E
get pressed() : cette fonction du module key de Pygame renvoie
un tableau de booléens (plus exactement de 0 et de 1) indiquant une
liste de l’état des touches enfoncées au moment de l’appel. Les indices correspondent aux codes des touches. Par exemple, si vous
voulez savoir si la touche espace est enfoncée, il vous suffit de taper :
touches = pygame.key.get_pressed()
if touches[K_SPACE] :
# Touche espace enfoncée ...
Pour préciser : en Python les booléens sont identifiés avec les nombres
0 (Faux) et 1 (Vrai) :
>>> True + True
2
E X E2 Utiliser cette nouvelle fonction pour alléger le code précédent.
En fait les deux méthodes présentées pour surveiller le clavier sont
intéressantes à connaı̂tre : si vous voulez demander à l’utilisateur
d’entrer son nom par exemple, il est préférable d’utiliser les
événements KEYUP et KEYDOWN car ils seront récupérés dans l’ordre
dans lequel ils se sont déclenchés. Si c’est pour déplacer un personnage, la nouvelle fonction présentée sera très appréciable.
Si vous avez réussi l’exercice, vous devriez avoir un écran semblable
à celui ci-dessous. On s’aperçoit alors que le personnage ne peut pas aller
tout contre les bords de l’écran. Cela est dû à l’image : le personnage n’est
pas rectangulaire ! Il serait donc préférable de ne pas prendre pour rectangle la totalité de celle-ci mais seulement la zone du corps de Cupidon.
Cela sera d’ailleurs d’autant mieux lorsque l’on testera les collisions avec
les ennemis.
37
44
16
72
image 93 × 100
162
C UPIDON
E X E3 Modifier le programme pour utiliser un rectangle autour du personnage
de taille 44 × 72 comme indiqué sur le schéma précédent.
E X E4 À présent, nous allons tenter d’animer notre personnage.
1. Charger toutes les images du personnage dans une liste.
2. Ajouter un compteur entier etat qui va indiquer l’état du personnage.
À chaque tour de boucle, cet état sera augmenté de 1. Pour avoir le temps
de voir les images défiler, on se propose de changer d’image à chaque fois
que etat est un multiple de 10, comme l’illustre le schéma ci-dessous.
0 1 2 3 4 5 6 7 8 9 10 11 12 ... 18 19 20 21 22 ... 28 29 30 31 32 ... 38 39
��� � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � ��� � � � � � � � � � � � � � � � � � � � � � � � ��� � � � � � � � � � � � � � � � � � � � � � � � � ��� � � � � � � � � � � � � � � � � � � � � � � � �� � � � � � � � � � � � � � � � � � � � � � � � � ��� � � � � � � � � � � � � � � � � � � � � � � � ��� � � � � � � � � � � � � � � � � � � � � � � � �
perso 0
perso 1
perso 2
perso 3
Ajouter ensuite l’animation à votre jeu.
3 - La flèche
Il nous faut à présent gérer le décochement d’une flèche lors de l’appui
sur la touche espace. Pour l’animation, nous utiliserons les images 4 et 5
du personnage (il a encore la flèche à la main) et les images 6 et 7, la flèche
est lâchée, comme cela est présenté aux pages précédentes.
E X E5 Dans un premier temps, modifier le code du programme pour qu’à chaque
appui sur la touche espace, l’animation du personnage se déclenche (sans lâcher
réellement une flèche pour le moment).
E X E6 Au moment où Cupidon passe au personnage 6, il faut créer un nouvel
élément : ”la flèche”. Comme il en faudra sûrement plusieurs, on se propose d’utiliser une liste de rectangles pour renseigner les positions de chacune d’entre elles.
La figure ci-dessous indique comment placer l’image de la flèche (de taille 50 × 7)
sur l’image de Cupidon au moment du lâcher.
1. Compléter le lâcher de flèches à l’appui sur la touche espace.
2. Pour que le jeu soit plus réaliste, limiter à 5 le nombre de flèches présentes
à l’écran.
163
C HAPITRE E
22
37
+
44
16
36
=
72
4 - Les ennemis
Contrairement à Cupidon, les ennemis peuvent se retourner pour venir le piquer. Nous disposons donc d’images :
● mechant 0.png à mechant 3.png pour un
méchant en action se déplaçant vers la droite.
● mechant 4.png à mechant 9.png lorsqu’il est
en train de mourir (en regardant vers la droite).
● mechant 10.png à mechant 19.png qui sont similaires mais où le monstre regarde vers la gauche.
Le premier travail consiste à programmer le
déplacement du méchant en direction de Cupidon à
vitesse constante. Si on note C le centre de Cupidon
et M celui du méchant, ce dernier doit se déplacer
��→
dans le sens MC et avec une vitesse constante v. Le
v ��→
vecteur de translation v⃗ = ��→ MC convient.
∥ MC ∥
M
C �→
Si Cupidon se déplace en ligne droite comme cela est illustré ci-dessus,
on visualise le mouvement du monstre qui s’adapte en temps réel. Ce type
de courbe est appelé ≪ courbe du chien ≫ ou parfois courbe de poursuite.
164
C UPIDON
C UPIDON
E X E7 Écrire une fonction vitesse qui reçoit en paramètre deux rectangles R1
etE XR2
retourne
formevitesse
d’un couple,
les coordonnées
du deux
vecteur
de translaE7etÉcrire
unesous
fonction
qui reçoit
en paramètre
rectangles
R1
tion
pour
que
R1
aille
en
direction
de
R2
à
une
vitesse
de
2
pixels.
et R2 et retourne sous forme d’un couple, les coordonnées du vecteur de translation pour que R1 aille en direction de R2 à une vitesse de 2 pixels.
E X E8 En utilisant la fonction précédemment programmée, placer un premier
méchant
(assez
éloigné
Cupidon)
et qui se déplace
vers sa cible.
Pour
E X E8 En
utilisant
la de
fonction
précédemment
programmée,
placer
un l’animapremier
tion
du
méchant,
on
utilisera
comme
pour
Cupidon,
deux
variables
:
RMechant
méchant (assez éloigné de Cupidon) et qui se déplace vers sa cible. Pour l’animapour
le rectangle
l’étatpour
de l’animation.
tion du
méchant, et
onetatM
utiliserapour
comme
Cupidon, deux variables : RMechant
pour le rectangle et etatM pour l’état de l’animation.
E X E9 Avec un seul méchant, le jeu est bien trop facile, nous allons en mettre
plus
comme
E X Een
9 utilisant
Avec un des
seullistes
méchant,
le d’habitude.
jeu est bien trop facile, nous allons en mettre
plus1.enModifier
utilisantledes
listes comme
programme
pourd’habitude.
que tous les 100 tours de boucle, un nouveau
méchant
apparaisse
à
un
endroit
sur l’écran.
1. Modifier le programme pour que aléatoire
tous les 100
tours de boucle, un nouveau
à un
endroit
aléatoire sur l’écran.
2. méchant
Améliorerapparaisse
le code pour
que
:
2. Améliorer
le code pour
que :
● les méchants
n’apparaissent
pas n’importe où, mais plutôt dans le dos
de
Cupidon
et
”assez
loin”
de
luin’importe
pour que oleù,jeu
reste
intéressant
● les méchants n’apparaissent pas
mais
plutôt
dans le ;dos
Cupidon et
”assez loin”d’un
de lui
pour que
le jeu reste
intéressant
;
● de
la fréquence
d’apparition
nouveau
méchant
dépende
du nombre
de fréquence
méchants d’apparition
déjà présentsd’un
à l’écran.
Parméchant
exemple,dépende
à chaque
un
● la
nouveau
dutour,
nombre
1
de
méchants
déjà présents
l’écran.
Par exemple,
nouveau
monstre
apparaı̂t àavec
une probabilité
de à chaque tour,
où un
N
100N1 + 10
est le nombre
de méchants
où N
nouveau
monstre
apparaı̂t présents.
avec une probabilité de
100N + 10
le nombre
de méchants
présents.
3. Enfin,estgérer
les images
des monstres,
afin qu’ils regardent vers Cupidon
lorsque
ce
dernier
se
déplace.
3. Enfin, gérer les images des monstres, afin qu’ils regardent vers Cupidon
lorsque ce dernier se déplace.
Il reste encore à gérer les collisions des différents éléments du jeu, c’està-dire
les interactions
méchant
↔ flèche
méchant éléments
↔ Cupidon.
Il reste
encore à gérer
les collisions
desetdifférents
du jeu, c’est-
à-dire les interactions méchant ↔ flèche et méchant ↔ Cupidon.
E X E10 Commencer par le premier type de collisions ce qui permettra d’effectuer
les
risquer de
E Xtests
E10 sans
Commencer
parmourir.
le premier type de collisions ce qui permettra d’effectuer
les tests sans risquer de mourir.
E X E11 Sur le même principe, gérer les collisions entre Cupidon et les monstres.
E X E11 Sur le même principe, gérer les collisions entre Cupidon et les monstres.
E X E12 À présent que l’on sait dessiner un texte à l’écran, gérer les scores se-
lon
la12règle
suivanteque
: pour
une dessiner
flèche donnée,
le premier
EX E
À présent
l’on sait
un texte
à l’écran,monstre
gérer lestouché
scoresrapseporte
1
point,
puis
2
pour
le
suivant,
4
....
et
ainsi
de
suite
avec
une
progression
lon la règle suivante : pour une flèche donnée, le premier monstre touché rapgéométrique
raison
2. le suivant, 4 .... et ainsi de suite avec une progression
porte 1 point,depuis
2 pour
géométrique de raison 2.
Voici l’intégralité du code du jeu proposé duquel nous allons repartir
pour
la suite
du chapitre
:
Voici
l’intégralité
du code
du jeu proposé duquel nous allons repartir
165
pour la suite du chapitre :
165
C HAPITRE E
C HAPITRE E
from pygame.locals import *
from math import sqrt
from pygame.locals
import *
time import sleep
from math
import
sqrt
random
import
random, randint
from time import sleep
from
random import random, randint
def afficheScore(s):
font=pygame.font.Font(pygame.font.match_font('arial'), 40, bold=True)
def afficheScore(s):
text = font.render(str(s),True,(0,0,0))
font=pygame.font.Font(pygame.font.match_font('arial'),
40, bold=True)
fenetre.blit(text, (10,550))
text = font.render(str(s),True,(0,0,0))
(10,550))
def fenetre.blit(text,
vitesse(R1,R2) :
x1,y1 = R1.center
def vitesse(R1,R2)
:
x2,y2 = R2.center
x1,y1
dx, dy==R1.center
x2-x1, y2-y1
x2,y2
= R2.center
l = sqrt(dx
**2+dy**2)
dx,
y2-y1
if ldy
> =
0 x2-x1,
:
l = sqrt(dx
return **
2*2+dy
dx/l,
2*dy/l
**2)
if
l >
else
: 0 :
return 2
0,0
*dx/l, 2*dy/l
else :
return 0,0
pygame.init()
fenetre = pygame.display.set_mode((1000,600))
pygame.init()
etat = 0
fenetre
score = =
0 pygame.display.set_mode((1000,600))
etat
perso==0[pygame.image.load('perso_'+str(i)+'.png').convert_alpha() for ⤦
score
� =i 0in range(0,13)]
perso
for ⤦
Rperso==[pygame.image.load('perso_'+str(i)+'.png').convert_alpha()
Rect(37,16,44,72)
in range(0,13)]
fond �= ipygame.image.load('fond.jpg').convert_alpha()
Rperso
fleche = Rect(37,16,44,72)
pygame.image.load('fleche.png').convert_alpha()
fond
= pygame.image.load('fond.jpg').convert_alpha()
Rfleches
= []
fleche
mechant==pygame.image.load('fleche.png').convert_alpha()
[pygame.image.load('mechant_'+str(i)+'.png').convert_alpha() ⤦
Rfleches
� for= i[]in range(0,20)]
mechant
⤦
Rmechant==[pygame.image.load('mechant_'+str(i)+'.png').convert_alpha()
[]
� =for
etatM
[] i in range(0,20)]
Rmechant
continuer==[]
True
etatM
clock = []
pygame.time.Clock()
continuer
= True
while continuer:
clock
= pygame.time.Clock()
clock.tick(100)
while
continuer:
for
event in pygame.event.get():
clock.tick(100)
if event.type == QUIT :
for event
in pygame.event.get():
continuer
= False
if event.type
== QUIT :
touches
= pygame.key.get_pressed()
continuerand
= False
if touches[K_UP]
Rperso.top > 0 :
touches
= pygame.key.get_pressed()
Rperso.move_ip((0,-2))
if touches[K_UP]
and
Rperso.top
> 0 :< 600 :
touches[K_DOWN]
and
Rperso.bottom
Rperso.move_ip((0,-2))
Rperso.move_ip((0,2))
if touches[K_DOWN]
:
touches[K_LEFT] and Rperso.bottom
Rperso.left > <
0 600
:
Rperso.move_ip((0,2))
Rperso.move_ip((-2,0))
if touches[K_LEFT]
> 0
touches[K_RIGHT]and
andRperso.left
Rperso.right
< :
1000:
Rperso.move_ip((-2,0))
Rperso.move_ip((2,0))
if touches[K_RIGHT]
touches[K_ESCAPE]and
: Rperso.right < 1000:
Rperso.move_ip((2,0))
continuer = False
if
touches[K_ESCAPE]
:
etat
= etat + 1
continuer
if etat
== 40 =
orFalse
etat == 80 :
etatetat
= etat
= 0+ 1
if etat == 40
== 80
: flèche
60 or
: #etat
Lâcher
d'une
etat = 0
166 if etat == 60 : # Lâcher d'une flèche
166
C UPIDON
Rfleches.append((Rect(Rperso.x-25,Rperso.y+20,50,7),1))
if touches[K_SPACE] and etat < 40 and len(Rfleches) < 5 :
etat = 40
fenetre.blit(fond,(0,0))
afficheScore(score)
fenetre.blit(perso[etat//10],Rperso.move(-37,-16))
# Ajout d'un méchant ?
if random() < 1/(100*len(Rmechant)+10) :
x, y = Rperso.center # On est sûr que cette position ne ⤦
� convient pas.
while (x-Rperso.x)**2+(y-Rperso.y)**2 < 100**2 :
x, y = randint(Rperso.x,1000), randint(0,600)
Rmechant.append(Rect(x,y,64,64))
etatM.append(0)
# Gestion des méchants
i = 0
while i < len(Rmechant) :
v = vitesse(Rmechant[i],Rperso)
Rmechant[i].move_ip(v)
directionM = 10 if v[0] < 0 else 0
fenetre.blit(mechant[etatM[i]//10+directionM], Rmechant[i])
etatM[i] = etatM[i] + 1
if etatM[i] == 40 :
etatM[i] = 0
if etatM[i] == 100 :
etatM.pop(i)
Rmechant.pop(i)
else :
i = i + 1
# Gestion des flèches
for r,p in Rfleches :
fenetre.blit(fleche,r)
r.move_ip(-3,0)
if r.right < 0 :
Rfleches.remove((r,p))
# Collision entre une flèche et un méchant
for j in range(len(Rfleches)) :
r,p = Rfleches[j]
L = r.collidelistall(Rmechant)
for i in L :
if etatM[i] < 40 :
etatM[i] = 40
score = score + p
p = p*2
Rfleches[j] = (r,p)
# Collision entre un méchant et Cupidon
if etat < 80 :
for i in Rperso.collidelistall(Rmechant) :
if etatM[i] < 40 :
etat = 80
if etat == 129 :
continuer = False
pygame.display.flip()
if etat == 129 : sleep(4)
pygame.quit()
167
C HAPITRE E
5 - Introduction à la programmation objet
Dès le premier chapitre, nous avons vu comment regrouper plusieurs
informations dans un dictionnaire de manière à n’utiliser qu’une variable
par entité (balle, raquette, ...). Nous allons faire mieux en créant des objets dans lesquels nous allons regrouper un certain nombre de variables
(on parlera d’attributs) et un certain nombre de fonctions (on parlera de
méthodes).
La programmation objet ou POO (Programmation Orientée Objet) est
un type de programmation qui permet de créer des entités indépendantes
les unes des autres avec leurs propres informations et leurs propres actions. Ainsi dans une équipe de développeurs, chacun peut créer ses
propres objets sans risquer d’entrer en conflit avec les autres.... Heureusement, nous verrons que les objets peuvent aussi interagir entre eux.
Pour vous rassurer, sachez que dès votre premier programme en Python vous avez manipulé des objets sans même le savoir, car en Python
tout est objet : un nombre, une liste... et même un module... Dans tous les
cas, tout ce qui peut être réalisé en programmation objet peut aussi l’être
de manière classique, mais vous verrez qu’à l’usage le code produit est
souvent bien plus clair en POO.
Pour utiliser des objets, nous allons définir un modèle que l’on appelle
classe qui explicitera les attributs et les méthodes. Commençons par
décrire la classe Mechant. On pourrait commencer ainsi en partant d’une
nouvelle page vide :
1
2
class Mechant :
""" Classe d'un méchant """
3
4
5
6
7
168
def __init__(self) :
self.x = 100
self.y = 200
self.etat = 0
C UPIDON
Dans l’exemple précédent :
1. On a créé une nouvelle classe du nom de Mechant. La déclaration
d’une nouvelle classe commence par le mot clé class suivi de
son nom. À noter qu’il est d’usage que les noms des classes commencent par une majuscule.
2. Juste sous la déclaration de la classe, on place une chaı̂ne de caractères donnant une description de la classe en question, comme
pour les fonctions (que l’on appelle docstring).
4. On a ensuite utilisé une fonction au nom particulier de init .
Cette fonction, appelée constructeur en informatique, est invoquée
à chaque fois qu’un objet de la classe est créé. Elle permet, entre
autres, d’initialiser tous les attributs de ce nouvel objet. Cette
méthode possède un paramètre, usuellement noté self, qui
représente l’objet en question lors de l’appel. Toutes les méthodes
que nous créerons comporteront ce paramètre self. Si vous voulez mettre un autre nom que self, vous le pouvez mais sachez que
99,99999999% des programmeurs utilisent celui-ci.
5. On déclare 3 attributs de ce nouvel objet : x et y qui seront les coordonnées du coin Nord-Ouest de la position initiale du joueur et
etat, un entier pour connaı̂tre le numéro de l’animation à afficher.
À présent que nous avons déclaré la classe Mechant, nous pouvons
créer une instance de cette classe, c’est-à-dire un objet de type Mechant :
jeanMarie = Mechant()
On peut alors accéder aux informations de cette manière :
>>> type(jeanMarie)
<class '__main__.Mechant'>
>>> jeanMarie.x
100
On accède donc à l’attribut a d’un objet obj en tapant obj.a (le
point peut être vu comme le ≪ ’s ≫ de possession en anglais).
169
C HAPITRE E
C HAPITRE E
Ces variables sont accessibles en lecture / écriture (nous y reviendrons
plus
loin pour
C HAPITRE
E préciser certaines choses), mais deux objets créés dans cette
Ces variables sont accessibles en lecture / écriture (nous y reviendrons
classe
sont
indépendants.
L’illustration
qui
suit y
montre
que x
Ces
variables
sont accessibles
en lecture
/ écriture
(nous
reviendrons
C HAPITRE Etotalement
plus loin pour préciser certaines choses), mais deux objets créés dans cette
est
clairement
un
attribut
d’instance
(chaque
instance
possède
ses
propres
plus loin pour préciser certaines choses), mais deux objets créés dans cette
classe
totalement
indépendants.
L’illustration
qui
suit y
montre
que x
Cessont
variables
sont accessibles
en lecture
/ écriture
(nous
reviendrons
attributs)
classe sont: totalement indépendants. L’illustration qui suit montre que x
est clairement
attribut
d’instance
(chaque
possède
ses
propres
plus
loinvariables
pour un
préciser
certaines
choses),
maisinstance
deux objets
créés
dans
cette
Ces
accessibles
en lecture
écriture
(nous
y reviendrons
est clairement
un sont
attribut
d’instance
(chaque/ instance
possède
ses propres
attributs)
:
classe
sont
totalement
indépendants.
L’illustration
qui
suit
montre
que
x
plus
loin pour
préciser certaines choses), mais deux objets créés dans cette
attributs)
:
est clairement
un attribut
d’instance (chaque
instance
ses propres
classe
sont totalement
indépendants.
L’illustration
quipossède
suit montre
que x
>>>
jeanMarie
=un
Mechant()
attributs)
:
est
clairement
attribut d’instance (chaque instance possède ses propres
>>> donald = Mechant()
attributs)
:
>>>
jeanMarie.x + 10
>>> jeanMarie.x
jeanMarie = =
Mechant()
>>>
>>>
>>>
110
>>>
>>>
>>>
100
>>>
>>>
110
>>>
110
>>>
>>>
>>>
100
>>>
>>>
100
110
>>>
>>>
110
100
>>>
100
jeanMarie
= Mechant()
jeanMarie.x
donald = Mechant()
donald
= Mechant()
jeanMarie.x
= jeanMarie.x
donald.x
jeanMarie.x
jeanMarie.x
jeanMarie = =
Mechant()
jeanMarie.x
jeanMarie.x
donald = Mechant()
jeanMarie = Mechant()
donald.x
jeanMarie.x = jeanMarie.x
donald
= Mechant()
donald.x
jeanMarie.x
jeanMarie.x = jeanMarie.x
jeanMarie.x
donald.x
+ 10
+ 10
+ 10
+ 10
donald.x
Il est possible de donner des valeurs à certains attributs au moment de
la création de l’objet et même des valeurs par défaut :
Il est possible de donner des valeurs à certains attributs au moment de
Il est possible de donner des valeurs à certains attributs au moment de
la création de l’objet et même des valeurs par défaut :
la
création
de :l’objet et même des valeurs par défaut :
class Mechant
Il
est
possible
de donner des valeurs à certains
attributs au moment de
""" Classe d'un méchant """
>>> vladimir = Mechant(300)
la
création
de :l’objet
et mêmedes
desvaleurs
valeursà par
défaut
:
Il est
possible
de donner
certains
attributs
au moment de
>>>
vladimir.x
class
Mechant
class
Mechant
:
300
def
__init__(self,
xdebut,
:
"""
Classe
d'un
méchant
"""ydebut=200)
>>>
vladimir
= Mechant(300)
la
création
de
l’objet
et
même
des
valeurs
par
défaut
:
"""
Classe
méchant """
vladimir = Mechant(300)
>>> vladimir.y
self.x
= d'un
xdebut
>>> vladimir.x
200
self.y
= ydebut
>>>
class
Mechant
:
300 vladimir.x
def
__init__(self,
xdebut, ydebut=200) :
self.etat
= 0 méchant
def
__init__(self,
xdebut,
300 vladimir.y
"""
Classe
"""ydebut=200) :
vladimir = Mechant(300)
self.x
= d'un
xdebut
class
Mechantxdebut
:
>>>
self.x
>>>
200
self.y =
= ydebut
>>> vladimir.y
vladimir.x
"""
Classe
méchant """
>>>
vladimir = Mechant(300) self.y
= d'un
ydebut
200
self.etat
= 0
300
def
__init__(self,
xdebut, ydebut=200) :
>>> vladimir.x
self.etat
=
0
>>> vladimir.y
self.x = xdebut
def
__init__(self,
: 300
Les
attributs
ne sexdebut,
limitentydebut=200)
pas à des entiers
bien évidemment. On peut
200
self.y
= ydebut
>>> vladimir.y
self.x = xdebut
= 0
par self.etat
exemple
pré-charger
toutes les images du
méchant dans le construc-
200
self.y
= ydebut
Les
attributs
ne
se
limitent
pas
à
des
entiers
bien
évidemment. On peut
self.etat
= 0ne se limitent pas à des entiers bien évidemment. On peut
teurLes
ainsi
:
attributs
par exemple pré-charger toutes les imagesdu méchant dans le construc-
par exemple pré-charger toutes les images du méchant dans le constructeurLes
ainsi
:
attributs
ne se limitent pas à des entiers bien évidemment. On peut
teur ainsi :
par Les
exemple
pré-charger
toutes
lesà des
images
du bien
méchant
dans le construcattributs ne se limitent
pas
entiers
évidemment.
On peut
def
__init__(self,
xdebut,
ydebut)
:
teur
ainsi : pré-charger toutes les images du méchant dans le construcpar exemple
...
teur
ainsi :
self.images=[pygame.image.load('mechant_'+str(i)+'.png').convert_alpha()
def
__init__(self, xdebut, ydebut) :
def __init__(self,
xdebut,
ydebut) :
for i
in range(0,20)]
...
...
self.images=[pygame.image.load('mechant_'+str(i)+'.png').convert_alpha()
self.images=[pygame.image.load('mechant_'+str(i)+'.png').convert_alpha()
def
__init__(self,
ydebut) :
for xdebut,
i in range(0,20)]
...
for i in range(0,20)]
def __init__(self, xdebut, ydebut) :
self.images=[pygame.image.load('mechant_'+str(i)+'.png').convert_alpha()
...
for i in range(0,20)]
self.images=[pygame.image.load('mechant_'+str(i)+'.png').convert_alpha()
for i in range(0,20)]
Laissons de côté la classe Mechant pour le moment.
Laissons de côté la classe Mechant pour le moment.
170 Laissons de côté la classe Mechant pour le moment.
170 Laissons de côté la classe Mechant pour le moment.
170
Laissons de côté la classe Mechant pour le moment.
170
170
C UPIDON
E X E13 On s’intéresse à la déclaration du héros Cupidon.
1. Écrire une classe Cupidon pour laquelle les objets possèderont les attributs :
● sprites : une liste contenant toutes les images du personnage Cupidon.
● rectangle : un objet de type Rect représentant la position de
Cupidon.
● un entier etat : pour indiquer l’image en cours pour l’animation
du héros.
2. Créer une instance cupi de cette classe et réaliser les modifications
nécessaires au programme pour qu’il fonctionne en retirant les variables
perso, Rperso et etat de la version d’origine.
Même si le programme de l’exercice précédent fonctionne correctement avec les modifications apportées, tout n’est pas satisfaisant pour autant : en effet, on accède et modifie les attributs de l’objet cupi depuis le
programme principal, ce qui est contraire au principe d’encapsulation.
Lorsque l’on modifie des attributs d’un objet, il se peut que cela ait
des conséquences. Par exemple qu’il faille effectuer des vérifications
au préalable ou que cela entraı̂ne des modifications d’autres attributs. C’est pour cette raison qu’en programmation objet, on interdit, en principe, l’accès en lecture/écriture à ces informations depuis
l’extérieur. Dans certains langages comme le Java ou le C++ cela se
traduit par des attributs publics ou privés. Python, conforme à ses
principes de grande souplesse d’utilisation, permet par défaut ces
accès.
Pour le moment, nous allons donc continuer à profiter de cette possibilité d’accéder aux attributs depuis l’extérieur de la classe, tout en gardant
à l’esprit qu’il est préférable de s’en passer. Voici par exemple comment
on peut déclarer une fonction qui agit sur les attributs à l’intérieur même
d’une classe (on parle alors de méthode sur l’objet). L’exemple qui suit
illustre comment modifier l’ordonnée d’un objet de type Mechant.
class Mechant :
...
def up(self) :
self.y = self.y - 1
>>>
>>>
20
>>>
>>>
19
vladimir = Mechant(10,20)
vladimir.y
vladimir.up()
vladimir.y
171
C HAPITRE E
L’appel à la méthode vous est déjà familier, par exemple lors d’un
l.sort() pour trier une liste l. Comme pour la fonction init . On
peut bien entendu préciser un certain nombre de paramètres, mais le premier (self) est nécessaire pour définir une méthode :
...
def deplace(self, vx, vy) :
""" Effectue une translation de
vecteur (vx, vy)"""
self.x = self.x + vx
self.y = self.y + vy
>>> vladimir = Mechant(10,20)
>>> vladimir.x, vladimir.y
(10, 20)
>>> vladimir.deplace(5,-3)
>>> vladimir.x, vladimir.y
(15, 17)
Noter que lorsqu’une méthode agit sur les attributs d’un objet, il n’est
pas nécessaire d’utiliser l’instruction return, puisqu’il n’y a rien à renvoyer. C’est ainsi que vous procédez déjà pour les listes avec la méthode
append, d’ailleurs souvent source d’erreur chez les débutants qui tapent
l = l.append(x) au lieu de l.append(x) détruisant ainsi la liste l.
Avant de repartir sur quelques exercices, présentons une méthode au
nom réservé str . Cette méthode est appelée lorsque Python tente de
transformer un objet en chaı̂ne de caractères via l’instruction str ou encore lors d’un print (dans ce cas Python transforme en chaı̂ne sans que
vous ne le sachiez avant d’afficher le résultat). Cette méthode spéciale permet donc d’afficher facilement les informations d’un objet :
def __str__(self)
infos =
infos = infos +
infos = infos +
infos = infos +
return infos
:
"Méchant : \n"
"------------------------\n"
"Le méchant est en ("+str(self.x)+","+str(self.y)+")\n"
"dans l'état "+str(self.etat)
Pour afficher ces informations, il faut mettre le print dans la console,
sinon, c’est une autre méthode qui est appelée ( repr ).
>>> vladimir = Mechant(10,20)
>>> print(vladimir)
Méchant :
-----------------------Le méchant est en (10,20)
dans l′ état 0
E X E14 Écrire pour la classe Cupidon une fonction update qui ne reçoit pas
de paramètre (excepté pour préciser l’objet sur lequel elle agit). Dans cette fonction, on essaiera de placer un maximum d’actions de la boucle principale du jeu,
sans s’occuper pour le moment des lancers de flèches, ni des interactions avec les
ennemis.
172
C UPIDON
Continuons la présentation de la programmation objet autour de notre
exemple de la classe Mechant. Sa déclaration devrait ressembler à celle de
Cupidon : un rectangle, un état et une liste d’images. A y réfléchir, c’est
peut-être dommage de placer dans chaque instance de la classe la liste
des images, puisqu’elles sont communes à tout objet de la classe. C’est
là qu’interviennent les attributs de classe. L’exemple suivant permet de
mieux comprendre la portée des variables :
class Mechant :
""" Classe d'un méchant """
etat = 10
def __init__(self, xdebut, ydebut= 200) :
self.x = xdebut
self.y = ydebut
self.etat = 0
def attributs(self) :
print(self.etat)
print(Mechant.etat)
print(etat)
etat = 5
self.etat est un attribut d’instance : il >>>
est propre à chaque objet. Mechant.etat >>>
est un attribut de classe : il est commun à 0
10
la classe alors que le dernier etat est une 5
variable globale du programme.
drDenfer = Mechant(5,5)
drDenfer.attributs()
Avant d’aller plus loin avec la classe Mechant, créons la classe Fleche
qui est plus simple mais sur la même idée : elle comporte un attribut de
classe sprite (l’image d’une flèche) ainsi que deux attributs d’instance
rectangle et points, dont les noms explicites devraient suffire à comprendre leur utilité. Si on déclare ainsi la classe :
class Fleche :
sprite = pygame.image.load('fleche.png').convert_alpha()
def __init__....
On obtient alors le message d’erreur suivant dès l’exécution du code :
pygame.error: cannot convert without pygame.display initialized
Ce qui s’explique par le fait que c’est dès le début du programme que la
classe est lue et donc que la variable sprite est mise en mémoire (contrairement aux cas précédents où la lecture des images se faisait au moment de
l’instanciation). Or la méthode convert alpha permet de convertir une
173
C HAPITRE E
image dans le même format vidéo que la fenêtre d’affiche qui, ici, n’est pas
encore créée.
On peut néanmoins contourner le problème en chargeant les images
dès le début, mais en ne les convertissant qu’ensuite, une fois que la fenêtre
d’affichage a été déclarée. Pour cela, on aura recours à une fonction d’initialisation, par exemple :
class Fleche :
sprite = pygame.image.load('fleche.png').convert_alpha()
def initialise() :
Fleche.sprite = Fleche.sprite.convert_alpha()
...puis, dans le programme principal...
fenetre = pygame.display.set_mode((1000,600))
Fleche.initialise()
E X E15 Compléter :
1. la déclaration de la classe Fleche par :
● une méthode constructeur qui recevra en paramètre un objet de la classe
Cupidon (pour savoir d’où part la flèche en question) ;
● une méthode update qui déplace un objet Fleche vers la gauche. Cette
méthode renvoie True si la flèche sort de l’écran et False sinon.
2. le reste du programme pour gérer :
● les créations et suppressions de flèches,
● leurs déplacements,
● les collisions avec les méchants.
Pour finaliser le projet sous sa forme POO, il ne reste plus qu’à s’occuper enfin de la classe Mechant, mais vous devez savoir faire cela à présent.
E X E16 On peut évidemment s’inspirer de la déclaration de la classe précédente.
Notre nouvelle classe possèdera :
● un attribut de classe (sprites) qui contiendra une liste d’images et qui
sera converti une fois la fenêtre de jeu déclarée.
● un constructeur qui recevra le héros comme paramètre de manière à ce
qu’un méchant n’apparaisse pas trop près de lui.
● une méthode update qui fera évoluer les monstres vers Cupidon (qu’il
faudra donc penser à placer en paramètre), changer leurs états et qui renverra True s’il faut supprimer ce méchant.
174
C UPIDON
C UPIDON
C UPIDON
Adapter le reste du programme pour que le jeu continue de fonctionner.
Adapter le reste du programme pour que le jeu continue de fonctionner.
Adapter le reste du programme pour que le jeu continue de fonctionner.
Voici un projet qui touche à sa fin. Bien entendu certaines parties du
Voici un projet qui touche à sa fin. Bien entendu certaines parties du
code
resteraient
à améliorer
le Bien
fait de
modifier
des attributs
Voici
un projet
qui touchecomme
à sa fin.
entendu
certaines
parties dedu
code resteraient à améliorer comme le fait de modifier des attributs depuis l’extérieur
la classe. comme
On aurait
aussi
inclure des
le score
dansdela
code
resteraient de
à améliorer
le fait
depu
modifier
attributs
puis l’extérieur de la classe. On aurait aussi pu inclure le score dans la
classel’extérieur
Cupidon et
toutes
classes
dans
module
pour
puis
deplacer
la classe.
Onces
aurait
aussi
puuninclure
leextérieur
score dans
la
classe Cupidon et placer toutes ces classes dans un module extérieur pour
réduireCupidon
le code principal...
Tant de
reste à découvrir
dans
classe
et placer toutes
ceschoses
classesqu’il
dansnous
un module
extérieur pour
réduire le code principal... Tant de choses qu’il nous reste à découvrir dans
les prochains
! Tant de choses qu’il nous reste à découvrir dans
réduire
le codechapitres
principal...
les prochains chapitres !
les prochains chapitres !
6
- Aide pour
les exercices
6
6 -- Aide
Aide pour
pour les
les exercices
exercices
Aide pour l’exercice E5 Inutile de créer de nouvelles variables ici, utilisez
Aide pour l’exercice E5 Inutile de créer de nouvelles variables ici, utilisez
à bonpour
escient
la variable
etat etde
pensez
qu’il
n’est physiquement
posAide
l’exercice
E5 Inutile
créer de
nouvelles
variables ici,pas
utilisez
à bon escient la variable etat et pensez qu’il n’est physiquement pas possible
de
lancer
2
flèches
en
même
temps
!
à bon escient la variable etat et pensez qu’il n’est physiquement pas possible de lancer 2 flèches en même temps !
sible de lancer 2 flèches en même temps !
Aide pour l’exercice E7 En considérant les centres des rectangles du
Aide pour l’exercice E7 En considérant les centres des rectangles du
méchant
et de Cupidon
on peut déterminer
formules
donnant
Aide
pour(M)
l’exercice
E7 En (C),
considérant
les centreslesdes
rectangles
du
méchant (M) et de Cupidon (C), on peut déterminer les formules donnant
le vecteur(M)
vitesse
de norme
méchant
et de(ici
Cupidon
(C),2).
on peut déterminer les formules donnant
le vecteur vitesse (ici de norme 2).
le vecteur vitesse (ici de norme 2).
�
→
→
C →
● Si MC = 0, �
v→ = �
0
�
C →
● Si MC = 0, �
v =�
0
C ● Si MC = 0, →
v = 0
�
�
→
2
→
�→, avec :
● Sinon �
v→ = 2 �
MC
�→, avec :
2 �
● Sinon �
v = MC
MC
�
→
● Sinon
MC , avec :
√ v = MC
2
2
MC = √
(x − xMC
M )2 + (yC − yM )2
MC = √(xC
C − xM )2 + (yC − yM )2
MC = (xC − xM ) + (yC − yM )
��→ xC − xM
M �→ (xC − xM )
et �
MC
M �
�→ (xy − yxM )
et
MC
M − yM )
et MC (yC
yC
C − yM
Aide pour l’exercice E9
Aide pour l’exercice E9
Aide pour l’exercice E9
1. Pour déterminer une position à l’écran, on peut utiliser la fonction
1. Pour déterminer une position à l’écran, on peut utiliser la fonction
du module
de
1. randint
Pour déterminer
une random
position :àrandint(a,b)
l’écran, on peutrenvoie
utiliserun
la entier
fonction
randint du module random : randint(a,b) renvoie un entier de
l’intervalledu
[a,b].
randint
module random : randint(a,b) renvoie un entier de
l’intervalle [a,b].
l’intervalle [a,b].
175
175
175
C HAPITRE E
2. Une technique pour faire une action avec une probabilité de p est de
tirer aléatoirement un nombre dans l’intervalle [0,1[ selon la loi uniforme (situation d’équiprobabilité) à l’aide de l’instruction random()
du module random. Si ce nombre est inférieur à p, on réalise l’action.
0
x = random()
p
1
3. Pensez que la première composante du vecteur vitesse vous donne
la direction où regarder.
Aide pour l’exercice E10 Pensez à la méthode collidelistall qui vous
renverra la liste des indices des méchants entrant en collision avec la flèche
par exemple.
Aide pour l’exercice E14 On peut inclure dans la méthode :
● la prise en compte des touches,
● l’évolution de l’attribut etat,
● l’affichage du personnage.
7 - Solutions des exercices
Retour sur l’exercice E1
1. Voici une proposition pour la première question qui ne devrait pas
nécessiter d’explication. On a ajouté une pause avant de quitter
Pygame pour laisser le temps d’admirer le résultat :
import pygame
from time import sleep
pygame.init()
fenetre = pygame.display.set_mode((1000,600))
perso = pygame.image.load('perso_1.png').convert_alpha()
Rperso = perso.get_rect()
fond = pygame.image.load('fond.jpg').convert_alpha()
fenetre.blit(fond,(0,0))
fenetre.blit(perso,Rperso)
pygame.display.flip()
sleep(5)
pygame.quit()
176
C UPIDON
2. Pour les déplacements, on va s’inspirer de l’exemple de la balle du
jeu de PONG donné précédemment. On ajoute aussi l’événement
quitter pour terminer proprement le jeu.
Après avoir importé les variables de Pygame
from pygame.locals import *
fenetre.blit(fond,(0,0))
fenetre.blit(perso,Rperso)
pygame.display.flip()
sleep(5)
par cette boucle :
Touches = []
continuer = True
clock = pygame.time.Clock()
while continuer:
clock.tick(100)
for event in pygame.event.get():
if event.type == QUIT :
continuer = False
elif event.type == KEYDOWN and event.key not in Touches :
Touches.append(event.key)
elif event.type == KEYUP and event.key in Touches :
Touches.remove(event.key)
if K_UP in Touches and Rperso.top > 0 :
Rperso.move_ip((0,-2))
if K_DOWN in Touches and Rperso.bottom < 600 :
Rperso.move_ip((0,2))
if K_LEFT in Touches and Rperso.left > 0 :
Rperso.move_ip((-2,0))
if K_RIGHT in Touches and Rperso.right < 1000:
Rperso.move_ip((2,0))
fenetre.blit(fond,(0,0))
fenetre.blit(perso,Rperso)
pygame.display.flip()
177
Solutions des exercices
nous allons remplacer les 4 lignes
C HAPITRE E
Retour sur l’exercice E2 Il n’y a pas grand-chose à modifier :
import pygame
from pygame.locals import *
from time import sleep
pygame.init()
fenetre = pygame.display.set_mode((1000,600))
perso = pygame.image.load('perso_1.png').convert_alpha()
Rperso = perso.get_rect()
fond = pygame.image.load('fond.jpg').convert_alpha()
continuer = True
clock = pygame.time.Clock()
while continuer:
clock.tick(100)
for event in pygame.event.get():
if event.type == QUIT :
continuer = False
touches = pygame.key.get_pressed()
if touches[K_UP] and Rperso.top > 0 :
Rperso.move_ip((0,-2))
if touches[K_DOWN] and Rperso.bottom < 600 :
Rperso.move_ip((0,2))
if touches[K_LEFT] and Rperso.left > 0 :
Rperso.move_ip((-2,0))
if touches[K_RIGHT] and Rperso.right < 1000:
Rperso.move_ip((2,0))
if touches[K_ESCAPE] :
continuer = False
fenetre.blit(fond,(0,0))
fenetre.blit(perso,Rperso)
pygame.display.flip()
print('fin')
pygame.quit()
Retour sur l’exercice E3 Il n’y a que deux lignes à changer :
● L’initialisation :
Rperso = perso.get_rect()
Rperso = Rect(37,16,44,72)
�⇒ ● et l’affichage :
fenetre.blit(perso,Rperso)
fenetre.blit(perso,Rperso.move(-37,-16))
�⇒ À noter que dans cette dernière ligne, on utilise un move au lieu d’un
moveip car on ne souhaite pas modifier le rectangle, mais juste le décaler
temporairement pour l’affichage.
178
C UPIDON
Retour sur l’exercice E4
1. Une première méthode consiste à saisir la liste manuellement :
perso = [pygame.image.load('perso_0.png').convert_alpha(),
pygame.image.load('perso_1.png').convert_alpha(),
pygame.image.load('perso_2.png').convert_alpha(),
...
pygame.image.load('perso_13.png').convert_alpha()]
perso = []
for i in range(0,13) :
perso.append(pygame.image.load('perso_'+str(i)+'.png') ⤦
� .convert_alpha())
ou même en une ligne, à l’aide d’une liste en compréhension :
perso = [pygame.image.load('perso_'+str(i)+'.png') ⤦
� .convert_alpha() for i in range(0,13)]
2. Pour l’animation, il nous faut :
● initialiser la valeur etat à 0 avant la boucle principale du jeu :
etat = 0
● incrémenter la valeur etat dans la boucle et la remettre à 0 si
elle atteint 40 :
etat = etat + 1
if etat == 40 :
etat = 0
On pourrait optimiser ainsi :
etat = (etat + 1) % 40
puisque dans ce cas, si etat+1 est strictement inférieur à 40 le
reste de la division ne le modifiera pas, et lorsqu’il passera à 40,
le reste vaudra alors 0.
● Enfin, on modifie légèrement la ligne d’affichage :
fenetre.blit(perso[etat//10], Rperso.move(-37, -16))
Comme d’habitude, l’intégralité du code est disponible sur le site du
livre.
179
Solutions des exercices
Mais si on est peu courageux, comme tout bon programmeur, une
boucle sera la bienvenue :
C HAPITRE E
Retour sur l’exercice E5 Pour lancer l’animation, il faut faire franchir à
etat le seuil de 40. Une fois arrivé à 80, on revient alors à 0. Voici la partie
incrémentation de etat modifiée :
etat = etat + 1
if etat == 40 or etat == 80 :
etat = 0
if touches[K_SPACE] and etat < 40 :
etat = 40
Retour sur l’exercice E6
1. Dans la partie initialisation, on charge l’image et on initialise la liste
des rectangles qui vont représenter les flèches :
fleche = pygame.image.load('fleche.png').convert_alpha()
Rfleches = []
Dans la boucle principale, lorsque le personnage passe à l’image 6
(c’est-à-dire etat = 60), on crée et ajoute un rectangle à la bonne
place :
if etat == 60 : # Lâcher d'une flèche
Rfleches.append(Rect(Rperso.x-25,Rperso.y+20,50,7))
Reste alors à animer chaque flèche à l’aide d’une boucle for et à la
supprimer lorsqu’elle est totalement sortie de l’écran :
for r in Rfleches :
fenetre.blit(fleche,r)
r.move_ip(-3,0)
if r.right < 0 :
Rfleches.remove(r)
2. Pour ne pas utiliser plus de 5 flèches, il faut remplacer la ligne :
if touches[K_SPACE] and etat < 40 :
etat = 40
par :
if touches[K_SPACE] and etat < 40 and len(Rfleches) < 5 :
etat = 40
180
C UPIDON
Retour sur l’exercice E7 En s’inspirant de la figure donnée dans l’aide, on
peut calculer la distance puis appliquer la formule (pensez à importer la
bibliothèque math) :
from math import sqrt
Retour sur l’exercice E8 La programmation de cet exercice ressemble beaucoup à ce qui a été fait pour le personnage :
● initialisation :
mechant = [pygame.image.load('mechant_'+str(i)+'.png'). ⤦
� convert_alpha() for i in range(0,20)]
Rmechant = mechant[0].get_rect()
etatM = 0
● et dans la boucle principale :
Rmechant.move_ip(vitesse(Rmechant,Rperso))
fenetre.blit(mechant[etatM//10], Rmechant)
etatM = etatM + 1
if etatM == 40 :
etatM = 0
Retour sur l’exercice E9
1. Les deux variables suivantes vont être remplacées par des listes vides
et on ajoute une variable qui compte le nombre de tours de boucle :
Rmechant = mechant[0].get_rect()
etatM = 0
devient :
Rmechant = []
etatM = []
nbtours = 0
181
Solutions des exercices
def vitesse(R1,R2) :
x1,y1 = R1.center
x2,y2 = R2.center
dx, dy = x2-x1, y2-y1
l = sqrt(dx**2+dy**2)
if l > 0 :
return 2*dx/l, 2*dy/l
else :
return 0,0
C HAPITRE E
Dans un second temps, il faut programmer l’apparition d’un joueur
dans la boucle principale :
nbtours = nbtours + 1
if nbtours == 100 :
nbtours = 0
x, y = randint(0,1000), randint(0,600)
Rmechant.append(Rect(x,y,64,64))
etatM.append(0)
Enfin, la partie animation doit à présent utiliser les listes Rmechant
et etatM :
for i in range(len(Rmechant)) :
Rmechant[i].move_ip(vitesse(Rmechant[i],Rperso))
fenetre.blit(mechant[etatM[i]//10], Rmechant[i])
etatM[i] = etatM[i] + 1
if etatM[i] == 40 :
etatM[i] = 0
2. Pour le tirage aléatoire, l’aide apporte déjà des idées qu’il suffit
d’implémenter en remplacement de la condition nbtours == 100
(on peut d’ailleurs supprimer cette variable). Pour la position d’apparition, on peut choisir une position aléatoire jusqu’à ce que la distance avec le monstre soit supérieure à 100 pixels :
if random() < 1/(100*len(Rmechant)+10) :
x, y = Rperso.center # Cette position ne convient pas.
while (x-Rperso.x)**2+(y-Rperso.y)**2 < 100**2 :
x, y = randint(Rperso.x,1000), randint(0,600)
Rmechant.append(Rect(x,y,64,64))
etatM.append(0)
3. Enfin, pour gérer la direction, nous allons regarder pour chaque
monstre si l’abscisse du vecteur vitesse est positive ou négative, pour
déterminer le sens dans lequel celui-ci doit regarder. On va donc
remplacer les 2 lignes :
Rmechant[i].move_ip(vitesse(Rmechant[i],Rperso))
fenetre.blit(mechant[etatM[i]//10], Rmechant[i])
par le calcul d’une valeur directionM qui indique le décalage (ou
non) de 10 images de manière à obtenir celles tournées vers la droite
(0 à 9) ou celles vers la gauche (10 à 19) :
182
C UPIDON
v = vitesse(Rmechant[i],Rperso)
Rmechant[i].move_ip(v)
if v[0] < 0 :
directionM = 10
else :
directionM = 0
Rmechant[i].move_ip(v)
fenetre.blit(mechant[etatM[i]//10+directionM], Rmechant[i])
ou même plus court et plus pythonesque :
Retour sur l’exercice E10 On peut ajouter ces quelques lignes avant le
pygame.display.flip() de fin de boucle, à l’aide de la fonction
collidelistall. On va, pour chaque flèche, regarder l’ensemble des
indices des monstres touchés :
for r in Rfleches :
L = r.collidelistall(Rmechant)
for i in L :
if etatM[i] < 40 :
etatM[i] = 40
#
#
#
#
#
Pour chaque flèche
On regarde les monstres touchés
Pour chaque monstre touché,
s'il n'est pas déjà mourant
on le fait mourir
Il faut encore gérer la disparition des monstres une fois carbonisés. Si vous
aviez opté pour une boucle for pour gérer les monstres, comme proposé
dans la correction de l’exercice 7, vous allez avoir des soucis, car lors de
la suppression d’un monstre, la taille de la liste va être modifiée et vos
indices (calculés au moment de l’entrée dans la boucle) vont dépasser la
taille de celle-ci. Il faut donc remplacer :
# Gestion des méchants
for i in range(len(Rmechant)) :
...
par
# Gestion des méchants
i = 0
while i < len(Rmechant) :
...
if etatM[i] == 100 :
etatM.pop(i)
Rmechant.pop(i)
else :
i = i + 1
# Si l'état du monstre arrive à 100
# On le supprime
# Sinon on passe au monstre suivant.
183
Solutions des exercices
v = vitesse(Rmechant[i],Rperso)
Rmechant[i].move_ip(v)
directionM = 10 if v[0] < 0 else 0
fenetre.blit(mechant[etatM[i]//10+directionM], Rmechant[i])
C HAPITRE E
Vous aurez remarqué que lorsque l’on supprime un monstre, la valeur
de i n’est pas augmentée puisque, le monstre ayant disparu, on passe au
suivant sans changer d’indice.
Retour sur l’exercice E11 Cette fois, on peut utiliser la méthode
collidelist puisqu’il n’est pas nécessaire de récupérer toutes les collisions : dès qu’un monstre touche Cupidon, le jeu s’arrête. On ajoute donc
ces quelques lignes avant le display.flip :
# Collision entre un méchant et Cupidon
if etat < 80 and Rperso.collidelist(Rmechant) > -1 :
etat = 80
Il faut aussi penser à quitter le jeu lorsque la partie est terminée :
if etat == 129 : # Fin du jeu
continuer = False
Vous pourrez constater en le testant que lorsqu’un monstre est mourant, il
ne faut (toujours) pas le toucher. On peut corriger ce problème, en
récupérant cette fois les indices de tous les monstres touchés et en ne tenant compte que de ceux qui sont en pleine forme :
if etat < 80 :
for i in Rperso.collidelistall(Rmechant) :
if etatM[i] < 40 :
etat = 80
Enfin, on peut aussi ajouter avant le pygame.quit() la ligne :
if etat == 129 : sleep(4)
afin de laisser un petit délai avant la fermeture de la fenêtre.
Retour sur l’exercice E12 On peut déjà commencer par créer une fonction
qui affichera (ici en bas à gauche) le score en s’inspirant de la fonction
proposée dans le projet PONG :
def afficheScore(s):
font=pygame.font.Font(pygame.font.match_font('arial'), 40, bold=True)
text = font.render(str(s),True,(0,0,0))
fenetre.blit(text, (10,550))
184
C UPIDON
Il faut aussi créer, avant la boucle principale, une variable score que l’on
initialise à 0, puis afficher ce score dans la boucle juste après le fond par
exemple (tout dépend si vous préférez voir le score par dessus ou par dessous les personnages) :
fenetre.blit(fond,(0,0))
afficheScore(score)
Dans un second temps, il faut réaliser le calcul de ce score en associant
à chaque flèche le nombre de points qu’elle rapporte par monstre touché
(1, puis 2, 4...). Plusieurs solutions s’offrent à nous : on peut par exemple
créer une seconde liste SFleches qui sera maintenue à jour en même
temps que RFleches ou, et c’est ce que nous allons faire ici, ne conserver qu’une liste RFleches dans laquelle on placera des couples : (Rect,
points). Quelques lignes vont devoir évoluer :
● lorsqu’une nouvelle flèche est créée :
if etat == 60 : # Lâcher d'une flèche
Rfleches.append(Rect(Rperso.x-25,Rperso.y+20,50,7))
devient :
if etat == 60 : # Lâcher d'une flèche
Rfleches.append((Rect(Rperso.x-25,Rperso.y+20,50,7),1))
● lors du déplacement des flèches :
for r in Rfleches :
fenetre.blit(fleche,r)
r.move_ip(-3,0)
if r.right < 0 :
Rfleches.remove(r)
devient :
for r,p in Rfleches :
fenetre.blit(fleche,r)
r.move_ip(-3,0)
if r.right < 0 :
Rfleches.remove((r,p))
185
Solutions des exercices
C HAPITRE E
● enfin, pour gérer les points lorsqu’une flèche touche un monstre,
on ne peut plus utiliser la boucle for r,p in Rfleches puisque
nous allons avoir à modifier les éléments de la liste Rfleches ; il
faut donc réaliser une boucle sur les indices et non les valeurs :
for r in Rfleches :
L = r.collidelistall(Rmechant)
for i in L :
if etatM[i] < 40 :
etatM[i] = 40
devient :
for j in range(len(Rfleches)) :
r,p = Rfleches[j]
L = r.collidelistall(Rmechant)
for i in L :
if etatM[i] < 40 :
etatM[i] = 40
score = score + p
p = p*2
Rfleches[j] = (r,p)
Retour sur l’exercice E13 Pour la première question, on peut s’inspirer de
la classe Mechant :
class Cupidon :
""" La classe du joueur """
def __init__(self) :
self.sprites = [pygame.image.load('perso_'+str(i)+'.png'). ⤦
� convert_alpha() for i in range(0,13)]
self.rectangle = Rect(37,16,44,72)
self.etat = 0
Pour la seconde question, on peut initialiser l’objet cupi en remplaçant
les 3 lignes :
perso = [pygame.image.load('perso_'+str(i)+'.png').convert_alpha() for ⤦
� i in range(0,13)]
Rperso = Rect(37,16,44,72)
etat = 0
par :
cupi = Cupidon()
Puis, dans le corps du programme de remplacer tous les
● perso par cupi.sprites
● Rperso par cupi.rectangle
● etat par cupi.etat
186
C UPIDON
Retour sur l’exercice E14 Dans la méthode update :
● on surveille les touches enfoncées
● on fait évoluer l’attribut etat
def update(self) :
touches = pygame.key.get_pressed()
if touches[K_UP] and cupi.rectangle.top > 0 :
cupi.rectangle.move_ip((0,-2))
if touches[K_DOWN] and cupi.rectangle.bottom < 600 :
cupi.rectangle.move_ip((0,2))
if touches[K_LEFT] and cupi.rectangle.left > 0 :
cupi.rectangle.move_ip((-2,0))
if touches[K_RIGHT] and cupi.rectangle.right < 1000:
cupi.rectangle.move_ip((2,0))
cupi.etat = cupi.etat + 1
if cupi.etat == 40 or cupi.etat == 80 :
cupi.etat = 0
fenetre.blit(cupi.sprites[cupi.etat//10],cupi.rectangle.move(-37,-16))
Il suffit alors dans la boucle principale de supprimer ces lignes et de
les remplacer par :
cupi.update()
Retour sur l’exercice E15 Commençons par la création complète de la
classe. Elle ne comporte pas de difficulté particulière : la fonction
initialise ayant déjà été présentée, le constructeur récupère l’objet
représentant le joueur pour déterminer les coordonnées de départ de la
flèche comme cela a déjà été fait dans la version non-objet :
class Fleche :
sprite = pygame.image.load('fleche.png')
def initialise() :
Fleche.sprite = Fleche.sprite.convert_alpha()
def __init__(self, cup) :
self.rectangle = Rect(cup.rectangle.x-25,cup.rectangle.y+20,50,7)
self.points = 1
def update(self) :
fenetre.blit(Fleche.sprite, self.rectangle)
self.rectangle.move_ip(-3,0)
if self.rectangle.right < 0 :
return True
return False
187
Solutions des exercices
● on finit par afficher le personnage à sa place
C HAPITRE E
À noter que les trois dernières lignes de la méthode update peuvent
être avantageusement remplacées par :
return self.rectangle.right < 0
Pour le reste du programme, il faut aussi :
● avant la boucle principale, initialiser la variable fleches qui contiendra une liste de flèches ainsi que la classe Fleche pour convertir
l’image :
fleches = []
Fleche.initialise()
● dans la boucle, mettre en place le lâcher de flèches lorsque l’état du
joueur est à 60 :
if cupi.etat == 60 : # Lâcher d'une flèche
fleches.append(Fleche(cupi))
● gérer les déplacements et suppressions de flèches. On peut faire le
choix de modifier la liste fleches dans la méthode update car Python le permet. Personnellement, je préfère éviter (lorsque cela est
possible) de modifier une variable globale à l’intérieur d’une fonction, c’est pour cela que la méthode update renvoie True s’il faut
supprimer l’objet. Pour les mêmes raisons que dans la version nonobjet, on utilise un TANT QUE plutôt qu’un POUR car la taille de la
liste évolue au fur et à mesure des disparitions de flèches. La collision avec les méchants quant à elle fonctionne sur le même principe
que précédemment, c’est un peu plus simple à gérer dans la mesure
où le rectangle de la flèche et le nombre de points sont deux variables
séparées avec cette version objet :
j = 0
while j < len(fleches) :
f = fleches[j]
if f.update() : # La flèche disparait ?
fleches.remove(f)
else : # Sinon, on teste le contact avec un méchant
L = f.rectangle.collidelistall(Rmechant)
for i in L :
if etatM[i] < 40 :
etatM[i] = 40
score = score + f.points
f.points = 2*f.points
j = j + 1
● il faut aussi penser à remplacer tous les len(Rfleches) qui
peuvent traı̂ner dans le reste du code par len(fleches). La version complète du programme est proposée sur le site.
188
C UPIDON
Retour sur l’exercice E16 En suivant le plan qui est proposé, on peut donc
déclarer la classe et les fonctions demandées :
class Mechant :
sprites = [pygame.image.load('mechant_'+str(i)+'.png') for i in ⤦
� range(0,20)]
Solutions des exercices
def vitesse(R1,R2) :
x1,y1 = R1.center
x2,y2 = R2.center
dx, dy = x2-x1, y2-y1
l = sqrt(dx**2+dy**2)
if l > 0 :
return 2*dx/l, 2*dy/l
else :
return 0,0
def initialise() :
for i in range(len(Mechant.sprites)) :
Mechant.sprites[i] = Mechant.sprites[i].convert_alpha()
def __init__(self, cup) :
x, y = cup.rectangle.center
while (x-cup.rectangle.x)**2+(y-cup.rectangle.y)**2 < 100**2 :
x, y = randint(cup.rectangle.x,1000), randint(0,600)
self.rectangle = Rect(x,y,64,64)
self.etat = 0
def update(self, cup) :
v = Mechant.vitesse(self.rectangle,cup.rectangle)
self.rectangle.move_ip(v)
directionM = 10 if v[0] < 0 else 0
image = Mechant.sprites[self.etat//10+directionM]
fenetre.blit(image, self.rectangle)
self.etat = self.etat + 1
if self.etat == 40 :
self.etat = 0
return self.etat == 100
Le contenu du code ne devrait pas poser de difficulté dans la mesure où il
a déjà été étudié en début de chapitre. Noter que l’on a inclus la fonction
vitesse dans la classe Mechant, on l’appelle donc en indiquant le nom
de la classe comme pour un module.
L’ajout d’un méchant est géré selon la même idée que l’ajout de flèches,
on initialise le chargement des images en début de jeu ainsi qu’une liste
vide destinée à contenir les méchants :
mechants = []
Mechant.initialise()
189
HAPITRE E
E
C HAPITRE
peut alors
alors ajouter
ajouterde
denouveaux
nouveauxméchants
méchantsde
detemps
tempsenentemps
temps
On peut
: :
random() << 1/(100
1/(100**len(mechants)+10)
len(mechants)+10)::
if random()
mechants.append(Mechant(cupi))
mechants.append(Mechant(cupi))
faire évoluer
évolueret
etdisparaı̂tre
disparaı̂tre: :
Et les faire
i = 0
len(mechants) ::
while ii << len(mechants)
m == mechants[i]
mechants[i]
if
if m.update(cupi)
m.update(cupi) ::
mechants.remove(m)
mechants.remove(m)
else
else ::
ii == ii ++ 11
La collision
collision d’un
d’un méchant
méchantavec
avecCupidon
Cupidondoit
doitaussi
aussiêtre
êtreadaptée
adaptéepuisque
puisque
dans la version
versionprécédente,
précédente,on
ondisposait
disposaitde
delalaliste
listedes
desrectangles
rectangles
Rmechant,
Rmechant,
il faut donc
donc la
la recréer
recréer::
if cupi.etat
cupi.etat << 80
80 ::
for ii in
in cupi.rectangle.collidelistall([m.rectangle
cupi.rectangle.collidelistall([m.rectanglefor
form mininmechants]):
mechants]):
if
if mechants[i].etat
mechants[i].etat << 40
40 ::
cupi.etat
cupi.etat == 80
80
Il reste àà faire
faire de
de même
même pour
pour lalacollision
collisiondes
desflèches
flèchesetetdes
desméchants.
méchants.
Comme
Comme d’habitude,
d’habitude,l’ensemble
l’ensembledu
ducode
codeest
estdisponible
disponiblesur
surlelesite.
site.
cam
190
Objectif
Réaliser un lanceur de jeux
Programmation
Lecture/Écriture d’un fichier de configuration
Utiliser le module transform de pygame
Mathématiques
Courbes paramétrées
Enroulement d’une image sur un cylindre
Chapitre F
Front-end
On souhaite réaliser un menu de style rétro ressemblant à la capture
d’écran ci-dessous. Ce menu nous servira à lancer les jeux de notre création.
Ce type d’interface porte aussi le nom de ≪ front-end ≫ c’est-à-dire, d’une
manière plus générale, la partie visible d’une application communicante
avec d’autres.
MENU
Nous allons, au travers de ce décor vintage, revisiter quelques animations classiques des démos des années 90... À l’époque j’étais en seconde
et mon frère en terminale et nous avions mis sur pied un petit groupe
de création de démos sur Atari : la team LEGEND . Nous passions pas
mal de temps à l’époque à chercher des idées de démos et à les coder des
191
C HAPITRE F
après-midi durant. Pour les nostalgiques, ou les curieux, un site regroupe
un certain nombre de ces écrans recodés pour l’occasion en HTML5 : We
Are Back : http://www.wab.com/ WAB
1 - Manipuler les fichiers de configuration
Pour ne pas avoir à modifier le code du programme à chaque fois que
l’on ajoute ou supprime un jeu, nous allons utiliser un fichier de configuration pour y placer la liste des jeux que l’on souhaite afficher.
Les fichiers de configuration sont des fichiers permettant de regrouper
de manière structurée des informations. Ici nous présenterons les fichiers
INI qui ont été introduits par Windows dès ses premières versions et qui
sont de nos jours toujours très répandus.
Voici par exemple un extrait du fichier de configuration de PyScripter,
l’éditeur utilisé dans EduPython :
...
Item88=[Default @ TabSplitter]
Item89=SplitterRestorePos=60
Item90=
Count=91
[Other Settings]
PyScripter Version=2.6.0.0
Language=fr
File Explorer Filter=TRUE
File Explorer Path=
Status Bar=TRUE
Theme Name=Dream
Active Project=
[IDE Options]
; Options interface
TimeOut=0
UndoAfterSave=TRUE
SaveFilesBeforeRun=TRUE
;Enregistrer avant exécution
PythonFileFilter=Python Files (*.py;*.pyw)|*.py;*.pyw
AutoCheckForUpdates=FALSE
DaysBetweenChecks=7
DetectUTF8Encoding=TRUE
CheckSyntaxLineLimit=1000
[IDE Options\AutoCompletionFont]
Charset=DEFAULT_CHARSET
Color=clWindowText
Height=-13
...
192
F RONT- END
On peut y voir que les informations sont regroupées par sections délimitées par des crochets
à l’intérieur desquels on retrouve différentes clés
(ou paramètres) et leurs valeurs séparées par le
symbole =.
La structure est donc celle-ci :
[SECTION A]
clé1 = valeur1
clé2 = valeur2
clé3 = valeur3
clé4 = valeur4
[SECTION B]
clé1 = valeur1
...
On pourrait donc manipuler ces fichiers comme de simples fichiers
texte comme nous l’avons appris dans le tome 1 au chapitre E, mais le
module configparser va nous simplifier la vie.
Création et enregistrement
On peut soit créer et éditer manuellement un fichier INI avec un
simple éditeur de texte type bloc-notes, soit le générer en Python, une
fois l’objet de type configparser créé, la manipulation ressemble
à celle des dictionnaires vus au premier chapitre :
import configparser
config = configparser.ConfigParser()
# On renseigne toute une section
config['ECRAN'] = {
'Largeur' : 1600,
'Hauteur' : 900,
'16/9' : 'YES'}
# Ou on en crée une que l'on complète au fur et à mesure
config['TOUCHES'] = {}
config['TOUCHES']['VALIDER'] = 'SPACE'
config['TOUCHES']['UP'] = '8'
config['TOUCHES']['DOWN'] = '2'
# Enregistrement du fichier
fichier = open('infos.ini', 'w')
config.write(fichier)
fichier.close()
193
C HAPITRE F
On obtient alors le fichier ci-contre. On peut cependant remarquer que :
✍ ● L’ordre des clés a été modifié, ce qui n’est
pas étonnant puisque les informations sont
stockées dans un dictionnaire.
● Toutes les clés sont écrites en minuscules.
● Il n’y a pas de différence entre le nombre
900 et la chaı̂ne 8.
[ECRAN]
16/9 = YES
hauteur = 900
largeur = 1600
[TOUCHES]
valider = SPACE
up = 8
down = 2
Pour finir, on peut remarquer que, dans le code précédent, la variable
fichier n’est utilisée que pour écrire et que nous n’en avons plus
besoin ensuite. On pourrait la détruire :
# Enregistrement du fichier
fichier = open('infos.ini', 'w')
config.write(fichier)
fichier.close()
del(fichier)
ou la déclarer comme variable locale à l’aide de l’instruction with :
# Enregistrement du fichier
with open('infos.ini', 'w') as fichier :
config.write(fichier)
fichier.close()
Ainsi, lorsque l’on quitte l’indentation, la variable fichier n’existe
plus, selon le même principe que lorsque l’on quitte une fonction.
Lecture des informations
Lorsque l’on dispose d’un fichier INI, on peut lire les informations :
import configparser
config = configparser.ConfigParser()
config.read('infos.ini')
On peut récupérer la liste des sections :
>>> config.sections()
['ECRAN', 'TOUCHES']
194
F RONT- END
Pour une section donnée, on peut lister les clés et les valeurs
comme pour un dictionnaire :
# Liste des clés dans une section :
S = config['ECRAN']
for cle in S :
print(cle, '--->', S[cle])
16/9 ---> YES
largeur ---> 1600
hauteur ---> 900
On peut directement accéder à la valeur d’une clé si on connaı̂t la
clé et la section de celle-ci. Les 3 lignes suivantes ont le même effet :
config['ECRAN']['largeur']
S['largeur']
config.get('ECRAN', 'Largeur')
À noter qu’avec la méthode get , on peut préciser une valeur de
retour si la clé n’est pas trouvée :
>>> config.get('ECRAN', 'diagonale', fallback="Calcule !")
'Calcule !'
Enfin, pour pallier au problème de typage de données (par défaut
toute valeur est une chaı̂ne de caractères), on peut forcer le type à la
lecture :
config.getint('ECRAN', 'hauteur', fallback = -1 )
config.getboolean('ECRAN', '16/9', fallback = True )
config.getfloat('ECRAN', 'largeur' )
900
True
1600.0
Revenons à notre projet. Notre fichier INI
sera structuré comme le montre la capture
ci-contre : les noms de sections sont les
noms des jeux. Pour chaque section, un certain nombre de clés en dessous donnent
des informations sur celui-ci : l’auteur, la
date ou le nom de l’image à afficher pour
présenter le jeu...
E X F1 Après avoir téléchargé sur le site le fichier liste.ini,
1. Écrire un programme qui affiche la liste des jeux.
2. Le compléter pour qu’il affiche la liste des images associées.
195
C HAPITRE F
Vous aurez donc remarqué que certains jeux n’ont pas de clé image
associée et que pour d’autres, l’image est renseignée mais n’est en réalité
pas présente dans le dossier images, ce qui va poser des problèmes pour
la suite. En Python, on peut tester la présence d’un fichier à l’aide de la
fonction isfile du module path du package os :
isfile(f ich) : renvoie un booléen indiquant si le fichier f ich est
présent. La chaı̂ne de caractères f ich peut contenir :
● un chemin absolu, par exemple "C:/monimage.png" (noter
qu’il est préférable, même sous Windows, de mettre des / dans
les chemins (comme sur le web ou Linux) plutôt que des /),
● un chemin relatif, par exemple "image.png". Dans ce cas Python cherche dans le dossier courant, c’est-à-dire par défaut le
dossier où votre programme est enregistré,
● un chemin relatif mais qui comporte des dossiers, par exemple
"images/monimage.png", dans ce cas Python cherche dans
le dossier courant, un dossier images et dans ce dossier le
fichier monimage.png. On peut aussi remonter dans l’arborescence du disque à l’aide de la chaı̂ne "../". Par exemple
"../monimage.png" recherche le fichier dans le dossier père
du dossier courant.
E X F2 Prendre en compte ces remarques pour que le programme précédent affiche
le nom de l’image si elle est définie et bien présente. Dans le cas contraire, on
remplacera l’image par l’image notfound.png :
turtle.png
196
notfound.png
F RONT- END
2 - La bande de film
Intéressons-nous dans un premier temps au plus gros du travail, c’està-dire la création de la bande de film déroulante qui contiendra la liste des
jeux.
Pour cela, on dispose d’un certain nombre d’images de taille 300 ×
225 dans un dossier images dont nous avons déjà parlé dans la partie précédente. On dispose aussi d’une image pellicule.png de taille
420 × 300 dont la partie centrale est transparente et que nous allons donc
superposer à nos images pour obtenir un effet de bande de film :
+
=
60
E X F3 Écrire une fonction montage qui reçoit
un nom de fichier et réalise le montage de
l’image (si elle n’est pas présente, l’image de
substitution précédente est utilisée). La fonction
renvoie le résultat sous la forme d’une surface.
13
image.png
pellicule.png
Dans notre menu, la bande vidéo sera placée sur le côté droit de l’écran,
comme l’illustre la figure ci-dessous. On dispose d’une image d’un fond
vintage de taille 1180 × 900 qui sera, elle, placée à gauche de l’écran.
197
C HAPITRE F
1180
1600
BAND
E VIDE
O
0
E X F4 Pour commencer, on s’intéresse à l’initialisation de
la classe en créant quelques attributs :
● config qui représentera le contenu du fichier
list.ini afin d’obtenir les informations sur les
jeux au moment voulu,
● bande : une longue surface de 420 pixels de large
où les images des différents jeux seront placées les
unes en dessous des autres et montées sur l’image
pellicule,
● nbj : le nombre de jeux présents.
En vous appuyant sur les précédents exercices (lecture du
fichier ini, création de la pellicule...) réaliser le constructeur de cette classe nommée Jeux.
À noter qu’avec Pygame, la longueur et la largeur
d’une surface sont limitées à 65535 = 216 − 1 de
manière à être codées sur 2 octets. Ce qui nous laisse
un potentiel de plus de 200 jeux, cela devrait nous
suffire dans ce livre !
bande :
198
F RONT- END
E X F5 Il nous faut à présent afficher la bande dans le menu. Pour cela, on va
ajouter un attribut decale initialisé à 0 et qui représentera le décalage de la
bande comme l’illustre la figure suivante. Programmer une méthode dessine
pour la classe Jeux qui, comme son nom l’indique, placera la bande à l’écran avec
le décalage souhaité. Cerise sur le gâteau, on place par dessus la bande, l’image
transparente ombre.png pour donner un effet de halo.
decale=150
+
ombre.png
On souhaite à présent pouvoir faire glisser la bande à l’aide des touches
de direction du clavier.
E X F6 Réaliser une méthode update qui surveille le clavier et affiche la bande
en conséquence. Cette méthode renvoie False si la touche échappe est enfoncée
et True sinon. Comme pour les autres projets, l’appel à cette méthode sera placé
dans une boucle infinie jusqu’à ce que le joueur quitte le menu. Les problèmes de
dépassements en début et fin de bande seront gérés dans l’exercice suivant.
1
E X F7 Comme vous l’aurez remarqué, il y a un souci lorsque l’on
2
monte trop ou descend trop. Pour cela, nous allons utiliser une astuce en recopiant le contenu de la hauteur d’un écran (900 pixels soit
3 images de 300 pixels) en fin de bande. Ainsi, lorsque la valeur de
decale vaut la valeur max, on la remet à 0. Inversement, lorsque
decale passe en dessous de 0 on le remet à max, donnant l’illusion
que la bande est cyclique. Modifier le code précédent pour mettre en
place cette amélioration.
3
max
1
2
3
Et voilà ! Pas si compliqué, au final.
199
C HAPITRE F
E X F8 Il nous reste à gérer une action lorsque l’on sélectionne un jeu.
1. Compléter votre programme pour qu’il affiche le nom du jeu dans la console
lors de l’appui sur la touche espace.
2. Améliorer le code pour qu’au fur et à mesure du scrolling de la bande vidéo,
le jeu en cours de sélection apparaisse dans le téléviseur du bas.
Ce dernier exercice termine la partie principale de notre menu. Si on
souhaite réaliser une action, il nous faut lancer une ligne de commande,
par exemple python monjeu.py pour lancer un nouveau script Python
ou mame tnzs.zip pour démarrer le jeu ”The Newzealand Story” sur
l’émulateur MAME (à condition qu’ils soient présents sur votre machine
bien entendu !).
system(cmd) : exécute la ligne de commande cmd. Cette fonction
se trouve dans le module os de Python.
Il ne reste plus qu’à placer quelques éléments de décoration sur notre
menu. La partie suivante va nous apprendre à réaliser un Unlimited Sprites,
un grand classique des démos des années 90.
3 - Unlimited sprites
À l’époque où notre menu nous propulse, réaliser le moindre
déplacement d’un personnage demandait un temps machine non
négligeable. Pourtant, certains petits malins arrivaient à animer une infinité d’objets à l’écran...
De nos jours, l’affichage est beaucoup plus rapide... toujours est-il qu’il
reste - et restera - impossible de déplacer une infinité de sprites sur un
écran. Attention, les pages qui suivent révèlent un tour de magie démasqué
par mon frère (AKA Rick from LEGEND) !
Commençons par déplacer un unique sprite sur la télévision A dont
les dimensions ont été mesurées à l’aide du logiciel de retouche d’images
GIMP.
200
F RONT- END
218
149
A
sprite.png
20 × 20
687
286
Le cadre n’étant pas grand, on a choisi un sprite assez petit de taille
20 × 20 pixels. Le déplacement de sprites se fait traditionnellement le long
d’une courbe paramétrée. Pour notre menu, nous allons nous inspirer des
courbes de Lissajous, un physicien français du XIXe siècle.
Avant de nous lancer dans la détermination de l’équation de la trajectoire, estimons la place dont nous disposons :
L’écran de télévision mesure 286 × 218 pixels auxquels il faut retirer 2 × 10 pixels sur les bords, soit
266 × 198 pixels. Nous allons donc construire une
courbe dont les points auront une abscisse comprise entre −133 et 133 et une ordonnée entre −99
et 99.
Un peu de géométrie analytique nous sera utile :
1
{
−1
1
−1
x(t) = sin(t)
y(t) = cos(t)
[Équation d’un cercle] à t = 0 : on
part du point blanc, puis lorsque t
augmente, on tourne dans le sens des
aiguilles d’une montre. Nous allons
”dilater” les dimensions pour rentrer
dans l’écran....
201
C HAPITRE F
{
−100
100
[Une ellipse] Nous allons à présent
augmenter la fréquence sur les abscisses.
{
−100
100
100
202
100
x(t) = 133 sin(8t)
y(t) = 99 cos(t)
Puis la même idée sur les
ordonnées...
{
−100
x(t) = 133 sin(2t)
y(t) = 99 cos(t)
Encore un peu plus...
{
−100
x(t) = 133 sin(t)
y(t) = 99 cos(t)
x(t) = 133 sin(8t)
y(t) = 99 cos(5t)
Il n’y a plus qu’à effectuer
une translation...
F RONT- END
{
x(t) = 133 + 133 sin(8t)
y(t) = 99 + 99 cos(5t)
E X F9 Écrire une classe USprites qui possèdera les attributs sprite
(l’image) et t (le temps), ainsi qu’une méthode update qui aura pour effet
de déplacer le sprite à l’écran (on ne s’occupe pas pour le moment d’effacer les
marques au fur et à mesure).
Mais comment passer de cet unique sprite à une infinité ? Il n’est
évidemment pas question de tous les redessiner un à un à chaque
déplacement ! L’astuce consiste à utiliser une technique de ≪ multi buffering ≫, c’est-à-dire utiliser plusieurs écrans virtuels comme l’illustre la
figure ci-dessous avec 3 écrans :
Écran 1
Écran 2
Écran 3
À l’étape suivante, on recommence à dessiner sur le premier écran :
Ainsi à chaque fois que l’on retrace un sprite sur l’écran 1, on donne
l’impression qu’un nouveau sprite apparait alors qu’en fait il s’agit de la
trace du sprite précédent !
E X F10 À vous de programmer cet effet magique en ajoutant les attributs
nécessaires et en mettant à jour la fonction update.
203
C HAPITRE F
4-
3D Starfield
Un autre effet à la mode à l’époque était
la réalisation de champs d’étoiles : une
collection de pixels illuminés et animés.
Capture de la démo Dark-Trip
Le menu de la borne a été imaginé pour
le club info que j’anime au lycée Thuillier
d’Amiens où j’enseigne. Nous utiliserons
donc ce logo, mais vous pouvez bien sûr
le changer !
Comme vous pouvez le voir sur la vidéo en ligne associée à ce chapitre,
nous allons représenter ce logo à l’aide d’un champ d’étoiles plaquées sur
B Les dimensions de
un cylindre. Nous le dessinerons dans le télécran .
celui-ci sont données sur l’image ci-après :
120
123
B
99 160
Notre premier travail consiste à ≪ enrouler ≫ le logo autour d’un cylindre. Pour cela l’ordonnée d’un pixel sera conservée comme la hauteur
de l’étoile et l’abscisse sera transformée en un angle par une transformation affine telle que 0 ↦ π et 160 ↦ 0 comme l’illustre la figure suivante.
204
F RONT- END
0
160
π
0
×
×
E X F11 Décrire une classe Etoile telle que :
1. Les objets contiendront 3 attributs : un entier hauteur, un flottant angle
et un triplet couleur indiquant la couleur du pixel au format (R,V,B). Le
constructeur sera appelé avec 3 paramètres (x,y,c) représentant respectivement l’abscisse, l’ordonnée et la couleur de l’étoile.
2. Les objets possèderont une méthode dessine(s) qui ”plaquera” l’étoile
sur la surface s donnée en paramètre (n’hésitez pas à aller voir l’aide pour
trouver cette nouvelle formule).
E X F12 Créer une nouvelle classe Champ qui contiendra dans ses attributs une
liste d’étoiles. Pour gérer la transparence, on ne va considérer comme étoile que les
pixels qui ne sont pas de la couleur du fond du télécran (176,161,128). Ajouter
enfin une méthode update qui affiche le résultat à l’écran.
Notre menu est à présent terminé, comme pour chaque projet, un certain nombre d’améliorations sont possibles :
● Lancer effectivement une commande en ligne (cela peut se faire à
l’aide de la fonction system du module os de Python).
● Accélérer la vitesse de la bande au fur et à mesure que les touches
de direction sont enfoncées.
● Écrire les informations du jeu dans le dernier écran de télévision non
utilisé, il faudra alors tourner l’image pour un meilleur rendu, vous
allez apprendre à faire cela dans le prochain chapitre...
205
C HAPITRE F
5 - Aide pour les exercices
Aide pour l’exercice F8 Pour le premier problème, comme on peut s’y attendre, il existe un lien entre la valeur de decale et le numéro du jeu
sélectionné. Au départ par exemple, lorsque decale vaut 0, c’est le jeu
numéro 1 qui est au centre de l’écran, jusqu’à ce que la valeur de decale
soit égale à 150. Vous pouvez trouver une formule liant ces deux quantités :
decale : 0
150
450
n° jeu :
1
2
max-150
750
|
3
...
max
|
1
Pour le second, on peut utiliser la
capture tv.png fournie sur le site
de même dimension que les images
des jeux (420 × 300). La position de
la capture est donnée ci-dessous.
606
546
206
300
420
490
300
C
225
503
RONT--END
END
FFRONT
Aide pour l’exercice F11 Pour
Pour la
la seconde
secondequestion,
question,on
onva
vaprojeter
projeterles
lespoints
points
(du cylindre) sur l’écran (axe)
(axe) ::
1
Pour
Pour
00⩽⩽aa⩽⩽π,
π,
−1
−1⩽⩽cos
cosaa⩽⩽1,
1,
−80
−80⩽⩽80
80cosa
cosa⩽⩽80,
80,
00⩽⩽80
80++80
80cosa
cosa⩽⩽160.
160.
a
0
cos
cosaa
11
6 - Solutions des exercices
exercices
Retour sur l’exercice F1 Pour
Pour afficher
afficher les
les jeux,
jeux, ilil suffit
suffit d’afficher
d’afficher les
les secsections du fichier :
import configparser
config = configparser.ConfigParser()
configparser.ConfigParser()
config.read('liste.ini')
config.read('liste.ini')
print(config.sections())
print(config.sections())
Pour afficher les noms des images,
images, on
on cherche
cherche la
la clé
clé souhaitée.
souhaitée.Attention
Attention
certains jeux n’ont pas d’image,
d’image, ilil faut
faut donc
donc gérer
gérer le
le problème.
problème.
import configparser
config = configparser.ConfigParser()
configparser.ConfigParser()
config.read('liste.ini')
config.read('liste.ini')
for jeu in config.sections()
config.sections() ::
print (config.get(jeu,
(config.get(jeu, 'image',
'image', fallback
fallback == 'impossible'))
'impossible'))
Retour sur l’exercice F2 Sur
Sur le
le même
même principe
principe que
que dans
dans l’exercice
l’exercice
précédent, mais on ajoute quelques
quelques lignes
lignes pour
pour tester
tester la
la présence
présence du
du fifichier avant de l’afficher :
import configparser
import os
for jeu in config.sections()
config.sections() ::
fichier = config.get(jeu,
config.get(jeu, 'image',
'image', fallback
fallback == 'impossible')
'impossible')
if not os.path.isfile('images/'+fichier)
os.path.isfile('images/'+fichier) ::
fichier = 'notfound.png'
'notfound.png'
print(fichier)
207
207
Solutions des exercices
−1
C HAPITRE F
Retour sur l’exercice F3 Il suffit d’appliquer deux fois la méthode blit :
def montage(origine) :
image = pygame.image.load(origine)
pellicule = pygame.image.load('pellicule.png')
finale = pygame.Surface((420,300))
finale.blit(image,(60,13))
finale.blit(pellicule,(0,0))
return finale
Retour sur l’exercice F4 On peut inclure la fonction montage dans la
classe puisqu’elle ne sera utilisée que dans celle-ci. Pour le reste, pas de
difficulté majeure : on charge le contenu du fichier INI, récupère le nom
du jeu et place dans la bande l’image montée sur la pellicule :
class Jeux :
def montage(origine) :
image = pygame.image.load(origine)
pellicule = pygame.image.load('pellicule.png')
finale = pygame.Surface((420,300))
finale.blit(image,(60,13))
finale.blit(pellicule,(0,0))
return finale
def __init__(self) :
self.config = configparser.ConfigParser()
self.config.read('liste.ini')
self.nbj = len(self.config.sections())
self.bande = pygame.Surface((420,300*(self.nbj)))
y = 0
for jeu in self.config.sections() :
fichier = self.config.get(jeu, 'image', fallback = 'impossible')
if not os.path.isfile('images/'+fichier) :
fichier = 'notfound.png'
img = Jeux.montage('images/'+fichier)
self.bande.blit(img,(0,y))
y = y + 300
Si vous voulez pouvoir vérifier le résultat, n’hésitez pas à ajouter
pygame.image.save(self.bande,'bande.png')
Retour sur l’exercice F5 Il faut en effet créer deux nouveaux attributs : un
premier decale et un second ombre qui contiendra l’image. Ensuite deux
simples blit suffisent pour afficher la portion de la bande en question et
l’effet souhaité. Merci David ! ! !
208
F RONT- END
F RONT- END
class Jeux :
Retour sur l’exercice F6 On peut s’inspirer du code de Cupidon pour gérer
les interactions
du clavier
: peut s’inspirer du code de Cupidon pour gérer
Retour
sur l’exercice
F6 On
class
Jeux :
les
interactions
du clavier :
...
class
Jeux
:
def
update(self)
:
...
touches = pygame.key.get_pressed()
def update(self)
:
if touches[K_UP]
:
touches
= pygame.key.get_pressed()
self.decale
= self.decale + 1
if touches[K_UP]
: :
touches[K_DOWN]
+ 1
self.decale = self.decale if touches[K_ESCAPE]
touches[K_DOWN] : :
self.decale
return
False= self.decale - 1
if touches[K_ESCAPE] :
self.dessine()
return
return
TrueFalse
self.dessine()
return
True
Il faut
ensuite
créer - enfin - la boucle principale.
Dans cette correction,
on peut
quitter
le menu
fermant
la fenêtre
ou en appuyant
sur
la touche
Il faut
ensuite
créer -en
enfin
- la boucle
principale.
Dans cette
correction,
échappe.
on peut quitter le menu en fermant la fenêtre ou en appuyant sur la touche
pygame.init()
échappe.
pygame.display.set_caption("SELECTIONNER VOTRE JEU")
pygame.init()
fenetre = pygame.display.set_mode((1600,900))
pygame.display.set_caption("SELECTIONNER
VOTRE JEU")
image = pygame.image.load('fond.png')
fenetre
= pygame.display.set_mode((1600,900))
fenetre.blit(image,(0,0))
image
pygame.image.load('fond.png')
jeux ==Jeux()
fenetre.blit(image,(0,0))
jeux = Jeux()
continuer
= True
clock = pygame.time.Clock()
continuer
= True
while
continuer:
clock
= pygame.time.Clock()
clock.tick(100)
while
continuer:
for
event in pygame.event.get():
clock.tick(100)
if event.type == QUIT :
for event
in pygame.event.get():
continuer
= False
if jeux.update()
event.type == :
QUIT :
if not
continuer
= False
continuer
= False
if not jeux.update() :
pygame.display.flip()
continuer = False
pygame.quit()
pygame.display.flip()
pygame.quit()
Retour sur l’exercice F7 Dans un premier temps, il faut modifier quelque
peu la déclaration
deF7
l’attribut
bande
en agrandissant
surface :quelque
Retour
sur l’exercice
Dans un
premier
temps, il fautlamodifier
209
peu la déclaration de l’attribut bande en agrandissant la surface :
209
Solutions des exercices
class
Jeux
:
def
__init__(self)
:
...
def __init__(self)
self.decale = 0:
...= pygame.image.load('ombre.png').convert_alpha()
self.ombre
self.decale
... = 0
self.ombre = pygame.image.load('ombre.png').convert_alpha()
...
def dessine(self)
:
fenetre.blit(self.bande,(1180,0),(0, self.decale,420,900))
def dessine(self)
:
fenetre.blit(self.ombre,(1180,0))
fenetre.blit(self.bande,(1180,0),(0, self.decale,420,900))
fenetre.blit(self.ombre,(1180,0))
C HAPITRE F
self.bande = pygame.Surface((420,300*self.nbj))
devient :
self.bande = pygame.Surface((420,300*(self.nbj+3)))
et on recopie les 3 premières images en fin de bande :
self.bande.blit(self.bande,(0,300*self.nbj),(0,0,420,900))
On peut alors modifier la méthode update en conséquence avec les
remarques précédentes :
def update(self) :
touches = pygame.key.get_pressed()
if touches[K_UP] :
self.decale = self.decale + 1
if self.decale == self.nbj * 300 :
self.decale = 0
if touches[K_DOWN] :
self.decale = self.decale - 1
if self.decale == -1 :
self.decale = self.nbj*300-1
if touches[K_ESCAPE] :
return False
self.dessine()
return True
Ce code fonctionne mais si vous possédez plus de 10 jeux, vous
allez rapidement vous rendre compte que la vitesse de défilement n’est pas
assez rapide. La solution suivante permet de régler une variable pas de
défilement. Il faut alors modifier un peu les conditions et les affectations :
s’il y a 10 jeux et que vous avancez de 7 en 7, peu de chance que decale
soit égale à 3000 à un moment ou un autre. En effet, le premier multiple
de 7 qui dépasse 3000 est 3003, dans ce cas, il faut remettre decale à 3 et
non à 0 :
def update(self) :
pas = 2
touches = pygame.key.get_pressed()
if touches[K_UP] :
self.decale = self.decale + pas
if self.decale >= self.nbj * 300 :
self.decale = self.decale - self.nbj * 300
if touches[K_DOWN] :
self.decale = self.decale - pas
if self.decale < 0 :
self.decale = self.decale + self.nbj*300
if touches[K_ESCAPE] :
return False
self.dessine()
return True
210
Retour sur l’exercice F8 Comme le numéro du jeu nj change à chaque
fois que decale augmente de 300, nous allons effectuer une division par
300. Par exemple nj vaut 2 lorsque 150 ⩽ decale < 450. Si on réalise la division par 300, on va obtenir 2 valeurs possibles (0 ou 1). Il faut donc augmenter decale de 150. Avec la formule nj = (decale + 150)//300,
nj vaut 1 tout l’intervalle considéré. Il faut encore incrémenter la valeur
de nj de 1 ou, ce qui revient au même, augmenter decale de 300 : nj
= (decale + 450)//300. Enfin, il faut gérer les dépassements lorsque
nj dépasse le nombre maximum de jeux. On peut faire cela avec un test
ou en calculant un reste, comme cela a déjà été fait plusieurs fois dans le
livre. On ajoute alors ces quatre lignes dans la méthode update de notre
classe :
if touches[K_SPACE] :
nj = ((self.decale+450) // 300) % self.nbj
print(self.config.sections()[nj])
return False
Enfin, pour afficher le jeu sélectionné dans le téléviseur, on ajoute un
attribut tv qui contiendra l’image du téléviseur et qui sera chargé lors
de l’instanciation de l’objet, puis on met à jour la méthode update en
affichant l’image puis la TV aux coordonnées indiquées dans l’aide.
def update(self) :
pas = 2
touches = pygame.key.get_pressed()
if touches[K_UP] :
self.decale = self.decale + pas
if self.decale >= self.nbj * 300 :
self.decale = self.decale - self.nbj * 300
if touches[K_DOWN] :
self.decale = self.decale - pas
if self.decale < 0 :
self.decale = self.decale + self.nbj*300
if touches[K_ESCAPE] :
return False
nj = ((self.decale+450) // 300) % self.nbj
fichier = self.config.get(self.config.sections()[nj], 'image', ⤦
� fallback = 'impossible')
if not os.path.isfile('images/'+fichier) :
fichier = 'notfound.png'
img = pygame.image.load('images/'+fichier)
fenetre.blit(img,(606,503))
fenetre.blit(self.tv,(546,490))
if touches[K_SPACE] :
print (self.config.sections()[nj])
return False
self.dessine()
return True
211
Solutions des exercices
F RONT- END
C HAPITRE F
Retour sur l’exercice F9 Il n’y a pas de difficulté particulière puisque
l’équation de la courbe a été donnée :
class USprites() :
def __init__(self) :
self.sprite = pygame.image.load('sprite.png').convert_alpha()
self.t = 0
def update(self) :
x = 687 + 133 + 133*sin(8 * self.t)
y = 149 + 99 + 99*cos(5 * self.t)
self.t = self.t + 0.01
fenetre.blit(self.sprite,(x,y))
Il faut néanmoins penser :
● à importer les fonctions sinus et cosinus :
from math import cos, sin
● déclarer une instance de la classe avant la boucle principale :
unlimited = USprites()
● appeler la mise à jour dans la boucle principale :
unlimited.update()
Retour sur l’exercice F10 Après quelques essais, je vous propose d’utiliser
5 écrans pour un meilleur rendu. Commençons par ajouter deux attributs :
● ecrans est une liste de 5 surfaces de taille 286 × 218.
● numero indique le numéro de l’écran sur lequel on est en train de
dessiner.
def __init__(self) :
self.sprite = pygame.image.load('sprite.png').convert_alpha()
self.t = 0
self.ecrans = [pygame.Surface((286,218)) for i in range(5)]
self.numero = 0
Il faut ensuite adapter la fonction update, on conserve le même principe que dans l’exercice précédent, mais au lieu de dessiner directement
sur la fenêtre, on dessine sur l’écran virtuel, puis on recopie l’intégralité
de cet écran sur la fenêtre,
212
F RONT- END
def update(self) :
x = 133 + 133*sin(8 * self.t)
y = 99 + 99*cos(5 * self.t)
self.t = self.t + 0.002
self.ecrans[self.numero].blit(self.sprite,(x,y))
fenetre.blit(self.ecrans[self.numero],(687,149))
self.numero = (self.numero + 1)%5
Retour sur l’exercice F11
1. Déterminons une formule qui permet de
passer de l’abscisse x à l’angle a. L’énoncé nous dit que la fonction est affine donc on a a(x) = mx + p. Comme a(0) = π, on trouve
immédiatement que p = π. D’autre part a(160) = 160m + π = 0 donc
π
π
(160 − x)π
m=−
. Finalement a(x) = −
x+π =
160
160
160
class Etoile :
def __init__(self, x, y, c) :
self.hauteur = y
self.angle = (160-x)*pi/160
self.couleur = c
2. A l’aide de la formule expliquée dans l’aide, on peut écrire la méthode
dessine sans difficulté :
def dessine(self, surf) :
x = int(80 + 80*cos(self.angle))
surf.set_at((x, self.hauteur), self.couleur)
Retour sur l’exercice F12 En suivant les instructions, on obtient le code :
class Champ :
def __init__(self) :
image = pygame.image.load('thuillier.png')
self.ciel = []
for x in range(160) :
for y in range(120) :
c = image.get_at((x,y))
if c != (176,161,128) :
self.ciel.append(Etoile(x, y, c))
def update(self) :
fond = pygame.Surface((160,120))
fond.fill((176,161,128))
for e in self.ciel :
e.dessine(fond)
e.angle = e.angle + 0.01
fenetre.blit(fond,(99,123))
213
Solutions des exercices
C HAPITRE F
Si on veut éviter de modifier l’attribut angle d’une étoile depuis
l’extérieur de la classe, comme cela a déjà été conseillé, on peut faire le
choix de modifier à l’intérieur de la méthode dessine :
class Etoile :
def __init__(self, x, y, c) :
self.hauteur = y
self.angle = (160-x)*pi/160
self.couleur = c
def dessine(self, surf) :
x = int(80+80*cos(self.angle))
surf.set_at((x, self.hauteur), self.couleur)
self.angle = self.angle + 0.01
class Champ :
def __init__(self) :
image = pygame.image.load('thuillier.png')
self.ciel = []
for x in range(160) :
for y in range(120) :
c = image.get_at((x,y))
if c != (176,161,128) :
self.ciel.append(Etoile(x, y, c))
def update(self) :
fond = pygame.Surface((160,120))
fond.fill((176,161,128))
for e in self.ciel :
e.dessine(fond)
fenetre.blit(fond,(99,123))
214
Objectif
Programmer un PAC-MAN
Programmation
Lecture d’un fichier CSV
Traitement d’image
Héritage et surcharge en POO
Informatique
Table de hashage.
Chapitre G
EHPAD-RUN
EHPAD-RUN est un jeu directement
inspiré du très célèbre jeu vidéo PacMan créé par Toru Iwatani pour la
société Namco. Sa sortie au Japon
le 22 mai 1980 marque un point de
repère dans l’histoire du jeu vidéo.
Ce jeu a généré plus de 2,5 milliards
de dollars au cours des années 90,
une des meilleures recettes pour un
jeu vidéo.
Par Namco (vectorisation vectorlib.free.fr) - Arcade Vector graphics
Le jeu d’origine consiste à déplacer Pac-Man à l’intérieur d’un labyrinthe, afin de lui faire manger toutes les pac-gommes qui s’y trouvent, en
évitant d’être touché par des fantômes. Dans cette version revisitée, vous
incarnez un dentier qui tente de ramasser le plus de colle dentaire tout en
étant poursuivi par des grabataires aux canines défaillantes.
215
C HAPITRE G
1 - Manipulation d’images
Jusque-là, nous avons manipulé des images fournies sur le site du livre
et réalisées par le talentueux David BEAUGET. Nous allons apprendre à
en créer par nous-mêmes.
Transformations d’images
Lorsque l’on dispose d’un objet de type Surface, nous avons déjà
présenté comment récupérer ou modifier la couleur d’un pixel via les fonctions get at et set at. Voici quelques méthodes supplémentaires :
Méthode
surf .get size()
Effet
Retourne la dimension de l’image en
pixels sous la forme d’un couple (largeur, hauteur).
surf .get width()
Retourne la largeur de l’image.
surf .get height()
Retourne la hauteur de l’image.
surf .get bytesize()
Renvoie le nombre d’octets utilisés
pour coder un pixel de l’image. Cela
permet donc d’avoir des informations
sur l’image(*) .
surf .subsurface(r)
Retourne une copie de la surface surf
délimitée par le rectangle r qui peut être
donnée par un quadruplet (x,y,l,h).
✍ (*) : 1 octet : Mode 256 couleurs (par exemple c’est le cas des images
GIF, qui utilisent une palette de 256 couleurs) - 3 octets : Mode RVB
(16 millions de couleurs) et 4 : Mode RVBA (16 millions de couleurs avec transparence). Attention, l’appel à cette fonction doit se
faire avant l’appel de la méthode convert qui convertit justement
l’image au format de la fenêtre de jeu.
216
EHPAD-RUN
E X G1 Écrire un programme qui charge une image et affiche différentes informa-
tions :
● Son poids (en octets).
● Sa taille (en pixels).
● Le mode de couleurs (256 couleurs ou 16 millions).
● S’il y a des pixels transparents.
● S’il n’y a pas transparence, le nombre de couleurs différentes utilisées.
En tapant le code de l’exercice sur le site, vous pourrez télécharger des fichiers de
tests, voici ce que vous devriez trouver :
barbe.png
Taille : 144224 octets
Résolution : 400 x 335 pixels
16 millions de couleurs
Image avec transparence.
danseurs.png
Taille : 177519 octets
Résolution : 640 x 354 pixels
256 couleurs
Image sans transparence
254 couleurs utilisées.
danseurs.jpg
Taille : 81139 octets
Résolution : 640 x 354 pixels
16 millions de couleurs
Image sans transparence
3686 couleurs utilisées.
oiseau.gif
Taille : 14949 octets
Résolution : 157 x 216 pixels
256 couleurs
Image sans transparence
255 couleurs utilisées.
Intéressons nous à présent à quelques transformations géométriques
simples sur ces images :
E X G2
Écrire des fonctions qui reçoivent une surface
RVB en argument et qui la transforment et renvoient une nouvelle surface en réalisant...
1. ... une symétrie d’axe vertical
217
C HAPITRE G
2. ... une symétrie d’axe horizontal
3.
... une rotation de 90○
dans le sens horaire.
Comme vous l’aurez peut-être pressenti, ces transformations de base
ont déjà été implémentées dans Pygame. Ces fonctions ont été regroupées dans le module transform :
● pygame.transform.flip(s,v,h) : renvoie l’image de la surface
s obtenue par symétrie d’axe vertical si v vaut True et horizontal si
h vaut True. Si h et v valent toutes les deux True, les deux transformations sont appliquées créant ainsi une symétrie centrale par
rapport au centre de l’image.
● pygame.transform.rotate(s,a) : renvoie une surface contenant
l’image de la surface s après rotation d’angle a (en degrés) dans le
sens trigonométrique (anti-horaire). Attention les dimensions de la
nouvelle image après rotation seront modifiées, comme l’illustre la
figure ci-dessous.
Illustration par Dmitry Abramov - avec une rotation de 30°
218
EHPAD-RUN
Ainsi, dans l’exercice précédent, on aurait obtenu le même résultat en
saisissant :
def vertical(img): return pygame.transform.flip(img, True, False)
def horizontal(img): return pygame.transform.flip(img, False, True)
def rotation(img) : return pygame.transform.rotate(img, -90)
E X G3 On peut fabriquer le négatif d’une image en transformant chaque pixel
en son complément au blanc. Par exemple un pixel orange dont le code couleur
RVB est (229,123,28) sera transformé en bleu turquoise (26,132,227) obtenu
par le calcul suivant : (255−229,255−123,255−28). Écrire un programme qui
affiche le négatif d’une image en mode RVB.
E X G4 On cherche à présent à redimensionner une image. L’algorithme le plus
naı̈f consiste à regarder d’où provient chaque point de l’image finale. Un simple
produit en croix permet de trouver les relations :
sx × sy
newX × newY
Avant
Après
Si on note (x,y) les coordonnées d’un pixel sur l’image d’origine et (x′ ,y ′ )
les coordonnées du pixel correspondant sur l’image redimensionnée, il nous faut
donc x et y en fonction de x′ et y ′ .
Abscisse avant
Abscisse après
0
0
sx -1
newX -1
x
x′
Ordonnée avant
Ordonnée après
0
0
sy -1
newY -1
y
y′
y ′ × (sy − 1)
x′ × (sx − 1)
et y =
,
newX − 1
newY − 1
qu’il faudra arrondir à l’entier le plus proche.
Ainsi, on obtient : x =
Écrire une fonction zoom qui reçoit comme paramètres une surface et ses nouvelles dimensions et renvoie l’image redimensionnée.
Sur un exemple avec le fichier sphere.png de dimensions 2000 ×
2000, disponible sur le site, on obtient cette image en la redimensionnant
en 100 × 100 (les images ne sont pas à l’échelle pour mieux voir et comprendre) :
219
C HAPITRE G
C HAPITRE G
La nouvelle image est crénelée, on
La nouvelle
image d’aliasing,
est crénelée,
on
parle
de problème
ce qui
parle
de
problème
d’aliasing,
ce
qui
peut s’expliquer par le schéma ci-contre.
peut s’expliquer
par
le schéma
ci-contre.
Pour
passer d’une
image
de 2000
pixels
Pour
passer
d’une
image
de
2000
pixels
de large à 100, on a découpé l’image
de
large
à
100,
on
a
découpé
l’image
de départ sur sa largeur en 100 carrés
sur(ici
sa largeur
100 carrés
de départ
20 pixels
5 pour en
l’explication).
de
20
pixels
(ici
5
pour
l’explication).
Au lieu de remplacer chaque carré par
Au pixel
lieu dederemplacer
chaque
carré
par
un
la couleur
du coin
hautun
pixel
de
la
couleur
du
coin
hautgauche de celui-ci, il serait préférable de
gauche
celui-ci, de
il serait
faire la de
moyenne
tous préférable
les pixels de
(y
faire la moyenne
de tous les si
pixels
(y
compris
pour la transparence
elle est
compris
pour
la
transparence
si
elle
est
présente).
présente).
Ainsi, avec le premier algorithme, les 5 cases de la première ligne deAinsi, avec
le premier
les 5 cases
de la première
ligne deviendraient
blanches,
alorsalgorithme,
qu’avec la nouvelle
méthode
on obtiendrait
viendraient blanches, alors qu’avec la nouvelle méthode on obtiendrait
E X G5 Écrire une nouvelle fonction zoom1 qui reçoit une image de taille 2000×
E X G5
Écrire
une fnouvelle
fonction
zoom1 qui
reçoit une
de taille
2000×
2000
et un
facteur
qui divise
ses dimensions
et renvoie
uneimage
surface
représentant
2000 et un
facteurdont
f quiles
divise
ses dimensions
unepar
surface
l’image
d’origine
dimensions
auront etétérenvoie
divisées
f enreprésentant
utilisant la
l’image d’origine
dont les
dimensions auront été divisées par f en utilisant la
méthode
d’anti-aliasing
présentée.
méthode d’anti-aliasing présentée.
220
220
EHPAD-RUN
EHPAD-RUN
Il existe de
de nombreux
nombreux algoalgorithmes pour
pour modifier
modifier la
la taille
taille
d’une image,
image, comme
comme l’illustre
l’illustre lala
liste des choix
choix de
de filtres
filtres dans
dans
Gimp :
Un certain
certain nombre
nombre de
de filtres
filtressont
sontdéjà
déjàimplémentés
implémentésdans
danslelemomodule PIL (Python
(PythonImaging
ImagingLibrary)
Library)qui
quiest
estun
unmodule
modulespécialisé
spécialisédans
dans
le traitement
traitement d’images
d’images et
et dont
dontvous
vouspouvez
pouvezdécouvrir
découvrirl’étendue
l’étenduedes
des
possibilités sur
sur le
le site
sitehttps
https://pillow.readthedocs.io/
://pillow.readthedocs.io/sisilelesujet
sujetvous
vous
intéresse.
Les algorithmes
algorithmes ayant
ayant les
lesmeilleurs
meilleursrendus
rendussont
sontsouvent
souventles
lesplus
plus
gourmands
gourmands en
en temps
temps de
de calcul.
calcul.Pour
Pourcela
celaililexiste
existedeux
deuxmanières
manièresdede
réaliser un zoom
zoom avec
avec Pygame
Pygame : :
● pygame.transform.scale(s,(l,h))
pygame.transform.scale(s,(l,h)): :renvoie
renvoieune
unesurface
surfacecorcorrespondant
respondant àà l’image
l’image de
de lalasurface
surfacesszoomée
zoomée(ou
(ourétrécie)
rétrécie)aux
aux
dimensions
dimensions ll ××h.
h.
● pygame.transform.smoothscale(s,(l,h))
pygame.transform.smoothscale(s,(l,h)): :même
mêmeeffet,
effet,
mais avec
avec une
une meilleure
meilleurequalité
qualitéde
detransformation
transformation(mais
(maisaussi
aussi
plus lente).
lente).
Ainsi, au cours
cours de
de vos
vos jeux,
jeux,vous
vousdevrez
devrezsouvent
souventfaire
faireun
unchoix
choixentre
entre
la qualité de
de transformation
transformation ou
oulalavitesse
vitessede
detransformation
transformation: :sisiune
une
image peut être
être pré-calculée
pré-calculéeavant
avantleledéroulement
déroulementdu
dujeu,
jeu,autant
autantmimiser sur la qualité.
qualité. Si
Si par
par contre
contre lalatransformation
transformationaalieu
lieuau
aucours
coursdu
du
jeu, il est préférable
préférable de
demiser
misersur
surlalavitesse
vitessede
detransformation.
transformation.ÀÀnoter
noter
que contrairement
contrairement au
au module
modulePIL,
PIL,ililn’est
n’estactuellement
actuellementpas
paspossible
possible
de préciser la
la qualité
qualité de
de la
latransformation
transformationpour
pourune
unerotation.
rotation.
Finalement,
Finalement, les
les deux
deux exercices
exercicesprécédents
précédentspeuvent
peuventêtre
êtrecodés
codésdede
manière bien
bien plus
plus simple
simple par
par::
def zoom(img,
zoom(img, newX,
newX, newY)
newY) ::
return
return pygame.transform.scale(img,
pygame.transform.scale(img, (newX,
(newX, newY))
newY))
def zoom1(img,
zoom1(img, f)
f) ::
sx, sy
sy == img.get_size()
img.get_size()
return
return pygame.transform.smoothscale(img,
pygame.transform.smoothscale(img, (sx
(sx//
//f,
f,sy
sy////f))
f))
221
221
C HAPITRE G
Dessiner des images
Nous avons déjà utilisé à de nombreuses reprises la fonction blit qui
permet de réaliser des copier-coller d’images.
E X G6 À partir de l’image ci-dessous de fleur de lys de dimensions 250 × 150,
réaliser une image de taille 600 × 400 représentant le drapeau officiel du Québec,
aussi appelé drapeau fleur de lys.
�⇒
E X G7 Réaliser un programme qui charge une image, la découpe en 100 morceaux (10 × 10) et la mélange tel un puzzle (pour jouer au jeu du taquin par
exemple). On pourra se limiter à des images dont les dimensions sont des multiples de 10 pour simplifier le problème.
E X G8 Un artiste a réalisé sur feuille le croquis d’un tatouage parfaitement symétrique. Malheureusement, un
accident de parcours a dégradé l’œuvre de deux taches.
Saurez-vous reconstituer le dessin d’origine ?
Plus efficacement que de dessiner point par point, on peut, avec le module draw de Pygame, dessiner des formes. Dans la suite des explications,
les noms des fonctions doivent être précédés de pygame.draw. .
222
×
✍
✍
✍
✍
Dessine un segment reliant le point
deb = (x1 , y1 )
×
de
coordonnées
deb au
pointlede
coDessine
un
segment
reliant
point
Une ligne : line(s,c,deb,f in,e) :
ordonnées
f in avec
c. On
de coordonnées
deb la
aucouleur
point de
codeb = (x1 , y1 )
×
EHPAD-RUN
peut
préciser
l’épaisseur
e
en
pixels
(1
ordonnées
f
in
avec
la
couleur
c.
On
Dessine un segment reliant le point
par
défaut).
peut
préciser l’épaisseur
en pixels
(1
de
coordonnées
deb au epoint
de co×
f in = (x2 , y2 )
par
défaut).
ordonnées f in avec la couleur c. On
×
f in = (x2 , y2 )
Une ligne : line(s,c,deb,f in,e) :
peut préciser l’épaisseur e en pixels (1
deb = (x1 , y1 )
×
par défaut).
Dessine
un segment reliant le point
×
f in = (x2 , y2 )
de coordonnées deb au point de coordonnées f in avec la couleur c. On
peut préciser l’épaisseur e en pixels (1
par défaut).
×
in = (x2à, y2bout
)
Il est aussi possible de dessiner plusieurs lignes f bout
sous
forme
de lignes
brisées
la fonction
lines
et bout
même
Il est aussi
possible
de avec
dessiner
plusieurs
lignes
à des
boutlignes
sous
plus
avec anti-aliasing
nouslines
l’avons
évoqué
pour
formejolies
de lignes
brisées avec comme
la fonction
et déjà
même
des lignes
le
zoom.
N’hésitez
pas
à aller
vous
documenter
sur les
fonctions
plus
jolies
avec
anti-aliasing
comme
nous
l’avons
évoqué
pour
Il
est
aussi
possible
de dessiner
plusieurs
lignes déjà
bout
à bout
sous
aaline
aalines
queavec
détaillerons
pasetici.
le zoom.
pas
ànous
aller
vous
documenter
sur
lesdes
fonctions
forme
deetN’hésitez
lignes
brisées
lane
fonction
lines
même
lignes
aaline
et avec
aalines
que nous
ne détaillerons
pas ici.
plus jolies
anti-aliasing
comme
nous l’avons
déjà évoqué pour
le zoom. N’hésitez pas à aller vous documenter sur les fonctions
aaline
et aalines
quedessiner
nous ne plusieurs
détaillerons
pas ici.
Il est aussi
possible de
lignes
bout à bout sous
forme de lignes brisées avec la fonction lines et même des lignes
plus jolies avec anti-aliasing comme nous l’avons déjà évoqué pour
Un rectangle : rect(s,c,r,e)
le zoom. N’hésitez pas à aller vous documenter sur les fonctions
la surface s un rectangle délimité par le rectangle r
Un Dessine
rectanglesur
: rect(s,c,r,e)
aaline et aalines que nous ne détaillerons pas ici.
avecDessine
la couleur
c.
l’épaisseur
est précisée
et strictement
positive,r
sur laSisurface
s un erectangle
délimité
par le rectangle
on
un: rectangle
vide dont
contoureta strictement
une épaisseur
de e.
avec
la couleur
c. Si l’épaisseur
e estleprécisée
positive,
Un obtient
rectangle
rect(s,c,r,e)
Dans
les
autres
cas,
le
rectangle
est
plein.
Pensez
que
pour
dessiner
on obtient
un
rectangle
vide
dont
le
contour
a
une
épaisseur
de e.r
Dessine sur la surface s un rectangle délimité par le rectangle
un
rectangle
plein,
sur une
surface
la taille
souDans
autres
cas,
leméthode
rectanglefill
Pensez
quedepour
dessiner
avec
lalescouleur
c.
Silal’épaisseur
eest
estplein.
précisée
et strictement
positive,
haitée
est bien
plus la
efficace
! dont
un rectangle
méthode
filllesur
une surface
la taille de
souon
obtient
unplein,
rectangle
vide
contour
a une de
épaisseur
e.
haitée
est
bien
plus
efficace
!
Dans
les autres
cas, le rectangle est plein. Pensez que pour dessiner
Un
rectangle
: rect(s,c,r,e)
un rectangle
plein,
la méthode
sur une
surface
taille sou-r
Dessine sur la surface
s unfill
rectangle
délimité
pardelelarectangle
haitéelaest
bien plus
!
avec
couleur
c. Siefficace
l’épaisseur
e est précisée et strictement positive,
r
on obtient un rectangle vide dont le contour a une épaisseur de e.
r
Dans
autres
cas, le rectangle est plein. Pensez que pour dessiner
Une les
ellipse
: ellipse(s,c,r,e)
un
rectangle
plein,
la méthode
fill
surdesune surface de la taille souAvec
les mêmes
paramètres
que
rect,
Une
ellipse
: ellipse(s,c,r,e)
r
haitée
est
bien
plus
efficace
! que
sine une
ellipse
inscrite
dans
unrect,
rectangle
Avec
les mêmes
paramètres
des(imaginaire)
sine une
ellipse
inscrite dans un rectangle
Une
ellipse
:r.ellipse(s,c,r,e)
(imaginaire)
r. paramètres que rect, desAvec
les mêmes
sine une ellipse inscrite dans un rectangle
r
(imaginaire) r.
223
Une ellipse : ellipse(s,c,r,e)
223
Avec les mêmes paramètres que rect, dessine une ellipse inscrite dans un rectangle
223
(imaginaire) r.
223
C HAPITRE G
C HAPITRE
Un arc G
d’ellipse : arc(s,c,r,ad,af ,e)
C HAPITRE G
Avec les mêmes paramètres que ellipse,
Un
arc d’ellipse
: arc(s,c,r,ad,af
dessine
un arc d’ellipse
délimité ,e)
par l’angle
Un
arc d’ellipse
: arc(s,c,r,ad,af
,e)
de
départ
ad
et
l’angle
de
fin
af
. ContraiAvec
mêmes: arc(s,c,r,ad,af
paramètres que,e)
ellipse,
Un
arc les
d’ellipse
Avec
les
mêmes
paramètres
que
ellipse,
rement
rectangles
ellipses, par
les arcs
ne
dessine aux
un arc
d’ellipseetdélimité
l’angle
dessine
arc d’ellipse
délimité
l’angle
Avec
lesun
mêmes
paramètres
que par
ellipse,
peuvent
être
de
départ
adpleins.
et l’angle de fin af . Contraide départ
l’angle délimité
de fin afpar
. Contraidessine
un ad
arcetd’ellipse
l’angle
rement
auxetrectangles
et ellipses, les arcs ne
Ici
ad
=
10
af
=
130
rement
auxad
rectangles
ellipses,
arcs ne
de
départ
et l’angleet de
fin af les
. Contraipeuvent être pleins.
peuventaux
êtrerectangles
pleins. et ellipses, les arcs ne
rement
Ici ad = 10
et af
= 130
peuvent
être
pleins.
Ici ad = 10
et af
= 130
Ici ad = 10 et af = 130
On peut dessiner un polygone :
r
r
r
r
On peut dessiner un polygone :
On peut dessiner un polygone :
Un polygone : polygon(s, c, pts, fill= 0)
On peut dessiner un polygone :
Dessine sur la surface s un polygone dont les sommets sont donnés
par polygone
la liste pts: polygon(s,
= [(x1 ,y1 ),(x2,y
Un
c,2 ),...,(x
pts, fill=
0) de la couleur c. Le
n ,yn )] et
Un
polygone
: polygon(s,
c,
pts,
fill=
0)
dernier
paramètre
fill svaut
0 par
défaut,
polyDessine
sur la surface
un polygone
dont c’est-à-dire
les sommetsque
sontledonnés
Dessine
sur
la
surface
s
un
polygone
dont
les
sommets
sont
donnés
Un
polygone
:
polygon(s,
c,
pts,
fill=
0)
gonelasera
Si c’est
un entier
strictement
il représente
par
listerempli.
pts = [(x
,y1 ),(x2,y
de la couleur
c. Le
n ,yn )] et positif,
2 ),...,(x
par
la liste de
ptslala
=ligne
[(x11 ,y
),(x2,y
),...,(x
,yvide.
etsommets
de la couleur
c. Le
Dessine
sur
surface
s un
polygone
sont donnés
ndont
n )]les
l’épaisseur
et1le
polygone
sera
dernier paramètre
fill
vaut
0 2par défaut,
c’est-à-dire que le polydernier
paramètre
fill
vaut
0
par
défaut,
c’est-à-dire
que
le
poly,y
),(x2,y
),...,(x
,y
)]
et
de
la
couleur
c. Le
par
la liste
pts = [(x
n n
1 un entier
2
gone sera rempli. Si 1c’est
strictement
positif, il représente
gone
sera
rempli.
Si
c’est
un
entier
strictement
positif,
il
représente
dernier paramètre fill vaut 0 par défaut, c’est-à-dire que le polyl’épaisseur de la ligne et le polygone sera vide.
l’épaisseur
de la ligne
et leun
polygone
sera vide. positif, il représente
gone
sera rempli.
Si c’est
entier strictement
l’épaisseur de la ligne et le polygone sera vide.
E X G9 Dessiner le drapeau de la Jamaı̈que, sachant qu’il est deux fois plus large
que haut et que, comme sur le schéma, les points marqués d’une bille se trouvent
àEune
distance
d’un
dixième de la longueur
segment
à partir
d’une
X G9
Dessiner
le drapeau
Jamaı̈que,du
sachant
qu’il
est deux
foisextrémité.
plus large
E X G9 Dessiner le drapeau de la Jamaı̈que, sachant qu’il est deux fois plus large
que haut et que, comme sur le schéma, les points marqués d’une bille se trouvent
que
haut
et que, comme
sur ledeschéma,
les points
marqués
d’une
bille
trouvent
EX G
9 Dessiner
le drapeau
la Jamaı̈que,
sachant
qu’il est
deux
foisseplus
large
à une distance d’un dixième de la longueur du segment à partir d’une extrémité.
à une
distance
dixième
deschéma,
la longueur
du segment
à partir
extrémité.
que
haut
et que,d’un
comme
sur le
les points
marqués
d’uned’une
bille se
trouvent
à une distance d’un dixième de la longueur du segment à partir d’une extrémité.
224
224
224
224
EHPAD-RUN
2 - L’interface du jeu
E X G10 On cherche à dessiner un tuyau, comme sur la capture ci-dessous.
Pour cela, je vous suggère de vous inspirer de la méthode proposée par le logiciel GIMP : on se donne une liste de couleurs et les emplacements de celles-ci
(triangles noirs). Entre deux triangles, les couleurs évoluent de manière linéaire
(si on ne tient pas compte des triangles blancs) :
225
C HAPITRE G
Dans notre cas, on souhaiterait obtenir cette
image.
1280+noir = [0, 0, 0]
880+noir = [0, 0, 0]
760+blanc = [255, 255, 255]
740+gris = [190, 190, 190]
640+jaune = [255, 255, 100]
540+gris = [190, 190, 190]
520+blanc = [255, 255, 255]
400+noir = [0, 0, 0]
image tuyau.png - 1280 × 1280
0+noir = [0, 0, 0]
1. Pour le moment, on ne gère qu’une couche (par exemple la bleue). Écrire une
fonction affine(couleurs, hauteurs, h) qui reçoit une liste d’entiers représentant la liste des couleurs, une seconde représentant la liste
des hauteurs et un entier indiquant la hauteur souhaitée et renvoie la couleur à afficher à cette hauteur. Pour tester, vous pouvez vous appuyer sur
l’exemple :
>>>
>>>
>>>
0
>>>
128
>>>
136
>>>
100
C = [0, 0, 255, 190, 100, 190, 255, 0, 0]
H = [0, 400, 520, 540, 640, 740, 760, 880, 1280]
affine(C, H, 200)
affine(C, H, 460)
affine(C, H, 600)
affine(C, H, 640)
2. Modifier la fonction précédente pour obtenir une fonction identique mais
dont le premier paramètre est une liste de couleurs (les éléments sont des
triplets (R,V,B) ) et la sortie un triplet (R,V,B).
3. En utilisant la fonction précédente, écrire un programme qui génère l’image
tuyau.png souhaitée.
226
EHPAD-RUN
On cherche à partir de l’image tuyau.png de taille 1280×1280 pixels,
à générer une série de 11 images de taille 32×32 pour créer le décor comme
ci-dessous :
horiz.png
vert.png
croix.png
1.png
5.png
2.png
6.png
3.png
7.png
4.png
8.png
E X G11 À l’aide de ce que vous avez appris dans ce chapitre, créer les images
horiz.png et vert.png de taille 32 × 32.
E X G12 On cherche à fusionner les images vert.png et horiz.png pour
obtenir l’image croix.png ci-contre.
1. Donner l’équation des droites
(D1 ) et (D2 ).
vert
2. En déduire les conditions
nécessaires et suffisantes pour
qu’un point de coordonnées (x,y)
de l’image croix.jpg provienne de l’image horiz.jpg.
3. Justifier que la précédente condition peut s’écrire
horiz
horiz
vert
D2
D1
(x − y)(31 − x − y) < 0
4. Écrire un programme générant l’image croix.png.
E X G13 S’inspirer du programme précédent pour générer l’image 5.png, puis
les images 6.png, 7.png et 8.png.
E X G14 Plus difficile, on souhaite à présent ”tordre” l’image tuyau.png pour
obtenir le quart de cercle 1.png. On travaille cette fois-ci avec des images en
résolution 1280 × 1280 afin de garantir une meilleure qualité.
1. En remarquant que le point A(x,y) de la figure 1.png est de la couleur
des points de la ligne d = OA de la figure tuyau.png, imaginer un programme qui crée le fichier 1.png qu’il faudra ensuite redimensionner en
image 32 × 32 :
227
C HAPITRE G
C HAPITRE G
O
O
d
d
d
d
A
A
tuyau.png
tuyau.png
1.png
1.png
2. Créer enfin les images 2.png, 3.png, et 4.png.
2. Créer enfin les images 2.png, 3.png, et 4.png.
À présent que nous disposons des images pour dessiner le décor du
jeu,Ànous
allons
pouvoir
le dessiner.
est dedessiner
générer leundécor
décordu
à
présent
que
nous disposons
desL’objectif
images pour
partir
d’unallons
fichierpouvoir
CSV. le dessiner. L’objectif est de générer un décor à
jeu, nous
partir d’un fichier CSV.
�⇒
�⇒
Capture d’écran réalisée lors d’un
atelier aux
journées
nationales
de
Capture
d’écran
réalisée
lors d’un
l’APMEP
en 2015
à Laon. de
atelier
aux journées
nationales
l’APMEP en 2015 à Laon.
Pour un fichier d’import/export de feuilles de calculs d’un tableur, le
format
(Comma
Separated Values
: Valeurs
Séparées
partableur,
des VirPourCSV
un fichier
d’import/export
de feuilles
de calculs
d’un
le
gules) est
le plus
commun.
C’est un
simple
fichier Séparées
texte (la mise
en page
format
CSV
(Comma
Separated
Values
: Valeurs
par des
Virn’est
où les contenus
colonnes
sont
séparés
par en
despage
virgules)pas
estprésente)
le plus commun.
C’est undes
simple
fichier
texte
(la mise
gules.
Onprésente)
peut donc
lirecontenus
très facilement
comme
un séparés
simple fichier
n’est pas
oùleles
des colonnes
sont
par destexte
viravec
Cependant,
tous
logiciels ne
fonctionnant
pas fichier
de la même
gules.Python.
On peut
donc le lire
trèslesfacilement
comme
un simple
texte
manière
(choix
du séparateur
delogiciels
colonne,nechoix
du séparateur
pour
un
avec Python.
Cependant,
tous les
fonctionnant
pas de la
même
nombre
le module de
csvcolonne,
permet choix
de simplifier
les accès
à ces
manière décimal...)
(choix du séparateur
du séparateur
pour
un
fichiers,
à
l’instar
des
fichiers
INI
présentés
au
chapitre
précédent.
nombre décimal...) le module csv permet de simplifier les accès à ces
fichiers, à l’instar des fichiers INI présentés au chapitre précédent.
228
228
EHPAD-RUN
csv.reader(f , options) : renvoie un objet reader dont le contenu
est celui du fichier f. On peut alors itérer sur cet objet pour parcourir le fichier, chaque ligne étant alors une liste de chaı̂nes de caractères. En option, on peut préciser le délimiteur de colonnes, par
exemple delimiter=’;’. Voici un exemple simple qui lit et affiche
le contenu du fichier niveau1.csv :
import csv
with open('niveau1.csv') as f:
contenu = csv.reader(f, delimiter = ';')
for ligne in contenu:
print(ligne)
On obtient alors des lignes ressemblant à :
['x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', ...
['x', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ...
['x', '', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', '', 'x', 'x', ...
...
✍ Ici, nous n’utiliserons pas ces fonctions, mais sachez que :
● vous pouvez aussi enregistrer des informations au format CSV
avec la fonction writer.
● vous pouvez aussi lire/enregistrer dans des dictionnaires directement avec DictReader et DictWriter. Si votre feuille
tableur contient par exemple des titres à vos colonnes (NOM,
PRENOM, ...) vous pourrez alors avoir directement accès aux
informations par ces titres qui seront alors les clés de ce dictionnaire.
https://docs.python.org/fr/3/library/csv.html
Enfin, si vous retapez le code précédent, ne faites pas comme moi en
enregistrant votre programme sous le nom csv.py. Il y aurait alors
un conflit pour charger le module du nom csv !
E X G15 Ecrire une fonction charge qui reçoit un nom de fichier et renvoie une
liste de listes (on parlera de matrice par la suite) représentant le décor avec la
convention :
0 : Case vide
1 : Mur
2 : Gomme
229
C HAPITRE G
E X G16 Écrire une fonction decor qui reçoit la ma-
trice précédente et retourne une surface où le vide est
représenté par un carré blanc, les murs par un carré
noir et les gommes par un cercle jaune.
On souhaite à présent faire un décor plus soigné en ajoutant des
courbes, des croisements ainsi que les autres images générées en début
de chapitre. Pour cela, on va regarder les 4 voisins (nord, sud, est, ouest)
de la case que l’on veut représenter.
Par exemple si on a le motif
x
x
x , on dessinera l’image
À terme, regarder à chaque fois les 4 voisins, en prenant garde aux
problèmes de bords va rapidement être rébarbatif à mettre en œuvre pour
lister les 11 cas qui peuvent se présenter. On se propose alors de coder la
configuration des voisins avec un nombre de 4 bits en donnant à chacun
des voisins un poids :
1
2 x 4 . Dans l’exemple précédent, le code du virage est 4 + 8 = 12.
8
E X G17 Écrire une fonction code(m, l, c) qui reçoit le numéro de la ligne
et de la colonne de la case à dessiner et renvoie le code correspondant.
E X G18 À l’aide d’une table de correspondance (aussi appelée table de hashage),
adapter la fonction decor pour qu’elle dessine un beau décor. Pour dessiner les
gommes, on pourra prendre l’image gomme.png présente sur le site du livre et
représentant une goutte de colle à dentier.
3 - Les personnages
Pac-Man : le dentier
Pour gérer le déplacement de notre héros, on peut s’inspirer du personnage de Cupidon à quelques exceptions près :
● il faut tenir compte des éléments de décor pour autoriser ou non le
déplacement ;
230
EHPAD-RUN
● une fois un déplacement dans une direction demandée, le personnage se déplace jusqu’à toucher un mur ou jusqu’à ce qu’une autre
touche de direction possible soit enfoncée : c’est la tradition du jeu.
On dispose des images 32 × 32 pour réaliser l’animation :
pac1.png
pac2.png
pac3.png
pac4.png
E X G19 Écrire la classe Pacman qui possèdera pour attributs :
● x et y : les coordonnées du coin Nord-Ouest du personnage en pixels.
● direction : la direction vers laquelle se déplace Pac-Man. On gardera
la même convention que pour la recherche de voisins : 1, 2, 4, 8.
● animation : le numéro de l’animation en cours pour animer le personnage.
● images : une liste d’images :
attente
haut
droite
bas
gauche
et comme méthode :
● update : une fonction qui analyse les touches enfoncées et déplace PacMan dans la direction voulue (on ne s’occupe pas du décor pour le moment).
Il nous faut à présent tenir
compte du décor afin de savoir
si le déplacement est possible
ou non. Pensez que le point
(x,y) est le point × du schéma
ci-contre.
Ainsi si on veut aller à droite
par exemple il faudra calculer
les futures coordonnées, puis
vérifier si les deux points ⧫ et ●
ne sont pas sur un obstacle. En
effet, si vous ne vérifiez qu’un
des points, votre personnage
risque de chevaucher les murs.
×
))
231
C HAPITRE G
E X G20 Ajouter une méthode dep possible(m, d) qui reçoit la matrice de
décor et la direction souhaitée (d ∈ {1; 2; 4; 8}) et renvoie un booléen indiquant si
le déplacement est possible.
E X G21 Mettre à jour la méthode update pour que le personnage puisse se
déplacer en respectant le décor du jeu.
E X G22 Terminer le ramassage des gommes pour qu’à chaque gomme avalée, le
score augmente de 10 points.
Les fantômes : les pensionnaires de l’EHPAD
Nous allons ajouter des fantômes. Pour cela, créons donc une nouvelle
classe Fantome possédant comme attributs :
● x et y : ses coordonnées,
● animation : le numéro de l’animation et nb anim le nombre d’animations, car attention, tous ne possèdent pas autant d’images pour
les animer,
● direction : la direction en cours (1, 2, 4 ou 8),
● pas : le nombre de pixels dont avance le fantôme par frame lorsque
celui-ci se déplace (sa vitesse en quelque sorte),
● images : une liste d’images,
et comme méthodes :
● dessine : une méthode qui dessine le fantôme,
● dep possible : comme pour Pac-Man,
● ...
E X G23 Créer le constructeur, qui sera appelé ainsi : Fantome(img, xd,
yd) où (xd, yd) sont les coordonnées de départ et img une chaı̂ne indiquant
le préfixe de l’image. Par exemple les images du premier fantôme se nomment
F1-1.png et F1-2.png et il se trouve en (320,448) au début du jeu. L’instanciation se fera donc avec l’appel : Fantome(’F1’,320,448).
E X G24 Ajouter la méthode update qui effectue le déplacement indiqué par
l’attribut direction et anime le fantôme à l’écran. Attention, un fantôme ne
mange pas de gomme !
232
EHPAD-RUN
Comme vous l’aurez remarqué, si on dissocie la partie affichage de la
partie déplacement du Pac-Man, les fonctions d’affichage pour
un fantôme
EHPAD-RUN
Comme vous l’aurez remarqué, si on dissocie la partie affichage de la
ou notre héros sont identiques. C’est d’ailleurs pareil pour la méthode
partie déplacement du Pac-Man, les fonctions d’affichage pour un fantôme
dep possible qui serait un simple copier-coller... Comme vous vous en
ou notre
héros
sont identiques.
C’est
d’ailleurs
pareil
pour
la méthode
Comme
vous
remarqué,
si on
dissocie
la partie
affichage
de la
doutez,
inutile
de l’aurez
recommencer
plusieurs
fois la même
chose.
Découvrons
dep
possible
qui
serait
un
simple
copier-coller...
Comme
vous
vous
en
partie
déplacement
du Pac-Man, les fonctions d’affichage pour un fantôme
ensemble
la notion d’héritage.
doutez,
recommencer
fois la même
ou
notreinutile
hérosde
sont
identiques.plusieurs
C’est d’ailleurs
pareilchose.
pour Découvrons
la méthode
Dans
la
nous
allons
ajouter
différents fant
ômes (Snoop,
Reniensemble
la suite,
notion
d’héritage.
dep
possible
qui
serait
un simple
copier-coller...
Comme
vous vous
en
fleur,
Tom-Tom)
plus
ou
moins
intelligents.
Un
certain
nombre
de
doutez,
de recommencer
plusieurs
fois la même
chose.
Découvrons
Dansinutile
la suite,
nous allons ajouter
différents
fantômes
(Snoop,
Reniméthodes la
etnotion
d’attributs
seront communs à tous les fantômes, d’autres seensemble
d’héritage.
fleur, Tom-Tom) plus ou moins intelligents. Un certain nombre de
ront spécifiques à tel ou tel fantôme comme le choix de la direction à
méthodes
d’attributs
à tous les
fant
ômes,
d’autres
seDans laetsuite,
nous seront
allons communs
ajouter différents
fant
ômes
(Snoop,
Reniprendre.
ront spécifiques
tel ou
fantôme
comme leUn
choix
de la nombre
directionde
à
fleur,
Tom-Tom) àplus
outelmoins
intelligents.
certain
prendre. et d’attributs seront communs à tous les fantômes, d’autres seméthodes
ront spécifiques à tel ou tel fantôme comme le choix de la direction à
prendre.
Personnage
attributs : x, y, animation,
direction,..
Personnage
méthodes : update, dep possible,...
attributs : x, y, animation, direction,..
Personnage
méthodes
: update, dep possible,...
Pacman
Fantome
attributs : x, y, animation, direction,..
Pacman
Fantome
méthodes
: update, dep possible,...
score,...
nb anim,...
mange,...
collision,...
Pacman
Fantome
score,...
nb anim,...
mange,...
collision,...
Snoop
Renifleur
score,...
nb anim,...
choix
choix
Snoop
Renifleur
mange,...
collision,...
choix
choix
Snoop
Renifleur
GPS
Glouton
choix
choix
choix
choix
GPS
Glouton
choix
Glouton
choix
GPS
choix
choix
Comme le personnage de Pac-Man est déjà opérationnel, nous
n’allons pas dans ce chapitre créer la classe Personnage, mais cela pourra
Comme le personnage de Pac-Man est déjà opérationnel, nous
être réalisé pour d’autres projets. Par contre nous allons créer les 3 classes
n’allons pas dans ce chapitre créer la classe Personnage, mais cela pourra
Snoop, Renifleur et Tomtom à partir de la classe Fantome.
êtreComme
réalisé pour
d’autres projets.
Par contre est
nousdéjà
allons
créer les 3 classes
le personnage
de Pac-Man
opérationnel,
nous
Snoop,
Renifleur
et
Tomtom
à
partir
de
la
classe
Fantome.
n’allons pas dans ce chapitre créer la classe Personnage, mais cela pourra
233
être réalisé pour d’autres projets. Par contre nous allons créer les 3 classes
233
Snoop, Renifleur et Tomtom à partir de la classe Fantome.
233
C HAPITRE G
Le fantôme Snoop : comprendre l’héritage et la surcharge
Programmons ensemble le cas du fantôme
Snoop afin de comprendre la notion
d’héritage. Pour indiquer à Python que
la classe Snoop est héritée de la classe
Fantome, on déclare ainsi :
class Fantome() :
def __init__(self,
self.x, self.y
self.nb_anim =
self.direction
img, xdeb, ydeb) :
= xdeb, ydeb
0
= 1
class Snoop(Fantome) :
pass
Et voilà ! Le simple fait de spécifier la classe mère au moment de la
déclaration d’une classe fille suffit à indiquer l’héritage. La dernière ligne
pass signifie simplement ≪ ne rien faire ≫ et évite à Python de planter,
car il attend quelque chose après la déclaration de la classe. Vérifions le
résultat obtenu :
>>> s = Snoop('F1',32,320)
>>> s
<__main__.Snoop object at 0x0352F890>
>>> s.x
32
>>> doe = Fantome('F1',32,320)
>>> doe
<__main__.Fantome object at 0x0352FFD0>
Ainsi l’objet s est de type Snoop et doe de type Fantome, mais leurs
comportements sont identiques... Pas trop utile de ce fait pour le moment...
Ajoutons un constructeur à la classe Snoop et une méthode info :
class Snoop(Fantome) :
def __init__(self, img, xdeb, ydeb) :
self.nom = 'Snoop'
def info(self) :
print('Mon nom est', self.nom)
Les objets de la classe Snoop possèdent un nouvel attribut nom :
>>> s = Snoop('F1',32,32)
>>> s.info()
Mon nom est Snoop
234
EHPAD-RUN
Par contre, les autres attributs ont été perdus !
>>> s.x
Traceback (most recent call last):
File "<string>", line 301, in runcode
File "<interactive input>", line 1, in <module>
AttributeError: 'Snoop' object has no attribute 'x'
Voici l’explication : lorsque l’on a défini la méthode init de la nouvelle classe, on a écrasé celle de la classe mère. On dit dans ce cas que l’on
a surchargé la méthode. Pour Python, la portée des méthodes ou des attributs suit toujours la même logique : le moteur commence par chercher au
plus près puis remonte dans la hiérarchie des classes jusqu’à la trouver et
déclenche une erreur sinon. Dans ce dernier exemple, comme le constructeur est défini dans la classe Snoop, Python choisit cette déclaration et
n’exécute donc pas la méthode init de la classe Fantome, contrairement à l’exemple précédent. Heureusement, si l’on souhaite instancier
l’objet comme un objet de la classe Fantome, puis ajouter d’autres choses,
on peut procéder comme suit, en précisant le nom de la classe
de la méthode que l’on souhaite exécuter :
class Snoop(Fantome) :
def __init__(self, xdeb, ydeb) :
Fantome.__init__(self, 'F1', xdeb, ydeb)
self.nom = 'Snoop'
>>>
>>>
Mon
>>>
32
s = Snoop(32,320)
s.info()
nom est Snoop
s.x
E X G25 Écrire la description de la classe Snoop qui possède comme méthodes :
● un constructeur qui prend 2 paramètres xdeb et ydeb indiquant la position de départ et appellera à son tour le constructeur de la classe Fantome ;
● une fonction choix qui reçoit en paramètre une matrice de décor et modifie
l’attribut direction du fantôme selon une règle très simple : si on peut
aller vers le haut, on y va, sinon, on s’arrête... Cette fonction fera appel à la
méthode dep possible de la classe Fantome que vous aurez ajoutée ;
● une fonction update qui sera une surcharge de la méthode update de
la classe Fantome. Cette nouvelle méthode nécessite un paramètre m en
entrée pour indiquer le décor et se chargera de faire l’appel à choix, puis
d’afficher le fantôme.
235
C HAPITRE G
E X G26 Améliorer la méthode choix précédente pour que le fantôme Snoop ait
son comportement final : il avance jusqu’à toucher un mur et choisit alors une
nouvelle direction aléatoire parmi les directions possibles.
L’ennemi Renifleur
Le fantôme de la classe ≪ Renifleur ≫ se dirige systématiquement vers une case qui
minimise la distance de Manhattan jusqu’à
Pac-Man (Voir chapitre B).
E X G27 Créer une classe Renifleur ressemblant à la classe Snoop, mais dont
la méthode choix correspond au comportement souhaité.
Si vous avez réussi le précédent exercice, vous aurez remarqué que
l’appel à update pour les 2 classes diffère un peu au niveau des paramètres, votre programme ressemble sûrement à :
f1 = Snoop(320,448)
f2 = Renifleur(320,448)
...
while continuer :
...
f1.update(matrice)
f2.update(matrice, pm)
...
On préfèrerait une solution plus harmonisée, ce qui permettrait d’automatiser les appels. Par exemple :
ennemis = [Snoop(320,448), Renifleur(320,448)]
...
while continuer :
...
for f in ennemis : f.update(matrice, pm)
...
E X G28 Effectuer les changements nécessaires pour uniformiser les appels.
236
EHPAD-RUN
Le fantôme Tom-Tom
Ce dernier fantôme a un sixième sens lui
permettant de réaliser des prouesses tel un
GPS. Pour trouver son chemin, nous allons
utiliser à nouveau un parcours en largeur
du décor tel une tâche d’huile qui se répand
dans le labyrinthe permettant de connaı̂tre
les distances de Pac-Man à chaque case,
comme dans le chapitre B.
Avant de nous lancer dans le code en lui-même, nous allons préparer
la matrice de décor pour pouvoir faire les calculs. La convention retenue
sera de mettre des -1 où il y a des murs et des -2 où il y a autre chose
(gommes, cases vides....).
E X G29 Écrire une fonction copie qui reçoit une matrice de décor et retourne
une nouvelle matrice correspondant à la représentation exposée.
E X G30 Programmer une fonction tache qui reçoit une matrice ne contenant
que des -1 (il y a un mur) et des -2 (la case est accessible) ainsi que l et c indiquant
la ligne et la colonne où se trouve Pac-Man. Cette fonction renvoie une matrice
où, sur toute case accessible, se trouve le nombre de pas la séparant de Pac-Man.
>>> matrice = [
[-1, -1, -1,
[-1, -2, -2,
[-1, -2, -1,
[-1, -2, -2,
[-1, -2, -1,
[-1, -1, -1,
-1,
-2,
-1,
-2,
-2,
-1,
-1,
-2,
-1,
-1,
-2,
-1,
-1,
-1,
-2,
-2,
-2,
-1,
−1 −1 −1 −1 −1 −1 −1
-1],
-1],
-1],
-1],
-1],
-1]]
1
−1 2
3
2
3 −1 −1
−1 1 −1 −1 −1 9 −1
>>> tache(matrice,1,1)
−1 0
donne :
4 −1 8 −1
−1 3 −1 5
6
7 −1
−1 −1 −1 −1 −1 −1 −1
L’intérêt d’avoir obtenu cette matrice est que, quel que soit l’endroit où
se trouve le fantôme, on connaı̂t le chemin à prendre (il suffit de prendre
la direction d’une case avec une valeur plus petite).
E X G31 Écrire une fonction vers qui reçoit cette nouvelle matrice et deux entiers indiquant la ligne et la colonne du fantôme puis retourne un entier de l’ensemble {1; 2; 4; 8} donnant la direction à prendre pour rejoindre Pac-Man.
>>> chemins = tache(matrice,1,1)
>>> vers(chemins, 3, 2)
8
237
C HAPITRE G
Un problème néanmoins subsiste : dès que Pac-Man se déplace, il faut
recalculer toutes les distances avec la fonction tache, ce qui risque à terme,
de ralentir le jeu. On se propose d’avoir recours à une table de hashage,
comme cela a été présenté dans l’exercice 18. Cette table sera un dictionnaire dont les clés seront des quadruplets de la forme (lp,cp,lf ,cf ) indiquant les positions de Pac-Man et du fantôme et les valeurs de la direction
à prendre. Dans notre exemple, table[(1,1,3,2)] vaut 8.
E X G32 Écrire un programme qui renseigne cette table.
E X G33 Écrire une classe Tomtom permettant de créer ce type de fantôme et
inclure celle-ci dans la partie pour terminer le projet.
Voilà, l’aventure de Pac-Man touche à sa fin, mais d’autres vont suivre.
Bien entendu, on peut améliorer le projet :
● limiter la vitesse du fantôme Tom-Tom qui est un peu trop fort pour
nous permettre de gagner. On peut par exemple diviser sa vitesse
par 2, ou encore retirer des clés de table ;
● détecter la fin de partie quand on a gagné ou quand on a perdu ;
● placer les fantômes et Pac-Man en fonction des informations dans le
fichier csv ;
● alléger la table en ne conservant en mémoire que les cases où le
fantôme a des choix de directions (sinon, on continue dans la direction en cours) ;
● ....
Ce projet pourrait être plus abouti en utilisant les connaissances
développées dans les chapitres précédents, ce n’est pas notre objectif ici.
Néanmoins une version plus complète mais non commentée est présente
sur le site avec le code PACMAN .
4 - Aide pour les exercices
Aide pour l’exercice G1 La première question n’a pas grand rapport avec
les images, mais c’est l’occasion de découvrir la fonction getsize du module path disponible dans le package os qui renvoie la taille d’un fichier.
238
EHPAD-RUN
Aide pour l’exercice G7 Une technique relativement simple pour mélanger deux blocs peut être :
● de choisir 2 blocs au hasard,
● de les échanger,
● de recommencer ces opérations un certain nombre de fois.
Pour mémoire, l’appel randint(a,b) du module random renvoie
un entier de l’intervalle [a,b].
Aide pour l’exercice G9 Si vous avez des difficultés à calculer les coordonnées, voici les coordonnées des différents points qui pourraient vous
intéresser si le drapeau mesure 600 × 300 pixels.
Pour les triangles verts, inutile de
perdre du temps à calculer les coordonnées du sommet restant, on peut
se contenter de dessiner un triangle un
peu plus grand en allant jusqu’au milieu du drapeau.
0
30
0
60
540 600
270
300
Aide pour l’exercice G10 Si on représente par des points les couleurs et
les hauteurs connues, on peut représenter les situations ainsi (les données
sont fictives) :
couleurs
c1
A
�→ h2 − h1
��→ h − h1
AB (
) et AM (
)
c2 − c1
c − c1
M
c =? c2
B
hauteurs
h1
h
h2
239
C HAPITRE G
Pour répondre à notre problème, il faut donc commencer par trouver
les points A et B dont les abscisses encadrent h, puis déterminer c. On
�→ ��→
pourra remarquer que les vecteurs AB et AM sont colinéaires et donc que
leurs coordonnées sont proportionnelles, on en déduit :
(h2 − h1 ) × (c − c1 ) = (c2 − c1 ) × (h − h1 )
Aide pour l’exercice G18 Voici la table de correspondance :
0
1
2
3
4
5
6
1
8
1
2
8
1
2
8
1
2
8
1
2
8
1
2
8
7
1
2
8
1
2
8
2
4
4
4
4
4
4
4
4
horiz
vert
horiz
4
vert
vert
3
7
8
9
10
11
12
13
14
15
1
1
1
1
1
1
1
8
2
8
2
8
2
8
2
8
2
8
2
8
1
2
8
2
4
4
4
4
4
4
4
4
horiz
1
horiz
8
2
5
6
croix
Aide pour l’exercice G22 On pourra ajouter une méthode mange et un
attribut score à la classe Pacman.
Aide pour l’exercice G23 Pour charger les images, comme on ne connaı̂t
pas à l’avance le nombre de celles-ci, vous pouvez utiliser la fonction
isfile déjà présentée dans le livre.
Aide pour l’exercice G27 On peut commencer par écrire une fonction
manhattan(x1,y1,x2,y2) qui renvoie la distance de Manhattan entre
2 cases de coordonnées (x1,y1) et (x2,y2).
Aide pour l’exercice G30 N’hésitez pas consulter l’exercice B20 si vous êtes
bloqué.
5 - Solutions des exercices
Retour sur l’exercice G1 Une fois la fonction getsize connue, il n’y a pas
de difficulté pour le reste. Pour compter le nombre de couleurs, on peut
créer une liste et y placer chaque couleur trouvée si elle n’y est pas encore
présente :
240
EHPAD-RUN
import pygame
from os.path import getsize
fichier = "barbe.png"
print('Taille :',getsize(fichier), 'octets')
m = img.get_bytesize()
if m == 1 :
print('256 couleurs')
elif m >= 3 :
print('16 millions couleurs')
else :
print('Autre mode')
if m == 4 :
print('Image avec transparence.')
else :
print('Image sans transparence')
L = []
for x in range(sx) :
for y in range(sy) :
coul = img.get_at((x,y))
if coul not in L :
L.append(coul)
print(len(L),'couleurs utilisées.')
Retour sur l’exercice G2
1. Pour effectuer une inversion verticale, on va ”retourner” chaque
ligne, c’est-à-dire que : si on note sx la largeur de l’image, pendant
qu’on lit les pixels de x = 0 à x = sx − 1, on écrit sur l’image finale de
x = sx − 1 à x = 0. Ainsi on obtient le code suivant :
import pygame
def vertical(img) :
taille = img.get_size()
nouvelle = pygame.Surface(taille)
sx, sy = taille
for x in range(sx) :
for y in range(sy) :
c = img.get_at((x,y))
nouvelle.set_at((sx-x-1,y),c)
return nouvelle
image = pygame.image.load('pacman.png')
pygame.image.save(vertical(image),'pacmanV.png')
241
Solutions des exercices
img = pygame.image.load(fichier)
sx, sy = img.get_size()
print('Résolution :',sx,'x',sy,'pixels')
C HAPITRE G
2. Même principe que précédemment mais on retourne les colonnes
(seule la seconde partie du programme est modifiée) :
def horizontal(img) :
taille = img.get_size()
nouvelle = pygame.Surface(taille)
sx, sy = taille
# Miroir vertical :
for x in range(sx) :
for y in range(sy) :
c = img.get_at((x,y))
nouvelle.set_at((x,sy-y-1),c)
return nouvelle
3. Pour la rotation, regardez où arrivent les 4 points aux coins de l’image.
Trouver la formule n’est pas très difficile. Attention de créer une
image aux bonnes dimensions, si celle-ci n’est pas carrée :
def rotation(img) :
sx, sy = img.get_size()
nouvelle = pygame.Surface((sy, sx))
for x in range(sx) :
for y in range(sy) :
c = img.get_at((x,y))
nouvelle.set_at((sy-y-1,x),c)
return nouvelle
Retour sur l’exercice G3 Il suffit d’appliquer l’algorithme indiqué :
import pygame
def negatif(img) :
if img.get_bytesize() != 3 :
raise Exception("Cette image n'est pas en mode RVB")
taille = img.get_size()
nouvelle = pygame.Surface(taille)
sx, sy = taille
for x in range(sx) :
for y in range(sy) :
r, v, b, _ = img.get_at((x,y))
nouvelle.set_at((x,y),(255-r, 255-v, 255-b))
return nouvelle
image = pygame.image.load('pacman.png')
pygame.image.save(negatif(image),'pacmanN.png')
Pour récupérer les 3 composantes rouge, vert, bleu, on pouvait écrire :
c = img.get_at((x,y))
r, v, b = c[0], c[1], c[2]
mais l’affectation directe telle qu’elle est présentée est bien plus efficace
(on a une quatrième quantité qui vaut 255 puisqu’il n’y a pas de transparence).
242
EHPAD-RUN
Ici on a fait le choix dans la correction de lever une exception à l’aide
de la commande raise lorsque le mode de l’image n’est pas RGB,
ce qui a pour effet de stopper l’exécution du programme....
Retour sur l’exercice G4 En appliquant l’algorithme présenté :
def zoom(img, newX, newY) :
taille = img.get_size()
sx, sy = taille
nouvelle = pygame.Surface((newX, newY))
for x in range(newX) :
for y in range(newY) :
x_origine = round(x*(sx-1)/(newX-1))
y_origine = round(y*(sy-1)/(newY-1))
c = img.get_at((x_origine,y_origine))
nouvelle.set_at((x,y),c)
return nouvelle
image = pygame.image.load('sphere.png')
pygame.image.save(zoom(image, 100, 100), 'sphereZ.png')
Retour sur l’exercice G5 Pour appliquer la méthode proposée, on a appelé ici x et y les coordonnées du pixel dans l’image finale, il faut donc
les multiplier par f pour avoir les coordonnées dans l’image de départ.
Les variables xc et yc permettent, elles, de se déplacer dans une ”case”
et ainsi d’additionner toutes les couleurs. Pour obtenir la moyenne, il faut
enfin diviser par f 2 qui est le nombre de pixels par case.
import pygame
def zoom1(img, f) :
taille = img.get_size()
sx, sy = taille
if sx % f != 0 or sy % f != 0 :
raise Exception("Trop dur pour moi !")
nouvelle = pygame.Surface((sx//f, sy//f))
for x in range(sx//f) :
for y in range(sy//f) :
newr, newv, newb = 0, 0, 0
for xc in range(f) :
for yc in range(f) :
r, v, b, _ = img.get_at((f*x+xc, 20*y+yc))
newr, newv, newb = newr + r, newv + v, newb + b
newr, newv, newb = newr // f**2, newv // f**2, newb // f**2
nouvelle.set_at((x,y),(newr, newv, newb))
return nouvelle
image = pygame.image.load('sphere.png')
pygame.image.save(zoom1(image, 20), 'sphereZ1.png')
243
Solutions des exercices
import pygame
C HAPITRE G
zoom
zoom1
Retour sur l’exercice G6 Ici, seule l’instruction blit sera utilisée 4 fois :
import pygame
lys = pygame.image.load('lys.png')
drapeau = pygame.Surface((600,400))
drapeau.fill((255,255,255))
drapeau.blit(lys,(0,0))
drapeau.blit(lys,(350,0))
drapeau.blit(lys,(350,250))
drapeau.blit(lys,(0,250))
pygame.image.save(drapeau,'quebec.png')
Retour sur l’exercice G7 En s’appuyant sur l’aide proposée, on va répéter
100 fois l’échange de 2 blocs choisis aléatoirement. Ici on a besoin d’une
variable temporaire pour stocker le bloc n°1, car lorsque l’on copie le
bloc n°2 sur le bloc n°1 ce dernier est écrasé.
import pygame
from random import randint
puzzle = pygame.image.load('danseurs.png')
sx, sy = puzzle.get_size()
larg, haut = sx // 10, sy // 10
temporaire = pygame.Surface((larg, haut))
for i in range(100) :
x1, y1 = larg*randint(0, 9), haut*randint(0, 9) # On choisit un bloc
x2, y2 = larg*randint(0, 9), haut*randint(0, 9) # Puis un second
temporaire.blit(puzzle, (0,0), (x1, y1, larg, haut))
puzzle.blit(puzzle, (x1, y1), (x2, y2, larg, haut))
puzzle.blit(temporaire, (x2,y2))
pygame.image.save(puzzle,'puzzle.png')
244
EHPAD-RUN
EHPAD-RUN
Retour sur l’exercice G8 Une première solution en utilisant une surface
pour faire les copier-coller :
Retour sur l’exercice G8 Une première solution en utilisant une surface
pour
faire les copier-coller :
import pygame
False), (0,0))
False), (0,0))
False), (215, 230))
False), (215, 230))
Une seconde sans surface intermédiaire :
Une
seconde sans surface intermédiaire :
import pygame
from pygame.transform import flip
import pygame
t = pygame.image.load('masque.png')
from pygame.transform import flip
t.blit(flip(t.subsurface((215,0,215,230)),
t.blit(flip(t.subsurface((0,230,215,230)),
t = pygame.image.load('masque.png')
pygame.image.save(t, 'propre.png')
t.blit(flip(t.subsurface((215,0,215,230)),
t.blit(flip(t.subsurface((0,230,215,230)),
pygame.image.save(t, 'propre.png')
True, False), (0,0))
True, False), (215, 230))
True, False), (0,0))
True, False), (215, 230))
Retour sur l’exercice G9 Une solution :
Retour
sur l’exercice G9 Une solution :
import pygame
jama = pygame.Surface((600,300))
import pygame
vert = (0,156,55)
pygame.draw.polygon(jama, vert, [(60,0),(300,150),(570,0)])
jama = pygame.Surface((600,300))
pygame.draw.polygon(jama, vert, [(60,300),(300,150),(570,300)])
vert = (0,156,55)
jaune = (254,210,0)
pygame.draw.polygon(jama, vert, [(60,0),(300,150),(570,0)])
pygame.draw.polygon(jama, jaune, [(0,0), (60,0), (600,270), (600,300), ⤦
pygame.draw.polygon(jama, vert, [(60,300),(300,150),(570,300)])
� (540,300), (0,30)])
jaune = (254,210,0)
pygame.draw.polygon(jama, jaune, [(0,300), (60,300), (600,30), (600,0), ⤦
pygame.draw.polygon(jama, jaune, [(0,0), (60,0), (600,270), (600,300), ⤦
� (540,0), (0,270)])
� (540,300), (0,30)])
pygame.draw.polygon(jama, jaune, [(0,300), (60,300), (600,30), (600,0), ⤦
pygame.image.save(jama, 'jamaique.png')
� (540,0), (0,270)])
pygame.image.save(jama, 'jamaique.png')
245
245
Solutions des exercices
tortue = pygame.image.load('masque.png')
import pygame
# On copie le coin Nord-Est
propre = tortue.subsurface((215,0,215,230))
tortue = pygame.image.load('masque.png')
# On le colle à l'Ouest avec un miroir vertical
# On copie le coin Nord-Est
tortue.blit(pygame.transform.flip(propre, True,
propre = tortue.subsurface((215,0,215,230))
# On copie le coin Sud-Ouest
# On le colle à l'Ouest avec un miroir vertical
propre = tortue.subsurface((0,230,215,230))
tortue.blit(pygame.transform.flip(propre, True,
# On le colle à l'Est avec un miroir vertical
# On copie le coin Sud-Ouest
tortue.blit(pygame.transform.flip(propre, True,
propre = tortue.subsurface((0,230,215,230))
pygame.image.save(tortue, 'propre.png')
# On le colle à l'Est avec un miroir vertical
tortue.blit(pygame.transform.flip(propre, True,
pygame.image.save(tortue, 'propre.png')
C HAPITRE G
Retour sur l’exercice G10
1. Avec la formule démontrée dans l’aide, on peut coder la fonction
demandée :
def affine(couleurs, hauteurs, h) :
# On cherche les 2 couleurs qui nous intéressent
for i in range(len(couleurs)-1) :
if hauteurs[i] <= h <= hauteurs[i+1] :
h1, c1 = hauteurs[i], couleurs[i]
h2, c2 = hauteurs[i+1], couleurs[i+1]
# Formule proposée :
return c1 + round((h-h1)*(c2-c1)/(h2-h1))
2. La seconde question ne pose pas de problème, il suffit d’effectuer les
mêmes opérations sur les trois couches :
def affine2(couleurs, hauteurs, h) :
for i in range(len(couleurs)-1) :
if hauteurs[i] <= h <= hauteurs[i+1] :
h1 = hauteurs[i]
r1, v1, b1 = couleurs[i]
h2 = hauteurs[i+1]
r2, v2, b2 = couleurs[i+1]
return r1 + round((h-h1)*(r2-r1)/(h2-h1)), v1 + ⤦
� round((h-h1)*(v2-v1)/(h2-h1)), b1 + ⤦
� round((h-h1)*(b2-b1)/(h2-h1))
3. On code alors l’image avec les informations de l’exercice :
import pygame
tuyau = pygame.Surface((1280, 1280))
noir = (0, 0, 0)
jaune = (255, 255, 100)
gris = (190, 190, 190)
blanc = (255, 255, 255)
C = [noir, noir, blanc, gris, jaune, gris, blanc, noir, noir]
H = [0, 500, 520, 540, 640, 740, 760, 780, 1280]
for h in range(1280) :
coul = affine2(C, H, h)
pygame.draw.line(tuyau, coul, (0,h),(1280,h))
pygame.image.save(tuyau, 'tuyau.png')
Retour sur l’exercice G11 Avec les fonctions smoothscale et rotate,
on crée facilement les 2 images :
import pygame
horiz = pygame.image.load('tuyau.png')
horiz = pygame.transform.smoothscale(horiz,(32,32))
vert = pygame.transform.rotate(horiz,90)
pygame.image.save(horiz, 'horiz.png')
pygame.image.save(vert, 'vert.png')
246
EHPAD-RUN
Retour sur l’exercice G12
1. La droite (D1 ) passe par les points (0,0) et (31,31). Son équation est
donc D1 ∶ y = x. De même (D2 ) passe par les points (0,31) et (31,0),
son équation est donc D2 ∶ y = −x + 31.
horiz
vert
horiz
horiz
horiz
2.
vert
vert
D1
D2
y >x
y > −x + 31
Ainsi, on doit copier l’image horiz.png si et seulement si :
{
x−y ⩾ 0
y >x
y ⩽x
x−y < 0
⇐⇒ {
ou {
.
ou {
31 − x − y < 0
y ⩽ 31 − x
y > 31 − x
31 − x − y ⩾ 0
3. Si on ne tient pas compte des cas d’égalité, on doit copier l’image
horiz.png si et seulement si (x − y)(31 − x − y) < 0.
4. On obtient donc le programme suivant :
import pygame
horiz = pygame.image.load('horiz.png')
vert = pygame.transform.rotate(horiz,90)
croix = pygame.Surface((32,32))
for x in range(32) :
for y in range(32) :
if (x-y)*(31-x-y) < 0 :
coul = horiz.get_at((x,y))
else:
coul = vert.get_at((x,y))
croix.set_at((x,y), coul)
pygame.image.save(croix, 'croix.png')
247
Solutions des exercices
vert
C HAPITRE G
Retour sur l’exercice G13 Avec les explications de l’exercice précédent, on
s’aperçoit que pour l’image 5.png, cette fois, il ne faut copier les pixels
x−y < 0
de l’image horiz.png que lorsque {
. Les 3 autres images
31 − x − y ⩾ 0
s’obtiennent ensuite par simple rotation :
import pygame
horiz = pygame.image.load('horiz.png')
vert = pygame.image.load('vert.png')
img5 = pygame.Surface((32,32))
for x in range(32) :
for y in range(32) :
if x-y < 0 and 31-x-y >= 0 :
coul = horiz.get_at((x,y))
else:
coul = vert.get_at((x,y))
img5.set_at((x,y), coul)
pygame.image.save(img5, '5.png')
pygame.image.save(pygame.transform.rotate(img5,90), '6.png')
pygame.image.save(pygame.transform.rotate(img5,180), '7.png')
pygame.image.save(pygame.transform.rotate(img5,270), '8.png')
Retour sur l’exercice G14 En suivant les instructions données en énoncé,
on arrive à nos fins :
import pygame
from math import sqrt
horiz = pygame.image.load('tuyau.png')
arc = pygame.Surface((1280,1280))
for x in range(1280) :
for y in range(1280) :
d = round(sqrt(x**2+y**2))
if d > 1279 : d = 1279
coul = horiz.get_at((0,d))
arc.set_at((x,y), coul)
arc = pygame.transform.smoothscale(arc,(32,32))
pygame.image.save(arc, '1.png')
pygame.image.save(pygame.transform.rotate(arc,90), '2.png')
pygame.image.save(pygame.transform.rotate(arc,180), '3.png')
pygame.image.save(pygame.transform.rotate(arc,270), '4.png')
248
EHPAD-RUN
Retour sur l’exercice G15 Une solution s’inspirant de l’exemple du cours :
def charge(fichier) :
matrice = []
with open(fichier) as f:
contenu = csv.reader(f, delimiter = ';')
for ligne in contenu :
ligne_decor = []
for case in ligne :
if case == 'x' : ligne_decor.append(1)
elif case == '' : ligne_decor.append(2)
else : ligne_decor.append(0)
matrice.append(ligne_decor)
return matrice
Toutes les cases autres qu’un mur ou une gomme sont considérées vides.
Retour sur l’exercice G16 En utilisant la fonction charge précédemment
créée :
def decor(m) :
nbl, nbc = len(m), len(m[0])
surf = pygame.Surface((nbc*32, nbl*32))
surf.fill((255,255,255))
for l in range(nbl) :
for c in range(nbc) :
if m[l][c] == 1 :
surf.fill((0,0,0),(c*32,l*32,32,32))
elif m[l][c] == 2 :
pygame.draw.ellipse(surf,(128,128,0),(c*32+8,l*32+8,16,16))
return surf
Retour sur l’exercice G17 Cet exercice ne devrait pas poser de difficulté.
On regarde successivement, lorsqu’on le peut, au dessus, à droite, en dessous et à gauche :
def code(m, l, c) :
res = 0
if l > 0 and m[l-1][c] == 1 : # On regarde dessus si c'est possible
res = res + 1
if c < len(m[0])-1 and m[l][c+1] == 1 : # On regarde à droite si ⤦
� c'est possible
res = res + 2
if l < len(m)-1 and m[l+1][c] == 1 : # On regarde dessous si c'est ⤦
� possible
res = res + 4
if c > 0 and m[l][c-1] == 1 : # On regarde à gauche si c'est possible
res = res + 8
return res
249
Solutions des exercices
import csv
C HAPITRE G
Retour sur l’exercice G18 Avec la table de hashage déterminée dans l’aide,
on peut écrire le code suivant :
def decor(m) :
table = ['horiz', 'vert', 'horiz', '4', 'vert', 'vert', '3', '7',
'horiz', '1', 'horiz','8','2','5','6','croix']
nbl, nbc = len(m), len(m[0])
surf = pygame.Surface((nbc*32, nbl*32))
for l in range(nbl) :
for c in range(nbc) :
if m[l][c] == 1 :
nom = table[code(m,l,c)]+".png"
surf.blit(pygame.image.load(nom), (c*32, l*32))
elif m[l][c] == 2 :
surf.blit(pygame.image.load('gomme.png'), (c*32, l*32))
return surf
Retour sur l’exercice G19 Il y a un peu de travail pour cet exercice, mais
pas de difficulté, car la situation est identique à celle de Cupidon.
On réalise la boucle principale habituelle, dans laquelle on utilise les
fonctions qui ont été programmées pour afficher le décor :
pygame.init()
pygame.display.set_caption('PAC-MAN')
fenetre = pygame.display.set_mode((960,640))
matrice = charge('niveau1.csv')
fond = decor(matrice).convert()
fenetre.blit(fond, (0,0))
pm = Pacman()
continuer = True
clock = pygame.time.Clock()
while continuer:
clock.tick(100)
for event in pygame.event.get():
if event.type == QUIT :
continuer = False
pm.update()
pygame.display.flip()
pygame.quit()
Pour la partie constructeur, on ne dispose pas de toutes les images. Il
faut donc les construire, par rotation et symétries.
250
EHPAD-RUN
EHPAD-RUN
À noter que la commande images[-1] permet d’accéder au dernier
À noter que la commande images[-1] permet d’accéder au dernier
élément, images[-2] à l’avant dernier...
élément, images[-2] à l’avant dernier...
Enfin, si vous pensiez, comme moi, gagner du temps en effectuant une
Enfin, si vous pensiez, comme moi, gagner du temps en effectuant une
boucle et de simples rotations, vous vous apercevrez qu’à la deuxième
boucle et de simples rotations, vous vous apercevrez qu’à la deuxième
rotation, Pac-Man se retrouve avec un œil à l’envers !
rotation, Pac-Man se retrouve avec un œil à l’envers !
Reste à programmer la méthode update : on efface par un carré noir,
Reste à programmer la méthode update : on efface par un carré noir,
l’ancien Pac-Man et on met à jour la direction souhaitée en fonction des
l’ancien Pac-Man et on met à jour la direction souhaitée en fonction des
touches enfoncées (pour le moment, on ne s’occupe pas du décor). Les
touches enfoncées (pour le moment, on ne s’occupe pas du décor). Les
deux varaiables pas et vitesse anim représentent respectivement le
deux varaiables pas et vitesse anim représentent respectivement le
nombre de pixels dont on avance à chaque étape et le nombre de frames
nombre de pixels dont on avance à chaque étape et le nombre de frames
que dure chaque animation. On peut ainsi modifier ces informations rapique dure chaque animation. On peut ainsi modifier ces informations rapidement si besoin. Enfin pour connaı̂tre le numéro de l’image à afficher, on
dement si besoin. Enfin pour connaı̂tre le numéro de l’image à afficher, on
utilise encore une table de correspondance pour simplifier le code :
utilise encore une table de correspondance pour simplifier le code :
def update(self) :
def update(self)
:
fenetre.fill((0,0,0),
(self.x, self.y,32,32)) # efface ancienne pos.
fenetre.fill((0,0,0),
self.y,32,32)) # efface ancienne pos.
pas, vitesse_anim = 2,(self.x,
10
pas,
vitesse_anim
=
2,
10
touches = pygame.key.get_pressed()
touches
= pygame.key.get_pressed()
if touches[K_UP]
: self.direction = 1
if
: self.direction
= 1= 2
if touches[K_UP]
touches[K_RIGHT]
: self.direction
if
touches[K_RIGHT]
self.direction==42
if touches[K_DOWN] ::self.direction
if
if touches[K_DOWN]
touches[K_LEFT] :
: self.direction
self.direction =
= 4
8
if
touches[K_LEFT]
: self.direction
decalage
= {0:0, 1:1,
2:2, 4:3, 8:4}= 8
decalage
= {0:0,
2:2, 4:3, 8:4}
self.animation
= 1:1,
(self.animation
+ 1 ) % (2*vitesse_anim)
self.animation
= (self.animation
+ 1 ) % (2*vitesse_anim)
if self.direction
== 1 :
if self.direction
==-1pas
:
self.y = self.y
self.y
=
self.y
pas
if self.direction == 2 :
if self.direction
==+2pas
:
self.x = self.x
self.x = self.x
if self.direction
==+4pas
:
if self.direction
==+4pas
:
self.y = self.y
self.y = self.y
if self.direction
==+8pas
:
if self.direction
==-8pas
:
self.x = self.x
self.x = self.x - pas
fenetre.blit(self.images[self.animation
// vitesse_anim ⤦
fenetre.blit(self.images[self.animation
// vitesse_anim
� +2*decalage[self.direction]], (self.x,
self.y)) ⤦
� +2*decalage[self.direction]], (self.x, self.y))
251
251
Solutions des exercices
class Pacman() :
class
Pacman() :
def __init__(self)
:
def
__init__(self)
:
self.x,
self.y = 32,
32
self.x, self.y =self.animation
32, 32
self.direction,
= 0, 0
self.direction,
self.animation
= 0, 0
self.images = [
self.images
=
[
pygame.image.load('pac1.png').convert_alpha(),
pygame.image.load('pac1.png').convert_alpha(),
pygame.image.load('pac2.png').convert_alpha(),
pygame.image.load('pac2.png').convert_alpha(),
pygame.image.load('pac3.png').convert_alpha(),
pygame.image.load('pac3.png').convert_alpha(),
pygame.image.load('pac4.png').convert_alpha()]
pygame.image.load('pac4.png').convert_alpha()]
self.images.append(pygame.transform.rotate(self.images[-2],-90))
self.images.append(pygame.transform.rotate(self.images[-2],-90))
self.images.append(pygame.transform.rotate(self.images[-2],-90))
self.images.append(pygame.transform.rotate(self.images[-2],-90))True))
self.images.append(pygame.transform.flip(self.images[-4],False,
self.images.append(pygame.transform.flip(self.images[-4],False, True))
True))
self.images.append(pygame.transform.flip(self.images[-4],False,
self.images.append(pygame.transform.flip(self.images[-4],False,
True))
self.images.append(pygame.transform.flip(self.images[-4],True, False))
self.images.append(pygame.transform.flip(self.images[-4],True, False))
False))
self.images.append(pygame.transform.flip(self.images[-4],True,
self.images.append(pygame.transform.flip(self.images[-4],True, False))
C HAPITRE G
Retour sur l’exercice G20 Il faut diviser par 32 l’abscisse d’un point pour
obtenir le numéro de la colonne correspondante dans la matrice et faire
de même sur l’ordonnée pour avoir le numéro de la ligne. À chaque fois,
il y a deux points à étudier. On s’aperçoit que l’on a à nouveau besoin de
connaı̂tre la valeur de pas si on veut calculer les futures coordonnées du
personnage. On passe alors pas comme attribut de la classe plutôt que
variable de la fonction update :
def dep_possible(self, m, d) :
if d == 1 : # Vers le haut
l1, c1 = (self.y-self.pas) // 32, self.x // 32 # croix
l2, c2 = (self.y-self.pas) // 32, (self.x+31) // 32 # losange
elif d == 2 : # Vers la droite
l1, c1 = (self.y) // 32, (self.x+31+self.pas) // 32 # losange
l2, c2 = (self.y+31) // 32, (self.x+31+self.pas) // 32 # Disque
elif d == 4 : # Vers le bas
l1, c1 = (self.y+31+self.pas) // 32, self.x // 32 # triangle
l2, c2 = (self.y+31+self.pas) // 32, (self.x+31) // 32 # disque
else : # Vers la gauche
l1, c1 = (self.y) // 32, (self.x-self.pas) // 32 # croix
l2, c2 = (self.y+31) // 32, (self.x-self.pas) // 32 # triangle
return m[l1][c1] != 1 and m[l2][c2] != 1
Retour sur l’exercice G21 On ajoute les tests nécessaires : si Pac-Man
heurte un mur, il s’arrête, sinon il continue dans la même direction, à
moins qu’une touche ait été pressée :
def update(self) :
vitesse_anim = 10
# On efface l'ancienne position
fenetre.fill((0,0,0), (self.x, self.y,32,32))
# Si on se cogne, on s'arrête
if not self.dep_possible(matrice, self.direction) :
self.direction = 0
# Si on a appuyé sur une touche :
touches = pygame.key.get_pressed()
if touches[K_UP] and self.dep_possible(matrice, 1) :
self.direction = 1
if touches[K_RIGHT] and self.dep_possible(matrice, 2) :
self.direction = 2
if touches[K_DOWN] and self.dep_possible(matrice, 4) :
self.direction = 4
if touches[K_LEFT] and self.dep_possible(matrice, 8) :
self.direction = 8
decalage = {0:0, 1:1, 2:2, 4:3, 8:4}
self.animation = (self.animation + 1 ) % (2*vitesse_anim)
.../...
252
EHPAD-RUN
if self.direction == 1 :
self.y = self.y - self.pas
if self.direction == 2 :
self.x = self.x + self.pas
if self.direction == 4 :
self.y = self.y + self.pas
if self.direction == 8 :
self.x = self.x - self.pas
num_img = self.animation//vitesse_anim+2*decalage[self.direction]
fenetre.blit(self.images[num_img], (self.x, self.y))
Retour sur l’exercice G22 En jetant un œil si besoin au chapitre Pong pour
afficher le score, on obtient le code suivant :
def mange(self, m) :
l, c = (self.y+16) // 32, (self.x+16) // 32 #Au centre de Pac-Man
if m[l][c] == 2 :
self.score = self.score + 10
m[l][c] = 0
fichier = pygame.font.match_font('arial')
fnt = pygame.font.Font(fichier, 80)
texte = fnt.render(str(self.score), True, (200,200,0))
ltxt, htxt = fnt.size(str(self.score))
xtxt = 32*23+(32*6 - ltxt)//2
ytxt = 32 + (32*3 - htxt)//2
fenetre.fill((0,0,0),(32*23,32,32*6,32*3))
fenetre.blit(texte, (xtxt, ytxt))
Bien évidemment on pourrait améliorer le programme pour que le score
s’affiche au bon endroit en fonction du décor du niveau.
Retour sur l’exercice G23 On cherche tous les fichiers images avec le
préfixe demandé pour renseigner les attributs de l’objet :
class Fantome() :
def __init__(self, img, xdeb, ydeb) :
self.x, self.y = xdeb, ydeb
self.nb_anim = 0
self.images = []
while os.path.isfile(img+'-'+str(self.nb_anim+1)+'.png') :
self.images.append(pygame.image.load(img+'-' + ⤦
� str(self.nb_anim+1)+'.png').convert_alpha())
self.nb_anim = self.nb_anim + 1
self.pas = 2
self.animation = 0
self.direction = 0
253
Solutions des exercices
C HAPITRE G
Retour sur l’exercice G24 La méthode update pour les fantômes
ressemble fortement à celle de Pac-Man. Les différences résident d’une
part dans le fait que les fantômes sont animés, mais ont toujours même allure quelle que soit la direction et d’autre part dans le fait qu’il ne faut pas
effectuer un fill pour effacer le fantôme avant de l’afficher à nouveau,
sinon ce dernier mangerait les gommes, il faut donc copier un morceau du
fond au lieu de déposer un carré noir :
def update(self) :
vitesse_anim = 10
# On efface l'ancienne position
fenetre.blit(fond, (self.x, self.y), (self.x, self.y,32,32))
self.animation = (self.animation + 1 ) % (self.nb_anim*vitesse_anim)
if self.direction == 1 :
self.y = self.y - self.pas
if self.direction == 2 :
self.x = self.x + self.pas
if self.direction == 4 :
self.y = self.y + self.pas
if self.direction == 8 :
self.x = self.x - self.pas
num_img = self.animation//vitesse_anim
fenetre.blit(self.images[num_img], (self.x, self.y))
Un autre problème se pose : lorsque les fantômes repassent sur un chemin où des gommes avaient été mangées, elles réapparaissent ! Il est donc
préférable de recopier aussi le fond avant de dessiner Pac-Man et d’effacer les gommes lorsque celles-ci sont mangées. Dans la méthode update
de la classe Pacman, on remplace :
fenetre.fill((0,0,0), (self.x, self.y,32,32))
par :
fenetre.blit(fond, (self.x, self.y), (self.x, self.y,32,32))
et dans la méthode mange, on ajoute dans le test une ligne :
if m[l][c] == 2 :
self.score = self.score + 10
m[l][c] = 0
fond.fill((0,0,0), (c*32, l*32,32,32))
254
EHPAD-RUN
Retour sur l’exercice G25 Voici une déclaration possible qui correspond à
l’énoncé :
class Snoop(Fantome) :
def __init__(self, xdeb, ydeb) :
Fantome.__init__(self, 'F1', xdeb, ydeb)
def update(self, m ) :
self.choix(m)
Fantome.update(self)
En réalité, la variable matrice de notre programme étant une variable
globale, on pourrait se passer de la mettre en paramètre des fonctions
choix et update.
Retour sur l’exercice G26 Il suffit de mettre à jour la méthode : on place
dans une liste l’ensemble des directions possibles, puis on en choisit une à
l’aide de la fonction choice :
def choix(self, m) :
if not self.dep_possible(m, self.direction) :
choix = []
for d in(1,2,4,8) :
if self.dep_possible(m, d) : choix.append(d)
self.direction = choice(choix)
Retour sur l’exercice G27 Voici le code d’une solution que nous allons
commenter ensuite :
class Renifleur(Fantome) :
def __init__(self, xdeb, ydeb) :
Fantome.__init__(self, 'F2', xdeb, ydeb)
def manhattan(x1, y1, x2, y2) :
return abs(x1-x2)+abs(y1-y2)
.../...
255
Solutions des exercices
def choix(self, m) :
if self.dep_possible(m, 1) :
self.direction = 1
else :
self.direction = 0
C HAPITRE G
def choix(self, m, pac) :
choix = []
if self.dep_possible(m, 1) :
dist = Renifleur.manhattan(pac.x,
choix.append((dist,1))
if self.dep_possible(m, 2) :
dist = Renifleur.manhattan(pac.x,
choix.append((dist,2))
if self.dep_possible(m, 4) :
dist = Renifleur.manhattan(pac.x,
choix.append((dist,4))
if self.dep_possible(m, 8) :
dist = Renifleur.manhattan(pac.x,
choix.append((dist,8))
choix.sort()
while choix[0][0] < choix[-1][0] :
choix.pop(-1)
self.direction = choice(choix)[1]
pac.y, self.x, self.y-self.pas)
pac.y, self.x+self.pas, self.y)
pac.y, self.x, self.y+self.pas)
pac.y, self.x-self.pas, self.y)
def update(self, m, pac) :
self.choix(m, pac)
Fantome.update(self)
● La partie constructeur ne vous surprendra pas. On se contente de
modifier l’image du fantôme et on déclare ensuite la fonction permettant de calculer la distance de Manhattan entre deux points :
d(A; B) = ∣xB − xA ∣ + ∣yB − yA ∣
● Pour la méthode choix, on ne peut plus utiliser une boucle for. On
va donc créer à la main une liste contenant non seulement les directions possibles, mais aussi la distance qui séparera Pac-Man de la future position du fantôme. On stocke ces informations sous la forme
(distance,direction). Ensuite on demande à Python de trier la liste.
Comme ici il s’agit de couples à trier, le tri va se faire par ordre lexicographique, c’est à dire que la première composante du couple va
servir de critère d’ordonnancement, puis, à premières composantes
identiques, on départagera les ex-æquo en fonction de la seconde.
On se retrouve donc avec une liste triée selon les distances restant
à parcourir dans l’ordre croissant. On pourrait donc choisir le premier élément de cette nouvelle liste. Ici on gardera tous les couples
à distance égale pour laisser un peu de hasard. On supprime donc
le dernier élément de la liste (d’indice -1) tant que la distance du
dernier couple est strictement supérieure à la distance du premier.
Reste alors à tirer au hasard un élément et mettre à jour la distance
souhaitée.
256
EHPAD-RUN
● Pour la méthode update, il faut prendre en compte le fait que nous
avons non seulement besoin de la matrice comme dans le cas
précédent, mais aussi de la position de Pac-Man
Retour sur l’exercice G29 Il est en effet vraiment préférable de faire une
copie, sinon vous écraseriez les valeurs présentes dans le décor et ne pourriez pas recommencer l’opération avec une autre position de départ (les
listes sont des alias). Pour ce faire on peut, soit créer la nouvelle matrice
valeur par valeur :
def copie(m) :
nouv = []
for l in range(len(m)) :
ligne = []
for c in range(len(m[0])) :
if m[l][c] == 1 : # un mur
ligne.append(-1)
else :
ligne.append(-2)
nouv.append(ligne)
return nouv
soit aussi utiliser des listes en compréhension :
def copie(m) :
def f(x) :
return -1 if x == 1 else -2
return [[f(x) for x in ligne] for ligne in m]
ou encore en une ligne, si vous êtes joueur :
def copie(m) :
return [[(lambda x : -1 if x==1 else -2)(x) for x in li] for li in m]
Attention : si vous décidez de faire une copie de la matrice pour ensuite
corriger les valeurs, la commande nouvelle = ancienne[:] ne suffit pas, car chaque élément de ancienne est, à son tour, une liste. On
peut par contre utiliser la fonction deepcopy du module copy qui permet
comme son nom l’indique de faire une copie ≪ en profondeur ≫. Comme
l’illustre le schéma suivant, seule B3 est une réelle copie de la liste :
257
Solutions des exercices
Retour sur l’exercice G28 Il suffit d’ajouter un paramètre à la fonction
update de la classe Snoop et d’effectuer les modifications proposées.
Pour limiter la place sur le livre, le code est seulement disponible sur le
site.
C HAPITRE G
C HAPITRE G
from copy import deepcopy
from
copy import
deepcopy
[-1,6]]
A = [[8,4],
A ==[[8,4],
[-1,6]]
B1
A
B1 =
= A[:]
A
B2
B2 =
= deepcopy(A)
A[:]
B3
B3 = deepcopy(A)
Capture réalisée via le site pythontutor.com
Capture réalisée via le site pythontutor.com
Retour sur l’exercice G30 Une fois une réelle copie obtenue, on peut alors
Retour sur l’exercice G30 Une fois une réelle copie obtenue, on peut alors
coder l’algorithme de parcours, en regardant à chaque étape les 4 direccoder l’algorithme de parcours, en regardant à chaque étape les 4 directions possibles, si elles n’ont pas été visitées :
tions possibles, si elles n’ont pas été visitées :
def tache(m, ldeb, cdeb) :
def nouvelle
tache(m, =
ldeb,
cdeb) :
copie(m)
nouvelle
= copie(m) = 0
nouvelle[ldeb][cdeb]
nouvelle[ldeb][cdeb] = 0
Atraiter = [(ldeb, cdeb)]
Atraiter
= [(ldeb,
cdeb)]
while Atraiter
!= []
:
while
!= [] :
l,Atraiter
c = Atraiter.pop(0)
l,
= Atraiter.pop(0)
if c
nouvelle[l-1][c]
== -2 :
if nouvelle[l-1][c]
:
nouvelle[l-1][c]==
= -2
nouvelle[l][c]
nouvelle[l-1][c]
= nouvelle[l][c]
Atraiter.append((l-1,c))
Atraiter.append((l-1,c))
if nouvelle[l+1][c]
== -2 :
if nouvelle[l+1][c]
:
nouvelle[l+1][c]==
= -2
nouvelle[l][c]
nouvelle[l+1][c]
= nouvelle[l][c]
Atraiter.append((l+1,c))
Atraiter.append((l+1,c))
if nouvelle[l][c-1]
== -2 :
if nouvelle[l][c-1]
:
nouvelle[l][c-1]==
= -2
nouvelle[l][c]
nouvelle[l][c-1]
= nouvelle[l][c]
Atraiter.append((l,c-1))
Atraiter.append((l,c-1))
if nouvelle[l][c+1]
== -2 :
if nouvelle[l][c+1]
:
nouvelle[l][c+1]==
= -2
nouvelle[l][c]
nouvelle[l][c+1]
= nouvelle[l][c]
Atraiter.append((l,c+1))
Atraiter.append((l,c+1))
return nouvelle
return nouvelle
258
258
+ 1
+ 1
+ 1
+ 1
+ 1
+ 1
+ 1
+ 1
EHPAD-RUN
On peut, comme d’habitude, factoriser le code en évitant les 4 blocs
qui se ressemblent fortement :
def tache(m, ldeb, cdeb) :
nouvelle = copie(m)
nouvelle[ldeb][cdeb] = 0
Atraiter = [(ldeb, cdeb)]
while Atraiter != [] :
l, c = Atraiter.pop(0)
for Dl, Dc in (-1,0), (0,-1), (1,0), (0,1) :
if nouvelle[l+Dl][c+Dc] == -2 :
nouvelle[l+Dl][c+Dc] = nouvelle[l][c] + 1
Atraiter.append((l+Dl,c+Dc))
return nouvelle
Retour sur l’exercice G31 Quatre if suffisent à réaliser cette fonction :
def vers(chem, l, c) :
if -1 < chem[l-1][c]
if -1 < chem[l][c+1]
if -1 < chem[l+1][c]
if -1 < chem[l][c-1]
<
<
<
<
chem[l][c]
chem[l][c]
chem[l][c]
chem[l][c]
:
:
:
:
return
return
return
return
1
2
4
8
Retour sur l’exercice G32 On parcourt toutes les cases de la matrice qui
sont libres (leurs valeurs est à -2) :
table = {} # Un dictionnaire vide
for lp in range(len(matrice)) :
for cp in range(len(matrice[0])) :
if matrice[lp][cp] == -2 : # Dans ce case, on peut mettre Pac-Man
chemins = tache(matrice, lp, cp)
for lf in range(len(matrice)) :
for cf in range(len(matrice[0])) :
if matrice[lf][cf] == -2 and (lp, cp) != (lf, cf) :
table[(lp, cp, lf, cf)] = vers(chemins, lf, cf)
259
Solutions des exercices
C HAPITRE G
Retour sur l’exercice G33 Voici la déclaration de la classe. Au moment de
l’instanciation, la table table est calculée une fois pour toute et renseignée.
Il suffit alors pour se déplacer, d’aller dans la direction souhaitée si cela est
possible :
class Tomtom(Fantome) :
def __init__(self, xdeb, ydeb) :
Fantome.__init__(self, 'F3', xdeb, ydeb)
self.pas = 1
nouvelle = Tomtom.copie(matrice)
self.table = {} # Un dictionnaire vide
for lp in range(len(nouvelle)) :
for cp in range(len(nouvelle[0])) :
if nouvelle[lp][cp] == -2 :
chemins = Tomtom.tache(nouvelle, lp, cp)
for lf in range(len(nouvelle)) :
for cf in range(len(nouvelle[0])) :
if nouvelle[lf][cf] == -2 and (lp, cp) != (lf, cf) :
self.table[(lp,cp,lf,cf)] = Tomtom.vers(chemins, lf, cf)
def copie(m) :
...
def tache(m, ldeb, cdeb) :
...
def vers(chem, l, c) :
...
def choix(self, m, pac) :
lp, cp = (pac.y+16) // 32, (pac.x+16) // 32
lf, cf = (self.y+16) // 32, (self.x+16) // 32
if (lp, cp, lf, cf) in self.table :
d = self.table[(lp, cp, lf, cf)]
if self.dep_possible(matrice, d) :
self.direction = d
def update(self, m, pac) :
self.choix(m, pac)
Fantome.update(self)
Vous aurez peut-être à ajouter le nom de la classe par-ci par-là pour appeler les fonctions. L’intégralité du code se trouve sur le site du livre.
260
Chapitre H
Les bases de données
1 - Présentation
Dans les précédents chapitres, nous avons vu comment gérer différents
type de fichiers. Cela permet de lire et enregistrer des informations sur la
machine (ou un serveur à distance) comme les identifiants, les scores, le
nombre de parties jouées, voire les identités bancaires du joueur si vous
comptez vivre de vos créations !
Pour stocker et accéder à diverses informations, on peut aussi utiliser
des dictionnaires comme cela a été présenté dans le premier chapitre, cependant cela oblige à avoir le dictionnaire complet en mémoire ce qui n’est
souvent pas utile et gourmand en mémoire si la quantité d’informations
est très importante. Utiliser alors des fichiers est une alternative, mais il
va rapidement devenir compliqué d’aller lire juste telle ou telle information sans parcourir tout le fichier. Les bases de données sont faites pour ça,
elles permettent de lire et d’écrire des informations très simplement et très
rapidement, le moteur de base de données se chargeant pour nous de la
manière dont celles-ci sont stockées.
Le système de base de données existant par défaut sous Python est
SQLite. Ce système est particulièrement adapté à un usage local, d’ailleurs
de nombreuses applications comme Thunderbird, Firefox ou encore Skype
l’utilisent. Pour une utilisation et un stockage en ligne sur la toile, il existe
d’autres solutions, MySQL est sûrement la plus connue. .
261
C HAPITRE H
Nous n’allons pas programmer de
jeu dans ce chapitre, mais de
manière à être le plus concret possible, les explications vont s’appuyer sur l’exemple d’un jeu de
type RPG comme World of Warcraft.
sources Wikipédia
Pour ceux qui ne connaissent pas ce jeu, il s’agit d’un jeu vidéo très
populaire de type MMORPG, c’est-à-dire un jeu de rôle en ligne massivement multijoueur. Il est développé par la société Blizzard Entertainment
et détient le Guinness World Record pour la plus grande popularité d’un
MMORPG : le 7 octobre 2010, Blizzard annonce que plus de 12 millions de
joueurs ont un compte World of Warcraft actif.
Dans la suite on s’intéresse à ce que pourrait être une représentation de
la base de données du jeu sous forme extrêmement simplifiée. Cette base
a été construite de façon aléatoire via http ://fr.fakenamegenerator.com/,
un site de générateur d’identité. Inutile donc de tenter de contacter les
personnes de cette base, tout est fictif !
2 - Utilité des tables
Une table peut être vue comme un tableau à double entrée. Les lignes
sont appelées enregistrements et les titres des colonnes sont appelés
champs. Chaque colonne ne contient qu’un type de données.
Pour conserver les données des clients du jeu, nous allons utiliser une
table que nous nommerons membres et dont voici un extrait :
262
LLES
ES BASES
BASES DE
DE DONN
DONNÉES
ÉES
●● La
La table
table comporte
comporte 13
13 champs
champs et
et autant
autant d’enregistrements
d’enregistrements que
que
de
de comptes
comptes clients
clients (3000
(3000 dans
dans notre
notre exemple).
exemple).
●● Les
Les champs
champs id,
id, sexe
sexe et
et cp
cp (code
(code postal)
postal) sont
sont de
de type
type INT
INT
(entiers)
(entiers) les
les autres
autres sont
sont de
de type
type TEXT
TEXT (chaı̂nes
(chaı̂nes de
de caractères).
caractères). Il
Il
n’existe
n’existe que
que 33 autres
autres types
types de
de données
données REAL
REAL (pour
(pour les
les nombres
nombres
flottants),
flottants), NUMERIC
NUMERIC (qui
(qui peut
peut regrouper
regrouper les
les deux
deux types
types de
de
nombres)
nombres) et
et BLOB,
BLOB, anciennement
anciennement appelé
appelé NONE,
NONE, qui
qui stocke
stocke les
les
données
données brutes
brutes sans
sans aucune
aucune conversion.
conversion.
●● Le
Le champ
champ id
id est
est une
une clé
clé primaire,
primaire, c’est-à-dire
c’est-à-dire qu’elle
qu’elle est
est inindexée
dexée (ce
(ce qui
qui permet
permet une
une recherche
recherche plus
plus rapide
rapide par
par le
le moteur
moteur
de
de base
base de
de données)
données) et
et unique
unique (impossible
(impossible d’avoir
d’avoir deux
deux enreenregistrements
gistrements avec
avec la
la même
même clé)
clé) .. Ce
Ce champ
champ permet
permet donc
donc d’idend’identifier
tifier rapidement
rapidement et
et de
de manière
manière unique
unique les
les enregistrements.
enregistrements.
Sous
Sous SQLite,
SQLite, ilil existe
existe donc
donc peu
peu de
de types
types de
de champs,
champs, d’ailleurs
d’ailleurs lors
lors de
de
la
la création
création de
de la
la table,
table, le
le type
type indiqué
indiqué n’est
n’est qu’une
qu’une affinité,
affinité, c’est-à-dire
c’est-à-dire que
que
le
le moteur
moteur SQLite
SQLite tentera
tentera de
de stocker
stocker la
la quantité
quantité sous
sous le
le type
type indiqué
indiqué s’il
s’il le
le
peut,
peut, sinon
sinon ilil le
le sauvegardera
sauvegardera sous
sous un
un autre
autre type.
type. On
On parle
parle alors
alors de
de typage
typage
dynamique
dynamique àà l’inverse
l’inverse du
du moteur
moteur MySQL
MySQL ooùù le
le typage
typage est
est statique.
statique. Dans
Dans
tous
tous les
les cas,
cas, c’est
c’est une
une bonne
bonne habitude
habitude d’utiliser
d’utiliser un
un type
type fixé
fixé pour
pour chaque
chaque
champ
champ et
et de
de s’y
s’y tenir
tenir afin
afin d’éviter
d’éviter toute
toute surprise.
surprise.
Dans
Dans ce
ce chapitre,
chapitre, nous
nous allons
allons nous
nous contenter
contenter de
de manipuler
manipuler les
les bases
bases de
de
données
données sans
sans interaction
interaction avec
avec un
un programme
programme Python.
Python. Le
Le logiciel
logiciel utilisé
utilisé
pour
pour administrer
administrer les
les bases
bases en
en question
question sera
sera DB
DB Browser
Browser for
for SQLite,
SQLite, ilil
s’agit
s’agit d’un
d’un logiciel
logiciel libre
libre et
et multiplateforme.
multiplateforme. Si
Si vous
vous avez
avez fait
fait le
le (bon)
(bon) choix
choix
d’utiliser
d’utiliser EduPython,
EduPython, ce
ce logiciel
logiciel yy est
est déjà
déjà inclus
inclus àà partir
partir de
de la
la version
version
1.4.
.. Sinon,
1.4. Pour
Pour l’ouvrir
l’ouvrir ilil suffit
suffit de
de cliquer
cliquer sur
sur l’ic
l’icône
ône
Sinon, vous
vous pouvez
pouvez
télécharger
télécharger le
le logiciel
logiciel depuis
depuis le
le site
site officiel
officiel http
http ://sqlitebrowser.org
://sqlitebrowser.org ou
ou
en
en tapant
tapant le
le code
code DBB
DBB sur
sur le
le site
site du
du livre
livre et
et ainsi
ainsi l’utiliser
l’utiliser de
de manière
manière
indépendante.
indépendante.
Une
Une base
base de
de données
données SQLite
SQLite se
se présente
présente sous
sous la
la forme
forme d’un
d’un seul
seul fifichier
chier avec
avec des
des extensions
extensions diverses
diverses (.db,
(.db, .db3,
.db3, .sqlite
.sqlite ou
ou encore
encore .sqlite3).
.sqlite3). La
La
base
base de
de données
données qui
qui contient
contient notre
notre exemple
exemple est
est un
un fichier
fichier wow.db3,
wow.db3, que
que
vous
vous pouvez
pouvez télécharger
télécharger en
en tapant
tapant WOW
WOW pour
pour l’ouvrir
l’ouvrir dans
dans DB
DB BrowBrowser.
ser. Vous
Vous obtiendrez
obtiendrez un
un écran
écran semblable
semblable àà l’image
l’image suivante
suivante qui
qui contient
contient
toute
toute la
la structure
structure de
de la
la base
base sur
sur laquelle
laquelle nous
nous reviendrons
reviendrons plus
plus tard.
tard. Pour
Pour
le
le moment,
moment, on
on peut
peut déjà
déjà remarquer
remarquer que
que l’on
l’on yy retrouve
retrouve la
la table
table membres
membres
déjà
déjà présentée.
présentée.
263
263
C HAPITRE H
Capture d’écran de la structure de la base de données utilisée
Dans l’onglet ”parcourir les données”, on peut afficher le contenu des
tables une à une, ajouter et supprimer un enregistrement. Pensez à sauvegarder votre base au fur et à mesure si vous voulez que les changements
soient effectifs dans le fichier. On ne peut pas (à l’heure actuelle) effectuer
de recherches à l’exception de quelques filtres de base.
Ici on a filtré les membres qui habitent dans une ”rue” à PARIS
264
L ES
BASES DE DONN ÉES
3 - Premières requêtes
Pour extraire les données avec plus de finesse, nous allons écrire des
requêtes, c’est-à-dire des instructions, dans un nouveau langage appelé
langage SQL (Structured Query Language, en français langage de requête
structurée). Comme pour Python, il existe différentes versions de ce langage, nous présenterons ici le SQL3. Les requêtes seront tapées dans le
dernier onglet du logiciel.
Exemple de requête renvoyant tous les membres dont le prénom est Vincent.
La sélection
On appelle sélection l’extraction de tous les champs des enregistrements d’une table respectant certains critères.
SELECT * FROM table
SELECT * FROM table WHERE conditions
✍
SELECT * FROM membres WHERE prenom = "Vincent"
extrait de la table membres, tous les enregistrements dont le champ
prenom est Vincent. Si aucune instruction WHERE n’est précisée,
l’intégralité des enregistrements est retournée.
265
C HAPITRE H
Pour les conditions, excepté le test d’égalité qui ne comporte qu’un
symbole d’égalité (on peut néanmoins en mettre deux), on retrouve
les mêmes syntaxes et opérateurs qu’en Python ( >=, ! =, %, AND, OR,
NOT...). Attention cependant, la division / sur des entiers renvoie le
quotient entier (// n’existe pas).
E X H1 Donner (et tester) les requêtes permettant :
1. D’afficher toutes les femmes inscrites.
2. D’afficher toutes les personnes résidant à Nice.
3. D’afficher toutes les femmes résidant à Nice.
4. D’afficher les personnes vivant dans la Somme (département 80).
5. D’afficher les Bretons, la Bretagne étant composée des départements Côtesd’Armor(22), Finistère(29), Ille-et-Vilaine(35) et Morbihan(56).
Les requêtes sur les chaı̂nes de caractères respectent la casse, c’est-àdire les différences majuscules/minuscules. Par exemple, la requête
SELECT * FROM membres WHERE prenom = "vincent"
ne
renvoie aucun enregistrement à cause du v minuscule. Pour pallier
cette contrainte, on utilise souvent l’opérateur LIKE à la place du
symbole égal. Ce dernier compare les chaı̂nes sans tenir compte de
la casse, par contre cela ne règle pas (contrairement en MySQL) le
problème des accents et des caractères spéciaux : si vous cherchez
"Clemence" ou "Clémence", vous n’obtiendrez pas la même chose.
D’autre part, on dispose du symbole joker % qui peut remplacer
n’importe quelle chaı̂ne de caractères (même vide). Ainsi par exemple,
la requête SELECT * FROM membres WHERE nom LIKE "%Z%"
revoie toutes les personnes dont le nom contient un Z.
E X H2 Trouver les requêtes permettant d’afficher
1. Les enregistrements représentant des personnes dont le login commence par
la lettre C.
2. Les personnes nées en 1976.
3. Les personnes qui se sont inscrites en mars (champ inscription).
4. Les personnes dont le mail est hébergé par superrito.com.
266
L ES
BASES DE DONN ÉES
La projection
En pratique, on a rarement besoin d’extraire tout l’enregistrement, car
seulement quelques champs nous intéressent.
On appelle projection l’extraction de quelques champs d’une table.
SELECT champ1,champ2,... FROM table
On mixe bien souvent les projections et les sélections :
SELECT champ1,champ2,... FROM table WHERE conditions
✍
SELECT prenom, nom FROM membres WHERE pass LIKE "%z%z%"
Renvoie les noms et prénoms des 17 personnes dont le mot de passe
comporte (au moins) deux fois la lettre z.
E X H3 Écrire les requêtes permettant de renvoyer
1. le login et le mot de passe d’Armand LALONDE.
2. les numéros de téléphone des personnes inscrites en 2015.
3. l’adresse de monsieur Abril.
4 - Compléments sur les requêtes
Pour découvrir de nouvelles possibilités de requêtes, nous allons compléter notre base de données en ajoutant deux nouvelles tables. Dans notre
jeu, chaque membre peut créer jusqu’à 3 personnages. On pourrait alors
imaginer d’ajouter un champ personnage à notre table déjà existante, mais
il faudrait alors faire autant de copies d’enregistrements que de personnages, ce qui serait très gourmand en terme de place et complexe au niveau de la maintenance : imaginez qu’un membre change d’adresse, il faudrait alors répercuter ces changements sur tous les enregistrements. C’est
tout l’intérêt d’une base de données : éviter les répétitions inutiles.
267
C HAPITRE H
Nous allons donc créer une nouvelle table persos dont la structure
est la suivante :
Chaque personnage dispose d’un identifiant
idPerso unique (c’est une clé primaire
pour la table persos) et est rattaché au
compte du membre propriétaire via le champ
idMembre, il s’agit là d’une clé étrangère,
car elle correspond nécessairement à une clé
primaire d’une autre table.
idRace race
Le champ nom indique le nom du personnage (et non
1
Humain
celui de son propriétaire), les deux autres champs
2
Elf
nbConnect et score contiennent respectivement le
3
Nain
nombre de parties et le meilleur score réalisé par le
4
Hobbit
personnage. Enfin, le champ idRace indique à quelle
5
Barbare
”race” appartient le personnage parmi les choix ci6
Reptile
contre (désolé pour ce terme assez vilain, ”famille”
7
Démon
serait plus judicieux, mais c’est le vocabulaire utilisé
8
Magicien
dans les jeux RPG).
9
Dragon
champ
idPerso
idMembre
nom
nbConnect
score
idRace
type
ENTIER
ENTIER
TEXTE
ENTIER
ENTIER
ENTIER
Pour les même raisons de non répétition on est donc amené à stocker le
numéro de la race plutôt que son nom. On a donc recours à une troisième
table races donnée ci-dessus. Elle contient deux champs : idRace une
clé primaire et race une chaı̂ne de caractères indiquant la race.
Nous verrons comment ces différentes tables peuvent être consultées
de manière simultanée un peu plus tard, mais intéressons-nous pour le
moment à quelques requêtes réalisables sur celle-ci.
Clauses
Pour éviter les doublons, on peut remplacer la commande SELECT...
par SELECT DINSTINCT...
E X H4 Écrire une requête affichant les différentes villes des membres du jeu.
268
L ES
BASES DE DONN ÉES
On peut ajouter en fin de requête quelques instructions que l’on appelle clause pour limiter ou ordonner les retours :
LIMIT nb : Limite le nombre d’enregistrements renvoyés aux nb premiers
LIMIT p,nb : Passe p enregistrements, et renvoie les nb suivants
ORDER BY champ ASC : Renvoie les enregistrements en les triant selon le champ champ par ordre croissant.
● On peut écrire DESC pour classer dans l’ordre décroissant.
● On peut classer selon plusieurs champs, il suffit alors de les
séparer par une virgule.
✍ Par exemple, pour avoir le nom du personnage de race Dragon qui
s’est le plus connecté (Kissle), on pourra taper :
SELECT nom FROM persos WHERE idRace = 9 ORDER BY nbConnect DESC ⤦
� LIMIT 1
E X H5 Donner les requêtes permettant d’afficher :
1. la liste des races par ordre alphabétique,
2. les 5 meilleurs scores et les noms des personnages qui l’ont réalisé.
Fonctions d’agrégation
Les fonctions d’agrégation sont des fonctions qui permettent d’effectuer un calcul statistique sur le résultat d’une requête. Nous en
utiliserons cinq :
fonction effet
COUNT
Renvoie le nombre d’enregistrements résultant d’une
requête.
SUM
AVG
Renvoient respectivement la somme et la moyenne
d’un champ sur un ensemble d’enregistrements
MAX
MIN
Renvoient respectivement la valeur maximale et la
valeur minimale d’un champ parmi les
enregistrements résultant d’une requête.
269
C HAPITRE H
✍ L’utilisation classique est la suivante :
SELECT fonction(champ) FROM table ...
Par exemple pour savoir le nombre moyen de connexions chez les Elf
(race 2) :
SELECT AVG(nbConnect) FROM persos WHERE idRace = 2
E X H6 En utilisant des fonctions d’agrégation, écrire des requêtes permettant de
renvoyer :
1. Le nombre de personnages au total.
2. Le score moyen des Magiciens.
3. Le nombre de personnages que possède le premier joueur inscrit sur le site
ainsi que son meilleur score.
5 - Les jointures
On peut schématiser la base de données à l’étape actuelle de la manière
suivante. Par convention :
● Les champs soulignés indiquent les clés primaires.
● Les flèches indiquent les clés étrangères.
membres
id (ENT)
nom (TXT)
prenom (TXT)
login (TXT)
pass (TXT)
inscription (TXT)
sexe (ENT)
adresse (TXT)
ville (TXT)
cp (ENT)
mail (TXT)
telephone (TXT)
dateNaiss (TXT)
270
persos
idPerso (ENT)
idMembre (ENT18)
nom (TXT)
nbConnect (ENT)
score (ENT)
idRace (ENT)
races
idRace (ENT)
race (TXT)
L ES
BASES DE DONN ÉES
Nous allons présenter comment réaliser des requêtes sur plusieurs
tables simultanément. Pour cela nous allons effectuer une jointure qui
peut être vue mathématiquement comme un produit cartésien, c’est-à-dire
que l’on va créer une table contenant toutes les juxtapositions possibles
entre un enregistrement d’une table et un enregistrement d’une autre table.
SELECT * FROM table1 JOIN table2 JOIN table3 ...
renvoie une jointure des différentes tables notée informatiquement table1 table2 ...
✍
Prenons l’exemple (fichier JOIN ) où l’on
dispose de deux tables contenant chacune un
champ :
table1 :
table2 :
lettre
nombre
A
1
B
2
C
On obtient le résultat de droite avec la requête :
SELECT * FROM table1 JOIN table2
table1table2
lettre nombre
A
1
A
2
B
1
B
2
C
1
C
2
Prenons un second exemple (fichier JOIN2 ) en considérant les deux
tables suivantes :
id
1
2
3
produits
désignation
Savon
Brosse à dents
Peigne
stock
prix
idp quantite
2,5 et 1
2
1,5
2
0
3
3
3
SELECT SUM(quantite * prix) FROM produits JOIN stock
l’appel renvoie
35 et non 14 comme on pourrait peut-être s’y attendre... L’explication se
trouve à la page suivante...
271
C HAPITRE H
SELECT
La requête * FROM produits JOIN stock
donne :
produits � stock
id désignation
prix idp quantite
1
savon
2.5
1
2
1
savon
2.5
2
0
1
savon
2.5
3
3
2
brosse à dents 1.5
1
2
2
brosse à dents 1.5
2
0
2
brosse à dents 1.5
3
3
3
peigne
3.0
1
2
3
peigne
3.0
2
0
3
peigne
3.0
3
3
��� � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � �� � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � ��
��� � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � �
table produits
table stock
La jointure obtenue, contient donc beaucoup trop d’enregistrements et
le résultat 35 provient du calcul suivant :
2 × 2,5 + 0 × 2,5 + 3 × 2,5 + 2 × 1, 5 + 0 × 1,5 + 3 × 1,5 + 2 × 3, 0 + 0 × 3, 0 + 3 × 3,0 = 35
Il faut donc préciser au moment de la jointure la condition de jointure :
le champ id de la table produits doit être le même que le champ idp de
la table stock. Cette contrainte s’exprime à l’aide de l’instruction ON :
SELECT * FROM table1 JOIN table2 ON conditions
Cette fois-ci, l’appel suivant renvoie bien 14 :
SELECT SUM(quantite * prix) FROM produits JOIN stock ON id = idp
Enfin, comme vous l’aurez remarqué dans l’exemple précédent, on
peut mixer la jointure avec une projection ou une sélection :
SELECT champ1 ,champs2 FROM table1 JOIN table2
ON conditions WHERE conditions
272
L ES
BASES DE DONN ÉES
E X H7 Donner les requêtes permettant d’afficher :
1. Les numéros de téléphone des dix Amiénois (habitants de la ville d’Amiens)
ayant obtenu le meilleur score.
2. Le nombre total de parties jouées par la gent féminine.
3. Le prénom et le nombre de personnages que possède le membre dont le login
est ”Frolit”
Si à la dernière question de l’exercice précédent, on tente d’afficher le
nom de famille du joueur en question plutôt que son prénom, on obtient le
message d’erreur suivant : "ambiguous column name : nom : ..." car les tables
membres et persos contiennent chacune un champ nom. Cette limitation
va facilement pouvoir être levée par le complément qui suit.
● Lorsque l’on retrouve le même nom de champ dans deux tables
différentes, pour ne pas qu’il y ait ambiguı̈té lors des requêtes, on
doit précéder le nom du champ de celui de la table en les séparant
par un point tel le ’s d’appartenance en anglais. Par exemple
membres.nom signifie le champ nom de la table membres.
● Les deux tables persos et races possèdent toutes deux un
champ nommé idRace, celui de la table perso est une clé
étrangère associée à la clé primaire de la table races. Lorsque
l’on fait la jointure, on fait donc naturellement :
SELECT ... FROM persos JOIN races ON persos.idRace = races.idRace
on peut simplifier cette requête en utilisant NATURAL JOIN :
SELECT ... FROM persos NATURAL JOIN races
E X H8 Donner les requêtes permettant d’afficher :
1. Les noms et prénoms des membres possédant un personnage de type Hobbit
(race 4).
2. La race du personnage qui s’est le plus connecté.
3. Le nom et la race des personnages que possède Juliette Thibodeau.
273
C HAPITRE H
6 - Ajouter, modifier, supprimer des enregistrements
Jusque là, nous nous sommes contentés de lire dans notre base de
données, voyons à présent comment la modifier. Petit conseil : pensez à
sauvegarder régulièrement votre base. On effectue souvent en début d’apprentissage des modifications non voulues et parfois irréversibles !
Ajouter des enregistrements
On ajoute un nouvel enregistrement à l’aide de la fonction INSERT
avec la syntaxe suivante :
INSERT INTO table(champ1 , champ2 , ....) VALUES (val1 , val2 ,...)
Si l’on souhaite ne renseigner que quelques champs, on précisera la
liste de ces champs entre parenthèses, dans le cas contraire cette information est facultative. Lors d’un enregistrement, les champs qui
ne sont pas renseignés sont alors
● soit complétés par la valeur indiquée dans la colonne défaut
lors de la création de la table (comme dans la capture ci-dessous).
● soit auto-incrémentés s’il s’agit d’une clé primaire numérique
(le moteur SQL attribue alors la valeur maxi + 1 où maxi est la
valeur maximale actuelle du champ)
Par exemple :
●
INSERT INTO races VALUES(20, ’Ours’)
Ajoute un enregistrement contenant le champ idRace à 20 et race
à ”Ours”.
●
INSERT INTO races VALUES(5, ’Drow’)
Retourne l’erreur UNIQUE constraint failed : races.idRace : INSERT
INTO races VALUES(5, ’Drow’) car le champ idRace est une clé primaire, il ne peut donc pas avoir deux enregistrements dont l’idRace
vaut 5.
●
INSERT INTO races(race) VALUES(’Orque’)
Ajoute un nouvel enregistrement dont le champ idRace vaut 21 et
race vaut ”Orque”.
274
L ES
●
BASES DE DONN ÉES
INSERT INTO persos(idMembre, nom, idRace) VALUES(123, ’Bob’, 10)
Renvoie l’erreur FOREIGN KEY constraint failed : INSERT INTO persos(idMembre, nom, idRace) VALUES(123, ’Bob’, 10) car idRace est une
clé étrangère et la valeur 10 n’a pas de correspondance dans la table
races
Sur cette capture de la table persos, on peut voir les deux clés étrangères et
les valeurs par défaut des champs nbConnect et score.
275
C HAPITRE H
Supprimer un enregistrement
La suppression d’un enregistrement se fait de la manière suivante :
DELETE FROM table WHERE conditions
Prenez garde de mettre à la suite du WHERE une condition identifiant
de manière unique (souvent une clé primaire) le ou les enregistrement(s) à supprimer ! On peut aussi supprimer une table entière :
DROP TABLE table
E X H9
1. Réaliser les instructions pour supprimer les deux nouvelles races créées
précédemment (Ours et Orque).
2. Que se passe-t-il si l’on tente de supprimer la race des magiciens :
DELETE FROM races WHERE idRace = 8
Modifier un enregistrement
Terminons cette partie en présentant la manière de modifier des enregistrements.
La modification de valeurs dans une table se fait de la manière suivante :
UPDATE table SET champ1 =..., champ2 = ... WHERE conditions
L’exercice suivant va vous permettre de réaliser quelques modifications sur la base. De manière à conserver la base initiale, n’enregistrez pas
ces changements une fois l’exercice terminé .
E X H10 Ecrire les requêtes SQL permettant de répondre aux demandes suivantes :
1. Une association de consommateurs s’est plainte du terme ”Nain” comme
nom de race. Écrire une requête pour changer ce nom en Gnome.
2. Madame Voisine s’est mariée, son nouveau nom est à présent Madame Riverain. Effectuer la modification.
3. Le personnage du nom de Zu (il n’y en a qu’un) vient de se connecter.
Mettre à jour le champ nbConnect
276
L ES
BASES DE DONN ÉES
Sous SQLite, il est impossible de réaliser un UPDATE avec une jointure. Imaginons que le membre dont le login est Lawen2005 ait obtenu un score de 420 175 avec son personnage Frey (il y a plusieurs
personnages portant ce nom) et que l’on souhaite mettre à jour ce
nouveau score. Voici une solution : avec un SELECT, on récupère l’id
du membre dont on connaı̂t le login. Cet id est réutilisé dans la condition du UPDATE.
UPDATE persos SET score = 420175 WHERE nom = "Frey" AND idMembre = ⤦
� (SELECT id FROM membres WHERE login = "Lawen2005")
Si le SELECT avait renvoyé plusieurs valeurs, on aurait alors pu
utiliser l’opérateur IN plutôt que le symbole d’égalité pour tester
l’appartenance à la table retournée.
E X H11 Pour la journée de la femme, augmenter les scores des femmes de 10% !
7 - Compléments sur les dates et chaı̂nes
Dans nos bases de données, nous aurons régulièrement à manipuler
des textes ainsi que des dates. Prenons le temps d’étudier plus en détails
ces derniers.
Les chaı̂nes de caractères
Quelques fonctions permettent de travailler avec les chaı̂nes :
fonction
LENGTH(ch)
effet
Renvoie la longueur de la chaı̂ne ch
REPLACE(c1,c2 ,c3 )
Renvoie la chaı̂ne c1 où toutes les occurrences c2 sont remplacées par la chaı̂ne c3
SUBSTR(ch,deb,lg)
Retourne la chaı̂ne de caractères extraite de
ch en partant de la position définie par deb
et sur la longueur lg. Si ce dernier paramètre n’est pas précisé, la chaı̂ne est
extraite jusqu’à la fin. Attention : la numérotation des caractères commence à 1 et
non 0 comme en Python
277
C HAPITRE H
C HAPITRE H
fonction
C HAPITRE
H ,c )
TRIM(c
fonction
1 2
LTRIM(c
TRIM(c
fonction1,c
1,c
2)
2)
RTRIM(c
,c
LTRIM(c
TRIM(c1,c
1 2)
2)
fonction
RTRIM(c1,c2 )
LTRIM(c
TRIM(c1,c2 )
RTRIM(c1,c2 )
LTRIM(c1,c2 )
cRTRIM(c
|| c 1,c2 )
effet
Renvoie
la chaı̂ne c1 où tous les caractères c2
effet
situés respectivement
aux deux
extrémités,
Renvoie
la chaı̂ne c1 où tous
les caractères
c2
effet
avant
et
après
c
ont
été
supprimés.
Si
c
situés respectivement
aux deux
extrémités,
Renvoie
la chaı̂ne 1c1 où tous
les caractères
2c2
effet
n’est
précisé
les
espaces
qui
avant
et
après
c1 ce
ontsont
été
supprimés.
Si c2
situéspas
respectivement
aux
deux
extrémités,
Renvoie
la chaı̂ne
c osont
ù tous les
caractères
c
sont
n’est
pas
précisé
espaces
qui
avantsupprimés.
et après
c1 1ce
ont été les
supprimés.
Si c2 2
situés
respectivement
aux
deux
extrémités,
sont
n’estsupprimés.
pas précisé ce sont les espaces qui
avant et
c1 ont été(juxtaposition)
supprimés. Si des
c2
Renvoie
la après
concaténation
sont supprimés.
1
2
n’est
pas
précisé
ce
sont
les
espaces
qui
chaı̂neslacconcaténation
de la barredes
c1 || c2
Renvoie
(juxtaposition)
1 et c2 . Le symbole
sont
supprimés.
verticale
s’obtient
avec
chaı̂neslacconcaténation
et c2en
. Leanglais)
symbole
de la barre
c1 || c2
Renvoie
(juxtaposition)
des
1(pipe
et
6
.
la
touchec1(pipe
AltcGr
verticale
en
anglais)
s’obtient
avec
chaı̂nes
et
.
Le
symbole
de
la
barre
2
c1 || c2
Renvoie
la concaténation
(juxtaposition) des
6 . s’obtient avec
la
touche
Alt Gren et
verticale
(pipe
anglais)
À noter que ces fonctions
utilisées
dans
la sélection
chaı̂nes c1peuvent
et c2 . Leêtre
symbole
de la
barre
et 6 .
la touche Alt Gr
ou dans
la projection.
verticale (pipe
en anglais)
s’obtient
À noter
que ces fonctions
peuvent
être utilisées
dansavec
la sélection
6 utilisées
.
la touche peuvent
Alt Gr etêtre
ou dans
la projection.
À noter
que ces fonctions
dans la sélection
ou dans la projection.
À noter que ces fonctions peuvent être utilisées dans la sélection
ou dans la projection.
✍
✍
✍
Ainsi,
Ainsi, DISTINCT nom FROM membres WHERE LENGTH(nom) = 12
SELECT
Ainsi,
SELECT DISTINCT nom FROM membres WHERE LENGTH(nom) = 12
renvoie les huit noms de familles composés de 12 lettres alors que DISTINCT nom FROM membres WHERE LENGTH(nom) = 12
✍ SELECT
Ainsi,
renvoie
les huit LENGTH(nom)
noms de familles
composés
de BY
12 LENGTH(nom)
lettres alors ASC
que SELECT DISTINCT
FROM membres
ORDER
SELECT
DISTINCT
FROM
WHERE
LENGTH(nom)
= 12 alors que
renvoie
les huit nom
noms
de membres
familles
composés
de BY
12 LENGTH(nom)
lettres
SELECT DISTINCT
LENGTH(nom)
FROM membres
ORDER
ASC
nous informe que les noms des membres contiennent entre 3 et 14
SELECT
DISTINCT
LENGTH(nom)
FROM
membres
ORDER
BY
LENGTH(nom)
ASC
renvoie
les huitque
noms
familles
composéscontiennent
de 12 lettresentre
alors 3que
lettres.
nous informe
les de
noms
des membres
et 14
SELECT
DISTINCTque
LENGTH(nom)
ORDER
BY LENGTH(nom)
lettres.
nous informe
les nomsFROM
des membres
membres
contiennent
entre ASC
3 et 14
lettres.
nous informe que les noms des membres contiennent entre 3 et 14
lettres.
E X H12 Pour des raisons de sécurité, on souhaite que les utilisateurs aient un
mot
de12
passe
de des
plusraisons
de 8 caractères.
Pour
automatiquement
un SMS
EX H
Pour
de sécurité,
onenvoyer
souhaite
que les utilisateurs
aientaux
un
utilisateurs
dont
le raisons
mot
passe
est Pour
troponcourt,
onautomatiquement
cherche
extraireun
lesSMS
numéros
mot
de12
passe
de des
plus
de 8de
caractères.
envoyer
EX H
Pour
de sécurité,
souhaite
que lesàutilisateurs
aientaux
un
de
de plus
ces
personnes
sous
forme
d’un
nombre
10 chiffres
(sans
les
utilisateurs
le mot
passe
est la
trop
court,
onautomatiquement
cherche à extraire
lesSMS
numéros
mottéléphone
de passedont
de
de 8de
caractères.
Pour
envoyer
un
aux
E X H12Écrire
Pour
des
raisons
de
sécurité,
on
souhaite
que
les
utilisateurs
aient
un
points).
la
requête
correspondante.
de
téléphonedont
de ces
personnes
sous
forme
d’un
10 chiffres
les
utilisateurs
le mot
de passe
est la
trop
court,
on nombre
cherche à extraire
les (sans
numéros
mot de passe
delaplus
de 8 caractères.
Pour envoyer automatiquement un SMS aux
points).
Écrire
requête
correspondante.
de téléphone
de ces
personnes
sous la forme d’un nombre à 10 chiffres (sans les
utilisateurs dont le mot de passe est trop court, on cherche à extraire les numéros
points). Écrire la requête correspondante.
de téléphone de ces personnes sous la forme d’un nombre à 10 chiffres (sans les
points).
Écrireen
la Python,
requête correspondante.
Comme
si dans un SUBSTR, le second paramètre est négatif,
on
parcourt
la chaı̂ne
depuis
fin.
Comme
en Python,
si dans
unlaSUBSTR,
le second paramètre est négatif,
on
parcourt
la
chaı̂ne
depuis
la
fin.
Comme en Python, si dans un SUBSTR, le second paramètre est négatif,
on parcourt la chaı̂ne depuis la fin.
Comme en Python, si dans un SUBSTR, le second paramètre est négatif,
on
parcourt la chaı̂ne depuis la fin.
278
278
278
278
L ES
BASES DE DONN ÉES
E X H13 En utilisant la remarque précédente, donner sur une première colonne
en sortie, les prénoms et noms des personnes membres habitant Vincennes et sur
une seconde colonne leur âge au 1er janvier 2018.
Le prochain exercice est important pour la suite, car il va nous permettre de mettre le champ de la date d’inscription dans un format standard compris par SQLite. Enregistrez les modifications dans la base une
fois les questions terminées.
E X H14 Le champ de la table perso n’est pas correctement formaté...C’est volontaire, je vous l’admets ! Les dates sont données sous forme de texte avec le
format j /m/a où j et m représentent les numéros du jour et du mois sur 1 ou 2
chiffres et a les deux derniers (ou le dernier) chiffre de l’année. Nous allons les
mettre sous forme AAAA − MM − JJ en quelques étapes.
1. Ajouter un 0 devant le numéro du jour si celui-ci ne comporte qu’un chiffre.
2. Ajouter les 0 éventuels devant le numéro du mois puis celui des années.
3. Inverser les valeurs des années avec les jours.
4. Remplacer les / par des -.
5. Ajouter ”20” devant le numéro de l’année (la première année étant 2005).
E X H15 Corriger de la même manière le champ dateNaiss où seuls le nombre
de chiffres pour coder le numéro du jour, l’ordre des informations et le séparateur
posent problème.
Enregistrez à présent la base de données corrigée ou
récupérez-la sur le site où l’exercice précédent est corrigé
pour étudier la fin du chapitre.
Les heures et les dates
Comme vous l’aurez peut-être remarqué, il n’existe pas de type date
ou heure, il nous faut donc les stocker sous forme de chaı̂nes de caractères.
Heureusement il existe quelques fonctions pour travailler sur les dates relativement facilement. Nous en présentons 3 :
279
C HAPITRE H
fonction
DATE(ch,m1,m2 ,...)
effet
Renvoie une chaı̂ne de caractères, au
format ’AAAA-MM-JJ’, représentant
la date ch éventuellement rectifiée
par les modificateurs m1 , m2 ....
TIME(ch,m1,m2 ,...)
Même effet que DATE, mais renvoie une
chaı̂ne de caractères au format
’HH:MM:SS’ représentant l’heure
DATETIME(ch,m1,m2 ,...)
Même effet que DATE, mais renvoie une
chaı̂ne de caractères au format
’AAAA-MM-JJ HH:MM:SS’
représentant la date et l’heure
À noter que :
● on peut demander en sortie des formats plus exotiques (numéro du
jour de l’année, jour de la semaine, numéro de semaine, ...) en utilisant alors la fonction STRFTIME, mais nous n’en parlerons pas ici.
D’ailleurs les 3 formats décrits ci-dessus à l’anglo-saxonne ont le gros
avantage de pouvoir être très facilement comparés : l’ordre sur les
chaı̂nes étant alors l’ordre chronologique.
● La chaı̂ne de caractères indiquant la date ou l’heure donnée en paramètre peut être à l’un des 3 formats précédents. On peut mettre
ou non les secondes et on utilise parfois le caractère T à la place de
l’espace pour séparer la date de l’heure.
● on dispose de la chaı̂ne ’now’ qui correspond à la date et l’heure
du système en UTC (Temps Universel Coordonné), c’est-à-dire pour
simplifier sur le méridien de Greenwich, il peut donc y avoir un
décalage d’une heure ou deux selon les saisons (nous y reviendrons
juste après)
Quelques exemples pour éclairer mes propos (au moment où j’écris
nous sommes le 17 février 2017 et il est 15 heures 10) :
SELECT TIME(’2017-01-01 17:32’)
SELECT DATE(’2017-01-01 17:32’)
SELECT DATETIME(’now’)
280
�→
17:32:00
�→
2017-01-01
�→
2017-02-17 14:10:47
L ES
BASES DE DONN ÉES
Revenons à présent sur les modificateurs. Il s’agit de mots-clés donnés
sous forme de textes qui vont avoir pour effet de modifier la date, en voici
quelques uns :
modificateur
’n days’
’n hours’
’n minutes’
’n seconds’
’n months’
’n years’
effet
Ajoute respectivement n jours, heures,
minutes, secondes, mois, années à la date
donnée, n étant un entier positif ou
négatif (voire flottant pour le nombre de
secondes). Le ’s’ à la fin du mot est facultatif si vous voulez être en accord avec
l’orthographe !
’start of month’
’start of year’
’start of day’
Modifie la date au premier jour à 00 :00 du
mois, de l’année ou respectivement du
jour indiqué par la date donnée.
’weekday n’
Avance la date au prochain nème jour de la
semaine, où n est un entier avec comme
convention que dimanche = 0, lundi = 1,
... L’heure est quant à elle inchangée.
’localtime’
Renvoie l’heure locale (corrigeant ainsi les
problèmes de fuseaux horaires)
Ces modificateurs peuvent s’enchaı̂ner, par exemple :
● Le deuxième dimanche du mois de février 2016 :
SELECT DATE(’2016-02-01’,’weekday 0’,’+7 days’)
● Le lundi de la semaine en cours :
SELECT DATE(’now’, ’+1 day’, ’weekday 1’, ’-7 day’)
● Le dernier jour du mois :
SELECT DATE(’now’, ’start of month’,’+1 month’, ’-1 day’)
E X H16 Le beaujolais nouveau se célèbre chaque année, le troisième jeudi de novembre. Écrire une requête qui donne la date de la sortie de ce vin d’exception
pour l’année en cours.
281
C HAPITRE H
E X H17 Avec la base de données contenant les dates correctement formatées,
écrire les requêtes permettant d’afficher les noms et prénoms des membres
1. inscrits en 2010 après le 14 mars ;
2. dont l’anniversaire tombe cette semaine.
8 - Les groupements
Terminons ce chapitre par un ultime complément sur les possibilités
de groupements en SQL.
L’instruction GROUP BY permet de regrouper les résultats en vue
d’utiliser une fonction d’agrégation par exemple
SELECT champ1 ,f (champ2 ),... FROM table GROUP BY champ1
On peut alors effectuer une restriction avec l’instruction HAVING :
SELECT champ1 ,f (champ2 ),... FROM table
GROUP BY champ1 HAVING condition
La fonction HAVING est presque similaire à la fonction WHERE,
mais on peut mettre des fonctions d’agrégations dans la condition.
✍ Pour avoir le nombre de membres par ville, on peut procéder ainsi :
SELECT ville, COUNT(id) FROM membres GROUP BY ville
Le score moyen et l’id des membres possédant deux personnages :
SELECT idMembre, AVG(score) FROM persos
� count(score) = 2
GROUP BY idMembre HAVING ⤦
et si on souhaite afficher les noms et prénoms, on complète avec une
jointure :
SELECT prenom, membres.nom, AVG(score) FROM persos JOIN membres ON ⤦
� persos.idMembre = membres.id GROUP BY idMembre HAVING ⤦
� count(score) = 2
282
L ES
BASES DE DONN ÉES
E X H18 À l’aide de groupements, trouver :
1. Le meilleur score de chaque race, ainsi que le nom de la race, le nom du
personnage et celui de son propriétaire.
2. Le mois de l’année où il y a eu le plus d’inscriptions.
3. Pour chaque Angloy (habitant d’Anglet), le nombre moyen de connexions.
9 - Aides pour les exercices
Aide pour l’exercice H2 Pour les personnes inscrites en mars, pensez que
le mois est entouré du caractère /.
Aide pour l’exercice H10 Pour modifier le nombre de connexions, pensez
que, comme en Python, la commande x = x + 1 ajoute 1 au champ x.
Aide pour l’exercice H14 Pour savoir si le numéro du jour comporte un
ou deux chiffres, on pourra regarder le 2e caractère du champ.
10 -
Solutions des exercices
Retour sur l’exercice H1 Voici des requêtes répondant à la demande :
1. Vous aurez remarqué que, pour les femmes le champ sexe est à 0
et 1 pour les hommes. (Ni voyez aucun sexisme ici mais plutôt un
moyen mnémotechnique !) Il y a 1504 réponses.
SELECT * FROM membres WHERE sexe = 0
2. Pas de difficulté pour cette requête qui retourne 26 enregistrements.
SELECT * FROM membres WHERE ville = "NICE"
3. Il suffit de combiner les deux conditions pour trouver les 9 personnes
répondant aux critères :
SELECT * FROM membres WHERE sexe = 0 AND ville = "NICE"
4. En s’appuyant sur l’astuce donnée dans l’aide, on trouve 23 personnes résidant dans la Somme.
SELECT * FROM membres WHERE cp/1000 = 80
283
Solutions des exercices
Aide pour l’exercice H1 On peut extraire les deux premiers chiffres du
code postal en calculant le quotient de la division par 1000.
C HAPITRE H
5. Pour extraire les 62 Bretons de la liste, on peut utiliser l’opérateur
OR :
SELECT * FROM membres WHERE cp/1000 = 22 OR cp/1000 = 29 OR ⤦
� cp/1000 = 35 OR cp/1000 = 56
On peut simplifier cette requête avec l’opérateur IN qui s’utilise comme en Python, la liste des valeurs étant placée entre
parenthèses
SELECT * FROM membres WHERE cp/1000 IN (22, 29, 35, 56)
Retour sur l’exercice H2
1. Si on souhaite que le login commence par c, le motif de recherche est
”c%” (190 réponses) :
SELECT * FROM membres WHERE login LIKE "c%"
2. De la même manière, la date de naissance doit terminer par 1976 (51
réponses) :
SELECT * FROM membres WHERE dateNaiss LIKE "%1976"
3. Comme le mois de mars est le troisième mois, on cherche le motif
”%/3/%”. (247 réponses)
SELECT * FROM membres WHERE inscription LIKE "%/3/%"
Il faut donc bien faire attention au format de la date, ici le mois de
mars est codé 3 et non 03, nous verrons un peu plus tard comment
gérer les requêtes sur les dates.
4. Pas de difficulté pour cette dernière requête (291 réponses)
SELECT * FROM membres WHERE mail LIKE "%@superrito.com"
Retour sur l’exercice H3
1. Son login est Farmay et son mot de passe Shoozae3oo
SELECT login, pass FROM membres WHERE prenom LIKE "Armand" AND ⤦
� nom LIKE "Lalonde"
284
L ES
BASES DE DONN ÉES
2. Il y a 231 numéros à afficher :
SELECT telephone FROM membres WHERE inscription LIKE "%/15"
3. Il y a 6 personnes dont le nom est Abril, dont un seul homme Léon
Abril, au 51 rue de Geneve - 80000 AMIENS.
SELECT prenom, nom, adresse, cp, ville FROM membres WHERE nom ⤦
� LIKE "Abril" AND sexe = 1
Retour sur l’exercice H4 Il y a 452 villes différentes
SELECT DISTINCT ville FROM membres
Retour sur l’exercice H5
1.
SELECT race FROM races ORDER BY race ASC
On obtient : Barbare, Dragon, Démon, Elf, Hobbit, ...
2.
SELECT nom, score FROM persos ORDER BY score DESC LIMIT 5
On obtient : Gildagil Gamwic (357 913 pts), Leest (357 777 pts), Drelg
(357 673 pts), Gnordwy (357 670 pts) et Sago (357 657 pts)
Retour sur l’exercice H6
1. En utilisant la fonction COUNT :
SELECT COUNT(*) FROM persos
Remarque : on peut aussi ne sélectionner qu’un champ (n’importe
lequel).
SELECT COUNT(idPerso) FROM persos
2. Les magiciens sont de la race 8, donc
SELECT AVG(score) FROM persos WHERE race = 8
On trouve un score moyen d’environ 180 409
3. Le premier inscrit sur le site a pour identifiant 1, on trouve qu’il
détient 2 personnages et que son meilleur score est 299300
SELECT MAX(score), COUNT(score) FROM persos WHERE idMembre = 1
285
Solutions des exercices
C HAPITRE
H
HAPITRE H
Retour sur l’exercice
l’exercice H7
H7
l’exercice
H7
1. Avec la
la requête
requêtesuivante
suivante:::
requête
suivante
SELECT telephone
telephone
telephone FROM
FROM
FROM membres
membres
membresJOIN
JOIN
JOIN
JOINpersos
persos
persos
persosON
ON
ON
ONid
id
id
id====idMembre
idMembre
idMembre
idMembre
WHERE
WHERE
WHERE
⤦⤦
SELECT
WHERE
⤦⤦
�
� ville
ville
ville === "AMIENS"
"AMIENS"
"AMIENS" ORDER
ORDER
ORDERBY
BY
BYscore
score
score
scoreDESC
DESC
DESC
DESCLIMIT
LIMIT
LIMIT
LIMIT10
10
10
10
on obtient
obtient bien
bien
bien 10
10
10enregistrements,
enregistrements,
enregistrements,cependant
cependant
cependant
cependantvotre
votre
votre
votre
esprit
esprit
esprit
esprit
vif
vif
vif
vif
aura
aura
aura
aura
remarqué
remarqué que
que
quepar
par
parexemple
exemple
exemplele
lelenuméro
numéro
numéro03.68.16.84.21
03.68.16.84.21
03.68.16.84.21
03.68.16.84.21
de
de
de
de
E.Déziel
E.Déziel
E.Déziel
E.Déziel
apapapapeeee
parait
parait en
en 44eee place
place
place(avec
(avec
(avec340957
340957
340957points)
points)
points)et
eteten
en
en
en10
10
10
10
place
place
place
place
(avec
(avec
(avec
(avec
266687
266687
266687
266687
points).
points). De
De même
même
mêmepour
pour
pourG.Jardine
G.Jardine
G.Jardinedont
dont
dontlelelenuméro
numéro
numéro
numéro
est
est
est
est
03.79.32.18.18
03.79.32.18.18
03.79.32.18.18
03.79.32.18.18
etet
etet
eee
eee
qui arrive
arrive en
en 222 et
et
et777 position
position
positionpuisque
puisque
puisqueces
ces
cesdeux
deux
deux
deuxjoueurs
joueurs
joueurs
joueurs
ont
ont
ont
ont
chacun
chacun
chacun
chacun
deux personnages
personnages
personnagesbien
bien
bienclassés.
classés.
classés.On
On
Onpeut
peut
peutdonc
donc
donc
donctenter
tenter
tenter
tenter
de
de
de
de
supprimer
supprimer
supprimer
supprimer
les doublons
doublons ainsi
ainsi
ainsi:::
SELECT DISTINCT
DISTINCT
DISTINCT telephone
telephone
telephoneFROM
FROM
FROMmembres
membres
membres
membresJOIN
JOIN
JOIN
JOINpersos
persos
persos
persos
ON
ON
ON
id
id
id
=⤦
⤦⤦
SELECT
ON
id
===⤦
�
� idMembre
idMembre
idMembre WHERE
WHERE
WHERE ville
ville
ville==="AMIENS"
"AMIENS"
"AMIENS"
"AMIENS"ORDER
ORDER
ORDER
ORDERBY
BY
BY
BYscore
score
score
score
DESC
DESC
DESC
DESC
LIMIT
LIMIT
LIMIT
LIMIT
10
10
10
10
Cette
Cette fois,
fois, surprise
surprise
surprise!!!E.Déziel
E.Déziel
E.Dézieldont
dont
dontlelelenuméro
numéro
numéro
numéroest
est
est
est03.68.16.84.21
03.68.16.84.21
03.68.16.84.21
03.68.16.84.21
est
est
est
est
passé
passé en
en première
première
première position,
position,
position,ce
ce
cequi
qui
quiest
est
estimpossible,
impossible,
impossible,
impossible,d’autant
d’autant
d’autant
d’autant
que
que
que
que
lele
lele
meilleur
meilleur joueur
joueur
joueur (03.33.17.58.09)
(03.33.17.58.09)
(03.33.17.58.09)aaalui
lui
luitotalement
totalement
totalement
totalementdisparu.
disparu.
disparu.
disparu.
En
En
En
En
fait,
fait,
fait,
fait,
ilililil
e
e ee e
ne devrait
devrait yy avoir
avoir
avoirque
que
quedeux
deux
deuxnouveaux
nouveaux
nouveauxclassés
classés
classés
classésen
en
en
en
999e9eeet
et
etet
10
10
10
10
place.
place.
place.
place.
La raison
raison de
de ce
ce
ce résultat
résultat
résultatsurprenant
surprenant
surprenantvient
vient
vientdu
du
du
dufait
fait
fait
faitque
que
que
que
lele
lele
DISTINCT
DISTINCT
DISTINCT
DISTINCT
s’exécute
s’exécuteavant
avant
avantles
les
lesautres
autres
autresinstructions
instructions
instructions(ORDER
(ORDER
(ORDER
(ORDER
etet
etet
LIMIT),
LIMIT),
LIMIT),
LIMIT),
ainsi,
ainsi,
ainsi,
ainsi,
comme
comme
comme
comme
le premier
premier score
score
scorede
de
deG.Jardine
G.Jardine
G.Jardineest
est
est317965,
317965,
317965,les
les
lesscores
scores
scores
scores
suivants
suivants
suivants
suivants
(comme
(comme
(comme
(comme
celui de
de 351839)
351839)
351839)sont
sont
sontignorés
ignorés
ignoréscar
car
carce
ce
cenuméro
numéro
numéroaaaadéjà
déjà
déjà
déjà
été
été
été
été
rencontré.
rencontré.
rencontré.
rencontré.
On peut
peut s’en
s’en sortir
sortir
sortir en
en
eneffectuant
effectuant
effectuantune
une
unerequête
requête
requête
requêtede
de
de
derequête
requête
requête
requête
: ::on
:on
on
on
efefefeffectue
fectue la
la première
première
première sélection
sélection
sélectionen
en
enclassant
classant
classantpar
par
par
parscores
scores
scores
scoresles
les
les
les
numéros
numéros
numéros
numéros
de
de
de
de
téléphones
téléphones mais
mais
mais sans
sans
sans limiter
limiter
limiteraux
aux
aux10
10
10premières
premières
premières
premièresréponses.
réponses.
réponses.
réponses.
Dans
Dans
Dans
Dans
lala
lala
table obtenue,
obtenue,
obtenue,on
on
oneffectue
effectue
effectuealors
alors
alorsune
une
unesélection
sélection
sélection
des
des
des
des
numéros
numéros
numéros
numéros
de
de
de
de
téléphone
téléphone
téléphone
téléphone
avec un
un DISTINCT,
DISTINCT,
DISTINCT,mais
mais
maiscette
cette
cettefois-ci
fois-ci
fois-ciles
les
lespremiers
premiers
premiers
premiers
numéros
numéros
numéros
numéros
rencontrés
rencontrés
rencontrés
rencontrés
sont les
les meilleurs
meilleurs
meilleursscores
scores
scoresdonc
donc
donccela
cela
celane
ne
nepose
pose
poseplus
plus
plus
plusde
de
de
de
problème.
problème.
problème.
problème.
Reste
Reste
Reste
Reste
alors àà ordonner
ordonner
ordonnerpar
par
parscores
scores
scorespuis
puis
puisàààlimiter
limiter
limiteraux
aux
aux
aux10
10
10
10premiers
premiers
premiers
premiers
enregisenregisenregisenregistrements
trements ::
SELECT
SELECT DISTINCT
DISTINCT
DISTINCT telephone
telephone
telephoneFROM
FROM
FROM(SELECT
(SELECT
(SELECT
(SELECTtelephone,
telephone,
telephone,
telephone,score
score
score
score
FROM
FROM
FROM
FROM
⤦⤦
⤦⤦
�
� membres
membres
membres JOIN
JOIN
JOIN persos
persos
persosON
ON
ONid
id
id====idMembre
idMembre
idMembre
idMembreWHERE
WHERE
WHERE
WHEREville
ville
ville
ville
==="AMIENS"
="AMIENS"
"AMIENS"
"AMIENS"
⤦⤦
⤦⤦
�
� ORDER
ORDER
ORDER BY
BY
BY score
score
score DESC)
DESC)
DESC)ORDER
ORDER
ORDER
ORDERBY
BY
BY
BYscore
score
score
scoreDESC
DESC
DESC
DESCLIMIT
LIMIT
LIMIT
LIMIT
10
10
10
10
On peut
peut simplifier
simplifier
simplifierla
la
larequête
requête
requêteen
en
enutilisant
utilisant
utilisantun
un
un
ungroupement.
groupement.
groupement.
groupement.
Cette
Cette
Cette
Cette
nonononotion est
est présentée
présentée
présentéeplus
plus
plusloin
loin
loindans
dans
dansle
lelechapitre.
chapitre.
chapitre.
2. Les femmes
femmes ont
ont
ontfait
fait
fait444569
569
569937
937
937parties
parties
parties:: :
SELECT
SELECT SUM(NbConnect)
SUM(NbConnect)
SUM(NbConnect) FROM
FROM
FROMmembres
membres
membres
membresJOIN
JOIN
JOIN
JOINpersos
persos
persos
persosON
ON
ON
ONid
id
id
id
====
idMembre
idMembre
idMembre
idMembre
⤦⤦
⤦⤦
�
� WHERE
WHERE
WHERE sexe
sexe
sexe === 000
286
L ES
BASES DE DONN ÉES
L ES
BASES DE DONN ÉES
3. Avec la même jointure que précédemment, on trouve que Martine
3 personnages
3. possède
Avec la même
jointure :que précédemment, on trouve que Martine
possède
3 personnages
:
SELECT prenom,
COUNT(idPerso)
FROM membres JOIN persos ON id = ⤦
� idMembre WHERE login = ’Frolit’
prenom, COUNT(idPerso) FROM membres JOIN persos ON id = ⤦
� idMembre WHERE login = ’Frolit’
SELECT
Retour sur l’exercice H8
l’identifiant
de la race
Hobbit
(4)membres
:
SELECT membres.nom,
prenom
FROM
JOIN persos ON id = ⤦
� idMembre WHERE idRace = 4
SELECT membres.nom, prenom FROM membres JOIN persos ON id = ⤦
� idMembre WHERE idRace = 4
Soit utiliser la dénomination de la race. Il faut alors faire une seconde
jointure
aveclala
table races :de la race. Il faut alors faire une seconde
Soit utiliser
dénomination
jointuremembres.nom,
avec la tableprenom
races
: membres JOIN persos ON id = ⤦
SELECT
FROM
� idMembre NATURAL JOIN races WHERE race = ’Hobbit’
membres.nom, prenom FROM membres JOIN persos ON id = ⤦
� idMembre NATURAL JOIN races WHERE race = ’Hobbit’
SELECT
2. On peut écrire la requête :
2. On
peut
écrire
la persos
requêteJOIN
:
SELECT
race
FROM
races ON persos.idRace = ⤦
� races.idRace ORDER BY nbConnect DESC LIMIT 1
SELECT race FROM persos JOIN races ON persos.idRace = ⤦
� races.idRace ORDER BY nbConnect DESC LIMIT 1
ou avec une jointure naturelle :
ou
avecrace
uneFROM
jointure
naturelle
SELECT
persos
NATURAL:
JOIN races ORDER BY nbConnect ⤦
� DESC LIMIT 1
SELECT race FROM persos NATURAL JOIN races ORDER BY nbConnect ⤦
� DESC LIMIT 1
3. Prêt pour une triple jointure ? En effet, nous avons besoin de la table
pourtriple
chercher
Juliette
la table
persos
pour
3. membres
Prêt pour une
jointure
? EnThibodeau,
effet, nousde
avons
besoin
de la table
trouver
nomchercher
du personnage
et enfin de
la table
table persos
races pour
pour
membreslepour
Juliette Thibodeau,
de la
connaı̂tre
l’intitulé
de
la
race
à
laquelle
il
correspond
:
trouver le nom du personnage et enfin de la table races pour
connaı̂tre
l’intitulé race
de laFROM
race membres
à laquelle
il correspond
SELECT persos.nom,
JOIN
persos ON id:
�
SELECT
�
�
�
= idMembre ⤦
NATURAL JOIN races WHERE membres.nom = ’Thibodeau’ AND ⤦
persos.nom,
race FROM membres JOIN persos ON id = idMembre ⤦
prenom
= ’Juliette’
NATURAL JOIN races WHERE membres.nom = ’Thibodeau’ AND ⤦
prenom = ’Juliette’
À noter qu’il est plus efficace (et plus clair) d’effectuer les ON
au
fur etqu’il
à mesure
desefficace
JOIN. (et plus clair) d’effectuer les ON
À noter
est plus
au fur et à mesure des JOIN.
287
287
Solutions des exercices
Retour
1. Il sur
fautl’exercice
penser àH8
préciser de quelle table on veut extraire le champ
afficher
les 946de
réponses,
on peut
directement
1. nom.
Il fautPour
penser
à préciser
quelle table
on soit
veututiliser
extraire
le champ
l’identifiant
de
la
race
Hobbit
(4)
:
nom. Pour afficher les 946 réponses, on peut soit utiliser directement
C HAPITRE H
Retour sur l’exercice H9
1. On peut supprimer les deux enregistrements en une fois en utilisant
le champ idRace :
DELETE FROM races WHERE idRace >= 20
Ou encore en listant les races à supprimer :
DELETE FROM races WHERE race IN ("Ours", "Orque")
2. Si l’on tente de supprimer les magiciens, on obtient l’erreur : FOREIGN KEY constraint failed : DELETE FROM races WHERE idRace
= 8 puisqu’il y a des personnages qui sont magiciens dans la table
persos et que la champ persos.idRace a été déclaré en tant que
clé étrangère.
Retour sur l’exercice H10
1. Pas de difficulté pour cette question :
UPDATE races SET race = "Gnome" WHERE race = "Nain"
2. Attention de bien préciser le sexe, sinon vous allez modifier 3 enregistrements, car il y a une Madame Voisine et 2 Monsieur Voisine !
UPDATE membres SET nom = "Riverain" WHERE nom = "Voisine" AND ⤦
� sexe = 0
3. Avec la remarque donnée dans l’aide :
UPDATE persos SET nbConnect = nbConnect + 1 WHERE nom = "Zu"
Retour sur l’exercice H11 On va dans un premier temps récupérer les id
de toutes les femmes via un SELECT, puis mettre à jour les scores correspondants. Voici une première solution :
UPDATE persos SET score = score * 1.1 WHERE
� FROM membres WHERE sexe = 0)
idMembre IN (SELECT id ⤦
Cependant, les scores censés être entiers, ne le sont plus forcément... La
fonction ROUND, comme en Python, va régler le problème :
UPDATE persos SET score = score * 1.1 WHERE
� FROM membres WHERE sexe = 0)
288
idMembre = IN (SELECT id ⤦
L ES
BASES DE DONN ÉES
Retour sur l’exercice H12 Voici une solution affichant les numéros de téléphone des 390 personnes concernées :
SELECT REPLACE(telephone,’.’,’’) FROM membres WHERE LENGTH(pass) <= 8
Retour sur l’exercice H13 Il nous faut extraire les 4 derniers caractères du
champ dateNaiss, ainsi on obtient :
SELECT prenom || ’ ’ || nom, 2018-SUBSTR(dateNaiss,-4) FROM membres ⤦
� WHERE ville LIKE ’vincennes’
Les 5 membres de Vincennes sont donc Linette Rodrigue (33 ans), André
Martin (47 ans), Vachel Charest (45 ans), Ambra Lanteigne (46 ans) et
Granville Bertrand (38 ans).
Noter que dans cet exemple, la conversion de la chaı̂ne représentant
l’année en nombre s’est faite de manière implicite. On peut forcer ce
chargement si besoin avec la fonction CAST valeur AS type
Retour sur l’exercice H14
1. Si le 2e caractère est un /, on ajoute le caractère ”0” (986 enregistrements doivent être modifiés)
UPDATE membres SET inscription = "0" || inscription WHERE ⤦
� SUBSTR(inscription,2,1) = "/"
2. De la même manière, pour les mois (2219 modifications) :
UPDATE membres SET inscription = SUBSTR(inscription,1,3) || "0" ⤦
� || SUBSTR(inscription,4) WHERE SUBSTR(inscription,5,1) = "/"
Pour les années (1194 modifications) :
UPDATE membres SET inscription = SUBSTR(inscription,1,6) || "0" ⤦
� || SUBSTR(inscription,7) WHERE LENGTH(inscription) = 7
3. Avec SUBSTR, l’inversion ne pose pas de difficulté :
UPDATE membres SET inscription = SUBSTR(inscription,7,2) || ⤦
� SUBSTR(inscription,3,4) || SUBSTR(inscription,1,2)
4. Pas de problème non plus pour transformer les symboles / en - avec
la fonction REPLACE :
UPDATE membres SET inscription = REPLACE(inscription,’/’,’-’)
289
Solutions des exercices
C HAPITRE H
C HAPITRE H
5. Pour l’ajout des centaines d’années :
5. UPDATE
Pour l’ajout
desSET
centaines
d’années
:
membres
inscription
= "20"
|| inscription
UPDATE membres SET inscription = "20" || inscription
Retour sur l’exercice H15 En reprenant les idées de l’exercice précédent,
on obtient
requêtes
: En reprenant les idées de l’exercice précédent,
Retour
sur ces
l’exercice
H15
on
obtient
ces requêtes
:
-- Ajout
éventuel
d’un zéro
UPDATE membres SET dateNaiss = "0" || dateNaiss WHERE LENGTH(dateNaiss)=9;
-- Ajout
éventuel
zéro de chaine
Mise de
l’annéed’un
en début
UPDATE membres SET dateNaiss = "0"
|| dateNaiss WHERE||
LENGTH(dateNaiss)=9;
SUBSTR(dateNaiss,7,4)
⤦
-- Mise
de l’année en début de
� SUBSTR(dateNaiss,3,4)
|| chaine
SUBSTR(dateNaiss,1,2);
UPDATE
membres SET
dateNaiss
= SUBSTR(dateNaiss,7,4) || ⤦
-- Remplacement
du /
par � SUBSTR(dateNaiss,3,4)
SUBSTR(dateNaiss,1,2);
UPDATE
membres SET dateNaiss ||
= REPLACE(dateNaiss,"/","-")
-- Remplacement du / par UPDATE membres SET dateNaiss = REPLACE(dateNaiss,"/","-")
À noter que :
● on
peut
À noter
que
: enchaı̂ner les requêtes en les séparant par un pointvirgule
; enchaı̂ner les requêtes en les séparant par un point● on peut
●virgule
on peut
; placer des commentaires en les précédant de deux tirets
ou,
entre un /* et en
*/.les
Cette
deuxième
● on peutcomme
placer en
desC,commentaires
précédant
de solution
deux tipermet
en
particulier
de
mettre
un
commentaire
sur
plusieurs
rets ou, comme en C, entre un /* et */. Cette deuxième solution
lignes.
permet en particulier de mettre un commentaire sur plusieurs
lignes.
Retour sur l’exercice H16 Voici une possibilité :
Retour
sur l’exercice
H16 of
Voici
une possibilité
SELECT DATE(’now’,
’start
year’,’10
months’, : ’weekday
4’, ’+14 days’)
SELECT DATE(’now’, ’start of year’,’10 months’, ’weekday 4’, ’+14 days’)
Partant de la date actuelle, on cherche le premier janvier de l’année en
cours,
le date
1er novembre,
puis
le 1er le
jeudi
de novembre
et l’année
enfin le en
3e
Partantpuis
de la
actuelle, on
cherche
premier
janvier de
jeudi
novembre
14 jours plus
tard.
cours,de
puis
le 1er novembre,
puis
le 1er jeudi de novembre et enfin le 3e
jeudi de novembre 14 jours plus tard.
Retour sur l’exercice H17
Retour
l’exercice
H17recours aux fonctions présentées dans cette par1. Icisur
inutile
d’avoir
Les dates
étantrecours
correctement
formatées,
l’ordre alphabétique
est
1. tie.
Ici inutile
d’avoir
aux fonctions
présentées
dans cette paraussi
l’ordre
chronologique.
Ainsi
pour
obtenir
la
liste
des
196
pertie. Les dates étant correctement formatées, l’ordre alphabétique est
sonnes
concernées
(classées par
ordre
d’inscription)
: des 196 peraussi l’ordre
chronologique.
Ainsi
pour
obtenir la liste
sonnes
concernées
par FROM
ordremembres
d’inscription)
:
SELECT prenom,
nom, (classées
inscription
WHERE inscription
�
SELECT
�
�
�
290 290
> ⤦
’2010-03-14’ AND inscription < ’2011-01-01’ ORDER BY ⤦
prenom, nom,ASC
inscription FROM membres WHERE inscription > ⤦
inscription
’2010-03-14’ AND inscription < ’2011-01-01’ ORDER BY ⤦
inscription ASC
L ES
BASES DE DONN ÉES
2. Pour cette question, on peut s’inspirer de l’exemple sur les modificateurs donnant le lundi de la semaine en cours. Pour comparer les
dates il ne faut pas tenir compte des années, d’où la présence du
SUBSTR. Voici une solution :
SELECT
�
�
�
prenom, nom, dateNaiss FROM membres WHERE ⤦
SUBSTR(dateNaiss,6) >= SUBSTR(DATE(’now’, ’+1 day’, ⤦
’weekday 1’, ’-7 day’),6) AND SUBSTR(dateNaiss,6) < ⤦
SUBSTR(DATE(’now’, ’+1 day’, ’weekday 1’),6)
1. Avec la table persos, on peut effectuer un regroupement sur le
champ idRace :
SELECT MAX(score), idRace FROM persos GROUP BY idRace
Si on souhaite en plus écrire le nom de la race, on réalise une jointure :
SELECT MAX(score), race FROM persos NATURAL JOIN races GROUP BY ⤦
� idRace
et avec le nom des propriétaires :
SELECT MAX(score), persos.nom, race, prenom, membres.nom FROM ⤦
� persos NATURAL JOIN races JOIN membres ON id = idMembre ⤦
� GROUP BY idRace
2. On peut déjà afficher le nombre d’inscrits en fonction des mois de
l’année :
SELECT SUBSTR(inscription,6,2), count(id) FROM membres GROUP BY ⤦
� SUBSTR(inscription,6,2)
En ordonnant, on trouve que c’est en juillet, qu’il y a eu le plus d’inscriptions (278).
SELECT SUBSTR(inscription,6,2), count(id) FROM membres GROUP BY ⤦
� SUBSTR(inscription,6,2) ORDER BY count(id) DESC LIMIT 1
291
Solutions des exercices
Retour sur l’exercice H18
C HAPITRE H
3. Les 4 habitants d’Anglet, C. Harcourt, D. Jeanne, B. Platt et M. Quennel, se sont connectés en moyenne 1293, 1289, 1202 et 1606,5 fois. On
peut soit utiliser un WHERE :
SELECT membres.nom, prenom, AVG(NbConnect) FROM membres JOIN ⤦
� persos ON membres.id = persos.idMembre WHERE ville LIKE ⤦
� "ANGLET" GROUP BY idMembre
soit un HAVING :
SELECT membres.nom, prenom, AVG(NbConnect) FROM membres JOIN ⤦
� persos ON membres.id = persos.idMembre GROUP BY idMembre ⤦
� HAVING ville LIKE "ANGLET"
Remarquons quand même que la première solution est environ deux
fois plus rapide que la seconde, car le filtre d’être Angloy est effectué
avant le groupement.
292
Objectif
Programmer The Hardest Game
Programmation Python
Lire dans une base de données avec Python
Propriétés en programmation objet
Gestion des masques binaires dans Pygame
Algorithmique
Intersection de disques et de carrés
Chapitre I
O-But
Installons-nous tranquillement sur une place ombragée d’un village
du sud de la France aux abords d’un terrain de pétanque au son des cigales... Notre prochain jeu est un remake de The World’s Hardest Game, un
jeu qui se revendique tout simplement être le jeu le plus dur au monde !
L’objectif du jeu d’origine est de déplacer le carré pour le faire arriver
dans une zone de sécurité représentée en vert sans se faire toucher par les
boules.
293
C HAPITRE I
Dans notre version, vous piloterez une araignée traversant un terrain
de pétanque pour rejoindre sa toile. Trois types d’embûches pourront être
présentes :
● les bouteilles de breuvage anisé : elles sont fixes et posées au sol
● les boules de pétanque : elles se déplacent par translation
● les cochonnets : ils se déplacent par rotation.
OBUT
1 - Chargement des niveaux
Comme cela est souvent d’usage en réalité, nous
allons utiliser une base de données comme fiches
pour stoker facilement les informations sur le niveau,
l’accès aux données étant facilité par les requêtes
SQL. On dispose de 4 tables, voici la première :
niveaux
id : integer
plateau : text
image : text
La table niveaux recense les informations d’un niveau :
● id est une clé primaire indiquant le numéro du niveau.
● plateau est une chaı̂ne de caractères composée de ’0’ (case vide)
et de ’1’ (case noire) permettant de représenter le niveau. Le terrain
de jeu étant ainsi ”quadrillé” par des cases de dimensions 48 × 48
pixels. Le caractère | (qui se prononce "pipe" à l’anglaise, c’est à
dire tuyau) permet d’indiquer la fin de ligne. Enfin le caractère ’2’
294
O-B UT
indique la position de l’araignée Gibsy et le ’3’ le but à atteindre. Par
exemple,
est représenté par "111111|121301|101101|100001|111111".
● image est une chaı̂ne de caractères qui indique le nom d’un fichier
à charger pour personnaliser le niveau. Si elle n’est pas renseignée,
le fond est beige et les cases pleines sont noires.
Le module sqlite3 présent par défaut dans Python permet d’interagir avec une base de données au format sqlite. La structure d’un
programme utilisant ce module est typiquement :
1
2
3
import sqlite3
conn = sqlite3.connect('base.db')
c = conn.cursor()
# Ouverture d'une connexion
# Création d'un curseur
4
5
c.execute(votre requete) # Exécution d'une requête
6
7
8
conn.commit()
conn.close()
# Enregistrement des changements (si nécessaire)
# Fermeture de la connexion
L’interrogation d’une base de données se passe en plusieurs temps,
analysons les lignes du code ci-dessus :
2. ouverture d’une connexion,
3. création d’un curseur que nous interrogerons lors des requêtes,
7. si besoin on enregistre les modifications dans la base pour informer d’autres utilisateurs éventuels,
8. en fin d’utilisation, on ferme la connexion.
La requête de la ligne 5. est donnée sous forme d’une chaı̂ne de
caractères dans le langage SQL qui a été présenté dans le chapitre
précédent.
295
C HAPITRE I
✍ Si vous avez besoin de paramètres dépendants de variables Python,
vous pouvez générer une chaı̂ne de caractères, par exemple :
niv = 3
c.execute('SELECT * FROM niveaux WHERE id = '+str(niv))
Cependant, cette méthode est une fausse bonne idée, en particulier
s’il y a des interactions possibles avec l’utilisateur. Imaginez par
exemple que vous demandiez le nom du joueur pour le stocker dans
la table joueurs et qu’un utilisateur saisisse comme nom celui de
≪ DROPTABLE joueurs ≫... Une méthode plus fiable est d’utiliser le
caractère ” ?” et d’ajouter un tuple contenant les variables :
niv = (3,) # Un tuple ici à un élément.
c.execute('SELECT * FROM niveaux WHERE id = ?', niv)
ou avec plusieurs variables :
var = (3,100)
c.execute('SELECT * FROM niveaux WHERE id = ? AND largeur > ?', var)
Il existe différentes solutions pour récupérer le résultat de notre requête
une fois celle-ci effectuée :
On a exécuté la requête suivante :
c.execute('SELECT * FROM niveaux')
● c.fetchone() : renvoie un tuple contenant les colonnes du premier enregistrement retourné par la requête.
● c.fetchall() : même effet mais renvoie une liste de tuples correspondant à tous les enregistrements renvoyés par la requête.
● On peut aussi simplement parcourir les enregistrements à l’aide d’une
simple boucle for :
for ligne in c.execute('SELECT * FROM niveaux') :
print(ligne)
Dans ce dernier cas, ligne prend tour à tour chaque valeur des enregistrements sous forme d’un tuple.
296
O-B UT
E X I1 La classe Niveau : pour instancier un objet, on donnera le numéro du
niveau (valide), par exemple niv = Niveau(1)
1. Réaliser un programme qui décrit la classe dont les attributs sont :
● nbl, nbc : le nombre de lignes et de colonnes du niveau ;
● plateau : la chaı̂ne de caractères présente dans la base de données correspondant au niveau ;
● fond : une surface correspondant à l’image si elle est renseignée ou à un
fond généré avec des carrés blancs et noirs.
2. Ajouter une méthode mur(x,y) qui renvoie un booléen pour indiquer la
présence d’un mur sur le plateau de jeu au point de coordonnées (x,y)
donné en pixels.
Pour la suite du jeu, nous aurons besoin d’une classe générique,
nommée Obstacle, que nous dériverons (c’est-à-dire que nous allons
créer des classes héritées de celle-ci) en différentes classes selon les types
d’embûches.
E X I2 Écrire une classe Obstacle
1. Elle possède comme attributs x et y, les coordonnées du centre de l’obstacle
ainsi que image une image représentant l’obstacle. Ces 3 valeurs seront
placées en paramètre dans le constructeur.
2. Elle possède la méthode dessine qui, comme son nom l’indique, dessine
l’obstacle sur la fenêtre de jeu à l’endroit donné par les attributs x et y.
Intéressons-nous à la première classe dérivée, la classe
Bouteille. Les objets bouteilles sont fixes, ils sont instanciés
ainsi Bouteille(x,y,a) où (x, y) sont les coordonnées du
centre et a l’angle de rotation en degré. (0° : Bouteille debout).
Pour une meilleure qualité d’image lors de la rotation, l’image
bouteille.png est 10 fois plus grande que nécessaire. À vous
de la redimensionner une fois la rotation effectuée.
E X I3 Écrire la classe Bouteille correspondant à la description donnée cidessus et déclarant les attributs et méthodes.
Sur le même principe, on définit la classe Boule pour
représenter les boules de pétanque qui se déplacent par
⃗
translation de vecteur v(vx,vy).
297
C HAPITRE I
E X I4 Écrire la classe Boule correspondant à la description donnée ci-dessus.
L’instanciation se faisant ainsi : Boule(x, y, vx, vy).
E X I5 Ajouter une méthode update qui met à jour les attributs x et y et appelle
la méthode dessine de la classe Obstacle.
1. À la classe Boule (pour le moment, on ne gère pas les dépassements de
terrain). Tester cette méthode en créant une boule de pétanque arbitraire
sur le niveau 1.
2. À la classe Bouteille, qui se contentera d’afficher celle-ci, pour harmoniser les appels.
Reste à s’occuper de la classe Cochonnet. Ces obstacles se
déplacent par rotation autour du point Ω(x0,y0) avec une
vitesse angulaire a (angle dont le cochonnet tourne à chaque
étape de temps) et un angle initial a0. On note enfin r la
distance ΩCt (Ct étant la position du cochonnet à l’instant t).
Ct+1
x = r × cos(angle) + x0
a
y = r × sin(angle) + y0
Ct
r
Ω
angle
a0
(x0,y0)
E X I6 Créer une classe Cochonnet qui possède les attributs nécessaires aux
calculs et la méthode update. Les objets de cette classe seront instanciés par
l’appel Cochonnet(x0,y0,a,a0,r).
E X I7 Terminer le code du projet, pour que sur le terrain, s’anime une liste d’obstacles de manière automatique. Pour le moment, on se contente de créer manuellement une liste d’obstacles pour tester.
298
O-B UT
O-B UT
Sur le même principe que pour l’ensemble des niveaux, chaque type
d’embûche possède sa propre table. Ces tables sont présentées ici :
Sur le même principe que pour l’ensemble des niveaux, chaque type
d’embûche possède sa propre table. Ces tables sont présentées ici :
cochonnets
boules
bouteilles
id : integer
id : integer
id : integer
niv
: integer
cochonnets
niv
: integer
boules
niv
: integer
x0
bouteilles
id : integer
x
id: integer
: integer
x
:
integer
y0
id : integer
niv: integer
: integer
y
: integer
niv
: integer
y
: integer
a
niv
: integer
x0: real
: integer
vx
: integer
x : integer
angle
: integer
a0
x : integer
y0 : real
integer
vy
: integer
y : integer
rayon
y : integer
a : real : real
vx : integer
angle : integer
a0 : real
vy : integer
rayon : real
Pour chacune de ces tables, id est la clé primaire alors que niv est une
clé étrangère indiquant le numéro du niveau où se trouve cette embûche.
Pour chacune de ces tables, id est la clé primaire alors que niv est une
clé étrangère indiquant le numéro du niveau où se trouve cette embûche.
Il est possible dans un programme Python d’identifier le résultat
d’une requête SQLite par le nom des champs plutôt que par leurs
indices.
Il suffitdans
d’ajouter
la ligne suivante
au moment
de la
Il
est possible
un programme
Python
d’identifier
le création
résultat
de la connexion
:
d’une
requête SQLite
par le nom des champs plutôt que par leurs
indices.
Il suffit d’ajouter
la ligne suivante au moment de la création
conn.row_factory
= sqlite3.Row
de la connexion :
On peut alors accéder aux informations plus simplement par le nom
conn.row_factory = sqlite3.Row
du champ. Par exemple le script suivant affiche la liste des images de
chaque
:
On peutniveau
alors accéder
aux informations plus simplement par le nom
du
champ.
Par
exemple
le script suivant affiche la liste des images de
conn = sqlite3.connect("base.db3")
conn.row_factory
=
sqlite3.Row
chaque niveau :
c = conn.cursor()
connligne
= sqlite3.connect("base.db3")
for
in c.execute('SELECT * FROM niveaux') :
conn.row_factory
= sqlite3.Row
print(ligne['image'])
c
= conn.cursor()
conn.commit()
for
ligne in c.execute('SELECT * FROM niveaux') :
conn.close()
print(ligne['image'])
conn.commit()
conn.close()
E X I8 Écrire une fonction qui reçoit le numéro d’un niveau et renvoie une liste
d’objets Obstacles en allant chercher les informations dans la base de données.
E X I8 Écrire une fonction qui reçoit le numéro d’un niveau et renvoie une liste
d’objets Obstacles en allant chercher les informations dans la base de données.
299
299
C HAPITRE I
2 - Déplacement des boules - Propriétés
Les bouteilles étant fixes et les cochonnets placés de manière à ne pas
entrer en collision avec le décor, intéressons-nous au déplacement des
boules de pétanque, qui, dans notre cas, ne se déplaceront que verticalement et horizontalement.
Pour gérer les collisions avec le décor, nous allons avoir besoin d’accéder
à l’attribut plateau du niveau en cours. Or comme cela a été évoqué dès
la présentation de la programmation objet dans le projet Cupidon, cela est
contraire au principe d’encapsulation.
En effet, on comprend assez aisément que la modification
directe de l’attribut d’un objet peut avoir des répercussions
sur d’autres attributs ou même sur des attributs d’autres
objets. Prenez l’exemple de Mario qui mange un superchampignon et grandit, il va alors falloir vérifier l’endroit où
Mario se trouve pour éviter qu’il ne défonce tout le décor !
Pour la lecture, même si cela semble moins problématique, l’accès direct à un attribut peut ne pas être pertinent. Prenons un exemple : vous
avez un objet Joueur2 qui contient un attribut pos indiquant la position de ce joueur sur le plateau d’un jeu en réseau. Lorsque vous voulez
accéder à cette information, il faut prévoir d’effectuer une requête sur le
réseau pour mettre à jour cette information avant de la renvoyer. Donc autant prévoir une fois pour toute que la requête s’effectuera à chaque fois
que l’on demande la valeur de pos.
Nous allons avoir recours à des getters (ou accesseurs en français)
pour lire les informations et des setters (ou mutateurs) pour les modifier.
Pour comprendre, restons sur class Boule() :
l’exemple de notre boule de
def __init__(self,x,y,vx,vy)
pétanque dont on simplifie la
self.vx, self.vy = vx, vy
self.x, self.y = x, y
déclaration. Créons alors l’instance
B placée en (20,50) et qui avance B = Boule(20, 50, 2, 0)
selon le vecteur vitesse (−2,0).
class Boule() :
...
def recup_abscisse(self) :
return self.x
300
:
À présent au lieu de lire l’attribut avec
abscisse = B.x, nous allons créer
une méthode et nous ferons l’appel
abscisse = B.recup abscisse().
O-B UT
Boule() :
De même, on remplacera class
...
l’appel B.x = 30, par l’appel
def change_abscisse(self,
self.x = x1
B.change abscisse(30).
x1) :
Nous venons de fabriquer un getter (recup abscisse) et un setter
(change abscisse) pour l’attribut x... Bon, pas sûr que je vous ai totalement convaincu et je vous comprends : d’une part c’est bien plus lourd
dans la déclaration de la classe, mais aussi dans l’usage dans la suite du
code. Pour tenter de vous convaincre un peu plus, voici quelques arguments sur la nécessité de faire ainsi parfois. Pensez qu’un jour vous créerez
peut-être des bibliothèques avec des objets que d’autres utiliseront, vous
aurez parfois besoin de conserver non accessibles ou non modifiables certains attributs pour assurer le bon fonctionnement de votre classe. D’autre
part, en passant par un setter, vous pourrez facilement débugger un code
Python, en surveillant les modifications de tel ou tel attribut.
En C++ ou en Java, c’est au moment de la création de la classe que l’on
précise si les attributs (et méthodes) sont publics ou privés. En Python,
la philosophie est différente : on s’autorise à lire et modifier les attributs
comme on le faisait dans les chapitres précédents, tant que cela ”ne risque
rien”. Python est basé sur la confiance des usagers ; ainsi la convention
est d’ajouter un underscore (caractère ) avant le nom d’un attribut ou
d’une méthode pour préciser à l’utilisateur que son usage direct n’est pas
souhaité. Pour accéder aux attributs sensibles ou que l’on veut protéger,
on aura recours à des propriétés.
S’il nous prend l’envie de sécuriser l’attribut x, on va procéder ainsi :
1
Dans la console :
class Boule() :
2
3
4
5
def __init__(self, x, y, vx, vy) :
self.vx, self.vy = vx, vy
self._x, self.y = x, y
8
9
def _get_x(self) :
print('il est passé par ici')
return self._x
12
13
def _set_x(self, x1) :
print('il repassera par là')
self._x = x1
5
6
8
9
10
11
14
15
4
7
10
11
2
3
6
7
1
x = property(_get_x, _set_x)
12
>>> B = Boule(20, 50, 2, 0)
>>> B.x
il est passé par ici
20
>>> B.x = 10
il repassera par là
>>> B.x
il est passé par ici
10
>>> B.x = B.x + 5
il est passé par ici
il repassera par là
301
C HAPITRE I
Quelques explications sur ce que l’on vient de réaliser : au lieu de créer
un attribut x comme précédemment, on a créé l’attribut x pour indiquer
que l’on ne souhaite pas y accéder depuis l’extérieur. Tout cela n’est encore
une fois qu’une histoire de convention et d’habitude ; si vous voulez appeler cette variable lavariablexquejeprotege vous pouvez ! Ensuite
on a aussi fait précéder les noms de nos getters et setters d’un underscore
pour préciser qu’à priori, on ne fera pas d’appel depuis l’extérieur à ces
fonctions. Ici on a ajouté des print pour que vous compreniez ce qui se
passe au moment des appels.
En fin de déclaration de la classe on crée une propriété que l’on nomme
x et dont on précise le comportement des contrôles d’accès.
x=property(f 1, f 2 , f 3 , txt) définit une propriété x telle que :
● on appelle la fonction f 1 lorsqu’on lit la valeur de x,
● on appelle la fonction f 2 lorsqu’on modifie la valeur de x,
● on appelle la fonction f 3 lorsqu’on détruit la valeur de x,
● on renvoie le docstring txt lorsqu’on invoque l’aide de x.
Chacun de ces paramètres est optionnel. Si par exemple vous ne
renseignez qu’un seul paramètre et que vous tentez de modifier x,
vous déclencherez une erreur.
Une fois cette propriété déclarée, l’usage est totalement transparent
pour l’utilisateur de la classe, comme vous pouvez le constater dans la
capture précédente de la console. Quand, à la ligne 2, on demande la valeur de B.x, l’accesseur get x est exécuté. Quand, à la ligne 5, on modifie
la valeur de B.x, c’est le mutateur set x qui l’est cette fois-ci. Pour l’appel de la ligne 10, les deux fonctions sont appelées : une première fois on
demande la valeur de x, puis on ajoute 5, et enfin on modif ie la valeur de
x.
Encore un exemple : si on souhaite que
_set_x(self, x1) :
lorsque l’abscisse de la boule dépasse 500 elle defself._x
= x1
revienne à 0, on peut modifier le setter comme
if self._x > 500 :
ci-contre et ainsi ne plus jamais avoir à se sou- self._x = self._x-500
cier des sorties d’écran !
302
O-B UT
Voyez donc l’usage des propriétés comme une nouvelle opportunité
qui vous est offerte et non comme une contrainte. Cette ultime remarque
me permet de clore la parenthèse puisque vous savez à présent encapsuler de manière propre vos objets. Il existe enfin une manière plus concise
de procéder en utilisant les décorateurs de Python. Nous n’aurons pas le
temps de la présenter ici car nos boules de pétanque sont toujours immobiles pour l’heure !
E X I9 Modifier la méthode update de la classe Boule pour gérer les rebonds
sur le décor. Il ne semble pas utile d’avoir recours aux propriétés à priori.
3 - Déplacement du personnage - La classe Mask
À présent notre décor est planté et les embûches sont mobiles. Nous
allons donc nous atteler au déplacement de Gibsy l’araignée.
gibsy0.png
gibsy1.png
gibsy2.png
gibsy3.png
Dans l’exercice précédent, nous avons géré assez facilement la collision d’une forme ronde avec des lignes horizontales ou verticales. Pour
d’autres formes...
● La collision entre deux cercles est facile aussi à tester.
Avec les notations de la figure,
il suffit de regarder si :
AB < r1 + r2
B
r2
A r1
● S’il s’agit de deux formes rectangulaires, c’est un peu plus difficile,
mais heureusement Pygame sait faire le travail pour nous à l’aide
303
C HAPITRE I
des méthodes contains, collidepoint, colliderect ou encore collidelist... que nous avons déjà présentées dans le chapitre Cupidon.
● Si on souhaite tester la collision entre un carré et un cercle (c’est par
exemple le cas de Gibsy et d’une boule de pétanque), ce n’est pas
simple non plus et cette fois-ci, Pygame ne vient pas à notre secours.
La fonction suivante répond à la question :
def collision(x1, y1, r, x2, y2, c) :
deltaX, deltaY = abs(x1-x2), abs(y1-y2)
if deltaX > c//2+r : return False
if deltaY > c//2+r : return False
if deltaX <= c//2 : return True
if deltaY <= c//2 : return True
dx, dy = deltaX-c//2, deltaY-c//2
return dx**2 + dy**2 <= r**2
Avec pour paramètres : (x1, y1) les coordonnées du centre d’un
cercle de rayon r et (x2, y2) les coordonnées du centre d’un carré
de côté c.
E X I10 Écrire un programme (ou le télécharger, là n’est pas le plus important)
qui permet de piloter un carré et un disque de côté et de rayon donnés. Afficher
s’il y a ou non collision, en précisant quel return a terminé la fonction afin de
comprendre le fonctionnement de celle-ci.
Nous avons presque tout ce qu’il nous faut pour jouer, à l’exception des
collisions avec les bouteilles sur lesquelles nous reviendrons juste après.
E X I11 Effectuer quelques modifications pour pouvoir jouer :
1. Ajouter à la classe Niveau deux propriétés depart et arrivee permettant de lire les coordonnées (centres) du joueur au départ et de celles du but
à atteindre.
2. Créer une classe Gibsy qui possède comme attributs :
● x et y : la position du centre du joueur.
● dep : un couple indiquant les coordonnées de départ (pour repartir
au départ quand on perd).
● but : un couple indiquant les coordonnées du centre du but à atteindre.
● image : une surface représentant le personnage (on ne s’occupe pas
pour le moment de l’animation) et toile la surface image de l’objectif à atteindre.
304
O-B UT
On créera une instance de cette classe via la ligne gib = Gibsy(niv)
où niv est le niveau en cours.
3. Comme pour les autres éléments, ajouter la méthode update qui surveille
les touches du clavier et met à jour les coordonnées de Gibsy en respectant
les décors. On ne tient pas compte des autres obstacles pour le moment,
tester avec le niveau 1 qui est vide si vous voulez.
Voici encore une autre utilisation des propriétés : imaginez que vous
n’avez pas anticipé le fait que l’attribut image pouvait changer (selon la
direction, si on veut animer l’araignée...). On peut grâce aux propriétés,
sans changer le reste du code, mettre à jour de manière dynamique l’attribut image.
E X I12 Ajouter pour la classe Gibsy trois attributs privés :
● images : une liste de 16 images dans cet ordre : haut0, haut1, haut2,
haut3, droite0, droite1, droite2, droite3, bas0, bas1, bas2, bas3, gauche0,
gauche1, gauche2, gauche3.
● frame : le numéro de l’animation en cours (0, 1, 2 ou 3).
● direction : le numéro de la direction en cours (0 : Haut, 1 : Droite, 2 :
Bas ou 3 : Gauche).
Modifier alors la déclaration de la classe Gibsy pour que l’attribut image devienne une propriété et ainsi renvoie la bonne surface à afficher.
Reste à gérer les collisions avec les obstacles. Comme le test va dépendre
du type d’obstacle, il est préférable de placer celui-ci en tant que méthode
des classes Boule, Cochonnet et Bouteille plutôt que sur la classe
Gibsy.
305
C HAPITRE I
E X I13 Les méthodes touche reçoivent un objet de la classe Gibsy et renvoient
un booléen indiquant s’il y a eu collision ou non.
1. Créer la méthode touche pour la classe Boule sachant que les boules de
pétanque sont de diamètre 32 (pour vos tests, le niveau 2 ne contient que
des boules).
2. Créer la méthode touche pour la classe Cochonnet, sachant que les cochonnets sont de diamètre 16 (le niveau 3 ne contenant que des cochonnets).
3. Inclure alors ce test de collision pour que dès que Gibsy touche un obstacle,
elle reparte à sa position initiale et dès qu’elle atteint son objectif, le jeu
s’arrête.
Pour la collision avec les bouteilles au sol, le travail s’annonce bien plus
complexe, car celles-ci ont des formes bien moins simples. Heureusement
nous pouvons compter sur la classe Mask de Pygame !
Un petit code vaut parfois mieux qu’un
long discours :
import pygame
from pygame.locals import *
image = pygame.image.load('gibsy.png')
m = pygame.mask.from_surface(image)
xmax, ymax = m.get_size()
for y in range(ymax) :
ligne = ""
for x in range(xmax) :
if m.get_at((x,y)) == 1 :
ligne = ligne + 'X'
else :
ligne = ligne + " "
print(ligne)
XX
XX
XXXX
XXXX
XXXX
XXXX
XXXX
XXXX
XXXXX
XXXX
XXXXXX
XXXXXX
XXXXXXX
XXXXXX
XXXXXXX
XXXXXXX
XXXXXXX
XXXXXXX
XXXXXXXX
XXX XXX
XXXXXXX
XXXXXXXX XXXXXXXXXX XXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXX X
XXXXXXXXXXXXXXXXXXXXXXXX XXX
XX XXXXXXXXXXXXXXXXXXXXXX XXXX
XXXXX XXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXX XXXX
XXXX XXXXXXXXXX XXXX
XXX XXXXXXXXXX
XXX
XXX XXXXXXXXXX
XXXX
XXX
XXXXXXXX
XXX
XXX
XXXXXX
XXXX
XXXX
XXXX
XXX
XXX
XXX
XXX
XXX
X
X
La classe Mask va nous permettre de créer des masques binaires de
nos images. Un masque binaire peut être vu comme une image en noir et
blanc : elle ne contient que des 0 et des 1, c’est à dire que l’information
d’un pixel tient sur 1 bit, contrairement à une information d’un pixel en
mode RGBA qui nécessite 4 × 8 = 32 bits. Ainsi les tests de collisions seront
bien plus rapides que des tests manuels pixel par pixel. En effet si l’algorithme de détection de collision est programmé de manière optimisée, un
ordinateur 64 bits peut traiter en une opération élémentaire, une série de
64 points.
306
O-B UT
Décrivons quelques fonctions et méthodes sur cette classe :
● m = pygame.mask.from surface(surf ,s) : place dans m un objet de type Mask généré à partir de la surface surf . On peut préciser
un seuil s (entre 0 et 255) pour les images possédant un canal alpha
de transparence.
● m = pygame.mask.Mask(s) : place dans m un objet de type Mask
vide (que des 0) de taille s = (x,y).
● m.get bounding rects() : renvoie un objet Rect indiquant la
position ”utile” du masque, c’est à dire le plus petit rectangle contenant tous les 1 du masque m (si par exemple il y a des 0 sur la bordure de l’image).
● m.get at((x,y)) : renvoie 1 si le pixel de coordonnées (x,y) est
un point (noir) du masque binaire m et 0 sinon.
● m.set at((x,y),v) : affecte au pixel (x,y) du masque m la valeur
v ∈ {0; 1}.
● m1 .overlap area(m2, d) : renvoie le nombre de pixels en collision lorsque le masque m1 chevauche le masque m2 après avoir subi
un décalage de d = (Δx,Δy), comme l’illustre la figure. Le décalage
peut être négatif si besoin (attention à l’ordre des masques) :
Δy
Δx
m1
m2
307
C HAPITRE I
E X I14 Créer la méthode touche pour la classe Bouteille et admirer le
résultat sur les niveaux 4 et 5.
4 - Ce n’est qu’un au revoir...
Voilà pour ce dernier chapitre. Il reste tant de choses à dire sur Pygame
et sur Python, j’espère avoir éveillé votre curiosité et vous avoir donné
envie d’aller plus loin. Souvent, il aurait été possible de faire mieux, plus
efficace, plus rapide, d’utiliser d’autres outils.
Par exemple, et pour vous donner encore quelques pistes, le module
Pygame propose une classe Sprite que l’on peut dériver pour définir les
différents objets d’un jeu. En effet, vous aurez remarqué que l’on a souvent
besoin des mêmes informations :
● un attribut image pour représenter le personnage,
● un attribut rect de type Rect indiquant la position et les dimensions de ce dernier,
● une méthode update pour mettre à jour les informations le concernant et le représenter à l’écran si nécessaire,
● ...
Cette classe permet donc de regrouper ces informations et même de les
générer à la volée si besoin. Il existe même des classes pour regrouper
ces objets Sprite en collections pour leur faire effectuer des tâches communes.
Bref, en Python il existe toujours une classe répondant à nos attentes,
mais quel plaisir de créer de toutes pièces nos propres fonctionnalités pour
répondre à nos besoins. Comme pour le premier livre, souvenez-vous que
le but ici est d’apprendre de nouveaux concepts, de nouveaux algorithmes
et non de devenir un expert en Pygame.
J’espère que vous aurez pris du plaisir à lire ce livre, je vous donne
rendez-vous sur le forum du site web d’Edupython pour échanger et peutêtre dans un prochain livre pour découvrir l’interface KIVY qui permet de
programmer en Python des applications sur ordinateur et tablettes Android.
Portez-vous bien !
Le 13 avril 2020, un soir de confinement
308
O-B UT
5 - Aide pour les exercices
Aide pour l’exercice I9 Puisque les déplacements de la boule sont uniquement horizontaux et verticaux, on peut utiliser la méthode mur du niveau,
en ne testant qu’un point selon la direction souhaitée :
32
(x; y − 16 + vy )
(x − 16 + vx ; y)
(x,y)
(x + 16 + vx ; y)
32
(x; y + 16 + vy )
Aide pour l’exercice I14 Pour le masque représentant Gibsy, plusieurs
solutions s’offrent à nous :
● soit on le recalcule à chaque fois (c’est un peu long, mais comme
l’image est petite, c’est jouable) ;
● soit on les calcule tous à l’avance, comme pour les images ;
● soit enfin, on peut créer un masque plein de 32 × 32 pixels, car le
dessin de l’araignée est presque carré :
m = pygame.mask.Mask((32,32))
for x in range(32) :
for y in range(32) :
m.set_at((x,y),1)
ou avec une image et un seuil de -1 :
image = pygame.Surface((32,32))
m = pygame.mask.from_surface(image, -1)
309
C HAPITRE I
C HAPITRE I
6 - Solutions des exercices
6 - Solutions des exercices
Retour sur l’exercice I1
Retour sur l’exercice I1
1. Il suffit d’effectuer la requête permettant de récupérer les deux informations qui nous intéressent. La longueur d’une ligne est l’indice
1. Il suffit d’effectuer la requête permettant de récupérer les deux indu premier ’|’ trouvé. En effet s’il est en position 8, cela signifie bien
formations qui nous intéressent. La longueur d’une ligne est l’indice
qu’il y a 8 caractères avant (0 à 7). Pour connaı̂tre la hauteur, on peut
du premier ’|’ trouvé. En effet s’il est en position 8, cela signifie bien
ajouter 1 au nombre de symboles ’|’ présents dans la chaı̂ne (celle-ci
qu’il y a 8 caractères avant (0 à 7). Pour connaı̂tre la hauteur, on peut
ne se termine pas par un |) ou encore calculer le quotient de la lonajouter 1 au nombre de symboles ’|’ présents dans la chaı̂ne (celle-ci
gueur de la chaı̂ne augmentée de 1 (pour le dernier |) par le nombre
ne se termine pas par un |) ou encore calculer le quotient de la londe caractères sur une ligne (y compris le |). Pour la génération du
gueur de la chaı̂ne augmentée de 1 (pour le dernier |) par le nombre
plateau au cas où il n’y ait pas d’image précisée, le code devrait sufde caractères sur une ligne (y compris le |). Pour la génération du
fire à comprendre.
plateau au cas où il n’y ait pas d’image précisée, le code devrait suffire à comprendre.
class Niveau() :
def
__init__(self,
n) :
class
Niveau()
:
conn = sqlite3.connect('obut.db3')
= conn.cursor()
def c
__init__(self,
n) :
c.execute('SELECT
plateau, image FROM niveaux WHERE id ⤦
conn = sqlite3.connect('obut.db3')
� =?',(n,))
c = conn.cursor()
resultat
= c.fetchone()
c.execute('SELECT
plateau, image FROM niveaux WHERE id ⤦
# Remplissage
de la matrice
� =?',(n,))
self.plateau
= resultat[0]
resultat = c.fetchone()
self.largeur
self.plateau.index('|')
# Remplissage=de
la matrice
self.hauteur
// (self.largeur+1)
self.plateau =
= (1+len(self.plateau))
resultat[0]
#
Fond
self.largeur
= self.plateau.index('|')
if
resultat[1]
self.hauteur
= :
(1+len(self.plateau)) // (self.largeur+1)
self.fond = pygame.image.load('images/'+resultat[1])
# Fond
else:
if resultat[1] :
self.fond
⤦
self.fond =
= pygame.image.load('images/'+resultat[1])
� pygame.Surface((self.largeur*48,self.hauteur*48))
else:
self.fond.fill((242,
208, 95))
self.fond = ⤦
x, y �= pygame.Surface((self.largeur
0, 0
*48,self.hauteur*48))
for
car in self.plateau
: 95))
self.fond.fill((242,
208,
x, yif
= car
0, 0== '1' :
y, 48, 48))
for car self.fond.fill(0,(x,
in self.plateau :
x
x +==
48'1' :
if=car
if car
== '|' : # Retour y,
à la
ligne.
self.fond.fill(0,(x,
48,
48))
= 0, y + 48
x = x,
x +y48
conn.close()
if car == '|' : # Retour à la ligne.
x, y = 0, y + 48
conn.close()
310
310
En téléchargeant le code sur le site, vous aurez un programme complet permettant d’afficher le niveau 1 pour visualiser le résultat.
En téléchargeant le code sur le site, vous aurez un programme complet permettant d’afficher le niveau 1 pour visualiser le résultat.
O-B UT
2. Pour savoir s’il y a un mur, vous pouvez vous inspirer de l’exercice
A4. Pour être plus pédagogue, je propose une solution en 3 lignes,
mais on peut la faire en une.
def mur(self, x, y) :
l, c = y // 48, x // 48
# On cherche la ligne et la colonne
pos = l*(self.largeur+1)+c # On calcule l'indice dans la chaı̂ne
return self.plateau[pos] == '1'
class Obstacle() :
def __init__(self, x, y, img) :
self.x = x
self.y = y
self.image = img
def dessine(self) :
l, h = self.image.get_size()
fenetre.blit(self.image, (self.x-l//2, self.y-h//2))
Retour sur l’exercice I3 Voici un code possible pour la classe demandée :
class Bouteille(Obstacle) :
def __init__(self, x, y, a) :
image = pygame.image.load('images/bouteille.png').convert_alpha()
image = pygame.transform.rotate(image, a)
l, h = image.get_size()
image = pygame.transform.smoothscale(image,(l//10, h//10))
Obstacle.__init__(self, x, y, image)
À noter que l’on a utilisé la méthode smoothscale pour redimensionner
l’image puisque cet appel ne se produit qu’une fois lors de l’instanciation.
On peut donc privilégier la qualité à la vitesse.
Retour sur l’exercice I4 Même principe que pour l’exercice précédent :
class Boule(Obstacle) :
def __init__(self, x, y, vx, vy) :
image = pygame.image.load('images/boule.png').convert_alpha()
self.vx, self.vy = vx, vy
Obstacle.__init__(self, x, y, image)
311
Solutions des exercices
Retour sur l’exercice I2 Il faut faire attention à centrer l’image lorsqu’on
la dessine puisque les attributs x et y représentent le centre de celle-ci :
C HAPITRE I
Retour sur l’exercice I5 Ajoutons ces méthodes sur ces classes :
1. Pour la première, il suffit d’effectuer une translation :
class Boule(Obstacle) :
...
def update(self) :
self.x = self.x+self.vx
self.y = self.y+self.vy
self.dessine()
2. Pour la seconde, il y a juste à afficher l’image :
class Bouteille(Obstacle) :
...
def update(self) :
self.dessine()
Retour sur l’exercice I6 Les arguments x0, y0, a et r donnés lorsque l’on
instancie un objet doivent être conservés en mémoire pour permettre de
calculer les coordonnées du cochonnet à l’instant t. D’autre part, on ajoute
un attribut angle initialisé avec la valeur de a0 et qui évoluera au fil du
temps :
class Cochonnet(Obstacle) :
def __init__(self, x0, y0, a, a0, r) :
image = pygame.image.load('images/cochonnet.png').convert_alpha()
self.a, self.angle = a, a0
self.x0, self.y0 = x0, y0
self.r = r
self.x = self.x0+self.r*cos(self.angle)
self.y = self.y0+self.r*sin(self.angle)
Obstacle.__init__(self, self.x, self.y, image)
def update(self) :
self.angle = self.angle + self.a
self.x = self.x0+self.r*cos(self.angle)
self.y = self.y0+self.r*sin(self.angle)
self.dessine()
Pensez à importer les fonctions cos et sin du module math pour pouvoir
effectuer les calculs.
Retour sur l’exercice I7 Rien de trop compliqué dans cet exercice, il suffit d’initialiser une liste d’objets avant la boucle principale du jeu, par
exemple :
312
O-B UT
embuches = [Cochonnet(150,100,0.05,40,30), Bouteille(300,200,-45), ⤦
� Boule(600,300,-1,0) ]
et d’ajouter à l’intérieur de la boucle :
while continuer:
...
for e in embuches :
e.update()
...
Retour sur l’exercice I8 Nous allons mettre en œuvre la récupération des
informations par nom de colonne, comme nous venons de le voir :
def charge_embuches(n) :
liste = []
conn = sqlite3.connect('obut.db3')
conn.row_factory = sqlite3.Row
c = conn.cursor()
# Les bouteilles
for ligne in c.execute('SELECT * FROM bouteilles WHERE niv =?',(n,)):
liste.append(Bouteille(ligne['x'], ligne['y'], ligne['a']))
# Les boules
for ligne in c.execute('SELECT * FROM boules WHERE niv =?',(n,)):
liste.append(Boule(ligne['x'], ligne['y'], ligne['vx'], ligne['vy']))
# Les cochonnets
for ligne in c.execute('SELECT * FROM cochonnets WHERE niv =?',(n,)):
liste.append(Cochonnet(ligne['x0'],ligne['y0'], ligne['a'], ⤦
� ligne['a0'], ligne['r']))
conn.close()
return liste
Retour sur l’exercice I9 Comme proposé dans l’aide, on peut utiliser plusieurs fois la méthode mur pour les 4 collisions possibles. Si on touche un
mur, on modifie le vecteur vitesse en conséquence avant de modifier les
coordonnées de la boule :
def update(self) :
if self. vx < 0 and niv.mur(self.x+self.vx-16, self.y) :
self.vx = -self.vx
elif self. vx > 0 and niv.mur(self.x+self.vx+16, self.y) :
self.vx = -self.vx
elif self. vy < 0 and niv.mur(self.x, self.y+self.vy-16) :
self.vy = -self.vy
elif self. vy > 0 and niv.mur(self.x, self.y+self.vy+16) :
self.vy = -self.vy
self.x = self.x + self.vx
self.y = self.y + self.vy
self.dessine()
313
Solutions des exercices
pour mettre à jour tous les obstacles. L’intégralité du code est disponible
sur le site.
C HAPITRE I
Retour sur l’exercice I10 Si vous avez téléchargé la solution que je propose, on déplace le carré avec les flèches de direction et le disque avec les
flèches en maintenant simultanément la touche CTRL enfoncée.
● 1er cas : l’écart entre les abscisses est trop grand (zone grise), on est
sûr qu’il n’y a pas collision :
Δx
×
● 2e cas : l’écart entre les ordonnées est trop grand (zone grise), on est
sûr qu’il n’y a pas collision :
×
Δy
● 3e cas : l’écart entre les abscisses est trop petit (zone grise) et on n’est
pas dans les cas hachurés déjà traités, on est donc sûr qu’il y a collision :
314
O-B UT
● 4e cas : avec la même idée, si l’écart entre les ordonnées est trop
petit (zone grise) et puisque l’on n’est pas dans les cas hachurés déjà
traités, on est sûr qu’il y a collision :
×
● Dernier cas : si on arrive à cette ligne de code, c’est que le centre
du cercle est à l’intérieur d’un des carrés blancs. Par symétrie, et
puisque l’on a calculé des écarts absolus, ces 4 situations sont identiques : il s’agit de déterminer si le coin du carré est à l’intérieur du
cercle. En soustrayant la demi-longueur du côté, on se retrouve avec
ces quantités (on a zoomé sur le carré Sud-Ouest) :
315
Solutions des exercices
×
C HAPITRE I
×
dy
?
dx
On vérifie alors si ? ⩽ r, c’est à dire si
√
dx2 + dy 2 ⩽ r ou encore si
dx2 + dy 2 ⩽ r 2
Retour sur l’exercice I11
1. Chercher la position de départ ou d’arrivée relève du même principe : il faut chercher la position du caractère ’2’ (ou ’3’) dans l’attribut plateau, puis transformer cette position en ligne et colonne
et enfin repasser en coordonnées cartésiennes. On peut donc créer
cette fonction cherche une fois pour toutes (vous aurez remarqué
que l’on précède cette méthode d’un pour préciser qu’il s’agit d’une
méthode privée). Reste ensuite à créer la propriété :
class Niveau() :
...
def _cherche(self, car) :
pos = self.plateau.index(car)
l, c = pos // (self.largeur+1), pos % (self.largeur+1)
return c*48+24, l*48+24
def _depart(self) :
return self._cherche('2')
depart = property(_depart)
Comme souvent, on peut gagner un peu de temps en utilisant des
fonctions anonymes appelées en Python des fonctions lambda.
316
O-B UT
O-B UT
Comme en mathématiques lorsqu’on écrit f ∶ x �→ 2x + 1, on
peut créer
une ligne une fonction,
def2x: +O-B
UT
Comme
enen
mathématiques
lorsqu’onsans
écritutiliser
f ∶ x �→
1, on
peutf créer
une
def2x: + 1, on
Comme
enen
mathématiques
lorsqu’onsans
écritutiliser
f ∶ x �→
>>>
= lambda
x : ligne
2*x+1une fonction,
>>> f(4)
peut
créer
en
une
ligne
une
fonction,
sans
utiliser
def :
>>>
f = lambda x : 2*x+1
9
Comme
en mathématiques lorsqu’on écrit f ∶ x �→ 2x + 1, on
>>> f(4)
>>>
= lambda
x : ligne
2*x+1une fonction, sans utiliser def :
peutf créer
en une
9
>>>
f(4)
L’instruction
lambda renvoie une fonction qu’il n’est même
9
>>> f = lambda x : 2*x+1
≪ fonction ano-
pas
nécessairelambda
de nommer
(d’oune
ù l’appellation
L’instruction
renvoie
fonction qu’il
n’est même
>>>
f(4)
≫
nyme
)
:
9
≪ fonction
pas nécessairelambda
de nommer
(d’oune
ù l’appellation
anoL’instruction
renvoie
fonction qu’il
n’est même
propriétés
: peut procéder
depart
= property(lambda
obj ainsi
: obj._cherche('2'))
depart,
on
en déclarant directement les
Dans l’exemple
précédent,
inutile
en fait de créer la méthode
arrivee
= property(lambda
obj
: obj._cherche('3'))
propriétés
:
depart
= property(lambda
obj : obj._cherche('2'))
depart, on peut procéder ainsi en déclarant directement les
arrivee = property(lambda obj : obj._cherche('3'))
depart
= property(lambda
obj : obj._cherche('2'))
propriétés
:
arrivee = property(lambda obj : obj._cherche('3'))
depart = property(lambda obj : obj._cherche('2'))
arrivee = property(lambda obj : obj._cherche('3'))
2. On peut se demander quel est l’intérêt de créer les attributs dep et
pourseles
objets dequel
la classe
Gibsyde
alors
que
2. but
On peut
demander
est l’intérêt
créer
lesl’information
attributs depest
et
déjà
accessible
avec
la
classe
niveau.
Gardez
en
mémoire
la remarque
pourseles
objets dequel
la classe
Gibsyde
alors
que
2. but
On peut
demander
est l’intérêt
créer
lesl’information
attributs
depest
et
suivante,
déjà
accessible
avec lade
classe
niveau.
Gardez
en que
mémoire
la remarque
but
pour les objets
la classe
Gibsy
alors
l’information
est
2. On
peut se demander quel est l’intérêt de créer les attributs dep et
suivante,
déjà
accessible avec la classe niveau. Gardez en mémoire la remarque
but pour les objets de la classe Gibsy alors que l’information est
suivante,
déjà accessible avec la classe niveau. Gardez en mémoire la remarque
Attention à l’usage des propriétés : lorsque vous faites appel à
suivante,
une
propriété,
par exemple
niv.debut,
pensez
s’agità
Attention
à l’usage
des propriétés
: lorsque
vousqu’il
faitesne
appel
pas d’un
simple
une
donnée
(numérique
dans
notre
une
propriété,
paraccès
exemple
niv.debut,
pensez
ne
s’agit
Attention
à l’usage
des àpropriétés
: lorsque
vousqu’il
faites
appel
à
cas),
mais
bien
à
l’exécution
d’une
fonction.
pas
simple
à une
donnée (numérique
dans
notre
une d’un
propriété,
paraccès
exemple
niv.debut,
pensez qu’il
ne s’agit
Attention
à l’usage
des propriétés
: lorsque vous faites appel à
cas),
mais simple
bien
à l’exécution
d’une
fonction.
pas d’un
accès à une
donnée
(numérique dans notre
une propriété, par exemple niv.debut, pensez qu’il ne s’agit
cas), mais bien à l’exécution d’une fonction.
pas d’un simple accès à une donnée (numérique dans notre
cas),
mais
l’exécution
d’une
Dans notre
cas,bien
si ààchaque
frame
vousfonction.
cherchez à savoir si on est
arrivé
au but,
retrouver
la position
du caractère
’3’sidans
la
Dans notre
cas,il sifaut
à chaque
frame
vous cherchez
à savoir
on est
chaı̂ne
plateau...
ce
qui
est
à
priori
inutile
et
co
ûteux
en
temps.
arrivénotre
au but,
retrouver
la position
du caractère
’3’sidans
la
Dans
cas,il sifaut
à chaque
frame
vous cherchez
à savoir
on est
Autant
stocker
une
fois
pour
toutes
le
résultat
dans
une
variable
chaı̂ne
plateau...
ce
qui
est
à
priori
inutile
et
co
ûteux
en
temps.
arrivé au but, il faut retrouver la position du caractère ’3’ dans laà
Dans
notre
siun
à fois
chaque
frame
vous
cherchez
à tête,
savoir
si
on est
laquelle
on cas,
auraune
rapide.
Une
fois
ceci
en
code
Autant
stocker
pour
toutes
le inutile
résultat
uneleen
variable
à
chaı̂ne
plateau...
ceaccès
qui
est
à priori
etdans
coûteux
temps.
arrivé
auon
but,
il faut
retrouver
la Une
position
’3’code
dansest
la
très
simple
:aura
laquelle
un fois
accès
rapide.
fois du
cecicaractère
en tête,
Autant
stocker
une
pour
toutes
le résultat
dans
unelevariable
à
chaı̂ne
plateau...
ce qui est à priori inutile et coûteux en temps.
très simple
laquelle
on :aura un accès rapide. Une fois ceci en tête, le code est
Autant stocker une fois pour toutes le résultat dans une variable
317à
très simple :
laquelle on aura un accès rapide. Une fois ceci en tête, le code est
317
très simple :
317
317
Solutions des exercices
≫) :
nyme
>>>
(lambda
a,b de
: 2nommer
pas
nécessaire
(d’où l’appellation ≪ fonction ano*a-3*b)(4,5)
L’instruction
lambda renvoie une fonction qu’il n’est même
-7
≫) :
nyme
>>> (lambda a,b : 2*a-3*b)(4,5)
pas nécessaire de nommer (d’où l’appellation ≪ fonction ano-
-7
>>>
(lambda
a,b :précédent,
2*a-3*b)(4,5)
≫) :
Dans
l’exemple
inutile en fait de créer la méthode
nyme
-7
depart,
ona,b
peut
procéderinutile
ainsi en
directement
les
Dans
l’exemple
endéclarant
fait de créer
la méthode
>>>
(lambda
:précédent,
2*a-3*b)(4,5)
propriétés
:
-7
depart,
on peut
procéderinutile
ainsi en
directement
les
Dans
l’exemple
précédent,
endéclarant
fait de créer
la méthode
C HAPITRE I
C HAPITRE I
class IGibsy()
C HAPITRE
:
def __init__(self, n) :
self.dep = n.depart
class Gibsy()
: = n.arrivee
self.but
class Gibsy() :
self.x, self.y = self.dep
def self.image
__init__(self,
= ⤦ n) :
def __init__(self,
n) :
self.dep
= n.depart
� pygame.image.load('images/gibsy1.png').convert_alpha()
self.dep
=
self.but
= n.depart
n.arrivee
self.toile
= ⤦
self.but
= n.arrivee
self.x,
self.y
= self.dep
� pygame.image.load('images/toile.png').convert_alpha()
self.x,
self.y
self.image
= ⤦ = self.dep
self.image
= ⤦
� pygame.image.load('images/gibsy1.png').convert_alpha()
� pygame.image.load('images/gibsy1.png').convert_alpha()
self.toile
= ⤦
self.toile
= ⤦
� pygame.image.load('images/toile.png').convert_alpha()
� pygame.image.load('images/toile.png').convert_alpha() 3. Comme dans le chapitre EHPAD, il faut penser à regarder deux pixels
pour s’assurer que le déplacement dans la direction demandée est
valide et qu’on n’est pas ”à cheval” sur un mur. L’image de Gibsy
3. étant
Comme
le chapitre
faut
penser
regarder deuxdepixels
dedans
dimension
32 ×EHPAD,
32 et (x,il
y) étant
lesààcoordonnées
son
3. Comme
dans
le chapitre
EHPAD,
il faut
penser
regarder deux pixels
pour
s’assurer
que
le
déplacement
dans
la
direction
demandée est
centre,
on
a
:
pour s’assurer que le déplacement dans la direction demandée est
valide et qu’on n’est pas ”à cheval” sur un mur. L’image de Gibsy
valide et qu’on n’est pas ”à cheval” sur un mur. L’image de Gibsy
étant de dimension 32 × 32 et (x, y) étant les coordonnées de son
étantdefdeupdate(self)
dimension 32
: × 32 et (x, y) étant les coordonnées de son
centre,
on a :
touches
centre, on
a : = pygame.key.get_pressed()
if touches[K_UP] and not niv.mur(self.x-16, self.y-17) ⤦
� and not niv.mur(self.x+15, self.y-17) :
def update(self)
self.y = :
self.y - 1
def touches
update(self)
:
= pygame.key.get_pressed()
if touches[K_DOWN]
and not niv.mur(self.x-16, self.y+16) ⤦
touches
=
pygame.key.get_pressed()
if touches[K_UP]
and not niv.mur(self.x-16,
⤦
� and not niv.mur(self.x+15,
self.y+16) self.y-17)
:
if touches[K_UP]
and not niv.mur(self.x-16,
⤦
� and not
niv.mur(self.x+15,
self.y-17) self.y-17)
:
self.y
= self.y
+ 1
� and not
niv.mur(self.x+15,
self.y-17) :
self.y
= self.y
- 1not niv.mur(self.x-17,
if touches[K_LEFT]
and
self.y-16) ⤦
self.y = self.yand
- 1not niv.mur(self.x-16, self.y+16) ⤦
if touches[K_DOWN]
� and not niv.mur(self.x-17,
self.y+15) :
if touches[K_DOWN]
and not niv.mur(self.x-16,
� and not
niv.mur(self.x+15,
self.y+16) : self.y+16) ⤦
self.x
= self.x
- 1
�
and
not
niv.mur(self.x+15,
self.y+16)
:
self.y = self.y +
1 not niv.mur(self.x+16,
if touches[K_RIGHT]
and
self.y-16) ⤦
self.y = self.yand
+ 1not niv.mur(self.x-17, self.y-16) ⤦
if touches[K_LEFT]
� and not niv.mur(self.x+16, self.y+15) :
if touches[K_LEFT]
and
not
niv.mur(self.x-17,
self.y-16)
⤦
� and not
niv.mur(self.x-17,
self.y+15) :
self.x
= self.x
+ 1
� and not
niv.mur(self.x-17,
self.y+15) :
self.x
=
self.x
1
fenetre.blit(self.image, (self.x-16, self.y-16))
self.x = self.x and
- 1 not niv.mur(self.x+16, self.y-16) ⤦
if
touches[K_RIGHT]
fenetre.blit(self.toile,
(self.but[0]-16, self.but[1]-16))
if touches[K_RIGHT]
and not niv.mur(self.x+16,
� and not niv.mur(self.x+16,
self.y+15) : self.y-16) ⤦
� and not
niv.mur(self.x+16,
self.y+15) :
self.x
= self.x
+ 1
self.x = self.x + 1 (self.x-16, self.y-16))
fenetre.blit(self.image,
fenetre.blit(self.image, (self.but[0]-16,
(self.x-16, self.y-16))
fenetre.blit(self.toile,
self.but[1]-16))
fenetre.blit(self.toile, (self.but[0]-16, self.but[1]-16))
Retour sur l’exercice I12 On commence par modifier le constructeur en
conséquence (vous noterez la manière astucieuse de récupérer toutes les
directions de Gibsy en effectuant des rotations des images précédentes) :
Retour sur l’exercice I12 On commence par modifier le constructeur en
Retour sur l’exercice I12 On commence par modifier le constructeur en
conséquence (vous noterez la manière astucieuse de récupérer toutes les
conséquence (vous noterez la manière astucieuse de récupérer toutes les
directions de Gibsy en effectuant des rotations des images précédentes) :
directions de Gibsy en effectuant des rotations des images précédentes) :
318
318
318
O-B UT
def __init__(self, n) :
self.dep = n.depart
self.but = n.arrivee
self.x, self.y = self.dep
self.toile = pygame.image.load('images/toile.png').convert_alpha()
self._images = [
pygame.image.load('images/gibsy0.png').convert_alpha(),
pygame.image.load('images/gibsy1.png').convert_alpha(),
pygame.image.load('images/gibsy2.png').convert_alpha(),
pygame.image.load('images/gibsy3.png').convert_alpha()]
self._frame, self._direction = 0, 0
# Rotation des images
for i in range(12) :
self._images.append(pygame.transform.rotate(self._images[-4], -90))
puis on déclare la propriété :
image = property(lambda o : o._images[o._frame+o._direction*4])
Il faut alors changer la méthode update en conséquence pour modifier
les attributs frame et direction :
def update(self) :
touches = pygame.key.get_pressed()
if touches[K_UP] :
self._frame, self._direction = (self._frame+1)%4, 0
if not niv.mur(self.x-16, self.y-17) and not ⤦
� niv.mur(self.x+15, self.y-17) :
self.y = self.y - 1
elif touches[K_DOWN] :
self._frame, self._direction = (self._frame+1)%4, 2
if not niv.mur(self.x-16, self.y+16) and not ⤦
� niv.mur(self.x+15, self.y+16) :
self.y = self.y + 1
elif touches[K_LEFT] :
self._frame, self._direction = (self._frame+1)%4, 3
if not niv.mur(self.x-17, self.y-16) and not ⤦
� niv.mur(self.x-17, self.y+15) :
self.x = self.x - 1
elif touches[K_RIGHT] :
self._frame, self._direction = (self._frame+1)%4, 1
if not niv.mur(self.x+16, self.y-16) and not ⤦
� niv.mur(self.x+16, self.y+15) :
self.x = self.x + 1
fenetre.blit(self.image, (self.x-16, self.y-16))
fenetre.blit(self.toile, (self.but[0]-16, self.but[1]-16))
319
Solutions des exercices
class Gibsy() :
C HAPITRE I
Retour sur l’exercice I13 Les deux premières questions sont assez simples
dans la mesure où la fonction de collision a déjà été étudiée.
1. Pour la classe Boule :
class Boule(Obstacle) :
...
def touche(self, gib) :
return collision(self.x, self.y, 16, gib.x, gib.y, 32)
2. Pour la classe Cochonnet :
class Cochonnet(Obstacle) :
...
def touche(self, gib) :
return collision(self.x, self.y, 8, gib.x, gib.y, 32)
3. Reste à inclure ces tests dans la boucle principale (attention, si vous
testez avec un niveau comportant des bouteilles, vous déclencherez
une erreur car la classe de cet objet ne possède pas encore de méthode
touche).
continuer = True
clock = pygame.time.Clock()
while continuer:
clock.tick(100)
fenetre.blit(niv.fond,(0,0))
g.update()
perdu = False
for e in embuches :
e.update()
perdu = perdu or e.touche(g)
if perdu :
g.x, g.y = g.dep
if (g.x-g.but[0])**2 + (g.y-g.but[1])**2 < 400 :
continuer = False
print('GAGNE')
for event in pygame.event.get():
if event.type == QUIT :
continuer = False
pygame.display.flip()
320
O-B UT
Retour sur l’exercice I14 Dans la méthode constructeur, on ajoute le calcul
du masque de la bouteille et celui de Gibsy (j’ai opté pour la troisième
méthode proposée dans l’aide) :
def __init__(self, x, y, a) :
image = pygame.image.load('images/bouteille.png').convert_alpha()
image = pygame.transform.rotate(image, a)
l, h = image.get_size()
image = pygame.transform.smoothscale(image,(l//10, h//10))
Obstacle.__init__(self, x, y, image)
self.masque = pygame.mask.from_surface(image)
im = pygame.Surface((32,32))
self.masqueG = pygame.mask.from_surface(im, -1)
voir(self.masque)
Reste à coder la méthode touche :
def touche(self, gib) :
larg, long = self.image.get_size()
Dx = (gib.x-16) - (self.x-larg//2)
Dy = (gib.y-16) - (self.y-long//2)
return self.masque.overlap_area(self.masqueG, (Dx, Dy)) > 0
Quelques explications sur le calcul de Dx et Dy sont peut-être nécessaires :
les coordonnées sont celles des centres, or on a besoin de connaı̂tre le
décalage par rapport au coin Nord-Ouest comme d’habitude :
32
y)
haut
(gib.x,gib.y)
32
x, Δ
(Δ
(self.x,self.y)
larg
321
Solutions des exercices
class Bouteille(Obstacle) :
Glossaire
Glossaire
Les listes en Python
Les listes en Python
27
len
len(L) : renvoie le nombre d’éléments de la liste L.
27
27
in
len
27
27
for
in
27
27
append
for
27
27
insert
append
e in L : teste si l’élément e est dans la liste L.
len(L) : renvoie le nombre d’éléments de la liste L.
for e in L : réalise une boucle dans laquelle la vaà tour les
valeurs
des
éléments
de la
eriable
in eLprend
: teste tour
si l’élément
e est
dans la
liste
L.
liste L.
for e in L : réalise une boucle dans laquelle la vaL.append(e) : ajoute l’élément e à la fin de la liste L.
riable e prend tour à tour les valeurs des éléments de la
liste L.
L.insert(j ,e) : insère l’élément e au rang j de la liste
L.
L.append(e)
: ajoute l’élément e à la fin de la liste L.
27
27
remove
insert
27
27
pop
remove
27
27
min,
pop
max
min(L),
: renvoie
le plus
petit (led’indice
plus grand)
L.pop(i)max(L)
: supprime
et renvoie
l’élément
i de
élément
de
la
liste
L.
la liste L.
27
27
min,
sorted
max
sorted(L)
: renvoie
une nouvelle
liste(lecontenant
les
plus grand)
min(L),
max(L)
: renvoie
le plus petit
élémentsde
ordonnés
élément
la liste L.de la liste L.
27
sorted
sorted(L) : renvoie une nouvelle liste contenant les
323
éléments ordonnés de la liste L.
L.remove(e) : supprime la première occurrence de
L.insert(j ,e) : insère l’élément e au rang j de la liste
l’élément e dans la liste L.
L.
L.pop(i) : supprime et renvoie l’élément d’indice i de
L.remove(e) : supprime la première occurrence de
la liste L.
l’élément e dans la liste L.
323
Les chaı̂nes de caractères en Python
Les chaı̂nes de caractères en Python
24
len
len(ch) : renvoie la longueur de la chaı̂ne ch.
24
24
[i]
len
ch[i] : caractère d’indice i (accessible seulement en
lecture). : renvoie la longueur de la chaı̂ne ch.
len(ch)
24
[i ∶ j ]
[i]
ch[i ∶ j:]caractère
: envoie une
copiei de
la chaı̂neseulement
ch composée
ch[i]
d’indice
(accessible
en
des caractères d’indice k ∈ i; j .
lecture).
24
24
str
[i
∶ j]
24
24
int
str
str(n)
unecopie
chaı̂ne
contenant
ch[i
∶ j ] : renvoie
envoie une
dede
la caractères
chaı̂ne ch composée
l’écriture
décimale
du
nombre
n.
des caractères d’indice k ∈ i; j .
24
24
for
int
24
for
64
index
64
index
upper
lower
136
136
139
upper
join
lower
139
141
join
in
141
in
133
find
133
find
324
int(ch): :renvoie
transforme
chaı̂ne
en entier.contenant
str(n)
une la
chaı̂ne
dech
caractères
l’écriture décimale du nombre n.
for c in ch : réalise une boucle dans laquelle la variable c va: transforme
valoir tour àlatour
chaque
de ch.
int(ch)
chaı̂ne
ch enlettre
entier.
txt.index(ch,deb)
renvoie
position
la
for
c in ch : réalise :une
boucle la
dans
laquellede
la vapremière
occurrence
de
la
chaı̂ne
ch
dans
la
chaı̂ne
riable c va valoir tour à tour chaque lettre de ch.
txt à partir de la position deb si elle est précisée (ou
txt.index(ch,deb)
: renvoie
la pas
position
deune
la
du début sinon). Si la chaı̂ne
ch n’est
présente
première
de la chaı̂ne ch dans la chaı̂ne
exception occurrence
est déclenchée.
txt à partir de la position deb si elle est précisée (ou
ch.upper()
et ch.lower()
renvoient
respectivedu
début sinon).
Si la chaı̂ne ch: n’est
pas présente
une
ment la chaı̂ne
ch en majuscule et minuscule.
exception
est déclenchée.
ch.join(L) et
: renvoie
une chaı̂ne
de caractères
où
ch.upper()
ch.lower()
: renvoient
respectivetous les
éléments
de majuscule
la liste L ont
été concaténés en
ment
la chaı̂ne
ch en
et minuscule.
utilisant la chaı̂ne ch comme jointure.
ch.join(L) : renvoie une chaı̂ne de caractères où
ch1 in
ch2 : donne
indiquant
si la chaı̂ne
tous
les éléments
deun
la booléen
liste L ont
été concaténés
en
ch1 est incluse
dans
chaı̂nejointure.
ch2 .
utilisant
la chaı̂ne
chlacomme
txt.find(ch,deb)
: renvoie la position de la
ch
1 in ch2 : donne un booléen indiquant si la chaı̂ne
première
occurrence
la chaı̂ne
ch1 est incluse dans lade
chaı̂ne
ch2 . ch dans la chaı̂ne
txt à partir de la position deb si elle est précisée (detxt.find(ch,deb)
: la
renvoie
la n’est
position
de la
puis
le début sinon). Si
chaı̂ne ch
pas présente
première
occurrence
de la chaı̂ne
ch dans la chaı̂ne
dans
txt, la
fonction renvoie
-1.
txt à partir de la position deb si elle est précisée (depuis le début sinon). Si la chaı̂ne ch n’est pas présente
dans txt, la fonction renvoie -1.
Les dictionnaires en Python
49
{}
dict={} : crée un dictionnaire vide et le nomme dict.
49
[c]
dict[cle] : élément associé à la clé cle du dictionnaire
dict.
49
pop
v=d.pop(cle) : supprime la clé cle après avoir renvoyé
la valeur de l’élément associé.
49
copy
d2=d1.copy() : crée une copie (indépendante) du dictionnaire d1 et la stocke dans le dictionnaire d2.
49
len
len(dict) : renvoie le nombre d’enregistrements dans
le dictionnaire dict.
49
in
cle in dict : renvoie un booléen indiquant si cle est
une clé du dictionnaire dict.
51
keys
d.keys() : renvoie la liste des clés du dictionnaire d.
51
values
d.values() : renvoie la liste des valeurs du dictionnaire d.
51
items
d.items() : renvoie la liste des couples (clés, valeurs)
de d.
Le module math de Python
√
x.
57
sqrt
sqrt(x) : renvoie
57
cos, sin
tan
cos(x), sin(x) et tan(x) : représentent les fonctions trigonométriques cosinus, sinus et tangente.
57
degrees
radians
degrees(x), radians(x) : permet de convertir
en degrés un angle en radians et inversement.
57
asin
asin(x) : renvoie la valeur de l’arcsinus de x
70
copysign
copysign(A,x) : renvoie la valeur (absolue)
de A précédée du signe de x. En particulier
copysign(1,x) : renvoie 1 si x ⩾ 0 et −1 sinon.
325
Le module re de Python (expressions régulières)
fullmatch
fullmatch(m,ch) : teste si la chaı̂ne ch est un
représentant du motif m.
118
match
match(m,ch) : comme la fonction précédente,
mais teste si la chaı̂ne ch commence par un
représentant du motif m.
118
search
search(m,ch) : comme la fonction fullmatch,
mais teste si la chaı̂ne ch contient un représentant
du motif m.
134
findall
findall(m, ch) : renvoie sous forme d’une liste
l’ensemble de chaı̂nes correspondant au motif m
dans le texte ch.
133
start
sre.start() : renvoie la position de départ de la
chaı̂ne trouvée.
133
end
sre.end() : renvoie la position de la fin de la
chaı̂ne trouvée.
133
string
sre.string : renvoie la chaı̂ne dans laquelle s’est
effectuée la recherche.
group
sre.group(0) : renvoie la chaı̂ne trouvée.
sre.group(i) : renvoie la partie de la chaı̂ne
trouvée correspondant au i ème groupe (à la i ème
paire de parenthèses).
118
133
Le module random de Python
148
choice
choice(x) : renvoie aléatoirement un élément
de la liste x ou une lettre de la chaı̂ne x avec
équiprobabilité.
239
randint
randint(a,b) : renvoie un entier de l’intervalle
[a,b].
326
Le module os de Python
196
isfile
isfile(f ich) : renvoie un booléen indiquant si le
fichier f ich est présent.
200
system
system(cmd) : exécute la ligne de commande cmd.
Cette fonction se trouve dans le module os de Python.
238
getsize
path.getsize(nf ) : renvoie, en octets, la taille du
fichier portant le nom nf .
PYGAME
Le module pygame.display (fenêtre)
75
set mode
set mode(s,opt) : renvoie une surface qui
représentera la fenêtre présente à l’écran.
75
set caption
set caption(txt) : affiche le texte txt dans la
barre du haut de la fenêtre.
75
flip
flip() : recopie la surface virtuelle à l’écran.
75
set icon
set icon(s) : associe à la fenêtre l’icône définie
par la surface s.
Le module pygame.image
76
load
load(f ich) : renvoie une surface représentant l’image
contenue dans le fichier f ich.
76
save
save(surf ,f ich) : sauvegarde la surface surf sous forme
d’un fichier image nommé f ich.
327
Les objets Surface de Pygame
Surface
Surface(s,opt) : crée et renvoie une surface
de dimension s donnée sous forme de couple.
En option opt, on peut préciser SRCALPHA si
on veut une image au format RGBA.
fill
surf .fill(coul) : remplit la surface surf
de la couleur coul. Cette dernière peut être
donnée par un triplet ou un quadruplet selon
le format de la surface.
set at
surf .set at(p,coul) : colorie le pixel de coordonnées p = (x,y) de la couleur coul. Comme
d’habitude le point de coordonnées (0,0) est le
coin en haut à gauche de l’image.
get at
surf .get at(p) : renvoie la couleur du pixel
de coordonnées p sous la forme d’un quadruplet (R, G, B, A). Si la surface n’a pas d’information de transparence, le canal alpha est à 255.
216
get size
surf .get size() : retourne la dimension de
l’image en pixels sous la forme d’un couple
(largeur, hauteur).
216
get width
surf .get width() : retourne la largeur de
l’image.
216
get height
surf .get height() : retourne la hauteur de
l’image.
216
get bytesize
surf .get bytesize() : retourne le nombre
d’octets utilisés pour coder un pixel de l’image.
Cela permet donc d’avoir des informations sur
l’image.
216
subsurface
surf .subsurface(r) : renvoie une copie de
la surface surf délimitée par le rectangle r qui
peut être donné par un quadruplet (x,y,l,h).
78
78
78
78
328
79
79
copy
copy
79
79
blit
blit
80
80
convert
convert
80
80
convert alpha
convert alpha
surf .copy() : renvoie une copie de la surface
surf ..copy() : renvoie une copie de la surface
surf
surf .
surf .blit(s1,p,r) : copie la surface s1 sur la
surf
.blit(s
copie
la surface
s1 sur la
surface
surf à1,p,r)
partir: du
point
de coordonnées
surface
à partirrdu
point
de coordonnées
p.
Si unsurf
quadruplet
= (x
0 ,y0 ,l,h) est précisé,
p.
Si
un
quadruplet
r
=
(x
,y
,l,h)
estde
précisé,
0
0
la surface s1 est limitée aux pixels
coorla
surface
s
est
limitée
aux
pixels
de
1 vérifiant : x0 ⩽ x < x0 + l etcoordonnées (x,y)
y0 ⩽
données
vérifiant
: x0 ⩽ x l’intégralité
< x0 + l et y0de
⩽
y < y0 + h.(x,y)
Si r n’est
pas précisé,
yla<surface
y0 + h. Si
r
n’est
pas
précisé,
l’intégralité
de
s1 est recopiée.
la surface s1 est recopiée.
surf .convert() : convertit la surface surf
surfformat
.convert()
: convertit
la surface surf
au
de la fenêtre
d’affichage.
au format de la fenêtre d’affichage.
surf .convert alpha() : convertit la surface
surf .convert
: convertit
la surface
surf
au format alpha()
de la fenêtre
d’affichage
en
surf au format
de la fenêtre d’affichage en
conservant
la transparence.
conservant la transparence.
Le module pygame.font
Le module pygame.font
122
122
Font
Font
122
122
get fonts
get fonts
122
122
match font
match font
122
122
render
render
122
122
size
size
Font(f ich, s) : renvoie un objet représentant la
Font(f
ich,
s) :f renvoie
objets. représentant la
police du
fichier
ich et deun
taille
police du fichier f ich et de taille s.
get fonts() : retourne la liste des polices insget
fonts()
: retourne
la liste des polices installées
dans le système
d’exploitation.
tallées dans le système d’exploitation.
match font(txt) : retourne, sous forme de
match
retourne,
sous forme
de
chaı̂ne, font(txt)
le chemin de: la
police contenant
le mot
chaı̂ne,
le chemin de la police contenant le mot
txt cherché.
txt cherché.
f nt.render(txt,l,c) : retourne une surface
fcontenant
nt.render(txt,l,c)
: retourne
surface
le texte txt écrit
avec laune
police
f nt
contenant
le
texte
txt
écrit
avec
la
police
f
nt
avec la couleur c donnée sous forme d’un triplet
avec
la
couleur
c
donnée
sous
forme
d’un
triplet
(R,V,B). Le paramètre l (appelé antialias) est un
(R,V,B).
paramètre
(appelé
booléen Le
indiquant
si lel texte
doitantialias)
être lissé. est un
booléen indiquant si le texte doit être lissé.
f nt.size(txt) : retourne un couple indiquant la
flargeur
nt.size(txt)
: retourne
un couple
indiquant
la
et la hauteur
en pixels
du texte
txt écrit
largeur
et
la
hauteur
en
pixels
du
texte
txt
écrit
avec la police f nt.
avec la police f nt.
329
Les objets Rect de Pygame
Rect
Rect(x,y,l,h) : renvoie un rectangle dont le
coin supérieur gauche est situé en (x,y), de
largeur l et de hauteur h.
get rect
img.get rect() : renvoie un rectangle dont
le coin supérieur gauche se trouve en (0,0)
et dont les dimensions sont celles de l’image
img.
x, y
left, x, top, y, right, bottom, centerx,
centery, width, w, height, h : sont des attributs entiers d’un objet Rect renseignant sa
position.
159
topleft...
topleft,
topright,
bottomright,
bottomleft, midtop, midleft, midright,
midbottom, center, size : sont des attributs d’un objet Rect indiquant la position du
rectangle sous forme de couple.
159
copy
R1.copy() : renvoie une copie du rectangle
R1.
159
move
R1.move(x,y) : renvoie un nouveau rectangle, obtenu comme image du rectangle R1
par la translation de vecteur v⃗(x,y).
159
collidepoint
R1.collidepoint((x,y)) : renvoie un
booléen indiquant si P(x,y) est à l’intérieur
de R1.
159
colliderect
R1.colliderect(R2) : renvoie un booléen
indiquant s’il y a collision entre les rectangles
R1 et R2.
158
158
158
330
159
collidelist
159
collidelist
collidelistall
159
159
collidelist
collidelistall
159
collidelistall
de la première collision entre une liste R de
Rects et R1.
R1.collidelistall(R)
: retourne
la
R1.collidelist(R) : retourne
l’index
liste
tous lescollision
indices des
rectangles
R
de lade
première
entre
une liste de
R de
qui
entrent
en
collision
avec
le
rectangle
R1.
Rects et R1.
R1.collidelist(R) : retourne l’index
R1.collidelistall(R)
retourne
la
de
la première collision entre:une
liste R de
liste
Rectsde
et tous
R1. les indices des rectangles de R
qui entrent en collision avec le rectangle R1.
R1.collidelistall(R) : retourne la
liste de tous les indices des rectangles de R
qui entrent en collision avec le rectangle R1.
Les modules pygame.event et pygame.key
Les modules pygame.event et pygame.key
89
Les modules pygame.event et pygame.key
QUIT : quand l’utilisateur tente de quitter
QUIT
le programme.
89
KEYUP
89
89
QUIT
KEYDOWN
KEYUP : une touche est relâchée.
QUIT : quand l’utilisateur tente de quitter
le
programme.
KEYDOWN
: une touche est enfoncée.
89
89
125
KEYUP
QUIT
MOUSEBUTTONDOWN
QUIT : quand
l’utilisateur
tente de quitter
KEYUP
: une
touche
est
relâchée.
Lorsqu’un
bouton
de
la
souris
est enfoncé.
le programme.
89
125
89
KEYDOWN
MOUSEBUTTONUP
KEYUP
KEYDOWN
: bouton
une touche
est enfoncée.
Lorsqu’un
KEYUP
: une
toucheest
estrelâché.
relâchée.
125
125
89
MOUSEBUTTONDOWN
MOUSEMOTION
KEYDOWN
Lorsqu’un
de déplacée.
la souris est enfoncé.
Lorsque la: bouton
souris
est
KEYDOWN
une touche
est enfoncée.
125
125
MOUSEBUTTONUP
MOUSEBUTTONDOWN
125
125
161
125
MOUSEMOTION
MOUSEBUTTONUP
get pressed
MOUSEMOTION
161
get pressed
161
get pressed
Lorsqu’un bouton est relâché.
Lorsqu’un bouton de la souris est enfoncé.
get pressed() : cette fonction du moLorsque
souris
est déplacée.
dule keylade
Pygame
renvoie un tableau
Lorsqu’un
bouton
est relâché.
de 0 et de 1 indiquant une liste de l’état
des touches
enfoncées
au moment de l’apLorsque
la souris
est
déplacée.
get
pressed()
:
cette
fonction
ducodes
mopel. Les indices correspondent
aux
dule
key de Pygame renvoie un tableau
des touches.
de 0 et de 1 indiquant une liste de l’état
gettouches
pressed()
: cette
modes
enfoncées
au fonction
moment du
de l’apduleLes
keyindices
de Pygame
renvoie un
pel.
correspondent
auxtableau
codes
de
0
et
de
1
indiquant
une
liste
de l’état
des touches.
des touches enfoncées au moment de l’appel. Les indices correspondent aux codes
des touches.
331
Quelques noms de touches usuelles dans Pygame
Quelques noms de touches usuelles dans Pygame
Quelques noms de touches usuelles dans Pygame
89
89
89
89
89
89
89
89
89
89
89
89
89
89
89
89
89
89
89
89
89
89
89
89
89
89
89
89
89
89
89
89
89
89
K
K
K
K
K
K
K
K
K
K
K
K
K
K
K
K
K
K
K
K
K
K
K
K
K
K
K
K
K
K
K
K
K
K
Quelques noms de touches usuelles dans Pygame
BACKSPACE Retour arrière
TAB
Tabulation
RETURN
BACKSPACE Return
Retour arrière
PAUSE
Pause
TAB
BACKSPACE
Tabulation
Retour arrière
UP
Flèche
haut
RETURN
TAB
Return
Tabulation
RIGHT
Flèche
PAUSE
RETURN
Pause
Returndroite
BACKSPACE
Retour
arrière
Flèche
gauche
LEFT
UP
PAUSE
Flèche
haut
Pause
TAB
Tabulation
DOWN
Flèche
RIGHT
UP
droite
Flèche bas
haut
RETURN
Return
Echap
ESCAPE
Flèche
gauche
LEFT
RIGHT
PAUSE
Pause droite
Espace
SPACE
DOWN
Flèche bas
gauche
LEFT
UP
Flèche
haut
F1
F1
Echap bas
ESCAPE
DOWN
RIGHT
Flèche
droite
0
0Espace
SPACE
Echap gauche
ESCAPE
Flèche
LEFT
a
A
F1
Espacebas
SPACE
F1
DOWN
Flèche
RSHIFT
Shift
0
F1
0F1 droit
Echap
ESCAPE
Control
gauche
LCTRL
a
0
A
Espace
0
SPACE
RALT
ALT
droit
RSHIFT
a
Shift
droit
A
F1
F1
Control
gauche
LCTRL
RSHIFT
Shift
droit
0
0
RALT
Control
gauche
LCTRL
ALT droit
a
A
RALT
ALT droit
RSHIFT
Shift
droit
Control
gauche
LCTRL
RALT
ALT droit
Les objets Event de Pygame
Les objets Event de Pygame
Les objets Event de Pygame
89
key
89
125
89
key
pos
key
125
89
125
125
pos
key
button
pos
125
125
125
button
pos
button
125
button
332
Les objets Event de Pygame
evt.key : donne le code de la touche enfoncée.
evt.pos :: donne
indique
sous de
la la
forme
d’un
couple (x,y)
evt.key
le code
touche
enfoncée.
la
position
du
pointeur
de
la
souris
au
moment de
evt.key : donne le code de la touche enfoncée.
l’événement.
evt.pos
: indique sous la forme d’un couple (x,y)
la
position
du pointeur
de
latouche
souris
aucouple
moment
de
evt.pos :: donne
indique
sous de
la la
forme
d’un
(x,y)
evt.key
le code
enfoncée.
evt.button
: indique
le numéro
du bouton
en action.
l’événement.
la position du pointeur de la souris au moment de
l’événement.
evt.pos
: indique sous la forme d’un couple (x,y)
evt.button : indique le numéro du bouton en action.
la position du pointeur de la souris au moment de
evt.button : indique le numéro du bouton en action.
l’événement.
evt.button : indique le numéro du bouton en action.
Le module pygame.transform
Le module pygame.transform
Le module pygame.transform
218
218
218
218
218
218
218
221
218
221
221
221
221
221
221
221
Le module pygame.transform
flip(s,v,h) : renvoie l’image de la surface s
flip
obtenue par symétrie d’axe vertical (v) et/ou
flip(s,v,h)
horizontal
(h).: renvoie l’image de la surface s
flip
obtenue
par symétrie
vertical
et/ous
flip(s,v,h)
: renvoied’axe
l’image
de la (v)
surface
rotate(s,a)
: renvoied’axe
une surface
horizontal
(h).symétrie
obtenue par
vertical contenant
(v) et/ou
flip
rotate
l’image
de (h).
la surface s après rotation d’angle a
horizontal
flip(s,v,h)
: renvoie l’image de la surface s
rotate(s,a)
: renvoie une surface contenant
autour
de son centre.
flip
obtenue par symétrie d’axe vertical (v) et/ou
rotate
l’image
de la surface
s après
rotationcontenant
d’angle a
rotate(s,a)
: renvoie
une surface
horizontal
(h). : renvoie une surface corresscale(s,(l,h))
rotate
autour
centre. s après rotation d’angle a
l’imagede
deson
la surface
scale
pondant
l’image
de la surface s zoomée (ou
autour deàson
centre.
rotate(s,a)
:
renvoie
uneune
surface contenant
scale(s,(l,h))
: renvoie
rétrécie)
aux dimensions
l × h. surface corresrotate
l’image
de
surface
après
rotation
d’angle
scale
pondant
à la
l’image
des la
surface
s zoomée
(oua
scale(s,(l,h))
: renvoie
une surface
corresautour
de
son
centre.
smoothscale(s,(l,h))
même
mais avec
rétrécie)
dimensions
× h. effet
scale
pondant aux
à l’image
de la :lsurface
s zoomée
(ou
smoothscale une
meilleure
qualité
de
transformation
(plus
rétrécie) aux dimensions
h. surface corresscale(s,(l,h))
: renvoiel ×une
smoothscale(s,(l,h))
: même effet mais avec
lente).
scale
pondant à l’image de la surface s zoomée (ou
smoothscale une
meilleure qualité de: même
transformation
smoothscale(s,(l,h))
effet mais(plus
avec
rétrécie) aux dimensions l × h.
smoothscale lente).
une meilleure qualité de transformation (plus
lente).
smoothscale(s,(l,h))
: même effet mais avec
smoothscale une meilleure qualité de transformation (plus
lente).
Le module pygame.draw
Le module pygame.draw
Le module pygame.draw
222
line
222
222
222
222
222
222
222
222
222
222
222
222
line
rect
line
rect
ellipse
rect
line
ellipse
arc
ellipse
rect
arc
polygon
arc
ellipse
222
222
222
polygon
arc
polygon
222
polygon
Le module pygame.draw
line(s,c,deb,f in,e) : dessine une ligne.
line(s,c,deb,f
: dessine
une ligne.
rect(s,c,r,e) : in,e)
dessine
un rectangle.
line(s,c,deb,f in,e) : dessine une ligne.
rect(s,c,r,e)
: dessine
un rectangle.
ellipse(s,c,r,e)
: dessine
une ellipse.
rect(s,c,r,e)
:
dessine
un
rectangle.
line(s,c,deb,f in,e) : dessine une ligne.
ellipse(s,c,r,e)
ellipse.
arc(s,c,r,ad,af ,e): :dessine
dessineune
un arc
d’ellipse.
ellipse(s,c,r,e)
: dessine
une ellipse.
rect(s,c,r,e)
: dessine
un rectangle.
polygon(s, c,,e)
pts,
fill=
arc(s,c,r,ad,af
: dessine
un0)
arc: dessine
d’ellipse.un
gone.
arc(s,c,r,ad,af ,e): :dessine
dessineune
un arc
d’ellipse.
ellipse(s,c,r,e)
ellipse.
polygon(s, c, pts, fill= 0) : dessine un
gone.
polygon(s, c,,e)
pts,
fill=
arc(s,c,r,ad,af
: dessine
un0)
arc: dessine
d’ellipse.un
gone.
polygon(s, c, pts, fill= 0) : dessine un
gone.
polypolypolypoly-
333
306
306
306
306
306
306
306
306
306
306
306
306
334
Le module pygame.mask
Le module pygame.mask
from surface(surf ,s) : renvoie un
objet
type Mask généré
à partir un
de
from surface
from de
surface(surf
,s) : renvoie
la
surface
surfMask
.
objet
de type
généré à partir de
from surface
la surface surf .
Mask(s) : renvoie un objet de type
Mask
Mask
vide: de
taille s un
= (x,y).
Mask(s)
renvoie
objet de type
Mask
Mask vide de taille s = (x,y).
m.get bounding rects() : renvoie
objetbounding
Rect indiquant
la : position
get bounding rects un
m.get
rects()
renvoie
duRect
masque.
un objet
indiquant la position
get bounding rects ”utile”
”utile” du masque.
m.get at((x,y)) : renvoie 1 si le
pixel
de at((x,y))
coordonnées: (x,y)
est un
get at
m.get
renvoie
1 point
si le
(noir)
du
masque
binaire
m
et
0
sinon.
pixel de coordonnées (x,y) est un point
get at
(noir) du masque binaire m et 0 sinon.
m.set at((x,y),v) : affecte au pixel
set at
(x,y)
duat((x,y),v)
masque m un:valeur
∈ {0;
1}.
m.set
affectevau
pixel
set at
(x,y) du masque m un valeur v ∈ {0; 1}.
m1 .overlap area(m2, d) : renvoie
le 1nombre
de pixels
en collision
lorsque
m
.overlap
area(m
2 , d) : renvoie
masque de
m1pixels
chevauche
le masque
m2
overlap area
le nombre
en collision
lorsque
après
avoir
subi
un
décalage
de
d
le masque m1 chevauche le masque m=2
overlap area
(Δx,Δy).
après
avoir subi un décalage de d =
(Δx,Δy).
CRÉATIONS NUMÉRIQUES
Ce livre, basé sur une progression originale de démarche de projets,
vous propose d’approfondir vos connaissances en Python au travers
de la réalisation de plusieurs jeux.
Vous découvrirez l’interface Pygame et la plupart des modules qui
la composent. Les projets vous permettront d’aborder de nouvelles
notions, comme :
• le parcours d’un graphe
• les expressions régulières
• la lecture en Python de pages web, de fichiers CSV ou JSON
• la manipulation de bases de données et l’interfaçage avec Python
• le paradigme de la programmation objet où l’héritage et les
subtilités du principe d’encapsulation seront expliqués au fur et à
mesure des chapitres.
Comme pour les autres ouvrages de la collection « créations numériques »,
tous les exercices sont corrigés en ligne et l’intégralité des ressources
graphiques nécessaires est téléchargeable.
Illustrations de couverture : © David Beauget
Que vous soyez lycéen en NSI, étudiant en CPGE, enseignant ou simple
curieux, ce livre devrait aiguiser votre curiosité pour le Grand Oral,
vos projets ou vos TIPE.
Dans la même collection :
-:HSMDOA=UYWVUU:
Download