Précédent Index Suivant

Exercices

Coordonnées polaires

Les coordonnées de la bibliothèque Graphics sont cartésiennes. Un segment y est représenté par son point de départ (x0,y0) et son point d'arrivée (x1,y2) Il peut être utile d'utiliser des coordonnées polaires. Un segment est alors décrit par son point de départ (x0,y0) la taille du segment (r) et son angle (a) Le rapport entre coordonnées cartésiennes et coordonnées polaires est définie par les équations :
ì
í
î
x1 = x0 + r * cos(a)
y1 = y0 + r * sin(a)
Le type suivant définit les coordonnées polaires d'un segment :

# type seg_pol = {x:float; y:float; r:float; a:float};;
type seg_pol = { x: float; y: float; r: float; a: float }


  1. Écrire la fonction to_cart de transformation de coordonnées polaires en coordonnées cartésiennes.

    # let to_cart p =
    (p.x,p.y),(p.x +. p.r *. (cos p.a), p.y +. p.r *. (sin p.a)) ;;
    val to_cart : seg_pol -> (float * float) * (float * float) = <fun>


  2. Écrire la fonction draw_seg qui affiche un segment défini en coordonnées polaires dans le repère de Graphics.

    # let draw_seg p =
    let (x1,y1),(x2,y2) = to_cart p in
    Graphics.moveto (int_of_float x1) (int_of_float y1);
    Graphics.lineto (int_of_float x2) (int_of_float y2) ;;
    val draw_seg : seg_pol -> unit = <fun>


  3. Un des intérêts des coordonnées polaires est de pouvoir facilement appliquer des transformations sur un segment. Une translation ne modifiera que le point de départ, une rotation sera uniquement effectuée sur le champ angle, de même pour un changement d'échelle sur le champ longueur. De façon générale, on peut représenter une transformation comme un triplet de flottants : le premier représente la translation (on ne considère, ici, que des translations sur la droite du segment), le second la rotation et le troisième le facteur d'échelle. Définir la fonction app_trans qui prend un segment en coordonnées polaires et un triplet de transformations et retourne le nouveau segment.

    # let app_trans seg ( da, dr, dxy ) =
    let _,(x1,y1) = to_cart {seg with r = seg.r *. dxy} in
    {x=x1; y=y1; a=seg.a +. da; r=seg.r *. dr} ;;
    val app_trans : seg_pol -> float * float * float -> seg_pol = <fun>


  4. On peut construire des dessins récursifs par itération de transformations. Écrire la fonction dessin_r qui prend en arguments un segment s, un nombre d'itérations n, une liste de transformations l et affiche tous les segments résultant des transformations sur s itérées jusqu'à n.

    # let rec dessin_r s n l =
    if n = 0 then ()
    else
    begin
    draw_seg s;
    List.iter (fun t -> dessin_r (app_trans s t) (n-1) l) l
    end ;;
    val dessin_r : seg_pol -> int -> (float * float * float) list -> unit = <fun>


  5. Vérifier que le programme suivant produit bien les images de la figure 5.10.

    let pi = 3.1415927 ;;
    let s = {x=100.; y= 0.; a= pi /. 2.; r = 100.} ;;
    dessin_r s 6 [ (-.pi/.2.),0.6,1.; (pi/.2.), 0.6,1.0] ;;
    Graphics.clear_graph();;
    dessin_r s 6 [(-.pi /. 6.), 0.6, 0.766;
    (-.pi /. 4.), 0.55, 0.333;
    (pi /. 3.), 0.4, 0.5 ] ;;

    # Graphics.close_graph();;
    - : unit = ()
    # Graphics.open_graph ":0 200x200";;
    - : unit = ()
    # let pi = 3.1415927 ;;
    val pi : float = 3.1415927
    # let s = {x=100.; y= 0.; a= pi /. 2.; r = 100.} ;;
    val s : seg_pol = {x=100; y=0; r=100; a=1.57079635}
    # dessin_r s 6 [ (-.pi/.2.),0.6,1.; (pi/.2.), 0.6,1.0] ;;
    - : unit = ()
    # Graphics.clear_graph();;
    - : unit = ()
    # dessin_r s 6 [(-.pi /. 6.), 0.6, 0.766;
    (-.pi /. 4.), 0.55, 0.333;
    (pi /. 3.), 0.4, 0.5 ] ;;
    - : unit = ()


Figure 5.10 : dessins récursifs


Éditeur de bitmaps

On cherche à un écrire un petit éditeur de bitmap (à la manière de la commande bitmap de X-window). Pour cela on représente un bitmap par ses dimensions (largeur et hauteur), la taille des pixels, et un tableau à 2 dimensions de booléens.

  1. Définir un type etat_bitmap décrivant l'information nécessaire pour conserver les valeurs des pixels, la taille du bitmap et les couleurs d'affichage et d'effacement.


    # type etat_bitmap =
    {w : int; h : int; fg : Graphics.color; bg : Graphics.color;
    pix : bool array array; s : int} ;;
    type etat_bitmap =
    { w: int;
    h: int;
    fg: Graphics.color;
    bg: Graphics.color;
    pix: bool array array;
    s: int }


  2. Écrire les fonctions de création d'un bitmap (create_bitmap) et d'affichage d'un bitmap (draw_bitmap) .

    # let create_bitmap x y f g t =
    let r = Array.make_matrix x y false in
    { w = x; h = y; fg = f; bg = g; pix = r; s = t} ;;
    val create_bitmap :
    int -> int -> Graphics.color -> Graphics.color -> int -> etat_bitmap =
    <fun>

    # let draw_pix i j s c =
    Graphics.set_color c;
    Graphics.fill_rect (i*s+1) (j*s+1) (s-1) (s-1) ;;
    val draw_pix : int -> int -> int -> Graphics.color -> unit = <fun>

    # let draw_bitmap b =
    for i=0 to b.w-1 do
    for j=0 to b.h-1 do
    draw_pix i j b.s (if b.pix.(i).(j) then b.fg else b.bg)
    done
    done ;;
    val draw_bitmap : etat_bitmap -> unit = <fun>


  3. Écrire les fonctions read_bitmap et write_bitmap qui respectivement lit et écrit un bitmap dans un fichier passé en paramètre en suivant le format ASCII de X-window. Si le fichier n'existe pas, alors la fonction de lecture crée un nouveau bitmap en utilisant la fonction create_bitmap.

    # let read_file filename =
    let ic = open_in filename in
    let rec aux () =
    try
    let line = (input_line ic) in
    line :: (aux ())
    with End_of_file -> close_in ic ; []
    in aux ();;
    val read_file : string -> string list = <fun>

    # let read_bitmap filename =
    let r = Array.of_list (read_file filename) in
    let h = Array.length r in
    let w = String.length r.(0) in
    let b = create_bitmap w h Graphics.black Graphics.white 10 in
    for j = 0 to h - 1 do
    for i = 0 to w - 1 do
    b.pix.(i).(j) <- ( r.(j).[i] = '#')
    done
    done;
    b ;;
    val read_bitmap : string -> etat_bitmap = <fun>

    # let save_bitmap filename b =
    let oc = open_out filename in
    let f x = output_char oc (if x then '#' else '-') in
    Array.iter (fun x -> (Array.iter f x); output_char oc '\n') b.pix ;
    close_out oc ;;
    val save_bitmap : string -> etat_bitmap -> unit = <fun>
    Un pixel allumé est représenté par le caractère #, l'absence d'un pixel par le caractère -. Chaque ligne de caractères représente une ligne du bitmap. On pourra tester le programme en utilisant les fonctions atobm et bmtoa de X-window qui effectueront les conversions entre ce format ASCII et le format des bitmaps créés par la commande bitmap. Voici un exemple.

    
     ###################-------------#######---------######
     ###################---------------###-------------##--
     ###-----###-----###---------------###-------------#---
     ##------###------##----------------###-----------##---
     #-------###-------#-----------------###---------##----
     #-------###-------#-----------------###--------##-----
     --------###--------------------------###-------#------
     --------###-------###############-----###----##-------
     --------###-------###---------###------###--##--------
     --------###-------###----------##-------###-#---------
     --------###-------###-----------#-------#####---------
     --------###-------###-----------#--------###----------
     --------###-------###--------------------####---------
     --------###-------###--------------------####---------
     --------###-------###------#-----------##---###-------
     --------###-------###------#----------##----###-------
     --------###-------##########----------#------###------
     --------###-------##########---------##-------###-----
     --------###-------###------#--------##--------###-----
     --------###-------###------#-------##----------###----
     --------###-------###--------------#------------###---
     ------#######-----###-----------#######--------#######
     ------------------###---------------------------------
     ------------------###-----------#---------------------
     ------------------###-----------#---------------------
     ------------------###----------##---------------------
     ------------------###---------###---------------------
     ------------------###############---------------------
    
  4. On reprend le squelette de boucle d'interaction de la page ?? pour construire l'interface graphique de l'éditeur. L'interface homme-machine est fort simple. Le bitmap est affiché en permanence dans la fenêtre graphique. Un clic souris sur une des cases du bitmap inverse sa valeur. Ce changement est répercuté à l'écran. L'appui sur la touche 'S' sauve le bitmap dans le fichier. L'appui sur la touche 'Q' fait sortir du programme.
  5. Écrire une fonction go qui prend en paramètre un nom de fichier, charge le bitmap puis l'affiche et lance la boucle d'interaction.

    # let go name =
    let b = try
    read_bitmap name
    with
    _ -> create_bitmap 10 10 Graphics.black Graphics.white 10
    in squel (start b) stop (key name b) (mouse b) (fun e -> ()) ;;
    val go : string -> unit = <fun>

Ver de Terre

Le ver de terre est un petit organisme, longiforme, d'une certaine taille qui va croître soit avec le temps soit en ramassant des objets dans un monde. Le ver de terre est toujours en mouvement selon une direction. Ses actions propres ou dirigées par un joueur permettent uniquement un changement de direction. Le ver de terre disparaît s'il touche un bord du monde ou s'il passe sur une partie de son corps. On le représente le plus souvent par un vecteur de coordonnées avec deux indices principaux : sa tête et sa queue. Un mouvement sera donc le calcul des nouvelles coordonnées de sa tête, et son affichage, et l'effacement de sa queue. Une croissance ne modifiera que sa tête sans toucher à la queue du ver de terre.
  1. Écrire le ou les types Objective CAML pour représenter un ver de terre et le monde où il évolue. On peut représenter un ver de terre par une file d'attente de ses coordonnées.

    # type cell = Vide | Pleine ;;
    type cell = | Vide | Pleine

    # type monde = { l : int; h : int; cases : cell array array } ;;
    type monde = { l: int; h: int; cases: cell array array }

    # type vdt = { mutable tete : int; mutable queue : int; mutable taille : int;
    mutable vx : int; mutable vy : int;
    pos : (int * int) array } ;;
    type vdt =
    { mutable tete: int;
    mutable queue: int;
    mutable taille: int;
    mutable vx: int;
    mutable vy: int;
    pos: (int * int) array }

    # type jeu = { m : monde; v : vdt; t_cell : int;
    fg : Graphics.color; bg : Graphics.color } ;;
    type jeu =
    { m: monde;
    v: vdt;
    t_cell: int;
    fg: Graphics.color;
    bg: Graphics.color }


  2. Écrire les fonctions d'initialisation et d'affichage d'un ver de terre dans un monde.

    # let init_monde larg haut =
    { l = larg; h = haut; cases = Array.create_matrix larg haut Vide} ;;
    val init_monde : int -> int -> monde = <fun>

    # let init_vdt t t_max larg =
    if t > larg then failwith "init_vdt"
    else
    begin
    let v = { tete = t-1; queue = 0; taille = t; vx = 1; vy = 0;
    pos = Array.create t_max (0,0) } in
    let y = larg / 2
    and rx = ref (larg/2 - t/2) in
    for i=0 to t-1 do v.pos.(i) <- (!rx,y) ; incr rx done ;
    v
    end ;;
    val init_vdt : int -> int -> int -> vdt = <fun>

    # let init_jeu t t_max larg haut tc c1 c2 =
    let j = { m = init_monde larg haut; v = init_vdt t t_max larg;
    t_cell = tc; fg = c1; bg = c2 } in
    for i=j.v.tete to j.v.queue do
    let (x,y) = j.v.pos.(i) in
    j.m.cases.(x).(y) <- Pleine
    done ;
    j ;;
    val init_jeu :
    int -> int -> int -> int -> int -> Graphics.color -> Graphics.color -> jeu =
    <fun>

    # let affiche_case x y t c =
    Graphics.set_color c;
    Graphics.fill_rect (x*t) (y*t) t t ;;
    val affiche_case : int -> int -> int -> Graphics.color -> unit = <fun>

    # let affiche_monde jeu =
    let m = jeu.m in
    for i=0 to m.l-1 do
    for j=0 to m.h-1 do
    let col = if m.cases.(i).(j) = Pleine then jeu.fg else jeu.bg in
    affiche_case i j jeu.t_cell col
    done
    done ;;
    val affiche_monde : jeu -> unit = <fun>


  3. Modifier la fonction squel de squelette du programme qui à chaque tour dans la boucle d'interaction effectue une action, paramétrée par une fonction. La gestion de l'événement clavier ne doit pas être bloquante.

    (************** interaction **********)

    # let tempo ti =
    for i = 0 to ti do ignore (i * ti * ti ) done ;;
    val tempo : int -> unit = <fun>

    # exception Fin;;
    exception Fin
    # let squel f_init f_end f_key f_mouse f_except f_run =
    f_init ();
    try
    while true do
    try
    tempo 200000 ;
    if Graphics.key_pressed() then f_key (Graphics.read_key()) ;
    f_run ()
    with
    Fin -> raise Fin
    | e -> f_except e
    done
    with
    Fin -> f_end () ;;
    val squel :
    (unit -> 'a) ->
    (unit -> unit) ->
    (char -> unit) -> 'b -> (exn -> 'c) -> (unit -> 'c) -> unit = <fun>


  4. Écrire la fonction run qui fait avancer le ver de terre dans le jeu. Cette fonction déclenche les exceptions Gagne (si le ver a atteint une certaine taille) et Perdu s'il rencontre une case pleine ou un bord du monde.

    # exception Perdu;;
    exception Perdu
    # exception Gagne;;
    exception Gagne

    # let run jeu temps itemps () =
    incr temps;
    let v = jeu.v in
    let t = Array.length v.pos in
    let ox,oy = v.pos.(v.tete) in
    let nx,ny = ox+v.vx,oy+v.vy in
    if (nx < 0 ) || (nx >= jeu.m.l) || (ny < 0) || (ny >= jeu.m.h)
    then raise Perdu
    else if jeu.m.cases.(nx).(ny) = Pleine then raise Perdu
    else if v.tete = v.queue then raise Gagne
    else
    begin
    let ntete = (v.tete + 1) mod t in
    v.tete <- ntete ;
    v.pos.(v.tete) <- (nx,ny);
    jeu.m.cases.(nx).(ny) <- Pleine ;
    affiche_case nx ny jeu.t_cell jeu.fg ;
    if (!temps mod !itemps < (!itemps - 2)) then
    begin
    let qx,qy = v.pos.(v.queue) in
    jeu.m.cases.(qx).(qy) <- Vide ;
    affiche_case qx qy jeu.t_cell jeu.bg ;
    v.queue <- (v.queue + 1) mod t
    end
    end ;;
    val run : jeu -> int ref -> int ref -> unit -> unit = <fun>


  5. Écrire la fonction d'interaction clavier qui modifie la direction du ver de terre.

    # let key lact jeu c =
    match c with
    'q' | 'Q' -> raise Fin
    | 'p' | 'P' -> ignore (Graphics.read_key ())
    | '2' | '4' | '6' | '8' ->
    let dx,dy = List.assoc c lact in
    jeu.v.vx <- dx ;
    jeu.v.vy <- dy
    | _ -> () ;;
    val key : (char * (int * int)) list -> jeu -> char -> unit = <fun>


  6. Écrire les autres fonctions utilitaires de gestion de l'interaction et les passer au nouveau squelette de programme.

    # let start jeu () =
    let ow = jeu.t_cell * jeu.m.l and oh = jeu.t_cell * jeu.m.h in
    let size = (string_of_int ow) ^ "x" ^ (string_of_int oh) in
    Graphics.open_graph (":0 " ^ size) ;
    Graphics.set_color (Graphics.rgb 150 150 150);
    Graphics.fill_rect 0 0 ow oh;
    affiche_monde jeu;
    ignore (Graphics.read_key()) ;;
    val start : jeu -> unit -> unit = <fun>

    # let stop jeu () =
    ignore (Graphics.read_key());
    Graphics.close_graph() ;;
    val stop : 'a -> unit -> unit = <fun>

    # let mouse x y = () ;;
    val mouse : 'a -> 'b -> unit = <fun>

    # let except e = match e with
    Perdu -> print_endline "PERDU"; raise Fin
    | Gagne -> print_endline "GAGNE"; raise Fin
    | e -> raise e ;;
    val except : exn -> 'a = <fun>


  7. Écrire la fonction principale de lancement de l'application.

    # let la = [ ('2',(0,-1)) ; ('4',(-1,0)) ; ('6',(1,0)) ; ('8',(0,1)) ] ;;
    val la : (char * (int * int)) list =
    ['2', (0, -1); '4', (-1, 0); '6', (1, 0); '8', (0, ...)]

    # let go larg haut tc =
    let col = Graphics.rgb 150 150 150 in
    let jeu = init_jeu 5 (larg*haut /2) larg haut tc Graphics.black col in
    squel (start jeu) (stop jeu) (key la jeu) mouse except (run jeu (ref 0) (ref 100));;
    val go : int -> int -> int -> unit = <fun>

Précédent Index Suivant