Loading [MathJax]/jax/output/HTML-CSS/fonts/STIX-Web/fontdata.js

Rappels

Le module interface_grille - qui s'appuie sur matplotlib - permet de créer une interface graphique :

  • composée d'une grille à maillage carré ;
  • dont chaque case peut comporter (ou pas) :
    • une couleur de fond,
    • du texte,
    • des murs séparateurs,
    • une image ;
  • qui peut écouter quelques évènements (sélection d'un objet à la souris, clic souris et appui sur une touche du clavier) ;
  • qui dispose d'une sortie textuelle ;
  • qui peut effectuer des animations.

On rappelle que les instructions de création de la grille diffèrent un peu selon l'environnement de travail (basthon, jupyter ou autre), ici ce sont celles pour basthon qui sont utilisées.

On s'intéresse dans ce notebook à l'écoute des évènements.

0 : Rappels

Dans le notebook sur les textes, nous avons vu comment déplacer un personnage sur une carte. Pour cela on utilisait une fonction deplacer qui :

  • prenait en paramètres :
    • les matrices et dictionnaires représentant le plateau de jeu,
    • la vue réalisant l'affichage,
  • modifiait certaines valeurs des matrices ou dictionnaires,
  • actualisait l'affichage.
    --------------                             -------------------
   |   matrices   |                           | actualisation des |
   |   & dicos    |\                    ----->| matrices & dicos  |
    --------------  \     -----------  /       -------------------
                     --->| fonction  |/        
                         |           |               puis
                     --->| deplacer  |\              
    --------------  /     -----------  \       -------------------
   |     vue      |/                    ----->| actualisation de  |
    --------------                            |   l'affichage     |
                                               -------------------

Il est important de comprendre que quel que soit le «jeu» utilisant l'interface grille, ce schéma resterait valable pour toute autre fonction effectuant des «actions de jeu». Par exemple une fonction qui changerait des couleurs de fond, qui détruirait ou ajouterait des murs, qui rendrait visibles ou invisibles certaines images etc.

Finalement pour un jeu qui évolue au fil des différents tours de jeu et dont le «plateau de jeu» est modélisé en mémoire par des matrices et dictionnaires, on obtient le schéma plus général suivant :

    -------------------                              -------------------
   | plateau de jeu =  |                            | actualisation des |
   | matrices & dicos  |\                     ----->| matrices & dicos  |
    -------------------  \     ------------  /       -------------------
                          --->| fonction   |/        
                              | "action    |               puis
                          --->|   de jeu"  |\              
    -------------------  /     ------------  \       -------------------
   |        vue        |/                     ----->| actualisation de  |
    -------------------                             |   l'affichage     |
                                                     -------------------

I : Comprendre ce qu'est la gestion d'évènements

Considérons une grille correspondant - par exemple - à un jeu auquel on souhaiterait jouer en utilisant :

  • la souris : par exemple en faisant des clics sur certaines cases,
  • le clavier : par exemple en appuyant sur les flèches du clavier ou des lettres.

Pour le programme informatique, les interactions clavier et souris sont appelés des évènements.

En première approche on peut faire l'analyse ci-dessous :

  -----------------------------                   ------------
 |  Evènement sur la grille  : |                 |   Action   |
 |     - souris                |   ---------->   |     de     |
 |     - clavier               |                 |     jeu    |
  -----------------------------                   ------------

En réfléchissant davantage, on réalise que selon l'évènement effectué à la souris ou au clavier et selon l'état du plateau de jeu à ce moment là, l'action de jeu à réaliser peut changer.

Il faut donc des fonctions qui vont «gérer» les évènements en décidant - selon les évènements et l'état du plateau de jeu - quelles fonctions «action de jeu» effectuer. Finalement on obtient le schéma suivant :

    -------------------                                                     
   | plateau de jeu =  |                                                      -------------------
   | matrices & dicos  |\                                                    | actualisation des |
    -------------------  \                                             ----->| matrices & dicos  |
                          \     ------------            ------------  /       -------------------
    -------------------    --->|  fonction  | choisit  | fonction   |/               
   |     évènement     |------>|gestionnaire|--------->| "action    |              puis
    -------------------    --->| d'évènement|          |   de jeu"  |\  
                          /     ------------            ------------  \       -------------------                      
    -------------------  /                                             ----->| actualisation de  |   
   |        vue        |/                                                    |   l'affichage     |
    -------------------                                                       -------------------

Enfin, il faut savoir qu'une instruction spécifique (binding ou liaison) doit être effectuée afin d'associer un type d'évènement à la fonction gestionnaire qui le prend en charge. Cela permet, dès que cet évènement survient, de déclencher automatiquement un appel à la fonction gestionnaire.

II : Quels sont les évènements gérés dans une vue ?

Les instances de la classe Vue gèrent cinq évènements. Les informations associées à ces cinq évènements sont passées en paramètre à la fonction gestionnaire sous forme de dictionnaire :

  • 'appui_touche' :

    • Déclenché lorsqu'une touche du clavier est pressée,
    • Voici l'évènement passé en paramètre à la fonction gestionnaire lors de l'appui sur la touche «e minuscule» 32.67 secondes après le lancement de l'interface graphique alors que la souris survole la case d'indice de ligne 2 et d'indice de colonne 3 :
    • {'moment': 32.67, 'evenement': 'appui_touche', 'touche': 'e', 'lig': 2, 'col': 3}
  • 'fin_appui_touche'

    • Déclenché lorsqu'une touche du clavier est relâchée,
    • Voici l'évènement passé en paramètre à la fonction gestionnaire lors du relâchement de la touche «e minuscule» 33.25 secondes après le lancement de l'interface graphique alors que la souris survole la case d'indice de ligne 2 et d'indice de colonne 3 :
    • {'moment': 33.25, 'evenement': 'appui_touche', 'touche': 'e', 'lig': 2, 'col': 3}
  • 'selection_objet'

    • Déclenché lorsqu'un clic de souris est effectué sur un objet ('fond', 'texte', 'mur' ou 'image') visible,
    • Voici l'évènement passé en paramètre à la fonction gestionnaire lors d'un clic avec le bouton gauche sur le 'texte' de la case d'indice de ligne 3 et d'indice de colonne 0, 12.17 secondes après le lancement de l'interface graphique :
    • {'moment': 12.17, 'evenement': 'selection_objet', 'objet': 'texte', 'bouton': 'gauche', 'touche': None, 'lig': 3, 'col': 0}
    • Si une touche du clavier est pressée en même temps que le clic, le caractère de cette touche est associé à la clé 'touche' du dictionnaire.
    • Si l'objet est 'mur', le dictionnaire comprend en plus une clef 'd' (pour «direction») à laquelle est associée la direction du mur ('N', 'E', 'S' ou 'O').
  • 'clic_souris'

    • Déclenché lorsqu'un clic de souris est effectué sur la grille,
    • Voici l'évènement passé en paramètre à la fonction gestionnaire lors d'un clic avec le bouton droit sur la case d'indice de ligne 2 et d'indice de colonne 2, 125.78 secondes après le lancement de l'interface graphique et tout en pressant la touche «k minuscule» :
    • {'moment': 125.78, 'evenement': 'clic_souris', 'bouton': 'droit', 'touche': 'k', 'lig': 2, 'col': 2}
    • Si le clic est effectué sur un objet visible, l'évènement selection_objet est également déclenché.
    • Si le clic n'est pas effectué sur un objet visible, l'évènement clic_souris est seul à être déclenché.
  • 'fin_clic_souris'

    • Déclenché lorsqu'un clic de souris sur la grille se termine,
    • Voici l'évènement passé en paramètre à la fonction gestionnaire lors de la fin d'un clic avec le bouton droit sur la case d'indice de ligne 2 et d'indice de colonne 2, 126.34 secondes après le lancement de l'interface graphique et tout en pressant la touche «k minuscule» :
    • {'moment': 126.34, 'evenement': 'fin_clic_souris', 'bouton': 'droit', 'touche': 'k', 'lig': 2, 'col': 2}

Remarque : avez-vous compris la différence entre 'clic_souris' et 'selection_objet' ?

Question 1 :

Exécutez les deux cellules de code ci-dessous puis déclenchez des évènements avec votre clavier et votre souris.

Observez le contenu du dictionnaire evenements qui s'affiche dans la zone textuelle.

En particulier :

  • on fera quelques clics sur les cases sans fond ni texte pour observer la différence entre 'selection_objet' et 'clic_souris',
  • on fera des clics prolongés ou des appuis sur les touches prolongés pour observer la différence entre 'clic_souris' et 'fin_clic_souris' ainsi qu'entre 'appui_touche' et 'fin_appui_touche'.
Entrée[ ]:
Entrée[ ]:

III : Effectuer la liaison entre un évènement et sa fonction gestionnaire

On utilise la méthode lier_evenement(evenement, gestionnaire, activer, argument) où :

  • evenement est une chaîne de caractères désignant l'évènement que l'on souhaite prendre en charge ('appui_touche', 'fin_appui_touche', 'selection_objet', 'clic_souris' ou 'fin_clic_souris'.

  • gestionnaire est la fonction gestionnaire d'évènement.

  • activer indique si l'on souhaite commencer à écouter l'évènement (True) ou si l'on souhaite terminer l'écoute de l'évènement (False).

  • argument est un argument qui permet de passer des informations supplémentaires à la fonction gestionnaire. Pour un jeu, argument sera typiquement constitué des informations relatives au plateau de jeu (matrices, dictionnaires etc.).

Il est important de noter que la fonction gestionnaire devra systématiquement avoir trois paramètres :

  • vue qui correspond à la vue utilisée pour l'interface graphique (instance de la classe Vue),
  • evenement qui correspond au dictionnaire de l'évènement déclencheur (voir II),
  • argument qui correspond à l'argument indiqué lors de la liaison (éventuellement None si il n'y en a pas besoin).

IV : Un premier exemple passant du mode «fonctionnel» au mode «évènementiel»

La cellule de code ci-dessous permet d'obtenir un «jeu» dans lequel un singe doit rejoindre la case en bas à droite de la grille en récoltant des fruits. L'objectif est d'obtenir le plus de points possibles sachant que :

  • une fraise rapporte 3 points,
  • une poire rapporte 5 points,
  • une grappe de raison rapporte 8 points,
  • une banane rapporte 10 points.

Le singe ne peut se déplacer que vers le bas ou vers la droite.



Le «plateau de jeu» plateau est un dictionnaire contenant :

  • la matrice mémorisant les positions des différents fruits,
  • l'indice de ligne de la position du singe,
  • l'indice de colonne de la position du singe,
  • le score obtenu au fur et à mesure par le singe.

Au fur et à mesure des déplacements du singe chacun des quatre éléments du plateau de jeu est susceptible d'évoluer.

Regrouper ces quatre éléments dans un seul objet (ici un dictionnaire) permet de les passer de façon très simple en argument aux fonctions, et cela permet aussi de faciliter le travail de notre cerveau (quand on code, on sait que TOUTES les informations qui modélisent l'état du plateau de jeu sont contenues dans la variable plateau).

En classe de terminale, on pourrait créer une classe Plateau ...



Les variables globales (dont les noms sont en MAJUSCULES) sont les constantes correspondent à des informations qui ne changent pas au cours du jeu.



Pour jouer il suffit :

  • d'exécuter l'instruction vue, plateau = nouvelle_partie()

  • puis d'exécuter l'instruction bas(vue, plateau) ou droite(vue, plateau) selon le déplacement souhaité.

Entrée[ ]:
Entrée[ ]:
Entrée[ ]:
Entrée[ ]:

Question 1 :

Effectuer une partie de jeu en exécutant successivement plusieurs fois les cellules de code ci-dessus.

Question 2 :

  • Dans la fonction nouvelle_partie, effectuer la liaison entre l'évènement de type appui_touche et la fonction gestionnaire gestion_appui_touche(vue, evenement, argument), pour laquelle l'argument passé en argument sera le plateau de jeu,

  • Compléter le code de la fonction gestion_appui_touche afin :

    • qu'un appui sur la touche 2 provoque un déplacement vers le bas,
    • qu'un appui sur la touche 6 provoque un déplacement vers la gauche.

Remarque : Ne pas oublier de cliquer une fois sur la grille pour rendre possible la détection des appuis sur les touches.

Solution
import interface_grille as ig
from random import randint, choices

NB_LIG = 10
NB_COL = 10
FRAISE, POIRE, RAISIN, BANANE, SINGE = '🍓', '🍐', '🍇', '🍌', '🐒'
COULEURS = {FRAISE: (255, 35, 35), POIRE: (35, 155, 35), RAISIN: (45, 25, 125), BANANE:(255, 255, 45), SINGE:(155, 145, 112)}
POINTS = {FRAISE: 3, POIRE: 5, RAISIN: 8, BANANE : 10, None: 0}

def matrice_initiale():
    """Renvoie une matrice modélisant la répartition initiale des fruits sur la grille."""
    global NB_LIG, NB_COL, FRAISE, POIRE, RAISIN, BANANE
    mat = [[choices([FRAISE, POIRE, RAISIN, BANANE, None], weights = [1, 1, 1, 1, 10])[0] for _ in range(NB_COL)] for __ in range(NB_LIG)]
    mat[0][0] = None
    mat[NB_LIG-1][NB_COL-1] = None
    return mat

def affichage(vue, plateau):
    """Affiche la grille et la zone de texte conformément à l'état du plateau de jeu."""
    global NB_LIG, NB_COL, SINGE, COULEURS
    mat_fruits, lig_singe, col_singe, score = plateau['mat_fruits'], plateau['lig_singe'], plateau['col_singe'], plateau['score']
    
    for lig in range(NB_LIG):
        for col in range(NB_COL):
            vue.mg('fond', lig, col, ci = (145, 243, 187), v = True)
            if mat_fruits[lig][col] is not None:
                vue.mg('texte', lig, col, tp = 25, t = mat_fruits[lig][col], c = COULEURS[mat_fruits[lig][col]], v = True)
            else:
                vue.mg('texte', lig, col, v = False)
                
    vue.mg('fond', 0, 0, ci = (235, 124, 176))
    vue.mg('fond', NB_LIG-1, NB_COL-1, ci = (235, 124, 176))
    vue.mg('texte', lig_singe, col_singe, tp = 30, c = COULEURS[SINGE], t = SINGE, v = True)
    vue.mzt(t = 'SCORE : ' + str(score), tp = 50, ct = (43, 56, 195), cf = (105, 203, 147))

    if lig_singe == NB_LIG-1 and col_singe == NB_COL-1:
        vue.mzt(t = 'PARTIE FINIE AVEC UN SCORE DE : ' + str(score) + ' !!!', tp = 50, ct = (43, 56, 195), cf = (105, 203, 147))
    
def bas(vue, plateau):
    """
    Modifie l'état du plateau de jeu puis actualise l'affichage 
    lorsqu'un déplacement vers le bas est effectué.
    """
    global NB_LIG, POINTS
    mat_fruits, lig_singe, col_singe, score = plateau['mat_fruits'], plateau['lig_singe'], plateau['col_singe'], plateau['score']
    
    if lig_singe < NB_LIG - 1:
        lig_singe = lig_singe + 1
    score = score + POINTS[mat_fruits[lig_singe][col_singe]]
    mat_fruits[lig_singe][col_singe] = None
        
    plateau['mat_fruits'], plateau['lig_singe'], plateau['score'] = mat_fruits, lig_singe, score
    affichage(vue, plateau)

def droite(vue, plateau):
    """
    Modifie l'état du plateau de jeu puis actualise l'affichage 
    lorsqu'un déplacement vers la droite est effectué.
    """
    global NB_COL, POINTS
    mat_fruits, lig_singe, col_singe, score = plateau['mat_fruits'], plateau['lig_singe'], plateau['col_singe'], plateau['score']
    
    if col_singe < NB_COL - 1:
        col_singe = col_singe + 1
    score = score + POINTS[mat_fruits[lig_singe][col_singe]]
    mat_fruits[lig_singe][col_singe] = None
        
    plateau['mat_fruits'], plateau['col_singe'], plateau['score'] = mat_fruits, col_singe, score    
    affichage(vue, plateau) 
    
def gestion_appui_touche(vue, evenement, argument):
    plateau = argument
    if evenement['touche'] == '2':
        bas(vue, plateau)
    elif evenement['touche'] == '6':
        droite(vue, plateau)
    
def nouvelle_partie():
    global NB_LIG, NB_COL
    vue = ig.Vue(NB_LIG, NB_COL, env='basthon', police = 'emoji', utiliser_zonetexte = True) 
    ig.plt.show()
    plateau = {'mat_fruits' : matrice_initiale(), 'lig_singe': 0, 'col_singe': 0, 'score': 0}
    affichage(vue, plateau)
    
    # rajouter ici la liaison (binding) entre l'évènement `appui_touche` et la fonction `gestion_appui_touche`
    vue.lier_evenement('appui_touche', gestion_appui_touche, activer = True, argument = plateau)
    
    return vue, plateau

IV : Un second exemple passant du mode «fonctionnel» au mode «évènementiel»

La cellule de code ci-dessous permet d'obtenir un «jeu» dans lequel l'utilisateur peut générer des grilles de labyrinthe.



Le «plateau de jeu» plateau est un dictionnaire contenant :

  • une matrice indiquant les murs verticaux,
  • une matrice indiquant les murs horizontaux,
  • un booléen indiquant si les séparations des cases doivent être indiquées :
    • une couleur extérieure identique à la couleur intérieure est préférable pour l'export en PNG,
    • une couleur extérieure noire est préférable pour savoir où cliquer pour faire apparaître un mur.

Au fur et à mesure de la création du labyrinthe, chacun de ces trois éléments du plateau de jeu est susceptible d'évoluer.

Regrouper ces trois éléments dans un seul objet (ici un dictionnaire) permet de les passer de façon très simple en argument aux fonctions, et cela permet aussi de faciliter le travail de notre cerveau (quand on code, on sait que TOUTES les informations qui modélisent l'état du plateau de jeu sont contenues dans la variable plateau).

En classe de terminale, on pourrait créer une classe Plateau ...



Les variables globales (dont les noms sont en MAJUSCULES) sont les constantes correspondent à des informations qui ne changent pas au cours du jeu.



Pour jouer il suffit :

  • d'exécuter l'instruction vue, plateau = nouvelle_partie()

  • puis d'exécuter l'instruction modifier_mur(vue, plateau, lig, col, direction) qui va :

    • faire apparaître le mur si celui-ci était caché,
    • faire disparaître le mur si celui n'était pas caché.
  • ou d'exécuter l'instruction modifier_separations(vue, plateau) qui va mettre la couleur extérieure des fonds de case :

    • identique à la couleur intérieure si la couleur extérieure était en noir,
    • en noir si la couleur extérieure était identique à la couleur intérieure.


Remarque : La sélection d'objet ne fonctionne que sur les objets ayant la propriété visible = True. Donc pour pouvoir être adapté en utilisant uniquement la souris, les murs sont tous visibles mais :

  • les murs qui ne doivent pas être affichés ont une transparence alpha = 0,
  • les murs qui doivent être affichés ont une transparence alpha = 1.
Entrée[ ]:
Entrée[ ]:
Entrée[ ]:
Entrée[ ]:

Question 1 :

Créer un labyrinthe en exécutant successivement plusieurs fois les cellules de code ci-dessus (on modifiera évidemment les indices de ligne et de colonne ainsi que la direction des murs à supprimer ou à ajouter).

Question 2 :

  • Dans la fonction nouvelle_partie, effectuer la liaison entre :

    • l'évènement de type selection_objet et la fonction gestionnaire gestion_selection_objet(vue, evenement, argument), pour laquelle l'argument passé en argument sera le plateau de jeu,
    • l'évènement de type clic_souris et la fonction gestionnaire gestion_clic_souris(vue, evenement, argument), pour laquelle l'argument passé en argument sera le plateau de jeu.
  • Compléter le code de la fonction gestion_selection_objet afin que la sélection d'un mur avec le bouton gauche de la souris le fasse apparaître ou disparaître.

  • Compléter le code de la fonction gestion_clic_souris afin qu'un clic de la souris avec le bouton droit de la souris fasse apparaître ou disparaître les séparations entre les fonds des cases.

Solution
import interface_grille as ig
from random import randint, choices

NB_LIG = 5
NB_COL = 5
COULEURS = {'mur': (125, 205, 35), 'fond': (215, 255, 175)}

def matrices_initiales():
    """Renvoie deux matrices modélisant les murs horizontaux et verticaux."""
    global NB_LIG, NB_COL
    murs_h = [[True for col in range(NB_COL)] for lig in range(NB_LIG + 1)]
    murs_v = [[True for col in range(NB_COL + 1)] for lig in range(NB_LIG)]
    return murs_h, murs_v

def affichage(vue, plateau):
    """Affiche la grille conformément à l'état du plateau de jeu."""
    global NB_LIG, NB_COL, COULEURS
    murs_h, murs_v, separations = plateau['murs_h'], plateau['murs_v'], plateau['separations']
    
    for lig in range(NB_LIG):
        for col in range(NB_COL):
            if separations :
                vue.mg('fond', lig, col, ci = COULEURS['fond'], ce = (0, 0, 0), v = True)
            else:
                vue.mg('fond', lig, col, c = COULEURS['fond'], v = True)
                
            if murs_h[lig][col] :
                vue.mg('mur', lig, col, d = 'N', ci = COULEURS['mur'], a = 1, v = True)
            else:
                vue.mg('mur', lig, col, d = 'N', ci = COULEURS['mur'], a = 0, v = True)

            if murs_v[lig][col] :
                vue.mg('mur', lig, col, d = 'O', ci = COULEURS['mur'], a = 1, v = True)
            else:
                vue.mg('mur', lig, col, d = 'O', ci = COULEURS['mur'], a = 0, v = True)
                
    for lig in range(NB_LIG):
        if murs_v[lig][NB_COL] :
            vue.mg('mur', lig, NB_COL - 1, d = 'E', ci = COULEURS['mur'], a = 1, v = True)
        else:
            vue.mg('mur', lig, NB_COL - 1, d = 'E', ci = COULEURS['mur'], a = 0, v = True)        

    for col in range(NB_COL):
        if murs_h[NB_LIG][col] :
            vue.mg('mur', NB_LIG - 1, col, d = 'S', ci = COULEURS['mur'], a = 1, v = True)
        else:
            vue.mg('mur', NB_LIG - 1, col, d = 'S', ci = COULEURS['mur'], a = 0, v = True)

def modifier_mur(vue, plateau, lig, col, direction):
    """
    Modifie l'état du plateau de jeu puis actualise l'affichage 
    lorsqu'un mur est modifié.
    """
    global NB_LIG, NB_COL
    murs_h, murs_v = plateau['murs_h'], plateau['murs_v']
    
    if direction == 'N':
        murs_h[lig][col] = not murs_h[lig][col]
    elif direction == 'E':
        murs_v[lig][col+1] = not murs_v[lig][col+1]
    elif direction == 'S':
        murs_h[lig+1][col] = not murs_h[lig+1][col]
    elif direction == 'O':
        murs_v[lig][col] = not murs_v[lig][col]       
        
    plateau['murs_h'], plateau['murs_v'] = murs_h, murs_v
    affichage(vue, plateau)
    
def modifier_separations(vue, plateau):
    """
    Modifie l'état du plateau de jeu puis actualise l'affichage lorsqu'on demande
    à changer l'affichage de de la couleur extérieure des fonds de cases.
    """
    plateau['separations'] = not plateau['separations']
    affichage(vue, plateau)
    
def gestion_selection_objet(vue, evenement, argument):
    plateau = argument
    if evenement['bouton'] == 'gauche' and evenement['objet'] == 'mur':
        modifier_mur(vue, plateau, evenement['lig'], evenement['col'], evenement['dir'])
        
def gestion_clic_souris(vue, evenement, plateau):
    if evenement['bouton'] == 'droit':
        modifier_separations(vue, plateau)
        
        
def nouvelle_partie():
    global NB_LIG, NB_COL
    vue = ig.Vue(NB_LIG, NB_COL, env='basthon', utiliser_zonetexte = True) 
    ig.plt.show()
    murs_h, murs_v = matrices_initiales()
    plateau = {'murs_h' : murs_h, 'murs_v': murs_v, 'separations': True}
    affichage(vue, plateau)
    
    # rajouter ici la liaison (binding) entre l'évènement `selection_objet` et la fonction `gestion_selection_objet`
    vue.lier_evenement('selection_objet', gestion_selection_objet, activer = True, argument = plateau)
    
    # rajouter ici la liaison (binding) entre l'évènement `clic_souris` et la fonction `gestion_clic_souris`
    vue.lier_evenement('clic_souris', gestion_clic_souris, activer = True, argument = plateau)
    
    return vue, plateau

V : Bugs

L'appui sur certaines touches n'est pas détecté lorsque la combinaison de touches est un raccourci clavier du navigateur :

  • Firefox :

    • appui sur la touche apostrophe "'" ne peut pas être pris en compte (ouvre la recherche rapide de Firefox).
  • Chrome :

    • rien de détecté à ce jour.