Inhalt

Aktueller Ordner: /

chess_gui.py

import tkinter as tk
from tkinter import ttk, messagebox
import threading
import time
from chessteg import ChesstegEngine, WHITE, BLACK, EMPTY, PAWN, ROOK, BISHOP, KNIGHT, QUEEN, KING, DUMMY  # DUMMY hinzufügen

class ChessBoard(tk.Canvas):
    def __init__(self, parent, engine, gui, cell_size=60):
        self.cell_size = cell_size
        self.board_size = cell_size * 8
        super().__init__(parent, width=self.board_size, height=self.board_size, 
                        bg='white', highlightthickness=2, highlightbackground='black')
        
        self.engine = engine
        self.gui = gui  # Referenz zur Haupt-GUI
        
        self.selected_cell = None
        self.from_pos = None
        self.possible_moves = []
        
        # 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 (Unicode) - KORRIGIERTE DARSTELLUNG
        self.piece_symbols = {
            'wP': '♙', 'wR': '♖', 'wB': '♗', 'wN': '♘', 'wQ': '♕', 'wK': '♔',
            'bP': '♟', 'bR': '♜', 'bB': '♝', 'bN': '♞', 'bQ': '♛', 'bK': '♚'
        }

        
        self.bind('<Button-1>', self.on_click)
        self.draw_board()
        self.draw_pieces()
    
    def get_piece_code(self, piece_value):
        if piece_value == EMPTY:
            return None
            
        color = 'w' if piece_value > 0 else 'b'  # Weiß = positive Werte
        piece_type = abs(piece_value)
        
        types = {
            PAWN: 'P',
            ROOK: 'R', 
            BISHOP: 'B',
            KNIGHT: 'N',
            QUEEN: 'Q',
            KING: 'K'
        }
        
        return color + types.get(piece_type, '?')
    
    def position_to_coords(self, position):
        """Konvertiert interne Position zu Brett-Koordinaten"""
        row = position // 10
        col = position % 10
        # Umrechnung für UI-Darstellung
        ui_row = 8 - (row - 1)  # Von unten nach oben
        ui_col = col - 1         # Von links nach rechts
        return ui_row, ui_col
    
    def screen_to_board_pos(self, x, y):
        """Konvertiert Bildschirm-Koordinaten zu Brett-Position"""
        col = x // self.cell_size
        row = y // self.cell_size
        
        # Umrechnung in interne Position
        board_row = 8 - row
        board_col = col + 1
        position = (board_row + 1) * 10 + board_col
        
        return position
    
    def draw_board(self):
        """Zeichnet das Schachbrett"""
        self.delete("all")
        
        # Zeichne Felder
        for row in range(8):
            for col in range(8):
                x1 = col * self.cell_size
                y1 = row * self.cell_size
                x2 = x1 + self.cell_size
                y2 = y1 + self.cell_size
                
                color = self.white_color if (row + col) % 2 == 0 else self.black_color
                self.create_rectangle(x1, y1, x2, y2, fill=color, outline='')
        
        # Zeichne Koordinaten
        files = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
        for i, file in enumerate(files):
            x = i * self.cell_size + self.cell_size // 2
            self.create_text(x, self.board_size + 15, text=file, font=('Arial', 10))
            
        for i in range(8):
            y = i * self.cell_size + self.cell_size // 2
            self.create_text(-15, y, text=str(8 - i), font=('Arial', 10))
    
    def draw_pieces(self):
        """Zeichnet die Schachfiguren"""
        self.delete('piece')  # Alte Figuren entfernen
    
        for piece in self.engine.pieces:
            if piece['captured']:
                continue
            
            row, col = self.position_to_coords(piece['pos'])
            x = col * self.cell_size + self.cell_size // 2
            y = row * self.cell_size + self.cell_size // 2
        
            piece_code = self.get_piece_code(piece['type'] * piece['color'])
            if piece_code in self.piece_symbols:
                symbol = self.piece_symbols[piece_code]
                # KORREKTUR: Richtige Farbzuordnung
                # Weiße Figuren (WHITE = 1) sollen weißen Text haben
                # Schwarze Figuren (BLACK = -1) sollen schwarzen Text haben
                fill_color = 'white' if piece['color'] == WHITE else 'black'
                self.create_text(x, y, text=symbol, font=('Arial', 32), 
                               fill=fill_color, tags='piece')
    
    def highlight_cell(self, position, color):
        """Hervorhebung einer Zelle"""
        row, col = self.position_to_coords(position)
        x1 = col * self.cell_size
        y1 = row * self.cell_size
        x2 = x1 + self.cell_size
        y2 = y1 + self.cell_size
        
        self.create_rectangle(x1, y1, x2, y2, fill=color, outline='', tags='highlight')
        # Stelle sicher, dass die Hervorhebung unter den Figuren liegt
        self.tag_lower('highlight')
    
    def show_possible_moves(self, from_position):
        """Zeigt mögliche Züge für eine Figur an"""
        piece = next((p for p in self.engine.pieces 
                     if p['pos'] == from_position and not p['captured']), None)
        
        if not piece:
            return
        
        self.possible_moves = [move for move in self.engine.generate_moves(piece['color']) 
                              if move['from_pos'] == from_position]
        
        for move in self.possible_moves:
            if move['captured']:
                self.highlight_cell(move['to_pos'], self.possible_capture_color)
            else:
                self.highlight_cell(move['to_pos'], self.possible_move_color)
        
        # Hervorhebung der ausgewählten Figur
        self.highlight_cell(from_position, self.highlight_color)
    
    def clear_highlights(self):
        """Entfernt alle Hervorhebungen"""
        self.delete('highlight')
        self.possible_moves = []
    
    def on_click(self, event):
        """Behandelt Mausklicks auf dem Brett"""
        board_pos = self.screen_to_board_pos(event.x, event.y)
        
        # Prüfe Spielmodus-Beschränkungen
        if self.gui.game_mode == 'ai_vs_ai':
            messagebox.showinfo("Info", "Im KI vs KI Modus sind keine manuellen Züge möglich")
            return
            
        if self.gui.game_mode == 'human_vs_ai':
            human_color = WHITE  # Annahme: Mensch spielt Weiß
            current_color = WHITE if self.engine.white_turn else BLACK
            if current_color != human_color:
                messagebox.showinfo("Info", "Computer ist am Zug - bitte warten")
                return
        
        # Zurücksetzen der visuellen Auswahl
        self.clear_highlights()
        
        # Prüfe ob auf eine eigene Figur geklickt wurde
        piece_value = self.engine.board[board_pos]
        is_own_piece = ((self.engine.white_turn and piece_value > 0) or 
                       (not self.engine.white_turn and piece_value < 0))
        
        # KORREKTUR: DUMMY wurde importiert
        if is_own_piece and piece_value != EMPTY and piece_value != DUMMY:
            # Startfeld ausgewählt
            self.selected_cell = board_pos
            self.from_pos = board_pos
            
            # Mögliche Züge anzeigen
            self.show_possible_moves(board_pos)
        else:
            # Zielfeld ausgewählt
            self.to_pos = board_pos
            
            # Automatisch ausführen wenn beide Felder ausgefüllt sind
            if self.from_pos and self.to_pos:
                # Finde den entsprechenden Zug
                piece = next((p for p in self.engine.pieces 
                            if p['pos'] == self.from_pos and not p['captured']), None)
                
                if piece:
                    possible_moves = self.engine.generate_moves(piece['color'])
                    valid_moves = [move for move in possible_moves 
                                 if move['from_pos'] == self.from_pos and move['to_pos'] == self.to_pos]
                    
                    if valid_moves:
                        move = valid_moves[0]
                        if self.engine.execute_move(move):
                            self.gui.update_display()
                            # Automatischer Computerzug
                            self.gui.automatic_computer_move()
                        else:
                            messagebox.showerror("Fehler", "Ungültiger Zug!")
                    else:
                        messagebox.showerror("Fehler", "Ungültiger Zug für diese Figur!")
                else:
                    messagebox.showerror("Fehler", "Keine Figur auf dem Startfeld!")
                
                self.clear_selection()
    
    def clear_selection(self):
        """Setzt die Auswahl zurück"""
        self.from_pos = None
        self.to_pos = None
        self.selected_cell = None
        self.clear_highlights()
    
    def update_display(self):
        """Aktualisiert die Anzeige"""
        self.draw_board()
        self.draw_pieces()

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'
        self.ai_thinking = False
        
        self.create_widgets()
        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, fill=tk.BOTH, expand=True)
        
        self.chess_board = ChessBoard(left_frame, self.engine, self)
        self.chess_board.pack(pady=10)
        
        # Rechte Seite: Steuerung
        right_frame = ttk.Frame(main_frame, width=250)
        right_frame.pack(side=tk.RIGHT, fill=tk.Y, padx=(10, 0))
        right_frame.pack_propagate(False)
        
        # Titel
        title_label = ttk.Label(right_frame, text="Chessteg Schach", 
                               font=('Arial', 16, 'bold'))
        title_label.pack(pady=(0, 20))
        
        # Status-Anzeige
        self.status_frame = ttk.LabelFrame(right_frame, text="Spielstatus", padding=10)
        self.status_frame.pack(fill=tk.X, pady=(0, 10))
        
        self.turn_label = ttk.Label(self.status_frame, text="Weiß am Zug")
        self.turn_label.pack(anchor=tk.W)
        
        self.eval_label = ttk.Label(self.status_frame, text="Bewertung: 0")
        self.eval_label.pack(anchor=tk.W)
        
        self.status_label = ttk.Label(self.status_frame, text="Status: Läuft")
        self.status_label.pack(anchor=tk.W)
        
        # Spielmodus
        mode_frame = ttk.LabelFrame(right_frame, text="Spielmodus", padding=10)
        mode_frame.pack(fill=tk.X, pady=(0, 10))
        
        self.mode_label = ttk.Label(mode_frame, text="Modus: Mensch vs Mensch")
        self.mode_label.pack(anchor=tk.W)
        
        ttk.Button(mode_frame, text="Computerzug", 
                  command=self.computer_move).pack(fill=tk.X, pady=2)
        ttk.Button(mode_frame, text="Spieler 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)
        
        # Steuerung
        control_frame = ttk.LabelFrame(right_frame, text="Steuerung", padding=10)
        control_frame.pack(fill=tk.X, pady=(0, 10))
        
        ttk.Button(control_frame, text="Neues Spiel", 
                  command=self.new_game).pack(fill=tk.X, pady=2)
        ttk.Button(control_frame, text="Reset", 
                  command=self.reset_game).pack(fill=tk.X, pady=2)
        ttk.Button(control_frame, text="Zug zurück", 
                  command=self.undo_move).pack(fill=tk.X, pady=2)
        
        # KI-Status
        self.thinking_label = ttk.Label(right_frame, text="", foreground='red')
        self.thinking_label.pack(pady=5)
    
    def get_mode_name(self):
        names = {
            'human_vs_human': 'Mensch vs Mensch',
            'human_vs_ai': 'Mensch vs KI', 
            'ai_vs_ai': 'KI vs KI'
        }
        return names.get(self.game_mode, 'Unbekannt')
    
    def update_status(self):
        """Aktualisiert die Status-Anzeige"""
        turn_text = "Weiß am Zug" if self.engine.white_turn else "Schwarz am Zug"
        self.turn_label.config(text=turn_text)
        
        self.eval_label.config(text=f"Bewertung: {self.engine.evaluation}")
        
        if self.engine.checkmate:
            status_text = "SCHACHMATT!"
        elif self.engine.stalemate:
            status_text = "Patt!"
        elif self.engine.is_king_in_check(WHITE if self.engine.white_turn else BLACK):
            status_text = "SCHACH!"
        else:
            status_text = "Läuft"
        self.status_label.config(text=f"Status: {status_text}")
        
        self.mode_label.config(text=f"Modus: {self.get_mode_name()}")
        
        if self.ai_thinking:
            self.thinking_label.config(text="KI denkt nach...")
        else:
            self.thinking_label.config(text="")
    
    def computer_move(self):
        """Führt einen Computerzug aus"""
        if self.ai_thinking:
            return
            
        self.ai_thinking = True
        self.update_status()
        
        def ai_thread():
            best_move = self.engine.computer_move()
            if best_move:
                self.engine.execute_move(best_move)
            
            self.ai_thinking = False
            self.root.after(0, self.update_display)
        
        # Starte KI-Berechnung in separatem Thread
        thread = threading.Thread(target=ai_thread)
        thread.daemon = True
        thread.start()
    
    def automatic_computer_move(self):
        """Startet automatischen Computerzug nach Spielerzug"""
        if self.game_mode in ['human_vs_ai', 'ai_vs_ai']:
            current_color = WHITE if self.engine.white_turn else BLACK
            
            # Prüfe ob Computer am Zug ist
            if ((self.game_mode == 'human_vs_ai' and current_color == BLACK) or 
                self.game_mode == 'ai_vs_ai'):
                
                # Kurze Verzögerung
                self.root.after(1000, self.computer_move)
    
    def set_game_mode(self, mode):
        """Setzt den Spielmodus"""
        self.game_mode = mode
        self.update_status()
        
        if mode == 'ai_vs_ai':
            self.start_ai_vs_ai()
    
    def start_ai_vs_ai(self):
        """Startet KI vs KI Modus"""
        if self.game_mode == 'ai_vs_ai' and not self.ai_thinking:
            self.computer_move()
    
    def new_game(self):
        """Startet ein neues Spiel"""
        self.engine = ChesstegEngine()
        self.chess_board.engine = self.engine
        self.chess_board.clear_selection()
        self.game_mode = 'human_vs_human'
        self.update_display()
    
    def reset_game(self):
        """Setzt das Spiel zurück"""
        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()
    
    def undo_move(self):
        """Macht den letzten Zug rückgängig"""
        if self.engine.undo_move():
            self.update_display()
    
    def update_display(self):
        """Aktualisiert die gesamte Anzeige"""
        self.chess_board.update_display()
        self.update_status()
    
    def run(self):
        """Startet die Hauptschleife"""
        self.root.mainloop()

if __name__ == "__main__":
    gui = ChessGUI()
    gui.run()