#!/usr/bin/env python # -*- coding: utf-8 -*- # logiciel pour Python2 # (c) 2014 Raphaël SEBAN (tarball69) # publié sous licence GNU General Public License GPLv3 # cf http://www.gnu.org/licenses/gpl.html # on demande en premier lieu # la librairie graphique Tkinter from Tkinter import * # on veut aussi utiliser # les nombres aléatoires import random # ------------------- DEBUT DU PROGRAMME ------------------------------- def debut_jeu (): "le programme commence ici" # init variables globales global fenetre, canvas, bouton_jouer global c_width, c_height, center_x, center_y # on crée la fenêtre principale fenetre = Tk() fenetre.title("Mon super jeu !") fenetre.resizable(width=False, height=False) # on ajoute des composants graphiques canvas = Canvas(fenetre, bg="black", width=400, height=300) canvas.pack(padx=5, pady=5) # on met des données courantes en globales c_width = canvas.winfo_reqwidth() c_height = canvas.winfo_reqheight() center_x = c_width // 2 center_y = c_height // 2 # bouton 'jouer' bouton_jouer = Button(fenetre) bouton_jouer.pack(side=LEFT, padx=50, pady=5) # bouton 'quitter' Button( fenetre, text="Quitter", command=fenetre.destroy ).pack(side=RIGHT, padx=5, pady=5) # on affiche une présentation du jeu presenter_jeu() # et on lance la # boucle événementielle principale fenetre.mainloop() # end def def presenter_jeu (): "on affiche ici une présentation du jeu" # on commence toujours par effacer le canevas graphique # qui nous fait office d'écran de jeu canvas.delete(ALL) # on affiche un titre # par rapport au centre de l'écran de jeu canvas.create_text( center_x, center_y - 100, text="Terrain miné", font="sans 16 bold", fill="navajowhite2", # couleur du texte ) # on affiche des règles du jeu succinctes canvas.create_text( center_x, center_y + 30, text="""\ Règles du jeu: Vous devez déplacer une grosse boule vers un carré placé en bas de l'écran. Malheureusement pour vous, le terrain est miné ! Saurez-vous atteindre votre but sans vous faire exploser ? Utilisez les flèches du clavier pour vous déplacer. """, justify=LEFT, font="sans 10 bold italic", fill="Pale Goldenrod", width=0.8 * c_width, # 80% de la largeur totale ) # on s'assure que le joueur pourra (re)jouer bouton_jouer.configure( text="Jouer !", command=nouvelle_partie, state=NORMAL ) # end def def nouvelle_partie (): "on réinitialise une nouvelle partie ici" # init variables globales global tid_joueur, tid_mines # on autorise le joueur à arrêter la partie quand il le souhaite bouton_jouer.configure( text="Stop !", command=arreter_partie, state=NORMAL ) # on efface le canevas graphique canvas.delete(ALL) # on crée l'avatar du joueur (boule) creer_joueur() # on crée le carré d'arrivée creer_carre_final() # on mine le terrain miner_terrain() # on associe les touches fléchées du clavier # à nos propres fonctions de gestion du mouvement fenetre.bind_all("", deplacer_haut) fenetre.bind_all("", deplacer_bas) fenetre.bind_all("", deplacer_gauche) fenetre.bind_all("", deplacer_droite) # c'est ici qu'on lance les boucles inscrites dans le temps tid_joueur = fenetre.after(100, boucle_joueur) # les mines aussi vont avoir leur propre cycle de vie tid_mines = fenetre.after(1000, boucle_mines) # end def def creer_joueur (): """ on crée l'avatar du joueur; ici le joueur est représenté par une grosse boule; on conserve l'identifiant de l'objet sur le canevas; ça nous servira plus tard; on prend des coordonnées au hasard; """ # init variables globales global diametre_boule, rayon global boule_x, boule_y global sens_x, sens_y, pas global boule_joueur # inits diametre_boule = 20 # diamètre de la boule rayon = diametre_boule // 2 boule_x = diametre_boule + random.randint(0, c_width - diametre_boule) boule_y = diametre_boule + random.randint(0, 10) sens_x, sens_y = (0, 0) # sens de déplacement initial pas = random.randint(2, 5) # quantité de déplacement (pixels) # on dessine l'avatar joueur (boule) boule_joueur = canvas.create_oval( boule_x - rayon, boule_y - rayon, boule_x + rayon, boule_y + rayon, outline="ivory2", # couleur du contour width=1, # épaisseur du trait de contour (pixels) fill="light sky blue", # couleur du remplissage ) # end def def creer_carre_final (): """ on crée le carré final (carré d'arrivée); on conserve l'identifiant de l'objet sur le canevas; ça nous servira plus tard; on prend des coordonnées au hasard; """ # init variables globales global taille_carre, demi_carre global carre_x, carre_y global carre_final # on crée le carré d'arrivée taille_carre = diametre_boule + 6 demi_carre = taille_carre // 2 carre_x = 10 + random.randint(0, c_width - taille_carre - 10) carre_y = c_height - taille_carre - random.randint(5, 20) # on dessine le carré final (arrivée) carre_final = canvas.create_rectangle( carre_x - demi_carre, carre_y - demi_carre, carre_x + demi_carre, carre_y + demi_carre, outline="ivory", # couleur du contour width=1, # épaisseur du trait de contour (pixels) fill="palegreen1", # couleur du remplissage ) # end def def miner_terrain (): "on mine le terrain au début d'une partie" # on choisit un nombre au hasard nombre_mines = random.randint(10, 20) # on boucle pour poser autant de mines for i in range(nombre_mines): # on pose une mine au hasard poser_mine() # end for # end def def poser_mine (): """ on pose une mine au hasard, mais jamais dans le voisinage immédiat du joueur; """ # init variables locales tailles = [8, 10, 12] couleurs = ["sandy brown", "peru", "sienna1", "coral1"] couleur_contour = "burlywood1" # on boucle tant que ce n'est pas bon # ici, on bouclera cent fois (par précaution) # et on abandonnera si on n'y arrive pas for i in range(100): # on choisit de nouvelles coordonnées au hasard mine_x = random.randint(0, c_width) mine_y = random.randint(0, c_height) # les mines ont une taille variable; taille_mine = random.choice(tailles) # on recherche les collisions avec d'autres objets collisions = canvas.find_overlapping( *b_rect(mine_x, mine_y, taille_mine) ) # aucun objet dans le voisinage ? if not collisions: # on choisit une couleur parmi une palette color = random.choice(couleurs) # on peut poser notre mine ! canvas.create_oval( b_rect(mine_x, mine_y, taille_mine//2), outline=couleur_contour, width=1, # épaisseur de trait de contour (pixels) fill=color, # couleur du remplissage ) # on remonte la boule joueur # au premier plan d'affichage canvas.tag_raise(boule_joueur, ALL) # c'est bon, on a posé notre mine # on peut quitter la boucle break # end if # end for # end def def b_rect (x, y, size): """ retourne les coordonnées (top, left, bottom, right) d'un rectangle de délimitation autour d'un point P(x, y); """ # rectangle d'influence (bounding rectangle) return (x - size, y - size, x + size, y + size) # end def def boucle_joueur (): "boucle temporelle qui gère les mouvements du joueur" # init variables globales global boule_x, boule_y global tid_joueur # le joueur a atteint le carré final ? if carre_x - demi_carre < boule_x < carre_x + demi_carre and \ carre_y - demi_carre < boule_y < carre_y + demi_carre: # bravo ! c'est gagné ! return bravo() # end if # on recherche des collisions collisions = canvas.find_overlapping(*canvas.bbox(boule_joueur)) # le joueur a heurté autre chose que le carré final ? if len(collisions) > 1 and carre_final not in collisions: # aïe ! dommage ! return game_over() # end if # tout est OK on déplace le joueur boule_x = (boule_x + sens_x * pas) % c_width boule_y = (boule_y + sens_y * pas) % c_height canvas.coords( boule_joueur, boule_x - rayon, boule_y - rayon, boule_x + rayon, boule_y + rayon, ) # on boucle à nouveau dans le temps tid_joueur = fenetre.after(50, boucle_joueur) # end def def boucle_mines (): "boucle temporelle qui gère la vie des mines" # init variables globales global tid_mines # on pose une mine de temps en temps poser_mine() # prochaine mine dans... (millisecondes) delai = random.randint(1000, 2000) # on relance la boucle après ce délai tid_mines = fenetre.after(delai, boucle_mines) # end def def bravo (): "super ! le joueur a réussi !" # on affiche l'écran final ecran_final( titre="BRAVO", sous_titre="C'est gagné !", couleur_titre="lemon chiffon", couleur_sous_titre="pale green", ) # end def def game_over (): "c'est ballot ! le joueur a perdu !" # on affiche l'écran final ecran_final( titre="GAME OVER", sous_titre="C'est perdu !", couleur_titre="firebrick1", couleur_sous_titre="floral white", ) # end def def arreter_partie (): "le joueur souhaite arrêter la partie" # on affiche l'écran final ecran_final( titre="ABANDON", sous_titre="Partie annulée !", couleur_titre="gold", couleur_sous_titre="light grey", ) # end def def ecran_final (**kw): "on effectue ici des opérations communes à tous les écrans finaux" # on bloque les clics intempestifs sur le bouton 'jouer' bouton_jouer.configure(state=DISABLED) # on stoppe les boucles temporelles qui pourraient # éventuellement être encore en cours d'exécution arreter_boucles() # on efface le canevas graphique canvas.delete(ALL) # on récupère le titre et sa couleur titre = kw.get("titre") or "" couleur = kw.get("couleur_titre") or "white" # on affiche le titre canvas.create_text( center_x, center_y - 40, text=titre, font="sans 36 bold", fill=couleur, ) # on récupère le sous-titre et sa couleur sous_titre = kw.get("sous_titre") or "" couleur = kw.get("couleur_sous_titre") or "white" # on affiche le sous-titre canvas.create_text( center_x, center_y, text=sous_titre, font="sans 16 bold italic", fill=couleur, ) # on relance presenter_jeu() après @delai # avec toutefois un minimum de 1 seconde delai = kw.get("delai") or 2000 # valeur par défaut fenetre.after(max(1000, delai), presenter_jeu) # end def def arreter_boucles (): "on arrête les boucles temporelles susceptibles de tourner encore" # init variables globales global tid_joueur, tid_mines # stooop ! fenetre.after_cancel(tid_joueur) fenetre.after_cancel(tid_mines) # RAZ thread ids tid_joueur = tid_mines = 0 # end def def deplacer_haut (event=None): "on déplace l'avatar du joueur vers le haut" # init variables globales global sens_x, sens_y # grâce à la boucle du jeu, # nous n'avons pas besoin de plus que ça sens_x, sens_y = (0, -1) # end def def deplacer_bas (event=None): "on déplace l'avatar du joueur vers le bas" # init variables globales global sens_x, sens_y # grâce à la boucle du jeu, # nous n'avons pas besoin de plus que ça sens_x, sens_y = (0, 1) # end def def deplacer_gauche (event=None): "on déplace l'avatar du joueur vers la gauche" # init variables globales global sens_x, sens_y # grâce à la boucle du jeu, # nous n'avons pas besoin de plus que ça sens_x, sens_y = (-1, 0) # end def def deplacer_droite (event=None): "on déplace l'avatar du joueur vers la droite" # init variables globales global sens_x, sens_y # grâce à la boucle du jeu, # nous n'avons pas besoin de plus que ça sens_x, sens_y = (1, 0) # end def # ------------------- FIN DU PROGRAMME --------------------------------- # ce script est automatiquement lancé grâce à ceci : # /!\ à connaître par coeur /!\ if __name__ == "__main__": # lancer le programme debut_jeu() # end if