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