Inhalt
Aktueller Ordner:
chessteg_modular/enginerules.py
"""
Chessteg Rules Module - KORRIGIERTE VERSION
Vollständige Implementierung spezieller Schachregeln
"""
import copy
from typing import Dict, Any, Optional, List, Tuple
class ChessRules:
"""
Implementiert spezielle Schachregeln: Rochade, en Passant, Bauernumwandlung
"""
def __init__(self, engine):
self.engine = engine
self.en_passant_target = None
self.castling_rights = {
'white_kingside': True,
'white_queenside': True,
'black_kingside': True,
'black_queenside': True
}
self.move_history = []
def process_move(self, move: Dict[str, Any], piece: Dict[str, Any], captured_piece: Optional[Dict[str, Any]]):
"""
Verarbeitet spezielle Zugregeln nach einem Zug
Args:
move: Der ausgeführte Zug
piece: Die bewegte Figur
captured_piece: Geschlagene Figur (falls vorhanden)
"""
# 🛡️ ROBUSTHEIT: FALLBACK für fehlende Felder
move_type = move.get('type')
if move_type is None and piece is not None:
move_type = piece['type']
move_color = move.get('color')
if move_color is None and piece is not None:
move_color = piece['color']
# En Passant Recht aktualisieren (nur für Bauern)
if move_type == 1: # PAWN
self.update_en_passant_after_move(move, move_color)
# Rochaderechte aktualisieren
self.update_castling_rights_after_move(move, piece)
# Bauernumwandlung prüfen
if self.check_pawn_promotion_required(move, piece):
move['requires_promotion'] = True
def validate_castling(self, color: int, side: str) -> bool:
"""
Prüft ob Rochade möglich ist
Args:
color: Farbe (1=weiß, -1=schwarz)
side: 'kingside' oder 'queenside'
Returns:
bool: True wenn Rochade legal
"""
# Grundvoraussetzungen prüfen
if not self._check_basic_castling_requirements(color, side):
return False
# König darf nicht im Schach stehen
if self.engine.is_king_in_check(color):
return False
# Felder zwischen König und Turm dür nicht angegriffen sein
if not self._check_castling_squares_safety(color, side):
return False
# Felder zwischen König und Turm müssen frei sein
if not self._check_castling_squares_empty(color, side):
return False
return True
def execute_castling(self, color: int, side: str) -> bool:
"""
Führt Rochade aus
Args:
color: Farbe (1=weiß, -1=schwarz)
side: 'kingside' oder 'queenside'
Returns:
bool: True wenn erfolgreich
"""
if not self.validate_castling(color, side):
return False
# König und Turm positionen bestimmen
king_from, king_to, rook_from, rook_to = self._get_castling_positions(color, side)
# König finden
king = next((p for p in self.engine.pieces
if p['type'] == 99 and p['color'] == color and not p['captured']), None)
if not king:
return False
# Turm finden
rook = next((p for p in self.engine.pieces
if p['type'] == 5 and p['color'] == color and
p['position'] == rook_from and not p['captured']), None)
if not rook:
return False
# Rochade ausführen
# 1. König bewegen
self.engine.board[king_from] = 0 # EMPTY
self.engine.board[king_to] = 99 * color
king['position'] = king_to
# 2. Turm bewegen
self.engine.board[rook_from] = 0 # EMPTY
self.engine.board[rook_to] = 5 * color
rook['position'] = rook_to
# Rochaderecht für diese Farbe aufheben
self._revoke_castling_rights(color)
# Zug zur History hinzufügen
castling_move = {
'type': 'castling',
'color': color,
'side': side,
'king_from': king_from,
'king_to': king_to,
'rook_from': rook_from,
'rook_to': rook_to
}
self.move_history.append(castling_move)
print(f"Castling executed: {self._get_castling_notation(color, side)}")
return True
def validate_en_passant(self, pawn: Dict[str, Any], target_pos: int) -> bool:
"""
Prüft en Passant
Args:
pawn: Bauer der schlagen will
target_pos: Zielfeld
Returns:
bool: True wenn en Passant legal
"""
# Grundvoraussetzungen prüfen
if not self._check_basic_en_passant_requirements(pawn, target_pos):
return False
# Es muss ein en Passant Ziel geben
if self.en_passant_target is None:
return False
# Zielposition muss dem en Passant Ziel entsprechen
if target_pos != self.en_passant_target:
return False
# Gegnerischer Bauer muss existieren
opponent_pawn_pos = self._get_opponent_pawn_position_for_en_passant(pawn, target_pos)
opponent_pawn = self.engine.get_piece_at(opponent_pawn_pos)
if not opponent_pawn or opponent_pawn['type'] != 1 or opponent_pawn['color'] != -pawn['color']:
return False
# Der Zug darf keinen Selbstschach verursachen
return self._validate_no_self_check_after_en_passant(pawn, target_pos, opponent_pawn_pos)
def execute_en_passant(self, pawn: Dict[str, Any], target_pos: int) -> bool:
"""
Führt en Passant aus
Args:
pawn: Bauer der schlägt
target_pos: Zielfeld
Returns:
bool: True wenn erfolgreich
"""
if not self.validate_en_passant(pawn, target_pos):
print(f"En Passant validation failed for {self._position_to_notation(pawn['position'])} to {self._position_to_notation(target_pos)}")
return False
# Gegnerischen Bauer finden und position
opponent_pawn_pos = self._get_opponent_pawn_position_for_en_passant(pawn, target_pos)
opponent_pawn = self.engine.get_piece_at(opponent_pawn_pos)
if not opponent_pawn:
print(f"En Passant: No opponent pawn found at {self._position_to_notation(opponent_pawn_pos)}")
return False
# En Passant ausführen
# 1. Bauer bewegen
from_pos = pawn['position']
self.engine.board[from_pos] = 0 # EMPTY
self.engine.board[target_pos] = 1 * pawn['color']
pawn['position'] = target_pos
# 2. Gegnerischen Bauer schlagen
self.engine.board[opponent_pawn_pos] = 0 # EMPTY
opponent_pawn['captured'] = True
# En Passant Recht zurücksetzen
self.en_passant_target = None
# Zug zur History hinzufügen
en_passant_move = {
'type': 'en_passant',
'color': pawn['color'],
'from_pos': from_pos,
'to_pos': target_pos,
'captured_pawn_pos': opponent_pawn_pos
}
self.move_history.append(en_passant_move)
print(f"En passant executed: {self._position_to_notation(from_pos)}{self._position_to_notation(target_pos)}")
return True
def handle_pawn_promotion(self, pawn: Dict[str, Any], promotion_piece: str = 'queen') -> bool:
"""
Behandelt Bauernumwandlung
Args:
pawn: Bauer der umgewandelt werden soll
promotion_piece: Gewünschte Figur ('queen', 'rook', 'bishop', 'knight')
Returns:
bool: True wenn erfolgreich
"""
# Prüfen ob Umwandlung möglich ist
if not self._can_pawn_promote(pawn):
return False
# Figurtyp bestimmen
piece_type = self._get_promotion_piece_type(promotion_piece)
if piece_type is None:
return False
# Umwandlung durchführen
position = pawn['position']
# Alten Bauer entfernen
self.engine.pieces = [p for p in self.engine.pieces if p != pawn]
# Neue Figur hinzufügen
self.engine.pieces.append({
'type': piece_type,
'color': pawn['color'],
'position': position,
'captured': False
})
# Brett aktualisieren
self.engine.board[position] = piece_type * pawn['color']
# Umwandlung zur History hinzufügen
promotion_move = {
'type': 'promotion',
'color': pawn['color'],
'position': position,
'promotion_piece': promotion_piece
}
self.move_history.append(promotion_move)
print(f"Pawn promotion: {promotion_piece} at {self._position_to_notation(position)}")
return True
def check_pawn_promotion_required(self, move: Dict[str, Any], piece: Dict[str, Any] = None) -> bool:
"""
Prüft ob nach einem Bauerzug Umwandlung erforderlich ist
Args:
move: Ausgeführter Bauerzug
piece: Figur (falls move['type'] fehlt)
Returns:
bool: True wenn Umwandlung erforderlich
"""
# 🛡️ ROBUSTHEIT: FALLBACK für fehlende Felder
move_type = move.get('type')
if move_type is None and piece is not None:
move_type = piece['type']
move_color = move.get('color')
if move_color is None and piece is not None:
move_color = piece['color']
if move_type != 1: # Nur für Bauern
return False
target_row = move['to_pos'] // 10
# Weißer Bauer auf 8. Reihe oder schwarzer Bauer auf 1. Reihe
return (move_color == 1 and target_row == 9) or (move_color == -1 and target_row == 2)
def update_castling_rights_after_move(self, move: Dict[str, Any], piece: Dict[str, Any] = None):
"""
Aktualisiert Rochaderechte nach einem Zug
Args:
move: Ausgeführter Zug
piece: Figur (falls move['type'] fehlt)
"""
# 🛡️ ROBUSTHEIT: FALLBACK für fehlende Felder
move_type = move.get('type')
if move_type is None and piece is not None:
move_type = piece['type']
move_color = move.get('color')
if move_color is None and piece is not None:
move_color = piece['color']
# Wenn König bewegt wurde, Rochaderechte aufheben
if move_type == 99: # KING
self._revoke_castling_rights(move_color)
# Wenn Turm bewegt wurde, entsprechendes Rochaderecht aufheben
elif move_type == 5: # ROOK
self._revoke_rook_castling_rights(move_color, move['from_pos'])
def update_en_passant_after_move(self, move: Dict[str, Any], move_color: int = None):
"""
Aktualisiert en Passant Recht nach einem Zug - KORRIGIERTE VERSION
"""
# 🛡️ ROBUSTHEIT: FALLBACK für fehlende Felder
if move_color is None:
move_color = move.get('color')
# En Passant Recht zurücksetzen
self.en_passant_target = None
# Wenn Bauer Doppelschritt, en Passant Recht setzen
move_type = move.get('type')
if move_type == 1: # PAWN
from_row = move['from_pos'] // 10
to_row = move['to_pos'] // 10
# Doppelschritt erkannt (2 Reihen Differenz)
if abs(from_row - to_row) == 2:
# Feld hinter dem Bauer setzen
direction = 10 if move_color == 1 else -10
self.en_passant_target = move['from_pos'] + direction
# print(f"En passant target set: {self._position_to_notation(self.en_passant_target)}") # 🚨 DEBUG auskommentiert
def get_available_promotion_pieces(self) -> List[str]:
"""
Gibt verfügbare Umwandlungsfiguren zurück
Returns:
List[str]: Liste der Figurennamen
"""
return ['queen', 'rook', 'bishop', 'knight']
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 == 1 and row == 9) or (color == -1 and row == 2)
# =========================================================================
# HILFSFUNKTIONEN - ROCHADE
# =========================================================================
def _check_basic_castling_requirements(self, color: int, side: str) -> bool:
"""Prüft grundlegende Rochade-Voraussetzungen"""
# Rochaderecht muss vorhanden sein
if not self._has_castling_right(color, side):
return False
# König und Turm müssen auf Startpositionen sein
king_pos, rook_pos = self._get_king_rook_start_positions(color, side)
king = self.engine.get_piece_at(king_pos)
rook = self.engine.get_piece_at(rook_pos)
if not king or king['type'] != 99 or king['color'] != color:
return False
if not rook or rook['type'] != 5 or rook['color'] != color:
return False
return True
def _check_castling_squares_safety(self, color: int, side: str) -> bool:
"""Prüft ob Felder zwischen König und Turm sicher sind"""
king_from, king_to, _, _ = self._get_castling_positions(color, side)
# Felder die der König passiert prüfen
if side == 'kingside':
check_squares = [king_from + 1, king_from + 2] # f1, g1 für Weiß
else: # queenside
check_squares = [king_from - 1, king_from - 2] # d1, c1 für Weiß
# Temporär König bewegen und Felder prüfen
original_pos = king_from
king = self.engine.get_piece_at(king_from)
for square in check_squares:
# Temporär König auf Feld bewegen
self.engine.board[original_pos] = 0
self.engine.board[square] = 99 * color
if king:
king['position'] = square
# Prüfen ob König im Schach
if self.engine.is_king_in_check(color):
# Zustand zurücksetzen
self.engine.board[original_pos] = 99 * color
self.engine.board[square] = 0
if king:
king['position'] = original_pos
return False
# Zustand zurücksetzen
self.engine.board[original_pos] = 99 * color
self.engine.board[square] = 0
if king:
king['position'] = original_pos
return True
def _check_castling_squares_empty(self, color: int, side: str) -> bool:
"""Prüft ob Felder zwischen König und Turm frei sind"""
king_from, king_to, rook_from, rook_to = self._get_castling_positions(color, side)
if side == 'kingside':
# Felder zwischen König und Turm
squares = [king_from + 1, king_from + 2]
else: # queenside
# Felder zwischen König und Turm (inkl. b1/c1 für große Rochade)
squares = [king_from - 1, king_from - 2, king_from - 3]
for square in squares:
if self.engine.board[square] != 0: # Nicht EMPTY
return False
return True
def _get_castling_positions(self, color: int, side: str) -> Tuple[int, int, int, int]:
"""Gibt Positionen für Rochade zurück"""
if color == 1: # WHITE
if side == 'kingside':
return 25, 27, 28, 26 # e1-g1, h1-f1
else: # queenside
return 25, 23, 21, 24 # e1-c1, a1-d1
else: # BLACK
if side == 'kingside':
return 95, 97, 98, 96 # e8-g8, h8-f8
else: # queenside
return 95, 93, 91, 94 # e8-c8, a8-d8
def _get_king_rook_start_positions(self, color: int, side: str) -> Tuple[int, int]:
"""Gibt Startpositionen von König und Turm zurück"""
if color == 1: # WHITE
king_pos = 25 # e1
rook_pos = 28 if side == 'kingside' else 21 # h1 oder a1
else: # BLACK
king_pos = 95 # e8
rook_pos = 98 if side == 'kingside' else 91 # h8 oder a8
return king_pos, rook_pos
def _has_castling_right(self, color: int, side: str) -> bool:
"""Prüft ob Rochaderecht vorhanden ist"""
if color == 1: # WHITE
return (self.castling_rights['white_kingside'] if side == 'kingside'
else self.castling_rights['white_queenside'])
else: # BLACK
return (self.castling_rights['black_kingside'] if side == 'kingside'
else self.castling_rights['black_queenside'])
def _revoke_castling_rights(self, color: int):
"""Hebt Rochaderechte für eine Farbe auf"""
if color == 1: # WHITE
self.castling_rights['white_kingside'] = False
self.castling_rights['white_queenside'] = False
else: # BLACK
self.castling_rights['black_kingside'] = False
self.castling_rights['black_queenside'] = False
def _revoke_rook_castling_rights(self, color: int, rook_pos: int):
"""Hebt spezifisches Rochaderecht basierend auf Turmposition auf"""
if color == 1: # WHITE
if rook_pos == 28: # h1 - kingside
self.castling_rights['white_kingside'] = False
elif rook_pos == 21: # a1 - queenside
self.castling_rights['white_queenside'] = False
else: # BLACK
if rook_pos == 98: # h8 - kingside
self.castling_rights['black_kingside'] = False
elif rook_pos == 91: # a8 - queenside
self.castling_rights['black_queenside'] = False
def _get_castling_notation(self, color: int, side: str) -> str:
"""Gibt algebraische Notation für Rochade zurück"""
if side == 'kingside':
return 'O-O' if color == 1 else 'O-O'
else:
return 'O-O-O' if color == 1 else 'O-O-O'
# =========================================================================
# HILFSFUNKTIONEN - EN PASSANT
# =========================================================================
def _check_basic_en_passant_requirements(self, pawn: Dict[str, Any], target_pos: int) -> bool:
"""Prüft grundlegende en Passant Voraussetzungen - KORRIGIERT"""
# Nur Bauern können en Passant
if pawn['type'] != 1:
return False
# Ziel muss diagonal sein (aber nicht notwendigerweise besetzt)
from_pos = pawn['position']
row_diff = abs((from_pos // 10) - (target_pos // 10))
col_diff = abs((from_pos % 10) - (target_pos % 10))
# Bauer muss sich diagonal bewegen (eine Reihe vor, eine Spalte seitlich)
if not (row_diff == 1 and col_diff == 1):
return False
# Ziel muss leer sein (bei en Passant ist das Zielfeld immer leer)
if self.engine.board[target_pos] != 0:
return False
return True
def _get_opponent_pawn_position_for_en_passant(self, pawn: Dict[str, Any], target_pos: int) -> int:
"""Gibt Position des zu schlagenden Bauern zurück"""
# Bauer steht eine Reihe hinter dem Zielfeld
if pawn['color'] == 1: # WHITE
return target_pos - 10 # Eine Reihe zurück
else: # BLACK
return target_pos + 10 # Eine Reihe vor
def _validate_no_self_check_after_en_passant(self, pawn: Dict[str, Any], target_pos: int,
opponent_pawn_pos: int) -> bool:
"""Prüft ob en Passant keinen Selbstschach verursacht"""
# Zustand speichern
original_board = self.engine.board.copy()
original_pieces = copy.deepcopy(self.engine.pieces)
# Temporären en Passant ausführen
from_pos = pawn['position']
self.engine.board[from_pos] = 0
self.engine.board[target_pos] = 1 * pawn['color']
pawn['position'] = target_pos
# Gegnerischen Bauer schlagen
opponent_pawn = self.engine.get_piece_at(opponent_pawn_pos)
if opponent_pawn:
self.engine.board[opponent_pawn_pos] = 0
opponent_pawn['captured'] = True
# Schach prüfen
in_check = self.engine.is_king_in_check(pawn['color'])
# Zustand wiederherstellen
self.engine.board = original_board
self.engine.pieces = original_pieces
return not in_check
# =========================================================================
# HILFSFUNKTIONEN - BAUERNUMWANDLUNG
# =========================================================================
def _can_pawn_promote(self, pawn: Dict[str, Any]) -> bool:
"""Prüft ob Bauer umwandeln kann"""
if pawn['type'] != 1: # Nur Bauern
return False
position = pawn['position']
row = position // 10
# Weißer Bauer auf 8. Reihe oder schwarzer Bauer auf 1. Reihe
return (pawn['color'] == 1 and row == 9) or (pawn['color'] == -1 and row == 2)
def _get_promotion_piece_type(self, promotion_piece: str) -> Optional[int]:
"""Konvertiert Figurenname zu Typ"""
piece_types = {
'queen': 9,
'rook': 5,
'bishop': 3,
'knight': 4
}
return piece_types.get(promotion_piece.lower())
# =========================================================================
# ALLGEMEINE HILFSFUNKTIONEN
# =========================================================================
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
return f"{files[file]}{row - 1}"
# Test der Schachregeln
if __name__ == "__main__":
print("Testing ChessRules...")
from core import ChesstegEngine
engine = ChesstegEngine()
rules = ChessRules(engine)
# Test: Rochade-Rechte
print("Castling rights initialized:", rules.castling_rights)
# Test: En Passant
print("En passant target:", rules.en_passant_target)
# Test: Umwandlungsfiguren
print("Available promotion pieces:", rules.get_available_promotion_pieces())
print("ChessRules test completed!")