我为connect4游戏制作了一个minmax算法,该算法非常不稳定。 在第一步中,它可以轻松地与用户抗争并预测用户何时可以获胜。 经过一些交流之后,它开始跌跌撞撞,在那里它无法直接赢取价值,也无法做出有助于玩家获胜的举动,我拍了张照片以显示AI wrong move
的样子我看到了很多有关minmax算法的文档,并且不知道该怎么做。感谢您的帮助! :)
西蒙(Simon)
* Find the best move to do
* @param game game table
* @param depth depth to be explored by the algorithm
* @param turnsLeft number of turns before a drow
* @param maximizingPlayer true if it's AI's turn
* @return the column of the best move and its value
*/
private static minimaxResult miniMax(Puissance4Struct game, int depth, int turnsLeft, boolean maximizingPlayer) {
minimaxResult minimaxResult = new minimaxResult();
if (game.verifWinner()) {
//game.afficherPuissance4();
if (game.IATurn()) {
//System.out.println("Je vois une victoire pour l'ordinateur");
minimaxResult.setValue(100000);
} else {
//System.out.println("Je vois une victoire pour le joueur");
minimaxResult.setValue(-100000);
}
return minimaxResult;
}
if (depth == 0 || turnsLeft == 0) {
minimaxResult.setValue(game.tableEvaluation());
return minimaxResult;
}
if (maximizingPlayer) {
int maxValue = -10000;
int maxColon = 0;
minimaxResult.setValue(maxValue);
for (int col = 0; col < game.getWidthTable(); col++) {
if (game.findIndex(col)) {
game.setToken(Puissance4Struct.getAIToken());
game.putToken();
minimaxResult = miniMax(game, depth-1, turnsLeft-1, false);
game.eraseTokenColon(col);
if (maxValue < minimaxResult.getValue()) {
maxValue = minimaxResult.getValue();
maxColon = col;
}
}
}
minimaxResult.setColonne(maxColon);
minimaxResult.setValue(maxValue);
} else {
int minValue = 100000;
int minColonne = 0;
minimaxResult.setValue(minValue);
for (int col = 0; col < game.getWidthTable(); col++) {
if (game.findIndex(col)) {
game.setToken(Puissance4Struct.getPlayerToken());
game.putToken();
minimaxResult = miniMax(game, depth-1, turnsLeft-1, true);
game.eraseTokenColon(col);
if (minValue > minimaxResult.getValue()) {
minValue = minimaxResult.getValue();
minColonne = col;
}
}
}
minimaxResult.setColonne(minColonne);
minimaxResult.setValue(minValue);
game.eraseToken();
}
game.changeToken();
return minimaxResult;
}
如有需要,这是游戏类:
public static void main(String[] args) {
Puissance4Struct game = new Puissance4Struct();
minimaxResult minimaxResult;
int input;
int numberMovesPlayed = 0;
int maximumMoves = game.getNombreCases();
boolean winner = false;
boolean AITurn = false;
System.out.println("Do you want to play against an AI ? \n1: yes | 2: no");
input = (int) Verif.Input(1, 2);
if (input == 1) { // JCAI
// Initialise the static token reference for player and AI
Puissance4Struct.setJetonJoueur(game.getToken());
game.changeToken();
Puissance4Struct.setJetonAI(game.getToken());
while (!winner && numberMovesPlayed < maximumMoves) {
if (AITurn) {
game.setToken(Puissance4Struct.getAIToken());
minimaxResult = miniMax(game, 4, maximumMoves - numberMovesPlayed, true);
game.setToken(Puissance4Struct.getAIToken());
game.findIndex(minimaxResult.getColonne());
game.putToken();
++numberMovesPlayed;
if (numberMovesPlayed >= 7) {
winner = game.verifWinner();
}
AITurn = false;
} else {
game.setToken(Puissance4Struct.getPlayerToken());
player(game);
game.putToken();
numberMovesPlayed++;
if (numberMovesPlayed >= 7) {
winner = game.verifWinner();
}
AITurn = true;
}
}
game.printConnect4();
} else { // JCJ
while (!winner && numberMovesPlayed < maximumMoves) {
player(game);
numberMovesPlayed++;
game.putToken();
if (numberMovesPlayed >= 7) {
winner = game.verifWinner();
}
System.out.println();
game.printConnect4();
}
}
if (winner) {
System.out.println("\nBravo au joueur " + game.getToken() + " pour avoir gagné en " + numberMovesPlayed + " tours !");
} else {
System.out.println("c'est un match nul. Quelle intensité !");
}
}
和游戏板类:
package puissance4;
class Puissance4Struct {
private char [][] tableau = new char[7][7];
private int[] indice = new int[2];
private char jeton;
private static char jetonAI;
private static char jetonJoueur;
Puissance4Struct() {
jeton = 'X';
initialisationTableau();
}
static char getPlayerToken() {
return jetonJoueur;
}
static void setJetonJoueur(char jetonJoueur) {
if (jetonJoueur == 'O' || jetonJoueur == 'X') {
Puissance4Struct.jetonJoueur = jetonJoueur;
}
}
int getNombreCases() {
return tableau.length * tableau[0].length;
}
int getWidthTable() {
return tableau[0].length;
}
/**
* Place l'indice sur la première ligne possible d'une colonne choisie. Renvoie faux s'il n'y a aps de colonne possible.
* @param colonne l'indice de la colonne.
* @return <code>false</code> s'il n'y a pas de colonne possible.
*/
boolean findIndex(int colonne) {
int i = 0;
while (i < tableau.length && tableau[i][colonne] == ' ') {
i++;
}
if (i != 0) {
indice[0] = i - 1;
indice[1] = colonne;
return true;
}
else {
return false;
}
}
/**
* Attribue des valeur à la variable indice
* @param ligne ligne du puissance4
* @param colonne colonne du puisance 4
*/
private void setIndice(int ligne, int colonne) {
indice[0] = ligne;
indice[1] = colonne;
}
/**
* Copie de setIndice, mais ne modifie pas la variable indice de l'objet
* @param colonne l'indice de la colonne
* @return retourne le nombre de ligne de la colonne passée en argument. (est-il nécessaire de retourner une valeur?
* ('void' est peut-être plus approprié)
*/
private int getIndiceLigne(int colonne) {
int i = 0;
while (i < tableau.length - 1 && tableau[i][colonne] == ' ') {
i++;
}
return i;
}
char getToken() {
return jeton;
}
void setToken(char jeton) {
if (jeton == 'X' || jeton == 'O')
this.jeton = jeton;
}
void changeToken() {
if (jeton == 'O')
jeton = 'X';
else {
jeton = 'O';
}
}
private void initialisationTableau() {
for (int i = 0; i < tableau.length; i++) {
for (int j = 0; j < tableau[0].length; j++) {
tableau[i][j] = ' ';
}
}
}
void printConnect4() {
for (int i = 0; i < tableau[0].length; i++) // Affiche les nombres au dessus des colonnes
if (i < 10)
System.out.print(" "+ (i+1));
else
System.out.print(" "+ (i+1));
System.out.println();
for (char[] chars : tableau) { // Affichage du puissance 4
for (int j = 0; j < tableau[0].length; j++) {
System.out.print(" | " + chars[j]);
}
System.out.println(" |");
System.out.print(" ");
for (int k = 0; k < tableau[0].length; k++)
System.out.print("----");
System.out.println("-");
}
}
void putToken() {
tableau[indice[0]][indice[1]] = jeton;
}
/**
* Procède aux vérifications permettant de savoir si le joueur a gagné.
* @return <code>true</code> si le joueur a gagné.
*/
boolean verifWinner() {
// Le vectorValeur faisant à chaque fois la vérification du vecteur opposé,
// il n'est pas nécéssaire de mettre les 8 directions.
for (int k = -1; k <= 1; k++) {
if (vectorValeur( k, -1) >= 4)
return true;
}
return vectorValeur(1, 0) >= 4;
}
/**
* Donne le nombre de même jetons sur un sens de l'indice
* @param vectorI direction verticale du vecteur donné
* @param vectorJ direction horizontale du vecteur donné
* @return le nombre de même jeton dans le sens donné
*/
private int vectorValeur(int vectorI, int vectorJ) {
int recurrence = 0;
int i = indice[0];
int j = indice[1];
int edgeI = calculEdgePoint(vectorI, 'i'), edgeJ = calculEdgePoint(vectorJ, 'j');
for (int k = 0; k < 2; k++) {
do {
i += vectorI;
j += vectorJ;
recurrence++;
} while (i != edgeI && j != edgeJ && tableau[i][j] == jeton);
if (k == 0) {
i = indice[0];
j = indice[1];
vectorI = -vectorI; // Continue à chercher dans la direction opposée, c'est la raison pour le For avec k
vectorJ = -vectorJ;
edgeI = calculEdgePoint(vectorI, 'i');
edgeJ = calculEdgePoint(vectorJ, 'j');
}
}
recurrence -= 1;
return recurrence;
}
/**
* @param vectorPoint donne la case de l'indice
* @param indice définit si c'est un point i ou j
* @return le point prime que i ou j ne devra pas dépasser
*/
private int calculEdgePoint(int vectorPoint, char indice) { // Donne la valeur pour i ou j à ne pas dépasser
if (vectorPoint == -1 || vectorPoint == 0) {
return -1;
} else if (indice == 'i') {
return tableau.length;
} else {
return tableau[0].length;
}
}
/**
* Donne la valeur de la combinaison testée.
* @return la valeur de la combinaison.
*/
private int verifTailleCombinaison() {
int valCombinaisons = 0;
for (int k = -1; k <= 1; k = k + 2) {
valCombinaisons += vectorValeur(k, -1);
}
valCombinaisons += vectorValeur(1, 0);
return valCombinaisons;
}
/**
* Indique si le tour actuel est celui de l'IA
* @return <code>true</code> si le jeton utilisé correspond à celui de l'IA
*/
boolean IATurn() {
return getToken() == getAIToken();
}
/**
* Donne une évaluation du tableau actuelle.
* Elle est négative ou positive si le tableau est favorable pour l'humain ou l'IA respectivement
* @return l'évaluation du tableau.
*/
int tableEvaluation() {
int baseI = indice[0], baseJ = indice[1];
int score = 0;
for (int ligne = 0; ligne < tableau.length; ligne++) {
for (int colonne = 0; colonne < tableau[0].length; colonne++) {
if (findIndex(colonne)) {
putToken();
score += verifTailleCombinaison();
eraseTokenColon(colonne);
}
}
}
changeToken();
for (int col = 0; col < tableau.length; col++) {
if (findIndex(col)) {
putToken();
score -= verifTailleCombinaison();
eraseTokenColon(col);
}
}
changeToken();
setIndice(baseI, baseJ);
return score;
}
/**
* Efface le jeton de la colonne choisie
* @param colonne la colonne choisie
*/
void eraseTokenColon(int colonne) {
int ligne = getIndiceLigne(colonne);
tableau[ligne][colonne] = ' ';
}
/**
* Efface le jeton actuellement sélectionné par l'indice
*/
void eraseToken() {
tableau[indice[0]][indice[1]] = ' ';
}
static char getAIToken() {
return jetonAI;
}
static void setJetonAI(char jetonAI) {
if (jetonAI == 'X' || jetonAI == 'O') {
Puissance4Struct.jetonAI = jetonAI;
}
}
}