MiniMax Alogrithmus

hitman52

Mitglied
Hi!
Ich habe TicTacToe in Java programmiert. Jetzt bin ich dabei meine KI zu verbessern. Auf der Suche nach weiteren Informationen stolpert man zwangsläufig über den Minimax-Algorithmus. Jetzt hapert es da aber noch an der Umsetzung.

Die Bewertungsfunktion hab ich einfach gehalten (Sieg= +1, Niederlage= -1 und Unentschieden= 0), da dürfte das Problem aber auch nicht liegen (oder?). Der Computer reagiert auch auf meine Züge, er spielt jedoch verdammt schlecht. Eigentlich sollte er mit dieser Bewertungsfunktion aber perfekt spielen:

Zitat Wikipedia:
Eine ideale Bewertungsfunktion ordnet einer Stellung den Wert +1 zu, wenn Spieler A gewinnt,
und den Wert -1, wenn Spieler B gewinnt, und 0 bei Unentschieden. Kann man von sämtlichen
Spielpositionen den Suchbaum bis zur maximalen Tiefe aufbauen (bis zur Endstellung, wo man
sieht, wer gewinnt), so spielt der Algorithmus ein perfektes Spiel.

Daraus schließe ich, dass mein Algorithmus einen Fehler hat, ich weiß aber nicht wo.

Wäre nett, wenn mir jemand helfen könnte!

Hier ist mein Code:

Java:
public class GuldeKI extends Daten {

    public Point next;

    public int maxWert(int tiefe) {
        int ermittelt = -1000000000;
        int zugWert;
        for (int y = 0; y < feld[0].length; y++) {
            for (int x = 0; x < feld.length; x++) {
                //Suche mögliche Züge
                if (feld[x][y] == 0) {
                    //Simuliere den Zug
                    feld[x][y] = 2;
                    //Wenn keine Züge mehr möglich
                    if (tiefe <= 1 || isWinner(2) || remis()) {
                        zugWert = bewertungsFunktion();
                    } else {
                        zugWert = minWert(tiefe - 1);
                        //System.out.println("kuckuckmax");
                    }
                    //Zugsimulation zurücksetzen
                    feld[x][y] = 0;
                    if (zugWert > ermittelt) {
                        ermittelt = zugWert;
                        next = new Point(x, y); //für das Hauptprogramm (Position des nächsten Zugs)
                    }
                }
            }
        }
        return ermittelt;
    }

    public int minWert(int tiefe) {
        int ermittelt = 1000000000;
        int zugWert;

        for (int y = 0; y < feld[0].length; y++) {
            for (int x = 0; x < feld.length; x++) {
                //Suche mögliche Züge
                if (feld[x][y] == 0) {
                    //Simuliere den Zug
                    feld[x][y] = 1;
                    //Wenn keine Züge mehr möglich
                    if (tiefe <= 1 || isWinner(1) || remis()) {
                        zugWert = bewertungsFunktion();
                    } else {
                        zugWert = maxWert(tiefe - 1);
                        //System.out.println("kuckuckmin");
                    }
                    //Zugsimulation zurücksetzen
                    feld[x][y] = 0;
                    if (zugWert < ermittelt) {
                        ermittelt = zugWert;
                    }
                }
            }
        }
        return ermittelt;
    }

    private int bewertungsFunktion() {
        if (isWinner(2)) { //X -> KI
            return -1;
        } else if (isWinner(1)) {
            return 1;
        } else {
            return 0;
        }
    }

Den MiniMax Algorithmus rufe ich folgendermaßen auf:

Java:
int x = ki.maxWert(9 - ki.count());
            ki.setElement(ki.next.x, ki.next.y, 2);

Bitte um Hilfe
 
Zuletzt bearbeitet:

Marco13

Top Contributor
Hmja, da hab' ich neulich auch mal wieder dran gesessen. Eigentlich wollte ich nur schnell was ausprobieren, und war einfach nur faul ihn selbst zu schreiben, und habe versucht den mit ein bißchen was ge-Copy&Paste-tetem schnell zusammenzufrickeln. Das Ergebnis waren ein paar Stunden debugging :rolleyes: deswegen werde ich das jetzt nicht nochmal durch machen ;) Am Ende hat sich Negamax - Wikipedia, the free encyclopedia als "einfacher mal schnell hinzuhacken" herausgestellt...
 

eRaaaa

Top Contributor
Bist du dir sicher dass deine Schleifen da korrekt sind? ???:L

Java:
        for (int y = 0; y < feld[0].length; y++) {
            for (int x = 0; x < feld.length; x++) {
                //....
               feld[x][y] = 2;

sieht ein wenig seltsam aus(im Bezug auf feld[0] in der äußeren Schleife?). Den Bezeichnungen zu folge müsste es doch auch eher
Code:
feld[y][x]
heißen oder? ???:L
 

XHelp

Top Contributor
Bist du dir sicher dass deine Schleifen da korrekt sind? ???:L

Java:
        for (int y = 0; y < feld[0].length; y++) {
            for (int x = 0; x < feld.length; x++) {
                //....
               feld[x][y] = 2;

sieht ein wenig seltsam aus(im Bezug auf feld[0] in der äußeren Schleife?). Den Bezeichnungen zu folge müsste es doch auch eher
Code:
feld[y][x]
heißen oder? ???:L

Warum? Wenn es
Code:
feld[y][x]
sein sollte, dann dürfe
Code:
y
nicht größer als
Code:
feld.length()
und nicht
Code:
feld[0].length()
sein. Du hast aber recht:
Java:
for (int x = 0; x < feld.length; x++) {
  for (int y = 0; y < feld[x].length; y++) {
wäre imho nachvollziehbarer.
 
Zuletzt bearbeitet:

hitman52

Mitglied
Ich glaube nicht, das es daran liegt. Mit diesen Zeilen wird einfach nur das 2D Array durchlaufen und zwar in folgender Reihenfolge:

1 2 3
4 5 6
7 8 9
Vertauscht man x und y, wird das Array so durchlaufen:
1 4 7
2 5 8
3 6 9

Was meiner Ansicht nach auch funktionieren würde. Das ist aber sicher nicht das Problem.

Danke trotzdem!

Edit:
feld[0].length gibt die größe des 2D-Arrays in y Richtung aus!
 

XHelp

Top Contributor
Der Algorithmus macht nach meiner Vorstellung Sinn (abgesehen davon, dass man ihn nur für einen Spieler laufen lassen kann).
Was mich unsicher macht ist die Bewertungsfunktion, denn Spieler 2 ist dein Max-Spieler, also müsste er ja auch beim gewinnen +1 bekommen. Bei dir bekommt er -1.
P.S. Du musst zwar die Züge "simulieren", aber es auf dem echten Spielfeld zu machen ist imho unschön.
 

Marco13

Top Contributor
Ob man durch ein 3x3-Feld läuft, oder durch ein 3x3-Feld, ist egal. Und wo man die Züge simulieren sollte, wenn nicht auf dem Spielfeld, ist mir im Moment nicht ganz klar. Trotz allem wäre es IMHO gut, das ganze etwas ... ... "prosaischer" zu schreiben. Ein paar Methoden wie
Code:
List<Point> computePossibleMoves()
void apply(Point move)
void undo(Point move)
kombiniert mit einem automatischen Umschalten des aktiven Spielers dürfte das ganze deutlich übersichtlicher und flexibler machen. Dazu kommen noch details wie dass das "Ende-Kriterium" nicht unbedingt IN der Schleife, sondern vielleicht am Methodenanfang geprüft werden sollte.

Wie auch immer. Ich glaube, du könntest mal
Code:
    public int maxWert(int tiefe)
    {
        int ermittelt = 1000000000;
        ...
        if (zugWert < ermittelt) {
        ...
    }

    public int minWert(int tiefe) 
    {
        int ermittelt = -1000000000;
        ...
        if (zugWert > ermittelt) {
        ...
    }
schreiben. Da scheinen min und max vertauscht zu sein.
 

XHelp

Top Contributor
Ob man durch ein 3x3-Feld läuft, oder durch ein 3x3-Feld, ist egal. Und wo man die Züge simulieren sollte, wenn nicht auf dem Spielfeld, ist mir im Moment nicht ganz klar.
Ich dachte da an eine Kopie des Spielfeldes.
Dazu kommen noch details wie dass das "Ende-Kriterium" nicht unbedingt IN der Schleife, sondern vielleicht am Methodenanfang geprüft werden sollte.
Man macht ja in der Schleife den Zug und erst dannach prüft man, ob das Spiel zuende ist. Deswegen macht es in meinen Augen schon Sinn.
Wie auch immer. Ich glaube, du könntest mal...schreiben. Da scheinen min und max vertauscht zu sein.
Dann würde er ja bei dem Max-Spieler das Minimum suchen und umkehert. Oder stehe ich auf dem Schlauch?
 

Marco13

Top Contributor
Das mit dem 3x3 vs. 3x3 bezog sich auf die Beiträge weiter oben (die waren zwar berechtigt, hatten mit dem Problem aber nichts zu tun)

Die Frage, ob man in der Schleife prüft oder wer den max- und wer den min-Wert (wie) berechnet hängen zusammen, je nachdem wie man's sieht. Ich finde eine Methode wie
Code:
int besterErreichbarerWert(...)
{
    if (verloren) return ganzSchlecht;
    for (alle Züge) { 
        machZug
        min oder max von besterErreichbarerWert(gegenspieler)
        undoZug
    }
}
irgendwie übersichtlicher und einleuchtender. Man muss aufpassen, was man wo zurückgibt: Max will ja wissen, welchen Zug er machen muss, damit der Wert, den Min nach dem Zug maximal erreichen kann der minimale ist - max will ja den minimalen (d.h. schlechtestmöglichen) Zug für Min erzwingen. Aber zugegeben: Wenn dann noch dazukommt, dass die Bewertungsfunktion nur für EINEN Spieler stimmt, wird's komplett chaotisch ... *kurz überleg* ich glaube sogar im Moment, dass es sein KÖNNTE, dass man mit einer Bewertungsfunktion den Minimax gar nicht richtig implementieren kann, weil die Bewertung ja davon abhängt, wer der aktive Spieler ist ... aber wie gesagt, so wie es ist, ist es nicht gerade übersichtlich, und wenn Unübersichtlichkeit und potentielle Vorzeichenfehler zusammenkommen ist mein Antrieb, mich da tiefer reinzufräsen nicht soooo groß.:oops:
 

babuschka

Top Contributor
Hallo,

mit diesem Algorithmus tauchen anscheinend wieder und wieder Probleme auf. Ich habe mich vor kurzer Zeit auch einmal mit dem Minimax beschäftigt (auch für TTT, die Kommilitonen hatten da ein Programmierprojekt, das mich auf die idee gebracht hat) und die KI spielt verdammt gut (bisher hat sie niemand geschlagen ;) )
Prinzipiell solltest Du mit einer Kopie Deines Spielfelds arbeiten. Das verhindert eine Verfälschung desselben und spätestens bei einer GUI mit eigenem Zeichenthread würdest Du auf Probleme stoßen...
Da ich gerade nicht viel Zeit habe, poste ich jetzt einfach mal meinen Code, vielleicht kannst Du damit ja was anfangen ;) Mit dem boolean min wird angegeben, ob der Spieler minimierend oder maximierend spielt.

Java:
private int bewerten(int zuege, Spieler eig, Spieler geg) {
        Spieler gewinner = spiel.gewinner(feld);

        if (gewinner == null) {
            //Unentschieden
            return 0;
        } else if (gewinner.equals(eig)) {
            //KI hat gewonnen
            return 10 - zuege;
        }
        return -(10 - zuege);
    }

    public int min(char[][] feld, Spieler minsp, Spieler maxsp, int zuege) {
        int ermittelt = 0;
        int zugWert = 0;
        ermittelt = Integer.MAX_VALUE;
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                if (spiel.legeStein(minsp, feld, i, j, false)) {
                    if (zuege >= 8 || spiel.gewinner(feld) != null) {
                        zugWert = -bewerten(zuege, minsp, maxsp);
                    } else {
                        zugWert = max(feld, maxsp, minsp, zuege + 1);
                    }
                    feld[i][j] = ' ';
                    if (zugWert < ermittelt) {
                        ermittelt = zugWert;
                        if (min) {
                            if (zuege == spiel.getZugzahl()) {
                                xnext = i;
                                ynext = j;
                            }
                        }
                    }
                }
            }
        }
        return ermittelt;
    }

    public int max(char[][] feld, Spieler maxsp, Spieler minsp, int zuege) {
        int ermittelt = 0;
        int zugWert = 0;
        ermittelt = Integer.MIN_VALUE;
        for (int i = 0; i < 3; i++) {
            for (int j = 0; j < 3; j++) {
                if (spiel.legeStein(maxsp, feld, i, j, false)) {
                    if (zuege >= 8 || spiel.gewinner(feld) != null) {
                        zugWert = bewerten(zuege, maxsp, minsp);
                    } else {
                        zugWert = min(feld, minsp, maxsp, zuege + 1);
                    }
                    feld[i][j] = ' ';
                    if (zugWert > ermittelt) {
                        ermittelt = zugWert;
                        if (!min) {
                            if (zuege == spiel.getZugzahl()) {
                                xnext = i;
                                ynext = j;
                            }
                        }
                    }
                }
            }
        }
        return ermittelt;
    }

Gruß,

JohnMcLane
 

XHelp

Top Contributor
Die Frage, ob man in der Schleife prüft oder wer den max- und wer den min-Wert (wie) berechnet hängen zusammen, je nachdem wie man's sieht.
Aso, sry, hab die Aussage dann falsch verstanden
Max will ja wissen, welchen Zug er machen muss, damit der Wert, den Min nach dem Zug maximal erreichen kann der minimale ist - max will ja den minimalen (d.h. schlechtestmöglichen) Zug für Min erzwingen.
Imho umgekehrt. Der Max-Spieler versucht das Maximum für sich rauszuholen während Min-Spieler das Minimum für den Gegner.
Ist zwar nicht die wissenschaftlichste Quelle, aber: Minimax-Algorithmus ? Wikipedia. Der Pseudocode ist wie beim TO
 
Zuletzt bearbeitet:

hitman52

Mitglied
@ Xhelp

Muss dir Recht geben, habe in der Tat einen Vorzeichenfehler in bewertungsFunktion() gehabt.
Habs jetzt folgendermaßen geändert:

Java:
private int bewertungsFunktion() {
        if (isWinner(2)) { //X -> KI
            return 1;
        } else if (isWinner(1)) {
            return -1;
        } else {
            return 0;
        }
    }

@JohnMcLane

Danke! Werds mir mal anschauen. :)

Danke und bis zur nächsten Frage. :D
 

Marco13

Top Contributor
Aso, sry, hab die Aussage dann falsch verstanden
Imho umgekehrt. Der Max-Spieler versucht das Maximum für sich rauszuholen während Min-Spieler das Minimum für den Gegner.

Hm. Der schlechtestmögliche Zug für Min ist der, der am wenigsten negativ ist - also der Maximale :rolleyes: ... ich glaube es reicht, wenn's jetzt geht ist's ja OK :)
 

Marco13

Top Contributor
Irgendwie habe ich das Gefühl, dass wir eigentlich das gleiche sagen (wollen) ;) Man sucht den besten Zug. Aber der beste Zug ist eben gerade darüber definiert, dass der beste Zug, den der Gegner danach machen kann, der schlechteste beste Zug ist, zu dem man ihn nötigen kann.
 

Ähnliche Java Themen

Neue Themen


Oben