Inhalt

Aktueller Ordner: chessteg_modular/gui
⬅ Übergeordnet

chess_board.py

"""
Chess Board Module - KORRIGIERTE VERSION (Behebt GUI-Aktualisierungsproblem)
"""

import tkinter as tk
from tkinter import ttk, messagebox
import tkinter.simpledialog

# KORREKTUR: Korrekter Import über das 'engine.core' Paket
from engine.core import ChesstegEngine, WHITE, BLACK, EMPTY, PAWN, ROOK, BISHOP, KNIGHT, QUEEN, KING, DUMMY
from engine.core import PIECE_SYMBOLS # Annahme, dass PIECE_SYMBOLS aus core importiert wird


class ChessBoard(tk.Frame):
    def __init__(self, parent, engine, gui, cell_size=60):
        super().__init__(parent)

        self.cell_size = cell_size
        self.board_size = cell_size * 8
        self.engine = engine
        self.gui = gui

        self.selected_cell = None
        self.from_pos = None
        self.possible_moves = []
        self.editor_mode = False

        # Farben
        self.white_color = '#f0d9b5'
        self.black_color = '#b58863'
        self.highlight_color = '#ffeb3b'
        self.possible_move_color = '#90ee90'
        self.possible_capture_color = '#ffb6c1'

        # Figuren-Symbole
        self.piece_symbols = PIECE_SYMBOLS

        # Canvas
        self.canvas = tk.Canvas(self, width=self.board_size, height=self.board_size, bg='white')
        self.canvas.pack(padx=5, pady=5)
        self.canvas.bind("<Button-1>", self.on_square_click)

        # 🚨 KORREKTUR: Initialisiere das Board sofort
        self.draw_board()
        self.draw_pieces()

    # =========================================================================
    # ZEICHNEN
    # =========================================================================

    def draw_board(self):
        """Zeichnet das 8x8 Schachbrett"""
        self.canvas.delete("square")
        self.canvas.delete("coord")  # 🚨 KORREKTUR: Lösche auch Koordinaten
        
        for row in range(8):
            for col in range(8):
                # Wechsle die Farbe für Schachbrettmuster
                color = self.white_color if (row + col) % 2 == 0 else self.black_color

                x1 = col * self.cell_size
                y1 = row * self.cell_size
                x2 = x1 + self.cell_size
                y2 = y1 + self.cell_size

                # Speichere die interne 10x10-Position im Tag
                engine_row = 9 - row
                engine_col = col + 1
                pos_10x10 = engine_row * 10 + engine_col

                self.canvas.create_rectangle(x1, y1, x2, y2, fill=color, tags=("square", f"cell_{pos_10x10}"))

        # Zeichne Achsenbeschriftungen
        self._draw_coordinates()

    def _draw_coordinates(self):
        """Zeichnet die algebraischen Koordinaten (a1, b1, ...)"""
        font = ("Arial", 10)
        offset = 5 # Abstand zum Rand

        # Dateien (a-h)
        files = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
        for i, file in enumerate(files):
            x = i * self.cell_size + self.cell_size // 2
            # Unten
            self.canvas.create_text(x, self.board_size - offset, text=file, tags="coord", fill="black", anchor=tk.S, font=font)
            # Oben
            self.canvas.create_text(x, offset, text=file, tags="coord", fill="black", anchor=tk.N, font=font)

        # Reihen (1-8)
        for i in range(8):
            rank = str(i + 1)
            y = (7 - i) * self.cell_size + self.cell_size // 2
            # Links
            self.canvas.create_text(offset, y, text=rank, tags="coord", fill="black", anchor=tk.W, font=font)
            # Rechts
            self.canvas.create_text(self.board_size - offset, y, text=rank, tags="coord", fill="black", anchor=tk.E, font=font)

    def draw_pieces(self):
        """Zeichnet alle Figuren auf das Brett."""
        self.canvas.delete("piece") # 🚨 KORREKTUR: Entferne NUR Figuren, nicht das ganze Board
        
        # Debug-Ausgabe zur Überprüfung
        piece_count = 0
        
        # Iteriere durch alle Figuren der Engine
        for piece in self.engine.pieces:
            # Überspringe geschlagene Figuren und Figuren außerhalb des Spielfeldes
            if piece.get('captured', False) or not self.engine.is_valid_position(piece['position']):
                continue

            # Die interne 10x10 Position
            pos = piece['position']

            # 10x10 zu 8x8 Koordinaten
            row = pos // 10
            gui_row = 9 - row 
            col = pos % 10
            gui_col = col - 1

            # Position auf dem Canvas
            x = gui_col * self.cell_size + self.cell_size // 2
            y = gui_row * self.cell_size + self.cell_size // 2

            # Figur zeichnen
            symbol = piece.get('symbol', '?')
            fill_color = "black" if piece['color'] == BLACK else "black"  # 🚨 KORREKTUR: Immer schwarz für bessere Sichtbarkeit
            
            self.canvas.create_text(
                x, y,
                text=symbol,
                font=("Arial", int(self.cell_size * 0.6), "bold"),  # 🚨 KORREKTUR: Kleinere Schrift mit Bold
                fill=fill_color,
                tags=("piece", f"piece_{piece['id']}", f"cell_{pos}")
            )
            piece_count += 1
        
        # 🚨 DEBUG: Überprüfe ob Figuren gezeichnet werden
        if piece_count == 0:
            print("⚠️  WARNUNG: Keine Figuren auf dem Brett gezeichnet!")
        else:
            print(f"✅ {piece_count} Figuren auf dem Brett gezeichnet")
            
    # =========================================================================
    # EREIGNISBEHANDLUNG
    # =========================================================================

    def on_square_click(self, event):
        """Behandelt Mausklicks auf dem Schachbrett."""
        # 🚨 KORREKTUR: Prüfe ob KI denkt - blockiere Benutzerinteraktion
        if self.gui.ai_thinking:
            messagebox.showinfo("KI denkt", "Die KI berechnet einen Zug. Bitte warten...")
            return
            
        # Finde die 8x8 Koordinate
        col = event.x // self.cell_size
        row = event.y // self.cell_size

        # Konvertiere zu 10x10 Engine-Position
        engine_row = 9 - row
        engine_col = col + 1
        clicked_pos = engine_row * 10 + engine_col
        
        if not self.engine.is_valid_position(clicked_pos):
            return
            
        print(f"🎯 Klick auf Position: {self._position_to_notation(clicked_pos)}")  # 🚨 DEBUG
        
        # Wenn der Editor-Modus aktiv ist
        if self.editor_mode:
            self._handle_editor_click(clicked_pos)
        else:
            self._handle_game_click(clicked_pos)


    def _handle_editor_click(self, clicked_pos):
        """Behandelt Klicks im Editor-Modus."""
        # Wähle die zu platzierende Figur
        piece_type = self.gui.selected_editor_piece['type']
        color = self.gui.selected_editor_piece['color']
        
        # Leere Feld (EMPTY) oder Klick auf dieselbe Figur entfernt
        piece_at_pos = self.engine.get_piece_at(clicked_pos)
        is_same_piece = piece_at_pos and piece_at_pos['type'] == piece_type and piece_at_pos['color'] == color

        if piece_type == EMPTY or is_same_piece:
            # Figur entfernen
            self.engine.editor_remove_piece(clicked_pos)
        else:
            # Figur platzieren
            self.engine.editor_place_piece(piece_type, color, clicked_pos)
        
        self.update_display()
        self.gui.update_status()

    def _handle_game_click(self, clicked_pos):
        """Behandelt Klicks im Spielmodus (Zugausführung)."""
        # 🚨 KORREKTUR: Prüfe ob KI denkt
        if self.gui.ai_thinking:
            messagebox.showinfo("KI denkt", "Die KI berechnet einen Zug. Bitte warten...")
            return
            
        piece_at_pos = self.engine.get_piece_at(clicked_pos)
        current_turn_color = WHITE if self.engine.white_turn else BLACK
        
        # Prüfe ob Spieler am Zug ist (nicht KI)
        if self.gui.game_mode == 'human_vs_ai' and current_turn_color == BLACK:
            messagebox.showinfo("KI am Zug", "Die KI ist am Zug. Bitte warten...")
            return
            
        print(f"🎮 Spielzug: Position {self._position_to_notation(clicked_pos)}, Figur: {piece_at_pos['type'] if piece_at_pos else 'leer'}")  # 🚨 DEBUG
            
        # 1. ERSTER KLICK: Auswahl der eigenen Figur
        if not self.from_pos:
            if piece_at_pos and piece_at_pos['color'] == current_turn_color:
                self.from_pos = clicked_pos
                self.selected_cell = f"cell_{clicked_pos}"
                self.highlight_selection(self.selected_cell)
                self.possible_moves = self._get_possible_moves_for_pos(self.from_pos)
                self.highlight_possible_moves()
                print(f"✅ Figur ausgewählt: {self._position_to_notation(clicked_pos)}")
            else:
                print(f"❌ Keine eigene Figur auf {self._position_to_notation(clicked_pos)}")
            
        # 2. ZWEITER KLICK: Zugausführung oder neue Auswahl
        elif self.from_pos:
            # Wenn auf die gleiche Figur geklickt wird (Abwahl)
            if clicked_pos == self.from_pos:
                self.clear_selection()
                print("✅ Auswahl aufgehoben")
            # Wenn auf eine andere eigene Figur geklickt wird (Neue Auswahl)
            elif piece_at_pos and piece_at_pos['color'] == current_turn_color:
                self.clear_selection()
                self.from_pos = clicked_pos
                self.selected_cell = f"cell_{clicked_pos}"
                self.highlight_selection(self.selected_cell)
                self.possible_moves = self._get_possible_moves_for_pos(self.from_pos)
                self.highlight_possible_moves()
                print(f"✅ Neue Figur ausgewählt: {self._position_to_notation(clicked_pos)}")
            # Wenn auf ein Zielfeld geklickt wird (Zugversuch)
            else:
                self.to_pos = clicked_pos
                move = self._find_move_in_list(self.from_pos, self.to_pos)
                
                if move:
                    print(f"✅ Zug gefunden: {self._position_to_notation(self.from_pos)} -> {self._position_to_notation(self.to_pos)}")
                    if self._handle_special_move_prompt(move):
                        self._execute_move(move)
                    else:
                        print("❌ Spezieller Zug abgebrochen")
                else:
                    print(f"❌ Kein legaler Zug von {self._position_to_notation(self.from_pos)} nach {self._position_to_notation(self.to_pos)}")
                    messagebox.showwarning("Ungültiger Zug", "Dieser Zug ist nicht erlaubt.")
                
                self.clear_selection()

    def _handle_special_move_prompt(self, move: dict) -> bool:
        """Behandelt spezielle Züge wie Bauernumwandlung."""
        if move.get('special_type') == 'promotion':
            choice = tk.simpledialog.askstring("Bauernumwandlung", "Wähle eine Figur (Dame, Turm, Läufer, Springer):", parent=self)
            
            if choice:
                # Annahme: engine.rules._get_promotion_piece_type existiert und ist korrekt
                promotion_type = self.engine.rules._get_promotion_piece_type(choice) 
                if promotion_type:
                    move['promotion_type'] = promotion_type
                    return True
                else:
                    messagebox.showerror("Fehler", "Ungültige Figur gewählt. Standard: Dame.")
                    move['promotion_type'] = QUEEN # Standard: Dame
                    return True
            else:
                return False # Abbruch der Umwandlung
        
        return True

    def _execute_move(self, move):
        """Führt den Zug aus und aktualisiert die Anzeige."""
        print(f"🔧 Führe Zug aus: {self._position_to_notation(move['from_pos'])} -> {self._position_to_notation(move['to_pos'])}")
        
        if self.engine.make_move(move):
            print("✅ Zug erfolgreich ausgeführt")
            self.update_display()
            self.gui.update_status()

            # Prüfe, ob die KI an der Reihe ist (nur im Mensch vs KI Modus)
            self.gui.check_ai_turn()
        else:
            print("❌ Zug fehlgeschlagen")
            messagebox.showwarning("Illegaler Zug", "Dieser Zug ist nicht erlaubt.")

    # =========================================================================
    # ZUG-HILFSFUNKTIONEN
    # =========================================================================

    def _get_possible_moves_for_pos(self, from_pos):
        """Filtert legale Züge nach Startposition - KORRIGIERTE VERSION"""
        color = WHITE if self.engine.white_turn else BLACK
        all_legal_moves = self.engine.generate_all_moves(color) 
        moves_for_pos = [move for move in all_legal_moves if move['from_pos'] == from_pos]
        
        # 🚨 DEBUG: Zeige alle gefundenen Züge
        print(f"🔍 {len(moves_for_pos)} mögliche Züge für Position {self._position_to_notation(from_pos)}")
        for move in moves_for_pos:
            move_type = "Normal"
            if move.get('special_type') == 'castling':
                move_type = "Rochade"
            elif move.get('special_type') == 'en_passant':
                move_type = "En Passant" 
            elif move.get('promotion_piece'):
                move_type = f"Umwandlung zu {move['promotion_piece']}"
            print(f"   - {self._position_to_notation(move['from_pos'])} -> {self._position_to_notation(move['to_pos'])} [{move_type}]")
        
        return moves_for_pos

    def _find_move_in_list(self, from_pos, to_pos):
        """Findet den entsprechenden Zug in der Liste der möglichen Züge."""
        for move in self.possible_moves:
            if move['to_pos'] == to_pos:
                return move
        return None

    # =========================================================================
    # MARKIERUNG (HIGHLIGHTING)
    # =========================================================================

    def clear_highlights(self):
        """Entfernt alle Markierungen vom Brett."""
        self.canvas.delete("highlight")
        self.canvas.delete("possible_move")
        # 🚨 KORREKTUR: Zeichne das Brett NICHT neu, da dies die Figuren löscht
        # Stattdessen setze nur die Umrandungen zurück
        highlighted_items = self.canvas.find_withtag("square")
        for item in highlighted_items:
            if self.canvas.type(item) == "rectangle":
                # Setze Umrandung auf Standard (keine Umrandung)
                self.canvas.itemconfig(item, outline="", width=1)

    def highlight_selection(self, tag):
        """Markiert die ausgewählte Zelle (Rechteck)."""
        self.clear_highlights()  # 🚨 KORREKTUR: Entferne zuerst alle Highlights
        
        self.canvas.addtag_withtag("highlight", tag)
        self.canvas.tag_raise("highlight") # Highlights über Figuren zeichnen
        
        # KORREKTUR: Finde alle Rechtecke mit dem Highlight-Tag und konfiguriere sie
        highlighted_items = self.canvas.find_withtag("highlight")
        for item in highlighted_items:
            # Prüfe ob es sich um ein Rechteck handelt
            if self.canvas.type(item) == "rectangle":
                self.canvas.itemconfig(item, outline=self.highlight_color, width=3)

    def highlight_possible_moves(self):
        """Markiert mögliche Zielfelder."""
        for move in self.possible_moves:
            to_pos = move['to_pos']
            tag = f"cell_{to_pos}"

            piece_at_target = self.engine.get_piece_at(to_pos)

            if piece_at_target:
                # Markierung für Schlag (Kreis)
                color = self.possible_capture_color
                self._draw_circle_highlight(to_pos, color)
            else:
                # Markierung für normalen Zug (Punkt)
                color = self.possible_move_color
                self._draw_circle_highlight(to_pos, color)
            
            # Markierung für das Feld selbst (Rückseite)
            self.canvas.addtag_withtag("possible_move", tag)
            self.canvas.tag_lower("possible_move") # Unter die Figuren/Kreise

    def _draw_circle_highlight(self, pos_10x10, color):
        """Zeichnet einen Kreis auf einem Zielfeld."""
        # Konvertiere 10x10-Position zu Canvas-Koordinaten
        row = pos_10x10 // 10
        col = pos_10x10 % 10
        gui_row = 9 - row
        gui_col = col - 1

        x = gui_col * self.cell_size + self.cell_size // 2
        y = gui_row * self.cell_size + self.cell_size // 2
        radius = self.cell_size * 0.15  # 🚨 KORREKTUR: Kleinere Kreise

        # Zeichne Kreis
        self.canvas.create_oval(
            x - radius, y - radius, 
            x + radius, y + radius, 
            fill=color, 
            outline="black",
            width=1,
            tags=("possible_move")
        )

    # =========================================================================
    # HILFSFUNKTIONEN
    # =========================================================================
    
    def _position_to_notation(self, position: int) -> str:
        """Konvertiert interne Position zu algebraischer Notation"""
        files = ['', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', '']
        row = position // 10
        file = position % 10
        if 1 <= file <= 8 and 2 <= row <= 9:
            return f"{files[file]}{row - 1}"
        return "??"

    # =========================================================================
    # ZUSAMMENFASSUNG / EDITOR-FUNKTIONEN
    # =========================================================================

    def enable_editor_mode(self):
        """Aktiviert den Editor-Modus"""
        self.editor_mode = True
        self.canvas.config(cursor="hand2")
        self.clear_selection()

    def disable_editor_mode(self):
        """Deaktiviert den Editor-Modus"""
        self.editor_mode = False
        self.canvas.config(cursor="")
        self.clear_selection()

    def flip_board(self):
        """Dreht das Brett um (visuell)"""
        self.update_display()

    def clear_selection(self):
        """Setzt die Auswahl zurück"""
        self.from_pos = None
        self.to_pos = None
        self.selected_cell = None
        self.possible_moves = []
        self.clear_highlights()

    def update_display(self):
        """Aktualisiert die Anzeige - 🚨 KORREKTUR: Vereinfachte Methode"""
        print("🔄 Aktualisiere Brett-Anzeige...")
        
        # 🚨 KORREKTUR: Zeichne zuerst das Brett, dann die Figuren
        self.draw_board()
        self.draw_pieces()
        
        # 🚨 KORREKTUR: Erzwinge Canvas-Update
        self.canvas.update_idletasks()
        
        print("✅ Brett-Anzeige aktualisiert")