PytactX – Créez vos propres règles du jeu 🎲

Vous avez aimé PytactX et vous aimeriez créer votre propre arène avec vos propres règles du jeu ? Ce tutoriel est fait pour vous !

⚙️ Prérequis

🔍 Information
Pour aller plus vite ou si vous êtes bloqué, n’hésitez pas à nous contacter ici si vous souhaitez des cours particuliers en présentiel ou distanciel 😉

👮 Tout est Agent

Dans PytactX, vous incarnez un agent (un robot virtuel) dans une arène, qui exécute à la lettre les requêtes que vous lui avez dictées dans votre script Python.

Dans le premier tutoriel, vous avez pris en main les variables et les fonctions de cet agent pour le faire évoluer dans une arène.

Grâce à la fonction agent.actualiser(), votre script envoyait les requêtes précédemment formulées (par exemple agent.deplacer(5,5) pour se déplacer vers le milieu de l’arène) au serveur de jeu. Ce dernier vérifiait la validité de la requête (est-ce que vous êtes encore en vie, est-ce qu’il n’y a personne sur votre chemin) et si tout était valide, le serveur vous renvoyait ensuite le nouvel état de votre agent, que vous pouviez interroger après l’appel de la fonction agent.actualiser() grâce à ses variables d’état (agent.vie, agent.x, agent.y …).

Pour créer votre propre jeu, nous allons faire de même en développant et en exécutant un script d’un agent un peu spécial, qu’on nommera arbitre, qui sera chargé de surveiller l’état du jeu (grâce à ses variables arbitre.jeu et arbitre.voisins) et de modifier l’état du jeu et de l’arène grâce à sa fonction agent.changerRegle.

Pour donner à votre agent arbitre les supers pouvoirs de Néo (comme le droit de modifier l’arène, de se téléporter, d’être invisible des autres agents et invincible), il faudra à sa création lui donner un nom spécifique qui vous sera communiqué lors de la création de votre arène privée.

Important
Il ne peut y avoir qu’un seul agent avec le même nom connecté à la fois dans une arène. A votre charge donc de traiter votre arbitre comme celui-dont-on-ne-doit-pas-prononcer-le-nom ! Ne communiquez jamais son nom ni votre programme de manière publique ! Si vous êtes amené à travail sur des repositories publiques (github, gitlab, replit…), utilisez la fonction input() en Python dans votre code et rentrer à chaque exécution son nom dans la console.

🔍 Information
Même sans super pouvoir, tout agent « normal » peut surveiller l’état du jeu et calculer des scores et implémenter d’autres règles du jeu en surveillant ses variables agent.jeu et agent.voisins. Cependant, il ne pourra pas changer l’état du jeu, seul l’arbitre à ce privilège.

🏆 En route vers la victoire

Dans tout jeu, le but premier est de gagner ! Mais de quelle façon peut-on établir qui gagne dans PytactX ? Comment implémenter nos règles du jeu dans le script de cet Agent arbitre ?

Et bien grâce à ces 2 variables arbitre.jeu et arbitre.voisins !

Dans la variable arbitre.voisins, sont répertoriés en temps réel tous les états de tous les agents du jeu voisins de notre agent (i.e. dans son champs de vision). Parmi ces informations structurées dans un dictionnaire de paires de clé:valeur, nous avons la vie « life », la position « x » « y », la direction « dir », l’équipe « team » … C’est clés s’affichent telles que décrites dans par les valeurs associées aux clés publish dans le dictionnaire ci-dessous qui décrit toutes les variables d’état des agents .

"color": {"?fr":"Couleur RGB de l'agent", "ini":[0,255,0], "publish":"led"},
"profile": {"?fr":"Id du profile de l'agent", "ini":0, "min":0, "publish":"profile"},
"team": {"?fr":"Id de l'equipe de l'agent", "ini":0, "min":0, "publish":"team"},
"x": {"?fr":"Abscisse sur la grille", "ini":0, "min":0, "publish":"x"},
"y": {"?fr":"Ordonnee sur la grille", "ini":0, "min":0, "publish":"y"},
"dir": {"?fr":"Orientation sur la grille", "ini":0, "min":0, "max":3, "publish":"dir"},
"distance": {"?fr":"Capteur de distance frontale", "min":0, "ini":0, "publish":"d", "readonly":True},
"fire": {"?fr":"Si l'agent est en train de tirer", "ini":False, "publish":"fire", "readonly":True},
"life": {"?fr":"Vie de l'agent", "ini":100, "min":0, "max":10000, "publish":"life"},
"ammo": {"?fr":"Nom de munitions de l'agent", "ini":10, "min":0, "max":10000, "publish":"ammo"},
"range": {"?fr":"Dico des agents dans le voisinage", "ini":{}, "publish":"range", "readonly":True},
"info": {"?fr":"Message d'information sur l'agent qui apparaitra à la place de son nom dans l'arène", "ini":"", "publish":"info"},
"nFire": {"?fr":"Stats du nombre de tirs effectues", "ini":0, "min":0, "publish":"nFire"},
"nHit": {"?fr":"Stats du nombre de dommages effectues a d'autres agents", "ini":0, "min":0, "publish":"nHit"},
"nMove": {"?fr":"Stats du nombre de deplacements effectues", "ini":0, "min":0, "publish":"nMove"},
"nDeath": {"?fr":"Stats du nombre de fois où l'agent a été tué", "ini":0, "min":0, "publish":"nDeath"},
"nKill": {"?fr":"Stats du nombre de meurtre que l'agent a commis", "ini":0, "min":0, "publish":"nKill"}

🔍 Information
Par défaut dans l’arène public « demo », tous les agents y compris les « non arbitre » ont un champs de vision infini. Ils voient tout sur l’arène. En d’autre termes, il n’y a pas de brouillard de guerre. Mais ces règles peuvent être changées par profile d’agent via l’arbitre (expliqué ci-après dans ce tuto) ou via l’interface administrateur (PytactX Admin)

Dans la variable arbitre.jeu, sont inventoriés en temps réel toutes les règles ainsi que l’état du jeu. En guise de documentation, voici le dictionnaire initial de toute arène PytactX. Dans ce dictionnaire, seules les valeurs des clés « ini » sont envoyées aux agents, et sont présentes dans leur dico agent.jeu. Les clé:valeur « ?fr » explique le rôle de chaque règle clé de ce dictionnaire.

# 🚷 Règles pour gérer les joueurs
"fireTeam" : { "?fr":"Si oui ou non tir ami possible entre agents de même team", "ini":false, "publish":true },
"dtRespawn" : { "?fr":"Délai de renaissance apres mort (en msecs)", "ini":5000, "min":1000, "max":60000, "publish":false },
"nRespawn" : { "?fr":"Nb de respawn possible. 0=infini", "ini":0, "min":0, "max":10000, "publish":true },


# 🖼️ Règle pour customiser l'arène : taille, images, couleurs ...
"gridColumns" : { "?fr":"Nombre de colonnes dans la grille", "ini":40, "min":0, "max":50, "publish":true },
"gridRows" : { "?fr":"Nombre de lignes dans la grille", "ini":30, "min":0, "max":50, "publish":true },
"resPath" : { "?fr":"Adresse vers le serveur des resources images", "ini":"https://jusdeliens.com/play/pytactx/resources/jda0nsi2223/", "publish":true },
"bgImg" : { "?fr":"Url de l'image de fond", "ini":"bg.jpg", "publish":true },
"bgColor" : { "?fr":"Code rgba du fond de l'arène", "ini":[255,255,255,0.4], "publish":true },
"gridColor" : { "?fr":"Code rgba des lignes et colonnes de l'arène", "ini":[255,255,255,0.4], "publish":true },
"hitImg" : { "?fr":"Url de l'image de dégat. Cercle rouge par défaut", "ini":"hit.png", "publish":true },
"map" : {"?fr":"Map d'obstacle sur la grille. 0: pas d'obstacle, >0:obstacle dont image correspond a l'index dans mapImgs", "ini":[], "publish":true},
"mapRand" : {"?fr":"Generation random d'obstacle sur la grille", "ini":true, "publish":false},
"mapRandFreq" : {"?fr":"Frequence random d'obstacle sur la grille", "ini":0.0, "publish":false},
"mapImgs" : { "?fr":"Url des images de chaque profiles d'obstacle. Chaine vide => dessin cercle", "ini":["", ""], "publish":true },

# 🥷 Règles par profile de joueur
"profiles" : { "?fr":"Liste des profiles de agents avec stats différentes", "ini":["default", "attaquant", "defenseur", "arbitre", "ball"], "publish":true },
"pIcons" : { "?fr":"Icone de chaque profiles de agents", "ini":["", "⚔️", "🛡️", "👀", ""], "publish":true },
"pImgs" : { "?fr":"Url des images de chaque profiles de agents. Chaine vide => dessin cercle", "ini":["ova.svg", "ova.svg", "ova.svg", "ova.svg", "ball.svg"], "publish":true },
"pPnj" : { "?fr":"Classe d'ia pour request auto. IA possibles '':desactiver ia, 'Idle':inoffensive, 'StaticTurret:tourne sur soi-même et tir, 'RandomMovingTurret':déplacement aléatoire et tir, 'SearchNDestroyBehaviour': agents dans metal gear solid 1", "ini":["","","","","Idle","SearchNDestroy"], "publish":false },
"range" : { "?fr":"Rayon de visibilite. En nb de cases. 0 pour tout voir.", "ini":[10,10,10,0,0], "min":0, "max":10, "publish":true },
"dtDir" : { "?fr":"Délai entre 2 changements d'orientation (en msecs)", "ini":[300,300,450,10,10], "min":0, "max":10000, "publish":true },
"dtMove" : { "?fr":"Délai entre 2 déplacements (en msecs)", "ini":[300,300,450,10,10], "min":0, "max":10000, "publish":true },
"dtFire" : { "?fr":"Délai entre 2 tirs (en msecs)", "ini":[300,300,300,0,0], "min":0, "max":10000, "publish":true },
"fxFire" : { "?fr":"Si oui ou non fonction tir possible", "ini":[true,true,true,true,false], "publish":true },
"hitFire" : { "?fr":"Dégats infligés par tir", "ini":[10,15,10,100,0], "min":0, "max":100, "publish":true },
"hitCollision" : { "?fr":"Dégats infligés par collision", "ini":[10,15,10,0,0], "min":0, "max":100, "publish":true },
"dxMax" : { "?fr":"Déplacement max autorisé en x", "ini":[1,1,1,100,1], "min":0, "max":10, "publish":true },
"dyMax" : { "?fr":"Déplacement max autorisé en y", "ini":[1,1,1,1,1,100,1], "min":0, "max":10, "publish":true },
"lifeIni" : { "?fr":"Nombre vie par agent", "ini":[100,75,150,0,0], "min":0, "max":100, "publish":true },
"ammoIni" : { "?fr":"Nombre munitions par agent", "ini":[100,100,100,0,0], "min":0, "max":100, "publish":true },
"invisible": { "?fr":"Si oui ou non invisible", "ini":[false,false,false,true,false], "publish":true },
"invincible": { "?fr":"Si oui ou non invincible", "ini":[false,false,false,true,true], "publish":true },
"infiniteAmmo": { "?fr":"Si oui ou non munitions infinies", "ini":[false,false,false,true,false], "publish":true },
"collision" : { "?fr":"Si oui ou non collision possible avec autres agents", "ini":[true,true,true,false,true], "publish":true },

# 👨‍👩‍👧‍👦 Règles par équipe
"teamColor" : { "?fr":"Couleur de chaque equipe en code RGB", "ini":[[255,255,255],[43,250,250],[255,0,64]], "publish":true },
"teamNb" : { "?fr":"Nombre de equipes. 0=pas de equipe", "ini":3, "publish":false },
"teamName" : { "?fr":"Nom des equipes", "ini":["white","blue","red"], "publish":false },
"spawnArea" : { "?fr":"Dictionnaire précisant la zone de naissance des agents. Si vide, alors spawn random sur toute la carte", "ini":{"x":[20,4,35], "y":[15,15,15], "r":[1,3,3]}, "publish":true },

# ⏯️ Règles pour connaitre et changer l'état du jeu 
"info" : { "?fr":"Message public affiché dans l'arene", "ini":"", "publish":true },
"pause" : { "?fr":"Mettre le jeu en pause. Attention seul admin peut passer de true à false", "ini":false, "publish":True },
"reset" : { "?fr":"Supprime tous les agents et leurs stats et redemarre le jeu", "ini":true, "reset":True}
"open" : { "?fr":"Ouvrir ou fermer les portes de l'arène. Une fois fermée, aucun nouveau joueur ne pourra rentrer.", "ini":true, "publish":false },

🦸 Les supers pouvoirs d’arbitre

Grâce à la fonction arbitre.changerArene(cle, valeur) votre arbitre pourra modifier en temps réel les règles de l’arène ci-dessus. Par exemple, si vous souhaitez donner une cap d’invisibilité aux agents de profile attaquant (2e dans la liste valeur de la clé profile, donc 2e egalement pour la liste valeur de la clé invisible), vous pouvez exécuter le code suivant :

arbitre.changerArene("invisible", [False,True,False,True,False])

🔍 Information
A leurs modifications, certaines règles du jeu entrainent la réinitialisation de l’arène. Il s’agit des règles qui ont pour valeur True pour la clé « reset ». Dans ce cas, veuillez à bien faire les changements de ces règles à l’initialisation de votre arbitre, avant le début du jeu.

La modification de certaines règles auront un impact visible immédiatement dans l’arène comme gridColumns ou gridRows ou fireTeam, tandis que pour d’autres, l’impact sera visible en différé, comme les règles définissant les profiles des joueurs. C’est dernières peuvent prendre effet au prochain spawn de l’agent concerné, comme lifeIni ou ammoIni.

Si vous souhaitez modifier les états des joueurs en temps réel, utilisez la fonction arbitre.changerJoueur(agentId, nomAttribut, valeurAttribut). Les nomAttributs possibles sont les clés listées dans le dictionnaire décrivant les états de tous les agents (voir ci-dessus).

Grâce à cette fonction, vous pourrez par exemple accorder à un agent spécifique des récompenses comme des munitions :

arbitre.changerJoueur("toto", "ammo", 300)

Au prochain actualiser, le joueur nommé « toto » aura 300 munitions sur le serveur du jeu.

📌 Un exemple siouplaît ?

Dans l’exemple ci-dessous, nous créons un arbitre nommé « LArchitecte », qui sera chargé de créer 2 agents autonomes ennemis, « Néo » et « Smith », de comptabiliser leurs scores et de les afficher dans l’arène.

🔍 Information
Pour ceux qui n’ont pas encore vu la trilogie Matrix, l’architecte (présent dans le 2e et 3e volet) est le protagoniste qui construit et administre la Matrice ainsi que tous ses programmes agents, notemment celui de Néo (l’élu subversif capable de renverser la Matrice) et celui de l’agent Smith (un agent surpuissant initialement censé surveiller le comportement des autres programmes).

# Création de l'arbitre
arbitre = pytactx.AgentFr("Architecte","TheMatrX")
arbitre.changerArene("info", "⌛ Initialisation de l'arbitre...")
while ( len(arbitre.jeu) == 0 ):
    arbitre.actualiser()
    sleep(0.5)

# Création d'agents actualisés par l'arène elle-même
agents = {
    "Neo" : 0,
    "Smith": 0 
}
arbitre.changerArene("info", "⌛ Création des pnj...")
for agentId in agents.keys() :
    arbitre.changerJoueur(agentId, "profile", arbitre.jeu["profiles"].index("pnj"))

# Affichage dans l'arène du début de la partie par l'arbitre
arbitre.changerArene("info", "🟢 C'est parti !")
arbitre.actualiser()

# Boucle principale pour actualiser l'arbitre 
while True:
    # Changement d'orientation de l'arbitre pour montrer qu'il est actif dans l'arène
    arbitre.orienter((arbitre.orientation+1)%4)
    arbitre.actualiser()

    # Actualisation des scores des agents
    tableauScore = ""
    for agentId in bots.keys():
        if ( agentId in arbitre.voisins ):
            nouveauScore = arbitre.voisins[agentId]["nKill"]
            # Ajout de vie et de munitions à chaque kill
            if ( nouveauScore > agents[agentId] ):
                arbitre.changerJoueur(agentId , "life", arbitre.voisins[agentId]["life"]+50)
                arbitre.changerJoueur(agentId , "ammo", arbitre.voisins[agentId]["ammo"]+10)
            agents[agentId] = nouveauScore
        tableauScore += " - 🏆"+agentId +" "+ str(agents[agentId])

    # Affichage du score des 2 bots en temps réel
    arbitre.changerArene("info", tableauScore)

Pour que ces 2 derniers soient doués d’intelligences subversives et évoluent de manière quelque peu violente dans l’arène, notre arbitre leur affectera le profile de « pnj » (Personnage Non Joueur), ce profile exécutant par défaut la stratégie « SearchNDestroy » sauvegardé dans la règle « pPnj » déjà existante au démarrage de l’arène.

Notre arbitre démarre enfin sa boucle principale, dans laquelle il change son orientation et effectue une mise à jour de son dictionnaire de score par agent qu’il affiche ensuite dans le panneau « info » de l’arène. Le score ici correspond au nombre de kills effectués par chaque agent. A chaque kill, l’arbitre attribue une récompense de 50 de vie et 10 de munitions à l’agent concerné.

🎮 A vous de jouer !

Avec toutes ces informations, il ne vous restent plus qu’à bien définir vos règles du jeu, puis de vous lancer dans l’implémentation de votre script arbitre.

Pour plus d’aide, vous pouvez vous inspirer des travaux des précédents apprentis jus de liens.

Code of the Hill ⛰️

Combat d’IA en équipe programmée en Python, dans lequel des robots s’affrontent pour prendre possession d’une colline. Inventé par Augustin, Matthias et Yann.

👉 Voir les sources du projet

Capture the Snake 🐍

Combat d’IA en équipe programmée en Python, dans lequel des robots s’affrontent pour capturer le drapeau de leur ennemi. Créé par Michel, Alban, Alexandre et Loïs.

👉 Voir les règles du jeu

Et après ?

Vous avez aimé ce tutoriel et vous en voulez d’autres ?

Faites le nous savoir en donnant vos avis et vos envies et nous nous empresserons de vous en rédigez d’autres 😉

Suivez le (jus de) lien(s) 👉 https://g.page/r/CQoJnRiyLqsqEB0/review

C’est fini pour ce tutoriel ! A vous de jouer maintenant 😉

Leave a Reply

Your email address will not be published.


*