Une nouvelle version du casse-briques que j'avais posté il y a quelques années.
L'aspect reste inchangé, les modifications interviennent surtout sur le petit moteur de jeu qui se voit grandement allégé par l'utilisation de la POO qui facilite l'interaction entre objets.
Source / Exemple :
# -*- coding: cp1252 -*-
################################################################################
# #
# Astres 3.0 #
# #
# Genre : Casse-briques #
# Langage : Python 2.6 #
# Auteur : Guillaume Michon #
# Date 22/04/2013 #
# #
################################################################################
from ressources2 import Ressources
from module_scores import *
from module_edition import *
from classeSprite5c import *
from Tkinter import *
from math import cos,sin,radians,sqrt,fabs,atan2
from random import randrange
import pickle
import tkFileDialog
import os
import glob
#----------------- Chargement des tableaux -------------------#
def tableaux_de_base(event):
"""Appel le chargement des tableaux de base par défaut."""
chargement("tableau.cb")
def tableaux_personnels(event):
"""Ouverture d'une fenêtre de dialogue contenant toutes les
séries de taleaux.(touche 3)"""
cheminFichier = tkFileDialog.askopenfilename(filetypes = [("Liste de Tableaux","*.cb")])
(filepath, nomFichier) = os.path.split(cheminFichier)
if nomFichier != "":
chargement(nomFichier)
def chargement(nomFichier):
"""Chargement du fichier contenant une serie de tableaux et
les scores correspondants à cette série."""
global nomTableau,listeTableaux,scoresEtTableaux
nomTableau = nomFichier
fichier = open(nomFichier,'r')
scoresEtTableaux = pickle.load(fichier)
listeTableaux = scoresEtTableaux[1]
fichier.close
nouvelle_partie()
#------- Création des briques en fonction du tableau courant ------#
def creation_tableau():
"""L'espace de jeu est redessiné en fonction du tableau courant"""
#--- on efface tout
can.delete(ALL)
#--- on reaffiche les indications en jeu
can.create_image(319,329,image=ressources.imageFond)
raquette.textePoints = can.create_text(10,5,text="SCORE : "+str(raquette.nombrePoints),anchor=NW,font="Century 10 normal bold",fill='white')
can.create_text(200,5,text="Menu : M",anchor=NW,font="Century 10 normal bold",fill='white')
can.create_text(350,5,text="Pause : P",anchor=NW,font="Century 10 normal bold",fill='white')
raquette.texteVie = can.create_text(590,5,text=" X "+str(raquette.nombreVie),anchor=NW,font="Century 10 normal bold",fill='white')
can.create_image(550,8,anchor=NW,image=ressources.image_raquetteVie)
can.create_text(10,640,text="ASTRE 3.0",anchor=NW,font="Century 10 normal bold",fill='white')
Bonus.textBonus = can.create_text(638,640,text='',anchor=NE,font="Century 10 normal bold",fill='white')
#--- on remet la raquette à son état initial
raquette.imageSprite = Raquette.imageNormale
raquette.caneva.itemconfigure(raquette.imageID,image=raquette.imageSprite)
raquette.set_dimension()
raquette.place()
raquette.tableauCibles = []
#--- on reitinialise les variables de classes
Sprite.matriceJeu = listeTableaux[tableauCourant]
Sprite.tableauSpritesMobiles = []
Sprite.tableauSpritesMobiles.append(raquette)
Brique.tableauObjetsIdentiques = []
Brique.tableauCibles = []
Brique.tableauBriquesCassables = []
Obus.tableauObjetsIdentiques = []
Obus.tableauCibles = []
Bonus.tableauObjetsIdentiques = []
Bonus.tableauCibles = []
#------------------------------------------------------------------------------------------------------------------------
# la matrice des briques de chaque tableau est remplie de :
# - (chiffre,sens de déplacement) : pour une brique de soliditée 'chiffre' et de mouvement 'sens de déplacement'
# ou de - [] : si il n'y a pas de brique
for indiceLigne in range(0,len(listeTableaux[tableauCourant])):
for indiceColonne in range(0,len(listeTableaux[tableauCourant][0])):
briqueCourante = listeTableaux[tableauCourant][indiceLigne][indiceColonne]
if briqueCourante:
# chaque objet 'Brique' est créé avec l'image d'indice 'chiffre' dans le tableau : 'tableauImagesBriques'
brique = Brique(Brique.tableauImagesBriques[briqueCourante[0]])
# sa soliditée est égale à ce 'chiffre'
brique.durabilitee = briqueCourante[0]
# si il est nul c'est une brique incassable, donc on ne l'ajoute pas au tableau 'tableauBriquesCassables'
# qui permet au moteur de jeu de déterminer quand le tableau est terminé
if brique.durabilitee != 0:
Brique.tableauBriquesCassables.append(brique)
# le nombre de points attribués quand la brique disparraît
brique.pointsAttribues = 50
# positionnement de la brique dans le canvas
brique.indiceLigne = indiceLigne
brique.indiceColonne = indiceColonne
brique.set_position(indiceColonne*(brique.largeur+2),indiceLigne*(brique.hauteur+2))
# comportement de la brique vis-à-vis des bords
brique.reaction_bord_droit = "rebond"
brique.reaction_bord_gauche = "rebond"
brique.reaction_bord_haut = "rebond"
brique.reaction_bord_bas = "rebond"
Brique.tableauObjetsIdentiques.append(brique)
# si c'est une brique en mouvement on lui imprime une vitesse et on l'ajoute au tableau des sprites mobiles
if briqueCourante[1] == 'horizontal':
brique.vitesseHorizontale = 4
brique.vitesse_X = brique.vitesseHorizontale
brique.sensDeplacementH = True
Sprite.tableauSpritesMobiles.append(brique)
elif briqueCourante[1] == 'vertical':
brique.vitesseVerticale = 4
brique.vitesse_Y = brique.vitesseVerticale
brique.sensDeplacementV = True
brique.rebord_bas = brique.rebord_bas - (2*brique.hauteur)
Sprite.tableauSpritesMobiles.append(brique)
# les objets 'Obus' l'auront pour cible
Obus.tableauCibles.append(brique)
# On détermine par un jet de 100,si la brique fera pop un bonus quand elle sera détruite
hazard = randrange(0,100)
if hazard <= 15:
brique.briqueBonus = True
# on positionne succesivement les objets 'brique' dans une matrice pour résoudre certain cas de collision (cf. méthodes de collision de l'objet brique)
Sprite.matriceJeu[brique.indiceLigne][brique.indiceColonne] = brique
brique.place()
determination_briques_cibles()
creation_balle()
def determination_briques_cibles():
"""Fonction qui met dans le tableau des cibles de chaque objets 'Brique' en mouvement créé, toutes les objets 'Brique' sur sa trajectoire."""
for brique in Brique.tableauObjetsIdentiques:
if brique.sensDeplacementH :
for briqueCible in Brique.tableauObjetsIdentiques:
if briqueCible.indiceLigne == brique.indiceLigne and briqueCible != brique:
brique.tableauCibles.append(briqueCible)
if brique.sensDeplacementV :
for briqueCible in Brique.tableauObjetsIdentiques:
if briqueCible.indiceColonne == brique.indiceColonne and briqueCible != brique:
brique.tableauCibles.append(briqueCible)
#----- Création de la 1ère balle pour le tableau courant ------#
def creation_balle():
"""Fonction qui crée la 1ère balle lors d'un nouveau tableau ou après la perte d'une vie."""
global balle,pause
balle = Balle(Balle.imageNormale)
# pour la variable 'pourcentageRaquette' voir la classe Raquette
# on considère la balle au centre à sa création donc son angle de renvoie vaut 90 degrés
balle.pourcentageRaquette = 0.5
balle.angleRenvoie = radians(balle.pourcentageRaquette * 180)
balle.set_position((raquette.coord_x+(balle.pourcentageRaquette*raquette.largeur))-(balle.largeur/2),(raquette.coord_y-balle.hauteur))
# les différentes réactions
balle.reaction = 'stop' # pour maintenir la balle sur la raquette (cf:mouvement())
balle.reaction_bord_droit = "rebond"
balle.reaction_bord_gauche = "rebond"
balle.reaction_bord_haut = "rebond"
balle.reaction_bord_bas = "destruction"
# la vitesse de la balle (module et composantes)
balle.vitesseDeBase = 10
balle.vitesse_X = balle.vitesseDeBase * cos(balle.angleRenvoie)
balle.vitesse_Y = -(balle.vitesseDeBase* sin(balle.angleRenvoie))
raquette.reaction = 'rebond'
raquette.fenetre.bind('<Button-1>',raquette.decolle_balle)
raquette.fenetre.bind('<n>',raquette.decolle_balle)
# variable de classe permettant d'avoir accès à toutes les balles
Balle.tableauObjetsIdentiques = []
Balle.tableauObjetsIdentiques.append(balle)
# variable de classe contenant toutes les cibles de la ou des balles
Balle.tableauCibles = []
balle.tableauCibles.append(raquette)
for brique in Brique.tableauObjetsIdentiques:
balle.tableauCibles.append(brique)
Brique.tableauCibles.append(balle)
for brique in Brique.tableauObjetsIdentiques:
brique.tableauCibles.append(balle)
Sprite.tableauSpritesMobiles.append(balle)
balle.place()
pause = False
fenetre.after(250,mouvement)
#------ Mise en mouvement des sprites mobiles ------#
def mouvement():
"""Fonction principale du moteur qui déplace chaque objet contenu dans la variable de classe
'Sprite' : 'tableauSpritesMobiles' suivant les composantes de leur vitesse."""
global pause,tableauCourant
for mobile in Sprite.tableauSpritesMobiles:
mobile.oldCoord = mobile.get_position() # on sauvegarde l'ancienne position du sprite
# si le sprite sort de l'écran et qu'aucune alternative (rebond,wrap,stop) n'est définie, sa méthode 'deplacement' renverra 'False'
if not mobile.deplacement():
can.delete(mobile.imageID) # alors on l'efface
if mobile in mobile.__class__.tableauObjetsIdentiques:
mobile.__class__.tableauObjetsIdentiques.remove(mobile) # on le retire de son groupe de classe
Sprite.tableauSpritesMobiles.remove(mobile) # il n'est plus concerné par les déplacements
# si c'est une balle on le retire des cibles des briques
if isinstance(mobile,Balle):
Brique.tableauCibles.remove(mobile)
if not Balle.tableauObjetsIdentiques: # si c'etait la dernière balle on perd une vie
pause = True # on arrete tout
vie_perdue() # si c'est possible une balle sera remise et on relancera cette boucle
# si c'est un bonus on le retire des cibles de la raquette
if isinstance(mobile,Bonus):
Raquette.tableauObjetsIdentiques[0].tableauCibles.remove(mobile)
mobile.newCoord = mobile.get_position() # on sauvegarde sa nouvelle position
# on effectue alors un test de collision avec chacun des sprites cibles
for sprite in mobile.tableauCibles:
#if sprite.coord_y <= mobile.coord_y+mobile.hauteur or sprite.coord_y+sprite.hauteur >= mobile.coord_y:
if mobile.test_collision(sprite) :
mobile.set_position(mobile.oldCoord[0],mobile.oldCoord[1]) # si collision on remet le sprite à sa position avant collision
# on détermibe alors de quel côté elle a eu lieu
# on positionne le sprite juste au bord de celui percuté
# et on invoque la méthode correspondant à ce côté
# Collision par la droite"
if mobile.coord_x + mobile.largeur <= sprite.coord_x:
mobile.coord_x = sprite.coord_x - mobile.largeur
mobile.oldCoord2 = mobile.get_position()
mobile.collision_droite(sprite)
break
# Collision par la gauche"
elif mobile.coord_x >= sprite.coord_x + sprite.largeur:
mobile.coord_x = sprite.coord_x + sprite.largeur
mobile.oldCoord2 = mobile.get_position()
mobile.collision_gauche(sprite)
break
# Collision par le Haut"
elif mobile.coord_y >= sprite.coord_y + sprite.hauteur:
mobile.coord_y = sprite.coord_y + sprite.hauteur
mobile.oldCoord2 = mobile.get_position()
mobile.collision_haut(sprite)
break
# Collision par le bas"
elif mobile.coord_y + mobile.hauteur <= sprite.coord_y:
mobile.coord_y = sprite.coord_y - mobile.hauteur
mobile.oldCoord2 = mobile.get_position()
mobile.collision_bas(sprite)
break
# il peut arriver qu'avant la collision les sprites se chevauchaient déja
# il est alors nécessaire de prévoir une méthode gérant ce cas de figure
else:
mobile.collision_deja_engagee(sprite)
# en debut de partie ou quand le bonus 'raquette collante' est actif
# la ou les balles sont en mode 'stop' quand elle sont au contact de la raquette,
# elle doivent donc rester à cette position jusqu'à ce qu'elle soit libérée
for mobile in Balle.tableauObjetsIdentiques:
if mobile.reaction == 'stop':
mobile.set_position((raquette.coord_x+(mobile.pourcentageRaquette*raquette.largeur))-(mobile.largeur/2),(raquette.coord_y-mobile.hauteur))
# si le tableau des briques cassables est vide on change de tableau ou la partie est terminée
if not Brique.tableauBriquesCassables:
pause = True
if tableauCourant == len(listeTableaux)-1:
partie_terminee()
else:
tableauCourant += 1
fenetre.after(500,creation_tableau)
# après avoir déplacer tous les sprites mobiles on les redessine
redessine_mobiles()
if not pause:
fenetre.after(30,mouvement)
def redessine_mobiles():
"""Fonction qui redessine chaque sprite mobile à sa nouvelle position."""
for mobile in Sprite.tableauSpritesMobiles:
mobile.dessine_sprite()
def mise_en_pause(event):
"""Fonction permettant de mettre le jeu en pause"""
global pause,textPause
if pause:
pause = False
can.delete(textPause)
fenetre.after(250,mouvement)
else:
textPause = can.create_text(270,250,text="PAUSE",anchor=NW,font="Century 15 normal bold",fill='white')
pause = True
#------ Vie perdue -----------------------------#
def vie_perdue():
"""Fonction invoquée lorqu'une vie est perdue."""
raquette.nombreVie -= 1
# on reinitialise les tableaux d'objets identiques des 'Bonus' et des 'Obus' et sprites mobiles
Bonus.tableauObjetsIdentiques = []
Obus.tableauObjetsIdentiques = []
Sprite.tableauSpritesMobiles = []
# on redessine l'air de jeu
can.delete(ALL)
can.create_image(319,329,image=ressources.imageFond)
can.itemconfigure(raquette.textePoints,state='normal')
raquette.textePoints = can.create_text(10,5,text="SCORE : "+str(raquette.nombrePoints),anchor=NW,font="Century 10 normal bold",fill='white')
can.create_text(200,5,text="Menu : M",anchor=NW,font="Century 10 normal bold",fill='white')
can.create_text(350,5,text="Pause : P",anchor=NW,font="Century 10 normal bold",fill='white')
can.create_image(550,8,anchor=NW,image=ressources.image_raquetteVie)
raquette.texteVie = can.create_text(590,5,text=" X "+str(raquette.nombreVie),anchor=NW,font="Century 10 normal bold",fill='white')
can.create_text(10,640,text="ASTRE 3.0",anchor=NW,font="Century 10 normal bold",fill='white')
texteBonus = can.create_text(638,640,text="",anchor=NE,font="Century 10 normal bold",fill='white')
Bonus.textBonus = can.create_text(638,640,text='',anchor=NE,font="Century 10 normal bold",fill='white')
# on remet la raquette dans son état normal
Raquette.tableauObjetsIdentiques[0].imageSprite = Raquette.imageNormale
raquette.caneva.itemconfigure(Raquette.tableauObjetsIdentiques[0].imageID,image=Raquette.imageNormale)
Raquette.tableauObjetsIdentiques[0].set_dimension()
raquette.reaction = 'rebond'
Raquette.tableauObjetsIdentiques[0].fenetre.unbind('<Button-1>')
Raquette.tableauObjetsIdentiques[0].fenetre.unbind('n')
raquette.place()
raquette.tableauCibles = []
Sprite.tableauSpritesMobiles.append(raquette)
# on réaffiche toutes les briques
for brique in Brique.tableauObjetsIdentiques:
brique.place()
if brique.vitesse_X != 0 or brique.vitesse_Y != 0:
Sprite.tableauSpritesMobiles.append(brique)
if raquette.nombreVie == 0:
partie_terminee()
else:
fenetre.after(250,creation_balle)
#--- Partie terminee ou game over -----#
def partie_terminee():
"""Verifie si le score actuel fait parti des 5 premiers"""
global pause,nom
pause = True
fenetre.unbind('<Right>')
fenetre.unbind('<Left>')
fenetre.unbind('<KeyRelease-Right>')
fenetre.unbind('<KeyRelease-Left>')
fenetre.unbind('<Motion>')
fenetre.unbind('<n>')
fenetre.unbind('<Button-1>')
if raquette.nombrePoints > scoresEtTableaux[0][-1][1]:
nom = ""
ecart = 0
message_top()
else:
fenetre.after(1000,menu_principal,'m')
def message_top():
"""Ecrit le texte ammenant l'utilisateur à entrer son pseudo"""
can.delete(ALL)
can.create_image(319,329,image=ressources.imageFond)
can.create_text(80,180,text="Votre score fait parti du top 5 !\n(pour cette liste de tableau)",anchor=NW,font="Century 15 normal bold",fill='white')
can.create_text(80,240,text="Veuillez saisir votre nom (10 lettres max) :",anchor=NW,font="Century 15 normal bold",fill='white')
fenetre.bind('<Key>',recup_nom)
fenetre.bind('<Return>',valide_nom)
fenetre.bind('<BackSpace>',efface_lettre)
can.create_text(180,280,text=nom+"_",anchor=NW,font="Century 15 normal bold",fill='white')
def efface_lettre(event):
"""Efface une lettre de la variable nom"""
global nom
nom = nom[:-1]
message_top()
def recup_nom(event):
"""Récupère la lettre tappée et l'ajoute à la variable 'nom'"""
global nom
c = event.char
if len(nom) < 10:
nom += c
message_top()
def valide_nom(event):
"""Le nom est ajouté a la liste des scores.Celle-ci est triée par
scores croissants.Le dernier score est retiré de la liste."""
scoresEtTableaux[0].append((nom[:10],raquette.nombrePoints))
tab_score = scoresEtTableaux[0]
tab_scores_range = sorted(tab_score, key=lambda tab_score: tab_score[1],reverse=True)
scoresEtTableaux[0] = tab_scores_range[:]
del scoresEtTableaux[0][-1]
fichier = open(nomTableau,'w')
pickle.dump(scoresEtTableaux,fichier)
fichier.close
fenetre.unbind('<Key>')
fenetre.unbind('<Return>')
fenetre.unbind('<Left>')
can.after(1000,option_scores,'4')
#------- Menu principal ----------------#
def menu_principal(event):
"""Création de l'interface de menu."""
global pause
pause = True
fenetre.unbind('<Key>')
fenetre.unbind('<Return>')
fenetre.unbind('<Button-1>')
fenetre.unbind("<Button1-Motion>")
fenetre.unbind('<Right>')
fenetre.unbind('<Left>')
fenetre.unbind('7')
fenetre.unbind('8')
fenetre.unbind('1')
fenetre.unbind('2')
fenetre.unbind('3')
fenetre.unbind('4')
fenetre.unbind('s')
fenetre.unbind('0')
fenetre.unbind('g')
fenetre.unbind('c')
fenetre.unbind('+')
fenetre.unbind('-')
fenetre.unbind('h')
fenetre.unbind('v')
fenetre.unbind('f')
can.delete(ALL)
can.create_image(319,329,image=ressources.imageFond)
can.create_text(180,200,text="A S T R E 3.0",anchor=NW,font="Century 28 normal bold",fill='white')
can.create_text(180,250,text="1 - Nouvelle Partie",anchor=NW,font="Century 18 normal bold",fill='white')
can.create_text(180,280,text="2 - Mode Edition",anchor=NW,font="Century 18 normal bold",fill='white')
can.create_text(180,310,text="3 - Charger Des Tableaux",anchor=NW,font="Century 18 normal bold",fill='white')
can.create_text(180,340,text="4 - Scores",anchor=NW,font="Century 18 normal bold",fill='white')
can.create_text(180,370,text="5 - Quitter",anchor=NW,font="Century 18 normal bold",fill='white')
can.create_text(440,640,text="Auteur - Guillaume Michon",anchor=NW,font="Century 10 normal bold",fill='white')
fenetre.bind('1',tableaux_de_base)
fenetre.bind('2',option_edition)
fenetre.bind('3',tableaux_personnels)
fenetre.bind('4',option_scores)
fenetre.bind('5',quitter)
def nouvelle_partie():
"""Fonction invoquée lors d'une nouvelle partie."""
global tableauCourant,raquette
fenetre.unbind('1')
fenetre.unbind('2')
fenetre.unbind('3')
fenetre.unbind('4')
fenetre.unbind('5')
fenetre.bind('<m>',menu_principal)
fenetre.bind('<p>',mise_en_pause)
# un objet raquette est créé et initialisé à chaque nouvelle partie
raquette = Raquette(Raquette.imageNormale)
raquette.set_position(260,638)
raquette.place()
raquette.vitesseHorizontale = 20
raquette.reaction = 'rebond'
raquette.souris_deplacement_vertical = False
raquette.fenetre.bind('<Right>',raquette.touche_droite)
raquette.fenetre.bind('<Left>',raquette.touche_gauche)
raquette.fenetre.bind('<KeyRelease-Right>',raquette.touche_droite_relache)
raquette.fenetre.bind('<KeyRelease-Left>',raquette.touche_gauche_relache)
raquette.fenetre.bind('<Motion>',raquette.souris)
raquette.nombrePoints = 0
raquette.nombreVie = 3
raquette.seuilVieSupp = 10000
raquette.pasSeuilVieSupp = 10000
Raquette.tableauObjetsIdentiques = []
Raquette.tableauObjetsIdentiques.append(raquette)
# on invoque la fonction de création du premier tableau ( ou celui désiré pour un test)
tableauCourant = 0
creation_tableau()
def option_scores(event):
"""Fonction qui invoque la méthode d'affichage des scores."""
fenetre.bind('<m>',menu_principal)
nomTableau = "tableau.cb"
# 'scores' est définie dans le module 'module_scores'
scores(fenetre,can,nomTableau)
def option_edition(event):
"""Fonction qui invoque le mode édition."""
fenetre.bind('<m>',menu_principal)
# 'edition' est définie dans le module 'module_edition'
edition(fenetre,can)
def quitter(event):
"""Quitte l'application."""
fenetre.quit()
fenetre.destroy()
if __name__ == '__main__':
#--- Fenêtre Principale ---
fenetre = Tk()
fenetre.title("Astre 3.0")
fenetre.iconbitmap("Fond2.ico")
fenetre.resizable(0,0)
hauteurEcran = fenetre.winfo_height()
largeurEcran = fenetre.winfo_width()
fenetre.winfo_screenwidth()
pos_x = str(((fenetre.winfo_screenwidth()-largeurEcran)/2)-300)
pos_y = str(((fenetre.winfo_screenheight()-hauteurEcran)/2)-300)
pos = '+' + pos_x + '+' + pos_y
fenetre.geometry(pos)
#--- Canevas Principal ---
can = Canvas(fenetre,bg='black',width=638,height=658)
can.pack()
#--- Création d'un objet 'ressources' contenant les images et sons ---
ressources = Ressources()
Sprite.fenetre = fenetre
Sprite.caneva = can
Raquette.imagePetite = ressources.image_raquettePetite
Raquette.imageNormale = ressources.image_raquette
Raquette.imageGrande = ressources.image_raquetteGrande
Raquette.imageGun = ressources.image_raquetteGun
Raquette.imageCollante = ressources.image_raquetteCollante
Balle.imageNormale = ressources.image_balle
Balle.imageTueuse = ressources.image_balleTueuse
Brique.imageIncassable = ressources.image_briqueIncassable
Brique.imageUnCoup = ressources.image_brique1coups
Brique.imageDeuxCoups = ressources.image_brique2coups
Brique.imageTroisCoups = ressources.image_brique3coups
Brique.tableauImagesBriques = [Brique.imageIncassable,Brique.imageUnCoup,Brique.imageDeuxCoups,Brique.imageTroisCoups]
Bonus.tableauBonus = []
Bonus.tableauBonus.append((ressources.image_bonusMultiball,'multi balle'))
Bonus.tableauBonus.append((ressources.image_bonusBalleTueuse,'balle tueuse'))
Bonus.tableauBonus.append((ressources.image_bonusGun,'raquette gun'))
Bonus.tableauBonus.append((ressources.image_bonusRaquettePetite,'petite raquette'))
Bonus.tableauBonus.append((ressources.image_bonusRaquetteNormale,'raquette normale'))
Bonus.tableauBonus.append((ressources.image_bonusRaquetteGrande,'grande raquette'))
Bonus.tableauBonus.append((ressources.image_bonusColle,'raquette collante'))
Bonus.tableauBonus.append((ressources.image_bonusPoints,'bonus points'))
Bonus.tableauBonus.append((ressources.image_bonusVie,'bonus vie'))
Bonus.tableauBonus.append((ressources.image_bonusBallelente,'balle lente'))
Bonus.tableauBonus.append((ressources.image_bonusBalleNormale,'balle normale'))
Bonus.tableauBonus.append((ressources.image_bonusBallerapide,'balle rapide'))
Obus.imageObus = ressources.image_obus
menu_principal('m')
fenetre.mainloop()
Vous n'êtes pas encore membre ?
inscrivez-vous, c'est gratuit et ça prend moins d'une minute !
Les membres obtiennent plus de réponses que les utilisateurs anonymes.
Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.
Le fait d'être membre vous permet d'avoir des options supplémentaires.