首页 > 解决方案 > 使特定矩形内的图像居中

问题描述

我正在使用 tkinter 模块制作一个有点幼稚的国际象棋游戏版本。我已经使用 Canvas 成功地“绘制”了一块棋盘,并将这些棋子放在它们的起始格上。我已经编写了允许棋子在棋盘上自由移动的函数,但我还没有处理确定合法移动和其他游戏规则的更复杂的逻辑。基本上,棋盘由 64 个交替颜色的“矩形”对象组成(如棋盘),并且棋子已创建为图像对象(使用 PIL 从 .png 文件处理)。

我想做的是,当一块被拾起并放置在这 64 个矩形之一中时,该块会自动在最接近其中心的矩形内居中。我的想法是创建一个函数,将工件重新定位在该中心坐标上,但我不确定如何用我的代码准确定位该坐标。我在想我可能会使用 .find_closest() 方法,但是当我单击它们时,它似乎没有注册矩形(但它在单击时确实注册了片段)。

import tkinter as tk
from PIL import Image, ImageTk
class Chessboard(tk.Frame):

def __init__(self):
    tk.Frame.__init__(self)
    self.pack()
    self.master.title('Chess')
    self.move_data = {'x': 0, 'y': 0, 'piece': None}
    self.draw_board(master)
    self.place_pieces(self.board)

def place_pieces(self,board):
    for r in range (7,5,-1):
        for c in range(8):
            x0 = c*80+24
            y0 = r*80+24
            x1 = c*80+104
            y1 = r*80+104
            center = ((x0+x1)/2, (y0+y1)/2)
            image = pieces[(r,c)][0]
            self.create_piece(center, image, ('piece', pieces[(r,c)][1]))
            self.board.tag_bind(pieces[(r,c)][1], '<ButtonPress-1>', self.choose_piece)
            self.board.tag_bind(pieces[(r,c)][1], '<ButtonRelease-1>', self.release_piece)
            self.board.tag_bind(pieces[(r,c)][1], '<B1-Motion>', self.move_piece)
    for r in range (1,-1,-1):
        for c in range(8):
            x0 = c*80+24
            y0 = r*80+24
            x1 = c*80+104
            y1 = r*80+104
            center = ((x0+x1)/2, (y0+y1)/2)
            image = pieces[(r,c)][0]
            self.create_piece(center, image, ('piece', pieces[(r,c)][1]))
            self.board.tag_bind(pieces[(r,c)][1], '<ButtonPress-1>', self.choose_piece)
            self.board.tag_bind(pieces[(r,c)][1], '<ButtonRelease-1>', self.release_piece)
            self.board.tag_bind(pieces[(r,c)][1], '<B1-Motion>', self.move_piece)

def draw_board(self, master):

    self.board = tk.Canvas(master, width = 680, height = 680, borderwidth = 20)
    self.board.pack(fill='both', expand=True)
    for r in range(7, -1, -1):
        for c in range(8):
            if c&1 ^ r&1:
                fill = 'sienna2'

            else:
                fill = 'wheat1'
            x0 = c*80+24
            y0 = r*80+24
            x1 = c*80+104
            y1 = r*80+104
            coords = (x0, y0, x1, y1)
            center = ((x0+x1)/2, (y0+y1)/2)
            sq = files[c] + ranks[-r-1]
            self.board.create_rectangle(coords, fill=fill, width = 0, tags = ('square', sq))


def create_piece(self, coord, image, tags):
    self.board.create_image(coord, image=image, tags = tags)


def choose_piece(self, event):
    self.move_data['piece'] = self.board.find_closest(event.x, event.y)[0]
    self.move_data['x'] = event.x
    self.move_data['y'] = event.y



def release_piece(self, event):


    self.move_data['piece'] = None
    self.move_data['x'] = 0
    self.move_data['y'] = 0



def move_piece(self, event):
    dx = event.x - self.move_data['x']
    dy = event.y - self.move_data['y']
    self.board.move(self.move_data['piece'], dx, dy)
    self.move_data['x'] = event.x
    self.move_data['y'] = event.y

标签: pythontkintertkinter-canvas

解决方案


find_closest在拖放时无法找到棋盘格,因为画布上最近的对象将是您正在拖动的对象。

相反,找到被拖动对象的中心,并使用find_overlapping获取与该坐标重叠的所有对象的列表。然后,使用您想要的任何算法,在返回的列表中选择一项。例如,您可能总是选择第一个,或者总是选​​择最后一个。如果棋盘在堆叠顺序中较低,则列表中的最后一项将是被移动的棋子。

除非您在屏幕上放置大量重叠的对象,find_overlapping否则通常会返回两个项目:正在移动的项目和下方的正方形。但是,您需要为用户将其精确放置在四个正方形的交叉点或两个正方形之间的边界上的情况做好准备。

从那里,只需一点数学即可计算出正方形的中心和被移动对象的中心,然后移动对象以使它们具有相同的中心。

首先,定义一个函数来返回对象的中心:

def _get_center(self, item):
    coords = self.board.coords(item)
    cx = int((coords[2]+coords[0])/2)
    cy = int((coords[3]+coords[1])/2)
    return (cx,cy)

接下来,定义一个函数以将一个对象居中于另一个对象上:

def align(self, item1, item2):
    x0, y0 = self._get_center(item1)
    x1, y1 = self._get_center(item2)
    deltax = x1 - x0
    deltay = y1 - y0
    self.board.move(item1, deltax, deltay)

最后,在处理拖放的代码中,找到画布上重叠的对象,然后使用该信息对齐对象。

在以下示例中,代码获取由find_overlapping. 你可能想要一个更好的算法。例如,您可能有一个函数返回一个棋子的可能移动,然后使用出现在允许移动列表中的第一个重叠项。

cx, cy = self._get_center("current")
nearest = self.board.find_overlapping(cx, cy, cx, cy)[0]
self.align("current", nearest)

注意:"current"是一个特殊的画布标签,它指的是光标下最上面的项目。那应该是你拖的东西。在您的情况下,您可以self.move_data['piece']改用。


推荐阅读