Skip to content

Commit 8541edb

Browse files
Copilotfsmosca
andauthored
Implement drag and drop of pieces (#62)
* Implement drag and drop of pieces on the chess board Add drag-and-drop support alongside the existing two-click piece movement. Uses tkinter ButtonPress/ButtonRelease bindings on each board square button to detect when a piece is dragged from one square to another. The target square is determined via winfo_containing() and the move is injected into the FreeSimpleGUI event loop via write_event_value(), where the existing move validation and execution code handles it. Agent-Logs-Url: https://github.com/fsmosca/Python-Easy-Chess-GUI/sessions/8a412d60-5a44-4b76-91b9-d11bd4e558c2 Co-authored-by: fsmosca <22366935+fsmosca@users.noreply.github.com> * Address code review feedback: optimize widget lookup and add debug logging Agent-Logs-Url: https://github.com/fsmosca/Python-Easy-Chess-GUI/sessions/8a412d60-5a44-4b76-91b9-d11bd4e558c2 Co-authored-by: fsmosca <22366935+fsmosca@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: fsmosca <22366935+fsmosca@users.noreply.github.com>
1 parent 0168241 commit 8541edb

1 file changed

Lines changed: 100 additions & 0 deletions

File tree

python_easy_chess_gui.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -779,6 +779,11 @@ def __init__(self, theme, engine_config_file, user_config_file,
779779
self.fen = None
780780
self.psg_board = None
781781
self.menu_elem = None
782+
783+
# Drag-and-drop state
784+
self._drag_source = None
785+
self._widget_to_square = {}
786+
self._drag_window = None
782787
self.engine_id_name_list = []
783788
self.engine_file_list = []
784789
self.username = 'Human'
@@ -897,6 +902,9 @@ def create_new_window(self, window, flip=False):
897902
self.update_labels_and_game_tags(w, human=self.username)
898903
break
899904

905+
# Re-bind drag-and-drop on the new window's board squares
906+
self.setup_board_drag_drop(w)
907+
900908
window.Close()
901909
return w
902910

@@ -1595,6 +1603,60 @@ def redraw_board(self, window):
15951603
elem.Update(button_color=('white', color),
15961604
image_filename=piece_image, )
15971605

1606+
def setup_board_drag_drop(self, window):
1607+
"""Bind drag-and-drop events to board square buttons.
1608+
1609+
After window finalization, this binds mouse press/release events
1610+
to each board square so that pieces can be moved by dragging.
1611+
"""
1612+
self._widget_to_square = {}
1613+
self._drag_window = window
1614+
for i in range(8):
1615+
for j in range(8):
1616+
elem = window.find_element(key=(i, j))
1617+
widget = elem.Widget
1618+
self._widget_to_square[widget] = (i, j)
1619+
widget.bind('<ButtonPress-1>',
1620+
self._on_drag_press, add='+')
1621+
widget.bind('<ButtonRelease-1>',
1622+
self._on_drag_release, add='+')
1623+
1624+
def _on_drag_press(self, event):
1625+
"""Record the source square when mouse is pressed on a board square."""
1626+
self._drag_source = self._widget_to_square.get(event.widget)
1627+
1628+
def _on_drag_release(self, event):
1629+
"""Handle mouse release for drag-and-drop moves.
1630+
1631+
Uses winfo_containing to find which board square the cursor is
1632+
over when the mouse button is released. If the target is a
1633+
different square from the source, injects a drag move event
1634+
into the FreeSimpleGUI event queue.
1635+
"""
1636+
if self._drag_source is None:
1637+
return
1638+
source = self._drag_source
1639+
self._drag_source = None
1640+
1641+
# Find the widget under the cursor at release position
1642+
target_widget = event.widget.winfo_containing(
1643+
event.x_root, event.y_root)
1644+
1645+
# Check directly first, then walk up the widget hierarchy
1646+
target_square = self._widget_to_square.get(target_widget)
1647+
if target_square is None:
1648+
w = getattr(target_widget, 'master', None) \
1649+
if target_widget is not None else None
1650+
while w is not None:
1651+
target_square = self._widget_to_square.get(w)
1652+
if target_square is not None:
1653+
break
1654+
w = getattr(w, 'master', None)
1655+
1656+
if target_square is not None and target_square != source:
1657+
self._drag_window.write_event_value(
1658+
'__drag_move__', (source, target_square))
1659+
15981660
def render_square(self, image, key, location):
15991661
""" Returns an RButton (Read Button) with image image """
16001662
if (location[0] + location[1]) % 2:
@@ -2148,6 +2210,41 @@ def play_game(self, window: sg.Window, board: chess.Board):
21482210
self.game.headers['FEN'] = self.fen
21492211
break
21502212

2213+
# Mode: Play, stm: User, handle drag-and-drop move
2214+
if button == '__drag_move__':
2215+
drag_from, drag_to = value['__drag_move__']
2216+
d_fr_row, d_fr_col = drag_from
2217+
d_piece = self.psg_board[d_fr_row][d_fr_col]
2218+
d_moved_piece = board.piece_type_at(
2219+
chess.square(d_fr_col, 7 - d_fr_row))
2220+
2221+
if d_piece != BLANK and d_moved_piece is not None:
2222+
# If a click-based move was in progress, restore
2223+
# the color of the previously selected square
2224+
if move_state == 1:
2225+
prev_color = self.sq_dark_color \
2226+
if (move_from[0] + move_from[1]) % 2 \
2227+
else self.sq_light_color
2228+
window.find_element(key=move_from).Update(
2229+
button_color=('white', prev_color))
2230+
2231+
# Set up state as if source square was clicked
2232+
move_from = drag_from
2233+
fr_row, fr_col = d_fr_row, d_fr_col
2234+
piece = d_piece
2235+
moved_piece = d_moved_piece
2236+
self.change_square_color(window, fr_row, fr_col)
2237+
move_state = 1
2238+
2239+
# Re-assign button to destination so existing
2240+
# move_state==1 code below handles execution
2241+
button = drag_to
2242+
else:
2243+
logging.debug(
2244+
'Drag from empty or inconsistent square '
2245+
'(%d, %d): psg_board=%s, piece_type=%s',
2246+
d_fr_row, d_fr_col, d_piece, d_moved_piece)
2247+
21512248
# Mode: Play, stm: User, user starts moving
21522249
if type(button) is tuple:
21532250
# If fr_sq button is pressed
@@ -3318,6 +3415,9 @@ def main_loop(self):
33183415
self.update_labels_and_game_tags(window, human=self.username)
33193416
break
33203417

3418+
# Set up drag-and-drop bindings for board squares
3419+
self.setup_board_drag_drop(window)
3420+
33213421
# Mode: Neutral, main loop starts here
33223422
while True:
33233423
button, value = window.Read(timeout=50)

0 commit comments

Comments
 (0)