# -*- coding: utf-8 -*-
#
# import des librairies
from time import sleep # pour rendre transparent côté élève l'utilisation de "sleep"
from random import randint #pour la fonction vecteur2 et pour cercle_aleatoire
from .detection_collision import *
import sys
import math
import pygame
from .couleurs import rgb
from .touches_pygames import TOUCHES_PYGAMES
# Un petit message invitant a lire la doc
print("""
Merci d'utiliser la librairie graphique du module pydiderotlibs.\n
N'hésitez pas à consulter la documentation en ligne:\n
https://pydiderotlibs.rtfd.io/librairies/graphique.html
""")
# On définit les variables globales.
# Si quelqu'un veut s'amuser à sous-classer pygame.Surface pour faire plus propre il est bienvenue
_axeMath = False
_fenetre = None
_autorefresh = True
def creer_fenetre(largeur=600, hauteur=600, orientation_axe_ordonnees=False, titre="Fenetre graphique", autorefresh=True):
fenetre(largeur, hauteur, orientation_axe_ordonnees, titre, autorefresh)
def window(largeur=600, hauteur=500, orientation_axe_ordonnees=False, titre="Fenetre graphique", autorefresh=True):
fenetre(largeur, hauteur, orientation_axe_ordonnees, titre, autorefresh)
[docs]def fenetre(
largeur=600,
hauteur=500,
orientation_axe_ordonnees=False,
titre="Fenetre graphique",
autorefresh=True
):
"""
Crée et affiche une fenêtre graphique.
Alias: ``window()``, ``creer_fenetre()``
Arguments:
largeur (int, optionnel): Largeur de la fenetre en pixels (``600`` par défaut)
hauteur (int, optionnel): Hauteur de la fenetre en pixels (``500`` par défaut)
orientation_axe_ordonnees: Si on met cet argument à True, l'axe des ordonnées sera orienté de bas en haut comme en maths. Sinon il est orienté dans l'autre sens comme habituellement en informatique (``False`` par défaut)
titre (str, optionnel): Titre de la fenetre (``Fenetre graphique`` par défaut)
autorefresh(bool, optionnel): Active le rafraichissement automatique de la fenetre graphique (`False` par défaut)
"""
pygame.init()
global _fenetre
global _axeMath
global _autorefresh
_axeMath = orientation_axe_ordonnees
_autorefresh = autorefresh
_fenetre = pygame.display.set_mode((largeur, hauteur))
pygame.display.set_caption(titre)
# active la répétition des touches
pygame.key.set_repeat(1)
_fenetre.fill(rgb('blanc'))
pygame.display.update()
def _ordo(y):
global _axeMath
ymax = pygame.display.Info().current_h
if _axeMath:
return ymax-y
return y
[docs]def rafraichir():
"""
Rafraîchie la fenêtre graphique.
C'est uniquement utile si vous désactivez l'option :ref:`autorefresh <autorefresh>`.
"""
pygame.display.update()
def ecoute_evenements():
return demande_evenements()
def events():
return demande_evenements()
[docs]def demande_evenements():
"""
Récupère les évenements pygame gère la fermeture de la fenetre et retourne les évenements formatés.
Renvoie un dictionnaire d'évenements formaté comme suit:
``{'touche1': None, 'touche2':None, 'souris': [x,y], 'click': [x,y]}``
Les valeurs ``None`` pour les touches peuvent surprendre mais il est nécéssaire d'utiliser un dictionnaire pour avoir les coordonnées
éventuelles de la souris lors d'un click par exemple. Pour les touches clavier, l'importance est la présence de la cléf et la valeur associée est donc ``None``.
- Les caractères alphanumériques sont encodés en ascii (``'a'``, ``'n'``, ``';'``) et, si présent, leur valeur est ``None``.
- les touches spéciales ont les clefs ``'espace'``, ``'haut'``, ``'bas'``, ``'droite'``, ``'gauche'`` et, si présent, leur valeur est ``None``.
- Un clic avec le bouton gauche de la souris ajoute une clef ``'clic'``. Sa valeur est une liste ``[x, y]`` des coordonnées de la souris.
- Un déplacement de la souris ajoute une clef ``'souris'``. Sa valeur est une liste ``[x, y]`` des coordonnées de la souris.
Alias: ``events()``, ``ecoute_evenements()``
"""
# Initialisation du dictionnaire de sortie
evenements = {}
for event in pygame.event.get():
# Gestion de la fermeture de la fenetre
if event.type == pygame.QUIT:
sys.exit()
# Gestion des evenements souris
elif event.type == pygame.MOUSEMOTION:
evenements['souris'] = list(event.pos)
elif event.type == pygame.MOUSEBUTTONDOWN:
evenements['clic'] = list(event.pos)
# Gestion des touches. pygame.KeyDown ne va pas gérer plusieurs touches enfoncées
# https://www.pygame.org/docs/ref/key.html#pygame.key.get_pressed
touches = pygame.key.get_pressed()
for touche, k_touche in TOUCHES_PYGAMES.items():
if touches[getattr(pygame, k_touche)]:
evenements[touche] = None
return evenements
[docs]def efface(couleur='blanc'):
"""
Efface l'écran.
Arguments:
couleur (:ref:`couleur <couleur>`, optionnel): Couleur de remplissage de l'écran (``'blanc'`` par défaut).
"""
_fenetre.fill(rgb(couleur))
if _autorefresh:
pygame.display.update()
def erase(couleur='blanc'):
efface(couleur)
def trace_cercle(x, y, couleur='bleu', rayon=25, epaisseur=0):
cercle(x, y, couleur, rayon, epaisseur)
def circle(x, y, couleur='bleu', rayon=25, epaisseur=0):
cercle(x, y, couleur, rayon, epaisseur)
[docs]def cercle(x, y, couleur='bleu', rayon=25, epaisseur=0):
"""
Trace un cercle dans la fenetre graphique.
Alias: ``circle()``, ``trace_cercle()``
Arguments:
x (int): Abscisse du centre du cercle
y (int): Ordonnée du centre du cercle
rayon (int, optionnel): Rayon du cercle (25 par défaut)
epaisseur (int, optionnel): Epaisseur du cercle (``0`` par défaut). Si ``0``, le cercle sera rempli et apparaitra comme un disque.
couleur (:ref:`couleur <couleur>`, optionnel): Couleur du cercle (bleu par défaut)
"""
couleur = rgb(couleur)
pygame.draw.circle(_fenetre, couleur, (x, _ordo(y)), rayon, epaisseur)
if _autorefresh:
pygame.display.update()
def trace_cercle_aleatoire(couleur='bleu', rayon=5, epaisseur=0):
cercle_aleatoire(couleur, rayon, epaisseur)
def random_circle(couleur='bleu', rayon=5, epaisseur=0):
cercle_aleatoire(couleur, rayon, epaisseur)
def randcircle(couleur='bleu', rayon=5, epaisseur=0):
cercle_aleatoire(couleur, rayon, epaisseur)
[docs]def cercle_aleatoire(couleur='bleu', rayon=5, epaisseur=0):
"""
Trace un (petit) cercle dans la fenetre graphique, à un endroit choisit au hasard.
(Utile pour fairre de la neige par exemple.)
Alias: ``random_circle()``, ``randcircle()``, ``trace_cercle_aleatoire()``
Arguments:
rayon (int, optionnel): Rayon du cercle (5 par défaut)
epaisseur (int, optionnel): Epaisseur du cercle (``0`` par défaut). Si ``0``, le cercle sera rempli et apparaitra comme un disque.
couleur (:ref:`couleur <couleur>`, optionnel): Couleur du cercle (bleu par défaut)
"""
couleur = rgb(couleur)
ymax = pygame.display.Info().current_h
xmax = pygame.display.Info().current_w
centre = (randint(-rayon, xmax + rayon),randint(-rayon, ymax + rayon))
pygame.draw.circle(_fenetre, couleur, centre, rayon, epaisseur)
if _autorefresh:
pygame.display.update()
def trace_point(x, y, couleur='bleu'):
point(x, y, couleur)
[docs]def point(x, y, couleur='bleu'):
"""
Trace un point dans la fenetre graphique.
Alias: ``trace_point()``
Arguments:
x (int): Abscisse du point
y (int): Ordonnée du point
couleur (:ref:`couleur <couleur>`, optionnel): Couleur du point (bleu par défaut)
"""
couleur = rgb(couleur)
pygame.draw.circle(_fenetre, couleur, (x, _ordo(y)), 1, 0)
if _autorefresh:
pygame.display.update()
def trace_rectangle(x, y, largeur=100, hauteur=50, couleur='bleu', epaisseur=0):
rectangle(x, y, largeur, hauteur, couleur, epaisseur)
[docs]def rectangle(x, y, largeur=100, hauteur=50, couleur='bleu', epaisseur=0):
"""
Trace un rectangle horizontal dans la fenetre graphique .
Le sommet haut-gauche à pour coordonnées ``(x,y)``, la ``largeur`` est la taille en abscisse
et la ``hauteur`` la taille en ordonnée.
Alias: ``trace_rectangle()``
Arguments:
x (int): abscisse du sommet haut gauche du rectangle
y (int): ordonnée du sommet haut gauche du rectangle
largeur (int): taille du rectangle sur l'axe des abscisses
hauteur (int): taille du rectangle sur l'axe des ordonnées
couleur (:ref:`couleur <couleur>`, optionnel): Couleur du rectangle (``bleu`` par défaut)
epaisseur (int, optionnel): Epaisseur des cotés du rectangle (``0`` par défaut). Si ``0``, le rectangle est rempli.
"""
couleur = rgb(couleur)
pygame.draw.rect(_fenetre, couleur, (x, _ordo(y), largeur, hauteur), epaisseur)
if _autorefresh:
pygame.display.update()
def trace_triangle(x1, y1, x2, y2, x3, y3, couleur='bleu', epaisseur=0):
triangle(x1, y1, x2, y2, x3, y3, couleur, epaisseur)
[docs]def triangle(x1, y1, x2, y2, x3, y3, couleur='bleu', epaisseur=0):
"""
Trace un triangle dans la fenetre graphique .
Alias: ``trace_triangle()``
Arguments:
x1 (int): abscisse du premier sommet du triangle
y1 (int): ordonnée du premier sommet du triangle
x2 (int): abscisse du deuxième sommet du triangle
y2 (int): ordonnée du deuxième sommet du triangle
x3 (int): abscisse du troisième sommet du triangle
y3 (int): ordonnée du troisième sommet du triangle
couleur (:ref:`couleur <couleur>`, optionnel): Couleur du triangle (``bleu`` par défaut)
epaisseur (int, optionnel): Epaisseur des cotés du triangle (``0`` par défaut). Si ``0``, le triangle est rempli.
"""
couleur = rgb(couleur)
pygame.draw.polygon(_fenetre, couleur, [(x1, _ordo(y1)), (x2, _ordo(y2)), (x3, _ordo(y3))], epaisseur)
if _autorefresh:
pygame.display.update()
def trace_segment(x1, y1, x2, y2, couleur='bleu', epaisseur=2):
segment(x1, y1, x2, y2, couleur, epaisseur)
[docs]def segment(x1, y1, x2, y2, couleur='bleu', epaisseur=2):
"""
Trace un segment entre les points de coordonées ``(x1, y1)`` et ``(x2, y2)``.
Alias: ``trace_segment()``
Arguments:
x1 (int): abscisse de la première extremité du segment
y1 (int): ordonnée de la première extremité du segment
x2 (int): abscisse de la deuxieme extrémité du segment
y2 (int): ordonnée de la deuxieme extrémité du segment
couleur (:ref:`couleur <couleur>`, optionnel): Couleur du segment (``bleu`` par défaut)
epaisseur (int, optionnel): Epaisseur du segment (``2`` par défaut)
"""
couleur = rgb(couleur)
pygame.draw.lines(_fenetre, couleur, False, [(x1, _ordo(y1)), (x2, _ordo(y2))], epaisseur)
if _autorefresh:
pygame.display.update()
def trace_vecteur(x, y, v, couleur='rouge', epaisseur=2):
vecteur(x, y, v, couleur, epaisseur)
def vector(x, y, v, couleur='rouge', epaisseur=2):
vecteur(x, y, v, couleur, epaisseur)
[docs]def vecteur(x, y, v, couleur='rouge', epaisseur=2):
"""
Trace la représentation du vecteur ``v`` à partir du point d'origine ``(x, y)``.
Alias: ``vector()``, ``trace_vecteur()``
Arguments:
x (int): abscisse du point d'origine de la représentation du vecteur
y (int): ordonnée du point d'origine de la représentation du vecteur
v (list): Coordonnées de la deuxieme extrémité du segment
couleur (:ref:`couleur <couleur>`, optionnel): Couleur du segment (``rouge`` par défaut)
epaisseur (int, optionnel): Epaisseur du segment (``2`` par défaut)
"""
couleur = rgb(couleur)
trace_segment(x, y, x + v[0], y + v[1], couleur, epaisseur)
w1 = [0, 0]
w2 = [0, 0]
w1[0] = -.3 * math.cos(15 * math.pi / 180) * v[0] + .3 * math.sin(15 * math.pi / 180) * (-v[1])
w1[1] = -.3 * math.cos(15 * math.pi / 180) * v[1] + .3 * math.sin(15 * math.pi / 180) * v[0]
w2[0] = -.3 * math.cos(15 * math.pi / 180) * v[0] - .3 * math.sin(15 * math.pi / 180) * (-v[1])
w2[1] = -.3 * math.cos(15 * math.pi / 180) * v[1] - .3 * math.sin(15 * math.pi / 180) * v[0]
pygame.draw.polygon(
_fenetre,
couleur,
[
(x + v[0], _ordo(y + v[1])),
(x + v[0] + w1[0], _ordo(y + v[1] + w1[1])),
(x + v[0] + w2[0], _ordo(y + v[1] + w2[1])),
(x + v[0], _ordo(y + v[1]))
],
0
)
if _autorefresh:
pygame.display.update()
[docs]def vecteur2(xv, yv, couleur='rouge', epaisseur=2):
"""
Trace la représentation du vecteur de coordonnées ``(xv, yv)`` à partir d'une origine choisie au hasard.
Alias: ``vector2()``, ``trace_vecteur2()``
Arguments:
xv (int): abscisse du vecteur
yv (int): ordonnée du vecteur
couleur (:ref:`couleur <couleur>`, optionnel): Couleur du segment (``rouge`` par défaut)
epaisseur (int, optionnel): Epaisseur du segment (``2`` par défaut)
"""
ymax = pygame.display.Info().current_h
xmax = pygame.display.Info().current_w
x = randint(xv, xmax - xv)
y = randint(yv, ymax - yv)
v = [xv, yv]
vector(x, y, v, couleur, epaisseur)
def trace_vecteur2(xv, yv, couleur='rouge', epaisseur=2):
vecteur2(xv, yv, couleur, epaisseur)
def vector2(xv, yv, couleur='rouge', epaisseur=2):
vecteur2(xv, yv, couleur, epaisseur)
def trace_image(x, y, nom, largeur=100, hauteur=100):
image(x, y, nom, largeur, hauteur)
[docs]def image(x, y, nom, largeur=100, hauteur=100):
"""
Trace une image dans la fenetre graphique.
Alias:``trace_image()``
Arguments:
x (int): Abscisse du centre de l'image
y (int): Ordonnée du centre de l'image
nom (str) : nom du fichier image (qui doit être dans le répertoire du script)
largeur (int, optionnel): Largeur de l'image (100 par défaut)
hauteur (int, optionnel): Hauteur de l'image (100 par défaut)
"""
pygame_image = pygame.transform.scale(pygame.image.load(
nom).convert_alpha(), (largeur, hauteur))
_fenetre.blit(pygame_image, (int(x - largeur / 2), _ordo(int(y - hauteur / 2))))
if _autorefresh:
pygame.display.update()
def trace_explosion(x, y, couleur='orange', r=50, c=0.5, n=10):
explosion(x, y, couleur, r, c, n)
[docs]def explosion(x, y, couleur='orange', r=25, c=0.5, n=10):
'''
Trace un polygône régulier étoilé à ``2n`` côté,
de rayon extérieur ``r``,
et tel que le rayon intérieur est égal à ``c*r``
(pour ``c=0``, le polygône est réduit à ``n`` rayons du cencle de rayon ``r``
pour ``c=1``, c'est un polygône régulier à ``2n`` côtés)
Alias: ``trace_explosion()``
Arguments:
x (int): Abscisse du centre de l'explosion
y (int): Ordonnée du centre de l'explosion
couleur (:ref:`couleur <couleur>`, optionnel): Couleur (``orange`` par défaut)
r (int): Rayon extérieur
c (float):Coefficient pour obtenir le rayon intérieur égal à ``c*r``
n (int): Nombre de sommets
'''
couleur = rgb(couleur)
pointlist = []
theta = 2 * math.pi / n
for k in range(n):
pointlist.append((
x + r * math.cos(k * theta),
_ordo(y + r * math.sin(k * theta))
))
pointlist.append((
x + c * r * math.cos((k + 1 / 2) * theta),
_ordo(y + c * r * math.sin((k + 1 / 2) * theta))
))
pointlist.append((x + r, _ordo(y)))
pygame.draw.polygon(_fenetre, couleur, pointlist)
if _autorefresh:
pygame.display.update()
def trace_axes(color='noir'):
axes(color)
[docs]def axes(color='noir'):
'''
Dessine les axes de coordonnées pour une meilleure compréhension par les élèves.
Alias: ``trace_axes()``
'''
couleur = rgb(color)
ymax = pygame.display.Info().current_h
xmax = pygame.display.Info().current_w
epaisseur = 2
correction = 0
if _axeMath:
## cette correction vient du fait que les textes continuent d'être définis par rapport à leur coin haut-gauche même si l'axe est à l'envers
correction = 12
pygame.draw.lines(_fenetre, couleur, False, [(5, _ordo(0)), (5, _ordo(ymax))], epaisseur)
pygame.draw.lines(
_fenetre, couleur, False, [
(0, _ordo(ymax - 5)), (5, _ordo(ymax))], epaisseur)
pygame.draw.lines(
_fenetre, couleur, False, [
(10, _ordo(ymax - 5)), (5, _ordo(ymax))], epaisseur)
pygame.draw.lines(_fenetre, couleur, False, [(0, _ordo(5)), (xmax, _ordo(5))], epaisseur)
pygame.draw.lines(
_fenetre, couleur, False, [
(xmax - 5, _ordo(0)), (xmax, _ordo(5))], epaisseur)
pygame.draw.lines(
_fenetre, couleur, False, [
(xmax - 5, _ordo(10)), (xmax, _ordo(5))], epaisseur)
font = pygame.font.Font(None, 24, bold=False, italic=False)
text = font.render(str(ymax), 1, couleur)
_fenetre.blit(text, (15, _ordo(ymax - 35 + correction)))
text = font.render("y", 1, couleur)
_fenetre.blit(text, (15, _ordo(ymax - 17 + correction)))
text = font.render(str(xmax), 1, couleur)
_fenetre.blit(text, (xmax - 35, _ordo(10 + correction)))
text = font.render("x", 1, couleur)
_fenetre.blit(text, (xmax - 15, _ordo(25 + correction)))
text = font.render("0", 1, couleur)
_fenetre.blit(text, (10, _ordo(10 + correction)))
if _autorefresh:
pygame.display.update()
# Gestion du texte.
# voir https://nerdparadise.com/programming/pygame/part5
# On stocke les polices dans une globale pour ne pas avoir à la regénérer à chaque affichage.
_cached_fonts = {}
# On stocke les textes sous forme d'images pour ne pas avoir à la regénérer à chaque affichage.
# Un meme texte a de grande chance d'être affiché plusieurs fois.
_cached_text = {}
def _make_font(fonts, size):
"""
Initialise une police de caractères à partir d'une liste.
On retourne la première police de la liste installée sur la machine.
Si aucune police n'est installée, on retourne la police systeme par défaut.
Arguments:
fonts(list): Liste de polices de caractères
size(int): Taille de la police de caractère
Returns:
la première police de la liste installée sur la machine de taille ``size``.
"""
available = pygame.font.get_fonts()
# get_fonts() returns a list of lowercase spaceless font names
choices = map(lambda x:x.lower().replace(' ', ''), fonts)
for choice in choices:
if choice in available:
return pygame.font.SysFont(choice, size)
return pygame.font.Font(None, size)
def _get_font(font_preferences, size):
"""
Retourne une police de caractère dans la liste ``font_preferences`` de taille ``size``
Si la police a déja été utilisée on utilise la cache ``_cached_fonts``.
"""
global _cached_fonts
key = str(font_preferences) + '|' + str(size)
font = _cached_fonts.get(key, None)
if font is None:
font = _make_font(font_preferences, size)
_cached_fonts[key] = font
return font
def _create_text(text, fonts, size, color):
global _cached_text
key = '|'.join(map(str, (fonts, size, color, text)))
text_image = _cached_text.get(key, None)
if text_image is None:
font = _get_font(fonts, size)
text_image = font.render(text, True, color)
_cached_text[key] = text_image
return text_image
[docs]def texte(message, x, y, police='', taille=12, couleur="noir"):
"""
Affiche un texte dans la fenetre graphique.
Arguments:
message(str): le texte à afficher
x(int): abscisse du début du texte
y(int): ordonnée du haut du début du texte
police(str, optionnel): la police de caractère à utiliser. Si non renseigné ou si la police n'est pas installée, on utilise la police de caractère défaut du system
taille(int, optionnel): taille du texte
couleur(:ref:`couleur <couleur>`, optionnel): couleur du texte
"""
texte_image = _create_text(message, [police], taille, rgb(couleur))
pygame.display.get_surface().blit(texte_image, (x, y))
if _autorefresh:
pygame.display.update()
[docs]def polices_disponibles():
"""
Retourne les polices caractères disponibles
"""
return pygame.font.get_fonts()
[docs]def test_police(police):
"""
Test si une police de caractère est installée.
Arguments:
police(str): police de caractère à tester
Returns:
True si la police est installée, False sinon
"""
return police.lower().replace(' ', '') in polices_disponibles()