├── docs ├── img │ ├── readme.md │ └── headerbild.jpeg ├── js │ └── readme.md ├── readme.md └── index.html ├── chessteg_modular ├── readme.md ├── engine │ ├── readme.md │ ├── __init__.py │ ├── search.py │ ├── evaluation.py │ ├── core.py │ ├── move_generation.py │ └── rules.py ├── gui │ ├── readme.md │ ├── init.py │ ├── __init__.py │ ├── chess_gui.py │ └── chess_board.py ├── struktur.txt └── main.py ├── chessteg.JPG ├── ChesstegMod.png ├── chesstegJS.png ├── chesstegMAC.png ├── chesstegPY.png ├── chessteg_modular.zip ├── README.md ├── main.py ├── unitchesstegmain.lfm ├── chess_gui.py └── unitchesstegmain.pas /docs/img/readme.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/js/readme.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/readme.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /chessteg_modular/readme.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /chessteg_modular/engine/readme.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /chessteg_modular/gui/readme.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /chessteg.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkoopongithub/chessteg/main/chessteg.JPG -------------------------------------------------------------------------------- /ChesstegMod.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkoopongithub/chessteg/main/ChesstegMod.png -------------------------------------------------------------------------------- /chesstegJS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkoopongithub/chessteg/main/chesstegJS.png -------------------------------------------------------------------------------- /chesstegMAC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkoopongithub/chessteg/main/chesstegMAC.png -------------------------------------------------------------------------------- /chesstegPY.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkoopongithub/chessteg/main/chesstegPY.png -------------------------------------------------------------------------------- /chessteg_modular.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkoopongithub/chessteg/main/chessteg_modular.zip -------------------------------------------------------------------------------- /docs/img/headerbild.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pkoopongithub/chessteg/main/docs/img/headerbild.jpeg -------------------------------------------------------------------------------- /chessteg_modular/gui/init.py: -------------------------------------------------------------------------------- 1 | """ 2 | Chessteg GUI Module 3 | GUI-Komponenten für das Chessteg Schachprogramm 4 | """ 5 | 6 | from .chess_board import ChessBoard 7 | from .chess_gui import ChessGUI 8 | 9 | __all__ = ['ChessBoard', 'ChessGUI'] -------------------------------------------------------------------------------- /chessteg_modular/gui/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Chessteg GUI Module 3 | GUI-Komponenten für das Chessteg Schachprogramm 4 | """ 5 | 6 | from .chess_board import ChessBoard 7 | from .chess_gui import ChessGUI 8 | 9 | __all__ = ['ChessBoard', 'ChessGUI'] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # chessteg 2 | chess Lazarus Object Pascal 3 | 4 | 5 | 6 | ![screenshot](./chessteg.JPG) 7 | ![screenshot](./chesstegMAC.png) 8 | ![screenshot](./chesstegJS.png) 9 | ![screenshot](./chesstegPY.png) 10 | ![screenshot](./ChesstegMod.png) 11 | 12 | -------------------------------------------------------------------------------- /chessteg_modular/struktur.txt: -------------------------------------------------------------------------------- 1 | chessteg_modular/ 2 | ├── main.py 3 | ├── engine/ 4 | │ ├── __init__.py 5 | │ ├── core.py 6 | │ ├── move_generation.py 7 | │ ├── evaluation.py 8 | │ ├── search.py 9 | │ └── rules.py 10 | ├── gui/ 11 | │ ├── __init__.py 12 | │ ├── init.py (wahrscheinlich funktionslos?) 13 | │ ├── chess_board.py 14 | │ └── chess_gui.py 15 | └── tests/ 16 | ├── __init__.py 17 | └── test_engine.py -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Chessteg - Didaktisches Schachprogramm 4 | Original in Pascal (1994-1995), portiert zu Python mit Tkinter GUI 5 | """ 6 | 7 | from chess_gui import ChessGUI 8 | 9 | def main(): 10 | print("Chessteg - Didaktisches Schachprogramm") 11 | print("Original in Pascal (1994-1995), portiert zu Python") 12 | print("=" * 50) 13 | 14 | gui = ChessGUI() 15 | gui.run() 16 | 17 | if __name__ == "__main__": 18 | main() -------------------------------------------------------------------------------- /chessteg_modular/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Chessteg - Didaktisches Schachprogramm 4 | Original in Pascal (1994-1995), portiert zu Python mit Tkinter GUI 5 | """ 6 | 7 | # ✅ KORREKTUR: Import aus gui Modul statt direkt 8 | from gui.chess_gui import ChessGUI 9 | 10 | def validate_engine(): 11 | """Validiert die Engine-Komponenten vor GUI-Start - ERWEITERTE VERSION""" 12 | try: 13 | from engine.core import ChesstegEngine 14 | engine = ChesstegEngine() 15 | 16 | # Teste Zug-Generierung mit Debug-Info 17 | moves_white = engine.generate_all_moves(1) 18 | moves_black = engine.generate_all_moves(-1) 19 | 20 | print(f"✅ Validierung: {len(moves_white)} Züge für Weiß, {len(moves_black)} Züge für Schwarz") 21 | 22 | # Zähle spezielle Züge 23 | castling_moves = [m for m in moves_white if m.get('special_type') == 'castling'] 24 | promotion_moves = [m for m in moves_white if m.get('promotion_piece')] 25 | 26 | print(f"✅ Rochade-Züge verfügbar: {len(castling_moves)}") 27 | print(f"✅ Umwandlungs-Züge verfügbar: {len(promotion_moves)}") 28 | 29 | # Teste Zug-Struktur 30 | if moves_white: 31 | test_move = moves_white[0] 32 | required_keys = ['type', 'color', 'from_pos', 'to_pos', 'piece_id'] 33 | missing_keys = [key for key in required_keys if key not in test_move] 34 | 35 | if missing_keys: 36 | print(f"❌ FEHLENDE KEYS im Zug: {missing_keys}") 37 | print(f" Vorhandene Keys: {list(test_move.keys())}") 38 | return False 39 | else: 40 | print(f"✅ Zug-Struktur validiert: {list(test_move.keys())}") 41 | return True 42 | else: 43 | print("❌ KEINE ZÜGE generiert") 44 | return False 45 | 46 | except Exception as e: 47 | print(f"❌ CRITICAL ENGINE ERROR: {e}") 48 | import traceback 49 | traceback.print_exc() 50 | return False 51 | 52 | def main(): 53 | print("Chessteg - Didaktisches Schachprogramm") 54 | print("Original in Pascal (1994-1995), portiert zu Python") 55 | print("=" * 50) 56 | 57 | # Engine vor GUI-Start validieren 58 | print("🔧 Engine-Validierung wird durchgeführt...") 59 | if not validate_engine(): 60 | print("❌ ENGINE VALIDIERUNG FEHLGESCHLAGEN - Programm wird beendet") 61 | return 62 | 63 | print("✅ Engine validiert - Starte GUI...") 64 | gui = ChessGUI() 65 | gui.run() 66 | 67 | if __name__ == "__main__": 68 | main() 69 | -------------------------------------------------------------------------------- /chessteg_modular/engine/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Chessteg Engine Module - KORRIGIERTE VERSION 3 | """ 4 | 5 | # ============================================================================= 6 | # KONSTANTEN 7 | # ============================================================================= 8 | WHITE = 1 9 | BLACK = -1 10 | EMPTY = 0 11 | DUMMY = 100 12 | PAWN = 1 13 | BISHOP = 3 14 | KNIGHT = 4 15 | ROOK = 5 16 | QUEEN = 9 17 | KING = 99 18 | 19 | PIECE_SYMBOLS = { 20 | 'wP': '♙', 'wR': '♖', 'wB': '♗', 'wN': '♘', 'wQ': '♕', 'wK': '♔', 21 | 'bP': '♟', 'bR': '♜', 'bB': '♝', 'bN': '♞', 'bQ': '♛', 'bK': '♚' 22 | } 23 | 24 | PIECE_VALUES = { 25 | PAWN: 100, KNIGHT: 320, BISHOP: 330, ROOK: 500, QUEEN: 900, KING: 20000 26 | } 27 | 28 | # ============================================================================= 29 | # MODUL-IMPORTS - KEIN EXCEPTION HANDLING MEHR 30 | # ============================================================================= 31 | from .core import ChesstegEngine 32 | from .move_generation import MoveGenerator 33 | from .evaluation import PositionEvaluator 34 | from .search import SearchAlgorithm 35 | from .rules import ChessRules 36 | 37 | # ============================================================================= 38 | # ÖFFENTLICHE SCHNITTSTELLE 39 | # ============================================================================= 40 | __all__ = [ 41 | 'ChesstegEngine', 'MoveGenerator', 'PositionEvaluator', 42 | 'SearchAlgorithm', 'ChessRules', 'WHITE', 'BLACK', 'EMPTY', 'DUMMY', 43 | 'PAWN', 'BISHOP', 'KNIGHT', 'ROOK', 'QUEEN', 'KING', 44 | 'PIECE_SYMBOLS', 'PIECE_VALUES' 45 | ] 46 | 47 | # ============================================================================= 48 | # HILFSFUNKTIONEN 49 | # ============================================================================= 50 | def get_piece_name(piece_value): 51 | if piece_value == EMPTY: return "Empty" 52 | if piece_value == DUMMY: return "Dummy" 53 | color = 'White' if piece_value > 0 else 'Black' 54 | piece_type = abs(piece_value) 55 | names = {PAWN: 'Pawn', ROOK: 'Rook', BISHOP: 'Bishop', 56 | KNIGHT: 'Knight', QUEEN: 'Queen', KING: 'King'} 57 | return f"{color} {names.get(piece_type, 'Unknown')}" 58 | 59 | def get_piece_code(piece_value): 60 | if piece_value == EMPTY or piece_value == DUMMY: return None 61 | color = 'w' if piece_value > 0 else 'b' 62 | piece_type = abs(piece_value) 63 | types = {PAWN: 'P', ROOK: 'R', BISHOP: 'B', KNIGHT: 'N', QUEEN: 'Q', KING: 'K'} 64 | return color + types.get(piece_type, '?') 65 | 66 | def position_to_notation(position): 67 | files = ['', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', ''] 68 | row = position // 10 69 | file = position % 10 70 | if 1 <= file <= 8 and 2 <= row <= 9: 71 | return f"{files[file]}{row - 1}" 72 | return "??" 73 | 74 | def notation_to_position(notation): 75 | if len(notation) != 2: return None 76 | file_char = notation[0].lower() 77 | rank_char = notation[1] 78 | files = {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5, 'f': 6, 'g': 7, 'h': 8} 79 | if file_char in files and rank_char.isdigit(): 80 | file = files[file_char] 81 | rank = int(rank_char) + 1 82 | return rank * 10 + file 83 | return None 84 | 85 | print("✅ Chessteg Engine Module MIT VOLLSTÄNDIGEN MODULEN geladen") -------------------------------------------------------------------------------- /unitchesstegmain.lfm: -------------------------------------------------------------------------------- 1 | object frmChessteg: TfrmChessteg 2 | Left = 791 3 | Height = 641 4 | Top = 73 5 | Width = 425 6 | Caption = 'Chessteg' 7 | ClientHeight = 641 8 | ClientWidth = 425 9 | OnCreate = FormCreate 10 | OnDestroy = FormDestroy 11 | LCLVersion = '1.6.4.0' 12 | object grdBrettansicht: TStringGrid 13 | AnchorSideLeft.Side = asrCenter 14 | Left = 8 15 | Height = 404 16 | Top = 8 17 | Width = 408 18 | AutoEdit = False 19 | ColCount = 8 20 | DefaultColWidth = 50 21 | DefaultRowHeight = 50 22 | FixedCols = 0 23 | FixedRows = 0 24 | RowCount = 8 25 | TabOrder = 0 26 | OnDrawCell = grdBrettansichtDrawCell 27 | OnSelectCell = grdBrettansichtSelectCell 28 | end 29 | object btnNaechsterZug: TButton 30 | Left = 104 31 | Height = 25 32 | Top = 448 33 | Width = 150 34 | Caption = '&Nächster Zug' 35 | OnClick = btnNaechsterZugClick 36 | TabOrder = 1 37 | end 38 | object LabelSpielerzug: TLabel 39 | Left = 16 40 | Height = 13 41 | Top = 432 42 | Width = 156 43 | Caption = 'Chessteg spielt gegen sich selbst' 44 | ParentColor = False 45 | end 46 | object von: TEdit 47 | Left = 296 48 | Height = 21 49 | Top = 528 50 | Width = 32 51 | Enabled = False 52 | TabStop = False 53 | TabOrder = 2 54 | Text = '0' 55 | end 56 | object LabelZugVon: TLabel 57 | Left = 272 58 | Height = 13 59 | Top = 536 60 | Width = 21 61 | Caption = 'von:' 62 | ParentColor = False 63 | end 64 | object nach: TEdit 65 | Left = 368 66 | Height = 21 67 | Top = 528 68 | Width = 32 69 | Enabled = False 70 | TabOrder = 3 71 | Text = '0' 72 | end 73 | object LabelZugNach: TLabel 74 | Left = 336 75 | Height = 13 76 | Top = 536 77 | Width = 27 78 | Caption = 'nach:' 79 | ParentColor = False 80 | end 81 | object StaticText1: TStaticText 82 | Left = 417 83 | Height = 17 84 | Top = 26 85 | Width = 23 86 | Caption = '1' 87 | TabOrder = 4 88 | end 89 | object StaticText2: TStaticText 90 | Left = 417 91 | Height = 17 92 | Top = 72 93 | Width = 23 94 | Caption = '2' 95 | TabOrder = 5 96 | end 97 | object StaticText3: TStaticText 98 | Left = 417 99 | Height = 17 100 | Top = 128 101 | Width = 23 102 | Caption = '3' 103 | TabOrder = 6 104 | end 105 | object StaticText4: TStaticText 106 | Left = 417 107 | Height = 17 108 | Top = 176 109 | Width = 23 110 | Caption = '4' 111 | TabOrder = 7 112 | end 113 | object StaticText5: TStaticText 114 | Left = 417 115 | Height = 17 116 | Top = 224 117 | Width = 23 118 | Caption = '5' 119 | TabOrder = 8 120 | end 121 | object StaticText6: TStaticText 122 | Left = 417 123 | Height = 17 124 | Top = 272 125 | Width = 23 126 | Caption = '6' 127 | TabOrder = 9 128 | end 129 | object StaticText7: TStaticText 130 | Left = 416 131 | Height = 17 132 | Top = 328 133 | Width = 23 134 | Caption = '7' 135 | TabOrder = 10 136 | end 137 | object StaticText8: TStaticText 138 | Left = 416 139 | Height = 17 140 | Top = 376 141 | Width = 23 142 | Caption = '8' 143 | TabOrder = 11 144 | end 145 | object StaticText9: TStaticText 146 | Left = 16 147 | Height = 17 148 | Top = 416 149 | Width = 23 150 | Caption = 'H' 151 | TabOrder = 12 152 | end 153 | object StaticText10: TStaticText 154 | Left = 72 155 | Height = 17 156 | Top = 416 157 | Width = 23 158 | Caption = 'G' 159 | TabOrder = 13 160 | end 161 | object StaticText11: TStaticText 162 | Left = 120 163 | Height = 17 164 | Top = 416 165 | Width = 23 166 | Caption = 'F' 167 | TabOrder = 14 168 | end 169 | object StaticText12: TStaticText 170 | Left = 176 171 | Height = 17 172 | Top = 416 173 | Width = 23 174 | Caption = 'E' 175 | TabOrder = 15 176 | end 177 | object StaticText13: TStaticText 178 | Left = 224 179 | Height = 17 180 | Top = 416 181 | Width = 23 182 | Caption = 'D' 183 | TabOrder = 16 184 | end 185 | object StaticText14: TStaticText 186 | Left = 272 187 | Height = 17 188 | Top = 416 189 | Width = 23 190 | Caption = 'C' 191 | TabOrder = 17 192 | end 193 | object StaticText15: TStaticText 194 | Left = 320 195 | Height = 17 196 | Top = 416 197 | Width = 23 198 | Caption = 'B' 199 | TabOrder = 18 200 | end 201 | object StaticText16: TStaticText 202 | Left = 368 203 | Height = 17 204 | Top = 416 205 | Width = 23 206 | Caption = 'A' 207 | TabOrder = 19 208 | end 209 | object amZug: TLabel 210 | Left = 16 211 | Height = 13 212 | Top = 458 213 | Width = 68 214 | Caption = 'Weiss am Zug' 215 | ParentColor = False 216 | end 217 | object WeisserZug: TButton 218 | Left = 104 219 | Height = 25 220 | Top = 488 221 | Width = 75 222 | Caption = 'Weisser Zug' 223 | OnClick = WeisserZugClick 224 | TabOrder = 20 225 | end 226 | object SchwarzerZug: TButton 227 | Left = 179 228 | Height = 25 229 | Top = 488 230 | Width = 75 231 | Caption = 'SchwarzerZug' 232 | OnClick = SchwarzerZugClick 233 | TabOrder = 21 234 | end 235 | object SpielerZieht: TButton 236 | Left = 106 237 | Height = 25 238 | Top = 529 239 | Width = 150 240 | Caption = 'Spieler zieht' 241 | OnClick = SpielerZiehtClick 242 | TabOrder = 22 243 | end 244 | object LabelSpielerzug1: TLabel 245 | Left = 104 246 | Height = 13 247 | Top = 560 248 | Width = 165 249 | Caption = 'Spielerzug ( mit der Maus Ein Click)' 250 | ParentColor = False 251 | end 252 | end 253 | -------------------------------------------------------------------------------- /chessteg_modular/engine/search.py: -------------------------------------------------------------------------------- 1 | """ 2 | Chessteg Search Module - KORRIGIERTE VERSION MIT DEBUG 3 | Vollständiger Alpha-Beta Suchalgorithmus mit Quiescence Search 4 | """ 5 | 6 | import math 7 | import time 8 | import copy 9 | from typing import List, Dict, Any, Optional, Tuple 10 | 11 | 12 | class SearchAlgorithm: 13 | """ 14 | Vollständiger Alpha-Beta Suchalgorithmus mit erweiterten Features 15 | """ 16 | 17 | # Konstanten für Alpha-Beta 18 | MATE_SCORE = 20000000 19 | MAX_DEPTH = 30 20 | 21 | # Figuren-Typen und Werte 22 | PAWN = 1 23 | KNIGHT = 4 24 | BISHOP = 3 25 | ROOK = 5 26 | QUEEN = 9 27 | KING = 99 28 | 29 | # Figurenwerte für Move Ordering 30 | PIECE_VALUES = { 31 | PAWN: 100, 32 | KNIGHT: 320, 33 | BISHOP: 330, 34 | ROOK: 500, 35 | QUEEN: 900, 36 | KING: 20000 37 | } 38 | 39 | def __init__(self, engine): 40 | self.engine = engine 41 | self.ai_settings = { 42 | 'search_depth': 3, 43 | 'extended_evaluation': True, 44 | 'quiescence_search': False, # 🚨 KORREKTUR: Erstmal deaktivieren für Stabilität 45 | 'quiescence_depth': 2, 46 | 'timeout_ms': 5000 # 🚨 KORREKTUR: Mehr Zeit für Debugging 47 | } 48 | 49 | # Suchstatistiken 50 | self.node_counter = 0 51 | self.move_counter = 0 52 | self.calculation_start_time = 0 53 | self.transposition_table = {} 54 | 55 | # Move Ordering Cache 56 | self.move_ordering_cache = {} 57 | 58 | def computer_move(self) -> Optional[Dict[str, Any]]: 59 | """ 60 | Berechnet den besten Zug für den Computer mit vollständiger Alpha-Beta-Suche 61 | """ 62 | print("=== KI MOVE CALCULATION STARTED ===") 63 | start_time = time.time() 64 | self.calculation_start_time = start_time 65 | self.node_counter = 0 66 | self.move_counter = 0 67 | self.transposition_table = {} 68 | 69 | current_color = 1 if self.engine.white_turn else -1 70 | 71 | # 1. Alle legalen Züge generieren 72 | all_moves = self.engine.generate_all_moves(current_color) 73 | if not all_moves: 74 | print("❌ Keine legalen Züge gefunden - Matt oder Patt?") 75 | print(f"Checkmate: {self.engine.checkmate}, Stalemate: {self.engine.stalemate}") 76 | print(f"King in check: {self.engine.is_king_in_check(current_color)}") 77 | return None 78 | 79 | print(f"🔍 {len(all_moves)} legale Züge verfügbar für {'Weiß' if current_color == 1 else 'Schwarz'}") 80 | 81 | # 2. Züge sortieren (Move Ordering) 82 | sorted_moves = self._order_moves(all_moves) 83 | 84 | best_move = None 85 | best_score = -self.MATE_SCORE - 1 86 | 87 | # 3. Iterative Deepening 88 | depth = self.ai_settings['search_depth'] 89 | 90 | # 4. Alpha-Beta-Suche starten 91 | alpha = -self.MATE_SCORE - 1 92 | beta = self.MATE_SCORE + 1 93 | 94 | print(f"🎯 Starte Alpha-Beta-Suche mit Tiefe {depth}, {len(sorted_moves)} Zügen") 95 | 96 | for i, move in enumerate(sorted_moves): 97 | self.move_counter += 1 98 | 99 | # Zug ausführen (transaktional) 100 | if not self.engine.make_move(move): 101 | print(f"❌ Zug {i+1}/{len(sorted_moves)}: {self._move_to_notation(move)} konnte nicht ausgeführt werden") 102 | continue 103 | 104 | # Rufe Alpha-Beta für die nächste Tiefe auf 105 | score = -self._alpha_beta(depth - 1, -beta, -alpha) 106 | 107 | # Zug rückgängig machen 108 | self.engine.undo_move() 109 | 110 | print(f"🔍 Zug {i+1}/{len(sorted_moves)}: {self._move_to_notation(move)} -> Score: {score}") 111 | 112 | # Timeout-Check 113 | if self._check_timeout(): 114 | print(f"⏰ Timeout erreicht nach {self.move_counter} Zügen") 115 | break 116 | 117 | # Alpha-Beta Update 118 | if score > best_score: 119 | best_score = score 120 | best_move = move 121 | print(f"🏆 NEUER BESTER ZUG: {self._move_to_notation(move)} mit Score {score}") 122 | 123 | if score > alpha: 124 | alpha = score 125 | 126 | # Beta-Cutoff 127 | if alpha >= beta: 128 | print(f"✂️ Beta-Cutoff bei Zug {i+1}") 129 | break 130 | 131 | end_time = time.time() 132 | duration = end_time - start_time 133 | 134 | print(f"=== KI MOVE CALCULATION FINISHED ===") 135 | print(f"Best Score: {best_score}") 136 | print(f"Best Move: {self._move_to_notation(best_move) if best_move else 'None'}") 137 | print(f"Search Depth: {depth}") 138 | print(f"Nodes Visited: {self.node_counter}") 139 | print(f"Moves Evaluated: {self.move_counter}") 140 | print(f"Calculation Time: {duration:.2f}s") 141 | print(f"NPS: {self.node_counter / duration if duration > 0 else 0:.0f}") 142 | 143 | if best_move: 144 | print(f"✅ KI hat Zug gefunden: {self._move_to_notation(best_move)}") 145 | else: 146 | print("❌ KI konnte keinen Zug finden") 147 | 148 | return best_move 149 | 150 | def _alpha_beta(self, depth: int, alpha: int, beta: int) -> int: 151 | """ 152 | Der rekursive Alpha-Beta-Suchalgorithmus. 153 | """ 154 | self.node_counter += 1 155 | 156 | # Timeout-Check 157 | if self._check_timeout(): 158 | return 0 159 | 160 | # 1. Prüfe auf Spielende (Matt/Patt) 161 | if self.engine.is_game_over(): 162 | score = self._mate_or_stalemate_score(depth) 163 | # 🚨 DEBUG: Zeige Matt/Patt-Erkennung 164 | if abs(score) > 1000000: 165 | print(f" 🏁 Terminalstellung: Score {score} (Tiefe {depth})") 166 | return score 167 | 168 | # 2. Basisfall: Tiefe erreicht 169 | if depth <= 0: 170 | score = self.engine.evaluator.evaluate_position() 171 | # 🚨 DEBUG: Zeige erste Bewertungen 172 | if self.node_counter < 10: 173 | print(f" 📊 Blattevaluation: {score} (Tiefe {depth})") 174 | return score 175 | 176 | # 3. Zuggenerierung 177 | current_color = 1 if self.engine.white_turn else -1 178 | all_moves = self.engine.generate_all_moves(current_color) 179 | 180 | if not all_moves: 181 | score = self._mate_or_stalemate_score(depth) 182 | print(f" ❌ Keine Züge in Tiefe {depth}: Score {score}") 183 | return score 184 | 185 | # 4. Move Ordering 186 | sorted_moves = self._order_moves(all_moves) 187 | 188 | # 5. Haupt-Alpha-Beta-Loop 189 | best_score = -self.MATE_SCORE - 1 190 | 191 | for move in sorted_moves: 192 | # 🚨 DEBUG: Zeige ersten paar Züge 193 | if self.node_counter < 5 and depth == self.ai_settings['search_depth'] - 1: 194 | print(f" 🔍 Prüfe Zug: {self._move_to_notation(move)} in Tiefe {depth}") 195 | 196 | if not self.engine.make_move(move): 197 | continue 198 | 199 | # Rekursiver Aufruf 200 | score = -self._alpha_beta(depth - 1, -beta, -alpha) 201 | 202 | self.engine.undo_move() 203 | 204 | # Alpha-Beta Update 205 | if score > best_score: 206 | best_score = score 207 | 208 | if score > alpha: 209 | alpha = score 210 | 211 | if alpha >= beta: 212 | break 213 | 214 | return best_score 215 | 216 | def _mate_or_stalemate_score(self, depth: int) -> int: 217 | """Berechnet den Score bei Matt oder Patt - KORRIGIERTE VERSION""" 218 | current_color = 1 if self.engine.white_turn else -1 219 | 220 | # Prüfe auf Schach 221 | if self.engine.is_king_in_check(current_color): 222 | # Matt - sehr schlecht für den aktuellen Spieler 223 | mate_score = -self.MATE_SCORE + (self.MAX_DEPTH - depth) 224 | print(f" ♟️ MATT erkannt! Score: {mate_score} (Tiefe {depth})") 225 | return mate_score 226 | else: 227 | # Patt - Unentschieden 228 | print(f" 🤝 PATT erkannt! Score: 0 (Tiefe {depth})") 229 | return 0 230 | 231 | def _check_timeout(self) -> bool: 232 | """Prüft, ob das Timeout erreicht ist.""" 233 | if self.ai_settings['timeout_ms'] <= 0: 234 | return False 235 | 236 | elapsed_time_ms = (time.time() - self.calculation_start_time) * 1000 237 | return elapsed_time_ms >= self.ai_settings['timeout_ms'] 238 | 239 | def _order_moves(self, moves: List[Dict[str, Any]], quiescence: bool = False) -> List[Dict[str, Any]]: 240 | """ 241 | Sortiert Züge für bessere Alpha-Beta-Performance. 242 | """ 243 | scored_moves = [] 244 | for move in moves: 245 | score = 0 246 | 247 | # Schlagzüge priorisieren 248 | if move.get('is_capture') or move.get('special_type') == 'en_passant': 249 | # MVV-LVA Scoring 250 | if move.get('special_type') == 'en_passant': 251 | victim_value = self.PIECE_VALUES[1] # Bauer 252 | else: 253 | victim = self.engine.get_piece_at(move['to_pos']) 254 | victim_value = self.PIECE_VALUES.get(victim['type'], 0) if victim else 0 255 | 256 | attacker_value = self.PIECE_VALUES.get(move['piece']['type'], 0) 257 | score += 10 * victim_value - attacker_value 258 | score += 10000 # Hoher Bonus für Schläge 259 | 260 | # Umwandlungen priorisieren 261 | elif move.get('promotion_piece'): 262 | promotion_value = self.PIECE_VALUES.get(move['promotion_piece'], 0) 263 | score += 900 + promotion_value 264 | 265 | scored_moves.append((score, move)) 266 | 267 | # Absteigend sortieren nach Score 268 | scored_moves.sort(key=lambda x: x[0], reverse=True) 269 | 270 | return [move for score, move in scored_moves] 271 | 272 | def _get_move_key(self, move: Dict[str, Any]) -> Tuple[int, int, int]: 273 | """Erzeugt einen eindeutigen Schlüssel für einen Zug.""" 274 | promo = move.get('promotion_type', 0) 275 | return (move['from_pos'], move['to_pos'], promo) 276 | 277 | def _move_to_notation(self, move: Dict[str, Any]) -> str: 278 | """Konvertiert Zug zu algebraischer Notation""" 279 | from_pos = self._position_to_notation(move['from_pos']) 280 | to_pos = self._position_to_notation(move['to_pos']) 281 | return f"{from_pos}{to_pos}" 282 | 283 | def _position_to_notation(self, position: int) -> str: 284 | """Konvertiert interne Position zu algebraischer Notation""" 285 | files = ['', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', ''] 286 | row = position // 10 287 | file = position % 10 288 | return f"{files[file]}{row - 1}" 289 | 290 | 291 | # Test des Search-Algorithmus 292 | if __name__ == "__main__": 293 | print("Testing SearchAlgorithm...") 294 | 295 | # Benötigt alle Engine-Komponenten für einen vollständigen Test 296 | try: 297 | from core import ChesstegEngine 298 | 299 | engine = ChesstegEngine() 300 | searcher = SearchAlgorithm(engine) 301 | 302 | # Test: Standardstellung, Tiefe 3 303 | print(f"\n--- Test: Startstellung, Tiefe {searcher.ai_settings['search_depth']} ---") 304 | best_move = searcher.computer_move() 305 | 306 | if best_move: 307 | print(f"Bester Zug gefunden: {searcher._move_to_notation(best_move)}") 308 | # Führe den Zug aus 309 | engine.make_move(best_move) 310 | 311 | # Test: Nach einem Zug, Tiefe 3 312 | print(f"\n--- Test: Nach erstem Zug, Tiefe {searcher.ai_settings['search_depth']} ---") 313 | best_move_2 = searcher.computer_move() 314 | if best_move_2: 315 | print(f"Bester Zug für Schwarz gefunden: {searcher._move_to_notation(best_move_2)}") 316 | else: 317 | print("Kein Zug für Schwarz gefunden.") 318 | else: 319 | print("Kein Zug für Weiß gefunden.") 320 | 321 | except ImportError as e: 322 | print(f"ERROR: Full test requires all engine components (core, __init__, etc.). Failed to import: {e}") -------------------------------------------------------------------------------- /chess_gui.py: -------------------------------------------------------------------------------- 1 | import tkinter as tk 2 | from tkinter import ttk, messagebox 3 | import threading 4 | import time 5 | from chessteg import ChesstegEngine, WHITE, BLACK, EMPTY, PAWN, ROOK, BISHOP, KNIGHT, QUEEN, KING, DUMMY # DUMMY hinzufügen 6 | 7 | class ChessBoard(tk.Canvas): 8 | def __init__(self, parent, engine, gui, cell_size=60): 9 | self.cell_size = cell_size 10 | self.board_size = cell_size * 8 11 | super().__init__(parent, width=self.board_size, height=self.board_size, 12 | bg='white', highlightthickness=2, highlightbackground='black') 13 | 14 | self.engine = engine 15 | self.gui = gui # Referenz zur Haupt-GUI 16 | 17 | self.selected_cell = None 18 | self.from_pos = None 19 | self.possible_moves = [] 20 | 21 | # Farben 22 | self.white_color = '#f0d9b5' 23 | self.black_color = '#b58863' 24 | self.highlight_color = '#ffeb3b' 25 | self.possible_move_color = '#90ee90' 26 | self.possible_capture_color = '#ffb6c1' 27 | 28 | # Figuren-Symbole (Unicode) - KORRIGIERTE DARSTELLUNG 29 | self.piece_symbols = { 30 | 'wP': '♙', 'wR': '♖', 'wB': '♗', 'wN': '♘', 'wQ': '♕', 'wK': '♔', 31 | 'bP': '♟', 'bR': '♜', 'bB': '♝', 'bN': '♞', 'bQ': '♛', 'bK': '♚' 32 | } 33 | 34 | 35 | self.bind('', self.on_click) 36 | self.draw_board() 37 | self.draw_pieces() 38 | 39 | def get_piece_code(self, piece_value): 40 | if piece_value == EMPTY: 41 | return None 42 | 43 | color = 'w' if piece_value > 0 else 'b' # Weiß = positive Werte 44 | piece_type = abs(piece_value) 45 | 46 | types = { 47 | PAWN: 'P', 48 | ROOK: 'R', 49 | BISHOP: 'B', 50 | KNIGHT: 'N', 51 | QUEEN: 'Q', 52 | KING: 'K' 53 | } 54 | 55 | return color + types.get(piece_type, '?') 56 | 57 | def position_to_coords(self, position): 58 | """Konvertiert interne Position zu Brett-Koordinaten""" 59 | row = position // 10 60 | col = position % 10 61 | # Umrechnung für UI-Darstellung 62 | ui_row = 8 - (row - 1) # Von unten nach oben 63 | ui_col = col - 1 # Von links nach rechts 64 | return ui_row, ui_col 65 | 66 | def screen_to_board_pos(self, x, y): 67 | """Konvertiert Bildschirm-Koordinaten zu Brett-Position""" 68 | col = x // self.cell_size 69 | row = y // self.cell_size 70 | 71 | # Umrechnung in interne Position 72 | board_row = 8 - row 73 | board_col = col + 1 74 | position = (board_row + 1) * 10 + board_col 75 | 76 | return position 77 | 78 | def draw_board(self): 79 | """Zeichnet das Schachbrett""" 80 | self.delete("all") 81 | 82 | # Zeichne Felder 83 | for row in range(8): 84 | for col in range(8): 85 | x1 = col * self.cell_size 86 | y1 = row * self.cell_size 87 | x2 = x1 + self.cell_size 88 | y2 = y1 + self.cell_size 89 | 90 | color = self.white_color if (row + col) % 2 == 0 else self.black_color 91 | self.create_rectangle(x1, y1, x2, y2, fill=color, outline='') 92 | 93 | # Zeichne Koordinaten 94 | files = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'] 95 | for i, file in enumerate(files): 96 | x = i * self.cell_size + self.cell_size // 2 97 | self.create_text(x, self.board_size + 15, text=file, font=('Arial', 10)) 98 | 99 | for i in range(8): 100 | y = i * self.cell_size + self.cell_size // 2 101 | self.create_text(-15, y, text=str(8 - i), font=('Arial', 10)) 102 | 103 | def draw_pieces(self): 104 | """Zeichnet die Schachfiguren""" 105 | self.delete('piece') # Alte Figuren entfernen 106 | 107 | for piece in self.engine.pieces: 108 | if piece['captured']: 109 | continue 110 | 111 | row, col = self.position_to_coords(piece['pos']) 112 | x = col * self.cell_size + self.cell_size // 2 113 | y = row * self.cell_size + self.cell_size // 2 114 | 115 | piece_code = self.get_piece_code(piece['type'] * piece['color']) 116 | if piece_code in self.piece_symbols: 117 | symbol = self.piece_symbols[piece_code] 118 | # KORREKTUR: Richtige Farbzuordnung 119 | # Weiße Figuren (WHITE = 1) sollen weißen Text haben 120 | # Schwarze Figuren (BLACK = -1) sollen schwarzen Text haben 121 | fill_color = 'white' if piece['color'] == WHITE else 'black' 122 | self.create_text(x, y, text=symbol, font=('Arial', 32), 123 | fill=fill_color, tags='piece') 124 | 125 | def highlight_cell(self, position, color): 126 | """Hervorhebung einer Zelle""" 127 | row, col = self.position_to_coords(position) 128 | x1 = col * self.cell_size 129 | y1 = row * self.cell_size 130 | x2 = x1 + self.cell_size 131 | y2 = y1 + self.cell_size 132 | 133 | self.create_rectangle(x1, y1, x2, y2, fill=color, outline='', tags='highlight') 134 | # Stelle sicher, dass die Hervorhebung unter den Figuren liegt 135 | self.tag_lower('highlight') 136 | 137 | def show_possible_moves(self, from_position): 138 | """Zeigt mögliche Züge für eine Figur an""" 139 | piece = next((p for p in self.engine.pieces 140 | if p['pos'] == from_position and not p['captured']), None) 141 | 142 | if not piece: 143 | return 144 | 145 | self.possible_moves = [move for move in self.engine.generate_moves(piece['color']) 146 | if move['from_pos'] == from_position] 147 | 148 | for move in self.possible_moves: 149 | if move['captured']: 150 | self.highlight_cell(move['to_pos'], self.possible_capture_color) 151 | else: 152 | self.highlight_cell(move['to_pos'], self.possible_move_color) 153 | 154 | # Hervorhebung der ausgewählten Figur 155 | self.highlight_cell(from_position, self.highlight_color) 156 | 157 | def clear_highlights(self): 158 | """Entfernt alle Hervorhebungen""" 159 | self.delete('highlight') 160 | self.possible_moves = [] 161 | 162 | def on_click(self, event): 163 | """Behandelt Mausklicks auf dem Brett""" 164 | board_pos = self.screen_to_board_pos(event.x, event.y) 165 | 166 | # Prüfe Spielmodus-Beschränkungen 167 | if self.gui.game_mode == 'ai_vs_ai': 168 | messagebox.showinfo("Info", "Im KI vs KI Modus sind keine manuellen Züge möglich") 169 | return 170 | 171 | if self.gui.game_mode == 'human_vs_ai': 172 | human_color = WHITE # Annahme: Mensch spielt Weiß 173 | current_color = WHITE if self.engine.white_turn else BLACK 174 | if current_color != human_color: 175 | messagebox.showinfo("Info", "Computer ist am Zug - bitte warten") 176 | return 177 | 178 | # Zurücksetzen der visuellen Auswahl 179 | self.clear_highlights() 180 | 181 | # Prüfe ob auf eine eigene Figur geklickt wurde 182 | piece_value = self.engine.board[board_pos] 183 | is_own_piece = ((self.engine.white_turn and piece_value > 0) or 184 | (not self.engine.white_turn and piece_value < 0)) 185 | 186 | # KORREKTUR: DUMMY wurde importiert 187 | if is_own_piece and piece_value != EMPTY and piece_value != DUMMY: 188 | # Startfeld ausgewählt 189 | self.selected_cell = board_pos 190 | self.from_pos = board_pos 191 | 192 | # Mögliche Züge anzeigen 193 | self.show_possible_moves(board_pos) 194 | else: 195 | # Zielfeld ausgewählt 196 | self.to_pos = board_pos 197 | 198 | # Automatisch ausführen wenn beide Felder ausgefüllt sind 199 | if self.from_pos and self.to_pos: 200 | # Finde den entsprechenden Zug 201 | piece = next((p for p in self.engine.pieces 202 | if p['pos'] == self.from_pos and not p['captured']), None) 203 | 204 | if piece: 205 | possible_moves = self.engine.generate_moves(piece['color']) 206 | valid_moves = [move for move in possible_moves 207 | if move['from_pos'] == self.from_pos and move['to_pos'] == self.to_pos] 208 | 209 | if valid_moves: 210 | move = valid_moves[0] 211 | if self.engine.execute_move(move): 212 | self.gui.update_display() 213 | # Automatischer Computerzug 214 | self.gui.automatic_computer_move() 215 | else: 216 | messagebox.showerror("Fehler", "Ungültiger Zug!") 217 | else: 218 | messagebox.showerror("Fehler", "Ungültiger Zug für diese Figur!") 219 | else: 220 | messagebox.showerror("Fehler", "Keine Figur auf dem Startfeld!") 221 | 222 | self.clear_selection() 223 | 224 | def clear_selection(self): 225 | """Setzt die Auswahl zurück""" 226 | self.from_pos = None 227 | self.to_pos = None 228 | self.selected_cell = None 229 | self.clear_highlights() 230 | 231 | def update_display(self): 232 | """Aktualisiert die Anzeige""" 233 | self.draw_board() 234 | self.draw_pieces() 235 | 236 | class ChessGUI: 237 | def __init__(self): 238 | self.root = tk.Tk() 239 | self.root.title("Chessteg - Didaktisches Schachprogramm") 240 | self.root.geometry("800x600") 241 | 242 | self.engine = ChesstegEngine() 243 | self.game_mode = 'human_vs_human' 244 | self.ai_thinking = False 245 | 246 | self.create_widgets() 247 | self.update_display() 248 | 249 | def create_widgets(self): 250 | """Erstellt die GUI-Elemente""" 251 | # Haupt-Frame 252 | main_frame = ttk.Frame(self.root) 253 | main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) 254 | 255 | # Linke Seite: Schachbrett 256 | left_frame = ttk.Frame(main_frame) 257 | left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) 258 | 259 | self.chess_board = ChessBoard(left_frame, self.engine, self) 260 | self.chess_board.pack(pady=10) 261 | 262 | # Rechte Seite: Steuerung 263 | right_frame = ttk.Frame(main_frame, width=250) 264 | right_frame.pack(side=tk.RIGHT, fill=tk.Y, padx=(10, 0)) 265 | right_frame.pack_propagate(False) 266 | 267 | # Titel 268 | title_label = ttk.Label(right_frame, text="Chessteg Schach", 269 | font=('Arial', 16, 'bold')) 270 | title_label.pack(pady=(0, 20)) 271 | 272 | # Status-Anzeige 273 | self.status_frame = ttk.LabelFrame(right_frame, text="Spielstatus", padding=10) 274 | self.status_frame.pack(fill=tk.X, pady=(0, 10)) 275 | 276 | self.turn_label = ttk.Label(self.status_frame, text="Weiß am Zug") 277 | self.turn_label.pack(anchor=tk.W) 278 | 279 | self.eval_label = ttk.Label(self.status_frame, text="Bewertung: 0") 280 | self.eval_label.pack(anchor=tk.W) 281 | 282 | self.status_label = ttk.Label(self.status_frame, text="Status: Läuft") 283 | self.status_label.pack(anchor=tk.W) 284 | 285 | # Spielmodus 286 | mode_frame = ttk.LabelFrame(right_frame, text="Spielmodus", padding=10) 287 | mode_frame.pack(fill=tk.X, pady=(0, 10)) 288 | 289 | self.mode_label = ttk.Label(mode_frame, text="Modus: Mensch vs Mensch") 290 | self.mode_label.pack(anchor=tk.W) 291 | 292 | ttk.Button(mode_frame, text="Computerzug", 293 | command=self.computer_move).pack(fill=tk.X, pady=2) 294 | ttk.Button(mode_frame, text="Spieler vs KI", 295 | command=lambda: self.set_game_mode('human_vs_ai')).pack(fill=tk.X, pady=2) 296 | ttk.Button(mode_frame, text="KI vs KI", 297 | command=lambda: self.set_game_mode('ai_vs_ai')).pack(fill=tk.X, pady=2) 298 | 299 | # Steuerung 300 | control_frame = ttk.LabelFrame(right_frame, text="Steuerung", padding=10) 301 | control_frame.pack(fill=tk.X, pady=(0, 10)) 302 | 303 | ttk.Button(control_frame, text="Neues Spiel", 304 | command=self.new_game).pack(fill=tk.X, pady=2) 305 | ttk.Button(control_frame, text="Reset", 306 | command=self.reset_game).pack(fill=tk.X, pady=2) 307 | ttk.Button(control_frame, text="Zug zurück", 308 | command=self.undo_move).pack(fill=tk.X, pady=2) 309 | 310 | # KI-Status 311 | self.thinking_label = ttk.Label(right_frame, text="", foreground='red') 312 | self.thinking_label.pack(pady=5) 313 | 314 | def get_mode_name(self): 315 | names = { 316 | 'human_vs_human': 'Mensch vs Mensch', 317 | 'human_vs_ai': 'Mensch vs KI', 318 | 'ai_vs_ai': 'KI vs KI' 319 | } 320 | return names.get(self.game_mode, 'Unbekannt') 321 | 322 | def update_status(self): 323 | """Aktualisiert die Status-Anzeige""" 324 | turn_text = "Weiß am Zug" if self.engine.white_turn else "Schwarz am Zug" 325 | self.turn_label.config(text=turn_text) 326 | 327 | self.eval_label.config(text=f"Bewertung: {self.engine.evaluation}") 328 | 329 | if self.engine.checkmate: 330 | status_text = "SCHACHMATT!" 331 | elif self.engine.stalemate: 332 | status_text = "Patt!" 333 | elif self.engine.is_king_in_check(WHITE if self.engine.white_turn else BLACK): 334 | status_text = "SCHACH!" 335 | else: 336 | status_text = "Läuft" 337 | self.status_label.config(text=f"Status: {status_text}") 338 | 339 | self.mode_label.config(text=f"Modus: {self.get_mode_name()}") 340 | 341 | if self.ai_thinking: 342 | self.thinking_label.config(text="KI denkt nach...") 343 | else: 344 | self.thinking_label.config(text="") 345 | 346 | def computer_move(self): 347 | """Führt einen Computerzug aus""" 348 | if self.ai_thinking: 349 | return 350 | 351 | self.ai_thinking = True 352 | self.update_status() 353 | 354 | def ai_thread(): 355 | best_move = self.engine.computer_move() 356 | if best_move: 357 | self.engine.execute_move(best_move) 358 | 359 | self.ai_thinking = False 360 | self.root.after(0, self.update_display) 361 | 362 | # Starte KI-Berechnung in separatem Thread 363 | thread = threading.Thread(target=ai_thread) 364 | thread.daemon = True 365 | thread.start() 366 | 367 | def automatic_computer_move(self): 368 | """Startet automatischen Computerzug nach Spielerzug""" 369 | if self.game_mode in ['human_vs_ai', 'ai_vs_ai']: 370 | current_color = WHITE if self.engine.white_turn else BLACK 371 | 372 | # Prüfe ob Computer am Zug ist 373 | if ((self.game_mode == 'human_vs_ai' and current_color == BLACK) or 374 | self.game_mode == 'ai_vs_ai'): 375 | 376 | # Kurze Verzögerung 377 | self.root.after(1000, self.computer_move) 378 | 379 | def set_game_mode(self, mode): 380 | """Setzt den Spielmodus""" 381 | self.game_mode = mode 382 | self.update_status() 383 | 384 | if mode == 'ai_vs_ai': 385 | self.start_ai_vs_ai() 386 | 387 | def start_ai_vs_ai(self): 388 | """Startet KI vs KI Modus""" 389 | if self.game_mode == 'ai_vs_ai' and not self.ai_thinking: 390 | self.computer_move() 391 | 392 | def new_game(self): 393 | """Startet ein neues Spiel""" 394 | self.engine = ChesstegEngine() 395 | self.chess_board.engine = self.engine 396 | self.chess_board.clear_selection() 397 | self.game_mode = 'human_vs_human' 398 | self.update_display() 399 | 400 | def reset_game(self): 401 | """Setzt das Spiel zurück""" 402 | self.engine.initialize_pieces() 403 | self.engine.white_turn = True 404 | self.engine.checkmate = False 405 | self.engine.stalemate = False 406 | self.chess_board.clear_selection() 407 | self.game_mode = 'human_vs_human' 408 | self.update_display() 409 | 410 | def undo_move(self): 411 | """Macht den letzten Zug rückgängig""" 412 | if self.engine.undo_move(): 413 | self.update_display() 414 | 415 | def update_display(self): 416 | """Aktualisiert die gesamte Anzeige""" 417 | self.chess_board.update_display() 418 | self.update_status() 419 | 420 | def run(self): 421 | """Startet die Hauptschleife""" 422 | self.root.mainloop() 423 | 424 | if __name__ == "__main__": 425 | gui = ChessGUI() 426 | gui.run() -------------------------------------------------------------------------------- /chessteg_modular/gui/chess_gui.py: -------------------------------------------------------------------------------- 1 | """ 2 | Chessteg GUI Module - KORRIGIERTE VERSION (Behebt KI-Probleme) 3 | """ 4 | 5 | import tkinter as tk 6 | from tkinter import ttk, messagebox 7 | import threading 8 | import time 9 | 10 | # KORREKTUR: Korrekter Import über das 'engine' Paket 11 | from engine.core import ChesstegEngine, WHITE, BLACK, EMPTY, PAWN, ROOK, BISHOP, KNIGHT, QUEEN, KING, DUMMY 12 | from .chess_board import ChessBoard 13 | 14 | class ChessGUI: 15 | def __init__(self): 16 | self.root = tk.Tk() 17 | self.root.title("Chessteg - Didaktisches Schachprogramm") 18 | self.root.geometry("800x600") 19 | 20 | self.engine = ChesstegEngine() 21 | self.game_mode = 'human_vs_human' # Standard: Mensch vs Mensch 22 | self.ai_thinking = False 23 | self.editor_mode = False 24 | self.selected_editor_piece = {'type': PAWN, 'color': WHITE} 25 | 26 | # 🚨 KORREKTUR: Thread-Synchronisation 27 | self.thread_lock = threading.Lock() 28 | 29 | self.create_widgets() 30 | self.setup_editor_controls() 31 | self.update_display() 32 | 33 | def create_widgets(self): 34 | """Erstellt die GUI-Elemente""" 35 | # Haupt-Frame 36 | main_frame = ttk.Frame(self.root) 37 | main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) 38 | 39 | # Linke Seite: Schachbrett 40 | left_frame = ttk.Frame(main_frame) 41 | left_frame.pack(side=tk.LEFT, padx=10, pady=10) 42 | 43 | self.chess_board = ChessBoard(left_frame, self.engine, self) 44 | self.chess_board.pack() 45 | 46 | # Rechte Seite: Steuerung und Status 47 | right_frame = ttk.Frame(main_frame) 48 | right_frame.pack(side=tk.RIGHT, fill=tk.Y, padx=10, pady=10) 49 | 50 | # Status 51 | status_frame = ttk.LabelFrame(right_frame, text="Status & Bewertung") 52 | status_frame.pack(fill=tk.X, pady=5) 53 | 54 | self.status_label = ttk.Label(status_frame, text="Status: Neues Spiel") 55 | self.status_label.pack(fill=tk.X, padx=5, pady=5) 56 | 57 | self.turn_label = ttk.Label(status_frame, text="Am Zug: Weiß (Human)") 58 | self.turn_label.pack(fill=tk.X, padx=5, pady=5) 59 | 60 | # Bewertung 61 | self.eval_label = ttk.Label(status_frame, text="Bewertung: N/A") 62 | self.eval_label.pack(fill=tk.X, padx=5, pady=5) 63 | 64 | # Spielmodus-Steuerung 65 | mode_frame = ttk.LabelFrame(right_frame, text="Spielmodus") 66 | mode_frame.pack(fill=tk.X, pady=10) 67 | 68 | ttk.Button(mode_frame, text="Mensch vs Mensch", 69 | command=lambda: self.set_game_mode('human_vs_human')).pack(fill=tk.X, pady=2) 70 | ttk.Button(mode_frame, text="Mensch vs KI", 71 | command=lambda: self.set_game_mode('human_vs_ai')).pack(fill=tk.X, pady=2) 72 | ttk.Button(mode_frame, text="KI vs KI", 73 | command=lambda: self.set_game_mode('ai_vs_ai')).pack(fill=tk.X, pady=2) 74 | 75 | # Aktionen 76 | action_frame = ttk.LabelFrame(right_frame, text="Aktionen") 77 | action_frame.pack(fill=tk.X, pady=10) 78 | 79 | ttk.Button(action_frame, text="Neues Spiel", command=self.new_game).pack(fill=tk.X, pady=2) 80 | ttk.Button(action_frame, text="Zurücksetzen", command=self.reset_game).pack(fill=tk.X, pady=2) 81 | ttk.Button(action_frame, text="Zug zurück", command=self.undo_move).pack(fill=tk.X, pady=2) 82 | ttk.Button(action_frame, text="Brett drehen (visuell)", command=self.chess_board.flip_board).pack(fill=tk.X, pady=2) 83 | 84 | # Editor-Steuerung 85 | self.editor_frame = ttk.LabelFrame(right_frame, text="Editor", style='TFrame') 86 | self.editor_toggle_button = ttk.Button(right_frame, text="Editor AN/AUS", command=self.toggle_editor_mode) 87 | self.editor_toggle_button.pack(fill=tk.X, pady=10) 88 | 89 | 90 | def setup_editor_controls(self): 91 | """Erstellt die Steuerelemente für den Editor-Modus""" 92 | 93 | # Figurenauswahl 94 | piece_options_frame = ttk.Frame(self.editor_frame) 95 | piece_options_frame.pack(fill=tk.X, padx=5, pady=5) 96 | 97 | ttk.Label(piece_options_frame, text="Figur:").pack(side=tk.LEFT, padx=5) 98 | 99 | self.piece_var = tk.StringVar(self.root, 'P') 100 | pieces = ['P', 'N', 'B', 'R', 'Q', 'K', 'E'] # 'E' für Empty/Entfernen 101 | piece_menu = ttk.OptionMenu(piece_options_frame, self.piece_var, 'P', *pieces, command=self._update_selected_piece) 102 | piece_menu.pack(side=tk.LEFT, fill=tk.X, expand=True) 103 | 104 | ttk.Label(piece_options_frame, text="Farbe:").pack(side=tk.LEFT, padx=5) 105 | self.color_var = tk.StringVar(self.root, 'W') 106 | colors = ['W', 'B'] 107 | color_menu = ttk.OptionMenu(piece_options_frame, self.color_var, 'W', *colors, command=self._update_selected_piece) 108 | color_menu.pack(side=tk.LEFT, fill=tk.X, expand=True) 109 | 110 | self._update_selected_piece() 111 | 112 | # Aktionen 113 | editor_actions_frame = ttk.Frame(self.editor_frame) 114 | editor_actions_frame.pack(fill=tk.X, padx=5, pady=5) 115 | 116 | ttk.Button(editor_actions_frame, text="Brett leeren", command=self._editor_clear).pack(fill=tk.X, pady=2) 117 | ttk.Button(editor_actions_frame, text="Standardstellung", command=self._editor_standard).pack(fill=tk.X, pady=2) 118 | 119 | 120 | def _get_piece_code(self, symbol): 121 | """Hilfsfunktion, um Symbole in Engine-Typen zu übersetzen""" 122 | mapping = {'P': PAWN, 'N': KNIGHT, 'B': BISHOP, 'R': ROOK, 'Q': QUEEN, 'K': KING, 'E': EMPTY} 123 | return mapping.get(symbol, PAWN) 124 | 125 | def _get_color_code(self, symbol): 126 | """Hilfsfunktion, um Symbole in Engine-Farben zu übersetzen""" 127 | return WHITE if symbol == 'W' else BLACK 128 | 129 | def _update_selected_piece(self, *args): 130 | """Aktualisiert die Figur, die im Editor platziert werden soll.""" 131 | self.selected_editor_piece = { 132 | 'type': self._get_piece_code(self.piece_var.get()), 133 | 'color': self._get_color_code(self.color_var.get()) 134 | } 135 | 136 | def _editor_clear(self): 137 | self.engine.editor_clear_board() 138 | self.update_display() 139 | self.update_status() 140 | 141 | def _editor_standard(self): 142 | self.engine.editor_standard_position() 143 | self.update_display() 144 | self.update_status() 145 | 146 | def toggle_editor_mode(self): 147 | """Schaltet den Editor-Modus um""" 148 | self.editor_mode = not self.editor_mode 149 | if self.editor_mode: 150 | self.chess_board.enable_editor_mode() 151 | self.editor_frame.pack(fill=tk.X, pady=5) 152 | self.set_game_mode('editor') 153 | self.editor_toggle_button.config(text="Editor AN/AUS (Editor Modus aktiv)") 154 | else: 155 | self.chess_board.disable_editor_mode() 156 | self.editor_frame.pack_forget() 157 | self.set_game_mode('human_vs_human') 158 | self.editor_toggle_button.config(text="Editor AN/AUS") 159 | 160 | def set_game_mode(self, mode): 161 | """Setzt den aktuellen Spielmodus.""" 162 | self.game_mode = mode 163 | if mode != 'editor': 164 | # Verhindert, dass der Editor-Modus aktiv bleibt 165 | self.editor_mode = False 166 | self.chess_board.disable_editor_mode() 167 | self.editor_frame.pack_forget() 168 | self.editor_toggle_button.config(text="Editor AN/AUS") 169 | 170 | # Startet ggf. den KI vs KI Modus 171 | if self.game_mode == 'ai_vs_ai': 172 | self.start_ai_vs_ai() 173 | 174 | self.chess_board.clear_selection() 175 | self.update_status() 176 | self.check_ai_turn() 177 | 178 | def update_status(self): 179 | """Aktualisiert die Status-Labels.""" 180 | current_turn = "Weiß" if self.engine.white_turn else "Schwarz" 181 | current_color = WHITE if self.engine.white_turn else BLACK 182 | 183 | mode_text = f" ({self.game_mode})" 184 | if self.game_mode == 'human_vs_human': 185 | mode_text = " (Mensch vs Mensch)" 186 | elif self.game_mode == 'human_vs_ai': 187 | opponent = "KI" if current_color == BLACK else "Mensch" 188 | mode_text = f" (Mensch vs KI - {opponent} am Zug)" 189 | elif self.game_mode == 'ai_vs_ai': 190 | mode_text = " (KI vs KI)" 191 | elif self.game_mode == 'editor': 192 | mode_text = " (Editor Modus)" 193 | 194 | self.turn_label.config(text=f"Am Zug: {current_turn}{mode_text}") 195 | 196 | if self.engine.checkmate: 197 | winner = "Schwarz" if self.engine.white_turn else "Weiß" 198 | self.status_label.config(text=f"Status: Schachmatt! {winner} gewinnt.") 199 | elif self.engine.stalemate: 200 | self.status_label.config(text="Status: Patt!") 201 | elif self.engine.is_king_in_check(current_color): 202 | self.status_label.config(text="Status: Schach!") 203 | else: 204 | self.status_label.config(text="Status: Im Spiel") 205 | 206 | # KORREKTUR: Aufruf der Methode evaluate_position() im Evaluator-Objekt 207 | if self.engine.evaluator: 208 | evaluation_score = self.engine.evaluator.evaluate_position() 209 | self.eval_label.config(text=f"Bewertung: {evaluation_score:.2f}") 210 | else: 211 | self.eval_label.config(text="Bewertung: N/A") 212 | 213 | def computer_move(self): 214 | """Lässt die KI einen Zug berechnen und ausführen (in einem Thread)""" 215 | if self.ai_thinking: 216 | return 217 | 218 | self.ai_thinking = True 219 | print("🤖 KI beginnt mit Zugberechnung...") 220 | 221 | # 🚨 KORREKTUR: Thread-Safe GUI Updates 222 | def calculate_move(): 223 | try: 224 | with self.thread_lock: 225 | best_move = self.engine.search_algorithm.computer_move() 226 | 227 | # 🚨 KORREKTUR: Thread-sicheres GUI-Update 228 | if self.root and self.root.winfo_exists(): 229 | self.root.after(0, lambda: self._process_computer_move(best_move)) 230 | 231 | except Exception as e: 232 | print(f"❌ KI-Berechnungsfehler: {e}") 233 | import traceback 234 | traceback.print_exc() 235 | # 🚨 KORREKTUR: Immer sicherstellen, dass ai_thinking zurückgesetzt wird 236 | if self.root and self.root.winfo_exists(): 237 | self.root.after(0, self._reset_ai_thinking) 238 | 239 | threading.Thread(target=calculate_move, daemon=True).start() 240 | 241 | def _process_computer_move(self, best_move): 242 | """Verarbeitet das Ergebnis des KI-Zugs - KORRIGIERTE VERSION""" 243 | print("🔄 Verarbeite KI-Zug...") 244 | self.ai_thinking = False 245 | 246 | if best_move: 247 | print(f"🔧 KI möchte Zug ausführen: {self._move_to_notation(best_move)}") 248 | if self.engine.make_move(best_move): 249 | print(f"✅ KI-Zug ausgeführt: {self._move_to_notation(best_move)}") 250 | self.update_display() 251 | self.update_status() 252 | 253 | # Im KI vs KI Modus sofort nächsten Zug starten 254 | if self.game_mode == 'ai_vs_ai' and not self.engine.is_game_over(): 255 | print("🔄 KI vs KI: Starte nächsten Zug...") 256 | self.root.after(1000, self.computer_move) # 1 Sekunde Pause zwischen Zügen 257 | else: 258 | self.check_ai_turn() # Normale Prüfung für Mensch vs KI 259 | else: 260 | print("❌ KI-Zug fehlgeschlagen - Zug konnte nicht ausgeführt werden") 261 | # Im KI vs KI Modus trotzdem weitermachen 262 | if self.game_mode == 'ai_vs_ai' and not self.engine.is_game_over(): 263 | fallback_move = self._get_fallback_move() 264 | if fallback_move: 265 | print(f"🔄 Versuche Fallback-Zug: {self._move_to_notation(fallback_move)}") 266 | if self.engine.make_move(fallback_move): 267 | self.update_display() 268 | self.update_status() 269 | self.root.after(1000, self.computer_move) 270 | else: 271 | print("❌ Kein KI-Zug gefunden") 272 | # Prüfe ob Spiel vorbei ist 273 | if self.engine.checkmate: 274 | print("🏁 Schachmatt erkannt!") 275 | messagebox.showinfo("Spielende", "Schachmatt!") 276 | elif self.engine.stalemate: 277 | print("🏁 Patt erkannt!") 278 | messagebox.showinfo("Spielende", "Patt!") 279 | else: 280 | print("⚠️ KI Problem - kein Zug gefunden aber Spiel nicht beendet") 281 | # Im KI vs KI Modus trotzdem weitermachen mit Fallback 282 | if self.game_mode == 'ai_vs_ai': 283 | fallback_move = self._get_fallback_move() 284 | if fallback_move: 285 | print(f"🔄 Verwende Fallback-Zug: {self._move_to_notation(fallback_move)}") 286 | if self.engine.make_move(fallback_move): 287 | self.update_display() 288 | self.update_status() 289 | self.root.after(1000, self.computer_move) 290 | else: 291 | print("💥 Fallback-Zug auch fehlgeschlagen - KI vs KI gestoppt") 292 | else: 293 | print("💥 Kein Fallback-Zug verfügbar - KI vs KI gestoppt") 294 | 295 | def _get_fallback_move(self): 296 | """Findet einen einfachen Fallback-Zug falls die KI versagt - KORRIGIERTE VERSION""" 297 | current_color = -1 if self.engine.white_turn else 1 # Gegenteilige Farbe (KI war am Zug) 298 | moves = self.engine.generate_all_moves(current_color) 299 | if moves: 300 | # Nimm den ersten verfügbaren Zug (einfacher Algorithmus) 301 | print(f"🔄 Fallback: {len(moves)} Züge verfügbar, nehme ersten") 302 | return moves[0] 303 | else: 304 | print("💥 Fallback: Keine Züge verfügbar!") 305 | return None 306 | 307 | def _reset_ai_thinking(self): 308 | """Setzt den KI-Denkstatus zurück (Fehlerbehandlung).""" 309 | self.ai_thinking = False 310 | print("🔄 KI-Denkstatus zurückgesetzt") 311 | 312 | def check_ai_turn(self): 313 | """Prüft, ob die KI am Zug ist und startet ggf. die Berechnung.""" 314 | # 🚨 KORREKTUR: Prüfe ob KI bereits denkt 315 | if self.ai_thinking: 316 | return 317 | 318 | current_turn_color = WHITE if self.engine.white_turn else BLACK 319 | 320 | is_ai_turn = False 321 | if self.game_mode == 'human_vs_ai' and current_turn_color == BLACK: 322 | is_ai_turn = True 323 | elif self.game_mode == 'ai_vs_ai': 324 | is_ai_turn = True 325 | 326 | if is_ai_turn and not self.engine.checkmate and not self.engine.stalemate: 327 | # 🚨 KORREKTUR: Verzögerung für GUI-Responsiveness 328 | print(f"🤖 KI ist am Zug (Farbe: {'Schwarz' if current_turn_color == BLACK else 'Weiß'})") 329 | self.root.after(500, self.computer_move) 330 | 331 | def start_ai_vs_ai(self): 332 | """Startet KI vs KI Modus - KORRIGIERTE VERSION""" 333 | if self.game_mode == 'ai_vs_ai' and not self.ai_thinking: 334 | print("🎮 Starte KI vs KI Modus...") 335 | # Kurze Verzögerung für bessere GUI-Responsiveness 336 | self.root.after(2000, self.computer_move) 337 | 338 | def new_game(self): 339 | """Startet ein neues Spiel""" 340 | # 🚨 KORREKTUR: Thread-Safe Engine-Reset 341 | with self.thread_lock: 342 | self.engine = ChesstegEngine() 343 | self.chess_board.engine = self.engine 344 | self.chess_board.clear_selection() 345 | self.game_mode = 'human_vs_human' 346 | self.update_display() 347 | print("🔄 Neues Spiel gestartet") 348 | 349 | def reset_game(self): 350 | """Setzt das Spiel zurück""" 351 | # 🚨 KORREKTUR: Thread-Safe Reset 352 | with self.thread_lock: 353 | self.engine.initialize_pieces() 354 | self.engine.white_turn = True 355 | self.engine.checkmate = False 356 | self.engine.stalemate = False 357 | self.chess_board.clear_selection() 358 | self.game_mode = 'human_vs_human' 359 | self.update_display() 360 | print("🔄 Spiel zurückgesetzt") 361 | 362 | def undo_move(self): 363 | """Macht den letzten Zug rückgängig""" 364 | # 🚨 KORREKTUR: Thread-Safe Undo 365 | with self.thread_lock: 366 | if self.engine.undo_move(): 367 | self.update_display() 368 | self.update_status() # Status nach Undo aktualisieren 369 | print("↩️ Zug rückgängig gemacht") 370 | 371 | def update_display(self): 372 | """Aktualisiert die gesamte Anzeige""" 373 | try: 374 | self.chess_board.update_display() 375 | self.update_status() 376 | # 🚨 KORREKTUR: Explizites Update des Hauptfensters 377 | self.root.update_idletasks() 378 | except Exception as e: 379 | print(f"❌ Fehler beim GUI-Update: {e}") 380 | 381 | def _move_to_notation(self, move): 382 | """Konvertiert Zug zu algebraischer Notation""" 383 | files = ['', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', ''] 384 | from_pos = move['from_pos'] 385 | to_pos = move['to_pos'] 386 | 387 | from_file = files[from_pos % 10] 388 | from_rank = str(from_pos // 10 - 1) 389 | to_file = files[to_pos % 10] 390 | to_rank = str(to_pos // 10 - 1) 391 | 392 | return f"{from_file}{from_rank}{to_file}{to_rank}" 393 | 394 | def run(self): 395 | """Startet die Tkinter-Hauptschleife""" 396 | try: 397 | print("🎮 Chessteg GUI gestartet") 398 | self.root.mainloop() 399 | except KeyboardInterrupt: 400 | print("Programm durch Benutzer beendet") 401 | except Exception as e: 402 | print(f"Unerwarteter Fehler: {e}") 403 | finally: 404 | # 🚨 KORREKTUR: Sauberes Beenden 405 | if hasattr(self, 'root') and self.root: 406 | self.root.quit() 407 | print("👋 Chessteg beendet") -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Chessteg – Didaktisches Schachprogramm 7 | 466 | 467 | 468 |
469 | Chessteg Header 470 |
471 | 472 | 482 | 483 |
484 |

Chessteg – Didaktisches Schachprogramm

485 |

486 | Mehr erfahren / Learn more 487 |

488 | 489 | 490 | 524 | 525 |
526 |
527 |
528 | 529 |
530 |
531 | 532 |
533 |
534 |

Spielsteuerung

535 | 536 | 537 | 538 |
539 | Weiß am Zug 540 | 541 |
542 | 543 | 544 | 545 | 546 | 547 | 548 |
549 | 550 | 551 | 552 | 553 |
554 |
555 | 556 |
557 |

Spielerzug

558 |
559 | 560 | 561 | 562 | 563 |
564 | 565 |
566 | 567 |
568 |

Spielinformation

569 |
570 |
Bewertung: 0
571 |
Status: Läuft
572 |
Rochade: Möglich
573 |
574 |
575 | 576 | 577 |
578 |

Zugrücknahme

579 |
580 | 583 | 586 | 589 |
590 |
591 |
Zug: 1Vollzug: 1
592 |
Historie: 0/0
593 |
594 |
595 | 596 | 597 |
598 |

Debug-Tools

599 | 600 | 601 | 602 |
603 | Debug-Info: Bereit 604 |
605 |
606 | 607 |
608 |

System

609 | 610 | 611 |
612 |
613 |
614 |
615 | 616 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | -------------------------------------------------------------------------------- /chessteg_modular/engine/evaluation.py: -------------------------------------------------------------------------------- 1 | """ 2 | Chessteg Evaluation Module - KORRIGIERTE VERSION MIT DEBUG 3 | Vollständige Bewertungsfunktion für Schachstellungen 4 | """ 5 | 6 | import math 7 | from typing import Dict, Any, List 8 | 9 | 10 | class PositionEvaluator: 11 | """ 12 | Bewertet Schachstellungen basierend auf Material, Position und strategischen Faktoren 13 | """ 14 | 15 | def __init__(self, engine): 16 | self.engine = engine 17 | 18 | # Erweiterte Bewertungstabellen 19 | self.evaluation_table = { 20 | 'material': { 21 | 1: 100, # PAWN 22 | 4: 320, # KNIGHT 23 | 3: 330, # BISHOP 24 | 5: 500, # ROOK 25 | 9: 900, # QUEEN 26 | 99: 20000 # KING 27 | }, 28 | 29 | 'position': { 30 | # Bauer - Positionstabelle 31 | 1: [ 32 | [0, 0, 0, 0, 0, 0, 0, 0], 33 | [50, 50, 50, 50, 50, 50, 50, 50], 34 | [10, 10, 20, 30, 30, 20, 10, 10], 35 | [5, 5, 10, 25, 25, 10, 5, 5], 36 | [0, 0, 0, 20, 20, 0, 0, 0], 37 | [5, -5, -10, 0, 0, -10, -5, 5], 38 | [5, 10, 10, -20, -20, 10, 10, 5], 39 | [0, 0, 0, 0, 0, 0, 0, 0] 40 | ], 41 | 42 | # Springer - Positionstabelle 43 | 4: [ 44 | [-50, -40, -30, -30, -30, -30, -40, -50], 45 | [-40, -20, 0, 5, 5, 0, -20, -40], 46 | [-30, 5, 10, 15, 15, 10, 5, -30], 47 | [-30, 0, 15, 20, 20, 15, 0, -30], 48 | [-30, 5, 15, 20, 20, 15, 5, -30], 49 | [-30, 0, 10, 15, 15, 10, 0, -30], 50 | [-40, -20, 0, 0, 0, 0, -20, -40], 51 | [-50, -40, -30, -30, -30, -30, -40, -50] 52 | ], 53 | 54 | # Läufer - Positionstabelle 55 | 3: [ 56 | [-20, -10, -10, -10, -10, -10, -10, -20], 57 | [-10, 0, 0, 0, 0, 0, 0, -10], 58 | [-10, 0, 5, 10, 10, 5, 0, -10], 59 | [-10, 5, 5, 10, 10, 5, 5, -10], 60 | [-10, 0, 10, 10, 10, 10, 0, -10], 61 | [-10, 10, 10, 10, 10, 10, 10, -10], 62 | [-10, 5, 0, 0, 0, 0, 5, -10], 63 | [-20, -10, -10, -10, -10, -10, -10, -20] 64 | ], 65 | 66 | # Turm - Positionstabelle 67 | 5: [ 68 | [0, 0, 0, 5, 5, 0, 0, 0], 69 | [-5, 0, 0, 0, 0, 0, 0, -5], 70 | [-5, 0, 0, 0, 0, 0, 0, -5], 71 | [-5, 0, 0, 0, 0, 0, 0, -5], 72 | [-5, 0, 0, 0, 0, 0, 0, -5], 73 | [-5, 0, 0, 0, 0, 0, 0, -5], 74 | [5, 10, 10, 10, 10, 10, 10, 5], 75 | [0, 0, 0, 0, 0, 0, 0, 0] 76 | ], 77 | 78 | # Dame - Positionstabelle 79 | 9: [ 80 | [-20, -10, -10, -5, -5, -10, -10, -20], 81 | [-10, 0, 5, 0, 0, 0, 0, -10], 82 | [-10, 5, 5, 5, 5, 5, 0, -10], 83 | [0, 0, 5, 5, 5, 5, 0, -5], 84 | [-5, 0, 5, 5, 5, 5, 0, -5], 85 | [-10, 0, 5, 5, 5, 5, 0, -10], 86 | [-10, 0, 0, 0, 0, 0, 0, -10], 87 | [-20, -10, -10, -5, -5, -10, -10, -20] 88 | ], 89 | 90 | # König - Positionstabelle 91 | 99: [ 92 | [20, 30, 10, 0, 0, 10, 30, 20], 93 | [20, 20, 0, 0, 0, 0, 20, 20], 94 | [-10, -20, -20, -20, -20, -20, -20, -10], 95 | [-20, -30, -30, -40, -40, -30, -30, -20], 96 | [-30, -40, -40, -50, -50, -40, -40, -30], 97 | [-30, -40, -40, -50, -50, -40, -40, -30], 98 | [-30, -40, -40, -50, -50, -40, -40, -30], 99 | [-30, -40, -40, -50, -50, -40, -40, -30] 100 | ] 101 | } 102 | } 103 | 104 | # Debug-Zähler 105 | self.eval_counter = 0 106 | 107 | def evaluate_position(self) -> int: 108 | """ 109 | Vollständige Stellungsbewertung - KORRIGIERTE VERSION MIT DEBUG 110 | """ 111 | self.eval_counter += 1 112 | 113 | # Terminal-Stellungen zuerst prüfen 114 | if self.engine.checkmate: 115 | current_color = 1 if self.engine.white_turn else -1 116 | if self.engine.is_king_in_check(current_color): 117 | score = -30000 if current_color == 1 else 30000 118 | if self.eval_counter <= 5: 119 | print(f" ♟️ MATT Bewertung: {score}") 120 | return score 121 | 122 | if self.engine.stalemate: 123 | if self.eval_counter <= 5: 124 | print(f" 🤝 PATT Bewertung: 0") 125 | return 0 126 | 127 | # Einfache Materialbewertung zuerst testen 128 | material = self._evaluate_material() 129 | 130 | # 🚨 DEBUG: Zeige erste Bewertungen 131 | if self.eval_counter <= 5: 132 | print(f" 📊 Bewertung #{self.eval_counter}: Material = {material}") 133 | 134 | # Nur Material für erste Tests - später erweitern 135 | total_score = material 136 | 137 | if self.eval_counter <= 5: 138 | print(f" 📈 Gesamtbewertung: {total_score}") 139 | 140 | return total_score 141 | 142 | def _evaluate_material(self) -> int: 143 | """ 144 | Einfache Materialbewertung - KORRIGIERTE VERSION 145 | """ 146 | material = 0 147 | 148 | for piece in self.engine.pieces: 149 | if piece['captured']: 150 | continue 151 | 152 | piece_value = self.evaluation_table['material'].get(piece['type'], 0) 153 | 154 | if piece['color'] == 1: # WHITE 155 | material += piece_value 156 | else: # BLACK 157 | material -= piece_value 158 | 159 | return material 160 | 161 | def _evaluate_piece_squares(self) -> int: 162 | """ 163 | Bewertet die Position der Figuren auf dem Brett 164 | """ 165 | position_score = 0 166 | 167 | for piece in self.engine.pieces: 168 | if piece['captured']: 169 | continue 170 | 171 | board_row, board_col = self._position_to_coordinates(piece['position']) 172 | 173 | # Für weiße Figuren: Tabelle von unten nach oben 174 | # Für schwarze Figuren: Tabelle spiegeln 175 | if piece['color'] == 1: # WHITE 176 | row = board_row - 2 # 0-7 von weißer Seite 177 | else: # BLACK 178 | row = 7 - (board_row - 2) # 0-7 von schwarzer Seite 179 | 180 | col = board_col - 1 # 0-7 181 | 182 | # Sicherstellen, dass Indizes im gültigen Bereich 183 | row = max(0, min(7, row)) 184 | col = max(0, min(7, col)) 185 | 186 | # Positionswert aus Tabelle holen 187 | pos_value = self.evaluation_table['position'][piece['type']][row][col] 188 | 189 | if piece['color'] == 1: 190 | position_score += pos_value 191 | else: 192 | position_score -= pos_value 193 | 194 | return position_score 195 | 196 | def _evaluate_attacks(self) -> int: 197 | """ 198 | Bewertet Angriffe auf gegnerische Figuren 199 | """ 200 | attack_score = 0 201 | 202 | for piece in self.engine.pieces: 203 | if piece['captured']: 204 | continue 205 | 206 | # Angriffene Felder dieser Figur 207 | attacked_squares = self._get_attacked_squares(piece) 208 | 209 | for square in attacked_squares: 210 | target_piece = self._get_piece_at(square) 211 | if target_piece and target_piece['color'] != piece['color']: 212 | # Bonus für Angriff auf gegnerische Figur 213 | target_value = self.evaluation_table['material'][target_piece['type']] 214 | attack_bonus = target_value * 0.1 # 10% des Figurenwerts 215 | 216 | if piece['color'] == 1: 217 | attack_score += attack_bonus 218 | else: 219 | attack_score -= attack_bonus 220 | 221 | return attack_score 222 | 223 | def _evaluate_defense(self) -> int: 224 | """ 225 | Bewertet Verteidigung eigener Figuren 226 | """ 227 | defense_score = 0 228 | 229 | for piece in self.engine.pieces: 230 | if piece['captured']: 231 | continue 232 | 233 | # Zähle Verteidiger dieser Figur 234 | defenders = self._get_defenders(piece) 235 | piece_value = self.evaluation_table['material'][piece['type']] 236 | 237 | # Bonus für verteidigte Figuren 238 | defense_bonus = len(defenders) * piece_value * 0.05 # 5% pro Verteidiger 239 | 240 | if piece['color'] == 1: 241 | defense_score += defense_bonus 242 | else: 243 | defense_score -= defense_bonus 244 | 245 | return defense_score 246 | 247 | def _evaluate_king_safety(self) -> int: 248 | """ 249 | Bewertet Sicherheit der Könige 250 | """ 251 | safety_score = 0 252 | 253 | for color in [1, -1]: 254 | king = self._get_king(color) 255 | if not king: 256 | continue 257 | 258 | king_pos = king['position'] 259 | king_row, king_col = self._position_to_coordinates(king_pos) 260 | 261 | # Strafe für exponierten König in der Mitte 262 | if 3 <= king_col <= 6: # König in der Mitte 263 | safety_penalty = -30 264 | else: # König am Rand (sicherer) 265 | safety_penalty = 10 266 | 267 | # Zusätzliche Strafe wenn keine Bauern um den König 268 | pawn_shield = self._count_pawn_shield(king_pos, color) 269 | safety_penalty += (3 - pawn_shield) * -10 # Bis zu -30 Strafe 270 | 271 | if color == 1: 272 | safety_score += safety_penalty 273 | else: 274 | safety_score -= safety_penalty 275 | 276 | return safety_score 277 | 278 | def _evaluate_mobility(self) -> int: 279 | """ 280 | Bewertet Bewegungsfreiheit der Figuren 281 | """ 282 | mobility_score = 0 283 | 284 | for piece in self.engine.pieces: 285 | if piece['captured'] or piece['type'] == 99: # König ausgeschlossen 286 | continue 287 | 288 | # KORREKTUR: Verwende move_generator statt engine direkt 289 | possible_moves = self.engine.move_generator.generate_piece_moves(piece) 290 | move_count = len(possible_moves) 291 | 292 | # Mobilitätsbonus basierend auf Figurentyp 293 | mobility_bonus = move_count * self._get_mobility_weight(piece['type']) 294 | 295 | if piece['color'] == 1: 296 | mobility_score += mobility_bonus 297 | else: 298 | mobility_score -= mobility_bonus 299 | 300 | return mobility_score 301 | 302 | def _evaluate_center_control(self) -> int: 303 | """ 304 | Bewertet Kontrolle des Zentrums 305 | """ 306 | center_score = 0 307 | center_fields = [44, 45, 54, 55] # d4, e4, d5, e5 308 | 309 | for field in center_fields: 310 | # Figur auf Zentrumsfeld 311 | piece = self._get_piece_at(field) 312 | if piece: 313 | piece_value = self.evaluation_table['material'][piece['type']] / 100 314 | if piece['color'] == 1: 315 | center_score += piece_value * 5 316 | else: 317 | center_score -= piece_value * 5 318 | 319 | # Angriffe auf Zentrumsfelder 320 | attackers = self._get_attackers(field) 321 | for attacker in attackers: 322 | attacker_value = self.evaluation_table['material'][attacker['type']] / 100 323 | if attacker['color'] == 1: 324 | center_score += attacker_value * 2 325 | else: 326 | center_score -= attacker_value * 2 327 | 328 | return center_score 329 | 330 | def _evaluate_pawn_structure(self) -> int: 331 | """ 332 | Bewertet Bauernstruktur 333 | """ 334 | structure_score = 0 335 | 336 | # Doppelbauern bestrafen 337 | pawns_per_file = {} 338 | 339 | for piece in self.engine.pieces: 340 | if not piece['captured'] and piece['type'] == 1: # PAWN 341 | file = piece['position'] % 10 342 | key = f"{file}-{piece['color']}" 343 | pawns_per_file[key] = pawns_per_file.get(key, 0) + 1 344 | 345 | for key, count in pawns_per_file.items(): 346 | if count > 1: 347 | color = 1 if key.endswith("1") else -1 348 | double_pawn_penalty = -20 * (count - 1) # -20 pro zusätzlichem Bauer 349 | 350 | if color == 1: 351 | structure_score += double_pawn_penalty 352 | else: 353 | structure_score -= double_pawn_penalty 354 | 355 | return structure_score 356 | 357 | # ========================================================================= 358 | # HILFSFUNKTIONEN 359 | # ========================================================================= 360 | 361 | def _position_to_coordinates(self, position: int) -> tuple: 362 | """Konvertiert interne Position zu (row, col)""" 363 | row = position // 10 364 | col = position % 10 365 | return row, col 366 | 367 | def _get_piece_at(self, position: int) -> Any: 368 | """Gibt Figur an einer Position zurück""" 369 | for piece in self.engine.pieces: 370 | if piece['position'] == position and not piece['captured']: 371 | return piece 372 | return None 373 | 374 | def _get_attacked_squares(self, piece: Dict[str, Any]) -> List[int]: 375 | """Gibt alle von einer Figur angegriffenen Felder zurück""" 376 | # Verwende die MoveGenerator-Funktion falls verfügbar 377 | if hasattr(self.engine.move_generator, 'get_attacked_squares'): 378 | return self.engine.move_generator.get_attacked_squares(piece) 379 | 380 | # Fallback: Einfache Implementierung 381 | attacked_squares = [] 382 | piece_type = piece['type'] 383 | 384 | if piece_type == 1: # PAWN 385 | color = piece['color'] 386 | forward = 10 if color == 1 else -10 387 | for side in [forward + 1, forward - 1]: 388 | field = piece['position'] + side 389 | if self.engine.board[field] != 100: # Nicht DUMMY 390 | attacked_squares.append(field) 391 | 392 | # Für andere Figurentypen: Vereinfachte Logik 393 | # In der Praxis sollte dies vom MoveGenerator kommen 394 | return attacked_squares 395 | 396 | def _get_defenders(self, piece: Dict[str, Any]) -> List[Dict[str, Any]]: 397 | """Findet Verteidiger einer Figur""" 398 | defenders = [] 399 | 400 | for potential_defender in self.engine.pieces: 401 | if (potential_defender['captured'] or 402 | potential_defender['color'] != piece['color']): 403 | continue 404 | 405 | attacked_squares = self._get_attacked_squares(potential_defender) 406 | if piece['position'] in attacked_squares: 407 | defenders.append(potential_defender) 408 | 409 | return defenders 410 | 411 | def _get_king(self, color: int) -> Any: 412 | """Findet König einer Farbe""" 413 | for piece in self.engine.pieces: 414 | if (piece['type'] == 99 and 415 | piece['color'] == color and 416 | not piece['captured']): 417 | return piece 418 | return None 419 | 420 | def _count_pawn_shield(self, king_pos: int, color: int) -> int: 421 | """Zählt Bauern in der Nähe des Königs""" 422 | king_row, king_col = self._position_to_coordinates(king_pos) 423 | pawn_count = 0 424 | 425 | # Prüfe Bauern vor dem König 426 | for file_offset in [-1, 0, 1]: 427 | check_file = king_col + file_offset 428 | if 1 <= check_file <= 8: 429 | if color == 1: # Weißer König 430 | pawn_row = king_row - 1 # Reihe vor dem König 431 | else: # Schwarzer König 432 | pawn_row = king_row + 1 # Reihe vor dem König 433 | 434 | pawn_pos = pawn_row * 10 + check_file 435 | pawn = self._get_piece_at(pawn_pos) 436 | if pawn and pawn['type'] == 1 and pawn['color'] == color: 437 | pawn_count += 1 438 | 439 | return pawn_count 440 | 441 | def _get_mobility_weight(self, piece_type: int) -> int: 442 | """Gibt Mobilitätsgewicht für Figurentyp zurück""" 443 | weights = { 444 | 1: 1, # PAWN 445 | 4: 3, # KNIGHT 446 | 3: 2, # BISHOP 447 | 5: 2, # ROOK 448 | 9: 1 # QUEEN 449 | } 450 | return weights.get(piece_type, 1) 451 | 452 | def _get_attackers(self, position: int) -> List[Dict[str, Any]]: 453 | """Findet alle Figuren, die ein Feld angreifen""" 454 | attackers = [] 455 | 456 | for piece in self.engine.pieces: 457 | if piece['captured']: 458 | continue 459 | 460 | attacked_squares = self._get_attacked_squares(piece) 461 | if position in attacked_squares: 462 | attackers.append(piece) 463 | 464 | return attackers 465 | 466 | 467 | # Test des Evaluators 468 | if __name__ == "__main__": 469 | print("Testing PositionEvaluator...") 470 | 471 | from core import ChesstegEngine 472 | engine = ChesstegEngine() 473 | evaluator = PositionEvaluator(engine) 474 | 475 | eval = evaluator.evaluate_position() 476 | print(f"Initial position evaluation: {eval}") 477 | 478 | # Test spezifischer Komponenten 479 | material = evaluator._evaluate_material() 480 | position = evaluator._evaluate_piece_squares() 481 | print(f"Material: {material}, Position: {position}") -------------------------------------------------------------------------------- /chessteg_modular/gui/chess_board.py: -------------------------------------------------------------------------------- 1 | """ 2 | Chess Board Module - KORRIGIERTE VERSION (Behebt GUI-Aktualisierungsproblem) 3 | """ 4 | 5 | import tkinter as tk 6 | from tkinter import ttk, messagebox 7 | import tkinter.simpledialog 8 | 9 | # KORREKTUR: Korrekter Import über das 'engine.core' Paket 10 | from engine.core import ChesstegEngine, WHITE, BLACK, EMPTY, PAWN, ROOK, BISHOP, KNIGHT, QUEEN, KING, DUMMY 11 | from engine.core import PIECE_SYMBOLS # Annahme, dass PIECE_SYMBOLS aus core importiert wird 12 | 13 | 14 | class ChessBoard(tk.Frame): 15 | def __init__(self, parent, engine, gui, cell_size=60): 16 | super().__init__(parent) 17 | 18 | self.cell_size = cell_size 19 | self.board_size = cell_size * 8 20 | self.engine = engine 21 | self.gui = gui 22 | 23 | self.selected_cell = None 24 | self.from_pos = None 25 | self.possible_moves = [] 26 | self.editor_mode = False 27 | 28 | # Farben 29 | self.white_color = '#f0d9b5' 30 | self.black_color = '#b58863' 31 | self.highlight_color = '#ffeb3b' 32 | self.possible_move_color = '#90ee90' 33 | self.possible_capture_color = '#ffb6c1' 34 | 35 | # Figuren-Symbole 36 | self.piece_symbols = PIECE_SYMBOLS 37 | 38 | # Canvas 39 | self.canvas = tk.Canvas(self, width=self.board_size, height=self.board_size, bg='white') 40 | self.canvas.pack(padx=5, pady=5) 41 | self.canvas.bind("", self.on_square_click) 42 | 43 | # 🚨 KORREKTUR: Initialisiere das Board sofort 44 | self.draw_board() 45 | self.draw_pieces() 46 | 47 | # ========================================================================= 48 | # ZEICHNEN 49 | # ========================================================================= 50 | 51 | def draw_board(self): 52 | """Zeichnet das 8x8 Schachbrett""" 53 | self.canvas.delete("square") 54 | self.canvas.delete("coord") # 🚨 KORREKTUR: Lösche auch Koordinaten 55 | 56 | for row in range(8): 57 | for col in range(8): 58 | # Wechsle die Farbe für Schachbrettmuster 59 | color = self.white_color if (row + col) % 2 == 0 else self.black_color 60 | 61 | x1 = col * self.cell_size 62 | y1 = row * self.cell_size 63 | x2 = x1 + self.cell_size 64 | y2 = y1 + self.cell_size 65 | 66 | # Speichere die interne 10x10-Position im Tag 67 | engine_row = 9 - row 68 | engine_col = col + 1 69 | pos_10x10 = engine_row * 10 + engine_col 70 | 71 | self.canvas.create_rectangle(x1, y1, x2, y2, fill=color, tags=("square", f"cell_{pos_10x10}")) 72 | 73 | # Zeichne Achsenbeschriftungen 74 | self._draw_coordinates() 75 | 76 | def _draw_coordinates(self): 77 | """Zeichnet die algebraischen Koordinaten (a1, b1, ...)""" 78 | font = ("Arial", 10) 79 | offset = 5 # Abstand zum Rand 80 | 81 | # Dateien (a-h) 82 | files = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'] 83 | for i, file in enumerate(files): 84 | x = i * self.cell_size + self.cell_size // 2 85 | # Unten 86 | self.canvas.create_text(x, self.board_size - offset, text=file, tags="coord", fill="black", anchor=tk.S, font=font) 87 | # Oben 88 | self.canvas.create_text(x, offset, text=file, tags="coord", fill="black", anchor=tk.N, font=font) 89 | 90 | # Reihen (1-8) 91 | for i in range(8): 92 | rank = str(i + 1) 93 | y = (7 - i) * self.cell_size + self.cell_size // 2 94 | # Links 95 | self.canvas.create_text(offset, y, text=rank, tags="coord", fill="black", anchor=tk.W, font=font) 96 | # Rechts 97 | self.canvas.create_text(self.board_size - offset, y, text=rank, tags="coord", fill="black", anchor=tk.E, font=font) 98 | 99 | def draw_pieces(self): 100 | """Zeichnet alle Figuren auf das Brett.""" 101 | self.canvas.delete("piece") # 🚨 KORREKTUR: Entferne NUR Figuren, nicht das ganze Board 102 | 103 | # Debug-Ausgabe zur Überprüfung 104 | piece_count = 0 105 | 106 | # Iteriere durch alle Figuren der Engine 107 | for piece in self.engine.pieces: 108 | # Überspringe geschlagene Figuren und Figuren außerhalb des Spielfeldes 109 | if piece.get('captured', False) or not self.engine.is_valid_position(piece['position']): 110 | continue 111 | 112 | # Die interne 10x10 Position 113 | pos = piece['position'] 114 | 115 | # 10x10 zu 8x8 Koordinaten 116 | row = pos // 10 117 | gui_row = 9 - row 118 | col = pos % 10 119 | gui_col = col - 1 120 | 121 | # Position auf dem Canvas 122 | x = gui_col * self.cell_size + self.cell_size // 2 123 | y = gui_row * self.cell_size + self.cell_size // 2 124 | 125 | # Figur zeichnen 126 | symbol = piece.get('symbol', '?') 127 | fill_color = "black" if piece['color'] == BLACK else "black" # 🚨 KORREKTUR: Immer schwarz für bessere Sichtbarkeit 128 | 129 | self.canvas.create_text( 130 | x, y, 131 | text=symbol, 132 | font=("Arial", int(self.cell_size * 0.6), "bold"), # 🚨 KORREKTUR: Kleinere Schrift mit Bold 133 | fill=fill_color, 134 | tags=("piece", f"piece_{piece['id']}", f"cell_{pos}") 135 | ) 136 | piece_count += 1 137 | 138 | # 🚨 DEBUG: Überprüfe ob Figuren gezeichnet werden 139 | if piece_count == 0: 140 | print("⚠️ WARNUNG: Keine Figuren auf dem Brett gezeichnet!") 141 | else: 142 | print(f"✅ {piece_count} Figuren auf dem Brett gezeichnet") 143 | 144 | # ========================================================================= 145 | # EREIGNISBEHANDLUNG 146 | # ========================================================================= 147 | 148 | def on_square_click(self, event): 149 | """Behandelt Mausklicks auf dem Schachbrett.""" 150 | # 🚨 KORREKTUR: Prüfe ob KI denkt - blockiere Benutzerinteraktion 151 | if self.gui.ai_thinking: 152 | messagebox.showinfo("KI denkt", "Die KI berechnet einen Zug. Bitte warten...") 153 | return 154 | 155 | # Finde die 8x8 Koordinate 156 | col = event.x // self.cell_size 157 | row = event.y // self.cell_size 158 | 159 | # Konvertiere zu 10x10 Engine-Position 160 | engine_row = 9 - row 161 | engine_col = col + 1 162 | clicked_pos = engine_row * 10 + engine_col 163 | 164 | if not self.engine.is_valid_position(clicked_pos): 165 | return 166 | 167 | print(f"🎯 Klick auf Position: {self._position_to_notation(clicked_pos)}") # 🚨 DEBUG 168 | 169 | # Wenn der Editor-Modus aktiv ist 170 | if self.editor_mode: 171 | self._handle_editor_click(clicked_pos) 172 | else: 173 | self._handle_game_click(clicked_pos) 174 | 175 | 176 | def _handle_editor_click(self, clicked_pos): 177 | """Behandelt Klicks im Editor-Modus.""" 178 | # Wähle die zu platzierende Figur 179 | piece_type = self.gui.selected_editor_piece['type'] 180 | color = self.gui.selected_editor_piece['color'] 181 | 182 | # Leere Feld (EMPTY) oder Klick auf dieselbe Figur entfernt 183 | piece_at_pos = self.engine.get_piece_at(clicked_pos) 184 | is_same_piece = piece_at_pos and piece_at_pos['type'] == piece_type and piece_at_pos['color'] == color 185 | 186 | if piece_type == EMPTY or is_same_piece: 187 | # Figur entfernen 188 | self.engine.editor_remove_piece(clicked_pos) 189 | else: 190 | # Figur platzieren 191 | self.engine.editor_place_piece(piece_type, color, clicked_pos) 192 | 193 | self.update_display() 194 | self.gui.update_status() 195 | 196 | def _handle_game_click(self, clicked_pos): 197 | """Behandelt Klicks im Spielmodus (Zugausführung).""" 198 | # 🚨 KORREKTUR: Prüfe ob KI denkt 199 | if self.gui.ai_thinking: 200 | messagebox.showinfo("KI denkt", "Die KI berechnet einen Zug. Bitte warten...") 201 | return 202 | 203 | piece_at_pos = self.engine.get_piece_at(clicked_pos) 204 | current_turn_color = WHITE if self.engine.white_turn else BLACK 205 | 206 | # Prüfe ob Spieler am Zug ist (nicht KI) 207 | if self.gui.game_mode == 'human_vs_ai' and current_turn_color == BLACK: 208 | messagebox.showinfo("KI am Zug", "Die KI ist am Zug. Bitte warten...") 209 | return 210 | 211 | print(f"🎮 Spielzug: Position {self._position_to_notation(clicked_pos)}, Figur: {piece_at_pos['type'] if piece_at_pos else 'leer'}") # 🚨 DEBUG 212 | 213 | # 1. ERSTER KLICK: Auswahl der eigenen Figur 214 | if not self.from_pos: 215 | if piece_at_pos and piece_at_pos['color'] == current_turn_color: 216 | self.from_pos = clicked_pos 217 | self.selected_cell = f"cell_{clicked_pos}" 218 | self.highlight_selection(self.selected_cell) 219 | self.possible_moves = self._get_possible_moves_for_pos(self.from_pos) 220 | self.highlight_possible_moves() 221 | print(f"✅ Figur ausgewählt: {self._position_to_notation(clicked_pos)}") 222 | else: 223 | print(f"❌ Keine eigene Figur auf {self._position_to_notation(clicked_pos)}") 224 | 225 | # 2. ZWEITER KLICK: Zugausführung oder neue Auswahl 226 | elif self.from_pos: 227 | # Wenn auf die gleiche Figur geklickt wird (Abwahl) 228 | if clicked_pos == self.from_pos: 229 | self.clear_selection() 230 | print("✅ Auswahl aufgehoben") 231 | # Wenn auf eine andere eigene Figur geklickt wird (Neue Auswahl) 232 | elif piece_at_pos and piece_at_pos['color'] == current_turn_color: 233 | self.clear_selection() 234 | self.from_pos = clicked_pos 235 | self.selected_cell = f"cell_{clicked_pos}" 236 | self.highlight_selection(self.selected_cell) 237 | self.possible_moves = self._get_possible_moves_for_pos(self.from_pos) 238 | self.highlight_possible_moves() 239 | print(f"✅ Neue Figur ausgewählt: {self._position_to_notation(clicked_pos)}") 240 | # Wenn auf ein Zielfeld geklickt wird (Zugversuch) 241 | else: 242 | self.to_pos = clicked_pos 243 | move = self._find_move_in_list(self.from_pos, self.to_pos) 244 | 245 | if move: 246 | print(f"✅ Zug gefunden: {self._position_to_notation(self.from_pos)} -> {self._position_to_notation(self.to_pos)}") 247 | if self._handle_special_move_prompt(move): 248 | self._execute_move(move) 249 | else: 250 | print("❌ Spezieller Zug abgebrochen") 251 | else: 252 | print(f"❌ Kein legaler Zug von {self._position_to_notation(self.from_pos)} nach {self._position_to_notation(self.to_pos)}") 253 | messagebox.showwarning("Ungültiger Zug", "Dieser Zug ist nicht erlaubt.") 254 | 255 | self.clear_selection() 256 | 257 | def _handle_special_move_prompt(self, move: dict) -> bool: 258 | """Behandelt spezielle Züge wie Bauernumwandlung.""" 259 | if move.get('special_type') == 'promotion': 260 | choice = tk.simpledialog.askstring("Bauernumwandlung", "Wähle eine Figur (Dame, Turm, Läufer, Springer):", parent=self) 261 | 262 | if choice: 263 | # Annahme: engine.rules._get_promotion_piece_type existiert und ist korrekt 264 | promotion_type = self.engine.rules._get_promotion_piece_type(choice) 265 | if promotion_type: 266 | move['promotion_type'] = promotion_type 267 | return True 268 | else: 269 | messagebox.showerror("Fehler", "Ungültige Figur gewählt. Standard: Dame.") 270 | move['promotion_type'] = QUEEN # Standard: Dame 271 | return True 272 | else: 273 | return False # Abbruch der Umwandlung 274 | 275 | return True 276 | 277 | def _execute_move(self, move): 278 | """Führt den Zug aus und aktualisiert die Anzeige.""" 279 | print(f"🔧 Führe Zug aus: {self._position_to_notation(move['from_pos'])} -> {self._position_to_notation(move['to_pos'])}") 280 | 281 | if self.engine.make_move(move): 282 | print("✅ Zug erfolgreich ausgeführt") 283 | self.update_display() 284 | self.gui.update_status() 285 | 286 | # Prüfe, ob die KI an der Reihe ist (nur im Mensch vs KI Modus) 287 | self.gui.check_ai_turn() 288 | else: 289 | print("❌ Zug fehlgeschlagen") 290 | messagebox.showwarning("Illegaler Zug", "Dieser Zug ist nicht erlaubt.") 291 | 292 | # ========================================================================= 293 | # ZUG-HILFSFUNKTIONEN 294 | # ========================================================================= 295 | 296 | def _get_possible_moves_for_pos(self, from_pos): 297 | """Filtert legale Züge nach Startposition - KORRIGIERTE VERSION""" 298 | color = WHITE if self.engine.white_turn else BLACK 299 | all_legal_moves = self.engine.generate_all_moves(color) 300 | moves_for_pos = [move for move in all_legal_moves if move['from_pos'] == from_pos] 301 | 302 | # 🚨 DEBUG: Zeige alle gefundenen Züge 303 | print(f"🔍 {len(moves_for_pos)} mögliche Züge für Position {self._position_to_notation(from_pos)}") 304 | for move in moves_for_pos: 305 | move_type = "Normal" 306 | if move.get('special_type') == 'castling': 307 | move_type = "Rochade" 308 | elif move.get('special_type') == 'en_passant': 309 | move_type = "En Passant" 310 | elif move.get('promotion_piece'): 311 | move_type = f"Umwandlung zu {move['promotion_piece']}" 312 | print(f" - {self._position_to_notation(move['from_pos'])} -> {self._position_to_notation(move['to_pos'])} [{move_type}]") 313 | 314 | return moves_for_pos 315 | 316 | def _find_move_in_list(self, from_pos, to_pos): 317 | """Findet den entsprechenden Zug in der Liste der möglichen Züge.""" 318 | for move in self.possible_moves: 319 | if move['to_pos'] == to_pos: 320 | return move 321 | return None 322 | 323 | # ========================================================================= 324 | # MARKIERUNG (HIGHLIGHTING) 325 | # ========================================================================= 326 | 327 | def clear_highlights(self): 328 | """Entfernt alle Markierungen vom Brett.""" 329 | self.canvas.delete("highlight") 330 | self.canvas.delete("possible_move") 331 | # 🚨 KORREKTUR: Zeichne das Brett NICHT neu, da dies die Figuren löscht 332 | # Stattdessen setze nur die Umrandungen zurück 333 | highlighted_items = self.canvas.find_withtag("square") 334 | for item in highlighted_items: 335 | if self.canvas.type(item) == "rectangle": 336 | # Setze Umrandung auf Standard (keine Umrandung) 337 | self.canvas.itemconfig(item, outline="", width=1) 338 | 339 | def highlight_selection(self, tag): 340 | """Markiert die ausgewählte Zelle (Rechteck).""" 341 | self.clear_highlights() # 🚨 KORREKTUR: Entferne zuerst alle Highlights 342 | 343 | self.canvas.addtag_withtag("highlight", tag) 344 | self.canvas.tag_raise("highlight") # Highlights über Figuren zeichnen 345 | 346 | # KORREKTUR: Finde alle Rechtecke mit dem Highlight-Tag und konfiguriere sie 347 | highlighted_items = self.canvas.find_withtag("highlight") 348 | for item in highlighted_items: 349 | # Prüfe ob es sich um ein Rechteck handelt 350 | if self.canvas.type(item) == "rectangle": 351 | self.canvas.itemconfig(item, outline=self.highlight_color, width=3) 352 | 353 | def highlight_possible_moves(self): 354 | """Markiert mögliche Zielfelder.""" 355 | for move in self.possible_moves: 356 | to_pos = move['to_pos'] 357 | tag = f"cell_{to_pos}" 358 | 359 | piece_at_target = self.engine.get_piece_at(to_pos) 360 | 361 | if piece_at_target: 362 | # Markierung für Schlag (Kreis) 363 | color = self.possible_capture_color 364 | self._draw_circle_highlight(to_pos, color) 365 | else: 366 | # Markierung für normalen Zug (Punkt) 367 | color = self.possible_move_color 368 | self._draw_circle_highlight(to_pos, color) 369 | 370 | # Markierung für das Feld selbst (Rückseite) 371 | self.canvas.addtag_withtag("possible_move", tag) 372 | self.canvas.tag_lower("possible_move") # Unter die Figuren/Kreise 373 | 374 | def _draw_circle_highlight(self, pos_10x10, color): 375 | """Zeichnet einen Kreis auf einem Zielfeld.""" 376 | # Konvertiere 10x10-Position zu Canvas-Koordinaten 377 | row = pos_10x10 // 10 378 | col = pos_10x10 % 10 379 | gui_row = 9 - row 380 | gui_col = col - 1 381 | 382 | x = gui_col * self.cell_size + self.cell_size // 2 383 | y = gui_row * self.cell_size + self.cell_size // 2 384 | radius = self.cell_size * 0.15 # 🚨 KORREKTUR: Kleinere Kreise 385 | 386 | # Zeichne Kreis 387 | self.canvas.create_oval( 388 | x - radius, y - radius, 389 | x + radius, y + radius, 390 | fill=color, 391 | outline="black", 392 | width=1, 393 | tags=("possible_move") 394 | ) 395 | 396 | # ========================================================================= 397 | # HILFSFUNKTIONEN 398 | # ========================================================================= 399 | 400 | def _position_to_notation(self, position: int) -> str: 401 | """Konvertiert interne Position zu algebraischer Notation""" 402 | files = ['', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', ''] 403 | row = position // 10 404 | file = position % 10 405 | if 1 <= file <= 8 and 2 <= row <= 9: 406 | return f"{files[file]}{row - 1}" 407 | return "??" 408 | 409 | # ========================================================================= 410 | # ZUSAMMENFASSUNG / EDITOR-FUNKTIONEN 411 | # ========================================================================= 412 | 413 | def enable_editor_mode(self): 414 | """Aktiviert den Editor-Modus""" 415 | self.editor_mode = True 416 | self.canvas.config(cursor="hand2") 417 | self.clear_selection() 418 | 419 | def disable_editor_mode(self): 420 | """Deaktiviert den Editor-Modus""" 421 | self.editor_mode = False 422 | self.canvas.config(cursor="") 423 | self.clear_selection() 424 | 425 | def flip_board(self): 426 | """Dreht das Brett um (visuell)""" 427 | self.update_display() 428 | 429 | def clear_selection(self): 430 | """Setzt die Auswahl zurück""" 431 | self.from_pos = None 432 | self.to_pos = None 433 | self.selected_cell = None 434 | self.possible_moves = [] 435 | self.clear_highlights() 436 | 437 | def update_display(self): 438 | """Aktualisiert die Anzeige - 🚨 KORREKTUR: Vereinfachte Methode""" 439 | print("🔄 Aktualisiere Brett-Anzeige...") 440 | 441 | # 🚨 KORREKTUR: Zeichne zuerst das Brett, dann die Figuren 442 | self.draw_board() 443 | self.draw_pieces() 444 | 445 | # 🚨 KORREKTUR: Erzwinge Canvas-Update 446 | self.canvas.update_idletasks() 447 | 448 | print("✅ Brett-Anzeige aktualisiert") 449 | -------------------------------------------------------------------------------- /chessteg_modular/engine/core.py: -------------------------------------------------------------------------------- 1 | """ 2 | Chessteg Core Engine Module - KORRIGIERTE VERSION (Behebt IndexError) 3 | """ 4 | 5 | import copy 6 | import sys 7 | import os 8 | from typing import List, Dict, Any, Optional 9 | 10 | # Füge den aktuellen Pfad zum Python-Pfad hinzu für relative Imports 11 | sys.path.append(os.path.dirname(__file__)) 12 | 13 | # Konstanten für bessere Lesbarkeit 14 | WHITE = 1 15 | BLACK = -1 16 | EMPTY = 0 17 | DUMMY = 100 18 | 19 | # Figuren-Typen 20 | PAWN = 1 21 | KNIGHT = 4 22 | BISHOP = 3 23 | ROOK = 5 24 | QUEEN = 9 25 | KING = 99 26 | 27 | # Figuren-Symbole für die Darstellung (Hier in der Engine, um sie unabhängig zu machen) 28 | PIECE_SYMBOLS = { 29 | 1: '♙', # Weißer Bauer 30 | -1: '♟', # Schwarzer Bauer 31 | 4: '♘', # Weißer Springer 32 | -4: '♞', # Schwarzer Springer 33 | 3: '♗', # Weißer Läufer 34 | -3: '♝', # Schwarzer Läufer 35 | 5: '♖', # Weißer Turm 36 | -5: '♜', # Schwarzer Turm 37 | 9: '♕', # Weiße Dame 38 | -9: '♛', # Schwarze Dame 39 | 99: '♔', # Weißer König 40 | -99: '♚', # Schwarzer König 41 | EMPTY: ' ' 42 | } 43 | 44 | # Import der Komponenten (relative Imports in einer echten Modulstruktur) 45 | try: 46 | from move_generation import MoveGenerator 47 | from evaluation import PositionEvaluator 48 | from search import SearchAlgorithm 49 | from rules import ChessRules 50 | except ImportError: 51 | print("WARNING: Relative imports failed. Using dummy components.") 52 | class DummyComponent: 53 | def __init__(self, *args): pass 54 | def generate_moves(self, *args): return [] 55 | def evaluate_position(self): return 0 56 | def computer_move(self): return None 57 | 58 | MoveGenerator = DummyComponent 59 | PositionEvaluator = DummyComponent 60 | SearchAlgorithm = DummyComponent 61 | ChessRules = DummyComponent 62 | 63 | 64 | class ChesstegEngine: 65 | """ 66 | Die zentrale Schach-Engine. Verwaltet den Spielzustand, Figuren und die 67 | Schnittstellen zu den modularen Komponenten (Züge, Bewertung, Suche, Regeln). 68 | """ 69 | 70 | def __init__(self): 71 | self.board = [EMPTY] * 120 # 10x12 Array, die Ränder sind DUMMY 72 | self.pieces: List[Dict[str, Any]] = [] 73 | self.white_turn = True 74 | self.checkmate = False 75 | self.stalemate = False 76 | self.next_piece_id = 1 # Eindeutige ID für jede Figur 77 | self.move_history: List[Dict[str, Any]] = [] # Speichert vergangene Zustände 78 | 79 | # Komponenten initialisieren 80 | self.move_generator = MoveGenerator(self) 81 | self.evaluator = PositionEvaluator(self) 82 | self.search_algorithm = SearchAlgorithm(self) 83 | self.rules = ChessRules(self) # Regeln müssen vor initialize_pieces initialisiert werden 84 | 85 | self.initialize_board() 86 | self.initialize_pieces() 87 | self.synchronize_board_state() 88 | 89 | # ========================================================================= 90 | # ZUSTANDSVERWALTUNG 91 | # ========================================================================= 92 | 93 | def initialize_board(self): 94 | """Setzt das 120-Felder-Board (10x12) mit DUMMY-Rändern auf.""" 95 | # Alle Felder auf EMPTY setzen 96 | self.board = [EMPTY] * 120 # Indices 0 bis 119 97 | 98 | # KORREKTUR: Ränder setzen 99 | # 1. Unterer und oberer vollständiger Rand (Rows 0 und 11, jeweils 10 Felder) 100 | for i in range(10): # i von 0 bis 9 (10 Iterationen) 101 | # Row 0 (Indices 0 bis 9) 102 | self.board[i] = DUMMY 103 | # Row 11 (Indices 110 bis 119) 104 | self.board[110 + i] = DUMMY 105 | 106 | # 2. Linke und rechte Ränder (Spalten 0 und 9) für Rows 1 bis 10 107 | for i in range(1, 11): # i von 1 bis 10 (10 Iterationen) 108 | self.board[i * 10] = DUMMY # Col 0 (z.B. 10, 20, ..., 100) 109 | self.board[i * 10 + 9] = DUMMY # Col 9 (z.B. 19, 29, ..., 109) 110 | 111 | def initialize_pieces(self): 112 | """Setzt die Figuren auf die Standard-Anfangsstellung.""" 113 | self.pieces.clear() 114 | self.next_piece_id = 1 115 | 116 | # Initialisierung der Regeln (für Castling Rights etc.) 117 | self.rules.__init__(self) 118 | 119 | # Schwarze Figuren (Reihe 9 und 8) - A8=91 bis H8=98; A7=81 bis H7=88 120 | self._add_piece(ROOK, BLACK, 91) 121 | self._add_piece(KNIGHT, BLACK, 92) 122 | self._add_piece(BISHOP, BLACK, 93) 123 | self._add_piece(QUEEN, BLACK, 94) 124 | self._add_piece(KING, BLACK, 95) 125 | self._add_piece(BISHOP, BLACK, 96) 126 | self._add_piece(KNIGHT, BLACK, 97) 127 | self._add_piece(ROOK, BLACK, 98) 128 | for i in range(81, 89): 129 | self._add_piece(PAWN, BLACK, i) 130 | 131 | # Weiße Figuren (Reihe 2 und 3) - A1=21 bis H1=28; A2=31 bis H2=38 132 | for i in range(31, 39): 133 | self._add_piece(PAWN, WHITE, i) 134 | self._add_piece(ROOK, WHITE, 21) 135 | self._add_piece(KNIGHT, WHITE, 22) 136 | self._add_piece(BISHOP, WHITE, 23) 137 | self._add_piece(QUEEN, WHITE, 24) 138 | self._add_piece(KING, WHITE, 25) 139 | self._add_piece(BISHOP, WHITE, 26) 140 | self._add_piece(KNIGHT, WHITE, 27) 141 | self._add_piece(ROOK, WHITE, 28) 142 | 143 | self.white_turn = True 144 | self.checkmate = False 145 | self.stalemate = False 146 | self.move_history.clear() 147 | 148 | self.synchronize_board_state() 149 | 150 | def _add_piece(self, piece_type: int, color: int, position: int): 151 | """Fügt dem Figuren-Array eine neue Figur hinzu.""" 152 | piece_value = piece_type * color 153 | symbol_key = piece_value 154 | 155 | new_piece = { 156 | 'id': self.next_piece_id, 157 | 'type': piece_type, 158 | 'color': color, 159 | 'value': piece_value, 160 | 'position': position, 161 | 'captured': False, 162 | 'has_moved': False, 163 | 'symbol': PIECE_SYMBOLS.get(symbol_key, '?') 164 | } 165 | self.pieces.append(new_piece) 166 | self.next_piece_id += 1 167 | 168 | def synchronize_board_state(self, silent=False): 169 | """ 170 | Stellt sicher, dass das 120-Felder-Board den aktuellen Positionen 171 | im `pieces` Array entspricht. 172 | """ 173 | # 1. Board resetten (ohne DUMMY-Felder zu überschreiben) 174 | self.initialize_board() 175 | 176 | # 2. Figuren positionieren 177 | for piece in self.pieces: 178 | if not piece['captured']: 179 | position = piece['position'] 180 | piece_value = piece['value'] 181 | 182 | if self.is_valid_position(position): 183 | self.board[position] = piece_value 184 | else: 185 | if not silent: 186 | print(f"WARNUNG: Figur ID {piece['id']} an ungültiger Position {position}") 187 | piece['captured'] = True 188 | 189 | def get_piece_at(self, position: int) -> Optional[Dict[str, Any]]: 190 | """Gibt das Figuren-Objekt an einer 10x10 Position zurück.""" 191 | for piece in self.pieces: 192 | if not piece['captured'] and piece['position'] == position: 193 | return piece 194 | return None 195 | 196 | def get_piece_by_id(self, piece_id: int) -> Optional[Dict[str, Any]]: 197 | """Gibt das Figuren-Objekt anhand der ID zurück.""" 198 | for piece in self.pieces: 199 | if piece['id'] == piece_id: 200 | return piece 201 | return None 202 | 203 | def get_king(self, color: int) -> Optional[Dict[str, Any]]: 204 | """Gibt das König-Objekt der angegebenen Farbe zurück.""" 205 | for piece in self.pieces: 206 | if piece['type'] == KING and piece['color'] == color and not piece['captured']: 207 | return piece 208 | return None 209 | 210 | def is_valid_position(self, position: int) -> bool: 211 | """Prüft, ob eine Position innerhalb des 8x8 Spielfeldes liegt (21-98).""" 212 | if 20 < position < 100: 213 | col = position % 10 214 | return 1 <= col <= 8 215 | return False 216 | 217 | # ========================================================================= 218 | # ZUG-AUSFÜHRUNG 219 | # ========================================================================= 220 | 221 | def make_move(self, move: Dict[str, Any]) -> bool: 222 | """Führt einen Zug aus und aktualisiert den Spielzustand - KORRIGIERTE VERSION""" 223 | 224 | # 1. Speichere den aktuellen Zustand vor der Ausführung 225 | self._save_state() 226 | 227 | # 2. Führe den Zug durch (Figuren-Update) 228 | piece = self.get_piece_at(move['from_pos']) 229 | if not piece: 230 | print(f"❌ Keine Figur auf Startposition {self._position_to_notation(move['from_pos'])}") 231 | self._restore_state() 232 | return False 233 | 234 | # 🚨 NEU: Spezielle Zug-Behandlung VOR der normalen Ausführung 235 | # Rochade 236 | if move.get('special_type') == 'castling': 237 | return self._execute_castling_move(move, piece) 238 | 239 | # En Passant 240 | if move.get('special_type') == 'en_passant': 241 | return self._execute_en_passant_move(move, piece) 242 | 243 | # Bauernumwandlung 244 | if move.get('promotion_piece'): 245 | return self._execute_promotion_move(move, piece) 246 | 247 | # Normale Zugausführung 248 | captured_piece = self.get_piece_at(move['to_pos']) 249 | if captured_piece: 250 | captured_piece['captured'] = True 251 | move['captured_piece_id'] = captured_piece['id'] 252 | else: 253 | move['captured_piece_id'] = None 254 | 255 | # Figur an Zielposition bewegen 256 | piece['position'] = move['to_pos'] 257 | piece['has_moved'] = True 258 | 259 | # 3. Spezielle Regeln ausführen 260 | if hasattr(self.rules, 'process_move'): 261 | self.rules.process_move(move, piece, captured_piece) 262 | 263 | # 4. Board und Zustand synchronisieren 264 | self.synchronize_board_state() 265 | self.white_turn = not self.white_turn 266 | 267 | # 5. Prüfe auf Schachmatt/Patt 268 | self._check_game_end() 269 | 270 | return True 271 | 272 | def _execute_castling_move(self, move: Dict[str, Any], king: Dict[str, Any]) -> bool: 273 | """Führt Rochade aus""" 274 | rook = self.get_piece_by_id(move['rook_id']) 275 | if not rook: 276 | return False 277 | 278 | # Bewege König 279 | king['position'] = move['to_pos'] 280 | king['has_moved'] = True 281 | 282 | # Bewege Turm 283 | rook['position'] = move['rook_to'] 284 | rook['has_moved'] = True 285 | 286 | # Rochaderechte aktualisieren 287 | self.rules._revoke_castling_rights(king['color']) 288 | 289 | self.synchronize_board_state() 290 | self.white_turn = not self.white_turn 291 | self._check_game_end() 292 | return True 293 | 294 | def _execute_en_passant_move(self, move: Dict[str, Any], pawn: Dict[str, Any]) -> bool: 295 | """Führt en Passant aus""" 296 | # Bewege Bauer 297 | pawn['position'] = move['to_pos'] 298 | 299 | # Schlage gegnerischen Bauer 300 | captured_pawn_pos = move['capture_pos'] 301 | captured_pawn = self.get_piece_at(captured_pawn_pos) 302 | if captured_pawn: 303 | captured_pawn['captured'] = True 304 | move['captured_piece_id'] = captured_pawn['id'] 305 | 306 | self.synchronize_board_state() 307 | self.white_turn = not self.white_turn 308 | self._check_game_end() 309 | return True 310 | 311 | def _execute_promotion_move(self, move: Dict[str, Any], pawn: Dict[str, Any]) -> bool: 312 | """Führt Bauernumwandlung aus""" 313 | # Normale Bewegung 314 | captured_piece = self.get_piece_at(move['to_pos']) 315 | if captured_piece: 316 | captured_piece['captured'] = True 317 | move['captured_piece_id'] = captured_piece['id'] 318 | 319 | # Bewege Bauer zur Umwandlungsposition 320 | pawn['position'] = move['to_pos'] 321 | 322 | # Umwandlung durchführen 323 | promotion_piece_type = move['promotion_piece'] 324 | pawn['type'] = promotion_piece_type 325 | pawn['value'] = promotion_piece_type * pawn['color'] 326 | pawn['symbol'] = PIECE_SYMBOLS.get(pawn['value'], '?') 327 | 328 | self.synchronize_board_state() 329 | self.white_turn = not self.white_turn 330 | self._check_game_end() 331 | return True 332 | 333 | def undo_move(self) -> bool: 334 | """Macht den letzten Zug rückgängig.""" 335 | if not self.move_history: 336 | return False 337 | 338 | # Lade den vorherigen Zustand 339 | previous_state = self.move_history.pop() 340 | 341 | self.pieces = previous_state['pieces'] 342 | self.white_turn = previous_state['white_turn'] 343 | self.checkmate = previous_state['checkmate'] 344 | self.stalemate = previous_state['stalemate'] 345 | self.next_piece_id = previous_state['next_piece_id'] 346 | 347 | # Lade den Zustand der Regeln 348 | self.rules.en_passant_target = previous_state['rules']['en_passant_target'] 349 | self.rules.castling_rights = previous_state['rules']['castling_rights'] 350 | 351 | self.synchronize_board_state() 352 | return True 353 | 354 | def _save_state(self): 355 | """Speichert den aktuellen Spielzustand in der Historie.""" 356 | current_state = { 357 | 'pieces': copy.deepcopy(self.pieces), 358 | 'white_turn': self.white_turn, 359 | 'checkmate': self.checkmate, 360 | 'stalemate': self.stalemate, 361 | 'next_piece_id': self.next_piece_id, 362 | 'rules': { 363 | 'en_passant_target': self.rules.en_passant_target, 364 | 'castling_rights': copy.deepcopy(self.rules.castling_rights) 365 | } 366 | } 367 | self.move_history.append(current_state) 368 | 369 | def _restore_state(self): 370 | """Stellt den Zustand aus dem letzten Eintrag in der Historie wieder her.""" 371 | if self.move_history: 372 | self.pieces = self.move_history[-1]['pieces'] 373 | self.white_turn = self.move_history[-1]['white_turn'] 374 | self.checkmate = self.move_history[-1]['checkmate'] 375 | self.stalemate = self.move_history[-1]['stalemate'] 376 | self.next_piece_id = self.move_history[-1]['next_piece_id'] 377 | self.rules.en_passant_target = self.move_history[-1]['rules']['en_passant_target'] 378 | self.rules.castling_rights = self.move_history[-1]['rules']['castling_rights'] 379 | self.synchronize_board_state() 380 | 381 | # ========================================================================= 382 | # SCHACH/MATT/PATT-PRÜFUNG 383 | # ========================================================================= 384 | 385 | def generate_all_moves(self, color: int) -> List[Dict[str, Any]]: 386 | """Generiert alle legalen Züge für die angegebene Farbe.""" 387 | # Aufruf der korrigierten Methode im MoveGenerator 388 | return self.move_generator.generate_moves(color) 389 | 390 | def is_king_in_check(self, color: int) -> bool: 391 | """Prüft, ob der König der gegebenen Farbe im Schach steht.""" 392 | king = self.get_king(color) 393 | if not king: 394 | return False 395 | 396 | opponent_color = BLACK if color == WHITE else WHITE 397 | 398 | return self.move_generator.is_square_attacked(king['position'], opponent_color) 399 | 400 | def _check_game_end(self): 401 | """Prüft, ob die aktuelle Stellung Schachmatt oder Patt ist.""" 402 | color = WHITE if self.white_turn else BLACK 403 | 404 | legal_moves = self.generate_all_moves(color) 405 | 406 | if not legal_moves: 407 | if self.is_king_in_check(color): 408 | self.checkmate = True 409 | self.stalemate = False 410 | else: 411 | self.checkmate = False 412 | self.stalemate = True 413 | else: 414 | self.checkmate = False 415 | self.stalemate = False 416 | 417 | def is_game_over(self) -> bool: 418 | """Prüft, ob das Spiel beendet ist.""" 419 | return self.checkmate or self.stalemate 420 | 421 | def take_snapshot(self): 422 | """Erstellt eine Momentaufnahme des aktuellen Zustands.""" 423 | return { 424 | 'pieces': copy.deepcopy(self.pieces), 425 | 'white_turn': self.white_turn, 426 | 'checkmate': self.checkmate, 427 | 'stalemate': self.stalemate, 428 | 'board': self.board.copy() 429 | } 430 | 431 | def restore_snapshot(self, snapshot): 432 | """Stellt einen Zustand aus einer Momentaufnahme wieder her.""" 433 | self.pieces = snapshot['pieces'] 434 | self.white_turn = snapshot['white_turn'] 435 | self.checkmate = snapshot['checkmate'] 436 | self.stalemate = snapshot['stalemate'] 437 | self.board = snapshot['board'] 438 | 439 | def _apply_move_internal(self, move): 440 | """Wendet einen Zug an, ohne den Spielzustand vollständig zu ändern (für Suchalgorithmus).""" 441 | piece = self.get_piece_by_id(move['piece_id']) 442 | if not piece: 443 | return 444 | 445 | # Alte Position leeren 446 | self.board[piece['position']] = EMPTY 447 | 448 | # Geschlagene Figur entfernen 449 | if move.get('capture_pos'): 450 | captured_piece = self.get_piece_at(move['capture_pos']) 451 | if captured_piece: 452 | captured_piece['captured'] = True 453 | self.board[move['capture_pos']] = EMPTY 454 | 455 | # Figur bewegen 456 | piece['position'] = move['to_pos'] 457 | self.board[move['to_pos']] = piece['value'] 458 | 459 | # Promotion behandeln 460 | if move.get('promotion_piece'): 461 | piece['type'] = move['promotion_piece'] 462 | piece['value'] = move['promotion_piece'] * piece['color'] 463 | piece['symbol'] = PIECE_SYMBOLS.get(piece['value'], '?') 464 | 465 | # ========================================================================= 466 | # HILFSFUNKTIONEN (NOTATION) 467 | # ========================================================================= 468 | 469 | def _position_to_notation(self, position: int) -> str: 470 | """Konvertiert interne Position zu algebraischer Notation""" 471 | files = ['', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', ''] 472 | row = position // 10 473 | file = position % 10 474 | if 1 <= file <= 8 and 2 <= row <= 9: 475 | return f"{files[file]}{row - 1}" 476 | return "??" 477 | 478 | # ========================================================================= 479 | # EDITOR-FUNKTIONEN 480 | # ========================================================================= 481 | 482 | def editor_place_piece(self, piece_type: int, color: int, position: int) -> bool: 483 | """Platziert eine Figur auf einem Feld (Editor-Funktion)""" 484 | if not self.is_valid_position(position): 485 | return False 486 | 487 | # Entferne existierende Figur 488 | existing_piece = self.get_piece_at(position) 489 | if existing_piece: 490 | existing_piece['captured'] = True 491 | 492 | # Füge neue Figur hinzu (verwendet _add_piece mit neuer ID) 493 | self._add_piece(piece_type, color, position) 494 | 495 | # Synchronisiere das Board, um die neue Figur anzuzeigen 496 | self.synchronize_board_state(silent=True) 497 | return True 498 | 499 | def editor_remove_piece(self, position): 500 | """Entfernt eine Figur vom Brett (Editor-Funktion)""" 501 | piece = self.get_piece_at(position) 502 | if piece: 503 | piece['captured'] = True 504 | self.synchronize_board_state(silent=True) 505 | return True 506 | 507 | return False 508 | 509 | def editor_clear_board(self): 510 | """Entfernt alle Figuren vom Brett""" 511 | for piece in self.pieces: 512 | piece['captured'] = True 513 | self.synchronize_board_state(silent=True) 514 | 515 | def editor_standard_position(self): 516 | """Setzt die Standard-Anfangsstellung""" 517 | self.initialize_pieces() -------------------------------------------------------------------------------- /chessteg_modular/engine/move_generation.py: -------------------------------------------------------------------------------- 1 | """ 2 | Chessteg Move Generation Module - KORRIGIERTE VERSION MIT DEBUG 3 | Vollständige Zuggenerierung mit allen Schachregeln 4 | """ 5 | 6 | import copy 7 | from typing import List, Dict, Any, Optional 8 | 9 | # Konstanten für Figurentypen (aus Core Engine) 10 | PAWN = 1 11 | KNIGHT = 4 12 | BISHOP = 3 13 | ROOK = 5 14 | QUEEN = 9 15 | KING = 99 16 | WHITE = 1 17 | BLACK = -1 18 | 19 | # Richtungen für 10x10 Board 20 | DIRECTIONS = { 21 | 'N': 10, 'S': -10, 'E': 1, 'W': -1, 22 | 'NE': 11, 'NW': 9, 'SE': -9, 'SW': -11 23 | } 24 | 25 | # Springer-Züge (L-Form) 26 | KNIGHT_MOVES = [21, 19, 12, 8, -8, -12, -19, -21] 27 | 28 | 29 | class MoveGenerator: 30 | """ 31 | Vollständige Zuggenerierung für alle Figurentypen inklusive spezieller Regeln 32 | """ 33 | 34 | def __init__(self, engine): 35 | self.engine = engine 36 | 37 | def generate_moves(self, color: int) -> List[Dict[str, Any]]: 38 | """ 39 | Generiert alle legalen Züge für eine Farbe inklusive spezieller Züge 40 | 41 | Args: 42 | color: Farbe (1=weiß, -1=schwarz) 43 | 44 | Returns: 45 | List[Dict]: Liste der legalen Züge 46 | """ 47 | all_moves = [] 48 | 49 | for piece in self.engine.pieces: 50 | if not piece['captured'] and piece['color'] == color: 51 | piece_moves = self.generate_piece_moves(piece) 52 | all_moves.extend(piece_moves) 53 | 54 | # Spezielle Züge hinzufügen 55 | special_moves = self._generate_special_moves(color) 56 | all_moves.extend(special_moves) 57 | 58 | # Nur legale Züge zurückgeben (ohne Selbstschach) 59 | legal_moves = [move for move in all_moves if self.is_move_legal(move)] 60 | 61 | # DEBUG: Zeige Anzahl der generierten Züge 62 | # print(f"Generierte legale Züge für {color}: {len(legal_moves)}") 63 | 64 | return legal_moves 65 | 66 | def generate_legal_moves(self, color: int) -> List[Dict[str, Any]]: 67 | """Generiert legale Züge - Alias für generate_moves für Kompatibilität.""" 68 | return self.generate_moves(color) 69 | 70 | def generate_active_moves(self, color: int) -> List[Dict[str, Any]]: 71 | """Generiert nur aktive Züge (Schläge) für Quiescence Search.""" 72 | all_moves = self.generate_moves(color) 73 | active_moves = [] 74 | 75 | for move in all_moves: 76 | # Schlagzüge und Bauernumwandlungen als "aktiv" betrachten 77 | if (move.get('capture_pos') or 78 | move.get('special_type') == 'en_passant' or 79 | move.get('promotion_piece')): 80 | active_moves.append(move) 81 | 82 | return active_moves 83 | 84 | def is_move_legal(self, move: Dict[str, Any]) -> bool: 85 | """ 86 | Prüft, ob ein Zug legal ist (König nicht im Schach nach dem Zug) 87 | 88 | Args: 89 | move: Der Zug im Diktionär-Format 90 | 91 | Returns: 92 | bool: True wenn der Zug legal ist 93 | """ 94 | piece_id = move['piece_id'] 95 | piece = self.engine.get_piece_by_id(piece_id) 96 | 97 | if not piece: 98 | return False 99 | 100 | color = piece['color'] 101 | 102 | # Führe den Zug temporär aus 103 | snapshot = self.engine.take_snapshot() 104 | 105 | # Verwende die niedrigstufige Methode, um den Zug durchzuführen, 106 | # ohne die white_turn-Variable zu ändern. 107 | self.engine._apply_move_internal(move) 108 | 109 | is_legal = not self.engine.is_king_in_check(color) 110 | 111 | # Mache den Zug rückgängig 112 | self.engine.restore_snapshot(snapshot) 113 | 114 | return is_legal 115 | 116 | def _generate_special_moves(self, color: int) -> List[Dict[str, Any]]: 117 | """ 118 | Generiert Rochade und Bauernumwandlungen - KORRIGIERTE VERSION 119 | """ 120 | special_moves = [] 121 | 122 | # Rochade-Züge 123 | rules = self.engine.rules 124 | 125 | # Kleine Rochade 126 | if rules.validate_castling(color, 'kingside'): 127 | if color == WHITE: 128 | king_pos = 25 # e1 129 | king_to = 27 # g1 130 | rook_pos = 28 # h1 131 | rook_to = 26 # f1 132 | else: 133 | king_pos = 95 # e8 134 | king_to = 97 # g8 135 | rook_pos = 98 # h8 136 | rook_to = 96 # f8 137 | 138 | king = self.engine.get_piece_at(king_pos) 139 | rook = self.engine.get_piece_at(rook_pos) 140 | 141 | if king and king['type'] == KING and rook and rook['type'] == ROOK: 142 | special_moves.append({ 143 | 'piece_id': king['id'], 144 | 'piece': king, # 🚨 WICHTIG: Füge piece-Objekt hinzu 145 | 'type': KING, # 🚨 WICHTIG: Explizit setzen 146 | 'color': color, # 🚨 WICHTIG: Explizit setzen 147 | 'from_pos': king_pos, 148 | 'to_pos': king_to, 149 | 'capture_pos': None, 150 | 'promotion_piece': None, 151 | 'special_type': 'castling', 152 | 'rook_id': rook['id'], 153 | 'rook_from': rook_pos, 154 | 'rook_to': rook_to 155 | }) 156 | 157 | # Große Rochade 158 | if rules.validate_castling(color, 'queenside'): 159 | if color == WHITE: 160 | king_pos = 25 # e1 161 | king_to = 23 # c1 162 | rook_pos = 21 # a1 163 | rook_to = 24 # d1 164 | else: 165 | king_pos = 95 # e8 166 | king_to = 93 # c8 167 | rook_pos = 91 # a8 168 | rook_to = 94 # d8 169 | 170 | king = self.engine.get_piece_at(king_pos) 171 | rook = self.engine.get_piece_at(rook_pos) 172 | 173 | if king and king['type'] == KING and rook and rook['type'] == ROOK: 174 | special_moves.append({ 175 | 'piece_id': king['id'], 176 | 'piece': king, # 🚨 WICHTIG 177 | 'type': KING, # 🚨 WICHTIG 178 | 'color': color, # 🚨 WICHTIG 179 | 'from_pos': king_pos, 180 | 'to_pos': king_to, 181 | 'capture_pos': None, 182 | 'promotion_piece': None, 183 | 'special_type': 'castling', 184 | 'rook_id': rook['id'], 185 | 'rook_from': rook_pos, 186 | 'rook_to': rook_to 187 | }) 188 | 189 | return special_moves 190 | 191 | def generate_piece_moves(self, piece: Dict[str, Any]) -> List[Dict[str, Any]]: 192 | """ 193 | Generiert alle möglichen Züge für eine einzelne Figur (ohne Legalitätsprüfung) 194 | """ 195 | piece_type = piece['type'] 196 | 197 | if piece_type == PAWN: 198 | return self._generate_pawn_moves(piece) 199 | elif piece_type == ROOK: 200 | return self._generate_sliding_moves(piece, ['N', 'S', 'E', 'W']) 201 | elif piece_type == BISHOP: 202 | return self._generate_sliding_moves(piece, ['NE', 'NW', 'SE', 'SW']) 203 | elif piece_type == QUEEN: 204 | return self._generate_sliding_moves(piece, list(DIRECTIONS.keys())) 205 | elif piece_type == KNIGHT: 206 | return self._generate_knight_moves(piece) 207 | elif piece_type == KING: 208 | return self._generate_king_moves(piece) 209 | 210 | return [] 211 | 212 | def _create_move(self, piece: Dict[str, Any], to_pos: int, capture_pos: Optional[int] = None, 213 | special_type: Optional[str] = None, promotion_piece: Optional[int] = None) -> Dict[str, Any]: 214 | """ 215 | Erstellt ein standardisiertes Zug-Diktionär - KORRIGIERTE VERSION 216 | """ 217 | move_dict = { 218 | 'piece_id': piece['id'], 219 | 'piece': piece, 220 | 'type': piece['type'], # 🚨 KRITISCH: Figurentyp 221 | 'color': piece['color'], # 🚨 KRITISCH: Farbe 222 | 'from_pos': piece['position'], 223 | 'to_pos': to_pos, 224 | 'capture_pos': capture_pos, 225 | 'promotion_piece': promotion_piece, 226 | 'special_type': special_type 227 | } 228 | 229 | # Markiere Schlagzüge für Move Ordering 230 | if capture_pos is not None: 231 | move_dict['is_capture'] = True 232 | 233 | # 🚨 NEU: Setze promotion_type falls promotion_piece vorhanden 234 | if promotion_piece is not None: 235 | move_dict['promotion_type'] = promotion_piece 236 | 237 | return move_dict 238 | 239 | def _generate_pawn_moves(self, pawn: Dict[str, Any]) -> List[Dict[str, Any]]: 240 | """ 241 | Generiert Züge für einen Bauern 242 | """ 243 | moves = [] 244 | color = pawn['color'] 245 | start_pos = pawn['position'] 246 | 247 | # Die Richtung, in die der Bauer zieht 248 | forward = DIRECTIONS['N'] * color 249 | 250 | # Position der 1. und 2. Reihe (bezogen auf das 10x10 Board) 251 | # Die 1. Reihe (aus Benutzersicht) ist die Reihe 2, die 8. Reihe ist die Reihe 9. 252 | # Weiße Bauern starten auf Reihe 3 (31-38), Schwarze auf Reihe 8 (81-88). 253 | start_row = 3 if color == WHITE else 8 254 | 255 | # ========================================================================= 256 | # 1. Vorwärtszug (Ein Feld) 257 | # ========================================================================= 258 | one_step = start_pos + forward 259 | if self.engine.get_piece_at(one_step) is None: 260 | 261 | # KORREKTUR: Eigene Implementierung für Promotions-Prüfung 262 | if self._is_promotion_rank(one_step, color): 263 | # Füge alle Promotion-Züge (Dame, Turm, Läufer, Springer) hinzu 264 | for p_type in [QUEEN, ROOK, BISHOP, KNIGHT]: 265 | moves.append(self._create_move(pawn, one_step, promotion_piece=p_type)) 266 | else: 267 | moves.append(self._create_move(pawn, one_step)) 268 | 269 | # ========================================================================= 270 | # 2. Vorwärtszug (Zwei Felder) 271 | # ========================================================================= 272 | if pawn['position'] // 10 == start_row: 273 | two_step = start_pos + 2 * forward 274 | if self.engine.get_piece_at(two_step) is None: 275 | moves.append(self._create_move(pawn, two_step, special_type='double_pawn_push')) 276 | 277 | # ========================================================================= 278 | # 3. Schlagzüge (Diagonal) 279 | # ========================================================================= 280 | capture_dirs = [forward + DIRECTIONS['E'], forward + DIRECTIONS['W']] 281 | 282 | for capture_dir in capture_dirs: 283 | target_pos = start_pos + capture_dir 284 | if not self.engine.is_valid_position(target_pos): 285 | continue 286 | 287 | target_piece = self.engine.get_piece_at(target_pos) 288 | 289 | # Normaler Schlagzug 290 | if target_piece and target_piece['color'] != color: 291 | if self._is_promotion_rank(target_pos, color): 292 | # Promotion-Schlagzug 293 | for p_type in [QUEEN, ROOK, BISHOP, KNIGHT]: 294 | moves.append(self._create_move(pawn, target_pos, target_pos, promotion_piece=p_type)) 295 | else: 296 | moves.append(self._create_move(pawn, target_pos, target_pos)) 297 | 298 | # En Passant 299 | elif target_pos == self.engine.rules.en_passant_target: 300 | # Das geschlagene Bauernfeld ist immer 10 Schritte (eine Reihe) hinter dem Ziel 301 | captured_pawn_pos = target_pos - forward 302 | 303 | moves.append(self._create_move(pawn, target_pos, captured_pawn_pos, special_type='en_passant')) 304 | 305 | return moves 306 | 307 | def _is_promotion_rank(self, position: int, color: int) -> bool: 308 | """ 309 | Prüft ob eine Position auf der Umwandlungsreihe für die gegebene Farbe liegt 310 | """ 311 | row = position // 10 312 | # Weißer Bauer auf 8. Reihe (Row 9) oder schwarzer Bauer auf 1. Reihe (Row 2) 313 | return (color == WHITE and row == 9) or (color == BLACK and row == 2) 314 | 315 | def _generate_knight_moves(self, piece: Dict[str, Any]) -> List[Dict[str, Any]]: 316 | """ 317 | Generiert Züge für einen Springer 318 | """ 319 | moves = [] 320 | current_pos = piece['position'] # KORREKTUR: Verwende 'position' 321 | color = piece['color'] 322 | 323 | for move in KNIGHT_MOVES: 324 | target_pos = current_pos + move 325 | if self.engine.is_valid_position(target_pos): 326 | target_piece = self.engine.get_piece_at(target_pos) 327 | 328 | if target_piece is None: 329 | # Leeres Feld 330 | moves.append(self._create_move(piece, target_pos)) 331 | elif target_piece['color'] != color: 332 | # Schlagzug 333 | moves.append(self._create_move(piece, target_pos, target_pos)) 334 | 335 | return moves 336 | 337 | def _generate_king_moves(self, piece: Dict[str, Any]) -> List[Dict[str, Any]]: 338 | """ 339 | Generiert Züge für den König (Rochade wird separat in _generate_special_moves behandelt) 340 | """ 341 | moves = [] 342 | current_pos = piece['position'] # KORREKTUR: Verwende 'position' 343 | color = piece['color'] 344 | 345 | for direction in DIRECTIONS.values(): 346 | target_pos = current_pos + direction 347 | if self.engine.is_valid_position(target_pos): 348 | target_piece = self.engine.get_piece_at(target_pos) 349 | 350 | if target_piece is None: 351 | # Leeres Feld 352 | moves.append(self._create_move(piece, target_pos)) 353 | elif target_piece['color'] != color: 354 | # Schlagzug 355 | moves.append(self._create_move(piece, target_pos, target_pos)) 356 | 357 | return moves 358 | 359 | def _generate_sliding_moves(self, piece: Dict[str, Any], directions: List[str]) -> List[Dict[str, Any]]: 360 | """ 361 | Generiert Züge für gleitende Figuren (Dame, Turm, Läufer) 362 | """ 363 | moves = [] 364 | current_pos = piece['position'] # KORREKTUR: Verwende 'position' 365 | color = piece['color'] 366 | 367 | for direction_str in directions: 368 | direction = DIRECTIONS[direction_str] 369 | field = current_pos + direction 370 | 371 | while self.engine.is_valid_position(field): 372 | target_piece = self.engine.get_piece_at(field) 373 | 374 | if target_piece is None: 375 | # Leeres Feld: Zug hinzufügen und weiter in diese Richtung 376 | moves.append(self._create_move(piece, field)) 377 | elif target_piece['color'] != color: 378 | # Gegnerische Figur: Schlagzug hinzufügen und Schleife beenden 379 | moves.append(self._create_move(piece, field, field)) 380 | break 381 | else: 382 | # Eigene Figur: Blockiert, Schleife beenden 383 | break 384 | 385 | field += direction 386 | 387 | return moves 388 | 389 | def get_attacked_squares(self, piece: Dict[str, Any]) -> List[int]: 390 | """ 391 | Gibt eine Liste aller Felder zurück, die von einer bestimmten Figur angegriffen werden. 392 | Wird für die Schachprüfung verwendet. 393 | """ 394 | piece_type = piece['type'] 395 | attacked_squares = [] 396 | 397 | # KORREKTUR: Die Position der Figur ist IMMER 'position', NICHT 'pos'. 398 | current_pos = piece['position'] 399 | color = piece['color'] 400 | 401 | if piece_type == PAWN: 402 | forward = DIRECTIONS['N'] * color 403 | # Bauern-Angriffszüge sind diagonal (keine normalen Züge) 404 | capture_dirs = [forward + DIRECTIONS['E'], forward + DIRECTIONS['W']] 405 | 406 | for capture_dir in capture_dirs: 407 | target_pos = current_pos + capture_dir 408 | if self.engine.is_valid_position(target_pos): 409 | # Nur die Angriffsfelder zurückgeben, unabhängig davon, ob eine Figur dort steht 410 | attacked_squares.append(target_pos) 411 | 412 | elif piece_type == KNIGHT: 413 | for move in KNIGHT_MOVES: 414 | target_pos = current_pos + move 415 | if self.engine.is_valid_position(target_pos): 416 | attacked_squares.append(target_pos) 417 | 418 | elif piece_type == KING: 419 | for direction in DIRECTIONS.values(): 420 | target_pos = current_pos + direction 421 | if self.engine.is_valid_position(target_pos): 422 | attacked_squares.append(target_pos) 423 | 424 | elif piece_type in [ROOK, BISHOP, QUEEN]: 425 | if piece_type == ROOK: 426 | directions = ['N', 'S', 'E', 'W'] 427 | elif piece_type == BISHOP: 428 | directions = ['NE', 'NW', 'SE', 'SW'] 429 | else: # QUEEN 430 | directions = list(DIRECTIONS.keys()) 431 | 432 | for direction_str in directions: 433 | direction = DIRECTIONS[direction_str] 434 | field = current_pos + direction 435 | 436 | # Dies ist der Teil, der im Traceback (Zeile 642) den Fehler verursachte, 437 | # wenn er in einer Unterfunktion fälschlicherweise 'pos' verwendete. 438 | # Hier ist die Korrektur: Die Startposition ist current_pos = piece['position']. 439 | 440 | while self.engine.is_valid_position(field): 441 | attacked_squares.append(field) 442 | target_piece = self.engine.get_piece_at(field) 443 | 444 | # Bei Angriffsgenerierung stoppen wir nur, wenn wir auf eine Figur treffen 445 | if target_piece is not None: 446 | break 447 | 448 | field += direction 449 | 450 | return attacked_squares 451 | 452 | def is_square_attacked(self, position: int, attacker_color: int) -> bool: 453 | """ 454 | Prüft, ob ein Feld von einer Figur der gegebenen Farbe angegriffen wird 455 | 456 | Args: 457 | position: Zu prüfendes Feld 458 | attacker_color: Farbe der Angreifer 459 | 460 | Returns: 461 | bool: True wenn Feld angegriffen wird 462 | """ 463 | for piece in self.engine.pieces: 464 | if (piece['captured'] or 465 | piece['color'] != attacker_color): 466 | continue 467 | 468 | attacked_squares = self.get_attacked_squares(piece) 469 | if position in attacked_squares: 470 | return True 471 | 472 | return False 473 | 474 | 475 | # Test des erweiterten MoveGenerators 476 | if __name__ == "__main__": 477 | print("Testing Extended MoveGenerator...") 478 | 479 | from core import ChesstegEngine 480 | engine = ChesstegEngine() 481 | move_gen = MoveGenerator(engine) 482 | 483 | # Test: Züge für Weiß generieren 484 | white_moves = move_gen.generate_moves(1) 485 | print(f"White moves in initial position: {len(white_moves)}") 486 | 487 | # Test: Spezielle Züge 488 | castling_moves = [m for m in white_moves if m.get('special_type') == 'castling'] 489 | print(f"Castling moves available: {len(castling_moves)}") 490 | 491 | # Test: Springer-Züge 492 | knights = [p for p in engine.pieces if p['type'] == 4 and p['color'] == 1] 493 | for knight in knights: 494 | knight_moves = move_gen._generate_knight_moves(knight) 495 | print(f"Knight at {engine._position_to_notation(knight['position'])} moves: {len(knight_moves)}") 496 | -------------------------------------------------------------------------------- /unitchesstegmain.pas: -------------------------------------------------------------------------------- 1 | unit unitChesstegMain; 2 | (****************************************************************) 3 | (* (c) 1994 1995 Paul Koop chessteg didaktisches Schachprogramm *) 4 | (* das didaktische Schachprogramm chessteg wurde *) 5 | (* urspruenglisch im rahmen der entwicklung der *) 6 | (* Algorithmisch Rekursive Sequenzanalyse *) 7 | (* zur ueberpruefung der verwendbarkeit von *) 8 | (* spielbaeumen und alpha beta suche entwickelt *) 9 | (* nicht als spielstarkes programm beabsichtigt *) 10 | (* liegt seine staerke in der didaktischen verwendbarkeit *) 11 | (* *) 12 | (* mit enpassant als bewerterer Zug innerhalb Suchbaum *) 13 | (* Bauernumwandlung in Dame, Turm, Springer oder Laeufer *) 14 | (* nach umwandlungsfaehigem Bauernzug wird separat bewertet und *) 15 | (* umgewandelt in Dame, Turm, Springer oder Laeufer *) 16 | (* Rochade als Figurenumstellung an Stelle von bewertetem Zug *) 17 | (* fuer dos (derivate) unix (derivate) turbo-pascal free-pascal *) 18 | (* das programm darf mit copyright paul koop kostenfrei zu *) 19 | (* nicht kommerziellen zwecken verwendet werden *) 20 | (****************************************************************) 21 | 22 | {$mode objfpc}{$H+} 23 | 24 | interface 25 | 26 | uses 27 | Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ComCtrls, 28 | Grids, StdCtrls, FPImage, FPCanvas, FPImgCanv, 29 | FPWritePNG, FPReadPNG,UnitChesstegEngine; 30 | 31 | type 32 | 33 | { TfrmChessteg } 34 | 35 | TfrmChessteg = class(TForm) 36 | btnNaechsterZug: TButton; 37 | amZug: TLabel; 38 | LabelSpielerzug1: TLabel; 39 | SpielerZieht: TButton; 40 | SchwarzerZug: TButton; 41 | WeisserZug: TButton; 42 | StaticText1: TStaticText; 43 | StaticText10: TStaticText; 44 | StaticText11: TStaticText; 45 | StaticText12: TStaticText; 46 | StaticText13: TStaticText; 47 | StaticText14: TStaticText; 48 | StaticText15: TStaticText; 49 | StaticText16: TStaticText; 50 | StaticText2: TStaticText; 51 | StaticText3: TStaticText; 52 | StaticText4: TStaticText; 53 | StaticText5: TStaticText; 54 | StaticText6: TStaticText; 55 | StaticText7: TStaticText; 56 | StaticText8: TStaticText; 57 | StaticText9: TStaticText; 58 | von: TEdit; 59 | nach: TEdit; 60 | grdBrettansicht: TStringGrid; 61 | LabelSpielerzug: TLabel; 62 | LabelZugVon: TLabel; 63 | LabelZugNach: TLabel; 64 | procedure btnNaechsterZugClick(Sender: TObject); 65 | procedure SchwarzerZugClick(Sender: TObject); 66 | procedure SpielerZiehtClick(Sender: TObject); 67 | procedure WeisserZugClick(Sender: TObject); 68 | procedure FormCreate(Sender: TObject); 69 | procedure FormDestroy(Sender: TObject); 70 | procedure grdBrettansichtDrawCell(Sender: TObject; aCol, aRow: Integer; 71 | aRect: TRect; aState: TGridDrawState); 72 | procedure grdBrettansichtSelectCell(Sender: TObject; aCol, aRow: Integer; 73 | var CanSelect: Boolean); 74 | procedure grdZeigebrett(); 75 | 76 | private 77 | { private declarations } 78 | public 79 | { public declarations } 80 | end; 81 | 82 | var 83 | frmChessteg: TfrmChessteg; 84 | weissAmZug: boolean; 85 | SpielerSetztZug,SpielerWaehltZug:boolean; 86 | pltBauerWeiss, 87 | rltTurmWeiss, 88 | bltLaeuferWeiss, 89 | nltSpringerWeiss, 90 | qdtDameWeiss, 91 | kltKoenigWeiss, 92 | pdtBauerSchwarz, 93 | rdtTurmSchwarz, 94 | bdtLaeuferSchwarz, 95 | ndtSpringerSchwarz, 96 | qdtDameSchwarz, 97 | kdtKoenigSchwarz : TPortableNetworkGraphic; 98 | 99 | 100 | 101 | 102 | 103 | implementation 104 | 105 | {$R *.lfm} 106 | 107 | { TfrmChessteg } 108 | procedure TfrmChessteg.FormDestroy(Sender: TObject); 109 | begin 110 | abbauFigurenListe(figurenliste); 111 | end; 112 | 113 | procedure TfrmChessteg.grdBrettansichtDrawCell(Sender: TObject; aCol, 114 | aRow: Integer; aRect: TRect; aState: TGridDrawState); 115 | begin 116 | IF (trunc(aCol mod 2)= trunc(aRow mod 2)) 117 | THEN 118 | BEGIN 119 | grdBrettansicht.Canvas.Brush.Color:= clWhite; 120 | grdBrettansicht.Canvas.fillrect(aRect); 121 | (*StringGrid1.Cells[aCol, aRow] :=StringGrid1.Cells[aCol, aRow]*) 122 | END 123 | 124 | ELSE 125 | BEGIN 126 | grdBrettansicht.Canvas.Brush.Color:= clGray; 127 | grdBrettansicht.Canvas.fillrect(aRect); 128 | (*StringGrid1.Cells[aCol, aRow] :=StringGrid1.Cells[aCol, aRow]*) 129 | END; 130 | 131 | CASE grdBrettansicht.Cells[aCol, aRow] OF 132 | 'sb':BEGIN grdBrettansicht.Canvas.StretchDraw(aRect,pdtBauerSchwarz); END; 133 | 'sD':BEGIN grdBrettansicht.Canvas.StretchDraw(aRect,qdtDameSchwarz); END; 134 | 'sK':BEGIN grdBrettansicht.Canvas.StretchDraw(aRect,kdtKoenigSchwarz); END; 135 | 'sL':BEGIN grdBrettansicht.Canvas.StretchDraw(aRect,bdtLaeuferSchwarz); END; 136 | 'sS':BEGIN grdBrettansicht.Canvas.StretchDraw(aRect,ndtSpringerSchwarz); END; 137 | 'sT':BEGIN grdBrettansicht.Canvas.StretchDraw(aRect,rdtTurmSchwarz); END; 138 | 'wb':BEGIN grdBrettansicht.Canvas.StretchDraw(aRect,pltBauerWeiss); END; 139 | 'wD':BEGIN grdBrettansicht.Canvas.StretchDraw(aRect,qdtDameWeiss); END; 140 | 'wK':BEGIN grdBrettansicht.Canvas.StretchDraw(aRect,kltKoenigWeiss); END; 141 | 'wL':BEGIN grdBrettansicht.Canvas.StretchDraw(aRect,bltLaeuferWeiss); END; 142 | 'wS':BEGIN grdBrettansicht.Canvas.StretchDraw(aRect,nltSpringerWeiss); END; 143 | 'wT':BEGIN grdBrettansicht.Canvas.StretchDraw(aRect,rltTurmWeiss); END; 144 | 145 | ELSE 146 | //write('fehler bei art '); 147 | END; 148 | 149 | 150 | end; 151 | 152 | procedure TfrmChessteg.grdBrettansichtSelectCell(Sender: TObject; aCol, 153 | aRow: Integer; var CanSelect: Boolean); 154 | 155 | function feld (i:integer):String; 156 | var idiv,imod:Integer; 157 | BEGIN 158 | idiv:= i div 10; 159 | imod:= i mod 10; 160 | CASE imod of 161 | 1: feld:='A'; 162 | 2: feld:='B'; 163 | 3: feld:='C'; 164 | 4: feld:='D'; 165 | 5: feld:='E'; 166 | 6: feld:='F'; 167 | 7: feld:='G'; 168 | 8: feld:='H'; 169 | END; 170 | feld:= feld+ inttostr(idiv-1); 171 | END; 172 | begin 173 | IF (SpielerWaehltZug) 174 | THEN 175 | BEGIN (*von *) 176 | SpielerWaehltZug := false; 177 | SpielerSetztZug:=false; 178 | von.Text:= feld((10*aRow+20)+8-aCol); 179 | Spielerzug.vonpos:= ((10*aRow+20)+8-aCol); 180 | nach.Text:=' '; 181 | 182 | 183 | END 184 | ELSE 185 | BEGIN (* nach *) 186 | SpielerWaehltZug := true; 187 | SpielerSetztZug:=true; 188 | nach.Text:= feld((10*aRow+20)+8-aCol); 189 | Spielerzug.nachpos:= ((10*aRow+20)+8-aCol); 190 | END 191 | end; 192 | 193 | 194 | procedure TfrmChessteg.grdZeigebrett(); 195 | var x,y:integer; figur:string[2]; 196 | PROCEDURE schreibeFigur (art:INTEGER); 197 | BEGIN 198 | CASE abs(art) OF 199 | cb:figur:=figur + 'b'; 200 | cl:figur:=figur + 'L'; 201 | cs:figur:=figur + 'S'; 202 | ct:figur:=figur + 'T'; 203 | cd:figur:=figur + 'D'; 204 | ck:figur:=figur + 'K'; 205 | 206 | 207 | ELSE 208 | //write('fehler bei art '); 209 | END; 210 | END; 211 | 212 | begin 213 | 214 | For x:=0 to 7 do 215 | for y:=0 to 7 do 216 | BEGIN 217 | figur:= ' '; 218 | if brett[(10*(x+1)+10)+(9-(y+1))]<>0 219 | then 220 | begin 221 | if brett[(10*(x+1)+10)+(9-(y+1))]<0 then figur:='s' else figur := 'w'; 222 | schreibeFigur (brett[(10*(x+1)+10)+(9-(y+1))]); 223 | end; 224 | grdBrettansicht.Cells[y, x] := figur; 225 | END; (* Cells[aCol, aRow] *) 226 | 227 | end; 228 | 229 | 230 | 231 | 232 | 233 | procedure TfrmChessteg.FormCreate(Sender: TObject); 234 | var x:integer; 235 | begin 236 | figurenliste:=figurenListeGenerieren(); 237 | endmatt:=false;patt:=false; rochiertweiss:=false;rochiertschwarz:=false; 238 | 239 | 240 | weissAmZug:=true; 241 | SpielerSetztZug:=false; 242 | SpielerWaehltZug:=true; 243 | 244 | grdBrettansicht.Row:=1; 245 | grdBrettansicht.Col:=3; 246 | grdZeigebrett(); 247 | 248 | 249 | {Die Abbildungen der Figuren sind verfuegbar unter 250 | https://commons.m.wikimedia.org/wiki/Category:PNG_chess_pieces/Standard_transparent 251 | https://creativecommons.org/licenses/by-sa/3.0/} 252 | 253 | For X:=1 To 12 254 | DO 255 | BEGIN 256 | CASE x OF 257 | 1:BEGIN 258 | pltBauerWeiss:=TPortableNetworkGraphic.Create; 259 | pltBauerWeiss.LoadFromFile('Chess_plt60.png'); 260 | END; 261 | 262 | 2:BEGIN 263 | rltTurmWeiss:=TPortableNetworkGraphic.Create; 264 | rltTurmWeiss.LoadFromFile('Chess_rlt60.png'); 265 | END; 266 | 3:BEGIN 267 | bltLaeuferWeiss:=TPortableNetworkGraphic.Create; 268 | bltLaeuferWeiss.LoadFromFile('Chess_blt60.png'); 269 | END; 270 | 4:BEGIN 271 | nltSpringerWeiss:=TPortableNetworkGraphic.Create; 272 | nltSpringerWeiss.LoadFromFile('Chess_nlt60.png'); 273 | END; 274 | 5:BEGIN 275 | qdtDameWeiss:=TPortableNetworkGraphic.Create; 276 | qdtDameWeiss.LoadFromFile('Chess_qlt60.png'); 277 | END; 278 | 6:BEGIN 279 | kltKoenigWeiss:=TPortableNetworkGraphic.Create; 280 | kltKoenigWeiss.LoadFromFile('Chess_klt60.png'); 281 | END; 282 | 283 | 7:BEGIN 284 | pdtBauerSchwarz:=TPortableNetworkGraphic.Create; 285 | pdtBauerSchwarz.LoadFromFile('Chess_pdt60.png'); 286 | END; 287 | 8:BEGIN 288 | rdtTurmSchwarz:=TPortableNetworkGraphic.Create; 289 | rdtTurmSchwarz.LoadFromFile('Chess_rdt60.png'); 290 | END; 291 | 9:BEGIN 292 | bdtLaeuferSchwarz:=TPortableNetworkGraphic.Create; 293 | bdtLaeuferSchwarz.LoadFromFile('Chess_bdt60.png'); 294 | END; 295 | 10:BEGIN 296 | ndtSpringerSchwarz:=TPortableNetworkGraphic.Create; 297 | ndtSpringerSchwarz.LoadFromFile('Chess_ndt60.png'); 298 | END; 299 | 11:BEGIN 300 | qdtDameSchwarz:=TPortableNetworkGraphic.Create; 301 | qdtDameSchwarz.LoadFromFile('Chess_qdt60.png'); 302 | END; 303 | 12:BEGIN 304 | kdtKoenigSchwarz:=TPortableNetworkGraphic.Create; 305 | kdtKoenigSchwarz.LoadFromFile('Chess_kdt60.png'); 306 | END; 307 | 308 | END; 309 | END; 310 | 311 | end; 312 | 313 | procedure TfrmChessteg.btnNaechsterZugClick(Sender: TObject); 314 | begin 315 | von.text:='von'; 316 | nach.text:='nach'; 317 | IF weissAmZug THEN 318 | BEGIN (* weisAmZug *) 319 | amZug.Caption:='Schwarz am Zug'; 320 | IF (NOT(endmatt)OR(patt)) 321 | THEN 322 | BEGIN 323 | IF (kleinerochademoeglich(figurenliste,cweiss) OR grosserochademoeglich(figurenliste,cweiss)) AND(NOT rochiertweiss) 324 | THEN 325 | BEGIN 326 | IF kleinerochademoeglich(figurenliste,cweiss) 327 | THEN 328 | BEGIN 329 | //writeln(' Kleine Rochade Weiss'); 330 | kleinerochade(figurenliste,cweiss); 331 | grdZeigebrett(); 332 | (*figuren(figurenliste); *) 333 | //readln;clrscr; 334 | END 335 | ELSE 336 | BEGIN 337 | //writeln(' Grosse Rochade Weiss'); 338 | grosserochade(figurenliste,cweiss); 339 | grdZeigebrett(); 340 | (*figuren(figurenliste); *) 341 | //readln;clrscr; 342 | END 343 | END 344 | ELSE 345 | BEGIN 346 | bewertung:=computerzug(cweiss,cminInteger,cmaxInteger,1,ctiefe,'-'); 347 | //writeln(bewertung,' computerzug'); 348 | grdZeigebrett(); 349 | (*figuren(figurenliste);*) 350 | //readln;clrscr; 351 | END; 352 | weissAmZug:=false; 353 | END; 354 | END (* weissAmZug *) 355 | ELSE 356 | BEGIN (* schwarzAmZug *) 357 | amZug.Caption:='Weiss am Zug'; 358 | IF (NOT(endmatt)OR(patt)) 359 | THEN 360 | BEGIN 361 | IF (kleinerochademoeglich(figurenliste,cschwarz) OR grosserochademoeglich(figurenliste,cschwarz)) AND(NOT rochiertschwarz) 362 | THEN 363 | BEGIN 364 | IF kleinerochademoeglich(figurenliste,cschwarz) 365 | THEN 366 | BEGIN 367 | //writeln(bewertung,' Kleine Rochade Schwarz'); 368 | kleinerochade(figurenliste,cschwarz); 369 | grdZeigebrett(); 370 | (*figuren(figurenliste);*) 371 | //readln;clrscr; 372 | END 373 | ELSE 374 | BEGIN 375 | //writeln(bewertung,' Grosse Rochade Schwarz'); 376 | grosserochade(figurenliste,cschwarz); 377 | grdZeigebrett(); 378 | (*figuren(figurenliste);*) 379 | //readln;clrscr; 380 | END 381 | END 382 | ELSE 383 | BEGIN 384 | bewertung:=computerzug(cschwarz,cmaxInteger,cminInteger,1,ctiefe,'-'); 385 | //writeln(bewertung,' computerzug'); 386 | grdZeigebrett(); 387 | (*figuren(figurenliste);*) 388 | //readln;clrscr; 389 | END; 390 | weissAmZug:=true; 391 | END 392 | END;(* schwarzAmZug *) 393 | end; 394 | 395 | 396 | 397 | 398 | 399 | procedure TfrmChessteg.SchwarzerZugClick(Sender: TObject); 400 | BEGIN (* schwarzAmZug *) 401 | amZug.Caption:='Weiss am Zug'; 402 | IF (NOT(endmatt)OR(patt)) 403 | THEN 404 | BEGIN 405 | IF (kleinerochademoeglich(figurenliste,cschwarz) OR grosserochademoeglich(figurenliste,cschwarz)) AND(NOT rochiertschwarz) 406 | THEN 407 | BEGIN 408 | IF kleinerochademoeglich(figurenliste,cschwarz) 409 | THEN 410 | BEGIN 411 | //writeln(bewertung,' Kleine Rochade Schwarz'); 412 | kleinerochade(figurenliste,cschwarz); 413 | grdZeigebrett(); 414 | (*figuren(figurenliste);*) 415 | //readln;clrscr; 416 | END 417 | ELSE 418 | BEGIN 419 | //writeln(bewertung,' Grosse Rochade Schwarz'); 420 | grosserochade(figurenliste,cschwarz); 421 | grdZeigebrett(); 422 | (*figuren(figurenliste);*) 423 | //readln;clrscr; 424 | END 425 | END 426 | ELSE 427 | BEGIN 428 | bewertung:=computerzug(cschwarz,cmaxInteger,cminInteger,1,ctiefe,'-'); 429 | //writeln(bewertung,' computerzug'); 430 | grdZeigebrett(); 431 | (*figuren(figurenliste);*) 432 | //readln;clrscr; 433 | END; 434 | weissAmZug:=true; 435 | END 436 | END;(* schwarzAmZug *) 437 | 438 | procedure TfrmChessteg.SpielerZiehtClick(Sender: TObject); 439 | var SpielerHatGezogen: Boolean; 440 | var farbe: Integer; 441 | begin 442 | (* *) 443 | (* Spielt hat gezogen false *) 444 | SpielerHatGezogen := false; 445 | IF (NOT(endmatt)OR(patt)) 446 | THEN 447 | BEGIN (* (NOT(endmatt)OR(patt)) *) 448 | IF weissAmZug THEN farbe := cweiss ELSE farbe := cschwarz; 449 | (* Rochade Weiss *) 450 | IF weissAmZug THEN 451 | BEGIN 452 | IF (kleinerochademoeglich(figurenliste,cweiss) OR grosserochademoeglich(figurenliste,cweiss)) AND(NOT rochiertweiss) 453 | THEN 454 | BEGIN 455 | IF kleinerochademoeglich(figurenliste,cweiss) AND ((Spielerzug.vonpos=E1)AND(Spielerzug.nachpos=G1)) 456 | THEN 457 | BEGIN 458 | //writeln(' Kleine Rochade Weiss'); 459 | kleinerochade(figurenliste,cweiss); 460 | SpielerHatGezogen := true; 461 | grdZeigebrett(); 462 | (*figuren(figurenliste); *) 463 | //readln;clrscr; 464 | END 465 | ELSE 466 | BEGIN 467 | //writeln(' Grosse Rochade Weiss'); 468 | IF (grosserochademoeglich(figurenliste,cweiss))AND((Spielerzug.vonpos=E1)AND(Spielerzug.nachpos=C1)) THEN 469 | BEGIN 470 | grosserochade(figurenliste,cweiss); 471 | SpielerHatGezogen := true; 472 | grdZeigebrett(); 473 | END; 474 | (*figuren(figurenliste); *) 475 | //readln;clrscr; 476 | END 477 | END 478 | (* SpielerHatGezogen := true;*) 479 | END 480 | ELSE 481 | (* Rochade Schwarz*) 482 | BEGIN 483 | IF (kleinerochademoeglich(figurenliste,cschwarz) OR grosserochademoeglich(figurenliste,cschwarz)) AND(NOT rochiertschwarz) 484 | THEN 485 | BEGIN 486 | IF kleinerochademoeglich(figurenliste,cschwarz) AND ((Spielerzug.vonpos=E8)AND(Spielerzug.nachpos=G8)) 487 | THEN 488 | BEGIN 489 | //writeln(' Kleine Rochade Weiss'); 490 | kleinerochade(figurenliste,cschwarz); 491 | SpielerHatGezogen := true; 492 | grdZeigebrett(); 493 | (*figuren(figurenliste); *) 494 | //readln;clrscr; 495 | END 496 | ELSE 497 | BEGIN 498 | //writeln(' Grosse Rochade Weiss'); 499 | IF (grosserochademoeglich(figurenliste,cschwarz))AND((Spielerzug.vonpos=E8)AND(Spielerzug.nachpos=C8)) THEN 500 | BEGIN 501 | grosserochade(figurenliste,cschwarz); 502 | SpielerHatGezogen := true; 503 | grdZeigebrett(); 504 | END; 505 | (*figuren(figurenliste); *) 506 | //readln;clrscr; 507 | END 508 | END 509 | (* SpielerHatGezogen := true;*) 510 | (* SpielerHatGezogen := true; *) 511 | END; 512 | 513 | (* andere Zuege *) 514 | IF NOT(SpielerHatGezogen) THEN 515 | BEGIN 516 | (* Hier Spielerzug: prüfen 1. In zugliste 2. setzt nicht ins Schach *) 517 | SpielerHatGezogen:=computerzugSPIELER (Spielerzug,farbe,'-'); 518 | END; 519 | (* Zug andere Farbe *) 520 | 521 | IF SpielerHatGezogen THEN 522 | BEGIN 523 | IF (NOT(endmatt)OR(patt)) 524 | THEN 525 | BEGIN 526 | SpielerHatGezogen :=false; 527 | IF weissAmZug THEN farbe := cweiss ELSE farbe := cschwarz; 528 | (* Rochade Weiss *) 529 | IF NOT(weissAmZug) THEN 530 | BEGIN 531 | IF (kleinerochademoeglich(figurenliste,cweiss) OR grosserochademoeglich(figurenliste,cweiss)) AND(NOT rochiertweiss) 532 | THEN 533 | BEGIN 534 | IF kleinerochademoeglich(figurenliste,cweiss) 535 | THEN 536 | BEGIN 537 | //writeln(' Kleine Rochade Weiss'); 538 | kleinerochade(figurenliste,cweiss); 539 | SpielerHatGezogen := true; 540 | grdZeigebrett(); 541 | (*figuren(figurenliste); *) 542 | //readln;clrscr; 543 | END 544 | ELSE 545 | BEGIN 546 | //writeln(' Grosse Rochade Weiss'); 547 | grosserochade(figurenliste,cweiss); 548 | SpielerHatGezogen := true; 549 | grdZeigebrett(); 550 | (*figuren(figurenliste); *) 551 | //readln;clrscr; 552 | END 553 | END 554 | (* SpielerHatGezogen := true;*) 555 | END 556 | ELSE 557 | (* Rochade Schwarz*) 558 | BEGIN 559 | IF (kleinerochademoeglich(figurenliste,cschwarz) OR grosserochademoeglich(figurenliste,cschwarz)) AND(NOT rochiertschwarz) 560 | THEN 561 | BEGIN 562 | IF kleinerochademoeglich(figurenliste,cschwarz) 563 | THEN 564 | BEGIN 565 | //writeln(' Kleine Rochade Weiss'); 566 | kleinerochade(figurenliste,cschwarz); 567 | SpielerHatGezogen := true; 568 | grdZeigebrett(); 569 | (*figuren(figurenliste); *) 570 | //readln;clrscr; 571 | END 572 | ELSE 573 | BEGIN 574 | //writeln(' Grosse Rochade Weiss'); 575 | grosserochade(figurenliste,cschwarz); 576 | SpielerHatGezogen := true; 577 | grdZeigebrett(); 578 | (*figuren(figurenliste); *) 579 | //readln;clrscr; 580 | END 581 | END 582 | (* SpielerHatGezogen := true;*) 583 | (* SpielerHatGezogen := true; *) 584 | END; 585 | IF NOT(SpielerHatGezogen) THEN 586 | BEGIN 587 | IF -farbe = cschwarz 588 | THEN 589 | bewertung:=computerzug(cschwarz,cmaxInteger,cminInteger,1,ctiefe,'-') 590 | ELSE 591 | bewertung:=computerzug(cweiss,cminInteger,cmaxInteger,1,ctiefe,'-'); 592 | //writeln(bewertung,' computerzug'); 593 | grdZeigebrett(); 594 | (*figuren(figurenliste);*) 595 | //readln;clrscr; 596 | END; 597 | END; 598 | 599 | END; 600 | END; (* (NOT(endmatt)OR(patt)) *) 601 | end; 602 | 603 | procedure TfrmChessteg.WeisserZugClick(Sender: TObject); 604 | BEGIN (* weisAmZug *) 605 | amZug.Caption:='Schwarz am Zug'; 606 | IF (NOT(endmatt)OR(patt)) 607 | THEN 608 | BEGIN 609 | IF (kleinerochademoeglich(figurenliste,cweiss) OR grosserochademoeglich(figurenliste,cweiss)) AND(NOT rochiertweiss) 610 | THEN 611 | BEGIN 612 | IF kleinerochademoeglich(figurenliste,cweiss) 613 | THEN 614 | BEGIN 615 | //writeln(' Kleine Rochade Weiss'); 616 | kleinerochade(figurenliste,cweiss); 617 | grdZeigebrett(); 618 | (*figuren(figurenliste); *) 619 | //readln;clrscr; 620 | END 621 | ELSE 622 | BEGIN 623 | //writeln(' Grosse Rochade Weiss'); 624 | grosserochade(figurenliste,cweiss); 625 | grdZeigebrett(); 626 | (*figuren(figurenliste); *) 627 | //readln;clrscr; 628 | END 629 | END 630 | ELSE 631 | BEGIN 632 | bewertung:=computerzug(cweiss,cminInteger,cmaxInteger,1,ctiefe,'-'); 633 | //writeln(bewertung,' computerzug'); 634 | grdZeigebrett(); 635 | (*figuren(figurenliste);*) 636 | //readln;clrscr; 637 | END; 638 | weissAmZug:=false; 639 | END; 640 | END; (* weissAmZug *) 641 | 642 | 643 | 644 | end. 645 | 646 | -------------------------------------------------------------------------------- /chessteg_modular/engine/rules.py: -------------------------------------------------------------------------------- 1 | """ 2 | Chessteg Rules Module - KORRIGIERTE VERSION 3 | Vollständige Implementierung spezieller Schachregeln 4 | """ 5 | 6 | import copy 7 | from typing import Dict, Any, Optional, List, Tuple 8 | 9 | 10 | class ChessRules: 11 | """ 12 | Implementiert spezielle Schachregeln: Rochade, en Passant, Bauernumwandlung 13 | """ 14 | 15 | def __init__(self, engine): 16 | self.engine = engine 17 | self.en_passant_target = None 18 | self.castling_rights = { 19 | 'white_kingside': True, 20 | 'white_queenside': True, 21 | 'black_kingside': True, 22 | 'black_queenside': True 23 | } 24 | self.move_history = [] 25 | 26 | def process_move(self, move: Dict[str, Any], piece: Dict[str, Any], captured_piece: Optional[Dict[str, Any]]): 27 | """ 28 | Verarbeitet spezielle Zugregeln nach einem Zug 29 | 30 | Args: 31 | move: Der ausgeführte Zug 32 | piece: Die bewegte Figur 33 | captured_piece: Geschlagene Figur (falls vorhanden) 34 | """ 35 | # 🛡️ ROBUSTHEIT: FALLBACK für fehlende Felder 36 | move_type = move.get('type') 37 | if move_type is None and piece is not None: 38 | move_type = piece['type'] 39 | 40 | move_color = move.get('color') 41 | if move_color is None and piece is not None: 42 | move_color = piece['color'] 43 | 44 | # En Passant Recht aktualisieren (nur für Bauern) 45 | if move_type == 1: # PAWN 46 | self.update_en_passant_after_move(move, move_color) 47 | 48 | # Rochaderechte aktualisieren 49 | self.update_castling_rights_after_move(move, piece) 50 | 51 | # Bauernumwandlung prüfen 52 | if self.check_pawn_promotion_required(move, piece): 53 | move['requires_promotion'] = True 54 | 55 | def validate_castling(self, color: int, side: str) -> bool: 56 | """ 57 | Prüft ob Rochade möglich ist 58 | 59 | Args: 60 | color: Farbe (1=weiß, -1=schwarz) 61 | side: 'kingside' oder 'queenside' 62 | 63 | Returns: 64 | bool: True wenn Rochade legal 65 | """ 66 | # Grundvoraussetzungen prüfen 67 | if not self._check_basic_castling_requirements(color, side): 68 | return False 69 | 70 | # König darf nicht im Schach stehen 71 | if self.engine.is_king_in_check(color): 72 | return False 73 | 74 | # Felder zwischen König und Turm dür nicht angegriffen sein 75 | if not self._check_castling_squares_safety(color, side): 76 | return False 77 | 78 | # Felder zwischen König und Turm müssen frei sein 79 | if not self._check_castling_squares_empty(color, side): 80 | return False 81 | 82 | return True 83 | 84 | def execute_castling(self, color: int, side: str) -> bool: 85 | """ 86 | Führt Rochade aus 87 | 88 | Args: 89 | color: Farbe (1=weiß, -1=schwarz) 90 | side: 'kingside' oder 'queenside' 91 | 92 | Returns: 93 | bool: True wenn erfolgreich 94 | """ 95 | if not self.validate_castling(color, side): 96 | return False 97 | 98 | # König und Turm positionen bestimmen 99 | king_from, king_to, rook_from, rook_to = self._get_castling_positions(color, side) 100 | 101 | # König finden 102 | king = next((p for p in self.engine.pieces 103 | if p['type'] == 99 and p['color'] == color and not p['captured']), None) 104 | if not king: 105 | return False 106 | 107 | # Turm finden 108 | rook = next((p for p in self.engine.pieces 109 | if p['type'] == 5 and p['color'] == color and 110 | p['position'] == rook_from and not p['captured']), None) 111 | if not rook: 112 | return False 113 | 114 | # Rochade ausführen 115 | # 1. König bewegen 116 | self.engine.board[king_from] = 0 # EMPTY 117 | self.engine.board[king_to] = 99 * color 118 | king['position'] = king_to 119 | 120 | # 2. Turm bewegen 121 | self.engine.board[rook_from] = 0 # EMPTY 122 | self.engine.board[rook_to] = 5 * color 123 | rook['position'] = rook_to 124 | 125 | # Rochaderecht für diese Farbe aufheben 126 | self._revoke_castling_rights(color) 127 | 128 | # Zug zur History hinzufügen 129 | castling_move = { 130 | 'type': 'castling', 131 | 'color': color, 132 | 'side': side, 133 | 'king_from': king_from, 134 | 'king_to': king_to, 135 | 'rook_from': rook_from, 136 | 'rook_to': rook_to 137 | } 138 | self.move_history.append(castling_move) 139 | 140 | print(f"Castling executed: {self._get_castling_notation(color, side)}") 141 | return True 142 | 143 | def validate_en_passant(self, pawn: Dict[str, Any], target_pos: int) -> bool: 144 | """ 145 | Prüft en Passant 146 | 147 | Args: 148 | pawn: Bauer der schlagen will 149 | target_pos: Zielfeld 150 | 151 | Returns: 152 | bool: True wenn en Passant legal 153 | """ 154 | # Grundvoraussetzungen prüfen 155 | if not self._check_basic_en_passant_requirements(pawn, target_pos): 156 | return False 157 | 158 | # Es muss ein en Passant Ziel geben 159 | if self.en_passant_target is None: 160 | return False 161 | 162 | # Zielposition muss dem en Passant Ziel entsprechen 163 | if target_pos != self.en_passant_target: 164 | return False 165 | 166 | # Gegnerischer Bauer muss existieren 167 | opponent_pawn_pos = self._get_opponent_pawn_position_for_en_passant(pawn, target_pos) 168 | opponent_pawn = self.engine.get_piece_at(opponent_pawn_pos) 169 | 170 | if not opponent_pawn or opponent_pawn['type'] != 1 or opponent_pawn['color'] != -pawn['color']: 171 | return False 172 | 173 | # Der Zug darf keinen Selbstschach verursachen 174 | return self._validate_no_self_check_after_en_passant(pawn, target_pos, opponent_pawn_pos) 175 | 176 | def execute_en_passant(self, pawn: Dict[str, Any], target_pos: int) -> bool: 177 | """ 178 | Führt en Passant aus 179 | 180 | Args: 181 | pawn: Bauer der schlägt 182 | target_pos: Zielfeld 183 | 184 | Returns: 185 | bool: True wenn erfolgreich 186 | """ 187 | if not self.validate_en_passant(pawn, target_pos): 188 | print(f"En Passant validation failed for {self._position_to_notation(pawn['position'])} to {self._position_to_notation(target_pos)}") 189 | return False 190 | 191 | # Gegnerischen Bauer finden und position 192 | opponent_pawn_pos = self._get_opponent_pawn_position_for_en_passant(pawn, target_pos) 193 | opponent_pawn = self.engine.get_piece_at(opponent_pawn_pos) 194 | 195 | if not opponent_pawn: 196 | print(f"En Passant: No opponent pawn found at {self._position_to_notation(opponent_pawn_pos)}") 197 | return False 198 | 199 | # En Passant ausführen 200 | # 1. Bauer bewegen 201 | from_pos = pawn['position'] 202 | self.engine.board[from_pos] = 0 # EMPTY 203 | self.engine.board[target_pos] = 1 * pawn['color'] 204 | pawn['position'] = target_pos 205 | 206 | # 2. Gegnerischen Bauer schlagen 207 | self.engine.board[opponent_pawn_pos] = 0 # EMPTY 208 | opponent_pawn['captured'] = True 209 | 210 | # En Passant Recht zurücksetzen 211 | self.en_passant_target = None 212 | 213 | # Zug zur History hinzufügen 214 | en_passant_move = { 215 | 'type': 'en_passant', 216 | 'color': pawn['color'], 217 | 'from_pos': from_pos, 218 | 'to_pos': target_pos, 219 | 'captured_pawn_pos': opponent_pawn_pos 220 | } 221 | self.move_history.append(en_passant_move) 222 | 223 | print(f"En passant executed: {self._position_to_notation(from_pos)}{self._position_to_notation(target_pos)}") 224 | return True 225 | 226 | def handle_pawn_promotion(self, pawn: Dict[str, Any], promotion_piece: str = 'queen') -> bool: 227 | """ 228 | Behandelt Bauernumwandlung 229 | 230 | Args: 231 | pawn: Bauer der umgewandelt werden soll 232 | promotion_piece: Gewünschte Figur ('queen', 'rook', 'bishop', 'knight') 233 | 234 | Returns: 235 | bool: True wenn erfolgreich 236 | """ 237 | # Prüfen ob Umwandlung möglich ist 238 | if not self._can_pawn_promote(pawn): 239 | return False 240 | 241 | # Figurtyp bestimmen 242 | piece_type = self._get_promotion_piece_type(promotion_piece) 243 | if piece_type is None: 244 | return False 245 | 246 | # Umwandlung durchführen 247 | position = pawn['position'] 248 | 249 | # Alten Bauer entfernen 250 | self.engine.pieces = [p for p in self.engine.pieces if p != pawn] 251 | 252 | # Neue Figur hinzufügen 253 | self.engine.pieces.append({ 254 | 'type': piece_type, 255 | 'color': pawn['color'], 256 | 'position': position, 257 | 'captured': False 258 | }) 259 | 260 | # Brett aktualisieren 261 | self.engine.board[position] = piece_type * pawn['color'] 262 | 263 | # Umwandlung zur History hinzufügen 264 | promotion_move = { 265 | 'type': 'promotion', 266 | 'color': pawn['color'], 267 | 'position': position, 268 | 'promotion_piece': promotion_piece 269 | } 270 | self.move_history.append(promotion_move) 271 | 272 | print(f"Pawn promotion: {promotion_piece} at {self._position_to_notation(position)}") 273 | return True 274 | 275 | def check_pawn_promotion_required(self, move: Dict[str, Any], piece: Dict[str, Any] = None) -> bool: 276 | """ 277 | Prüft ob nach einem Bauerzug Umwandlung erforderlich ist 278 | 279 | Args: 280 | move: Ausgeführter Bauerzug 281 | piece: Figur (falls move['type'] fehlt) 282 | 283 | Returns: 284 | bool: True wenn Umwandlung erforderlich 285 | """ 286 | # 🛡️ ROBUSTHEIT: FALLBACK für fehlende Felder 287 | move_type = move.get('type') 288 | if move_type is None and piece is not None: 289 | move_type = piece['type'] 290 | 291 | move_color = move.get('color') 292 | if move_color is None and piece is not None: 293 | move_color = piece['color'] 294 | 295 | if move_type != 1: # Nur für Bauern 296 | return False 297 | 298 | target_row = move['to_pos'] // 10 299 | 300 | # Weißer Bauer auf 8. Reihe oder schwarzer Bauer auf 1. Reihe 301 | return (move_color == 1 and target_row == 9) or (move_color == -1 and target_row == 2) 302 | 303 | def update_castling_rights_after_move(self, move: Dict[str, Any], piece: Dict[str, Any] = None): 304 | """ 305 | Aktualisiert Rochaderechte nach einem Zug 306 | 307 | Args: 308 | move: Ausgeführter Zug 309 | piece: Figur (falls move['type'] fehlt) 310 | """ 311 | # 🛡️ ROBUSTHEIT: FALLBACK für fehlende Felder 312 | move_type = move.get('type') 313 | if move_type is None and piece is not None: 314 | move_type = piece['type'] 315 | 316 | move_color = move.get('color') 317 | if move_color is None and piece is not None: 318 | move_color = piece['color'] 319 | 320 | # Wenn König bewegt wurde, Rochaderechte aufheben 321 | if move_type == 99: # KING 322 | self._revoke_castling_rights(move_color) 323 | 324 | # Wenn Turm bewegt wurde, entsprechendes Rochaderecht aufheben 325 | elif move_type == 5: # ROOK 326 | self._revoke_rook_castling_rights(move_color, move['from_pos']) 327 | 328 | def update_en_passant_after_move(self, move: Dict[str, Any], move_color: int = None): 329 | """ 330 | Aktualisiert en Passant Recht nach einem Zug - KORRIGIERTE VERSION 331 | """ 332 | # 🛡️ ROBUSTHEIT: FALLBACK für fehlende Felder 333 | if move_color is None: 334 | move_color = move.get('color') 335 | 336 | # En Passant Recht zurücksetzen 337 | self.en_passant_target = None 338 | 339 | # Wenn Bauer Doppelschritt, en Passant Recht setzen 340 | move_type = move.get('type') 341 | if move_type == 1: # PAWN 342 | from_row = move['from_pos'] // 10 343 | to_row = move['to_pos'] // 10 344 | 345 | # Doppelschritt erkannt (2 Reihen Differenz) 346 | if abs(from_row - to_row) == 2: 347 | # Feld hinter dem Bauer setzen 348 | direction = 10 if move_color == 1 else -10 349 | self.en_passant_target = move['from_pos'] + direction 350 | # print(f"En passant target set: {self._position_to_notation(self.en_passant_target)}") # 🚨 DEBUG auskommentiert 351 | 352 | def get_available_promotion_pieces(self) -> List[str]: 353 | """ 354 | Gibt verfügbare Umwandlungsfiguren zurück 355 | 356 | Returns: 357 | List[str]: Liste der Figurennamen 358 | """ 359 | return ['queen', 'rook', 'bishop', 'knight'] 360 | 361 | def is_promotion_rank(self, position: int, color: int) -> bool: 362 | """ 363 | Prüft ob eine Position auf der Umwandlungsreihe für die gegebene Farbe liegt 364 | """ 365 | row = position // 10 366 | # Weißer Bauer auf 8. Reihe (Row 9) oder schwarzer Bauer auf 1. Reihe (Row 2) 367 | return (color == 1 and row == 9) or (color == -1 and row == 2) 368 | 369 | # ========================================================================= 370 | # HILFSFUNKTIONEN - ROCHADE 371 | # ========================================================================= 372 | 373 | def _check_basic_castling_requirements(self, color: int, side: str) -> bool: 374 | """Prüft grundlegende Rochade-Voraussetzungen""" 375 | # Rochaderecht muss vorhanden sein 376 | if not self._has_castling_right(color, side): 377 | return False 378 | 379 | # König und Turm müssen auf Startpositionen sein 380 | king_pos, rook_pos = self._get_king_rook_start_positions(color, side) 381 | 382 | king = self.engine.get_piece_at(king_pos) 383 | rook = self.engine.get_piece_at(rook_pos) 384 | 385 | if not king or king['type'] != 99 or king['color'] != color: 386 | return False 387 | 388 | if not rook or rook['type'] != 5 or rook['color'] != color: 389 | return False 390 | 391 | return True 392 | 393 | def _check_castling_squares_safety(self, color: int, side: str) -> bool: 394 | """Prüft ob Felder zwischen König und Turm sicher sind""" 395 | king_from, king_to, _, _ = self._get_castling_positions(color, side) 396 | 397 | # Felder die der König passiert prüfen 398 | if side == 'kingside': 399 | check_squares = [king_from + 1, king_from + 2] # f1, g1 für Weiß 400 | else: # queenside 401 | check_squares = [king_from - 1, king_from - 2] # d1, c1 für Weiß 402 | 403 | # Temporär König bewegen und Felder prüfen 404 | original_pos = king_from 405 | king = self.engine.get_piece_at(king_from) 406 | 407 | for square in check_squares: 408 | # Temporär König auf Feld bewegen 409 | self.engine.board[original_pos] = 0 410 | self.engine.board[square] = 99 * color 411 | if king: 412 | king['position'] = square 413 | 414 | # Prüfen ob König im Schach 415 | if self.engine.is_king_in_check(color): 416 | # Zustand zurücksetzen 417 | self.engine.board[original_pos] = 99 * color 418 | self.engine.board[square] = 0 419 | if king: 420 | king['position'] = original_pos 421 | return False 422 | 423 | # Zustand zurücksetzen 424 | self.engine.board[original_pos] = 99 * color 425 | self.engine.board[square] = 0 426 | 427 | if king: 428 | king['position'] = original_pos 429 | 430 | return True 431 | 432 | def _check_castling_squares_empty(self, color: int, side: str) -> bool: 433 | """Prüft ob Felder zwischen König und Turm frei sind""" 434 | king_from, king_to, rook_from, rook_to = self._get_castling_positions(color, side) 435 | 436 | if side == 'kingside': 437 | # Felder zwischen König und Turm 438 | squares = [king_from + 1, king_from + 2] 439 | else: # queenside 440 | # Felder zwischen König und Turm (inkl. b1/c1 für große Rochade) 441 | squares = [king_from - 1, king_from - 2, king_from - 3] 442 | 443 | for square in squares: 444 | if self.engine.board[square] != 0: # Nicht EMPTY 445 | return False 446 | 447 | return True 448 | 449 | def _get_castling_positions(self, color: int, side: str) -> Tuple[int, int, int, int]: 450 | """Gibt Positionen für Rochade zurück""" 451 | if color == 1: # WHITE 452 | if side == 'kingside': 453 | return 25, 27, 28, 26 # e1-g1, h1-f1 454 | else: # queenside 455 | return 25, 23, 21, 24 # e1-c1, a1-d1 456 | else: # BLACK 457 | if side == 'kingside': 458 | return 95, 97, 98, 96 # e8-g8, h8-f8 459 | else: # queenside 460 | return 95, 93, 91, 94 # e8-c8, a8-d8 461 | 462 | def _get_king_rook_start_positions(self, color: int, side: str) -> Tuple[int, int]: 463 | """Gibt Startpositionen von König und Turm zurück""" 464 | if color == 1: # WHITE 465 | king_pos = 25 # e1 466 | rook_pos = 28 if side == 'kingside' else 21 # h1 oder a1 467 | else: # BLACK 468 | king_pos = 95 # e8 469 | rook_pos = 98 if side == 'kingside' else 91 # h8 oder a8 470 | 471 | return king_pos, rook_pos 472 | 473 | def _has_castling_right(self, color: int, side: str) -> bool: 474 | """Prüft ob Rochaderecht vorhanden ist""" 475 | if color == 1: # WHITE 476 | return (self.castling_rights['white_kingside'] if side == 'kingside' 477 | else self.castling_rights['white_queenside']) 478 | else: # BLACK 479 | return (self.castling_rights['black_kingside'] if side == 'kingside' 480 | else self.castling_rights['black_queenside']) 481 | 482 | def _revoke_castling_rights(self, color: int): 483 | """Hebt Rochaderechte für eine Farbe auf""" 484 | if color == 1: # WHITE 485 | self.castling_rights['white_kingside'] = False 486 | self.castling_rights['white_queenside'] = False 487 | else: # BLACK 488 | self.castling_rights['black_kingside'] = False 489 | self.castling_rights['black_queenside'] = False 490 | 491 | def _revoke_rook_castling_rights(self, color: int, rook_pos: int): 492 | """Hebt spezifisches Rochaderecht basierend auf Turmposition auf""" 493 | if color == 1: # WHITE 494 | if rook_pos == 28: # h1 - kingside 495 | self.castling_rights['white_kingside'] = False 496 | elif rook_pos == 21: # a1 - queenside 497 | self.castling_rights['white_queenside'] = False 498 | else: # BLACK 499 | if rook_pos == 98: # h8 - kingside 500 | self.castling_rights['black_kingside'] = False 501 | elif rook_pos == 91: # a8 - queenside 502 | self.castling_rights['black_queenside'] = False 503 | 504 | def _get_castling_notation(self, color: int, side: str) -> str: 505 | """Gibt algebraische Notation für Rochade zurück""" 506 | if side == 'kingside': 507 | return 'O-O' if color == 1 else 'O-O' 508 | else: 509 | return 'O-O-O' if color == 1 else 'O-O-O' 510 | 511 | # ========================================================================= 512 | # HILFSFUNKTIONEN - EN PASSANT 513 | # ========================================================================= 514 | 515 | def _check_basic_en_passant_requirements(self, pawn: Dict[str, Any], target_pos: int) -> bool: 516 | """Prüft grundlegende en Passant Voraussetzungen - KORRIGIERT""" 517 | # Nur Bauern können en Passant 518 | if pawn['type'] != 1: 519 | return False 520 | 521 | # Ziel muss diagonal sein (aber nicht notwendigerweise besetzt) 522 | from_pos = pawn['position'] 523 | row_diff = abs((from_pos // 10) - (target_pos // 10)) 524 | col_diff = abs((from_pos % 10) - (target_pos % 10)) 525 | 526 | # Bauer muss sich diagonal bewegen (eine Reihe vor, eine Spalte seitlich) 527 | if not (row_diff == 1 and col_diff == 1): 528 | return False 529 | 530 | # Ziel muss leer sein (bei en Passant ist das Zielfeld immer leer) 531 | if self.engine.board[target_pos] != 0: 532 | return False 533 | 534 | return True 535 | 536 | def _get_opponent_pawn_position_for_en_passant(self, pawn: Dict[str, Any], target_pos: int) -> int: 537 | """Gibt Position des zu schlagenden Bauern zurück""" 538 | # Bauer steht eine Reihe hinter dem Zielfeld 539 | if pawn['color'] == 1: # WHITE 540 | return target_pos - 10 # Eine Reihe zurück 541 | else: # BLACK 542 | return target_pos + 10 # Eine Reihe vor 543 | 544 | def _validate_no_self_check_after_en_passant(self, pawn: Dict[str, Any], target_pos: int, 545 | opponent_pawn_pos: int) -> bool: 546 | """Prüft ob en Passant keinen Selbstschach verursacht""" 547 | # Zustand speichern 548 | original_board = self.engine.board.copy() 549 | original_pieces = copy.deepcopy(self.engine.pieces) 550 | 551 | # Temporären en Passant ausführen 552 | from_pos = pawn['position'] 553 | self.engine.board[from_pos] = 0 554 | self.engine.board[target_pos] = 1 * pawn['color'] 555 | pawn['position'] = target_pos 556 | 557 | # Gegnerischen Bauer schlagen 558 | opponent_pawn = self.engine.get_piece_at(opponent_pawn_pos) 559 | if opponent_pawn: 560 | self.engine.board[opponent_pawn_pos] = 0 561 | opponent_pawn['captured'] = True 562 | 563 | # Schach prüfen 564 | in_check = self.engine.is_king_in_check(pawn['color']) 565 | 566 | # Zustand wiederherstellen 567 | self.engine.board = original_board 568 | self.engine.pieces = original_pieces 569 | 570 | return not in_check 571 | 572 | # ========================================================================= 573 | # HILFSFUNKTIONEN - BAUERNUMWANDLUNG 574 | # ========================================================================= 575 | 576 | def _can_pawn_promote(self, pawn: Dict[str, Any]) -> bool: 577 | """Prüft ob Bauer umwandeln kann""" 578 | if pawn['type'] != 1: # Nur Bauern 579 | return False 580 | 581 | position = pawn['position'] 582 | row = position // 10 583 | 584 | # Weißer Bauer auf 8. Reihe oder schwarzer Bauer auf 1. Reihe 585 | return (pawn['color'] == 1 and row == 9) or (pawn['color'] == -1 and row == 2) 586 | 587 | def _get_promotion_piece_type(self, promotion_piece: str) -> Optional[int]: 588 | """Konvertiert Figurenname zu Typ""" 589 | piece_types = { 590 | 'queen': 9, 591 | 'rook': 5, 592 | 'bishop': 3, 593 | 'knight': 4 594 | } 595 | return piece_types.get(promotion_piece.lower()) 596 | 597 | # ========================================================================= 598 | # ALLGEMEINE HILFSFUNKTIONEN 599 | # ========================================================================= 600 | 601 | def _position_to_notation(self, position: int) -> str: 602 | """Konvertiert interne Position zu algebraischer Notation""" 603 | files = ['', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', ''] 604 | row = position // 10 605 | file = position % 10 606 | return f"{files[file]}{row - 1}" 607 | 608 | 609 | # Test der Schachregeln 610 | if __name__ == "__main__": 611 | print("Testing ChessRules...") 612 | 613 | from core import ChesstegEngine 614 | engine = ChesstegEngine() 615 | rules = ChessRules(engine) 616 | 617 | # Test: Rochade-Rechte 618 | print("Castling rights initialized:", rules.castling_rights) 619 | 620 | # Test: En Passant 621 | print("En passant target:", rules.en_passant_target) 622 | 623 | # Test: Umwandlungsfiguren 624 | print("Available promotion pieces:", rules.get_available_promotion_pieces()) 625 | 626 | print("ChessRules test completed!") --------------------------------------------------------------------------------