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 }
-
É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>
- É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>
- 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>
- 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>
- Vérifier
que le programme suivant produit bien les images de la
figure 5.10.
let
pi
=
3
.
1
4
1
5
9
2
7
;;
let
s
=
{x=
1
0
0
.
;
y=
0
.
;
a=
pi
/.
2
.
;
r
=
1
0
0
.}
;;
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
.
7
6
6
;
(-.
pi
/.
4
.
),
0
.
5
5
,
0
.
3
3
3
;
(pi
/.
3
.
),
0
.
4
,
0
.
5
]
;;
# Graphics.close_graph();;
- : unit = ()
# Graphics.open_graph
":0 200x200"
;;
- : unit = ()
# let
pi
=
3
.
1
4
1
5
9
2
7
;;
val pi : float = 3.1415927
# let
s
=
{x=
1
0
0
.
;
y=
0
.
;
a=
pi
/.
2
.
;
r
=
1
0
0
.}
;;
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
.
7
6
6
;
(-.
pi
/.
4
.
),
0
.
5
5
,
0
.
3
3
3
;
(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.
-
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 }
- É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>
- É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
1
0
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.
###################-------------#######---------######
###################---------------###-------------##--
###-----###-----###---------------###-------------#---
##------###------##----------------###-----------##---
#-------###-------#-----------------###---------##----
#-------###-------#-----------------###--------##-----
--------###--------------------------###-------#------
--------###-------###############-----###----##-------
--------###-------###---------###------###--##--------
--------###-------###----------##-------###-#---------
--------###-------###-----------#-------#####---------
--------###-------###-----------#--------###----------
--------###-------###--------------------####---------
--------###-------###--------------------####---------
--------###-------###------#-----------##---###-------
--------###-------###------#----------##----###-------
--------###-------##########----------#------###------
--------###-------##########---------##-------###-----
--------###-------###------#--------##--------###-----
--------###-------###------#-------##----------###----
--------###-------###--------------#------------###---
------#######-----###-----------#######--------#######
------------------###---------------------------------
------------------###-----------#---------------------
------------------###-----------#---------------------
------------------###----------##---------------------
------------------###---------###---------------------
------------------###############---------------------
- 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.
-
Écrire la fonction start
de type etat_bitmap -> unit -> unit
qui ouvre la fenêtre graphique et affiche le bitmap passé en paramètre.
# exception
Fin
;;
exception Fin
# let
squel
f_init
f_end
f_key
f_mouse
f_except
=
f_init
();
try
while
true
do
try
let
s
=
Graphics.wait_next_event
[
Graphics.
Button_down;
Graphics.
Key_pressed]
in
if
s.
Graphics.keypressed
then
f_key
s.
Graphics.key
else
if
s.
Graphics.button
then
f_mouse
s.
Graphics.mouse_x
s.
Graphics.mouse_y
with
Fin
->
raise
Fin
|
e
->
f_except
e
done
with
Fin
->
f_end
()
;;
val squel :
(unit -> 'a) ->
(unit -> unit) ->
(char -> unit) -> (int -> int -> unit) -> (exn -> unit) -> unit = <fun>
# let
start
b
()
=
let
ow
=
1
+
b.
w*
b.
s
and
oh
=
1
+
b.
h*
b.
s
in
Graphics.open_graph
(":0 "
^
(string_of_int
ow)
^
"x"
^
(string_of_int
oh))
;
Graphics.set_color
(Graphics.rgb
1
5
0
1
5
0
1
5
0
)
;
Graphics.fill_rect
0
0
ow
oh
;
draw_bitmap
b
;;
val start : etat_bitmap -> unit -> unit = <fun>
- Écrire la fonction stop
qui ferme la fenêtre graphique et sort du programme.
# let
stop
()
=
Graphics.close_graph()
;
exit
0
;;
val stop : unit -> 'a = <fun>
- Écrire la fonction mouse
de type etat_bitmap -> int -> int -> unit
qui modifie l'état du pixel correspondant au clic souris
et affiche ce changement.
# let
mouse
b
x
y
=
let
i,
j
=
(x
/
b.
s),
(y/
b.
s)
in
if
(
i
<
b.
w
)
&&
(
j
<
b.
h)
then
begin
b.
pix.
(i).
(j)
<-
not
b.
pix.
(i).
(j)
;
draw_pix
i
j
b.
s
(if
b.
pix.
(i).
(j)
then
b.
fg
else
b.
bg)
end
;;
val mouse : etat_bitmap -> int -> int -> unit = <fun>
- Écrire la fonction key
de type string -> etat_bitmap -> char -> unit
qui prend en paramètre un nom de fichier, un bitmap et le caractère de la touche appuyée
et effectue les actions associées : sauvegarde dans un fichier pour la touche 'S'
et déclenchement de l'exception Fin pour la touche 'Q'.
# let
key
filename
b
c
=
match
c
with
'q'
|
'Q'
->
raise
Fin
|
's'
|
'S'
->
save_bitmap
filename
b
|
_
->
()
;;
val key : string -> etat_bitmap -> char -> unit = <fun>
- É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
1
0
1
0
Graphics.black
Graphics.white
1
0
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.
-
É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 }
- É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>
- 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
2
0
0
0
0
0
;
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>
- É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>
- É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>
- É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
1
5
0
1
5
0
1
5
0
);
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>
- É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
1
5
0
1
5
0
1
5
0
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
1
0
0
));;
val go : int -> int -> int -> unit = <fun>