""" Chessteg GUI Module - KORRIGIERTE VERSION (Behebt KI-Probleme) """ import tkinter as tk from tkinter import ttk, messagebox import threading import time # KORREKTUR: Korrekter Import über das 'engine' Paket from engine.core import ChesstegEngine, WHITE, BLACK, EMPTY, PAWN, ROOK, BISHOP, KNIGHT, QUEEN, KING, DUMMY from .chess_board import ChessBoard class ChessGUI: def __init__(self): self.root = tk.Tk() self.root.title("Chessteg - Didaktisches Schachprogramm") self.root.geometry("800x600") self.engine = ChesstegEngine() self.game_mode = 'human_vs_human' # Standard: Mensch vs Mensch self.ai_thinking = False self.editor_mode = False self.selected_editor_piece = {'type': PAWN, 'color': WHITE} # 🚨 KORREKTUR: Thread-Synchronisation self.thread_lock = threading.Lock() self.create_widgets() self.setup_editor_controls() self.update_display() def create_widgets(self): """Erstellt die GUI-Elemente""" # Haupt-Frame main_frame = ttk.Frame(self.root) main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) # Linke Seite: Schachbrett left_frame = ttk.Frame(main_frame) left_frame.pack(side=tk.LEFT, padx=10, pady=10) self.chess_board = ChessBoard(left_frame, self.engine, self) self.chess_board.pack() # Rechte Seite: Steuerung und Status right_frame = ttk.Frame(main_frame) right_frame.pack(side=tk.RIGHT, fill=tk.Y, padx=10, pady=10) # Status status_frame = ttk.LabelFrame(right_frame, text="Status & Bewertung") status_frame.pack(fill=tk.X, pady=5) self.status_label = ttk.Label(status_frame, text="Status: Neues Spiel") self.status_label.pack(fill=tk.X, padx=5, pady=5) self.turn_label = ttk.Label(status_frame, text="Am Zug: Weiß (Human)") self.turn_label.pack(fill=tk.X, padx=5, pady=5) # Bewertung self.eval_label = ttk.Label(status_frame, text="Bewertung: N/A") self.eval_label.pack(fill=tk.X, padx=5, pady=5) # Spielmodus-Steuerung mode_frame = ttk.LabelFrame(right_frame, text="Spielmodus") mode_frame.pack(fill=tk.X, pady=10) ttk.Button(mode_frame, text="Mensch vs Mensch", command=lambda: self.set_game_mode('human_vs_human')).pack(fill=tk.X, pady=2) ttk.Button(mode_frame, text="Mensch vs KI", command=lambda: self.set_game_mode('human_vs_ai')).pack(fill=tk.X, pady=2) ttk.Button(mode_frame, text="KI vs KI", command=lambda: self.set_game_mode('ai_vs_ai')).pack(fill=tk.X, pady=2) # Aktionen action_frame = ttk.LabelFrame(right_frame, text="Aktionen") action_frame.pack(fill=tk.X, pady=10) ttk.Button(action_frame, text="Neues Spiel", command=self.new_game).pack(fill=tk.X, pady=2) ttk.Button(action_frame, text="Zurücksetzen", command=self.reset_game).pack(fill=tk.X, pady=2) ttk.Button(action_frame, text="Zug zurück", command=self.undo_move).pack(fill=tk.X, pady=2) ttk.Button(action_frame, text="Brett drehen (visuell)", command=self.chess_board.flip_board).pack(fill=tk.X, pady=2) # Editor-Steuerung self.editor_frame = ttk.LabelFrame(right_frame, text="Editor", style='TFrame') self.editor_toggle_button = ttk.Button(right_frame, text="Editor AN/AUS", command=self.toggle_editor_mode) self.editor_toggle_button.pack(fill=tk.X, pady=10) def setup_editor_controls(self): """Erstellt die Steuerelemente für den Editor-Modus""" # Figurenauswahl piece_options_frame = ttk.Frame(self.editor_frame) piece_options_frame.pack(fill=tk.X, padx=5, pady=5) ttk.Label(piece_options_frame, text="Figur:").pack(side=tk.LEFT, padx=5) self.piece_var = tk.StringVar(self.root, 'P') pieces = ['P', 'N', 'B', 'R', 'Q', 'K', 'E'] # 'E' für Empty/Entfernen piece_menu = ttk.OptionMenu(piece_options_frame, self.piece_var, 'P', *pieces, command=self._update_selected_piece) piece_menu.pack(side=tk.LEFT, fill=tk.X, expand=True) ttk.Label(piece_options_frame, text="Farbe:").pack(side=tk.LEFT, padx=5) self.color_var = tk.StringVar(self.root, 'W') colors = ['W', 'B'] color_menu = ttk.OptionMenu(piece_options_frame, self.color_var, 'W', *colors, command=self._update_selected_piece) color_menu.pack(side=tk.LEFT, fill=tk.X, expand=True) self._update_selected_piece() # Aktionen editor_actions_frame = ttk.Frame(self.editor_frame) editor_actions_frame.pack(fill=tk.X, padx=5, pady=5) ttk.Button(editor_actions_frame, text="Brett leeren", command=self._editor_clear).pack(fill=tk.X, pady=2) ttk.Button(editor_actions_frame, text="Standardstellung", command=self._editor_standard).pack(fill=tk.X, pady=2) def _get_piece_code(self, symbol): """Hilfsfunktion, um Symbole in Engine-Typen zu übersetzen""" mapping = {'P': PAWN, 'N': KNIGHT, 'B': BISHOP, 'R': ROOK, 'Q': QUEEN, 'K': KING, 'E': EMPTY} return mapping.get(symbol, PAWN) def _get_color_code(self, symbol): """Hilfsfunktion, um Symbole in Engine-Farben zu übersetzen""" return WHITE if symbol == 'W' else BLACK def _update_selected_piece(self, *args): """Aktualisiert die Figur, die im Editor platziert werden soll.""" self.selected_editor_piece = { 'type': self._get_piece_code(self.piece_var.get()), 'color': self._get_color_code(self.color_var.get()) } def _editor_clear(self): self.engine.editor_clear_board() self.update_display() self.update_status() def _editor_standard(self): self.engine.editor_standard_position() self.update_display() self.update_status() def toggle_editor_mode(self): """Schaltet den Editor-Modus um""" self.editor_mode = not self.editor_mode if self.editor_mode: self.chess_board.enable_editor_mode() self.editor_frame.pack(fill=tk.X, pady=5) self.set_game_mode('editor') self.editor_toggle_button.config(text="Editor AN/AUS (Editor Modus aktiv)") else: self.chess_board.disable_editor_mode() self.editor_frame.pack_forget() self.set_game_mode('human_vs_human') self.editor_toggle_button.config(text="Editor AN/AUS") def set_game_mode(self, mode): """Setzt den aktuellen Spielmodus.""" self.game_mode = mode if mode != 'editor': # Verhindert, dass der Editor-Modus aktiv bleibt self.editor_mode = False self.chess_board.disable_editor_mode() self.editor_frame.pack_forget() self.editor_toggle_button.config(text="Editor AN/AUS") # Startet ggf. den KI vs KI Modus if self.game_mode == 'ai_vs_ai': self.start_ai_vs_ai() self.chess_board.clear_selection() self.update_status() self.check_ai_turn() def update_status(self): """Aktualisiert die Status-Labels.""" current_turn = "Weiß" if self.engine.white_turn else "Schwarz" current_color = WHITE if self.engine.white_turn else BLACK mode_text = f" ({self.game_mode})" if self.game_mode == 'human_vs_human': mode_text = " (Mensch vs Mensch)" elif self.game_mode == 'human_vs_ai': opponent = "KI" if current_color == BLACK else "Mensch" mode_text = f" (Mensch vs KI - {opponent} am Zug)" elif self.game_mode == 'ai_vs_ai': mode_text = " (KI vs KI)" elif self.game_mode == 'editor': mode_text = " (Editor Modus)" self.turn_label.config(text=f"Am Zug: {current_turn}{mode_text}") if self.engine.checkmate: winner = "Schwarz" if self.engine.white_turn else "Weiß" self.status_label.config(text=f"Status: Schachmatt! {winner} gewinnt.") elif self.engine.stalemate: self.status_label.config(text="Status: Patt!") elif self.engine.is_king_in_check(current_color): self.status_label.config(text="Status: Schach!") else: self.status_label.config(text="Status: Im Spiel") # KORREKTUR: Aufruf der Methode evaluate_position() im Evaluator-Objekt if self.engine.evaluator: evaluation_score = self.engine.evaluator.evaluate_position() self.eval_label.config(text=f"Bewertung: {evaluation_score:.2f}") else: self.eval_label.config(text="Bewertung: N/A") def computer_move(self): """Lässt die KI einen Zug berechnen und ausführen (in einem Thread)""" if self.ai_thinking: return self.ai_thinking = True print("🤖 KI beginnt mit Zugberechnung...") # 🚨 KORREKTUR: Thread-Safe GUI Updates def calculate_move(): try: with self.thread_lock: best_move = self.engine.search_algorithm.computer_move() # 🚨 KORREKTUR: Thread-sicheres GUI-Update if self.root and self.root.winfo_exists(): self.root.after(0, lambda: self._process_computer_move(best_move)) except Exception as e: print(f"❌ KI-Berechnungsfehler: {e}") import traceback traceback.print_exc() # 🚨 KORREKTUR: Immer sicherstellen, dass ai_thinking zurückgesetzt wird if self.root and self.root.winfo_exists(): self.root.after(0, self._reset_ai_thinking) threading.Thread(target=calculate_move, daemon=True).start() def _process_computer_move(self, best_move): """Verarbeitet das Ergebnis des KI-Zugs - KORRIGIERTE VERSION""" print("🔄 Verarbeite KI-Zug...") self.ai_thinking = False if best_move: print(f"🔧 KI möchte Zug ausführen: {self._move_to_notation(best_move)}") if self.engine.make_move(best_move): print(f"✅ KI-Zug ausgeführt: {self._move_to_notation(best_move)}") self.update_display() self.update_status() # Im KI vs KI Modus sofort nächsten Zug starten if self.game_mode == 'ai_vs_ai' and not self.engine.is_game_over(): print("🔄 KI vs KI: Starte nächsten Zug...") self.root.after(1000, self.computer_move) # 1 Sekunde Pause zwischen Zügen else: self.check_ai_turn() # Normale Prüfung für Mensch vs KI else: print("❌ KI-Zug fehlgeschlagen - Zug konnte nicht ausgeführt werden") # Im KI vs KI Modus trotzdem weitermachen if self.game_mode == 'ai_vs_ai' and not self.engine.is_game_over(): fallback_move = self._get_fallback_move() if fallback_move: print(f"🔄 Versuche Fallback-Zug: {self._move_to_notation(fallback_move)}") if self.engine.make_move(fallback_move): self.update_display() self.update_status() self.root.after(1000, self.computer_move) else: print("❌ Kein KI-Zug gefunden") # Prüfe ob Spiel vorbei ist if self.engine.checkmate: print("🏁 Schachmatt erkannt!") messagebox.showinfo("Spielende", "Schachmatt!") elif self.engine.stalemate: print("🏁 Patt erkannt!") messagebox.showinfo("Spielende", "Patt!") else: print("⚠️ KI Problem - kein Zug gefunden aber Spiel nicht beendet") # Im KI vs KI Modus trotzdem weitermachen mit Fallback if self.game_mode == 'ai_vs_ai': fallback_move = self._get_fallback_move() if fallback_move: print(f"🔄 Verwende Fallback-Zug: {self._move_to_notation(fallback_move)}") if self.engine.make_move(fallback_move): self.update_display() self.update_status() self.root.after(1000, self.computer_move) else: print("💥 Fallback-Zug auch fehlgeschlagen - KI vs KI gestoppt") else: print("💥 Kein Fallback-Zug verfügbar - KI vs KI gestoppt") def _get_fallback_move(self): """Findet einen einfachen Fallback-Zug falls die KI versagt - KORRIGIERTE VERSION""" current_color = -1 if self.engine.white_turn else 1 # Gegenteilige Farbe (KI war am Zug) moves = self.engine.generate_all_moves(current_color) if moves: # Nimm den ersten verfügbaren Zug (einfacher Algorithmus) print(f"🔄 Fallback: {len(moves)} Züge verfügbar, nehme ersten") return moves[0] else: print("💥 Fallback: Keine Züge verfügbar!") return None def _reset_ai_thinking(self): """Setzt den KI-Denkstatus zurück (Fehlerbehandlung).""" self.ai_thinking = False print("🔄 KI-Denkstatus zurückgesetzt") def check_ai_turn(self): """Prüft, ob die KI am Zug ist und startet ggf. die Berechnung.""" # 🚨 KORREKTUR: Prüfe ob KI bereits denkt if self.ai_thinking: return current_turn_color = WHITE if self.engine.white_turn else BLACK is_ai_turn = False if self.game_mode == 'human_vs_ai' and current_turn_color == BLACK: is_ai_turn = True elif self.game_mode == 'ai_vs_ai': is_ai_turn = True if is_ai_turn and not self.engine.checkmate and not self.engine.stalemate: # 🚨 KORREKTUR: Verzögerung für GUI-Responsiveness print(f"🤖 KI ist am Zug (Farbe: {'Schwarz' if current_turn_color == BLACK else 'Weiß'})") self.root.after(500, self.computer_move) def start_ai_vs_ai(self): """Startet KI vs KI Modus - KORRIGIERTE VERSION""" if self.game_mode == 'ai_vs_ai' and not self.ai_thinking: print("🎮 Starte KI vs KI Modus...") # Kurze Verzögerung für bessere GUI-Responsiveness self.root.after(2000, self.computer_move) def new_game(self): """Startet ein neues Spiel""" # 🚨 KORREKTUR: Thread-Safe Engine-Reset with self.thread_lock: self.engine = ChesstegEngine() self.chess_board.engine = self.engine self.chess_board.clear_selection() self.game_mode = 'human_vs_human' self.update_display() print("🔄 Neues Spiel gestartet") def reset_game(self): """Setzt das Spiel zurück""" # 🚨 KORREKTUR: Thread-Safe Reset with self.thread_lock: self.engine.initialize_pieces() self.engine.white_turn = True self.engine.checkmate = False self.engine.stalemate = False self.chess_board.clear_selection() self.game_mode = 'human_vs_human' self.update_display() print("🔄 Spiel zurückgesetzt") def undo_move(self): """Macht den letzten Zug rückgängig""" # 🚨 KORREKTUR: Thread-Safe Undo with self.thread_lock: if self.engine.undo_move(): self.update_display() self.update_status() # Status nach Undo aktualisieren print("↩️ Zug rückgängig gemacht") def update_display(self): """Aktualisiert die gesamte Anzeige""" try: self.chess_board.update_display() self.update_status() # 🚨 KORREKTUR: Explizites Update des Hauptfensters self.root.update_idletasks() except Exception as e: print(f"❌ Fehler beim GUI-Update: {e}") def _move_to_notation(self, move): """Konvertiert Zug zu algebraischer Notation""" files = ['', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', ''] from_pos = move['from_pos'] to_pos = move['to_pos'] from_file = files[from_pos % 10] from_rank = str(from_pos // 10 - 1) to_file = files[to_pos % 10] to_rank = str(to_pos // 10 - 1) return f"{from_file}{from_rank}{to_file}{to_rank}" def run(self): """Startet die Tkinter-Hauptschleife""" try: print("🎮 Chessteg GUI gestartet") self.root.mainloop() except KeyboardInterrupt: print("Programm durch Benutzer beendet") except Exception as e: print(f"Unerwarteter Fehler: {e}") finally: # 🚨 KORREKTUR: Sauberes Beenden if hasattr(self, 'root') and self.root: self.root.quit() print("👋 Chessteg beendet")