完整的源码: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('' , self.clickCanvas) # 绑定鼠标左键
self.canvas.bind('' , 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('' , 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}