Die Schachengine mit Mini-Max-Suche
Die hier implementierte Schachengine stellt das zentrale Modul zur automatisierten Entscheidungsfindung in eurem Schachcomputer dar. Sie basiert auf dem klassischen Mini-Max-Algorithmus, einem fundamentalen Konzept der Spieltheorie, das systematisch nach dem optimalen Zug für eine Spielfigur sucht. Dabei geht der Algorithmus davon aus, dass sowohl der eigene als auch der gegnerische Spieler stets rational und optimal agieren.
Ziel ist es, für jede gegebene Spielsituation den bestmöglichen Zug zu finden – und zwar unter Einbezug der potenziellen Antworten des Gegners. Dies geschieht rekursiv, indem der Spielbaum bis zu einer konfigurierbaren Tiefe durchsucht wird.
Struktur und Komponenten
Die Engine setzt sich aus mehreren Schlüsselkomponenten zusammen:
MinMaxArg: Kapselt die aktuelle Suchtiefe und die Spielerfarbe (weiß oder schwarz). Dient als Steuerstruktur für den rekursiven Ablauf der Suche.Move: Repräsentiert einen möglichen Zug samt zugehöriger Bewertung (Score). Wird zur Ergebnisübermittlung und Anzeige genutzt.evaluate_all_possible_moves: Bewertet alle legalen Züge für die aktuelle Farbe auf Basis der evaluate()-Funktion des Bretts. Simuliert jeden Zug temporär, analysiert die Stellung und verwirft sie danach wieder.minMax: Kern des rekursiven Mini-Max-Algorithmus. Er simuliert eigene Züge und prüft rekursiv die besten Antworten des Gegners, um daraus den besten Gesamtzug abzuleiten.minMax_cached: Erweiterung des Standard-Mini-Max mit Caching. Doppelte Berechnungen identischer Spielstellungen werden vermieden, was zu erheblichen Performancegewinnen führt.suggest_move: Einstiegspunkt für die Engine. Startet den Suchprozess und liefert einen spielbaren Zug zurück.
Algorithmische Idee
Das Grundprinzip des Mini-Max-Algorithmus ist einfach, aber wirkungsvoll:
Simulation: Für jeden legalen Zug wird der resultierende Spielzustand simuliert.
Rekursion: Der Gegnerzug wird rekursiv ebenfalls mit Mini-Max simuliert.
Bewertung: Die resultierenden Stellungen werden numerisch bewertet (immer aus Sicht von Weiß).
Auswahl: Der Zug, der langfristig zur besten Bewertung führt, wird zurückgegeben.
Dabei wird zwischen zwei Modi unterschieden:
Maximierer (z. B. Weiß): Wählt den Zug mit dem höchsten Score.
Minimierer (z. B. Schwarz): Wählt den Zug mit dem niedrigsten Score.
Die Bewertung basiert auf der Board.evaluate()-Funktion, welche das Material, Mobilität und taktische Motive (z. B. Bedrohungen) berücksichtigt.
Leistungsoptimierung
Um den hohen Rechenaufwand zu reduzieren, implementiert die Engine ein Hash-basiertes Caching: Spielstellungen werden eindeutig identifiziert und einmal berechnete Bewertungen gespeichert. So lassen sich insbesondere in tieferen Suchtiefen erhebliche Performancegewinne erzielen.
Außerdem werden in der letzten Phase der Suche zufällige Elemente eingeführt, um die Vorhersagbarkeit zu verringern und die Spielvielfalt zu erhöhen (z. B. Zufallsauswahl aus gleichwertigen Top-Zügen).
Fazit
Diese Schachengine bietet eine robuste, nachvollziehbare und erweiterbare Grundlage für KI-gesteuertes Schachspiel. Sie verbindet algorithmische Eleganz mit praktischer Spielstärke – und bildet damit den entscheidenden Baustein für euren eigenen Schachcomputer.
Die Kern-Methoden der Engine
- engine.evaluate_all_possible_moves(board, minMaxArg, maximumNumberOfMoves=10)[source]
TODO: This method must evaluate all possible moves from all pieces of the current color.
So if minMaxArg.playAsWhite is True, all possible moves of all white pieces must be evaluated. And if minMaxArg.playAsWhite is False, all possible moves of all black pieces must be evaluated.
Iterate over all cells with pieces on them by calling the
iterate_cells_with_piecesmethod. For each piece, retrieve all valid moves by calling theget_valid_cellsmethod of that piece.In order to evaluate a valid move, first you need to place that piece on the respective cell. Call the
set_cellmethod to do so. Before doing so, remember the cell the piece is currently placed on as you will need to place it back later. Because placing a piece on a new cell could potential hit (and thus remove) an opposing piece currently placed on this cell, you need to remember the piece on the target cell as well. Callget_cellto retrieve that piece and store it in a variable.After the new board configuration is set in place, call the
evaluatemethod. You can use theMoveclass to store the move (piece and target cell) alongside its achieved evaluation score in a list.Restore the original board configuration by placing the piece in its original cell and restoring any potentially removed piece before moving on to the next move or piece.
Remember the
evaluatemethod always evaluates from WHITEs perspective, so a higher evaluation relates to a better position for WHITE.Moves must be sorted with respect o the scalar evaluation according to the minMax scheme. So if minMaxArg.playAsWhite is True, the moves must be sorted in descending order, so the best evaluated move for white is in array position 0. So if minMaxArg.playAsWhite is False, the moves must be sorted in ascending order, so the worst evaluated move for white is in array position 0.
Use the lists sort method and provide a proper key to the sorting algorithm, such that it sorts the moves according to the achieved score.
After sorting, a maximum number of moves as provided by the respective parameter must be returned. If there are more moves possible (in most situations there are), only return the top (or worst). Hint: Slice the list after sorting.
- engine.minMax(board, minMaxArg)[source]
TODO: This method implement the core mini-max search algorithm. The minMaxArg contain information about whether we are currently playing as white or black as well as the remaining search depth.
If minMaxArg.depth equals 1, no additional moves will be considered and the best evaluated move for the current board configuration should be returned. If, however, minMaxArg.depth is greater than 1, for each possible move of the current color, all answering moves of the opposite color need to be considered.
HINT: Start by calling
evaluate_all_possible_moveswith the provided board and minMaxArg in order to receive a list of evaluated moves. Note that the list is already sorted and contains only the best possible moves for the current color. This means if white is playing, the returned list will contain the best moves for white to make whereas if black is playing, the returned list will contain the best moves for black to make first.You will need to handle the special case that there are no possible moves left, meaning the
evaluate_all_possible_movesmethod returns an empty list. If there are no possible moves left, this means the current color has lost the game. Indicate that by returning an instance of theMoveclass where you set the score attribute to a very high or very low value (remember: Always think from whites perspective!)If the remaining search depth is greater than 1 (minMaxArg.depth > 1), iterate over all possible moves. Implement each move by placing the piece in question on the respective cell. Call the
set_cellmethod to do so. Before doing so, remember the cell the piece is currently placed on as you will need to place it back later. Because placing a piece on a new cell could potential hit (and thus remove) an opposing piece currently placed on this cell, you need to remember the piece on the target cell as well. Callget_cellto retrieve that piece and store it in a variable.After the new board configuration is set in place, call the
minMax_cachedmethod with the next minMaxArg (callnext)Overwrite the current moves score with the result from the recursive call.
Restore the original board configuration by placing the piece in its original cell and restoring any potentially removed piece before moving on to the next move.
After all moves and their counter-moves have been evaluated sort the list again in the correct order according to the (new) scores. If playing as white (minMaxArg.playAsWhite == True), you need to sort in descending order (highest ranked move first) whereas if playing as black (minMaxArg.playAsBlack == False), you need to sort in ascending order (lowest ranked move first). Use the lists sort method and define a proper key function to implement the needed search.
In the most basic implementation of the algorithm return the best move after sorting.
NOTE: You can improve the replayability of your chess engine a bit if you add some randomness to the evaluation of moves. For example, you can randomly increment and decrement each evaluation score. Alternatively you can return a random move out of the best three instead of simply the best one.
Feel free to experiment with this once you have the core algorithm properly implemented.
- Parameters:
board (
board.Board) – Reference to the board we need to play onminMaxArg (
MinMaxArg) – The combined arguments for the mini-max search algorithm.
- Returns:
Return the best move to make in the current situation.
- Return type:
Reference - MinMaxArg
- class engine.MinMaxArg(depth=3, playAsWhite=True)[source]
Helper Class for the MinMax Algorithm. This class stores the current search depth and whether we are playing as white or black in this stage.
Note: You don´t need to implement anything in this case, you can use it in the MinMax Algorithm as you seem fit.
Reference - Move
- class engine.Move(piece, cell, score)[source]
Helper class to store an evaluated move for the MinMax Algorithm. This class contains the piece that should be moved as well as the cell it should move into alongside with the evaluation score for this move.
Note: You don´t need to implement anything in this case, you can use it in the MinMax Algorithm as you seem fit.
Hilfsmethoden
- engine.minMax_cached(board, minMaxArg)[source]
A cached version of the minMax method. This methods caches results based on its parameters. If called again with a known board configuration and minMaxArgs, the result is taken from the cache instead of repeating the mini-max algorithm again. This can save computation time as it avoid to repeat evaluations over and over again.