Nach den eher theoretischen Erörterungen zum Thema Stellungsanalyse und Kennzahlen im letzten Artikel wärmen wir uns für die kommenden Aufgaben spielerisch etwas auf. Die Werkzeuge dazu liegen ja nun bereit.
- wir schreiben ein kleines Python-Programm für ein Jupyter-Notebook, dass uns als Demonstrations-Tafel dienen soll.
- Wir verwenden die bereits bekannte python-chess Programm-Bibliothek, die viele nützliche Funktionen rund um das Schachspiel bietet.
- für unser Endziel, den besten Schachspieler aller Zeiten zu finden, benutzen wir kein Geringeres als das aktuell spielstärkste Schachprogramm der Welt, Stockfish 17.1.
eine Partie-Analyse als Fingerübung
Wir wählen als Prototypen für unsere weiteren Analysen die in Schachkreisen weithin bekannte Opernhaus-Partie, die wir in einem vorherigen Artikel bereits erwähnt hatten. Obwohl diese Partie inzwischen auch in unserer Schachdatenbank enthalten ist, laden wir sie in unserem Demo-Python-Programm hier ganz simpel aus einer PGN-Datei, welche auch nur diese eine Partie umfasst. Damit verkomplizieren wir die Dinge nicht unnötig und richten den Blick auf das Wesentliche, die Analyse.
Hier die Opernhaus-Partie im leicht verständlichen PGN-Format
[Event ""]
[Site "Paris"]
[Date "1858.??.??"]
[Round ""]
[White "Morphy, Paul"]
[Black "Von Braunschweig/Isouard"]
[Result "1-0"]
1.e4 e5 2.Nf3 d6 3.d4 Bg4 4.dxe5 Bxf3 5.Qxf3 dxe5 6.Bc4 Nf6 7.Qb3 Qe7 8.
Nc3 c6 9.Bg5 b5 10.Nxb5 cxb5 11.Bxb5+ Nbd7 12.O-O-O Rd8 13.Rxd7 Rxd7 14.
Rd1 Qe6 15.Bxd7+ Nxd7 16.Qb8+ Nxb8 17.Rd8# 1-0
Unser kleines Analyseprogramm ist, wie unten zu sehen, mit weniger als 50 Zeilen Python-Code sehr kompakt und leicht zu verstehen.
- in den Zeilen 1-3 wird die benötigte Schach-Funktionalität aus der chess-Bibliothek importiert.
- in Zeile 6 und 8 wird quasi der „Schachmotor“ Stockfish 17.1 integriert.
- wir übergehen die weiteren Hilfsfunktionen und sehen uns stattdessen das Hauptprogramm in den Zeilen 37-47 an. Dort lesen wir zunächst die Partie aus der Datei opera_game.pgn ein und zeigen nachfolgend in einer Programmschleife Zug für Zug die zugehörige Stellung grafisch an („display(board)“). Im Anschluss daran führen wir eine Stellungsanalyse aus und lassen uns die dabei berechneten Kennzahlen anzeigen.

Wie in Zeile 44 zu sehen ist, übergeben wir drei Parameter an die Analysefunktion
- die aktuelle Schachstellung als FEN Zeichenkette codiert
- wir wollen nicht nur die (beste) Hauptvariante sehen, sondern die beiden besten weiteren Nebenvarianten. Dementsprechend übergeben wir hier den Wert 3 für den Parameter num_moves_to_return.
- Damit das Schachprogramm nicht alle möglichen Züge und Gegenzüge bis zum Ende der Welt rechnet, limitieren wir die Suchtiefe mittels des Parameters depth_limit auf 20 Halbzüge. Dies ist von einem aktuellen schnellen Prozessor in wenigen Sekunden durchführbar.
Sehen wir uns einige ausgewählte Stellungsbilder sowie deren Bewertung der Reihe nach an.

Die Ausgangsstellung ist natürgemäß wenig spannend. Stockfish bewertet die Stellung für Weiß mit 0,4 Bauerneinheiten als etwas besser. Das entspricht dem üblichen Anzugsvorteil von Weiß. Interessant ist allenfalls die Bevorzugung eines offenen Spiels mit 1) e2-e4. Ein mögliches Matt wird per Voreinstellung separat ausgewiesen („mate_score“) und nicht in centipawns umgerechnet. Das kann man aber auf Wunsch ändern. Die beiden alternativen Züge 1)d2-d4 und 1)g1-f3 sowie deren Hauptvarianten (bei der Ausgabe hier auf 3 Züge verkürzt), erhalten erwartungsgemäß schlechtere Bewertungen.
Wir springen direkt zum Stellungsbild nach dem dritten Zug von Schwarz

Wir sehen, dass Weiss nach dem letzten Zug von Schwarz 3)…, c8g4 bereits um mehr als einen ganzen Bauern in Vorteil ist. Allerdings bevorzugt Stockfish einen anderen Zug als von Weiß tatsächlich gespielt wurde.
Wir kommen zur entscheidenden Stelle dieser Partie vor dem zehnten Zug von Weiß. Schwarz hatte mit dem Zug 9)…, b7-b5 seine Stellung gerade nochmal um eine ganze Bauern-Einheit verschlechtert.

Bei dem nun innerhalb der nächsten 10 Züge folgenden Angriff von Weiß inklusive Damenopfer wählte Paul Morphy jeweils exakt die von Stockfish errechneten besten Züge aus und krönte sein Spiel mit einem furiosen Matt im siebzehnten Zug.
Nach dem vierzehnten Zug von Schwarz empfiehlt Stockfish den starken Zug 15)b5-d7! für Weiß, der die Partie schnell entscheidet. Dies ist auch eine der Schlüsselstellungen in diesem Spiel, auf die wir gleich noch einmal zurückkommen!

Nach dem fünfzehnten Zug von Schwarz ist das Matt nicht mehr abwendbar, was Stockfish auch korrekt berechnet.

Die wunderschöne Schluss-Stellung darf hier ebenfalls nicht vorenthalten werden!

Da ist noch eine Sache…
Wir sehen also, dass Stockfish in seinen Bewertungen unseren Erwartungen entspricht und in dieser Partie jeweils die besten Züge empfiehlt. Im vorigen Artikel hatten wir weitere Kennzahlen identifiziert, an denen wir interessiert sind. Doch wir wollen diesem umfangreichen Thema lieber einen nachfolgenden Artikel widmen und uns hier zum Einstieg auf Grundsätzliches beschränken.
Stattdessen wollen wir der Frage nachgehen, wie ein Schachprogramm (hier Stockfisch) eigentlich funktioniert und wie wir die Berechnung der besten Züge über die Parametrisierung beeinflussen können, konkret am Beispiel unserer Opernhaus-Partie.
Die Details von Zugauswahl und Bewertungsfunktion eines Schachprogramms zu erläutern wollen wir uns hier ersparen, sondern nur eine wesentliche Grundfunktion aller Schachprogramme kurz beleuchten, die sog. Baumsuche, anschaulich erklärt z.B. auf Minimax-Verfahren oder Alpha-Beta-Suche.

Wenn man sich vorstellt, dass in jeder gegebenen Schachposition normalerweise mehrere legale Züge für die jeweilige Seite möglich sind und auf jeden dieser Züge wiederum mehrere Gegenzüge erfolgen können und sich das immer weiter fortsetzt, bis eine Seite durch Schachmatt gewinnt oder die Partie zwangsläufig Remis endet (z.B.wenn nur noch zwei Könige auf dem Spielfeld vorhanden sind), kann man erahnen, dass in den allermeisten Fällen eine vollständige Baumsuche aufgrund des Umfanges unmöglich ist. Es gibt zwar Optimierungsmöglichkeiten wie etwa die Monte-Carlo-Baumsuche, aber das grundsätzliche Problem der exponentiellen kombinatorischen Explosion bleibt bestehen.
Was lernen wie daraus? Zum einen müssen wir uns von der Vorstellung verabschieden, dass selbst ein modernes Schachprogramm in jeder beliebigen Situation tatsächlich den objektiv besten Zug finden kann, selbst wenn es heute zusätzlich Verfahren der Künstlichen Intelligenz nutzen kann. Zum anderen werden wir immer einen Kompromiss zwischen guten Analyse-Ergebnissen und einer akzeptablen Rechenzeit finden müssen. D.h., wir können Stockfish in der Regel glauben, aber nicht blind vertrauen!
Sehen wir uns das Phänomen dieses Horizont-Effektes an unserem Beispiel an. Ohne näher darauf einzugehen, hatten wir in unserem obigen Analyse-Programm die Tiefe bei der Baumsuche kurzerhand auf 20 Halbzüge festgelegt (Parameter depth der chess..engine). In dieser Partie war die gewählte Suchtiefe offenbar ausreichend, um die besten Züge finden zu können und die Bewertung für eine Position erfolgte in nur wenigen Sekunden. Allerdings gibt es auch viele Spielpositionen in der Praxis, in denen die Analyse damit suboptimale oder gar völlig falsche Ergebnisse liefert!
Wir demonstrieren diesen Sachverhalt in unserem Beispiel dadurch, indem wir Stockfish nur eine Suchtiefe von 6 Halbzügen gestatten und es damit neu starten. Trotz dieser starken Einschränkung schlägt sich das Programm überraschend gut und findet die meisten guten Züge. Da Stockfish ein cleveres, hochoptimiertes Programm ist, fängt es nicht etwa nach jedem neuen Zug von vorne an, sämtliche weiteren Möglichkeiten zu berechnen, sondern nutzt die erzielten Such-Ergebnisse aus den vorherigen Zuganalysen. Wenigstens solange, bis wir die engine beenden oder explizit die Zwischenergebnisse löschen lassen.
Daher fügen wir unserem obigen Programm am Ende noch eine Zeile hinzu und starten es dann zweimal hintereinander neu, um sicher zu gehen, dass nicht etwa noch Zwischenergebnisse von vorherigen Läufen mit Suchtiefe 20 vorhanden sind.
...
with open("../data/opera_game.pgn", encoding="utf8") as pgn:
game = chess.pgn.read_game(pgn)
while game != None:
board = game.board()
display(board)
if not board.outcome():
for line in analyze_position(
board.fen(), num_moves_to_return=3, depth_limit=6
):
print(line)
game = game.next()
engine.quit()
Wir überspringen die neuen Stellungsbewertungen von Stockfish und kommen direkt zur Position vor dem fünfzehnten Zug von Weiß.

Im direkten Vergleich zum vorherigen Lauf mit Suchtiefe 20 findet Stockfish mit der Beschränkung auf 6 Halbzüge den besten Zug 15)b5-d7 von Weiß nicht mehr, ja zieht ihn nicht einmal in Erwägung für die drei angeblich besten Züge!
Wir beenden diesen Artikel daher mit einem lachenden und einem weinenden Auge. Die Schachanalyse funktioniert sehr gut und ist leicht zu programmieren. Auf der anderen Seite müssen wir noch einige Arbeit für weitere Kennzahlen in die Programmierung stecken. Und wir müssen ein ausgewogenes Verhältnis zwischen hinreichend guten Ergebnissen und aktzeptabler Laufzeit finden. Dabei wollen wir Stockfish dann auch so parametrisieren, dass es unter Ausnutzung der vorhandenen Rechnerkapazität mit optimaler Geschwindigkeit läuft.