""" Chessteg Move Generation Module - KORRIGIERTE VERSION MIT DEBUG Vollständige Zuggenerierung mit allen Schachregeln """ import copy from typing import List, Dict, Any, Optional # Konstanten für Figurentypen (aus Core Engine) PAWN = 1 KNIGHT = 4 BISHOP = 3 ROOK = 5 QUEEN = 9 KING = 99 WHITE = 1 BLACK = -1 # Richtungen für 10x10 Board DIRECTIONS = { 'N': 10, 'S': -10, 'E': 1, 'W': -1, 'NE': 11, 'NW': 9, 'SE': -9, 'SW': -11 } # Springer-Züge (L-Form) KNIGHT_MOVES = [21, 19, 12, 8, -8, -12, -19, -21] class MoveGenerator: """ Vollständige Zuggenerierung für alle Figurentypen inklusive spezieller Regeln """ def __init__(self, engine): self.engine = engine def generate_moves(self, color: int) -> List[Dict[str, Any]]: """ Generiert alle legalen Züge für eine Farbe inklusive spezieller Züge Args: color: Farbe (1=weiß, -1=schwarz) Returns: List[Dict]: Liste der legalen Züge """ all_moves = [] for piece in self.engine.pieces: if not piece['captured'] and piece['color'] == color: piece_moves = self.generate_piece_moves(piece) all_moves.extend(piece_moves) # Spezielle Züge hinzufügen special_moves = self._generate_special_moves(color) all_moves.extend(special_moves) # Nur legale Züge zurückgeben (ohne Selbstschach) legal_moves = [move for move in all_moves if self.is_move_legal(move)] # DEBUG: Zeige Anzahl der generierten Züge # print(f"Generierte legale Züge für {color}: {len(legal_moves)}") return legal_moves def generate_legal_moves(self, color: int) -> List[Dict[str, Any]]: """Generiert legale Züge - Alias für generate_moves für Kompatibilität.""" return self.generate_moves(color) def generate_active_moves(self, color: int) -> List[Dict[str, Any]]: """Generiert nur aktive Züge (Schläge) für Quiescence Search.""" all_moves = self.generate_moves(color) active_moves = [] for move in all_moves: # Schlagzüge und Bauernumwandlungen als "aktiv" betrachten if (move.get('capture_pos') or move.get('special_type') == 'en_passant' or move.get('promotion_piece')): active_moves.append(move) return active_moves def is_move_legal(self, move: Dict[str, Any]) -> bool: """ Prüft, ob ein Zug legal ist (König nicht im Schach nach dem Zug) Args: move: Der Zug im Diktionär-Format Returns: bool: True wenn der Zug legal ist """ piece_id = move['piece_id'] piece = self.engine.get_piece_by_id(piece_id) if not piece: return False color = piece['color'] # Führe den Zug temporär aus snapshot = self.engine.take_snapshot() # Verwende die niedrigstufige Methode, um den Zug durchzuführen, # ohne die white_turn-Variable zu ändern. self.engine._apply_move_internal(move) is_legal = not self.engine.is_king_in_check(color) # Mache den Zug rückgängig self.engine.restore_snapshot(snapshot) return is_legal def _generate_special_moves(self, color: int) -> List[Dict[str, Any]]: """ Generiert Rochade und Bauernumwandlungen - KORRIGIERTE VERSION """ special_moves = [] # Rochade-Züge rules = self.engine.rules # Kleine Rochade if rules.validate_castling(color, 'kingside'): if color == WHITE: king_pos = 25 # e1 king_to = 27 # g1 rook_pos = 28 # h1 rook_to = 26 # f1 else: king_pos = 95 # e8 king_to = 97 # g8 rook_pos = 98 # h8 rook_to = 96 # f8 king = self.engine.get_piece_at(king_pos) rook = self.engine.get_piece_at(rook_pos) if king and king['type'] == KING and rook and rook['type'] == ROOK: special_moves.append({ 'piece_id': king['id'], 'piece': king, # 🚨 WICHTIG: Füge piece-Objekt hinzu 'type': KING, # 🚨 WICHTIG: Explizit setzen 'color': color, # 🚨 WICHTIG: Explizit setzen 'from_pos': king_pos, 'to_pos': king_to, 'capture_pos': None, 'promotion_piece': None, 'special_type': 'castling', 'rook_id': rook['id'], 'rook_from': rook_pos, 'rook_to': rook_to }) # Große Rochade if rules.validate_castling(color, 'queenside'): if color == WHITE: king_pos = 25 # e1 king_to = 23 # c1 rook_pos = 21 # a1 rook_to = 24 # d1 else: king_pos = 95 # e8 king_to = 93 # c8 rook_pos = 91 # a8 rook_to = 94 # d8 king = self.engine.get_piece_at(king_pos) rook = self.engine.get_piece_at(rook_pos) if king and king['type'] == KING and rook and rook['type'] == ROOK: special_moves.append({ 'piece_id': king['id'], 'piece': king, # 🚨 WICHTIG 'type': KING, # 🚨 WICHTIG 'color': color, # 🚨 WICHTIG 'from_pos': king_pos, 'to_pos': king_to, 'capture_pos': None, 'promotion_piece': None, 'special_type': 'castling', 'rook_id': rook['id'], 'rook_from': rook_pos, 'rook_to': rook_to }) return special_moves def generate_piece_moves(self, piece: Dict[str, Any]) -> List[Dict[str, Any]]: """ Generiert alle möglichen Züge für eine einzelne Figur (ohne Legalitätsprüfung) """ piece_type = piece['type'] if piece_type == PAWN: return self._generate_pawn_moves(piece) elif piece_type == ROOK: return self._generate_sliding_moves(piece, ['N', 'S', 'E', 'W']) elif piece_type == BISHOP: return self._generate_sliding_moves(piece, ['NE', 'NW', 'SE', 'SW']) elif piece_type == QUEEN: return self._generate_sliding_moves(piece, list(DIRECTIONS.keys())) elif piece_type == KNIGHT: return self._generate_knight_moves(piece) elif piece_type == KING: return self._generate_king_moves(piece) return [] def _create_move(self, piece: Dict[str, Any], to_pos: int, capture_pos: Optional[int] = None, special_type: Optional[str] = None, promotion_piece: Optional[int] = None) -> Dict[str, Any]: """ Erstellt ein standardisiertes Zug-Diktionär - KORRIGIERTE VERSION """ move_dict = { 'piece_id': piece['id'], 'piece': piece, 'type': piece['type'], # 🚨 KRITISCH: Figurentyp 'color': piece['color'], # 🚨 KRITISCH: Farbe 'from_pos': piece['position'], 'to_pos': to_pos, 'capture_pos': capture_pos, 'promotion_piece': promotion_piece, 'special_type': special_type } # Markiere Schlagzüge für Move Ordering if capture_pos is not None: move_dict['is_capture'] = True # 🚨 NEU: Setze promotion_type falls promotion_piece vorhanden if promotion_piece is not None: move_dict['promotion_type'] = promotion_piece return move_dict def _generate_pawn_moves(self, pawn: Dict[str, Any]) -> List[Dict[str, Any]]: """ Generiert Züge für einen Bauern """ moves = [] color = pawn['color'] start_pos = pawn['position'] # Die Richtung, in die der Bauer zieht forward = DIRECTIONS['N'] * color # Position der 1. und 2. Reihe (bezogen auf das 10x10 Board) # Die 1. Reihe (aus Benutzersicht) ist die Reihe 2, die 8. Reihe ist die Reihe 9. # Weiße Bauern starten auf Reihe 3 (31-38), Schwarze auf Reihe 8 (81-88). start_row = 3 if color == WHITE else 8 # ========================================================================= # 1. Vorwärtszug (Ein Feld) # ========================================================================= one_step = start_pos + forward if self.engine.get_piece_at(one_step) is None: # KORREKTUR: Eigene Implementierung für Promotions-Prüfung if self._is_promotion_rank(one_step, color): # Füge alle Promotion-Züge (Dame, Turm, Läufer, Springer) hinzu for p_type in [QUEEN, ROOK, BISHOP, KNIGHT]: moves.append(self._create_move(pawn, one_step, promotion_piece=p_type)) else: moves.append(self._create_move(pawn, one_step)) # ========================================================================= # 2. Vorwärtszug (Zwei Felder) # ========================================================================= if pawn['position'] // 10 == start_row: two_step = start_pos + 2 * forward if self.engine.get_piece_at(two_step) is None: moves.append(self._create_move(pawn, two_step, special_type='double_pawn_push')) # ========================================================================= # 3. Schlagzüge (Diagonal) # ========================================================================= capture_dirs = [forward + DIRECTIONS['E'], forward + DIRECTIONS['W']] for capture_dir in capture_dirs: target_pos = start_pos + capture_dir if not self.engine.is_valid_position(target_pos): continue target_piece = self.engine.get_piece_at(target_pos) # Normaler Schlagzug if target_piece and target_piece['color'] != color: if self._is_promotion_rank(target_pos, color): # Promotion-Schlagzug for p_type in [QUEEN, ROOK, BISHOP, KNIGHT]: moves.append(self._create_move(pawn, target_pos, target_pos, promotion_piece=p_type)) else: moves.append(self._create_move(pawn, target_pos, target_pos)) # En Passant elif target_pos == self.engine.rules.en_passant_target: # Das geschlagene Bauernfeld ist immer 10 Schritte (eine Reihe) hinter dem Ziel captured_pawn_pos = target_pos - forward moves.append(self._create_move(pawn, target_pos, captured_pawn_pos, special_type='en_passant')) return moves def _is_promotion_rank(self, position: int, color: int) -> bool: """ Prüft ob eine Position auf der Umwandlungsreihe für die gegebene Farbe liegt """ row = position // 10 # Weißer Bauer auf 8. Reihe (Row 9) oder schwarzer Bauer auf 1. Reihe (Row 2) return (color == WHITE and row == 9) or (color == BLACK and row == 2) def _generate_knight_moves(self, piece: Dict[str, Any]) -> List[Dict[str, Any]]: """ Generiert Züge für einen Springer """ moves = [] current_pos = piece['position'] # KORREKTUR: Verwende 'position' color = piece['color'] for move in KNIGHT_MOVES: target_pos = current_pos + move if self.engine.is_valid_position(target_pos): target_piece = self.engine.get_piece_at(target_pos) if target_piece is None: # Leeres Feld moves.append(self._create_move(piece, target_pos)) elif target_piece['color'] != color: # Schlagzug moves.append(self._create_move(piece, target_pos, target_pos)) return moves def _generate_king_moves(self, piece: Dict[str, Any]) -> List[Dict[str, Any]]: """ Generiert Züge für den König (Rochade wird separat in _generate_special_moves behandelt) """ moves = [] current_pos = piece['position'] # KORREKTUR: Verwende 'position' color = piece['color'] for direction in DIRECTIONS.values(): target_pos = current_pos + direction if self.engine.is_valid_position(target_pos): target_piece = self.engine.get_piece_at(target_pos) if target_piece is None: # Leeres Feld moves.append(self._create_move(piece, target_pos)) elif target_piece['color'] != color: # Schlagzug moves.append(self._create_move(piece, target_pos, target_pos)) return moves def _generate_sliding_moves(self, piece: Dict[str, Any], directions: List[str]) -> List[Dict[str, Any]]: """ Generiert Züge für gleitende Figuren (Dame, Turm, Läufer) """ moves = [] current_pos = piece['position'] # KORREKTUR: Verwende 'position' color = piece['color'] for direction_str in directions: direction = DIRECTIONS[direction_str] field = current_pos + direction while self.engine.is_valid_position(field): target_piece = self.engine.get_piece_at(field) if target_piece is None: # Leeres Feld: Zug hinzufügen und weiter in diese Richtung moves.append(self._create_move(piece, field)) elif target_piece['color'] != color: # Gegnerische Figur: Schlagzug hinzufügen und Schleife beenden moves.append(self._create_move(piece, field, field)) break else: # Eigene Figur: Blockiert, Schleife beenden break field += direction return moves def get_attacked_squares(self, piece: Dict[str, Any]) -> List[int]: """ Gibt eine Liste aller Felder zurück, die von einer bestimmten Figur angegriffen werden. Wird für die Schachprüfung verwendet. """ piece_type = piece['type'] attacked_squares = [] # KORREKTUR: Die Position der Figur ist IMMER 'position', NICHT 'pos'. current_pos = piece['position'] color = piece['color'] if piece_type == PAWN: forward = DIRECTIONS['N'] * color # Bauern-Angriffszüge sind diagonal (keine normalen Züge) capture_dirs = [forward + DIRECTIONS['E'], forward + DIRECTIONS['W']] for capture_dir in capture_dirs: target_pos = current_pos + capture_dir if self.engine.is_valid_position(target_pos): # Nur die Angriffsfelder zurückgeben, unabhängig davon, ob eine Figur dort steht attacked_squares.append(target_pos) elif piece_type == KNIGHT: for move in KNIGHT_MOVES: target_pos = current_pos + move if self.engine.is_valid_position(target_pos): attacked_squares.append(target_pos) elif piece_type == KING: for direction in DIRECTIONS.values(): target_pos = current_pos + direction if self.engine.is_valid_position(target_pos): attacked_squares.append(target_pos) elif piece_type in [ROOK, BISHOP, QUEEN]: if piece_type == ROOK: directions = ['N', 'S', 'E', 'W'] elif piece_type == BISHOP: directions = ['NE', 'NW', 'SE', 'SW'] else: # QUEEN directions = list(DIRECTIONS.keys()) for direction_str in directions: direction = DIRECTIONS[direction_str] field = current_pos + direction # Dies ist der Teil, der im Traceback (Zeile 642) den Fehler verursachte, # wenn er in einer Unterfunktion fälschlicherweise 'pos' verwendete. # Hier ist die Korrektur: Die Startposition ist current_pos = piece['position']. while self.engine.is_valid_position(field): attacked_squares.append(field) target_piece = self.engine.get_piece_at(field) # Bei Angriffsgenerierung stoppen wir nur, wenn wir auf eine Figur treffen if target_piece is not None: break field += direction return attacked_squares def is_square_attacked(self, position: int, attacker_color: int) -> bool: """ Prüft, ob ein Feld von einer Figur der gegebenen Farbe angegriffen wird Args: position: Zu prüfendes Feld attacker_color: Farbe der Angreifer Returns: bool: True wenn Feld angegriffen wird """ for piece in self.engine.pieces: if (piece['captured'] or piece['color'] != attacker_color): continue attacked_squares = self.get_attacked_squares(piece) if position in attacked_squares: return True return False # Test des erweiterten MoveGenerators if __name__ == "__main__": print("Testing Extended MoveGenerator...") from core import ChesstegEngine engine = ChesstegEngine() move_gen = MoveGenerator(engine) # Test: Züge für Weiß generieren white_moves = move_gen.generate_moves(1) print(f"White moves in initial position: {len(white_moves)}") # Test: Spezielle Züge castling_moves = [m for m in white_moves if m.get('special_type') == 'castling'] print(f"Castling moves available: {len(castling_moves)}") # Test: Springer-Züge knights = [p for p in engine.pieces if p['type'] == 4 and p['color'] == 1] for knight in knights: knight_moves = move_gen._generate_knight_moves(knight) print(f"Knight at {engine._position_to_notation(knight['position'])} moves: {len(knight_moves)}")