This article mainly introduces the graduation thesis about python games, which has certain reference value. Friends in need can refer to it. I hope you will gain a lot after reading this article. Let the editor take you to understand it together.
Complete source code: Python GUI tkinter development Lianliankan mini game source code
Three elements of the game
map
- The map background is a 10*10 grid
- Each square is randomly filled with a vegetable or fruit
Sound effects
- Background music
- Music for mouse click on vegetables or fruits
game rules
- Click two squares in succession
- If the pictures in the squares are the same and can be connected, the two pictures will be eliminated.
- The game ends when all the pictures in the squares are eliminated.
Build game window
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)
Note: For this method of
tk.geometry() , we generally follow the standard form of parameters such as "400x400+20+20", but the multiplication sign here is the lowercase letter x, not X and *, nor ×.
It is also required that it must be an integer without a decimal point.
Add menu
Two menus:
- drop-down menu
- Pop-up menu
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)
Add background music
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()
Add game background canvas
- Define canvas
- Draw pictures using canvas
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左上角对齐
Design game map
Analyze the graph, which consists of rows and columns, each with 10 cells, and a total of 100 areas. How to use the python for statement .
Array implementation
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)
The relationship between points and coordinates
The game's background image, with space above and to the left. When choosing the position, you need to consider at least the width, height and border of the small picture, and even more horizontally and vertically when choosing the coordinate points.
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
Extract game material icons
Extract these small pictures of fruits and vegetables
Idea analysis
The first picture, 0,0, w,h in the lower right corner.
The second picture, w,0, 2w,h, in the lower right corner.
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))
Analysis of small icon drawing ideas
It is currently a map with a total of 100 10*10 grids, and each position is called a point.
The origin on the upper left side of each one is the starting position of the small icon. Then put the cut picture above into the grid according to the origin position to form an image.
According to the random function, each small icon is randomly placed in the 100 grids to draw a map.
Image plotted on map
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')
Add game actions and eliminate small icons
- Add click sound effects
- After clicking the small icon, there will be a red border indicating the selected status.
- When clicking again at the same position as the first click, uncheck the state
- If you click again on a position other than the first click, it will be judged whether the pictures are the same. If they are the same, it will be judged whether they are connected. Connectivity is eliminated, not the unchecked state of connectivity.
- If they are not the same picture, uncheck the status.
audioput sound effects
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')
Game play rules analysis
1. Adjacent and connected
2. Connected by straight lines
After the first and second click, it is judged from the small point to the big point, and gradually pans to determine whether it exists. If it is empty, it continues to pan. If it reaches the second click position, it is judged to be connected. The same goes for the vertical direction.
3. Connection of one corner
When there is a corner between P1 and P2, take the intersection point of the row of P1 and the column of P2. The intersection point is P3. Determine whether P1 and P3 are directly connected horizontally and whether P2 and P3 are directly connected vertically. If these two conditions are met, Just judge that P1 and P2 are connected.
4. Two corners connected
- The positions of the two icons P1, P2
- Find two positions P3 and P4, and see whether P1 and P3 are directly connected, whether P2 and P4 are directly connected, and whether P3 and P4 are directly connected. If three conditions are met, P1 and P2 are directly connected.
- Based on this idea, find the positions of P3 and P4. Start traversing to the right from (0,3), when an empty point is encountered, create P3, start a sub-loop, start traversing to the right from (0,6), find an empty point and create P4, according to the direct connection logic, determine whether P3 and P4 connect. When there is no P3, P4 connection, the cycle continues.
5. Three corners connected
6. More corners connected
An algorithm is needed to specifically solve the multi-angle case.
Direct connection algorithm implementation
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}
Implementation of an angle connection algorithm
Between P1 and P2, find an intersection point P3 and
there will be two points. One of them is enough!
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}
Implementation of algorithm for connecting two corners
- When point P1 is connected to point P3, point P2 is connected to point P4, and point P3 is connected to point P4, it is determined that P1 is connected to P2.
- Draw two lines, (0,3) is regarded as P3, (0,6) is regarded as P4, P3 starts to be traversed one by one, the points with icons are skipped, and the empty position point is found. At this time, P1 and P3 are connected with a horizontal straight line; start traversing P4, use the same method to find a straight line connecting P2 and P4, find a straight line connecting P4 and P3 in the vertical direction, and judge that P1 is connected to P2.
- Until the end of the traversal, if there are no connected two points, it is judged that they are not connected.
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}
Optimized connectivity judgment algorithm
Astar algorithm principle:
Astar algorithm steps
- Add starting point A to the open list (open list list to be checked)
- Check the adjacent nodes of starting point A and add the traversable nodes to the open list.
- Move A from open list to close list (close list closes the list and then searches for the node and no longer pays attention)
- Find the node with the lowest cost from the open list. Cost: distance from the starting point to the current node + distance from the end of the current node. Distance from the starting point
to the current node: the number of steps that have been taken.
Distance from the end of the current node: the estimated number of steps. - Check whether the nodes adjacent to the lowest cost node are feasible
- Repeat the above two steps until the end
. End condition:
- The termination node is added to the close list
- open list is empty
Astar algorithm implementation
# -!- 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
Call the Astar algorithm
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
Add easter eggs to facilitate testing
Click the middle mouse button to delete a small icon directly
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) # 绑定事件 鼠标中键
Draw the shortest path and eliminate boxes
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}