1. Introduction

Le jeu vidéo est un média conséquent, le nombres de jeux disponibles est important, il y en à de tous types, sur tous supports. Cependant, parmis la masse, le nom de "Mario Kart" est connu d'un grand nombre. Il s'agit d'un jeu de course où chacun contrôle un kart et tente de finir premier, en bastonnant ces adversaires (et amis) au passage à coup de carapaces et peaux de bananes.
Ce projet vise à recréer une version simplifiée de ce jeu, dans la réalité, à l'aide de robots Thymio-II, produits par l'EPFL. Chaque joueur possède deux Thymios, l'un étant une voiture, ou un kart, sur la piste, et l'autre se tenant entre les mains et faisant office de manette. Les contrôles se font à l'aide des capteurs de proximité de la manette, l'un pour avancer, l'autre pour reculer. Pour tourner, il suffit de l'incliner comme un volant, tout en avançant ou en reculant.

2. Matériel et méthodes

2.1 Matériel

• 4 Thymio-II (Attention ce projet utilise la communication wireless, les Thymios de première génération de fonctionnent donc pas).
• 1 Dongle (Fournis avec les Thymio-II).
• Le logiciel Aseba (Disponible ici).
• Une piste composée d'une surface reflétant les infrarouges et de murs quelconques (En carton par exemple).
• Une bande absorbant bien les infrarouges.
• Une bande absorbant moyennement les infrarouges.
• Pour utiliser le script python distribuant le code il faut "Aseba Studio" sur une distribution Linux.

2.2 Méthode

Le projet se déroule en plusieurs étapes, tout d'abord il faut imaginer un système d'état afin de savoir à quel stade de la partie on se trouve. Puis il faut implémenter un système de communication entre les Thymios pour pouvoir contrôler un kart à distance à l'aide d'un autre Thymio. Ensuite il faut concevoir sur les karts un système pour compter les tours, lorsque ceux-ci passe sur la ligne de départ/arrivée. Il a fallu ajouter un système de son et lumière pour indiquer aux joueurs certaines informations utiles au déroulement de la partie. Pour finir il y a eu tentative d'implémenter un script capable de scanner le réseau de Thymios et de distribuer automatiquement le code aux karts et aux manettes.

2.2.1 Les différents états

Tout d'abord, on subdivise la partie en 2 états pour les karts et 3 états pour les manettes. La valeur de l'état actuel est conservée dans une variable appelée "mode", dans chaque Thymio. Pour les kart si mode == 1, alors le Thymio peut recevoir des communications et se déplacer, si mode == 2, alors là, la course est finie, le kart s'arrête et refuse d'être contrôlé. Pour les manettes il s'agit essentiellement de la même chose, à part que si mode == 1, alors les manettes n'envoient aucune instructions, car cela signifie que la course n'a pas commencé, qu'il faut attendre le signal de départ. Dans cette état, on ne peut qu'appuyer sur le bouton centrale de la manette, pour lancer un compte à rebours de départ, qui lance la course une fois au bout. Si mode == 2, la course est en cours et les communications peuvent se faire, et si mode == 3, alors la course est finie et l'on ne peut que redémarrer en appuyant sur le bouton centrale. Ces différents états permettent d'avoir une idée de la partie en cours, afin d'éviter de faire bouger un kart avant que le départ ne soit donné, par exemple.

2.2.2 Les communications

Avant toutes choses il faut connecter les différents Thymios sur un même réseau. Pour ce faire il suffit de suivre les instructions disponibles ici, chaque Thymio sur un réseau se fait appeler nœud. Ensuite dans "Aseba Studio", quatre fenêtres s'ouvrent pour programmer les quatre Thymios séparément. Ces derniers fonctionnent en programmation événementielle, c'est à dire que le robot exécute des portions de code lorsque certains événements sont déclenchés, par exemple lorsqu'un de ces capteurs détecte quelque chose. Pour la communication inter-Thymio le principe est similaire, les robots peuvent envoyés des événements globaux qui seront reçus par les autres robots qui exécutent ainsi la portion de code correspondant à cet événement. Les événements globaux peuvent aussi envoyer des valeurs sous forme d'arguments. Il faut d'abord ajouter ces événements dans Aseba Studio, dans l'onglet "Événements globaux", il faut aussi à ce moment préciser le nombre d'arguments que prend l'événement.
On peut aussi éditer le fichier .aesl directemen, en modifiant les lignes sous la balise suivante :

<!--list of global events-->

Pour ajouter un événements "move" contenant trois arguments, il suffit de rajouter ceci :

<event size="3" name="move"/>

La première communication vise à contrôler le kart à l'aide de la manette. Elle doit émettre un événement "move" qui enverra la valeur des différents boutons, c'est à dire les deux capteurs de proximité pour avancer et reculer, et la valeur de l’accéléromètre, sur le premier axe. Les arguments doivent être stockés dans un tableau, ici appelé commands.

onevent prox
	if mode == 2 then	
		#Enregistre la valeur des gachettes
		call math.max(commands[0],THRESHOLD,prox.horizontal[3])
		
		call math.max(commands[1],THRESHOLD,prox.horizontal[1])
		
		#Enregistre la valeur de l accelerometre
		commands[2] = acc[0]
		
		#Si les deux gachettes sont enclanchées -> envoie zero pour les deux
		if commands[0] > THRESHOLD and commands[1] > THRESHOLD then
			commands[0] = THRESHOLD
			commands[1] = THRESHOLD
		end
		
		#Recentre les valeurs
		commands[0] = commands[0] - THRESHOLD
		commands[1] = commands[1] - THRESHOLD
		
		#Les instructions sont envoyées uniquement si le Thymio est tenu entre les mains à la verticale
		if  acc[2] &lt;= INHANDTHRESHHOLD then
			emit move commands
		#Si le controller n'est pas tenu, le Thymio s'arrete
		else
			emit move [0,0,0]
		end
	end

On lance l'instruction depuis l'événement prox car on peut faire bouger le Thymio uniquement si une des deux "gâchettes" (capteurs de proximité) est enclenchées. Il faut aussi vérifier qu'elles ne sont pas enclenchées en même temps, sinon le kart n'avance pas. C'est aussi ici que l'on vérifie que la manette est tenue entre les mains, car si on la laisse sur la table, il est facile de déclencher les gâchettes sans faire exprès, et créer des mouvements involontaires. pour ceci on vérifie juste grâce à l'accéléromètre que la manette est à la verticale. Il faut aussi dépasser un certain seuil pour pouvoir déclencher les gâchettes, sinon elles seraient activées trop facilement.
Le Thymio receveur peut utiliser ces valeurs avec l'instruction suivante :

onevent move
	if event.source == idManette and mode == 1 then
		#Gérer les gachettes
		if event.args[0] > 0  then
			direction = 1
		elseif event.args[1] > 0 then
			direction = -1
		else
			direction = 0
		end
	
		#Gere l accelerometre pour les virages
		if event.args\[2] > SENSIBILITE or event.args\[2] < -SENSIBILITE  then
			call math.min(motor.right.target,MAXSPEED,NORMALSPEED+ COEFF*event.args[2])
			call math.max(motor.left.target,-MAXSPEED,NORMALSPEED- COEFF*event.args[2])
		#Et ici pour le tout droit
		else
			motor.right.target = NORMALSPEED
			motor.left.target = NORMALSPEED
		end
		
		#Applique la bonne direction
		motor.right.target = motor.right.target * direction
		motor.left.target = motor.left.target * direction
		
	end


On vérifie tout d’abord l'état mais aussi l'id de l'expéditeur. Chaque Thymio à un identifiant qui est visible si on affiche les variables cachées dans "Aseba Studio". Lorsqu'un événements est émis, il vient avec un arguments supplémentaire contenant l'id de l'émetteur, accessible avec la variable envent.source. On compare cet id avec une variable "idManette" ou l'on a inscrit au préalable l'id de la manette pour ce kart. Il en va de même pour la manette qui possède une variable "idKart". Grâce à ce système les communications ne se mélangent pas.
Il s'agit ici des principales communications, il y a aussi 4 autres événements globaux : start, restart, finish et color. Les trois premiers permettent de synchroniser les états entre les Thymios tandis que le dernier permet à un kart d'envoyer sa couleur à sa manette afin que les joueurs reconnaissent directement leur kart, mais ceci sera abordé dans le point 2.2.4.

2.2.3 Détection de la ligne de départ/arrivée

Pour la ligne de départ, on aurait pu mettre une simple bande noire, les karts auraient incrémenté une variable "laps" à chaque fois qu'ils passaient sur cette ligne. Cependant dans cette situation il est aisé de tricher, il suffit de passer cette ligne dans un sens puis dans l'autre, en boucle pour arriver très vite à 4 tours. Il faut donc imaginer un système qui comprend si la ligne est passée dans un sens ou dans l'autre. Pour ceci j'ai utilisé deux lignes absorbant les infrarouges de façon différentes, espacées par du blanc, comme le montre ce schéma :
Shema_Ligne.png
Figure 2.1. Schéma de la ligne de départ/arrivée
Les matériaux utilisés donnent, pour la valeur prox.reflected du Thymio, environ 950 pour le blanc, environ 500 pour le gris, et environ 150 pour le noir. Ce n'est pas tant la couleur qui fait la différence, mais surtout le matériaux, cependant, par soucis de simplicité les termes de lignes noires, pour la ligne absorbant, et ligne grise, pour la ligne absorbant moyennement, seront employés. Dans les faits, les deux lignes sont noires. mais l'une est en plastique, l'autre est en carton. Le code, présent dans les deux kart, est le suivant :

#Verifie quelle lignes il a traversées
		if black == 1 and wasOnGrey == 1 then
			laps = laps - 1	
			callsub resetLapSystem
		elseif black == 1 and wasOnBlack == 0 then
			wasOnBlack = 1
			black = 0
		elseif black == 1 then
			wasOnBlack = 0
			black = 0
		end
		
		if grey == 1 and wasOnBlack == 1 then
			laps = laps + 1	
			callsub resetLapSystem
		elseif grey == 1 and wasOnGrey == 0 then
			wasOnGrey = 1
			grey = 0
		elseif grey == 1 then
			wasOnGrey = 0
			grey = 0
		
		end
					
	end
	
	#S'il vois du gris
	if prox.ground.reflected[1] < WHITE and prox.ground.reflected[1] > GREY  then
		grey =  1
		
		#Si c'est une erreur du capteur, car il vient du noir, remettre grey à 0
		if black == 1 then
			grey = 0
		end
		
	end
	
	#S'il voit du noir
	if prox.ground.reflected[1] < GREY then
		black = 1
		grey = 0
		
	end


Ce code résout un problème particulier. Lorsque le Thymio passe par la ligne noire, la transition ne se fait pas toujours net. Dans certain cas le Thymio lit une valeur proche de 500 pour le prox.reflected, ce qui correspond au gris. Le code pour la détection du gris se met en marche, alors que le Thymio n'est pas passé dessus. Pour contrer ceci, le Thymio réinitialise la variable "grey" lorsqu'il rentre sur du noir, et il la laisse à 0 s'il sort du noir, car l'erreur peut intervenir au moment d'entrer ou de sortir de la ligne noire.

2.2.4 Indication Son/Lumière

Pour pouvoir fournir un retour de la partie au joueur, il a fallu intégrer un système d'indications sonores et visuelles. Premièrement, il est utile de reconnaitre quel kart est contrôlé par quelle manette. Pour ceci on peut utiliser les LEDs rgb du Thymio, de sorte qu'une paire de Thymio partage la même couleur. Pour ceci j'ai coder un générateur aléatoire de couleur, à l'aide de la fonction suivante :

call math.rand(red)
call math.rand(green)
call math.rand(blue)
red = red / 1024
green = green / 1024
blue = blue / 1024

#Change les valeurs negative en positive
if blue &lt; 0 then
	blue = blue * -1
end
if red < 0 then
	red = red * -1
end
if green &lt; 0 then
	green = green * -1
end

Les trois variables red, green, blue sont ensuite envoyées par l'événement global color à la manette pour qu'elle arbore les même couleurs.
Le problème est que la fonction math.rand n'est pas modulable, on ne peut pas lui préciser l'intervalle dans le quel prendre un nombre pseudo-aléatoire, comme en python, par exemple, avec random.randint(a,b). Ici le nombre retourné se trouve dans l'intervalle [-32268 ; 32767], or on ne peut que passer des valeurs entre 0 et 32 pour les LEDs. C'est pourquoi j'ai divisé cette valeur par 1024, car 32268/1024 = 32, et que j'ai forcé les nombres à être positif. Il aurait été judicieux d'utiliser la valeur absolue des nombres mais cette fonction n'est pas présente dans la librairie mathématique du Thymio, d'où l'utilisation des trois blocs "if".
Pour le nombre de tours, j'ai simplement affiché la variable "laps", grâce aux LEDs en cercle, chaque quart de cercle représentant un tour.
Pour le départ, il a fallu mettre un décompte, inspiré de celui présent dans le jeu original. Il suffit d'utiliser le timer du Thymio avec le code suivant, dans le Thymio-manette :

onevent timer0
	if countdown < 3 then
		call sound.freq(300,40)
		countdown = countdown + 1
	elseif countdown == 3 then
		call sound.freq(900,40) 
		emit start
		mode = 2
		timer.period[0] = 0
		countdown = 0
	end

La variable "countdown" est initialisée à 0. Le timer est appelé toutes les secondes, du moment où l'on appuie sur le bouton centrale. Les trois premières fois, un son grave est joué, et la quatrième fois, un son plus aigu. De plus on remet "countdown" à 0, on arrête le timer et on change les variables "mode" des Thymios en émettant l'événement "start". L'aspect visuel donne au final ceci :

PhotosManetteKart.png
Figure2.2. Configuration initiale d'une partie

Les Thymios sont synchronisés au niveau de la couleur, de plus les manettes arborent un certain motif au niveau des LEDs circulaires, pour qu'on sache ce qui est un kart et ce qui est une manette.

2.2.5 Automatiser la distribution du code

Le code est déjà fonctionnel, cependant, il n'est compatible qu'avec les 4 Thymios utilisés pendant le développement. Si on regarde le fichier .aesl (format de fichier Aseba) on se rend compte que les codes sont entourés des balises suivantes :

<!--node thymio-II-->
<node nodeId="6952" name="thymio-II">

</network>


La variable "nodeId" est égal à l'Id d'un Thymio du réseau formé au point 2.2.2, si d'autres Thymios sont utilisés, il faut changer ces valeurs et les variables "idKart" et "idManette" des karts et des manettes. Ce travail est fastidieux et le but a été de l'automatiser. La première solution est d'utiliser asebamedulla, un outil permettant d'accéder aux Thymios, à travers D-Bus, utilisable uniquement depuis une distribution Linux. On peut l'utiliser depuis un script Python, nous permettant d'en écrire un, que voici :

#!/usr/bin/python

import dbus
import dbus.mainloop.glib
import gobject
import sys

#Se connecte au network des Thymios
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
bus = dbus.SessionBus()
network = dbus.Interface(bus.get_object('ch.epfl.mobots.Aseba', '/'), dbus_interface='ch.epfl.mobots.AsebaNetwork')
#Affiche les noeuds Thymio
dbus_array = network.GetNodesList()
size = len(dbus_array)
print([str(dbus_array[x]) for x in range(0,size)])
#Charge le script en argument
network.LoadScripts(sys.argv\[1])


La documentation étant faible, le code est inspiré de ce qui peut se trouver dans celui-ci. Les premières lignes servent à se connecter aux Thymios, mais il faut pour ceci lancer la commande suivante depuis un autre terminal :

asebamedulla "ser:name=Thymio-II"


La suite du code permet d'afficher les noeuds Thymio du réseau, et d'envoyer le code fournit en argument, aux Thymios. En executant la commande dans un terminal on remarque qu'il ne détecte qu'un noeud, correspondant à tous les Thymios, ainsi lorsque l'on exécute network.LoadScripts(sys.argv1), tous les Thymios chargent le même code. On peut envoyer du code de la manière suivante :

./CodeSenderForThymio.py 'Code A Envoyer.aesl'

Le chemin du code à envoyer doit être absolu. En envoyant le code "ThymioKart.aesl", donc celui contenant les mêmes id que ceux des Thymios connectés, tout se passe normalement. Chaque Thymio reçoit le même code, mais chacun prend la partie le concernant. La deuxième étape est de charger un script ne contenant pas les bons id, comme "ThymioKartDistribuable.aesl", qui utilises les Ids 1, 2, 3 et 4. Dans ce cas, seul un Thymio charge le code, et il le prend dans sa globalité, c'est à dire que pendant une seconde, il lit tous le fichier et alterne entre le code manette et le code kart, avant de s’arrêter sur le dernier, cette méthode ne fonctionne pas.

3. Résultats

Les résultats sont visibles sur cette vidéo : Démonstration des contrôles
Les résultats au niveau de la communication sont bons, tout d'abord les contrôles répondent rapidement, le jeu se prend en main facilement. Le problème se trouve au niveau de l'envoie du code; en passant par "Aseba Studio" ou le script python, assez souvent, un Thymio, ou plus, ne reçoit pas le code et reste en attente. Il faut donc lancer la commande plusieurs fois jusqu'à ce que tous les robots aient reçu le code, ensuite tout se déroule comme prévu. Il y a aussi de temps en temps de la latence, elle peut dépendre des Thymios, de la distance au Dongle, elle semble quelque peu aléatoire, et n'a pas tant affecté les parties.
La détection de la ligne de départ est plus problématique. La détection ne se fait pas toujours, et si un kart ne détecte pas une des bandes, il est complétement déréglé. S'il ne détecte pas la première bande, il pensera, en passant la deuxième, qu'il commence à traverser la ligne à contre-sens, ce qui aboutit à perdre des tours plutôt que d'en gagner. Aucune partie ne s'est déroulée sans qu'un des deux kart ai eu un problème à ce niveau.
L'envoie automatique du code ne fonctionne pas, le code n'est envoyé qu'a un seul Thymio, qui l'éxecute dans sa totalité. Ce que fait asebamedulla est de chercher les Thymios non par leur Id mais par leur nom, comme indiqué ici, sauf que tous les Thymios possède le même nom.

4. Discussion

Pour faire fonctionner la distribution automatique du code, on peut utiliser asebaswitch, qui permet de remapper les Id des Thymios, sauf que cet outil ne semble pas être accessible depuis Python, rendant la tâche complexe. Si la liste des id des Thymios étaient accessible depuis un script, (ce qui est peut-être possible avec asebaswitch), on pourrait générer un fichier .aesl temporaire, en utilisant le module tempfile en Python par exemple, correspondant aux Thymios du réseau en question. Une autre amélioration possible serait de penser un code qui fonctionne pour un nombre indéterminé de Thymio. Cela fonctionne déjà, il faut juste copier coller le code de la manette et le code du kart autant de fois qu'on veut, mais comme avant, il serait préférable que cette étape soit automatisée. Si à nouveau on arrive à accéder à la liste des Ids des Thymios, on obtient du même coup le nombre de Thymios, et il suffit de générer un fichier temporaire dont la taille dépend de ce nombre.
La détection de la ligne de départ/arrivée est très aléatoire, il est possible que les bandes soient trop fine pour être bien détectées, ou que les ombres sur la piste provoque des erreurs. On devrait aussi modifier les valeurs des constantes GREY et WHITE, qui indiquent le niveau de réflexion des matériaux, peut-être que celles-ci ne conviennent pas à la réalité. En attendant, une fonction forçant le redémarrage de la partie aurait été judicieux, vu le nombre de fois où celle-ci se bloque. On ne peut que redémarrer si la course est finie.
Les Thymios sont de fabrications un peu bon marché, de ce fait certain se déplace plus rapidement que les autres, l'équilibrage du jeu en est affecté.

5. Conclusion

Voilà ce travail achevé. Malgré les soucis présent, tant pour le faire fonctionner avec des Thymios inconnu, que pour réussir à finir une partie, le résultat est amusant. Les parties sont dynamiques, mais finissent assez souvent sur un combat de Thymio télécommandé plus que sur une course de kart. Ce n'est pas un travail visant à changer notre société, son but est juste de passer un bon moment, ce qui est atteint si vous êtes un minimum joueur.

Références