Projet Python

L'objectif de ce projet est de mettre en pratique les connaissances acquises durant les modules de programmation en Python. De plus, des notions essentielles au développement d'applications telles que modularité, protection, interface seront étudiées. L'évaluation se fera sur la base du respect du cahier des charges, de la qualité de la programmation, de la présence de commentaires pertinents, des objectifs atteints et des options réalisées.

Introduction au projet

Ce projet s'inspire d'une application présente à la Cité des Sciences sous le nom de "Laser Laboratory". Le but de ce puzzle game est d'atteindre une cible par l'intermédiaire d'un rayon laser et de quelques miroirs orientés judicieusement.

Consignes

Modularité et protection des données

Une attention particulière doit être accordée à ce point. Le projet est subdivisé en quatres modules : cible, laser, miroir et main, ce dernier comprenant le programme principal et l'interface graphique utilisateur (chaque module comportant le même préfixe, par exemple laser_).

Pour chaque module, on peut indiquer que certaines variables et fonctions sont d'un usage interne au module. Pour cela, on préfixera leur nom d'un underscore (_), ce qui signifie dans la philosophie ouverte du langage que l'on ne souhaite pas qu'elles soient visibles (et donc directement utilisables) dans les autres modules. Cette "protection" n'est effective que si l'on effectue une importation au niveau de l'espace des noms global. Exemple :

# module1
_x = 0

def _f():
  pass
# module principal
from module1 import *

_f()  # NameError: name '_f' is not defined

Test unitaire

Chaque module doit comporter un test unitaire vérifiant l'ensemble de ses fonctionnalités. Exemple :

# module 1
def _f():
  pass

def g():
  pass

# tests unitaires
if __name__ == '__main__':
  _f()
  g()

Débogage

L'option -O de l'interprète Python, associée à la variable interne __debug__, offre un moyen de commutation des messages de débogage. Exemple :

# desactive par l'option -O
if __debug__:
  print("mode debug : python lance sans -O")
else:
  print("python lance avec l'option -O")

Aspects géométriques

Nous nous plaçons dans un milieu homogène et isotrope et nous utilisons la loi de Snell-Descartes pour la réflexion.

Schéma de principe de la loi de la réflexion

Cette loi, à la base de l'optique géométrique, énonce le fait que le rayon réfléchi fait un angle égal à celui du rayon incident par rapport à la normale à la surface considérée.

Pour éviter d'avoir à faire des rappels sur les espaces vectoriels et leurs propriétés, nous nous limiterons à l'utilisation de notions simples de la trigonométrie. A noter que ce choix aura des conséquences sur l'exactitude de la simulation.

Les miroirs sont assimilés à des rectangles de faible "épaisseur". Ils seront définis par leurs dimensions (la même pour tous), leur position et l'angle qu'ils forment avec l'horizontale.

Le rayon laser est constitué d'une succession de segments de droites (ou ligne brisée). Chacun des changements de direction du rayon peut se calculer simplement en comparant l'orientation de son rayon incident et l'orientation du miroir considéré. En effet, soit \(\Theta\) l'angle que font les rayons incident et réfléchi avec la normale au plan du miroir, I l'angle que fait le dernier segment du rayon laser, M l'angle que fait le miroir et R l'angle du rayon réfléchi avec l'horizontale, il vient :

\begin{equation*} \Theta = (I+180) - (M+90) \end{equation*}
\begin{equation*} R = (I+180) - 2\Theta \end{equation*}

On en déduit, par substitution :

\begin{equation*} R = 2M - I \end{equation*}

Dans cette approche, il faut tenir compte de l'orientation relative du rayon incident et du miroir. Si le miroir "regarde" dans la même direction que le rayon incident, il faut augmenter ou diminuer M de 180.

Les modules

Le module laser doit comporter au minimum les fonctions suivantes :

  • les accesseurs pour la position, l'angle et l'identificateur de l'objet graphique ainsi que les mutateurs pour les deux premiers paramètres,
  • la création de l'objet initial,
  • l'affichage graphique à la position courante,
  • la rotation absolue et relative du générateur,
  • l'effacement graphique du rayon laser,
  • le calcul de l'angle du rayon réfléchi (tenant compte de l'angle du rayon laser incident et du miroir concerné),
  • la fonction qui trace le parcours complet du rayon laser.

Le module miroir doit comporter au minimum les fonctions suivantes :

  • les accesseurs pour la position, l'angle et l'identificateur de l'objet graphique ainsi que les mutateurs pour les deux premiers paramètres,
  • la création d'un miroir, à une position définie et suivant une orientation par défaut de 0° par rapport à l'horizontale,
  • la rotation relative d'un miroir,
  • l'identificateur du miroir situé à la position (x, y) fournie en paramètre (None sinon).

Le module cible doit comporter au minimum les fonctions suivantes :

  • les accesseurs pour la position et l'identificateur de l'objet graphique,
  • l'affichage graphique à la position (x, y),
  • la procédure qui indique si la cible est touchée (avec changement de couleur).

L'interface

L'interface est le module principal. Il permet d'initialiser le jeu, de gérer les actions de l'utilisateur (plus précisément de tourner un miroir, de tourner le laser, de lancer le tir) et de mettre à jour les données et l'affichage. Pour cela un module cng en version 3.4, en version 3.2 ou en version 2.7 est mis à disposition (qu'il faut renommer cng.pyc). Il permet d'ouvrir une fenêtre graphique, d'afficher des primitives graphiques et de prendre en compte les périphériques d'entrée/sortie (souris, clavier, écran). Son utilisation repose sur les modules Tkinter (fourni par défaut avec le langage Python) et PIL - Python Imaging Library (pour Python version 2.x) ou Pillow (un "fork" de PIL pour Python 3.x). L'aspect graphique de l'application est laissé à la discrétion du développeur.

image0 image1

Au lancement de l'application, l'utilisateur doit donner le nom du fichier définissant les positions du générateur laser, des miroirs et de la cible. Pour cela, chaque position sera signalée par une lettre (l, m ou c) suivi d'un couple de coordonnées. Exemple :

l 200 300
m 400 500
m 850 50
m 1000 200
c 500 700

Ces informations sont communiquées en ligne de commandes. Exemple : ./laser lvl01.las.

Options

Différentes options au choix peuvent compléter le projet.

  • prise en compte de l'épaisseur des miroirs pour la détermination des coordonnées du point de réflexion,
  • placement automatique des miroirs,
  • création d'une distribution (voir pour cela les documentations du setup.py et de l'utilitaire distutils).