Implementierung eines Schachprogramms - olle

Werbung
Seminararbeit
Implementierung eines
Schachprogramms
vorgelegt von
Dipl.cand.-MedInf. Stefan Koppitz
Matr.-Nr.: 3075856
Betreuer:
Doz. Dr. habil. Uwe Petersohn
Fakultät Informatik
Institut für Künstliche Intelligenz
Technische Universität Dresden
Dresden, im August 2006
Seminar: Neuronale Netze und Fallbasiertes Schließen
Thema: Nr VI.2 Implementierung eines Schachprogramms
Implementieren Sie in Anlehnung an Russel / Norvig ein Schachprogramm. Wenden Sie
verschiedene dort beschriebene Strategien zur Verbesserung des Suchraumes an und dokumentieren Sie Ihr Vorgehen.
Messen Sie die Performance Ihres Schachspielers“.
”
Es wird ein selbständiges Literaturstudium erwartet. Die angegebenen Literaturstellen
sind nur richtungsweisend.
Inhaltsverzeichnis
1 Einleitung
1
2 Such-Algorithmus
2
2.1
Minimax-Algorithmus
. . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2
2.2
Alpha-Beta-Suche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
6
2.3
Vergleich Minimax- und Alpha-Beta-Suche . . . . . . . . . . . . . . . . . .
8
3 Optimierung
9
3.1
Zugsortierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
9
3.2
NegaScout-Suche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
9
3.3
Iterative Tiefensuche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
3.4
Aspiration Window . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
3.5
Killer-Heuristik . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
3.6
Quiescent-Suche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
3.7
Null-Zug-Suche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
3.8
Hash-Tabellen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
3.9
Ruhesuche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
4 Bewertungsfunktion
15
5 Zusammenfassung und Ausblick
16
Literaturverzeichnis
17
Kapitel 1
Einleitung
Im Gegensatz zu Würfelspielen ist Schach nicht vom Zufall abhängig. Im Gegenteil: es ist
offen und alle benötigten Informationen sind vorhanden. D. h. in jeder Spielsituation sind
jedem der beiden Spieler alle Zugmöglichkeiten des Gegenspielers bekannt.
Der Verlust des Gegners ist dabei der eigene Gewinn. In solchen Fällen lässt sich die
optimale Strategie mit dem Minimax-Algorithmus ermitteln. Die optimale Strategie ist
dann gefunden, wenn sie zum bestmöglichen Ergebnis eines Spielers führt. Dabei geht
man von optimaler Spielweise des Gegenspielers aus.
Diese Arbeit soll nun den Minimax-Algorithmus als Suchalgorithmus am Beispiel eines
Schachprogramms vorstellen und durch Optimierung verbessern.
Suchalgorithmen spielen in der künstlichen Intelligenz eine wesentliche Rolle, um effizient
optimale Strategien zu finden.
Um den Algorithmus zu visualisieren, wird ein Programm implementiert. Dazu wird es
die Performance des Programms messen und die Gedanken des Computers“ während
”
des Spielens aufzeigen. Es ist weiterhin möglich bestimmte Optimierungen zu- bzw. abzuschalten.
Nachfolgend sollen der Minimax-Algorithmus und seine Verbesserungen, dann die Optimierungsverfahren für den Algorithmus und schließlich die Bewertungsfunktion vorgestellt
werden.
Kapitel 2
Such-Algorithmus
2.1
Minimax-Algorithmus
Schach kann vereinfacht als Baum dargestellt werden. Dabei kennzeichnen alle Knoten und
Blätter dieses sogenannten Suchbaums eine Stellung der Figuren auf dem Schachbrett.
Jede Verzweigung dieses Baumes stellt dann ein Übergang von einer Spielstellung zu einer
anderen, also einen Schachzug, dar.
Der Baum hat eine Tiefe d und beginnt an der Wurzel mit d = 0.
Man kann sich nun denken, dass es nicht möglich ist, den Baum nach allen Spielkombinationen abzusuchen. Es müssten dann nämlich alle 35100 Knoten des Suchbaums untersucht
werden. Davon sind aber bloß 1040 zulässige Positionen.
Der Minimax-Algorithmus nutzt daher eine Vereinfachung. Um den Suchaufwand zu minimieren, kann man sich überlegen, immer die beste Stellung erreichen zu wollen. Andererseits wird der Gegenspieler das gleiche versuchen. Also ordnet man einem Spieler eine
maximale Punktzahl und dem anderen eine minimale Punktzahl zu.
Der Spieler, welcher die maximalen Punkte erhofft, wird auch Maximizer“ und der Ge”
genspieler Minimizer“ genannt.
”
Da die Spieler abwechselnd eine Spielfigur ziehen, ist immer ein Spieler auf einer bestimmten Baumebene am Zug. Daher können wir diese Ebenen auch Maximizing Level“ und
”
Minimizing Level“ nennen.
”
Ein Spiel wird dann formal als ein Suchproblem über die Menge aller möglichen Züge mit
folgenden Komponenten definiert:
• Der Zustand, der die Position der Figuren auf dem Brett enthält und anzeigt,
2 Such-Algorithmus
3
welcher Spieler als nächstes am Zug ist.
• Eine Menge von Operatoren, welche die zulässigen Züge definieren, die ein Spieler
ausführen darf.
• Einen Abbruchkriterium, der das Ende des Spieles anzeigt. Zustände, die diesen
Test erfüllen, werden Endzustände genannt.
• Eine Bewertungsfunktion, die dem Ausgang des Spiels einen numerischen Wert
zuweist.
Der formale Algorithmus läuft dann wie folgt ab:
1. Generiere den Suchbaum bis zu den Endzuständen
2. Wende die Bewertungsfunktion auf alle Endzustände bzw. Blätter an
3. Falls die aktuelle Ebene ein Maximizing/Minimizing Level ist, dann erhalten die
Elternknoten den jeweils höchsten/niedrigsten Wert ihrer Kindknoten
4. Propagiere mit Schritt 3 bis zur Wurzel
Daraus lässt sich dieser Pseudocode konstruieren. Der rekursive Algorithmus arbeitet nach
dem Prinzip der Tiefensuche. D. h. es wird vom Startknoten aus durch Expansion des
jeweils ersten auftretenden Nachfolgeknoten im Suchbaum nach und nach weiter in die
Tiefe gesucht.
function MINIMAX_WERT(zustand)
{
if END_TEST(zustand) then
return BEWERTEN(zustand)
else
init MIN_WERT maximal;
init MAX_WERT minimal;
for each op in OPERATOREN do
if MAX ist am Zug then
MAX_WERT = MAX(MAX_WERT,MINIMAX_WERT(zustand-1))
else
MIN_WERT = MIN(MIN_WERT,MINIMAX_WERT(zustand-1))
end
if MAX ist am Zug then
2 Such-Algorithmus
4
return MAX_WERT
else
return MIN_WERT
end
}
Der Algorithmus kann weiterhin vereinfacht werden, indem jeder Knoten des Suchbaums
gleich behandeln wird. Dabei wird definiert, dass jeder Spieler versucht, ein für sich selbst
maximales Ergebnis zu erzielen. Dazu muss die Bewertung der Folgestellungen mit −1
multipliziert werden. Daher auch der Name NegaMax-Algorithmus. Damit muss nicht
mehr unterschieden werden, ob Schwarz oder Weiß am Zug ist. Es wird in jeder Stellung
immer nur das Maximum der negierten Bewertungen der Folgestellungen berechnet. Der
Pseudocode sieht dann wie folgt aus:
function NEGAMAX_WERT(zustand)
{
if END_TEST(zustand) then
return BEWERTEN(zustand)
else
init WERT minimal;
for each op in OPERATOREN do
WERT = MAX(WERT,-NEGAMAX_WERT(zustand-1))
end
return WERT
end
}
Es sei gesagt, dass der Minimax-Algorithmus vollständig bei einem endlichen Suchbaum
ist. Durch die definierten Schachregeln wird der Determinismus garantiert. Weiterhin ist
der Algorithmus optimal unter der Voraussetzung, dass auch gegen ein optimal spielenden
Gegner gespielt wird.
Betrachten wir nun die Komplexität des Algorihtmus. Der Zeitbedarf wächst durch das
iterieren über den Suchbaum exponentiell und beträgt O(nd ). Der Platzbedarf ist durch
Speichern aller Knoten O(n · d) (Fierz 2005). Dabei sind n die Anzahl der möglichen
Züge und d die Suchbaum-Tiefe. Die Konplexität ist somit nicht praktikabel für Schach
mit n ≈ 35 und d ≈ 100. Die naheliegende Veränderungen des Minimax-Algorithmus ist
folglich die Einführung einer Abbruchtiefe, die den Spielend-Test ersetzt.
2 Such-Algorithmus
5
Nehmen wir an, es sind 100 s pro Zug erlaubt. Dann können 104 Knoten pro s durchsucht
werden. Da die Ressourcen auf dem Computer beschränkt sind, muss die Ungleichung
nd ≤ 104 erfüllt sein. Mit n = 35 folgt daraus d = 2 Züge Vorausschau für die Schach-KI.
Wir werden im folgenden Absatz den Minimax-Algorithmus direkt auf das Schachspiel
anwenden und detailiert die einzelnen Phasen durchgehen: Ausgehend von der gerade am
Brett befindlichen Stellung ermittelt das Programm mittels des Zuggenerators alle regelkonformen Züge und führt im Geiste“ den ersten möglichen davon aus; in der neu ent”
standenen Stellung werden nun wiederum alle Züge bestimmt, der erste ausgeführt usw.,
bis zu einer bestimmten Tiefe d. Ist diese erreicht, schätzt man die entstandene Stellung
mittels der Bewertungsfunktion ab; anschließend wird der letzte Zug zurückgenommen
und nacheinander alle Alternativmöglichkeiten durchprobiert, wobei hier noch jeweils unmittelbar im Anschluss die Bewertungsfunktion aufgerufen wird. Sind alle Möglichkeiten
untersucht, wird auch der vorletzte Zug zurückgenommen und nun dessen Alternativen
getestet, worauf genau das gleiche Verfahren einsetzt (d. h. Ausprobieren aller möglichen
Züge und Aufruf der Bewertungsfunktion). Macht man so weiter, hat man am Ende alle
Stellungen besichtigt, die sich nach d Zügen ergeben könnten. Da für den optimalen Zug
dessen Bewertung und der Pfad“ (d. h. die Züge, die zur Stellung führten) gespeichert
”
sind, kann man diese Zug nun ausführen.
function NEGAMAX_SCHACH(zustand)
{
if END_TEST(zustand) then
return BEWERTEN(zustand)
else
init WERT minimal;
for each op in OPERATOREN do
AUSFUEHREN(op)
WERT = MAX(WERT,-NEGAMAX_SCHACH(zustand-1))
ZURUECKNEHMEN(op)
end
return WERT
end
}
2 Such-Algorithmus
2.2
6
Alpha-Beta-Suche
Der Minimax-Algorithmus bewertet den vollständigen Suchbaum. Dabei werden aber auch
Knoten betrachtet, die in das Ergebnis (die Wahl des Zweiges an der Wurzel) nicht einfließen. Die Alpha-Beta-Suche versucht, möglichst viele dieser Knoten zu ignorieren. Denn
wir wollen nur wissen, welche Zugfolge am besten ist und nicht wie viel besser sie ist (Lorenz 2006). Was beim ersten Lesen vielleicht lediglich spitzfindig klingt, birgt gewaltiges
Einsparungspotential in sich:
Nehmen wir an, das Programm hat in einem beliebigen Knoten K0 des Baumes bereits
einige Züge untersucht und für den bisher besten den Wert w0 aus Sicht der am Zug
befindlichen Partei A ermittelt. Gemäß des Minimax-Algorithmus untersucht es nun die
nächste Alternative; sollte dann bei der Suche in diesem Teilbaum an irgendeiner Stelle
der Gegner B die Möglichkeit haben, einen Zug z mit einer Bewertung wz , die aus Sicht
von A nicht besser als w0 ist, zu spielen, so weiß man, dass einer der vorangegangenen
Züge von A (ab dem Knoten K0 ) bereits nicht optimal war, denn den Wert w0 kann
er ja dadurch erreichen, dass er in K0 den zu w0 gehörigen Zug wählt. Es ist dann an
dieser Stelle unnötig, noch weitere Gegenzüge von B auszuprobieren; das vorhergehende
Spiel von A kann bereits durch z widerlegt werden, ob es noch stärkere Züge gibt, ist
uninteressant (Heuner 2002).
Folglich würde an dieser Stelle der Wert wz als obere Schranke für den Wert des letzten
Zuges von A zurückgegeben und die nächste Alternative zu diesem getestet. Der Wert
w0 kann also bei allen Folgeknoten von K0 als sicherer Wert für die Partei A angesehen
werden.
Bei der Untersuchung eines Knotens bezeichnet bezeichnet man den zu diesem Zeitpunkt
sicheren Wert für die Partei am Zug als Alpha-, den für den Gegner als Beta-Wert,
wobei Alpha stets kleiner Beta ist.
Die Idee ist also, dass diese zwei Werte weitergereicht werden, die das Worst-Case- Szenario der Spieler beschreiben (Wikipedia.org 2006). Der Alpha-Wert ist das Ergebnis,
das Spieler A mindestens erreichen wird, der Beta-Wert ist das Ergebnis, das Spieler B
mindestens erreichen wird. Besitzt ein maximierender Knoten (von Spieler A) einen Zug,
dessen Rückgabe den Beta-Wert überschreitet, wird die Suche in diesem Knoten abgebrochen (Beta-Cutoff, denn Spieler B würde erst gar nicht diese Variante wählen, weil sie ein
zu gutes Ergebnis für Spieler A liefern würde). Liefert der Zug stattdessen ein Ergebnis,
das den Alpha-Wert übersteigt, wird dieser entsprechend nach oben angehoben. Analoges
gilt für die minimierenden Knoten, wobei bei Werten kleiner als Alpha abgebrochen wird
(Alpha-Cutoff) und der Beta Wert nach unten angepasst wird.
2 Such-Algorithmus
7
Der Pseudocode der Alpha-Beta-Such sieht dann wie folgt aus:
function ALPHA_BETA(zustand,alpha,beta)
{
if END_TEST(zustand) then
return BEWERTEN(zustand)
else
init WERT minimal;
for each op in OPERATOREN do
AUSFUEHREN(op)
WERT = -ALPHA_BETA(zustand-1,-alpha,-beta)
ZURUECKNEHMEN(op)
if WERT >= beta then
return beta
if WERT > alpha then
alpha = WERT
end
return alpha
end
}
MAX
-∞ 12 ∞
10 12 ∞
-∞ 10 ∞
12 3 ∞
MIN
MAX
-∞ 10 ∞
-∞ 12 10
10 12 ∞
10 13 12
12
3 ∞
10 -5
-6 12 ?
10 12 3
13 ?
3
2
3
?
-4
?
?
?
Bild 2.1: Obige Abbildung zeigt einen Beispielbaum mit 18 Blättern, von denen nur 12
von der Alpha-Beta-Suche ausgewertet werden. Die drei umrandeten Werte eines inneren
Knotens beschreiben den Alpha-Wert, den Rückgabewert und den Beta-Wert. rot: 12,13 Beta-Cut; rot: 3 - Alpha-Cut
Es sei an dieser Stelle noch einmal betont, dass die Alpha-Beta-Suche erlaubt, risikolos
2 Such-Algorithmus
8
bestimmte Äste des Baumes wegzulassen, d. h. es wird genau der gleiche Zug mit dem gleichen Wert als bester ermittelt wie bei der vollständigen Minimax- Suche. Die Einsparung
dagegen ist ganz enorm. Nur um ein Gefühl für die Einsparung durch die Alpha-BetaSuche zu vermitteln, hier die Zahl zu bewertender Endknoten n beim Idealfall für einen
Baum der Hohe d und (als konstant angenommenem) Verzweigungsfaktor z:
für d ungerade:
n=z
d+1
2
+z
d−1
2
−1
für d gerade:
d
n = 2 · z2 − 1
Wie man den Formeln entnimmt, steigt die Zahl zu bewertender Endknoten bei Erhöhung
√
der Suchtiefe um einen Zug im Mittel um den Faktor z; zur Erinnerung: mit dem
einfachen Minimax-Algorithmus ist dieser gleich z! Grob gesprochen kann man also mit
der Alpha-Beta-Suche bei gleicher Endknotenzahl etwa doppelt so tief rechnen wie mit
dem Minimax-Algorithmus (Zwanzger 2004).
2.3
Vergleich Minimax- und Alpha-Beta-Suche
Nachfolgende Tabelle zeigt eine Beispielberechnung einer Schachstellung bei konstanter
Suchtiefe von vier Zügen. Es wurde der normale Minimax-Algorithmus angewendet und
Alpha-Beta ohne Zugsortierung. Die Prozentangabe bei den Cutoffs beziehen sich auf den
gesamten Suchbaum und beschreibt, wie viel des gesamten Suchbaumes nicht ausgewertet
wurde. Es handelt sich dabei um Schätzungen, denen zugrunde liegt, dass die Teilbäume
in etwa gleich groß sind (bei Cutoffs ist nicht bekannt, wie groß der weggeschnittene
Teilbaum wirklich wäre).
Algorithmus
Bewertungen
Cutoffs
Anteil der Cutoffs in %
Rechenzeit in s
Minimax
28018531
0
0,00
134.87
Alpha-Beta
2005246
136478
91,50
9.88
Tabelle 2.1: Vergleich der Algorithmen Minimax und Alpha-Beta mit einer Beispielberechnung.
Es ist deutlich zu erkennen, dass die Alpha-Beta-Suche eine erhebliche Geschwindigkeitssteigerung gegenüber Minimax bedeutet.
Kapitel 3
Optimierung
3.1
Zugsortierung
Anders als beim Minimax-Algorithmus spielt bei der Alpha-Beta-Suche die Reihenfolge,
in der Kindknoten (Züge) bearbeitet werden, eine wesentliche Rolle. Je schneller das
Alpha-Beta-Fenster verkleinert wird, desto mehr Cutoffs werden erreicht. Deshalb ist es
wichtig, zuerst die Züge zu betrachten, die das beste Ergebnis versprechen. In der Praxis
werden verschiedene Heuristiken verwendet. Bei Schach z. B. kann man die Züge danach
sortieren, ob bzw. welche Figur geschlagen wird, oder auch welche Figur schlägt. Turm
”
schlägt Dame“ wird danach vor Turm schlägt Turm“ einsortiert und Bauer schlägt
”
”
Turm“ wird zwischen beiden einsortiert.
3.2
NegaScout-Suche
Zunächst sei erwähnt, dass der NegaScout-Algorithmus (oft auch Principal-VariationSuche genannt) nicht zwangsläufig besser ist als die Alpha-Beta-Suche. Unter welchen
Umständen er für mehr Schnitte sorgt und somit insgesamt zu einer PerformanceSteigerung führt, sei im Folgenden erläutert.
Die Idee des NegaScout-Algorithmus besteht darin, nach der Bewertung des ersten Unterbaumes, die analog zum Alpha-Beta-Algorithmus erfolgt, alle weiteren a-priori als unterlegen zu betrachten und eine Nullfenstersuche (d. h. setze Beta = Alpha ± 1) durchzuführen. Dadurch werden mehr Schnitte im Suchbaum vorgenommen. Kann die Unterlegenheit nicht bewiesen werden, d. h. es gab doch noch einen besseren Zug, so muss eine
Wiederholungssuche mit offenem Fenster durchgeführt werden.
3 Optimierung
10
Diese Vorgehensweise ist vor allem dann effektiv, wenn mit geeigneten Heuristiken sichergestellt wird, dass auch tatsächlich aussichtsreiche Züge zuerst untersucht werden. Die
Ersparnis der Suche mit Nullfenster ist dann in der Regel so groß, dass der Mehraufwand
gelegentlicher Wiederholungssuchen bei weitem aufgewogen wird.
Um nun die Alpha-Beta-Suche durch die NegaScout-Suche zu optimieren, brauchen wir
noch Knotenarten. Ein Knoten bei der Alpha-Beta-Suche gehört einer von drei Kategorien
an (bezogen auf die NegaMax-Variante):
• Alpha-Knoten: Jeder Folgezug liefert einen Wert kleiner oder gleich Alpha, was
bedeutet, dass hier kein guter Zug möglich ist.
• Beta-Knoten: Mindestens ein Folgezug liefert einen Wert größer oder gleich Beta,
was einen Cutoff bedeutet.
• Principal-Variation-Knoten: Mindestens ein Folgezug liefert einen Wert größer als
Alpha, aber alle liefern einen Wert kleiner oder gleich Beta.
Manchmal kann man frühzeitig erkennen, um welchen Knoten es sich handelt. Liefert
der erste getestete Folgezug einen Wert größer gleich Beta, dann ist es ein Beta-Knoten.
Liefert er einen Wert kleiner gleich Alpha, dann ist es möglicherweise ein Alpha-Knoten
(vorausgesetzt, die Züge sind gut vorsortiert). Liefert er aber einen Wert zwischen Alpha
und Beta, so handelt sich möglicherweise um einen Principal-Variation-Knoten.
Die NegaScout-Suche nimmt nun an, dass ein Folgezug, der einen Wert zwischen Alpha und Beta liefert, sich als bester möglicher Zug herausstellen wird. Deshalb wird das
Alpha-Beta-Fenster im folgenden minimal verkleinert (Nullfenstersuche), um eine maximale Anzahl an Cutoffs zu erreichen, aber dennoch die verbleibenden Züge als schlechter
zu beweisen.
Im folgenden der Pseudocode für die mit NegaScout verbesserte Alpha-Beta-Suche:
function ALPHA_BETA_NEGASCOUT(zustand,alpha,beta)
{
PV_GEFUNDEN = false
if END_TEST(zustand) then
return BEWERTEN(zustand)
else
init WERT minimal;
for each op in OPERATOREN do
AUSFUEHREN(op)
3 Optimierung
11
if PV_GEFUNDEN then
WERT = -ALPHA_BETA_NEGASCOUT(zustand-1,-alpha-1,-alpha)
if WERT > alpha und WERT < beta then
WERT = -ALPHA_BETA_NEGASCOUT(zustand-1,-beta,-alpha)
else
WERT = -ALPHA_BETA_NEGASCOUT(zustand-1,-beta,-alpha)
end
ZURUECKNEHMEN(op)
if WERT >= beta then
return beta
if WERT > alpha then
alpha = WERT
PV_GEFUNDEN = true
end
return alpha
end
}
MAX
-∞ 12 ∞
-∞ 10 ∞
10 12 ∞
12 3 ∞
MIN
MAX
-∞ 10 ∞
-∞ 12 10
10 12 ∞
10 13 12
12
3 ∞
10 -5
-6 12 ?
10 12 ?
13 ?
3
2
3
?
-4
?
?
?
Bild 3.1: Obige Abbildung zeigt einen Beispielbaum mit 18 Blättern, von denen nur 11
durch den NegaScout-Algorithmus ausgewertet werden. Die drei umrandeten Werte eines
inneren Knotens beschreiben den Alpha-Wert, den Rückgabewert und den Beta-Wert. rot:
12,13 - Beta-Cut; rot: 3 - Alpha-Cut; grün: 12 - NegaScout-Cut
3 Optimierung
3.3
12
Iterative Tiefensuche
Die iterative Tiefensuche ist die schrittweise Erhöhung der Tiefe des Suchbaumes. Da
die Alpha-Beta-Suche eine Tiefensuche ist, kann man meist vorher nicht bestimmen, wie
lange die Berechnung dauern wird. Deshalb beginnt man mit einer geringen Suchtiefe
und erhöht diese nach und nach. Das Ergebnis einer Berechnung kann benutzt werden,
um bei der nächsten Berechnung die Züge besser vorzusortieren (s. Zugvorsortierung,
NegaScout-Suche).
3.4
Aspiration Window
Aspiration windows werden zusammen mit der iterativen Tiefensuche verwendet.
Grundsätzlich beginnt die Alpha-Beta-Suche an der Wurzel mit einem maximalen Fenster.
Bei der iterativen Tiefensuche kann aber angenommen werden, dass eine neue Berechnung
mit höherer Tiefe einen ähnlichen Ergebniswert liefert wird wie die vorangegangene. Deshalb kann das Suchfenster initial auf einen (relativ) kleinen Bereich um den Ergebniswert
der vorherigen Berechnung gesetzt werden. Stellt sich heraus, dass dieses Fenster zu klein
war, muss man (ähnlich wie bei der Principal-Variation-Suche) die Suche mit maximalem
Fenster wiederholen.
3.5
Killer-Heuristik
Die Killer-Heuristik ist eine spezielle Art der Zugvorsortierung. Man nimmt hierbei an,
dass Züge, die einen Cutoff verursacht haben, auch in anderen Teilen des Suchbaumes (bei
gleicher Tiefe) einen Cutoff verursachen werden. Deshalb werden sie künftig immer zuerst
betrachtet, sofern sie in der gerade betrachteten Spielposition gültige Züge darstellen.
Diese Heuristik kann nicht bei allen Spielen sinnvoll angewendet werden, da die Aussicht
darauf, dass Killerzüge auch in anderen Teilen des Suchbaumes noch gültige Züge sind,
gering ist. Diese Heuristik wird deswegen nicht in dem Schachprogramm implementiert
sein.
3.6
Quiescent-Suche
Die Alpha-Beta-Suche bzw. der Minimax-Algorithmus allgemein haben das Problem, dass
bei einer gewissen Tiefe die Suche strikt abgebrochen wird, obwohl der Ergebniswert an
3 Optimierung
13
dieser Stelle die Brettstellung nicht besonders gut widerspiegelt. Anstatt die Alpha-BetaSuche bei der erreichten Höchsttiefe abzubrechen, wird eine spezielle Quiescent-Suche
fortgeführt, die nur noch wenige wichtige der möglichen Züge betrachtet. Dadurch wird
vermieden, dass kritische Spielpositionen an den Blättern allein durch die Bewertungsfunktion evaluiert werden.
3.7
Null-Zug-Suche
Speziell in Schachprogrammen hat sich die Null-Zug-Suche (oft auch das NullmovePruning genannt) bewährt. Diese Technik wird benötigt, um die Ermittlung der
Spielstärke möglicher Züge bzw. Spielverläufe zu beschleunigen: Es werden Züge, welche
durch unten beschriebenes Verfahren als zu schwach ermittelt werden, von einer weiteren
Berechnung ausgeschlossen.
Ausgehend von der Annahme, dass das Zugrecht einen Vorteil darstellt, wird beim
Nullmove-Pruning in der Baumsuche einer Seite ermöglicht zwei Züge auszuführen. Ist
der dadurch erzielte Vorteil nicht groß genug, so war wahrscheinlich schon der erste der
beiden Züge minderwertig, und der daraus resultierende Ast des Spielbaums braucht nicht
weiter untersucht zu werden. Er wird einfach abgeschnitten. Hierdurch können minderwertige Varianten gut und schnell erkannt werden und die zur Verfügung stehende Zeit
für die Analyse wichtigerer Varianten genutzt werden.
3.8
Hash-Tabellen
Bisher wurde immer so getan, als ob verschiedene Zugfolgen auch immer zwangsläufig zu
verschiedenen Stellungen führen würden. Das ist allerdings im Schach nicht der Fall. Zum
Beispiel führen, wenn keine Schlagfälle o. ä. dabei sind, Zugfolgen vom Typ A-B-C und CB-A zur gleichen Stellung. Es wäre unvorteilhaft, die komplette Suche noch einmal in der
Stellung nach C-B-A durchzuführen, wenn die Stellung A-B-C bereits untersucht wurde.
Oftmals kann nämlich das Ergebnis unverändert übernommen werden (die Einschränkung
ergibt sich durch evtl. andere Alpha- und Beta-Werte beim Aufruf). Es wäre also sinnvoll,
wenn das Programm sich merken würde, welche Stellungen es bereits untersucht hat und
zu welchem Ergebnis es ggf. gekommen ist. Das macht man, indem diese Ergebnisse in
einer sogenannten Hashtabelle“ gespeichert werden.
”
Es kann allerdings durchaus vorkommen, dass die gleiche Stellung während einer Suche
mehrmals bei einer unterschiedlichen Zahl vorangegangener Züge und entsprechend höher-
3 Optimierung
14
er bzw. niedrigerer Resttiefe besichtigt wird. Deshalb muss das Programm sich auch noch
merken, welche Tiefe die Suche hatte, auf der der Eintrag basiert.
3.9
Ruhesuche
Nun soll noch eine Lösung zu einem (schachspezifischen) Problem vorgestellt werden, das
der bisher vorgestellte Algorithmus mit sich bringt.
Würde man tatsächlich immer nur fix bis zu einer festen Tiefe rechnen, so käme es am
Ende der Varianten, die das Programm ausgibt, meist zu einem wahren Schlagabtausch.
Der Grund: da nach Erreichen der vorgesehen Suchtiefe nicht mehr weitergerechnet wird,
werden auch einstehende Figuren nicht mehr erkannt. Die Partei, die den letzten Zug
ausführen darf, könnte also ungestraft gedeckte Steine schlagen und würde dafür auch noch
mit einem hohen Wert belohnt, weil das mögliche Wiederschlagen nicht mehr erfasst wird.
Dieser Effekt kann bei längeren Schlagfolgen auch schon ein paar Halbzüge vor Erreichen
der Endknoten eintreten. Die auf diese Weise ermittelten besten Züge wären dann oft
zufälliger Natur. Grundsätzlich wäre es also besser, wenn die Bewertungsfunktion erst
dann aufgerufen wird, wenn Ruhe“ am Brett herrscht, d. h. fürs erste keine Schlagzüge
”
mehr möglich sind.
Da solche Stellungen jedoch oft viel zu spät eintreten, behilft man sich folgendermaßen: ist
die entsprechende Tiefe erreicht, so wird der Wert, den die Bewertungsfunktion zurückliefert, als sicherer Wert für die Partei am Zug angesehen. Weiterhin werden nun aber auch
noch alle Schlagzüge untersucht. Nach Ausführung eines solchen wird wieder der Wert der
entstandenen Stellung bestimmt. Diesen hat nun die gegnerische Partei sicher, und jetzt
werden für diese alle Schlagzüge untersucht und so weiter. Jede Partei kann also, wenn sie
am Zug ist, noch eine Schlagserie beginnen oder aussteigen“. Auf diese Weise wird das
”
oben geschilderte Problem vermieden. Denn es ist sichergestellt, dass auf jedes Schlagen
das evtl. mögliche Wiederschlagen erkannt wird.
Da die Zahl der Schlagzüge in aller Regel sehr gering ist und sich Schlagfolgen auch
noch selbst begrenzen (im Schach ist hier nach spätestens dreißig Zügen mangels Material
Schluss), ist der Zusatzaufwand nicht allzu extrem. Das Spiel des Programms wird jedoch
merklich verbessert.
Kapitel 4
Bewertungsfunktion
Die Bewertungsfunktion ist das, was ein Schachprogramm erst richtig intelligent macht.
Zwar werden durch den Suchalgorithmus alle Züge schnell gefunden und ausgeführt, aber
welcher Zug das ist, wird eben genau durch diese Bewertungsfunktion bestimmt.
Die Bewertungsfunktion liefert die Bewertung einer Stellung zurück, ohne die Nachfolgezüge zu bestimmen. Sie setzt sich aus einer materiellen und einer positionellen Komponente zusammen. Die positionelle Komponente ergänzt die materielle, da die Stärke der
Spielfiguren auch von ihren Positionen untereinander abhängen. Das Schachprogramm
zeigt die Bewertung einer Spielsituation numerisch, in sogenannten Bauerneinheiten, an.
Wobei positive Werte Vorteile und negative Werte Nachteile für einen bestimmten Spieler
bedeuten.
Für die materielle Wertung werden für die auf dem Brett befindlichen Spielfiguren Werte
addiert. Dabei entsprechen Bauer = 100, Springer = 275, Läufer = 325, Turm = 465
und Dame = 900. Für Schwarz gelten die entsprechenden negativen Werte. Der König
braucht nicht mitgezählt zu werden, da beide Parteien während des Spiels nur einen König
haben.
Die positionelle Wertung zu bestimmen, ist eine Aufgabe von größerer Komplexität, in der
sich die verschiedenen Schachprogramme deutlich voneinander unterscheiden. Dabei wird
versucht, Stellungen aufgrund von schachrelevanten Parametern zu bewerten. Schachrelevante Parameter lassen sich grob klassifizieren in Königssicherheit, Bauernstruktur,
beherrschte und bedrohte Felder sowie Figurenentwicklung. So wird zum Beispiel eine
Stellung bei der die Türme noch eingeengt zwischen Springern und Bauern stehen schlechter bewertet als eine, bei der die Türme schon auf offenen Linien stehen. Man kann also
sagen, dass sich die positionelle Bewertung auf Heuristiken stützt. (Lüscher 2000)
Kapitel 5
Zusammenfassung und Ausblick
Im Rahmen der vorliegenden Arbeit wurde der Minimax-Algorithmus für ein Schachprogramm adaptiert und durch die Alpha-Beta-Suche und später durch die NegaScout-Suche
optimiert. Der vorgestellte und verbesserte Algorithmus wurde in einem Schachprogramm
implementiert.
An nächster Stelle wurden auch weitere Optimierungsverfahren vorgestellt, welche jedoch
nicht im Schachprogramm implementiert sind.
Die Wichtigkeit der Bewertungsfunktion ist in dieser Arbeit erwähnt, wird aber nicht
weiter vertieft. Eine sicherlich interessanter Ausblick wäre die Diskussion über das Lernen bzw. automatische Generieren solch einer Bewertungsfunktion (z. B. mit Neuronalen
Netzen).
Literaturverzeichnis
Fierz 2005
Fierz, Martin: Strategy Game Programming. 2005
Heuner 2002
Heuner: Schachprogrammierung. 2002
Lorenz 2006
Lorenz, Ulf: Der Alphabeta-Algorithmus für Spielbaumsuche. 2006
Lüscher 2000
Lüscher, Matthias: Automatisierte Generierung einer Bewertungs-
funktion für Schachendspiele, ETH Zürich, Diplomarbeit, 2000
Wikipedia.org 2006
Zwanzger 2004
Wikipedia.org: Alpha-Beta-Suche. 2006
Zwanzger, Johannes: Das Geheimnis der Schachprogramme. In:
Schachprogramme (2004), S. 48–55
Herunterladen