Projet IHM - Duel dans le ciel

Ce projet est avant tout un projet d'IHM. Vous devrez donc vous focaliser sur cet aspect en priorité puisque l'évaluation de votre travail reposera dessus : respectez les principes ergonomiques listés ci-après, réfléchissez aux conditions d'utilisation, à l'homogénéité de l'interface, etc. Un fonctionnement parfait de l'application n'est pas demandé.

Introduction au sujet

Le but de ce projet est de simuler un combat aérien durant la 1ère Guerre Mondiale. Dans ce jeu pour deux joueurs en "hot seat", chaque joueur va diriger un avion et va tenter d'abattre l'avion adverse. Pour ce faire, chacun dispose d'un avion qui va se déplacer sur l'espace de jeu et d'un ensemble de manœuvres pour programmer les déplacements.

Chaque avion est représenté en vue du dessus et dispose d'un ensemble de manœuvres qui lui est propre (glissade à droite ou à gauche, décrochage, piqué, etc.). Certains avions sont plus robustes mais plus lents, d'autres plus manœuvrables mais plus fragiles. Chaque avion possède donc une vitesse, des points de structure et une portée de tir qui le caractérisent. Chaque joueur contrôlant un avion planifie son tour de jeu en choisissant une séquence de trois manœuvres. Ensuite l'application simule la première manœuvre de chaque avion en même temps, puis la deuxième et enfin la troisième. Les mouvements sont donc exécutés simultanément (remarque : les avions peuvent se chevaucher). Un avion en dehors de l'aire de jeu à la fin d'un tour (de trois manœuvres) est éliminé. Le joueur avec son avion sur l'aire de jeu, après l'élimination ou la sortie de l'avion adverse, a gagné la partie.

Si un avion peut faire feu durant ses manœuvres, il le fait automatiquement. Le résultat de chaque tir est déterminé aléatoirement et peut occasionner des dégâts spéciaux (enrayage, palonnier bloqué, etc.). Les dégâts se soustraient aux points de structure d'un avion et sont cumulatifs. Certains dégâts spéciaux peuvent interdire des manœuvres durant quelques tours.

En dehors des manœuvres classiques (voler en ligne droite, faire un virage à droite ou à gauche, effectuer un piqué), la plupart des avions peuvent effectuer : virage court (en l'absence de stabilisateur vertical), glissade à droite ou à gauche, Immelmann et Split S (ou retournement) (uniquement après une manœuvre en ligne droite pour ces deux figures).

Vous trouverez dans ce fichier des ressources utiles à la réalisation de ce projet.

Cahier des charges

L'environnement est défini par un fond représentant la vue aérienne d'une région et par les avions qui évoluent au dessus.

Les avions doivent être caractérisés par un ensemble de manœuvres et des points de structure (entre 10 et 20 points). D'autres caractéristiques peuvent être envisagées (distance de tir, etc.). Toutes les manœuvres se font dans le même plan, il n'y a pas initialement de notion d'altitude. Pour des raisons à la fois pratique et pour maintenir un certain équilibre les trajectoires doivent s'inscrire dans un rectangle englobant de dimension unique.

Les tirs sont automatiques et ont lieu une seule fois par manœuvre dès que les conditions de portée et d'angle de tir sont réunis. Ils peuvent entraîner l'enrayage de la mitrailleuse suivant une probabilité de 0,1. Les dégâts sur un avion ennemi se soustraient aux points de structure de l'avion. Ils sont déterminés aléatoirement sur un intervalle croissant d'entiers (avec une probabilité de plus en plus faible) et il est conseillé de ne pas dépasser 5 points par tir. Des dégâts spéciaux peuvent s'ajouter aux dégâts initiaux : palonnier bloqué à gauche ou à droite (probabilité 0,05) empêchant certaines manœuvres, pilote blessé (p = 0,05), moteur endommagé (p = 0,0025), avion en feu (p = 0,05) occasionnant des dégâts supplémentaires ou explosion de l'appareil (p = 0,0025). Un seul dégât spécial peut survenir par tir. L'enrayage et les dégâts spéciaux "palonnier bloqué" et "avion en feu" ne s'appliquent que pour les trois prochaines manœuvres. Les dégâts spéciaux "pilote blessé" et "moteur endommagé" sont permanents et s'ils surviennent une deuxième fois entraînent la perte de l'appareil.

Certains dégâts spéciaux peuvent être traités de façon plus précise. Ainsi, un pilote blessé ne peut pas faire feu durant les trois prochaines manœuvres ni après avoir exécuté un piqué, un Immelmann ou un Split-S durant le reste de la partie. Si le moteur est endommagé, l'avion doit effectuer au moins une manœuvre de piqué à chaque tour jusqu'à la fin de la partie. Enfin, un avion en feu subit des dégâts supplémentaires (mais pas de dégâts spéciaux) durant les trois prochaines manœuvres et crache de la fumée.

L'interface doit permettre :

  • de définir l'environnement en choisissant le lieu et les modèles d'avions qui vont s'affronter,
  • de personnaliser le déroulement d'une partie,
  • de permettre à deux joueurs partageant la même machine de programmer trois manœuvres consécutives pour leur avion,
  • de visualiser le résultat de ces manœuvres,
  • de prendre en compte les dégâts ainsi que les résultats spéciaux,
  • de déterminer le vainqueur.

Aspect technique

Dans le cadre d'une programmation impérative structurée, il est fortement recommandé d'utiliser des dictionnaires comme structures de données. L'alternative évidente, étant donné le caractère multi-paradigme du langage Python, est d'adopter une programmation objet.

Les manœuvres s'inscrivent dans une trajectoire plane définie par un système paramétrique et un domaine de définition. L'exemple le plus simple est la manœuvre en ligne droite caractérisée par le système paramétrique x = 0, y = t avec t parcourant l'intervalle de 0 à max suivant un certain pas. Les virages larges peuvent être assimilés à des arcs d'ellipses, les virages courts à des arcs de cercles et les glissades à des hystérésis. Exemples :

image0 image1 image2

Les manœuvres ayant lieu simultanément, il est impératif que toutes les courbes comportent le même nombre de pas de discrétisation, ceci afin de synchroniser naturellement les mouvements des deux avions.

Pour les trajectoires courbes (la glissade est un cas à part) il est nécessaire de calculer à chaque pas l'orientation de l'avion, ce que l'on peut faire aisément pas le calcul de la dérivée en chaque point qui donne naturellement la pente de la tangente en ce point.

Pour effectuer les transformations idoines aux sprites des avions, le plus simple est d'utiliser le module Pillow, un "fork" du module PIL (Python Imaging Library). Cette bibliothèque permet de manipuler de nombreux formats d'images (PNG en particulier) et d'effectuer des transformations géométriques sur celles-ci (notamment rotation et dilatation). Néanmoins il est tout de même nécessaire de formaliser un certain nombre de points aussi bien techniques que mathématiques.

L'affichage dans un canvas de tkinter d'une image lue par l'intermédiaire du module Pillow se fait en trois étapes :

  • lecture du fichier image : im = Image.open(name),
  • conversion au format PhotoImage : imp =  ImageTk.PhotoImage(im),
  • affichage dans le canvas : id = canv.create_image(x, y, image=imp).

Attention : l'identificateur associé à la PhotoImage doit avoir une durée de vie égale à celle du programme (sinon aucune image ne s'affichera dans le canvas et ce sans message d'erreur).

La rotation (im.rotate(angle) avec angle en degrés) et la dilatation d'une image (im.resize(largeur, hauteur, Image.ANTIALIAS)) nécessitent de créer une nouvelle PhotoImage avant de l'afficher.

Dans le contexte d'une modification de l'image originale du sprite en fonction de la trajectoire suivie, il est conseillé de faire coïncider origine et direction de l'image et de la courbe. C'est pourquoi le domaine de définition du paramètre t doit correspondre à une portion de courbe débutant à l'origine et ayant une direction générale verticale (comme dans les exemples ci-dessus). Dans ces conditions, il reste à effectuer un changement de repère pour tenir compte de la manœuvre précédente. On rappelle les équations paramétriques de la rotation vectorielle : x' = x*cos(alpha) - y*sin(alpha), y' = x*sin(alpha) + y*cos(alpha) ainsi que les propriétés du produit scalaire (qui permet de calculer le cosinus de l'angle entre deux vecteurs, directement si les deux vecteurs sont unitaires) et du produit vectoriel (qui permet de savoir dans quel sens on tourne : sens direct ou indirect).

changement de repère

Par exemple, sur le schéma ci-dessus, l'avion a pour position (-300, 300) dans le repère initial (en rouge) et bien entendu (0, 0) dans le nouveau repère associé à la prochaine manœuvre (en noir). De même, son orientation est déterminée par le vecteur (-1/2, 4/5) dans le repère initial et par le vecteur (0, 1) dans le nouveau repère.

Pour résoudre simplement le problème de la portée et de l'arc de tir, il suffit de procéder en deux étapes : déterminer si la cible est à portée en comparant la distance qui sépare l'avion de sa cible et la portée de l'arme (i.e la cible est-elle dans le cercle de rayon la portée de l'arme et de centre l'avion) puis, si c'est le cas, utiliser une fois encore le produit scalaire pour établir si la cible est dans l'angle de tir.

Principes ergonomiques

Cohérence : style graphique homogène, si l'interface privilégie un certain type de widgets elle doit le faire pour toutes les facettes de l'application.

Concision : les choix initiaux, la programmation des manœuvres doivent nécessiter le moins d'actions possible.

Retour d'informations : à toute action doit correspondre une réponse visuelle, une aide (si possible contextuelle) doit être mise à disposition.

Structuration des activités : toute action complexe doit être décomposée en suite d'actions simples et intuitives.

Flexibilité : nonobstant certains choix visuels, le déroulement d'une partie doit pouvoir être personnalisé.

Gestion des erreurs : à toute erreur doit être associé un message clair et explicite, des reprises sur erreurs doivent être présentes.