如何从坐标数组识别正方形?PyQt5网格绘图功能实现遇阻求助
我来帮你一步步拆解解决这两个核心问题——先搞定PyQt5里点击网格时的位置识别与绘制时机问题,再实现从坐标数组里检测正方形的逻辑。
一、解决PyQt5点击网格与绘制时机/位置的识别问题
你目前的核心问题应该是没有正确绑定鼠标点击事件与Qt的绘制流程,Qt的绘制系统有严格的触发机制,直接随意调用QPainter会导致画面异常。这里给你一套可落地的实现方案:
正确捕获网格内的点击坐标
重写你的画布类的mousePressEvent方法,把屏幕像素坐标转换为网格逻辑坐标(比如每个网格单元固定50x50像素),再存入对应玩家的点击列表。同时要记得触发重绘:from PyQt5.QtWidgets import QWidget from PyQt5.QtGui import QPainter, QPen from PyQt5.QtCore import Qt class GridCanvas(QWidget): def __init__(self): super().__init__() self.grid_cell_size = 50 # 单个网格单元的像素尺寸 # 初始化玩家的点击数据 self.clicked0 = [] self.distlist0 = [] self.distdict0 = {} self.clicked1 = [] self.distlist1 = [] self.distdict1 = {} self.current_player = 0 # 当前操作的玩家标识 def mousePressEvent(self, event): # 将屏幕坐标转换为网格单元的左上角坐标(对齐网格) grid_x = (event.x() // self.grid_cell_size) * self.grid_cell_size grid_y = (event.y() // self.grid_cell_size) * self.grid_cell_size click_point = (grid_x, grid_y) # 根据当前玩家存入对应列表(避免重复点击同一个点) target_clicked = self.clicked0 if self.current_player == 0 else self.clicked1 if click_point not in target_clicked: target_clicked.append(click_point) # 触发重绘——这是Qt绘制的核心,所有绘制必须在paintEvent中执行 self.update()在paintEvent中完成绘制与距离计算
所有绘制操作都要放在paintEvent方法里,update()会自动触发这个方法。这里可以同时完成网格绘制、点击点标记,以及两点间距离的计算与存储:def paintEvent(self, event): painter = QPainter(self) # 1. 绘制网格背景 painter.setPen(QPen(Qt.gray, 1)) for x in range(0, self.width(), self.grid_cell_size): painter.drawLine(x, 0, x, self.height()) for y in range(0, self.height(), self.grid_cell_size): painter.drawLine(0, y, self.width(), y) # 2. 处理玩家0的点击点与距离数据 self._draw_player_data(painter, self.clicked0, self.distlist0, self.distdict0, Qt.blue) # 3. 处理玩家1的点击点与距离数据 self._draw_player_data(painter, self.clicked1, self.distlist1, self.distdict1, Qt.red) def _draw_player_data(self, painter, clicked_list, dist_list, dist_dict, color): # 绘制玩家的点击标记点 painter.setPen(QPen(color, 3)) for point in clicked_list: # 在网格单元中心画个小圆标记 painter.drawEllipse(point[0]+15, point[1]+15, 20, 20) # 计算并更新两点间距离列表与字典 dist_list.clear() dist_dict.clear() if len(clicked_list) >= 2: for i in range(len(clicked_list)): for j in range(i+1, len(clicked_list)): p1, p2 = clicked_list[i], clicked_list[j] # 用距离平方代替实际距离,避免浮点精度问题 dist_sq = (p1[0]-p2[0])**2 + (p1[1]-p2[1])**2 dist_list.append(dist_sq) dist_dict[dist_sq] = (p1, p2)这里要重点强调:绝对不要在mousePressEvent或其他非paintEvent方法里直接调用QPainter绘制,Qt的绘制上下文只有在paintEvent中才是有效的,直接绘制会导致画面刷新紊乱。
二、从坐标数组中识别正方形的算法
正方形的核心几何特征可以转化为两点间距离的规律:4个点之间会产生6组距离,其中4组是边长(长度相同),2组是对角线(长度相同,且是边长的√2倍)。基于这个特征,我们可以用距离平方来避免浮点误差,实现高效识别:
from itertools import combinations def is_valid_square(points): """判断给定的4个点是否能构成正方形""" if len(points) != 4: return False # 计算所有两点间的距离平方 dist_sq_list = [] for i in range(4): for j in range(i+1, 4): dx = points[i][0] - points[j][0] dy = points[i][1] - points[j][1] dist_sq = dx * dx + dy * dy dist_sq_list.append(dist_sq) # 去重后必须只有两种距离,且不能有0(排除重复点) unique_dists = list(set(dist_sq_list)) if len(unique_dists) != 2 or 0 in unique_dists: return False # 验证对角线平方是边长平方的2倍 min_dist, max_dist = sorted(unique_dists) if max_dist != 2 * min_dist: return False # 验证边长出现4次,对角线出现2次 return dist_sq_list.count(min_dist) == 4 and dist_sq_list.count(max_dist) == 2 def find_all_squares(points): """从给定的点列表中找出所有能构成正方形的4点组合""" squares = [] # 生成所有4点的组合并逐一验证 for combo in combinations(points, 4): if is_valid_square(combo): squares.append(combo) return squares
额外优化(针对网格场景)
如果你的点击点都是网格单元的顶点(坐标是网格尺寸的整数倍),还可以用更高效的判断逻辑:
- 4个点的x坐标必须恰好有两个不同的值,y坐标也恰好有两个不同的值
- 两个x值的差的绝对值等于两个y值的差的绝对值(保证是正方形而非长方形)
这样可以跳过距离计算,直接通过坐标特征判断,效率更高。
内容的提问来源于stack exchange,提问作者user9727520
相关产品推荐
相关产品推荐

