Algo puissance 4 sur Arduino

Bonjour a tous !
Le projet que je souhaite réaliser est un projet très complexe et peut etre vraiment dur a mettre en place (mais vous etes là !!).
Alors, j'ai codé un 'petit' programme qui permet de jouer au puissance 4 contre l'arduino avec l'interface du moniteur série. Ici, l'arduino joue totalement aléatoirement (pseudo-aleatoirement en fait...) ; et bien moi ce que je voudrais c'est que l'arduino joue d'une maniere plus competitive et plus avancé pour etre tres forte (voir peut etre quasi-imbattable). Mes recherches m'ont deja conduit au minimax mais je suis pas bien arriver. Pourriez-vous m'aider s'il vous plait, je sais très bien qu'il s'agit d'un projet tres complexe (surtout sur un petit microcontroleur) mais je cherche les pistes possibles. Voici le code que j'ai fait :

char joueur = 'X';
char emplacement[6][7] = {
  {' ', ' ', ' ', ' ', ' ', ' ', ' '},
  {' ', ' ', ' ', ' ', ' ', ' ', ' '},
  {' ', ' ', ' ', ' ', ' ', ' ', ' '},
  {' ', ' ', ' ', ' ', ' ', ' ', ' '},
  {' ', ' ', ' ', ' ', ' ', ' ', ' '},
  {' ', ' ', ' ', ' ', ' ', ' ', ' '}
};

void setup() {

  joueur = 'X';

  randomSeed(analogRead(A0));
  Serial.begin(9600);
  Serial.println(" ~~~~~~~~ PUISSANCE 4 ~~~~~~~~");
  Serial.println("");
  afficher_puissance();
  Serial.println("C'est a vous !");
  // randomSeed(analogRead(A0));
}

void loop() {



  if (joueur == 'O') {

    // Ici c'est le tour de l'arduino

    int colonne = random(6);
    int ligne;
    for (ligne = 5; ligne >= 0; ligne--) {
      if (emplacement[ligne][colonne] == ' ') {
        break;
      }
    }
    emplacement[ligne][colonne] = 'O';
    if (verifier_victoire(ligne, colonne)) {
      if (joueur == 'O') {
        afficher_puissance();
        Serial.println("L'ordinateur a gagne !");
        while (1);
      }


      // Réinitialisez le jeu ou effectuez d'autres actions après la victoire
    } else {
      joueur = 'X';
      afficher_puissance();
      Serial.println("L'ordinateur a joue.");
      Serial.println("C'est a vous !");
    }

  }

  if (Serial.available() > 0) {
    String input = Serial.readStringUntil('\n'); // Lire l'entrée jusqu'au caractère de nouvelle ligne

    // Vérifier si l'entrée est valide (1 lettre)
    if (input.length() == 1 && isalpha(input.charAt(0))) {

      char lettre = tolower(input.charAt(0)); // Convertir en minuscule pour gérer les majuscules
      int colonne = lettre - 'a'; // Convertir la lettre en indice de colonne (A=0, B=1, ...)

      // Trouver la première case vide dans la colonne choisie
      int ligne;
      for (ligne = 5; ligne >= 0; ligne--) {
        if (emplacement[ligne][colonne] == ' ') {
          break;
        }
      }

      // Vérifier si les coordonnées sont valides
      if (colonne >= 0 && colonne < 7 && ligne >= 0 && ligne < 6) {
        emplacement[ligne][colonne] = joueur; // Modifier le tableau
        // Afficher le tableau mis à jour

        if (verifier_victoire(ligne, colonne)) {
          if (joueur == 'X') {
            afficher_puissance();
            Serial.println("Vous avez gagne !");
          }

          while (1);
          // Réinitialisez le jeu ou effectuez d'autres actions après la victoire
        } else {
          joueur = 'O';


        }
      } else {
        Serial.println("Coordonnees invalides / Case PLEINE !");
      }
    } else {
      Serial.println("Format des coordonnees invalide !");
    }

    // Demander de nouvelles coordonnées à l'utilisateur

  }

}




void afficher_puissance () {
  Serial.println("");
  Serial.println("  ___________________________");
  Serial.println(" | A | B | C | D | E | F | G |");

  for (int i = 0; i < 6; i++) {
    // Numéro de ligne
    Serial.print(" | ");
    for (int j = 0; j < 7; j++) {
      Serial.print(emplacement[i][j]); // Affichage du caractère
      Serial.print(" | ");
    }
    Serial.println(); // Nouvelle ligne à la fin de chaque ligne du tableau
  }
}

bool verifier_victoire(int ligne, int colonne) {
  char joueur_actuel = emplacement[ligne][colonne];

  // Vérification horizontale
  int compteur = 0;
  for (int i = -3; i <= 3; i++) {
    if (colonne + i >= 0 && colonne + i < 7 && emplacement[ligne][colonne + i] == joueur_actuel) {
      compteur++;
      if (compteur >= 4) return true;
    } else {
      compteur = 0;
    }
  }

  // Vérification verticale
  compteur = 0;
  for (int i = -3; i <= 3; i++) {
    if (ligne + i >= 0 && ligne + i < 6 && emplacement[ligne + i][colonne] == joueur_actuel) {
      compteur++;
      if (compteur >= 4) return true;
    } else {
      compteur = 0;
    }
  }

  compteur = 0;
  // Vérification diagonale (vers le bas à droite)
  for (int i = -3; i <= 3; i++) {
    if (ligne - i >= 0 && ligne - i < 6 && colonne + i >= 0 && colonne + i < 7 && emplacement[ligne - i][colonne + i] == joueur_actuel) {
      compteur++;
      if (compteur >= 4) return true;
    } else {
      compteur = 0;
    }
  }

  // Vérification diagonale (vers le bas à gauche)
  for (int i = -3; i <= 3; i++) {
    if (ligne - i >= 0 && ligne - i < 6 && colonne - i >= 0 && colonne - i < 7 && emplacement[ligne - i][colonne - i] == joueur_actuel) {
      compteur++;
      if (compteur >= 4) return true;
    } else {
      compteur = 0;
    }
  }

  // Vérification diagonale (vers le bas à gauche)
  compteur = 0;
  for (int i = -3; i <= 3; i++) {
    if (ligne + i >= 0 && ligne + i < 6 && colonne - i >= 0 && colonne - i < 7 && emplacement[ligne + i][colonne - i] == joueur_actuel) {
      compteur++;
      if (compteur >= 4) return true;
    } else {
      compteur = 0;
    }
  }

  return false;
}

Merci beaucoup d'avance pour vos réponses

Expliquez quel est votre souci vraiment

Le min max n’est pas si compliqué que cela

Je ne vois pas dans ton code de recherche de min ou max.
Du coup je ne comprends pas ta question, tu veux que l'on t'aide à quoi ?
Trouver un algorithme imbattable?
Une aide pour implémenter cet algo minmax ?

Merci Terwal et J-M-L, c’est vrai que je n’est pas forcément été très clair dans mon lancement de sujet. Dans le code que j’ai fournis, l’arduino joue totalement aléatoirement (elle est « nul »), et ceci grâce à : random(6);
Eh bien ce que je voudrais, c’est que l’arduino soit plus compétitive et pourquoi pas, quasi imbattable. Et c’est la où j’ai besoin de votre aide, j’ai réussi à obtenir quelques structures et fonctions pour le minimax mais cela n’aboutis a rien. C’est vraiment cette étape de l’implémentation qui me pause problème. Il faudrait retourner au lieu de random(6); une valeur plus coeherente que un nombre aléatoire. Je ne suis jamais arrivé à cela et c’est pour ça que je vous demande de l’aide !

A bientôt :blush: !

Je n'ai pas l'impression que tu as répondu à toutes mes questions :slight_smile:

Veux tu que l'on t'aide à implémenter l'algorithme ?
Veux tu que l'on t'écrive l'algorithme ?

Bon en gros, je pense que je peux aussi parler au nom de @J-M-L, nous n'écrivons pas le code à la place des développeurs, donc on peut t'aider que si tu réponds "oui", à la première question :rofl:

Je n'ai jamais implémenté cet algorithme, mais il semble plutôt simple.
Je pense qu'il faut y aller par étape.

  • le plus compliqué et stratégique me semble la fonction d'évaluation, qui prend un tableau représentant un état de la partie et renvois un score d'avantage
  • faire une fonction qui peut créer tout les coups possibles jouable à partir d'un tableau donné.
  • ajouter à la fonction précédent le stockage en arborescence, chaque nœud contenant un tableau et son score.

Je pense que la première étape est déjà très intéressante niveau algorithmique, a tu déjà cette fonction d'évaluation ou sa façon de faire?

tout à fait d'accord.

le principe:

  • Tout d'abord, le joueur qui utilise l'algorithme évalue la position actuelle du jeu. Cette évaluation peut être basée sur plusieurs facteurs, tels que le nombre de jetons alignés, la présence de menaces potentielles, etc. Une valeur numérique est attribuée à la position, indiquant à quel point elle est favorable ou défavorable (souvent un nombre négatif quand défavorable et positif quand favorable, plus ce nombre est grand plus la situation est dans un sens ou l'autre)

  • Ensuite, l'algorithme génère tous les coups possibles à partir de la position actuelle ➜ Dans Puissance 4, cela signifie placer un jeton dans l'une des colonnes encore disponibles donc au max 7 choix (certaines colonnes peuvent être déjà pleines).

  • C'est là où ça se complique un peu. Pour chaque coup possible, l'algorithme effectue une recherche récursive dans l'arbre de jeu en alternant entre les deux joueurs. Le joueur cherche à maximiser sa valeur d'évaluation (minimiser pour l'adversaire), tandis que l'adversaire cherche à minimiser cette valeur (maximiser pour lui-même). Cette alternance se poursuit jusqu'à atteindre une certaine profondeur de recherche ou jusqu'à ce qu'une condition d'arrêt soit remplie (comme la victoire ou le match nul).

  • Une fois que l'arbre de jeu a été exploré jusqu'à la profondeur souhaitée, l'algorithme remonte l'arbre pour déterminer le meilleur coup à jouer à partir de la position initiale. Le meilleur coup est celui qui maximise la valeur d'évaluation pour le joueur et donc c'est le coup joué.

L'algorithme du min-max garantit que le joueur choisira toujours la meilleure stratégie possible, en supposant que l'adversaire joue également de manière optimale.

Cependant, l'exploration de l'arbre de jeu peut devenir très coûteuse en termes de puissance de calcul et de mémoire car il faut mémoriser récursivement les positions. Puissance 4 a une matrix de 7x6, chaque case pouvant être dans 3 états (vide, jeton rouge, jeton jaune) donc il faut 2 bits au minimum par case 7x6x2 = 84 bits soit 11 octets pour une position. il faut ajouter l'évaluation etc donc disons 20 octets pour une position. 7 coups au premier niveau, 7 réponse possibles de l'adversaire, 7 coups au niveau suivant possible rien que pour regarder donc au second coup vous avez 7x7x7=343 positions x 20 octets par position = quasiment 7Ko de RAM. Un petit Uno ou Nano avec ses 2 Ko de RAM ne peut pas le faire par exemple.

➜ il est donc courant d'utiliser des améliorations telles que l'élagage alpha-bêta pour réduire le nombre de nœuds explorés.

il vous faut lire un peu sur la théorie des jeux et lire des exemples de code pour traiter la récursivité et les évaluations. Une fois que vous maîtriserez cela, en fonction de l'arduino retenu, vous pourrez décider de la profondeur de l'arbre à traiter (ou un max de positions à analyser)

Merci pour vos réponses très detaillées !!
Alors, je reponds aux questions de terwal : "Veux tu que l'on t'aide à implémenter l'algorithme ?
Veux tu que l'on t'écrive l'algorithme ?"
Bien sur je ne cherche pas a ce que vous me "mâchiez" le travail, mon but est de comprendre et surtout d'apprendre (l'inverse marche aussi :smile: )
Ensuite, je compte utilisé une arduino mega, donc sur la memoire ca devrait aller...
J'ai réussi a me documenter un peu sur internet et a obtenir un code que j'ai "melanger" au mien :

char joueur = 'X';
char emplacement[6][7] = {
  {' ', ' ', ' ', ' ', ' ', ' ', ' '},
  {' ', ' ', ' ', ' ', ' ', ' ', ' '},
  {' ', ' ', ' ', ' ', ' ', ' ', ' '},
  {' ', ' ', ' ', ' ', ' ', ' ', ' '},
  {' ', ' ', ' ', ' ', ' ', ' ', ' '},
  {' ', ' ', ' ', ' ', ' ', ' ', ' '}
};

void setup() {

  joueur = 'X';

  randomSeed(analogRead(A0));
  Serial.begin(9600);
  Serial.println(" ~~~~~~~~ PUISSANCE 4 ~~~~~~~~");
  Serial.println("");
  afficher_puissance();
  Serial.println("C'est a vous !");
  // randomSeed(analogRead(A0));
}

void loop() {



  if (joueur == 'O') {

    // Ici c'est le tour de l'arduino

    int colonne = fonction(); // Ajout de la fonction...  'fonction' qui est censé retourner le meilleur coup possible
    int ligne;
    for (ligne = 5; ligne >= 0; ligne--) {
      if (emplacement[ligne][colonne] == ' ') {
        break;
      }
    }
    emplacement[ligne][colonne] = 'O';
    if (verifier_victoire(ligne, colonne)) {
      if (joueur == 'O') {
        afficher_puissance();
        Serial.println("L'ordinateur a gagne !");
       
        while (1);
      }


      // Réinitialisez le jeu ou effectuez d'autres actions après la victoire
    } else {
      joueur = 'X';
      afficher_puissance();
      Serial.println("L'ordinateur a joue.");
      Serial.println("C'est a vous !");
    }

  }

  if (Serial.available() > 0) {
    String input = Serial.readStringUntil('\n'); // Lire l'entrée jusqu'au caractère de nouvelle ligne

    // Vérifier si l'entrée est valide (1 lettre)
    if (input.length() == 1 && isalpha(input.charAt(0))) {

      char lettre = tolower(input.charAt(0)); // Convertir en minuscule pour gérer les majuscules
      int colonne = lettre - 'a'; // Convertir la lettre en indice de colonne (A=0, B=1, ...)

      // Trouver la première case vide dans la colonne choisie
      int ligne;
      for (ligne = 5; ligne >= 0; ligne--) {
        if (emplacement[ligne][colonne] == ' ') {
          break;
        }
      }

      // Vérifier si les coordonnées sont valides
      if (colonne >= 0 && colonne < 7 && ligne >= 0 && ligne < 6) {
        emplacement[ligne][colonne] = joueur; // Modifier le tableau
        // Afficher le tableau mis à jour

        if (verifier_victoire(ligne, colonne)) {
          if (joueur == 'X') {
            afficher_puissance();
            Serial.println("Vous avez gagne !");
          }

          while (1);
          // Réinitialisez le jeu ou effectuez d'autres actions après la victoire
        } else {
          joueur = 'O';


        }
      } else {
        Serial.println("Coordonnees invalides / Case PLEINE !");
      }
    } else {
      Serial.println("Format des coordonnees invalide !");
    }

    // Demander de nouvelles coordonnées à l'utilisateur

  }

}




void afficher_puissance () {
  Serial.println("");
  Serial.println("  ___________________________");
  Serial.println(" | A | B | C | D | E | F | G |");

  for (int i = 0; i < 6; i++) {
    // Numéro de ligne
    Serial.print(" | ");
    for (int j = 0; j < 7; j++) {
      Serial.print(emplacement[i][j]); // Affichage du caractère
      Serial.print(" | ");
    }
    Serial.println(); // Nouvelle ligne à la fin de chaque ligne du tableau
  }
}

bool verifier_victoire(int ligne, int colonne) {
  char joueur_actuel = emplacement[ligne][colonne];

  // Vérification horizontale
  int compteur = 0;
  for (int i = -3; i <= 3; i++) {
    if (colonne + i >= 0 && colonne + i < 7 && emplacement[ligne][colonne + i] == joueur_actuel) {
      compteur++;
      if (compteur >= 4) return true;
    } else {
      compteur = 0;
    }
  }

  // Vérification verticale
  compteur = 0;
  for (int i = -3; i <= 3; i++) {
    if (ligne + i >= 0 && ligne + i < 6 && emplacement[ligne + i][colonne] == joueur_actuel) {
      compteur++;
      if (compteur >= 4) return true;
    } else {
      compteur = 0;
    }
  }

  compteur = 0;
  // Vérification diagonale (vers le bas à droite)
  for (int i = -3; i <= 3; i++) {
    if (ligne - i >= 0 && ligne - i < 6 && colonne + i >= 0 && colonne + i < 7 && emplacement[ligne - i][colonne + i] == joueur_actuel) {
      compteur++;
      if (compteur >= 4) return true;
    } else {
      compteur = 0;
    }
  }

  // Vérification diagonale (vers le bas à gauche)
  for (int i = -3; i <= 3; i++) {
    if (ligne - i >= 0 && ligne - i < 6 && colonne - i >= 0 && colonne - i < 7 && emplacement[ligne - i][colonne - i] == joueur_actuel) {
      compteur++;
      if (compteur >= 4) return true;
    } else {
      compteur = 0;
    }
  }

  // Vérification diagonale (vers le bas à gauche)
  compteur = 0;
  for (int i = -3; i <= 3; i++) {
    if (ligne + i >= 0 && ligne + i < 6 && colonne - i >= 0 && colonne - i < 7 && emplacement[ligne + i][colonne - i] == joueur_actuel) {
      compteur++;
      if (compteur >= 4) return true;
    } else {
      compteur = 0;
    }
  }

  return false;
}

int fonction() {
  int meilleurCoup = -1; // Initialise la meilleure colonne à -1 (non définie)
  int meilleurScore = -1000; // Initialise le meilleur score avec une valeur très faible

  // Parcourez toutes les colonnes pour trouver le meilleur coup
  for (int colonne = 0; colonne < 7; colonne++) {
    // Vérifiez si la colonne est valide (non pleine)
    if (emplacement[0][colonne] == ' ') {
      // Effectuez un coup simulé pour l'Arduino (joueur 'O')
      int ligne;
      for (ligne = 5; ligne >= 0; ligne--) {
        if (emplacement[ligne][colonne] == ' ') {
          break;
        }
      }
      emplacement[ligne][colonne] = 'O';

      // Évaluez le score de ce coup en appelant une fonction d'évaluation (par exemple, simple_evaluation)
      int score = simple_evaluation();

      // Annulez le coup simulé
      emplacement[ligne][colonne] = ' ';

      // Mettez à jour le meilleur coup si ce coup est meilleur
      if (score > meilleurScore) {
        meilleurScore = score;
        meilleurCoup = colonne;
      }
    }
  }

  return meilleurCoup;
}

int simple_evaluation() {
  int score = 0;

  // Évaluation horizontale
  for (int ligne = 0; ligne < 6; ligne++) {
    for (int colonne = 0; colonne <= 3; colonne++) {
      int countO = 0;
      int countX = 0;
      for (int k = 0; k < 4; k++) {
        if (emplacement[ligne][colonne + k] == 'O') {
          countO++;
        } else if (emplacement[ligne][colonne + k] == 'X') {
          countX++;
        }
      }
      if (countO == 4) {
        return 1000; // 'O' gagne
      } else if (countX == 4) {
        return -1000; // 'X' gagne
      } else {
        score += countO * countO;
      }
    }
  }

  // Évaluation verticale
  for (int colonne = 0; colonne < 7; colonne++) {
    for (int ligne = 0; ligne <= 2; ligne++) {
      int countO = 0;
      int countX = 0;
      for (int k = 0; k < 4; k++) {
        if (emplacement[ligne + k][colonne] == 'O') {
          countO++;
        } else if (emplacement[ligne + k][colonne] == 'X') {
          countX++;
        }
      }
      if (countO == 4) {
        return 1000; // 'O' gagne
      } else if (countX == 4) {
        return -1000; // 'X' gagne
      } else {
        score += countO * countO;
      }
    }
  }

  // Évaluation diagonale (vers le bas à droite)
  for (int ligne = 0; ligne <= 2; ligne++) {
    for (int colonne = 0; colonne <= 3; colonne++) {
      int countO = 0;
      int countX = 0;
      for (int k = 0; k < 4; k++) {
        if (emplacement[ligne + k][colonne + k] == 'O') {
          countO++;
        } else if (emplacement[ligne + k][colonne + k] == 'X') {
          countX++;
        }
      }
      if (countO == 4) {
        return 1000; // 'O' gagne
      } else if (countX == 4) {
        return -1000; // 'X' gagne
      } else {
        score += countO * countO;
      }
    }
  }

  // Évaluation diagonale (vers le bas à gauche)
  for (int ligne = 0; ligne <= 2; ligne++) {
    for (int colonne = 3; colonne < 7; colonne++) {
      int countO = 0;
      int countX = 0;
      for (int k = 0; k < 4; k++) {
        if (emplacement[ligne + k][colonne - k] == 'O') {
          countO++;
        } else if (emplacement[ligne + k][colonne - k] == 'X') {
          countX++;
        }
      }
      if (countO == 4) {
        return 1000; // 'O' gagne
      } else if (countX == 4) {
        return -1000; // 'X' gagne
      } else {
        score += countO * countO;
      }
    }
  }

  return score;
}

Donc ce code est plus avancé mais insuffisant, en effet, l'IA perd a chaque fois et c'est la que je bloque aujourd'hui, je n'ai qu'une partie du code en fait (ou dumoins un code incomplet). Après, compte tenu du fait que le temps de calcul peut etre tres long, l'IA (ou l'algorithme) ne doit pas necessairement et strictement etre IMBATTABLE mais plus competitive serait le mieux (ce qui garantirait moins d'utilisation de SRAM et moins de temps de calcul...)

Voila, j'espere vous aider... à m'aider :smile:
N'hesitez pas si ma réponse est incomplete ou si vous souhaitez plus d'informations !

FAIT !!!!
Après avoir passer quelques temps à lire sur internat des dizaines de sites internet et d'articles, j'ai reussi a obtenir une I.A. quasi-imbattable et qui joue tres methodiquement (en tout cas je n'est jamais reussi a battre mon microcontroleur. Lorsque je fait jouer deux arduino entre elles, elles font par contre un match nul ! Mais peu importe, sinon elle gagne tres souvent !!!!

Je vous met le code si vous voulez le tester (attention le moniteur serie doit etre configuré en 9600 et pas de fin de ligne :

 char joueur = 'X';
char emplacement[6][7] = {
  {' ', ' ', ' ', ' ', ' ', ' ', ' '},
  {' ', ' ', ' ', ' ', ' ', ' ', ' '},
  {' ', ' ', ' ', ' ', ' ', ' ', ' '},
  {' ', ' ', ' ', ' ', ' ', ' ', ' '},
  {' ', ' ', ' ', ' ', ' ', ' ', ' '},
  {' ', ' ', ' ', ' ', ' ', ' ', ' '}
};

void setup() {
  joueur = 'X';
  randomSeed(analogRead(A0));
  Serial.begin(9600);
  Serial.println(" ~~~~~~~~ PUISSANCE 4 ~~~~~~~~");
  Serial.println("");
  afficher_puissance();
  Serial.println("C'est à vous !");
}

void loop() {
  if (joueur == 'O') {
    // Tour de l'IA
    int colonne = meilleurCoup();
    int ligne;
    for (ligne = 5; ligne >= 0; ligne--) {
      if (emplacement[ligne][colonne] == ' ') {
        break;
      }
    }
    emplacement[ligne][colonne] = 'O';

    if (verifier_victoire(ligne, colonne, 'O')) {
      afficher_puissance();
      Serial.println("L'ordinateur a gagné !");
      while (1);
    } else {
      joueur = 'X';
      afficher_puissance();
      Serial.println("L'ordinateur a joué.");
      Serial.println("C'est à vous !");
    }
  }

  if (Serial.available() > 0) {
    String input = Serial.readStringUntil('\n');

    if (input.length() == 1 && isalpha(input.charAt(0))) {
      char lettre = tolower(input.charAt(0));
      int colonne = lettre - 'a';
      int ligne;
      for (ligne = 5; ligne >= 0; ligne--) {
        if (emplacement[ligne][colonne] == ' ') {
          break;
        }
      }

      if (colonne >= 0 && colonne < 7 && ligne >= 0 && ligne < 6) {
        emplacement[ligne][colonne] = joueur;

        if (verifier_victoire(ligne, colonne, joueur)) {
          afficher_puissance();
          Serial.println("Vous avez gagné !");
          while (1);
        } else {
          joueur = 'O';
        }
      } else {
        Serial.println("Coordonnées invalides / Case PLEINE !");
      }
    } else {
      Serial.println("Format de coordonnées invalide !");
    }
  }
}

void afficher_puissance() {
  Serial.println("");
  Serial.println("  ___________________________");
  Serial.println(" | A | B | C | D | E | F | G |");

  for (int i = 0; i < 6; i++) {
    Serial.print(" | ");
    for (int j = 0; j < 7; j++) {
      Serial.print(emplacement[i][j]);
      Serial.print(" | ");
    }
    Serial.println();
  }
}

bool verifier_victoire(int ligne, int colonne, char symbol) {
  char joueurGagnant = symbol;
  int compte = 0;

  // Vérification horizontale
  for (int i = -3; i <= 3; i++) {
    if (colonne + i >= 0 && colonne + i < 7 && emplacement[ligne][colonne + i] == joueurGagnant) {
      compte++;
      if (compte >= 4) return true;
    } else {
      compte = 0;
    }
  }

  // Vérification verticale
  compte = 0;
  for (int i = -3; i <= 3; i++) {
    if (ligne + i >= 0 && ligne + i < 6 && emplacement[ligne + i][colonne] == joueurGagnant) {
      compte++;
      if (compte >= 4) return true;
    } else {
      compte = 0;
    }
  }

  // Vérification diagonale (bas droite)
  compte = 0;
  for (int i = -3; i <= 3; i++) {
    if (ligne - i >= 0 && ligne - i < 6 && colonne + i >= 0 && colonne + i < 7 && emplacement[ligne - i][colonne + i] == joueurGagnant) {
      compte++;
      if (compte >= 4) return true;
    } else {
      compte = 0;
    }
  }

  // Vérification diagonale (bas gauche)
  compte = 0;
  for (int i = -3; i <= 3; i++) {
    if (ligne - i >= 0 && ligne - i < 6 && colonne - i >= 0 && colonne - i < 7 && emplacement[ligne - i][colonne - i] == joueurGagnant) {
      compte++;
      if (compte >= 4) return true;
    } else {
      compte = 0;
    }
  }

  return false;
}

int meilleurCoup() {
  int meilleurColonne = -1;
  int meilleurScore = -1000;

  for (int colonne = 0; colonne < 7; colonne++) {
    if (emplacement[0][colonne] == ' ') {
      int ligne;
      for (ligne = 5; ligne >= 0; ligne--) {
        if (emplacement[ligne][colonne] == ' ') {
          break;
        }
      }
      emplacement[ligne][colonne] = 'O';
      int score = minimax(4, false, -1000, 1000);
      emplacement[ligne][colonne] = ' ';

      if (score > meilleurScore) {
        meilleurScore = score;
        meilleurColonne = colonne;
      }
    }
  }

  return meilleurColonne;
}

int minimax(int profondeur, bool estMax, int alpha, int beta) {
  if (profondeur == 0 || verifier_match_nul()) {
    return evaluer();
  }

  if (estMax) {
    int meilleurScore = -1000;

    for (int colonne = 0; colonne < 7; colonne++) {
      if (emplacement[0][colonne] == ' ') {
        int ligne;
        for (ligne = 5; ligne >= 0; ligne--) {
          if (emplacement[ligne][colonne] == ' ') {
            break;
          }
        }
        emplacement[ligne][colonne] = 'O';
        int score = minimax(profondeur - 1, false, alpha, beta);
        emplacement[ligne][colonne] = ' ';

        meilleurScore = max(meilleurScore, score);
        alpha = max(alpha, meilleurScore);

        if (beta <= alpha) {
          break;
        }
      }
    }

    return meilleurScore;
  } else {
    int meilleurScore = 1000;

    for (int colonne = 0; colonne < 7; colonne++) {
      if (emplacement[0][colonne] == ' ') {
        int ligne;
        for (ligne = 5; ligne >= 0; ligne--) {
          if (emplacement[ligne][colonne] == ' ') {
            break;
          }
        }
        emplacement[ligne][colonne] = 'X';
        int score = minimax(profondeur - 1, true, alpha, beta);
        emplacement[ligne][colonne] = ' ';

        meilleurScore = min(meilleurScore, score);
        beta = min(beta, meilleurScore);

        if (beta <= alpha) {
          break;
        }
      }
    }

    return meilleurScore;
  }
}

int evaluer() {
  int score = 0;
  score += evaluer_lignes();
  score += evaluer_colonnes();
  score += evaluer_diagonales();
  return score;
}

int evaluer_lignes() {
  int score = 0;

  for (int ligne = 0; ligne < 6; ligne++) {
    for (int colonne = 0; colonne <= 3; colonne++) {
      int countO = 0;
      int countX = 0;
      for (int k = 0; k < 4; k++) {
        if (emplacement[ligne][colonne + k] == 'O') {
          countO++;
        } else if (emplacement[ligne][colonne + k] == 'X') {
          countX++;
        }
      }
      score += evaluer_case(countO, countX);
    }
  }

  return score;
}

int evaluer_colonnes() {
  int score = 0;

  for (int colonne = 0; colonne < 7; colonne++) {
    for (int ligne = 0; ligne <= 2; ligne++) {
      int countO = 0;
      int countX = 0;
      for (int k = 0; k < 4; k++) {
        if (emplacement[ligne + k][colonne] == 'O') {
          countO++;
        } else if (emplacement[ligne + k][colonne] == 'X') {
          countX++;
        }
      }
      score += evaluer_case(countO, countX);
    }
  }

  return score;
}

int evaluer_diagonales() {
  int score = 0;

  // Diagonales vers le bas à droite
  for (int ligne = 0; ligne <= 2; ligne++) {
    for (int colonne = 0; colonne <= 3; colonne++) {
      int countO = 0;
      int countX = 0;
      for (int k = 0; k < 4; k++) {
        if (emplacement[ligne + k][colonne + k] == 'O') {
          countO++;
        } else if (emplacement[ligne + k][colonne + k] == 'X') {
          countX++;
        }
      }
      score += evaluer_case(countO, countX);
    }
  }

  // Diagonales vers le bas à gauche
  for (int ligne = 0; ligne <= 2; ligne++) {
    for (int colonne = 3; colonne < 7; colonne++) {
      int countO = 0;
      int countX = 0;
      for (int k = 0; k < 4; k++) {
        if (emplacement[ligne + k][colonne - k] == 'O') {
          countO++;
        } else if (emplacement[ligne + k][colonne - k] == 'X') {
          countX++;
        }
      }
      score += evaluer_case(countO, countX);
    }
  }

  return score;
}

int evaluer_case(int countO, int countX) {
  if (countX == 0) {
    if (countO == 1) return 1;
    if (countO == 2) return 10;
    if (countO == 3) return 100;
    if (countO == 4) return 1000;
  } else if (countO == 0) {
    if (countX == 1) return -1;
    if (countX == 2) return -10;
    if (countX == 3) return -100;
    if (countX == 4) return -1000;
  }
  return 0;
}

bool verifier_match_nul() {
  for (int colonne = 0; colonne < 7; colonne++) {
    if (emplacement[0][colonne] == ' ') {
      return false;
    }
  }
  return true;
}

Je vous épargne toutes les modifications car je suis sur que vous les comprendrez tres facilement ! :smile:

Merci pour votre aide !!

bravo :slight_smile:

Quelques améliorations

  • afficher la grille quand le joueur a joué plutôt que d'attendre la réponse de l'ordinateur
  • recommencer automatiquement une partie à la fin
  • rendre tout paramétrable
  • modifier la saisie sur la console série (pas la peine de prendre des String, autant juste lire un caractère — ça permet de jouer jusqu'à une taille de 26 colonnes sans modifier le code de 'A' à 'Z')
  • passer à 115200 bauds parce que 9600 c'est has been :slight_smile:

Voici une version modifiée pour le fun :slight_smile:

const int nbLignes = 6;             // taille du plateau de jeu : lignes
const int nbColonnes = 7;           // taille du plateau de jeu : colonnes (26 max)              
const int nbAlignementGain = 4;     // combien de pions doit on aligner pour gagner 
const int profondeurAnalyse = 4;    // exploration de l'arbre des coups possibles

long tableDesPuissanceDe10[nbAlignementGain + 1];

enum {DEBUT, JOUEUR, AI, FIN } etat = DEBUT;

char emplacement[nbLignes][nbColonnes];

void raz() {
  for (int c = 0; c < nbColonnes; c++)
    for (int l = 0; l < nbLignes; l++)
      emplacement[l][c] = ' ';
}

void setup() {
  for (int i = 0; i < nbAlignementGain + 1; i++) tableDesPuissanceDe10[i] = (long) pow(10, i);
  randomSeed(analogRead(A0));
  Serial.begin(115200);
  Serial.println(" ~~~~~~~~ PUISSANCE 4 ~~~~~~~~");
  Serial.println("");
}

void loop() {
  switch (etat) {
    case DEBUT:
      raz();
      if (random(100) > 50) {
        afficher_puissance();
        Serial.println(F("Vous jouez en premier."));
        etat = JOUEUR;
      } else {
        Serial.print(F("Je commence → "));
        etat = AI;
      }
      break;

    case JOUEUR:
      { // Tour du joueur
        if (Serial.available() > 0) {
          char input = toupper(Serial.read());
          if (input >= 'A' && input < ('A' + nbColonnes)) {
            int colonne = input - 'A';
            int ligne;
            for (ligne = nbLignes - 1; ligne >= 0; ligne--) {
              if (emplacement[ligne][colonne] == ' ') {
                break;
              }
            }

            if (ligne >= 0) {
              emplacement[ligne][colonne] = 'X';
              afficher_puissance();

              if (verifier_victoire(ligne, colonne, 'X')) {
                Serial.println(F("Vous avez gagné !"));
                etat = FIN;
              } else {
                etat = AI;
              }
            } else {
              Serial.print(input);
              Serial.println(F(" → impossible, colonne pleine."));
            }
          } else {
            if (input != '\r' && input != '\n') {
              Serial.print(input);
              Serial.println(F(" → Colonne invalide."));
            }
          }
        }
        break;
      }

    case AI:
      { // Tour de l'IA

        Serial.print(F("\nJe réfléchis..."));
        int colonne = meilleurCoup();
        Serial.print(F(" → "));
        Serial.write('A' + colonne);
        Serial.println();

        int ligne;
        for (ligne = nbLignes - 1; ligne >= 0; ligne--) {
          if (emplacement[ligne][colonne] == ' ') {
            break;
          }
        }
        emplacement[ligne][colonne] = 'O';

        if (verifier_victoire(ligne, colonne, 'O')) {
          afficher_puissance();
          Serial.println(F("J'ai gagné !"));
          etat = FIN;
        } else {
          etat = JOUEUR;
          afficher_puissance();
          Serial.println(F("C'est à vous !"));
        }
        break;
      }

    case FIN:
      Serial.println(F("\n\n-----------------------------\nAllez, On refait une partie !\n\n"));
      etat = DEBUT;
      break;
  }


}

void afficher_puissance() {
  Serial.print("  ");
  for (int j = 0; j < nbColonnes; j++) {
    Serial.write("____");
  }
  Serial.println();

  Serial.print(" | ");
  for (int j = 0; j < nbColonnes; j++) {
    Serial.write('A' + j);
    Serial.print(" | ");
  }
  Serial.println();

  for (int i = 0; i < nbLignes; i++) {
    Serial.print(" | ");
    for (int j = 0; j < nbColonnes; j++) {
      Serial.print(emplacement[i][j]);
      Serial.print(" | ");
    }
    Serial.println();
  }

  Serial.flush(); // on s'assure que tout est affiché

}

bool verifier_victoire(int ligne, int colonne, char symbol) {
  char joueurGagnant = symbol;
  int compte = 0;

  // Vérification horizontale
  for (int i = -nbAlignementGain + 1; i <= nbAlignementGain - 1; i++) {
    if (colonne + i >= 0 && colonne + i < nbColonnes && emplacement[ligne][colonne + i] == joueurGagnant) {
      compte++;
      if (compte >= nbAlignementGain) return true;
    } else {
      compte = 0;
    }
  }

  // Vérification verticale
  compte = 0;
  for (int i = -nbAlignementGain + 1; i <= nbAlignementGain - 1; i++) {
    if (ligne + i >= 0 && ligne + i < nbLignes && emplacement[ligne + i][colonne] == joueurGagnant) {
      compte++;
      if (compte >= nbAlignementGain) return true;
    } else {
      compte = 0;
    }
  }

  // Vérification diagonale (bas droite)
  compte = 0;
  for (int i = -3; i <= 3; i++) {
    if (ligne - i >= 0 && ligne - i < nbLignes && colonne + i >= 0 && colonne + i < nbColonnes && emplacement[ligne - i][colonne + i] == joueurGagnant) {
      compte++;
      if (compte >= nbAlignementGain) return true;
    } else {
      compte = 0;
    }
  }

  // Vérification diagonale (bas gauche)
  compte = 0;
  for (int i = -3; i <= 3; i++) {
    if (ligne - i >= 0 && ligne - i < nbLignes && colonne - i >= 0 && colonne - i < nbColonnes && emplacement[ligne - i][colonne - i] == joueurGagnant) {
      compte++;
      if (compte >= nbAlignementGain) return true;
    } else {
      compte = 0;
    }
  }

  return false;
}

int meilleurCoup() {
  int meilleurColonne = -1;
  long meilleurScore = -1000;

  for (int colonne = 0; colonne < nbColonnes; colonne++) {
    if (emplacement[0][colonne] == ' ') {
      int ligne;
      for (ligne = nbLignes - 1; ligne >= 0; ligne--) {
        if (emplacement[ligne][colonne] == ' ') {
          break;
        }
      }
      emplacement[ligne][colonne] = 'O';
      long score = minimax(profondeurAnalyse, false, -1000, 1000);
      emplacement[ligne][colonne] = ' ';

      if (score > meilleurScore) {
        meilleurScore = score;
        meilleurColonne = colonne;
      }
    }
  }

  return meilleurColonne;
}

long minimax(int profondeur, bool estMax, long alpha, long beta) {
  if (profondeur == 0 || verifier_match_nul()) {
    return evaluer();
  }

  if (estMax) {
    long meilleurScore = -1000;

    for (int colonne = 0; colonne < nbColonnes; colonne++) {
      if (emplacement[0][colonne] == ' ') {
        int ligne;
        for (ligne = nbLignes - 1; ligne >= 0; ligne--) {
          if (emplacement[ligne][colonne] == ' ') {
            break;
          }
        }
        emplacement[ligne][colonne] = 'O';
        long score = minimax(profondeur - 1, false, alpha, beta);
        emplacement[ligne][colonne] = ' ';

        meilleurScore = max(meilleurScore, score);
        alpha = max(alpha, meilleurScore);

        if (beta <= alpha) {
          break;
        }
      }
    }

    return meilleurScore;
  } else {
    long meilleurScore = 1000;

    for (int colonne = 0; colonne < nbColonnes; colonne++) {
      if (emplacement[0][colonne] == ' ') {
        int ligne;
        for (ligne = nbLignes - 1; ligne >= 0; ligne--) {
          if (emplacement[ligne][colonne] == ' ') {
            break;
          }
        }
        emplacement[ligne][colonne] = 'X';
        long score = minimax(profondeur - 1, true, alpha, beta);
        emplacement[ligne][colonne] = ' ';

        meilleurScore = min(meilleurScore, score);
        beta = min(beta, meilleurScore);

        if (beta <= alpha) {
          break;
        }
      }
    }

    return meilleurScore;
  }
}

long evaluer() {
  long score = 0;
  score += evaluer_lignes();
  score += evaluer_colonnes();
  score += evaluer_diagonales();
  return score;
}

long evaluer_lignes() {
  long score = 0;

  for (int ligne = 0; ligne < nbLignes; ligne++) {
    for (int colonne = 0; colonne <= nbColonnes - nbAlignementGain; colonne++) {
      int countO = 0;
      int countX = 0;
      for (int k = 0; k < nbAlignementGain; k++) {
        if (emplacement[ligne][colonne + k] == 'O') {
          countO++;
        } else if (emplacement[ligne][colonne + k] == 'X') {
          countX++;
        }
      }
      score += evaluer_case(countO, countX);
    }
  }

  return score;
}

long evaluer_colonnes() {
  long score = 0;

  for (int colonne = 0; colonne < nbColonnes; colonne++) {
    for (int ligne = 0; ligne <= nbLignes - nbAlignementGain; ligne++) {
      int countO = 0;
      int countX = 0;
      for (int k = 0; k < nbAlignementGain; k++) {
        if (emplacement[ligne + k][colonne] == 'O') {
          countO++;
        } else if (emplacement[ligne + k][colonne] == 'X') {
          countX++;
        }
      }
      score += evaluer_case(countO, countX);
    }
  }

  return score;
}

long evaluer_diagonales() {
  long score = 0;

  // Diagonales vers le bas à droite
  for (int ligne = 0; ligne <= nbLignes - nbAlignementGain; ligne++) {
    for (int colonne = 0; colonne <= nbColonnes - nbAlignementGain; colonne++) {
      int countO = 0;
      int countX = 0;
      for (int k = 0; k < nbAlignementGain; k++) {
        if (emplacement[ligne + k][colonne + k] == 'O') {
          countO++;
        } else if (emplacement[ligne + k][colonne + k] == 'X') {
          countX++;
        }
      }
      score += evaluer_case(countO, countX);
    }
  }

  // Diagonales vers le bas à gauche
  for (int ligne = 0; ligne <= nbLignes - nbAlignementGain; ligne++) {
    for (int colonne = nbAlignementGain - 1; colonne < nbColonnes; colonne++) {
      int countO = 0;
      int countX = 0;
      for (int k = 0; k < nbAlignementGain; k++) {
        if (emplacement[ligne + k][colonne - k] == 'O') {
          countO++;
        } else if (emplacement[ligne + k][colonne - k] == 'X') {
          countX++;
        }
      }
      score += evaluer_case(countO, countX);
    }
  }

  return score;
}

long evaluer_case(int countO, int countX) {
  if (countX == 0) {
    if (countO >= 1 && countO <= nbAlignementGain) return +tableDesPuissanceDe10[countO - 1];
  } else if (countO == 0) {
    if (countX >= 1 && countX <= nbAlignementGain) return -tableDesPuissanceDe10[countX - 1];
  }
  return 0;
}

bool verifier_match_nul() {
  for (int colonne = 0; colonne < nbColonnes; colonne++) {
    if (emplacement[0][colonne] == ' ') {
      return false;
    }
  }
  return true;
}

Cool : mais, j'ai gagné à la première partie ! :grin:

  ____________________________
 | A | B | C | D | E | F | G | 
 |   |   |   |   |   |   |   | 
 |   |   | X | O |   |   |   | 
 |   |   | O | X |   |   |   | 
 | O |   | X | O |   |   |   | 
 | X |   | O | O |   | O | O | 
 | X |   | O | X | X | X | X | 
Vous avez gagné !

Je pense qu'il commence toujours par la colonne C, car c'est elle qui optimise sa fonction de cout. Il faudrait l'autoriser à commencer par d'autres cases, pour varier un peu son jeu.

Oui jai eu aussi ce cas où il ne bloque pas le gain en G et oui on pourrait ajouter un peu d'aléa pour le premier coup C ou D sans doute sont des cases fortes

Avec une plus jolie fonction d'affichage pour représenter le tableau de jeu

┌───┬───┬───┬───┬───┬───┬───┐
│ A │ B │ C │ D │ E │ F │ G │ 
├───┼───┼───┼───┼───┼───┼───┤
│   │   │   │   │   │   │   │ 
│   │   │   │   │   │   │   │ 
│   │   │   │   │   │   │   │ 
│   │   │   │   │   │   │   │ 
│   │   │ O │   │ O │   │   │ 
│ X │   │ X │ O │ X │   │   │ 
└───┴───┴───┴───┴───┴───┴───┘

void afficher_puissance() {
  Serial.print("┌");
  for (int j = 0; j < nbColonnes - 1; j++) {
    Serial.write("───┬");
  }
  Serial.println("───┐");

  Serial.print("│ ");
  for (int j = 0; j < nbColonnes; j++) {
    Serial.write('A' + j);
    Serial.print(" │ ");
  }
  Serial.println();

  Serial.print("├");
  for (int j = 0; j < nbColonnes - 1; j++) {
    Serial.write("───┼");
  }
  Serial.println("───┤");

  for (int i = 0; i < nbLignes; i++) {
    Serial.print("│ ");
    for (int j = 0; j < nbColonnes; j++) {
      Serial.print(emplacement[i][j]);
      Serial.print(" │ ");
    }
    Serial.println();
  }

  Serial.print("└");
  for (int j = 0; j < nbColonnes - 1; j++) {
    Serial.write("───┴");
  }
  Serial.println("───┘");

  Serial.flush(); // on s'assure que tout est affiché

}
1 Like

Reponse à lesept : "Cool : mais, j'ai gagné à la première partie ! :grin:" :

Aie... Mais ce n'est pas grave l'idée n'est pas que l’Arduino soit imbattable mais qu'on puisse espérer gagner quand même pour vraiment s'amuser (quand on est tout seul :wink: ), meme s'il est vrai que si on joue TOUJOURS de la meme maniere, on aura TOUJOURS le meme résultat :sweat_smile:...

Pas mal... Merci JML pour cette amelioration, mon idée pour plus tard sera d'afficher la grille sur un écran OLED ou LCD et de données les entrés a la carte avec un 'keypad' pour eviter d'avoir un ordi lors du fonctionnement !

Pense à un écran tactile

On pourrait avoir des bandeaux de LEDs et un encodeur rotatif.

les bandeaux représentaient le tableau (en colonnes)

On a un bandeau horizontal en plus en haut qui permet à l'utilisateur de choisir une colonne (en tournant l'encodeur dans un sens ou l'autre) et faire tomber son pion en appuyant sur l'encodeur, il y aurait une petite animation graphique où le pion irait se poser au bas de la colonne.

Pendant que l'IA réfléchi son pion de couleur se balade sur la ligne du haut en fonction du meilleur coup mis à jour par le minimax

lors du gain les LEDs deviennent moins brillantes sauf la ligne gagnante qui clignote.

ce serait cool :slight_smile:

➜ il y a tout ce qu'il faut pour faire cela sur Wokwi :slight_smile:

Actuellement je n'ai pas de bandeau de LED, mais c'est une idée super !! Je vais réfléchir au code et en commander un si ça vaut la peine, merci pour cette super idée J-M-L !!!
Et il est vrai que l'écran tactile n'est pas une si mauvaise idée non plus...

A voir... :wink:

C'est pour ça qu'avant d'acheter quoi que ce soit, tu peux tester ton programme et le matériel associé sur le simulateur wokwi

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.