Semaine 8 – Interprète d’un mini-langage (3)

, par
L’interprète du mini-langage consiste essentiellement en la définition du type des programmes et d’une fonction run. Des instructions sans paramètres "Nord" "Sud" "Est" "Ouest" ont fait leur apparition de façon à déplacer un objet sur plan quadrillé. Pour cela, la fonction run prend non seulement en paramètre un programme du mini-langage mais aussi une fonction qui gère les déplacements (et qui pourrait éventuellement affecter les variables).
La fonction test_run montre un usage simple de la fonction run.
- exception VariableNonDeclaree;;
- | Plus of expression * expression
- | Mult of expression * expression
- | Div of expression * expression
- | Moins of expression * expression
- | Opp of expression
- | Egal of expression * expression
- | Sup of expression * expression
- | Inf of expression * expression
- ;;
- | While of expression * instruction
- | If of expression * instruction
- | Print of expression
- | Println
- | Nord
- | Sud
- | Est
- | Ouest
- ;;
- type direction = N | S | E | O;;
- let run p deplacer =
- let variables = [] in
- let rec valeur_variable nom vars = match vars with
- | [] -> raise VariableNonDeclaree
- | {nom=n; valeur=v}::xs when n = nom -> v
- | {nom=n; valeur=v}::xs -> valeur_variable nom xs
- in
- let rec affecter_variable nom va vars = match vars with
- | [] -> raise VariableNonDeclaree
- | c::xs when c.nom = nom -> c.valeur <- va
- | c::xs -> affecter_variable nom va xs
- in
- let declarer_variable nom vars = {nom=nom; valeur=0}::vars
- in
- let rec eval expression vars = match expression with
- | Ent n -> n
- | Plus (e1, e2) -> (eval e1 vars) + (eval e2 vars)
- | Mult (e1, e2) -> (eval e1 vars) * (eval e2 vars)
- | Div (e1, e2) -> (eval e1 vars) / (eval e2 vars)
- | Moins (e1, e2) -> (eval e1 vars) - (eval e2 vars)
- | Opp e1 -> (-1) * (eval e1 vars)
- | Egal (e1, e2) -> (match (eval e1 vars) = (eval e2 vars)
- with true -> 1
- | false -> 0)
- | Sup (e1, e2) -> (match (eval e1 vars) > (eval e2 vars)
- with true -> 1
- | false -> 0)
- | Inf (e1, e2) -> (match (eval e1 vars) < (eval e2 vars)
- with true -> 1
- | false -> 0)
- | Var s -> valeur_variable s vars
- in
- let rec exec instruction vars =
- match instruction with
- | Declaration s -> declarer_variable s vars
- | Affectation (s, e) -> affecter_variable s (eval e vars) vars; vars
- | Bloc [] -> vars
- | Bloc (i::is) -> exec (Bloc is) (exec i vars)
- | While (e, i) as w -> (match (eval e vars)
- with 0 -> vars
- | _ -> exec w (exec i vars))
- | If (e, i) -> (match (eval e vars) with
- | 0 -> vars
- | _ -> exec i vars)
- | Nord -> deplacer N vars
- | Sud -> deplacer S vars
- | Est -> deplacer E vars
- | Ouest -> deplacer O vars
- in
- exec p variables
- ;;
- "x = 10;
- while (x < 40) {
- print x;
- println;
- x = x + 10;
- }";;
- let test_run () =
- let () = begin
- end
- in
- let deplacer direction variables =
- let
- aller (x, y) =
- begin
- variables
- end
- in
- match direction with
- | N -> aller(0, 10)
- | S -> aller(0, -10)
- | E -> aller(10, 0)
- | O -> aller(-10, 0)
- in
- let p = Bloc [
- Declaration "x";
- Affectation ("x", Ent 0);
- While (Inf (Var "x", Ent 10),
- Bloc [
- Est;
- Nord;
- Affectation ("x", Plus (Var "x", Ent 1))
- ]);
- Affectation ("x", Ent 0);
- While (Inf (Var "x", Ent 10),
- Bloc [
- Nord;
- Ouest;
- Affectation ("x", Plus (Var "x", Ent 1))
- ]);
- Affectation ("x", Ent 0);
- While (Inf (Var "x", Ent 10),
- Bloc [
- Ouest;
- Sud;
- Affectation ("x", Plus (Var "x", Ent 1))
- ]);
- Affectation ("x", Ent 0);
- While (Inf (Var "x", Ent 10),
- Bloc [
- Sud;
- Est;
- Affectation ("x", Plus (Var "x", Ent 1))
- ]);
- ]
- in
- run p deplacer;;
- ;;
Question pour une prochaine fois
1. Écrire une fonction déplacer qui prend en paramètre un plateau de jeu représentant le quadrillage (notamment les déplacements possibles et impossibles), une direction et un paramètre supplémentaire non utilisé et déplace un objet sur le plateau selon les quatre directions cardinales, uniquement lorsque le déplacement est possible. Comment stocker la position de l’objet, pour la mettre à jour d’un appel à l’autre à la fonction déplacer ?
2. En ajoutant des instructions Gauche, Droite, Avancer, pour respectivement tourner de 90° à gauche, tourner de 90° à droite, ou avancer d’une case, vous pourriez représenter le déplacement du crayon à la première personne plutôt que de façon absolue avec les points cardinaux. Où stocker la direction dans laquelle va le crayon ? Pouvez-vous stocker cette information comme une variable d’environnement dans la liste des variables manipulée par l’interprète ? Peut-on faire en sorte que la valeur d’une variable d’environnement ne puisse jamais être écrite (directement) par le programme interprété (on admettra le masquage de son nom par une variable locale) ?
3. Pouvez vous afficher le code source du programme à l’écran, d’une façon élégante (en utilisant une syntaxe concrète plus agréable que la syntaxe abstraite) ?
- Interprète graphique encore amélioré
- Pourriez-vous afficher le code source du programme ? Et déplacer le vaisseau comme si c’était le crayon ?
4. Comment faire pour que le mini-langage admette des définitions et des appels de fonctions ?
Retour sur l’affichage en double buffer
L’affichage ocaml fonctionne bien avec un mécanisme de double buffer :
un back buffer dans lequel on écrit lorsque on a appelé remember_mode avec comme argument true
un front buffer qui est simplement le contenu de l’écran et dans lequel on écrit lorsque on a appelé display_mode avec l’argument true
Pour dessiner un décor puis un ou plusieurs objets mobiles on va afficher un certain nombre d’images fixes dont la succession rapide donnera l’illusion d’animation. Ces images doivent êtres faites du décor par dessus lequel les objets mobiles auront été placés. Le plus simple est de n’écrire que sur l’écran et pas dans le back buffer. Le back buffer sera préparé avec le décor une fois pour toute. Après quoi on cesse d’y écrire et on dessine les objets mobiles uniquement à l’écran. Après la première scène, et à chaque fois après chaque scène, on recopie alors le contenu du back buffer à l’écran pour restaurer un décor vierge avant d’y dessiner les objets mobiles à leurs nouvelles positions. (L’idéal serait de bénéficier d’un troisième buffer pour préparer cette nouvelle scène avant de l’afficher mais le module graphics ne fournit pas cette facilité.) Pour restaurer le contenu du back buffer dans le front buffer (l’écran) on utilise la fonction synchronize
Ceci est une première solution pour réaliser des animations. On remarquera toutefois que chaque scène nécessite la recopie complète du back buffer dans le front buffer. Une solution plus élégante serait de ne rétablir l’image de fond (le décor) que là où la scène précédente contenait un objet qui doit être effacé dans la nouvelle scène. Pour approcher de ce résultat il faut utiliser la fonction
Cette fonction a un comportement intéressant (mais non documenté ?). En effet seul le contenu du back buffer est lu par cette fonction (elle ne tient pas compte de ce qui est dans le front buffer.
- #load "graphics.cma";;
- (* rectangle bleu back et front *)
- (* rectangle vert front *)
- verte = blue;; (* <-- true *)
- (* rectangle vert back et front *)
- verte = bleue;; (* <-- false *)
Par ailleurs cette fonction get_image vous sera utile pour composer vos effets de transparence. On peut d’ailleurs coupler la composition de la transparence à la restauration du décor en mettant une marge complètement transparente autour de nos images d’objets mobiles. Attention toutefois : deux objets du front ne seront pas transparents l’un pour l’autre.