- Minimaxalgorithmus
-
Der Minimax-Algorithmus ist ein Algorithmus zur Ermittlung der optimalen Spielstrategie für bestimmte Spiele, bei denen zwei gegnerische Spieler abwechselnd Züge ausführen (z. B. Schach, Go, Reversi, Dame, Mühle oder Vier gewinnt), insbesondere für Nullsummenspiele. Die Minimax-Strategie sichert bei Nullsummenspielen den höchstmöglichen Gewinn bei optimaler Spielweise des Gegners (das aus den Minimax-Strategien beider Spieler gebildete Strategie-Paar bildet ein Nash-Gleichgewicht). Bei Nicht-Nullsummenspielen können andere Algorithmen besser sein.
Im Gegensatz zu Würfelspielen sind die genannten Spiele nicht vom Zufall abhängig, im Gegensatz zu Karten- und Ratespielen sind sie offen, d.h. in jeder Spielsituation sind jedem der beiden Spieler alle Zugmöglichkeiten des jeweiligen Gegenspielers bekannt.
Die Niederlage des Gegners ist dabei der eigene Gewinn.
In solchen Fällen lässt sich die optimale Strategie für das jeweilige Spiel mit dem Minimax-Verfahren ermitteln. Die optimale Strategie ist dann gefunden, wenn sie zum (bestmöglichen) Ergebnis eines Spielers führt, wenn man von optimaler Spielweise des Gegners ausgeht.
Für einige Spiele, wie das so genannte Nim-Spiel, lässt sich eine optimale Strategie auch direkt ohne Minimax berechnen.
Inhaltsverzeichnis
Bewertungsfunktion
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. Allerdings ist in der Praxis der vollständige Aufbau eines Suchbaums nur bei sehr einfachen Spielen wie Tic-Tac-Toe möglich.
Bei fast allen anderen Spielen ist dies zu rechenaufwändig. Deshalb begnügt man sich damit, den Suchbaum nur bis zu einer vorgegebenen Suchtiefe (Horizont) aufzubauen. Die Bewertungsfunktion wird modifiziert, sehr gute Spielpositionen für A erhalten sehr hohe Werte, sehr gute Spielpositionen für B erhalten sehr niedrige Werte. Zur Ermittlung der Werte bedient man sich Heuristiken zur Schätzung.
Die steigende Rechenleistung von Computern und bessere Programme haben mittlerweile dazu geführt, dass selbst bei so komplexen Spielen wie Schach heutzutage die meisten Menschen ohne Mühe vom Computer geschlagen werden können. Die dabei verwendete Strategie beruht im Wesentlichen auf dem Minimax-Algorithmus.
Suchbaum-Beispiel
Das Bild rechts zeigt einen einfachen Baum mit Suchtiefe 4. Spieler A ist am Zug.
Die Knoten der Ebenen 0 und 2 entsprechen Spielsituationen, in denen Spieler A am Zug ist. Hier wird jeweils die Bewertungsfunktion der untergeordneten Knoten maximiert, d.h. der für Spieler A günstige Zug ausgewählt und dessen Wert dem Elternknoten zugewiesen.
Die Knoten der Ebenen 1 und 3 entsprechen Spielsituationen, in denen Spieler B am Zug ist. Hier wird jeweils die Bewertungsfunktion der untergeordneten Knoten minimiert, d.h. der für Spieler B günstigste Zug ausgewählt und dessen Wert dem Elternknoten zugewiesen.
Anmerkungen
- Das Minimax-Verfahren ist im Kern vom speziellen Spiel unabhängig, d. h. z. B. Schach und Reversi benutzen denselben Algorithmus.
- Schnittstellen zum speziellen Spiel sind lediglich die beiden folgenden Programmteile:
- Welche Züge sind in einer konkreten Spielsituation möglich?
- Wie wird eine Spielsituation numerisch bewertet?
- Neben dem Minimax-Verfahren kann ein Spiel weitere spielspezifische Verfahren anwenden, z. B. vorberechnete Bibliotheken für Eröffnungszüge.
Der Minimax-Algorithmus ist linear bezüglich der Anzahl der zu überprüfenden möglichen Züge. In der Regel benötigt man also mit steigender Suchtiefe exponentiell längere Zeit. (Man beachte, dass in der Theorie bei einem Spiel mit endlich vielen Zuständen die Laufzeit konstant ist, da ab einer gewissen Tiefe sich die Rechenzeit nicht mehr erhöht. Da bei den meisten Spielen diese Tiefe aber niemals realistisch erreicht werden kann, ist es durchaus berechtigt von einem exponentiellen Wachstum zu sprechen.) Andererseits steigt in der Regel (abhängig von der numerischen Bewertung) bei höherer Suchtiefe auch die Qualität des Suchergebnisses.
Es existieren daher verschiedene optimierte Varianten, z. B.
- Variable Suchtiefe: Wenn nur noch wenige Zugmöglichkeiten pro Spielsituation existieren, z. B. weil sich nur noch wenige Spielsteine auf dem Spielfeld befinden, kann die Suchtiefe erhöht werden (und umgekehrt).
- Dynamische Suchtiefe: Wenn sich die Zahlenwerte an einer Stelle des Suchbaums von Ebene zu Ebene stark ändern, kann die Suchtiefe lokal erhöht werden (und umgekehrt).
- Die Alpha-Beta-Suche kann verwendet werden.
Eine wesentliche Zeitersparnis ergibt sich durch Speicherung der bisher untersuchten Stellungen und deren Bewertungen. Wird eine Stellung durch verschiedene Zugfolgen von der Ausgangsstellung erreicht, braucht nicht jedes Mal wieder der gesamte darunter liegende Suchbaum durchsucht zu werden.
Iterative Tiefensuche
Speziell bei eingeschränkter Zeit für die Suche (z. B. im Turnierschach) wird iterative Tiefensuche (iterative deepening) verwendet. Dabei wird die Suche, ausgehend von der zu untersuchenden Stellung, wiederholt gestartet und dabei die gewünschte Suchtiefe schrittweise erhöht. Werden die bereits untersuchten Stellungen, wie oben beschrieben, gespeichert, müssen nur die gegenüber der vorhergehenden Suche neu erreichten Stellungen mit der Bewertungsfunktion bewertet werden. Dieses Verfahren wird so lange fortgesetzt, bis die verfügbare Suchzeit überschritten oder ein "hinreichend gutes" Ergebnis erzielt wurde.
Ohne iterative Tiefensuche wäre beim Überschreiten des Zeitlimits im schlimmsten Fall nur ein einziger Zug untersucht worden, dieser aber bis in sehr große Tiefe. Der nächste Zug, der vielleicht schon nach nur einem einzigen Gegenzug den Gewinn gesichert hätte, wäre gar nicht erst ausprobiert worden.
Pseudocode
Hauptprogramm (Auszug): var doNext : number dummy := maxWert ( gewünschte suchTiefe ) Zug doNext ausführen
function maxWert ( restTiefe ) returns number var ermittelt, zugWert : number begin ermittelt := - unendlich für alle möglichen Züge begin Zug simulieren if restTiefe <= 1 or keineFolgezügeMehrMöglich then zugWert := bewertungsFunktion else zugWert := minWert ( restTiefe - 1 ) Zug-Simulation zurücksetzen if zugWert > ermittelt then begin ermittelt := zugWert if restTiefe = gewünschte suchTiefe /* doNext nur wenn auf oberster Ebene setzen */ doNext := nummer des Zuges /* für das Hauptprogramm */ end end return ermittelt end maxWert
function minWert ( restTiefe ) returns number var ermittelt, zugWert : number begin ermittelt := + unendlich für alle möglichen Züge begin Zug simulieren if restTiefe <= 1 or keineFolgezügeMehrMöglich then zugWert := bewertungsFunktion else zugWert := maxWert ( restTiefe - 1 ) Zug-Simulation zurücksetzen if zugWert < ermittelt then ermittelt := zugWert end return ermittelt end minWert
Variante: Der NegaMax-Algorithmus
Um den Code zu vereinfachen und jeden Knoten des Suchbaumes gleich behandeln zu können, definiert man, dass jeder Spieler versucht, ein für sich selbst maximales Ergebnis, das heißt maximalen Wert der Bewertungsfunktion, zu erzielen. Dazu muss die Bewertung der Folgestellungen mit − 1 multipliziert werden (Negation, daher der Name). Damit muss nicht mehr unterschieden werden, ob A oder B am Zug ist und daher das Maximum oder das Minimum berechnet werden soll, sondern es wird in jeder Stellung immer nur das Maximum der negierten Bewertungen der Folgestellungen berechnet.
Siehe auch
Weblinks
Wikimedia Foundation.