Inhalt
Aktueller Ordner:
chessteg_modular/guichess_gui.py
"""
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")