Intelligence artificielle et jeux de cartes type CCG / deckbuilding

Récemment un ami passionné par les jeux de sociétés (et notamment les jeux de cartes type CCG / deck building — et non, ce n’est pas @MoDDiB ) m’a demandé s’il était possible d’écrire une I.A. pour des jeux de cartes de ce type (notamment afin de tester des designs de cartes).

La plupart des versions “jeu vidéo” de ces jeux de cartes disposent d’une I.A., qui même si elle n’est pas experte (ie. ne battra pas un joueur professionnel) est tout à fait capable de faire quelques parties contre des humains, tout en restant un challenge pour ceux qui découvrent le jeu.

Je me suis donc posé la question: Mais comment est implémentée cette I.A.? J’ai fouillé le net, et je suis tombé sur plusieurs articles qui pourraient répondre à cette problématique:

Le dernier lien était le plus intéressant, car décrivant un cas concret (je suis pas vraiment un théoricien). En gros, le joueur applique une règle qui calcule ce qu’on nomme le card advantage (ce qui correspond en gros au delta de cartes jouables entre sa main et la main de l’adversaire) et une autre valeur nommée le tempo (qui correspond au nombre de cartes en jeu).

Après réflexion, je me suis dit que ces deux valeurs étaient plus ou moins liées, et qu’on pouvait lier ces deux valeurs en une seule, qui serait en fait la valeur de “puissance” d’une carte (je reviendrai dessus après). On m’a aussi demandé à ce que l’intelligence artificielle soit la plus générique possible. Impossible donc de coder en dur des comportements de jeu.

Chaque carte dispose d’options de jeu, comme “sortez la carte de la main, mettez là en jeu”, ou “tourner cette carte, recevez X ressources”, ou encore “sortez la carte de la main, choisissez une carte de l’adversaire, mettez la dans la défausse”. Du classique pour ceux qui connaissent les CCG et autres jeux de deck-building.

Il est important de préciser que les ressources ont aussi une puissance, étant donné que la plupart des jeux se servent de ressources pour décider si le jeu est fini (par exemple à Magic, la ressource “Points de vie” décide de la fin de la partie, mais dans un autre jeu, ca serait des “Points de victoire”).

J’en suis arrivé à la philosophie suivante:

  • Pendant la phase de jeu, l’I.A. regarde la somme de la puissance de chaque carte en jeu pour son coté (main + jeu posé) et la compare à la puissance de l’adversaire. La main de l’adversaire n’étant pas connue, on assigne une valeur par défaut pour chaque carte inconnue, par exemple 100.
  • L’I.A. explore toutes les possibilités de jeu en construisant un arbre ordonnée des options de jeu de carte. Par exemple pour l’option “détruisez une carte adverse”, il y aura n entrées dans les possibilités, chacune correspondant à une carte de l’adversaire.
  • On trie cet arbre de possibilités en fonction du plus gros avantage reçu. Par exemple, dans le cas de “détruisez la carte adverse”, la carte détruite ayant le plus de puissance sera prioritaire dans l’arbre par rapport à une carte ayant moins de puissance. De plus dans cet exemple précis, la carte que l’IA a joué ayant aussi une puissance, l’option peut se montrer non viable (ie la carte “détruisez la carte adverse” a plus de puissance que toutes les cartes adversaires pouvant être détruites).
  • On joue les options dans l’ordre du gain d’avantage.

On fait jouer l’I.A. comme ça jusqu’à la fin de la partie et on note si elle a réussi ou pas, et on applique un algorithme génétique sur les puissances des cartes, en mélangeant plusieurs sets de puissances et en mutant la puissance de chaque carte entre chaque partie (du classique en algorithmique génétique je pense, mais je ne suis pas un spécialiste).

Au bout de n parties (n > 1000?), on devrait normalement avoir une tendance qui se dégage et les puissances des cartes qui devraient être approximativement correctes.

Mes questions maintenant:

  • Est ce que la logique de base pour l’I.A. vous semble correcte?
  • Dans le cas d’un jeu qui nécessite un deck à construire, certaines cartes peuvent avoir une puissance complètement différentes à causes des différentes synergies possibles. Par exemple, à Magic, personne ne mettrait des gobelins (nécessitant de la mana rouge) dans un deck bleu. Dans ce cas, il y a fort à parier que la carte gobelin perde en puissance si on n’évalue que le deck courant au lieu d’évaluer toutes les cartes. La puissance des cartes est elle donc dépendante du deck, ou de la collection complète de cartes?
4 « J'aime »

Je réagie juste sur cette partie de ton post.
Tu risques de te confronter à une explosion combinatoire. En fonction de la profondeur de l’arbre des possibilité que tu vas créer ca va prendre vraiment beaucoup de temps.

Regarde peut etre du coté des chaines de Markov, ou (c’est plus mon domaine) sur une IA décentralisé. C’est a dire que ce sont les cartes qui vont se mettre d’accord (par concencus, déliberation, election,…) entre elles pour savoir quoi jouer. Toujours dans le décentralisé, regarde aussi du coté de l’Eco-résolution, ca marche pas mal pour le jeu de carte et c’est tres facile a coder… en plus, l’ajout de nouvelle carte ne perturbe pas l’algo.

2 « J'aime »

C’est effectivement un des problèmes que je n’ai pas abordé; il est vrai que dans ma tête je visualisais l’arbre des possibilités comme démarrant à chaque fois d’une des cartes soit dans la main, soit en jeu, et que l’arbre s’arrête dès que toutes les options sont épuisées, limitant donc la profondeur de l’arbre à trois niveaux maximum (choix de la carte, choix de l’option, choix de la cible).

Mais si je fais ça, des cartes de type instant boost (qui pourraient booster temporairement une carte pendant le tour) risquent potentiellement de ne jamais être jouées, sauf si je réévalue l’arbre des possibilités à chaque fois que j’ai sélectionné une possibilité.

Si l’on compte une moyenne de 2-3 options par carte ( par exemple, pour une créature: déployer sur le jeu, tapper pour attaquer, et encore une « special feature ») on obtient n * 3 * m options de jeux, avec n le nombre de créature de mon coté et m le nombre de créatures chez l’adversaire. avec, par exemple 5 créatures de chaque coté, cela fait 75 options si je ne m’abuse. Mais si on monte à 20 créatures (qui est je pense un grand maximum) cela fait déjà 1200 options à évaluer.

Je n’ai rien trouvé sur le net sur l’éco-résolution (que des références à des traités d’économies), aurais tu un lien pour moi?

J’espere que je vais pas dire de bêtise, hésitez pas à me corriger.

Le problème de l’explosion combinatoire va surtout venir avec l’avancement de la partie. La taille de la main (et donc la combinaison de carte) les cartes sur le plateau et les cartes déjà jouées représente une combinaison parmi beaucoup. Du coup modéliser le problème sous forme d’arbre est naturel pour une humain mais je pense une erreur pour une IA.

Si on retourne à la base, pour prendre une décision, on le fait en 4 étapes:
1/ La collecte d’info (mémoire + carte déjà jouer + carte en main + carte sur le plateau)
2/ La modélisation du problème ( sous forme d’arbre si on veut, sous forme de formule math, etc). Ca revient a lister toutes les possibilités acceptables.
3/ Le choix ( on prend la solution qui semble la meilleur)
4/ L’évaluation (on regarde que le choix qu’on a fait est bon pour améliorer la modélisation du problème ou la politique de choix.

Les étapes 1 et 4, c’est facile, tu connais ton jeu. La 2 et 3 sont assez liées en fonction de comment tu veux faire ton IA.

2 possibilités:
1/ Ton IA joue de façon empirique sans tenir compte de ce qui est fait avant.
tu vas pouvoir modéliser ton problème avec un modèle de markov, un multi-agent réactif tout simple, un petit RNA, un arbre binaire de décision (mais attention à l’explosion)
Le plus compliqué ici c’est d’avoir une fonction qui te donne un score pour chaque carte pour savoir celle qui a le plus de chance de faire avancer ton IA vers la victoire. Il faudra donner a ton système des capacité d’adaptation et d’organisation (self-*)

2/ Ton IA joue avec une mémoire
tu vas pouvoir modéliser ton problème avec un sma cognitif, un RNA adaptatif, un alog gén,…
Le plus compliqué ici c’est d’avoir une fonction qui te donne un score pour une séquence de cartes à jouer pour savoir celle qui a le plus de chance de faire avancer ton IA vers la victoire. Il faudra donc donner a ton système de la connaissance sur la façon de jouer.

Apres, c’est surement sortir le lance roquette pour tuer une mouche. Dans un premier temps, tu peux surement partir sur une fonction de fitness qui te calcule un score pour chaque carte en ne tenant compte que de ce qui a dans la main de l’IA et sur le plateau. La question que je me pose, c’est avec une pioche et un adversaire, est ce que c’est intéressant d’avoir une mémoire et une politique de jeu a long terme ?

un peu de lecture sur les cartes:

sur HMM:

sur les SMA :


1 « J'aime »

Youpla boum vu que je suis spawné je vais réagir promptement.
Personnellement voici comment je procéderai :

  • Procédons étape par étape tout d’abord mettre de côté le deckbuilding : dans un premier temps il est important que l’IA se démerde au mieux avec les cartes dont elle dispose.
  • Pour cela un simple minimax ( avec alpha-beta ) : le plus “difficile” va être de donner un score à l’état d’une partie à un instant donné.
  • Grâce à cela tu pourras tester toutes les combinaisons possibles sur X tours ( ça va dépendre du nombre d’options possibles dans ton jeu ) en partant du principe que l’adversaire joue une bonne carte.
  • Tu vas pouvoir étalonner le score de l’état de la partie par algo génétique sur un grand nombres de parties.
  • C’est niquel tu as une IA qui sait jouer ! Désormais il faut qu’elle construise son deck.
  • Un bon algo génétique avec au départ une bonne vingtaine de decks complètement random, à chaque génération ce sera les cartes qui seront le moins utilisées qui auront le plus de chance d’être remplacées.
  • Ca va dépendre du nombre de cartes totales mais il faudra certainement des milliers d’affrontement pour en tirer un bon deck.

De cette manière tu ne donneras jamais une valeur fixe à une carte : en effet une carte peut être puissante d’elle même mais totalement inutile dans un deck !

1 « J'aime »

Attention: cas con: les decks « rush » avec moulte petites cartes => ta carte destruction (ou debuff etc …) aura des chances d’être systématiquement supérieure aux valeurs de puissances des cartes posées en face.
Il faudrait donc aussi arriver à trouver un facteur « perte de puissance acceptable » qui représenterait le cas du joueur qui se dit « c’est pas opti, mais j’ai pas le choix ».

Autre chose, un peu lié, mais je crois comprendre que tu as au moins commencé à le prendre en compte, la puissance d’une carte peut être dynamique. Pour reprendre l’exemple du dessus une carte « boost toutes les autres creatures » sera plus puissante dans un deck petites creatures que dans un deck grosses creature. Il faudrait donc qu’elle ait une puissance du genre "+X / carte créature dans mon deck ou +X/nombre de créatures moyen dans un deck pour l’adversaire.

Attention à ne pas confondre la « puissance » avec la « valeur d’attaque » de la créature. La puissance (ou valeur intrinsèque si tu préfères) est la capacité de la carte à être utile dans un deck, et, dans ma théorie est obtenue a coup d’essais / erreurs et non en fonction de la la force de la créature via l’algorithme génétique.

En gros on a un deck plein de petites créatures. On tire des valeurs aléatoire pour ces créatures (qui vont donc influencer l’ordre de jeu). On fait jouer ce deck n fois avec n valeurs différentes et aléatoires. On garde les decks qui ont gagnés, on moyenne les valeurs de chaque carte d’un deck gagnant, on régénère n decks en prenant pour base cette nouvelle valeur et on introduit une mutation dans la valeur.

Par exemple mon deck était gagnant avec une carte gobelin avec les valeurs intrinsèques de 10, 30, 50, mais a perdu avec une valeur intrinsèque de 110, 130, 150. La nouvelle valeur de base sera de 30 ± 20. Si je dis pas de bêtises, la somme des valeurs intrinsèques des cartes du paquet ne devrait pas changer au fur et a mesure du temps. Au pire, on peut refaire un étalonnage des valeurs pour que cette somme soit invariante (ie si la moyenne des somme des valeurs intrinsèques est inférieur à la valeur intrinsèque donnée par défaut, on peut ajouter le delta à toutes les cartes).

Autre problématique: ton deck rush induit une notion de meta-game. L’I.A. ne peut pas savoir si à un moment ou a un autre, t’as pas fait une blague dans ton deck et foutu une grosse créature dedans pour laquelle ta carte destruction pourrait avoir un sens (alors que le bon sens voudrait que non). Du coup elle fait quoi? Elle ne la joue pas, en attendant potentiellement qu’une grosse créature sorte…

Et toi, en tant qu’humain, tu ferais quoi? Parier sur le fait que le mec en face n’a que des petites créatures, ou prévoir le pire?

En tant qu’humain, si j’ai 20pvs, que j’ai 20 créatures 1/1 en face et 0 créatures sur la table, même si j’ai la carte « Big destroyer of the world » qui coute 20 mana de couleurs différentes et qui remove from the game n’importe quelle créature ciblée, je la joue pour espérer jouer un tour de plus.
=> Au delà du méta game, il y a la situation dans laquelle je suis, qui peut complètement chambouler mes priorités: si je ne joue pas cette carte, je suis foutu. Mais en temps normal jamais de la vie je la jouerais.
Le cas « facile » à coder serait à minima « si tu vois qu’au prochain la ressource va faire gagner l’adversaire (plus de pv, trop de points de victoire etc … » alors bascule sur un algo « d’urgence » qui cherche non pas à optimiser mais à survivre.

Si vous commencez à raisonner en “cas particulier” vous n’obtiendrez rien de satisfaisant. Ce ne sera qu’un catalogue d’action.

On en a parlé un peu ce midi au boulot. On peut partir sur un algo décentralisé assez facile a coder.

  • chaque est autonome et “réfléchi” par elle même
  • chaque carte a 3 fonctions principales (perception, décision, action… on y reviendra)
  • chaque carte exécute ces fonctions dans le même ordre les une après les autres.

Détaillons les fonctions de la carte:

  • Perception: La carte stock dans un de ses attribut ce qu’elle voit sur le plateau et dans la main de l’IA
  • Décision: on peut partir sur une délibération.
    – la carte va demander au carte stocké dans la perception si elle doit “se jouer”
    – chaque carte de la perception va répondre avec une note (-100 non pas du tout, 100, oui a 100%). avec ce mécanisme, tu vas faire émerger le principe de combo (une carte qui s’associe bien avec une autre aura une note plus élevée)
    – la carte fait la somme de ses notes
    – la carte reçoit les notes des autre cartes dans la main

*Acition : si la carte a la plus haute note, elle “se joue” sinon elle attend le prochain tick.

Tu as un scheduler qui appelle en boucle perception()/decision()/action() de chaque carte et si il a un tick bien réglé tu pourra même voir émerger des “contres” avec les cartes qui peuvent être jouées instantanément sans codé quoi que ce soit de plus.

Voila une petite idée d’IA sans prétention pour un jeu de carte.

C’est pas forcément du cas particulier que j’envisage, mais au contraire une généralisation de plus (ce qui est peut être trop quand même): définir un système de poids pour afficher des priorités.
Du coup, au tout début du tour, examen des forces en présence (ça c’est déjà prévu) et calcul de la priorité pour le tour.
Je suis sur le point de mourir, il faut que je supprime le plus de risques possibles.
L’adversaire est sur le point de mourir ? On va l’achever.
Je suis en manque de mana /cristaux / chatons: il faut que je pose une carte qui m’en fait gagner.

Un peu comme les 3 lois de la robotique, mais pour les cartes :slight_smile:

Tu prends le chemin vers un système de règle, qui va devenir impossible avec l’explosion combinatoire.
Ce genre de comportement de ton système doit émerger de tes itérations (algo genétique, renforcement, etc…) et pas être issus d’une logique hypothético déductive.