Programmez des jeux réseau
Rebol/View dispose de capacités graphiques et sonores suffisantes pour réaliser des jeux vidéos. Rebol étant également un langage spécialisé dans le domaine de l'échange d'informations, il constitue un formidable outils pour l'écriture de jeux en réseau. Le support natif des protocoles TCP et UDP permet de transférer simplement des données entre des postes clients et un serveur central. Que ce soit dans un Intranet ou sur Internet, vous pouvez concevoir des jeux où des dizaines de participants s'affrontent simultanément.
Dans cet article, nous allons mettre en place les éléments permettant la réalisation d'un jeu d'aventure ou d'un jeu de réflexion. Nous allons écrire un serveur et un client, utilisant le protocole HTTP pour communiquer. Cette solution permet aux clients de traverser aisément n'importe quel firewall (un logiciel destiné à protéger un domaine des intrusions extérieures). Ces scripts sont donc parfaitement utilisables quelque soit le type de réseau que vous utilisez (Intranet et Internet).
Archive contenant les fichiers client.r et serveur.r
La programmation d'un jeu en réseau nécessite la répartition des traitements entre le serveur et le client. Le serveur a en général la charge de la retransmission des données provenant d'un client vers les autres clients (si par exemple, le joueur se déplace, il faut avertir les autres joueurs d'un changement d'état). Le serveur est également l'arbitre et le maitre du jeu. Il peut avoir la capacité de déclencher un événement sur l'un des clients et de décider de la victoire ou de la défaite de l'un des participants. De son coté, le client a la charge de la gestion de l'affichage, de l'interactivité avec le joueur mais aussi de certains traitements (animation, événements, ...).
Le principal problème de ce type de programme scindé en deux entités est de déterminer comment les parties vont communiquer. Supposons qu'un joueur déplace son personnage, par quel moyen les autres joueurs vont ils en être avertis ? Il y a plusieurs scénarios possibles :
Les scripts clients affichent chacun des utilisateurs connectés sous la forme d'un cercle dont la couleur a été choisie aléatoirement par le serveur. Chaque client peut déplacer son cercle et ses mouvements sont répercutés sur les écrans des autres joueurs. Lorsqu'un client se connecte ou se déconnecte, cette action est visible pour les autres utilisateurs.
Les clients et le serveur utilisent le port 80/TCP et le protocole HTTP pour communiquer. Le client effectue à intervalle régulier une requête HTTP vers le serveur pour l'informer de sa position. En retour, le serveur lui fourni la position des autres participants afin qu'il puisse mettre à jour son affichage. Lorsqu'un client se connecte pour la première fois, il reçoit un identifiant numérique unique qui permettra au serveur de déterminer le client avec lequel il communique. Lorsqu'un client "ferme" sa connexion, l'identifiant permet au serveur de déterminer les informations devenue inutiles. Pour chaque client, le serveur stocke son identifiant, la couleur et la position sur l'écran.
Les différentes actions du client sont transmises au serveur à l'aide de l'URL de la requête HTTP. Les types d'action sont définies sous la forme d'instructions qui finalement, constituent un dialecte pour Rebol :
| Instruction | Paramêtre | signification |
| GET-ID | - | Le client se connecte pour la première fois et demande au serveur un identifiant. |
| ID | integer! | Le client indique son identifiant au serveur. |
| SET-POSITION | pair! | Le client fourni la position de l'utilisateur. |
| EXIT | - | Le client prévient le serveur que l'utilisateur se déconnecte. |
Notre serveur est en fait un mini serveur HTTP. Il écoute sur le port 80/tcp et traite les requêtes des clients. La gestion des sessions est ici réalisée à l'aide de l'identifiant unique déterminé par le serveur et fourni dans chaque requête par les clients (à l'exeption bien sur de GET-ID). Le serveur fonctionne dans une boucle infinie (FOREVER) et stocke la requête d'un client dans la variable BUFFER.
listen-port: open/lines tcp://:80cmd: copy ""forever [ ; attente et lecture d'une requête HTTP ; ------------------------------------- http-port: first wait listen-port buffer: copy "" while [not empty? request: first http-port][ repend buffer [request newline] ]
La requête est ensuite découpée pour en extraire les instructions et chaque commande est ajoutée à un bloc nommé CODE. C'est ce bloc qui va être interprété à l'aide d'un dialecte :
parse buffer [ thru "GET /" copy cmd to "HTTP" ] cmd: parse cmd "/=" code: copy [] foreach ele cmd [ append code (to-word ele) ] reponse: copy [] id: none parse code diacom-rules
Le dialecte se nomme DIACOM et est défini à l'aide des règles suivantes :
diacom-rules: [ any [ 'GET-ID ( current-id: current-id + 1 couleur: random 255.255.255 append utilisateurs make object! [ id: current-id position: none color: couleur ] append reponse reduce [ current-id couleur ] aff-nbr-utilisateurs ) | 'ID set id word! ( ; on doit convertir un word! en un integer! cle-id: (make integer! make string! id) ) | 'SET-POSITION set pos word! ( utilisateurs: head utilisateurs forall utilisateurs [ ptr: first utilisateurs ; on envoie les positions des autres joueurs either ptr/id = cle-id [ ptr/position: make pair! make string! pos ] [ append reponse ptr ] ] ) | 'EXIT ( utilisateurs: head utilisateurs forall utilisateurs [ ptr: first utilisateurs if ptr/id = cle-id [ remove utilisateurs ] ] aff-nbr-utilisateurs ) ]]
Une fois l'interprétation terminé, le serveur doit générer et envoyer la réponse destiné au client. Il ne lui reste plus qu'à fermer le port TCP utilisé :
data: make string! mold reponse ; Envoi au client ; --------------- insert http-port rejoin ["HTTP/1.0 200 OK^/Content-type: text/plain^/"] write-io http-port data (length? data) close http-port
Pour surveiller le trafic sur le serveur, vous pouvez ajouter une fenêtre affichant le nombre d'utilisateur connectés :
view/title/new layout/size [ backdrop 0.0.255 across at 10x10 text white bold "Nombre de connectés:" nbr: text white 50 "0"] 230x40 "Serveur"aff-nbr-utilisateurs: does [ nbr/text: length? (head utilisateurs) show nbr]

Le serveur affiche le nombre de clients connectés.
Il vous faut également gérer la fermeture de la fenêtre du serveur afin que le script puisse se terminer normalement. Le port d'écoute LISTEN-PORT doit être libéré :
evt-close: func [ f event ] [ either event/type = 'close [ close listen-port quit ] [ event ]]insert-event-func :evt-close
Le script du client commence par la déclaration des variables et par la gestion de la fermeture de sa fenêtre. La variable SERVEUR contient l'adresse IP de la machine hébergeant le code serveur. Le contenu de SET-NET doit être adapté à votre réseau. Si vous communiquez avec un serveur sur Internet, il vous faut probablement déclarer un proxy. La propriété SYSTEM/SCHEMES/DEFAULT/TIMEOUT permet de fixer à 1 seconde le délai d'attente maximum autorisé pour la réponse du serveur. La variable DELAI signifie que le client communiquera avec le serveur à chaque fois qu'une seconde se sera écoulée :
serveur: 172.29.143.128system/schemes/default/timeout: 1delai: 0:0:1set-net [ none none none none none ]id: nonepos-joueur: 100x100autre-joueurs: copy []evt-close: func [ f event ] [ either event/type = 'close [ if error? try [ read to-url join "http://" [ serveur "/ID=" id "/EXIT" ] ] [ ] ] [ event ]]insert-event-func :evt-close
Le client peut maintenant demander au serveur son identifiant. Il débute ici sa connexion. Vous devez générer une requête ayant pour syntaxe READ HTTP://172.29.143.128/GET-ID. Le serveur lui renvoi alors son ID et sa couleur. Vous pouvez ensuite envoyer votre position sur l'écran et récupérer ainsi celle des autres utilisateurs connectés. L'URL a la forme suivante: HTTP://172.29.143.128/ID=1/SET-POSITION=100x100. La gestion des erreurs permet de détecter les éventuels problèmes de connexion du client au serveur :
if error? try [ requete: to-url join "http://" [ serveur "/GET-ID" ] data: do read requete id: first data couleur: second data ; on récupère les positions autres joueurs requete: to-url join "http://" [ serveur "/ID=" id "/SET-POSITION=" pos-joueur ] reponse: reduce do read requete] [ alert "ERREUR: connexion impossible au serveur" quit]
La plus grande partie du code est contenue dans un LAYOUT nommé ECRAN. Un timer est fixé à 0.1 seconde. Lorsque l'événement 'TIME est détecté, l'écran est redessiné. Les cercles symbolisant les différents utilisateurs sont réaffichés. La variable DELAI est décrémentée de 0:0:1 et si celle ci atteint la valeur 0:0:0, le client envoi au serveur une requête SET-POSITION qui permet de raffraichir les différentes positions et de savoir si une nouvelle connexion a été initialisée ou si une déconnexion à eu lieu. Les boutons en bas de l'écran permettent de déplacer le cercle de l'utilisateur sur l'écran en modifiant la variable POS-JOUEUR.
Chaque cercle représente un client connecté.
view layout/size [ at 0x0 ecran: box 400x400 0.0.0 rate 0:0:0.1 feel [ engage: func [ face action event ] [ if action = 'time [ ecran/effect/draw: copy [] append ecran/effect/draw reduce [ 'pen couleur 'circle pos-joueur 20 ] reponse: head reponse forall reponse [ ptr: first reponse append ecran/effect/draw reduce [ 'pen ptr/color 'circle ptr/position 20 ] ] show ecran delai: delai - 0.1 if delai = 0:0:0 [ requete: to-url join "http://" [ serveur "/ID=" id "/SET-POSITION=" pos-joueur ] if error? try [ reponse: reduce do read requete ] [] delai: 0:0:1 ] ] ] ] effect [ draw [] ] at 0x400 panel 400x50 [ across at 10x5 button 60 "Up" [ pos-joueur/y: pos-joueur/y - 5 ] button 60 "Down" [ pos-joueur/y: pos-joueur/y + 5 ] button 60 "Left" [ pos-joueur/x: pos-joueur/x - 5] button 60 "Right" [ pos-joueur/x: pos-joueur/x + 5 ] button 60 "Exit" [ if error? try [ read to-url join "http://" [ serveur "/ID=" id "/EXIT" ] ] [ ] quit ] ]] 400x450
Pour ceux d'entre-vous qui veulent tester ces deux exemples de client et de serveur, vous pouvez télécharger l'archive contenant ces deux scripts. Vous disposez maintenant de tous les éléments nécessaires à l'écriture d'un jeu en réseau avec Rebol. Le serveur est assez générique et il vous suffira d'enrichir le dialecte pour lui ajouter les fonctionnalités nécessaires à un véritable jeu.
Olivier Auverlot