Browse Source

Mise à jour de 'SpaceInvader.py'

master
Treacky 4 days ago
parent
commit
b689e98219
  1. 625
      SpaceInvader.py

625
SpaceInvader.py

@ -1,306 +1,319 @@
''' '''
Programme pyxel inspiré d'un tutoriel en ligne de "la Nuit du Code" 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://nuit-du-code.forge.apps.education.fr/DOCUMENTATION/PYTHON/01-presentation/
https://www.cahiernum.net/KV8H5B https://www.cahiernum.net/KV8H5B
Licence GNU (https://github.com/nuitducode/DOCUMENTATION/blob/main/LICENSE) 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. Module basé sur une architecture MVC (modèle-vue-controleur) globale et également intégrée aux objets.
''' '''
# Importation # Importation
import pyxel import pyxel
import random import random
# Constantes # Constantes
COULEUR_VAISSEAU = 1 COULEUR_VAISSEAU = 1
COULEUR_TIR = 10 COULEUR_TIR = 10
COULEUR_ENNEMI = 8 COULEUR_ENNEMI = 8
# Déclaration des classes # Déclaration des classes
class Vaisseau: class Vaisseau:
"""Classe intégrant la gestion du Modèle et de la Vue relative au vaisseau du joueur.""" """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: def __init__(self, couleur:int=COULEUR_VAISSEAU) -> None:
self.x = 60 # coordonnée x du coin haut à gauche du carré 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.y = 60 # coordonnée y du coin haut à gauche du carré
self.couleur = couleur # couleur du vaisseau à l'écran self.jump = None
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""" def set_x(self:'Vaisseau', dx:int) -> None:
self.x = self.x + dx """Déplace le vaisseau à gauche si dx positif, à droite si négatif"""
if self.x < 0: self.x = self.x + dx
self.x = 0 if self.x < 0:
elif self.x >= 120: self.x = 0
self.x = 120 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""" def set_y(self:'Vaisseau', dy:int) -> None:
self.y = self.y + dy """Déplace le vaisseau en bas si dy positif, en haut si négatif"""
if self.y < 0: self.y = self.y + dy
self.y = 0 if self.y < 0:
elif self.y >= 120: self.y = 0
self.y = 120 elif self.y >= 120:
self.y = 120
def get_coord(self:'Vaisseau') -> tuple[int, int]: def set_jump(self):
"""Renvoie le couple (x, y) qui contient les coordonnées (du coin haut gauche) du vaisseau""" self.jump = 10
return (self.x, self.y)
def get_coord(self:'Vaisseau') -> tuple[int, int]:
def afficher(self:'Vaisseau') -> None: """Renvoie le couple (x, y) qui contient les coordonnées (du coin haut gauche) du vaisseau"""
"""Affiche le vaisseau""" return (self.x, self.y)
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 def afficher(self:'Vaisseau') -> None:
"""Affiche le vaisseau"""
pyxel.blt(self.x, self.y, 0, 0, 0, 8, 8)
class Joueur: # (..., 0, 0, 0, 8, 8) car Image 0 à partir de (0;0) de taille 8*8
"""Classe intégrant la gestion du Modèle relative au joueur."""
def move(self):
def __init__(self, vaisseau:'Vaisseau', vies:int) -> None: if self.jump!=None:
self.vies = vies # A 0, le joueur a perdu self.jump = self.jump - 1
self.vaisseau = vaisseau # L'instance de Vaisseau du joueur if self.jump > 0:
self.y = self.y - 1
def est_vivant(self:'Joueur') -> bool: if self.jump < 0:
"""Prédicat qui renvoie vrai si le joueur a encore des vies""" self.y = self.y + 1
return self.vies > 0 if self.jump == -100:
self.jump = None
def get_vies(self:'Joueur') -> int:
"""Renvoie le nombre de vies du joueur""" class Joueur:
return self.vies """Classe intégrant la gestion du Modèle relative au joueur."""
def perd_une_vie(self:'Joueur') -> None: def __init__(self, vaisseau:'Vaisseau', vies:int) -> None:
"""Fait perdre une vie au joueur mais en imposant un minimum de 0""" self.vies = vies # A 0, le joueur a perdu
self.vies = self.vies - 1 self.vaisseau = vaisseau # L'instance de Vaisseau du joueur
if self.vies < 0:
self.vies = 0 def est_vivant(self:'Joueur') -> bool:
"""Prédicat qui renvoie vrai si le joueur a encore des vies"""
class Tir: return self.vies > 0
"""Classe intégrant la gestion du Modèle et de la Vue relative au vaisseau des tirs."""
def get_vies(self:'Joueur') -> int:
def __init__(self, xv:int, yv:int, couleur:int=COULEUR_TIR) -> None: """Renvoie le nombre de vies du joueur"""
self.x = xv + 4 return self.vies
self.y = yv - 4
self.couleur = couleur def perd_une_vie(self:'Joueur') -> None:
"""Fait perdre une vie au joueur mais en imposant un minimum de 0"""
def deplacement(self:'Tir') -> None: self.vies = self.vies - 1
"""Déplace le tir d'un pixel vers le haut""" if self.vies < 0:
self.y = self.y - 1 self.vies = 0
def est_hors_ecran(self:'Tir') -> bool: class Tir:
"""Prédicat qui renvoie True si le tir est sorti de l'écran par le haut""" """Classe intégrant la gestion du Modèle et de la Vue relative au vaisseau des tirs."""
return self.y < -8
def __init__(self, xv:int, yv:int, couleur:int=COULEUR_TIR) -> None:
def get_coord(self:'Tir') -> tuple[int, int]: self.x = xv + 4
"""Renvoie le couple (x,y) des coordonnées (du coin haut gauche) du tir""" self.y = yv - 4
return (self.x, self.y) self.couleur = couleur
def afficher(self:'Tir') -> None: def deplacement(self:'Tir') -> None:
pyxel.blt(self.x, self.y, 0, 12, 0, 1, 8) """Déplace le tir d'un pixel vers le haut"""
# (..., 0, 12, 0, 1, 8) car Image 0 à partir de (12;0) de taille 1*8 self.y = self.y - 1
def est_hors_ecran(self:'Tir') -> bool:
class Ennemi: """Prédicat qui renvoie True si le tir est sorti de l'écran par le haut"""
"""Classe intégrant la gestion du Modèle et de la Vue relative au vaisseau des ennemis.""" return self.y < -8
def __init__(self, x:int, y:int, couleur:int=COULEUR_ENNEMI) -> None: def get_coord(self:'Tir') -> tuple[int, int]:
self.x = x """Renvoie le couple (x,y) des coordonnées (du coin haut gauche) du tir"""
self.y = y return (self.x, self.y)
self.couleur = couleur
def afficher(self:'Tir') -> None:
def deplacement(self:'Ennemi') -> None: pyxel.blt(self.x, self.y, 0, 12, 0, 1, 8)
"""Déplace l'ennemi d'un pixel vers le bas""" # (..., 0, 12, 0, 1, 8) car Image 0 à partir de (12;0) de taille 1*8
self.y = self.y + 1
def get_coord(self:'Ennemi') -> tuple[int, int]: class Ennemi:
"""Renvoie le couple (x,y) des coordonnées (du coin haut gauche) de l'ennemi""" """Classe intégrant la gestion du Modèle et de la Vue relative au vaisseau des ennemis."""
return (self.x, self.y)
def __init__(self, x:int, y:int, couleur:int=COULEUR_ENNEMI) -> None:
def est_hors_ecran(self:'Ennemi') -> bool: self.x = x
"""Prédicat qui renvoie True si l'ennemi est sorti de l'écran par le bas""" self.y = y
return self.y > 128 self.couleur = couleur
def est_touche_par(self:'Ennemi', tir:'Tir') -> bool: def deplacement(self:'Ennemi') -> None:
"""Prédicat qui renvoie True si l'ennemi est en contact avec le tir""" """Déplace l'ennemi d'un pixel vers le bas"""
tir_x, tir_y = tir.get_coord() # les coordonnées du tir, objet d'une autre classe self.x = self.x - 1
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 def get_coord(self:'Ennemi') -> tuple[int, int]:
if tir_x >= self.x: # si le x du tir dépasse le bord gauche du monstre """Renvoie le couple (x,y) des coordonnées (du coin haut gauche) de l'ennemi"""
if tir_x < (self.x + 8): # si le x du tir ne dépasse pas le bord droite du monstre return (self.x, self.y)
# 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 def est_hors_ecran(self:'Ennemi') -> bool:
return True """Prédicat qui renvoie True si l'ennemi est sorti de l'écran par le bas"""
return False return self.x < 0
def touche_vaisseau(self:'Ennemi', v:'Vaisseau') -> bool: def est_touche_par(self:'Ennemi', tir:'Tir') -> bool:
"""Prédicat qui renvoie True si l'ennemi est en contact avec le vaisseau du joueur""" """Prédicat qui renvoie True si l'ennemi est en contact avec le tir"""
v_x, v_y = v.get_coord() # on récupère les coordonnées d'un objet d'une autre classe tir_x, tir_y = tir.get_coord() # les coordonnées du tir, 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 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
def afficher(self:'Ennemi') -> None: if tir_x >= self.x: # si le x du tir dépasse le bord gauche du monstre
"""Affiche l'ennemi""" if tir_x < (self.x + 8): # si le x du tir ne dépasse pas le bord droite du monstre
pyxel.blt(self.x, self.y, 0, 0, 8, 8, 8) # si on arrive ici, c'est que l'abscisse du tir est compatible avec une touche
# (..., 0, 0, 8, 8, 8) car Image 0 à partir de (0;8) de taille 8*8 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
class Explosion:
"""Classe intégrant la gestion des explosions.""" def touche_vaisseau(self:'Ennemi', v:'Vaisseau') -> bool:
def __init__(self, ennemi:'Ennemi') -> None: """Prédicat qui renvoie True si l'ennemi est en contact avec le vaisseau du joueur"""
"""On génère l'explosion du monstre en (x,y)""" v_x, v_y = v.get_coord() # on récupère les coordonnées d'un objet d'une autre classe
xe, ye = ennemi.get_coord() return self.x <= v_x + 8 and self.y <= v_y + 8 and self.x + 8 >= v_x and self.y + 8 >= v_y
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 def afficher(self:'Ennemi') -> None:
self.force = 0 """Affiche l'ennemi"""
self.couleur = 8 + self.force % 3 pyxel.blt(self.x, self.y, 0, 0, 8, 8, 8)
self.rayon = 2 * (self.force // 4) # (..., 0, 0, 8, 8, 8) car Image 0 à partir de (0;8) de taille 8*8
def propager(self:'Explosion') -> None:
"""Propage l'explosion en augmentant son rayon""" class Explosion:
self.force = self.force + 1 # on augmente la force """Classe intégrant la gestion des explosions."""
self.couleur = 8 + self.force % 3 # on calcule la nouvelle couleur def __init__(self, ennemi:'Ennemi') -> None:
self.rayon = 2 * (self.force // 4) # on calcule le rayon de l'explosion """On génère l'explosion du monstre en (x,y)"""
xe, ye = ennemi.get_coord()
def est_au_maximum(self:'Explosion') -> None: self.x = xe + 4 # car self.x est le centre du cercle, pas son bord gauche
"""Prédicat qui renvoie True si l'explosion a atteint son étendue maximale""" self.y = ye + 4 # car self.y est le centre du cerlce, pas son bord haut
return self.force >= 12 self.force = 0
self.couleur = 8 + self.force % 3
def afficher(self:'Explosion') -> None: self.rayon = 2 * (self.force // 4)
"""Affiche l'explosion"""
pyxel.circb(self.x, self.y, self.rayon, self.couleur) def propager(self:'Explosion') -> None:
"""Propage l'explosion en augmentant son rayon"""
class Jeu: self.force = self.force + 1 # on augmente la force
"""Classe intégrant la gestion du jeu.""" self.couleur = 8 + self.force % 3 # on calcule la nouvelle couleur
def __init__(self) -> None: self.rayon = 2 * (self.force // 4) # on calcule le rayon de l'explosion
# Création de la fenêtre graphique def est_au_maximum(self:'Explosion') -> None:
pyxel.init(128, 128, title="Nuit du c0de") """Prédicat qui renvoie True si l'explosion a atteint son étendue maximale"""
pyxel.load("space.pyxres") return self.force >= 12
# Initialisation des données du jeu def afficher(self:'Explosion') -> None:
self.vaisseau = Vaisseau() """Affiche l'explosion"""
self.joueur = Joueur(self.vaisseau, 4) pyxel.circb(self.x, self.y, self.rayon, self.couleur)
self.tirs = [] # Tableau des tirs
self.ennemis = [] # Tableau des ennemis présents class Jeu:
self.explosions = [] # Tableau des explosions """Classe intégrant la gestion du jeu."""
def __init__(self) -> None:
# Lancement de l'alternance 30x par seconde entre controleur et vue
pyxel.run(self.controler, self.afficher) # Création de la fenêtre graphique
pyxel.init(128, 128, title="Nuit du c0de")
def controler(self:'Jeu') -> None: pyxel.load("space.pyxres")
"""déplacement avec les touches de directions"""
# Initialisation des données du jeu
self.se_deplacer() self.vaisseau = Vaisseau()
self.tirer() self.joueur = Joueur(self.vaisseau, 4)
self.deplacer_tirs() self.tirs = [] # Tableau des tirs
self.ajouter_nouvel_ennemi() self.ennemis = [] # Tableau des ennemis présents
self.deplacer_ennemis() self.explosions = [] # Tableau des explosions
self.supprimer_ennemis_touches()
self.modifier_explosions() # Lancement de l'alternance 30x par seconde entre controleur et vue
pyxel.run(self.controler, self.afficher)
def se_deplacer(self:'Jeu') -> None:
"""Contrôle les touches de déplacement et lance le déplacement au besoin""" def controler(self:'Jeu') -> None:
"""déplacement avec les touches de directions"""
if pyxel.btn(pyxel.KEY_RIGHT):
self.vaisseau.set_x(1) self.se_deplacer()
if pyxel.btn(pyxel.KEY_LEFT): self.tirer()
self.vaisseau.set_x(-1) self.deplacer_tirs()
if pyxel.btn(pyxel.KEY_DOWN): self.ajouter_nouvel_ennemi()
self.vaisseau.set_y(1) self.deplacer_ennemis()
if pyxel.btn(pyxel.KEY_UP): self.supprimer_ennemis_touches()
self.vaisseau.set_y(-1) self.modifier_explosions()
def tirer(self:'Jeu') -> None: def se_deplacer(self:'Jeu') -> None:
"""Contrôle la touche de création d'un tir et lance la création au besoin""" """Contrôle les touches de déplacement et lance le déplacement au besoin"""
if pyxel.btnr(pyxel.KEY_SPACE): if pyxel.btn(pyxel.KEY_RIGHT):
self.ajouter_un_tir() self.vaisseau.set_x(1)
if pyxel.btn(pyxel.KEY_LEFT):
def ajouter_un_tir(self:'Jeu') -> None: self.vaisseau.set_x(-1)
"""Ajoute un nouveau tir dans le jeu""" if pyxel.btn(pyxel.KEY_DOWN):
self.vaisseau.set_y(1)
xv, yv = self.vaisseau.get_coord() if pyxel.btn(pyxel.KEY_UP):
nouveau_tir = Tir(xv, yv) self.vaisseau.set_jump()
self.tirs.append(nouveau_tir) self.vaisseau.move()
def deplacer_tirs(self) -> None: def tirer(self:'Jeu') -> None:
"""Contrôle le déplacement des tirs et leur suppression quand ils sortent du cadre""" """Contrôle la touche de création d'un tir et lance la création au besoin"""
for tir in list(self.tirs): # list() pour travailler sur une copie et éviter le problème de décalage des cases if pyxel.btnr(pyxel.KEY_SPACE):
tir.deplacement() self.ajouter_un_tir()
if tir.est_hors_ecran():
self.tirs.remove(tir) def ajouter_un_tir(self:'Jeu') -> None:
"""Ajoute un nouveau tir dans le jeu"""
def ajouter_nouvel_ennemi(self:'Jeu') -> None:
"""Création aléatoire des ennemis""" xv, yv = self.vaisseau.get_coord()
nouveau_tir = Tir(xv, yv)
if (pyxel.frame_count % 30 == 0): # 30 images / s donc un ennemi par seconde self.tirs.append(nouveau_tir)
nouvel_ennemi = Ennemi(random.randint(0, 120), 0)
self.ennemis.append(nouvel_ennemi) def deplacer_tirs(self) -> None:
"""Contrôle le déplacement des tirs et leur suppression quand ils sortent du cadre"""
def deplacer_ennemis(self:'Jeu') -> None:
"""Déplace les ennemis et les supprime quand ils sortent du cadre ou sont touchés""" for tir in list(self.tirs): # list() pour travailler sur une copie et éviter le problème de décalage des cases
tir.deplacement()
for ennemi in list(self.ennemis): # list() pour travailler sur une copie et éviter le problème de décalage de cases if tir.est_hors_ecran():
ennemi.deplacement() self.tirs.remove(tir)
if ennemi.est_hors_ecran():
self.ennemis.remove(ennemi) def ajouter_nouvel_ennemi(self:'Jeu') -> None:
elif ennemi.touche_vaisseau(self.vaisseau): """Création aléatoire des ennemis"""
self.ennemis.remove(ennemi)
self.joueur.perd_une_vie() if (pyxel.frame_count % 30 == 0): # 30 images / s donc un ennemi par seconde
nouvel_ennemi = Ennemi(120, 0)
def supprimer_ennemis_touches(self:'Jeu') -> None: self.ennemis.append(nouvel_ennemi)
"""Supprime l'ennemi et le tir au contact"""
def deplacer_ennemis(self:'Jeu') -> None:
for ennemi in list(self.ennemis): # list() pour obtenir une copie des ennemis """Déplace les ennemis et les supprime quand ils sortent du cadre ou sont touchés"""
for tir in list(self.tirs): # list() pour obtenir une copie des ennemis
if ennemi.est_touche_par(tir): for ennemi in list(self.ennemis): # list() pour travailler sur une copie et éviter le problème de décalage de cases
self.ajouter_explosion(ennemi) ennemi.deplacement()
self.ennemis.remove(ennemi) if ennemi.est_hors_ecran():
self.tirs.remove(tir) self.ennemis.remove(ennemi)
elif ennemi.touche_vaisseau(self.vaisseau):
def ajouter_explosion(self:'Jeu', ennemi:'Ennemi') -> None: self.ennemis.remove(ennemi)
'''Ajoute dans les données une explosion naissante suite à la destruction de l'ennemi''' self.joueur.perd_une_vie()
explosion = Explosion(ennemi) def supprimer_ennemis_touches(self:'Jeu') -> None:
self.explosions.append(explosion) """Supprime l'ennemi et le tir au contact"""
def modifier_explosions(self:'Jeu') -> None: for ennemi in list(self.ennemis): # list() pour obtenir une copie des ennemis
"""Modification des données des explosions""" for tir in list(self.tirs): # list() pour obtenir une copie des ennemis
if ennemi.est_touche_par(tir):
for explosion in list(self.explosions): # list() pour obtenir une copie temporaire des explosions self.ajouter_explosion(ennemi)
explosion.propager() self.ennemis.remove(ennemi)
if explosion.est_au_maximum(): self.tirs.remove(tir)
self.explosions.remove(explosion)
def ajouter_explosion(self:'Jeu', ennemi:'Ennemi') -> None:
def afficher(self:'Jeu') -> None: '''Ajoute dans les données une explosion naissante suite à la destruction de l'ennemi'''
"""création et positionnement des objets (30 fois par seconde)"""
explosion = Explosion(ennemi)
pyxel.cls(0) # efface le contenu de la fenetre self.explosions.append(explosion)
if self.joueur.est_vivant(): # si le joueur possède encore des vies def modifier_explosions(self:'Jeu') -> None:
self.afficher_vies() """Modification des données des explosions"""
self.vaisseau.afficher()
for tir in self.tirs: for explosion in list(self.explosions): # list() pour obtenir une copie temporaire des explosions
tir.afficher() explosion.propager()
for ennemi in self.ennemis: if explosion.est_au_maximum():
ennemi.afficher() self.explosions.remove(explosion)
for explosion in self.explosions :
explosion.afficher() def afficher(self:'Jeu') -> None:
"""création et positionnement des objets (30 fois par seconde)"""
else: # sinon: GAME OVER
self.afficher_game_over() pyxel.cls(0) # efface le contenu de la fenetre
def afficher_vies(self:'Jeu') -> None: if self.joueur.est_vivant(): # si le joueur possède encore des vies
"""Affiche le nombre de vies du joueur""" self.afficher_vies()
pyxel.text(5, 5, f"VIES: {self.joueur.get_vies()}", 7) self.vaisseau.afficher()
for tir in self.tirs:
def afficher_game_over(self:'Jeu') -> None: tir.afficher()
"""Affiche que le jeu est fini""" for ennemi in self.ennemis:
pyxel.text(50, 64, 'GAME OVER', 7) ennemi.afficher()
for explosion in self.explosions :
explosion.afficher()
application = Jeu()
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()

Loading…
Cancel
Save