Author: Christian.Queinnec@lip6.fr

## Des robots en goguette

L'exemple de ce paragraphe illustre l'utilisation d'objets ainsi que la bibliothèque graphique. Nous verrons comment Objective CAML reprend les notions d'héritage simple, d'héritage multiple, de redéfinition de méthode et de liaison dynamique. Nous verrons également comment les classes paramétrées peuvent être mises à profit.
L'application comprend deux catégories principales d'objets : un monde et des robots. Le monde est un ensemble de cases sur lesquelles évoluent des robots. Nous aurons plusieures classes de robots. Chacune d'elles possédera sa propre stratégie de déplacement dans le monde. Le principe d'interaction du monde et des robots est ici extrêmement simple. Le monde est entièrement maître du jeu : il demande tour à tour à chacun des robots qu'il connaît quelle est sa prochaine position. Chaque robot détermine sa prochaine position à l'aveugle : il ne connaît ni la géométrie du monde, ni les autres robots présents. Si la position demandée par un robot est légale et libre alors le monde l'y déplace.
Le monde matérialisera l'évolution des robots par une interface. La complexité (toute relative) de la conception et du développement de cet exemple est dans la toujours nécessaire séparation entre un traitement (ici : l'évolution des robots) et son interface (ici : la trace de cette évolution).

##### Description générale.

L'application est développée en deux temps.

1. un ensemble de définitions donnant des classes de calcul pur pour le monde et pour divers robots envisagés.
2. un ensemble de définitions, utilisant les précédentes et ajoutant ce qui est nécessaire à la mise en place d'une interface. Nous donnerons deux exemples d'interfaces : une rudimentaire sous forme de texte ; une, plus élaborée, utilisant la bibliothèque graphique.

### Robots << éthérés >>

Dans un premier temps, nous nous intéressons aux robots hors de toute considération sur l'environnement qui les entourent, c'est à dire de l'interface qui les affiche.

    # class virtual robot (i0:int) (j0:int) =
       object
         val mutable i = i0
         val mutable j = j0
         method get_pos = (i,j)
         method set_pos (i', j') = i <- i'; j <- j'
         method virtual next_pos : unit -> (int * int)
       end  ;;

En toute généralité, un robot est une entité connaissant, ou croyant connaître, sa position (`i` et `j`), capable de la donner à qui la lui demande (`get_pos`), susceptible de modifier cette connaissance si on la lui précise (`set_pos`) et sachant décider d'un éventuel mouvement vers une nouvelle position (`next_pos`).

> Figure 17.9 : hiérarchie de classes des robots purs

Pour améliorer la lisibilité du programme, nous définissons les mouvements relatifs à une direction absolue :

    # type dir = North | East | South | West | Nothing ;;
    # let walk (x,y) = function
         North -> (x,y+1) | South -> (x,y-1)
       | West  -> (x-1,y) | East  -> (x+1,y)
       | Nothing -> (x,y) ;;
    val walk : int * int -> dir -> int * int = <fun>
    # let turn_right = function
       North -> East | East -> South | South -> West | West -> North | x -> x ;;
    val turn_right : dir -> dir = <fun>

Du schéma induit par la classe virtuelle des `robots`, nous définissons quatre espèces de robots distinctes (voir la figure 17.9) en précisant leur manière de se déplacer :

- les robots fixes qui ne bougent jamais :

      # class fix_robot i0 j0 =
         object
           inherit robot i0 j0
           method next_pos() = (i,j)
         end  ;;

- les robots fous qui se déplacent au hasard :

      # class crazy_robot i0 j0 =
         object
           inherit robot i0 j0
           method next_pos () = ( i+(Random.int 3)-1 , j+(Random.int 3)-1 )
         end ;;

- les robots obstinés qui conservent la même direction tant qu'ils peuvent avancer,

      # class obstinate_robot i0 j0 =
         object(self)
           inherit robot i0 j0
           val mutable wanted_pos = (i0,j0)
           val mutable dir = West

           method private set_wanted_pos d = wanted_pos <- walk (i,j) d
           method private change_dir = dir <- turn_right dir
           method next_pos () = if (i,j) = wanted_pos
             then let np = walk (i,j) dir in ( wanted_pos <- np ; np )
             else ( self#change_dir ; wanted_pos <- (i,j) ; (i,j) )
        end ;;

- les robots téléguidés qui obéissent à un opérateur extérieur :

      # class virtual interactive_robot i0 j0 =
         object(self)
           inherit robot i0 j0
           method virtual private get_move : unit -> dir
           method next_pos () = walk (i,j) (self#get_move ())
          end ;;

Le cas du robot interactif est différent des autres car son comportement est lié à l'interface qui permettra de lui communiquer des ordres. En attendant, nous nous appuyons sur une méthode qui, virtuellement, communique cet ordre et en conséquence la classe `interactive_robot` demeure abstraite.
Remarquons que non seulement les quatre classes de robots spécialisés héritent de la classe `robot` mais que de surcroît elles en ont le type. En effet, les seules méthodes que nous ayons ajoutées sont des méthodes privées et donc n'apparaissent pas dans le type des instances de ces classes (voir page ??). Ce point nous est indispensable si nous souhaitons considérer tous les robots comme des objets de même type.

### Monde pur

Un monde pur est un monde indépendant de l'interface. Il y est connu l'ensemble des positions qu'un robot est susceptible d'occuper. Cela prend la forme d'une grille de taille `l`×`h`, d'une méthode `is_legal` assurant qu'un couple d'entiers est bien une position dans le monde, et d'une méthode `is_free` indiquant si un robot occupe ou non une position donnée.
En outre, un monde dispose de la liste des robots présents sur sa surface `robots` ainsi que d'une méthode `add` permettant de faire rentrer de nouveaux robots.
Pour finir, un monde est pourvu de la méthode `run` lui permettant de prendre vie.

    # class virtual ['robot_type] world (l0:int) (h0:int) =
       object(self)
         val l = l0
         val h = h0
         val mutable robots =  ( [] : 'robot_type list )
         method add r = robots <- r::robots
         method is_free p = List.for_all (fun r -> r#get_pos <> p)  robots
         method virtual is_legal : (int * int) -> bool

         method private run_robot r =
           let p = r#next_pos ()
           in if (self#is_legal p) & (self#is_free p) then r#set_pos p

         method run () =
           while true do List.iter (function r -> self#run_robot r) robots done
      end ;;
    class virtual ['a] world :
      int ->
      int ->
      object
        constraint 'a =
          < get_pos : int * int; next_pos : unit -> int * int;
            set_pos : int * int -> unit; .. >
        val h : int
        val l : int
        val mutable robots : 'a list
        method add : 'a -> unit
        method is_free : int * int -> bool
        method virtual is_legal : int * int -> bool
        method run : unit -> unit
        method private run_robot : 'a -> unit
      end

Le système de type d'Objective CAML ne permet pas de laisser le type des robots non déterminé (voir page ??). Pour résoudre ce problème, nous avions la possibilité de restreindre ce type à celui de la classe `robot`. Mais dans ce cas, nous nous interdisions de pouvoir peupler un monde d'autres objets que ceux ayant exactement le même type que `robot`. Donc, nous avons choisi de paramétrer la classe `world` par le type des robots qui le peuplent. Nous pourrons ensuite instancier ce paramètre de type par des robots textuels ou par des robots graphiques.

### Robots textuels

##### Des objets texte

Pour obtenir des robots gérables par une interface texte, nous définissons la classe des objets textuels (`txt_object`).

    # class txt_object (s0:string) =
       object
         val name = s0
          method get_name = name
       end  ;;

##### Une classe de spécification : les robots textuels abstraits

Par héritage double de `robots` et `txt_object`, nous obtenons la classe abstraite `txt_robot` des robots textuels.

    # class virtual txt_robot i0 j0 =
       object
         inherit robot i0 j0
         inherit txt_object "Anonymous"
      end  ;;
    class virtual txt_robot :
      int ->
      int ->
      object
        val mutable i : int
        val mutable j : int
        val name : string
        method get_name : string
        method get_pos : int * int
        method virtual next_pos : unit -> int * int
        method set_pos : int * int -> unit
      end

Cette classe nous sert pour définir un monde à interface texte (voir page ??). Les habitants de ce monde ne seront ni des objets de `txt_robot` (puisque cette classe est abstraite) ni des héritiers de cette classe. La classe `txt_robots` est en quelque sorte une classe de spécification permettant au compilateur d'identifier les types des méthodes (calcul et interface) des habitants du monde à interface texte. L'utilisation d'une telle classe de spécification vient de la séparation que nous voulons maintenir entre les calculs et l'interface.

##### Les robots concrets en mode texte

Ils s'obtiennent simplement par double héritage; la figure 17.10 donne leur hiérarchie de classes.

> Figure 17.10 : hiérarchie de classes des robots en mode texte

    # class fix_txt_robot i0 j0 =
       object
         inherit fix_robot i0 j0
         inherit txt_object "Fix robot"
       end  ;;

    # class crazy_txt_robot i0 j0 =
       object
         inherit crazy_robot i0 j0
         inherit txt_object "Crazy robot"
       end  ;;

    # class obstinate_txt_robot i0 j0 =
       object
         inherit obstinate_robot i0 j0
         inherit txt_object "Obstinate robot"
       end  ;;

Les robots interactifs doivent pour devenir concrets définir leur méthode d'interaction avec l'utilisateur.

    # class interactive_txt_robot i0 j0 =
       object
         inherit interactive_robot i0 j0
         inherit txt_object "Interactive robot"
         method private get_move () =
           print_string "Which dir : (n)orth (e)ast (s)outh (w)est ? ";
           match read_line() with
               "n" -> North  | "s" -> South
             | "e" -> East   | "w" -> West
             |  _  -> Nothing
       end ;;

### Monde textuel

Le monde à interface texte se dérive du monde pur par

1. héritage de la classe générique `world` en instanciant son paramètre de type par la classe de spécification `txt_robot`,
2. redéfinition de la méthode `run` pour y inclure les différents affichages textuels.

    # class virtual txt_world (l0:int) (h0:int) =
       object(self)
         inherit [txt_robot] world l0 h0 as super

         method private display_robot_pos r =
           let (i,j) = r#get_pos in Printf.printf "(%d,%d)" i j

         method private run_robot r =
          let p = r#next_pos ()
          in if (self#is_legal p) & (self#is_free p)
             then
               begin
                 Printf.printf "%s is moving from " r#get_name ;
                 self#display_robot_pos r ;
                 print_string " to " ;
                 r#set_pos p;
                 self#display_robot_pos r ;
               end
             else
               begin
                 Printf.printf "%s is staying at " r#get_name ;
                 self#display_robot_pos r
               end ;
            print_newline () ;
            print_string"next - ";
            ignore (read_line())

         method run () =
           let print_robot r =
             Printf.printf "%s is at " r#get_name ;
             self#display_robot_pos r ;
             print_newline ()
           in
              print_string "Initial state :\n";
              List.iter print_robot robots;
              print_string "Running :\n";
              super#run()  (* 1 *)
       end  ;;

Nous attirons l'attention du lecteur sur l'appel à la méthode `run` de la classe ancêtre (marqué `(* 1 *)` dans le code) dans la redéfinition de cette même méthode. Nous avons là une illustration des deux types de liaison des méthodes possibles : statique ou dynamique (voir page ??). L'appel à super#run est statique; c'est l'intérêt de nommer la superclasse que de pouvoir appeler ses méthodes alors qu'elles ont été redéfinies. Par contre, dans cette méthode super#run se trouve un appel à self#run_robot. C'est ici une liaison dynamique qui a lieu; c'est la méthode définie dans la classe `txt_world` qui est exécutée et non celle de `world`, sans quoi nous n'obtiendrions aucun affichage.

##### Le monde plan rectangulaire textuel

s'obtient en implantant la dernière méthode encore abstraite : `is_legal`.

    # class closed_txt_world l0 h0 =
       object(self)
         inherit txt_world l0 h0
         method is_legal (i,j) = (0<=i) & (i<l) & (0<=j) & (j<h)
       end  ;;

> Figure 17.11 : hiérarchie de classes du monde plan rectangulaire en mode texte

On peut procéder à un petit essai en tapant :

    let w = new closed_txt_world 5 5
    and r1 = new fix_txt_robot 3 3
    and r2 = new crazy_txt_robot 2 2
    and r3 = new obstinate_txt_robot 1 1
    and r4 = new interactive_txt_robot 0 0
    in w#add r1; w#add r2; w#add r3; w#add r4; w#run () ;;

Nous allons passer à présent à la réalisation de l'interface graphique pour notre monde de robots. En fin de course, nous obtiendrons une application ayant l'apparence de la figure 17.12.

> Figure 17.12 : Le monde graphique des robots

### Robots graphiques

Nous obtenons des robots en mode graphique en suivant le même schéma que le mode texte :

1. définition d'un objet graphique générique,
2. définition d'une classe abstraite de robots graphiques par double héritage des robots et des objets graphiques (analogue de la classe de spécification du paragraphe 17),
3. définition par double héritage des robots possédant un comportement particulier.

#### Objets graphiques génériques

Un objet graphique simple est un objet possédant une méthode `display` qui prend en argument les coordonnées d'un pixel et s'affiche.

    # class virtual graph_object =
       object
         method virtual display : int -> int -> unit
       end ;;

De cette spécification, il est possible de tirer des objets graphiques extrêmement complexes. Nous allons nous contenter ici d'une classe `graph_item` affichant le bitmap qui sert à la construire.

    # class graph_item x y im =
       object (self)
         val size_box_x = x
         val size_box_y = y
         val bitmap = im
         val mutable last = None

         method private erase = match last with
             Some (x,y,img) -> Graphics.draw_image img x y
           | None -> ()

         method private draw i j = Graphics.draw_image bitmap i j
         method private keep i j =
           last <- Some (i,j,Graphics.get_image i j size_box_x size_box_y) ;

         method display i j = match last with
             Some (x,y,img) -> if x<>i || y<>j
                               then ( self#erase ; self#keep i j ; self#draw i j )
           | None -> ( self#keep i j ; self#draw i j )
       end ;;

Un objet `graph_item` conserve la portion d'image sur laquelle il est affiché pour la restaurer lors de l'affichage suivant. De plus, si l'image n'a pas bougé elle n'est pas réaffichée.

    # let foo_bitmap =  [|[| Graphics.black |]|] ;;
    # class square_item x col =
       object
         inherit graph_item x x (Graphics.make_image foo_bitmap)
         method private draw i j = Graphics.set_color col ;
                                   Graphics.fill_rect (i+1) (j+1) (x-2) (x-2)
       end ;;

    # class disk_item r col =
       object
         inherit graph_item (2*r) (2*r) (Graphics.make_image foo_bitmap)
         method private draw i j = Graphics.set_color col ;
                                   Graphics.fill_circle (i+r) (j+r) (r-2)
       end ;;

    # class file_bitmap_item name =
       let ch = open_in name
       in let x = Marshal.from_channel ch
       in let y = Marshal.from_channel ch
       in let im = Marshal.from_channel ch
       in let () = close_in ch
       in object
             inherit graph_item x y (Graphics.make_image im)
          end ;;

Nous avons spécialisé les `graph_item` en carrés, disques et bitmaps lus depuis un fichier.

##### Le robot graphique abstrait

est à la fois un robot et un objet graphique.

    # class virtual graph_robot i0 j0 =
       object
         inherit robot i0 j0
         inherit graph_object
       end  ;;

##### Les robots graphiques fixes, fous et obstinés

sont des objets graphiques spécialisés.

    # class fix_graph_robot i0 j0 =
       object
         inherit fix_robot i0 j0
         inherit disk_item 7 Graphics.green
       end ;;

    # class crazy_graph_robot i0 j0 =
       object
         inherit crazy_robot i0 j0
         inherit file_bitmap_item "crazy_bitmap"
        end ;;

    # class obstinate_graph_robot i0 j0 =
       object
         inherit obstinate_robot i0 j0
         inherit square_item 15 Graphics.black
       end ;;

##### Le robot graphique interactif

utilise les primitives `key_pressed` et `read_key` du module `Graphics` pour l'acquisition du déplacement. On reconnaîtra les touches `8`, `6`, `2` et `4` du pavé numérique (touche `NumLock` active). De cette façon, l'utilisateur n'est pas obligé de donner une indication de déplacement à chaque interrogation du monde.

    # class interactive_graph_robot i0 j0 =
       object
         inherit interactive_robot i0 j0
         inherit file_bitmap_item "interactive_bitmap"
         method private get_move () =
           if not (Graphics.key_pressed ()) then Nothing
           else match Graphics.read_key() with
             '8' -> North | '2' -> South | '4' -> West  | '6' -> East | _ -> Nothing
       end ;;

### Monde graphique

On obtient un monde à interface graphique par héritage du monde pur en instanciant le paramètre `'a_robot` avec la classe abstraite des robots graphiques `graph_robot`. Comme pour le monde en mode texte, le monde graphique redéfinit la méthode `run_robot` de traitement d'un robot et la méthode d'activation générale `run`.

    # let delay x = let t = Sys.time () in while (Sys.time ()) -. t < x do () done ;;

    # class virtual graph_world l0 h0 =
       object(self)
         inherit [graph_robot] world l0 h0 as super
         initializer
           let gl = (l+2)*15 and  gh = (h+2)*15 and lw=7 and cw=7
           in Graphics.open_graph (" "^(string_of_int gl)^"x"^(string_of_int gh)) ;
              Graphics.set_color (Graphics.rgb 170 170 170) ;
              Graphics.fill_rect 0 lw gl lw ;
              Graphics.fill_rect (gl-2*lw) 0 lw gh ;
              Graphics.fill_rect 0 (gh-2*cw) gl cw ;
              Graphics.fill_rect lw 0 lw gh

         method run_robot r = let p = r#next_pos ()
                              in delay 0.001 ;
                                 if (self#is_legal p) & (self#is_free p)
                                 then ( r#set_pos p ; self#display_robot r)

         method display_robot r = let (i,j) = r#get_pos
                                  in r#display (i*15+15) (j*15+15)

         method run() = List.iter self#display_robot robots ;
                        super#run()
       end ;;

Notez que la fenêtre graphique est créée à l'initialisation d'un objet de cette classe.

##### Le monde plan rectangulaire et graphique

s'obtient de la même manière que pour le monde plan rectangulaire et textuel.

    # class closed_graph_world l0 h0 =
       object(self)
         inherit graph_world l0 h0
         method is_legal (i,j) = (0<=i) & (i<l) & (0<=j) & (j<h)
       end ;;
    class closed_graph_world :
      int ->
      int ->
      object
        val h : int
        val l : int
        val mutable robots : graph_robot list
        method add : graph_robot -> unit
        method display_robot : graph_robot -> unit
        method is_free : int * int -> bool
        method is_legal : int * int -> bool
        method run : unit -> unit
        method run_robot : graph_robot -> unit
      end

On peut alors tester l'application graphique en tapant

    let w = new closed_graph_world 10 10 ;;
    w#add (new fix_graph_robot 3 3) ;;
    w#add (new crazy_graph_robot 2 2) ;;
    w#add (new obstinate_graph_robot 1 1) ;;
    w#add (new interactive_graph_robot 5 5) ;;
    w#run () ;;

### Pour en faire plus

L'implantation de la méthode `run_robot` des différents mondes sous-entend que les robots sont potentiellement capables de se rendre en tout point du monde du moment que celui-ci est libre et légal. De plus, rien n'interdit à un robot de modifier sa position sans en prévenir le monde. Une amélioration possible consiste à faire gérer l'ensemble des positions des robots par le monde; lors du déplacement d'un robot, le monde vérifie d'une part si la nouvelle position est légale mais aussi si elle constitue un déplacement autorisé. Dans ce cas, le robot devra être capable de demander au monde sa propre position; ce qui entraîne que la classe des robots devra être dépendante de la classe du monde. On pourra définir une classe robot prenant comme paramètre de type une classe de monde.
Cette modification permet alors de définir des robots capables d'interroger le monde qui les entoure et donc de se comporter en fonction de celui-ci. Nous pourrons réaliser des robots qui suivent ou qui fuient d'autres robots, qui tentent de les bloquer, etc. L'étape suivante est de permettre aux robots de communiquer entre eux pour s'échanger des informations et constituer ainsi des équipes de robots.
Les chapitres de la partie suivante de l'ouvrage permettent de libérer l'exécution des robots les unes des autres : soit en ayant recours aux `Threads` (voir page ??) pour que chacun s'exécute sur un processus distinct, soit en profitant des possibilités de l'informatique distribuée (voir page ??) pour que les robots soient des clients s'exécutant sur des machines distantes qui annoncent leur déplacement ou demandent des informations à un monde qui serait un serveur. Ce problème est traité à la page ??.