赞
踩
完整的源码:Python GUI tkinter 开发连连看小游戏 源码
def window_center(self, width, height):
# 创建居中的窗口
screenwidth = self.windows.winfo_screenwidth() # 获取桌面屏幕的宽度
screenheight = self.windows.winfo_screenheight() # 获取桌面屏幕的高度
size = "%dx%d+%d+%d" % (
width, height, screenwidth / 2 - width / 2, screenheight / 2 - height / 2) # 宽x高+X轴位置+Y轴位置
self.windows.geometry(size)
注意:
tk.geometry() 对于这个方法,我们一般按照标准形式是"400x400+20+20"这样的参数,但是这里面的乘号是小写字母x不是X和*,也不是×。
同样也要求必须是整数,不能带小数点
两种菜单:
def add_components(self):
# 创建菜单
self.menubar = tk.Menu(self.windows, bg="lightgrey", fg="black")
self.file_menu = tk.Menu(self.menubar, bg="lightgrey", fg="black")
self.file_menu.add_command(label="新游戏", command=self.file_menu_clicked, accelerator="Ctrl+N")
self.menubar.add_cascade(label="游戏", menu=self.file_menu)
self.windows.configure(menu=self.menubar)
def play_music(self, music, volume=0.5):
pygame.mixer.music.load(music)
pygame.mixer.music.set_volume(volume)
pygame.mixer.music.play()
def stop_music(self):
pygame.mixer.music.stop()
def add_components(self):
# 创建背景画布的canvas
self.canvas = tk.Canvas(self.windows, bg="white", width=800, height=750)
self.canvas.pack()
def draw_background(self):
self.background_im = ImageTk.PhotoImage(file="images/bg.png")
self.canvas.create_image((0,0),anchor='nw', image=self.background_im) # 从0,0点开始 nw左上角对齐
分析一下图形,由行和列组成,各有10个小格,共有100个区域。
def init_map(self):
"""
初始化地图数组
0,1,2...24
:return:
"""
records = []
for i in range(0, self._iconCount):
for j in range(0, 4):
records.append(i)
np.random.shuffle(records) # 所有元素随机排序
self._map = np.array(records).reshape(10, 10)
游戏的背景图片,上面和左边都留有空白。取位置的至少需要考虑小图片的宽高和边框,更加横纵排位取坐标点。
class MainWindow: # 省略之前的代码 函数 。。。 # 以下新增 def getX(self, row): """ 获取row的X轴的起始坐标 :return: """ return self._margin + row * self._iconWidth def getY(self, column): """ 获取column的Y轴的起始坐标 :return: """ return self._margin + column * self._iconHeight def get_origin_Coordinate(self, row, column): """ 获取点位的左上角原点坐标 """ return self.getX(row), self.getY(column) def get_gamePoint(self, x, y): """ 获取玩家点击的x,y坐标在游戏地图上的点位 :param x: :param y: :return: """ for row in range(0, self._gameSize): x1 = self.getX(row) x2 = self.getX(row + 1) if x1 <= x < x2: point_row = row for column in range(0, self._gameSize): j1 = self.getY(column) j2 = self.getY(column + 1) if j1 <= y < j2: point_column = column return Point(point_row,point_column) class Point: # 游戏中的点位 def __init__(self,row,column): self.row=row self.column = column def isEqual(self, point): if self.row == point.row and self.column == point.column: return True else: return False
把这些水果和蔬菜的小图片,提取出来
第一张图片,0,0 右下角 w,h
第二张图片,w,0 右下角 2w,h
class MainWindow:
# 省略之前的代码 函数 。。。
# 以下新增
def extractSmalllconList(self):
# 提取小图片素材到icons列表中
image_source = Image.open("images/fruits.png")
for index in range(0,self._iconCount):
# 裁剪图片,指定图片的左上角和右下角
region = image_source.crop((index*self._iconWidth,0,(index+1)*self._iconWidth,self._iconHeight))
self._icons.append(ImageTk.PhotoImage(region))
当前是一个地图,10*10格子总共100个,每个位置称为一个点位。
每个各自左上的原点,是小图标开始的位置,再把上面切割好的图片,根据原点位置放入格子中,形成一个图像。
在根据随机函数,把每个小图标随机的放置在这100个格子中,绘制出地图。
def __init__(self): # 添加新的代码 # 准备小图标的图片 self.extractSmalllconList() def file_menu_clicked(self): self.stop_music() self.init_map() self.draw_map() # 把绘制地图放在菜单点击事件中 def draw_map(self): # 根据地图绘制小图标 for row in range(0,self._gameSize): for column in range(0, self._gameSize): x,y = self.get_origin_Coordinate(row, column) self.canvas.create_image((x,y), image=self._icons[self._map[row][column]], anchor='nw')
audio放入音效
class MainWindow(): # 省略之前的代码 函数 。。。 # 以下新增 _isFirst = True # 第一次点击小头像 _isGameStart = False # 游戏是否开始 NONE_LINK = 0 # 不连通 LINK_LINK = 1 # 连通 NEIGHBOR_LINK = 10 # 相邻连通 EMPTY = -1 def addComponents(self): # 省略之前的代码 函数 。。。 # 以下新增 # 添加绑定事件 self.canvas.bind('<Button-1>', self.clickCanvas) # 绑定鼠标左键 self.canvas.bind('<Button-2>', self.eggClickCanvas) # 鼠标中键 def clickCanvas(self, event): if self._isGameStart: point = self.getGamePoint(event.x, event.y) if self._isFirst: print('第一次点击') self.playMusic('audio/click1.mp3') self._isFirst = False self.drawSelectedArea(point) # 选择的点位标红框 self._formerPoint = point else: print('第二次点击') self.playMusic('audio/click2.mp3') if point.isEqual(self._formerPoint): print('两次点击的点位相同') self.canvas.delete('rectRedOne') # 删除红框 self._isFirst = True else: print('两次点击的点位不同') type = self.getLinkType(self._formerPoint, point) if type['type'] != self.NONE_LINK: self.clearLinkedBlocks(self._formerPoint, point) self.canvas.delete('rectRedOne') self._isFirst = True def drawSelectedArea(self, point): """选择的点位标红框""" lt_x, lt_y = self.getOriginCoordinate(point.row, point.column) # 左上角 rb_x, rb_y = self.getOriginCoordinate(point.row + 1, point.column + 1) # 右下角,下一行下一列格子的左上角位置 self.canvas.create_rectangle(lt_x, lt_y, rb_x, rb_y, outline='red', tags='rectRedOne') def getLinkType(self, p1, p2): """取得两个点位的连通情况""" if self._map[p1.row][p1.column] != self._map[p2.row][p2.column]: return {'type': self.NONE_LINK} if self.isNeighbor(p1, p2): print('两个小头像是相邻连通') return {'type': self.NEIGHBOR_LINK} def isNeighbor(self, p1, p2): """判断两个点位是否相邻""" # 垂直方向 if p1.column == p2.column: # 大小判断 if p2.row < p1.row: if p2.row + 1 == p1.row: return True else: if p1.row + 1 == p2.row: return True # 水平方向 if p1.row == p2.row: # 大小判断 if p2.column < p1.column: if p2.column + 1 == p1.column: return True else: if p1.column + 1 == p2.column: return True return False def clearLinkedBlocks(self, p1, p2): """消除两个点位的小头像""" print('消除选中的两个点位上的小头像') self.canvas.delete('image%d%d' % (p1.row, p1.column)) self.canvas.delete('image%d%d' % (p2.row, p2.column)) self._map[p1.row][p1.column] = self.EMPTY self._map[p2.row][p2.column] = self.EMPTY self.playMusic('audio/link.mp3')
第一次与第二次点击,判断从点位小的向点位大的,逐步平移判断是否存在,空的就继续平移,能达到第二次点击位置就判断相连。垂直方向同理。
P1和P2两个位置存在一个拐角的时候,取P1的行和P2的列的交点,交点为P3,判断P1与P3是否水平直连,P2与P3是否垂直直连,满足这两个条件,就判断P1与P2相连。
需要一种算法,专门解决多角情况。
class MainWindow(): # 省略之前的代码 函数 。。。 # 以下新增 LINE_LINK = 11 # 直线相连 def isStraightLink(self, p1, p2): """判断两个点位是否直线相连""" # 水平方向判断 if p1.row == p2.row: if p1.column > p2.column: # 找小的 start = p2.column end = p1.column else: start = p1.column end = p2.column for column in range(start + 1, end): if self._map[p1.row][column] != self.EMPTY: # p1.row 行 一样的 return False return True # 垂直方向判断 elif p1.column == p2.column: if p1.row > p2.row: # 找小的 start = p2.row end = p1.row else: start = p1.row end = p2.row for row in range(start + 1, end): if self._map[row][p1.column] != self.EMPTY: # p1.column列 一样的 return False return True def getLinkType(self, p1, p2): """取得两个点位的连通情况""" if self._map[p1.row][p1.column] != self._map[p2.row][p2.column]: return {'type': self.NONE_LINK} if self.isNeighbor(p1, p2): print('两个小头像是相邻连通') return {'type': self.NEIGHBOR_LINK} elif self.isStraightLink(p1, p2): # 写的是这个函数 直连算法 print('两个小头像是直线相连') return {'type': self.LINE_LINK}
P1与P2之间,找一个交叉点P3
会出现两个点,有一个满足就可以!
def isEmptyInMap(self, point): """判断一个点位是否为空""" if self._map[point.row][point.column] == self.EMPTY: return True else: return False def isOneCornerLink(self, p1, p2): """一个角相连算法""" pointCorner = Point(p1.row, p2.column) if self.isEmptyInMap(pointCorner) and self.isStraightLink(p1, pointCorner) and self.isStraightLink(p2, pointCorner): return pointCorner pointCorner = Point(p2.row, p1.column) if self.isEmptyInMap(pointCorner) and self.isStraightLink(p1, pointCorner) and self.isStraightLink(p2, pointCorner): return pointCorner return False def getLinkType(self, p1, p2): """取得两个点位的连通情况""" if self._map[p1.row][p1.column] != self._map[p2.row][p2.column]: return {'type': self.NONE_LINK} if self.isNeighbor(p1, p2): print('两个小头像是相邻连通') return {'type': self.NEIGHBOR_LINK} elif self.isStraightLink(p1, p2): print('两个小头像是直线相连') return {'type': self.LINE_LINK} elif self.isOneCornerLink(p1, p2): # 添加 一个角相连算法 return {'type': self.ONE_LINK}
def isTwoCornerLink(self, p1, p2): """两个角相连算法""" # 水平方向判断 for column in range(0, self._gameSize): if column == p1.column or column == p2.column: continue pointCorner1 = Point(p1.row, column) pointCorner2 = Point(p2.row, column) if self.isStraightLink(p1, pointCorner1) \ and self.isStraightLink(pointCorner1, pointCorner2) \ and self.isStraightLink(pointCorner2, p2) \ and self.isEmptyInMap(pointCorner1) \ and self.isEmptyInMap(pointCorner2): return {'p1': pointCorner1, 'p2': pointCorner2} # 垂直方向判断 for row in range(0, self._gameSize): if row == p1.row or row == p2.row: continue pointCorner1 = Point(row, p1.column) pointCorner2 = Point(row, p2.column) if self.isStraightLink(p1, pointCorner1) \ and self.isStraightLink(pointCorner1, pointCorner2) \ and self.isStraightLink(pointCorner2, p2) \ and self.isEmptyInMap(pointCorner1) \ and self.isEmptyInMap(pointCorner2): return {'p1': pointCorner1, 'p2': pointCorner2} return False def getLinkType(self, p1, p2): """取得两个点位的连通情况""" if self._map[p1.row][p1.column] != self._map[p2.row][p2.column]: return {'type': self.NONE_LINK} if self.isNeighbor(p1, p2): print('两个小头像是相邻连通') return {'type': self.NEIGHBOR_LINK} elif self.isStraightLink(p1, p2): print('两个小头像是直线相连') return {'type': self.LINE_LINK} elif self.isOneCornerLink(p1, p2): return {'type': self.ONE_LINK} elif self.isTwoCornerLink(p1, p2): # 两个角相连算法 return {'type': self.TWO_LINK}
Astar算法原理:
# -!- coding:utf-8 -!- class Point: """游戏中的点位""" def __init__(self, row, column): self.row = row self.column = column def isEqual(self, point): if self.row == point.row and self.column == point.column: return True else: return False class Node: """节点""" def __init__(self, point: Point, endPoint: Point): """ 初始化 :param point: 点位 :param endPoint: 终止点位 """ self.point = point # 点位 self.father = None # 父节点 self.g = 0 # 到起点的步数 self.h = abs(endPoint.row - point.row) + abs(endPoint.column - point.column) # 到终止节点的估算步数 class AStar: """ A星算法 """ def __init__(self, map, startNode: Node, endNode: Node, passTag): """ 初始化函数 :param map:地图 :param startNode:开始节点 :param endNode: 终止节点 :param passTag: 可行走标记 """ self.openList = [] # 待探索节点列表 self.closeList = [] # 已探索节点列表 self.map = map # 地图 self.startNode = startNode # 开始节点 self.endNode = endNode # 终止节点 self.passTag = passTag # 可行走标记 def findMinFNode(self): """ 查找代价最低节点 :return: Node """ oneNode = self.openList[0] for node in self.openList: if node.g + node.h < oneNode.g + oneNode.h: oneNode = node return oneNode def nodeInCloseList(self, nearNode: Node): """ 节点是否在close list中 :param nearNode: 待判断的节点 :return: Node """ for node in self.closeList: if node.point.isEqual(nearNode.point): return node return False def nodeInOpenList(self, nearNode: Node): """ 节点是否在open list中 :param nearNode: 待判断的节点 :return: Node """ for node in self.openList: if node.point.isEqual(nearNode.point): return node return False def searchNearNode(self, minFNode: Node, offsetX, offsetY): """ 查找邻居节点 :param minFNode: 最小代价节点 :param offsetX: X轴偏移量 :param offsetY: Y轴偏移量 :return: Node 或 None """ nearPoint = Point(minFNode.point.row + offsetX, minFNode.point.column + offsetY) nearNode = Node(nearPoint, self.endNode.point) # 越界检查 if nearNode.point.row < 0 or nearNode.point.column < 0 or nearNode.point.row > len( self.map) - 1 or nearNode.point.column > len(self.map[0]) - 1: print('越界') return # 障碍检查 if self.map[nearNode.point.row][nearNode.point.column] != self.passTag and not nearNode.point.isEqual( self.endNode.point): print('障碍') return # 判断是否在close list中 if self.nodeInCloseList(nearNode): print('在close list中') return print("找到可行节点") if not self.nodeInCloseList(nearNode): self.openList.append(nearNode) nearNode.father = minFNode # 计算g值 step = 1 node = nearNode while node.point.isEqual(self.startNode.point): step += 1 node = node.father nearNode.g = step return nearNode def start(self): if self.map[self.endNode.point.row][self.endNode.point.column] == self.passTag: return print("起点: ", self.startNode.point.column, self.startNode.point.row) print("终点: ", self.endNode.point.column, self.endNode.point.row) # 1.将起点加入open list self.openList.append(self.startNode) while True: # 2.从open list中查找代价最低的节点 minFNode = self.findMinFNode() # 3.从open list中移除,并加入close list self.openList.remove(minFNode) self.closeList.append(minFNode) # 4.查找四个邻居节点 self.searchNearNode(minFNode, 0, -1) # 向上查找 self.searchNearNode(minFNode, 1, 0) # 向右查找 self.searchNearNode(minFNode, 0, 1) # 向下查找 self.searchNearNode(minFNode, -1, 0) # 向左查找 # 5.判断是否终止 endNode = self.nodeInCloseList(self.endNode) if endNode: print('两个节点是连通的') path = [] node = endNode while not node.point.isEqual(self.startNode.point): path.append(node) if node.father: node = node.father path.reverse() # 逆向排序 得到起点到终点的路径 return path if len(self.openList) == 0: print('两个节点不连通') return None
def getLinkTypeAStar(self, p1, p2): """通过A星算法取得两个点位的连通情况""" if self._map[p1.row][p1.column] != self._map[p2.row][p2.column]: return {'type': self.NONE_LINK} startNode = Node(p1, p2) endNode = Node(p2, p2) pathList = AStar(self._map, startNode, endNode, self.EMPTY).start() if pathList: return {'type': self.LINK_LINK} else: return {'type': self.NONE_LINK} def clickCanvas(self, event): if self._isGameStart: point = self.getGamePoint(event.x, event.y) if self._isFirst: print('第一次点击') self.playMusic('audio/click1.mp3') self._isFirst = False self.drawSelectedArea(point) self._formerPoint = point else: print('第二次点击') self.playMusic('audio/click2.mp3') if point.isEqual(self._formerPoint): print('两次点击的点位相同') self.canvas.delete('rectRedOne') self._isFirst = True else: print('两次点击的点位不同') type = self.getLinkTypeAStar(self._formerPoint, point) # 修改成AStar算法 if type['type'] != self.NONE_LINK: self.clearLinkedBlocks(self._formerPoint, point) self.canvas.delete('rectRedOne') self._isFirst = True
点击鼠标中键,直接删除一个小图标
def clearOneBlock(self, p1): """消除玩家点击的小头像""" print('消除选中的一个点位上的小头像') self.canvas.delete('image%d%d' % (p1.row, p1.column)) self._map[p1.row][p1.column] = self.EMPTY def eggClickCanvas(self, event): """彩蛋功能""" if self._isGameStart: point = self.getGamePoint(event.x, event.y) self.clearOneBlock(point) def addComponents(self): # 创建菜单 # 省略之前的代码 函数 。。。 # 以下新增 self.canvas.bind('<Button-2>', self.eggClickCanvas) # 绑定事件 鼠标中键
def drawPathPoint(self, point): """路径点位标绿框""" lt_x, lt_y = self.getOriginCoordinate(point.row, point.column) rb_x, rb_y = self.getOriginCoordinate(point.row + 1, point.column + 1) self.canvas.create_rectangle(lt_x, lt_y, rb_x, rb_y, outline='green', tags='path%d%d' % (point.row, point.column)) def getLinkTypeAStar(self, p1, p2): """通过A星算法取得两个点位的连通情况""" if self._map[p1.row][p1.column] != self._map[p2.row][p2.column]: return {'type': self.NONE_LINK} startNode = Node(p1, p2) endNode = Node(p2, p2) pathList = AStar(self._map, startNode, endNode, self.EMPTY).start() if pathList: # 绘制路径 for node in pathList: self.drawPathPoint(node.point) # 添加 路径点位标绿框 return {'type': self.LINK_LINK} else: return {'type': self.NONE_LINK}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。