Introduction à l’algorithme Minimax

Introduction à l'algorithme Minimax

1. Vue d'ensemble

Dans cet article, nous allons discuter de l'algorithme Minimax et de ses applications en IA. Comme il s'agit d'un algorithme de théorie des jeux, nous allons mettre en œuvre un jeu simple en l'utilisant.

Nous allons également discuter des avantages de l'utilisation de l'algorithme et voir comment il peut être amélioré.

2. introduction

Minimax est un algorithme de prise de décision,typically used in a turn-based, two player games. Le but de l'algorithme est de trouver le prochain mouvement optimal.

Dans l'algorithme, un joueur s'appelle le maximiseur et l'autre joueur est un minimiseur. Si nous attribuons un score d'évaluation au tableau de jeu, un joueur essaye de choisir un état de jeu avec le score maximum, tandis que l'autre choisit un état avec le score minimum.

En d'autres termes,themaximizer works to get the highest score, while the minimizer tries get the lowest score by trying to counter moves.

Il est basé sur le conceptzero-sum game. In a zero-sum game, the total utility score is divided among the players. An increase in one player’s score results into the decrease in another player’s score. Ainsi, le score total est toujours nul. Pour qu'un joueur gagne, l'autre doit perdre. Des exemples de tels jeux sont les échecs, le poker, les dames, le tic-tac-toe.

Fait intéressant: en 1997, l'ordinateur de jeu d'échecs d'IBM Deep Blue (construit avec Minimax) a battu Garry Kasparov (le champion du monde d'échecs).

3. Algorithme Minimax

Notre objectif est de trouver le meilleur coup pour le joueur. Pour ce faire, nous pouvons simplement choisir le nœud avec le meilleur score d'évaluation. Pour rendre le processus plus intelligent, nous pouvons également regarder vers l’avenir et évaluer les mouvements de l’adversaire potentiel.

Pour chaque mouvement, nous pouvons anticiper autant de mouvements que notre puissance de calcul le permet. L'algorithme suppose que l'adversaire joue de manière optimale.

Techniquement, nous commençons par le nœud racine et choisissons le meilleur nœud possible. Nous évaluons les nœuds en fonction de leurs scores d'évaluation. Dans notre cas, la fonction d'évaluation peut attribuer des scores aux seuls nœuds de résultats (feuilles). Par conséquent, nous atteignons récursivement les feuilles avec des scores et propagons les scores en arrière.

Considérez l'arbre de jeu ci-dessous:

image

Maximizerstarts with the root node et choisit le coup avec le score maximum. Malheureusement, seules les feuilles ont des scores d'évaluation, et l'algorithme doit donc atteindre les nœuds feuilles de manière récursive. Dans l’arborescence de jeu donnée, c’est actuellement au tour du minimiseur de passer àchoose a move from the leaf nodes, donc les nœuds avec des scores minimum (ici, les nœuds 3 et 4) seront sélectionnés. Il continue à sélectionner les meilleurs nœuds de la même manière, jusqu'à atteindre le nœud racine.

Maintenant, définissons formellement les étapes de l'algorithme:

  1. Construire l'arbre de jeu complet

  2. Évaluer les scores des feuilles à l'aide de la fonction d'évaluation

  3. Sauvegardez les partitions des feuilles aux racines, en fonction du type de joueur:

    • Pour joueur maximum, sélectionnez l'enfant avec le score maximum

    • Pour joueur min, sélectionnez l'enfant avec le score minimum

  4. Au nœud racine, choisissez le nœud avec la valeur maximale et effectuez le déplacement correspondant.

4. la mise en oeuvre

Maintenant, mettons en œuvre un jeu.

Dans le jeu, nous avons unheap with n number of bones. Les deux joueurs doivent à leur tourpick up 1,2 or 3 bones. Un joueur qui ne peut prendre aucun os perd la partie. Chaque joueur joue de manière optimale. Étant donné la valeur den, écrivons une IA.

Pour définir les règles du jeu, nous implémenterons la classeGameOfBones:

class GameOfBones {
    static List getPossibleStates(int noOfBonesInHeap) {
        return IntStream.rangeClosed(1, 3).boxed()
          .map(i -> noOfBonesInHeap - i)
          .filter(newHeapCount -> newHeapCount >= 0)
          .collect(Collectors.toList());
    }
}

De plus, nous avons également besoin de l'implémentation pour les classesNode etTree:

public class Node {
    int noOfBones;
    boolean isMaxPlayer;
    int score;
    List children;
    // setters and getters
}
public class Tree {
    Node root;
    // setters and getters
}

Nous allons maintenant mettre en œuvre l'algorithme. Il faut un arbre de jeu pour regarder en avant et trouver le meilleur coup. Mettons en œuvre cela:

public class MiniMax {
    Tree tree;

    public void constructTree(int noOfBones) {
        tree = new Tree();
        Node root = new Node(noOfBones, true);
        tree.setRoot(root);
        constructTree(root);
    }

    private void constructTree(Node parentNode) {
        List listofPossibleHeaps
          = GameOfBones.getPossibleStates(parentNode.getNoOfBones());
        boolean isChildMaxPlayer = !parentNode.isMaxPlayer();
        listofPossibleHeaps.forEach(n -> {
            Node newNode = new Node(n, isChildMaxPlayer);
            parentNode.addChild(newNode);
            if (newNode.getNoOfBones() > 0) {
                constructTree(newNode);
            }
        });
    }
}

Nous allons maintenant implémenter la méthodecheckWin qui simulera un play-out, en sélectionnant les mouvements optimaux pour les deux joueurs. Il établit le score à:

  • +1 si le maximiseur gagne

  • -1, si le minimiseur gagne

LecheckWin retournera vrai si le premier joueur (dans notre cas - maximizer) gagne:

public boolean checkWin() {
    Node root = tree.getRoot();
    checkWin(root);
    return root.getScore() == 1;
}

private void checkWin(Node node) {
    List children = node.getChildren();
    boolean isMaxPlayer = node.isMaxPlayer();
    children.forEach(child -> {
        if (child.getNoOfBones() == 0) {
            child.setScore(isMaxPlayer ? 1 : -1);
        } else {
            checkWin(child);
        }
    });
    Node bestChild = findBestChild(isMaxPlayer, children);
    node.setScore(bestChild.getScore());
}

Ici, la méthodefindBestChild trouve le nœud avec le score maximum si un joueur est un maximisateur. Sinon, il retourne l'enfant avec le score minimum:

private Node findBestChild(boolean isMaxPlayer, List children) {
    Comparator byScoreComparator = Comparator.comparing(Node::getScore);
    return children.stream()
      .max(isMaxPlayer ? byScoreComparator : byScoreComparator.reversed())
      .orElseThrow(NoSuchElementException::new);
}

Enfin, implémentons un scénario de test avec des valeurs den (le nombre de bones dans un tas):

@Test
public void givenMiniMax_whenCheckWin_thenComputeOptimal() {
    miniMax.constructTree(6);
    boolean result = miniMax.checkWin();

    assertTrue(result);

    miniMax.constructTree(8);
    result = miniMax.checkWin();

    assertFalse(result);
}

5. Amélioration

Pour la plupart des problèmes, il n'est pas possible de construire un arbre de jeu complet. In practice, we can develop a partial tree (construct the tree till a predefined number of levels only).

Ensuite, nous devrons implémenter unevaluation function, qui devrait être capable de décider de la qualité de l'état actuel pour le joueur.

Même si nous ne construisons pas d'arbres de jeu complets, le calcul des mouvements pour les jeux avec un facteur de branchement élevé peut prendre du temps.

Fortunately, there is an option to find the optimal move, without exploring every node de l'arborescence du jeu. Nous pouvons ignorer certaines branches en suivant certaines règles, et cela n'affectera pas le résultat final. This process is calledpruning. Alpha–beta pruning est une variante courante de l'algorithme minimax.

6. Conclusion

L'algorithme Minimax est l'un des algorithmes les plus populaires pour les jeux de société sur ordinateur. Il est largement appliqué dans les jeux à tour de rôle. Cela peut être un bon choix lorsque les joueurs ont des informations complètes sur le jeu.

Ce n'est peut-être pas le meilleur choix pour les jeux avec un facteur de branchement exceptionnellement élevé (par exemple, jeu de GO). Néanmoins, avec une implémentation appropriée, il peut s'agir d'une IA assez intelligente.

Comme toujours, le code complet de l'algorithme peut être trouvéover on GitHub.