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).


Principes de fonctionnement

Cahier des charges

Développement du serveur

Développement du client

Conclusion

Archive contenant les fichiers client.r et serveur.r


Principes de fonctionnement

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 :

Cahier des charges

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.

 

Développement du serveur

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


Développement du client

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

Conclusion

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

olivier.auverlot@free.fr

Retour