Minimax simple example
Table des matières
1. Présentation du problème
A) Entrée
On nous fournit:
- Un entier
N
qui correspond au nombre de lettres dans la pile; - Un entier
Q
qui correspond au nombre de mots de score disponibles; - Une liste de
N
lettres toutes différentes, séparées par des espaces; Q
lignes qui contiennent chacune un mot composé des lettres disponibles, ainsi que son score associé.
B) But du jeu
Deux joueurs s’affrontent en récupérant, tour à tour, une lettre parmi celles disponibles dans une pile. À chaque tour, ils peuvent choisir de prendre soit la première lettre, soit la deuxième. Lors du dernier tour, le dernier joueur n’a pas d’autre choix que de prendre la dernière lettre restante. Une fois toutes les lettres récupérées, les joueurs marquent des points en formant des mots à partir de leurs lettres collectées. Un dictionnaire donné indique les mots valides ainsi que leur score. Les lettres peuvent être utilisées plusieurs fois pour créer plusieurs mots.
L’objectif est de conseiller le premier joueur sur le choix optimal de sa première lettre afin de maximiser son avantage sur son adversaire. Pour cela, il faut :
- Simuler les différents choix possibles et leurs conséquences.
- Estimer les scores finaux des joueurs en utilisant une approche stratégique optimale (Minimax, et potentiellement avec l’élagage alpha-bêta pour améliorer l’efficacité du calcul).
- Déterminer la meilleure lettre à choisir en premier.
L’objectif du premier joueur est d’obtenir la plus grande différence positive entre son score et celui de son adversaire.
C) Sortie
Il faut afficher le résultat sous la forme : lettre choisie, suivi du score attendu pour chaque joueur (S1-S2).
2. Ma méthode de résolution
A) Approche synoptique
J’ai utilisé un algorithme permettant d’explorer toutes les options possibles et d’évaluer leur impact sur le score final. Afin d’optimiser cette analyse et éviter de tester des scénarios inutiles, j’applique une méthode de filtrage qui réduit les calculs. Grâce à cette simulation complète, j’ai identifier le meilleur choix de lettre pour le premier joueur, garantissant le score le plus avantageux possible, avant d’afficher la solution finale.
B) Approche algorithmique et explication de mon programme
Je commence d’abord par créer deux tructures de données pour organiser les informations ainsi que pour manipuler facilement les données du jeu :
wscore_t
stocke les mots du dictionnaire et leur score.pscore_t
contient les scores des deux joueurs.
Ensuite je crée une première fonction de calcul des scores (score
) qui permet de parcourir le dictionnaire pour vérifier quels mots peuvent être construits avec les lettres collectées par un joueur. Pour cela, elle commence par initialiser le score à zéro, elle vérifie ensuite que chaque lettre d’un mot du dictionnaire est bien présente dans la chaîne resultat
grâce à strchr()
et enfin elle ajoute le score du mot si toutes ses lettres sont présentes. La fonction strchr()
est très intéressante, en effet elle est inclut dans la librairie string.h, elle permet, en prennant comme argument une chaine de caractère et un caractère, de retourner un pointeur sur la première occurence du caractère passé en argument dans la chaine de caractère également passé en argument de la fonction.
La fonction score
retourne pour finir le score total obtenu par le joueur.
J’implémente aussi une fonction qui permet la suppression d’une lettre dans la pile (supprimerStr
). En effet, lorsque le joueur sélectionne une lettre, elle doit être retirée de la pile. J’utilise donc la fonction memmove()
qui décale les caractères à gauche pour supprimer la lettre choisie.
Je finis par implémenter ma dernière fonction qui est l’algorithme Minimax avec élagage alpha-bêta (minmax
). Cette fonction est le cœur du programme. Elle simule chaque tour en analysant toutes les possibilités jusqu’à la fin du jeu. Il y a plusieurs possibilités:
- Le cas de base lorsqu’il ne reste plus de lettres. Dans ce cas là, le programme calcule les scores des joueurs et retourne le résultat.
- Ensuite, vient le tour du joueur 1 qui correspond à la partie où on effectue la maximisation de son score par rapport à celui de son adversaire. Pour chaque choix possible, on copie temporairement la pile et les lettres du joueur, on ajoute la lettre choisie à la chaîne du joueur, puis on supprime la lettre de la pile. A la suite de tout cela, on effectue un appel récursif
minmax()
avec les nouvelles données, si le score obtenu est meilleur que le précédent, il est conservé, et si concernant la profondeur:depth = 0
, l’algorithme sauvegarde la meilleure première lettre (*bestChoice
). - Puis, c’est au tour du joueur 2 (minimisation du score du joueur 1), le programme essaie de réduire l’écart en choisissant la lettre la moins favorable à l’adversaire. Le programme suis la même logique que pour le joueur 1, mais ici, il sélectionne le pire résultat pour son adversaire.
- Pour finir, il reste l’élagage alpha-bêta qui permet d’arréter la recherche au bon moment. De ce fait, pour éviter de tester des choix inutiles, on utilise deux bornes (
alpha
etbeta
) :alpha
représente la meilleure valeur trouvée pour le joueur maximisant.beta
représente la meilleure valeur trouvée pour le joueur minimisant.- Si
beta <= alpha
, on coupe la recherche dans cet arbre.
Concernant le main
, il lit d’abord les entrées, puis appel la fonction minmax()
avec les lettres initiales et une chaîne vide pour chaque joueur pour rechercher la meilleure première lettre (bestChoice
). Puis, il finit par afficher la lettre optimale et les scores finaux.
#include <stdlib.h> #include <stdio.h> #include <string.h> #include <stdbool.h> typedef struct word_score { int score; char word[27]; } wscore_t; typedef struct player_score { int score1; int score2; } pscore_t; int score(char resultat[], wscore_t dict[], int q) { int points = 0; for (int i = 0; i < q; i++) { bool valid = true; for (int j = 0; j < strlen(dict[i].word); j++) { if (strchr(resultat, dict[i].word[j]) == NULL) { valid = false; break; } } if (valid) { points += dict[i].score; } } return points; } void supprimerStr(char *str, int n) { memmove(&str[n], &str[n+1], strlen(str) - n); } pscore_t minmax(char lettres[], int alpha, int beta, int play, char p1[], char p2[], wscore_t dict[], int q, int depth, char *bestChoice) { if (strlen(lettres) == 0) { return (pscore_t){score(p1, dict, q), score(p2, dict, q)}; } pscore_t bestMove; char tempLetters[27], tempP1[27], tempP2[27]; if (play) { bestMove.score1 = 0; bestMove.score2 = 65535; for (int i = 0; i < 2; i++) { strcpy(tempLetters, lettres); strcpy(tempP1, p1); sprintf(tempP1, "%s%c", p1, tempLetters[i]); supprimerStr(tempLetters, i); pscore_t result = minmax(tempLetters, alpha, beta, 0, tempP1, p2, dict, q, depth + 1, bestChoice); if ((result.score1 - result.score2) > (bestMove.score1 - bestMove.score2)) { bestMove = result; if (depth == 0) { *bestChoice = lettres[i]; } } alpha = (bestMove.score1 - bestMove.score2) > alpha ? (bestMove.score1 - bestMove.score2) : alpha; if (beta <= alpha) break; } } else { bestMove.score1 = 65535; bestMove.score2 = 0; for (int i = 0; i < 2; i++) { strcpy(tempLetters, lettres); strcpy(tempP2, p2); sprintf(tempP2, "%s%c", p2, tempLetters[i]); supprimerStr(tempLetters, i); pscore_t result = minmax(tempLetters, alpha, beta, 1, p1, tempP2, dict, q, depth + 1, bestChoice); if ((result.score1 - result.score2) < (bestMove.score1 - bestMove.score2)) { bestMove = result; } beta = (bestMove.score1 - bestMove.score2) < beta ? (bestMove.score1 - bestMove.score2) : beta; if (beta <= alpha) break; } } return bestMove; } int main() { int n, q; char letters[27] = ""; scanf("%d%d", &n, &q); for (int i = 0; i < n; i++) { char letter[2]; scanf("%s", letter); sprintf(letters, "%s%c", letters, letter[0]); } wscore_t dict[100]; for (int i = 0; i < q; i++) { char word[27]; int score; scanf("%s%d", word, &score); dict[i].score = score; strcpy(dict[i].word, word); } char bestChoice = ' '; pscore_t finalScore = minmax(letters, -65535, 65535, 1, "", "", dict, q, 0, &bestChoice); printf("%c %d-%d\n", bestChoice, finalScore.score1, finalScore.score2); return 0; }
3. Un autre programme
#include <stdlib.h> #include <stdio.h> #include <string.h> // ----------------------------------------------------------- int n; int q; char letters[26]; char words[100][11]; // 11 might need documenting int hwords[100]; int scores[100]; char bestChoice; // ----------------------------------------------------------- typedef struct { int score1, score2; } game; // ----------------------------------------------------------- int h1,h2; #define hash(_H,_c) _H |= 1 << ( _c - 'A') int hashWord(const char * w) { int h = 0; char c; while ( c = *w++) { hash(h,c); } return h; } // ----------------------------------------------------------- int eval(game * g){ g->score1 = 0; g->score2 = 0; for (int i= 0; i < q; i++) { int h = hwords[i]; if ( (h & h1) == h) g->score1 += scores[i]; if ( (h & h2) == h) g->score2 += scores[i]; } return g->score1 - g->score2; } // ----------------------------------------------------------- int mini(game *g, int i, int j , int alpha, int beta){ if (i == n) { return eval(g); } game ga; int hsave = h2; hash( h2, letters[i]) ; int va = maxi(&ga, j, j+1, alpha, beta); h2 = hsave; if ( va < beta ) beta = va ; if ( beta < alpha ) { // cutoff *g = ga; return va; } if (j == n) { *g = ga; return va; } game gb; hash( h2, letters[j]) ; int vb = maxi(&gb,i,j+1, alpha, beta); h2 = hsave; if (va < vb){ *g = ga; return va; } *g = gb; return vb ; } // ----------------------------------------------------------- int maxi(game *g, int i, int j, int alpha, int beta ){ if (i == n) { return eval(g); } int hsave = h1; game ga; hash( h1, letters[i]) ; int va = mini(&ga, j, j+1, alpha, beta); h1 = hsave; if ( va > alpha ) alpha = va ; if ( beta < alpha ) { // cutoff *g = ga; return va; } if (j == n) { *g = ga; return va; } game gb; hash( h1, letters[j]) ; int vb = mini(&gb, i, j+1, alpha, beta); h1 = hsave; if (va >= vb){ *g = ga; return va; } if (i==0) bestChoice = letters[j]; *g = gb; return vb ; } // ----------------------------------------------------------- main() { scanf("%d%d", &n, &q); int H = 0; for (int i = 0; i < n; i++) { char letter[2]; scanf("%s", letter); letters[i] = letter[0]; hash(H, letters[i]); } for (int i = 0; i < q; i++) { scanf("%s%d", words[i], &scores[i]); hwords[i] = hashWord(words[i]); } game g; bestChoice = letters[0]; int score = maxi(&g, 0, 1, -100000, +100000); printf("%c %d-%d\n", bestChoice, g.score1, g.score2); }
Ce programme utilise Minimax avec élagage alpha-bêta, comme le mien, mais encode les lettres et mots sous forme de masques de bits pour accélérer la vérification des mots valides. Il manipule directement ces masques plutôt que des chaînes, ce qui réduit les opérations coûteuses. La sélection du meilleur choix de lettre pour le premier joueur est intégrée dans le parcours de l’arbre, évitant des comparaisons additionnelles en fin d’exécution. Mon approche est plus intuitive avec des chaînes car j’ai implémentée la fonction comme je le ferai dans ma tête ou sur une feuille, tandis que celle-ci privilégie une gestion optimisée des données pour un calcul plus rapide.
Vous trouverez ici la page de téléchargementdu du fichier orgmode concernant le rapport de minimax simple example.