Sommaire Carte Index Recherche Nouvelles Archives Liens A propos
[Barre Superieure]
[Barre Inferieure]
Philipp Gühring
par Philipp Gühring

L´auteur:

Philipp vient juste de passer son bac scientifique à HTL Wiener Neustadt, une école technique supérieure en informatique. Il a maintenant du temps à consacrer à son équipe de développement appelée Futureware 2001. Il est également fan de Linux et membre actif du Linux User Group Austria

Sommaire:

Dialog - un langage de programmation de dialogue.

[Illustration]

Résumé:

Dialog est un langage de programmation spécialisé dans les dialogues avec l'utilisateur. Il a été utilisé dans le logiciel de simulation commerciale Würstelstand. Cet article décrit le développement de Dialog et ses applications.



 

Introduction

Würstelstand est un logiciel de simulation commerciale autrichienne en allemand qui propose au joueur de gérer un stand de hot-dogs, et dans lequel le contact avec les clients devient presque un jeu d'aventure. C'est précisément pour ce contact-client que j'ai développé le langage Dialog, qui répond aux besoins suivants : Lorsqu'il fallut écrire les conversations télephoniques, je réutilisai le moteur de dialog et l'étendis. Le joueur contrôle le personnage Leni, qui est le propriétaire du stand, avec un système à choix multiples. Les clients sont simulés par l'ordinateur. L'objectif du joueur est de conseiller aux clients de lui acheter quelquechose et de bavarder avec eux. Les clients apparaissent automatiquement lorsqu'ils ont faim et qu'ils ont le temps de venir. De plus, le joueur a la possibilité d'être actif, et peut appeller plusieurs numéros de son téléphone.
 

Le Langage Dialog

Les dialogues sont stockés dans des fichiers ASCII, et sont interprétés ligne par ligne. Il est également possible d'avancer à une ligne déterminée à tout moment. Ces fichiers sont simplement créés avec un éditeur de texte. Le nom de fichier est Name.BAT (i.e. : HALR.BAT). Quand le joueur (Leni) parle, cela donne quelque chose comme :
Leni: Text
Leni: Bonjour, monsieur ! 
      Que souhaitez vous aujourd'hui ?
Leni: Regardez-moi ces jeunes !
      Incroyable !
Leni a la parole
Regardez-moi ces jeunes ! Incroyable !
Etes-vous tombé du lit ?
Le client a la parole
Deux Francfort avec du pain et deux Colas.
Allons, laisses tomber, vieil homme !

Quand l'interlocuteur dit quelque chose: (Kunde signifie client en Allemand, et Telefon signifie téléphone.)

Kunde: Text
Kunde: Deux Francfort avec du pain et deux Cola.
       Allons, laisses tomber, vieil homme !
Telefon: Futureware 2001, Philipp Gühring.
         Que puis-je pour vous ?
	 

Tous les dialogues habituels se terminent par

Ende
(Ende signifie fin en Allemand)

Un exemple simple :
Leni: Bonjour! Que souhaitez-vous ce matin ?
Kunde: Salut ! Un Käsekrainer s'il-vous-plaît !
Leni: Un instant.
Leni: Et voilà.
Kunde: Merci beaucoup. Au revoir.
Leni: Au revoir !
Ende

Les étiquettes de saut sont définies par un caractère "deux points" au début d'une ligne, qui est suivi immédiatement par le nom de l'étiquette. On peut atteindre une étiquette avec la commande Sprung (Sprung signifie saut en Allemand) :

:Target
//Suit un exemple de saut :
Sprung Target

Exemple
...
Leni: 1
//Commençons par ceci
SPRUNG MENU_0
//Je reviendrai !
...
//Ces commandes ne seront pas exécutées.
Leni: 2
:MENU_0
//Je suis de retour ! 
Leni: 3
Que fait l'interpréteur dans cet exemple ? Tout d'abord, il trouve la commande Leni: et affiche le texte 1. Ensuite il y a un commentaire sur la ligne suivante (Commençons par ceci) qui sera ignoré. Après celà, il trouve la commande Sprung. L'interpréteur recherche alors l'étiquette MENU_0 dans tout le dialogue, la trouve quelques lignes plus loin, et effectue un saut à cette position. De nouveau, on trouve un commentaire (Je suis de retour !). Enfin, la dernière commande est Leni:, qui affiche 3 à l'écran. En fin de compte, la commande Leni: 2 a été laissée de coté dans l'exemple et Leni ne dit pas 2.

Comme nous l'avons vu, une ligne peut :

Les commentaires commencent par ;(point virgule),//(double slash), (espace),*(astérisque) Ils permettent de documenter et de clarifier le dialogue et sont complètement ignorés par l'interpréteur. par exemple :
// Ceci est un commentaire
**************************************************
*On peut faire aussi des commentaires comme celà.*
**************************************************
Les commentaires ne peuvent pas être sur la même ligne qu'une commande :
Leni: Je ne comprends plus rien.  // SANS COMMENTAIRE
L'interpreteur va reconnaître Je ne comprends plus rien. // SANS COMMENTAIRE comme le texte à afficher !  

Le Système de Choix Multiples

Multiple-Choice
  • Que faites-vous dans la vie ?
  • Est-ce que mon Würstelstand vous plaît ?
  • Que puis-je faire pour vous ?
Dialog propose le mécanisme suivant : Le système gère une liste, dans laquelle sont insérées les entrées correspondantes aux possibilités offertes à l'utilisateur. En temps utile, le menu est affiché à l'écran et l'utilisateur fait son choix. L'interpréteur poursuit l'exécution par la partie du programme qui gère l'entrée choisie.

Tout d'abord, les entrées sont insérées dans la liste par les commandes NEU et ALT. Ces deux commandes sont suivies de l'étiquette cible pour le saut et du texte de l'entrée. Le texte peut être très long, dans ce cas, le système insère automatiquement des sauts de lignes. Avec la commande MENÜ, toute la liste est affichée et le joueur peut alors choisir une des propositions.



Voyons maintenant les trois types de questions.  

Thèmes indépendant du contexte

Ce premier type sert pour les sujets de discussions :
Neu acheter, un bien chaud, comme d'hab, ou pas ?
Neu travail, Comment ca va au travail ?
Neu langue, Suivez vous toujours le cours de langues à WIFI ?
Neu family, Comment va la famille ?
Neu weather, Vous profitez du beau temps ?
Menü
De cette façon, le joueur a la possibilité de choisir chacune des entrées dans un ordre quelconque. Les choix peuvent être répétés. Les entrées qui ne sont pas choisies restent dans la liste et pourront être sélectionnées plus tard. Ainsi, si nous choisissons travail dans l'exemple précédent :
:travail
Leni: Comment ca va le travail ?
Client: Trop de choses à faire, comme toujours.
Menü
Comme prévu, seule la ligne choisie disparait. Dans le menu suivant, restent les sujets : Maintenant, il reste deux autres formes de choix :  

Options, réponses contextuelles

Client: Combien vous en voulez ?
Alt un lot, 10 Pièces
Alt deux lots, 20 Pièces
Alt dix lots, 100 Pièces
Menü

:un lot
//Nous continuons ici, quand l'utilisateur choisit 10 pièces.

:deux lots
...

:dix lots
...
Comme cela n'a pas de sens de conserver les entrées choisies dans la liste, toutes les entrées seront automatiquement effacées après le choix de l'utilisateur. Si par exemple celui-ci choisit deux lots, l'interpréteur saute à l'étiquette deux lots:
:deux lots
Client: Etes-vous sûr ?
Leni: Oui, je veux 20 pièces.
Client: Pour quand les voulez-vous ?
Alt 1, Demain
Alt 2, Après demain
Alt 3, Plus tard
Menü
Finalement, nous allons associer ces deux types :  

Contexte, Changement de sujet

Nous rencontrons une nouvelle subtilité des dialogues : Quand on a discuté d'un sujet avec son interlocuteur, et qu'il nous reste une remarque en tête, on peut choisir d'exprimer cette remarque ou de changer de sujet. Si l'on change de sujet, la remarque devient hors-contexte et inutile. On a donc la possibilité de continuer sur un thème ou de passer à un autre. Ceci a été implanté dans Dialog de la façon suivante : La remarque est insérée comme une option normale dans la liste, alors que celle-ci contient déjà des thèmes. Toutes les options suivant le choix de l'utilisateur seront supprimés, alors que les thèmes non choisis seront conservés.
Kunde: Rappelles-toi le bon vieux temps.
Alt Memoire, Ah oui, je viens juste de me souvenir, que ...
MENÜ
 

Implantation

Vous vous demandez sans doute maintenant comme on met en pratique ces trois différents concepts. Vous avez sans doute réalisé, à ce stade, que la distinction s'effectue par l'usage de NEU ou de ALT. Si l'on insert un sujet dans la liste avec l commande NEU, alors il restera dans la liste jusqu'à ce qu'il soit choisi. Inversement, si l'on insère une option dans la liste avec la commande ALT, elle sera automatiquement supprimée, choisie ou pas.  

Listes multiples

Que se passe-t-il par exemple, si l'on veut effectuer un choix alors que l'on discute un thème, et cela sans afficher les autres thèmes dans le menu ? Dans ce but, j'ai développé, non pas une, mais trois listes :

La liste 0 est recommandée pour les options. La liste 1 est destinée aux sujets généraux, par exemple famille, travail, loisirs, cuisine... Si l'on veut parler travail, et qu'il y a d'autres sujets à l'intérieur du sujet travail, on choisira la liste 2. Nous avons vu un exemple avec le dialogue avec Hale. Dans le cas ou un plus grand nombre de listes seraient nécéssaires, il faudra changer la constante dans le source de l'interpréteur.  

Comment utiliser les différentes listes ?

Au démarrage, la liste 1 est la liste courante. Avec la commande LISTE, on peut sélectionner une autre liste comme liste courante.
LISTE 0
LISTE 1
LISTE 2
Evidemment, les entrées de chaque liste ne sont pas modifiées par cette action. Les commandes NEU, ALT, MENÜ et LÖSCHEN s'appliquent toujours à la liste courante.  

Version précédente de Dialog

Dans la version précédente de Dialog, qui a été utilisée pour Würstelstand, le système de choix était légèrement différent : A la place de la commande ALT, nous ajoutions un paragraphe à la commande NEU après la virgule :
Neu Memoire, Ah oui, je viens juste de me souvenir, que ... 
La liste 0 était automatiquement effacée après le menu. Elle n'était donc utile que pour des options, et non pour les thèmes.

Je vous recommande maintenant de consulter les exemples HALE.BAT et PETER.BAT, dans lesquels les listes sont très largement utilisées.

LÖSCHEN  étiquette
efface toutes les entrées de la liste courante pointant vers étiquette. Par exemple:
LÖSCHEN famille
Pour effacer toutes les entrées de la liste courante, on ajoute un astérisque :
LÖSCHEN *
(Si désiré, le support des expressions régulières peut être ajouté ;-)

Menü affiche toutes les entrées de la liste courante, et permet à l'utilisateur de faire son choix parmi celles-ci. Après celà, l'entrée choisie et toutes les options ( ajoutées avec ALT) seront supprimées. Enfin l'interpréteur effectue un saut vers l'étiquette correspondante. Dans le cas où il ne reste plus qu'une seule entrée dans la liste, cette entrée est supposée être choisie et donc aucun choix n'est proposé. Si la liste est vide, ou si la cible est introuvable, l'interpréteur continuera à la ligne suivant MENÜ.  

Interfaces

Comment est ce que le dialogue peut réagir à son environnement, l'influencer et échanger des données avec d'autres dialogues?  

Memoire et Registres

Dans Würstelstand, chaque dialogue a accès à 256 registres. Chacun de ces registres contient un nombre compris entre -2 milliard et +3 milliard. Les 256 registres se répartissent en trois groupes :

Registres systèmes

Les 100 premiers registres (de 0 à à 99) sont résérvés par le système. Leur valeurs sont définies avant le démarrage d'un dialogue et lui permette de réagir à son environnement. Les registres marqués par //S seront présentés et utilisés à la fin du dialogue. Ils constituent le résultat du dialogue. Voici la liste des registres système de Würstelstand :
1Event; //Numéro d'évênement (voir texte.h)
2geliefert;//S//0-10 nombre de dixièmes fournis
3wtag; //jour de la semaine
4tag; //jour du mois
5monat; //mois
6jahr; //année
7Datum; //code du jour (1.1.1997 = 0)
8wetter; //la météo du jour
9konto;//S//compte bancaire
10kapital;//S//cash
11ausgaben;//S//dépenses du jour
12einnahmen;//S//revenus du jour
13sterne;//S// évaluation de la qualité du stand (0-5 étoiles)
14wverkauf; //Nombre de produit vendus cette semaine
15weinnahmen; //revenu de la semaine
16wausgaben; //dépense de la semaine
170;//S//nouveau revenu/dépense (conséquence du dialogue)
18Nachrichtenserie; //quelle série d'infos(0=Elch,1=...)
19Nachricht; //quelle info dans la série courante (0=1.Tag,1=2...)
20LottoNr[0]; //combien de numeros cochés dans la grille(0-6)
21LottoErgebnis[0]; //combien de numéros corrects
22LottoGewinn[LottoErgebnis[0]]; //gains de Leni
23S.Image;//S//Image de Leni
24S.Override;//S//Evênement masque
25S.wverkauf[1]; //produits vendus la semaine dernière
26S.weinnahmen[1]; //revenus de la semaine dernière
27S.wausgaben[1]; //dépenses de la semaine dernière
28S.wverkauf[2]; //produits vendus il y a deux semaine
29S.weinnahmen[2]; //revenus d'il y a deux semaine
30S.wausgaben[2]; //dépenses d'il y a deux semaines
31S.NOverride;//S//evênement masque de demain
32S.wetter_bericht; // prévision météo
33Gesamtwert(); //Valeur totale dy stand
34Wetterbericht[S.wetter_bericht].Ereignis; //Evênement météo
35Tageszeit; //Heure du jour en minutes
70..79Lagermenge //stocks
80..89Verkaufspreis//S//prix de vente
90..99Kaufmenge//S//quantité commandée

Registre de Dialogue

Les 100 registres suivants (de 100 à 199) sont propres à chaque dialogue. Ils sont initialisés à zéro au démarrage du jeu, et leur valeur est persistente pendant toute la durée du jeu. Ces registres sont accessibles seulement par leur dialogue respectifs et sont sauvés dans les sauvegardes du jeu. Le système ne peut ni lire ni écrire dans les registres de dialogue. Il est utile de documenter au début de chaque dialogue les registres qu'il utilise et leur usage.
batch.cpp

// Client: Peter Hinzing 
// 
// Registres utilisés
//[100] Combien de fois il est venu
//[101] Argent de poche
//[102] Différents évênements
//[103] Nombre aléatoire: commande
//[104] Nombre aléatore: réponse à la commande
//[105] dialogues différents : thème travail à partir du 5ème jour
//[106] Vente
//[107] Le jeu commence. Jeu après choix.
//[108] Jeu.mise.type
//[109] Jeu.mise.quantite
//[110] Jeu.choix.Peter
//[111] Jeu.choix.Leni
//[112] Activation Hobby
//[113] Activation Maison
//[114] Dialogue à propos de Würstelstand 
//[115] stock total coke
//[116] trop cher ?*************************
//* à implanter
Dans le regisre [100] Pierre se souvient combien de fois il est déjà venu. Quand il arrive la première fois, il se présente. Lors de sa dixième arrivée, il utilise le tutoiement En [101] il gère son argent de poche,...

Mémoire partagée

Les 56 derniers registres (ils pourraient être plus nombreux, le nombre exact n'est pas important) constituent une mémoire partagée entre tous les dialogues.

Tous les dialogues voient les mêmes valeurs et tous peuvent y écrire. Il devrait y avoir un système pour réguler l'usage de ces registres. Les trois registres suivants sont utilisés par des dialogues de Würstelstand (documentés dans daten.h) :

[200]: Leni peut aller au bureau de l'immigration avec Hale.
[201]: Leni lit la circulaire sur les chiens recherchés
[202]: Leni a joué à papier caillou ciseau avec Peter. (terrible !)
 

Evênements

Un systême d'évênements a été dévéloppé pour Würstelstand. chaque évênement a un numéro unique, qui doit être défini dans un fichier de référence. Les évênements peuvent correspondre à des choses comme : Comment ça marche ?
Pour les dialogues produits, clients, téléphone et les séries d'informations, il suffit d'insérer le numéro de l'évênement dans le fichier de données correspondant et la valeur de départ/fin.

Comment déclencher des évênements ?

Aktion expression
// activation des Cheats:
Aktion 3
// activating de l'évênement, qui a été calculé dans le registre 100 :
Aktion [100]
Que faire de ces évênements ? Voici un extrait de la liste d'évênements de Würstelstand :
0Erreur/JamaisL'Evênement 0 a été reçu sans pouvoir être géré.
1Initialisationest déclenché au démarrage et active de nombreux produits clients, ...
2Findevrait être déclenché à la fin du jeu.
3activation FW-CheatQui a codé ça ?!?
4désactivation FW-CheatGardez le secret !
5Leni.competition.journalL'image de Leni est suffisament bonne -> activation de l''article de journal sur la prochaine compétition
6Leni.competition.journal->No Téléphone L'article du journal active le numéro de téléphone pour s'inscrire à la compétition.
7Leni.competition.deactivation No Téléphone Après l'appel téléphonque et tout étant réglé, le numéro est désactivé.
8désactivation de HaleHale se désactive, car Leni l'a offensé.
9Hale recommande JosiHale active Josi dès qu'il en a parlé. (Smalltalk est important!)
10deactivation JosiJosi se désactive
11deactivation PeterPeter se désactive
12Info de Sepp, sans accord de LeniSepp offre une production illégale, Leni refuse, toute l'histoire devient publique.
13Info de Sepp, avec accord de LeniLeni accepte la production illégale et les problèmes suivent
14jeu perduLe postier Gottfried annonce la fin du jeu
15jeu gagnéGottfried réalise la valeur du stand de hot-dog et Leni gagne
16Hale.info article droit d'asile activationLeni parle à Hale de sa famille, ce qui suscite un article de journal sur le droit d'asile.
17Hale.info article->No téléphone activationLe journal publie le numéro qui peut maintenant être utilisé.
18Hale-> article->No téléphone desactivationLa conversation désactivates le numéro de téléphone
19Hale->Famille activationLa famille de Hale reçoit le droit d'asile.
20activation de l'espionLeni devrait se voir recommandé un détective, mais cela n'a jamais été mis en pratique..
33Nouveau produit 1 (Nouveau fournisseur)Cet évênement étend la gamme produit.
100competition gagnéeLeni remporte le concours, les clients vont maintenant en parler, ...
101compétion perdue
102Prix de LoterieLeni a gagné à la loterie
Comme on peut le voir, les évênements sont un outil très puissant pour implanter la logique du jeu.  

Calculs

Avec la commande Rechne (Rechne signifie calcule en Allemand) vous pouvez évaluer des expressions mathématiques et placer le résultat dans un registre. Par example :
Rechne [100]: 20 + [30] * 10
Le contenu du registre 30, multiplié par 10, ajouté à 20 est placé dans le registre 100.

Les opérations suivantes sont disponibles :
OpérationNotationExampleSolution
Parenthèse(a)(10+20)*30900
Registre[a][20]Le contenu du registre 20
Multiplicationa*b3*412
Divisiona/b10/52
Moduloa%b10%31
Additiona+b1+12
Soustractiona-b1-10
Accès mémoire[a]:b[10]:20Place la valeur 20 à l'emplacement 10.
Test booléena?bOui(1) ou Non(0)
Test égalitea=b10=20Non(0)
Test inférieura<b10<20Oui(1)
Test supérieura>b[10]>[20]
ETa&b1=1 & 2=2Si 1 égale 1 ET 2 égale 2
OUa|b1=1 | 2=2Si 1 égale 1 OU 2 égale 2 ist
Nombre aléatoirea Z b1 Z 6retourne un nombre aléatoire entre 1 et 6

Résultat des comparaisons en nombre : 1 pour VRAI et 0 pour FAUX. Ils peuvent être également placés dans les registres. Les espaces ont autorisés dans les expressions ( 10 + 20 ), mais pas obligatoires (10+10).

Le plus grand challenge a été le développement de l'évaluateur mathématique, qui est maintenant capable de gérer des expressions de la forme :

Hypothèse : [100]=5, [24]=14, 1Z6=2

[[100]+1]:((1Z6)*([24]>3)+10/2-10%5)
[5    +1]:((2  )*(14  >3)+10/2-10%5)
[6      ]:(2    *(1        )+5   -0   )
[6      ]:(2    *1          +5        )
[6      ]:(7                          )
[6      ]:7
Solution: [6]:7 LA valeur 7 est sauvée dans le registre 6.

 

Suivi des registres

Avec la commande Wenn (Wenn signifie si en Allemand):
Wenn condition
then
on peut faire des comparaisons, par exemple :
Wenn [100+1]>10
Client: Le nombre dans le registre 101 est supérieur à 10 !
Wenn 1>1
Client: ERREUR !
Si la condition est vraie, l'interpréteur passe à la ligne suivante, sinon il passe une ligne. Cette instruction peut être utilisée en association avec des sauts:
Wenn [102]<10
Sprung PLUSPETIT
Wenn [102]=10
Sprung EGAL
Wenn [102]>10
Sprung PLUSGRAND
...
:PLUSPETIT
...
:EGAL
...
:PLUSGRAND
 

Visualiser des images

BILD expression
(Bild signifie image en allemand) Si par example
Bild 5
est contenu dans HALE.BAT, alors HALE5.DAT (un format d'image spécial) est affiché. Un click souris est attendu puis le dialogue se poursuit.  

Référence des commandes

Pour avoir une meilleure vue d'ensemble, j'ai réalisé un index des commandes :
// commentaireCOMMAND REFERENCE

Client: textLe client dit quelquechose
Tel: textL'interlocuteur dit quelquechose.
Leni: textLeni dit quelquechose
:targetEtiquette à laquelle l'interpréteur peut sauter
Liste numberFaire d'une liste la liste courante.
Löschen *Effacer toutes les entrées de la liste courante.
Löschen targetEffacer toutes les entrées de la liste courante qui pointent vers target.
Aktion numberDéclenche un évênement
EndeFin du dialogue
Bild numberAffiche l'image dans le fichier NameNumber.dat
Sprung targetEffectue un saut vers target
Neu target,TextInsère un nouveau sujet dans la liste courante
Alt target,TextInsère un nouveau choix dans la liste courante
MenüAffiche le menu, fait choisir le joueur, ...
Wenn conditionComparaison (voir plus loin)
//thenSi vraie, l'interpréteur exécute la ligne suivante.
//elseSi fausse, l'interpréteur passe la ligne suivante.
Rechne expressionEvalue l'expressions dans un registre
Bild expressionAffiche une image, attends un click souris
 

Désavantages du système à choix multiples.

 

Dialog Maker

Markus Muntaneau a développé un programme Delphi appelé Dialog-Maker, avec lequel le développement de dialogue devrait être simplifié. Malheureusement il n'a jamais été terminé (Il reste des bugs) et de ce fait n'est pas très utile. Je recommande cependant aux développeurs d'y jeter un coup d'oeil.

 

Astuces de programmation

Comme l'ensemble du projet Würstelstand comprend à peu près 10 000 lignes de code C(++), et que les temps de compilations étaient encore acceptables, je n'ai pas modularisé proprement le code (ok, j'admet que j'ai été paresseux). A la place, j'ai développé le système Test-Include : Le code du module est intégré dans un fichier .c, qui est exécutable séparément, et qui offre en même temps un programme de test pour les routines des modules. Ceci peut être désactivé par un #ifdef s'il est inclus à partir du module principal. Ainsi, on peut économiser des fichiers d'en-tête. ;-)

batch.cpp
#ifndef _DIALOG_H 
#define _DIALOG_H 
 
#ifndef MAIN_MODULE
  #define DIALOG_TEXT 
  #define DEBUG 
  //Ici sont les fichiers include nécessaires
  #include <stdio.h>
  //... 
#endif 
 
//Ici sont les routines du dialogue
//..
S2 Dialog(char *Filename, TYP Array[])
{
  //...
}

#ifndef MAIN_MODULE
 //Ici tout dont a besoin le programme de test
TYP Feld[256]; 
int main(short argc,char *argv[]) 
{ 
  // Programme de test
  Dialog(Filename,Feld);
} 
#endif
wurst.cpp
#define MAIN_MODULE
#include "batch.cpp"
TYP Felder[10][256];
int main(short argc,char *argv[]) 
{ 
  Dialog(Filename,Felder[i]);
}

 

Remarques finales

La version Linux de la simulation Würstelstand est disponible sur Futureware (http://poboxes.com/futureware). La version 1.1 de dialog peut être téléchargée ici (dialog-1.1.tar.gz).
En cas d'importante demande (envoyer vos emails à l'auteur), d'autres articles sur des examples pratiques de dialogues suivront.
Site Web maintenu par l´équipe d´édition LinuxFocus
© Philipp Gühring
LinuxFocus 1999
Translation information:
de -> -- Philipp Gühring
de -> fr Cyril Hansen

1999-11-22, generated by lfparser version 0.7

mirror server hosted at Truenetwork, Russian Federation.