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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
| '''
Programme pyxel inspiré d'un tutoriel en ligne de "la Nuit du Code"
https://nuit-du-code.forge.apps.education.fr/DOCUMENTATION/PYTHON/01-presentation/
https://www.cahiernum.net/KV8H5B
Licence GNU (https://github.com/nuitducode/DOCUMENTATION/blob/main/LICENSE)
Module basé sur une architecture MVC (modèle-vue-controleur) globale et également intégrée aux objets.
'''
# Importation
import pyxel
import random
# Constantes
COULEUR_VAISSEAU = 1
COULEUR_TIR = 10
COULEUR_ENNEMI = 8
# Déclaration des classes
class Vaisseau:
"""Classe intégrant la gestion du Modèle et de la Vue relative au vaisseau du joueur."""
def __init__(self, couleur:int=COULEUR_VAISSEAU) -> None:
self.x = 60 # coordonnée x du coin haut à gauche du carré
self.y = 60 # coordonnée y du coin haut à gauche du carré
self.couleur = couleur # couleur du vaisseau à l'écran
def set_x(self:'Vaisseau', dx:int) -> None:
"""Déplace le vaisseau à gauche si dx positif, à droite si négatif"""
self.x = self.x + dx
if self.x < 0:
self.x = 0
elif self.x >= 120:
self.x = 120
def set_y(self:'Vaisseau', dy:int) -> None:
"""Déplace le vaisseau en bas si dy positif, en haut si négatif"""
self.y = self.y + dy
if self.y < 0:
self.y = 0
elif self.y >= 120:
self.y = 120
def get_coord(self:'Vaisseau') -> tuple[int, int]:
"""Renvoie le couple (x, y) qui contient les coordonnées (du coin haut gauche) du vaisseau"""
return (self.x, self.y)
def afficher(self:'Vaisseau') -> None:
"""Affiche le vaisseau"""
pyxel.blt(self.x, self.y, 0, 0, 0, 8, 8)
# (..., 0, 0, 0, 8, 8) car Image 0 à partir de (0;0) de taille 8*8
class Joueur:
"""Classe intégrant la gestion du Modèle relative au joueur."""
def __init__(self, vaisseau:'Vaisseau', vies:int) -> None:
self.vies = vies # A 0, le joueur a perdu
self.vaisseau = vaisseau # L'instance de Vaisseau du joueur
def est_vivant(self:'Joueur') -> bool:
"""Prédicat qui renvoie vrai si le joueur a encore des vies"""
return self.vies > 0
def get_vies(self:'Joueur') -> int:
"""Renvoie le nombre de vies du joueur"""
return self.vies
def perd_une_vie(self:'Joueur') -> None:
"""Fait perdre une vie au joueur mais en imposant un minimum de 0"""
self.vies = self.vies - 1
if self.vies < 0:
self.vies = 0
class Tir:
"""Classe intégrant la gestion du Modèle et de la Vue relative au vaisseau des tirs."""
def __init__(self, xv:int, yv:int, couleur:int=COULEUR_TIR) -> None:
self.x = xv + 4
self.y = yv - 4
self.couleur = couleur
def deplacement(self:'Tir') -> None:
"""Déplace le tir d'un pixel vers le haut"""
self.y = self.y - 1
def est_hors_ecran(self:'Tir') -> bool:
"""Prédicat qui renvoie True si le tir est sorti de l'écran par le haut"""
return self.y < -8
def get_coord(self:'Tir') -> tuple[int, int]:
"""Renvoie le couple (x,y) des coordonnées (du coin haut gauche) du tir"""
return (self.x, self.y)
def afficher(self:'Tir') -> None:
pyxel.blt(self.x, self.y, 0, 12, 0, 1, 8)
# (..., 0, 12, 0, 1, 8) car Image 0 à partir de (12;0) de taille 1*8
class Ennemi:
"""Classe intégrant la gestion du Modèle et de la Vue relative au vaisseau des ennemis."""
def __init__(self, x:int, y:int, couleur:int=COULEUR_ENNEMI) -> None:
self.x = x
self.y = y
self.couleur = couleur
def deplacement(self:'Ennemi') -> None:
"""Déplace l'ennemi d'un pixel vers le bas"""
self.y = self.y + 1
def get_coord(self:'Ennemi') -> tuple[int, int]:
"""Renvoie le couple (x,y) des coordonnées (du coin haut gauche) de l'ennemi"""
return (self.x, self.y)
def est_hors_ecran(self:'Ennemi') -> bool:
"""Prédicat qui renvoie True si l'ennemi est sorti de l'écran par le bas"""
return self.y > 128
def est_touche_par(self:'Ennemi', tir:'Tir') -> bool:
"""Prédicat qui renvoie True si l'ennemi est en contact avec le tir"""
tir_x, tir_y = tir.get_coord() # les coordonnées du tir, objet d'une autre classe
return tir_x >= self.x and tir_x < (self.x + 8) and tir_y < (self.y + 8) #
# ou cette version, plus compréhensible car étape par étape
if tir_x >= self.x: # si le x du tir dépasse le bord gauche du monstre
if tir_x < (self.x + 8): # si le x du tir ne dépasse pas le bord droite du monstre
# si on arrive ici, c'est que l'abscisse du tir est compatible avec une touche
if tir_y < (self.y + 8): # si le coin haut du tir est plus petit que le coin bas du monstre
return True
return False
def touche_vaisseau(self:'Ennemi', v:'Vaisseau') -> bool:
"""Prédicat qui renvoie True si l'ennemi est en contact avec le vaisseau du joueur"""
v_x, v_y = v.get_coord() # on récupère les coordonnées d'un objet d'une autre classe
return self.x <= v_x + 8 and self.y <= v_y + 8 and self.x + 8 >= v_x and self.y + 8 >= v_y
def afficher(self:'Ennemi') -> None:
"""Affiche l'ennemi"""
pyxel.blt(self.x, self.y, 0, 0, 8, 8, 8)
# (..., 0, 0, 8, 8, 8) car Image 0 à partir de (0;8) de taille 8*8
class Explosion:
"""Classe intégrant la gestion des explosions."""
def __init__(self, ennemi:'Ennemi') -> None:
"""On génère l'explosion du monstre en (x,y)"""
xe, ye = ennemi.get_coord()
self.x = xe + 4 # car self.x est le centre du cercle, pas son bord gauche
self.y = ye + 4 # car self.y est le centre du cerlce, pas son bord haut
self.force = 0
self.couleur = 8 + self.force % 3
self.rayon = 2 * (self.force // 4)
def propager(self:'Explosion') -> None:
"""Propage l'explosion en augmentant son rayon"""
self.force = self.force + 1 # on augmente la force
self.couleur = 8 + self.force % 3 # on calcule la nouvelle couleur
self.rayon = 2 * (self.force // 4) # on calcule le rayon de l'explosion
def est_au_maximum(self:'Explosion') -> None:
"""Prédicat qui renvoie True si l'explosion a atteint son étendue maximale"""
return self.force >= 12
def afficher(self:'Explosion') -> None:
"""Affiche l'explosion"""
pyxel.circb(self.x, self.y, self.rayon, self.couleur)
class Jeu:
"""Classe intégrant la gestion du jeu."""
def __init__(self) -> None:
# Création de la fenêtre graphique
pyxel.init(128, 128, title="Nuit du c0de")
pyxel.load("space.pyxres")
# Initialisation des données du jeu
self.vaisseau = Vaisseau()
self.joueur = Joueur(self.vaisseau, 4)
self.tirs = [] # Tableau des tirs
self.ennemis = [] # Tableau des ennemis présents
self.explosions = [] # Tableau des explosions
# Lancement de l'alternance 30x par seconde entre controleur et vue
pyxel.run(self.controler, self.afficher)
def controler(self:'Jeu') -> None:
"""déplacement avec les touches de directions"""
self.se_deplacer()
self.tirer()
self.deplacer_tirs()
self.ajouter_nouvel_ennemi()
self.deplacer_ennemis()
self.supprimer_ennemis_touches()
self.modifier_explosions()
def se_deplacer(self:'Jeu') -> None:
"""Contrôle les touches de déplacement et lance le déplacement au besoin"""
if pyxel.btn(pyxel.KEY_RIGHT):
self.vaisseau.set_x(1)
if pyxel.btn(pyxel.KEY_LEFT):
self.vaisseau.set_x(-1)
if pyxel.btn(pyxel.KEY_DOWN):
self.vaisseau.set_y(1)
if pyxel.btn(pyxel.KEY_UP):
self.vaisseau.set_y(-1)
def tirer(self:'Jeu') -> None:
"""Contrôle la touche de création d'un tir et lance la création au besoin"""
if pyxel.btnr(pyxel.KEY_SPACE):
self.ajouter_un_tir()
def ajouter_un_tir(self:'Jeu') -> None:
"""Ajoute un nouveau tir dans le jeu"""
xv, yv = self.vaisseau.get_coord()
nouveau_tir = Tir(xv, yv)
self.tirs.append(nouveau_tir)
def deplacer_tirs(self) -> None:
"""Contrôle le déplacement des tirs et leur suppression quand ils sortent du cadre"""
for tir in list(self.tirs): # list() pour travailler sur une copie et éviter le problème de décalage des cases
tir.deplacement()
if tir.est_hors_ecran():
self.tirs.remove(tir)
def ajouter_nouvel_ennemi(self:'Jeu') -> None:
"""Création aléatoire des ennemis"""
if (pyxel.frame_count % 30 == 0): # 30 images / s donc un ennemi par seconde
nouvel_ennemi = Ennemi(random.randint(0, 120), 0)
self.ennemis.append(nouvel_ennemi)
def deplacer_ennemis(self:'Jeu') -> None:
"""Déplace les ennemis et les supprime quand ils sortent du cadre ou sont touchés"""
for ennemi in list(self.ennemis): # list() pour travailler sur une copie et éviter le problème de décalage de cases
ennemi.deplacement()
if ennemi.est_hors_ecran():
self.ennemis.remove(ennemi)
elif ennemi.touche_vaisseau(self.vaisseau):
self.ennemis.remove(ennemi)
self.joueur.perd_une_vie()
def supprimer_ennemis_touches(self:'Jeu') -> None:
"""Supprime l'ennemi et le tir au contact"""
for ennemi in list(self.ennemis): # list() pour obtenir une copie des ennemis
for tir in list(self.tirs): # list() pour obtenir une copie des ennemis
if ennemi.est_touche_par(tir):
self.ajouter_explosion(ennemi)
self.ennemis.remove(ennemi)
self.tirs.remove(tir)
def ajouter_explosion(self:'Jeu', ennemi:'Ennemi') -> None:
'''Ajoute dans les données une explosion naissante suite à la destruction de l'ennemi'''
explosion = Explosion(ennemi)
self.explosions.append(explosion)
def modifier_explosions(self:'Jeu') -> None:
"""Modification des données des explosions"""
for explosion in list(self.explosions): # list() pour obtenir une copie temporaire des explosions
explosion.propager()
if explosion.est_au_maximum():
self.explosions.remove(explosion)
def afficher(self:'Jeu') -> None:
"""création et positionnement des objets (30 fois par seconde)"""
pyxel.cls(0) # efface le contenu de la fenetre
if self.joueur.est_vivant(): # si le joueur possède encore des vies
self.afficher_vies()
self.vaisseau.afficher()
for tir in self.tirs:
tir.afficher()
for ennemi in self.ennemis:
ennemi.afficher()
for explosion in self.explosions :
explosion.afficher()
else: # sinon: GAME OVER
self.afficher_game_over()
def afficher_vies(self:'Jeu') -> None:
"""Affiche le nombre de vies du joueur"""
pyxel.text(5, 5, f"VIES: {self.joueur.get_vies()}", 7)
def afficher_game_over(self:'Jeu') -> None:
"""Affiche que le jeu est fini"""
pyxel.text(50, 64, 'GAME OVER', 7)
application = Jeu()
|