Inhalt
Aktueller Ordner:
chessteg_modular/enginecore.py
"""
Chessteg Core Engine Module - KORRIGIERTE VERSION (Behebt IndexError)
"""
import copy
import sys
import os
from typing import List, Dict, Any, Optional
# Füge den aktuellen Pfad zum Python-Pfad hinzu für relative Imports
sys.path.append(os.path.dirname(__file__))
# Konstanten für bessere Lesbarkeit
WHITE = 1
BLACK = -1
EMPTY = 0
DUMMY = 100
# Figuren-Typen
PAWN = 1
KNIGHT = 4
BISHOP = 3
ROOK = 5
QUEEN = 9
KING = 99
# Figuren-Symbole für die Darstellung (Hier in der Engine, um sie unabhängig zu machen)
PIECE_SYMBOLS = {
1: '♙', # Weißer Bauer
-1: '♟', # Schwarzer Bauer
4: '♘', # Weißer Springer
-4: '♞', # Schwarzer Springer
3: '♗', # Weißer Läufer
-3: '♝', # Schwarzer Läufer
5: '♖', # Weißer Turm
-5: '♜', # Schwarzer Turm
9: '♕', # Weiße Dame
-9: '♛', # Schwarze Dame
99: '♔', # Weißer König
-99: '♚', # Schwarzer König
EMPTY: ' '
}
# Import der Komponenten (relative Imports in einer echten Modulstruktur)
try:
from move_generation import MoveGenerator
from evaluation import PositionEvaluator
from search import SearchAlgorithm
from rules import ChessRules
except ImportError:
print("WARNING: Relative imports failed. Using dummy components.")
class DummyComponent:
def __init__(self, *args): pass
def generate_moves(self, *args): return []
def evaluate_position(self): return 0
def computer_move(self): return None
MoveGenerator = DummyComponent
PositionEvaluator = DummyComponent
SearchAlgorithm = DummyComponent
ChessRules = DummyComponent
class ChesstegEngine:
"""
Die zentrale Schach-Engine. Verwaltet den Spielzustand, Figuren und die
Schnittstellen zu den modularen Komponenten (Züge, Bewertung, Suche, Regeln).
"""
def __init__(self):
self.board = [EMPTY] * 120 # 10x12 Array, die Ränder sind DUMMY
self.pieces: List[Dict[str, Any]] = []
self.white_turn = True
self.checkmate = False
self.stalemate = False
self.next_piece_id = 1 # Eindeutige ID für jede Figur
self.move_history: List[Dict[str, Any]] = [] # Speichert vergangene Zustände
# Komponenten initialisieren
self.move_generator = MoveGenerator(self)
self.evaluator = PositionEvaluator(self)
self.search_algorithm = SearchAlgorithm(self)
self.rules = ChessRules(self) # Regeln müssen vor initialize_pieces initialisiert werden
self.initialize_board()
self.initialize_pieces()
self.synchronize_board_state()
# =========================================================================
# ZUSTANDSVERWALTUNG
# =========================================================================
def initialize_board(self):
"""Setzt das 120-Felder-Board (10x12) mit DUMMY-Rändern auf."""
# Alle Felder auf EMPTY setzen
self.board = [EMPTY] * 120 # Indices 0 bis 119
# KORREKTUR: Ränder setzen
# 1. Unterer und oberer vollständiger Rand (Rows 0 und 11, jeweils 10 Felder)
for i in range(10): # i von 0 bis 9 (10 Iterationen)
# Row 0 (Indices 0 bis 9)
self.board[i] = DUMMY
# Row 11 (Indices 110 bis 119)
self.board[110 + i] = DUMMY
# 2. Linke und rechte Ränder (Spalten 0 und 9) für Rows 1 bis 10
for i in range(1, 11): # i von 1 bis 10 (10 Iterationen)
self.board[i * 10] = DUMMY # Col 0 (z.B. 10, 20, ..., 100)
self.board[i * 10 + 9] = DUMMY # Col 9 (z.B. 19, 29, ..., 109)
def initialize_pieces(self):
"""Setzt die Figuren auf die Standard-Anfangsstellung."""
self.pieces.clear()
self.next_piece_id = 1
# Initialisierung der Regeln (für Castling Rights etc.)
self.rules.__init__(self)
# Schwarze Figuren (Reihe 9 und 8) - A8=91 bis H8=98; A7=81 bis H7=88
self._add_piece(ROOK, BLACK, 91)
self._add_piece(KNIGHT, BLACK, 92)
self._add_piece(BISHOP, BLACK, 93)
self._add_piece(QUEEN, BLACK, 94)
self._add_piece(KING, BLACK, 95)
self._add_piece(BISHOP, BLACK, 96)
self._add_piece(KNIGHT, BLACK, 97)
self._add_piece(ROOK, BLACK, 98)
for i in range(81, 89):
self._add_piece(PAWN, BLACK, i)
# Weiße Figuren (Reihe 2 und 3) - A1=21 bis H1=28; A2=31 bis H2=38
for i in range(31, 39):
self._add_piece(PAWN, WHITE, i)
self._add_piece(ROOK, WHITE, 21)
self._add_piece(KNIGHT, WHITE, 22)
self._add_piece(BISHOP, WHITE, 23)
self._add_piece(QUEEN, WHITE, 24)
self._add_piece(KING, WHITE, 25)
self._add_piece(BISHOP, WHITE, 26)
self._add_piece(KNIGHT, WHITE, 27)
self._add_piece(ROOK, WHITE, 28)
self.white_turn = True
self.checkmate = False
self.stalemate = False
self.move_history.clear()
self.synchronize_board_state()
def _add_piece(self, piece_type: int, color: int, position: int):
"""Fügt dem Figuren-Array eine neue Figur hinzu."""
piece_value = piece_type * color
symbol_key = piece_value
new_piece = {
'id': self.next_piece_id,
'type': piece_type,
'color': color,
'value': piece_value,
'position': position,
'captured': False,
'has_moved': False,
'symbol': PIECE_SYMBOLS.get(symbol_key, '?')
}
self.pieces.append(new_piece)
self.next_piece_id += 1
def synchronize_board_state(self, silent=False):
"""
Stellt sicher, dass das 120-Felder-Board den aktuellen Positionen
im `pieces` Array entspricht.
"""
# 1. Board resetten (ohne DUMMY-Felder zu überschreiben)
self.initialize_board()
# 2. Figuren positionieren
for piece in self.pieces:
if not piece['captured']:
position = piece['position']
piece_value = piece['value']
if self.is_valid_position(position):
self.board[position] = piece_value
else:
if not silent:
print(f"WARNUNG: Figur ID {piece['id']} an ungültiger Position {position}")
piece['captured'] = True
def get_piece_at(self, position: int) -> Optional[Dict[str, Any]]:
"""Gibt das Figuren-Objekt an einer 10x10 Position zurück."""
for piece in self.pieces:
if not piece['captured'] and piece['position'] == position:
return piece
return None
def get_piece_by_id(self, piece_id: int) -> Optional[Dict[str, Any]]:
"""Gibt das Figuren-Objekt anhand der ID zurück."""
for piece in self.pieces:
if piece['id'] == piece_id:
return piece
return None
def get_king(self, color: int) -> Optional[Dict[str, Any]]:
"""Gibt das König-Objekt der angegebenen Farbe zurück."""
for piece in self.pieces:
if piece['type'] == KING and piece['color'] == color and not piece['captured']:
return piece
return None
def is_valid_position(self, position: int) -> bool:
"""Prüft, ob eine Position innerhalb des 8x8 Spielfeldes liegt (21-98)."""
if 20 < position < 100:
col = position % 10
return 1 <= col <= 8
return False
# =========================================================================
# ZUG-AUSFÜHRUNG
# =========================================================================
def make_move(self, move: Dict[str, Any]) -> bool:
"""Führt einen Zug aus und aktualisiert den Spielzustand - KORRIGIERTE VERSION"""
# 1. Speichere den aktuellen Zustand vor der Ausführung
self._save_state()
# 2. Führe den Zug durch (Figuren-Update)
piece = self.get_piece_at(move['from_pos'])
if not piece:
print(f"❌ Keine Figur auf Startposition {self._position_to_notation(move['from_pos'])}")
self._restore_state()
return False
# 🚨 NEU: Spezielle Zug-Behandlung VOR der normalen Ausführung
# Rochade
if move.get('special_type') == 'castling':
return self._execute_castling_move(move, piece)
# En Passant
if move.get('special_type') == 'en_passant':
return self._execute_en_passant_move(move, piece)
# Bauernumwandlung
if move.get('promotion_piece'):
return self._execute_promotion_move(move, piece)
# Normale Zugausführung
captured_piece = self.get_piece_at(move['to_pos'])
if captured_piece:
captured_piece['captured'] = True
move['captured_piece_id'] = captured_piece['id']
else:
move['captured_piece_id'] = None
# Figur an Zielposition bewegen
piece['position'] = move['to_pos']
piece['has_moved'] = True
# 3. Spezielle Regeln ausführen
if hasattr(self.rules, 'process_move'):
self.rules.process_move(move, piece, captured_piece)
# 4. Board und Zustand synchronisieren
self.synchronize_board_state()
self.white_turn = not self.white_turn
# 5. Prüfe auf Schachmatt/Patt
self._check_game_end()
return True
def _execute_castling_move(self, move: Dict[str, Any], king: Dict[str, Any]) -> bool:
"""Führt Rochade aus"""
rook = self.get_piece_by_id(move['rook_id'])
if not rook:
return False
# Bewege König
king['position'] = move['to_pos']
king['has_moved'] = True
# Bewege Turm
rook['position'] = move['rook_to']
rook['has_moved'] = True
# Rochaderechte aktualisieren
self.rules._revoke_castling_rights(king['color'])
self.synchronize_board_state()
self.white_turn = not self.white_turn
self._check_game_end()
return True
def _execute_en_passant_move(self, move: Dict[str, Any], pawn: Dict[str, Any]) -> bool:
"""Führt en Passant aus"""
# Bewege Bauer
pawn['position'] = move['to_pos']
# Schlage gegnerischen Bauer
captured_pawn_pos = move['capture_pos']
captured_pawn = self.get_piece_at(captured_pawn_pos)
if captured_pawn:
captured_pawn['captured'] = True
move['captured_piece_id'] = captured_pawn['id']
self.synchronize_board_state()
self.white_turn = not self.white_turn
self._check_game_end()
return True
def _execute_promotion_move(self, move: Dict[str, Any], pawn: Dict[str, Any]) -> bool:
"""Führt Bauernumwandlung aus"""
# Normale Bewegung
captured_piece = self.get_piece_at(move['to_pos'])
if captured_piece:
captured_piece['captured'] = True
move['captured_piece_id'] = captured_piece['id']
# Bewege Bauer zur Umwandlungsposition
pawn['position'] = move['to_pos']
# Umwandlung durchführen
promotion_piece_type = move['promotion_piece']
pawn['type'] = promotion_piece_type
pawn['value'] = promotion_piece_type * pawn['color']
pawn['symbol'] = PIECE_SYMBOLS.get(pawn['value'], '?')
self.synchronize_board_state()
self.white_turn = not self.white_turn
self._check_game_end()
return True
def undo_move(self) -> bool:
"""Macht den letzten Zug rückgängig."""
if not self.move_history:
return False
# Lade den vorherigen Zustand
previous_state = self.move_history.pop()
self.pieces = previous_state['pieces']
self.white_turn = previous_state['white_turn']
self.checkmate = previous_state['checkmate']
self.stalemate = previous_state['stalemate']
self.next_piece_id = previous_state['next_piece_id']
# Lade den Zustand der Regeln
self.rules.en_passant_target = previous_state['rules']['en_passant_target']
self.rules.castling_rights = previous_state['rules']['castling_rights']
self.synchronize_board_state()
return True
def _save_state(self):
"""Speichert den aktuellen Spielzustand in der Historie."""
current_state = {
'pieces': copy.deepcopy(self.pieces),
'white_turn': self.white_turn,
'checkmate': self.checkmate,
'stalemate': self.stalemate,
'next_piece_id': self.next_piece_id,
'rules': {
'en_passant_target': self.rules.en_passant_target,
'castling_rights': copy.deepcopy(self.rules.castling_rights)
}
}
self.move_history.append(current_state)
def _restore_state(self):
"""Stellt den Zustand aus dem letzten Eintrag in der Historie wieder her."""
if self.move_history:
self.pieces = self.move_history[-1]['pieces']
self.white_turn = self.move_history[-1]['white_turn']
self.checkmate = self.move_history[-1]['checkmate']
self.stalemate = self.move_history[-1]['stalemate']
self.next_piece_id = self.move_history[-1]['next_piece_id']
self.rules.en_passant_target = self.move_history[-1]['rules']['en_passant_target']
self.rules.castling_rights = self.move_history[-1]['rules']['castling_rights']
self.synchronize_board_state()
# =========================================================================
# SCHACH/MATT/PATT-PRÜFUNG
# =========================================================================
def generate_all_moves(self, color: int) -> List[Dict[str, Any]]:
"""Generiert alle legalen Züge für die angegebene Farbe."""
# Aufruf der korrigierten Methode im MoveGenerator
return self.move_generator.generate_moves(color)
def is_king_in_check(self, color: int) -> bool:
"""Prüft, ob der König der gegebenen Farbe im Schach steht."""
king = self.get_king(color)
if not king:
return False
opponent_color = BLACK if color == WHITE else WHITE
return self.move_generator.is_square_attacked(king['position'], opponent_color)
def _check_game_end(self):
"""Prüft, ob die aktuelle Stellung Schachmatt oder Patt ist."""
color = WHITE if self.white_turn else BLACK
legal_moves = self.generate_all_moves(color)
if not legal_moves:
if self.is_king_in_check(color):
self.checkmate = True
self.stalemate = False
else:
self.checkmate = False
self.stalemate = True
else:
self.checkmate = False
self.stalemate = False
def is_game_over(self) -> bool:
"""Prüft, ob das Spiel beendet ist."""
return self.checkmate or self.stalemate
def take_snapshot(self):
"""Erstellt eine Momentaufnahme des aktuellen Zustands."""
return {
'pieces': copy.deepcopy(self.pieces),
'white_turn': self.white_turn,
'checkmate': self.checkmate,
'stalemate': self.stalemate,
'board': self.board.copy()
}
def restore_snapshot(self, snapshot):
"""Stellt einen Zustand aus einer Momentaufnahme wieder her."""
self.pieces = snapshot['pieces']
self.white_turn = snapshot['white_turn']
self.checkmate = snapshot['checkmate']
self.stalemate = snapshot['stalemate']
self.board = snapshot['board']
def _apply_move_internal(self, move):
"""Wendet einen Zug an, ohne den Spielzustand vollständig zu ändern (für Suchalgorithmus)."""
piece = self.get_piece_by_id(move['piece_id'])
if not piece:
return
# Alte Position leeren
self.board[piece['position']] = EMPTY
# Geschlagene Figur entfernen
if move.get('capture_pos'):
captured_piece = self.get_piece_at(move['capture_pos'])
if captured_piece:
captured_piece['captured'] = True
self.board[move['capture_pos']] = EMPTY
# Figur bewegen
piece['position'] = move['to_pos']
self.board[move['to_pos']] = piece['value']
# Promotion behandeln
if move.get('promotion_piece'):
piece['type'] = move['promotion_piece']
piece['value'] = move['promotion_piece'] * piece['color']
piece['symbol'] = PIECE_SYMBOLS.get(piece['value'], '?')
# =========================================================================
# HILFSFUNKTIONEN (NOTATION)
# =========================================================================
def _position_to_notation(self, position: int) -> str:
"""Konvertiert interne Position zu algebraischer Notation"""
files = ['', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', '']
row = position // 10
file = position % 10
if 1 <= file <= 8 and 2 <= row <= 9:
return f"{files[file]}{row - 1}"
return "??"
# =========================================================================
# EDITOR-FUNKTIONEN
# =========================================================================
def editor_place_piece(self, piece_type: int, color: int, position: int) -> bool:
"""Platziert eine Figur auf einem Feld (Editor-Funktion)"""
if not self.is_valid_position(position):
return False
# Entferne existierende Figur
existing_piece = self.get_piece_at(position)
if existing_piece:
existing_piece['captured'] = True
# Füge neue Figur hinzu (verwendet _add_piece mit neuer ID)
self._add_piece(piece_type, color, position)
# Synchronisiere das Board, um die neue Figur anzuzeigen
self.synchronize_board_state(silent=True)
return True
def editor_remove_piece(self, position):
"""Entfernt eine Figur vom Brett (Editor-Funktion)"""
piece = self.get_piece_at(position)
if piece:
piece['captured'] = True
self.synchronize_board_state(silent=True)
return True
return False
def editor_clear_board(self):
"""Entfernt alle Figuren vom Brett"""
for piece in self.pieces:
piece['captured'] = True
self.synchronize_board_state(silent=True)
def editor_standard_position(self):
"""Setzt die Standard-Anfangsstellung"""
self.initialize_pieces()